在嵌入式开发中,传感器数据采集是核心场景之一,而 I2C(Inter-Integrated Circuit)作为仅需两根线的同步串行通信协议,因接线简洁、支持多设备挂载,成为传感器与 MCU 通信的首选方案。nRF Connect SDK 基础课程第六课串行通信(I2C)聚焦 I2C 协议基础与 SDK 驱动实战,通过两个适配不同硬件的实操练习,让开发者掌握直接使用 I2C API 与传感器交互的能力(而非依赖封装好的传感器驱动)。
课程定位:掌握传感器 I2C 通信核心能力
在前序课程中,我们已掌握 UART 串口通信的异步驱动开发,而 I2C 作为传感器专属通信协议,应用场景更为聚焦。本课作为传感器通信入门课,核心目标是让开发者理解:
- I2C 协议的双线通信原理、速率与寻址机制
- nRF Connect SDK 中 I2C 驱动的启用与配置方法
- 如何通过 I2C API 直接读写传感器寄存器
- 实战:与 BH1749 颜色传感器或 BME280 环境传感器完成数据交互
本课面向嵌入式软件工程师、固件开发者及传感器应用爱好者,需注意两个练习适配不同硬件(普通 DK 适配 BME280,Thingy 系列适配 BH1749),完成任一即可掌握 I2C 核心开发能力。
核心学习目标
完成本课学习后,你将能够:
- 理解 I2C 协议的双线结构(SCL/SDA)、通信速率与设备寻址机制
- 在 nRF Connect SDK 中启用 I2C 驱动,通过设备树获取 I2C 节点信息
- 熟练使用 I2C 核心 API(i2c_write_dt()、i2c_burst_read_dt())读写传感器寄存器
- 独立完成传感器初始化配置与数据采集流程
- 适配不同硬件平台(普通 DK/Thingy 系列)完成 I2C 通信实战
- 理解 I2C 多设备挂载的核心原理与地址冲突规避方法
核心知识模块:I2C 协议基础与 SDK 驱动核心
本课核心知识围绕 “I2C 协议原理 - SDK 驱动配置 - API 使用” 三层展开,所有知识点均为传感器通信实战服务,无额外冗余内容。
模块一:I2C 协议基础
I2C 是双线同步串行通信协议(又称两线接口 TWI),专为短距离板内设备互联设计,所有 Nordic SoC 均至少集成一个 I2C 控制器,支持两种工作角色:
- 控制器(Controller):发起通信、控制时钟信号(SCL)
- 目标设备(Target):响应控制器指令,被动传输数据(如传感器)
1. 核心硬件特性
- 通信线路:仅需两根线 —— 串行时钟线(SCL)和串行数据线(SDA),所有设备共接这两根线
- 通信速率:支持三种标准速率,默认 100 kbps
- 标准速率(I2C_BITRATE_STANDARD):100 kbps
- 快速速率(I2C_BITRATE_FAST):400 kbps
- 高速速率(I2C_BITRATE_FAST_PLUS):1000 kbps
- 设备寻址:每个目标设备拥有唯一地址,默认 7 位地址(部分设备支持 10 位地址),用于控制器区分总线上的不同设备
2. 通信核心原理
- SCL 由控制器生成,同步所有设备的通信时序;
- SDA 为双向数据线,数据传输方向由控制器指令决定(读 / 写);
- 支持多设备挂载:同一 I2C 总线上可连接多个目标设备,通过唯一地址区分,无需额外增加通信线路。
模块二:nRF Connect SDK I2C 驱动核心
nRF Connect SDK 基于 Zephyr RTOS 提供标准化 I2C 驱动,核心开发流程与 GPIO、UART 保持一致,遵循 “启用驱动 - 获取设备 - 配置参数 - 调用 API” 的统一范式。
1. 驱动启用(prj.conf)
需在工程配置文件中启用 I2C 驱动,若使用 TF-M 安全固件,需额外禁用冲突的 UART 日志:
# 基础I2C驱动启用
CONFIG_I2C=y
# 支持浮点格式打印(用于传感器数据输出)
CONFIG_CBPRINTF_FP_SUPPORT=y
# TF-M环境下禁用安全UART(避免地址冲突)
CONFIG_TFM_SECURE_UART=n
CONFIG_TFM_LOG_LEVEL_SILENCE=y
2. 头文件引入
#include <zephyr/drivers/i2c.h> // I2C核心API
#include <zephyr/sys/printk.h> // 控制台打印
3. 设备树相关操作
I2C 设备的总线信息、传感器地址等已在设备树中定义(内置传感器)或需通过 overlay 文件配置(外置传感器),核心操作如下:
- 通过节点标签获取 I2C 节点标识:DT_NODELABEL(bh1749)(以 BH1749 为例)
- 获取 API 专属设备结构:I2C_DT_SPEC_GET(I2C_NODE),封装总线指针、设备地址等信息
- 设备就绪检查:device_is_ready(dev_i2c.bus),确保 I2C 总线可正常使用
4. 核心 API 介绍
本课重点使用两个核心 API 完成寄存器读写,适配传感器通信场景:
- 寄存器写操作:i2c_write_dt(const struct i2c_dt_spec *spec, const void *buf, size_t len)
- 功能:向指定 I2C 设备的目标寄存器写入数据
- 参数:设备规格(spec)、数据缓冲区(buf,含寄存器地址 + 数据)、数据长度(len)
- 寄存器连续读操作:i2c_burst_read_dt(const struct i2c_dt_spec *spec, uint8_t reg, void *buf, size_t len)
- 功能:从指定寄存器开始,连续读取多个字节数据(适用于传感器多字节数据寄存器)
- 参数:设备规格(spec)、起始寄存器地址(reg)、接收缓冲区(buf)、读取长度(len)
经典案例拆解:BH1749 颜色传感器 I2C 通信
本课以 Thingy:53/Thingy:91 内置的 BH1749 颜色传感器为核心案例,该传感器通过 I2C 接口输出 RGB(红、绿、蓝)三色光强数据,每个颜色值占 2 字节,通信流程分为 “传感器初始化 - 数据采集 - 数据解析” 三步。
通信核心逻辑
- 初始化:向 MODE_CONTROL1 寄存器写入配置值(设置增益、测量周期),向 MODE_CONTROL2 寄存器写入使能指令(开启 RGB 测量);
- 数据采集:从 RED_DATA_LSB 寄存器开始,连续读取 6 字节数据(RGB 各 2 字节,LSB+MSB);
- 数据解析:将每个颜色的高低字节拼接为 16 位数值,输出实际光强数据。
实操练习:Thingy 系列板载 BH1749 传感器数据读取
本练习适配 Thingy:53/Thingy:91(Thingy:91 X 不支持),完整保留官方课程代码块与步骤,实操性极强。
步骤 1:工程准备
- 从课程 GitHub 仓库获取基础代码,路径为l6/l6_e2;
- 按前文要求配置prj.conf,启用 I2C 驱动与浮点打印支持。
步骤 2:引入头文件与定义寄存器地址
#include <zephyr/drivers/i2c.h>
#include <zephyr/sys/printk.h>
// BH1749核心寄存器地址(来自传感器 datasheet)
#define BH1749_SYSTEM_CONTROL 0x40
#define BH1749_MODE_CONTROL1 0x41
#define BH1749_MODE_CONTROL2 0x42
#define BH1749_RED_DATA_LSB 0x50
// 配置常量
#define BH1749_MODE_CONTROL2_RGB_EN_ENABLE BIT(4) // 开启RGB测量
#define BH1749_MODE_CONTROL1_DEFAULTS 0x2A // IR/RGB增益x1,测量周期120ms
步骤 3:获取 I2C 设备并检查就绪状态
// 获取BH1749传感器的I2C节点标识
#define I2C_NODE DT_NODELABEL(bh1749)
// 从设备树获取I2C设备规格(总线、地址等)
static const struct i2c_dt_spec dev_i2c = I2C_DT_SPEC_GET(I2C_NODE);
// 检查I2C总线是否就绪
if (!device_is_ready(dev_i2c.bus)) {
printk("I2C bus %s is not ready!\n\r", dev_i2c.bus->name);
return -1;
}
步骤 4:初始化 BH1749 传感器
通过 I2C 写入操作配置传感器工作模式:
int ret;
// 配置MODE_CONTROL1寄存器(设置增益与测量周期)
char buff1[] = {BH1749_MODE_CONTROL1, BH1749_MODE_CONTROL1_DEFAULTS};
ret = i2c_write_dt(&dev_i2c, buff1, sizeof(buff1));
if (ret != 0) {
printk("Failed to write to I2C device address 0x%x at Reg. 0x%x\n", dev_i2c.addr, BH1749_MODE_CONTROL1);
return ret;
}
// 配置MODE_CONTROL2寄存器(开启RGB测量)
char buff2[] = {BH1749_MODE_CONTROL2, BH1749_MODE_CONTROL2_RGB_EN_ENABLE};
ret = i2c_write_dt(&dev_i2c, buff2, sizeof(buff2));
if (ret != 0) {
printk("Failed to write to I2C device address 0x%x at Reg. 0x%x\n", dev_i2c.addr, BH1749_MODE_CONTROL2);
return ret;
}
步骤 5:采集并解析 RGB 数据
通过连续读操作获取 6 字节数据,拼接为 RGB 实际值并打印:
uint8_t rgb_value[6] = {0}; // 存储RGB各2字节数据(LSB+MSB)
// 从RED_DATA_LSB寄存器开始,连续读取6字节
ret = i2c_burst_read_dt(&dev_i2c, BH1749_RED_DATA_LSB, rgb_value, sizeof(rgb_value));
if (ret != 0) {
printk("Failed to read from I2C device address 0x%x at Reg. 0x%x\n", dev_i2c.addr, BH1749_RED_DATA_LSB);
return ret;
}
// 拼接高低字节,输出RGB值(格式:LSB | (MSB << 8))
printk("_______________________________\n");
printk("Red Value:\t %d\n", (rgb_value[0] | rgb_value[1] << 8));
printk("Green Value:\t %d\n", (rgb_value[2] | rgb_value[3] << 8));
printk("Blue Value:\t %d\n", (rgb_value[4] | rgb_value[5] << 8));
步骤 6:编译烧录与测试
- 编译配置:Thingy:91 选择目标板thingy91_nrf9160_ns,Thingy:53 选择thingy53_nrf5340_cpuapp_ns;
- 烧录程序到开发板;
- 测试验证:通过串口终端查看 RGB 数据输出,遮挡传感器或改变环境光颜色,观察数据变化(遮挡时数值显著降低,特定颜色光下对应通道数值升高)。
补充:BME280 传感器练习适配说明
若使用普通 Nordic DK(如 nRF54L15 DK),可选择 Exercise 1(BME280 环境传感器),核心差异如下:
- 硬件要求:需外接支持 I2C 的 BME280 传感器板(如 Waveshare 15231、Adafruit BME280);
- 接线方式:DK 的 P1.11(SCL)、P1.12(SDA)连接传感器对应引脚,同时连接 VCC 与 GND;
- 核心流程:通过 I2C 配置 BME280 工作模式(正常模式 / 强制模式),连续读取温度、气压、湿度寄存器数据,使用传感器内置校准参数计算实际值。
课后测评
本课配套课后测验,核心考察内容包括:
- I2C 协议的线路组成、通信速率与寻址机制;
- nRF Connect SDK 中 I2C 驱动的启用与设备树操作;
- i2c_write_dt()与i2c_burst_read_dt()的参数含义与使用场景;
- 传感器寄存器读写的核心流程;
- 不同 I2C 设备的地址冲突规避方法。
通过测评可快速检验核心知识点掌握情况,查漏补缺。
本课学习价值
I2C 协议是绝大多数传感器(如温湿度、加速度、颜色、气压传感器)的标准通信接口,本课教授的 “直接操作 I2C API 读写寄存器” 的方法,是理解传感器工作原理的核心途径 —— 相比使用封装好的传感器驱动,这种方式能让开发者更灵活地配置传感器参数、适配特殊场景。
掌握本课内容后,你将具备与任意 I2C 接口传感器通信的能力,可直接应用于物联网数据采集、工业监测、智能硬件等场景,同时为后续 SPI 协议学习、多传感器协同采集打下基础。