星期日, 12月 24, 2017

使用 minimify code 網站簡化 HTML 網頁的注意事項

在之前的〈ESP8266 全域變數佔用太多導致不斷重置〉中提到需要將 HTML 內容縮減, 才能放入 ESP8266 可用的記憶體中, 試用了許多提供縮減網頁內容的網站後, 發現許多網站縮減出來的內容都有大小不同的問題, 像是:
  • 無法同時縮減同時含有 HTML/JavaScript/CSS 的單一網頁內容, 必須自己分開處理。
  • 無法正確處理註解, 縮減成單一行後可能因為註解導致原來在註解之後的內容都變成註解了。
  • 任意將單引號換成雙引號, 導致 JavaScript 程式碼出錯。
最後最能符合我需求的是 minify code 網站, 符合我需要縮減內含 HTML/JavaScript/CSS 的單一 HTML 檔需求, 唯一的問題是它在處理 JavaScript 字串中的 "\n" 時會轉成 "n", 例如原始內容為:
縮減後變成:
要自己手動找出來修改, 這是美中不足的地方。

星期五, 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% 以下會比較保險, 程式跑起來才會比較穩定。

星期一, 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 時有以下訊息:
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));

星期四, 11月 09, 2017

Arduino 命令行錯誤:avr-g++: error: missing argument to '-mmcu='

不知道為什麼, 突然遇到使用 Arduino 命令行編譯上傳程式到 Nano 板時, 出現以下錯誤:

avr-g++: error: missing argument to '-mmcu='

明明之前都沒問題, 不知道是動到了什麼, 經過網路查詢之後, 明確加上 CPU 型號就可以了:

--board arduino:avr:nano:cpu=atmega328

雖然可解, 但我還是不知道出了什麼事?而且同樣的指令在其他機器上是可以正常運作的。

附記:使用上述方法正常編譯後, 再把 CPU 參數去掉, 就恢復正常了, 我還是不知道出了什麼事?

星期五, 9月 29, 2017

Visual Studio Code 中文字型設定

在 Visual Studio Code 中, 若沒有特別設定, 中文字會採用預設的新細明體, 如果想要更改設定, 必須更改設定中的 editor.fontFamily 項目, 例如:



 就會設定英文使用 Consolas, 中文使用思源等寬黑體。

星期日, 9月 03, 2017

在 Windows 10 安裝 .NET Framework

Windows 10 不允許直接執行離線版本的 .NET Framework 安裝程式, 如果要安裝 .NET Framework, 只能透過 Windows Update 或是使用部署映像服務與管理工具, 前者就是到『Windows 功能』(可從『控制台/應用程式與功能/程式與功能/開啟或關閉 Windows 功能』或直接搜尋『Windows 功能』) 中啟用 .NET Framework:
不過這種方式我常遇到會因為未知原因安裝失敗, 所以只好採取第二種方式。利用部署映象服務與管理工具安裝 .NET Framework 需要 Windows 10 原始光碟, 不過其實它真正需要的是光碟上 \sources\sxs 資料夾下的 microsoft-windows-netfx3-ondemand-package.cab 檔案, 如果你要安裝的機器沒有光碟機, 也可以將這個檔案複製到隨身碟, 即可依照相同的步驟安裝。

安裝的方式需要以管理員身份開啟命令提示字元, 下達 "dism /online /enable-feature /featurename:netfx3 /All /LimitAccess /Source:檔案路徑" 指令, 這裡的檔案路徑指的就是 microsoft-windows-netfx3-ondemand-package.cab 檔案的所在位置, 例如我將檔案複製到隨身碟的根資料夾下, 而隨身碟的磁碟代號為 d 時, 執行範例如下:
等待進度完成即可。

星期四, 8月 10, 2017

使用 Arduino 程式庫讀取新版 DHT11 溫濕度模組出錯的原因

DHT11 是許多人都用過的溫濕度模組, 不過在 2017 年 3 月時 DHT11 開始供給新版本的模組, 舊版模組就停產了。新版模組可說是功能加強版, 可偵測的範圍變大, 而且溫度也可以提供到小數 1 位。不過就是因為這小數 1 位, 如果您是使用 Arduino Playground 的程式庫讀取溫濕度, 遇到新版的 DHT11 模組就會發生校驗不正確, 幾乎無法讀取溫濕度的情況。

