本项目为基于 GD32F30x 系列 MCU 的电子钟固件。内容涵盖钟表主界面、秒表、倒计时、闹钟与设置等子应用。项目以 Keil/MDK 项目为主,使用 ST7920 LCD(并基于帧缓冲)显示位图字模和图标,采用 DS3231 RTC 作为主要时钟源,并支持通过 GPS(GT-U8)授时。代码组织清晰,模块分层,便于扩展与移植。
Project/- Keil/MDK 工程文件(MyGD32F30X.uvprojx等)User/- 应用层源代码(包括 UI、子应用、驱动、外设、字体与持久化)- 关键文件:
main.c- 程序入口与应用切换clock_app.c/clock_app.h- 主钟面与交互逻辑display.c/display.h- ST7920 显示驱动与帧缓冲绘制keypad.c/keypad.h- 按键输入(去抖、短/长按)buzzer.c/buzzer.h- 蜂鸣器控制rtc_ds3231.c/rtc_ds3231.h- DS3231(软件 I2C)RTC 驱动gps_gtu8.c/gps_gtu8.h- GPS(GT-U8, NMEA RMC)解析stopwatch_app.c,countdown_app.c,alarm_app.c,settings_app.c- 各子应用flash_storage.c/flash_storage.h- Flash 持久化(双槽原子写入)status_bar.c- 顶部状态栏(时间/温度/标题)
- 关键文件:
Firmware/- 底层外设抽象层 (GD32 系列 HAL、库)build/、Objects/、Project/build/- 编译产物
- 实时时钟显示(支持 DS3231 RTC),同步周期:默认每 1s(可配置)
- GPS 授时支持(NMEA RMC 解析),可在设置中开启/关闭
- 顶部状态栏:时分、小字温度显示与当前页面标题
- 主界面:日期/时间显示、功能区(秒表、倒计时、闹钟、设置)
- 秒表:开始/停止、圈次(lap)与圈次视图
- 倒计时:编辑、预置快捷启动、暂停/取消、结束提示
- 闹钟:设置闹钟时间、启/停、闹铃响铃与确认
- 设置页:整点报时(hourly chime)、GPS 授时开关
- 持久化:当/关/闹钟配置写入 Flash,使用双槽原子更新策略
- 人机交互:4 个按键(K1..K4)支持短按/长按/长按重复
- 蜂鸣器提示:短/长按音、闹钟和倒计时警报(支持连续方波)
注:该映射由代码中对应驱动文件里定义的 GPIO 引脚决定(见
display.c,rtc_ds3231.c,gps_gtu8.c,keypad.c,buzzer.c)。
- MCU:GD32F30x(请使用与
Project/MyGD32F30X.uvprojx匹配的芯片型号) - LCD:ST7920 或兼容的图形 LCD,接口为并行 8-bit(代码驱动)
- 数据线:GPIOB
PB0..PB7 - RS:
PB8 - RW:
PB9 - E:
PB10 - PSB:
PB11 - RST:
PB12
- 数据线:GPIOB
- DS3231 RTC (软件 I2C,bit-banged)
- SCL:
PB13 - SDA:
PB14
- SCL:
- GPS (GT-U8, UART)
- USART0 TX:
PA9, RX:PA10(GPS_USART = USART0)
- USART0 TX:
- Keypad(4个按键)
- K1:
PA0 - K2:
PA1 - K3:
PA2 - K4:
PA3
- K1:
- 蜂鸣器(Buzzer)
PA8
- 其它:按需接线(例如 VCC 与 GND, I2C 上拉电阻等)
硬件电气注意事项:
- DS3231 使用开漏 I2C 引脚;若外部模块未提供上拉,则需要外部上拉电阻。
- GPS 波特率 9600,需将其串口 TX -> MCU RX (PA10) 连接。
- LCD 初始化与时序基于 ST7920,若换屏请检查命令序列与电源复位脉冲。
四个按键(K1..K4)带有短按(SHORT)与长按(LONG)能力:
通用按键行为(在不同页面略有差别):
- K1:短按选择/进入编辑/开始/确认;长按用于特殊操作或退出编辑
- K2:在编辑模式中增值;在查看模式中移动/切换功能(向左/向上)
- K3:在编辑模式中减值;在查看模式中移动/切换功能(向右/向下)
- K4:短按切换光标行(日期->时间->功能区);长按进入表盘页或取消/退出
各应用中按键分配(摘录主要行为):
-
主界面(
clock_app)- K1 短按:进入编辑(日期或时间)或在功能区启动选中功能
- K1 长按:与短按相同(编辑/启动); 编辑时长按保存并退出
- K2:在编辑时增加字段;查看时移动功能选择
- K3:在编辑时减少字段;查看时移动功能选择
- K4 短按:光标在日期 -> 时间 -> 功能区 循环;编辑时退出编辑(不保存)
- K4 长按:进入表盘页(CLOCK_FACE)
-
秒表(
stopwatch_app)- K1 短按:开始/停止;K1 长按:重置
- K2 短按:在非 lap_view 时添加圈次;在 lap_view 时翻页
- K3 切换 lap_view / 返回主秒表界面
- K4 退出秒表页回到主界面
-
倒计时(
countdown_app)- K1:短按进入/推进编辑/开始;长按快速开始或退出编辑
- K2/K3:在编辑中调整当前位;在查看中改变预设或控制选择
- K4 退出或隐藏页面
-
闹钟(
alarm_app)- 支持编辑闹钟小时/分钟、启用/禁用闹钟
- 闹钟到时会触发响铃(蜂鸣器),任意按键可关闭响铃;K4 可同时退出页面
-
设置(
settings_app)- K2/K3: 上/下选择项目
- K1: 切换当前选项(ON/OFF),同时保存到 Flash(与闹钟/设置信息一并)
- K4: 退出设置页
下面对各个模块的行为、UI 交互和关键流程做更详细的说明,帮助开发者与测试人员理解状态转换、按键响应、和与外设(RTC/GPS/Flash)交互的期望行为。
- 屏幕基于帧缓冲(
s_lcd_buffer)设计,绘图操作写入缓冲后通过display_flush_rows(row_start, row_end)将行数据写入 LCD。仅刷新需要更新的行以降低开销。 - 状态栏顶部绘制(时间/标题/温度)以较短时间间隔采样;子应用在刷新时继续调用
status_bar_render(now_ms, title)来维护一致显示。 - 主界面、功能页与子应用切换通过
main.c的状态机实现(APP_MODE_*)——主循环调用loop()、background_tick(),处理页面间的进入/退出。
- 每 500ms 采样一次 RTC 时间用于小字时间更新(显示 MM:SS),每 5s 采样一次温度。
- 仅当时间/温度/标题变化或被标记为 force redraw 时才刷新(节约 IO)。
- 布局:顶部状态栏;日期行(16 px 高);时间区(大字 12x24 或 8x16 带冒号);底部功能行(4 个图标,16x16 或 24x24)。
- 进入方式:程序启动默认进入主时钟界面;按 K4 可在日期→时间→功能 行间切换光标。
- 光标与编辑:
- 初始:光标默认在日期行(
CURSOR_LINE_DATE),不可见直到任何键操作触发光标可见。 - 光标闪烁:编辑模式下每 400ms 切换一次;若 5 秒无按键活动,光标超时并隐藏,回到默认(日期)位置。
- 进入编辑:光标在日期/时间时按 K1 进入编辑。K1 还用于推进编辑步(从日的个位向更高位推进,或从秒向分/时推进)。
- 编辑保存/撤销:K1 长按(或在编辑过程中走完最后一步)保存并应用编辑;K4 短按在编辑模式下退出并不保存(K4 长按同样退出不保存)。
- 时间/日期限制:通过
days_in_month、is_leap_year等函数保证范围有效,增减支持环绕(例如小时 23 + 1 → 0)并会在修改分钟时同步调整日期(如跨天)。
- 初始:光标默认在日期行(
- 可选功能:秒表、倒计时、闹钟、设置(图标顺序以
kFeatureIcons定义)。 - 查看模式在功能区时,K2/K3 用于左右切换选中图标,K1 短按进入选中功能,K1 长按同样进入。K4 短按在功能区循环回到日期(上一级)。
- 状态:Stopped / Running / Paused。UI 支持主显示时间与圈次视图。
- K1:短按开始/停止;长按重置到 0。
- K2:短按记录 Lap(在主屏),在 Lap View 中用于向前翻页;长按在主屏用于清空所有圈次。
- K3:切换 Lap View(查看圈次列表)/返回主秒表界面。
- K4:退出秒表界面返回主时钟。
- 支持 6 位编辑(
hh:mm:ss的拓展 / 以秒为单位),4 个预设(1 小时、30 分钟、10 分钟、3 分钟)。 - 状态:Idle / Running / Finished。
- K1:短按进入编辑/推进编辑或在运行状态下暂停/恢复;K1 长按在 Idle 可快速开始或在编辑时保存并开始。
- K2/K3:左右切换或增加/减少当前位;在选择预设时 K1 可直接触发预设快速开始。
- 倒计时完成音响并显示 Finish UI,任意键停止声音;若隐藏页面(K4)则可继续倒计时/结束动作仍然会触发警报。
- 支持编辑
hour:min与启用/禁用,保存会写入flash_storage(与 settings 一并保存)。 - 当达到闹钟时间(秒 == 0)且开启时,如果未在当日触发(
last_trigger_token并发保护),会进入 Ringing 状态并启动蜂鸣器连续方波。任意按键停止响铃并返回配置界面;K4 键还会触发退出事件。
- 支持项:整点报时(Hourly Chime),GPS 授时(GPS Sync)。
- 通过 K2/K3 改变选中行,K1 短按切换 ON/OFF 并保存(写入 flash)并将闹钟/设置信息同步写入持久化槽。
- RTC 采用软件 bit-banged I2C(
PB13/PB14),读写时间与温度(温度返回值以 0.01°C 为单位)。 - GPS 解析 RMC 报文并仅提取 UTC 时间(应用层可决定是否做时区偏移)。GPS 授时由
settings中gps_sync开关控制,且在clock_app中每 2 小时以g_next_gps_sync_ms驱动查询/同步。 - 在
clock_app中,RTC 同步优先在g_next_rtc_sync_ms周期进行,失败则按重试间隔处理。
- 采用双槽(A/B)策略:探测 flash 尺寸、页大小后分配两个槽;写入时先擦除备用槽并写入带 CRC 校验的 blob,确保原子更新。
flash_storage_blob_t中保存:magic、version、crc、hourly_chime、gps_sync、alarm_hour、alarm_minute、alarm_enabled等字段。
- 按键使用轮询采样(
KEYPAD_SAMPLE_MS= 2ms),消抖(KEYPAD_DEBOUNCE_MS= 10ms),短按最小值(20ms),长按判定(300ms)并支持长按重复(500ms 重复周期)。 - 按键事件通过
keypad_take_events(KEYPAD_Kn)取出并清零,事件标志KEYPAD_EVENT_SHORT/KEYPAD_EVENT_LONG并支持重复长按事件上报。
- 支持短时蜂鸣(timed)和连续方波(continuous square wave)。
- 定时或方波的持续/停止由
buzzer_task(now_ms)驱动;闹铃或倒计时警报使用连续方波,短按提示音使用定时蜂鸣。
- 设置当前时间(本地调整)
- K4 多次短按使光标切换到“时间”行
- K1 短按进入时间编辑
- K2/K3 调整当前高/低位(K1 每次短按推进到下一位)
- K1 长按或在最后一位随后 K1 提交编辑(保存并写入 RTC);K4 在编辑时短按可放弃编辑
- 设置闹钟
- K4 多次短按使光标切换到 Feature 区,并用 K2/K3 选中 Alarm 图标
- K1 进入闹钟 App,K1 再次进入编辑;K2/K3 增减小时/分钟;K1 提交保存并写入 Flash
- 在闹钟触发时,任意按键停止响铃;K4 在触发时会同时触发退出
以下是项目中主要模块与职责:
main.c:系统初始化、APP 入口、模式切换(APP_MODE_*)、全局 background tick 与事件循环bsp_systick.c:系统定时(毫秒级)用于按键去抖、动画、tick 等display.c:ST7920 LCD 的驱动与帧缓冲,提供绘制 API(像素/位图/字形等),并实现display_flush_rows刷写函数fonts.c:字模资源(8x16、12x24、16x16、4x16 等),主要用于界面绘制keypad.c:按键去抖、短长按识别与事件队列(按键事件由keypad_take_events取走)buzzer.c:基于 GPIO 的蜂鸣器(支持短时维持与方波)rtc_ds3231.c:DS3231 的 bit-banged I2C 驱动(PB13,PB14);支持读取/写入时间、温度gps_gtu8.c:通过USART0解析 NMEA(RMC)消息,提取 UTC 时间(应用层自行做时区/本地化)flash_storage.c:双槽原子写入策略,保存flash_storage_blob_t(包含hourly_chime,gps_sync, alarm info)- 子应用 (
*_app.c):每个应用实现自己的init,enter,loop,exit,background_tick接口,便于主循环切换。
项目默认使用 Keil/MDK 构建 (Project 文件位于 Project/):
- 打开
Project/MyGD32F30X.uvprojx(Keil uVision5) - 选择目标芯片(GD32F30x)并配置工具链
- 编译(按 F7 或 Build)
- 烧录:使用 Keil flash 配置或你使用的设备(例如 ST-Link、J-Link)
VS Code / EIDE 任务:
- Workspace 中包含 VSCode 任务(可通过
Terminal->Run Task):build:构建工程flash:烧录到设备build and flash:编译并烧录rebuild:clean + buildclean:清理构建
注意:Keil 工程(.uvprojx)里定义了 Target/Linker/Upload 设置;若要使用命令行编译,请使用 Keil 提供的 nrf 或 armcc 工具链/包装。
本仓库使用 EIDE(或 EIDE 插件)作为默认的构建工具链,工程在 build/ 目录下包含用于 EIDE 的 builder.params、compile_commands.json 等配置文件:
- 打开 VSCode 工作区(
GD32F303Clock.code-workspace)。 - 通过
Terminal -> Run Task运行以下预定义任务(在工作区中):build:仅编译项目flash:把编译产物烧录到设备build and flash:编译并烧写rebuild:重新构建(clean + build)clean:清理生成文件
注意:
Project/*.uvprojx提供 Keil/MDK 兼容性(如果你要在 Keil 中打开工程)。在本项目中uvprojx文件通常没有必要显式配置头文件包含路径,因为 EIDE 的构建参数(builder.params、compile_commands.json)负责包含目录、宏等构建环境。换言之,uvprojx对于 EIDE 构建不是必需的(但保留做兼容与 Keil 工具链使用)。
若你想在 Keil 中编译,请按需在 uvprojx 中手动配置 include 目录与编译宏。
flash_storage.c提供flash_storage_init/flash_storage_load/flash_storage_save接口。- 保存内容为
flash_storage_blob_t(定义在flash_storage.h),包含:magic、version、hourly_chime、gps_sync、alarm_hour、alarm_minute、enabled、crc 等字段。 - 采用双槽(A/B)策略实现原子更新,支持回滚。
- 调试串口:当前项目未包含通用日志输出接口。若需要打印调试信息,请使用
usart(例如USART1)或板上调试器。 - 修改配置(例如时区或默认闹钟)可在
clock_app.c中更改TIME_ZONE_OFFSET或alarm_app默认时间。 - 增加图标或语言:
fonts.c提供字模数组,若要添加汉字或图标请在fonts.c与fonts.h中添加相应的数据。 - 如果遇到 LCD 花屏/不刷新的情况,请先检查
display_init中的复位与 GDRAM 清屏序列。 - DS3231 I2C 时序使用软件 bit-banged;若需要改用硬件 I2C,请将底层实现替换到
rtc_ds3231.c或添加条件编译。
- 代码风格:模块化、每个应用拥有自己的
init/enter/loop/exit;无需绕过主循环 - 提交:请在提交中写清变更点,尽量在每个子模块上添加注释与单元测试(若可行)
- 新特性:添加新的 UI 页或外设时,建议遵循现有模块化接口与 UI 事件模型
Q: 为什么显示模糊/图标残留?
- 检查
display_flush_rows是否正在刷新对应区块;如需强制刷新使用display_flush_rows(0, DISPLAY_HEIGHT - 1)。
Q: DS3231 读取失败或时间不对?
- 检查
PB13/PB14是否正确连接到 RTC;并确保 I2C pull-up 电阻存在。 - GPS 授时依赖
config.gps_sync开关。
Q: 如何修改默认时间/闹钟?
- 修改
alarm_app的默认值(alarm_reset_state)或使用settings_app的保存机制在运行时做配置。
- 作者/维护者:Steven
- 硬件参考:GD32F30x 数据手册、ST7920 LCD、DS3231 RTC、GT-U8 GPS 模块
- 感谢 zhdzhd 提供的字模取模软件 PCtoLCD2018
- 感谢 Gemini 提供技术支持和问题分析
以下表格总结了在不同页面下 K1..K4 的典型行为:
| 应用 | K1 | K2 | K3 | K4 |
|---|---|---|---|---|
| 主界面(Clock) | 短按:进入编辑/启动选中功能;长按:提交编辑 | 视情况:编辑增加/选择功能左移 | 视情况:编辑减少/选择功能右移 | 短按:切换光标行(日期→时间→功能);长按:进入表盘 |
| 秒表(Stopwatch) | 短按:开始/停止;长按:重置 | 短按:在非 lap_view 时添加圈次;在 lap_view 时翻页 | 切换 lap_view / 返回主秒表界面 | 退出页面 |
| 倒计时(Countdown) | 短按:进入/推进编辑/开始;长按快速开始或退出编辑 | 编辑时增加位/预置左(或切换) | 编辑时减少位/预置右(或切换) | 隐藏页/退出 |
| 闹钟(Alarm) | 短按:进入编辑/切换启用;长按:用于退出编辑 | 编辑时增加当前位/切换 | 编辑时减少当前位/切换 | 短按:退出/确认;长按:退出并返回 |
| 设置(Settings) | 切换选项(ON/OFF,并保存) | 上移选项 | 下移选项 | 退出设置 |
小贴士:K1 为“确认/进入/切换”的惯用键,K2/K3 为“增/减或左/右”的操作,K4 为“返回/切换行/进入表盘”。