基于51单片机和WS2812B彩色点阵屏(32X8)、DS3231、红外遥控的可调滚动时钟+农历、温度、节气

系列文章目录


前言

网传的农历数据有误(2025年、2057年、2089年),已修改正确(这三处错误已跟Windows的日历中的农历进行校对,其他地方可能还有错误,发现了再修改)。

【编程环境】Keil C251
【单片机】STC32G12K128
【频率】1T@30.000MHz
【外设】WS2812B彩色点阵屏(32X8)、DS3231时钟芯片、红外收发模块

之前做了一个MAX7219驱动的32X8点阵屏的像素时钟,当时就计划做一个彩色的。当时还没学会点亮WS2812B,所以就做了一个普通的练一下。

当时也还没研究农历日期、节气等怎么显示,所以之前的较简单,这次的彩色像素时钟,除了可以显示年月日、时分秒、星期,还可以显示农历日期、节气对应的序号、温度。

还有一个不同之处就是,之前的星期需要自行设置,现在是根据公历的年月日自行计算,无需设置。

效果查看/操作演示:B站搜索“甘腾胜”或“gantengsheng”查看。
源代码下载:B站对应视频的简介有工程文件下载链接。

一、效果展示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、原理分析

1、本案例参照以下博文

基于51单片机和DS1302时钟模块、32X8点阵屏(MAX7219驱动)的红外遥控的可调滚动像素时钟

2、WS2812B灯珠的驱动

请搜索其他博主关于WS2812B芯片的介绍。

驱动WS2812B灯珠时写1和写0跟DS18B20的通信差不多,都是根据高电平的时长来确定是1还是0。

我觉得最重要的函数是让指定位置的WS2812B灯珠按指定的颜色进行显示,这样就可以从点到面,按自己想法显示想要的数字、字母、图案了。

3、农历日期、节气、星期的计算

农历日期、节气、星期这三个都是通过查表法计算得出,因农历日期、节气的计算跟天体运动周期有关,直接计算会很复杂。

关于农历的简单介绍请看以下博客:
基于51单片机和LCD12864、DS3231、独立按键的万年历可调时钟+温度显示

节气的序号和温度通过二进制显示,高位在上。

4、温度显示

DS3231时钟芯片除了计时精确外,还可以测温度,无需再接多一个DS18B20,直接从DS3231芯片的寄存器中读取温度即可。

5、数字的滚动显示

需要写一个滚动显示的函数,参数里有偏移量,根据偏移量确定偏移多少个像素,偏移量的变量在定时器中按一定的时间间隔自增1。

也需要记录上一次从时钟芯片读出的时分秒,跟新读出来的时分秒进行比较,有改变的话就进行滚动显示,没有改变的话就保持偏移量为0,不滚动显示。

6、其他

由于正放的时候供电线在上边,不方便我贴上墙,就将整个显示旋转了180°,变成供电线在下边了。只需要修改一个函数就行了,即让指定位置的WS2812B灯珠按指定的颜色进行显示这个函数。

三、各模块代码

1、延时函数

h文件

#ifndef	__DELAY_H__
#define	__DELAY_H__
 
void Delay(unsigned int xms);
 
#endif

c文件

/**
  * 函    数:延时函数,延时约xms毫秒
  * 参    数:xms 延时的时间,范围:0~65535
  * 返 回 值:无
  */
void Delay(unsigned int xms)	//1T@30.0000MHz
{
	unsigned long edata i;
	while(xms--)
	{
		i=7500UL;
		while(i){i--;}
	}
}

2、彩色灯珠WS2812B

h文件

#ifndef __WS2812B_H__
#define __WS2812B_H__

extern unsigned char xdata WS2812B_Buffer[];
void WS2812B_Clear(void);
void WS2812B_WriteByte(unsigned char Byte);
void WS2812B_Update(void);
void WS2812B_SetBuffer(unsigned char X,unsigned char Y,unsigned char R,unsigned char G,unsigned char B);
void WS2812B_ShowNum(unsigned char Position,unsigned char Number,unsigned char Quantity,char Offset,unsigned char R,unsigned char G,unsigned char B);
void WS2812B_ClearNum(unsigned char Position);
void WS2812B_ShowWeek(unsigned char Week,unsigned char R1,unsigned char G1,unsigned char B1,unsigned char R2,unsigned char G2,unsigned char B2);
void WS2812B_ClearWeek(unsigned char Week);
void WS2812B_ShowColon(unsigned char R,unsigned char G,unsigned char B);
void WS2812B_ClearColon(void);

#endif

c文件

#include <STC32G.H>
#include <INTRINS.H>	//需要用空操作 _nop_(); 来延时

//引脚定义
sbit WS2812B_Din=P2^3;

//用3*256个字节作为WS2812B彩色点阵屏(32X8像素)的显示缓存
//共有256个灯珠,每个灯珠需要写入24Bit(3个字节)控制显示的颜色
//WS2812B芯片要求按G、R、B的顺序发送数据,并且每个字节要高位先发
//缓存数组中每三个字节为一组,每一组分别对应一个灯珠的R(红)、G(绿)、B(蓝)三原色
//第一组对应数据传输方向的第一个灯珠,第二组对应第二个灯珠,以此类推
//想更改WS2812B彩屏的显示,先更改此显示缓存中的数据,再通过函数WS2812B_Update将显示缓存的数据写入每个灯珠的WS2812B芯片内
unsigned char xdata WS2812B_Buffer[3*256];

//数字字模数据,横向取模,高位在左,阴码(亮点为1),使用每个字节的低3位
//每个数字的大小是宽3高5
unsigned char code MyNumber[]={
0x00,0x07,0x05,0x05,0x05,0x07,0x00,//0
0x00,0x02,0x06,0x02,0x02,0x07,0x00,//1
0x00,0x07,0x01,0x07,0x04,0x07,0x00,//2
0x00,0x07,0x01,0x07,0x01,0x07,0x00,//3
0x00,0x05,0x05,0x07,0x01,0x01,0x00,//4
0x00,0x07,0x04,0x07,0x01,0x07,0x00,//5
0x00,0x07,0x04,0x07,0x05,0x07,0x00,//6
0x00,0x07,0x01,0x01,0x01,0x01,0x00,//7
0x00,0x07,0x05,0x07,0x05,0x07,0x00,//8
0x00,0x07,0x05,0x07,0x01,0x07,0x00,//9
};

/**
  * 函    数:WS2812B彩屏私有延时函数,1T@24.000MHz调用可延时约100us
  * 参    数:无
  * 返 回 值:无
  * 说    明:系统初始化时务必将WTST初始化为00H
  */
void WS2812B_Delay100us(void)
{
	unsigned long edata i;
	i=750UL;
	while(i){i--;}
}

/**
  * 函    数:WS2812B彩屏清空显示缓存
  * 参    数:无
  * 返 回 值:无
  * 说    明:需要调用函数WS2812B_Update才能更新显示
*/
void WS2812B_Clear(void)
{
	unsigned int i;
	for(i=0;i<3*256;i++){WS2812B_Buffer[i]=0;}
}

/**
  * 函    数:WS2812B彩屏写入一个字节
  * 参    数:Byte 要写入的字节
  * 返 回 值:无
  * 说    明:频率要求:1T@30.000MHz,如果要换其他频率,则需要调整“_nop_();”的数量
  */
void WS2812B_WriteByte(unsigned char Byte)
{
	unsigned char i;
	
	EA=0;	//关闭总中断,因时序要求严格,不能被打断,并要求中断函数执行的时间不能太长,否则相当于发送了重置信号

	for(i=0;i<8;i++)
	{
		if(Byte&(0x80>>i))	//写1(高位先发)
		{
			WS2812B_Din=1;	//根据高电平的时长确定发送的是1还是0,跟DS18B20类似
			//用空操作进行延时,单片机使用不同的频率,就需要不一样“_nop_();”的数量
			_nop_();_nop_();_nop_();_nop_();
			_nop_();_nop_();_nop_();_nop_();
			_nop_();_nop_();_nop_();_nop_();
			_nop_();_nop_();_nop_();_nop_();
			WS2812B_Din=0;	//经测试,数据线拉低后不用加延时
		}
		else	//写0
		{
			WS2812B_Din=1;
			_nop_();_nop_();_nop_();_nop_();
			_nop_();_nop_();_nop_();_nop_();
			WS2812B_Din=0;	//经测试,数据线拉低后不用加延时
		}
	}
	
	EA=1;	//开启总中断

}

/**
  * 函    数:WS2812B彩屏更新显示
  * 参    数:无
  * 返 回 值:无
  * 说    明:将显示缓存数组WS2812B_Buffer的数据写入到灯珠的WS2812B芯片内
  */
void WS2812B_Update(void)
{
	unsigned int i;
	
	for(i=0;i<256;i++)
	{
		WS2812B_WriteByte(WS2812B_Buffer[3*i+1]);	//绿
		WS2812B_WriteByte(WS2812B_Buffer[3*i  ]);	//红
		WS2812B_WriteByte(WS2812B_Buffer[3*i+2]);	//蓝
	}
	
	WS2812B_Delay100us();	//Reset(重置)信号
}

/**
  * 函    数:WS2812B彩屏设置一个灯珠的缓存
  * 参    数:X 水平坐标,范围:0~31,以左上角的灯珠为原点,向右为X轴正方向
  * 参    数:Y 竖直坐标,范围:0~7 ,以左上角的灯珠为原点,向下为Y轴正方向
  * 参    数:R(Red  )红,范围:0~255
  * 参    数:G(Green)绿,范围:0~255
  * 参    数:B(Blue )蓝,范围:0~255
  * 返 回 值:无
  * 说    明:需要调用函数WS2812B_Update才能更新显示
  */
void WS2812B_SetBuffer(unsigned char X,unsigned char Y,unsigned char R,unsigned char G,unsigned char B)
{
	/*以下两个if-else选择其中一个使用,第一个屏幕正放时使用,第二个倒放时使用*/

//	if(X%2==0)
//	{
//		WS2812B_Buffer[3*(8*X+Y)  ]=R;	//红
//		WS2812B_Buffer[3*(8*X+Y)+1]=G;	//绿
//		WS2812B_Buffer[3*(8*X+Y)+2]=B;	//蓝
//	}
//	else
//	{
//		WS2812B_Buffer[3*(8*X+7-Y)  ]=R;	//红
//		WS2812B_Buffer[3*(8*X+7-Y)+1]=G;	//绿
//		WS2812B_Buffer[3*(8*X+7-Y)+2]=B;	//蓝
//	}

	if(X%2==0)
	{
		WS2812B_Buffer[3*(8*(31-X)+Y)  ]=R;	//红
		WS2812B_Buffer[3*(8*(31-X)+Y)+1]=G;	//绿
		WS2812B_Buffer[3*(8*(31-X)+Y)+2]=B;	//蓝
	}
	else
	{
		WS2812B_Buffer[3*(8*(31-X)+7-Y)  ]=R;	//红
		WS2812B_Buffer[3*(8*(31-X)+7-Y)+1]=G;	//绿
		WS2812B_Buffer[3*(8*(31-X)+7-Y)+2]=B;	//蓝
	}

}

/**
  * 函    数:WS2812B彩屏按设定的位置、偏移量、颜色显示数字
  * 参    数:Position 要显示数字的位置,范围:1~6
  * 参    数:Number 要显示的数字,范围:0~9
  * 参    数:Quantity 这一位置所显示数字的总数量,例如,秒的个位,可以显示0~9这十个数字,显示的数字的总数量为10
  * 参    数:R(Red  )红,范围:0~255
  * 参    数:G(Green)绿,范围:0~255
  * 参    数:B(Blue )蓝,范围:0~255
  * 返 回 值:无
  * 说    明:需要调用函数WS2812B_Update才能更新显示
  */
