STM32串口通信(基於緩衝區)

一、串口的初始化和中斷設置

1、初始化GPIO:

        根據手冊的8.1.11節,我們可以找到下表:

  在全雙工的模式下,發送引腳需要設置爲推輓複用輸出,接收引腳則設置爲浮空輸入或帶上拉的輸入。因爲一般不用同步和流量控制的方式,所以CK、RST、CTS引腳不作配置。當然啦,在使用STM32外設的時候不要忘記打開外設時鐘(GPIO和USART的RCC)。

1. USART_FLAG_RXNE—接受數據寄存器非空標誌位(Receive data register not empty flag)
當RDR移位寄存器中的數據被轉移到USART_DR寄存器中,該位被硬件置位 (表示接受到一個字節) 。如果USART_CR1寄存器中的RXNEIE爲1,則產生中斷。對USART_DR的讀操作可以將該位清零。RXNE位也可以通過寫入0來清除,只有在多緩存通訊中才推薦這種清除程序。

0:數據沒有收到;
1:收到數據,可以讀出。

2. USART_FLAG_TXE —發送數據寄存器空標誌位(Transmit data register empty flag)
當TDR寄存器中的數據被硬件轉移到移位寄存器的時候,該位被硬件置位 (表示發送數據寄存器爲空,可以繼續寫入數據。注:此時並不確定數據是否發送完成) 。如果USART_CR1寄存器中的TXEIE爲1,則產生中斷。對USART_DR的寫操作,將該位清零。

0:數據還沒有被轉移到移位寄存器;
1:數據已經被轉移到移位寄存器。

3. USART_FLAG_TC — 發送完成標誌位(Transmission Complete flag)
包含有數據的一幀發送完成後,並且TXE=1時,由硬件將該位置’1’ (表示一幀數據發送完成,可以繼續發送下一幀)。如果USART_CR1中的TCIE爲’1’,則產生中斷。由軟件序列清除該位(先讀USART_SR,然後寫入USART_DR)。TC位也可以通過寫入’0’來清除,只有在多緩存通訊中才推薦這種清除程序。

0:發送還未完成;
1:發送完成。

有一種形象的說法是:TXE是指“彈倉”空;TC是“槍膛”空。
也就是說,你寫數據到串口時,是裝入彈倉,硬件會將數據移到槍膛,這時,TXE爲1,TC爲0,STM32硬件的TX腳正在發送數據,但你還可以裝入數據到彈倉,裝入後,TXE爲0,TC爲0.
TX發送完一個數據後,立即將數據從彈倉移入槍膛,這時,TXE爲1,TC爲0.
最後TX發送完數據,你又沒有裝入新數據,這時。TXE爲1,TC爲1.
————————————————
版權聲明:本文爲CSDN博主「LittleAshes」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_41746317/java/article/details/97754167

 

 

GPIO_InitTypeDef GPIO_InitStructure;
    
//開啓串口和GPIO的時鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
    
//配置發送引腳
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
//發送引腳設置爲推輓複用
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
    
//配置接收引腳
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
//接收引腳設置爲浮空輸入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);

2、配置串口參數

        有專門用於初始化串口的庫函數(USART_Init)和對應的結構體(USART_InitTypeDef),好像每個外設都有這樣的配套,具體內容可參看《STM32F10xxx固件庫_3.xx.pdf》。

USART_InitTypeDef USART_InitStructure;
//波特率
USART_InitStructure.USART_BaudRate = 9600;                    
//數據長度
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;
//初始化串口1
USART_Init(USART1, &USART_InitStructure);

3、中斷配置
        在使用STM32的中斷前,要對NVIC中斷控制器進行配置,設置中斷的優先級。

//配置中斷優先級
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

4、使能串口及串口中斷
        注意1:初始化時不要隨意打開TXE中斷!只要TX-DR寄存器爲空,TX和TXE標誌都會馬上被置位而立即會產生中斷(參考《STM32中文參考手冊》的25.3.2節),即使中斷標誌被清除,也會被重新置位。因此,我採用的是TC中斷而不是採用TXE中斷。

   注意2:不要採用在一箇中斷配置函數中同時打開兩個中斷!例如:USART_ITConfig(USART1, USART_IT_TC | USART_IT_RXNE, ENABLE);    咋眼一看,明明只打開TC中斷和RX中斷,然而卻會同時把TXE中斷也打開。

