加入星計(jì)劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴(kuò)散
  • 作品版權(quán)保護(hù)
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長(zhǎng)期合作伙伴
立即加入
  • 正文
    • 一、前言
    • 二、芯片介紹
    • 三、IIC通訊介紹
    • 四、BH1750的命令
    • 五、BH1750編程教學(xué)
    • 六、測(cè)試
    • 七、總結(jié)
  • 推薦器件
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請(qǐng)入駐 產(chǎn)業(yè)圖譜

BH1750光照傳感器超詳細(xì)攻略(從原理到代碼講解,看完你就懂了)

03/23 10:55
1.7萬(wàn)
閱讀需 39 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

一、前言

之所以寫這篇文章,原因有兩個(gè)。

一是:有個(gè)師弟跟我說(shuō)我發(fā)布的文章都偏向于工作者,能不能寫一些大學(xué)生能用到的東西,我想了一下,確實(shí)是,我寫的文章大多是我在工作中總結(jié)出來(lái)的心得,對(duì)于初學(xué)者來(lái)說(shuō)確實(shí)有點(diǎn)難以理解。

二是:我覺(jué)得這個(gè)光照傳感器很多大學(xué)生都能用到,但是網(wǎng)上的教程雖多卻也不一定能夠幫助大家深入了解這款傳感器。大家更多的是看完攻略之后能夠驅(qū)動(dòng),但是其實(shí)并不了解它的工作原理,想要在光照傳感器的基礎(chǔ)上增加別的功能也無(wú)從下手。

所以,我覺(jué)得我還是有必要寫一篇更加詳細(xì)更加深入的攻略來(lái)幫助大家理解。我覺(jué)得能驅(qū)動(dòng)一個(gè)芯片和會(huì)驅(qū)動(dòng)一個(gè)芯片是不一樣的,如果你學(xué)會(huì)了如何去驅(qū)動(dòng)一個(gè)芯片,那么換了別的類似的芯片你也能夠得舉一反三。不然的話你每次換一個(gè)芯片都只能去找人家寫好的代碼。

好了,廢話不多說(shuō)了,BH1750的講解馬上開(kāi)始。(注:請(qǐng)一定要從頭到尾看下去,粗略看一下也行,因?yàn)閮?nèi)容是環(huán)環(huán)相扣的,一直看,一直爽!?。。?/p>

我再多說(shuō)一句,就一句,真的,接下來(lái)我講的所有代碼以及相關(guān)的所有文件都可以免費(fèi)發(fā)給你們,鏈接在文章底部,自己去下載吧。

除了本文這個(gè)驅(qū)動(dòng)外,還有另外一種使用方法,可以參考我發(fā)布的博文:

基于stm32驅(qū)動(dòng)bh1750光照傳感器的一種超簡(jiǎn)單的編程方法

二、芯片介紹

BH1750FVI是一款數(shù)字型光強(qiáng)度傳感器集成芯片。某寶上面很多寫著GY30模塊,那些其實(shí)也是用BH1750FVI芯片,只不過(guò)是它把BH1750FVI芯片以及外圍的一些電路做到了一個(gè)板子上面,然后把BH1750FVI的通訊引腳引出來(lái)方便你們用單片機(jī)控制而已。(話說(shuō)大部分國(guó)產(chǎn)芯片都是這個(gè)套路,把人家的芯片拿過(guò)來(lái),加一點(diǎn)外圍電路,然后重新包一層外殼,換個(gè)型號(hào),就變成自己的產(chǎn)品了)

電路工作原理:如圖1所示,BH1750的內(nèi)部由光敏二極管、運(yùn)算放大器、ADC采集、晶振等組成。PD二極管通過(guò)光生伏特效應(yīng)將輸入光信號(hào)轉(zhuǎn)換成電信號(hào),經(jīng)運(yùn)算放大電路放大后,由ADC采集電壓,然后通過(guò)邏輯電路轉(zhuǎn)換成16位二進(jìn)制數(shù)存儲(chǔ)在內(nèi)部的寄存器中(注:進(jìn)入光窗的光越強(qiáng),光電流越大,電壓就越大,所以通過(guò)電壓的大小就可以判斷光照大小,但是要注意的是電壓和光強(qiáng)雖然是一一對(duì)應(yīng)的,但不是成正比的,所以這個(gè)芯片內(nèi)部是做了線性處理的,這也是為什么不直接用光敏二極管而用集成IC的原因)。

BH1750引出了時(shí)鐘線和數(shù)據(jù)線,單片機(jī)通過(guò)I2C協(xié)議可以與BH1750模塊通訊,可以選擇BH1750的工作方式,也可以將BH1750寄存器的光照度數(shù)據(jù)提取出來(lái)。

在這里插入圖片描述

引腳定義:

