[原创] SHT31测评+尝试读取传感器I2C总线数据

hujj   2020-1-20 15:50 楼主

    原计划修改范例,增加串口输出温、湿度数据的功能,但我暂时没有J-LINK下载工具,同时又看到坛友尝试下载烧录未成功,转而一想还不如直接截取I2C总线上的数据,这样对原评测板不需要做任何变动,仅仅通过我的扩展板就行,于是我就用了一块现成的STM32L412板子进行实验,下图为实验装置:

I2C_test_0.jpg

    通过逻辑分析仪获取了评测板上的I2C数据,评测板是以每秒为周期对传感器进行一次读写操作:

I2C_test_1.jpg

    实际操作是先写入命令,然后等近1秒再读出,所以从逻辑分析仪截取的时序图是读在前、写在后:

I2C_test_2.jpg

    读操作是先发出地址码139,然后连续读出6个字节数据:

I2C_test_3.jpg

    从数据手册上得知前3个字节是温度数据,即16位的温度数据加1个字节的校验码,后3个字节是湿度数据,同样也是16的湿度数据加1个字节的校验码:

I2C_test_8.jpg    写入的则是16位的命令,地址码是138:

I2C_test_4.jpg

    根据这些资料写了一段代码,这6个字节的数据读了出来,下面是读取I2C总线的代码:

    读一个字节:

/******************************************************
*程 序 名:Read_I2C
*作    用:读取I2C总线上的数据 
*输入参数:无
*返回参数:读到的数据(1字节)
******************************************************/
uint8_t Read_I2C(void)
{
    uint8_t dat,i;
    //开始读一字节数据
    for(i=0;i<8;i++){
        while(HAL_GPIO_ReadPin(SI2C_SCL_GPIO_Port,SI2C_SCL_Pin) == 0);
        dat <<= 1;
        if(HAL_GPIO_ReadPin(SI2C_SDA_GPIO_Port,SI2C_SDA_Pin) == 1) dat |= 0x01;
        while(HAL_GPIO_ReadPin(SI2C_SCL_GPIO_Port,SI2C_SCL_Pin) == 1);
    }
    return dat;
}

 

    下面是读全部数据的代码:

/******************************************************
*程 序 名:Intercept_I2C
*作    用:截取I2C总线上的读数据 
*输入参数:无
*返回参数:
******************************************************/
void Intercept_I2C(void)
{
    uint8_t i,d[7];
    uint32_t temp=0,hum=0;
    uint32_t d_t,d_h;
    //等待I2C开始信号
    while(HAL_GPIO_ReadPin(SI2C_SCL_GPIO_Port,SI2C_SCL_Pin)==1){
        while(HAL_GPIO_ReadPin(SI2C_SDA_GPIO_Port,SI2C_SDA_Pin)==1);
    }

//    while(Read_I2C() != 139)    //I2C总线上出现读地址
        for(i = 0; i < 7; i++)
            d = Read_I2C();
/*
        temp = Read_I2C();  //取高八位
        temp <<= 8;
        temp |= (uint16_t )Read_I2C();
        hum = Read_I2C();   //校验字节(弃用)
        hum = Read_I2C();
        hum <<= 8;
        hum |= (uint16_t )Read_I2C();
*/
        d_t = d[1];
        d_t <<= 8;
        d_t |= d[2];
//        temp = (d_t * 175 / 65535) - 45;
        temp = d_t *1000 / 267 - 4500;
        d_h = d[4];
        d_h <<= 8;
        d_h |= d[5];
        hum = d_h * 10000 / 65535; 
        LCD_write_value(12,3,5,0,1,temp);
        LCD_write_value(12,4,5,0,1,hum);
        LCD_write_value(50,3,5,0,1,d_t);
        LCD_write_value(50,4,5,0,1,d_h);
    
    LCD_write_value(0,0,3,0,1,d[0]);
//    LCD_write_value(24,0,3,0,1,d[0]);
    LCD_write_value(0,1,3,0,1,d[1]);
    LCD_write_value(24,1,3,0,1,d[2]);
    LCD_write_value(50,1,3,0,1,d[3]);
    LCD_write_value(0,2,3,0,1,d[4]);
    LCD_write_value(24,2,3,0,1,d[5]);
    LCD_write_value(50,2,3,0,1,d[6]);
    HAL_Delay(2);
}

    经过反复测试,这些数据基本是读出了,但似乎不太正确,尤其是计算过程不正确,计算出的温、湿度数据与实际显示的大相径庭。下图中第1行是读取到的地址码,第2~3行分别是温、湿度数据及校验码,蓝色圈起的分别是读出的温、湿度数据,红色圈起的是计算出的温、湿度数值,与实际显示的不一致,尤其是湿度数据跳跃变动,不稳定。

