这里只记主要关于STM32应用,不记原理,关于所有通信相关的物理和协议层面的详细知识总结将会放在【通信协议】专栏。
目录
USART简介
STM32的USART异步通信
USART寄存器
状态寄存器USART_SR(state reg)
数据寄存器USART_DR(data reg)
波特率寄存器(USART_BRR)
控制寄存器 1(USART_CR1)
控制寄存器 2(USART_CR2)、控制寄存器 3(USART_CR3)
USART的库函数应用
配置步骤
常用函数
代码范例
USART(Universal Synchronous/Asynchronous Receiver/Transmitter),即通用同步/异步串行接收/发送器。
所谓同步通信和异步通信的主要区别是前者有公共时钟,总线上的所有设备按统一的时序、统一的传输周期进行信息传输。后者没有公共时钟,没有固定的传输周期,采用应答方式通信。简单的说,“同步”就是发送方发出数据后,等接收方发回响应以后才发下一个数据包的通讯方式。 “异步”就是发送方发出数据后,不等接收方发回响应,接着发送下个数据包的通讯方式。异步通信发送方式下,在每一个字符的开始和结束分别加上开始位和停止位,以便使接收端能够正确地将每一个字符接收来。
我们用的最多的UART(Universal Asynchronous Receiver/Transmitter)就是异步通信方式,也就是说,虽然STM32支持USART,但是就一般使用而言,很少使用同步模式,多是使用异步模式。
STM32支持多路串口,具体支持多少串口可以到相应的数据手册查看。比如F103ZET6支持5路串口(USART1~USART5),F103VBT6支持三个串口(USART1~USART3)。STM23的USART模块的框图如下
常用的寄存器如下,主要用途如括号中所示
USART_SR 状态寄存器 (发送寄存器空、发送完成、接受寄存器非空等)
USART_DR 数据寄存器 (存放发送和接收的数据)
USART_BRR 波特率寄存器 (设置波特率参数,BAUD = f/(16*DIV))
USART_CR1 控制寄存器 (发送使能、接受使能、一些关于串口接受和发送的中断使能)
这里主要用到的是一下三个位(发送寄存器空、发送完成、接受寄存器非空):
TXE(Transmit data register empty) 发送数据寄存器空标志位
当TDR寄存器中的数据被硬件转移到移位寄存器的时候,该位被硬件置位。如果USART_CR1寄存器中的TXEIE为1,则产生中断。对USART_DR的写操作,将该位清零。
0 表示数据还没有被转移到移位寄存器;
1 表示数据已经被转移到移位寄存器。
注意:单缓冲器传输中使用该位。
TC (Transmission complete) 发送完成标志位
当包含有数据的一帧发送完成后,并且TXE=1时,由硬件将该位置’1’。如果USART_CR1中的TCIE为’1’,则产生中断。由软件序列清除该位(先读USART_SR,然后写入USART_DR)。TC位也可以通过写入’0’来清除,只有在多缓存通讯中才推荐这种清除程序。
0 表示发送还未完成
1 表示发送完成。
关于TXE和TC的区别:
顾名思义,TXE是发送数据寄存器空,而TC是发送完成标志位。那这个时候我们就要搞清楚两个概念:
1.什么是发送数据寄存器?
2.发送完成是什么意思?前文的框图中可以看到,发送和接收过程是由一个数据寄存器和一个移位寄存器来完成的。
数据寄存器是内核接收和发送串口数据的直接缓存单元,用于临时存放接收到的数据和将要发送的数据。
我们知道串口数据是串行数据,接收的时候不可能同时将一个字节全部写入数据寄存器,发送的时候也不可能同时将数据寄存器中的数据发送出去,这时候就需要移位寄存器将接收的串行数据通过移位的方式写到接收数据寄存器中,或将发送寄存器中的数据通过移位的方式变成串行数据发送出去。
总之,在发送的过程中,内核首先将数据写入发送数据寄存器TDR,然后移位寄存器将发送寄存器中的数据一位一位地发送出去,接收的过程与此相反。此外,我们需要明白,移位寄存器的工作是需要时间的!
搞明白了这个过程,我们再回头看TXE和TC的名字就应该比较清楚了。TXE就是发送数据寄存器空,发送寄存器里没有数据(被移位寄存器取走)的时候就会置位。那发送完成就是发送移位寄存器里的数据发送完成,即空的时候!
举个形象的例子,有个一大水缸,水缸下边有一个小水龙头可以放出水缸里的水,你有一个水桶,一桶水一桶水地忘水缸里倒水,水缸里的水不停地往外流。你每次倒一桶水你的水桶就会空一次,但是只要你持续地倒水,水缸就不会空,直到,你不倒水了,等水缸里的水流完水缸就空了。这里的水桶就是数据寄存器,水缸就是移位寄存器。TXE空表示的就是水桶空、TC空表示的就是水缸空了。
RXNE(Read data register not empty) 读数据寄存器非空
当RDR移位寄存器中的数据被转移到USART_DR寄存器中,该位被硬件置位。如果USART_CR1寄存器中的RXNEIE为1,则产生中断。对USART_DR的读操作可以将该位清零。RXNE位也可以通过写入0来清除,只有在多缓存通讯中才推荐这种清除程序。
0 表示数据没有收到;
1 表示收到数据,可以读出。
此外该寄存器还有以下标志位,均是为1时表示错误发生。
IDLE(IDLE line detected) 监测到总线空闲标志位
ORE(Overrun error) 过载错误标志位
NE(Noise error flag) 噪声错误标志
FE(Framing error) 帧错误标志位
PE(Parity error) 校验错误标志位
配合USART_CR1寄存器,IDLE、ORE和PE可以直接触发中断。NE和FE也可以间接地触发相应的中断,因为它和读数据寄存器非空位RXNE一起出现,硬件会在设置RXNE标志时产生中断。
该寄存器只是用了其中的低八位,包含了发送或接收的数据。由于它是由两个寄存器组成的,一个给发送用(TDR),一个给接收
用(RDR),该寄存器兼具读和写的功能。TDR寄存器提供了内部总线和输出移位寄存器之间的并行接口。RDR寄存器提供了输入移位寄存器和内部总线之间的并行接口。
DIV_Mantissa[11:0] 这12位定义了USART分频器除法因子(USARTDIV)的整数部分。
DIV_Fraction[3:0] 这四位按比例定义了USART分频器除法因子(USARTDIV)的小数部分,具体的值为DIV_Fraction / 16。
比如 Fraction (USARTDIV) = 12/16 = 0.75,小数部分就是0.75,而整数部分是多少就是多少。
波特率寄的计算公式方法如下,在调用库函数的时候并不需要自己计算,只需要配置波特率就行了,所以这里了解就行。
这里的是USART的时钟源,在文章《STM32-GPIO的配置和使用》第二节中提到过“APB1上连接的是低速外设,包括电源接口、备份接口、CAN、USB、I2C1、I2C2、UART2、UART3(如果有USART4和USART5,也在APB1下)等等。APB2上连接的是高速外设,包括UART1、SPI1、Timer1、ADC1、ADC2、GPIO、第二功能IO口等”。所以,USART1是在PCLK1时钟下,而USART2~5(有的芯片没有USART4和5)是在PCLK2下,分别来自APB1分频器(一般输出36M)和APB分频器(一般输出72M)。这里非常重要,因为使用一个外设必须要使能他的时钟源!
STM32 的每个串口都有 3 个控制寄存器 USART_CR1~3,用来对USART模块的一些功能的使能操作。其中基本功能的使用的话只会涉及到CR1,毕竟一般我们使用串口最多的可能就是用来打印log。而在CR1中最需要了解的位在下方都进行了加粗,这些位关系到串口的基本收发功能能不能使用。
UE 串口使能位,通过该位置 1,以使能串口。
M 字长选择位,当该位为 0 的时候数据为8 个数位和n 个停止位,停止位的个数(n)是根据 USART_CR2 的[13:12]位设置来决定的。
WAKE 唤醒方法选择位,为0的时候表示被空闲总线唤醒,为1的时候表示被地址标记唤醒。(详细了解这部分参考《STM32中/英参考手册》25.3.6节)
PCE 校验使能位,0时禁止校验,1时使能校验。
PS 校验位选择,当PCE为1,该位才有效。PS为0 是偶校验,否则为奇校验。
PEIE PE(校验错误)中断使能,如果该位为1,当USART_SR中的PE为’1’(校验错误)时,产生USART中断,
TXIE 发送缓冲区空中断使能位,设置该位为 1,当USART_SR 中的 TXE 位为1 时产生串口中断。
TCIE 发送完成中断使能位,设置该位为 1,当 USART_SR 中的 TC位为 1 时产生串口中断。
RXNEIE 接收缓冲区非空中断使能,设置该位为 1,当 USART_SR中的 ORE 或者 RXNE 位为 1 时产生串口中断。
IDLEIE 检测到总线空闲中断使能位,如果该位为1,当USART_SR中的IDLE为’1’时(检测到总线空闲),产生USART中断
TE 发送使能位,为 1时使能发送功能。
RE 接收使能位,与TE类似。
CR2和CR3和CR1相同,它们控制着USART模块的一些相对复杂的功能,这些功能对于一般用户而言用到的很少。
我们知道使用串口发送数据是比较占用CPU时间的,STM32支持DMA方式发送和接收串口数据,以解放CPU,提高串口的效率,而USART的DMA模式发送和接收信息的使能位是在CR3中。
使用到的函数:
RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState) //比如:使能USART1和GPIOA(USART1默认使用PA9和PA10,所以使能GPIOA) //RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);
使用到的函数主要是
GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
就是基本的GPIO配置而已
主要用到的函数
USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct)
举例:
USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate = bound;//串口波特率 USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式 USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位 USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式 USART_Init(USART1, &USART_InitStructure); //初始化串口1
任何时候使用到中断都要对中断进行优先级配置,USART串口中断并没有什么特别之处。前文《STM32-NVIC中断嵌套优先级管理器》已经进行了介绍。
这里只贴一个例子:
NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//开启串口1中断 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2 ;//抢占优先级3 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //子优先级3 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能 NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
这里只是开启了USART1模块的中断,但还没有配置是USART1哪个功能的中断。
需要用中断配置函数进行配置,比如,下方是开启了USART1的接收中断。
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
另外,注意在主函数中设置中断优先级分组!
USART_Cmd(USART1, ENABLE); //使能串口1
中断服务函数只能以此命名,以此编写中断服务函数,在中断函数中要检查是哪个功能触发的中断,以执行相应的操作。
查看中断状态的函数为
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT)
用以查看哪个中断标志位被置位,参数有以下选项,在状态寄存器部分已经写到,其中常用的就是发送完成中断标志位USART_IT_TC、发送数据寄存器空中断标志位USART_IT_TXE、接收数据寄存器非空中断标志位USART_IT_RXNE。
#define IS_USART_GET_IT(IT) (((IT) == USART_IT_PE) || ((IT) == USART_IT_TXE) || \ ((IT) == USART_IT_TC) || ((IT) == USART_IT_RXNE) || \ ((IT) == USART_IT_IDLE) || ((IT) == USART_IT_LBD) || \ ((IT) == USART_IT_CTS) || ((IT) == USART_IT_ORE) || \ ((IT) == USART_IT_NE) || ((IT) == USART_IT_FE))
该函数的返回值为查询位的状态,为SET(非0)或RESET(0)。
数据接收
使用以下函数,在中断服务函数中判断接收数据寄存器非空中标标志位被置位时调用,以读取数据寄存器中的数据,返回值就是该数据。
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);
中断服务函数接收数据举例:
u8 Res;//如需外部访问,定义为全局变量 void USART1_IRQHandler(void) //串口1中断服务程序 { if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收数据寄存器非空标志位置位 { Res =USART_ReceiveData(USART1); //读取接收到的数据 } }
数据接收中断函数里获取了中断标志位,再读取完之后,为什么没有清除中断标志位呢?
因为通过读数据寄存器USART_DR可以将数据寄存器非空中断标志位清零,也可以向该位写 0,直接清除。同理,清除发送完成中断标志位(TC)也可以通过写数据寄存器USART_DR的方式,此外读USART_SR也可以清除TC中断标志位。
数据发送
数发送只需要调用USART_SendData()函数就行,发送数据之后,要等待发送完成(USART_FLAG_TC被置位)。
void SendData(u8 data) { USART_SendData(USART1,data); while(USART_GetFlagStatus(USART1,USART_FLAG_TC) != SET); }
这里只是简单地等待置位,在串口出现异常的时候容易导致程序死在这里。为了保险起见,最好设置一个等待时长,超过一定时间就退出while循环的等待。
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG); void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG); ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT); void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);
所有关于USART的函数基本都在stm32f10x_usart.h文件中声明
void USART_DeInit(USART_TypeDef* USARTx);
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
void USART_StructInit(USART_InitTypeDef* USART_InitStruct);
void USART_ClockInit(USART_TypeDef* USARTx, USART_ClockInitTypeDef* USART_ClockInitStruct);
void USART_ClockStructInit(USART_ClockInitTypeDef* USART_ClockInitStruct);
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);
void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState NewState);
void USART_SetAddress(USART_TypeDef* USARTx, uint8_t USART_Address);
void USART_WakeUpConfig(USART_TypeDef* USARTx, uint16_t USART_WakeUp);
void USART_ReceiverWakeUpCmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_LINBreakDetectLengthConfig(USART_TypeDef* USARTx, uint16_t USART_LINBreakDetectLength);
void USART_LINCmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);
void USART_SendBreak(USART_TypeDef* USARTx);
void USART_SetGuardTime(USART_TypeDef* USARTx, uint8_t USART_GuardTime);
void USART_SetPrescaler(USART_TypeDef* USARTx, uint8_t USART_Prescaler);
void USART_SmartCardCmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_SmartCardNACKCmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_HalfDuplexCmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_OverSampling8Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_OneBitMethodCmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_IrDAConfig(USART_TypeDef* USARTx, uint16_t USART_IrDAMode);
void USART_IrDACmd(USART_TypeDef* USARTx, FunctionalState NewState);
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);
u8 Res;//外部使用的变量定义为全局变量
void uart_init(u32 bound)
{
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟
//USART1_TX GPIOA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
//USART1_RX GPIOA.10初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2 ;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
//USART 初始化设置
USART_InitStructure.USART_BaudRate = bound;//串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口1
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
USART_Cmd(USART1, ENABLE); //使能串口1
}
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
while(1)
{
USART_SendData(Res);//发送接受到的数据
//最好进行适当的延时,不然有可能导致上位机内存炸掉而崩溃
}
}
void USART1_IRQHandler(void) //串口1中断服务程序
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收数据寄存器非空中断标志位
{
Res =USART_ReceiveData(USART1); //读取接收到的数据
}
}