戰(zhàn):TaoToken統(tǒng)一Key打通嵌入式輸入鏈路)
1. QT/E 觸摸屏從驅(qū)動(dòng)到應(yīng)用層為什么總斷鏈Q(jìng)T/E 觸摸屏支持這件事說(shuō)穿了就是把一條鏈路打通觸摸面板產(chǎn)生中斷內(nèi)核 input 子系統(tǒng)上報(bào)事件tslib 做校準(zhǔn)和濾波QT/E 的鼠標(biāo)協(xié)議插件讀取設(shè)備節(jié)點(diǎn)最后 QWS 或 EGLFS 把坐標(biāo)投遞給你的 QWidget。這條鏈上任何一環(huán)斷了表現(xiàn)都是「點(diǎn)了沒(méi)反應(yīng)」或者「點(diǎn)左邊跳右邊」。工控 HMI 和手持終端開(kāi)發(fā)者最常遇到的就是驅(qū)動(dòng)明明加載了、cat /dev/input/event0也有數(shù)據(jù)但 QT 程序里就是不動(dòng)。我在一塊 i.MX6 工控板上調(diào)這塊花了整整兩天?,F(xiàn)象是命令行用ts_test畫(huà)線完全正常一跑./app -qws就失靈。后來(lái)發(fā)現(xiàn)是 QWS_MOUSE_PROTO 指向了/dev/h3600_tsraw這個(gè)默認(rèn)名而我的板子實(shí)際節(jié)點(diǎn)是/dev/input/event1。QT/E 的 LinuxTp 插件在編譯時(shí)把設(shè)備路徑寫(xiě)死了不改源碼或者不通過(guò)環(huán)境變量覆蓋它永遠(yuǎn)找不到正確的節(jié)點(diǎn)。這條鏈路涉及幾個(gè)關(guān)鍵概念先對(duì)齊一下。tslib 是觸摸屏的抽象層負(fù)責(zé)把原始 ADC 值通過(guò)pointercal校準(zhǔn)文件換算成屏幕像素坐標(biāo)同時(shí)做去抖和濾波。QT/E 的鼠標(biāo)協(xié)議插件qmouselinuxtp_qws.cpp對(duì)應(yīng) LinuxTpqmouselinuxinput_qws.cpp對(duì)應(yīng) LinuxInput負(fù)責(zé)從 tslib 或直接 from evdev 讀事件。QWS 是 QT/E 自帶的窗口系統(tǒng)EGLFS 是 QT5 之后基于 OpenGL ES 的全屏平臺(tái)插件兩者讀取觸摸參數(shù)的路徑不一樣。適合讀這篇的人正在做 QT/E 嵌入式 HMI、手持終端、醫(yī)療設(shè)備面板的開(kāi)發(fā)者已經(jīng)能交叉編譯 QT/E 庫(kù)但觸摸沒(méi)調(diào)通的以及從 QT4 QWS 遷移到 QT5 EGLFS 時(shí)觸摸配置對(duì)不上的。下面我按「先定位問(wèn)題、再給可復(fù)制配置、最后驗(yàn)證和排障」的順序?qū)懨恳徊蕉寄苤苯映侥愕陌遄由吓?。核心檢索詞先明確QT/E 觸摸屏支持、tslib 校準(zhǔn)配置、QWS_MOUSE_PROTO、EGLFS 觸摸參數(shù)、pointercal 文件、觸摸坐標(biāo)偏移排查。這些詞你搜到的多數(shù)文章只講一半我把驅(qū)動(dòng)到應(yīng)用層整條鏈補(bǔ)齊。2. TaoToken 統(tǒng)一 Key 在嵌入式輸入鏈路調(diào)試?yán)锏那爸脺?zhǔn)備調(diào)試觸摸鏈路時(shí)我經(jīng)常需要查 QT/E 源碼里鼠標(biāo)插件的實(shí)現(xiàn)、對(duì)比不同版本的qmouselinuxtp_qws.cpp差異或者讓模型幫我分析evtest的原始輸出格式。這些查詢?nèi)绻看味家袚Q不同的 API Key 和模型入口效率很低。TaoToken 在這里的作用是提供一個(gè)統(tǒng)一的 Key把模型對(duì)話、代碼補(bǔ)全、文檔檢索收斂到一個(gè)入口省掉在多個(gè)平臺(tái)之間倒騰憑證的時(shí)間。先說(shuō)清楚它是什么TaoToken 是一個(gè)大模型 API 聚合服務(wù)你用同一個(gè) Key 就能調(diào)用不同廠商的模型。對(duì)嵌入式開(kāi)發(fā)者來(lái)說(shuō)實(shí)際價(jià)值在于調(diào)試時(shí)可以讓模型讀你的evtest輸出、幫你寫(xiě) tslib 的校準(zhǔn)腳本、解釋 QT/E 源碼里的坐標(biāo)變換邏輯而不用為每個(gè)模型單獨(dú)申請(qǐng)和配置 Key。它適合誰(shuí)需要頻繁查文檔、讀源碼、寫(xiě)測(cè)試腳本又不想在憑證管理上花時(shí)間的開(kāi)發(fā)者。前置準(zhǔn)備分三步。第一步拿到 Key。訪問(wèn) API Keys 管理頁(yè)https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite登錄后創(chuàng)建一個(gè)新 Key復(fù)制保存。這個(gè) Key 后面在環(huán)境變量或配置文件里用。第二步確認(rèn)你的調(diào)用方式。如果你是在開(kāi)發(fā)機(jī)上用命令行工具查資料可以直接用 curl 測(cè)通如果你是在 IDE 里用插件比如 Cline、Continue需要在插件設(shè)置里填 Base URL 和 Key。Base URL 是https://taotoken.net/api注意這個(gè)地址不帶 UTM 參數(shù)直接寫(xiě)就行。第三步選模型。調(diào)試觸摸鏈路時(shí)我一般用擅長(zhǎng)讀代碼和長(zhǎng)文本的模型來(lái)分析源碼用響應(yīng)快的模型做日常查詢。你可以在模型對(duì)話頁(yè)https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite看當(dāng)前可用的模型列表記下你要用的 Model ID后面配置里要填。這里有個(gè)坑要提前說(shuō)TaoToken 是 API 服務(wù)不是編輯器插件本身也不是代理工具。它的作用是讓你用一個(gè) Key 調(diào)用模型你的 QT/E 代碼、tslib 配置、設(shè)備節(jié)點(diǎn)這些還是在你本地板子和開(kāi)發(fā)機(jī)上操作。別指望它替你編譯 QT 庫(kù)或者燒寫(xiě)文件系統(tǒng)。如果你打算長(zhǎng)期在嵌入式項(xiàng)目里用模型輔助編碼和調(diào)試可以了解 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite它面向的是持續(xù)編碼和 Agent 場(chǎng)景比單次調(diào)用更適合日常開(kāi)發(fā)流。但這一節(jié)的重點(diǎn)是先把 Key 拿到、Base URL 記好、Model ID 選好后面配置觸摸鏈路時(shí)隨時(shí)能查資料。3. tslib 校準(zhǔn)與 QWS/EGLFS 觸摸參數(shù)的可復(fù)制配置這一節(jié)是全文的核心我給出一套可以直接抄的配置。分三塊tslib 校準(zhǔn)、QWS 環(huán)境變量、EGLFS 參數(shù)。每塊都給出文件路徑和完整內(nèi)容。3.1 tslib 校準(zhǔn)與 pointercal 文件生成tslib 的校準(zhǔn)文件叫pointercal默認(rèn)放在/etc/pointercal。它是一行 7 個(gè)數(shù)字格式是a0 a1 a2 a3 a4 a5 a6前 6 個(gè)是坐標(biāo)變換系數(shù)第 7 個(gè)是縮放因子。你不需要手算用ts_calibrate生成即可。前提是你的板子上已經(jīng)編譯并安裝了 tslib且TSLIB_TSDEVICE指向正確的設(shè)備節(jié)點(diǎn)。先確認(rèn)設(shè)備節(jié)點(diǎn)ls /dev/input/ cat /proc/bus/input/devices找到你的觸摸屏對(duì)應(yīng)的 event 節(jié)點(diǎn)比如/dev/input/event1。然后設(shè)置環(huán)境變量并運(yùn)行校準(zhǔn)export TSLIB_TSDEVICE/dev/input/event1 export TSLIB_CALIBFILE/etc/pointercal export TSLIB_CONFFILE/etc/ts.conf export TSLIB_PLUGINDIR/usr/lib/ts ts_calibrate屏幕上會(huì)出現(xiàn)十字光標(biāo)依次點(diǎn)擊五個(gè)點(diǎn)。完成后/etc/pointercal就生成了。如果提示open /dev/input/event1: No such file or directory說(shuō)明節(jié)點(diǎn)名不對(duì)回到cat /proc/bus/input/devices重新確認(rèn)。/etc/ts.conf的內(nèi)容也要對(duì)常見(jiàn)配置如下module_raw input module pthres pmin1 module variance delta30 module dejitter delta100 module linearmodule_raw input表示從 Linux input 子系統(tǒng)讀原始事件。如果你用的是老式 ADC 觸摸屏可能是module_raw h3600之類(lèi)按你的驅(qū)動(dòng)來(lái)。module linear負(fù)責(zé)用 pointercal 做線性變換這行必須有。3.2 QWS 環(huán)境變量配置QT/E 在 QWS 模式下觸摸協(xié)議通過(guò)QWS_MOUSE_PROTO指定。格式是協(xié)議名:設(shè)備節(jié)點(diǎn)。如果你用 tslib協(xié)議名是Tslib如果直接用 Linux input協(xié)議名是LinuxInput。在板子的啟動(dòng)腳本里比如/etc/profile或你的應(yīng)用啟動(dòng)腳本加上export QTDIR/usr/local/qt-e export QWS_MOUSE_PROTOTslib:/dev/input/event1 export QWS_DISPLAYLinuxFb:/dev/fb0 export TSLIB_TSDEVICE/dev/input/event1 export TSLIB_CALIBFILE/etc/pointercal export TSLIB_CONFFILE/etc/ts.conf export TSLIB_PLUGINDIR/usr/lib/ts export LD_LIBRARY_PATH$QTDIR/lib:/usr/lib/ts:$LD_LIBRARY_PATH注意QWS_MOUSE_PROTO里的設(shè)備節(jié)點(diǎn)要和TSLIB_TSDEVICE一致。如果你在編譯 QT/E 時(shí)改了qmouselinuxtp_qws.cpp里的默認(rèn)路徑這里的環(huán)境變量會(huì)覆蓋它。如果你用的是 QT5 的 EGLFSQWS_MOUSE_PROTO 不再生效改用QT_QPA_EGLFS_TSLIB或QT_QPA_EGLFS_INTEGRATION。下一小節(jié)說(shuō)。3.3 EGLFS 觸摸參數(shù)配置QT5 之后EGLFS 平臺(tái)插件讀取觸摸的方式變了。你需要設(shè)置export QT_QPA_PLATFORMeglfs export QT_QPA_EGLFS_INTEGRATIONeglfs_integration export QT_QPA_EGLFS_TSLIB1 export QT_QPA_EGLFS_PHYSICAL_WIDTH154 export QT_QPA_EGLFS_PHYSICAL_HEIGHT86 export TSLIB_TSDEVICE/dev/input/event1 export TSLIB_CALIBFILE/etc/pointercal export TSLIB_CONFFILE/etc/ts.confQT_QPA_EGLFS_TSLIB1告訴 EGLFS 用 tslib 讀觸摸。物理寬高按你屏幕的實(shí)際毫米數(shù)填影響的是 DPI 計(jì)算不影響觸摸坐標(biāo)但填錯(cuò)會(huì)導(dǎo)致界面元素縮放異常。如果你不用 tslib直接用 libinput可以設(shè)export QT_QPA_EGLFS_INTEGRATIONeglfs_kms export QT_QPA_EGLFS_KMS_CONFIG/etc/qt-eglfs-kms.json然后在 JSON 里配觸摸設(shè)備。不過(guò)工控場(chǎng)景下 tslib 更常見(jiàn)因?yàn)樾?zhǔn)文件好管理。3.4 設(shè)備節(jié)點(diǎn)映射的 udev 規(guī)則板子重啟后 event 節(jié)點(diǎn)號(hào)可能變導(dǎo)致配置失效。用 udev 規(guī)則固定節(jié)點(diǎn)名# /etc/udev/rules.d/99-touchscreen.rules SUBSYSTEMinput, ATTRS{name}Goodix-TS, SYMLINKinput/touchscreen這樣無(wú)論 event 號(hào)怎么變/dev/input/touchscreen始終指向你的觸摸屏。然后把所有配置里的/dev/input/event1換成/dev/input/touchscreen。ATTRS{name}的值從cat /proc/bus/input/devices里的Name拿。4. 觸摸坐標(biāo)驗(yàn)證與多點(diǎn)觸控回歸測(cè)試配置寫(xiě)完怎么確認(rèn)真的通了分三步驗(yàn)證原始事件、tslib 層、QT 應(yīng)用層。4.1 用 evtest 看原始事件evtest /dev/input/event1手指點(diǎn)屏幕你應(yīng)該看到類(lèi)似輸出Event: time 1690000000.123456, type 3 (EV_ABS), code 57 (ABS_MT_TRACKING_ID), value 0 Event: time 1690000000.123456, type 3 (EV_ABS), code 53 (ABS_MT_POSITION_X), value 400 Event: time 1690000000.123456, type 3 (EV_ABS), code 54 (ABS_MT_POSITION_Y), value 300 Event: time 1690000000.123456, type 1 (EV_KEY), code 330 (BTN_TOUCH), value 1如果這里沒(méi)輸出問(wèn)題在驅(qū)動(dòng)層跟 QT 無(wú)關(guān)。檢查設(shè)備樹(shù)里觸摸屏的 interrupt 和 reset 引腳配置。4.2 用 ts_test 驗(yàn)證 tslib 層export TSLIB_TSDEVICE/dev/input/event1 ts_test屏幕上會(huì)出現(xiàn)一個(gè)十字你手指移動(dòng)時(shí)十字跟著動(dòng)。如果十字不動(dòng)或者亂跳檢查/etc/ts.conf里的module_raw和module linear是否加載。如果坐標(biāo)偏移重新跑ts_calibrate。4.3 QT 應(yīng)用層驗(yàn)證寫(xiě)一個(gè)最小 QT 程序打印觸摸坐標(biāo)#include QApplication #include QWidget #include QMouseEvent #include QDebug class TouchWidget : public QWidget { protected: void mousePressEvent(QMouseEvent *e) override { qDebug() press: e-pos() global: e-globalPos(); } void mouseMoveEvent(QMouseEvent *e) override { qDebug() move: e-pos(); } }; int main(int argc, char **argv) { QApplication app(argc, argv); TouchWidget w; w.resize(800, 480); w.show(); return app.exec(); }編譯后運(yùn)行./touch_test -qws點(diǎn)屏幕終端應(yīng)該打印坐標(biāo)。如果沒(méi)打印檢查QWS_MOUSE_PROTO是否設(shè)置、設(shè)備節(jié)點(diǎn)是否存在、LD_LIBRARY_PATH是否包含 tslib 庫(kù)。4.4 多點(diǎn)觸控回歸測(cè)試如果你的觸摸屏支持多點(diǎn)用evtest看 ABS_MT_SLOT 和 ABS_MT_TRACKING_ID 事件。QT/E 的 QWS 對(duì)多點(diǎn)支持有限QT5 的 EGLFS 配合 libinput 支持更好。測(cè)試動(dòng)作# 同時(shí)用兩根手指點(diǎn)屏幕觀察 evtest 輸出 evtest /dev/input/event1 | grep -E ABS_MT_SLOT|ABS_MT_TRACKING_ID|ABS_MT_POSITION如果只有單點(diǎn)事件說(shuō)明驅(qū)動(dòng)沒(méi)上報(bào)多點(diǎn)檢查設(shè)備樹(shù)里touchscreen-max-points或類(lèi)似屬性。回歸測(cè)試腳本可以寫(xiě)成#!/bin/bash # touch_regression.sh DEV/dev/input/event1 echo 開(kāi)始觸摸回歸測(cè)試請(qǐng)依次點(diǎn)擊屏幕四角和中點(diǎn) for i in 1 2 3 4 5; do echo 請(qǐng)點(diǎn)擊第 $i 個(gè)點(diǎn)... timeout 10 evtest $DEV | head -20 echo --- done5. 本篇常見(jiàn)錯(cuò)排查401、local proxy failed、reading choices、OAuth調(diào)試觸摸鏈路時(shí)如果你同時(shí)用模型輔助查資料可能會(huì)遇到 API 調(diào)用報(bào)錯(cuò)。這些錯(cuò)和觸摸本身無(wú)關(guān)但會(huì)打斷你的調(diào)試節(jié)奏這里一并列出來(lái)。5.1 401 Unauthorized報(bào)錯(cuò)原文{error:{message:Invalid API key provided,type:invalid_request_error}}原因Key 填錯(cuò)、過(guò)期、或者復(fù)制時(shí)帶了空格。檢查你的環(huán)境變量或配置文件里的 Key確保沒(méi)有多余字符。如果你在 Cline 或 Continue 里配置注意 Base URL 要填https://taotoken.net/api不要多寫(xiě)路徑。5.2 local proxy failed報(bào)錯(cuò)原文Error: local proxy failed: dial tcp 127.0.0.1:7890: connect: connection refused原因你的開(kāi)發(fā)機(jī)上配了本地代理但代理服務(wù)沒(méi)啟動(dòng)。嵌入式開(kāi)發(fā)機(jī)經(jīng)常為了拉依賴配代理后來(lái)關(guān)了但環(huán)境變量還在。檢查http_proxy和https_proxyenv | grep -i proxy unset http_proxy https_proxy all_proxy然后重試。注意這里說(shuō)的是開(kāi)發(fā)機(jī)上的本地代理配置問(wèn)題不是讓你去用什么網(wǎng)絡(luò)工具只是清理殘留的環(huán)境變量。5.3 reading choices 報(bào)錯(cuò)報(bào)錯(cuò)原文Error: reading choices: unexpected end of JSON input原因模型返回的 JSON 被截?cái)嗤ǔJ?max_tokens 設(shè)太小或者網(wǎng)絡(luò)中斷。如果你在讓模型分析長(zhǎng)源碼把 max_tokens 調(diào)大或者分段發(fā)送。在 Cline 里可以在設(shè)置里調(diào)Max Tokens。5.4 OAuth 相關(guān)報(bào)錯(cuò)報(bào)錯(cuò)原文Error: OAuth token expired, please re-authenticate原因如果你用的是需要 OAuth 的客戶端比如某些 IDE 插件token 過(guò)期了。重新走一遍授權(quán)流程或者在插件設(shè)置里換成 API Key 方式。TaoToken 用的是 API Key不涉及 OAuth所以如果你在插件里看到這個(gè)錯(cuò)說(shuō)明插件還在用舊的認(rèn)證方式改成填 Base URL Key 即可。5.5 CC Switch / Cline MCP / Codex auth.json 三件套如果你用 CC Switch 管理多個(gè)模型配置或者用 Cline 的 MCP 功能或者用 Codex 的 auth.json記住三件套必須齊全Base URL、Key、Model ID。CC Switch 的配置示例{ name: taotoken, baseUrl: https://taotoken.net/api, apiKey: sk-你的Key, model: claude-3-5-sonnet-20241022 }Cline 的 MCP 配置在cline_mcp_settings.json{ mcpServers: { taotoken: { url: https://taotoken.net/api, apiKey: sk-你的Key } } }Codex 的auth.json{ apiKey: sk-你的Key, baseUrl: https://taotoken.net/api, model: gpt-4o }三件套缺一不可。只填 Key 不填 Base URL會(huì)走默認(rèn)端點(diǎn)只填 Base URL 不填 Model ID會(huì)報(bào)模型不存在。5.6 觸摸鏈路本身的常見(jiàn)錯(cuò)回到觸摸。如果你遇到open /dev/input/event1: Permission denied檢查設(shè)備節(jié)點(diǎn)權(quán)限ls -l /dev/input/event1 chmod 666 /dev/input/event1或者把你的應(yīng)用加到input組。如果 QT 程序報(bào)QWSMouseHandler: Cannot open mouse device檢查QWS_MOUSE_PROTO格式冒號(hào)前后不能有空格。如果坐標(biāo)偏移但ts_test正常檢查 QT 的屏幕分辨率是否和觸摸屏分辨率一致。QWS 下用-display參數(shù)指定EGLFS 下檢查QT_QPA_EGLFS_PHYSICAL_WIDTH/HEIGHT。6. 把統(tǒng)一 Key 接進(jìn)你的嵌入式調(diào)試流觸摸鏈路調(diào)通之后日常開(kāi)發(fā)里還有很多重復(fù)勞動(dòng)讀 QT/E 源碼找坐標(biāo)變換函數(shù)、寫(xiě) tslib 校準(zhǔn)的自動(dòng)化腳本、分析 evtest 日志、對(duì)比不同板子的設(shè)備樹(shù)配置。這些事如果每次都要手動(dòng)翻文檔很耗時(shí)間。我的做法是把 TaoToken 的 Key 配到開(kāi)發(fā)機(jī)的環(huán)境變量里然后在終端里用 curl 快速查詢export TAOTOKEN_KEYsk-你的Key curl -s https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_KEY \ -H Content-Type: application/json \ -d { model: claude-3-5-sonnet-20241022, messages: [{role:user,content:解釋 QT/E qmouselinuxtp_qws.cpp 里坐標(biāo)變換的流程}] }這樣在調(diào)試板子的時(shí)候不用切瀏覽器就能查源碼邏輯。如果你在 IDE 里寫(xiě) QT 代碼把 Key 配到 Cline 或 Continue 里寫(xiě)觸摸事件處理時(shí)可以直接讓模型補(bǔ)全。對(duì)于長(zhǎng)期做嵌入式 HMI 的Coding Plan 更適合因?yàn)樗嫦虺掷m(xù)編碼場(chǎng)景不用每次單獨(dú)調(diào)用。你可以從接入文檔https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite看具體的接入方式文檔里有各語(yǔ)言的示例。最后給一個(gè)實(shí)用技巧把觸摸調(diào)試的常用命令寫(xiě)成腳本配合模型查詢形成固定流程。比如#!/bin/bash # touch_debug.sh echo 設(shè)備節(jié)點(diǎn) cat /proc/bus/input/devices | grep -A5 -i touch echo tslib 校準(zhǔn)文件 cat /etc/pointercal 2/dev/null || echo pointercal 不存在 echo 環(huán)境變量 env | grep -E TSLIB|QWS_MOUSE|QT_QPA echo 原始事件測(cè)試5秒 timeout 5 evtest /dev/input/event1 | head -30每次板子出問(wèn)題先跑這個(gè)腳本把輸出貼給模型分析比你自己一行行查快得多。觸摸鏈路的問(wèn)題無(wú)非就是節(jié)點(diǎn)、權(quán)限、校準(zhǔn)、協(xié)議四類(lèi)腳本跑完基本能定位到哪一類(lèi)。