單片機(jī)串口發(fā)送數(shù)據(jù)很慢?這種方法幫助你提高!
1. 簡(jiǎn)介
串口由于使用簡(jiǎn)單,價(jià)格低廉,配合RS485芯片可以實(shí)現(xiàn)長(zhǎng)距離、抗干擾能力強(qiáng)的局域網(wǎng)絡(luò)而被廣泛使用。隨著產(chǎn)品功能的增多,需要處理的任務(wù)也越來(lái)越復(fù)雜,系統(tǒng)任務(wù)也越來(lái)越需要及時(shí)響應(yīng)。絕大多數(shù)的現(xiàn)代單片機(jī)(ARM7、Cortex-M3)串口都帶有一定數(shù)量的硬件FIFO,本文將介紹如何使用硬件FIFO來(lái)減少接收中斷次數(shù),提高發(fā)送效率。在此之前,先來(lái)列舉一下傳統(tǒng)串口數(shù)據(jù)收發(fā)的不足之處:(1)每接收一個(gè)字節(jié)數(shù)據(jù),產(chǎn)生一次接收中斷。不能有效的利用串口硬件FIFO,減少中斷次數(shù)。(2)應(yīng)答數(shù)據(jù)采用等待發(fā)送的方法。由于串行數(shù)據(jù)傳輸?shù)臅r(shí)間遠(yuǎn)遠(yuǎn)跟不上CPU的處理時(shí)間,等待串口發(fā)送完當(dāng)前字節(jié)再發(fā)送下一字節(jié)會(huì)造成CPU資源浪費(fèi),不利于系統(tǒng)整體響應(yīng)(在1200bps下,發(fā)送一字節(jié)大約需要10ms,如果一次發(fā)送幾十個(gè)字節(jié)數(shù)據(jù),CPU會(huì)長(zhǎng)時(shí)間處于等待狀態(tài))。(3)應(yīng)答數(shù)據(jù)采用中斷發(fā)送。增加一個(gè)中斷源,增加系統(tǒng)的中斷次數(shù),這會(huì)影響系統(tǒng)整體穩(wěn)定性(從可靠性角度考慮,中斷事件應(yīng)越少越好)。(4)針對(duì)上述的不足之處,將結(jié)合一個(gè)常用自定義通訊協(xié)議,提供一個(gè)完整的解決方案。2.串口FIFO
串口FIFO可以理解為串口專用的緩存,該緩存采用先進(jìn)先出方式。數(shù)據(jù)接收FIFO和數(shù)據(jù)發(fā)送FIFO通常是獨(dú)立的兩個(gè)硬件。串口接收的數(shù)據(jù),先放入接收FIFO中,當(dāng)FIFO中的數(shù)據(jù)達(dá)到觸發(fā)值(通常觸發(fā)值為1、2、4、8、14字節(jié))或者FIFO中的數(shù)據(jù)雖然沒(méi)有達(dá)到設(shè)定值但是一段時(shí)間(通常為3.5個(gè)字符傳輸時(shí)間)沒(méi)有再接收到數(shù)據(jù),則通知CPU產(chǎn)生接收中斷;發(fā)送的數(shù)據(jù)要先寫(xiě)入發(fā)送FIFO,只要發(fā)送FIFO未空,硬件會(huì)自動(dòng)發(fā)送FIFO中的數(shù)據(jù)。寫(xiě)入發(fā)送FIFO的字節(jié)個(gè)數(shù)受FIFO最大深度影響,通常一次寫(xiě)入最多允許16字節(jié)。上述列舉的數(shù)據(jù)跟具體的硬件有關(guān),CPU類型不同,特性也不盡相同,使用前應(yīng)參考相應(yīng)的數(shù)據(jù)手冊(cè)。3.數(shù)據(jù)接收與打包
FIFO可以緩存串口接收到的數(shù)據(jù),因此我們可以利用FIFO來(lái)減少中斷次數(shù)。以NXP的lpc1778芯片為例,接收FIFO的觸發(fā)級(jí)別可以設(shè)置為1、2、4、8、14字節(jié),推薦使用8字節(jié)或者14字節(jié),這也是PC串口接收FIFO的默認(rèn)值。這樣,當(dāng)接收到大量數(shù)據(jù)時(shí),每8個(gè)字節(jié)或者14個(gè)字節(jié)才會(huì)產(chǎn)生一次中斷(最后一次接收除外),相比接收一個(gè)字節(jié)即產(chǎn)生一個(gè)中斷,這種方法串口接收中斷次數(shù)大大減少。將接收FIFO設(shè)置為8或者14字節(jié)也十分簡(jiǎn)單,還是以lpc1778為例,只需要設(shè)置UART FIFO控制寄存器UnFCR即可。接收的數(shù)據(jù)要符合通訊協(xié)議規(guī)定,數(shù)據(jù)與協(xié)議是密不可分的。通常我們需要將接收到的數(shù)據(jù)根據(jù)協(xié)議打包成一幀,然后交由上層處理。下面介紹一個(gè)自定義的協(xié)議幀格式,并給出一個(gè)通用打包成幀的方法。自定義協(xié)議格式如圖3-1所示。- 幀首:通常是3~5個(gè)0xFF或者0xEE
- 地址號(hào):要進(jìn)行通訊的設(shè)備的地址編號(hào),1字節(jié)
- 命令號(hào):對(duì)應(yīng)不同的功能,1字節(jié)
- 長(zhǎng)度:數(shù)據(jù)區(qū)域的字節(jié)個(gè)數(shù),1字節(jié)
- 數(shù)據(jù):與具體的命令號(hào)有關(guān),數(shù)據(jù)區(qū)長(zhǎng)度可以為0,整個(gè)幀的長(zhǎng)度不應(yīng)超過(guò)256字節(jié)
- 校驗(yàn):異或和校驗(yàn)(1字節(jié))或者CRC16校驗(yàn)(2字節(jié)),本例使用CRC16校驗(yàn)
3.1 定義數(shù)據(jù)結(jié)構(gòu)
typedef?struct?{??
????uint8_t?*?dst_buf;??????????????????//指向接收緩存??
????uint8_t?sfd;????????????????????????//幀首標(biāo)志,為0xFF或者0xEE??
????uint8_t?sfd_flag;???????????????????//找到幀首,一般是3~5個(gè)FF或EE??
????uint8_t?sfd_count;??????????????????//幀首的個(gè)數(shù),一般3~5個(gè)??
????uint8_t?received_len;???????????????//已經(jīng)接收的字節(jié)數(shù)??
????uint8_t?find_fram_flag;?????????????//找到完整幀后,置1??
????uint8_t?frame_len;??????????????????//本幀數(shù)據(jù)總長(zhǎng)度,這個(gè)區(qū)域是可選的??
}find_frame_struct;
3.2 初始化數(shù)據(jù)結(jié)構(gòu),一般放在串口初始化中
/**?*?@brief????初始化尋找?guī)臄?shù)據(jù)結(jié)構(gòu)?
*?@param????p_fine_frame:指向打包幀數(shù)據(jù)結(jié)構(gòu)體變量?
*?@param????dst_buf:指向幀緩沖區(qū)?
*?@param????sfd:幀首標(biāo)志,一般為0xFF或者0xEE?
*/??
void?init_find_frame_struct(find_frame_struct?*?p_find_frame,uint8_t?*dst_buf,uint8_t?sfd)??
{??
????p_find_frame->dst_buf=dst_buf;??
????p_find_frame->sfd=sfd;??
????p_find_frame->find_fram_flag=0;??
????p_find_frame->frame_len=10;???????
????p_find_frame->received_len=0;??
????p_find_frame->sfd_count=0;??
????p_find_frame->sfd_flag=0;??
}?
3.3 數(shù)據(jù)打包程序
/**?*?@brief????尋找一幀數(shù)據(jù)??返回處理的數(shù)據(jù)個(gè)數(shù)?
*?@param????p_find_frame:指向打包幀數(shù)據(jù)結(jié)構(gòu)體變量?
*?@param????src_buf:指向串口接收的原始數(shù)據(jù)?
*?@param????data_len:src_buf本次串口接收到的原始數(shù)據(jù)個(gè)數(shù)?
*?@param????sum_len:幀緩存的最大長(zhǎng)度?
*?@return???本次處理的數(shù)據(jù)個(gè)數(shù)?
*/??
uint32_t?find_one_frame(find_frame_struct?*?p_find_frame,const?uint8_t?*?src_buf,uint32_t?data_len,uint32_t?sum_len)??
{??
????uint32_t?src_len=0;??
????while(data_len--)??
????{??
????????if(p_find_frame?->sfd_flag==0)????????????????????????
????????{???//沒(méi)有找到起始幀首??
????????????if(src_buf[src_len ]==p_find_frame?->sfd)??
????????????{??
????????????????p_find_frame?->dst_buf[p_find_frame?->received_len ]=p_find_frame?->sfd;??
????????????????if( p_find_frame?->sfd_count==5)??????????
????????????????{??
????????????????????p_find_frame?->sfd_flag=1;??
????????????????????p_find_frame?->sfd_count=0;??
????????????????????p_find_frame?->frame_len=10;??
????????????????}??
????????????}??
????????????else??
????????????{??
????????????????p_find_frame?->sfd_count=0;???
????????????????p_find_frame?->received_len=0;???
????????????}??
????????}??
????????else???
????????{???//是否是"長(zhǎng)度"字節(jié)??Y->獲取這幀的數(shù)據(jù)長(zhǎng)度??
????????????if(7==p_find_frame?->received_len)????????????????
????????????{??
????????????????p_find_frame->frame_len=src_buf[src_len] 5 1 1 1 2;?//幀首 地址號(hào) 命令號(hào) 數(shù)據(jù)長(zhǎng)度 校驗(yàn)???????
????????????????if(p_find_frame->frame_len>=sum_len)??
????????????????{???//這里處理方法根據(jù)具體應(yīng)用不一定相同??
????????????????????MY_DEBUGF(SLAVE_DEBUG,("數(shù)據(jù)長(zhǎng)度超出緩存!\n"));??
????????????????????p_find_frame->frame_len=?sum_len;???????
????????????????}??
????????????}??
??????????????
????????????p_find_frame?->dst_buf[p_find_frame->received_len ]=src_buf[src_len ];????????????????
????????????if(p_find_frame?->received_len==p_find_frame?->frame_len)??????????????????
????????????{??
????????????????p_find_frame?->received_len=0;??????????????//一幀完成????
????????????????p_find_frame?->sfd_flag=0;??
????????????????p_find_frame?->find_fram_flag=1;???????????????????
????????????????return?src_len;??
????????????}??
????????}??
????}??
????p_find_frame?->find_fram_flag=0;??
????return?src_len;??
}?
使用例子:定義數(shù)據(jù)結(jié)構(gòu)體變量:find_frame_struct?slave_find_frame_srt;
定義接收數(shù)據(jù)緩沖區(qū):#define?SLAVE_REC_DATA_LEN??128
uint8_t?slave_rec_buf[SLAVE_REC_DATA_LEN];
在串口初始化中調(diào)用結(jié)構(gòu)體變量初始化函數(shù):init_find_frame_struct(