单片机实验(三)

news2025/1/10 23:29:55

前言

实验一:利用定时器T1的中断控制P1.7引脚输出音频信号,启动蜂鸣器发出一段熟悉的与众不同的具有10个音节的音乐音频。

实验二:使用定时器/计数器来实现一个LCD显示年、月、日、星期 、时、分、秒的电子表,要求时和分可以方便设置。液晶显示器采用LCD 1602.

参考链接

LED数码管的静态显示与动态显示(Keil+Proteus)-CSDN博客

定时器/计数器的应用-CSDN博客

基于51单片机的7键电子琴音乐播放器proteus_c51电子琴程序_BT-BOX的博客-CSDN博客​​​​​​

笔记:C51单片机——音乐播放,模拟钢琴键。_c51音乐_c-tion的博客-CSDN博客

51单片机学习--蜂鸣器播放音乐_51单片机蜂鸣器音乐代码-CSDN博客

闰年(历法中的名词)_百度百科 (baidu.com)

蜂鸣器播放音乐_哔哩哔哩_bilibili

【51单片机】蜂鸣器播放音乐 - 知乎 (zhihu.com)【51单片机】蜂鸣器播放音乐 - 知乎 (zhihu.com)

独立键盘接口设计(Keil+Proteus)-CSDN博客

如何看懂音乐简谱_简谱怎么看-CSDN博客

【蓝桥杯——单片机学习笔记】十六.蜂鸣器播放音乐(STC15F2K60S2)_有源蜂鸣器可以播放音乐吗-CSDN博客

简谱_百度百科 (baidu.com)

[11-2] 蜂鸣器播放提示音&音乐_哔哩哔哩_bilibili

实验一

Keil

这个找了半天,不是付费的就是代码不全的,然后是从视频上面找到一个简单的敲了一下,还是没怎么弄懂。

#include<reg51.h>
#define uchar unsigned char
#define uint unsigned int
	
sbit speaker=P1^7;

//生日快乐歌的音符频率表,不同频率由不同的延时来决定
uchar code SONG_TONE[]={
	212,212,190,212,159,169,212,212,190,212,142,159,212,
	212,106,126,159,169,190,119,119,126,159,142,159,0};

//生日快乐歌节奏节拍表,节拍决定每个音符的演奏长短
uchar code SONG_LONG[]={
	9,3,12,12,12,24,9,3,12,12,24,9,3,
	12,12,12,12,12,9,3,12,12,12,24,0};

//延时函数
void delay(uint x){
	uchar t;
	while(x--)
		for(t=0;t<120;t++);
}
//播放音乐
void PlayMusic(){
	uint i=0,j,k;
	for(i=0;i<26;i++){
		for(j=0;j<SONG_LONG[i]*20;j++){
			speaker=~speaker;
			for(k=0;k<SONG_TONE[i]/3;k++);
		}
		delay(100);
	}
}

void main(){
	while(1){
		PlayMusic();
	}
}

Proteus

原理图到没什么需要更改的,主要是不同频率的声音应该怎么发出来。

拓展 

其实主要还是代码部分,原理图不需要修改,需要先找一份简谱,然后需要知道怎么看音乐乐谱。

实验需要把握的就是“音的高低”“音的长短”。

在目前的代码中不考虑倍低音和低音,自己低音、中音、高音对应的代码。

音的高低部分需要知道,在基本音符上方加一个“.”,表示该音升高一个八度,称为高音;在基本音符下方加一个“.”,表示该音降低一个八度,称为低音。这里只需要知道区分低中高音即可。

知道了音的高低接下来就是音的长短部分,其实就是表示那个音延迟的时间,这里就只需要记住在基本音符下方加记一条短横线,表示缩短原音符时值的一半。

这两个问题都解决了就可以直接编写程序了,其实也不需要很复杂的乐理知识,甚至基本音符是什么都可以不用知道,就是看着简谱修改成自己对应的即可,对于特殊的符号还需要自己查资料看表示什么。

基本音符 

1234567
DoReMiFaSolLaSi

 《平凡之路》的部分简谱

 

 缺点对应倍低音的地方无法编写程序,因为这只有低音、中音、高音频率的代码,然后音也有点不准吧,但是调子感觉也差不多,可以听出来大致的调子。

#include<reg51.h>
#define uchar unsigned char
	
#define L1  1
#define L1_ 2
#define L2  3
#define L2_ 4
#define L3  5
#define L4  6
#define L4_ 7
#define L5  8
#define L5_ 9
#define L6  10
#define L6_ 11
#define L7  12
#define M1  13
#define M1_ 14
#define M2  15
#define M2_ 16
#define M3  17
#define M4  18
#define M4_ 19
#define M5  20
#define M5_ 21
#define M6  22
#define M6_ 23
#define M7  24
#define H1  25
#define H1_ 26
#define H2  27
#define H2_ 28
#define H3  29
#define H4  30
#define H4_ 31
#define H5  32
#define H5_ 33
#define H6  34
#define H6_ 35
#define H7  36

