

环境搭建
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 的配置流程,希望之后能有更多机会评测商城的其它产品.

