最新教程下載: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 初學者重要提示
- 學習本章節前,務必優先學習第23章。
- 串口FIFO的實現跟前面章節按鍵FIFO的機制是一樣的。
- 本章節比較重要,因為后面的ESP8266,GPS,RS485,GPRS等試驗都是建立在這個驅動的基礎上實現。
- 大家自己做的板子,測試串口收發是亂碼的話,重點看stm32f4xx_hal_conf.h文件中的HSE_VALUE的大小跟板子上實際晶振大小是否一致,然后再看PLL配置。
- 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)
實驗目的:
- 學習串口與PC通信。
實驗內容:
- 啟動一個自動重裝軟件定時器,每100ms翻轉一次LED2。
實驗操作:
- 串口接收到字符命令'1',返回串口消息"接收到串口命令1"。
- 串口接收到字符命令'2',返回串口消息"接收到串口命令2"。
- 串口接收到字符命令'3',返回串口消息"接收到串口命令3"。
- 串口接收到字符命令'4',返回串口消息"接收到串口命令4"。
- K1按鍵按下,串口打印"按鍵K1按下"。
- K2按鍵按下,串口打印"按鍵K2按下"。
- 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)
實驗目的:
- 學習串口與PC通信。
實驗內容:
- 啟動一個自動重裝軟件定時器,每100ms翻轉一次LED2。
實驗操作:
- 串口接收到字符命令'1',返回串口消息"接收到串口命令1"。
- 串口接收到字符命令'2',返回串口消息"接收到串口命令2"。
- 串口接收到字符命令'3',返回串口消息"接收到串口命令3"。
- 串口接收到字符命令'4',返回串口消息"接收到串口命令4"。
- K1按鍵按下,串口打印"按鍵K1按下"。
- K2按鍵按下,串口打印"按鍵K2按下"。
- 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的實現,而且移植也比較簡單,可放心用於項目實戰。