串口相关配置寄存器
状态寄存器(USART_SR)
状态寄存器适用于检测串口此时所处的状态。
它能够检测到的状态有:发送寄存器空位、发送完成位、读数据寄存器非空位、检测到主线空闲位、过载错误为等等。
这边主要关注两个位:RXNE和TC(第5、6两位)。
- RXNE(读数据寄存器非空):当该位被置1的时候,就是提示已经有数据被接收到了,并且可以读出来了(即RDR移位寄存器中的数据被转移到USART_DR寄存器中)。这时候要做的就是尽快读取USART_DR,从而将该位清零,也可以向该位写0,直接清除。
- TC(发送完成):当该位被置1的时候,表示USART_DR内的数据已经被发送完成了。如果设置了这个位的中断,则会产生中断。该位也有两种清零方式:读USART_SR,写USART_DR;直接向该位写0。
数据寄存器(USART_DR)
USART_DR实际是包含了两个寄存器,一个专门用于发送的TDR,一个专门用于接收的RDR。进行发送数据操作时,往USART_DR写入数据会自动存储在TDR内;当进行读取数据操作时,向USART_DR读取数据会自动提取RDR数据。
串行通信时一位一位传输的,所以TDR和RDR寄存器都是介于系统总线和移位寄存器间的;发送数据时把TDR内容转移到发送移位寄存器上,接收数据时则是把接收到的每一位顺序保存在接收移位寄存器内进而转移到RDR。
波特率寄存器 (USART_BRR)
波特率寄存器包括定义了两个部分:DIV_Mantissa(整数部分)和DIV_Fraction(小数部分)。
控制寄存器(USART_CRx)
控制寄存器主要是设置USART使能、检验控制使能、校验选择(奇校验偶校验)、PE中断使能、发送缓冲区空中断使能、发送完成中断使能、接收缓冲区非空使能、发送使能、接受使能、字长等等。
USART外设引脚复用
当使用USART的时候,GPIO需要引脚复用,下图介绍了USART的引脚设置:
波特率计算方法
学习波特率之前,首先了解一下通讯速率。通讯速率通常是以比特率来表示,即每秒钟传输的二进制位数,单位为比特每秒(bit/s)。容易和比特率混淆的概念是“波特率”,它表示每秒传输了多少码元。
码元是通讯信号调制的概念,时间间隔相同的符号来表示一个二进制数字,这样的信号就称为码元。如常见的通讯传输中:用0V表示数字0,5V表示数字1,那么一个码元可以表示两种状态0和1,所以一个码元等于一个二进制比特位,此时波特率的大小与比特率一致;若传输中,有0V、2V、4V和6V分别表示00、01、10、11,那么每个码元可以表示四种状态,两个二进制比特位,所以码元数是二进制比特位数的一半,这个时候的波特率为比特率的一半。
因为很多常见的通讯中一个码元都是表示两种状态,人们常常直接以波特率来表示比特率,其实二者是有区别的。
异步通讯由于没有时钟信号,所以两个通讯设备需要规约好波特率,即每个码元的长度,以便对信号进行解码。常见的波特率为4800、9600、115200。
上面的公式中,fpclkx是给串口的时钟(
PCLK1用于USART2、3、4、5,PCLK2用于USART1);USARTDIV是一个无符号定点数。
我们只要得到USARTDIV的值,就可以计算出波特率。
串口操作相关库函数
1个初始化函数
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
作用:用于串口波特率,数据字长,奇偶校验,硬件流控以及收发使能等配置的初始化。
2个使能函数
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);
作用:前者使能串口,后者使能串口的相关中断。
2个数据收发函数
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);
作用:前者发送数据到串口,后者从串口接收数据。
4个状态位函数
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);
作用:前两者获取(或清除)状态标志位,后两者为获取(或清除)中断状态标志位。
串口操作的一般步骤
GPIO时钟使能,串口时钟使能。调用函数:RCC_APB2PeriphClockCmd()(可以参阅:【STM32】STM32端口复用和重映射(AFIO辅助功能时钟) 的部分内容);
串口复位(这一步不是必须的)。调用函数:USART_DeInit();
GPIO外设功能下的端口模式设置。调用函数:GPIO_Init();
串口参数初始化。调用函数:USART_Init();
开启中断并且初始化NVIC(如果需要开启中断才需要这个步骤)。调用函数:NVIC_Init();USART_ITConfig();
使能串口。调用函数:USART_Cmd();
编写中断处理函数。调用函数:USARTx_IRQHandler();
串口数据收发。调用函数:USART_SendData();USART_ReceiveData();
串口传输状态获取。调用函数:USART_GetFlagStatus();USART_ClearITPendingBit();
下面按照这个一般步骤来进行一个简单的串口程序:
void My_USART1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStrue;
USART_InitTypeDef USART_InitStrue;
NVIC_InitTypeDef NVIC_InitStrue;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//GPIO端口使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//串口端口使能
GPIO_InitStrue.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStrue.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStrue.GPIO_Speed=GPIO_Speed_10MHz;
GPIO_Init(GPIOA,&GPIO_InitStrue);
GPIO_InitStrue.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_InitStrue.GPIO_Pin=GPIO_Pin_10;
GPIO_InitStrue.GPIO_Speed=GPIO_Speed_10MHz;
GPIO_Init(GPIOA,&GPIO_InitStrue);
USART_InitStrue.USART_BaudRate=115200;
USART_InitStrue.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
USART_InitStrue.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;
USART_InitStrue.USART_Parity=USART_Parity_No;
USART_InitStrue.USART_StopBits=USART_StopBits_1;
USART_InitStrue.USART_WordLength=USART_WordLength_8b;
USART_Init(USART1,&USART_InitStrue);//
USART_Cmd(USART1,ENABLE);//使能串口1
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//开启接收中断
NVIC_InitStrue.NVIC_IRQChannel=USART1_IRQn;
NVIC_InitStrue.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStrue.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStrue.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStrue);
}
void USART1_IRQHandler(void)
{
u8 res;
if(USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET)
{
res= USART_ReceiveData(USART1);
USART_SendData(USART1,res);
}
}
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
My_USART1_Init();
while(1);
}
usrt_init函数
此处的GPIO端口模式设置,是在端口复用情况下的端口模式设置。至于在复用功能下,GPIO的模式怎么设置,可以查看手册《STM32中文参考手册》p110的内容;
USART_BaudRate波特率的设定是直接写值进去的,MDK5是没有预设的宏定义来选择的;
USART_Mode模式选择用USART_Mode_Tx|USART_Mode_Rx来表示发送使能和接收使能;
NVIC_IRQChannel中断通道,这是在stm32f10x.h开头部分以IRQn结尾的宏定义。
USART1_IRQHandlar函数
USART1_IRQHandlar函数是中断处理函数,不能随意定义的,需要遵循MDK的定义。这些函数的声明在启动文件startup_stm32f10x_hd.s文件中,可以在其中找到中断处理函数的名称。
中断处理函数中首先进行状态位判断,这是非常有必要的。因为可能我们在串口的中断设置中,设置了不止一处的中断,但是每个中断进入的中断处理函数都是同一个,这就要求我们要在中断处理函数中通过状态位的判断再进行接下来的操作。
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
这里通过这个判断来确定是否接收中断。这个函数的返回值是ITStatus类型,它的定义是:
typedef enum {RESET = 0, SET = !RESET} FlagStatus, ITStatus;
SET代表着1(接收到中断),RESET代表着0(未接收中断)。
除了中断处理函数的ITStatus需要判断之外,通常发送数据、接收数据之后也需要判断。比如,下面一段程序:
USART_SendData(USART1, USART_RX_BUF[t]);//向串口1发送数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
即运用while循环,在向串口发送数据之后,通过判断等待发送结束。
这段程序还有一个问题曾经困扰了我很久很久都没有领悟清楚:
在中断处理函数中,使用一下程序来接收数据:
u8 res;
res= USART_ReceiveData(USART1);
由于res是一个u8类型的数,若一次性向串口发送很多的数据,串口使用此程序进行接收的时候,一个u8很多装不下。而这个函数中使用的是if判断,如果一次装不下,if判断程序就走一遍,怎么把所有的数据全部接受呢?
解答:其实我们看一下RXNE标志位引发的中断,当RDR移位寄存器中的数据被转移到USART_DR寄存器中时,也就是有数据可以被接收到的时候,该位置1,引发中断,进入中断处理函数。但是我们在这个中断处理函数中,并没有和其他中断一样做清除中断位的操作,这就导致该位一直是1,不断地进入中断。那么什么时候停止呢?当数据接收完毕了,此时该位清零。
printf函数
printf函数支持的代码在SYSTEM文件夹下的usart.c文件中定义了,加入下面的代码就可以通过printf函数向串口发送需要的内容。这段代码不需要修改,只要引入到usart.h即可使用。
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
_sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}
#endif
整个代码块比较奇怪,重定义了fputc函数之后,也并没有printf函数的显示声明,这样就可以向串口发送内容了。看了许久,也没有看明白是怎么一回事……