10浏览
查看: 10|回复: 0

[ESP8266/ESP32] FireBeetle 2 ESP32-C5 外设测试驱动BH1750光线传感器

[复制链接]
本帖最后由 御坂10032号 于 2025-10-15 22:48 编辑

简介
本文的主要内容是如何使用ESP-IDF的I2C驱动来驱动BH1750光照传感器从而实现光照强度Lux的读取。



正文

在文章开始之前,我要首先夸赞一下DF的这个底板设计,这里额外引出的I2C的pin设计的很好,三个引脚,还自带VCC和GND。可以解决非使用QWI2C线缆同时接多个I2C设备PIN不够用的情况(或者使用面包板转接,相信设计的时候也考虑到了这个问题)


FireBeetle 2 ESP32-C5 外设测试驱动BH1750光线传感器图1




LED运行状态指示

在上一篇文章中我们已经实现了LED的Toggle,我发现如果这个LED作为一个运行状态的指示非常好,于是在这次的代码中就进行了保留。

下面为LED配置的代码
  1. static void configure_led(void)
  2. {
  3.     ESP_LOGI(TAG, "Configuring GPIO15 as output with pull-up!");
  4.     /* Reset the GPIO pin */
  5.     gpio_reset_pin(BLINK_GPIO);
  6.     /* Configure GPIO as output with pull-up */
  7.     gpio_config_t io_conf = {
  8.         .intr_type = GPIO_INTR_DISABLE,
  9.         .mode = GPIO_MODE_OUTPUT,
  10.         .pin_bit_mask = (1ULL << BLINK_GPIO),
  11.         .pull_down_en = GPIO_PULLDOWN_DISABLE,
  12.         .pull_up_en = GPIO_PULLUP_ENABLE,
  13.     };
  14.     gpio_config(&io_conf);
  15.     /* Set initial state to LOW */
  16.     gpio_set_level(BLINK_GPIO, 0);
  17. }
复制代码


下面为LED闪烁的控制代码
  1. static void blink_led_task(void *pvParameter)
  2. {
  3.     /* Toggle the GPIO level */
  4.     while (1)
  5. {
  6.     ESP_LOGI(TAG, "Turning the LED %s!", s_led_state ? "ON" : "OFF");
  7.     s_led_state = !s_led_state;
  8.     gpio_set_level(BLINK_GPIO, s_led_state);
  9.     vTaskDelay(pdMS_TO_TICKS(1000)); // 1 second delay
  10.     }
  11.     /* Task should never reach here, but if it does, delete itself */
  12.     vTaskDelete(NULL);
  13. }
复制代码


然后在main方法中启动这个任务就好

  1. xTaskCreate(blink_led_task, "blink_led", 2048, NULL, 5, &led_task_handle);
复制代码



BH1750光照传感器驱动

首先我们需要根据硬件和对应的底板等来定义对应的I2C pin 和时钟(一般100K-400K都可以)

  1. #define I2C_MASTER_SCL_IO 10           // SCL时钟线连接到GPIO 10
  2. #define I2C_MASTER_SDA_IO 9            // SDA数据线连接到GPIO 9  
  3. #define I2C_MASTER_NUM I2C_NUM_0       // 使用I2C端口0
  4. #define I2C_MASTER_FREQ_HZ 100000      // I2C时钟频率100kHz(标准模式)
  5. #define I2C_MASTER_TIMEOUT_MS 1000     // I2C操作超时时间1秒
复制代码


之后根据上面定义的I2C信息来初始化I2C
  1. static esp_err_t i2c_master_init(void)
  2. {
  3.     int i2c_master_port = I2C_MASTER_NUM;
  4.     i2c_config_t conf = {
  5.         .mode = I2C_MODE_MASTER,
  6.         .sda_io_num = I2C_MASTER_SDA_IO,
  7.         .scl_io_num = I2C_MASTER_SCL_IO,
  8.         .sda_pullup_en = GPIO_PULLUP_ENABLE,
  9.         .scl_pullup_en = GPIO_PULLUP_ENABLE,
  10.         .master.clk_speed = I2C_MASTER_FREQ_HZ,
  11.     };
  12.     i2c_param_config(i2c_master_port, &conf);
  13.     return i2c_driver_install(i2c_master_port, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0);
  14. }
复制代码


需要注意的一点是这里我们初始化I2C之后初始的I2C是0,那么之后我们就可以通过I2C 0 来操作对应在这个总线上的设备了。

