基于STM32的嵌入式软件开发(9)——USART串口通讯
本文介绍了STM32串口开发过程,基于标准固件库、CubeMx+HAL库的形式,介绍了USART的配置过程,包括查询、中断,以及scanf和printf函数的重定义,并分别辅以简单的示例代码在本文的介绍过程中,对于串口的电路原理、寄存器、总线电平等方面并未做详细介绍,因为那些背离了我写这篇专栏的初衷:不进行老学究式的详细研究,而是能够帮助同学们快速上手STM32的开发,时钟牢记程序员第一要义:能跑
一、引言
串口通讯是最常见的通讯方式,基本所有的MCU都会带有串口,由其引出了各种通信标准和通信协议,如232、485、Modbus、LIN等,是一种全双工串行通信,STM32的串口USART同时支持同步、异步通讯,(一般仅支持异步通信的串口名为UART)。
典型的异步串口通信使用三线制:Tx、Rx、GND。
二、基于固件库配置串口通讯
1. 硬件连接
STM32F407ZGT6 最多可提供 6 路串口,一般STM32的USART1均为PA9/PA10,

2. 常用寄存器
2.1 状态寄存器 (USART_SR)
常用的标志位有



2.2 数据寄存器 (USART_DR)
用来存放收发数据
其他的,比如串口内部电路架构、波特率寄存器、控制寄存器,感兴趣的同志们可自行研究。另外,关于串口通讯总线上的空闲位、停止位等,感兴趣也可自己查阅芯片手册。
3. 基于标准固件库STM32串口通讯
关于如何在Keil5工程中添加.c\.h文件,在前面几篇文章中已经多次提到,本章不再赘述。
3.1 中断接收
串口中断接收是一种常用的策略,基于固件库,使串口收发数据,具体初始化配置如下
static GPIO_InitTypeDef USART1_pin_init_str =
{
GPIO_Pin_9 | GPIO_Pin_10,
GPIO_Mode_AF,//复用模式
GPIO_Fast_Speed,
GPIO_OType_PP,
GPIO_PuPd_UP,
};
USART_InitTypeDef USART1_init_str =
{
115200,//波特率115200
USART_WordLength_8b,//8位数据位
USART_StopBits_1,//1位停止位
USART_Parity_No,//无奇偶校验
USART_Mode_Rx|USART_Mode_Tx,//RX和TX功能全部初始化
USART_HardwareFlowControl_None//无硬件流
};
NVIC_InitTypeDef USART1_IRQ_Init =
{
USART1_IRQn,//串口1中断事件
1,//抢占优先级为1
1,//响应优先级为1
ENABLE//使能中断事件
};
void USART1_Init(void)
{
/*enable clock for PA9&PA10*/
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
/*init PA9&PA10*/
GPIO_Init(GPIOA,&USART1_pin_init_str);
/*choose which periph to pin reuse */
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
/*enable clock for USART1*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
/*init usart1*/
USART_Init(USART1,&USART1_init_str);
/*Enable usart1*/
USART_Cmd(USART1,ENABLE);
/*enough configration if do not use interrupt*/
/*ENABLE USART1 Rx Interrupt: receive is not empty*/
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
NVIC_Init(&USART1_IRQ_Init);/*NVIC*/
}
配置比较简单,PA9、PA10复用为串口,并打开接收中断,抢占优先级和响应优先级均为1
中断处理函数如下:其目的是将接收到的数据原封不动的发送出去,而后清除中断标志位
/*interrupt function, all interrupt functions are in startup_stm32f40_41xxx.s, no return and input parameters*/
void USART1_IRQHandler(void)
{
uint16_t data = 0;
if (SET == USART_GetITStatus(USART1,USART_IT_RXNE))
{
data = USART_ReceiveData(USART1);
USART_SendData(USART1,data);
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
}
主函数中的代码如下
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
USART1_Init();
while (1)
{
/*do nothing*/
}
}
3.2 发送字符串
上文中,发送数据,调用的是USART_SendData(USART1,data);该函数每次仅能发送一个字节数据,通过循环调用该函数,便可以实现字符串的发送
static void USART_Str_Send(uint8_t *in_str,uint8_t len)
{
uint8_t i;
for ( i = 0; i < len; i++)
{
while (RESET == USART_GetFlagStatus(USART1,USART_FLAG_TC));
USART_SendData(USART1,in_str[i]);
}
}
上段代码判断TC,发送完成标志,如果该标志位为0,则盲等。
3.3 查询接收
如果不使用中断,通过不断查询RXNE位,也能实现串口接收
此时,串口初始化配置为3.1节中的配置删去中断的内容
static GPIO_InitTypeDef USART1_pin_init_str =
{
GPIO_Pin_9 | GPIO_Pin_10,
GPIO_Mode_AF,//复用模式
GPIO_Fast_Speed,
GPIO_OType_PP,
GPIO_PuPd_UP,
};
USART_InitTypeDef USART1_init_str =
{
115200,//波特率115200
USART_WordLength_8b,//8位数据位
USART_StopBits_1,//1位停止位
USART_Parity_No,//无奇偶校验
USART_Mode_Rx|USART_Mode_Tx,//RX和TX功能全部初始化
USART_HardwareFlowControl_None//无硬件流
};
void USART1_Init(void)
{
/*enable clock for PA9&PA10*/
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
/*init PA9&PA10*/
GPIO_Init(GPIOA,&USART1_pin_init_str);
/*choose which periph to pin reuse */
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
/*enable clock for USART1*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
/*init usart1*/
USART_Init(USART1,&USART1_init_str);
/*Enable usart1*/
USART_Cmd(USART1,ENABLE);
/*enough configration if do not use interrupt*/
}
main函数中的代码如下
int main(void)
{
uint8_t data;
USART1_Init();
while (1)
{
if (SET == USART_GetFlagStatus(USART1,USART_FLAG_RXNE))
{
data = USART_ReceiveData(USART1);
USART_SendData(USART1,data);
}
}
}
上述代码,在while(1)循环中不断查询RXNE标志位,一旦该标志位置1,意味着接收非空
3.4 usart文件夹
前文介绍了正点原子SYSTEM文件夹中所提供的sys、delay文件夹,现在介绍一下usart文件夹,该文件夹默认以PA9\PA10作为串口,进行串口初始化,并可以在usart.h文件中通过EN_USART1_RX宏定义打开或关闭接收中断的配置。其中的内容还是那个问均已经介绍过。值得关注的是,在usart.c文件中有如下重定义函数:
struct __FILE
{
int handle;
};
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}
该重定义基于一个C语言的一个原则:如果用户自己定义的函数与标准库中已有的函数冲突,则以用户自定义函数为准。上述函数中,USART1->SR&0X40其实判断的就是TC标志位
如此,便可使用printf函数实现字符串发送,如
printf("hello world!")
三、基于CubeMx配置USART
关于CubeMx如何新建工程,前文已经讲得很细致,读者若不理解,可查看专栏内往期文章。本文基于前文所建立的工程,进行串口配置添加
使用串口1,PA9(USART_TX), PA10(USART_RX),波特率115200
首先要配置GPIO引脚,CubeMx中引脚配置不知如何开始的请参照本专栏往期内容

