星期五, 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 上也有人確認我的疑問