前言
网上看见多篇OLED12864+DS3231的万年历制作文章,发觉有改善空间,笔者也尝试制造一个。
硬件规格详述:
- MCU 主控:STC32G12K128(具备高算力、丰富周边与大容量 Flash)。
- 即时时钟( RTC ) - DS3231 模组 :
- 采用 I²C 介面通讯,内建高精确度 TCXO (温度补偿晶体振荡器),实测平均月误差 < 5 秒。
- 内建可编程 Aging Offset 老化微调暂存器,可手动修正日误差(最大幅度约 \\pm 1 秒/天)。
- 具备双电源自动切换设计,主电源(+5V)移除后自动切换至 CR2032 钮扣电池 供电进入极低功耗模式,可维持计时运行数年。
- 显示屏 - 0.96 吋 OLED 12864 显示板 :
- 基于 SSD1306 驱动晶片,采用 IIC 总线 传输。
- 解像度 128 \\times 64 像素,自发光、无背光盲区、对比度极高,非常适合用作桌面型万年历的资讯显示载体。
- 4x4 矩阵键盘
0.96"OLED 12864 显示屏
· 解析度与尺寸:采用 0.96 吋、解析度为 128 \\times 64 像素的 OLED 显示板。
· 驱动 IC 与通讯介面 :通常内建 SSD1306 驱动晶片,支援 IIC ( I²C )介面,仅需占用单片机的 SCL 与 SDA 两条信号线即可完成通讯,极大地节省了 STC32G 的 GPIO 资源。
· 自发光与对比度特性:OLED 具备自发光特性(无需背光)、高对比度、广视角以及极快的反应时间,在室内或夜间环境下显示清晰且功耗低。
· 供电电压:支援 3.3V 至 5V 宽电压输入(模组通常内建 3.3V 稳压电路),方便与 STC32G 单片机直接对接。
DS3231 RTC 模块
· 高精度核心( TCXO ):内建精准的温度补偿晶体振荡器(TCXO)。传统石英晶振容易受环境温度影响而产生频率漂移,而 DS3231 内部整合了温度感测器,能自动微调震荡频率,使晶振频率在工作温度范围内保持高度稳定。
· 时间误差与老化微调 :平均一个月误差少于 5 秒。并具备 Aging Offset (老化补偿暂存器) 功能,允许使用者透过程式手动微调石英晶体的老化漂移,一天可加快或减慢最多 1 秒钟,达到高精度校准。
· I²C 总线与暂存器:采用高速(400kHz)I²C 串列介面与 STC32G 通讯,内部提供秒、分、时、星期、日、月、年等实时时历暂存器,并自动处理大小月及闰年天数补偿。
· 双电源与自动切换:模组支援主电源与备用电池(CR2032 钮扣电池)自动切换功能。当 +5V 主电源断电时,RTC 自动切换至电池供电,确保内部时钟持续运行且耗电量极低(仅微安级别),确保重新上电后时间不遗失。
· 内建温度感测器:DS3231 本身附带数位温度输出功能(虽然原作者在此专案中取消了温度显示改为显示节气,但硬体层面具备此扩充潜力)。
4x4 矩阵键盘
· 硬件结构与节省引脚:4x4 矩阵键盘透过 4 条行线(Row)和 4 条列线(Column)交叉组成,总共仅需 8 个 MCU 的 GPIO 引脚,即可支援高达 16 个按键的输入,大幅节省单片机的引脚资源。
· 连接与埠口配置:配合 STC32G 单片机使用时,需将行线与列线分别接到相应的 GPIO 埠。编程时通常需要将埠口设置为准双向口(Quasi-bidirectional)并启用内部弱上拉,以确保能正确检测高低电平变化。
· 按键扫描与防弹跳( Debounce ):程式采用行扫描或行列反转扫描法来识别被按下的键。为了避免机械接点弹跳造成误触发,软体层面通常会加入延时消抖处理,或者采用非阻塞式的按键状态机。
· 在此万年历中的实际应用:
- 校正模式触发:如文中提到,先按 # 键启动秒数或时间调校模式,使秒数开始闪烁。
- 数值调整与归零:在校正模式的 30 秒有效视窗内,按下 * 键可将秒数快速归零,方便精准对时;其他按键则可用于切换或增减年、月、日、时、分等参数。
- 可以拆除:如果您已经完成了时间的校正、设定,且日常只需观看万年历显示,只要拔除矩阵键盘与单片机之间的连接线,硬体上完全可以正常运作。OLED 显示屏与 DS3231 时钟模组会继续正常显示时间和农历。
制成品:

MCU接口连线图:

