在介绍终端控制体系之前,要先普及 一下数据通信的基本方式。
数据通信
数据通信的基本方式可分为并行通信与串行通信两种:
- 并行通信:利用多条数据线将数据的各位同时传送。它的特点是传输速度快,适用于短距离通信。
- 串行通信:利用一条数据线将数据一位位地顺序传送。特点就是通信线路简单,利用简单的线缆就能实现通信,低成本,适用于远距离通信。
异步通信
异步通信是以字符为传输单位,通信中两个字符间的时间间隔是不固定的,然而同一个字符中的两个相邻位之间的时间间隔是固定的。
通信协议:是指双方约定的一些规则。在使用异步串口发送一个字符的信息时,对数据格式有如下约定:规定有空闲位、起始位、资料位、奇偶校验位、停止位。
波特率:是衡量数据传送速率的指针。表示每秒钟传送的二进制位数。
传送方式
终端概述
在Linux中,TTY(终端)是一类字符设备的统称,包括了3种类型:控制台,串口和伪终端。
控制台
供内核使用的终端为控制台。控制台在Linux启动时,通过命令 console=… 指定,如果没有指定控制台,系统把第一个注册的终端(tty)作为控制台。
- 控制台是一个虚拟的终端,它必须映射到真正的终端上。
- 控制台可以简单的理解为 printk 输出的地方。
- 控制台是个只输出的设备,功能很简单,只能在内核中访问。
伪终端
终端体系
在Linux中,TTY体系分为:TTY核心,TTY线路规程,TTY驱动三部分。TTY核心从用户获取要发送给TTY设备的数据,然后把数据传递给TTY线路规程,它对数据进行处理后,负责把数据传递到TTY驱动程序,TTY驱动程序负责格式化数据,并通过硬件发送出去。
终端体系—串口
数据流
读操作:
TTY驱动从硬件收到数据后,负责把数据传递到TTY核心,TTY核心将从TTY驱动收到的数据缓存到一个 tty_flip_buffer 类型的结构中。该结构包含两个数据数组。从tty设备接受到的数据被存储于第一个数组,当这个数组满,等待数据的用户将被通知。当用户从这个数组读数据时,任何从TTY驱动新来的数据被存储在第二个数组。当第二个数组存满后,数据再次提交给用户,并且驱动又开始填充第一个数组,以此交替。
串口驱动描述
Linux内核使用 uart_driver 描述串口驱动,它包含串口设备的驱动名、设备名、设备号等信息。
struct uart_driver
{
struct module *owner;
const char *driver_name; //驱动名
const char *dev_name; //设备名
int major; //主设备号
int minor; //起始次设备号
int nr; //设备数
struct console;
struct uart_state *state;
struct tty_driver *tty_driver;
};
串口驱动注册
Linux 为串口驱动注册提供了如下接口:
int uart_register_driver(struct uart_driver *drv);
UART端口描述
uart_port 用于描述一个 UART 端口(一个串口)的地址、FIFI大小、端口等信息;
struct uart_port
{
spinlock_t lock; //端口锁
unsigned int iobase; //IO端口基地址
unsigned char __iomem *membase; //IO内存基地址
unsigned int irq; //中断号
unsigned char fifosize; //传输fifo大小
const struct uart_ops *ops;
......
};
操作串口
uart_ops 定义了针对串口的一系列操作,包括发送、接受及线路设置等。
struct uart_ops
{
unsigned int(*tx_empty) (struct uart_port*);
void (*set_mctrl) (struct uart_port *,unsigned int mctrl);
void (*stop_tx) (struct uart_port*); //停止发送
void (*start_tx) (struct uart_port*); //开始发送
void (*send_xchar) (struct uart_port*,char ch); //发送xchar
void (*stop_rx) (struct uart_port*); //停止接收
......
};
添加端口
串口核心层提供如下函数来添加1个端口:
int uart_add_one_port(struct uart_driver *drv,struct uart_port *port);
串口驱动程序设计
操作流程
- 定义一个 uart_driver 的变量,并初始化;
- 使用 uart_register_driver 来注册这个驱动;
- 初始化 uart_port 和 ops 函数表;
- 调用 uart_add_one_port() 添加初始化好的 uart_port。