void WS2812B_ShowNum(unsigned char Position,unsigned char Number,unsigned char Quantity,char Offset,unsigned char R,unsigned char G,unsigned char B)
{
    unsigned char i,j,k;

	k=7*Quantity;
	j=(7*Number+Offset+k)%k;

	if(Position==1)
	{
		for(i=0;i<7;i++)
		{
			if(MyNumber[(j+i)%k] & 0x04){WS2812B_SetBuffer(2,i,R,G,B);}
			else{WS2812B_SetBuffer(2,i,0,0,0);}
			if(MyNumber[(j+i)%k] & 0x02){WS2812B_SetBuffer(3,i,R,G,B);}
			else{WS2812B_SetBuffer(3,i,0,0,0);}
			if(MyNumber[(j+i)%k] & 0x01){WS2812B_SetBuffer(4,i,R,G,B);}
			else{WS2812B_SetBuffer(4,i,0,0,0);}
		}
	}
	else if(Position==2)
	{
		for(i=0;i<7;i++)
		{
			if(MyNumber[(j+i)%k] & 0x04){WS2812B_SetBuffer(6,i,R,G,B);}
			else{WS2812B_SetBuffer(6,i,0,0,0);}
			if(MyNumber[(j+i)%k] & 0x02){WS2812B_SetBuffer(7,i,R,G,B);}
			else{WS2812B_SetBuffer(7,i,0,0,0);}
			if(MyNumber[(j+i)%k] & 0x01){WS2812B_SetBuffer(8,i,R,G,B);}
			else{WS2812B_SetBuffer(8,i,0,0,0);}
		}
	}
	else if(Position==3)
	{
		for(i=0;i<7;i++)
		{
			if(MyNumber[(j+i)%k] & 0x04){WS2812B_SetBuffer(12,i,R,G,B);}
			else{WS2812B_SetBuffer(12,i,0,0,0);}
			if(MyNumber[(j+i)%k] & 0x02){WS2812B_SetBuffer(13,i,R,G,B);}
			else{WS2812B_SetBuffer(13,i,0,0,0);}
			if(MyNumber[(j+i)%k] & 0x01){WS2812B_SetBuffer(14,i,R,G,B);}
			else{WS2812B_SetBuffer(14,i,0,0,0);}
		}
	}
	else if(Position==4)
	{
		for(i=0;i<7;i++)
		{
			if(MyNumber[(j+i)%k] & 0x04){WS2812B_SetBuffer(16,i,R,G,B);}
			else{WS2812B_SetBuffer(16,i,0,0,0);}
			if(MyNumber[(j+i)%k] & 0x02){WS2812B_SetBuffer(17,i,R,G,B);}
			else{WS2812B_SetBuffer(17,i,0,0,0);}
			if(MyNumber[(j+i)%k] & 0x01){WS2812B_SetBuffer(18,i,R,G,B);}
			else{WS2812B_SetBuffer(18,i,0,0,0);}
		}
	}
	else if(Position==5)
	{
		for(i=0;i<7;i++)
		{
			if(MyNumber[(j+i)%k] & 0x04){WS2812B_SetBuffer(22,i,R,G,B);}
			else{WS2812B_SetBuffer(22,i,0,0,0);}
			if(MyNumber[(j+i)%k] & 0x02){WS2812B_SetBuffer(23,i,R,G,B);}
			else{WS2812B_SetBuffer(23,i,0,0,0);}
			if(MyNumber[(j+i)%k] & 0x01){WS2812B_SetBuffer(24,i,R,G,B);}
			else{WS2812B_SetBuffer(24,i,0,0,0);}
		}
	}
	else if(Position==6)
	{
		for(i=0;i<7;i++)
		{
			if(MyNumber[(j+i)%k] & 0x04){WS2812B_SetBuffer(26,i,R,G,B);}
			else{WS2812B_SetBuffer(26,i,0,0,0);}
			if(MyNumber[(j+i)%k] & 0x02){WS2812B_SetBuffer(27,i,R,G,B);}
			else{WS2812B_SetBuffer(27,i,0,0,0);}
			if(MyNumber[(j+i)%k] & 0x01){WS2812B_SetBuffer(28,i,R,G,B);}
			else{WS2812B_SetBuffer(28,i,0,0,0);}
		}
	}
}

/**
  * 函    数:WS2812B彩屏清除显示
  * 参    数:Position 要清除显示位置,范围:1~6
  * 返 回 值:无
  * 说    明:需要调用函数WS2812B_Update才能更新显示
  */
void WS2812B_ClearNum(unsigned char Position)
{
    unsigned char i;

	if(Position==1)
	{
		for(i=0;i<7;i++)
		{
			WS2812B_SetBuffer(2,i,0,0,0);
			WS2812B_SetBuffer(3,i,0,0,0);
			WS2812B_SetBuffer(4,i,0,0,0);
		}
	}
	else if(Position==2)
	{
		for(i=0;i<7;i++)
		{
			WS2812B_SetBuffer(6,i,0,0,0);
			WS2812B_SetBuffer(7,i,0,0,0);
			WS2812B_SetBuffer(8,i,0,0,0);
		}
	}
	else if(Position==3)
	{
		for(i=0;i<7;i++)
		{
			WS2812B_SetBuffer(12,i,0,0,0);
			WS2812B_SetBuffer(13,i,0,0,0);
			WS2812B_SetBuffer(14,i,0,0,0);
		}
	}
	else if(Position==4)
	{
		for(i=0;i<7;i++)
		{
			WS2812B_SetBuffer(16,i,0,0,0);
			WS2812B_SetBuffer(17,i,0,0,0);
			WS2812B_SetBuffer(18,i,0,0,0);
		}
	}
	else if(Position==5)
	{
		for(i=0;i<7;i++)
		{
			WS2812B_SetBuffer(22,i,0,0,0);
			WS2812B_SetBuffer(23,i,0,0,0);
			WS2812B_SetBuffer(24,i,0,0,0);
		}
	}
	else if(Position==6)
	{
		for(i=0;i<7;i++)
		{
			WS2812B_SetBuffer(26,i,0,0,0);
			WS2812B_SetBuffer(27,i,0,0,0);
			WS2812B_SetBuffer(28,i,0,0,0);
		}
	}
}

/**
  * 函    数:WS2812B彩屏显示星期
  * 参    数:Week 需要显示的星期,范围:1~7(对应周一至周日)
  * 参    数:R1(Red  )红,范围:0~255
  * 参    数:G1(Green)绿,范围:0~255
  * 参    数:B1(Blue )蓝,范围:0~255
  * 参    数:R2(Red  )红,范围:0~255
  * 参    数:G2(Green)绿,范围:0~255
  * 参    数:B2(Blue )蓝,范围:0~255
  * 返 回 值:无
  * 说    明:点阵屏的最后一行,从左到右分别是:日、一、二、三、四、五、六
  * 说    明:当天对应星期的位置的颜色是:R2、G2、B2,剩余的是R1、G1、B1
  * 说    明:需要调用函数WS2812B_Update才能更新显示
  */
void WS2812B_ShowWeek(unsigned char Week,unsigned char R1,unsigned char G1,unsigned char B1,unsigned char R2,unsigned char G2,unsigned char B2)
{
	unsigned char i;

	for(i=0;i<32;i++){WS2812B_SetBuffer(i,7,0,0,0);}	//清空最后一行

	WS2812B_SetBuffer( 2,7,R1,G1,B1);WS2812B_SetBuffer( 3,7,R1,G1,B1);WS2812B_SetBuffer( 4,7,R1,G1,B1);
	WS2812B_SetBuffer( 6,7,R1,G1,B1);WS2812B_SetBuffer( 7,7,R1,G1,B1);WS2812B_SetBuffer( 8,7,R1,G1,B1);
	WS2812B_SetBuffer(10,7,R1,G1,B1);WS2812B_SetBuffer(11,7,R1,G1,B1);WS2812B_SetBuffer(12,7,R1,G1,B1);
	WS2812B_SetBuffer(14,7,R1,G1,B1);WS2812B_SetBuffer(15,7,R1,G1,B1);WS2812B_SetBuffer(16,7,R1,G1,B1);
	WS2812B_SetBuffer(18,7,R1,G1,B1);WS2812B_SetBuffer(19,7,R1,G1,B1);WS2812B_SetBuffer(20,7,R1,G1,B1);
	WS2812B_SetBuffer(22,7,R1,G1,B1);WS2812B_SetBuffer(23,7,R1,G1,B1);WS2812B_SetBuffer(24,7,R1,G1,B1);
	WS2812B_SetBuffer(26,7,R1,G1,B1);WS2812B_SetBuffer(27,7,R1,G1,B1);WS2812B_SetBuffer(28,7,R1,G1,B1);
	
	switch(Week)
	{
		case 7:WS2812B_SetBuffer( 2,7,R2,G2,B2);WS2812B_SetBuffer( 3,7,R2,G2,B2);WS2812B_SetBuffer( 4,7,R2,G2,B2);break;
		case 1:WS2812B_SetBuffer( 6,7,R2,G2,B2);WS2812B_SetBuffer( 7,7,R2,G2,B2);WS2812B_SetBuffer( 8,7,R2,G2,B2);break;
		case 2:WS2812B_SetBuffer(10,7,R2,G2,B2);WS2812B_SetBuffer(11,7,R2,G2,B2);WS2812B_SetBuffer(12,7,R2,G2,B2);break;
		case 3:WS2812B_SetBuffer(14,7,R2,G2,B2);WS2812B_SetBuffer(15,7,R2,G2,B2);WS2812B_SetBuffer(16,7,R2,G2,B2);break;
		case 4:WS2812B_SetBuffer(18,7,R2,G2,B2);WS2812B_SetBuffer(19,7,R2,G2,B2);WS2812B_SetBuffer(20,7,R2,G2,B2);break;
		case 5:WS2812B_SetBuffer(22,7,R2,G2,B2);WS2812B_SetBuffer(23,7,R2,G2,B2);WS2812B_SetBuffer(24,7,R2,G2,B2);break;
		case 6:WS2812B_SetBuffer(26,7,R2,G2,B2);WS2812B_SetBuffer(27,7,R2,G2,B2);WS2812B_SetBuffer(28,7,R2,G2,B2);break;
		default:break;
	}
}

/**
  * 函    数:WS2812B彩屏清除星期所在位置的显示
  * 参    数:Week 需要清除显示的星期,范围:1~7(对应周一至周日)
  * 返 回 值:无
  * 说    明:点阵屏的最后一行,从左到右分别是:日、一、二、三、四、五、六
  * 说    明:需要调用函数WS2812B_Update才能更新显示
  */
void WS2812B_ClearWeek(unsigned char Week)
{
	switch(Week)
	{
		case 7:WS2812B_SetBuffer( 2,7,0,0,0);WS2812B_SetBuffer( 3,7,0,0,0);WS2812B_SetBuffer( 4,7,0,0,0);break;
		case 1:WS2812B_SetBuffer( 6,7,0,0,0);WS2812B_SetBuffer( 7,7,0,0,0);WS2812B_SetBuffer( 8,7,0,0,0);break;
		case 2:WS2812B_SetBuffer(10,7,0,0,0);WS2812B_SetBuffer(11,7,0,0,0);WS2812B_SetBuffer(12,7,0,0,0);break;
		case 3:WS2812B_SetBuffer(14,7,0,0,0);WS2812B_SetBuffer(15,7,0,0,0);WS2812B_SetBuffer(16,7,0,0,0);break;
		case 4:WS2812B_SetBuffer(18,7,0,0,0);WS2812B_SetBuffer(19,7,0,0,0);WS2812B_SetBuffer(20,7,0,0,0);break;
		case 5:WS2812B_SetBuffer(22,7,0,0,0);WS2812B_SetBuffer(23,7,0,0,0);WS2812B_SetBuffer(24,7,0,0,0);break;
		case 6:WS2812B_SetBuffer(26,7,0,0,0);WS2812B_SetBuffer(27,7,0,0,0);WS2812B_SetBuffer(28,7,0,0,0);break;
		default:break;
	}
}

/**
  * 函    数:WS2812B彩屏显示冒号(时分秒之间的冒号)
  * 参    数:R(Red  )红,范围:0~255
  * 参    数:G(Green)绿,范围:0~255
  * 参    数:B(Blue )蓝,范围:0~255
  * 返 回 值:无
  * 说    明:需要调用函数WS2812B_Update才能更新显示
  */
void WS2812B_ShowColon(unsigned char R,unsigned char G,unsigned char B)
{
	WS2812B_SetBuffer(10,2,R,G,B);
	WS2812B_SetBuffer(10,4,R,G,B);
	WS2812B_SetBuffer(20,2,R,G,B);
	WS2812B_SetBuffer(20,4,R,G,B);
}

/**
  * 函    数:WS2812B清除显示冒号(时分秒之间的冒号)
  * 参    数:无
  * 返 回 值:无
  * 说    明:需要调用函数WS2812B_Update才能更新显示
  */
void WS2812B_ClearColon(void)
{
	WS2812B_SetBuffer(10,2,0,0,0);
	WS2812B_SetBuffer(10,4,0,0,0);
	WS2812B_SetBuffer(20,2,0,0,0);
	WS2812B_SetBuffer(20,4,0,0,0);
}

3、时钟芯片DS3231

h文件

#ifndef	__DS3231_H__
#define	__DS3231_H__
 
