STM32应用开发——BH1750光照传感器详解

news2024/9/20 3:19:27

STM32应用开发——BH1750光照传感器详解

目录

  • STM32应用开发——BH1750光照传感器详解
    • 前言
    • 1 硬件介绍
      • 1.1 BH1750简介
      • 1.2 硬件接线
    • 2 软件编程
      • 2.1 软件原理
        • 2.1.1 IIC设备地址
        • 2.1.2 IIC读写
        • 2.1.3 BH1750指令集
        • 2.1.4 BH1750工作流程
        • 2.1.5 BH1750测量模式
      • 2.2 测试代码
      • 2.3 运行测试
      • 2.4 常见问题
    • 结束语

前言

几年前我发布了一篇关于STM32+BH1750光照传感器的博客,因为大学时期经验欠缺,无论是代码还是博客,都有很多不足之处,也收到了非常多同学的反馈。于是我重新整理了代码和文章,这是一个全新版本。

1 硬件介绍

1.1 BH1750简介

BH1750FVI 是一种用于两线式串行总线接口的数字型光强度传感器集成电路。这种集成电路可以根据收集的光线强度数据来调整液晶或者键盘背景灯的亮度。利用它的高分辨率可以探测较大范围的光强度变化。

注:这个模块只适用于室内环境或者学习使用,户外场景下会超量程。

BH1750的引脚如下:

引脚号名称功能
1VCC电源正
2ADDR地址端口
3GND电源负
4SDAIIC数据线
5DVIIIC端口参考电压
6SCLIIC时钟线

注:上述引脚指的是芯片的引脚,如果用的是某宝买的那种传感器模式,引出来的排针引脚排列是不同的。

参考外围电路如下:
在这里插入图片描述

1.2 硬件接线

本文使用STM32F103作为主控MCU,STM32和BH1750的引脚连接如下表:

STM32BH1750功能
VCCVCC电源正,3.3V供电
GNDGND电源负
PB6SCLIIC时钟线,必须外接上拉电阻
PB7SDAIIC数据线,必须外接上拉电阻
GNDADDR地址端口

注:BH1750用的是模块,模块上已经有接上拉电阻了,因此STM32这边就不需要再接了。另外,ADDR引脚默认有一个下拉电阻,因此不接地其实也是没关系的。

BH1750模块的电路图如下:
在这里插入图片描述

2 软件编程

2.1 软件原理

BH1750是一个IIC设备,通讯协议遵循标准IIC协议。只需要着重关注设备地址,发送命令,读取寄存器即可。

2.1.1 IIC设备地址

所有IIC设备都有一个专属的设备地址,BH1750也不例外,BH1750一共有2个设备地址,可以通过芯片的ADDR引脚切换。
数据手册原文如下:
在这里插入图片描述
翻译过来的话,设备地址的配置如下:

ADDR引脚电平7bit地址8bit地址
高电平0x5C0xB8
低电平0x230x46

注:这里解释一下7bit地址和8bit地址。
IIC设备的地址一般来说都只有7位,也就是7bit地址,BH1750数据手册上面写的也是7bit地址。
但是设备地址并不是单独使用的,在地址后面需要补1个读写位(不懂的先看下IIC通信协议),也就相当于把7bit的地址左移了1位,那么得到的值就是8bit地址。
所以,在IIC进行读写的时候,要用7bit地址还是8bit地址取决于你这个读写函数里面有没有对这个地址进行移位操作。

2.1.2 IIC读写

1、IIC写入流程。
在这里插入图片描述
时序解释:先发送设备地址+读写位,再发送指令数据。

题外话:
大多数IIC设备,在发送的这串数据中,往往会把第1个字节的数据作为1个寄存器地址(有些也叫偏移),随后才是真正的数据。这样一来,就可以通过1个字节或多个字节的寄存器地址,来实现数据和命令的扩展。
不过BH1750可能是因为功能比较简单,它没有采用这种方式,读写似乎都在同一个寄存器里面操作。

2、IIC读取流程。
在这里插入图片描述
时序解释:先发送设备地址+读写位,随后读取两个数据(其中高位数据在前,低位数据在后)。

题外话:
这个读取的流程跟绝大多数IIC设备的读取流程是一样的。

2.1.3 BH1750指令集

在初始化BH1750时,通过发送1个字节的指令即可配置BH1750的模式。

详细指令集如下表:
在这里插入图片描述

2.1.4 BH1750工作流程

根据数据手册的描述,配置流程如下图:

在这里插入图片描述
总结一下这个流程:
第1步:给BH1750供电。
第2步:BH1750上电后默认为断电模式(此断电模式不是说芯片没有电,而是芯片没有进入工作模式)。
第3步:通过发送指令,把BH1750配置为通电模式(此时芯片进入工作模式)。
第4步:发送测量指令(测量方式有多种,详细信息看上面2.1.3的指令集)。
第5步:读取测量结果并转换成光照值。

按照上面这个流程走,就可以使BH1750正常工作。

2.1.5 BH1750测量模式

BH1750有6种测量模式,每种模式有各自的特点,详见下表:

测量模式精度测量时间备注
一次H分辨率模式1 lx120ms该模式配置完成后会自动进行一次测量,测量完成后会切换到断电模式
一次H分辨率模式20.5 lx120ms该模式配置完成后会自动进行一次测量,测量完成后会切换到断电模式
一次L分辨率模式4 lx16ms该模式配置完成后会自动进行一次测量,测量完成后会切换到断电模式
连续H分辨率模式1 lx120ms该模式配置完成后会自动进行连续测量,无需重复配置
连续H分辨率模式20.5 lx120ms该模式配置完成后会自动进行连续测量,无需重复配置
连续L分辨率模式4 lx16ms该模式配置完成后会自动进行连续测量,无需重复配置

