回覆列表
  • 1 # 曉川說

    結合按鍵程式,我們設計這樣一個功能程式:按數字鍵 1~9,控制電機轉過 1~9 圈;配合上下鍵改變轉動方向,按向上鍵後正向轉 1~9 圈,向下鍵則反向轉 1~9 圈;左鍵固定正轉 90 度,右鍵固定反轉 90;Esc 鍵終止轉動。透過這個程式,我們也可以進一步體會到如何用按鍵來控制程式完成複雜的功能,以及控制和執行模組之間如何協調工作,而你的程式設計水平也可以在這樣的實踐練習中得到鍛鍊和提升。

    #include

    sbit KEY_IN_1 = P2^4;

    sbit KEY_IN_2 = P2^5;

    sbit KEY_IN_3 = P2^6;

    sbit KEY_IN_4 = P2^7;

    sbit KEY_OUT_1 = P2^3;

    sbit KEY_OUT_2 = P2^2;

    sbit KEY_OUT_3 = P2^1;

    sbit KEY_OUT_4 = P2^0;

    unsigned char code KeyCodeMap[4][4] = { //矩陣按鍵編號到標準鍵盤鍵碼的對映表

    { 0x31, 0x32, 0x33, 0x26 }, //數字鍵 1、數字鍵 2、數字鍵 3、向上鍵

    { 0x34, 0x35, 0x36, 0x25 }, //數字鍵 4、數字鍵 5、數字鍵 6、向左鍵

    { 0x37, 0x38, 0x39, 0x28 }, //數字鍵 7、數字鍵 8、數字鍵 9、向下鍵

    { 0x30, 0x1B, 0x0D, 0x27 } //數字鍵 0、ESC 鍵、 回車鍵、 向右鍵

    };

    unsigned char KeySta[4][4] = { //全部矩陣按鍵的當前狀態

    {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}

    };

    signed long beats = 0; //電機轉動節拍總數

    void KeyDriver();

    void main(){

    EA = 1; //使能總中斷

    TMOD = 0x01; //設定 T0 為模式 1

    TH0 = 0xFC; //為 T0 賦初值 0xFC67,定時 1ms

    TL0 = 0x67;

    ET0 = 1; //使能 T0 中斷

    TR0 = 1; //啟動 T0

    while (1){

    KeyDriver(); //呼叫按鍵驅動函式

    }

    }

    /* 步進電機啟動函式,angle-需轉過的角度 */

    void StartMotor(signed long angle){

    //在計算前關閉中斷,完成後再開啟,以避免中斷打斷計算過程而造成錯誤

    EA = 0;

    beats = (angle * 4076) / 360; //實測為 4076 拍轉動一圈

    EA = 1;

    }

    /* 步進電機停止函式 */

    void StopMotor(){

    EA = 0;

    beats = 0;

    EA = 1;

    }

    /* 按鍵動作函式,根據鍵碼執行相應的操作,keycode-按鍵鍵碼 */

    void KeyAction(unsigned char keycode){

    static bit dirMotor = 0; //電機轉動方向

    //控制電機轉動 1-9 圈

    if ((keycode>=0x30) && (keycode<=0x39)){

    if (dirMotor == 0){

    StartMotor(360*(keycode-0x30));

    }else{

    StartMotor(-360*(keycode-0x30));

    }

    }else if (keycode == 0x26){ //向上鍵,控制轉動方向為正轉

    dirMotor = 0;

    }else if (keycode == 0x28){ //向下鍵,控制轉動方向為反轉

    dirMotor = 1;

    }else if (keycode == 0x25){ //向左鍵,固定正轉 90 度

    StartMotor(90);

    }else if (keycode == 0x27){ //向右鍵,固定反轉 90 度

    StartMotor(-90);

    }else if (keycode == 0x1B){ //Esc 鍵,停止轉動

    StopMotor();

    }

    }

    /* 按鍵驅動函式,檢測按鍵動作,排程相應動作函式,需在主迴圈中呼叫 */

    void KeyDriver(){

    unsigned char i, j;

    static unsigned char backup[4][4] = { //按鍵值備份,儲存前一次的值

    {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}

    };

    for (i=0; i<4; i++){ //迴圈檢測 4*4 的矩陣按鍵

    for (j=0; j<4; j++){

    if (backup[i][j] != KeySta[i][j]){ //檢測按鍵動作

    if (backup[i][j] != 0){ //按鍵按下時執行動作

    KeyAction(KeyCodeMap[i][j]); //呼叫按鍵動作函式

    }

    backup[i][j] = KeySta[i][j]; //重新整理前一次的備份值

    }

    }

    }

    }

    /* 按鍵掃描函式,需在定時中斷中呼叫,推薦呼叫間隔 1ms */

    void KeyScan(){

    unsigned char i;

    static unsigned char keyout = 0; //矩陣按鍵掃描輸出索引

    static unsigned char keybuf[4][4] = { //矩陣按鍵掃描緩衝區

    {0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF},

    {0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF}

    };

    //將一行的 4 個按鍵值移入緩衝區

    keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;

    keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;

    keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;

    keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;

    //消抖後更新按鍵狀態

    for (i=0; i<4; i++){ //每行 4 個按鍵,所以迴圈 4 次

    if ((keybuf[keyout][i] & 0x0F) == 0x00){

    //連續 4 次掃描值為 0,即 4*4ms 內都是按下狀態時,可認為按鍵已穩定的按下

    KeySta[keyout][i] = 0;

    }else if ((keybuf[keyout][i] & 0x0F) == 0x0F){

    //連續 4 次掃描值為 1,即 4*4ms 內都是彈起狀態時,可認為按鍵已穩定的彈起

    KeySta[keyout][i] = 1;

    }

    }

    //執行下一次的掃描輸出

    keyout++; //輸出索引遞增

    keyout = keyout & 0x03; //索引值加到 4 即歸零

    //根據索引,釋放當前輸出引腳,拉低下次的輸出引腳

    switch (keyout){

    case 0: KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;

    case 1: KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;

    case 2: KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;

    case 3: KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;

    default: break;

    }

    }

    /* 電機轉動控制函式 */

    void TurnMotor(){

    unsigned char tmp; //臨時變數

    static unsigned char index = 0; //節拍輸出索引

    unsigned char code BeatCode[8] = { //步進電機節拍對應的 IO 控制程式碼

    0xE, 0xC, 0xD, 0x9, 0xB, 0x3, 0x7, 0x6

    };

    if (beats != 0){ //節拍數不為 0 則產生一個驅動節拍

    if (beats > 0){ //節拍數大於 0 時正轉

    index++; //正轉時節拍輸出索引遞增

    index = index & 0x07; //用&操作實現到 8 歸零

    beats--; //正轉時節拍計數遞減

    }else{ //節拍數小於 0 時反轉

    index--; //反轉時節拍輸出索引遞減

    index = index & 0x07; //用&操作同樣可以實現到-1 時歸 7

    beats++; //反轉時節拍計數遞增

    }

    tmp = P1; //用 tmp 把 P1 口當前值暫存

    tmp = tmp & 0xF0; //用&操作清零低 4 位

    tmp = tmp | BeatCode[index]; //用|操作把節拍程式碼寫到低 4 位

    P1 = tmp; //把低 4 位的節拍程式碼和高 4 位的原值送回 P1

    }else{ //節拍數為 0 則關閉電機所有的相

    P1 = P1 | 0x0F;

    }

    }

    /* T0 中斷服務函式,用於按鍵掃描與電機轉動控制 */

    void InterruptTimer0() interrupt 1{

    static bit p = 0;

    TH0 = 0xFC; //重新載入初值

    TL0 = 0x67;

    KeyScan(); //執行按鍵掃描

    //用一個靜態 bit 變數實現二分頻,即 2ms 定時,用於控制電機

    p = ~p;

    if (p == 1){

    TurnMotor();

    }

    }針對電機要完成正轉和反轉兩個不同的操作,我們並沒有使用正轉啟動函式和反轉啟動函式這麼兩個函式來完成,也沒有在啟動函式定義的時候增加一個形式引數來指明其方向。我們這裡的啟動函式 void StartMotor(signed long angle)與單向正轉時的啟動函式唯一的區別就是把形式引數 angle 的型別從 unsigned long 改為了 signed long,我們用有符號數固有的正負特性來區分正轉與反轉,正數表示正轉 angle 度,負數就表示反轉 angle 度,這樣處理是不是很簡潔又很明瞭呢?而你對有符號數和無符號數的區別用法是不是也更有體會了?

    針對終止電機轉動的操作,我們定義了一個單獨的 StopMotor 函式來完成,儘管這個函式非常簡單,儘管它也只在 Esc 按鍵分支內被呼叫了,但我們仍然把它單獨提出來作為了一個函式。而這種做法就是基於這樣一條程式設計原則:儘可能用單獨的函式來完成硬體的某種操作,當一個硬體包含多個操作時,把這些操作函式組織在一起,形成一個對上層的統一介面。這樣的層次化處理,會使得整個程式條理清晰,既有利於程式的除錯維護,又有利於功能的擴充。

    中斷函式中要處理按鍵掃描和電機驅動兩件事情,而為了避免中斷函式過於複雜,我們就又分出了按鍵掃描和電機驅動兩個函式(這也同樣符合上述 2 的程式設計原則),而中斷函式的邏輯就變得簡潔而清晰了。這裡還有個矛盾,就是按鍵掃描我們選擇的定時時間是 1ms,而本章之前的例項中電機節拍持續時間都是 2ms;很顯然,用 1ms 的定時可以定出 2ms 的間隔,而用 2ms 的定時卻得不到準確的 1ms 間隔;所以我們的做法就是,定時器依然定時 1ms,然後用一個 bit 變數做標誌,每 1ms 改變一次它的值,而我們只選擇值為 1 的時候執行一次動作,這樣就是 2ms 的間隔了;如果我要 3ms、4ms„„呢,把 bit 改為 char 或 int 型,然後對它們遞增,判斷到哪個值該歸零,就可以了

  • 中秋節和大豐收的關聯?
  • 俗話說“十賭九輸”,從數學上講是不對的,但存在即合理,那“十賭九輸”的合理性在哪?