調(diào)實戰(zhàn):數(shù)據(jù)可視化終端設計)
1. 為什么我選STM32F103C8T6加淘晶馳T1這套組合搞嵌入式的人手里多少都攥著幾塊STM32F103C8T6最小系統(tǒng)板這塊藍板子幾乎成了入門和做小項目的標配。我這次要做的是把采集到的數(shù)據(jù)實時顯示到一塊淘晶馳T1串口屏上做一個能看、能改、能交互的小型數(shù)據(jù)可視化終端。說白了就是讓單片機把傳感器數(shù)據(jù)算好通過串口丟給屏幕屏幕負責把數(shù)字、曲線、進度條漂漂亮亮地畫出來同時屏幕上按個按鈕單片機那邊還能收到指令去執(zhí)行動作。這套方案解決的核心問題很直接很多場景下用電腦看數(shù)據(jù)太笨重用OLED屏又只能顯示幾行字而串口屏自帶字庫、圖片、觸摸控件開發(fā)效率高顯示效果也拿得出手。STM32F103C8T6負責邏輯和采集淘晶馳T1負責呈現(xiàn)和交互兩者通過UART串口通信分工明確。適合誰看剛學完STM32串口收發(fā)、想找個綜合項目練手的同學做過一些裸屏驅(qū)動、被字庫和UI折磨過的開發(fā)者還有需要快速搭一個帶觸摸交互的數(shù)據(jù)顯示終端的工程師。哪怕你之前沒碰過串口屏跟著走一遍也能把整套鏈路跑通。我先把結(jié)論放前面這套聯(lián)調(diào)的關(guān)鍵不在STM32端而在串口屏的控件命名和指令格式。很多人卡住是因為屏幕那邊控件名寫錯一個字母或者指令末尾忘了加三個0xFF導致單片機發(fā)出去的數(shù)據(jù)石沉大海。下面我把整個設計思路、硬件連接、代碼實現(xiàn)和踩坑記錄完整拆開講。2. 整體方案設計與選型背后的考量2.1 主控為什么是STM32F103C8T6STM32F103C8T6這顆芯片72MHz主頻、64KB Flash、20KB SRAM帶3個USART、2個SPI、2個I2C對于這種“采集串口通信少量邏輯”的任務來說綽綽有余。我選它主要看三點一是資料多標準庫、HAL庫、寄存器版本隨便挑出問題好查二是價格便宜最小系統(tǒng)板十幾塊錢壞了不心疼三是引腳夠用PA9/PA10做USART1PA2/PA3做USART2一路接屏幕一路留調(diào)試或者接其他模塊互不打架。這里要提一句市面上有國產(chǎn)替代的F103C8T6引腳和寄存器基本兼容但個別批次的Flash容量標注和實際有出入做量產(chǎn)要留意。自己做項目練手無所謂我手上這塊就是普通版本跑起來很穩(wěn)。2.2 顯示端為什么選淘晶馳T1串口屏淘晶馳T1屬于入門級串口屏尺寸常見有2.4寸、2.8寸、3.5寸分辨率從320x240到480x320不等。它最大的好處是“所見即所得”在電腦端的USART HMI軟件里拖控件、設屬性、下載工程屏幕自己就能顯示單片機只需要發(fā)指令去改變量。相比自己用SPI驅(qū)動TFT再移植字庫省下的時間不是一點半點。它和單片機之間的協(xié)議是自定義的ASCII指令比如改變文本控件txt0的內(nèi)容發(fā)“txt0.txtHello\””加上三個0xFF結(jié)束符就行。觸摸事件也會以指令形式回傳比如按下b0按鈕屏幕發(fā)“b0 1”加結(jié)束符。這種設計讓單片機端代碼極其簡單不用解析復雜協(xié)議字符串拼接就能搞定。2.3 通信方式與波特率選擇兩者之間用UART異步串口TTL電平直連。STM32的TX接屏幕的RXSTM32的RX接屏幕的TX共地。波特率我選115200原因是數(shù)據(jù)量不大但要求響應快115200在F103上用72MHz主頻分頻很準誤差小。屏幕端在工程設置里把波特率也改成115200兩邊必須一致否則收到的全是亂碼。有人問要不要加電平轉(zhuǎn)換淘晶馳T1的串口是3.3V TTL電平STM32F103的IO也是3.3V直接連就行不用MAX232那種電平轉(zhuǎn)換芯片。但如果你用的是5V的單片機那就得加轉(zhuǎn)換不然可能燒屏幕。2.4 數(shù)據(jù)可視化的實現(xiàn)層次“數(shù)據(jù)可視化”這個詞聽起來大落到這個項目上其實分三層第一層是數(shù)值顯示把溫度、電壓這些數(shù)字填到文本控件里第二層是圖形化用進度條控件顯示百分比用波形控件畫實時曲線第三層是交互反饋觸摸按鈕改變單片機的工作模式屏幕上的指示燈跟著變。這三層我都做了后面會分別講實現(xiàn)方法。3. 硬件連接與工程配置的實操細節(jié)3.1 硬件清單與接線表先把要用的東西列清楚避免做到一半發(fā)現(xiàn)少東西。器件型號/規(guī)格數(shù)量備注主控板STM32F103C8T6最小系統(tǒng)板1帶8MHz晶振和復位電路串口屏淘晶馳T12.8寸1分辨率320x240下載器ST-Link V21用于燒錄STM32程序串口線4Pin杜邦線若干母對母電源5V USB供電1屏幕和主控可共用接線就四根STM32的5V接屏幕的5VGND接GNDPA9USART1_TX接屏幕的RXPA10USART1_RX接屏幕的TX。注意TX和RX要交叉我見過有人TX接TX調(diào)了半天以為代碼有問題其實是線接錯了。提示屏幕的5V和GND最好從主控板取這樣上電順序一致避免屏幕先上電、主控后上電時串口狀態(tài)不確定導致誤觸發(fā)。3.2 STM32端USART1的配置我用標準庫來配因為F103的標準庫資料最全寄存器操作也直觀。USART1掛在APB2總線上時鐘72MHz。配置步驟開GPIOA和USART1時鐘PA9配成復用推挽輸出PA10配成浮空輸入USART1設成115200、8位數(shù)據(jù)、1位停止、無校驗、無硬件流控最后使能收發(fā)和USART。關(guān)鍵代碼片段如下void USART1_Init(u32 bound) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); USART_InitStructure.USART_BaudRate bound; USART_InitStructure.USART_WordLength USART_WordLength_8b; USART_InitStructure.USART_StopBits USART_StopBits_1; USART_InitStructure.USART_Parity USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, USART_InitStructure); USART_Cmd(USART1, ENABLE); }發(fā)送單字節(jié)和發(fā)送字符串的函數(shù)也要寫發(fā)送字符串時我習慣用指針遍歷直到遇到‘\0’。void USART1_SendByte(u8 data) { while(USART_GetFlagStatus(USART1, USART_FLAG_TC) RESET); USART_SendData(USART1, data); } void USART1_SendString(char *str) { while(*str) { USART1_SendByte(*str); } }3.3 淘晶馳T1工程的新建與控件命名打開USART HMI軟件新建工程選對型號和分辨率。然后在頁面上拖控件。我建了這幾個一個文本控件命名t0用來顯示溫度一個文本控件t1顯示電壓一個進度條j0顯示電量百分比一個波形控件s0畫實時曲線兩個按鈕b0和b1分別控制LED開關(guān)??丶兄v究名字只能用字母和數(shù)字不能有中文和特殊符號而且一旦下載到屏幕里單片機就是靠這個名字去尋址的。我建議命名時帶點含義比如t_temp、j_bat比t0、j0好記后期改起來不容易亂。但要注意名字長度別太長屏幕那邊有長度限制。3.4 屏幕工程下載與波特率設置在軟件里點“下載”選對串口號把工程燒進屏幕。下載完成后屏幕會重啟顯示你設計的界面。然后在工程的“設置”里把波特率改成115200和STM32一致。這一步很多人忘結(jié)果屏幕默認9600單片機發(fā)115200兩邊對不上屏幕上什么都不動。注意每次修改工程重新下載后屏幕里的變量會復位如果單片機在持續(xù)發(fā)數(shù)據(jù)下載瞬間可能收到半截指令建議下載時先斷開單片機。4. 串口屏指令格式與STM32端代碼實現(xiàn)4.1 淘晶馳指令的基本格式淘晶馳的指令是ASCII字符串格式為“控件名.屬性值”末尾必須跟三個0xFF作為結(jié)束符。比如給文本控件t0賦值25.6發(fā)的是t0.txt25.6后面跟0xFF 0xFF 0xFF。注意引號是英文引號值如果是數(shù)字可以不加引號但加了也沒錯。我習慣文本類都加引號數(shù)字類不加。觸摸事件回傳的格式是“控件名 事件類型”比如按下b0回傳“b0 1”后面同樣跟三個0xFF。事件類型1表示按下0表示彈起。4.2 封裝一個發(fā)送指令的函數(shù)為了代碼整潔我封裝了一個函數(shù)傳入控件名、屬性和值自動拼出完整指令并加上結(jié)束符。void HMI_SendCmd(char *obj, char *attr, char *value) { USART1_SendString(obj); USART1_SendString(.); USART1_SendString(attr); USART1_SendString(); USART1_SendString(value); USART1_SendByte(0xFF); USART1_SendByte(0xFF); USART1_SendByte(0xFF); }調(diào)用時比如設置t0的文本為“25.6”HMI_SendCmd(t0, txt, \25.6\);這里值帶了轉(zhuǎn)義引號因為C語言里字符串本身要用雙引號里面的引號得轉(zhuǎn)義。如果你嫌麻煩也可以直接拼字符串但封裝成函數(shù)后改起來方便。4.3 數(shù)值轉(zhuǎn)字符串的處理單片機采集到的溫度、電壓是浮點數(shù)要轉(zhuǎn)成字符串才能發(fā)給屏幕。標準庫沒有sprintf的浮點支持默認精簡版我一般用整數(shù)放大法比如溫度25.6先乘10變成256再拆成整數(shù)部分和小數(shù)部分拼字符串。void FloatToString(float val, char *buf) { int int_part (int)val; int dec_part (int)((val - int_part) * 10); if(dec_part 0) dec_part -dec_part; sprintf(buf, %d.%d, int_part, dec_part); }這樣避免了浮點sprintf的坑也省Flash空間。實測下來精度夠用顯示一位小數(shù)足夠。4.4 波形控件的特殊處理波形控件s0要顯示實時曲線指令格式和文本不同。淘晶馳的波形控件用“add”指令往里面加數(shù)據(jù)點格式是“s0.add值”值范圍0到255。比如HMI_SendCmd(s0, add, 128);每加一個點曲線就往左移一格。我定時器里每100ms加一個點這樣曲線滾動速度合適。注意值要限制在0到255之間超了屏幕可能不認。4.5 觸摸事件的接收與解析STM32端要開USART1接收中斷把收到的字節(jié)存到緩沖區(qū)檢測到連續(xù)三個0xFF就認為一幀結(jié)束然后解析緩沖區(qū)內(nèi)容。解析時判斷開頭是不是“b0”或“b1”是的話執(zhí)行對應動作。void USART1_IRQHandler(void) { u8 res; if(USART_GetITStatus(USART1, USART_IT_RXNE) ! RESET) { res USART_ReceiveData(USART1); // 存入緩沖區(qū)檢測0xFF // ... USART_ClearITPendingBit(USART1, USART_IT_RXNE); } }緩沖區(qū)我開了64字節(jié)足夠放一幀指令。解析時用strstr找“b0”找到就翻轉(zhuǎn)LED同時回發(fā)指令讓屏幕上的指示燈變色。5. 數(shù)據(jù)可視化界面的搭建與聯(lián)調(diào)過程5.1 界面布局設計屏幕就320x240空間有限我這樣布局頂部一行放兩個文本控件左邊t0顯示溫度右邊t1顯示電壓中間放波形控件s0占大半屏底部左邊放進度條j0右邊放兩個按鈕b0和b1。字體用默認的字號16顏色白底黑字對比度高陽光下也能看清。進度條j0設了背景色和前景色前景色用綠色值從0到100。波形控件s0設了網(wǎng)格線和背景色看起來像示波器。5.2 聯(lián)調(diào)第一步驗證通信鏈路代碼燒進去之前我先用USB轉(zhuǎn)TTL模塊單獨連屏幕用電腦串口助手發(fā)指令確認屏幕能正確響應。發(fā)“t0.txttest\””加三個0xFF屏幕上t0立刻變成test說明屏幕端沒問題。然后再把STM32接上發(fā)同樣的指令如果屏幕也變說明STM32端發(fā)送正常。這一步很關(guān)鍵能把問題定位在發(fā)送端還是接收端。我見過有人直接上整套結(jié)果屏幕不亮不知道是線的問題、代碼的問題還是屏幕工程的問題排查起來費勁。5.3 聯(lián)調(diào)第二步觸摸回傳驗證屏幕發(fā)指令給STM32我用串口助手看STM32收到的數(shù)據(jù)。按下b0串口助手應該顯示“b0 1”加三個0xFF的十六進制。如果收到亂碼檢查波特率如果收不到檢查TX/RX是不是接反了。確認能收到后在中斷里加解析邏輯收到b0就翻轉(zhuǎn)PC13的LED最小系統(tǒng)板上自帶的LED??吹絃ED跟著按鈕動這條鏈路就通了。5.4 聯(lián)調(diào)第三步實時數(shù)據(jù)刷新我讓定時器2每100ms觸發(fā)一次在中斷里模擬采集溫度用ADC讀內(nèi)部溫度傳感器或者隨便給個變化的值轉(zhuǎn)成字符串發(fā)給屏幕同時往波形控件加一個點。屏幕上就能看到數(shù)字在跳曲線在滾。這里要注意發(fā)送頻率。100ms發(fā)一次一次幾十字節(jié)115200波特率下完全夠用。如果發(fā)太快比如10ms一次屏幕可能處理不過來出現(xiàn)卡頓。我實測50ms一次也很流暢但沒必要。5.5 完整主循環(huán)與中斷配合主循環(huán)里我做了兩件事一是檢測按鍵如果有物理按鍵二是處理屏幕發(fā)來的指令隊列。中斷負責收數(shù)據(jù)和定時發(fā)送。整體架構(gòu)是“中斷收、中斷發(fā)、主循環(huán)處理邏輯”這樣實時性好也不會阻塞。int main(void) { USART1_Init(115200); TIM2_Init(99, 7199); // 100ms LED_Init(); NVIC_Config(); HMI_SendCmd(t0, txt, \Ready\); while(1) { // 處理接收到的指令 // 其他邏輯 } }6. 常見問題排查與避坑經(jīng)驗實錄6.1 屏幕不響應指令的排查順序遇到屏幕沒反應按這個順序查第一波特率兩邊是否一致第二TX/RX是否交叉連接第三指令末尾三個0xFF是否發(fā)全第四控件名是否和屏幕工程里完全一致大小寫敏感第五屏幕工程是否下載成功。我踩過的坑是控件名在屏幕里叫“t0”代碼里寫成“T0”結(jié)果怎么都不動查了半天。6.2 收到亂碼或半截指令亂碼通常是波特率不對或者晶振不起振。F103最小系統(tǒng)板如果外部晶振沒焊好系統(tǒng)會切到內(nèi)部RC主頻變成8MHz分頻出來的波特率就偏了。用示波器看PA9波形或者直接量晶振引腳有沒有起振。半截指令一般是發(fā)送太快屏幕緩沖區(qū)溢出降低發(fā)送頻率或者加延時能解決。6.3 觸摸事件誤觸發(fā)有時候沒按按鈕屏幕卻回傳了按下事件。這可能是屏幕工程里按鈕的“觸摸效果”設成了“持續(xù)”或者串口干擾。我在按鈕屬性里把觸摸效果改成“瞬變”并且給串口線加了磁環(huán)誤觸發(fā)就沒了。6.4 波形控件不顯示曲線波形控件加數(shù)據(jù)點值必須在0到255之間。我一開始傳了溫度值25.6屏幕不認因為帶小數(shù)點。后來改成傳整數(shù)把溫度映射到0到255范圍曲線就出來了。映射公式value (int)((temp - min) / (max - min) * 255)。6.5 常見問題速查表現(xiàn)象可能原因解決方法屏幕無顯示供電不足單獨給屏幕5V供電指令無響應波特率不一致兩邊都設115200收到亂碼晶振未起振檢查晶振和負載電容觸摸無回傳TX/RX接反交叉連接曲線不滾動發(fā)送頻率太低提高到100ms一次數(shù)值顯示不全字符串超長控件寬度調(diào)大或縮短文本6.6 幾個提升穩(wěn)定性的小技巧第一串口發(fā)送前先判斷TC標志確保上一個字節(jié)發(fā)完再發(fā)下一個不然會丟字節(jié)。第二接收中斷里不要做耗時操作把數(shù)據(jù)存緩沖區(qū)主循環(huán)再解析。第三屏幕工程里把不用的控件刪掉減少內(nèi)存占用。第四給STM32的串口引腳加TVS管防止靜電打壞。第五長期運行的話定時器里加個看門狗喂狗防止死機。7. 代碼組織與后續(xù)擴展方向7.1 工程文件結(jié)構(gòu)我把代碼分成幾個文件main.c放主循環(huán)和初始化usart.c放串口配置和收發(fā)函數(shù)hmi.c放屏幕指令封裝timer.c放定時器配置led.c放LED控制。這樣結(jié)構(gòu)清晰改哪部分找哪個文件。頭文件里用extern聲明全局函數(shù)避免重復定義。7.2 后續(xù)可以加的功能這套框架搭好后擴展很容易。比如加個DHT11溫濕度傳感器把濕度也顯示到屏幕上加個繼電器屏幕上按按鈕控制繼電器開關(guān)加個SD卡把數(shù)據(jù)存下來甚至加個ESP8266把數(shù)據(jù)傳到手機上看。串口屏那邊只需要加控件和對應指令STM32端加采集和發(fā)送邏輯就行。7.3 關(guān)于國產(chǎn)替代芯片的兼容性前面提到國產(chǎn)替代的F103C8T6我實測過幾塊標準庫代碼直接燒進去能跑串口、定時器、GPIO都正常。但要注意有些替代片的ADC精度和內(nèi)部溫度傳感器有偏差如果做精密采集建議外接基準或者用外部傳感器。做顯示和交互這種數(shù)字邏輯完全沒問題。7.4 最后分享一個調(diào)試習慣我調(diào)試串口通信時習慣在STM32端加一個“回環(huán)”功能收到什么就發(fā)回什么。這樣用串口助手發(fā)指令能立刻看到STM32收到了什么判斷是發(fā)送問題還是接收問題。這個習慣幫我省了很多時間你也可以試試。整套聯(lián)調(diào)下來最深的體會是串口屏的難點不在技術(shù)而在細心??丶?、波特率、結(jié)束符任何一個細節(jié)錯了現(xiàn)象都是“沒反應”但原因各不相同。把排查順序理清楚一個個排除問題總能解決。代碼我已經(jīng)跑通屏幕上的數(shù)字跳得穩(wěn)穩(wěn)的曲線滾得順順的按鈕按下去LED跟著亮這套東西拿去做課程設計或者小產(chǎn)品原型完全夠用。