戰(zhàn))
1. 從一次嵌入式項(xiàng)目選型說起Qt 6.8 LTS 和 Qt for MCUs 2.9 到底意味著什么去年底我接手了一個(gè)工業(yè)HMI項(xiàng)目主控是一顆Cortex-M7的MCU屏幕分辨率800x480需求里明確要求界面要有動(dòng)畫過渡、支持觸摸滑動(dòng)、最好能跑個(gè)像樣的圖表。當(dāng)時(shí)團(tuán)隊(duì)里分成兩派一派主張上LinuxQt另一派覺得成本壓不住想用裸機(jī)LVGL。爭論了整整兩周最后卡在一個(gè)問題上——MCU上到底能不能跑Qt跑起來是什么效果。這個(gè)問題的答案在Qt 6.8 LTS和Qt for MCUs 2.9發(fā)布之后變得比之前清晰了很多。Qt 6.8是Qt 6系列的第三個(gè)LTS版本意味著它會(huì)有長期的補(bǔ)丁維護(hù)對(duì)于商業(yè)項(xiàng)目來說這是硬指標(biāo)——你不可能用一個(gè)每半年就停止維護(hù)的版本去交付工業(yè)設(shè)備。而Qt for MCUs 2.9最大的變化是正式支持了Zephyr RTOS這件事對(duì)嵌入式圈子來說比版本號(hào)本身更有分量。這篇文章不是官方發(fā)布稿的復(fù)述。我想從一個(gè)實(shí)際做項(xiàng)目的人的角度把這兩個(gè)版本里真正影響開發(fā)決策的東西拆開講LTS到底鎖定了什么、Zephyr支持意味著什么、MCU上跑Qt的真實(shí)邊界在哪里、以及從老版本遷移時(shí)那些文檔里不會(huì)寫的坑。如果你正在做嵌入式GUI選型或者手上有個(gè)Qt項(xiàng)目要升級(jí)這篇內(nèi)容應(yīng)該能幫你省掉不少試錯(cuò)時(shí)間。2. Qt 6.8 LTSLTS這三個(gè)字母背后的實(shí)際約束2.1 LTS版本對(duì)商業(yè)項(xiàng)目的真正價(jià)值很多人看到LTS第一反應(yīng)是穩(wěn)定這個(gè)理解對(duì)但不夠具體。Qt的LTS策略實(shí)際包含三層含義補(bǔ)丁維護(hù)周期、API凍結(jié)承諾、商業(yè)授權(quán)支持窗口。Qt 6.8 LTS作為長期支持版本會(huì)獲得持續(xù)的bug修復(fù)和安全補(bǔ)丁而普通版本比如6.7、6.9在下一個(gè)版本發(fā)布后基本就進(jìn)入維護(hù)尾聲了。對(duì)做工業(yè)設(shè)備、醫(yī)療儀器、車載終端的團(tuán)隊(duì)來說這意味著你的產(chǎn)品生命周期內(nèi)不需要被迫升級(jí)Qt版本。我見過太多項(xiàng)目因?yàn)橛昧朔荓TS版本兩年后客戶報(bào)了一個(gè)bug結(jié)果發(fā)現(xiàn)官方已經(jīng)不再為該版本提供修復(fù)只能自己啃源碼或者整體升級(jí)——后者往往牽一發(fā)動(dòng)全身。提示如果你現(xiàn)在還在用Qt 5.15.x6.8 LTS是跳轉(zhuǎn)的合理節(jié)點(diǎn)。5.15到6.x的遷移成本在6.8上已經(jīng)比6.2時(shí)期低了很多大部分模塊的API已經(jīng)穩(wěn)定。2.2 Qt 6.8里幾個(gè)容易被忽略但很實(shí)用的變化官方發(fā)布說明里列了一長串改進(jìn)但從實(shí)際開發(fā)角度我挑幾個(gè)真正會(huì)改變你寫代碼方式的點(diǎn)。第一是QCharts的渲染性能優(yōu)化。之前用QChart畫實(shí)時(shí)曲線數(shù)據(jù)點(diǎn)超過幾千個(gè)就開始卡很多人被迫轉(zhuǎn)去用QCustomPlot或者自己用QPainter畫。6.8里圖表模塊的底層渲染路徑做了調(diào)整實(shí)測在同樣硬件上萬級(jí)數(shù)據(jù)點(diǎn)的刷新幀率有明顯提升。當(dāng)然如果你要做的是每秒幾萬點(diǎn)的示波器級(jí)應(yīng)用該自己畫還是得自己畫。第二是網(wǎng)絡(luò)模塊對(duì)HTTP/2的支持更完整了。之前QNetworkAccessManager對(duì)HTTP/2的支持有些邊角情況處理得不好比如服務(wù)端推送、流優(yōu)先級(jí)這些。6.8里這部分補(bǔ)齊了不少對(duì)于需要和現(xiàn)代后端服務(wù)通信的應(yīng)用來說少了很多手動(dòng)處理的麻煩。第三是CMake構(gòu)建系統(tǒng)的進(jìn)一步統(tǒng)一。Qt 6從qmake向CMake遷移是大趨勢6.8里CMake相關(guān)的模塊定義、依賴查找更加規(guī)范。如果你還在用qmake現(xiàn)在是個(gè)逐步切換的好時(shí)機(jī)——不是說明天qmake就不能用了而是新項(xiàng)目的工具鏈生態(tài)越來越圍繞CMake展開。2.3 從Qt 5遷移到6.8 LTS時(shí)最常踩的三個(gè)坑遷移這件事官方文檔寫得再全實(shí)際操作中還是會(huì)遇到意料之外的問題。我總結(jié)了自己和周圍同行遇到最多的三類??右籕RegExp徹底移除。Qt 6里QRegExp被QRegularExpression取代這不是簡單改個(gè)類名的事。兩者的語法有差異特別是反向引用和某些斷言寫法。我遇到過一個(gè)項(xiàng)目正則表達(dá)式在5.15下工作正常遷到6.8后匹配結(jié)果完全不對(duì)排查了半天才發(fā)現(xiàn)是語法不兼容。建議遷移時(shí)把所有正則表達(dá)式拿出來單獨(dú)測試一遍??佣String的隱式轉(zhuǎn)換收緊。Qt 6對(duì)QString和char*之間的隱式轉(zhuǎn)換做了限制很多在5.x下能編譯的代碼在6.x下會(huì)報(bào)錯(cuò)。這個(gè)其實(shí)是好事避免了編碼問題但遷移時(shí)會(huì)有大量編譯錯(cuò)誤需要處理。我的做法是先集中把這類錯(cuò)誤改完再處理邏輯層面的問題避免混在一起。坑三高DPI處理的默認(rèn)行為變化。Qt 6默認(rèn)啟用了高DPI縮放這在4K屏幕上效果很好但在一些工業(yè)觸摸屏上可能導(dǎo)致界面元素尺寸不符合預(yù)期。如果目標(biāo)設(shè)備是固定分辨率的嵌入式屏可能需要顯式設(shè)置縮放策略。// Qt 6中控制高DPI行為的典型寫法 QApplication::setHighDpiScaleFactorRoundingPolicy( Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);這段代碼在遷移時(shí)經(jīng)常需要加上具體用哪種策略取決于你的目標(biāo)屏幕和設(shè)計(jì)稿的匹配情況。3. Qt for MCUs 2.9 Zephyr RTOSMCU上跑Qt的真實(shí)邊界3.1 為什么Zephyr支持是個(gè)大事Qt for MCUs不是新東西但之前它主要支持FreeRTOS和裸機(jī)環(huán)境。Zephyr RTOS這兩年在嵌入式圈子的勢頭很猛特別是需要網(wǎng)絡(luò)協(xié)議棧、設(shè)備驅(qū)動(dòng)框架、電源管理的場景。2.9版本正式支持Zephyr意味著你可以在一套已經(jīng)跑著Zephyr的MCU系統(tǒng)上直接疊加Qt的GUI層不需要為了GUI去換RTOS。這件事的實(shí)際意義在于系統(tǒng)集成成本的降低。以前如果項(xiàng)目已經(jīng)選了Zephyr做底層想加Qt界面就得做大量移植工作或者干脆換成FreeRTOS。現(xiàn)在兩條路可以并存對(duì)于物聯(lián)網(wǎng)設(shè)備、智能家居面板、工業(yè)傳感器網(wǎng)關(guān)這類產(chǎn)品來說選型靈活度高了很多。3.2 MCU上跑Qt的性能邊界在哪里這是所有人最關(guān)心的問題。我用STM32H7系列Cortex-M7480MHz帶SDRAM做過測試說幾個(gè)實(shí)際數(shù)據(jù)。分辨率方面800x480是舒適區(qū)1024x600可以跑但動(dòng)畫要精簡再往上就比較吃力了。這不是Qt本身的問題而是MCU的顯存帶寬和GPU能力決定的。大部分MCU沒有獨(dú)立GPU靠DMA2D或者軟件渲染像素填充率是硬瓶頸。動(dòng)畫方面簡單的位移、透明度變化、顏色過渡可以做到流暢但復(fù)雜的粒子效果、大面積模糊、多層疊加的視差滾動(dòng)就不要想了。Qt for MCUs用的是QML的一個(gè)子集叫Qt Quick Ultralite它針對(duì)MCU做了大量裁剪不支持的特性比支持的還多。內(nèi)存方面這是最需要精打細(xì)算的。一個(gè)中等復(fù)雜度的界面RAM占用通常在幾MB到十幾MB之間具體取決于你用了多少圖片資源、多少動(dòng)態(tài)元素。Flash占用方面Qt Quick Ultralite的運(yùn)行時(shí)加上你的應(yīng)用代碼通常在2-5MB范圍。資源類型典型占用范圍優(yōu)化方向RAM運(yùn)行時(shí)2-8 MB減少動(dòng)態(tài)對(duì)象、復(fù)用緩沖區(qū)RAM幀緩沖分辨率x色深x緩沖數(shù)用單緩沖局部刷新FlashQt運(yùn)行時(shí)1.5-3 MB裁剪不需要的模塊Flash應(yīng)用資源1-4 MB圖片壓縮、字體子集化3.3 Qt Quick Ultralite和標(biāo)準(zhǔn)QML的差異清單如果你之前做的是桌面或移動(dòng)端QML開發(fā)轉(zhuǎn)到MCU上會(huì)發(fā)現(xiàn)很多熟悉的東西不見了。這不是bug是設(shè)計(jì)取舍。不支持的JavaScript動(dòng)態(tài)執(zhí)行大部分、ShaderEffect、粒子系統(tǒng)、復(fù)雜的錨點(diǎn)布局嵌套、動(dòng)態(tài)創(chuàng)建大量QML對(duì)象、網(wǎng)絡(luò)請(qǐng)求相關(guān)的QML類型。支持的基礎(chǔ)QML類型Rectangle、Text、Image、MouseArea等、狀態(tài)和過渡、簡單的動(dòng)畫、ListView但要注意性能、屬性綁定有限制。寫法上的關(guān)鍵差異MCU上的QML更強(qiáng)調(diào)靜態(tài)聲明盡量避免運(yùn)行時(shí)創(chuàng)建對(duì)象。屬性綁定也要謹(jǐn)慎使用因?yàn)槊看谓壎ㄇ笾刀加虚_銷。我通常建議把能在C側(cè)算好的東西都在C側(cè)算完QML只負(fù)責(zé)展示。// MCU上推薦的寫法靜態(tài)聲明減少綁定 Rectangle { width: 200 height: 100 color: #2d2d2d Text { id: label anchors.centerIn: parent text: Pressure: sensorValue // 簡單綁定可以 color: white } }3.4 在Zephyr上集成Qt for MCUs的實(shí)操要點(diǎn)如果你決定走Zephyr Qt for MCUs這條路有幾個(gè)環(huán)節(jié)需要特別注意。第一是板級(jí)支持包的匹配。Qt for MCUs對(duì)Zephyr的支持是綁定特定板子的不是所有Zephyr支持的板子都能直接跑。你需要確認(rèn)你的目標(biāo)硬件在Qt的supported platforms列表里或者做好自己移植BSP的準(zhǔn)備。移植工作主要涉及顯示驅(qū)動(dòng)、觸摸驅(qū)動(dòng)、時(shí)鐘配置這幾塊。第二是內(nèi)存布局的規(guī)劃。Zephyr有自己的內(nèi)存管理機(jī)制Qt for MCUs也有自己的內(nèi)存分配策略兩者需要協(xié)調(diào)。通常的做法是把幀緩沖和Qt的堆放在外部SDRAM里Zephyr的內(nèi)核對(duì)象放在內(nèi)部SRAM里。鏈接腳本需要仔細(xì)調(diào)整否則容易出現(xiàn)內(nèi)存溢出或者性能問題。第三是構(gòu)建系統(tǒng)的整合。Zephyr用west CMake構(gòu)建Qt for MCUs也有自己的構(gòu)建流程。2.9版本在這方面做了不少工作但實(shí)際項(xiàng)目中還是可能需要手動(dòng)調(diào)整一些配置。我的經(jīng)驗(yàn)是先把Qt for MCUs的示例工程在目標(biāo)板上跑通再逐步把Zephyr的應(yīng)用代碼合并進(jìn)來不要一上來就試圖整合一個(gè)復(fù)雜項(xiàng)目。注意Zephyr的版本和Qt for MCUs 2.9支持的版本有對(duì)應(yīng)關(guān)系不要隨意混用。版本不匹配導(dǎo)致的編譯錯(cuò)誤往往很難排查。4. 版本選型決策什么項(xiàng)目該上6.8 LTS什么項(xiàng)目該考慮MCUs方案4.1 一張表幫你判斷該用哪個(gè)Qt選型這件事沒有絕對(duì)答案但可以根據(jù)幾個(gè)關(guān)鍵維度快速縮小范圍。判斷維度選Qt 6.8 LTS標(biāo)準(zhǔn)版選Qt for MCUs 2.9硬件平臺(tái)Cortex-A / x86 / RISC-V帶MMUCortex-M / RISC-V無MMU操作系統(tǒng)Linux / Windows / QNX / AndroidZephyr / FreeRTOS / 裸機(jī)內(nèi)存128MB以上4-32MB界面復(fù)雜度高多窗口、復(fù)雜動(dòng)畫、3D中低單窗口、簡單動(dòng)畫開發(fā)語言C QML完整版C QMLUltralite子集啟動(dòng)時(shí)間要求秒級(jí)毫秒級(jí)成本敏感度中低高這張表不是絕對(duì)的但如果你在某個(gè)維度上明顯偏向一邊選型方向基本就定了。最怕的是硬件選了MCU但需求按LinuxQt的復(fù)雜度來提那后期會(huì)很痛苦。4.2 混合架構(gòu)MCU跑界面主控跑邏輯實(shí)際項(xiàng)目中還有一種常見架構(gòu)主控芯片跑LinuxQt 6.8做復(fù)雜邏輯和數(shù)據(jù)處理MCU跑Qt for MCUs做實(shí)時(shí)界面顯示兩者通過串口或SPI通信。這種方案在車載儀表、工業(yè)控制面板上很常見。這種架構(gòu)的好處是各司其職MCU保證界面的實(shí)時(shí)響應(yīng)和快速啟動(dòng)主控負(fù)責(zé)復(fù)雜的業(yè)務(wù)邏輯。壞處是通信協(xié)議要設(shè)計(jì)好數(shù)據(jù)同步和狀態(tài)管理會(huì)增加復(fù)雜度。我做過的一個(gè)項(xiàng)目里MCU負(fù)責(zé)儀表盤的指針動(dòng)畫和報(bào)警燈顯示主控負(fù)責(zé)導(dǎo)航和媒體播放兩者通過CAN總線通信整體效果不錯(cuò)但調(diào)試通信協(xié)議花了不少時(shí)間。4.3 從Qt 5.15 LTS升級(jí)到6.8 LTS的決策框架如果你現(xiàn)在還在5.15 LTS上要不要升6.8我的建議是分情況。建議升級(jí)的情況新項(xiàng)目立項(xiàng)、需要用到Qt 6特有的功能比如更好的CMake支持、改進(jìn)的QML引擎、目標(biāo)平臺(tái)是較新的硬件、團(tuán)隊(duì)有精力做遷移測試??梢詴壕彽那闆r現(xiàn)有項(xiàng)目穩(wěn)定運(yùn)行且沒有新功能需求、依賴的第三方庫還沒有Qt 6版本、團(tuán)隊(duì)人手緊張且遷移風(fēng)險(xiǎn)不可控。如果決定升級(jí)我的建議是分模塊逐步遷移不要一次性全量切換。先把構(gòu)建系統(tǒng)從qmake切到CMake再把核心模塊遷到Qt 6最后處理UI層。每一步都保證有可運(yùn)行的版本這樣出問題容易定位。5. 實(shí)操中那些文檔不會(huì)告訴你的細(xì)節(jié)5.1 Qt 6.8在嵌入式Linux上的部署清單在嵌入式Linux上部署Qt 6.8有幾個(gè)環(huán)節(jié)容易出問題。平臺(tái)插件方面Qt 6默認(rèn)使用eglfs或者linuxfb作為嵌入式平臺(tái)的顯示后端。如果你的設(shè)備有GPUeglfs是首選如果沒有l(wèi)inuxfb是備選但性能有限。配置的時(shí)候要注意環(huán)境變量QT_QPA_PLATFORM的設(shè)置以及相關(guān)的eglfs集成參數(shù)。# 典型的eglfs啟動(dòng)配置 export QT_QPA_PLATFORMeglfs export QT_QPA_EGLFS_INTEGRATIONeglfs_kms export QT_QPA_EGLFS_KMS_CONFIG/etc/qt/kms.json字體方面嵌入式系統(tǒng)通常沒有完整的字體庫需要手動(dòng)部署。Qt 6對(duì)字體子集化的支持比5.x好可以用工具把TTF字體裁剪成只包含用到的字符能省不少Flash空間。觸摸校準(zhǔn)方面如果是電阻屏需要配置tslib或者libinput的校準(zhǔn)參數(shù)。電容屏一般不需要校準(zhǔn)但要注意觸摸事件和鼠標(biāo)事件的映射關(guān)系。5.2 Qt for MCUs的資源優(yōu)化實(shí)戰(zhàn)技巧MCU上資源緊張優(yōu)化是永恒的話題。分享幾個(gè)我實(shí)際用過的技巧。圖片資源方面盡量用索引色或者壓縮格式。Qt for MCUs支持RLE壓縮的圖片對(duì)于圖標(biāo)類資源效果很好。另外能用矢量描述的就不要用位圖比如簡單的幾何圖形直接用QML的Rectangle和Shape畫比貼圖省資源。字體方面MCU上通常只需要顯示數(shù)字和少量文字用字體子集化工具把用到的字符提取出來一個(gè)完整的TTF字體可能幾MB子集化后可能只有幾十KB。動(dòng)畫方面能用屬性動(dòng)畫就不要用幀動(dòng)畫。屬性動(dòng)畫是運(yùn)行時(shí)計(jì)算的幀動(dòng)畫需要預(yù)存每一幀的圖片資源占用差距很大。另外動(dòng)畫的幀率不要盲目追求60fps30fps在很多場景下已經(jīng)足夠流暢但CPU占用能降不少。5.3 常見編譯和運(yùn)行問題速查問題現(xiàn)象可能原因排查方向編譯報(bào)錯(cuò)找不到Qt模塊CMake配置不完整檢查find_package和target_link_libraries運(yùn)行時(shí)黑屏平臺(tái)插件未加載檢查QT_QPA_PLATFORM和插件路徑界面卡頓渲染后端選擇不當(dāng)嘗試切換eglfs/linuxfb/software觸摸無響應(yīng)輸入設(shè)備未識(shí)別檢查evdev配置和權(quán)限內(nèi)存溢出資源未釋放用valgrind或Qt自帶工具分析字體顯示方塊字體缺失或編碼問題確認(rèn)字體文件和字符集配置這張表里的問題我基本都遇到過排查思路比具體答案更重要。嵌入式開發(fā)的特點(diǎn)就是環(huán)境差異大同樣的問題在不同板子上原因可能完全不同。6. 我個(gè)人的版本跟進(jìn)策略做了這么多年Qt項(xiàng)目我自己的策略是生產(chǎn)項(xiàng)目鎖LTS學(xué)習(xí)研究追最新。Qt 6.8 LTS發(fā)布后我手上兩個(gè)在維護(hù)的項(xiàng)目會(huì)在下一個(gè)迭代周期評(píng)估升級(jí)但不會(huì)立刻切。新立項(xiàng)的項(xiàng)目直接用6.8 LTS起步。Qt for MCUs這邊如果客戶項(xiàng)目涉及Zephyr2.9是必選如果還是FreeRTOS2.8也能用但2.9的改進(jìn)值得跟進(jìn)。還有一點(diǎn)體會(huì)是Qt的版本發(fā)布節(jié)奏比較快但真正影響項(xiàng)目決策的往往是那些支持了什么新平臺(tái)修復(fù)了什么長期問題這類變化而不是版本號(hào)本身。6.8 LTS和MCUs 2.9這兩個(gè)版本前者是給桌面和嵌入式Linux項(xiàng)目的一個(gè)穩(wěn)定錨點(diǎn)后者是給MCU GUI開發(fā)打開了一扇新門。具體怎么選還是得回到你的硬件、需求和團(tuán)隊(duì)能力上來判斷。