2017年7月30日 星期日

2017 年第 30 周記事

本周末來了兩個颱風, 週六是尼莎, 週日是海棠, 高雄市幾乎都沒感受到風雨, 只有今晚海棠過境時有點颱風味道. 菁菁與姊姊她們的花蓮行今日結束, 我晚上 8 點去高鐵接她們時剛好海棠風雨正強, 原本還擔心南迴段會因颱風無法通行哩!

本周二三請國內旅遊帶左營阿姨與她孫子, 淑珍姐, 以及爸爸去恆春玩, 造訪了台灣最南端碑, 出火, 龍鑾潭等地. 週三剛回來, 菁菁又跟水某同事的母姊團到花蓮, 她的暑輔等於本周只上了周一的課.

由於颱風之故, 本周沒有回鄉下, 但其實這兩個颱風在高雄影響還不大, 實在是多慮了. 剛剛得知明日周一高雄放颱風假, 但我覺得應該只是下雨而已.

MicroPython on ESP8266 (十六) : 蜂鳴器測試

之前的 MicroPython 測試都是以 LED 為輸出對象, 局限於光訊號之呈現, 本篇要複製之前的 Arduino 聲音測試, 利用蜂鳴器 (Buzzer) 來測試 ESP8266 GPIO 的 PWM 聲音輸出功能, 參考 :

# 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 on ESP8266 (十五) : 光敏電阻與 ADC 測試

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

以下我採用了 WeMOS D1 Mini (ESP-12 模組) 進行測試, 因為這塊板子有內建 Micro USB 插槽,具備 4MB Flash 記憶體, 同時 D4 針腳 (GPIO 2) 有板上內建 LED, 隨插即可進行物聯網專案測試開發. 關於 D1 Mini 參考 :

# WeMOS D1 Mini 開發板測試

其針腳編號 D0~D8 與 ESP8266 GPIO 之對應如下 :

 板子腳位 ESP8266 腳位 功能
 D0 GPIO16 IO
 D1 GPIO5 IO, SCL 
 D2 GPIO4 IO, SDA 
 D3 GPIO0 IO, 內建 10K 上拉電阻
 D4 GPIO2 IO, 內建 10K 上拉電阻與 LED
 D5 GPIO14 IO, SCK
 D6 GPIO12 IO, MISO
 D7 GPIO13 IO, MOSI
 D8 GPIO15 IO, SS, 內建 10K 上拉電阻
 TX TXD UART 送端
 RX RXD UART 收端
 A0 ADC 類比輸入 (0~3.3V)

ESP8266 GPIO 的 PWM 功能測試已紀錄在上述第八篇文章中, 簡言之, 就是利用 MicroPython 的 machine 模組之 PWM 類別, 傳入指定 GPIO 腳之 Pin 物件來建立 PWM 物件, 再呼叫 PWM 物件之 freq() 與 duty() 方法來調變輸出之方波, 例如 :

from machine import Pin, PWM
pwm2=PWM(Pin(2))   #建立 GPIO 2 的 PWM 物件
pwm2.freq(300)           #設定 PWM 物件之頻率  
pwm2.duty(512)           #設定 PWM 物件之工作週期

其中 freq() 與 duty() 若未傳入參數則為 getter, 即讀取最近之設定值. 若要停止 PWM 功能, 則呼叫 deinit() 方法即可, 相當於 Arduino 的 noTone() 函數功能. PWM 物件之方法表列如下 :

 PWM 方法 說明
 freq([Hz]) 傳入 0~1000 設定方波頻率, 否則為讀取
 duty([cycle]) 傳入 0~1023 設定 Duty cycle (512 為 50%), 否則為讀取
 deinit() 關閉 PWM 功能

參考官網文件 :

# PWM (pulse width modulation)

注意, ESP8266 的 GPIO 中, 只有 GPIO 16 (即 D1 Mini 的 D0) 不具 PWM 功能 :

"PWM can be enabled on all pins except Pin(16). There is a single frequency for all channels, with range between 1 and 1000 (measured in Hz). The duty cycle is between 0 and 1023 inclusive."

如果傳入 Pin(16) 給 PWM() 建構式無法建立 PWM 物件 :

>>> pwm=PWM(Pin(16))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: PWM not supported on pin 16  

接下來我想複製 Arduino 聲音測試的範例, 改編為 MicroPython on ESP8266 版. 首先是讓蜂鳴器發出 1KHz 的 Bee Bee 聲, 硬體接線上只要將無源蜂鳴器的 + 端接在 D1 Mini 的 D2 腳 (GPIO 4), 另一端接地即可 (無源蜂鳴器是有極性的). 雖然蜂鳴器上有標示 5V 規格, 其實 3.3V 也是可以用的.

測試 1 : 使蜂鳴器發出 1KHz 的 Bee Bee 告警聲

#main.py or myapp.py
from machine import Pin,PWM
import time

def alarmBeep(pwm):
   pwm.freq(1000)     #設定頻率為 1KHz    
   pwm.duty(512)      #設定工作週期為 50%
   time.sleep(1)          #持續時間 1 秒
   pwm.deinit()          #停止發聲
   time.sleep(2)          #持續時間 2 秒

pwm=PWM(Pin(4))

while True:
    alarmBeep(pwm)


上面的程式裡, 我將發聲的程式碼寫成一個函數 alarmBeep(), 只要在無窮迴圈中呼叫此函數, 並將 PWM 物件傳給它即可.

測試 2 : 模擬鬧鐘鈴聲

from machine import Pin,PWM
import time

def alarmClockBeep(pwm):
   for i in range(1,5):        #4 次迴圈 (1~4)
       pwm.freq(1000)       #設定頻率為 1KHz  
       pwm.duty(512)         #設定工作週期為 50%
       time.sleep_ms(100)  #持續時間 0.1 秒
       pwm.deinit()             #停止發聲
       time.sleep_ms(200)   #持續時間 0.2 秒
   time.sleep_ms(800)

pwm=PWM(Pin(4))

while True:
    alarmClockBeep(pwm)

這裡要注意的是 range(a, b) 是傳回 a ~ b-1 的整數, 所以 4 次迴圈需用 range(1, 5).

測試 3 : 模擬電話鈴聲

from machine import Pin,PWM
import time

def ringTone(pwm):
   for i in range(1,11):         #10 次迴圈 (1~10)
       pwm.freq(1000)          #設定頻率為 1KHz
       pwm.duty(512)           #設定工作週期為 50%
       time.sleep_ms(50)      #持續時間 50 毫秒
       pwm.freq(500)            #設定頻率為 500Hz
       time.sleep_ms(50)      #持續時間 50 毫秒
   pwm.deinit()                   #停止發聲
   time.sleep(2)                   #持續時間 2 秒

pwm=PWM(Pin(4))

while True:
    ringTone(pwm)

這電話聲模擬得還真像.

測試 4 : 模擬警車鈴聲

from machine import Pin,PWM
import time

def policeSiren(pwm):
   for i in range(150,1800):
       pwm.freq(i)          
       pwm.duty(512)
       time.sleep_ms(10)
       pwm.deinit()
       time.sleep_ms(1)
   for i in range(1800,150):
       pwm.freq(i)          
       pwm.duty(512)
       time.sleep_ms(10)
       pwm.deinit()
       time.sleep_ms(1)

pwm=PWM(Pin(4))

while True:
    policeSiren(pwm)

有點怪怪的, 原因是 ESP8266 GPIO PWM 的最高頻率只到 1000 Hz 而已, 而這程式卻要用到 1800 Hz.

測試 5 : 救護車的歐伊歐伊

from machine import Pin,PWM
import time

def ambulenceSiren(pwm):
    pwm.freq(400)          
    pwm.duty(512)
    time.sleep_ms(500)
    pwm.freq(800)
    time.sleep_ms(500)
    pwm.deinit()

pwm=PWM(Pin(4))

while True:
    ambulenceSiren(pwm)

接下來要演奏馬力歐一個小節, 各音符對應的頻率參考 :

Arduino 的聲音測試 (一)

測試 6 : 馬力歐 (1)

from machine import Pin,PWM
import time

