STM32-串口通信波特率计算以及寄存器的配置详解

串口通信基本原理

处理器与外部设备通信的两种方式

并行通信

传输原理:数据各个位同时传输。

优点:速度快

缺点:占用引脚资源多

串行通信

传输原理:数据按位顺序传输

优点:占用引脚资源少

缺点:速度相对较慢

按照数据传送方向,分为:

单工:数据传输只支持数据在一个方向上传输;

半双工:允许数据在两个方向上传输,但是,在某一时刻,只允许数据在一个方向上传输,它实际上是一种切换方向的单工通信;

全双工:允许数据同时在两个方向上传输,因此,全双工通信是两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力。

串行通信的通信方式

同步通信:带时钟同步信号传输。如SPI,lIC通信接口

异步通信:不带时钟同步信号。如UART(通用异步收发器),单总线

常见串行通信接口

STM32的串口通信接口

UART:通用异步收发器

USART:通用同步异步收发器

大容量STM32F10x系列芯片,如STM32F103ZET6,包含3个USART和2个UART

可以在芯片的数据手册中查到各串口所接的引脚

UART异步通信方式特点

  1. 全双工异步通信;
  2. 分数波特率发生器系统,提供精确的波特率;
  3. 发送和接收共用的可编程波特率,最高可达4.5Mbits/s;
  4. 可编程的数据字长度(8位或者9位);
  5. 可配置的停止位(支持1或者2位停止位);
  6. 可配置的使用DMA多缓冲器通信;
  7. 单独的发送器和接收器使能位;
  8. 检测标志:1.接收缓冲器 2.发送缓冲器空 3.传输结束标志
  9. 多个带标志的中断源。触发中断。
  10. 其他:校验控制,四个错误检测标志。

串口通信过程

STM32串口异步通信需要定义的参数

当通信线路上电平状态为1,表示当前线路上没有数据传送,串口处于空闲。

1.起始位:先发出一个逻辑"0"的信号,表示传输字符的开始。

2.数据位(8位或者9位):紧接着起始位之后,数据位的个数可以是4、5、6、7、8等,构成一个字符,从最低位开始传送。

3.奇偶校验位(第9位):数据位加上这一位后,使得"1"的位数应为偶数(偶校验)或奇数(奇校验),以此来校验资料传送的正确性。可有可无,CRC校验更准确

4.停止位(1,15,2位):它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。

5.波特率设置

波特率是每秒钟传输的比特位,一般为4800、9600、115200,波特率越大,则数据传输速度越快,但这样就需要接收端也要快速的接收,同时容易受到干扰;波特率越小则传输越慢,抗干扰能力加强;

一般工业领域使用9600比较多,如果是单片机与模块之间的通信,如WiFi模块,则用115200,能及时通信

常用的串口相关寄存器

USART_SR状态寄存器

USART_ DR数据寄存器

USART_BRR波特率寄存器

寄存器的详细描述在stm32中文参考手册中25.6节USART寄存器描述中可以查看到

波特率计算方法

在STM32中文参考手册的USART框图中,这一部分是波特率设置示意图

公式:

上式中,fPCLKx是给串口的时钟(PCLK1用于USART2、3、4、5,PCLK2用于USART1);USARTDIV是一个无符号定点数。我们只要得到USARTDIV的值,就可以得到串口波特率寄存器USART1->BRR的值,反过来,我们得到USART1->BRR的值,也可以推导出USARTDIV的值。但我们更关心的是如何从 USARTDIV的值得到USART_BRR的值,因为一般我们知道的是波特率,和 fPCLKx的时钟,要求的就是USART_BRR的值。

注意

只有USART1使用PCLK2(最高72MHz),其它USART使用PCLK1(最高36MHz)

参考手册中的波特率计算表

串口相关寄存器

状态寄存器(USART_SR)

该寄存器的TXE、TC、RXNE位比较重要,这些位分别与发送数据寄存器(TDR)、发送移位寄存器、接收数据寄存器(RDR)相关

