Feedback
Feedback

If you are having issues with the exercises, please create a ticket on DevZone: devzone.nordicsemi.com
Drag & Drop Files, Choose Files to Upload 您最多可以上传 2 个文件。
  • 简体中文
    Eng
    简体中文
  • I2C 驱动程序

    要学习如何在 nRF Connect SDK 中设置 I2C,我们将重点关注 I2C 控制器 API。

    启用驱动程序

    1. 通过在应用程序配置文件 prj.conf 中添加以下行来启用 I2C 驱动程序。

    CONFIG_I2C=y
    Kconfig

    2. 在源代码文件中包含 I2C API 的头文件。

    #include <zephyr/drivers/i2c.h>
    C

    初始化设备

    与第 2 课中介绍的 GPIO 驱动程序一样,Zephyr 中的通用 I2C 驱动程序具有一个特定于 API 的结构体 i2c_dt_spec,其签名如下:

    该结构体包含 I2C 总线 const struct device *bus 的设备指针和目标地址 uint16_t addr。

    要检索此结构体,我们需要使用特定于 API 的函数 I2C_DT_SPEC_GET(),其签名如下:

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

    如果板卡的设备树中尚未定义传感器,则需要使用设备树覆盖文件手动将传感器作为子设备树节点添加到 I2C 控制器中。

    重要

    此步骤取决于您的板卡文件中是否已定义 I2C 目标设备。对于 Thingy:91 和 Thingy:91 X,传感器已定义为其 I2C 控制器的子节点。例如,请参阅 <install_path>\nrf\boards\nordic\thingy91\thingy91_nrf9160_common.dts 中提供的 Thingy:91 设备树文件。BME680 和 BH1749 传感器已作为子节点添加到它们连接的 i2c2 控制器中。

    3.1 创建覆盖文件。
    正如我们在第 3 课中所学,从详情视图中展开 Config files->Devicetree 并创建一个覆盖文件。

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

    您也可以手动创建覆盖文件。文件必须以 .overlay 扩展名结尾,并且名称应与设备的板卡目标匹配。建议将覆盖文件存储在应用程序文件夹中名为“boards”的专用子文件夹中。但是,将它们保存在应用程序的根目录中也是可以接受的。

    例如,如果您的板卡目标是 nrf52840dk/nrf52840,则相应的覆盖文件应命名为 nrf52840dk_nrf52840.overlay。基本上是将 / 替换为 _ 。

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

    根据所使用的 Nordic 芯片,可能会有多个 I2C 控制器,因此请务必选择连接到传感器的控制器。

    这可以通过检查板卡或开发套件的原理图来确认,以找到连接到 SDA 和 SCL 的引脚。我们将在本课的练习部分对此进行检查。您可以使用 VS Code 中的 DeviceTree 查看器来显示可用 I2C 控制器的设备树节点,它会告诉您控制器使用了哪些引脚。

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

    您至少需要指定 compatible 属性、I2C 目标设备的地址及其标签。

    &i2c0 {
        mysensor: mysensor@4a{
            compatible = "i2c-device";
            reg = < 0x4a >;
            label = "MYSENSOR";
        };
    };
    Devicetree

    注意,对于 compatible 成员,如果 SDK 中存在该传感器的驱动程序,您可以指定它。但是,本课的重点是原始 I2C 事务。

    4. 定义节点标识符

    下面这行代码使用设备树宏 DT_NODELABEL() 来获取节点标识符符号 I2C0_NODE,它将代表 I2C 硬件控制器 i2c0。

    #define I2C0_NODE DT_NODELABEL(mysensor)
    C

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

    nRF Connect for VS Code 中的 DeviceTree 视图
    &i2c0 节点的 SDA 和 SCL 引脚

    5. 检索特定于 API 的设备结构体。

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

    static const struct i2c_dt_spec dev_i2c = I2C_DT_SPEC_GET(I2C0_NODE);
    C

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

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

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

    I2C 写入

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

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

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

    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]);
    }
    C

    I2C 读取

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

    此函数要求您首先对要读取的内部寄存器调用 i2c_write_dt()。

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

    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]);
    }
    C

    或者,您可以使用 i2c_reg_read_byte_dt(),它将寄存器地址写入和数据读取合并为一个函数调用。此函数非常适合从设备直接读取单个字节。

    从 I2C 设备读取数据的另一种方法是使用 i2c_burst_read_dt() 函数。此函数按顺序从多个寄存器读取数据。其签名如下:

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

    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));
    C

    本课的练习 2 中将更详细地使用和解释此函数。

    I2C 写入/读取

    对于 I2C 设备,通常使用函数 i2c_write_read_dt() 连续执行写入和读取数据操作,其签名如下:

    这种情况的一个常见场景是先写入要读取的内部寄存器的地址,然后紧接着进行读取以获取该寄存器的内容。我们将在本课的练习部分更详细地演示这一点。

    例如,以下代码片段将值 sensor_regs[0] = 0x02 写入地址为 0x4A 的 I2C 设备,然后从该设备读取 1 个字节并将其保存在变量 temp_reading[0] 中。

    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]);
    }
    C

    Switch language?

    Progress is tracked separately for each language. Switching will continue from your progress in that language or start fresh if you haven't begun.

    Your current progress is saved, and you can switch back anytime.

    Register an account
    Already have an account? Log in
    (All fields are required unless specified optional)

    • 8 or more characters
    • Upper and lower case letters
    • At least one number or special character

    Forgot your password?
    Enter the email associated with your account, and we will send you a link to reset your password.