总结:
L分辨率模式采集时间短,但精度差,适用于采集光照变化大且变化非常快的场景。
H分辨率模式采集时间长,但精度高,适用于光照变化速度不快的场景。
一次采集模式适用于采集间隔时间长或者需要省电的场景。
连续采集模式适用于对设备节能没有要求的大多数场景。

2.2 测试代码

根据上述原理,编写测试代码,主要的代码清单如下:

文件名代码内容
iic_software.h定义了软件IIC所需的引脚定义和电平设置,方便修改IO或者移植
iic_software.c软件IIC代码主体
iic_hardware.h定义了硬件IIC所需的引脚定义和电平设置,方便修改IO或者移植
iic_hardware.c硬件IIC代码主体
bh1750.h定义了BH1750的相关信息,如设备地址,命令等
bh1750.cBH1750驱动主体,底层调用软件IIC接口
uart.c串口主体,用来打印测量过程和结果
main.c主函数

注:
1、除了串口的代码,其他代码全部都在下面贴出来了。串口的教程很多,这里就不细说了。
2、我这里采用软件IIC和硬件IIC两种方式测试,但实际上两种方式只能同时使用其中一种。
3、本文主要介绍如何驱动BH1750,尝试了几种测量模式,结果都由串口打印出来,不需要使用串口的话注释掉即可,如果需要现在在LCD屏或者OLED屏,请自行增加这部分的代码。

测试示例代码如下:

iic_software.h :
注:打开宏USE_SOFTWARE_IIC,表示使用软件IIC。此时要注释掉iic_hardware.h的宏USE_HARDWARE_IIC

#ifndef __IIC_SOFTWARE_H__
#define __IIC_SOFTWARE_H__

#define USE_SOFTWARE_IIC   // 使用软件IIC(注:软件IIC和硬件IIC只能同时使用一种)

#ifdef USE_SOFTWARE_IIC
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"

// IIC SCL引脚配置
#define IIC_SCL_CLOCK   RCC_APB2Periph_GPIOB     // 时钟
#define IIC_SCL_PORT    GPIOB                    // 端口
#define IIC_SCL_PIN     GPIO_Pin_6               // 引脚号
// IIC SCL电平设置
#define SET_IIC_SCL(s)  s > 0 ? GPIO_SetBits(IIC_SCL_PORT, IIC_SCL_PIN) : GPIO_ResetBits(IIC_SCL_PORT, IIC_SCL_PIN)

// IIC SDA引脚配置
#define IIC_SDA_CLOCK   RCC_APB2Periph_GPIOB     // 时钟
#define IIC_SDA_PORT    GPIOB                    // 端口
#define IIC_SDA_PIN     GPIO_Pin_7               // 引脚号
// IIC SDA电平设置
#define SET_IIC_SDA(s)  s > 0 ? GPIO_SetBits(IIC_SDA_PORT, IIC_SDA_PIN) : GPIO_ResetBits(IIC_SDA_PORT, IIC_SDA_PIN)  
#define READ_IIC_SDA    GPIO_ReadInputDataBit(IIC_SDA_PORT, IIC_SDA_PIN)

void iic_init(void);
uint8_t iic_write_bytes(uint8_t addr, uint8_t *buf, uint8_t buf_size);
uint8_t iic_receive_bytes(uint8_t addr, uint8_t *buf, uint8_t buf_size);
#endif
#endif

iic_software.c :

/**************************************************************************
 * 文件名  :iic_software.c
 * 描述    :软件模拟IIC程序  
****************************************************************************/
#include "iic_software.h"
#include "delay.h"
#include "stdio.h"

#ifdef USE_SOFTWARE_IIC  // 使用软件IIC(注:软件IIC和硬件IIC只能同时使用一种)
/* IIC软件模拟时序延时函数 */
void iic_delay(uint16_t us)
{
    delay_us(us);
}

/* IIC起始信号 */
void set_iic_sda_mode(uint8_t mode)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    if(mode > 0)
    {// 设置为输出模式
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD; // 开漏或推挽,外部需要接上拉电阻
    }
    else
    {// 设置为输入模式
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; // 浮空或上拉,外部需要接上拉电阻
    }
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStruct.GPIO_Pin = IIC_SDA_PIN;
    GPIO_Init(IIC_SDA_PORT, &GPIO_InitStruct);
}

/* IIC起始信号 */
void iic_start(void)
{
    SET_IIC_SDA(1);
    SET_IIC_SCL(1);
    iic_delay(5);
    SET_IIC_SDA(0);     
    SET_IIC_SCL(0);          
    iic_delay(5);  
}

/* IIC停止信号 */
void iic_stop(void)
{
    SET_IIC_SCL(0);
    SET_IIC_SDA(0);                
    iic_delay(5);                    
    SET_IIC_SCL(1);           
    SET_IIC_SDA(1);                  
    iic_delay(5);
}

/* IIC发送应答信号 */
uint8_t iic_send_ack(int ack)
{
    set_iic_sda_mode(1);
    if(ack == 1)
    {// 发送ACk
        SET_IIC_SDA(1); 
    }
    else if(ack == 0)
    {// 发送NACK
        SET_IIC_SDA(0); 
    }
    else
    {
        return 0;  // 入参有误,发送失败
    }         
    SET_IIC_SCL(1);     
    iic_delay(5);     
    SET_IIC_SCL(0);     
    iic_delay(5);    

    return 1;  // 发送成功
}