之后我们使用I2C Scan函数来帮助我们确定挂载在总线上的I2C设备。
  1. /**
  2. * @brief I2C scanner function
  3. */
  4. static void i2c_scanner(void)
  5. {
  6.     ESP_LOGI(TAG, "Starting I2C scan...");
  7.     uint8_t address;
  8.     esp_err_t ret;
  9.     int devices_found = 0;
  10.     for (address = 1; address < 127; address++)
  11.     {
  12.         i2c_cmd_handle_t cmd = i2c_cmd_link_create();
  13.         i2c_master_start(cmd);
  14.         i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_WRITE, true);
  15.         i2c_master_stop(cmd);
  16.         ret = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, 10 / portTICK_PERIOD_MS);
  17.         i2c_cmd_link_delete(cmd);
  18.         if (ret == ESP_OK)
  19.         {
  20.             ESP_LOGI(TAG, "Found device at address: 0x%02X", address);
  21.             devices_found++;
  22.         }
  23.     }
  24.     if (devices_found == 0)
  25.     {
  26.         ESP_LOGI(TAG, "No I2C devices found!");
  27.     }
  28.     else
  29.     {
  30.         ESP_LOGI(TAG, "Scan completed. Found %d device(s).", devices_found);
  31.     }
  32. }
复制代码

如下图,正确的扫描的I2C的设备0x23, 对应其BH1750传感器

FireBeetle 2 ESP32-C5 外设测试驱动BH1750光线传感器图2


之后我们定义BH1750的寄存器信息,稍后会用到。
  1. #define BH1750_POWER_DOWN    0x00   // 断电指令
  2. #define BH1750_POWER_ON      0x01   // 上电指令  
  3. #define BH1750_RESET         0x07   // 重置数据寄存器
  4. #define BH1750_CONT_HIGH_RES 0x10   // 连续高分辨率测量(1lx,120ms)
复制代码


然后我们使用命令链的方式封装一个读和写的操作。

  1. /**
  2. * @brief 向BH1750发送指令
  3. */
  4. static esp_err_t bh1750_write_cmd(uint8_t cmd)
  5. {
  6.     i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
  7.     i2c_master_start(i2c_cmd);
  8.     i2c_master_write_byte(i2c_cmd, (bh1750_addr << 1) | I2C_MASTER_WRITE, true);
  9.     i2c_master_write_byte(i2c_cmd, cmd, true);
  10.     i2c_master_stop(i2c_cmd);
  11.     esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_NUM, i2c_cmd, I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS);
  12.     i2c_cmd_link_delete(i2c_cmd);
  13.     return ret;
  14. }
  15. /**
  16. * @brief 从BH1750读取数据
  17. */
  18. static esp_err_t bh1750_read_data(uint8_t *data, size_t len)
  19. {
  20.     i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
  21.     i2c_master_start(i2c_cmd);
  22.     i2c_master_write_byte(i2c_cmd, (bh1750_addr << 1) | I2C_MASTER_READ, true);
  23.     if (len > 1)
  24.     {
  25.         i2c_master_read(i2c_cmd, data, len - 1, I2C_MASTER_ACK);
  26.     }
  27.     i2c_master_read_byte(i2c_cmd, data + len - 1, I2C_MASTER_NACK);
  28.     i2c_master_stop(i2c_cmd);
  29.     esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_NUM, i2c_cmd, I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS);
  30.     i2c_cmd_link_delete(i2c_cmd);
  31.     return ret;
  32. }
复制代码



相信大家看出来了,上述的I2C操作相对于传统的方式有所不同,相对传统方式I2C 命令链是一种将多步 I2C 操作(如 START、WRITE、READ、STOP)打包成一条指令序列并一次性执行的机制,相比传统逐步读写方式,它能显著简化代码、减少 CPU 干预、提高通信效率和可靠性,是 ESP-IDF 推荐的 I2C 操作方式。

