5.3 I2C设备
title: "5.3 I2C设备"
shortTitle: "I2C设备"
description: "OneOS I2C 设备驱动详解,包括 I2C 总线设备与从设备双层抽象、消息传输、客户端读写操作与完整使用示例"
icon: "fa6-solid:arrows-left-right"
author: 学习阁
date: 2026-07-18
category: 设备驱动
tag:
- 驱动
- I2C
- 总线
- 传感器
order: 4
5.3 I2C设备
📚 本节导读
学习时长: 约 50 分钟
难度级别: ⭐⭐⭐⭐☆
前置知识: I2C 总线协议基础(SCL/SDA、起始/停止条件、ACK/NACK、设备地址)、5.1 设备驱动框架
🎯 学习目标
- 理解 OneOS 中 I2C 总线设备与 I2C 从设备的双层抽象设计
- 掌握
os_i2c_bus_device和os_i2c_client核心数据结构 - 掌握 I2C 消息传输机制与标志位含义
- 掌握主机发送/接收与客户端读写寄存器的 API 使用
- 了解 I2C 控制命令与设备模式配置
一、概述
I2C(Inter-Integrated Circuit)是两线制串行总线,由 SCL(时钟线)和 SDA(数据线)组成,支持多主多从架构。OneOS 将 I2C 分为两层抽象:
- I2C 总线设备(
os_i2c_bus_device):表示一条 I2C 总线控制器,负责底层时序控制和数据传输 - I2C 从设备/客户端(
os_i2c_client):表示挂载在总线上的具体外设芯片,封装了总线引用和设备地址
这种双层设计使得一套 I2C 总线驱动可以服务于多个不同类型的 I2C 外设,实现了总线资源与设备逻辑的分离。
二、核心数据结构
2.1 I2C 总线设备 struct os_i2c_bus_device
struct os_i2c_bus_device
{
struct os_device parent; /* 继承自 os_device_t */
const struct os_i2c_bus_device_ops *ops; /* I2C 总线操作函数表 */
uint16_t addr; /* 总线自身地址 */
uint16_t flags; /* 总线标志 */
os_mutex_id lock; /* 互斥锁,保证总线访问互斥 */
uint32_t timeout; /* 传输超时时间 */
uint32_t retries; /* 重试次数 */
void *priv; /* 私有数据 */
};| 成员 | 说明 |
|---|---|
parent | 继承自 os_device_t,可注册到设备框架中 |
ops | I2C 总线操作函数表,实现底层传输 |
lock | 互斥锁,保证同一时刻只有一个任务访问总线 |
timeout | 传输超时时间,超过此时间未完成则返回错误 |
retries | 传输失败后的重试次数 |
priv | 私有数据指针,通常指向 MCU 特定的 I2C 外设寄存器结构 |
2.2 I2C 从设备(客户端) struct os_i2c_client
struct os_i2c_client
{
struct os_i2c_bus_device *bus; /* 所属 I2C 总线 */
uint16_t client_addr; /* 从设备地址 */
};os_i2c_client 是一个轻量级结构体,仅包含总线引用和设备地址,用于表示挂载在 I2C 总线上的具体外设芯片。
2.3 I2C 消息结构体 struct os_i2c_msg
struct os_i2c_msg
{
uint16_t addr; /* 从设备地址 */
uint16_t flags; /* 传输标志 */
uint16_t len; /* 数据长度 */
uint8_t *buf; /* 数据缓冲区 */
};一条 I2C 消息描述了一次原子传输操作。多个 os_i2c_msg 可以组成一次批量传输(如先写寄存器地址,再读数据)。
2.4 I2C 总线操作函数表 struct os_i2c_bus_device_ops
struct os_i2c_bus_device_ops
{
os_size_t (*i2c_transfer)(struct os_i2c_bus_device *bus,
struct os_i2c_msg msgs[], uint32_t num);
os_size_t (*i2c_slave_transfer)(struct os_i2c_bus_device *bus,
struct os_i2c_msg msgs[], uint32_t num);
os_err_t (*i2c_bus_control)(struct os_i2c_bus_device *bus, void *arg);
};| 函数指针 | 说明 |
|---|---|
i2c_transfer | 主机模式传输,执行 msgs 数组中的若干条消息 |
i2c_slave_transfer | 从机模式传输,响应外部主机的访问 |
i2c_bus_control | 总线控制,如设置速率、切换模式等 |
三、I2C 标志位与模式
3.1 读写方向标志
#define OS_I2C_WR 0x0000 /* 写方向 */
#define OS_I2C_RD (1u << 0) /* 读方向 */3.2 地址模式标志
#define OS_I2C_ADDR_10BIT (1u << 2) /* 10 位地址模式 */默认使用 7 位地址模式,设置此标志后使用 10 位地址。
3.3 传输控制标志
#define OS_I2C_NO_START (1u << 4) /* 不发送起始条件 */
#define OS_I2C_IGNORE_NACK (1u << 5) /* 忽略 NACK 应答 */
#define OS_I2C_NO_READ_ACK (1u << 6) /* 读取时不发送 ACK */| 标志 | 说明 |
|---|---|
OS_I2C_NO_START | 不发送起始条件,用于连续传输(Restart) |
OS_I2C_IGNORE_NACK | 忽略从设备的 NACK,继续传输 |
OS_I2C_NO_READ_ACK | 读取最后一个字节后不发送 ACK,表示读取结束 |
3.4 设备模式
#define OS_I2C_DEV_MEM_MODE 0 /* 内存模式 */
#define OS_I2C_DEV_MASTER_MODE 1 /* 主机模式 */
#define OS_I2C_DEV_SLAVE_MODE 2 /* 从机模式 */3.5 控制命令
#define OS_I2C_DEV_CTRL_10BIT IOC_I2CBUS(0x00) /* 设置 10 位地址模式 */
#define OS_I2C_DEV_CTRL_ADDR IOC_I2CBUS(0x01) /* 设置设备地址 */
#define OS_I2C_DEV_CTRL_TIMEOUT IOC_I2CBUS(0x02) /* 设置超时时间 */
#define OS_I2C_DEV_CTRL_RW IOC_I2CBUS(0x03) /* 设置读写模式 */这些命令通过 os_device_control 接口发送给 I2C 总线设备。
四、核心 API 详解
4.1 I2C 总线设备注册与查找
os_err_t os_i2c_bus_device_register(struct os_i2c_bus_device *device,
const char *bus_name, void *data);
struct os_i2c_bus_device *os_i2c_bus_device_find(const char *bus_name);os_i2c_bus_device_register:
- 注册 I2C 总线设备到设备框架
bus_name:总线名称,如"i2c1"data:用户数据,通常为 MCU 特定的 I2C 外设配置- 内部调用
os_device_register完成注册
os_i2c_bus_device_find:
- 按名称查找已注册的 I2C 总线设备
- 返回总线设备指针,未找到返回
OS_NULL
4.2 I2C 批量传输
os_size_t os_i2c_transfer(struct os_i2c_bus_device *bus,
struct os_i2c_msg msgs[], uint32_t num);- 在指定 I2C 总线上执行批量消息传输
msgs:消息数组,可包含多条消息(如先写寄存器地址再读数据)num:消息数量- 返回值:成功传输的消息数量
4.3 主机发送
os_size_t os_i2c_master_send(struct os_i2c_bus_device *bus,
uint16_t addr,
uint16_t flags,
const uint8_t *buf,
uint32_t count);- 以主机模式向指定地址的从设备发送数据
addr:从设备地址flags:传输标志,通常为OS_I2C_WRbuf:发送数据缓冲区count:发送字节数- 返回值:成功发送的字节数
4.4 主机接收
os_size_t os_i2c_master_recv(struct os_i2c_bus_device *bus,
uint16_t addr,
uint16_t flags,
uint8_t *buf,
uint32_t count);- 以主机模式从指定地址的从设备接收数据
addr:从设备地址flags:传输标志,通常为OS_I2C_RDbuf:接收数据缓冲区count:期望接收的字节数- 返回值:成功接收的字节数
4.5 客户端写寄存器
os_err_t os_i2c_client_write(struct os_i2c_client *client,
uint32_t cmd, uint8_t cmd_len,
uint8_t *buff, uint32_t len);
os_err_t os_i2c_client_write_byte(struct os_i2c_client *client,
uint32_t cmd, uint8_t cmd_len, uint8_t data);os_i2c_client_write:
- 向 I2C 从设备的指定寄存器(或命令)写入数据
cmd:寄存器地址或命令字cmd_len:寄存器地址长度(1/2/4 字节)buff:要写入的数据缓冲区len:写入数据长度- 内部自动构造两条消息:先发送寄存器地址(写),再发送数据(写)
os_i2c_client_write_byte:
- 向 I2C 从设备写入单个字节
data:要写入的单个字节数据
4.6 客户端读寄存器
os_err_t os_i2c_client_read(struct os_i2c_client *client,
uint32_t cmd, uint8_t cmd_len,
uint8_t *buff, uint32_t len);
uint8_t os_i2c_client_read_byte(struct os_i2c_client *client,
uint32_t cmd, uint8_t cmd_len);os_i2c_client_read:
- 从 I2C 从设备的指定寄存器读取数据
cmd:寄存器地址或命令字cmd_len:寄存器地址长度(1/2/4 字节)buff:读取数据缓冲区len:期望读取的数据长度- 内部自动构造两条消息:先发送寄存器地址(写),再读取数据(读)
os_i2c_client_read_byte:
- 从 I2C 从设备读取单个字节
- 返回值:读取到的字节数据
五、使用示例
示例 1:I2C 主机发送与接收
#include <i2c.h>
#include <os_task.h>
#define SENSOR_ADDR 0x48 /* 传感器 7 位地址 */
void i2c_master_demo(void)
{
struct os_i2c_bus_device *bus;
uint8_t tx_data[2] = {0x00, 0x55}; /* 寄存器地址 0x00,写入数据 0x55 */
uint8_t rx_data[4] = {0};
/* 1. 查找 I2C 总线 */
bus = os_i2c_bus_device_find("i2c1");
if (bus == OS_NULL)
{
os_kprintf("I2C bus not found!\r\n");
return;
}
/* 2. 主机发送:向传感器写配置 */
os_size_t ret = os_i2c_master_send(bus, SENSOR_ADDR, OS_I2C_WR, tx_data, 2);
if (ret != 2)
{
os_kprintf("I2C master send failed: %d\r\n", ret);
return;
}
os_kprintf("Sent %d bytes to sensor\r\n", ret);
/* 3. 主机接收:从传感器读取数据 */
ret = os_i2c_master_recv(bus, SENSOR_ADDR, OS_I2C_RD, rx_data, 4);
if (ret != 4)
{
os_kprintf("I2C master recv failed: %d\r\n", ret);
return;
}
os_kprintf("Received: %02X %02X %02X %02X\r\n",
rx_data[0], rx_data[1], rx_data[2], rx_data[3]);
}示例 2:I2C 客户端读写寄存器(模拟 EEPROM)
#include <i2c.h>
#include <os_task.h>
#define EEPROM_ADDR 0x50 /* EEPROM 7 位地址 */
void i2c_client_eeprom_demo(void)
{
struct os_i2c_bus_device *bus;
struct os_i2c_client client;
uint8_t write_buf[8] = "OneOS!";
uint8_t read_buf[8] = {0};
/* 1. 查找 I2C 总线 */
bus = os_i2c_bus_device_find("i2c1");
if (bus == OS_NULL)
{
os_kprintf("I2C bus not found!\r\n");
return;
}
/* 2. 构造客户端结构体 */
client.bus = bus;
client.client_addr = EEPROM_ADDR;
/* 3. 向 EEPROM 地址 0x0000 写入数据(2 字节寄存器地址) */
os_err_t ret = os_i2c_client_write(&client, 0x0000, 2, write_buf, 6);
if (ret != OS_EOK)
{
os_kprintf("EEPROM write failed: %d\r\n", ret);
return;
}
os_kprintf("EEPROM write success: %s\r\n", write_buf);
/* 4. 从 EEPROM 地址 0x0000 读取数据 */
ret = os_i2c_client_read(&client, 0x0000, 2, read_buf, 6);
if (ret != OS_EOK)
{
os_kprintf("EEPROM read failed: %d\r\n", ret);
return;
}
os_kprintf("EEPROM read success: %s\r\n", read_buf);
}示例 3:使用 I2C 消息批量传输
#include <i2c.h>
#define SENSOR_ADDR 0x68
os_size_t sensor_read_register(struct os_i2c_bus_device *bus,
uint8_t reg_addr, uint8_t *data, uint32_t len)
{
struct os_i2c_msg msgs[2];
/* 第一条消息:发送寄存器地址(写操作) */
msgs[0].addr = SENSOR_ADDR;
msgs[0].flags = OS_I2C_WR;
msgs[0].len = 1;
msgs[0].buf = ®_addr;
/* 第二条消息:读取数据(读操作) */
msgs[1].addr = SENSOR_ADDR;
msgs[1].flags = OS_I2C_RD;
msgs[1].len = len;
msgs[1].buf = data;
/* 批量传输:先写寄存器地址,再读数据 */
return os_i2c_transfer(bus, msgs, 2);
}
void i2c_msg_demo(void)
{
struct os_i2c_bus_device *bus;
uint8_t data[6] = {0};
bus = os_i2c_bus_device_find("i2c1");
if (bus == OS_NULL) return;
/* 读取传感器寄存器 0x3B 开始的 6 个字节 */
os_size_t ret = sensor_read_register(bus, 0x3B, data, 6);
if (ret == 2)
{
os_kprintf("Sensor data: %02X %02X %02X %02X %02X %02X\r\n",
data[0], data[1], data[2], data[3], data[4], data[5]);
}
}示例 4:I2C 总线控制(设置超时时间)
#include <i2c.h>
#include <device.h>
void i2c_config_demo(void)
{
struct os_i2c_bus_device *bus;
os_device_t *dev;
bus = os_i2c_bus_device_find("i2c1");
if (bus == OS_NULL) return;
/* 获取父设备指针 */
dev = (os_device_t *)bus;
/* 设置传输超时时间为 100ms */
os_uint32_t timeout = 100;
os_device_control(dev, OS_I2C_DEV_CTRL_TIMEOUT, &timeout);
os_kprintf("I2C timeout set to %d ms\r\n", timeout);
}📝 本节小结
本节介绍了 OneOS 中 I2C 设备驱动的使用方法,包括:
- 双层抽象设计:I2C 总线设备(
os_i2c_bus_device)与 I2C 客户端(os_i2c_client)分离,实现总线资源与设备逻辑的解耦 - 消息传输机制:通过
os_i2c_msg结构体描述一次原子传输,支持多条消息的批量传输 - 标志位体系:读写方向、地址模式、传输控制等多维度标志位,灵活控制 I2C 传输行为
- 客户端接口:
os_i2c_client_write/read封装了"先写寄存器地址再传输数据"的常见模式,支持 1/2/4 字节寄存器地址 - 主机接口:
os_i2c_master_send/recv提供简洁的主机发送/接收接口 - 总线控制:通过
os_device_control可动态配置超时时间、地址模式等参数
I2C 是嵌入式系统中连接传感器、存储器、显示器等外设最常用的总线之一,掌握 I2C 设备驱动是进行外设开发的关键技能。