Linux SPI/I2C 学习笔记

学习日期: 2026-01-03
硬件平台: Orange Pi AI Pro 8T
操作系统: Ubuntu 22.04 (Linux 5.10.0+ aarch64)
作者: 嵌入式学习者 (从STM32转Linux)


📚 目录

  1. 项目概述
  2. I2C学习笔记
  3. SPI学习笔记
  4. C vs Python对比
  5. 常见问题
  6. 参考资料

项目概述

🎯 学习目标

本项目旨在帮助从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

🎓 关键知识点

  1. 设备地址: Linux中使用7位地址(不含读写位),与STM32相同
  2. 权限问题: 需要root权限或将用户加入i2c
  3. 错误处理: 检查返回值,使用errno获取错误信息
  4. 超时设置: 可通过ioctl(fd, I2C_TIMEOUT, 100)设置超时
  5. 时钟速率: 通常由设备树配置,用户空间无法直接修改

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

🎓 关键知识点

  1. 全双工通信: SPI同时发送和接收,即使只读也要发送dummy数据
  2. 片选管理: Linux驱动自动管理CS信号
  3. 字节序: 注意大端/小端,Flash地址通常是大端(高字节在前)
  4. 时序要求: 设置正确的模式和速度,查看设备手册
  5. 速度限制: 实际速度受硬件和驱动限制,可能不是设置的精确值

C vs Python对比

性能对比

指标 C语言 Python
执行速度 ⭐⭐⭐⭐⭐ 快 ⭐⭐⭐ 较慢
内存占用 ⭐⭐⭐⭐⭐ 小 ⭐⭐⭐ 较大
编译时间 需要编译 无需编译
启动时间 ⭐⭐⭐⭐⭐ 快 ⭐⭐⭐ 较慢
实时性 ⭐⭐⭐⭐⭐ 好 ⭐⭐⭐ 一般

开发效率对比

指标 C语言 Python
代码量 少(约1/3)
调试难度 较高 较低
学习曲线 陡峭 平缓
开发速度
错误处理 手动管理 异常机制

使用建议

选择C语言的场景:

  • 对性能有严格要求
  • 需要实时响应
  • 嵌入式产品级开发
  • 与现有C代码集成

选择Python的场景:

  • 快速原型验证
  • 学习和测试
  • 数据处理和分析
  • 脚本自动化

我的建议:

  1. 学习阶段: 先用Python快速理解概念
  2. 深入理解: 用C语言掌握底层细节
  3. 实际项目: 根据需求选择合适的语言

常见问题

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

检查步骤:

  1. 查看可用设备:
ls /dev/i2c-*
ls /dev/spidev*
  1. 检查内核模块是否加载:
lsmod | grep i2c
lsmod | grep spi
  1. 加载模块(如果未加载):
sudo modprobe i2c-dev
sudo modprobe spidev
  1. 检查设备树配置:
# 查看I2C总线
ls /sys/bus/i2c/devices/

# 查看SPI总线
ls /sys/bus/spi/devices/

Q3: I2C扫描找不到设备

可能原因:

  1. 设备未连接: 检查硬件连接
  2. 电源问题: 确认设备供电正常
  3. 上拉电阻: I2C需要上拉电阻(通常4.7kΩ)
  4. 地址错误: 确认设备地址(7位)
  5. 总线选错: 尝试其他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

解决方案:

  1. 更新pip:
pip3 install --upgrade pip
  1. 使用国内镜像:
pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple smbus2
pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple spidev
  1. 安装系统包:
sudo apt-get install python3-smbus
sudo apt-get install python3-spidev

Q6: SPI Flash无法识别

调试步骤:

  1. 检查连接:

    • MOSI → Flash的DI/MOSI
    • MISO → Flash的DO/MISO
    • CLK → Flash的CLK
    • CS → Flash的CS
    • 共地
  2. 检查电源电压(通常3.3V)

  3. 尝试不同的SPI模式(0或3)

  4. 降低时钟速度(1MHz以下)

  5. 用逻辑分析仪查看波形


快速参考

常用命令

# ========== 编译 ==========
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)

进阶学习

推荐阅读

  1. 内核文档:

    • /usr/src/linux/Documentation/i2c/
    • /usr/src/linux/Documentation/spi/
  2. 设备手册:

    • I2C设备数据手册
    • SPI Flash数据手册
    • 开发板原理图
  3. 在线资源:

    • Linux内核文档: https://www.kernel.org/doc/
    • I2C工具: https://i2c.wiki.kernel.org/
    • Python smbus2: https://pypi.org/project/smbus2/

下一步学习建议

  1. 设备树 (Device Tree):

    • 学习如何配置I2C/SPI设备树节点
    • 理解设备树与硬件的映射关系
  2. 内核驱动:

    • 编写简单的I2C设备驱动
    • 编写SPI设备驱动
    • 理解Linux驱动框架
  3. 实际项目:

    • I2C: OLED显示屏、温湿度传感器、RTC时钟
    • SPI: Flash存储器、SD卡、LoRa模块
  4. 高级特性:

    • DMA传输
    • 中断驱动
    • 多主机模式

学习心得

STM32 vs Linux 对比总结

相似之处:
  • 通信协议本质相同(I2C/SPI标准)
  • 设备地址概念一致
  • 寄存器操作逻辑相似
主要差异:
  • Linux通过文件系统抽象硬件
  • 不直接操作寄存器,通过系统调用
  • 权限管理更严格
  • 时钟配置由内核管理
学习建议:
  1. 先理解协议本质(与STM32经验结合)
  2. 掌握Linux设备文件概念
  3. 学会使用系统工具(i2cdetect等)
  4. 逐步深入内核驱动

项目总结

通过本项目,我们完成了:

✅ 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经验,理解差异
  • 从简单到复杂,循序渐进
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