之后我们便可以来初始化传感器。

  1. /**
  2. * @brief Initialize BH1750 sensor
  3. */
  4. static esp_err_t bh1750_init_sensor(void)
  5. {
  6.     esp_err_t ret;
  7.     // 先断电再上电,复位传感器
  8.     ret = bh1750_write_cmd(BH1750_POWER_DOWN);
  9.     if (ret != ESP_OK)
  10.     {
  11.         ESP_LOGE(TAG, "Failed to power down BH1750: %s", esp_err_to_name(ret));
  12.         return ret;
  13.     }
  14.     vTaskDelay(pdMS_TO_TICKS(10)); // 等待10ms
  15.     // 上电
  16.     ret = bh1750_write_cmd(BH1750_POWER_ON);
  17.     if (ret != ESP_OK)
  18.     {
  19.         ESP_LOGE(TAG, "Failed to power on BH1750: %s", esp_err_to_name(ret));
  20.         return ret;
  21.     }
  22.     vTaskDelay(pdMS_TO_TICKS(10)); // 等待10ms
  23.     // 重置数据寄存器
  24.     ret = bh1750_write_cmd(BH1750_RESET);
  25.     if (ret != ESP_OK)
  26.     {
  27.         ESP_LOGE(TAG, "Failed to reset BH1750: %s", esp_err_to_name(ret));
  28.         return ret;
  29.     }
  30.     vTaskDelay(pdMS_TO_TICKS(10)); // 等待10ms
  31.     // 设置为连续高分辨率模式
  32.     ret = bh1750_write_cmd(BH1750_CONT_HIGH_RES);
  33.     if (ret != ESP_OK)
  34.     {
  35.         ESP_LOGE(TAG, "Failed to set BH1750 mode: %s", esp_err_to_name(ret));
  36.         return ret;
  37.     }
  38.     ESP_LOGI(TAG, "BH1750 sensor initialized successfully");
  39.     return ESP_OK;
  40. }
复制代码



然后来根据数据手册计算LUX的方法。
  1. /**
  2. * @brief Read light level from BH1750 sensor
  3. */
  4. static esp_err_t bh1750_read_light(uint16_t *lux)
  5. {
  6.     uint8_t data[2];
  7.     esp_err_t ret = bh1750_read_data(data, 2);
  8.     if (ret != ESP_OK)
  9.     {
  10.         ESP_LOGE(TAG, "Failed to read from BH1750: %s", esp_err_to_name(ret));
  11.         return ret;
  12.     }
  13.     // BH1750返回的是16位大端序数据
  14.     // 高字节在前,低字节在后
  15.     uint16_t raw_data = (data[0] << 8) | data[1];
  16.     // 在高分辨率模式下,每个计数值等于1.2lux
  17.     // 转换公式: lux = raw_data / 1.2
  18.     // 为了避免浮点运算,使用整数运算: lux = raw_data * 10 / 12
  19.     *lux = (raw_data * 10) / 12;
  20.     return ESP_OK;
  21. }
复制代码



然后撰写读取BH1750 LUX的方法,实际上就是调用上述方法,两秒一次。

  1. /**
  2. * @brief BH1750 monitoring task
  3. */
  4. static void bh1750_task(void *pvParameter)
  5. {
  6.     uint16_t lux;
  7.     while (1)
  8.     {
  9.         if (bh1750_read_light(&lux) == ESP_OK)
  10.         {
  11.             ESP_LOGI(TAG, "Light level: %d lux", lux);
  12.         }
  13.         vTaskDelay(pdMS_TO_TICKS(2000)); // Read every 2 seconds
  14.     }
  15.     vTaskDelete(NULL);
  16. }
复制代码



完整的Main方法

  1. void app_main(void)
  2. {
  3.     configure_led();
  4.     // Initialize I2C master
  5.     ESP_ERROR_CHECK(i2c_master_init());
  6.     ESP_LOGI(TAG, "I2C initialized successfully");
  7.     // Perform I2C scan
  8.     i2c_scanner();
  9.     // Initialize BH1750 sensor
  10.     esp_err_t ret = bh1750_init_sensor();
  11.     if (ret == ESP_OK)
  12.     {
  13.         // Create BH1750 monitoring task
  14.         xTaskCreate(bh1750_task, "bh1750_task", 4096, NULL, 4, NULL);
  15.         ESP_LOGI(TAG, "BH1750 monitoring task created successfully");
  16.     }
  17.     else
  18.     {
  19.         ESP_LOGE(TAG, "Failed to initialize BH1750 sensor, skipping light monitoring");
  20.     }
  21.     xTaskCreate(blink_led_task, "blink_led", 2048, NULL, 5, &led_task_handle);
  22.     ESP_LOGI(TAG, "LED blink task created successfully");
  23. }
复制代码



实验现象如下:


FireBeetle 2 ESP32-C5 外设测试驱动BH1750光线传感器图3

总结

原本是打算直接用组件管理器中的BH1750驱动进行驱动的,但是没有研究明白,一直通信失败。最后还是决定自己写一次驱动好了。这样可以直接的了解I2C的命令链模式和BH1750的驱动开发。


下载附件blink-led.zip


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2025 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail