在使用GD32L233KBT6通过sensirion官方驱动调试SCD4X二氧化碳传感器时,遇到软件模拟IIC通信正常,硬件IIC无法读写的问题,折腾了好久终于调试通过!记录如下:
1.遇到的问题
基于上一篇《GD32L233通过I2C总线驱动AHT20温湿度传感器》的硬件I2C代码,无法正常驱动SCD4X,现象是地址发完后就触发了STOP信号,导致无法正常读写。
2.原因
先看原来的iic_write代码:
uint8_t aht20_interface_iic_write_cmd(uint8_t addr, uint8_t *buf, uint16_t len)
{
uint16_t count=len;
// 配置为发送模式
i2c_master_addressing(I2C1, addr, I2C_MASTER_TRANSMIT);
/* wait until I2C bus is idle */
while(i2c_flag_get(I2C1, I2C_FLAG_I2CBSY));
// 配置要发送的字节数
i2c_transfer_byte_number_config(I2C1, count);
// 生成开始条件
i2c_start_on_bus(I2C1);
/* wait until the transmit data buffer is empty */
I2C_STAT(I2C1) |= I2C_STAT_TBE;
while(!i2c_flag_get(I2C1, I2C_FLAG_TBE));
// 发送数据
while(count--) {
i2c_data_transmit(I2C1, *buf++);
// 等待可以发送数据
while(i2c_flag_get(I2C1, I2C_FLAG_TBE) == RESET) {
}
}
// 等待传输完成
while(i2c_flag_get(I2C1, I2C_FLAG_TC) == RESET) {
}
// 生成停止条件
i2c_stop_on_bus(I2C1);
/* wait until stop condition generate */
while(!i2c_flag_get(I2C1, I2C_FLAG_STPDET));
/* clear the STPDET bit */
i2c_flag_clear(I2C1, I2C_FLAG_STPDET);
return 0;
}
分析
在产生START后,GD32自动触发address发送
// 生成开始条件
i2c_start_on_bus(I2C1);
此时,紧跟着代码状态置位TBE,这里是不对的,问题就出在I2C_STAT(I2C1) |= I2C_STAT_TBE;这句,如果有这句,下面的while等待直接就过了不会等待。为什么驱动AHT20时没有出现问题,暂时不明。但是驱动SCD4X,这里必须去掉 I2C_STAT(I2C1) |= I2C_STAT_TBE;否则后面等待I2C_FLAG_TBE无效。
/* wait until the transmit data buffer is empty */
I2C_STAT(I2C1) |= I2C_STAT_TBE;
while(!i2c_flag_get(I2C1, I2C_FLAG_TBE));
修改后的iic_write底层实现代码:
int8_t sensirion_i2c_hal_write(uint8_t address, const uint8_t* data,
uint16_t count) {
// 配置为发送模式
i2c_master_addressing(I2C1, address, I2C_MASTER_TRANSMIT);
/* wait until I2C bus is idle */
while(i2c_flag_get(I2C1, I2C_FLAG_I2CBSY));
// 配置要发送的字节数
i2c_transfer_byte_number_config(I2C1, count);
// 生成开始条件
i2c_start_on_bus(I2C1);
/* wait until the transmit data buffer is empty */
//这里等待address发送完毕,gd32在start信号后自动发送地址
while(!i2c_flag_get(I2C1, I2C_FLAG_TBE));
// 发送数据
while(count--) {
i2c_data_transmit(I2C1, *data++);
// 等待可以发送数据
while(!i2c_flag_get(I2C1, I2C_FLAG_TBE));
}
// 等待传输完成
while(!i2c_flag_get(I2C1, I2C_FLAG_TC));
// 生成停止条件
i2c_stop_on_bus(I2C1);
/* wait until stop condition generate */
while(!i2c_flag_get(I2C1, I2C_FLAG_STPDET));
/* clear the STPDET bit */
i2c_flag_clear(I2C1, I2C_FLAG_STPDET);
return 0;
}
同理,修改后的iic_read函数如下:
int8_t sensirion_i2c_hal_read(uint8_t address, uint8_t* data, uint16_t count) {
/* wait until I2C bus is idle */
while(i2c_flag_get(I2C1, I2C_FLAG_I2CBSY));
// 配置为接收模式
i2c_master_addressing(I2C1, address, I2C_MASTER_RECEIVE);
// 配置要接收的字节数
i2c_transfer_byte_number_config(I2C1, count);
// 生成开始条件
i2c_start_on_bus(I2C1);
//等待地址发送完成
while(!i2c_flag_get(I2C1, I2C_FLAG_TBE));
// 读取数据
while(count--) {
while(i2c_flag_get(I2C1, I2C_FLAG_RBNE) == RESET) {
}
*data++ = i2c_data_receive(I2C1);
}
// 等待传输完成
while(!i2c_flag_get(I2C1, I2C_FLAG_TC));
// 生成停止条件
i2c_stop_on_bus(I2C1);
/* wait until stop condition generate */
while(!i2c_flag_get(I2C1, I2C_FLAG_STPDET));
/* clear the STPDET bit */
i2c_flag_clear(I2C1, I2C_FLAG_STPDET);
return 0;
}
传感器官方驱动文件github下载地址:embedded-i2c-scd4x
拷贝如下几个文件及对应头文件到工程:
scd4x_i2c.c
sensirion_common.c
sensirion_i2c_hal.c
sensirion_i2c.c
重点修改文件:sensirion_i2c_hal.c,该文件实现了i2c底层的读写接口。
scd4x_i2c_example_usage.c中有测试示例代码:
int main(void) {
int16_t error = 0;
sensirion_i2c_hal_init();
// Clean up potential SCD40 states
scd4x_wake_up();
scd4x_stop_periodic_measurement();
scd4x_reinit();
uint16_t serial_0;
uint16_t serial_1;
uint16_t serial_2;
error = scd4x_get_serial_number(&serial_0, &serial_1, &serial_2);
if (error) {
printf("Error executing scd4x_get_serial_number(): %i\n", error);
} else {
printf("serial: 0x%04x%04x%04x\n", serial_0, serial_1, serial_2);
}
// Start Measurement
error = scd4x_start_periodic_measurement();
if (error) {
printf("Error executing scd4x_start_periodic_measurement(): %i\n",
error);
}
printf("Waiting for first measurement... (5 sec)\n");
for (;;) {
// Read Measurement
sensirion_i2c_hal_sleep_usec(100000);
bool data_ready_flag = false;
error = scd4x_get_data_ready_flag(&data_ready_flag);
if (error) {
printf("Error executing scd4x_get_data_ready_flag(): %i\n", error);
continue;
}
if (!data_ready_flag) {
continue;
}
uint16_t co2;
int32_t temperature;
int32_t humidity;
error = scd4x_read_measurement(&co2, &temperature, &humidity);
if (error) {
printf("Error executing scd4x_read_measurement(): %i\n", error);
} else if (co2 == 0) {
printf("Invalid sample detected, skipping.\n");
} else {
printf("CO2: %u\n", co2);
printf("Temperature: %d m°C\n", temperature);
printf("Humidity: %d mRH\n", humidity);
}
}
return 0;
}
3.运行效果
示例代码中温度和湿度都是1000倍值,我除1000后打印效果如下:
logic抓包写指令:
logic抓包读序列:
4.总结
gd32l233这个i2c驱动不如STM32好用,甚至都没有iic速度配置,需要自己去换算i2c_sda和i2c_scl的保持时间来实现速度控制。
建议先调试write接口,再调试read接口。比如调用scd4x_wake_up();函数,就只会发2字节指令下去。通过抓包看下实际波形确保硬件没问题。
走了很多弯路,包括加延时之类的,其实不需要。本质是对gd32这个硬件i2c驱动理解不深刻。
gd32的硬件i2c在i2c_start_on_bus(I2C1);后会自动发送address字节,这里一定要等待发送完成,因此要跟一句while(!i2c_flag_get(I2C1, I2C_FLAG_TBE));
不管是read还是write,在读写字节完成后,一定要等待传输完成-> while(!i2c_flag_get(I2C1, I2C_FLAG_TC));
如果遇到stop信号或data无法发送,抓包只有address字节,基本可以确定就是硬件上还没发送完成造成的,必须增加相应的TC/TBE等待。上述代码是调试通过的,可以直接使用。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/kingnike/article/details/143596601
|
|