引腳號(hào) 名稱 說(shuō)明
1 VCC 供電電壓源正極
2 SCL IIC時(shí)鐘線,時(shí)鐘輸入引腳,由MCU輸出時(shí)鐘
3 SDA IIC數(shù)據(jù)線,雙向IO口,用來(lái)傳輸數(shù)據(jù)
4 ADDR IIC地址線,接GND時(shí)器件地址為0100011 ,接VCC時(shí)器件地址為1011100
5 GND 供電電壓源負(fù)極

三、IIC通訊介紹

IIC通訊過(guò)程簡(jiǎn)介

既然BH1750是用IIC通訊的,那么我們就要先了解IIC的通訊原理。IIC由時(shí)鐘線(SCL)和數(shù)據(jù)線(SDA)組成。時(shí)鐘線,聽(tīng)這個(gè)名字就知道和時(shí)間有關(guān)系,沒(méi)錯(cuò),它其實(shí)管理著IIC的通訊時(shí)間。而數(shù)據(jù)線,顧名思義就是用來(lái)傳輸數(shù)據(jù)的線。那么時(shí)鐘線和數(shù)據(jù)線它們是什么關(guān)系呢?你可以把時(shí)鐘線理解為紅綠燈,高電平是綠燈,低電平是紅燈,而數(shù)據(jù)線傳輸?shù)拿恳粋€(gè)數(shù)據(jù)則相當(dāng)于一輛汽車,高電平是奔馳,低電平是寶馬。當(dāng)綠燈亮了的時(shí)候,汽車就可以過(guò)去,只不過(guò)這里的交通規(guī)則是每亮一次綠燈,只能通過(guò)一輛汽車。所以,IIC通訊的過(guò)程就是紅綠燈交替閃爍(也就是時(shí)鐘線輸出方波脈沖),汽車跟著一輛一輛的過(guò)去,過(guò)去的是奔馳,就是傳輸了一個(gè)“1”,過(guò)去的寶馬,就是傳輸了一個(gè)“0”,連續(xù)傳輸8次,就可以組成一個(gè)8位的二進(jìn)制數(shù),也就是一個(gè)字節(jié)的數(shù)據(jù),反復(fù)這個(gè)過(guò)程就能實(shí)現(xiàn)兩個(gè)設(shè)備之間的通訊。

好,上面已經(jīng)大概講解了IIC的通訊過(guò)程,那么下面來(lái)補(bǔ)充一些細(xì)節(jié)。IIC通訊的兩個(gè)設(shè)備是有主從關(guān)系的,比如我們的單片機(jī)在這里就是主設(shè)備,BH1750是從設(shè)備。

時(shí)鐘線是由主設(shè)備輸出,從設(shè)備輸入的,也就是單片機(jī)和BH1750通訊的時(shí)候,單片機(jī)的IO口要給SCL引腳輸出一個(gè)方波脈沖,因?yàn)镮IC設(shè)備支持的最大通訊頻率一般都是400kHz,也就是說(shuō)一個(gè)時(shí)鐘周期(一個(gè)高電平加一個(gè)低電平為一個(gè)周期)不能小于2.5us。單片機(jī)輸出時(shí)鐘的時(shí)候一定要注意高低電平延時(shí)的時(shí)間,延時(shí)的時(shí)間越長(zhǎng),通訊的速率越慢。另外,時(shí)鐘線不會(huì)一直輸出脈沖,只會(huì)在需要通訊的時(shí)候輸出,并且要遵循一定的規(guī)則。需要通訊的時(shí)候時(shí)鐘線先要輸出一個(gè)“起始信號(hào)”告訴從設(shè)備我要開(kāi)始通訊了,其實(shí)就是電平由高到低跳變,但是這個(gè)高電平的持續(xù)時(shí)間不能太短,具體最少要多少時(shí)間需要看芯片手冊(cè),反正延長(zhǎng)一點(diǎn)準(zhǔn)沒(méi)錯(cuò)。然后再根據(jù)固定的時(shí)間輸出高低脈沖,直到到了要停止通訊的時(shí)候,時(shí)鐘線要輸出一個(gè)“結(jié)束信號(hào)”告訴從設(shè)備我不通訊了,其實(shí)就是電平一直拉高。

