掰手腕機(jī)械臂實(shí)戰(zhàn):從EMG采集到舵機(jī)控制)
1. 從零理解肌電信號(hào)摔跤手臂這個(gè)項(xiàng)目到底在做什么第一次看到 Forearm Wrestling 這個(gè)標(biāo)題很多人會(huì)以為是掰手腕比賽其實(shí)這是一個(gè)用肌電信號(hào)EMG驅(qū)動(dòng)舵機(jī)的嵌入式交互項(xiàng)目。核心邏輯很直接把電極貼在手臂肌肉上采集肌肉收縮時(shí)產(chǎn)生的微弱電信號(hào)經(jīng)過(guò) Arduino 采樣、濾波、映射之后輸出一個(gè)角度值去控制舵機(jī)轉(zhuǎn)動(dòng)。兩個(gè)人各戴一套采集裝置各自控制一個(gè)舵機(jī)舵機(jī)帶動(dòng)機(jī)械臂做“掰手腕”的對(duì)抗動(dòng)作誰(shuí)的肌肉發(fā)力更強(qiáng)誰(shuí)的舵機(jī)扭矩輸出更大機(jī)械臂就往對(duì)方那邊倒。這個(gè)項(xiàng)目解決的是一個(gè)很典型的“人機(jī)交互入門”問(wèn)題怎么把人體生物信號(hào)變成機(jī)器能理解的數(shù)字信號(hào)再變成物理動(dòng)作。它適合三類人參考第一類是剛接觸 Arduino、想找一個(gè)有視覺(jué)沖擊力的項(xiàng)目練手的初學(xué)者第二類是想了解 EMG 信號(hào)采集與處理流程的電子愛(ài)好者第三類是做創(chuàng)客教育或科普展示、需要一個(gè)能吸引觀眾互動(dòng)的裝置的人。我用的主控是Arduino UNO R4 Minima這是 Arduino 官方較新的一代 UNO核心是 Renesas RA4M1跑 48MHzADC 是 14 位分辨率。相比經(jīng)典 UNO R3 的 10 位 ADCR4 Minima 在采集 EMG 這種幅度只有幾百微伏到幾毫伏的信號(hào)時(shí)量化精度明顯更好能分辨出更細(xì)微的肌肉發(fā)力差異。舵機(jī)用的是常見(jiàn)的 MG996R 金屬齒輪舵機(jī)扭矩夠大帶動(dòng)機(jī)械臂不費(fèi)勁。開(kāi)發(fā)環(huán)境就是Arduino IDE沒(méi)有用 PlatformIO 或 VSCode因?yàn)轫?xiàng)目本身不復(fù)雜IDE 自帶的串口監(jiān)視器和繪圖器足夠調(diào)試用。注意EMG 采集涉及人體接觸電極貼片必須使用一次性醫(yī)用級(jí)表面電極供電必須用電池或隔離電源絕對(duì)不能直接用市電供電的 USB 連著電腦再貼到人身上。這一點(diǎn)后面會(huì)詳細(xì)展開(kāi)。整個(gè)系統(tǒng)的信號(hào)鏈路是這樣的表面電極 → 前置放大 → 帶通濾波 → 整流 → 包絡(luò)檢波 → Arduino ADC → 軟件映射 → PWM 輸出 → 舵機(jī)。每一環(huán)都有坑下面我按實(shí)際搭建順序拆開(kāi)講。2. 硬件選型與信號(hào)鏈路設(shè)計(jì)為什么這么搭2.1 主控為什么選 UNO R4 Minima 而不是 R3 或 ESP32先說(shuō)結(jié)論R4 Minima 是這個(gè)項(xiàng)目里性價(jià)比最均衡的選擇。我試過(guò) R3也試過(guò) ESP32各有問(wèn)題。R3 的問(wèn)題是 ADC 只有 10 位參考電壓默認(rèn) 5V最小分辨電壓是 5V/1024 ≈ 4.88mV。而 EMG 信號(hào)經(jīng)過(guò)放大濾波后有效幅度大概在 0.5V 到 3V 之間波動(dòng)4.88mV 的量化步長(zhǎng)意味著很多細(xì)微的發(fā)力變化會(huì)被“抹平”表現(xiàn)出來(lái)就是舵機(jī)動(dòng)作一頓一頓的不夠跟手。R4 Minima 的 14 位 ADC 在同樣 5V 參考下分辨電壓是 5V/16384 ≈ 0.305mV細(xì)膩了 16 倍肌肉輕微發(fā)力也能被捕捉到。ESP32 的 ADC 理論上也是 12 位但它的 ADC 線性度在靠近 0V 和 3.3V 兩端比較差而且 WiFi 工作時(shí)會(huì)有噪聲耦合進(jìn)模擬通道。這個(gè)項(xiàng)目不需要聯(lián)網(wǎng)用 ESP32 屬于殺雞用牛刀還要額外處理 3.3V 電平和 5V 舵機(jī)的電平轉(zhuǎn)換反而增加復(fù)雜度。R4 Minima 還有一個(gè)隱藏優(yōu)勢(shì)它的 ADC 采樣率可以配置得比較高我在代碼里設(shè)到 1kHz 左右采樣配合簡(jiǎn)單的滑動(dòng)平均濾波信號(hào)已經(jīng)足夠干凈。另外 R4 Minima 的 PWM 輸出頻率和分辨率也比 R3 靈活控制舵機(jī)時(shí)抖動(dòng)更小。主控型號(hào)ADC 位數(shù)分辨電壓5V 參考適合度主要問(wèn)題UNO R310 位4.88mV勉強(qiáng)可用精度低舵機(jī)動(dòng)作粗糙UNO R4 Minima14 位0.305mV推薦無(wú)明顯短板ESP3212 位0.8mV3.3V 參考不推薦ADC 線性度差電平轉(zhuǎn)換麻煩2.2 EMG 前置放大電路為什么不能直接接 Arduino這是新手最容易踩的坑。有人覺(jué)得電極貼手上信號(hào)線直接插 Arduino 模擬口不就行了實(shí)測(cè)下來(lái)完全不行。原因有三個(gè)第一信號(hào)幅度太小。肌肉表面 EMG 信號(hào)典型幅度是 50μV 到 5mV而 Arduino 能識(shí)別的最小電壓變化是 0.3mV 到 5mV 級(jí)別大部分信號(hào)直接淹沒(méi)在量化噪聲里。第二阻抗不匹配。人體皮膚和電極之間的接觸阻抗在幾千歐到幾十千歐而 Arduino 模擬輸入阻抗雖然不低但直接連接會(huì)形成分壓信號(hào)進(jìn)一步衰減。第三共模干擾。人體相當(dāng)于一根天線會(huì)拾取 50Hz 工頻干擾這個(gè)干擾幅度往往比 EMG 信號(hào)本身還大。不做差分放大和濾波讀出來(lái)的全是噪聲。所以必須加前置放大。我用的方案是AD8232 模塊這是一個(gè)專門為 ECG 和 EMG 設(shè)計(jì)的集成前端芯片內(nèi)部集成了儀表放大器、帶通濾波、右腿驅(qū)動(dòng)電路。模塊輸出直接是放大后的模擬電壓接 Arduino 模擬口就行。AD8232 的增益是 100 倍帶寬 0.5Hz 到 40Hz默認(rèn)配置正好覆蓋 EMG 的主要能量頻段。如果你不想用模塊想自己搭運(yùn)放電路那至少需要三級(jí)儀表放大器如 INA128增益 100 倍→ 高通濾波截止 20Hz去運(yùn)動(dòng)偽跡→ 低通濾波截止 500Hz去高頻噪聲。這個(gè)方案成本更低但調(diào)試麻煩新手建議直接用 AD8232 模塊。2.3 舵機(jī)與機(jī)械結(jié)構(gòu)扭矩計(jì)算和安裝要點(diǎn)舵機(jī)選 MG996R堵轉(zhuǎn)扭矩標(biāo)稱 9.4kg·cm6V 供電實(shí)際帶機(jī)械臂時(shí)有效扭矩大概打七折算 6.5kg·cm。機(jī)械臂長(zhǎng)度我設(shè)計(jì)的是 15cm那么末端能輸出的力是 6.5kg·cm / 15cm ≈ 0.43kg也就是 430 克左右的推力。兩個(gè)人對(duì)抗時(shí)如果一方舵機(jī)往一個(gè)方向推另一方往反方向推實(shí)際作用力會(huì)疊加所以這個(gè)扭矩夠用。機(jī)械結(jié)構(gòu)我用的是 3D 打印的支架兩個(gè)舵機(jī)背對(duì)背固定各自伸出一根 15cm 的臂臂末端用一根連桿連接。當(dāng)兩個(gè)舵機(jī)角度不同時(shí)連桿會(huì)傾斜傾斜方向就代表“誰(shuí)贏了”。這個(gè)結(jié)構(gòu)比直接讓兩個(gè)臂硬碰硬更安全因?yàn)檫B桿有活動(dòng)余量不會(huì)燒舵機(jī)。實(shí)操心得舵機(jī)供電一定要獨(dú)立。我一開(kāi)始用 Arduino 的 5V 引腳給舵機(jī)供電結(jié)果舵機(jī)一動(dòng)Arduino 就復(fù)位。原因是舵機(jī)啟動(dòng)瞬間電流能到 2A 以上把 USB 供電拉垮了。后來(lái)?yè)Q成 6V 2A 的獨(dú)立電源共地連接問(wèn)題解決。2.4 電源方案為什么必須用電池或隔離電源前面提過(guò)安全警告這里展開(kāi)說(shuō)。EMG 采集時(shí)電極貼在人體皮膚上如果整個(gè)系統(tǒng)由市電供電的 USB 適配器供電一旦適配器漏電或接地不良人體就可能成為電流通路。雖然 Arduino 工作電壓只有 5V但市電側(cè)的故障電壓可能直接竄到人體上。正確做法是采集部分AD8232 Arduino用電池供電比如 9V 電池經(jīng)過(guò)降壓模塊給 Arduino 供電或者直接用充電寶。舵機(jī)部分可以用獨(dú)立電源但必須和 Arduino 共地。如果一定要用電腦 USB 調(diào)試那在貼電極之前必須拔掉 USB用電池運(yùn)行。我自己的配置是Arduino 用一塊 2000mAh 的充電寶供電舵機(jī)用 6V 鎳氫電池組供電兩路電源共地。這樣即使電腦連著 Arduino 調(diào)代碼人體也不會(huì)有任何電氣風(fēng)險(xiǎn)因?yàn)槌潆妼毷歉〉氐摹?. 軟件實(shí)現(xiàn)從 ADC 采樣到舵機(jī)映射的完整代碼邏輯3.1 采樣與濾波為什么用滑動(dòng)平均而不是卡爾曼EMG 信號(hào)經(jīng)過(guò) AD8232 輸出后已經(jīng)是一個(gè)比較干凈的包絡(luò)信號(hào)但仍然有高頻抖動(dòng)。我在 Arduino 里做的第一件事是以 1kHz 頻率采樣模擬口然后做 20 點(diǎn)滑動(dòng)平均。為什么不用卡爾曼濾波因?yàn)榭柭枰⑾到y(tǒng)模型而 EMG 信號(hào)沒(méi)有明確的運(yùn)動(dòng)學(xué)模型強(qiáng)行套卡爾曼反而會(huì)引入滯后。滑動(dòng)平均雖然簡(jiǎn)單但在這個(gè)場(chǎng)景下足夠用而且計(jì)算量小R4 Minima 跑起來(lái)毫無(wú)壓力。采樣代碼的核心是analogRead()配合定時(shí)器中斷。R4 Minima 的analogRead()單次轉(zhuǎn)換時(shí)間大概 20μs1kHz 采樣完全來(lái)得及。我實(shí)際測(cè)試下來(lái)不加中斷直接用millis()做時(shí)間片也能跑但抖動(dòng)會(huì)大一點(diǎn)。用Timer庫(kù)配置 1kHz 中斷更穩(wěn)。#include Timer.h Timer t; const int emgPin A0; const int servoPin 9; const int windowSize 20; int samples[windowSize]; int sampleIndex 0; long sum 0; void setup() { Serial.begin(115200); pinMode(servoPin, OUTPUT); t.every(1, sampleEMG); // 1ms 一次即 1kHz } void sampleEMG() { int val analogRead(emgPin); sum - samples[sampleIndex]; samples[sampleIndex] val; sum val; sampleIndex (sampleIndex 1) % windowSize; } void loop() { t.update(); int filtered sum / windowSize; // 后續(xù)映射邏輯 }這段代碼里sum維護(hù)的是滑動(dòng)窗口的和每次更新只減一個(gè)舊值加一個(gè)新值避免每次重新遍歷數(shù)組效率高很多。這是我在實(shí)際跑的時(shí)候優(yōu)化出來(lái)的一開(kāi)始用for循環(huán)求和1kHz 下 CPU 占用率明顯偏高。3.2 信號(hào)映射從 ADC 值到舵機(jī)角度的非線性處理濾波后的 ADC 值范圍大概在 300 到 800 之間14 位 ADC0 到 16383但 AD8232 輸出偏置在中間電平附近。直接線性映射到舵機(jī) 0 到 180 度會(huì)有一個(gè)問(wèn)題靜息狀態(tài)下肌肉有微弱電活動(dòng)ADC 值不是零如果直接映射舵機(jī)會(huì)在靜息時(shí)也亂動(dòng)。我的處理方法是動(dòng)態(tài)基線校準(zhǔn)上電后前 2 秒采集靜息狀態(tài)的平均值作為基線之后每次采樣減去基線再映射。這樣靜息時(shí)輸出接近零舵機(jī)不動(dòng)發(fā)力時(shí)輸出增大舵機(jī)轉(zhuǎn)動(dòng)。映射公式我做了非線性處理int baseline 0; int calibrated filtered - baseline; if (calibrated 0) calibrated 0; int angle map(calibrated, 0, 500, 90, 180); angle constrain(angle, 90, 180);為什么從 90 度開(kāi)始而不是 0 度因?yàn)闄C(jī)械臂的初始位置是水平的對(duì)應(yīng)舵機(jī) 90 度。發(fā)力時(shí)舵機(jī)往 180 度方向轉(zhuǎn)代表“推過(guò)去”。這樣兩個(gè)舵機(jī)對(duì)抗時(shí)各自從 90 度出發(fā)誰(shuí)的角度大誰(shuí)就占優(yōu)。非線性處理體現(xiàn)在map的上限 500 上。實(shí)測(cè)發(fā)現(xiàn)肌肉最大發(fā)力時(shí) ADC 變化量大概在 400 到 600 之間取 500 作為滿量程可以讓舵機(jī)在中等發(fā)力時(shí)就達(dá)到較大角度響應(yīng)更靈敏。如果取 1000那要很大力才能轉(zhuǎn)到底手感很“肉”。3.3 雙人對(duì)戰(zhàn)邏輯如何判斷勝負(fù)和防止舵機(jī)堵轉(zhuǎn)兩個(gè)人各有一套采集和舵機(jī)兩個(gè) Arduino 之間需要通信。我用的是最簡(jiǎn)單的串口通信一個(gè) Arduino 作為主機(jī)另一個(gè)作為從機(jī)主機(jī)通過(guò) SoftwareSerial 發(fā)送自己的角度值給從機(jī)從機(jī)比較兩個(gè)角度決定自己的舵機(jī)往哪個(gè)方向轉(zhuǎn)。但這里有個(gè)問(wèn)題如果兩個(gè)舵機(jī)都往對(duì)方方向推機(jī)械連桿會(huì)卡死舵機(jī)堵轉(zhuǎn)電流飆升容易燒。我的解決方案是引入“讓步”邏輯當(dāng)檢測(cè)到對(duì)方角度大于自己角度超過(guò) 30 度時(shí)自己的舵機(jī)主動(dòng)往回收 10 度給連桿留出活動(dòng)空間。這個(gè)邏輯用幾行代碼就能實(shí)現(xiàn)if (opponentAngle - myAngle 30) { myAngle myAngle - 10; myAngle constrain(myAngle, 90, 180); }實(shí)測(cè)下來(lái)這個(gè)讓步邏輯讓整個(gè)對(duì)抗過(guò)程更流暢不會(huì)出現(xiàn)卡死。而且從觀眾角度看機(jī)械臂的“掙扎”動(dòng)作反而更真實(shí)像真的在掰手腕。注意兩個(gè) Arduino 共地是必須的否則串口通信會(huì)亂碼。我用一根杜邦線把兩個(gè)板子的 GND 連在一起問(wèn)題解決。3.4 串口監(jiān)視器調(diào)試怎么用繪圖器看 EMG 波形Arduino IDE 自帶的串口繪圖器是這個(gè)項(xiàng)目調(diào)試的神器。把濾波后的 ADC 值用Serial.println()輸出打開(kāi)繪圖器就能實(shí)時(shí)看到肌肉發(fā)力時(shí)的波形變化。靜息時(shí)是一條平線發(fā)力時(shí)是一個(gè)鼓包放松后又回到平線。我調(diào)試時(shí)發(fā)現(xiàn)如果波形毛刺太多說(shuō)明滑動(dòng)平均窗口太小加到 30 或 40 會(huì)好很多。如果波形反應(yīng)遲鈍說(shuō)明窗口太大減到 10 左右。這個(gè)參數(shù)需要根據(jù)實(shí)際電極位置和皮膚狀態(tài)微調(diào)沒(méi)有固定值。另外繪圖器的采樣率設(shè)置要和代碼里的輸出頻率匹配。我在代碼里每 10ms 輸出一次100Hz繪圖器也設(shè) 100Hz這樣波形不會(huì)失真。如果輸出太快繪圖器會(huì)丟數(shù)據(jù)波形看起來(lái)斷斷續(xù)續(xù)。4. 實(shí)操全流程從貼電極到跑通對(duì)抗的每一步4.1 電極貼放位置為什么貼肱橈肌而不是肱二頭肌EMG 電極貼放位置直接決定信號(hào)質(zhì)量。我試過(guò)貼肱二頭肌上臂前側(cè)也試過(guò)貼肱橈肌前臂外側(cè)最后選了肱橈肌。原因是掰手腕這個(gè)動(dòng)作主要靠前臂肌群發(fā)力肱橈肌在握拳和屈腕時(shí)激活明顯信號(hào)幅度大且穩(wěn)定。肱二頭肌雖然也參與但它的信號(hào)更容易受到上臂運(yùn)動(dòng)偽跡干擾手臂稍微一動(dòng)基線就漂了。具體貼法兩個(gè)采集電極沿肌纖維方向貼在肱橈肌肌腹上間距 2cm 左右參考電極貼在尺骨莖突手腕小骨頭上。貼之前用酒精棉片擦拭皮膚去掉油脂和死皮接觸阻抗能降一個(gè)數(shù)量級(jí)。電極貼好后用手按壓 10 秒確保貼合緊密。實(shí)操心得電極貼好后不要馬上開(kāi)始等 1 分鐘讓導(dǎo)電凝膠充分浸潤(rùn)皮膚信號(hào)會(huì)穩(wěn)定很多。我一開(kāi)始貼完就測(cè)基線一直漂等了一會(huì)兒就好了。4.2 電路連接與上電順序連接順序很重要搞錯(cuò)了容易燒模塊。正確順序是先連接 AD8232 模塊到 ArduinoVCC 接 3.3VGND 接 GNDOUTPUT 接 A0LO 和 LO- 接 D10 和 D11用于檢測(cè)電極脫落。連接舵機(jī)信號(hào)線接 D9電源線接獨(dú)立 6V 電源地線共地。連接兩個(gè) Arduino 的串口TX 接 RXRX 接 TXGND 共地。最后上電先開(kāi) Arduino 電源再開(kāi)舵機(jī)電源。為什么先開(kāi) Arduino 再開(kāi)舵機(jī)因?yàn)槎鏅C(jī)上電瞬間可能產(chǎn)生反向電動(dòng)勢(shì)如果 Arduino 先上電它的 IO 口已經(jīng)處于確定狀態(tài)不會(huì)因?yàn)槎鏅C(jī)電源波動(dòng)而誤觸發(fā)。反過(guò)來(lái)如果舵機(jī)先上電Arduino 還沒(méi)初始化IO 口是高阻態(tài)容易受干擾。4.3 基線校準(zhǔn)與閾值設(shè)定上電后前 2 秒是基線校準(zhǔn)期這時(shí)候手要放松不要發(fā)力。代碼里會(huì)采集這 2 秒的平均 ADC 值作為基線。校準(zhǔn)完成后串口會(huì)打印“Calibration done, baseline XXX”。閾值設(shè)定我用了兩級(jí)死區(qū)閾值和滿量程閾值。死區(qū)閾值是基線以上 50 個(gè) ADC 單位低于這個(gè)值認(rèn)為沒(méi)有發(fā)力舵機(jī)保持 90 度不動(dòng)。滿量程閾值是基線以上 500 個(gè)單位達(dá)到這個(gè)值舵機(jī)轉(zhuǎn)到 180 度。中間線性映射。這個(gè)閾值需要根據(jù)個(gè)人皮膚阻抗和電極狀態(tài)微調(diào)。我一般讓測(cè)試者先用力握拳幾次看串口輸出的最大值然后取最大值的 80% 作為滿量程閾值。這樣既能充分利用量程又不會(huì)因?yàn)閭€(gè)體差異導(dǎo)致靈敏度不夠。4.4 對(duì)抗測(cè)試與參數(shù)調(diào)優(yōu)兩個(gè)人各戴一套設(shè)備面對(duì)面坐好手臂放在桌面上機(jī)械臂在中間。測(cè)試流程雙方放松確認(rèn)舵機(jī)都在 90 度位置。一方開(kāi)始發(fā)力觀察自己的舵機(jī)是否往 180 度方向轉(zhuǎn)。另一方也發(fā)力觀察兩個(gè)舵機(jī)是否對(duì)抗。調(diào)整讓步邏輯的閾值直到對(duì)抗過(guò)程流暢不卡頓。我實(shí)測(cè)下來(lái)讓步閾值設(shè) 30 度、讓步幅度 10 度比較合適。如果閾值太小比如 10 度舵機(jī)會(huì)頻繁讓步看起來(lái)軟綿綿的如果太大比如 60 度又容易卡死。這個(gè)參數(shù)和機(jī)械臂長(zhǎng)度、舵機(jī)扭矩都有關(guān)系需要實(shí)際試。參數(shù)初始值調(diào)整方向影響滑動(dòng)平均窗口20毛刺多則增大遲鈍則減小信號(hào)平滑度死區(qū)閾值50舵機(jī)亂動(dòng)則增大靜息穩(wěn)定性滿量程閾值500靈敏度低則減小響應(yīng)靈敏度讓步閾值30 度卡死則減小對(duì)抗流暢度讓步幅度10 度卡死則增大連桿活動(dòng)余量5. 踩坑實(shí)錄與常見(jiàn)問(wèn)題排查5.1 信號(hào)漂移為什么基線一直在變這是最常見(jiàn)的問(wèn)題。表現(xiàn)是靜息時(shí)舵機(jī)慢慢往一個(gè)方向轉(zhuǎn)或者串口輸出的基線值一直往上爬。原因通常有三個(gè)電極接觸不良。導(dǎo)電凝膠干了或者電極貼片松了接觸阻抗變大信號(hào)漂移。解決方法是重新貼電極或者換新貼片。運(yùn)動(dòng)偽跡。手臂輕微移動(dòng)電極和皮膚之間產(chǎn)生相對(duì)位移引起低頻干擾。解決方法是讓測(cè)試者手臂放松放在桌面上不要懸空。AD8232 的右腿驅(qū)動(dòng)沒(méi)接。AD8232 模塊上有一個(gè) RL 引腳用于右腿驅(qū)動(dòng)不接的話共模抑制能力下降工頻干擾會(huì)串進(jìn)來(lái)。雖然 EMG 不像 ECG 那么依賴右腿驅(qū)動(dòng)但接上更好。我一般把 RL 接一個(gè)參考電極貼在手腕上。5.2 舵機(jī)抖動(dòng)為什么舵機(jī)在靜息時(shí)嗡嗡響舵機(jī)在靜息時(shí)抖動(dòng)說(shuō)明 PWM 信號(hào)不穩(wěn)定。原因可能是ADC 噪聲耦合到 PWM。Arduino 的模擬和數(shù)字部分共用電源ADC 采樣時(shí)的電流波動(dòng)會(huì)影響 PWM 輸出。解決方法是在 ADC 輸入加一個(gè) 0.1μF 電容到地濾掉高頻噪聲。舵機(jī)電源不足。舵機(jī)在微小角度調(diào)整時(shí)電流變化大如果電源內(nèi)阻高電壓波動(dòng)會(huì)導(dǎo)致舵機(jī)抖動(dòng)。解決方法是換更大容量的電源或者在舵機(jī)電源端并一個(gè) 1000μF 電解電容。代碼里沒(méi)有死區(qū)。如果映射后的角度在 90 度附近微小變化舵機(jī)會(huì)一直來(lái)回修正。加一個(gè)死區(qū)判斷角度變化小于 2 度時(shí)不更新 PWM就能解決。5.3 串口通信亂碼兩個(gè) Arduino 之間怎么穩(wěn)定通信兩個(gè) Arduino 串口通信亂碼90% 是波特率不匹配或共地沒(méi)接。我一開(kāi)始用 9600 波特率發(fā)現(xiàn)數(shù)據(jù)偶爾丟后來(lái)提到 115200 就穩(wěn)了。共地一定要接否則電平參考不一致數(shù)據(jù)全是亂碼。還有一個(gè)坑是SoftwareSerial 和硬件串口沖突。R4 Minima 的硬件串口是 D0 和 D1這兩個(gè)腳同時(shí)也連著 USB 調(diào)試口。如果用了硬件串口通信就不能用 USB 串口監(jiān)視器了。所以我用 SoftwareSerial 在 D2 和 D3 上模擬串口硬件串口留給調(diào)試輸出。5.4 常見(jiàn)問(wèn)題速查表現(xiàn)象可能原因排查方法解決方案基線漂移電極接觸不良看串口基線值是否持續(xù)變化重新貼電極清潔皮膚舵機(jī)靜息抖動(dòng)ADC 噪聲或電源不足示波器看 PWM 波形加濾波電容換電源串口亂碼波特率不匹配或未共地檢查兩邊波特率設(shè)置統(tǒng)一 115200接 GND舵機(jī)堵轉(zhuǎn)讓步邏輯閾值太大觀察對(duì)抗時(shí)是否卡死減小讓步閾值增大讓步幅度信號(hào)幅度小電極位置不對(duì)串口繪圖器看發(fā)力時(shí)波形調(diào)整電極到肌腹位置Arduino 復(fù)位舵機(jī)電流拉垮電源舵機(jī)一動(dòng)就復(fù)位舵機(jī)獨(dú)立供電共地5.5 獨(dú)家避坑技巧技巧一用膠帶固定電極線。電極線晃動(dòng)會(huì)產(chǎn)生運(yùn)動(dòng)偽跡用醫(yī)用膠帶把線固定在手臂上信號(hào)穩(wěn)定性提升明顯。技巧二代碼里加看門狗。R4 Minima 有內(nèi)置看門狗如果程序跑飛看門狗會(huì)自動(dòng)復(fù)位。在setup()里加wdt.enable(2000)2 秒沒(méi)喂狗就復(fù)位防止舵機(jī)卡在某個(gè)角度。技巧三串口輸出加時(shí)間戳。調(diào)試時(shí)在每行數(shù)據(jù)前加millis()值方便分析信號(hào)變化的時(shí)間關(guān)系。比如發(fā)力后多久舵機(jī)響應(yīng)讓步邏輯觸發(fā)了幾次一目了然。技巧四機(jī)械臂用尼龍螺絲。金屬螺絲在對(duì)抗時(shí)容易松動(dòng)尼龍螺絲有彈性能吸收沖擊而且不會(huì)導(dǎo)電短路。6. 項(xiàng)目擴(kuò)展與個(gè)人體會(huì)這個(gè)項(xiàng)目跑通之后擴(kuò)展空間很大。比如把舵機(jī)換成直線推桿對(duì)抗力度更真實(shí)或者加一個(gè)OLED 屏幕實(shí)時(shí)顯示雙方發(fā)力數(shù)值觀眾看得更直觀還可以把數(shù)據(jù)通過(guò)串口傳到電腦用 Python 畫實(shí)時(shí)對(duì)抗曲線做成科普展示裝置。我自己在實(shí)際操作中的體會(huì)是EMG 項(xiàng)目最難的不是代碼而是信號(hào)質(zhì)量的控制。同樣的代碼電極貼得好和貼得差效果天差地別。所以如果你要復(fù)現(xiàn)這個(gè)項(xiàng)目建議先在電極和皮膚處理上多花時(shí)間把基線穩(wěn)定性搞定后面的事情就順了。另外Arduino UNO R4 Minima 這塊板子在這個(gè)項(xiàng)目里表現(xiàn)很穩(wěn)14 位 ADC 的優(yōu)勢(shì)在 EMG 采集上體現(xiàn)得很明顯。如果你手頭只有 R3也能跑但舵機(jī)動(dòng)作會(huì)粗糙一些可以通過(guò)增大滑動(dòng)平均窗口來(lái)彌補(bǔ)代價(jià)是響應(yīng)變慢。取舍看你的需求。最后分享一個(gè)小技巧調(diào)試階段可以用函數(shù)發(fā)生器模擬 EMG 信號(hào)輸入到 AD8232這樣不用每次都貼電極調(diào)代碼效率高很多。信號(hào)用 100Hz 正弦波幅度 1mV 左右經(jīng)過(guò) AD8232 放大后正好在 Arduino 量程中間非常方便。