#define ClockSpeed 12000000 //时钟频率,Hz
#define SongSpeed 330       //ms,八分音符
sbit beepIO = P1^7;         //定义蜂鸣器IO口

unsigned char freq_select;

//音阶频率表
unsigned int code freq_table[]={0,61714 ,61928 ,62131 ,62322 ,62502 ,62673 ,62833 ,62985 ,63128 ,63263 ,63391 ,63511, //低音
																	63628 ,63731 ,63835 ,63928 ,64021 ,64103 ,64185 ,64260 ,64331 ,64400 ,64463 ,64524, //中音
																	64580 ,64633 ,64684 ,64732 ,64777 ,64820 ,64860 ,64898 ,64934 ,64968 ,65000 ,65030 //高音
	                        };				


//平凡之路
uchar code song[]={
		M1,2, M3,1, M1,1, L7,1, M1,1, M2,1, L5,1, 0,1, M3,1, M3,1, M3,1, M6,1, M6,1, M6,1, M1,1, M2,1, M3,1, M3,1, 0,2, 0,2, 0,2,
		0,1, M3,1, M3,1, M6,1, M6,1, M6,3, M5,1, M5,2, M4,1, M3,3, 0,2, 0,2, 0,1, M3,1, M3,1, M6,1, M6,1, M1,1, M2,1, M3,1,
		M3,2, 0,2, 0,2, 0,2, 0,1, M3,1, M3,1, M1,1, M4,1, M4,1, M4,1, M4,1, M3,1, M1,2, 0,2, 0,2,  40};
													
void timer0_initial()
{
	beepIO = 0;
	TH0   = 0xFD;	
	TL0   = 0x09;
	TMOD  = 0x01;  //选择定时器0,工作方式1
	ET0   = 1;     //允许定时器0中断
	EA    = 1;     //CPU开放中断
	TF0   = 0;     //溢出标志位清0
	TR0   = 1;     //开启定时器0
}

void BeepTimer0() interrupt 1	//中断函数
{
	beepIO = !beepIO;   //蜂鸣器IO口高低电平转换
	TH0 = freq_table[freq_select]/256 ;
	TL0 = freq_table[freq_select]%256 ;
}

void delay_ms(unsigned int x) //延时函数
{
	unsigned char t;
	while(x--) for(t=0;t<120;t++);
}

void main()
{
  unsigned char select;
	
	timer0_initial();
	
	while(song[select]!= 40)        //判断歌曲是否结束
	{
		freq_select=song[select];		
		if(freq_select)         //判断是否是休止符0
		{
			select++;
			delay_ms(song[select]*SongSpeed);
			TR0 = 0;   //关闭蜂鸣器一段时间再打开,模拟按键抬手动作。
			delay_ms(10);
			TR0 = 1;
			select++;
		}else{			
			TR0 = 0;
			select++;
			delay_ms(song[select]*SongSpeed);
			TR0 = 1;
			select++;
		}
	}
}

然后就是此次的作业《倔强》

 这个写我还是比较满意,就是中间会断掉然后重新开始,推测是因为储存不了这么多的数据,不能完整播放整首歌,但是也还不错,还是比较有成就感的。

#include<reg51.h>
#define uchar unsigned char
	
#define L1  1
#define L1_ 2
#define L2  3
#define L2_ 4
#define L3  5
#define L4  6
#define L4_ 7
#define L5  8
#define L5_ 9
#define L6  10
#define L6_ 11
#define L7  12
#define M1  13
#define M1_ 14
#define M2  15
#define M2_ 16
#define M3  17
#define M4  18
#define M4_ 19
#define M5  20
#define M5_ 21
#define M6  22
#define M6_ 23
#define M7  24
#define H1  25
#define H1_ 26
#define H2  27
#define H2_ 28
#define H3  29
#define H4  30
#define H4_ 31
#define H5  32
#define H5_ 33
#define H6  34
#define H6_ 35
#define H7  36

#define ClockSpeed 12000000 //时钟频率,Hz
#define SongSpeed 330       //ms,八分音符
sbit beepIO = P1^7;         //定义蜂鸣器IO口

uchar freq_select;

//音阶频率表
unsigned int code freq_table[]={0,61714 ,61928 ,62131 ,62322 ,62502 ,62673 ,62833 ,62985 ,63128 ,63263 ,63391 ,63511, //低音
																	63628 ,63731 ,63835 ,63928 ,64021 ,64103 ,64185 ,64260 ,64331 ,64400 ,64463 ,64524, //中音
																	64580 ,64633 ,64684 ,64732 ,64777 ,64820 ,64860 ,64898 ,64934 ,64968 ,65000 ,65030 //高音
	                        };				

		
