目錄
本章介紹 在ESP8266上使用IIC總線驅動BH1750光強傳感器的方法。
一、BH1750是什麼
BH1750是一個光強傳感器,能夠對環境光強度進行量化,轉化爲環境中的光強度lux。它是使用IIC總線進行通信,通過讀取寄存器來獲得傳感器的真實數據。我們常說的智能調光,就要用到此類傳感器,因爲這個傳感器的精度還是很不錯的,可以使用這個傳感器作爲反饋,將環境光照度穩定在一個期望的數值。本章只介紹環境光傳感器的驅動,通過PID穩定環境光強度的內容將在後面介紹。
二、驅動原理&代碼
BH1750是使用IIC總線進行驅動的,IIC協議僅用4根線 VCC、GND、SCL 、SDA 就可以實現數據的交互,在BH1750傳感器中還有一個位(AD0)是來控制不同地址的,置不同的電平可以改變傳感器內部的地址,方便用來在IIC總線上做設備擴展。
關於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驅動
本次所寫的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哈哈~