STM32:USART的原理与配置

1 前言

  USART全称universal synchronous asynchronous receiver transmitter通用同步异步接收发送器;速率最高可达4.5Mbits/s,波特率460800;

  数据按位顺序发送的串行通信接口简称串口,USART模块是采用串行通信接口最常见的模块,为了方便,就把USART简称为串口;

  USART接口通过RX,TX,GND同其他设备相连;当TX引脚被禁止时,该引脚恢复GPIO的配置;当TX引脚使能且未发送数据时,该引脚处于高电平(空闲态);

  USART接口的数据字长度可编程,停止位长度可编程;可配置为DMA多缓冲通信;

2 USART的帧格式

  串口数据应该遵循USART帧的格式,才能被串口识别;

  首先总线需要持续至少一个空闲帧,然后连续发送数据帧,数据帧与数据帧之间有时会有断开帧,断开帧后需要接1-2bit停止位,连接下个数据帧;

  断开帧只能为10bit或11bit低电平的帧(CR1_SBK[0]);然后接1或2bit的高电平作为停止位,然后接下一个数据帧;

  数据帧的数据字有两种格式,(1)8 bit 数据位;(2)8bit 数据位 + 1 bit 奇偶校验位;

   

3 USART的寄存器使用

  每个USART都有7个自己的寄存器;用来配置该USART的所有功能;

  有许多功能诸如硬件流控制,LIN模式,智能卡模式等,由于没用过或是用不上,实在晦涩难懂费时费力,故在此全部跳过;

  以下给出了USART作为常用串口收发数据的工作框图,以及相关的寄存器配置;

  3.1 工作框图

    

    

  3.2 相关寄存器配置

    1)首先需要配置USART的6个参数:

     波特率USART_BRR,字长M,停止位STOP,校验位PCE,PS,PEIE,USART的收发模式TE和RE和硬件流控制CTSIE,CTSE,RTSE;

    2)USART提供了8个中断:TXEIE, TCIE, RXNEIE, PEIE,  IDLEIE, CTSIE, LBDIE, EIE;

     8个中断使能均可以进入USART的中断函数,根据需要配置合适的中断使能位为1;通常为RXNEIE位;

    3)然后使能接收器RE和发送器TE;

    4)然后使能UE中断;

     

4 USART的代码示例

  4.1 标准库提供的常用USART接口

    标准库为所有的外设都提供了封装寄存器的API接口函数,文件名为stm32f10x_peripheral.c;以下为usart外设的常用函数;

//串口USARTx的参数配置初始化函数;
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);

//使能串口,(主要是分频器和输出的设置)
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
//使能串口中断,(就是那8个中断,均可以进入中断函数)
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);

//都是处理一个字节;
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);

//读取SR寄存器的状态,SR的状态都是硬件设置的;
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
//读取SR寄存器和CRx控制寄存器的状态,和上面一个功能相同的;
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);

//修改SR寄存器的状态,单功能通讯用不上;
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);

  4.2 USART1使用代码

#include "usartDemo.h"  

u8 USART1_RX_BUF[256];    //接收缓存
u8 USART1_RX_CNT = 0;    //接收字节计数
u8 USART1_REV_0D = 0;    //收到

u8 USART1_REV_0A = 0;    //收到
和


//usart1初始化之后,便可以通过串口读写了;
void Usart1_Init(u32 bound)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);    //设置NVIC中断分组2:2位抢占优先级,2位响应优先级   0-3;

    //USART1外设中断配置
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;    //抢占优先级3
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;            //子优先级3
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                //IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);  
    
    //GPIO初始化 USART1_TX    PA9
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; 
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;    //输出需要配置速率,输入不需要配置速率;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;        //复用推挽输出,<中文..手册>8.1.11外设的GPIO配置
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    //GPIO初始化    USART1_RX    PA10
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;        //浮空输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);

   //USART1初始化
    USART_InitStructure.USART_BaudRate = bound;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    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;   //CR1中的TE,RE  
    USART_Init(USART1, &USART_InitStructure); 
    
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//CR1中的RXNEIE中断
    USART_Cmd(USART1, ENABLE);                    //CR1中的UE
}

