目录
一、BH1750是什么
二、驱动原理&代码
关于IIC
BH1750驱动
三、结语
本章介绍 在ESP8266上使用IIC总线驱动BH1750光强传感器的方法。
BH1750是一个光强传感器,能够对环境光强度进行量化,转化为环境中的光强度lux。它是使用IIC总线进行通信,通过读取寄存器来获得传感器的真实数据。我们常说的智能调光,就要用到此类传感器,因为这个传感器的精度还是很不错的,可以使用这个传感器作为反馈,将环境光照度稳定在一个期望的数值。本章只介绍环境光传感器的驱动,通过PID稳定环境光强度的内容将在后面介绍。
BH1750是使用IIC总线进行驱动的,IIC协议仅用4根线 VCC、GND、SCL 、SDA 就可以实现数据的交互,在BH1750传感器中还有一个位(AD0)是来控制不同地址的,置不同的电平可以改变传感器内部的地址,方便用来在IIC总线上做设备扩展。
IIC驱动我己经介绍过多次了,很多设备都使用IIC协议进行通信,但是他们实现的代码略有差异,有些只用到了部分功能,有些则是用到了全部的功能,有些通信速率高,有些通信速率低,但他们都是IIC协议,基本原理不变,规则不变。接下来我就针对这个传感器编写了适应的IIC驱动(C++语言版本)。(PS:获取以后有机会写一个完整的软件IIC驱动可以应对所有传感器而不需要特别写一个)
IIC类文件
class IIC_Device
{
private:
gpio_num_t sda_io_num; //I2C_MASTER_SDA_GPIO
gpio_num_t scl_io_num; //I2C_MASTER_SCL_GPIO
public:
IIC_Device(gpio_num_t sda_io, gpio_num_t scl_io)
:sda_io_num(sda_io),scl_io_num(scl_io)
{
gpio_init(sda_io_num,scl_io_num);
}
/*
* IIC GPIO初始化函数
* 参数:sda_io_num SDA引脚,scl_io_num SCL引脚
* 返回结果 :成功
*/
esp_err_t gpio_init(gpio_num_t sda_io, gpio_num_t scl_io);
protected:
void IIC_Start(void); //IIC 开始信号
void IIC_Stop(void); //IIC 结束信号
/*
* IIC等待应答函数
* 返回1--应答出错
* 返回0--应答正确
*/
uint8_t IIC_Wait_Ask(void);
/*
* 写一个字节
* 参数:要写入的数据
*/
void IIC_WriteByte(uint8_t data);
/*
* 读一个字节
* 返回值:读出的字节
*/
uint8_t IIC_ReadByte(void);
/*
* 发送Ack 应答信号
* 参数:是否应答 1->NOACK 0->Ack
*/
void SendACK(uint8_t ack);
};
IIC实现函数:
/*
* IIC GPIO初始化函数
* 参数:sda_io_num SDA引脚,scl_io_num SCL引脚
* 返回结果 :成功
*/
esp_err_t IIC_Device::gpio_init(gpio_num_t sda_io_num, gpio_num_t scl_io_num)
{
gpio_config_t io_conf;
printf("init BH1750 i2c\n");
// disable interrupt
io_conf.intr_type = GPIO_INTR_DISABLE;
// set as output mode
io_conf.mode = GPIO_MODE_OUTPUT_OD;
// bit mask of the pins that you want to set
io_conf.pin_bit_mask = (1ULL << sda_io_num) | (1ULL << scl_io_num);
// disable pull-down mode
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
// disable pull-up mode
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
// configure GPIO with the given settings
ESP_ERROR_CHECK(gpio_config(&io_conf));
ESP_ERROR_CHECK(gpio_set_level(sda_io_num, 1));
ESP_ERROR_CHECK(gpio_set_level(scl_io_num, 1));
printf("\nBH1750_SDA_GPIO:%d BH1750_SCL_GPIO:%d", sda_io_num, scl_io_num);
return ESP_OK;
}
/*
* IIC 开始信号
*/
void IIC_Device::IIC_Start(void)
{
gpio_set_direction(sda_io_num, GPIO_MODE_OUTPUT); //SDA_OUT();
I2C_MASTER_GPIO_OUT(sda_io_num, 1); //IIC_SDA=1;
I2C_MASTER_GPIO_OUT(scl_io_num, 1); //IIC_SCL=1;
delay_us(2);
I2C_MASTER_GPIO_OUT(sda_io_num, 0); //IIC_SDA=0;
delay_us(2);
I2C_MASTER_GPIO_OUT(scl_io_num, 0); //IIC_SCL=0;
delay_us(2);
}
/*
* IIC 结束信号
*/
void IIC_Device::IIC_Stop(void)
{
I2C_MASTER_GPIO_OUT(scl_io_num, 1); //IIC_SCL=1;
I2C_MASTER_GPIO_OUT(sda_io_num, 0); //IIC_SDA=0;
delay_us(2);
I2C_MASTER_GPIO_OUT(sda_io_num, 1); //IIC_SDA=1;
delay_us(2);
}
/*
* IIC等待应答函数
* 返回1--应答出错
* 返回0--应答正确
*/
uint8_t IIC_Device::IIC_Wait_Ask(void)
{
int count = 0;
gpio_set_direction(sda_io_num, GPIO_MODE_INPUT); //SDA_IN();
I2C_MASTER_GPIO_OUT(scl_io_num, 1); //IIC_SCL=1;
delay_us(2);
while (gpio_get_level(sda_io_num)) //
{
count++;
if (count > 250)
{
IIC_Stop();
return 1;
}
}
I2C_MASTER_GPIO_OUT(scl_io_num, 0); //IIC_SCL=0;
delay_us(2);
return 0;
}
/*
* 写一个字节
* 参数:要写入的数据
*/
void IIC_Device::IIC_WriteByte(uint8_t data)
{
uint8_t i;
gpio_set_direction(sda_io_num, GPIO_MODE_OUTPUT); //SDA_OUT();
for (i = 0; i < 8; i++)
{
I2C_MASTER_GPIO_OUT(scl_io_num, 0); //IIC_SCL=0;
delay_us(2);
if (data & 0x80) //MSB,从高位开始一位一位传输
I2C_MASTER_GPIO_OUT(sda_io_num, 1); //IIC_SDA=1;
else
I2C_MASTER_GPIO_OUT(sda_io_num, 0); //IIC_SDA=0;
I2C_MASTER_GPIO_OUT(scl_io_num, 1); //IIC_SCL=1;
delay_us(2);
I2C_MASTER_GPIO_OUT(scl_io_num, 0); //IIC_SCL=0;
data <<= 1;
}
}
/*
* 读一个字节
* 返回值:读出的字节
*/
uint8_t IIC_Device::IIC_ReadByte(void)
{
uint8_t data = 0, i = 0;
I2C_MASTER_GPIO_OUT(sda_io_num, 1); //IIC_SDA=1;
delay_us(2);
gpio_set_direction(sda_io_num, GPIO_MODE_INPUT); //SDA_OUT();
for (i = 0; i < 8; i++)
{
data <<= 1;
I2C_MASTER_GPIO_OUT(scl_io_num, 0); //IIC_SCL=0;
delay_us(2);
I2C_MASTER_GPIO_OUT(scl_io_num, 1); //IIC_SCL=1;
delay_us(2);
if (gpio_get_level(sda_io_num)) //
data = data | 0x01;
else
data = data & 0xFE;
}
I2C_MASTER_GPIO_OUT(scl_io_num, 0); //IIC_SCL=0;
delay_us(2);
return data;
}
/*
* 发送Ack 应答信号
* 参数:是否应答 1->NOACK 0->Ack
*/
void IIC_Device::SendACK(uint8_t ack)
{
gpio_set_direction(sda_io_num, GPIO_MODE_OUTPUT); //MPU_SDA_OUT();
gpio_set_level(scl_io_num, 0); //MPU_IIC_SCL=0;
I2C_MASTER_GPIO_OUT(sda_io_num, ack); //SDA = ack; //写应答信号
I2C_MASTER_GPIO_OUT(scl_io_num, 1); //SCL = 1; //拉高时钟线
delay_us(2); //延时
I2C_MASTER_GPIO_OUT(scl_io_num, 0); //SCL = 0; //拉低时钟线
delay_us(2); //延时
}
本次所写的BH1750是 通过使用IIC类作为父类进行实现的。BH1750继承了IIC的特性,所以可以复用所有IIC中定义的功能。
本次编写的驱动库中,支持对传感器测量精度的控制和传感器值得读取。
光强传感器类定义
class BH1750_Device : public IIC_Device
{
private:
uint8_t SlaveAddress = 0x46; //定义器件在IIC总线中的从地址,根据ALT ADDRESS地址引脚不同修改
//ALT ADDRESS引脚接地时地址为0xA6,接电源时地址为0x3A
uint8_t BUF[8] = {0, 0, 0, 0, 0, 0, 0, 0}; //接收数据缓存区
/**
* 通过IIC向BH1750发送数据
*/
void BH1750_SendByte(uint8_t data);
/**
* 通过IIC读取BH1750数据
*/
uint8_t BH1750_RecvByte();
/**
* 向BH1750目标地址写数据
* 参数:目标地址
*/
void Single_Write_BH1750(uint8_t REG_Address);
/**
* 连续读出BH1750内部数据
*/
void Multiple_Read_BH1750(void);
public:
float data = 0;
/**
* BH1750运行的精度模式
*/
BH1750_MODE currect_mode = BH1750_FAST_MODE;
BH1750_Device(gpio_num_t sda_io_num, gpio_num_t scl_io_num) : IIC_Device(sda_io_num, scl_io_num)
{
init();
}
/**
* 初始化BH1750,根据需要请参考pdf进行修改****
*/
void init();
/**
* 读取BH1750传感器数据
*/
float read_data();
/**
* 设置BH1750 的精度模式
*/
void set_mode(BH1750_MODE mode);
esp_err_t delay_ms(uint32_t time);
};
光强传感器函数实现
/**
* 通过IIC向BH1750发送数据
*/
void BH1750_Device::BH1750_SendByte(uint8_t data)
{
IIC_WriteByte(data);
IIC_Wait_Ask();
}
/**
* 通过IIC读取BH1750数据
*/
uint8_t BH1750_Device::BH1750_RecvByte()
{
return IIC_ReadByte();
}
/**
* 向BH1750目标地址写数据
* 参数:目标地址
*/
void BH1750_Device::Single_Write_BH1750(uint8_t REG_Address)
{
IIC_Start(); //起始信号
BH1750_SendByte(SlaveAddress); //发送设备地址+写信号
BH1750_SendByte(REG_Address); //内部寄存器地址,请参考中文pdf22页
//BH1750_SendByte(REG_data); //内部寄存器数据,请参考中文pdf22页
IIC_Stop(); //发送停止信号
}
/**
* 连续读出BH1750内部数据
*/
void BH1750_Device::Multiple_Read_BH1750(void)
{
uint8_t i;
IIC_Start(); //起始信号
BH1750_SendByte(SlaveAddress | 0x01); //发送设备地址+读信号
for (i = 0; i < 3; i++) //连续读取6个地址数据,存储中BUF
{
BUF[i] = BH1750_RecvByte(); //BUF[0]存储0x32地址中的数据
if (i == 3)
{
SendACK(1); //最后一个数据需要回NOACK
}
else
{
SendACK(0); //回应ACK
}
}
IIC_Stop(); //停止信号
delay_ms(5);
}
/**
* 初始化BH1750,根据需要请参考pdf进行修改****
*/
void BH1750_Device::init()
{
delay_ms(10);
Single_Write_BH1750(0x01);
}
/**
* 设置BH1750 的精度模式
*/
void BH1750_Device::set_mode(BH1750_MODE mode)
{
currect_mode = mode;
}
/**
* 读取BH1750传感器数据
*/
float BH1750_Device::read_data()
{
float temp = 0;
int dis_data = 0; //变量
if (currect_mode == BH1750_FAST_MODE)
{
Single_Write_BH1750(0x01); // power on
Single_Write_BH1750(0x13); // L- resolution mode
delay_ms(18);
}
else if (currect_mode == BH1750_ACCURATE_MODE)
{
Single_Write_BH1750(0x01); // power on
Single_Write_BH1750(0x10); // H- resolution mode
delay_ms(180); //延时180ms
}
Multiple_Read_BH1750(); //连续读出数据,存储在BUF中
//printf("BUF = %d,%d,%d,%d,%d,%d,%d,%d",BUF[0],BUF[1],BUF[2],BUF[3],BUF[4],BUF[5],BUF[6],BUF[7]);
dis_data = BUF[0];
dis_data = (dis_data << 8) + BUF[1]; //合成数据
temp = (float)dis_data / 1.2;
data = temp;
return temp;
}
需要注意的是,BH1750光强传感器有三种精度,一种精度较低(4lx),但是可以转换速度快,每18ms就能够完成一次光强转换;另两种种转换精度高(0.5lx 1lx),但是转换速度低180ms(手册上写120ms~180ms)才能完成一次光强转换。
此外该传感器还有两种读取方式,一种是连续读取,该模式电源一直处于打开状态,还有一种是单次读取模式,此模式每次读取结束之后都会自动关闭电源,以达到节能的目的。
本次驱动中并没有写这一部分,只分了快速连续模式和精确连续模式,或许以后用到会再进行完善,或者如果大家有兴趣可以添加这部分简单的代码推送到我的github仓库,我会将好的代码merge进去。
最近写博客的质量下降的很厉害,我自己也有感觉到,但是最近实在是太累了,每天很晚才回到家中。每天都在学习没有接触过得新鲜知识,感觉没有太多的精力去对以前的项目做博客分享这些事情,但我还是会坚持下去的,毕竟这是自己喜欢的事情。现在是凌晨1:30分,我已经困倦不堪,希望等我下一次闲下来的时候能够好好地再梳理一下我想要分享的这些东西。
本次项目的例程在我的github仓库上:https://github.com/gengyuchao/ESP8266_example/tree/master/project_BH1750 欢迎大家来我的博客评论和给我留言,或者给我的github项目点星星,提issue,提交pull request。把更多更好更有趣的知识传递下去。O(∩_∩)O哈哈~