【STM32F407開發板用戶手冊】第24章 STM32F407的USART應用之八個串口FIFO實現


最新教程下載:http://www.armbbs.cn/forum.php?mod=viewthread&tid=93255

第24章       STM32F407的USART應用之八個串口FIFO實現

本章節為大家講解STM32F407的6個串口的FIFO驅動實現,后面的ESP8266,GPS,RS485,GPRS等試驗都是建立在這個驅動的基礎上實現。

除了串口FIFO的驅動實現,RS232通信也通過本章節做個講解。

24.1 初學者重要提示

24.2 硬件設計

24.3 串口驅動設計

24.4 串口FIFO板級支持包(bsp_uart_fifo.c)

24.5 串口FIFO驅動移植和使用

24.6 實驗例程設計框架

24.7 實驗例程說明(MDK)

24.8 實驗例程說明(IAR)

24.9 總結

 

 

24.1 初學者重要提示

  1.   學習本章節前,務必優先學習第23章。
  2.   串口FIFO的實現跟前面章節按鍵FIFO的機制是一樣的。
  3.   本章節比較重要,因為后面的ESP8266,GPS,RS485,GPRS等試驗都是建立在這個驅動的基礎上實現。
  4.   大家自己做的板子,測試串口收發是亂碼的話,重點看stm32f4xx_hal_conf.h文件中的HSE_VALUE的大小跟板子上實際晶振大小是否一致,然后再看PLL配置。
  5.   CH340/CH341的USB轉串口Windows驅動程序的安裝包,支持32/64位 Windows 10/8.1/8/7。http://www.armbbs.cn/forum.php?mod=viewthread&tid=32826

24.2 硬件設計

STM32F407IGT6最多可以支持6個獨立的串口。其中串口4和串口5和SDIO的GPIO是共用的,也就是說,如果要用到SD卡,那么串口4和串口5將不能使用。串口功能可以分配到不同的GPIO。我們常用的引腳分配如下:

串口USART1  TX = PA9,   RX = PA10

串口USART2  TX = PA2,   RX = PA3

串口USART3  TX = PB10,  RX = PB11

串口UART4   TX = PC10,  RX = PC11 (和SDIO共用)

串口UART5   TX = PC12,  RX = PD2  (和SDIO共用)

串口USART6  TX = PG14,  RX = PC7  

STM32-V5開發板使用了4個串口設備。

  •   串口1用於RS232接口,很多例子的pritnf結果就是輸出到串口1
  •   串口2用於GPS
  •   串口3用於RS485接口
  •   串口6 用於TTL串口插座,板子上有GPRS插座和串口WIFI插座。

下面是RS232的原理圖:

 

關於232的PHY芯片SP3232E要注意以下幾個問題:

  •   SP3232E的作用是TTL電平轉RS232電平。
  •   電阻R130的作用是避免CPU復位期間,TX為高阻時串口線上出現異常數據。
  •   檢測SP3232E的好壞可以采用回環的方式,即短接T1OUT和R1IN,對應到DB9插座上就是短接引腳2和引腳3。

 

實際效果如下:

 

通過這種方式,可以在應用程序中通過串口發送幾個字符,查看是否可以正確接收來判斷232 PHY芯片是否有問題。

  •   由於這里是TTL轉RS232,如果電腦端自帶DB9串口,可以找根交叉線直接接上。如果電腦端沒有,就需要用RS232轉USB的串口線。這里要注意是RS232轉USB,不是TTL轉USB。像我們用的CH340就是RS232轉USB芯片。
  •   檢測串口線的好壞跟板子上的232 PHY一樣,將電腦端的串口助手打開,串口線接到電腦端並短接串口線的2腳和3腳,然后使用串口助手進行自收發測試即可。

24.3 串口FIFO驅動設計

24.3.1 串口FIFO框架

為了方便大家理解,先來看下串口FIFO的實現框圖:

 

  第1階段,初始化:

  • 通過函數bsp_InitUart初始化串口結構體,串口硬件參數。

  第2階段,串口中斷服務程序:

  •   接收中斷是一直開啟的。
  •   做了發送空中斷和發送完成中斷的消息處理。

  第3階段,串口數據的收發:

  •  串口發送函數會開啟發送空中斷。
  •  串口接收中斷接收到函數后,可以使用函數comGetChar獲取數據。

24.3.2 串口FIFO之相關的變量定義

串口驅動的核心文件為:bsp_uart_fifo.c, bsp_uart_fifo.h。

這里面包括有串口硬件的配置函數、中斷處理函數,以及串口的讀寫接口函數。還有ptinft函數的實現。

每個串口都有2個FIFO緩沖區,一個是用於發送數據的TX_FIFO,一個用於保存接收數據的RX_FIFO。

我們來看下這個FIFO的定義,在bsp_uart_fifo.h文件。

/* 定義串口波特率和FIFO緩沖區大小,分為發送緩沖區和接收緩沖區, 支持全雙工 */
#if UART1_FIFO_EN == 1
    #define UART1_BAUD            115200
    #define UART1_TX_BUF_SIZE    1*1024
    #define UART1_RX_BUF_SIZE    1*1024
#endif

/* 串口設備結構體 */
typedef struct
{
    USART_TypeDef *uart;        /* STM32內部串口設備指針 */
    uint8_t *pTxBuf;            /* 發送緩沖區 */
    uint8_t *pRxBuf;            /* 接收緩沖區 */
    uint16_t usTxBufSize;        /* 發送緩沖區大小 */
    uint16_t usRxBufSize;        /* 接收緩沖區大小 */
    __IO uint16_t usTxWrite;    /* 發送緩沖區寫指針 */
    __IO uint16_t usTxRead;        /* 發送緩沖區讀指針 */
    __IO uint16_t usTxCount;    /* 等待發送的數據個數 */

    __IO uint16_t usRxWrite;    /* 接收緩沖區寫指針 */
    __IO uint16_t usRxRead;        /* 接收緩沖區讀指針 */
    __IO uint16_t usRxCount;    /* 還未讀取的新數據個數 */

    void (*SendBefor)(void);     /* 開始發送之前的回調函數指針(主要用於RS485切換到發送模式) */
    void (*SendOver)(void);     /* 發送完畢的回調函數指針(主要用於RS485將發送模式切換為接收模式) */
    void (*ReciveNew)(uint8_t _byte);    /* 串口收到數據的回調函數指針 */
    uint8_t Sending;            /* 正在發送中 */
}UART_T;

bsp_uart_fifo.c文件定義變量。我們以串口1為例,其他的串口都是一樣的代碼。

/* 定義每個串口結構體變量 */
#if UART1_FIFO_EN == 1
    static UART_T g_tUart1;
    static uint8_t g_TxBuf1[UART1_TX_BUF_SIZE];        /* 發送緩沖區 */
    static uint8_t g_RxBuf1[UART1_RX_BUF_SIZE];        /* 接收緩沖區 */
