专注电子技术学习与研究
当前位置:单片机教程网 >> MCU设计实例 >> 浏览文章

AVR单片机学习(五)按键与数码管的程序设计

作者:zww 1988   来源:本站原创   点击数:  更新时间:2014年04月18日   【字体:
按键与数码管的程序设计
  • AVR  IO口的输入模式与上拉电阻
  • 选择结构语句与按键的查询方式程序设计
  • 数码管基本原理
  • 扫描方式显示多位数码管
  • 一、输入状态IO寄存器设置

      1、DDRx 某一位置0,相应位的IO口被设置为输入

       2、PORTx某一位置1,使能对应IO口相应位的上拉电阻

      3、PINx的对应位是输入的数据,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)的值,执行相应代码

     

     除非你上电之前一直讲按键按下,否则上电的一瞬间程序就执行到while(1);了所以要将他们加入到死循环里面如下图


    这样就实现了按键的不停的检测。 其实DDRD 上电默认都是0  所以清0  置为输入也没有意义。

    程序在if判断设置断点然后全速执行可以看到只要没有按键按下程序进不去断点,如果我们在板子上按下K3则如下图所示进入断点,再按下单步执行蜂鸣器响了如图。



    所以这个程序就达到了我们的目的。

    现在换一种判断 就是按键被按下而不是没被按下 用逻辑非


    这样达到了预想的目标。但是这样只能判断一个按键如果多个按键怎么办呢?2种办法 

    一、采用if elseif else

    if (){

        elseif{

        }

        elseif {

        }

        else{

     

        }

    }

    二、采用

    switch (表达式){

    case  相符合的条件  {

        break;

    }

    case 相符合的条件 {

        break;

    }

    default{

     

    }

    }

    一、用if 实现

    #include
    int main(void){
     //PD6 设置为输入  K3
     DDRD &= ~(1 << 6);
     //输入状态下将数据寄存器使能上拉电阻
     PORTD |= (1<<6);
     //PD7 设置为输入  K4
     DDRD &= ~(1 << 7); 
     //输入状态下将数据寄存器使能上拉电阻
     PORTD |= (1<<7);
     //PD2 设置为输入  K1
     DDRD &= ~(1 << 2); 
     //输入状态下将数据寄存器使能上拉电阻
     PORTD |= (1<<2);//这个上拉不上拉没关系因为上拉是百K的电阻所以开关打开还是认为是低电平
     //PD3 设置为输入  K2
     DDRD &= ~(1 << 3); 
     //输入状态下将数据寄存器使能上拉电阻
     PORTD |= (1<<3);

     //蜂鸣器PA3 设置方向寄存器为输出
     DDRA |= (1<<3);
     //蜂鸣器关掉
     PORTA &= ~(1<<3);
     //流水灯端口全部设为输出
     DDRB = 0xff;
      while(1){
      //判断PIND 这位是否为1 为真的话就是按键没有按下
      if (!(PIND & (1<<6))){ //本来没有按下 进入 现在变成了没有按下 不进入了取非了  被按下进入了 PD6
       //按键被按下用蜂鸣器表示一下 PA3
        PORTA |= (1<<3);
      }
      else if(!(PIND & (1<<7))){ //PD7  K4按下让流水灯产生动作   必须上面使能K4上拉电阻
       PORTB |= (1<<0);//第一个灯发光 就是等于1
      }
      else if (PIND & (1<<2)){//因为按下的时候是低电平接的是电源
       //第二个灯发光
       PORTB |= (1<<1);
      }
      else if (!(PIND & (1<<3))){
       //第三个灯发光
       PORTB |= (1<<2);
      }
      else{
       //变成了按键没有按下 
         PORTA &= ~(1<<3); //蜂鸣器
       PORTB = 0;//灯
      }
      //看到没有按下一直响的,按下就不响了。
     }
    }

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

    二、用switch 来实现就需要一次性将这四位读回来。 代码如下

    #include
    int main(void){
     //PD6 设置为输入  K3
     DDRD &= ~(1 << 6);
     //输入状态下将数据寄存器使能上拉电阻
     PORTD |= (1<<6);
     //PD7 设置为输入  K4
     DDRD &= ~(1 << 7); 
     //输入状态下将数据寄存器使能上拉电阻
     PORTD |= (1<<7);
     //PD2 设置为输入  K1
     DDRD &= ~(1 << 2); 
     //输入状态下将数据寄存器使能上拉电阻
     PORTD |= (1<<2);//这个上拉不上拉没关系因为上拉是百K的电阻所以开关打开还是认为是低电平
     //PD3 设置为输入  K2
     DDRD &= ~(1 << 3); 
     //输入状态下将数据寄存器使能上拉电阻
     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 发光
         PORTB |= (1<<0);
         break;
        }
        case 0b11000000: {//只有第 二个按键按下 0b11001100  接的电源抬起是0
         //LED 0 发光
         PORTB |= (1<<1);
         break;
        }
        case 0b10001100: {//只有第三个按键按下 0b11001100  接的电源抬起是0
         //LED 0
         PORTB |= (1<<2);
         break;
        }
        case 0b01001100: {//只有第四个按键按下 0b11001100  接的电源抬起是0
         //LED 0 发光
         PORTB |= (1<<3);
         break;
        }
        default :{   //都没有按下 0b11001000  因为有下拉
         //变成了按键没有按下 
           PORTA &= ~(1<<3); //蜂鸣器
           PORTB = 0;//灯

           break;
        }
       }
      
       //判断PIND 这位是否为1 为真的话就是按键没有按下
      
     }
    }

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

    以上都是查询方式因为都是在while循环一边一边的查询,按键有动作就执行相应的代码这样很耽误CPU的时间的,在下一篇博客我会稍微降讲用中断的方式来编写按键的程序。下面继续说呵呵、

    八段数码管 

    一、八段数码管

    1、八段数码管由八段LED构成

    2、各LED阴极或阳极并在一起,称为“位选线”:共阴、共阳

    3、其余8个引脚各自引出,称为“段选线”,各段可以分别控制




    记住一般一位的数码管有10个脚

    个人理解:( 其中2脚是连在一起的是公共端。其他8个是段选  比如1、6接电源 其他接IO口个人理解的)

    多位合一的数码管

    一、多位合一的数码管

    1、将多个八段数码管的段选线分别并在一起,位选线引出如下图



    由上图看出是4位 应该是8个段选线(7段加一个点)  4个位选线  共12根线 

    com0 ---- com3 是位选 

    a-g 加 dp   是段选。

    多位数码管的使用

    1、多位数码管的各个位均可以单独显示不同的数据,但一个时刻只能点亮一位、(点快点人眼看不出来)

    2、依次点亮多位数码管中的各个位,由于人眼的视觉暂留效应,看起来是同时点亮

    3、如下图是电路图 硬件电路是下图设计的

     




    它的每一段相当于一个发光二极管,电流大约是10个mA左右(5--10)mA,因此段选可以直接用单片机的IO驱动是足够的不论是拉电流还是灌电流,这里面我们用的是一个共阴极的数码管,因此应该是向外拉电流,而段选线我们可以计算下段选线上最大电流时多少?假设每段都点亮没段是10mA的话,那么位选线上也就是10*8 = 80mA 所以我们不能用IO口,一般的单片机不可能输出这么大的电流,所以我采用一个三极管来进行驱动,共阴极的数码管一般要用NPN型的数码管,它的接法如下图的样子。


    再来张清楚点的下图


    可以看到C0 接的是COM0 位选线,IO口通过1K电阻接到三极管基极上,如果IO是个高电平的话电流就通过三极管到射极流下来的,因此三极管达到饱和,CO点相当于导通相当于接地。4个段选分别接到PA4到PA7 四个IO口上因此我们写程序首先将PA4 输出一个1 PA5 PA6 PA7 全都输出0 这样我们选中第0个第一位数码管此时在PB口上输出的数据就会显示在数码管上面。编写程序:

     



    设置一个断点然后再单步调试(F10)。看看它显示的是那一段。同时流水灯也亮了,因为是同一个IO口。这样对应PB上的每一段都找到了。

    好了这样我们就去编写一下数码管的段码;

    首先是显示1   只要将需要点亮的各个段置1就实现了段码的功能,具体的编写过程自己去画画看



    这是我自己用数组的形式定义的。

    首先是什么类型的数组  名称  元素个数

    0-9

    A-F

    全部显示出来就是16个元素,一个字符型数 加一个逗号分开。一直放16个,使用时候要从第0个开始下标从0开始的。

    #include
    int main(void){
     //PD6 设置为输入  K3
     DDRD &= ~(1 << 6);
     //输入状态下将数据寄存器使能上拉电阻
     PORTD |= (1<<6);
     //PD7 设置为输入  K4
     DDRD &= ~(1 << 7); 
     //输入状态下将数据寄存器使能上拉电阻
     PORTD |= (1<<7);
     //PD2 设置为输入  K1
     DDRD &= ~(1 << 2); 
     //输入状态下将数据寄存器使能上拉电阻
     PORTD |= (1<<2);//这个上拉不上拉没关系 因为上拉是百K的电阻所以开关打开还是认为是低电平
     //PD3 设置为输入  K2
     DDRD &= ~(1 << 3); 
     //输入状态下将数据寄存器使能上拉电阻
     PORTD |= (1<<3);
     //蜂鸣器PA3 设置方向寄存器为输出
     DDRA |= (1<<3);
     //蜂鸣器关掉
     PORTA &= ~(1<<3);
     //数码管全部置为输出
     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){
       //数码管也需要扫描所以也用死循环 while(1)
       //先将数码管的第一位点亮  选中位选
       PORTA |=(1<<4); 
       //再将数据送到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

      }
    }
    //这就是数码管用段码显示。那么怎么对数码管扫描显示呢?

    我们可以遵循这样一个顺序,首先将数码管位选中,送数据  PORTA 选中  PORTB送上数据然后打开相应这一位。让他显示出来,显示出来之后呢?再让这一位熄灭可以把所有四位都熄灭  位选 PORTA &0x0f;高四位清0这样就完成了数码管的高四位显示。

    #include
    int main(void){
     int j ;

     //PD6 设置为输入  K3
     DDRD &= ~(1 << 6);
     //输入状态下将数据寄存器使能上拉电阻
     PORTD |= (1<<6);
     //PD7 设置为输入  K4
     DDRD &= ~(1 << 7); 
     //输入状态下将数据寄存器使能上拉电阻
     PORTD |= (1<<7);
     //PD2 设置为输入  K1
     DDRD &= ~(1 << 2); 
     //输入状态下将数据寄存器使能上拉电阻
     PORTD |= (1<<2);//这个上拉不上拉没关系 因为上拉是百K的电阻所以开关打开还是认为是低电平
     //PD3 设置为输入  K2
     DDRD &= ~(1 << 3); 
     //输入状态下将数据寄存器使能上拉电阻
     PORTD |= (1<<3);
     //蜂鸣器PA3 设置方向寄存器为输出
     DDRA |= (1<<3);
     //蜂鸣器关掉
     PORTA &= ~(1<<3);
     //数码管全部置为输出
     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){
       //数码管也需要扫描所以也用死循环 while(1)
       //先将数码管的第一位点亮  选中位选
       PORTA |=(1<<4); 
       //再将数据送到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 熄灭  一直循环
       //全速执行看看显示4个数字 可以看到亮度不怎么亮的,因为 点亮熄灭只有那么一小段时间是发光的
       //所以如果要增加亮度只需要加一个延时程序  声明一个变量j  在看效果  全速执行亮度是明显增加了
    //这就是数码管程序扫描的程序设计
       
      }
    }

     

     

     

     

    关闭窗口

    相关文章