干貨|高質(zhì)量代碼是怎么寫(xiě)出來(lái)的?串口環(huán)形隊(duì)列

摘要:串口是通信中最常用的通信方式,可能寫(xiě)串口的驅(qū)動(dòng),能寫(xiě)幾十種方法, 查詢(xún)方式,中斷方式,DMA方式,定時(shí)器方式。可能也其中幾種方式的組合形式,經(jīng)典的用法是:發(fā)送用查詢(xún)方式,接收用中斷方式,或者DMA+空閑中斷。本篇不講串口是啥,現(xiàn)在還在講串口是啥,估計(jì)會(huì)被噴。今天來(lái)聊一聊串口常用的幾種方式,最簡(jiǎn)單的方法就不說(shuō)了。

一、經(jīng)典方法

  1. 查詢(xún)方式 可靠性很高,要考慮下個(gè)數(shù)據(jù)包覆蓋上一個(gè)數(shù)據(jù)包的問(wèn)題,小數(shù)據(jù)量,在10個(gè)字節(jié)以?xún)?nèi),可以這樣考慮, 很簡(jiǎn)單,很方便,很可靠。但是在數(shù)據(jù)量大的時(shí)候,程序阻塞的時(shí)間特別長(zhǎng),影響其他比較重要的外設(shè)的處理。

  2. 中斷方式 中斷方式 , 不占用系統(tǒng)資源,但是如果數(shù)據(jù)量大,會(huì)頻繁中斷cpu, 會(huì)其他高優(yōu)先的數(shù)據(jù)處理造成影響。但是沒(méi)有DMA不占用資源的好處, 如果沒(méi)有串口隊(duì)列的實(shí)現(xiàn),必須通過(guò)標(biāo)志位判斷上一個(gè)包數(shù)據(jù)是否發(fā)送完成,在把新的數(shù)據(jù)覆蓋到串口的緩沖區(qū)。

  3. DMA方式 優(yōu)點(diǎn):  不占用系統(tǒng)資源,減少CPU對(duì)中斷的響應(yīng)。如何不建立數(shù)據(jù)包的隊(duì)列,還是會(huì)出現(xiàn),需要等待阻塞的問(wèn)題。

二、環(huán)形隊(duì)列

隊(duì)列這個(gè)詞在數(shù)據(jù)局結(jié)構(gòu)中出現(xiàn)的比較多,與之對(duì)應(yīng)的就是堆棧,但是兩者的讀取方式又完全不同。

FIFO 是First-In First-Out的縮寫(xiě),它是一個(gè)具有先入先出特點(diǎn)的緩沖區(qū)。串口設(shè)計(jì)FIFO的目的是為了提高串口的通訊性能。如果沒(méi)有FIFO或者說(shuō)緩沖區(qū)的長(zhǎng)度只有1字節(jié),那么使用接收中斷,就意味著每次收到一個(gè)字節(jié)的數(shù)據(jù)就要進(jìn)一次中斷,這樣頻繁進(jìn)中斷會(huì)占用CPU資源。另外如果沒(méi)有及時(shí)讀走數(shù)據(jù),那么下一個(gè)字節(jié)數(shù)據(jù)就會(huì)覆蓋之前的數(shù)據(jù),導(dǎo)致數(shù)據(jù)丟失,這在通訊速率高的場(chǎng)合很有可能出現(xiàn)

使用FIFO,可以在連續(xù)接收若干個(gè)數(shù)據(jù)后才產(chǎn)生一次中斷,然后一起進(jìn)行處理。這樣可以提高接收效率,避免頻繁進(jìn)中斷,適用于大數(shù)據(jù)傳輸。你可能會(huì)想到如果FIFO中的數(shù)據(jù)沒(méi)有達(dá)到指定長(zhǎng)度而無(wú)法產(chǎn)生中斷怎么辦,通常MCU會(huì)有接收超時(shí)中斷,即在一定的時(shí)間內(nèi)沒(méi)有接收到數(shù)據(jù)會(huì)進(jìn)入中斷,可以利用這個(gè)中斷把不足FIFO長(zhǎng)度的數(shù)據(jù)最后都讀取完。

FIFO類(lèi)似售票排隊(duì)窗口,先到的人看到能先買(mǎi)到票,然后先走,后來(lái)的人只能后買(mǎi)到票。

干貨|高質(zhì)量代碼是怎么寫(xiě)出來(lái)的?串口環(huán)形隊(duì)列的圖1

在計(jì)算機(jī)中,每個(gè)信息都是存儲(chǔ)在存儲(chǔ)單元中的,當(dāng)有大量數(shù)據(jù)的時(shí)候,我們不能存儲(chǔ)所有的數(shù)據(jù),那么計(jì)算機(jī)處理數(shù)據(jù)的時(shí)候,只能先處理先來(lái)的,那么處理完后呢,就會(huì)把數(shù)據(jù)釋放掉,再處理下一個(gè)。那么,已經(jīng)處理的數(shù)據(jù)的內(nèi)存就會(huì)被浪費(fèi)掉。因?yàn)楹髞?lái)的數(shù)據(jù)只能往后排隊(duì),如過(guò)要將剩余的數(shù)據(jù)都往前移動(dòng)一次,那么效率就會(huì)低下了,肯定不現(xiàn)實(shí),所以,環(huán)形隊(duì)列就出現(xiàn)了。