#endif

關於FIFO的機制,我們在按鍵FIFO驅動已經做過詳細的介紹,這個地方就不贅述了。每個串口有兩個FIFO緩沖區,每個FIFO對應一個寫指針和一個讀指針。這個結構中還有三個回調函數。回調函數就是一個通過函數指針調用的函數。如果你把函數的指針(地址)作為參數傳遞給另一個函數,當這個指針被用為調用它所指向的函數時,我們就說這是回調函數。

24.3.3 串口FIFO初始化

串口的初始化代碼如下;

/*
*********************************************************************************************************
*    函 數 名: bsp_InitUart
*    功能說明: 初始化串口硬件,並對全局變量賦初值.
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_InitUart(void)
{
    
    UartVarInit();    /* 必須先初始化全局變量,再配置硬件 */

    InitHardUart();    /* 配置串口的硬件參數(波特率等) */

    RS485_InitTXE();    /* 配置RS485芯片的發送使能硬件,配置為推挽輸出 */
}

下面將初始化代碼實現的功能依次為大家做個說明。

  函數UartVarInit

這個函數實現的功能比較好理解,主要是串口設備結構體變量的初始化,代碼如下:

/*
*********************************************************************************************************
*    函 數 名: UartVarInit
*    功能說明: 初始化串口相關的變量
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
static void UartVarInit(void)
{
#if UART1_FIFO_EN == 1
    g_tUart1.uart = USART1;                        /* STM32 串口設備 */
    g_tUart1.pTxBuf = g_TxBuf1;                    /* 發送緩沖區指針 */
    g_tUart1.pRxBuf = g_RxBuf1;                    /* 接收緩沖區指針 */
    g_tUart1.usTxBufSize = UART1_TX_BUF_SIZE;         /* 發送緩沖區大小 */
    g_tUart1.usRxBufSize = UART1_RX_BUF_SIZE;         /* 接收緩沖區大小 */
    g_tUart1.usTxWrite = 0;                        /* 發送FIFO寫索引 */
    g_tUart1.usTxRead = 0;                        /* 發送FIFO讀索引 */
    g_tUart1.usRxWrite = 0;                        /* 接收FIFO寫索引 */
    g_tUart1.usRxRead = 0;                        /* 接收FIFO讀索引 */
    g_tUart1.usRxCount = 0;                        /* 接收到的新數據個數 */
    g_tUart1.usTxCount = 0;                        /* 待發送的數據個數 */
    g_tUart1.SendBefor = 0;                        /* 發送數據前的回調函數 */
    g_tUart1.SendOver = 0;                        /* 發送完畢后的回調函數 */
    g_tUart1.ReciveNew = 0;                        /* 接收到新數據后的回調函數 */
    g_tUart1.Sending = 0;                        /* 正在發送中標志 */
#endif
    /* 串口2-8的初始化省略未寫 */
}

  函數InitHardUart

此函數主要用於串口的GPIO,中斷和相關參數的配置。

1.    /* 串口1的GPIO  PA9, PA10   RS323 DB9接口 */
2.    #define USART1_CLK_ENABLE()              __HAL_RCC_USART1_CLK_ENABLE()
3.    
4.    #define USART1_TX_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOA_CLK_ENABLE()
5.    #define USART1_TX_GPIO_PORT              GPIOA
6.    #define USART1_TX_PIN                    GPIO_PIN_9
7.    #define USART1_TX_AF                     GPIO_AF7_USART1
8.    
9.    #define USART1_RX_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOA_CLK_ENABLE()
10.    #define USART1_RX_GPIO_PORT              GPIOA
11.    #define USART1_RX_PIN                    GPIO_PIN_10
12.    #define USART1_RX_AF                     GPIO_AF7_USART1
13.    
14.    /* 串口2-8的引腳和時鍾宏定義未寫 */
15.    
16.    /*
17.    ******************************************************************************************************
18.    *    函 數 名: InitHardUart
19.    *    功能說明: 配置串口的硬件參數(波特率,數據位,停止位,起始位,校驗位,中斷使能)適合於STM32-F4開
20.    *              發板
21.    *    形    參: 無
22.    *    返 回 值: 無
23.    ******************************************************************************************************
24.    */
25.    static void InitHardUart(void)
26.    {
27.        GPIO_InitTypeDef  GPIO_InitStruct;
28.        RCC_PeriphCLKInitTypeDef RCC_PeriphClkInit;
29.        
30.        /* 
31.           下面這個配置可以注釋掉,預留下來是為了方便以后選擇其它時鍾使用 
32.           默認情況下,USART1和USART6選擇的PCLK2,時鍾100MHz。
33.           USART2,USART3,UART4,UART5,UART6,UART7和UART8選擇的時鍾是PLCK1,時鍾100MHz。
34.        */
35.        RCC_PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART16;
36.        RCC_PeriphClkInit.Usart16ClockSelection = RCC_USART16CLKSOURCE_D2PCLK2;
37.        HAL_RCCEx_PeriphCLKConfig(&RCC_PeriphClkInit);    
38.    
39.    #if UART1_FIFO_EN == 1        /* 串口1 */
40.        /* 使能 GPIO TX/RX 時鍾 */
41.        USART1_TX_GPIO_CLK_ENABLE();
42.        USART1_RX_GPIO_CLK_ENABLE();
43.        
44.        /* 使能 USARTx 時鍾 */
45.        USART1_CLK_ENABLE();    
46.    
47.        /* 配置TX引腳 */
48.        GPIO_InitStruct.Pin       = USART1_TX_PIN;
49.        GPIO_InitStruct.Mode      = GPIO_MODE_AF_PP;
50.        GPIO_InitStruct.Pull      = GPIO_PULLUP;
51.        GPIO_InitStruct.Speed     = GPIO_SPEED_FREQ_VERY_HIGH;
52.        GPIO_InitStruct.Alternate = USART1_TX_AF;
53.        HAL_GPIO_Init(USART1_TX_GPIO_PORT, &GPIO_InitStruct);    
54.        
55.        /* 配置RX引腳 */
56.        GPIO_InitStruct.Pin = USART1_RX_PIN;
57.        GPIO_InitStruct.Alternate = USART1_RX_AF;
58.        HAL_GPIO_Init(USART1_RX_GPIO_PORT, &GPIO_InitStruct);
59.    
60.        /* 配置NVIC the NVIC for UART */   
61.        HAL_NVIC_SetPriority(USART1_IRQn, 0, 1);
62.        HAL_NVIC_EnableIRQ(USART1_IRQn);
63.      
64.        /* 配置波特率、奇偶校驗 */
65.        bsp_SetUartParam(USART1,  UART1_BAUD, UART_PARITY_NONE, UART_MODE_TX_RX);
66.    
67.        SET_BIT(USART1->ICR, USART_ICR_TCCF);   /* 清除TC發送完成標志 */
68.        SET_BIT(USART1->RQR, USART_RQR_RXFRQ);  /* 清除RXNE接收標志 */
69.        // USART_CR1_PEIE | USART_CR1_RXNEIE
70.        SET_BIT(USART1->CR1, USART_CR1_RXNEIE); /* 使能PE. RX接受中斷 */
71.    #endif
72.    /* 串口2-6的初始化省略未寫 */
73.    }
  •   第2-12行,以宏定義的方式設置串口1-8的GPIO時鍾、引腳和串口時鍾,方便修改。
  •   第35-37行,這里的配置可以注釋掉,預留下來僅僅是為了方便以后選擇其它時鍾使用。默認情況下,USART1和USART6選擇的PCLK2,時鍾100MHz。USART2,USART3,UART4,UART5,UART6選擇的時鍾是PLCK1,時鍾100MHz。
  •   第61-62行,配置串口中斷優先級並使能串口中斷,用戶可以根據實際工程修改優先級大小。
  •   第65行,配置串口的基本參數,具體配置在函數里面有注釋。
