串口原理圖
串口1咱們已經(jīng)用作rtt的print使用了,所以使用另外一組串口來(lái)進(jìn)行串口的教程,這里一定要注意下,alios的這個(gè)板子原理圖是有點(diǎn)問(wèn)題的,標(biāo)注的是串口3PA2和PA3,實(shí)際上小飛哥調(diào)了好久,最后萬(wàn)用表量引腳才發(fā)現(xiàn)是原理圖標(biāo)注錯(cuò)誤,實(shí)際上是UART4,PA0和PA1
cubemx中引腳選擇預(yù)配置
選擇PA0、PA1,配置為串口模式,波特率什么的見(jiàn)圖示:
開(kāi)啟中斷,優(yōu)先級(jí)可以根據(jù)自己的需求配置,本次不使用DMA,所以DMA就先不進(jìn)行配置了
配置是非常簡(jiǎn)單的,就不多啰嗦了,配置完直接生成代碼就OK了
HAL庫(kù)串口代碼詳解
cubemx里面配置了一大堆,生成的應(yīng)用代碼主要在初始化中:
關(guān)于串口的接口是很多的,本次主要使用3個(gè)接口,發(fā)送、接收和接收回調(diào)
HAL庫(kù)數(shù)據(jù)接收的設(shè)計(jì)思想是底層配置完成后,暴露給用戶的是一組回調(diào)函數(shù),用戶不用關(guān)心底層實(shí)現(xiàn),只需要關(guān)注應(yīng)用層邏輯即可,回調(diào)函數(shù)是定義為_(kāi)weak屬性的接口,用戶可以在應(yīng)用層實(shí)現(xiàn)
/** *@briefRxTransfercompletedcallback. *@paramhuartUARThandle. *@retvalNone */ __weakvoidHAL_UART_RxCpltCallback(UART_HandleTypeDef*huart) { /*Preventunusedargument(s)compilationwarning*/ UNUSED(huart); /*NOTE:Thisfunctionshouldnotbemodified,whenthecallbackisneeded, theHAL_UART_RxCpltCallbackcanbeimplementedintheuserfile. */ }
發(fā)送也有對(duì)應(yīng)的callback,我們只需要在callback處理我們的邏輯即可。
串口收發(fā)設(shè)計(jì)
教程不玩虛的,本章節(jié)小飛哥從實(shí)際應(yīng)用出發(fā),通過(guò)解析協(xié)議數(shù)據(jù),順便講解uart的收發(fā)設(shè)計(jì)。
1、串口接收:
先來(lái)看看HAL庫(kù)串口接收的接口函數(shù),這就是使用庫(kù)函數(shù)的好處,底層實(shí)現(xiàn)不用關(guān)心,只要會(huì)用接口就行了
/** *@briefReceiveanamountofdataininterruptmode. *@noteWhenUARTparityisnotenabled(PCE=0),andWordLengthisconfiguredto9bits(M1-M0=01), *thereceiveddataishandledasasetofu16.Inthiscase,Sizemustindicatethenumber *ofu16availablethroughpData. *@paramhuartUARThandle. *@parampDataPointertodatabuffer(u8oru16dataelements). *@paramSizeAmountofdataelements(u8oru16)tobereceived. *@retvalHALstatus */ HAL_StatusTypeDefHAL_UART_Receive_IT(UART_HandleTypeDef*huart,uint8_t*pData,uint16_tSize);
如何使用這個(gè)接口接收數(shù)據(jù)呢?
從接口描述可以看到,第1個(gè)參數(shù)是我們的串口號(hào),第2個(gè)參數(shù)數(shù)我們用于接收數(shù)據(jù)的buffer,第3個(gè)參數(shù)是數(shù)據(jù)長(zhǎng)度,即要接受的數(shù)據(jù)量,這里我們每次僅接收一個(gè)數(shù)據(jù)即進(jìn)入邏輯處理
每次取一個(gè)數(shù)據(jù),放到rxdata的變量中
HAL_UART_Receive_IT(&huart4,&rxdata,1);
HAL庫(kù)所有的串口是共享一個(gè)回調(diào)函數(shù)的,那么如何區(qū)分?jǐn)?shù)據(jù)是來(lái)自哪一個(gè)串口的?這個(gè)邏輯可以在應(yīng)用實(shí)現(xiàn),區(qū)分不同的串口號(hào),根據(jù)對(duì)應(yīng)的串口號(hào)實(shí)現(xiàn)對(duì)應(yīng)的邏輯即可
voidHAL_UART_RxCpltCallback(UART_HandleTypeDef*huart) { if(huart->Instance==UART4) { //rt_sem_release(sem_uart_rec); embedded_set_uart_rec_flag(RT_TRUE); embedded_set_uart_timeout_cnt(0); HAL_UART_Receive_IT(&huart4,&rxdata,1); mb_process_frame(rxdata,CHANNEL_MODBUS); } }
2、數(shù)據(jù)幀接收完成判斷
通訊基本上都是不定長(zhǎng)數(shù)據(jù)的接收,一般對(duì)于一個(gè)完整的通訊幀來(lái)說(shuō),是有長(zhǎng)度字段的,分以下幾種接收完成判斷方式
特殊數(shù)據(jù)格式,比如結(jié)束符,像正點(diǎn)原子串口教程的“回車(chē)、換行(0x0D,0x0A)”
數(shù)據(jù)長(zhǎng)度,適用已知數(shù)據(jù)長(zhǎng)度的數(shù)據(jù)幀,根據(jù)接收到的數(shù)據(jù)長(zhǎng)度跟數(shù)據(jù)幀里面的長(zhǎng)度是否一致,判斷接受是否完成
超時(shí)判斷,定時(shí)器設(shè)計(jì)一個(gè)超時(shí)機(jī)制,一定時(shí)間內(nèi)沒(méi)有數(shù)據(jù)進(jìn)來(lái)即認(rèn)為數(shù)據(jù)傳輸結(jié)束
空閑中斷,串口是有個(gè)空閑中斷的,這個(gè)實(shí)現(xiàn)類(lèi)似于超時(shí)機(jī)制
也可以從軟件設(shè)計(jì)實(shí)現(xiàn),比如設(shè)計(jì)一個(gè)隊(duì)列,取數(shù)據(jù)即可,隊(duì)列中沒(méi)數(shù)據(jù)即認(rèn)為數(shù)據(jù)接受完成
方式有很多,本章節(jié)主要使用數(shù)據(jù)長(zhǎng)度和定時(shí)器超時(shí)兩種方式來(lái)講解
3、串口發(fā)送
串口發(fā)送比較簡(jiǎn)單,先來(lái)看看發(fā)送接口函數(shù),類(lèi)似接收函數(shù),只需要把我們的數(shù)據(jù)放進(jìn)發(fā)送buffer,啟動(dòng)發(fā)送即可
/** *@briefSendanamountofdatainblockingmode. *@noteWhenUARTparityisnotenabled(PCE=0),andWordLengthisconfiguredto9bits(M1-M0=01), *thesentdataishandledasasetofu16.Inthiscase,Sizemustindicatethenumber *ofu16providedthroughpData. *@noteWhenFIFOmodeisenabled,writingadataintheTDRregisteraddsone *datatotheTXFIFO.WriteoperationstotheTDRregisterareperformed *whenTXFNFflagisset.Fromhardwareperspective,TXFNFflagand *TXEaremappedonthesamebit-field. *@paramhuartUARThandle. *@parampDataPointertodatabuffer(u8oru16dataelements). *@paramSizeAmountofdataelements(u8oru16)tobesent. *@paramTimeoutTimeoutduration. *@retvalHALstatus */ HAL_StatusTypeDefHAL_UART_Transmit(UART_HandleTypeDef*huart,constuint8_t*pData,uint16_tSize,uint32_tTimeout);
數(shù)據(jù)接收及協(xié)議幀解析設(shè)計(jì)
數(shù)據(jù)接收:
基于數(shù)據(jù)長(zhǎng)度和超時(shí)時(shí)間完成數(shù)據(jù)幀發(fā)送完成的判斷:
定時(shí)器中斷回調(diào)設(shè)計(jì),實(shí)現(xiàn)邏輯為,當(dāng)收到串口數(shù)據(jù)時(shí),開(kāi)始計(jì)時(shí),超過(guò)100ms無(wú)數(shù)據(jù)進(jìn)來(lái),認(rèn)為數(shù)據(jù)幀結(jié)束,同時(shí)釋放數(shù)據(jù)接收完成的信號(hào)量,接收到接受完成的信號(hào)量之后,重置一些數(shù)據(jù),為下一次接收做好準(zhǔn)備
voidHAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef*htim) { if(htim->Instance==TIM15) { //if(RT_EOK==rt_sem_take(sem_uart_rec,RT_WAITING_NO)) //{ if(embedded_get_uart_rec_flag()) { /*100ms超時(shí)無(wú)數(shù)據(jù)接收*/ if(embedded_get_uart_timeout_cnt()>9) { embedded_set_uart_rec_flag(RT_FALSE); rt_sem_release(sem_uart_timeout); } } //} } }
串口回調(diào)設(shè)計(jì):
串口回調(diào)要實(shí)現(xiàn)的邏輯比較簡(jiǎn)單,主要是數(shù)據(jù)接收、解析:
voidHAL_UART_RxCpltCallback(UART_HandleTypeDef*huart) { if(huart->Instance==UART4) { //rt_sem_release(sem_uart_rec); embedded_set_uart_rec_flag(RT_TRUE); embedded_set_uart_timeout_cnt(0); HAL_UART_Receive_IT(&huart4,&rxdata,1); process_frame(rxdata,CHANNEL_UART4); } }
/協(xié)議架構(gòu)/
/數(shù)據(jù)頭(2字節(jié))+數(shù)據(jù)長(zhǎng)度(2字節(jié),不包含數(shù)據(jù)頭)+功能碼+數(shù)據(jù)+校驗(yàn)碼(CRC16-MODBUS)/
我們采用這個(gè)協(xié)議框架來(lái)解析數(shù)據(jù),數(shù)據(jù)解析可以設(shè)計(jì)成一個(gè)簡(jiǎn)單的狀態(tài)機(jī),根據(jù)每一步?jīng)Q定下一步做什么
比如針對(duì)上面的協(xié)議,我們就可以分幾步設(shè)計(jì):
1、解析數(shù)據(jù)頭1;
2、解析數(shù)據(jù)頭2;
3、解析數(shù)據(jù)長(zhǎng)度;
4、接收數(shù)據(jù);
5、校驗(yàn)數(shù)據(jù)CRC;
6、調(diào)用命令回調(diào)函數(shù);
把握好這個(gè)步驟,設(shè)計(jì)其實(shí)非常簡(jiǎn)單
先來(lái)定義一個(gè)簡(jiǎn)單的枚舉,表示每一個(gè)狀態(tài):
typedefenum { STATUS_HEAD1=0, STATUS_HEAD2, STATUS_LEN, STATUS_HANDLE_PROCESS }frame_status_e;
然后封裝數(shù)據(jù)解析函數(shù):
/*協(xié)議架構(gòu)*/ /**數(shù)據(jù)頭(1字節(jié))+數(shù)據(jù)長(zhǎng)度(2字節(jié),不包含數(shù)據(jù)頭)+功能碼+數(shù)據(jù)+校驗(yàn)碼(CRC16-MODBUS)**/ #definePROTOCOL_HEAD10x5A #definePROTOCOL_HEAD20xA5 intprocess_frame(constuint8_tdata,constuint8_tchannel) { uint16_tcrc=0; uint16_tlen=0; staticframe_status_eframe_status; staticuint16_tindex=0; /*timeoutresetthereceivestatus*/ if(RT_EOK==rt_sem_take(sem_uart_timeout,RT_WAITING_NO)) { index=0; frame_status=STATUS_HEAD1; } switch(frame_status) { caseSTATUS_HEAD1: if(data==PROTOCOL_HEAD1) { frame_status=STATUS_HEAD2; buffer[index++]=data; } else { frame_status=STATUS_HEAD1; index=0; } break; caseSTATUS_HEAD2: if(data==PROTOCOL_HEAD2) { frame_status=STATUS_LEN; buffer[index++]=data; } else { frame_status=STATUS_HEAD1; index=0; } break; caseSTATUS_LEN: if(data>=0&&data<=?MAX_DATA_LEN) ????????{ ????????????frame_status?=?STATUS_HANDLE_PROCESS; ????????????buffer[index++]?=?data; ????????} ????????else ????????{ ????????????frame_status?=?STATUS_HEAD1; ????????????index?=?0; ????????} ????????break; ????case?STATUS_HANDLE_PROCESS: ????????buffer[index++]?=?data; ????????len?=?buffer[LEN_POS]; ????????if?(index?-?3?==?len) ????????{ ????????????crc?=?embedded_mbcrc16(buffer,?index?-?2); ????????????if?(crc?==?(buffer[index?-?1]?|?buffer[index?-?2]?<8)) ????????????{ ????????????????call_reg_cb(buffer,?index,?channel,?buffer[CMD_POS]); ????????????} ????????????index?=?0; ????????????frame_status?=?STATUS_HEAD1; ????????} ????????break; ????default: ????????frame_status?=?STATUS_HEAD1; ????????index?=?0; ????} }
對(duì)用的功能函數(shù):
我們采用 attribute at機(jī)制的方式,將我們的回調(diào)函數(shù)注冊(cè)進(jìn)去:
typedefvoid(*uart_dispatcher_func_t)(constuint32_t,constuint8_t*,constuint32_t); typedefstructuart_dispatcher_item { union { struct { uint8_tchannel; uint8_tcmd_id; }; uint32_tmagic_number; }; uart_dispatcher_func_tfunction; }uart_dispatcher_item_t; #defineUART_DISPATCHER_CALLBACK_REGISTER(ch,id,fn)staticconstuart_dispatcher_item_tuart_dis_table_##ch##_##id __attribute__((section("uart_dispatcher_table"),__used__,aligned(sizeof(void*))))= {.channel=ch,.cmd_id=id,.function=fn} intcall_reg_cb(uint8_t*frame,uint8_tdata_len,intchannel,uint8_tcmd_id);
回調(diào)函數(shù):
這樣設(shè)計(jì)可以把驅(qū)動(dòng)層,協(xié)議解析層和應(yīng)用層完全分開(kāi),用戶只需要注冊(cè)相關(guān)的命令,實(shí)現(xiàn)回調(diào)即可,完全不用關(guān)心底層實(shí)現(xiàn)
voiddispatcher_on_02_callback(constuint32_tchannel,constuint8_t*data,constuint32_tdata_len) { constchar*str="func02isrunning "; uart_write((uint8_t*)str,rt_strlen(str),100); rt_kprintf("func02isrunning "); } UART_DISPATCHER_CALLBACK_REGISTER(1,0x02,dispatcher_on_02_callback); voiddispatcher_on_03_callback(constuint32_tchannel,constuint8_t*data,constuint32_tdata_len) { constchar*str="func03isrunning "; uart_write((uint8_t*)str,rt_strlen(str),100); rt_kprintf("func03isrunning "); } UART_DISPATCHER_CALLBACK_REGISTER(1,0x03,dispatcher_on_03_callback); voiddispatcher_on_04_callback(constuint32_tchannel,constuint8_t*data,constuint32_tdata_len) { constchar*str="func04isrunning "; uart_write((uint8_t*)str,rt_strlen(str),100); rt_kprintf("func04isrunning "); } UART_DISPATCHER_CALLBACK_REGISTER(1,0x04,dispatcher_on_04_callback); voiddispatcher_on_05_callback(constuint32_tchannel,constuint8_t*data,constuint32_tdata_len) { rt_kprintf("func05isrunning "); } UART_DISPATCHER_CALLBACK_REGISTER(1,0x05,dispatcher_on_05_callback); voiddispatcher_on_06_callback(constuint32_tchannel,constuint8_t*data,constuint32_tdata_len) { rt_kprintf("func06isrunning "); } UART_DISPATCHER_CALLBACK_REGISTER(1,0x06,dispatcher_on_06_callback);
測(cè)試效果
通過(guò)上面的回調(diào)函數(shù)注冊(cè),我們來(lái)測(cè)試下是不是達(dá)到預(yù)期情況:
審核編輯:劉清
-
定時(shí)器
+關(guān)注
關(guān)注
23文章
3347瀏覽量
120955 -
RTT
+關(guān)注
關(guān)注
0文章
66瀏覽量
17994 -
UART接口
+關(guān)注
關(guān)注
0文章
124瀏覽量
16213 -
HAL庫(kù)
+關(guān)注
關(guān)注
1文章
121瀏覽量
7355
原文標(biāo)題:04-HAL庫(kù)UART配置及協(xié)議解析設(shè)計(jì)
文章出處:【微信號(hào):小飛哥玩嵌入式,微信公眾號(hào):小飛哥玩嵌入式】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
stm32 HAL庫(kù)UART中斷 使用說(shuō)明
STM32 HAL庫(kù) CUBEMX配置 ADC采集 精選資料分享
HAL庫(kù)配合CUBEMX配置
【STM32的HAL庫(kù)開(kāi)發(fā)】CubeMX配置HAL庫(kù),不進(jìn)串口中斷的問(wèn)題 精選資料分享
使用CUBEMX配置hal庫(kù)輸入捕獲
STM32CubeMX 配置STM32F407 實(shí)現(xiàn)HAL庫(kù)延時(shí)微妙方案

HAL庫(kù)與CubeMX系列|Cubemx新建HAL庫(kù)工程

串口通信小試牛刀~使用STM32CubeMX+ HAL庫(kù)點(diǎn)亮流水燈

STM32實(shí)戰(zhàn) 2 | STM32CubeMX及HAL庫(kù)點(diǎn)亮LED

【STM32 HAL】UART串口通訊

STM32 HAL庫(kù) CubeMX教程(五)串口通信基礎(chǔ)

STM32 HAL庫(kù) CUBEMX配置 ADC采集

STM32串口通信HAL庫(kù)配置中 UART_IT_xx與UART_FLAG_xx 的區(qū)別

評(píng)論