1. 程式人生 > >STM32F103系列實戰之通用同步非同步收發器(USART)

STM32F103系列實戰之通用同步非同步收發器(USART)

通用同步/非同步收發器(USART)

STM32F103xC、 STM32F103xD和STM32F103xE增強型系列產品中,內建了3個通用同步/非同步收發器(USART1、 USART2和USART3),和2個通用非同步收發器(UART4和UART5)。這5個介面提供非同步通訊、支援IrDA SIR ENDEC傳輸編解碼、多處理器通訊模式、單線半雙工通訊模式和LIN主/從功能。USART1介面通訊速率可達4.5兆位/秒,其他介面的通訊速率可達2.25兆位/秒。USART1、 USART2和USART3介面具有硬體的CTS和RTS訊號管理、相容ISO7816的智慧卡模式和類SPI通訊模式,除了UART5之外所有其他介面都可以使用DMA操作。

1.USART主要特性
1.1.
全雙工的,非同步通訊
1.2.NRZ
標準格式
1.3.分數波特率發生器系統
    1.3.1傳送和接收共用的可程式設計波特率,最高達4.5Mbits/s
1.4.可程式設計資料字長度(8位或9位)
1.5.可配置的停止位-支援1或2個停止位
1.6.LIN主傳送同步斷開符的能力以及LIN從檢測斷開符的能力
    1.6.1當USART硬體配置成LIN時,生成13位斷開符;檢測10/11位斷開符
1.7.傳送方為同步傳輸提供時鐘
1.8.IRDA SIR 編碼器解碼器
    1.8.1.在正常模式下支援3/16位的持續時間
1.9.智慧卡模擬功能
    1.9.1.智慧卡介面支援ISO7816-3標準裡定義的非同步智慧卡協議
    1.9.2.智慧卡用到的0.5和1.5個停止位
1.10.單線半雙工通訊
1.11.可配置的使用DMA的多緩衝器通訊
    1.11.1.在SRAM裡利用集中式DMA緩衝接收/傳送位元組
1.12.單獨的傳送器和接收器使能位
1.13.檢測標誌
    1.13.1.接收緩衝器滿
    1.13.2.傳送緩衝器空
    1.13.3.傳輸結束標誌
1.14.校驗控制
    1.14.1.傳送校驗位
    1.14.2.對接收資料進行校驗
1.15.四個錯誤檢測標誌

1.15.1.溢位錯誤
1.15.2.
噪音錯誤
1.15.3.幀錯誤
1.15.4.校驗錯誤

1.16.10個帶標誌的中斷源
    1.16.1.CTS
改變
    1.16.2.LIN斷開符檢測
    1.16.3.傳送資料暫存器空
    1.16.4.傳送完成
    1.16.5.接收資料暫存器滿
    1.16.6.檢測到匯流排為空閒
    1.16.7.溢位錯誤
    1.16.8.幀錯誤
    1.16.9.噪音錯誤

    1.16.10.校驗錯誤
1.17.
多處理器通訊 -- 如果地址不匹配,則進入靜默模式
1.18.從靜默模式中喚醒(通過空閒匯流排檢測或地址標誌檢測)
1.19.兩種喚醒接收器的方式:地址位(MSB,第9位),匯流排空閒