/*
*********************************************************************************************************
*    函 數 名: bsp_SetUartParam
*    功能說明: 配置串口的硬件參數(波特率,數據位,停止位,起始位,校驗位,中斷使能)適合於STM32- F4開發板
*    形    參: Instance   USART_TypeDef類型結構體
*             BaudRate   波特率
*             Parity     校驗類型,奇校驗或者偶校驗
*             Mode       發送和接收模式使能
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_SetUartParam(USART_TypeDef *Instance,  uint32_t BaudRate, uint32_t Parity, uint32_t Mode)
{
    UART_HandleTypeDef UartHandle;    
    
    /*##-1- 配置串口硬件參數 ######################################*/
    /* 異步串口模式 (UART Mode) */
    /* 配置如下:
      - 字長    = 8 位
      - 停止位  = 1 個停止位
      - 校驗    = 參數Parity
      - 波特率  = 參數BaudRate
      - 硬件流控制關閉 (RTS and CTS signals) */

    UartHandle.Instance        = Instance;

    UartHandle.Init.BaudRate   = BaudRate;
    UartHandle.Init.WordLength = UART_WORDLENGTH_8B;
    UartHandle.Init.StopBits   = UART_STOPBITS_1;
    UartHandle.Init.Parity     = Parity;
    UartHandle.Init.HwFlowCtl  = UART_HWCONTROL_NONE;
    UartHandle.Init.Mode       = Mode;
    UartHandle.Init.OverSampling = UART_OVERSAMPLING_16;
    
    
    if (HAL_UART_Init(&UartHandle) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);
    }
}

  函數RS485_InitTXE

此函數主要用於485 PHY芯片的發送使能,直接配置引腳為推挽輸出模式即可使用。具體代碼如下:

/*
*********************************************************************************************************
*    函 數 名: RS485_InitTXE
*    功能說明: 配置RS485發送使能口線 TXE
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void RS485_InitTXE(void)
{
    GPIO_InitTypeDef gpio_init;
    
    /* 打開GPIO時鍾 */
    RS485_TXEN_GPIO_CLK_ENABLE();
    
    /* 配置引腳為推挽輸出 */
    gpio_init.Mode = GPIO_MODE_OUTPUT_PP;            /* 推挽輸出 */
    gpio_init.Pull = GPIO_NOPULL;                 /* 上下拉電阻不使能 */
    gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH;    /* GPIO速度等級 */
    gpio_init.Pin = RS485_TXEN_PIN;
    HAL_GPIO_Init(RS485_TXEN_GPIO_PORT, &gpio_init);    
}

24.3.4 串口中斷服務程序工作流程

串口中斷服務程序是最核心的部分,主要實現如下三個功能

  •   收到新的數據后,會將數據壓入RX_FIFO。
  •   檢測到發送緩沖區空后,會從TX_FIFO中取下一個數據並發送。
  •   如果是RS485半雙工串口,發送前會設置一個GPIO=1控制RS485收發器進入發送狀態,當最后一個字節的最后一個bit傳送完畢后,設置這個GPIO=0讓RS485收發器進入接收狀態。

 

下面我們分析一下串口中斷處理的完整過程。

當產生串口中斷后,CPU會查找中斷向量表,獲得中斷服務程序的入口地址。入口函數為USART1_IRQHandler,這個函數在啟動文件startup_stm32f407xx.s匯編代碼中已經有實現。我們在c代碼中需要重寫一個同樣名字的函數就可以重載它。如果不重載,啟動文件中缺省的中斷服務程序就是一個死循環,等於 while(1);

我們將串口中斷服務程序放在bsp_uart_fifo.c文件,沒有放到 stm32f4xx_it.c。當應用不需要串口功能時,直接從工程中刪除bsp_uart_fifo.c接口,不必再去整理stm32f4xx_it.c這個文件。下面展示的代碼是6個串口的中斷服務程序:

#if UART1_FIFO_EN == 1
void USART1_IRQHandler(void)
{
    UartIRQ(&g_tUart1);
}
#endif

#if UART2_FIFO_EN == 1
void USART2_IRQHandler(void)
{
    UartIRQ(&g_tUart2);
}
#endif

#if UART3_FIFO_EN == 1
void USART3_IRQHandler(void)
{
    UartIRQ(&g_tUart3);
}
#endif

#if UART4_FIFO_EN == 1
void UART4_IRQHandler(void)
{
    UartIRQ(&g_tUart4);
}
#endif

#if UART5_FIFO_EN == 1
void UART5_IRQHandler(void)
{
    UartIRQ(&g_tUart5);
}
#endif

#if UART6_FIFO_EN == 1
void USART6_IRQHandler(void)
{
    UartIRQ(&g_tUart6);
}
#endif

大家可以看到,這6個中斷服務程序都調用了同一個處理函數UartIRQ。我們只需要調通一個串口FIFO驅動,那么其他的串口驅動也就都通了。

下面,我們來看看UartIRQ函數的實現代碼。