注意:TXE和TC的复位值为1,RXNE的复位值为0!

TXE为1: TDR里的数据全部移到了移位寄存器,且没有新的数据进TDR。

TXE为0: TDR里有数据,未空,则TXE=0。

在串口中使用USART_SendData()函数发送数据后,最好用USART_GetFlagStatus()函数判断TXE位是否为1,为1才表示数据已经被转移到移位寄存器中,如果一直往发送数据寄存器写入数据,没有等待数据转移到移位寄存器中的话,先写入的数据会被后写入的数据覆盖

TC为1:从TDR过来的数据全部被移送到TX引脚,且TDR里也没有新的数据

TC为0:从TDR里过来的数据还没有全部移过来,或者之前TDR里的数据被移走了,但TDR里又来了新的数据

TC标志位的清除是要先读USART_SR,再写入USART_DR,顺序不能反

这里有个串口发送的例子解析TC标志位的影响

上图程序先调用USART_SendData函数往DR寄存器放入一个‘H’字符,再判断TC标志位是否为1,为1则表示‘H’字符从串口TX发送了出去,上面程序最后结果应该是在串口助手上显示‘HKG’,但实际是显示‘KG’,这是为什么呢?

首先这里有两个问题,1:为什么’H‘字符没有发送出去;2:为什么后面KG都正常发送

解答1:需要注意的是,USART_SR寄存器的复位值是0x00C0,这说明上电复位时TXE和TC位默认是高电平的,系统刚上电,程序执行完了第10行,把‘H’字符放入到了DR寄存器中,但由于TC位上电默认高电平,所以11行的whlie循环就没有起到延时等待效果,就立马执行了12行,又往DR寄存器里放入了’K‘字符,此时’H‘字符还没来得及送到移位寄存器就被’K‘字符覆盖了,所以造成了’H‘字符的发送丢失

解答2:因为TC标志位清除的条件是先读USART_SR,再写入USART_DR,而程序中的第10、11行是先写入USART_SR,再读USART_SR,这顺序明显反了,所以不会清除TC标志位,而程序执行完11行再执行12行后,正好是先读USART_SR,再写入USART_SR,所以满足了TC清零的条件,TC在12行后被清零,所以在13行的whlie循环就起到了延时等待TC标志位再次被置1的作用,当TC被置1,退出循环,'K’字符就被发送了出去,后面的’G‘字符同理

修改方法:

1.在USART_SendData(USART1,‘H’)前加上读USART_SR的操作即可,也就是加上一条while循环,加上后串口助手接到的就是’HKG’;

2.也可以在发送’H‘字符前,调用USART_ClearFlag(USART1,USART_FLAG_TC),手动清除上电时TC标志位默认的高电平,同样能发送’HKG’

控制寄存器(USART_CR)

5、6、7位与状态寄存器的有点类似,这里主要是控制中断使能

串口操作相关库函数(省略入口参数)

void USART_Init();		//串口初始化:波特率,数据字长,奇偶校验,硬件流控以及收发使能
void USART_Cmd();		//使能串口
void USART_ITConfig();	//使能相关中断

操作DR数据寄存器:

void USART_SendData();			//发送数据到串口, 放入DR
uint16_t USART_ReceiveData();	//接受数据,从DR读取接收到的数据

操作SR状态寄存器

FlagStatus USART_GetFlagStatus();		//获取状态标志位
void USART_ClearFlag();					//清除状态标志位
ITStatus USART_GetITStatus();			//获取中断状态标志位
void USART_ClearITPendingBit();			//清除中断状态标志位

USART_GetFlagStatus()和USART_GetITStatus()的区别

共同点:都是访问串口的SR状态寄存器。

不同点:

USART_GetITStatus():

该函数不仅会判断标志位是否置1,同时还会判断是否使能了相应的中断。所以在串口中断函数中,通常使用该函数

