Showing posts with label C. Show all posts
Showing posts with label C. Show all posts

Sunday, January 17, 2016

以 EnumLookup 查詢 C enumerator

前陣子我把 AWK 撿回來練習,順便研究一下 Windows 下怎麼跑 GAWK 跟 AWKA 之類的工具。這期間,我想了幾個簡單的題目來練習。其中有個我稱作 EnumLookup 的工具,一些慣 C 的人應該會用到,所以在這裡做個分享。

顧名思義,這個工具的目的是針對 enum ,它可以在多個 C enumerations 上查詢 enumerator 或它們對應的值。這裡是安裝跟執行說明文件,大家可以根據上面的指引,直接下載內含執行檔的下載包來試用。

這個工具有個搭配的 enum.bat ,執行後可以雙向查詢:可反覆敲進數字來查對應的名字(enumerator),或者敲入名字(enumerator)來查對應的索引數字。

需要注意的是,這個工具沒有真的去實作完整的 C enum parser ,只是認「通常」情況下的 enum 特徵。下面這種寫成一行的寫法,無法正確執行:

typedef enum {SPRING, SUMMER, AUTUMN, WINTER} Season;

因為這工具不認得寫成一行的寫法,只認得下面這種,比較正規的,分行寫法:

typedef enum {
    SEASON_BEGIN,
    SPRING = SEASON_BEGIN, 
    SUMMER, 
    AUTUMN, 
    WINTER,
    SEASON_END,
    SEASON_TOTALS = SEASON_END // the total number of seasons
} Season;


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: [] [] [] [] []

Sunday, November 05, 2006

Console I/O Without OS

在〈Debugging Embedded Systems〉中曾建議:把 uC 的 UART 跟 PC 的 serial port 第一時間 link 起來,然後就可以在適當的地方 print out 一些訊息,幫助我們確認程式的執行狀況。

這個廣為採用的作法,在沒有 preemptive multitasking OS 支援下,很容易因 print out 的訊息太多,使系統 delay 過久。這在許多場合是無法接受的。一個常見的例子是同時處理另一個網路連線下,許多 protocol 都嚴格限制裝置回應時間。

解法也很簡單,只需把要 print out 的東西丟到 queue 中,然後再拆成一個個 char ,找時間分批餵給 PC 就好了。這叫「化整為零」:P

Renesas H8 系列 HEW 開發環境底層就有兩個分別被 stdin/stdout function 群共用, 處理單一 char 的 function : charput() 被 printf() 及 puts() 等調用;charget() 被 scanf() 及 gets() 等調用。

註:用於 MSC51 最著名的 Keil C 環境底層則分別調用 putchar() 和 _getkey() 。

我們只要自行定義 charput() 就可以解決前述發生在 print out 的 delay 問題。方法如下圖右半:

修改後的 console output (圖右半)讓我們可以無縫使用 printf() 及 puts() 等 functions ,不用改變對 ANSI C standard library 的使用習慣。

console input 部份(圖左半),雖然我也想盡量維持在 PC 下 C 程式的使用習慣,但還是難逃 Joel 提出的〈抽象滲漏法則〉,不但無法適用於 scanf() ,用於 gets() 時還要自行處理換行符號。

註:抽象滲漏法則--All non-trivial abstractions, to some degree, are leaky.

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

Saturday, May 20, 2006

An Array Implementation of Queue

Embedded System 程式開發, queue 是很常用的資料結構: UART 在接收及傳輸資料時,通常各需要一個 character queue;在處理 keypad 的按鍵輸入時需要一個 key queue ;task 間的溝通,也可能要用到某種 event queue 。

除了提供一個這樣的 queue 的實作外,在〈A Unit Testing Toy〉中曾經提出的 ToyUnit ,也剛好藉這次,展示其實際的應用:

Architecture

這麼簡單的程式,我們只要分成下列三個 source files 就可以了:

  1. Queue.h -> interface
  2. Queue.c -> implementation
  3. Queue_test.c -> unit test

Writing Test Code First

在真正實作 queue 時,我們可以先利用先前提過的 ToyUnit 來為它寫 test code 。

Queue_test.c 程式片段如下:

 8: #include "Queue.h"
 9: #include "ToyUnit.h"
