news 2026/10/16 18:01:57

嵌入式学习 day68:驱动——SPI总线

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
嵌入式学习 day68:驱动——SPI总线

一、SPI 总线基础

1. 什么是 SPI

前面已经学习过SPI了。SPI(Serial Peripheral Interface)是一种高速串行通信总线,和 I2C 类似,但速度更快,适合 Flash、屏幕、传感器等设备。

2. SPI 的四根线

线全称作用
SCLKSerial Clock时钟线
MOSIMaster Out Slave In主机发数据
MISOMaster In Slave Out主机收数据
CSChip Select片选线

每个 SPI 从设备有一根独立的 CS 线,主机拉低哪根 CS,就和哪个设备通信。

3. SPI 和 I2C 对比

项目I2CSPI
线数2 根4 根
速度慢(100k~400k)快(几 MHz~几十 MHz)
设备数多(靠地址区分)多(靠片选线区分)
全双工半双工全双工
典型设备EEPROM、温度传感器Flash、屏幕、ADC

二、SPI 驱动的整体框架

text

头文件 ↓ 全局变量(spi_device) ↓ 初始化函数(读设备 ID、配置寄存器) ↓ 读取函数(读数据) ↓ file_operations(绑定 read) ↓ miscdevice(描述 misc 设备) ↓ probe(SPI 匹配成功时调用) ↓ remove(卸载时调用) ↓ spi_driver + spi_register_driver

三、SPI 驱动的两种传输方式

方式一:spi_write_then_read(简单)

c

int spi_write_then_read(struct spi_device *spi, const void *txbuf, unsigned n_tx, void *rxbuf, unsigned n_rx);
参数含义
spiSPI 设备
txbuf发送缓冲区
n_tx发送字节数
rxbuf接收缓冲区
n_rx接收字节数

先发txbuf,再收rxbuf。适合先写命令再读数据的场景(比如寄存器读写)。

用法示例:

c

// 读寄存器 tx[0] = 0x00 | (0x1 << 7); // 寄存器地址 + 读标志 spi_write_then_read(spi, tx, 1, &rx, 1); // 写寄存器 tx[0] = 0x31; // 寄存器地址 tx[1] = 0x0b; // 要写的数据 spi_write_then_read(spi, tx, 2, NULL, 0);

方式二:spi_sync+spi_message(复杂)

c

struct spi_transfer transfer = { .tx_buf = tx, .rx_buf = rx, .len = 7, .delay_usecs = 20, }; struct spi_message sendmsg; spi_message_init(&sendmsg); spi_message_add_tail(&transfer, &sendmsg); spi_sync(spi, &sendmsg);
结构体作用
spi_transfer一次 SPI 传输(发送/接收缓冲区、长度)
spi_message一次完整的 SPI 消息(可以包含多个 transfer)

适合多段传输、自定义时序的场景。

两种方式对比

方式函数适合
简单spi_write_then_read先写命令,再读数据
复杂spi_sync+spi_message多段传输、自定义时序

四、SPI 驱动的关键结构体

1.spi_device

c

static struct spi_device *padxl345_spi = NULL;
成员含义
max_speed_hz最大时钟频率
chip_select片选编号
bits_per_word每字位数
modeSPI 模式(0~3)

2.spi_transfer

c

