:從協(xié)議時序到FPGA實現(xiàn)與調(diào)試經(jīng)驗)
簡介這是一份基于STM32 HAL庫的串口接收數(shù)據(jù)參考代碼包面向使用STM32F405/STM32F4系列開發(fā)串口通信功能的嵌入式開發(fā)者重點演示如何接收ASCII碼數(shù)據(jù)并以回車作為幀結(jié)束符進行判斷。壓縮包共存85個文件以50個h頭文件與22個c源文件為主體涵蓋HAL庫驅(qū)動、主程序、UART/IO驅(qū)動及中斷處理附帶.ioc工程配置、.uvprojx MDK工程、.hex固件與調(diào)試配置可導入Keil直接編譯驗證。包體僅647KB結(jié)構(gòu)緊湊適合已有基礎、希望快速參照串口接收邏輯的STM32學習者。目前已有2127人學習下載參考價值得到一定驗證。通過該資源可理清串口接收的初始化、中斷回調(diào)、幀結(jié)束判定與緩存處理流程也能借助工程文件快速搭建自己的收發(fā)調(diào)試環(huán)境。 打開這個5 UARTRecIT.zip的時候我第一反應是這又是一份從哪個嵌入式項目里摳出來的UART接收器源碼。但認真讀完工程里的注釋和幾個版本的改動記錄我意識到這個包比想象中值得拆解。名字里的 RecIT 就是 Receiver 的縮寫前面那個5代表這是第五次迭代——從最早純仿真用的簡單接收模塊到后來帶上過采樣、RS485方向控制、異步FIFO的完整收發(fā)鏈路每一版都在解決實際問題。對于正在學串口通信、打算在FPGA或單片機上自己寫UART接收邏輯的朋友來說這個工程就是一套現(xiàn)成的教科書式參考能幫你少走至少兩個月的彎路。1. 項目到底在解決什么問題1.1 這類UART工程包的典型內(nèi)容先說說解壓之后能看到什么。純正的UART接收器工程包目錄結(jié)構(gòu)通常長這樣rtl目錄uart_rec_it.v頂層接收模塊、baud_gen.v波特率發(fā)生器、edge_det.v邊沿檢測模塊sim目錄testbench測試平臺、串口回環(huán)測試腳本doc目錄時序說明、版本變更記錄這個包最值得看的不是代碼量而是第五版相比前面幾版改了什么。從版本記錄來看v1只是裸的采樣邏輯v2加了16倍過采樣v3補了起始位毛刺濾波v4加入RS485方向控制v5才把接收FIFO和狀態(tài)機徹底重構(gòu)了一遍——每一版的修改都對應一個真實踩過的坑。1.2 為什么接收器比發(fā)送器更有講究寫過UART的人都有體會發(fā)送容易接收難。發(fā)送是主動行為你只要按時鐘把數(shù)據(jù)一位一位推出去就行接收完全是被動的你不知道對端什么時候發(fā)數(shù)據(jù)線路上還有噪聲你得在連續(xù)的比特流里準確找到起始位然后穩(wěn)定地采回每一位。這就像你在嘈雜的火車站等一個不告訴具體時刻的朋友得在人群里先認出他招手再一路盯著他走到你面前中間不能看丟。UART接收器設計的核心矛盾就是異步、無時鐘參考、還得抗干擾。別忘了UART之所以叫通用異步收發(fā)器關鍵就在異步兩個字收發(fā)雙方?jīng)]有共享時鐘全靠約定好的波特率和對線路電平變化的捕捉。這個工程里所有的設計細節(jié)本質(zhì)上都是在解決這兩個問題。2. UART協(xié)議核心要素不懂時序就搞不定接收2.1 幀格式與電平邏輯UART一幀數(shù)據(jù)的結(jié)構(gòu)很經(jīng)典空閑狀態(tài)時TX線保持高電平要發(fā)送數(shù)據(jù)時先拉低一個位周期作為起始位然后從最低位開始依次發(fā)送數(shù)據(jù)位一般是8位最后拉高一個位周期作為停止位。有的協(xié)議中間還會帶一個校驗位做奇校驗或偶校驗。我在工程里看到接收模塊有專門的校驗位跳過邏輯而且在配置寄存器里開放了無校驗/奇校驗/偶校驗三檔選擇這個細節(jié)很多人寫接收模塊時會忽略直接固定死無校驗結(jié)果遇到帶校驗的數(shù)據(jù)就全部錯位。這里必須強調(diào)一個新手很容易搞錯的概念UART的TTL電平邏輯是反的。邏輯1對應高電平3.3V或5V邏輯0對應低電平0V。發(fā)送起始位是把線路從高拉低這個下降沿就是接收端一切判決的起點。很多人在示波器上看波形看到起始位是往下掉的就開始懷疑自己是不是接反了——其實這是正常的UART的空閑態(tài)就是高電平。2.2 波特率與采樣時鐘的關系波特率baud rate是每秒傳輸?shù)拇a元數(shù)常見的有9600、115200、921600等。接收端必須知道對方的波特率才能確定每一位持續(xù)多長時間。但實際工程中不能只靠知道還得有一個比波特率快得多的采樣時鐘這就是過采樣的概念。主流做法是16倍過采樣用一個頻率等于波特率16倍的時鐘去采樣RX線。舉個例子115200波特率對應位周期約8.68微秒16倍過采樣時鐘就是1.8432MHz每個位周期內(nèi)會采到16個點。為什么是16倍而不是4倍、8倍折中考慮——倍數(shù)太低抗噪聲和抗相位偏差的能力弱倍數(shù)太高對時鐘頻率要求高FPGA里分頻也麻煩。16倍是一個行業(yè)驗證過的平衡點既能提供足夠的時間分辨率又不會讓邏輯資源爆炸。2.3 UART跟I2C、SPI、USART、CAN到底差在哪這個話題幾乎是每次串口調(diào)試必被問到的。一句話總結(jié)UART是異步串行、點對點、全雙工SPI是同步串行、主從式、全雙工有四根線I2C是同步串行、多主多從、半雙工兩根線靠地址尋址USART是同步/異步可切換的增強版UARTCAN則是差分信號、多主、帶優(yōu)先級仲裁的總線協(xié)議物理層和支持的距離、節(jié)點數(shù)完全不同。我在工程文檔里看到一張對比表整理得相當直觀協(xié)議時鐘方式數(shù)據(jù)線雙工典型場景UART異步TX/RX兩根全雙工調(diào)試串口、GPS、藍牙模塊SPI同步SCKMOSI/MISO/SCK/CS全雙工Flash、SD卡、傳感器I2C同步SCLSDA/SCL兩根半雙工EEPROM、溫濕度傳感器USART同步或異步TX/RX可帶SCK全雙工單片機的增強串口CAN同步位同步CANH/CANL差分半雙工車載、工業(yè)總線理解這些區(qū)別的實際意義是項目選型時就不會拿著UART硬上。比如長距離工業(yè)現(xiàn)場通信UART經(jīng)過RS485轉(zhuǎn)換后也能跑但協(xié)議層沒有多節(jié)點仲裁得自己設計一主多從輪詢?nèi)绻?jié)點多、實時性要求高直接用CAN可能更省事。3. 接收器實現(xiàn)的關鍵細節(jié)從波特率分頻到狀態(tài)機3.1 波特率分頻器的計算與誤差FPGA或單片機上做UART接收第一步是生成過采樣時鐘。系統(tǒng)時鐘50MHz要得到115200波特率的16倍過采樣時鐘分頻系數(shù)計算公式是分頻系數(shù) 系統(tǒng)時鐘頻率 / (波特率 × 16) 50,000,000 / (115,200 × 16) ≈ 27.12取整27后實際產(chǎn)生的采樣頻率是 50,000,000 / 27 ≈ 1.85185MHz對應的實際波特率是 1,851,850 / 16 ≈ 115,740與標準115200的偏差大約是0.47%。這個誤差在可接受范圍內(nèi)因為UART只要雙方波特率偏差不超過±2%左右配合過采樣和中心采樣就能可靠通信。但如果系統(tǒng)時鐘是25MHz直接除以16倍過采樣在115200下就是25,000,000 / (115,200 × 16) ≈ 13.56取整14后偏差會變到2.6%左右跑長幀很容易出錯。工程里專門寫了個校準模塊在初始化時通過測量起始位寬度來動態(tài)修正分頻系數(shù)這一招在FPGA頻率不夠理想時非常管用。3.2 起始位檢測與毛刺過濾接收模塊最關鍵的時序就是檢測起始位。線路空閑時為高電平一旦檢測到下降沿先認為可能有起始位但不能馬上信。工程里v3版本加的邏輯是檢測到下降沿后延時半個位周期即8個過采樣時鐘周期再采一次RX線如果采到的是低電平說明確實是起始位如果已經(jīng)跳回高電平說明只是一個毛刺直接忽略繼續(xù)回到空閑狀態(tài)。這個防抖邏輯類似按鍵消抖的思路就是你說你來了我得等半拍再確認一下你的確還在。3.3 數(shù)據(jù)位的中間采樣策略確認起始位之后進入數(shù)據(jù)采樣階段。每一位持續(xù)16個過采樣時鐘周期理論上在每個位周期的中點附近采樣最安全因為那里離數(shù)據(jù)跳變沿最遠抗信號上升沿緩慢和反射噪聲的能力最強。工程里的狀態(tài)機是這樣的IDLE等待下降沿檢測到后進入STARTSTART延時8個時鐘確認低電平進入DATADATA每16個時鐘采一次樣采集8位進入STOPSTOP等待到停止位結(jié)束回到IDLE有的實現(xiàn)會在每個數(shù)據(jù)位的中段連采三個點用多數(shù)表決來決定這一位的電平比如采到3次里至少2次為高就判定為1。這個工程v5版本也加了類似邏輯在實際通信距離超過2米、線路有點干擾時這種冗余采樣能明顯降低誤碼率。3.4 接收數(shù)據(jù)的后續(xù)處理數(shù)據(jù)位采完并不是結(jié)束。8位數(shù)據(jù)拼接完成后工程里還做了一件重要的事把并行數(shù)據(jù)寫入一個異步FIFO然后由上層邏輯按自己的節(jié)奏讀取。為什么需要FIFO因為UART接收是持續(xù)不斷的事件流而CPU或上層狀態(tài)機可能正在忙別的任務沒有FIFO緩沖的話數(shù)據(jù)一來就得立刻處理處理不及時就丟字節(jié)。另外FIFO深度選擇也有講究。工程里用的是16字節(jié)深度對于典型的調(diào)試串口夠用但如果你接的是GPS這種每秒輸出一大段NMEA語句的模塊建議至少做到64字節(jié)甚至256字節(jié)否則突發(fā)數(shù)據(jù)一來就會溢出。4. 配套硬件與驅(qū)動USB轉(zhuǎn)UART和電平轉(zhuǎn)換別踩坑4.1 FT231X、FT232R驅(qū)動那些事光有FPGA或單板機上的UART邏輯還不夠調(diào)試的時候總得把數(shù)據(jù)接到電腦上看。這時候USB轉(zhuǎn)UART芯片就是剛需。FTDI家的FT232R和FT231X是市面上最常見的兩顆芯片很多開發(fā)板、USB轉(zhuǎn)串口模塊都在用。FT232R是老將支持USB 2.0 Full Speed內(nèi)置EEPROM可配置VID/PID和輸出電平FT231X是后起之秀功耗更低、封裝更小但引腳電壓范圍略有不同設計時要注意。驅(qū)動安裝是最容易卡住新手的一關。FT232R在Windows 10/11下系統(tǒng)有時會自動裝上一個微軟自帶的usbser.sys驅(qū)動表現(xiàn)為設備管理器里能識別到COM口號但打開串口助手時提示無法打開或配置無效——這是因為系統(tǒng)驅(qū)動版本和FTDI的VCP驅(qū)動不兼容。正確做法是去FTDI官網(wǎng)下載最新的VCP驅(qū)動Virtual COM Port Driver安裝前先在設備管理器里把當前驅(qū)動卸載勾選刪除此設備的驅(qū)動程序軟件再重新安裝FTDI官方驅(qū)動。FT231X在Win10下類似如果插上去顯示USB Serial Device而不是USB Serial Port基本就是驅(qū)動不對。Linux下相對省心內(nèi)核自帶ftdi_sio驅(qū)動插上就能出ttyUSB0。但要注意的是Linux下默認可能啟用低延遲模式在高波特率持續(xù)傳輸時反而會丟數(shù)據(jù)用setserial設置一下參數(shù)通常能改善。4.2 3.3V與1.8V電平轉(zhuǎn)換電路現(xiàn)在很多模組和芯片組比如某些高精度GPS模塊、低功耗MCU串口電平是1.8V而FPGA或單片機的IO可能是3.3V。直接對接大概率出問題3.3V的TX輸出高電平2.8V以上對1.8V器件來說可能擊穿反過來1.8V輸出的高電平只有1.8V3.3V器件不一定能識別為邏輯1。正可靠的方案是用專用的電平轉(zhuǎn)換芯片比如TXS0108E、TXB0104這類自動方向感應的轉(zhuǎn)換器。如果不想加芯片也可以用兩個MOS管加電阻搭簡易雙向電平轉(zhuǎn)換電路——這種方法適合低速場景UART在115200波特率下可行但波特率上到921600甚至更高時MOS管電路的上升沿會變緩時序就不達標了。工程文檔里特意標注了電平轉(zhuǎn)換電路的走線要短盡量靠近信號源端否則寄生電容會讓波形失真。4.3 RS485方向控制與收發(fā)切換時序RS485是UART在工業(yè)場景下最常用的物理層補充技術(shù)半雙工、差分傳輸抗共模干擾能力強通信距離能到1200米。工程里的v4版本專門加了RS485方向控制邏輯核心是一根DE/RE信號高電平時發(fā)送、低電平時接收。這里最容易被忽略的是方向切換的時序余量。從發(fā)送模式切到接收模式必須在最后一個停止位發(fā)送完之后至少再等一個位周期再切換方向否則對端回的數(shù)據(jù)起始位會被自己截掉。我在調(diào)RS485時遇到過典型的燈閃一下就沒反應問題排查半天發(fā)現(xiàn)就是DE信號切換太快對方回復的數(shù)據(jù)剛起來就被切斷了。工程里用狀態(tài)機控制DE信號在STOP狀態(tài)結(jié)束后插入2個位周期的延時再拉低DE問題立刻消失。5. 調(diào)試實錄亂碼、丟字節(jié)、驅(qū)動不通怎么查5.1 先看波形再想代碼UART調(diào)不通時第一步永遠是用示波器或邏輯分析儀看波形。把TX、RX引腳同時接上設置好觸發(fā)抓一段通信數(shù)據(jù)。正常的115200波形應該是空閑高電平下降沿起始位緊接著一串電平均勻的數(shù)據(jù)位最后回到高電平。如果看到的波形高電平時脈寬明顯變窄先檢查是不是波特率配置錯了如果波形有毛刺或不規(guī)則跳變重點查電源紋波和地線。邏輯分析儀是排查UART問題的神器市面上的邏輯分析儀都支持UART協(xié)議解碼直接把協(xié)議分析打開解碼出的十六進制數(shù)據(jù)跟代碼里發(fā)送的數(shù)據(jù)一比對就能快速定位是發(fā)送端問題還是接收端問題。5.2 亂碼的三類原因亂碼排在UART調(diào)試問題第一位。歸納下來就三類波特率不匹配、收發(fā)時鐘偏差超過容限、電平不對。波特率不匹配最容易查串口助手換幾個波特率試試即可。時鐘偏差超過容限則隱蔽很多常見于MCU內(nèi)部RC振蕩器本身精度就不足的情況標稱115200實際偏差2%以上高波特率長幀必亂碼。工程里的動態(tài)校準模塊在這種場合價值很大測量起始位寬度來反推實際波特率然后修正分頻系數(shù)。電平不對呈現(xiàn)出的亂碼很滑稽能收到字符但全是?或者干脆沒反應。通常是因為對方是3.3V或者1.8V電平而接收端輸入引腳配置成了5V容忍但沒做電平轉(zhuǎn)換或者RX、TX兩端電平不匹配。先用萬用表測空閑時的電平正常的UART空閑電平應該在3V以上TTL 3.3V電平或者接近3.3V低于2V就要檢查電平轉(zhuǎn)換電路了。5.3 丟第一個字節(jié)的經(jīng)典原因很多項目的UART表現(xiàn)是連續(xù)發(fā)一堆數(shù)據(jù)接收端丟失第一到兩個字節(jié)。這個現(xiàn)象的原因通常是接收模塊在初始化完成后RX輸入的高電平建立時間不夠或者說第一個起始位的下降沿來得太早而FPGA內(nèi)部的過采樣時鐘相位還沒有對齊。工程里v2版本就是這個問題代碼里RX同步用了兩級寄存器但上電后兩級寄存器的復位狀態(tài)全是0導致線路空閑狀態(tài)被誤判為低電平第一個起始位被漏掉。解決辦法是在上電后強制把RX同步寄存器的復位值置為1即空閑高電平實測立竿見影。另一個容易忽略的原因是芯片上電后TX引腳默認狀態(tài)錯誤。有些USB轉(zhuǎn)UART芯片在上電瞬間TX引腳會短暫拉低對接收端來說這就是一個假的起始位如果接收模塊不把這個毛刺過濾掉后續(xù)所有數(shù)據(jù)都會錯開一位。5.4 長線傳輸?shù)囊呻y雜癥串口線超過2米后問題不再是協(xié)議邏輯而是物理層信號完整性。最常見的現(xiàn)象是短距離測試一切正常加長線后偶爾出現(xiàn)誤碼。這時候優(yōu)先檢查兩件事串口線是不是用的平行排線——換成雙絞線或屏蔽線會有明顯改善有沒有給收發(fā)設備共地——UART是單端信號通信雙方必須共地地線飄了再多軟件優(yōu)化也救不回來。RS485場景則要注意終端匹配電阻。120歐終端電阻只在總線兩端各加一個而且在節(jié)點多的總線上必須把所有節(jié)點的偏置電阻和終端電阻統(tǒng)一規(guī)劃否則總線空閑時電平處于不確定區(qū)間接收器會收到一堆亂碼幀。工程里的RS485文檔提到一個實用技巧在每個節(jié)點的A、B線上加一個10K的上拉/下拉偏置確??偩€空閑態(tài)穩(wěn)定在邏輯1可以大幅減少空閑噪聲引起的誤觸發(fā)。6. 把經(jīng)驗沉淀到下一個項目里回過頭再看這個工程包真正有價值的不只是那幾段Verilog代碼而是五版迭代里沉淀下來的問題意識。我自己做UART接收模塊踩過最深的一個坑是仿真通過、上板亂碼最后發(fā)現(xiàn)是復位信號釋放時機不對接收時鐘少跑了兩個周期。 UART這個協(xié)議看起來簡單但每個細節(jié)都考驗工程師對時序的敏感度。如果你準備自己寫接收模塊我的建議是別一上來就追求完整功能先按檢測起始位→中點采樣→拼裝數(shù)據(jù)這個最小路徑跑通再加毛刺過濾、FIFO、RS485方向控制這些進階特性。每個版本都保留一次上板實測的記錄像這個工程包一樣把版本迭代留檔碰到問題的時候回看變更記錄往往比重新調(diào)試效率高得多。最后再分享一個小技巧所有UART調(diào)試代碼里一定要加一個統(tǒng)計誤碼的計數(shù)器測誤碼率比盯著串口助手看花眼靠譜太多了。本文還有配套的精品資源點擊獲取