def mario(pwm):
    pwm.freq(659) #E5        
    pwm.duty(512)
    time.sleep_ms(150)
    pwm.deinit()
    time.sleep_ms(150)
    pwm.freq(659) #E5
    pwm.duty(512)
    time.sleep_ms(150)
    pwm.deinit()
    time.sleep_ms(150)
    pwm.freq(659) #E5
    pwm.duty(512)
    time.sleep_ms(150)
    pwm.deinit()
    time.sleep_ms(150)
    time.sleep_ms(150)
    pwm.freq(523) #C5
    pwm.duty(512)
    time.sleep_ms(150)
    pwm.deinit()
    time.sleep_ms(150)
    pwm.freq(659) #E5
    pwm.duty(512)
    time.sleep_ms(150)
    pwm.deinit()
    time.sleep_ms(150)
    time.sleep_ms(150)
    pwm.freq(784) #G5
    pwm.duty(512)
    time.sleep_ms(150)
    pwm.deinit()
    time.sleep(3)

pwm=PWM(Pin(4))

while True:
    mario(pwm)

注意, 只要呼叫過 deinit() 方法, 頻率與工作週期都會被清掉, 下一個音要再呼叫 freq() 與 duty() 重新設定. 直接使用頻率似乎非常亂, 應該使用音名來代表頻率數字, 但由於 Python 沒有像 C 語言那樣可用 define 定義常數, 只好用變數, 上面的程式可以改寫如下 :

測試 7 : 馬力歐 (2)

from machine import Pin,PWM
import time

E5=659
C5=523
G5=784

def mario(pwm):
    pwm.freq(E5)          
    pwm.duty(512)
    time.sleep_ms(150)
    pwm.deinit()
    time.sleep_ms(150)
    pwm.freq(E5)
    pwm.duty(512)
    time.sleep_ms(150)
    pwm.deinit()
    time.sleep_ms(150)
    pwm.freq(E5)
    pwm.duty(512)
    time.sleep_ms(150)
    pwm.deinit()
    time.sleep_ms(150)
    time.sleep_ms(150)
    pwm.freq(C5)
    pwm.duty(512)
    time.sleep_ms(150)
    pwm.deinit()
    time.sleep_ms(150)
    pwm.freq(E5)
    pwm.duty(512)
    time.sleep_ms(150)
    pwm.deinit()
    time.sleep_ms(150)
    time.sleep_ms(150)
    pwm.freq(G5)
    pwm.duty(512)
    time.sleep_ms(150)
    pwm.deinit()
    time.sleep(3)

pwm=PWM(Pin(4))

while True:
    mario(pwm)

上面的 mario() 有點長, 因為重複的部分很多, 可以模仿 Arduino 的 tone() 函數來精簡如下 :

測試 8 : 馬力歐 (3)

from machine import Pin,PWM
import time

E5=659
C5=523
G5=784

def tone(pwm,note,duration):
    pwm.freq(note)            
    pwm.duty(512)
    time.sleep_ms(duration)
    pwm.deinit()

def mario(pwm):
    tone(pwm,E5,150)
    time.sleep_ms(150)
    tone(pwm,E5,150)
    time.sleep_ms(150)
    tone(pwm,E5,150)
    time.sleep_ms(150)
    time.sleep_ms(150)
    tone(pwm,C5,150)
    time.sleep_ms(150)
    tone(pwm,E5,150)
    time.sleep_ms(150)
    time.sleep_ms(150)
    tone(pwm,G5,150)
    time.sleep_ms(150)
    time.sleep(3)

pwm=PWM(Pin(4))

while True:
    mario(pwm)

上面我定義了一個 tone() 函數來模擬 Arduino 的 tone(), 這樣就能跟 Arduino 的程式一一對應了. 由於 ESP8266 的 GPIO 最高只能調變到 1000 Hz, C6 (1046 Hz) 以上的音名無法發出來, 所以要用 ESP8266 的 PWM 演奏音樂受到限制.

下列範例改編自 Arduino 的測試 9 :

測試 9 : Melody

from machine import Pin,PWM
import time

C0=18
CS0=17
D0=18
DS0=19
E0=21
F0=22
FS0=23
G0=25
GS0=26
A0=28
AS0=29
B0=31
C1=33
CS1=35
D1=37
DS1=39
B0=31
C1=33
CS1=35
D1=37
DS1=39
E1=41
F1=44
FS1=46
G1=49
GS1=52
A1=55
AS1=58
B1=62
C2=65
CS2=69
D2=73
DS2=78
E2=82
F2=87
FS2=93
G2=98
GS2=104
A2=110
AS2=117
B2=123
C3=131
CS3=139
D3=147
DS3=156
E3=165
F3=175
FS3=185
G3=196
GS3=208
A3=220
AS3=233
B3=247
C4=262
CS4=277
D4=294
DS4=311
E4=330
F4=349
FS4=370
G4=392
GS4=415
A4=440
AS4=466
B4=494
C5=523
CS5=554
D5=587
DS5=622
E5=659
F5=698
FS5=740
G5=784
GS5=831
A5=880
AS5=932
B5=988

note=(C4, G3, G3, A3, G3, 0, B3, C4)
duration=(4, 8, 8, 4, 4, 4, 4, 4)

def tone(pwm,note,duration):
    pwm.freq(note)          
    pwm.duty(512)
    time.sleep_ms(duration)
    pwm.deinit()

def melody(pwm):
    for i in range(0,8):
        d=int(1000/duration[i])
        tone(pwm, note[i], d)
        p=int(d*1.3)
        time.sleep_ms(p)
    time.sleep(2)

pwm=PWM(Pin(4))

while True:
    melody(pwm)

但是我測試結果發現倒數第二個音 B3 卻沒發出來, 反覆檢查程式沒錯啊! WHY?

測試 9 : 小蜜蜂

from machine import Pin,PWM
import time

C0=18
CS0=17
D0=18
DS0=19
E0=21
F0=22
FS0=23
G0=25
GS0=26
A0=28
AS0=29
B0=31
C1=33
CS1=35
D1=37
DS1=39
B0=31
C1=33
CS1=35
D1=37
DS1=39
E1=41
F1=44
FS1=46
G1=49
GS1=52
A1=55
AS1=58
B1=62
C2=65
CS2=69
D2=73
DS2=78
E2=82
F2=87
FS2=93
G2=98
GS2=104
A2=110
AS2=117
B2=123
C3=131
CS3=139
D3=147
DS3=156
E3=165
F3=175
FS3=185
G3=196
GS3=208
A3=220
AS3=233
B3=247
C4=262
CS4=277
D4=294
DS4=311
E4=330
F4=349
FS4=370
G4=392
GS4=415
A4=440
AS4=466
B4=494
C5=523
CS5=554
D5=587
DS5=622
E5=659
F5=698
FS5=740
G5=784
GS5=831
A5=880
AS5=932
B5=988

note=(G5, E5, E5, 0, F5, D5, D5, 0, C5, D5, E5, F5, G5, G5, G5, 0,
      G5, E5, E5, 0, F5, D5, D5, 0, C5, E5, G5, G5, E5, 0, 0, 0,
      D5, D5, D5, D5, D5, E5, F5, 0, E5, E5, E5, E5, E5, F5, G5, 0,
      G5, E5, E5, 0, F5, D5, D5, 0, C5, E5, G5, G5, C5, 0, 0, 0)
duration=(4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
          4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
          4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
          4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4)

def tone(pwm,note,duration):
    pwm.freq(note)          
    pwm.duty(512)
    time.sleep_ms(duration)
    pwm.deinit()

def littleBee(pwm):
    for i in range(0,len(note)):
        d=int(1000/duration[i])
        tone(pwm, note[i], d)
        p=int(d*1.3)
        time.sleep_ms(p)
    time.sleep(2)

pwm=PWM(Pin(4))

while True:
    littleBee(pwm)

很奇怪, 還是會掉好幾個音, 不知哪裡有問題, 有空再研究.

參考 :

# ESP8266 first project: home automation with relays, switches, PWM, and an ADC (Good)

神奇的 LED 魔鏡

