原计划修改范例,增加串口输出温、湿度数据的功能,但我暂时没有J-LINK下载工具,同时又看到坛友尝试下载烧录未成功,转而一想还不如直接截取I2C总线上的数据,这样对原评测板不需要做任何变动,仅仅通过我的扩展板就行,于是我就用了一块现成的STM32L412板子进行实验,下图为实验装置:
通过逻辑分析仪获取了评测板上的I2C数据,评测板是以每秒为周期对传感器进行一次读写操作:
实际操作是先写入命令,然后等近1秒再读出,所以从逻辑分析仪截取的时序图是读在前、写在后:
读操作是先发出地址码139,然后连续读出6个字节数据:
从数据手册上得知前3个字节是温度数据,即16位的温度数据加1个字节的校验码,后3个字节是湿度数据,同样也是16的湿度数据加1个字节的校验码:
根据这些资料写了一段代码,这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行分别是温、湿度数据及校验码,蓝色圈起的分别是读出的温、湿度数据,红色圈起的是计算出的温、湿度数值,与实际显示的不一致,尤其是湿度数据跳跃变动,不稳定。
根据数据手册,温、湿度的计算公式如下:
可是我按照这个公式写的代码计算出的数据却与手工计算的不同,也不知道代码那里错了,下面是代码:
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原创,如需转载或用于商业用途需征得作者同意并注明出处
引用: dcexpert 发表于 2020-1-20 21:35 可能还是时序问题,捕捉的数据有错误
可是读出的地址数据是正确的,并没有出错。
经过仔细排查,没能获得正确数据的原因还是我忽略的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开发板获得的温、湿度数据:
有时会出现少许误差,这应该是两者刷新不完全同步所致:
至此,这个测试是成功了。截取数据用的是STM32L412KB开发板,显示屏是LCD5110。