本笔记基于STC89C52RC进行学习
链接 51单片机入门教程-
单片机
单片机内部集成了 CPU,RAM,ROM,定时器,中断系统,通讯接口等一系列电脑的常用硬件
功能单片机的任务是信息采集,处理,和硬件设备的控制
使用工具
- keil5 程序的编写。
- STC-ISP 下载(烧入)程序到单片机,以及一些额外的工具如串口助手,延迟代码生成。
发光二极管
发光二极管单向导通,导通且电流合适时发光。
开发板中的二极管正极始终为高电平,而负极与单片机的P20-P27引脚连接。(串联了2k欧的限流电阻来防止电流过大)
要控制二极管的亮灭就需要控制引脚输出的高低电平。
单片机中的P2八位寄存器,每一位都连了驱动器,使其的值反映为引脚的高低电平。
所以问题转化为控制P2寄存器中的数值来控制发光二极管的状态。
—————————————
后续章节指出能用P2_0=0;
来实现单个二极管的控制
(也可以推测出各位默认为1)
点亮一个LED
若需要点亮D1二极管
则P2应该为 1111 1110B
代码如下
#include <REGX52.h>
//头文件中引入了P2口对应寄存器的地址
void main()
{
P2=0xFE;//1111 1110
while(1){
}//使得赋值只执行了一次
}
LED的闪烁
亮灭的操作看上去只有两步:
点亮与熄灭
但实际上,还有一步很重要的延时
毕竟晶振主频很高,单纯循环两步操作
肉眼是不可见其闪烁的
延时0.2秒的代码如下
#include <REGX52.h>
#include <INTRINS.h>
void Delay200ms(void)
{
unsigned char data i, j, k;
_nop_();//在intrins.h头文件中,什么都不做执行一步
i = 2;
j = 103;
k = 147;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void main()
{
while(1){
P2=0xFE;//点亮
Delay200ms();//调用函数,来延时
P2=0xFF;//熄灭
Delay200ms();
}
}
延时函数的优化
使其可以延时任意秒~~(但我估摸着精度会有所降低)~~
void sleep(int a) //Delayams
{
unsigned char data i, j;
do
{
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}while (--a);
}
LED流水灯
可以按照视频中的顺序赋值来完成流水灯
也可以利用<<,>>,~ 等运算符来完成目标~~(不能白学了C)~~
以下代码展示用乘法来替代移位运算
#include <REGX52.h>
#include <INTRINS.h>
void sleep(int a) //延时函数
{
unsigned char data i, j;
do
{
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}while (--a);
}
void main()
{
short a=128;
while(1){
if(a==128){
a=1;
}else{
a*=2;
}
P2=~a;//按位取反
sleep(500);
}
}
独立按键
介绍
按键按下时左右两边导通,
一边接引脚一边接地
按下时会使引脚变为低电平。
对应的置零的的位为
- K1-P3_1
- K2-P3_0
- K3-P3_2
- K4-P3_3
按键的抖动&消抖&检测松手
对于机械开关,当机械触点断开闭合时,由于机械出点的弹性作用,一个开关在闭合时不会马上稳定地接通,在断开时也不会以下子断开,会伴随一连串的抖动
可以通过软件层面延迟20ms来进行消抖
部分代码如下
if(P3_0==0){
sleep(20);
P2_1=~P2_1;
}
若松手后执行,部分代码如下
if(P3_0==0){
sleep(20);
while(P3_0==0);
sleep(20);
P2_1=~P2_1;
}
控制LED状态
通过K2来实现D2的亮灭
代码如下
#include <REGX52.h>
#include <INTRINS.h>
void sleep(unsigned int a)
{
unsigned char data i, j;
do
{
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}while (--a);
}
void main()
{
while(1){
if(P3_0==0){
sleep(20);
while(P3_0==0);
sleep(20);
P2_1=~P2_1;
}
}
}
控制LED显示二进制
代码如下(相比于视频,没有引入新的变量且有减法运算且长按能一直进行运算)
#include <REGX52.h>
#include <INTRINS.h>
void sleep(unsigned int a)
{
unsigned char data i, j;
do
{
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}while (--a);
}
void main()
{
while(1){
//加法
if(P3_0==0){
sleep(20);
while(P3_0==0)
{
sleep(250);
if(P3_0==0){//还是零的话,就要开始猛猛加了
while(P3_0==0)
{
P2=~P2;
P2++;
P2=~P2;
sleep(100);
}
}
}
sleep(20);//按住没超过250ms
P2=~P2;
P2++;
P2=~P2;
}
//减法
if(P3_1==0){
sleep(20);
while(P3_1==0)
{
sleep(250);
if(P3_1==0){
while(P3_1==0)
{
P2=~P2;
P2--;
P2=~P2;
sleep(100);
}
}
}
sleep(20);
P2=~P2;
P2--;
P2=~P2;
}
}
}
独立按键控制LED位移
代码如下
#include <REGX52.h>
#include <INTRINS.h>
void sleep(unsigned int a)
{
unsigned char data i, j;
do
{
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}while (--a);
}
void main()
{
while(1){
//go left
if(P3_0==0){
sleep(20);
while(P3_0==0);
sleep(20);
P2=~P2;
if(P2==0){
P2=1;
}else{
P2=P2<<1;
}
P2=~P2;
}
if(P3_1==0){
sleep(20);
while(P3_1==0);
sleep(20);
P2=~P2;
if(P2==0){
P2=128;
}else{
P2=P2>>1;
}
P2=~P2;
}
}
}
数码管
介绍
单个数字是由8个二极管的亮灭来显示的
A
F B
G
E C
D DP
dp g f e d c b a对应一个8位二进制。
U: 0 0 1 1 1 1 1 0
-: 0 1 0 0 0 0 0 0
开发板中能’‘同时’‘显示8个数字。
但实际上,在一瞬间里只有一个数字是被点亮的。
之所以看上去同时点亮了多个,是因为点亮的数字切的很快。
决定哪个数字亮,通过3个引脚(P22-P24)表示三位二进制数,再解码(74LS38芯片)得到哪组二极管发光。
三位解码
P24x4+P23x2+P22x1+1=Loc
Loc为亮的LED组编码
八位控制亮灭
从各个引脚到二极管的正极,有一个74HC245芯片,用来增强高电平的驱动能力。对应单片机引脚为P00-P07。
静态数码管显示
构建了函数来使得任意位置显示任意数字
代码如下(与视频条件判断不同,是基于二进制的运算来确定二极管组号)
#include <REGX52.h>
#include <INTRINS.h>
char diction[10]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
void Nixie(unsigned char loc,char num)
{
loc--;
P2_2=loc%2;
P2_3=loc/2%2;
P2_4=loc/4%2;
P0=diction[num];
}
void main()
{
while(1)
{
Nixie(1,1);
}
}
动态数码管显示
如果调用上面的函数来同时显示多个数字会存在拖影的现象。
所以需要优化前面构建的函数
void Nixie(unsigned char loc,char num)
{
char diction[10]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
loc--;
P2_2=loc%2;
P2_3=loc/2%2;
P2_4=loc/4%2;
P0=diction[num];
sleep(1);//使其不会迅速归为全灭状态
P0=0x00;//归为全灭,之后再赋值时就不会拖影了
}
对应任务
代码中对数码管编号的定义与任务书上相反,
代码中从右到左是一到八。
代码如下
#include <REGX52.h>
#include <INTRINS.h>
void sleep(unsigned int a) //延时函数
{
unsigned char data i, j;
do
{
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}while (--a);
}
void Nixie(unsigned char loc,char num)//自定义显示函数 U=10 -=11
{
char diction[12]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x3E,0x40};
loc--;
P2_2=loc%2;
P2_3=loc/2%2;
P2_4=loc/4%2;
P0=diction[num];
sleep(1);
P0=0x00;//防止拖影
}
void united_output(char p7,char p2,char p1,char flag)//根据要求 格式化输出
{
Nixie(8,10);//U字符
Nixie(7,p7);
Nixie(1,p1);
Nixie(2,p2);
if(flag){//是否显示中间短横
Nixie(3,11);
Nixie(4,11);
}
}
int jud() //因为是在消抖后执行的jud()函数,这一步里面没有延时。
{
char a=0;
if(P3_0==0){
a=2;
}
if(P3_1==0){
a=1;
}
if(P3_2==0){
a=3;
}
if(P3_3==0){
a=4;
}
return a;
}
void xiaodou_danxianshi(char p7,char p2,char p1,char flag)//消抖的同时保持显示
{
short i,n=7;//因为united_output()函数的执行也需要一定时间,
//所以循环次数比20少。测试的时候如果n=10,按松的快一点就会没反应
for(i=0;i<n;i++){
sleep(1);
united_output(p7,p2,p1,flag);
}
}
//写了四个不同的函数来执行四个不同的模式
int mode1()
{
char flag=1;
while(1)
{
united_output(1,0,1,0);
if(P3_0==0 || P3_2==0 || P3_3==0){//如果其他按钮被按下
xiaodou_danxianshi(1,0,1,0);//消抖
flag=jud();//改变flag
while(P3_0==0 || P3_2==0 || P3_3==0)//在按住的时候也要保持显示
{
united_output(1,0,1,0);
}
xiaodou_danxianshi(1,0,1,0);
break;
}
}
return flag;//把新的flag传回到excute函数
}
int mode2()
{
char flag=2;
int tick=0;//计时的问题,由新变量tick来解决,tick记录已经循环了多少次
char num=0;//数字显示
while(1)
{
united_output(2,num/10,num%10,0);
if(tick==203){//具体tick多大再使其回到零,其实是拿秒表恰的x
tick=0;
num=(num+1)%11;
}
tick++;
if(P3_1==0 || P3_2==0 || P3_3==0){
xiaodou_danxianshi(2,num/10,num%10,0);
flag=jud();
while(P3_1==0 || P3_2==0 || P3_3==0)
{
united_output(2,num/10,num%10,0);
}
xiaodou_danxianshi(2,num/10,num%10,0);
break;
}
}
return flag;
}
int mode3()
{
char flag=3;
int tick=0;//同mode2用tick确定跑了几次了
char flag2=0;//是否点亮--
while(1)
{
united_output(3,0,3,flag2);
if(tick==96){//没对半分开,没有-- 和 有-- 执行耗时比约为2:3,循环次数比则约为3:2
flag2=1;
}else if(tick==160){
flag2=0;
tick=0;
}
tick++;
if(P3_1==0 || P3_0==0 || P3_3==0){
xiaodou_danxianshi(3,0,3,flag2);
flag=jud();
while(P3_1==0 || P3_0==0 || P3_3==0)
{
united_output(3,0,3,flag);
}
xiaodou_danxianshi(3,0,3,flag2);
break;
}
}
return flag;
}
int mode4()
{
char flag=4;
char a=0;
while(1)
{
united_output(4,a,a,0);
if(P3_3==0)
{
xiaodou_danxianshi(4,a,a,0);
while(P3_3==0){united_output(4,a,a,0);}
xiaodou_danxianshi(4,a,a,0);
a=(a+1)%10;
}
if(P3_1==0 || P3_0==0 || P3_2==0){
xiaodou_danxianshi(4,a,a,0);
flag=jud();
while(P3_1==0 || P3_0==0 || P3_2==0)
{
united_output(4,a,a,0);
}
xiaodou_danxianshi(4,a,a,0);
break;
}
}
return flag;
}
int excute(char flag)
{
if(flag==1){
flag=mode1();
}else if(flag==2){
flag=mode2();
}else if(flag==3){
flag=mode3();
}else if(flag==4){
flag=mode4();
}
return flag;//传回给主函数
}
void main()
{
int flag=0;//根据flag的值来决定执行哪个函数
while(1)
{
P0=0x00;
if(P3_1==0)
{
sleep(20);
while(P3_1==0);
sleep(20);
flag=1;
}
if(P3_0==0)
{
sleep(20);
while(P3_0==0);
sleep(20);
flag=2;
}
if(P3_2==0)
{
sleep(20);
while(P3_2==0);
sleep(20);
flag=3;
}
if(P3_3==0)
{
sleep(20);
while(P3_3==0);
sleep(20);
flag=4;
}
flag=excute(flag);//将flag交给执行函数,进入对应mode函数,当mode切换时再将新的flag传回来。
}
}