董純, 蔣方亮, 季啟政, 楊林鵬
(北京東方計(jì)量測(cè)試研究所,北京 100086)
在多串口應(yīng)用中,使用多片ST16C2552為BF518擴(kuò)展串口設(shè)備。在uClinux系統(tǒng)中,基于字符設(shè)備驅(qū)動(dòng)模型[1]開(kāi)發(fā)ST16C2552串口驅(qū)動(dòng)程序存在以下不足:第一,字符設(shè)備驅(qū)動(dòng)模型只提供最基本的和操作系統(tǒng)的接口,不實(shí)現(xiàn)任何通用邏輯,因此所有串口功能都需要編程實(shí)現(xiàn),比如數(shù)據(jù)緩沖邏輯、阻塞邏輯和配置命令等,開(kāi)發(fā)和測(cè)試成本高;第二,為每個(gè)串口設(shè)備單獨(dú)開(kāi)發(fā)驅(qū)動(dòng),由于同型號(hào)芯片操作方式相同,會(huì)產(chǎn)生大量重復(fù)代碼,代碼復(fù)用性差且不支持?jǐn)U展。采用uClinux平臺(tái)設(shè)備驅(qū)動(dòng)技術(shù)和TTY終端設(shè)備驅(qū)動(dòng)模型進(jìn)行串口驅(qū)動(dòng)程序開(kāi)發(fā),TTY終端設(shè)備驅(qū)動(dòng)中已經(jīng)實(shí)現(xiàn)了串口通用邏輯,因此只需對(duì)標(biāo)準(zhǔn)接口進(jìn)行實(shí)現(xiàn)[2],就可以開(kāi)發(fā)出功能完備的串口驅(qū)動(dòng)程序,縮短了開(kāi)發(fā)和測(cè)試周期。當(dāng)硬件連接更多擴(kuò)展芯片時(shí),在系統(tǒng)中創(chuàng)建對(duì)應(yīng)的平臺(tái)設(shè)備,就可以實(shí)現(xiàn)驅(qū)動(dòng)程序的復(fù)用和擴(kuò)展。
圖1描述了使用ST16C2552擴(kuò)展串口設(shè)備的硬件連接方式。為了提高BF518異步總線[3]的擴(kuò)展能力,使用CPLD對(duì)BF518異步總線的高位地址線(A16-A19)進(jìn)行譯碼,產(chǎn)生多個(gè)片選信號(hào)。BF518通過(guò)CPLD連接了4片ST16C2552芯片,一片ST16C2552提供2個(gè)獨(dú)立串口,因此總共擴(kuò)展出8個(gè)串口。為每個(gè)串口分配一個(gè)用來(lái)連接中斷信號(hào)線的GPIO。
圖1 BF518與ST16C2552硬件連接圖
平臺(tái)設(shè)備驅(qū)動(dòng)架構(gòu)包含總線、設(shè)備和驅(qū)動(dòng),總線自動(dòng)完成設(shè)備和驅(qū)動(dòng)的匹配工作[4]。使用平臺(tái)設(shè)備驅(qū)動(dòng)技術(shù)可以隔離板級(jí)信息和驅(qū)動(dòng)程序,在平臺(tái)設(shè)備信息中定義設(shè)備使用的資源、設(shè)備的具體配置信息,而在驅(qū)動(dòng)中,使用標(biāo)準(zhǔn)API去獲取資源和信息,做到了板相關(guān)代碼和驅(qū)動(dòng)代碼的分離,使驅(qū)動(dòng)具有擴(kuò)展性。
ST16C2552驅(qū)動(dòng)程序使用module_init(st16c2552_uart_init)與module_exit(st16c2552_uart_exit)指定加載函數(shù)與卸載函數(shù)。st16c2552_uart_init函數(shù)調(diào)用uart_register_driver和platform_driver_register完成uart_driver和platform_driver的注冊(cè),st16c2552_uart_exit函數(shù)調(diào)用uart_unregister_driver和platform_driver_unregister完成uart_driver和platform_driver的卸載。
platform_device是平臺(tái)設(shè)備接口,對(duì)于每個(gè)串口設(shè)備,都需要實(shí)現(xiàn)該接口。platform_device數(shù)據(jù)結(jié)構(gòu)包含name、id、resource等成員變量。name是平臺(tái)驅(qū)動(dòng)中實(shí)現(xiàn)驅(qū)動(dòng)和設(shè)備匹配的關(guān)鍵,總線會(huì)根據(jù)name尋找對(duì)應(yīng)的設(shè)備驅(qū)動(dòng)[5]。id字段用來(lái)區(qū)分name字段完全相同的平臺(tái)設(shè)備。resource是資源數(shù)組,將內(nèi)存基地址和中斷號(hào)信息抽取出來(lái)保存到資源數(shù)組中,是實(shí)現(xiàn)設(shè)備相關(guān)代碼與驅(qū)動(dòng)代碼分離的關(guān)鍵。根據(jù)ST16C2552的芯片特性,在這個(gè)數(shù)組中定義了兩個(gè)元素:基地址資源、中斷號(hào)資源。數(shù)組元素中的flags字段表示該資源類別,IORESOURCE_MEM表示內(nèi)存資源,IORESOURCE_IRQ表示中斷資源。驅(qū)動(dòng)程序需要支持8個(gè)串口,因此建立8個(gè)platform_device,每個(gè)platform_device的name成員設(shè)置相同內(nèi)容,id成員從0依次累加,根據(jù)實(shí)際情況設(shè)置每片ST16C2552的基地址和中斷號(hào)。
platform_driver是平臺(tái)驅(qū)動(dòng)接口,需要為probe和remove函數(shù)指針及name變量賦值,platform_device和platform_driver匹配時(shí)會(huì)調(diào)用probe指向的函數(shù),注銷platform_device或platform_driver時(shí)會(huì)調(diào)用remove指向的函數(shù)。name字段用來(lái)匹配對(duì)應(yīng)的platform_device。
platform_driver中的probe函數(shù)主要完成驅(qū)動(dòng)程序的初始化工作,首先創(chuàng)建uart_port,然后使用標(biāo)準(zhǔn)API獲取platform_device中的基地址和中斷號(hào)信息,對(duì)uart_port進(jìn)行初始化,最后通過(guò)調(diào)用dev_set_drvdata函數(shù)設(shè)置uart_port與platform_device關(guān)聯(lián),調(diào)用uart_add_one_port函數(shù)設(shè)置uart_port與uart_driver關(guān)聯(lián)。上述操作完成后,系統(tǒng)中會(huì)生成與uart_port對(duì)應(yīng)的設(shè)備節(jié)點(diǎn),設(shè)備名稱由uart_driver的dev_name與uart_port的line編號(hào)組成。
1)uart_driver與uart_port接口的實(shí)現(xiàn)
在驅(qū)動(dòng)程序中實(shí)現(xiàn)uart_driver接口,該接口driver_name字段是驅(qū)動(dòng)名稱,dev_name字段是設(shè)備名稱,major是主設(shè)備號(hào),minor是從設(shè)備起始號(hào),nr是uart_driver對(duì)應(yīng)串口設(shè)備的最大數(shù)量,硬件上擴(kuò)展了8個(gè)串口,所以設(shè)定為8。
uart_port用來(lái)保存了串口設(shè)備信息,初始化uart_port時(shí),需要設(shè)置晶振頻率、發(fā)送FIFO大小、line編號(hào)、iotype 等參數(shù)字段,還要為其關(guān)聯(lián)uart_ops操作函數(shù)集。uart_ops包含與硬件相關(guān)的底層操作函數(shù)接口,需要編程實(shí)現(xiàn)這些操作函數(shù)。
2)startup函數(shù)的實(shí)現(xiàn)
在應(yīng)用層調(diào)用open函數(shù)時(shí),會(huì)最終調(diào)用uart_ops中的startup函數(shù),該函數(shù)主要完成申請(qǐng)中斷、設(shè)置中斷處理函數(shù)以及使能ST16C2552接收中斷的操作。為了實(shí)現(xiàn)驅(qū)動(dòng)程序的復(fù)用性,ST16C2552芯片寄存器的訪問(wèn)地址通過(guò)計(jì)算方式獲得,使用基地址加上ST16C2552芯片內(nèi)部寄存器的偏移地址,得到硬件訪問(wèn)地址。
3)set_termios函數(shù)的實(shí)現(xiàn)
在應(yīng)用層進(jìn)行串口參數(shù)設(shè)置時(shí),會(huì)最終調(diào)用uart_ops中的set_termios函數(shù),實(shí)現(xiàn)設(shè)置串口參數(shù),包括數(shù)據(jù)位、停止位、奇偶校驗(yàn)和波特率等功能。該函數(shù)獲取來(lái)自應(yīng)用層的termios數(shù)據(jù)結(jié)構(gòu),從該數(shù)據(jù)結(jié)構(gòu)中讀取串口參數(shù)的設(shè)置值,根據(jù)波特率計(jì)算分頻值,然后關(guān)閉ST16C2552中斷使能,將分頻值寫入DLL和DLM寄存器,將設(shè)置值寫入LCR寄存器,最后使能ST16C2552中斷,根據(jù)最新的設(shè)置值更新端口的timeout值。
4)start_tx函數(shù)的實(shí)現(xiàn)
用戶發(fā)送數(shù)據(jù)時(shí),通過(guò)“write()系統(tǒng)調(diào)用-TTY核心-線路規(guī)程”的層層調(diào)用,最終調(diào)用uart_ops中的start_tx函數(shù)。在start_tx函數(shù)中,首先在ST16C2552中斷使能寄存器中設(shè)置發(fā)送中斷使能位,然后將發(fā)送緩沖區(qū)數(shù)據(jù)依次傳遞給ST16C2552發(fā)送寄存器。發(fā)送緩沖區(qū)中數(shù)據(jù)比較多時(shí),未發(fā)送的數(shù)據(jù)會(huì)在后續(xù)的發(fā)送中斷處理程序中發(fā)送出去。
5)中斷服務(wù)程序的實(shí)現(xiàn)
ST16C2552提供了基于優(yōu)先級(jí)的中斷管理機(jī)制,ISR(中斷狀態(tài)寄存器)中總是保存當(dāng)前優(yōu)先級(jí)最高的中斷代碼,其余中斷則保存到隊(duì)列中等待后續(xù)服務(wù),需要將中斷觸發(fā)方式設(shè)置為電平觸發(fā),在中斷處理程序中讀取ISR寄存器,根據(jù)中斷代碼判斷中斷類型,然后進(jìn)行相應(yīng)處理。
圖2是中斷處理函數(shù)流程圖。首先讀取ST16C2552中斷狀態(tài)寄存器獲得中斷代碼,然后判斷中斷類型。如果是接收中斷,先讀取線路狀態(tài)寄存器,然后讀取數(shù)據(jù)接收寄存器,接收數(shù)據(jù)計(jì)數(shù)累加,判斷是否產(chǎn)生錯(cuò)誤,有則設(shè)置錯(cuò)誤標(biāo)志,調(diào)用uart_insert_char函數(shù),將接收到的數(shù)據(jù)拷貝到tty_struct結(jié)構(gòu)體的buf緩沖區(qū)中,最后調(diào)用tty_flip_buffer_push將數(shù)據(jù)拷貝到tty_struct結(jié)構(gòu)體的read_buf緩沖區(qū)中。如果是發(fā)送中斷,意味著硬件已經(jīng)將之前的數(shù)據(jù)發(fā)送完畢,串口發(fā)送硬件處于空閑狀態(tài),這時(shí)可以將新的數(shù)據(jù)傳遞給硬件。程序先檢查發(fā)送緩沖區(qū)中數(shù)據(jù)的數(shù)量和停止發(fā)送標(biāo)志,如果發(fā)送數(shù)據(jù)緩沖區(qū)里沒(méi)有待發(fā)送的數(shù)據(jù)或停止標(biāo)志有效,就將ST16C2552中斷使能寄存器的發(fā)送中斷使能標(biāo)志清零,禁用發(fā)送中斷,以減少對(duì)CPU資源的消耗,接下來(lái)檢查是否設(shè)置了高優(yōu)先級(jí)的字符,如果設(shè)置了,就先發(fā)送高優(yōu)先級(jí)字符,然后將發(fā)送數(shù)據(jù)緩沖區(qū)里的數(shù)據(jù)依次寫入ST16C2552的發(fā)送寄存器,由硬件發(fā)送,每次將數(shù)據(jù)寫入發(fā)送寄存器時(shí),對(duì)已發(fā)送字符個(gè)數(shù)進(jìn)行一次累加,最后檢查發(fā)送緩沖區(qū)中剩余字符的個(gè)數(shù),如果小于256個(gè),就喚醒向發(fā)送緩沖區(qū)寫數(shù)據(jù)的線程。
使用insmod指令將所有platform_device和platform_driver安裝到系統(tǒng)中,進(jìn)入/dev目錄,執(zhí)行l(wèi)s命令列出所有設(shè)備節(jié)點(diǎn),系統(tǒng)自動(dòng)生成tty_ST16C2552_UART0~tty_ST16C2552_UART7共8個(gè)串口設(shè)備節(jié)點(diǎn)。
首先使用open系統(tǒng)調(diào)用打開(kāi)串口,然后使用tcgetattr函數(shù)獲取當(dāng)前的termios數(shù)據(jù)結(jié)構(gòu),設(shè)置8數(shù)據(jù)位、1停止位、無(wú)奇偶校驗(yàn)、波特率9 600 bps、原始輸入模式,設(shè)置更新termios數(shù)據(jù)結(jié)構(gòu)[6],循環(huán)中使用read系統(tǒng)調(diào)用接收數(shù)據(jù),并將接收到的數(shù)據(jù)通過(guò)write系統(tǒng)調(diào)用發(fā)送出去。將測(cè)試板串口與計(jì)算機(jī)串口相連,在計(jì)算機(jī)上運(yùn)行串口調(diào)試助手,設(shè)置與測(cè)試程序一致的串口參數(shù),使用串口調(diào)試助手發(fā)送數(shù)據(jù),停止發(fā)送后觀測(cè)到發(fā)送與接收字節(jié)數(shù)及數(shù)據(jù)內(nèi)容都一致,證明串口驅(qū)動(dòng)工作正常。
圖2 中斷處理函數(shù)流程圖
使用平臺(tái)設(shè)備驅(qū)動(dòng)技術(shù)及TTY終端設(shè)備驅(qū)動(dòng)模型相結(jié)合的方法開(kāi)發(fā)了串口驅(qū)動(dòng)程序,并編寫應(yīng)用程序?qū)︱?qū)動(dòng)程序進(jìn)行調(diào)用測(cè)試,測(cè)試結(jié)果顯示該驅(qū)動(dòng)程序?qū)崿F(xiàn)了預(yù)期功能,目前已經(jīng)投入到項(xiàng)目使用中。使用該方案開(kāi)發(fā)串口驅(qū)動(dòng)程序主要有兩個(gè)優(yōu)點(diǎn):第一,當(dāng)硬件連接更多ST16C2552時(shí),只需編寫platform_device并動(dòng)態(tài)加載到系統(tǒng)中,可實(shí)現(xiàn)對(duì)新增ST16C2552串口設(shè)備的支持,體現(xiàn)了驅(qū)動(dòng)程序的動(dòng)態(tài)擴(kuò)展特性;第二,uClinux下的TTY設(shè)備驅(qū)動(dòng)模型已包含通用邏輯的實(shí)現(xiàn),只需要實(shí)現(xiàn)它提供的一組接口函數(shù),就可以開(kāi)發(fā)出功能完備的串口驅(qū)動(dòng)程序,降低了開(kāi)發(fā)和測(cè)試成本,提高了驅(qū)動(dòng)程序的可靠性。