我也是小白一个,为了让自己对知识掌握得更牢一点写下这篇博客。如果大家发现什么问题欢迎指出
- CubeMX实现串口的基本配置
- 串口接收
串口的接收会比较复杂,大家慢慢看
串口接收通过HAL库来实现,串口的HAL库接收函数有三种
HAL_UART_Receive;
HAL_UART_Receive_IT;
HAL_UART_Receive_DMA;
由于第三种涉及到DMA,这里就只讲前面两种
HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
是阻塞式接收函数,也就是串口在接收数据时,单片机会处于等待状态,直到串口接收完成,单片机才继续处理后面的程序。这种方式优点缺点都很明显,优点是逻辑简单,缺点占用处理器资源。
HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
通过串口中断来实现串口数据的接收。
首先我们要在程序中调用HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
使能串口接收中断。(CubeMX并没有帮我们使能!) 并且在接收完一次数据后,要再次使能,不然串口可能无法正常接收。
使能完成后,当串口接收到足够数量的字符,就会产生接收中断。
程序会先进入USART1_IRQHandler()
在里面调用HAL_UART_IRQHandler();
在这个HAL库函数中,程序会判断发生的是哪一种中断,比如:接收、发送、错误中断等,然后调用相应的中断函数。在这个中断函数里再调用对应的回调函数HAL_UART_RxCpltCallback();
这个过程看起来让人头晕,但在编程的时候,我们只需要改写回调函数就行,前面的过程CubeMX已经帮我们封装好了。
-
串口接收的逻辑
我们在上面说到,调用HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
后,程序会在收到我们指定的size个数据后进入中断。但在我们的日常使用中,我们一般不能知道上位机会发送多少个字节的数据过来。因此,我们也就不知道什么时候结束串口数据接收,通过接收到合适长度的数据来进入中断的方法就显得很鸡肋。因此,我们想出了一些办法来处理这个问题。
下面要说到的这两个方法是我从正点原子的例程中总结出来的。
A. 通过判断是否连续收到0x0a,0x0d这两个数据来判断一次传输是否完成
我们将HAL_UART_Receive_IT()
的uint16_t size
设置为1,也就是收到一个字节的数据就进入中断,并且这个数据会被保存在uint8_t *pData
中。然后我们自己定义一个保存接收串口数据的数组uint_8 USART_RX_BUF[USART_REC_LEN];
和一个该数组的索引号USART_RX_STA
。USART_RX_STA
的高两位用来标志接收是否完成。
在我们自己编写的回调函数中,将每次接收到的一个字节的uint8_t *pData
保存到USART_RX_BUF[USART_REC_LEN]
中,并使USART_RX_STA++
。如果连续收到0x0a,0x0d这两个数据,USART_RX_STA
的最高位就会被置一,标志接收完成。检测到USART_RX_STA
的最高位为一后,我们就可以读取USART_RX_BUF
中的数据,也就是这一次上位机发来的数据。当然,这要求上位机在它要发送的数据结尾加上0x0a,0x0d这两个数据。
B. 通过判断接收连续2个字符之间的时间差不大于100ms来决定是不是一次连续的数据
这里我们同样用USART_RX_STA
的最高位来做标志,但是置一的条件不同了。在初始化过程中,我们配置一个定时器,每次进入串口接收回调函数的时候,将定时器的值清零,并使能。如果串口收到的两个字符时间差太大,定时器超过一定的值,就会进入定时器中断。在定时器的中断里,我们将USART_RX_STA
的最高位置一。 -
串口发送
串口发送可以通过将printf函数重定向实现,非常方便。只需在main.c中加入如下代码(转载自正点原子例程)
//加入以下代码,支持printf函数,而不需要选择use MicroLIB
//#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}
#endif
当然,通过HAL库函数进行发送也是可以的
HAL_UART_Transmit();
HAL_UART_Transmit_IT();