一、前言
之所以寫(xiě)這篇文章,原因有兩個(gè)。
一是:有個(gè)師弟跟我說(shuō)我發(fā)布的文章都偏向于工作者,能不能寫(xiě)一些大學(xué)生能用到的東西,我想了一下,確實(shí)是,我寫(xiě)的文章大多是我在工作中總結(jié)出來(lái)的心得,對(duì)于初學(xué)者來(lái)說(shuō)確實(shí)有點(diǎn)難以理解。
二是:我覺(jué)得這個(gè)光照傳感器很多大學(xué)生都能用到,但是網(wǎng)上的教程雖多卻也不一定能夠幫助大家深入了解這款傳感器。大家更多的是看完攻略之后能夠驅(qū)動(dòng),但是其實(shí)并不了解它的工作原理,想要在光照傳感器的基礎(chǔ)上增加別的功能也無(wú)從下手。
所以,我覺(jué)得我還是有必要寫(xiě)一篇更加詳細(xì)更加深入的攻略來(lái)幫助大家理解。我覺(jué)得能驅(qū)動(dòng)一個(gè)芯片和會(huì)驅(qū)動(dòng)一個(gè)芯片是不一樣的,如果你學(xué)會(huì)了如何去驅(qū)動(dòng)一個(gè)芯片,那么換了別的類(lèi)似的芯片你也能夠得舉一反三。不然的話你每次換一個(gè)芯片都只能去找人家寫(xiě)好的代碼。
好了,廢話不多說(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)度傳感器集成芯片。某寶上面很多寫(xiě)著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) | 名稱(chēng) | 說(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)于一輛汽車(chē),高電平是奔馳,低電平是寶馬。當(dāng)綠燈亮了的時(shí)候,汽車(chē)就可以過(guò)去,只不過(guò)這里的交通規(guī)則是每亮一次綠燈,只能通過(guò)一輛汽車(chē)。所以,IIC通訊的過(guò)程就是紅綠燈交替閃爍(也就是時(shí)鐘線輸出方波脈沖),汽車(chē)跟著一輛一輛的過(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è)讀寫(xiě)位(0表示是寫(xiě)入,1表示讀?。?,器件地址和讀寫(xiě)位加起來(lái)剛好是一個(gè)字節(jié),然后BH1750會(huì)給你回一個(gè)應(yīng)答位,意思就是“我收到了”。然后單片機(jī)就可以接著發(fā)送數(shù)據(jù)了,每次都是以1個(gè)字節(jié)為間隔發(fā)。收也是類(lèi)似的,只是把單片機(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通訊的讀寫(xiě)過(guò)程,(OPT3001是我在項(xiàng)目中用到一款低功耗光照傳感器,和BH1750類(lèi)似,也是IIC通訊協(xié)議,感興趣的同學(xué)可以看一下我發(fā)之前的博文,有講解這個(gè)IC的驅(qū)動(dòng)方式),看懂了這個(gè)圖你就理解IIC的通訊方式了,你就可以當(dāng)著博主的面大聲地說(shuō)“你寫(xiě)的博文有毛用,你說(shuō)的我全都知道”,如果你還有不理解的地方,那么就坐下來(lái)好好聽(tīng)我解說(shuō)吧。
首先,我們看一下IIC的寫(xiě)入過(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”是讀寫(xiě)位,上面我有說(shuō)到,這里是寫(xiě)入,所以這里的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ò)程和寫(xiě)入類(lèi)似,先是“起始信號(hào)”,再是器件地址+讀寫(xiě)位,接著是應(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é)渣,我還寫(xiě)了一篇博文講述一個(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寫(xiě)入的過(guò)程基本一樣,先是“起始信號(hào)(ST)”,接著是“器件地址+讀寫(xiě)位”(器件地址我在上面引腳定義那里有寫(xiě)),然后是應(yīng)答位,緊接著就是測(cè)量的命令“00010000”(關(guān)于測(cè)量命令,下面會(huì)詳細(xì)說(shuō)明),然后應(yīng)答,最后是“結(jié)束信號(hào)(SP)”。(相比于OPT3001的寫(xiě)入過(guò)程,BH1750少了一個(gè)發(fā)送寄存器地址的步驟,因?yàn)樗挥幸粋€(gè)寄存器,所以就沒(méi)必要了)
第3步:等待測(cè)量結(jié)束。
測(cè)量的時(shí)間手冊(cè)上面有寫(xiě),我這里就不列出來(lái)了,高分辨率連續(xù)測(cè)量需要等待的時(shí)間最長(zhǎng),手冊(cè)上面寫(xiě)的是平均120ms,最大值180ms,所以為了保證每次讀取到的數(shù)據(jù)都是最新測(cè)量的,程序上面可以延時(shí)200ms以上,當(dāng)然也不用太長(zhǎng),浪費(fèi)時(shí)間。如果你用別的測(cè)量模式,等待時(shí)間都比這個(gè)模式要短。
第4步:讀取數(shù)據(jù)。
先是“起始信號(hào)(ST)”,接著是“器件地址+讀寫(xiě)位”,然后是應(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ī)在程序上只是引腳配置的寫(xiě)法不太一樣,別的基本沒(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)必要自己去寫(xiě),能夠看懂每一個(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)的地方就很多了
//比如引腳的拉高拉低,這里是直接寫(xiě)“SDA=1;”,但是這個(gè)SDA的定義是在正點(diǎn)原子自己寫(xiě)的一個(gè)庫(kù)里面的
//如果你用別的工程,沒(méi)有把這個(gè)庫(kù)加進(jìn)來(lái),那么這個(gè)定義就不成立了
//最好的寫(xiě)法我覺(jué)得是分成兩個(gè)定義SDA_High和SDA_Low
//然后在頭文件聲明#define SDA_High GPIO_SetBits(GPIOB,GPIO_Pin_0)
//#define SDA_GPIO_ResetBits(GPIOB,GPIO_Pin_0)
//這樣寫(xiě)的好處是如果要移植,只需要把 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) //寫(xiě)應(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寫(xiě)入和讀取的函數(shù)
//上面講了IIC的幾個(gè)基本的函數(shù),包括了發(fā)送1字節(jié)和接收1字節(jié)
//但是和BH1750通訊,不僅僅是發(fā)送1個(gè)字節(jié)或者接收1個(gè)字節(jié)那么簡(jiǎn)單
//我們對(duì)BH1750發(fā)送命令的時(shí)候,是要先發(fā)送器件地址+寫(xiě)入位,然后發(fā)送指令
//讀取數(shù)據(jù)的時(shí)候,需要先發(fā)送器件地址+讀取位,然后連續(xù)讀取2個(gè)字節(jié)
//如果我上面說(shuō)的你都懂了,那么你就可以去看代碼了,如果能跟時(shí)序圖一一對(duì)應(yīng)上,你就理解代碼了
//另外,如果你用的不是BH1750,而是別的IIC通訊的芯片,這兩個(gè)函數(shù)的寫(xiě)法可能也是不同的
//比如OPT3001,發(fā)送命令的時(shí)候不僅發(fā)發(fā)送命令數(shù)據(jù)還需要發(fā)送寄存器地址,所以一般函數(shù)定義的時(shí)候就要定義兩個(gè)變量
//又或者一些命令是兩個(gè)字節(jié)的,那么你定義的變量類(lèi)型就需要注意了,函數(shù)里面也需要多發(fā)送一個(gè)字節(jié)數(shù)據(jù)
//這里不理解也無(wú)所謂,不影響學(xué)習(xí)BH1750的驅(qū)動(dòng),以后你做項(xiàng)目用到了別的芯片你可能就突然理解了
//寫(xiě)入指令
void Single_Write_BH1750(uchar REG_Address)//REG_Address是要寫(xiě)入的指令
{
BH1750_Start(); //起始信號(hào)
BH1750_SendByte(SlaveAddress); //發(fā)送設(shè)備地址+寫(xiě)信號(hào)
BH1750_SendByte(REG_Address); //寫(xiě)入指令
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)以前的工程寫(xiě)的居然是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ù)部分
}
//這里寫(xiě)的程序還是有點(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í)序,了解通訊的原理,然后才是寫(xiě)驅(qū)動(dòng)程序。驅(qū)動(dòng)程序也可以分成三部分,第一部分是IIC通訊基本的協(xié)議(一般抄就完事了),第二部分是芯片的讀寫(xiě)過(guò)程,需要根據(jù)實(shí)際芯片的通訊方式寫(xiě)。第三部分是指令控制相關(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)閑話吧,寫(xiě)到這里剛好有點(diǎn)感觸,其實(shí)寫(xiě)博文的初衷只是為了把工作中總結(jié)出來(lái)的一些經(jīng)驗(yàn)記錄下來(lái),加強(qiáng)記憶。因?yàn)橄裎覀冞@種做硬件研發(fā)的,經(jīng)驗(yàn)是最值錢(qián)的,然后上傳資源也只是為了賺點(diǎn)積分,因?yàn)檫€在大學(xué)那會(huì)想下載別人的程序的時(shí)候總是恨自己沒(méi)有積分。后來(lái)發(fā)現(xiàn)我寫(xiě)的博文和上傳的資源能夠幫助到一些人,所以我就一直堅(jiān)持著更新,哪怕每天加班也會(huì)抽空寫(xiě)一下文章寫(xiě)一下代碼,即使我自己賺的積分其實(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é)生專(zhuān)欄,如果你們還有什么問(wèn)題,可以評(píng)論留言或者私信給我。