单片机实验
1、实验1 基本输入输出实验
题目:在Proteus环境下,设计基于51单片机(采用AT89C51)控制电路。P3.2和P3.3口线接两个按钮开关K0、K1,P1口和P2口接了两个共阴极LED显示器。编程实现:开始显示数字50,定义K0和K1分别为 +1 和 -1 键,即按K0显示的数字是 +1,按K1显示的数字是 -1。当显示数字 +1 到99后(或显示数字 -1 到0后),恢复显示数字初值50。K0和K1的管理采用查询的方式。
代码:
sbit K0=P3^2;
sbit K1=P3^3;
uchar data i; //定一个RAM单元
uchar data dir_buf[2]; //显示缓存区
uchar code dirtable[18]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}; //显示表
void delay(uint xms) //延时函数
{
uint t1,t2;
for(t1=0;t1<xms;t1++)
for(t2=0;t2<120;t2++);
}
void display() //显示函数
{
P1=dirtable[dir_buf[0]]; //P2口显示
P2=dirtable[dir_buf[1]]; //P1口显示
}
void key() //按键管理函数
{
uchar key1;
K0=1;K1=1; //读按键前先写1
key1=P3; //读按键值
key1=key1&0x0c; //屏蔽掉无用位
while(key1!=0x0c) //判断是否有按键按下
{
delay(6); //去抖动
key1=P3; //再读
key1=key1&0x0c;
while(key1!=0x0c) //再次判断是否有按键按下
{
if(K0==0) //K0按下的时候加1
i=i+1;
else
i=i-1;
K0=1;K1=1;
key1=P3;
key1=key1&0x0c;
while(key1!=0x0c) //判断按键是否抬起
{
key1=P3;
key1=key1&0x0c;
}
}
}
}
void main()
{
i=50;
while(1)
{
if(i>99 || i<0)
{
i=50;
}
else
{
key(); //调用按键管理
dir_buf[0]=i/10; //十位
dir_buf[1]=i%10; //个位
display();
}
}
}
电路图:
实验2 并行I/O口应用设计实验
题目要求:设计用两个开关S1、S2控制P1.0引脚实现蜂鸣器报警的程序,要求如下: (1)开关S1、S2分别接到P3.0、P3.1引脚上。 (2)蜂鸣器接到P1.0引脚上。 (3)开关S1闭合发出频率为1kHz的声音,发声时间约为1s。 (4)开关S2闭合发出频率为500Hz的声音,发声时间约为0.5s。 提示: (1)假设单片机系统时钟频率为12MHz。 (2)采用软件延时。 (3)在Proteus中,可以使用的蜂鸣器有两种,分别是SOUNDER和BUZZER,实验前请查阅资料了解蜂鸣器的使用方法。
代码:
sbit S1=P3^0;
sbit S2=P3^1;
sbit sourder=P1^0;
uchar mask=0x03;
uint t3,t4;
void delay(uint xms) //延时函数
{
uint t1,t2;
for(t1=0;t1<xms;t1++)
for(t2=0;t2<110;t2++);
}
void delay_time(uint times)
{
uint i;
for(i=0;i<times;i++);
}
void play(times) //根据不同的频率进行播放的函数
{
sourder=1;
delay_time(times);
sourder=0;
delay_time(times);
}
void keyspan() //按键管理函数
{
uchar key;
S1=1;S2=1; //读按键前先写1
key=P3; //读按键值
key=key&mask; //屏蔽掉无用位
while(key!=mask) //判断是否有按键按下
{
delay(6); //去抖动
key=P3; //再读
key=key&mask;
while(key!=mask) //再次判断是否有按键按下
{
if(S1==0) //S1按下的时候加1
{
for(t3=0;t3<1000;t3++) //频率为1000hz,发声时间为1s
play(62); //0.5ms延时来获取1khz的频率(高低电平之间的转换延时)
}
else
{
for(t4=0;t4<250;t4++) //频率为500hz,发声时间为0.5s
play(125); //1ms的延时来获取500hz的频率(高低电平之间的转换延时)
}
S1=1;S2=1;
key=P3;
key=key&mask;
while(key!=mask) //判断按键是否抬起
{
key=P3;
key=key&mask;
}
}
}
}
void main()
{
sourder=0;
while(1)
{
keyspan();
}
}
电路图:
实验3 中断应用实验
本实验为租车里程测量模拟计算实验,要求如下: 假设出租车车轮转1圈产生2个负脉冲,轮胎周长为2m。试测量并显示出租车的行驶里程,测量与显示范围0~999999米。设计硬件电路并编写程序。(车轮转动信号通过中断方式获取) 设置一个按键S1,控制出租车行驶里程显示的启动和清零。(用中断方式控制) 【提示】 (1)行驶里程显示可以使用7段六位共阴级数码管(7SEG-MPX6-CC)或7段六位共阳级数码管(7SEG-MPX6-CA)。 (2)可以用Proteus中的虚拟仪器“SIGNAL GENERATOR(信号发生器)”(见右图)作为外部中断输入信号,模拟车轮转动产生的脉冲信号。 (3)实验前查阅资料,了解出租车里程计算和计价器计价原理。
代码:
unsigned char code table[10]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};//数字显示的代码表(0~9)
bit flag = 0; //里程计数开关(0停止并清零,1启动)
ulong s = 0; //里程计量(满999999后清零)
uchar odometer[6]; //里程表
ulong c[7]= {1,10,100,1000,10000,100000,1000000}; //进行每位的值计算
void delay(uchar xms)//延时函数
{
uchar i,j;
for(i=0;i<xms;i++)
for(j=0;j<110;j++);
}
void display() //显示函数
{
int bitcode,i;
// odometer [0] = (s%10)/1; //第6位
// odometer [1] = (s%100)/10; //第5位
// odometer [2] = (s%1000)/100; //第4位
// odometer [3] = (s%10000)/1000; //第3位
// odometer [4] = (s%100000)/10000; //第2位
// odometer [5] = (s%1000000)/100000;//第1位
for(i=0;i<6;i++) //找到规律一次性初始化各个位
{
odometer[i] = (s%c[i+1])/c[i];
}
bitcode=0xfe;
for(i=5;i>=0;i--)
{
P2=bitcode;
P1=table[odometer[i]];
delay(1);
bitcode=(bitcode<<1)| 0x01;
}
}
void interupt_0(void) interrupt 0 //信号中断控制
{
if(flag==1)
{
s++;
if(s>999999)
s=0;
}
}
void interupt_1(void) interrupt 2 //按钮S1中断控制
{
if(flag==0) //启动
{
flag=~flag;
}
else
{
s=0; //清零
flag=~flag;
}
}
void main(void)
{
IT0 = 1; //外部中断0
IT1 = 1; //外部中断1
EA = 1; //中断允许
EX1 = 1;
EX0 = 1;
PX1 = 1;
while(1)
{
display();
}
}
电路图:
实验4 定时器应用实验
本实验为“秒表设计”实验,即用单片机定时/计数器设计一个秒表。具体要求如下: (1)秒表显示格式为“0000”,前两位为秒(s),计时到60s清0,后两位为百分之一秒,计到100即清0并且使前2位加1。 (2)设置两个按键K1和K2,其中,K1控制秒表的“启动/暂停”(即按一次“启动”,再按一次“暂停”),K2控制秒表“清零”,按键的同时要显示按键状态值(设“启动”、“暂停”和“清零”三种按键状态值分别为1、2、3)。要求K1和K2按中断方式工作。 【提示】 秒表显示和按键状态值显示可以使用7段六位共阴级数码管(7SEG-MPX6-CC)或7段六位共阳级数码管(7SEG-MPX6-CA),显示功能分配见下图。
代码:
分析:
在这个实验中,我使用的是工作方式1,因为要求是百分之一秒来进行计时,故0.01s=10ms.故需要最大定时时间大于10ms的方式。(当然也可以使用方式0,思路是:让定时器5ms中断一次,可以设定一个计中断次数的值n,当中断两次即10ms,使秒表的百分位加1同时将n重新赋值为1),使用方式1的时候,让定时器10ms中断一次同时将百分位加1即可。 方式1:定时初值计算:计数初值 = 65536– 10ms / ( 12MHz×12 )=65536-10000=55536。
unsigned char code table[10]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};//数字显示的代码表(0~9)
int count; //led显示 1 启动 2 暂停 3 清零
bit isStart; //开始 暂停标志位 0 开始 1 暂停
ulong s = 0; //秒表计时整体时间
uchar time[6]; //经过运算后显示管各位的数值
ulong c[7]= {1,10,100,1000,10000,100000,1000000}; //进行每位的值计算
void delay(uchar xms) //延时函数
{
uchar i,j;
for(i=0;i<xms;i++)
for(j=0;j<110;j++);
}
void display() //显示函数
{
uchar bitcode,i;
for(i=0;i<6;i++) //找到规律一次性初始化各个位
{
time[i] = (s%c[i+1])/c[i];
}
time[5]=count; //将显示管的最高显示位设置位启动、暂停和清零的状态值
bitcode=0xdf;
for(i=0;i<6;i++)
{
P2=bitcode;
P1=table[time[i]];
delay(1);
if(i==4)
{
bitcode=0xfd; //当在显示管从左往右看的第二位的时候,重新赋值。
}
bitcode=(bitcode>>1)|0x82; //每次都不选显示管从左往右看的第二位(即不进行选通位选段)
}
}
void timer0() interrupt 1 //定时器0
{
TR0=0; //停止T0
if(isStart==0)
{
TH0=(65536-10000)/256;
TL0=(65536-10000)%256; //T0初始化
s++;
if(s>6000)
{
s=0;
}
}
else if(isStart==1)
{
TR0=0;
}
TR0=1;
}
void my_int0(void) interrupt 0
{
if(isStart==0) //启动
{
isStart=1;
count=2;
}
else if(isStart==1) //暂停
{
isStart=0;
count=1;
}
}
void my_int1(void) interrupt 2
{
s=0; //清零
count=3;
isStart=1;
}
int main()
{
TMOD=0x01;
TH0=(65536-10000)/256;
TL0=(65536-10000)%256; //T0初始化
EA=1;
ET0=1; //IE定时器允许
TR0=1; //启动定时器
IT0 = 1; //外部中断0
IT1 = 1; //外部中断1
EX1 = 1;
EX0 = 1;
isStart=1; //开始,计时器暂停。
count=2;
while(1)
{
display();
}
}
电路图:
实验5 计数器应用实验
编写程序实现:用定时器/计数器T0监视一生产线(用逻辑触发器模拟),每产生10个工件,发出一个包装命令(即由P1.0引脚输出一个正脉冲),同时记录其箱数并将当前的箱数值显示在LED显示器上(显示到100后清0)。
分析 计数器工作方式选择及计数初值计算分析 因为题目中要求需要自动装箱,且10次装一次,故需要选取能自动装值的。 方式2的特点:初始化时把计数初值分别装入TL0和TH0中,当计数溢出时,由预置计数器自动给计数器TH0重新装初值。 初值计算:计数初值 = 256 – N ;即计数初值=256-10=246
代码:
uchar code table[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};//数码表
sbit Single=P1^0; //装箱脉冲信号
sbit s0=P3^0; //十位显示器位选
sbit s1=P3^1; //个位显示器位选
uint count=0; //装箱个数计数器
void delay(uchar xms) //延时函数
{
uchar i,j;
for(i=0;i<xms;i++)
for(j=0;j<110;j++);
}
void display()
{
P2=table[count/10]; //装箱数的十位数
s0=1;s1=0; //选择十位显示器
delay(1);
s0=0;s1=0;
P2=table[count%10]; //装箱数的个位数
s0=0;s1=1; //选择个位数
delay(1);
s0=0;s1=0;
}
void count0() interrupt 1 //T0当作计数器
{
count=count+1; //累计装箱数
if(count==100) //做溢出处理
count=0;
Single=1; //装箱控制信号
delay(1);
Single=0;
}
void main()
{
count=0;
Single=0;
TMOD=0x06;
TH0=TL0=256-10; //使用方式2,初值可以自动装入8位定时器/计数器中
EA=1;
ET0=1;
TR0=1;
while(1)
{
display();
}
}
电路图: 实验结果: (1)程序刚开始运行 (2)产生了10个工件,进行一次包装 (3)第99次装箱,即产生了990个工件
实验6 D/A转换实验
波形发生器设计。
- 功能要求 (1)有4个功能键,分别用来选择输出:三角波、梯形波、方波和正弦波。 (2)按下某个功能键,进入中断,在中断程序中查询,确定按下的是哪个功能键,并输出对应的波形。
(3)有一个2位的数码管显示器,显示功能号01、02、03、04,分别代表输出三角波、梯形波、方波和正弦波。 - 设计任务 (1)完成单片机最小系统电路设计。 (2)完成按键电路设计。 (3)完成D/A转换及接口电路的设计。 (4)完成显示电路设计。
代码:
uchar code table[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};//数码表
sbit s0=P3^0; //十位显示器位选
sbit s1=P3^1; //个位显示器位选
sbit KEY1=P2^0; //三角波
sbit KEY2=P2^1; //梯形波
sbit KEY3=P2^2; //方波
sbit KEY4=P2^3; //正弦波
code uchar sintab[]={0x7f,0x8a,0x95,0xa0,0xab,0xb5,0xbf,0xc8,
0xd1,0xd9,0xe1,0xe8,0xee,0xf3,0xf7,0xfb,0xfd,0xff,0xff};
uint count=0; //功能键标志(01、02、03、04,分别代表输出三角波、梯形波、方波和正弦波)
uint cou=0;
//延时函数
void delay(uchar xms)
{
uchar t1,t2;
for(t1=1;t1<xms;t1++)
for(t2=1;t2<120;t2++);
}
void display()
{
P1=table[count/10]; //十位数
s0=1;s1=0; //选择十位显示器
delay(1);
s0=0;s1=0;
P1=table[count%10]; //个位数
s0=0;s1=1; //选择个位数
delay(1);
s0=0;s1=0;
}
//三角波
void Triangle()
{
uchar i = 0;
while(1)
{
if(KEY1==0)
{
for(i=0;i<255;i++)
{DAC0832=i;display();} //从0开始到FEH(上升阶段)
for(i=255;i>0;i--)
{DAC0832=i;display();} //从FEH开始到0(下降阶段)
}
else
{
break;
}
}
}
//梯形波
void Trapezoidal()
{
uchar i = 0;
while(1)
{
if(KEY2==0)
{
for(i=255;i>0;i--)
{DAC0832=i;display();}
for(i=0;i<255;i++)
{DAC0832=0;display();}
for(i=0;i<255;i++)
{DAC0832=i;display();}
}
else
{
break;
}
}
}
//方波
void square()
{
uchar i = 0;
while(1)
{
if(KEY3==0)
{
for(i=0;i<100;i++)
{DAC0832=255;display();}
for(i=0;i<100;i++)
{DAC0832=0;display();}
}
else
{
break;
}
}
}
//正弦波
void sin()
{
uchar i = 0;
while(1)
{
if(KEY4==0)
{
for(i=0;i<18;i++)
{DAC0832 = sintab[i];display();} //第1个1/4周期
for(i=18;i>0;i--)
{DAC0832 = sintab[i];display();} //第2个1/4周期
for(i=0;i<18;i++)
{DAC0832 = ~sintab[i];display();} //第3个1/4周期
for(i=18;i>0;i--)
{DAC0832 = ~sintab[i];display();} //第4个1/4周期
}
else
{
break;
}
}
}
void bo()
{
if(count==01)
{
Triangle();
}
if(count==02)
{
Trapezoidal();
}
if(count==03)
{
square();
}
if(count==04)
{
sin();
}
}
void int0_interrupt(void) interrupt 0 using 3//外部中断0
{
EX0=0;//关中断
if(KEY1==0) {count=01;}
if(KEY2==0) {count=02;}
if(KEY3==0) {count=03;}
if(KEY4==0) {count=04;}
EX0=1;//开中断
}
void main()
{
IT0=1;//INT0下降沿中断
EX0=1;
EA=1;
while(1)
{
bo();
}
}
电路图:
实验结果: (1)三角波 (2)梯形波 (3)方波 (4)正弦波
实验7 A/D转换实验
直流数字电压表的设计与实现。
- 功能要求 数字电压表(DigitalVoltmeter)简称DVM,它是采用数字化测量技术,把连续的模拟量转换成不连续、离散的数字形式并加以显示的仪表。要求使用AT89C51单片机,采用动态显示的方式,把8通道模数转换器ADC0808/0809采样的电压值的大小经单片机处理后由数码管显示出来,量程为0
? 5V,显示格式为X.XXX。 - 设计任务 (1)完成单片机最小系统电路设计。 (2)完成A/D转换及接口电路的设计。 (3)完成显示电路设计。
代码:
sbit P33=P3^3;
sbit P26=P2^6;
sbit P25=P2^5;
sbit P24=P2^4;
sbit P23=P2^3;
uchar code table[10]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};//普通数字显示的代码表(0~9)
uchar code numdep[10]={0xbf,0x86,0xdb,0xcf,0xe6,0xed,0xfd,0x87,0xff,0xef};//带小数点的数字显示的代码表(0~9)
uchar vdata; //输出的数字电压值
int c[5]= {10000,1000,100,10,1}; //进行每位的值计算
int t[4]; //经过运算后显示管各位的数值
//延时函数
void delay(uchar xms)
{
uchar i,j;
for(i=0;i<xms;i++)
for(j=0;j<60;j++);
}
//显示函数
void display()
{
uchar i;
float dv;
int s=0;
dv=0.0196078431373*vdata; //将数字电压值转换成模拟电压值(5/255=0.0196078431373)
s=(int)(dv*1000); //具有一定格式的电压值(例如2.0133...转化为2013)
for(i=0;i<4;i++) //找到规律一次性初始化各个位
{
t[i] = (s%c[i])/c[i+1];
}
for(i=0;i<4;i++)
{
if(i==0)
{
P26=0;P25=1;P24=1;P23=1;
P1=numdep[t[i]];
}
if(i==1)
{
P26=1;P25=0;P24=1;P23=1;
P1=table[t[i]];
}
if(i==2)
{
P26=1;P25=1;P24=0;P23=1;
P1=table[t[i]];
}
if(i==3)
{
P26=1;P25=1;P24=1;P23=0;
P1=table[t[i]];
}
delay(1);
}
}
void interrupt_1() interrupt 2
{
vdata=ADC0809;
ADC0809=0;
}
void main()
{
EA=1;EX1=1;IT1=1;
vdata=0;
ADC0809=0;
while(1)
{
display();
}
}
电路图: 实验结果: (1)显示1.450V (2)显示2.450V (3)显示3.901V
|