Microcontroller Experiment (3)

Preface

Experiment 1: Use the interrupt of timer T1 to control the P1.7 pin to output an audio signal, and start the buzzer to emit a familiar and distinctive musical audio with 10 syllables.

Experiment 2: Use a timer/counter to implement an electronic watch with an LCD that displays the year, month, day, day of the week, hours, minutes, and seconds. It is required that the hours and minutes can be easily set. The liquid crystal display uses LCD 1602.

Reference link

Static display and dynamic display of LED digital tube (Keil+Proteus)-CSDN Blog

Timer/Counter Application-CSDN Blog

7-key electronic keyboard music player proteus_c51 electronic keyboard program based on 51 microcontroller_BT-BOX's blog-CSDN blog​​​​​

Notes: C51 microcontroller - music playback, simulated piano keys. _c51 Music_c-tion’s blog-CSDN blog

51 microcontroller learning--buzzer plays music_51 microcontroller buzzer music code-CSDN blog

Leap year (noun in calendar)_Baidu Encyclopedia (baidu.com)

Buzzer plays music_bilibili_bilibili

[51 MCU] The buzzer plays music - Zhihu (zhihu.com)[51 MCU] The buzzer plays music - Zhihu (zhihu.com) a>

Independent keyboard interface design (Keil+Proteus)-CSDN Blog

How to read musical notation_How to read musical notation-CSDN Blog

[Lanqiao Cup - Microcontroller Study Notes] 16. Buzzer plays music (STC15F2K60S2)_Can active buzzer play music-CSDN Blog

Simplified musical notation_Baidu Encyclopedia (baidu.com)

[11-2] Buzzer plays sound & music_bilibili_bilibili

experiment one

Keil

I searched for this for a long time, either it was paid or the code was incomplete. Then I found one from the video and simply typed it, but I still didn’t understand it.

#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

There is nothing that needs to be changed in the schematic diagram, the main thing is how the sounds of different frequencies should be produced.

Expand 

In fact, it is mainly the code part. The schematic diagram does not need to be modified. You need to find a simplified musical score first, and then you need to know how to read music scores.

What needs to be grasped in the experiment is"the pitch of the sound"and"the length of the sound".

In the current code, double bass and bass are not considered, and the corresponding codes for bass, midrange and treble are provided.

You need to know the high and low parts of the note. Add a "." above the basic note of to indicate that the note is raised by one octave, which is called For treble; add a "." below the basic note to indicate This sound is lowered one octave and is called the bassbass. Here you just need to know how to distinguish between low, mid and high notes.

After knowing the pitch of the sound, the next step is the length of the sound, which actually represents the delay time of that sound. All you need to remember here isAdd a short note below the basic note. A horizontal line indicates shortening the duration of the original note by half.

Once these two problems are solved, you can write the program directly. In fact, you don’t need very complicated music theory knowledge. You don’t even need to know what the basic notes are. You can just look at the simplified music and modify it to your own. For special symbols You still need to check the data yourself to see what it means.

basic notes 

1 2 3 4 5 6 7
Do Re Me But Sun The And

 Part of the simplified score of "The Ordinary Road"

 

 Disadvantage: It is impossible to write a program corresponding to the double bass, because it only has codes for bass, midrange, and treble frequencies, and the sound is a bit inaccurate, but the tone feels similar and you can hear the general tone.

#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++;
		}
	}
}

Then there is this assignment "Stubborn"

 I am quite satisfied with this writing, but it will cut off in the middle and then start again. It is speculated that it is because it cannot store so much data and cannot play the entire song completely, but it is not bad, and it still gives a sense of accomplishment.

#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++;
		}
	}
}

Experiment 2

Keil

The code is still similar to the previous one, and then we need to add the processing of year, month, day and week. The week is usually represented by three letters, and the year, month and day are all numbers. With the previous foundation, it is easy to determine the display of LCD 1602 As well as the carry of the clock, what needs to be considered here is that the number of days is inconsistent according to different years and months, so this needs to be taken into account. It is not reflected here in the program. You can verify it by setting the time node. Whether the requirements are met.

