STM32F407ZG开发板学习(4)
串口简介
串口是MCU的重要外部接口,可以实现MCU与外部设备的互相通信,同时在软件开发中可以通过将MCU的数据传输到PC上查看以供程序员进行调试。STM32F407ZGT6最多提供六路串口,有分数波特率发生器、支持 同步 单线通信和半双工单线通讯、支持 LIN 、 支持调制解调器操作、 智能卡协议和 IrDA SIR ENDEC 规范 、具有 DMA 等。
官方文档的介绍:

通信接口

USART:一个全双工通用同步/异步串行收发模块,该接口是一个高度灵活的串行通信设备。
I2C:一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。
SPI:串行外设接口(Serial Peripheral Interface)是一种同步外设接口,它可以使单片机与各种外围设备以串行方式进行通信以交换信息。外围设备包括Flash RAM,网络控制器、LCD显示驱动器、A/D转换器和MCU等。
CAN:控制器域网 (Controller Area Network, CAN),属于总线式串行通信网络。
USB:Universal Serial Bus(通用串行总线)是一个外部总线标准,用于规范电脑与外部设备的连接和通讯。是应用在PC领域的接口技术。
USART
接线
接口通过三个引脚从外部连接到其它设备。
任何USART双向通信均需要至少两个引脚:接收数据输入引脚 (RX) 和发送数据引脚输出 (TX):
RX:接收数据输入引脚就是串行数据输入引脚。过采样技术可区分有效输入数据和噪声,从而用于恢复数据。
TX:发送数据输出引脚。如果关闭发送器,该输出引脚模式由其 I/O 端口配置决定。如果使 能了发送器但没有待发送的数据,则 TX 引脚处于高电平。在单线和智能卡模式下,该 I/O 用于发送和接收数据(USART 电平下,随后在 SW_RX 上接收数据)。

TX和RX两个设备间交叉连接,这两条线的电平是相对于GND的,因此GND也是必须连接的。而VCC可以视情况连接,主要看各设备有无单独电源。若只连接设备1到设备2的TX -> RX,那么通信方式将从全双工变为单工,仅允许设备1发送设备2接收。
电平标准
电平标准用于规定数据1和0的表达方式,主要有以下几种:
TTL电平:3.3/5V 表示1,0V 表示0。
RS232:-3 ~ -15V 表示1,+3 ~ +15V 表示0。
RS485:两线压差 +2 ~ +6V 表示1,-2 ~ -6V 表示0。(差分信号)
数据帧
USART为异步通信,数据帧在传输时需要添加帧头帧尾以检测区分,也就是启动位和停止位,同时需要规定波特率来确定检测电平的时间点以得到正确的数据,既不重复采样也不丢弃数据。
如图,空闲时为高电平,启动位为低电平,检测到下降沿即开始准备接收一帧数据接收完一帧后会有一个停止位,即将电平置回空闲时的高电平。
数据帧有9位长和8位长两种,9位长通常是8bit的数据加上1bit的奇偶校验位,而8位长通常不加校验位。

实验:固定帧头帧尾数据传输
需求
- 从PC输入数据,串口识别相应数据帧接收后发送回PC打印查看。
- 数据帧帧头0xEE,帧尾0xFF。
最终思路以及思考过程
思路
接收时中断,判断是否有效,有效则存入缓冲区,存入完整一帧后,在主循环打印。
中断函数程序段长度的问题
在进行最开始的中断函数的设计时,我将打印数据的操作放在了中断函数之中,这意味着当接收到0xFF时,需要将缓冲区的完整一帧数据在此次中断完成打印,并清空缓冲区。这将导致中断函数运行的时间大大增加且分配不均,而在调试过程中还出现了在一次性输入多条数据帧时会有数据丢失的诡异现象。
至今我也没有找到数据丢失这个问题原因,先认为是中断函数不宜有过长的程序段以及复杂的操作。
于是转而把打印与存数据的操作分开来,中断函数中仅完成存数的操作。
缓冲区数据结构的决定
- 一个数组,这种方式最为简单,将接收到的所有数据都存入缓冲区,这样中断函数甚至不需要判断帧头帧尾,但个人认为,当一条长数据帧的帧头不幸丢失,这会导致缓冲区极大的存储空间浪费。
- 一个数组、一个数据帧计数量、一个打印游标,在中断函数中实现帧头帧尾检测,只存入有用的数据,然后以0xFF作为每帧结尾存入缓冲区,这就解决了上面那种缓冲区浪费的情况。而且帧头帧尾判断也不过是对接收到的数据的if else判断是否存入缓冲区,实际上每次进入中断函数并不会有大量的代码段操作,它只会根据情况执行一些设置标志位和存数据的操作。
- 上一点的升级版——循环队列。 根据前两点的思考,又考虑到了新的一点。就是当数据帧计数量减小,打印游标往后移动,前面的数据不断打印出后,实际上就已经不再需要了,但是前面的空间却还是在被占用着。想要循环利用前面的这片空间,不由得想到循环队列这种数据结构。因为入队是在队尾,出队是在队头,这恰好与我们打印与接收的时序是一致的,在打印时只需要移动Front,在存数据时也只需要移动Rear,打印时的pop操作也将前面的空间“释放”出来以循环利用。
- 歪想法——链表。 循环队列的想法已经让我认为比较合理了,后续就是考虑溢出时的操作。而我个人又比较怕麻烦,就想能不能考虑一种不需要考虑溢出,即动态分配内存的方式来存数据呢。因为从需求来看,根本不需要随机访问,那么链表的数据结构看起来是一个不错的选择,它可以动态分配内存,数据帧有多少我就分多少来存,打印时只需要判断链表是否为空依次打印。然而,STM32的MCU是32位的,这代表一个地址需要4B的存储空间,链表中next指针的存在使得我存一个1B的数据反而多用了4B的开销!!! 再者,在STM32使用C标准库的malloc等动态分配内存,将导致本就不富裕的内存多了很多无法使用的碎片,这是很恐怖的。因此,存数据这种操作,尽量别用链表,别动态分配内存。
因此,最后选择了循环队列的方式来作为缓冲区。
初始化配置
配置串口的主要步骤如下:
- 使能相应引脚所在GPIO的时钟和串口时钟。
- GPIO相应引脚复用为USART,使用 GPIO_PinAF_Config 函数。
- 用 GPIO_InitTypeDef 初始化GPIO。
- 用USART_InitTypeDef 初始化相应串口,包括波特率、流控制、校验位、数据长度、停止位的大小(0.5 / 1 / 1.5 / 2)等。
- 开启需要的中断并配置中断等级(NVIC_InitTypeDef),仅需要中断才配置。
- 使能串口,编写中断服务函数 USARTx_IQRHandler 。
如图选择USART3,我的开发板是复用GPIOB的PB10和PB11

GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
//使能USART3和GPIOB的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB

本文详细介绍了STM32F407ZG开发板中串口通信的原理与实践,包括USART接口、电平标准、数据帧结构和实验配置。作者探讨了数据帧传输的需求,采用中断服务函数处理数据接收,设计了循环队列作为缓冲区,处理了溢出问题,确保了数据完整性和效率。实验结果显示,即便在溢出情况下也能正确处理数据帧。
最低0.47元/天 解锁文章
4043





