Learning Objectives
- 說出鮑率、資料位元、同位元、停止位元的意思,算出 8N1 每秒能傳幾個位元組
- 用 pyserial 開啟序列埠、送出指令、讀回一行回應
- 分辨位元組(bytes)與字串(str),會用 encode() 與 decode() 轉換
- 知道終止字元與逾時的作用,能判斷「讀到空的 b''」的可能原因
最簡單的一條線
序列通訊(serial communication)把資料一個位元接一個位元送出去。電腦的 COM 埠(RS-232)、微控制器的 UART、 很多儀器背後的 DB9 接頭都是這一類。它的線很少、規則簡單,所以到現在還很常見:老儀器、溫度控制器、條碼掃描器、 開發板的除錯輸出都在用。
序列通訊沒有共同的時脈線,雙方要事先約好速度與格式,才能正確地切出每一個位元:
| 參數 | 意思 | 常見值 |
|---|---|---|
| 鮑率(baud rate) | 每秒傳幾個位元(符號) | 9600、19200、115200 |
| 資料位元 | 每個字元幾個位元 | 8 |
| 同位元(parity) | 簡單的錯誤檢查:無(N)、偶(E)、奇(O) | N |
| 停止位元 | 每個字元結尾的「休息」位元 | 1 |
「9600 8N1」就是鮑率 9600、8 個資料位元、無同位元、1 個停止位元,也是儀器最常見的預設值。 每個字元前面還有 1 個起始位元,所以 8N1 傳一個位元組要 1+8+1=10 個位元,9600 鮑率每秒約可傳 960 個位元組。
RS-232 用正負電壓表示 0 與 1(常見範圍是 ±3~±15 V),和微控制器 3.3 V 的 UART 腳位不同,不能直接相接,中間要有轉換晶片或 USB 轉序列轉接線。
pyserial:用 Python 開序列埠
Python 用 pyserial 套件(pip install pyserial,匯入名稱是 serial)控制序列埠:
import serial
ser = serial.Serial("COM3", 9600, timeout=1) # Windows 是 COM3,Linux 常見 /dev/ttyUSB0
ser.write(b"*IDN?\n") # 送出:一定要是位元組(bytes)
reply = ser.readline() # 讀到換行為止,回傳位元組
ser.close()
三個重點:
- 送出與收到的都是位元組(
b"..."),不是字串。字串要用.encode()轉成位元組,收到的位元組用.decode()轉回字串。 - 終止字元:儀器靠一個特殊字元(常見是換行
\n,有些是\r\n或\r)知道「一個指令結束了」,回應也用它結尾。 本課的電錶收\n、回\r\n。 - 逾時(timeout):
readline()最多等幾秒。時間到還沒收到換行,就回傳目前收到的內容,什麼都沒收到時是空的b''。 不設 timeout 的話,對方不回應時程式會一直卡住。
逐步看:第一次和電錶對話
看下方通訊紀錄:先「開啟序列埠 COM3 9600」,然後 → 送出 *IDN?\n、← 收到電錶的回答。注意第 10 行印出的是位元組(前面有 b、結尾有 \r\n)。
讀到 b'' 的時候
序列通訊最常見的問題是 readline() 回傳空的 b'':程式等到逾時,什麼都沒收到。常見原因:
| 原因 | 發生什麼事 | 怎麼辦 |
|---|---|---|
| 鮑率不一致 | 對方切錯位元,收到的是亂碼,不會回應 | 查儀器的設定選單或手冊,兩邊設成一樣 |
| 少了終止字元 | 對方還在等指令結束 | 指令結尾加上 \n(或手冊規定的字元) |
| 送的不是查詢 | 設定類指令(例如 CONF:VOLT)本來就不會回應 |
只有查詢(結尾有 ?)才讀回應 |
| 接錯埠、線沒接、TX/RX 接反 | 對方根本沒收到 | 用 comports() 確認埠名,檢查接線 |
開啟不存在的埠(例如 COM9)則會直接丟出 serial.SerialException。下一個範例示範前兩種問題。
逐步看:鮑率錯了、終止字元忘了
同樣送 *IDN?,先用錯誤的鮑率,再忘記換行。注意虛擬時間:每次 readline() 等不到回應,就要等滿逾時 0.5 秒。
逐步看:字串與位元組、把讀值轉成數字
先故意把字串直接傳給 write(),看 pyserial 怎麼拒絕;再用 encode() 送出量電阻的指令,把收到的位元組轉成數字。
小測驗:序列通訊的參數
「19200 8N1」中的 N 是什麼意思?
鮑率 115200、8N1,每秒最多大約可以傳多少個位元組?
電腦送出 *IDN?\n,但鮑率設成 115200,電錶是 9600。readline()(timeout=1)會怎樣?
小測驗:位元組與字串
下面的程式會印出什麼?
raw = b"+2.50000E+00\r\n"
text = raw.decode().strip()
print(len(raw), len(text), float(text) * 2)
下面哪一行能正確送出指令 MEAS:VOLT:DC??(ser 已經開啟)
重點整理
- 序列通訊雙方要事先約好鮑率與資料格式(常見 9600 8N1);8N1 每個位元組 10 個位元。
- RS-232 用正負電壓,不能直接接微控制器的 3.3 V UART。
- pyserial:
serial.Serial(埠, 鮑率, timeout=秒)、write(位元組)、readline()、close();list_ports.comports()列出埠。 - 送出與收到的都是位元組:
"文字".encode()→ 位元組,b"...".decode()→ 字串。 - 指令結尾要有終止字元(常見
\n);設timeout避免卡住。讀到b'':檢查鮑率、終止字元、是不是查詢、接線。
序列埠要自己處理位元組、終止字元與逾時;下一課先學儀器聽得懂的指令語言 SCPI,第 06 課再用 PyVISA 把這些細節交給 VISA 處理。