1. 项目概述
这个迷你OLED时钟项目是我最近完成的一个有趣DIY作品。作为一个嵌入式开发爱好者,我一直想做一个既实用又能展示技术的小项目。这个时钟不仅具备基本的时间显示功能,还集成了农历、温度监测、电池管理等实用特性,完全可以作为日常使用的桌面时钟。
整个项目从硬件选型到软件开发都是独立完成的,过程中遇到了不少挑战,也积累了一些经验。下面我会详细分享这个项目的完整实现过程,包括硬件设计、软件开发和调试心得。无论你是刚接触嵌入式开发的新手,还是有经验的开发者,都能从中获得一些实用的参考。
2. 硬件设计与选型
2.1 核心组件选择
主控芯片选用的是STM32F103CBT6,这是一款性价比极高的Cortex-M3内核微控制器。选择它的主要原因是:
- 72MHz主频足够处理时钟显示和各类传感器数据
- 丰富的GPIO和外设接口
- 成熟的开发环境和社区支持
- 价格实惠,适合DIY项目
时钟模块没有使用STM32内置的RTC,而是外接了DS3231高精度时钟芯片。DS3231的优势在于:
- 内置温度补偿晶体振荡器(TCXO)
- 精度可达±2ppm(约每月误差1分钟)
- 自带电池备份,断电不影响计时
- I2C接口,连接简单
OLED显示屏选用的是0.96寸128x64分辨率的I2C接口屏幕。这种屏幕的优势是:
- 自发光,无需背光
- 高对比度,可视角度大
- 低功耗,适合电池供电设备
- 接口简单,驱动方便
2.2 电源系统设计
电源部分采用3.7V锂电池供电,配合TP4056充电管理芯片。这个方案的特点是:
-
充电管理:
- 支持USB 5V输入充电
- 充电电流可通过电阻调节(本项目设为500mA)
- 具有充电状态指示(红灯充电中,绿灯充满)
-
电源开关:
- 使用MOSFET设计软开关电路
- 按键控制电源通断
- 避免物理开关的机械磨损
-
电压转换:
- 锂电池电压通过LDO稳压到3.3V供主控使用
- 确保系统稳定工作
2.3 结构设计与布局
为了追求紧凑的外观,PCB设计遵循以下原则:
- 三块板子垂直堆叠:电源板、主控板、显示板
- 每层板尺寸与OLED屏幕一致(约30mm×27mm)
- 板间通过排针连接,便于组装和维修
- 所有接口都设计在侧面,保持正面整洁
特别设计的水银开关用于自动调整显示方向。当设备倒置时,屏幕内容会自动旋转180度,确保可读性。这个功能通过检测水银开关状态和软件配合实现。
3. 软件开发与实现
3.1 系统架构设计
软件采用前后台系统架构:
- 前台:主循环处理显示更新和用户交互
- 后台:定时器中断处理时间基准和状态检测
主要功能模块包括:
-
时间管理模块
- DS3231驱动
- 时间格式转换
- 农历计算
-
显示驱动模块
- OLED初始化
- 图形绘制
- 显示缓冲管理
-
电源管理模块
- 电池电量检测
- 充电状态监测
- 省电模式控制
-
用户接口模块
- 按键检测
- 菜单系统
- 参数设置
3.2 关键代码实现
3.2.1 时间获取与显示
c复制// 时间结构体定义
typedef struct {
uint8_t seconds;
uint8_t minutes;
uint8_t hours;
uint8_t day;
uint8_t month;
uint16_t year;
uint8_t weekday;
} RTC_TimeTypeDef;
// 从DS3231读取时间
void DS3231_GetTime(RTC_TimeTypeDef *time)
{
uint8_t buf[7];
I2C_Read(DS3231_ADDR, 0x00, buf, 7);
time->seconds = BCD2DEC(buf[0] & 0x7F);
time->minutes = BCD2DEC(buf[1]);
time->hours = BCD2DEC(buf[2] & 0x3F);
time->weekday = buf[3];
time->day = BCD2DEC(buf[4]);
time->month = BCD2DEC(buf[5]);
time->year = BCD2DEC(buf[6]) + 2000;
}
// 时间显示函数
void Display_Time(RTC_TimeTypeDef time)
{
char str[16];
// 显示时分秒
sprintf(str, "%02d:%02d:%02d", time.hours, time.minutes, time.seconds);
OLED_ShowString(0, 0, str);
// 显示年月日
sprintf(str, "%04d-%02d-%02d", time.year, time.month, time.day);
OLED_ShowString(0, 2, str);
// 显示星期
const char *weekdays[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
OLED_ShowString(90, 2, weekdays[time.weekday-1]);
}
3.2.2 农历计算实现
农历计算采用查表法,预先存储1900-2100年的农历数据,通过算法计算出当前公历日期对应的农历日期。核心函数如下:
c复制// 农历数据结构
typedef struct {
uint8_t month;
uint8_t day;
bool isLeapMonth;
char zodiac[3]; // 生肖
char solarTerm[24]; // 节气
} LunarDate;
// 公历转农历
void Solar2Lunar(RTC_TimeTypeDef solarDate, LunarDate *lunarDate)
{
// 计算农历年份
uint16_t lunarYear = GetLunarYear(solarDate.year, solarDate.month, solarDate.day);
// 计算农历月份和日期
GetLunarMonthDay(solarDate.year, solarDate.month, solarDate.day,
&lunarDate->month, &lunarDate->day, &lunarDate->isLeapMonth);
// 获取生肖
GetZodiac(lunarYear, lunarDate->zodiac);
// 获取节气
GetSolarTerm(solarDate.year, solarDate.month, solarDate.day, lunarDate->solarTerm);
}
3.2.3 省电模式实现
c复制// 省电模式控制
void PowerSave_Handler(void)
{
static uint32_t lastActiveTime = 0;
// 检测用户活动
if(Key_IsPressed() || Tilt_IsChanged()) {
lastActiveTime = HAL_GetTick();
if(OLED_IsOff()) {
OLED_DisplayOn();
}
}
// 10秒无操作进入省电模式
if(HAL_GetTick() - lastActiveTime > 10000) {
if(!OLED_IsOff()) {
OLED_Clear();
OLED_DisplayOff();
}
}
}
4. 调试与优化
4.1 硬件调试要点
-
I2C总线调试:
- 确保上拉电阻正确(通常4.7kΩ)
- 检查设备地址设置
- 使用逻辑分析仪捕获通信波形
-
电源系统测试:
- 测量各点电压:电池电压、LDO输出等
- 测试充电功能,验证充电电流和截止电压
- 检查静态功耗,优化省电模式
-
显示方向检测:
- 验证水银开关安装方向
- 测试不同姿态下的显示方向是否正确
- 优化防抖算法,避免误触发
4.2 软件调试技巧
-
时间同步问题:
- 确保DS3231初始化正确
- 验证时间读取和写入的BCD转换
- 测试RTC电池备份功能
-
显示刷新优化:
- 采用局部刷新代替全屏刷新
- 优化显示缓冲区管理
- 调整刷新频率平衡功耗和流畅度
-
按键处理:
- 实现软件消抖
- 设计合理的按键响应时间
- 处理长按和短按的不同功能
5. 项目改进方向
这个时钟项目已经实现了基本功能,但还有不少可以改进的地方:
-
增加网络对时功能:
- 通过WiFi或蓝牙模块连接手机
- 自动同步网络时间
- 支持OTA固件升级
-
增强显示效果:
- 添加多种显示主题
- 实现平滑的动画过渡
- 支持自定义显示布局
-
扩展传感器功能:
- 增加湿度传感器
- 添加光线传感器自动调节亮度
- 集成空气质量检测
-
外壳设计:
- 3D打印定制外壳
- 优化人机交互布局
- 提升整体美观度
这个项目最让我满意的是它的实用性和可扩展性。通过选择合适的硬件和优化软件设计,最终产品的功耗很低,充一次电可以使用很长时间。而且整个系统非常稳定,已经连续运行一个月没有出现任何问题。