會出現這樣的問題, 主要是因為從 DHT11 讀取到的資料共 5 個位元組, 依序是相對濕度的整數部分、小數部分, 攝氏溫度的整數部分、小數部分, 以及由前面這 4 個位元組相加所得到的校驗值, 在程式庫中就是 bits[0]~bits[4]。不過由於舊版 DHT11 的濕度與溫度精確度都只到整數, 不會有小數, 也就是 bit[1] 和 bit[3] 永遠為 0, 所以程式庫中計算校驗值時就偷懶的只把 bit[0] 和 bit[2] 相加, 如以下所示:


當遇到溫度有小數部分時, 上列程式最後一行的校驗值檢查就一定不相等, 所以就無法正確讀取溫濕度了。要解決這個問題很簡單, 只要在計算校驗值時乖乖的把 4 個位元組相加就可以了:


雖然市面上可能還有為數不少的舊版模組, 不過我自己最近買到的就是新版的模組, 還差點因為這個問題以為買到了瑕疵品。大家可以先修改程式庫, 這樣不論新舊版模組都不會有問題。

星期六, 7月 08, 2017

製作給 Arduino PCM 程式庫使用的聲音資料

使用 Arduino PCM 程式庫需要一個儲存聲音數位化後的資料陣列, 這個陣列的內容就是以 8KHz 頻率取樣的樣本, 為了讓你自己的聲音檔可以轉換成此陣列格式, 可以合用兩個工具:AudacityEncodeAudio (這個網頁有 Mac OS 和 Linux 版), Audacity 可以把音樂檔轉成 8KHz 單聲道, 而 EncodeAudio 可以把轉好的聲音檔案轉成所需的資料陣列。

首先使用 Audacity 匯入原始的聲音檔, 然後改成單聲道:
再從左下角將取樣頻率改為 8000, 也就是 8KHz:
之後就可以匯出聲音檔案:
儲存時存檔類型請選『WAV 16-bit PCM』
接著請執行 EncodeAudio, 指定要使用的聲音檔案:
轉換完成它會把資料複製到剪貼簿中:
接著用編輯器開啟使用 PCM 程式庫的檔案, 把其中的聲音資料陣列內容換掉就可以了:

星期四, 6月 29, 2017

解決 PCM 播放與 Servo 程式庫衝突的問題

由於同事在製作使用伺服馬達的六腳機器人專案, 想要讓機器人遇到障礙物時可以用喇叭播放驚恐的聲音, 因此就利用了善心人士寫好的 PCM 播放程式庫, 不過一用才知道, 原來 Servo 程式庫和 PCM 程式庫都用到了 Arduino UNO 上的 Timer, 因此無法共存。研究了一下發現, Servo 會用到 Timer1 產生中斷送出給伺服馬達的方波訊號, 而 PCM 程式庫為了播放 8KHz 的聲音, 同時用了 Timer1 和 Timer2, Timer1 負責每秒產生 8000 次中斷, 每次中斷切換下一個 PCM 採樣值, 而 Timer2 則負責將 Sample 資料以 PWM 方式送出給喇叭發聲。兩個程式庫都用到了 Timer1, 如果混在一起用, 連編譯都不會過。即使編譯會過, 程式也無法正常運作, 因為兩個程式所需要的 Timer1 頻率根本不一樣。

為了解決問題, 必須有一方讓出 Timer1 給對方使用。討論之後發現, 機器人遇到障礙時可發出聲音後再繼續動作, 並不一定要邊發出聲音邊動作, 所以決定修改 PCM 程式庫, 把 Timer1 讓出來, 改成用很單純的迴圈搭配 delayMicroseconds(), 每 125μs 切換 PCM 採樣值發聲, 問題就解決了。修改過的程式庫可在這裡下載, 如果您也有一樣的困擾, 也許可以試用看看。

有關 Timer, 可參考這一篇文章, 有簡明易懂的說明, 並且整理了常用的程式庫所佔用的 Timer, 如果你的程式遇到怪現象, 例如使用了 tone() 函式後, 3 和 11 腳的 PWM 就失效, 很可能就是 Timer 的問題。

星期六, 6月 17, 2017

Windows 上 Python2/3 共存的最簡單方式