软件:
- main.c
- DS3231.h
- DS3231.c
- Iic.h
- Iic.c
- lunar.h
- lunar.c
- oled.h
- oled.c
- oledfont.h
特式:
- DS3231 RTC 模块极之准确,内置了 温度补偿晶体振荡器( TCXO ) **,**平均一个月差误少于5秒。此外,DS3231 附有Aging Offset 调校工作,可以加快或减慢电子钟运行,一天可加快或减慢最多1秒钟。
- DS3231 十分省电, 当拔除+5V 电源之后,OLED 熄灭,DS3231 处于省电模式 内置一枚CR2032 钮扣电池可以维持DS3231 运行数年之久, 当重新接入+5V 电源之后便可以回复正常工作。
- 此程式设有校正模式,可以调校电子钟的年、月、日、时、分、秒。秒设有归零按钮,先按'#' 键,秒数开始闪烁,启动秒数调校模式,在30秒钟内,按"*" 秒数归零,方便根据天文台、收音机报时钟对时。
- 此程式另一特色,此万年历可一起显示西历、星期、农历及节气。故此,此程式亦可以变成一个中西历对照工具,方便个别人士将西历转换为农历,例如1901年1月1日星期二是农历十一月十一, 2034年1月5日星期四是农历闰十一月十五当天更是小寒。
- 本程式农历数据已根据以下两书
《一百六十年历表 1901-2060年》 (刘宝琳,刘婷婷编, 科学出版社, 2006)
《百年历表及日月食 2001-2100》 (刘宝琳,刘婷婷编, 科学普及出版社, 2008)
及利用香港天文台《公历与农历日期对照表》校对过, 精确无误。
本程式是根据
《OLED12864+DS3231的万年历制作,带农历,大字体(单片机源码+实物)》
改动而来,作出以下修改:
- 农历十一月显示"冬"月,十二月显示"腊"月,与传统历书不同, 今改回十一月、十二月。
- 取消温度显示,改为显示节气。
- 原程式不能显示"闰"月,今改正。
- 修正农历程式中不能处理"闰十二月"的问题。(当然此问题在未来1000年都不会出现。)
- 扩充农历的上下限,原本只限 2000-2099年,今扩充至 1901-2100年。
- 修正原农历数据中的三个错误,分别是2057年、2089年及 2097年。
如何取字模
因为需要显示节气, 需要加入节气的 OLED 中文字字模, 执行PCtoLCD2002,选取以下选项及输入以下节气名称:
小寒大寒立春雨水惊蛰春分清明谷雨立夏小满芒种夏至
小暑大暑立秋处暑白露秋分寒露霜降立冬小雪大雪冬至
虽然有些字重复了,但为了方便C程序的编写,各中文字的排列不可以改变。

取字方式 -- 共阴码, 列行式, 16x16, 逆向输出(低位在前), C51 格式

源码获取:
本项目的完整源代码(包括 main.c、DS3231.h/c、Iic.h/c、lunar.h/c、oled.h/c、oledfont.h 等文件)已托管在开源平台。您可以通过以下方式获取:
附件下载:本文末尾或相关论坛帖子通常提供源码压缩包,解压后即可获得全部文件。
编译与烧录:源码使用 Keil C51 或 STC-ISP 工具链编译,针对 STC32G12K128 单片机。请确保已正确配置头文件路径与芯片型号,编译生成 HEX 文件后,通过 STC-ISP 软件烧录至单片机。
Keil uVision编译设定
C源码文件:

Keil uVision Project Options:
Memory Model: Small: data vars, far const, ptr-4。

使用 STC-ISP 软件烧录 HEX 文件
MCU 頻率: 24.000 MHz
使用方法:
A键:调校西历年 份,年份末两位数开始闪烁
B键:调校西历月 份,月份两位数开始闪烁
C键:调校西历日 期, 日期两位数开始闪烁
D 键:结束
2 键 :+ 1
8 键 :- 1
4 键:移至上一项 (年、月、日、时、分循环闪烁)
6键:移至下一项 (年、月、日、时、分循环闪烁)
键:启动秒数归零 模式, 秒数开始闪烁
* 键:秒数归零 (必须在秒数开始闪烁30秒内执行, 否则取消)

