基于STM32的电子钟与万年历设计

基于STM32的电子钟与万年历设计

一、系统概述与核心功能

1. 系统定位

基于STM32的电子钟与万年历以“精准计时-日期管理-人机交互-低功耗续航”为核心,实现实时时间(时/分/秒)、完整日期(年/月/日/星期)、闰年自动判断、闹钟提醒、温度监测(可选)及多模式显示,支持按键/旋转编码器设置,适用于家庭、办公室、教室等场景,替代传统石英钟/纸质日历,提供智能化时间管理方案。

2. 核心功能模块

模块 功能描述
时间基准 实时时钟(RTC)提供精准时间(年/月/日/时/分/秒/星期),支持断电走时(纽扣电池备份)
日期管理 自动计算闰年、每月天数,支持农历转换(可选),显示节气/节日(扩展)
显示输出 OLED/LCD显示时间、日期、星期、温度(可选),支持多界面切换(时间/日历/闹钟)
用户交互 按键/旋转编码器设置时间、闹钟,蜂鸣器提示(闹钟/操作反馈),LED状态指示
数据存储 存储闹钟设置、用户偏好(如亮度),支持掉电保存(EEPROM/Flash)
低功耗设计 待机时关闭非必要模块(显示/传感器),STM32进入STOP模式,续航≥6个月(纽扣电池)

二、硬件设计方案

1. 核心硬件选型

模块 型号 关键参数 接口方式
主控MCU STM32F103C8T6 72MHz Cortex-M3,64KB Flash,20KB RAM,I2C、SPI、UART,支持低功耗模式 核心控制器
实时时钟 DS3231 精度±2ppm(±1分钟/年),I2C接口,内置温度补偿,纽扣电池(CR2032)备份 I2C1(PB6=SCL,PB7=SDA)
显示模块 OLED 12864(I2C) 0.96寸,128×64像素,自发光,低功耗(<10mA),显示时间/日期/温度 I2C1(复用DS3231)
输入模块 旋转编码器+轻触按键 编码器(调节时间/音量),按键(设置/确认/返回),上拉输入,消抖处理 GPIO(PA0-PA2编码器,PC0-PC1按键)
温度传感器 DS18B20(可选) 单总线接口,测温范围-55~125℃,精度±0.5℃,数字输出 GPIO(PA1,单总线)
电源模块 CR2032纽扣电池+AMS1117 3V纽扣电池(RTC备份),5V Micro USB供电(主电源),AMS1117-3.3V稳压 双电源供电(主电源优先)
辅助模块 蜂鸣器+LED 有源蜂鸣器(5V,85dB),LED(红/绿双色,电源/闹钟状态) GPIO(PB0=蜂鸣器,PB1=LED)

2. 硬件电路设计要点

2.1 核心电路连接

2.2 低功耗设计

三、软件设计与核心代码

1. 系统架构(轮询+中断)

采用前后台系统(无RTOS),以主循环轮询为主,中断处理按键/编码器/闹钟事件,核心流程:

  1. 初始化:配置时钟、I2C、GPIO、RTC、显示模块。
  2. 主循环:读取RTC时间/日期→更新显示→扫描按键/编码器→处理设置/闹钟。
  3. 中断服务:外部中断(编码器旋转/按键)、RTC闹钟中断(触发蜂鸣器)。

2. 核心代码实现(基于标准库)

2.1 DS3231 RTC驱动(I2C通信)

#include "ds3231.h"
#include "i2c.h"

// DS3231寄存器地址
#define DS3231_SEC   0x00  // 秒
#define DS3231_MIN   0x01  // 分
#define DS3231_HOUR  0x02  // 时
#define DS3231_WEEK  0x03  // 星期
#define DS3231_DATE  0x04  // 日
#define DS3231_MON   0x05  // 月
#define DS3231_YEAR  0x06  // 年
#define DS3231_CTRL  0x0E  // 控制寄存器
#define DS3231_STATUS 0x0F  // 状态寄存器

// 写DS3231寄存器
void DS3231_WriteReg(uint8_t reg, uint8_t val) {
  HAL_I2C_Mem_Write(&hi2c1, 0x68<<1, reg, 1, &val, 1, 100);
}

// 读DS3231寄存器
uint8_t DS3231_ReadReg(uint8_t reg) {
  uint8_t val;
  HAL_I2C_Mem_Read(&hi2c1, 0x68<<1, reg, 1, &val, 1, 100);
  return val;
}

// 设置时间(时/分/秒,BCD码)
void DS3231_SetTime(uint8_t hour, uint8_t min, uint8_t sec) {
  DS3231_WriteReg(DS3231_SEC, sec);   // 秒(BCD码,如30秒=0x30)
  DS3231_WriteReg(DS3231_MIN, min);   // 分
  DS3231_WriteReg(DS3231_HOUR, hour); // 时(24小时制)
}

// 读取时间(返回BCD码,需转换为十进制)
void DS3231_ReadTime(uint8_t *hour, uint8_t *min, uint8_t *sec) {
  *sec = DS3231_ReadReg(DS3231_SEC);
  *min = DS3231_ReadReg(DS3231_MIN);
  *hour = DS3231_ReadReg(DS3231_HOUR);
}