而數(shù)據(jù)線傳輸?shù)臄?shù)據(jù)是雙向的,單片機(jī)可以給BH1750發(fā)數(shù)據(jù),也可以讀取BH1750的數(shù)據(jù)(也就是BH1750給單片機(jī)發(fā))。需要注意的,單片機(jī)給BH1750發(fā)的數(shù)據(jù)不是隨便發(fā)的,也要符合一定的規(guī)則。首先,單片機(jī)要先發(fā)一個(gè)器件地址(器件地址是7位的,詳細(xì)的內(nèi)容我后面再說(shuō)),再發(fā)送一個(gè)讀寫位(0表示是寫入,1表示讀?。骷刂泛妥x寫位加起來(lái)剛好是一個(gè)字節(jié),然后BH1750會(huì)給你回一個(gè)應(yīng)答位,意思就是“我收到了”。然后單片機(jī)就可以接著發(fā)送數(shù)據(jù)了,每次都是以1個(gè)字節(jié)為間隔發(fā)。收也是類似的,只是把單片機(jī)發(fā)數(shù)據(jù)改成收數(shù)據(jù),這里就不多說(shuō)了,后面會(huì)詳細(xì)講。(注:器件地址是用來(lái)區(qū)分從設(shè)備的,因?yàn)橛袝r(shí)候同一根時(shí)鐘線和數(shù)據(jù)線可能會(huì)連接多個(gè)從設(shè)備,也就是說(shuō)主設(shè)備發(fā)送的數(shù)據(jù)所有的從設(shè)備都可以收到,所以主設(shè)備要先發(fā)送一個(gè)器件地址,告訴所有的從設(shè)備我是給哪個(gè)設(shè)備發(fā)命令,其他設(shè)備收到了也不要執(zhí)行)。

IIC通訊實(shí)例

下面我們看一個(gè)實(shí)際的例子。圖2是OPT3001通訊的讀寫過(guò)程,(OPT3001是我在項(xiàng)目中用到一款低功耗光照傳感器,和BH1750類似,也是IIC通訊協(xié)議,感興趣的同學(xué)可以看一下我發(fā)之前的博文,有講解這個(gè)IC的驅(qū)動(dòng)方式),看懂了這個(gè)圖你就理解IIC的通訊方式了,你就可以當(dāng)著博主的面大聲地說(shuō)“你寫的博文有毛用,你說(shuō)的我全都知道”,如果你還有不理解的地方,那么就坐下來(lái)好好聽(tīng)我解說(shuō)吧。

首先,我們看一下IIC的寫入過(guò)程,最左邊先是有一個(gè)“Start by Master”,也就是單片機(jī)先給一個(gè)“起始信號(hào)”,然后后面接著傳輸了8位數(shù)據(jù)(1 0 0 0 1 A1 A0 R/W)。其中,“1 0 0 0 1 A1 A0”是器件地址,因?yàn)檫@里的器件地址有4個(gè)可選,所以用了A1和A0表示,(注:BH1750只有2個(gè)器件地址),“R/W”是讀寫位,上面我有說(shuō)到,這里是寫入,所以這里的R/W應(yīng)該是一個(gè)“0”。 接著是“ACK by OPT3001”,這是從設(shè)備給主設(shè)備發(fā)的應(yīng)答,就是說(shuō)“你發(fā)的數(shù)據(jù)我收到了,你可以接著發(fā)了”,然后接下來(lái)的RA7-RA0是寄存器地址(因?yàn)榧拇嫫鞑恢挂粋€(gè)所以要先發(fā)地址,告訴它你接下來(lái)要把數(shù)據(jù)存到哪里),再后面的D15-D0是兩個(gè)字節(jié)的數(shù)據(jù)(這些數(shù)據(jù)就是存到前面發(fā)的那個(gè)地址的寄存器里面)。

讀取的過(guò)程和寫入類似,先是“起始信號(hào)”,再是器件地址+讀寫位,接著是應(yīng)答,然后開(kāi)始接收數(shù)據(jù)(單片機(jī)的IO口要從輸出改成輸入了),D15-D0是接收到兩個(gè)字節(jié)的數(shù)據(jù),“ACK by Master”是單片機(jī)給OPT3001發(fā)的應(yīng)答。(只要是接收的一方都要發(fā)應(yīng)答,不應(yīng)答的話通訊就會(huì)結(jié)束,比如讀取的第二個(gè)字節(jié)后面的“No ACK by Master”)

在這里插入圖片描述

好,如果你能堅(jiān)持看到這里,那我敬你是條漢子??!如果你看懂了,那么恭喜你,如果沒(méi)看懂,那也沒(méi)關(guān)系,上面那是IIC一般的通訊方式,后面BH1750的通訊要更加簡(jiǎn)單。

(問(wèn):那你為什么不直接講BH1750。答:我喜歡,你咬我呀,略略略….啪,略略啪,略別別….我錯(cuò)了。)

BH1750的通訊過(guò)程

其實(shí)前面之所以要先講這個(gè)OPT3001而不是直接講BH1750,是因?yàn)锽H1750的IIC其實(shí)算是一個(gè)簡(jiǎn)化版的,不具有通用性,你學(xué)會(huì)了OPT3001的通訊方法,你再去驅(qū)動(dòng)BH1750就很簡(jiǎn)單,相反,如果你只會(huì)驅(qū)動(dòng)BH1750,那么換成別的IIC的芯片你就不一定會(huì)了。