/*
*********************************************************************************************************
*    函 數 名: UartIRQ
*    功能說明: 供中斷服務程序調用,通用串口中斷處理函數
*    形    參: _pUart : 串口設備
*    返 回 值: 無
*********************************************************************************************************
*/
static void UartIRQ(UART_T *_pUart)
{
    uint32_t isrflags   = READ_REG(_pUart->uart->ISR);
    uint32_t cr1its     = READ_REG(_pUart->uart->CR1);
    uint32_t cr3its     = READ_REG(_pUart->uart->CR3);
    
    /* 處理接收中斷  */
    if ((isrflags & USART_ISR_RXNE) != RESET)
    {
        /* 從串口接收數據寄存器讀取數據存放到接收FIFO */
        uint8_t ch;

        ch = READ_REG(_pUart->uart->RDR);               /* 讀串口接收數據寄存器 */
        _pUart->pRxBuf[_pUart->usRxWrite] = ch;         /* 填入串口接收FIFO */
        if (++_pUart->usRxWrite >= _pUart->usRxBufSize) /* 接收FIFO的寫指針+1 */
        {
            _pUart->usRxWrite = 0;
        }
        if (_pUart->usRxCount < _pUart->usRxBufSize)    /* 統計未處理的字節個數 */
        {
            _pUart->usRxCount++;
        }

        /* 回調函數,通知應用程序收到新數據,一般是發送1個消息或者設置一個標記 */
        //if (_pUart->usRxWrite == _pUart->usRxRead)
        //if (_pUart->usRxCount == 1)
        {
            if (_pUart->ReciveNew)
            {
                _pUart->ReciveNew(ch); /* 比如,交給MODBUS解碼程序處理字節流 */
            }
        }
    }

    /* 處理發送緩沖區空中斷 */
    if ( ((isrflags & USART_ISR_TXE) != RESET) && (cr1its & USART_CR1_TXEIE) != RESET)
    {
        //if (_pUart->usTxRead == _pUart->usTxWrite) 
        if (_pUart->usTxCount == 0)  /* 發送緩沖區已無數據可取 */
        {
        /* 發送緩沖區的數據已取完時, 禁止發送緩沖區空中斷 (注意:此時最后1個數據還未真正發送完畢)*/
            //USART_ITConfig(_pUart->uart, USART_IT_TXE, DISABLE);
            CLEAR_BIT(_pUart->uart->CR1, USART_CR1_TXEIE);

            /* 使能數據發送完畢中斷 */
            //USART_ITConfig(_pUart->uart, USART_IT_TC, ENABLE);
            SET_BIT(_pUart->uart->CR1, USART_CR1_TCIE);
        }
        Else  /* 還有數據等待發送 */
        {
            _pUart->Sending = 1;
            
            /* 從發送FIFO取1個字節寫入串口發送數據寄存器 */
            //USART_SendData(_pUart->uart, _pUart->pTxBuf[_pUart->usTxRead]);
            _pUart->uart->TDR = _pUart->pTxBuf[_pUart->usTxRead];
            if (++_pUart->usTxRead >= _pUart->usTxBufSize)
            {
                _pUart->usTxRead = 0;
            }
            _pUart->usTxCount--;
        }

    }
    /* 數據bit位全部發送完畢的中斷 */
    if (((isrflags & USART_ISR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET))
    {
        //if (_pUart->usTxRead == _pUart->usTxWrite)
        if (_pUart->usTxCount == 0)
        {
            /* 如果發送FIFO的數據全部發送完畢,禁止數據發送完畢中斷 */
            //USART_ITConfig(_pUart->uart, USART_IT_TC, DISABLE);
            CLEAR_BIT(_pUart->uart->CR1, USART_CR1_TCIE);

            /* 回調函數, 一般用來處理RS485通信,將RS485芯片設置為接收模式,避免搶占總線 */
            if (_pUart->SendOver)
            {
                _pUart->SendOver();
            }
            
            _pUart->Sending = 0;
        }
        else
        {
            /* 正常情況下,不會進入此分支 */

            /* 如果發送FIFO的數據還未完畢,則從發送FIFO取1個數據寫入發送數據寄存器 */
            //USART_SendData(_pUart->uart, _pUart->pTxBuf[_pUart->usTxRead]);
            _pUart->uart->TDR = _pUart->pTxBuf[_pUart->usTxRead];
            if (++_pUart->usTxRead >= _pUart->usTxBufSize)
            {
                _pUart->usTxRead = 0;
            }
            _pUart->usTxCount--;
        }
    }
    
    /* 清除中斷標志 */
    SET_BIT(_pUart->uart->ICR, UART_CLEAR_PEF);
    SET_BIT(_pUart->uart->ICR, UART_CLEAR_FEF);
    SET_BIT(_pUart->uart->ICR, UART_CLEAR_NEF);
    SET_BIT(_pUart->uart->ICR, UART_CLEAR_OREF);
    SET_BIT(_pUart->uart->ICR, UART_CLEAR_IDLEF);
    SET_BIT(_pUart->uart->ICR, UART_CLEAR_TCF);
    SET_BIT(_pUart->uart->ICR, UART_CLEAR_LBDF);
    SET_BIT(_pUart->uart->ICR, UART_CLEAR_CTSF);
    SET_BIT(_pUart->uart->ICR, UART_CLEAR_CMF);
    SET_BIT(_pUart->uart->ICR, UART_CLEAR_WUF);
    SET_BIT(_pUart->uart->ICR, UART_CLEAR_TXFECF);    
}

中斷服務程序的處理主要分為兩部分,接收數據的處理和發送數據的處理,詳情看程序注釋即可,已經比較詳細,下面重點把思路說一下。

  •   接收數據處理

接收數據的處理是判斷ISR寄存器的USART_ISR_RXNE標志是否置位,如果置位表示RDR接收寄存器已經存入數據。然后將數據讀入到接收FIFO空間。

特別注意里面的ReciveNew處理,這個在Modbus協議里面要用到。

  •   發送數據處理

發送數據主要是發送空中斷TEX和發送完成中斷TC的處理,當TXE=1時,只是表示發送數據寄存器為空了,此時可以填充下一個准備發送的數據了。當為TDR發送寄存器賦值后,硬件啟動發送,等所有的bit傳送完畢后,TC標志設置為1。如果是RS232全雙工通信,可以只用TXE標志控制發送過程。如果是RS485半雙工通信,就需要利用TC標志了,因為在最后一個bit傳送完畢后,需要設置RS485收發器進入到接收狀態。

24.3.5 串口數據發送

串口數據的發送主要涉及到下面三個函數:

/*
*********************************************************************************************************
*    函 數 名: comSendBuf
*    功能說明: 向串口發送一組數據。數據放到發送緩沖區后立即返回,由中斷服務程序在后台完成發送
*    形    參: _ucPort: 端口號(COM1 - COM8)
*              _ucaBuf: 待發送的數據緩沖區
*              _usLen : 數據長度
*    返 回 值: 無
*********************************************************************************************************
*/
void comSendBuf(COM_PORT_E _ucPort, uint8_t *_ucaBuf, uint16_t _usLen)
{
    UART_T *pUart;

    pUart = ComToUart(_ucPort);
    if (pUart == 0)
    {
        return;
    }

    if (pUart->SendBefor != 0)
    {
        pUart->SendBefor();        /* 如果是RS485通信,可以在這個函數中將RS485設置為發送模式 */
    }

    UartSend(pUart, _ucaBuf, _usLen);
}

/*
*********************************************************************************************************
*    函 數 名: comSendChar
*    功能說明: 向串口發送1個字節。數據放到發送緩沖區后立即返回,由中斷服務程序在后台完成發送
*    形    參: _ucPort: 端口號(COM1 - COM8)
*              _ucByte: 待發送的數據
*    返 回 值: 無
*********************************************************************************************************
*/
void comSendChar(COM_PORT_E _ucPort, uint8_t _ucByte)
{
    comSendBuf(_ucPort, &_ucByte, 1);
}
/*
*********************************************************************************************************
*    函 數 名: UartSend
*    功能說明: 填寫數據到UART發送緩沖區,並啟動發送中斷。中斷處理函數發送完畢后,自動關閉發送中斷
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
static void UartSend(UART_T *_pUart, uint8_t *_ucaBuf, uint16_t _usLen)
{
    uint16_t i;

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

            DISABLE_INT();
            usCount = _pUart->usTxCount;
            ENABLE_INT();

            if (usCount < _pUart->usTxBufSize)
            {
                break;
            }
            else if(usCount == _pUart->usTxBufSize)/* 數據已填滿緩沖區 */
            {
                if((_pUart->uart->CR1 & USART_CR1_TXEIE) == 0)
                {
                    SET_BIT(_pUart->uart->CR1, USART_CR1_TXEIE);
                }  
            }
        }

        /* 將新數據填入發送緩沖區 */
        _pUart->pTxBuf[_pUart->usTxWrite] = _ucaBuf[i];

        DISABLE_INT();
        if (++_pUart->usTxWrite >= _pUart->usTxBufSize)
        {
            _pUart->usTxWrite = 0;
        }
        _pUart->usTxCount++;
        ENABLE_INT();
    }

    SET_BIT(_pUart->uart->CR1, USART_CR1_TXEIE);    /* 使能發送中斷(緩沖區空) */
}