今天在找 NeoPixel WS2812 時意外看到 Youtube 上這個介紹如何製作 LED 神奇魔鏡的影片, 利用兩層鏡子的反射塑造立體的視覺效果, 有點類似 85 大樓電梯兩側都安裝鏡子, 看過去有無窮多個層疊影像 :

# Make An L.E.D Illusion Mirror!


WS2812 蠻好玩的, 可以利用 Arduino 或 ESP8266 來控制, 參考 :


底下這個正妹介紹如何把 LED 應用在 Cosplay 上 :


八燈的 WS2812 大約 60~100 元左右, 參考 :

# [含稅]8位 WS2812 5050 RGB LED 智慧全彩RGB燈環開發板-大環
# [Bob][Arduino]WS2812B WS2812 各式規格 RGB 5050 LED 非 WS2811

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

2017年7月28日 星期五

鄉村基礎木作第三課 : 铣溝槽, 修花邊與組抽屜

由於菁菁, 姊姊, 與水某週四出發去花蓮玩, 二哥晚上又要去補習班自修, 晚上只有我一個人在家, 本想將另外兩塊散裝的 ESP-12S 模組與 break out 板子焊一焊, 但是想想本周六木樂園不能去上課, 只剩周四晚上可上, 於是六點多臨時決定去補這禮拜的課.

我以為晚上人會比週六早上少, 哪知又遇到第二堂課時的原班人馬 (似乎是同一家公司的人), 連我有 7~8 個人之多. 今天接續上次課程, 首先教如何用修邊機給小收納櫃的底板與天板铣溝槽, 這溝槽是用來固定背板用的, 底板是左右貫穿, 溝深 0.5 公分, 使用直徑 0.85 的鑽頭, 但因深度較深要分兩次铣, 第一次深度 0.3 公分, 第二次才調為 0.5 公分, 這樣比較好推. 背板離後端 1 公分, 所以先用鐵尺從後端量 1 公分畫線, 然後將修邊機的調整板調為距底板後端 1 公分, 鑽深 0.3 公分. 

而天板因為有左右前方突出 2 公分, 因此雖然也是離後端 1 公分畫線, 但铣溝槽時不可左右貫穿, 否則外面就會看到此溝槽了, 而是铣到左板與右板內緣即可, 但要注意, 因鑽頭是圓形, 而背板是方形, 因此铣到左右板內緣時要超過鑽頭一個半徑, 這樣背板插入時就不會被圓弧擋住了. 老師要我先在一塊廢料上練習, 熟悉後再在我的板子上铣溝. 

铣好溝槽後接著是給天板的上面修邊, 這樣才會有藝術感, 同樣是使用修邊機, 但鑽頭要換成前端有滾輪可抵住天板邊緣的, 鑽頭深度自己決定, 不在規格之內, 花樣自己喜歡就好. 我修完後發覺橫斷面有毛邊, 就再修一遍, 但這次我沒對好位置修歪了, 有長達 10 公分的瑕疵, 老師說毛邊本來就會有的, 只要用砂紙磨掉就好, 唉, 我真是多此一舉. 

最後是組兩個抽屜, 使用已裁切好的油桐木, 質地輕但纖維較鬆易裂. 首先是畫線鑽洞用來鎖螺絲. 前蓋板有高 (外) 低 (內) 兩塊, 在內側板左右量約 3.5 公分各鑽兩個洞鎖螺絲將內外兩板鎖在一起. 因抽屜板材厚度 1.5 公分, 因此要在抽屜左右板的前後緣各量 0.75 公分畫線, 前緣下 2 公分, 上 4 公分 (因前蓋內板較低) , 後緣則上下各 2 公分畫線, 與 0.75 線交叉處鑽洞鎖螺絲. 

鑽好洞後便於接合處塗膠黏好 (注意, 一次黏一邊, 不要全部一次黏合, 等鎖好一邊, 另外一邊已緊緊黏住, 有歪掉就沒辦法調整了), 用垂直尺確定抽屜四腳內角都是垂直後後用固定器夾住, 用起子鎖上螺絲即可 (注意要左右交替鎖, 不要同邊一起鎖). 

組完抽屜剛好時間到, 九點半了, 其餘留待下次做了.  

2017年7月26日 星期三

恆春行 (二)

今天行程是早上到鵝鑾鼻國境之南的台灣最南端碑, 路過十幾年前帶小狐狸來過的夏都, 那時還沒有因為海角七號而聞名, 但一晚也要 4000 元以上, 不過它的設施真的不錯. 到了最南端碑停車場, 由於是非假日, 整個空蕩蕩的, 我找了有樹蔭的格子停好, 便與阿姨沿著步道走下去, 因為要走約 6~7 分鐘, 且有斜坡, 所以爸, 菁, 淑真姊, 以及阿姨的孫子都留在車上吹冷氣. 哈! 似乎來國境之南只是為了我啊!

由於時間的關係, 就不去四重溪或佳洛水了, 直接去萬巒吃豬腳. 回程經過知名的小杜包子特地停下來, 因為阿姨說要帶她孫子給老闆的姊姊看, 這位杜阿姨是一起生活超過 20 年的故人, 她以前在左營阿姨家的診所工作, 看著表弟三兄妹長大, 感情非常深厚.

離開小杜包子後想帶大家去吃知名的阿伯綠豆蒜, 好不容易從大街繞到小巷找到店面了, 哪知大門緊閉-今日公休. 無奈只好放棄這美味 (結果回來後水某說, 那裏又不只一家賣綠豆蒜, 不會換一家喔! 對哦!)

經過枋山與楓港交界處 (即往台東的台九線舊起點) 時左方社區有一個小牌樓, 進去就是蔡英文總統的祖厝, 昨天前往恆春時比較順路, 但我沒有先設導航 (其實導航機也找不到該住址), 阿姨雖然去過, 但沒先提醒一下子就過頭了. 回程去就要左轉穿越台 26. 裡面巷道很窄, 我是開到德隆宮前面小廣場停好車再走進去.

【屏東枋山】蔡英文楓港祖厝

2017年7月25日 星期二

恆春行 (一)

左營阿姨的孫子下來高雄, 阿姨要我規劃帶她們去恆春玩, 以下是今天行程規劃 :

國 1 -> 88 快速 -> 國 3 -> 南州 424km -> 187 乙 -> 台 1 -> 台 26

後壁湖半潛艇
屏東縣恆春鎮大光里大光路79-57號
http://0975033929.okgo.tw/display_2.html#menu
後壁湖 半潛艇搭乘券
營業時間:9:00~17:00(航班整點出發,最後一班為下午4點),全程來回約40分鐘
※每日固定出航班次:11點/14點(2人以上),其餘為整點航班(9~15點),16點需滿6位才會出航

白砂灣
恆春鎮白砂路 (屏 153) 全天開放 電話:08-8861321#232
從後壁湖走大光路右接白沙路

屏東恆春關山高山巖福德宮
屏東縣恆春鎮山海里檳榔路17-1號

龍鑾潭自然中心賞鳥
從白沙灣沿屏 153 往北經紅柴坑接屏 164 與檳榔路至龍鑾潭自然中心賞鳥
屏東縣恆春鎮草潭路250巷86號
停車費小型車:$50
自然中心:免費

# 出火
屏東縣恆春鎮縣道200線

# 恆春鎮知名小吃

東港餃子館 : 屏東縣恆春鎮恆公路688號 08-8899575
營業時間:11:00am~14:00pm 17:00pm~21:00pm(週日公休)
手工水餃一顆 6 塊錢 + 豬肉湯

鄉村冬粉鴨
屏東縣恆春鎮福德路28號 / (08)889-8824
營業時間: 17:00-02:00

恆春阿伯綠豆蒜 : 屏東縣恆春鎮中山路115號
營業時間 : AM10:00~PM20:00 (星期三公休)

參考 :

# 恆春半島旅遊活動地圖

2017-07-26 補充 :

