串行通讯(I2C)

概述

与传感器和外部组件通信是微控制器非常常见的任务。集成电路(12C)总线是一种常用的协议,用于与外部组件通信。在本课中,我们将讲解12C和学习如何使用nRFConnect SDK中的I2C外设驱动与外部传感器接口。通常,在nRFConnect SDK/Zephyr中,传感器驱动程序作为独立模块"设备驱动程序"开发。不过,在本课中,我们重点学习的是I2CAPl,因此我们将直接通过应用与传感器交互。

目标

  • 学习如何在nRFConnectSDK中使用I2C驱动程序
  • 通过动手练习实践使用I12C驱动API
  • 使用I12C总线与外部传感器通信

I2C协议

12C是一种广泛应用的2线同步串行通信协议。它常用于将微控制器与各种传感器和集成电路(包括其他微控制器)进行接口连接。由于它采用2线结构,因此I2C协议也被称为"双线接口"(TWI)

正如其名所示,该产品旨在实现短距离范围内的互联,通常仅限于设备内的印制电路板(PCB)内。所有北欧SoC都至少配备有1个12C控制器,该控制器可承担两种角色:作为"控制器"发起交易并控制时钟运行;或作为"目标"响应交易指令

Nordic芯片上的大多数12C控制器支持多种速度:100 (12C_BITRATE STANDARD)、400(12C_BITRATE_FAST) 和1000(12C_BITRATE_FAST_PLUS) kbps. 默i认速度为100 kb/s.

两条I2C线被称为串行时钟(SCL)和串行数据(SDA)

。总线上的所有设备都连接到这两条线上,如下图所示。

SCL是由I2C控制器通过同步总线上的所有设备至同一时钟信号来生成的,而SDA线路则为双向的,因此数据可以双向传输(从控制器至目标或从目标至控制器)。

每一台I2C目标设备都拥有独一无二的地址,这使其有别于同一总线上的其他12C目标设备。该地址通常为7位值,不过部分12C目标设备也会采用10位值。

I2C驱动

为了学习如何在nRF ConnectSDK中设置12C,我们将重点介绍I12C控制器APl。

使能I2C驱动

  1. 通过将以下行添加到应用程序配置文件prj.conf中来启用I2C驱动程序。
c 复制代码
CONFIG_I2C=y
  1. 在源代码文件中包含12CAPL的头文件。
c 复制代码
#include <zephyr/drivers/i2c.h>

正在初始化设备

就像在第2课中讨论的GPIO驱动器一样,Zephyr中的通用12C驱动程序有一个特定于API的数据结构i2c_dt_spec,其签名如下:

c 复制代码
/**
 * @brief Complete I2C DT information
 *
 * @param bus is the I2C bus
 * @param addr is the target address
 */
struct i2c_dt_spec {
	const struct device *bus;
	uint16_t addr;
};

要获取此结构,我们需要使用API特定函数I2C_DT_SPEC_GET(),其签名如下:

c 复制代码
/**
 * @brief Structure initializer for i2c_dt_spec from devicetree
 *
 * This helper macro expands to a static initializer for a <tt>struct
 * i2c_dt_spec</tt> by reading the relevant bus and address data from
 * the devicetree.
 *
 * @param node_id Devicetree node identifier for the I2C device whose
 *                struct i2c_dt_spec to create an initializer for
 */
#define I2C_DT_SPEC_GET(node_id)					\
	{								\
		COND_CODE_1(DT_ON_BUS(node_id, i3c),			\
			    (I2C_DT_SPEC_GET_ON_I3C(node_id)),		\
			    (I2C_DT_SPEC_GET_ON_I2C(node_id)))		\
	}

3.指定您的设备(传感器)连接到哪个I2C控制器及其I2C地址。

如果传感器尚未在板子的devicetree中定义,您需要手动将您的传感器作为子devicetree节点添加到i2c控制器中,使用devicetree覆盖文件。

3.1创建 overlay文件

正如我们在第3课中学到的,从详细视图中展开配置文件一设备树,并创建一个覆盖文件。

这将在您的应用程序根目录中创建一个空的覆盖文件。