函數comSendChar是發送一個字節,通過調用函數comSendBuf實現,而函數comSendBuf又是通過調用函數UartSend實現,這個函數是重點。

函數UartSend的作用就是把要發送的數據填到發送緩沖區里面,並使能發送空中斷。

  •   如果要發送的數據沒有超過發送緩沖區大小,實現起來還比較容易,直接把數據填到FIFO里面,並使能發送空中斷即可。
  •   如果超過了FIFO大小,就需要等待有空間可用,針對這種情況有個重要的知識點,就是當緩沖剛剛填滿的時候要判斷發送空中斷是否開啟了,如果填滿了還沒有開啟,就會卡死在while循環中,所以多了一個剛填滿時的判斷,填滿了還沒有開啟發送空中斷,要開啟下。

 

注意:由於函數UartSend做了static作用域限制,僅可在bsp_uart_fifo.c文件中調用。函數comSendChar和comSendBuf是供用戶調用的。

 

函數comSendBuf中調用了一個函數pUart = ComToUart(_ucPort),這個函數是將整數的COM端口號轉換為UART結構體指針。

 

/*
*********************************************************************************************************
*    函 數 名: ComToUart
*    功能說明: 將COM端口號轉換為UART指針
*    形    參: _ucPort: 端口號(COM1 - COM8)
*    返 回 值: uart指針
*********************************************************************************************************
*/
UART_T *ComToUart(COM_PORT_E _ucPort)
{
    if (_ucPort == COM1)
    {
        #if UART1_FIFO_EN == 1
            return &g_tUart1;
        #else
            return 0;
        #endif
    }
    else if (_ucPort == COM2)
    {
        #if UART2_FIFO_EN == 1
            return &g_tUart2;
        #else
            return 0;
        #endif
    }
    else if (_ucPort == COM3)
    {
        #if UART3_FIFO_EN == 1
            return &g_tUart3;
        #else
            return 0;
        #endif
    }
    else if (_ucPort == COM4)
    {
        #if UART4_FIFO_EN == 1
            return &g_tUart4;
        #else
            return 0;
        #endif
    }
    else if (_ucPort == COM5)
    {
        #if UART5_FIFO_EN == 1
            return &g_tUart5;
        #else
            return 0;
        #endif
    }
    else if (_ucPort == COM6)
    {
        #if UART6_FIFO_EN == 1
            return &g_tUart6;
        #else
            return 0;
        #endif
    }
    else
    {
        Error_Handler(__FILE__, __LINE__);
        return 0;
    }
}

24.3.6 串口數據接收

下面我們再來看看接收的函數:

 

/*
*********************************************************************************************************
*    函 數 名: comGetChar
*    功能說明: 從接收緩沖區讀取1字節,非阻塞。無論有無數據均立即返回。
*    形    參: _ucPort: 端口號(COM1 - COM8)
*              _pByte: 接收到的數據存放在這個地址
*    返 回 值: 0 表示無數據, 1 表示讀取到有效字節
*********************************************************************************************************
*/
uint8_t comGetChar(COM_PORT_E _ucPort, uint8_t *_pByte)
{
    UART_T *pUart;

    pUart = ComToUart(_ucPort);
    if (pUart == 0)
    {
        return 0;
    }

    return UartGetChar(pUart, _pByte);
}