10: 
11: enum {
12:     BUF_SIZE= 3
13: };
14: 
15: char buf[BUF_SIZE];
16: 
17: int main()
18: {
19:     Queue q;
20: 
21:     Q_init( &q, buf, BUF_SIZE );
22:     TU_ASSERT("03", Q_empty(&q));
23:     TU_ASSERT("04", !Q_full(&q));
24:     TU_ASSERT("05", Q_size(&q) == 0);
25: 
26:     Q_put(&q, 'a');
27:     TU_ASSERT("12", Q_first(&q)=='a');
28:     TU_ASSERT("13", !Q_empty(&q));
29:     TU_ASSERT("14", !Q_full(&q));
30:     TU_ASSERT("15", Q_size(&q) == 1);
31:     TU_ASSERT("16", Q_last(&q)=='a');
32: 
33:     Q_put(&q, 'b');
34:     TU_ASSERT("22", Q_first(&q)=='a');
35:     TU_ASSERT("23", !Q_empty(&q));
36:     TU_ASSERT("24", !Q_full(&q));
37:     TU_ASSERT("25", Q_size(&q) == 2);
38:     TU_ASSERT("26", Q_last(&q)=='b');
39: 
40:     Q_put(&q, 'c');
41:     TU_ASSERT("32", Q_first(&q)=='a');
42:     TU_ASSERT("33", !Q_empty(&q));
43:     TU_ASSERT("34", Q_full(&q));
44:     TU_ASSERT("35", Q_size(&q) == 3);
45:     TU_ASSERT("36", Q_last(&q)=='c');
46: 
47:     TU_ASSERT("41", Q_get(&q)=='a');
48:     TU_ASSERT("42", Q_first(&q)=='b');
49:     TU_ASSERT("43", !Q_empty(&q));
50:     TU_ASSERT("44", !Q_full(&q));
51:     TU_ASSERT("45", Q_size(&q) == 2);
52:     TU_ASSERT("46", Q_last(&q)=='c');
53: 
54:     TU_ASSERT("51", Q_get(&q)=='b');
55:     TU_ASSERT("52", Q_first(&q)=='c');
56:     TU_ASSERT("53", !Q_empty(&q));
57:     TU_ASSERT("54", !Q_full(&q));
58:     TU_ASSERT("55", Q_size(&q) == 1);
59:     TU_ASSERT("56", Q_last(&q)=='c');
60: 
61:     TU_ASSERT("61", Q_get(&q)=='c');
62:     TU_ASSERT("63", Q_empty(&q));
63:     TU_ASSERT("64", !Q_full(&q));
64:     TU_ASSERT("65", Q_size(&q) == 0);
65: 
66:     Q_put(&q, 'd');
67:     TU_ASSERT("72", Q_first(&q)=='d');
68:     TU_ASSERT("73", !Q_empty(&q));
69:     TU_ASSERT("74", !Q_full(&q));
70:     TU_ASSERT("75", Q_size(&q) == 1);
71:     TU_ASSERT("76", Q_last(&q)=='d');
72: 
73:     TU_ASSERT("81", Q_get(&q)=='d');
74:     TU_ASSERT("83", Q_empty(&q));
75:     TU_ASSERT("84", !Q_full(&q));
76:     TU_ASSERT("85", Q_size(&q) == 0);
77: 
78:     TU_ASSERT("91", Q_unget(&q)=='d');
79:     TU_ASSERT("92", Q_first(&q)=='d');
80:     TU_ASSERT("93", !Q_empty(&q));
81:     TU_ASSERT("94", !Q_full(&q));
82:     TU_ASSERT("95", Q_size(&q) == 1);
83:     TU_ASSERT("96", Q_last(&q)=='d');
84: 
85:     Q_clear(&q);
86:     TU_ASSERT("103", Q_empty(&q));
87:     TU_ASSERT("104", !Q_full(&q));
88:     TU_ASSERT("105", Q_size(&q) == 0);
89: 
90:     TU_RESULT();
91: 
92:     return 0;
93: }

算一算,這裡總共寫了 52 個測試。

Compiling -> 一大堆錯誤 -> failure

當然啦,因為根本還沒真正開始寫這個 queue :p

為了記憶體的使用是可預期的,這個 queue 不使用動態記憶體分配,改而讓 client 在 initialize queue 時自己提供 buffer,通常這個 buffer 是一個靜態的 array。

Extracts the Interface

根據先前那個無法編譯過關的 unit test code ,我們已經知道怎麼使用這個 queue 了,從中可以很直覺地抽出 queue 的介面。

Queue.h 程式片段如下:

12: #include <stdlib.h>
13: #include "platform.h"
14: 
15: 
16: typedef char QueueItem; ///< the item type in a queue
17: 
18: typedef struct {
19:     Index first;    ///< index of the first (front) item of a queue.
20:     Index end;      ///< index of the end (last+1) of a queue.
21:     size_t count;   ///< the number of items in a queue.
22:     QueueItem* buf; ///< a pointer that indicates the buffer of a queue.
23:     size_t buf_size;///< buffer size.
24: } Queue;
25: 
26: 
27: void Q_init( Queue* q, QueueItem* buf, size_t buf_size );
28: void Q_clear( Queue* );
29: 
30: void Q_put( Queue*, QueueItem );
31: QueueItem Q_get( Queue* );
32: 
33: QueueItem Q_unget( Queue* );
34: 
35: QueueItem Q_first( const Queue* );
36: QueueItem Q_last( const Queue* );
37: 
38: size_t Q_size( const Queue* );
39: bool Q_empty( const Queue* );
40: bool Q_full( const Queue* );

在一連串的往返,敲定介面的過程中,Compiling 終於過了。

但是 Linking 時又出錯,應該不令人意外吧。 :)

Done the Implementation

現在就來為 queue 補上血肉。如果實作沒出錯,期望看到的 unit test 執行結果如下:

./Queue_test
....................................................
Tests [Pass-Fail]: [52-0]

每 pass 一個 test ,console 畫面上就會打一個點,否則會印出 fail 的訊息。

畫面上有 52 個點,看起來乾乾淨淨、清清爽爽,真讓人覺得愉快!

TU_RESULT(.) 會為我們秀出簡單的測試總結:這個例子顯示,共有 52 個 tests , pass 52 個, fail 0 個。

這裡再次顯現這類 UnitTest framework 的設計哲學:

  1. 讓電腦幫我們比對「預期的執行結果」及「實際的執行結果」間是否一致。
  2. 如果測試沒出錯,就不要秀一堆有的沒的東西,擾亂視覺。
  3. 執行測試變得簡單、直覺,就會鼓勵我們多測試,這是良性的循環。

Queue.c 完整的實作如下:

  1: /**
  2:  * @file queue.c
  3:  *      Implementes a queue module, and that uses a circular array.
  4:  * @author Jiang Yu-Kuan
  5:  * @date 2006/05/07 (initial version)
  6:  * @date 2006/05/18 (last revision)
  7:  * @version 2.0
  8:  */
  9: #include "Queue.h"
 10: #include <assert.h>
 11: 
 12: 
 13: /** Initilizes a queue.
 14:  * @param[in,out] q the queue to be initialized.
 15:  * @param[in] buf the buffer to store items.
 16:  * @param[in] buf_size the buffer size.
 17:  */
 18: void Q_init( Queue* q, QueueItem* buf, size_t buf_size )
 19: {
 20:     q->first= 0;
 21:     q->end= 0;
 22:     q->count= 0;
 23:     q->buf= buf;
 24:     q->buf_size= buf_size;
 25: }
 26: 
 27: 
 28: /** Clear the queue */
 29: void Q_clear( Queue* q )
 30: {
 31:     q->first= 0;
 32:     q->end= 0;
 33:     q->count= 0;
 34: }
 35: 
 36: 
 37: /** Puts an item to the end of a queue.
 38:  * @param[in,out] q the queue to add an item.
 39:  * @param[in] i the added item.
 40:  */
 41: void Q_put( Queue* q, QueueItem i )
 42: {
 43:     assert (!Q_full(q));
 44: 
 45:     ++q->count;
 46:     q->buf[q->end]= i;
 47:     q->end= (q->end+1) % q->buf_size;
 48: }
 49: 
 50: 
 51: /** Gets the first item of a queue.
 52:  * @param[in,out] q the queue to get an item.
 53:  * @return the gotton item
 54:  */
 55: QueueItem Q_get( Queue* q )
 56: {
 57:     QueueItem i;
 58:     assert (!Q_empty(q));
 59: 
 60:     --q->count;
 61:     i= q->buf[q->first];
 62:     q->first= (q->first+1) % q->buf_size;
 63:     return i;
 64: }
 65: 
 66: 
 67: /** Rolls back a "get" operation.
 68:  * This cannot work correctly next to 'clear' operation.
 69:  * @return the previous character
 70:  */
 71: QueueItem Q_unget( Queue* q )
 72: {
 73:     ++q->count;
 74:     if (q->first == 0)
 75:         q->first= q->buf_size - 1;
 76:     else
 77:         --q->first;
 78:     return q->buf[q->first];
 79: }
 80: 
 81: 
 82: /** Peeks the first item of a queue.
 83:  * @param[in] q the queue to get an item.
 84:  * @return the peeked item
 85:  */
 86: QueueItem Q_first( const Queue* q )
 87: {
 88:     assert (!Q_empty(q));
 89: 
 90:     return q->buf[q->first];
 91: }
 92: 
 93: 
 94: /** Peeks the last item of a queue.
 95:  * @param[in] q the queue to get an item.
 96:  * @return the peeked item
 97:  */
 98: QueueItem Q_last( const Queue* q )
 99: {
100:     Index i;
101:     assert (!Q_empty(q));
102: 
103:     if (q->end == 0)
104:         i= q->buf_size - 1;
105:     else
106:         i= q->end - 1;
107:     return q->buf[i];
108: }
109: 
110: 
111: /** Gets the size of a queue at the moment. */
112: size_t Q_size( const Queue* q )
113: {
114:     return q->count;
115: }
116: 
117: 
118: /** Determines if a queue is empty. */
119: bool Q_empty( const Queue* q )
120: {
121:     return q->count == 0;
122: }
123: 
124: 
125: /** Determines if an queue is full */
126: bool Q_full( const Queue* q )
127: {
128:     return q->count == q->buf_size;
129: }

