:從接線到MicroPython調試全攻略)
串口通信這四個字在嵌入式項目里幾乎天天被掛在嘴邊。Pico 作為一塊幾十塊錢的開發(fā)板UART 資源雖然不算多但足夠應付絕大多數(shù)傳感器、串口屏、舵機控制板和數(shù)據(jù)采集場景。我最早接觸 Pico 串口的時候以為只是發(fā)個字符串的事結果被電平轉換、波特率誤差、數(shù)據(jù)粘包挨個錘了一遍才把通信調穩(wěn)定。這篇文章不打算從零講晶體管電平而是把所有和 Pico 串口實戰(zhàn)相關的硬件細節(jié)、MicroPython 編程寫法、調試工具用法直接擺出來幫你跳過我已經(jīng)走過的彎路。不管你是剛拿到板子的新手還是正在做項目的開發(fā)老手這篇內容應該都能給你省下幾個調試的夜晚。我在寫這篇內容時參照了身邊好幾個真實項目的經(jīng)驗也把社區(qū)里討論比較多的串口問題整理了進來。整體思路是先講硬件選型和接線再講 MicroPython 里怎么寫代碼接著是調試工具怎么選怎么用最后是一份排查實錄。每部分盡量說人話把“為什么這么做”也交代清楚。1. 串口通信在 Pico 項目里的定位1.1 為什么是串口而不是 I2C 或 SPI很多剛接觸 Pico 的朋友會問我需要通信的時候到底選串口 UART、I2C 還是 SPI簡單說I2C 和 SPI 都是板級總線適合芯片與芯片之間短距離、高速率傳輸而 UART 是設備與設備之間最通用的通信方式不需要時鐘線兩根信號線就能跑大量外設模塊——GPS、指紋模塊、激光雷達、串口屏、舵機驅動板——默認都是 UART 接口。所以 Pico 上做項目往外接的第一種通信大概率就是串口。UART 通信的原理不復雜發(fā)送方把并行數(shù)據(jù)轉成串行位流按約定的波特率、數(shù)據(jù)位、校驗位、停止位逐位發(fā)送接收方在正確的位時間上采樣把位流重新拼成字節(jié)。它沒有時鐘線所以收發(fā)雙方必須提前約定好“節(jié)奏”這也是為什么串口調試里波特率一旦出錯就會完全收不到數(shù)據(jù)。1.2 適用場景與常見項目形態(tài)在我接觸到的實際項目里Pico 串口通信大概有這幾種典型形態(tài)傳感器數(shù)據(jù)采集上傳Pico 讀取溫濕度、氣壓、IMU 等數(shù)據(jù)通過 UART 發(fā)給上位機顯示或存檔。設備控制上位機通過串口下發(fā)指令Pico 解析后控制舵機、電機、LED很多機器人項目就是這種架構。模塊對接接串口屏、GPS、藍牙模塊、4G 模組這類模塊本身只暴露 UART 接口。調試信息輸出把 Pico 運行狀態(tài)打印到串口終端方便實時觀察變量和程序執(zhí)行流程。這些場景和我早期做 51 單片機串口通信、STM32 串口通信實驗時用的思路是相通的但 Pico 的 MicroPython 環(huán)境讓原型開發(fā)速度快不少不用管寄存器配置一行UART(0, baudrate115200)就把外設打開了。對于需要快速驗證方案的項目這種開發(fā)效率優(yōu)勢非常明顯。2. Pico 的 UART 硬件特性與接線2.1 UART0、UART1 外設資源與引腳映射RP2040 芯片內部只有兩個 UART 外設UART0 和 UART1。每個 UART 在物理上可以映射到多組引腳MicroPython 對 Pico 官方保留了默認映射UART0TX GP0RX GP1默認UART1TX GP4RX GP5默認這里要注意一點默認映射只是 MicroPython 的約定你完全可以通過txPin(x), rxPin(y)指定其他引腳。但 UART0 和 UART1 在 RP2040 上掛了不同的引腳組具體哪些引腳能映射到哪個 UART建議查官方 datasheet 的 GPIO 功能表。我在實際項目里習慣固定使用默認引腳方便查閱和復用除非用到的別的引腳被其他外設占用才會重新映射。每個 UART 還支持硬件流控 RTS/CTS在 Pico 上默認沒暴露如果接的是帶流控的模塊比如某些藍牙模組需要確認固件和引腳是否支持。大多數(shù)場景用不到流控把 TX、RX、GND 三根線接好就能跑。2.2 電氣特性3.3V 電平與魔改 5V 串口的風險這是 Pico 串口通信最容易出問題的地方。Pico 的 GPIO 是 3.3V 電平忍耐輸入極限大約是 3.6V 左右。很多傳統(tǒng)串口設備是 5V TTL 電平比如老一點的 GPS 模塊、51 單片機擴展板、某些 STM32 開發(fā)板上的 5V 串口。如果你直接拿 5V 設備的 TX 接到 Pico 的 RX長期使用有燒壞引腳的風險。我見過不少人圖省事直接接上去“能用”然后某天引腳就廢了。正確的做法是加電平轉換芯片比如 TXS0108E、BSS138 雙 MOS 方案或者用電阻分壓把 5V 降到 3.3V 再接 Pico。反過來Pico 的 TX 接 5V 設備的 RX 一般問題不大因為很多 5V 設備把高電平閾值設在 2.0V 以上3.3V 也能識別但最好也做轉換保證電平標準一致。如果接的是 RS232 電平的 DB9 串口那就更危險了。RS232 的空閑態(tài)是負電壓正負擺幅能達到 ±12V 左右直接接 Pico 基本必燒。這時候必須用 MAX3232 這一類的 RS232 轉 TTL 芯片做轉換。我常跟身邊的人說接串口先看對方是 TTL 還是 RS232 電平再看邏輯電平是 3.3V 還是 5V這兩個判斷能避開 90% 的接線問題。2.3 多串口擴展與 USB 轉串口接線參考Pico 原生只有兩個 UART如果項目里需要更多串口有幾條路可以走用 PIO 實現(xiàn)軟件串口。RP2040 的 PIO 非常靈活社區(qū)有人實現(xiàn)了 PIO UART可以擴展出多個串口但 CPU 占用和穩(wěn)定性需要測試。用 I2C 轉 UART 芯片比如 SC16IS752把額外串口掛到 I2C 總線上代碼里通過驅動讀寫。某些社區(qū)固件開始支持 USB Host接上 USB 轉串口適配器就能多出串口。這個方案依賴固件成熟度穩(wěn)定性需要自己驗證。在做 Pico 和電腦通信時常用 USB 轉 TTL 模塊CH340、CP2102、FT232 等把 Pico 的 UART 接到電腦 USB 口。接線就三根線Pico 的 TX 接模塊的 RXPico 的 RX 接模塊的 TX兩邊 GND 共地。注意共地這件事特別重要不共地的話信號電平?jīng)]有參考點經(jīng)常出現(xiàn)時通時不通的問題。3. MicroPython 串口編程實操3.1 固件準備與開發(fā)環(huán)境要在 Pico 上跑 MicroPython先得把固件刷進去。去官方下載最新的 .uf2 固件按住 Pico 板子上的 BOOTSEL 鍵插入 USB會彈出一個名為 RPI-RP2 的 U 盤把 .uf2 文件拖進去就自動刷好了。之后用 Thonny 作為 IDE 比較省心它對 Pico 的支持很完善可以直接在編輯區(qū)寫代碼點運行就能在板子上執(zhí)行還能在下方 Shell 里看 print 輸出。當然如果你不喜歡 Thonny也可以用 VS Code 加 MicroPico 插件或者在命令行直接用 mpremote 連接設備執(zhí)行腳本。我個人調試串口時用 Thonny 比較多因為它內置的文件管理方便修改main.py后按 CtrlD 軟復位就能生效。3.2 UART 對象初始化參數(shù)到底怎么選MicroPython 里初始化 UART 的代碼很簡單from machine import UART, Pin # UART0TXGP0, RXGP1默認引腳 uart0 UART(0, baudrate115200, txPin(0), rxPin(1), bits8, parityNone, stop1)這幾個參數(shù)中baudrate 是最需要對齊的兩側必須一致。bits 一般選 8parity 為 Nonestop 為 1這也是絕大多數(shù)串口設備的默認配置。如果連接的是老設備或者特殊模塊需要看對方手冊確認是否用了 7 位數(shù)據(jù)位、偶校驗或者 2 位停止位。如果兩側參數(shù)不一樣能收到數(shù)據(jù)但解出來的字節(jié)是錯的。有一點要提醒UART(0, ...)這種寫法在創(chuàng)建時就會初始化外設所以不需要再調用init()。如果你后面想改參數(shù)可以調用uart0.init(baudrate9600)重新配置外設不用重新創(chuàng)建。3.3 基本收發(fā)write/read/readline 的正確用法MicroPython 的 UART 對象主要有這幾個方法write(data)發(fā)送數(shù)據(jù)data可以是 bytes 或 str。read(n)讀取最多 n 個字節(jié)不加參數(shù)則讀取全部可讀數(shù)據(jù)。readline()讀取一行以換行符結束。any()返回接收緩沖區(qū)中的字節(jié)數(shù)可用來判斷是否有數(shù)據(jù)。一個輪詢收發(fā)的例子from machine import UART, Pin import time uart UART(0, baudrate115200, txPin(0), rxPin(1)) while True: if uart.any(): data uart.read() print(recv:, data) uart.write(becho: ) uart.write(data) time.sleep_ms(10)這里我習慣把any()放在主循環(huán)里輪詢最簡單的場景夠用。但要注意read()讀取的內容可能不是完整的一幀數(shù)據(jù)如果上位機一次發(fā)來多個字節(jié)可能被拆成兩次讀出所以最好按協(xié)議幀來解析而不是直接判斷“讀到了就是一條完整指令”。這個后面會展開講。3.4 按行解析協(xié)議以串口控制舵機為例用 Pico 控制舵機是很常見的入門項目很多朋友做機械臂、云臺都會用到。串口控制舵機的思路是上位機通過 UART 發(fā)來類似#90\n的指令Pico 解析出角度值再通過 PWM 輸出控制舵機轉到對應角度。舵機 PWM 信號周期一般用 20ms50Hz高電平脈沖寬度 0.5ms~2.5ms 對應 0°~180°。Pico 的 PWM 模塊是 16 位的要算占空比。from machine import UART, Pin, PWM import time uart UART(0, baudrate115200, txPin(0), rxPin(1)) servo PWM(Pin(15)) servo.freq(50) def angle_to_duty(angle): # 0.5ms / 20ms 2.5% - 65535 * 0.025 1638 # 2.5ms / 20ms 12.5% - 65535 * 0.125 8192 return int(1638 (angle / 180.0) * (8192 - 1638)) while True: if uart.any(): line uart.readline() if line: line line.strip() print(cmd:, line) if line[:1] b#: try: angle int(line[1:]) angle max(0, min(180, angle)) servo.duty_u16(angle_to_duty(angle)) uart.write(OK angle%d\n % angle) except ValueError: uart.write(ERR bad number\n) else: uart.write(ERR unknown cmd\n) time.sleep_ms(10)這里有兩點經(jīng)驗第一解析協(xié)議時一定要做異常處理。串口本質上是不可靠信道什么亂七八糟的字節(jié)都可能收到如果int()轉換失敗沒有處理程序很容易崩在解析處。第二用readline()按行解析時協(xié)議里的換行符必須和發(fā)送方保持一致。如果上位機發(fā)的是\r\nPico 這邊可以先用line.replace(b\r, b)清理一下再做解析否則b90\r轉 int 會報錯。3.5 使用 IRQ 和 FIFO 處理不定長數(shù)據(jù)在實時性要求高的項目里主循環(huán)輪詢any()可能不夠及時。Pico 的 MicroPython 固件支持 UART 的 IRQ 中斷可以用uart.irq(triggerUART.RX_ANY, handleron_rx)注冊回調函數(shù)數(shù)據(jù)到達時自動觸發(fā)。from machine import UART, Pin import time uart UART(0, baudrate115200, txPin(0), rxPin(1)) rx_buf bytearray() def on_uart_rx(u): while u.any(): rx_buf.append(u.read(1)[0]) uart.irq(triggerUART.RX_ANY, handleron_uart_rx)使用中斷時要注意回調函數(shù)里不要做耗時操作比如打印、大量字符串拼接、網(wǎng)絡請求這些都應該放到主循環(huán)處理。中斷里只負責把數(shù)據(jù)快速搬進緩沖區(qū)然后在主循環(huán)里檢查緩沖區(qū)解析完整幀。如果數(shù)據(jù)量很大比如每秒幾千字節(jié)bytearray 的append效率也夠用但要注意緩沖區(qū)長度。MicroPython 在 Pico 上給 UART 分配了 FIFO 和緩沖區(qū)空間處理不過來時數(shù)據(jù)會丟所以協(xié)議設計上最好有幀頭、幀長、校驗和解析失敗就丟棄重新同步。3.6 與宿主機聯(lián)動Windows 下用串口連接 VMware 中的 Linux這個需求在調試場景里很常見。比如你的上位機跑在 Windows但編譯和測試程序在 Linux 虛擬機里想讓虛擬機里的 Python 直接訪問 Pico 串口。在 VMware 里可以給虛擬機添加一個串行端口選擇“使用命名管道”例如\\.\pipe\com_1然后在 Windows 宿主機上用一些工具把物理 COM 口轉發(fā)到這條命名管道這樣虛擬機里的 Linux 看到的/dev/ttyS0就對應著 Pico。也可以在宿主機上用 Python 的 pyserial 寫個十幾行的轉發(fā)腳本把串口字節(jié)流原樣搬到管道另一端。這種方式比每次都在 VMware 里單獨映射 USB 設備要靈活尤其當 Pico 不是 USB 直接連接而是通過 USB 轉 TTL 模塊接入時。如果不想用虛擬機直接在 Windows 上用 pyserial 讀取 Pico 串口數(shù)據(jù)也很方便。安裝好 pyserial 后一行代碼就能列出可用串口import serial.tools.list_ports for p in serial.tools.list_ports.comports(): print(p.device, p.description)Windows 下 Pico 的 USB 轉串口設備通常顯示為 COM3、COM5 之類的編號具體是哪個可以用這個列表確認。4. 調試工具選型與實戰(zhàn)技巧4.1 串口調試助手怎么選串口調試助手的工具很多我用過的場景里比較順手的有PuTTY老牌終端支持串口連接界面樸素但穩(wěn)定適合純文本收發(fā)。sscom經(jīng)典的串口調試助手界面友好支持定時發(fā)送、十六進制顯示、文件發(fā)送。VOFA帶波形顯示看傳感器的連續(xù)數(shù)據(jù)流非常方便可以自定義協(xié)議。minicom/picocomLinux 終端下的串口工具適合在虛擬機或樹莓派上直接調試。選工具的標準在我看來有兩個一是能不能方便地切換十六進制/ASCII 顯示二是能不能定時發(fā)送和保存日志。這兩點決定了你在解析復雜協(xié)議時能不能快速定位問題。這里還要提一下 Unity 串口通信的場景。如果你用 Unity 做上位機讀取 Pico 數(shù)據(jù)不要在 Unity 的主線程里直接讀寫 SerialPort 的ReadLine()或者長時間阻塞等待否則 Unity 的渲染線程會被卡住嚴重時會出現(xiàn)類似渲染管線異常之類的報錯。正確做法是開一個后臺線程讀串口把數(shù)據(jù)放入隊列或共享變量主線程在Update()里取數(shù)據(jù)這樣畫面才不卡串口讀寫也更穩(wěn)定。4.2 邏輯分析儀是排查串口問題的神器很多時候軟件看起來沒問題但就是收不到數(shù)據(jù)。這時候我最推薦的調試工具是邏輯分析儀。我手里一直放著 Saleae 邏輯分析儀的 16 通道版本平時接上 TX、RX、GND 三根線在軟件里選擇 UART 解碼協(xié)議設置好波特率就能看到波形和解析出來的數(shù)據(jù)幀。邏輯分析儀能幫你確定三件事發(fā)送端到底有沒有在發(fā)數(shù)據(jù)波形上有沒有脈沖。數(shù)據(jù)的內容是什么和發(fā)送的字節(jié)是否一致。實際波特率和設置的波特率有沒有偏差。解碼時要注意采樣率至少要高于波特率的 4 倍否則采樣點不夠容易解碼出錯。我通常用 10M 采樣率去測 115200 的串口非常充裕。如果買的是其他品牌只要軟件支持 UART 協(xié)議解碼用法都差不多。4.3 虛擬串口與自動化測試在調試上位機程序時把真實硬件和軟件耦合在一起有時候很痛苦。比如想測試協(xié)議解析邏輯但硬件還沒到位。這時可以用虛擬串口工具比如 com0com 在 Windows 上創(chuàng)建一對虛擬 COM 口把 COM5 和 COM6 連在一起一個程序往 COM5 寫數(shù)據(jù)另一個程序就能從 COM6 讀到相同的數(shù)據(jù)。由此可以搭一套自動化測試環(huán)境用 Python 腳本模擬設備端向虛擬串口發(fā)送預定好的測試幀同時校驗上位機返回的應答。跑回歸測試時不用真實硬件效率高很多。等到真實硬件到位后把串口名換掉就能跑同樣的測試用例。如果是在 Linux 下虛擬串口可以用socat -d -d pty,raw,echo0 pty,raw,echo0創(chuàng)建一對偽終端用法類似。4.4 用 Modbus/Python 腳本擴展調試能力串口調試不只限于收發(fā)數(shù)據(jù)。很多工業(yè)傳感器、電表、道閘控制器用的是 Modbus RTU 協(xié)議這時候用串口調試助手直接發(fā) Modbus 幀也能調但效率很低。我一般會用 Python 的minimalmodbus或pymodbus庫直接按寄存器地址讀寫調試起來清晰很多。其實不只是 Modbus任何自定義協(xié)議都可以用 Python 腳本做上位機模擬。比如先用 pyserial 寫好數(shù)據(jù)幀封裝、CRC 校驗、發(fā)送重試邏輯再配合日志功能就能快速驗證 Pico 端協(xié)議解析的邊界條件。這種腳本后期還能直接轉成正式的上位機程序雛形省不少開發(fā)時間。5. 常見問題與排查實錄5.1 波特率9600 能通、4800 不通是怎么回事有朋友遇到過串口波特率設置為 9600 能正常通信但改成 4800 就完全沒有數(shù)據(jù)的情況。這個現(xiàn)象往往不是“波特率越低越容易通”這個直覺能解釋的根源在于兩側的實際波特率誤差。UART 是異步通信接收端通過起始位同步后在每一個位時間的中心點采樣。如果實際波特率和設置值之間存在偏差累積到幀末的停止位時偏差會越來越大。絕大多數(shù) UART 外設能容忍大約 ±2%~3% 的波特率誤差。如果 A 設備實際波特率是 9615B 設備按 9600 收誤差只有 0.15%沒問題但如果把 B 設備設成 4800A 設備仍按 9600 發(fā)位采樣點完全錯位自然收不到。另一種常見情況是兩塊板子都聲稱是 4800但其中一塊用了精度不高的時鐘源或者代碼里波特率計算有誤導致實際差距超過容差。Pico 的時鐘來自 USB PLL精度很高不大會出現(xiàn)這種問題但如果你接的模塊內部是 RC 振蕩器就有可能出現(xiàn)“9600 能通、4800 不能通”的反直覺現(xiàn)象。解決辦法是用邏輯分析儀實測一下發(fā)送端波形的實際波特率或者降低通信速率到一個雙方都真正支持且誤差足夠小的值。5.2 亂碼、丟字節(jié)與緩沖區(qū)溢出亂碼最常見的原因就是收發(fā)雙方波特率或數(shù)據(jù)格式不一致。先確認兩側的波特率、數(shù)據(jù)位、停止位、校驗位完全一致。其次檢查共地GND 不連時信號電平?jīng)]有參考點容易出現(xiàn)隨機亂碼和間歇性丟字節(jié)。丟字節(jié)則多半和接收端處理速度有關。如果上位機 600ms 才讀一次串口而 Pico 以高速率持續(xù)發(fā)送緩沖區(qū)一旦溢出新的數(shù)據(jù)就會覆蓋舊數(shù)據(jù)。MicroPython 的 UART 內部有緩沖區(qū)在 Pico 上一般有 256 字節(jié)左右如果你用read()一次性讀完還好但若沒有及時讀取FIFO 滿了之后數(shù)據(jù)就丟了。我習慣的做法是協(xié)議幀盡量短或者發(fā)送端加適當延時接收端用中斷或高頻輪詢盡快把數(shù)據(jù)搬到自己的解析緩沖區(qū)同時給每幀數(shù)據(jù)加上校驗和或 CRC解析失敗就丟棄重發(fā)。工業(yè)上常用的做法是幀頭加長度加數(shù)據(jù)加 CRC32雖然看起來有些冗余但在真實環(huán)境中非常有價值。5.3 收不到數(shù)據(jù)從硬件到軟件逐步排查收不到數(shù)據(jù)時別急著改代碼按下面這個順序排查最快用邏輯分析儀看 Pico 的 TX 引腳到底有沒有波形。如果沒有說明代碼沒發(fā)出去或者引腳接錯。檢查接線是否交叉。Pico 的 TX 要接對方設備的 RXPico 的 RX 要接對方設備的 TX。很多人把 TX-TX 直連自然收不到。確認共地。GND 不接或者接觸不良信號電平?jīng)]有參考收不到是常見現(xiàn)象。用電腦的 USB 轉 TTL 模塊直接測 Pico排除對方設備的問題。檢查代碼里 UART 的引腳參數(shù)是不是寫錯了比如把rxPin(1)寫成了rxPin(0)和 TX 針腳沖突。用串口調試助手發(fā)數(shù)據(jù)給 Pico 時確認助手打開的端口沒錯且 Pico 端程序確實在跑any()輪詢。這套排查順序我基本每次都能定位到問題而且大概率是接線或者引腳配置錯誤真正是芯片壞掉的情況極少。5.4 電平適配問題與設備燒毀防范再強調一次電平問題因為燒壞引腳是“不可逆”的。Pico 的 3.3V 引腳耐壓有限接 5V TTL 設備時必須做電平轉換。我用得最多的是 BSS138 雙 MOS 電平轉換模塊幾塊錢一個支持雙向傳輸I2C 和 UART 都能用。如果只是單向傳輸用電阻分壓也可以但分壓值要算好確保高電平不低于接收端的高電平閾值。RS232 設備則必須走 MAX3232 之類的芯片。很多人把電腦背后的 9 針 DB9 串口當成普通 TTL 串口用結果一接上 Pico 就燒了。DB9 使用的 RS232 電平是負邏輯完全不能直接對接 3.3V TTL。另外熱插拔也要小心。串口線最好在斷電狀態(tài)下接線尤其是和外部電源模塊相連時。我見過不少設備損壞案例是在設備帶電時插拔串口線瞬間的電壓尖峰把引腳打壞了。穩(wěn)妥的做法是先接 GND再接 TX、RX最后才上電。串口通信看起來只是兩根線的事但真正調穩(wěn)了你才會明白背后是波特率、電平標準、緩沖管理和協(xié)議設計的一整套配合。我在 Pico 上做串口項目的過程中最大的體會就是不要低估接口本身的坑寧可多花十分鐘接好電平轉換和共地也不要圖省事直接懟上去。最后再分享一個小技巧養(yǎng)成保存串口日志的習慣。無論是用串口助手的日志功能還是在 Python 腳本里把收發(fā)數(shù)據(jù)記錄到文件調試復雜協(xié)議時這些日志就是最寶貴的線索。很多看起來隨機出現(xiàn)的問題翻日志時往往能發(fā)現(xiàn)固定的規(guī)律。這篇文章里寫到的每個坑幾乎都是從日志里一點點扒出來的。希望這些經(jīng)驗能幫你少走彎路早點把串口通信跑通跑穩(wěn)。