往期内容
本专栏往期内容:Uart子系统
- UART串口硬件介绍
- 深入理解TTY体系:设备节点与驱动程序框架详解
- Linux串口应用编程:从UART到GPS模块及字符设备驱动
- 解UART 子系统:Linux Kernel 4.9.88 中的核心结构体与设计详解
- IMX 平台UART驱动情景分析:注册篇
- IMX 平台UART驱动情景分析:open篇
- IMX 平台UART驱动情景分析:read篇–从硬件驱动到行规程的全链路剖析
- IMX 平台UART驱动情景分析:write篇–从 TTY 层到硬件驱动的写操作流程解析
- 深入浅出UART驱动开发与调试:从基础调试到虚拟驱动实现
10.Linux 内核日志系统—printk的机制与应用
interrupt子系统专栏:
- 专栏地址:interrupt子系统
- Linux 链式与层级中断控制器讲解:原理与驱动开发
– 末片,有专栏内容观看顺序pinctrl和gpio子系统专栏:
专栏地址:pinctrl和gpio子系统
编写虚拟的GPIO控制器的驱动程序:和pinctrl的交互使用
– 末片,有专栏内容观看顺序
input子系统专栏:
- 专栏地址:input子系统
- input角度:I2C触摸屏驱动分析和编写一个简单的I2C驱动程序
– 末片,有专栏内容观看顺序I2C子系统专栏:
- 专栏地址:IIC子系统
- 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
– 末篇,有专栏内容观看顺序总线和设备树专栏:
- 专栏地址:总线和设备树
- 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
– 末篇,有专栏内容观看顺序
目录
- 往期内容
- 前言
- 1.early_printk和earlycon
- 1.1 内核信息的早期打印
- 1.2 early_printk
- 1.3 earlycon
- 2.RS485简单讲解
- 2.1 RS485线路图
- 2.2 RS485应用编程
- 2.2.1 标准用法
- 2.2.2 自己控制引脚
- 2.3 驱动速览
- 2.3.1 使用GPIO的RS485驱动
前言
本文围绕Linux内核早期打印机制和RS485通信技术展开,详细解析了early_printk和earlycon的实现与使用场景,以及RS485的基础原理和应用实践。在早期打印部分,阐明了early_printk和earlycon的区别:前者通过手动实现打印函数实现早期日志输出,后者则结合设备树实现灵活硬件映射,适用于现代系统。在RS485部分,介绍了其差分信号传输、抗干扰能力强等技术特点,并结合SP3485芯片说明了发送与接收的硬件原理,同时提供了Linux环境下的标准驱动编程示例,涵盖RTS控制模式与GPIO实现方式。
1.early_printk和earlycon
linux 4.9.88
- arch\arm\kernel\early_printk.c📎early_printk.c
- drivers\tty\serial\earlycon.c📎earlycon.c
1.1 内核信息的早期打印
console驱动,它属于uart_driver的一部分。
注册了uart_driver、并调用uart_add_one_port后,它里面才注册console,在这之后才能使用printk。
如果想更早地使用printk函数,比如在安装UART驱动之前就使用printk,这时就需要自己去注册console。
更早地、单独地注册console,有两种方法:
- early_printk:自己实现write函数,不涉及设备树,简单明了
- earlycon:通过设备树传入硬件信息,跟内核中驱动程序匹配
earlycon是新的、推荐的方法,在内核已经有驱动的前提下,通过设备树或cmdline指定寄存器地址即可。
1.2 early_printk
源码为:arch\arm\kernel\early_printk.c
,要使用它,必须实现这几点:
- 配置内核,选择:CONFIG_EARLY_PRINTK
- 内核中实现:printch函数
- cmdline中添加:earlyprintk
1.3 earlycon
arch\arm\kernel\early_printk.c📎early_printk.c
drivers\tty\serial\earlycon.c📎earlycon.c
提供硬件信息的两种方法:
earlycon就是early console的意思,实现的功能跟earlyprintk是一样的,只是更灵活。
我们知道,对于console,最主要的是里面的write函数:它不使用中断,相对简单。
所以很多串口console的write函数,只要确定寄存器的地址就很容易实现了。
假设芯片的串口驱动程序,已经在内核里实现了,我们需要根据板子的配置给它提供寄存器地址。
怎么提供?
- 设备树
- cmdline参数
early_param("earlycon", param_setup_earlycon);
//如上图所示,当在cmdline参数中有erlycon这个属性的时候,就会导致param_setup_earlycon被调用/* early_param wrapper for setup_earlycon() */
static int __init param_setup_earlycon(char *buf)
{int err;/** Just 'earlycon' is a valid param for devicetree earlycons;* don't generate a warning from parse_early_params() in that case*/if (!buf || !buf[0]) {if (IS_ENABLED(CONFIG_ACPI_SPCR_TABLE)) {earlycon_init_is_deferred = true;return 0;} else {return early_init_dt_scan_chosen_stdout(); //1. earlycon不带参数,参数采用设备节点表示}}err = setup_earlycon(buf); //2. earlycon = XX,带参数if (err == -ENOENT || err == -EALREADY)return 0;return err;
}
-
如果cmdline中只有"earlycon",不带更多参数:对应
early_init_dt_scan_chosen_stdout
函数-
使用"/chosen"下的"stdout-path"找到节点
-
或使用"/chosen"下的"linux,stdout-path"找到节点
-
节点里有"compatible"和"reg"属性
- 根据"compatible"找到
OF_EARLYCON_DECLARE
,里面有setup函数,它会提供write函数 - write函数写什么寄存器?在"reg"属性里确定
- 根据"compatible"找到
-
-
如果cmdline中"earlycon=xxx",带有更多参数:对应
setup_earlycon
函数- earlycon=xxx格式为:
<name>,io|mmio|mmio32|mmio32be,<addr>,<options>
<name>,0x<addr>,<options>
<name>,<options>
<name>
-
- 根据"name"找到
OF_EARLYCON_DECLARE
,里面有setup函数,它会提供write函数 - write函数写什么寄存器?在"addr"参数里确定
- 根据"name"找到
2.RS485简单讲解
Documentation\serial\serial-rs485.txt📎serial-rs485.txt
2.1 RS485线路图
RS485通信----基本原理+电路图-CSDN博客
RS485 是美国电子工业协会(Electronic Industries Association,EIA)于1983年发布的串行通信接口标准,经通讯工业协会(TIA)修订后命名为 TIA/EIA-485-A。
RS485 是一种工业控制环境中常用的通讯协议,其中RS 是 Recommended Standard 的缩写。
RS485 是 半双工异步 串行通信。
特点
- 支持多节点:一般最大支持 32 个节点。
- 传输距离远:最远通讯距离可达1200米。
- 抗干扰能力强:差分信号传输。
- 连接简单:只需要两根信号线(A+和B-)就可以进行正常的通信。
差分信号传输
RS485 通信采用差分信号传输,通常情况下只需要两根信号线就可以进行正常的通信。
在差分信号中,逻辑0和逻辑1是用两根信号线(A+和B-)的电压差来表示。
- 逻辑 1:两根信号线(A+和B-)的电压差在 +2V~+6V 之间。
- 逻辑 0:两根信号线(A+和B-)的电压差在 -2V~-6V 之间。
SP3485 芯片****是一款非常经典的+3.3V低功耗半双工RS485收发器
RS485使用A、B两条差分线传输数据:
-
要发送数据时
- 把SP3485的DE(Driver output Enable)引脚设置为高
- 通过TxD引脚发送
1
给SP3485,SP3485会驱动A、B引脚电压差为+2~+6V
- 通过TxD引脚发送
0
给SP3485,SP3485会驱动A、B引脚电压差为-6~-2V
- SP3485自动把TxD信号转换为AB差分信号
- 对于软件来说,通过RS485发送数据时,跟一般的串口没区别,只是多了DE引脚的设置
-
要读取数据时
- 把SP3485的nRE(Receiver Output Enable)引脚设置为低
- SP3485会根据AB引脚的电压差驱动RO为1或0
- RO的数据传入UART的RxD引脚
- 对于软件来说,通过RS485读取数据时,跟一般的串口没区别,只是多了nRE引脚的设置
-
nRE和DE使用同一个引脚时,可以简化成这样:
-
- 发送:RTS输出高 — 导致DE为高,使能发送,nRE为低,接收禁止
- 接收:RTS输出低 ---- 导致nRE为低,使能接收,DE为低,发送禁止
引脚 | 名称 | 功能 |
---|---|---|
1 | RO | 接收器输出----接RX |
2 | RE | 接收器输出使能(低电平-接收使能) |
3 | DE | 驱动器输出使能(高电平-发送使能) |
4 | DI | 驱动器输入----接TX |
5 | GND | 接地 |
6 | A | 驱动器输出/接收器输入(同相) |
7 | B | 驱动器输出/接收器输入(反相) |
8 | VCC | 芯片供电+3.3V |
2.2 RS485应用编程
2.2.1 标准用法
在Linux的串口驱动中,它已经支持RS485,可以使用RTS引脚控制RS485芯片的DE引脚,分两种情况
- 有些UART驱动:使用UART的RTS引脚
- 有些UART驱动:使用GPIO作为RTS引脚,可以通过设备树指定这个GPIO
在使用RS485发送数据前,把RTS设置为高电平就可以。
通过serial_rs485
结构体控制RTS,示例代码如下:
#include <linux/serial.h>/* 用到这2个ioctl: TIOCGRS485, TIOCSRS485 */
#include <sys/ioctl.h>struct serial_rs485 rs485conf;/* 打开串口设备 */
int fd = open ("/dev/mydevice", O_RDWR);
if (fd < 0) {/* 失败则返回 */return -1;
}/* 读取rs485conf */
if (ioctl (fd, TIOCGRS485, &rs485conf) < 0) {/* 处理错误 */
}/* 使能RS485模式 */
rs485conf.flags |= SER_RS485_ENABLED;/* 当发送数据时, RTS为1 */
rs485conf.flags |= SER_RS485_RTS_ON_SEND;/* 或者: 当发送数据时, RTS为0 */
rs485conf.flags &= ~(SER_RS485_RTS_ON_SEND);/* 当发送完数据后, RTS为1 */
rs485conf.flags |= SER_RS485_RTS_AFTER_SEND;/* 或者: 当发送完数据后, RTS为0 */
rs485conf.flags &= ~(SER_RS485_RTS_AFTER_SEND);/* 还可以设置: * 发送数据之前先设置RTS信号, 等待一会再发送数据* 等多久? delay_rts_before_send(单位ms)*/
rs485conf.delay_rts_before_send = ...;/* 还可以设置: * 发送数据之后, 等待一会再清除RTS信号* 等多久? delay_rts_after_send(单位ms)*/
rs485conf.delay_rts_after_send = ...;/* 如果想在发送RS485数据的同时也接收数据, 还可以这样设置 */
rs485conf.flags |= SER_RS485_RX_DURING_TX;if (ioctl (fd, TIOCSRS485, &rs485conf) < 0) {/* 处理错误 */
}/* 使用read()和write()就可以读、写数据了 *//* 关闭设备 */
if (close (fd) < 0) {/* 处理错误 */
}
2.2.2 自己控制引脚
发送之前,自己设置GPIO控制DE引脚。
2.3 驱动速览
源码为:Linux-4.9.88\drivers\tty\serial\imx.c
:📎imx.c
2.3.1 使用GPIO的RS485驱动
这里只是举个例子。
源码为:Linux-4.9.88\drivers\tty\serial\omap-serial.c
📎omap-serial.c
1. 从设备树获得用作RTS的GPIO
2.发送数据前设置GPIO