【外设移植】I2C 光照度传感器(BH1750)M61开发板驱动

[复制链接]
查看1816 | 回复12 | 2024-1-13 15:32:30 | 显示全部楼层 |阅读模式
本帖最后由 1084504793 于 2024-4-20 16:52 编辑

IIC光度传感器模块种类很多,我选择一种比较常用的模块(BH1750)做外设移植。BH1750的优点如下
    1、支持标准快速模式的IIC通信
    2、模块IIC通信地址可以通过特定引脚的电平进行改变,在同一IIC总线下可以挂载两个BH1750模块
    3、可捕获光照强度范围广,范围为1至65535lx
    4、接近视觉灵敏度的光谱灵敏度特性
    5、通过50Hz/60Hz除光噪音功能实现稳定的测定
    6、光源依懒性弱
    7、最小误差为±20%
    8、受红外影响很小
        BH1750模块如下图所示
eea97de49079e2c32868bb08f066431.jpg


BH1750电气参数
        BH1750的电气参数如下表所示
电气参数1.png
电气特性2.png
        需要重点关注的是模块电源电压的额定值为4.5V,所以模块的供电电压最好用3.3V的电源供电,否则会影响模块的使用寿命。

BH1750测量程序步骤

        官方的技术文档给出了各种模块下测量的程序步骤,这对驱动的编写很有用处。测量程序步骤如下图所示

测试流程.png
        从图中可以看出,BH1750模块主要有两种测量模块,一种是一次测量,另外一种是连续测量。从测量模式的流程不难看出,一次测量模式主要应用于间歇性定时测量,这样可以节约模块的电量损耗,尤其是模块用在需要用电池供电的移动设备。但是一次测量模式带来的缺点是测量时间会变长,因为每次都需要重新给模块通电。对应的连续测量模式主要应用在对电量损耗影响可以忽略且需要实时获取光照强度值的设备中,每次测量时间相对来说会比较短一点。
        但是在测试过程中发现若发送的指令是一次测量的指令,读取数据后还可以继续读取下一次测量的数据,且数据是有效的。这一点和官方给的文档有很大的差距,可能购买的模块是改过的。这一点在大家使用过程中需要着重注意一下。

BH1750IIC设备地址
        BH1750IIC的设备地址可以通过模块引脚的ADDR逻辑电平值决定。对应模块的ADO引脚,如图所示

地址选择引脚.png
        手册中也给出当ADO(有些模块的丝印是ADDR)接不同的逻辑电平时通信的时序实例,如下图所示

地址不一样的时序.png
        从图中可以看出当ADO接逻辑低电平(GND)时,BH1750的IIC通信地址为0x23;当ADO接逻辑高电平(VCC)时,BH1750的IIC通信地址为0x5c。可以看出0x23和0x5c为七位二进制互为取反。且经过自测发现ADO的高电平逻辑的电压是可以接到5V上的,但是建议在接线的时候最好是接到和VCC保持一样的电平值。若不使用BH1750的ADO修改IIC通信地址的功能需要将ADO接至高电平或低电平,不能悬空。ADO也可以接I/O口,通过I/O来控制BH1750的IIC通信地址,但是在一个完整的指令传输中,发送的地址要和ADO的控制逻辑要一致,否则会通信失败。

BH1750支持的指令
        BH1750支持多种不同分辨率的测量模式,可以支持1lx、0.5lx和4lx不同分辨率的测试模式。也支持通过指令控制模块的供电状态以及测量时间的改变(建议不要修改测量时间,否则会影响测量的准确度,驱动也并未提供修改测量时间的接口函数,若有兴趣深入学习的朋友可以根据提供的文档进行尝试)。具体支持的指令集如下表所示
