專注電子技術學習與研究
當前位置:單片機教程網 >> MCU設計實例 >> 瀏覽文章

AVR單片機學習(五)按鍵與數(shù)碼管的程序設計

作者:zww 1988   來源:本站原創(chuàng)   點擊數(shù):  更新時間:2014年04月18日   【字體:
按鍵與數(shù)碼管的程序設計
  • AVR  IO口的輸入模式與上拉電阻
  • 選擇結構語句與按鍵的查詢方式程序設計
  • 數(shù)碼管基本原理
  • 掃描方式顯示多位數(shù)碼管
  • 一、輸入狀態(tài)IO寄存器設置

      1、DDRx 某一位置0,相應位的IO口被設置為輸入

       2、PORTx某一位置1,使能對應IO口相應位的上拉電阻

      3、PINx的對應位是輸入的數(shù)據(jù),0或1

     
    選擇結構語句

    一、關系運算符和關系表達式

    小于< 小于等于<= 大于> 大于或等于== 不等于!=

    二、邏輯運算符和邏輯表達式

    邏輯與&&邏輯或||邏輯非!

    三、if 語句結構

    if(表達式1)語句1

    else if(表達式2)語句2

    else 語句3

    四、switch 語句結構

    switch(表達式)

    {case 常量1:表達式1

    case 常量2:表達式2

    .........

    default:表達式n}


    按鍵的查詢方式程序設計

    一、PIND & (1<<6)

    二、1<<6

    1、1左移6位,即:0b01000000

    怎么判斷一個按鍵按下了呢?首先看下圖是4個按鍵

     



     






    第一個是PD2 上一段接VCC  其他都是一段接IO(PD3  PD6 PD7)口另一端接地線。

    所以當按鍵閉合時候相應IO都輸入一個0,當按鍵抬起來的時候IO輸入多少呢?

    所以這些IO口必須將上拉電阻進行使能,將按鍵打開相當于輸入一個1.所以我們判斷這3個按鍵按沒按下去的話,就判斷輸入是不是0就行了。

    對于第一個按鍵如果按下輸入是1,當抬起來時候由于AVR內部不帶下拉電阻的,所以按鍵打開時候輸入是0.

    所以就需要判斷某一位是0,還是1.某一位是0還是1就用到了& 與運算了。 1 跟1 與就1  1 與0 就是0

    上面代碼(temp& (1<<6))  (temp & 0b01000000) temp本身值不變,只是結果來判斷某一位是0還是1

    比如:PD6 上的K3    因為PD6   所以 PIND &(1<<6)的結果就行了 

    三、PIND & (1<<6)

    1、移除第6位之外其他位清零

    2、第6位保持輸入的值

    四、與選擇結構語句的結合

    1、判斷PIND & (1<<6)的值,執(zhí)行相應代碼

     

     除非你上電之前一直講按鍵按下,否則上電的一瞬間程序就執(zhí)行到while(1);了所以要將他們加入到死循環(huán)里面如下圖


    這樣就實現(xiàn)了按鍵的不停的檢測。 其實DDRD 上電默認都是0  所以清0  置為輸入也沒有意義。

    程序在if判斷設置斷點然后全速執(zhí)行可以看到只要沒有按鍵按下程序進不去斷點,如果我們在板子上按下K3則如下圖所示進入斷點,再按下單步執(zhí)行蜂鳴器響了如圖。



    所以這個程序就達到了我們的目的。

    現(xiàn)在換一種判斷 就是按鍵被按下而不是沒被按下 用邏輯非


    這樣達到了預想的目標。但是這樣只能判斷一個按鍵如果多個按鍵怎么辦呢?2種辦法 

    一、采用if elseif else

    if (){

        elseif{

        }

        elseif {

        }

        else{

     

        }

    }

    二、采用

    switch (表達式){

    case  相符合的條件  {

        break;

    }

    case 相符合的條件 {

        break;

    }

    default{

     

    }

    }

    一、用if 實現(xiàn)

    #include
    int main(void){
     //PD6 設置為輸入  K3
     DDRD &= ~(1 << 6);
     //輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
     PORTD |= (1<<6);
     //PD7 設置為輸入  K4
     DDRD &= ~(1 << 7); 
     //輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
     PORTD |= (1<<7);
     //PD2 設置為輸入  K1
     DDRD &= ~(1 << 2); 
     //輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
     PORTD |= (1<<2);//這個上拉不上拉沒關系因為上拉是百K的電阻所以開關打開還是認為是低電平
     //PD3 設置為輸入  K2
     DDRD &= ~(1 << 3); 
     //輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
     PORTD |= (1<<3);

     //蜂鳴器PA3 設置方向寄存器為輸出
     DDRA |= (1<<3);
     //蜂鳴器關掉
     PORTA &= ~(1<<3);
     //流水燈端口全部設為輸出
     DDRB = 0xff;
      while(1){
      //判斷PIND 這位是否為1 為真的話就是按鍵沒有按下
      if (!(PIND & (1<<6))){ //本來沒有按下 進入 現(xiàn)在變成了沒有按下 不進入了取非了  被按下進入了 PD6
       //按鍵被按下用蜂鳴器表示一下 PA3
        PORTA |= (1<<3);
      }
      else if(!(PIND & (1<<7))){ //PD7  K4按下讓流水燈產生動作   必須上面使能K4上拉電阻
       PORTB |= (1<<0);//第一個燈發(fā)光 就是等于1
      }
      else if (PIND & (1<<2)){//因為按下的時候是低電平接的是電源
       //第二個燈發(fā)光
       PORTB |= (1<<1);
      }
      else if (!(PIND & (1<<3))){
       //第三個燈發(fā)光
       PORTB |= (1<<2);
      }
      else{
       //變成了按鍵沒有按下 
         PORTA &= ~(1<<3); //蜂鳴器
       PORTB = 0;//燈
      }
      //看到沒有按下一直響的,按下就不響了。
     }
    }

    ---------------------------------------------------------------

    二、用switch 來實現(xiàn)就需要一次性將這四位讀回來。 代碼如下

    #include
    int main(void){
     //PD6 設置為輸入  K3
     DDRD &= ~(1 << 6);
     //輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
     PORTD |= (1<<6);
     //PD7 設置為輸入  K4
     DDRD &= ~(1 << 7); 
     //輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
     PORTD |= (1<<7);
     //PD2 設置為輸入  K1
     DDRD &= ~(1 << 2); 
     //輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
     PORTD |= (1<<2);//這個上拉不上拉沒關系因為上拉是百K的電阻所以開關打開還是認為是低電平
     //PD3 設置為輸入  K2
     DDRD &= ~(1 << 3); 
     //輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
     PORTD |= (1<<3);
     //蜂鳴器PA3 設置方向寄存器為輸出
     DDRA |= (1<<3);
     //蜂鳴器關掉
     PORTA &= ~(1<<3);
     //流水燈端口全部設為輸出
     DDRB = 0xff;
      while(1){
       //首先一次性將這4個位都讀回來   2 3 6 7 腳
       switch(PIND & 0b11001100) {
        case 0b11001100: {//只有第一個按鍵按下 0b11001100  接的電源按下是1
         //LED 0 發(fā)光
         PORTB |= (1<<0);
         break;
        }
        case 0b11000000: {//只有第 二個按鍵按下 0b11001100  接的電源抬起是0
         //LED 0 發(fā)光
         PORTB |= (1<<1);
         break;
        }
        case 0b10001100: {//只有第三個按鍵按下 0b11001100  接的電源抬起是0
         //LED 0
         PORTB |= (1<<2);
         break;
        }
        case 0b01001100: {//只有第四個按鍵按下 0b11001100  接的電源抬起是0
         //LED 0 發(fā)光
         PORTB |= (1<<3);
         break;
        }
        default :{   //都沒有按下 0b11001000  因為有下拉
         //變成了按鍵沒有按下 
           PORTA &= ~(1<<3); //蜂鳴器
           PORTB = 0;//燈

           break;
        }
       }
      
       //判斷PIND 這位是否為1 為真的話就是按鍵沒有按下
      
     }
    }

    -------------------------------------------------------------------------

    以上都是查詢方式因為都是在while循環(huán)一邊一邊的查詢,按鍵有動作就執(zhí)行相應的代碼這樣很耽誤CPU的時間的,在下一篇博客我會稍微降講用中斷的方式來編寫按鍵的程序。下面繼續(xù)說呵呵、

    八段數(shù)碼管 

    一、八段數(shù)碼管

    1、八段數(shù)碼管由八段LED構成

    2、各LED陰極或陽極并在一起,稱為“位選線”:共陰、共陽

    3、其余8個引腳各自引出,稱為“段選線”,各段可以分別控制




    記住一般一位的數(shù)碼管有10個腳

    個人理解:( 其中2腳是連在一起的是公共端。其他8個是段選  比如1、6接電源 其他接IO口個人理解的)

    多位合一的數(shù)碼管

    一、多位合一的數(shù)碼管

    1、將多個八段數(shù)碼管的段選線分別并在一起,位選線引出如下圖



    由上圖看出是4位 應該是8個段選線(7段加一個點)  4個位選線  共12根線 

    com0 ---- com3 是位選 

    a-g 加 dp   是段選。

    多位數(shù)碼管的使用

    1、多位數(shù)碼管的各個位均可以單獨顯示不同的數(shù)據(jù),但一個時刻只能點亮一位、(點快點人眼看不出來)

    2、依次點亮多位數(shù)碼管中的各個位,由于人眼的視覺暫留效應,看起來是同時點亮

    3、如下圖是電路圖 硬件電路是下圖設計的

     




    它的每一段相當于一個發(fā)光二極管,電流大約是10個mA左右(5--10)mA,因此段選可以直接用單片機的IO驅動是足夠的不論是拉電流還是灌電流,這里面我們用的是一個共陰極的數(shù)碼管,因此應該是向外拉電流,而段選線我們可以計算下段選線上最大電流時多少?假設每段都點亮沒段是10mA的話,那么位選線上也就是10*8 = 80mA 所以我們不能用IO口,一般的單片機不可能輸出這么大的電流,所以我采用一個三極管來進行驅動,共陰極的數(shù)碼管一般要用NPN型的數(shù)碼管,它的接法如下圖的樣子。


    再來張清楚點的下圖


    可以看到C0 接的是COM0 位選線,IO口通過1K電阻接到三極管基極上,如果IO是個高電平的話電流就通過三極管到射極流下來的,因此三極管達到飽和,CO點相當于導通相當于接地。4個段選分別接到PA4到PA7 四個IO口上因此我們寫程序首先將PA4 輸出一個1 PA5 PA6 PA7 全都輸出0 這樣我們選中第0個第一位數(shù)碼管此時在PB口上輸出的數(shù)據(jù)就會顯示在數(shù)碼管上面。編寫程序:

     



    設置一個斷點然后再單步調試(F10)?纯此@示的是那一段。同時流水燈也亮了,因為是同一個IO口。這樣對應PB上的每一段都找到了。

    好了這樣我們就去編寫一下數(shù)碼管的段碼;

    首先是顯示1   只要將需要點亮的各個段置1就實現(xiàn)了段碼的功能,具體的編寫過程自己去畫畫看



    這是我自己用數(shù)組的形式定義的。

    首先是什么類型的數(shù)組  名稱  元素個數(shù)

    0-9

    A-F

    全部顯示出來就是16個元素,一個字符型數(shù) 加一個逗號分開。一直放16個,使用時候要從第0個開始下標從0開始的。

    #include
    int main(void){
     //PD6 設置為輸入  K3
     DDRD &= ~(1 << 6);
     //輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
     PORTD |= (1<<6);
     //PD7 設置為輸入  K4
     DDRD &= ~(1 << 7); 
     //輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
     PORTD |= (1<<7);
     //PD2 設置為輸入  K1
     DDRD &= ~(1 << 2); 
     //輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
     PORTD |= (1<<2);//這個上拉不上拉沒關系 因為上拉是百K的電阻所以開關打開還是認為是低電平
     //PD3 設置為輸入  K2
     DDRD &= ~(1 << 3); 
     //輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
     PORTD |= (1<<3);
     //蜂鳴器PA3 設置方向寄存器為輸出
     DDRA |= (1<<3);
     //蜂鳴器關掉
     PORTA &= ~(1<<3);
     //數(shù)碼管全部置為輸出
     DDRB = 0xff;
     //位選線高四位全部置1 也是輸出 因為是置其中4位所以用|=
     DDRA |= 0Xf0;
     char scandata[16]={
     0b10101111,//0
     0b10100000,//1
     0b11000111,//2
     0b11100110,//3
     0b11100000,//4
     0b01101110,//5
     0b01101111,//6
     0b10100010,//7
     0b11101111,//8
     0b11101110,//9
     0b11100111,//A
     0b01101101,//b
     0b00001111,//c
     0b11000001,//d
     0b01001111,//E
     0b01001111//F
     
     };
      while(1){
       //數(shù)碼管也需要掃描所以也用死循環(huán) while(1)
       //先將數(shù)碼管的第一位點亮  選中位選
       PORTA |=(1<<4); 
       //再將數(shù)據(jù)送到PB口上哪一段對應哪一位 要事先測量下 編一個程序測量下 
       PORTB = scandata[0];//顯示0
       PORTB = scandata[1];//顯示1
       PORTB = scandata[2];//顯示2
       PORTB = scandata[3];//顯示3
       PORTB = scandata[4];//顯示4
       PORTB = scandata[5];//顯示5
       PORTB = scandata[6];//顯示6
       PORTB = scandata[7];//顯示7
       PORTB = scandata[8];//顯示8
       PORTB = scandata[9];//顯示9
       PORTB = scandata[10];//顯示A
       PORTB = scandata[11];//顯示B
       PORTB = scandata[12];//顯示C
       PORTB = scandata[13];//顯示D
       PORTB = scandata[14];//顯示E
       PORTB = scandata[15];//顯示F

      }
    }
    //這就是數(shù)碼管用段碼顯示。那么怎么對數(shù)碼管掃描顯示呢?

    我們可以遵循這樣一個順序,首先將數(shù)碼管位選中,送數(shù)據(jù)  PORTA 選中  PORTB送上數(shù)據(jù)然后打開相應這一位。讓他顯示出來,顯示出來之后呢?再讓這一位熄滅可以把所有四位都熄滅  位選 PORTA &0x0f;高四位清0這樣就完成了數(shù)碼管的高四位顯示。

    #include
    int main(void){
     int j ;

     //PD6 設置為輸入  K3
     DDRD &= ~(1 << 6);
     //輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
     PORTD |= (1<<6);
     //PD7 設置為輸入  K4
     DDRD &= ~(1 << 7); 
     //輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
     PORTD |= (1<<7);
     //PD2 設置為輸入  K1
     DDRD &= ~(1 << 2); 
     //輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
     PORTD |= (1<<2);//這個上拉不上拉沒關系 因為上拉是百K的電阻所以開關打開還是認為是低電平
     //PD3 設置為輸入  K2
     DDRD &= ~(1 << 3); 
     //輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
     PORTD |= (1<<3);
     //蜂鳴器PA3 設置方向寄存器為輸出
     DDRA |= (1<<3);
     //蜂鳴器關掉
     PORTA &= ~(1<<3);
     //數(shù)碼管全部置為輸出
     DDRB = 0xff;
     //位選線高四位全部置1 也是輸出 因為是置其中4位所以用|=
     DDRA |= 0Xf0;
     char scandata[16]={
     0b10101111,//0
     0b10100000,//1
     0b11000111,//2
     0b11100110,//3
     0b11100000,//4
     0b01101110,//5
     0b01101111,//6
     0b10100010,//7
     0b11101111,//8
     0b11101110,//9
     0b11100111,//A
     0b01101101,//b
     0b00001111,//c
     0b11000001,//d
     0b01001111,//E
     0b01001111//F
     
     };
      while(1){
       //數(shù)碼管也需要掃描所以也用死循環(huán) while(1)
       //先將數(shù)碼管的第一位點亮  選中位選
       PORTA |=(1<<4); 
       //再將數(shù)據(jù)送到PB口上哪一段對應哪一位 要事先測量下 編一個程序測量下 
       PORTB = scandata[0];//顯示0
       for (j=0;j<400;j++);//延時
       PORTA &= 0x0f;
       PORTA |= (1<<5);
       PORTB = scandata[1];
       PORTA &= 0x0f;
       for (j=0;j<400;j++);//延時
       PORTA |= (1<<6);
       PORTB = scandata[2];
       PORTA &= 0x0f;
       for (j=0;j<400;j++);//延時
       PORTA |= (1<<7);
       PORTB = scandata[3];
       for (j=0;j<400;j++);//延時
       PORTA &= 0x0f;
       //單步仿真看看  就是1位亮了0  熄滅 2位亮1  熄滅 3位亮2  熄滅  4位亮3 熄滅  一直循環(huán)
       //全速執(zhí)行看看顯示4個數(shù)字 可以看到亮度不怎么亮的,因為 點亮熄滅只有那么一小段時間是發(fā)光的
       //所以如果要增加亮度只需要加一個延時程序  聲明一個變量j  在看效果  全速執(zhí)行亮度是明顯增加了
    //這就是數(shù)碼管程序掃描的程序設計
       
      }
    }

     

     

     

     

    關閉窗口

    相關文章