2017年7月29日 星期六

MicroPython on ESP8266 (十五) : 光敏電阻與 ADC 測試

週四晚上去學木工回來後找出前不久在露天買的兩塊散裝 ESP-12S 模組 (附 Breakout 板與排針), 剛好零件盒裡還有一對 2*8 排母, 就動手焊接 Breakout 板與洞洞板. 焊好後測試正常, 今天就用這開發板的類比輸入腳 ADC 來測試光敏電阻吧! 關於光敏電阻, 參考之前的 Arduino 測試文章 :

Arduino 光敏電阻測試

本系列之前的測試紀錄參考 :

MicroPython on ESP8266 (二) : 數值型別測試
MicroPython on ESP8266 (三) : 序列型別測試
MicroPython on ESP8266 (四) : 字典與集合型別測試
MicroPython on ESP8266 (五) : WiFi 連線與 WebREPL 測試
MicroPython on ESP8266 (六) : 檔案系統測試
MicroPython on ESP8266 (七) : 時間日期測試
MicroPython on ESP8266 (八) : GPIO 測試
MicroPython on ESP8266 (九) : PIR 紅外線移動偵測
MicroPython on ESP8266 (十) : socket 模組測試
MicroPython on ESP8266 (十一) : urllib.urequest 模組測試
MicroPython on ESP8266 (十二) : urequests 模組測試
MicroPython on ESP8266 (十三) : DHT11 溫溼度感測器測試
# MicroPython on ESP8266 (十四) : 網頁伺服器測試

MicroPython 文件參考 :

http://docs.micropython.org/en/latest/micropython-esp8266.pdf
http://docs.micropython.org/en/latest/pyboard/library/usocket.html#class-socket
http://docs.micropython.org/en/v1.8.7/esp8266/library/usocket.html#module-usocket

ESP8266 不像 Arduino 的 ATMEGA328P 晶片那樣具備多個類比輸入腳 (A0~A7), 很陽春地, 它只提供了一個類比輸入 ADC, 可進行 10 位元精度的類比數位轉換. 注意, 上海安信可 (AI-Thinker) 製造的ESP-01 模組沒有接出 ADC 腳, 因此無法進行光敏電阻實驗, 必須使用有接出 ADC 的 ESP-07 或 ESP-12 系列模組; 另外, NodeMCU 與 D1 Mini 等開發板也有接出此 ADC, 不過板子上的 ADC 腳的名稱有些標 A0 (例如 D1 Mini 即是), ADC 的其他別名還有 Pin 6 與 TOUT 等, 參考 :

# ESP8266 ADC – Reading Analog Values with NodeMCU

ESP8266 的 ADC 腳接受 0~3.3V 的類比輸入電壓, 內建的 10 位元類比數位轉換電路會將其轉成 0~1024 的整數數值, 亦即 0V 對應 0, 而 3.3V 對應 1024. 但根據上面這篇文章, 較早製造的 ESP8266 的 ADC 雖然可輸入 3.3V 以內的電壓, 但是到 1V 時就已到達 1024 了, 亦即事實上 ADC 的轉換範圍只有 0~1 V 而已 :

"Note: with earlier versions of the ESP, the A0 pin operated at maximum voltage of 1.0V, so check your board A0 voltage before following the next circuit."

在 MicroPython 官網也有相同的說明, 參考 :

# 8. Analog to Digital Conversion

"The values returned from the read() function are between 0 (for 0.0 volts) and 1024 (for 1.0 volts). Please note that this input can only tolerate a maximum of 1.0 volts and you must use a voltage divider circuit to measure larger voltages."

其實官網說 ADC 最大容忍電壓為 1V 似乎言過其實, 我手上的三塊 ESP-12 模組都是在 1V 時即輸出 1024, 但我將 ADC 接到 3.3V 去測試並不會燒毀此腳 (還是輸出 1024), 我想 ESP8266 裡面可能有內建限壓電路, 但為了讓 ADC 輸出與光度呈線性變化, 必須去除 1~3.3V 的非線性飽和區域, 所以還是照它建議使用一個電阻分壓電路來將 ADC 最高電壓限制在 1V.