I2C_test_11.jpg

I2C_test_10.jpg

    根据数据手册,温、湿度的计算公式如下:

I2C_test_9.jpg    可是我按照这个公式写的代码计算出的数据却与手工计算的不同,也不知道代码那里错了,下面是代码:

        d_t = d[1];
        d_t <<= 8;
        d_t |= d[2];
//        temp = (d_t * 175 / 65535) - 45;
        temp = d_t *1000 / 267 - 4500;
        d_h = d[4];
        d_h <<= 8;
        d_h |= d[5];
        hum = d_h * 10000 / 65535; //按保留两位小数

    我将变量的类型从16位无符号数改成32位无符号数,结果仍然不对。

    我用手工计算也无法对应到正确的温、湿度数值,不知道是那里还有问题。

    在读写I2C总线数据时,我忽略了每个字节后面的ACK,会不会是这个原因造成取数不正确?从时序图上分析,1 个ACK是3微秒,再到下个字节开始的时间是5.125微秒,总共是8.125微秒。不知道从读字节函数返回到下次再进入的时间需要多少,但这不应该会影响到同步,因为读下个字节是依据SCL时钟,除非返回到下次再进入的时间少于3微秒。

    

 

 


此内容由EEWORLD论坛网友hujj原创,如需转载或用于商业用途需征得作者同意并注明出处

  • I2C_test_5.jpg
  • I2C_test_6.jpg
  • I2C_test_7.jpg

回复评论 (5)

是数据溢出造成的吗?

点赞  2020-1-20 16:42
引用: dcexpert 发表于 2020-1-20 16:42 是数据溢出造成的吗?

好象不是,我用的数据类型是32位无符号,对于16位的数据进行乘175后再除65535,按理不会溢出啊。

 

点赞  2020-1-20 16:59

可能还是时序问题,捕捉的数据有错误

点赞  2020-1-20 21:35
引用: dcexpert 发表于 2020-1-20 21:35 可能还是时序问题,捕捉的数据有错误

可是读出的地址数据是正确的,并没有出错。

点赞  2020-1-20 22:30

    经过仔细排查,没能获得正确数据的原因还是我忽略的ACK引起的,我在读I2C数据中加入了等待ACK的代码,获得的数据就正确了,代码如下:

/******************************************************
*程 序 名:Read_I2C
*作    用:读取I2C总线上的数据 
*输入参数:无
*返回参数:读到的数据(1字节)
******************************************************/
uint8_t Read_I2C(void)
{
    uint8_t dat,i;
    //开始读一字节数据
    for(i=0;i<8;i++){
        while(HAL_GPIO_ReadPin(SI2C_SCL_GPIO_Port,SI2C_SCL_Pin) == 0);
        dat <<= 1;
        if(HAL_GPIO_ReadPin(SI2C_SDA_GPIO_Port,SI2C_SDA_Pin) == 1) dat |= 0x01;
        while(HAL_GPIO_ReadPin(SI2C_SCL_GPIO_Port,SI2C_SCL_Pin) == 1);
    }
    while(HAL_GPIO_ReadPin(SI2C_SCL_GPIO_Port,SI2C_SCL_Pin) == 0);
    while(HAL_GPIO_ReadPin(SI2C_SCL_GPIO_Port,SI2C_SCL_Pin) == 1);  //等待ACK
    return dat;
}

    下面是STM32L412开发板获得的温、湿度数据:

I2C_test_22.jpg

    有时会出现少许误差,这应该是两者刷新不完全同步所致:

I2C_test_21.jpg

    至此,这个测试是成功了。截取数据用的是STM32L412KB开发板,显示屏是LCD5110。

I2C_test_20.jpg

 

点赞  2020-1-21 11:20
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复