支持的指令集.png
        需要注意的是表中的L模式的分辨率为41lx与英文文档(4lx)的分辨率不一样,是因为该表是从中文文档截取的。BH1750的中文文档也是多方查找才找到的资源,虽然有错误的地方,但是并不影响整体的阅读。从测试流程可以得知测试模式有一次和连续的,从上图中也可以进一步知道在一次和连续测试模式下根据分辨率的不同又有H、H2和L模式的区分。所以BH1750总共有6中测试模式,但是在实际测试过程中只有三种测试模式,因为一次的测试模式也可以连续读取有效的数据。

BH1750驱动编写

        BH1750用的是IIC通信,所以可以复用上一篇AHT20外设移植时写的IIC指令下发和数据读取两个函数,在此只复制过来,代码很简单,详细的介绍可以看AHT20外设移植帖子。IIC写指令函数定义为
  1. void IIC_SenCmd(uint8_t _Cmd, uint8_t *Sendbuf, uint16_t _length)
  2. {
  3.         if (BH1750_ADDR)
  4.         {
  5.                 msgs[0].addr = BH1750_ADDR_H;
  6.         }
  7.         else
  8.         {
  9.                 msgs[0].addr = BH1750_ADDR_L;
  10.         }
  11.     msgs[0].flags = I2C_M_NOSTOP;
  12.     msgs[0].buffer = &_Cmd;
  13.     msgs[0].length = 1;

  14.         if (_length != 0)
  15.         {
  16.                 /* 有数据的发送 */
  17.                 if (BH1750_ADDR)
  18.                 {
  19.                         msgs[0].addr = BH1750_ADDR_H;
  20.                 }
  21.                 else
  22.                 {
  23.                         msgs[0].addr = BH1750_ADDR_L;
  24.                 }
  25.              msgs[1].flags = 0;
  26.              msgs[1].buffer = Sendbuf;
  27.              msgs[1].length = _length;
  28.              bflb_i2c_transfer(i2c, msgs, 2);
  29.         }
  30.         else
  31.         {
  32.                 /* 只发一个字节的数据 */
  33.                 msgs[0].flags = 0;
  34.                 bflb_i2c_transfer(i2c, msgs, 1);
  35.         }
  36. }
复制代码
        IIC读数据指令为
  1. void IIC_RecData(uint8_t *_Recbuf, uint16_t _length)
  2. {
  3.            if (BH1750_ADDR)
  4.         {
  5.                 msgs[0].addr = BH1750_ADDR_H;
  6.         }
  7.         else
  8.         {
  9.                 msgs[0].addr = BH1750_ADDR_L;
  10.         }
  11.     msgs[0].flags = I2C_M_READ;
  12.     msgs[0].buffer = _Recbuf;
  13.     msgs[0].length = _length;
  14.     bflb_i2c_transfer(i2c, msgs, 1);
  15. }
复制代码
        接下来是封装BH1750指令的函数。首先是电源状态控制函数,函数定义如下
  1. void BH1750_SetPowerSta(BH1750PowerSta _powerSta)
  2. {
  3.         if (BH1750_POWER_STA_ON == _powerSta)
  4.         {
  5.                 IIC_SenCmd(BH1750_POWAER_ON_CMD, 0, 0);
  6.                 //printf("Chip Power ON CMD send over\r\n");
  7.         }
  8.         else
  9.         {
  10.                 IIC_SenCmd(BH1750_POWAER_OFF_CMD, 0, 0);
  11.                 //printf("Chip Power OFF CMD send over\r\n");
  12.         }
  13.    
  14. }
复制代码
        参数_powerSta传递BH1750电源通电状态控制指令,通电需要下发0x01,断电需要下发0x00。参数枚举类型定义如下
  1. /* 芯片供电状态 */
  2. typedef enum
  3. {
  4.         BH1750_POWER_STA_OFF = 0,   /* 芯片断电 */
  5.         BH1750_POWER_STA_ON,       /* 芯片供电 */
  6.         BH1750_POWER_STA_LAST,
  7. }BH1750PowerSta;