點(diǎn)擊下方視頻動(dòng)態(tài)演示出隊(duì)入隊(duì)干貨|高質(zhì)量代碼是怎么寫(xiě)出來(lái)的?串口環(huán)形隊(duì)列的圖2

1、環(huán)形隊(duì)列的實(shí)現(xiàn)

在計(jì)算機(jī)中,是沒(méi)有環(huán)形的內(nèi)存的,只不過(guò)是我們將順序的內(nèi)存處理過(guò),讓某一段內(nèi)存形成環(huán)形,使他們首尾相連,簡(jiǎn)單來(lái)說(shuō),這其實(shí)就是一個(gè)數(shù)組,只不過(guò)有兩個(gè)指針,一個(gè)指向列隊(duì)頭,一個(gè)指向列隊(duì)尾。指向列隊(duì)頭的指針是緩沖區(qū)可讀的數(shù)據(jù),指向列隊(duì)尾的指針是緩沖區(qū)可寫(xiě)的數(shù)據(jù),通過(guò)移動(dòng)這兩個(gè)指針即可對(duì)緩沖區(qū)的數(shù)據(jù)進(jìn)行讀寫(xiě)操作了,直到緩沖區(qū)已滿(頭尾相接),將數(shù)據(jù)處理完,可以釋放掉數(shù)據(jù),又可以進(jìn)行存儲(chǔ)新的數(shù)據(jù)了。

實(shí)現(xiàn)的原理

視頻來(lái)自正在一名考研的UP主:禿頭少女王某。計(jì)算機(jī)專(zhuān)業(yè)考研這個(gè)是必考點(diǎn),視頻講的很棒,祝她一戰(zhàn)成碩,金榜題名!干貨|高質(zhì)量代碼是怎么寫(xiě)出來(lái)的?串口環(huán)形隊(duì)列的圖3干貨|高質(zhì)量代碼是怎么寫(xiě)出來(lái)的?串口環(huán)形隊(duì)列的圖4

串口環(huán)形緩沖區(qū)收發(fā):在初學(xué)單片機(jī)的時(shí)候我們知道的串口收發(fā)都是:接收一個(gè)數(shù)據(jù),觸發(fā)中斷,然后把數(shù)據(jù)發(fā)回來(lái)。這種處理方式是沒(méi)有緩沖的,當(dāng)數(shù)量太大的時(shí)候,亦或者當(dāng)數(shù)據(jù)接收太快的時(shí)候,我們來(lái)不及處理已經(jīng)收到的數(shù)據(jù),那么,當(dāng)再次收到數(shù)據(jù)的時(shí)候,就會(huì)將之前還未處理的數(shù)據(jù)覆蓋掉。那么就會(huì)出現(xiàn)丟包的現(xiàn)象了,對(duì)我們的程序是一個(gè)致命的創(chuàng)傷。

那么如何避免這種情況的發(fā)生呢,很顯然,上面說(shuō)的一些隊(duì)列的特性很容易幫我們實(shí)現(xiàn)我們需要的情況。將接受的數(shù)據(jù)緩存一下,讓處理的速度有些許緩沖,使得處理的速度趕得上接收的速度,上面又已經(jīng)分析了普通隊(duì)列與環(huán)形隊(duì)列的優(yōu)劣了,那么我們肯定是用環(huán)形隊(duì)列來(lái)進(jìn)行實(shí)現(xiàn)了。下面就是代碼的實(shí)現(xiàn):

2、定義一個(gè)結(jié)構(gòu)體

typedef struct
{

    uint16_t usWrite;
    uint16_t usRead;
    uint16_t usLenght;
    /* FIFO 結(jié)構(gòu) */
    uint8_t  ucRing_Buff[RINGBUFF_LEN];

}RingBuff_T;

extern RingBuff_T g_ringBuff;

3、初始化隊(duì)列

初始化結(jié)構(gòu)體相關(guān)信息:使得我們的環(huán)形緩沖區(qū)是頭尾相連的,并且里面沒(méi)有數(shù)據(jù),也就是空的隊(duì)列,所有元素清0。

void RingBuff_Init(void)
{
    g_ringBuff.usWrite = 0;
    g_ringBuff.usRead = 0;
    g_ringBuff.usLenght = 0;
}

4、數(shù)據(jù)壓入隊(duì)列

/**
* @brief  Write_RingBuff
* @param  uint8_t _ucWriteData
* @return 0:環(huán)形緩沖區(qū)已滿,寫(xiě)入失敗;1:寫(xiě)入成功
* @note   往環(huán)形緩沖區(qū)寫(xiě)入uint8_t類(lèi)型的數(shù)據(jù)
*/