extern char Time[];
extern char Alarm[];
void DS3231_I2C_Start(void);
void DS3231_I2C_Stop(void);
void DS3231_I2C_SendByte(unsigned char Byte);
unsigned char DS3231_I2C_ReceiveByte(unsigned char AckBit);
void DS3231_WriteByte(unsigned char WordAddress,unsigned char Data);
unsigned char DS3231_ReadByte(unsigned char WordAddress);
void DS3231_SetTime(void);
void DS3231_ReadTime(void);
void DS3231_SetAlarm(void);
void DS3231_ReadAlarm(void);
void DS3231_ConvertT(void);
unsigned char DS3231_CheckBusy(void);
float DS3231_ReadT(void);

#endif

c文件

#include <STC32G.H>
#include <INTRINS.H>

#define DS3231_ADDRESS 0xD0	//DS3231的I2C地址

//DS3231引脚定义
sbit DS3231_SDA=P2^4;
sbit DS3231_SCL=P2^5;

//DS3231的时间地址:年,月,日,时,分,秒,星期
const unsigned char TimeAddress[7]={0x06,0x05,0x04,0x02,0x01,0x00,0x03,};

//DS3231的闹钟地址:A1日期/星期,A1时,A1分,A1秒,A2日期/星期,A2时,A2分
const unsigned char AlarmAddress[7]={0x0A,0x09,0x08,0x07,0x0D,0x0C,0x0B,};

//时间数组:年,月,日,时,分,秒,星期
char Time[]={25,3,12,11,23,53,3};	//时间设置的初始值

//闹钟数组:A1日期/星期,A1时,A1分,A1秒,A2日期/星期,A2时,A2分
char Alarm[]={1,6,7,0,1,6,7};	//闹钟设置的初始值

/**
  * 函    数:DS3231私有延时函数,延时约0.3us
  * 参    数:无
  * 返 回 值:无
  */
void DS3231_Delay(void)	//1T@30.0000MHz
{
	unsigned long edata i;
	i=2UL;
	while(i){i--;}
}

/**
  * 函    数:I2C开始
  * 参    数:无
  * 返 回 值:无
  */
void DS3231_I2C_Start(void)
{
	DS3231_SDA=1;
	DS3231_Delay();
	DS3231_SCL=1;
	DS3231_Delay();
	DS3231_SDA=0;
	DS3231_Delay();
	DS3231_SCL=0;
	DS3231_Delay();
}

/**
  * 函    数:I2C停止
  * 参    数:无
  * 返 回 值:无
  */
void DS3231_I2C_Stop(void)
{
	DS3231_SDA=0;DS3231_Delay();
	DS3231_SCL=1;DS3231_Delay();
	DS3231_SDA=1;DS3231_Delay();
}

/**
  * 函    数:I2C发送一个字节
  * 参    数:Byte 要发送的字节
  * 返 回 值:无
  */
void DS3231_I2C_SendByte(unsigned char Byte)
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		DS3231_SDA=Byte&(0x80>>i);DS3231_Delay();
		DS3231_SCL=1;DS3231_Delay();
		DS3231_SCL=0;DS3231_Delay();
	}
	DS3231_SCL=1;DS3231_Delay();	//额外一个时钟,不处理应答信号
	DS3231_SCL=0;DS3231_Delay();
}

/**
  * 函    数:I2C接收一个字节
  * 参    数:AckBit 要发送的应答位,0为应答,1为非应答
  * 返 回 值:接收到的一个字节数据
  */
unsigned char DS3231_I2C_ReceiveByte(unsigned char AckBit)
{
	unsigned char i,Byte=0x00;
	DS3231_SDA=1;DS3231_Delay();
	for(i=0;i<8;i++)
	{
		DS3231_SCL=1;DS3231_Delay();
		if(DS3231_SDA){Byte|=(0x80>>i);}
		DS3231_SCL=0;DS3231_Delay();
	}
	DS3231_SDA=AckBit;DS3231_Delay();	//发送应答位
	DS3231_SCL=1;DS3231_Delay();
	DS3231_SCL=0;DS3231_Delay();
	return Byte;
}

/**
  * 函    数:DS3231写入一个字节
  * 参    数:WordAddress 要写入字节的地址
  * 参    数:Data 要写入的数据
  * 返 回 值:无
  */
void DS3231_WriteByte(unsigned char WordAddress,unsigned char Data)
{
	DS3231_I2C_Start();
	DS3231_I2C_SendByte(DS3231_ADDRESS);
	DS3231_I2C_SendByte(WordAddress);
	DS3231_I2C_SendByte(Data);
	DS3231_I2C_Stop();
}

/**
  * 函    数:DS3231读取一个字节
  * 参    数:WordAddress 要读出字节的地址
  * 返 回 值:读出的数据
  */
unsigned char DS3231_ReadByte(unsigned char WordAddress)
{
	unsigned char Data;
	DS3231_I2C_Start();
	DS3231_I2C_SendByte(DS3231_ADDRESS);
	DS3231_I2C_SendByte(WordAddress);
	DS3231_I2C_Start();
	DS3231_I2C_SendByte(DS3231_ADDRESS|0x01);
	Data=DS3231_I2C_ReceiveByte(1);
	DS3231_I2C_Stop();
	return Data;
}

/**
  * 函    数:DS3231设置时间
  * 参    数:无
  * 返 回 值:无
  * 说    明:调用之后,Time数组的数据会被设置到DS3231中
  */
void DS3231_SetTime(void)
{
	unsigned char i;
	for(i=0;i<7;i++)	//依次写入:年,月,日,时,分,秒,星期
	{
		DS3231_WriteByte(TimeAddress[i],Time[i]/10*16+Time[i]%10);	//十进制转换为BCD码
	}
}

/**
  * 函    数:DS3231读取时间
  * 参    数:无
  * 返 回 值:无
  * 说    明:调用之后,DS3231中的数据会被读取到Time数组中
  */
void DS3231_ReadTime(void)
{
	unsigned char Temp,i;
	for(i=0;i<7;i++)	//依次读取:年,月,日,时,分,秒,星期
	{
		Temp=DS3231_ReadByte(TimeAddress[i]);
		Time[i]=Temp/16*10+Temp%16;	//BCD码转十进制后读取
	}
}

/**
  * 函    数:DS3231设置闹钟数据
  * 参    数:无
  * 返 回 值:无
  */
void DS3231_SetAlarm(void)
{
	unsigned char i;
	for(i=0;i<7;i++)	//依次写入:A1日期/星期,A1时,A1分,A1秒,A2日期/星期,A2时,A2分
	{
		DS3231_WriteByte(AlarmAddress[i],Time[i]/10*16+Time[i]%10);	//十进制转换为BCD码
	}
}

/**
  * 函    数:DS3231读取闹钟数据
  * 参    数:无
  * 返 回 值:无
  */
void DS3231_ReadAlarm(void)
{
	unsigned char Temp,i;
	for(i=0;i<7;i++)	//依次读取:A1日期/星期,A1时,A1分,A1秒,A2日期/星期,A2时,A2分
	{
		Temp=DS3231_ReadByte(AlarmAddress[i]);
		Alarm[i]=Temp/16*10+Temp%16;	//BCD码转十进制后读取
	}
}

/**
  * 函    数:DS3231转换温度
  * 参    数:无
  * 返 回 值:无
  */
void DS3231_ConvertT(void)
{
	unsigned char Data;
	Data=DS3231_ReadByte(0x0E);
	DS3231_WriteByte(0x0E,Data|0x20);
}

/**
  * 函    数:DS3231检查是否已转换完温度
  * 参    数:无
  * 返 回 值:无
  */
unsigned char DS3231_CheckBusy(void)
{
	unsigned char BusyState;
	BusyState=DS3231_ReadByte(0x0F);
	BusyState&=0x04;
	return BusyState;
}

/**
  * 函    数:DS3231读取温度
  * 参    数:无
  * 返 回 值:无
  */
float DS3231_ReadT(void)
{
	unsigned char TM,TL;
	int Temp;
	float T;
	TM=DS3231_ReadByte(0x11);
	TL=DS3231_ReadByte(0x12);
	Temp=(TM<<8)|TL;
	Temp>>=6;
	T=Temp/4.0;
	return T;
}

4、农历时间

h文件

#ifndef __LUNARTIME__
#define __LUNARTIME__

extern unsigned char LunarYear,LunarMonth,LunarDay; //农历年月日全局变量
extern unsigned char LunarLeapMonth;  //农历闰月全局变量,无时为0
unsigned char IsLeapYear(unsigned char Year);
unsigned char GetWeek(unsigned char Year,unsigned char Month,unsigned char Day);
unsigned char GetLunarDays(unsigned char LunarYear,unsigned char LunarMonth);
unsigned char GetLunarLeapMonth(unsigned char LunarYear);
void ConvertSolarToLunar(unsigned char Year,unsigned char Month,unsigned char Day);
unsigned char IsLunarNewYear(unsigned char Year,unsigned char Month,unsigned char Day);
unsigned char GetSolarTerms(unsigned char Year,unsigned char Month,unsigned char Day);
char GetSolarTermsMonth(unsigned char Year,unsigned char Month,unsigned char Day);
unsigned char GetMonthGanZhi(char SolarTermsMonth);
unsigned int CalculateDays(unsigned char Year,unsigned char Month,unsigned char Day);

#endif

c文件

#include <STC32G.H>

extern char Time[];	//时间缓存(声明这个数组是在外部定义的)
unsigned char MonthDays[]={31,28,31,30,31,30,31,31,30,31,30,31};	//平年每个月份的天数表
unsigned int code AccumulatedDays1[]={0,31,59,90,120,151,181,212,243,273,304,334};	//平年月份累计天数表
unsigned int code AccumulatedDays2[]={0,31,60,91,121,152,182,213,244,274,305,335};	//闰年月份累计天数表
unsigned char LunarYear,LunarMonth,LunarDay; //农历年月日全局变量
unsigned char LunarLeapMonth;  //农历闰月全局变量,无时为0
//2000年各月第一天星期表(用来自动计算所设置日期的星期值)
unsigned char code Week2000[12]={6,2,3,6,1,4,6,2,5,7,3,5};

