Saturday, December 16, 2006

Motor-controlling PWMs

一個脈寬調變(Pulse-width Modulation, PWM)訊號可控制一顆 DC motor 轉速,或決定一具 servomotor 的方向、位置或轉速。在複雜的機器人身上,常用上好幾顆馬達,因而能以一顆微控制器(microcontroller, uC)產生多組 PWM 訊號是非常實用的。

前陣子在 RobotFun.net 論壇看到一群機器人愛好者討論自製串列伺服控制器(serial servo controller, SSC)的討論。後來又在 CSZone 的 Robotics 版跟 happosaiMasterChang 討論了「以 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 不夠一般化:
    1. 它要求每個 PWM 的 period 都要一樣。
      • 這在通常的應用,每俱 servo 都一樣時,問題不大。
    2. 當 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 期間處理。

建議閱讀

Tags: [] [] [] [] []

6 comments:

Godspeed Lee said...

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

York said...

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 秒左右。


真高興有人看我寫的東西 :)

Godspeed Lee said...

今天又重看了一次你的虛擬碼,終於
看懂了(可能最近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了。

York said...

我是指 list2 裡面重複把 GPIO 設成 LOW ,照程式的邏輯是不必要的。因為設成 LOW 後,你不去改它,理論上不會自己變成 HIGH;但在一些罕見的意外下,很難說這種見鬼的事絕對不會發生 :p

LED PWM 要調多快,還有 duty cycle 要多長,就如你說的,我沒特別堅持,這原本就要實際調調看最準 :)

就如你所說的 List1 的 Pause 在 servo 組數夠多時,當然不必要。這裡只是把它一般化,避免一些特例。畢竟這是虛擬碼,不是實際要拿來用的 :)

York said...

後來查一下資料,

人眼不同的感光細胞,對光線有不同的反應,以視覺暫留來說,主要有 1/25 sec 及 1/4 sec 兩大類。

且眼睛要接受到一定強度的光線,才感測得到,所以 PWM 的 duty cycle 也不可以太小。

York said...

會逛到這裡的,應該都是機器人 DIY 同好吧,我在 diigo 那成立了 Robotics 版,並把相關資訊整理一份到那,希望能為大家省些時間,達到拋磚引玉的效果 :)

那兒分成
Robotics Bookmarks
Robotics Forum
兩大塊。