2.USART功能概述
介面通過三個引腳與其他裝置連線在一起(見下圖)。任何USART雙向通訊至少需要兩個腳:接收資料輸入(RX)和傳送資料輸出(TX)。
RX:接收資料輸入。通過過取樣技術來區別資料和噪音,從而恢復資料。
TX:傳送資料輸出。當傳送器被禁止時,輸出引腳恢復到它的I/O埠配置。當傳送器被啟用,並且不傳送資料時,TX引腳處於高電平。在單線和智慧卡模式裡,此I/O口被同時用於資料的傳送和接收。
2.1.匯流排在傳送或接收前應處於空閒狀態
2.2.一個起始位
2.3.一個數據字(8或9位),最低有效位在前
2.4.0.5,1,1.5, 2個的停止位,由此表明資料幀的結束
2.5.使用分數波特率發生器 —— 12位整數和4位小數的表示方法。
2.6.一個狀態暫存器(USART_SR)
2.7.資料暫存器(USART_DR)
2.8.一個波特率暫存器(USART_BRR), 12位的整數和4位小數
2.9.一個智慧卡模式下的保護時間暫存器(USART_GTPR)
在同步模式中需要下列引腳:
CK:傳送器時鐘輸出。此引腳輸出用於同步傳輸的 時鐘, (在Start位和Stop位上沒有時鐘脈衝,軟體可選地,可以在最後一個數據位送出一個時鐘脈衝)。資料可以在RX上同步被接收。這可以用來控制帶有移位暫存器的外部裝置(例如LCD驅動器)。時鐘相位和極性都是軟體可程式設計的。在智慧卡模式裡,CK可以為智慧卡提供時鐘。
在IrDA模式裡需要下列引腳:
IrDA_RDI: IrDA模式下的資料輸入;
IrDA_TDO: IrDA模式下的資料輸出。
下列引腳在硬體流控模式中需要:
nCTS: 清除傳送,若是高電平,在當前資料傳輸結束時阻斷下一次的資料傳送;

nRTS: 傳送請求,若是低電平,表明USART準備好接收資料。

下面以STM32F103ZET6為例,通過庫函式結合暫存器配置來介紹一下通用同步非同步收發器的非同步收發器使用(專案驗證執行穩定)。

串列埠設定的一般(常規)步驟可以總結為如下幾個步驟:
1) 串列埠時鐘使能, GPIO 時鐘使能;
2) 串列埠復位 (可省略);
3) GPIO 埠模式設定;
4) 串列埠引數初始化;
5) 開啟中斷並且初始化 NVIC(如果需要開啟中斷才需要這個步驟);
6) 使能串列埠;
7) 編寫中斷處理函式。

除了上述常規的配置方法,USART還可以利用DMA連續通訊。 Rx緩衝器和Tx緩衝器的DMA請求是分別產生的。

利用DMA傳送
使用DMA進行傳送,可以通過設定USART_CR3暫存器上的DMAT位啟用。當TXE位被置為’1’時, DMA就從指定的SRAM區傳送資料到USART_DR暫存器。為USART的傳送分配一個DMA通道的步驟如下(x表示通道號):
1. 在DMA控制暫存器上將USART_DR暫存器地址配置成DMA傳輸的目的地址。在每個TXE事件後,資料將被傳送到這個地址。
2. 在DMA控制暫存器上將儲存器地址配置成DMA傳輸的源地址。在每個TXE事件後,將從此儲存器區讀出資料並傳送到USART_DR暫存器。
3. 在DMA控制暫存器中配置要傳輸的總的位元組數。
4. 在DMA暫存器上配置通道優先順序。
5. 根據應用程式的要求,配置在傳輸完成一半還是全部完成時產生DMA中斷。
6. 在DMA暫存器上啟用該通道。
當傳輸完成DMA控制器指定的資料量時, DMA控制器在該DMA通道的中斷向量上產生一中斷。
在傳送模式下,當DMA傳輸完所有要傳送的資料時, DMA控制器設定DMA_ISR暫存器的TCIF標誌;監視USART_SR暫存器的TC標誌可以確認USART通訊是否結束,這樣可以在關閉USART或進入停機模式之前避免破壞最後一次傳輸的資料;軟體需要先等待TXE=1,再等待TC=1。