昨天早上阿姨坐高鐵去新竹接他孫子下來, 到高雄已十點四十, 我又先到左營去接淑真姊, 直到快 11 點才從文自路上高速公路, 走 88 轉國 3 到南州轉台 1 線,  先在交流道下來不遠的全家買一打礦泉水, 吃完冰棒繼續南行. 到達恆春市區剛好 1 點, 整整開了快兩小時 (其實還要扣掉在超商休息約 15 分鐘). 先在市區吃過午餐後才前往後壁湖.

後壁湖其實不只一家半潛艇, 我到以前帶小狐狸們去的那家, 結果一問之下才知道我線上買的票是對面紅屋頂的那家, 售票阿姨說我可以退掉線上訂的票, 改買她們的票, 說她們的是新船. 但我覺得用信用卡購票要退訂似乎挺麻煩, 所以就婉拒了. 最後是搭 3 點的半潛艇出海. 感想 : 跟十幾年前比, 一. 海水混濁多了, 二. 魚少很多, 都是小魚.

接著前往白沙灣, 但到了停車場, 菁因為瘡膿傷口還沒完全好不想下去, 阿姨也說因為忘了買小網子給孫子, 下去也沒意思, 所以就前往下一站 : 龍鑾潭, 我原先計畫的關山, 菁菁說要走很久, 阿姨說只是看海景, 所以就不去了. 龍鑾潭自然中心離停車場有一段距離 (700~800 公尺), 約走十五分鐘可達. 等我們到達門口, 才發現離閉館只剩十分鐘, 而太陽還非常強烈! 趕緊跑上樓用望遠鏡觀察, 可能是夏天的緣故, 雁鴨與水鳥並不多.

離開龍鑾潭後就前往下榻的會館卸下行李, 等菁菁洗完澡便前往水某上回帶菁菁來墾丁時沒吃到的東港餃子館, 由於客人頗多, 我們還等了十分鐘才排到桌次. 我照網路上的建議只點水餃 70 顆與豬肉湯 6 碗 (小碗 45), 阿姨大讚餃子好吃, 豬肉湯也非常棒. 因為餃子大顆又厚實, 所以 5 大 1 小點 70 顆實在多了些, 我覺得一個人十顆比較適當.

2017年7月23日 星期日

2017 年第 29 周記事 : 製作小型太陽能發電控制箱

本周仍是姐姐陪我回鄉下, 但她要忙動漫展擺攤作品畫水彩, 所以就不陪我上市集了. 以前菁菁還沒上補習班時, 每周都陪我去買菜, 周周如此就不覺得特別. 直到最近才了解一件事, 人生中感覺每天或經常都會發生的場景, 其實都只有非常有限的保鮮期, 期限過後可能此生不再. 不要以為常常會發生就覺得那沒甚麼. 暑假過後姊姊可能要去台北讀書了, 像這樣每周陪我回鄉下的場景, 以後會越來越稀少. 

爸說頂樓水塔有漏水情況, 早上跑上去察看, 果真桶身中央有一隙縫, 漏出來的水沿著桶身外緣往下走, 但又不是直接掉到地面, 而是繼續流向桶底才滴下去, 平均約 2~3 秒滴一滴, 雖然不多, 但桶子下方因為潮濕都長滿青苔與雜草, 而且匯聚到外牆往樓下滴, 在一樓會聽到如同大樓冷氣滴水那種狀況. 本想早上出去買菜時到五金行去找漏斗來將漏水導至排水口, 但時間不夠只好下周再來處理.

從水塔下來時想說整理一下頂樓靠路邊庫房, 找到一片三合板木材與一片當初給風力發電機固定用的松木, 突然覺得這可以拿來製作我那 20W 實驗用的小型太陽能發電控制箱, 目前充電控制器與蓄電池都還是克難地放在舊豬舍的窗台上, 如果颱風來的話恐怕會被窗縫中滲進來的水弄濕. 於是午飯後不睡午覺, 簡單地畫好裁切線後便啟動小鋸床開始裁切板材, 預計中間放一隔板, 蓄電池放底下, 充控與其他電路板放上層, 

裁好後就用螺絲鎖緊, 松木雖軟但較厚很好鎖, 三合板較薄沒定位好螺絲鑽偏了還會裂開哩. 組好後就先在舊豬舍廁所外牆上用尺與鉛筆定位要上牆的位置, 然後用電鑽鑽出三個洞, 用鐵鎚將塑膠墊敲進去再用螺絲鎖緊. 然後把蓄電池與充控移過來, 將連接太陽能板的電線剪短後從控制箱與牆之間預留的隙縫中穿過來重新接上充控, 再連接蓄電池即大功告成啦! 

正面

背面

近看

遠看

這樣就幫蓄電池與充控建造一個家了, 太陽能板與充控的距離變短了(約 2 公尺), 充電效率會較好. 雖然有帶一些零件回來, 但時間不夠, LED 照明控制與物聯網部分就等下周再安裝了.

2017年7月22日 星期六

製作 ESP-12 模組開發板

今天真是焊瘋了, 焊完直流電源供應器後, 既然烙鐵都熱了, 乾脆給上回焊好的 ESP-12F 模組製作一個實驗板, 主要是利用兩個 2*8 的排母給 ESP-12F 引出板當基座, 加上一個 AMS1117-3.3V 模組供電, 以及一個 Reset 按鈕就可以了. 相關之前篇文章參考 :

# ESP-12F 模組與轉接板焊接

這回我沒有先進行紙上規劃作業, 繪製洞洞板佈線圖, 把零件擺上去後心裡估量如何佈線, 然後就開始焊接. 有上回製作 Arduino+ESP8266 模組經驗後, 這回火線作業就很得心應手. 下面是正面元件佈置與佈線圖 :


左上方的兩個排針用來引出 GND 腳, 以便其他模組可用 (例如 USB-TTL 上傳線共接地之用); 右下方的兩個排針則是引出 3.3V 電源. 正下方的三個排針引出 AMS1117 的三隻腳, 由左至右分別是 GND, 3.3V, VIN; 外部電源 (0~9V) 是接在 VIN 與 GND 上. 下面是背面佈線圖 :


完成後先不要插上 ESP-12F 模組, 先測量重製按鈕按下後, Reset 腳位是否接地, 該絕緣的有無絕緣, 該連接的有無連接, 然後插上 AMS1117-3.3V 模組, 以三用電表量 VCC 是否為 3.3V, 都沒問題了才關電插上 ESP-12F 模組, 將 PL2303 USB-TTL 上傳線的 T/R 線與 ESP-12F 的 R/T 反向對接, 接地線也共接 (這很重要), 然後送電 : BRAVO! 成功進入 MicroPython REPL 介面 :


老實說, 自己焊 ESP-12 的實驗板實在吃力不討好, 沒有 USB 介面, 得依賴 PL2303 之類的 USB-TTL 上傳線, 90 元的 ESP-12 模組加上 AMS1117 模組 (約 10 元), 洞洞板 (約 10 元), 加上排母 (10 元), 已超過 D1 Mini 的 100 元價格, 若加上 PL2303 上傳線 (約 30 元), 那就更不划算了. 所以開發 ESP8266 應用還是以 D1 Mini 最方便最經濟. 以下是最近兩天焊好的 ESP8266 開發板排排站 :


左邊是 WeMOS NodeMCU v2, 中間是 WeMOS D1 Mini, 而右邊則是今天製作的 ESP-12 實驗板, 可見 D1 Mini 是最小巧的, 插入 35*47 mm (170 孔) 的最小麵包板剛剛好, 要把 GPIO 腳非常方便, 如果只是要做一個測量溫濕度與亮度的小型測候站, 全部都可在這個小麵包板上完成.

OK, 該焊的都焊完了, 今天可說是 "焊錫日" .

2017-08-03 補充 :

今天下班後去禾樺替換買錯的 2*8 排母, 晚上把第三塊 ESP-12S 開發板焊起來, 這次我直接使用 AMS1117-3.3V 穩壓 IC, 前後端各加 105 電容一顆, 另外按鈕開關改用較小兩接點的, 正面與背面布局如下 :



這樣三塊 ESP-12 就都可用了, 以後不會這麼費事買 ESP-12 模組來焊, 這麼做真是多此一舉, 直接使用 D1 Mini 省時又省錢.