好了,接下來(lái)我們來(lái)看一下BH1750的通訊,BH1750的通訊過(guò)程可以分成5步,中間3步如圖3所示。

(啪啪,問(wèn):為什么要用英文的圖,別以為我不知道有中文版的手冊(cè),說(shuō)你是不是在裝*。答:冤枉,真不是,那個(gè)中文版的圖太糊了,而且英文版其實(shí)也不影響大家去看,老實(shí)說(shuō)我是一個(gè)英語(yǔ)學(xué)渣,我還寫了一篇博文講述一個(gè)學(xué)渣如何看懂英文數(shù)據(jù)手冊(cè),有興趣的同學(xué)可以看一下。真不是打廣告哦。)

在這里插入圖片描述

第1步:發(fā)送上電命令。(上電命令是0x01)。

因?yàn)檫@里沒(méi)有圖,我就不詳細(xì)說(shuō)了,發(fā)送的過(guò)程和第2步基本一致。就是把測(cè)量命令(0x10)改成上電命令(0x01)。

第2步:發(fā)送測(cè)量命令。

下面圖片上的例子,ADDR引腳是接GND的,發(fā)送的測(cè)量命令是“連續(xù)高分辨率測(cè)量(0x10)”。

發(fā)送數(shù)據(jù)的過(guò)程和之前講的OPT3001寫入的過(guò)程基本一樣,先是“起始信號(hào)(ST)”,接著是“器件地址+讀寫位”(器件地址我在上面引腳定義那里有寫),然后是應(yīng)答位,緊接著就是測(cè)量的命令“00010000”(關(guān)于測(cè)量命令,下面會(huì)詳細(xì)說(shuō)明),然后應(yīng)答,最后是“結(jié)束信號(hào)(SP)”。(相比于OPT3001的寫入過(guò)程,BH1750少了一個(gè)發(fā)送寄存器地址的步驟,因?yàn)樗挥幸粋€(gè)寄存器,所以就沒(méi)必要了)

第3步:等待測(cè)量結(jié)束。

測(cè)量的時(shí)間手冊(cè)上面有寫,我這里就不列出來(lái)了,高分辨率連續(xù)測(cè)量需要等待的時(shí)間最長(zhǎng),手冊(cè)上面寫的是平均120ms,最大值180ms,所以為了保證每次讀取到的數(shù)據(jù)都是最新測(cè)量的,程序上面可以延時(shí)200ms以上,當(dāng)然也不用太長(zhǎng),浪費(fèi)時(shí)間。如果你用別的測(cè)量模式,等待時(shí)間都比這個(gè)模式要短。

第4步:讀取數(shù)據(jù)。

先是“起始信號(hào)(ST)”,接著是“器件地址+讀寫位”,然后是應(yīng)答位,緊接著接收1個(gè)字節(jié)的數(shù)據(jù)(單片機(jī)在這個(gè)時(shí)候要把SDA引腳從輸出改成輸入了),然后給BH1750發(fā)送應(yīng)答,繼續(xù)接收1個(gè)字節(jié)數(shù)據(jù),然后不應(yīng)答(因?yàn)槲覀兘邮盏臄?shù)據(jù)只有2個(gè)字節(jié),收完就可以結(jié)束通訊了),最后是“結(jié)束信號(hào)(SP)”。

第5步:計(jì)算結(jié)果。

接收完兩個(gè)字節(jié)還不算完成,因?yàn)檫@個(gè)數(shù)據(jù)還不是測(cè)量出來(lái)的光照強(qiáng)度值,我們還需要進(jìn)行計(jì)算,計(jì)算公式是:光照強(qiáng)度 =(寄存器值[15:0] * 分辨率) / 1.2 (單位:勒克斯lx)

因?yàn)槲覀儚腂H1750寄存器讀出來(lái)的是2個(gè)字節(jié)的數(shù)據(jù),先接收的是高8位[15:8],后接收的是低8位[7:0],所以我們需要先把這2個(gè)字節(jié)合成一個(gè)數(shù),然后乘上分辨率,再除以1.2即可得到光照值。

例如:我們讀出來(lái)的第1個(gè)字節(jié)是0x12(0001 0010),第2個(gè)字節(jié)是0x53(0101 0011),那么合并之后就是0x1253(0001 0010 0101 0011),換算成十進(jìn)制也就是4691,乘上分辨率(我用的分辨率是1),再除以1.2,最后等于3909.17 lx。

四、BH1750的命令

