1. 项目概述与硬件选型
在嵌入式系统开发中,电子时钟是最经典的练手项目之一。这次我们要用STM32F103单片机配合Proteus仿真软件,实现一个同时驱动LCD1602液晶屏和数码管的双显电子时钟。这种设计既能锻炼GPIO和定时器的使用,又能学习不同显示设备的驱动逻辑。
硬件选择上,我推荐使用STM32F103C8T6这款性价比极高的芯片,它内置RTC实时时钟模块,72MHz主频完全够用。显示部分采用常见的LCD1602(16字符x2行)和4位共阳数码管组合。仿真环境用Proteus 8.9以上版本,实际开发可以用STM32CubeMX生成初始化代码。
注意:Proteus中的STM32模型与实际硬件存在时序差异,仿真时定时器参数需要特别调整,后文会具体说明。
2. 硬件电路设计要点
2.1 显示模块接口设计
LCD1602采用标准的4位数据线接法(DB4-DB7),节省GPIO资源。具体连接:
- RS -> PA0
- RW -> GND(只写模式)
- E -> PA1
- D4-D7 -> PA4-PA7
数码管使用动态扫描方式驱动,四个位选信号接PB8-PB11,段选信号通过74HC595串转并芯片连接,只需占用STM32的三个引脚(PB12-PB14)。这种设计在真实项目中能有效减少线材数量。
2.2 关键外围电路
- 晶振电路:8MHz主晶振+32.768kHz RTC晶振(仿真时可省略)
- 复位电路:10k上拉电阻+0.1uF电容
- 按键电路:三个独立按键接PC13-PC15,用于时间设置
3. 软件架构设计
3.1 主程序流程图
int main(void) { HAL_Init(); SystemClock_Config(); LCD_Init(); DigitalTube_Init(); TIM3_Init(10, 884); // 1ms定时 RTC_Init(); while(1) { Key_Process(); // 按键扫描 Time_Update(); // 时间计算 LCD_Display(); // LCD刷新 Tube_Display(); // 数码管刷新 } }3.2 定时器配置关键代码
使用TIM3产生1ms中断作为系统时基:
void TIM3_Init(uint16_t arr, uint16_t psc) { TIM_HandleTypeDef htim3; htim3.Instance = TIM3; htim3.Init.Prescaler = psc; htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.Period = arr; HAL_TIM_Base_Init(&htim3); HAL_TIM_Base_Start_IT(&htim3); }4. 双显示驱动实现
4.1 LCD1602驱动优化
采用状态机方式优化显示刷新,避免频繁操作影响实时性:
void LCD_Refresh(void) { static uint8_t state = 0; switch(state) { case 0: LCD_SetCursor(0,0); LCD_WriteString("Time:"); state++; break; case 1: LCD_SetCursor(6,0); printf("%02d:%02d:%02d", hours, mins, secs); state++; break; case 2: LCD_SetCursor(0,1); LCD_WriteString("Date:"); state++; break; case 3: LCD_SetCursor(6,1); printf("%04d-%02d-%02d", year, month, day); state = 0; break; } }4.2 数码管动态扫描
在定时器中断中实现扫描,避免闪烁:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { static uint8_t pos = 0; DigitalTube_Clear(); switch(pos) { case 0: Show_Hour_Tens(); break; case 1: Show_Hour_Units(); break; case 2: Show_Min_Tens(); break; case 3: Show_Min_Units(); break; } DigitalTube_Select(pos); pos = (pos+1)%4; }5. 时间管理与设置功能
5.1 RTC初始化
使用STM32内置RTC模块,需先配置LSE时钟源:
void RTC_Init(void) { RTC_TimeTypeDef sTime = {0}; sTime.Hours = 12; sTime.Minutes = 0; sTime.Seconds = 0; HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN); }5.2 按键时间调整
采用状态机实现多功能按键:
void Key_Process(void) { if(KEY1_Pressed()) { mode = (mode+1)%3; // 切换设置模式 } if(mode == 1) { if(KEY2_Pressed()) hours++; if(KEY3_Pressed()) hours--; } // 其他模式类似... }6. Proteus仿真注意事项
- 数码管扫描频率建议设置在200-500Hz之间,过低会闪烁,过高可能导致仿真卡顿
- LCD1602的使能信号E脉宽需要>450ns,仿真时可适当增加延时
- 使用"Digital"仿真模式而非"Mixed"模式,可提高运行速度
- 定时器参数需要根据仿真速度调整,实际1ms定时可能对应仿真参数(10,884)
7. 常见问题排查
- LCD显示乱码:
- 检查初始化时序是否严格遵循数据手册
- 测量VO引脚电压(应为0.5Vcc左右)
- 在第一次初始化后延迟500ms再操作
- 数码管显示错位:
- 确认位选和段选信号没有接反
- 检查动态扫描间隔时间(建议2-5ms)
- 增加位选信号切换后的消隐时间
- 时间走时不准:
- 调整RTC异步预分频器(PREDIV_A)和同步预分频器(PREDIV_S)
- 在仿真中可以用"Debug->Set Animation Options"调整时间因子
这个项目我做过多次迭代,发现最关键的优化点是显示刷新策略。通过将LCD的刷新分散到不同周期,并合理设置数码管扫描频率,可以显著降低CPU占用率。实际测试中,这个方案即使在加入温度显示等功能后,仍然能保持流畅运行。