閉環(huán)控制實(shí)戰(zhàn))
1. 項(xiàng)目整體設(shè)計(jì)與需求拆解1.1 為什么選MSPM0G3507這顆芯片來(lái)做PID電機(jī)控制先聊一個(gè)很多人會(huì)問(wèn)的問(wèn)題做電機(jī)控制入門(mén)為什么不用爛大街的STM32非要選立創(chuàng)天猛星MSPM0G3507我的答案很直接因?yàn)樗阋恕蛴?、而且調(diào)試鏈路極其順手。MSPM0G3507是TI推出的Cortex-M0內(nèi)核MCU主頻最高80MHz片上集成了一堆對(duì)電機(jī)控制非常友好的外設(shè)——高級(jí)定時(shí)器可以輸出帶死區(qū)互補(bǔ)的PWM、內(nèi)置運(yùn)放OPA、12位ADC支持多通道同步采樣再加上DMA可以自動(dòng)搬運(yùn)編碼器計(jì)數(shù)值這些資源放在一個(gè)入門(mén)級(jí)芯片上屬實(shí)厚道。更關(guān)鍵的是立創(chuàng)天猛星這塊板子把MSPM0G3507的引腳幾乎全部引出板上自帶了按鍵、LED、USB轉(zhuǎn)串口還預(yù)留了電機(jī)驅(qū)動(dòng)接口的擴(kuò)展排針。你不需要自己畫(huà)板子也不需要外接一堆亂七八糟的調(diào)試工具一塊板加一個(gè)直流減速電機(jī)再加一個(gè)帶霍爾編碼器的電機(jī)輪就能把PID閉環(huán)跑起來(lái)。實(shí)測(cè)下來(lái)這套組合的核心優(yōu)勢(shì)是從點(diǎn)燈到電機(jī)轉(zhuǎn)起來(lái)只要半天從開(kāi)環(huán)調(diào)速到閉環(huán)穩(wěn)速也就是一個(gè)下午的事。對(duì)于大二大三做課程設(shè)計(jì)、社團(tuán)培訓(xùn)或者畢業(yè)設(shè)計(jì)預(yù)研的同學(xué)來(lái)說(shuō)這個(gè)學(xué)習(xí)曲線非常舒服。1.2 這個(gè)項(xiàng)目到底要解決什么問(wèn)題如果你只是想讓電機(jī)轉(zhuǎn)起來(lái)直接給PWM占空比就行根本不用PID。但真正常見(jiàn)的痛點(diǎn)是這幾個(gè)第一電機(jī)帶載后轉(zhuǎn)速會(huì)掉。你給30%的占空比空載時(shí)每分鐘轉(zhuǎn)2000轉(zhuǎn)用手捏住輪子轉(zhuǎn)速可能掉到1200轉(zhuǎn)而你希望它穩(wěn)定在2000轉(zhuǎn)。第二不同的PWM占空比和轉(zhuǎn)速之間不是線性關(guān)系。電池電壓在變化、電機(jī)溫度在變化、摩擦力在變化開(kāi)環(huán)控制永遠(yuǎn)追不平。第三你需要的不是一個(gè)固定占空比而是一個(gè)穩(wěn)定轉(zhuǎn)速。這就必須引入閉環(huán)測(cè)出當(dāng)前轉(zhuǎn)速和期望轉(zhuǎn)速做比較差值經(jīng)過(guò)PID控制器計(jì)算調(diào)整PWM占空比讓電機(jī)轉(zhuǎn)速收斂到目標(biāo)值。所以這個(gè)項(xiàng)目的本質(zhì)是用MSPM0G3507采集霍爾編碼器的脈沖信號(hào)換算成實(shí)際轉(zhuǎn)速再通過(guò)增量式PID算法計(jì)算出應(yīng)該輸出的PWM占空比最終實(shí)現(xiàn)電機(jī)轉(zhuǎn)速在負(fù)載擾動(dòng)下的閉環(huán)穩(wěn)定。做完了這一步你后面再去搞循跡小車(chē)、平衡車(chē)、四軸云臺(tái)、甚至無(wú)刷電機(jī)FOC都是在這個(gè)基礎(chǔ)上加外設(shè)、加算法維度而已。PID是底層地基地基打不牢上層全是空中樓閣。2. MSPM0G3507電機(jī)控制硬件方案與接線2.1 核心器件清單與選型理由在動(dòng)手接線之前先把器件備齊。我的配置如下全部來(lái)自立創(chuàng)商城成本控制在100塊以內(nèi)器件型號(hào)/規(guī)格用途備注主控板立創(chuàng)天猛星MSPM0G3507運(yùn)行PID算法、輸出PWM、采集編碼器板載DAP-Link調(diào)試器USB供電直流減速電機(jī)JGA25-370帶霍爾編碼器被控對(duì)象減速比1:30左右?guī)B相編碼器電機(jī)驅(qū)動(dòng)TB6612FNG模塊將MCU的PWM信號(hào)放大為電機(jī)驅(qū)動(dòng)電流比L298N體積小、壓降低電源7.4V 18650兩串鋰電池組給電機(jī)供電邏輯電路由USB的5V供電共地杜邦線若干公對(duì)公、公對(duì)母接線調(diào)試建議多備幾種顏色區(qū)分信號(hào)USB轉(zhuǎn)串口模塊CH340上位機(jī)調(diào)試數(shù)據(jù)回傳板載串口也可直接用選TB6612FNG而不是L298N是因?yàn)長(zhǎng)298N的飽和壓降太高實(shí)測(cè)接近2V電機(jī)在低壓時(shí)扭矩輸出很肉而且模塊體積大、發(fā)熱嚴(yán)重。TB6612的內(nèi)部MOSFET導(dǎo)通電阻只有零點(diǎn)幾歐姆PWM頻率高了也不發(fā)燙更適合這種學(xué)習(xí)場(chǎng)景。JGA25-370的減速電機(jī)則是因?yàn)樗阋?、皮?shí)、編碼器集成的方案成熟淘寶一搜一大把替換件也好買(mǎi)。2.2 供電、電機(jī)驅(qū)動(dòng)與編碼器接線全圖解接線前先強(qiáng)調(diào)一個(gè)非常重要的原則電機(jī)驅(qū)動(dòng)電源和MCU邏輯電源必須共地。否則PWM信號(hào)沒(méi)有參考地驅(qū)動(dòng)板無(wú)法識(shí)別電平電機(jī)要么不動(dòng)要么亂抖。我的接線方式如下電源模塊正極接TB6612的VM引腳負(fù)極接GND同時(shí)從負(fù)極引一根線接到天猛星板的GND上完成共地。TB6612的PWMA接MSPM0G3507的PA10TIMG8通道0AIN1接PA11AIN2接PA12STBY接3.3V。這里PWMA控制轉(zhuǎn)速AIN1和AIN2控制方向——一個(gè)高一個(gè)低時(shí)正轉(zhuǎn)反向則反轉(zhuǎn)。電機(jī)M、M-分別接TB6612的AO1、AO2。編碼器的VCC接3.3VGND接GNDA相輸出接PB6TIMG12通道0B相輸出接PB7TIMG12通道1。為什么編碼器要接在定時(shí)器的兩個(gè)通道上因?yàn)镸SPM0G3507的QEI正交編碼器接口外設(shè)需要同時(shí)對(duì)A、B兩相計(jì)數(shù)才能判斷旋轉(zhuǎn)方向和精確位置。單通道只能計(jì)數(shù)不能辨向。接線完成后先別急著上電。用萬(wàn)用表測(cè)一下TB6612的VM和GND之間電壓是否正常再測(cè)STBY引腳是否為高電平——這個(gè)引腳如果接地了驅(qū)動(dòng)芯片會(huì)直接進(jìn)入待機(jī)模式電機(jī)怎么調(diào)都不轉(zhuǎn)我見(jiàn)過(guò)太多人栽在這個(gè)細(xì)節(jié)上。2.3 為什么PWM頻率選10kHz而不是1kHz或100kHzPWM頻率的選擇直接影響電機(jī)運(yùn)行的噪音和力矩平滑度。頻率太低比如1kHz電機(jī)電流紋波大轉(zhuǎn)速在低速時(shí)會(huì)一頓一頓的聽(tīng)得到明顯的高頻嘯叫。頻率太高100kHz雖然電流平滑但MOS管的開(kāi)關(guān)損耗急劇上升TB6612會(huì)發(fā)熱厲害而且驅(qū)動(dòng)板布線寄生參數(shù)的影響也開(kāi)始顯現(xiàn)。10kHz是一個(gè)折中值超出了人耳聽(tīng)覺(jué)敏感區(qū)的大部分范圍實(shí)際上還是能聽(tīng)到輕微嘶嘶聲開(kāi)關(guān)損耗可控對(duì)霍爾編碼器采集也沒(méi)有干擾。這個(gè)頻率在絕大多數(shù)直流電機(jī)驅(qū)動(dòng)場(chǎng)景下都是安全選擇。配置時(shí)注意MSPM0G3507的PWM用TIMG8計(jì)數(shù)模式選UpCount周期值存到LOAD寄存器占空比存到CMP寄存器。我以80MHz的時(shí)鐘源為例預(yù)分頻設(shè)為1那么定時(shí)器計(jì)數(shù)頻率是40MHz要得到10kHz的PWMLOAD值就是40_000_000 / 10_000 4000。占空比50%時(shí)CMP2000占空比100%時(shí)CMP4000一般不要滿載給死區(qū)留余量。提示占空比的更新一定要在PWM周期中斷里做或者使用定時(shí)器的影子寄存器功能否則在計(jì)數(shù)中途改CMP值輸出波形可能出現(xiàn)毛刺電機(jī)就會(huì)突然抖一下。3. PID控制算法原理與離散化落地3.1 大白話理解P、I、D三個(gè)參數(shù)各管什么事PID這三個(gè)字母拆開(kāi)看都不復(fù)雜但很多人栽在不知道每個(gè)參數(shù)調(diào)大了會(huì)怎樣、調(diào)小了會(huì)怎樣。我用送外賣(mài)來(lái)打比方P比例相當(dāng)于你離目的地還有1公里你決定開(kāi)多快的車(chē)。誤差大就開(kāi)快一點(diǎn)誤差小就開(kāi)慢一點(diǎn)。比例系數(shù)Kp越大反應(yīng)越激進(jìn)但容易沖過(guò)頭在目標(biāo)附近來(lái)回震蕩。I積分相當(dāng)于你意識(shí)到一直有股逆風(fēng)光靠速度控制到不了目的地于是你開(kāi)始累計(jì)這段路程的“欠賬”把欠的里程折算成額外的速度補(bǔ)上去。積分系數(shù)Ki解決的是穩(wěn)態(tài)誤差——比如電機(jī)帶著負(fù)載比例控制只能讓轉(zhuǎn)速逼近目標(biāo)但總是差一截積分項(xiàng)能把這截補(bǔ)平。D微分相當(dāng)于你看到前面100米是紅燈提前開(kāi)始剎車(chē)減速而不是沖到跟前才猛踩。微分系數(shù)Kd抑制超調(diào)但也不能調(diào)太大否則對(duì)噪聲特別敏感一個(gè)測(cè)量毛刺就能讓輸出劇烈抖動(dòng)。三句話總結(jié)P管響應(yīng)快不快I管準(zhǔn)不準(zhǔn)D管穩(wěn)不穩(wěn)。3.2 增量式PID與位置式PID怎么選實(shí)際工程里兩種形式都有應(yīng)用但直流電機(jī)調(diào)速場(chǎng)景更推薦增量式。位置式PID的輸出是直接給執(zhí)行器的絕對(duì)量公式為u(k) Kp*e(k) Ki*sum(e(k)) Kd*(e(k) - e(k-1))這個(gè)公式的問(wèn)題在于積分項(xiàng)累計(jì)了所有歷史誤差。一旦某一階段誤差持續(xù)很大sum項(xiàng)會(huì)不斷膨脹等誤差變?yōu)?時(shí)積分輸出還需要時(shí)間回落導(dǎo)致系統(tǒng)的超調(diào)量被放大。另外如果你需要切換控制模式或者換向位置式的輸出會(huì)有一個(gè)很大的跳變對(duì)電機(jī)和驅(qū)動(dòng)器都不友好。增量式PID則計(jì)算的是控制量的增量delta_u(k) Kp*(e(k)-e(k-1)) Ki*e(k) Kd*(e(k)-2*e(k-1)e(k-2)) u(k) u(k-1) delta_u(k)每次只改一點(diǎn)點(diǎn)輸出平滑安全性高而且天然自帶抗積分飽和的屬性——就算誤差一直很大增量也不會(huì)無(wú)限累積。代碼上只需要保存e(k-1)和e(k-2)兩個(gè)歷史值內(nèi)存開(kāi)銷(xiāo)極小非常適合M0內(nèi)核這種小資源MCU。所以在這個(gè)項(xiàng)目里我采用增量式PID控制周期固定為10ms。3.3 從連續(xù)PID到差分方程的推導(dǎo)這一步是很多人看到數(shù)學(xué)公式就繞道的地方其實(shí)根本不復(fù)雜。連續(xù)PID的頻域表達(dá)式是U(s) (Kp Ki/s Kd*s) * E(s)要做數(shù)字控制得把它離散化。這里用最直觀的差分近似誤差的積分近似為誤差的累加所以積分項(xiàng)從連續(xù)域的Ki * integral(e(t))dt變成離散域的Ki * T * sum(e(k))其中T是采樣周期。誤差的微分近似為誤差的變化量除以周期所以微分項(xiàng)從Kd * de(t)/dt變成Kd * (e(k) - e(k-1)) / T。把這兩個(gè)替換代入位置式PID得到的就是前面寫(xiě)的那串公式。增量式的推導(dǎo)也不復(fù)雜把位置式u(k)減掉u(k-1)中間一堆項(xiàng)約掉最后只留下e(k)、e(k-1)、e(k-2)三個(gè)誤差的組合。我在調(diào)試時(shí)踩過(guò)一個(gè)坑如果采樣周期T不固定微分項(xiàng)的分母時(shí)大時(shí)小會(huì)導(dǎo)致Kd在不同時(shí)刻的實(shí)際增益飄忽不定系統(tǒng)會(huì)出現(xiàn)莫名其妙的抖動(dòng)。所以務(wù)必用定時(shí)器中斷固定控制周期不要在while循環(huán)里隨緣采樣。代碼里我用的控制周期是10ms對(duì)應(yīng)的控制頻率是100Hz。這個(gè)頻率對(duì)直流減速電機(jī)的轉(zhuǎn)速環(huán)來(lái)說(shuō)完全夠用給得太高反而容易引入測(cè)量噪聲。4. 編碼器測(cè)速與MSPM0G3507底層驅(qū)動(dòng)實(shí)現(xiàn)4.1 霍爾編碼器的工作原理與倍頻技巧JGA25-370電機(jī)尾部那個(gè)霍爾編碼器內(nèi)部其實(shí)是AB兩相互差90度的方波信號(hào)。電機(jī)每轉(zhuǎn)一圈編碼器輸出特定數(shù)量的脈沖。我的這個(gè)電機(jī)減速比1:30編碼器是13線經(jīng)過(guò)減速后輸出軸每轉(zhuǎn)一圈對(duì)應(yīng)13 * 30 390個(gè)脈沖。AB兩相的相位差還帶了方向信息A相超前B相90度為正轉(zhuǎn)反之是反轉(zhuǎn)。用定時(shí)器的正交編碼器模式來(lái)計(jì)數(shù)硬件會(huì)自動(dòng)根據(jù)相位關(guān)系做加計(jì)數(shù)或減計(jì)數(shù)CPU完全不用干預(yù)。這里有個(gè)關(guān)鍵技巧你可以將A、B兩相都用來(lái)計(jì)數(shù)得到4倍頻的效果也就是實(shí)際脈沖計(jì)數(shù)是390 * 4 1560。用MSPM0G3507的QEI模式配置計(jì)數(shù)器的核心知識(shí)是每次A或B信號(hào)變化計(jì)數(shù)器都會(huì)更新一次而不是只在一個(gè)沿更新。這樣低速時(shí)測(cè)速分辨率更高不容易出現(xiàn)“轉(zhuǎn)速顯示為0但電機(jī)明明在轉(zhuǎn)”的尷尬。4.2 定時(shí)器配置與編碼器計(jì)數(shù)值讀取MSPM0G3507的QEI外設(shè)配置步驟大致如下初始化PB6、PB7為T(mén)IMG12的通道0和通道1輸入設(shè)置為正交編碼器模式。配置TIMG12的計(jì)數(shù)模式為Encode Mode這樣計(jì)數(shù)方向由A/B相相位自動(dòng)決定。配置溢出中斷當(dāng)計(jì)數(shù)器值超過(guò)設(shè)定范圍或者跨過(guò)零時(shí)觸發(fā)中斷方便做多圈累計(jì)。在10ms的控制周期里我讀取一次計(jì)數(shù)器值然后立刻清零這樣拿到的是“10ms內(nèi)的脈沖增量”。這個(gè)增量除以編碼器分辨率再除以時(shí)間就得到轉(zhuǎn)速。舉個(gè)具體例子10ms內(nèi)計(jì)數(shù)值讀到780電機(jī)軸每轉(zhuǎn)一圈計(jì)數(shù)1560那么轉(zhuǎn)速就是每10ms圈數(shù) 780 / 1560 0.5圈/10ms 每秒圈數(shù) 0.5 * 100 50圈/秒 每分鐘轉(zhuǎn)速 50 * 60 3000轉(zhuǎn)/分這個(gè)換算邏輯務(wù)必自己推一遍。脫褲子放屁地說(shuō)轉(zhuǎn)速計(jì)算的本質(zhì)就一句話測(cè)出單位時(shí)間內(nèi)的脈沖數(shù)再除以每圈的脈沖數(shù)就得出單位時(shí)間的圈數(shù)。4.3 DMA在編碼器采集里的作用搜索熱詞里有人提到mspm0g3507 dma這里展開(kāi)說(shuō)一下。在更復(fù)雜的應(yīng)用里比如你要同時(shí)采集編碼器、電流傳感器、電壓傳感器如果每個(gè)控制周期都靠CPU輪詢?nèi)プx計(jì)數(shù)器CPU會(huì)被打斷得很頻繁導(dǎo)致控制時(shí)序抖動(dòng)。DMA可以做到定時(shí)器觸發(fā)一個(gè)事件DMA自動(dòng)把計(jì)數(shù)器值搬運(yùn)到內(nèi)存變量里CPU全程不用管。我在這個(gè)入門(mén)項(xiàng)目里沒(méi)有用DMA因?yàn)橹蛔x一個(gè)編碼器計(jì)數(shù)器沒(méi)那么大壓力。但如果你做了循跡小車(chē)板子上又接了多個(gè)傳感器我強(qiáng)烈建議把DMA用起來(lái)。MSPM0G3507的DMA通道可以配置為定時(shí)器事件觸發(fā)每次控制周期中斷產(chǎn)生的同時(shí)DMA把編碼器計(jì)數(shù)值搬運(yùn)到全局變量中斷服務(wù)函數(shù)里直接讀取即可。配置DMA時(shí)有個(gè)坑源地址必須配置為編碼器計(jì)數(shù)器的寄存器地址而且是增量模式關(guān)閉每次搬運(yùn)的都是同一個(gè)地址。目標(biāo)地址可以是內(nèi)存里的一個(gè)數(shù)組開(kāi)啟循環(huán)模式這樣搬運(yùn)一次自動(dòng)重新武裝不用反復(fù)手動(dòng)啟動(dòng)。4.4 電機(jī)轉(zhuǎn)向判斷與堵轉(zhuǎn)保護(hù)有了AB相正交編碼器判斷轉(zhuǎn)向非常簡(jiǎn)單讀定時(shí)器的方向標(biāo)志位或者看計(jì)數(shù)器的值是正增長(zhǎng)還是負(fù)增長(zhǎng)。正增長(zhǎng)為正轉(zhuǎn)負(fù)增長(zhǎng)為反轉(zhuǎn)。堵轉(zhuǎn)保護(hù)是一個(gè)容易被忽略但很重要的功能。我在代碼里加了一個(gè)邏輯如果設(shè)定轉(zhuǎn)速大于0但實(shí)際轉(zhuǎn)速小于某個(gè)閾值比如50轉(zhuǎn)/分持續(xù)超過(guò)1秒就判定為堵轉(zhuǎn)自動(dòng)把PWM輸出降到0防止電機(jī)持續(xù)大電流發(fā)熱燒毀。這個(gè)保護(hù)邏輯在課程設(shè)計(jì)演示時(shí)很加分萬(wàn)一你的輪子被卡住了板子不會(huì)冒煙。5. 增量式PID的代碼實(shí)現(xiàn)與參數(shù)調(diào)試實(shí)戰(zhàn)5.1 主程序架構(gòu)與控制周期怎么規(guī)劃控制周期定時(shí)器我用的是TIMG0配置為10ms中斷一次。中斷優(yōu)先級(jí)設(shè)置合理即可主循環(huán)里完全不用做和PID相關(guān)的事空出來(lái)做顯示刷新、按鍵掃描、串口打印。偽代碼如下volatile int32_t encoder_pulses 0; volatile int32_t encoder_pulses_prev 0; int main(void) { // 初始化系統(tǒng)時(shí)鐘、GPIO、PWM、編碼器接口、控制定時(shí)器 sys_init(); pwm_init(10000); // 10kHz PWM qei_init(); control_timer_init(10); // 10ms控制周期 int16_t target_speed 2000; // 目標(biāo)轉(zhuǎn)速 2000 RPM int16_t current_speed 0; int16_t pwm_output 0; while(1) { // 非實(shí)時(shí)任務(wù)按鍵、OLED顯示、串口打印 if (status_changed) { target_speed read_target_from_key(); print_speed(target_speed, current_speed, pwm_output); } } } void TIMG0_IRQHandler(void) { // 讀編碼器增量 int32_t delta encoder_pulses - encoder_pulses_prev; encoder_pulses_prev encoder_pulses; // 換算成轉(zhuǎn)速 RPM current_speed (int16_t)(delta * 60000 / (ENCODER_RESOLUTION * CONTROL_PERIOD_MS)); // 調(diào)用增量式PID pwm_output pid_update(target_speed, current_speed); if (pwm_output 0) { set_direction_reverse(); pwm_output -pwm_output; } else { set_direction_forward(); } pwm_set_duty(pwm_output); }這段代碼是骨架突出的是“實(shí)時(shí)任務(wù)全放中斷”的思想。很多初學(xué)者習(xí)慣在while循環(huán)里delay一會(huì)兒再采一次樣一旦主循環(huán)有別的耗時(shí)操作控制周期就亂了PID就調(diào)不好。記住PID控制周期不允許被其他業(yè)務(wù)邏輯阻塞。5.2 PID結(jié)構(gòu)體與核心計(jì)算函數(shù)我習(xí)慣把PID封裝成結(jié)構(gòu)體這樣后面做串級(jí)PID比如循跡小車(chē)的位置環(huán)速度環(huán)可以直接復(fù)用。typedef struct { float kp; float ki; float kd; float target; // 目標(biāo)值 float error_prev; // e(k-1) float error_prev2; // e(k-2) float output_prev; // u(k-1) } PidObject_t; float pid_update(PidObject_t *pid, float target, float actual) { float error target - actual; float delta_u pid-kp * (error - pid-error_prev) pid-ki * error pid-kd * (error - 2 * pid-error_prev pid-error_prev2); float output pid-output_prev delta_u; // 輸出限幅防止PWM飽和 if (output 4000) output 4000; if (output 0) output 0; // 更新?tīng)顟B(tài) pid-error_prev2 pid-error_prev; pid-error_prev error; pid-output_prev output; return output; }注意幾點(diǎn)一是輸出限幅要放在更新output_prev之前否則飽和時(shí)積分項(xiàng)繼續(xù)累積恢復(fù)響應(yīng)變慢這個(gè)在位置式里叫積分飽和增量式雖然好一些但也要限幅二是誤差和輸出變量的類型在M0上float運(yùn)算比int慢不少但增量式PID計(jì)算量不大用float完全沒(méi)問(wèn)題代碼可讀性更高。5.3 參數(shù)整定先KP再KD最后KI參數(shù)整定是這個(gè)項(xiàng)目最磨人的環(huán)節(jié)也是最有干貨的地方。我的經(jīng)驗(yàn)是遵循一套固定流程不要瞎試先令Ki0Kd0只留Kp。從小到大慢慢加Kp同時(shí)用串口上位機(jī)觀察轉(zhuǎn)速曲線。當(dāng)Kp比較小時(shí)電機(jī)響應(yīng)慢轉(zhuǎn)速達(dá)不到目標(biāo)穩(wěn)定在目標(biāo)值附近存在明顯靜差繼續(xù)加大Kp轉(zhuǎn)速開(kāi)始逼近目標(biāo)但如果Kp過(guò)大轉(zhuǎn)速會(huì)在目標(biāo)值附近來(lái)回震蕩示波器上看就是正弦波一樣的波動(dòng)。此時(shí)記下開(kāi)始震蕩的Kp值然后把Kp設(shè)為這個(gè)震蕩臨界值的一半到三分之二。接著引入Kd。Kd的作用是抑制超調(diào)但這個(gè)時(shí)候如果轉(zhuǎn)速曲線還是震蕩的說(shuō)明Kd太小調(diào)大Kd震蕩幅值會(huì)變小直到臨界點(diǎn)變成平滑收斂。但Kd不能太大因?yàn)榫幋a器測(cè)速本身有量化噪聲Kd過(guò)大會(huì)把這種測(cè)量噪聲放大成PWM抖動(dòng)電機(jī)發(fā)出不規(guī)則的嗡嗡聲。最后引入Ki。Ki用來(lái)消除靜差。把Ki從0慢慢加觀察穩(wěn)定后的轉(zhuǎn)速是否和目標(biāo)吻合。如果Ki太大轉(zhuǎn)速會(huì)出現(xiàn)低頻的“喘振”比如目標(biāo)2000轉(zhuǎn)實(shí)際在1800和2200之間緩慢搖擺這就是積分過(guò)強(qiáng)導(dǎo)致的周期振蕩減小Ki即可。一個(gè)比較實(shí)用的初始參數(shù)組合Kp0.5Ki0.01Kd0.02單位是按PWM滿量程4000、轉(zhuǎn)速單位RPM來(lái)的。你可以自己換算。5.4 使用Vofa上位機(jī)觀察PID最優(yōu)曲線搜熱詞的時(shí)候看到有人在找PID模擬器和調(diào)試工具這里我推薦一個(gè)特別好用的組合串口輸出格式化數(shù)據(jù) Vofa JustFloat協(xié)議。Vofa是一款上位機(jī)軟件支持波形顯示底下的實(shí)現(xiàn)邏輯很簡(jiǎn)單按照J(rèn)ustFloat協(xié)議發(fā)送float型數(shù)據(jù)末尾加幀尾0x00 0x00 0x80 0x7f上位機(jī)就能把多個(gè)數(shù)據(jù)實(shí)時(shí)畫(huà)成曲線。我實(shí)際發(fā)的數(shù)據(jù)結(jié)構(gòu)是這樣目標(biāo)轉(zhuǎn)速float 實(shí)際轉(zhuǎn)速float PWM占空比f(wàn)loat 幀尾這樣能在電腦上直觀看到轉(zhuǎn)速曲線怎么逼近目標(biāo)、超調(diào)多少、PWM怎么變化。什么叫“PID最優(yōu)曲線”不是一條直線而是啟動(dòng)時(shí)轉(zhuǎn)速快速上升接近目標(biāo)時(shí)有輕微超調(diào)但不超過(guò)5%隨后迅速收斂到目標(biāo)值穩(wěn)定后曲線平直沒(méi)有持續(xù)的周期性波動(dòng)。我調(diào)試時(shí)的判斷標(biāo)準(zhǔn)就是以目標(biāo)2000RPM為例最好能在300ms內(nèi)第一次到達(dá)目標(biāo)超調(diào)控制在100RPM以內(nèi)之后不再震蕩。6. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄6.1 電機(jī)不轉(zhuǎn)但板子燈已經(jīng)在閃了這種問(wèn)題90%出在硬件接線上排查順序非常明確第一步量TB6612的VM和GND電壓確認(rèn)電機(jī)供電正常。第二步量STBY引腳確認(rèn)是高電平。第三步用示波器或者萬(wàn)用表頻率檔量PWMA引腳確認(rèn)有10kHz方波且占空比在變化——如果沒(méi)有看代碼里PWM初始化是否正確通道映射對(duì)不對(duì)。第四步檢查AIN1和AIN2的組合是否滿足正轉(zhuǎn)條件——相同電平是剎車(chē)一高一低才是轉(zhuǎn)。第五步也是很多人會(huì)犯的編碼器方向接反了PID輸出反向?qū)е码姍C(jī)在0占空比和反向死區(qū)之間徘徊看起來(lái)像“沒(méi)轉(zhuǎn)”。6.2 電機(jī)抖動(dòng)、嘯叫轉(zhuǎn)速不穩(wěn)定抖動(dòng)分為好幾種情況挨個(gè)說(shuō)高頻抖動(dòng)聲音尖銳大概率是Kd過(guò)大放大了編碼器量化噪聲。把Kd調(diào)到原來(lái)的一半試試。低頻喘振轉(zhuǎn)速不規(guī)則搖擺大概率是Ki過(guò)大嘗試減小Ki。電機(jī)有規(guī)律地一頓一頓PWM頻率太低或者控制周期太長(zhǎng)。PWM頻率低于5kHz時(shí)低速抖動(dòng)感很明顯。我用的10kHz基本沒(méi)這個(gè)問(wèn)題。啟動(dòng)瞬時(shí)劇烈震蕩然后收斂Kp太大啟動(dòng)瞬間誤差大輸出瞬間飽和導(dǎo)致電機(jī)猛然一沖。解決方法是給輸出變化率限幅或者減小Kp。6.3 轉(zhuǎn)速值測(cè)量不準(zhǔn)串口看到的數(shù)字亂跳編碼器接線的信號(hào)線建議使用雙絞線或者盡量短一點(diǎn)不要和電機(jī)電源線綁在一起。電機(jī)轉(zhuǎn)動(dòng)時(shí)碳刷會(huì)產(chǎn)生電磁干擾編碼器信號(hào)線如果貼太近AB相信號(hào)會(huì)被毛刺干擾造成計(jì)數(shù)錯(cuò)誤。這個(gè)現(xiàn)象我實(shí)測(cè)過(guò)電機(jī)線束和編碼器線束平行走線時(shí)串口看到的轉(zhuǎn)速比實(shí)際高了20%左右而且曲線毛糙把它們分離開(kāi)之后毛刺立刻消失。如果你的板子已經(jīng)把編碼器接口排針擋住了不好理線可以嘗試在編碼器供電腳加一個(gè)100nF去耦電容也有明顯改善。6.4 電機(jī)發(fā)熱嚴(yán)重但轉(zhuǎn)速上不去先檢查是不是控制方向反了導(dǎo)致PID永遠(yuǎn)在滿占空比輸出如果你期望正轉(zhuǎn)但實(shí)際上電機(jī)的反電動(dòng)勢(shì)讓編碼器計(jì)數(shù)為負(fù)誤差就一直很大PID輸出一直在上限電機(jī)全速運(yùn)行發(fā)熱很正常。這種情況的典型特征是斷電后用手?jǐn)Q輪子編碼器方向標(biāo)志位和你設(shè)定目標(biāo)方向不一致。解決辦法是在代碼里檢測(cè)到方向和目標(biāo)不一致時(shí)交換AIN1和AIN2的邏輯或者直接交換編碼器A、B相接線到MCU的兩個(gè)引腳。另外通電時(shí)間長(zhǎng)了發(fā)熱也正常但正常跑10分鐘表面溫度應(yīng)該在50度左右如果超過(guò)60度摸上去燙手檢查一下PWM頻率是不是設(shè)定太低了。6.5 串口打印的數(shù)據(jù)為什么全是0xFFMSPM0G3507的串口配置方面有個(gè)容易踩的坑默認(rèn)UART時(shí)鐘來(lái)源不是你要的那個(gè)波特率偏差超過(guò)3%就會(huì)亂碼。檢查SysConfig里的UART時(shí)鐘源選擇務(wù)必勾選正確的低頻時(shí)鐘還是高頻時(shí)鐘。另外TX和RX的復(fù)用功能表也要對(duì)清楚不是隨便一個(gè)引腳都能映射串口功能。還有一個(gè)細(xì)節(jié)板載USB轉(zhuǎn)串口用的是板子上的調(diào)試串口如果需要額外接CH340記得共地否則電平參考點(diǎn)不一致打印出來(lái)的數(shù)據(jù)全是亂碼。6.6 常見(jiàn)問(wèn)題速查表現(xiàn)象可能原因排查/解決電機(jī)完全不轉(zhuǎn)STBY引腳為低將STBY接3.3V電機(jī)只往一個(gè)方向轉(zhuǎn)換向控制腳未配置確認(rèn)AIN1/AIN2邏輯PID調(diào)半天不收斂控制周期不固定用定時(shí)器中斷別在while里delay轉(zhuǎn)速目標(biāo)達(dá)到但頻繁跳變編碼器信號(hào)受干擾信號(hào)線遠(yuǎn)離電機(jī)線縮短長(zhǎng)度轉(zhuǎn)速穩(wěn)定但和目標(biāo)差一截積分項(xiàng)太小或?yàn)?增大Ki啟動(dòng)就瘋狂震蕩Kp過(guò)大減小Kp或者加輸出限幅PWM占空比100%但電機(jī)無(wú)力電機(jī)供電電壓不夠檢查電池電壓TB6612壓降串口亂碼波特率偏差、未共地核對(duì)UART時(shí)鐘源和波特率配置7. 實(shí)操心得與后續(xù)擴(kuò)展方向7.1 這個(gè)項(xiàng)目做完你能帶走什么做完這個(gè)項(xiàng)目你的收獲絕不只是“電機(jī)轉(zhuǎn)起來(lái)了”這么簡(jiǎn)單。你對(duì)MCU的定時(shí)器、PWM、正交編碼器、中斷這些基礎(chǔ)外設(shè)會(huì)有一次完整的實(shí)戰(zhàn)理解這些外設(shè)在STM32、華大、沁恒甚至更高端的DSP28379上都是通用的概念只是寄存器名字和配置方式不同而已。更重要的是你建立了一個(gè)“閉環(huán)”的思維框架先測(cè)量再計(jì)算誤差再輸出控制量。這套框架可以無(wú)縫遷移到溫度控制、舵機(jī)角度控制、二維碼循跡小車(chē)上。你再看網(wǎng)上那些dsp28379電機(jī)控制代碼、無(wú)刷電機(jī)FOC工程會(huì)發(fā)現(xiàn)它們?cè)購(gòu)?fù)雜核心也是在這個(gè)基礎(chǔ)上疊加了電流環(huán)、速度環(huán)、位置環(huán)三層PID而已。7.2 我建議你下一步做的三件事第一把增量式PID改成位置式PID對(duì)比兩者在啟動(dòng)和換向時(shí)的表現(xiàn)差異體會(huì)為什么工程上更喜歡增量式。這個(gè)對(duì)比實(shí)驗(yàn)用時(shí)一小時(shí)但你對(duì)PID的理解會(huì)深一個(gè)層次。第二加一個(gè)OLED屏幕顯示目標(biāo)轉(zhuǎn)速、實(shí)際轉(zhuǎn)速和PWM占空比把調(diào)參過(guò)程從“串口看曲線”變成“隨時(shí)看板子”方便現(xiàn)場(chǎng)演示。OLED的I2C初始化在MSPM0G3507上很順半天能搞定。第三試著把控制周期從10ms改到5ms和20ms觀察同一組PID參數(shù)下的轉(zhuǎn)速曲線差異。你會(huì)發(fā)現(xiàn)采樣周期對(duì)控制效果的影響比想象中更大這個(gè)經(jīng)驗(yàn)在你以后做更復(fù)雜的控制系統(tǒng)時(shí)非常寶貴。我再分享一個(gè)個(gè)人經(jīng)驗(yàn)PID調(diào)參不要追求一次到位先把Kp調(diào)出穩(wěn)定的臨界震蕩記下參數(shù)再退出Kd壓超調(diào)最后加Ki補(bǔ)靜差。過(guò)程中把Vofa曲線截圖保存方便對(duì)比每一輪參數(shù)調(diào)整帶來(lái)的變化。這些調(diào)試記錄寫(xiě)進(jìn)你的實(shí)驗(yàn)報(bào)告或者博客會(huì)顯得非常扎實(shí)。真的從零到一跑通一次閉環(huán)控制比看一百篇理論文章都管用。拿上板子接好電機(jī)開(kāi)始吧。