【GD32VW553-IoT 评测】BME 280 传感器实现的 BLE 环境监测服务

分享作者:scgummy
作者昵称:scgummy
评测品牌:萤火工场
评测型号:GD32VW553-IOT
发布时间:2025-08-19 11:47:54
1
概要
本次评测我们在面包板上搭建了一套以 GD32VW553-IoT 为主控,读取 BME 280 传感器数据后通过 BLE 低功耗蓝牙传送的环境监测服务,同时利用 Web Bluetooth API 编写了一份面向最终用户可使用的数据统计网页.
开源口碑分享内容

环境搭建

  Linux hydrogen 6.15.6-arch1-1 #1 SMP PREEMPT_DYNAMIC Thu, 10 Jul 2025 17:10:18 +0000 x86_64 GNU/Linux

笔者使用的环境是 Arch Linux,GD 官方的 Embedded Builder 只提供了 Windows 上的二进制可再分发包,因此我们要想办法变通. 考虑到 GD 和 SEGGER 有合作,同时也是 GD 官方推荐的方式之一,我们不妨在 Linux 上使用 Embedded Studio;当然了,Windows 上也是可以使用 Embedded Studio 的.

经过亿些配置就可以工作啦,修改过的样例工程和本次评测的工程在 https://codeberg.org/scgummy/gd32vw55-envsensing 开源

简单说几个点吧!需要修改 GCC Target,经过测试 Embedded Studio 自带的 RISC-V 工具链都可以编译通过,前提是需要把 RISC-V 扩展指令集 bp 给关掉.

然后是正确地调整链接库的位置,因为我们没有使用 nuclei 的工具链,这里就使用了 Arch Linux 的 extra/riscv64-elf-gcc 配合 extra/riscv32-elf-newlib,其中 libgcc 提供部分封装的 RISC-V 原语,newlib 提供 C 库.

BME280 驱动

I2C 初始化

我们需要对引脚的 IOMUX 功能进行初始化,可以参考官方数据手册的 IOMUX 矩阵,同时以下初始化过程也参考了 GD32VW55x 里的 AT24 EEPROM 读写例程. 这里使用了 PB0 和 PB1 作为 SCL 和 SDA.

  #define ES_RCU_GPIO_I2C_SCL RCU_GPIOB
  #define ES_RCU_GPIO_I2C_SDA RCU_GPIOB
  #define ES_RCU_I2C          RCU_I2C0
  
  #define ES_I2CX           I2C0
  #define ES_I2C_SCL_GPIO   GPIOA
  #define ES_I2C_SCL_PIN    GPIO_PIN_2
  #define ES_I2C_SCL_AF_NUM GPIO_AF_4
  #define ES_I2C_SDA_GPIO   GPIOA
  #define ES_I2C_SDA_PIN    GPIO_PIN_3
  #define ES_I2C_SDA_AF_NUM GPIO_AF_4
  
  static void app_es_config(void) {
      rcu_periph_clock_enable(ES_RCU_GPIO_I2C_SCL);
      rcu_periph_clock_enable(ES_RCU_GPIO_I2C_SDA);
  
      gpio_af_set(ES_I2C_SCL_GPIO, ES_I2C_SCL_AF_NUM, ES_I2C_SCL_PIN);
      gpio_af_set(ES_I2C_SDA_GPIO, ES_I2C_SDA_AF_NUM, ES_I2C_SDA_PIN);
      gpio_mode_set(ES_I2C_SCL_GPIO, GPIO_MODE_AF, GPIO_PUPD_PULLUP, ES_I2C_SCL_PIN);
      gpio_output_options_set(ES_I2C_SCL_GPIO, GPIO_OTYPE_OD, GPIO_OSPEED_25MHZ, ES_I2C_SCL_PIN);
  
      gpio_mode_set(ES_I2C_SDA_GPIO, GPIO_MODE_AF, GPIO_PUPD_PULLUP, ES_I2C_SDA_PIN);
      gpio_output_options_set(ES_I2C_SDA_GPIO, GPIO_OTYPE_OD, GPIO_OSPEED_25MHZ, ES_I2C_SDA_PIN);
  
      rcu_periph_clock_enable(ES_RCU_I2C);
  
      i2c_timing_config(ES_I2CX, 7, 0x08, 0x00);
      i2c_master_clock_config(ES_I2CX, 0x30, 0x91);
      i2c_enable(ES_I2CX);
  }


