1. 项目概述
这个电子日历项目是我去年帮一个学弟做的毕业设计,用STM32F103C8T6作为主控芯片,实现了时间日期显示、农历转换、温度监测和闹钟功能。整个系统成本不到50块钱,但功能比市面上百元级的电子日历还要丰富。最让我自豪的是,我们通过硬件和软件的优化,让这个系统在显示农历和温度的同时,待机电流只有3mA左右。
提示:选择STM32F103C8T6是因为它性价比极高,10块钱左右的价格就能获得72MHz主频的Cortex-M3内核,64KB Flash和20KB RAM完全够用。
系统架构上,我们采用了模块化设计思路:
- 主控:STM32F103C8T6最小系统板
- 时钟源:DS3231高精度RTC芯片(年误差±2分钟)
- 显示:LCD1602液晶屏(改造成4线模式节省IO)
- 温度传感器:DS18B20(±0.5℃精度)
- 用户输入:4个轻触按键(设置、加、减、确认)
- 报警输出:有源蜂鸣器
2. 硬件设计详解
2.1 核心器件选型
2.1.1 STM32最小系统
我们选用STM32F103C8T6蓝色小板,自带3.3V LDO和复位电路。需要注意两点:
- BOOT0引脚必须通过10K电阻下拉,否则可能无法启动
- 虽然芯片支持最高72MHz,但为了降低功耗,实际工作在8MHz(HSI)
c复制// 时钟配置示例(使用HAL库)
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
HAL_RCC_OscConfig(&RCC_OscInitStruct);
2.1.2 时钟芯片对比
我们测试了三款常见RTC芯片:
| 型号 | 精度 | 温度补偿 | 接口 | 价格 |
|---|---|---|---|---|
| DS1302 | ±5分钟/月 | 无 | SPI | 2元 |
| DS1307 | ±2分钟/月 | 无 | I2C | 3元 |
| DS3231 | ±2分钟/年 | 有 | I2C | 8元 |
最终选择DS3231SN(-40℃~+85℃工业级),它的32.768kHz晶振内置温度补偿,实测在室温下月误差不超过10秒。
2.2 关键电路设计
2.2.1 电源电路
系统采用5V供电,但STM32需要3.3V,这里有个坑要注意:
- 不能直接用LDO从5V降到3.3V,因为DS3231和LCD1602需要5V
- 最终方案:5V主电源 → AMS1117-3.3 → STM32
→ 直接供给其他外设
经验:给DS3231供电最好加个100μF电容,否则设置时间时容易因电压波动导致写入失败。
2.2.2 LCD1602接口优化
标准1602需要8位数据线+3位控制线,我们通过硬件改造实现了4线模式:
- 将LCD的DB0-DB3悬空
- DB4-DB7接STM32的PA4-PA7
- 修改初始化代码:
c复制void LCD_Init() {
// 4位模式初始化序列
HAL_Delay(50);
LCD_WriteCmd(0x33);
HAL_Delay(5);
LCD_WriteCmd(0x32);
HAL_Delay(1);
LCD_WriteCmd(0x28); // 4位模式,2行显示
// ...其他初始化
}
2.2.3 蜂鸣器驱动电路
直接用IO口驱动蜂鸣器会导致电流不足,我们设计了三极管驱动电路:
code复制蜂鸣器正极 → 5V
蜂鸣器负极 → 2N3904集电极
2N3904发射极 → GND
2N3904基极 → 1K电阻 → PB8
代码中通过PWM控制鸣响频率:
c复制TIM3->CCR3 = 500; // 设置占空比50%
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);
3. 软件实现关键点
3.1 农历算法实现
农历计算是最大难点,我们采用以下方案:
- 预先计算2000-2050年的农历数据,压缩存储为uint32_t数组
- 每个uint32_t表示一年的数据,按位存储闰月和大月信息
- 运行时通过查表+计算得出具体日期
c复制// 示例:判断某年是否有闰月
uint32_t lunar_year_data = lunar_table[year - 2000];
uint8_t leap_month = (lunar_year_data >> 20) & 0x0F;
3.2 多任务调度
没有用RTOS,而是采用时间片轮询:
- 主循环每10ms执行一次
- 通过状态机管理不同功能模块
c复制while(1) {
uint32_t tick = HAL_GetTick();
// 每10ms扫描按键
if(tick - last_key_scan >= 10) {
Key_Scan();
last_key_scan = tick;
}
// 每500ms刷新显示
if(tick - last_disp_update >= 500) {
Display_Update();
last_disp_update = tick;
}
}
3.3 低功耗优化
通过以下措施将待机电流降到3mA:
- 关闭未用外设时钟
c复制
__HAL_RCC_ADC1_CLK_DISABLE(); __HAL_RCC_TIM2_CLK_DISABLE(); - 降低主频到8MHz
- 显示背光通过PWM调光(夜间自动降低亮度)
- 使用HAL_Delay()而非空循环等待
4. 常见问题与解决方案
4.1 LCD显示乱码
可能原因及排查:
- 对比度不合适 → 调节电位器
- 初始化时序不对 → 增加延时
- 数据线接触不良 → 重新焊接
4.2 时间不准
排查步骤:
- 检查DS3231电池电压(应≥2.3V)
- 用示波器看32.768kHz波形(幅度应≥0.4Vpp)
- 检查I2C上拉电阻(建议4.7K)
4.3 按键失灵
典型解决方案:
- 增加硬件消抖电路(100nF电容并联10K电阻)
- 软件消抖优化:
c复制uint8_t Key_Scan() {
static uint8_t last_state = 1;
uint8_t current = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
if(last_state && !current) {
HAL_Delay(20); // 延时20ms避开抖动
if(!HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0))
return 1;
}
last_state = current;
return 0;
}
5. 项目优化方向
- 改用OLED显示屏:可以显示更多内容(如天气预报图标)
- 增加WIFI模块:实现网络对时和天气获取
- 添加语音报时功能:通过SYN6288语音芯片实现
- 改用锂电池供电:配合充电电路实现便携式设计
这个项目最让我意外的是DS3231的精度表现,连续运行半年后,与手机时间对比只差了不到8秒。有学弟在毕业答辩时被问到为什么不用软件RTC,他的回答很精彩:"在-10℃的实验室环境下,STM32的内部RC时钟漂移能达到5%,而DS3231还能保持±2ppm的精度,这就是专业器件存在的意义。"