• 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

  • 相关阅读:
    Java 理论与实践: 正确使用 Volatile 变量
    VS2005 C++,断点无法命中的解决办法
    IndexWriterConfig的各个配置项说明(转)
    LINQ&Entity Framework
    CDN
    德鲁克之沟通的四原则
    项目管理过程之项目团队
    OOAD & OOP
    双线接入(全网路由)简单介绍
    唐僧为什么可以领导孙悟空
  • 原文地址:https://www.cnblogs.com/caesura-k/p/12912367.html
Copyright © 2020-2023  润新知