我參考下面這篇建議, 使用 220 歐姆與 100 歐姆串聯來分壓, 這樣 100 歐姆電阻的壓降就剛剛好是 1V 左右 (若使用 D1 Mini 不需要這 220+100 的分壓電路, 接 3.3v 即可, 參考底下補充說明) :

3.3V*100/(100+220)=1.03V

ESP8266 ADC - Analog Sensors

我用 upverter 繪製完整電路圖如下 :


上圖中經過 220+100 歐姆分壓電路獲得 1V 電壓後接到光敏電阻與 10K 的串聯電路, 光敏電阻在上, 10K 電阻在下, 中間分壓點就直接連到 ESP8266 的 ADC 腳即可. 這裡光敏電阻放在上面的原因是我想讓 ADC 輸出與光度成正比, 亦即最亮時輸出 1024, 完全暗掉時輸出 0, 如果反過來光敏電阻在下, 10K 在上的話就會呈反比 : 最亮時輸出 0, 最暗時輸出 1024 (之前在做 Arduino 測試時就是採用這種配置). 因為光敏電阻的電阻值會隨亮度增加而降低, 最亮時會降到接近 1 歐姆, 而最暗時則在 1M 歐姆左右. 因此最亮時壓降幾乎落在 10K 電阻上, ADC 輸入電壓為 1V 左右, 因此輸出為 1024; 而最暗時壓降落在光敏電阻, 10K 的壓降接近 0V, 因此輸出接近 0.

MicroPython 在 machine 模組提供了 ADC() 函數來建立一個 ADC 物件,  呼叫此物件的 read() 方法即可讀取類比數位轉換器的輸出 (對應 0~1V 的整數 0~1024) :

>>> import machine
>>> adc=machine.ADC(0)  #指定 Analog Pin 0 建立 ADC 物件
>>> type(adc)
<class 'ADC'>                            #型態為 ADC 的物件
>>> adc.read()
535

下面測試 1 以無窮迴圈來連續測量 ADC 的數位輸出 (每秒測量一次) :

測試 1 : 每秒測量一次亮度 (0~100%)

#main.py
import machine
import time

while True:
    adc=machine.ADC(0)    #建立 ADC 物件
    value=adc.read()            #讀取類比數位轉換器輸出
    print(value, str(round(value*100/1024)) + '%')     #輸出值與百分數
    time.sleep(1)                  #延遲 1 秒

因為要用到延遲功能以免讀取頻率太快, 所以也要匯入 time 模組. 這裡要注意 round() 的輸出為數值, 與 '%' 串接之前必須用 str() 函數轉成字串.

MicroPython v1.9.1-8-g7213e78d on 2017-06-12; ESP module with ESP8266
Type "help()" for more information.
>>>
PYB: soft reboot
#11 ets_task(40100164, 3, 3fff829c, 4)
WebREPL is not configured, run 'import webrepl_setup'
Connecting to AP ...
Connected:IP= 192.168.2.111
1024 100%
451 44%
499 49%
454 44%
853 83%
870 85%
871 85%
881 86%
870 85%
869 85%
871 85%
871 85%
877 86%
872 85%
874 85%
979 96%
871 85%
977 95%
1024 100%
1024 100%
4 0%
5 0%



下面測試 2 是複製 Arduino 版的範例 2, 使用積分濾波器來抑制雜訊突波 :

測試 2 : 每秒測量一次亮度 (0~100%) - 使用積分濾波器

import machine
import time

while True:
    adc=machine.ADC(0)  #建立 ADC 物件
    value=adc.read()          #讀取類比數位轉換器輸出
    value=0.3*value + 0.7*adc.read();  #積分濾波
    print(value, str(round(value*100/1024)) + '%') #輸出值與百分數
    time.sleep(1)                #延遲 1 秒