// 设置日期(年/月/日/星期,BCD码)
void DS3231_SetDate(uint8_t year, uint8_t month, uint8_t date, uint8_t week) {
  DS3231_WriteReg(DS3231_WEEK, week);  // 星期(1-7,1=周日)
  DS3231_WriteReg(DS3231_DATE, date);  // 日
  DS3231_WriteReg(DS3231_MON, month);  // 月
  DS3231_WriteReg(DS3231_YEAR, year);  // 年(后两位,如2023=0x23)
}

2.2 时间日期处理(闰年判断+星期计算)

// BCD码转十进制
uint8_t bcd2dec(uint8_t bcd) {
  return (bcd>>4)*10 + (bcd&0x0F);
}

// 十进制转BCD码
uint8_t dec2bcd(uint8_t dec) {
  return ((dec/10)<<4) | (dec%10);
}

// 闰年判断(返回1=闰年,0=平年)
uint8_t Is_LeapYear(uint16_t year) {
  if ((year%4==0 && year%100!=0) || year%400==0) return 1;
  return 0;
}

// 计算每月天数(考虑闰年2月)
uint8_t Days_In_Month(uint16_t year, uint8_t month) {
  uint8_t days[] = {31,28,31,30,31,30,31,31,30,31,30,31};
  if (month==2 && Is_LeapYear(year)) return 29;
  return days[month-1];
}

// 计算星期(基姆拉尔森公式,返回0-6=周日-周六)
uint8_t Calc_Weekday(uint16_t year, uint8_t month, uint8_t day) {
  if (month < 3) { month += 12; year--; }
  return (day + 2*month + 3*(month+1)/5 + year + year/4 - year/100 + year/400) % 7;
}

2.3 OLED显示驱动(时间/日期界面)

#include "oled.h"
#include "font.h"  // 字库(6x8/8x16 ASCII)

// OLED写命令/数据
void OLED_WriteCmd(uint8_t cmd) {
  HAL_I2C_Mem_Write(&hi2c1, 0x3C<<1, 0x00, 1, &cmd, 1, 100);
}

void OLED_WriteData(uint8_t data) {
  HAL_I2C_Mem_Write(&hi2c1, 0x3C<<1, 0x40, 1, &data, 1, 100);
}

// 清屏
void OLED_Clear(void) {
  for (uint8_t page=0; page<8; page++) {
    OLED_WriteCmd(0xB0+page);  // 页地址
    OLED_WriteCmd(0x00);        // 列低4位
    OLED_WriteCmd(0x10);        // 列高4位
    for (uint8_t col=0; col<128; col++) OLED_WriteData(0x00);
  }
}

// 显示时间(时:分:秒,居中)
void OLED_ShowTime(uint8_t hour, uint8_t min, uint8_t sec) {
  char str[9];
  sprintf(str, "%02d:%02d:%02d", bcd2dec(hour), bcd2dec(min), bcd2dec(sec));
  OLED_ShowString(40, 2, str, 16);  // 第2页(16x16字体),x=40居中
}

// 显示日期(年-月-日 星期X)
void OLED_ShowDate(uint8_t year, uint8_t month, uint8_t date, uint8_t week) {
  char *week_str[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
  char str[20];
  sprintf(str, "20%02d-%02d-%02d %s", bcd2dec(year), bcd2dec(month), bcd2dec(date), week_str[week]);
  OLED_ShowString(10, 4, str, 16);  // 第4页
}

2.4 主程序框架(时间更新+显示+按键处理)

#include "stm32f1xx_hal.h"
#include "ds3231.h"
#include "oled.h"
#include "key.h"  // 按键/编码器驱动

int main(void) {
  HAL_Init();
  SystemClock_Config();  // 72MHz
  MX_I2C1_Init();        // I2C1(DS3231+OLED)
  OLED_Init();           // OLED初始化
  DS3231_Init();         // RTC初始化(检查电池,设置初始时间)
  Key_Init();            // 按键/编码器初始化
  
  OLED_Clear();
  uint8_t hour, min, sec, year, month, date, week;
  
  while (1) {
    // 1. 读取RTC时间日期
    DS3231_ReadTime(&hour, &min, &sec);
    DS3231_ReadDate(&year, &month, &date, &week);  // 需补充DS3231_ReadDate函数
    
    // 2. 更新显示
    OLED_ShowTime(hour, min, sec);
    OLED_ShowDate(year, month, date, week);
    
    // 3. 按键处理(设置时间/闹钟)
    Key_Process();  // 扫描按键,处理设置逻辑
    
    // 4. 低功耗处理(无操作5分钟后进入STOP模式)
    if (idle_time > 300) {  // 5分钟=300秒
      OLED_Clear();
      HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);  // 进入STOP模式
      SystemClock_Config();  // 唤醒后重新配置时钟
    }
    
    HAL_Delay(200);  // 200ms刷新一次
  }
}

参考代码 基于STM32的电子钟与万年历设计 www.youwenfan.com/contentcnt/133691.html

四、关键技术与优化

1. 时间精度保障

2. 显示优化

3. 低功耗设计

五、系统调试与扩展

1. 调试步骤

阶段 操作 工具
硬件调试 测量I2C信号(SCL/SDA)、DS3231电压(3.3V) 示波器、万用表
RTC校时 用串口打印DS3231时间,对比标准时钟 串口助手、标准时钟
显示测试 显示固定字符串,检查OLED是否缺画/花屏 肉眼观察、逻辑分析仪(I2C波形)
低功耗测试 进入STOP模式,测量纽扣电池电流(应<1μA) 万用表(电流档,串联测量)

2. 扩展功能

专注于matlab/simulink,电子电路,编程