返回值:ITStatus,ITStatus的值为1,表示可以读取数值了,ITStatus的值为0,表示还不可以读取数值

USART_GetFlagStatus() :

该函数只判断标志位。在没有使能相应的中断时,通常使用该函数来判断标志位是否置1。

串口配置的一般步骤

串口中断根据需要开启

TX引脚模式设置为推挽复用输出(GPIO_Mode_AF_PP)

RX引脚模式设置为浮空输入或带上拉输入(GPIO_Mode_IN_FLOATING)

串口实验代码详解

串口初始化USART1_Init()

先对串口1所接的引脚PA9和PA10初始化,再对串口初始化,要开启中断的话,要对NVIC进行初始化

/**
* @name 	USART1_Init
* @brief	串口1初始化
* @param	bound:波特率
* @retval	None
*/
void USART1_Init(u32 bound)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    //GPIO时钟,串口1时钟使能
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_USART1,ENABLE);

    //串口复位(可无)
    USART_DeInit(USART1);

    //USART1_TX PA9
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;        //设置为复用推挽输出
    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_9;             //PA9接TX
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    //PA9引脚初始化
    GPIO_Init(GPIOA,&GPIO_InitStructure);

    //USART1_RX PA10
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;      //设置为浮空输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;                 //PA10接RX
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    
    //PA10引脚初始化
    GPIO_Init(GPIOA,&GPIO_InitStructure);

    //USART1
    USART_InitStructure.USART_BaudRate = bound;                  //波特率
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;     //无硬件数据流控制
    USART_InitStructure.USART_Mode = USART_Mode_Tx|USART_Mode_Rx;       //设置发送和接收模式
    USART_InitStructure.USART_Parity = USART_Parity_No;         //无奇偶校验位
    USART_InitStructure.USART_StopBits = USART_StopBits_1;      //一个停止位
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;     //字长为8位数据格式

    //USART1初始化
    USART_Init(USART1,&USART_InitStructure);

    //开启中断并初始化NVIC
    //USART1 NVIC 配置
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;           //串口1全局中断
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;             //IRQ通道使能
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;   //抢占优先级为3
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;          //子优先级为3

    //根据指定的参数初始化NVIC寄存器
    NVIC_Init(&NVIC_InitStructure);

    //开启中断,接收到数据就进入中断
    USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);

    //使能串口
    USART_Cmd(USART1,ENABLE);
}

中断处理函数USART1_IRQHandler()

//0x0d是回车键的十六进制值
//0x0a是换行/n的十六进制值
//中断处理判断数据结束标准是:回车+换行

#define USART_REC_LEN 200
u16 USART_RX_STA = 0;                   //接收状态标记
u8 USART_RX_BUF[USART_REC_LEN];        //接收数组,最大USART_REC_LEN个字节