//倔强
uchar code song[]={
	M1,3, M1,1, M1,1, L7,1, M1,1, M2,1, M2,1, L7,1,
	M1,3, M1,1, M1,1, L7,1, M1,1, M2,1, M2,1, M2,1, M3,1, M1,1,
	M1,3, M1,1, M1,1, M1,1, L5,1, M1,1, M1,1, M2,1, M4,1, M3,1, M2,1, M1,1, M3,1, M2,1, M2,2,
	M1,3, M1,1, M1,1, L7,1, M1,1, M2,1, M2,1, L7,1,
	M1,3, M1,1, M1,1, L7,1, M1,1, M2,1, M2,1, M2,1, M3,1, M1,1,
	M1,3, M1,1, M1,1, M1,1, L5,1, M1,1, M1,1, M2,1, M4,1, M3,1, M2,1, M1,1, M3,1, M2,1, M2,1, 0,1,
	M1,1, L7,1, M1,1, M1,1, L7,1, M1,1, M1,1, 0,2, M1,1, L7,1, M1,1, M1,1, M2,1, M3,1, M3,2, 
	M1,1, L7,1, M1,1, M1,1, M2,1, M4,1, M4,1, M3,1, M2,1, M1,1, M1,1, L7,1, M1,1, M3,1, M6,1, M5,1, M5,1,
	M3,1, M2_,1, M3,1, M3,1, M4,1, M5,1, M5,1, M4,1, M3,1, M3,1, M2,1, M1,1, L7,1, M1,1, M1,1, M2,1, M3,1, M3,1, M2,1, M1,1, L7,1,
	M1,1, L7,1, M1,1, M1,1, M6,1, M7,1, M7,1, M6,1, M5,1, M5,1, M3,1, M5,1, M6,1, M1,1, M1,1, M1,3, M6,1, M7,1, M3,1, M5,1, M5,1, 0,1,
	M3,1, M3,1, M3,1, M3,1, M4,1, M5,1, M5,1, M4,1, M3,1, M3,1, M3,1, M2,1, M1,1, M1,1, M1,1, M1,1, M2,1, M3,1, M2,1, M1,1, L7,1, 
	M1,1, L6,1, M1,1, M1,1, M6,1, M7,1, M6,1, M5,1, M3,1, M6,1, M6,1, M1,1, M1,1, M1,3, M6,1, M7,1, M6,1, M5,1, M5,1, L6,1, M1,1, M1,2, 
	40
};
													
void timer0_initial()
{
	beepIO = 0;
	TH0   = 0xFD;	
	TL0   = 0x09;
	TMOD  = 0x01;  //选择定时器0,工作方式1
	ET0   = 1;     //允许定时器0中断
	EA    = 1;     //CPU开放中断
	TF0   = 0;     //溢出标志位清0
	TR0   = 1;     //开启定时器0
}

void BeepTimer0() interrupt 1	//中断函数
{
	beepIO = !beepIO;   //蜂鸣器IO口高低电平转换
	TH0 = freq_table[freq_select]/256 ;
	TL0 = freq_table[freq_select]%256 ;
}

void delay_ms(unsigned int x) //延时函数
{
	uchar t;
	while(x--) for(t=0;t<120;t++);
}

void main()
{
  uchar select;
	
	timer0_initial();
	
	while(song[select]!= 40)        //判断歌曲是否结束
	{
		freq_select=song[select];		
		if(freq_select)         //判断是否是休止符0
		{
			select++;
			delay_ms(song[select]*SongSpeed);
			TR0 = 0;   //关闭蜂鸣器一段时间再打开,模拟按键抬手动作。
			delay_ms(10);
			TR0 = 1;
			select++;
		}else{			
			TR0 = 0;
			select++;
			delay_ms(song[select]*SongSpeed);
			TR0 = 1;
			select++;
		}
	}
}

实验二

Keil

代码还是和之前的差不多,然后就是需要增加年月日星期的处理,星期常见的就是用三个字母来表示,年月日都是用数字,有了前面的基础就很容易确定LCD 1602的显示以及时钟的进位,这里需要考虑的是根据不同的年份以及月份,他的天数是不一致的,所以这个需要考虑进去,经过在程序中这里也没有体现到,可以通过设置时间的节点来去验证他是否满足要求。

0123456789ABCDEF
80yyyy.MM.ddww
C0TIMEhh:mm:ss

我这里犯了一个很严重的错误就是对于年份在用LCD输出的时候用到取余和整除,年份的类型是字符型,这就导致输出的数不对,后面还是通过控制变量法来缩小他的范围最后是想到uchar最大是到255,这个年份都是4位数的,所以会导致越界计算出来的数值就不正确,后面是改成了整型。 

#include<reg51.h>
#define uchar unsigned char
#define uint unsigned int
sbit RS=P2^0;
sbit RW=P2^1;
sbit E=P2^2;
 