BH1750所有的命令都在圖4。這次我用的是中文版的圖,方便大家看,有點(diǎn)糊勿怪。 這里的指令雖然多,但是實(shí)際上如果僅僅是測(cè)光照值,只用兩條就夠了,通電指令和測(cè)量指令。這里的幾條測(cè)量指令我就不詳細(xì)說(shuō)了,手冊(cè)上是有講的,如果后面你們需要的話我再補(bǔ)上吧。寄存器也只有一個(gè),沒(méi)什么好說(shuō)的。(才不是因?yàn)閼幸膊皇且驗(yàn)樘焯於家影啵?/p>

在這里插入圖片描述

五、BH1750編程教學(xué)

下面的編程我以stm32為例,其實(shí)換成51,stm8或者別的單片機(jī),程序也基本一樣的,不同的單片機(jī)在程序上只是引腳配置的寫法不太一樣,別的基本沒(méi)差別。

我的這個(gè)程序是用OLED顯示光照強(qiáng)度的,想用串口,藍(lán)牙或者別的方式也可以。

注:我下面展示的程序跟我發(fā)給你們的工程會(huì)有一點(diǎn)不一樣,主要是備注,因?yàn)闉榱俗屇銈兏美斫?,我展示的代碼是加了很多備注的,而工程是以前的,備注會(huì)少一點(diǎn)。

1、IIC驅(qū)動(dòng)代碼

//IIC的驅(qū)動(dòng)程序沒(méi)必要自己去寫,能夠看懂每一個(gè)函數(shù)的作用,知道IIC的通訊過(guò)程即可,我這里用的是正點(diǎn)原子的例程
//IIC通訊最基本的幾個(gè)函數(shù)是:起始信號(hào),結(jié)束信號(hào),發(fā)送應(yīng)答(或不應(yīng)答),發(fā)送1個(gè)字節(jié)數(shù)據(jù),接收1個(gè)字節(jié)數(shù)據(jù)
//這些我前面都有講到,如果你前面看懂了,將上面OPT3001的時(shí)序圖和這個(gè)程序結(jié)合起來(lái)看你就很容易想明白

//閑話(可以跳過(guò)):這一份程序是以前大學(xué)做項(xiàng)目的時(shí)候?qū)懙?,其?shí)大部分都是抄的,當(dāng)時(shí)對(duì)程序的理解也是一知半解
//現(xiàn)在回頭看,發(fā)現(xiàn)這個(gè)程序的兼容性很差
//雖然在這個(gè)工程上面運(yùn)行是沒(méi)有問(wèn)題的,但是如果移植到別的工程或者用別的單片機(jī),需要改動(dòng)的地方就很多了
//比如引腳的拉高拉低,這里是直接寫“SDA=1;”,但是這個(gè)SDA的定義是在正點(diǎn)原子自己寫的一個(gè)庫(kù)里面的
//如果你用別的工程,沒(méi)有把這個(gè)庫(kù)加進(jìn)來(lái),那么這個(gè)定義就不成立了
//最好的寫法我覺(jué)得是分成兩個(gè)定義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í)函數(shù)delay_us,這里用的是定時(shí)器,也是要用到正點(diǎn)原子的庫(kù)delay.c
//其實(shí)這里我覺(jué)得可以用for函數(shù)延時(shí),因?yàn)檠訒r(shí)的時(shí)間比較短,也不需要很精確
//如果換了一個(gè)單片機(jī),晶振頻率不同,只需要改一下for函數(shù)延時(shí)的次數(shù)
//然后用示波器量一下這個(gè)時(shí)間,確保是在正常通訊的時(shí)間范圍內(nèi)即可

/***起始信號(hào)***/
void BH1750_Start()
{
  SDA=1;                    //拉高數(shù)據(jù)線
  SCL=1;                   //拉高時(shí)鐘線
  delay_us(5);                 //延時(shí)
  GPIO_ResetBits(bh1750_PORT, sda);                    //產(chǎn)生下降沿
  delay_us(5);                 //延時(shí)
  GPIO_ResetBits(bh1750_PORT, scl);                    //拉低時(shí)鐘線
}

/*****停止信號(hào)******/
void BH1750_Stop()
{
    SDA=0;                   //拉低數(shù)據(jù)線
    SCL=1;                      //拉高時(shí)鐘線
    delay_us(5);                 //延時(shí)
    GPIO_SetBits(bh1750_PORT, sda);                    //產(chǎn)生上升沿
    delay_us(5);                 //延時(shí)
}

