STM32——串口通信
宗旨:技术的学习是有限的,分享的精神的无限的。
一、异步串口通信协议
STM32 的串口非常强大,它不仅支持最基本的通用串口同步、异步通信,还具有 LIN 总线功能(局域互联网)、IRDA 功能(红外通信)、SmartCard 功能。一般我们利用串口打印调试信息。
二、串口工作过程分析
我们只需要大概了解串口发送的过程即可。从下至上,我们看到串口外设主要由三个部分组成,分别是波特率控制、收发控制和数据存储转移。
1、 波特率
波特率,即每秒传输的二进制位数,用 b/s (bps)表示,通过对时钟的控制可以改变波特率。在配置波特率时,我们向波特比率寄存器 USART_BRR 写入参数,修改了串口时钟 的分 频 值USARTDIV 。USART_BRR 寄存 器 包括 两 部分 , 分别 是 DIV_Mantissa(USARTDIV 的整数部分)和 DIV_Fraction(USARTDIV 的小数)部分,最终,计算公式为 USARTDIV=DIV_Mantissa+(DIV_Fraction/16)。
USARTDIV 是对串口外设的时钟源进行分频的,对于 USART1,由于它挂载在 APB2总线上,所以它的时钟源为 f PCLK2 ;而 USART2、3 挂载在 APB1 上,时钟源则为 fPCLK1,串口的时钟源经过 USARTDIV 分频后分别输出作为发送器时钟及接收器时钟,控制发送和接收的时序。
2、 收发控制
寄存器 :CR1、CR2、CR3 和 SR,即USART 的三个控制寄存器(Control Register)及一个状态寄存器(Status Register)。通过向寄存器写入各种控制参数来控制发送和接收,如奇偶校验位、停止位等,还包括对USART 中断的控制;串口的状态在任何时候都可以从状态寄存器中查询得到。具体的控制和状态检查,我们都是使用库函数来实现的。
3、 数据存储转移
收发控制器根据我们的寄存器配置,对数据存储转移部分的移位寄存器进行控制。当我们需要发送数据时,内核或 DMA 外设(一种数据传输方式,在后面介绍)把数据从内存(变量)写入到发送数据寄存器 TDR 后,发送控制器将适时地自动把数据从 TDR 加载到发送移位寄存器,然后通过串口线 Tx,把数据一位一位地发送出去,当数据从 TDR转移到移位寄存器时,会产生发送寄存器 TDR 已空事件 TXE,当数据从移位寄存器全部发送出去时,会产生数据发送完成事件 TC,这些事件可以在状态寄存器中查询到。
而接收数据则是一个逆过程,数据从串口线 Rx 一位一位地输入到接收移位寄存器,然后自动地转移到接收数据寄存器 RDR,最后用内核指令或 DMA
三、UART
串口配置流程:
(1) 使能串口1的时钟
(2) 配置串口1的I/O
(3) 配置串口1的工作模式,具体为波特率为 115200 、8 个数据位、1 个停止位、无硬件流控制。即 115200 8-N-1。
void USART1_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; // 串口IO结构体 USART_InitTypeDef USART_InitStructure; /* 配置串口时钟 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); /* TX --PA9 -- 通用推挽式输出 -- 50MHZ */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); /* RX --PA10 -- 输入*/ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); /* 串口初始化 -- 115200-8-1 */ USART_InitStructure.USART_BaudRate = 115200; // 波特率115200 USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 8位数据位 USART_InitStructure.USART_StopBits = USART_StopBits_1; // 1位停止位 USART_InitStructure.USART_Parity = USART_Parity_No ; // 无奇偶校验位 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowC ontrol_None; // 无硬件流 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 配置串口的模式。为了配置双线全双工通信,需要把 Rx 和 Tx 模式都开启。 USART_Init(USART1, &USART_InitStructure); // 填充完结构体, 向寄存器写入配置参数 USART_Cmd(USART1, ENABLE); // 使能 USART1 外设 }<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>
要想 printf() 函数工作的话,我们需要把 printf() 重新定向到串口中。为了实现重定向 printf() 函数,我们需要重写 fputc() 这个 C 标准库函数,因为 printf()在C 标准库函数中实质是一个宏,最终是调用了 fputc() 这个函数。
// 重定向到串口 int fputc(int ch, FILE *f) { /* 发送一个字节数据到 串口 */ USART_SendData(USART1, (uint8_t) ch); /* 等待发送完毕 */ while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); return (ch); }