更多嵌入式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所示:
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=161+8時,采樣的值為bit0資料;當計數器的值為40=162+8時,采樣的值為bit1資料;依次類推,進行後面6個資料的采樣。如果需要進行奇偶校驗位,則當計數器的值為152=169+8時,采樣的值為奇偶位;當計數器的值為168=1610+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流控制分析,分析主機發送/接收流程:
實體連接配接
主機的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.2所示,tty裝置發送資料的流程為:tty核心從一個使用者擷取将要發送給一個tty裝置的資料,tty核心将資料傳遞給tty線路規程驅動,接着資料被傳到tty驅動,tty驅動将資料轉換為可以發給硬體的格式。
接收資料的流程為:從tty硬體接收到的資料向上交給tty驅動,接着進入tty線路規程驅動,再進入tty核心,在這裡它被一個使用者擷取。
2.2. 關鍵資料結構
2.2.1. Struct uart_driver
uart_driver 包含了序列槽裝置名,序列槽驅動名,主次裝置号,序列槽控制台(可選))等資訊,還封裝了tty_driver
(底層序列槽驅動無需關心tty_driver)
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
實作控制台列印功能必須要注冊的結構體
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将接收到的資料傳遞給線路規程層。
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實作對應一個序列槽裝置。
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涵蓋了驅動可對序列槽的所有操作
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的注冊流程圖
注冊過程主要做了以下操作:
- 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所示:
2.4. 資料收發流程
2.4.1. 打開裝置(open操作)
open裝置的大體流程如圖3-5所示:
2.4.2. 資料發送流程(write操作)
發送資料大體流程如圖3-6所示:
2.4.3. 資料接收流程(read操作)
接收資料的大體流程如圖3-7所示:
2.4.4. 關閉裝置(close操作)
close裝置的大體流程如圖3-8所示:
2.4.5. 登出流程
2.4.5.1. 移除uart_port
此接口用于從uart driver上登出一個uart port,該接口在uart driver中的remove函數中調用。uart移除port的流程如圖3-9所示:
2.4.5.2. 登出uart_driver
此接口在uart driver中調用,用來從kernel中登出uart_driver,調用階段在uart driver的退出階段,例如:module_exit(),uart driver的登出流程如圖3.10所示
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半雙工功能,電路如圖所示:
接收:
預設沒有資料時,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
/*功能: uart_register_driver用于序列槽驅動uart_driver注冊到核心(序列槽核心層)中,通常在子產品初始化函數調用該函數。
*參數:drv:要注冊的uart_driver
*傳回值:成功,傳回0;否則傳回錯誤碼
*/
int uart_register_driver(struct uart_driver *drv)
3.1.2. uart_unregister_driver
/*功能:uart_unregister 用于登出我們已注冊的uart_driver,通常在子產品解除安裝函數調用該函數,
*參數 : drv:要登出的uart_driver
*傳回值:成功傳回0,否則傳回錯誤碼
*/
void uart_unregister_driver(struct uart_driver *drv)
3.1.3. uart_add_one_port
/*功能: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
/*功能: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
/*功能:uart_write_wakeup喚醒上層因序列槽端口寫資料而堵塞的程序,通常在序列槽發送中斷處理函數中調用該函數
*參數:
* port: 需要喚醒寫堵塞程序的序列槽端口
*/
void uart_write_wakeup(struct uart_port *port)
3.1.6. uart_suspend_port
/*功能:uart_suspend_port用于挂起特定的序列槽端口
*參數:
* drv:要挂起的序列槽端口鎖所屬的序列槽驅動
* port:要挂起的序列槽端口
*傳回值:成功傳回0;否則傳回錯誤碼
*/
int uart_suspend_port(struct uart_driver *drv, struct uart_port *port)
3.1.7. uart_resume_port
/*功能: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
/*功能: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
/*功能:uart_get_divisor 用于計算某一波特率的序列槽時鐘分頻數(序列槽波特率除數)
*參數:
* port:要計算分頻數的序列槽端口
* baud:期望的波特率
*傳回值:序列槽時鐘分頻數
*/
unsigned int uart_get_divisor(struct uart_port *port, unsigned int baund)
3.1.10. uart_update_timeout
/*功能: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
/*功能: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
/*功能: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);
struct termious newtio, oldtio;
tegetattr(fd, &oldtio);
- (2) 激活選項有CLOCAL和CREAD,用于本地連接配接和接收使用
newtio.cflag |= CLOCAL|CREAD;
- (3) 設定波特率
newtio.c_cflag = B115200;
- (4) 設定資料位,需使用掩碼設定
newtio.c_cflag &= ~CSIZE;
Newtio.c_cflag |= CS8;
- (5) 設定停止位,通過激活c_cflag中的CSTOP實作。若停止位為1,則清除CSTOPB,若停止位為2,則激活CSTOP
newtio.c_cflag &= ~CSTOPB; /*停止位設定為1*/
Newtio.c_cflag |= CSTOPB; /*停止位設定為2 */
- (6) 設定流控
newtio.c_cfag |= CRTSCTS /*開啟硬體流控 */
newtio.c_cfag |= (IXON | IXOFF | IXANY); /*開啟軟體流控*/
-
(7) 奇偶檢驗位設定,使用c_cflag和c_ifag.
設定奇校驗
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
設定偶校驗
newtio.c_iflag |= (INPCK | ISTRIP);
newtio.c_cflag |= PARENB;
newtio.c_cflag |= ~PARODD;
- (8) 設定最少字元和等待時間,對于接收字元和等待時間沒有什麼特别的要求,可設定為0:
newtio.c_cc[VTIME] = 0;
newtio.c_cc[VMIN] = 0;
-
(9) 處理要寫入的引用對象
tcflush函數刷清(抛棄)輸入緩沖(終端程式已經接收到,但使用者程式尚未讀)或輸出緩沖(使用者程式已經寫,但未發送)。
int tcflash(int filedes, int quene)
quene數應當是下列三個常數之一:
*TCIFLUSH 刷清輸入隊列
*TCOFLUSH 刷清輸出隊列
*TCIOFLUSH 刷清輸入、輸出隊列
例如:
tcflush(fd, TCIFLUSH);
- (10) 激活配置,在完成配置後,需要激活配置使其生效。使用tcsetattr()函數:
int tcsetarr(int filedes, const struct termios *termptr);
opt 指定在什麼時候新的終端屬性才起作用,
*TCSANOW:更改立即發生
*TCSADRAIN:發送了所有輸出後更改才發生。若更改輸出參數則應使用此選項
*TCSAFLUSH:發送了所有輸出後更改才發生。更進一步,在更改發生時未讀的
所有輸入資料都被删除(刷清)
例如:tcsetatrr(fd, TCSANOW, &newtio);
4.1.3. 使用流程
- (1)打開序列槽,例如"/dev/ttySLB0"
fd = open("/dev/ttySLB0",O_RDWR | O_NOCTTY | O_NDELAY);
O_NOCTTY:是為了告訴Linux這個程式不會成為這個端口上的“控制終端”。如果不這樣做的話,所有的輸入,比如鍵盤上過來的Ctrl+C中止信号等等,會影響到你的程序。
O_NDELAY:這個标志則是告訴Linux這個程式并不關心DCD信号線的狀态,也就是不管序列槽是否有資料到來,都是非阻塞的,程式繼續執行。
- (2)恢複序列槽狀态為阻塞狀态,用于等待序列槽資料的讀入,用fcntl函數:
fcntl(fd,F_SETFL,0); //F_SETFL:設定檔案flag為0,即預設,即阻塞狀态
- (3)接着測試打開的檔案描述符是否應用一個終端裝置,以進一步确認序列槽是否正确打開。
isatty(STDIN_FILENO);
- (4)讀寫序列槽
序列槽的讀寫與普通檔案一樣,使用read,write函數
read(fd, buf ,8);
write(fd,buff,8);
4.1.4. Demo
以下給出一個測溫子產品收取資料的例子
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <log/log.h>
#include <stdlib.h>
#define UART_DEVICE "/dev/ttySLB1"
struct temp {
float temp_max1;
float temp_max2;
float temp_max3;
float temp_min;
float temp_mean;
float temp_enviromem;
char temp_col[1536];
};
int main(void)
{
int count, i, fd;
struct termios oldtio, newtio;
struct temp *temp;
temp = (struct temp *)malloc(sizeof(struct temp));
if (!temp) {
printf("malloc failed\n");
return -1;
}
char cmd_buf1[] = { 0xAA, 0x01, 0x04, 0x00, 0x06, 0x10, 0x05, 0x00, 0xBB};
char cmd_buf2[] = { 0xAA, 0x01, 0x04, 0x00, 0x00, 0xA0, 0x00, 0x03, 0xBB};
char cmd_buf3[] = { 0xAA, 0x01, 0x04, 0x00, 0x03, 0x10, 0x01, 0x00, 0xBB};
char read_buf[2000];
//-----------打開uart裝置檔案------------------
fd = open(UART_DEVICE, O_RDWR | O_NOCTTY);
if (fd < 0) {
printf("Open %s failed\n", UART_DEVICE);
return -1;
} else {
printf("Open %s successfully\n", UART_DEVICE);
}
//-----------設定操作參數-----------------------
tcgetattr(fd, &oldtio);//擷取目前操作模式參數
memset(&newtio, 0, sizeof(newtio));
//波特率=230400 資料位=8 使能資料接收
newtio.c_cflag = B230400 | CS8 | CLOCAL | CREAD | CSTOPB;
newtio.c_iflag = IGNPAR;
tcflush(fd, TCIFLUSH);//清空輸入緩沖區和輸出緩沖區
tcsetattr(fd, TCSANOW, &newtio);//設定新的操作參數
//printf("input: %s, len = %d\n", cmd_buf, strlen(cmd_buf));
//------------向urat發送資料-------------------
for (i = 0; i < 9; i++)
printf("%#X ", cmd_buf1[i]);
count = write(fd, cmd_buf1, 9);
if (count != 9) {
printf("send failed\n");
return -1;
}
usleep(500000);
memset(read_buf, 0, sizeof(read_buf));
count = read(fd, read_buf, sizeof(read_buf));
if (count > 0) {
for (i = 0; i < count; i++);
temp->temp_max1 = read_buf[7] << 8 | read_buf[6];
temp->temp_max2 = read_buf[9] << 8 | read_buf[8];
temp->temp_max3 = read_buf[11] << 8 | read_buf[10];
temp->temp_min = read_buf[13] << 8 | read_buf[12];
temp->temp_mean = read_buf[15] << 8 | read_buf[14];
printf("temp->temp_max1 = %f\n", temp->temp_max1 * 0.01);
printf("temp->temp_max2 = %f\n", temp->temp_max2 * 0.01);
printf("temp->temp_max3 = %f\n", temp->temp_max3 * 0.01);
printf("temp->temp_min = %f\n", temp->temp_min * 0.01);
printf("temp->temp_mean = %f\n", temp->temp_mean * 0.01);
} else {
printf("read temp failed\n");
return -1;
}
count = write(fd, cmd_buf3, 9);
if (count != 9) {
printf("send failed\n");
return -1;
}
usleep(365);
memset(read_buf, 0, sizeof(read_buf));
count = read(fd, read_buf, sizeof(read_buf));
if (count > 0) {
for (i = 0; i < count; i++);
temp->temp_enviromem = read_buf[7] << 8 | read_buf[6];
printf("temp->temp_enviromem = %f\n", temp->temp_enviromem * 0.01);
} else {
printf("read enviromem failed\n");
return -1;
}
count = write(fd, cmd_buf2, 9);
if (count != 9) {
printf("send failed\n");
return -1;
}
usleep(70000);
memset(read_buf, 0, sizeof(read_buf));
memset(temp->temp_col, 0, sizeof(temp->temp_col));
count = read(fd, read_buf, sizeof(read_buf));
printf("count = %d\n", count);
if (count > 0) {
for (i = 0; i < count - 7; i++)
temp->temp_col[i] = read_buf[i+6];
for (i = 0; i < 1536; i++)
{
if (!(i%10))
printf("\n");
printf("%#X ", temp->temp_col[i]);
}
} else {
printf("read temp colour failed\n");
return -1;
}
free(temp);
close(fd);
tcsetattr(fd, TCSANOW, &oldtio); //恢複原先的設定
return 0;
}
歡迎關注公衆号:一口Linux