//2000~2099年农历表
//数据来源:普中开发板店家提供的例程
//数据有误(包括网上流传的),2025年、2057年、2089年的,都已经修改正确了(已跟Windows的日历校对过),其他农历日期有发现错误再修改
unsigned char code LunarCalendar[300]={
/*数据含义

每年三个字节,
第一个字节:B7~B4表示闰月月份,值为0表示无闰月,B3~B0对应农历第1~4月的大小月情况,
第二个字节:B7-B0对应农历第5~12月的大小月情况,
第三个字节:B7表示农历第13个月的大小月情况(如果无闰月,则该位无意义),B6~B5表示春节的公历月份,B4~B0表示春节的公历日期
月份对应的位为1则表示此农历月为大月(30天),为0则表示小月(29天)

以2000年和2001年的数据为例

【2001年】
将三个字节展开为二进制格式:
0100 1101 0100 1010 1011 1000
二进制格式共24位,从左到右分别为第1位到第24位
第1位到第4位(0100):表示2001年闰四月
第5位到第17位(1101 0100 1010 1):表示13个农历月的大小月情况,1为大月30天,0为小月29天
第18位到第19位(01):表示春节在公历2001年的1月
第20位到第25位(1 1000):表示春节在公历2001年的(1月)24日

【2000年】
将三个字节展开为二进制格式
0000 1100 1001 0110 0100 0101
展开为二进制格式共24位,从左到右分别为第1位到第24位
第1位到第4位(0000):表示2000年无闰月
第5位到第17位(1100 1001 0110 0):表示12个农历月的大小月情况,1为大月30天,0为小月29天
(因2000年无闰月,则使用第5位到第17位的前12位(1100 1001 0110),第13位的“0”无意义)
第18位到第19位(10):表示春节在公历2000年的2月
第20位到第25位(0 0101):表示春节在公历2000年的(2月)5日
*/

0x0C,0x96,0x45,	//2000
0x4D,0x4A,0xB8,	//2001
0x0D,0x4A,0x4C,	//2002
0x0D,0xA5,0x41,	//2003
0x25,0xAA,0xB6,	//2004
0x05,0x6A,0x49,	//2005
0x7A,0xAD,0xBD,	//2006
0x02,0x5D,0x52,	//2007
0x09,0x2D,0x47,	//2008
0x5C,0x95,0xBA,	//2009
0x0A,0x95,0x4E,	//2010
0x0B,0x4A,0x43,	//2011
0x4B,0x55,0x37,	//2012
0x0A,0xD5,0x4A,	//2013
0x95,0x5A,0xBF,	//2014
0x04,0xBA,0x53,	//2015
0x0A,0x5B,0x48,	//2016
0x65,0x2B,0xBC,	//2017
0x05,0x2B,0x50,	//2018
0x0A,0x93,0x45,	//2019
0x47,0x4A,0xB9,	//2020
0x06,0xAA,0x4C,	//2021
0x0A,0xD5,0x41,	//2022
0x24,0xDA,0xB6,	//2023
0x04,0xB6,0x4A,	//2024
0x6A,0x57,0x3d,	//2025	//原:0x69,0x57,0x3D,	//2025 
0x0A,0x4E,0x51,	//2026
0x0D,0x26,0x46,	//2027
0x5E,0x93,0x3A,	//2028
0x0D,0x53,0x4D,	//2029
0x05,0xAA,0x43,	//2030
0x36,0xB5,0x37,	//2031
0x09,0x6D,0x4B,	//2032
0xB4,0xAE,0xBF,	//2033
0x04,0xAD,0x53,	//2034
0x0A,0x4D,0x48,	//2035
0x6D,0x25,0xBC,	//2036
0x0D,0x25,0x4F,	//2037
0x0D,0x52,0x44,	//2038
0x5D,0xAA,0x38,	//2039
0x0B,0x5A,0x4C,	//2040
0x05,0x6D,0x41,	//2041
0x24,0xAD,0xB6,	//2042
0x04,0x9B,0x4A,	//2043
0x7A,0x4B,0xBE,	//2044
0x0A,0x4B,0x51,	//2045
0x0A,0xA5,0x46,	//2046
0x5B,0x52,0xBA,	//2047
0x06,0xD2,0x4E,	//2048
0x0A,0xDA,0x42,	//2049
0x35,0x5B,0x37,	//2050
0x09,0x37,0x4B,	//2051
0x84,0x97,0xC1,	//2052
0x04,0x97,0x53,	//2053
0x06,0x4B,0x48,	//2054
0x66,0xA5,0x3C,	//2055
0x0E,0xA5,0x4F,	//2056
0x06,0xAA,0x44,	//2057	//原:0x06,0xB2,0x44,	//2057
0x4A,0xB6,0x38,	//2058
0x0A,0xAE,0x4C,	//2059
0x09,0x2E,0x42,	//2060
0x3C,0x97,0x35,	//2061
0x0C,0x96,0x49,	//2062
0x7D,0x4A,0xBD,	//2063
0x0D,0x4A,0x51,	//2064
0x0D,0xA5,0x45,	//2065
0x55,0xAA,0xBA,	//2066
0x05,0x6A,0x4E,	//2067
0x0A,0x6D,0x43,	//2068
0x45,0x2E,0xB7,	//2069
0x05,0x2D,0x4B,	//2070
0x8A,0x95,0xBF,	//2071
0x0A,0x95,0x53,	//2072
0x0B,0x4A,0x47,	//2073
0x6B,0x55,0x3B,	//2074
0x0A,0xD5,0x4F,	//2075
0x05,0x5A,0x45,	//2076
0x4A,0x5D,0x38,	//2077
0x0A,0x5B,0x4C,	//2078
0x05,0x2B,0x42,	//2079
0x3A,0x93,0xB6,	//2080
0x06,0x93,0x49,	//2081
0x77,0x29,0xBD,	//2082
0x06,0xAA,0x51,	//2083
0x0A,0xD5,0x46,	//2084
0x54,0xDA,0xBA,	//2085
0x04,0xB6,0x4E,	//2086
0x0A,0x57,0x43,	//2087
0x45,0x27,0x38,	//2088
0x0d,0x16,0x4A,	//2089	//原:0x0D,0x26,0x4A,	//2089
0x8E,0x93,0x3E,	//2090
0x0D,0x52,0x52,	//2091
0x0D,0xAA,0x47,	//2092
0x66,0xB5,0x3B,	//2093
0x05,0x6D,0x4F,	//2094
0x04,0xAE,0x45,	//2095
0x4A,0x4E,0xB9,	//2096
0x0A,0x4D,0x4C,	//2097
0x0D,0x15,0x41,	//2098
0x2D,0x92,0xB5,	//2099
};

//2000~2099年节气表
//数据来源:https://blog.csdn.net/qq_46041930/article/details/108653836
unsigned char code The24SolarTerms[1200]={
/*数据含义

数据为二十四节气对应年的日期表
依次从1月开始,每月用一个字节存放,用15减去高四位数据即得到第一个节气对应日期
低四位数据加上15即得到第二个节气对应日期

以2000年第1个月的数据为例:0x96:
15-9=6:1月6日为小寒
15+6=21:1月21日为大寒
以2001年第1个月的数据为例:0xA5:
15-10=5:1月5日为小寒
15+5=20: 1月20日为大寒
*/
0x96,0xB4,0xA5,0xB5,0xA6,0xA6,0x87,0x88,0x88,0x78,0x87,0x86,	//2000
0xA5,0xB3,0xA5,0xA5,0xA6,0xA6,0x88,0x88,0x88,0x78,0x87,0x87,	//2001
0xA5,0xB4,0x96,0xA5,0x96,0x96,0x88,0x88,0x78,0x78,0x87,0x87,	//2002
0x95,0xB4,0x96,0xA5,0x96,0x97,0x88,0x78,0x78,0x69,0x78,0x87,	//2003
0x96,0xB4,0xA5,0xB5,0xA6,0xA6,0x87,0x88,0x88,0x78,0x87,0x86,	//2004
0xA5,0xB3,0xA5,0xA5,0xA6,0xA6,0x88,0x88,0x88,0x78,0x87,0x87,	//2005
0xA5,0xB4,0x96,0xA5,0xA6,0x96,0x88,0x88,0x78,0x78,0x87,0x87,	//2006
0x95,0xB4,0x96,0xA5,0x96,0x97,0x88,0x78,0x78,0x69,0x78,0x87,	//2007
0x96,0xB4,0xA5,0xB5,0xA6,0xA6,0x87,0x88,0x87,0x78,0x87,0x86,	//2008
0xA5,0xB3,0xA5,0xB5,0xA6,0xA6,0x88,0x88,0x88,0x78,0x87,0x87,	//2009
0xA5,0xB4,0x96,0xA5,0xA6,0x96,0x88,0x88,0x78,0x78,0x87,0x87,	//2010
0x95,0xB4,0x96,0xA5,0x96,0x97,0x88,0x78,0x78,0x79,0x78,0x87,	//2011
0x96,0xB4,0xA5,0xB5,0xA5,0xA6,0x87,0x88,0x87,0x78,0x87,0x86,	//2012
0xA5,0xB3,0xA5,0xB5,0xA6,0xA6,0x87,0x88,0x88,0x78,0x87,0x87,	//2013
0xA5,0xB4,0x96,0xA5,0xA6,0x96,0x88,0x88,0x78,0x78,0x87,0x87,	//2014
0x95,0xB4,0x96,0xA5,0x96,0x97,0x88,0x78,0x78,0x79,0x77,0x87,	//2015
0x95,0xB4,0xA5,0xB4,0xA5,0xA6,0x87,0x88,0x87,0x78,0x87,0x86,	//2016
0xA5,0xC3,0xA5,0xB5,0xA6,0xA6,0x87,0x88,0x88,0x78,0x87,0x87,	//2017
0xA5,0xB4,0xA6,0xA5,0xA6,0x96,0x88,0x88,0x78,0x78,0x87,0x87,	//2018
0xA5,0xB4,0x96,0xA5,0x96,0x96,0x88,0x78,0x78,0x79,0x77,0x87,	//2019
0x95,0xB4,0xA5,0xB4,0xA5,0xA6,0x97,0x87,0x87,0x78,0x87,0x86,	//2020
0xA5,0xC3,0xA5,0xB5,0xA6,0xA6,0x87,0x88,0x88,0x78,0x87,0x86,	//2021
0xA5,0xB4,0xA5,0xB5,0xA6,0x96,0x88,0x88,0x88,0x78,0x87,0x87,	//2022
0xA5,0xB4,0x96,0xA5,0x96,0x96,0x88,0x78,0x78,0x79,0x77,0x87,	//2023
0x95,0xB4,0xA5,0xB4,0xA5,0xA6,0x97,0x87,0x87,0x78,0x87,0x96,	//2024
0xA5,0xC3,0xA5,0xB5,0xA6,0xA6,0x87,0x88,0x88,0x78,0x87,0x86,	//2025
0xA5,0xB3,0xA5,0xA5,0xA6,0xA6,0x88,0x88,0x88,0x78,0x87,0x87,	//2026
0xA5,0xB4,0x96,0xA5,0x96,0x96,0x88,0x78,0x78,0x78,0x87,0x87,	//2027
0x95,0xB4,0xA5,0xB4,0xA5,0xA6,0x97,0x87,0x87,0x78,0x87,0x96,	//2028
0xA5,0xC3,0xA5,0xB5,0xA6,0xA6,0x87,0x88,0x88,0x78,0x87,0x86,	//2029
0xA5,0xB3,0xA5,0xA5,0xA6,0xA6,0x88,0x88,0x88,0x78,0x87,0x87,	//2030
0xA5,0xB4,0x96,0xA5,0x96,0x96,0x88,0x78,0x78,0x78,0x87,0x87,	//2031
0x95,0xB4,0xA5,0xB4,0xA5,0xA6,0x97,0x87,0x87,0x78,0x87,0x96,	//2032
0xA5,0xC3,0xA5,0xB5,0xA6,0xA6,0x87,0x88,0x88,0x78,0x87,0x86,	//2033
0xA5,0xB3,0xA5,0xA5,0xA6,0xA6,0x88,0x88,0x88,0x78,0x87,0x87,	//2034
0xA5,0xB4,0x96,0xA5,0xA6,0x96,0x88,0x88,0x78,0x78,0x87,0x87,	//2035
0x95,0xB4,0xA5,0xB4,0xA5,0xA6,0x97,0x87,0x87,0x78,0x87,0x96,	//2036
0xA5,0xC3,0xA5,0xB5,0xA6,0xA6,0x87,0x88,0x88,0x78,0x87,0x86,	//2037
0xA5,0xB3,0xA5,0xA5,0xA6,0xA6,0x88,0x88,0x88,0x78,0x87,0x87,	//2038
0xA5,0xB4,0x96,0xA5,0xA6,0x96,0x88,0x88,0x78,0x78,0x87,0x87,	//2039
0x95,0xB4,0xA5,0xB4,0xA5,0xA6,0x97,0x87,0x87,0x78,0x87,0x96,	//2040
0xA5,0xC3,0xA5,0xB5,0xA5,0xA6,0x87,0x88,0x87,0x78,0x87,0x86,	//2041
0xA5,0xB3,0xA5,0xB5,0xA6,0xA6,0x88,0x88,0x88,0x78,0x87,0x87,	//2042
0xA5,0xB4,0x96,0xA5,0xA6,0x96,0x88,0x88,0x78,0x78,0x87,0x87,	//2043
0x95,0xB4,0xA5,0xB4,0xA5,0xA6,0x97,0x87,0x87,0x88,0x87,0x96,	//2044
0xA5,0xC3,0xA5,0xB4,0xA5,0xA6,0x87,0x88,0x87,0x78,0x87,0x86,	//2045
0xA5,0xB3,0xA5,0xB5,0xA6,0xA6,0x87,0x88,0x88,0x78,0x87,0x87,	//2046
0xA5,0xB4,0x96,0xA5,0xA6,0x96,0x88,0x88,0x78,0x78,0x87,0x87,	//2047
0x95,0xB4,0xA5,0xB4,0xA5,0xA5,0x97,0x87,0x87,0x88,0x86,0x96,	//2048
0xA4,0xC3,0xA5,0xB4,0xA5,0xA6,0x97,0x87,0x87,0x78,0x87,0x86,	//2049
0xA5,0xC3,0xA5,0xB5,0xA6,0xA6,0x87,0x88,0x88,0x78,0x87,0x87,	//2050
0xA5,0xB4,0xA5,0xA5,0xA6,0x96,0x88,0x88,0x88,0x78,0x87,0x87,	//2051
0xA5,0xB4,0xA5,0xB4,0xA5,0xA5,0x97,0x87,0x97,0x88,0x86,0x96,	//2052
0xA4,0xC3,0xA5,0xB4,0xA5,0xA6,0x97,0x87,0x87,0x78,0x87,0x86,	//2053
0xA5,0xC3,0xA5,0xB5,0xA6,0xA6,0x87,0x88,0x88,0x78,0x87,0x87,	//2054
0xA5,0xB4,0xA5,0xA5,0xA6,0xA6,0x88,0x88,0x88,0x78,0x87,0x87,	//2055
0xA5,0xB4,0xA5,0xB4,0xA5,0xA5,0x97,0x87,0x87,0x88,0x86,0x96,	//2056
0xA4,0xC3,0xA5,0xB4,0xA5,0xA6,0x97,0x87,0x87,0x78,0x87,0x96,	//2057
0xA5,0xC3,0xA5,0xB5,0xA6,0xA6,0x87,0x88,0x88,0x78,0x87,0x86,	//2058
0xA5,0xB4,0xA5,0xA5,0xA6,0xA6,0x88,0x88,0x88,0x78,0x87,0x87,	//2059
0xA5,0xB4,0xA5,0xB4,0xA5,0xA5,0x97,0x87,0x87,0x87,0x96,0x96,	//2060
0xA4,0xC3,0xA5,0xB4,0xA5,0xA6,0x97,0x87,0x87,0x78,0x87,0x96,	//2061
0xA5,0xC3,0xA5,0xB5,0xA6,0xA6,0x87,0x88,0x88,0x78,0x87,0x86,	//2062
0xA5,0xB3,0xA5,0xA5,0xA6,0xA6,0x88,0x88,0x88,0x78,0x87,0x87,	//2063
0xA5,0xB4,0xA5,0xB4,0xA5,0xA5,0x97,0x87,0x87,0x87,0x96,0x96,	//2064
0xA4,0xC3,0xA5,0xB4,0xA5,0xA6,0x97,0x87,0x87,0x78,0x87,0x96,	//2065
0xA5,0xC3,0xA5,0xB5,0xA6,0xA6,0x87,0x88,0x88,0x78,0x87,0x86,	//2066
0xA5,0xB3,0xA5,0xA5,0xA6,0xA6,0x88,0x88,0x88,0x78,0x87,0x87,	//2067
0xA5,0xB4,0xA5,0xB4,0xB5,0xA5,0x97,0x97,0x87,0x87,0x96,0x96,	//2068
0xA4,0xC3,0xA5,0xB4,0xA5,0xA6,0x97,0x87,0x87,0x78,0x87,0x96,	//2069
0xA5,0xC3,0xA5,0xB5,0xA5,0xA6,0x87,0x88,0x87,0x78,0x87,0x86,	//2070
0xA5,0xB3,0xA5,0xA5,0xA6,0xA6,0x88,0x88,0x88,0x78,0x87,0x87,	//2071
0xA5,0xB4,0xA5,0xB4,0xB5,0xA5,0x97,0x97,0x87,0x87,0x96,0x96,	//2072
0xA4,0xC3,0xA5,0xB4,0xA5,0xA6,0x97,0x87,0x87,0x88,0x87,0x96,	//2073
0xA5,0xC3,0xA5,0xB5,0xA5,0xA6,0x87,0x88,0x87,0x78,0x87,0x86,	//2074
0xA5,0xB3,0xA5,0xB5,0xA6,0xA6,0x87,0x88,0x88,0x78,0x87,0x87,	//2075
0xA5,0xB4,0xA5,0xB4,0xB5,0xA5,0x97,0x97,0x87,0x87,0x96,0x96,	//2076
0xA4,0xC3,0xA5,0xB4,0xA5,0xA6,0x97,0x87,0x87,0x88,0x87,0x96,	//2077
0xA5,0xC3,0xA5,0xB4,0xA5,0xA6,0x97,0x88,0x87,0x78,0x87,0x86,	//2078
0xA5,0xB3,0xA5,0xB5,0xA6,0xA6,0x87,0x88,0x88,0x78,0x87,0x87,	//2079
0xA5,0xB4,0xA5,0xB4,0xB5,0xA5,0x97,0x97,0x87,0x87,0x96,0x96,	//2080
0xA4,0xC3,0xA5,0xB4,0xA5,0xA5,0x97,0x87,0x87,0x88,0x86,0x96,	//2081
0xA5,0xC3,0xA5,0xB4,0xA5,0xA6,0x97,0x87,0x87,0x78,0x87,0x86,	//2082
0xA5,0xC3,0xA5,0xB5,0xA6,0xA6,0x87,0x88,0x88,0x78,0x87,0x87,	//2083
0xA5,0xB4,0xB4,0xB4,0xB5,0xA5,0x97,0x97,0x97,0x87,0x96,0x96,	//2084
0xB4,0xC3,0xA5,0xB4,0xA5,0xA5,0x97,0x87,0x87,0x88,0x86,0x96,	//2085
0xA4,0xC3,0xA5,0xB4,0xA5,0xA6,0x97,0x87,0x87,0x78,0x87,0x86,	//2086
0xA5,0xC3,0xA5,0xB5,0xA6,0xA6,0x87,0x88,0x88,0x78,0x87,0x87,	//2087
0xA5,0xB4,0xB4,0xB4,0xB5,0xB5,0x97,0x97,0x97,0x87,0x96,0x96,	//2088
0xB4,0xC3,0xA5,0xB4,0xA5,0xA5,0x97,0x87,0x87,0x88,0x86,0x96,	//2089
0xA4,0xC3,0xA5,0xB4,0xA5,0xA6,0x97,0x87,0x87,0x78,0x87,0x96,	//2090
0xA5,0xC3,0xA5,0xB5,0xA6,0xA6,0x87,0x88,0x88,0x78,0x87,0x86,	//2091
0xA5,0xB4,0xB4,0xB4,0xB5,0xB5,0x97,0x97,0x97,0x87,0x96,0x96,	//2092
0xB4,0xC3,0xA5,0xB4,0xA5,0xA5,0x97,0x87,0x87,0x87,0x96,0x96,	//2093
0xA4,0xC3,0xA5,0xB4,0xA5,0xA6,0x97,0x87,0x87,0x78,0x87,0x96,	//2094
0xA5,0xC3,0xA5,0xB5,0xA6,0xA6,0x87,0x88,0x88,0x78,0x87,0x86,	//2095
0xA5,0xB3,0xB4,0xB4,0xB5,0xB5,0x97,0x97,0x97,0x87,0x96,0x96,	//2096
0xB4,0xC3,0xA5,0xB4,0xA5,0xA5,0x97,0x97,0x87,0x87,0x96,0x96,	//2097
0xA4,0xC3,0xA5,0xB4,0xA5,0xA6,0x97,0x87,0x87,0x78,0x87,0x96,	//2098
0xA5,0xC3,0xA5,0xB5,0xA6,0xA6,0x87,0x88,0x88,0x78,0x87,0x86,	//2099	
};