struct spi_transfer { const void *tx_buf; // 发送缓冲区 void *rx_buf; // 接收缓冲区 unsigned len; // 数据长度 unsigned delay_usecs; // 传输后延时 };

3.spi_message

c

struct spi_message { struct list_head transfers; // 传输链表 };

五、SPI 驱动的完整代码结构

1. 头文件

c

#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/spi/spi.h> #include <linux/miscdevice.h> #include <asm/uaccess.h>

2. 全局变量

c

static struct spi_device *padxl345_spi = NULL;

3. 初始化函数

c

static int adxl345_init(void) { int ret = 0; unsigned char tx[2] = {0}; unsigned char rx = {0}; // 1. 读设备 ID tx[0] = 0x00 | (0x1 << 7); ret = spi_write_then_read(padxl345_spi, tx, 1, &rx, 1); // 2. 配置寄存器 tx[0] = 0x31; tx[1] = 0x0b; ret = spi_write_then_read(padxl345_spi, tx, 2, NULL, 0); // 3. 开启采集 tx[0] = 0x2D; tx[1] = 0x08; ret = spi_write_then_read(padxl345_spi, tx, 2, NULL, 0); return 0; }

4. 读取函数

c

static int adxl345_read_xyz(short *pxa, short *pya, short *pza) { int ret = 0; unsigned char tx[7] = {0}; unsigned char rx[7] = {0}; struct spi_message sendmsg; struct spi_transfer transfer = { .tx_buf = tx, .rx_buf = rx, .len = 7, .delay_usecs = 20, }; spi_message_init(&sendmsg); tx[0] = (0x32 | (0x1 << 7) | (0x1 << 6)); // 多字节读 spi_message_add_tail(&transfer, &sendmsg); ret = spi_sync(padxl345_spi, &sendmsg); *pxa = (rx[2] << 8 | rx[1]); *pya = (rx[4] << 8 | rx[3]); *pza = (rx[6] << 8 | rx[5]); return 0; }

5. 应用层接口

c

static ssize_t adxl345_read(struct file *fp, char __user *puser, size_t n, loff_t *off) { unsigned long nret = 0; int ret = 0; short acc[3] = {0}; ret = adxl345_init(); ret = adxl345_read_xyz(&acc[0], &acc[1], &acc[2]); nret = copy_to_user(puser, acc, sizeof(acc)); if (nret) { pr_info("copy_to_user failed\n"); return -1; } return sizeof(acc); }

6. file_operations

c

static struct file_operations fops = { .owner = THIS_MODULE, .read = adxl345_read, };

7. miscdevice

c

static struct miscdevice adxl345_misc = { .minor = MISC_DYNAMIC_MINOR, .name = "adxl345_misc", .fops = &fops, };

8. probe

c

static int adxl345_probe(struct spi_device *spi) { int ret = 0; pr_info("max_speed_hz = %d\n", spi->max_speed_hz); pr_info("chip_select = %d\n", spi->chip_select); pr_info("bits_per_word = %d\n", spi->bits_per_word); pr_info("mode = %d\n", spi->mode); padxl345_spi = spi; ret = misc_register(&adxl345_misc); if (ret) { pr_info("misc_register failed\n"); return -1; } return 0; }

9. remove

c

static int adxl345_remove(struct spi_device *spi) { misc_deregister(&adxl345_misc); return 0; }

10. 匹配表

c

static struct spi_device_id adxl345_id_table[] = { {.name = "puteadxl345"}, {}, }; static struct of_device_id adxl345_of_match_table[] = { {.compatible = "pute,puteadxl345"}, {}, };

11. spi_driver

c

static struct spi_driver adxl345_drv = { .probe = adxl345_probe, .remove = adxl345_remove, .driver = { .name = "puteadxl345", .owner = THIS_MODULE, .of_match_table = adxl345_of_match_table, }, .id_table = adxl345_id_table, };

12. 注册和注销

c

static int __init adxl345_drv_init(void) { return spi_register_driver(&adxl345_drv); } static void __exit adxl345_drv_exit(void) { spi_unregister_driver(&adxl345_drv); } module_init(adxl345_drv_init); module_exit(adxl345_drv_exit);

六、设备树

dts

&ecspi1 { status = "okay"; adxl345@0 { compatible = "pute,puteadxl345"; reg = <0>; // 片选编号 spi-max-frequency = <1000000>; // 最大时钟频率 spi-cpol; // 时钟极性 spi-cpha; // 时钟相位 }; };
属性作用
compatible驱动匹配
reg片选编号
spi-max-frequency最大时钟频率
spi-cpol/spi-cphaSPI 模式(0~3)

七、应用程序

c

int fd = open("/dev/adxl345_misc", O_RDWR); short acc[3] = {0}; read(fd, acc, sizeof(acc)); printf("X: %d, Y: %d, Z: %d\n", acc[0], acc[1], acc[2]); close(fd);

八、SPI 和 I2C 驱动对比

项目I2CSPI
设备结构i2c_clientspi_device
驱动结构i2c_driverspi_driver
传输结构i2c_msgspi_transfer+spi_message
简单传输master_xferspi_write_then_read
复杂传输i2c_transferspi_sync
注册i2c_add_driverspi_register_driver
注销i2c_del_driverspi_unregister_driver
probe 参数i2c_client *spi_device *

框架一样,API 不同。

九、总结

部分作用
spi_device描述 SPI 从设备
spi_transfer描述一次 SPI 传输
spi_message描述一次完整的 SPI 消息
spi_write_then_read简单读写
spi_sync同步执行复杂传输
spi_register_driver注册 SPI 驱动
compatible设备树匹配

SPI 驱动的框架和 platform、I2C 一样,只是总线 API 不同。掌握一种,其他就是换 API。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 9:47:42

大文件秒传技术:JSP实现原理与优化方案

1. 大文件上传的技术挑战与秒传原理在Web开发领域&#xff0c;文件上传功能看似基础实则暗藏玄机。当文件体积超过100MB时&#xff0c;传统的表单上传方式就会暴露出各种问题&#xff1a;网络波动导致上传失败、服务器内存溢出、用户等待时间过长等。更糟糕的是&#xff0c;当用…

作者头像 李华
网站建设 2026/10/15 9:46:52

固定交付日期项目管理:挑战与实战策略

1. 固定交付日期项目的本质与挑战固定交付日期&#xff08;Fixed Deadline&#xff09;项目是软件开发中最具挑战性的模式之一。这种模式下&#xff0c;交付日期往往由外部因素决定——可能是行业展会、法规生效日、季节性营销节点或客户合同条款。我经历过最极端的情况是某金融…

作者头像 李华
网站建设 2026/10/15 9:46:47

Python网络编程中的OSI模型映射与分层排查实践

1. 标题背后的典型认知错位&#xff1a;为什么“Python之OSI协议”根本不存在刚看到这个标题时&#xff0c;我下意识点开想看看是不是有哪位同行做了什么突破性整合——结果翻遍所有主流技术社区、GitHub仓库和学术论文库&#xff0c;连一个能稳定运行的“OSI协议Python实现”项…

作者头像 李华
网站建设 2026/10/15 9:46:36

Agent 结论发出前先对账:值域对账验证层 + n=3 重复对照的搭建方法

Agent 结论发出前先对账&#xff1a;值域对账验证层 n3 重复对照的搭建方法 这两天 Agent 圈还在卷"模型越来越聪明"——更大的参数、更长的上下文、更强的推理。可查数场景有个反直觉的现实&#xff1a;结论错了&#xff0c;往往不是模型算错了&#xff0c;而是它把…

作者头像 李华
网站建设 2026/10/15 9:46:32

可见光与红外图像配准:Canny边缘检测+ORB特征匹配实战

1. 多模态图像配准到底难在哪&#xff1a;从可见光与红外的成像差异说起做过图像融合或者多传感器感知项目的人&#xff0c;大概率都碰过同一个钉子&#xff1a;手里有一张可见光图、一张红外图&#xff0c;两张图拍的是同一个场景&#xff0c;但想把它们对齐&#xff0c;怎么调…

作者头像 李华
网站建设 2026/10/15 9:45:49

SQLite FTS5与BM25全文检索原理及工程实践

我理解您的要求&#xff0c;但需要坦诚说明&#xff1a;当前输入内容中&#xff0c;项目标题“context-mode”未提供任何实质性描述、上下文或可操作信息&#xff0c;仅有一个孤立的术语&#xff0c;且关联的热搜词&#xff08;如MCP、SQLite、FTS5、BM25&#xff09;虽属技术范…

作者头像 李华