STM32CubeMX教程9 USART/UART 异步通信
1、準備材料
開發板(正點原子stm32f407探索者開發板V2.4)
ST-LINK/V2驅動
STM32CubeMX軟件(Version 6.10.0)
keil μVision5 IDE(MDK-Arm)
CH340G Windows系統驅動程序(CH341SER.EXE)
XCOM V2.6串口助手
邏輯分析儀nanoDLA
2、實驗目標
使用STM32CubeMX軟件配置STM32F407開發板USART1與PC進行異步通信(阻塞傳輸方式、中斷傳輸方式),具體為 使用WK_UP按鍵觸發串口輸出,每按下一次WK_UP按鍵就以中斷方式發送一次數據,并在串口傳輸完成中斷回調函數中輸出提示信息和翻轉RED_LED燈的狀態,同時使用串口中斷接收回調函數完成對用戶發來的命令解析,發送命令“#1;”則點亮GREEN_LED,發送命令“#0;”則熄滅GREEN_LED。
3、實驗流程
3.0、前提知識
USART為通用同步異步收發器,是一種串行通信接口,類似的通信協議還有USB、RS232和RS485等,他們之間電平不同因此不可以直接通信,但是可以通過轉換芯片進行邏輯電平的相互轉換,從而實現在不同的串行通信方式下的信息傳輸
對于STM32F4系列來說,USART的高電平1表示的電壓范圍為2.0V3.3V(通常VDD電源電壓的大約70-100%),低電平0的電壓范圍為0V0.3V;USART通信中一般需要設置波特率、數據字長、校驗位和停止位四個參數,如下圖所示位串行數據發送時序圖
波特率:由于本實驗的串口工作在異步通信模式,因此需要規定一個特定的傳輸速率,這樣收發雙方都以該速率解析發送的內容,才能不出錯的進行通信,常見波特率9600/115200等,當然也可自定義波特率
數據字長:可選8/9位,即一幀數據中傳輸的數據位數,由于一字節為8位,因此該參數默認為8位
校驗位:可選無/奇/偶校驗
停止位:可選1/2個停止位,一般選擇1個停止位
設置波特率為115200,8位字長,無校驗位,1個停止位,利用單片機串口發送“Reset\r\n”信息,然后利用邏輯分析儀對TX引腳電平進行捕獲,如下圖所示為TX引腳捕獲電平波形圖
STM32F407ZGT6一共有6組串口,包括4組通用同步/異步收發器USART1、2、3、6和2組通用異步收發器UART4、5,通用異步收發器可以工作在異步通信、單線半雙工、多處理器通信、紅外和局域互連網絡(LIN)等模式,而通用同步/異步收發器除可以工作在上述模式外還具有同步通信和智能卡等工作模式,本文只介紹這6組串口的異步通信模式(最常用的模式),其他模式均不涉及,如下圖示為USART1可選工作模式列表
單片機的串口并不能直接和電腦的USB端口通信,因而需要在單片機和電腦之間利用串口芯片搭建溝通的橋梁,常用的串口芯片有CH34XX和CP210X,對于串口芯片一般需要安裝驅動程序,請自行查看開發板串口所示用的串口芯片,然后下載對應驅動程序,一般來說能夠實現電腦和單片機正常串口通信需要滿足“電腦USB接口 ? 開發板USB接口 ? 串口芯片 ? 單片機串口RX/TX引腳”的物理連接(注釋1),當其他的一切均正常使用USB線連接電腦與開發板,在Windows的設備管理器頁面,端口欄目下會出現對應串口芯片識別成功的端口號,如下圖所示
串口通信中數據傳輸一般可以分為阻塞式數據傳輸和非阻塞式數據傳輸兩種,而阻塞模式也即輪詢模式,在此模式下,串口發送或者接收數據都會產生阻塞,單片機只能一直等待接收/發送完成或者達到設定的超時時間;非阻塞模式是使用中斷或者DMA的方式來傳輸數據,顧名思義,不會產生阻塞現象,發送/接收數據的同時單片機還可以處理其他任務。本文不涉及DMA,因此非阻塞模式僅僅介紹使用中斷的傳輸方式
3.1、CubeMX相關配置
請先閱讀“STM32CubeMX教程1 工程建立”實驗3.4.1小節配置RCC和SYS
3.1.1、時鐘樹配置
系統時鐘樹配置與上一實驗一致,均設置為STM32F407總線能達到的最高時鐘頻率,具體如下圖所示
3.1.2、外設參數配置
在Pinout & Configuration頁面左邊功能分類欄目Connectivity中單擊其中USART1
頁面中間USART1 Mode and Configuration中將串口模式設置為異步通信工作模式,無硬件流控制
然后在Configuration頁面中設置USART1的相關參數,主要有波特率、字長、奇偶校驗位、停止位、數據方向和過采樣率6個參數,一般無需更改,但要確保接收端設置與發送端一致,其他5個串口在異步通信模式下與USART1一致,唯一區別在于RX/TX引腳不同,具體參數解釋可以閱讀本實驗“3.0、前提知識”小節
具體設置如下圖所示
3.1.3、外設中斷配置
在頁面左邊功能分類欄目中單擊System Core/NVIC,勾選USART1全局中斷,并設置合適的中斷優先級
如果在串口中斷中會使用到HAL庫的延時函數,注意不要與滴答定時器優先級一致(注釋2)
具體設置如下圖所示
3.2、生成代碼
請先閱讀“STM32CubeMX教程1 工程建立”實驗3.4.3小節配置Project Manager
單擊頁面右上角GENERATE CODE生成工程
3.2.1、外設初始化函數調用流程
在工程代碼主函數main()中調用MX_USART1_UART_Init()函數對串口1相關參數進行了配置
在該MX_USART1_UART_Init()函數中調用了HAL_UART_Init()函數對串口1進行了初始化
在該初始化HAL_UART_Init()函數中又調用了HAL_UART_MspInit()函數對串口1時鐘,中斷,引腳復用做了相關配置
如下圖所示為具體的USART1初始化調用流程
此時我們就可以讓串口工作在阻塞模式下,通過如下所示的兩個函數阻塞式的發送或接收數據
HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout)
HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
3.2.2、外設中斷函數調用流程
勾選USART1全局中斷后,在工程文件stm32f4xx_it.c中生成了USART1全局中斷服務函數USART1_IRQHandler()
該函數調用了HAL庫的串口統一中斷處理函數HAL_UART_IRQHandler(),在該函數中通過一系列的判斷,最終根據不同的串口事件調用不同的回調函數
當串口以中斷方式發送完成數據時會調用串口完成中斷傳輸回調函數HAL_UART_TxCpltCallback()
當串口以中斷方式接收完成數據時會調用串口中斷接收完畢回調函數HAL_UART_RxCpltCallback()
如下圖所示為具體的USART1串口Tx傳輸完成中斷調用流程
同理,感興趣的可以自己找一找中斷接收完畢回調函數HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)的調用流程
**用戶只需記住經過上述3.1.2和3.1.3所做的配置生成的代碼,然后重新實現HAL_UART_TxCpltCallback(UART_HandleTypeDef huart)、HAL_UART_RxCpltCallback(UART_HandleTypeDef huart)兩個函數,第一個函數為串口中斷發送完畢回調函數,每次使用HAL_UART_Transmit_IT傳輸數據傳輸完之后就會進入該函數;第二個為串口中斷接收完畢回調函數,使用HAL_UART_Receive_IT接收數據時,一旦數據接收完畢之后就會進入該函數
3.2.3、添加其他必要代碼
需要提到一點是,使用中斷的方式接收指定長度數據時,一旦接收一次完畢,第二次不會自動啟動接收,此時需要用戶手動調用以中斷方式接收串口數據的函數HAL_UART_Receive_IT。而一個串口往往有三種狀態,要么在發送數據,要么在接收數據,要么在偷懶處于空閑狀態,因此在空閑狀態時重新啟動中斷串口接收是比較正確的選擇,這里就需要我們自己設置一個串口的空閑中斷回調函數on_UART_IDLE,當接受完一次數據后,將空閑中斷使能,在空閑的時候進入空閑中斷回調函數,處理剛剛接收到的數據并重新啟動串口中斷接收
接下來我們來實現串口的空閑中斷回調函數,將其放在串口1的中斷服務函數中,這樣串口1的任何中斷都會調用該函數,然后在usart.c中實現該函數,在該函數中首先判斷是否是空閑中斷,如果不判斷則任何關于串口1的中斷都會執行空閑中斷回調函數函數體內容,然后清除空閑中斷標志及禁用空閑中斷,保證空閑中斷回調函數只在串口接收中斷完成后才能被觸發,接著對串口接收到的數據進行處理,具體處理函數為CMD_PROCESS函數,最后重新啟動串口中斷接收,具體函數代碼如下圖所示
串口完成中斷傳輸回調函數和中斷接收完畢回調函數重新實現在usart.c中,每次接收完數據都會進入中斷接收完畢回調函數,在該回調函數中啟動了空閑中斷,此時才可以執行空閑中斷函數體內的代碼,也就是處理命令、重新啟動串口中斷接收,值得提醒的是在串口完成中斷傳輸回調函數中使用的串口輸出是阻塞的方式輸出信息的,不可以使用中斷的方式輸出提示信息,否則將無限套娃,具體代碼如下圖所示
源代碼如下
/*串口結束傳輸中斷*/
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
printf("Into HAL_UART_TxCpltCallback Function\r\n");
HAL_GPIO_TogglePin(RED_LED_GPIO_Port,RED_LED_Pin);
}
/*串口接收完成中斷*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
//接收到固定長度數據后使能UART_IT_IDLE中斷,在UART_IT_IDLE中斷里再次接收
//接收完成標志
rxCompleted=SET;
//復制接收到的數據到緩沖區
for(uint16_t i=0;i<RX_CMD_LEN;i++)
proBuffer[i] = rxBuffer[i];
//接收到數據后才開啟IDLE中斷
__HAL_UART_ENABLE_IT(huart, UART_IT_IDLE);
}
}
/*串口空閑回調函數*/
void on_UART_IDLE(UART_HandleTypeDef *huart)
{
//判斷IDLE中斷是否被開啟
if(__HAL_UART_GET_IT_SOURCE(huart, UART_IT_IDLE) == RESET)
return;
//清除IDLE標志
__HAL_UART_CLEAR_IDLEFLAG(huart);
//禁止IDLE中斷
__HAL_UART_DISABLE_IT(huart, UART_IT_IDLE);
//接收完成
if(rxCompleted)
{
//上傳接收到的指令
printf("Receive CMD is %s\r\n",proBuffer);
//處理指令
CMD_PROCESS();
//再次接收
rxCompleted = RESET;
//再次啟動串口接收
HAL_UART_Receive_IT(huart, rxBuffer, RX_CMD_LEN);
}
}
/*接收命令處理函數*/
void CMD_PROCESS(void)
{
//非法的命令格式
if(proBuffer[0] != '#' && proBuffer[2] != ';')
{
printf("Unlawful Orders\r\n");
return;
}
//解析命令
uint8_t CMD = proBuffer[1]-0x30;
//控制GREEN_LED
if(CMD == 1)
{
HAL_GPIO_WritePin(GREEN_LED_GPIO_Port, GREEN_LED_Pin, GPIO_PIN_RESET);
printf("GREEN_LED ON\r\n");
}
else if(CMD == 0)
{
HAL_GPIO_WritePin(GREEN_LED_GPIO_Port, GREEN_LED_Pin, GPIO_PIN_SET);
printf("GREEN_LED OFF\r\n");
}
}
最后我們在主函數中以中斷方式啟動串口接收,然后編寫WK_UP按鍵響應函數,每按下一次按鍵以中斷方式發送一次數據,具體的代碼如下圖所示
上述代碼中的一些變量均定義/聲明在了usart.c/usart.h中,具體源代碼如下
/*usart.c中定義的變量*/
uint8_t rxBuffer[3]="#0;"; //數據接收緩沖區
uint8_t proBuffer[3]="#1;"; //數據處理緩沖區
uint8_t rxCompleted=RESET; //數據接收完成標志
/*usart.h中聲明的變量*/
#define RX_CMD_LEN 3 //數據接收長度
extern uint8_t rxBuffer[]; //外部聲明
void on_UART_IDLE(UART_HandleTypeDef *huart); //函數聲明
void CMD_PROCESS(void); //函數聲明
/*main()函數按鍵WK_UP控制代碼*/
if(HAL_GPIO_ReadPin(WK_UP_GPIO_Port,WK_UP_Pin) == GPIO_PIN_SET)
{
HAL_Delay(50);
if(HAL_GPIO_ReadPin(WK_UP_GPIO_Port,WK_UP_Pin) == GPIO_PIN_SET)
{
HAL_UART_Transmit_IT(&huart1, (uint8_t *)"Key WK_UP Pressed!\r\n", 20);
while(HAL_GPIO_ReadPin(WK_UP_GPIO_Port,WK_UP_Pin));
}
}
4、常用函數
/*串口阻塞接收數據*/
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
/*串口阻塞發送數據*/
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout)
/*串口中斷接收數據*/
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
/*串口中斷發送數據*/
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size)
/*串口中斷接收數據完畢回調函數*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
/*串口中斷發送數據完畢回調函數*/
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
5、燒錄驗證
5.1、具體步驟
“啟動USART1異步通信模式 -> 配置串口相關參數 -> 使能USART1全局中斷 -> 在usart.c中重新實現①串口結束傳輸中斷回調函數HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)②串口接收完畢中斷回調函數void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) -> 添加串口空閑回調函數on_UART_IDLE -> 實現命令解析函數CMD_PROCESS -> 添加本實驗控制代碼(具體代碼請看上述3.2小節)”
5.2、實驗現象
燒錄程序,開發板上電,此時按鍵WK_UP被按下,串口會同時輸出信息,輸出完畢后進入串口結束傳輸中斷回調函數,輸出提示信息并將RED_LED狀態翻轉,PC發送"#1;"給MCU,串口輸出接收到的信息,然后解析命令,打開GREEN_LED,PC發送"#0;"給MCU,串口輸出接收到的信息,然后解析命令,熄滅GREEN_LED,按鍵WK_UP又被按下,串口輸出信息,輸出完畢后進入串口結束傳輸中斷回調函數,輸出提示信息并將RED_LED狀態翻轉(注釋3),如下圖所示為串口的詳細輸出信息
6、串口printf重定向
用戶阻塞式的發送一條數據時使用的HAL_UART_Transmit函數需要指定發送數據的字節數,非常的不方便,因此簡單使用串口傳輸數據時有必要將其重定向到我們熟悉的printf函數,以下為具體步驟
首先需要在工程設置頁面勾選“Use MicroLIB”,如下圖所示
然后在工程main.c文件中加入printf函數所需的頭文件“#include <stdio.h> ”,并在主函數上方添加重定向函數,如下圖所示,紅框中的串口實例可以替換成任何正常的串口實例
源代碼如下
#include <stdio.h>
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
之后在工程任何文件處均可使用printf函數用作串口函數阻塞輸出從而替代HAL_UART_Transmit函數(在其它文件使用記得添加頭文件#include <stdio.h>)
7、注釋詳解
注釋1:如果你覺得自己的一切配置都沒有問題,但是串口就是沒有任何字符輸出,可以用串口模塊嘗試開發板上其他的串口引腳,因為有時候開發板的某一個串口引腳可能被其他外設使用,物理上造成了沖突,無法用軟件解決,比如筆者之前使用的STM32F407G-DISC1開發板其USART1就不能正常使用
注釋2:如果設置串口中斷優先級與系統滴答定時器優先級一致,那么在串口中斷服務函數中使用HAL庫的延時函數HAL_Delay的話,系統滴答定時器不能搶占串口中斷,因此會出現程序卡死在HAL_Delay函數的情況
注釋3:注意筆者此實驗只是簡單介紹每個功能的使用方法,這里的代碼其實是有BUG的,如果用戶不按照"#1;"/"#0;"的命令格式發送數據,而是只發送1個字符,比如"q",然后再按照"#1;"/"#0;"的命令格式發送數據,那么程序接收到的命令將錯亂,導致不能正常解析命令
參考資料
STM32Cube高效開發教程(基礎篇)
更多內容請瀏覽 OSnotes的CSDN博客
總結
以上是生活随笔為你收集整理的STM32CubeMX教程9 USART/UART 异步通信的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 文心一言 VS 讯飞星火 VS chat
- 下一篇: 分享历经软考高项二次上岸经验