BLE數(shù)據(jù)透傳:從UART到APP的全鏈路調(diào)試指南)
做嵌入式這些年串口轉(zhuǎn)WiFi、串口轉(zhuǎn)藍(lán)牙這類需求沒少碰。最近手上一個(gè)案子用到WT2605C這顆帶BLE的音頻SoC做數(shù)據(jù)透傳從MCU的串口出來經(jīng)過BLE通道最后落到手機(jī)App上實(shí)時(shí)顯示。整個(gè)鏈路不算復(fù)雜但真正調(diào)起來涉及串口電平、BLE協(xié)議棧、GATT服務(wù)、App權(quán)限好幾個(gè)層面任何一個(gè)環(huán)節(jié)掉鏈子都讓人頭大。這篇文章就按我實(shí)際調(diào)試的順序把從串口到App的這條鏈路完整捋一遍。不管你是準(zhǔn)備用這顆料做傳感器數(shù)據(jù)采集還是想把老設(shè)備的串口“無線化”這中間的思路和踩過的坑應(yīng)該都能幫上忙。我會(huì)把硬件接線、串口配置、BLE參數(shù)、App端連接套路、數(shù)據(jù)分包處理這些關(guān)鍵環(huán)節(jié)逐個(gè)展開最后附一份排查問題速查表方便你直接抄作業(yè)。1. 鏈路全景WT2605C在串口轉(zhuǎn)BLE方案里的角色1.1 為什么用WT2605C而不是單買BLE透傳模塊先說說選型這件事。市面上做串口轉(zhuǎn)BLE的方案其實(shí)不少有純透傳模塊也有集成MCU的SoC。WT2605C比較特別的地方在于它本身是顆音頻藍(lán)牙SoC除了常規(guī)的BLE數(shù)傳還帶音頻通路等于一顆料同時(shí)覆蓋了音頻播放和數(shù)據(jù)透傳兩類場景。如果你的產(chǎn)品恰好既要播報(bào)語音、又要傳數(shù)據(jù)這顆料就很合適。如果只是純粹的數(shù)據(jù)透傳其它純BLE透傳模塊也能干但WT2605C在國產(chǎn)方案里資源豐富、SDK坑相對少價(jià)格也壓得比較低。從整個(gè)數(shù)據(jù)鏈路來看WT2605C的角色像一個(gè)“串口到BLE的橋”。它一邊通過UART和你的主控MCU通信一邊通過BLE和手機(jī)App通信。主控不需要懂藍(lán)牙協(xié)議只需要按照約定好的串口協(xié)議把數(shù)據(jù)丟給WT2605C剩下的事情芯片自己處理。反過來也一樣手機(jī)App通過BLE把數(shù)據(jù)寫進(jìn)來WT2605C再通過UART吐給主控。1.2 鏈路里每一跳的數(shù)據(jù)形態(tài)與責(zé)任分工完整鏈路拆開看是三個(gè)部分主控MCU到WT2605C這一段是UART串口通信WT2605C到手機(jī)App這一段是BLE GATT通信App內(nèi)部再把收到的數(shù)據(jù)解析、顯示或者上傳。每一跳的數(shù)據(jù)形態(tài)都不一樣。串口這邊是一幀一幀的字節(jié)流BLE這邊則是一個(gè)一個(gè)的ATT數(shù)據(jù)包中間還夾著MTU限制、重傳機(jī)制、分包處理這些事。很多人第一次調(diào)這種鏈路容易犯一個(gè)錯(cuò)誤把串口那邊的數(shù)據(jù)幀格式直接套用到BLE。實(shí)際上串口一幀可能是幾十上百字節(jié)而BLE默認(rèn)單包只有20字節(jié)可用中間必須做拆包和重組。這個(gè)會(huì)在后面專門講。先記住一條總原則串口側(cè)解決“幀”的問題BLE側(cè)解決“包”的問題App側(cè)解決“流”的問題三層各自處理各自的別混在一起。2. 硬件與初始化先把串口這半邊跑通2.1 最小系統(tǒng)接線與電平坑WT2605C的最小系統(tǒng)除了芯片本身電源、串口、天線這三樣是關(guān)鍵。電源部分這顆料主流是3.3V供電但有些模塊板載LDO也能接受5V輸入具體看你的板子設(shè)計(jì)。串口部分WT2605C的UART是TTL電平如果你的主控是3.3V的MCU直接對接沒問題如果主控是5V的就要注意電平匹配最好加電平轉(zhuǎn)換芯片或者確認(rèn)WT2605C的IO能容忍5V輸入否則長時(shí)間跑很容易燒IO口。接線無非就是TX、RX、GND三根線但這里有個(gè)經(jīng)典翻車點(diǎn)主控的TX要接WT2605C的RX主控的RX接WT2605C的TX也就是交叉連接。我當(dāng)初第一次焊板子就因?yàn)檫@個(gè)低級(jí)錯(cuò)誤調(diào)了整整一個(gè)下午最后用萬用表量電平才發(fā)現(xiàn)TX對TX接了。另外如果鏈路里牽涉到模塊的固件燒錄燒錄口和數(shù)傳串口經(jīng)常是復(fù)用關(guān)系這時(shí)候要看清模塊手冊別把燒錄線和數(shù)據(jù)線混用。2.2 串口參數(shù)怎么定波特率、流控、校驗(yàn)位串口通信雙方必須保證波特率、數(shù)據(jù)位、停止位、校驗(yàn)位完全一致。WT2605C的SDK里一般默認(rèn)是115200、8N1也就是波特率1152008個(gè)數(shù)據(jù)位無校驗(yàn)1個(gè)停止位。如果你要改成9600或者其它波特率需要同時(shí)改芯片端和主控端的配置并且改完芯片端要重新上電生效不是所有參數(shù)都支持熱切換。波特率選擇有一個(gè)容易忽略的點(diǎn)數(shù)傳場景里波特率越高單位時(shí)間能塞進(jìn)BLE通道的數(shù)據(jù)就越多但BLE的實(shí)際吞吐瓶頸不只在串口側(cè)。WT2605C這類芯片的BLE單連接實(shí)際有效吞吐一般也就在幾百字節(jié)每秒到1~2KB/s這個(gè)量級(jí)跟連接間隔、MTU有關(guān)所以如果業(yè)務(wù)數(shù)據(jù)量不大串口波特率設(shè)個(gè)9600到38400就完全夠用沒必要盲目拉高。數(shù)據(jù)量大的情況下真正要優(yōu)化的是BLE連接參數(shù)而不是串口波特率。流控方面除非你的數(shù)據(jù)量已經(jīng)逼近串口和BLE兩端的處理極限否則一般建議關(guān)閉硬件流控RTS/CTS少兩根線軟件上也省事。如果數(shù)據(jù)量大且出現(xiàn)丟包再考慮把流控打開同時(shí)確認(rèn)主控和WT2605C兩端都支持對應(yīng)的流控引腳。2.3 上電初始化順序?qū)LE廣播的影響WT2605C上電之后內(nèi)部固件需要一段時(shí)間做射頻校準(zhǔn)和協(xié)議棧初始化然后才會(huì)開始廣播。這個(gè)時(shí)間一般在一兩百毫秒到幾百毫秒之間。如果你在主控上電后立刻就往串口丟數(shù)據(jù)大概率會(huì)丟在最前面的一小段因?yàn)樾酒€沒準(zhǔn)備好。穩(wěn)妥的做法是主控等WT2605C的初始化完成信號(hào)或者干脆在主控代碼里做延時(shí)等待等芯片廣播指示燈亮起來之后再開始發(fā)送。如果板子上沒有指示燈也可以通過串口發(fā)一條AT指令或者查詢命令確認(rèn)芯片是否就緒。不同廠家的SDK指令集不一樣但一般都有類似的握手機(jī)制。這個(gè)等待機(jī)制看著小實(shí)際項(xiàng)目里如果沒處理很容易出現(xiàn)“上電后第一次數(shù)據(jù)永遠(yuǎn)發(fā)不出去”這種詭異問題排查一圈最后發(fā)現(xiàn)是初始化時(shí)序的鍋。3. BLE協(xié)議棧側(cè)廣播、連接與參數(shù)調(diào)優(yōu)3.1 廣播參數(shù)怎么配才容易被掃到WT2605C上電后會(huì)進(jìn)入廣播狀態(tài)相當(dāng)于不斷在喊“我在這快來連我”。廣播參數(shù)里最重要的兩個(gè)是廣播間隔和廣播數(shù)據(jù)內(nèi)容。廣播間隔單位是0.625ms可配置范圍一般在20ms到10.24s之間。間隔越短被掃描到的速度越快但功耗越高間隔越長越省電但手機(jī)端掃描體驗(yàn)會(huì)差一些。實(shí)際項(xiàng)目里如果設(shè)備是手持工具或者充電式設(shè)備廣播間隔建議設(shè)到100ms到200ms手機(jī)基本一兩秒內(nèi)就能掃到功耗也可以接受。如果是電池供電且長期處于待廣播狀態(tài)可以設(shè)到500ms甚至1s以上但要做好“用戶打開App要等幾秒才發(fā)現(xiàn)設(shè)備”的心理準(zhǔn)備。還有一個(gè)技巧廣播間隔可以加一點(diǎn)隨機(jī)延時(shí)比如設(shè)定100ms系統(tǒng)實(shí)際會(huì)在100ms到110ms之間隨機(jī)廣播這是BLE規(guī)范允許的可以減少多個(gè)設(shè)備同時(shí)廣播時(shí)的碰撞概率。廣播數(shù)據(jù)內(nèi)容方面至少要包含設(shè)備名稱和Service UUID。設(shè)備名稱就是手機(jī)端掃描列表里顯示的名字建議帶產(chǎn)品標(biāo)識(shí)前綴方便識(shí)別。Service UUID用來讓App做過濾App掃描時(shí)可以直接篩選只包含特定UUID的設(shè)備避免用戶在滿屏設(shè)備里找。注意廣播包大小有31字節(jié)限制設(shè)備名稱太長會(huì)占掉Service UUID的位置所以產(chǎn)品命名上盡量精簡短小。3.2 GATT服務(wù)結(jié)構(gòu)Server和Characteristic的關(guān)系BLE通信模式下數(shù)據(jù)通過GATT服務(wù)傳輸服務(wù)端是WT2605C客戶端是手機(jī)App。一個(gè)服務(wù)由多個(gè)Characteristic組成每個(gè)Characteristic有讀、寫、通知等不同的屬性。數(shù)傳應(yīng)用里標(biāo)準(zhǔn)做法是定義兩個(gè)核心Characteristic一個(gè)用來接收手機(jī)下發(fā)數(shù)據(jù)的寫通道一個(gè)用來向手機(jī)推送數(shù)據(jù)的通知通道。第一次接觸BLE的人容易把Characteristic類比成“寄存器”甚至“串口”其實(shí)它更像一個(gè)帶屬性標(biāo)注的信箱手機(jī)往“可寫”信箱里放數(shù)據(jù)設(shè)備通過“可通知”信箱主動(dòng)把數(shù)據(jù)推給手機(jī)。通知模式下數(shù)據(jù)的讀取完全靠手機(jī)訂閱訂閱成功之后每次芯片有數(shù)據(jù)就會(huì)主動(dòng)推送給手機(jī)。整個(gè)過程非常像MQTT的訂閱發(fā)布模型只是把Topic換成了UUID。WT2605C出廠固件一般自帶默認(rèn)的GATT配置但UUID和Characteristic結(jié)構(gòu)通常可以按需定制。如果你用的是SDK二次開發(fā)會(huì)通過配置表把Service UUID、Characteristic UUID、屬性類型定下來。這里建議UUID不要用0000xxxx之類的公共號(hào)段直接自己隨機(jī)生成128位的UUID避免和市面上其它BLE設(shè)備沖突。3.3 MTU協(xié)商、連接間隔與數(shù)據(jù)吞吐的取舍BLE默認(rèn)MTU是23字節(jié)扣除ATT協(xié)議頭3字節(jié)真正可用載荷是20字節(jié)。這意味著如果你一次性往串口丟200字節(jié)數(shù)據(jù)芯片藍(lán)牙層面至少要拆成10包才能發(fā)完。好在BLE支持MTU協(xié)商連接建立之后主從雙方可以協(xié)商一個(gè)更大的MTU比如185或247這樣單包可用載荷能提升到182甚至244字節(jié)吞吐量直接翻倍。MTU協(xié)商在APP端做還是設(shè)備端做取決于哪一側(cè)發(fā)起。通常是手機(jī)端GATT Client發(fā)起協(xié)商WT2605C作為服務(wù)端響應(yīng)。像Android的BluetoothGatt就提供了requestMtu方法iOS的CoreBluetooth默認(rèn)會(huì)嘗試協(xié)商大MTU開發(fā)者不需要額外操作。如果協(xié)商失敗或者沒有協(xié)商就按20字節(jié)一包走吞吐會(huì)低不少但功能上沒影響。連接間隔也是影響吞吐和功耗的關(guān)鍵參數(shù)。連接間隔范圍是7.5ms到4s間隔越小單位時(shí)間內(nèi)的通信窗口越多數(shù)據(jù)吞吐越大但功耗也越高。如果產(chǎn)品對實(shí)時(shí)性要求高比如控制類應(yīng)用間隔設(shè)在15ms到30ms比較合適如果是傳感器數(shù)據(jù)采集間隔30ms到50ms能兼顧吞吐和功耗。WT2605C實(shí)際支持的連接參數(shù)范圍以SDK為準(zhǔn)過小的間隔有些固件會(huì)拒絕。還有一個(gè)詞叫從機(jī)延遲Slave Latency它允許從機(jī)跳過若干個(gè)連接事件不監(jiān)聽省電效果非常明顯。但如果設(shè)得太大手機(jī)發(fā)數(shù)據(jù)給設(shè)備時(shí)設(shè)備沒有及時(shí)監(jiān)聽會(huì)有額外的延遲。所以在數(shù)傳應(yīng)用里建議從機(jī)延遲設(shè)成0換取低延遲和穩(wěn)定吞吐。4. 手機(jī)App從掃描到收數(shù)的完整套路4.1 掃描過濾用設(shè)備名還是MAC地址手機(jī)App這側(cè)第一步是掃描設(shè)備。Android和iOS的藍(lán)牙API雖然不同但掃描后的結(jié)果集長得差不多設(shè)備名、信號(hào)強(qiáng)度RSSI、廣播數(shù)據(jù)以及一個(gè)用來標(biāo)識(shí)設(shè)備的標(biāo)識(shí)符。這里有個(gè)非??拥牟町怉ndroid的BluetoothDevice.getAddress()返回的是MAC地址而iOS的CoreBluetooth里對應(yīng)的peripheral.identifier是一個(gè)UUID每次系統(tǒng)生成的UUID可能都不一樣重新掃描或者重啟藍(lán)牙之后甚至?xí)?。所以在做App過濾策略時(shí)千萬不要把iOS的identifier當(dāng)永久設(shè)備ID存下來。正確做法是以廣播包里的設(shè)備名或者自定義廣播數(shù)據(jù)里的設(shè)備唯一標(biāo)識(shí)為準(zhǔn)。比如你的產(chǎn)品在廣播數(shù)據(jù)里帶了一個(gè)序列號(hào)字段App掃描到這個(gè)序列號(hào)后在本地存一份下次再掃到同一個(gè)序列號(hào)就直接連接這樣iOS和Android都能保持一致體驗(yàn)。另外如果你用的是uni-app這類跨平臺(tái)框架iOS端“可以根據(jù)deviceId建立連接嗎”這個(gè)問題經(jīng)常有人問。答案是可以但要注意的是這個(gè)“deviceId”對應(yīng)的是CBPeripheral的UUID它只在本機(jī)當(dāng)前生命周期內(nèi)有效。如果你的應(yīng)用被殺掉重新搜索這個(gè)值可能已經(jīng)變了。所以uni-app里BLE連接的正確姿勢同樣是掃描過濾名字拿到deviceId后立即連接連接后不要緩存這個(gè)id做長期綁定。4.2 連接、發(fā)現(xiàn)服務(wù)、訂閱Notify的時(shí)序從掃描到收數(shù)的完整流程是掃描到目標(biāo)設(shè)備后發(fā)起連接連接成功后調(diào)用發(fā)現(xiàn)服務(wù)接口找到目標(biāo)Service后找到目標(biāo)Characteristic然后對通知通道執(zhí)行訂閱操作最后才能收到設(shè)備主動(dòng)推上來的數(shù)據(jù)。這個(gè)時(shí)序是固定的少一步都不行而且每一步都是異步回調(diào)你必須在回調(diào)里推進(jìn)下一步。很多人卡在“連上了但收不到數(shù)據(jù)”這個(gè)問題其實(shí)八成是沒訂閱Notify。連接成功只是建立了底層鏈路服務(wù)發(fā)現(xiàn)和Notify訂閱還都沒做設(shè)備自然不會(huì)往上推數(shù)據(jù)。訂閱Notify的時(shí)機(jī)也很關(guān)鍵要在發(fā)現(xiàn)服務(wù)完成后立即訂閱不要在收到第一批數(shù)據(jù)之后再訂閱否則前幾包就漏掉了。在Android原生開發(fā)中連接和訂閱的回調(diào)回調(diào)順序受系統(tǒng)藍(lán)牙棧影響偶發(fā)出現(xiàn)“先收到通知再收到訂閱成功回調(diào)”這種亂序情況。代碼里要做好冪等處理。iOS的CoreBluetooth相對嚴(yán)格但要注意在主隊(duì)列操作回調(diào)避免線程競爭導(dǎo)致連接狀態(tài)錯(cuò)亂。uni-app這類框架封裝了Android和iOS的差異API統(tǒng)一成startBluetoothDevicesDiscovery、createBLEConnection、notifyBLECharacteristicValueChange這組方法邏輯上順序是一樣的。4.3 App端粘包與分包處理BLE之所以存在粘包問題是因?yàn)橐淮蜰otify事件對應(yīng)的數(shù)據(jù)載荷雖然有上限但設(shè)備側(cè)可能把好幾幀串口數(shù)據(jù)塞在連續(xù)幾次Notify里發(fā)射出來。App端在短時(shí)間內(nèi)會(huì)連續(xù)收到多個(gè)notify回調(diào)每個(gè)回調(diào)里可能是一段不完整的幀也可能一次包含好幾幀。如果你直接把每個(gè)回調(diào)的數(shù)據(jù)拿去解析大概率會(huì)得到一堆亂碼或者錯(cuò)誤幀。正確的處理方式是在App端維護(hù)一個(gè)接收緩沖區(qū)收到數(shù)據(jù)先追加進(jìn)緩沖區(qū)然后循環(huán)從緩沖區(qū)里按照協(xié)議幀格式解析數(shù)據(jù)。解析出一幀就消費(fèi)一幀剩下的留在緩沖區(qū)等下一批數(shù)據(jù)。這個(gè)邏輯其實(shí)就是串口開發(fā)里經(jīng)典的“緩沖狀態(tài)機(jī)解析”模式換到BLE只是把“串口接收中斷”換成了“Notify回調(diào)”而已。有個(gè)細(xì)節(jié)值得注意BLE協(xié)議不保證一次notify的數(shù)據(jù)邊界和你的幀邊界對齊。舉個(gè)例子你定義了一幀64字節(jié)的數(shù)據(jù)設(shè)備可能分4次notify發(fā)完每次20字節(jié)也可能一次notify里塞了兩個(gè)半幀因?yàn)榍懊娴木彺胬飻€了太多數(shù)據(jù)。所以一切必須以幀頭和長度字段為準(zhǔn)不能以notify回調(diào)次數(shù)作為分幀依據(jù)。5. 串口到BLE的橋接數(shù)據(jù)幀設(shè)計(jì)與緩沖策略5.1 數(shù)據(jù)幀格式幀頭、長度、校驗(yàn)一個(gè)不能少主控MCU和WT2605C之間的串口協(xié)議是整個(gè)鏈路里最容易偷懶但最不應(yīng)該偷懶的地方。很多前期原型直接把裸數(shù)據(jù)往串口里丟結(jié)果一旦數(shù)據(jù)量上來或者信號(hào)受干擾幀錯(cuò)位、丟字節(jié)的問題立刻暴露。我的建議是無論項(xiàng)目多簡單都至少要定義一個(gè)帶幀頭、長度、校驗(yàn)的格式。一個(gè)典型的幀格式可以是幀頭0xAA 0x55、數(shù)據(jù)長度1字節(jié)、數(shù)據(jù)域N字節(jié)、CRC校驗(yàn)1到2字節(jié)。幀頭盡量用兩個(gè)字節(jié)降低隨機(jī)字節(jié)誤當(dāng)幀頭和的數(shù)據(jù)概率長度字段用來告訴接收方這一幀總共多少字節(jié)方便解析時(shí)定位幀尾CRC校驗(yàn)用CRC8或者CRC16至少保證能發(fā)現(xiàn)傳輸錯(cuò)誤而不是靜默丟數(shù)據(jù)。WT2605C本身不關(guān)心你串口傳過來的數(shù)據(jù)格式它只會(huì)把收到的字節(jié)原封不動(dòng)轉(zhuǎn)發(fā)到BLE側(cè)所以幀格式完全由你自己定。我在實(shí)際項(xiàng)目里見過有人嫌CRC計(jì)算麻煩用累加和代替也沒問題。關(guān)鍵是校驗(yàn)必須在不能省。BLE傳輸本身是有校驗(yàn)的但串口側(cè)主控到WT2605C的通信如果出錯(cuò)BLE那層根本發(fā)現(xiàn)不了所以串口側(cè)必須自己校驗(yàn)。5.2 串口接收用DMA還是中斷主控MCU從串口接收數(shù)據(jù)有幾種方式阻塞查詢、中斷逐字節(jié)接收、DMA加空閑中斷。這幾種方式的可靠性和CPU占用差別很大數(shù)傳場景下強(qiáng)烈建議用DMA。中斷逐字節(jié)接收也不是不能用但如果你的MCU主頻不高串口波特率又比較高中斷頻率會(huì)很嚇人。比如115200波特率下大約每87微秒一個(gè)字節(jié)每個(gè)字節(jié)觸發(fā)一次中斷大量CPU時(shí)間就耗在進(jìn)中斷出中斷上了。DMA加空閑中斷的思路是DMA自動(dòng)把串口數(shù)據(jù)搬到內(nèi)存緩沖區(qū)當(dāng)串口線空閑了觸發(fā)一次空閑中斷主控在空閑中斷里判斷這一批收到了多少數(shù)據(jù)然后處理。這樣一條串口數(shù)據(jù)幀只觸發(fā)一次中斷CPU壓力小非常多。以STM32為例配置串口DMA接收時(shí)需要特別注意DMA緩沖區(qū)的長度設(shè)置。緩沖區(qū)長度至少要比最大幀長要大否則DMA滿了會(huì)覆蓋或者產(chǎn)生溢出錯(cuò)誤。常見做法是DMA緩沖區(qū)設(shè)成1024字節(jié)每次觸發(fā)空閑中斷后把DMA的接收計(jì)數(shù)讀出來計(jì)算出本次接收到的數(shù)據(jù)長度然后拷貝走處理再重新啟動(dòng)DMA接收。這個(gè)循環(huán)邏輯不難但實(shí)現(xiàn)時(shí)要注意時(shí)序競爭最好在處理數(shù)據(jù)期間暫時(shí)關(guān)閉DMA接收防止新數(shù)據(jù)覆蓋緩沖區(qū)。5.3 背靠背緩沖與流控機(jī)制串口和BLE之間的數(shù)據(jù)速率是失配的串口一下子來一長串?dāng)?shù)據(jù)BLE這邊受MTU限制只能慢慢發(fā)背后必然需要一個(gè)緩沖隊(duì)列。WT2605C內(nèi)部有一定大小的緩沖區(qū)數(shù)據(jù)超過了緩沖區(qū)就只能丟棄。所以大流量場景下主控發(fā)送數(shù)據(jù)時(shí)不能無腦往串口丟要考慮WT2605C的緩沖能力和BLE鏈路的實(shí)際吞吐。一個(gè)簡單有效的辦法是主控側(cè)維護(hù)一個(gè)發(fā)送隊(duì)列通過查詢WT2605C的狀態(tài)比方說通過一條AT指令查剩余緩沖區(qū)空間控制發(fā)送節(jié)奏。如果芯片支持硬件流控RTS/CTS也可以借助CTS信號(hào)做反壓。沒有流控的情況下就需要主控估算數(shù)據(jù)發(fā)送速率保證平均速率不超過BLE的實(shí)際吞吐否則遲早緩沖區(qū)溢出丟數(shù)。丟數(shù)據(jù)這件事水往低處流串口快、BLE慢堆積不可避免??孔V的做法是應(yīng)用層加上確認(rèn)重傳機(jī)制主控發(fā)出去的一幀數(shù)據(jù)App收到后回一條確認(rèn)主控如果在超時(shí)時(shí)間內(nèi)沒收到確認(rèn)就重發(fā)。這個(gè)邏輯對實(shí)時(shí)性要求高的控制類產(chǎn)品特別重要純單向數(shù)據(jù)采集的產(chǎn)品可以適當(dāng)放寬允許少量丟幀。6. 調(diào)試驗(yàn)證三板斧串口助手、BLE調(diào)試器、抓包工具6.1 先用串口助手把UART側(cè)調(diào)明白整個(gè)鏈路調(diào)試我習(xí)慣的順序是從兩端往中間擠先把串口側(cè)調(diào)通再把BLE側(cè)調(diào)通最后聯(lián)調(diào)。串口側(cè)的調(diào)試工具很多Windows下XCOM、SSCOM、友善串口助手、sscom基本功能都差不多關(guān)鍵是支持十六進(jìn)制收發(fā)和定時(shí)發(fā)送方便做自定義協(xié)議測試。調(diào)試串口側(cè)的第一步是驗(yàn)證PC能不能通過USB轉(zhuǎn)TTL模塊直接和WT2605C通信。把USB轉(zhuǎn)TTL的TX接芯片RX、RX接芯片TX、GND接GND打開串口助手選對COM口和波特率發(fā)一條AT指令或者自定義測試數(shù)據(jù)看能不能收到回復(fù)。這里有一個(gè)高頻坑USB轉(zhuǎn)TTL模塊用的CH340芯片有時(shí)候驅(qū)動(dòng)沒裝好設(shè)備管理器里根本不顯示COM口或者顯示一個(gè)帶感嘆號(hào)的設(shè)備。這種問題優(yōu)先重裝CH340驅(qū)動(dòng)別急著換硬件。串口側(cè)調(diào)試還有一個(gè)容易忽略的點(diǎn)如果你通過USB轉(zhuǎn)TTL同時(shí)給WT2605C供電極少數(shù)劣質(zhì)USB轉(zhuǎn)TTL模塊的供電能力不足芯片上電后工作不穩(wěn)定現(xiàn)象是時(shí)好時(shí)壞。遇到莫名其妙的串口通信異常先換一根高規(guī)格的供電線或者改用獨(dú)立電源給模塊供電排除供電問題再往下查。6.2 BLE側(cè)用手機(jī)調(diào)試工具驗(yàn)證GATT串口側(cè)通了之后下一步驗(yàn)證WT2605C的BLE廣播和質(zhì)量。手機(jī)裝一個(gè)通用BLE調(diào)試工具我習(xí)慣用nRF ConnectAndroid和iOS都有功能完整且免費(fèi)。打開它掃描周圍設(shè)備正常情況下幾秒內(nèi)就能看到你的設(shè)備出現(xiàn)在列表里點(diǎn)開連接進(jìn)去能看到完整的GATT服務(wù)列表包括Service和Characteristic的UUID、屬性和當(dāng)前值。在nRF Connect里可以直接對Characteristic做讀寫操作也可以訂閱通知。這個(gè)階段能驗(yàn)證三件事設(shè)備廣播是否正常、GATT服務(wù)結(jié)構(gòu)是否符合預(yù)期、以及數(shù)據(jù)能不能從設(shè)備推送到手機(jī)。驗(yàn)證通知推送時(shí)你可以在串口側(cè)用串口助手往WT2605C發(fā)數(shù)據(jù)同時(shí)觀察nRF Connect的通知窗口如果發(fā)了數(shù)據(jù)立刻在手機(jī)上看到對應(yīng)的十六進(jìn)制內(nèi)容證明“串口進(jìn)、BLE出”這條路已經(jīng)通了。反向驗(yàn)證也在這做在nRF Connect里往可寫Characteristic寫入數(shù)據(jù)然后看串口助手能不能收到對應(yīng)字節(jié)。這兩條通路都通了鏈路的核心就算打通了后面寫的App代碼只是在自動(dòng)化這個(gè)過程。6.3 用抓包工具看鏈路時(shí)序手機(jī)調(diào)試工具能解決大部分功能問題但遇到疑難雜癥——連接建立緩慢、數(shù)據(jù)延遲異常、MTU協(xié)商失敗、偶發(fā)掉線——僅憑手機(jī)端信息很難定位這時(shí)候就得請出藍(lán)牙抓包工具。思路其實(shí)和TCP/IP調(diào)試一樣抓包能讓你看到空中鏈路上真實(shí)發(fā)生的每一次交互。硬件抓包可以用支持藍(lán)牙Sniffer的開發(fā)板來做比如配合專用的藍(lán)牙協(xié)議分析軟件就能解析空中的BLE數(shù)據(jù)包看到廣播包、連接請求、連接參數(shù)更新、MTU協(xié)商、ATT讀寫的完整時(shí)序。抓包能看到手機(jī)端看不到的細(xì)節(jié)設(shè)備到底有沒有在廣播、廣播內(nèi)容是什么、連接請求有沒有被設(shè)備拒絕、連接建立后參數(shù)更新成了多少、每一包數(shù)據(jù)在空中的實(shí)際間隔是多少。使用抓包工具的過程中有一點(diǎn)要習(xí)慣空中抓到的包是按照時(shí)序排列的原始事件流和邏輯層面的事件不完全一一對應(yīng)需要把一段時(shí)間內(nèi)同一對設(shè)備之間的包串起來才能還原交互過程。第一次接觸可能覺得信息量大但多抓幾次就會(huì)發(fā)現(xiàn)大部分連接問題和數(shù)據(jù)問題都藏在這些包之間的時(shí)間縫隙上。7. 常見問題與排查技巧實(shí)錄7.1 連不上、掉線、亂碼三類高頻故障速查表把項(xiàng)目里和同行交流中遇到的典型問題整理成一張速查表按現(xiàn)象、可能原因、排查順序三列來組織方便直接對著查現(xiàn)象可能原因排查順序手機(jī)掃不到設(shè)備設(shè)備未進(jìn)入廣播狀態(tài)確認(rèn)芯片上電是否完成、廣播指示燈是否亮、SDK配置里廣播是否開啟掃不到但設(shè)備在廣播廣播間隔過大或信道被干擾縮短廣播間隔到200ms以內(nèi)換個(gè)環(huán)境測試用抓包工具確認(rèn)空中有廣播包能掃到但連不上連接請求被拒絕或參數(shù)不匹配抓包看連接請求響應(yīng)確認(rèn)連接參數(shù)是否在設(shè)備支持范圍內(nèi)連接后立即掉線距離太遠(yuǎn)或信號(hào)差先靠近測試排除信號(hào)問題后檢查供電穩(wěn)定性連上但收不到數(shù)據(jù)未訂閱Notify檢查App是否調(diào)用了通知訂閱接口在調(diào)試工具里驗(yàn)證訂閱是否成功數(shù)據(jù)亂碼波特率不匹配核對主控和WT2605C兩端串口參數(shù)是否完全一致數(shù)據(jù)偶發(fā)丟失BLE吞吐不足計(jì)算實(shí)際數(shù)據(jù)量與BLE有效吞吐的關(guān)系必要時(shí)加大MTU、縮短連接間隔數(shù)據(jù)幀錯(cuò)亂幀格式未定義或未處理分包檢查幀頭長度校驗(yàn)設(shè)計(jì)App端補(bǔ)充緩沖拼包邏輯這張表解決的場景基本覆蓋了從硬件到軟件的大多數(shù)問題。有一個(gè)規(guī)律可以提前說明功能性問題大多出在串口配置、訂閱邏輯這些簡單環(huán)節(jié)性能問題和穩(wěn)定性問題才需要往協(xié)議棧和參數(shù)層面深挖。7.2 排查順序從終端倒推還是從源頭正推遇到復(fù)雜問題建議先確定排查方向不要東敲一下西打一下。我的習(xí)慣是優(yōu)先倒推從現(xiàn)象端一步步往回查手機(jī)App收不到數(shù)據(jù)先看手機(jī)調(diào)試工具能不能收到如果不能看設(shè)備側(cè)是否真的發(fā)出了數(shù)據(jù)再看串口側(cè)有沒有把數(shù)據(jù)送進(jìn)WT2605C。每一層用一個(gè)可觀測的工具去驗(yàn)證很快就能把問題定位到具體某一跳上。反過來如果倒推不好定位就從源頭正推。串口側(cè)發(fā)一包測試數(shù)據(jù)用BLE調(diào)試工具看在BLE側(cè)能不能看到對應(yīng)內(nèi)容如果BLE側(cè)看不到說明問題出在串口到芯片內(nèi)部轉(zhuǎn)發(fā)這一段可能是串口參數(shù)錯(cuò)、數(shù)據(jù)格式錯(cuò)也可能是芯片固件本身對數(shù)據(jù)做了限制。正推的好處是每一步都有明確的預(yù)期結(jié)果預(yù)期不符就是問題所在。排查過程還有一個(gè)容易忽略的環(huán)節(jié)明確“當(dāng)前改動(dòng)過什么”。BLE鏈路受環(huán)境干擾影響大同一套代碼今天正常明天異常的例子很常見。遇到這種時(shí)好時(shí)壞的故障先想想最近是不是改了App端的連接邏輯、更新過SDK版本、換過測試場地。大部分偶發(fā)問題背后都有一個(gè)之前認(rèn)為無關(guān)緊要的改動(dòng)在搗亂。7.3 幾個(gè)容易被忽略的細(xì)節(jié)坑最后分享幾個(gè)我實(shí)際踩過、并且在多個(gè)項(xiàng)目里反復(fù)出現(xiàn)的細(xì)節(jié)坑。第一個(gè)是WT2605C這類帶音頻功能的芯片你的固件配置可能同時(shí)啟用了音頻通道和數(shù)據(jù)通道音頻播放和數(shù)據(jù)傳輸會(huì)搶占內(nèi)部資源數(shù)據(jù)傳輸時(shí)延產(chǎn)生抖動(dòng)是正常的。如果你的業(yè)務(wù)對數(shù)據(jù)實(shí)時(shí)性要求敏感測試時(shí)不要開著音頻播放測數(shù)傳。第二個(gè)是Android的藍(lán)牙權(quán)限在不同版本上分裂得很厲害Android 12之后掃描附近設(shè)備需要BLUETOOTH_SCAN權(quán)限連接需要BLUETOOTH_CONNECT權(quán)限而且這些都是運(yùn)行時(shí)權(quán)限需要在App里動(dòng)態(tài)申請。很多人把代碼從舊項(xiàng)目搬過來后在Android 13以上的手機(jī)上看不到設(shè)備八成就是權(quán)限沒適配新版本。第三個(gè)和iOS有關(guān)如果你的App在iOS上第一次掃描就能連上設(shè)備第二次進(jìn)App卻連不上大概率是系統(tǒng)藍(lán)牙緩存了舊的設(shè)備信息。解決方法是把設(shè)備的Service UUID和Characteristic UUID都做成固定值并且App在連接失敗時(shí)主動(dòng)調(diào)用系統(tǒng)的取消連接接口必要時(shí)可以在設(shè)置里忽略該藍(lán)牙設(shè)備后重啟App重試。寫在最后整個(gè)WT2605C串口轉(zhuǎn)BLE的鏈路調(diào)試下來我最深的體會(huì)是這類方案的項(xiàng)目難點(diǎn)往往不在單一環(huán)節(jié)而在于多段鏈路之間的銜接。串口側(cè)調(diào)通了不代表鏈路通了BLE廣播正常也不代表App能收數(shù)每一跳都有各自的協(xié)議和語義而產(chǎn)品最終要的結(jié)果是端到端的穩(wěn)定。調(diào)試時(shí)把鏈路拆開逐段驗(yàn)證出問題時(shí)從現(xiàn)象端倒推把每一個(gè)環(huán)節(jié)的預(yù)期結(jié)果確認(rèn)清楚再復(fù)雜的問題也能逐步收窄到可控范圍。如果做的產(chǎn)品對數(shù)據(jù)延遲和丟包敏感建議從項(xiàng)目一開始就把幀格式、緩沖策略、重傳機(jī)制設(shè)計(jì)好不要在原型階段裸數(shù)據(jù)裸跑后面再補(bǔ)框架不僅工作量大可靠性也很難驗(yàn)證清楚。先把串口和BLE兩側(cè)的固定套路跑熟再根據(jù)業(yè)務(wù)特性做裁剪這條路走下來遇到同類項(xiàng)目基本就能舉一反三了。