1. Linux串行通信驱动开发概述

在嵌入式系统开发中,串行通信扮演着至关重要的角色。UART、I2C和SPI作为三种最常用的串行通信协议,各自具有独特的优势和应用场景。这些协议通过硬件接口和软件驱动的协同工作,实现了设备间的高效数据交换。

Linux内核为这三种通信协议提供了完整的驱动框架支持。UART驱动基于TTY子系统构建,I2C和SPI则分别有自己的核心框架。这种分层架构设计使得驱动开发更加模块化,上层应用可以通过统一的设备文件接口(如/dev/ttyS0、/dev/i2c/0等)访问硬件,而不需要关心底层实现细节。

以TI DaVinci平台为例,其UART控制器基于行业标准的TL16C550异步通信芯片,I2C符合Philips 2.1规范,SPI支持多设备片选操作。这些硬件特性通过Linux驱动得到了充分利用,为开发者提供了稳定可靠的通信基础。

2. UART驱动开发详解

2.1 UART硬件架构与Linux驱动模型

TL16C550是UART控制器的核心,它作为TL16C450的功能升级版,提供了更强大的异步通信能力。在Linux内核中,UART驱动采用分层架构设计:

  • 底层是硬件相关的控制驱动,直接操作UART寄存器
  • 中间层是Linux串行核心(Serial Core),提供统一的接口
  • 上层是TTY子系统,负责与用户空间交互

这种架构使得驱动开发可以专注于硬件相关部分,而通用功能则由内核框架提供。在DaVinci平台上,UART设备被映射为/dev/ttyS0(UART0)和/dev/ttyS1(UART1,仅DM355 EVM支持)设备文件。

2.2 UART驱动关键实现

UART驱动的核心任务是配置通信参数并实现数据收发。主要功能包括:

  1. 初始化硬件(设置波特率、数据位、停止位等)
  2. 实现中断处理程序(接收数据、发送完成、错误处理)
  3. 提供缓冲区管理(环形缓冲区设计)
  4. 实现termios接口(参数设置)

在DaVinci平台上,波特率通过分频器配置。计算公式为:

分频值 = (UART输入时钟频率) / (16 × 期望波特率)

例如,输入时钟为48MHz,要求波特率为115200时:

分频值 = 48,000,000 / (16 × 115200) ≈ 26

2.3 UART用户空间接口

应用层通过系统调用和ioctl与UART驱动交互。支持的主要操作包括:

  • 基本文件操作:open(), close(), read(), write()
  • 专用控制:ioctl()

重要ioctl命令示例:

// 获取串口参数
struct serial_struct serinfo;
ioctl(fd, TIOCGSERIAL, &serinfo);

// 设置波特率为115200
serinfo.flags &= ~ASYNC_SPD_MASK;
serinfo.flags |= ASYNC_SPD_CUST;
serinfo.custom_divisor = 26; 
ioctl(fd, TIOCSSERIAL, &serinfo);

2.4 UART开发注意事项

  1. 缓冲区管理 :内核驱动使用环形缓冲区存储待发送/接收数据,大小通常为4KB。开发者应注意避免缓冲区溢出。

  2. 流控制 :硬件流控制(RTS/CTS)能有效防止数据丢失,特别是在高速通信时建议启用。

  3. 中断处理 :UART中断频率与波特率直接相关。115200bps时,每个字节产生一次中断,CPU负载约1-2%。

  4. 性能优化 :对于高波特率(>1Mbps)场景,可考虑使用DMA传输减少CPU开销(虽然DaVinci UART驱动当前不支持)。

提示:调试UART驱动时,建议先使用较低波特率(9600bps)验证基本功能,再逐步提高速率。

3. I2C驱动开发详解

3.1 I2C协议与Linux驱动架构

I2C(Inter-Integrated Circuit)是一种同步、多主从的串行总线,由Philips(现NXP)开发。DaVinci平台的I2C控制器完全符合Philips I2C-bus规范2.1版本。

Linux I2C子系统采用分层设计:

用户空间程序
  ↓
/dev/i2c/0 (i2c-dev)
  ↓
I2C核心 (i2c-core)
  ↓
适配器算法 (i2c-algo-bit)
  ↓
硬件适配器 (i2c-davinci)
  ↓
I2C设备硬件

3.2 I2C驱动关键特性

DaVinci I2C驱动支持以下核心功能:

  • 7位地址寻址(标准模式100kbps,快速模式400kbps)
  • 中断驱动的工作模式
  • 多主设备支持
  • 时钟延展(clock stretching)

当前版本的限制:

  • 不支持10位地址模式(Linux内核中尚属实验性功能)
  • 不支持DMA传输方式

3.3 I2C用户空间访问

开发者可以通过i2c-dev接口在用户空间操作I2C设备。基本使用流程:

  1. 打开设备文件:
int fd = open("/dev/i2c/0", O_RDWR);
  1. 设置从设备地址(7位):
ioctl(fd, I2C_SLAVE, 0x50); // 设置地址为0x50
  1. 进行读写操作:
// 写入1字节数据
uint8_t buf[2] = {0x00, 0x55};
write(fd, buf, 2);

// 读取1字节数据
uint8_t val;
read(fd, &val, 1);

高级操作示例(组合读写):

struct i2c_msg msgs[2] = {
    {0x50, 0, 1, &reg_addr},  // 写入寄存器地址
    {0x50, I2C_M_RD, 1, &data} // 读取数据
};
struct i2c_rdwr_ioctl_data ioctl_data = {
    .msgs = msgs,
    .nmsgs = 2
};
ioctl(fd, I2C_RDWR, &ioctl_data);