uint8_t Write_RingBuff(uint8_t _ucWriteData)
{
    if(g_ringBuff.usLenght >= RINGBUFF_LEN) /*判斷緩沖區(qū)是否已滿*/
    {
        return 0;
    } 
    g_ringBuff.ucRing_Buff[g_ringBuff.usRead] = _ucWriteData;
    g_ringBuff.usRead = (g_ringBuff.usRead + 1) % RINGBUFF_LEN; /*防止越界非法訪問(wèn)*/
    g_ringBuff.usLenght++;
    return 1;
}

5、從隊(duì)列中讀出數(shù)據(jù)

/**
* @brief  Read_RingBuff
* @param  u8 *rData,用于保存讀取的數(shù)據(jù)
* @return 0:環(huán)形緩沖區(qū)沒(méi)有數(shù)據(jù),讀取失敗; 1:讀取成功
* @note   從環(huán)形緩沖區(qū)讀取一個(gè)uint8_t類(lèi)型的數(shù)據(jù)
*/

uint8_t Read_RingBuff(uint8_t *_usReadData)
{
    if(g_ringBuff.usLenght == 0)/*判斷非空*/
    {
        return 0;
    }
    *_usReadData = g_ringBuff.ucRing_Buff[g_ringBuff.usWrite];/*先進(jìn)先出FIFO,從緩沖區(qū)頭出*/
    g_ringBuff.usWrite = (g_ringBuff.usWrite + 1) % RINGBUFF_LEN; /*防止越界非法訪問(wèn)*/
    g_ringBuff.usLenght--;
    return 1;
}

對(duì)于讀寫(xiě)操作需要注意的地方有兩個(gè):

1:判斷隊(duì)列是否為空或者滿,如果空的話,是不允許讀取數(shù)據(jù)的,返回0。如果是滿的話,也是不允許寫(xiě)入數(shù)據(jù)的,避免將已有數(shù)據(jù)覆蓋掉。那么如果處理的速度趕不上接收的速度,可以適當(dāng)增大緩沖區(qū)的大小,用空間換取時(shí)間。2:防止指針越界非法訪問(wèn),程序有說(shuō)明,需要使用者對(duì)整個(gè)緩沖區(qū)的大小進(jìn)行把握。

四、環(huán)形緩沖器

環(huán)形緩沖器(ringr buffer),也稱(chēng)作圓形隊(duì)列(circular queue),循環(huán)緩沖區(qū)(cyclic buffer),圓形緩沖區(qū)(circula buffer),是一種用于表示一個(gè)固定尺寸、頭尾相連的緩沖區(qū)的數(shù)據(jù)結(jié)構(gòu),適合緩存數(shù)據(jù)流。

圓形緩沖區(qū)的一個(gè)有用特性是:當(dāng)一個(gè)數(shù)據(jù)元素被用掉后,其余數(shù)據(jù)元素不需要移動(dòng)其存儲(chǔ)位置。相反,一個(gè)非圓形緩沖區(qū)(例如一個(gè)普通的隊(duì)列)在用掉一個(gè)數(shù)據(jù)元素后,其余數(shù)據(jù)元素需要向前搬移。換句話說(shuō),圓形緩沖區(qū)適合實(shí)現(xiàn)先進(jìn)先出緩沖區(qū),而非圓形緩沖區(qū)適合后進(jìn)先出緩沖區(qū)。

那么如何將環(huán)形緩沖器ringr buffer應(yīng)用到串口上面呢?這里我們使用RT-Thread的源碼。

1、定義一個(gè)結(jié)構(gòu)體

/* ring buffer */
struct rt_ringbuffer
{

    uint8_t *buffer_ptr;
    uint16_t read_mirror : 1;
    uint16_t read_index : 15;
    uint16_t write_mirror : 1;
    uint16_t write_index : 15;
    uint16_t buffer_size;
};

2、初始化ringbuffer

void rt_ringbuffer_init(struct rt_ringbuffer *rb,
                        uint8_t           *pool,
                        uint16_t            size)

{
    RT_ASSERT(rb != NULL);
    RT_ASSERT(size > 0);

    /* initialize read and write index */
    rb->read_mirror = rb->read_index = 0;
    rb->write_mirror = rb->write_index = 0;

    /* set buffer pool and size */
    rb->buffer_ptr = pool;
    rb->buffer_size = RT_ALIGN_DOWN(size, RT_ALIGN_SIZE);
}

3、將數(shù)據(jù)壓入ringbuffer

unsigned long rt_ringbuffer_put(struct rt_ringbuffer *rb,
                            const uint8_t     *ptr,
                            uint16_t           length)