第三行最多可显示8个中文字,以上是最长可显示的农历数据。
其中main.c 在调校过程中,按 2 键 、8键数字+ 1、-1后,日期有可能出现不应该出现的日期,例如 2026年2月不会有29日,所以每次调校完之后都会进行日期校验工作:
main.c
cpp
/* 找出西历月份内最大日数 */
unsigned char month_daymax (unsigned short year, unsigned char month)
{
unsigned char max;
// 'static' 只会初始化一次,其数值会在函数结束后继续保留
static const unsigned char month_day[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (month > 12 || month < 1) return 0;
max = month_day[month - 1];
if (month == 2)
{
// Check for leap year
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
{
max = 29;
}
}
return max;
}
/* 调校时间/日期 */
void Set(unsigned char sel,unsigned char selby)
{
unsigned int full_year;
unsigned char address;
int item; // 可以是负数
unsigned char max,min;
if(sel==1) {address=DS3231_HOUR; max=23;min=0;} //小时
if(sel==2) {address=DS3231_MINUTE; max=59;min=0;} //分钟
if(sel==4) {address=DS3231_YEAR; max=99;min=0;} //年
if(sel==5) {address=DS3231_MONTH; max=12;min=1;} //月
if(sel==6) {address=DS3231_DAY;
max=month_daymax(year+century, month); min=1;}//日
item= read_random(address); // item 是 hex 格式
if (selby==0)
{
item++;
if (item > max)
{
item = min;
if (sel == 4) century += 100;
}
if (sel == 4 && century + item > 2100) item = min; // 年上限是 2100
}
else
{
item--;
if (item < min)
{
item = max;
if (sel == 4) century -= 100;
}
if (sel == 4 && century + item < 1901) item = 1; // 年下限是 1901
}
ModifyTime(address, item); // item 是 hex 格式
if (sel == 4 || sel == 5) // 已调校年、月, 校验日期是否合理
{
unsigned char new_year = year; // 改变过的年
unsigned char new_month = month; // 改变过的月
unsigned char day_max = 31; // 最大日期
unsigned char current_day = read_random(DS3231_DAY);
if (sel == 4) new_year = item; // DS3231內的年
if (sel == 5) new_month = item; // DS3231內的月
day_max = month_daymax(new_year+century, new_month);
// 如果日期超出合理范围,立即修正至准许的最大值
if (current_day > day_max)
{
ModifyTime(DS3231_DAY, day_max);
}
}
}
lunar.h 农历数据如下:
cpp
#ifndef __lunar_H__
#define __lunar_H__
#define uchar unsigned char /*宏定义*/
#define uint unsigned int
#define uint32_t unsigned long
int LunarCalendar(int year,int month,int day);
int CheckSolarTerm(int year, int month, int day);
/* Declare them here with extern so main.c can see them */
extern unsigned char lunar_month;
extern unsigned char lunar_day;
/*
* 用来表示1900年到2100年间农历年份的相关信息,共24位元的16进制表示,其中:
* 1. 前4位表示该年闰哪个月;
* 2. 5-17位表示农历年份13个月的大小月分布, 如果没有闰月, 第13月为0,0表示小,1表示大;
* 3. 最后7位表示农历年首(正月初一)对应的公历日期。
*
* 以2014年的数据0x955ABF为例: 二進制 1001 0101 0101 1010 1011 1111
* 闰九月 正月小, 二月大, ..., 九月大, 闰九月小, 十月大, 十一月小, 十二月大 农历正月初一对应公历1月31号
*/
static const uint32_t code LunarCalendar_Info[201]={
0x84B6BF,/*1900*/
0x04AE53,0x0A5748,0x5526BD,0x0D2650,0x0D9544,0x46AAB9,0x056A4D,0x09AD42,0x24AEB6,0x04AE4A,/*1901-1910*/
0x6A4DBE,0x0A4D52,0x0D2546,0x5D52BA,0x0B544E,0x0D6A43,0x296D37,0x095B4B,0x749BC1,0x049754,/*1911-1920*/
0x0A4B48,0x5B25BC,0x06A550,0x06D445,0x4ADAB8,0x02B64D,0x095742,0x2497B7,0x04974A,0x664B3E,/*1921-1930*/
0x0D4A51,0x0EA546,0x56D4BA,0x05AD4E,0x02B644,0x393738,0x092E4B,0x7C96BF,0x0C9553,0x0D4A48,/*1931-1940*/
0x6DA53B,0x0B554F,0x056A45,0x4AADB9,0x025D4D,0x092D42,0x2C95B6,0x0A954A,0x7B4ABD,0x06CA51,/*1941-1950*/
0x0B5546,0x555ABB,0x04DA4E,0x0A5B43,0x352BB8,0x052B4C,0x8A953F,0x0E9552,0x06AA48,0x6AD53C,/*1951-1960*/
0x0AB54F,0x04B645,0x4A5739,0x0A574D,0x052642,0x3E9335,0x0D9549,0x75AABE,0x056A51,0x096D46,/*1961-1970*/
0x54AEBB,0x04AD4F,0x0A4D43,0x4D26B7,0x0D254B,0x8D52BF,0x0B5452,0x0B6A47,0x696D3C,0x095B50,/*1971-1980*/
0x049B45,0x4A4BB9,0x0A4B4D,0xAB25C2,0x06A554,0x06D449,0x6ADA3D,0x0AB651,0x095746,0x5497BB,/*1981-1990*/
0x04974F,0x064B44,0x36A537,0x0EA54A,0x86B2BF,0x05AC53,0x0AB647,0x5936BC,0x092E50,0x0C9645,/*1991-2000*/
0x4D4AB8,0x0D4A4C,0x0DA541,0x25AAB6,0x056A49,0x7AADBD,0x025D52,0x092D47,0x5C95BA,0x0A954E,/*2001-2010*/
0x0B4A43,0x4B5537,0x0AD54A,0x955ABF,0x04BA53,0x0A5B48,0x652BBC,0x052B50,0x0A9345,0x474AB9,/*2011-2020*/
0x06AA4C,0x0AD541,0x24DAB6,0x04B64A,0x6A573D,0x0A4E51,0x0D2646,0x5E933A,0x0D534D,0x05AA43,/*2021-2030*/
0x36B537,0x096D4B,0xB4AEBF,0x04AD53,0x0A4D48,0x6D25BC,0x0D254F,0x0D5244,0x5DAA38,0x0B5A4C,/*2031-2040*/
0x056D41,0x24ADB6,0x049B4A,0x7A4BBE,0x0A4B51,0x0AA546,0x5B52BA,0x06D24E,0x0ADA42,0x355B37,/*2041-2050*/
0x09374B,0x8497C1,0x049753,0x064B48,0x66A53C,0x0EA54F,0x06AA44,0x4AB638,0x0AAE4C,0x092E42,/*2051-2060*/
0x3C9735,0x0C9649,0x7D4ABD,0x0D4A51,0x0DA545,0x55AABA,0x056A4E,0x0A6D43,0x452EB7,0x052D4B,/*2061-2070*/
0x8A95BF,0x0A9553,0x0B4A47,0x6B553B,0x0AD54F,0x055A45,0x4A5D38,0x0A5B4C,0x052B42,0x3A93B6,/*2071-2080*/
0x069349,0x7729BD,0x06AA51,0x0AD546,0x54DABA,0x04B64E,0x0A5743,0x452738,0x0D164A,0x8E933E,/*2081-2090*/
0x0D5252,0x0DAA47,0x66B53B,0x056D4F,0x04AE45,0x4A4EB9,0x0A2D4C,0x0D1541,0x2D92B5,0x0D5249 /*2091-2100*/
};
/*
西历1月至12月节气名称,每个月两个, 共24个节气
*/
static const char code Solar_Term[] = {"*", "小寒","大寒","立春","雨水","惊蛰","春分","清明","谷雨",
"立夏","小满","芒种","夏至","小暑","大暑","立秋","处暑",
"白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至"};
//节气基本日期
static const unsigned int code Solar_Basic[] = {4,19,3,18,4,19,4,19,4,20,4,20,6,22,6,22,6,22,7,22,6,21,6,21};
/*
1900年到2100年节气日期偏移量
每个月占4位元, 由西历1月至12月, 1年共用6个位元组
首2位元代表1月上半月节气偏移量, 其后2位元代表下半月节气偏移量
例如:1900年 首个位元组0x95 = 1001 0101 表示 1月4日 + 二进制 10 = 1月6日小寒, 1月19日 + 二进制 01 = 1月20日大寒
2月3日 + 二进制 01 = 2月4日立春, 2月18日 + 二进制 01 = 2月19日雨水
最后位元组0xA5 = 1010 0101 表示 11月6日 + 二进制 10 =11月8日立冬, 11月21日 + 二进制 10 =11月23日小雪
12月6日 + 二进制 01 =12月7日大雪, 12月21日 + 二进制 01 =12月22日冬至
*/
static const unsigned int code Solar_Offset[][6] = {
0x95,0xA5,0x9A,0x59,0x9A,0xA5,/*1900*/
0xA5,0xA6,0xAA,0x9A,0xAA,0xA9,/*1901*/
0xA9,0xAA,0xAE,0xAA,0xAA,0xAA,/*1902*/
0xAA,0xFA,0xEE,0xAE,0xEA,0xAA,/*1903*/
0xEA,0xA5,0x9A,0x59,0x9A,0xA5,/*1904*/
0xA5,0xA6,0xAA,0x9A,0xAA,0xA9,/*1905*/
0xA9,0xAA,0xAA,0xAA,0xAA,0xAA,/*1906*/
0xAA,0xFA,0xEE,0xAE,0xEA,0xAA,/*1907*/
0xEA,0xA5,0x9A,0x59,0x9A,0xA5,/*1908*/
0xA5,0xA6,0xAA,0x9A,0xAA,0xA9,/*1909*/
0xA9,0xAA,0xAA,0xAA,0xAA,0xAA,/*1910*/
0xAA,0xFA,0xEE,0xAE,0xEA,0xAA,/*1911*/
0xEA,0xA5,0x9A,0x59,0x9A,0xA5,/*1912*/
0x95,0xA6,0xAA,0x9A,0xAA,0xA9,/*1913*/
0xA5,0xA6,0xAA,0xAA,0xAA,0xAA,/*1914*/
0xAA,0xBA,0xAE,0xAA,0xEA,0xAA,/*1915*/
0xAA,0xA5,0x9A,0x59,0x96,0x95,/*1916*/
0x95,0xA6,0x9A,0x9A,0x9A,0xA5,/*1917*/
0xA5,0xA6,0xAA,0xAA,0xAA,0xA9,/*1918*/
0xAA,0xBA,0xAE,0xAA,0xEA,0xAA,/*1919*/
0xAA,0xA5,0x9A,0x59,0x96,0x95,/*1920*/
0x95,0xA5,0x9A,0x9A,0x9A,0xA5,/*1921*/
0xA5,0xA6,0xAA,0xAA,0xAA,0xA9,/*1922*/
0xA9,0xAA,0xAE,0xAA,0xEA,0xAA,/*1923*/
0xAA,0xA5,0x9A,0x59,0x96,0x95,/*1924*/
0x95,0xA5,0x9A,0x9A,0x9A,0xA5,/*1925*/
0xA5,0xA6,0xAA,0x9A,0xAA,0xA9,/*1926*/
0xA9,0xAA,0xAE,0xAA,0xAA,0xAA,/*1927*/
0xAA,0xA5,0x99,0x59,0x95,0x55,/*1928*/
0x95,0xA5,0x9A,0x59,0x9A,0xA5,/*1929*/
0xA5,0xA6,0xAA,0x9A,0xAA,0xA9,/*1930*/
0xA9,0xAA,0xAE,0xAA,0xAA,0xAA,/*1931*/
0xAA,0xA5,0x99,0x59,0x95,0x55,/*1932*/
0x95,0xA5,0x9A,0x59,0x9A,0xA5,/*1933*/
0xA5,0xA6,0xAA,0x9A,0xAA,0xA9,/*1934*/
0xA9,0xAA,0xAA,0xAA,0xAA,0xAA,/*1935*/
0xAA,0xA5,0x99,0x59,0x95,0x55,/*1936*/
0x95,0xA5,0x9A,0x59,0x9A,0xA5,/*1937*/
0xA5,0xA6,0xAA,0x9A,0xAA,0xA9,/*1938*/
0xA9,0xAA,0xAA,0xAA,0xAA,0xAA,/*1939*/
0xAA,0xA5,0x99,0x59,0x95,0x55,/*1940*/
0x95,0xA5,0x9A,0x59,0x9A,0xA5,/*1941*/
0xA5,0xA6,0xAA,0x9A,0xAA,0xA9,/*1942*/
0xA9,0xAA,0xAA,0xAA,0xAA,0xAA,/*1943*/
0xAA,0xA5,0x59,0x59,0x95,0x55,/*1944*/
0x95,0xA5,0x9A,0x59,0x96,0x95,/*1945*/
0x95,0xA6,0xAA,0x9A,0x9A,0xA9,/*1946*/
0xA5,0xA6,0xAA,0xAA,0xAA,0xAA,/*1947*/
0xAA,0x65,0x59,0x55,0x95,0x55,/*1948*/
0x55,0xA5,0x9A,0x59,0x96,0x95,/*1949*/
0x95,0xA5,0x9A,0x9A,0x9A,0xA9,/*1950*/
0xA5,0xA6,0xAA,0xAA,0xAA,0xAA,/*1951*/
0xAA,0x65,0x59,0x55,0x95,0x55,/*1952*/
0x55,0xA5,0x9A,0x59,0x96,0x95,/*1953*/
0x95,0xA5,0x9A,0x9A,0x9A,0xA5,/*1954*/
0xA5,0xA6,0xAA,0x9A,0xAA,0xA9,/*1955*/
0xAA,0x55,0x59,0x55,0x95,0x55,/*1956*/
0x55,0xA5,0x9A,0x59,0x96,0x95,/*1957*/
0x95,0xA5,0x9A,0x59,0x9A,0xA5,/*1958*/
0xA5,0xA6,0xAA,0x9A,0xAA,0xA9,/*1959*/
0xA9,0x55,0x59,0x55,0x55,0x55,/*1960*/
0x55,0xA5,0x99,0x59,0x95,0x55,/*1961*/
0x95,0xA5,0x9A,0x59,0x9A,0xA5,/*1962*/
0xA5,0xA6,0xAA,0x9A,0xAA,0xA9,/*1963*/
0xA9,0x55,0x59,0x55,0x55,0x55,/*1964*/
0x55,0xA5,0x99,0x59,0x95,0x55,/*1965*/
0x95,0xA5,0x9A,0x59,0x9A,0xA5,/*1966*/
0xA5,0xA6,0xAA,0x9A,0xAA,0xA9,/*1967*/
0xA9,0x55,0x55,0x55,0x55,0x55,/*1968*/
0x55,0xA5,0x99,0x59,0x95,0x55,/*1969*/
0x95,0xA5,0x9A,0x59,0x9A,0xA5,/*1970*/
0xA5,0xA6,0xAA,0x9A,0xAA,0xA9,/*1971*/
0xA9,0x55,0x55,0x55,0x55,0x55,/*1972*/
0x55,0xA5,0x59,0x59,0x95,0x55,/*1973*/
0x95,0xA5,0x9A,0x59,0x9A,0xA5,/*1974*/
0xA5,0xA6,0xAA,0x9A,0x9A,0xA9,/*1975*/
0xA9,0x51,0x55,0x55,0x55,0x55,/*1976*/
0x55,0xA5,0x59,0x55,0x95,0x55,/*1977*/
0x95,0xA5,0x9A,0x59,0x96,0xA5,/*1978*/
0xA5,0xA6,0x9A,0x9A,0x9A,0xA9,/*1979*/
0xA9,0x51,0x55,0x55,0x55,0x55,/*1980*/
0x55,0xA5,0x59,0x55,0x95,0x55,/*1981*/
0x95,0xA5,0x9A,0x59,0x96,0x95,/*1982*/
0x95,0xA5,0x9A,0x9A,0x9A,0xA9,/*1983*/
0xA5,0x51,0x55,0x45,0x55,0x55,/*1984*/
0x55,0x65,0x59,0x55,0x95,0x55,/*1985*/
0x55,0xA5,0x9A,0x59,0x96,0x95,/*1986*/
0x95,0xA5,0x9A,0x5A,0x9A,0xA5,/*1987*/
0xA5,0x51,0x55,0x45,0x55,0x54,/*1988*/
0x55,0x55,0x59,0x55,0x55,0x55,/*1989*/
0x55,0xA5,0x99,0x59,0x96,0x95,/*1990*/
0x95,0xA5,0x9A,0x59,0x9A,0xA5,/*1991*/
0xA5,0x51,0x55,0x45,0x55,0x54,/*1992*/
0x54,0x55,0x59,0x55,0x55,0x55,/*1993*/
0x55,0xA5,0x99,0x59,0x95,0x55,/*1994*/
0x95,0xA5,0x9A,0x59,0x9A,0xA5,/*1995*/
0xA5,0x51,0x55,0x45,0x55,0x54,/*1996*/
0x54,0x55,0x55,0x55,0x55,0x55,/*1997*/
0x55,0xA5,0x99,0x59,0x95,0x55,/*1998*/
0x95,0xA5,0x9A,0x59,0x9A,0xA5,/*1999*/
0xA5,0x51,0x55,0x45,0x55,0x54,/*2000*/
0x54,0x55,0x55,0x55,0x55,0x55,/*2001*/
0x55,0xA5,0x99,0x59,0x95,0x55,/*2002*/
0x95,0xA5,0x9A,0x59,0x9A,0xA5,/*2003*/
0xA5,0x51,0x55,0x45,0x55,0x54,/*2004*/
0x54,0x55,0x55,0x55,0x55,0x55,/*2005*/
0x55,0xA5,0x59,0x55,0x95,0x55,/*2006*/
0x95,0xA5,0x9A,0x59,0x9A,0xA5,/*2007*/
0xA5,0x51,0x55,0x45,0x45,0x54,/*2008*/
0x54,0x51,0x55,0x55,0x55,0x55,/*2009*/
0x55,0xA5,0x59,0x55,0x95,0x55,/*2010*/
0x95,0xA5,0x9A,0x59,0x96,0xA5,/*2011*/
0xA5,0x51,0x45,0x45,0x45,0x54,/*2012*/
0x54,0x51,0x55,0x45,0x55,0x55,/*2013*/
0x55,0xA5,0x59,0x55,0x95,0x55,/*2014*/
0x95,0xA5,0x9A,0x59,0x96,0x95,/*2015*/
0x95,0x50,0x45,0x45,0x45,0x54,/*2016*/
0x50,0x51,0x55,0x45,0x55,0x55,/*2017*/
0x55,0x65,0x59,0x55,0x95,0x55,/*2018*/
0x55,0xA5,0x99,0x59,0x96,0x95,/*2019*/
0x95,0x50,0x45,0x04,0x45,0x54,/*2020*/
0x50,0x51,0x55,0x45,0x55,0x54,/*2021*/
0x55,0x55,0x59,0x55,0x55,0x55,/*2022*/
0x55,0xA5,0x99,0x59,0x96,0x95,/*2023*/
0x95,0x50,0x45,0x04,0x45,0x50,/*2024*/
0x50,0x51,0x55,0x45,0x55,0x54,/*2025*/
0x54,0x55,0x55,0x55,0x55,0x55,/*2026*/
0x55,0xA5,0x99,0x59,0x95,0x55,/*2027*/
0x95,0x50,0x45,0x04,0x45,0x50,/*2028*/
0x50,0x51,0x55,0x45,0x55,0x54,/*2029*/
0x54,0x55,0x55,0x55,0x55,0x55,/*2030*/
0x55,0xA5,0x99,0x59,0x95,0x55,/*2031*/
0x95,0x50,0x45,0x04,0x45,0x50,/*2032*/
0x50,0x51,0x55,0x45,0x55,0x54,/*2033*/
0x54,0x55,0x55,0x55,0x55,0x55,/*2034*/
0x55,0xA5,0x59,0x55,0x95,0x55,/*2035*/
0x95,0x50,0x45,0x04,0x45,0x50,/*2036*/
0x50,0x51,0x55,0x45,0x55,0x54,/*2037*/
0x54,0x55,0x55,0x55,0x55,0x55,/*2038*/
0x55,0xA5,0x59,0x55,0x95,0x55,/*2039*/
0x95,0x50,0x45,0x04,0x45,0x50,/*2040*/
0x50,0x51,0x45,0x45,0x45,0x54,/*2041*/
0x54,0x51,0x55,0x55,0x55,0x55,/*2042*/
0x55,0xA5,0x59,0x55,0x95,0x55,/*2043*/
0x95,0x50,0x45,0x04,0x41,0x50,/*2044*/
0x50,0x50,0x45,0x45,0x45,0x54,/*2045*/
0x54,0x51,0x55,0x45,0x55,0x55,/*2046*/
0x55,0xA5,0x59,0x55,0x95,0x55,/*2047*/
0x95,0x50,0x44,0x04,0x41,0x40,/*2048*/
0x40,0x50,0x45,0x04,0x45,0x54,/*2049*/
0x50,0x51,0x55,0x45,0x55,0x55,/*2050*/
0x55,0x55,0x59,0x55,0x55,0x55,/*2051*/
0x55,0x50,0x44,0x04,0x41,0x40,/*2052*/
0x40,0x50,0x45,0x04,0x45,0x54,/*2053*/
0x50,0x51,0x55,0x45,0x55,0x55,/*2054*/
0x55,0x55,0x55,0x55,0x55,0x55,/*2055*/
0x55,0x50,0x44,0x04,0x41,0x40,/*2056*/
0x40,0x50,0x45,0x04,0x45,0x50,/*2057*/
0x50,0x51,0x55,0x45,0x55,0x54,/*2058*/
0x55,0x55,0x55,0x55,0x55,0x55,/*2059*/
0x55,0x50,0x44,0x04,0x40,0x00,/*2060*/
0x40,0x50,0x45,0x04,0x45,0x50,/*2061*/
0x50,0x51,0x55,0x45,0x55,0x54,/*2062*/
0x54,0x55,0x55,0x55,0x55,0x55,/*2063*/
0x55,0x50,0x44,0x04,0x40,0x00,/*2064*/
0x40,0x50,0x45,0x04,0x45,0x50,/*2065*/
0x50,0x51,0x55,0x45,0x55,0x54,/*2066*/
0x54,0x55,0x55,0x55,0x55,0x55,/*2067*/
0x55,0x50,0x04,0x00,0x40,0x00,/*2068*/
0x40,0x50,0x45,0x04,0x45,0x50,/*2069*/
0x50,0x51,0x45,0x45,0x45,0x54,/*2070*/
0x54,0x55,0x55,0x55,0x55,0x55,/*2071*/
0x55,0x50,0x04,0x00,0x40,0x00,/*2072*/
0x40,0x50,0x45,0x04,0x41,0x50,/*2073*/
0x50,0x51,0x45,0x45,0x45,0x54,/*2074*/
0x54,0x51,0x55,0x45,0x55,0x55,/*2075*/
0x55,0x50,0x04,0x00,0x40,0x00,/*2076*/
0x40,0x50,0x45,0x04,0x41,0x50,/*2077*/
0x50,0x50,0x45,0x05,0x45,0x54,/*2078*/
0x54,0x51,0x55,0x45,0x55,0x55,/*2079*/
0x55,0x50,0x04,0x00,0x40,0x00,/*2080*/
0x40,0x50,0x44,0x04,0x41,0x40,/*2081*/
0x50,0x50,0x45,0x04,0x45,0x54,/*2082*/
0x50,0x51,0x55,0x45,0x55,0x55,/*2083*/
0x55,0x00,0x04,0x00,0x00,0x00,/*2084*/
0x00,0x50,0x44,0x04,0x41,0x40,/*2085*/
0x40,0x50,0x45,0x04,0x45,0x54,/*2086*/
0x50,0x51,0x55,0x45,0x55,0x55,/*2087*/
0x55,0x00,0x00,0x00,0x00,0x00,/*2088*/
0x00,0x50,0x44,0x04,0x41,0x40,/*2089*/
0x40,0x50,0x45,0x04,0x45,0x50,/*2090*/
0x50,0x51,0x55,0x45,0x55,0x54,/*2091*/
0x55,0x00,0x00,0x00,0x00,0x00,/*2092*/
0x00,0x50,0x44,0x04,0x40,0x00,/*2093*/
0x40,0x50,0x45,0x04,0x45,0x50,/*2094*/
0x50,0x51,0x55,0x45,0x55,0x54,/*2095*/
0x54,0x00,0x00,0x00,0x00,0x00,/*2096*/
0x00,0x50,0x44,0x00,0x40,0x00,/*2097*/
0x40,0x50,0x45,0x04,0x45,0x50,/*2098*/
0x50,0x51,0x55,0x45,0x55,0x54,/*2099*/
0x54,0x55,0x55,0x55,0x55,0x55 /*2100*/
};
#endif
因为DS3231 只储存两位数字的年份, 西历星期有可能出错, 故此每次读取完DS3231 的日期之后,都需要進行星期换算工作, 才输出正确的星期,而不會採用DS3231內的星期數據。
DS3231.c
cpp
/*************************************************************************
函数功能: 输入阳历年月日 (4位年份),输出星期数据 (适用于 1901-2100年)
返回值: 1 = 星期一, ..., 6 = 星期六, 7 = 星期日
*****************************************************************************/
// Zeller's Congruence formula
unsigned char GetDayofWeek(unsigned int y, uchar m, uchar d)
{
int j, k, week;
int day_mapping[] = {6, 7, 1, 2, 3, 4, 5};
// 在该公式中,传统的1月和2月要看作上一年的13月和14月
if (m == 1 || m == 2) {
m += 12;
y--;
}
// 世纪 j 及世纪內年份 k
j = y / 100;
k = y % 100;
week = (d + (13 * (m + 1)) / 5 + k + (k / 4) + (j / 4) + (5 * j)) % 7;
return day_mapping[week]; // 1代表星期一, 2代表星期二, ..., 7代表星期日
}
iic.c 采用软件模拟iic方式进行通讯,好处是方便,不受个别 MCU硬件的限制:
cpp
#include "iic.h"
bit ack;
//========================================================================
// 函数名称: IIC_Init
// 函数功能: IIC初始化
// 入口参数: 无
// 函数返回: 无
//========================================================================
void IIC_Init() //IIC初始化
{
WTST = 0; // 设置程序指令延时参数,赋值为0时速度设置为最快, 程序代码不等待
EAXFR = 1; // 扩展寄存器(XFR)访问使能
CKCON = 0; // 提高访问XRAM速度, xdata访问不增加等待
P0M1 = 0x00; P0M0 = 0x00;
P1M1 = 0x00; P1M0 = 0x00; // 准双向口, SCL=P2.5, SDA=P2.4 (弱上拉,兼容传统8051)
P2M1 = 0x00; P2M0 = 0x00;
P3M1 = 0x00; P3M0 = 0x00;
P4M1 = 0x00; P4M0 = 0x00;
P5M1 = 0x00; P5M0 = 0x00;
P6M1 = 0x00; P6M0 = 0x00;
P7M1 = 0x00; P7M0 = 0x00;
P4PU = 0xFF; // 使能 P4 端口内部的4.1K 上拉电阻 (4x4矩阵键盘)
}
//========================================================================
// 函数名称: Delay1us
// 函数功能: IIC延时 1us
// 入口参数: 无
// 函数返回: 无
//========================================================================
void Delay1us() //@24.000MHz 延时1us
{
_nop_(); _nop_(); _nop_();_nop_();
_nop_(); _nop_(); _nop_();_nop_();
_nop_(); _nop_(); _nop_();_nop_();
_nop_(); _nop_(); _nop_();_nop_();
_nop_(); _nop_(); _nop_();_nop_();
_nop_(); _nop_(); //1T单片机用22个 NOP, 呼叫函数及返回 2T
}
// IIC 总线函数
/**********************************************
//IIC Start
//SCL 高电平时,SDA 从高变低
**********************************************/
void IIC_Start()
{
SDA = 1; //发送起始条件的数据信号
SCL = 1;
Delay1us(); //起始条件建立时间大于4.7us,延时
SDA = 0;
Delay1us(); //起始条件锁定时间大于4μs
SCL = 0;
}
/**********************************************
//IIC Stop
//SCL 高电平时,SDA 从低变高
**********************************************/
void IIC_Stop()
{
//SCL = 0; // SCL 已经是 0
SDA = 0;
Delay1us();
SCL = 1;
Delay1us(); //结束条件建立时间大于4us
SDA = 1;
Delay1us();
}
/**********************************************
// I2C 发送一个字节 (高位先行)
功能: 将数据c发送出去,可以是地址,也可以是数据,发完后等待应答,
并对此状态位进行操作.(不应答或非应答都使 ack=0)
发送数据正常,ack=1; ack=0 表示被控器无应答或损坏。
**********************************************/
void IIC_SendByte(u8 c)
{
uchar BitCnt;
for(BitCnt=0;BitCnt<8;BitCnt++) //要传送的数据长度为8位
{
if((c<<BitCnt) & 0x80)
SDA=1; //判断发送位
else
SDA=0;
Delay1us();
SCL=1; //置时钟线为高,通知被控器开始接收数据位
Delay1us(); //保证时钟高电平周期大于4μs
SCL=0;
}
SDA=1; //8位发送完后释放数据线,准备接收应答位
Delay1us();
SCL=1; // 接收ACK
Delay1us();
if(SDA==1)
ack=0;
else
ack=1; // 判断是否接收到应答信号
SCL=0;
Delay1us();
}
/**********************************************
// I2C 接收一个字节 (高位先行)
//
//功能: 用来接收从器件传来的数据,并判断总线错误(不发应答信号),
// 发完后请用应答函数应答从机。
**********************************************/
uchar IIC_RcvByte()
{
uchar retc;
uchar BitCnt;
retc=0;
SDA=1; //置数据线为输入方式
for(BitCnt=0;BitCnt<8;BitCnt++)
{
Delay1us();
SCL=0; //置时钟线为低,准备接收数据位
Delay1us(); //时钟低电平周期大于4.7μs
SCL=1; //置时钟线为高使数据线上数据有效
Delay1us();
retc=retc<<1;
if(SDA==1)
retc=retc+1; //读数据位,接收的数据位放入retc中
Delay1us();
}
SCL=0;
Delay1us();
return(retc);
}
/********************************************************************
//函数名称: IIC_ACK 应答子函数
//函数原型: void IIC_ACK(bit a);
//功能: 主控器进行应答信号(可以是应答或非应答信号,由位参数a决定)
// 发送 ACK, a=0 ACK, a=1 NAK
********************************************************************/
void IIC_ACK(bit a)
{
SDA = a; // a=0 ACK, a=1 NACK
Delay1us(); // delay to make SDA signal stable
SCL=1;
Delay1us(); //时钟低电平周期大于4us
SCL=0; //清时钟线,钳住I2C总线以便继续接收
Delay1us();
}
//========================================================================
// 函数名称: IIC_Write_NByte
// 函数功能: IIC连续写入几个字节
// 入口参数: @slave:从机地址 @addr:数据地址 @p:数组 @number:数据个数
// 函数返回: 无
//========================================================================
void IIC_Write_NByte( u8 slave,u8 addr,u8 *p,u8 number ) //IIC写入连续的几个字节
{
IIC_Start();
IIC_SendByte(slave);
if (ack == 0) return;
IIC_SendByte(addr);
if (ack == 0) return;
do
{
IIC_SendByte(*p);
if (ack==0) return;
p++;
}
while(--number);
IIC_Stop();
}
//========================================================================
// 函数名称: IIC_Read_NByte
// 函数功能: IIC连续读取几个字节
// 入口参数: @slave:从机地址 @addr:数据地址 @p:数组 @number:数据个数
// 函数返回: *p 读到的数据
//========================================================================
void IIC_Read_NByte( u8 slave,u8 addr,u8 *p,u8 number ) //IIC读取连续的几个字节
{
IIC_Start();
IIC_SendByte(slave);
if (ack==0) return;
IIC_SendByte(addr);
if (ack==0) return;
IIC_Start();
IIC_SendByte((u8)(slave+0x01));
if (ack==0) return;
while(number > 0)
{
*p = IIC_RcvByte(); // 读取数据
p++;
number--;
if( number > 0 )
IIC_ACK(0); // 还有位元组,回传 ACK (假设 0 代表 ACK)
else
IIC_ACK(1); // 最后一个位元组,回传 NACK (假设 1 代表 NACK)
}
IIC_Stop();
}
未来可以改进的地方,包括增加年、月、日、时的干支,显示公众假期,例如五一劳动节、中秋节。加入中国传统节日例如三伏,一九,二九等资料。