复制代码
        指令BH1750_POWAER_ON_CMDBH1750_POWAER_OFF_CMD是两个宏定义,也是发送给BH1750指令的内容。他们的定义如下
  1. #define BH1750_POWAER_OFF_CMD             0x0       /* BH1750断电指令 */
  2. #define BH1750_POWAER_ON_CMD              0x1       /* BH1750通电指令 */
复制代码
        该函数在IIC初始化后会被调用一次,且参数为BH1750_POWAER_ON_CMD,用于给BH1750模块的芯片发送供电指令。
        然后是BH1750重置函数,函数定义如下,
  1. void BH1750_Reset(void)
  2. {
  3.         IIC_SenCmd(BH1750_RESET_CMD, 0, 0);
  4.         
  5.     //printf("Chip Reset CMD send over\r\n");
  6. }
复制代码
        重置BH1750是下发指令BH1750_RESET_CMD,且BH1750_RESET_CMD为一个宏定义,定义如下
  1. #define BH1750_RESET_CMD                  0x7       /* BH1750重置指令 */
复制代码
       参数指令下发模式函数定义如下
  1. void BH1750_SendTestMode(uint8_t _TestMode)
  2. {
  3.         IIC_SenCmd(_TestMode, 0, 0);

  4.         //printf("Chip Test Mode CMD send over\r\n");
  5. }
复制代码
        参数_TestMode可以选择下发的模式是什么,这样通用性更强。
        最后是获取并计算光照强度数据。相关函数定义为
  1. BH1750ReadRes BH1750_ReadLightIntensity(float *_LuxVal)
  2. {
  3.         uint8_t RecData[3] = {0};
  4.         uint16_t Temp = 0;

  5.         if ((BH1750_TEST_MODE == BH1750_TEST_MODE_ONCE_H_CMD) || (BH1750_TEST_MODE == BH1750_TEST_MODE_ONCE_H_2_CMD) || (BH1750_TEST_MODE == BH1750_TEST_MODE_ONCE_L_CMD))
  6.         {
  7.                 /* 芯片通电 */
  8.                 BH1750_SetPowerSta(BH1750_POWER_STA_ON);
  9.         }

  10.         BH1750_SendTestMode(BH1750_TEST_MODE);
  11.         
  12.         if ((BH1750_TEST_MODE == BH1750_TEST_MODE_CON_L_CMD) || (BH1750_TEST_MODE == BH1750_TEST_MODE_ONCE_L_CMD))
  13.         {
  14.                 bflb_mtimer_delay_ms(BH1750_TEST_DELAY_24MS);
  15.         }
  16.         else
  17.         {
  18.                 bflb_mtimer_delay_ms(BH1750_TEST_DELAY_180MS);
  19.         }

  20.         IIC_RecData(RecData, 2);

  21.         if ((BH1750_TEST_MODE == BH1750_TEST_MODE_CON_H_2_CMD) || (BH1750_TEST_MODE == BH1750_TEST_MODE_ONCE_H_2_CMD))
  22.         {
  23.                 if ((RecData[1] & 0x01) == 0x01)
  24.                 {
  25.                         Temp = (RecData[0] << 7) | (RecData[1] >> 1);
  26.                         *_LuxVal = ((float)Temp + 0.5) / 1.2;
  27.                 }
  28.                 else
  29.                 {
  30.                         Temp = (RecData[0] << 7) | (RecData[1] >> 1);
  31.                         *_LuxVal = (float)Temp / 1.2;
  32.                 }
  33.         }
  34.         else
  35.         {
  36.                 Temp = (RecData[0] << 8) | RecData[1];
  37.                 *_LuxVal = (float)Temp / 1.2;
  38.         }

  39.         return BH1750_READ_OK;
  40. }
