单片机实验(三)

前言

实验一:利用定时器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

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

cpp 复制代码
#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

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

拓展

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

实验需要把握的就是**"音的高低"** 和**"音的长短"。**

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

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

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

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

基本音符

|--------|--------|--------|--------|---------|--------|--------|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| Do | Re | Mi | Fa | Sol | La | Si |

《平凡之路》的部分简谱

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

复制代码
#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++;
		}
	}
}

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

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

cpp 复制代码
#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的显示以及时钟的进位,这里需要考虑的是根据不同的年份以及月份,他的天数是不一致的,所以这个需要考虑进去,经过在程序中这里也没有体现到,可以通过设置时间的节点来去验证他是否满足要求。

|----|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
| 80 | | y | y | y | y | . | M | M | . | d | d | | w | w | | |
| C0 | | T | I | M | E | | | h | h | : | m | m | : | s | s | |

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

cpp 复制代码
#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这两种情况怎么让小时的位置上面的数发生相应的变化。

cpp 复制代码
#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;
}

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

综合

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

总结

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

相关推荐
Rousson2 小时前
硬件学习笔记--82 连接器的选用原则与流程
笔记·单片机·学习
三佛科技-134163842122 小时前
高速风筒方案开发 高速风筒MCU控制方案设计
单片机·嵌入式硬件·智能家居·pcb工艺
清风6666668 小时前
基于单片机的螺旋藻生长大棚PH智能控制设计
单片机·嵌入式硬件·毕业设计·课程设计
ting_zh9 小时前
微控制器(Micro Controller Unit, MCU)基础整理
单片机·嵌入式硬件
清风6666669 小时前
基于单片机的图书馆智能座位管理平台
数据库·单片机·嵌入式硬件·毕业设计·课程设计
得单片机的运11 小时前
STM32的以太网的搭建
stm32·单片机·嵌入式硬件·物联网·以太网·iot·w5500
酷飞飞11 小时前
RTC和看门狗基于GD32F407VE的天空星的配置
stm32·单片机·嵌入式硬件·mcu
WD1372980155713 小时前
WD5030A,24V降5V,15A 大电流,应用于手机、平板、笔记本充电器
stm32·单片机·嵌入式硬件·智能手机·汽车·电脑·51单片机
日更嵌入式的打工仔13 小时前
GPIO 中断通用配置指南
stm32·单片机·嵌入式硬件
平凡灵感码头13 小时前
基于 STM32 的智能门锁系统,系统界面设计
stm32·单片机·嵌入式硬件