下面測試 3 複製 Arduino 版的範例 3, 我把 GPIO 14 設為輸出埠, 然後串接一個 LED 與 150 歐姆電阻, 利用 CdS 的亮度來控制 LED 閃爍, 光度越亮 LED 閃爍速率越慢, 越暗閃得越快 :

測試 3 : 利用 CdS 的亮度來控制 LED 閃爍

import machine
import time

p14=machine.Pin(14, machine.Pin.OUT)    #設定 GPIO 14 為輸出

while True:
    adc=machine.ADC(0)  #建立 ADC 物件
    value=adc.read()    #讀取類比數位轉換器輸出
    value=int(0.3*value + 0.7*adc.read());  #積分濾波 (轉成整數)
    print(value, str(round(value*100/1024)) + '%') #輸出值與百分數
    p14.value(1)    #點亮 LED
    time.sleep_ms(value)    #延遲 value 毫秒
    p14.value(0)    #熄滅 LED
    time.sleep_ms(value)    #延遲 value 毫秒

這裡仍然延續測試 2 的積分濾波器來抑制雜訊, 但是另外加上 int() 函數來轉成整數, 因為 time.sleep() 函數的傳入參數必須是整數.


接著要複製 Arduino 版的範例 5, 利用光敏電阻測得之亮度來控制 LED 燈明滅. 這裡要用到上回買的一路低準位觸發繼電器, 輸入端有 3 隻腳 : VCC, GND 與 IN (控制信號輸入), 雖然繼電器蓋子上低壓側打的是 5V, 但實際上 VCC 與 IN 用 3.3V 也是可以驅動的. 輸出端也有 3 個電器端子 : COM (共接點), NO (Normal Open) 與 NC (Normal Close), 表示控制信號未觸發時 (即 IN 為 HIGH), NO 接點與 COM 之間是斷路的, NC 接點與 COM 之間是接通的. 當 IN 在 LOW 位準時將觸發繼電器動作, 使 NO-COM 之間接通, 而 NC-COM 之間斷開, 參考 :

# 1路 繼電器 模組 5V 低電平 arduino 適用 $18

高壓側部分此繼電器最高可接 250V, 此處為了安全我把智慧小車用的兩節 18650 (約 8.5V) 拿來驅動規格為 12V 的 LED 聚光燈, 亦即將 LED 聚光燈接在 NO 端子後串接 18650 電池再接到 COM 端子, 當 IN 未觸發 (=3.3V) 時高壓側是斷開的, LED 不亮, 當 IN 觸發時 (=0V), NO-COM 閉合使得 LED 點亮.

測試 4 : 利用光敏電阻控制 LED 燈明滅

import machine
import time

p14=machine.Pin(14, machine.Pin.OUT)

while True:
    adc=machine.ADC(0)  #建立 ADC 物件
    value=adc.read()    #讀取類比數位轉換器輸出
    value=int(0.3*value + 0.7*adc.read());  #積分濾波
    luminance=round(value*100/1024)
    print(value, str(luminance) + '%') #輸出值與百分數
    if luminance < 30:      #點亮 LED 燈
        p14.value(0)               #此繼電器為低觸發 (LED 燈接 NO 端)
    elif luminance > 35:  #熄滅 LED 燈
        p14.value(1)               #HIGH:不觸發
    time.sleep(1)


上面程式中我為了測試方便將點亮 LED 的亮度設為低於 30% 時點亮 LED; 高於 35% 時熄滅 LED, 中間的 5% 差距用來抑制臨界現象, 如果只用單一門檻來明滅 LED 的話, 當亮度在該臨界值附近徘徊時會造成 LED 閃爍. 實際上運用時應該調低, 例如控制路燈時 10% 以下點亮路燈, 15% 以上熄滅路燈應該是很適當的.

關於繼電器控制, 可以參考下面這篇不錯的文章 :

# Controlling relays using Micropython and an ESP8266 

