51单片机课程设计课题(通用6篇)
篇1:51单片机课程设计课题
重邮专用
51单片机课程设计报告
学院:
专业班级:姓名:
指导教师:
设计时间:
51单片机课程设计
一、设计任务与要求
1.任务:制作并调试51单片机学习板 2.要求:
(1)了解并能识别学习板上的各种元器件,会读元器件标示;(2)会看电路原理图;
(3)制作51单片机学习板;
(4)学会使用Keil C软件下载调试程序;
用调试程序将51单片机学习板调试成功。
二、总原理图及元器件清单
1.总原理图
要求:用铅笔在A4纸整页绘制
2.元件清单
三、模块电路分析
1.最小系统:
单片机最小系统电路分为振荡电路和复位电路,振荡电路选用 12MHz 高精度晶振, 振荡电容选用 22p和30p 独石电容;
晶振为单片机提供时钟激励,保证单片机内部和外部电路的时序逻辑电路协调动作,课程中使用的是12M的晶振,可以产生每秒12M频率的激
励。而构成振荡回路的俩个电容为负载电容,可以影响晶振的谐振频率和振荡幅度。
图 1图
2复位电路使用 RC 电路,使用普通的电解电容与金属膜电阻即可;
图
3当单片机上电瞬间由于电容电压不能突变会使电容两边的电位相同,此时RST为高电平,之后随着时间推移电源负极通过电阻对电容放电,放完电时RST为低电平。正常工作为低电平,高电平复位。(分析振荡电路:测振荡频率; 分析复位电路:高或低电平复位?)
2.显示模块:
发光二极管显示电路:
Usb的为系统加电时,power的发光二极管处于高电位,发光。当程序控制其余四个脚的电位为低电位,输出端口为高电位,剩下的四个发光二极管发光
图
4数码管显示电路
本课程使用数码管显示状态为静态显示。静态显示就是显示驱动电路具有
输出锁存功能。单片机将所要显示的数据输出后,数码管显示数据不变。Cpu不再控制led。静态显示的接口电路采用一个并行口接一个数码管。数码管的公共端按共阴极或共阳极分别接地或
VCC
图
5四、硬件调试
1、是否短路
用万用表检查P2两端是短路。电阻为0,则短路,电阻为一适值,电路正常。
2、焊接顺序
焊接的顺序很重要,按功能划分的器件进行焊接,顺序是功能部件的焊接--调试--另一功能部件的焊接,这样容易找到问题的所在。
3、器件功能
1)检查原理图连接是否正确
2)检查原理图与PCB图是否一致
3)检查原理图与器件的DATASHEET上引脚是否一致 4)用万用表检查是否有虚焊,引脚短路现象
5)查询器件的DATASHEET,分析一下时序是否一致,同时分析一下命令字是否正确
6)通过示波器对芯片各个引脚进行检查,检查地址线是否有信号的7)飞线。用别的的口线进行控制,看看能不能对其进行正常操作,多试验,才能找到问题出现在什么地方。
六、软件调试
1、设置硬件仿真环境
单片机应用系统程序的编译和仿真在KeilμVision环境下进行,在调试程序之前,需要对工程进行Debug设置,选择软件仿真或硬件仿真。软件仿真使用计算机来模拟程序的运行,不需要建立硬件平台就可以快速得到某些运行结果;硬件仿真是最准确的仿真方法,必须建立硬件平台,通过PC机→硬件仿真器→用户目标系统进行系统调试。采用硬件仿真的方法,硬件平台即为带有图1所示接口电路的单片机应用系统,设置硬件仿真环境的具体操作步骤如下:
首先,点击所建工程:Project菜单中的Options for Target„Targer 1‟,出现工程的配置窗口,点击Debug设置,选择KeilMcmitor-51 Driver,具体参数设置如图6所示。
图 6
然后,设置仿真器参数。建议波特率设置范围300~38 400。为避免程序中的中断和Keil硬件仿真环境中的中断互相冲突,不选择“Stop ProgramExecution with SerialInterrupt”。仿真器参数的设置如图7所示。
图7
完成51单片机在Keil μVision环境中的硬件仿真环境设置后,可以进行程序的调试仿真。
2、调试仿真
1)导入测试代码:文件→打开→key and display.Uv2 2)重建全部工程:工程→重建全部目标文件
重建结果为,“DA_5615” – 0 Error(s), 0 Warning(s).3)调试:调试→Start/Stop Debug session(Ctrl + F5)
调试结果为:Connected to Monitor_51 V3.4Load “C:......DA_56511、详细描述软件调试步骤。及各模块调试结果。
2、详细描述调试过程中出现的故障现象,并作故障分析,及解决方法
七、心得
自己写啦~~
篇2:51单片机课程设计课题
题:
基于51单片机数字温度计设计
专
业:
电子信息工程
班
级:
班
学
号:
姓
名:
指导教师:
设计日期:
成绩:
XX大学XX学院电气学院
基于51单片机数字温度计设计
一、设计目的1、掌握单片机电路的设计原理、组装与调试方法。
2、掌握LED数码显示电路的设计和使用方法。
3、掌握DS18B20温度传感器的工作原理及使用方法。
二、设计要求
1、本次单片机课程设计要求以51系列单片机为核心,以开发板为平台。
2、设计一个数字式温度计,要求使用DS18B20温度传感器测量温度。
3、经单片机处理后,要求用4位一体共阴LED数码管来设计显示电路,以显示测量的温度值。
4、另外还要求在设计中加入报警系统,如果我们所设计的系统用来监控某一设备,当设备的温度超过或低于我们所设定的温度值时,系统会产生报警。
5、要求在设计中加入上下限警报温度设置电路。
三、设计的具体实现
1数字温度计设计的方案
在做数字温度计的单片机电路中,对信号的采集电路大多都是使用传感器,这是非常容易实现的,所以可以采用一只温度传感器DS18B20,此传感器,可以很容易直接读取被测温度值,进行转换,就可以满足设计要求。采集之后,通过使用51系列的单片机,可以对数据进行相应的处理,再由LED显示电路对其数据进行显示。
2系统设计框图
温度计电路设计总体设计方框图如下图所示,控制器采用单片机AT89C51,温度传感器采用DS18B20,用4位一体共阴LED数码管以串口传送数据实现温度显示。此外,还添加了报警系统,对温度实施监控。
3主控器AT89C51芯片
对于单片机的选择,可以考虑使用8031与8051系列,由于8031没有内部RAM,系统又需要大量内存存储数据,因而不适用。AT89C51
以低价位单片机可为提供许多高性价比的应用场合,可灵活应用于各种控制领域,对于简单的测温系统已经足够。单片机AT89C51具有低电压供电和体积小等特点,四个端口只需要两个口就能满足电路系统的设计需要该器件是INTEL公司生产的MCS一5l系列单片机中的基础产品,采用了可靠的CMOS工艺制造技术,具有高性能的8位单片机,属于标准的MCS—51的CMOS产品。
AT8951的管脚如下图所示:
AT89C51芯片管脚图
4时钟电路
80C51时钟有两种方式产生,即内部方式和外部方式。80C51中有一个构成内部震荡器的高增益反向放大器,引脚XTAL1和XTAL2分别是该放大器的输入端和输出端。本次采用内部震荡电路,瓷片电容采用22PF,晶振为12MHZ。
晶体震荡电路图
复位电路
单片机系统的复位电路在这里采用的是上电+按钮复位电路形式,其中电阻R采用10KΩ的阻值,电容采用10μF的电容值。
复位电路
温度传感电路
DALLAS
最新单线数字温度传感器DS18B20是一种新型的“一线器件”,其体积更小、更适用于多种场合、且适用电压更宽、更经济。DALLAS
半导体公司的数字化温度传感器DS18B20是世界上第一片支持“一线总线”接口的温度传感器。温度测量范围为-55~+125
摄氏度,可编程为9位~12
位转换精度,测温分辨率可达0.0625摄氏度,分辨率设定参数以及用户设定的报警温度存储在EEPROM
中,掉电后依然保存。被测温度用符号扩展的16位数字量方式串行输出。
DS18B20内部结构主要由四部分组成:64位光刻ROM、温度传感器、非挥发的温度报警触发器TH和TL、配置寄存器。DS18B20的管脚排列、各种封装形式,DQ
为数据输入/输出引脚。开漏单总线接口引脚。当被用着在寄生电源下,也可以向器件提供电源;GND为地信号;VDD为可选择的VDD引脚。当工作于寄生电源时,此引脚必须接地,如下图所示。
DS18B20管脚图
显示电路
对于数字温度的显示,我们采用4位一体共阴LED数码管。足够显示0~100中各位数,并且还能显示一位小数部分。
4位LED数码显示管
温度报警电路
对于数字温度计的设计,除了温度的数字显示功能外还加入了报警系统,当测量的温度超过或低于我们所设定的温度值时,系统会产生报警并亮红灯报警。
其电路图如下所示。
蜂鸣器红灯报警系统电路图
源程序:
/********************************************************************
*
程序名;
基于51单片机的温度计
*
功
能:
实时测量温度,超过上下限报警,报警温度可手动调整。K1是用来
*
进入上下限调节模式的,当按一下K1进入上限调节模式,再按一下进入下限
*
调节模式。在正常模式下,按一下K2进入查看上限温度模式,显示1s左右自动
*
退出;按一下K3进入查看下限温度模式,显示1s左右自动退出;按一下K4消除
*
按键音,再按一下启动按键音。在调节上下限温度模式下,K2是实现加1功能,*
K1是实现减1功能,K3是用来设定上下限温度正负的。
*********************************************************************/
#include
#include
#define
uint
unsigned
int
#define
uchar
unsigned
char
uchar
max=0x00,min=0x00;
//max是上限报警温度,min是下限报警温度
bit
s=0;
//s是调整上下限温度时温度闪烁的标志位,s=0不显示200ms,s=1显示1s左右
bit
s1=0;
//s1标志位用于上下限查看时的显示
void
display1(uint
z);
#include“ds18b20.h“
#include“keyscan.h“
#include“display.h“
/******************************************************/
/*
主函数
/
/*****************************************************/
void
main()
{
beer=1;
//关闭蜂鸣器
led=1;
//关闭LED灯
timer1_init(0);
//初始化定时器1(未启动定时器1)
get_temperature(1);
//首次启动DS18B20获取温度(DS18B20上电后自动将EEPROM中的上下限温度复制到TH和TL寄存器)
while(1)
{
keyscan();
get_temperature(0);
display(temp,temp_d*0.625);
alarm();
}
}
/**********************************************************************
*
程序名;
ds18b20数码管动态显示头文件
*
功
能:
通过定时器0延时是数码管动态显示
**********************************************************************/
#ifndef
__ds18b20_display_H__
#define
__ds18b20_display_H__
#define
uint
unsigned
int
//变量类型宏定义,用uint表示无符号整形(16位)
#define
uchar
unsigned
char
//变量类型宏定义,用uchar表示无符号字符型(8位)
sbit
wei1=P2^4;
//可位寻址变量定义,用wei1表示P2.4口
sbit
wei2=P2^5;
//用wei2表示P2.5口
sbit
wei3=P2^6;
//用wei3表示P2.6口
sbit
wei4=P2^7;
//用wei4表示P2.7口
uchar
num=0;
//定义num为全局无符号字符型变量,赋初值为‘0’
uchar
code
temperature1[]={
0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
//定义显示码表0~9
uchar
code
temperature2[]={
0xbf,0x86,0xdb,0xcf,0xe6,0xed,0xfd,0x87,0xff,0xef};
//带小数点的0~9.uchar
code
temperature3[]={
0x00,0x80,0x40,0x76,0x38};//依次是‘不显示’‘.’‘-’‘H’‘L’
/******************************************************/
/
延时子函数
/
/*****************************************************/
void
display_delay(uint
t)
//延时1ms左右
{
uint
i,j;
for(i=t;i>0;i--)
for(j=120;j>0;j--);
}
/******************************************************/
/*
定时器1初始化函数
/
/*****************************************************/
void
timer1_init(bit
t)
{
TMOD=0x10;
TH0=0x3c;
TL0=0xb0;
EA=1;
ET1=1;
TR1=t;
//
局部变量t为1启动定时器1,为0关闭定时器1
}
/******************************************************/
/*
定时器1中断函数
/
/*****************************************************/
void
timer1()
interrupt
{
TH0=0x3c;
//重新赋初值,定时50ms
TL0=0xb0;
num++;
//每进入一次定时器中断num加1(每50ms加1一次)
if(num<5)
{s=1;if(w==1){beer=1;led=1;}else{beer=1;led=1;}}
else
//进入4次中断,定时200ms时若报警标志位w为‘1’则启动报警,不为‘1’不启动
//实现间歇性报警功能
{s=0;if(w==1){beer=0;led=0;}else{beer=1;led=1;}}
if(num>20)
//进入20次中断,定时1s
{
num=0;
//num归0,重新定开始定时1s
s1=0;
//定时1s时间到时自动关闭报警上下限显示功能
v1=1;
//定时1s时间到时自动关闭报警上下限查看功能
}
}
/******************************************************/
/*
调整报警上下限选择函数
/
/*****************************************************/
void
selsct_1(uchar
f,uchar
k)
//消除百位的0显示,及正负温度的显示选择
{
if(f==0)
//若为正温度,百位为0则不显示百位,不为0则显示
{
if(k/100==0)
P0=temperature3[0];
else
P0=temperature1[k/100];
}
if(f==1)
//若为负温度,若十位为0,百位不显示,否则百位显示‘-’
{
if(k%100/10==0)
P0=temperature3[0];
else
P0=temperature3[2];
}
}
void
selsct_2(bit
f,uchar
k)
//消除十位的0显示,及正负温度的显示选择
{
if(f==0)
//若为正温度,百位十位均为0则不显示十位,否则显示十位
{
if((k/100==0)&&(k%100/10==0))
P0=temperature3[0];
else
P0=temperature1[k%100/10];
}
if(f==1)
//若为负温度,若十位为0,十位不显示,否则十位显示‘-’
{
if(k%100/10==0)
P0=temperature3[2];
else
P0=temperature1[k%100/10];
}
}
/******************************************************/
/*
主函数显示
/
/*****************************************************/
void
display(uchar
t,uchar
t_d)
//用于实测温度、上限温度的显示
{
uchar
i;
for(i=0;i<4;i++)
//依次从左至右选通数码管显示,实现动态显示
{
switch(i)
{
case
0:
//选通第一个数码管
wei2=1;
//关第二个数码管
wei3=1;
//关第三个数码管
wei4=1;
//关第四个数码管
wei1=0;
//开第一个数码管
if(a==0){selsct_1(f,t);}
//若a=0则在第一个数码管上显示测量温度的百位或‘-’
if(a==1)
{
P0=temperature3[3];
//若a=1则在第一个数码管上显示‘H’
}
if(a==2)
{
P0=temperature3[4];
//若a=2则在第一个数码管上显示‘L’
}
break;
case
1:
//选通第二个数码管
wei1=1;
wei3=1;
wei4=1;
wei2=0;
if(a==0){selsct_2(f,t);}
//若a=0则在第二个数码管上显示测量温度的十位或‘-’
if(a==1)
//若a=1则在第二个数码管上显示上限报警温度的百位或‘-’
{
if(s==0)
selsct_1(f_max,max);//若s=0则显示第二个数码管,否则不显示
else
P0=temperature3[0];
//通过s标志位的变化实现调节上下限报警温度时数码管的闪烁
if(s1==1)
selsct_1(f_max,max);//若s1=1则显示第二个数码管(s1标志位用于上下限查看时的显示)
}
if(a==2)
//若a=2则在第二个数码管上显示下限报警温度的百位或‘-’
{
if(s==0)
selsct_1(f_min,min);
else
P0=temperature3[0];
if(s1==1)
selsct_1(f_min,min);
}
break;
case
2:
//选通第三个数码管
wei1=1;
wei2=1;
wei4=1;
wei3=0;
if(a==0){P0=temperature2[t%10];}//若a=0则在第三个数码管上显示测量温度的个位
if(a==1)
//若a=1则在第三个数码管上显示上限报警温度的十位或‘-’
{
if(s==0)
selsct_2(f_max,max);//若s=0则显示第三个数码管,否则不显示
else
P0=temperature3[0];
if(s1==1)
selsct_2(f_max,max);//若s1=1则显示第三个数码管
}
if(a==2)
//若a=2则在第三个数码管上显示下限报警温度的十位或‘-’
{
if(s==0)
selsct_2(f_min,min);
else
P0=temperature3[0];
if(s1==1)
selsct_2(f_min,min);
}
break;
case
3:
//选通第四个数码管
wei1=1;
wei2=1;
wei3=1;
wei4=0;
if(a==0){P0=temperature1[t_d];}//若a=0则在第四个数码管上显示测量温度的小数位
if(a==1)
//若a=1则在第四个数码管上显示上限报警温度的个位
{
if(s==0)
P0=temperature1[max%10];//若s=0则显示第四个数码管,否则不显示
else
P0=temperature3[0];
if(s1==1)
P0=temperature1[max%10];//若s1=1则显示第四个数码管
}
if(a==2)
//若a=2则在第四个数码管上显示下限报警温度的个位
{
if(s==0)
P0=temperature1[min%10];
else
P0=temperature3[0];
if(s1==1)
P0=temperature1[min%10];
}
break;
}
display_delay(10);
//每个数码管显示3ms左右
}
}
/******************************************************/
/*
开机显示函数
/
/*****************************************************/
void
display1(uint
z)
//用于开机动画的显示
{
uchar
i,j;
bit
f=0;
for(i=0;i //‘z’是显示遍数的设定 { for(j=0;j<4;j++) //依次从左至右显示‘-’ { switch(j) { case 0: wei2=1; wei3=1; wei4=1; wei1=0; break; P0=temperature3[2];//第一个数码管显示 case 1: wei1=1; wei3=1; wei4=1; wei2=0;break; P0=temperature3[2];//第二个数码管显示 case 2: wei1=1; wei2=1; wei4=1; wei3=0;break; P0=temperature3[2];//第三个数码管显示 case 3: wei1=1; wei2=1; wei3=1; wei4=0;break; P0=temperature3[2];//第四个数码管显示 } display_delay(400); //每个数码管显示200ms左右 } } } #endif /******************************************************************** * 程序名; DS18B20头文件 * 说 明:用到的全局变量是:无符号字符型变量temp(测得的温度整数部分),temp_d * (测得的温度小数部分),标志位f(测量温度的标志位‘0’表示“正温度”‘1’表 * 示“负温度”),标志位f_max(上限温度的标志位‘0’表示“正温度”、‘1’表 * 示“负温度”),标志位f_min(下限温度的标志位‘0’表示“正温度”、‘1’表 * 示“负温度”),标志位w(报警标志位‘1’启动报警‘0’关闭报警)。 *********************************************************************/ #ifndef __ds18b20_h__ //定义头文件 #define __ds18b20_h__ #define uint unsigned int #define uchar unsigned char sbit DQ= P2^3; //DS18B20接口 sbit beer=P1^0; //用beer表示P1.0 sbit led=P1^1; //用led表示P1.1 uchar temp=0; //测量温度的整数部分 uchar temp_d=0; //测量温度的小数部分 bit f=0; //测量温度的标志位,0’表示“正温度” ‘1’表示“负温度”) bit f_max=0; //上限温度的标志位‘0’表示“正温度” ‘1’表示“负温度”) bit f_min=0; //下限温度的标志位‘0’表示“正温度”、‘1’表示“负温度”) bit w=0; //报警标志位‘1’启动报警‘0’关闭报警 /******************************************************/ /* 延时子函数 / /*****************************************************/ void ds18b20_delayus(uint t) //延时几μs { while(t--); } void ds18b20_delayms(uint t) //延时1ms左右 { uint i,j; for(i=t;i>0;i--) for(j=120;j>0;j--); } /******************************************************/ /* DS18B20初始化函数 / /*****************************************************/ void ds18b20_init() { uchar c=0; DQ=1; DQ=0; //控制器向DS18B20发低电平脉冲 ds18b20_delayus(80); //延时15-80μs DQ=1; //控制器拉高总线,while(DQ); //等待DS18B20拉低总线,在60-240μs之间 ds18b20_delayus(150); //延时,等待上拉电阻拉高总线 DQ=1; //拉高数据线,准备数据传输; } /******************************************************/ /* DS18B20字节读函数 / /*****************************************************/ uchar ds18b20_read() { uchar i; uchar d=0; DQ = 1; //准备读; for(i=8;i>0;i--) { d >>= 1; //低位先发; DQ = 0; _nop_(); _nop_(); DQ = 1; //必须写1,否则读出来的将是不预期的数据; if(DQ) //在12us处读取数据; d |= 0x80; ds18b20_delayus(10); } return d; //返回读取的值 } /******************************************************/ /* DS18B20字节写函数 / /*****************************************************/ void ds18b20_write(uchar d) { uchar i; for(i=8;i>0;i--) { DQ=0; DQ=d&0x01; ds18b20_delayus(5); DQ=1; d >>= 1; } } /******************************************************/ /* 获取温度函数 / /*****************************************************/ void get_temperature(bit flag) { uchar a=0,b=0,c=0,d=0; uint i; ds18b20_init(); ds18b20_write(0xcc); //向DS18B20发跳过读ROM命令 ds18b20_write(0x44); //写启动DS18B20进行温度转换命令,转换结果存入内部RAM if(flag==1) { display1(1); //用开机动画耗时 } else ds18b20_delayms(1); ds18b20_init(); ds18b20_write(0xcc); ds18b20_write(0xbe); a=ds18b20_read(); //读内部RAM (LSB) b=ds18b20_read(); //读内部RAM (MSB) if(flag==1) //局部位变量f=1时读上下线报警温度 { max=ds18b20_read(); //读内部RAM (TH) min=ds18b20_read(); //读内部RAM (Tl) } if((max&0x80)==0x80) //若读取的上限温度的最高位(符号位)为‘1’表明是负温度 {f_max=1;max=(max-0x80);} //将上限温度符号标志位置‘1’表示负温度,将上限温度装换成无符号数。 if((min&0x80)==0x80)//若读取的下限温度的最高位(符号位)为‘1’表明是负温度 {f_min=1;min=(min-0x80);} //将下限温度符号标志位置‘1’表示负温度,将下限温度装换成无符号数。 i=b; i>>=4; if (i==0) { f=0; //i为0,正温度,设立正温度标记 temp=((a>>4)|(b<<4)); //整数部分 a=(a&0x0f); temp_d=a; //小数部分 } else { f=1; //i为1,负温度,设立负温度标记 a=~a+1; b=~b; temp=((a>>4)|(b<<4)); //整数部分 a=(a&0x0f); //小数部分 temp_d=a; } } /******************************************************/ /* 存储极限温度函数 / /*****************************************************/ void store_t() { if(f_max==1) //若上限温度为负,将上限温度转换成有符号数 max=max+0x80; if(f_min==1) //若下限温度为负,将上限温度转换成有符号数 min=min+0x80; ds18b20_init(); ds18b20_write(0xcc); ds18b20_write(0x4e); //向DS18B20发写字节至暂存器2和3(TH和TL)命令 ds18b20_write(max); //向暂存器TH(上限温度暂存器)写温度 ds18b20_write(min); //向暂存器TL(下限温度暂存器)写温度 ds18b20_write(0xff); //向配置寄存器写命令,进行温度值分辨率设置 ds18b20_init(); ds18b20_write(0xcc); ds18b20_write(0x48); //向DS18B20发将RAM中2、3字节的内容写入EEPROM } //DS18B20上电后会自动将EEPROM中的上下限温度拷贝到TH、TL暂存器 /******************************************************/ /* 温度超限报警函数 / /*****************************************************/ void alarm() { //若上限值是正值 if(f_max==0) { if(f_min==0) //若下限值是正值 { if(f==0) //若测量值是正值 { if(temp<=min||temp>=max) {w=1;TR1=1;} //当测量值小于最小值或大于最大值时报警 if((temp {w=0;} //当测量值大于最小值且小于最大值时不报警 } if(f==1){w=1;TR1=1;} //若测量值是负值时报警 } if(f_min==1) //若下限值是负值 { if(f==0) //若测量值是正值 { if(temp>=max)//当测量值大于最大值时报警 {w=1;TR1=1;} if(temp {w=0;} } if(f==1) //若测量值是负值 { if(temp>=min)//当测量值大于最小值时报警 {w=1;TR1=1;} if(temp {w=0;} } } } if(f_max==1) //若下限值是负值 { if(f_min==1) //若下限值是负值 { if(f==1) //若测量值是负值 { if((temp<=max)||(temp>=min)) {w=1;TR1=1;} //当测量值小于最大值或大于最小值时报警 if((temp {w=0;} //当测量值小于最小值且大于最大值时不报警 } if(f==0){w=1;TR1=1;} //若测量值是正值时报警 } } } #endif /******************************************************************** * 程序名; 基于51单片机的温度计 * 功 能: 实时测量温度,超过上下限报警,报警温度可手动调整。K1是用来 * 进入上下限调节模式的,当按一下K1进入上限调节模式,再按一下进入下限 * 调节模式。在正常模式下,按一下K2进入查看上限温度模式,显示1s左右自动 * 退出;按一下K3进入查看下限温度模式,显示1s左右自动退出;按一下K4消除 * 按键音,再按一下启动按键音。在调节上下限温度模式下,K2是实现加1功能,* K1是实现减1功能,K3是用来设定上下限温度正负的。 *********************************************************************/ #include #include //将intrins.h头文件包含到主程序(调用其中的_nop_()空操作函数延时) #define uint unsigned int #define uchar unsigned char uchar max=0x00,min=0x00; //max是上限报警温度,min是下限报警温度 bit s=0; //s是调整上下限温度时温度闪烁的标志位,s=0不显示200ms,s=1显示1s左右 bit s1=0; //s1标志位用于上下限查看时的显示 void display1(uint z); //声明display1()函数(display.h头文件中的函数,ds18b20.h要用应先声明) #include“ds18b20.h“ #include“keyscan.h“ #include“display.h“ /******************************************************/ /* 主函数 / /*****************************************************/ void main() { beer=1; //关闭蜂鸣器 led=1; //关闭LED灯 timer1_init(0); //初始化定时器1(未启动定时器1) get_temperature(1); //首次启动DS18B20获取温度(DS18B20上电后自动将EEPROM中的上下限温度复制到TH和TL寄存器) while(1) { keyscan(); get_temperature(0); display(temp,temp_d*0.625); alarm(); } } /********************************************************************** * 程序名; ds18b20keyscan函数 * 功 能: 通过键盘设定设定上下限报警温度 **********************************************************************/ #ifndef __keyscan_H__ //定义头文件 #define __keyscan_H__ sbit key1=P2^2; sbit key2=P2^1; sbit key3=P2^0; sbit key4=P3^3; uchar i=0; //定义全局变量i用于不同功能模式的选择,‘0’正常模式,‘1’上限调节模式,‘2’下限调节模式 uchar a=0; //定义全局变量a用于不同模式下数码管显示的选择 bit k4=0; //K4按键双功能选择位,k4=0时K4按键选择消按键音的功能,k4=1时K4按键选择正负温度设定功能 bit v=0; //K2、K3按键双功能选择位,v=0时选择上下限查看功能,v=1时选择上下限温度加减功能 bit v1=0; //v1=1时定时1250ms时间到自动关闭报警上下限查看功能 bit v2=0; /消按键音功能调整位,为‘0’时开按键音,为‘1’时关按键音 /******************************************************/ /* 读键盘延时子函数 / /*****************************************************/ void keyscan_delay(uint z) //延时1ms左右 { uint i,j; for(i=z;i>0;i--) for(j=120;j>0;j--); } /******************************************************/ /* 温度调节函数 / /*****************************************************/ int temp_change(int count,bit f) //上下限温度调整 { if(key2==0) //判断K2是否按下 { if(v2==0)beer=0; //v2=0开按键音,否则消按键音 keyscan_delay(10); //延时10ms if(key2==0) //再次判断K2是否按下(实现按按键时消抖) { beer=1; //K2按下关按键音 if(f==0) //若温度为正 { count++; //每按一下K2温度上调1 if(a==1){if(count>125) count=125;}//当温度值大于125时不上调 if(a==2){if(count>125) count=125;} } if(f!=0) //若温度为负 { count++; //每按一下K2温度下调1 if(a==1){if(count>55) count=55;}//当温度值小于-55时不再下调 if(a==2){if(count>55) count=55;} } } while(key2==0); //K2松开按键时消抖 keyscan_delay(10); } if(key3==0) { if(v2==0)beer=0; keyscan_delay(10); if(key3==0) //K3按按键时消抖 { beer=1; count--; //每按一下K3温度为正时下调1,为负时上调1 if(a==1){if(count<0) count=0;}//当温度值达到0时不再调 if(a==2){if(count<0) count=0;} } while(key3==0); keyscan_delay(10); //K3松开按键时消抖 } return count; } /******************************************************/ /* 读键盘函数 / /*****************************************************/ void keyscan() { if(key1==0) { if(v2==0)beer=0; keyscan_delay(10); if(key1==0) //K1按按键时消抖 { beer=1; TR1=1; //开定时器1,通过s标志位的变化,实现在上下限温度调整时温度显示时闪烁的功能 k4=1; //在上下温度调节功能模式下选择K4的调整上下限温度正负的功能 v=1; //在上下温度调节功能模式下选择K2、K3的温度加减功能 i++; //K1按一下i加1,i=‘0’进入正常模式,i=‘1’进入调上限模式,i=‘2’进入调下限模式 if(i>2) //K1按下三次后退出调节模式 { i=0; //进入正常模式 TR1=0; //关定时器1 k4=0; //在正常模式下选择K4的消按键音功能 v=0; //在正常模式下选择K2、K3的查看上下限报警温度功能 store_t(); //存储调整后的上下限报警温度 } switch(i) //显示选择 { case 0:a=0;break; //a=0选择显示测得的温度 case 1:a=1;break; //a=1选择显示上限温度 case 2:a=2;break; //a=2选择显示下限温度 default:break; } } while(key1==0); //K1松按键时消抖 keyscan_delay(10); } if(a==1&&v==1) //a=1选择显示上限温度且v=1时选择上下限温度加功能 {led=0;max=temp_change(max,f_max);}//显示上限温度 else if(a==2&&v==1) //a=2选择显示下限温度且v=1时选择上下限温度减功能 {led=1;min=temp_change(min,f_min);} else; if(k4==1) //k4=1时K4按键选择正负温度设定功能 { if(key4==0) { if(v2==0)beer=0; keyscan_delay(5); if(key4==0) { beer=1; if(a==1) {if(max>55) f_max=0;else f_max=~f_max;}//当温度大于55度时,只能设定为正温度 if(a==2) {if(min>55) f_max=0;else f_min=~f_min;}//当温度大于55度时,只能设定为正温度 } while(key4==0); keyscan_delay(10); } } if(v==0) //v=0时选择上下限查看功能 { if(key2==0) { if(v2==0)beer=0; keyscan_delay(10); if(key2==0) { beer=1; a=1; //选择上限显示 TR1=1; //开定时器1开始定时一分钟左右 s1=1; //上限显示不闪烁,显示一分钟左右自动退出 } while(key2==0); keyscan_delay(10); } if(key3==0) { if(v2==0)beer=0; keyscan_delay(10); if(key3==0) { beer=1; a=2; //选择下限显示 TR1=1; //开定时器1开始定时1s s1=1; //下限显示不闪烁,显示1s自动退出 } while(key3==0); keyscan_delay(10); } if(v1==1) //v1=1时定时1s时间到自动关闭报警上下限查看功能 {a=0;v1=0;TR1=0;} //a=0显示实测温度,v1清零,关定时器1 if(k4==0) //k4=0时K4按键选择消按键音的功能 { if(key4==0) { if(v2==0)beer=0; keyscan_delay(10); if(key4==0) { beer=1; v2=~v2; //为‘0’时开按键音,为‘1’时关按键音 } while(key4==0); keyscan_delay(10); } } } } #endif 四、总结 单片机的学习与应用相关的总结与体会。在课设过程中,我们不仅巩固了平时所学习的单片机知识,而且通过不断查阅相关资料,学习新的知识,可以说,通过这次单片机的实践学习,我们学到了很多,而且对单片机的有关知识以及其在现实生活中的多方面应用有了更深层次的认识,这对于我们以后的学习和步入社会后参加工作都有很大的帮助。 在此次课程设计的进程中,我们遇到了很多问题,例如,一开始我们在确定课设题目后,在编写程序时,由于思路不太清晰,而且设计要求中需要使用新器件DS18B20智能测温,而其相关知识我们很模糊甚至可以说一无所知,不过后来,我们通过查找一些相关的资料书以及寻求辅导老师的帮助,又经过我们的主动思考,理清思路,终于将程序修改正确。在仿真时,由于我们有了之前的数模电课设仿真经验,所以此时我们课设进行的很顺利,并没有受到什么大的阻碍。 通过此次单片机课程设计,我们明白了很多,理论指导实践,但是理论也需要实践给予证明,不能盲目的相信书本,凡事都要通过自己的思考推敲,否则自己不会取的大的进步。而且在平时的学习生活中应该多和周围的同学相互学习,交流经验,遇到不会的东西时,切忌焦躁,首先要经过自己的独立思考,有了一定想法后,可以去查找相关的资料书刊或者找同学讨论,如果实在解释不了,再去找辅导老师,在这个遇到问题解决问题的过程中,不断加强自我的动脑能力,进而去指导动手能力,也只有这样,在思路清晰,条理顺畅的时候,再去进行软件编写和硬件操作工作,才有可能起到事半功倍的效果。 五、附录 系统硬件原理电路图 数字温度计设计器材表 单片机STC89C52 DS18B20 晶振12M 三极管8850 电容30PF 电解电容10UF/25V 小蜂鸣器 LED ø5红 电阻10k,3k,2k,1k,510,330 各5 4位一体共阴数码管 AC/DC(5V/1A)电源 IC插座40 9X15cm万用板 六、参考文献 51单片机在微型智能控制系统中应用广泛。随着人们对控制系统要求的不断提高,针对51单片机不具备实时多任务支持功能,在控制系统的进一步发展很受限制的情况,人们做了很多关于51单片机多任务实时编程的研究和实验。罗江等在四川省教育厅资助的基金项目《基于多任务机制的51单片机在微型智能控制系统中的应用研究》中,借鉴多任务操作系统的设计特点,提出了利用时间片分配机制,实现多任务分时轮流执行,和利用中断强行切换任务的多任务编程方法[1];在www.ddvip.com的单片机技术交流中,阮元提出了依据分时操作系统思想实现单片机多任务编程的方法[2];厦门大学的王辉堂等在一安防系统的设计中,通过对嵌入式实时操作系统RTOS的分析,认为其核心是利用中断切换任务,提出了用C51编程实现的多任务编程方案[3];美国Keil公司开发的MCS51系列单片机的实时多任务操作系统RTX51,占用定时器T0中断产生时间片来切换任务[4,5];此外,还有时间片轮转算法[6]、时分多线程[7]等多种单片机多任务设计方法在实际系统中应用。 综上所述,在目前51单片机多任务编程工作中,大多采用了基于实时操作系统RTOS分时操作的思路和采用中断切换任务。但也有人认为,这种任务切换产生大量数据,额外占用系统资源,不适合资源有限的单片机系统[8]。本文提出的51单片机多任务编程方法,不采用时间片,而是基于一个完整过程切换任务,并将任务调度分配到各任务内部,任务的切换和调度都不占用系统额外资源。通过实际系统应用证明,用该方法设计系统,硬件电路更简单,单片机工作效率更高。 本文首先阐述多任务编程设计方法的原理,然后介绍几种典型任务的设计方法,最后通过一个简单的实例来说明该方法的可行性。 1 多任务设计原理 多任务要求系统在同一时间执行多个任务,对于一个处理器,并不可能在同一时间运行多个任务程序,而是按时间片在各个任务间快速切换执行来完成多任务要求的。这是基于实时操作系统RTOS的方法。本文提出的方法也是按时间片切换任务的,但有所不同的是,执行任务的时间不是由定时器平均分配的,而是按照执行任务中一个完整过程的时间来自动分配的。 在单片机系统设计中,可按系统的功能或模块划分为任务,而每个任务可按具体作业细分为各个过程。可见任务由过程组成。按时间片分配任务的设计,任务的调度可分为两级,一是对任务的调度,二是在每项任务中对过程的调度。由于采用两级调度,比较繁琐,也占用了系统较多资源。如果不考虑对任务的调度,直接调度各任务的过程,其系统运行的效率就能极大地提高。同时,在任务中主动设置切换点自动切换任务,切换时不需保留大量现场数据,系统效率就会更高。再进一步,将过程的调度分配到各个实际任务之中,不设专门的过程调度表。系统的运行效率就会达到最高。为此本文提出的51单片机多任务编程的原理是: 1)将系统的各任务依次排成队列,处理器依次执行各任务,在执行完最后一项任务后,马上回头执行第一任务,并以此循环。 2)在每次执行一项任务时,只执行该任务的其中一个过程,并完整执行。其它的过程需等待下一轮执行该任务时才有机会执行。这可保证系统尽快从本任务切换到其它任务。 3)每个过程不包括循环延时、等待等浪费CPU时间的程序,循环延时、等待等程序将作为一个特殊过程独立设计。 4)常规任务(甚至实时任务)都设有一个空执行过程,以便系统跳过该任务的执行。优先任务或抢占式任务可利用此功能保证任务优先执行或独占运行。 5)系统不设专门的过程调度表,过程的调度在实际任务之中进行。 6)中断只用于调度过程,不处理任务,更符合系统实时性要求。 7)系统、任务和过程间均通过全局变量共享和交换数据。任务、过程间切换不保留现场数据和传递参数。 由此可见、系统按完整过程(最小作业单元)自动切换任务,不需保留临时现场数据,不需定时被动切换,不需额外的调度表。和单任务编程相比,多任务编程也没有占用系统任何额外资源。其结构和代码的可读性也没有较大的改变。 实际上,设计操作系统与设计应用系统最本质的区别是,操作系统面临的任务是不确定的,在设计完成后还允许应用添加新的任务;而应用系统面临的任务是事先定制的。因此,多任务应用系统的设计可不完全基于操作系统分时的思路。本文提出的多任务编程方法,就不需要强制的时间片划分,也不需要占用中断切换任务。 2 多任务编程的实现方法 2.1 一般任务的设计 任务分为实时任务和常规任务,实时任务是需无条件执行的任务,即在系统每次扫描任务队列时都必需执行的任务。通常是显示,外围检测等实时任务;常规任务是在满足一定条件时才启动的任务,通常被中断或其它任务调度,也能被中断或其它任务中止。为将实时任务与常规任务排在同一队列中执行,我们为常规任务增设了一个特殊过程,在调度这个过程时,该任务不执行任务的实际操作。这个过程称为空过程。例如,下面例举的任务包括一个空过程和三个实际操作过程,在任何其它任务中,通过屏蔽本任务的空过程,即SETB 00H,就可启动本任务,同理,CLR 00H就可在任何时候中止本任务的执行: PROC:JNB 00H,PROCE;空过程调度,不执行本任务 JB 11H,PROC2;11H置位时,执行过程2 JB 10H,PROC1;10H置位时,执行过程1 PROC0:……;过程0:本过程通常为任务初始化 SETB 10H;调度过程1 AJMP PROCE PROC1:…….;过程1 SETB 11H;调度过程2 AJMP PROCE PROC2:……;过程2 CLR 00H;任务完成,调度本任务的空过程 PROCE:NOP;空过程 由任务设计可以看出,过程是通过消息(位寻址变量置位)来调度的。每次执行任务只执行该任务的一个过程,并且过程不能包括循环延时、等待等代码,以保证系统轮流执行多任务的实时性。如果一个过程必需包括等待或延时,则将过程从等待或延时处分解为两个过程,并在两个过程中插入专门的延时或等待过程。延时和等待过程的设计说明如下。 2.2 延时等待过程设计 延时,等待等过程在程序设计中是必不可少的,为不影响系统的实时性,多任务编程的延时和等待需要特殊设计。 延时分为短延时和长延时,短延时是通过一个和几个计数变量计数就能完成的延时,长延时则通常利用计数变量配合计时器中断来完成延时。短延时可设计成任务中的一个特殊过程,长延时可设计成任务中的一个特殊过程或一个独立的特殊任务。 1)短延时,以下程序设计了一个特殊过程PROC1:一个计数变量30H递减实现延时,其计数周期是系统执行任务队列中全部任务所需的时间。 2.3 串行口连续发送数据任务 串行口发送数据需占用较长时间,连续发送多个字节数据将影响多任务系统的实时性。基于时间片的多任务切换系统,一般通过计算发送一帧数据所需的时间,采用约大于这一时间的定时方式,来作为发送两帧数据间的时间间歇,而不是在检测到发送数据缓冲区SBUF为空时立即发送后一帧数据。其连续发送数据的实时性较差,不能保证在数据总线上较好地与其它系统协调工作。本方法通过以下程序解决这一问题(假设数据缓冲区30H-3FH的16字节数据需要从串行口发送): 3 单片机多任务编程实例 3.1 说明 本实例是从楼宇可视对讲系统的门口主机系统中简化出来的,可视对讲系统是一计算机支持的分布式多设备协同工作系统,总线制通信对串行口收发数据具有很高的实时要求。门口主机系统包括4位LED数码显示,12键键盘,串行口发送、接受数据,处理数据,以及控制开锁、发出蜂鸣声,密码数据闪存,计时,切换工作状态等多项任务。采用多任务编程是十分必要的。本实例就其LED显示,键盘扫描和串行口交换数据功能来说明该系统的多任务编程过程。 3.2 硬件设计 能完成上述几项功能的硬件电路设计如图1所示,这是一个单片机最小系统。4位LED数码管采用动态显示,键盘为矩阵扫描键盘,这两个模块在单任务设计中,都需要较多的延时过程,很难保证系统的实时性,操作按键时还会造成显示闪烁或停顿。为此,很多类似设计都增加外围硬件或采用专用芯片如CH451来驱动[9],而多任务编程可省去这些硬件。图中75176芯片的AB端接楼宇对讲系统的485总线,与其它设备交换数据协同工作。 3.3 软件设计 3.3.1 编程说明 编程目的: 1)从键盘键入4位数据,键入过程中可按*键清除重输。 2)每键入1位数据。LED屏从最右边1位开始显示,原各位显示向左移1位。 3)键入数据过程中,如果有5s钟没有继续按键,系统清除输入数据和显示。 4)当输入完4位数据后,系统将4位数据从串行口发送到数据总线。 5)当从数据总线上接收到数据#0D8H时,开始发送4位数据。 6)75176芯片平时为接收状态,当接收到#0D8H时,如果系统有数据要发送,则变为发送状态,发送数据,当数据发送完毕时,75176芯片恢复为接收状态。 根据上述编程目的将系统编程划分为以下7个任务: 1)4位LED数码管动态显示; 2)键盘扫描检测;当有按键时,启动第3任务。 3)处理键盘检测结果,设置防抖动延时;计算键值;并在释放键后启动第4任务; 4)将键值转换为输入数据和显示数据,分别送输入数据缓存区和显示缓存区;当键入*时清除已输入数据和显示;当输入第1位数据时,启动第5任务;当输入数据缓冲区满时,启动第6任务的一个条件; 5)5s延时,5s过后没有继续按键时,清除已输入数据和显示; 6)串行口发送数据,启动本任务同时需满足有数据要发送和接收到#0D8H两个条件; 7)串行口接收数据,当接收到#0D8H时,启动第6任务的另一条件。 其它说明: 1)在上述7个任务中,LED显示和键盘检测是实时任务,其余是常规任务。 2)系统采用执行其它任务的时间处理LED动态显示和键盘扫描检测任务所需的延时。 3)系统采用定时器0中断处理5s延时。定时标志20H置位间歇为25ms;计数变量6EH初始化和重置值为200(#0C8H)。 4)系统采用串行口中断处理串行数据的接收和发送。串行口接收中断启动系统第7项任务。 3.3.2 系统流程图 图2是7个任务轮流执行的系统总流程图 3.3.3 任务设计说明 1)4位LED数码管动态显示 在单任务设计中,4位LED数码管显示是一次完成的[10],每显示1位,都要设计一定的延时,这会浪费CPU时间,影响其它任务的实时执行。多任务设计的方案是,每次执行显示任务只依次显示一位,然后就执行下一任务。系统利用执行其它任务的时间来为LED的每位显示延时,即在显示延时过程中,系统同时在执行其它任务。 系统设有一个显示缓冲区7CH-7FH,LED显示是通过自动扫描显示缓冲区进行的。 显示初始化: 矩阵键盘扫描检测的典型设计是依次置每行电平为低,对键盘行进行扫描,检测各列是否有键按下。由图1所示,矩阵键盘的行线和LED显示的位线是一致的,键盘扫描和动态显示扫描保持同步,很便于程序设计。所以,在多任务设计方案中,和LED显示任务一样,每次执行键盘检测任务时,只对一行进行检测。当没有按键按下时,系统依次对每行都进行检测;当有按键按下时,系统只对上次检测到按键按下的行进行检测,从而锁定对该按键的继续检测。 键盘扫描任务与LED显示任务通过共享全局变量R0保持同步: 3)按键的处理 按键的处理主要包括计算按键持续按下的时间,键值计算和对按键释放后的处理。为防止按键抖动,持续按键需在一定的延时后,才能处理。多任务设计是在其任务中插入一个短延时过程。在处理完按键键值后,需等待释放按键。多任务设计通过设置消息(11H置位)屏蔽本任务和不启动后续相关任务的方式来设计等待过程: 4)键值转换和数据处理 键值转换的目的是将键值转换为输入数据和显示数据,分别存入数据缓冲区和显示缓冲区。当输入数据缓冲区满时,系统将缓冲区数据从串行口发送到总线上,当输入数据是*时,系统清除缓冲区数据和显示。为检测输入一位数据后,继续输入是否被放弃,每输入一位数据,系统启动或重置一个5s延时。R2为输入数据缓冲区指针,初始值为#50H: 5)按键间延时 系统通过按键连续输入数据,要求按键间间隙时间不能超过5s,超过5s被认为是放弃输入,系统将清除已输入数据和显示。在本实例中,按键间延时设计为一个特殊任务。该任务的5s计数值在键值转换中启动或重置,可保证每次按键后都有5s时间延时。该延时可在5s后自动结束或被其它任务中止。 按键延时代码中,21H在定时器0中断程序中置位,设计置位周期为50ms,计算变量6EH在键值转换任务中设置或重置: 6)串行口发送4字节数据 串行口连续发送数据任务是配合串行口发送中断进行的。除第2节典型示例外,串行口发送数据任务还可按下面方式设计: 7)串行口接收数据 为了串行口中断不对系统的实时性产生较大影响,对串行口接收数据的处理是在中断外多任务队列中进行的。限于篇幅,系统只考虑从管理中心接收到发送数据允许同步信号#0D8H,开始向管理中心发送数据: 8)串行中断程序 在串行发送数据中断中,系统设计了75176芯片转换为接收状态的过程,48H在串行发送最后一字节时置位,以便在发送完数据后,改变系统状态。 在串行接收数据中断中,04H置位,通知系统从串行口接收到一字节数据,并已存入4FH,同时04H启动串行口接收数据任务: 3.3.4 系统的构成 将系统的7个任务经过上述设计,然后排成队列,并在队列最后设置一个长调转指令到队列中最前一个任务,构成一个依次执行并轮流循环的系统,这就是多任务编程的系统实例。在系统中,各任务需然是依次排成队列执行的,但任务的排序是可任意改变的。在任务间任意插入新的任务,也不会影响原系统正常运行。基于这一特征,各任务被看成是并发执行的。 4 结论 单片机多任务编程方法可归纳为: 1)在单片机多任务编程中,各任务依次排成队列轮流执行。 2)每次执行任务只调用其一个过程来执行,可保证各任务间最快速地切换。 3)和时间片任务切换不同,切换任务不占用堆栈和额外的系统资源。 4)各任务、过程间使用全局变量共享或交换数据,避免各种参数传递。 5)基于消息(位寻址变量的置位),每项任务,都可被其它任务启动和中止。 通过对循环延时和等待过程的重新设计,单片机多任务设计的每项任务和每个过程都是连续执行的。这可保证多任务间的切换最快,单片机的运行效率最高。因为任务的过程是完整执行的,所以和时间片强制切换不同,任务和过程的切换不占用堆栈和额外的系统资源。因为任何任务都可中止和屏蔽其它任务的执行,本方法也可设计抢占式任务。只要给实时任务也设计一个空过程,抢占式任务甚至还可屏蔽实时任务。达到最优先,最快执行的目的。本方法基于消息,但没有单独的消息循环过程[11,12,13],可最大限度地减少系统开支。 本文介绍的多任务编程方法是用汇编语言实现的,但显而易见,根据本方法的原理,用C51语言同样也可设计单片机多任务系统。 在过去采用传统方法设计复杂的单片机系统过程中,人们容易发现系统交叉调用多,重复代码多,系统运行效率差,容易逻辑混乱且难以调试。例如要完成本文实例的设计,键盘处理过程需反复进行键盘检测。为了不让显示中断,键盘处理防抖延时和等待释放键盘时,又要多次调用显示过程,串行口连续发送数据过程中,更是频繁调用显示。显示、键盘检测、键盘处理等任务不能成为低耦合性的独立模块。系统程序的层次结构和可读性也都较差,并很难移植和重用。鉴于此,为探索一个结构清晰,易调试,任务明确且可重用、提高开发效率,无相互调用,无重复代码的系统,一个新的编程方式开始了实践和研究,并被总结为多任务编程方法。在近八年不断利用新方法设计(如楼宇对讲系统、联网报警系统、门禁系统,安防监控、广播控制、一卡通等)智能系统产品的同时,本多任务编程方法取得了成熟和发展。与传统设计相比,这些系统产品硬件资源少,运行效率高。其硬件功能更多以软件取代,所以运行更稳定,且易维护,性价比高,取得了更高的经济效益。 摘要:本文论述了在51系列单片机系统中,一种多任务系统编程设计方法。该方法不基于实时操作系统RTOS分时操作的思路和采用中断切换任务。本文通过一个具有4位LED数码显示,12键的键盘扫描和用串行口与其它系统交换数据的简单系统为例,说明该方法编程具有硬件设计简单、单片机工作效率高,实时性强等特点。该方法编程采用汇编语言,但根据相同的原理和思路,也不难用到C51语言编程上面。 【关键词】单片机 项目教学法 智能小车 教学改革 【中图分类号】G 【文献标识码】A 【文章编号】0450-9889(2016)08C-0161-02 在电子技术快速发展的时代,单片机技术在电子行业中扮演着极其重要的的角色,掌握了单片机技术,就掌握了电子技术的核心,因此,各大中专院校的电气工程、机电工程、自动化专业及计算机专业等电类专业都把单片机作为一门重要专业课程来抓。然而,在现实教学过程中,单片机实验课程教学却存在着诸多的不足。很多学生反映该课程不好学,知识难记,程序难写,讲课内容不好理解等。教师也反映学生刚开始认真学,后来越来越不认真,最后布置的练习和作业都不做,学习态度不好等,由此产生教师的教和学生的学达不到平衡,教学效果不理想,教学过程偏离教学计划,达不到教学的预期培养目标。本文针对上述所存在的问题,以制作“智能小车”为整门课程的大项目,采用项目教学法将课程的各章节融入项目制作过程中,通过做项目,让学生在制作的过程中理解并掌握课程的知识点。 一、单片机实验教学的现状与不足 职业院校不同于普通高等院校,职业院校的学生更是异于普通高等院校的学生。普通高等院校的学生自学能力强,自觉性高,相反,职业院校的学生则大多自我认识不高,自学能力较差,自觉性不高,比较好动。如果在职业院校教学中采用传统的教学方法,教师在黑板前单纯讲课,或通过多媒体单纯授课,学生在下面听,即便教师讲得十分生动、精细,也许台下的学生也是无心听课,不能真正掌握好单片机技术。 一些教师采用计算机房进行授课,先讲一定的教材内容,然后通过计算机演示和示范程序,让学生按照示范程序进行练习,然后用Proteus软件或单片机系统板进行程序仿真。此种教学方法比上述传统方法好很多,但授课一段时间后却发现,学生基本上是将教师的示范程序照抄一遍,然后下载到Proteus软件或单片机系统板中,仿真成功后,接下来就是拿出手机开始玩游戏,没有继续强化知识的意识或精神。而那些打字慢或搬抄过程中存在错漏的学生,可能准备下课了都没打完字,也可能是因为中途编译错误太多,又找不出错误的地方,最后干脆就放弃,直接玩手机或睡觉。 为了避免上述现象问题的发生,提高教学质量和教学效果,我们应该根据职业院校学生的情况,制定出符合他们特点的教学方法,让他们对课程感兴趣,进而激发他们自主去学习和探索知识。 二、单片机实验教学项目教学法改革 经过多年的教学经验总结,本文对51单片机实验课采用项目教学法进行改革,主要由以下几个环节组成: (一)兴趣入门。心理学上有一个专业的术语叫做“初次效果”,意思是初次见面的一瞬间就能决定胜败。这是对人说的,其实对教学课程也有同样的效果。如果在第一次给学生上这门单片机实验课时,做好开头,让学生从一开始就对这门课充满兴趣和期待,那么,可以说,这门课程的教学就算成功三分之一了。 对本课程第一次课的教学设计,大部分教师都是简单做自我介绍,提出课程的学习要求,然后就开始打开课本上新课。本文的设计则不同,为了做好“初次效果”,本文的第一次课没有直接上新课,而是跟学生一起看视频、交流和讨论,通过交流与讨论的形式让学生对单片机有一个感性的认识,再通过一定手段使学生对课程学习产生兴趣和欲望,然后才慢慢引入新课。具体内容如下: 1.相互认识。先由教师简单向学生做自我介绍,然后再对全班学生进行点名,认识班里的班干,简单了解班级的一些基本情况。 2.看视频。相比学习文字材料,职业院校的学生对图片、视频,甚至实物更加感兴趣,利用这一特点,教师在上课前准备好搬运机器人、足球机器人、工业生产自动焊接机器人等几个具有代表性的视频材料,通过多媒体播放给学生看,然后再向学生介绍这些智能机器人之所以能完成那么多智能控制功能,都是由我们即将学习的单片机芯片来控制,随即放出单片机芯片的相关图片。运用此种方法,通过播放视频和图片的形式,将全班学生的目光吸引到屏幕上,让学生从感官上对单片机有一个初步的认识。 3.项目实物演示。此环节是第一次课的高潮部分,教师准备好本门课程项目教学的项目实物——智能小车,用一纸箱装好,等到这一环节时将它亮相在学生面前,然后试运行给学生看,甚至让学生亲手操作智能小车,让智能小车动起来,最后现场给学生指出单片机芯片,由此激发学生对本课程学习的兴趣。 4.布置项目任务与讨论。通过以上几个步骤后,学生对单片机已经有一定的兴趣,此时,教师粗略地讲一下智能小车的大致工作原理,然后向学生宣布项目任务—— 制作一辆智能小车。教师将实现的功能和要求罗列出来,最后与学生一起讨论如何去完成这个项目,包括硬件部分如何制作,需要用什么电路模块,最后如何去编写程序等,拟出一个大体的方案,调动学生的积极性,激发学生求知欲。 5.材料准备。做完项目布置和讨论环节后,最后一环节就是材料准备的布置,要求学生列出自己项目所需要的材料,然后用一周的时间去准备,材料可以自己制作,也可以购买现成模块,这样,学生有事可做后,也不会再觉得课程枯燥无味,相应地学习兴趣也就提高了。 (二)硬件制作。通过前期学生的材料准备后,接下来就进入硬件的制作过程。 教师将学生带到实验室,通过分小组的形式进行硬件的制作指导。首先对全班学生提出总的制作要求和注意事项,然后将全班学生分成若干个小组,再召集各组的组长进行硬件的制作指导。比如,教师亲自动手示范指导组长们如何装配智能小车的底盘,如何安装驱动电机和轮子,如何安装电路板及相应控制电路模块等,然后再由各组长对本组的组员进行指导和装配示范,组员们按照组长的装配方法进行装配。同时,教师巡视课堂,了解每个组的制作情况和进度,对有问题或装不对的学生进行指导和提醒,最终完成项目的硬件装配与制作过程。 (三)简单驱动。项目的硬件制作完成以后,教师组织学生带上课本和智能小车进入单片机仿真实验室(或计算机机房),对硬件的相应部件进行功能测试以及程序的编写和仿真,完成一些简单功能的驱动任务。 首先驱动智能小车的运动部件,即驱动电机带动轮子转动。在驱动之前,先给学生设一个疑问:如何让轮子转进来?引发学生对本任务的思考。然后对硬件的电路及原理进行简单的讲解,引出单片机的IO口控制知识,并提示学生翻到课本的相应页码,融合课本教材相应内容进行简单的讲解。接着,教师写示范程序并下载到小车单片机中,驱动车轮转起来,再让学生按此方法进行程序的编写和驱动。 学生驱动得电机让小车跑起来后,学习兴趣肯定大大提高,场面也会很热闹。此时,教师又提出探索性任务,提示学生如何驱动LED指示灯亮起来,又如何实现灯闪铄,以及蜂鸣器的驱动等。通过完成这类小任务,学生去探索和驱动其他的一些电路,达到知识拓展的目的,同时也完成单片机IO口的简单驱动任务。 (四)功能完善。通过以上的方法驱动简单电路后,学生对单片机IO口的编程和控制已经有了一定的认识和掌握,接下来就可以引导学生一步一步地将一些功能电路模块综合起来驱动,实现从简单到复杂的智能控制过程。 比如,引导学生运用红外光电传感器来识别路面的黑线,再控制小车的运动情况,如当小车没检测到黑线时,小车一直向前行进;当检测到黑线时,小车停止前进。学生完成此任务后,教师又提出深一点要求,如当小车没检测到黑线时,小车一直向前行进;当检测到黑线时,小车转弯或调头行进等,实现综合性控制功能。 当学生掌握这些较简单的综合控制功能的编程和控制方法后,再往知识较深、程序较难、单片机功能更强的内容去学,比如超声波避障功能、液晶显示功能、定时器使用等,引导学生一步一步地、从简单到复杂地学习,最终完成智能小车所有功能的实现,从而完成本课程主要内容的授课与学习。 (五)考核评比。本课程的最后几次课是项目完成情况的考核评比与总结。当学生把课程项目的所有功能都完成后,安排一个时间对全班学生制作的项目进行演示和考核评比。在教室的讲台做一个演示台,让每个学生将自己的项目作品拿到演示台上,在全班同学面前展示和演示自己做的项目作品,看看所能实现的功能与开学初的计划是否一致,再简单讲解作品的工作原理和制作方法,最后,教师再对作品的结果进行点评和总结。 通过演示作品和教师的点评总结,学生不但收获了学习成功的喜悦,也认识了自己的一些缺陷和不足,进而努力去弥补和更正。同时,教师也能从这个环节中了解到学生学习的掌握情况和教学上存在的不足,进而对下一节课程进行完善和改进。 三、实验教学改革效果分析 51单片机实验课项目教学法的教学改革,打破了传统理论教学和单纯Proteus软件或单片机系统板仿真教学的缺陷,将整门课程改成项目作品的制作,再将课程内容知识融入项目的制作过程中,让学生既学会了课本知识,又掌握了知识的实际运用,达到理论与实践相结合的教学目的。 通过多年的尝试发现,改革后的单片机实验教学,教师的教学过程轻松了很多,对学生的指导时间多了很多,学生学习的兴趣和积极性更是大幅度地提高,课上睡觉或玩手机的学生大幅度地减少,更多学生将时间和精力投入到了项目的制作过程中,碰到问题时,学生也懂得借助网络进行相关资料的查找,学会了知识的探索,最后在毕业设计中,单片机项目的论文尤其是单片机毕业作品都明显多了很多。 综上,以制作“智能小车”为整门课程的大项目,采用项目教学法将课程的各章节融入项目制作过程中,通过学生亲自动手做项目的形式,培养学生对本课程的学习兴趣,提高学生的学习的积极性和主动性,引导学生将理论知识应用到实践中,增强学生的技能水平和实际应用能力,达到理论与实践相结合的目的。经过多年的教学实践证明,改革后的项目教学法不但提高了教育教学质量和教学效果,而且学生学习的兴趣了大为提高,实际动手能力也增强了。 【参考文献】 [1]赵永熹,黄云峰.基于项目驱动法的单片机课程体系建设[J].中国电力教育,2013(4) [2]谢宏霖.基于“项目驱动法”的单片机类课程教学探索[J].电脑知识与技术,2012(30) [3]杜志强.机械类专业“单片机原理及应用”课程教学探索与实践[J].理工高教研究,2010(3) 【作者简介】梁 飘(1984— ),男,壮族,广西靖西人,广西大学在读研究生,广西理工职业技术学院助理讲师,专职教师兼实验员。 单片机课程设计 题目:用51单片机实现电子时钟 院 部 物理与电子信息工程学院 专 业 名 称 电子信息科学与技术 班 级 1111 姓 名 杨庆月 学 号 2011111136 指 导 教 师 李刚 2013年12月09日 目录 摘要------------------------------1 1 单片机的相关知识------------1 1.1 单片机的简介--------------------1 1.2 单片机的特点--------------------1 1.3 89C52单片机的基本特点------------2 2 电子时钟--------------------3 2.1电子时钟的基本特点----------------3 2.2电子时钟的原理-------------------4 控制系统的硬件设计---------4 3.1单片机型号的选择-----------------4 3.2 lcd1602工作的原理---------------4 3.3 键盘电路的设计------------------6 3.4 复位电路设计-------------------------6 3.5 时钟电路设计-------------------7 3.6 整体电路原理图-----------------7 控制系统的软件的设计------8 4.1程序的设计----------------------8 4.2程序源代码----------------------8 5 仿真结果和实物图---------------19 5.1仿真结果------------------------------19 5.2实物图-19 6 总结--20 参考文献------------------------21 摘要:单片计算机即单片微型计算机。由 RAM ,ROM,CPU构成,定时,计数和多种接口于一体的微控制器。它体积小,成本低,功能强,广泛应用于智能产业和工业自动化上。而 51系列单片机是各单片机中最为典型和最有代表性的一种。这次课程设计通过对它的学习,应用,从而达到学习、设计、开发软、硬的能力。 本设计主要设计了一个基于 AT89C52单片机的电子时钟。并在 1602上显示相应的时间。并通过一个控制键用来实现时间的调节和是否进入省电模式的转换。 具有时钟和日历的功能,年限显示范围是2013-2099(可修改),且具有闰年自动修正功能 关键字:单片机;子时钟;键盘控制;LCD1602。单片机识的相关知识 1.1 单片机简介 MCS-51是 INTEL公司在成功推广的 MCS-48单片机基础上加以改进而成的 8位单片机。 这种单片机大约是上世纪 70年代末推出的,内部程序可重写的为 8751,外扩程序的是 8031,一次性生产,不可改变程序的是 8051。外形一般为 DIP40封装。不久又推出了增强型的 8052,其资源更加丰富。以后又采用 CHMOS技术推出了 80c51,耗电大大降低。到了 90年代,INTEL公司把精力放到更赚钱的计算机上,将 51单片机技术转让给了一此其它公司,如 ATMEL Philips等半导体制造公司,使 51系列单片机的市场份额不断扩大。 尽管十多年前就有人认为 51单片机会很快淘汰,但事实证明 51单片机经过不断的改进后,由于技术成熟,使用方便,至今在 8位单片机市场仍然拥有庞大的用户。特别是 MCS-51技术的 20年专利期限到期后,大量的兼容型号不断推出。从上世纪 90年代后期开始,美国 ATMEL公司在掌握快速擦写的存储器后,推出了 AT89C系列,此系列在中国获得了广泛的应用。 在此之前,由于可擦写的 8751价格昂贵,国内长时间采用 8031+27C64这样的外扩存程序储器方式。 51单片机最初只有 DIP40这种很古老的封装,后来推出了 CHMOS工艺的80C51后开始有了 PLCC44这种相对较小的方形封装。AT89C系列中开始有 20脚的 DIP20的精简型封装,这极大方便了在一些相对简单的单片机应用,缩小了 PCB的体积。20脚的有 AT89C1051、AT89C1051、AT89C1051,对应程序存储器分别为 1K、2K、4K。 标准的 51为 4K程序空间,128字节的 RAM,32条端口,5个中断,2个定时/计数器,12个时钟周期执行一条基本指令,最长的除法为 48个周期。52为 8K程序空间,256字节的 RAM,32条端口,6个中断,3个定时/计数器。AT89S51是可在板上直接下载程序的改进型号,并增加了看门狗功能,AT89C51只能在编程器下写入程序,所以经常会有人在 PCB上安装 IC插座,以便取下来编程更新程序。 AT的 51系列后来也推出了单周期的 51,但价格没什么优势,国内很少使用。最近几年宏晶在国内大量推广 STC51系列单片机,最近又推出不少所谓 1T的单 片机,价格较低 STC采用串口直接下载程序,写入程序很方便。 1.2 单片机的特点.单片机的存储器ROM 和RAM 时严格区分的。ROM 称为程序存储器,只存放 程序,固定常数,及数据表格。RAM 则为数据存储器,用作工作区及存放用户数 据。2.采用面向控制的指令系统。为满足控制需要,单片机有更强的逻辑控制能力,特别是单片机具有很强的位处理能力。.单片机的I/O 口通常时多功能的。由于单片机芯片上引脚数目有限,为了 解决实际引脚数和需要的信号线的矛盾,采用了引脚功能复用的方法,引脚处于 何种功能,可由指令来设置或由机器状态来区分。.单片机的外部扩展能力很强。在内部的各种功能部件不能满足应用的需求 时,均可在外部进行扩展,与许多通用的微机接口芯片兼容,给应用系统设计带 来了很大的方便。 1.3 89C52单片机介绍 P0 口:P0 口为一个8 位漏级开路双向I/O 口,每脚可吸收8TTL 门电流。当 P1 口的管脚第一次写1 时,被定义为高阻输入。P0 能够用于外部程序数据存储 器,它可以被定义为数据/地址的第八位。在FIASH 编程时,P0 口作为原码输入 口,当FIASH 进行校验时,P0 输出原码,此时P0 外部必须被拉高。 P1 口:P1 口是一个内部提供上拉电阻的8 位双向I/O 口,P1 口缓冲器能接 收输出4TTL 门电流。P1 口管脚写入1 后,被内部上拉为高,可用作输入,P1 口被外部下拉为低电平时,将输出电流,这是由于内部上拉的缘故。在FLASH 编程和校验时,P1 口作为第八位地址接收。 P2 口:P2 口为一个内部上拉电阻的8 位双向I/O 口,P2 口缓冲器可接收,输出4 个TTL 门电流,当P2 口被写“1”时,其管脚被内部上拉电阻拉高,且 作为输入。并因此作为输入时,P2 口的管脚被外部拉低,将输出电流。这是由 于内部上拉的缘故。P2 口当用于外部程序存储器或16 位地址外部数据存储器 进行存取时,P2 口输出地址的高八位。在给出地址“1”时,它利用内部上拉优 势,当对外部八位地址数据存储器进行读写时,P2 口输出其特殊功能寄存器的 内容。P2 口在FLASH 编程和校验时接收高八位地址信号和控制信号。 P3 口:P3 口管脚是8 个带内部上拉电阻的双向I/O 口,可接收输出4 个TTL 门电流。当P3 口写入“1”后,它们被内部上拉为高电平,并用作输入。作为输 入,由于外部下拉为低电平,P3 口将输出电流(ILL)这是由于上拉的缘故。P3 口也可作为AT89C52 的一些特殊功能口,如下表所示: 口管脚备选功能 P3.0 RXD(串行输入口)P3.1 TXD(串行输出口)P3.2 /INT0(外部中断0)P3.3 /INT1(外部中断1)P3.4 T0(记时器0 外部输入)P3.5 T1(记时器1 外 部输入)P3.6 /WR(外部数据存储器写选通)P3.7 /RD(外部数据存储器读选通)P3 口同时为闪烁编程和编程校验接收一些控制信号。 RST:复位输入。当振荡器复位器件时,要保持RST 脚两个机器周期的高电平时 间。 ALE/PROG:当访问外部存储器时,地址锁存允许的输出电平用于锁存地址的 地位字节。在FLASH 编程期间,此引脚用于输入编程脉冲。在平时,ALE 端以不 变的频率周期输出正脉冲信号,此频率为振荡器频率的1/6。因此它可用作对外 部输出的脉冲或用于定时目的。然而要注意的是:每当用作外部数据存储器时,将跳过一个ALE 脉冲。如想禁止ALE 的输出可在SFR8EH 地址上置0。此时,ALE 只有在执行MOVX,MOVC 指令是ALE 才起作用。另外,该引脚被略微拉高。 如果微处理器在外部执行状态ALE 禁止,置位无效。PSEN:外部程序存储器的选通信号。在由外部程序存储器取指期间,每个机 器周期两次/PSEN 有效。但在访问外部数据存储器时,这两次有效的/PSEN 信号 将不出现。 EA/VPP:当/EA 保持低电平时,则在此期间外部程序存储(0000H-FFFFH),不 管是否有内部程序存储器。注意加密方式1 时,/EA 将内部锁定为RESET;当/EA 端保持高电平时,此间内部程序存储器。在FLASH 编程期间,此引脚也用于施加 12V 编程电源(VPP)。电子时钟 2.1 电子时钟的基本特点 现在高精度的计时工具大多数都使用了石英晶体振荡器,由于电子钟、石英 钟、石英表都采用了石英技术,因此走时精度高,稳定性好,使用方便,不需要 经常调试,数字式电子钟用集成电路计时时,译码代替机械式传动,用用液晶显 示器代替指针显示进而显示时间,减小了计时误差,这种表具有时、分、秒显示 时间的功能,还可以进行时和分的校对,片选的灵活性好。 2.2 电子时钟的原理 该电子时钟由89C52,1602 液晶等构成,采用晶振电路作为驱动电路,由延时程序和循环程序达到时分秒的计时,六十秒为一分钟,六十分钟为一小时,满二十四小时为一天。而电路中有四个控制按键,一个是选择,一个进行加数,一个进行减数,还有一个保存。例如按下选择键,然后1602显示光标,此时可以用加或减来进行调节,在按下选择键,光标移到不同的单位上,同理进行调节,最后待日期时间调节好后,按下保存键,时钟开始计时。控制系统的硬件设计 3.1 单片机型号的选择 通过对51单片机的学习,认为STC89C52 是最理想的电子时钟开发芯片。STC89C52,最终认为89C52是一种带8K 字节闪烁可编程可擦除只读存储器的低电压,高性能CMOS8位微处理器,器件采用高密度非易失存储器制造技术制造,与工业标准的MCS-52指令集和输出引脚相兼容。还有一点重要原因,就是采用AT89C52时不能用开发板进行程序的下载,所以最终选用STC89C52进行设计。 3.2 1602 工作原理及显示电路 字符型LCD 通常有14 条引脚线或16 条引脚线的LCD,多出来的2 条线是背 光电源线VCC(15 脚)和地线GND(16 脚),其控制原理与14 脚的LCD 完全一样 1602液晶的基本的操作分为以下四种: 状态字读操作:输入RS=低、RW=高、EP=高; 输出:DB0~7 读出为状态字; 数据读出操作:输入RS=高、RW=高、EP=高; 输出:DB0~7 读出为数据; 指令写入操作:输入RS=低、RW=低、EP=上升沿; 输出:无; 数据写入操作:输入RS=高、RW=低、EP=上升沿; 输出:无。 如图 1602模块的引脚 LCD1602正面 LCD1602背面 1602与单片机连接图 3.3 键盘电路设计 本时钟采用四个按键控制,一个(实物图蓝色线24号引脚)是选择,一个进行加数(实物图紫色线25号引脚),一个进行减数(实物图灰色线26号引脚),还有一个保存(实物图白色线27号引脚)。例如按下选择键,然后1602显示光标,此时可以用加或减来进行调节,在按下选择键,光标移到不同的单位上,同理进行调节,最后待日期时间调节好后,按下保存键,时钟开始计时。 3.4 复位电路设计 单片机复位有上电复位和手动复位两种方式,上电复位是接通电源后利用RC充电来实现复位。手动复位是通过人为干预,强制系统复位。 连接至9号复位引脚 复位电路如图所示,可以实现上电复位和手动复位功能。 3.5 时钟电路设计 系统时钟源由内部时钟方式产生,时钟电路由12MH晶振和两个30PF瓷片电容组成,构成自激振荡,形成振荡源提供给单片机。电容可在5PF到30PF之间选择,电容的大小对振荡频率有微小影响,可起频率微调作用。 3.6整体电路原理图 控制系统的软件设计 4.1 程序设计 由于C 语言程序设计较汇编可读性强,可移植性,且可以大大降低编程的难 度和缩短开发周期,本系统程序采用c 语言设计。 4.2 程序源代码 #include //包含单片机寄存器的头文件 #include #define uchar unsigned char #define uint unsigned int sbit RS=P2^0; //寄存器选择位,将RS位定义为P2.0引脚 sbit RW=P2^1; //读写选择位,将RW位定义为P2.1引脚 sbit E=P2^2; //使能信号位,将E位定义为P2.2引脚 sbit BF=P0^7; //忙碌标志位,将BF位定义为P0.7引脚 uchar code table[]=“2013-12-07 WEEK6”; //初始化液晶显示 16 uchar code table1[]=“TIME: 19-27-50”; //14 uchar count,s1num;char second,minute,hour,day,month,year,week; sbit s1=P2^3; //功能键 sbit s2=P2^4; //加键 sbit s3=P2^5; //减键 sbit s4=P2^6; //保存并退出 /* 延时若干毫秒 */ void delay(uchar n){ uchar i,a,b;for(i=0;i for(b=199;b>0;b--) for(a=1;a>0;a--);} /*********************************************** 函数功能:判断液晶模块的忙碌状态 返回值:result。result=1,忙碌;result=0,不忙 ************************************************/ uchar BusyTest(void){ bit result;RS=0;//根据规定,RS为低电平,RW为高电平时,可以读状态 RW=1;E=1; //E=1,才允许读写 _nop_(); //空操作 _nop_();_nop_();_nop_(); //空操作四个机器周期,给硬件反应时间 result=BF;//将忙碌标志电平赋给result E=0; //将E恢复低电平 return result;} /******************************************** 函数功能:写指令 入口参数:dictate *********************************************/ void WriteInstruction(uchar dictate){ while(BusyTest()==1); //如果忙就等待 RS=0;//根据规定,RS和R/W同时为低电平时,可以写入指令 RW=0;E=0; //E置低电平(根据表8-6,写指令时,E为高脉冲,//就是让E从0到1发生正跳变,所以应先置“0” _nop_(); _nop_(); //空操作两个机器周期,给硬件反应时间 P0=dictate; //将数据送入P0口,即写入指令或地址 _nop_();_nop_();_nop_();_nop_(); //空操作四个机器周期,给硬件反应时间 E=1; //E置高电平 _nop_();_nop_();_nop_();_nop_(); //空操作四个机器周期,给硬件反应时间 E=0; //当E由高电平跳变成低电平时,液晶模块开始执行命令 } /********************************************* 函数功能:写数据 入口参数:y(为字符常量)**********************************************/ void WriteData(uchar y){ while(BusyTest()==1);RS=1; //RS为高电平,RW为低电平时,可以写入数据 RW=0;E=0; //E置低电平(根据表8-6,写指令时,E为高脉冲,//就是让E从0到1发生正跳变,所以应先置“0” P0=y;//将数据送入P0口,即将数据写入液晶模块 _nop_();_nop_();_nop_();_nop_(); //空操作四个机器周期,给硬件反应时间 E=1; //E置高电平 _nop_();_nop_();_nop_();_nop_(); //空操作四个机器周期,给硬件反应时间 E=0; //当E由高电平跳变成低电平时,液晶模块开始执行命令 } /****************************************** 函数功能:对LCD的显示模式进行初始化设置 *******************************************/ void LcdInitiate(void){ uchar num; second=50;minute=27;hour=19;week=6;day=7;month=12;year=13;count=0;s1num=0;E=0;delay(15);//延时15ms,首次写指令时应给LCD一段较长的反应时间 WriteInstruction(0x38);//显示模式设置:16×2显示,//5×7点阵,8位数据接口 delay(5);//延时5ms?,给硬件一点反应时间 WriteInstruction(0x38);delay(5);WriteInstruction(0x38);//连续三次,确保初始化成功 delay(5);WriteInstruction(0x0c);//显示模式设置:显示开,无光标,//光标不闪烁 delay(5);WriteInstruction(0x06);//显示模式设置:光标右移,字符不移 delay(5);WriteInstruction(0x01);//清屏幕指令,将以前的显示内容清除 delay(5);WriteInstruction(0x80);for(num=0;num<16;num++)//让液晶显示日期 { WriteData(table[num]);delay(5);} WriteInstruction(0x80+0x40);for(num=0;num<14;num++)//让液晶显示时间 { WriteData(table1[num]);delay(5);} TMOD=0x01; //定时器中断初始化 TH0=(65536-50000)/256;TL0=(65536-50000)%256;EA=1; ET0=1;TR0=1;} //-------写年月日---------------void write_nyr(uchar add,uchar date){ uchar i,j;i=date/10;j=date%10;WriteInstruction(0x80+add);WriteData(0x30+i);WriteData(0x30+j);} //--------写时分秒---------------void write_sfm(uchar add,uchar date){ uchar i,j;i=date/10;j=date%10;WriteInstruction(0x80+0x40+add);WriteData(0x30+i);WriteData(0x30+j);} //-------------写星期-------------void write_week(uchar add,uchar date){ WriteInstruction(0x80+add);WriteData(0x30+date);} //---------该年是否是闰年-------------bit leap_year(){ int leap;if((year%4==0&&year%100!=0)||year%400==0) leap=1; //是闰年 else leap=0; //非闰年 return leap;} //----------键盘扫描--------------------void keyscan(){ if(s1==0) //第一个键是否按下 { delay(5); if(s1==0) { while(!s1); s1num++; if(s1num>7) s1num=1; if(s1num==1) //第一个键被按一次 { TR0=0; WriteInstruction(0x80+0x40+13); WriteInstruction(0x0f); } if(s1num==2) { WriteInstruction(0x80+0x40+10); } if(s1num==3) { WriteInstruction(0x80+0x40+7); } if(s1num==4) { WriteInstruction(0x80+9); } if(s1num==5) { WriteInstruction(0x80+6); } if(s1num==6) { WriteInstruction(0x80+3); } if(s1num==7) { WriteInstruction(0x80+15); } } } if(s1num!=0) //如果功能键被按下 { if(s2==0)//第二个按下 { delay(5); if(s2==0) { while(!s2); if(s1num==1) //第一个键被按一次,秒钟加一 { second++; if(second==60) second=0; write_sfm(12,second); WriteInstruction(0x80+0x40+13); } if(s1num==2) //第一个键被按二次,分钟加一 { minute++; if(minute==60) minute=0; write_sfm(9,minute); WriteInstruction(0x80+0x40+10); } if(s1num==3) //第一个键被按三次,时钟加一 { hour++; if(hour==24) hour=0; write_sfm(6,hour); WriteInstruction(0x80+0x40+7); } if(s1num==4) //日期加一 { day++; if(day==32) day=1; write_nyr(8,day); WriteInstruction(0x80+9); } if(s1num==5) //月加一 { month++; if(month==13) month=1; write_nyr(5,month); WriteInstruction(0x80+6); } if(s1num==6) //年加一 { year++; if(year==99) year=0; write_nyr(2,year); WriteInstruction(0x80+3); } if(s1num==7) //星期加一 { week++; if(week==8) week=1; write_week(15,week); WriteInstruction(0x80+15); } } } if(s3==0) //第三个键被按下 { delay(5);if(s3==0){ while(!s3); if(s1num==1) //秒减一 { second--; if(second==-1) second=59; write_sfm(12,second); WriteInstruction(0x80+0x40+13); } if(s1num==2) //分减一 { minute--; if(minute==-1) minute=59; write_sfm(9,minute); WriteInstruction(0x80+0x40+10); } if(s1num==3) //时减一 { hour--; if(hour==-1) hour=23; write_sfm(6,hour); WriteInstruction(0x80+0x40+7);} if(s1num==4) //日减一 { day--; if(day==0) day=31; write_nyr(8,day); WriteInstruction(0x80+9);} if(s1num==5) //月减一 { month--; if(month==0) month=12; write_nyr(5,month); WriteInstruction(0x80+6);} if(s1num==6) //年减一 { year--; if(year==-1) year=99; write_nyr(2,year); WriteInstruction(0x80+3);} if(s1num==7) //日期减一 { week--; if(week==0) week=7; write_week(15,week); WriteInstruction(0x80+15); } } } if(s4==0) //保存并退出 { s1num=0; WriteInstruction(0x0c); TR0=1; } } } /****************************************** main function *******************************************/ void main(void){ uchar k=0;LcdInitiate(); //调用LCD初始化函数 while(1){ keyscan(); k=1;} } /***************************************** 函数功能:定时器T0的中断服务函数 ******************************************/ void timer0()interrupt 1 { count++;if(count==13){ count=0; second++; if(second==60) //秒计满60,秒归0,分+1 { second=0; minute++; if(minute==60)//分计满60,分归0,时+1 { minute=0; hour++; if(hour==24)//时计满24,时归0,星期+1,日+1 { hour=0; week++; day++; if(week==8) week=1;//星期计满7,星期归1 if(month==1||month==3||month==5||month==7||month==8||month==10||month==12)//大月三十一天 { if(day==32) //大月天数计满31,日归1,月+1 { day=1; month++; } } if(month==4||month==6||month==9||month==11)//小月三十天 { if(day==31) { //小月天数计满30,日归1,月+1 day=1; month++; } } if(month==2) { if(leap_year()) { if(day==30)//闰年二月29天??计满,日归1,月+1 { day=1; month++; } } else { if(day==29)//非闰年二月28天 计满,日归1,月+1 { day=1; month++; } } } if(month==13)//月计满12,月归1,年+1 { month=1; year++; } if(year==99)//年计满99,年归0 { year=0; } write_nyr(2,year); } write_nyr(5,month); } write_nyr(8,day); write_week(15,week); } write_sfm(6,hour); } write_sfm(9,minute);} write_sfm(12,second);5 仿真结果和实物图 5.1 仿真结果 5.2 实物图 总结: 说句实话,这个时钟在硬件上没有什么太多的技术含量,只有一个单片机的最小系统和一个显示电路,其实它们可以结合在一起,但是为了以后的方便,我还是将它们设计了两个部分,方便以后最小系统的其他方面的应用。还有就是程序,这个时钟程序如果让我自己写的话那我肯定不能再规定时间内完成,所以还是靠外界力量的帮忙。也正是如此,我找到我学习单片机的弱点,那就是程序的编写,记得室友百度开玩笑说:“程序是单片机的灵魂”,想想当时很搞笑,但仔 细一想,那还真是个恰当的比喻,如果说单片机没有程序的输入,那么它不能完成任何事情。虽然本学期的单片机课程即将结束,但是我学习单片机的过程还没有结束,以后还是要在程序的编写上多多下工夫。 此次的电子时钟设计给我奠定了一个实践基础,我会在以后的学习、生活中磨练自己,使自己适应于以后的竞争。当遇到不会或是设计不出来的地方,我们就会在QQ 群里讨论或者是同学之间相互帮助。团结就是力量,无论在现在的学习中还是在以后的工作中,团结都是至关重要的,有了团结会有更多的理念、更多的思维、更多的情感。 参考文献 1.1 出租车计价器设计要求 设计一个出租车自动计费器,计费包括起步价、行车里程计费、等待时间计费三部分,用七段数码管显示总金额,单价,运行里程,起步价为6元,超过6元,每一公里增加1元或2元,等待时间单价为每30秒钟1元,计费功能:费用的计算是按行驶里程收费。设起步价为6元。 1、当总金额<6元时,按起价计算费用 2、当总金额>6元时,每公里按1元或2元计费 3、等待累计时间>30s时,按1元/30s计费 4、S1为启动按钮、S2转换单价按钮、S3复位按钮。显示功能: 1、显示行驶里程:用三位数字显示,显示方式为“XX.X”,单位为km。计程范围0-99.9km,精确到0.1km。 2、显示单价:用两位数字显示,显示方式为“X.X”,单位为元。 3、显示总费用:用三位数字显示,显示方式为“XX.X”,单位为元。计价范围0-99.9元,精确到0.1元。 1.2 系统主要功能 本出租车自动计费,上电后显示最初的起步价,里程计费单价。同时具有运行,复位,转换等状态,可以切换白天与晚上不同计费单价,可以实现每等待30s收1元功能。出租车显示行驶的总费用,里程,单价。 1.3 方案论证与比较 方案一:采用数字电子技术,利用555定时芯片构成多谐振荡器,或采用外围的晶振电路作为时钟脉冲信号,采用计数芯片对脉冲尽心脉冲的计数和分频,最后通过译码电路对数据进行译码,将译码所得的数据送给数码管显示,一下是该方案的流程框图,方案一如图1-1所示: 图1-1方案一 方案二:采用EDA技术,根据层次化设计理论,该设计问题自顶向下可分为分频模块,控制模块 计量模块、译码和动态扫描显示模块,其系统框图如图1-2所示: 图1-2方案二 方案三:采用MCU技术,通过单片机作为主控器,采用外部晶振作为时钟脉冲,通过按键可以方便调节,以下是方案三的系统流程图,本方案主要是必须对于数字电路比较熟悉,成本又不高。方案图如图1-3所示: 图1-3方案三 方案总结:通过各个方案的比较,本次采用方案三,不但控制简单,而且成本低廉,设计电路简单。 第二章 出租车计价系统的硬件设计 2.1 AT89C51单片机介绍 AT89C51是一种带4K字节闪烁可编程可擦除只读存储器的低电压,高性能CMOS8位微处理器,俗称单片机。该器件采用ATMEL高密度非易失存储器制造技术制造,与工业标准的MCS-51指令集合输出管脚相兼容。由于将多功能8位CPU和闪烁存储器组合在单个芯片中,ATMEL的AT89S51是一种高效微控器,为很多嵌入式控制系统提供了一种灵活性且廉价的方案 单片机各引脚功能说明: VCC:供电电压。GND:接地。 P0口:P0口为一个8位漏级开路双向I/O口,每脚可吸收8TTL门流。当P1口的管脚第一次写1时,被定义为高阻输入。P0能够用于外部程序数据存储器,它可以被定义为数据/地址的第八位。在FIASH编程时,P0 口作为原码输入口,当FIASH进行。校验时,P0输出原码,此时P0外部必须被拉高。 P1口:P1口是一个内部提供上拉电阻的8位双向I/O口,P1口缓冲器能接收输出4TTL门电流。P1口管脚写入1后,被内部上拉为高,可用作输入,P1口被外部下拉为低电平时,将输出电流,这是由于内部上拉的缘故。在FLASH编程和校验时,P1口作为第八位地址接收。 P2口:P2口为一个内部上拉电阻的8位双向I/O口,P2口缓冲器可接收,输出4个TTL门电流,当P2口被写“1”时,其管脚被内部上拉电阻拉高,且作为输入。并因此作为输入时,P2口的管脚被外部拉低,将输出电流。这是由于内部上拉的缘故。P2口当用于外部程序存储器或16位地址外部数据存储器进行存取时,P2口输出地址的高八位。在给出地址“1”时,它利用内部上拉优势,当对外部八位地址数据存储器进行读写时,P2口输出其特殊功能寄存器的内容。P2口在FLASH编程和校验时接收高八位地址信号和控制信号。 P3口:P3口管脚是8个带内部上拉电阻的双向I/O口,可接收输出4个TTL门电流。当P3口写入“1”后,它们被内部上拉为高电平,并用作输入。作为输入,由于外部下拉为低电平,P3口将输出电流(ILL)这是由于上拉的缘故。P3口也可作为AT89S52的一些特殊功能口,P3口同时为闪烁编程和编程校验接收一些控制信号。 RST:复位输入。当振荡器复位器件时,要保持RST脚两个机器周期的高电平时间。 ALE/PROG:当访问外部存储器时,地址锁存允许的输出电平用于锁存地址的地位字节。 /PSEN:外部程序存储器的选通信号。在由外部程序存储器取指期间,每个机器周期两次/PSEN有效。但在访问外部数据存储器时,这两次有效的/PSEN信号将不出现。 /EA/VPP:当/EA保持低电平时,则在此期间选择外部程序存储器(0000H-FFFFH),不管是否有内部程序存储器。 XTAL1:反向振荡放大器的输入及内部时钟工作电路的输入。XTAL2:来自反向振荡器的输出。 2.2 里程计算、计价单元的设计 里程计算是通过安装在车轮上的霍尔传感器A44E检测到的信号,送到单片机,经处理计算,送给显示单元的。其原理如图2-1所示 图2-1 由于A44E 属于开关型的霍尔器件,其工作电压范围比较宽(4.5~18V),其输出的信号符合TTL 电平标准,可以直接接到单片机的I/O 端口上,而且其最高检测频率可达到1MHZ。 如图2-2,A44E 集成霍耳开关由稳压器A、霍耳电势发生器(即硅霍耳片)B、差分放大器C、施密特触发器D 和OC 门输出E 五个基本部分组成。 图2-2 路程测量电路 在输入端输入电压CC V,经稳压器稳压后加在霍耳电势发生器的两端,根据霍耳效应原理,当霍耳片处在磁场中时,在垂直于磁场的方向通以电流,则与这二者相垂直的方向上将会产生霍耳电势差H V 输出,该H V 信号经放大器放大后送至施密特触发器整形,使其成为方波输送到OC 门输出。当施加的磁场达到工作点(即OP B)时,触发器输出高电压(相对于地电位),使三极管导通,此时OC 门输出端输出低电压,通常称这种状态为开。当施加的磁场达到释放点(即rP B)时,触发器输出低电压,三极管截止,使OC 门输出高电压,这种状态为关。这样两次电压变换,使霍耳开关完成了一次开关动作。 我们选择了P3.5 口作为信号的输入端,内部采用计数的方式,车轮每转一圈(我们设车轮的周长是10米),霍尔开关就检测并输出信号,引起单片机对脉冲计数,当计数达到10次时,也就是0.1 公里,单片机就控制将金额自动的加增加,其计算公式:当前单价× 公里数=金额。 2.3 数据显示单元的设计 由于设计要求有单价(2 位)、路程(3位)、总金额(3位)显示输出,我们采用8个7段数码管动态显示。如图2-3: 图2-3 数据显示硬件电路图 2.4 按键单元的设计 电路共采用了四个按键,S1、S2、S3、S4,其功能分别是:S1 启动计价开关、S2 白天/晚上转换开关、S3 数据复位清零开关、S4闭合表示出租车正常运行,计价器也正常计价;若打开S4则表示出租车在暂停,转入判断是否收费程序,闭合超过30秒,开始计价。如图2-4: 图2-4 按键单元电路图 2.5 振荡电路 单片机内部有一个高增益、反相放大器,其输入端为芯片引脚XTAL1,其输出端为引脚XTAL2。通过这两个引脚在芯片外并接石英晶体振荡器和两只电容(电容和一般取33pF)。这样就构成一个稳定的自激振荡器。振荡电路脉冲经过二分频后作为系统的时钟信号,再在二分频的基础上三分频产生ALE信号,此时得到的信号时机器周期信号。振荡电路如图2-5所示: 图2-5 振荡电路 2.6 复位电路 复位操作有两种基本形式:一种是上电复位,另一种是按键复位。按键复位具有上电复位功能外,若要复位,只要按图中的RESET键,电源VCC经电阻R1、R2分压,在RESET端产生一个复位高电平。上电复位电路要求接通电源后,通过外部电容充电来实现单片机自动复位操作。上电瞬间RESET引脚获得高电平,随着电容的充电,RERST引脚的高电平将逐渐下降。RERST引脚的高电平只要能保持足够的时间(2个机器周期),单片机就可以进行复位操作。按键复位电路图如图2-6所示。 图2-6 复位电路 总电路图 第三章 出租车计价系统的软件设计 3.1主程序流程图记描述 由于用到了外部中断0,所以,按中断系统的编程结构在0000H处放置一条长跳转指令LJMP START跳转到主程序入口,在外部中断0的中断入口地址0003H处放置一条长跳转指令LJMP EXT0跳转到外部中断0服务程序处。主程序进行程序中用到的一些存储单元的初始化,T0,T1的初始化。首先,进行存储器单元初始化,将71H-79H单元赋值。然后进行定时器的设置。设置T1的工作方式为模式2,计数状态,自启动。T0的工作方式,模式1,定时状态,自启动,对输入的脉冲进行计数,给T0、T1赋初值。之后调用计数脉冲子程序,及等待子程序,数码管动态显示子程序,最后进入键盘扫描子程序进行扫描。主程序不断进行调用数码管显示子程序及键盘扫描子程序循环操作,等待中断。如图3-1: 图3-1 主程序流程图 3.2计费子程序流程图 72H存储总金额小数位,73H存储器总金额个位,78H存储器总金额十位,77H存储单价元。将72H中值放入A中,再将A与77H相加,即小数位与单价相加,结果存入A,再进行十进制调整,将结果存于R6,将高四位与低四位交换并屏蔽高四位,保留低四位与个位相加得到总金额个位。若总金额超过10则清零,并且总金额十位加1。将R6中数值取出,屏蔽高四位,输出总金额小数位。如图3-2: 图3-2 计费子程序流程图 3.3 等待是否收费子程序 在出租车的计价系统中,出租车在等待的时候也要计价,本设计体现了这点。程序判断了出租车是否停止,若停止30秒以上是,开始计价。55H中赋初值20,56H中赋初值10,总共30秒延时。超过30秒时,调用计费子程序。如图3-3 图3-3 等待是否收费子程序流程图 3.4 按键子程序流程图 图3-4 等待收费子程序流程图 3.5 外部中断0流程图 图3-5 外部中断0子程序流程图 程序 ORG 0000H LJMP MAIN ORG 0003H;外部中断入口地址 LJMP REST ORG 030H MAIN: MOV 71H,#00H;脉冲计数每10个脉冲里程数加0.1显示缓冲单元 MOV 72H,#00H;存储总金额小数位 MOV 73H,#00H;存储总金额个位 MOV 74H,#00H;存储里程数小数位 MOV 75H,#00H;存储里程数个位 MOV 76H,#00H;存储单价角位 MOV 77H,#01H;存储单价元位 MOV 78H,#00H;存储总金额十位 MOV 79H,#00H;存储里程数十位 MOV 55H,#20;给定等待初值20*50ms MOV 56H,#10;10秒等待时间初值 SETB EA;开总中断 SETB EX0;开外部中断0 MOV TCON,#04H;外部中断0低电平 MOV TMOD,#61H;设置使用定时器1,模式2,计数状态,自启动,定时器0,模式1,定时状态,自启动 MOV TL0,#0B0H;50ms初值设定C350H MOV TH0,#03CH MOV TL1,#0FFH;设置定时器低八位初值为FFH,即有一脉冲输入就溢出 MOV TH1,#0FFH;设置定时器高八位初值为FFH,用于重装低八位 START: LCALL PULSE;调用计数脉冲子程序 LCALL WAIT;调用等待子程序 LCALL DISPLAY;调用显示程序进行显示 LCALL KEY;调用键盘扫描程序 SJMP START;返回START处继续循环执行 REST: PUSH ACC MOV 71H,#00H;脉冲计数每10个脉冲里程数加1显示缓存单元 MOV 72H,#00H;存储总金额个位 MOV 73H,#00H;存储总金额十位 MOV 74H,#00H;存储里程数个位 MOV 75H,#00H;存储里程数十位 MOV 78H,#00H;存储总金额百位 MOV 79H,#00H;存储里程数百位 MOV 55H,#20 MOV 56H,#10 POP ACC RETI PULSE: JBC TF1,PTO1;TF1为1则转PT01 LJMP PSOUT PTO1: MOV 55H,#20 MOV 56H,#10 INC 71H MOV R1,71H CJNE R1,#0AH,PSOUT;脉冲调整,改变立即数即可;里程数个位74H,R1不等于0AH则转PSOUT MOV 71H,#00H INC 74H LCALL MONEY MOV R1,74H CJNE R1,#0AH,PSOUT;里程数十位75H MOV 74H,#00H INC 75H MOV R1,75H CJNE R1,#0AH,PSOUT MOV 75H,#00H INC 79H MOV R1,79H CJNE R1,#0AH,PSOUT MOV 79H,#00H PSOUT:RET WAIT: JBC TF0,PTO2 LJMP CHU0 PTO2:MOV TL0,#0B0H MOV TH0,#03CH DJNZ 55H,CHU0;不为0则转 MOV 55H,#20 DJNZ 56H,CHU0 MOV 56H,#10;30秒等待计时 INC 72H MOV A,72H LCALL MONEY1 CHU0:RET MONEY: MOV A,72H ADD A,77H MONEY1:DA A MOV R6,A SWAP A ANL A,#0FH ADD A,73H MOV 73H,A CJNE A,#0AH,MONEY2 MOV 73H,#00H INC 78H MOV A,78H CJNE A,#0AH,MONEY2 MOV 78H,#00H MONEY2:MOV A,R6 ANL A,#0FH MOV 72H,A RET DISPLAY:MOV A,78H CJNE A,#00H,L2 MOV A,73H CJNE A,#06H,L1 L1:JNC L2 MOV A,#82H ADD A,#80H MOV P0,A MOV P2,#02H LCALL DELAY MOV P2,#00H MOV A,#0C0H MOV P0,A MOV P2,#01H LCALL DELAY MOV P2,#00H MOV A,#0C0H MOV P0,A MOV P2,#40H LCALL DELAY MOV P2,#00H LJMP L3 L2:MOV A,73H MOV DPTR,#TAB MOVC A,@A+DPTR ADD A,#80H MOV P0,A MOV P2,#02H LCALL DELAY MOV P2,#00H MOV A,72H MOVC A,@A+DPTR MOV P0,A MOV P2,#01H LCALL DELAY MOV P2,#00H MOV A,78H MOVC A,@A+DPTR MOV P0,A MOV P2,#40H LCALL DELAY MOV P2,#00H L3:MOV A,75H MOV DPTR,#TAB MOVC A,@A+DPTR ADD A,#80H MOV P0,A MOV P2,#08H LCALL DELAY MOV P2,#00H MOV A,74H MOVC A,@A+DPTR MOV P0,A MOV P2,#04H LCALL DELAY MOV P2,#00H MOV A,77H MOVC A,@A+DPTR ADD A,#80H MOV P0,A MOV P2,#20H LCALL DELAY MOV P2,#00H MOV A,76H MOVC A,@A+DPTR MOV P0,A MOV P2,#10H LCALL DELAY MOV P2,#00H 【51单片机课程设计课题】相关文章: 基于AT89S51单片机多功能交通灯控制系统设计下载04-25 基于STC89C51单片机的智能送餐机器人的设计与开发09-10 51单片机电子时钟论文04-18 c51单片机实习报告04-08 基于51单片机的毕业论文题目05-08 秒表单片机课程设计04-20 单片机课程设计参考08-06 单片机课程设计格式08-06 优秀单片机课程设计09-05篇3:51单片机多任务编程设计及应用
篇4:51单片机课程设计课题
篇5:51单片机课程设计课题
篇6:51单片机课程设计课题