動串口屏USART_HMI:從協(xié)議到調(diào)試全解析)
寫這個系列之前我一直覺得FPGA搞顯示是很勸退的一件事。數(shù)字邏輯寫到能跑UART、能收發(fā)字符串已經(jīng)算入門了結(jié)果一看需求要把采集到的電壓、溫度、狀態(tài)幀值顯示在屏幕上還得有幾個按鈕能切頁面——很多人在這一步直接轉(zhuǎn)投ARM了。直到我把USART_HMI串口屏引入項目才發(fā)現(xiàn)FPGA做交互界面也可以這么省心。這篇是FPGA串口收發(fā)字符串系列的第四篇專門把串口屏USART_HMI這塊講透包括協(xié)議幀格式、FPGA側(cè)指令發(fā)送模塊怎么寫、聯(lián)調(diào)時會遇到哪些坑以及后續(xù)可以往雙向交互延伸的方向。手里已經(jīng)跑通UART收發(fā)模塊的朋友這一章可以直接落地就算前面幾篇沒讀只要懂串口字節(jié)怎么發(fā)也能跟上。1. 為什么FPGA項目里我會選串口屏來顯示1.1 顯示方案的選型對比很多FPGA工程師第一次面對要給項目加顯示這個問題時列出的候選方案大概跟我當年差不多數(shù)碼管、LCD1602/COG12864、SPI或者RGB接口的TFT屏最后才輪到串口屏。我把實際對比結(jié)果放在一張表里大家感受一下差異。方案顯示能力人機交互開發(fā)工作量FPGA資源占用適合場景數(shù)碼管數(shù)字和少量字母內(nèi)容有限基本無低動態(tài)掃描即可低電壓、計數(shù)、狀態(tài)指示LCD1602/COG12864字符或簡單圖形需自己取模無或按鍵擴展中初始化時序和字庫都要處理中簡單文本顯示SPI/RGB接口TFT色彩豐富像素級控制需要自己寫觸摸和GUI高顯存、字庫、GUI框架全自己做高RGB屏還得外掛SRAM/SDRAM對顯示刷新速度要求極高的場合USART_HMI串口屏彩色顯示文字、圖形、曲線、圖片均可自帶觸摸事件上報低上位機做界面FPGA只發(fā)指令極低占兩個IO和一個UART設(shè)備狀態(tài)顯示、參數(shù)面板、人機交互這里不是說數(shù)碼管和TFT不好而是工程上有性價比的考量。數(shù)碼管適合那種看一眼就夠的數(shù)據(jù)TFT屏適合需要高頻刷新畫面的場景但代價是FPGA里要維護一整套路顯存和繪圖庫開發(fā)周期以周為單位。串口屏把這塊全包了它內(nèi)部有一顆主控芯片專門負責解析指令、刷新LCD、掃描觸摸FPGA要做的只是按協(xié)議把字符串或者數(shù)值幀發(fā)過去。1.2 USART_HMI解決的核心問題用一句話概括串口屏的本質(zhì)它不是一塊普通顯示屏而是一個帶GUI引擎的UART從設(shè)備。你在上位機軟件里畫好界面拖好文本控件、數(shù)字控件、按鈕分配好變量地址燒錄到屏幕里然后FPGA這邊只需要通過串口告訴它把變量0x0001的內(nèi)容改成多少多少屏幕上對應(yīng)的控件就會自動刷新。這和傳統(tǒng)TFT方案完全是兩種思維方式。以前是FPGA逐像素去畫現(xiàn)在是FPGA只發(fā)命令畫面怎么渲染、字體怎么顯示、按鈕怎么響應(yīng)都是串口屏自己去處理。就像一個是自己調(diào)油漆刷墻另一個是打電話讓裝修隊干活活一樣能干完但工作量根本不是一個量級。我用串口屏之后最直觀的感受是FPGA側(cè)的邏輯可以專注在信號采集、算法處理、狀態(tài)控制這些真正核心的事情上界面交互只是偶爾發(fā)一串字節(jié)的事。開發(fā)周期從幾周縮短到一兩天而且改界面布局不用重新綜合FPGA工程只改串口屏上位機的工程再燒錄就行這在項目調(diào)試階段非常救命。1.3 什么場景不適合串口屏串口屏不是銀彈它的短板也很明顯我這里把丑話說在前面。首先是刷新速度以115200波特率為例理論上一秒只能傳約11520字節(jié)一條簡單的寫變量指令大概10字節(jié)左右也就是說一秒最多發(fā)一千多次但實際考慮幀間隙和串口屏內(nèi)部處理時間穩(wěn)定跑到幾十赫茲就到頭了。所以它適合展示狀態(tài)數(shù)據(jù)和參數(shù)不適合做高速波形輸出之類需要像素級實時更新的界面。其次是成本串口屏的單價明顯高于同等尺寸的裸屏加驅(qū)動方案如果產(chǎn)品已經(jīng)確定要大規(guī)模量產(chǎn)而且顯示內(nèi)容極其固定那還是老老實實自己驅(qū)動屏幕更劃算。最后還有一點容易忽略串口屏是帶固件的黑盒一旦遇到官方固件不支持的協(xié)議細節(jié)你沒法繞過它直接操作像素靈活度確實比不上自己寫驅(qū)動。理解了這個邊界你才知道什么時候該用它什么時候不該用。2. USART_HMI協(xié)議先看懂再動手2.1 指令幀的基本結(jié)構(gòu)串口屏雖然界面華麗但通信協(xié)議其實相當簡潔核心就是一幀一指令。USART_HMI的指令幀通常由幀頭、數(shù)據(jù)長度、指令碼、數(shù)據(jù)區(qū)和校驗字段組成大致是這個樣子字段長度說明幀頭1字節(jié)固定值0xA5表示一幀的開始數(shù)據(jù)長度1字節(jié)從指令碼到校驗之前的字節(jié)數(shù)用于接收端分幀指令碼1字節(jié)標識這條指令要做什么比如寫變量、讀變量、跳頁面數(shù)據(jù)區(qū)N字節(jié)變量地址、數(shù)據(jù)類型、具體的數(shù)據(jù)內(nèi)容校驗1字節(jié)通常是對前面數(shù)據(jù)區(qū)做異或和用于驗證幀沒有傳錯做FPGA的人看到這種結(jié)構(gòu)應(yīng)該很親切它跟我們的UART狀態(tài)機是天然匹配的。接收端先等幀頭0xA5收到之后再讀數(shù)據(jù)長度然后按長度把剩下的字節(jié)收滿最后做一遍校驗幀就完整還原了。這也是為什么串口屏特別適合跟FPGA配合——協(xié)議本身就對硬件邏輯友好不需要PC上那種復(fù)雜的緩沖解析。不同品牌和固件版本在字段定義細節(jié)上有差異比如淘晶馳TJC系列、Nextion系列和迪文DGUS系列就是三套不同的體系甚至同一品牌不同批次固件對長度字段的計數(shù)方式都可能不一樣。我在項目里固定用淘晶馳的USART_HMI協(xié)議本文也以它為例。動手之前一定要把自己手上屏的協(xié)議手冊打印出來對照別拿網(wǎng)上例程的幀直接抄。2.2 最常用的幾條指令用串口屏控制界面絕大多數(shù)時間就三條指令寫變量、讀變量、跳頁面。所謂變量就是你在上位機里給每個控件綁定的數(shù)據(jù)通道比如文本控件t0綁定了變量v0數(shù)字控件n0綁定了變量n0這些變量在屏的內(nèi)部存儲里都有固定地址FPGA只要往對應(yīng)地址寫數(shù)據(jù)控件就會刷新。寫變量指令的語義是往地址X寫入數(shù)據(jù)Y。數(shù)據(jù)Y可以是字符串也可以是二進制數(shù)值具體是哪一種由數(shù)據(jù)類型字段區(qū)分。舉個例子如果要在文本控件里顯示OK兩個字指令幀的數(shù)據(jù)區(qū)就包含控件地址、字符串類型標識、以及字符O和K的ASCII碼。FPGA要做的事情并不復(fù)雜地址、類型、ASCII碼都是確定的算好長度和校驗把整幀發(fā)出去即可。讀變量指令是反過來FPGA向屏請求讀取某地址的數(shù)據(jù)屏收到后會把當前值回傳。這條指令在做雙向交互時很有用比如FPGA上電后要讀取屏上保存的參數(shù)。跳頁面指令就更直白了告訴屏切換到某個頁面ID適合做多菜單、多界面切換。在FPGA里設(shè)計發(fā)送模塊的時候我建議把這些指令統(tǒng)一封裝成一個指令序列器每條指令是一個固定模板需要變化的只是地址和數(shù)據(jù)區(qū)。模板加上動態(tài)參數(shù)拼成一幀再送到UART模塊發(fā)出去。這樣代碼結(jié)構(gòu)清晰后面要加新指令只是加一個模板的事。2.3 控件變量地址與數(shù)據(jù)類型的坑用串口屏上位機拖控件的時候很多人會忽略地址分配這件事。上位機軟件的變量控件列表里每個變量都有一個地址一般是按創(chuàng)建順序從0x0000開始編號。FPGA側(cè)寫指令時用的就是這些地址。如果你只記得控件名字不記得地址那寫指令時就會對不上。我一般會在上位機里把變量名和地址做成一張對照表和FPGA代碼放同一個工程目錄下改一次同步一次不然調(diào)試時極容易暈。數(shù)據(jù)類型字段是另一個容易出岔子的地方。字符串和數(shù)值的編碼方式不一樣字符串直接用ASCII碼字節(jié)流數(shù)值可以用二進制直接傳輸也可以轉(zhuǎn)成ASCII形式的數(shù)字字符。串口屏接收不同數(shù)據(jù)類型時解析邏輯完全不同一旦類型標識傳錯屏上顯示的就是亂碼或者0。這個字段在上位機協(xié)議文檔里有明確的取值表寫代碼前要確認好。還有一個細節(jié)容易被新手忽略字符串長度和控件顯示區(qū)域的關(guān)系。文本控件如果設(shè)置了最多顯示4個字符你一幀發(fā)8個字符進去要么被截斷要么把旁邊內(nèi)容擠亂。所以FPGA側(cè)發(fā)送字符串之前要么自己做好截斷要么在上位機里把控件長度放寬。我自己寫模塊時會在指令序列器里加一個最大長度寄存器配合控件屬性一起設(shè)置避免指令本身沒問題但顯示效果稀碎。3. FPGA端串口指令發(fā)送模塊怎么設(shè)計3.1 模塊劃分思路FPGA端驅(qū)動串口屏不需要一個萬能協(xié)議棧那反而把事情搞復(fù)雜了。我習(xí)慣把邏輯拆成三塊指令序列器、字節(jié)發(fā)送器、參數(shù)生成邏輯。字節(jié)發(fā)送器說白了就是系列前幾章寫的UART TX模塊負責把一個字節(jié)變成串行波形發(fā)出去帶busy或者done信號。參數(shù)生成邏輯負責算地址、算長度、做校驗比如數(shù)值轉(zhuǎn)ASCII、字符串拼接。指令序列器是大腦負責按順序把一幀里的字節(jié)逐個送給字節(jié)發(fā)送器中間控制節(jié)奏保證幀內(nèi)字節(jié)連續(xù)、幀間留出間隔。這個劃分的好處是每一塊都能單獨測試。UART TX單獨連電腦驗證過參數(shù)生成邏輯可以用Modelsim仿真對比輸出字節(jié)流指令序列器只要狀態(tài)機不跑飛整幀順序就不會亂。等到最后聯(lián)調(diào)時問題范圍能快速鎖定到某一小塊不會滿工程找bug。3.2 狀態(tài)機加FIFO的設(shè)計方式指令序列器我一律采用先組裝到發(fā)送FIFO再自動發(fā)送的方式而不是一字節(jié)一狀態(tài)地硬憋。為什么因為指令幀拼裝過程中可能要插入動態(tài)參數(shù)如果整個幀的字節(jié)都是一個時鐘一個時鐘地直接塞給UART中間一旦被更高優(yōu)先級的邏輯打斷幀就殘了。用FIFO緩沖一層組裝過程可以分步完成組裝完畢后UART模塊從FIFO里連續(xù)取字節(jié)發(fā)送幀的原子性就有保證。核心狀態(tài)機示意代碼大致是這樣// 指令序列器核心狀態(tài)機節(jié)選 localparam IDLE 3d0; localparam ASSEMBLE 3d1; localparam WAIT_EMPTY 3d2; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; end else begin case (state) IDLE: if (tx_start) begin byte_cnt 0; state ASSEMBLE; end ASSEMBLE: begin // 每個時鐘寫一個字節(jié)到FIFO // 先寫幀頭0xA5再寫長度、指令碼、數(shù)據(jù)區(qū)、校驗 fifo_wr_en 1b1; fifo_wr_data frame_byte_gen(byte_cnt); if (byte_cnt frame_total_len - 1) begin fifo_wr_en 1b0; state WAIT_EMPTY; end else begin byte_cnt byte_cnt 1b1; end end WAIT_EMPTY: if (fifo_empty) begin // 整幀發(fā)送完成 tx_done 1b1; state IDLE; end endcase end end實際工程里frame_byte_gen不會是一個簡單函數(shù)而是根據(jù)byte_cnt查表0號字節(jié)固定是0xA51號字節(jié)是長度2號字節(jié)是指令碼后面的字節(jié)可能是變量地址、數(shù)據(jù)類型、數(shù)據(jù)內(nèi)容最后是校驗字節(jié)。動態(tài)參數(shù)部分在上層邏輯觸發(fā)tx_start之前就預(yù)先算好放在參數(shù)寄存器里組裝階段只是把它們按順序搬進FIFO。UART模塊那邊從FIFO讀字節(jié)、逐字節(jié)發(fā)送發(fā)送完成信號把FIFO讀指針推進。這個方案的妙處在于FIFO天然隔離了兩個時鐘域如果有的話和兩級邏輯組裝過程不占用UART發(fā)送時間幀與幀之間還能方便地插入任意延時。3.3 長度和校驗的計算時機長度和校驗是FPGA工程師最容易寫錯的地方。長度字段指的是從這個字段自身后面開始、直到數(shù)據(jù)區(qū)結(jié)束的字節(jié)數(shù)。校驗字段通常是對長度、指令碼和數(shù)據(jù)區(qū)做異或幀頭0xA5不參與校驗。這兩者的計算必須放在組裝階段的開頭算出結(jié)果存進寄存器隨幀一起寫入FIFO而不是在UART發(fā)送過程中臨時去算。為什么強調(diào)時機因為很多新手會在發(fā)送每一字節(jié)的組合邏輯里去動態(tài)計算校驗結(jié)果綜合出來的電路出現(xiàn)奇怪的時序問題幀內(nèi)容對了但校驗總不對。我現(xiàn)在的做法是先算后發(fā)。幀長如果固定長度字段就直接是一個常量校驗則在PREPARE階段用組合邏輯異或樹算好或者更省事——因為一幀字節(jié)數(shù)不多完全可以在寫入FIFO的時候同步計算校驗最后一個字節(jié)寫進去時校驗值也已經(jīng)算出來直接跟著寫進去。這里還有個工程上的建議第一版盡量把所有指令設(shè)計成定長幀。定長幀的長度字段是常量地址偏移是常量校驗樹也可以預(yù)先確定結(jié)構(gòu)調(diào)試難度會低很多。等定長幀跑通了再改成變長幀處理動態(tài)字符串就會從容很多。4. FPGA變量如何變成屏幕上的數(shù)字和字符串4.1 數(shù)值轉(zhuǎn)ASCII的處理FPGA內(nèi)部處理的數(shù)據(jù)是二進制數(shù)但串口屏要顯示的是人類能讀的十進制數(shù)字。所以中間必須有一個數(shù)值轉(zhuǎn)ASCII的過程。最基礎(chǔ)的方法是把二進制數(shù)轉(zhuǎn)成BCD碼然后把每一位BCD碼加上0x30就得到對應(yīng)數(shù)字的ASCII碼。比如二進制數(shù)27轉(zhuǎn)成BCD碼是0010 0111即十位2、個位7加上0x30后得到0x32和0x37對應(yīng)字符2和7。二進制轉(zhuǎn)BCD常用的是加3移位算法也叫移位加3法一段循環(huán)邏輯就能實現(xiàn)不需要除法器非常適合FPGA。對于0到9999范圍的數(shù)值移位16次就可以完成轉(zhuǎn)換占用資源很少。如果你要顯示的是有符號數(shù)還得先處理符號位在前面加一個負號0x2D。我看到有些人在FPGA里直接用取模和除法運算符去轉(zhuǎn)ASCII仿真沒問題但綜合出的除法器吃資源比較多而且路徑延遲大。在小規(guī)模顯示應(yīng)用里加3移位法既省資源又容易控制時序是我比較推薦的方案。4.2 字符串指令幀的拼接實例舉個實際例子假設(shè)要把溫度值27度顯示在屏上的文本控件控件地址是0x0001。FPGA側(cè)要發(fā)出的指令幀大致是這樣的先幀頭0xA5然后是長度、寫變量指令碼、地址高字節(jié)、地址低字節(jié)、數(shù)據(jù)類型標識接著是2和7兩個ASCII字節(jié)最后是校驗。整個過程在指令序列器里就是按byte_cnt依次輸出這些字節(jié)。數(shù)值轉(zhuǎn)ASCII的結(jié)果怎么和固定幀頭拼接呢我一般用一個可變長度移位寄存器組方式把固定部分、動態(tài)部分、校驗部分分別存到三個小寄存器區(qū)組裝時按順序選通。如果幀長固定動態(tài)部分也只是長度固定那整個幀甚至可以定義成一個寄存器數(shù)組ASSEMBLE狀態(tài)里按地址查表輸出即可。調(diào)試的時候有一個很實用的技巧在UART TX的輸出引腳上接邏輯分析儀或者直接在FPGA里把那路信號引到一個空閑引腳用示波器看波形。整幀字節(jié)的時序波形一目了然哪里的字節(jié)不對對照協(xié)議文檔一比對就能定位到是拼接邏輯還是數(shù)值轉(zhuǎn)換的問題。4.3 顯示刷新節(jié)奏與帶寬估算串口屏能接受的顯示刷新速度沒有你想象的那么高。我實測下來狀態(tài)類數(shù)據(jù)10到20Hz刷新就已經(jīng)非常平滑了沒必要每毫秒無腦刷。原因有兩個一是串口帶寬有限115200波特率一秒鐘才能傳一萬多字節(jié)一條指令就是十幾字節(jié)刷新頻率越高留給其他指令的帶寬就越少二是串口屏內(nèi)部解析幀、刷新LCD也需要時間指令過來太密會出現(xiàn)排隊甚至丟幀。所以我一般在FPGA里給串口屏發(fā)送加一個節(jié)流器比如用一個計數(shù)器產(chǎn)生10ms或50ms的周期脈沖每個脈沖最多觸發(fā)一幀顯示指令。這樣既保證了人眼看起來連續(xù)又不會把串口屏和串口帶寬壓到極限。我在做多通道數(shù)據(jù)采集顯示時用5ms周期更新一個頁面同時還要處理觸摸事件上報沒有出現(xiàn)過串口擁堵的問題。記住一個原則顯示類的數(shù)據(jù)寧可少刷不要暴刷。5. 聯(lián)調(diào)實錄這幾個坑我替你們踩過了5.1 波特率誤差帶來的隱性故障FPGA和串口屏之間的波特率如果對不上表現(xiàn)不是完全不通而是時好時壞尤其當誤差積累到采樣的臨界點時會隨機出亂碼。這個坑隱蔽性很高因為它不穩(wěn)定復(fù)現(xiàn)很容易讓人懷疑是屏壞了或者代碼邏輯錯了。正確的做法是先用頻率計確認FPGA系統(tǒng)的基準時鐘頻率再算波特率分頻值。比如50MHz系統(tǒng)時鐘跑115200波特率分頻值是50_000_000/115200約等于434取整后誤差不到0.01%基本可以忽略。但如果你的板子用的是24MHz或者12MHz晶振分頻后的誤差就可能超過1%這時候就要考慮換一個更合適的波特率或者用小數(shù)分頻器來處理。我的習(xí)慣是FPGA側(cè)和串口屏都固定用115200這幾乎是兩者之間最穩(wěn)的一個平衡點。9600雖然更抗干擾但顯示刷新有明顯的打字機感不太適合做界面。另外要注意串口屏模塊的默認波特率可能和它實際跑的波特率不一致收到屏之后第一次通電就要用串口助手確認屏的配置。5.2 幀被切斷和指令被吞的問題聯(lián)調(diào)時最常遇到的現(xiàn)象是屏上偶爾顯示一次正確內(nèi)容然后連續(xù)發(fā)好幾條指令都沒反應(yīng)或者顯示出來的是殘缺數(shù)據(jù)。排查下來的典型原因是FPGA側(cè)的指令序列器在多幀連續(xù)發(fā)送時幀間隙控制不好或者處理器邏輯在發(fā)送過程中搶占了串口發(fā)送的優(yōu)先級導(dǎo)致幀頭后面的數(shù)據(jù)沒跟上。解決思路就是我前面說的FIFO緩沖方案讓整幀組裝成一個不可分割的整體。發(fā)送期間絕不允許其他邏輯往UART模塊里插入字節(jié)必須等當前幀的最后一個字節(jié)發(fā)完、FIFO清空之后才允許下一幀開始組裝。這個原子性設(shè)計不是可選項而是必須項。另外幀與幀之間要留出足夠的間隔讓串口屏來得及解析上一幀。我實測淘晶馳系列的屏處理一幀普通指令大概需要幾毫秒到十幾毫秒不等取決于指令類型。所以我在指令序列器里配置了一個幀間隔計數(shù)器一般取5ms到10ms如果連續(xù)快速切換頁面時發(fā)現(xiàn)偶發(fā)不響應(yīng)就把這個間隔再拉大一些。5.3 上電時序和初始化等待FPGA和串口屏有一個很容易被忽略的時序關(guān)系上電瞬間誰先就緒。串口屏內(nèi)部有操作系統(tǒng)和GUI引擎上電后需要幾百毫秒甚至更長時間初始化如果FPGA在這個窗口期就往外發(fā)指令屏根本收不到。我見過最頭疼的問題就是FPGA啟動后立刻發(fā)初始化指令配置界面結(jié)果指令被吞界面停留在默認狀態(tài)但單獨用串口助手手動發(fā)指令又一切正常。解決方案很簡單也很粗暴FPGA的串口屏初始化流程必須在一個延時后才開始。我在頂層狀態(tài)機里加了一個上電等待計數(shù)器一般取500ms到1秒。先讓串口屏初始化完再發(fā)第一條寫變量或跳頁面指令。如果有條件更穩(wěn)妥的做法是先用串口助手上電觀察屏的返回信息確認屏完全就緒后再讓FPGA發(fā)指令。如果做出來的產(chǎn)品還有休眠喚醒這類需求那上電時序就更要謹慎了。串口屏進入休眠后不代表串口還能照常收數(shù)據(jù)喚醒時序也得按手冊來不能默認它一直在線。5.4 先用串口助手驗證再上FPGA這個經(jīng)驗我每次帶人都要強調(diào)一遍不要一上來就拿FPGA和串口屏對調(diào)先用USB轉(zhuǎn)串口模塊接電腦用串口調(diào)試助手手工發(fā)指令確認屏的界面和協(xié)議沒問題。這個操作能幫你把問題分成兩個階段非常省時間。具體操作流程是第一步用串口助手連接屏幕把上位機生成的指令示例逐條發(fā)過去觀察屏幕反應(yīng)確認地址、長度、校驗都對第二步把電腦斷開把FPGA的UART TX引腳接到串口屏的RX引腳先用FPGA發(fā)一條最簡單的指令用邏輯分析儀抓UART TX的波形和串口助手里發(fā)過的指令字節(jié)流做對比第三步確認波形一致后再讓FPGA跑完整邏輯。這三步看起來多花了一點時間但實際上能省掉大量無頭緒的排查。我遇到過很多人把問題定位在FPGA側(cè)結(jié)果是串口屏上位機工程里控件地址沒分配對也有人懷疑屏壞了結(jié)果根本原因是FPGA的引腳電平不匹配。先驗證屏自身沒問題是從源頭排除故障面的好習(xí)慣。6. 從這一章還能往哪走雙向交互與進階玩法6.1 觸摸事件上報與FPGA接收解析串口屏不光是顯示設(shè)備它還是一個完整的人機交互終端。當你用手指按下屏上的按鈕時串口屏?xí)鲃酉蛑鳈C發(fā)送一條事件上報幀里面通常包含頁面ID、控件ID和事件類型。這就意味著FPGA不光要發(fā)還要收。接收側(cè)的核心也是那個熟悉的套路狀態(tài)機檢測幀頭0xA5然后讀長度字段按長度收完整幀做校驗。解析到是一個觸摸事件后根據(jù)控件ID跳轉(zhuǎn)到對應(yīng)的處理邏輯。比如一個啟動/停止按鈕FPGA接收到按下事件后置位或清除一個控制寄存器進而控制外設(shè)狀態(tài)。我建議接收和發(fā)送共用同一個FIFO緩沖思路只不過方向反過來。串口屏發(fā)來的字節(jié)流被UART RX模塊接收后先寫進接收FIFOFPGA主邏輯空閑時再去解析。這樣即使一幀事件在接收過程中被其他邏輯打斷也不會丟字節(jié)。等這一塊跑通你的FPGA系統(tǒng)就真正有了面板操作的能力而不只是單方向顯示。6.2 頁面切換、曲線顯示與后續(xù)擴展USART_HMI協(xié)議里還有很多值得玩的功能。頁面切換指令可以讓你的FPGA設(shè)備實現(xiàn)菜單層級跳轉(zhuǎn)按下設(shè)置進入設(shè)置頁按下返回回到主頁面。曲線控件可以把采集到的連續(xù)數(shù)據(jù)流動態(tài)畫出來雖然刷新率不如直接驅(qū)動屏幕高但做溫濕度歷史曲線這類低頻波形完全夠用。圖片推送功能甚至可以把FPGA采集的圖像數(shù)據(jù)一幀一幀推到串口屏上顯示配合SD卡存儲可以做一個很輕量的數(shù)據(jù)記錄儀。如果項目到了一定規(guī)模我建議把串口屏通信封裝成一個獨立模塊對外只留寫字符串變量寫數(shù)值變量注冊觸摸回調(diào)這幾個接口。上層邏輯永遠不直接拼幀所有協(xié)議細節(jié)都隔離在模塊內(nèi)部。這樣以后換屏幕品牌、升級協(xié)議版本只需要替換這一個模塊頂層控制邏輯一行都不用改。我現(xiàn)在項目里的分工基本是FPGA專心做信號采集和算法處理串口屏專心做人機界面兩者之間只有一組UART連接。這種把“界面”從“邏輯”里剝離開的思路其實是FPGA工程成熟之后很自然的演進方向。串口收發(fā)字符串本身只是基本功接上一個像USART_HMI串口屏這樣真正能出活的終端它才變成生產(chǎn)力。這大概是我寫這個系列到現(xiàn)在最想留給你的東西。