Linux串口驱动分析及移植
1 概述
????在Linux中,常碰到“控制臺”、“終端”、“console”、“tty”等術語,也會經常使用一些設備文件:/dev/console、/dev/ttyS0、/dev/ttyUSB0等。tty是Teletype的縮寫,Teletype是最早出現的一種終端設備,Linux通常使用tty來表示“終端”,終端設備的種類有很多,比如串行終端、鍵盤和顯示器、通過網絡實現的終端等。
????UART與USART都是單片機上的串口通信, UART(universal asynchronous receiver and transmitter)通用異步收/發器,USART(universal synchronous asynchronous receiver and transmitter)通用同步/異步收/發器。從名字上可以看出,USART在UART基礎上增加了同步功能,即USART是UART的增強型。當我們使用USART在異步通信的時候,它與UART沒有什么區別,但是用在同步通信的時候,區別就很明顯了:同步通信需要時鐘來觸發數據傳輸,也就是說USART相對UART的區別之一就是能提供主動時鐘。
????串口屬于終端設備,它的驅動程序并不僅僅是簡單的初始化硬件、接收/發送數據,在基本硬件操作的基礎上,還增加了很多軟件功能,是一個多層次的驅動程序。
2 串口驅動程序層次結構
????串口驅動程序層次結構如圖2.1所示。簡單來說,串口驅動程序層次結構可以分為兩層,下層為串口驅動層,它直接與硬件相接觸,需要填充一個 struct uart_ops 的結構體。上層為tty層,包括tty核心層及線路規程,它們各自都有一個 ops 結構體,用戶空間可以通過tty注冊的字符設備節點來訪問串口設備。如圖2.1所示,涉及到了4個 ops 結構體,層層進行跳轉。
????下面以Microchip SAMA5D4處理器的UART控制器來進行代碼分析。
2.1 串口驅動注冊-uart_register_driver
????路徑: \linux-at91\drivers\tty\serial\atmel_serial.c。對于Atmel平臺,是這樣來注冊串口驅動的,首先分配一個struct uart_driver 簡單填充,并調用uart_register_driver 注冊到內核中去。
struct uart_driver 中,只是填充了一些名字、設備號等信息,這些都是不涉及底層硬件訪問的。
下面看一下完整的 uart_driver 結構。
????在struct uart_driver atmel_uart結構體中,有兩個成員未被賦值,分別是tty_driver和uart_state。對于tty_driver,代表的是上層,它會在 uart_ register_driver 的過程中賦值。而uart_state ,則代表下層,uart_state也會在uart_ register_driver 的過程中分配空間,但是它里面真正設置硬件相關的東西是 uart_state->uart_port ,這個uart_port 是需要從其它地方調用 uart_add_one_port 來添加的。
? 下層(串口驅動層)
首先,認識幾個結構體。
1) 結構體:uart_state。
路徑:\linux-at91\linux-at91\linux-at91\include\linux\serial_core.h
????在注冊 driver 時,會根據 uart_driver->nr 來申請 nr 個 uart_state 空間,用來存放驅動所支持的串口(端口)物理信息。
2) 結構體:uart_port
????
路徑:\linux-at91\linux-at91\linux-at91\include\linux\serial_core.h
????struct uart_port,這個結構體對應于一個串口設備,如果平臺有3個串口那么就需要填充3個uart_port ,并且通過 uart_add_one_port 添加到 uart_driver->uart_state->uart_port 中去。當然 uart_driver 有多個 uart_state ,每個 uart_state 有一個 uart_port。在 uart_port 里還有一個非常重要的成員 struct uart_ops *ops ,一般芯片廠家都寫好了,只需要稍作修改。
????下面是struct uart_ops 結構體。
? 上層(tty核心層)
????tty 核心層要從 uart_register_ driver 來看起,因為 tty_driver 是在注冊過程中構建的,順便了解注冊過程。
uart_register_ driver 注冊過程干了哪些事:
???? 1)根據driver支持的最大設備數,申請n個 uart_state 空間,每一個 uart_state 都有一個 uart_port ;
???? 2)分配一個 tty_driver ,并將drv->tty_driver 指向它;
???? 3)對 tty_driver 進行設置,其中包括默認波特率、校驗方式等,還有一個重要的ops ,uart_ops ,它是tty核心與串口驅動通信的接口;
???? 4)初始化每一個 uart_state ;
???? 5)注冊 tty_driver 。
????注冊 uart_driver 實際上是注冊 tty_driver,因此與用戶空間打交道的工作完全交給了 tty_driver ,而且這一部分都是內核實現好的,基本不需要修改,了解一下工作原理即可。
這里介紹下uart_ops結構體。
static const struct tty_operations uart_ops = {.open = uart_open,.close = uart_close,.write = uart_write,.put_char = uart_put_char,.flush_chars = uart_flush_chars,.write_room = uart_write_room,.chars_in_buffer= uart_chars_in_buffer,.flush_buffer = uart_flush_buffer,.ioctl = uart_ioctl,.throttle = uart_throttle,.unthrottle = uart_unthrottle,.send_xchar = uart_send_xchar,.set_termios = uart_set_termios,.set_ldisc = uart_set_ldisc,.stop = uart_stop,.start = uart_start,.hangup = uart_hangup,.break_ctl = uart_break_ctl,.wait_until_sent= uart_wait_until_sent, #ifdef CONFIG_PROC_FS.proc_fops = &uart_proc_fops, #endif.tiocmget = uart_tiocmget,.tiocmset = uart_tiocmset,.get_icount = uart_get_icount, #ifdef CONFIG_CONSOLE_POLL.poll_init = uart_poll_init,.poll_get_char = uart_poll_get_char,.poll_put_char = uart_poll_put_char, #endif };這個是 tty 核心層的 ops ,簡單一看,后面分析調用關系時,我們再來看具體的里邊的函數。
下面來看 tty_driver 的注冊,tty_register_driver。
tty_register_driver注冊過程干了哪些事:
1)為線路規程和termios分配空間,并使 tty_driver 相應的成員指向它們;
2)注冊字符設備,名字是 uart_driver->name 我們這里是“ttyS”,文件操作函數集是 tty_fops。可查看tty_cdev_add 函數的定義;
3)將該 uart_driver->tty_drivers 添加到全局鏈表 tty_drivers ;
4)向 proc 文件系統添加 driver ,這個暫時不了解。
下面是結構體 tty_fops。
????至此,文章起初的結構圖中的4個ops已經出現了3個,另一個關于線路規程的在哪?下面來看一下調用關系。
? 調用關系
????tty_driver注冊了一個字符設備(/dev/ttyS0),從它的 tty_fops 入手,可以得知用戶空間是如何訪問到最底層的硬件操作函數。以 open、read、write 為例,用戶空間 open 時將調用到 uart_port.ops.startup ,在用戶空間 write 則調用 uart_port.ops.start_tx。這些內核都已經實現好,在驅動開發過程中幾乎不涉及這些代碼的修改移植工作。圖2.2為串口讀寫函數調用關系。
2.2 平臺驅動注冊-platform _driver_register
????目前Linux2.6版本以后的ARM 結構使用設備樹(DTS,Device Tree Source)來分管設備,DeviceTree是一種描述硬件的數據結構,為什么要引入DTS?這是因為在Linux2.6中,ARM架構的板極硬件細節過多地被硬編碼在arch/arm/plat-xxx和arch/arm/mach-xxx,比如板上的platform設備、resource、i2c_board_info、spi_board_info以及各種硬件的platform_data,這些板級細節代碼對內核來講只不過是垃圾代碼。而采用DeviceTree后,許多硬件的細節可以直接透過它傳遞給Linux,而不再需要在kernel中進行大量的冗余編碼。
????platform_driver的入口函數,仍采用platform_driver_register注冊。platform_driver_register用來枚舉名稱為 “atmel_usart” 的平臺設備,只要內核中有相同名稱的平臺設備,platform_driver_register函數就會調用atmel_serial_driver 中的atmel_serial_probe函數來枚舉它。
????函數:platform_driver_register
結構體:atmel_serial_driver
????看結構體參數 atmel_serial_driver 的定義,該結構體一個很重要的成員是 .of_match_table = of_match_ptr(atmel_serial_dt_ids), of_match_ptr函數用于將atmel_serial_dt_ids 中.compatible 屬性與.dts 文件中描述的設備節點.compatible 屬性進行匹配,且不區分大小寫,當完成配對之后,就調用atmel_serial_probe函數完成驅動注冊的最后工作。
????結構體:atmel_serial_dt_ids
? 設備樹
????Dtsi和dts同為設備樹文件,dtsi為被包含文件,可被dts或者dtsi文件包含。
????設備樹文件sama5d4.dtsi中對串口設備節點的描述。路徑:\linux-at91\arch\arm\boot\dts\sama5d4.dtsi。
????sama5d4.dtsi中描述了7個串口設備節點,2個uart,5個usart。
(別名)
(節點描述)描述了節點的一些屬性。
(引腳定義)
設備樹文件at91-sama5d4_xplained.dts中對串口設備節點的描述。路徑:\linux-at91\arch\arm\boot\dts\at91-sama5d4_xplained.dts。
3 串口移植
3.1 查看板卡的串口引腳
??閱讀A5處理器資料,查看UART章節和USART章節對串口的相關描述,根據硬件原理圖確定串口的引腳定義。
3.2 修改情況
1) 設備樹文件,到sama5d4.dtsi 文件下去搜索 USART ,對應找到相應的串口節點以及串口的引腳定義部分。sama5d4.dtsi 文件不需要進行修改。
? 節點描述
? 引腳定義
2) 設備樹文件,查看 at91-sama5d4_xplained.dts 文件中的串口USART,在此文件下對節點進行相應參數修改。
—原來的串口節點信息如下:原始狀態使能了uart0但我們沒有接其引腳使用,使能了usart3的收發功能,使能了usart4但不具備收發功能,沒有使能usart2。
—修改后的串口節點信息如下:
3) 驅動文件,在\linux-at91\drivers\tty\serial\atmel_serial.c文件中,查找static int __init atmel_serial_init(void)函數,找到uart_register_driver()函數,轉到該函數定義,函數定義在 serial_core.c 文件中,文件中修改波特率,。原始波特率9600 ,可按自己的需求修改波特率,我們這里將其改為115200。
????到此,串口驅動移植完成,重新編譯驅動文件,包括設備樹文件和驅動文件,燒錄至開發板,測試串口是否可以正常使用。
4 串口收發功能測試
????將編譯成功的內核鏡像和設備樹文件編譯到開發板中,然后使用超級終端接開發板查看打印的日志。
????串口收發功能測試步驟如下:
????1) 查看tty設備。
????(1)編譯運行內核,如果UART驅動加載成功會在/dev目錄下產生相應UART設備節點; 名稱為:ttySx。這里會產生 ttyS0, ttyS1, ttyS2, ttyS5(沒有使用)。
ttyS0對應USART3,為DEGB口;
ttyS1對應 USART4;
ttyS2對應 USART2;
ttyS5 對應UART0;
????(2)運行命令: $ cat /proc/tty/driver/atmel_serial ,可以查看顯示設備節點詳細信息,其中通過地址可以對照設備節點;
root@sama5d4-xplained:/proc/tty/driver# cat atmel_serial
serinfo:1.0 driver revision:
0: uart:ATMEL_SERIAL mmio:0xFC00C000 irq:34 tx:2176533 rx:2142522 RTS|CTS|DTR|DSR|CD|RI
1: uart:ATMEL_SERIAL mmio:0xFC010000 irq:35 tx:1253 rx:68 brk:68 CTS|DSR|CD|RI
2: uart:ATMEL_SERIAL mmio:0xFC008000 irq:33 tx:6 rx:0 DSR|CD|RI
如果UART設備節點未產生,可在其相應驅動程序xx_probe函數中添加打印,查看xx_probe函數是否被調用,進一步查找原因。
????2) 通過跳冒選擇串口。
下圖中,兩個黑色串口共用一個芯片。左邊USART3和USART4共用一個串口,通過跳冒來區分,右邊USART2單獨使用一個串口。
跳線冒J3用于設置選擇DEBG- USART3和串口4,電路圖如下圖4.2。
具體的跳線設置如下表4.1和表4.2所示。
表4.1 DEGU模式跳線狀態
3) 如果成功產生了UART設備節點,下面進行串口收發測試;
方法1:
可通過軟件回環測試確認UART驅動程序功能是否正常。編寫測試程序,將串口2、3引腳短接。讀寫數據。如果管腳信號測試通過,則串口功能基本調試成功。此方法的優點是無需上位機串口助手的配合,在串口模塊到位之前提前完成接口調試工作。
方法2:
Echo 命令回顯測試;
在超級終端下進入 /dev 目錄下,以測試USART2為例,輸入命令: echo test > ttyS2.在串口助手中查看收到的信息,這一步測試串口的發。在超級終端下輸入命令: cat ttyS2, 在串口助手中發送clear,在超級終端下查看收到信息,這一步查看串口的收。由于USART2不是調試口,無法打印系統信息,需要使用SSH連接開發板。具體的測試如下圖所示。
方法3: 使用系統集成的串口調試工具 busybox microcom
1) busybox microcom工具的使用;
命令(busybox microcom)使用方法很簡單:
Usage: microcom [-d DELAY] [-t TIMEOUT] [-s SPEED] [-X] TTY
參數如下:
-d 表示延時時間,一般我都不設置。
-t 表示超時時間,超多少時間就自動退出。單位為ms
-s 表示傳輸速度,波特率的意思,這個根據自己的情況而定。
-X 不加
最后指定你的串口設備。如 /dev/ttyO0 , 這是TI的串口設備節點
2)測試方式如下:
? 將要測試串口與pc端連接,在pc端開啟串口調試工具,波特率設定跟等下microcom設定一樣。
? 在產品端運行如下命令:
microcom -s 115200 /dev/ttyS2
? 在超級終端命令行輸出信息,在PC是否有正確顯示。反之也測試一下。
總結
以上是生活随笔為你收集整理的Linux串口驱动分析及移植的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 匹配算法
- 下一篇: 网络安全技术入门到项目实战