王維
摘要:探討船舶導(dǎo)航儀對(duì)多串口的需求,提出一種用GD32F305單片機(jī)擴(kuò)展多串口的方案,該方案采用USB 通信。以RK3128主板為例介紹該擴(kuò)展方案的硬件連接,接著探討了單片機(jī)程序的具體實(shí)現(xiàn),最后介紹用libusb進(jìn)行數(shù)據(jù)傳輸驗(yàn)證。
關(guān)鍵詞:串口擴(kuò)展;GD32;單片機(jī);USB
船用很多電子設(shè)備是通過(guò) RS-422串口傳輸數(shù)據(jù),比如導(dǎo)航儀通過(guò) RS-422串口傳輸 NMEA -0183數(shù)據(jù),這些數(shù)據(jù)包括定位信息,導(dǎo)航信息,船艏信息,雷達(dá)信息等。這就要求船上的顯示終端需要有很多的串口來(lái)接收和發(fā)送數(shù)據(jù)。導(dǎo)航儀主板上的 SoC 芯片原生串口數(shù)量有限,有時(shí)不能滿(mǎn)足用戶(hù)需要,這就需要外接模塊來(lái)擴(kuò)展串口。USB 總線(xiàn)連接簡(jiǎn)單,信號(hào)只需要一對(duì)差分?jǐn)?shù)據(jù)線(xiàn)傳輸,全速傳輸模式下帶寬可達(dá)12 Mbps;常見(jiàn)的船舶電子設(shè)備,其中 RS-422最高傳輸需求波特率為115200 bps,USB 全速模式下傳輸率超過(guò)其100倍。 USB總線(xiàn)擴(kuò)展多個(gè)串口具有連線(xiàn)簡(jiǎn)單,傳輸率高的優(yōu)勢(shì),適合做多串口擴(kuò)展。
本文探討的擴(kuò)展模塊基于單片機(jī) GD32F305設(shè)計(jì),采用 USB 總線(xiàn)擴(kuò)展最多5路串口。GD32F305是兆易創(chuàng)新公司出品的一個(gè)單片機(jī)系列,該系列單片機(jī)有一路USB 總線(xiàn),5路串口,CPU 核心采用 Cortex-M4,可以運(yùn)行在120 MHz,功能和性能均可滿(mǎn)足設(shè)計(jì)要求。
1信號(hào)連接框圖
本文以一款 RK3128導(dǎo)航儀主板為例,探討串口擴(kuò)展方案。圖1是主板信號(hào)連接圖,為重點(diǎn)說(shuō)明擴(kuò)展方案,信號(hào)只保留 USB 和串口部分。
RK3128 是瑞芯微出品的 ARM Cortex-A7 4 核處理器,RK3128有3個(gè)原生串口,其中串口2和SD接口復(fù)用,實(shí)際可用的原生串口只有 2 個(gè) , 不夠連接外部設(shè)備,因此采用本文所述方案擴(kuò)展串口。如上圖 RK3128 有 1 個(gè)USB OTG,和 1 個(gè) USB HOST 接口,其中 USB OTG用于其它通用外設(shè)(如 U 盤(pán),鼠標(biāo)) 和引導(dǎo)鏡像燒寫(xiě),USB HOST 接口連接 GD32F305RB 擴(kuò)展串口;GD32 的5 個(gè)串口全部引出用于連接其它船用電子設(shè)備。
GD32F305 系列單片機(jī),CPU 核心采用 Cortex-M4,最大運(yùn)行頻率為 120 MHz,內(nèi)置最少 64 KB SRAM,最少128 KB FLASH,包含1個(gè)USB OTG端口,5個(gè)串口(3個(gè) USART 和 2 個(gè) UART,5 個(gè)串口支持最高 9 Mbit/s波特率) 及其他豐富的外設(shè)資源。
2單片機(jī)程序
兆易創(chuàng)新提供了 GD32F305的固件庫(kù),其中包含工程模板,啟動(dòng)程序,豐富的外設(shè)調(diào)用程序及范例程序,并且還有用于簡(jiǎn)化 USB 固件程序設(shè)計(jì)的 USB 程序框架。為加速開(kāi)發(fā)過(guò)程,本方案充分利用了固件庫(kù),并參照其中的 USB CDC 范例代碼 , 以 USB 程序框架為基礎(chǔ)設(shè)計(jì)了 USB 通信程序。單片機(jī)程序包括串口收發(fā)程序,USB 收發(fā)程序,數(shù)據(jù)轉(zhuǎn)發(fā)及命令處理程序,圖2是整個(gè)單片機(jī)程序的概要圖。
3 串口收發(fā)程序
串口收發(fā)封裝為以下函數(shù) :
void uart_init();
int uart_read(int chn, void*dat, int size);
int uart_write(int chn, constvoid* dat, int size);
int uart_ioctl(int chn, intcmd, void * args);
uart_init() 為串口初始化函數(shù),用于初始化所有用到的串口,主要包括收發(fā)緩沖初始化,串口引腳功能初始化,功能寄存器初始化,中斷初始化。
uart_read() 為串口接收函數(shù),chn為串口編號(hào) , dat為接收數(shù)據(jù)緩沖指針,size 為數(shù)據(jù)緩沖的字節(jié)數(shù),返回值為實(shí)際讀取到的字節(jié)數(shù)。
uart_write() 為串口發(fā)送函數(shù),chn為串口編號(hào),dat為要發(fā)送的數(shù)據(jù)指針,size 為要發(fā)送的數(shù)據(jù)字節(jié)數(shù),返回實(shí)際寫(xiě)入串口發(fā)送緩沖的字節(jié)數(shù)。
uart_ioctl() 用于響應(yīng)控制命令,chn為串口編號(hào),cmd為命令編號(hào),args為命令參數(shù),返回值根據(jù)不同命令定義。uart_ioctl() 主要處理串口波特率設(shè)置,回應(yīng)當(dāng)前對(duì)應(yīng)串口發(fā)送緩沖字節(jié)數(shù)這兩個(gè)功能。
uart_read() 和uart_write() 都是非阻塞設(shè)計(jì),都是對(duì)相應(yīng)的串口收發(fā)緩沖操作,實(shí)際數(shù)據(jù)收發(fā)是在中斷函數(shù)中處理。串口的中斷處理函數(shù)uart_irq_handle() 定義如下:
如上代碼,串口中斷處理函數(shù)uart_irq_handle()調(diào)用了固件庫(kù)串口函數(shù)usart_interrupt_flag_get()來(lái)判斷當(dāng)前串口是否觸發(fā)了接收和發(fā)送中斷,usart_data_ receive()用于從當(dāng)前串口接收寄存器讀取接收到的數(shù)據(jù),usart_data_transmit()用于將1個(gè)字節(jié)的數(shù)據(jù)寫(xiě)入當(dāng)前串口的發(fā)送寄存器發(fā)送數(shù)據(jù)。bfifo_in_byte()和bfifo_out_byte()是一種環(huán)形緩沖bfifo的操作函數(shù),bfifo_in_byte()用于將1個(gè)字節(jié)數(shù)據(jù)寫(xiě)入緩沖,bfifo_ out_byte()用于從緩沖讀取1個(gè)字節(jié)數(shù)據(jù),成功讀取返回 true,如果緩沖無(wú)數(shù)據(jù)則返回 false。
串口一次收發(fā)字節(jié)數(shù)不固定,環(huán)形緩沖很適合這種中斷處理隨機(jī)字節(jié)數(shù)據(jù)流的收發(fā)。環(huán)形緩沖是一種有固定存儲(chǔ)空間的數(shù)據(jù)結(jié)構(gòu),有讀、寫(xiě)兩個(gè)指針,讀取緩沖時(shí)只操作讀指針,不會(huì)修改寫(xiě)指針;往緩沖寫(xiě)入數(shù)據(jù)時(shí)只操作寫(xiě)指針而不會(huì)修改讀指針,環(huán)形緩沖的這種指針操作機(jī)制使得操作指針時(shí)不需要對(duì)指針做中斷互斥保護(hù),因此不需要在收發(fā)數(shù)據(jù)時(shí)關(guān)閉開(kāi)啟中斷。
環(huán)形緩沖的操作,要將指針的操作限定在環(huán)形緩沖大小之內(nèi),一般可以采用取模運(yùn)算,比如以 f->ptr_out為環(huán)形緩沖的讀指針,f->size 為環(huán)形緩沖的字節(jié)大小,當(dāng)讀取完一個(gè)字節(jié),讀指針前進(jìn)為例,代碼如下:
本方案采用的bfifo參照l(shuí)inux kernel 的kfifo, 在上述基礎(chǔ)上優(yōu)化了指針的操作,將環(huán)形緩沖的大小限定為2的 n 次方,n 為整數(shù),將取模操作用與運(yùn)算替代以加速計(jì)算過(guò)程。同上述例子一樣的操作,設(shè) f->mask=f-> size-1,代碼如下:
由于串口接收到數(shù)據(jù)后,中斷處理函數(shù)將數(shù)據(jù)保存到了環(huán)形接收緩沖中,uart_read()函數(shù)只需要從環(huán)形接收緩沖將數(shù)據(jù)讀出保存到形參;uart_write()則將形參指向的數(shù)據(jù)寫(xiě)入到相應(yīng)的環(huán)形發(fā)送緩沖中,并判斷當(dāng)前串口發(fā)送中斷是否關(guān)閉,如果發(fā)送中斷關(guān)閉則重新打開(kāi),單片機(jī)將觸發(fā)發(fā)送中斷,發(fā)送環(huán)形發(fā)送緩沖的數(shù)據(jù)。
4 USB 數(shù)據(jù)收發(fā)程序
USB 數(shù)據(jù)收發(fā)程序封裝為以下函數(shù):
usb_init()為初始化函數(shù),主要初始化 USB 端口,USB 程序框架用到的定時(shí)器,USB 中斷,各種 USB 描述符等。
usb_write()為 USB 數(shù)據(jù)送函數(shù),負(fù)責(zé)將數(shù)據(jù)通過(guò) bulk 端點(diǎn)發(fā)往主機(jī),dat為要發(fā)送的數(shù)據(jù)指針,size 為要發(fā)送的數(shù)據(jù)字節(jié)數(shù),返回實(shí)際發(fā)送的字節(jié)數(shù)。
usb_read()是 USB 數(shù)據(jù)讀取函數(shù),負(fù)責(zé)讀取從主機(jī)發(fā)送往 bulk 端點(diǎn)的數(shù)據(jù),dat為數(shù)據(jù)接收緩沖指針,size 為緩沖字節(jié)數(shù),返回值為實(shí)際讀取到的字節(jié)數(shù)。
usb_set_class_callback()用于設(shè)置 USB Class 請(qǐng)求回調(diào),callback 為回調(diào)函數(shù),callback 的參數(shù)wIndex,bRequest,wValue,wLength對(duì)應(yīng) USB 標(biāo)準(zhǔn)控制傳輸?shù)南鄳?yīng)參數(shù),dat為數(shù)據(jù)緩沖指針,程序?qū)Request作為請(qǐng)求命令,當(dāng)wLength>0時(shí),程序根據(jù)bRequest內(nèi)容讀取dat或往dat寫(xiě)數(shù)據(jù)。
USB 數(shù)據(jù)收發(fā)程序相比串口數(shù)據(jù)收發(fā)程序復(fù)雜很多,因此本方案借助兆易創(chuàng)新的 USB 程序框架來(lái)簡(jiǎn)化設(shè)計(jì)。 USB 程序框架實(shí)現(xiàn)了基本的 USB 傳輸,調(diào)用固件庫(kù)提供的 USB 設(shè)備初始化函數(shù),設(shè)置好相應(yīng)的回調(diào)程序指針和 USB 描述符,可快速實(shí)現(xiàn)基本的 USB 數(shù)據(jù)傳輸。
固件庫(kù) USB 設(shè)備初始化函數(shù)為usbd_init(),其定義如下:
其中參數(shù)udev為 USB 驅(qū)動(dòng)句柄指針,usbd_init將初始化其數(shù)據(jù)結(jié)構(gòu),之后程序操作 USB 設(shè)備將用到該句柄。
參數(shù) core 為 USB 設(shè)備驅(qū)動(dòng)核心枚舉。USB 固件庫(kù)支持 USB 全速和 USB 高速設(shè)備,core 用來(lái)指示這兩種類(lèi)型設(shè)備的其中1種。(全速設(shè)備帶寬為12 Mbps,可滿(mǎn)足設(shè)計(jì),本方案實(shí)現(xiàn)的是全速設(shè)備;高速設(shè)備的帶寬為480 Mbps,實(shí)現(xiàn)高速USB 設(shè)備,需要外加 ULPI 芯片。)
參數(shù) desc 為 USB 描述符指針,desc 定義了設(shè)備描述符、配置描述符、接口描述符等。這些描述符用來(lái)描述 USB 設(shè)備的屬性和用途。主機(jī)會(huì)在枚舉設(shè)備時(shí)獲取以確定設(shè)備是什么樣的設(shè)備,需要的總線(xiàn)資源,通訊方式等。
參數(shù)class_core為 USB 類(lèi)結(jié)構(gòu)體,該結(jié)構(gòu)體定義了 USB 類(lèi)的初始化、反初始化、類(lèi)請(qǐng)求、數(shù)據(jù)收發(fā)等函數(shù)指針,程序在初始化時(shí)設(shè)置好這些指針,這些指針將在 USB 程序框架中被調(diào)用。其定義如下:
其中init為初始化函數(shù)指針,當(dāng) USB 連接時(shí)該指針指向的函數(shù)被調(diào)用,程序可在初始化函數(shù)中分配端點(diǎn),初始化收發(fā)緩沖等;deinit為反初始化函數(shù)指針,USB 連接斷開(kāi)時(shí)被調(diào)用,程序要在這里釋放資源;req_proc為設(shè)備請(qǐng)求函數(shù)指針,用于處理端點(diǎn) 0 控制傳輸,當(dāng)主機(jī)通過(guò)端點(diǎn) 0 請(qǐng)求傳輸時(shí),該指針指向的函數(shù)被調(diào)用,本方案在這里響應(yīng)類(lèi)請(qǐng)求,處理串口波特率設(shè)置和串口緩沖大小獲取;data_in是處理 data in 傳輸?shù)暮瘮?shù)指針,當(dāng)主機(jī)向 USB 設(shè)備請(qǐng)求數(shù)據(jù)時(shí),該指針指向的函數(shù)被調(diào)用,程序在這里準(zhǔn)備好要發(fā)往主機(jī)的數(shù)據(jù);data_out是處理 data out 傳輸?shù)暮瘮?shù)指針,當(dāng)主機(jī)往 USB 設(shè)備發(fā)送數(shù)據(jù)時(shí),該指針指向的函數(shù)被調(diào)用,程序在這里接收主機(jī)下發(fā)的數(shù)據(jù)。
分析 USB 程序框架,USB 數(shù)據(jù)傳輸采用 DMA,1次可能傳輸多個(gè)字節(jié)數(shù)據(jù);data_in和data_out都是在中斷處理程序中被調(diào)用,因此本文案設(shè)計(jì)一種環(huán)形緩沖加雙緩沖的方案來(lái)提高數(shù)據(jù)傳輸效率。環(huán)形緩沖用于避免變量互斥沖突,而雙緩沖用于提高 DMA 傳輸效率。
上述雙緩沖,由1個(gè)寫(xiě)緩沖和1個(gè)讀緩沖構(gòu)成,數(shù)據(jù)結(jié)構(gòu)如下:
結(jié)構(gòu)體成員 buffer 為內(nèi)存緩沖,buf_len為雙緩沖的字節(jié)數(shù),程序分配雙緩沖時(shí),分配 buffer 空間為雙倍buf_len字節(jié)數(shù); index 為數(shù)據(jù)索引,用于指示當(dāng)前讀寫(xiě)緩沖的地址;len為當(dāng)前寫(xiě)緩沖的數(shù)據(jù)字節(jié)數(shù)。
當(dāng)程序往雙緩沖寫(xiě)數(shù)據(jù)時(shí),先獲取寫(xiě)緩沖的地址,寫(xiě)緩沖的地址為buffer+index*buf_len,再將數(shù)據(jù)寫(xiě)入寫(xiě)緩沖的末尾,地址為buffer+index*buf_len+len,之后再根據(jù)數(shù)據(jù)大小累加len。
當(dāng)程序要讀取雙緩沖數(shù)據(jù)時(shí),程序先讀取當(dāng)前寫(xiě)緩沖的字節(jié)數(shù),獲取當(dāng)前寫(xiě)緩沖的內(nèi)存地址,再對(duì)雙緩沖做一次數(shù)據(jù)緩沖翻轉(zhuǎn),將原來(lái)的讀寫(xiě)緩沖互換。雙緩沖的翻轉(zhuǎn),重點(diǎn)是對(duì) index 進(jìn)行反運(yùn)算,index =!index 。當(dāng) DMA 完成一次傳輸時(shí),程序可以快速翻轉(zhuǎn)雙緩沖,將讀寫(xiě)緩沖地址交給 DMA 控制器進(jìn)行下一次數(shù)據(jù)傳輸。如此可達(dá)到減少 DMA 控制器等待時(shí)間的目的,以提高數(shù)據(jù)傳輸效率。
關(guān)于往 bulk 端點(diǎn)發(fā)送數(shù)據(jù),本方案定義了一個(gè)前文所述的環(huán)形緩沖fifo_bulk_in和雙緩沖dbuf_bulk_in來(lái)緩存數(shù)據(jù),程序通過(guò)調(diào)用usb_write()函數(shù)完成。usb_ write()主要負(fù)責(zé)將形參數(shù)據(jù)寫(xiě)入fifo_bulk_in,并檢測(cè)當(dāng)前 USB 框架是否正在傳輸數(shù)據(jù),這個(gè)狀態(tài)由變量 is_ bulk_in_busy表示,如果還未啟動(dòng)數(shù)據(jù)傳輸,則取出環(huán)形緩沖fifo_bulk_in的數(shù)據(jù)轉(zhuǎn)存至dbuf_bulk_in,翻轉(zhuǎn)dbuf_bulk_in,并調(diào)用固件庫(kù)函數(shù)usbd_ep_send()啟動(dòng)一次 DMA 傳輸。當(dāng)單片機(jī)完成一次傳輸,USB 框架會(huì)調(diào)用回調(diào)函數(shù)data_in(),此時(shí)根據(jù)data_in()傳入的端點(diǎn)號(hào),判斷端點(diǎn)號(hào)為 bulk 端點(diǎn)準(zhǔn)備 bulk 數(shù)據(jù)發(fā)送。檢測(cè)fifo_bulk_in是否有數(shù)據(jù)和上次傳輸?shù)淖止?jié)數(shù)是否為空,函數(shù)根據(jù)以下幾種情況處理:
如果fifo_bulk_in有數(shù)據(jù),則和上述usb_write()檢測(cè)到未啟動(dòng)傳輸時(shí)一樣,取fifo_bulk_in數(shù)據(jù)轉(zhuǎn)存至dbuf_bulk_in,翻轉(zhuǎn)dbuf_bulk_in,再次發(fā)起一次 DMA 傳輸。
如果fifo_bulk_in無(wú)數(shù)據(jù),則發(fā)起一次0數(shù)據(jù)傳輸以表示當(dāng)前傳輸完成
當(dāng)fifo_bulk_in無(wú)數(shù)據(jù),且上次是0數(shù)據(jù)傳輸時(shí),則將is_bulk_in_busy變量設(shè)置為 false,表示 USB 程序框架已停止 bulk 數(shù)據(jù)發(fā)送
bulk 端點(diǎn)數(shù)據(jù)接收也采用了一個(gè)環(huán)形緩沖和一個(gè)雙緩沖來(lái)緩存數(shù)據(jù),分別用變量fifo_bulk_out和dbuf_ bulk_out表示。
當(dāng)程序調(diào)用usb_read()時(shí),先從fifo_bulk_out中取數(shù)據(jù)存儲(chǔ)到形參接收緩沖,接著檢查當(dāng)前 bulk 端點(diǎn)是否正在接收數(shù)據(jù),該狀態(tài)用is_bulk_out_busy表示,當(dāng)is_bulk_out_busy值為 false 時(shí)調(diào)用固件庫(kù)函數(shù)usb_ep_recv(),發(fā)起 DMA 傳輸將數(shù)據(jù)存至dbuf_bulk_out,并將is_bulk_out_busy值設(shè)置為 true。
當(dāng) bulk 端點(diǎn)接收到數(shù)據(jù)時(shí),USB 程序框架調(diào)用data_out(),此時(shí)取出dbuf_bulk_out的接收緩沖指針和接收數(shù)據(jù)字節(jié)數(shù)。先判斷fifo_bulk_out剩余空間是否大于 bulk 端點(diǎn)最大傳輸量,如果空間足夠則翻轉(zhuǎn)dbuf_ bulk_out并調(diào)用usb_ep_recv()發(fā)起下一次 DMA 傳輸;否則設(shè)置is_bulk_out_busy值為 false,表示 bulk 端點(diǎn)接收空閑。最后通過(guò)之前暫存的dbuf_bulk_out接收緩沖指針和接收數(shù)據(jù)字節(jié)數(shù)將本次傳輸接收到的數(shù)據(jù)轉(zhuǎn)存到fifo_bulk_out完成本次 bulk 端點(diǎn)數(shù)據(jù)接收處理。
當(dāng)主機(jī)向單片機(jī)請(qǐng)求類(lèi)的控制傳輸時(shí),USB 程序框架將調(diào)用回調(diào)函數(shù)req_proc,請(qǐng)求的內(nèi)容從req_proc的參數(shù) req 獲得,req 的類(lèi)型usb_req定義如下:
程序接收到類(lèi)控制傳輸請(qǐng)求時(shí),根據(jù) req->bm? RequestType判斷當(dāng)前數(shù)據(jù)傳輸方向 s,如果是 IN 類(lèi)型的傳輸,則調(diào)用前文所述usb_set_class_callback()設(shè)置的回調(diào)函數(shù),傳遞 req 的其它參數(shù),如果 req->wLength不為0,從回調(diào)函數(shù)讀取數(shù)據(jù)到全局變量ctlbuf準(zhǔn)備將數(shù)據(jù)回傳給主機(jī)。將ctlbuf和 req->wLength傳遞給 USB 程序框架,USB 程序框架將發(fā)送數(shù)據(jù)和狀態(tài)給主機(jī)。當(dāng)數(shù)據(jù)發(fā)送完成時(shí),USB 程序框架調(diào)用前文所述 data_ in 通知程序,程序設(shè)置調(diào)用 API 通知 USB 程序框架無(wú)剩余數(shù)據(jù),完成本次控制傳輸請(qǐng)求。
如果當(dāng)前數(shù)據(jù)傳輸類(lèi)型是 OUT 傳輸時(shí),則先判斷 req->wLength是否為0,如果 req->wLength為0時(shí),直接調(diào)用前文所述usb_set_class_callback()設(shè)置的回調(diào)函數(shù)即可。當(dāng) req->wLength不為0時(shí),表示此次控制傳輸附帶數(shù)據(jù),此時(shí)程序先用全局變量last_req暫存 req 值,然后調(diào)用 API 通知 USB 程序框架將把此次傳輸?shù)臄?shù)據(jù)保存到ctlbuf。當(dāng) USB 程序框架接收完此次傳輸?shù)臄?shù)據(jù),將調(diào)用前文所述的data_out通知程序,這時(shí)程序?qū)鬟f上述last_req變量及ctlbuf通知前文所述usb_ set_class_callback()設(shè)置的回調(diào)函數(shù)。
5數(shù)據(jù)轉(zhuǎn)發(fā)程序
數(shù)據(jù)轉(zhuǎn)發(fā)程序負(fù)責(zé)將所有串口的數(shù)據(jù)通過(guò) USB 端口轉(zhuǎn)發(fā)到主機(jī),同時(shí)通過(guò) USB 端口從主機(jī)讀取數(shù)據(jù)發(fā)送給指定的串口。中間的數(shù)據(jù)傳輸采用特定的數(shù)據(jù)格式對(duì)串口數(shù)據(jù)進(jìn)行封裝,標(biāo)記同步頭,串口編號(hào),字節(jié)數(shù)。本方案采用的數(shù)據(jù)包格式如下:
其中 sync 為同步頭,固定為兩個(gè)’$’字符,用于解析時(shí)找到數(shù)據(jù)包的起始位置;chn為串口編號(hào),對(duì)應(yīng)收發(fā)數(shù)據(jù)的串口;len為數(shù)據(jù)字節(jié)數(shù),用于表示后面dat的實(shí)際大??;dat為實(shí)際收發(fā)的數(shù)據(jù),此處定義的數(shù)組大小不作為實(shí)際數(shù)據(jù)大小。
本方案定義了函數(shù)mux_pack_data()用于封裝串口數(shù)據(jù),其聲明如下:
其中dst為數(shù)據(jù)緩沖地址,用于存放封裝好的數(shù)據(jù)包;chn為串口編號(hào);dat為要傳輸?shù)臄?shù)據(jù);len為上述dat的數(shù)據(jù)字節(jié)數(shù);返回封裝后的數(shù)據(jù)字節(jié)數(shù)。
數(shù)據(jù)轉(zhuǎn)發(fā)程序的串口接收部分主要操作為,逐一讀取各個(gè)串口的數(shù)據(jù),調(diào)用mux_pack_data()將數(shù)據(jù)封裝成一個(gè)個(gè)數(shù)據(jù)包存儲(chǔ)至臨時(shí)緩沖out_buf,最后調(diào)用前文所述 USB 收發(fā)程序的發(fā)送函數(shù)usb_write()將out_buf的數(shù)據(jù)發(fā)給主機(jī)。
串口發(fā)送部分操作為,調(diào)用usb_read()函數(shù)從主機(jī)讀取數(shù)據(jù)并解析,根據(jù)解析的數(shù)據(jù)包調(diào)用uart_write()往對(duì)應(yīng)的串口發(fā)送數(shù)據(jù)。解析函數(shù)為mux_parse_data(),其聲明如下:
其中src和len為原始數(shù)據(jù)緩沖指針及數(shù)據(jù)大小; callback為回調(diào)函數(shù);回調(diào)函數(shù)的參數(shù)chn表示串口編號(hào),dat為數(shù)據(jù)緩沖指針,len為數(shù)據(jù)字節(jié)數(shù)。這里將從usb_read()讀取到的數(shù)據(jù)傳入?yún)?shù)src和len,當(dāng)mux_parse_data()解析到數(shù)據(jù)包,將通過(guò) callback 通知,此時(shí)程序?qū)⒄{(diào)用uart_write()將數(shù)據(jù)發(fā)往指定串口。
6命令處理程序
命令處理程序主要負(fù)責(zé)響應(yīng)主機(jī)設(shè)置串口波特率,獲取串口寫(xiě)緩沖的請(qǐng)求。這些請(qǐng)求通過(guò) USB 控制傳輸?shù)念?lèi)請(qǐng)求來(lái)處理,程序通過(guò)上文所述usb_set_class_callback()設(shè)置類(lèi)請(qǐng)求回調(diào)函數(shù)。類(lèi)請(qǐng)求回調(diào)函數(shù)聲明如下:
bRequest用于表示請(qǐng)求的命令,wIndex表示串口編號(hào),wValue根據(jù)bRequest不同用于表示設(shè)置的值,dat和wLength用于當(dāng)前請(qǐng)求需要補(bǔ)充的數(shù)據(jù)。
用宏定義表示請(qǐng)求的命令,REQ_SET_BAUD,REQ_ALL_UART_WRITE_ROOM 分別表示設(shè)置串口波特率,請(qǐng)求所有串口的剩余寫(xiě)緩沖空間。
當(dāng)主機(jī)請(qǐng)求設(shè)置串口波特率,handle_class_request將被調(diào)用,bRequest值為 REQ_SET_BAUD,wValue為波特率除以100的值(以9600為例,wValue值為96),wIndex表示串口編號(hào),此時(shí)程序調(diào)用前文所述串口函數(shù)uart_ioctl()設(shè)置編號(hào)為wIndex串口的波特率為wValue×100。
當(dāng)主機(jī)請(qǐng)求所有串口寫(xiě)緩沖時(shí),bRequest值為 REQ_ALL_UART_WRITE_ROOM,程序調(diào)用uart_ ioctl()逐一獲取每個(gè)串口的剩余寫(xiě)緩沖空間,寫(xiě)至dat參數(shù)。傳遞至dat的數(shù)據(jù)結(jié)構(gòu)如下:
typedef struct _all_room{
uint8_t chn_num;
uint16_t room[5];
}all_room_t;
其中chn_num為串口數(shù)量,room 為各個(gè)串口的剩余寫(xiě)緩沖字節(jié)數(shù),每個(gè)串口的剩余寫(xiě)緩沖字節(jié)數(shù)用2個(gè)字節(jié)的類(lèi)型 uint16_t 表示。
7傳輸驗(yàn)證
本方案 USB 數(shù)據(jù)傳輸采用libusb編寫(xiě)測(cè)試程序在 LINUX 系統(tǒng)下驗(yàn)證。libusb是一個(gè)在應(yīng)用層調(diào)用的跨平臺(tái) USB 庫(kù),包含了 USB 傳輸所需要的 API。相比在編寫(xiě)內(nèi)核驅(qū)動(dòng)來(lái)驗(yàn)證本方案的數(shù)據(jù)傳輸,采用libusb更快捷,更方便調(diào)試。
本方案驗(yàn)證傳輸采用了libusb中比較方便調(diào)試的同步 I/O API,聲明如下:
int libusb_control_transfer(libusb_device_handle *dev_handle,
uint8_t request_type, uint8_t bRequest, uint16_t wValue, uint16_t wIndex,
unsigned char *data, uint16_t wLength, unsigned int timeout);
int libusb_bulk_transfer(libusb_device_handle *dev_handle,
unsigned char endpoint, unsigned char *data, int length,
int *actual_length, unsigned int timeout);
其中l(wèi)ibusb_control_transfer()用于發(fā)起控制傳輸,參數(shù)dev_handle為 USB 設(shè)備句柄,data 為補(bǔ)充數(shù)據(jù)的指針,timeout 為超時(shí)毫秒數(shù),其它參數(shù)對(duì)應(yīng)控制傳輸 USB 的標(biāo)準(zhǔn)定義。函數(shù)返回傳輸狀態(tài),成功傳輸返回枚舉值LIBUSB? SUCCESS。
libusb_bulk_transfer()用于發(fā)起 bulk 傳輸,dev_ handle 為 USB 設(shè)備句柄,endpoint 為 bulk 端點(diǎn)編號(hào),data 為接收或發(fā)送的數(shù)據(jù)緩沖指針,length 為數(shù)據(jù)字節(jié)數(shù),actual_length為實(shí)際傳輸?shù)淖止?jié)數(shù),timeout 為超時(shí)毫秒數(shù)。
7.1本方案主機(jī)接收據(jù)傳輸驗(yàn)證,主要流程如下:
1)調(diào)用libusb_open_device_with_vid_pid(),根據(jù)設(shè)備 vid,pid打開(kāi) USB 設(shè)備 s
2)通過(guò)libusb_control_transfer()設(shè)置各個(gè)串口的波特率
3)通過(guò)電腦串口上發(fā)送測(cè)試文件到單片機(jī)的串口
4)電腦測(cè)試程序通過(guò)libusb_bulk_transfer()從單
片機(jī) USB 口讀取數(shù)據(jù),通過(guò)轉(zhuǎn)發(fā)程序定義的協(xié)議解析
數(shù)據(jù)包,根據(jù)串口號(hào)不同將數(shù)據(jù)分別存儲(chǔ)到不同的文件5)對(duì)比發(fā)送和接收到的文件
7.2主機(jī)發(fā)送數(shù)據(jù)傳輸驗(yàn)證,流程如下:
1)在電腦測(cè)試程序通過(guò)libusb_control_transfer()設(shè)置各個(gè)串口的波特率
2)電腦測(cè)試程序加載測(cè)試文件,通過(guò)轉(zhuǎn)發(fā)程序定義的協(xié)議將文件數(shù)據(jù)按各個(gè)串口封裝數(shù)據(jù)包,通過(guò)libusb_bulk_transfer()往單片機(jī) USB 口發(fā)送數(shù)據(jù)
3)通過(guò)電腦串口從單片機(jī)接收數(shù)據(jù)并根據(jù)不同串口保存到不同文件
4)對(duì)比發(fā)送和接收到的文件
由于 USB 傳輸速度遠(yuǎn)高于本方案串口,為避免緩沖溢出導(dǎo)致數(shù)據(jù)丟失,主機(jī)發(fā)送數(shù)據(jù)時(shí)需要根據(jù)串口最大發(fā)送緩沖大小和設(shè)計(jì)的最高波特率定時(shí)通過(guò)libusb_ control_transfer()獲取各個(gè)串口的剩余發(fā)送緩沖空間,測(cè)試程序根據(jù)單片機(jī)串口剩余發(fā)送緩沖空間確定當(dāng)時(shí)能發(fā)送的字節(jié)數(shù)。本方案設(shè)計(jì)單片機(jī)串口最大發(fā)送緩沖大小為1024個(gè)字節(jié),最高波特率為115200,根據(jù)最高波特率串口最高發(fā)送速度為 115 200/10等于 11 520 字節(jié) /s,根據(jù)以上參數(shù)可知最大緩沖填滿(mǎn)時(shí)間為 1 024/11 520 ≈ 88 ms,因此本方案輪詢(xún)單片機(jī)串口剩余發(fā)送緩沖時(shí)間間隔為 80 ms即可。
測(cè)試程序運(yùn)行命令如下 :
./mux-test -b 115200 -f ./origin.txt -s /dev/ttyUSB0-4
mux-test 為測(cè)試程序,-b 選項(xiàng)為波特率,這里設(shè)置為 115200;-f 選項(xiàng)為測(cè)試數(shù)據(jù)文件名,這里為origin.txt,origin.txt 為 64kB 的文本文件;-s 為電腦的串口tty設(shè)備名,這里 /dev/ttyUSB0 - 4 表示加載 /dev/ttyUSB0、/dev/ttyUSB1、…… /dev/ttyUSB4。
程序運(yùn)行后,將從電腦串口讀取到的數(shù)據(jù)存儲(chǔ)到 ./tty目錄下,文件根據(jù)串口編號(hào)命名,為 0.txt、1.txt、…… 4.txt;從 USB 讀取到的數(shù)據(jù)存儲(chǔ)到 ./usb目錄下,文件根據(jù)單片機(jī)串口編號(hào)命名,為 0.txt、1.txt、…… 4.txt。當(dāng)程序運(yùn)行完,運(yùn)行以下命令比較文件:
for i in $(seq 0 4); do diff -s ./origin.txt ./tty/$i.txt ; done; \
for i in $(seq 0 4); do diff -s ./origin.txt ./usb/$i.txt ; done
以下是運(yùn)行結(jié)果 :
Files ./origin.txt and ./tty/0.txt are identical
Files ./origin.txt and ./tty/1.txt are identical
Files ./origin.txt and ./tty/2.txt are identical
Files ./origin.txt and ./tty/3.txt are identical
Files ./origin.txt and ./tty/4.txt are identical
Files ./origin.txt and ./usb/0.txt are identical
Files ./origin.txt and ./usb/1.txt are identical
Files ./origin.txt and ./usb/2.txt are identical
Files ./origin.txt and ./usb/3.txt are identical
Files ./origin.txt and ./usb/4.txt are identical
根據(jù)結(jié)果可以判斷出收發(fā)的數(shù)據(jù)完全一致。
8 結(jié)束語(yǔ)
本文首先分析了船舶導(dǎo)航儀對(duì)多串口的需求,并提出 USB 擴(kuò)展多串口的方案,分析該方案的可行性及便利性,并提出兆易創(chuàng)新的 GD32F305 單片機(jī)來(lái)實(shí)現(xiàn)這一方案。接著以 RK3128 主板為例介紹該擴(kuò)展方案的硬件連接,然后探討了單片機(jī)程序的具體實(shí)現(xiàn),最后介紹用libusb進(jìn)行數(shù)據(jù)傳輸驗(yàn)證。
參考文獻(xiàn):
[1] GD32F305xx Datasheet Rev1.3[G].
[2] GD32F30x_User_Manual_Rev2.8.pdf[G].
[3] USBFS/HS Firmware Library User Guide Revision 1.0[G].
[4] Firmware Library User Guide Revison 1.0[G].
[5] USB in a Nutshell[G].