{
    uint16_t size;

    RT_ASSERT(rb != NULL);

    /* whether has enough space */
    size = rt_ringbuffer_space_len(rb);

    /* no space */
    if (size == 0)
        return 0;

    /* drop some data */
    if (size < length)
        length = size;

    if (rb->buffer_size - rb->write_index > length)
    {
        /* read_index - write_index = empty space */
        memcpy(&rb->buffer_ptr[rb->write_index], ptr, length);
        /* this should not cause overflow because there is enough space for
         * length of data in current mirror */

        rb->write_index += length;
        return length;
    }

    memcpy(&rb->buffer_ptr[rb->write_index],
           &ptr[0],
           rb->buffer_size - rb->write_index);
    memcpy(&rb->buffer_ptr[0],
           &ptr[rb->buffer_size - rb->write_index],
           length - (rb->buffer_size - rb->write_index));

    /* we are going into the other side of the mirror */
    rb->write_mirror = ~rb->write_mirror;
    rb->write_index = length - (rb->buffer_size - rb->write_index);

    return length;
}

4、從ringbuffer中讀數(shù)據(jù)

unsigned long rt_ringbuffer_get(struct rt_ringbuffer *rb,
                            uint8_t           *ptr,
                            uint16_t           length)

{
    unsigned long size;

    RT_ASSERT(rb != NULL);

    /* whether has enough data  */
    size = rt_ringbuffer_data_len(rb);

    /* no data */
    if (size == 0)
        return 0;

    /* less data */
    if (size < length)
        length = size;

    if (rb->buffer_size - rb->read_index > length)
    {
        /* copy all of data */
        memcpy(ptr, &rb->buffer_ptr[rb->read_index], length);
        /* this should not cause overflow because there is enough space for
         * length of data in current mirror */

        rb->read_index += length;
        return length;
    }

    memcpy(&ptr[0],
           &rb->buffer_ptr[rb->read_index],
           rb->buffer_size - rb->read_index);
    memcpy(&ptr[rb->buffer_size - rb->read_index],
           &rb->buffer_ptr[0],
           length - (rb->buffer_size - rb->read_index));

    /* we are going into the other side of the mirror */
    rb->read_mirror = ~rb->read_mirror;
    rb->read_index = length - (rb->buffer_size - rb->read_index);

    return length;
}

5、移植ringbuffer

1.首先將RT-Thread的ringbuffer.c和ringbuffer.h文件加入到我們的MDK工程中去。

干貨|高質(zhì)量代碼是怎么寫(xiě)出來(lái)的?串口環(huán)形隊(duì)列的圖5
干貨|高質(zhì)量代碼是怎么寫(xiě)出來(lái)的?串口環(huán)形隊(duì)列的圖6

2.編寫(xiě)串口相關(guān)的底層硬件bsp代碼,也就是初始化GPIO和串口相關(guān)的配置,這個(gè)就很簡(jiǎn)單,大家應(yīng)該都會(huì)。在串口初始化代碼中記得要手動(dòng)將串口的非空中斷和空閑中斷打開(kāi)。

UART_HandleTypeDef huart1;//這個(gè)要定義為全局的變量
void uart_init(u32 bound)
{
 huart1.Instance=USART1;         //USART1
 huart1.Init.BaudRate=bound;        //波特率
 huart1.Init.WordLength=UART_WORDLENGTH_8B;  //字長(zhǎng)為8位數(shù)據(jù)格式
 huart1.Init.StopBits=UART_STOPBITS_1;     //一個(gè)停止位
 huart1.Init.Parity=UART_PARITY_NONE;   //無(wú)奇偶校驗(yàn)位
 huart1.Init.HwFlowCtl=UART_HWCONTROL_NONE;   //無(wú)硬件流控
 huart1.Init.Mode=UART_MODE_TX_RX;      //收發(fā)模式
 HAL_UART_Init(&huart1);//HAL_UART_Init()會(huì)使能UART1  
 
 __HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);/*使能串口接收非空中斷 */ 
 __HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE); /*使能串口接收空閑中斷 */
 
}

void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
    //GPIO端口設(shè)置
 GPIO_InitTypeDef GPIO_InitStruct;
 
 if(huart->Instance==USART1)//如果是串口1,進(jìn)行串口1 MSP初始化
 {
  __HAL_RCC_USART1_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

  GPIO_InitStruct.Pin = GPIO_PIN_9;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  GPIO_InitStruct.Pin = GPIO_PIN_10;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
        //使用中斷肯定要配置NVIC
  HAL_NVIC_SetPriority(USART1_IRQn, 10);
  HAL_NVIC_EnableIRQ(USART1_IRQn);

 }
}

3.定義一個(gè)結(jié)構(gòu)頭rt_ringbuffer 類(lèi)型的變量ring_buf,變量名隨便取,阿貓阿狗都可以,只要你自己認(rèn)得就行。

struct rt_ringbuffer ring_buf; 

4.定義一個(gè)串口接收緩沖區(qū)數(shù)組,數(shù)組名隨便取,阿貓阿狗都可以,只要你自己認(rèn)得就行。

static uint8_t  s_USART1_RxBuf[256];

5.初始化ringbuffer

rt_ringbuffer_init(&ring_buf,s_USART1_RxBuf,256);

位置放在哪里都可以,我這里就放在串口串口初始化之前了。