/*
*********************************************************************************************************
*    函 數 名: UartGetChar
*    功能說明: 從串口接收緩沖區讀取1字節數據 (用於主程序調用)
*    形    參: _pUart : 串口設備
*              _pByte : 存放讀取數據的指針
*    返 回 值: 0 表示無數據  1表示讀取到數據
*********************************************************************************************************
*/
static uint8_t UartGetChar(UART_T *_pUart, uint8_t *_pByte)
{
    uint16_t usCount;

    /* usRxWrite 變量在中斷函數中被改寫,主程序讀取該變量時,必須進行臨界區保護 */
    DISABLE_INT();
    usCount = _pUart->usRxCount;
    ENABLE_INT();

    /* 如果讀和寫索引相同,則返回0 */
    //if (_pUart->usRxRead == usRxWrite)
    if (usCount == 0)    /* 已經沒有數據 */
    {
        return 0;
    }
    else
    {
        *_pByte = _pUart->pRxBuf[_pUart->usRxRead];        /* 從串口接收FIFO取1個數據 */

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

函數comGetChar是專門供用戶調用的,用於從接收FIFO中讀取1個數據。具體代碼的實現也比較好理解,主要是接收FIFO的空間調整。

注意:由於函數UartGetChar做了static作用域限制,僅可在bsp_uart_fifo.c文件中調用。

24.3.7 串口printf實現

printf函數是標准c庫函數。最原來的意思是打印輸出到顯示器。在單片機,我們常用它來打印調試信息到串口,通過計算機上運行的串口軟件來監視程序的運行狀態。

為什么要用printf函數,而不用串口發送的函數。因為printf函數的形參功能很強大,它支持各種數值轉換。比如將整數、浮點數轉換為字符串,支持整數左對齊、右對齊顯示等。

我們設計的很多裸機例子都是用printf函數輸出運行結果的。因為如果加上顯示屏驅動后,會將程序搞的很復雜,顯示部分的代碼量超過了例程本身要演示的核心功能代碼。用串口做輸出,移植很方便,現在很少有不帶串口的單片機。

實現printf輸出到串口,只需要在工程中添加兩個函數:

 

/*
*********************************************************************************************************
*    函 數 名: fputc
*    功能說明: 重定義putc函數,這樣可以使用printf函數從串口1打印輸出
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
int fputc(int ch, FILE *f)
{
#if 1    /* 將需要printf的字符通過串口中斷FIFO發送出去,printf函數會立即返回 */
    comSendChar(COM1, ch);
    
    return ch;
#else    /* 采用阻塞方式發送每個字符,等待數據發送完畢 */
    /* 寫一個字節到USART1 */
    USART1->TDR = ch;
    
    /* 等待發送結束 */
    while((USART1->ISR & USART_ISR_TC) == 0)
    {}
    
    return ch;
#endif
}

/*
*********************************************************************************************************
*    函 數 名: fgetc
*    功能說明: 重定義getc函數,這樣可以使用getchar函數從串口1輸入數據
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
int fgetc(FILE *f)
{

#if 1    /* 從串口接收FIFO中取1個數據, 只有取到數據才返回 */
    uint8_t ucData;

    while(comGetChar(COM1, &ucData) == 0);

    return ucData;
#else
    /* 等待接收到數據 */
    while((USART1->ISR & USART_ISR_RXNE) == 0)
    {}

    return (int)USART1->RDR;
#endif
}

printf函數是非阻塞的,執行后會立即返回,串口中斷服務程序會陸續將數據發送出去。

24.4 串口FIFO板級支持包(bsp_uart_fifo.c)

串口驅動文件bsp_uart_fifo.c主要實現了如下幾個API供用戶調用:

  •   bsp_InitUart
  •   comSendBuf
  •   comSendChar
  •   comGetChar

24.4.1 函數bsp_InitUart

函數原型:

void bsp_InitUart(void)

函數描述:

此函數主要用於串口的初始化,使用所有其它API之前,務必優先調用此函數。

使用舉例:

串口的初始化函數在bsp.c文件的bsp_Init函數里面調用。

24.4.2 函數comSendBuf

函數原型:

void comSendBuf(COM_PORT_E _ucPort, uint8_t *_ucaBuf, uint16_t _usLen);

函數描述:

此函數用於向串口發送一組數據,非阻塞方式,數據放到發送緩沖區后立即返回,由中斷服務程序在后台完成發送。

函數參數:

  •   第1個參數_ucPort是端口號,范圍COM1 - COM8。
  •   第2個參數_ucaBuf是待發送的數據緩沖區地址。
  •   第3個參數_usLen是要發送數據的字節數。

注意事項:

  •   此函數的解讀在本章24.3.5小節。
  •   發送的數據最好不要超過bsp_uart_fifo.h文件中定義的發送緩沖區大小,從而實現最優的工作方式。因為超過后需要在發送函數等待有發送空間可用。

使用舉例:

調用此函數前,務必優先調用函數bsp_InitUart進行初始化。

const char buf1[] = "接收到串口命令1\r\n";

comSendBuf(COM1, (uint8_t *)buf1, strlen(buf1));

24.4.3 函數comSendChar

函數原型:

void comSendChar(COM_PORT_E _ucPort, uint8_t _ucByte);

函數描述:

此函數用於向串口發送1個字節,非阻塞方式,數據放到發送緩沖區后立即返回,由中斷服務程序在后台完成發送。此函數是通過調用函數comSendBuf實現的。

函數參數:

  •   第1個參數_ucPort是端口號,范圍COM1 - COM8。
  •   第2個參數_ucByte是待發送的數據。

注意事項:

  •   此函數的解讀在本章24.3.2小節。

使用舉例:

調用此函數前,務必優先調用函數bsp_InitUart進行初始化。比如通過串口1發送一個字符c:

comSendChar(COM1, 'c')。

24.4.4 函數comGetChar

函數原型:

uint8_t comGetChar(COM_PORT_E _ucPort, uint8_t *_pByte)

函數描述:

此函數用於從接收緩沖區讀取1字節,非阻塞。無論有無數據均立即返回。

函數參數:

  •   第1個參數_ucPort是端口號,范圍COM1 - COM8。
  •   第2個參數_pByte用於存放接收到的數據。
  •   返回值,返回0表示無數據, 1 表示讀取到有效字節。

注意事項:

  •   此函數的解讀在本章24.3.6小節。

使用舉例:

調用此函數前,務必優先調用函數bsp_InitUart進行初始化。

比如從串口1讀取一個字符就是:comGetChar(COM1, &read)。

24.5 串口FIFO驅動移植和使用

串口FIFO移植步驟如下:

  •   第1步:復制bsp_uart_fifo.h和bsp_uart_fifo.c到自己的工程目錄,並添加到工程里面。
  •   第2步:根據自己要使用的串口和收發緩沖大小,修改下面的宏定義即可。

 

#define    UART1_FIFO_EN    1
#define    UART2_FIFO_EN    0
#define    UART3_FIFO_EN    0
#define    UART4_FIFO_EN    0
#define    UART5_FIFO_EN    0
#define    UART6_FIFO_EN    0
#define    UART7_FIFO_EN    0
#define    UART8_FIFO_EN    0

/* 定義串口波特率和FIFO緩沖區大小,分為發送緩沖區和接收緩沖區, 支持全雙工 */
#if UART1_FIFO_EN == 1
    #define UART1_BAUD            115200
    #define UART1_TX_BUF_SIZE    1*1024
    #define UART1_RX_BUF_SIZE    1*1024
#endif

#if UART2_FIFO_EN == 1
    #define UART2_BAUD            9600
    #define UART2_TX_BUF_SIZE    10
    #define UART2_RX_BUF_SIZE    2*1024
#endif

#if UART3_FIFO_EN == 1
    #define UART3_BAUD            9600
    #define UART3_TX_BUF_SIZE    1*1024
    #define UART3_RX_BUF_SIZE    1*1024
#endif

#if UART4_FIFO_EN == 1
    #define UART4_BAUD            115200
    #define UART4_TX_BUF_SIZE    1*1024
    #define UART4_RX_BUF_SIZE    1*1024
#endif

#if UART5_FIFO_EN == 1
    #define UART5_BAUD            115200
    #define UART5_TX_BUF_SIZE    1*1024
    #define UART5_RX_BUF_SIZE    1*1024
#endif

