51单片机学习笔记(1)
1. 环境配置
要使用单片机,需要下载下面两个软件:
Keil5用来编写程序,然后生成单片机可以识别的二进制文件,其后缀为.hex;
STC-ISP则是用来下载程序:打开生成的程序文件,然后通过数据线将程序下载到单片机上。
2.单片机
1.单片机介绍
ROM:只读存储器(Read-Only Memory),相当于电脑的硬盘,存储的数据也不会丢失。
RAM:随机存取存储器(Random Access Memory),相当于电脑的内存条,与CPU直接交换数据,断电后数据就会消失。
2.单片机管脚
3. LED
LED是发光二极管。LED省电而且亮度高,应用很广泛。
LED原理图:
1. 点亮LED
要点亮LED,就需要给P2寄存器特定数字,但C语言程序不能直接识别二进制,所以需要写成十六进制(前面要加上0x)。
#include <at89c51RC2.h> void main() { P2=0xFE;//1111 1110 while (1)//如果不加上while循环,会导致不断地跑上面那行代码 { } }
2. LED闪烁
要让LED闪烁,需要让LED不断重复亮灭的操作。但是如果亮了之后立马熄灭会因为视觉暂留导致看起来是一直亮。所以需要在每次操作执行完后加入一个延时操作。
#include <at89c51RC2.h> #include <INTRINS.H> //包含_nop_()函数的头文件 void Delay500ms() //@11.0592MHz { unsigned char i, j, k; _nop_(); i = 4; j = 129; k = 119; do { do { while (--k); } while (--j); } while (--i); } void main() { while (1) { P2=0xFE; //亮灯 Delay500ms(); P2=0xFF; //全灭 Delay500ms(); } }
3. LED流水灯
流水灯的操作通过让LED逐个亮起,同时每次操作后进行延时来实现。
为了能方便控制流水灯变化的速度,可以改变一毫秒延时函数:
void Delay1ms(unsigned int xms) //定义参数为我们需要的时间xms { unsigned char i, j; while(xms--){ //将一毫秒延时函数套入循环,循环次数为xms的大小 _nop_(); //这样就实现了xms的延时 i = 2; j = 199; do { while (--j); } while (--i); } }
流水灯的具体代码如下:
#include <at89c51RC2.h> #include <intrins.h> void Delay1ms(unsigned int xms) //@11.0592MHz { unsigned char i, j; while(xms--){ _nop_(); i = 2; j = 199; do { while (--j); } while (--i); } } void main() { while(1) { P2=0xFE;//1111 1110 Delay1ms(1000); P2=0xFD;//1111 1101 Delay1ms(1000); P2=0xFB;//1111 1011 Delay1ms(100); P2=0xF7;//1111 0111 Delay1ms(100); P2=0xEF;//1110 1111 Delay1ms(100); P2=0xDF;//1101 1111 Delay1ms(100); P2=0xBF;//1011 1111 Delay1ms(100); P2=0x7F;//0111 1111 Delay1ms(100); } }
4. 独立按键控制
按下按键时,io口的线相当于接地,所以可以通过寄存器对应的高低电平判断按键是否按下。
四个按键分别对应P3_1;P3_0;P3_2;P3_3。等于1表示按键未按下,等于0表示按键按下。
1. 位运算
位运算是对位进行操作:
按位左移/右移:0011 1100 <<x (x表示移动的位数)。左移时高位移出去,低位会补上0;右移时低位移出去,高位补上0。
按位与/或:0001 1000 &/| 0010 1010 每一位一一对应进行&或|的操作(0为假,1为真)。
按位异或:0001 1000 ^ 0010 1010 ->0010 0010 一个一个位比较,相同为0,不同为1。
按位取反:~0001 1000 ->1110 0111 0变成1,1变成0。
2. 独立按键控制LED亮灭
P2=0xFE是对8位的寄存器进行操作。
如果要单独对一个LED进行操作,需要写成P2_x=0或1 。(x为0到7,分别对应8个LED)
通过判断按键对应寄存器的电平,判断按键是否被按下,再执行按下后的操作。
#include <at89c51RC2.h> void main() { while(1) { if(P3_1==0) { P2_0=0; } else { P2_0=1; } } }
该代码为按键1按下后灯亮,一松开灯就会熄灭。
3. 独立按键控制LED状态
1.消抖
由于按键的抖动,当我们按下一次的时候,可能单片机会判断成多次按下,导致产生与预期效果不符的结果。
所以,我们需要在按下和松开按键之后加入一定时间的延时,保证开关为电平稳定后再进行操作;
为了知道什么时候松手,需要用一个循环,让按键按下后一直处于循环中,松手后才执行后面的代码:
if(P3_1==0) { Delay(20); while(P3_1==0);//开关一直按下,松手后离开循环 Delay(20); }
2. 控制LED状态
LED的初始状态均为1,也就是高电平,处于熄灭状态。
当我们对LED取反,1变为0,再取反0就变回1,从而实现LED状态的改变:
#include <at89c51RC2.h> #include <intrins.h> void Delay(unsigned int xms) //@11.0592MHz { unsigned char i, j; while (xms) { _nop_(); i = 2; j = 199; do { while (--j); } while (--i); xms--; } } void main() { while(1) { if(P3_1==0) { Delay(20); while(P3_1==0); Delay(20); P2_0=~P2_0; } } }
4. 独立按键控制LED显示二进制
要实现二进制的显示,可以通过0000 0000每一次循环都加1,然后取反。
但是P2一开始为1111 1111,并且P2直接控制了灯的亮灭,如果取反,加1,取反,也不能出现预期效果。
所以需要引入一个初始化为0的变量,执行完加1,取反后再将值赋给P2。
#include <at89c51RC2.h> #include <intrins.h> void Delay(unsigned int xms) //@11.0592MHz { unsigned char i, j; while(xms--) { _nop_(); i = 2; j = 199; do { while (--j); } while (--i); } } void main() { unsigned char LEDNum=0; while(1) { if(P3_1==0) { Delay(20); while(P3_1==0); Delay(20); // P2--; LEDNum++; P2=~LEDNum; } } }
再尝试过之后发现,其实直接写个P2--也可以实现一样的效果。
5.独立按键控制LED移位
控制LED移位可以用0000 0001再通过<<运算符移位,用一个初始化为0的变量a表示移位的位数,
按一次按键移位的位数就加1,再将取反后的值赋给P2。当这个变量a等于8时,将其重新变为0。
如果要实现通过其他按键执行相反方向的移位,需要按下该按键后让a减1,左移位数减少,表现为右移的效果。
要注意如果a=0,要让a变为7来实现右移。
#include <at89c51RC2.h> #include <intrins.h> void Delay(unsigned int xms); unsigned char LEDNum;//È«¾Ö±äÁ¿²»¸ø³õʼֵĬÈÏÊÇ0 void main() { P2=~0x01; while(1) { if(P3_1==0){ Delay(20); while(P3_1==0); Delay(20); LEDNum++; if(LEDNum>=8) { LEDNum=0; } P2=~(0x01<<LEDNum); } if(P3_0==0){ Delay(20); while(P3_0==0); Delay(20); if(LEDNum==0) { LEDNum=7; } else { LEDNum--; } P2=~(0x01<<LEDNum); } } } void Delay(unsigned int xms) //@11.0592MHz { unsigned char i, j; while(xms--) { _nop_(); i = 2; j = 199; do { while (--j); } while (--i); } }
5. 数码管
数码管由8个LED组成,7个构成数字或者其他的表示,1个用来表示小数点。
数码管接法:
3,8端为位选端,其他端为段选端(选中特定段的LED),此为共阴极接法,当位选为低电平,段选为高电平时,LED点亮。
四位一体数码管:
通过位选选中要显示的数码管位置,再通过段选选出想点亮的LED。所以每次点亮只能选中多个数码管中的一个,显示一个数字。
1. 静态数码管
1. 138译码器
138译码器控制位选:通过P2_4,P2_3,P2_2分别赋0或1,三个二进制数字转换为十进制,指定Y0到Y7中的一个为0。
使得对应数码管被选中。注意要从高位排到低位(P24,P23,P22)来写。
2. 74HC245
之前LED的点亮是给寄存器赋0点亮,是低电平点亮,低电平驱动能力较强,电流大,灯比较亮
但是这里数码管的点亮是高电平点亮,高电平驱动能力较弱,电流小。
所以用74HC245芯片作为缓冲器来提高驱动能力。
(将寄存器的信号作为控制信号,从自己接的电源上汲取能量,从而放大电流。)
3.数码管显示
通过P2的三个接口选中显示的数码管位置,再通过给P0段码的数据,点亮特定LED。
注意从高位排到低位(P0_7~P0_0)来写。
为了使主函数里的代码更简洁,我们可以通过函数和数组来实现模块化,具体代码如下:
unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F}; //上方数组对应数码管显示0到9 void Nixie(unsigned char Location,unsigned char Number) { switch(Location) { case 1:P2_4=1;P2_3=1;P2_2=1;break; case 2:P2_4=1;P2_3=1;P2_2=0;break; case 3:P2_4=1;P2_3=0;P2_2=1;break; case 4:P2_4=1;P2_3=0;P2_2=0;break; case 5:P2_4=0;P2_3=1;P2_2=1;break; case 6:P2_4=0;P2_3=1;P2_2=0;break; case 7:P2_4=0;P2_3=0;P2_2=1;break; case 8:P2_4=0;P2_3=0;P2_2=0;break; } P0=NixieTable[Number]; } //引用函数时写成Nixie(a,b) a为数码管位置,b为显示的数字。
然后在主函数里引用函数,输入对应的参数,(引用函数时写成Nixie(a,b) a为数码管位置,b为显示的数字),即可实现想要的结果:
#include <at89c51RC2.h> unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F}; void Nixie(unsigned char Location,unsigned char Number) { switch(Location) { case 1:P2_4=1;P2_3=1;P2_2=1;break; case 2:P2_4=1;P2_3=1;P2_2=0;break; case 3:P2_4=1;P2_3=0;P2_2=1;break; case 4:P2_4=1;P2_3=0;P2_2=0;break; case 5:P2_4=0;P2_3=1;P2_2=1;break; case 6:P2_4=0;P2_3=1;P2_2=0;break; case 7:P2_4=0;P2_3=0;P2_2=1;break; case 8:P2_4=0;P2_3=0;P2_2=0;break; } P0=NixieTable[Number]; } void main() { Nixie(5,7); while(1) { }; }
2 动态数码管
前面有说到数码管只能一次在一个位显示一个数字。要实现多位多个数字的显示,需要用到视觉残留。
当显示完一个后立马显示下一个,此时人眼就会看见多个数字。
但是如果直接把几行显示的代码紧密地接在一起,显示会错乱:动态数码管的显示是位选与段选的重复,如果接得太紧密,第一个数的段选会窜到下一个数的位选里,在那个位里面显示。
消影操作(在函数中加入代码):在每个数字显示之后Delay1毫秒,然后让数码管清零(P0=0x00)。从而消除显示错乱的现象。(在函数中加入代码)
#include <at89c51RC2.h> #include <intrins.h> unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F}; void Delay(unsigned int xms) //@11.0592MHz { unsigned char i, j; while(xms--) { _nop_(); i = 2; j = 199; do { while (--j); } while (--i); } } void Nixie(unsigned char Location,unsigned char Number) { switch(Location) { case 1:P2_4=1;P2_3=1;P2_2=1;break; case 2:P2_4=1;P2_3=1;P2_2=0;break; case 3:P2_4=1;P2_3=0;P2_2=1;break; case 4:P2_4=1;P2_3=0;P2_2=0;break; case 5:P2_4=0;P2_3=1;P2_2=1;break; case 6:P2_4=0;P2_3=1;P2_2=0;break; case 7:P2_4=0;P2_3=0;P2_2=1;break; case 8:P2_4=0;P2_3=0;P2_2=0;break; } P0=NixieTable[Number]; Delay(1); P0=0x00; } void main() { while(1) { Nixie(1,1); // Delay(20); Nixie(2,2); // Delay(20); Nixie(3,3); // Delay(20); }; }