复制代码
        函数是先下发测试模式,这里我选择的是连续H模式,通过BH1750.h文件中的宏定义决定测试模式是什么,宏定义如下
  1. #define BH1750_TEST_MODE_CON_H_CMD        0x10      /* BH1750连续H模式测试指令 */
  2. #define BH1750_TEST_MODE_CON_H_2_CMD      0x11      /* BH1750连续H2模式测试指令 */
  3. #define BH1750_TEST_MODE_CON_L_CMD        0x13      /* BH1750连续L模式测试指令 */
  4. #define BH1750_TEST_MODE_ONCE_H_CMD       0x20      /* BH1750一次H模式测试指令 */
  5. #define BH1750_TEST_MODE_ONCE_H_2_CMD     0x21      /* BH1750连续H2模式测试指令 */
  6. #define BH1750_TEST_MODE_ONCE_L_CMD       0x23      /* BH1750连续L模式测试指令 */
复制代码
        然后是延时180ms,等待芯片测量成功。不同的测量模式需要等待不同的测试时间,这里我已经把所有可选的测量模式的等待时间全部列出来,通过模式的选择确定需要等待多长时间。因为没有状态的读取,所以直接获取光照强度的数据。获取后根据光照强度的计算公式进行转换,然后将转换后的结果保存在参数指针_LuxVal中。
        由于文档没有明确给出具体的转换公式,所以只能从文档中给出的列子中去推导出转换公式

计算方式.png

        从图中可以看出,测量完毕后会收到16位(两字节)的数据,且这两字节的数据都是光照强度数值,没有其他的数据,高位在前低位在后。代码也是接收两个字节的数据(IIC_RecData(RecData, 2);)。图中的测试模式高分辨率模式和一次低分辨率模式分别对应H和L模式。通过计算公式可以推导出转换公式为:光照强度数值(lx)=((接收到的第一个字节数据  << 8)+接收到的第二个字节数据 )/1.2。其中也可以得出在L模式的测试下若收到的第二个字节数据的最低位为1的话,那么该数据或模式是有问题。因为L模式的分辨率为4lx,按照转换公式的话,接收到的光照强度数据的第二个字节数据的最低位必须为0。除了有H和L模式外还有H2模式,再次通过文档的例程可以继续推导出H2模式的转换公式。
H2.png
        从图中可以看出H2模式的各位的权值。可以看出的是收到的第二个字节数据的最低位的权值为-1,与H和L模式的权值0不一样。所以在转换过程中可以列出两种转换公式。在驱动代码中也是给出两种转换公式,也是根据BH1750.h文件中选择的测试模式进行选择转换公式。
        BH1750的初始化函数定义如下
  1. void BH1750_Init()
  2. {
  3.         /* IIC初始化 */
  4.         IIC0_Init();

  5.         /* 芯片通电 */
  6.         BH1750_SetPowerSta(BH1750_POWER_STA_ON);
  7. }
