数据在单条传输线上,一位接一位地按顺序传送的方式称为串行通信。串行通信有两种方式:异步方式和同步方式。实现串行通信的接口叫串行接口,它主要的功能就是实现CPU与输入输出设备之间的串并行转换。把从CPU传送过来的并行数据转换成串行从输出设备输出,以及把从输入设备输入的串行数据转换成并行数据送给CPU。串行通信中,因为数据是一位一位地传输,所以为了是接收端能够识别接收到的每位数据的具体意思,收发双方就需要遵守相同的约定,即串口通信协议,它用来保证接收方知晓数据传输的开始及结束,以及判断接收数据的正确性。在异步串行通信中,在发送数据之前会先发送一个开始位,表示数据传输的开始,在发送完所有的数据后,会再多发送一个结束位,表示整个传输过程结束。为了保证数据的正确性,通常会采用奇偶校验法来验证接收到的数据,所以在传输的原始数据后面会再加上奇偶校验位。在同步串行通信中,收发双方采用同一个时钟源来双方的同步,所以传输数据时无需起始位和停止位。同步的方法有外同步和内同步两种,外同步法是在发送数据之前向接收端发送一串时钟脉冲,接收端按这个时钟频率调整自己的时序,是接收时钟频率锁定在接收到的时钟频率上,内同步法是接收端从接收到的数据信息波形本身提取同步的方法。
RS-232C定义了串行通信的物理接口,主要包括机械指标和电气指标。RS-232C标准的连接器有DB25和DB9两种,目前PC上常见的COM口就是DB9类型的连接器。DB9包含9个信号Pin,分别是TxD/RxD,RTS/CTS,DTR/DSR,SG/DCD/RI。TxD/RxD是用来发送和接收数据的,RTS/CTS是用来实现硬件流量控制的,当数据终端设备准备好数据时,会发出RTS信号通知数据通信设备,如果数据通信设备已经准备好接收数据,就会发送一个CTS信号来响应RTS信号。DTR/DSR是数据终端设备和数据通信设备之间的联络信号,DTR表明数据终端设备准备就绪,而DSR用来表明数据通信设备准备就绪。SG为信号地,用来提供参考电平。当通信双方之间有接调制解调器的时候,DCD/RI用来反映调制解调器所侦测到的状态。
异步串行通信的核心是通用异步收发传输器,通常称为UART。常见的UART芯片有INS8250,PC16550,PC16650,PC16750。以PC16650为例,与CPU连接一侧,它有8条并行数据线实现与CPU的并行数据传输,另外还有一个选择和控制逻辑单元,负责实现芯片片选以及IO寄存器选址功能。与输入输出设备连接一侧,由接收移位寄存器和发送移位寄存器实现串行数据的接收和发送。同时还有一个Modem控制逻辑单元,负责数据终端设备和数据通信设备之间的联络信号以及流量控制。在芯片内部,有12个8位寄存器,但是PC16550与CPU之间只有三个寻址Pin脚用来寻址,所以最多只能访问到8个IO端口,也就是说只能存取8个寄存器,所以需要结合读写信号和线路控制寄存器LCR的最高位DLAB位来协同寻址。
DLAB | A2A1A0 | 被访问的寄存器 |
0 | 000 | 接收数据寄存器RBR(读) 发送保持寄存器THR(写) |
0 | 001 | 中断允许寄存器IER |
1 | 000 | 波特率除数寄存器(低字节) |
1 | 001 | 波特率除数寄存器(高字节) |
X | 010 | 中断识别寄存器IIR(只读) FIFO控制器FCR(只写) |
X | 011 | 线路控制寄存器LCR |
X | 100 | MODEM控制寄存器MCR |
X | 101 | 线路状态寄存器LSR |
X | 110 | MODEM状态寄存器MSR |
X | 111 | 暂存 |
在Linux中,UART属于TTY设备三大类别中的串口,所以UART驱动完全遵循TTY驱动的架构。但事实上,Linux内核在TTY驱动框架下又封装一层UART驱动。内核中用uart_driver来表示一个UART驱动。driver_name和dev_name分别表示驱动的名字和该驱动所管理的设备名字,在系统/dev目录下以ttyS开头的就是串口的名字。major和minor为主次设备号,nr表示该驱动支持的设备数目。tty_dirver就是UART驱动中用来对接TTY驱动框架的接口,uart_state指向一个串口设备数组。
struct uart_driver { struct module *owner; const char *driver_name; const char *dev_name; int major; int minor; int nr; struct console *cons; /* * 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; };
uart_state用来表示一个具体的串口设备,tty_port是从tty核心层的角度来表示一个串口设备,其中就封装了tty核心层要用到的数据结构tty_struct。uart_port用来表示设备的具体信息,包括设备的控制和状态信息。xmit是数据发送缓冲区,用来接收上层用户传递过来的数据。在uart_port包含了两个重要的数据结构,分别是uart_icount和uart_ops。
struct uart_state { struct tty_port port; int pm_state; struct circ_buf xmit; struct tasklet_struct tlet; struct uart_port *uart_port; };
uart_icount为串口信息计数器,包含了发送字符计数、接受字符计数等,在串口的发送中断处理函数和接收中断处理函数中,需要管理这些计数信息。uart_ops 跟tty_operations类似,是实现对UART操作的函数接口集合。
struct uart_icount { __u32 cts; __u32 dsr; __u32 rng; __u32 dcd; __u32 rx; __u32 tx; __u32 frame; __u32 overrun; __u32 parity; __u32 brk; __u32 buf_overrun; };
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 (*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 *); 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 void (*poll_put_char)(struct uart_port *, unsigned char); int (*poll_get_char)(struct uart_port *); #endif };