值得一提的是, ESP-12 模組的 PCB 天線底下有一個藍色 LED, 開機時會閃一下, 其實它是接到 GPIO 2 腳, 經 470 歐內建上拉電阻後接 VCC, 另一端接地. 所以可以利用這個板上 LED 來顯示程式執行狀態, 例如快閃表示 WiFi 尚未連線, 慢閃表示已連線等等. 閃燈可使用 PWM :

from machine import Pin,PWM
pwm2=PWM(Pin(2))
pwm2.freq(1)
pwm2.duty(512)

參考 :

# ESP-12E ONBOARD LED

組裝可調式直流電源供應器 (一)

昨晚焊接好 D1 Mini 與 18650 鋰電池接頭後意猶未竟, 早上起來在零組件箱翻到年初在禾樺買的直流電源供應器模組 (特價 200 元), 塞在箱子裡已經很久了, 擇日不如撞日, 早上就來焊一焊好了, 反正今天不能去木樂園做木工 (老師有暑期活動要開特別班, 其實還有一個原因 : 我那兩張設計圖放在鄉下沒帶來, 沒帶圖去上課覺得有點空虛).

拆開包裝後發現裡面有一張說明書, 一塊 PCB, 以及所需零件 :


仔細閱讀說明與電路圖, 才發現零件不含變壓器! 說明書建議購買 28V-0-28V 變壓器! 難怪才賣 200 元! 雖然有點失望, 但既然拆封了就把它焊起來, 改天再去長明街找看看有無中古 28V 變壓器. 把零件一個個插入 PCB 後, 就開始焊接 :


插零件最需注意的是電解電容, 電晶體, 以及二極體 (含 LED), 因為它們都有極性, 電解電容的塑膠外皮上都有一排大大的 "一", 旁邊那隻腳就是負極, 參考 :

# 如何分辨-固態電容與電解電容的正負極

電晶體則有 E, B, C 三極, 規格書上有圖示標明腳位, 也可以插入三用電表, 打到 HREF 檔, 若腳位插入正確就會有數值出現; 而二極體 N 極 (負極) 通常標白圈, LED 長腳為正極.


注意, 功率電晶體 2N3055 外殼是集極 (C), 基極 (B) 與射極 (E) 在底下, 腳位判別依據軸心位置, 參考 :




接下來要找 28V 變壓器, 合適的外殼, 以及一條電源插頭線. 另外需要一個電壓顯示器, 零組件箱還有兩三個可用. 但 28V 變壓器挑難找, 露天只看到一個 :

# 方型變壓器(3.9V-0V)(28V-0V)(11V-11V-0V)DB519 $80

等找齊了再來組裝測試.

2017年7月21日 星期五

向美國 aliexpress 購買 WeMOS D1 mini

今天在搜尋 WeMOS D1 Mini 的資料時看到 Aliexpress 這個美國購物網站竟然有賣電子零組件 (其實是阿里巴巴淘寶網的美國版), 進入這個網站馬上會跳出一個 100 美元折價券廣告, 我就先加入會員後再點折價券廣告, 原來這 100 美元是分次在 7/31 前依消費金額可用來部分折抵 :

# AliExpress USD100 Coupon



於是我就搜尋 D1 Mini 板子, 發現它有許多報價各有不同之供貨商, 我找到其中單價最低的 USD$2.57 元 (約台幣 77 元) 買了五塊板子, 它不論買幾塊運費都是 USD$1.89 元, 合計 2.57*5 +1.89=14.74 元 :

# 1PCS D1 mini V1 - Mini NodeMcu 4M bytes Lua WIFI Internet of Things development board based ESP8266 by WeMos USD$2.57



輸入收件地址與信用卡資訊後就會出現確認畫面, 上方有使用折價券選項, 這次消費可折抵 USD$2 元, 所以總共應付 14.74-2=12.74 美元 :


確認後顯示如下訊息 :

Your payment will be verified within 24 hours. Once it is verified, the seller will prepare your order for shipment. Otherwise, you will receive a notice in My AliExpress or by email.

交貨期 20~40 天, 大約 1 個月後才會收到, 難怪運費較便宜. 12.74 美元折合台幣約 382 元, 每塊板子才 77 元, 比露天還便宜 30 元, 而 2 美元的折價券相當於免運費. 1.89 美元的運費折合台幣 56 元, 也比超商取貨付款還便宜.

2017-07-22 :

信用卡付款經 24 小時確認後, 今天收到 aliexpress 出貨通知 :

Dear Tony,

This is an e-mail notification to inform you that your order .85353221731953 has been shipped by the seller on 2017.07.21 18:35. You are advised to contact the seller for shipment information.
The Purchase Protection of order .85353221731953  will end on 2017.09.19 18:35.
1. If you are satisfied with the items you have received, then you can click  'Confirm Order Received'.
2. If you have not received your items, or you are not satisfied with your items, you can click ’Open Dispute.’ before Purchase Protection deadline or Purchase will be automatically finalized.
3. If you would like to extend your Purchase Protection you can contact the seller directly or click  'Request to extend Purchase Protection' to ask for the seller's approval.
Please note if there is no action taken within Purchase Protection, your order will be automatically finalized with payment released.
Sincerely,
AliExpress.com 
2017.07.21 18:35

2017-08-02 補充 :

買 D1 Mini 這家 WAVGAT 也不錯, 運費 10 件以內都是 US$1.79 元 :

# D1 mini - Mini NodeMcu 4M bytes Lua WIFI Internet of Things development board based ESP8266 by WAVGAT

傍晚下班回家收到賣家從新加坡寄來的包裹了, 內有五小袋零組件無誤, 從 7/21 下訂起算共 13 天到貨, 比網頁上標示的 30~40 天還快. 隨便拆封一個來焊接, 這回我改用短腳排母, 因為 D1 Mini 上面可以玩疊疊樂, 如果是焊排針的話, D1 Mini 就要疊在其他模組上面; 反之使用排母就在下面.

2017-08-03 補充 :

今天收到 AliExpress 寄來貨品寄達確認信 :

Please Confirm Delivery 
for Order No. 85353221731953  
Dear Tony,
Your Order No. 85353221731953 has been delivered by AliExpress Standard Shipping and our system shows you have received the product. 
If you have received your full order in satisfactory condition, please Confirm Order Received.
If you have not received your items or are not satisfied with the items you received, please contact the seller to resolve it or ask seller to extend your product receipt time.Otherwise you can Open Dispute before 2017.08.12 09:22.If you do not open a dispute within this time AliExpress will consider the order to be satisfactory and will finalize the order process.

Sincerely,
AliExpress.com
2017.08.02 09:22

This is an automated system email. Please do not reply to this email.
意思是若已收到貨品就按 "Confirm Order Received" 登入網站確認; 若未收到或對貨品不滿意, 請跟賣家聯繫, 或者在期限前按 "Open Dispute" 登入網站開立爭議處理單, AliExpress 會介入處理. 確認後 15 天內若發現貨品有瑕疵, 仍可提出爭議處理要求 :


網站中有完整的交易流程紀錄, 從 GMT-7 研判, AliExpress 主機設在美西, 很可能是在加州的洛杉磯, 舊金山, 或矽谷, 但是在新加坡出貨 (出貨商 YuYang Electronic Components) :

