1. RS485驱动开发概述
在嵌入式Linux系统中,RS485通信的实现需要深入理解内核串口子系统的架构。作为工业通信的常用标准,RS485与普通UART最大的区别在于其半双工特性和方向控制机制。在全志T113这类嵌入式平台上,我们需要通过修改串口驱动来支持RS485特有的自动方向切换功能。
内核中的struct uart_ops是串口驱动的核心操作集,它定义了硬件相关的底层操作接口。对于RS485驱动开发而言,我们需要特别关注其中的几个关键函数:
set_mctrl/get_mctrl:用于控制RS485方向引脚set_termios:配置RS485特有的参数如延迟时间ioctl:实现RS485特有的控制命令
2. uart_ops结构深度解析
2.1 基础通信控制函数
c复制unsigned int (*tx_empty)(struct uart_port *);
这个回调函数用于检查发送缓冲区是否为空。在RS485通信中,这个函数尤为重要,因为我们需要确保在切换方向前所有数据都已发送完毕。典型的实现会读取UART的状态寄存器,检查TX FIFO是否为空以及移位寄存器是否空闲。
c复制void (*set_mctrl)(struct uart_port *, unsigned int mctrl);
void (*get_mctrl)(struct uart_port *);
这对函数用于控制调制解调器信号线。对于RS485来说,我们通常会将其中一个调制解调器控制线(如RTS)重新用作方向控制引脚:
c复制static void serial8250_set_mctrl(struct uart_port *port, unsigned int mctrl)
{
struct uart_8250_port *up = up_to_u8250p(port);
unsigned char mcr = serial8250_in_MCR(up);
/* RS485方向控制 */
if (port->rs485.flags & SER_RS485_ENABLED) {
if (mctrl & TIOCM_RTS)
mcr |= UART_MCR_RTS;
else
mcr &= ~UART_MCR_RTS;
}
serial8250_out_MCR(up, mcr);
}
2.2 数据传输控制函数
c复制void (*start_tx)(struct uart_port *);
void (*stop_tx)(struct uart_port *);
在RS485模式下,start_tx需要额外处理方向控制。典型的实现如下:
c复制static void serial8250_start_tx(struct uart_port *port)
{
struct uart_8250_port *up = up_to_u8250p(port);
/* RS485方向切换为发送模式 */
if (port->rs485.flags & SER_RS485_ENABLED) {
port->ops->set_mctrl(port, port->mctrl | TIOCM_RTS);
udelay(port->rs485.delay_rts_before_send);
}
/* 标准UART发送启动逻辑 */
if (!(up->ier & UART_IER_THRI)) {
up->ier |= UART_IER_THRI;
serial_port_out(port, UART_IER, up->ier);
}
}
2.3 流控与缓冲管理
c复制void (*throttle)(struct uart_port *);
void (*unthrottle)(struct uart_port *);
这对函数用于流量控制,在RS485通信中同样重要:
c复制static void serial8250_throttle(struct uart_port *port)
{
/* 禁用接收中断 */
port->ops->stop_rx(port);
/* 如果是硬件流控模式,设置RTS为高 */
if (port->flags & UPF_HARD_FLOW) {
port->mctrl &= ~TIOCM_RTS;
port->ops->set_mctrl(port, port->mctrl);
}
}
3. RS485特有功能实现
3.1 方向控制时序管理
RS485通信最关键的在于方向切换时序。内核提供了rs485结构体来管理相关参数:
c复制struct serial_rs485 {
__u32 flags; /* RS485特性标志 */
__u32 delay_rts_before_send; /* 发送前延迟(us) */
__u32 delay_rts_after_send; /* 发送后延迟(us) */
__u32 padding[5]; /* 内存对齐填充 */
};
在驱动中,我们需要在set_termios和ioctl中处理这些参数:
c复制static int serial8250_ioctl(struct uart_port *port, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case TIOCSRS485:
if (copy_from_user(&port->rs485, (struct serial_rs485 __user *)arg,
sizeof(struct serial_rs485)))
return -EFAULT;
return 0;
case TIOCGRS485:
if (copy_to_user((struct serial_rs485 __user *)arg, &port->rs485,
sizeof(struct serial_rs485)))
return -EFAULT;
return 0;
}
return -ENOIOCTLCMD;
}
3.2 自动方向切换实现
全志T113平台上的自动方向切换可以通过DMA或中断机制实现:
c复制static void t113_rs485_start_tx(struct uart_port *port)
{
struct t113_port *tp = to_t113_port(port);
/* 启用发送器并设置方向 */
writel(readl(tp->regs + UART_CTRL) | UART_CTRL_TX_EN, tp->regs + UART_CTRL);
gpiod_set_value(tp->rs485_dir_gpio, 1);
/* 配置发送延迟定时器 */
if (port->rs485.delay_rts_before_send) {
hrtimer_start(&tp->start_tx_timer,
ns_to_ktime(port->rs485.delay_rts_before_send * 1000),
HRTIMER_MODE_REL);
}
}
4. 全志T113平台适配要点
4.1 硬件配置
在全志T113平台上,RS485通常通过UART控制器外加收发器芯片实现。设备树中需要正确配置:
dts复制&uart3 {
pinctrl-names = "default";
pinctrl-0 = <&uart3_pins>;
status = "okay";
rs485-rts-delay = <100 100>;
linux,rs485-enabled-at-boot-time;
rs485-rts-active-low;
};
4.2 驱动适配关键点
- GPIO方向控制:
c复制static int t113_rs485_init(struct uart_port *port)
{
struct t113_port *tp = to_t113_port(port);
/* 获取方向控制GPIO */
tp->rs485_dir_gpio = devm_gpiod_get(port->dev, "rs485-rx-en", GPIOD_OUT_LOW);
if (IS_ERR(tp->rs485_dir_gpio))
return PTR_ERR(tp->rs485_dir_gpio);
/* 初始化RS485参数 */
port->rs485.flags = SER_RS485_ENABLED | SER_RS485_RTS_AFTER_SEND;
port->rs485.delay_rts_before_send = 100;
port->rs485.delay_rts_after_send = 100;
return 0;
}
- 中断处理增强:
c复制static irqreturn_t t113_uart_interrupt(int irq, void *dev_id)
{
struct uart_port *port = dev_id;
struct t113_port *tp = to_t113_port(port);
unsigned int iir = readl(tp->regs + UART_IIR);
/* 处理发送完成中断 */
if ((iir & UART_IIR_ID) == UART_IIR_THRI) {
spin_lock(&port->lock);
if (port->rs485.flags & SER_RS485_ENABLED) {
/* 发送完成后切换方向 */
hrtimer_start(&tp->stop_tx_timer,
ns_to_ktime(port->rs485.delay_rts_after_send * 1000),
HRTIMER_MODE_REL);
}
spin_unlock(&port->lock);
}
return IRQ_HANDLED;
}
5. 调试与性能优化
5.1 常见问题排查
- 数据丢失问题:
- 检查方向切换时序是否合理
- 验证
delay_rts_before_send和delay_rts_after_send参数 - 使用示波器观察方向引脚和数据线的时序关系
- 通信不稳定:
- 检查终端电阻匹配(通常120Ω)
- 验证波特率精度
- 确保总线布线符合RS485规范(双绞线、单点接地等)
5.2 性能优化技巧
- DMA传输优化:
c复制static void t113_uart_dma_tx_complete(void *param)
{
struct uart_port *port = param;
struct t113_port *tp = to_t113_port(port);
/* DMA传输完成后处理方向切换 */
if (port->rs485.flags & SER_RS485_ENABLED) {
gpiod_set_value(tp->rs485_dir_gpio, 0);
}
uart_xmit_advance(port, tp->tx_dma_size);
}
- 低延迟优化:
- 减小内核缓冲区大小
- 使用高精度定时器管理方向切换
- 优化中断处理流程
在实际项目中,我曾遇到过因方向切换延迟不足导致的通信失败问题。通过调整delay_rts_after_send参数并添加示波器验证,最终发现需要根据收发器芯片的规格设置足够的切换时间。这个经验告诉我,RS485驱动开发不能只关注代码逻辑,还必须深入理解硬件特性。