//202140200126
uint int_time;//定义中断次数变量(最多到200)
uchar code date[]=" 2022.12.31 SAT ";//LCD 第1行显示的内容
uchar code time[]=" TIME  23:59:55 ";//第2行显示的内容
uchar second=55,minute=59,hour=23;//秒,分钟,小时
uint year=2022;//年
uchar month=12,day=31,week=6;//月,日,星期
void delay(uint i);//延时函数
void write_com(uchar com);//写入命令数据到LCD
void write_data(uchar date);//写入字符显示数据到LCD
void init1602();//LCD1602初始化设定
void write_sfm(uchar add,uchar date);//向指定地址写入数据
void write_sfm_year(uchar add,uint year);//写年
void write_sfm_week(uchar add,uchar week);//写星期
void clock_init();//对时钟进行初始化
void clock_write(uint s,uint m,uint h,uint d,uint w,uint M,uint y);//写数据
int isleap(uint year);//判断是否是闰年
	
void main(){
	int_time=0;//中断次数、秒、分钟、小时,年,月,日,星期变量进行清零
	second=55;
	minute=59;
	hour=23;
	year=2022;
	month=12;
	day=31;
	week=6;
	
	init1602();//lcd的初始化
	clock_init();//时钟的初始化
	TMOD=0x01;//设置定时器T0为方式1定时
	EA=1;//总中断开
	ET0=1;//允许T0中断
	TH0=0xEE;//对T0进行初始化
	TH1=0x00;
	TR0=1;
	while(1){
		clock_write(second,minute,hour,day,week,month,year);
	}
}
 
void delay(uint j){//延时函数
	uchar i=110;
	for(;j>0;j--){
		while(i--);
		i=110;
	}
}
 
 
void write_com(uchar com){//写入命令数据到LCD
	RW=0;
	RS=0;
	P0=com;
	delay(5);
	E=1;
	delay(5);
	E=0;
}
 
void write_data(uchar date){//写入字符显示数据到LCD
	RW=0;
	RS=1;
	P0=date;
	delay(5);
	E=1;
	delay(5);
	E=0;
}
 
void init1602(){//LCD1602初始化设定
	RW=0;
	E=0;
	write_com(0x3C);
	write_com(0x0C);//开整体显示,光标关,无闪烁
	write_com(0x06);//光标右移,写入一个字符后地址指针加1
	write_com(0x01);//清屏
	write_com(0x80);//字符输入地址,字符的第一位
}
 
 
void write_sfm(uchar add,uchar date){//向指定地址写入数据
	uchar shi,ge;
	shi=date/10;//十位
	ge=date%10;//个位
	write_com(add);
	write_data(0x30+shi);//0x30表示48,让数字变成字符
	write_data(0x30+ge);
}

//这里头脑不清醒了一下,uchar是一个字节,最大是127,而年份是四位数的,所以需要用uint类型才可以uint是两个字节有(65535)
void write_sfm_year(uchar add,uint date){//向指定地址写入数据(年份是四位的,所以单独处理)
	uchar ge,shi,bai,qian;
	qian=date/1000;//千位
	bai=date/100%10;//百位
	shi=date/10%10;//十位
	ge=date%10;//个位
	write_com(add);
	write_data(0x30+qian);//0x30表示48,让数字变成字符
	write_data(0x30+bai);//0x30表示48,让数字变成字符
	write_data(0x30+shi);//0x30表示48,让数字变成字符
	write_data(0x30+ge);//0x30表示48,让数字变成字符
}

void write_sfm_week(uchar add,uchar date){//向指定地址写入数据(星期是用三个字母来表示)
	write_com(add);
	switch(date){
		case 0:
			write_data('S');write_data('U');write_data('N');break;//周日
		case 1:
			write_data('M');write_data('O');write_data('N');break;//周一
		case 2:
			write_data('T');write_data('U');write_data('E');break;//周二
		case 3:
			write_data('W');write_data('E');write_data('D');break;//周三
		case 4:
			write_data('T');write_data('H');write_data('U');break;//周四
		case 5:
			write_data('F');write_data('R');write_data('I');break;//周五
		case 6:
			write_data('S');write_data('A');write_data('T');break;//周六
	}
}

void clock_init(){//对时钟进行初始化
	uchar i,j;
	//写第一行
	write_com(0x80);
	for(i=0;i<16;i++){
		write_data(date[i]);
	}
	//写第二行
	write_com(0xC0);
	for(j=0;j<16;j++){
		write_data(time[j]);
	}
}
 
void clock_write(uint s,uint m,uint h,uint d,uint w,uint M,uint y){//向指定位置写数据
	//写年
	write_sfm_year(0x81,y);
	//写月
	write_sfm(0x86,M);
	//写星期
	write_sfm_week(0x8C,w);
	//写日
	write_sfm(0x89,d);
	//写小时
	write_sfm(0xC7,h);
	//写分钟
	write_sfm(0xCa,m);
	//写秒
	write_sfm(0xCd,s);
}

//判断是否是闰年
int isleap(uint year){
	//能被400整除或者能被4整除且不能被100整除为闰年
	if((year%400==0)||(year%4==0 && year %100!=0)){
		return 1;
	}else{
		return 0;
	}
}