串口USART1模块配置

生成代码,此时默认使用轮询方式进行输入输出
接收接口:HAL_UART_Receive() 发送接口:HAL_UART_Transmit()
以上两条函数均为固定长度数据接收\发送
此时,接收与发送的代码可写为
// Pollng USART1
if (HAL_OK == HAL_UART_Receive(&huart1,UART1_RecBuffer_Polling,7,100))
{
HAL_UART_Transmit(&huart1,UART1_RecBuffer_Polling,7,100);
}
上述代码实现功能为,将接收的数据存到UART1_RecBuffer_Polling中,然后进行发送。
利用重定向fputc/fgetc函数使用printf/scanf函数实现输入输出

- main.h文件中添加stdio.h,注意添加位置在USER区域
- 在main.c文件user code4区域重定义
int fputc(int ch, FILE *F)
{
//串口1采用轮询方式发送一个字节数据,超时时间设置为无限等待
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
int fgetc(FILE *F)
{
//串口1采用轮询方式接收1字节数据,超时时间设置为无限等待
int ch;
HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
编译之前,需要勾选Use MicroLIB

进行重定义后,便可写出如下代码,实现的功能为,scanf函数将接收到的数据保存到UART1_RecBuffer_Polling中,如果开始两个字符均为'a',则printf函数输出"Get it!!!",此时,可以收发不固定长度的数据
scanf("%s",UART1_RecBuffer_Polling);
if ((UART1_RecBuffer_Polling[0] == 'a') && (UART1_RecBuffer_Polling[1] == 'a'))
{
printf("Get it!!!");
}
如果想要开启中断,则需要配置NVIC


中断接收:HAL_UART_Receive_IT,中断发送:HAL_UART_Transmit_IT,也均为固定长度
并且可用上述函数使能接收、发送中断,该函数执行一次后会关闭中断使能,有需要的话要在重新调用上述接口使能中断
HAL库的USART1_IRQHandler自动判断哪个中断,接受完相应长度的数据后自动进入不同的中断函数,自动清除标志位

如接收中断回调函数: HAL_UART_RxCpltCallback
对于串口中断接收,用户只需开启接收中断,并在callback函数中重复开启中断即可
实现代码如下
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
USART1_RX_Finished = 1;//Rx Finished
//enable Rx interrupt again, data will store in UART1_RecBuffer_IT, 7字节
HAL_UART_Receive_IT(&huart1,(uint8_t *)UART1_RecBuffer_IT,7);again
}
}
并在main函数中初始化时候使能接受中断,随后while(1)循环中执行如下动作
//use this to enable rx interrupt, data will store in UART1_RecBuffer_IT,7 bytes
HAL_UART_Receive_IT(&huart1,UART1_RecBuffer_IT,7);
while(1)
{
if (USART1_RX_Finished == 1)
{
USART1_RX_Finished = 0;
printf("Rx data is: ");
//enable Tx interrupt, 7 bytes
HAL_UART_Transmit_IT(&huart1,UART1_RecBuffer_IT,7);
printf("\n\r");
}
}
注意,使用中断方式接收也会有长度的要求
四. 结语
本文介绍了STM32串口开发过程,基于标准固件库、CubeMx+HAL库的形式,介绍了USART的配置过程,包括查询、中断,以及scanf和printf函数的重定义,并分别辅以简单的示例代码
在本文的介绍过程中,对于串口的电路原理、寄存器、总线电平等方面并未做详细介绍,因为那些背离了我写这篇专栏的初衷:不进行老学究式的详细研究,而是能够帮助同学们快速上手STM32的开发,始终牢记程序员第一要义:能跑就行,别管怎么跑的。
另外,不对上述内容做进一步介绍还有一个原因:我也不会。。。。。。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐

所有评论(0)