在嵌入式Linux开发中,SPI(Serial Peripheral Interface)总线是最常用的外设接口之一。理解SPI子系统的框架对于开发SPI设备驱动至关重要。SPI子系统的核心架构可以分为两个关键部分:
SPI控制器作为总线的主设备,其注册流程遵循标准的platform驱动框架。当控制器设备与驱动匹配成功后,probe函数会执行以下关键操作:
c复制// 典型控制器probe函数示例
static int xxx_spi_probe(struct platform_device *pdev)
{
struct spi_master *master;
struct xxx_spi *xspi;
// 从设备树获取资源
master = spi_alloc_master(&pdev->dev, sizeof(*xspi));
// 初始化master参数
master->mode_bits = SPI_CPOL | SPI_CPHA;
master->setup = xxx_spi_setup;
master->transfer = xxx_spi_transfer;
// 注册master
spi_bitbang_start(&xspi->bitbang);
return 0;
}
控制器注册完成后,内核会自动遍历其下的子节点,为每个子节点生成对应的spi_device。这个过程需要注意:
重要提示:在设备树中,SPI控制器的status属性默认为"disabled",必须手动改为"okay"才能使能控制器。同时,cs-gpios属性必须正确定义,否则片选信号无法正常工作。
以IMX6ULL处理器连接ICM-20608传感器为例,设备树配置如下:
dts复制&ecspi3 {
fsl,spi-num-chipselects = <1>;
cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi3>;
status = "okay";
spidev: icm20608@0 {
compatible = "alientek,icm20608";
spi-max-frequency = <8000000>;
reg = <0>;
};
};
关键配置说明:
fsl,spi-num-chipselects:指定片选数量cs-gpios:定义片选GPIO(注意电平极性)spi-max-frequency:设置SPI通信最大频率reg:指定设备连接的片选线编号SPI设备驱动的核心是实现数据的读写功能。ICM-20608的寄存器读写函数如下:
c复制static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{
int ret;
u8 txdata = reg | 0x80; // 读操作寄存器地址最高位置1
struct spi_transfer t[] = {
{
.tx_buf = &txdata,
.len = 1,
}, {
.rx_buf = buf,
.len = len,
}
};
ret = spi_sync_transfer(dev->spi, t, ARRAY_SIZE(t));
return ret;
}
static int icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len)
{
u8 *txdata = kzalloc(len + 1, GFP_KERNEL);
txdata[0] = reg & ~0x80; // 写操作寄存器地址最高位清0
memcpy(&txdata[1], buf, len);
int ret = spi_write(dev->spi, txdata, len + 1);
kfree(txdata);
return ret;
}
ICM-20608的初始化需要严格按照数据手册的时序要求:
c复制void icm20608_reginit(void)
{
icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x80);
mdelay(50);
icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x01);
mdelay(50);
u8 value = icm20608_read_onereg(&icm20608dev, ICM20_WHO_AM_I);
printk("ICM20608 ID = %#X\n", value);
icm20608_write_onereg(&icm20608dev, ICM20_SMPLRT_DIV, 0x00);
icm20608_write_onereg(&icm20608dev, ICM20_GYRO_CONFIG, 0x18);
icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18);
icm20608_write_onereg(&icm20608dev, ICM20_CONFIG, 0x04);
icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2, 0x04);
icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_2, 0x00);
}
在SPI通信中,片选信号的控制至关重要。实测发现以下两种场景需要特别注意:
推荐做法:
c复制// 使用spi_write_then_read保持片选持续有效
static int icm20608_read_regs_optimized(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{
u8 addr = reg | 0x80;
return spi_write_then_read(dev->spi, &addr, 1, buf, len);
}
对于大数据量传输,可以采用以下优化手段:
c复制// DMA传输示例
static int icm20608_read_fifo(struct icm20608_dev *dev, void *buf, int len)
{
struct spi_transfer t = {
.rx_buf = buf,
.len = len,
.tx_buf = NULL,
};
return spi_sync_transfer(dev->spi, &t, 1);
}
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 读取ID不正确 | 1. 接线错误 2. 时钟极性/相位不匹配 3. 片选信号问题 |
1. 检查硬件连接 2. 确认SPI_MODE配置 3. 用示波器观察片选信号 |
| 数据传输不稳定 | 1. 时钟频率过高 2. 电源噪声 3. 地线干扰 |
1. 降低SPI时钟频率 2. 增加电源滤波电容 3. 优化PCB布局 |
| 寄存器写入无效 | 1. 写保护未解除 2. 时序不满足要求 3. 字节序错误 |
1. 检查设备写保护位 2. 添加适当延时 3. 确认数据格式 |
bash复制# 查看SPI设备信息
cat /sys/bus/spi/devices/spiX.Y/uevent
# 启用SPI调试输出
echo 8 > /proc/sys/kernel/printk
dmesg | grep spi
ICM-20608正常工作时,读取的原始数据应满足以下特征:
异常数据处理建议:
通过深入理解SPI子系统框架和掌握这些实战技巧,开发者可以高效地完成各种SPI设备驱动的开发工作。在实际项目中,建议结合具体芯片的数据手册,严格遵循其通信时序要求,同时充分利用Linux内核提供的SPI核心API,确保驱动的稳定性和可靠性。