/**
* @name 	USART1_IRQHandler
* @brief	串口1中断处理函数
* @param	None
* @retval	None
*/
void USART1_IRQHandler(void)        //名字不能随便起
{
    u8 Res;
    if(USART_GetITStatus(USART1,USART_IT_RXNE) != RESET)    //接收中断(接收到的数据必须是0x0d 0x0a结尾)
    {
        Res = USART_ReceiveData(USART1);    //读取到的数据,函数返回的是USART1->DR寄存器的[8:0]位

        /*STA最高位为接收完成标志位,接收完一串数据后才被置位,会被主函数或其他地方清零;
        这里判断该位是否为0,为0表示一串数据正在接收,没接收完,或者是其他地方清零了标志位,准备下一串数据的接收*/
        if((USART_RX_STA & 0x8000) == 0)     //接收未完成
        {
		/*判断接收0x0d标志位是否为1,如果上一个字节是0x0d,那该标志位就会被置位,这里取出该位判断,为1则进入里面继续判断本			次接收的字节是否为0x0a(换行),是则表示一串数据接收完毕,最高位接收完成标志位置1,否则清除STA标志,重新接收数据*/
            if(USART_RX_STA & 0x4000)   
            {
                if(Res != 0x0a)USART_RX_STA = 0;    //接收错误,重新开始
                else USART_RX_STA |= 0x8000;        //接收完成了,接收到回车后的换行字符,接收完成标志位置位
            }
            else    //没接收到0x0d,说明数据还没结束,继续接收
            {
                if(Res == 0x0d)     //如果这次收到的数据是0x0d
                {
                    USART_RX_STA |= 0x4000;      //接收到0x0d标志位置位
                }
                else    //如果这次收到的数据不是0x0d,说明是普通数据,进行保存
                {
                    /*将接收的数据存入数组中,STA的0 ~ 13位为接收到数据的个数,因为STA 0 ~ 13位一开始都是0,
                    0&1=0,所以[USART_RX_STA & 0x3FFF]为0,这里相当于下标的作用
                    */
                    USART_RX_BUF[USART_RX_STA & 0x3FFF] = Res;  
                    USART_RX_STA++;                             //长度+1,放下一个数据
                    if(USART_RX_STA>(USART_REC_LEN-1))          //如果数据个数(下标)大于接收数组长度,则清零STA
                    {
                        USART_RX_STA = 0;   				//STA清零,重新接收数据
                    }
                }
            }
        }
    }
}

主函数main

#include "delay.h"
#include "USART1.h"

int main()
{
	u16 i;
	u16 len;
	u16 time = 0;
	//延时函数初始化
	delay_Init();
	//设置NVIC中断分组2:2位抢占优先级,2位响应优先级
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	//串口初始化,波特率9600
	USART1_Init(9600);
	while(1)
	{
		if(USART_RX_STA & 0x8000)	//判断接收完成标志位是否为1
		{
			len = USART_RX_STA & 0x3FFF;	//得到此次接收到的数据长度
			for(i=0;i<len;i++)
			{
				USART_SendData(USART1,USART_RX_BUF[i]);	//往串口1发送数据
				while(USART_GetFlagStatus(USART1,USART_FLAG_TC) != SET);	//等待发送结束
			}
			printf("\r\n\r\n");		//换行
			USART_RX_STA = 0;		//清零STA
		}
		else		//接收完成标志位为0,说明stm32没有接收到数据
		{
			time++;
			if(time%500 == 0){printf("请输入数据,以回车键结束\r\n");}
			if(time%2000 == 0){printf("USART1串口实验\r\n");}
			delay_ms(10);
		}
	}
}

重写printf函数

在串口初始化的源文件中加入下面的代码,重写printf函数,让其往串口发送

//加入以下代码,支持printf函数,而不需要选择use MicroLIB	  
#if 1
#pragma import(__use_no_semihosting)             
//标准库需要的支持函数                 
struct __FILE 
{ 
	int handle; 

}; 

FILE __stdout;       
//定义_sys_exit()以避免使用半主机模式    
void _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 

串口发送一个字节函数

仿照USART_SendData函数进行重写

void USART_SendByte(USART_TypeDef* USARTx,uint8_t Data)
{
    assert_param(IS_USART_ALL_PERIPH(USARTx));
    assert_param(IS_USART_DATA(Data));
    USARTx->DR = (Data & (uint16_t)0x01FF);
    while(USART_GetFlagStatus(USARTx,USART_FLAG_TXE)==RESET);	//等待发送完成
}

串口发送一个字符串函数

依次发送字符串中字符,每发送一个检查下TXE标志位,发送完全部字符以后,最后检查TC标志位。

void USART_SendString( USART_TypeDef *USARTx, char*str)
{
    while(*str!=0)
    {
        USART_SendByte( USARTx,*str++ );
    }
	while(USART_GetFlagStatus(USARTx,USART_FLAG_TC)==RESET);
}
物联沃分享整理
物联沃-IOTWORD物联网 » STM32-串口通信波特率计算以及寄存器的配置详解

发表评论