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;    //收到\r
u8 USART1_REV_0A = 0;    //收到\r和\n

//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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM