一個脈寬調變(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 的文章
6 comments:
Hi, Yukuan:
最近比較有空看了一下你這篇文章,有些
問題想請教你:
list 1.
Pause(PERIOD - INNATE_DELAY - pulse_delay);
這是什麼意思呢?
還有 list 3. 我找不到 PWM pull low
的地方,有點怪怪的
我作過PWM LED與Servo,其實兩者
的 PWM 不太一樣,PWM LED頻率
比較高(顏色變化時才自然),而且
RGB三個PWM period要幾乎同步。
但是servo相對duty cycle很短period卻
很長,所以可以在period裡偷塞一些
其他servo的PWM,當然把PWM LED的
作法拿來用也不是不行,但是有個問題
是更新duty cycle最好是在週期開始之前,用偷塞的方式有個好處就是可以
用剩下的時間更新下一次的duty cycle
一般servo的max duty cycle
2.1ms
period: 20ms
20ms / 2.1ms = 9.5...
取 9 --> 2.1ms * 9 = 18.9ms
20ms - 18.9ms = 1.1ms
於是我們可以利用這 1.1ms
作一些計算或是讀取table
更新 duty cycle
To Godspeed,
首先
Pause(PERIOD-INNATE_DELAY-pulse_delay)
是指 busy waiting 時間。
PERIOD 是 PWM 的週期;
pulse_delay 可以看成是同時運作的那幾個 PWM 的脈衝(duty cycle)的加總;
INNATE_DELAY 指的是固有的時間延遲,像是 CPU 指令運算等需要消耗的時間所造成的延遲等。
上述作法,你應該比我還熟,只是大家的用語,及程式切割方式等不大一樣罷了。
再來,
list3 的 PWM pull low?
你是指 PWM[i] = LOW 這行嗎?
不是很確定你指的是那一部份。
無論如何,這只是虛擬碼,難免有缺省,只要意思到了就可以了 :p
另外,
就如你所說的,兩者的 PWM 不大一樣,除了頻率外,它們的 duty cycle 佔整個週期比例也不同。
PWM 調 LED 亮度或調色等應用,主要考量其實是人們視覺暫留的時間長度,這個值大概在 1/16 到 1/10 秒左右。
真高興有人看我寫的東西 :)
今天又重看了一次你的虛擬碼,終於
看懂了(可能最近FPPA寫太多變笨)
你的list1應該就同等於FPPA中的作法
,只不過我們是用2顆CPU去做,不過
看來用你的作法應該1顆也辦得到
(因為最近終於對servo的特性夠清楚
了)
此外我弄錯了,其實我指的是list5
(不過這不重要了)
還有你這句:
"去除動作冗餘來換取較少的 interrupt instruction cycles"
是什麼意思呢?(list2 & list3看不出
有多大分別)
感覺起來list5比較適合給有RTOS
的場合使用。
另外假如你是要給servo用,其實list1中Pause是不太必要的,因為servo要求的是duty精準但是period卻很寬鬆,
通常可以接受到16ms-20ms,甚至我還
看過8ms-26ms,也就是說只要PWM組數
夠多根本不用pause(拿這來做點其他
的事,比方說從FIFO讀取控制命令
不是更好?)
另外PWM LED 1/10秒是不夠的,假如
我沒記錯的話起碼要到200Hz才不會
閃爍(變換顏色時),因為連GAME都至少
要有80fps的水準,而且假如不夠快,
下面這個應用就做不出來了
http://www.youtube.com/watch?v=Nx6meWamnIE
這個應用是用MIC對聲音取樣再
轉換成LED的顏色,人聲的頻率就
不只1/10s了。
我是指 list2 裡面重複把 GPIO 設成 LOW ,照程式的邏輯是不必要的。因為設成 LOW 後,你不去改它,理論上不會自己變成 HIGH;但在一些罕見的意外下,很難說這種見鬼的事絕對不會發生 :p
LED PWM 要調多快,還有 duty cycle 要多長,就如你說的,我沒特別堅持,這原本就要實際調調看最準 :)
就如你所說的 List1 的 Pause 在 servo 組數夠多時,當然不必要。這裡只是把它一般化,避免一些特例。畢竟這是虛擬碼,不是實際要拿來用的 :)
後來查一下資料,
人眼不同的感光細胞,對光線有不同的反應,以視覺暫留來說,主要有 1/25 sec 及 1/4 sec 兩大類。
且眼睛要接受到一定強度的光線,才感測得到,所以 PWM 的 duty cycle 也不可以太小。
會逛到這裡的,應該都是機器人 DIY 同好吧,我在 diigo 那成立了 Robotics 版,並把相關資訊整理一份到那,希望能為大家省些時間,達到拋磚引玉的效果 :)
那兒分成
Robotics Bookmarks 及
Robotics Forum
兩大塊。
Post a Comment