/**************************************
發(fā)送應(yīng)答信號(hào)
入口參數(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)   //寫應(yīng)答信號(hào)
		SDA=1; 
	else if(ack == 0)
		SDA=0; 
	else
		return;			
  SCL=1;     //拉高時(shí)鐘線
  delay_us(5);                 //延時(shí)
  SCL=0;      //拉低時(shí)鐘線
  delay_us(5);                //延時(shí)
}

/**************************************
接收應(yīng)答信號(hào)
**************************************/
int BH1750_RecvACK()
{
  GPIO_InitTypeDef GPIO_InitStruct;
  GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;  /*這里一定要設(shè)成輸入上拉,否則不能讀出數(shù)據(jù)*/
  GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
  GPIO_InitStruct.GPIO_Pin=sda;
  GPIO_Init(bh1750_PORT,&GPIO_InitStruct);
	
  SCL=1;            //拉高時(shí)鐘線
  delay_us(5);               //延時(shí)	
  if(GPIO_ReadInputDataBit(GPIOA,sda)==1)//讀應(yīng)答信號(hào)
    mcy = 1 ;  
  else
    mcy = 0 ;				
  SCL=0;                    //拉低時(shí)鐘線
  delay_us(5);                 //延時(shí)
  GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
  GPIO_Init(bh1750_PORT,&GPIO_InitStruct);
  return mcy;
}

/**************************************
向IIC總線發(fā)送一個(gè)字節(jié)數(shù)據(jù)
**************************************/
void BH1750_SendByte(uchar dat)//dat是要發(fā)送的一個(gè)字節(jié)的數(shù)據(jù)
{
  uchar i;
  for (i=0; i<8; i++)         //8位計(jì)數(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次就把一個(gè)字節(jié)的數(shù)據(jù)發(fā)送出去了
    SCL=1;               //拉高時(shí)鐘線
    delay_us(5);             //延時(shí)
    SCL=0;                //拉低時(shí)鐘線
    delay_us(5);            //延時(shí)
  }
  BH1750_RecvACK();
}