既然已可利用 ADC 與光敏電阻取得亮度資訊, 就可以在之前的 DHT 溫溼度實驗上添加亮度資訊, 並記錄在 ThingSpeak 物聯網資料庫 (亮度我指定為 field4), 關於 DHT 參考 :

MicroPython on ESP8266 (十三) : DHT11 溫溼度感測器測試

在下面的測試 5 中我照上述實驗重新接上 DHT 模組, 並將其輸出信號連接到 ESP8266 的 GPIO 14. 此外, 為了方便觀察系統是否有在正常運作,  我也在 GPIO 16 串接了一個 LED 與 150 歐姆電阻, 在等待 ThingSpeak 規定的 16 秒寫入週期時間內, 讓此 LED 閃爍, 然後會停頓 1~2 秒以便傳送資料.

測試 5 : 將光敏電阻與 DHT 模組之亮度與溫濕度紀錄在 ThingSpeak 資料庫

from machine import Pin,ADC
import dht
import time
import urequests

DHTPIN=Pin(16, Pin.IN)
LEDPIN=Pin(14, Pin.OUT)
d=dht.DHT11(DHTPIN)
adc=ADC(0)

host='http://api.thingspeak.com'
api_key='NO5N8C7T2KINFCQE'

def LED_blink(pin,s):
    for i in range(1,10*s):
        pin.value(1)
        time.sleep_ms(50)
        pin.value(0)
        time.sleep_ms(50)

while True:
    try:
        d.measure()                
        t=d.temperature()      
        f=round(t * 9/5 + 32)
        h=d.humidity()
        a=adc.read()  
        a=int(0.3*a + 0.7*adc.read())
        a=round(a*100/1024)
        url='%s/update?api_key=%s&field1=%s&field2=%s&field3=%s&field4=%s' %(host, api_key, t, f, h, a)
        print('Temperature=', t, 'C', '/', f, 'F', 'Humidity=', h, '%', 'Luminance=', a, '%')
        r=urequests.get(url)
        print('response=', r.text)
    except:
        print('urequests.get() exception occurred!')
    LED_blink(LEDPIN,16)

注意, 這裡我改寫了 LED_brink() 函數, 添加了 pin 當作第一參數以便傳入任何作為 LED 輸出的 PIN 物件, 而非原先使用 ESP-01 模組時寫死在 GPIO 2 腳.



OK, 在這個尼莎颱風來襲的夜晚, 終於把 ADC 相關的實驗做完了, 真是可喜可賀. 本來下午看沒風沒雨的, 二哥補習班也停課, 有點想回鄉下去, 但舅媽說颱風天不用跑一趟, 她會煮冬粉與咖哩帶去我家給爸. 而且菁菁與姊姊明天傍晚回到高雄要去高鐵載她們, 周一可能還有個海棠要撲來, 所以就只好待在高雄了.

颱風天窩在家做實驗最棒了.

參考 :

自製光控開關 (天黑時自動點燈的電路)
ESP8266-based DIY wifi baby monitor
ESP8266 ADC – Reading Analog Values with NodeMCU
ESP8266 ADC - Analog Sensors
https://github.com/mithru/MicroPython-Examples
ESP8266 first project: home automation with relays, switches, PWM, and an ADC

2017-08-09 補充 :

這幾天在測試 1602 LCD 顯示溫溼度與亮度時發現, D1 Mini 的 A0 (ADC) 接腳與上面實驗使用的 ESP-12 模組不一樣, D1 Mini 不需要用 100+220 歐姆電阻分壓電路將 3.3v 分出 1V 電壓, 這樣做的話會在最亮強光下只得到 50% 亮度. 若使用 D1 Mini 應撤除分壓電路, 直接將光敏電阻與 10K 歐姆分壓電路接 3.3V 即可, 參考 D1 Mini 的接腳介紹 :

https://wiki.wemos.cc/products:d1:d1_mini

沒有留言 :