void USART1_Send_Data(u8 *buf,u16 len)
{
    u16 t;
    for(t=0;t<len;t++)        
    {
        USART_SendData(USART1,buf[t]);
        while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET); 
        //发送字节完成后,TC硬件置1;
    }    //先读SR,后写DR清除TC位;
    USART1_RX_CNT = 0;
    USART1_REV_0D = 0;
    USART1_REV_0A = 0;  
}

void USART1_IRQHandler(void)                    
{
    u8 Res;
    if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
     {
         Res =USART_ReceiveData(USART1);        //读DR,硬件清0 RXNE位;
        USART1_RX_BUF[USART1_RX_CNT]=Res;    //接收数据  
        USART1_RX_CNT++; 
        if(Res==0x0d)
            USART1_REV_0D = 1;
        if(USART1_REV_0D&&(Res==0x0a))
            USART1_REV_0A = 1;                      
     }
    //RXNE为1,读数据的同时又来了数据,那么新的数据丢失;产生溢出错误,读完数据后RXNE为0,但ORE标志还在;
    //RXNE为1,又来了数据,产生接收溢出错误,置位ORE;
    if(USART_GetFlagStatus(USART1,USART_FLAG_ORE) == SET)
    {
        USART_ReceiveData(USART1);
    //    USART_ClearFlag(USART1,USART_FLAG_ORE);//先读SR,后读DR,可以复位ORE位;应该不用软件清除了;
    }
    // USART_ClearFlag(USART1,USART_IT_RXNE); //读DR可以清除RXNE,应该不用软件清除了;
}

int main(void)
{
    Usart1_Init(460800);
    while(1)
    {
        if(USART1_REV_0A)
        {
            USART1_Send_Data(USART1_RX_BUF,USART1_RX_CNT);
        }
    }    
}

    4.2.1 在前面代码的基础上不使用串口中断,直接通过SR状态位来判断数据的收发;

      将上面代码的usart1初始化代码中CR1的RXNEIE配置行注释掉,然后修改main函数如下即可;

int main(void)
{
    Usart1_Init(460800); 
    while(1)
    {
        if ((USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==SET))
        {
            USART1_RX_BUF[USART1_RX_CNT] = USART_ReceiveData(USART1);
            if(USART1_RX_BUF[USART1_RX_CNT]==0x0a)
                USART1_REV_0A = 1;
            USART1_RX_CNT++;
        }
        if(USART1_REV_0A)
        {
            USART1_REV_0A = 0;
            for(int i=0;i<USART1_RX_CNT;i++)
            {
                USART_SendData(USART1, USART1_RX_BUF[i]);
                while(USART_GetFlagStatus(USART1, USART_FLAG_TC)==RESET);
            }
            USART1_RX_CNT=0;
        }
    }
}

5 总结

  5.1 USART的功能没想到还挺多的,寄存器看起来就有些费时了,很多概念都是新的,不好理解,直接拉低了效率;

    于是觉得这样不行,应该用什么看什么,用到再看,学海无涯,精力有限;

    另外人家费心费力写好标准库不就是为了帮开发人员省时间吗?了解一下即可,以后没必要深入;

  5.2 代码测试,参考网页以及正点原子的例程;

    本来是尝试直接在接收函数里将接收到的函数发送回去的;大概因为都是寄存器的操作,并且又是在中断函数里容易被中断打断;

    所以不稳定,容易出现寄存器操作到一半被中断打断;读写序列打乱,导致程序丢帧或不能正常运行;

    参考了别人的代码,还是要用标志变量来表示接收完成,然后接收和发送分开来执行;这样寄存器操作比较稳定,逻辑也清楚;

  5.3 关于NVIC中断优先级管理和串口的DMA传输功能,占个坑;

  5.4 参考网页:https://www.cnblogs.com/pertor/p/9488446.html

  5.5 本文代码github:https://github.com/caesura-k/stm32f1_usart

原文地址:https://www.cnblogs.com/caesura-k/p/12912367.html