/**************************************
在IIC總線接收一個(gè)字節(jié)數(shù)據(jù)
**************************************/
uchar BH1750_RecvByte()
{
  uchar i;
  uchar dat = 0;  //dat是存放接收到的一個(gè)字節(jié)的數(shù)據(jù)
  uchar bit;
	
  GPIO_InitTypeDef GPIO_InitStruct;
  GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;   /*這里一定要設(shè)成輸入上拉,否則不能讀出數(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)部上拉,準(zhǔn)備讀取數(shù)據(jù),
  for (i=0; i<8; i++)         //8位計(jì)數(shù)器
  {
    dat <<= 1;       //循環(huán)8次,每次接收一個(gè)位,8次之后完成一個(gè)字節(jié)數(shù)據(jù)的接收
    SCL=1;               //拉高時(shí)鐘線
    delay_us(5);             //延時(shí)
			
	if( SET == GPIO_ReadInputDataBit(bh1750_PORT,sda))//讀取SDA引腳的電平,如果是高電平,就是傳輸“1”
      bit = 0X01;
    else                     //電平傳輸?shù)氖恰?”
      bit = 0x00;  
	dat |= bit;           //讀數(shù)據(jù)    
	SCL=0;                //拉低時(shí)鐘線
    delay_us(5);          //延時(shí)
  }		
  GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_Init(bh1750_PORT, &GPIO_InitStruct );
  return dat;
}

2、BH1750寫入和讀取的函數(shù)

//上面講了IIC的幾個(gè)基本的函數(shù),包括了發(fā)送1字節(jié)和接收1字節(jié)
//但是和BH1750通訊,不僅僅是發(fā)送1個(gè)字節(jié)或者接收1個(gè)字節(jié)那么簡(jiǎn)單
//我們對(duì)BH1750發(fā)送命令的時(shí)候,是要先發(fā)送器件地址+寫入位,然后發(fā)送指令
//讀取數(shù)據(jù)的時(shí)候,需要先發(fā)送器件地址+讀取位,然后連續(xù)讀取2個(gè)字節(jié)
//如果我上面說(shuō)的你都懂了,那么你就可以去看代碼了,如果能跟時(shí)序圖一一對(duì)應(yīng)上,你就理解代碼了

//另外,如果你用的不是BH1750,而是別的IIC通訊的芯片,這兩個(gè)函數(shù)的寫法可能也是不同的
//比如OPT3001,發(fā)送命令的時(shí)候不僅發(fā)發(fā)送命令數(shù)據(jù)還需要發(fā)送寄存器地址,所以一般函數(shù)定義的時(shí)候就要定義兩個(gè)變量
//又或者一些命令是兩個(gè)字節(jié)的,那么你定義的變量類型就需要注意了,函數(shù)里面也需要多發(fā)送一個(gè)字節(jié)數(shù)據(jù)
//這里不理解也無(wú)所謂,不影響學(xué)習(xí)BH1750的驅(qū)動(dòng),以后你做項(xiàng)目用到了別的芯片你可能就突然理解了

//寫入指令
void Single_Write_BH1750(uchar REG_Address)//REG_Address是要寫入的指令
{
  BH1750_Start();                  //起始信號(hào)
  BH1750_SendByte(SlaveAddress);   //發(fā)送設(shè)備地址+寫信號(hào)
  BH1750_SendByte(REG_Address);    //寫入指令
  BH1750_Stop();                   //發(fā)送停止信號(hào)
}

//讀取指令
void mread(void)
{   
  uchar i;	
  BH1750_Start();                          //起始信號(hào)
  BH1750_SendByte(SlaveAddress+1);         //發(fā)送設(shè)備地址+讀信號(hào)

  //注意:這里的for函數(shù)的i<2和下面的if函數(shù)的i==2,我發(fā)現(xiàn)以前的工程寫的居然是3
  //這里其實(shí)我們只需要讀取2個(gè)字節(jié)就行了,后面的合成數(shù)據(jù)也是只用了BUF的前2個(gè)字節(jié)
  //工程文件我沒(méi)改,這個(gè)驅(qū)動(dòng)程序以前也用在了多個(gè)項(xiàng)目上,讀取3個(gè)字節(jié)肯定是也可以正常運(yùn)行的
  //但是我覺(jué)得還是改成2比較好,你們可以測(cè)試一下改成2有沒(méi)有問(wèn)題,測(cè)試之后一定要告訴我結(jié)果,謝謝!!
  for (i=0; i<2; i++)                      //連續(xù)讀取2個(gè)數(shù)據(jù),存儲(chǔ)到BUF里面
  {
    BUF[i] = BH1750_RecvByte();          //BUF[0]存儲(chǔ)高8位,BUF[1]存儲(chǔ)低8位
    if (i == 1)
    {
      BH1750_SendACK(1);                //最后一個(gè)數(shù)據(jù)需要回NOACK
    }
    else
    {		
      BH1750_SendACK(0);                //回應(yīng)ACK
    }
  }
  BH1750_Stop();                          //停止信號(hào)
  delay_ms(5);
}

3、BH1750初始化函數(shù)

//初始化BH1750,根據(jù)需要請(qǐng)參考pdf進(jìn)行修改****
void Init_BH1750()
{
  GPIO_InitTypeDef GPIO_InitStruct;
  /*開(kāi)啟GPIOB的外設(shè)時(shí)鐘*/ 
  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);            //延時(shí)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ù)測(cè)量命令(0x10)
  delay_ms(200); //等待測(cè)量結(jié)束,其實(shí)延時(shí)180ms就行了,延時(shí)200ms只是預(yù)留多一點(diǎn)時(shí)間,保證通訊萬(wàn)無(wú)一失
  mread();       //連續(xù)讀出數(shù)據(jù),存儲(chǔ)在BUF中
  dis_data=BUF[0];
  dis_data=(dis_data<<8)+BUF[1]; //2個(gè)字節(jié)合成數(shù)據(jù) 
  temp1=dis_data/1.2;//計(jì)算光照度
  temp2=10*dis_data/1.2;//把光照度放大10倍,目的是把小數(shù)點(diǎn)后一位數(shù)據(jù)也提取出來(lái)	
  temp2=(int)temp2%10;//求余得到小數(shù)點(diǎn)后一位
  OLED_ShowString(87,2,".",12); //OLED顯示小數(shù)點(diǎn)
  OLED_ShowNum(94,2,temp2,1,12);//OLED顯示小數(shù)	
  return temp1;//返回整數(shù)部分
}
//這里寫的程序還是有點(diǎn)亂的,小數(shù)部分直接在read_BH1750()顯示,整數(shù)部分返回,在main()函數(shù)調(diào)用的時(shí)候顯示
//這...其實(shí)最好是要么都在這個(gè)函數(shù)顯示,要么把temp1和temp2改成全局變量,然后都在main函數(shù)顯示
//這個(gè)變量的名字也是[捂臉],算了算了,往事不堪回首。要吐槽的地方有點(diǎn)多,也沒(méi)時(shí)間去一一改了
//不過(guò)其實(shí)也不影響你們學(xué)IIC通訊的編程方式,就這樣吧

5、main函數(shù)

int main(void)
{ 
  float light;
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 設(shè)置中斷優(yōu)先級(jí)分組2
  delay_init();	    	 //延時(shí)函數(shù)初始化	  
  uart_init(9600);	 	//串口初始化為9600
  LED_Init();				//初始化與LED連接的硬件接口
  Init_BH1750();       //初始化BH1750
  OLED_Init();     //初始化OLED
  OLED_Clear();     //清屏
	
  while(1)
  {
    	light=read_BH1750();  //讀取BH1750的光強(qiáng)數(shù)據(jù)
		OLED_ShowString(0,2,"light:",12);  //顯示光照強(qiáng)度
		OLED_ShowNum(48,2,light,6,12);	//顯示光照度
		OLED_ShowString(110,2,"lx",12); //顯示“l(fā)x”
		if(light<100)//光照度小于100lx,點(diǎn)亮LED燈
		{
			LED1=0;
			OLED_ShowString(38,5,"LED-ON ",12);
		}
		else
		{
			LED1=1;
			OLED_ShowString(38,5,"LED-OFF",12);
		}
  }		
}	