我真是後知後覺, 原來在 Windows 上從 Python 3.3 之後就提供了 Python launcher 工具程式, 可以讓你用 "py -版本" 的方式執行 Python 2 或是 Python 3 的直譯器。而對於套件管理軟體 pip, 也可以使用 pip2 或是 pip3 來區別管理 Python 2 與 Python 3 的套件:

星期四, 6月 01, 2017

Windows 下如何清除 DNS 快取與 NetBios 快取

平常我們再使用網路時, 可以直接透過主機名稱, 向是 www.yahoo.com.tw 瀏覽網頁, 或是透過區網中的電腦名稱直接連線, 例如我的主機電腦名稱叫做 tom, 就可以在檔案總管直接瀏覽 \\tom 察看分享的資源, 前者靠的是 TCP/IP 中的 DNS 名稱對應, 幫你從名稱找到對應的 IP 位址;後者靠的是微軟的 WINS 服務從 NetBIOS 協定使用的電腦名稱對應到 IP 位址。這兩種位址對應的資訊都會由系統快取下來, 後續再遇到相同的名稱實際不需要重新透過較繁瑣的查詢程序, 而可以直接從快取中找出之前查到的 IP 位址來使用。

但是當電腦在不同的網路中切換時, 比如說辦公室中有兩個以上的無線網路, 當你從 A 無線網路切換到 B 無線網路時, 系統還是會從快取中取得之前查詢的 IP 位址, 就可能會導致同樣以檔案總管瀏覽 \\tom 時卻無法連線的問題。這時我們可以先清除快取, 強迫讓系統重新查詢。要做到這件事, 必須以管理員身份執行『命令提示字元』(也就是 cmd.exe), 並透過以下指令清除 DNS 服務的名稱對應快取內容:

C:\WINDOWS\system32>ipconfig /flushdns

Windows IP 設定

成功清除 DNS 解讀器快取。

如果要清除 WINS 服務的 NetBIOS 名稱快取, 則要使用以下指令:

C:\WINDOWS\system32>nbtstat -R
順利清除和預先載入 NBT 遠端快取名稱表格。


星期四, 5月 11, 2017

Windows 10 下切換私人/公用網路的方法

許多人都遇到找不到切換 Windows 10 中私人/公用網路的設定, 其實很簡單, 只是現在沒有直接的選項, 而是必須以切換『讓此電腦可供探索』的方式來變更, 開啟『讓此電腦可供探索』就是私人網路, 關閉該選項就是公用網路。詳細設定過程就是先進『控制台』:


選取『網路與網際網路』:


我要改的是無線網路設定, 所以選『Wi-Fi』:


再按一下出現的無線網路名稱:


根據要切換成私人或是公用網路, 開啟或關閉『讓此電腦可供探索』選項即可。

星期三, 5月 03, 2017

使用 SSD1306 晶片控制的 OLED 顯示模組

由於 OLED 顯示實在很有科技感, 所以我測試了這一款從淘寶買到的模組, 賣家其實有提供一堆資料,包含 Arduino 可用的函式庫, 我自己測試時是採用 Adafruit 的 Adafruit_SSD1306 函式庫, 不過在使用這一個函式庫時有幾個注意事項:
  1. 它需要使用到 Adafruit 的另一個函式庫 Adafruit GFX graphics core library 才能運作, 這個函式庫是 Adafruit 所有繪圖類函式庫的核心。
  2. 我所買到的模組是使用 I2C 傳輸介面, 但是它的 I2C 位址和 Adafruit 自己賣的不一樣,經檢測後發現我的模組是 0x3C,如果你購買時買家沒有提供相關資訊,也可以接上 Arduino 後, 用 i2c_scanner 程式幫你搜尋。並修改範例中  display.begin(SSD1306_SWITCHCAPVCC, 0x3D);的 0x3D 為你模組的 I2C 位址。
  3. 必須修改函式庫中的 Adafruit_SSD1306.h 檔, 根據你模組的解析度調整取消對應的註解:
    #define SSD1306_128_64
    //   #define SSD1306_128_32
    //   #define SSD1306_96_16
    
這樣就可以讓 OLED 模組正常運作了。

星期四, 4月 13, 2017

使用 Raspberry 硬體加速播放影片當影片展示機

