基于上位机控制的液晶电子时钟设计
一、系统概述与核心功能
1. 系统定位
本设计打破传统电子钟“按键调时”的物理局限,采用“上位机(PC/平板电脑) + 下位机(STM32嵌入式端)”的架构。上位机软件拥有友好的图形界面(GUI),负责向嵌入式端发送高精度的北京时间指令;下位机STM32接收指令后,驱动液晶屏幕(LCD1602或OLED)实时显示时间,并内置RTC时钟晶振确保掉电或断连后的精准走时。该系统适用于实验室设备、桌面摆件或需要在PC端集中管理的计时场景。
2. 核心功能模块
| 模块 | 功能描述 |
|---|---|
| 上位机控制 | 开发PC端软件(C#/Python/Qt),提供直观的GUI时间设置面板,一键同步系统时间或手动输入时分秒 |
| 串口/网络通信 | 上位机通过USB转TTL串口或局域网UDP/TCP向下位机发送时间校准指令 |
| 下位机主控 | STM32F103C8T6作为主控,接收上位机指令,解析并更新RTC(实时时钟)寄存器 |
| 液晶显示 | 驱动LCD1602(经典字符屏)或0.96寸OLED,实时刷新显示“HH:MM:SS”格式的时间字符串 |
| 掉电走时 | 板载32.768kHz晶振配合STM32内部RTC,配合纽扣电池,确保PC端断连或断电后时间依然精准 |
二、硬件设计方案
1. 核心硬件选型
| 模块 | 型号 | 关键参数 | 接口方式 |
|---|---|---|---|
| 主控MCU | STM32F103C8T6 | 72MHz Cortex-M3,内置RTC实时时钟外设,64KB Flash,20KB RAM | 核心控制器 |
| 液晶显示 | LCD1602 / 0.96寸OLED | LCD1602:5V供电,HD44780驱动,I2C转接板;OLED:3.3V,I2C接口,自发光 | I2C1(PB6/PB7) |
| 通信接口 | CH340/CP2102 USB转TTL | 将PC端的USB接口虚拟为COM串口,波特率设定为9600或115200 | USART1(PA9/TX, PA10/RX) |
| 时钟源 | 32.768kHz无源晶振 | 专用于RTC外设,提供精准的1秒计时基准 | OSC32_IN/OUT |
| 电源与后备 | Micro USB + CR1220电池 | USB供电(5V转3.3V),CR1220纽扣电池专门给VBAT引脚供电,实现RTC掉电保持 | 电源管理 |
2. 硬件电路设计要点
- 通信连接:PC端USB插入USB转TTL模块,模块的TX接STM32的PA10(RX),RX接PA9(TX),GND共地。
- RTC后备电池:STM32的VBAT引脚串联一个普通的1N4148二极管(防止电流倒灌烧坏USB电源)后,连接到CR1220纽扣电池的正极。
- 液晶接口:LCD1602通常需要5V逻辑电平,需注意STM32的I2C引脚(PB6/PB7)最好配置为“开漏输出(Open-Drain)”并外接上拉电阻至5V,或者使用带有电平转换的I2C转接板。
三、软件设计与核心代码
1. 通信协议设计(上位机 ↔ 下位机)
为了保证通信的可靠性和易解析性,我们制定一套简单的“帧头 + 数据 + 帧尾”协议:
- 指令格式:
#HH:MM:SS\n(例如:#12:30:45\n) - 含义:
#为起始符,HH为两位数小时,MM为分钟,SS为秒,\n(换行符)为结束符。 - 优点:上位机发送的是人类可读的ASCII字符串,下位机用C语言字符串函数极易解析。
2. 下位机核心代码(基于STM32标准库)
2.1 USART串口中断接收与协议解析
我们利用STM32的串口接收中断(RXNE)来逐字节拼接来自上位机的指令。
#include "stm32f10x.h"
#include <string.h>
// 定义接收缓冲区
uint8_t rx_buffer[16];
uint8_t rx_index = 0;
uint8_t time_update_flag = 0; // 时间更新标志位
// USART1中断服务函数
void USART1_IRQHandler(void) {
uint8_t rx_data;
if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
rx_data = USART_ReceiveData(USART1); // 读取接收到的字节
// 判断是否是帧头 '#'
if (rx_data == '#') {
rx_index = 0; // 重置缓冲区索引,重新开始接收
rx_buffer[rx_index++] = rx_data;
}
// 判断是否是帧尾 '\n' (换行符)
else if (rx_data == '\n') {
rx_buffer[rx_index++] = rx_data;
rx_buffer[rx_index] = '\0'; // 添加字符串结束符
time_update_flag = 1; // 置位更新标志,通知主循环处理时间
}
// 普通数据字符,存入缓冲区
else {
if (rx_index < 15) { // 防止数组溢出
rx_buffer[rx_index++] = rx_data;
}
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
2.2 时间解析与RTC设置函数
当主循环检测到 time_update_flag 后,调用此函数将字符串转换为整数,并写入STM32的内部RTC寄存器。
#include "rtc.h"
// 解析上位机指令并设置RTC时间
void Parse_And_Set_RTC(uint8_t *cmd_str) {
uint8_t hour, min, sec;
// 使用C标准库 sscanf 轻松提取时间
// 格式匹配 "#12:30:45\n"
if (sscanf((char*)cmd_str, "#%02d:%02d:%02d", &hour, &min, &sec) == 3) {
RTC_TimeTypeDef RTC_TimeStructure;
// 配置时间结构体
RTC_TimeStructure.RTC_Hours = hour;
RTC_TimeStructure.RTC_Minutes = min;
RTC_TimeStructure.RTC_Seconds = sec;
RTC_TimeStructure.RTC_H12 = RTC_H12_AM; // 设为12小时制上午(或改为24小时制)
// 将时间写入STM32的RTC硬件寄存器
RTC_SetTime(RTC_Format_BIN, &RTC_TimeStructure);
// 可选:向上位机回传一条确认消息,表示时间已同步
USART_SendString("RTC Updated OK!\r\n");
} else {
USART_SendString("Invalid Cmd!\r\n");
}
}
2.3 液晶显示刷新(主循环逻辑)
#include "lcd1602.h" // 或 oled.h
// 主函数核心逻辑
int main(void) {
// ...(省略:系统时钟初始化、GPIO/USART/RTC/LCD初始化)...
while (1) {
// 1. 检测上位机是否发来了新的时间指令
if (time_update_flag) {
time_update_flag = 0; // 清除标志位
Parse_And_Set_RTC(rx_buffer); // 解析并更新RTC
}
// 2. 每秒读取一次RTC硬件时间,并显示在液晶屏上
static uint32_t last_tick = 0;
if (HAL_GetTick() - last_tick >= 1000) {
last_tick = HAL_GetTick();
RTC_TimeTypeDef current_time;
RTC_GetTime(RTC_Format_BIN, ¤t_time);
// 拼接显示字符串
char disp_str[16];
sprintf(disp_str, "Time: %02d:%02d:%02d",
current_time.RTC_Hours,
current_time.RTC_Minutes,
current_time.RTC_Seconds);
LCD1602_ShowString(0, 0, disp_str); // 在液晶第一行显示
}
}
}
3. 上位机软件设计思路(C# WinForms示例)
上位机不需要复杂的算法,只需一个简单的窗体应用程序,提供一个文本框和按钮。
using System;
using System.IO.Ports;
using System.Windows.Forms;
namespace PC_Clock_Controller {
public partial class Form1 : Form {
SerialPort serialPort = new SerialPort();
public Form1() {
InitializeComponent();
// 初始化串口,绑定到COM3(需在设备管理器中查看)
serialPort.PortName = "COM3";
serialPort.BaudRate = 9600;
serialPort.Open();
}
// “一键同步系统时间”按钮点击事件
private void btnSync_Click(object sender, EventArgs e) {
DateTime now = DateTime.Now; // 获取当前PC系统时间
// 拼接协议指令,例如:"#14:30:25\n"
string cmd = $"#{now.Hour:00}:{now.Minute:00}:{now.Second:00}\n";
if (serialPort.IsOpen) {
serialPort.Write(cmd); // 发送指令!
MessageBox.Show("时间已成功发送至下位机!");
}
}
}
}
参考代码 上位机控制的液晶电子时钟(提供VB,C语言程序) www.youwenfan.com/contentcnt/124083.html
四、系统调试与扩展
1. 调试步骤
| 阶段 | 操作 | 工具 |
|---|---|---|
| 硬件调试 | 测量32.768kHz晶振是否起振,纽扣电池电压是否≥2.8V | 示波器、万用表 |
| 通信测试 | PC端发送#12:00:00\n,用示波器抓取PA10(RX)引脚波形 |
串口助手、示波器 |
| 显示测试 | 观察LCD/OLED是否能稳定刷新时间,不会频繁闪烁或乱码 | 肉眼观察 |
| 掉电测试 | 拔掉USB线5分钟后重新插上,观察液晶显示时间是否连续 | 实际测试 |
2. 扩展功能
- 加入网络自动对时(NTP):在下位机侧集成ESP8266 Wi-Fi模块,STM32定期通过TCP协议向互联网NTP服务器请求标准时间,彻底脱离PC上位机。
- 加入温湿度显示:在STM32的I2C总线上挂接SHT30传感器,在液晶第二行滚动显示“Temp: 25.5C Hum: 60%”。
- 加入倒计时/闹钟功能:上位机软件增加“倒计时设定”面板,STM32接收后驱动板载蜂鸣器进行闹铃提醒。
五、总结
基于上位机控制的液晶电子时钟,巧妙地将PC丰富的人机交互能力与嵌入式系统的实时执行能力相结合。通过自定义串口协议,PC端能够毫秒级地完成对下位机时钟的精准“对表”。这种设计不仅提升了用户体验,更为后续接入物联网(IoT)功能打下了坚实的软件架构基础。

浙公网安备 33010602011771号