2017年8月3日 星期四

NOP 的使用時機



菜鳥:『我有個問題,剛剛我們看了一些時序圖,控制訊號之間都有時間的關係,我想知道我們的程式如何精準地控制時間?我知道有些操作要求的timing很嚴格,舉例來說,假設CPU透過兩根PIN腳與某個IC連接,一根是CS(chip select),另一根是輸入PIN – P#1data sheet裡說明如何從IC取得資料的時序圖如下。其中規定當CPUCSHigh被拉到Low時,該IC會開始動作,經過T1的時間後,IC會把資料放在P#1(這裡所謂的“資料”不是High就是Low,或者說不是0就是1),維持的時間為T2。這種狀況下,我們程式如何在正確的時間裡到P#1上抓取正確的資料?』


韌體老鳥:『剛剛已經講解過,要把某根輸出PINHigh/Low變化,以及要從某根輸入PIN讀出狀態都可以透過CPU的暫存器,這個基本的控制應該沒問題吧。這個例子還算簡單的,從時序圖中我們可以看出程式該做的事情是:

a.       CS PIN設定為Low
b.      等待T1的時間
c.       立即從P#1讀出狀態

通常執行步驟c的時間不會長於T2,所以上述的步驟中省略了T2;假設T1 = 10m s,先不要管操作CPU PIN腳的細節,我們可以將上面的步驟寫成下面這個簡單的程式: 』

/**********************************************************
       Function: drv_read_XXXIC_status
  return 0 if P#1 is Low, return 1 if P#1 is High
**********************************************************/
int drv_read_XXXIC_status(void)
{
 int i ;
 int P1_status ;

 // CS PIN設為Low
 //
 drv_set_XXXIC_CS_Low() ;

 //等待10ms
 //
 for(i=0;i<10;i++)
      drv_wait_1ms() ;

 // 讀出P#1 PIN的狀態
 //
 P1_status = drv_get_P1_status() ;

 // 依照時序圖CS PIN恢復為High
 //
 drv_set_XXXIC_CS_High() ;

 return P1_status ;
}

菜鳥:『這個程式很簡單,但我的問題就是drv_wait_ 1m s()要怎麼implement?難道是用timer嗎?』

韌體老鳥:『用timer也是一個方法,但設定timer、執行timer ISR都需要時間執行吧,假設我們要等待的這個T1很短,使用timer會造成一定的誤差。而且要驅動timer也需要一個driver,在開發時期盡量不需要把一件工作加入太多的變數。我們在開發驅動程式時,最常用,也是最簡單的方法就是寫一個什麼事情都不做的迴圈,例如: 』

#define COUNTER_PER_1MS  22

void drv_wait_1ms(void)
{
 int counter ;

 // 執行這個function的期間最好確認中斷不會產生
 // 否則執行ISR的時間會影響這個函數的準確度

 for(counter = 0 ; counter < COUNTER_PER_1MS ; counter++)
 {
       // busy waiting - no nothing
       //
       asm("nop") ;
 }

}

菜鳥:『這個程式就是讓CPU執行指定次數之無意義的指令,而執行這些指令所需的時間剛好就是我們想要等待的時間。這個程式很簡單,但我有兩個問題,第一個,我們怎麼得到“COUNTER_PER_ 1M S”的值?第二個,“nop”這個CPU的指令代表什麼意思?』

韌體老鳥:『先回答你第二個問題,“nop”是no operation的意思,CPU執行這行指令時什麼都不作,就是很單純的耗掉一個或多個clock的時間;應該是所有的CPU都會提供這樣的指令,程式可以用這樣的指令讓CPU空轉一段時間,而且不會影響任何記憶體或一般暫存器的值(“nop”也是CPU的指令,CPU執行指令時,PC(program, counter)暫存器一定會跟著改變,CPU才知道要到哪裡抓取下一個命令來執行。)。在回答你第一個問題之前,你先看一下這張表,每一個CPU都會提供這樣的表格,它說明了CPU執行不同的指令需要多少個clock,你會算每一個clock的時間是多長嗎? 』

菜鳥:『你之前教過我啊,如果CPU運作的頻率是8Mhz(這個頻率和震盪器輸入的頻率不一定相同,CPU內部可以升頻或降頻),也就是說一秒鐘有8Mclock,所以一個clock的時間就是1秒除以(8 x 1024 x 1024),等於7.6 micro-second (7.6 x 10-6)。』

韌體老鳥:『理論上CPU執行指令的時間是很精確的,如果你把上面那個迴圈的程式轉為組合語言,你可以知道這個迴圈執行了哪些指令,每一個指令需要多少個clock,你就可以算出這個迴圈要轉幾次才會用掉你想要的時間。 』

菜鳥:『大概知道原理了,你可不可以舉個實例?』

韌體老鳥:『OK,假設那個迴圈對應的CPU指令為: 』

;
不同的CPU有不同的組合語言指令,
以下程式旨在表達流程並非任何CPU的組合語言語法,
;
       R0 = COUNTER_PER_1MS                 ; 2 clock cycles
loop_start:
       nop                                                          ; 1 clock cycles
       R0 = R0 -1                                              ; 1 clock cycles
       jump to "loop_start" if R0 0              ; 4 clock cycles

菜鳥:『所以一次loop會用掉6clock的時間,所以我們可以很容易算出R0的初值等於多少時,執行這個迴圈的時間剛好是1ms。』

韌體老鳥:『沒錯,就是這麼簡單。這個function的誤差很小,此後在開發其他驅動程式,甚至系統程式與應用程式時都可以使用。 』


ref: Link



沒有留言:

張貼留言