六、測(cè)試

我這個(gè)程序是大學(xué)的時(shí)候做的一個(gè)課程設(shè)計(jì),現(xiàn)在也沒(méi)有實(shí)物可以測(cè)試了,我就發(fā)我以前報(bào)告里的圖給你們看一下效果吧。圖5是亮度大于100lx的時(shí)候,圖6是低于100lx的時(shí)候。

在這里插入圖片描述在這里插入圖片描述

七、總結(jié)

要驅(qū)動(dòng)BH1750,或者其他IIC通訊的芯片,最好還是先了解IIC通訊的時(shí)序,了解通訊的原理,然后才是寫驅(qū)動(dòng)程序。驅(qū)動(dòng)程序也可以分成三部分,第一部分是IIC通訊基本的協(xié)議(一般抄就完事了),第二部分是芯片的讀寫過(guò)程,需要根據(jù)實(shí)際芯片的通訊方式寫。第三部分是指令控制相關(guān)的函數(shù),BH175比較簡(jiǎn)單,只有測(cè)量和計(jì)算。有些可能還有多個(gè)設(shè)置不同的模式的函數(shù),校驗(yàn)數(shù)據(jù)的函數(shù)等等,不過(guò)它們其實(shí)都是發(fā)送指令,只是發(fā)不同的指令執(zhí)行不同的操作而已。

OPT3001的驅(qū)動(dòng)教程你們可以大概看一下:https://blog.csdn.net/ShenZhen_zixian/article/details/102876443

最后再說(shuō)點(diǎn)閑話吧,寫到這里剛好有點(diǎn)感觸,其實(shí)寫博文的初衷只是為了把工作中總結(jié)出來(lái)的一些經(jīng)驗(yàn)記錄下來(lái),加強(qiáng)記憶。因?yàn)橄裎覀冞@種做硬件研發(fā)的,經(jīng)驗(yàn)是最值錢的,然后上傳資源也只是為了賺點(diǎn)積分,因?yàn)檫€在大學(xué)那會(huì)想下載別人的程序的時(shí)候總是恨自己沒(méi)有積分。后來(lái)發(fā)現(xiàn)我寫的博文和上傳的資源能夠幫助到一些人,所以我就一直堅(jiān)持著更新,哪怕每天加班也會(huì)抽空寫一下文章寫一下代碼,即使我自己賺的積分其實(shí)一次都沒(méi)用過(guò)??赡苓@就是傳承吧,以前遇到問(wèn)題的時(shí)候總能在前輩的博文中找到答案,現(xiàn)在輪到自己分享自己的經(jīng)驗(yàn)給后來(lái)者了,希望我的文章也能夠幫助到你。

本文用到的工程源碼可以在下面的鏈接下載:

源碼下載鏈接1:https://pan.baidu.com/s/1HnedCg3sC4HU8iEOf4dYOw ,提取碼:xs8o
源碼下載鏈接2:https://pan.baidu.com/s/1QOC01P5M99LzP4i1Voro6g,提取碼:abcd

創(chuàng)作不易,希望你們尊重別人的勞動(dòng),點(diǎn)贊+關(guān)注支持一下吧,謝謝大家了,博主也會(huì)繼續(xù)更新更多的大學(xué)生專欄,如果你們還有什么問(wèn)題,可以評(píng)論留言或者私信給我。

推薦器件

更多器件
器件型號(hào) 數(shù)量 器件廠商 器件描述 數(shù)據(jù)手冊(cè) ECAD模型 風(fēng)險(xiǎn)等級(jí) 參考價(jià)格 更多信息
XRCGB25M000F3A00R0 1 Murata Manufacturing Co Ltd Parallel - Fundamental Quartz Crystal, 25MHz Nom, ROHS AND REACH COMPLIANT, SMALL, SMD, 3 PIN

ECAD模型

下載ECAD模型
$0.34 查看
M25P05-AVMN6P 1 Rochester Electronics LLC 64KX8 FLASH 2.7V PROM, PDSO8, 0.150 INCH, ROHS COMPLIANT, PLASTIC, SOP-8
$0.79 查看
S29GL256P10FFI020 1 AMD Flash, 16MX16, 100ns, PBGA64,
$9.29 查看

相關(guān)推薦

電子產(chǎn)業(yè)圖譜