//中断服务函数
void timer0(void) interrupt 1{
	uchar leap=isleap(year);//判断今年是否为闰年
	TR0=0;//停止计时,避免给计时造成误差(需要有这个,不然就一直在中断,导致时间不准确)
	TH0=0xEE;//对T0重新赋初值
	TH1=0x00;
	int_time++;//记录中断次数
	if(int_time==200){//中断次数满200次5ms*200=1s
		int_time=0;//中断次数变量清零
		second++;//加1秒
	}
	if(second==60){//60秒为一分钟
		second=0;
		minute++;
	}
	if(minute==60){//60分钟为一小时
		minute=0;
		hour++;
	}
	if(hour==24){//一天是24小时
		hour=0;
		week++;
		day++;
	}
	if(week==7){//0表示周日,week=7表示新的一周开始了
		week=0;
	}
	if(month==2){
		//是闰年(2月有29天)
		if(leap==1){
			if(day>29){
				day=0;
				month++;
			}
		}else{//不是闰年(2月有28天)
			if(day>28){
				day=0;
				month++;
			}
		}
	}else if(month==1 || month==3 || month==5 || month==7 || month==8 || month==10 || month==12){
		if(day>31){
			day=0;
			month++;
		}
	}else if(month==4 || month==6 || month==9 || month==11){
		if(day>30){
			day=0;
			month++;
		}
	}
	if(month==13){
		month=0;
		year++;
	}
	TR0=1;
}

Proteus

原理图和之前是保持一致的。

 在线过年,我们一起包饺子!!!

 

拓展

 

咱就说这个应不应该希望。这里大致的思路就是添加几个按钮,来控制增加或者减少年月日时分, 秒就不需要添加了,时钟一直在动这个调也不好调。

这里我一直在想这个调分钟这个进位怎么调,主要开始对于每月的天数,我突然想到我以前的电子表就是每个位置是独立的,调分钟不会影响其他的,就是分钟从0-59变化,抱着这样的想法,时钟就可以很快确定了,不就是求余吗,大家都会。

为了简单我又做了一个假设,当设置的时候每个月都是31天,而且分开设置的话,天数和星期就不是保持同步的了。

目前就是在之前的基础上增加了一个独立键盘的扫描,以及对应的求余功能,对于设置直接导致进位和借位的功能还没实现,目前先将简单实现的完成。扫描的时候尽量不和RS,RW,E用一个口,会导致不好查询,程序我不知道怎么编写,如果影响了那三根引脚的输出会导致LCD一直在查询发出警告。

这个我昨天想到了一个东西,我感觉突然就悟了一样,就是对于时间小于0前面要退一位吗,因为都是无符号数所以不好来设置,然后我就想到了一个方法。假设是要判断分钟minute,可以先给分钟加上一个60,然后整除60,如果他的结果是0,就表示之前的值是小于60的,如果他的值是等于1,就表示他的值在[0,60)之间,如果他的值等于2,就表示他大于等于60了,因为数值是实时更新的,所以这里就排除了数是一个很大的正数,或者是一个很小的负数的情况,主要是考虑-1,和60这两种情况怎么让小时的位置上面的数发生相应的变化。

#include<reg51.h>
#define uchar unsigned char
#define uint unsigned int
sbit RS=P2^0;
sbit RW=P2^1;
sbit E=P2^2;

//分别对应12个按钮
sbit S1=P1^0;
sbit S2=P1^1;
sbit S3=P1^2;
sbit S4=P1^3;
sbit S5=P1^4;
sbit S6=P1^5;
sbit S7=P1^6;
sbit S8=P1^7;
sbit S9=P3^04;
sbit S10=P3^5;
sbit S11=P3^6;
sbit S12=P3^7;

uint int_time;//定义中断次数变量(最多到200)
uchar code date[]=" 2022.12.31 SAT ";//LCD 第1行显示的内容
uchar code time[]=" TIME  23:59:55 ";//第2行显示的内容
uint second=55,minute=59,hour=23;//秒,分钟,小时
uint year=2022;//年
uchar month=12,day=31,week=6;//月,日,星期
uchar keyval;//定义键盘储存变量单元

void delay(uint i);//延时函数
void write_com(uint com);//写入命令数据到LCD
void write_data(uint date);//写入字符显示数据到LCD
void init1602();//LCD1602初始化设定
void write_sfm(uint add,uint date);//向指定地址写入数据
void write_sfm_year(uint add,uint year);//写年
void write_sfm_week(uint add,uint week);//写星期
void clock_init();//对时钟进行初始化
void clock_write(uint s,uint m,uint h,uint d,uint w,uint M,uint y);//写数据
int isleap(uint year);//判断是否是闰年
void key_scan(void);//扫描键盘