/**
  * @brief  判断是否为闰年
  * @param  Year  公历年(后两位)(2000~2099),范围:0~99
  * @retval 1表示闰年,0表示平年
  */
unsigned char IsLeapYear(unsigned char Year)
{
	unsigned int Temp;
	Temp=2000+Year;
	if( ( (Temp%4)==0) && ((Temp%100)!=0) || ((Temp%400)==0) )
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

/**
  * @brief  判断某年月日(公历)为星期几
  * @param  Year  公历年(后两位)(2000~2099),范围:0~99
  * @param  Month 公历月,范围:1~12
  * @param  Day   公历日,每个月份的日期范围不一样
  * @retval 星期的数据,范围:1~7,对应周一到周日
  */
unsigned char GetWeek(unsigned char Year,unsigned char Month,unsigned char Day)
{
	unsigned char Temp;
	unsigned char m,n;
	m=Year/4;
	n=Year%4;
	if(Month<3 && n!=0){Temp=( Week2000[Month-1] -1 + 5*m+n+1+(Day-1) )%7+1;}
	else{Temp=( Week2000[Month-1] -1 + 5*m+n+(Day-1) )%7+1;}
	return Temp;
}

/**
  * @brief  获取农历某月的天数
  * @param  LunarYear 要查询的年份(农历),范围:0~99
  * @param  LunarMonth 农历的第几个月,范围:1~13
  * @retval 无
  */
unsigned char GetLunarDays(unsigned char LunarYear,unsigned char LunarMonth)
{
	unsigned char Temp;
	switch(LunarMonth)
	{
		case 1:Temp=LunarCalendar[3*LunarYear]&0x08;break;
		case 2:Temp=LunarCalendar[3*LunarYear]&0x04;break;
		case 3:Temp=LunarCalendar[3*LunarYear]&0x02;break;
		case 4:Temp=LunarCalendar[3*LunarYear]&0x01;break;
		case 5:Temp=LunarCalendar[3*LunarYear+1]&0x80;break;
		case 6:Temp=LunarCalendar[3*LunarYear+1]&0x40;break;
		case 7:Temp=LunarCalendar[3*LunarYear+1]&0x20;break;
		case 8:Temp=LunarCalendar[3*LunarYear+1]&0x10;break;
		case 9:Temp=LunarCalendar[3*LunarYear+1]&0x08;break;
		case 10:Temp=LunarCalendar[3*LunarYear+1]&0x04;break;
		case 11:Temp=LunarCalendar[3*LunarYear+1]&0x02;break;
		case 12:Temp=LunarCalendar[3*LunarYear+1]&0x01;break;
		case 13:Temp=LunarCalendar[3*LunarYear+2]&0x80;break;
		default:break;
	}
	if(Temp==0)
	{
		return 29;
	}
	else
	{
		return 30;
	}
}

/**
  * @brief  获取闰月情况
  * @param  LunarYear 要查询的年份(农历),范围:0~99
  * @retval 所闰的月份,范围:0~12,0表示无闰月
  */
unsigned char GetLunarLeapMonth(unsigned char LunarYear)
{
	unsigned char Temp;
	Temp=(LunarCalendar[3*LunarYear]&0xF0)>>4;
	return Temp;
}

/**
  * @brief  公历转农历
  * @param  Year  公历年(后两位)(2000~2099),范围:0~99
  * @param  Month 公历月,范围:1~12
  * @param  Day   公历日,每个月份的日期范围不一样
  * @retval 无
  */
void ConvertSolarToLunar(unsigned char Year,unsigned char Month,unsigned char Day)
{
	unsigned char LunarNewYearMonth,LunarNewYearDay;	//春节公历月日
	unsigned int Temp1,Temp2;	//缓存变量

	LunarNewYearMonth=(LunarCalendar[3*Year+2]&0x60)>>5;	//春节所在公历的月数
	LunarNewYearDay=LunarCalendar[3*Year+2]&0x1F;	//春节所在公历的日数

	LunarLeapMonth=GetLunarLeapMonth(Year);  //获取当年闰月情况

	if(LunarNewYearMonth==1){Temp1=LunarNewYearDay-1;}   //计算春节离当年元旦的天数
	else{Temp1=31+LunarNewYearDay-1;}

	if(IsLeapYear(Year))	//判断此年是否为闰年
	{
		Temp2=AccumulatedDays2[Month-1]+Day-1;
	}
	else
	{
		Temp2=AccumulatedDays1[Month-1]+Day-1;
	}
	
	if(Temp2>=Temp1)	//如果此月日在春节后
	{
		LunarYear=Year;
		LunarMonth=1;
		Temp2=Temp2-Temp1;	//计算此月日离春节的天数
		Temp1=GetLunarDays(Year,LunarMonth);

		while(Temp2>=Temp1)
		{
			LunarMonth++;
			Temp2=Temp2-Temp1;
			Temp1=GetLunarDays(Year,LunarMonth); //计算当月天数
		}
		
		LunarDay=Temp2+1;
	}
	else	//如果此月日在春节前
	{
		if(Year==0)	//如果是农历2000年春节前
		{	//农历表中没有农历1999年冬月和腊月的数据,这里需要特殊处理一下
			LunarYear=99;	//农历1999年
			Temp1=Temp1-Temp2;	//计算此月日离春节的天数
			LunarMonth=12;
			Temp2=29;	//农历1999年12月有29天
			if(Temp1>=Temp2)
			{
				LunarMonth=11;
				Temp1=Temp1-Temp2;
				Temp2=30;	//农历1999年11月有30天
			}
		}
		else
		{
			LunarYear=Year-1;
			Temp1=Temp1-Temp2;	//计算此月日离春节的天数
			
			LunarLeapMonth=GetLunarLeapMonth(Year-1);
			if(LunarLeapMonth==0)   //若当年没有闰月
			{
				LunarMonth=12;
			}
			else
			{
				LunarMonth=13;
			}
			
			Temp2=GetLunarDays(Year-1,LunarMonth);
			while(Temp1>=Temp2)
			{
				LunarMonth--;
				Temp1=Temp1-Temp2;
				Temp2=GetLunarDays(Year-1,LunarMonth);
			}
		}
		LunarDay=Temp2-Temp1+1;
	}
}

/**
  * @brief  判断给定的公历年月日是否是春节
  * @param  Year  公历年(后两位)(2000~2099),范围:0~99
  * @param  Month 公历月,范围:1~12
  * @param  Day   公历日,每个月份的日期范围不一样
  * @retval 1:是春节,0:不是春节
  */
unsigned char IsLunarNewYear(unsigned char Year,unsigned char Month,unsigned char Day)
{
	unsigned char LunarNewYearMonth,LunarNewYearDay;	//春节公历月日
	unsigned char Temp1,Temp2;	//缓存变量

	LunarNewYearMonth=(LunarCalendar[3*Year+2]&0x60)>>5;	//春节所在公历的月数
	LunarNewYearDay=LunarCalendar[3*Year+2]&0x1F;	//春节所在公历的日数

	if(LunarNewYearMonth==1){Temp1=LunarNewYearDay-1;}   //计算春节离当年元旦的天数
	else{Temp1=31+LunarNewYearDay-1;}

	if(Month==1){Temp2=Day-1;}   //计算给定的公历年月日离当年元旦的天数
	else if(Month==2){Temp2=31+Day-1;}
	else{Temp2=0;}

	if(Temp1==Temp2){return 1;}
	else{return 0;}
}

/**
  * @brief  判断是否是二十四节气
  * @param  Year  公历年(后两位)(2000~2099),范围:0~99
  * @param  Month 公历月,范围:1~12
  * @param  Day   公历日,每个月份的日期范围不一样
  * @retval 是不是二十四节气,不是:返回0,是:返回1~24
  * @retval  1:立春, 2:雨水, 3:惊蛰, 4:春分, 5:清明, 6:谷雨  7:立夏, 8:小满, 9:芒种,10:夏至,11:小暑,12:大暑
  * @retval 13:立秋,14:处暑,15:白露,16:秋分,17:寒露,18:霜降 19:立冬,20:小雪,21:大雪,22:冬至,23:小寒,24:大寒
  */
unsigned char GetSolarTerms(unsigned char Year,unsigned char Month,unsigned char Day)
{
	unsigned char Temp=0;
	
	if( 15-(The24SolarTerms[12*Year+(Month-1)]/16)==Day )
	{
		Temp=(23-1+2*(Month-1))%24+1;
	}
	else if( 15+(The24SolarTerms[12*Year+(Month-1)]%16)==Day )
	{
		Temp=(24-1+2*(Month-1))%24+1;
	}
	
	return Temp;
}

/**
  * @brief  获取节气月份(用节气分开成12个月)
  * @param  Year  公历年(后两位)(2000~2099),范围:0~99
  * @param  Month 公历月,范围:1~12
  * @param  Day   公历日,每个月份的日期范围不一样
  * @retval 返回-2~10,-2=子,-1=丑,0=寅,1=卯,2=辰,3=巳,4=午,5=未,6=申,7=酉,8=戌,9=亥,10=子
  * @retval 寅月:立春~惊蛰,卯月:惊蛰~清明,辰月:清明~立夏,巳月:立夏~芒种,午月:芒种~小暑,未月:小暑~立秋
  * @retval 申月:立秋~白露,酉月:白露~寒露,戌月:寒露~立冬,亥月:立冬~大雪,子月:大雪~小寒,丑月:小寒~立春
  */
char GetSolarTermsMonth(unsigned char Year,unsigned char Month,unsigned char Day)
{
	char TempMonth;
	if( Day >= 15-(The24SolarTerms[12*Year+(Month-1)]/16) )
	{
		TempMonth=Month-2;
	}
	else
	{
		TempMonth=Month-3;
	}
	return TempMonth;
}

/**
  * @brief  获取节气月份的天干地支
  * @param  SolarTermsMonth  干支月(用节气分开成12个月),范围:-2~10
  * @retval 干支信息,高四位表示天干,低四位表示地支
  * @retval 高四位范围:0~9(甲乙丙丁戊己庚辛壬癸),低四位范围:0~11(子丑寅卯辰巳午未申酉戌亥)
  */
unsigned char GetMonthGanZhi(char SolarTermsMonth)
{
	unsigned char TempGanZhi=0;
	if(SolarTermsMonth>=0)	//如果是立春或立春之后,则农历年份跟公历年份相同
	{	//((Time[0]-4+10)%10%5)*2+2为寅月天干信息
		TempGanZhi=( (((Time[0]-4+10)%10%5)*2+2+SolarTermsMonth)%10 )*16;
	}
	else	//如果是立春之前,则农历年份=公历年份-1
	{
		TempGanZhi=( (((Time[0]-4+10-1)%10%5)*2+2+SolarTermsMonth+2)%10 )*16;
	}
	TempGanZhi+=(SolarTermsMonth+2)%12;
	return TempGanZhi;
}

/**
  * @brief  计算输入日期到2000年1月1日的天数
  * @param  Year  公历年(后两位)(2000~2099),范围:0~99
  * @param  Month 公历月,范围:1~12
  * @param  Day   公历日,每个月份的日期范围不一样
  * @retval 输入日期到2000年1月1日的天数
  */
unsigned int CalculateDays(unsigned char Year,unsigned char Month,unsigned char Day)
{
	unsigned char i;
	unsigned int TempDays=0;
	for(i=0;i<Year;i++)
	{
		if(IsLeapYear(i)){TempDays+=366;}
		else{TempDays+=365;}
	}
	MonthDays[1]=28;
	if(IsLeapYear(Year)){MonthDays[1]=29;}
	for(i=0;i<Month-1;i++)
	{
		TempDays+=MonthDays[i];
	}
	TempDays+=Day-1;
	return TempDays;
}

5、红外遥控

h文件

#ifndef __IR_H__
#define __IR_H__

//不同的遥控器对应的命令的操作不一样

//#define IR_POWER		0x45
//#define IR_MODE		0x46
//#define IR_MUTE		0x47
//#define IR_START_STOP	0x44
//#define IR_PREVIOUS	0x40
//#define IR_NEXT		0x43
//#define IR_EQ			0x07
//#define IR_VOL_MINUS	0x15
//#define IR_VOL_ADD	0x09
//#define IR_0			0x16
//#define IR_RPT		0x19
//#define IR_USD		0x0D
//#define IR_1			0x0C
//#define IR_2			0x18
//#define IR_3			0x5E
//#define IR_4			0x08
//#define IR_5			0x1C
//#define IR_6			0x5A
//#define IR_7			0x42
//#define IR_8			0x52
//#define IR_9			0x4A

#define IR_CHANNEL_MINUS	0x45
#define IR_CHANNEL			0x46
#define IR_CHANNEL_ADD		0x47
#define IR_PREV				0x44
#define IR_NEXT				0x40
#define IR_PLAYORPAUSE		0x43
#define IR_VOL_MINUS		0x07
#define IR_VOL_ADD			0x15
#define IR_EQ				0x09
#define IR_0				0x16
#define IR_100+				0x19
#define IR_200+				0x0D
#define IR_1				0x0C
#define IR_2				0x18
#define IR_3				0x5E
#define IR_4				0x08
#define IR_5				0x1C
#define IR_6				0x5A
#define IR_7				0x42
#define IR_8				0x52
#define IR_9				0x4A

void IR_Init(void);
unsigned char IR_GetDataFlag(void);
unsigned char IR_GetRepeatFlag(void);
unsigned char IR_GetAddress(void);
unsigned char IR_GetCommand(void);

#endif

c文件

#include <STC32G.H>

//红外接收的DO口接单片机的P32

unsigned long IR_Time;	//用来存储相邻两次下降沿之间的定时器的计数
unsigned char IR_State;	//外部中断0中断函数中的状态
unsigned char IR_Data[4];	//存储接收到的四个字节的数据
unsigned char IR_pData;	//接收数据时的“指针”
unsigned char IR_DataFlag;	//接收到数据的标志
unsigned char IR_RepeatFlag;	//接收到重复(repeat)的标志
unsigned char IR_Address;	//地址
unsigned char IR_Command;	//命令
unsigned char T1Count;	//定时器1的计数,计算溢出多少次

/**
  * 函    数:红外遥控初始化
  * 参    数:无
  * 返 回 值:无
  */
void IR_Init(void)
{
	/*定时器1初始化*/
	AUXR|=0x40;	//定时器时钟1T模式
	TMOD&=0x0F;	//设置定时器模式(低四位不变,高四位清零)
	TMOD|=0x10;	//设置定时器模式(通过高四位设为16位不自动重装)
	TL1=0;	//设置定时初值
	TH1=0;	//设置定时初值
	ET1=1;	//打开定时器1中断允许
	TF1=0;	//清除TF1标志
	TR1=0;	//定时器1不计时
	
	/*外部中断0初始化*/
	IT0=1;	//下降沿触发
	IE0=0;	//外部中断0请求标记
	EX0=1;	//打开中断0允许
	EA=1;	//打开总中断
	PX0=1;	//中断0优先级,1表示高级中断请求,0表示低级中断请求
}

/**
  * 函    数:红外遥控获取收到数据帧标志位
  * 参    数:无
  * 返 回 值:是否收到数据帧,1为收到,0为未收到
  */
unsigned char IR_GetDataFlag(void)
{
	if(IR_DataFlag)
	{
		IR_DataFlag=0;
		return 1;
	}
	return 0;
}

/**
  * 函    数:红外遥控获取收到连发帧标志位
  * 参    数:无
  * 返 回 值:是否收到连发帧,1为收到,0为未收到
  */
unsigned char IR_GetRepeatFlag(void)
{
	if(IR_RepeatFlag)
	{
		IR_RepeatFlag=0;
		return 1;
	}
	return 0;
}

/**
  * 函    数:红外遥控获取收到的地址数据
  * 参    数:无
  * 返 回 值:收到的地址数据
  */
unsigned char IR_GetAddress(void)
{
	return IR_Address;
}

/**
  * 函    数:红外遥控获取收到的命令数据
  * 参    数:无
  * 返 回 值:收到的命令数据
  */
unsigned char IR_GetCommand(void)
{
	return IR_Command;
}

/**
  * 函    数:外部中断0中断函数
  * 参    数:无
  * 返 回 值:无
  * 说    明:下降沿触发执行
  */
void Int0_Routine(void) interrupt 0
{
	if(IR_State==0)	//状态0,空闲状态
	{
		TH1=0;TL1=0;	//定时计数器清0
		TR1=1;	//定时器启动
		T1Count=0;
		IR_State=1;	//置状态为1
	}
	else if(IR_State==1)	//状态1,等待Start信号或Repeat信号
	{
		IR_Time=(65536*T1Count+(TH1<<8)|TL1)/30;	//获取上一次中断到此次中断的时间,单位us,1T@30.0000MHz
		TH1=0;TL1=0;	//定时计数器清0
		T1Count=0;
		//如果计时为13.5ms,则接收到了Start信号
		if(IR_Time>13500-500 && IR_Time<13500+500)
		{
			IR_State=2;	//置状态为2
		}
		//如果计时为11.25ms,则接收到了Repeat信号
		else if(IR_Time>11250-500 && IR_Time<11250+500)
		{
			IR_RepeatFlag=1;	//置收到连发帧标志位为1
			TR1=0;	//定时器停止
			IR_State=0;	//置状态为0
		}
		else	//接收出错
		{
			IR_State=1;	//置状态为1,这里如果置0,则有可能出错后检测不出Start信号或Repeat信号,
						//因为置0后,不是连续检测相邻下降沿的时间间隔
		}
	}
	else if(IR_State==2)	//状态2,接收数据
	{
		IR_Time=(65536*T1Count+(TH1<<8)|TL1)/30;	//获取上一次中断到此次中断的时间,单位us,1T@30.0000MHz
		TH1=0;TL1=0;	//定时计数器清0
		T1Count=0;
		//如果计时为1120us,则接收到了数据0
		if(IR_Time>1120-500 && IR_Time<1120+500)
		{
			IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8));	//数据对应位清0
			IR_pData++;	//数据位置指针自增
		}
		//如果计时为2250us,则接收到了数据1
		else if(IR_Time>2250-500 && IR_Time<2250+500)
		{
			IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8));	//数据对应位置1
			IR_pData++;	//数据位置指针自增
		}
		else	//接收出错
		{
			IR_pData=0;	//数据位置指针清0
			IR_State=1;	//置状态为1
		}
		if(IR_pData>=32)	//如果接收到了32位数据
		{
			IR_pData=0;	//数据位置指针清0
			if((IR_Data[0]==~IR_Data[1]) && (IR_Data[2]==~IR_Data[3]))	//数据验证
			{
				IR_Address=IR_Data[0];	//转存数据
				IR_Command=IR_Data[2];
				IR_DataFlag=1;	//置收到数据帧标志位为1
			}
			TR1=0;	//定时器停止
			IR_State=0;	//置状态为0
		}
	}
}

