highlight.js

星期一, 5月 10, 2021

『質量』很好, 到底是質好還是量多

很多與我一樣的 5 年級生會不習慣對岸的用詞, 這倒不一定是對岸的用詞一定不好, 而是有些詞用起來就是很混淆。舉例來說, 『質量』一詞就很怪, 因為『質』與『量』是兩件事, 但是對岸口語上講的『質量』通常是指『質』, 例如, 『A 選手這一球質量很高, B 選手雖然接到了球卻無法回擊』, 不如就直接講『A 選手這一球力道很強』。『質』『量』不分, 實在很有問題。

星期五, 9月 11, 2020

在 linux 下複製資料夾內的所有檔案但不複製資料夾

同事問了一個問題, 他想要只把 A 資料夾下包含子資料夾內的檔案通通複製到 B 資料夾下, 但是不要在 B 資料夾下建立各層的子資料夾, 例如:
$ ls -R test
test:
a.txt  t2/

test/t2:
a2.txt
若希望把 test 下的 a.txt 和 test/t2 下的 a2.txt 複製到同一個資料夾下, 例如 dd 下, 但是不要在 dd 內建立 t2 資料夾, 這只靠 cp 指令的 -r 選項是做不到的。根據估狗大神幫我找到了這一篇討論, 找到了使用 find 指令的方式如下:
$ find test -type f -exec cp "{}" dd ";"
$ ls dd
a2.txt  a.txt
find 指令可以使用 -exec 選項在每次找到一個檔案時執行指定的指令, 指令中的 {} 會被取代為找到的檔案名稱, -exec 到 ";" 之間就是要執行的指令。利用這個選項, 就可以為資料夾中的每一個檔案執行 cp 指令, 複製到同一個資料夾中了。上述指令中之所以要將 {} 以及 ; 用 "" 括起來, 是為了避免被 shell 執行 shell expansion 而出錯。

星期二, 5月 26, 2020

Arduino Keyboard 程式庫 println 沒有送出 Enter 的問題

同事有一個程式模擬鍵盤輸入裝置, 使用了 Arduino 的 Keyboard 函式庫, 程式大概是這樣:
  Keyboard.begin();
  delay(2000);
   Keyboard.press(KEY_LEFT_GUI);
  delay(500);
   Keyboard.press('r');
  delay(500);
   Keyboard.releaseAll();
   Keyboard.println("NOTEPAD");                   //輸入文字 NOTEPAD
  delay(500);