3.4 I2C开发注意事项

  1. 时序控制 :I2C对时序要求严格,SCL/SDA信号需要适当的上拉电阻(通常4.7kΩ)。

  2. 地址冲突 :确保总线上每个设备有唯一地址。7位地址范围是0x03-0x77。

  3. 错误处理 :I2C操作可能因总线忙、无应答等失败,应添加重试机制(通常3次)。

  4. 性能考虑 :400kHz模式下,理论吞吐量约40KB/s(考虑协议开销)。

  5. 调试技巧 :使用逻辑分析仪或示波器观察SCL/SDA波形是排查问题的有效手段。

经验分享:对于频繁访问的设备,建议实现内核驱动而非用户空间操作,可减少上下文切换开销。

4. SPI驱动开发详解

4.1 SPI协议与Linux驱动架构

SPI(Serial Peripheral Interface)是一种全双工、同步的串行通信协议,采用主从架构。DaVinci平台的SPI控制器支持:

  • 标准3线或4线模式(MISO/MOSI/CLK/CS)
  • 多设备片选(最多支持2个从设备)
  • 仅主机模式操作

Linux SPI子系统架构:

用户空间程序
  ↓
MTD设备节点(如/dev/mtd4)
  ↓
SPI EEPROM驱动
  ↓
SPI控制器驱动
  ↓
SPI从设备硬件

4.2 SPI驱动关键实现

DaVinci SPI驱动的主要特性包括:

  1. 工作模式 :

    • 支持中断和轮询模式(默认轮询)
    • 时钟极性(CPOL)和相位(CPHA)可配置
  2. 性能参数 :

    • 最大时钟频率:输入时钟的1/2
    • 实测吞吐量(DM355平台):
      • 读取:74.6KB/s(低延迟桌面模式)
      • 写入:9.5KB/s
  3. EEPROM支持 :

    • 通过MTD子系统提供块设备接口
    • 支持ATMEL25640A等SPI Flash芯片

4.3 SPI用户空间访问

SPI设备通常通过内核驱动访问,但也可以使用spidev进行原始操作:

  1. 打开设备:
int fd = open("/dev/spidev0.0", O_RDWR);
  1. 配置参数:
uint8_t mode = SPI_MODE_0;
ioctl(fd, SPI_IOC_WR_MODE, &mode);

uint8_t bits = 8;
ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);

uint32_t speed = 1000000; // 1MHz
ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
  1. 数据传输:
struct spi_ioc_transfer tr = {
    .tx_buf = (unsigned long)tx_buf,
    .rx_buf = (unsigned long)rx_buf,
    .len = len,
    .delay_usecs = delay,
    .speed_hz = speed,
    .bits_per_word = bits,
};
ioctl(fd, SPI_IOC_MESSAGE(1), &tr);

4.4 SPI性能优化技巧

  1. 时钟配置 :SPI性能直接受时钟频率影响。DM355平台实测数据:
工作模式 读取速率(KB/s) 写入速率(KB/s)
低延迟桌面 74.6 9.5
服务器模式 77.4 9.6
实时抢占模式 52.2 8.7
  1. 传输优化 :

    • 使用大缓冲区减少传输次数
    • 避免频繁的小数据包传输
    • 考虑使用DMA(如果硬件支持)
  2. EEPROM写入 :

    • SPI Flash写入需要页擦除操作
    • 典型页写入时间5-10ms,应适当延迟

避坑指南:SPI总线长度应尽量短(<30cm),过长会导致信号完整性问题。必要时可添加终端电阻(100Ω)。

5. 驱动开发调试与优化

5.1 常用调试工具

  1. 内核日志 :通过printk输出调试信息,动态控制级别:
echo 8 > /proc/sys/kernel/printk
  1. proc和sysfs接口 :
# 查看串口信息
cat /proc/tty/driver/serial

# I2C设备检测
i2cdetect -y 0
  1. 用户空间测试工具 :
    • 串口:minicom, screen
    • I2C:i2c-tools包(i2cget, i2cset)
    • SPI:spidev_test

5.2 性能分析方法

  1. 时间测量 :
struct timeval start, end;
gettimeofday(&start, NULL);
// 操作代码
gettimeofday(&end, NULL);
long elapsed = (end.tv_sec - start.tv_sec) * 1000000 + 
               (end.tv_usec - start.tv_usec);
  1. 系统负载监控 :
top -p `pidof your_app`
  1. 中断统计 :
cat /proc/interrupts

5.3 常见问题排查

  1. UART数据丢失 :

    • 检查流控制配置
    • 提高接收缓冲区大小
    • 优化中断处理延迟
  2. I2C无应答 :

    • 确认设备地址正确
    • 检查上拉电阻
    • 验证电源电压
  3. SPI数据错误 :

    • 确认CPOL/CPHA设置匹配从设备
    • 检查时钟频率是否超过从设备限制
    • 用示波器验证信号质量
  4. 驱动加载失败 :

    • 检查内核配置选项
    • 确认资源冲突(IRQ、内存区域)
    • 查看dmesg输出

在实际项目中,我曾遇到一个SPI通信不稳定的问题。最终发现是PCB布局导致CLK信号串扰。通过降低时钟频率和重新布线解决了该问题。这个经验告诉我,当通信异常时,除了检查软件配置,硬件因素也需充分考虑。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