1. RS485驱动开发概述
作为一名嵌入式Linux驱动开发者,我最近在全志T113平台上实现了RS485通信功能。RS485作为一种工业级串行通信标准,因其差分信号传输、抗干扰能力强、支持多点通信等特点,在工业自动化、智能家居等领域广泛应用。本文将详细解析Linux内核中RS485驱动的核心实现机制,特别是struct uart_port结构体的关键作用。
在Linux内核中,RS485功能是通过串口子系统实现的,核心在于对UART硬件的精确控制和时序管理。全志T113芯片内置多个UART控制器,通过适当的硬件连接和软件配置即可支持RS485通信。与普通UART驱动相比,RS485驱动需要额外处理方向控制信号(DE/RE)的切换时机,这对通信稳定性至关重要。
2. Linux内核中的RS485支持架构
2.1 核心数据结构关系
Linux内核通过分层设计实现RS485支持,主要涉及三个关键数据结构:
- struct serial_rs485:定义RS485通信参数
- struct uart_port:抽象UART硬件端口
- struct uart_ops:定义UART操作函数集
它们的关系如下图所示(文字描述):
- serial_rs485存储RS485配置参数
- uart_port包含serial_rs485实例
- uart_ops中的rs485_config函数负责应用配置
2.2 struct uart_port深度解析
struct uart_port是Linux串口子系统的核心结构体,定义在include/linux/serial_core.h中。它完整描述了一个UART硬件端口的所有属性和操作接口。
2.2.1 硬件资源管理成员
c复制unsigned long iobase; // IO端口基地址
unsigned char __iomem *membase; // 内存映射IO地址
unsigned int irq; // 中断号
unsigned long irqflags; // 中断触发方式
这些成员管理UART的硬件资源访问。在全志T113平台上,我们通常使用内存映射方式(membase)访问UART寄存器。
2.2.2 通信参数控制成员
c复制unsigned int uartclk; // UART时钟频率
unsigned int fifosize; // FIFO缓冲区大小
unsigned int timeout; // 字符超时时间
unsigned int (*get_divisor)(struct uart_port *,
unsigned int baud,
unsigned int *frac);
这些成员控制通信的时序和波特率。例如,uartclk用于波特率计算,fifosize决定DMA缓冲区大小。
2.2.3 RS485相关成员
c复制struct serial_rs485 rs485; // RS485配置参数
struct gpio_desc *rs485_term_gpio; // 终端电阻控制
int (*rs485_config)(struct uart_port *,
struct serial_rs485 *rs485);
rs485成员存储配置参数,rs485_config函数指针指向具体的配置实现。全志T113需要实现这个函数来支持RS485模式。
3. RS485驱动实现关键点
3.1 方向控制信号管理
RS485通信最大的特点是需要控制方向信号(DE/RE)。在全志T113上,通常使用一个GPIO引脚来控制方向:
- 发送数据前:拉高GPIO(使能发送器)
- 发送完成后:拉低GPIO(使能接收器)
内核提供了两种实现方式:
自动方向控制模式:
c复制rs485.flags |= SER_RS485_RTS_ON_SEND; // 发送时自动拉高RTS
rs485.flags |= SER_RS485_RTS_AFTER_SEND; // 发送后自动拉低RTS
手动控制模式:
c复制// 在发送中断处理中手动控制GPIO
static void t113_uart_start_tx(struct uart_port *port)
{
gpiod_set_value(port->rs485_term_gpio, 1);
// ...启动发送...
}
3.2 时序参数配置
RS485对时序有严格要求,特别是方向切换的延时:
c复制rs485.delay_rts_before_send = 1; // 发送前延时(ms)
rs485.delay_rts_after_send = 1; // 发送后延时(ms)
这些参数需要根据硬件特性调整。过短的延时会导致数据丢失,过长的延时会影响通信效率。
3.3 终端电阻控制
长距离通信时需要启用终端电阻匹配阻抗:
c复制port->rs485_term_gpio = devm_gpiod_get_optional(dev, "rs485-term",
GPIOD_OUT_LOW);
if (port->rs485_term_gpio)
gpiod_set_value(port->rs485_term_gpio, 1); // 启用终端电阻
4. 全志T113平台适配
4.1 设备树配置
全志T113的设备树需要正确配置UART和GPIO:
dts复制uart3: serial@05000c00 {
compatible = "allwinner,t113-uart";
reg = <0x05000c00 0x400>;
interrupts = <GIC_SPI 4 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&ccu CLK_BUS_UART3>;
resets = <&ccu RST_BUS_UART3>;
pinctrl-names = "default";
pinctrl-0 = <&uart3_pins>, <&uart3_rts_pins>;
rs485-gpios = <&pio 6 12 GPIO_ACTIVE_HIGH>; /* PG12 */
rs485-term-gpios = <&pio 6 11 GPIO_ACTIVE_HIGH>; /* PG11 */
linux,rs485-enabled-at-boot-time;
};
4.2 驱动实现要点
全志T113的UART驱动需要实现rs485_config函数:
c复制static int t113_rs485_config(struct uart_port *port,
struct serial_rs485 *rs485)
{
struct t113_uart_port *tport = to_t113_port(port);
/* 保存配置 */
port->rs485 = *rs485;
/* 配置硬件 */
if (rs485->flags & SER_RS485_ENABLED) {
/* 启用RS485模式 */
writel(RS485_EN, port->membase + UART_MCR_REG);
/* 设置方向控制极性 */
if (rs485->flags & SER_RS485_RTS_ON_SEND)
writel(RTS_POLARITY_HIGH, port->membase + UART_MSR_REG);
else
writel(RTS_POLARITY_LOW, port->membase + UART_MSR_REG);
} else {
/* 禁用RS485模式 */
writel(~RS485_EN, port->membase + UART_MCR_REG);
}
return 0;
}
5. 调试与问题排查
5.1 常见问题及解决方案
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 数据发送不完整 | 方向切换过早 | 增加delay_rts_after_send |
| 接收数据错误 | 方向切换过晚 | 增加delay_rts_before_send |
| 通信距离短 | 未启用终端电阻 | 配置rs485-term-gpios |
| 通信不稳定 | 波特率不匹配 | 检查uartclk和波特率分频 |
5.2 调试技巧
- 示波器观察:同时捕捉TX和DE信号,确保时序正确
- 内核日志:启用UART驱动调试选项
c复制echo 8 > /proc/sys/kernel/printk
- 用户空间测试:
bash复制stty -F /dev/ttyS2 115200 rs485
cat /dev/ttyS2 &
echo "test" > /dev/ttyS2
6. 性能优化建议
- DMA传输:对于高速通信,启用UART DMA
c复制port->fifosize = 64; // 启用FIFO
- 中断优化:合并小数据包,减少中断次数
- 电源管理:空闲时降低UART时钟频率
在全志T113平台上实现RS485驱动时,我发现硬件手册中有些时序参数描述不够明确,通过实际测试确定了最优的delay_rts参数组合。建议开发者在不同波特率下都进行充分的边界测试,特别是通信距离接近理论最大值时。