一、前言
之所以寫這篇文章,原因有兩個。
一是:有個師弟跟我說我發(fā)布的文章都偏向于工作者,能不能寫一些大學生能用到的東西,我想了一下,確實是,我寫的文章大多是我在工作中總結(jié)出來的心得,對于初學者來說確實有點難以理解。
二是:我覺得這個光照傳感器很多大學生都能用到,但是網(wǎng)上的教程雖多卻也不一定能夠幫助大家深入了解這款傳感器。大家更多的是看完攻略之后能夠驅(qū)動,但是其實并不了解它的工作原理,想要在光照傳感器的基礎上增加別的功能也無從下手。
所以,我覺得我還是有必要寫一篇更加詳細更加深入的攻略來幫助大家理解。我覺得能驅(qū)動一個芯片和會驅(qū)動一個芯片是不一樣的,如果你學會了如何去驅(qū)動一個芯片,那么換了別的類似的芯片你也能夠得舉一反三。不然的話你每次換一個芯片都只能去找人家寫好的代碼。
好了,廢話不多說了,BH1750的講解馬上開始。(注:請一定要從頭到尾看下去,粗略看一下也行,因為內(nèi)容是環(huán)環(huán)相扣的,一直看,一直爽!?。。?/p>
我再多說一句,就一句,真的,接下來我講的所有代碼以及相關的所有文件都可以免費發(fā)給你們,鏈接在文章底部,自己去下載吧。
除了本文這個驅(qū)動外,還有另外一種使用方法,可以參考我發(fā)布的博文:
基于stm32驅(qū)動bh1750光照傳感器的一種超簡單的編程方法
二、芯片介紹
BH1750FVI是一款數(shù)字型光強度傳感器集成芯片。某寶上面很多寫著GY30模塊,那些其實也是用BH1750FVI芯片,只不過是它把BH1750FVI芯片以及外圍的一些電路做到了一個板子上面,然后把BH1750FVI的通訊引腳引出來方便你們用單片機控制而已。(話說大部分國產(chǎn)芯片都是這個套路,把人家的芯片拿過來,加一點外圍電路,然后重新包一層外殼,換個型號,就變成自己的產(chǎn)品了)
電路工作原理:如圖1所示,BH1750的內(nèi)部由光敏二極管、運算放大器、ADC采集、晶振等組成。PD二極管通過光生伏特效應將輸入光信號轉(zhuǎn)換成電信號,經(jīng)運算放大電路放大后,由ADC采集電壓,然后通過邏輯電路轉(zhuǎn)換成16位二進制數(shù)存儲在內(nèi)部的寄存器中(注:進入光窗的光越強,光電流越大,電壓就越大,所以通過電壓的大小就可以判斷光照大小,但是要注意的是電壓和光強雖然是一一對應的,但不是成正比的,所以這個芯片內(nèi)部是做了線性處理的,這也是為什么不直接用光敏二極管而用集成IC的原因)。
BH1750引出了時鐘線和數(shù)據(jù)線,單片機通過I2C協(xié)議可以與BH1750模塊通訊,可以選擇BH1750的工作方式,也可以將BH1750寄存器的光照度數(shù)據(jù)提取出來。
引腳定義:
引腳號 | 名稱 | 說明 |
---|---|---|
1 | VCC | 供電電壓源正極 |
2 | SCL | IIC時鐘線,時鐘輸入引腳,由MCU輸出時鐘 |
3 | SDA | IIC數(shù)據(jù)線,雙向IO口,用來傳輸數(shù)據(jù) |
4 | ADDR | IIC地址線,接GND時器件地址為0100011 ,接VCC時器件地址為1011100 |
5 | GND | 供電電壓源負極 |
三、IIC通訊介紹
IIC通訊過程簡介
既然BH1750是用IIC通訊的,那么我們就要先了解IIC的通訊原理。IIC由時鐘線(SCL)和數(shù)據(jù)線(SDA)組成。時鐘線,聽這個名字就知道和時間有關系,沒錯,它其實管理著IIC的通訊時間。而數(shù)據(jù)線,顧名思義就是用來傳輸數(shù)據(jù)的線。那么時鐘線和數(shù)據(jù)線它們是什么關系呢?你可以把時鐘線理解為紅綠燈,高電平是綠燈,低電平是紅燈,而數(shù)據(jù)線傳輸?shù)拿恳粋€數(shù)據(jù)則相當于一輛汽車,高電平是奔馳,低電平是寶馬。當綠燈亮了的時候,汽車就可以過去,只不過這里的交通規(guī)則是每亮一次綠燈,只能通過一輛汽車。所以,IIC通訊的過程就是紅綠燈交替閃爍(也就是時鐘線輸出方波脈沖),汽車跟著一輛一輛的過去,過去的是奔馳,就是傳輸了一個“1”,過去的寶馬,就是傳輸了一個“0”,連續(xù)傳輸8次,就可以組成一個8位的二進制數(shù),也就是一個字節(jié)的數(shù)據(jù),反復這個過程就能實現(xiàn)兩個設備之間的通訊。
好,上面已經(jīng)大概講解了IIC的通訊過程,那么下面來補充一些細節(jié)。IIC通訊的兩個設備是有主從關系的,比如我們的單片機在這里就是主設備,BH1750是從設備。
時鐘線是由主設備輸出,從設備輸入的,也就是單片機和BH1750通訊的時候,單片機的IO口要給SCL引腳輸出一個方波脈沖,因為IIC設備支持的最大通訊頻率一般都是400kHz,也就是說一個時鐘周期(一個高電平加一個低電平為一個周期)不能小于2.5us。單片機輸出時鐘的時候一定要注意高低電平延時的時間,延時的時間越長,通訊的速率越慢。另外,時鐘線不會一直輸出脈沖,只會在需要通訊的時候輸出,并且要遵循一定的規(guī)則。需要通訊的時候時鐘線先要輸出一個“起始信號”告訴從設備我要開始通訊了,其實就是電平由高到低跳變,但是這個高電平的持續(xù)時間不能太短,具體最少要多少時間需要看芯片手冊,反正延長一點準沒錯。然后再根據(jù)固定的時間輸出高低脈沖,直到到了要停止通訊的時候,時鐘線要輸出一個“結(jié)束信號”告訴從設備我不通訊了,其實就是電平一直拉高。
而數(shù)據(jù)線傳輸?shù)臄?shù)據(jù)是雙向的,單片機可以給BH1750發(fā)數(shù)據(jù),也可以讀取BH1750的數(shù)據(jù)(也就是BH1750給單片機發(fā))。需要注意的,單片機給BH1750發(fā)的數(shù)據(jù)不是隨便發(fā)的,也要符合一定的規(guī)則。首先,單片機要先發(fā)一個器件地址(器件地址是7位的,詳細的內(nèi)容我后面再說),再發(fā)送一個讀寫位(0表示是寫入,1表示讀取),器件地址和讀寫位加起來剛好是一個字節(jié),然后BH1750會給你回一個應答位,意思就是“我收到了”。然后單片機就可以接著發(fā)送數(shù)據(jù)了,每次都是以1個字節(jié)為間隔發(fā)。收也是類似的,只是把單片機發(fā)數(shù)據(jù)改成收數(shù)據(jù),這里就不多說了,后面會詳細講。(注:器件地址是用來區(qū)分從設備的,因為有時候同一根時鐘線和數(shù)據(jù)線可能會連接多個從設備,也就是說主設備發(fā)送的數(shù)據(jù)所有的從設備都可以收到,所以主設備要先發(fā)送一個器件地址,告訴所有的從設備我是給哪個設備發(fā)命令,其他設備收到了也不要執(zhí)行)。
IIC通訊實例
下面我們看一個實際的例子。圖2是OPT3001通訊的讀寫過程,(OPT3001是我在項目中用到一款低功耗光照傳感器,和BH1750類似,也是IIC通訊協(xié)議,感興趣的同學可以看一下我發(fā)之前的博文,有講解這個IC的驅(qū)動方式),看懂了這個圖你就理解IIC的通訊方式了,你就可以當著博主的面大聲地說“你寫的博文有毛用,你說的我全都知道”,如果你還有不理解的地方,那么就坐下來好好聽我解說吧。
首先,我們看一下IIC的寫入過程,最左邊先是有一個“Start by Master”,也就是單片機先給一個“起始信號”,然后后面接著傳輸了8位數(shù)據(jù)(1 0 0 0 1 A1 A0 R/W)。其中,“1 0 0 0 1 A1 A0”是器件地址,因為這里的器件地址有4個可選,所以用了A1和A0表示,(注:BH1750只有2個器件地址),“R/W”是讀寫位,上面我有說到,這里是寫入,所以這里的R/W應該是一個“0”。 接著是“ACK by OPT3001”,這是從設備給主設備發(fā)的應答,就是說“你發(fā)的數(shù)據(jù)我收到了,你可以接著發(fā)了”,然后接下來的RA7-RA0是寄存器地址(因為寄存器不止一個所以要先發(fā)地址,告訴它你接下來要把數(shù)據(jù)存到哪里),再后面的D15-D0是兩個字節(jié)的數(shù)據(jù)(這些數(shù)據(jù)就是存到前面發(fā)的那個地址的寄存器里面)。
讀取的過程和寫入類似,先是“起始信號”,再是器件地址+讀寫位,接著是應答,然后開始接收數(shù)據(jù)(單片機的IO口要從輸出改成輸入了),D15-D0是接收到兩個字節(jié)的數(shù)據(jù),“ACK by Master”是單片機給OPT3001發(fā)的應答。(只要是接收的一方都要發(fā)應答,不應答的話通訊就會結(jié)束,比如讀取的第二個字節(jié)后面的“No ACK by Master”)
好,如果你能堅持看到這里,那我敬你是條漢子?。∪绻憧炊?,那么恭喜你,如果沒看懂,那也沒關系,上面那是IIC一般的通訊方式,后面BH1750的通訊要更加簡單。
(問:那你為什么不直接講BH1750。答:我喜歡,你咬我呀,略略略….啪,略略啪,略別別….我錯了。)
BH1750的通訊過程
其實前面之所以要先講這個OPT3001而不是直接講BH1750,是因為BH1750的IIC其實算是一個簡化版的,不具有通用性,你學會了OPT3001的通訊方法,你再去驅(qū)動BH1750就很簡單,相反,如果你只會驅(qū)動BH1750,那么換成別的IIC的芯片你就不一定會了。
好了,接下來我們來看一下BH1750的通訊,BH1750的通訊過程可以分成5步,中間3步如圖3所示。
(啪啪,問:為什么要用英文的圖,別以為我不知道有中文版的手冊,說你是不是在裝*。答:冤枉,真不是,那個中文版的圖太糊了,而且英文版其實也不影響大家去看,老實說我是一個英語學渣,我還寫了一篇博文講述一個學渣如何看懂英文數(shù)據(jù)手冊,有興趣的同學可以看一下。真不是打廣告哦。)
第1步:發(fā)送上電命令。(上電命令是0x01)。
因為這里沒有圖,我就不詳細說了,發(fā)送的過程和第2步基本一致。就是把測量命令(0x10)改成上電命令(0x01)。
第2步:發(fā)送測量命令。
下面圖片上的例子,ADDR引腳是接GND的,發(fā)送的測量命令是“連續(xù)高分辨率測量(0x10)”。
發(fā)送數(shù)據(jù)的過程和之前講的OPT3001寫入的過程基本一樣,先是“起始信號(ST)”,接著是“器件地址+讀寫位”(器件地址我在上面引腳定義那里有寫),然后是應答位,緊接著就是測量的命令“00010000”(關于測量命令,下面會詳細說明),然后應答,最后是“結(jié)束信號(SP)”。(相比于OPT3001的寫入過程,BH1750少了一個發(fā)送寄存器地址的步驟,因為它只有一個寄存器,所以就沒必要了)
第3步:等待測量結(jié)束。
測量的時間手冊上面有寫,我這里就不列出來了,高分辨率連續(xù)測量需要等待的時間最長,手冊上面寫的是平均120ms,最大值180ms,所以為了保證每次讀取到的數(shù)據(jù)都是最新測量的,程序上面可以延時200ms以上,當然也不用太長,浪費時間。如果你用別的測量模式,等待時間都比這個模式要短。
第4步:讀取數(shù)據(jù)。
先是“起始信號(ST)”,接著是“器件地址+讀寫位”,然后是應答位,緊接著接收1個字節(jié)的數(shù)據(jù)(單片機在這個時候要把SDA引腳從輸出改成輸入了),然后給BH1750發(fā)送應答,繼續(xù)接收1個字節(jié)數(shù)據(jù),然后不應答(因為我們接收的數(shù)據(jù)只有2個字節(jié),收完就可以結(jié)束通訊了),最后是“結(jié)束信號(SP)”。
第5步:計算結(jié)果。
接收完兩個字節(jié)還不算完成,因為這個數(shù)據(jù)還不是測量出來的光照強度值,我們還需要進行計算,計算公式是:光照強度 =(寄存器值[15:0] * 分辨率) / 1.2 (單位:勒克斯lx)
因為我們從BH1750寄存器讀出來的是2個字節(jié)的數(shù)據(jù),先接收的是高8位[15:8],后接收的是低8位[7:0],所以我們需要先把這2個字節(jié)合成一個數(shù),然后乘上分辨率,再除以1.2即可得到光照值。
例如:我們讀出來的第1個字節(jié)是0x12(0001 0010),第2個字節(jié)是0x53(0101 0011),那么合并之后就是0x1253(0001 0010 0101 0011),換算成十進制也就是4691,乘上分辨率(我用的分辨率是1),再除以1.2,最后等于3909.17 lx。
四、BH1750的命令
BH1750所有的命令都在圖4。這次我用的是中文版的圖,方便大家看,有點糊勿怪。 這里的指令雖然多,但是實際上如果僅僅是測光照值,只用兩條就夠了,通電指令和測量指令。這里的幾條測量指令我就不詳細說了,手冊上是有講的,如果后面你們需要的話我再補上吧。寄存器也只有一個,沒什么好說的。(才不是因為懶也不是因為天天都要加班)
五、BH1750編程教學
下面的編程我以stm32為例,其實換成51,stm8或者別的單片機,程序也基本一樣的,不同的單片機在程序上只是引腳配置的寫法不太一樣,別的基本沒差別。
我的這個程序是用OLED顯示光照強度的,想用串口,藍牙或者別的方式也可以。
注:我下面展示的程序跟我發(fā)給你們的工程會有一點不一樣,主要是備注,因為為了讓你們更好理解,我展示的代碼是加了很多備注的,而工程是以前的,備注會少一點。
1、IIC驅(qū)動代碼
//IIC的驅(qū)動程序沒必要自己去寫,能夠看懂每一個函數(shù)的作用,知道IIC的通訊過程即可,我這里用的是正點原子的例程
//IIC通訊最基本的幾個函數(shù)是:起始信號,結(jié)束信號,發(fā)送應答(或不應答),發(fā)送1個字節(jié)數(shù)據(jù),接收1個字節(jié)數(shù)據(jù)
//這些我前面都有講到,如果你前面看懂了,將上面OPT3001的時序圖和這個程序結(jié)合起來看你就很容易想明白
//閑話(可以跳過):這一份程序是以前大學做項目的時候?qū)懙模鋵嵈蟛糠侄际浅?,當時對程序的理解也是一知半解
//現(xiàn)在回頭看,發(fā)現(xiàn)這個程序的兼容性很差
//雖然在這個工程上面運行是沒有問題的,但是如果移植到別的工程或者用別的單片機,需要改動的地方就很多了
//比如引腳的拉高拉低,這里是直接寫“SDA=1;”,但是這個SDA的定義是在正點原子自己寫的一個庫里面的
//如果你用別的工程,沒有把這個庫加進來,那么這個定義就不成立了
//最好的寫法我覺得是分成兩個定義SDA_High和SDA_Low
//然后在頭文件聲明#define SDA_High GPIO_SetBits(GPIOB,GPIO_Pin_0)
//#define SDA_GPIO_ResetBits(GPIOB,GPIO_Pin_0)
//這樣寫的好處是如果要移植,只需要把 GPIO_SetBits(GPIOB,GPIO_Pin_0) 這部分換掉就行了
//比如用51,我們就可以把GPIO_SetBits(GPIOB,GPIO_Pin_0)換成P1_0=1
//同樣的IIC通訊的延時函數(shù)delay_us,這里用的是定時器,也是要用到正點原子的庫delay.c
//其實這里我覺得可以用for函數(shù)延時,因為延時的時間比較短,也不需要很精確
//如果換了一個單片機,晶振頻率不同,只需要改一下for函數(shù)延時的次數(shù)
//然后用示波器量一下這個時間,確保是在正常通訊的時間范圍內(nèi)即可
/***起始信號***/
void BH1750_Start()
{
SDA=1; //拉高數(shù)據(jù)線
SCL=1; //拉高時鐘線
delay_us(5); //延時
GPIO_ResetBits(bh1750_PORT, sda); //產(chǎn)生下降沿
delay_us(5); //延時
GPIO_ResetBits(bh1750_PORT, scl); //拉低時鐘線
}
/*****停止信號******/
void BH1750_Stop()
{
SDA=0; //拉低數(shù)據(jù)線
SCL=1; //拉高時鐘線
delay_us(5); //延時
GPIO_SetBits(bh1750_PORT, sda); //產(chǎn)生上升沿
delay_us(5); //延時
}
/**************************************
發(fā)送應答信號
入口參數(shù):ack (0:ACK 1:NAK)
**************************************/
void BH1750_SendACK(int ack)
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStruct.GPIO_Pin = sda;
GPIO_Init(bh1750_PORT, &GPIO_InitStruct);
if(ack == 1) //寫應答信號
SDA=1;
else if(ack == 0)
SDA=0;
else
return;
SCL=1; //拉高時鐘線
delay_us(5); //延時
SCL=0; //拉低時鐘線
delay_us(5); //延時
}
/**************************************
接收應答信號
**************************************/
int BH1750_RecvACK()
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU; /*這里一定要設成輸入上拉,否則不能讀出數(shù)據(jù)*/
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStruct.GPIO_Pin=sda;
GPIO_Init(bh1750_PORT,&GPIO_InitStruct);
SCL=1; //拉高時鐘線
delay_us(5); //延時
if(GPIO_ReadInputDataBit(GPIOA,sda)==1)//讀應答信號
mcy = 1 ;
else
mcy = 0 ;
SCL=0; //拉低時鐘線
delay_us(5); //延時
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_Init(bh1750_PORT,&GPIO_InitStruct);
return mcy;
}
/**************************************
向IIC總線發(fā)送一個字節(jié)數(shù)據(jù)
**************************************/
void BH1750_SendByte(uchar dat)//dat是要發(fā)送的一個字節(jié)的數(shù)據(jù)
{
uchar i;
for (i=0; i<8; i++) //8位計數(shù)器
{
if( 0X80 & dat ) //如果要發(fā)送的是1
GPIO_SetBits(bh1750_PORT,sda);
else //如果要發(fā)送的是0
GPIO_ResetBits(bh1750_PORT,sda);
dat <<= 1; //for循環(huán)每執(zhí)行一次,要發(fā)送的數(shù)據(jù)左移1位,循環(huán)8次就把一個字節(jié)的數(shù)據(jù)發(fā)送出去了
SCL=1; //拉高時鐘線
delay_us(5); //延時
SCL=0; //拉低時鐘線
delay_us(5); //延時
}
BH1750_RecvACK();
}
/**************************************
在IIC總線接收一個字節(jié)數(shù)據(jù)
**************************************/
uchar BH1750_RecvByte()
{
uchar i;
uchar dat = 0; //dat是存放接收到的一個字節(jié)的數(shù)據(jù)
uchar bit;
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; /*這里一定要設成輸入上拉,否則不能讀出數(shù)據(jù)*/
GPIO_InitStruct.GPIO_Pin = sda;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(bh1750_PORT,&GPIO_InitStruct );
GPIO_SetBits(bh1750_PORT,sda); //使能內(nèi)部上拉,準備讀取數(shù)據(jù),
for (i=0; i<8; i++) //8位計數(shù)器
{
dat <<= 1; //循環(huán)8次,每次接收一個位,8次之后完成一個字節(jié)數(shù)據(jù)的接收
SCL=1; //拉高時鐘線
delay_us(5); //延時
if( SET == GPIO_ReadInputDataBit(bh1750_PORT,sda))//讀取SDA引腳的電平,如果是高電平,就是傳輸“1”
bit = 0X01;
else //電平傳輸?shù)氖恰?”
bit = 0x00;
dat |= bit; //讀數(shù)據(jù)
SCL=0; //拉低時鐘線
delay_us(5); //延時
}
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(bh1750_PORT, &GPIO_InitStruct );
return dat;
}
2、BH1750寫入和讀取的函數(shù)
//上面講了IIC的幾個基本的函數(shù),包括了發(fā)送1字節(jié)和接收1字節(jié)
//但是和BH1750通訊,不僅僅是發(fā)送1個字節(jié)或者接收1個字節(jié)那么簡單
//我們對BH1750發(fā)送命令的時候,是要先發(fā)送器件地址+寫入位,然后發(fā)送指令
//讀取數(shù)據(jù)的時候,需要先發(fā)送器件地址+讀取位,然后連續(xù)讀取2個字節(jié)
//如果我上面說的你都懂了,那么你就可以去看代碼了,如果能跟時序圖一一對應上,你就理解代碼了
//另外,如果你用的不是BH1750,而是別的IIC通訊的芯片,這兩個函數(shù)的寫法可能也是不同的
//比如OPT3001,發(fā)送命令的時候不僅發(fā)發(fā)送命令數(shù)據(jù)還需要發(fā)送寄存器地址,所以一般函數(shù)定義的時候就要定義兩個變量
//又或者一些命令是兩個字節(jié)的,那么你定義的變量類型就需要注意了,函數(shù)里面也需要多發(fā)送一個字節(jié)數(shù)據(jù)
//這里不理解也無所謂,不影響學習BH1750的驅(qū)動,以后你做項目用到了別的芯片你可能就突然理解了
//寫入指令
void Single_Write_BH1750(uchar REG_Address)//REG_Address是要寫入的指令
{
BH1750_Start(); //起始信號
BH1750_SendByte(SlaveAddress); //發(fā)送設備地址+寫信號
BH1750_SendByte(REG_Address); //寫入指令
BH1750_Stop(); //發(fā)送停止信號
}
//讀取指令
void mread(void)
{
uchar i;
BH1750_Start(); //起始信號
BH1750_SendByte(SlaveAddress+1); //發(fā)送設備地址+讀信號
//注意:這里的for函數(shù)的i<2和下面的if函數(shù)的i==2,我發(fā)現(xiàn)以前的工程寫的居然是3
//這里其實我們只需要讀取2個字節(jié)就行了,后面的合成數(shù)據(jù)也是只用了BUF的前2個字節(jié)
//工程文件我沒改,這個驅(qū)動程序以前也用在了多個項目上,讀取3個字節(jié)肯定是也可以正常運行的
//但是我覺得還是改成2比較好,你們可以測試一下改成2有沒有問題,測試之后一定要告訴我結(jié)果,謝謝!!
for (i=0; i<2; i++) //連續(xù)讀取2個數(shù)據(jù),存儲到BUF里面
{
BUF[i] = BH1750_RecvByte(); //BUF[0]存儲高8位,BUF[1]存儲低8位
if (i == 1)
{
BH1750_SendACK(1); //最后一個數(shù)據(jù)需要回NOACK
}
else
{
BH1750_SendACK(0); //回應ACK
}
}
BH1750_Stop(); //停止信號
delay_ms(5);
}
3、BH1750初始化函數(shù)
//初始化BH1750,根據(jù)需要請參考pdf進行修改****
void Init_BH1750()
{
GPIO_InitTypeDef GPIO_InitStruct;
/*開啟GPIOB的外設時鐘*/
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStruct.GPIO_Pin = sda | scl ;
GPIO_Init(bh1750_PORT,&GPIO_InitStruct);
Single_Write_BH1750(0x01);
delay_ms(180); //延時180ms
}
4、獲取光照度函數(shù)
float read_BH1750(void)
{
int dis_data; //變量
float temp1;
float temp2;
Single_Write_BH1750(0x01); //發(fā)送上電命令(0x01)
Single_Write_BH1750(0x10); //發(fā)送高分辨率連續(xù)測量命令(0x10)
delay_ms(200); //等待測量結(jié)束,其實延時180ms就行了,延時200ms只是預留多一點時間,保證通訊萬無一失
mread(); //連續(xù)讀出數(shù)據(jù),存儲在BUF中
dis_data=BUF[0];
dis_data=(dis_data<<8)+BUF[1]; //2個字節(jié)合成數(shù)據(jù)
temp1=dis_data/1.2;//計算光照度
temp2=10*dis_data/1.2;//把光照度放大10倍,目的是把小數(shù)點后一位數(shù)據(jù)也提取出來
temp2=(int)temp2%10;//求余得到小數(shù)點后一位
OLED_ShowString(87,2,".",12); //OLED顯示小數(shù)點
OLED_ShowNum(94,2,temp2,1,12);//OLED顯示小數(shù)
return temp1;//返回整數(shù)部分
}
//這里寫的程序還是有點亂的,小數(shù)部分直接在read_BH1750()顯示,整數(shù)部分返回,在main()函數(shù)調(diào)用的時候顯示
//這...其實最好是要么都在這個函數(shù)顯示,要么把temp1和temp2改成全局變量,然后都在main函數(shù)顯示
//這個變量的名字也是[捂臉],算了算了,往事不堪回首。要吐槽的地方有點多,也沒時間去一一改了
//不過其實也不影響你們學IIC通訊的編程方式,就這樣吧
5、main函數(shù)
int main(void)
{
float light;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 設置中斷優(yōu)先級分組2
delay_init(); //延時函數(shù)初始化
uart_init(9600); //串口初始化為9600
LED_Init(); //初始化與LED連接的硬件接口
Init_BH1750(); //初始化BH1750
OLED_Init(); //初始化OLED
OLED_Clear(); //清屏
while(1)
{
light=read_BH1750(); //讀取BH1750的光強數(shù)據(jù)
OLED_ShowString(0,2,"light:",12); //顯示光照強度
OLED_ShowNum(48,2,light,6,12); //顯示光照度
OLED_ShowString(110,2,"lx",12); //顯示“l(fā)x”
if(light<100)//光照度小于100lx,點亮LED燈
{
LED1=0;
OLED_ShowString(38,5,"LED-ON ",12);
}
else
{
LED1=1;
OLED_ShowString(38,5,"LED-OFF",12);
}
}
}
六、測試
我這個程序是大學的時候做的一個課程設計,現(xiàn)在也沒有實物可以測試了,我就發(fā)我以前報告里的圖給你們看一下效果吧。圖5是亮度大于100lx的時候,圖6是低于100lx的時候。
七、總結(jié)
要驅(qū)動BH1750,或者其他IIC通訊的芯片,最好還是先了解IIC通訊的時序,了解通訊的原理,然后才是寫驅(qū)動程序。驅(qū)動程序也可以分成三部分,第一部分是IIC通訊基本的協(xié)議(一般抄就完事了),第二部分是芯片的讀寫過程,需要根據(jù)實際芯片的通訊方式寫。第三部分是指令控制相關的函數(shù),BH175比較簡單,只有測量和計算。有些可能還有多個設置不同的模式的函數(shù),校驗數(shù)據(jù)的函數(shù)等等,不過它們其實都是發(fā)送指令,只是發(fā)不同的指令執(zhí)行不同的操作而已。
OPT3001的驅(qū)動教程你們可以大概看一下:https://blog.csdn.net/ShenZhen_zixian/article/details/102876443
最后再說點閑話吧,寫到這里剛好有點感觸,其實寫博文的初衷只是為了把工作中總結(jié)出來的一些經(jīng)驗記錄下來,加強記憶。因為像我們這種做硬件研發(fā)的,經(jīng)驗是最值錢的,然后上傳資源也只是為了賺點積分,因為還在大學那會想下載別人的程序的時候總是恨自己沒有積分。后來發(fā)現(xiàn)我寫的博文和上傳的資源能夠幫助到一些人,所以我就一直堅持著更新,哪怕每天加班也會抽空寫一下文章寫一下代碼,即使我自己賺的積分其實一次都沒用過。可能這就是傳承吧,以前遇到問題的時候總能在前輩的博文中找到答案,現(xiàn)在輪到自己分享自己的經(jīng)驗給后來者了,希望我的文章也能夠幫助到你。
本文用到的工程源碼可以在下面的鏈接下載:
源碼下載鏈接1:https://pan.baidu.com/s/1HnedCg3sC4HU8iEOf4dYOw ,提取碼:xs8o
源碼下載鏈接2:https://pan.baidu.com/s/1QOC01P5M99LzP4i1Voro6g,提取碼:abcd
創(chuàng)作不易,希望你們尊重別人的勞動,點贊+關注支持一下吧,謝謝大家了,博主也會繼續(xù)更新更多的大學生專欄,如果你們還有什么問題,可以評論留言或者私信給我。