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