1、电平标准
根据通讯使用的电平标准不同,串口通讯可分为TTL标准和RS-232标准,如下表:
从图中可以看到,TTL电平标准使用5V表示高电平,使用0V表示低电平。在R232电平标准中,为了增加串口通讯的远距离传输及抗干扰能力,使用的是-15V表示高电平,使用+15V表示低电平。如下图为RS232和TLL电平标准表示同一个信号时的对比。
在电子威廉希尔官方网站
中,一般使用TTL电平进行通讯,而在PC机中则使用RS232电平进行通讯。所以为了使电子设备可以和PC机进行串口通讯,必须对TTL电平和RS232电平的信号进行互相转换。
2、串口协议
串口通讯的英文全称为Serial Communication,这是一种在设备间非常常用的串行通讯方式。
串口通讯的协议,串口通讯的数据包由发送设备通过自身的TXD接口传输到接收设备的RXD接口。在串口通讯的协议中,规定了数据包的内容,该内容由起始位、数据位、校验位以及停止位组成,通讯双方的数据包格式要约定一致才能正常收发数据。格式如下图:
在数据帧格式中,校验位可以要也可以不要。
一般在串口通信中,空闲状态下,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的框图如下:
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。
设置波特率时的误差如下图:
注意不要在通信过程中改变波特率寄存器的值。
8、USART中断请求
USART中断请求表:
USART中断映像图:
从中断映像图中可以看出,USART的各种中断事件被连接到同一个中断向量,也就是说USART产生的中断都会进入到同一个中断服务函数内。
各种中断事件:
发送期间:发送完成、清除发送、发送数据。
接收期间:空闲总线检测、溢出错误、接收数据寄存器非空、校验错误、LIN断开符号检测、噪音标志(仅在多缓冲器通信)和帧错误(仅在多缓冲器通信)。
如果设置了对应的使能控制位,这些事件就可以产生各自的中断,可以在中断服务函数内通过判断状态寄存器的位来区分是哪个中断。
9、USART寄存器
只说明一些用到的位。
USART_SR状态寄存器:
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数据寄存器:
该寄存器用来发送和接收数据。
USART_BRR波特比率寄存器:
该寄存器用来设置USART的波特率。
USART_CR1控制寄存器:
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控制寄存器:
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文件。
初始化代码如下:
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脚,既可以设置成浮空输入模式也可以设置成复用输入模式,这两个是一样的。
中断处理函数如下:
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!")。
1、电平标准
根据通讯使用的电平标准不同,串口通讯可分为TTL标准和RS-232标准,如下表:
从图中可以看到,TTL电平标准使用5V表示高电平,使用0V表示低电平。在R232电平标准中,为了增加串口通讯的远距离传输及抗干扰能力,使用的是-15V表示高电平,使用+15V表示低电平。如下图为RS232和TLL电平标准表示同一个信号时的对比。
在电子威廉希尔官方网站
中,一般使用TTL电平进行通讯,而在PC机中则使用RS232电平进行通讯。所以为了使电子设备可以和PC机进行串口通讯,必须对TTL电平和RS232电平的信号进行互相转换。
2、串口协议
串口通讯的英文全称为Serial Communication,这是一种在设备间非常常用的串行通讯方式。
串口通讯的协议,串口通讯的数据包由发送设备通过自身的TXD接口传输到接收设备的RXD接口。在串口通讯的协议中,规定了数据包的内容,该内容由起始位、数据位、校验位以及停止位组成,通讯双方的数据包格式要约定一致才能正常收发数据。格式如下图:
在数据帧格式中,校验位可以要也可以不要。
一般在串口通信中,空闲状态下,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的框图如下:
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。
设置波特率时的误差如下图:
注意不要在通信过程中改变波特率寄存器的值。
8、USART中断请求
USART中断请求表:
USART中断映像图:
从中断映像图中可以看出,USART的各种中断事件被连接到同一个中断向量,也就是说USART产生的中断都会进入到同一个中断服务函数内。
各种中断事件:
发送期间:发送完成、清除发送、发送数据。
接收期间:空闲总线检测、溢出错误、接收数据寄存器非空、校验错误、LIN断开符号检测、噪音标志(仅在多缓冲器通信)和帧错误(仅在多缓冲器通信)。
如果设置了对应的使能控制位,这些事件就可以产生各自的中断,可以在中断服务函数内通过判断状态寄存器的位来区分是哪个中断。
9、USART寄存器
只说明一些用到的位。
USART_SR状态寄存器:
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数据寄存器:
该寄存器用来发送和接收数据。
USART_BRR波特比率寄存器:
该寄存器用来设置USART的波特率。
USART_CR1控制寄存器:
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控制寄存器:
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文件。
初始化代码如下:
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脚,既可以设置成浮空输入模式也可以设置成复用输入模式,这两个是一样的。
中断处理函数如下:
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!")。
举报