2017.08.02 03:25 (GMT-7): Shipment delivered-Signature by consignee
2017.08.02 03:25 (GMT-7): Product Delivered (Country code: TW)
2017.08.02 18:25 (GMT-7): 【8】Product Delivered (Country code: TW)
2017.08.02 18:25 (GMT-7): 【+8】Shipment delivered-Signature by consignee
2017.08.01 08:59 (GMT-7): 【+8】Shipmnet arrived at destination country
2017.08.01 08:59 (GMT-7): 【8】Arrival at Processing Center (Country code: TW)
2017.08.01 08:59 (GMT-7): Arrive at destination country
2017.07.31 17:59 (GMT-7): Shipmnet arrived at destination country
2017.07.31 17:59 (GMT-7): Arrival at Processing Center (Country code: TW)
2017.07.28 15:42 (GMT-7): 【8】Despatched to overseas (Country code: TW)
2017.07.28 15:42 (GMT-7): 【+8,TWTPE】parcel departed from exchange office
2017.07.28 00:42 (GMT-7): Despatched to overseas (Country code: TW)
2017.07.28 00:42 (GMT-7): parcel departed from exchange office
2017.07.24 21:45 (GMT-7): 【8】Hand over to airline
2017.07.24 06:45 (GMT-7): Hand over to airline
2017.07.23 11:49 (GMT-7): 【8】Received by line-haul
2017.07.23 10:49 (GMT-7): 【8】Outbound in sorting center
2017.07.23 10:23 (GMT-7): 【+8】Shipment information received
2017.07.23 09:49 (GMT-7): 【8】Inbound in sorting center
2017.07.23 09:19 (GMT-7): 【8】Accepted by carrier
2017.07.23 07:34 (GMT-7): 【8】Waiting for pick up
2017.07.22 20:49 (GMT-7): Received by line-haul
2017.07.22 19:49 (GMT-7): Outbound in sorting center
2017.07.22 19:23 (GMT-7): Shipment information received
2017.07.22 18:49 (GMT-7): Inbound in sorting center
2017.07.22 18:19 (GMT-7): Accepted by carrier
2017.07.22 16:34 (GMT-7): Waiting for pick up
2017.07.22 09:34 (GMT-7): Shipment confirmation


整個購物控管追蹤機制相當完整, 有考慮再下訂採購另外一批 D1 Mini 相關零組件.

WeMOS D1 Mini 開發板測試

昨晚把前陣子在露天買的 WeMOS NodeMCU v2 與 D1 Mini 開發板找出來, 這兩個都是散裝, 必須自行焊接針腳, 因為已過 11 點, 焊下去會搞到 12 點後, 就先收起來. 今天吃過晚飯後開始動工, 順便將自走車要用的 18650 降壓板接線都一併焊好.

之前有問過賣家, 這兩塊板子都是 4MB Flash, WeMOS NodeMCU v2 這塊出廠搭載 NodeMCU, 而 D1 Mini 則無, 所以焊好後就來給 D1 Mini 灌 MicroPython 測試看看. WeMOS D1 Mini 的微控器是 ESP-12EX 模組, 運作於 3.3V 電壓位準, 拉出 9 個 GPIO 輸出入腳, 2 個 UART 腳, 以及一個 ADC(A0) 類比輸入腳, 板子左下方還有一個 Reset 按鈕, 規格詳見官網 :

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

ADC(A0) 的類比輸入準位為 0~3.3V, 經 ESP8266 內部 ADC 轉換為 0~1023 的整數, 高於 3.3V 的類比信號須經電阻分壓電路降壓至最大信號時為 3.3V. 另外, 在 MicroPython 中控制 GPIO 腳須以 ESP8266 腳位編號為對象, 不可用 D1 Mini 板子上的 D0~D8, 其對照如下 :

 板子腳位 ESP8266 腳位 功能
 D0 GPIO16 IO
 D1 GPIO5 IO, SCL 
 D2 GPIO4 IO, SDA 
 D3 GPIO0 IO, 內建 10K 上拉電阻
 D4 GPIO2 IO, 內建 10K 上拉電阻與 LED
 D5 GPIO14 IO, SCK
 D6 GPIO12 IO, MISO
 D7 GPIO13 IO, MOSI
 D8 GPIO15 IO, SS, 內建 10K 上拉電阻
 TX TXD UART 送端
 RX RXD UART 收端
 A0 ADC 類比輸入 (0~3.3V)

注意, 在燒錄 MicroPython 韌體前必須將 D3 (GPIO0) 接地再插入 Micro USB 開機, 這樣才會進入 Flash 燒錄模式. 然後開啟 ESP8266Flasher 軟體, 選定所接的 COM 埠, 指定要燒錄之韌體位置與 4MB 的 Flash 大小後, 按 Flash 鈕即會看到 ESP8266 的 MAC 位址顯示並開始燒錄, 過程中 D1 Mini 板上的藍色 LED 燈會閃爍, 表示韌體正寫入 Flash 中, 完成後左下角的 NODEMCU TEAM 前面會打勾 :




注意, ESP-12 系列的 Flash 記憶體都是 4MB.

韌體燒錄說明參見 :

# MicroPython on ESP8266 (一) : 燒錄韌體
# MicroPython v1.9.1 版韌體測試

燒錄完成後拔除 D3 (GPIO0) 的接地線重開機 (按 Reset 鈕或插拔 USB), 開啟 PuTTY 用 Serial 速率 115200 連線 D1 Mini, 按 Ctrl+B 即進入 REPL 介面, 用 os.listdir() 可知檔案目錄下面只有一個 boot.py 檔案 :

MicroPython v1.9.1-8-g7213e78d on 2017-06-12; ESP module with ESP8266
Type "help()" for more information.
>>> import os
>>> os.listdir()
['boot.py']
>>> f=open('boot.py','r')
>>> lines=f.readlines()
>>> for line in lines:
...     print(line)
...
...
...
# This file is executed on every boot (including wake-boot from deepsleep)

#import esp

#esp.osdebug(None)

import gc

#import webrepl

#webrepl.start()

gc.collect()

>>>

然後開啟 Web REPL 功能 :

>>> import webrepl_setup
WebREPL daemon auto-start status: disabled

Would you like to (E)nable or (D)isable it running on boot?
(Empty line to quit)
> E    (開啟 webrepl 功能)
To enable WebREPL, you must set password for it
New password: 123456
Confirm password: 123456
Changes will be activated after reboot
Would you like to reboot now? (y/n) Y
Would you like to reboot now? (y/n) y  (要用小寫 y)

寫入完成後會要求重啟 :

l▒▒|▒▒#4 ets_task(40100164, 3, 3fff829c, 4)
WebREPL daemon started on ws://192.168.4.1:8266
Started webrepl in normal mode
OSError: [Errno 2] ENOENT

MicroPython v1.9.1-8-g7213e78d on 2017-06-12; ESP module with ESP8266
Type "help()" for more information.

更多檔案操作參考 :

# MicroPython on ESP8266 (六) : 檔案系統測試

接下來參考之前伺服器測試這篇 :

# MicroPython on ESP8266 (十四) : 網頁伺服器測試

用記事本編輯如下 main.py 檔案 :

#main.py
import time
WAIT_FOR_CONNECT=8

def set_ap():
    html="""
    <!DOCTYPE html>
    <html>
      <head><title>AP Setup</title></head>
      <body>
        %s
      </body>
    </html>
    """
    form="""
        <form method=get action='/update_ap'>
          <table border="0">
            <tr>
              <td>SSID</td>
              <td><input name=ssid type=text></td>
            </tr>
            <tr>
              <td>PWD </td>
              <td><input name=pwd type=text></td>
            </tr>
            <tr>
              <td></td>
              <td align=right><input type=submit value=Connect></td>
            </tr>
          </table>
        </form>
    """
    import socket
    addr=socket.getaddrinfo('192.168.4.1', 80)[0][-1]
    s=socket.socket()
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(addr)
    s.listen(5)
    print('listening on', addr)
    while True:
        cs, addr=s.accept()
        print('client connected from', addr)
        data=cs.recv(1024)          
        request=str(data,'utf8')
        print(request, end='\n')
        if request.find('update_ap?') == 5:
            para=request[request.find('ssid='):request.find(' HTTP/')]
            ssid=para.split('&')[0].split('=')[1]
            pwd=para.split('&')[1].split('=')[1]
            sta.connect(ssid,pwd)
            while not sta.isconnected():
                pass
            print('Connected:IP=',sta.ifconfig()[0])
            cs.send(html % 'Connected:IP=' + sta.ifconfig()[0])
        else:
            cs.send(html % form)
        cs.close()
    s.close()

import network
sta=network.WLAN(network.STA_IF)
sta.active(True)
print('Connecting to AP ...')
time.sleep(WAIT_FOR_CONNECT)
if not sta.isconnected():
    set_ap()