/**
  * 函    数:定时器1中断函数
  * 参    数:无
  * 返 回 值:无
  * 说    明:计数器溢出会进入此函数
  */
void Timer1_Routine() interrupt 3
{
		T1Count++;
}

/*外部中断0中断函数模板
void Int0_Routine(void) interrupt 0
{
	
}
*/

/*定时器中断函数模板
void Timer1_Routine() interrupt 3	//定时器1中断函数
{
	static unsigned int T1Count;	//定义静态变量
	TL1=0xA0;	//设置定时初值,定时2ms,1T@30.0000MHz
	TH1=0x15;	//设置定时初值,定时2ms,1T@30.0000MHz
	T1Count++;
	if(T1Count>=100)
	{
		T1Count=0;
		
	}
}
*/

6、定时器0

h文件

#ifndef __TIMER0_H__
#define __TIMER0_H__

void Timer0_Init(void);

#endif

c文件

#include <STC32G.H>

/**
  * 函    数:定时器0初始化
  * 参    数:无
  * 返 回 值:无
  */
void Timer0_Init(void)
{
	AUXR|=0x80;	//定时器时钟1T模式
	TMOD&=0xF0;	//设置定时器模式(高四位不变,低四位清零)
	TMOD|=0x01;	//设置定时器模式(通过低四位设为16位不自动重装)
	TL0=0xA0;	//设置定时初值,定时2ms,1T@30.0000MHz
	TH0=0x15;	//设置定时初值,定时2ms,1T@30.0000MHz
	TF0=0;	//清除TF0标志
	TR0=1;	//定时器0开始计时
	ET0=1;	//打开定时器0中断允许
	EA=1;	//打开总中断
	PT0=0;	//当PT0=0时,定时器0为低优先级,当PT0=1时,定时器0为高优先级
}