//串口1使能
USART_Cmd(USART1, ENABLE);
//清除接收中斷標記
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
//清除發送完成中斷標記
USART_ClearITPendingBit(USART1, USART_IT_TC);
//打開串口1發送完中斷
USART_ITConfig(USART1, USART_IT_TC, ENABLE);
//打開串口1接收中斷        兩個中斷不能在一個函數中同時打開!!!太坑了T_T
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
————————————————

 這樣,串口1配置好了。但代碼一運行就會發現不妥!爲什麼每次初始化完成就馬上進入中斷了呢???遇到這種現象千萬不要大驚小怪,我很淡(dan)定(teng)地做了個實驗,發現處理器復位後,串口的SR寄存器中的TC標誌會被置位。而根《STM32中文參考手冊》25.3.2節,在串口使能後會自動發送一個空閒幀,發送完畢後TC也會置位,所以初始化將導致串口初始化完畢後馬上進入TC中斷。爲了避免這種情況,可以在串口使能後等待空閒幀發送完畢,再打開TC中斷。

具體看下面完整的初始化代碼:
————————————————

//配置串口1
void USART1_Config()
{
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
    
    //配置發送引腳
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    //發送引腳設置爲推輓複用
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    //配置接收引腳
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    //接收引腳設置爲浮空輸入
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    //波特率
    USART_InitStructure.USART_BaudRate = 9600;                    
    //數據長度
    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;
    //初始化串口1
    USART_Init(USART1, &USART_InitStructure);
 
    //USART1->SR寄存器復位後TC位爲1,在此清零
    USART_ClearFlag(USART1, USART_FLAG_TC);        
    //串口1使能
    USART_Cmd(USART1, ENABLE);
    
    //使能後串口發送一個空閒幀,等待空閒幀發送完畢後將TC標記位清零
     while(USART_GetFlagStatus(USART1, USART_FLAG_TC) != SET);
    //否則開啓TC中斷後會馬上中斷
     USART_ClearFlag(USART1, USART_FLAG_TC);
        
    //配置中斷優先級
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    
    /***************************************************************
        注意:初始化時不要隨意打開TXE中斷,
        因爲只要TX-DR寄存器爲空,TX和TXE都會馬上被置位而立即會產生中斷
    ***************************************************************/
    
    //清除接收中斷標記
    USART_ClearITPendingBit(USART1, USART_IT_RXNE);
    //清除發送完成中斷標記
    USART_ClearITPendingBit(USART1, USART_IT_TC);
    //打開串口1發送完中斷
    USART_ITConfig(USART1, USART_IT_TC, ENABLE);
    //打開串口1接收中斷        兩個中斷不能在一個函數中同時打開!!!太坑了T_T
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
}

————————————————

二、數據的發送和接收(注:以下代碼有bug,博主至今還未找到原因T-T,僅供思路以參考!)
        爲了提高代碼的效率,我使用基於環形緩衝的串口通信方式。

        發送數據原理:把要發送的數據全部加入到緩衝區中,讓處理器開始發送。一個數據發送結束後,即會產生TC中斷,此時在中斷服務程序中發送下一個數據。像吃飯看電視,在夾菜(發數據)的時候纔要把注意力放到菜盤子上,嚼飯的時候(數據發送中)可以看電視,在開始發送數據到數據發送完畢觸發中斷的這段時間裏,處理器可以去做別的事情。

        接收數據原理:當一個數據接收完畢後,將數據立存入緩衝區而不處理,並在未處理數據的計數器上加1。等到處理器空閒,再從緩衝區讀取這些數據做並處理(不在中斷函數中)。

        如此一來,串口的收發速率並不受影響,還能保證處理器在數據收發的過程中並行執行其他任務。