else:
    print('Connected:IP=', sta.ifconfig()[0])
    #Application code is written here or import from a separate file
    #import myapp
    #myapp.main()

存成 main.py 後用 ampy 將其上傳到 D1 Mini 裡 :

D:\test>ampy --port COM4 put main.py

然後按 Ctrl+D 軟開機 :

PYB: soft reboot
#9 ets_task(40100164, 3, 3fff829c, 4)
WebREPL is not configured, run 'import webrepl_setup'
Connecting to AP ...   (連線到最近一次設定之無線基地台, 等候 8 秒)
Connected:IP= 192.168.2.111    (連線成功)
MicroPython v1.9.1-8-g7213e78d on 2017-06-12; ESP module with ESP8266
Type "help()" for more information.
>>>

如果顯示 "listening on 192.168.4.1", 表示上次設定的 AP 目前收不到訊號無法連線, 這時打開手機 WiFi 功能, 找尋 D1 Mini 的 ESP8266 內建的 AP, 其 SSID 是以 "MicroPython-" 開頭 (後面是 MAC 後 6 碼), 以內建密碼 "micropythoN" 連線此 AP :

SSID=MicroPython-c111d1
PWD=micropythoN

再打開手機瀏覽器連線 192.168.4.1, 會看到一個 AP 設定網頁, 輸入欲連線之 AP 的 SSID 與 PWD 後按 Connect 即可, 這時 D1 Mini 重開機即可連線至該無線基地台了.

接著修改上面 main.py 的最後兩行, 拿掉前面的 #, 變成 :

#myapp.py
import myapp
myapp.main()

重新上傳 main.py, 然後參考下面這篇的測試 4 : "建立網頁伺服器顯示 GPIO 輸入腳的狀態" :

MicroPython on ESP8266 (十四) : 網頁伺服器測試

但要修改第二行的 pins 串列內的元組, 因為那篇是針對 ESP-01 模組, 它只有 GPIO0 與 GPIO2 兩支輸出入腳, 而 D1 Mini 卻有 9 支 GPIO 腳, 然後存成 myapp.py :

#myapp.py
import machine
pins=[machine.Pin(i, machine.Pin.IN) for i in (0, 2, 4, 5, 12, 13, 14, 15, 16)]  

html="""<!DOCTYPE html>
<html>
    <head> <title>ESP8266 Pins</title> </head>
    <body> <h1>ESP8266 Pins</h1>
        <table border="1"> <tr><th>Pin</th><th>Value</th></tr> %s </table>
    </body>
</html>
"""

import socket
addr=socket.getaddrinfo('192.168.4.1', 80)[0][-1]
s=socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen(5)
print('listening on', addr)

while True:
    cs, addr=s.accept()
    print('client connected from', addr)
    data=cs.recv(1024)
    print(str(data,'utf8'), end='\n')
    rows=['<tr><td>%s</td><td>%d</td></tr>' % (str(p), p.value()) for p in pins]
    response=html % '\n'.join(rows)
    cs.send(response)
    cs.close()

s.close()

將此 myapp 上傳至 D1 Mini :

D:\test>ampy --port COM4 put myapp.py

按 Ctrl+D 軟開機, 然後手機連線 ESP8266 本身內建的 AP, 再用連覽器連線 192.168.4.1 便可看到 D1 Mini 各 GPIO 腳的狀態了 :


可見除 GPIO15 與 GPIO16 外, 其餘拉出來的 IO 腳都被 Pull-up 到 HIGH 位準了.

我覺得 D1 Mini 比 ESP-01 模組似乎更穩, 而且大小也沒有大多少, 價格也只貴了 20~30 元左右, 但 D1 Mini 有內建 CH340G USB 晶片與 Micro USB 插槽, 光是加這兩個元件就超過 30 元了, 而且還比 ESP-01 多出 7 個 GPIO 埠與 1 個 ADC 埠, 最重要的是, Flash 記憶體大小 D1 Mini 是 ESP-01 的四倍 (=4MB), 便利性與功能性都比 ESP-01 模組強. 如果是簡單的物聯網應用, GPIO 需求不超過 2 個的話, 用 ESP-01 即可; 否則用 D1 Mini 也沒多多少成本.

 項目 ESP-01 模組 WeMOS D1 Mini
 價格 約 80 元 約 110 元
 GPIO 數 2 9
 Flash 大小 1M Bytes 4M Bytes
 ADC 腳 無 有 1 個
 USB 介面 無 (須外接轉換線) Micro USB
 Reset 鈕 無 有

除了 D1 Mini 外, WeMOS 還有 D1 Mini Pro, Lite, 以及 Lolin 32 等開發板, 參考 :

https://wiki.wemos.cc/products:d1:d1_mini_lite
https://wiki.wemos.cc/products:d1:d1_mini
https://wiki.wemos.cc/products:d1:d1_mini_pro
https://wiki.wemos.cc/products:lolin32:lolin32

Lite, Pro 板子大小與 Mini 大小一樣, 但 Lite 的 Flash 記憶體只有 1 MB, 而 Pro 則是 16MB; Lite 沒使用 ESP 模組, 而是將 ESP8266 與天線直接佈在單一板子上. Pro 則是改用陶瓷天線並附有外接天線座, 同時 USB-TTL 晶片改為 CP2102. Lolin 特點是內建藍芽並提供鋰電池接頭 (使用 PH-2 2.0mm), Flash 則維持 4MB.

 WeMOS D1 Mini Lite D1 Mini D1 Mini Pro Lolin
 微控器 ESP-8285 ESP-8266EX ESP-8266EX ESP-WROOM-32
 最高時脈 80/160MHz 80/160MHz 80/160MHz240MHz
 記憶體大小 1MB 4MB 16MB 4MB
 天線 PCB PCB 陶瓷 + 外接座 PCB
 藍芽 No No No Yes
 USB-TTL CH340G CH340G CH340G CP2102
 鋰電池接頭 No No No Yes

2017-07-30 補充 :

D1 Mini 天線底下有一顆藍色 LED, 開機送電時會閃一下, 根據上面的 GPIO 針腳對應表, 其中 D4 針腳 (GPIO 2) 有接 LED, 指的就是這顆 LED, 在開發物聯網應用時可以利用這個 LED 來顯示系統狀態, 例如 WiFi 尚未連線時快閃, 連線成功後常亮或慢閃; 系統異常時快閃等等, 這是使用 D1 Mini 的一個方便之處 : 不需外接 LED.

2017-08-02 補充 :

D1 Mini 也可以使用 Arduino 開發, 參考  :

# Wemos D1 Mini, ESP8266 Getting Started Guide With Arduino


另外跟同一賣家買的 Wemos NodeMCU V2 板子只是先焊好, 開機接上 COM 埠有顯示 Ready 表示板子 OK 即可, 賣家說預先搭載了 NodeMCU, 因此暫時不燒錄 MicroPython 進去, 等 MicroPython 弄完有空或許會研究看看, 參考 :

# Comparison of ESP8266 NodeMCU development boards
# TOP 6 ESP8266 MODULES FOR IOT PROJECTS
# Mini NodeMCU (Wemos), Prototype Board, NodeMCU (DOIT&LoLin), Arduino like Wemos, Witty


2017-08-14 補充 : 

如果 D1 Mini 有接 1602 LCD 或 SSD1306 OLED 顯示器, 在開機過程中可以將連線 AP 的情況顯示於 LCD 或 OLED 上, 參考 :

MicroPython on ESP8266 (十七) : 液晶顯示器 1602A 測試
MicroPython on ESP8266 (十八) : SSD1306 液晶顯示器測試

下面列出無顯示器版, 1602 LCD 版, 以及 SSD1306 OLED 版的 AP 設定程式 main.py :

1.  無顯示器版 AP 設定程式 :

#main.py
import time
WAIT_FOR_CONNECT=8