也就是模擬按了 Win + r 開啟 Widnows 的『執行』交談窗, 然後輸入 "notepad" 開啟記事本, 但是卻發現執行後只會停在『執行』交談窗, 少了最後按下 Enter 執行所輸入指令的動作。後來使用了新版的 Arduino IDE 重新編譯執行這個程式卻又正常, 可以開啟記事本, 經過查證, 才發現 Arduino IDE 1.8.5 (含) 之前的 Keyboard 程式庫有問題, 1.8.6 就修正了。這個問題主要在於 Keyboard 是繼承自 Print 類別, Print 的 println 會使用字串版的 write 輸出字串, 程式如下:
size_t Print::write(const uint8_t *buffer, size_t size)
{
  size_t n = 0;
  while (size--) {
    if (write(*buffer++)) n++;
    else break;
  }
  return n;
}
在 while 迴圈中, 如果目前輸出的字元是 0, 就會當成是字串結尾, 跳離迴圈;否則會叫用字元版的 write() 輸出單一字元, 而在 Keyboard 類別中重新定義了這個字元版的 write(), 程式如下:
size_t Keyboard_::write(uint8_t c)
{
	uint8_t p = press(c);  // Keydown
	release(c);            // Keyup
	return p;              // just return the result of press() since release() almost always returns 1
}
這主要就是模擬按下並放開鍵盤上對應該字元按鍵的動作, 不過 press() 中會把 ASCII 碼透過 _asciimap 這個陣列轉換成對應按鍵的掃描碼, 而 '\r' (CR, 0x0D) 會對應到掃描碼 0:
const uint8_t _asciimap[128] =
{
	0x00,             // NUL
	0x00,             // SOH
	0x00,             // STX
	0x00,             // ETX
	0x00,             // EOT
	0x00,             // ENQ
	0x00,             // ACK  
	0x00,             // BEL
	0x2a,			// BS	Backspace
	0x2b,			// TAB	Tab
	0x28,			// LF	Enter
	0x00,             // VT 
	0x00,             // FF 
	0x00,             // CR 
	......
使得字元版的 write() 傳回 0, 導致字串版的 write() 認為字串結束而返回。由於 println() 在送出字串後會再送出 "\r\n", 也就是 CRLF 當成換行, 因此處理到 '\r' 就被當成字串結束而停止, 最後的 '\n' 就沒有機會送出, 因此不會模擬成按下 Enter 了。 為了修正這個問題, 從 Arduino 1.8.6 開始, Keyboard 類別就重新定義了自己的 write() 版本, 如下所示:
size_t Keyboard_::write(const uint8_t *buffer, size_t size) {
	size_t n = 0;
	while (size--) {
		if (*buffer != '\r') {
			if (write(*buffer)) {
			  n++;
			} else {
			  break;
			}
		}
		buffer++;
	}
	return n;
}
你可以看到當遇到 'r' 字元時, 它會當成沒看到, 繼續處理下一個字元, 這樣就可以避免上述問題了。

判斷 Arduino IDE 版本

由於同事遇到一個怪問題, 需要根據 Arduino IDE 版本使用不同的程式, 查了一下才知道, Arduino 在編譯時會定義一個巨集 ARDUINO, 格式為類似 10812, 表示 1.8.12 版, 只要利用 #if 判斷, 即可區別不同版本的 Arduino IDE 了, 例如:
#if ARDUINO<=10805
   Keyboard.press(KEY_RETURN);                  //按下 Enter
   Keyboard.release(KEY_RETURN);
#endif
就可以讓程式只有在 Arduino IDE 版本為 1.8.5(含) 之前的版本才會執行, 教新的版本就不會執行了。

星期日, 4月 12, 2020

在 WSL 1/2 執行 GUI 程式

WSL 很好用, 不過預設的情況下你只有 Terminal 跑 Shell 可以用, 如果想要測試一些圖形化界面的程式, 就要借助額外的程式, 好在已經有許多善心人士提供了免費的 X server 軟體, 可以在 Windows 環境裡執行 GUI 程式。我自己習慣使用的是 mobaXterm 軟體, 預設安裝好並不需要其他設定, 只要執行就會啟動 X server。接著, 就可以在 WSL 裡設定 X server 的位址與螢幕編號:
export DISPLAY=:0
上例中表示 X server 的位址就是 localhost, 或者 127.0.0.1, 螢幕編號為 0, 也就是第一個螢幕。設定好之後就可以執行 GUI 程式, 例如 gvim:

View post on imgur.com


如果使用 WSL2, 它會像是 VirtulaBox 一樣幫你建立一個虛擬網路界面, 你可以透過 Windows 下的 ipconfig 指令看到有一個網路界面名稱裡面有一個有 WSL 字樣, 就會標示這個界面中 Windows 這一端的 IP:
D:\wsl2 ❯❯❯ ipconfig

Windows IP 設定

....

乙太網路卡 vEthernet (WSL):

   連線特定 DNS 尾碼 . . . . . . . . :
   連結-本機 IPv6 位址 . . . . . . . : fe80::2d93:534a:4692:bc2e%53
   IPv4 位址 . . . . . . . . . . . . : 172.31.80.1
   子網路遮罩 . . . . . . . . . . . .: 255.255.240.0
   預設閘道 . . . . . . . . . . . . .:

172.31.80.1 就是 Windows 在這個虛擬網路中的 IP 位址。或者你也可以在 WSL2 中使用 ip route 指令查看, 就可以知道 Windows 那一端的 IP:
$ip  route
default via 172.31.80.1 dev eth0
172.31.80.0/20 dev eth0 proto kernel scope link src 172.31.86.214
上述設定 X server 位址的指令就要改成:
export DISPLAY=172.31.80.1:0
才能正確連到 X server。

WSL2 與 VirtualBox 不相容

WSL2 因為使用了 Hyper-V 虛擬機器, 所以比透過 API 轉換層執行的 WSL1 快得多, 不過也因為這個原因, 和 VirtualBox 並不相容, 即使使用了支援 HyperV 的 VirtualBox 6.X 版也一樣, 如果你在啟用了 WSL2 的系統上使用 VirtualBox 安裝例如 Linux 作業系統, 就會遇到到一些靈異現象, 像是我自己就遇到安裝到最後不成功, 或是好像安裝成功, 但是使用時執行 git clone 老是說什麼 hashcode 不對、或者是下載 .deb 檔但要透過 dpkg 安裝卻解壓縮失敗之類的。目前若要同時使用 WSL 與 VirtualBox, 就必須改用 WSL1, 而且要確認沒有啟用『虛擬機器平台』功能:

View post on imgur.com


另外, 也要確認 WSL2 要求你要安裝的 WSL2 Linux kernel update package 也要移除, 才能正常使用 VirtualBox。

星期六, 4月 11, 2020

VIM 的眾多版本差異

在 Linux 上安裝 vim 如果沒有特別指名套件名稱, 例如:
sudo apt install vim
那麼安裝的會是 vim-common 套件, 這個套件只會安裝文字模式的 vim, 不會安裝圖形化版本的 gvim, 而沒有圖形化版本的套件, 邊一時是不會加上 +xterm_clipboard 模組的, 也就是無法讓 vim 複製資料到系統剪貼區 (clipboard), 也無法從剪貼區貼資料到 vim 中。

因此, 建議在安裝 vim 時, 可以選擇有圖形化版本的套件, 例如:
  • vim-gui-common:通用行的圖形化版本, 如果沒有什麼特別需求, 或是面對以下版本不知道該選那一種, 就可以安裝這個版本。
  • vim-athena:採用 X Athena 圖形元件程式庫的版本, 如果你執行這個套建中的 gvim, 會注意到他的圖形界面長的很不一樣, 那就是 X Athena 程式庫。這個版本體積小一點, 如果你不介意界面長的怪怪的, 或者根本就不會執行 gvim, 那安裝這個版本根本沒差。
  • vim-gtk/vim-gtk3:搭配 gtk 同行界面程式庫的版本, gvim 的界面看起來會正常許多。如果你的環境本來就會用到許多使用 gtk 建制的軟體, 那就可以安裝這一個套件。
如果你根本不會執行 gvim, 也不在乎能不能與系統剪貼簿交換資料, 那麼也可以安裝 vim-tiny 套件, 這個套件是 vim-common 的精簡版, 最主要的差別是 vim-tiny 只能使用 vim 語法撰寫 vim 腳本, 無法使用 Python 或是其他程式語言撰寫 vim 腳本。

星期四, 3月 19, 2020

ESP8266 MicroPython 所有的 PWM 都是同一頻率

下午看了一下 ESP8266 MicroPython 的原始碼, 才發現原來所有的 PWM 都是共同頻率, 無法個別設計, 無論使用哪一個 PWM 物件叫用 freq(), 都會影響到所有的 PWM 物件。原始碼中實際設定頻率的程式如下:
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 的頻率。

星期三, 3月 18, 2020

改用 highlight.js 為文章中的程式碼加上語法顏色標示

原本使用的 SyntaxHighlighter 在 Blogger 上一整個炸壞掉, 現在改用 highlight.js, 快速簡單, 中間也考慮過 Google 的 code-prettify, 但是會有程式過長疊到右邊側欄內容的問題。

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 版韌體中, 已經將上述修正併入正式版本了。

星期五, 2月 14, 2020

macOS 的 APFS 與 HFS+ 檔案系統對磁碟影像檔的影響

如果要製作磁碟影像檔, 由於 macOS 在 High Sierra(也就是 10.13) 以後的預設檔案系統是 APFS, 因此若沒有更改設定, 指定使用 HFS+ 檔案系統, 製作出來的磁碟影像檔就無法在舊版的作業系統上開啟。製作影像檔時請務必小心:


星期一, 2月 03, 2020

MicroPython 文件上 framebuffer 的 framebuf.MONO_HLSB 與 framebuf.MONO_HMSB 說明反了

在 MicroPython 的文件中, framebuffer 的說明中對於 framebuf.MONO_HLSB 這樣一段:

Monochrome (1-bit) color format This defines a mapping where the bits in a byte are horizontally mapped. Each byte occupies 8 horizontal pixels with bit 0 being the leftmost.

因此, 如果有以下的 8X8 的圖:


就應該表示為這樣:
[0b10001001,
 0b00001001,
 0b00001001,
 0b00001001.
 0b00001001.
 0b00000001,
 0b00000001,
 0b00000001]
不過實際上顯示出來的圖左右會相反, 因此推測文件上 framebuf.MONO_HLSB 與 framebuf.MONO_HMSB 的說明應該是弄反了。在 MicroPython Forum 上也有人確認我的疑問

星期一, 12月 23, 2019

Maixduino 編譯 selfie 範例出現 "cannot declare variable 'camera' to be of abstract type 'Sipeed_OV2640'" 錯誤

在編譯 Maixduino 板的 selfie 範例時, 竟然出現以下錯誤:
cannot declare variable 'camera' to be of abstract type 'Sipeed_OV2640'
經過查證, 在這一篇文章中提到在 0.3.11 版的原始碼中, Sipeed_OV2640 類別的內容把虛擬函式的名字 setRotation 打錯字變成 setRotaion, 導致繼承自 Camera 類別的 setRotation 虛擬函式沒有實作的內容, 讓 Sipeed_OV2640 類別仍舊是虛擬類別, 無法用來建立物件。解決的方法很簡單, 就是自己去 c:\users\你的使用者名稱\AppData\Local\Arduino15\packages\Maixduino\hardware\k210\0.3.11\libraries\Sipeed_OV2640\src 下, 把 Sipeed_OV2640.h 以及 Sipeed_OV2640.cpp 中的錯字 setRotaion 更正為 setRotation 就可以了。

星期二, 11月 26, 2019

MacOS Catalina App 『已損毀,無法打開。你應該將其丟到「垃圾桶」』的錯誤訊息

MacOS 升級到 Catalina 後, 一定會被他嚴密的安全控管驚嚇, 比如說, 你從 Arduino 官網下載了 Arduino, 可以正常執行, 但如果你把這個 Arduino 上傳到 Google 雲端硬碟, 然後再從雲端硬碟下載回來, 一執行可能就會遇到這樣的畫面:


這是因為 Catalina 會自動幫不是從註冊網站下載回來的 App 加上註記, 執行時會根據註記的資料, 判斷是否可安全執行。你可以透過以下的指令觀察註記資料:
xattr -l Arduino.app/
com.apple.quarantine: 0181;5ddd072e;Firefox;BCD73B6E-DE9E-4821-93E6-C73238A5F5AF
xattr 是操作檔案延伸屬性的指令, 這裡可以看到 Arduino.app 有一項延伸屬性, 屬性名稱是 com.apple.quarantine, 而屬性內容是雖然是編碼過的資料, 但可以看到註記了此 App 是使用 Firefox 下載回來的, 只要將此註記資料刪除, Catalina 就不會檢查 App 是不是從註冊網站下載的, 也就可以正常執行了。刪除此註記資料的指令如下:
xattr -rd com.apple.quarantine Arduino.app
-d 選項是刪除後面指定的延伸屬性, 而 -r 選項是遞迴處理資料夾內的所有檔案與子資料夾。

星期五, 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 放到最後就可以了。

使用 id 名稱直接存取 DOM 物件

同事問了我一個問題, 為什麼我們總是要叫用 document.getElementById() 來取得 HTML 網頁上的物件?不是明明就可以直接用 id 名稱嗎?由於我的知識停留在古時候, 一查 HTML 標準才發現原來可以直接用 id 或是 name 屬性定義的名稱直接存取, 不需要叫用 document.getElementById(), 例如:

See the Pen
TestID
by 黃昕暐 (@meebox)
on CodePen.



不過在文件中寫到, 到底是哪些名稱會對應到 window 物件的屬性在未來可能會變化, 因此並不建議這樣用, 還是請乖乖的用 document.gettElementById(), 既然如此, 那幹嘛提供這個功能呢?

星期四, 8月 01, 2019

macOS 上執行 Arduino 出現奇怪的錯誤

同事協助測試 macOS 版軟體時, 遇到從 Python 執行 Arduino 1.8.5 就會掛掉, 看到以下的錯誤:


實在搞不懂怎麼回事, 根據網路上找到的相關問題, 似乎和 MacBook Pro 的 touchbar 衝突, 後來改成 1.8.9 版的 Arduino 就沒事了, 算是先解決了, 但還是不很確定原因。

星期一, 1月 28, 2019

MicroPython 的記憶體限制

MicroPython 由於是跑在資源有限的單晶片控制板上, 所以寫程式時必須錙銖必較, 在 MicroPython ESP8266 的文件上有提到一種作法是避免建立不必要的物件, 原文是這樣說的:

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 和在一般電腦上跑得不一樣, 撰寫程式時要特別小心。

星期二, 1月 01, 2019

Python 中跨平台播放固定頻率聲音的模組--pysine

找了很久, 終於找到了, Windows 平台上有 winsound 模組簡單易用, 可是這是 Windows 專屬, 如果需要跨平台, 可以使用 pysine
from pysine import sine
sine(frequency=440.0, duration=1.0)

被誤解的亞里斯多德--爛翻譯的可怕

之前看到這一篇文章《提倡寫故事,貝佐斯禁止開會用PPT》時, 只覺得這是一篇誤會 PowerPoint 的文章, 你的投影片上只有條列項目, 當然效果差。不過今天老婆看到同一篇文章, 問我文章裡面提到亞里斯多德說:
有說服力的論據必須有效的3個要素,分別為精神、標誌和悲觀情緒
是什麼意思?一看直覺就是這一定是譯者亂翻, 找到原來的文章 Jeff Bezos Banned PowerPoint in Meetings. His Replacement Is Brilliant一看, 原來這三個要素是 "ethos, logos, and pathos.", 我會翻譯為『中心思想、邏輯與感染力』, 譯者顯然把 "logos" 當成 "logo+s", 所以翻成『標誌』, 也把 "pathos" 隨便查字典就譯為『悲觀情緒』, 完全不顧邏輯性, 真的是用自己的翻譯打臉自己的譯文。

附記:數位時代的文章是從科技新報而來, 但顯然沒有人審稿。