#if UART6_FIFO_EN == 1
    #define UART6_BAUD            115200
    #define UART6_TX_BUF_SIZE    1*1024
    #define UART6_RX_BUF_SIZE    1*1024
#endif
  •   第3步:這幾個驅動文件主要用到HAL庫的GPIO和串口驅動文件,簡單省事些可以添加所有HAL庫.C源文件進來。
  •   第4步,應用方法看本章節配套例子即可。

24.6 實驗例程設計框架

通過程序設計框架,讓大家先對配套例程有一個全面的認識,然后再理解細節,本次實驗例程的設計框架如下:

 

  第1階段,上電啟動階段:

  • 這部分在第14章進行了詳細說明。

  第2階段,進入main函數:

  •   第1部分,硬件初始化,主要是HAL庫,系統時鍾,滴答定時器和LED。
  •   第2部分,應用程序設計部分,實現了一個串口接收命令,返回消息的簡單功能。

24.7 實驗例程說明(MDK)

配套例子:

V5-006_串口和PC機通信(驅動支持8串口FIFO)

實驗目的:

  1. 學習串口與PC通信。

實驗內容:

  1. 啟動一個自動重裝軟件定時器,每100ms翻轉一次LED2。

實驗操作:

  1. 串口接收到字符命令'1',返回串口消息"接收到串口命令1"。
  2. 串口接收到字符命令'2',返回串口消息"接收到串口命令2"。
  3. 串口接收到字符命令'3',返回串口消息"接收到串口命令3"。
  4. 串口接收到字符命令'4',返回串口消息"接收到串口命令4"。
  5. K1按鍵按下,串口打印"按鍵K1按下"。
  6. K2按鍵按下,串口打印"按鍵K2按下"。
  7. K3按鍵按下,串口打印"按鍵K3按下"。

上電后串口打印的信息:

波特率 115200,數據位 8,奇偶校驗位無,停止位 1

 

程序設計:

  系統棧大小分配:

 

  硬件外設初始化

硬件外設的初始化是在 bsp.c 文件實現:

/*
*********************************************************************************************************
*    函 數 名: bsp_Init
*    功能說明: 初始化所有的硬件設備。該函數配置CPU寄存器和外設的寄存器並初始化一些全局變量。只需要調用一次
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_Init(void)
{
    /* 
       STM32H407 HAL 庫初始化,此時系統用的還是F407自帶的16MHz,HSI時鍾:
       - 調用函數HAL_InitTick,初始化滴答時鍾中斷1ms。
       - 設置NVIV優先級分組為4。
     */
    HAL_Init();

    /* 
       配置系統時鍾到168MHz
       - 切換使用HSE。
       - 此函數會更新全局變量SystemCoreClock,並重新配置HAL_InitTick。
    */
    SystemClock_Config();

    /* 
       Event Recorder:
       - 可用於代碼執行時間測量,MDK5.25及其以上版本才支持,IAR不支持。
       - 默認不開啟,如果要使能此選項,務必看V5開發板用戶手冊第8章
    */    
#if Enable_EventRecorder == 1  
    /* 初始化EventRecorder並開啟 */
    EventRecorderInitialize(EventRecordAll, 1U);
    EventRecorderStart();
#endif
    
    bsp_InitKey();        /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */
    bsp_InitTimer();      /* 初始化滴答定時器 */
    bsp_InitUart();        /* 初始化串口 */
    bsp_InitLed();        /* 初始化LED */    
    BEEP_InitHard();    /* 初始化蜂鳴器 */
}

  每10ms調用一次蜂鳴器和按鍵處理:

蜂鳴器處理是在滴答定時器中斷里面實現,每10ms執行一次檢測。

/*
*********************************************************************************************************
*    函 數 名: bsp_RunPer10ms
*    功能說明: 該函數每隔10ms被Systick中斷調用1次。詳見 bsp_timer.c的定時中斷服務程序。一些處理時間要求
*              不嚴格的任務可以放在此函數。比如:按鍵掃描、蜂鳴器鳴叫控制等。
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_RunPer10ms(void)
{
    bsp_KeyScan10ms();
     BEEP_Pro();
}

  主功能:

主程序實現如下操作:

  •   啟動一個自動重裝軟件定時器,每100ms翻轉一次LED2。
  •   串口接收到字符命令'1',返回串口消息"接收到串口命令1"。
  •   串口接收到字符命令'2',返回串口消息"接收到串口命令2"。
  •   串口接收到字符命令'3',返回串口消息"接收到串口命令3"。
  •   串口接收到字符命令'4',返回串口消息"接收到串口命令4"。
  •   K1按鍵按下,串口打印"按鍵K1按下"。
  •   K2按鍵按下,串口打印"按鍵K2按下"。
  •   K3按鍵按下,串口打印"按鍵K3按下"。
/*
*********************************************************************************************************
*    函 數 名: main
*    功能說明: c程序入口
*    形    參: 無
*    返 回 值: 錯誤代碼(無需處理)
*********************************************************************************************************
*/
int main(void)
{
    uint8_t ucKeyCode;    
    uint8_t read;
    const char buf1[] = "接收到串口命令1\r\n";
    const char buf2[] = "接收到串口命令2\r\n";
    const char buf3[] = "接收到串口命令3\r\n";
    const char buf4[] = "接收到串口命令4\r\n";
    
    
    bsp_Init();        /* 硬件初始化 */
    
    PrintfLogo();    /* 打印例程名稱和版本等信息 */
    PrintfHelp();    /* 打印操作提示 */

    bsp_StartAutoTimer(0, 100);    /* 啟動1個100ms的自動重裝的定時器 */
    
    /* 主程序大循環 */
    while (1)
    {
        /* CPU空閑時執行的函數,在 bsp.c */
        bsp_Idle();        
        
        /* 判斷定時器超時時間 */
        if (bsp_CheckTimer(0))    
        {
            /* 每隔100ms 進來一次 */
            /* 翻轉LED2的狀態 */
            bsp_LedToggle(2);    
        }
        
        /* 接收到的串口命令處理 */
        if (comGetChar(COM1, &read))
        {
            switch (read)
            {
                case '1':
                    comSendBuf(COM1, (uint8_t *)buf1, strlen(buf1));
                    break;

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

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

                case '4':
                    comSendBuf(COM1, (uint8_t *)buf4, strlen(buf4));
                    break;    
                
                default:
                    break;
            }
        }
        
        /* 處理按鍵事件 */
        ucKeyCode = bsp_GetKey();
        if (ucKeyCode > 0)
        {
            /* 有鍵按下 */
            switch (ucKeyCode)
            {
                case KEY_DOWN_K1:        /* 按鍵K1鍵按下 */
                    printf("按鍵K1按下\r\n");
                    bsp_LedToggle(1);    
                    break;        
                
                case KEY_DOWN_K2:        /* 按鍵K2鍵按下 */
                    printf("按鍵K2按下\r\n");
                    bsp_LedToggle(3);                    
                    break;

                case KEY_DOWN_K3:        /* 按鍵K3鍵按下 */
                    printf("按鍵K3按下\r\n");    
                    bsp_LedToggle(4);    
                    break;                
                
                default:
                    break;
            }
        }
    }
}

