共计 22483 个字符,预计需要花费 57 分钟才能阅读完成。
这篇文章主要讲解了“Linux 的 tty 架构及 UART 驱动知识点有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着丸趣 TV 小编的思路慢慢深入,一起来研究和学习“Linux 的 tty 架构及 UART 驱动知识点有哪些”吧!
一、模块硬件学习
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(继续 / 停止)。
硬件流控制常用的有 RTS/CTS 流控制和 DTR/DSR 流控制两种。
DTR ndash; 数据终端就绪 (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 信号无效,主机接收中止。
由于电缆的限制,在普通的控制通讯中一般不采用硬件流控制,而是使用软件流控制。
一般通过 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 概念
串口终端是使用计算机串口连接的终端设备。Linux 把每个串行端口都看做是一个字符设备。这些串行端口所对应的设备名称是 /dev/ttySAC*;
在 Linux 系统中,计算机的输出设备通常被称为控制台终端,这里特指 printk 信息输出到设备。/dev/console 是一个虚拟的设备,它需要映射到真正的 tty 上,比如通过内核启动参数“console=ttySCA0”就把 console 映射到了串口 0
当用户登录时,使用的是虚拟终端。使用 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 架构图
如图 3.2 所示,tty 设备发送数据的流程为:tty 核心从一个用户获取将要发送给一个 tty 设备的数据,tty 核心将数据传递给 tty 线路规程驱动,接着数据被传到 tty 驱动,tty 驱动将数据转换为可以发给硬件的格式。
接收数据的流程为: 从 tty 硬件接收到的数据向上交给 tty 驱动,接着进入 tty 线路规程驱动,再进入 tty 核心,在这里它被一个用户获取。
图 3.2 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. 注册流程
此接口在 uart driver 中调用,用来注册 uart_driver 到 kernel 中,调用阶段在 uart driver 的初始阶段,例如:module_init(), uart_driver 的注册流程图
图 3.3uart 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, 这一部分是内核实现好的不需要修改
此接口用于注册一个 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 流程图
2.4. 数据收发流程
2.4.1. 打开设备(open 操作)
open 设备的大体流程如图 3 - 5 所示:
图 3 -5 open 设备流程
2.4.2. 数据发送流程(write 操作)
发送数据大体流程如图 3 - 6 所示:
图 3 -6 发送数据流程
2.4.3. 数据接收流程(read 操作)
接收数据的大体流程如图 3 - 7 所示:
图 3 - 7 数据接收流程
2.4.4. 关闭设备(close 操作)
close 设备的大体流程如图 3 - 8 所示:
图 3 -8 close 设备流程
2.4.5. 注销流程
此接口用于从 uart driver 上注销一个 uart port,该接口在 uart driver 中的 remove 函数中调用。uart 移除 port 的流程如图 3 - 9 所示:
图 3.9 uart 移除 port 流程图
此接口在 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.
模式 12-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 模式。
模式 21-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 为低电平)
模式 3rs485-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
模式 4rs485-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 为高电平发送使能。当发送数据 lsquo;0 rsquo; 时,由于 DI 口连接地,此时数据 lsquo;0 rsquo; 就会传输到 AB 口 A-B 0, 传输 lsquo;0 rsquo;,完成了低电平的传输。当发送 lsquo;1 rsquo; 时,此时三极管导通,按理说 RO 使能,此时由于还处在发送数据中,这种状态下 485 处于高阻态,此时的状态通过 A 上拉 B 下拉电阻决定,此时 A -B 0 传输 lsquo;1 rsquo;,完成高电平的传输。
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. 串口控制函数
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 的 tty 架构及 UART 驱动知识点有哪些”的内容了,经过本文的学习后,相信大家对 Linux 的 tty 架构及 UART 驱动知识点有哪些这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是丸趣 TV,丸趣 TV 小编将为大家推送更多相关知识点的文章,欢迎关注!