你亦可通过手动方式创建你自己的覆盖文件。这些文件必须以.overlay扩展名结尾,且名称需与设备对应的电路板目标相匹配。建议将你的覆盖文件存储在应用程序文件夹中一个名为boards"的专用子文件夹内。不过,将其保存在应用程序根目录下也是可以接受的。

32在覆盖文件中,指定您的传感器所连接的I2C控制器及其地址。

根据使用的Nordic芯片,可能存在多个I2C控制器,因此请确保选择与您的传感器连接的控制器。

这可以通过查看你所用电路板或开发工具的原理图来确认,以找到与SDA和SCL相连的引脚。我们将在本课的练习部分对此进行探讨。你可以在VSCode中使用"设备树查看器"来显示可用I2C控制器对应的设备树节点,这有助于你了解控制器所使用的具体引脚

在下图中,我们假设传感器已连接到i2c0控制器,并具有目标地址0x4a(来自传感器数据手册),并将其标记为mysensor。

至少需要指定兼容性、12C目标设备的地址及其标签。

c 复制代码
&i2c0 {
    mysensor: mysensor@4a{
        compatible = "i2c-device";
        reg = < 0x4a >;
        label = "MYSENSOR";
    };
};

注意,对于兼容成员,如果SDK中存在传感器驱动程序,可以指定其驱动程序。然而,本课程的重点是原始I2C事务。

4.定义节点标识符

下面的行使用了devicetree宏DT_NODELABEL()来获取节点标识符符号I2CO_NODE,该符号将代表硬件控制器i2c0。

c 复制代码
#define I2C0_NODE DT_NODELABEL(mysensor)

I2C0_NODE 包含用于SDA和SCL的引脚信息、I2C控制器的内存映射、默认的I2C频率以及目标设备的地址。

5.获取I2C设备结构体

宏调用I2C_DT_SPEC_GET()返回结构体i2c_dt_spec,其中包含I2C总线的设备指针以及目标地址。

c 复制代码
static const struct i2c_dt_spec dev_i2c = I2C_DT_SPEC_GET(I2C0_NODE);

6.使用device_is_ready()来验证设备是否已准备好使用。

c 复制代码
if (!device_is_ready(dev_i2c.bus)) {
	printk("I2C bus %s is not ready!\n\r",dev_i2c.bus->name);
	return;
}

我们现在有一个设备结构体i2c_dt_spec*dev_i2c,可以将其传递给12C通用API接口以执行读写操作

I2C Write

向目标设备写入数据的最简单方法是通过函数i2c_write_dt(),其签名如下:

例如,以下代码片段会同步向12C目标设备写入2字节。

具体来说,我们正在将值0x8C写入I2C设备的内部寄存器0x03。

c 复制代码
uint8_t config[2] = {0x03,0x8C};
ret = i2c_write_dt(&dev_i2c, config, sizeof(config));
if(ret != 0){
	printk("Failed to write to I2C device address %x at reg. %x \n\r", dev_i2c.addr,config[0]);
}

I2C Read

从I12C目标设备读取数据的最简单方法是通过函数i2c_read_dt(),其签名如下:

此函数要求您首先调用i2c_write_dt()来读取您想要从内部寄存器中读取的数据。

例如,以下代码片段会同步地从I2C目标设备内部的寄存器0x03中读取1个字节,前提是我们在调用此代码片段之前已调用了先前的写入代码片段。

c 复制代码
uint8_t data;
ret = i2c_read_dt(&dev_i2c, &data, sizeof(data));
if(ret != 0){
	printk("Failed to read from I2C device address %x at Reg. %x \n\r", dev_i2c.addr,config[0]);
}

此外,你还可以使用i2c_reg_read_byte_dt()函数,该函数将寄存器地址写入和数据读取合并在一个函数调用中。此函数特别适用于直接从设备中简单读取单个字节的情况。

从I2C设备读取数据的另一种方法是使用i2c_burst_read_dt()函数。该函数可依次从多个寄存器中读取数据,其函数签名如下:

例如,以下代码片段从地址"BH1749_RED_DATA_LSB"所指向的寄存器的第一个字节开始,读取来自3个寄存器/6字节的数据。

