基于上位机控制的液晶电子时钟设计

一、系统概述与核心功能

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, &current_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)功能打下了坚实的软件架构基础。

posted @ 2026-04-18 16:27  yes_go  阅读(59)  评论(0)    收藏  举报