在 Raspberry Pi 中播放影片檔可能會發現有嚴重的卡頓, 這是因為你所使用的播放程式並沒有使用硬體解碼的功能, 要解決這個問題, 最簡單的方式就是使用 omxplayer 程式, 因為這個程式本來就是專為 Raspberry Pi 設計。不過 omxplayer 並沒有播放清單的設計, 如果你想重複連續播放多個檔案, 就無法完成。還好, 網路上已經有善心人士提供了簡易的 shell script, 可以幫你將指定的資料夾當成播放清單, 自動重複一一播放資料夾中影音檔案的工作。

像是我就將所有的影音檔放在 /home/pi/share/pv 下, 然後在 /home/pi 下建立一個 play.sh 檔:

#!/bin/sh

# get rid of the cursor so we don't see it when videos are running
setterm -cursor off

# set here the path to the directory containing your videos
VIDEOPATH="/home/pi/share/pv" 

# you can normally leave this alone
SERVICE="omxplayer"

# now for our infinite loop!
while true; do
 if ps ax | grep -v grep | grep $SERVICE > /dev/null
 then
  sleep 1;
 else
  for entry in $VIDEOPATH/*
  do
   clear

   # -r for stretched over the entire screen
   omxplayer -r $entry > /dev/null
  done
 fi
done

再使用以下的指令將 play.sh 變更為可執行檔:

sudo chmod +x videoloop.sh

就可以執行 /home/pi/play.sh 來重複一一播放 /home/pi/share/pv 下的所有影音檔了。如果你想要讓 Raspberry Pi 在開機後自動執行上述的 play.sh 檔, 可以修改 /home/pi/.config/lxsession/LXDE-pi/autostart 檔, 在最後加上一行執行 /home/pi/play.sh 的指令即可:

@lxpanel --profile LXDE-pi
@pcmanfm --desktop --profile LXDE-pi
@xscreensaver -no-splash
@point-rpi
@/home/pi/play.sh

上述方法只對開機後會登入圖形化介面的設定有效, 如果你是設定成開機後進入文字介面, 就要使用別的方式, 這裡不再多做介紹。

星期四, 2月 23, 2017

HC-SR04 超音波測距模組讀到 0 值後當住的解法

我在使用 Arduino 讀取 HC-SR04 超音波測距模組時, 常會遇到若 pulseIn() 傳回 0 (表示前方無任何障礙物, 所以 timeout) 後, 會有一段無法預期的時間 pulseIn() 都只會傳回 0, 像是 HC-SR04 的 echo 腳位完全沒有作用一樣。有的時候這段時間還超級長, 根本像是整個模組當掉了一樣。

經過搜尋網路上別人的經驗後, 發現這一篇討論串也提到相同的問題, 解決方法就是將 Arduino 原本用來讀取 HC-SR04 的 echo 訊號的腳位先故意設為輸出模式, 並且輸出低電位後一小段時間, 再重新設定回輸入模式, 就可以喚醒 HC-SR04, 或者說是 echo 的功能。

我自己把上述作法寫成以下的程式, 只要 pulseIn() 傳回 0, 我就採取前述對策:

const int echoPin = 11;
const int triggerPin = 12;

unsigned long ping1() {
  unsigned long pulse;

  digitalWrite(triggerPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(triggerPin, LOW);
  pulse = pulseIn(echoPin, HIGH, 50000);

  if(pulse == 0) {
    pinMode(echoPin, OUTPUT);
    digitalWrite(echoPin, LOW);
    delay(100);
    pinMode(echoPin, INPUT);
  }
  
  return pulse;
}


另外, HC-SR04 似乎對供電非常敏感, 我使用的有些 Arduino UNO 板如果只靠 USB 供電, 即使套用上述作法, 仍然無效, 必須外加電源供電, 才會正常, 也提供給大家參考。

星期四, 2月 09, 2017

Raspberry Pi 設定系統音量

Raspberry Pi 開機後會回復預設的音量, 而不是使用者設定的音量, 如果需要在程式中設定音量, 可以使用這篇文章提供的指令:

amixer set PCM -- 100%

如果使用 Python, 可以依照這篇文章的說明, 安裝 python-alsaaudio 模組, 即可利用以下程式片段調整音量:

import alsaaudio
m = alsaaudio.Mixer()
vol = m.getvolume()
vol = int(vol[0])

newVol = vol + 10
m.setvolume(newVol)