void ICACHE_FLASH_ATTR
pwm_set_freq(uint16 freq, uint8 channel) {
LOCK_PWM(critical); // enter critical
if (freq > PWM_FREQ_MAX) {
pwm.freq = PWM_FREQ_MAX;
} else if (freq < 1) {
pwm.freq = 1;
} else {
pwm.freq = freq;
}
pwm.period = PWM_1S / pwm.freq;
UNLOCK_PWM(critical); // leave critical
}
你可以看到雖然函式有 channel 參數, 但在函式內根本不會用到這個參數, 而且頻率是記錄在 pwm.freq 中, 不會區分是那個 channel 的頻率。
highlight.js
星期四, 3月 19, 2020
ESP8266 MicroPython 所有的 PWM 都是同一頻率
下午看了一下 ESP8266 MicroPython 的原始碼, 才發現原來所有的 PWM 都是共同頻率, 無法個別設計, 無論使用哪一個 PWM 物件叫用 freq(), 都會影響到所有的 PWM 物件。原始碼中實際設定頻率的程式如下:
星期三, 3月 18, 2020
ESP8266 MicroPython PWM 的奇怪現象
同事在使用 ESP8266 的 MicroPython 時, 使用 PWM 製作呼吸燈, 但卻遇到了奇怪的狀況, 程式如下:
from machine import Pin, PWM
import utime
# R:D5 G:D6 B:D7
r = PWM(Pin(14, Pin.OUT), freq=500, duty=0)
# this line would make PWM work in a strange way
g = PWM(Pin(12, Pin.OUT), freq=500, duty=0)
b = PWM(Pin(13, Pin.OUT), freq=500, duty=0)
while True:
for i in range(512):
b.duty(i)
#g.duty(i)
r.duty(i)
utime.sleep_ms(1)
for i in reversed(range(512)):
b.duty(i)
#g.duty(i)
r.duty(i)
utime.sleep_ms(1)
其實就是簡單的用 2 個腳位 PWM 從 0~512 再回到 0 製作陽春的呼吸燈效果, 不過執行後卻發現其中一個燈看起來一直亮著、另外一個燈卻偶而才閃一下, 根本沒有 PWM 漸次變化的效果。
經過實驗測試後發現, 程式一開始建立了 3 個 PWM 物件, 但是 g 這個 PWM 物件卻沒有用到, 如果不要建立這個多餘的 PWM 物件 g , 或是在實際上有使用到 g 物件, 例如把程式中 for 迴圈內的註解符號移除, 結果就正常了。目前還不知道實際發生問題的原因。
20200830 補充:這個問題我有貼到 MicroPython 論壇上, 經過善心人士回饋到 MicroPython github 上後, 已經把 bug 解掉了, 如果你改用 2020/3/27 之後的韌體版本, 就不會發生一樣的問題了。
20200909 補充:在 2020/09/02 發佈的 1.13 版韌體中, 已經將上述修正併入正式版本了。星期五, 9月 27, 2019
APDS9930 程式庫造成使用 bearssl 程式庫時編譯錯誤
如果你使用 ESP8266 為基礎的開發板, 例如 D1 mini, 搭配 APSD9930 程式庫, 同時又有使用到 ESP8266Wifi 程式庫的話, 依照以下順序撰寫 #incldue 敘述:
#include <APDS9930.h>
#include <ESP8266WiFi.h>
就會出現以下的編譯錯誤:In file included from D:\FlagsBlockSketch.ino:3:0:
D:\arduino\libraries\APDS9930-master\src/APDS9930.h:134:33: error: expected ',' or '...' before numeric constant
#define B 1.862
^
D:\arduino\hardware\esp8266com\esp8266/tools/sdk/include/bearssl/bearssl_ec.h:412:60: note: in expansion of macro 'B'
uint32_t (*muladd)(unsigned char *A, const unsigned char *B, size_t len,
^
這主要是因為 APDS9930.h 中定義了巨集 B 為 1.862, 而在 bearssl_ec.h 中個地方定義了函式的原型為:uint32_t (*muladd)(unsigned char *A, const unsigned char *B, size_t len,
const unsigned char *x, size_t xlen,
const unsigned char *y, size_t ylen, int curve);
所以前置處理器會把 B 展開替換成 1.862, 所以這個函式原型就變成:uint32_t (*muladd)(unsigned char *A, const unsigned char *1.862, size_t len,
const unsigned char *x, size_t xlen,
const unsigned char *y, size_t ylen, int curve);
而造成編譯錯誤, 錯誤訊息為:error: expected ',' or '...' before numeric constant
這個問題我已經通知程式庫的原作者, 也發了 pull request 給他, 你可以自己把程式庫中的巨集 B 改名字, 或者是更改 #include 的順序, 把 APDS9930.h 放到最後就可以了。
星期一, 1月 28, 2019
MicroPython 的記憶體限制
MicroPython 由於是跑在資源有限的單晶片控制板上, 所以寫程式時必須錙銖必較, 在 MicroPython ESP8266 的文件上有提到一種作法是避免建立不必要的物件, 原文是這樣說的:
底下有舉例, 一個常見的例子就是字串的串接操作, 每次使用 '+' 來串接字串時, 就會牽涉到 3 個字串物件, '+' 號左右各一個, 以及串接後建立的新字串物件。最近就剛好遇到一個案例, 使用 urequests.get 向 OpenWeatherMap 網站查詢器項資料, 為了讓人看清楚整個參數的結構, 所以寫成這樣 (整個程式不只這樣):
結果程式一執行, 雖然可以看到結果, 但是程式結束後要回到交談模式時, ESP8266 就會當掉。只要把其中 "q=" + ... 這邊改成不要串接, 直接寫成 "q=Taipei,TW" + 就可以了, 還害我找了好久的問題。畢竟在單晶片控制板上跑的 Python 和在一般電腦上跑得不一樣, 撰寫程式時要特別小心。
There are a number of situations where objects may unwittingly be created and destroyed. This can reduce the usability of RAM through fragmentation.
底下有舉例, 一個常見的例子就是字串的串接操作, 每次使用 '+' 來串接字串時, 就會牽涉到 3 個字串物件, '+' 號左右各一個, 以及串接後建立的新字串物件。最近就剛好遇到一個案例, 使用 urequests.get 向 OpenWeatherMap 網站查詢器項資料, 為了讓人看清楚整個參數的結構, 所以寫成這樣 (整個程式不只這樣):
結果程式一執行, 雖然可以看到結果, 但是程式結束後要回到交談模式時, ESP8266 就會當掉。只要把其中 "q=" + ... 這邊改成不要串接, 直接寫成 "q=Taipei,TW" + 就可以了, 還害我找了好久的問題。畢竟在單晶片控制板上跑的 Python 和在一般電腦上跑得不一樣, 撰寫程式時要特別小心。
星期一, 8月 06, 2018
firebase-arduino 程式庫無法更新資料
如果您有使用 firebase-arduino 程式庫由 ESP8266 控制板上傳資料到 Firebase 的資料庫, 會發現從約莫上週五開始, 就會失敗, 這是因為 Firebase 更新了他們的數位指紋 (fingerprint), 為此 firebase-arduino 也在幾天前修改了程式庫內容 (可參考這一篇文章)。您可以重新下載程式庫, 安裝到 Arduino 開發環境中就可以解決, 或者也可以直接修改 FirebaseHttpClient.h 檔, 將 kFirebaseFingerprint 字串的內容修改為 "6F D0 9A 52 C0 E9 E4 CD A0 D3 02 A4 B7 A1 92 38 2D CA 2F 26" , 再重新編譯程式執行即可。
星期二, 3月 13, 2018
ESP8266 MicroPython 使用 poll() 出現 Fatal exception 28(LoadProhibitedCause)
由於 MicroPython 預設並沒有像是 ESP8266 Arduino Core 提供有 ESP8266WebServer 程式庫那樣的簡易 Web Server, 所以打算撰寫一個超級簡易版來用。在設計的過程中, 發現 socket 的 accept() 在沒有新連線的情況下並不會返回, 這樣程式流程就會被凍結在這裡無法進行其他的工作, 根據 MicroPythono 文件的說明, 建議你改用 uselect 模組的 poll 類別, 於是我大概寫了這樣的程式:
我寫好的 Web Server 可以參考這裡。
server.bind(('0.0.0.0', port))
server.listen(1)
poller.register(server, uselect.POLLIN)
where True:
res = poller.poll(0)
if res:
(socket, sockaddr) = server.accept()
handle(socket)
...
程式運作基本上都很好, 但是會在某個沒有規律的神奇時間出現嚴重例外而重置, 出現以下訊息:Fatal exception 28(LoadProhibitedCause):
epc1=0x4025681d, epc2=0x00000000, epc3=0x00000000,
excvaddr=0x00000010, depc=0x00000000
ets Jan 8 2013,rst cause:2, boot mode:(3,6)
...
經過反覆實驗, 發現只要叫用 poll() 時不要給 0 作為 timeout, 就可以避免了。因此最後修改的程式就把 poll(0) 改為 poll(1) 即可。我寫好的 Web Server 可以參考這裡。
星期五, 2月 02, 2018
D1 Mini Motor Shield I2C 通訊問題
因為開發產品的緣故, 同事購買了 D1 Mini Motor Shield 來控制直流馬達, 不過實際使用時發現奇特的現象, 閒置一會兒就無法控制馬達。經過簡單的除錯步驟, 發現如果 D1 Mini 與 Motor Shield 間一段時間 (實際測試約 10 秒) 沒有進行 I2C 通訊, Motor Shield 的 I2C 就會沒有任何反應。解決的方法就是每隔一小段時間, 例如 5 秒鐘, 就進行一次 I2C 通訊, 就可以維持 Motor Shield 的 I2C 正常運作。
網路上也有善心人士針對此問題, 撰寫了修正版本的 Motor Shield 韌體, 如果覺得上述手動修改程式定時進行 I2C 通訊的方式很麻煩, 也可以是是看燒錄修正版本的韌體。
網路上也有善心人士針對此問題, 撰寫了修正版本的 Motor Shield 韌體, 如果覺得上述手動修改程式定時進行 I2C 通訊的方式很麻煩, 也可以是是看燒錄修正版本的韌體。
星期五, 1月 26, 2018
ESP8266 Arduino Core 2.4.0 min() 與 max() 問題
ESP8266 Arduino Core 在更新到 2.4.0 時, 把原本是使用巨集定義的 min() 和 max() 改成使用 C++ 標準的 std::min() 和 std::max() 樣板 (template), 這使得許多使用到 min() 和 max() 的函式庫編譯錯誤, 這主要是因為原本巨集的版本大概如下 (以下均以 min() 為例, max() 的情況相同):
#define min(x, y) ((x) < (y) ? (x) : (y))
其中 x 與 y 可以是任意型別, 因此如果 x 是浮點數、y 是整數也可以成功編譯。可是改用了 std::min() 樣板, 它的原型如下 (事實上有多個版本, 但都有類似的問題):template< class T > const T& min( const T& a, const T& b );
由於樣板中兩個比較的對象其資料型別都必須式樣板參數 T, 因此以下的程式編譯時就會有問題:float val = 200;
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
}
void loop() {
// put your main code here, to run repeatedly:
val = min(val, 210);
Serial.print("min = ");
Serial.println(val++);
delay(200);
}
你會看到錯誤訊息為:no matching function for call to 'min(float&, int)'
這是因為 val 是 float, 但是 210 是 int, 兩者型別不同, 所以無法用 std::min() 樣板建立對應呼叫的函式實體。要解決這個問題, 有兩種方法:一是在你的程式中自行定義巨集版本的 min(), 或者是在程式中強制轉型, 讓兩個比較的參數都是相同的型別, 例如: val = min((int)val, 210);就可以成功編譯, 也可以正確執行了。
星期日, 12月 24, 2017
使用 minimify code 網站簡化 HTML 網頁的注意事項
在之前的〈ESP8266 全域變數佔用太多導致不斷重置〉中提到需要將 HTML 內容縮減, 才能放入 ESP8266 可用的記憶體中, 試用了許多提供縮減網頁內容的網站後, 發現許多網站縮減出來的內容都有大小不同的問題, 像是:
縮減後變成:
要自己手動找出來修改, 這是美中不足的地方。
- 無法同時縮減同時含有 HTML/JavaScript/CSS 的單一網頁內容, 必須自己分開處理。
- 無法正確處理註解, 縮減成單一行後可能因為註解導致原來在註解之後的內容都變成註解了。
- 任意將單引號換成雙引號, 導致 JavaScript 程式碼出錯。
縮減後變成:
要自己手動找出來修改, 這是美中不足的地方。
星期五, 12月 22, 2017
ESP8266 全域變數佔用太多導致不斷重置
由於 ESP8266 本身具備 Wi-Fi 無線網路, 在 Arduno Core 中又提供有簡單好用的 WebServer 程式庫, 所以我就在 ESP8266 中塞了一個 Web Server, 利用 String 儲存 HTML 檔, 由於必須假設無法對外連網, 而且需要在客戶端利用 Ajax 通訊, 所以這個 HTML 檔當然也包含了一堆 JavaScript 程式碼, 以及一長串的 CSS 碼。原本測試的好好, 但隨著功能增加, 這個內含網頁 HTML 的字串就越來越大, 突然間原本可以正常運作的程式一上傳到 ESP8266 後, 內建的 LED 燈就不斷閃爍, 而且客戶端裝置根本連不上 ESP8266 自建的網路。開了終端機觀察, 才發現根本一跑起來要連網時就重置, 一直不斷重複這樣的過程:
再為頭看 Arduino IDE, 原來他早就警告我, 全域變數用量過高, 會導致系統不穩定:
對症下藥, 當然就是減少全域變數的用量, 而既然我放的是 HTML 網頁內容, 就找可以壓縮網頁的工具, 經過像是 minify code 處理後, 就正常了。我自己反覆測試, 大概要把全域變數的用量壓在佔記憶體的 65% 以下會比較保險, 程式跑起來才會比較穩定。
再為頭看 Arduino IDE, 原來他早就警告我, 全域變數用量過高, 會導致系統不穩定:
對症下藥, 當然就是減少全域變數的用量, 而既然我放的是 HTML 網頁內容, 就找可以壓縮網頁的工具, 經過像是 minify code 處理後, 就正常了。我自己反覆測試, 大概要把全域變數的用量壓在佔記憶體的 65% 以下會比較保險, 程式跑起來才會比較穩定。
星期一, 11月 27, 2017
ESP8266 Arduino Core 中 ctype.h 內預先定義的常數
我在編譯 ESP8266 的 Arduino 程式時, 遇到一個莫名其妙的事情, 底下這行程式碼一直遇到編譯錯誤:
double _N; //-- Number of samples錯誤訊息如下:
expected unqualified-id before numeric constant想說, 奇怪, 這裡哪來的數值常數?找了一下才發現, 原來 ESP8266 Arduino Core 中 ctype.h 定義了 _N 為 04, 如下:
#define _U 01 #define _L 02 #define _N 04 #define _S 010 #define _P 020 #define _C 040 #define _X 0100 #define _B 0200所以如果你的程式中使用到了 _N, 就會變前處理器取代成 04, 我的那一行程式就變成:
double 04; //-- Number of samples自然無法編譯成功了。要注意的是, 除了我碰到的 _N 外, 還有許多已經被 ctype.h 事先定義的名稱, 如果你遇到的奇怪的編譯結果, 可以到原始碼找看看, 可能你也跟我一樣遇到類似的情況, 解決的方法最簡單的就是更改自己的程式變數名稱, 避免和 ESP8266 Arduino Core 中的名稱衝突。
星期五, 11月 17, 2017
ESP8266 的 EEPROM 注意事項
在 ESP8266上使用 EEPROM 要注意兩件事, 首先是必須叫用 EEPROM.begin(), 並指定要使用的 EEPROM 區域大小:
EEPROM.begin(sizeof(int) * 4);其次, 寫入 EEPROM 的操作必須叫用 EEPROM.commit() 才會真的儲存下來:
EEPROM.commit();這是和 Arduino 不同的地方。如果同一個程式希望在 Arduino 和 ESP8266 上都能編譯,可以使用 ESP8266 Arduino Core 在前處理器上定義的名稱 ESP8266 來做條件式編譯, 例如:
#ifdef ESP8266 EEPROM.commit(); #endif這樣就只會在編譯 ESP8266程式時叫用 commit(), 若式編譯 Arduino 程式時, 這一行就不會生效了。
星期四, 11月 16, 2017
小心, ESP8266 有看門狗 (Watch Dog Timer)
ESP8266 雖然有 Arduino Core 可以直接用 Arduino IDE 寫程式, 不過 ESP8266 畢竟不是 Arduino, 有些硬體上的差異還是可能讓你的程式掛掉, 今天就遇到一個搞死我的問題。我有一個機器人的程式, 跑一些動作時 ESP2866 就會 reset, 本來以為是供電不足, 但馬達單獨供電還是會 reset, 開了 Serial 看資料才發現, reset 時有以下訊息:
解決的方法很簡單, 只要在閒置區域中呼叫 yield() 或是 delay(0) 就會更新看門狗計時器, 也就不會 reset 了。修改的程式如下:
Soft WDT reset ctx: cont ....原來 ESP8266 有看門狗計時器 (Watch Dog Timer), 程式會在 loop() 結束或是呼叫 delay() 時更新看門狗計時器, 如果很長一段時間沒有更新看門狗計時器, 就會啟動 reset 機制, 警告你程式某些地方有閒置過久的問題。舉例來說, 如果你在 ESP8266 上跑以下這個在 loop() 中閒置 10 秒的程式:
void setup() { Serial.begin(9600); Serial.println("Begin testing"); } unsigned long prevTime; void loop() { prevTime = millis(); while(millis() - prevTime < 10000); }就會在序列埠監控視窗中看到看門狗計時器 reset 的訊息:
解決的方法很簡單, 只要在閒置區域中呼叫 yield() 或是 delay(0) 就會更新看門狗計時器, 也就不會 reset 了。修改的程式如下:
void setup() { Serial.begin(9600); Serial.println("Begin testing"); } unsigned long prevTime; void loop() { prevTime = millis(); while(millis() - prevTime < 10000) {yield();} }
星期六, 11月 11, 2017
中計, 同一資料型別但不同平台資料長度不同的陷阱
這幾天測試 ESP8266 的 Arduino core, 拿以前寫的 Arduino 程式來試看看相容性, 遇到一個程式怎麼跑數值都錯, 這個程式會從感測器讀取兩個位元組的資料, 再由這兩個位元組組成一個 16-bit int, 結果原來是 ESP8266 的 int 是 32-bit, 而 Arduino 的 int 是 16-bit, 因此, 原本以下正確的程式:
return int(word(high,low));遇到負數時會出現超過 32767 的數值, 害我還回頭看感測器的規格書, 以為是自己程式寫錯了。後來才想到, 可能是資料長度的問題, 改成以下這樣就正確了:
return int16_t(word(high,low));
訂閱:
文章 (Atom)