Afterword

前面三個步驟,通常我並不會很明確地去區隔它們,而是多次往返、反覆,確保這三個部份能維持同步。

實際上,我習慣先把最基本的 interface 及 implementation 完成,再寫對應的 unit test,然後一個一個擴充介面、單元測試、及實作。

Unit test 主要目的有:

  1. 最基本的,它是強大的 bug 偵測器,我們要確保程式能通過測試,以證明它目前為止,還沒出錯。
  2. 日後如果還發生 bug ,我們就為它補上新的 test 來捕抓這個新的 bug 。
  3. 如果 module 有作任何修改,例如新增介面,改變演算法,調整結構等,我們寫的 unit test code 更可以發揮它的作用,讓我們在修改時無後顧之憂,因為 unit test 會捕捉這個過程所衍生的錯誤。
  4. 很重要的一點,unit test 還可以用來幫助我們確認模組的介面。

Sunday, April 02, 2006

Enjoy the Fine Code

在〈初等概念〉一文, Jserv 說道:

在我看來,C Programming Language 本來就是用來寫作業系統,所以相關的基礎知識也要有一定的理解,至少也需要對硬體有認知,這樣才有機會徹底理解 C 語言,而 C++ 就更不說了,不僅 ISO C++ spec 複雜難懂,又得遷就於 C++ compiler 許多不合標準但陳窠已久的特性,又得將其 OOPL 的理念貫徹於專案開發,這使得沒有對特定 domain knowledge 有足夠掌握者,難以透過 C++ 將其發揮得淋漓盡致。

由〈初等概念〉文末附的 links,我尋線逛到 Dan SaksEmbedded.com 發表的專欄,並發現許多精緻優雅的 C 程式片段,堪稱為程式中的小品文,值得好好駐足欣賞:

在〈Enumerations Q & A〉中,我看到 enumeration 的精彩用法:

1: enum currency
2:   {
3:   currency_before = -1,
4:   CAD, CHR, EUR, GBP, JPY, SFR, USD,
5:   currency_after,
6:   currency_min = currency_before+1,
7:   currency_max = currency_after-1,
8:   };

在〈More ways to map memory〉欣賞到漂亮的 special_register 使用方式:

 1: typedef unsigned int volatile special_register;
 2: 
 3: typedef struct dual_timers dual_timers;
 4: struct dual_timers
 5:     {
 6:     special_register TMOD;
 7:     special_register TDATA0;
 8:     special_register TDATA1;
 9:     special_register TCNT0;
10:     special_register TCNT1;
11:     };
12: 
13: enum { TE0 = 0x01, TE1 = 0x08 };
14: 
15: #define timers ((dual_timers *)0x03FF6000)
16: 
17: int main()
18:     {
19:     timers->TMOD &= ~(TE0 | TE1);
20:     timers->TDATA0 = 50000;
21:     return 0;
22:     }

在〈Place volatile accurately〉,意外發現 shadow register 的用法:

1: typedef uint32_t shadow_register;
2: typedef shadow_register volatile
3:     special_register;

...

此外,Jserv 在〈C99 的 offsetof macro〉引介的〈Learn a new trick with the offsetof() macro〉也很讓人激賞。

Tags: [] [] []