c 复制代码
uint8_t rgb_value[6]= {0};
//Do a burst read of 6 bytes as each color channel is 2 bytes
ret = i2c_burst_read_dt(&dev_i2c, BH1749_RED_DATA_LSB,rgb_value,sizeof(rgb_value));

该函数在本课的练习2中被使用并进行了更详细的解释。

I2C Write/Read

在I2C设备中,使用函数i2c_write_read_dt()进行写入和读取数据的来回操作非常常见,其签名如下:

通常的做法是首先将一个内部寄存器的地址写入,以便进行读取操作,随后直接进行读取以获取该寄存器的内容。我们将在本课程练习部分对此进行更为详尽的演示。

例如,以下代码片段会写入值sensor_regs0将值0x02发送至地址为Ox4A的12C设备,然后从该设备读取1个字节并将其保存至变量temp_reading中0.

c 复制代码
uint8_t sensor_regs[2] ={0x02,0x00};
uint8_t temp_reading[2]= {0};	
int ret = i2c_write_read_dt(&dev_i2c,&sensor_regs[0],1,&temp_reading[0],1);
if(ret != 0){
	printk("Failed to write/read I2C device address %x at Reg. %x \n\r", dev_i2c.addr,sensor_regs[0]);
}

练习1

连接BME280温度传感器

在本次实验中,我们将使用搭载于Waveshare 15231板上的BME280传感器来获取温度读数。我们选择了一款支持I2C和SPI通信协议的扩展板,以便在多个课程中使用该传感器。下图展示了BME280模块和该扩展板的外观。

Waveshare 15231板件可通过现有的一种引脚头连接器轻松连接到我们任意一款开发套件上。传感器板件应通过Pl连接器与DK板(此处为nRF54L15)相连。Pin Pl.ll将被配置为SCL,而P1.12为SDA。VDDIO和GND应当被连接为VCC和GND,分别接入传感器板件。

BME280传感器提供三种传感器模式:睡眠模式、强制模式和正常模式。在给传感器通电后,默认情况下它会进入睡眠模式。我们应该配置传感器在通电后进入正常或强制模式,也就是说,在读取值之前需要配置传感器的模式位。

与BME280的整个通信通过读写宽度为8位的寄存器来完成。BME280的内存映射如下所示:

部分寄存器已被保留,不应被更改。校准数据是只读的,因为这些值在制造时就已固定控制寄存器具有读写功能,用于控制设置。数据寄存器是传感器在完成传感操作后返回或存储的值,因此为只读。状态和芯片标识也是只读的,而复位则为只写。

数据寄存器可提供20位压力值、20位温度值以及16位湿度值。由于所有寄存器均为8位宽,我们将执行多字节读取操作,然后通过将其位/字节置于适当位置来构建这些值。为了在转换后读取数据值,建议采用突发模式,而非逐一单独寻址每个寄存器。数据读取过程始于从0xF7到OxFC(用于温度和压力)或从0xF7到0xFE(用于温度、压力及湿度)开始的突发读取。

数据以未签名的20位格式读取,既适用于温度和压力测量,也适用于未签名的16位格式读取湿度值。这些读取值代表了未经补偿的测量结果。随后,实际温度、压力及湿度值会通过使用补偿参数进行计算得出。这些补偿参数在生产时即被存储于设备的非易失性存储器中。这些补偿参数的字段地址、内容及数据类型均列于下表所示。

练习2

相关推荐
ScilogyHunter11 天前
Zephyr串口驱动开发及构建完全指南
驱动开发·uart·zephyr
ScilogyHunter11 天前
Zephyr Hello World应用开发构建完全指南
zephyr·hello world
ScilogyHunter11 天前
Zephyr Twister测试框架完全指南
zephyr·twister
ScilogyHunter12 天前
west init 命令详解
init·zephyr·west
ScilogyHunter12 天前
使用Kconfig配置Zephyr工程完全指南
kconfig·zephyr
ScilogyHunter12 天前
Zephyr设备树完全指南
zephyr
ScilogyHunter13 天前
Zephyr项目按需配置完全指南
zephyr
ScilogyHunter13 天前
Zephyr最简工程配置指南
zephyr
ScilogyHunter13 天前
Zephyr主仓库目录结构完全指南
zephyr
ScilogyHunter13 天前
Zephyr工程配置完全指南
zephyr