手把手教你学51单片机——函数进阶与按键
目录
写在前面
一、单片机最小系统解析
1.1电源
1.2晶振
1.3复位电路
二、C语言——函数
2.1函数的调用
2.2函数的形参和实参
三、按键
3.1独立按键
3.2独立按键控制led数码管显示0~F
3.3按键消抖
3.4矩阵按键
3.5矩阵按键控制led显示不同数值
3.6简易加法计算器
写在前面
本博客基于宋雪松老师的《手把手教你学51单片机》教学视频及教程,使用的也是视频配套的KST-51开发板。但是本博客所包含的大部分代码均为博主自己学习思考之后,独立写出。可能会与教程例程所给代码有所出入。同样的该博客可以看作本人在学习51单片机的学习笔记。
同时,欢迎大家指正、并在讨论群留言。
一、单片机最小系统解析
单片机的最小系统由电源、晶振和复位电路组成。
1.1电源
可以查找我们使用单片机的数据手册获得,我所使用的STC89C52单片机工作电压为5.5~3.4V,为5V单片机。同时还有一种常用的工作电压范围为2.7~3.6V、典型值是3.3V的单片机,我们称之为3.3V单片机。
1.2晶振
晶振通常分为有源晶振和无源晶振两种。其中有源晶振我们需要给它供电才可以产生震荡频率,并且可以提供高精度的频率基准,信号质量也比无源信号要好。而无源晶振自身无法振荡起来,它需要芯片内部的振荡电路一起工作才能振荡,它允许不同的电压,但是信号质量和精度较有源晶振差一些。
1.3复位电路
如图所示为KST-51单片机上的复位电路,该单片机RST引脚高电位复位,低电位正常,我们来简单的分析一下其工作原理。
电路稳态时:电容隔绝电流,RST接地,此时为低电平正常工作。
上电复位:电容充电,此时可以把电容看作一根导线,RST高电平。在电容充电过程中,电流减小,RST电压逐渐减小至0。
按键复位:按下RESET按键,电路导通,电容放电,RST高电平。松开RESET按键,该过程类似于上电复位过程,RST电压逐渐减小至0。
二、C语言——函数
函数的使用可以使我们的程序条理清晰,避免大块的代码重复。
2.1函数的调用
函数的调用一般形式为:函数名(实参列表)。
这里我们需要注意几点:
1.函数在调用时,不需要加函数类型。
2.函数在调用之前,必须先定义或声明。也就是说要么被调用函数应写在调用函数之前,要么我们在调用函数之前在文件开头进行声明。在这里宋老师推荐以“函数声明——main函数——功能函数——中断函数”的顺序编写程序。
3.函数声明时必须加函数类型。
2.2函数的形参和实参
在定义函数时,括号中的参数为形式参数。在调用函数时,括号里的参数为实际参数。
在这里我们也需要注意几点,可能刚开始不了解,不必过分纠结,先学会使用再对其进行深入理解。
1.函数定义中指定的形参,在未发生函数调用时不占内存,只有函数调用时,函数中的形参才被分配内存单元。
2.实参必须有确定的值。
3.形参必须指定数据类型。
4.实参向形参的数据传递是单向传递,不能由形参再回传给实参。
在这里,书中给了一个小例子,大家可以好好体会一下函数的使用、定义和注意事项。
unsigned char add(unsigned char x, unsigned char y); //函数声明
void main(){unsigned char a = 1;unsigned char b = 2;unsigned char c = 0;c = add(a, b); //调用时,a 和 b 就是实参,把函数的返回值赋给 c//执行完后,c 的值就是 3while(1);}unsigned char add(unsigned char x, unsigned char y) //函数定义,括号中的 x 和 y 就是形参{unsigned char z = 0;z = x + y;return z; //返回值 z 的类型就是函数 add 的类型}
三、按键
3.1独立按键
如图所示为独立按键的原理图,当按键松开时,KeyIn与+5V电位相同,二者同为高电平。而按键按下时,电流如图中红色线所示流向GND,此时KeyIn的GND电位相同,为低电平。
所以当按键发生改变时,KeyIn会发生高低电平的变化,我们就可以通过 KeyIn这个 IO 口的高低电平来判断是否有按键按下。
3.2独立按键控制led数码管显示0~F
#include<reg52.h>
sbit A0 = P1^0;
sbit A1 = P1^1;
sbit A2 = P1^2;
sbit A3 = P1^3;
sbit ENLED = P1^4;
sbit KeyIn1 = P2^4;unsigned char code LedChar[] ={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E} ;void main()
{bit backup = 1;signed char cnt = 0;A0 = 0;A1 = 0;A2 = 0;A3 = 1;ENLED = 0;P2 = 0xF7;while(1){P0 = LedChar[cnt];if(KeyIn1 != backup)//按键被按下,先不执行操作{if(backup == 0)//按键被松开,执行操作{cnt++;if(cnt>=15)cnt = 0;}backup = KeyIn1;//backup状态要么与keyin1状态相同,要么落后与其一个状态}}
}
3.3按键消抖
通常机械按键在按下、松开时都会带有10ms左右的不定状态,也就是按键的抖动。在这里我们提供两种软件消抖方式。
方法一:使用delay函数进行消抖。这里我直接复制了例程的代码,通过delay函数将抖动10ms延迟,不过较多的delay函数会时我们的程序出错,不推荐使用。
#include <reg52.h>sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
sbit KEY1 = P2^4;
sbit KEY2 = P2^5;
sbit KEY3 = P2^6;
sbit KEY4 = P2^7;unsigned char code LedChar[] = { //数码管显示字符转换表0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};void delay();void main()
{bit keybuf = 1; //按键值暂存,临时保存按键的扫描值bit backup = 1; //按键值备份,保存前一次的扫描值unsigned char cnt = 0; //按键计数,记录按键按下的次数ENLED = 0; //选择数码管DS1进行显示ADDR3 = 1;ADDR2 = 0;ADDR1 = 0;ADDR0 = 0;P2 = 0xF7; //P2.3置0,即KeyOut1输出低电平P0 = LedChar[cnt]; //显示按键次数初值while (1){keybuf = KEY4; //把当前扫描值暂存if (keybuf != backup) //当前值与前次值不相等说明此时按键有动作{delay(); //延时大约10msif (keybuf == KEY4) //判断扫描值有没有发生改变,即按键抖动{if (backup == 0) //如果前次值为0,则说明当前是弹起动作{cnt++; //按键次数+1if (cnt >= 10){ //只用1个数码管显示,所以加到10就清零重新开始cnt = 0;}P0 = LedChar[cnt]; //计数值显示到数码管上}backup = keybuf; //更新备份为当前值,以备进行下次比较}}}
}
/* 软件延时函数,延时约10ms */
void delay()
{unsigned int i = 1000;while (i--);
}
方法二 :对按键状态连续读取8位,若该8位为“0000 0000”则判断为按键按下,若该8位为“1111 1111”则判断为按键弹起,若其他数字则不对按键进行操作。推荐使用!
#include <reg52.h>sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
sbit KEY1 = P2^4;
sbit KEY2 = P2^5;
sbit KEY3 = P2^6;
sbit KEY4 = P2^7;unsigned char code LedChar[] = { //数码管显示字符转换表0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
bit KeySta = 1; //当前按键状态void main()
{bit backup = 1; //按键值备份,保存前一次的扫描值unsigned char cnt = 0; //按键计数,记录按键按下的次数EA = 1; //使能总中断ENLED = 0; //选择数码管DS1进行显示ADDR3 = 1;ADDR2 = 0;ADDR1 = 0;ADDR0 = 0;TMOD = 0x01; //设置T0为模式1TH0 = 0xF8; //为T0赋初值0xF8CD,定时2msTL0 = 0xCD;ET0 = 1; //使能T0中断TR0 = 1; //启动T0P2 = 0xF7; //P2.3置0,即KeyOut1输出低电平P0 = LedChar[cnt]; //显示按键次数初值while (1){if (KeySta != backup) //当前值与前次值不相等说明此时按键有动作{if (backup == 0) //如果前次值为0,则说明当前是弹起动作{cnt++; //按键次数+1if (cnt >= 10){ //只用1个数码管显示,所以加到10就清零重新开始cnt = 0;}P0 = LedChar[cnt]; //计数值显示到数码管上}backup = KeySta; //更新备份为当前值,以备进行下次比较}}
}
/* T0中断服务函数,用于按键状态的扫描并消抖 */
void InterruptTimer0() interrupt 1
{static unsigned char keybuf = 0xFF; //扫描缓冲区,保存一段时间内的扫描值TH0 = 0xF8; //重新加载初值TL0 = 0xCD;keybuf = (keybuf<<1) | KEY4; //缓冲区左移一位,并将当前扫描值移入最低位if (keybuf == 0x00){ //连续8次扫描值都为0,即16ms内都只检测到按下状态时,可认为按键已按下KeySta = 0;}else if (keybuf == 0xFF){ //连续8次扫描值都为1,即16ms内都只检测到弹起状态时,可认为按键已弹起KeySta = 1;}else{} //其它情况则说明按键状态尚未稳定,则不对KeySta变量值进行更新
}
3.4矩阵按键
同样的,理解了独立按键之后,矩阵按键也不难理解。下图为一矩阵按键的原理图,如果想让矩阵按键工作,那么KeyOut必须输出低电平,起到类似独立按键中GND的作用;KeyIn输出高电平。
我们可以将KeyOut理解为开关,只有在KeyOut输出0时,四个按键才能正常工作。而KeyIn理解成检测的工具,我们根据KeyIn的变化判断按键是否按下。
我们拿K1举例:
当按键松开:KeyIn1和+5V等电位,KeyIn1为高电平,KeyOut1为低电平。
当按键按下:电流如红色线所示,KeyIn1为低电平,KeyOut1为低电平。
3.5矩阵按键控制led显示不同数值
//实现矩阵键盘k1~k15控制数码管显示0~F;
#include<reg52.h>sbit A0 = P1^0;
sbit A1 = P1^1;
sbit A2 = P1^2;
sbit A3 = P1^3;
sbit ENLED = P1^4;
sbit KeyIn1 = P2^4;
sbit KeyIn2 = P2^5;
sbit KeyIn3 = P2^6;
sbit KeyIn4 = P2^7;
sbit KeyOut1 = P2^3;
sbit KeyOut2 = P2^2;
sbit KeyOut3 = P2^1;
sbit KeyOut4 = P2^0;unsigned char code LedChar[] ={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E
} ;
unsigned char Keyflag[4][4]={{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}
};void main()
{unsigned char i,j;unsigned char Backup[4][4]={{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}};A0 = 0;A1 = 0;A2 = 0;A3 = 1;ENLED = 0;EA = 1;TMOD = 0x01;TH0 = 0xFC;TL0 = 0x66;TR0 = 1;ET0 = 1;P0 = LedChar[0];while(1){for(i=0;i < 4;i++){for(j=0;j < 4;j++){if(Backup[i][j] != Keyflag[i][j]){if(Backup[i][j] != 0 ){P0 = LedChar[i*4+j];}Backup[i][j] = Keyflag[i][j];}}}}
}void InterruptTimer0() interrupt 1
{ unsigned char k;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},};TH0 = 0xFC;TL0 = 0x66;Keybuf[keyout][0] = (Keybuf[keyout][0] << 1) | KeyIn1;Keybuf[keyout][1] = (Keybuf[keyout][1] << 1) | KeyIn2;Keybuf[keyout][2] = (Keybuf[keyout][2] << 1) | KeyIn3;Keybuf[keyout][3] = (Keybuf[keyout][3] << 1) | KeyIn4; for(k=0;k < 4;k++){if((Keybuf[keyout][k] & 0x0F) == 0x00){Keyflag[keyout][k] = 0;}else if((Keybuf[keyout][k] & 0x0F) == 0x0F){Keyflag[keyout][k] = 1;}}keyout++;keyout = keyout & 0x03;switch(keyout){case 0:KeyOut2 = 1;KeyOut3 = 1;KeyOut4 = 1;KeyOut1 = 0;break;case 1:KeyOut1 = 1;KeyOut3 = 1;KeyOut4 = 1;KeyOut2 = 0;break;case 2:KeyOut1 = 1;KeyOut2 = 1;KeyOut4 = 1;KeyOut3 = 0;break;case 3:KeyOut1 = 1;KeyOut2 = 1;KeyOut3 = 1;KeyOut4 = 0;break;default:break;}
}
3.6简易加法计算器
此段代码对刚入门的新手确实有难度(比如我T T),建议大家先读两遍,抄一遍,最后再将代码默写出来,可能会花很长时间理解。宋老师在视频中说到:“当你跟着我写了七八十个这样的代码时,你的代码能力会得到很大的提高。”
//实现加法计算器
#include<reg52.h>sbit A0 = P1^0;
sbit A1 = P1^1;
sbit A2 = P1^2;
sbit A3 = P1^3;
sbit ENLED = P1^4;
sbit KeyIn1 = P2^4;
sbit KeyIn2 = P2^5;
sbit KeyIn3 = P2^6;
sbit KeyIn4 = P2^7;
sbit KeyOut1 = P2^3;
sbit KeyOut2 = P2^2;
sbit KeyOut3 = P2^1;
sbit KeyOut4 = P2^0;unsigned char code LedChar[] ={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E
} ;
unsigned char Keyflag[4][4]={{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}
};
unsigned char code Keycodemap[4][4]={{0x31,0x32,0x33,0x26},{0x34,0x35,0x36,0x25},{0x37,0x38,0x39,0x28},{0x30,0x1B,0x0D,0x27}
};
unsigned char ledbuff[6]={0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
};void KeyDriver();void main()
{A0 = 0;A1 = 0;A2 = 0;A3 = 1;ENLED = 0;EA = 1;TMOD = 0x01;TH0 = 0xFC;TL0 = 0x66;TR0 = 1;ET0 = 1;ledbuff[0] = LedChar[0];while(1){KeyDriver();}
}void Shownumber(unsigned long num) //将数字存储到缓冲区
{signed char i;unsigned char buf[6];for(i=0;i<6;i++){buf[i]= num % 10;num = num / 10;}for(i=5;i >= 1;i--){if(buf[i] == 0){ledbuff[i]=0xFF;}else{break;}}for( ;i >= 0;i--){ledbuff[i] = LedChar[buf[i]];}
}void Keyaction(unsigned char keycode) //将按键值转化为数字和动作
{static unsigned long addend = 0;static unsigned long result = 0;if((keycode >= 0x30) && (keycode <= 0x39)){addend = (addend*10) + (keycode - 0x30);Shownumber(addend);}else if(keycode == 0x0D){result += addend;addend = 0;Shownumber(result);}else if(keycode == 0x1B){result = 0;addend = 0;Shownumber(addend);}else if(keycode == 0x26){result = addend;addend = 0;Shownumber(result);}
}void keydriver() //对按键按下和松开进行判断
{static unsigned char backup[4][4]={{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}};unsigned char i;unsigned char j;for(i=0;i<4;i++){for(j=0;j<4;j++){if(backup[i][j] != Keyflag[i][j]){if(backup[i][j] == 1){Keyaction(Keycodemap[i][j]);}backup[i][j] = Keyflag[i][j];}}}
}void Keyscanf() //消抖
{static unsigned char keybuf[4][4]={{0xFF,0xFF,0xFF,0xFF},{0xFF,0xFF,0xFF,0xFF},{0xFF,0xFF,0xFF,0xFF},{0xFF,0xFF,0xFF,0xFF}};static unsigned char keyout = 0;unsigned char i;keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KeyIn1;keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KeyIn2;keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KeyIn3;keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KeyIn4;for(i=0;i<4;i++){if((keybuf[keyout][i] & 0x0F) == 0x00){Keyflag[keyout][i] = 0;}else if((keybuf[keyout][i] & 0x0F) == 0x0F){Keyflag[keyout][i] = 1;}}keyout++;keyout = keyout & 0x03;switch(keyout){case 0:KeyOut4 = 1;KeyOut1 = 0;break; //单片机先拉高再降低case 1:KeyOut1 = 1;KeyOut2 = 0;break;case 2:KeyOut2 = 1;KeyOut3 = 0;break;case 3:KeyOut3 = 1;KeyOut4 = 0;break;default:break;}
}void Ledscanf()
{static unsigned char i=0;P0 = 0xFF;switch(i){case 0:A0 = 0;A1 = 0;A2 = 0;P0 = ledbuff[i];i++;break;case 1:A0 = 1;A1 = 0;A2 = 0;P0 = ledbuff[i];i++;break;case 2:A0 = 0;A1 = 1;A2 = 0;P0 = ledbuff[i];i++;break;case 3:A0 = 1;A1 = 1;A2 = 0;P0 = ledbuff[i];i++;break;case 4:A0 = 0;A1 = 0;A2 = 1;P0 = ledbuff[i];i++;break;case 5:A0 = 1;A1 = 0;A2 = 1;P0 = ledbuff[i];i=0;break;default:break;}
}void Interrupttimer0()interrupt 1
{TH0 = 0xFC;TL0 = 0x66;Keyscanf();Ledscanf();
}
手把手教你学51单片机——函数进阶与按键相关推荐
- 手把手教你学51单片机-变量进阶与点阵LED
变量的作用域 所谓的作用域就是指变量起作用的范围,也是变量的有效范围.变量按他的作用域可以 分为局部变量和全局变量. 局部变量 在一个函数内部声明的变量是内部变量,它只在本函数内有效,在本函数以外是不 ...
- 第11章 UART 串口通信(手把手教你学51单片机pdf部分)
手把手教你学51单片机-C语言版.pdf
- c语言52单片机液晶屏显示,[手把手教你学51单片机C语言教程]22 LCD12864液晶屏显示.pdf...
您所在位置:网站首页 > 海量文档  > 计算机 > C/C++资料 [手把手教你学51单片机C语言教程]22 LCD ...
- 【手把手教你学51单片机】中断的优先级
注:本文章转载自<手把手教你学习51单片机>!因转载需要原文链接,故无法选择转载! 如若侵权,请联系我进行删除!上传至网络博客目的为了记录自己学习的过程的同时,同时能够帮助其他一同学习的小 ...
- 手把手教你学51单片机-c语言版期末考试,手把手从零教你学51单片机
课程简介: 51单片机的教程及开发板真的很多,我曾经也从零学单片机,看过的视频教程及玩过的开发板也比较多,但很多都是为了卖发板而做视频教程.从头到尾的就每个模块做些例子,或者就送些例子,而例子中所涉及 ...
- 《手把手教你学51单片机》之十三------1602液晶与串口的应用实例
第13章 1602液晶与串口的应用实例 理论上的内容要想逐步消化掌握,必须得通过大量的实践进行巩固,否则时间一长,极容易忘掉.尤其是一些编程相关的技巧,就是靠不停的写程序,不停的参考别人的程序慢慢积累 ...
- 数码管动态显示c语言,《手把手教你学51单片机-C语言》之六 中断与数码管动态显示...
中断是单片机系统重点中的重点,因为有了中断,单片机就具备了快速协调多模块工作的能力,可以完成复杂的任务.本章将首先带领大家学习一些必要的C语言基础知识,然后讲解数码管动态显示的原理,并最终借助于中断系 ...
- (学习笔记)手把手教你学51单片机:变量进阶与点阵LED
一.变量的作用域 作用域:变量的有效范围.分为:局部变量和全局变量. 1.1局部变量 在函数内部声明的变量,只在本函数内部可以使用. 1.2全局变量 在函数外声明的变量就是全局变量. 二.变量的存储类 ...
- 手把手教你学51单片机-如何学习单片机
大多数大学生之所以最后变的平庸,不是因为脑子多么笨,也不是全怪自己贪玩不上进,只是没有一个好的领路人,许多学校可能挂着导师的名头,但是多数是挂羊头卖狗肉或者是干脆不管.最后等大学生毕业之后,那些所谓的 ...
最新文章
- 深度信念网络研究现状与展望
- know about用法
- linux python mysqldb安装包,linux环境下python中MySQLdb模块的安装方法
- 爬虫项目——BS练手(2)
- 算法笔记_面试题_11.正则表达式匹配
- java下打包软件--生成exe文件
- 西门子s7-200解密软件下载_高邮哪里有西门子三菱PLC编程学习班?多久能学会?...
- C语言段错误的有用总结
- 基于Labview开发TestStand用户界面时ConnectCommand List
- idm integration module(idm) Chrome插件 安装
- Android6.0 Qualcomm Atheros QCA6174A WiFi Model Support
- python +高德地图API调用
- Intel Thunderbolt 3 接口介绍
- python做相册_《自拍教程73》Python 自动生成相册文件夹
- 图像处理与机器视觉网络资源收罗——倾心大放送(转载)
- mib browser打开mib文件
- 对学校的希望和寄语_新学期对学校的寄语
- centos7 修改和优化ssh
- 震惊!十六岁少女竟然被三名阿里p8老师讲解{常见面试题汇总}
- 中断服务程序编写规则