1、电平标准 

  根据通讯使用的电平标准不同,串口通讯可分为TTL标准和RS-232标准,如下表:

STM32串口通信-风君雪科技博客

  从图中可以看到,TTL电平标准使用5V表示高电平,使用0V表示低电平。在R232电平标准中,为了增加串口通讯的远距离传输及抗干扰能力,使用的是-15V表示高电平,使用+15V表示低电平。如下图为RS232和TLL电平标准表示同一个信号时的对比。

 STM32串口通信-风君雪科技博客

  在电子电路中,一般使用TTL电平进行通讯,而在PC机中则使用RS232电平进行通讯。所以为了使电子设备可以和PC机进行串口通讯,必须对TTL电平和RS232电平的信号进行互相转换。

2、串口协议

  串口通讯的英文全称为Serial Communication,这是一种在设备间非常常用的串行通讯方式。

  串口通讯的协议,串口通讯的数据包由发送设备通过自身的TXD接口传输到接收设备的RXD接口。在串口通讯的协议中,规定了数据包的内容,该内容由起始位、数据位、校验位以及停止位组成,通讯双方的数据包格式要约定一致才能正常收发数据。格式如下图:

STM32串口通信-风君雪科技博客

  在数据帧格式中,校验位可以要也可以不要。

  一般在串口通信中,空闲状态下,IO口的电平为高电平。

3、串口波特率

  串口通讯一般使用的是异步通讯,异步通讯是没有时钟信号的,为了保证两个设备能够正常通讯,必须在两个设备间约定好收发的速率,波特率就是设备的收发速率,波特率表示的是单位时间内收发的bit位,即一个bit的收发时长。比如波特率为9600的设备,那么该设备1S的时间内可以收发9600个bit,发送一个bit的时长位1/9600≈104us。

4、数据帧的起始信号和停止信号  

  串口通讯的一个数据包是从起始信号开始的,直到停止信号结束。数据包的起始信号由一个逻辑0的数据位表示,而数据包的停止信号可由0.5、1、1.5或2个逻辑1的数据位表示。

  有效数据:

  在数据包的起始位之后紧接着的就是要传输的主体数据内容,也称为有效数据,有效数据的长度常被约定位5、6、7或8位。

  数据校验:

  在有效数据之后,有一个可选的数据校验位,由于数据通信相对更容易受到外部干扰导致传输数据出现偏差,可以在传输过程中加上校验位来解决这个问题,校验方法有奇校验、偶校验、0校验、1校验及无校验。

  奇校验要求有效数据和校验位中“1”的个数为奇数,比如一个8位长的有效数据位:01101001,此时总共有4个“1”,为达到奇校验的效果,校验位为“1”,最后传输的数据将是8位的有效数据加上1位的校验位总共9位。

  偶校验与奇校验要求刚好相反,要求有效数据和校验位中“1”的个数为偶数,比如数据帧:11001010,此时有效数据“1”的个数位4,所以偶校验位为“0”。

  0校验是不管有效数据中的内容是什么,校验位总为“0”。

  1校验是不管有效数据中的内容是什么,校验位总为“1”。

5、UART和USART的区别 

  UART是指通用异步收发器,UASRT是指通用同步/异步收发器。从名称上可是看出,USART是在UART基础上增加了同步功能,即USART是UART的增强型。

  STM32的USART一般做异步通信,即UART功能。

6、USART功能概述 

  使用USART双向通信至少需要三个脚:GND、RX、TX。

  RX:接收数据引脚,通过采样技术来区别数据和噪音,从而恢复数据。

  TX:发送数据引脚,当发送器被禁止时,输出引脚恢复到它的IO端口配置。当发送器被激活,并且不发送数据时,TX引脚处于高电平,也就是说USART的空闲状态的电平时高电平。这里需要注意一点的是,当与设备相连的时候,如果设备断电,这需要将发送器禁止,使TX口输出低电平,否者有可能引起IO口电流倒灌。

  TX脚位在单线和智能卡模式里被同时用于数据的发送和接收。

  USART总线在发送或接收前应处于空闲状态,即高电平。

  一个起始位,起始位为1。

  一个数据字,可以使8位数据或9位数据。发送或接收数据的时候,是先发送或接收低位数据。

  停止位可设置为0.5、1、1.5、2个停止位,发送或接收停止位说明数据帧发送或接收完成。

  使用分数波特率发生器,由12位整数和4位小数组成。

  一个状态寄存器USART_SR

  数据寄存器USART_DR

  一个波特率寄存器USART_BRR,12位表示波特率的整数,4位表示波特率的小数。

  一个智能卡模式下的保护时间寄存器USART_GTPR

   USART的框图如下:

