收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
void i2c\_start(void) {
sda\_high();
\_nop\_(); \_nop\_();\_nop\_();\_nop\_(); \_nop\_();\_nop\_(); \_nop\_();
\_nop\_(); \_nop\_();\_nop\_(); \_nop\_();\_nop\_(); \_nop\_();\_nop\_();
\_nop\_(); \_nop\_();\_nop\_(); \_nop\_();\_nop\_(); \_nop\_();\_nop\_();
\_nop\_(); \_nop\_();\_nop\_(); \_nop\_();\_nop\_(); \_nop\_();\_nop\_();
scl\_high();
\_nop\_(); \_nop\_();\_nop\_(); \_nop\_();\_nop\_(); \_nop\_();\_nop\_();
\_nop\_(); \_nop\_();\_nop\_(); \_nop\_();\_nop\_(); \_nop\_();\_nop\_();
\_nop\_(); \_nop\_();\_nop\_(); \_nop\_();\_nop\_(); \_nop\_();\_nop\_();
sda\_low();
\_nop\_(); \_nop\_();\_nop\_(); \_nop\_();\_nop\_(); \_nop\_();\_nop\_();
\_nop\_(); \_nop\_();\_nop\_(); \_nop\_();\_nop\_(); \_nop\_();\_nop\_();
\_nop\_(); \_nop\_();\_nop\_(); \_nop\_();\_nop\_(); \_nop\_();\_nop\_();
scl\_low();
\_nop\_(); \_nop\_();\_nop\_(); \_nop\_();\_nop\_(); \_nop\_();\_nop\_();
\_nop\_(); \_nop\_();\_nop\_(); \_nop\_();\_nop\_(); \_nop\_();\_nop\_();
\_nop\_(); \_nop\_();\_nop\_(); \_nop\_();\_nop\_(); \_nop\_();\_nop\_();
}
这里的延时时间是根据自己以前使用的经验而定的,前面也说了 I2C 通讯中延时多少个机器周期,并没有准确的要求,多几个 nop 都是无所谓的。
但是我们可以可以把这么多 _nop_
写成一个函数,类似在 STM32 下一样,因为是不准确的,所以这里不用 us 表示:
void delay\_nop(uint32 delay){
uint32\_t i = 0;
for(i = 0; i < delay; i++)\_nop\_();
}
最终我们的 i2c.h 如下:
#include "i2c.h"
void delay\_nop(uint32 delay){
uint32 i;
for(i = 0; i < delay; i++)\_nop\_();
}
void i2c\_start(void) {
sda\_high();
delay\_nop(28);
scl\_high();
delay\_nop(28);
sda\_low();
delay\_nop(28);
scl\_low();
delay\_nop(28);
}
// ------------------------------------------------------------------
// send stop sequence (P)
void i2c\_stop(void) {
sda\_low();
delay\_nop(28);
scl\_low();
delay\_nop(28);
scl\_high();
delay\_nop(28);
sda\_high();
delay\_nop(28);
}
// ------------------------------------------------------------------
// returns the ACK or NACK
uint8 i2c\_write(uint8 u8Data)
{
uint8 u8Bit;
uint8 u8AckBit;
// write 8 data bits
u8Bit = 0x80; //msb first
while(u8Bit)
{
if(u8Data&u8Bit){
sda\_high();
delay\_nop(28);
}
else{
sda\_low();
delay\_nop(28);
}
scl\_high();
delay\_nop(80);
u8Bit >>= 1;
//next bit
scl\_low();
delay\_nop(90);
}
// read acknowledge (9th bit)
sda\_high();
delay\_nop(45);
scl\_high();
delay\_nop(45);
u8AckBit = sda\_read(); //#define sda\_read() (sda\_port & sda\_pin)? 1 :0 ack on bus is low -> u8AckBit = 1 sda\_port gpio0 sda\_pin SCSEDIO0
delay\_nop(45);
scl\_low();
delay\_nop(45);
return u8AckBit;
}
// ------------------------------------------------------------------
// pass the ack/nack
// returns the read data
uint8 i2c\_read(uint8 u8Ack)
{
uint8 u8Bit;
uint8 u8Data;
u8Bit = 0x80; // msb first
u8Data = 0;
// 8 data bits
while(u8Bit)
{
scl\_high();
delay\_nop(70);
u8Bit >>= 1; //next bit
u8Data <<= 1;
u8Data |= sda\_read(); //(sda\_port & sda\_pin)? 1 :0 sda\_port gpio0 sda\_pin SCSEDIO0
delay\_nop(30);
scl\_low();
delay\_nop(80);
}
// 9th bit acknowledge
if(u8Ack==I2C_ACK){
sda\_low();
delay\_nop(30);
} //I2C\_ACK=0
else
{
sda\_high();
delay\_nop(30);
}
scl\_high();
delay\_nop(30);
scl\_low();
delay\_nop(30);
sda\_high();
delay\_nop(30);
return u8Data;
}
二、 BH1750 驱动移植
通用驱动讲完了,我们 BH1750 驱动逻辑可以参考曾经分析的流程,具体的逻辑分析可参考下文:
2.1 bh1750.h
我们的 bh1750.h
完全可以和上文中的一样(但是注意一下头文件包含以及数据类型),如下图 :
2.2 bh1750.c
我们的主要任务在于 bh1750.c
如何实现,我们按照定好的逻辑来:
这里有一个问题需要注意,因为我们本次是需要低功耗设计,所以我们要考虑到模块通电以后默认状态是怎么状态? 是单次测量还是连续模式?这关系到我们是否每次上电都需要初始化。
带着这个问题我重新看了一遍资料的流程图:
所以其实 BH1750 并不需要我们曾经文章中提到的 void bh1750_init()
初始化函数,当然有也没有问题 ,只不过当成了进行一次单次测量。
那么我们本次初始化函数也不用写了,直接写测量读取函数就行了。
其实 I2C开始,结束这个倒直接换就行了,我们主要是要注意一下接收不接收 ACK 的处理。 当然,因为我在本次芯片上使用的函数是上面的 i2c.c
提供的,需要注意,如果大家愿意,可以自己修改一下驱动,改成和我们在 stm32 上面一样的,这样子把 通用驱动 修改,传感器驱动基本就一致了,这个看个人。
在我们以前的驱动中,发送一条消息等待 ACK 的语句如下:
IIC\_Send\_Byte(BH1750_ADDRESS << 1); //地址,和读写指令
MYIIC\_Wait\_Ack();
而在我们这个驱动中,我们需要这样做:
u8Ack = i2c\_write(BH1750_ADDRESS << 1);
直接上一下修改的驱动程序把,其中与以前的驱动对比的注释我留着没删除,以做比较:
#include "bh1750.h"
void bh1750\_read(uint16 \*lux)
{
uint8 read_buffer[2];
uint32 lv_lux;
uint8 u8Ack;
SensorPowerOn();
time\_wait(200);
i2c\_start();
// IIC\_Send\_Byte(BH1750\_ADDRESS << 1); //????????
// MYIIC\_Wait\_Ack();
u8Ack = i2c\_write(BH1750_ADDRESS << 1);
// delay\_us(150);
delay\_nop(500);
// IIC\_Send\_Byte(BH1750\_MODE\_ONE\_H\_RES); //????
// MYIIC\_Wait\_Ack();
u8Ack = i2c\_write(BH1750_MODE_ONE_H_RES);
i2c\_stop();
// HAL\_Delay(BH1750\_MEASURE\_DURATION\_MS);
time\_wait(BH1750_MEASURE_DURATION_MS);
i2c\_start();
// IIC\_Send\_Byte((BH1750\_ADDRESS << 1)|1); //????????
// MYIIC\_Wait\_Ack();
u8Ack = i2c\_write((BH1750_ADDRESS << 1)|1);
// read\_buffer[0] = IIC\_Read\_Byte(1);
// delay\_us(120);
// read\_buffer[1] = IIC\_Read\_Byte(0);
// delay\_us(120);
read_buffer[0] = i2c\_read(I2C_ACK);
delay\_nop(450);
read_buffer[1] = i2c\_read(I2C_NACK);
delay\_nop(450);
i2c\_stop();
SensorPowerOff();
lv_lux = ((read_buffer[0] << 8) | read_buffer[1]) \* 10 / 12;
\*lux = (uint16)lv_lux;
}
三、 测试
开始测试……
在需要读取光照的地方使用 bh1750_read(&lux_data);
读取即可。
3.1 问题一 (数据完全不对)
数据是有了,但是数据有点不正常
我们前面都是按照顺序一步一步走过来的,使用电筒照着数据不正确…… 先让我理一理……
这就对了,我早就知道会有问题,要不然也没必要写一篇移植的文章!
……测试中…… 测试中……
其实出了问题也比较麻烦,因为相对 STM32 来说,使用的这个 51 调试起来很麻烦。
还记得我们当时硬件设计的时候使用了电源开关电路(本次测试飞线使用的下图中第一个电路):
我们在上面的程序中使用了 200 ms 的延时:
当我们在做低功耗的传感器遇到问题了,为了解决问题我们要先排除电源的问题,所以这里我们先让传感器一直供电。
当然我以前也说过,I2C 通讯中很有可能出问题的地方是通讯的等待延时,传感器驱动 bh1750.c
中的延时我也修改了,我把驱动中需要的延时 等待改成了 1 ms,如下(这是前后测试了很多的得出的结论):
修改完成以后,我们测试了一下数据,看上去好像正常了:
3.2 问题二 (光强时数据异常)
我们测试光照往往是让他测一测正常环境,然后用手电筒照着看看数据是否变大。
经过一系列的折腾,最后测试我发现,在光照强度比较低的时候数据基本是正常的,但是光照强度太高的时候数据就异常了,如下图所示。
正常情况:
灯光照射异常情况:
这不由得让我想起难道是数据读取的时候,高字节的数据读取异常一直为0 ,只能读到 低 8位的数据?
我们来计算机看一下:
那么这样的话,会不会是读取这个地方有问题 ? 还是说数据处理的时候有问题?
数据问题解决
最后测试来测试去,发现是其中有一条语句有问题:
lv_lux = ((read_buffer[0] << 8) | read_buffer[1]) \* 10 / 12;
在程序中 read_buffer
为 uint8
类型,我们这样直接位移然后与一下是否会有问题?
我把程序改成:
lv_lux = ((read_buffer[0] << 8) + read_buffer[1]) \* 10 / 12;
发现数据就正常了!
为了验证一下是否是数据类型的问题,我把语句改成:
lv_lux = ((uint16)(read_buffer[0] << 8) | read_buffer[1]) \* 10 / 12;
能够正常的读取到光强时候的数据:
到头来,其实数据异常并不是驱动有问题,而是我们数据处理的细节问题!
3.3 再次处理电源控制的问题
我们把数据读取的问题解决以后,我们还得回到我们的应用上来,电源还是需要不用的时候关短,读取传感器数据时候打开。
那么我们这个问题一般如何解决,大部分情况下,都是加大打开电源后的延时时间!
这个延时时间越大,传感器采集的时候功耗就越大,因为这时候并不是休眠状态,但是太小我们前面测试的 200 ms,发现传感器数据会不正常,可能是电源没有稳定下来,也可能是传感器也需要准备时间,所以这个时间需要自己衡量和测试。
这里把时间改成 500 ms ,发现数据就可以正常的采集。
最后周期数据采集如下图,测试的时候 6s 采集一次,实际使用根据情况而定:
结语
本文我们把 BH1750 传感器移植到一个 51 内核的芯片上使用。
过程不算顺利,出了很多小问题,但是整体来说,本文所讲解的知识点都是没有问题的,驱动的移植也算是成功的。
居然在数据处理的时候出了问题,虽然我们当时在 STM32 中程序中的语句是这么写的,而且也测试过了,但是确实在 51 上这条语句确实出了问题,而且中途还找错了方向,以至于我画了很多时间在其他地方 = =!
不过最后通过找到问题,也算是给了大家一个很好的示例。
完成本文,BH1750 的实战教学篇就算完成了,相信大家学习以后,不管在什么芯片上使用 BH1750 甚至是其他 I2C 通讯的传感器,都会顺顺利利!
本文就到这里,谢谢大家! 另外,别忘了下面可以加我的技术群哦!
收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
示例。
完成本文,BH1750 的实战教学篇就算完成了,相信大家学习以后,不管在什么芯片上使用 BH1750 甚至是其他 I2C 通讯的传感器,都会顺顺利利!
本文就到这里,谢谢大家! 另外,别忘了下面可以加我的技术群哦!
收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
[外链图片转存中…(img-8jgMpBPD-1715845027206)]
[外链图片转存中…(img-jMtOPH7d-1715845027206)]
需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!