void main(){
	int_time=0;//中断次数、秒、分钟、小时,年,月,日,星期变量进行清零
	second=55;
	minute=59;
	hour=23;
	year=2022;
	month=12;
	day=31;
	week=6;
	keyval=0;//键值初始化为0
	
	init1602();//lcd的初始化
	clock_init();//时钟的初始化
	TMOD=0x01;//设置定时器T0为方式1定时
	EA=1;//总中断开
	ET0=1;//允许T0中断
	TH0=0xEE;//对T0进行初始化
	TH1=0x00;
	TR0=1;
	while(1){
		key_scan();
		switch(keyval){
			case 1://每年
				year++;year=(year+10000)%10000;break;
			case 2:
				year--;year=(year+10000)%10000;break;
			case 3://每月
				month++;month=(month+11)%12+1;break;//要让0表示12则需要先减1求余之后加1
			case 4:
				month--;month=(month+11)%12+1;break;//要让0表示12则需要先减1求余之后加1
			case 5://每周
				week++;week=(week+7)%7;break;
			case 6:
				week--;week=(week+7)%7;break;
			case 7://天数
				day++;day=(day+30)%31+1;break;
			case 8:
				day--;day=(day+30)%31+1;break;
			case 9://小时
				hour++;hour=(hour+60)%60;break;
			case 10:
				hour--;hour=(hour+60)%60;break;
			case 11://分钟
				minute++;minute=(minute+60)%60;break;
			case 12:
				minute--;minute=(minute+60)%60;break;
		}
		clock_write(second,minute,hour,day,week,month,year);
		keyval=0;
	}
}
 

	
//延时函数
void delay(uint j){
	uchar i=110;
	for(;j>0;j--){
		while(i--);
		i=110;
	}
}
 
//写入命令数据到LCD
void write_com(uint com){
	RW=0;
	RS=0;
	P0=com;
	delay(5);
	E=1;
	delay(5);
	E=0;
}
 
//写入字符显示数据到LCD
void write_data(uint date){
	RW=0;
	RS=1;
	P0=date;
	delay(5);
	E=1;
	delay(5);
	E=0;
}
 
//LCD1602初始化设定
void init1602(){
	RW=0;
	E=0;
	write_com(0x3C);
	write_com(0x0C);//开整体显示,光标关,无闪烁
	write_com(0x06);//光标右移,写入一个字符后地址指针加1
	write_com(0x01);//清屏
	write_com(0x80);//字符输入地址,字符的第一位
}
 
//向指定地址写入数据
void write_sfm(uint add,uint date){
	uchar shi,ge;
	shi=date/10;//十位
	ge=date%10;//个位
	write_com(add);
	write_data(0x30+shi);//0x30表示48,让数字变成字符
	write_data(0x30+ge);
}

//这里头脑不清醒了一下,uchar是一个字节,最大是127,而年份是四位数的,所以需要用uint类型才可以uint是两个字节有(65535)
void write_sfm_year(uint add,uint date){//向指定地址写入数据(年份是四位的,所以单独处理)
	uchar ge,shi,bai,qian;
	qian=date/1000;//千位
	bai=date/100%10;//百位
	shi=date/10%10;//十位
	ge=date%10;//个位
	write_com(add);
	write_data(0x30+qian);//0x30表示48,让数字变成字符
	write_data(0x30+bai);//0x30表示48,让数字变成字符
	write_data(0x30+shi);//0x30表示48,让数字变成字符
	write_data(0x30+ge);//0x30表示48,让数字变成字符
}

void write_sfm_week(uint add,uint date){//向指定地址写入数据(星期是用三个字母来表示)
	write_com(add);
	switch(date){
		case 0:
			write_data('S');write_data('U');write_data('N');break;//周日
		case 1:
			write_data('M');write_data('O');write_data('N');break;//周一
		case 2:
			write_data('T');write_data('U');write_data('E');break;//周二
		case 3:
			write_data('W');write_data('E');write_data('D');break;//周三
		case 4:
			write_data('T');write_data('H');write_data('U');break;//周四
		case 5:
			write_data('F');write_data('R');write_data('I');break;//周五
		case 6:
			write_data('S');write_data('A');write_data('T');break;//周六
	}
}

//对时钟进行初始化
void clock_init(){
	uchar i,j;
	//写第一行
	write_com(0x80);
	for(i=0;i<16;i++){
		write_data(date[i]);
	}
	//写第二行
	write_com(0xC0);
	for(j=0;j<16;j++){
		write_data(time[j]);
	}
}
 
//向指定位置写数据
void clock_write(uint s,uint m,uint h,uint d,uint w,uint M,uint y){
	//写年
	write_sfm_year(0x81,y);
	//写月
	write_sfm(0x86,M);
	//写星期
	write_sfm_week(0x8C,w);
	//写日
	write_sfm(0x89,d);
	//写小时
	write_sfm(0xC7,h);
	//写分钟
	write_sfm(0xCa,m);
	//写秒
	write_sfm(0xCd,s);
}

//判断是否是闰年
int isleap(uint year){
	//能被400整除或者能被4整除且不能被100整除为闰年
	if((year%400==0)||(year%4==0 && year %100!=0)){
		return 1;
	}else{
		return 0;
	}
}

