目录

写在前面

一、单片机最小系统解析

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单片机——函数进阶与按键相关推荐

  1. 手把手教你学51单片机-变量进阶与点阵LED

    变量的作用域 所谓的作用域就是指变量起作用的范围,也是变量的有效范围.变量按他的作用域可以 分为局部变量和全局变量. 局部变量 在一个函数内部声明的变量是内部变量,它只在本函数内有效,在本函数以外是不 ...

  2. 第11章 UART 串口通信(手把手教你学51单片机pdf部分)

    手把手教你学51单片机-C语言版.pdf  

  3. c语言52单片机液晶屏显示,[手把手教你学51单片机C语言教程]22 LCD12864液晶屏显示.pdf...

    您所在位置:网站首页 > 海量文档 &nbsp>&nbsp计算机&nbsp>&nbspC/C++资料 [手把手教你学51单片机C语言教程]22 LCD ...

  4. 【手把手教你学51单片机】中断的优先级

    注:本文章转载自<手把手教你学习51单片机>!因转载需要原文链接,故无法选择转载! 如若侵权,请联系我进行删除!上传至网络博客目的为了记录自己学习的过程的同时,同时能够帮助其他一同学习的小 ...

  5. 手把手教你学51单片机-c语言版期末考试,手把手从零教你学51单片机

    课程简介: 51单片机的教程及开发板真的很多,我曾经也从零学单片机,看过的视频教程及玩过的开发板也比较多,但很多都是为了卖发板而做视频教程.从头到尾的就每个模块做些例子,或者就送些例子,而例子中所涉及 ...

  6. 《手把手教你学51单片机》之十三------1602液晶与串口的应用实例

    第13章 1602液晶与串口的应用实例 理论上的内容要想逐步消化掌握,必须得通过大量的实践进行巩固,否则时间一长,极容易忘掉.尤其是一些编程相关的技巧,就是靠不停的写程序,不停的参考别人的程序慢慢积累 ...

  7. 数码管动态显示c语言,《手把手教你学51单片机-C语言》之六 中断与数码管动态显示...

    中断是单片机系统重点中的重点,因为有了中断,单片机就具备了快速协调多模块工作的能力,可以完成复杂的任务.本章将首先带领大家学习一些必要的C语言基础知识,然后讲解数码管动态显示的原理,并最终借助于中断系 ...

  8. (学习笔记)手把手教你学51单片机:变量进阶与点阵LED

    一.变量的作用域 作用域:变量的有效范围.分为:局部变量和全局变量. 1.1局部变量 在函数内部声明的变量,只在本函数内部可以使用. 1.2全局变量 在函数外声明的变量就是全局变量. 二.变量的存储类 ...

  9. 手把手教你学51单片机-如何学习单片机

    大多数大学生之所以最后变的平庸,不是因为脑子多么笨,也不是全怪自己贪玩不上进,只是没有一个好的领路人,许多学校可能挂着导师的名头,但是多数是挂羊头卖狗肉或者是干脆不管.最后等大学生毕业之后,那些所谓的 ...

最新文章

  1. 深度信念网络研究现状与展望
  2. know about用法
  3. linux python mysqldb安装包,linux环境下python中MySQLdb模块的安装方法
  4. 爬虫项目——BS练手(2)
  5. 算法笔记_面试题_11.正则表达式匹配
  6. java下打包软件--生成exe文件
  7. 西门子s7-200解密软件下载_高邮哪里有西门子三菱PLC编程学习班?多久能学会?...
  8. C语言段错误的有用总结
  9. 基于Labview开发TestStand用户界面时ConnectCommand List
  10. idm integration module(idm) Chrome插件 安装
  11. Android6.0 Qualcomm Atheros QCA6174A WiFi Model Support
  12. python +高德地图API调用
  13. Intel Thunderbolt 3 接口介绍
  14. python做相册_《自拍教程73》Python 自动生成相册文件夹
  15. 图像处理与机器视觉网络资源收罗——倾心大放送(转载)
  16. mib browser打开mib文件
  17. 对学校的希望和寄语_新学期对学校的寄语
  18. centos7 修改和优化ssh
  19. 震惊!十六岁少女竟然被三名阿里p8老师讲解{常见面试题汇总}
  20. 中断服务程序编写规则

热门文章

  1. STM32 HAL库 驱动 MT6701 磁编码器
  2. 常用的Linux命令(面试/工作必备)
  3. 贪吃蛇c语言自动寻路,C/C++编程笔记:C语言贪吃蛇源代码控制台(一),会动的那种哦!...
  4. 利用citrix xenapp and xendesktop建立你的云桌面
  5. IT30:天行健--君子以自强不息(启航)
  6. 《深入浅出Python》与《Python网络数据采集》读后感
  7. 【资源共享】eBook分享大集合
  8. 办公室布线电脑网络布线方案
  9. 刚工作不知道B端、C端什么意思?
  10. 公安部备案网址过程记录