/*定时器中断函数模板
void Timer0_Routine() interrupt 1	//定时器0中断函数
{
	static unsigned int T0Count;	//定义静态变量
	TL0=0xA0;	//设置定时初值,定时2ms,1T@30.0000MHz
	TH0=0x15;	//设置定时初值,定时2ms,1T@30.0000MHz
	T0Count++;
	if(T0Count>=1000)
	{
		T0Count=0;
		
	}
}
*/

四、主函数

main.c

/*by甘腾胜@20250321
【效果查看/操作演示】B站搜索“甘腾胜”或“gantengsheng”查看
【编程环境】Keil C251
【Keil设置】
(1)CPU Mode:251 native
(2)Memory Model:Xsmall
(3)Code Rom Size:Large
(4)HEX Format:HEX-386
(5)Encoding:UTF-8
【单片机】STC32G12K128
【频率】1T@30.000MHz
【外设】WS2812B彩色点阵屏、DS3231时钟芯片、红外收发模块
【接线】
(1)WS2812B彩屏:数据线接P23
(2)DS3231:SDA接P24,SCL接P25
(3)红外接收模块:数据线接P32
【简单的原理分析】https://blog.csdn.net/gantengsheng/article/details/143581157
【说明】
本代码适用于32X8彩色点阵屏,且数据传输方向要跟下面的相同
(如果不同则需要改一下WS2812B_SetBuffer这个函数的内容)
B0	B0	B0	B0	...
B1	B1	B1	B1	...
B2	B2	B2	B2	...
B3	B3	B3	B3	...
B4	B4	B4	B4	...
B5	B5	B5	B5	...
B6	B6	B6	B6	...
B7	B7	B7	B7	...
数据传输方向:先是第一列,从上到下,再到第二列,从下到上,以此类推,即按之字形传输
【操作说明】
用到的红外遥控上的按键:0、1、2、3		
一、正常走时模式:
0:切换为设置时间模式
1:在滚动显示时间和跳变显示时间之间切换
2:在显示公历日期和显示时分秒之间切换
3:在显示农历日期和显示时分秒之间切换
二、设置时间模式:
0:切换为正常走时模式
1:切换选择要设置的时间
2:减,短按减1,长按连续减小(设置秒的时候,清零)
3:增,短按加1,长按连续增加
【功能】
(1)能够用红外遥控设置年月日时分秒,星期不用设置,由年月日计算得出
(2)农历日期和节气序号均由年月日计算得出
(3)最后一行显示星期,从左到右分别是周日到周六
(4)如果是节气,则第一列以二进制(高位在上)显示节气对应的序号
(5)倒数第二列以二进制(高位在上)显示温度的整数部分
*/

#include <STC32G.H>
#include "Delay.h"
#include "WS2812B.h"
#include "DS3231.h"
#include "IR.H"
#include "Timer0.h"
#include "LunarTime.h"

unsigned char ReadTimeFlag;	//读取时间的标志,1:读取,0:不读取
unsigned char ReadTempFlag;	//读取温度的标志,1:读取,0:不读取
unsigned char FlashFlag;	//设置时间时闪烁的标志,1:不显示,0:显示
unsigned char Mode;	//模式,0:正常走时模式,1:设置时间模式
unsigned char AllowChangeModeFlag=1;	//允许改变模式的标志,1:允许改变,0:不允许改变
unsigned char TimeSelect;	//时间选择变量
unsigned char Command;	//红外接收头接收到的命令
char Offset1,Offset2,Offset3,Offset4,Offset5,Offset6;	//数字向上滚动的偏移量
//时十位,时个位,分十位,分个位,秒十位,秒个位
unsigned char LastHour_10,LastHour_1,LastMinute_10,LastMinute_1,LastSecond_10,LastSecond_1;
unsigned char RollFlag=1;	//时间滚动显示的标志,1:滚动显示,0:不滚动显示
unsigned char T0Count1,T0Count2,T0Count3;	//定时器中断函数的计数变量
unsigned char SolarTerms;	//节气,0:不是节气,1~24:表示第几个节气
float T;	//温度
unsigned char T_Integer;	//温度整数部分

//预设颜色(方便修改),三个字节一组,每一组分别对应红、绿、蓝
unsigned char code MyColor[]={
7,7,0,	//黄色:年月日,年月日之间的点
0,7,7,	//青色:时分秒,冒号
0,7,0,	//绿色:非当天星期
7,0,0,	//红色:当天星期
7,0,7,	//紫色:农历日期
7,0,7,	//紫色:节气、温度
};

/**
  * 函    数:检查时间,防止越界
  * 参    数:无
  * 返 回 值:无
  * 说    明:时间越界判断,防止向时钟芯片写入错误数据
  */
void CheckTime(void)
{
	if(Time[0]<0){Time[0]=99;}	//年越界判断,范围:0~99
	if(Time[0]>99){Time[0]=0;}
	if(Time[1]<1){Time[1]=12;}	//月越界判断,,范围:1~12
	if(Time[1]>12){Time[1]=1;}
	if(Time[1]==1 || Time[1]==3 || Time[1]==5 || Time[1]==7 || 
		Time[1]==8 || Time[1]==10 || Time[1]==12)	//日越界判断,区分大小月、闰年2月、平年2月
	{
		if(Time[2]<1){Time[2]=31;}	//大月
		if(Time[2]>31){Time[2]=1;}
	}
	else if(Time[1]==4 || Time[1]==6 || Time[1]==9 || Time[1]==11)
	{
		if(Time[2]<1){Time[2]=30;}	//小月
		if(Time[2]>30){Time[2]=1;}
	}
	else if(Time[1]==2)
	{
		if(Time[0]%4==0)	//年份范围是2000~2099,2000年是闰年
		{
			if(Time[2]<1){Time[2]=29;}	//闰年2月
			if(Time[2]>29){Time[2]=1;}
		}
		else
		{
			if(Time[2]<1){Time[2]=28;}	//平年2月
			if(Time[2]>28){Time[2]=1;}
		}
	}
	if(Time[3]<0){Time[3]=23;}	//时越界判断,范围:0~23
	if(Time[3]>23){Time[3]=0;}
	if(Time[4]<0){Time[4]=59;}	//分越界判断,范围:0~59
	if(Time[4]>59){Time[4]=0;}	
	if(Time[5]>59){Time[5]=0;}	//秒越界判断,范围:0~59
	if(Time[5]<0){Time[5]=59;}	//秒越界判断,范围:0~59
	Time[6]=GetWeek(Time[0],Time[1],Time[2]);	//根据年月日计算出星期
}

/**
  * @brief	显示时间
  * @param  无
  * @retval 无
  */
void ShowTime(void)
{
	unsigned char i;
	
	if(TimeSelect<=2)	//显示年月日
	{
		WS2812B_ClearColon();
		WS2812B_SetBuffer(10,3,MyColor[0],MyColor[1],MyColor[2]);
		WS2812B_SetBuffer(20,3,MyColor[0],MyColor[1],MyColor[2]);
		if(Mode==1 && FlashFlag && TimeSelect==0)	//年
		{
			WS2812B_ClearNum(1);
			WS2812B_ClearNum(2);
		}
		else
		{
			WS2812B_ShowNum(1,Time[0]/10,10,0,MyColor[0],MyColor[1],MyColor[2]);	//年的十位
			WS2812B_ShowNum(2,Time[0]%10,10,0,MyColor[0],MyColor[1],MyColor[2]);	//年的个位
		}

		if(Mode==1 && FlashFlag && TimeSelect==1)	//月
		{
			WS2812B_ClearNum(3);
			WS2812B_ClearNum(4);
		}
		else
		{
			WS2812B_ShowNum(3,Time[1]/10,10,0,MyColor[0],MyColor[1],MyColor[2]);	//月的十位
			WS2812B_ShowNum(4,Time[1]%10,10,0,MyColor[0],MyColor[1],MyColor[2]);	//月的个位
		}

		if(Mode==1 && FlashFlag && TimeSelect==2)	//日
		{
			WS2812B_ClearNum(5);
			WS2812B_ClearNum(6);
		}
		else
		{
			WS2812B_ShowNum(5,Time[2]/10,10,0,MyColor[0],MyColor[1],MyColor[2]);	//日的十位
			WS2812B_ShowNum(6,Time[2]%10,10,0,MyColor[0],MyColor[1],MyColor[2]);	//日的个位
		}		
	}
	else if(TimeSelect<=5)	//显示时分秒
	{
		WS2812B_SetBuffer(10,3,0,0,0);
		WS2812B_SetBuffer(20,3,0,0,0);
		WS2812B_ShowColon(MyColor[3],MyColor[4],MyColor[5]);
		if(Mode==1 && FlashFlag && TimeSelect==3)	//时
		{
			WS2812B_ClearNum(1);
			WS2812B_ClearNum(2);
		}
		else if(Mode==0 && RollFlag)
		{
			WS2812B_ShowNum(1,Time[3]/10,3,Offset1,MyColor[3],MyColor[4],MyColor[5]);	//时的十位
			if(Time[3]==0)
			{
				WS2812B_ShowNum(2,Time[3]%10,4,Offset2,MyColor[3],MyColor[4],MyColor[5]);	//时的个位
			}
			else
			{
				WS2812B_ShowNum(2,Time[3]%10,10,Offset2,MyColor[3],MyColor[4],MyColor[5]);	//时的个位
			}
			
		}
		else
		{
			WS2812B_ShowNum(1,Time[3]/10, 3,0,MyColor[3],MyColor[4],MyColor[5]);	//时的十位
			WS2812B_ShowNum(2,Time[3]%10,10,0,MyColor[3],MyColor[4],MyColor[5]);	//时的个位
		}
		
		if(Mode==1 && FlashFlag && TimeSelect==4)	//分
		{
			WS2812B_ClearNum(3);
			WS2812B_ClearNum(4);
		}
		else if(Mode==0 && RollFlag)
		{
			WS2812B_ShowNum(3,Time[4]/10, 6,Offset3,MyColor[3],MyColor[4],MyColor[5]);	//分的十位
			WS2812B_ShowNum(4,Time[4]%10,10,Offset4,MyColor[3],MyColor[4],MyColor[5]);	//分的个位
		}
		else
		{
			WS2812B_ShowNum(3,Time[4]/10, 6,0,MyColor[3],MyColor[4],MyColor[5]);	//分的十位
			WS2812B_ShowNum(4,Time[4]%10,10,0,MyColor[3],MyColor[4],MyColor[5]);	//分的个位
		}
		
		if(Mode==1 && FlashFlag && TimeSelect==5)	//秒
		{
			WS2812B_ClearNum(5);
			WS2812B_ClearNum(6);
		}
		else if(Mode==0 && RollFlag)
		{
			WS2812B_ShowNum(5,Time[5]/10, 6,Offset5,MyColor[3],MyColor[4],MyColor[5]);	//秒的十位
			WS2812B_ShowNum(6,Time[5]%10,10,Offset6,MyColor[3],MyColor[4],MyColor[5]);	//秒的个位
		}
		else
		{
			WS2812B_ShowNum(5,Time[5]/10, 6,0,MyColor[3],MyColor[4],MyColor[5]);	//秒的十位
			WS2812B_ShowNum(6,Time[5]%10,10,0,MyColor[3],MyColor[4],MyColor[5]);	//秒的个位
		}

	}
	else	//显示农历日期
	{
		WS2812B_ClearColon();
		WS2812B_SetBuffer(10,3,MyColor[12],MyColor[13],MyColor[14]);
		WS2812B_SetBuffer(20,3,MyColor[12],MyColor[13],MyColor[14]);
		
		WS2812B_ShowNum(1,LunarYear/10, 10,0,MyColor[12],MyColor[13],MyColor[14]);	//农历年的十位
		WS2812B_ShowNum(2,LunarYear%10, 10,0,MyColor[12],MyColor[13],MyColor[14]);	//农历年的个位
		WS2812B_ShowNum(3,LunarMonth/10,10,0,MyColor[12],MyColor[13],MyColor[14]);	//农历月的十位
		WS2812B_ShowNum(4,LunarMonth%10,10,0,MyColor[12],MyColor[13],MyColor[14]);	//农历月的个位
		WS2812B_ShowNum(5,LunarDay/10,  10,0,MyColor[12],MyColor[13],MyColor[14]);	//农历日的十位
		WS2812B_ShowNum(6,LunarDay%10,  10,0,MyColor[12],MyColor[13],MyColor[14]);	//农历日的个位
	}

	WS2812B_ShowWeek(Time[6],MyColor[6],MyColor[7],MyColor[8],MyColor[9],MyColor[10],MyColor[11]);	//星期
	
	if(SolarTerms)	//节气
	{
		for(i=0;i<8;i++)
		{
			if(SolarTerms & (0x80>>i)){WS2812B_SetBuffer(0,i,MyColor[15],MyColor[16],MyColor[17]);}
			else{WS2812B_SetBuffer(0,i,0,0,0);}
		}
	}
	else
	{
		for(i=0;i<8;i++)
		{
			WS2812B_SetBuffer(0,i,0,0,0);
		}
	
	}
	
	for(i=0;i<8;i++)	//温度
	{
		if(T_Integer & (0x80>>i)){WS2812B_SetBuffer(30,i,MyColor[15],MyColor[16],MyColor[17]);}
		else{WS2812B_SetBuffer(30,i,0,0,0);}
	}

	WS2812B_Update();	//更新显示
}

