利用 printf 來算出 delay 1ms 的函數

Posted by: 邱小新 at 下午4:38 in , ,
一般來說,要取得 delay_1ms 的函數有幾種方法。
  1. 利用 timer 中斷來取得。
  2. 利用 while loop 來取得,但是要算出 while loop 的數值,通常使用示波器來輔助求得。

使用第一項方式會浪費掉一個 timer,使用第二個方法又很麻煩(因為不會用示波器)。所以我就想利用 printf 加上 timer 來算出 while loop 的數值。程式如下:

#define CNT 100 void delay_1ms(int msec) { int i; while(msec--) { i = CNT; while(i--); } } void main(void) { long time, cnt; uart_init(); while (1) { TMOD = 0x01; TH0 = 0; TL0 = 0; TR0 = 1; delay_1ms(1); TR0 = 0; time = (TH0*256L + TL0) * 12L / 40L; cnt = 1000l * (long)CNT / time; printf("\n\rtime0 = %d,%d,%ld,%ld\n\r", TH0, TL0, time, cnt); getchar(); } }

計算過程

  1. 首先 delay_1ms 中的 i 值先給 100,燒進去執行。
  2. 取得 TH0, TL0,利用 timer 公式 (TH0*256+TL0)*12/Fosc,計算出 100 次要花多少 t 時間。
  3. 再利用公式 t/i0 = 1ms/i1,i1=1ms*i0/t,取得下一次要填入的 i1 值。
  4. 再次重覆計算,直到 i 值不變或 t>1ms 為止。

範例:W79L632A@40MHz

  1. TH0=2, TL0=75, t=176.1 us, i1=567.8591 取 568。
  2. TH0=12, TL0=168, t=972 us, i1=584.3621 取 584。
  3. TH0=13, TL0=3, t=999.3 us, i1=584.4090 取 585。
  4. TH0=13, TL0=8, t=1000.8 us, i 值設定成 585
  5. 同樣的硬體,同樣的 source code,使用 keil C51,計算出來的值卻是 711,由此可見 keil C51 跑得比較快??
  6. 最近又從別人的 code 發現,其實在 i=CNT 前加入 nop 指令可以用來增加準確度到 1us 喔,當然 nop 數量的多寡要經由測試才能得知,有興趣的人自己試看看吧。

測試誤差

void delay_1ms(int msec) { int i; while(msec--) { i = 585; while(i--); } } void main(void) { long time; int cnt=20; uart_init(); while (cnt--) { TMOD = 0x01; TH0 = 0; TL0 = 0; TR0 = 1; delay_1ms(cnt); TR0 = 0; time = (TH0*256L + TL0) * 12L / 40L; printf("\n\rtime0 = %d,%d,%ld,%d", TH0, TL0, time, cnt); } }

Keil-C 怪怪的問題

最近玩 mstar 的晶片,改用 Keil C 來測這個程式,結果發現一個怪異現象,就是每次 printf 出來的 TH0 都是 16bit 的數值,而 TL0 卻總是 0,time,cnt 算出的值也不對,真是莫明奇怪。後來發現把 TH0, TL0 放入 int 變數就正常了,如果放入 char 變數也是不對。由此證明 keil 在 char 變數的四則運算有點問題喔。

#define CNT 100 void delay_1ms(int msec) { int i; while(msec--) { i = CNT; while(i--); } } void main(void) { long time, cnt; int a, b; uart_init(); while (1) { TMOD = 0x01; TH0 = 0; TL0 = 0; TR0 = 1; delay_1ms(1); TR0 = 0; a=TH0; b=TL0; time = (a*256L + b) * 12L / 40L; cnt = 1000l * (long)CNT / time; printf("\n\rtime0 = %d,%d,%ld,%ld\n\r", a, b, time, cnt); getchar(); } }

3 意見

感謝大大的demo code, 今天突然想到這樣簡單的delay function到底該怎寫才是最有效率, 時間誤差最小的寫法.上網搜尋到大大的文章.
我是用 ATMEL ATTiny85 + WinAVR compiler,測試大大說得while loop, 以及NOP 的有無, 另外最後再測試for loop迴圈做1ms這幾種寫法.
1. 基本上都是先測試調整到各種寫法能夠delay 1ms 時,誤差小於10us.
2. 測試100ms, 以及1000ms 時的誤差.
3. Code Size比較
NOTE: 測試時, compiler不做optimize.

結果:
1. 當使用while loop時, delay 1ms 都可以很準確, for loop也是. (所謂的準確定義在誤差小於10us, 因使用內部RC震盪很難在更精準)

2. 當delay 100ms(msec=100)時, 使用while loop平均都會只有92ms左右, 但是for loop約為94ms

3. 當delay 1000ms(msec=1000)時, 使用while loop平均都會只有920ms左右, 但是for loop約為940ms

4. code size 使用while loop 做跟for loop一樣的事情會多約26byte(有無NOP)

結論:
不知道是否我的測試方式有問題, 或是compiler不同造成的差異, 感覺for 的指令速度更快, 誤差率更小. 甚至code size也是. 不過看來以WinAVR來講還是用for loop 比較適用的感覺. 不過還是謝謝大大的文章, 讓我受益很多.

code:
#if 1
void Delay_1ms(int msec)
{
int i;
while(msec--) {
_NOP(); //nop有無對誤差相差不大
i = 32;
while(i--);

}
}
#else
void Delay_1ms(int msec)
{
int i;
char j;

for(i = 0; i < msec; i++)
for(j = 0; j < 82; j++)
_NOP();
}
#endif

compiler 的差異其實蠻大的,我用 KeilC 及 SDCC 在同一個板子上所得出的 CNT 數值就有很大的差異。所以要用那種方法,只有自己實驗出來最適合囉。

>> 由此證明 keil 在 char 變數的四則運算有點問題喔。
沒有問題,
1. 請改用 unsign char 運算才是正確的.
2. 就像要印出 long 變數要 %ld 一樣, printf 時請改用 %bd 來印出 char 變數

張貼留言