本章内容将结合之前所学的周期、中断的相关知识完成新的实践内容。
内容包括:
利用中断发出1Khz的方波信号,驱动蜂鸣器鸣叫。
LED数码管秒表的制作。
使用定时器实现一个LCD显示时钟。
两个单片机串口通信。
将单片机串口与笔记本电脑串口模块相连,单片机发送内容,笔记本电脑接收。
目录
一、驱动蜂鸣器鸣叫
1、蜂鸣器介绍
蜂鸣器是一种电子元件,通常用于发出嗡嗡声或蜂鸣声。它通常由一个振动器和一个驱动电路组成。振动器产生声音的频率,而驱动电路控制振动器的工作方式和频率。
在嵌入式系统或电子设备中,蜂鸣器常用于发出警报、提醒或产生特定的声音效果。它们可以通过改变驱动电路中的频率和脉冲宽度来产生不同的声音。例如,通过调整驱动信号的频率和持续时间,可以使蜂鸣器发出连续的长蜂鸣声、短促的蜂鸣声或者间隔的蜂鸣声。
常见的蜂鸣器类型:压电蜂鸣器、磁性蜂鸣器。
51单片机上的蜂鸣器是一种常见的外围设备,用于发出声音信号。通常情况下,它是通过将电流传输到蜂鸣器内部的压电元件来产生声音。
2、任务实现
要求:利用T1的中断控制P1.7引脚输出频率为1kHz方波音频信号,驱动蜂鸣器发声。系统时钟为12MHz。方波音频信号周期1ms,因此T1的定时中断时间为0.5 ms,进入中断服务程序后,对P1.7求反。
proteus仿真电路原理图:
代码示例:
#include <REGX51.H>
sbit sound = P3^5;
void main()
{
EA=1; //开总中断
ET1=1; //允许定时器T1中断
TMOD=0x10; //TMOD=0001 000B,使用T1的方式1定时
TH1=0xFE;
TL1=0x33; //设置定时器1的初值为0xFE33,使定时器每次溢出所需的时间为500微秒
TR1=1; //启动T1
while(1)
{
}
}
void Timer1_Riutine(void) interrupt 3
{
sound = ~sound;
TH1=0xFE;
TL1=0x33;
}
仿真结果:
二、LED数码管秒表的制作
1、LED数码管
LED数码管是一种常用的数字显示器件,通常由7段LED组成,每段LED表示显示数字的一个部分(如水平线、垂直线等),加上一个小数点LED。它们排列成数字“8”的形状,用于显示数字0到9以及一些特殊字符。LED数码管广泛应用于各种计时器、计数器、温度计等数字显示应用中。LED数码管可分为共阳极和共阴极两种类型,它们的工作原理和接线方式有所不同。共阳极LED数码管中,所有的阳极连接在一起,而共阴极LED数码管中,所有的阴极连接在一起。
2、计时器
计时器是一种电子设备或计算机组件,用于测量和跟踪时间的流逝。它可以在各种应用中使用,从简单的秒表到复杂的定时器和计数器。计时器通常包含一个计时器芯片或是微控制器的计时器模块,用于精确地测量时间。
在微控制器中,计时器是一种硬件模块,用于生成精确的时间延迟或者跟踪时间间隔。它可以用于测量微秒、毫秒、甚至更长时间间隔。通过编程,可以配置计时器以执行各种功能,如生成脉冲、定时中断、PWM(脉冲宽度调制)输出等。
在电子应用中,计时器通常用于实现各种功能,例如控制设备的运行时间、测量事件的持续时间、生成定时器中断以执行特定任务等。在你描述的LED数码管秒表任务中,使用计时器可以让你以百毫秒为单位精确地测量时间,并实现秒表的功能。
上图为AT89S51定时器/计数器结构,如图所示,定时器/计数器T0由特殊功能寄存器TH0、TL0构成,T1由特殊功能寄存器TH1、TL1构成。(具体相关知识请查阅相关资料或浏览往期博客进行了解学习)
2、任务实现
要求:用2位数码管显示计时时间,最小计时单位为“百毫秒”,计时范围0.1~9.9s。当第1次按一下计时功能键时,秒表开始计时并显示;第2次按一下计时功能键时,停止计时,将计时的时间值送到数码管显示;如果计时到9.9s,将重新开始从0计时;第3次按一下计时功能键,秒表清0。再次按一下计时功能键,则重复上述计时过程。
proteus仿真电路原理图:
代码示例:
#include <REGX51.H>
typedef unsigned int uint;
typedef unsigned char uchar;
uchar discode1[]={0xbf,0x86,0xdb,0xcf,0xe6,0xed,0xfd,0x87,0xff,0xef};//第一个
uchar discode2[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};//第二个
uchar timer = 0;//中断次数
uchar second;//秒数
uchar key = 0;//按键次数
sbit keyif = P3^7;//定义按键引脚
void Delay1ms(unsigned int xms) //@12.000MHz
{
unsigned char i, j;
while(xms)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
xms--;
}
}//延时函数,按键消抖
void main()
{
TMOD = 0x01;
ET0 = 1;
EA = 1;
second = 0;
P0=discode1[second/10]; //显示秒位0
P2=discode2[second%10]; //显示0.1s位0
while(1)
{
if(keyif == 0)
{
Delay1ms(5);
if(keyif == 0)
{
key++;
switch(key)
{
case 1:
TH0 = 0xEE;
TL0 = 0x00;
TR0 = 1;
break;
case 2:
timer = 0;
TR0 = 0;
break;
case 3:
key = 0;
second = 0;
P0 = discode1[0];
P2 = discode2[0];
break;
}
while(keyif == 0); //如果按键时间过长在此循环
}
}
}
}
void timer0() interrupt 1
{
TR0 = 0;
timer++;
if(timer == 20)
{
second++;
P0=discode1[second/10]; //根据计时,即时显示秒位
P2=discode2[second%10]; //根据计时,即时显示0.1s位
timer = 0;
}
if(second == 99)//计时到达9.9s
{
TR0 = 0;//停止计时
second = 0;//秒数清零
key = 2;//停止计时
}
else
{
TR0 = 1;//继续计时
}
}
三、实现一个LCD显示时钟(采用LCD 1602)
1、LCD 1602
LCD 1602(也称为16x2 LCD)是一种常见的字符型液晶显示模块,其名称指的是其具有16个字符宽度和2行显示的特性。在使用LCD 1602时,用户可以通过编程指令来控制显示内容、光标位置、背光状态等。它广泛应用于各种嵌入式系统、DIY项目和学术实验中,用于显示信息、状态、菜单等。
引脚图及功能:
编号 | 符号 | 引脚说明 | 标号 | 符号 | 引脚说明 |
1 | GND | 接地 | 9 | D2 | 数据 |
2 | VCC | 电源正极 | 10 | D3 | 数据 |
3 | VL | 液晶显示偏压 | 11 | D4 | 数据 |
4 | RS | 数据/命令选择 | 12 | D5 | 数据 |
5 | RW | 读/写选择 | 13 | D6 | 数据 |
6 | E | 使能信号 | 14 | D7 | 数据 |
7 | D0 | 数据 | 15 | BL+ | 背光源正极 |
8 | D1 | 数据 | 16 | BL- | 背光源负极 |
控制指令:
序号 | 指令 | RS | R/W | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
1 | 清屏 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
2 | 光标复位 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | x |
3 | 输入方式设置 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | I/D | S |
4 | 显示开关控制 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | D | C | B |
5 | 光标或字符移位控制 | 0 | 0 | 0 | 0 | 0 | 1 | S/C | R/L | x | x |
6 | 功能设置 | 0 | 0 | 0 | 0 | 1 | DL | N | F | x | x |
7 | 字符发生存储器地址设置 | 0 | 0 | 0 | 1 | 字符发生存储器地址 | |||||
8 | 数据存储器地址设置 | 0 | 0 | 1 | 显示数据存储器地址 | ||||||
9 | 读忙标志或地址 | 0 | 1 | BF | 计数器地址 | ||||||
10 | 写入数据至CGRAM或DDRAM | 1 | 0 | 要写入的数据内容 | |||||||
11 | 从CGRAM或DDRAM中读取数据 | 1 | 1 | 读取的数据内容 |
连接方式 :
LCD1602与单片机的连接有两种方式,一种是直接控制方式,另一种是间接控制方式。它们的区别只是所用的数据线的数量不同。
1.直接控制方式 :LCD1602的8根数据线和3根控制线E,RS和R/W与单片机相连后即可正常工作。一般应用中只须往LCD1602中写入命令和数据,因此,可将LCD1602的R/W读/写选择控制端直接接地,这样可节省1根数据线。VO引脚是液晶对比度调试端,通常连接一个10kΩ的电位器即可实现对比度的调整;也可采用将一个适当大小的电阻从该引脚接地的方法进行调整,不过电阻的大小应通过调试决定。
2.间接控制方式 :间接控制方式也称为四线制工作方式,是利用HD44780所具有的4位数据总线的功能,将电路接口简化的一种方式。为了减少接线数量,只采用引脚DB4~DB7与单片机进行通信,先传数据或命令的高4位,再传低4位。采用四线并口通信,可以减少对微控制器I/O的需求,当设计产品过程中单片机的I/O资源紧张时,可以考虑使用此方法。
2、任务实现
要求:使用定时器实现一个LCD显示时钟。采用LCD1602,最小计时单位是秒,如何获得1s的定时?可将T0定时时间定为50ms,采用中断方式进行溢出次数累计,满20次,则秒计数变量second加1;若秒计满60,则分计数变量minute加1,同时将秒计数变量second清0;若分钟计满60,则小时计数变量hour加1;若小时计数变量满24,则将小时计数变量hour清0。
proteus仿真电路原理图:
代码示例:
说明:在使用LCD之前需要初始化,同时在使用过程中还需要写指令,写数据等操作,相应操作步骤及对应代码如下。
写指令步骤:
1:将RS置0;
2:将RW置0;
3:将指令(Command)写入LCD_DataPort;
4:将EN置1;
5:延时1ms;
6:将EN置0;
7:延时1ms;
//写指令
void LCD_WriteCommand(unsigned char Command)
{
LCD_RS=0;
LCD_RW=0;
LCD_DataPort=Command;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
写数据步骤:
1:将RS置0;
2:将RW置0;
3:将数据(Data)写入LCD_DataPort;
4:将EN置1;
5:延时1ms;
6:将EN置0;
7:延时1ms;
//写数据
void LCD_WriteData(unsigned char Data)
{
LCD_RS=1;
LCD_RW=0;
LCD_DataPort=Data;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
下述代码为该任务总代码:
#include <REGX51.H>
typedef unsigned char uchar;
typedef unsigned int uint;
uchar Hour=23,Min=59,Sec=55;
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P3
void LCD_Delay()
{
unsigned char i, j;
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
void LCD_WriteCommand(unsigned char Command)//LCD写命令
{
LCD_RS=0;
LCD_RW=0;
LCD_DataPort=Command;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
void LCD_WriteData(unsigned char Data)//LCD写数据
{
LCD_RS=1;
LCD_RW=0;
LCD_DataPort=Data;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
void LCD_SetCursor(unsigned char Line,unsigned char Column)//LCD设置光标位置
{
if(Line==1)
{
LCD_WriteCommand(0x80|(Column-1));
}
else if(Line==2)
{
LCD_WriteCommand(0x80|(Column-1+0x40));
}
}
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)//LCD显示字符
{
LCD_SetCursor(Line,Column);
LCD_WriteData(Char);
}
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)//LCD显示字符串
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=0;String[i]!='\0';i++)
{
LCD_WriteData(String[i]);
}
}
int LCD_Pow(int X,int Y)
{
unsigned char i;
int Result=1;
for(i=0;i<Y;i++)
{
Result*=X;
}
return Result;
}
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)//LCD显示数字
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
}
}
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
unsigned char i;
unsigned int Number1;
LCD_SetCursor(Line,Column);
if(Number>=0)
{
LCD_WriteData('+');
Number1=Number;
}
else
{
LCD_WriteData('-');
Number1=-Number;
}
for(i=Length;i>0;i--)
{
LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
}
}
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i,SingleNumber;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
SingleNumber=Number/LCD_Pow(16,i-1)%16;
if(SingleNumber<10)
{
LCD_WriteData(SingleNumber+'0');
}
else
{
LCD_WriteData(SingleNumber-10+'A');
}
}
}
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
}
}
void LCD_Init()
{
LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
LCD_WriteCommand(0x01);//光标复位,清屏
}
void Timer0_Init() //定时器初始化
{
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初值,与通过计算的得到有一定误差。
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1;
EA=1;//将中断打通即令ET0=1。
}
void main()
{
LCD_Init();
Timer0_Init();
LCD_ShowString(1,1," : : ");
while(1)
{
LCD_ShowNum(1,1,Hour,2);
LCD_ShowNum(1,4,Min,2);
LCD_ShowNum(1,7,Sec,2);
}
}
void Timer0_Routine() interrupt 1//定时器0的中断程序
{
static unsigned int T0Count;//静态变量,让局部变量变为全局变量。
TL0 = 0x18;
TH0 = 0xFC;//重新赋值,计时器重新开始运行。
T0Count++;
if(T0Count>=1000)//定时为1s
{
T0Count=0;
Sec++;
if(Sec>=60)
{
Sec=0;
Min++;
if(Min>=60)
{
Min=0;
Hour++;
if(Hour>=24)
{
Hour=0;
}
}
}
}//每隔1s。
}//中断函数
仿真结果:
四、两个单片机串口通信
1、串口
串口是一种用于数据通信的通用接口标准,通常用于在计算机和外部设备之间传输数据。串口允许设备以序列化的方式在物理链路上发送和接收数据。串口可以是硬件实现,也可以是通过软件模拟的虚拟串口。串口通信可以是同步的或异步的。在同步串口通信中,发送方和接收方使用共同的时钟信号进行数据传输。而在异步串口通信中,数据通过起始位、数据位、校验位和停止位来进行传输,不需要共享时钟信号。
常见的串口标准:RS-232、RS-422、RS-485等。
其中RS-232是最常见的串口标准之一,它定义了信号电平、连接器和数据传输协议。串口通常具有多个引脚,包括数据线、控制线和地线等。
串口在许多应用中仍然广泛使用,尤其是在嵌入式系统、网络设备、工业控制和通信设备中。它们通常用于连接调制解调器、打印机、传感器、GPS接收器等外部设备。
51单片机中的串口:
在51系列单片机中,串口通常被称为UART(通用异步收发器),它是一种常见的串行通信接口。UART允许单片机与外部设备进行异步的串行数据通信,而不需要共享时钟信号。
UART通常由两个主要寄存器控制:SBUF(串行数据缓冲器)和SCON(串行控制寄存器)。
如下为其结构图:
任务实现
要求:甲、乙两单片机进行串行通信。甲机把控制8个流水灯点亮的数据发送给乙机并点亮其P1口的8个LED。方式3比方式1多了1个可编程位TB8,该位一般作奇偶校验位。乙机接收到的8位二进制数据有可能出错,需进行奇偶校验,其方法是将乙机的RB8和PSW的奇偶校验位P进行比较,如果相同,接收数据;否则拒绝接收。
proteus仿真电路原理图:
代码示例:
甲单片机代码:
#include <REGX51.H>
sbit T_P=PSW^0;
unsigned char code Tab[8]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};//流水灯程序
void Send(unsigned char dat)
{
TB8=T_P;
SBUF=dat;
while(TI==0);
TI=0;
}
void Delay1ms(unsigned int xms) //@12.000MHz
{
unsigned char i, j;
while(xms)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
xms--;
}
}
void main()
{
unsigned char i;
TMOD=0x20;
SCON=0xc0;
PCON&=0x7f;
TH1=0xfd;
TL1=0xfd;
TR1=1;
while(1)
{
for(i=0;i<8;i++)
{
Send(Tab[i]);
Delay1ms(200);
}
}
}
乙单片机代码:
#include <REGX51.H>
sbit R_P=PSW^0;
unsigned char Receive()//接收一字节数据
{
unsigned char dat;
while(RI==0);//检测RI,RI=0,未接收完
RI=0; //接收数据完成RI手动清0
ACC=SBUF; //将接收缓冲器的数据存于ACC
if(RB8=R_P) //只有偶检验成功才能往下执行,接收数据
{
dat=ACC; //将ACC数据存于dat
return dat; //将接收的数据返回
}
}
void main()
{
TMOD=0x20;
SCON=0xd0;
PCON&=0x7f;
TH1=0xfd;
TL1=0xfd;
TR1=1;
while(1)
{
P2=Receive();
}
}
仿真结果:
LED依次亮起(注意LBL标注)
五、单片机发送,笔记本接收
任务实现
要求:将单片机串口与笔记本电脑串口模块相连,单片机每隔2秒发送“Hello C51”,笔记本电脑用串口助手软件接收。 如果串口助手发送字符“0" 给单片机,则单片机停止发送; 如果单片机收到“1”,则继续每隔2秒发送“Hello C51”。
代码示例:
#include <REGX51.H>
#include "stdio.h"
unsigned char a;
unsigned char Flag=1;
void Delay1ms(unsigned int xms) //@12.000MHz
{
unsigned char i, j;
while(xms)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
xms--;
}
}
void UartInit(void) //9600bps@12.000MHz
{
PCON &= 0x7F; //波特率不倍速
SCON = 0x50; //8位数据,可变波特率
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xFD; //设定定时初值
TH1 = 0xFD; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
EA = 1;
ES = 1;
}
void UartSend()
{
TI=1;
puts("Hello C51");
while(!TI);
TI=0;
Delay1ms(2000);
}
void main()
{
UartInit();
while(1)
{
if(Flag==1)
UartSend();
}
}
//串口中断函数模板
void UART_Routine() interrupt 4 //串口中断
{
if(RI==1)
{
RI=0;
a=SBUF;
if(a=='1')Flag=1;
if(a=='0')Flag=0;
}
}
串口助手:
注:上述为个人的理解和学习借鉴所得内容,如有错误或内容不全之处,请各位指出