:ePWM脈沖與梯形加減速調(diào)度)
簡介面向電機控制與嵌入式開發(fā)者這套基于TMS320F28335 DSP與Xilinx XC3S500E FPGA的步進電機控制資料包圍繞實際電機驅(qū)動應用提供完整軟硬件實現(xiàn)。壓縮包內(nèi)含248個文件大小約25.13MB主要文件類型包括C語言驅(qū)動源碼、FPGA VHDL設計文件、原理圖PDF、UCF約束文件以及bit配置位流等覆蓋從軟件算法到硬件設計的多個層次。已有349人學習下載。源碼部分涉及PWM波形生成、方向與速度控制、微步進細分技術、DSP與FPGA間的SPI/UART通信及同步機制并可能包含PID等閉環(huán)控制策略原理圖部分則清晰展示了主控芯片與步進電機驅(qū)動器的連接方式、電源管理及過流保護電路。將源碼、原理圖和FPGA配置結(jié)合研讀可快速掌握DSPFPGA聯(lián)合控制步進電機的工程實現(xiàn)方法對學習現(xiàn)代精密電機控制技術具有較高參考價值。1. 為什么用 DSP28335 驅(qū)動步進電機寄存器直控與實時中斷的取舍步進電機控制本身不算難難的是在頻繁加減速時讓脈沖序列保持穩(wěn)定。DSP28335 的 ePWM 模塊可以直接從硬件生成連續(xù)方波周期中斷觸發(fā)固定不會像普通單片機那樣被主循環(huán)里的一次串口處理打斷。這個項目正是一套圍繞 F28335 展開的步進電機開環(huán)控制方案外設初始化、梯形加減速調(diào)度、方向與限位邏輯都拆成了清晰的小模塊。適合正在做運動控制原型、準備把 DSP28335 用進實際設備的開發(fā)者也適合想從寄存器層面理解電機控制的人。2. 硬件與外設分配ePWM 脈沖鏈路、方向 GPIO 與限位輸入的搭法2.1 控制鏈路從 DSP 引腳到電機軸一條完整的控制鏈路可以拆成三段來理解控制段負責生成脈沖與方向信號隔離段負責把兩個地隔開功率段把信號放大成繞組電流。GPIO0 復用為 EPWM1A 后引腳輸出的是 3.3V 的方波這個信號一般不要直接接到驅(qū)動器因為驅(qū)動器側(cè)往往用 24V 邏輯電源地也沒與控制板共地。常見做法是讓脈沖信號先通過光耦再進驅(qū)動器的 PUL 端子隔離的同時完成電平轉(zhuǎn)換。方向信號 DIR 和使能信號 ENA 也走同樣的光耦通道。實際項目里我一般會在脈沖線上串一個 470Ω 左右的限流電阻光耦內(nèi)部的 LED 正向壓降約 1.2V3.3V 減去壓降再除以 470Ω電流約 4.5mA已經(jīng)足夠TLP 類光耦可靠導通。驅(qū)動器那一側(cè)的上拉電阻也不能省否則光耦輸出端在進入導通與截止切換時沿不夠陡高速脈沖下驅(qū)動器會漏檢測。2.2 外設分工哪根引腳負責什么引腳分配時有一個容易忽略的原則脈沖輸出一定要選硬件外設引腳而不是普通 GPIO。GPIO 翻轉(zhuǎn)依賴軟件參與一旦主循環(huán)里出現(xiàn)延時或高優(yōu)先級中斷脈沖間隔就會抖動電機跟著發(fā)出不均勻的噪音。EPWM 輸出完全由硬件時序產(chǎn)生CPU 只需要在狀態(tài)變化時更新周期寄存器。功能引腳外設說明脈沖 PULGPIO0EPWM1A3.3V 方波經(jīng)光耦接驅(qū)動器 PUL方向 DIRGPIO1普通 GPIO高電平正轉(zhuǎn)低電平反轉(zhuǎn)使能 ENAGPIO2普通 GPIO低電平使能驅(qū)動器功率輸出原點限位GPIO12輸入 GPIO常開壓合時接地終點限位GPIO13輸入 GPIO常開壓合時接地GPIO0 到 GPIO31 屬于 GPIOA 組所以方向位操作要用 GPASET 和 GPACLEAR而不是 GPIOB 那組。初始化代碼如下// gpio_init.c —— 管腳復用方向配置 void GPIO_Init(void) { EALLOW; GpioMuxRegs.GPAMUX1.bit.GPIO0 1; // GPIO0 復用為 EPWM1A GpioMuxRegs.GPADIR.bit.GPIO1 1; // GPIO1 方向輸出 GpioMuxRegs.GPADIR.bit.GPIO2 1; // GPIO2 使能輸出 GpioMuxRegs.GPADIR.bit.GPIO12 0; // GPIO12 限位輸入 GpioMuxRegs.GPADIR.bit.GPIO13 0; // GPIO13 限位輸入 EDIS; }GPAMUX1 負責 GPIO0-GPIO15 的復用選擇寫 1 表示選擇外設功能而非普通 IO。GPADIR 對應方向寄存器寫 1 為輸出寫 0 為輸入。這里的 EALLOW/EDIS 是 F28335 保護寄存器機制修改復用和方向時必須先解除保護否則寫操作會被忽略這一點剛上手時很容易踩。2.3 驅(qū)動器接口電平與使能邏輯驅(qū)動器側(cè)的 ENA 使能引腳多數(shù)是低電平有效接低時功率級打開電機進入鎖定狀態(tài)接高或懸空時功率級關閉。上電前最好把 ENA 通過上拉電阻固定到無效電平避免 DSP 復位期間的 IO 狀態(tài)不確定導致電機在啟動瞬間跳一步。限位開關的接法建議用常開型碰到擋塊時開關閉合把 GPIO 拉到地程序里檢測低電平即可如果常閉斷線故障不會被發(fā)現(xiàn)可靠性差一截。3. 核心代碼拆解從 ePWM 波形到梯形加減速的完整鏈路3.1 ePWM 脈沖輸出初始化F28335 的 ePWM 模塊沒有繁瑣的庫封裝直接面對寄存器。時基計數(shù)器從 0 遞增到 TBPRD然后歸零重新開始遞增過程中到達 CMPA 時輸出動作一次歸零時再動作一次這樣就產(chǎn)生了一個周期的方波。這里的關鍵是預分頻選擇TBPRD 只有 16 位如果 TBCLK 直接使用 150MHz1kHz 脈沖需要的周期值是 150000超出上限所以必須分頻。// epwm.c —— 脈沖輸出初始化脈沖引腳 GPIO0/EPWM1A void EPwm1_Init(void) { EALLOW; GpioMuxRegs.GPAMUX1.bit.GPIO0 1; // GPIO0 復用為 EPWM1A EDIS; // TBCLK SYSCLKOUT / 32 4.6875MHz // 避免低頻場景下 TBPRD 超過 16 位上限65535 EPwm1Regs.TBCTL.bit.CLKDIV 5; // 5 對應 /32 EPwm1Regs.TBCTL.bit.HSPCLKDIV 0; // 高速時鐘不分頻 EPwm1Regs.TBCTL.bit.PRDLD 1; // TBPRD 在計數(shù)器歸零時更新 EPwm1Regs.TBCTL.bit.CTRMODE 0; // 遞增計數(shù)模式 // 初始脈沖頻率 1kHz周期值 4.6875MHz / 1000Hz EPwm1Regs.TBPRD 4687 - 1; // 占空比 50%CMPA 取周期一半 EPwm1Regs.CMPA.bit.CMPA (4687 - 1) / 2; // 動作配置遞增計數(shù)到 CMPA 時輸出高歸零時輸出低 EPwm1Regs.AQCTLA.bit.CAU 2; // CAU 事件置高 EPwm1Regs.AQCTLA.bit.PRD 1; // 周期事件置低 // 周期中斷每次 TBCTR 歸零觸發(fā)一次用于脈沖計數(shù) EPwm1Regs.ETSEL.bit.INTEN 1; // 使能 ePWM 中斷 EPwm1Regs.ETSEL.bit.INTSEL 1; // 選擇周期事件觸發(fā) EPwm1Regs.ETPS.bit.INTPRD 1; // 每個周期事件都觸發(fā) }CLKDIV 取 5 意味著 TBCLK 4.6875MHz。1kHz 對應的 TBPRD 是 468716kHz 對應的 TBPRD 只有 292。在 16kHz 附近TBPRD 每變化 1頻率大約變化 55Hz做加減速足夠用了。PRDLD 位必須設為 1它決定了 TBPRD 的更新時刻是“計數(shù)器歸零時”而不是“寫入時”這直接關系著加減速過程中會不會出現(xiàn)畸形脈沖。3.2 1ms 節(jié)拍與梯形加減速調(diào)度加減速本質(zhì)上是一個頻率調(diào)度問題每毫秒把當前頻率往目標頻率推進一步然后把新的頻率寫進 ePWM。CPU Timer0 很適合做這個 1ms 節(jié)拍配置如下// main.c —— 初始化 CPU Timer0 為 1ms 中斷 void CpuTimer0_Init(void) { CpuTimer0Regs.PRD.all (150000000UL / 1000) - 1; // 1ms 150MHz CpuTimer0Regs.TPR.all 0xFFFF; CpuTimer0Regs.TPRH.all 0xFFFF; CpuTimer0Regs.TCR.bit.TSS 1; // 先停止 CpuTimer0Regs.TCR.bit.TRB 1; // 重載周期值 CpuTimer0Regs.TCR.bit.TIE 1; // 使能中斷 }然后是運動調(diào)度的核心。狀態(tài)機包含加速、勻速、減速、停止四個狀態(tài)每個 1ms 節(jié)拍更新一次頻率// motion.c —— 運動狀態(tài)機由 1ms 定時器中斷調(diào)用 #define MIN_FREQ 1000u // 啟動/停止脈沖頻率 Hz #define MAX_FREQ 16000u // 最高脈沖頻率 Hz #define ACCEL_RATE 400u // 每毫秒頻率增量 Hz/ms #define DECEL_RATE 400u // 每毫秒頻率減量 Hz/ms void motion_tick(void) { if (running 0) return; switch (state) { case STATE_ACCEL: freq_hz ACCEL_RATE; if (freq_hz MAX_FREQ) { freq_hz MAX_FREQ; state STATE_RUN; } break; case STATE_RUN: // 剩余脈沖數(shù)低于減速閾值時切入減速 if ((target_pulses - pulse_cnt) decel_threshold) { state STATE_DECEL; } break; case STATE_DECEL: if (freq_hz MIN_FREQ) { freq_hz - DECEL_RATE; } if (freq_hz MIN_FREQ) { freq_hz MIN_FREQ; state STATE_DONE; } break; default: break; } pwm_set_freq(freq_hz); }pwm_set_freq 負責把頻率換算成寄存器值// epwm.c —— 動態(tài)修改脈沖頻率 void pwm_set_freq(uint32_t freq_hz) { uint32_t period; if (freq_hz 0) { EPwm1Regs.TBCTL.bit.FREESOFT 2; // 立即停止輸出高電平 return; } // TBCLK 4.6875MHz四舍五入求周期 period (4687500UL freq_hz / 2) / freq_hz; EPwm1Regs.TBPRD period - 1; EPwm1Regs.CMPA.bit.CMPA (period - 1) / 2; // 同步更新占空比 }注意到減速閾值不能拍腦袋定它會決定減速段是否夠長。我的習慣是運行前按梯形面積估算從 MAX_FREQ 降到 MIN_FREQ 需要時間(MAX_FREQ - MIN_FREQ) / DECEL_RATE毫秒減速期間平均頻率約為(MAX_FREQ MIN_FREQ) / 2兩者相乘就是減速段預計發(fā)出的脈沖數(shù)。代入本項目參數(shù)減速大約需要 37.5ms發(fā)出約 318 個脈沖所以 decel_threshold 可以取 350 留一點余量。3.3 位置計數(shù)與到位判斷加減速調(diào)度是“宏觀”層面的脈沖計數(shù)則是“微觀”層面的。到位判斷如果放在 1ms 節(jié)拍里做誤差可能接近 1ms 內(nèi)發(fā)出的所有脈沖數(shù)在高速段相當于上百個脈沖的位置誤差。把計數(shù)放進 ePWM 周期中斷每個脈沖都精確記一次誤差最多一個脈沖周期。// epwm_isr —— 每個脈沖觸發(fā)一次 __interrupt void epwm1_isr(void) { pulse_cnt; if (pulse_cnt target_pulses) { pwm_set_freq(0); // 停止脈沖 state STATE_DONE; running 0; } EPwm1Regs.ETCLR.bit.INT 1; // 清除中斷標志 }pulse_cnt 與 target_pulses 都聲明為 volatile uint32_t因為它們在中斷和主循環(huán)之間共享。F28335 的 32 位讀寫是單指令完成的不需要臨界區(qū)保護如果以后改成 64 位變量就必須考慮先關中斷再讀寫。EPWM 中斷還要在 PIE 里使能中斷向量掛到 PieVectTable.EPWM1_INTPIEIER3 的 INTx1 置 1最后 IER | M_INT3。3.4 方向與限位邏輯方向控制放在運動啟動之前設置不要在運行途中切換否則電機在高速下突然反向會直接失步甚至堵轉(zhuǎn)。限位檢測放到主循環(huán)里輪詢即可不需要中斷因為限位是機械觸點輪詢周期 1ms 足夠快// motion.c —— 方向設置與限位讀取 #define DIR_POS 1 #define DIR_NEG 0 void motion_set_dir(uint16_t dir) { if (dir DIR_POS) { GpioDataRegs.GPASET.bit.GPIO1 1; // 正轉(zhuǎn) } else { GpioDataRegs.GPACLEAR.bit.GPIO1 1; // 反轉(zhuǎn) } } uint16_t motion_check_limit(void) { // 低電平表示限位開關被壓合 if (GpioDataRegs.GPADAT.bit.GPIO12 0) return 1; if (GpioDataRegs.GPADAT.bit.GPIO13 0) return 1; return 0; }檢測到限位后建議立即調(diào)用 pwm_set_freq(0) 并讓 running 置 0這是硬停止邏輯不能依賴后面的軟件狀態(tài)機慢慢降速。原點回歸邏輯可以在此基礎上加先向負方向找原點限位觸發(fā)后清零 pulse_cnt再正轉(zhuǎn)脫離限位一段距離即可。4. 工程落地CCS 建工程、CMD 內(nèi)存分配與示波器驗證4.1 CCS 工程結(jié)構與文件職責DSP28335 工程并不復雜但文件歸屬一定要清楚。最怕把所有代碼堆進 main.c外設初始化、運動邏輯、中斷服務混在一起后面想改一個加減速參數(shù)都要找半天。推薦按模塊拆文件文件職責main.c初始化順序與主循環(huán)輪詢gpio.c / gpio.h引腳復用與方向配置epwm.c / epwm.hePWM 初始化、頻率修改、脈沖計數(shù)中斷motion.c / motion.h加減速狀態(tài)機、方向、限位判斷DSP2833x_CpuTimers.c官方定時器驅(qū)動28335_RAM_lnk.cmdRAM 調(diào)試鏈接腳本DSP2833x_Headers_nonBIOS.cmd外設寄存器地址映射這些人文件在 CCS 工程里的關系是CMD 文件決定代碼和數(shù)據(jù)放在哪段內(nèi)存頭文件 CMD 決定外設寄存器地址能否被訪問。兩套地址映射同時生效前者管程序運行空間后者管外設訪問空間。4.2 CMD 內(nèi)存分配RAM 調(diào)試與 Flash 固化的差異RAM 調(diào)試版 CMD 把代碼段全部放進內(nèi)部 RAM程序通過仿真器直接加載運行斷電即失。適合開發(fā)前期反復改代碼的階段。Flash 版本需要把可執(zhí)行代碼寫進 Flash同時把運行時可變的段搬進 RAM啟動流程復雜得多。以下是一個簡化的 RAM 調(diào)試 CMD 片段MEMORY { PAGE 0: RAML0 : origin 0x008000, length 0x001000 PAGE 1: RAML1 : origin 0x009000, length 0x001000 } SECTIONS { .text : RAML0, PAGE 0 .cinit : RAML0, PAGE 0 .bss : RAML1, PAGE 1 .stack : RAML1, PAGE 1 }段名是編譯器規(guī)定的.text 是代碼.cinit 是全局變量初始值.bss 是未初始化全局變量.stack 是棧。如果某個段沒有被分配鏈接時直接報錯很容易定位。Flash 固化版要復雜很多還要處理 .codestart 段和 boot 模式引腳我的建議是前期一直用 RAM 調(diào)試功能全部驗證通過后再碰 Flash。4.3 示波器驗證從 1kHz 到 16kHz 的加減速觀察沒有示波器調(diào)步進電機基本等于盲調(diào)至少需要一臺能測到幾十 kHz 的雙通道示波器。驗證順序從靜態(tài)到動態(tài)單步進入調(diào)試器把 EPwm1Regs.TBPRD 加入變量監(jiān)視窗口手動改一次周期值看波形頻率有沒有立刻變化。確認寄存器寫入路徑正確后再跑完整的加減速流程。示波器探頭接 EPWM1A地線夾到控制板地。把目標位置設得足夠長讓電機完整經(jīng)歷加速-勻速-減速-停止觀察脈沖包絡是否呈現(xiàn)對稱的梯形如果減速段比加速段短很多說明 decel_threshold 估算偏小。另外測一下停止瞬間的波形應當立刻變平不能有殘留脈沖。5. 踩坑記錄脈沖毛刺、失步與下載跑飛的原因排查5.1 電機嗡嗡響但不轉(zhuǎn)方向信號被當脈沖了現(xiàn)象是上電后電機鎖定發(fā)出尖銳的嗡鳴聲軸在來回抖動始終沒有連續(xù)旋轉(zhuǎn)。示波器看脈沖引腳波形正常方向引腳也正常。原因排查到最后是光耦輸出側(cè)方向線和脈沖線在驅(qū)動器端子處接反了。驅(qū)動器把方向信號上的電平跳變誤認為脈沖電機在每個“脈沖”到來時改變一次轉(zhuǎn)向表現(xiàn)為原地高頻換向。這個坑在實物接線階段特別容易發(fā)生因為脈沖和方向在杜邦線上長得一模一樣。解決方法是把驅(qū)動器的 PUL 與 DIR 接線對調(diào)。為了減少這類問題我在后來的項目里統(tǒng)一用不同顏色線纜區(qū)分脈沖與方向脈沖用橙白、方向用藍白上電前拿萬用表先確認兩個端子的對地電平關系。5.2 加速過程中電機咔咔響速度越高越容易丟步現(xiàn)象是低速運行完全正常頻率超過 10kHz 之后電機發(fā)出明顯的失步噪音繼續(xù)加速就直接堵轉(zhuǎn)停住。原因通常是兩個一是 ACCEL_RATE 太大電機轉(zhuǎn)矩跟不上頻率爬升斜率二是驅(qū)動器的細分數(shù)設得過高比如 8 細分下 16kHz 脈沖對應每秒 10 圈已經(jīng)超過該電機在該負載下的失步力矩范圍。很多人只調(diào)軟件里的加速度忽略驅(qū)動器細分與最高頻率的關系最后在硬件參數(shù)上翻車。解決方法是先把 MAX_FREQ 降到 8kHz 跑通整套流程確認加減速曲線沒問題后再以 2kHz 為單位往上試探。同時適當加大驅(qū)動器的電流設定步進電機在高速段的輸出力矩會明顯下降提高供電電壓比調(diào)整電流更有效。5.3 加減速過程中示波器看到畸形脈沖電機一頓一頓現(xiàn)象是電機整體運行速度曲線看起來正常但示波器上偶爾出現(xiàn)一個特別窄或者特別寬的脈沖電機聲音也不均勻。根因是 TBPRD 的更新時機不對。默認配置下寫入 TBPRD 可能立即生效如果寫入發(fā)生在計數(shù)器計到一半的時候當前周期就被強行截斷或拉長形成一個寬度異常的脈沖。這個異常脈沖會讓驅(qū)動器在錯誤的時間點換相輕則頓挫重則失步。解決方法是把 PRDLD 設為 1強制周期值在計數(shù)器歸零時更新中斷里再去修改頻率時新的周期值只會從下一個完整周期開始生效。這個細節(jié)做運動控制時必須養(yǎng)成習慣凡是周期值、比較值可能在運行中被修改的 PWM 模塊都先看周期加載模式。5.4 仿真器跑得好好的燒進 Flash 就不運行現(xiàn)象是 RAM 調(diào)試一切正常換成 Flash 版本的 CMD 后燒錄成功但重新上電沒有任何反應程序沒有跑起來。原因在啟動流程F28335 從 Flash 啟動需要 boot 引腳電平配合同時 Flash 等待狀態(tài)寄存器沒有初始化會導致取指異常。另外中斷向量表默認在 RAM上電時如果不把向量表初始化好任何一個中斷觸發(fā)都會讓程序跑飛。解決方法是先確認 boot 模式引腳下拉到正確組合在 main 開頭先調(diào)用初始化 Flash 等待狀態(tài)的函數(shù)再把 PIE 向量表重新裝載一遍。我的習慣是 Flash 版本專門保留一個空白工程做對比每次只改 CMD 和必要的初始化代碼不混入業(yè)務邏輯。5.5 上電瞬間電機跳一步現(xiàn)象是每次按下電源開關電機都會“咯噔”響一聲軸偏轉(zhuǎn)一個角度。原因是 DSP 復位階段 GPIO 輸出高阻光耦輸入側(cè)懸空驅(qū)動器把噪聲當成了脈沖或者使能引腳 ENA 在上電瞬間處于無效電平功率級沒開但脈沖已經(jīng)先進來等使能一開就執(zhí)行了這一步。解決方法是硬件上在 DIR 和 ENA 引腳加下拉電阻讓復位期間電平固定在已知狀態(tài)軟件上初始化 GPIO 后先延時 200ms再打開使能信號。這個延時給了電源和外部電路一個穩(wěn)定時間之后才允許脈沖輸出。6. 二次開發(fā)思路加編碼器閉環(huán)、S 曲線與串口協(xié)議開環(huán)梯形加減速能滿足大部分點位運動需求但需要做位置精度補償或速度平穩(wěn)性優(yōu)化時有三條明確的升級路徑。第一是位置閉環(huán)。F28335 自帶 EQEP 模塊把編碼器 A/B 相接入 EQEP1 引腳初始化正交解碼后直接讀位置計數(shù)器。在運行中周期性對比目標位置與實際位置用比例系數(shù)把位置誤差映射到運行頻率誤差大就提高頻率誤差小就降頻。這個改進能消除丟步積累的位置偏差前提是誤差修正的速度遠低于加減速調(diào)度頻率通常在 1ms 節(jié)拍里做一次就很合適。第二是 S 形速度曲線。梯形加減速在加速段起點和終點存在加速度突變機械結(jié)構高速時會感受到明顯沖擊。把線性加頻率改成正弦插值加速進度從 0 到 1頻率增量按 sin(progress × 90°) 縮放讓加速度從零開始逐漸增加到峰值再回落到零。F28335 有浮點單元計算 sin 的開銷很小每毫秒算一次完全夠用。第三是串口指令協(xié)議。定義一個極簡幀格式幀頭 0xAA、命令字、4 字節(jié)目標脈沖數(shù)、末尾異或校驗。把運動控制封裝成執(zhí)行單一指令的函數(shù)收到目標后自動完成加減速和到位停止。這樣后續(xù)接上位機、觸摸屏或者視覺模塊都只需要維護協(xié)議解析部分核心運動邏輯不用動。那次我在一套送檢設備上試過直接加大加速度縮短節(jié)拍時間結(jié)果電機在共振區(qū)間反復失步后來強制自己每次修改加減速參數(shù)都先在 1kHz 低頻、短距離工況下完整跑一遍確認包絡正常后再放滿載。從那以后我改任何運動控制參數(shù)都會先出低頻測試不直接上高速驗證。希望幫到你。本文還有配套的精品資源點擊獲取