/**
  * 函    数:IO口初始化,全部设置为上拉模式
  * 参    数:无
  * 返 回 值:无
  */
void GPIO_Init(void)
{
	P0M1=0;P0M0=0;
	P1M1=0;P1M0=0;
	P2M1=0;P2M0=0;
	P3M1=0;P3M0=0;
	P4M1=0;P4M0=0;
	P5M1=0;P5M0=0;
	P6M1=0;P6M0=0;
	P7M1=0;P7M0=0;
}

/**
  * 函    数:主函数
  * 参    数:无
  * 返 回 值:无
  * 说    明:主函数是程序执行的起点,负责执行整个程序的主要逻辑‌
  */
void main()
{
    WTST=0;	//不加这行,则延时不准确
	GPIO_Init();	//IO口初始化
	WS2812B_Clear();	//WS2812B清空缓存
	WS2812B_Update();	//WS2812B更新显示
	DS3231_ConvertT();	//转换温度
	Timer0_Init();	//定时器0初始化
	IR_Init();	//外部中断0和定时器1初始化

	DS3231_ReadTime();	//上电先读取一次时间
	TimeSelect=5;	//上电默认显示滚动时钟,而不是跳变时钟
	if(Time[0]<0 || Time[0]>99 || Time[1]<1 || Time[1]>12 || Time[3]<0 ||
		Time[3]>23 || Time[4]<0 || Time[4]>59 || Time[5]>59 || Time[5]<0)	//如果年月日时分秒越界
	{
		CheckTime();	//就对时间进行调整
		DS3231_SetTime();	//再将调整后的时间写入时钟芯片
	}
	LastHour_10=Time[3]/10;
	LastHour_1=Time[3]%10;
	LastMinute_10=Time[4]/10;
	LastMinute_1=Time[4]%10;
	LastSecond_10=Time[5]/10;
	LastSecond_1=Time[5]%10;

	while(1)
	{

		if( IR_GetDataFlag() )	//如果红外遥控获取收到数据帧标志位
		{
			Command=IR_GetCommand();	//获取遥控器命令码

			if(Mode==0)		//如果是正常走时模式
			{
				if(Command==IR_0 && AllowChangeModeFlag)	//如果按了遥控器的“0”键,且允许切换模式
				{
					Mode=1;	//切换为设置时间模式
					AllowChangeModeFlag=0;	//允许切换模式的标志置0
					TimeSelect=0;
				}
				if(Command==IR_1)	//如果按了遥控器的“1”键
				{
					RollFlag=!RollFlag;	//在跳变显示和滚动显示之间切换
				}
				if(Command==IR_2)	//如果按了遥控器的“2”键
				{
					switch(TimeSelect)
					{
						case 0:TimeSelect=5;break;	//显示时分秒
						case 5:TimeSelect=0;break;	//显示年月日
						default:break;
					}
				}
				if(Command==IR_3)	//如果按了遥控器的“3”键
				{
					switch(TimeSelect)
					{
						case 5:TimeSelect=6;break;	//显示时分秒
						case 6:TimeSelect=5;break;	//显示年月日
						default:break;
					}
				}
			}

			if(Mode==1)		//如果是设置时间模式
			{
				if(Command==IR_0 && AllowChangeModeFlag)	//如果按了遥控器的“0”键,且允许切换模式
				{
					Mode=0;	//切换为正常走时模式
					AllowChangeModeFlag=0;	//允许切换模式的标志置0
					TimeSelect=5;	//设置完时间后默认显示时分秒
				}
				if(Command==IR_1)	//如果按了遥控器的“1”键
				{
					TimeSelect++;	//切换选择
					TimeSelect%=6;	//越界清零
				}
				if(Command==IR_2)	//如果按了遥控器的“2”键
				{
					switch(TimeSelect)	//时间数值减小
					{
						case 0:Time[0]--;break;
						case 1:Time[1]--;break;
						case 2:Time[2]--;break;
						case 3:Time[3]--;break;
						case 4:Time[4]--;break;
						case 5:Time[5]=0;break;	//秒清零
						default:break;
					}
					CheckTime();	//检查时间值是否越界
					DS3231_SetTime();	//将修改后的时间写入时钟芯片
					T0Count2=0;	//设置时间时暂不闪烁
					FlashFlag=0;	//设置时间时暂不闪烁
					ShowTime();	//更新显示
					Delay(333);	//防止短按时接收到连发信号
					IR_GetRepeatFlag();	//防止短按时接收到连发信号
				}
				if(Command==IR_3)	//如果按了遥控器的“2”键
				{
					switch(TimeSelect)	//时间数值增加
					{
						case 0:Time[0]++;break;
						case 1:Time[1]++;break;
						case 2:Time[2]++;break;
						case 3:Time[3]++;break;
						case 4:Time[4]++;break;
						case 5:Time[5]++;break;	//秒自增
					}
					CheckTime();	//检查时间值是否越界
					DS3231_SetTime();	//将修改后的时间写入时钟芯片
					T0Count2=0;
					FlashFlag=0;
					ShowTime();	//更新显示
					Delay(333);	//防止短按时接收到连发信号
					IR_GetRepeatFlag();	//防止短按时接收到连发信号
				}
			}
			
			AllowChangeModeFlag=1;
		}

		if( IR_GetRepeatFlag() )	//如果红外遥控获取收到连发帧标志位
		{
			Command=IR_GetCommand();	//获取遥控器命令码
			
			if(Mode==1)
			{
				if(Command==IR_2)	//如果按了遥控器的“2”键
				{
					switch(TimeSelect)	//时间数值减小
					{
						case 0:Time[0]--;break;
						case 1:Time[1]--;break;
						case 2:Time[2]--;break;
						case 3:Time[3]--;break;
						case 4:Time[4]--;break;
						case 5:Time[5]=0;break;	//秒清零
						default:break;
					}
					CheckTime();	//检查时间值是否越界
					DS3231_SetTime();	//将修改后的时间写入时钟芯片
					T0Count2=0;
					FlashFlag=0;
				}
				if(Command==IR_3)	//如果按了遥控器的“2”键
				{
					switch(TimeSelect)	//时间数值增加
					{
						case 0:Time[0]++;break;
						case 1:Time[1]++;break;
						case 2:Time[2]++;break;
						case 3:Time[3]++;break;
						case 4:Time[4]++;break;
						case 5:Time[5]++;break;	//秒自增
					}
					CheckTime();	//检查时间值是否越界
					DS3231_SetTime();	//将修改后的时间写入时钟芯片
					T0Count2=0;
					FlashFlag=0;
				}
			}				
		}

		if(Mode==0 || Mode==1)
		{
			if(ReadTimeFlag)
			{
				ReadTimeFlag=0;	//读取时间标志清零

				DS3231_ReadTime();	//读取时间
				
				ConvertSolarToLunar(Time[0],Time[1],Time[2]);	//根据公历日期计算出农历日期
				SolarTerms=GetSolarTerms(Time[0],Time[1],Time[2]);	//判断是否是节气

				/*如果时分秒的六个数字跟上次不一样,则进行滚动显示*/
				if(LastHour_10 != Time[3]/10){Offset1=-7;T0Count3=0;}
				if(LastHour_1 != Time[3]%10){Offset2=-7;T0Count3=0;}
				if(LastMinute_10 != Time[4]/10){Offset3=-7;T0Count3=0;}
				if(LastMinute_1 != Time[4]%10){Offset4=-7;T0Count3=0;}
				if(LastSecond_10 != Time[5]/10){Offset5=-7;T0Count3=0;}
				if(LastSecond_1 != Time[5]%10){Offset6=-7;T0Count3=0;}					

				/*将时分秒的六个数字跟新到“Last”变量中*/
				LastHour_10=Time[3]/10;
				LastHour_1=Time[3]%10;
				LastMinute_10=Time[4]/10;
				LastMinute_1=Time[4]%10;
				LastSecond_10=Time[5]/10;
				LastSecond_1=Time[5]%10;

				ShowTime();	//显示时间
			}
			if(ReadTempFlag)	//如果读取温度的标志为1
			{	//在ShowTime函数中显示温度
				ReadTempFlag=0;	//读取温度的标志清零
				if(DS3231_CheckBusy()==0)
				{
					T=DS3231_ReadT();
					DS3231_ConvertT();
					T_Integer=(unsigned char)T;
				}					
			}
		}

	}
}

void Timer0_Routine() interrupt 1	//定时器0中断函数
{
	TL0=0xA0;	//设置定时初值,定时2ms,1T@30.0000MHz
	TH0=0x15;	//设置定时初值,定时2ms,1T@30.0000MHz
	T0Count1++;
	T0Count2++;
	T0Count3++;
	if(T0Count1>=50)	//每隔100ms读取一次时间
	{
		T0Count1=0;
		ReadTimeFlag=1;
	}
	if(T0Count2>=250)	//每隔500ms将闪烁标志FlashFlag置反和读取一次温度
	{
		T0Count2=0;
		FlashFlag=!FlashFlag;
		ReadTempFlag=1;
	}
	if(T0Count3>=40)	//每隔80ms滚动一个像素
	{
		T0Count3=0;
		Offset1++;
		Offset2++;
		Offset3++;
		Offset4++;
		Offset5++;
		Offset6++;
		if(Offset1>0){Offset1=0;}
		if(Offset2>0){Offset2=0;}
		if(Offset3>0){Offset3=0;}
		if(Offset4>0){Offset4=0;}
		if(Offset5>0){Offset5=0;}
		if(Offset6>0){Offset6=0;}
	}
}

总结

自己对这个作品还是比较满意的,直接贴在客厅那里,晚上可以当一个夜灯使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值