利用DMA接收
可以通過設定USART_CR3暫存器的DMAR位啟用使用DMA進行接收,每次接收到一個位元組,DMA控制器就就把資料從USART_DR暫存器傳送到指定的SRAM區(參考DMA相關說明)。為USART的接收分配一個DMA通道的步驟如下(x表示通道號):
1. 通過DMA控制暫存器把USART_DR暫存器地址配置成傳輸的源地址。在每個RXNE事件後,將從此地址讀出資料並傳輸到儲存器。
2. 通過DMA控制暫存器把儲存器地址配置成傳輸的目的地址。在每個RXNE事件後,資料將從USART_DR傳輸到此儲存器區。
3. 在DMA控制暫存器中配置要傳輸的總的位元組數。
4. 在DMA暫存器上配置通道優先順序。
5. 根據應用程式的要求配置在傳輸完成一半還是全部完成時產生DMA中斷。
6. 在DMA控制暫存器上啟用該通道。
當接收完成DMA控制器指定的傳輸量時, DMA控制器在該DMA通道的中斷向量上產生一中斷。

多緩衝器通訊中的錯誤標誌和中斷產生

在多緩衝器通訊的情況下,通訊期間如果發生任何錯誤,在當前位元組傳輸後將置起錯誤標誌。如果中斷使能位被設定,將產生中斷。在單個位元組接收的情況下,和RXNE一起被置起的幀錯誤、溢位錯誤和噪音標誌,有單獨的錯誤標誌中斷使能位;如果設定了,會在當前位元組傳輸結束後,產生中斷。

程式碼如下:

1.定義接收和傳送資料快取變數

#define  COMx_RXBUFFER_SIZE     255                       //串列埠接收快取器長度
typedef struct COMx_RXBUFFER
{
  u8 buffer[COMx_RXBUFFER_SIZE];                          //接收快取器
  u8 RxFlag;                                              //接收資料標誌  1:接收到新資料  0:無新資料被接收
  u16 RxLen;                                              //接收到資料長度
}COMx_RXBUFFER;
#define  UART1_RX_LEN           COMx_RXBUFFER_SIZE        //USART1 DMA接收快取器長度
uint8_t Uart1_Tx[UART1_TX_LEN] = {0};               //串列埠1傳送DMA快取     
uint8_t Uart1_Rx[UART1_RX_LEN] = {0};               //串列埠1接收DMA快取 
//串列埠1接收DMA快取
COMx_RXBUFFER Uart1_RxBuffer;

2.配置串列埠

static void BSP_USART1_Init(u32 baud)
{
    USART_InitTypeDef   bsp_usart_init;
    GPIO_InitTypeDef    bsp_usartpin_init;    
    //使能USART1時鐘、使能USART1引腳時鐘
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);   
    //初始化USART1引腳PA9   TX
    bsp_usartpin_init.GPIO_Pin = GPIO_Pin_9;
    bsp_usartpin_init.GPIO_Speed =  GPIO_Speed_50MHz;
    bsp_usartpin_init.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &bsp_usartpin_init);    
    //初始化USART1引腳PA10  RX
    bsp_usartpin_init.GPIO_Pin = GPIO_Pin_10;
    bsp_usartpin_init.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &bsp_usartpin_init);    
    //初始化USART1引數
    bsp_usart_init.USART_BaudRate = baud;
    bsp_usart_init.USART_WordLength = USART_WordLength_8b;
    bsp_usart_init.USART_StopBits = USART_StopBits_1;
    bsp_usart_init.USART_Parity = USART_Parity_No;
    bsp_usart_init.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    bsp_usart_init.USART_HardwareFlowControl = USART_HardwareFlowControl_None;  
    USART_Init(USART1, &bsp_usart_init);    
    //初始化中斷
    BSP_NVIC_Init(USART1_IRQn, 1, 1);    
    //開啟USART1匯流排空閒中斷
    USART_ITConfig(USART1,USART_IT_TC,DISABLE);  
    USART_ITConfig(USART1,USART_IT_RXNE,DISABLE);  
    USART_ITConfig(USART1,USART_IT_IDLE,ENABLE); 
    //串列埠接收DMA配置
    BSP_DMAUsar1Rx_Init();
    //串列埠傳送DMA配置
    BSP_DMAUsar1Tx_Init();
    USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);                                //採用DMA方式接收
    //開啟USART1
    USART_Cmd(USART1, ENABLE);
}

