【Linux驱动开发】第24天:SPI 数据收发机制 Linux SPI 数据收发spi_transfer / spi_message 机制详解同步示例一、核心结构体基础1.struct spi_transfer单次传输片段描述一段连续收发缓冲区代表一次单向/双向数据移位操作。structspi_transfer{constvoid*tx_buf;// 发送缓冲区NULL只收不发void*rx_buf;// 接收缓冲区NULL只发不收unsignedlen;// 收发字节长度tx/rx长度一致unsignedcs_change:1;// 传输完是否拉高片选unsigneddelay_usecs;// 本次传输后延时u32 speed_hz;// 本次单独时钟覆盖spi_device默认u8 bits_per_word;// 位宽覆盖默认u8 tx_nbits;// MOSI线数单线/双线u8 rx_nbits;// MISO线数};关键规则tx_buf/rx_buf可单独置空tx非空、rx0只写rx非空、tx0只读都不为空全双工同时收发len是字节总数tx/rx长度必须相同cs_change1该transfer结束后释放片选默认整条message只首尾拉/拉高CS。2.struct spi_message一整笔SPI事务把多个连续spi_transfer打包为一次完整SPI操作全程片选保持低电平仅事务结束释放。structspi_message{structlist_headtransfers;// transfer链表structspi_device*spi;// 所属spi设备unsignedstatus;// 执行状态 0成功负数错误码void(*complete)(void*context);// 异步完成回调void*context;unsignedactual_length;// 实际传输字节};事务执行流程内核拉低CS依次执行链表内所有spi_transfer全部transfer执行完毕后拉高CS同步阻塞等待全部完成返回异步直接返回完成触发complete回调。3. 层级关系图spi_message完整事务CS全程选中 ├─ spi_transfer1写指令 ├─ spi_transfer2读数据 └─ spi_transfer3写寄存器二、同步 vs 异步传输区别同步传输spi_sync()调用进程阻塞直到整条message传输完毕简单易写90%普通外设Flash、OLED、传感器优先使用适合probe、read/write系统调用上下文接口原型intspi_sync(structspi_device*spi,structspi_message*msg);返回值0成功负数错误码。异步传输spi_async()调用立即返回不阻塞线程传输完成后执行message-complete回调函数适合高速大数据、DMA大批量传输LCD、摄像头注意回调运行在中断上下文不能睡眠、不能调用阻塞接口。三、内核封装简易同步API日常优先用内核基于spi_sync封装好简易接口不用手动构造message/transferspi_write_then_read()最常用先发指令再读数据Flash、传感器标准操作intspi_write_then_read(structspi_device*spi,constvoid*txbuf,unsignedn_tx,void*rxbuf,unsignedn_rx);spi_write()仅发送数据staticinlineintspi_write(structspi_device*spi,constvoid*buf,size_tlen)spi_read()仅读取数据staticinlineintspi_read(structspi_device*spi,void*buf,size_tlen)四、底层原生同步示例手动构造messagetransfer场景W25Q读取JEDEC ID先发送0x9F指令再读取3字节ID。#includelinux/spi/spi.h#includelinux/mutex// 互斥锁保护SPI并发访问staticDEFINE_MUTEX(spi_lock);staticintspi_read_jedec_id(structspi_device*spi,u8*id_buf){intret;structspi_messagemsg;structspi_transferxfer[2];// 两段传输发指令、读IDu8 tx_cmd0x9F;// W25读ID指令// 初始化spi_messagespi_message_init(msg);msg.spispi;// 第一段transfer只发送指令不接收memset(xfer[0],0,sizeof(xfer[0]));xfer[0].tx_buftx_cmd;xfer[0].rx_bufNULL;xfer[0].len1;spi_message_add_tail(xfer[0],msg);// 第二段transfer只接收数据不发送memset(xfer[1],0,sizeof(xfer[1]));xfer[1].tx_bufNULL;xfer[1].rx_bufid_buf;xfer[1].len3;spi_message_add_tail(xfer[1],msg);mutex_lock(spi_lock);retspi_sync(spi,msg);// 同步阻塞传输mutex_unlock(spi_lock);if(ret){dev_err(spi-dev,spi sync failed ret%d\n,ret);returnret;}return0;}代码流程说明spi_message_init()清空初始化消息构造2段transfer发指令、读数据spi_message_add_tail()将transfer挂入message链表spi_sync()提交给SPI控制器阻塞等待传输结束多线程操作SPI必须加互斥锁防止传输交叉错乱。五、spi_write_then_read 简化等效写法上面手动构造message的逻辑可一行简化功能完全一致u8 cmd0x9F;u8 id[3];intretspi_write_then_read(spi,cmd,1,id,3);内核内部自动封装spi_message、transfer、spi_sync开发优先使用该接口。六、纯发送、纯读同步示例1. 仅写操作发送单字节命令u8 cmd0x06;// W25写使能指令intretspi_write(spi,cmd,1);2. 仅读操作无前置指令u8 data[4];intretspi_read(spi,data,4);七、多段连续传输示例多transfer需求先发指令(1B) → 发地址(3B) → 读16字节数据全程CS不释放staticintspi_multi_xfer_demo(structspi_device*spi){intret;structspi_messagemsg;structspi_transferxfer[3];u8 tx_buf[4]{0x03,0x00,0x00,0x00};// 读指令地址u8 rx_buf[16]{0};spi_message_init(msg);msg.spispi;// xfer0:发送4字节指令地址xfer[0].tx_buftx_buf;xfer[0].len4;spi_message_add_tail(xfer[0],msg);// xfer1:接收16字节数据xfer[1].rx_bufrx_buf;xfer[1].len16;spi_message_add_tail(xfer[1]);// xfer2可选传输后拉高片选xfer[2].cs_change1;spi_message_add_tail(xfer[2],msg);mutex_lock(spi_lock);retspi_sync(spi,msg);mutex_unlock(spi);returnret;}八、关键机制总结分层模型spi_transfer最小数据分片spi_message完整一次CS选中的事务由多个transfer组成同步传输spi_sync阻塞适合绝大多数外设驱动开发成本最低简易封装接口spi_write_then_read覆盖90%寄存器读写场景优先使用多线程访问SPI必须互斥锁保护避免多条message交错执行一条message内部默认全程CS低仅全部transfer完成后释放单transfer末尾可通过cs_change提前释放片选。