Arduino 的 Serial
提供有 parseInt
方法可以從序列埠讀取整數, 不過如果不理解它的實作方式, 可能會讓你的程式卡住。
parseInt 方法的處理流程
parseInt
的處理流程是這樣的:
-
首先等待負號 (減號) 或是數字出現:它會去偷看 (peek) 收到的下一個字元, 如果是負號或是數字, 就會當成是輸入整數的開頭, 進入步驟 2;如果不是負號或數字, 就依據
lookahead
參數的設定決定處理方式:lookahead 參數 做法 SKIP_ALL(預設) 讀入該字元並丟棄, 重新等待下一個字元 SKIP_NONE 不讀入字元, 該字元會留在序列埠的暫存區, 直接傳回 0 SKIP_WHITESPACE 若是空格、定位、換行等空白類字元, 就讀入該字元並丟棄, 重新等待下一個字元, 否則不讀入字元, 直接傳回 0 如果依照上述規定一直等不到負號或是數字, 就會在
Serial.setTimeout
設定的逾時時間 (預設 1000 毫秒) 後強制停止, 並傳回整數 0。 -
一旦看到負號或是數字後, 就會不斷讀入並等待下一個數字, 一直到等待逾時或是看到下一個字元不是數字為止, 然後傳回目前解析出的整數。如果負號之後就沒有數字, 會傳回 0。
它的作法一樣是去偷看收到的下一個字元, 如果是數字, 就會讀取該字元, 並與已經讀到的數字合併計算整數植;如果下一個字元不是數字, 就會依據 ignore 參數指定的字元, 決定是否要讀入該字元並丟棄, 例如, 若
ignore
指定為,
, 就可以在數字之間加入英文逗號。如果沒有傳入ignore
參數, 就不能在數字之間夾雜非數字。
實際測試
以下是我們的測試程式:
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
}
void loop() {
// put your main code here, to run repeatedly:
Serial.println(Serial.parseInt());
}
測試時我們讓序列埠監控視窗加入時間戳記, 以便確認是因為逾時還是真的收到數字解析出整數, 並且設定沒有斷行字元, 不會在輸入資料後自動加上換行字元。一開始執行結果如下:
10:30:37.760 -> 0
10:30:38.771 -> 0
10:30:39.730 -> 0
10:30:40.733 -> 0
10:30:41.743 -> 0
10:30:42.783 -> 0
10:30:43.761 -> 0
10:30:44.756 -> 0
你可以看到每隔 1 秒會噴出 0, 這是因為沒有輸入任何資料, 所以在前述步驟 1 會因為等不到負號或是數字而逾時傳回 0。如果輸入 "123AB456", 結果如下:
10:33:27.422 -> 0
10:33:27.768 -> 123
10:33:28.741 -> 456
10:33:29.790 -> 0
你會看到先解析出 123, 這是因為遇到 'A', 並不是數字, 所以傳回目前收到的 "123" 解析出的整數 123。等到下一次叫用 parseInt
時, 因為 lookahead
預設為 SKIP_ALL
, 所以步驟 1 會將 "AB" 讀入並且丟棄, 直到 '4' 進入步驟 2。你可以注意到最後解析出 456 的時間與 123 相差 1 秒, 這是因為讀取 '6' 之後等不到下一個字元逾時的緣故。
如果輸入 "123AB456CD789", 就會看到類似的結果, 最後解析出來的整數 789 也是等到逾時才出現, 所以和前面的整數 456 相隔 1 秒, 但是 123 和 456 幾乎是同時解析出來:
10:41:46.813 -> 0
10:41:47.745 -> 123
10:41:47.745 -> 456
10:41:48.726 -> 789
10:41:49.731 -> 0
如果你希望最後的整數不要相隔 1 秒才出現, 可以在序列埠監控視窗設定換行, 確保在最後的數字之後加上換行字元, 這樣就可以在讀入最後一個數字後因為看到不是數字的換行字元而停止等待下一個字元, 立即解析整數, 這種情況下輸入同樣的內容結果如下:
10:44:29.415 -> 0
10:44:29.991 -> 123
10:44:29.991 -> 456
10:44:29.991 -> 789
10:44:30.973 -> 0
你可以看到三個整數幾乎同時解析出來了。
使用 ignore 參數
如果你習慣輸入帶有百分位逗號的數值, 就可以利用 ignore
參數指定, 我們把程式碼改成這樣:
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
}
void loop() {
// put your main code here, to run repeatedly:
Serial.println(Serial.parseInt(SKIP_ALL, ','));
}
注意到要使用 ignore
參數就必須要指定 lookahead
參數。以下保持剛剛序列埠監控視窗設定加入換行字元, 輸入 "-1,234AB12-3", 結果如下:
10:51:42.343 -> 0
10:51:42.905 -> -1234
10:51:42.905 -> 12
10:51:42.905 -> -3
10:51:43.914 -> 0
你可以看到 "-1,234" 可以正確解析為 -1234, 但是後面的 "12-3" 就會因為不能在數字間加入負號而被切開成兩個整數了。
使用 lookahead 參數
了解 parseInt
的運作原理後, 就必須小心使用, 如果指定 lookahead
參數為 SKIP_WHITESPACE
, 那步驟 1 就只會捨棄空白類字元, 我們修改一下程式碼進行測試:
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
}
void loop() {
// put your main code here, to run repeatedly:
Serial.println(Serial.parseInt(SKIP_WHITESPACE));
}
輸入 "123 456A789" 測試結果如下:
11:01:43.291 -> 0
11:01:44.297 -> 0
11:01:45.278 -> 0
11:01:46.282 -> 0
11:01:46.542 -> 123
11:01:46.542 -> 456
11:01:46.542 -> 0
11:01:46.542 -> 0
11:01:46.542 -> 0
11:01:46.542 -> 0
11:01:46.542 -> 0
你可以看到讀取 "123" 後遇到不是數字的空格所以解析出 123, 而 "456" 前面雖然有空格, 但是因為是 SKIP_WHITESPACE
, 所以這些空格在步驟 1 會被丟棄, 解析出 456。但是到了 "A789" 時, 因為 'A' 不是空白類字元, 所以不會被丟棄, 直接傳回 0, 而且因為 'A' 沒有被讀入, 仍然保留在序列埠的暫存區內, 所以之後每次叫用 parseInt
時, 在步驟 1 就會因為看到 'A' 而直接傳回 0, 因此之後的 0 就會一直不斷地連續噴出來。到了這裡, 即使再輸入其他數字也無法被讀讀入解析。
如果把 lookahead
設為 SKIP_NONE
, 那就會更慘, 只要不是數字就會卡住了, 這部分就留給大家自己測試。Serial
還有一個類似的方法 parseFloat
, 運作方式也是一樣, 只是步驟 1 可以是以小數點開頭, 而步驟 2 可以出現一次小數點而已, 其餘的用法與注意事項都與 parseInt
相同。
Top comments (0)