//键盘扫描
void key_scan(void){
	P1=0xFF;
	P3=0xFF;//不能给P2赋值,不然会导致LCD一直警告
	if((P1&0xFF)!=0xFF||(P3&0xF0)!=0xF0){
		delay(10);//适当延迟,怕到时候误判
		if(S1==0)keyval=1;
		if(S2==0)keyval=2;
		if(S3==0)keyval=3;
		if(S4==0)keyval=4;
		if(S5==0)keyval=5;
		if(S6==0)keyval=6;
		if(S7==0)keyval=7;
		if(S8==0)keyval=8;
		if(S9==0)keyval=9;
		if(S10==0)keyval=10;
		if(S11==0)keyval=11;
		if(S12==0)keyval=12;
	}
}

//中断服务函数
void timer0(void) interrupt 1{
	uchar leap=isleap(year);//判断今年是否为闰年
	TR0=0;//停止计时,避免给计时造成误差(需要有这个,不然就一直在中断,导致时间不准确)
	TH0=0xEE;//对T0重新赋初值
	TH1=0x00;
	int_time++;//记录中断次数
	if(int_time==200){//中断次数满200次5ms*200=1s
		int_time=0;//中断次数变量清零
		second++;//加1秒
	}
	
	if(second==60){//60秒为一分钟
		second=0;
		minute++;
	}
	
	if(minute==60){//60分钟为一小时
		minute=0;
		hour++;
	}
	
	if(hour==24){//一天是24小时
		hour=0;
		week++;
		day++;
	}
	
	if(week==7){//0表示周日,week=7表示新的一周开始了
		week=0;
	}
	
	
	if(month==2){
		//是闰年(2月有29天)
		if(leap==1){
			if(day>29){
				day=0;
				month++;
			}
		}else{//不是闰年(2月有28天)
			if(day>28){
				day=0;
				month++;
			}
		}
	}else if(month==1 || month==3 || month==5 || month==7 || month==8 || month==10 || month==12){
		if(day>31){
			day=0;
			month++;
		}
	}else if(month==4 || month==6 || month==9 || month==11){
		if(day>30){
			day=0;
			month++;
		}
	}
	if(month==13){
		month=0;
		year++;
	}
	TR0=1;
}

为了更好的表示按钮的含义这里用到了写文字的模块,如果直接用中文命名器件容易导致程序崩溃。

综合 

怎么好看怎么来吧,就是有点小卡。

总结

认真想下来其实还是很有意思的,但是也是很耗时间,有时间再来尝试一些新的功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1275064.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

云服务器anaconda(py39)+pytorch1.12.0(cu113)

用xshell连接ip地址&#xff0c;端口号22&#xff0c;输入用户密码 查看当前版本 conda -V conda info --envs 如果不是需要的版本&#xff0c;使用 anaconda-clean --yes rm -rf anaconda3 删除文件夹 安装anaconda 2022 10 py3.9 wget https://repo.anaconda.com/archi…

kafka 集群 ZooKeeper 模式搭建

Apache Kafka是一个开源分布式事件流平台&#xff0c;被数千家公司用于高性能数据管道、流分析、数据集成和关键任务应用程序 Kafka 官网&#xff1a;Apache Kafka 关于ZooKeeper的弃用 根据 Kafka官网信息&#xff0c;随着Apache Kafka 3.5版本的发布&#xff0c;Zookeeper现…

看这里!精确的用户画像是如何一步步构建的?

1. 用户画像的定义 用户画像是指根据用户的个人信息、行为特征和偏好等数据来描绘和分析用户的一种方法。它是通过收集和分析用户的各种数据&#xff0c;以便更好地了解用户需求和行为模式&#xff0c;从而为企业提供个性化、精准化的产品和服务。 2. 构建用户画像的步骤 &…

If和else的紧缩版本(Python)

epsilon_max 3 epsilon 2 epsilon_increment 1 epsilon epsilon * (1 epsilon_increment) if epsilon < epsilon_max else epsilon_max print(epsilon)

读书笔记-《数据结构与算法》-摘要1[数据结构]

文章目录 [数据结构]1. String - 字符串2. Linked List - 链表2.1 链表的基本操作2.1.1 反转链表单向链表双向链表 2.1.2 删除链表中的某个节点2.1.3 链表指针的鲁棒性2.1.4 快慢指针 3. Binary Tree - 二叉树3.1 树的遍历3.2 Binary Search Tree - 二叉查找树 4. Queue - 队列…

训练 CNN 对 CIFAR-10 数据中的图像进行分类-keras实现

1. 加载 CIFAR-10 数据库 import keras from keras.datasets import cifar10# 加载预先处理的训练数据和测试数据 (x_train, y_train), (x_test, y_test) cifar10.load_data() 2. 可视化前 24 个训练图像 import numpy as np import matplotlib.pyplot as plt %matplotlib …

桶装水订水送水小程序具备以下主要功能

桶装水订水送水小程序具备以下主要功能&#xff1a; 对比传统的电话订水&#xff0c;订水小程序展现出显著的优势&#xff1a; 1. 便捷性&#xff1a;用户通过小程序就能轻松预订水桶&#xff0c;无需亲自出门&#xff0c;极大提升了生活的便捷度。 2. 即时性&#xff1a;送水…

element-ui表格滚动效果,el-table滚动条样式重置

