一個脈寬調變(Pulse-width Modulation, PWM)訊號可控制一顆 DC motor 轉速,或決定一具 servomotor 的方向、位置或轉速。在複雜的機器人身上,常用上好幾顆馬達,因而能以一顆微控制器(microcontroller, uC)產生多組 PWM 訊號是非常實用的。
前陣子在 RobotFun.net 論壇看到一群機器人愛好者討論自製串列伺服控制器(serial servo controller, SSC)的討論。後來又在 CSZone 的 Robotics 版跟 happosai 及 MasterChang 討論了「以 uC 產生多組 PWM 訊號」的方法。這次就對這個議題作個整理:
Busy Waiting
我們先來看個最直接的作法:
1: // List 1. PWM loop busy-waiting version (for servo)
2:
3: int main()
4: {
5: // Here put initial code
6: ...
7:
8: for (;;) {
9: for (i=0; i<NUM_OF_PWM; ++i)
10: Pulse(&PWM[i], DutyCycle[i]); // precision in us
11:
12: // calculates the delay of all pulses above
13: pulse_delay = 0;
14: for (i=0; i<NUM_OF_PWM; ++i)
15: pulse_delay += DutyCycle[i];
16:
17: Pause(PERIOD - INNATE_DELAY - pulse_delay); // precision in ms will do
18:
19: // Do other thing here
20: ...
21: }
22: }
- 上面虛擬碼的作法,限制條件是:
- PERIOD ≥ INNATE_DELAY + pulse_delay
- INNATE_DELAY 要視核心頻率(Fcore)及 code 最佳化情形來給定。
- pulse_delay 則嚴重限制了此法能控制的 PWM 組數。
- 這個方法可造出非常精準的 pulse width ,但產生的 PWM 不夠一般化:
- 它要求每個 PWM 的 period 都要一樣。
- 這在通常的應用,每俱 servo 都一樣時,問題不大。
- 當 duty cycle 太大時,可控制的 PWM 組數減少。
- 對控制 servo 動作的 PWM 而言,相對於 period ,其 pulse width 都不大,所以還可以接受。
- 因此,此法較適用於控制 servos 。
Time Slicing
為了改進上述缺點,接下來就看看運用 timer interrupts 將時間切片的作法:
1: // List 2. PWM Timer time-slicing version
2:
3: void interrupt TimerISR_for_PWM1()
4: {
5: ...
6:
7: for (i=0; i<NUM_OF_PWM; ++i) {
8: if (cycle[i] < DutyCycle[i])
9: PWM[i] = HI;
10: else
11: PWM[i] = LOW;
12:
13: ++cycle[i];
14: cycle[i] %= Period[i];
15: }
16: }
- 這個方法的限制條件為:
- TICK_INTERVAL ≥ INNATE_DELAY
- 這裡的 INNATE_DELAY 指的是 interrupt instruction cycles 佔的時間
- INNATE_DELAY 也由核心頻率(Fcore)及 code 最佳化情形決定。
- TICK_INTERVAL 是 timer ticks 的間隔時間;減短這個間隔,可造出更精準的 PWM 。
- duty cycle 最小可為 0 ,最大可佔滿整個 PWM period;
- 除了用於 servo 外,也非常適於 LED 亮度控制, DC motor 轉速控制等。
- 只要設好 tick interval,注意要大於 interrupt instructions 執行的時間即可。
- 相較於 List 1 中 busy waiting 的作法,此法較方便在不同 uC 間移植。
- 此外,此法有動作冗餘(redundance),較 robust 。
我們可經由去除動作冗餘來換取較少的 interrupt instruction cycles:
1: // List 3. Another PWM Timer time-slicing version
2:
3: void interrupt TimerISR_for_PWM2()
4: {
5: ...
6:
7: for (i=0; i<NUM_OF_PWM; ++i) {
8: if (cycle[i]==0 && DutyCycle[i]>0)
9: PWM[i] = HI;
10: else if (cycle[i] == DutyCycle[i])
11: PWM[i] = LOW;
12:
13: ++cycle[i];
14: cycle[i] %= Period[i];
15: }
16: }
- 此法比 List 2 作法佔用更少的 instruction cycles ,所以可控制更多 PWMs 。
- 把 for loop 展開的話,程式雖變得累贅,但可進一步減少 instruction cycles 。
- 容許的 instruction cycles 受限於 timer ticks 間隔可執行的 instruction 數。
顯然,有個副程式把 List 2 或 List 3 中的 duty cycles 清為 0 是很方便的:
1: // List 4. Initializes PWMs
2:
3: void ResetPWM()
4: {
5: for (i=0; i<NUM_OF_PWM; ++i)
6: //PWM[i] = LOW;
7: cycle[i] = 0;
8: }
The Combination
List 2 或 List 3 的作法雖能產生夠一般化的 duty cycles 了,在核心頻率(Fcore)不太大提昇下,想造出高解析(resolution)的 pulse 時,還是得回到 List 1 那種純粹、直接的 loop busy-waiting。
以下就來看看綜合兩者優點(和缺點)的作法:
1: // List 5. The combination: time-slicing with busy-wating
2:
3: void interrupt TimerISR_for_PWMPeriod()
4: {
5: ...
6:
7: for (i=0; i<NUM_OF_PWM; ++i) {
8: if (cycle[i] == 0)
9: bPulseNow[i] = true;
10:
11: ++cycle[i];
12: cycle[i] %= Period[i];
13: }
14: }
15:
16:
17: int main()
18: {
19: // Here put initial code
20: ...
21:
22: for (;;) {
23: // for PWM pulse part
24: for (i=0; i<NUM_OF_PWM; ++i) {
25: if (bPulseNow[i]) {
26: Pulse(&PWM[i], DutyCycle[i]); // precision in us
27: bPulseNow[i] = false;
28: }
29: }
30:
31: // Do other thing here
32: ...
33: }
34: }
- 這個綜合法一方面以 interrupt time-slicing 決定 PWM 週期,另一方面又以 loop busy-waiting 產生高解析的 duty cycles 。
- 它綜合兩者的優點和缺點,非常適用在 servos 的高解析控制場合。
結論
- 組數不多的 servos 控制訊號,可用 List 1 的 busy wating 產生。
- 純粹的 interrupt time-slicing 較適用在 DC motors, LED flashing and brightness 及不需高解析控制 servos 的場合。
- 在要控制盡量多的 servos 且又要求高解析的 pulses 時,也許可以試一下綜合法(List 5)。
補充說明
- 為了有精準的 duty cycle ,產生 pulse 期間,不可讓其他中斷插進來。
- 其他工作,例如 SSC 的 command 可在非 pulse 期間處理。
建議閱讀
- 關於以 uC 產生 PWM
- "Programming Robot Controllers" by Myke Predko
- "PIC Robotics" by John Iovine
- 機器蟲 hexapod 的零件 -- 串列伺服機控制板自製達成
- 16 channel serial servo controller for robotic applications
- FPPA -- 伺服馬達應用篇---- 簡單的機械人
- 關於以 555 IC 產生 PWM 控制 DC motor 轉速
- 其他以類比或數位電路產生 PWM 的文章