def set_ap():
    html="""
    <!DOCTYPE html>
    <html>
      <head><title>AP Setup</title></head>
      <body>
        %s
      </body>
    </html>
    """
    form="""
        <form method=get action='/update_ap'>
          <table border="0">
            <tr>
              <td>SSID</td>
              <td><input name=ssid type=text></td>
            </tr>
            <tr>
              <td>PWD </td>
              <td><input name=pwd type=text></td>
            </tr>
            <tr>
              <td></td>
              <td align=right><input type=submit value=Connect></td>
            </tr>
          </table>
        </form>
    """
    import socket
    addr=socket.getaddrinfo('192.168.4.1', 80)[0][-1]
    s=socket.socket()
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(addr)
    s.listen(5)
    print('listening on', addr)
    while True:
        cs, addr=s.accept()
        print('client connected from', addr)
        data=cs.recv(1024)        
        request=str(data,'utf8')
        print(request, end='\n')
        if request.find('update_ap?') == 5:
            para=request[request.find('ssid='):request.find(' HTTP/')]
            ssid=para.split('&')[0].split('=')[1]
            pwd=para.split('&')[1].split('=')[1]
            sta.connect(ssid,pwd)
            while not sta.isconnected():
                pass
            print('Connected:IP=',sta.ifconfig()[0])
            cs.send(html % 'Connected:IP=' + sta.ifconfig()[0])
        else:
            cs.send(html % form)
        cs.close()
    s.close()

import network
sta=network.WLAN(network.STA_IF)
sta.active(True)
print('Connecting to AP ...')
time.sleep(WAIT_FOR_CONNECT)
if not sta.isconnected():
    set_ap()
else:
    print('Connected:IP=', sta.ifconfig()[0])
    #Application code is written here or import from a separate file
    import myapp
    myapp.main()

2.  1602 LCD 版 AP 設定程式 :

#main.py
from machine import Pin,PWM,I2C
import network
import time
from esp8266_i2c_lcd import I2cLcd

WAIT_FOR_CONNECT=8

pwm2=PWM(Pin(2), freq=5, duty=512)

i2c=I2C(scl=Pin(5),sda=Pin(4),freq=400000)
lcd=I2cLcd(i2c, 0x27, 2, 16)

def set_ap():
    html="""
    <!DOCTYPE html>
    <html>
      <head><title>AP Setup</title></head>
      <body>
        %s
      </body>
    </html>
    """
    form="""
        <form method=get action='/update_ap'>
          <table border="0">
            <tr>
              <td>SSID</td>
              <td><input name=ssid type=text></td>
            </tr>
            <tr>
              <td>PWD </td>
              <td><input name=pwd type=text></td>
            </tr>
            <tr>
              <td></td>
              <td align=right><input type=submit value=Connect></td>
            </tr>
          </table>
        </form>
    """
    import socket
    addr=socket.getaddrinfo('192.168.4.1', 80)[0][-1]
    s=socket.socket()
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(addr)
    s.listen(5)
    print('listening on', addr)
    while True:
        cs, addr=s.accept()
        print('client connected from', addr)
        data=cs.recv(1024)    
        request=str(data,'utf8')
        print(request, end='\n')
        if request.find('update_ap?') == 5:
            para=request[request.find('ssid='):request.find(' HTTP/')]
            ssid=para.split('&')[0].split('=')[1]
            pwd=para.split('&')[1].split('=')[1]
            sta.connect(ssid,pwd)
            while not sta.isconnected():
                pass
            print('Connected:IP=',sta.ifconfig()[0])
            cs.send(html % 'Connected:IP=' + sta.ifconfig()[0])
            lcd.move_to(0, 0)
            lcd.putstr('AP Connected')
            lcd.move_to(0, 1)
            lcd.putstr('IP=' + sta.ifconfig()[0])
        else:
            cs.send(html % form)
        cs.close()
    s.close()

def get_ip():
  return (network.WLAN(network.STA_IF).ifconfig()[0],
  network.WLAN(network.AP_IF).ifconfig()[0])

def ap_on():
  network.WLAN(network.AP_IF).active(True)

def ap_off():
  network.WLAN(network.AP_IF).active(False)

#try connecting to lastest configured AP
sta=network.WLAN(network.STA_IF)
sta.active(True)
print('Connecting to AP ...')
lcd.move_to(0, 0)
lcd.putstr('Connecting to AP')
time.sleep(WAIT_FOR_CONNECT)
if not sta.isconnected():
    set_ap()
else:
    pwm2.deinit()
    Pin(2).value(0)
    print('Connected:IP=', sta.ifconfig()[0])
    lcd.move_to(0, 0)
    lcd.putstr('AP Connected')
    lcd.move_to(0, 1)
    lcd.putstr('IP=' + sta.ifconfig()[0])
    #Application code is written here or import from a separate file
    import myapp
    myapp.main()


3.  SSD1306 OLED 版 AP 設定程式 :

#main.py
from machine import Pin,PWM,I2C
import network
import time
import ssd1306

WAIT_FOR_CONNECT=8

pwm2=PWM(Pin(2), freq=5, duty=512)

i2c=I2C(scl=Pin(5), sda=Pin(4))
oled=ssd1306.SSD1306_I2C(128, 32, i2c)

def set_ap():
    html="""
    <!DOCTYPE html>
    <html>
      <head><title>AP Setup</title></head>
      <body>
        %s
      </body>
    </html>
    """
    form="""
        <form method=get action='/update_ap'>
          <table border="0">
            <tr>
              <td>SSID</td>
              <td><input name=ssid type=text></td>
            </tr>
            <tr>
              <td>PWD </td>
              <td><input name=pwd type=text></td>
            </tr>
            <tr>
              <td></td>
              <td align=right><input type=submit value=Connect></td>
            </tr>
          </table>
        </form>
    """
    import socket
    addr=socket.getaddrinfo('192.168.4.1', 80)[0][-1]
    s=socket.socket()
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(addr)
    s.listen(5)
    print('listening on', addr)
    while True:
        cs, addr=s.accept()
        print('client connected from', addr)
        data=cs.recv(1024)    
        request=str(data,'utf8')
        print(request, end='\n')
        if request.find('update_ap?') == 5:
            para=request[request.find('ssid='):request.find(' HTTP/')]
            ssid=para.split('&')[0].split('=')[1]
            pwd=para.split('&')[1].split('=')[1]
            sta.connect(ssid,pwd)
            while not sta.isconnected():
                pass
            print('Connected:IP=',sta.ifconfig()[0])
            cs.send(html % 'Connected:IP=' + sta.ifconfig()[0])
            oled.fill(0)
            oled.text('AP connected',0,0)
            oled.text('IP=' + sta.ifconfig()[0],0,1)
            oled.show()
        else:
            cs.send(html % form)
        cs.close()
    s.close()

def get_ip():
  return (network.WLAN(network.STA_IF).ifconfig()[0],
  network.WLAN(network.AP_IF).ifconfig()[0])

def ap_on():
  network.WLAN(network.AP_IF).active(True)

def ap_off():
  network.WLAN(network.AP_IF).active(False)

#try connecting to lastest configured AP
sta=network.WLAN(network.STA_IF)
sta.active(True)
print('Connecting to AP ...')
oled.text('Connecting to AP',0,0)
oled.show()
time.sleep(WAIT_FOR_CONNECT)
if not sta.isconnected():
    set_ap()
else:
    pwm2.deinit()
    Pin(2).value(0)
    print('Connected:IP=', sta.ifconfig()[0])
    oled.fill(0)
    oled.text('AP connected',0,0)
    oled.text('IP=' + sta.ifconfig()[0],0,1)
    #Application code is written here or import from a separate file
    import myapp
    myapp.main()


注意, 顯示器版需下載驅動程式, 1602 LCD 需要下載下列兩個程式 :

# https://github.com/dhylands/python_lcd/blob/master/lcd/lcd_api.py
https://github.com/dhylands/python_lcd/blob/master/lcd/esp8266_i2c_lcd.py

SSD1306 OLED 版需下載下列程式 :

https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py
  
將這些驅動程式與 main.py 一起上傳到 D1 Mini 即可.

D:\ESP8266\test>ampy --port COM8 put main.py
D:\ESP8266\test>ampy --port COM8 put cd_api.py
D:\ESP8266\test>ampy --port COM8 put esp8266_i2c_lcd.py
D:\ESP8266\test>ampy --port COM8 put ssd1306.py