串口原理圖
串口1咱們已經(jīng)用作rtt的print使用了,所以使用另外一組串口來進(jìn)行串口的教程,這里一定要注意下,alios的這個(gè)板子原理圖是有點(diǎn)問題的,標(biāo)注的是串口3PA2和PA3,實(shí)際上小飛哥調(diào)了好久,最后萬用表量引腳才發(fā)現(xiàn)是原理圖標(biāo)注錯(cuò)誤,實(shí)際上是UART4,PA0和PA1
cubemx中引腳選擇預(yù)配置
選擇PA0、PA1,配置為串口模式,波特率什么的見圖示:
開啟中斷,優(yōu)先級可以根據(jù)自己的需求配置,本次不使用DMA,所以DMA就先不進(jìn)行配置了
配置是非常簡單的,就不多啰嗦了,配置完直接生成代碼就OK了
HAL庫串口代碼詳解
cubemx里面配置了一大堆,生成的應(yīng)用代碼主要在初始化中:
關(guān)于串口的接口是很多的,本次主要使用3個(gè)接口,發(fā)送、接收和接收回調(diào)
HAL庫數(shù)據(jù)接收的設(shè)計(jì)思想是底層配置完成后,暴露給用戶的是一組回調(diào)函數(shù),用戶不用關(guān)心底層實(shí)現(xiàn),只需要關(guān)注應(yīng)用層邏輯即可,回調(diào)函數(shù)是定義為_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ā)送也有對應(yīng)的callback,我們只需要在callback處理我們的邏輯即可。
串口收發(fā)設(shè)計(jì)
教程不玩虛的,本章節(jié)小飛哥從實(shí)際應(yīng)用出發(fā),通過解析協(xié)議數(shù)據(jù),順便講解uart的收發(fā)設(shè)計(jì)。
1、串口接收:
先來看看HAL庫串口接收的接口函數(shù),這就是使用庫函數(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ù)是我們的串口號,第2個(gè)參數(shù)數(shù)我們用于接收數(shù)據(jù)的buffer,第3個(gè)參數(shù)是數(shù)據(jù)長度,即要接受的數(shù)據(jù)量,這里我們每次僅接收一個(gè)數(shù)據(jù)即進(jìn)入邏輯處理
每次取一個(gè)數(shù)據(jù),放到rxdata的變量中
HAL_UART_Receive_IT(&huart4,&rxdata,1);
HAL庫所有的串口是共享一個(gè)回調(diào)函數(shù)的,那么如何區(qū)分?jǐn)?shù)據(jù)是來自哪一個(gè)串口的?這個(gè)邏輯可以在應(yīng)用實(shí)現(xiàn),區(qū)分不同的串口號,根據(jù)對應(yīng)的串口號實(shí)現(xiàn)對應(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ù)幀接收完成判斷
通訊基本上都是不定長數(shù)據(jù)的接收,一般對于一個(gè)完整的通訊幀來說,是有長度字段的,分以下幾種接收完成判斷方式
特殊數(shù)據(jù)格式,比如結(jié)束符,像正點(diǎn)原子串口教程的“回車、換行(0x0D,0x0A)”
數(shù)據(jù)長度,適用已知數(shù)據(jù)長度的數(shù)據(jù)幀,根據(jù)接收到的數(shù)據(jù)長度跟數(shù)據(jù)幀里面的長度是否一致,判斷接受是否完成
超時(shí)判斷,定時(shí)器設(shè)計(jì)一個(gè)超時(shí)機(jī)制,一定時(shí)間內(nèi)沒有數(shù)據(jù)進(jìn)來即認(rèn)為數(shù)據(jù)傳輸結(jié)束
空閑中斷,串口是有個(gè)空閑中斷的,這個(gè)實(shí)現(xiàn)類似于超時(shí)機(jī)制
也可以從軟件設(shè)計(jì)實(shí)現(xiàn),比如設(shè)計(jì)一個(gè)隊(duì)列,取數(shù)據(jù)即可,隊(duì)列中沒數(shù)據(jù)即認(rèn)為數(shù)據(jù)接受完成
方式有很多,本章節(jié)主要使用數(shù)據(jù)長度和定時(shí)器超時(shí)兩種方式來講解
3、串口發(fā)送
串口發(fā)送比較簡單,先來看看發(fā)送接口函數(shù),類似接收函數(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ù)長度和超時(shí)時(shí)間完成數(shù)據(jù)幀發(fā)送完成的判斷:
定時(shí)器中斷回調(diào)設(shè)計(jì),實(shí)現(xiàn)邏輯為,當(dāng)收到串口數(shù)據(jù)時(shí),開始計(jì)時(shí),超過100ms無數(shù)據(jù)進(jìn)來,認(rèn)為數(shù)據(jù)幀結(jié)束,同時(shí)釋放數(shù)據(jù)接收完成的信號量,接收到接受完成的信號量之后,重置一些數(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í)無數(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)的邏輯比較簡單,主要是數(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ù)長度(2字節(jié),不包含數(shù)據(jù)頭)+功能碼+數(shù)據(jù)+校驗(yàn)碼(CRC16-MODBUS)/
我們采用這個(gè)協(xié)議框架來解析數(shù)據(jù),數(shù)據(jù)解析可以設(shè)計(jì)成一個(gè)簡單的狀態(tài)機(jī),根據(jù)每一步?jīng)Q定下一步做什么
比如針對上面的協(xié)議,我們就可以分幾步設(shè)計(jì):
1、解析數(shù)據(jù)頭1;
2、解析數(shù)據(jù)頭2;
3、解析數(shù)據(jù)長度;
4、接收數(shù)據(jù);
5、校驗(yàn)數(shù)據(jù)CRC;
6、調(diào)用命令回調(diào)函數(shù);
把握好這個(gè)步驟,設(shè)計(jì)其實(shí)非常簡單
先來定義一個(gè)簡單的枚舉,表示每一個(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ù)長度(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; ????} }
對用的功能函數(shù):
我們采用 attribute at機(jī)制的方式,將我們的回調(diào)函數(shù)注冊進(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)用層完全分開,用戶只需要注冊相關(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);
測試效果
通過上面的回調(diào)函數(shù)注冊,我們來測試下是不是達(dá)到預(yù)期情況:
審核編輯:劉清
-
定時(shí)器
+關(guān)注
關(guān)注
23文章
3248瀏覽量
114770 -
RTT
+關(guān)注
關(guān)注
0文章
65瀏覽量
17122 -
UART接口
+關(guān)注
關(guān)注
0文章
124瀏覽量
15293 -
HAL庫
+關(guān)注
關(guān)注
1文章
121瀏覽量
6227
原文標(biāo)題:04-HAL庫UART配置及協(xié)議解析設(shè)計(jì)
文章出處:【微信號:小飛哥玩嵌入式,微信公眾號:小飛哥玩嵌入式】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論