项目首页需要展示一个表格滚动区域&#xff0c;特此来记录一下 HTML <div class"table-box" mouseenter"mouseenter" mouseleave"mouseleave"><el-table :data"tableList" border height"400px" v-loading"…

2023_Spark_实验二十四:Kafka集群环境搭建

Kafka集群环境搭建 一、环境说明 二、安装步骤 一、环境说明 目前的Kafka版本还是需要借助zookeeper来存储cluster、brokers、consumer等相关元信息&#xff0c;在当前版本即 在本案例中&#xff0c;我们采用了外部的zookeeper&#xff0c;即搭建了三节点的集群zookeeper环境…

Python Scrapy分布式爬虫

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在当今信息爆炸的时代&#xff0c;获取大规模数据对于许多应用至关重要。而分布式爬虫作为一种强大的工具&#xff0c;在处理大量数据采集和高效爬取方面展现了卓越的能力。 本文将深入探讨分布式爬虫的实际应用…

最新Graphviz python安装教程及使用

文章目录 Graphviz 安装python安装graphviz库 Graphviz 安装 Graphviz是一个独立的软件&#xff0c;在用python的pip下载之前&#xff0c;需要先下载软件。 网址&#xff1a;https://graphviz.org/download/ 找到合适的版本进行下载安装。记住自己的安装位置&#xff0c;完…

如何通过数据文化加速企业管理的转型升级?

#01 企业管理 更需要“转型升级” 中国企业管理在某种程度上来看&#xff0c;受到中国传统文化、社会价值观及现代化趋势等多方面影响的结果&#xff0c;比如说&#xff0c;中国传统文化强调长期思考和计划&#xff0c;这在企业管理中体现为对长期业务发展和可持续的关注。但…

小白备战蓝桥杯:Java常用API

一、什么是API 就是别人写好的一些类&#xff0c;给咱们程序员直接拿去调用即可解决问题的 我们之前接触过的Scanner和Random都是API 但java中提供的API很多&#xff0c;我们没有必要去学习所有的API&#xff0c;只需要知道一些常用的API&#xff0c;再借助帮助文档去使用AP…

蓝桥杯每日一题2023.12.1

题目描述 蓝桥杯大赛历届真题 - C 语言 B 组 - 蓝桥云课 (lanqiao.cn) 题目分析 对于此题目而言思路较为重要&#xff0c;实际可以转化为求两个数字对应的操作&#xff0c;输出最前面的数字即可 #include<bits/stdc.h> using namespace std; int main() {for(int i 1…

【前缀和]LeetCode1862:向下取整数对和

本文涉及的基础知识点 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 作者推荐 动态规划LeetCode2552&#xff1a;优化了6版的1324模式 题目 给你一个整数数组 nums &#xff0c;请你返回所有下标对 0 < i, j < nums.length 的 …

steam搬砖项目怎么赚钱?附详细拆解教程

互联网上的赚钱项目如林&#xff0c;为何他人能斩获成果&#xff0c;你却只能望洋兴叹&#xff1f;归根结底&#xff0c;还是因为你的心态过于急功近利。今天&#xff0c;我要为你揭示的Steam实体项目&#xff0c;是专门为热门竞技游戏CS:GO量身打造的。CS:GO&#xff0c;这款风…

自动机器学习AutoML

自动机器学习AutoML AutoMLAuto-SklearnAutoKerasAutoGluonGoogle AutoMLAzure自动机器学习 AutoML 模型的选择和超参数的调节等等任务对于机器学习算法的开发者来说是一件繁琐的工作&#xff0c;为了使得机器可以自动地设计模型并调优&#xff0c;自动机器学习AutoML便应运而…

打破语言障碍:跨境电商中的多语言营销策略

随着全球市场的不断扩大&#xff0c;跨境电商成为企业拓展国际业务的重要途径。然而&#xff0c;语言障碍往往成为企业在跨境电商中面临的挑战之一。为了打破这一障碍&#xff0c;实现全球市场的可持续发展&#xff0c;多语言营销策略变得至关重要。 多语言市场的挑战 在跨境电…

春秋云境:CVE-2022-32991(sql注入)

靶标介绍&#xff1a; 该CMS的welcome.php中存在SQL注入攻击。 获取登录地址 http://eci-2zeb0096que0556y47vq.cloudeci1.ichunqiu.com:80 登录注册 注册成功登录进入注册接口 参数接口一 发现接口参数q http://eci-2zeb0096que0556y47vq.cloudeci1.ichunqiu.com/welcome.p…

使用visual Studio MFC 平台实现对灰度图添加椒盐噪声,并进行均值滤波与中值滤波

平滑处理–滤波 本文使用visual Studio MFC 平台实现对灰度图添加椒盐噪声&#xff0c;并进行均值滤波与中值滤波 关于其他MFC单文档工程可参考 01-Visual Studio 使用MFC 单文档工程绘制单一颜色直线和绘制渐变颜色的直线 02-visual Studio MFC 绘制单一颜色三角形、渐变颜色边…