STM32串口通信-风君雪科技博客

7、STM32的USART波特率配置 

  STM32的每个串口都有一个独立的波特率寄存器USART_BRR,该寄存器由USART分频器除法因子USARTDIV的整数部分和小数部分组成。bit4~bit15这12位组成了整数部分DIV_Mantissa,bit0~bit3这4个位组成了分数部分DIV_Fraction。

  波特率的计数公式 = CLK/(16*USARTDIV)。 

  CLK是USART的时钟频率,USART2、USART3、USART4、USART5使用的是APB1总线时钟,一般是36MHZ(STM32F103系列);而USART1则使用的是APB2总线时钟,一般是72MHZ(STM32F103系列)。

  可以根据需要的波特率的值换算出USARTDIV的值。比如设置波特率为9600,则USARTDIV为:

    USARTDIV = CLK / (16 * 9600)

  如果CLK = 72MHZ,则:

  USARTDIV = 72000000/(16*9600) = 468.75

  将小数部分换算为16进制:

    DIV_Fraction = 0.75*16 = 12 = 0x0C

  将整数部分转为16进制:

    DIV_Mantissa = 468 = 0x1D4

  所以波特率寄存器USART_BRR的值为0x1D4C。

  设置波特率时的误差如下图:

STM32串口通信-风君雪科技博客

  注意不要在通信过程中改变波特率寄存器的值。

8、USART中断请求

  USART中断请求表:

  STM32串口通信-风君雪科技博客

  USART中断映像图:

STM32串口通信-风君雪科技博客

  从中断映像图中可以看出,USART的各种中断事件被连接到同一个中断向量,也就是说USART产生的中断都会进入到同一个中断服务函数内。

  各种中断事件:

  发送期间:发送完成、清除发送、发送数据。

  接收期间:空闲总线检测、溢出错误、接收数据寄存器非空、校验错误、LIN断开符号检测、噪音标志(仅在多缓冲器通信)和帧错误(仅在多缓冲器通信)。

  如果设置了对应的使能控制位,这些事件就可以产生各自的中断,可以在中断服务函数内通过判断状态寄存器的位来区分是哪个中断。

9、USART寄存器

  只说明一些用到的位。

  USART_SR状态寄存器:

STM32串口通信-风君雪科技博客

  Bit7 TXE位:当TDR寄存器中的数据被硬件转移到移位寄存器的时候,TXE被硬件置位。

  Bit6 TC位:当发送完一帧有效数据的时候,TC就会置位。

  Bit5 RXNE位:当USART的RDR移位寄存器中的数据被转移到USART_DR寄存器中时,RXNE位被置1,也就是收到数据的时候RXNE位被置1;如果USART_CR1寄存器中的RXNEIE使能,且USART的中断使能,则产生中断,就是所谓的接收中断。RXNE位可以通过对USART_DR寄存器的读取操作来清除,也可以对USART_SR寄存器的bit5写0来清除。

   USART_DR数据寄存器:

 STM32串口通信-风君雪科技博客

  该寄存器用来发送和接收数据。

  USART_BRR波特比率寄存器:

