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
    简体中文
  • 练习 1 – 连接 BME280 温度传感器

    连接 BME280 温度传感器

    在本练习中,我们将使用搭载在 Waveshare 15231 分线板上的 BME280 传感器来提供温度读数。我们选择了一款支持 I2C 和 SPI 通信协议的分线板,以便在多个课程中使用该传感器。下图显示了 BME280 模块和分线板。

    Waveshare 15231 分线板

    注意

    Thingy 不支持本练习。如果您使用的是 Thingy 原型设计平台(Thingy:53、Thingy:91、Thingy:91 X),请参阅本课的练习 2。

    Waveshare 15231 板可以通过现有的排针连接器轻松连接到我们的任何开发套件。传感器板应使用 P1 连接器连接到 DK 板(本例中为 nRF54L15)。引脚 P1.11 将配置为 SCL,P1.12 配置为 SDA。VDDIO 和 GND 应作为 VCC 和 GND 连接到传感器板。

    nRF54L15DK 板上的 P1 连接器

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

    与 BME280 的所有通信都是通过读取和写入 8 位宽的寄存器来完成的。BME280 的内存映射如下所示:

    BME280 寄存器内存映射

    某些寄存器是保留的,不应更改。校准数据是只读的,因为这些值在生产时已经固定。控制寄存器具有读写能力,用于控制设置。数据寄存器是传感器执行感测操作后返回或存储的数值,因此是只读的。状态和芯片 ID 也是只读的,而复位是只写的。

    数据寄存器提供 20 位压力值、20 位温度值和 16 位湿度值。由于所有寄存器都是 8 位宽,我们将执行多字节读取操作,然后通过将位/字节放在适当的位置来构建数值。要在转换后读取数据值,建议使用突发模式(burst mode),而不是单独寻址每个寄存器。数据读取通过从 0xF7 到 0xFC(针对温度和压力)或从 0xF7 到 0xFE(针对温度、压力和湿度)启动突发读取来完成。

    温度和压力数据以无符号 20 位格式读取,湿度以无符号 16 位格式读取。这些读取值代表未补偿测量值。实际的温度、压力和湿度值随后使用补偿参数计算得出。这些补偿参数在生产时存储在设备的非易失性存储器中。下表显示了这些补偿参数的寄存器地址、内容和数据类型。

    BME280 补偿参数寄存器地址

    练习步骤

    1. 在本课程的 GitHub 仓库中,打开位于 l6/l6_e1 的本练习基础代码。请注意,BME280 传感器用于 v2.7.0 及以上版本。

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

    CONFIG_I2C=y
    Kconfig

    3. 在 main.c 中,包含 I2C API 的头文件。

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

    4. 为了在控制台上显示传感器读数,我们将使用一个简单的 printk()。

    4.1 包含头文件 <sys/printk.h> 以使用 printk()。

    #include <zephyr/sys/printk.h>
    C

    4.2 向 prj.conf 添加以下配置选项以启用对浮点格式说明符的支持,这样我们就可以将温度读数打印为浮点数。默认不启用此选项的原因是为了节省内存空间,因为启用此选项会使代码大小增加至少 1KB。

    CONFIG_CBPRINTF_FP_SUPPORT=y
    Kconfig

    5. 设备树(Devicetree)准备

    5.1 创建一个名为 “boards” 的文件夹,并将其放在本练习的目录中,如下所示。

    5.2 为开发板添加覆盖文件(overlay file)

    注意

    如果您使用的是 nRF54L15DK 以外的开发套件,请使用下方选项卡中相应的覆盖内容,并根据您的特定目标板命名覆盖文件。

    由于传感器位于板卡外部,我们必须添加一个覆盖文件来指定该外部传感器是哪个 i2c 节点的子节点,并指定传感器的地址。我们还需要启用 i2c 节点并配置引脚。

    正如我们在上一主题(I2C 驱动程序)中所解释的,创建一个覆盖文件,命名格式为 [board]_[soc].overlay](对于 nRF54L15 DK,它将是 nrf54l15dk_nrf54l15_cpuapp.overlay),并在覆盖文件中添加以下代码。我们在 0x77 中获取它的方式将在步骤 5.3 中解释。

    &i2c21 {
        status = "okay";
        pinctrl-0 = <&i2c21_default>;
    	pinctrl-1 = <&i2c21_sleep>;
        pinctrl-names = "default", "sleep";
        mysensor: mysensor@77{
            compatible = "i2c-device";
            status = "okay";
            reg = < 0x77 >;
        };
    };
    
    &pinctrl {
    	/omit-if-no-ref/ i2c21_default: i2c21_default {
    		group1 {
    			psels = <NRF_PSEL(TWIM_SCL, 1, 11)>,
    				    <NRF_PSEL(TWIM_SDA, 1, 12)>;
    		};
    	};
        /omit-if-no-ref/ i2c21_sleep: i2c21_sleep {
    		group1 {
    			psels = <NRF_PSEL(TWIM_SCL, 1, 11)>,
    				    <NRF_PSEL(TWIM_SDA, 1, 12)>;
    		};
    	};
    };
    Devicetree
    &i2c22 {
        status = "okay";
        pinctrl-0 = <&i2c22_default>;
    	  pinctrl-1 = <&i2c22_sleep>;
        pinctrl-names = "default", "sleep";
        mysensor: mysensor@77{
            compatible = "i2c-device";
            status = "okay";
            reg = < 0x77 >;
        };
    };
    
    &pinctrl {
    	/omit-if-no-ref/ i2c22_default: i2c22_default {
    		group1  {
    			psels = <NRF_PSEL(TWIM_SCL, 1, 11)>,
    					<NRF_PSEL(TWIM_SDA, 1, 12)>;
    		};
    	};
    
    	/omit-if-no-ref/ i2c22_sleep: i2c22_sleep {
    		group1  {
    			psels = <NRF_PSEL(TWIM_SCL, 1, 11)>,
    					<NRF_PSEL(TWIM_SDA, 1, 12)>;
    			low-power-enable;
    		};
    	};
    };
    
    Devicetree
    
    &i2c21 {
        status = "okay";
        pinctrl-0 = <&i2c21_default>;
    	pinctrl-1 = <&i2c21_sleep>;
        pinctrl-names = "default", "sleep";
        mysensor: mysensor@77{
            compatible = "i2c-device";
            status = "okay";
            reg = < 0x77 >;
        };
    };
    
    &pinctrl {
    	/omit-if-no-ref/ i2c21_default: i2c21_default {
    		group1 {
    			psels = <NRF_PSEL(TWIM_SCL, 1, 11)>,
    				    <NRF_PSEL(TWIM_SDA, 1, 12)>;
    		};
    	};
        /omit-if-no-ref/ i2c21_sleep: i2c21_sleep {
    		group1 {
    			psels = <NRF_PSEL(TWIM_SCL, 1, 11)>,
    				    <NRF_PSEL(TWIM_SDA, 1, 12)>;
    		};
    	};
    };
    
    Devicetree
    
    &i2c21 {
        status = "okay";
        pinctrl-0 = <&i2c21_default>;
    	pinctrl-1 = <&i2c21_sleep>;
        pinctrl-names = "default", "sleep";
        mysensor: mysensor@77{
            compatible = "i2c-device";
            status = "okay";
            reg = < 0x77 >;
        };
    };
    
    &pinctrl {
    	/omit-if-no-ref/ i2c21_default: i2c21_default {
    		group1 {
    			psels = <NRF_PSEL(TWIM_SCL, 1, 11)>,
    				    <NRF_PSEL(TWIM_SDA, 1, 12)>;
    		};
    	};
        /omit-if-no-ref/ i2c21_sleep: i2c21_sleep {
    		group1 {
    			psels = <NRF_PSEL(TWIM_SCL, 1, 11)>,
    				    <NRF_PSEL(TWIM_SDA, 1, 12)>;
    		};
    	};
    };
    
    Devicetree
    &i2c0 {
        status = "okay";
      
        pinctrl-0 = <&i2c0_default>;
        pinctrl-1 = <&i2c0_sleep>;
        pinctrl-names = "default", "sleep";
        mysensor: mysensor@77{
            compatible = "i2c-device";
            status = "okay";
            reg = < 0x77 >;
        };
    };
    
    &pinctrl {
    	/omit-if-no-ref/ i2c0_default: i2c0_default {
    		group1  {
    			psels = <NRF_PSEL(TWIM_SCL, 0, 24)>,
    					<NRF_PSEL(TWIM_SDA, 0, 25)>;
    		};
    	};
    
    	/omit-if-no-ref/ i2c0_sleep: i2c0_sleep {
    		group1  {
    			psels = <NRF_PSEL(TWIM_SCL, 0, 24)>,
    					<NRF_PSEL(TWIM_SDA, 0, 25)>;
    			low-power-enable;
    		};
    	};
    };
    Devicetree
    &i2c0 {
        status = "okay";
        
        pinctrl-0 = <&i2c0_default>;
        pinctrl-1 = <&i2c0_sleep>;
        pinctrl-names = "default", "sleep";
        mysensor: mysensor@77{
            compatible = "i2c-device";
            status = "okay";
            reg = < 0x77 >;
        };
    };
    
    &pinctrl {
    	/omit-if-no-ref/ i2c0_default: i2c0_default {
    		group1  {
    			psels = <NRF_PSEL(TWIM_SCL, 0, 22)>,
    					<NRF_PSEL(TWIM_SDA, 0, 23)>;
    		};
    	};
    
    	/omit-if-no-ref/ i2c0_sleep: i2c0_sleep {
    		group1  {
    			psels = <NRF_PSEL(TWIM_SCL, 0, 22)>,
    					<NRF_PSEL(TWIM_SDA, 0, 23)>;
    			low-power-enable;
    		};
    	};
    };
    
    Devicetree
    &i2c0 {
        status = "okay";
    
        pinctrl-0 = <&i2c0_default>;
        pinctrl-1 = <&i2c0_sleep>;
        pinctrl-names = "default", "sleep";
        mysensor: mysensor@77{
            compatible = "i2c-device";
            status = "okay";
            reg = < 0x77 >;
        };
    };
    
    &pinctrl {
    	/omit-if-no-ref/ i2c0_default: i2c0_default {
    		group1  {
    			psels = <NRF_PSEL(TWIM_SCL, 1, 14)>,
    					<NRF_PSEL(TWIM_SDA, 1, 15)>;
    		};
    	};
    
    	/omit-if-no-ref/ i2c0_sleep: i2c0_sleep {
    		group1  {
    			psels = <NRF_PSEL(TWIM_SCL, 1, 14)>,
    					<NRF_PSEL(TWIM_SDA, 1, 15)>;
    			low-power-enable;
    		};
    	};
    };
    
    Devicetree
    &i2c2 {
        status = "okay";
        pinctrl-0 = <&i2c2_default>;
    	pinctrl-1 = <&i2c2_sleep>;
        pinctrl-names = "default", "sleep";
        mysensor: mysensor@77{
            compatible = "i2c-device";
            status = "okay";
            reg = < 0x77 >;
        };
    };
    
    &pinctrl {
    	/omit-if-no-ref/ i2c2_default: i2c2_default {
    		group1  {
    			psels = <NRF_PSEL(TWIM_SCL, 0, 12)>,
    					<NRF_PSEL(TWIM_SDA, 0, 13)>;
    		};
    	};
    
    	/omit-if-no-ref/ i2c2_sleep: i2c2_sleep {
    		group1  {
    			psels = <NRF_PSEL(TWIM_SCL, 0, 12)>,
    					<NRF_PSEL(TWIM_SDA, 0, 13)>;
    			low-power-enable;
    		};
    	};
    };
    
    Devicetree
    &i2c1 {
        status = "okay";
        pinctrl-0 = <&i2c1_default>;
    	pinctrl-1 = <&i2c1_sleep>;
        pinctrl-names = "default", "sleep";
        mysensor: mysensor@77{
            compatible = "i2c-device";
            status = "okay";
            reg = < 0x77 >;
        };
    };
    
    &pinctrl {
    	/omit-if-no-ref/ i2c1_default: i2c1_default {
    		group1  {
    			psels = <NRF_PSEL(TWIM_SCL, 1, 14)>,
    					<NRF_PSEL(TWIM_SDA, 1, 15)>;
    		};
    	};
    
    	/omit-if-no-ref/ i2c1_sleep: i2c1_sleep {
    		group1  {
    			psels = <NRF_PSEL(TWIM_SCL, 1, 14)>,
    					<NRF_PSEL(TWIM_SDA, 1, 15)>;
    			low-power-enable;
    		};
    	};
    };
    Devicetree

    &i2c2 {
        status = "okay";
        pinctrl-0 = <&i2c2_default>;
    	  pinctrl-1 = <&i2c2_sleep>;
        pinctrl-names = "default", "sleep";
        mysensor: mysensor@77{
            compatible = "i2c-device";
            status = "okay";
            reg = < 0x77 >;
        };
    };
    
    &pinctrl {
    	/omit-if-no-ref/ i2c2_default: i2c2_default {
    		group1  {
    			psels = <NRF_PSEL(TWIM_SCL, 0, 12)>,
    					    <NRF_PSEL(TWIM_SDA, 0, 13)>;
    		};
    	};
    
    	/omit-if-no-ref/ i2c2_sleep: i2c2_sleep {
    		group1  {
    			psels = <NRF_PSEL(TWIM_SCL, 0, 12)>,
    					    <NRF_PSEL(TWIM_SDA, 0, 13)>;
    			low-power-enable;
    		};
    	};
    };
    
    Devicetree

    注意

    向应用程序添加覆盖文件时,必须在烧录前运行全新构建(pristine build)。VS Code 中会弹出警告,询问您是否现在运行全新构建。

    5.3 7 位设备地址为 111011x。前 6 个 MSB 位是固定的。最后一位可通过 SDO 值更改(参见 bme280 数据手册),并可在运行期间更改。将 SDO 连接到 GND 会得到从机地址 1110110 (0x76);将其连接到 VDD/VDDIO 会得到从机地址 1110111 (0x77),这与 BMP280 的 I²C 地址相同。

    SDO 引脚在 Waveshare 15231 板上由 ADDR/MISO 表示。此引脚已上拉,因此如果连接未更改,BME280 中的 SDO 将保持高电平,从而给出 0x77 地址

    Waveshare 15231 板 ADDR/MISO 引脚。

    一些传感器供应商提供包含读/写位的 8 位地址。为了识别这一点,他们通常提供单独的写入和读取地址。在这种情况下,应仅使用地址的高 7 位。

    6. 获取传感器的节点标识符。这在 I2C 驱动程序部分的步骤 4 中有详细解释。

    #define I2C_NODE DT_NODELABEL(mysensor)
    C

    7. 获取特定于 API 的设备结构,并确保设备已准备就绪:

    static const struct i2c_dt_spec dev_i2c = I2C_DT_SPEC_GET(I2C_NODE);
    if (!device_is_ready(dev_i2c.bus)) {
    	printk("I2C bus %s is not ready!\n\r",dev_i2c.bus->name);
    	return -1;
    }
    C

    至此,我们有了指向 I2C 控制器设备结构的指针和传感器地址(目标设备地址),可以开始使用 I2C 驱动程序 API 来配置连接到 I2C 控制器的传感器。

    8. 定义相关寄存器的地址(来自传感器数据手册)。通常这些信息会放在一个单独的头文件(.h)中。但是,为了保持演示简单,我们将它们添加到 main.c 中。

    #define CTRLMEAS 0xF4
    #define CALIB00	 0x88
    #define ID	     0xD0
    #define TEMPMSB	 0xFA
    C

    9. 我们通过读取芯片 ID 来验证是否正确连接了传感器。当数值正确时(参见 bme280 数据手册),我们知道传感器工作正常,可以进行下一步 – 读取校准寄存器

      uint8_t id = 0;
    	uint8_t regs[] = {ID};
    
    	int ret = i2c_write_read_dt(&dev_i2c, regs, 1, &id, 1);
    
    	if (ret != 0) {
    		printk("Failed to read register %x \n", regs[0]);
    		return -1;
    	}
    
    	if (id != CHIP_ID) {
    		printk("Invalid chip id! %x \n", id);
    		return -1;
    	}

    10. 读取校准寄存器。大多数补偿参数是 2 字节值。在这种情况下,我们以突发模式读取温度补偿参数寄存器(每个 2B)。

    温度补偿参数从 0x88 地址开始,包含以下 3 个寄存器。

    温度校准寄存器

    	uint8_t values[6];
    
    	int ret = i2c_burst_read_dt(spec, CALIB00, values, 6);
    
    	if (ret != 0) {
    		printk("Failed to read register %x \n", CALIB00);
    		return;
    	}
    	

    从寄存器位置读取后,我们将位/字节放在适当的位置以形成补偿参数值。

    
    	sensor_data_ptr->dig_t1 = ((uint16_t)values[1]) << 8 | values[0];
    	sensor_data_ptr->dig_t2 = ((uint16_t)values[3]) << 8 | values[2];
    	sensor_data_ptr->dig_t3 = ((uint16_t)values[5]) << 8 | values[4];

    这些补偿参数将由补偿例程与未补偿的环境读取值一起使用,以生成正确的/补偿后的输出。数据手册中提供了补偿公式和例程。有关更多信息,请参阅数据手册。我们直接使用这些例程,仅对原型和变量数据类型进行微调。

    11. 现在我们需要通过写入配置寄存器来配置传感器。根据数据手册,我们必须通过将 MODE 位设置为 b’11 将寄存器置于活动/正常模式。为了将过采样参数设置为 b’100(即 8 倍过采样),我们必须向 CTRLMEAS 寄存器写入 0x93。这些寄存器定义在 main.c 的顶部。

    BME280 配置寄存器

    	uint8_t sensor_config[] = {CTRLMEAS, SENSOR_CONFIG_VALUE};
    
    	ret = i2c_write_dt(&dev_i2c, sensor_config, 2);
    
    	if (ret != 0) {
    		printk("Failed to write register %x \n", sensor_config[0]);
    		return -1;
    	}
    C

    12. 为了获取温度传感器数据,我们需要从 TEMPMSB 开始读取以下 3 个寄存器。

    		uint8_t temp_val[3] = {0};
    
    		int ret = i2c_burst_read_dt(&dev_i2c, TEMPMSB, temp_val, 3);
    
    		if (ret != 0) {
    			printk("Failed to read register %x \n", TEMPMSB);
    			k_msleep(SLEEP_TIME_MS);
    			continue;
    		}
    C

    12.1. 我们需要将寄存器中的数据按有效顺序排列,并将它们组合成 20 位温度有符号值(未补偿)

    		int32_t adc_temp =
    			(temp_val[0] << 12) | (temp_val[1] << 4) | ((temp_val[2] >> 4) & 0x0F);

    12.2 接下来,我们可以使用之前存储的参数来补偿温度值

    int32_t comp_temp = bme280_compensate_temp(&bmedata, adc_temp);

    12.3 下一步是将它们转换为摄氏度和华氏度。

    		float temperature = (float)comp_temp / 100.0f;
    		double fTemp = (double)temperature * 1.8 + 32;
    
    		// Print reading to console
    		printk("Temperature in Celsius : %8.2f C\n", (double)temperature);
    		printk("Temperature in Fahrenheit : %.2f F\n", fTemp);

    13. 使用杜邦线将传感器板连接到您的 DK。

    导线颜色传感器板nRF54L15 DK
    红色VCCVDDIO 通过端口 P1
    黑色GNDGND 通过端口 P1
    蓝色SDA端口 P1.12
    黄色SCL端口 P1.11
    传感器板连接
    DK 连接

    13.1 配置开发板以输出正确的电压。

    在 nRF Connect for Desktop 中,安装并启动 Board Configurator。Board Configurator 是一款桌面应用程序,可让您调整 Nordic 开发套件 (DK) 上“板载控制器”的设置。

    板载控制器是运行在 DK 接口 MCU 上的固件,负责控制 DK 的运行。使用 Board Configurator,您可以轻松更改 DK 的配置。

    在左上角,点击 Select Device,将出现一个包含所有已连接设备的下拉菜单。选择 nRF54L15 DK。

    在 VDD (nPM VOUT1) 下,确保选择了 3.3V。然后选择 Write config,将当前配置写入开发板。现在,开发板将提供正确的输出电压来为扩展板供电。

    13. 使用杜邦线将传感器板连接到您的 DK。

    导线颜色传感器板nRF52 DK
    红色VCCVDD
    黑色GNDGND
    蓝色SDA端口 P0.25
    黄色SCL端口 P0.24

    13. 使用杜邦线将传感器板连接到您的 DK。

    导线颜色传感器板nRF52833 DK
    红色VCCVDD
    黑色GNDGND
    蓝色SDA端口 P0.23
    黄色SCL端口 P0.22

    13. 使用杜邦线将传感器板连接到您的 DK。

    导线颜色传感器板nRF52840 DK
    红色VCCVDD
    黑色GNDGND
    蓝色SDA端口 P1.15
    黄色SCL端口 P1.14

    13. 使用杜邦线将传感器板连接到您的 DK。

    导线颜色传感器板nRF5340 DK
    红色VCCVDD
    黑色GNDGND
    蓝色SDA端口 P1.15
    黄色SCL端口 P1.14

    13. 使用杜邦线将传感器板连接到您的 DK。

    导线颜色传感器板nRF9160 DK
    红色VCCVDD
    黑色GNDGND
    蓝色SDA端口 P0.13
    黄色SCL端口 P0.12

    13. 使用杜邦线将传感器板连接到您的 DK。

    导线颜色传感器板nRF7002 DK
    红色VCCVDD
    黑色GNDGND
    蓝色SDA端口 P1.15
    黄色SCL端口 P1.14

    13. 使用杜邦线将传感器板连接到您的 DK。

    导线颜色传感器板nRF91x1 DK
    红色VCCVDD
    黑色GNDGND
    蓝色SDA端口 P0.13
    黄色SCL端口 P0.12

    14. 构建应用程序并将其烧录到您的开发套件中。

    使用串口终端,您应该看到以下输出:

    *** Booting nRF Connect SDK ***
    Temperature in Celsius : 26.37 C
    Temperature in Fahrenheit : 79.47 F
    Temperature in Celsius : 26.37 C
    Temperature in Fahrenheit : 79.47 F
    Temperature in Celsius : 26.37 C
    Temperature in Fahrenheit : 79.47 F
    Temperature in Celsius : 26.37 C
    Temperature in Fahrenheit : 79.36 F
    Temperature in Celsius : 26.37 C
    Temperature in Fahrenheit : 79.36 F
    Terminal

    尝试对着传感器吹气,观察读数的即时变化。

    本练习的解决方案可以在 GitHub 仓库 l6/l6_e1_sol 中找到。

    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.