/* IIC接收应答信号 */
uint8_t iic_wait_ack(void)
{
    uint8_t ack = 0;
    uint8_t timeout = 5;

    SET_IIC_SDA(1); 
    set_iic_sda_mode(0);  // SDA输入模式
    SET_IIC_SCL(1);           
    iic_delay(5);  
    while(timeout--)       
    {// 等待从设备发送ACK
        if(READ_IIC_SDA == 0)
        {// 读到应答信号
            ack = 1;  
        }
        else
        {// 没有读到应答信号,继续等待
            iic_delay(1);
            if(timeout == 0)
            {// 等待超时
                ack = 0; 
            }
        }
    }           
    SET_IIC_SCL(0);             
    iic_delay(5);        
    set_iic_sda_mode(1); // SDA输出模式
    
    return ack;
}

/* IIC总线发送1个字节数据 */
uint8_t iic_send_byte(uint8_t dat)
{
    uint8_t i;
    for (i = 0; i < 8; i++)
    {
        if(0x80 & dat)
        {
            SET_IIC_SDA(1); 
        }
        else
        {
            SET_IIC_SDA(0); 
        }
        dat <<= 1;
        SET_IIC_SCL(1);          
        iic_delay(5);             
        SET_IIC_SCL(0); 
        iic_delay(5);
    }

    return iic_wait_ack();  // 返回从设备应答信号
}

/* IIC总线接收1个字节数据 */
uint8_t iic_receive_byte(void)
{
    uint8_t i;
    uint8_t dat = 0;
    uint8_t bit;

    set_iic_sda_mode(0);  // SDA输入模式
    for (i = 0; i < 8; i++)
    {
        dat <<= 1;
        SET_IIC_SCL(1);
        iic_delay(5);
        if(READ_IIC_SDA == 1)
        {
            bit = 0X01;
        }
        else
        {
            bit = 0x00;  
        }
        dat |= bit;        //读数据    
        SET_IIC_SCL(0);
        iic_delay(5);
    }        
    set_iic_sda_mode(1); // SDA输出模式

    return dat;
}

/* IIC发送多个数据 */
uint8_t iic_write_bytes(uint8_t addr, uint8_t *buf, uint8_t buf_size)
{
    uint8_t i;
    uint8_t result = 0;

    iic_start();                   // 起始信号
    if(iic_send_byte(addr << 1))   // 发送设备地址(7bit地址)
    {// 收到应答,发送成功
        for (i = 0; i < buf_size; i++)  // 发送数据
        {
            if(iic_send_byte(buf[i]))
            {// 收到应答,发送成功
                result = 1;
            }
            else
            {// 没有收到应答,发送失败
                result = 0; 
            }
        }
    }
    iic_stop();                   // 发送停止信号

    return result;
}

/* IIC接收多个数据 */
uint8_t iic_receive_bytes(uint8_t addr, uint8_t *buf, uint8_t buf_size)
{
    uint8_t i;    
    uint8_t result = 0;

    iic_start();                        // 起始信号
    if(iic_send_byte((addr << 1) | 1))  // 发送设备地址(7bit地址)
    {
        for (i = 0; i < buf_size; i++)    // 连续读取数据
        {
            *buf++ = iic_receive_byte(); 
            if (i == buf_size - 1)
            {
                iic_send_ack(1);   // 最后一个数据需要回NACK
            }
            else
            {        
                iic_send_ack(0);   // 发送ACK
            }
        }
        result = 1;
    }
    iic_stop();                // 停止信号

	return result;
}

/* IIC初始化 */
void iic_init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;

    RCC_APB2PeriphClockCmd(IIC_SCL_CLOCK, ENABLE); 
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;  
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStruct.GPIO_Pin = IIC_SCL_PIN;
    GPIO_Init(IIC_SCL_PORT, &GPIO_InitStruct); 

    RCC_APB2PeriphClockCmd(IIC_SDA_CLOCK, ENABLE); 
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;  
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStruct.GPIO_Pin = IIC_SDA_PIN;
    GPIO_Init(IIC_SDA_PORT, &GPIO_InitStruct); 
}
#endif

iic_hardware.h :
注:打开宏USE_HARDWARE_IIC,表示使用硬件IIC。此时要注释掉iic_software.h的宏USE_SOFTWARE_IIC

#ifndef __IIC_HARDWARE_H__
#define __IIC_HARDWARE_H__

// #define USE_HARDWARE_IIC   // 使用硬件IIC(注:软件IIC和硬件IIC只能同时使用一种)

#ifdef USE_HARDWARE_IIC
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_i2c.h"
#include "stm32f10x_dma.h"
#include "misc.h"

// IIC 端口配置
#define IIC_PORT              I2C1
#define IIC_PORT_CLOCK        RCC_APB1Periph_I2C1
#define I2C_SPEED             400000             // IIC速率
#define I2C_SLAVE_ADDRESS7    0xA0               // IIC本机地址(无用)
#define IIC_LONG_TIMEOUT      10 * 0x1000        // IIC通讯超时时间

// IIC SCL引脚配置
#define IIC_SCL_CLOCK    RCC_APB2Periph_GPIOB     // 时钟
#define IIC_SCL_PORT     GPIOB                    // 端口
#define IIC_SCL_PIN      GPIO_Pin_6               // 引脚号

// IIC SDA引脚配置
#define IIC_SDA_CLOCK    RCC_APB2Periph_GPIOB     // 时钟
#define IIC_SDA_PORT     GPIOB                    // 端口
#define IIC_SDA_PIN      GPIO_Pin_7               // 引脚号