分析上面程式碼:

1.串列埠時鐘使能。 串列埠是掛載在 APB2 下面的外設,所以使能函式為:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1)

2.串列埠復位。 當外設出現異常的時候可以通過復位設定,實現該外設的復位,然後重新配置這個外設達到讓其重新工作的目的。一般在系統剛開始配置外設的時候,都會先執行復位該外設的操作(此處省略);

3.GPIO 埠模式設定 。按照下圖配置引腳,這裡用的USART1 對應引腳PA9 (TX)  PA10(RX);


4.串列埠引數配置。自定義波特率,1位起始位,1位停止位,8位資料位,無校驗,無流控。使能接收和傳送資料功能。

5.開啟匯流排空閒中斷,並初始化中斷。關於中斷參考相關內容

初始化中斷程式碼如下:

/*
*********************************************************************************************************
*                                            BSP_NVIC_Init()
*
* Description : 中斷優先順序初始化函式,用於給中斷設定優先順序
*
* Argument(s) : IRQChannel   中斷通道.
*               PreemptionPrio  中斷搶佔優先順序
*               SubPrio   中斷子優先順序       
*
* Return(s)   : none.
*
* Caller(s)   : Application.
*
* Note(s)     : none.
*********************************************************************************************************
*/
void BSP_NVIC_Init(u8 IRQChannel, u8 PreemptionPrio, u8 SubPrio)
{
    NVIC_InitTypeDef    bsp_nvic_init;
    
    bsp_nvic_init.NVIC_IRQChannel = IRQChannel;
    bsp_nvic_init.NVIC_IRQChannelPreemptionPriority = PreemptionPrio;
    bsp_nvic_init.NVIC_IRQChannelSubPriority = SubPrio;
    bsp_nvic_init.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&bsp_nvic_init);  
}

6.配置DMA方式接收串列埠資料。關於DMA參見DMA控制器介紹

程式碼如下:

//USART1接收DMA配置
static void BSP_DMAUsar1Rx_Init(void)
{
    DMA_InitTypeDef   DMA_InitStructure;	
      
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);                          //啟動DMA時鐘  
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&USART1->DR);              //外設地址      
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Uart1_Rx;                  //記憶體地址      
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;                          //dma傳輸方向單向      
    DMA_InitStructure.DMA_BufferSize = UART1_RX_LEN;                            //設定DMA在傳輸時緩衝區的長度     
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;            //禁止DMA的外設遞增模式,一個外設        
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;                     //設定DMA的記憶體遞增模式      
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;     //外設資料字長         
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;             //記憶體資料字長      
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;                               //設定DMA的傳輸模式      
    DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;                     //設定DMA的優先級別      
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                                //禁止DMA的2個memory中的變數互相訪問
    DMA_Init(DMA1_Channel5,&DMA_InitStructure);	  
    DMA_Cmd(DMA1_Channel5,ENABLE);                                              //使能通道5  
}

7.配置DMA方式傳送串列埠資料。程式碼如下:

//USART1傳送DMA配置
static void BSP_DMAUsar1Tx_Init(void)
{
    DMA_InitTypeDef   DMA_InitStructure;
    
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);                          //啟動DMA時鐘  
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&USART1->DR);              //DMA外設基地址
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Uart1_Tx;                  //DMA記憶體基地址
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;                          //資料傳輸方向,從記憶體讀取傳送到外設
    DMA_InitStructure.DMA_BufferSize = UART1_TX_LEN;                            //DMA通道的DMA快取的大小
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;            //外設地址暫存器不變
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;                     //記憶體地址暫存器遞增
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;     //資料寬度為8位
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;             //資料寬度為8位
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;                               //工作在正常模式
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;                       //DMA通道 x擁有中優先順序 
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                                //DMA通道x沒有設定為記憶體到記憶體傳輸
    DMA_Init(DMA1_Channel4, &DMA_InitStructure);                                //根據DMA_InitStruct中指定的引數初始化DMA的通道USART1_Tx_DMA_Channel所標識的暫存器                
}

