系列文章目录
前言
网传的农历数据有误(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;}
}
}
总结
自己对这个作品还是比较满意的,直接贴在客厅那里,晚上可以当一个夜灯使用。