干貨|高質(zhì)量代碼是怎么寫(xiě)出來(lái)的?串口環(huán)形隊(duì)列的圖7

6.編寫(xiě)中斷服務(wù)函數(shù)

void USART1_IRQHandler(void)                 

 uint8_t receive_char;
 
 if((__HAL_USART_GET_FLAG(&huart1, UART_FLAG_RXNE) != RESET)) //接收非空中斷
    {
  HAL_UART_Receive(&huart1, &receive_char, 11000);//將收到的數(shù)據(jù)放入receive_char  
  rt_ringbuffer_put(&ring_buf,&receive_char,1);//將receive_char壓入到ring_buf中
  USART1_Len++;  
  __HAL_USART_CLEAR_FLAG(&huart1,UART_FLAG_RXNE);//清除接收非空中斷
 }
  
 if((__HAL_USART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET)) //接收空閑中斷
    {
 
  rt_ringbuffer_get(&ring_buf,s_USART1_RxBuf, USART1_Len);//將rring_buf的數(shù)據(jù)讀出到到s_USART1_RxBuf中
  s_USART1_RxBuf[USART1_Len] = '\0';
  printf("%s\r\n",s_USART1_RxBuf);
  USART1_Len = 0 ;//數(shù)據(jù)幀長(zhǎng)度復(fù)位
  __HAL_USART_CLEAR_IDLEFLAG(&huart1);
 }
}

這里面的代碼我寫(xiě)的應(yīng)該很簡(jiǎn)單了,首先我們?cè)诔跏蓟惺悄芰私邮罩袛嗪涂臻e中斷,那么如果有數(shù)據(jù)過(guò)來(lái),就會(huì)觸發(fā)中斷。進(jìn)入中斷服務(wù)函數(shù),進(jìn)來(lái)之后首先判斷接收中斷標(biāo)志位是否置位為1,如果是1說(shuō)明收數(shù)據(jù)來(lái)了,通過(guò)HAL_UART_Receive函數(shù)將數(shù)據(jù)存入receive_char中,再通過(guò)rt_ringbuffer_put將數(shù)據(jù)壓入隊(duì)列之中。如果數(shù)據(jù)接收完了就會(huì)觸發(fā)空閑中斷,這時(shí)通過(guò)rt_ringbuffer_get函數(shù)將數(shù)據(jù)讀出到我們定義的數(shù)組中打印出來(lái)。

7.主函數(shù)

int main(void)

 HAL_Init(); 
 Stm32_Clock_Init();
 delay_init(72);
 uart_init(115200);  
 while(1)
 { 
 }
}

主函數(shù)啥都不要寫(xiě),完了。

干貨|高質(zhì)量代碼是怎么寫(xiě)出來(lái)的?串口環(huán)形隊(duì)列的圖8

五、隊(duì)列FIFO

隊(duì)列 (Queue):是一種先進(jìn)先出(First In First Out ,簡(jiǎn)稱(chēng) FIFO)的線性表,只允許在一端插入,在另一端進(jìn)行刪除。

FIFO一般用于不同時(shí)鐘域之間的數(shù)據(jù)傳輸,比如FIFO的一端是AD數(shù)據(jù)采集,另一端是計(jì)算機(jī)的PCI總線,假設(shè)其AD采集的速率為16位 100K SPS,那么每秒的數(shù)據(jù)量為100K×16bit=1.6Mbps,而PCI總線的速度為33MHz,總線寬度32bit,其最大傳輸速率為1056Mbps,在兩個(gè)不同的時(shí)鐘域間就可以采用FIFO來(lái)作為數(shù)據(jù)緩沖。另外對(duì)于不同寬度的數(shù)據(jù)接口也可以用FIFO,例如單片機(jī)位8位數(shù)據(jù)輸出,而DSP可能是16位數(shù)據(jù)輸入,在單片機(jī)與DSP連接時(shí)就可以使用FIFO來(lái)達(dá)到數(shù)據(jù)匹配的目的。

1、定義一個(gè)結(jié)構(gòu)體

/* 串口設(shè)備結(jié)構(gòu)體 */
typedef struct
{

 USART_TypeDef *uart;  /* STM32內(nèi)部串口設(shè)備指針 */
 uint8_t *pTxBuf;   /* 發(fā)送緩沖區(qū) */
 uint8_t *pRxBuf;   /* 接收緩沖區(qū) */
 
 uint16_t usTxBufSize;  /* 發(fā)送緩沖區(qū)大小 */
 uint16_t usRxBufSize;  /* 接收緩沖區(qū)大小 */
 
 __IO uint16_t usTxWrite; /* 發(fā)送緩沖區(qū)寫(xiě)指針 */
 __IO uint16_t usTxRead;  /* 發(fā)送緩沖區(qū)讀指針 */
 __IO uint16_t usTxCount; /* 等待發(fā)送的數(shù)據(jù)個(gè)數(shù) */

 __IO uint16_t usRxWrite; /* 接收緩沖區(qū)寫(xiě)指針 */
 __IO uint16_t usRxRead;  /* 接收緩沖區(qū)讀指針 */
 __IO uint16_t usRxCount; /* 還未讀取的新數(shù)據(jù)個(gè)數(shù) */

}UART_T;