8.使能DMA方式接收資料,開啟串列埠功能。

9.中斷處理函式如下:

//USART1中斷函式。
void USART1_IRQHandler(void)
{    
    uint16_t i = 0; 
    OSIntEnter(); 
    
    if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)  
    {
      //1.清除USART1接收完成中斷
        //USART_ClearFlag(USART1,USART_IT_IDLE);  
        USART1->SR;  
        USART1->DR;                                                             //清USART_IT_IDLE標誌  
      //2.儲存收到的資料內容、長度、標誌位
        DMA_Cmd(DMA1_Channel5,DISABLE);                                         //關閉DMA
        if(!Uart1_RxBuffer.RxFlag)                                              //判斷是否有資料包在處理
        {
            Uart1_RxBuffer.RxLen = UART1_RX_LEN - 
                                   DMA_GetCurrDataCounter(DMA1_Channel5);       //計算接收資料包長度      
            for (i = 0;i < Uart1_RxBuffer.RxLen;i++)                            //將接收到的資料包快取
            {  
                Uart1_RxBuffer.buffer[i] = Uart1_Rx[i];		
            }
            Uart1_RxBuffer.RxFlag = 1;                                          //置位接收狀態標誌位
        }
        DMA_SetCurrDataCounter(DMA1_Channel5,UART1_RX_LEN);                     //設定傳輸資料長度
        DMA_Cmd(DMA1_Channel5,ENABLE);                                          //開啟DMA 
    }     
    // 3. Send message to task_command_process
    OSMboxPost(MboxCmdSet, &Uart1_RxBuffer.buffer[0]);	// Inform App_TaskCommandProcess() that one command has arrived.
    OSIntExit(); 
}

因為專案基於ucos-ii系統開發的,所以中斷函式裡含有ucos-ii系統相關函式。中斷處理步驟如下:

    9.1.判斷中斷型別是否是匯流排空閒中斷。否,不作任何處理退出中斷;是,繼續一下處理;

    9.2.清除中斷標誌;

    9.3.關閉DMA接收功能,獲取接收資料長度;

    9.4.根據接收資料長度從DMA接收快取區提取接收到的資料;

    9.5.重設DMA傳輸資料長度並開啟DMA接收功能;

    9.6.退出中斷處理函式。

由於串列埠接收資料的時間點是未知的,所以採用中斷來處理接收的資料。但是隻有接收沒有放鬆明顯不行,下面介紹一下發送資料函式。程式碼如下:

//USART1 DMA傳送指定長度的資料
//str:要傳送的資料首地址
//cndtr:資料傳輸量 
void BSP_DMAUsart1Puts(unsigned char *str,u8 cndtr)
{   
    memcpy(Uart1_Tx, str, cndtr);
    USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);                                //使能串列埠1的DMA傳送
    DMA_Cmd(DMA1_Channel4, DISABLE );                                           //關閉USART1 TX DMA1 所指示的通道      
    DMA_SetCurrDataCounter(DMA1_Channel4,cndtr);                                //DMA通道的DMA快取的大小
    DMA_Cmd(DMA1_Channel4, ENABLE);                                             //使能USART1 TX DMA1 所指示的通道     
}

通過串列埠傳送資料的時刻是可預知的,所以採用普通的方式傳送串列埠資料即可。步驟如下:

1.將要傳送的資料傳送至DMA傳送快取區中;

2.關閉DMA串列埠傳送資料功能;

3.設定傳送資料size;

4.使能DMA串列埠傳送功能,將資料傳送出去。

到此,串列埠通訊的內容就介紹完了。想要獲取接收的資料,只要檢視變數Uart1_RxBuffer.buffer[]裡的資料;至於傳送資料,呼叫下面的函式即可。

void BSP_DMAUsart1Puts(unsigned char *str,u8 cndtr);