// IIC DMA设置
#define I2C_DMA                      DMA1   
// #define I2C_DMA_FLAG_TX_TC           DMA1_IT_TC6   
// #define I2C_DMA_FLAG_TX_GL           DMA1_IT_GL6 
// #define I2C_DMA_CHANNEL_TX           DMA1_Channel6
// #define I2C_DMA_TX_IRQn              DMA1_Channel6_IRQn
#define I2C_DMA_FLAG_RX_TC           DMA1_IT_TC7 
#define I2C_DMA_FLAG_RX_GL           DMA1_IT_GL7    
#define I2C_DMA_CHANNEL_RX           DMA1_Channel7 
#define I2C_DMA_RX_IRQn              DMA1_Channel7_IRQn 
#define I2C_DMA_CLK                  RCC_AHBPeriph_DMA1
#define I2C_DR_Address               ((uint32_t)0x40005410)
#define I2C_DMA_PREPRIO              0     // DMA中断优先级
#define I2C_DMA_SUBPRIO              0   

void iic_init(void);
uint8_t iic_write_bytes(uint8_t addr, uint8_t *buf, uint8_t buf_size);
uint8_t iic_receive_bytes(uint8_t addr, uint8_t *buf, uint8_t buf_size);
#endif
#endif

iic_hardware.c :

/**************************************************************************
 * 文件名  :iic_hardware.c
 * 描述    :硬件IIC程序  
****************************************************************************/
#include "iic_hardware.h"
#include "delay.h"
#include "stdio.h"
#include "string.h"

#ifdef USE_HARDWARE_IIC  // 使用软件IIC(注:软件IIC和硬件IIC只能同时使用一种)

uint8_t dma_rx_buffer[2];
uint8_t dma_receive_flag = 0;