2、初始化FIFO

static void Uart_FIFO_Init(void)
{
 g_tUart1.uart = USART1;      /* STM32 串口設(shè)備 */
 g_tUart1.pTxBuf = g_TxBuf1;     /* 發(fā)送緩沖區(qū)指針 */
 g_tUart1.pRxBuf = g_RxBuf1;     /* 接收緩沖區(qū)指針 */
 g_tUart1.usTxBufSize = 1024;       /* 發(fā)送緩沖區(qū)大小 */
 g_tUart1.usRxBufSize = 1024;    /* 接收緩沖區(qū)大小 */
 g_tUart1.usTxWrite = 0;      /* 發(fā)送FIFO寫(xiě)索引 */
 g_tUart1.usTxRead = 0;      /* 發(fā)送FIFO讀索引 */
 g_tUart1.usRxWrite = 0;      /* 接收FIFO寫(xiě)索引 */
 g_tUart1.usRxRead = 0;      /* 接收FIFO讀索引 */
 g_tUart1.usRxCount = 0;      /* 接收到的新數(shù)據(jù)個(gè)數(shù)*/
 g_tUart1.usTxCount = 0;      /* 待發(fā)送的數(shù)據(jù)個(gè)數(shù) */
}

3、初始化串口

UART_HandleTypeDef UartHandle; 
static void Uart_GPIO_Config(void)
{
 GPIO_InitTypeDef  GPIO_InitStruct; 

 /* 使能 GPIO TX/RX 時(shí)鐘 */
 USART1_TX_GPIO_CLK_ENABLE();
 USART1_RX_GPIO_CLK_ENABLE();
 
 /* 使能 USARTx 時(shí)鐘 */
 USART1_CLK_ENABLE(); 

 /* 配置TX引腳 */
 GPIO_InitStruct.Pin       = USART1_TX_PIN;
 GPIO_InitStruct.Mode      = GPIO_MODE_AF_PP;
 GPIO_InitStruct.Pull      = GPIO_PULLUP;
 GPIO_InitStruct.Speed     = GPIO_SPEED_FREQ_HIGH;
 HAL_GPIO_Init(USART1_TX_GPIO_PORT, &GPIO_InitStruct); 
 
 /* 配置RX引腳 */
 GPIO_InitStruct.Pin = USART1_RX_PIN;
 GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
 GPIO_InitStruct.Pull = GPIO_NOPULL;
 HAL_GPIO_Init(USART1_RX_GPIO_PORT, &GPIO_InitStruct);  

 UartHandle.Instance        = USART1;
 UartHandle.Init.BaudRate   = 115200;
 UartHandle.Init.WordLength = UART_WORDLENGTH_8B;
 UartHandle.Init.StopBits   = UART_STOPBITS_1;
 UartHandle.Init.Parity     = UART_PARITY_NONE;
 UartHandle.Init.HwFlowCtl  = UART_HWCONTROL_NONE;
 UartHandle.Init.Mode       = UART_MODE_TX_RX; 
 HAL_UART_Init(&UartHandle); 
    
 __HAL_USART_CLEAR_FLAG(&UartHandle, USART_FLAG_TC);  /* 清除TC發(fā)送完成標(biāo)志 */
 __HAL_USART_CLEAR_FLAG(&UartHandle, USART_FLAG_RXNE);/* 清除RXNE接收標(biāo)志 */
 __HAL_USART_ENABLE_IT(&UartHandle, USART_IT_RXNE);   /* 使能接收數(shù)據(jù)寄存器非空中斷 */ 
 
 /* 配置NVIC the NVIC for UART */   
 HAL_NVIC_SetPriority(USART1_IRQn, 01);
 HAL_NVIC_EnableIRQ(USART1_IRQn); 
}

4、將數(shù)據(jù)壓入FIFO

什么時(shí)候要將數(shù)據(jù)壓入FIFO?當(dāng)然是在向串口發(fā)送數(shù)據(jù)的時(shí)候?qū)?shù)據(jù)壓入FIFO去啦,這里面的邏輯也很簡(jiǎn)單就是將要發(fā)送的數(shù)據(jù)壓入FIFO緩沖區(qū),之后打開(kāi)發(fā)送中斷就可以了。