0 1 2 3 4 5 6 7 8 9 A B C D AND F
80 and and and and . M M . d d In In
C0 T I M AND h h : m m : s s

I made a very serious mistake here, which is to use the remainder and divisibility when outputting the year on the LCD. The type of the year is character type, which results in the output number being wrong. I will use the control variable method to reduce it later. Finally, I thought that the maximum range of uchar is 255. The year is 4 digits, so the calculated value out of bounds will be incorrect. Later, it was changed to an integer.​ 

#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=1;
				month++;
			}
		}
	}else if(month==1 || month==3 || month==5 || month==7 || month==8 || month==10 || month==12){
		if(day>31){
			day=1;
			month++;
		}
	}else if(month==4 || month==6 || month==9 || month==11){
		if(day>30){
			day=1;
			month++;
		}
	}
	if(month==13){
		month=1;
		year++;
	}
	TR0=1;
}

Proteus

The schematic diagram remains the same as before.

 Celebrate the New Year online, let’s make dumplings together! ! !

 

 

expand

 

Let’s just say whether this should be expected or not. The general idea here is to add a few buttons to control the increase or decrease of the year, month, day, hour, and minute. There is no need to add seconds. It is difficult to adjust the adjustment even if the clock keeps moving.

Here I have been thinking about how to adjust the minute and carry, mainly starting with the number of days in each month. I suddenly thought that my previous electronic watch was that each position was independent, and adjusting the minute would not affect other things, that is, the minute would go from 0- 59 changes, with this idea, the clock can be determined quickly. Isn’t it just asking for surplus? Everyone can do it.

For the sake of simplicity, I made another assumption. When setting, each month has 31 days, and if it is set separately, the days and weeks will not be synchronized.

At present, an independent keyboard scan and the corresponding remainder function have been added based on the previous ones. The functions that directly lead to carry and borrow have not been implemented yet. At present, the simple implementation will be completed first. When scanning, try not to use the same port as RS, RW, and E. This will cause difficulty in querying. I don't know how to write the program. If the output of those three pins is affected, the LCD will keep querying and issuing warnings.

I thought of something about this yesterday, and I felt like I suddenly realized it. Is it necessary to back off one digit before the time is less than 0? Because they are all unsigned numbers, it is difficult to set them. Then I thought of a method. Assume that you want to determine the minute. You can first add 60 to the minute, and then divide 60. If the result is 0, it means that the previous value is less than 60. If its value is equal to 1, it means that its value Between [0,60), if its value is equal to 2, it means that it is greater than or equal to 60. Because the value is updated in real time, it is excluded that the number is a large positive number or a very small number. In the case of negative numbers, we mainly consider how to make the number above the hour position change accordingly in the two cases of -1 and 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;
	}
	
	//week=(7+week)%7;//周期不用考虑借位和进位就最好求了
	
	if(month==2){
		//是闰年(2月有29天)
		if(leap==1){
			if(day>29){
				day=1;
				month++;
			}
		}else{//不是闰年(2月有28天)
			if(day>28){
				day=1;
				month++;
			}
		}
	}else if(month==1 || month==3 || month==5 || month==7 || month==8 || month==10 || month==12){
		if(day>31){
			day=1;
			month++;
		}
	}else if(month==4 || month==6 || month==9 || month==11){
		if(day>30){
			day=1;
			month++;
		}
	}
	if(month==13){
		month=1;
		year++;
	}
	TR0=1;
}

In order to better express the meaning of the button, a text writing module is used. If the device is named directly in Chinese, it will easily cause the program to crash.

comprehensive 

Whatever it looks like, it’s just a little stuck.

 

Supplement on December 3: I made a low-level mistake. Every month starts from the 1st, and every year starts from January, not from 0, so it cannot be assigned a value of 0 after crossing the boundary like time, but should be The value is assigned to 1. I feel like someone can point it out. If there are any other problems, you are welcome to correct me.

Summarize

It's actually very interesting if you think about it seriously, but it's also very time-consuming. I'll try some new features when I have time.

Guess you like

Origin blog.csdn.net/weixin_64066303/article/details/134624276