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