void UartSendBuf(uint8_t *_ucaBuf, uint16_t _usLen)
{
 uint16_t i;

 for (i = 0; i < _usLen; i++)
 {
  /* 如果發(fā)送緩沖區(qū)已經(jīng)滿了,則等待緩沖區(qū)空 */
  while (1)
  {
   __IO uint16_t usCount;

   usCount = g_tUart1.usTxCount;

   if (usCount < g_tUart1.usTxBufSize)
   {
    break;/*如果發(fā)送緩沖區(qū)沒(méi)有滿就跳出去,讓發(fā)送緩沖區(qū)填滿*/
   }
   else if(usCount == g_tUart1.usTxBufSize)/* 數(shù)據(jù)已填滿緩沖區(qū) */
   {
    if(__HAL_UART_GET_FLAG(&UartHandle,UART_FLAG_TXE)== RESET)
    {
     __HAL_UART_ENABLE_IT(&UartHandle,UART_IT_TXE);  /* 使能發(fā)送中斷(緩沖區(qū)空) */
    }  
   }
  }
  /* 將新數(shù)據(jù)填入發(fā)送緩沖區(qū) */
  g_tUart1.pTxBuf[g_tUart1.usTxWrite] = _ucaBuf[i];
  DISABLE_INT();
  if (++g_tUart1.usTxWrite >= g_tUart1.usTxBufSize)
  {
   g_tUart1.usTxWrite = 0;
  }
  g_tUart1.usTxCount++;
  ENABLE_INT();
 }
 __HAL_UART_ENABLE_IT(&UartHandle,UART_IT_TXE); /* 使能發(fā)送中斷(緩沖區(qū)空) */
}

如果要發(fā)送的數(shù)據(jù)沒(méi)有超過(guò)發(fā)送緩沖區(qū)大小,實(shí)現(xiàn)起來(lái)還比較容易,直接把數(shù)據(jù)填到 FIFO 里面,并使能發(fā)送空中斷即可。如果超過(guò)了 FIFO 大小,就需要等待有空間可用,針對(duì)這種情況有個(gè)重要的知識(shí)點(diǎn),就是當(dāng)緩沖剛剛填滿的時(shí)候要判斷發(fā)送空中斷是否開(kāi)啟了,如果填滿了還沒(méi)有開(kāi)啟,就會(huì)卡死在 while 循環(huán)中,所以多了一個(gè)剛填滿時(shí)的判斷,填滿了還沒(méi)有開(kāi)啟發(fā)送空中斷,要開(kāi)啟下。

5、從FIFO中讀出數(shù)據(jù)

什么時(shí)候要從FIFO中讀出?當(dāng)然是在從串口獲取數(shù)據(jù)的時(shí)候?qū)腇IFO中讀數(shù)據(jù)啦。

uint8_t UartGetChar(uint8_t *_pByte)

 uint16_t usCount;
 /* usRxWrite 變量在中斷函數(shù)中被改寫(xiě),主程序讀取該變量時(shí),必須進(jìn)行臨界區(qū)保護(hù) */
 DISABLE_INT();
 usCount = g_tUart1.usRxCount;
 ENABLE_INT();

 /* 如果讀和寫(xiě)索引相同,則返回0 */
 if (usCount == 0/* 已經(jīng)沒(méi)有數(shù)據(jù) */
 {
  return 0;
 }
 else
 {
  *_pByte = g_tUart1.pRxBuf[g_tUart1.usRxRead];/* 從串口接收FIFO取1個(gè)數(shù)據(jù) */

  /* 改寫(xiě)FIFO讀索引 */
  DISABLE_INT();
  if (++g_tUart1.usRxRead >=g_tUart1.usRxBufSize)
  {
   g_tUart1.usRxRead = 0;
  }
  g_tUart1.usRxCount--;
  ENABLE_INT();
  return 1;
 }
}

6、中斷服務(wù)函數(shù)

void USART1_IRQHandler(void)

 
 /* 處理接收緩沖區(qū)中斷  */
 if(__HAL_UART_GET_FLAG(&UartHandle,UART_FLAG_RXNE)!= RESET)
 {
  /* 從串口接收數(shù)據(jù)寄存器讀取數(shù)據(jù)存放到接收FIFO */
  uint8_t ch;

  ch = READ_REG(g_tUart1.uart->DR);
  g_tUart1.pRxBuf[g_tUart1.usRxWrite] = ch;
  if (++g_tUart1.usRxWrite >= g_tUart1.usRxBufSize)
  {
   g_tUart1.usRxWrite = 0;
  }
  if (g_tUart1.usRxCount < g_tUart1.usRxBufSize)
  {
   g_tUart1.usRxCount++;
  }
 }

 /* 處理發(fā)送緩沖區(qū)空中斷 */
 if(__HAL_UART_GET_FLAG(&UartHandle,UART_FLAG_TXE)!= RESET)
 {

  if (g_tUart1.usTxCount == 0)
  {
   /* 發(fā)送緩沖區(qū)的數(shù)據(jù)已取完時(shí), 禁止發(fā)送緩沖區(qū)空中斷 (注意:此時(shí)最后1個(gè)數(shù)據(jù)還未真正發(fā)送完畢)*/
   CLEAR_BIT(g_tUart1.uart->CR1, USART_CR1_TXEIE);

   /* 使能數(shù)據(jù)發(fā)送完畢中斷 */
   __HAL_UART_ENABLE_IT(&UartHandle,UART_IT_TC);/* 使能數(shù)據(jù)發(fā)送完畢中斷 */
   
  }
  else
  {   
   /* 從發(fā)送FIFO取1個(gè)字節(jié)寫(xiě)入串口發(fā)送數(shù)據(jù)寄存器 */
   g_tUart1.uart->DR = g_tUart1.pTxBuf[g_tUart1.usTxRead];
   if (++g_tUart1.usTxRead >= g_tUart1.usTxBufSize)
   {
    g_tUart1.usTxRead = 0;
   }
   g_tUart1.usTxCount--;
  }

 }
 /* 數(shù)據(jù)bit位全部發(fā)送完畢的中斷 */
 if(__HAL_UART_GET_FLAG(&UartHandle,UART_FLAG_TC)!= RESET)
 {
  if (g_tUart1.usTxCount == 0)
  {
   /* 如果發(fā)送FIFO的數(shù)據(jù)全部發(fā)送完畢,禁止數(shù)據(jù)發(fā)送完畢中斷 */
   __HAL_USART_CLEAR_FLAG(&UartHandle,USART_FLAG_TC);/*發(fā)送完畢中斷*/
  }
}