复制代码
       先是初始化IIC,这次用的外设资源是IIC0。初始化完之后是调用模块芯片供电函数,这个在前面已经介绍过了,在此不做过多介绍。
        在BH1750.c中有一个函数被注释掉,该函数的功能是轮询BH1750所有的测试模式,通过main函数的调用和数据的串口打印,可以对BH1750的各种测试模式进行轮询测试,大家若有兴趣可以将这段函数去掉注释,然后在main函数进行调用且用串口打印光照强度数值。
        函数BH1750_TestModePoll与光照强度读取函数BH1750_ReadLightIntensity的区别是函数BH1750_TestModePoll会轮询所有的测试模式并读取数据,函数BH1750_ReadLightIntensity会根据.h文件的模式选择宏定义进行读取,且若模式选择的是一次测量模式,为了与文档的测试流程一致所以先发送供电指令再发送测试模式设置,若把该步骤去掉模块也可以正常工作。
        在main函数中首先是调用BH1750的初始化代码,然后是每隔1S读取一下光强度值,将读取到的数据通过串口调试接口打印出来。通过绿灯闪烁频率为1S的方式告诉Ai-M61开发板在正常工作。main函数定义如下
  1. #include "board.h"
  2. #include "BH1750.h"
  3. #include "bflb_gpio.h"

  4. struct bflb_device_s *gpio;

  5. typedef enum
  6. {
  7.         LED_STA_OFF = 0,   /* 关闭 */
  8.         LED_STA_ON,        /* 打开 */
  9.         LED_STA_LAST,
  10. }LedSta;

  11. void LED_Init()
  12. {
  13.         /* 初始化绿灯,用来指示LED状态 */
  14.         gpio = bflb_device_get_by_name("gpio");
  15.     bflb_gpio_init(gpio, GPIO_PIN_14, GPIO_OUTPUT | GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_0);
  16. }
  17. void LED_SetSta(LedSta _Sta)
  18. {
  19.         if (_Sta)
  20.         {
  21.                 /* 高电平打开LED */
  22.                 bflb_gpio_set(gpio, GPIO_PIN_14);
  23.         }
  24.         else
  25.         {
  26.                 /* 低电平关闭LED */
  27.                 bflb_gpio_reset(gpio, GPIO_PIN_14);
  28.         }
  29. }
  30. int main(void)
  31. {
  32.         float Lux = 0;
  33.         LedSta LEDSta = LED_STA_ON;
  34.         
  35.         board_init();    /* 板卡初始化 */
  36.         LED_Init();      /* LED初始化 */
  37.         BH1750_Init();   /* 初始化BH1750 */
  38.         LED_SetSta(LEDSta);
  39.     while (1)
  40.         {
  41.                 /* 读取光照强度 */
  42.                 BH1750_ReadLightIntensity(&Lux);

  43.                 printf("BH1750 Read Light Intensity is:%.1f\r\n",Lux);
  44.         
  45.                 Lux = 0;

  46.                 bflb_mtimer_delay_ms(1000);
  47.                 /* LED状态翻转,用来识别MCU是否正常工作 */
  48.                 if (LED_STA_ON == LEDSta)
  49.                 {
  50.                         LEDSta = LED_STA_OFF;
  51.                 }
  52.                 else
  53.                 {
  54.                         LEDSta = LED_STA_ON;
  55.                 }
  56.                
  57.                 LED_SetSta(LEDSta);
  58.     }
  59. }
复制代码

BH1750驱动代码验证
        按照如下的接线方式进行接线,我用的是外设IIC0的资源获取BH1750采集到的的光强度数值,连接方式如下,
BH1750接线.png
       将程序烧进开发板后按下复位按键便可通过串口查看模块获取到的光强度值。串口打印信息如下图所示
串口打印内容.png


BH1750模块问题
        BH1750模块在写驱动的时候最大的问题是中文文档不好找,我也是在非常老的论坛上下载下来的,而且里面也有很多错误的地方,这些错误的地方也让我走了很多弯路,浪费了很长的时间。至于其他的问题,大家可以参考我写的问题汇总帖子。

BH1750驱动代码
        BH1750代码为 BH1750.rar (1.35 MB, 下载次数: 21)
BH1750接线方式.png

本帖被以下淘专辑推荐:

回复

使用道具 举报

沈夜 | 2024-1-13 16:06:20 | 显示全部楼层
学到了,感谢大佬
回复 支持 反对

使用道具 举报

WT_0213 | 2024-1-13 18:31:03 | 显示全部楼层
回复

使用道具 举报

lazy | 2024-1-13 19:42:58 | 显示全部楼层
真棒
回复

使用道具 举报

干簧管 | 2024-1-13 22:06:34 | 显示全部楼层
回复

使用道具 举报

san | 2024-1-13 23:56:33 | 显示全部楼层
回复

使用道具 举报

timo | 2024-1-14 10:25:48 | 显示全部楼层
感谢大佬
回复

使用道具 举报

sanfrans | 2024-1-22 21:44:42 | 显示全部楼层
学会学习
回复

使用道具 举报

WT_0213 | 2024-1-23 09:23:04 | 显示全部楼层
学习学习
回复

使用道具 举报

李白百 | 2024-2-1 18:53:45 | 显示全部楼层
学习
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则