I2C 总线读写

BME280 支持一次 transaction 写多个寄存器,这里我们实现了 bme280_write_multiple 用于这个目的,函数体很大程度上参考了 AT24 EEPROM 读写例程,并配合逻辑分析仪进行了微调,因为主机 ACK 和从机 ACK 有本质的不同.

  static void bme280_write_multiple(uint8_t *regs, uint8_t *values, size_t count)
  {
      I2C_STAT(ES_I2CX) |= I2C_STAT_TBE;
      i2c_master_addressing(ES_I2CX, BME280_ADDRESS, I2C_MASTER_TRANSMIT);
      i2c_transfer_byte_number_config(ES_I2CX, count * 2);
      i2c_automatic_end_enable(ES_I2CX);
  
      while (i2c_flag_get(ES_I2CX, I2C_FLAG_I2CBSY));
      i2c_start_on_bus(ES_I2CX);
  
      while (i2c_flag_get(ES_I2CX, I2C_FLAG_TBE) != SET);
  
      for (int i = 0; i < count; i++) {
          while (!i2c_flag_get(ES_I2CX, I2C_FLAG_TI));
          i2c_data_transmit(ES_I2CX, regs[i]);
          while (!i2c_flag_get(ES_I2CX, I2C_FLAG_TI));
          i2c_data_transmit(ES_I2CX, values[i]);
      }
  
      while (!i2c_flag_get(ES_I2CX, I2C_FLAG_STPDET));
      i2c_flag_clear(ES_I2CX, I2C_FLAG_STPDET);
  }
  
  static void bme280_write(uint8_t reg, uint8_t value)
  {
      bme280_write_multiple(®, &value, 1);
  }
  
  static void bme280_read(uint8_t offset, uint8_t *buffer, size_t count)
  {
      I2C_STAT(ES_I2CX) |= I2C_STAT_TBE;
      i2c_master_addressing(ES_I2CX, BME280_ADDRESS, I2C_MASTER_TRANSMIT);
      i2c_transfer_byte_number_config(ES_I2CX, 1);
      i2c_automatic_end_disable(ES_I2CX);
  
      while (i2c_flag_get(ES_I2CX, I2C_FLAG_I2CBSY));
      i2c_start_on_bus(ES_I2CX);
      while (i2c_flag_get(ES_I2CX, I2C_FLAG_TBE) != SET);
  
      i2c_data_transmit(ES_I2CX, offset);
      while (!i2c_flag_get(ES_I2CX, I2C_FLAG_TC));
  
      i2c_master_addressing(ES_I2CX, BME280_ADDRESS, I2C_MASTER_RECEIVE);
      i2c_transfer_byte_number_config(ES_I2CX, count);
      i2c_automatic_end_enable(ES_I2CX);
  
      i2c_start_on_bus(ES_I2CX);
      for (int i = 0; i < count; i++) {
          while (!i2c_flag_get(ES_I2CX, I2C_FLAG_RBNE));
          buffer[i] = i2c_data_receive(ES_I2CX);
      }
  
      while (!i2c_flag_get(ES_I2CX, I2C_FLAG_STPDET));
      i2c_flag_clear(ES_I2CX, I2C_FLAG_STPDET);
  }

BME 280 部分

BME280 的寄存器表如上图所示,因为携带了校正表,我们不能简单地将数据读出并量化. 不过无论如何,在读取数据前,我们首先需要配置采样率等信息:

手册中分别包含了定点数和浮点数系统的校正算法,这里我们采用定点数的算法,因为环境监测相关的 BLE 服务规范中也使用的是定点数,不过量化标准有所不同,这样做可以避免反复转换丢失精度.

