文章内容根据野火学习教程进行整理,仅仅是学习记录。
开发板: 野火STM32F429-挑战者V2
官方固件库版本: STM32F4xx_DSP_StdPeriph_Lib_V1.8.0
一、选择USART
要配置USART要先选择要配置哪一个USART/UART。
我用的是STM32F429这个芯片,从 《STM32F4xx中文数据手册》 的 “表 8. USART 的特性比较” 和 “表 10. STM32F427xx 和 STM32F429xx 引脚和焊球定义 ” 可以得知:
- 该芯片支持四个USART和四个UART。
- USART1 和 USART6 的时钟来源于 APB2 总线时钟(最大频率为 90MHz)。
- USART2、USART3、UART4、UART5、UART7、UART8的时钟来源于 APB1 总线时钟(最大频率为 45MHz)。
- 具体的USART/UART引脚所接的GPIO口是哪个。
整理如下图所示(引用自教程)。
然后具体的还是要看开发板的电路设计图,看看有哪个USART/UART通过哪个GPIO口引出(或者直接用杜邦线连接相应的GPIO引脚)。
我这里用的是USART1,主要是要找到TX和RX接在哪个引脚。
二、开始编码
1、声明宏定义(主要是为了方便修改)
/******************************************************************************/
/* 局部宏定义 */
/******************************************************************************/
#define USART1_HANDLE USART1 /* USART1的句柄 */
#define USART1_CLK RCC_APB2Periph_USART1 /* USART1的外围时钟 */
#define USART1_BAUDRATE 115200 /* USART1的波特率 */
#define USART1_GPIO_PORT GPIOA /* USART1的端口 */
#define USART1_TX_PIN GPIO_Pin_9 /* USART1的GPIO发送引脚 */
#define USART1_RX_PIN GPIO_Pin_10 /* USART1的GPIO接收引脚 */
#define USART1_TX_SOURCE GPIO_PinSource9 /* USART1的GPIO发送引脚序号 */
#define USART1_RX_SOURCE GPIO_PinSource10 /* USART1的GPIO接收引脚序号 */
#define USART1_GPIO_AF GPIO_AF_USART1 /* USART1的GPIO引脚复用功能 */
#define DEBUG_USART_IRQ USART1_IRQn /* USART1的中断序号 */
2、配置GPIO
(1)使能GPIO时钟
我们已经知道了USART1的是接在了GPIOA端口下的引脚,所以要先使能GPIOA端口的时钟。
/* 初始化GPIO端口时钟 */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
(2)对RX和TX引脚的GPIO进行配置
/**
* @brief USART的GPIO配置
* @param 无
* @retval 无
*/
static void gpio_cfg()
{
/* 配置USART1的GPIO */
bsp_gpio_config(USART1_GPIO_PORT, USART1_TX_PIN | USART1_RX_PIN, GPIO_Mode_AF, GPIO_Speed_50MHz, GPIO_OType_PP, GPIO_PuPd_UP);
/* 连接 PXx 到 USARTx_Tx*/
GPIO_PinAFConfig(USART1_GPIO_PORT, USART1_RX_SOURCE, USART1_GPIO_AF);
/* 连接 PXx 到 USARTx_Rx*/
GPIO_PinAFConfig(USART1_GPIO_PORT, USART1_TX_SOURCE, USART1_GPIO_AF);
}
这里我对GPIO引脚的配置封装了一下(没什么更大的好处,只是习惯),bsp_gpio_config函数为:
/**
* @brief GPIO配置函数
* @param *GPIOx: 所属GPIO端口
* @param pin: 要配置的GPIO的PIN脚
* @param mode: 要配置的GPIO模式(输入/输出/复用/模拟)
* @param speed: 要配置的GPIO速率(2MHz/25MHz/50MHz/100MHz),输入模式不需要配置填 0
* @param otype: 输出类型(推挽/开漏),输入模式不需要配置填 0
* @param pupd: 引脚默认状态(上拉/下拉/浮空)
* @retval 无
*/
void bsp_gpio_config(GPIO_TypeDef* GPIOx, uint32_t pin, GPIOMode_TypeDef mode, GPIOSpeed_TypeDef speed, GPIOOType_TypeDef otype, GPIOPuPd_TypeDef pupd)
{
GPIO_InitTypeDef GPIO_def;
GPIO_def.GPIO_Pin = pin;
GPIO_def.GPIO_Mode = mode;
GPIO_def.GPIO_PuPd = pupd;
GPIO_def.GPIO_Speed = speed;
GPIO_def.GPIO_OType = otype;
GPIO_Init(GPIOx, &GPIO_def);
}
3、配置USART
(1)使能USART时钟
由第一部分可以知道USART1的时钟是来源于APB2的时钟。
查看 stm32f4xx_rcc.c 源码也可以找到USART1通过 RCC_APB2PeriphClockCmd函数来配置时钟。
/* USART1_CLK 为 RCC_APB2Periph_USART1,在宏定义定义了 */
RCC_APB2PeriphClockCmd(USART1_CLK, ENABLE); /* 使能USART时钟 */
(2)初始化USART配置(使能USART时钟之后,不然没反应)
USART_InitTypeDef USART_def;
USART_def.USART_BaudRate = USART1_BAUDRATE; /* 配置波特率 */
USART_def.USART_WordLength = USART_WordLength_8b; /* 配置字长为8(数据位+校验位) */
USART_def.USART_StopBits = USART_StopBits_1; /* 配置停止位为1个停止位 */
USART_def.USART_Parity = USART_Parity_No; /* 配置校验位为不使用校验 */
USART_def.USART_HardwareFlowControl = USART_HardwareFlowControl_None; /* 配置硬件流控制为不使用硬件流 */
USART_def.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; /* 配置USART模式控制为同时使能接收和发送 */
USART_Init(USART1_HANDLE, &USART_def); /* 完成USART初始化配置 */
(3)使能串口
/* USART1_HANDLE 为 USART1, 在宏定义定义了 */
USART_Cmd(USART1_HANDLE, ENABLE); /* 使能串口 */
4、启用printf、scanf、getchar等函数
这里重定向fputc、fgetc即可使用以上函数,是因为以上函数中最终对字符的读写使用的是fputc、fgetc这两个函数。所以把最终的操作改成串口的读写即可。
放哪里都行,我和USART配置相关的代码放在一个.c文件里面了。
/**
* @brief 重定向重写C函数库中的fputc函数,重定向后可使用printf函数
* @param ch:要输出的字节
* @param *f:无用
* @retval 无
*/
int fputc(int ch, FILE *f)
{
USART_SendData(USART1_HANDLE, (uint8_t)ch); /* 发送一个字节数据到串口 */
while (USART_GetFlagStatus(USART1_HANDLE, USART_FLAG_TXE) == RESET); /* 等待发送完毕 */
return (ch);
}
/**
* @brief 重定向重写C函数库中的fgetc函数,重写向后可使用scanf、getchar等函数
* @param *f:无用
* @retval 从串口读取到的一个字节
*/
int fgetc(FILE *f)
{
while (USART_GetFlagStatus(USART1_HANDLE, USART_FLAG_RXNE) == RESET); /* 等待串口输入数据 */
return (int)USART_ReceiveData(USART1_HANDLE);
}
5、也可以自定义发送函数
/**
* @brief 发送一个字节到串口
* @param *pUSARTx:要使用的USART句柄
* @param ch:要输出的字节
* @retval 无
*/
void usart_send_byte(USART_TypeDef *pUSARTx, uint8_t ch)
{
USART_SendData(pUSARTx, ch); /* 发送一个字节数据到USART */
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); /* 等待发送数据寄存器为空 */
}
/**
* @brief 发送两个字节到串口
* @param *pUSARTx:要使用的USART句柄
* @param ch:要输出的字节
* @retval 无
*/
void usart_send_half_word(USART_TypeDef *pUSARTx, uint16_t ch)
{
uint8_t temp_h, temp_l;
temp_h = (ch & 0XFF00) >> 8; /* 取出高八位 */
temp_l = ch & 0XFF; /* 取出低八位 */
USART_SendData(pUSARTx, temp_h); /* 发送高八位 */
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
USART_SendData(pUSARTx, temp_l); /* 发送低八位 */
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
/**
* @brief 发送一个字符串到串口
* @param *pUSARTx:要使用的USART句柄
* @param ch:要输出的字符串
* @retval 无
*/
void usart_send_string(USART_TypeDef *pUSARTx, char *str)
{
unsigned int k = 0;
do
{
usart_send_byte(pUSARTx, *(str + k));
k++;
} while(*(str + k) != '\0');
while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET); /* 等待发送完成 */
}
到这位置USART的配置基本完成了,可以实现的功能有
- 通过printf输出字符串到串口。
- 通过getchar、scanf读取串口输入的内容。
5、配置USART的中断。
(1)注意点
- 开启中断后一定要重写终端服务函数,否则会进入死循环。
- 开启中断后getchar、scanf函数不太正常了,没法正常使用。亲测如此但还是不太明白什么原因。
(2)使能USART接收中断
/* USART1_HANDLE 为 USART1, 在宏定义定义了 */
USART_ITConfig(USART1_HANDLE, USART_IT_RXNE, ENABLE); /* 使能串口接收中断 */
(3)配置NVIC(嵌套向量中断控制器)
先选择嵌套向量中断控制器组(如果已经配置过就不要重复了)
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); /* 嵌套向量中断控制器组选择 */
配置USART1的中断
/* USART1_IRQ 为 USART1_IRQn, 在宏定义定义了 */
/* USART1_IRQn 定义在 stm32f4xx.h */
nvic_config(USART1_IRQ, 1, 1, ENABLE); /* 中断配置 */
nvic_config 为自己封装的函数
/**
* @brief NVIC配置函数
* @param channel: 中断源
* @param preemption: 抢断优先级
* @param sub: 子优先级
* @param cmd: 使能状态
* @retval 无
**/
void nvic_config(uint8_t channel, uint8_t preemption, uint8_t sub, FunctionalState cmd)
{
NVIC_InitTypeDef nvic_def;
nvic_def.NVIC_IRQChannel = channel; /* 配置USART为中断源 */
nvic_def.NVIC_IRQChannelPreemptionPriority = preemption; /* 抢断优先级为1 */
nvic_def.NVIC_IRQChannelSubPriority = sub; /* 子优先级为1 */
nvic_def.NVIC_IRQChannelCmd = cmd; /* 使能中断 */
NVIC_Init(&nvic_def); /* 初始化配置NVIC */
}
(4)重写中断服务函数
打开中断后必须重写中断服务函数,否则会跳入死循环。
中断服务函数函数必须与启动文件(startup_stm32f429_439xx.s)中的中断服务函数函数名相同,否则会跳入死循环。
这里只实现了收到数据并再发出去的内容,如果不使用getchar、scanf之类的函数也可以自定义buff接收数据处理。
/**
* @brief 重写USART1的中断函数,与startup_stm32f429_439xx.s中向量表定义的函数名必须相同
* @param 无
* @retval 无
*/
void USART1_IRQHandler(void)
{
uint16_t ucTemp;
if(USART_GetITStatus(USART1_HANDLE, USART_IT_RXNE) != RESET) {
ucTemp = USART_ReceiveData(USART1_HANDLE);
USART_SendData(USART1_HANDLE, ucTemp);
}
}
hrx-@@
发布了64 篇原创文章 · 获赞 106 · 访问量 22万+
私信
关注