7、主函數(shù)

const char buf1[] = "接收到串口命令1\r\n";
const char buf2[] = "接收到串口命令2\r\n";
const char buf3[] = "接收到串口命令3\r\n";
const char buf4[] = "接收到串口命令4\r\n";

int main(void)
{
    uint8_t  read[100] = {0} ; 
    bsp_Init();    

    while(1)
    {
        /* 接收到的串口命令處理 */
        if (UartGetChar(read)!=0)
        {
   UartSendBuf((uint8_t *)read, sizeof(read)/sizeof(uint8_t));
            switch (read[0])
            {
                case '1':
                    UartSendBuf((uint8_t *)buf1, strlen(buf1));
                    break;

                case '2':
                    UartSendBuf((uint8_t *)buf2, strlen(buf2));
                    break;

                case '3':
                    UartSendBuf((uint8_t *)buf3, strlen(buf3));
                    break;

                case '4':
                    UartSendBuf((uint8_t *)buf4, strlen(buf4));
                    break;

                default:
                    break;
            }
        }
    }
}
干貨|高質(zhì)量代碼是怎么寫(xiě)出來(lái)的?串口環(huán)形隊(duì)列的圖9

8、關(guān)于串口掃盲

串口掃盲就是幾個(gè)狀態(tài)標(biāo)志位不好理解,只要理解好這張圖就好辦了,其他的請(qǐng)參考相關(guān)的教程。

干貨|高質(zhì)量代碼是怎么寫(xiě)出來(lái)的?串口環(huán)形隊(duì)列的圖10
干貨|高質(zhì)量代碼是怎么寫(xiě)出來(lái)的?串口環(huán)形隊(duì)列的圖11
  1. USART_FLAG_TXE當(dāng)發(fā)送數(shù)據(jù)寄存器里的數(shù)據(jù)被全部取完時(shí),該寄存器是空的,那么該標(biāo)志位就會(huì)被置1。通過(guò)這個(gè)標(biāo)志位的值可以判斷發(fā)送數(shù)據(jù)寄存器中的數(shù)據(jù)有沒(méi)有完全被取走,當(dāng)該寄存器是空的時(shí)候,可以提醒CPU繼續(xù)往該寄存器里存入新的數(shù)據(jù);

  2. USART_FLAG_TC當(dāng)發(fā)送移位寄存器里的每個(gè)字節(jié)通過(guò)TX腳一位一位發(fā)送出去之后,該標(biāo)志位值就會(huì)被置1。通過(guò)這個(gè)標(biāo)志位的值可以判斷發(fā)送移位寄存器里的數(shù)據(jù)有沒(méi)有被全部發(fā)送出去;

  3. USART_FLAG_TXEUSART_FLAG_TC之間的聯(lián)系 結(jié)合上面流程圖來(lái)進(jìn)行說(shuō)明,實(shí)際上發(fā)送移位寄存器通過(guò)TX腳發(fā)送數(shù)據(jù)這個(gè)過(guò)程是比較耗時(shí)的,所以在此過(guò)程進(jìn)行時(shí),可通過(guò)判斷當(dāng)USART_FLAG_TXE = 1,即發(fā)送數(shù)據(jù)寄存器里的數(shù)據(jù)已被全部轉(zhuǎn)入發(fā)送移位寄存器時(shí),就讓CPU往發(fā)送數(shù)據(jù)寄存器轉(zhuǎn)入新的數(shù)據(jù)。當(dāng)發(fā)送移位寄存器把數(shù)據(jù)幀全部發(fā)送出去之后,可通過(guò)判斷USART_FLAG_TC = 1,證明數(shù)據(jù)幀的最后一個(gè)字節(jié)都已經(jīng)通過(guò)TX腳發(fā)送完了。

登錄后免費(fèi)查看全文
立即登錄
App下載
技術(shù)鄰APP
工程師必備
  • 項(xiàng)目客服
  • 培訓(xùn)客服
  • 平臺(tái)客服

TOP