嵌入式Linux串口与RS-485驱动开发实战
2026/7/25 22:17:26 网站建设 项目流程

1. 嵌入式Linux串口与485驱动实验概述

在嵌入式Linux开发中,串口通信是最基础也最关键的硬件接口之一。无论是调试信息输出、设备间通信还是工业控制,UART串口都扮演着重要角色。而RS-485作为串口的衍生标准,凭借其差分信号传输、抗干扰能力强、支持多点通信等特性,在工业自动化领域广泛应用。

本次实验将完整实现嵌入式Linux平台下的串口驱动开发,并在此基础上扩展RS-485通信功能。不同于简单的应用层操作,我们将深入Linux内核驱动框架,从底层理解UART设备的工作机制。实验环境基于常见的ARM嵌入式平台(如STM32MP157),但所述原理和方法适用于大多数Linux嵌入式系统。

2. Linux UART驱动框架解析

2.1 内核UART子系统架构

Linux内核中的UART驱动采用分层设计,主要包含以下几个核心组件:

  1. TTY层:提供统一的字符设备接口(/dev/ttyS*),处理线路规程(line discipline)
  2. UART核心层:实现uart_driver、uart_port等核心数据结构
  3. 硬件驱动层:与具体UART控制器交互的platform driver

注册一个UART驱动的典型流程如下:

static int __init serial_init(void) { ret = uart_register_driver(&serial_drv); // 注册UART驱动 ret = uart_add_one_port(&serial_drv, &serial_port); // 添加端口 ... }

2.2 关键数据结构解析

uart_driver结构体包含驱动名称、设备节点号、终端类型等元信息:

static struct uart_driver serial_drv = { .owner = THIS_MODULE, .driver_name = "ttyS", .dev_name = "ttyS", .major = TTY_MAJOR, .minor = 64, // 次设备号起始值 .nr = UART_NR, // 支持的端口数 .cons = SERIAL_CONSOLE, };

uart_port结构体描述具体的硬件端口:

struct uart_port { unsigned long iobase; // IO基地址 unsigned char __iomem *membase; // 内存映射地址 unsigned int irq; // 中断号 unsigned int uartclk; // 时钟频率 ... struct device *dev; // 关联的设备 };

3. 串口驱动实验实现

3.1 硬件准备与引脚复用

以STM32MP157为例,使用USART3作为实验串口,需要配置以下引脚:

  • TX: PB10 (AF7)
  • RX: PB12 (AF8)
  • RTS/CTS: 可选流控引脚

在设备树中配置:

&usart3 { pinctrl-names = "default"; pinctrl-0 = <&usart3_pins>; status = "okay"; };

引脚复用配置:

usart3_pins: usart3-0 { pins { pinmux = <STM32_PINMUX('B', 10, AF7)>, /* USART3_TX */ <STM32_PINMUX('B', 12, AF8)>; /* USART3_RX */ bias-disable; }; };

3.2 驱动加载与测试

加载驱动后,系统会生成对应的设备节点:

# 查看生成的tty设备 ls /dev/ttyS* # 设置波特率等参数 stty -F /dev/ttyS2 115200 cs8 -parenb -cstopb # 测试收发 echo "test" > /dev/ttyS2 cat /dev/ttyS2

注意:在嵌入式系统中,默认的串口终端可能会占用某个UART端口,需要先禁用getty服务:

systemctl stop serial-getty@ttyS2.service systemctl disable serial-getty@ttyS2.service

4. RS-485驱动扩展实现

4.1 485通信硬件设计

RS-485与普通串口的主要区别在于:

  1. 采用差分信号(A/B线)
  2. 需要方向控制(DE/RE)
  3. 典型电路包含隔离保护(如ADM2483)

硬件连接示意图:

+---------------+ | MCU | | | | UART_TX ----->|--> 485芯片 DI | UART_RX <-----|<-- 485芯片 RO | GPIO ----->|--> 485芯片 DE/RE +---------------+

4.2 内核驱动修改

需要在原有串口驱动基础上增加485控制:

  1. 在设备树中添加485控制GPIO:
&usart3 { rs485-rts-delay = <0 0>; linux,rs485-enabled-at-boot-time; rs485-rts-active-high; rts-gpios = <&gpioc 12 GPIO_ACTIVE_HIGH>; };
  1. 驱动代码中处理发送控制:
static void serial_rs485_start_tx(struct uart_port *port) { gpiod_set_value(port->rs485_gpio, 1); udelay(port->rs485_delay_rts_before_send); } static void serial_rs485_stop_tx(struct uart_port *port) { udelay(port->rs485_delay_rts_after_send); gpiod_set_value(port->rs485_gpio, 0); }

4.3 应用层测试

使用标准串口API,增加485方向控制:

int fd = open("/dev/ttyS2", O_RDWR); // 获取当前485参数 struct serial_rs485 rs485conf; ioctl(fd, TIOCGRS485, &rs485conf); // 设置485参数 rs485conf.flags |= SER_RS485_ENABLED; rs485conf.delay_rts_before_send = 1; rs485conf.delay_rts_after_send = 1; ioctl(fd, TIOCSRS485, &rs485conf); // 正常读写操作 write(fd, buf, len); read(fd, buf, len);

5. 常见问题与调试技巧

5.1 串口通信不稳定

可能原因及解决方案:

  1. 波特率误差过大:检查时钟源精度,确保波特率误差<2%
    # 查看当前波特率设置 stty -F /dev/ttyS2
  2. 缓冲区溢出:调整内核FIFO大小
    port->fifosize = 64; // 在驱动中设置
  3. 电气干扰:检查接地,必要时增加磁珠或TVS管

5.2 485通信异常排查

典型故障现象及处理:

  1. 只能发不能收
    • 检查RE引脚电平(接收时应使能)
    • 测量AB线差分电压(应>200mV)
  2. 长距离通信失败
    • 增加终端电阻(120Ω)
    • 检查线缆质量(推荐双绞屏蔽线)
  3. 多设备冲突
    • 确保总线只有一个发送器
    • 增加总线仲裁机制

5.3 内核调试技巧

  1. 打印寄存器值:
printk("LCR: %02x\n", serial_in(port, UART_LCR));
  1. 使用示波器抓取波形,对比TX与485输出
  2. 动态调整打印等级:
echo 8 > /proc/sys/kernel/printk

6. 性能优化与高级功能

6.1 DMA传输优化

对于高速通信(>1Mbps),建议启用DMA:

static struct uart_ops serial_ops = { .startup = serial_startup_dma, .shutdown = serial_shutdown_dma, ... }; // 在probe函数中分配DMA通道 port->dmatx = dma_request_chan(&pdev->dev, "tx"); port->dmarx = dma_request_chan(&pdev->dev, "rx");

6.2 硬件流控实现

完整硬件流控需要CTS/RTS引脚:

&usart3 { pinctrl-0 = <&usart3_pins &usart3_rtscts_pins>; uart-has-rtscts; };

应用层启用:

struct termios options; tcgetattr(fd, &options); options.c_cflag |= CRTSCTS; tcsetattr(fd, TCSANOW, &options);

6.3 自定义协议栈

在串口基础上实现协议封装示例:

// 帧结构:头(0xAA) + 长度 + 数据 + CRC struct serial_frame { uint8_t header; uint8_t len; uint8_t data[256]; uint16_t crc; }; // 在驱动中解析 static void parse_frame(struct uart_port *port, uint8_t ch) { static uint8_t buf[260], pos = 0; buf[pos++] = ch; if(pos >= sizeof(buf)) { if(check_crc(buf)) { // 完整帧处理 } pos = 0; } }

在实际项目中,串口驱动的稳定性和可靠性直接影响整个系统的表现。通过内核提供的丰富调试接口(如sysfs、debugfs)和硬件诊断工具(逻辑分析仪),可以快速定位问题根源。对于工业级应用,建议增加看门狗机制和心跳包检测,确保通信链路长期稳定工作。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询