————————————————
#include "usart1.h"
#include "string.h"
 
//發送緩衝區
u8 Usart1_SendBuffer[USART_SendBufferSize];
//接收緩衝區
u8 Usart1_RecvBuffer[USART_RecvBufferSize];
 
//發送緩衝區指針
int Usart1_SendPointer = 0;
//接收緩衝區指針
int Usart1_RecvPointer = 0;
 
//發送字符隊列的長度
int Usart1_SendDataSize = 0;
//接收未處理字符數
int Usart1_RecvDataSize = 0;
 
//串口1發送狀態
int Usart1_SendStatus = USART_Status_Idle;
 
//生成字符串的緩衝區
char StringBuffer[100];
 
//發送字符串
void USART1_SendString(char *str)
{
//     while(*str)
//     {
//         USART1_SendByte(*str++);
//     }
    USART1_SendArray((u8*)str, strlen(str));
}
 
//發送字節隊列
void USART1_SendArray(u8 *DataArray, int count)
{
    if(count <= 0)
    {
        return;
    }
    
    while(count)
    {
        USART1_SendByte(*DataArray++);
        count --;
    }
}
 
//發送一個字節
void USART1_SendByte(u8 data)
{
    int pos;
 
    //如果緩衝區滿了,要等待
    while(Usart1_SendDataSize >= USART_SendBufferSize);
 
    //計算數據在緩衝區的位置
    pos = Usart1_SendPointer + Usart1_SendDataSize;
    
    //數據位置超過緩衝區尾地址
    if(pos >= USART_SendBufferSize)
    {    
        //重新計算位置
        pos = pos - USART_SendBufferSize;
    }
    Usart1_SendBuffer[pos] = data;
    Usart1_SendDataSize ++;
    
    //如果串口空閒,立即發送
    if(Usart1_SendStatus == USART_Status_Idle)
    {
        Usart1_SendStatus = USART_Status_Busy;
        USART_SendData(USART1, Usart1_SendBuffer[Usart1_SendPointer++]);
        
        //指針移動到緩衝區尾地址後,循環到緩衝區首地址
        if(Usart1_SendPointer == USART_SendBufferSize)
        {
            Usart1_SendPointer = 0;                    
        }
            
        Usart1_SendDataSize --;
    }
}
 
 
//串口1中斷服務程序
void USART1_IRQHandler()
{
    //判斷髮送完成中斷
    if(USART_GetITStatus(USART1, USART_IT_TC) == SET)
    {    
        //清空發送完成TC標記位
        USART_ClearFlag(USART1, USART_FLAG_TC);
        //清空串口發送完成中斷TCIE標記
        USART_ClearITPendingBit(USART1, USART_IT_TC);
        
        if(Usart1_SendDataSize > 0)
        {
            //發送下一個數據
            USART_SendData(USART1, Usart1_SendBuffer[Usart1_SendPointer++]);
            
            //指針移動到緩衝區尾地址後,循環到緩衝區首地址
            if(Usart1_SendPointer == USART_SendBufferSize)
            {
                Usart1_SendPointer = 0;                    
            }
            
            //待發送數據減1
            Usart1_SendDataSize --;
        }
        else
        {
            //發送完畢,串口1發送狀態:空閒
            Usart1_SendStatus = USART_Status_Idle;
        }        
    }
    
    //接收中斷
    if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
    {    
        //清空串口接收標記
        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
        
        //獲取緩衝區數據      
        Usart1_RecvBuffer[Usart1_RecvPointer++] = USART_ReceiveData(USART1);
        
        //如果沒有溢出,待處理數據+1。否則丟棄該數據
        if(Usart1_RecvDataSize < USART_RecvBufferSize)
        {
            Usart1_RecvDataSize ++;
        }
        
        //指針移動到緩衝區尾地址後,循環到緩衝區首地址
        if(Usart1_RecvPointer == USART_RecvBufferSize)
        {    
            Usart1_RecvPointer = 0;                    
        }
    }
}
————————————————
版權聲明:本文爲CSDN博主「培培哥」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/u014695839/java/article/details/62037269

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章