24.8 實驗例程說明(IAR)

配套例子:

V6-006_串口和PC機通信(驅動支持8串口FIFO)

實驗目的:

  1. 學習串口與PC通信。

實驗內容:

  1. 啟動一個自動重裝軟件定時器,每100ms翻轉一次LED2。

實驗操作:

  1. 串口接收到字符命令'1',返回串口消息"接收到串口命令1"。
  2. 串口接收到字符命令'2',返回串口消息"接收到串口命令2"。
  3. 串口接收到字符命令'3',返回串口消息"接收到串口命令3"。
  4. 串口接收到字符命令'4',返回串口消息"接收到串口命令4"。
  5. K1按鍵按下,串口打印"按鍵K1按下"。
  6. K2按鍵按下,串口打印"按鍵K2按下"。
  7. K3按鍵按下,串口打印"按鍵K3按下"。

上電后串口打印的信息:

波特率 115200,數據位 8,奇偶校驗位無,停止位 1

 

程序設計:

  系統棧大小分配:

 

  硬件外設初始化

硬件外設的初始化是在 bsp.c 文件實現:

/*
*********************************************************************************************************
*    函 數 名: bsp_Init
*    功能說明: 初始化所有的硬件設備。該函數配置CPU寄存器和外設的寄存器並初始化一些全局變量。只需要調用一次
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_Init(void)
{
    /* 
       STM32H407 HAL 庫初始化,此時系統用的還是F407自帶的16MHz,HSI時鍾:
       - 調用函數HAL_InitTick,初始化滴答時鍾中斷1ms。
       - 設置NVIV優先級分組為4。
     */
    HAL_Init();

    /* 
       配置系統時鍾到168MHz
       - 切換使用HSE。
       - 此函數會更新全局變量SystemCoreClock,並重新配置HAL_InitTick。
    */
    SystemClock_Config();

    /* 
       Event Recorder:
       - 可用於代碼執行時間測量,MDK5.25及其以上版本才支持,IAR不支持。
       - 默認不開啟,如果要使能此選項,務必看V5開發板用戶手冊第8章
    */    
#if Enable_EventRecorder == 1  
    /* 初始化EventRecorder並開啟 */
    EventRecorderInitialize(EventRecordAll, 1U);
    EventRecorderStart();
#endif
    
    bsp_InitKey();        /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */
    bsp_InitTimer();      /* 初始化滴答定時器 */
    bsp_InitUart();        /* 初始化串口 */
    bsp_InitLed();        /* 初始化LED */    
    BEEP_InitHard();    /* 初始化蜂鳴器 */
}

  每10ms調用一次蜂鳴器和按鍵處理:

蜂鳴器處理是在滴答定時器中斷里面實現,每10ms執行一次檢測。

/*
*********************************************************************************************************
*    函 數 名: bsp_RunPer10ms
*    功能說明: 該函數每隔10ms被Systick中斷調用1次。詳見 bsp_timer.c的定時中斷服務程序。一些處理時間要求
*              不嚴格的任務可以放在此函數。比如:按鍵掃描、蜂鳴器鳴叫控制等。
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_RunPer10ms(void)
{
    bsp_KeyScan10ms();
     BEEP_Pro();
}

  主功能:

主程序實現如下操作:

  •   啟動一個自動重裝軟件定時器,每100ms翻轉一次LED2。
  •   串口接收到字符命令'1',返回串口消息"接收到串口命令1"。
  •   串口接收到字符命令'2',返回串口消息"接收到串口命令2"。
  •   串口接收到字符命令'3',返回串口消息"接收到串口命令3"。
  •   串口接收到字符命令'4',返回串口消息"接收到串口命令4"。
  •   K1按鍵按下,串口打印"按鍵K1按下"。
  •   K2按鍵按下,串口打印"按鍵K2按下"。
  •   K3按鍵按下,串口打印"按鍵K3按下"。

 

/*
*********************************************************************************************************
*    函 數 名: main
*    功能說明: c程序入口
*    形    參: 無
*    返 回 值: 錯誤代碼(無需處理)
*********************************************************************************************************
*/
int main(void)
{
    uint8_t ucKeyCode;    
    uint8_t read;
    const char buf1[] = "接收到串口命令1\r\n";
    const char buf2[] = "接收到串口命令2\r\n";
    const char buf3[] = "接收到串口命令3\r\n";
    const char buf4[] = "接收到串口命令4\r\n";
    
    
    bsp_Init();        /* 硬件初始化 */
    
    PrintfLogo();    /* 打印例程名稱和版本等信息 */
    PrintfHelp();    /* 打印操作提示 */

    bsp_StartAutoTimer(0, 100);    /* 啟動1個100ms的自動重裝的定時器 */
    
    /* 主程序大循環 */
    while (1)
    {
        /* CPU空閑時執行的函數,在 bsp.c */
        bsp_Idle();        
        
        /* 判斷定時器超時時間 */
        if (bsp_CheckTimer(0))    
        {
            /* 每隔100ms 進來一次 */
            /* 翻轉LED2的狀態 */
            bsp_LedToggle(2);    
        }
        
        /* 接收到的串口命令處理 */
        if (comGetChar(COM1, &read))
        {
            switch (read)
            {
                case '1':
                    comSendBuf(COM1, (uint8_t *)buf1, strlen(buf1));
                    break;

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

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

                case '4':
                    comSendBuf(COM1, (uint8_t *)buf4, strlen(buf4));
                    break;    
                
                default:
                    break;
            }
        }
        
        /* 處理按鍵事件 */
        ucKeyCode = bsp_GetKey();
        if (ucKeyCode > 0)
        {
            /* 有鍵按下 */
            switch (ucKeyCode)
            {
                case KEY_DOWN_K1:        /* 按鍵K1鍵按下 */
                    printf("按鍵K1按下\r\n");
                    bsp_LedToggle(1);    
                    break;        
                
                case KEY_DOWN_K2:        /* 按鍵K2鍵按下 */
                    printf("按鍵K2按下\r\n");
                    bsp_LedToggle(3);                    
                    break;

                case KEY_DOWN_K3:        /* 按鍵K3鍵按下 */
                    printf("按鍵K3按下\r\n");    
                    bsp_LedToggle(4);    
                    break;                
                
                default:
                    break;
            }
        }
    }
}

 

24.9 總結

本章節就為大家講解這么多, 重點是6串口FIFO的實現,而且移植也比較簡單,可放心用於項目實戰。

 


免責聲明!

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



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