1. 程式人生 > >基於Linux的tty架構及UART驅動詳解

基於Linux的tty架構及UART驅動詳解

更多嵌入式Linux原創,請關注公眾號:一口Linux # 一、模組硬體學習 ## 1.1. Uart介紹 通用非同步收發傳輸器(Universal Asynchronous Receiver/Transmitter),通常稱為UART,是一種非同步收發傳輸器,是電腦硬體的一部分。它將要傳輸的資料在序列通訊與並行通訊之間加以轉換。作為把並行輸入訊號轉成序列輸出訊號的晶片,UART 通常被集成於其他通訊介面的連上。 UART 是一種通用序列資料匯流排,用於非同步通訊。該匯流排雙向通訊,可以實現全雙工傳輸和接收。在嵌入式裝置中,UART 用於主機與輔助裝置通訊,如汽車音與外接AP 之間的通訊,與PC 機通訊包括與監控偵錯程式和其它器件,如EEPOM通訊。 ### 1.1.1. 通訊協議 UART作為非同步串列埠通訊協議的一種,工作原理是將傳輸資料的每個字元一位接一位地傳輸。 其中各位的意義如下: - 起始位:先發出一個邏輯”0”的訊號,表示傳輸字元的開始。 - 資料位:緊接著起始位之後。資料位的個數可以是5、6、7、8等,構成一個字元。通常採用ASCII碼。從最低位開始傳送,靠時鐘定位。 - 奇偶校驗位:資料位加上這一位後,使得“1”的位數應為偶數(偶校驗)或奇數(奇校驗),以此來校驗資料傳送的正確性 。 - 停止位:它是一個字元資料的結束標誌。可以是1位、1.5位、2位的高電平。 由於資料是在傳輸線上定時的,並且每一個裝置有其自己的時鐘,很可能在通訊中兩臺裝置間出現了小小的不同步。 因此停止位不僅僅是表示傳輸的結束,並且提供計算機校正時鐘同步的機會。適用於停止位的位數越多,不同時鐘同步的容忍程度越大,但是資料傳輸率同時也越慢。 - 空閒位:處於邏輯“1”狀態,表示當前線路上沒有資料傳送。 Uart傳輸資料如圖2-1所示: ![ ](https://img-blog.csdnimg.cn/20210204203803289.png) ### 1.1.2. 波特率 波特率是衡量資料傳送速率的指標。表示每秒鐘傳送的符號數(symbol)。一個符號代表的資訊量(位元數)與符號的階數有關。例如傳輸使用256階符號,每8bit代表一個符號,資料傳送速率為120字元/秒,則波特率就是120 baud,位元率是120*8=960bit/s。這兩者的概念很容易搞錯。 UART 的接收和傳送是按照相同的波特率進行收發的。波特率發生器產生的時鐘頻率不是波特率時鐘頻率,而是波特率時鐘頻率的16倍,目的是為在接收時進行精確的取樣,以提取出非同步的序列資料。根據給定的晶振時鐘和要求的波特率,可以算出波特率分頻計數值。 ### 1.1.3. 工作原理 傳送資料過程:空閒狀態,線路處於高電位;當收到傳送資料指令後,拉低線路一個數據位的時間T,接著資料位按低位到高位依次傳送,資料傳送完畢後,接著傳送奇偶檢驗位和停止位(停止位為高電位),一幀資料傳送結束。 接收資料過程: 空閒狀態,線路處於高電位;當檢測到線路的下降沿(線路電位由高電位變為低電位)時說明線路有資料傳輸,按照約定的波特率從低位到高位接收資料,資料接收完畢後,接著接收並比較奇偶檢驗位是否正確,如果正確則通知則通知後續裝置準備接收資料或存入快取。 由於UART是非同步傳輸,沒有傳輸同步時鐘。為了能保證資料傳輸的正確性,UART採用16倍資料波特率的時鐘進行取樣。每個資料有16個時鐘取樣,取中間的取樣值,以保證取樣不會滑碼或誤碼。一般UART一幀的資料位為8,這樣即使每一個數據有一個時鐘的誤差,接收端也能正確地取樣到資料。 UART的接收資料時序為:當檢測到資料下降沿時,表明線路上有資料進行傳輸,這時計數器CNT開始計數,當計數器,當計數器為8時,取樣的值為“0”表示開始位;當計數器為24=16*1+8時,取樣的值為bit0資料;當計數器的值為40=16*2+8時,取樣的值為bit1資料;依次類推,進行後面6個數據的取樣。如果需要進行奇偶校驗位,則當計數器的值為152=16*9+8時,取樣的值為奇偶位;當計數器的值為168=16*10+8時,取樣的值為“1”表示停止位,一幀資料收發完成。 ### 1.1.4. RS232與RS485 **UART**:通常說的UART指的是一種序列通訊協議,規定了資料幀格式,波特率等。 **RS232和RS485**:是兩種不同的電氣協議,也就是說,是對電氣特性以及物理特性的規定,作用於資料的傳輸通路上,它並不含對資料的處理方式。對應的物理器件有RS232或者RS485驅動晶片,將CPU經過UART傳送過來的電壓訊號驅動成RS232或者RS485電平邏輯。 RS232使用3-15V有效電平,而UART,因為對電氣特性沒有規定,所以直接使用CPU使用的電平,即TTL電平(在0-3.3V之間)。 更具體的,電氣的特性也決定了線路的連線方式,比如RS232,規定用電平表示資料,因此線路就是單線路的,兩根線能達到全雙工的目的;RS485使用差分電平表示資料,因此必須用兩根線才能達到傳輸資料的基本要求,要實現全雙工,必須使用4根線。 **RS232和RS485的區別** (1)抗干擾性 - RS485 介面是採用平衡驅動器和差分接收器的組合,具有抑制共模干擾的能力,抗噪聲干擾性強。 - RS232介面使用一根訊號線和一根訊號返回線而構成供地的傳輸形式,這種共地傳輸容易產生共模干擾,所以抗噪聲干擾性弱。 (2)傳輸距離 - RS485 介面的最大傳輸距離標準值為1200 米(9600bps 時),實際上可達3000米。 - RS232 傳輸距離有限,最大傳輸距離標準值為50米,實際上也只能用15米左右。 (3)通訊能力 - RS485介面在總線上最多可以連線128個收發器,即具有多站能力,而這樣的使用者可以利用單一的RS485介面方便的建立起裝置網路。 - RS232只允許一對一通訊。 (4)傳輸速率 - RS232傳輸速率較低,在非同步傳輸時,波特率為20Kbps. - RS485的資料最高傳輸速率為10Mbps. (5) 訊號線 - RS485全雙工:uart-tx 1根線,變成 RS485- A/B 2根線;uart-rx 1根線,變成 RS485- x/y 2根線, - RS485半雙工: 將全雙工的 A/B; X/Y 合併起來,分時複用。 - RS232只允許一對一通訊 (6)電氣電平值 - 邏輯“1”以兩線間的電壓差為+(2-6)V表示;邏輯“0”以兩線間的電壓差為-(2-6)V表示。 - 在RS232中任何一條訊號的電壓均為負邏輯關係。即:邏輯“1”-5-15V;邏輯“0”,+5~+15V,噪聲容限為2V。即要求接收器能識別低至+3V的訊號作為邏輯“0”,高到-3V的訊號的訊號作為邏輯“1”。 - RS232介面的訊號電平值較高,易損壞介面電路的晶片,又因為與TTL電平不相容故使用電平轉換電路方能與TTL電路連線。 - RS485介面訊號電平比RS232降低了,就不易損壞介面電路的晶片,且該電平與TTL電平相容,方便與TTL電路連線。 ### 1.1.5. 流控 資料在兩個串列埠傳輸時,常常會出現丟失資料的現象,或者兩臺計算機的處理速度不同,如桌上型電腦與微控制器之間的通訊,接收端資料緩衝區以滿,此時繼續傳送的資料就會丟失,流控制能解決這個問題,當接收端資料處理不過來時,就發出“不再接收”的訊號,傳送端就停止傳送,直到收到“可以繼續傳送”的訊號再發送資料。 因此流控制可以控制資料傳輸的程序,防止資料丟失。PC機中常用的兩種流控為:硬體流控(包括RTS/CTS、DTR/CTS等)和軟體流控制XON/XOFF(繼續/停止)。 #### 1.1.5.1. 硬體流控 硬體流控制常用的有RTS/CTS流控制和DTR/DSR流控制兩種。 **DTR–資料終端就緒(Data Terminal Ready)** 低有效,當為低時,表示本裝置自身準備就緒。此訊號輸出對端裝置,使用對端裝置決定能否與本裝置通訊。 **DSR-資料裝置就緒(Data Set Ready)** 低有效,此訊號由本裝置相連線的對端裝置提供,當為低時,本裝置才能與裝置端進行通訊。 **RTS - 請求傳送(資料)(Request To Send)** 低有效,此訊號由本裝置在需要傳送資料給對端裝置時設定。當為低時,表示本裝置有資料需要向對端裝置傳送。對端裝置能否接收到本方的傳送資料,則通過CTS訊號來應答。 **CTS - 接收發送(請求)(Clear To Send)** 低有效,對端裝置能否接收本方所傳送的資料,由CTS決定。若CTS為低,則表示對端的以準備好,可以接收本端傳送資料。 以RTS/CTS流控制分析,分析主機發送/接收流程: **物理連線** ![ ](https://img-blog.csdnimg.cn/20210204203906543.png) 主機的RTS(輸出訊號),連線到從機的CTS(輸入訊號)。 主機是CTS(輸入訊號),連線到從機的RTS(輸入訊號)。 - 1.主機的傳送過程: 主機查詢主機的CTS腳訊號,此訊號連線到從機的RTS訊號,受從機控制。如果主機CTS訊號有效(為低),表示從機的接收FIFO未滿,從機可以接收,此時主機可以向從機發送資料,並且在傳送過程中要一直查詢CTS訊號是否為有效狀態。主機查詢到CTS無效時,則中止傳送。 主機的CTS訊號什麼時候會無效呢? 從機在接收到主機發送的資料時,從機的接收模組的FIFO如果滿了,則會使從機RTS無效,也即主機的CTS訊號無效。主機查詢到CTS無效時,主機發送中止。 - 2.主機接收模式: 如果主機接收FIFO未滿,那麼使主機RTS訊號有效(為低),即從機的CTS訊號有效。此時如果從機要傳送,傳送前會查詢從機的CTS訊號,如果有效,則開始傳送。並且在傳送過程中要一直查詢從機CTS訊號的有效狀態,如果無效則終止傳送。是否有效由主機的RTS訊號決定。如果主機FIFO滿了,則使主機的RTS訊號無效,也即從機CTS訊號無效,主機接收中止。 #### 1.1.5.2. 軟體流控 由於電纜的限制,在普通的控制通訊中一般不採用硬體流控制,而是使用軟體流控制。一般通過XON/XOFF來實現軟體流控制。常用方法是:當接收端的輸入緩衝區內資料量超過設定的高位時,就向資料傳送端傳送XOFF字元後就立即停止傳送資料;當接收端的輸入緩衝區內資料量低於設定的低位時,就向資料傳送端傳送XON字元(十進位制的17或Control-Q),傳送端收到XON字元後就立即開始傳送資料。一般可從裝置配套源程式中找到傳送端收到XON字元後就立即傳送資料。一般可以從裝置配套源程式中找到傳送的是什麼位元組。 應注意,若傳輸的是二進位制的資料,標誌字元也可能在資料流中出現而引起誤操作,這是軟體流控的缺陷,而硬體流控不會出現這樣的問題。 # 二、Linux serial框架 在Linux系統中,終端是一種字元型裝置,它有多種型別,通常使用tty(Teletype)來簡稱各種型別的終端裝置。對於嵌入式系統而言,最普遍採用的是Uart(Universal Asynchronous Receiver/Transmitter),串列埠,日常生活中簡稱埠 ## 2.1. TTY驅動程式框架 ### 2.1.1. TTY概念 #### 2.1.1.1. 串列埠終端(/dev/ttyS*) 串列埠終端是使用計算機串列埠連線的終端裝置。Linux把每個串列埠都看做是一個字元裝置。這些串列埠所對應的裝置名稱是/dev/ttySAC*; #### 2.1.1.2. 控制檯終端(/dev/console) 在Linux系統中,計算機的輸出裝置通常被稱為控制檯終端,這裡特指printk資訊輸出到裝置。/dev/console是一個虛擬的裝置,它需要對映到真正的tty上,比如通過核心啟動引數“console=ttySCA0”就把console對映到了串列埠0 #### 2.1.1.3. 虛擬終端(/dev/tty*) 當用戶登入時,使用的是虛擬終端。使用Ctcl+Alt[F1 - F6]組合鍵時,我們就可以切換到tty1、tty2、tty3等上面去。tty*就稱為虛擬終端,而tty0則是當前所使用虛擬終端的一個別名。 ### 2.1.2. TTY架構分析 整個 tty架構大概的樣子如圖3.1所示,簡單來分的話可以說成兩層,一層是下層我們的串列埠驅動層,它直接與硬體相接觸,我們需要填充一個 struct uart_ops 的結構體,另一層是上層 tty 層,包括 tty 核心以及線路規程,它們各自都有一個 Ops 結構,使用者空通過間是 tty 註冊的字元裝置節點來訪問。 ![圖3.1tty架構圖](https://img-blog.csdnimg.cn/2021020420402917.png) 如圖3.2所示,tty裝置傳送資料的流程為:tty核心從一個使用者獲取將要傳送給一個tty裝置的資料,tty核心將資料傳遞給tty線路規程驅動,接著資料被傳到tty驅動,tty驅動將資料轉換為可以發給硬體的格式。 接收資料的流程為:從tty硬體接收到的資料向上交給tty驅動,接著進入tty線路規程驅動,再進入tty核心,在這裡它被一個使用者獲取。 ![圖3.2 tty裝置傳送、接收資料流程](https://img-blog.csdnimg.cn/20210204204141894.png) ## 2.2. 關鍵資料結構 ### 2.2.1. Struct uart_driver uart_driver 包含了串列埠裝置名,串列埠驅動名,主次裝置號,串列埠控制檯(可選))等資訊,還封裝了tty_driver (底層串列埠驅動無需關心tty_driver) ```c struct uart_driver { struct module *owner; /*擁有該uart_driver的模組,一般為THIS_MODULE*/ const char *driver_name; /*驅動串列埠名,串列埠裝置名以驅動名為基礎*/ const char *dev_name; /*串列埠裝置名*/ int major; /*主裝置號*/ int minor; /*次裝置號*/ int nr; /*該uart_driver支援的串列埠數*/ struct console *cons; /*其對應的console,若該uart_driver支援serial console, *否則為NULL*/ /* * these are private; the low level driver should not * touch these; they should be initialised to NULL */ struct uart_state *state; /*下層,視窗驅動層*/ struct tty_driver *tty_driver; /*tty相關*/ ``` ### 2.2.2. struct console 實現控制檯列印功能必須要註冊的結構體 ```c struct console { char name[16]; void(*write)(struct console *,const char *, unsigined); int (*read)(struct console *, char *, unsigned); struct tty_driver *(struct console *,int*); void (*unblank)(void); int (*setup)(struct console *, char *); int (*early_setup)(void); short flags; short index; /*用來指定該console使用哪一個uart port (對應的uart_port中的line),如果為-1,kernel會自動選擇第一個uart port*/ int cflag; void *data; struct console *next; }; ``` ### 2.2.3. struct uart_state 每一個uart埠對應著一個uart_state,該結構體將uart_port與對應的circ_buf聯絡起來。uart_state有兩個成員在底層串列埠驅動會用到:xmit和port。使用者空間程式通過串列埠傳送資料時,上層驅動將使用者資料儲存在xmit;而串列埠傳送中斷處理函式就是通過xmit獲取到使用者資料並將它們傳送出去。串列埠接收中斷處理函式需要通過port將接收到的資料傳遞給線路規程層。 ```c struct uart_state { struct tty_port port; enum uart_pm_state pm_state; struct circ_buf xmit; struct uart_port *uart_port; /*對應於一個串列埠裝置*/ }; ``` ### 2.2.4. struct uart_port uart_port用於描述串列埠埠的I/O埠或I/O記憶體地址、FIFO大小、埠型別、串列埠時鐘等資訊。實際上,一個uart_port實現對應一個串列埠裝置。 ```c struct uart_port { spinlock_t lock; /* port lock */ unsigned long iobase; /* in/out[bwl] */ unsigned char __iomem *membase; /* read/write[bwl] */ unsigned int (*serial_in)(struct uart_port *, int); void (*serial_out)(struct uart_port *, int, int); void (*set_termios)(struct uart_port *, struct ktermios *new, struct ktermios *old); int (*handle_irq)(struct uart_port *); void (*pm)(struct uart_port *, unsigned int state, unsigned int old); void (*handle_break)(struct uart_port *); unsigned int irq; /* irq number */ unsigned long irqflags; /* irq flags */ unsigned int uartclk; /* base uart clock */ unsigned int fifosize; /* tx fifo size */ unsigned char x_char; /* xon/xoff char */ unsigned char regshift; /* reg offset shift */ unsigned char iotype; /* io access style */ unsigned char unused1; #define UPIO_PORT (0) #define UPIO_HUB6 (1) #define UPIO_MEM (2) #define UPIO_MEM32 (3) #define UPIO_AU (4) /* Au1x00 and RT288x type IO */ #define UPIO_TSI (5) /* Tsi108/109 type IO */ unsigned int read_status_mask; /* driver specific */ unsigned int ignore_status_mask; /* driver specific */ struct uart_state *state; /* pointer to parent state */ struct uart_icount icount; /* statistics */ struct console *cons; /* struct console, if any */ #if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ) unsigned long sysrq; /* sysrq timeout */ #endif upf_t flags; #define UPF_FOURPORT ((__force upf_t) (1 << 1)) #define UPF_SAK ((__force upf_t) (1 << 2)) #define UPF_SPD_MASK ((__force upf_t) (0x1030)) #define UPF_SPD_HI ((__force upf_t) (0x0010)) #define UPF_SPD_VHI ((__force upf_t) (0x0020)) #define UPF_SPD_CUST ((__force upf_t) (0x0030)) #define UPF_SPD_SHI ((__force upf_t) (0x1000)) #define UPF_SPD_WARP ((__force upf_t) (0x1010)) #define UPF_SKIP_TEST ((__force upf_t) (1 << 6)) #define UPF_AUTO_IRQ ((__force upf_t) (1 << 7)) #define UPF_HARDPPS_CD ((__force upf_t) (1 << 11)) #define UPF_LOW_LATENCY ((__force upf_t) (1 << 13)) #define UPF_BUGGY_UART ((__force upf_t) (1 << 14)) #define UPF_NO_TXEN_TEST ((__force upf_t) (1 << 15)) #define UPF_MAGIC_MULTIPLIER ((__force upf_t) (1 << 16)) /* Port has hardware-assisted h/w flow control (iow, auto-RTS *not* auto-CTS) */ #define UPF_HARD_FLOW ((__force upf_t) (1 << 21)) /* Port has hardware-assisted s/w flow control */ #define UPF_SOFT_FLOW ((__force upf_t) (1 << 22)) #define UPF_CONS_FLOW ((__force upf_t) (1 << 23)) #define UPF_SHARE_IRQ ((__force upf_t) (1 << 24)) #define UPF_EXAR_EFR ((__force upf_t) (1 << 25)) #define UPF_BUG_THRE ((__force upf_t) (1 << 26)) /* The exact UART type is known and should not be probed. */ #define UPF_FIXED_TYPE ((__force upf_t) (1 << 27)) #define UPF_BOOT_AUTOCONF ((__force upf_t) (1 << 28)) #define UPF_FIXED_PORT ((__force upf_t) (1 << 29)) #define UPF_DEAD ((__force upf_t) (1 << 30)) #define UPF_IOREMAP ((__force upf_t) (1 << 31)) #define UPF_CHANGE_MASK ((__force upf_t) (0x17fff)) #define UPF_USR_MASK ((__force upf_t) (UPF_SPD_MASK|UPF_LOW_LATENCY)) unsigned int mctrl; /* current modem ctrl settings */ unsigned int timeout; /* character-based timeout */ unsigned int type; /* port type */ const struct uart_ops *ops; unsigned int custom_divisor; unsigned int line; /* port index */ resource_size_t mapbase; /* for ioremap */ struct device *dev; /* parent device */ unsigned char hub6; /* this should be in the 8250 driver */ unsigned char suspended; unsigned char irq_wake; unsigned char unused[2]; void *private_data; /* generic platform data pointer */ }; ``` ### 2.2.5. struct uart_ops struct uart_ops涵蓋了驅動可對串列埠的所有操作 ```c struct uart_ops { unsigned int (*tx_empty)(struct uart_port *); void (*set_mctrl)(struct uart_port *, unsigned int mctrl); unsigned int (*get_mctrl)(struct uart_port *); void (*stop_tx)(struct uart_port *); void (*start_tx)(struct uart_port *); void (*throttle)(struct uart_port *); void (*unthrottle)(struct uart_port *); void (*send_xchar)(struct uart_port *, char ch); void (*stop_rx)(struct uart_port *); void (*enable_ms)(struct uart_port *); void (*break_ctl)(struct uart_port *, int ctl); int (*startup)(struct uart_port *); void (*shutdown)(struct uart_port *); void (*flush_buffer)(struct uart_port *); void (*set_termios)(struct uart_port *, struct ktermios *new, struct ktermios *old); void (*set_ldisc)(struct uart_port *, int new); void (*pm)(struct uart_port *, unsigned int state, unsigned int oldstate); int (*set_wake)(struct uart_port *, unsigned int state); /* * Return a string describing the type of the port */ const char *(*type)(struct uart_port *); /* * Release IO and memory resources used by the port. * This includes iounmap if necessary. */ void (*release_port)(struct uart_port *); /* * Request IO and memory resources used by the port. * This includes iomapping the port if necessary. */ int (*request_port)(struct uart_port *); void (*config_port)(struct uart_port *, int); int (*verify_port)(struct uart_port *, struct serial_struct *); int (*ioctl)(struct uart_port *, unsigned int, unsigned long); #ifdef CONFIG_CONSOLE_POLL int (*poll_init)(struct uart_port *); void (*poll_put_char)(struct uart_port *, unsigned char); int (*poll_get_char)(struct uart_port *); #endif }; ``` ## 2.3. 關鍵流程 ### 2.3.1. 註冊流程 #### 2.3.1.1. 註冊uart_driver 此介面在uart driver中呼叫,用來註冊uart_driver到kernel中,呼叫階段在uart driver的初始階段,例如:module_init(), uart_driver的註冊流程圖 ![圖3.3uart driver註冊流程](https://img-blog.csdnimg.cn/20210204204355209.png) 註冊過程主要做了以下操作: - 1、根據driver支援的最大裝置數,申請n個uart_state空間,每一個uart_state都有一個uart_port。 - 2、分配一個tty_driver,並將uart_driver->tty_driver指向它。 - 3、對tty_driver進行設定,其中包括預設波特率、檢驗方式等,還有一個重要的ops,結構體tty_operation的註冊,它是tty核心與串列埠驅動通訊的介面。 - 4、初始化每一個uart_state的tty_port; - 5、註冊tty_driver。 註冊uart_driver實際上是註冊tty_driver,與使用者空間打交道的工作完全交給tty_driver,這一部分是核心實現好的不需要修改 #### 2.3.1.2. 新增uart_port 此介面用於註冊一個uart port 到uart driver上,通過註冊,uart driver就可以訪問對應的uart port,進行資料收發。該介面在uart driver中的probe函式呼叫,必須保證晚於uart_register_drver的註冊過程。 uart driver在呼叫介面前,要手動設定uart_port的操作uart_ops,使得通過呼叫uart_add_one_port介面後驅動完成硬體的操作介面註冊。uart新增port流程如圖3-4所示: ![圖3-4 uart新增port流程圖](https://img-blog.csdnimg.cn/20210204204416412.png) ## 2.4. 資料收發流程 ### 2.4.1. 開啟裝置(open操作) open裝置的大體流程如圖3-5所示: ![圖3-5 open裝置流程](https://img-blog.csdnimg.cn/20210204204524481.jpg) ### 2.4.2. 資料傳送流程(write操作) 傳送資料大體流程如圖3-6所示: ![圖3-6 傳送資料流程](https://img-blog.csdnimg.cn/20210204204625556.png) ### 2.4.3. 資料接收流程(read操作) 接收資料的大體流程如圖3-7所示: ![圖3-7資料接收流程](https://img-blog.csdnimg.cn/20210204204658351.png) ### 2.4.4. 關閉裝置(close操作) close裝置的大體流程如圖3-8所示: ![圖3-8 close裝置流程](https://img-blog.csdnimg.cn/202102042047283.png) ### 2.4.5. 登出流程 #### 2.4.5.1. 移除uart_port 此介面用於從uart driver上登出一個uart port,該介面在uart driver中的remove函式中呼叫。uart移除port的流程如圖3-9所示: ![圖3.9 uart移除port流程圖](https://img-blog.csdnimg.cn/20210204204745173.png) #### 2.4.5.2. 登出uart_driver 此介面在uart driver中呼叫,用來從kernel中登出uart_driver,呼叫階段在uart driver的退出階段,例如:module_exit(),uart driver的登出流程如圖3.10所示 ![ ](https://img-blog.csdnimg.cn/20210204204759638.png) ## 2.5. 使用rs485通訊 ### 2.5.1. rs485和rs232的區別 uart(TTL-3.3V)/rs232(工業級 +-12V)是電壓驅動,rs485是電流驅動(能傳輸更遠的距離) rS232用電平表示資料,使用2根線可實現全雙工,rs485用差分電平表示資料,因此必須用4根線實現全雙工rs485; **全雙工**:uart-tx 1根線變成rs485-A/B 2根線;uart-rx 1根線變成rs485- X/Y兩根線; **rs485半雙工**: 將全雙工的A/B和X/Y合併起來分時複用; rs485-de/re是給轉換器的一個控制訊號,對我們晶片來說,都是輸出; ### 2.5.2. rs485除錯方法: 首先保證uart模組和相關gpio,電壓轉換晶片工作正常: - a,保證uart tx/rx功能正常。 - b,用gpio-output來控制 de/re 相關的2個gpio,觀察 de/re的gpio輸出low/high是否正常 - c,在b的基礎上,單獨除錯 rs485-tx/rs485-rx,單端除錯是否pass. **模式1** 2-gpio-normal-uart-rs485-halfduplex (2個gpio獨立控制de/re, enable就是將相關gpio設定到active電平;不用uart控制器的rs485模式;uart控制器處於normal模式) - a, 預設re-en, de-dis,預設rs485-rx - b, 當要傳送的時候,re-dis, de-enable, 然後uart-tx. - c, tx完成之後,de-dis; re-en,進入預設的rs485-rx模式。 **模式2** 1-gpio-normal-uart-rs485-halfduplex 這個模式的前提條件,外設器件的 de/re必須是相反極性的,比如de是高電平有效,re是低電平有效,則可以用一個gpio,來控制 de/re,此時de/re一定是互斥的。 (1個gpio控制de/re, enable就是將相關gpio設定到active電平;不用uart控制器的rs485模式;uart控制器處於normal模式) - a, re-en,進入rs485-rx模式 (re 通常是低電平有效,這一步就是 設定 re對應的gpio為低電平) - b, 當要傳送的時候,設定gpio:re-disable, de-enable, 然後uart-tx.(re 通常是低電平有效,這一步就是 設定 re對應的gpio為高電平) - c, tx完成之後,de-disable; re-enable,進入預設的rs485-rx模式。(re 通常是低電平有效,這一步就是 設定 re對應的gpio為低電平) **模式3** rs485-software-halfduplex(de/re 獨立輸出) (使能uart控制器的rs485模式; 通過uart模組內部reg來控制 de/re 訊號) - a,使能uart控制器的 rs485模式,並按照電壓轉換晶片的特性,設定de/re polarity - b, 設定rs485的模式為 sw-half-duplex, 設定 de-timing暫存器; 設定 de/re turnaround 暫存器。 - c, 預設為rs485-rx模式,設定 de-dis/re-en - d, 當要tx的時候,設定 de-en/re-dis - e, 傳送完成,設定 de-dis/re-en **模式4** rs485-hardware-halfduplex(de/re 獨立輸出) 基本配置同模式3,但是設定 rs485模式為 hardware-halfduplex模式 - a, 只要設定 de-en/rx-en 都為1,然後就不用管了,硬體實現半雙工切換。 **模式5:** 使用純硬體的辦法實現RS485半雙工功能,電路如圖所示: ![ ](https://img-blog.csdnimg.cn/20210204204850181.png) **接收:** 預設沒有資料時,UART_TX為高電平,三極體導通,485晶片RE低電平使能,RO接收資料使能,此時從485AB口收到什麼資料就會通過RO通道傳到MCU,完成資料接收過程。 **傳送:** 當傳送資料時,UART_TX會有一個下拉的電平,表示開始傳送資料,此時三極體截止,DE為高電平傳送使能。當傳送資料‘0’時,由於DI口連線地,此時資料‘0’就會傳輸到AB口 A-B<0,傳輸‘0’,完成了低電平的傳輸。當傳送‘1’時,此時三極體導通,按理說RO使能,此時由於還處在傳送資料中,這種狀態下485處於高阻態,此時的狀態通過A上拉B下拉電阻決定,此時A-B>0傳輸‘1’,完成高電平的傳輸。 # 3. 模組詳細設計 ## 3.1. 關鍵函式介面 ### 3.1.1. uart_register_driver ```c /*功能: uart_register_driver用於串列埠驅動uart_driver註冊到核心(串列埠核心層)中,通常在模組初始化函式呼叫該函式。 *引數:drv:要註冊的uart_driver *返回值:成功,返回0;否則返回錯誤碼 */ int uart_register_driver(struct uart_driver *drv) ``` ### 3.1.2. uart_unregister_driver ```c /*功能:uart_unregister 用於登出我們已註冊的uart_driver,通常在模組解除安裝函式呼叫該函式, *引數 : drv:要登出的uart_driver *返回值:成功返回0,否則返回錯誤碼 */ void uart_unregister_driver(struct uart_driver *drv) ``` ### 3.1.3. uart_add_one_port ```c /*功能:uart_add_one_port用於為串列埠驅動新增一個串列埠埠,通常在探測到裝置後(驅動的裝置probe方法)呼叫該函式 *引數: * drv:串列埠驅動 * port:要新增的串列埠埠 *返回值:成功,返回0;否則返回錯誤碼 */ int uart_add_one_port(struct uart_driver *drv,struct uart_port *port) ``` ### 3.1.4. uart_remove_one_port ```c /*功能:uart_remove_one_port用於刪除一個已經新增到串列埠驅動中的串列埠埠,通常在驅動解除安裝時呼叫該函式 *引數: * drv:串列埠驅動 * port:要刪除的串列埠埠 *返回值:成功,返回0;否則返回錯誤碼 */ int uart_remove_one_port(struct uart_driver *drv,struct uart_port *port) ``` ### 3.1.5. uart_write_wakeup ```c /*功能:uart_write_wakeup喚醒上層因串列埠埠寫資料而堵塞的程序,通常在串列埠傳送中斷處理函式中呼叫該函式 *引數: * port: 需要喚醒寫堵塞程序的串列埠埠 */ void uart_write_wakeup(struct uart_port *port) ``` ### 3.1.6. uart_suspend_port ```c /*功能:uart_suspend_port用於掛起特定的串列埠埠 *引數: * drv:要掛起的串列埠埠鎖所屬的串列埠驅動 * port:要掛起的串列埠埠 *返回值:成功返回0;否則返回錯誤碼 */ int uart_suspend_port(struct uart_driver *drv, struct uart_port *port) ``` ### 3.1.7. uart_resume_port ```c /*功能:uart_resume_port用於恢復某一已掛起的串列埠 *引數: * drv:要恢復的串列埠埠所屬的串列埠驅動 * port:要恢復的串列埠埠 *返回值:成功返回0;否則返回錯誤碼 */ int uart_resume_port(struct uart_driver *drv, struct uart_port *port) ``` ### 3.1.8. uart_get_baud_rate ```c /*功能:uart_get_baud_rate通過解碼termios結構體來獲取指定串列埠的波特率 *引數: * port:要獲取波特率的串列埠埠 * termios:當前期望的termios配置(包括串列埠波特率) * old:以前的termios配置,可以為NULL * min:可以接受的最小波特率 * max:可以接受的最大波特率 * 返回值:串列埠波特率 */ unsigned int uart_get_baund_rate(struct uart_port *port, struct ktermios *termios, struct ktermios *old,unsigned int min, unsigned int max) ``` ### 3.1.9. uart_get_divisor ```c /*功能:uart_get_divisor 用於計算某一波特率的串列埠時鐘分頻數(串列埠波特率除數) *引數: * port:要計算分頻數的串列埠埠 * baud:期望的波特率 *返回值:串列埠時鐘分頻數 */ unsigned int uart_get_divisor(struct uart_port *port, unsigned int baund) ``` ### 3.1.10. uart_update_timeout ```c /*功能:uart_update_timeout用於更新(設定)串列埠FIFO超出時間 *引數: * port:要更新超時間的串列埠埠 * cfalg:termios結構體的cflag值 * baud:串列埠的波特率 */ void uart_update_timeout(struct uart_port *port,unsigned int cflag, unsigned int baud) ``` ### 3.1.11. uart_insert_char ```c /*功能:uart_insert_char用於向uart層插入一個字元 *引數: * port:要寫資訊的串列埠埠 * status:RX buffer狀態 * overrun:在status中的overrun bit掩碼 * ch:需要插入的字元 * flag:插入字元的flag:TTY_BREAK,TTY_PSRIYY, TTY_FRAME */ void uart_insert_char(struct uart_port *port, unsigned int status, unsigned int overrun,unsigned int ch, unsigned int flag) ``` ### 3.1.12. uart_console_write ```c /*功能:uart_console_write用於向串列埠埠寫一控制檯資訊 *引數: * port:要寫資訊的串列埠埠 * s:要寫的資訊 * count:資訊的大小 * putchar:用於向串列埠埠寫字元的函式,該函式有兩個引數:串列埠埠和要寫的字元 */ Void uart_console_write(struct uart_port *port,const char *s, unsigned int count,viod(*putchar)(struct uart_port*, int)) ``` # 4. 模組使用說明 ## 4.1. 串列埠程式設計 ### 4.1.1. 串列埠控制函式 | 屬性 |說明 | |--|:--| |tcgetatrr|取屬性(termios結構) tcsetarr|設定屬性(termios結構) cfgetispeed|得到輸入速度 cfsetispeed|得到輸出速度 cfstospeed|設定輸出速度 tcdrain|等待所有輸出都被傳輸 tcflow|掛起傳輸或接收 tcflush|刷請未決輸出和/或輸入 tcsendbreak|送BREAK字元 tcgetpgrp|得到前臺程序組ID Tcsetpgrp|設定前臺程序組ID ### 4.1.2. 串列埠配置流程 - (1) 保持原先串列埠配置,使用tegetatrr(fd, &oldtio); ```c struct termious newtio, oldtio; tegetattr(fd, &oldtio); ``` - (2) 啟用選項有CLOCAL和CREAD,用於本地連線和接收使用 ```c newtio.cflag |= CLOCAL|CREAD; ``` - (3) 設定波特率 ```c newtio.c_cflag = B115200; ``` - (4) 設定資料位,需使用掩碼設定 ```c newtio.c_cflag &= ~CSIZE; Newtio.c_cflag |= CS8; ``` - (5) 設定停止位,通過啟用c_cflag中的CSTOP實現。若停止位為1,則清除CSTOPB,若停止位為2,則啟用CSTOP ```c newtio.c_cflag &= ~CSTOPB; /*停止位設定為1*/ Newtio.c_cflag |= CSTOPB; /*停止位設定為2 */ ``` - (6) 設定流控 ```c newtio.c_cfag |= CRTSCTS /*開啟硬體流控 */ newtio.c_cfag |= (IXON | IXOFF | IXANY); /*開啟軟體流控*/ ``` - (7) 奇偶檢驗位設定,使用c_cflag和c_ifag. 設定奇校驗 ```c newtio.c_cflag |= PARENB; newtio.c_cflag |= PARODD; newtio.c_iflag |= (INPCK | ISTRIP); ``` 設定偶校驗 ```c newtio.c_iflag |= (INPCK | ISTRIP); newtio.c_cflag |= PARENB; newtio.c_cflag |= ~PARODD; ``` - (8) 設定最少字元和等待時間,對於接收字元和等待時間沒有什麼特別的要求,可設定為0: ```c newtio.c_cc[VTIME] = 0; newtio.c_cc[VMIN] = 0; ``` - (9) 處理要寫入的引用物件 tcflush函式刷清(拋棄)輸入緩衝(終端程式已經接收到,但使用者程式尚未讀)或輸出緩衝(使用者程式已經寫,但未傳送)。 ```c int tcflash(int filedes, int quene) quene數應當是下列三個常數之一: *TCIFLUSH 刷清輸入佇列 *TCOFLUSH 刷清輸出佇列 *TCIOFLUSH 刷清輸入、輸出佇列 例如: tcflush(fd, TCIFLUSH); ``` - (10) 啟用配置,在完成配置後,需要啟用配置使其生效。使用tcsetattr()函式: ```c int tcsetarr(int filedes, const struct termios *termptr); opt 指定在什麼時候新的終端屬性才起作用, *TCSANOW:更改立即發生 *TCSADRAIN:傳送了所有輸出後更改才發生。若更改輸出引數則應使用此選項 *TCSAFLUSH:傳送了所有輸出後更改才發生。更進一步,在更改發生時未讀的 所有輸入資料都被刪除(刷清) 例如:tcsetatrr(fd, TCSANOW, &newtio); ``` ### 4.1.3. 使用流程 - (1)開啟串列埠,例如"/dev/ttySLB0" ```c fd = open("/dev/ttySLB0",O_RDWR | O_NOCTTY | O_NDELAY); O_NOCTTY:是為了告訴Linux這個程式不會成為這個埠上的“控制終端”。如果不這樣做的話,所有的輸入,比如鍵盤上過來的Ctrl+C中止訊號等等,會影響到你的程序。 O_NDELAY:這個標誌則是告訴Linux這個程式並不關心DCD訊號線的狀態,也就是不管串列埠是否有資料到來,都是非阻塞的,程式繼續執行。 ``` - (2)恢復串列埠狀態為阻塞狀態,用於等待串列埠資料的讀入,用fcntl函式: ```c fcntl(fd,F_SETFL,0); //F_SETFL:設定檔案flag為0,即預設,即阻塞狀態 ``` - (3)接著測試開啟的檔案描述符是否應用一個終端裝置,以進一步確認串列埠是否正確開啟。 ```c isatty(STDIN_FILENO); ``` - (4)讀寫串列埠 ```c 串列埠的讀寫與普通檔案一樣,使用read,write函式 read(fd, buf ,8); write(fd,buff,8); ``` ### 4.1.4. Demo 以下給出一個測溫模組收取資料的例子 ```c #