简单的实现如下

  typedef struct {
      uint16_t dig_T1;
      int16_t  dig_T2;
      int16_t  dig_T3;
      uint16_t dig_P1;
      int16_t  dig_P2;
      int16_t  dig_P3;
      int16_t  dig_P4;
      int16_t  dig_P5;
      int16_t  dig_P6;
      int16_t  dig_P7;
      int16_t  dig_P8;
      int16_t  dig_P9;
      uint8_t  dig_H1;
      int16_t  dig_H2;
      uint8_t  dig_H3;
      int16_t  dig_H4;
      int16_t  dig_H5;
      int8_t   dig_H6;
  } bme280_calibration_t;
  
  int32_t bme280_compensate_temperature(bme280_calibration_t *calibration, int32_t raw) {
      int32_t var1, var2, T;
      
      var1 = ((((raw >> 3) - ((int32_t)calibration->dig_T1 << 1))) * 
             ((int32_t)calibration->dig_T2)) >> 11;
      
      var2 = (((((raw >> 4) - ((int32_t)calibration->dig_T1)) * 
               ((raw >> 4) - ((int32_t)calibration->dig_T1))) >> 12) * 
             ((int32_t)calibration->dig_T3)) >> 14;
      
      t_fine = var1 + var2;
      T = (t_fine * 5 + 128) >> 8;
      
      return T;
  }
  
  uint32_t bme280_compensate_pressure(bme280_calibration_t *calibration, int32_t raw) {
      int64_t var1, var2, p;
      
      var1 = ((int64_t)t_fine) - 128000;
      var2 = var1 * var1 * (int64_t)calibration->dig_P6;
      var2 = var2 + ((var1 * (int64_t)calibration->dig_P5) << 17);
      var2 = var2 + (((int64_t)calibration->dig_P4) << 35);
      var1 = ((var1 * var1 * (int64_t)calibration->dig_P3) >> 8) + 
             ((var1 * (int64_t)calibration->dig_P2) << 12);
      var1 = (((((int64_t)1) << 47) + var1)) * ((int64_t)calibration->dig_P1) >> 33;
      
      if (var1 == 0) {
          return 0;
      }
      
      p = 1048576 - raw;
      p = (((p << 31) - var2) * 3125) / var1;
      var1 = (((int64_t)calibration->dig_P9) * (p >> 13) * (p >> 13)) >> 25;
      var2 = (((int64_t)calibration->dig_P8) * p) >> 19;
      p = ((p + var1 + var2) >> 8) + (((int64_t)calibration->dig_P7) << 4);
      
      return (uint32_t)p;
  }
  
  uint32_t bme280_compensate_humidity(bme280_calibration_t *calibration, int32_t raw) {
      int32_t v_x1_u32r;
      
      v_x1_u32r = (t_fine - ((int32_t)76800));
      v_x1_u32r = (((((raw << 14) - (((int32_t)calibration->dig_H4) << 20) - 
                    (((int32_t)calibration->dig_H5) * v_x1_u32r)) + 
                   ((int32_t)16384)) >> 15) * 
                  (((((((v_x1_u32r * ((int32_t)calibration->dig_H6)) >> 10) * 
                     (((v_x1_u32r * ((int32_t)calibration->dig_H3)) >> 11) + 
                     ((int32_t)32768))) >> 10) + 
                    ((int32_t)2097152)) * 
                   ((int32_t)calibration->dig_H2) + 8192) >> 14));
      
      v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * 
                                ((int32_t)calibration->dig_H1)) >> 4));
      v_x1_u32r = (v_x1_u32r < 0 ? 0 : v_x1_u32r);
      v_x1_u32r = (v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r);
      
      return (uint32_t)(v_x1_u32r >> 12);
  }


BLE 服务

BLE 服务参考了官方的 BLE_APP_UART 例程.

Bluetooth SIG 针对环境监测服务有相关规范,正好囊括温湿度和气压,采取拿来主义,这样就不必自定义 UUID 又具有通用性(可以和识别 Bluetooth SIG 下环境监测服务的软件进行互操作).

这里我们的 GATT 服务中携带了 3 个 characteristic,可以由终端向 BLE 主机 notify 最新数据,所有 notify / indicate 的 characteristic 均需要携带一个 CCCD 用于控制订阅.

为了方便,我们将原先 BLE_APP_UART 例程的配对模式改为 NO_BOND 因为我们不需要持久化对端的信息.

总结

完整的项目开源于 https://codeberg.org/scgummy/gd32vw55-envsensing 有需要的盆友可自行查看,演示在 b 站可以观看,这次评测让我熟悉了 GD32 RISC-V 系列 MCU 的配置流程,希望之后能有更多机会评测商城的其它产品.

全部评论
暂无评论
0/144