STM32串口通信-风君雪科技博客

  该寄存器用来设置USART的波特率。

  USART_CR1控制寄存器:

 STM32串口通信-风君雪科技博客

  Bit13 UE位:这个位用来使能和停止USART模块,这有UE位被置1的时候,USART才能工作。

  Bit12 M位:该位用来定义数据字的长度,当M=0时,数据字的长度为8个bit;当M=1时,数据字的长度为9个bit。

  Bit10 PCE位:该位用来控制是否使用校验,当PCE=0时,禁止校验控制;当PCE=1时,使能校验控制。

  Bit9 PS位:该位用来选择校验方式,当PS=0时,使用偶校验方式;当PS=1时,使用奇校验。

  Bit8 PEIE位:该位用来控制校验错误中断,当PEIE=0时,禁止校验错误产生中断;当PEIE=1时,使能校验错误产生中断。

  Bit7 TXEIE位:这个是发送缓冲区空中断使能位,当TXEIE=0时,禁止发送缓冲区空时产生中断;当TXEIE=1时,使能发送缓冲区空时产生中断。

  Bit6 TCIE位:这个是发送完成 中断使能,当TCIE=0时,禁止发送完成产生中断;当TCIE=1时,使能发送完成产生中断。

  Bit5 RXNEIE位:这个是接收缓冲区非空中断使能,当RXNEIE=0时,禁止接收完成时产生中断;当RXNEIE=1时,使能接收完成时产生中断。

  Bit4 IDLEIE位:这个是IDLE总线空闲中断使能位,当IDLEIE=0时,禁止检测到总线空闲时产生中断;当IDLEIE=1时,使能检测到总线空闲时产生中断。

  Bit3 TE位:这个是发送使能位,当TE=0时,禁止USART发送数据;当TE=1时,使能USART发送数据。

  Bit2 RE位:这个是接收使能位,当RE=0时,禁止USART接收数据;当RE=1时,使能USART接收数据。

  这里主要区分发送缓冲区空和发送完成这两个点,发送缓冲器空是指USART_DR寄存器的数值被硬件转移到以为寄存器的时候,也就是说要发送的数值进入移位寄存器的时候,发送缓冲器空这个条件就会成立。而发送完成是指移位寄存器里的数据发送完成。

  当将一个数据写入USART_DR寄存器的时候,硬件将USART_DR寄存器的值送入移位寄存器,送完之后会置位USART_SR寄存器的bit7 TXE位,而当移位寄存器里的数值发送完成之后会置位USART_SR寄存器的bit6 TC位。

  USART_CR2控制寄存器:

STM32串口通信-风君雪科技博客

  Bit12~Bit13 STOP位:这个是停止位长度的选择位,当STOP=00时,使用1个停止位;当STOP=01时,使用0.5个停止位;当STOP=10时,使用2个停止位;当STOP=11时,使用1.5个停止位;

  需要注意的是UART4和UART5不能使用0.5和1.5个停止位。

10、HAL库操作USART

  在工程之中添加stm32f1xx_hal_uart.c和stm32f1xx_hal_uart.h文件。

  初始化代码如下:

复制代码

 1 UART_HandleTypeDef UART_Handler;
 2 
 3 uint8_t aRxBuffer;
 4 
 5 void USART_Init(void)
 6 {
 7 
 8     UART_Handler.Instance = USART1;
 9     UART_Handler.Init.BaudRate = 9600;
10     UART_Handler.Init.Mode = UART_MODE_TX_RX;
11     UART_Handler.Init.StopBits = UART_STOPBITS_1;
12     UART_Handler.Init.WordLength = UART_WORDLENGTH_8B;
13     UART_Handler.Init.Parity = UART_PARITY_NONE;
14     UART_Handler.Init.HwFlowCtl = UART_HWCONTROL_NONE;
15     HAL_UART_Init(&UART_Handler);
16     
17     //HAL_UART_Receive_IT(&UART_Handler,aRxBuffer,2);
18     __HAL_UART_ENABLE_IT(&UART_Handler, UART_IT_RXNE);
19     
20     HAL_NVIC_SetPriority(USART1_IRQn,2,1);
21     HAL_NVIC_EnableIRQ(USART1_IRQn);
22              
23 }

复制代码

  使用HAL_UART_Init()函数初始化USART设备。

  从初始化代码内,可以看到,需要选择初始化的USART、波特率、USART的模式(接收和发送)、停止位长度的选择、数据的长度选择、校验的选择、硬件控制流的选择。还需要设置USART的时钟和脚位,但是HAL_UART_Init()函数内会调用USART的时钟和脚位的初始化回调函数,可以在回调函数内设置脚位和时钟。

  初始化完USART之后,打开USART的接收中断,由于HAL库的接收程序比较复杂,这里直接调用宏__HAL_UART_ENABLE_IT(&UART_Handler, UART_IT_RXNE)来打开USART的接收中断。

  IO和RCC初始化函数如下:

复制代码

 1 void HAL_UART_MspInit(UART_HandleTypeDef *husart)
 2 {
 3     GPIO_InitTypeDef GPIO_InitStruct;
 4 
 5     __HAL_RCC_GPIOA_CLK_ENABLE();
 6     __HAL_RCC_USART1_CLK_ENABLE();
 7     
 8     GPIO_InitStruct.Pin = GPIO_PIN_9;
 9     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
10     GPIO_InitStruct.Pull = GPIO_NOPULL;
11     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
12     HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
13 
14     GPIO_InitStruct.Pin = GPIO_PIN_10;
15     GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
16     GPIO_InitStruct.Pull = GPIO_NOPULL;
17     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
18     HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);    
19 
20 }  

复制代码

  这里注意的是RX脚,既可以设置成浮空输入模式也可以设置成复用输入模式,这两个是一样的。

  中断处理函数如下:

复制代码

 1 void USART1_IRQHandler(void)
 2 {
 3     //HAL_UART_IRQHandler(&UART_Handler);
 4     
 5     if(__HAL_UART_GET_FLAG(&UART_Handler,UART_FLAG_RXNE))
 6     {
 7         aRxBuffer = UART_Handler.Instance->DR; 
 8         HAL_UART_Transmit(&UART_Handler,&aRxBuffer,1,1000);  
 9     }  
10 }

复制代码

  由于HAL库的中断服务处理比较繁琐,这里直接读取USART的中断标志位来处理接收中断。

  收到数据到,通过读取USART_DR寄存器的值来获取接收到的数值,这里将接收到的数据再发送出去。

HAL库中断处理函数说明:

  在HAL库中断处理中,进入中断处理函数之后会调用HAL_UART_IRQHandler()函数,在HAL_UART_IRQHandler()函数内判断是那种类型的中断,然后去执行响应的操作。比如接收到数据后,会在HAL_UART_IRQHandler()函数内调用UART_Receive_IT()函数。

  这里需要注意一个问题,在UART_Receive_IT()函数中,接收完成后会清除掉接收中断的使能位,实际在用HAL库来处理中断的时候,接收中断使能位莫名会被清掉。

  在UART_Receive_IT()函数中会调用接收数据的回调函数HAL_UART_RxCpltCallback()来处理数据。

  使用HAL_UART_Receive_IT(&UART_Handler,aRxBuffer,len)函数来接收数据的时候,如果数据长度不为1,则接收到1个数据的时候,不会进入到HAL_UART_RxCpltCallback()函数中,只有收到实际设置的数据长度时,才会进入HAL_UART_RxCpltCallback()函数。通过进入UART_Receive_IT()函数查看代码的时候,会发现,当接收完实际设置的数据长度时,会清除掉接收中断的使能位,这里需要小心,防止收到一次数据之后,不能再进入接收中断。

 添加支持printf()函数的代码:

复制代码

 1 //加入以下代码,支持printf函数,而不需要选择use MicroLIB      
 2 #pragma import(__use_no_semihosting)             
 3 //标准库需要的支持函数                 
 4 struct __FILE 
 5 { 
 6     int handle; 
 7 
 8 }; 
 9 
10 FILE __stdout;       
11 //定义_sys_exit()以避免使用半主机模式    
12 void _sys_exit(int x) 
13 { 
14     x = x; 
15 } 
16 //重定义fputc函数 
17 int fputc(int ch, FILE *f)
18 {      
19     while(__HAL_UART_GET_FLAG(&UART_Handler,UART_FLAG_TC) == RESET){}; 
20       
21     UART_Handler.Instance->DR = (uint8_t) ch;
22     
23     return ch;
24 }

复制代码

  加上以上代码后就可以使用printf()函数发送UART数据了,比如printf(“hello world!”)。