在经过了一段时间的C51课程学习后,使我更系统的认识了单片机的主要结构,本篇文章主要是将我所学习到的内容进行一个整理,并结合中断控制法这个综合性的任务尽量对每一个部分进行讲解,希望对大家的学习能够有一点帮助,文章内如有出错还请指正。
目录
一、前期准备工作
1、硬件选择
仿真软件中:单片机:AT89C51
数码管驱动:74ls47
数码管:7SEG-MPX2-CA
实体硬件:单片机:STC89C52RC
下载器:USB转TTL下载器
由于上课时所选择的单片机为宏晶科技的STC89C52RC,但仿真软件中只有AT系列单片机,故在仿真时选择AT89C51,编译仿真效果相同。
2、软件选择
编程软件:keil5
仿真软件:Proteus 8.9
下载器:STC-ISP(用于实物烧写程序,仅仿真则可不用)
二、仿真搭建
1、Proteus工程搭建及器件选择
打开安装好的Proteus ,点击新建工程
工程名称可以自行定义,并选择好工程文件存放路径,然后一直按下一步,设置均为默认即可
进入工程之后点击P选取所需元器件
左侧搜索栏搜索元器件(这里搜索的是AT89C51),中间可以选中器件,右侧有对应原理图以及封装图,选中后点击确定
依次添加好以下器件
右侧一栏选择终端模式,则可以选择调用电源以及接地,双击图标可以编辑字符
2、复位电路及外部时钟电路
(1)复位电路:
复位电路即初始化电路,其作用是将运行中的单片机恢复到初始状态,相当于重置运行状态
复位的触发要求,输入端RST至少保持两个机器周期为高电平(当晶振为12M时机器周期为1us)
(2)外部时钟电路:
该电路中,电容的选择由晶振决定,具体容量需参照技术手册选择,此处晶振的值为12MHz,单片机内部也有时钟电路,具体选择看个人需求
在仿真中可以不使用外部晶振,双击单片机在其设置中可以设置晶振的值
2、LED灯部分
这里设计红绿灯的引脚由P1组引脚控制,且为了方便,设计上选择南-北、东-西联动
对应关系为:
引脚 | 方位 | led灯灯色 | 方位 | led灯灯色 |
P1.0 | 北 | 绿 | 南 | 绿 |
P1.1 | 北 | 黄 | 南 | 黄 |
P1.2 | 北 | 红 | 南 | 红 |
P1.3 | 东 | 绿 | 西 | 绿 |
P1.4 | 东 | 黄 | 西 | 黄 |
P1.5 | 东 | 红 | 西 | 红 |
3、按键部分
选用P3.3引脚连接按键
4、数码管部分
选用P2组引脚进行控制,由高四位(P2.4-P2.7)输出十六进制数,并通过74LS47译码器转换为二进制数据并通过QA-QG控制数码管进行显示,P2.0与P2.1控制数码管两位数的显示切换
十六进制数 | P2对应二进制 | 数码管显示数字 | 十六进制数 | P2对应二进制 | 数码管显示数字 |
0x0d | 0000 1101 | 00 | 0x0e | 0000 1110 | 00 |
0x1d | 0001 1101 | 01 | 0x1e | 0001 1110 | 10 |
0x2d | 0010 1101 | 02 | 0x2e | 0010 1110 | 20 |
0x3d | 0011 1101 | 03 | 0x3e | 0011 1110 | 30 |
0x4d | 0100 1101 | 04 | 0x4e | 0100 1110 | 40 |
0x5d | 0101 1101 | 05 | 0x5e | 0101 1110 | 50 |
0x6d | 0110 1101 | 06 | 0x6e | 0110 1110 | 60 |
0x7d | 0111 1101 | 07 | 0x7e | 0111 1110 | 70 |
0x8d | 1000 1101 | 08 | 0x8e | 1000 1110 | 80 |
0x9d | 1001 1101 | 09 | 0x9e | 1001 1110 | 90 |
5、完整仿真图
三、程序编写
1、工程搭建
打开KEIL5,选择Project→New uVision Project...新建工程
依次选择Legacy Device Database [no RTE]→Microchip→AT98C51
新建文件,并引用头文件,点击保存,命名需要带上后缀.C
随后双击Source Group 1 ,找到刚才保存的路径,双击选择添加main.c文件,添加完成后点击编译
编译完成后可以看到main文件下已经引用了reg51.h文件
2、定义所需变量
数码管显示数据、控制数据及其他变量
unsigned char seg[10]={0x00,0x10,0x20,0x30,0x40,0x50,0x60,0x70,0x80,0x90};//十六进制码,对应0-9,用于数码管显示,注意该处使用的数码管为共阳极数码管,并且有74ls47译码器作为驱动,此处传输的高位数据对应输出口为P2.4-P2.7,一位十六进制转四位二进制码,参考8421编码,如 0x80 等同于 1000 0000B(B代表二进制码) 从左往右位次依次降低,对应P2.7-p2.0共八个端口
unsigned char con[2]={0x0d,0x0e};//对应数码管一二位码,低电平有效,即P2.1和P2.0
unsigned char time[3];//定义time[0]、time[1]、time[2],三个数组,前两个用于存放倒计时个位与十位的数值,第三个用于存放定义的倒计时时间,以便一个计时循环结束后重新装载数值
unsigned int j,i,come=0,ss=10;//i,j分别用于两个独立的循环自装载数值,come用于定时器中断,辅助以计时,ss为设置倒计时的计时时间
bit start=0;//定义一个一字节的变量,用于按键中断的判断储存
红绿灯控制引脚
sbit NB_G=P1^0; //定义变量用于存放对应控制红绿灯的引脚,便于后面引用
sbit NB_Y=P1^1; //分别为南北(NB)方向的绿灯(G)、黄灯(Y)、红灯(R)
sbit NB_R=P1^2; //东西(DX)方向的绿灯(G)、黄灯(Y)、红灯(R)
sbit DX_G=P1^3;
sbit DX_Y=P1^4;
sbit DX_R=P1^5;
3、中断源
C51中共有5个中断源,中断源:INT0(外部中断0),INT1(外部中断1),T0(定时器0),T1(定时器1),RXD和TXD(同属串口中断)
关于中断可参考:(C51学习四)外部中断和定时器中断_用外部中断int0触发 关闭定时器-CSDN博客
此处使用的是 T1(定时器1)与 INT1(外部中断1)
(1)定时器设置
此处使用的是定时器1进行中断(即T1)
TMOD=1; //打开内部定时器1
TH0=-20000>>8; //此处用于装载内部计时器的计数量,12M晶振时相当于计时20ms,及20ms触发一次内部中断,由于一次无法计时过长时间,故配合come进行多次计数以达到较长时间的计时
TL0=-20000; //具体使用方法请参考51单片机计时器的使用一节内容
EA=1;TR0=1;ET0=1; //开启计时器1中断功能以及设置,用于内部计时
EX1=1;IT1=1; //开启外部中断1用于读取外部按键是否按下
void ist_time0(void) interrupt 1 //定时器触发内部中断后的操作
{
TH0=-20000>>8; //重新装载内部计时器的数值
TL0=-20000;
}
(2)按键中断控制开始与停止
此处的中断是由P3.3(即INT1-外部中断1)进行判断
void ist_key(void) interrupt 2 //外部中断(按键按下)触发后内部的操作
{
start=!start; //将start的状态反转,由于只有一个字节,故只有0和1两种状态,初始转状态为0,第一次按下后反转为1,以此类推
}
4、红绿灯控制部分
if(ss==0) // 5 内部倒计时为0秒,应输出数值为0,但此处不能让0出现,要直接跳过进入到下一个循环
{
DX_Y=!DX_Y; //东西方向黄灯灭
NB_R=!NB_R; //南北方向红灯灭
ss=time[2]; //将初始值重新赋给ss
}
if(ss==3) // 4 内部倒计时3秒,外部倒计时显3秒时
{
DX_G=!DX_G; //东西方向绿灯灭
DX_Y=!DX_Y; //东西方向黄灯亮
}
if(ss==time[2]/2) // 3 部倒计时10秒,外部倒计时显10秒时,第一次倒计时结束,开始第二次倒计时
{
NB_Y=!NB_Y; //南北方向黄灯灭
NB_R=!NB_R; //南北方向红灯亮
DX_G=!DX_G; //东西方向绿灯亮
DX_R=!DX_R; //东西方向红灯灭
}
if(ss==time[2]/2+3) // 2 内部倒计时13秒,外部倒计时显示3秒时
{
NB_G=!NB_G; //控制南北方向绿灯的端口状态反转,及由0转变为1,此时灯灭
NB_Y=!NB_Y; //南北方向黄灯亮
}
if(ss==time[2]) // 1 初始值为装载数值,故灯的变化规律从此处开始并往上 ↑
{ //此处以倒计时10秒做示例,内部倒计时数值为20,显示为10,显示时将数值砍半后输出,操作在后面
NB_G=!NB_G; //控制南北方向绿灯的端口状态反转,及由1转变为0,此时灯亮
DX_R=!DX_R; //东西方向红灯亮
}
5、数码管控制部分
if(ss>time[2]/2) //当ss数值大于10时(10-20间)
{
time[0]=(ss-(time[2]/2))/10; //减去10,然后除以10取十位的数值并存放
time[1]=(ss-(time[2]/2))%10; //减去10,然后除以10取余,取个位的数值并存放
}
else //否则,即当ss数值小于10时(1-10间)
{
time[0]=ss/10; //直接除以10取十位的数值并存放
time[1]=ss%10; //直接除以10取余,取个位的数值并存放
}
ss--; //通过以上判断及取值赋值以后,ss数值减1,实现每秒减1
}//操作结束
for(i=0;i<2;i++) //此处在计数范围外,故是每20ms中断一次的时候都执行一次
{ //由于本程序使用的是数码管的动态显示,需要考虑刷新率问题,20ms正好为每秒50Hz刷新率,高于25HZ有效避免了闪烁问题
P2=seg[time[i]]|con[i]; //通过i的倒计时同时控制二数码管输出个位与十位的效果,本处通过或操作,将P2的高位和地位的有效数值相合并(1为有效,或操作保留1,与操作保留0),随后输出给74ls47驱动芯片和数码管,以控制数码管显示数字
for(j=500;j>0;j--); //软件延时,不同于计时器中断延时,此处通过j的自减使整个程序在此处执行j的减法运算,以稍微拉长数码管的显示时间,与上面的操作一起达到视觉暂留效果,但此处数值要合适,过长会拉低显示频率造成闪烁,过短亮度无法被人眼看到,造成无法显示
}
四、最终效果
五、完整工程链接
GitCode仓库:码路的小丘 / 【C51单片机应用】中断法倒计时红绿灯 · GitCode