/* IIC发送多个数据 */
uint8_t iic_write_bytes(uint8_t addr, uint8_t *buf, uint8_t buf_size)
{ 
    uint32_t timeout;

    /*!< While the bus is busy */
    timeout = IIC_LONG_TIMEOUT;
    while(I2C_GetFlagStatus(IIC_PORT, I2C_FLAG_BUSY))
    {
        if((timeout--) == 0) 
            return 0;
    }
    
    /*!< Send START condition */
    I2C_GenerateSTART(IIC_PORT, ENABLE);
    /*!< Check EV5 and clear it */
    timeout = IIC_LONG_TIMEOUT;
    while(!I2C_CheckEvent(IIC_PORT, I2C_EVENT_MASTER_MODE_SELECT))
    {
        if((timeout--) == 0) 
            return 0;
    }
    
    /*!< Send address for write */
    I2C_Send7bitAddress(IIC_PORT, (addr<<1), I2C_Direction_Transmitter);
    /*!< Check EV6 and clear it */
    timeout = IIC_LONG_TIMEOUT;
    while(!I2C_CheckEvent(IIC_PORT, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
    {
        if((timeout--) == 0) 
            return 0;
    }
    
    /*!< Send N byte data */
    while(buf_size--)
    {
        I2C_SendData(IIC_PORT, *buf);

        /*!< Check EV8 and clear it */
        timeout = IIC_LONG_TIMEOUT; 
        while(!I2C_CheckEvent(IIC_PORT, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
        {
            if((timeout--) == 0) 
                return 0;
        } 
    }

    /*!< Send STOP condition */
    I2C_GenerateSTOP(I2C1, ENABLE);

    /* If all operations OK, return 1 */
    return 1;
}

/**
  * @brief  This function handles DMA1 Channel 7 interrupt request.
  * @param  None
  * @retval None
  */
void DMA1_Channel7_IRQHandler(void)
{
    if(DMA_GetITStatus(DMA1_IT_TC7))
    {
        DMA_ClearITPendingBit(DMA1_IT_GL7);
        I2C_AcknowledgeConfig(IIC_PORT, DISABLE);
        I2C_GenerateSTOP(IIC_PORT, ENABLE);
        dma_receive_flag = 1;  // 已收到数据
    }
}

/* IIC接收多个数据 */
uint8_t iic_receive_bytes(uint8_t addr, uint8_t *buf, uint8_t buf_size)
{
    uint32_t timeout;

    /*!< While the bus is busy */
    timeout = IIC_LONG_TIMEOUT;
    while(I2C_GetFlagStatus(IIC_PORT, I2C_FLAG_BUSY))
    {
        if((timeout--) == 0) 
            return 0;
    }

    /*!< Send START condition */
    I2C_GenerateSTART(IIC_PORT, ENABLE);
    /*!< Check EV5 and clear it */
    timeout = IIC_LONG_TIMEOUT;
    while(!I2C_CheckEvent(IIC_PORT, I2C_EVENT_MASTER_MODE_SELECT))
    {
        if((timeout--) == 0) 
            return 0;
    }
    
    /*!< Send address for read */
    I2C_Send7bitAddress(IIC_PORT, (addr<<1), I2C_Direction_Receiver);
    /*!< Test on EV6 and clear it */
    timeout = IIC_LONG_TIMEOUT;
    while(!I2C_CheckEvent(IIC_PORT, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
    {
        if((timeout--) == 0) 
            return 0;
    }  
    
    I2C_AcknowledgeConfig(IIC_PORT, ENABLE);
    
    /* Inform the DMA that the next End Of Transfer Signal will be the last one */
    I2C_DMALastTransferCmd(IIC_PORT, ENABLE); 

    /* Configure the DMA Rx Channel with the buffer address and the buffer size */
    DMA_Cmd(I2C_DMA_CHANNEL_RX, DISABLE); 
    DMA_SetCurrDataCounter(DMA1_Channel7, buf_size);
    DMA_Cmd(I2C_DMA_CHANNEL_RX, ENABLE); 

    /* 接收数据 */
    timeout = IIC_LONG_TIMEOUT;
    while(!dma_receive_flag)
    {// 等待数据接收完成
        if((timeout--) == 0) 
        {
            dma_receive_flag = 0;
            return 0;
        }
    }
    memcpy(buf, dma_rx_buffer, buf_size);

    /* If all operations OK, return 1 */
    return 1;
}

/* IIC初始化 */
void iic_init(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;  
    DMA_InitTypeDef   DMA_InitStructure;   
    I2C_InitTypeDef  I2C_InitStructure; 

    /*!< I2C_SCL_GPIO_CLK and I2C_SDA_GPIO_CLK Periph clock enable */
    RCC_APB2PeriphClockCmd(IIC_SCL_CLOCK | IIC_SDA_CLOCK, ENABLE);

    /*!< I2C Periph clock enable */
    RCC_APB1PeriphClockCmd(IIC_PORT_CLOCK, ENABLE);
        
    /*!< GPIO configuration */  
    /*!< Configure I2C pins: SCL */
    GPIO_InitStructure.GPIO_Pin = IIC_SCL_PIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
    GPIO_Init(IIC_SCL_PORT, &GPIO_InitStructure);

    /*!< Configure I2C pins: SDA */
    GPIO_InitStructure.GPIO_Pin = IIC_SDA_PIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
    GPIO_Init(IIC_SDA_PORT, &GPIO_InitStructure); 

    /* Configure and enable I2C DMA RX Channel interrupt */
    NVIC_InitStructure.NVIC_IRQChannel = I2C_DMA_RX_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = I2C_DMA_PREPRIO;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = I2C_DMA_SUBPRIO;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);  
    
    /*!< I2C DMA RX channels configuration */
    /* Enable the DMA clock */
    RCC_AHBPeriphClockCmd(I2C_DMA_CLK, ENABLE);

    /* I2C RX DMA Channel configuration */
    DMA_DeInit(I2C_DMA_CHANNEL_RX);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)I2C_DR_Address;
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&dma_rx_buffer;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; 
    DMA_InitStructure.DMA_BufferSize = 2;         
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(I2C_DMA_CHANNEL_RX, &DMA_InitStructure);  
    
    /* Enable the DMA Channels Interrupts */
    DMA_ITConfig(I2C_DMA_CHANNEL_RX, DMA_IT_TC, ENABLE);  

    /*!< I2C configuration */
    /* I2C configuration */
    I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
    I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
    I2C_InitStructure.I2C_OwnAddress1 = I2C_SLAVE_ADDRESS7;
    I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
    I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    I2C_InitStructure.I2C_ClockSpeed = I2C_SPEED;
    
    /* Apply I2C configuration after enabling it */
    I2C_Init(IIC_PORT, &I2C_InitStructure);

    /* I2C Peripheral Enable */
    I2C_Cmd(IIC_PORT, ENABLE);

    /* Enable the I2C peripheral DMA requests */
    I2C_DMACmd(IIC_PORT, ENABLE);
}
#endif

bh1750.h :

#ifndef __BH1750_H__
#define __BH1750_H__

#include "stm32f10x.h"

#define BH1750_ADDRESS_LOW      0x23    // addr low  7bit:0x23 8bit:0x46
#define BH1750_ADDRESS_HIGH     0x5C    // addr high 7bit:0x5C 8bit:0xB8

#define BH1750_ADDRESS          BH1750_ADDRESS_LOW  

/*bh1750 registers define */
#define BH1750_POWER_ON			0x01	// power on
#define BH1750_POWER_DOWN   	0x00	// power down
#define BH1750_RESET			0x07	// reset	
#define BH1750_CON_H_RES_MODE	0x10	// Continuously H-Resolution Mode
#define BH1750_CON_H_RES_MODE2	0x11	// Continuously H-Resolution Mode2 
#define BH1750_CON_L_RES_MODE	0x13	// Continuously L-Resolution Mode
#define BH1750_ONE_H_RES_MODE	0x20	// One Time H-Resolution Mode
#define BH1750_ONE_H_RES_MODE2	0x21	// One Time H-Resolution Mode2
#define BH1750_ONE_L_RES_MODE	0x23	// One Time L-Resolution Mode

uint8_t bh1750_init(void);
void bh1750_read_example(void);

#endif

bh1750.c :

/**************************************************************************
 * 文件名  :bh1750.c
 * 描述    :光强传感模块     
****************************************************************************/
#include "bh1750.h"
#include "delay.h"
#include "iic_hardware.h"
#include "iic_software.h"

#define LOG_ENABLE

#ifdef LOG_ENABLE
    #include "stdio.h"
    #define LOG   printf
#else
    #define LOG(format, ...)    
#endif

uint8_t current_mode;    // BH1750的测量模式
float current_light;     // BH1750的测量光照值

// BH1750延时函数
void bh1750_delay(uint16_t ms)
{// ms级延时,BH1750每次测量都需要时间,该函数用于等待测量结果
    delay_ms(ms);
}

// 写命令
uint8_t bh1750_write_cmd(uint8_t cmd)
{
    return iic_write_bytes(BH1750_ADDRESS, &cmd, 1);
}

// 写寄存器
uint8_t bh1750_read_regs(uint8_t *buf, uint8_t buf_size)
{
    return iic_receive_bytes(BH1750_ADDRESS, buf, buf_size);
}

// 复位
uint8_t bh1750_reset(void)
{
    return bh1750_write_cmd(BH1750_RESET);
}
// 打开电源
uint8_t bh1750_power_on(void)
{
    return bh1750_write_cmd(BH1750_POWER_ON);
}

// 关闭电源
uint8_t bh1750_power_down(void)
{
    return bh1750_write_cmd(BH1750_POWER_DOWN);
}

// 设置测量模式
uint8_t bh1750_set_measure_mode(uint8_t mode)
{
    uint8_t result = 0;

    if(bh1750_write_cmd(mode))
    {
        result =  1;
    }
    return result;
}

// 单次读取光照值
uint8_t bh1750_single_read_light(uint8_t mode, float *light)
{// 单次测量模式在测量后会自动设置为断电模式   
    uint8_t temp[2];
    uint8_t result = 0;
    
    if(mode != BH1750_ONE_H_RES_MODE && mode != BH1750_ONE_H_RES_MODE2 && mode != BH1750_ONE_L_RES_MODE)
    {
        LOG("bh1750 single read measure mode error! mode:0x%02x\n", mode);
        return result;
    }
    if(bh1750_set_measure_mode(mode)) // 每次采集前先设置模式
    {
        LOG("bh1750 set measure mode success! mode:0x%02x\n", mode);
        current_mode = mode;
        switch (mode)
        {
        case BH1750_ONE_H_RES_MODE:  // 单次H分辨率模式(精度1lx,测量时间120ms)
            bh1750_delay(120);  // 等待采集完成
            break;
        case BH1750_ONE_H_RES_MODE2: // 单次H分辨率模式(精度0.5lx,测量时间120ms)
            bh1750_delay(120);  // 等待采集完成
            break;
        case BH1750_ONE_L_RES_MODE:  // 单次L分辨率模式(精度4lx,测量时间16ms)
            bh1750_delay(16);  // 等待采集完成
            break;
        default:
            break;
        }
        if(bh1750_read_regs(temp, 2))  // 读取测量结果
        {
            *light = ((float)((temp[0] << 8) + temp[1]) / 1.2); // 换算成光照值
            result = 1;
        }
        else
        {
            LOG("bh1750 read light failed!");
        }
    }
    else
    {
        LOG("bh1750 set measure mode failed! mode:0x%02x\n", mode);
        return result;
    }
    
    return result;
}

// 连续读取光照值
uint8_t bh1750_continuous_read_light(uint8_t mode, float *light)
{   
    uint8_t temp[2];
    uint8_t result = 0;
    
    if(mode != BH1750_CON_H_RES_MODE && mode != BH1750_CON_H_RES_MODE2 && mode != BH1750_CON_L_RES_MODE)
    {
        LOG("bh1750 continuous read measure mode error! mode:0x%02x\n", mode);
        return result;
    }

    if(mode != current_mode)
    {// 要使用的测量模式和BH1750当前的模式不同,则配置成相同模式
        if(bh1750_set_measure_mode(mode))
        {
            LOG("bh1750 set measure mode success! mode:0x%02x\n", mode);
            current_mode = mode;
        }
        else
        {// 模式设置失败
            LOG("bh1750 set measure mode failed! mode:0x%02x\n", mode);
            return result;
        }
        switch (mode)
        {
        case BH1750_CON_H_RES_MODE:  // 连续H分辨率模式(精度1lx,测量时间120ms)
            bh1750_delay(120);  // 等待采集完成
            break;
        case BH1750_CON_H_RES_MODE2: // 连续H分辨率模式(精度0.5lx,测量时间120ms)
            bh1750_delay(120);  // 等待采集完成
            break;
        case BH1750_CON_L_RES_MODE:  // 连续L分辨率模式(精度4lx,测量时间16ms)
            bh1750_delay(16);  // 等待采集完成
            break;
        default:
            break;
        }
    }
    if(bh1750_read_regs(temp, 2))  // 读取测量结果
    {
        *light = ((float)((temp[0] << 8) + temp[1]) / 1.2); // 换算成光照值
        result = 1;
    }
    else
    {
        LOG("bh1750 read light failed!");
    }
    
    return result;
}

// BH1750初始化
uint8_t bh1750_init(void)
{
    uint8_t result = 0;
    iic_init();        // IIC初始化
    result = bh1750_power_on(); // 打开BH1750电源
    current_mode = 0;
    return result;
}

// BH1750读取例程
void bh1750_read_example(void)
{
#if 1
    // 单次采集模式
    if(bh1750_single_read_light(BH1750_ONE_H_RES_MODE2, &current_light))
    {
        LOG("bh1750 single read light: %0.1f\n", current_light);
    }
    bh1750_delay(100);
#else
    // 连续采集模式
    if(bh1750_continuous_read_light(BH1750_CON_H_RES_MODE2, &current_light))
    {
        LOG("bh1750 continuous read light: %0.1f\n", current_light);
    }
    bh1750_delay(120); // 注:因为连续读取函数里面没有做延迟等待处理,因此这里的等待时间如果小于测量时间,那么读取到的值有可能是上一轮测量的值
#endif
}

main.c :

#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "bh1750.h"

int main(void)
{ 
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    delay_init();              
    uart_init(115200);      
    bh1750_init();
    while(1)
    {
        bh1750_read_example();
    }        
}    

2.3 运行测试

通过逻辑分析仪抓取IIC通讯的波形,分别测试了一次H分辨率模式2连续H分辨率模式2,结果如下:

1、一次H分辨率模式2时序测量
初始化设备:
先发送通电命令(0x01),再设置为单次 H 分辨率模式 2(0x21)。
请添加图片描述
读取光照值(设备地址为0x23):
在这里插入图片描述
串口打印结果:
在这里插入图片描述

2、连续H分辨率模式2时序测量
初始化设备:
先发送通电命令(0x01),再设置为连续 H 分辨率模式 2(0x11)。
在这里插入图片描述

读取光照值(设备地址为0x23):
在这里插入图片描述
串口打印结果:
在这里插入图片描述

3、其他测量
1、测量软件IIC时钟周期:
软件模拟IIC的速率约75kHz。
查看数据手册,BH7150对CLK时钟要求是小于等于400kHz,而IIC设备常用的速率是100k,200k和400k,速率慢点会更稳定,但通信时间会加长,因此最好根据实际情况调整。需要调快的话可以通过减少上面模拟IIC收发函数的CLK延时时间来实现。
在这里插入图片描述

2、测量硬件IIC时钟周期:
硬件IIC速率设置的是400kHz,实测很稳定。
在这里插入图片描述

测试结果:IIC波形没有异常,串口结果没有异常,BH1750工作一切正常。

2.4 常见问题

从我发布第一篇关于BH1750的文章以来,收到了非常多的私信和评论,也收集了不少问题,总结下来基本上都是IIC通信问题。

就拿我上面的测试用例来说,如果出现以下log,就说明STM32和BH1750的通信是失败的。(举个例子,不接入BH1750,让MCU自己跑,就会出现以下log,因为此时STM32就是访问不到从设备,正确连接设备以后,这个log就不会出现了)
在这里插入图片描述

排查方向:
1、检查STM32和BH1750各自的电源,接线是否正确,供电电压是否正常,可以借助万用表测量。
2、检查STM32使用的IIC的引脚和BH1750的IIC引脚是否一致且正确连接。
3、检查IIC总线上面是否有外部上拉电阻,阻值是否合理(一般2-10kΩ),如果用的是模块的话,可以看下模块的原理图,确认一下是否有上拉电阻。
4、检查BH1750的设备地址和代码配置的是否一致。
5、检查MCU和BH1750之间的接线是否正确且导通(之前有遇到过杜邦线内部断开的情况),可以借助万用表测量。
6、确认MCU和BH1750是否有损坏(之前有同学说BH1750模块被焊坏了)。
7、建议先用本文的测试用例跑一遍,因为这个代码经过测试,至少能保证软件的正确性,以此来排查硬件问题。有些同学是在我这移植了部分代码,或者在这基础上增加了其他代码,导致出现了bug。
8、如果以上方法都没用,就需要借助万用表或者逻辑分析仪,实际去抓取一下IIC通信的波形,首先要确认的是STM32是否发送了设备地址,BH1750是否有应答,如果没有,要么接线异常,要么地址不对,要么就是传感器坏了,大概率都是这些问题。

总结:在软件经过测试并保证正常运行的情况下,如果出现IIC通信异常问题(如上述log),归根结底还是硬件问题。

结束语

关于STM32如何驱动BH1750的讲解就到这里,如果还有什么问题,欢迎在评论区留言。

源码下载:
链接:https://pan.baidu.com/s/1aX2bYBAOgnkBlL2lzmGYjg
提取码:1234

如果这篇文章能够帮到你,就…你懂的。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1612710.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024年腾讯云免费服务器最新申请入口链接

腾讯云免费服务器申请入口 txybk.com/go/free 免费服务器可选轻量应用服务器和云服务器CVM&#xff0c;轻量配置可选2核2G3M、2核8G7M和4核8G12M&#xff0c;CVM云服务器可选2核2G3M和2核4G3M配置&#xff0c;腾讯云百科txybk.com分享2024年最新腾讯云免费服务器申请入口、限制…

基于SpringBoot + Vue实现的时装购物管理系统设计与实现+毕业论文+开题报告+答辩PPT

介绍 系统包含用户、管理员两个角色 管理员&#xff1a;首页、个人中心、用户管理、商品分类管理、颜色管理、商品信息管理、商品评价管理、系统管理、订单管理 用户:首页、个人中心、商品评价管理、我的收藏管理、订单管理 前台首页:首页、商品信息、商品资讯、个人中心、后台…

Android Studio实现页面跳转

建立文件 temp.xml <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"…

Python | Leetcode Python题解之第31题下一个排列

题目&#xff1a; 题解&#xff1a; class Solution:def nextPermutation(self, nums: List[int]) -> None:i len(nums) - 2while i > 0 and nums[i] > nums[i 1]:i - 1if i > 0:j len(nums) - 1while j > 0 and nums[i] > nums[j]:j - 1nums[i], nums[j…

SPI接口的74HC595驱动数码管实现

摸鱼记录 Day_17 (((^-^))) review 前边已经学习了&#xff1a; 数码管显示原理&#xff1a;数码管动态扫描显示-CSDN博客 且挖了个SPI的坑坑 1. 今日份摸鱼任务 学习循环移位寄存器18 串行移位寄存器原理详解_哔哩哔哩_bilibili 学习SPI接口的74HC595驱动数码管19 SPI…

【编程Tool】VS code安装与使用配置保姆级教程

目录 1.软件介绍 2.软件下载&#xff1a; 3.安装 3.1. 双击可执行文件 3.2. 同意协议 3.3. 选择安装路径&#xff0c;默认在C盘 3.4. 点击下一步 3.5. 可选择所有附加任务 3.6. 点击安装 3.7. 等待安装 3.8. 点击完成 3.9. 安装成功 4.下载MinGW64 4.1. MinGW-64下载地址 &…

电脑缺失d3dcompiler_43.dll如何修复?多种修复dll问题的有效方法分享

当用户尝试在个人计算机上运行特定的软件游戏时&#xff0c;系统弹出了一条错误提示信息&#xff0c;明确指出“d3dcompiler_43.dll”文件缺失。这个动态链接库文件(dll)是Direct3D编译器的重要组成部分&#xff0c;对于许多基于Windows操作系统的应用程序&#xff0c;尤其是那…

LeetCode 热题 100 题解:普通数组部分

文章目录 题目一&#xff1a;最大子数组和&#xff08;No. 53&#xff09;题解 题目二&#xff1a;合并区间&#xff08;No. 56&#xff09;题解 题目三&#xff1a;轮转数组&#xff08;No. 189&#xff09;题解 题目四&#xff1a;除自身以外数组的乘积&#xff08;No. 238&a…

从例题出发,提高离散数学兴趣(一)集合关系

关系的性质&#xff1a;(反)自反性&#xff0c;&#xff08;反&#xff09;对称性&#xff0c;可传递性&#xff01; 例题一&#xff1a; 复合关系与逆关系&#xff1a; 例题二&#xff1a; 覆盖与划分与等价关系&#xff1a; 重要的证明&#xff1a; 偏序关系&#xff08;自反…

【高阶数据结构】并查集 -- 详解

一、并查集的原理 1、并查集的本质和概念 &#xff08;1&#xff09;本质 并查集的本质&#xff1a;森林。 &#xff08;2&#xff09;概念 在一些应用问题中&#xff0c;需要将 n 个不同的元素划分成一些不相交的集合。 开始时&#xff0c;每个元素自成一个单元素集合&…

Spring Cloud 运维篇1——Jenkins CI/CD 持续集成部署

Jenkins 1、Jenkins是什么&#xff1f; Jenkins 是一款开源 CI/CD 软件&#xff0c;用于自动化各种任务&#xff0c;包括构建、测试和部署软件。 Jenkins 支持各种运行方式&#xff0c;可通过系统包、Docker 或者一个独立的 Java 程序。 Jenkins Docker Compose持续集成流…

linux中/etc/hosts文件的内容和功能

更准确的说是主机和ip地址映射绑定配置文件 用于主机名解析成ip地址的 转换配置 效果&#xff1a; 这个东西是局域网下面的解析&#xff0c;老师说是本地局域网解析 windows对应的就是

开源项目-汽车租赁管理系统

哈喽,大家好,今天主要给大家带来一个开源项目-汽车租赁管理系统 汽车租赁管理系统的主要功能包括汽车管理,新闻管理,用户管理,订单管理,数据展示等模块 注:后续文章都会附上安装教程,有问题也欢迎大家评论私信。 登录 汽车管理 汽车管理可以查看所有汽车进行线上汽…

python爬虫笔记1

1 爬虫介绍 爬虫概述&#xff1a; 获取网页并提取和保存信息的自动化程序 1.获取网页 2.提取信息 css选择器 xpath 3.保存数据&#xff08;大数据时代&#xff09; 4.自动化 爬虫&#xff08;资产收集&#xff0c;信息收集&#xff09; 漏扫&#xff08;帮我发现漏洞&#xff…

【机器学习】《ChatGPT速通手册》笔记

文章目录 第0章 前言第1章 ChatGPT的由来&#xff08;一&#xff09;自然语言处理任务&#xff08;二&#xff09;ChatGPT所用数据数据大小&#xff08;三&#xff09;ChatGPT的神经网络模型有175亿个参数&#xff08;四&#xff09;模型压缩 方案 第2章 ChatGPT页面功能介绍&a…

每日算法4/21

LCR 073. 爱吃香蕉的狒狒 题目 狒狒喜欢吃香蕉。这里有 N 堆香蕉&#xff0c;第 i 堆中有 piles[i] 根香蕉。警卫已经离开了&#xff0c;将在 H 小时后回来。 狒狒可以决定她吃香蕉的速度 K &#xff08;单位&#xff1a;根/小时&#xff09;。每个小时&#xff0c;她将会选…

Json三方库介绍

目录 Json是干什么的Json序列化代码Json反序列化代码 Json是干什么的 Json是一种轻量级的数据交换格式&#xff0c;也叫做数据序列化方式。Json完全独立于编程语言的文本格式来存储和表述数据。易于人阅读和编写&#xff0c;同时也易于机器解析和生成&#xff0c;并有效地提升…

MATLAB求和函数

语法 S sum(A) S sum(A,“all”) S sum(A,dim) S sum(A,vecdim) S sum(,outtype) S sum(,nanflag) 说明 示例 S sum(A) 返回沿大小大于 1 的第一个数组维度计算的元素之和。 如果 A 是向量&#xff0c;则 sum(A) 返回元素之和。 如果 A 是矩阵&#xff0c;则 sum(A) 将…

2023年网络安全行业:机遇与挑战并存

2023年全球网络安全人才概况 根据ISC2的《2023年全球网络安全人才调查报告》&#xff0c;全球的网络安全专业人才数量达到了550万&#xff0c;同比增长了8.7%。然而&#xff0c;这一年也见证了网络安全人才短缺达到了历史新高&#xff0c;缺口数量接近400万。尤其是亚太地区&am…

【题解】NC40链表相加(二)(链表 + 高精度加法)

https://www.nowcoder.com/practice/c56f6c70fb3f4849bc56e33ff2a50b6b?tpId196&tqId37147&ru/exam/oj class Solution {public:// 逆序链表ListNode* reverse(ListNode* head) {// 创建一个新节点作为逆序后链表的头节点ListNode* newHead new ListNode(0);// 当前…