參全解析)
簡(jiǎn)介一套基于STM32F10x的直線倒立擺控制程序面向嵌入式控制初學(xué)者、自動(dòng)化專業(yè)學(xué)生以及機(jī)電一體化開(kāi)發(fā)者核心解決倒立擺的實(shí)時(shí)平衡控制問(wèn)題。工程在經(jīng)典PD、PID基礎(chǔ)上引入微分先行、梯形積分、抗積分飽和與積分分離等優(yōu)化策略并配置了完整的系統(tǒng)初始化、硬件外設(shè)驅(qū)動(dòng)GPIO、ADC、PWM和傳感器數(shù)據(jù)處理代碼。包內(nèi)目錄劃分清晰BALANCE模塊負(fù)責(zé)平衡控制算法HARDWARE為底層驅(qū)動(dòng)SYSTEM包含時(shí)鐘與中斷配置USER存放應(yīng)用邏輯便于按模塊閱讀和復(fù)用。資源包共214個(gè)文件包括55個(gè)頭文件、48個(gè)C源文件、26個(gè)d和26個(gè)o等編譯中間文件、Keil工程文件uvprojx/uvopt及hex可執(zhí)行文件另有htm說(shuō)明文檔和txt說(shuō)明整體約3.79MB。目前已有818人學(xué)習(xí)瀏覽適合課程設(shè)計(jì)、畢業(yè)設(shè)計(jì)或競(jìng)賽作品參考也可作為平衡控制算法驗(yàn)證平臺(tái)。通過(guò)閱讀該工程還能理解STM32標(biāo)準(zhǔn)外設(shè)庫(kù)的調(diào)用方式、控制算法在嵌入式環(huán)境中的離散化實(shí)現(xiàn)以及模塊化工程組織方法是學(xué)習(xí)實(shí)時(shí)控制與嵌入式開(kāi)發(fā)的實(shí)用參考。1. 直線倒立擺不是玄學(xué)STM32控制程序到底能幫你省掉什么第一次接觸直線倒立擺的人十有八九是在課程設(shè)計(jì)或?qū)嶒?yàn)室里被它折磨過(guò)的擺桿裝上去了電機(jī)也轉(zhuǎn)了但小車就是不能把擺桿穩(wěn)穩(wěn)立在豎直位置。很多人下載了現(xiàn)成的STM32控制程序燒進(jìn)去發(fā)現(xiàn)擺桿瘋狂抖動(dòng)兩秒然后砸下來(lái)第一反應(yīng)是代碼有問(wèn)題第二反應(yīng)是硬件壞了。實(shí)際上直線倒立擺是一個(gè)典型的“系統(tǒng)匹配”問(wèn)題底層機(jī)制、傳感器選型、控制周期和算法參數(shù)缺一不可。這份STM32控制程序把機(jī)械結(jié)構(gòu)、編碼器接口、PWM驅(qū)動(dòng)、角度環(huán)和速度環(huán)的完整邏輯都打包好了我拆開(kāi)看過(guò)工程結(jié)構(gòu)清晰核心控制代碼帶注釋適合想快速?gòu)?fù)現(xiàn)并在這個(gè)基礎(chǔ)上調(diào)參數(shù)的人。省掉的不是寫代碼的時(shí)間而是你從零摸清“倒立擺為什么能站起來(lái)”這條思路的時(shí)間。新手能照著接好線、燒寫、調(diào)參熟手可以直接改控制律拿它當(dāng)算法驗(yàn)證平臺(tái)。2. 控制對(duì)象與硬件選型從機(jī)械結(jié)構(gòu)到STM32外設(shè)的匹配很多嵌入式項(xiàng)目可以先寫代碼再考慮硬件但直線倒立擺不行。它的控制效果很大程度取決于機(jī)械精度和傳感器分辨率如果硬件選型不對(duì)后續(xù)算法再怎么調(diào)都救不回來(lái)。這一章先講清楚控制對(duì)象是什么再說(shuō)明程序里每一個(gè)外設(shè)為什么這樣配。2.1 直線倒立擺的物理模型與狀態(tài)量直線倒立擺的經(jīng)典結(jié)構(gòu)是一條水平導(dǎo)軌導(dǎo)軌上有一個(gè)小車小車上固定一根可以自由旋轉(zhuǎn)的擺桿。電機(jī)帶動(dòng)小車在導(dǎo)軌上左右移動(dòng)通過(guò)給小車施加水平力來(lái)控制擺桿的角度。理想情況下不考慮摩擦和空氣阻力擺桿的運(yùn)動(dòng)方程可以寫成(M m) * x_ddot m * L * theta_ddot * cos(theta) - m * L * theta_dot^2 * sin(theta) F m * L * theta_ddot * cos(theta) m * L^2 * theta_ddot - m * g * L * sin(theta) 0其中 M 是小車質(zhì)量m 是擺桿質(zhì)量L 是擺桿質(zhì)心到旋轉(zhuǎn)軸的距離x 是小車位移theta 是擺桿與豎直方向的夾角。在擺桿穩(wěn)定在豎直位置附近時(shí)theta 很小可以近似 sin(theta) ≈ thetacos(theta) ≈ 1并把高階項(xiàng)去掉得到一個(gè)線性狀態(tài)空間方程。這個(gè)過(guò)程是整個(gè)控制程序的理論基礎(chǔ)——最終控制律都是在這個(gè)線性化模型上設(shè)計(jì)的。程序里讀到的狀態(tài)量通常有四個(gè)小車位置 x、小車速度 x_dot、擺桿角度 theta、擺桿角速度 theta_dot。前兩個(gè)來(lái)自導(dǎo)軌一端的編碼器或拉線傳感器后兩個(gè)來(lái)自擺桿旋轉(zhuǎn)軸上的編碼器。這個(gè)資源中默認(rèn)使用的是增量式編碼器通過(guò) STM32 的定時(shí)器編碼器模式直接讀取不需要額外搭解碼電路。2.2 電機(jī)、編碼器、驅(qū)動(dòng)器與STM32的接口設(shè)計(jì)選電機(jī)時(shí)這個(gè)程序針對(duì)的是直流減速電機(jī)帶有霍爾編碼器或光電編碼器。減速比一般在 30:1 以上否則小車加速太快控制精度跟不上。電機(jī)的功率不需要太大但扭矩要能拖動(dòng)小車和擺桿。驅(qū)動(dòng)部分用的是典型的 H 橋驅(qū)動(dòng)模塊比如常見(jiàn)的 L298N 或者 TB6612。STM32 輸出 PWM 信號(hào)控制電機(jī)轉(zhuǎn)速輸出兩個(gè) GPIO 信號(hào)控制正反轉(zhuǎn)。編碼器的接口是這個(gè)項(xiàng)目里最容易接錯(cuò)的地方。如果用的是霍爾增量式編碼器它有 A、B 兩路輸出直接接 STM32 的定時(shí)器通道。以某常用定時(shí)器為例PA0 和 PA1 可以配置成 TIM2_CH1 和 TIM2_CH2開(kāi)啟編碼器模式后定時(shí)器計(jì)數(shù)器值會(huì)隨電機(jī)轉(zhuǎn)動(dòng)自動(dòng)增減不需要中斷輪詢。這個(gè)程序里默認(rèn)編碼器分辨率為 13 線/圈經(jīng)過(guò) 4 倍頻后電機(jī)軸每轉(zhuǎn)一圈產(chǎn)生 52 個(gè)脈沖再乘上減速比就是輪子實(shí)際轉(zhuǎn)動(dòng)帶來(lái)的位移分辨率。如果發(fā)現(xiàn)位置反饋跳動(dòng)優(yōu)先檢查編碼器 A/B 相接反沒(méi)有接反會(huì)導(dǎo)致方向判斷混亂控制正反饋擺桿會(huì)越調(diào)越倒。電機(jī)驅(qū)動(dòng)部分的接線可以按下面的引腳對(duì)應(yīng)關(guān)系來(lái)確認(rèn)信號(hào)STM32引腳說(shuō)明PWM-IN1PA8定時(shí)器1通道1輸出PWMPWM-IN2PA9定時(shí)器1通道2輸出反向PWMDIR-1PB0方向控制高電平正轉(zhuǎn)DIR-2PB1方向控制低電平反轉(zhuǎn)ENCODER-APA0定時(shí)器2通道1ENCODER-BPA1定時(shí)器2通道2不要直接拿 STM32 的 3.3V 引腳驅(qū)動(dòng)電機(jī)驅(qū)動(dòng)模塊的邏輯端很多 H 橋模塊需要 5V 邏輯電平。程序里默認(rèn)在初始化 GPIO 時(shí)把引腳設(shè)置為復(fù)用開(kāi)漏輸出并外接上拉電阻這個(gè)細(xì)節(jié)很容易被復(fù)制代碼的人忽略結(jié)果就是 PWM 信號(hào)出來(lái)了但電機(jī)不動(dòng)。2.3 程序框架定時(shí)器、PWM與中斷優(yōu)先級(jí)安排整個(gè)控制程序跑起來(lái)依賴幾個(gè)關(guān)鍵外設(shè)一個(gè)定時(shí)器產(chǎn)生 PWM一個(gè)定時(shí)器工作在編碼器模式一個(gè)定時(shí)器產(chǎn)生固定周期的控制中斷。倒立擺控制的本質(zhì)是一個(gè)周期性任務(wù)每個(gè)控制周期讀取編碼器計(jì)算角度和位置通過(guò)控制律得到控制量更新 PWM 占空比??刂浦芷谠蕉滔到y(tǒng)可以處理的角速度范圍越大但對(duì)傳感器分辨率和計(jì)算開(kāi)銷也越苛刻。這個(gè)程序選的是 1kHz 控制頻率也就是 1ms 一個(gè)中斷周期。中斷優(yōu)先級(jí)安排是常見(jiàn)的隱藏坑。PWM 中斷和編碼器中斷如果優(yōu)先級(jí)設(shè)置不當(dāng)會(huì)導(dǎo)致控制周期抖動(dòng)。這個(gè)程序把控制周期定時(shí)器中斷優(yōu)先級(jí)設(shè)為最高搶占優(yōu)先級(jí)為 0子優(yōu)先級(jí)為 0編碼器更新中斷設(shè)為次高。因?yàn)榫幋a器工作在硬件計(jì)數(shù)模式其實(shí)不需要中斷只有定時(shí)器溢出時(shí)才需要讀取。串口中斷優(yōu)先級(jí)最低避免收發(fā)數(shù)據(jù)打斷控制周期??刂蒲h(huán)的骨架大致是這樣的void ControlTASK_Init(void) { TIM_TimeBaseInitTypeDef TIM_InitStruct; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); TIM_InitStruct.TIM_Prescaler 84 - 1; // 84MHz時(shí)鐘分頻到1MHz TIM_InitStruct.TIM_Period 1000 - 1; // 1kHz中斷頻率 TIM_InitStruct.TIM_ClockDivision TIM_CKD_DIV1; TIM_InitStruct.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, TIM_InitStruct); TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); NVIC_InitTypeDef NVIC_InitStruct; NVIC_InitStruct.NVIC_IRQChannel TIM3_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority 0; NVIC_InitStruct.NVIC_IRQChannelSubPriority 0; NVIC_InitStruct.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStruct); TIM_Cmd(TIM3, ENABLE); } void TIM3_IRQHandler(void) { if (TIM_GetITStatus(TIM3, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM3, TIM_IT_Update); Encoder_Read(); // 讀取編碼器計(jì)數(shù)值 Control_Update(); // 計(jì)算控制量 Motor_SetPWM(); // 更新PWM輸出 } }參數(shù)說(shuō)明TIM_Prescaler 設(shè)為 83 是因?yàn)橄到y(tǒng)主頻 84MHz分頻后計(jì)數(shù)時(shí)鐘為 1MHzTIM_Period 為 999計(jì)數(shù) 1000 次產(chǎn)生一次更新事件得到 1kHz 中斷。如果主頻不同這兩個(gè)參數(shù)需要按實(shí)際值換算否則控制周期會(huì)偏整定好的參數(shù)會(huì)失效。3. 控制算法落地從PID到LQR的實(shí)現(xiàn)與參數(shù)整定硬件接口通了以后真正的核心是控制算法。這個(gè)資源同時(shí)提供了 PID 和 LQR 兩種實(shí)現(xiàn)并且把切換方法寫在注釋里。新手用 PID 起步熟手可以改狀態(tài)反饋矩陣。這一章把兩條路都拆開(kāi)給出可以直接抄的代碼以及參數(shù)怎么從無(wú)到有地調(diào)出來(lái)。3.1 PID控制為什么能穩(wěn)住倒立擺倒立擺系統(tǒng)是一個(gè)開(kāi)環(huán)不穩(wěn)定的系統(tǒng)擺桿稍微偏離豎直位置重力就會(huì)讓它繼續(xù)倒下。PID 控制器的思路是檢測(cè)到角度偏差輸出一個(gè)力讓小車加速使擺桿恢復(fù)平衡。單環(huán) PID 往往不夠用因?yàn)橹豢刂平嵌葧?huì)讓小車越走越偏最后沖出導(dǎo)軌。所以這個(gè)程序采用串級(jí) PID 結(jié)構(gòu)內(nèi)環(huán)控制角度外環(huán)控制小車位置。角度環(huán)的輸入是擺桿角度偏差輸出是小車速度給定。位置環(huán)的輸入是位置偏差輸出作為角度環(huán)的修正量。換句話說(shuō)當(dāng)小車偏離導(dǎo)軌中心時(shí)角度環(huán)的目標(biāo)值會(huì)被修正讓擺桿略微傾斜產(chǎn)生一個(gè)加速度把小車?yán)刂行?。這種結(jié)構(gòu)在工程里很常用??匆幌陆嵌拳h(huán)的實(shí)現(xiàn)代碼float AnglePID_Calc(float target_angle, float measured_angle) { float error target_angle - measured_angle; float p_out angle_kp * error; float d_out angle_kd * (error - last_angle_error) / dt; last_angle_error error; return p_out d_out; }邏輯說(shuō)明這里沒(méi)有積分項(xiàng)是因?yàn)榈沽[角度環(huán)的主要目標(biāo)是快速抑制偏差積分作用反而容易引入超調(diào)和振蕩。實(shí)際調(diào)試中只需要調(diào)整 angle_kp 和 angle_kd 兩個(gè)參數(shù)。參數(shù)說(shuō)明angle_kp 決定恢復(fù)力的大小設(shè)太小時(shí)擺桿軟綿綿地倒下設(shè)太大時(shí)擺桿高頻抖動(dòng)。angle_kd 提供阻尼抑制角速度過(guò)大導(dǎo)致的過(guò)沖。一般先給 kp 一個(gè)較小值讓它勉強(qiáng)能撐住然后逐漸加大 kd 消除抖動(dòng)。位置環(huán)也是一個(gè)類似的比例加微分控制器輸出量疊加到目標(biāo)角度上float PosPID_Calc(float target_pos, float measured_pos) { float error target_pos - measured_pos; float p_out pos_kp * error; float d_out pos_kd * (error - last_pos_error) / dt; last_pos_error error; return p_out d_out; }整個(gè)控制量計(jì)算流程是先算位置環(huán)輸出用這個(gè)值修正角度環(huán)目標(biāo)再算角度環(huán)輸出得到一個(gè)速度給定最后把速度給定換算成 PWM 占空比限幅后送給電機(jī)。注意這里的兩個(gè) PID 都是 PD 形式積分項(xiàng)被故意去掉了因?yàn)榈沽[工作在平衡點(diǎn)附近穩(wěn)態(tài)誤差主要由摩擦力等非線性因素造成加積分項(xiàng)容易和微分項(xiàng)打架。3.2 LQR控制器狀態(tài)反饋矩陣的計(jì)算與離散化PID 調(diào)參需要經(jīng)驗(yàn)LQR 則提供了更系統(tǒng)的設(shè)計(jì)方法。LQR 是對(duì)線性狀態(tài)空間模型求解一個(gè)最優(yōu)狀態(tài)反饋矩陣 K使代價(jià)函數(shù) J ∫(x^T Q x u^T R u) dt 最小。Q 矩陣對(duì)應(yīng)各個(gè)狀態(tài)的權(quán)重R 矩陣對(duì)應(yīng)控制量的權(quán)重。這個(gè)程序里給出的方法是先離線算好 K 矩陣再寫入代碼而不是在 STM32 里做矩陣運(yùn)算。在 MATLAB 或 Python 里計(jì)算 K 矩陣的代碼import numpy as np from scipy import linalg # 直線倒立擺線性化后的狀態(tài)矩陣自行根據(jù)物理參數(shù)計(jì)算 A np.array([[0, 1, 0, 0], [0, 0, -3.0, 0], [0, 0, 0, 1], [0, 0, 30.0, 0]]) B np.array([[0], [1.0], [0], [5.0]]) Q np.diag([100.0, 10.0, 200.0, 20.0]) R np.array([[1.0]]) K linalg.solve_continuous_are(A, B, Q, R) K np.linalg.inv(R).dot(B.T.dot(K)) print(K matrix:, K)邏輯說(shuō)明A 矩陣的第二行第三列 -3.0 代表擺桿角度對(duì)小車的耦合項(xiàng)第四行第三列 30.0 是重力效應(yīng)項(xiàng)。不同擺桿長(zhǎng)度和質(zhì)量的 A 矩陣數(shù)值不同但結(jié)構(gòu)是一樣的。Q 矩陣?yán)飻[桿角度的權(quán)重遠(yuǎn)大于小車位置因?yàn)榭刂频氖滓繕?biāo)是穩(wěn)定擺桿位置靠后。R 矩陣代表電機(jī)出力代價(jià)R 越小控制越激進(jìn)但 PWM 更容易飽和。得到 K [k1, k2, k3, k4] 后控制量 u -K · x。在 STM32 里實(shí)現(xiàn)時(shí)狀態(tài)向量就是四個(gè)反饋值。因?yàn)榭刂浦芷诠潭?1ms可以離散化后直接算void LQR_Control(void) { float u - (lqr_k1 * state_x lqr_k2 * state_v lqr_k3 * state_theta lqr_k4 * state_omega); if (u PWM_MAX) u PWM_MAX; if (u -PWM_MAX) u -PWM_MAX; Motor_SetPWM((int16_t)u); }參數(shù)說(shuō)明lqr_k1 到 lqr_k4 就是上面算出的 K 矩陣元素。狀態(tài)量單位要注意如果角度用手算的弧度位置用米那么 K 矩陣也對(duì)應(yīng)這套單位。程序里默認(rèn)編碼器讀取的計(jì)數(shù)脈沖值需要轉(zhuǎn)換成米角度轉(zhuǎn)換成弧度否則控制量數(shù)量級(jí)不對(duì)。3.3 代碼結(jié)構(gòu)各模塊如何協(xié)同打開(kāi)工程目錄核心文件有幾個(gè)main.c 負(fù)責(zé)初始化和主循環(huán)encoder.c 負(fù)責(zé)編碼器數(shù)據(jù)處理motor.c 負(fù)責(zé) PWM 和方向控制control.c 負(fù)責(zé)控制算法uart.c 負(fù)責(zé)與上位機(jī)通信。工程文件里還有一份 README說(shuō)明引腳映射和怎么改物理參數(shù)。主循環(huán)里沒(méi)有復(fù)雜的邏輯真正的工作都放在中斷里int main(void) { SystemInit(); GPIO_Config(); Timer_Init(); Encoder_Config(); Motor_Config(); UART_Init(); Control_Struct_Init(); while (1) { // 主循環(huán)僅在必要時(shí)執(zhí)行低優(yōu)先級(jí)任務(wù) if (uart_rx_flag) { uart_rx_flag 0; Param_UpdateFromHost(); // 通過(guò)串口修改參數(shù) } } }這段代碼的意義是明確分層控制邏輯全部在中斷里執(zhí)行主循環(huán)只處理串口通信和參數(shù)修改。這樣避免了主循環(huán)中的任何一個(gè)程序分支拖慢控制周期??刂浦芷诶锏?Encoder_Read() 函數(shù)有兩個(gè)關(guān)鍵點(diǎn)一個(gè)是讀取擺桿編碼器要處理零點(diǎn)漂移另一個(gè)是讀取小車編碼器要考慮方向反轉(zhuǎn)。程序里通過(guò)編碼器的計(jì)數(shù)方向直接判斷小車運(yùn)動(dòng)方向因此初始化時(shí)必須用當(dāng)前編碼器值作為位置零點(diǎn)否則小車一上電就跑偏。4. 調(diào)試與避坑編碼器噪聲、零點(diǎn)漂移、電機(jī)死區(qū)等常見(jiàn)問(wèn)題排查寫代碼可以靠邏輯推演調(diào)倒立擺就是純體力活加眼力活。我拆過(guò)這個(gè)程序后把最容易出現(xiàn)的四個(gè)問(wèn)題按現(xiàn)象列出來(lái)每個(gè)都給出了排查路徑。你遇到的問(wèn)題大概率是其中一種。4.1 現(xiàn)象小車往一個(gè)方向頂死電機(jī)持續(xù)發(fā)熱這是最常見(jiàn)的翻車現(xiàn)場(chǎng)。上電后擺桿還沒(méi)立起來(lái)小車就瘋了一樣往導(dǎo)軌一端沖直到撞到限位開(kāi)關(guān)。原因有兩個(gè)一是擺桿編碼器的零點(diǎn)沒(méi)有對(duì)準(zhǔn)豎直位置。程序默認(rèn)上電瞬間的角度是零度但如果機(jī)械上擺桿此時(shí)不是豎直的控制程序會(huì)誤以為擺桿偏差很大于是朝一個(gè)方向猛推小車。二是 PWM 方向接反了導(dǎo)致負(fù)反饋?zhàn)兂闪苏答?。解決方法是先進(jìn)入“測(cè)試模式”把程序里的控制量輸出固定為一個(gè)很小的值觀察電機(jī)轉(zhuǎn)向是否與編碼器方向相反。然后手動(dòng)把擺桿撥到豎直位置在程序里讀取當(dāng)前編碼器值把這個(gè)值寫入 Angle_Zero_Offset。有些程序支持通過(guò)按鍵設(shè)置零點(diǎn)下載的這個(gè)工程里在串口指令中定義了 ZERO 命令發(fā)送后程序會(huì)把當(dāng)前編碼器值記錄到 Flash。從那以后我每次給擺桿做機(jī)械維護(hù)后都會(huì)強(qiáng)制走一遍零點(diǎn)校準(zhǔn)流程。4.2 現(xiàn)象擺桿高頻抖動(dòng)角度數(shù)據(jù)跳變抖動(dòng)說(shuō)明控制回路有噪聲或控制增益過(guò)高。拿著示波器或通過(guò)串口把角度數(shù)據(jù)打印出來(lái)如果看到角度值在 0 附近左右跳變而不是平滑變化基本可以斷定是編碼器信號(hào)噪聲。增量式編碼器的 A/B 線如果和電機(jī)電源線走在同一個(gè)線槽里電機(jī)換向時(shí)的電流沖擊會(huì)耦合到編碼器信號(hào)上。解決方法有幾個(gè)把編碼器線單獨(dú)走遠(yuǎn)離電機(jī)線在編碼器電源引腳加 100nF 去耦電容或者在程序里對(duì)編碼器計(jì)數(shù)值做濾波。這個(gè)程序里提供了一個(gè)簡(jiǎn)單的滑動(dòng)平均濾波函數(shù)適用于角度信號(hào)。注意濾波會(huì)增加延時(shí)濾波窗口太大會(huì)讓控制相位滯后所以窗口長(zhǎng)度不要超過(guò) 3 個(gè)采樣點(diǎn)。我當(dāng)時(shí)調(diào)這步時(shí)發(fā)現(xiàn)光是給編碼器線絞合就能消除大半跳變。4.3 現(xiàn)象能直立但抗擾性差一推就倒擺桿能立住但輕輕一碰就大幅度擺動(dòng)甚至倒掉。這說(shuō)明控制器的阻尼作用不夠。在 PID 模式里優(yōu)先加大 angle_kd在 LQR 模式里相當(dāng)于需要在 Q 矩陣中加大角速度的權(quán)重。如果你已經(jīng)用了默認(rèn)參數(shù)但系統(tǒng)仍然很軟還有一個(gè)隱藏原因是控制周期不對(duì)。這個(gè)程序默認(rèn) 1kHz但如果有人把 main.c 里的系統(tǒng)時(shí)鐘改了比如超頻到 168MHz而定時(shí)器分頻沒(méi)有相應(yīng)調(diào)整實(shí)際控制周期會(huì)變短或變長(zhǎng)。判斷方法很簡(jiǎn)單程序里有一個(gè)毫秒計(jì)數(shù)變量用秒表對(duì)比串口打印的時(shí)間戳。如果發(fā)現(xiàn)每 1000 個(gè)周期的時(shí)間明顯不是 1 秒需要重新計(jì)算定時(shí)器分頻值。常見(jiàn)做法是在初始化時(shí)用邏輯分析儀抓一個(gè) PWM 翻轉(zhuǎn)波形來(lái)驗(yàn)證周期。4.4 現(xiàn)象程序燒進(jìn)去沒(méi)反應(yīng)電機(jī)不轉(zhuǎn)這個(gè)問(wèn)題多數(shù)不是程序邏輯錯(cuò)誤而是硬件配置問(wèn)題。檢查點(diǎn)按順序來(lái)先量驅(qū)動(dòng)模塊邏輯電源是否 5V再看 STM32 的 PWM 引腳是否有波形輸出最后看編碼器是否插反。我在拆這個(gè)工程時(shí)發(fā)現(xiàn)它的電機(jī)驅(qū)動(dòng)部分默認(rèn)是共陽(yáng)接法需要把驅(qū)動(dòng)模塊的跳線帽調(diào)整到對(duì)應(yīng)模式如果跳線帽不對(duì)PWM 信號(hào)會(huì)被拉到地電機(jī)自然不動(dòng)。另外要注意 GPIO 的復(fù)用配置。這個(gè)程序用了定時(shí)器復(fù)用功能如果漏掉了 GPIO_PinRemapConfig或者把引腳占用了PWM 不會(huì)從正確的引腳輸出。程序里有供電前自檢流程如果某個(gè)外設(shè)初始化失敗LCD 或串口會(huì)打印錯(cuò)誤碼。先把自檢跑通再碰控制邏輯。5. 進(jìn)階讓倒立擺更穩(wěn)的四個(gè)技巧與驗(yàn)證方法當(dāng)你把基礎(chǔ)版調(diào)通以后擺桿能立起來(lái)但總感覺(jué)它像站在氣球上。我在復(fù)現(xiàn)過(guò)程中嘗試了四個(gè)改進(jìn)效果明顯而且改動(dòng)量都不大。第一個(gè)技巧是把編碼器角度信號(hào)和陀螺儀做互補(bǔ)濾波。純編碼器的分辨率有限在高速擺動(dòng)時(shí)角速度信號(hào)會(huì)有滯后和噪聲。如果擺桿上集成了一顆六軸傳感器可以用加速度計(jì)估算角度再用陀螺儀估算角速度與編碼器融合?;パa(bǔ)濾波的代碼我寫在控制中斷里float angle_fused alpha * (last_angle gyro_rate * dt) (1.0f - alpha) * accel_angle;alpha 的物理意義是信任陀螺儀的比例一般在 0.95 到 0.98 之間。alpha 越大融合后的角度越平滑但對(duì)加速度計(jì)的響應(yīng)越慢。這個(gè)參數(shù)需要和編碼器噪聲水平折中。第二個(gè)技巧是加入摩擦力前饋。直線導(dǎo)軌和齒輪箱的靜摩擦力會(huì)讓小車在低速時(shí)“卡頓”控制量小于靜摩擦閾值時(shí)小車不動(dòng)大于閾值后又突然竄動(dòng)。在算出控制量后加上一個(gè)與運(yùn)動(dòng)方向相反的常量補(bǔ)償if (current_speed 0) u friction_comp; else if (current_speed 0) u - friction_comp;friction_comp 的取值靠實(shí)驗(yàn)觀察讓程序輸出一個(gè)固定 PWM然后從小逐漸增大記錄小車剛好開(kāi)始移動(dòng)的 PWM 值乘一個(gè) 0.8 的系數(shù)作為補(bǔ)償量。第三個(gè)技巧是使用串口上位機(jī)在線調(diào)參。工程里包含了上位機(jī)通信協(xié)議格式是“參數(shù)名數(shù)值”字符串控制中斷的快照通過(guò) DMA 發(fā)送到串口。我通常用匿名上位機(jī)展示角度曲線邊推擺桿邊觀察波形邊改參數(shù)比反復(fù)燒寫程序效率高得多。第四個(gè)技巧是增加一個(gè)“軟限位”保護(hù)邏輯。導(dǎo)軌兩端有機(jī)械限位開(kāi)關(guān)但倒立擺慣性大撞上去容易損壞機(jī)械結(jié)構(gòu)。程序里在位置環(huán)中加入了緩沖區(qū)域當(dāng)小車位置接近導(dǎo)軌末端時(shí)自動(dòng)把位置權(quán)重加大強(qiáng)制讓小車減速。驗(yàn)證方法我習(xí)慣分三步走第一步讓擺桿從 30 度偏角釋放記錄恢復(fù)時(shí)間好的參數(shù)應(yīng)該在 1 秒內(nèi)擺回豎直位置并且不出現(xiàn)大的過(guò)沖第二步在擺桿穩(wěn)定后用手撥動(dòng)觀察能在幾次擺動(dòng)內(nèi)恢復(fù)第三步在小車勻速運(yùn)動(dòng)過(guò)程中給擺桿一個(gè)階躍干擾看角度偏差最大值是否能控制在 3 度以內(nèi)。這套方法我現(xiàn)在每個(gè)新參數(shù)組必跑一遍。調(diào)倒立擺最忌沒(méi)有章法地亂試。從那以后我每次拿到新的控制程序都會(huì)強(qiáng)制走一遍零點(diǎn)校準(zhǔn)、控制周期驗(yàn)證、編碼器噪聲排查這三件事再碰任何參數(shù)。希望幫到你剩下的交給時(shí)間和耐心。本文還有配套的精品資源點(diǎn)擊獲取