1. 项目概述
在嵌入式系统开发中,数据通信与存储是两大核心功能模块。本课程将深入探讨三种典型的硬件通信接口:SPI总线、CAN总线和SD/eMMC存储系统。这三种技术虽然应用场景不同,但都解决了同一个根本问题——如何实现CPU与外部设备之间的可靠数据传输。
作为一名嵌入式开发工程师,我经常需要在项目中同时处理多种总线协议。记得有一次在工业控制器项目中,我们同时使用了SPI连接传感器、CAN总线组网,并通过eMMC存储日志数据。这种多总线协同工作的场景在实际开发中非常普遍。
2. SPI总线深度解析
2.1 SPI基础原理
SPI(Serial Peripheral Interface)是一种同步串行通信接口,采用主从架构,包含以下信号线:
- SCLK:时钟信号,由主设备产生
- MOSI:主设备输出,从设备输入
- MISO:主设备输入,从设备输出
- SS/CS:片选信号,低电平有效
SPI有四种工作时序模式,由CPOL(Clock Polarity)和CPHA(Clock Phase)两个参数决定:
- CPOL=0:时钟空闲时为低电平
- CPOL=1:时钟空闲时为高电平
- CPHA=0:数据在时钟第一个边沿采样
- CPHA=1:数据在时钟第二个边沿采样
实际项目中,最常用的模式是Mode 0(CPOL=0, CPHA=0)和Mode 3(CPOL=1, CPHA=1)
2.2 Linux下的SPI开发
在Linux系统中,SPI设备通过spidev驱动暴露给用户空间。典型的开发流程如下:
- 确认SPI设备节点:
bash复制ls /dev/spidev*
- 基本操作代码框架:
c复制#include <linux/spi/spidev.h>
int spi_setup(int fd, uint8_t mode, uint32_t speed) {
int ret;
// 设置SPI模式
ret = ioctl(fd, SPI_IOC_WR_MODE, &mode);
if (ret == -1) return -1;
// 设置时钟频率
ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
if (ret == -1) return -1;
return 0;
}
int spi_transfer(int fd, uint8_t *tx_buf, uint8_t *rx_buf, uint32_t len) {
struct spi_ioc_transfer tr = {
.tx_buf = (unsigned long)tx_buf,
.rx_buf = (unsigned long)rx_buf,
.len = len,
.delay_usecs = 0,
};
return ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
}
2.3 SPI环路测试实战
环路测试是验证SPI通信可靠性的重要手段。具体步骤如下:
- 硬件连接:将MOSI与MISO短接,形成环路
- 测试程序编写:
c复制#define TEST_PATTERN 0xAA
int main() {
int fd = open("/dev/spidev0.0", O_RDWR);
if (fd < 0) { /* 错误处理 */ }
if (spi_setup(fd, SPI_MODE_0, 1000000) < 0) { /* 错误处理 */ }
uint8_t tx_buf[4] = {TEST_PATTERN, TEST_PATTERN, TEST_PATTERN, TEST_PATTERN};
uint8_t rx_buf[4] = {0};
if (spi_transfer(fd, tx_buf, rx_buf, sizeof(tx_buf)) < 0) { /* 错误处理 */ }
// 验证接收数据
for (int i = 0; i < sizeof(rx_buf); i++) {
if (rx_buf[i] != TEST_PATTERN) {
printf("SPI loopback test failed at byte %d\n", i);
return -1;
}
}
printf("SPI loopback test passed!\n");
close(fd);
return 0;
}
注意事项:环路测试时,时钟频率不宜过高,建议从1MHz开始逐步提高,观察通信稳定性
3. CAN总线通信详解
3.1 CAN协议基础
CAN(Controller Area Network)是一种广泛应用于工业控制的串行通信协议,主要特点包括:
- 多主架构:所有节点平等,通过仲裁机制解决冲突
- 差分信号:CAN_H和CAN_L组成差分对,抗干扰能力强
- 消息优先级:基于标识符(ID)的仲裁机制
- 错误检测:CRC校验、帧检查等多项错误检测机制
CAN帧主要分为数据帧和远程帧两种类型。数据帧结构如下:
| 字段 | 起始位 | 仲裁场 | 控制场 | 数据场 | CRC场 | 应答场 | 结束场 |
|---|---|---|---|---|---|---|---|
| 位数 | 1 | 11/29 | 6 | 0-64 | 15 | 2 | 7 |
3.2 Linux SocketCAN编程
Linux内核通过SocketCAN子系统提供CAN支持,开发流程如下:
- 加载CAN驱动并配置接口:
bash复制sudo modprobe can
sudo modprobe can_raw
sudo ip link set can0 type can bitrate 500000
sudo ip link set up can0
- 基本CAN通信代码:
c复制#include <linux/can.h>
#include <linux/can/raw.h>
int can_send(int sock, uint32_t id, uint8_t *data, uint8_t len) {
struct can_frame frame;
frame.can_id = id;
frame.can_dlc = len;
memcpy(frame.data, data, len);
return write(sock, &frame, sizeof(frame));
}
int can_receive(int sock, uint32_t *id, uint8_t *data, uint8_t *len) {
struct can_frame frame;
int nbytes = read(sock, &frame, sizeof(frame));
if (nbytes > 0) {
*id = frame.can_id;
*len = frame.can_dlc;
memcpy(data, frame.data, frame.can_dlc);
}
return nbytes;
}
3.3 CAN总线调试技巧
- 使用candump工具监控总线:
bash复制candump can0
- 波特率设置注意事项:
- 常见工业标准波特率:125kbps, 250kbps, 500kbps, 1Mbps
- 总线所有节点必须使用相同波特率
- 长距离传输建议使用较低波特率
- 终端电阻配置:
- CAN总线两端需要接120Ω终端电阻
- 可通过测量CAN_H和CAN_L之间的电阻验证(应为60Ω左右)
4. SD/eMMC存储系统
4.1 存储协议栈
SD/eMMC存储系统涉及多层协议:
- 物理层:电气特性、引脚定义
- 传输层:命令/响应机制、数据传输
- 文件系统层:FAT32、ext4等
在Linux系统中,这些层次被完美抽象,开发者只需关注最上层的文件操作:
c复制int fd = open("/mnt/sdcard/test.txt", O_WRONLY | O_CREAT, 0644);
write(fd, "Hello SD Card!", 14);
close(fd);
4.2 存储设备初始化流程
SD卡初始化过程涉及以下关键命令:
- CMD0:复位卡到空闲状态
- CMD8:验证电压范围
- ACMD41:初始化卡并获取OCR寄存器
- CMD2:获取CID信息
- CMD3:获取相对地址(RCA)
实际开发中,这些底层操作都由MMC/SD控制器驱动处理,开发者无需直接操作
4.3 性能优化技巧
- 块大小选择:
- 默认块大小通常为512字节
- 高性能应用可考虑更大的块大小(如4KB)
- 写缓存策略:
c复制// 禁用写缓存(更安全)
mount("/dev/mmcblk0p1", "/mnt/sdcard", "vfat", MS_SYNCHRONOUS, NULL);
// 启用写缓存(更高性能)
mount("/dev/mmcblk0p1", "/mnt/sdcard", "vfat", 0, NULL);
- 文件系统选择:
- FAT32:兼容性好,适合小文件
- ext4:更适合Linux系统,支持日志功能
5. 总线选型指南
在实际项目中,总线选择需要考虑以下因素:
| 特性 | SPI | CAN | SD/eMMC |
|---|---|---|---|
| 通信距离 | <1m | 可达1km | 板级连接 |
| 传输速率 | 可达50Mbps | 可达1Mbps | 可达200MB/s |
| 拓扑结构 | 主从 | 多主 | 点对点 |
| 典型应用 | 芯片间通信 | 工业控制网络 | 大容量存储 |
| 开发复杂度 | 低 | 中 | 高 |
选择建议:
- 短距离、高速、简单控制:选择SPI
- 工业环境、多节点组网:选择CAN
- 大容量数据存储:选择SD/eMMC
6. 常见问题排查
6.1 SPI通信问题
- 无数据返回:
- 检查硬件连接(MOSI-MISO是否接反)
- 验证片选信号是否有效
- 确认SPI模式设置正确
- 数据错误:
- 降低时钟频率测试
- 检查电源稳定性
- 确认总线负载是否过重
6.2 CAN通信问题
- 无法接收到数据:
- 使用candump确认总线是否有数据
- 检查过滤器设置
- 验证终端电阻配置
- 高错误率:
- 检查电缆质量和连接器
- 降低波特率测试
- 确认所有节点同步
6.3 存储设备问题
- 设备未识别:
- 检查电源供应
- 验证物理连接
- 查看内核日志(dmesg)
- 写入速度慢:
- 尝试更大的块大小
- 检查文件系统碎片
- 考虑使用更高性能的卡
在多年的嵌入式开发中,我发现总线问题90%以上源于硬件连接或配置错误。建议采用"分而治之"的调试策略:先验证硬件连接,再检查驱动配置,最后排查应用层代码。记得有一次,一个SPI通信问题困扰了我们团队两天,最后发现只是片选信号线上的一个虚焊点。