Linux SPI/I2C 学习笔记
Linux SPI/I2C 学习笔记
学习日期: 2026-01-03
硬件平台: Orange Pi AI Pro 8T
操作系统: Ubuntu 22.04 (Linux 5.10.0+ aarch64)
作者: 嵌入式学习者 (从STM32转Linux)
📚 目录
项目概述
🎯 学习目标
本项目旨在帮助从STM32开发转向Linux嵌入式开发的学习者,掌握Linux下的SPI和I2C通信接口。
📁 项目结构
linux_spi_i2c_learning/
├── i2c_test/ # I2C测试程序
│ ├── i2c_scan.c # I2C扫描工具(C语言)
│ ├── i2c_scan.py # I2C扫描工具(Python)
│ ├── i2c_rw.c # I2C读写示例(C语言)
│ ├── i2c_rw.py # I2C读写示例(Python)
│ └── Makefile # I2C编译脚本
├── spi_test/ # SPI测试程序
│ ├── spi_loopback.c # SPI回环测试(C语言)
│ ├── spi_loopback.py # SPI回环测试(Python)
│ ├── spi_rw.c # SPI Flash读写(C语言)
│ ├── spi_rw.py # SPI Flash读写(Python)
│ └── Makefile # SPI编译脚本
├── docs/ # 文档目录
│ └── 学习笔记.md # 本文档
├── common/ # 公共代码(预留)
└── Makefile # 总编译脚本
I2C学习笔记
📖 基础知识
I2C协议回顾(与STM32对比)
| 特性 | STM32 (HAL库) | Linux |
|---|---|---|
| 设备访问 | 直接操作寄存器 | 通过设备文件 /dev/i2c-X |
| 初始化 | HAL_I2C_Init() |
open() + ioctl() |
| 写数据 | HAL_I2C_Mem_Write() |
write() 或 i2c_smbus_*() |
| 读数据 | HAL_I2C_Mem_Read() |
read() 或 i2c_smbus_*() |
| 时钟配置 | 通过寄存器设置 | 由内核驱动管理 |
Linux I2C设备文件
在Orange Pi上,I2C设备位于 /dev/i2c-X,其中X是总线编号:
$ ls -l /dev/i2c-*
crw-rw---- 1 HwHiAiUser HwHiAiUser 89, 0 Jan 2 22:17 /dev/i2c-0
crw-rw---- 1 HwHiAiUser HwHiAiUser 89, 1 Jan 2 22:17 /dev/i2c-1
crw-rw---- 1 HwHiAiUser HwHiAiUser 89, 2 Jan 2 22:17 /dev/i2c-2
...
🔧 C语言实现
1. I2C扫描工具
文件: i2c_scan.c
核心代码:
// 打开I2C设备
int fd = open("/dev/i2c-1", O_RDWR);
// 设置从设备地址
ioctl(fd, I2C_SLAVE, address);
// 尝试读取(测试设备是否响应)
uint8_t buf;
if (read(fd, &buf, 1) >= 0) {
printf("找到设备: 0x%02X\n", address);
}
编译运行:
cd i2c_test
make
sudo ./i2c_scan 1 # 扫描 /dev/i2c-1
示例输出:
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: 50 51 -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
找到 2 个I2C设备
2. I2C读写示例
文件: i2c_rw.c
关键API:
| 函数 | 功能 | STM32等效 |
|---|---|---|
i2c_open() |
打开并初始化I2C | HAL_I2C_Init() |
i2c_write_reg() |
写寄存器 | HAL_I2C_Mem_Write() |
i2c_read_reg() |
读寄存器 | HAL_I2C_Mem_Read() |
i2c_close() |
关闭设备 | HAL_I2C_DeInit() |
使用示例:
I2C_Device i2c;
// 打开I2C-1总线,设备地址0x50
i2c_open(&i2c, 1, 0x50);
// 读取寄存器0x00
uint8_t data;
i2c_read_reg(&i2c, 0x00, &data, 1);
// 写入数据到寄存器0x10
uint8_t write_data[] = {0xAA, 0xBB};
i2c_write_reg(&i2c, 0x10, write_data, 2);
i2c_close(&i2c);
编译运行:
cd i2c_test
sudo ./i2c_rw 1 0x50 # 操作I2C-1上地址为0x50的设备
🐍 Python实现
安装依赖
pip3 install smbus2
Python示例
文件: i2c_rw.py
from smbus2 import SMBus
class I2CDevice:
def __init__(self, bus_num, dev_addr):
self.bus = SMBus(bus_num)
self.dev_addr = dev_addr
def write_reg(self, reg_addr, data):
# 写入单字节
if isinstance(data, int):
self.bus.write_byte_data(self.dev_addr, reg_addr, data)
# 写入多字节
else:
self.bus.write_i2c_block_data(self.dev_addr, reg_addr, data)
def read_reg(self, reg_addr, length=1):
# 读取单字节
if length == 1:
return self.bus.read_byte_data(self.dev_addr, reg_addr)
# 读取多字节
else:
return self.bus.read_i2c_block_data(self.dev_addr, reg_addr, length)
# 使用示例
i2c = I2CDevice(1, 0x50)
data = i2c.read_reg(0x00)
i2c.write_reg(0x10, [0xAA, 0xBB])
运行:
sudo python3 i2c_test/i2c_scan.py 1
sudo python3 i2c_test/i2c_rw.py 1 0x50
🎓 关键知识点
- 设备地址: Linux中使用7位地址(不含读写位),与STM32相同
- 权限问题: 需要root权限或将用户加入
i2c组 - 错误处理: 检查返回值,使用
errno获取错误信息 - 超时设置: 可通过
ioctl(fd, I2C_TIMEOUT, 100)设置超时 - 时钟速率: 通常由设备树配置,用户空间无法直接修改
SPI学习笔记
📖 基础知识
SPI协议回顾(与STM32对比)
| 特性 | STM32 (HAL库) | Linux |
|---|---|---|
| 设备访问 | 直接操作寄存器 | 通过设备文件 /dev/spidevX.Y |
| 初始化 | HAL_SPI_Init() |
open() + ioctl() 配置 |
| 传输 | HAL_SPI_TransmitReceive() |
ioctl(SPI_IOC_MESSAGE) |
| 模式配置 | 通过寄存器设置 | ioctl(SPI_IOC_WR_MODE) |
| 速度配置 | 通过时钟分频器 | ioctl(SPI_IOC_WR_MAX_SPEED_HZ) |
SPI模式
| 模式 | CPOL | CPHA | 说明 |
|---|---|---|---|
| 0 | 0 | 0 | 空闲低电平,第一个边沿采样 |
| 1 | 0 | 1 | 空闲低电平,第二个边沿采样 |
| 2 | 1 | 0 | 空闲高电平,第一个边沿采样 |
| 3 | 1 | 1 | 空闲高电平,第二个边沿采样 |
Linux SPI设备文件
$ ls -l /dev/spidev*
crw------- 1 root root 153, 0 Jan 2 22:17 /dev/spidev0.0
crw------- 1 root root 153, 1 Jan 2 22:17 /dev/spidev1.0
crw-rw---- 1 HwHiAiUser HwHiAiUser 153, 2 Jan 2 22:17 /dev/spidev3.0
格式: /dev/spidevX.Y
- X: SPI总线编号
- Y: 片选(CS)编号
🔧 C语言实现
1. SPI回环测试
文件: spi_loopback.c
硬件连接: 将MOSI和MISO短接
核心代码:
// 打开SPI设备
int fd = open("/dev/spidev0.0", O_RDWR);
// 设置SPI模式
uint8_t mode = 0;
ioctl(fd, SPI_IOC_WR_MODE, &mode);
// 设置时钟速度
uint32_t speed = 1000000; // 1MHz
ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
// 全双工传输
struct spi_ioc_transfer tr = {
.tx_buf = (unsigned long)tx_buffer,
.rx_buf = (unsigned long)rx_buffer,
.len = data_length,
.speed_hz = speed,
.bits_per_word = 8,
};
ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
编译运行:
cd spi_test
make
sudo ./spi_loopback 0 0 1000000 0
参数说明: 总线号 片选号 速度(Hz) 模式
示例输出:
【测试1】基本回环测试 (8字节):
发送数据: 01 23 45 67 89 AB CD EF
接收数据: 01 23 45 67 89 AB CD EF
✓ 测试通过!
2. SPI Flash读写
文件: spi_rw.c
支持的SPI Flash命令:
| 命令 | 值 | 功能 |
|---|---|---|
| JEDEC ID | 0x9F | 读取制造商和设备ID |
| Read Status | 0x05 | 读取状态寄存器 |
| Write Enable | 0x06 | 使能写操作 |
| Read Data | 0x03 | 读取Flash数据 |
示例:
// 读取Flash ID
uint8_t tx_buf[4] = {0x9F, 0x00, 0x00, 0x00};
uint8_t rx_buf[4];
spi_transfer(&spi_dev, tx_buf, rx_buf, 4);
uint8_t mfg_id = rx_buf[1]; // 制造商ID
uint8_t dev_id = rx_buf[2]; // 设备ID
常见Flash厂商ID:
- 0xEF: Winbond
- 0xC2: Macronix
- 0x20: Micron
- 0x01: Spansion
编译运行:
sudo ./spi_rw 0 0 1000000 0
🐍 Python实现
安装依赖
pip3 install spidev
Python示例
文件: spi_loopback.py
import spidev
class SPIDevice:
def __init__(self, bus_num, cs_num, speed, mode=0):
self.spi = spidev.SpiDev()
self.spi.open(bus_num, cs_num)
self.spi.max_speed_hz = speed
self.spi.mode = mode
self.spi.bits_per_word = 8
def transfer(self, tx_data):
"""全双工传输"""
return self.spi.xfer2(tx_data)
def write(self, tx_data):
"""只写"""
self.spi.writebytes(tx_data)
def read(self, length):
"""只读"""
return self.spi.readbytes(length)
# 使用示例
spi = SPIDevice(0, 0, 1000000, 0)
rx_data = spi.transfer([0x01, 0x23, 0x45, 0x67])
spi.close()
运行:
sudo python3 spi_test/spi_loopback.py 0 0 1000000 0
sudo python3 spi_test/spi_rw.py 0 0 1000000 0
🎓 关键知识点
- 全双工通信: SPI同时发送和接收,即使只读也要发送dummy数据
- 片选管理: Linux驱动自动管理CS信号
- 字节序: 注意大端/小端,Flash地址通常是大端(高字节在前)
- 时序要求: 设置正确的模式和速度,查看设备手册
- 速度限制: 实际速度受硬件和驱动限制,可能不是设置的精确值
C vs Python对比
性能对比
| 指标 | C语言 | Python |
|---|---|---|
| 执行速度 | ⭐⭐⭐⭐⭐ 快 | ⭐⭐⭐ 较慢 |
| 内存占用 | ⭐⭐⭐⭐⭐ 小 | ⭐⭐⭐ 较大 |
| 编译时间 | 需要编译 | 无需编译 |
| 启动时间 | ⭐⭐⭐⭐⭐ 快 | ⭐⭐⭐ 较慢 |
| 实时性 | ⭐⭐⭐⭐⭐ 好 | ⭐⭐⭐ 一般 |
开发效率对比
| 指标 | C语言 | Python |
|---|---|---|
| 代码量 | 多 | 少(约1/3) |
| 调试难度 | 较高 | 较低 |
| 学习曲线 | 陡峭 | 平缓 |
| 开发速度 | 慢 | 快 |
| 错误处理 | 手动管理 | 异常机制 |
使用建议
选择C语言的场景:
- 对性能有严格要求
- 需要实时响应
- 嵌入式产品级开发
- 与现有C代码集成
选择Python的场景:
- 快速原型验证
- 学习和测试
- 数据处理和分析
- 脚本自动化
我的建议:
- 学习阶段: 先用Python快速理解概念
- 深入理解: 用C语言掌握底层细节
- 实际项目: 根据需求选择合适的语言
常见问题
Q1: 权限不足 (Permission denied)
问题:
Error opening /dev/i2c-1: Permission denied
解决方案:
方法1: 使用sudo运行
sudo ./i2c_scan 1
方法2: 将用户加入i2c和spi组
sudo usermod -a -G i2c $USER
sudo usermod -a -G spi $USER
# 重新登录生效
方法3: 修改设备文件权限(不推荐)
sudo chmod 666 /dev/i2c-*
sudo chmod 666 /dev/spidev*
Q2: 设备不存在 (No such file or directory)
问题:
Error opening /dev/i2c-1: No such file or directory
检查步骤:
- 查看可用设备:
ls /dev/i2c-*
ls /dev/spidev*
- 检查内核模块是否加载:
lsmod | grep i2c
lsmod | grep spi
- 加载模块(如果未加载):
sudo modprobe i2c-dev
sudo modprobe spidev
- 检查设备树配置:
# 查看I2C总线
ls /sys/bus/i2c/devices/
# 查看SPI总线
ls /sys/bus/spi/devices/
Q3: I2C扫描找不到设备
可能原因:
- 设备未连接: 检查硬件连接
- 电源问题: 确认设备供电正常
- 上拉电阻: I2C需要上拉电阻(通常4.7kΩ)
- 地址错误: 确认设备地址(7位)
- 总线选错: 尝试其他I2C总线
调试方法:
# 使用i2cdetect工具扫描
sudo i2cdetect -y 1
# 查看I2C总线信息
sudo i2cdetect -l
Q4: SPI回环测试失败
检查清单:
- MOSI和MISO是否短接
- 连接线是否牢固
- 设备文件是否存在
- SPI模式是否正确
- 时钟速度是否过高
降低速度测试:
# 降低到100kHz测试
sudo ./spi_loopback 0 0 100000 0
Q5: Python模块安装失败
问题:
ERROR: Could not find a version that satisfies the requirement smbus2
解决方案:
- 更新pip:
pip3 install --upgrade pip
- 使用国内镜像:
pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple smbus2
pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple spidev
- 安装系统包:
sudo apt-get install python3-smbus
sudo apt-get install python3-spidev
Q6: SPI Flash无法识别
调试步骤:
-
检查连接:
- MOSI → Flash的DI/MOSI
- MISO → Flash的DO/MISO
- CLK → Flash的CLK
- CS → Flash的CS
- 共地
-
检查电源电压(通常3.3V)
-
尝试不同的SPI模式(0或3)
-
降低时钟速度(1MHz以下)
-
用逻辑分析仪查看波形
快速参考
常用命令
# ========== 编译 ==========
make # 编译所有程序
make i2c # 只编译I2C程序
make spi # 只编译SPI程序
make clean # 清理
# ========== I2C ==========
# 扫描I2C总线
sudo i2cdetect -y 1
sudo ./i2c_test/i2c_scan 1
sudo python3 i2c_test/i2c_scan.py 1
# 读写I2C设备
sudo ./i2c_test/i2c_rw 1 0x50
sudo python3 i2c_test/i2c_rw.py 1 0x50
# ========== SPI ==========
# SPI回环测试(需要MOSI-MISO短接)
sudo ./spi_test/spi_loopback 0 0 1000000 0
sudo python3 spi_test/spi_loopback.py 0 0 1000000 0
# SPI Flash测试
sudo ./spi_test/spi_rw 0 0 1000000 0
sudo python3 spi_test/spi_rw.py 0 0 1000000 0
# ========== 系统命令 ==========
# 查看I2C总线
ls /dev/i2c-*
i2cdetect -l
# 查看SPI设备
ls /dev/spidev*
# 查看设备树
ls /sys/bus/i2c/devices/
ls /sys/bus/spi/devices/
API速查表
I2C (C语言)
#include <linux/i2c-dev.h>
// 打开设备
int fd = open("/dev/i2c-1", O_RDWR);
// 设置从设备地址
ioctl(fd, I2C_SLAVE, 0x50);
// 写入数据
write(fd, buffer, length);
// 读取数据
read(fd, buffer, length);
// 关闭
close(fd);
I2C (Python)
from smbus2 import SMBus
bus = SMBus(1)
# 写单字节
bus.write_byte_data(addr, reg, value)
# 读单字节
value = bus.read_byte_data(addr, reg)
# 写多字节
bus.write_i2c_block_data(addr, reg, [data...])
# 读多字节
data = bus.read_i2c_block_data(addr, reg, length)
SPI (C语言)
#include <linux/spi/spidev.h>
// 打开设备
int fd = open("/dev/spidev0.0", O_RDWR);
// 设置模式
uint8_t mode = 0;
ioctl(fd, SPI_IOC_WR_MODE, &mode);
// 设置速度
uint32_t speed = 1000000;
ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
// 传输
struct spi_ioc_transfer tr = {
.tx_buf = (unsigned long)tx_buf,
.rx_buf = (unsigned long)rx_buf,
.len = len,
};
ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
SPI (Python)
import spidev
spi = spidev.SpiDev()
spi.open(0, 0)
spi.max_speed_hz = 1000000
spi.mode = 0
# 全双工传输
rx_data = spi.xfer2([0x01, 0x02, 0x03])
# 只写
spi.writebytes([0x01, 0x02])
# 只读
rx_data = spi.readbytes(4)
进阶学习
推荐阅读
-
内核文档:
/usr/src/linux/Documentation/i2c//usr/src/linux/Documentation/spi/
-
设备手册:
- I2C设备数据手册
- SPI Flash数据手册
- 开发板原理图
-
在线资源:
- Linux内核文档: https://www.kernel.org/doc/
- I2C工具: https://i2c.wiki.kernel.org/
- Python smbus2: https://pypi.org/project/smbus2/
下一步学习建议
-
设备树 (Device Tree):
- 学习如何配置I2C/SPI设备树节点
- 理解设备树与硬件的映射关系
-
内核驱动:
- 编写简单的I2C设备驱动
- 编写SPI设备驱动
- 理解Linux驱动框架
-
实际项目:
- I2C: OLED显示屏、温湿度传感器、RTC时钟
- SPI: Flash存储器、SD卡、LoRa模块
-
高级特性:
- DMA传输
- 中断驱动
- 多主机模式
学习心得
STM32 vs Linux 对比总结
相似之处:
- 通信协议本质相同(I2C/SPI标准)
- 设备地址概念一致
- 寄存器操作逻辑相似
主要差异:
- Linux通过文件系统抽象硬件
- 不直接操作寄存器,通过系统调用
- 权限管理更严格
- 时钟配置由内核管理
学习建议:
- 先理解协议本质(与STM32经验结合)
- 掌握Linux设备文件概念
- 学会使用系统工具(i2cdetect等)
- 逐步深入内核驱动
项目总结
通过本项目,我们完成了:
✅ I2C和SPI的C语言实现
✅ I2C和SPI的Python实现
✅ 设备扫描和诊断工具
✅ 完整的测试用例
✅ 详细的学习文档
收获:
- 理解了Linux用户空间的I2C/SPI编程
- 掌握了C和Python两种实现方式
- 学会了使用系统工具诊断问题
- 建立了从STM32到Linux的知识迁移
下一步计划:
- 连接实际I2C设备(OLED/传感器)进行测试
- 测试SPI Flash读写擦除功能
- 学习设备树配置
- 尝试编写内核驱动
附录
A. 硬件信息
开发板: Orange Pi AI Pro 8T
处理器: ARM Cortex-A (aarch64)
操作系统: Ubuntu 22.04
内核版本: Linux 5.10.0+
I2C总线: 10个 (i2c-0 ~ i2c-9, i2c-13)
SPI总线: 5个 (spidev0.0, spidev1.0, spidev3.0, spidev4.0, spidev5.0)
B. 工具安装
# 编译工具
sudo apt-get install build-essential
# I2C工具
sudo apt-get install i2c-tools libi2c-dev
# Python库
pip3 install smbus2 spidev
# 可选工具
sudo apt-get install tree # 目录树显示
C. 项目文件清单
总共文件: 15个
- C源文件: 4个
- Python脚本: 4个
- Makefile: 3个
- 文档: 1个
- README: 1个
D. 版本历史
- v1.0 (2026-01-03): 初始版本
- 实现I2C/SPI的C和Python版本
- 完成基础测试程序
- 编写学习文档
💡 学习Tips:
- 边学边实践,不要只看不做
- 遇到问题先查日志和错误信息
- 善用系统工具(i2cdetect, i2cdump等)
- 对比STM32经验,理解差异
- 从简单到复杂,循序渐进
更多推荐



所有评论(0)