Linux 外设通信接口基础概念全解析:UART、IIC、SPI、PWM、ADC 等
目录
Linux 操作系统在嵌入式开发、物联网、智能设备等领域广泛应用。无论是在开发板上调试传感器,还是在工控设备中通信控制,各种外设接口协议如 UART(串口通信)、IIC、SPI、PWM、ADC 等是常见的通信与控制方式。
本文将从基础概念、通信原理、Linux 驱动架构、用户态接口、调试方法、实践示例等角度,深入剖析这些外设接口的工作机制与实际应用。
一、UART(通用异步收发传输器)
1.1 UART 基础概念
UART(Universal Asynchronous Receiver/Transmitter)是一种异步串行通信协议,用于两点之间的数据收发,常见于 GPS 模块、蓝牙模块、串口调试等场景。
- 异步通信:不依赖时钟线,通过起始位、停止位、校验位实现数据同步。
- 通常包含 RX(接收)与 TX(发送)两根线。
1.2 通信参数
- 波特率(Baud rate):如 115200、9600。
- 数据位(Data bits):通常是 8 位。
- 校验位(Parity):无(None)、偶校验(Even)、奇校验(Odd)。
- 停止位(Stop bits):1 或 2。
1.3 Linux 驱动架构
Linux 中 UART 通常由 tty 驱动框架管理:
- 内核设备节点:
/dev/ttySx
(传统 UART)或/dev/ttyUSBx
(USB 转串口)等。 - 底层由
serial_core
框架管理,平台驱动需实现uart_ops
结构体。
1.4 用户态调试方法
- 使用
minicom
、screen
、cutecom
等工具。 - 示例:
sudo apt install minicom minicom -D /dev/ttyUSB0 -b 115200
1.5 实践示例(C 语言)
int fd = open("/dev/ttyS1", O_RDWR | O_NOCTTY);
struct termios tty;
tcgetattr(fd, &tty);
cfsetospeed(&tty, B115200);
cfsetispeed(&tty, B115200);
tcsetattr(fd, TCSANOW, &tty);
write(fd, "Hello UART\n", 11);
二、IIC(I²C,Inter-Integrated Circuit)
2.1 IIC 基础概念
IIC 是由 Philips 公司开发的多主多从、同步串行通信协议,常用于连接 EEPROM、温湿度传感器等低速设备。
- 两根线:SCL(时钟)、SDA(数据)
- 使用 7 位或 10 位地址识别设备。
- 支持多设备挂载在同一总线上。
2.2 IIC 通信流程
- 主机发起 START 信号。
- 发地址 + R/W 位。
- 从设备响应 ACK。
- 主从数据传输。
- 主机发 STOP 信号结束。
2.3 Linux 驱动架构
- 内核框架:
i2c-core
。 - 用户态节点:
/dev/i2c-x
。 - 典型驱动模型:
i2c_adapter
:控制器驱动。i2c_client
:挂载的外设驱动。
2.4 用户态操作示例
- 安装工具:
sudo apt install i2c-tools
- 扫描总线:
i2cdetect -y 1
- 读取寄存器:
i2cget -y 1 0x50 0x00
2.5 C 语言示例
int fd = open("/dev/i2c-1", O_RDWR);
ioctl(fd, I2C_SLAVE, 0x50); // 设置从地址
char buf[1] = {0x00};
write(fd, buf, 1);
read(fd, buf, 1);
三、SPI(串行外设接口)
3.1 SPI 基础概念
SPI 是一种全双工、高速、同步串行通信协议,广泛用于 Flash、LCD、ADC、IMU 等设备。
- 通信线:
- MISO:主机接收
- MOSI:主机发送
- SCLK:时钟
- CS(Chip Select):片选线
3.2 SPI 通信特点
- 全双工:同时收发数据。
- 主从模式:主设备控制通信。
- 可设置模式(MODE0 ~ MODE3),定义时钟极性和相位。
3.3 Linux 驱动架构
- 使用
spi-core
框架。 - 设备节点:
/dev/spidevX.Y
。 - 驱动层通过
spi_driver
进行绑定。
3.4 用户态操作示例
- 启用 SPI(以 Raspberry Pi 为例):
sudo raspi-config
- 查看设备:
ls /dev/spidev*
3.5 C 语言示例
int fd = open("/dev/spidev0.0", O_RDWR);
uint8_t tx[] = {0x9F};
uint8_t rx[3] = {0};
struct spi_ioc_transfer tr = {
.tx_buf = (unsigned long)tx,
.rx_buf = (unsigned long)rx,
.len = 1,
};
ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
四、PWM(脉冲宽度调制)
4.1 PWM 基础概念
PWM(Pulse Width Modulation)是一种通过调节脉冲宽度的占空比(Duty Cycle)来控制模拟信号的方法,广泛应用于控制LED亮度、电机转速、蜂鸣器音量等场景。
- 占空比(Duty Cycle):高电平持续时间占整个周期的比例,通常用百分比表示。
- 周期(Period):PWM 信号一个完整的高低电平变化所用时间。
- 极性(Polarity):PWM信号的高低电平起始状态,可为高有效或低有效。
4.2 PWM 在 Linux 中的实现
Linux 内核通过 PWM Framework 支持硬件的 PWM 控制。
- 内核暴露的 PWM 设备一般在
/sys/class/pwm/
下。 - 一个 PWM 控制器对应一个
pwmchipX
目录。 - 每个 PWM 通道以
pwmX
命名。 - 典型文件包括:
export
:启用某个 PWM 通道。unexport
:禁用 PWM 通道。period
:设置 PWM 周期(单位:纳秒)。duty_cycle
:设置占空比(单位:纳秒)。enable
:使能或关闭 PWM 输出。
4.3 用户态控制示例
假设系统中有一个 PWM 控制器 pwmchip0
,我们要控制第 0 路 PWM:
# 导出 PWM0 通道
echo 0 > /sys/class/pwm/pwmchip0/export
# 设置周期为 1ms(1,000,000 ns)
echo 1000000 > /sys/class/pwm/pwmchip0/pwm0/period
# 设置占空比为 50%(500,000 ns)
echo 500000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
# 使能 PWM 输出
echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable
# 停止 PWM:
echo 0 > /sys/class/pwm/pwmchip0/pwm0/enable
echo 0 > /sys/class/pwm/pwmchip0/unexport
4.4 PWM 的实际应用
-
LED 亮度调节:通过改变占空比,调整 LED 的亮度,实现渐变或闪烁效果。
-
电机转速控制:PWM 信号控制电机供电平均电压,从而控制速度。
-
蜂鸣器音量与频率控制:通过改变周期和占空比产生不同频率和音量的声音。
4.5 代码示例(C语言)
下面是一个简单的 Linux 用户态 PWM 控制示例,演示打开 PWM 通道,设置参数,启动和关闭:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int pwm_export(int chip, int channel) {
char path[64];
int fd;
snprintf(path, sizeof(path), "/sys/class/pwm/pwmchip%d/export", chip);
fd = open(path, O_WRONLY);
if (fd < 0) return -1;
char buf[8];
int len = snprintf(buf, sizeof(buf), "%d", channel);
write(fd, buf, len);
close(fd);
return 0;
}
int pwm_set_period(int chip, int channel, unsigned int period_ns) {
char path[64];
snprintf(path, sizeof(path), "/sys/class/pwm/pwmchip%d/pwm%d/period", chip, channel);
int fd = open(path, O_WRONLY);
if (fd < 0) return -1;
char buf[32];
int len = snprintf(buf, sizeof(buf), "%u", period_ns);
write(fd, buf, len);
close(fd);
return 0;
}
int pwm_set_duty_cycle(int chip, int channel, unsigned int duty_ns) {
char path[64];
snprintf(path, sizeof(path), "/sys/class/pwm/pwmchip%d/pwm%d/duty_cycle", chip, channel);
int fd = open(path, O_WRONLY);
if (fd < 0) return -1;
char buf[32];
int len = snprintf(buf, sizeof(buf), "%u", duty_ns);
write(fd, buf, len);
close(fd);
return 0;
}
int pwm_enable(int chip, int channel, int enable) {
char path[64];
snprintf(path, sizeof(path), "/sys/class/pwm/pwmchip%d/pwm%d/enable", chip, channel);
int fd = open(path, O_WRONLY);
if (fd < 0) return -1;
char buf[2];
int len = snprintf(buf, sizeof(buf), "%d", enable);
write(fd, buf, len);
close(fd);
return 0;
}
int main() {
int chip = 0;
int channel = 0;
unsigned int period = 1000000; // 1ms
unsigned int duty = 500000; // 50%
pwm_export(chip, channel);
pwm_set_period(chip, channel, period);
pwm_set_duty_cycle(chip, channel, duty);
pwm_enable(chip, channel, 1);
printf("PWM enabled. Press Enter to stop...\n");
getchar();
pwm_enable(chip, channel, 0);
return 0;
}
以上就是 Linux 下 PWM 基础概念及简单操作介绍,掌握这些内容可以帮助你快速在嵌入式项目中实现硬件控制和调节。
五、ADC(模拟转数字转换器)
5.1 ADC 基础概念
ADC(Analog-to-Digital Converter)将连续模拟信号转换为数字信号,在传感器数据读取中非常常见。
- 分辨率:如 10bit、12bit、16bit。
- 电压范围:03.3V,05V 等。
- 常见用途:温度、光照、电压检测。
5.2 Linux 驱动架构
- 使用
iio
框架(Industrial I/O)。 - 用户节点:
/sys/bus/iio/devices/iio:deviceX/in_voltageY_raw
。
5.3 用户态读取示例
cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw
5.4 实践应用
-
温度传感器读取。
-
电池电压监测。
六、调试技巧与开发建议
6.1 设备树配置
-
嵌入式平台多数使用 Device Tree 配置外设信息,确保:
-
节点启用(status = “okay”)。
-
正确的 pinmux 和中断号。
-
驱动兼容字符串匹配成功。
6.2 日志调试
-
使用 dmesg | grep 命令查看驱动加载和设备初始化信息。
-
查看中断分配情况:
cat /proc/interrupts
6.3 权限问题
-
设备文件权限不足时,建议使用 sudo 运行命令。
-
也可以通过 udev 规则给设备节点设置合适权限,避免频繁使用 sudo。
七、总结
接口 | 通信方式 | 特点 | 应用场景 |
---|---|---|---|
UART | 异步串口 | 简单稳定 | 调试口、蓝牙、GPS |
IIC | 同步串口 | 多从支持 | EEPROM、传感器 |
SPI | 同步串口 | 高速全双工 | LCD、Flash、IMU |
PWM | 模拟控制 | 占空比调制 | 电机、LED、蜂鸣器 |
ADC | 模拟采集 | 分辨率控制 | 电压、电流、温度 |
Linux 提供了统一且强大的外设抽象接口,使得在各种平台上开发变得规范和高效。掌握这些外设的基本概念与 Linux 实现机制,对于驱动开发、系统集成乃至上层应用开发都有极大的帮助。