概述
OV-Watch 是一款功能齐全、节能的智能手表,具有丰富的功能。该项目为具有广泛功能的可穿戴设备提供完整的固件和硬件设计。
主要功能包括:
- 环境监测(温度、湿度、海拔高度)
- 健康跟踪(心率、步数)
- 导航工具(指南针)
- 实用应用程序(计算器、秒表、日历)
- 娱乐(2048 和 Memory Game 等游戏)
- 用于无线固件更新的蓝牙连接
- 具有多种电源模式的节能运行
系统架构
OV-Watch 遵循分层架构模式,可分离关注点并提高可维护性。
特征 | 描述 |
---|---|
显示 | 1.69 英寸触控屏幕 |
单 片 机 | STM32F411CEU6 微控制器 |
运行状况监控 | 心率、计步 |
环境传感器 | 温度、湿度、海拔 |
连接 | 通过 KT6368A 模块进行蓝牙 |
电源管理 | 三种电源模式(运行、睡眠、关机) |
充电 | 磁性充电连接器 |
存储 | 用于设置和数据的外部 EEPROM |
用户界面 | 基于 LVGL v8.2 的图形系统 |
操作系统 | 用于任务管理的 FreeRTOS |
系统架构概述
硬件层
硬件层包括:
- STM32F411CEU6 微控制器:中央处理器
- 显示器和触摸屏:用户界面组件
- 传感器:
- MPU6050:运动跟踪和计步
- EM7028:心率监测
- AHT21:温度和湿度传感
- 气压计:海拔测量
- 电子罗盘
- KT6368A 蓝牙模块:无线连接
- 电源管理电路:电池和电源控制
- 外部 EEPROM:持久数据存储
固件层
固件层包括:
- Bootloader:处理系统初始化和固件更新
- 应用程序固件:包含主要的 watch 功能
- 硬件抽象层 (HAL):提供硬件独立性
系统层
系统层提供作环境:
- FreeRTOS:用于任务调度的实时作系统
- LVGL:用于 UI 渲染的图形库(版本 8.2)
应用层
应用层实现面向用户的功能:
- FreeRTOS 任务:用于特定作的单独任务
- UI 屏幕:用户界面组件和屏幕
- 实用工具函数:整个应用程序中使用的常见作
启动过程和固件更新
OV-Watch 实现了一个引导加载程序系统,该系统支持通过蓝牙进行无线 (OTA) 更新。
引导流程
启动过程包括:
- 检查复位期间是否按下 KEY1 进入 bootloader 模式
- 在 bootloader 模式下,启用 Bluetooth 以接收固件
- 验证和刷写新固件
- 跳转到应用程序代码(如果有效)
- 初始化 HAL、FreeRTOS 和应用程序任务
从 2.4.0 版本开始,固件已拆分为单独的 Bootloader 和 Application 组件,以支持 OTA 更新。
硬件抽象层
硬件抽象层 (HAL) 为硬件组件提供一致的接口,从而简化测试和可移植性。
HAL 结构
HAL 使用接口类型来抽象化硬件访问:
HW_InterfaceTypeDef
:包含所有硬件接口的主接口结构- 每个组件的硬件特定接口(RTC、IMU、AHT21 等)
HWDataAccess.c
基于条件编译实现这些接口
条件编译标志允许相同的代码在硬件和模拟环境中运行。HW_USE_HARDWARE
FreeRTOS 任务结构
OV-Watch 使用 FreeRTOS 进行任务管理和调度,多个任务处理系统功能的不同方面。
任务组织
主要任务包括:
HardwareInitTask
:初始化硬件组件KeyTask
:处理按键/按钮输入ScrRenewTask
:根据输入和传感器数据更新 UISensUpdateTask
:读取和处理传感器数据RunModeTasks
:处理特定的作模式DataSaveTask
:管理到 EEPROM 的数据持久性ChargCheckTask
:监控电池和充电状态MessageSendTask
:处理消息发送作
电源管理系统
OV-Watch 实现了一个复杂的电源管理系统,具有三种主要状态,以最大限度地延长电池寿命。
电源管理状态
电源管理系统包括:
-
运行模式:
- 所有组件均处于活动状态的正常作
- 电流消耗:70-80mA
-
睡眠模式:
- MCU 处于 STOP 模式
- MPU6050 保持活动状态以进行步数计数
- 显示器已关闭
- 消耗电流:约 800μA
- 在 RTC 中断或检测到手腕抬起时转换为 Running 模式
-
关机模式:
- TPS63020 已禁用电源调节器
- 3.3V 电源已关闭
- 只有 RTC 仍由电池供电
- 最小电流消耗
- 长按 KEY1 激活,通过电源键退出
从 2.4.1 版本开始,睡眠模式通过取消初始化 UART I/O 端口来降低功耗。
UI 导航系统
OV-Watch 使用基于堆栈的方法进行 UI 导航和屏幕管理,从而实现直观的用户交互。
UI 导航流程
UI 导航系统使用堆栈数据结构来跟踪导航历史记录:
- 进入新屏幕时,其指针使用
user_Stack_Push()
- 返回按钮可弹出堆栈并返回到上一个屏幕
user_Stack_Pop()
- 这可以通过适当的后退按钮功能实现直观的导航
README.md 中的导航代码片段示例:
//key1 pressed
if(keystr == 1)
{
user_Stack_Pop(&ScrRenewStack);
if(user_Stack_isEmpty(&ScrRenewStack))
{
ui_MenuPage_screen_init();
lv_scr_load_anim(ui_MenuPage,LV_SCR_LOAD_ANIM_MOVE_RIGHT,0,0,true);
user_Stack_Push(&ScrRenewStack,(long long int)&ui_HomePage);
user_Stack_Push(&ScrRenewStack,(long long int)&ui_MenuPage);
}
else if(ScrRenewStack.Data[ScrRenewStack.Top_Point-1] == (long long int)&ui_HomePage)
{
ui_HomePage_screen_init();
lv_scr_load_anim(ui_HomePage,LV_SCR_LOAD_ANIM_MOVE_RIGHT,0,0,true);
}
}
开发环境
OV-Watch 代码库支持在 Keil MDK 中开发实际固件,并支持在 Windows 上开发基于 VSCode 的 LVGL 模拟器进行 UI 开发。
LVGL 模拟器
LVGL 模拟器允许在没有物理硬件的情况下进行 UI 开发和测试。它在文件夹中进行配置,可以更快地进行 UI 原型设计和测试。lv_sim_vscode_win
对于 UI 应用程序开发和迁移:
- 在 LVGL 模拟器中开发和测试 UI 组件
- 将 and 文件夹复制到 Keil 工程的文件夹中
Func
GUI_App
User
- 设置标志以控制是使用实际硬件还是模拟接口
HW_USE_HARDWARE
数据存储
OV-Watch 使用外部 EEPROM 进行持久数据存储。该文件管理对设置和配置参数的此存储的读取和写入数据。Datasave.c
存储系统用于:
- 用户设置和首选项
- 校准数据
- 配置参数
- 史料
系统架构
系统架构
相关源文件目的和范围
本文档介绍了 OV-Watch 的整体系统架构,OV-Watch 是一个基于 STM32F411CEU6 微控制器的智能手表项目。它提供了硬件和软件组件及其交互的高级概述。有关特定组件的详细信息,请参阅 硬件组件 和 软件架构 页面。
架构概述
OV-Watch 采用分层架构,将硬件、系统软件和应用程序组件之间的关注点分开。这促进了整个系统的模块化、可维护性和代码可重用性。
代码组织
代码库遵循反映系统架构的结构化组织:
目录 | 描述 |
---|---|
Core/ | STM32CubeMX 生成的核心系统文件 |
BSP/ | 用于硬件组件的板级支持包 |
Middlewares/ | 第三方库,包括 FreeRTOS 和 LVGL |
User/ | 特定于应用程序的代码 |
User/Tasks/ | FreeRTOS 任务实现 |
User/Func/ | 实用程序和帮助程序函数 |
User/GUI_App/ | 基于 LVGL 的 UI 组件和资源 |
lv_sim_vscode_win/ | 用于在 PC 上进行 UI 开发的 LVGL 模拟器 |
内存映射
STM32F411CEU6 具有 OV-Watch 固件的特定内存布局,引导加载程序和应用程序代码有单独的部分:
应用程序的 vector table 是 offset 以匹配此内存组织:
SCB->VTOR = 0x0000C000U; // Set in main.c
FreeRTOS 任务结构
该应用程序围绕多任务架构构建,使用 FreeRTOS 来管理不同的功能:
任务描述:
任务 | 文件 | 目的 |
---|---|---|
HardwareInitTask | user_HardwareInitTask.c | 初始化硬件组件 |
KeyTask | user_KeyTask.c | 处理按键/按钮输入 |
ScrRenewTask | user_ScrRenewTask.c | 更新屏幕显示 |
SensUpdate任务 | user_SensUpdateTask.c | 收集和处理传感器数据 |
RunModeTasks | user_RunModeTasks.c | 管理作模式 |
DataSaveTask (数据保存任务) | user_DataSaveTask.c | 处理对 EEPROM 的数据持久性 |
ChargCheckTask | user_ChargCheckTask.c | 监控充电和电池状态 |
MessageSendTask | user_MessageSendTask.c | 管理消息通信 |
硬件抽象层
硬件抽象层 (HAL) 提供了一个统一的接口来访问硬件组件,从而实现了代码可移植性和更简单的测试:
HAL 在 HAL 中实现并包含条件编译,以支持硬件和模拟环境:HWDataAccess.c
#if HW_USE_HARDWARE
// Hardware implementation
#else
// Simulation implementation
#endif
此设计允许在实际设备和 LVGL 仿真器上运行相同的应用程序代码。
启动过程和固件更新
引导过程从检查特定条件开始,以确定是进入引导加载程序模式还是跳转到应用程序:
引导加载程序系统通过蓝牙启用无线 (OTA) 固件更新,无需物理连接即可更新手表。
电源管理系统
OV-Watch 实施三种电源模式以优化电池寿命:
主要电源管理功能:
- 运行模式:全系统运行,消耗 70-80mA
- 休眠模式:MCU 处于 STOP 模式,MPU6050活动以进行步数计数,消耗 ~800μA
- 关机模式:禁用TPS63020电源调节器,仅 RTC 由电池供电
MPU6050 实现需要自定义修改以降低 sleep 模式下的功耗。
UI 导航系统
用户界面使用基于堆栈的导航方法来管理屏幕转换:
导航系统在 堆栈中实现并使用堆栈来跟踪导航历史记录。导航逻辑的一个关键示例:PageManager.c
if(keystr == 1) { user_Stack_Pop(&ScrRenewStack); if(user_Stack_isEmpty(&ScrRenewStack)) { ui_MenuPage_screen_init(); lv_scr_load_anim(ui_MenuPage,LV_SCR_LOAD_ANIM_MOVE_RIGHT,0,0,true); user_Stack_Push(&ScrRenewStack,(long long int)&ui_HomePage); user_Stack_Push(&ScrRenewStack,(long long int)&ui_MenuPage); } else if(ScrRenewStack.Data[ScrRenewStack.Top_Point-1] == (long long int)&ui_HomePage) { ui_HomePage_screen_init(); lv_scr_load_anim(ui_HomePage,LV_SCR_LOAD_ANIM_MOVE_RIGHT,0,0,true); } }
UI 组件在 中找到的文件中实现,例如 、 等。User/GUI_App/Screens/Src/
ui_HomePage.c
ui_MenuPage.c
用于开发的 LVGL 仿真器
OV-Watch 包括一个 LVGL 模拟器,用于在 PC 上进行 UI 开发:
UI 开发工作流程的关键方面:
- 在 LVGL 模拟器 ( 目录) 中开发和测试 UI 更改
lv_sim_vscode_win
- 将修改后的文件从 和 文件夹复制到 Keil 项目
Func
GUI_App
- 为目标环境设置适当的值
HW_USE_HARDWARE
这种方法允许快速 UI 开发,而无需为每次更改部署到硬件。
数据存储
OV-Watch 使用外部 EEPROM 进行持久数据存储:
实现细节可以在该文件中找到,该文件提供了向 EEPROM 读取和写入数据的功能。Datasave.c
结论
OV-Watch 系统架构采用结构良好的模块化设计,在硬件、系统软件和应用程序层之间明确分离了关注点。关键架构元素包括:
- 将硬件、系统软件和应用程序问题分开的分层方法
- 用于任务管理和调度的 FreeRTOS
- 用于 UI 开发的 LVGL
- 用于代码可移植性的硬件抽象层
- 基于堆栈的 UI 导航
- 用于电池优化的三级电源管理系统
- OTA 固件更新功能
- 用于 UI 开发的模拟环境
此设计增强了可维护性,实现了功能可扩展性,并通过高效的电源管理优化了电池寿命。
硬件组件
概述
OV-Watch 围绕 STM32F411CEU6 微控制器构建,具有多个传感器,用于环境数据收集、健康监测和运动跟踪。该硬件专为低功耗而设计,具有不同的作模式,以最大限度地延长电池寿命。硬件抽象层为应用程序提供了与物理组件交互的标准接口。
核心硬件
STM32F411CEU6 微控制器
OV-Watch 的核心是 STM32F411CEU6 微控制器,即 ARM Cortex-M4 处理器,具有:
- 32 位架构
- 100MHz 工作频率
- 512KB 闪存
- 128KB SRAM
- 多个通信接口(I2C、SPI、UART)
- 低功耗模式可延长电池寿命
该微控制器处理所有处理、传感器数据收集、用户界面渲染和通信任务。
电源管理系统
电源系统采用TPS63020电源管理芯片,具有三种不同的工作模式:
关键的电源功能在 中实现,包括:HWDataAccess.c
- 电源初始化
- 电源状态检测
- 电池电量计算
- 电源关闭
外部 EEPROM
手表使用外部 EEPROM 进行持久数据存储,包括:
- 用户设置
- 校准数据
- 步数记录
- 系统配置
EEPROM 在硬件初始化序列中初始化,并提供跨电源周期持续存在的非易失性存储器。
输入/输出接口
显示和触控
OV-Watch 配备 1.69 英寸触摸显示屏,配备以下组件:
元件 | 描述 |
---|---|
LCD 显示屏 | 主可视化界面 |
CST816 触摸控制器 | 电容式触摸传感器 |
LCD 背光源 | PWM 控制的亮度 |
显示和触摸界面作为硬件初始化序列的一部分进行初始化,LCD 驱动程序为 LVGL 图形库提供绘图功能。
按钮
手表包括用于用户输入的硬件按钮,与触摸界面相得益彰。密钥初始化在引导序列的早期执行,以确保即使在显示器不可用时也能进行系统控制。
传感器套件
OV-Watch 包括一套用于各种测量的传感器:
MPU6050惯性测量单元
MPU6050 用于:
- 计步
- 移动侦测
- 手腕位置检测,用于显示屏唤醒
在硬件接口中实现的主要功能:
- 跨睡眠周期的步数跟踪持久性
- 手腕状态检测(向上/向下)
- 用于睡眠模式的低功耗配置
AHT21 温湿度传感器
AHT21 传感器提供环境数据:
- 测温
- 湿度测量
该传感器对于手表的环境数据显示功能非常重要。
SPL06-001 气压计
气压计传感器提供高度信息和大气压力数据。
LSM303 电子罗盘
LSM303 电子罗盘为罗盘功能提供方向信息:
- 磁场检测
- 方向计算
电子罗盘具有省电功能,不使用时进入睡眠模式。
EM7028 心率传感器
EM7028 传感器用于健康监测:
- 心率检测
- 血氧电位 (SPO2) 测量(标记为正在进行的工作)
与电子罗盘一样,心率传感器可以在不主动测量时进入睡眠模式以节省电量。
通信
KT6368A 蓝牙模块
KT6368A 蓝牙模块提供无线通信功能:
- 用于数据交换的蓝牙连接
- 支持 SPP (Serial Port Profile)
- OTA(无线)固件更新
蓝牙模块可以在不使用时禁用以节省电量。
硬件抽象层
OV-Watch 实现了一个硬件抽象层,为应用程序提供了一致的接口来访问硬件组件。该层将应用程序代码与特定的硬件实现隔离开来,使软件更易于维护和移植。
硬件接口是通过一个中央结构实现的,该结构提供对所有硬件组件的访问。HWInterface
Interface 组件 | 目的 |
---|---|
RealTimeClock 实时时钟 | 日期和时间管理 |
BLE 系列 | 蓝牙通信 |
权力 | 电源管理和电池监控 |
液晶显示器 | 显示控件 |
IMU | 运动和步数检测 |
AHT21 | 温度和湿度传感 |
晴雨表 | 大气压力和海拔高度 |
电子罗盘 | 方向感应 |
HR_meter | 心率和 SPO2 测量 |
硬件初始化过程
硬件初始化由 FreeRTOS 中管理,它对所有硬件组件的启动进行排序。HardwareInitTask
初始化过程遵循以下关键步骤:
- 设置核心外设(RTC、UART、PWM)
- 初始化电源管理和输入系统
- 设置具有重试机制的传感器以实现可靠性
- 从 EEPROM 加载设置和恢复状态
- 配置通信接口
- 初始化显示和触摸系统
- 设置图形库和用户界面
硬件条件编译
OV-Watch 代码库包括一个条件编译系统,允许在没有硬件的情况下测试 UI 组件。这对于在 PC 模拟器上进行开发和测试非常有用。
定义 | 目的 |
---|---|
HW_USE_HARDWARE | 用于启用/禁用所有硬件功能的主开关 |
HW_USE_RTC | 启用实时时钟功能 |
HW_USE_BLE | 启用 Bluetooth 功能 |
HW_USE_BAT | 启用电池/电源功能 |
HW_USE_LCD | 启用 LCD 显示功能 |
HW_USE_IMU | 启用运动传感器功能 |
HW_USE_AHT21 | 启用温度/湿度传感器功能 |
HW_USE_SPL06 | 启用气压计功能 |
HW_USE_LSM303 | 启用电子罗盘功能 |
HW_USE_EM7028 | 启用心率传感器功能 |
当设置为 0 时,硬件访问函数提供模拟数据,允许在没有物理硬件的情况下进行 UI 开发。HW_USE_HARDWARE
软件架构
概述
OV-Watch 软件架构基于分层设计,将硬件接口、作系统、UI 框架和应用程序逻辑分开。该系统在 STM32F411CEU6 微控制器上运行,并使用 FreeRTOS 进行任务管理,使用 LVGL 进行用户界面。这种模块化方法允许干净的抽象,使代码库可维护和适应性强。
系统堆栈
核心系统组件
FreeRTOS 集成
OV-Watch 固件基于 FreeRTOS 构建,提供任务调度、同步机制和任务间通信。主应用程序初始化 FreeRTOS 并启动调度程序,然后调度程序管理各种应用程序任务。
任务结构
该应用程序分为多个任务,每个任务负责手表功能的特定方面。这些任务通过共享数据结构和同步机制进行通信。
主要任务包括:
- HardwareInitTask:初始化硬件组件
- KeyTask:处理按键和触摸输入
- ScrRenewTask:更新屏幕并处理 UI 导航
- SensUpdateTask:从传感器收集数据
- RunModeTasks:管理不同的作模式
- DataSaveTask:处理数据持久性
- ChargCheckTask:监控电池和充电状态
- MessageSendTask:管理与外部设备的通信
硬件抽象层
硬件抽象层 (HAL) 为硬件组件提供一致的接口,使应用程序代码能够独立于特定的硬件实现。这是通过一组接口结构实现的,这些接口结构定义了不同硬件组件的 API。
该文件用作实现这些接口的中间层,有助于在不同硬件平台之间更轻松地移植。对于模拟环境,设置为 0 允许代码在没有实际硬件的情况下运行。HWDataAccess.c
HW_USE_HARDWARE
数据存储
OV-Watch 使用外部 EEPROM 进行持久数据存储,主要用于存储用户设置和配置。这是通过文件管理的。Datasave.c
用户界面系统
LVGL 集成
OV-Watch 的用户界面使用 LVGL(轻量级和多功能图形库)8.2 版。LVGL 提供了一组丰富的 UI 组件,并处理渲染、用户输入和动画。
LVGL 通过中间件层集成到项目中,并在分组中定义自定义字体和 UI 资产。GUI_FONT_IMG
UI 页面结构
OV-Watch UI 分为多个页面,每个页面代表不同的特性或功能。这些页面在 LVGL 中作为单独的屏幕实现,具有自己的初始化和事件处理功能。
GUI_App/Screens/Src
GUI_App/Screens/Inc
页面导航
OV-Watch 实施了一种基于堆栈的页面导航方法,允许用户返回到前面的页面。当用户导航到新页面时,当前页面将推送到堆栈上,当他们想要返回时,将从堆栈中弹出上一页。
以下代码片段显示了这种基于堆栈的导航的实现:
//key1 pressed
if(keystr == 1)
{
user_Stack_Pop(&ScrRenewStack);
if(user_Stack_isEmpty(&ScrRenewStack))
{
ui_MenuPage_screen_init();
lv_scr_load_anim(ui_MenuPage,LV_SCR_LOAD_ANIM_MOVE_RIGHT,0,0,true);
user_Stack_Push(&ScrRenewStack,(long long int)&ui_HomePage);
user_Stack_Push(&ScrRenewStack,(long long int)&ui_MenuPage);
}
else if(ScrRenewStack.Data[ScrRenewStack.Top_Point-1] == (long long int)&ui_HomePage)
{
ui_HomePage_screen_init();
lv_scr_load_anim(ui_HomePage,LV_SCR_LOAD_ANIM_MOVE_RIGHT,0,0,true);
}
}
请务必注意,将 UI 页面推送到堆栈上时,将使用页面的地址而不是页面本身,因为页面对象可能会动态更改。
电源管理
OV-Watch 实施了复杂的电源管理系统,以优化电池寿命。它有三种主要的作模式:

- 运行模式:功能齐全的正常运行
- 睡眠模式:功能受限的低功耗模式(计步)
- 关机模式:除 RTC 外完全关闭电源
在睡眠模式下,MCU 进入 STOP 模式以降低功耗。MPU6050 对于步数保持活动状态。最初,该项目尝试使用 MPU6050 的运动检测功能进行唤醒,但这需要太多的运动才能可靠地触发。相反,最终实现使用 RTC 周期性中断来检查手腕位置并确定何时唤醒。
MPU6050 配置需要特殊修改以降低功耗,因为直接使用 DMP 库会导致高功耗。
在最新版本 (V2.4.1) 中,通过取消初始化 UART I/O 端口并将其设置为睡眠期间的输入,降低了睡眠模式的功耗。
产品特性
计算器实现
计算器功能使用两个堆栈(一个用于数字,一个用于运算符)实现经典算法来计算数学表达式。
该算法通过以下方式处理表达式:1+2*6/3
- 从左到右解析表达式
- 遇到数字时,将其推到数字堆栈中
- 遇到作员时:
- 如果 Operator 堆栈为空,则将 Operator 推到其上
- 如果当前运算符的优先级高于堆栈的顶部,则将其
- 如果当前运算符的优先级较低或相等,则弹出两个数字和一个运算符,执行作,然后将结果推回
- 继续,直到处理完整个表达式
- 评估任何剩余作
此算法正确处理运算符优先级(加/减前乘/除),并且可以处理具有浮点数的表达式。
开发和模拟
OV-Watch 项目包括 Visual Studio Code 中适用于 Windows 的 LVGL 模拟器配置,允许开发人员在不部署到硬件的情况下测试 UI 更改。

该文件用作中间层,允许代码在模拟器和实际硬件上运行。设置为 0 将使代码在模拟模式下运行。HWDataAccess.c
HW_USE_HARDWARE
要将 UI 更改从仿真器移植到硬件,开发人员可以将 and 文件夹从仿真项目复制到 Keil 项目中的文件夹。Func
GUI_App
User
内存布局
OV-Watch 固件以特定的内存布局进行组织,以同时容纳 bootloader 和应用程序固件:
应用程序固件位于地址 0x800C000,如 Keil 项目设置和 main.c 文件中所指定:
//this must set same as keil setting
SCB->VTOR = 0x0000C000U;
这种内存布局允许 bootloader 在不影响自身的情况下更新应用程序固件,从而实现无线 (OTA) 更新。
结论
OV-Watch 软件架构展示了一个设计精良的嵌入式系统,可有效平衡功能、性能和功耗。分层方法在硬件、作系统、UI 和应用程序逻辑之间明确分离,使代码库可维护和可扩展。
关键的架构选择包括:
- 使用 FreeRTOS 进行任务管理和调度
- 实现硬件抽象层以实现硬件独立性
- 采用 LVGL 实现丰富且响应迅速的用户界面
- 使用基于堆栈的方法进行 UI 导航
- 实施复杂的电源管理策略
- 为 OTA 更新提供引导加载程序
这种架构使 OV-Watch 能够在保持良好电池寿命的同时提供丰富的功能,使其成为一个功能强大且实用的智能手表平台。
Bootloader 系统
Bootloader 系统是 OV-Watch 固件架构的关键组件,它使设备能够正常启动并促进固件更新,而无需与编程器进行物理连接。本文档介绍了 Bootloader 的整体结构、作,以及用于通过蓝牙更新主应用程序固件(OTA 更新)的机制。
有关在 bootloader 之后运行的应用程序系统的信息,请参阅 Application System。
目的和概述
OV-Watch 引导加载程序有三个主要用途:
- 初始引导管理:确定是启动主应用程序还是进入更新模式
- 固件更新:通过蓝牙促进无线 (OTA) 更新
- 应用程序验证:在尝试执行应用程序之前验证它是否存在
bootloader 位于 flash 存储器的下部,是器件上电时执行的第一个代码。它包含通过蓝牙通信、接收新固件并将其闪存到适当内存位置的所有必要功能。
内存布局
引导加载程序和应用程序存储在 STM32F411 闪存的不同区域,以确保它们不会相互干扰。
OV-Watch 固件由两个二进制文件组成:
BootLoader_F411.hex
:位于基址的 bootloader 程序0x08000000APP_OV_Watch_V2.4.0.bin
:位于地址 0x08008000 的主应用程序
最初刷写设备时,两个二进制文件都需要单独编程。初始设置后,只需通过 Bootloader 的 OTA 功能更新应用程序固件。
启动过程
OV-Watch 的启动过程遵循一个顺序,该顺序确定是运行主应用程序还是进入引导加载程序菜单进行固件更新。
当设备开机或重置时,将启动过程。系统首先初始化硬件组件,例如时钟、GPIO 和通信外围设备。
检查是否按下 KEY1 按钮时,会出现一个关键决策点。如果按下,设备将进入 bootloader 模式以进行固件更新。如果不存在,它将尝试执行主应用程序,首先验证是否存在有效的应用程序签名。
为了验证应用程序的有效性,引导加载程序会检查地址 0x08008000 处的 “APP FLAG” 签名。如果缺少此签名,引导加载程序会显示一条错误消息,提示用户下载有效的应用程序。
Bootloader 菜单界面
在引导加载程序模式下运行时,OV-Watch 通过 UART/蓝牙连接显示菜单界面。此菜单提供了几个用于更新和管理固件的选项。
菜单选项包括:
- 下载映像:通过 Ymodem 协议接收新固件并将其刷入应用程序内存区域
- 上传图像:通过 Ymodem 协议发送当前应用程序固件(用于备份)
- 执行新程序:验证并跳转到应用程序
- 禁用写保护:如果启用,则删除闪存写保护
用户输入是通过 Bluetooth 连接接收的,通常使用支持 Ymodem 协议的终端程序(如 SecureCRT)。
固件更新过程
固件更新过程允许更新应用程序,而无需对设备的编程接口进行物理访问。
蓝牙连接
OV-Watch 使用 KT6368A 蓝牙模块进行通信。更新过程从以下方面开始:
- 开机时按 KEY1 进入 bootloader 模式
- 通过蓝牙将 OV-Watch 与计算机配对
- 使用支持 Ymodem 的终端应用程序(如 SecureCRT)连接到蓝牙 COM 端口
Ymodem 协议实现
引导加载程序利用 Ymodem 协议实现可靠的固件文件传输。该协议包括:
- 错误检测和纠正机制
- 数据包排序以确保正确顺序
- 重新传输损坏的数据包
- 支持传输文件名和大小
bootloader 中的 Ymodem implementation 处理这些数据包的接收,验证它们,并将数据写入适当的 flash memory 位置。
固件验证
在执行应用程序之前,引导加载程序会执行两个关键的验证步骤:
- APP FLAG 验证:检查地址 0x08008000 处的“APP FLAG”签名
- 跳转地址验证:确保跳转地址(0x08008004处)指向有效代码

如果验证成功,引导加载程序将:
- 禁用 SysTick 计时器和中断
- 检索应用程序的入口点地址
- 将处理器的堆栈指针设置为应用程序的堆栈
- 跳转到应用程序入口点
安全注意事项
引导加载程序包括一些基本的安全功能,以保护设备的固件:
- 物理访问要求:进入 bootloader 模式需要在开机期间物理按下 KEY1 按钮
- 应用程序签名验证:防止执行无效或损坏的固件
- Flash Write Protection:固件更新后启用写保护的选项
但是,请务必注意,当前实现不包括高级安全功能,例如:
- 固件签名或加密
- 使用基于硬件的信任根进行安全启动
- 防止计时攻击或其他侧信道漏洞
对于需要更高安全性的应用程序,需要实施额外的措施。
与应用程序集成
构建应用程序时必须考虑到特定注意事项才能与 bootloader 一起使用:
- 内存映射:必须编译应用程序才能从地址 0x08008000 运行
- APP FLAG 签名:应用程序必须在其开头包含“APP FLAG”签名
- Vector Table Offset:应用程序必须将其 Vector Table 重新定位到 0x08008000
无线(OTA)更新过程
本文档介绍了在 OV-Watch 中实施的无线 (OTA) 更新机制。此过程允许通过蓝牙无线更新应用程序固件,而无需拆卸手表或连接物理编程电缆。
1. OTA 更新系统概述
OV-Watch 实现了一个强大的 OTA 更新系统,该系统通过蓝牙连接实现固件更新,同时最大限度地降低设备变砖的风险。这是通过将固件分为两个不同的组件来实现的:
- Bootloader - 一种最小、稳定的固件,用于处理固件更新,很少被修改
- 应用程序 - 包含可安全更新的手表功能的主固件
执行 OTA 更新时,引导加载程序使用 YMODEM 协议通过蓝牙接收新的应用程序固件,对其进行验证,然后将其写入闪存的应用程序区域。
内存组织
2. 进入 Bootloader 模式
要启动 OTA 更新过程,必须将手表置于引导加载程序模式:
要进入 bootloader 模式:
- 按住 KEY1 按钮(上按钮)
- 按住 KEY1 的同时,打开手表电源(或连接电源)
- 继续按住 KEY1 直到出现 bootloader 菜单
如果在启动过程中未按下 KEY1,则系统检查有效的应用程序,如果找到,则直接跳转到应用程序固件。
3. OTA 更新流程
进入引导加载程序模式后,完整的 OTA 更新过程将遵循以下顺序:
4. Bootloader 菜单界面
当处于 bootloader 模式时,以下菜单通过 Bluetooth 串行连接显示:

菜单选项提供以下功能:
- 下载固件:启动 YMODEM 接收器以接受新的固件文件
- 上传固件:读取当前固件(很少使用)
- 执行应用程序:退出引导加载程序并运行应用程序固件
- 禁用写保护:删除闪存写保护(很少使用)
5. 使用 YMODEM 协议进行固件传输
OTA 进程使用 YMODEM 协议通过蓝牙实现可靠的文件传输:

6. 详细更新步骤
更新 OV-Watch 固件的分步过程如下:
-
准备固件文件:
- 获取最新的固件文件
APP_OV_Watch_vX.X.X.bin
- 获取最新的固件文件
-
进入 bootloader 模式:
- 手表关机时,按住 KEY1
- 按住 KEY1 的同时,打开手表电源
- 继续按住,直到 bootloader 激活
-
设置 Bluetooth 连接:
- 在计算机上,与手表的蓝牙(通常称为 KT6368A-SPP)配对
- 在系统设置中将蓝牙设备添加到 COM 端口
-
连接终端并执行更新:
- 打开 SecureCRT(或类似的终端程序)
- 连接到相应的 COM 端口
- 您应该会看到 bootloader 菜单出现
- 键入以选择固件下载
1
- 手表将显示 “Waiting for the file to be sent...” 并发送 “C” 字符
- 在 SecureCRT 中,选择“发送 Ymodem”,然后选择固件文件
.bin
- 等待文件传输完成(可能需要几分钟)
-
执行更新的固件:
- 传输成功后,键入以执行应用程序
3
- 手表将使用新固件重新启动
- 传输成功后,键入以执行应用程序
7. 安全注意事项
OTA 更新机制包括多项安全功能:
-
物理触发要求:手表需要按物理按钮才能进入引导加载程序模式,以防止未经授权的远程触发更新
-
应用程序验证:引导加载程序在跳转到应用程序之前验证应用程序的完整性
-
蓝牙配对:蓝牙连接需要配对,增加一层认证
-
YMODEM 验证:YMODEM 协议包括 CRC 验证,以确保传输过程中的固件完整性
8. OTA 更新故障排除
常见问题及其解决方案:
问题 | 可能的原因 | 溶液 |
---|---|---|
无法进入 bootloader | 未正确握住 KEY1 | 在开机前和开机时按住 KEY1 |
未找到 Bluetooth 设备 | 不要在 bootloader 模式下观看 | 按住 KEY1 重新启动 |
传输开始但失败 | 干扰或电池电量不足 | 确保手表已充电并靠近计算机 |
“Invalid Number” 错误 | 输入了错误的菜单选项 | 仅输入数字 1-4,如菜单所示 |
“APP Flag 错误” | 固件验证失败 | 重试传输或尝试其他固件文件 |
“验证失败” | 文件传输损坏 | 在更近的距离重试传输 |
9. 组件和代码架构
启用 OTA 更新系统的关键组件包括:

10. 内存布局和寻址
STM32F411 闪存的组织方式是为了支持 OTA 更新:
地区 | 起始地址 | 大小 | 描述 |
---|---|---|---|
引导加载程序 | 0x08000000 | 32 KB | 包含 bootloader 固件 |
应用 | 0x08008000 | ~480 KB | 包含应用程序固件 |
配置 | 0x080F8000 | 32 KB | 配置存储(可选) |
bootloader 占据 flash 内存的开头,紧随其后的是应用程序固件。此组织确保 bootloader 始终存在以处理固件更新和恢复。
应用程序内编程 (IAP)
应用程序内编程 (IAP) 是一种无需物理访问编程接口即可实现 OV-Watch 固件更新的机制。该系统允许用户通过蓝牙更新手表固件,即使它已经组装并密封在表壳中。IAP 系统概述
OV-Watch 中的 IAP 系统由两个主要组件组成:
- 位于闪存下部的引导加载程序
- 位于闪存上部的应用程序固件
引导加载程序负责跳转到应用程序或进入固件更新模式。该决定基于在启动期间是否按下某个键以及内存中是否存在有效的应用程序。
IAP 架构
IAP 系统架构内存布局
闪存组织
OV-Watch 固件组织在闪存中,引导加载程序从 0x08000000 开始占用较低的地址,而应用程序固件从 0x08008000 开始占用地址。在应用程序区域 (0x08008000) 的开头,将存储一个 “APP FLAG” 标识符,以验证是否存在有效的应用程序。
启动过程和 IAP作
当 OV-Watch 开机或重置时,引导加载程序首先执行,并确定是进入 IAP 模式还是执行应用程序。
引导流程

引导过程遵循以下步骤:
- 设备在 bootloader 处开始执行
- 引导加载程序检查是否按下了 KEY1 按钮
- 如果按下 KEY1,它将进入 bootloader 模式并显示 IAP 菜单
- 如果未按下 KEY1,则检查有效的应用程序
- 为了检查有效的应用程序,它会在地址 0x08008000 处查找 “APP FLAG”
- 如果存在该标志,它将跳转到应用程序
- 如果该标志不存在,则会显示错误并等待固件下载
跳转到应用程序时,引导加载程序:
- 禁用 SysTick 计时器和所有中断
- 从 0x08008004 获取应用程序入口点地址
- 将 Main Stack Pointer (MSP) 设置为 0x08008000 的值
- 跳转到应用程序入口点
固件更新过程
固件更新过程允许用户通过蓝牙将新固件上传到手表,而无需打开外壳。
固件更新过程

更新固件的步骤
-
进入 Bootloader 模式:
- 在手表开机时按住 KEY1 按钮
- 手表显示屏将显示 bootloader 屏幕
-
通过蓝牙连接:
- 在计算机上,与手表的 Bluetooth 配对
- 蓝牙设备将显示为“KT6368A-SPP”或类似设备
- 在 Windows 设置中将蓝牙连接添加为 COM 端口
-
使用终端程序:
- 打开 SecureCRT 并连接到蓝牙 COM 端口
- 将显示 bootloader 菜单
-
传输固件:
- 输入“1”以选择下载选项
- 手表将发送“CCCCCC...”以指示它已准备就绪
- 使用 SecureCRT 中的“发送 YModem”选项
- 选择应用程序二进制文件(例如,APP_OV_Watch_V2.4.0.bin)
- 等待传输完成(这可能需要一些时间)
-
执行应用程序:
- 转账成功后,输入 “3” 执行申请
- 手表将重置并启动至新固件
实现细节
IAP 系统的核心功能在 Bootloader 固件中实现。关键组件包括:
APP FLAG 验证
引导加载程序通过检查应用程序区域开头的 “APP FLAG” 来验证是否存在有效的应用程序:
APP FLAG 验证
引导加载程序通过检查应用程序区域开头的 “APP FLAG” 来验证是否存在有效的应用程序:
// Check if "APP FLAG" is present at 0x08008000
char combined_str[9];
memcpy(str1, &data1, sizeof(data1));
memcpy(str2, &data2, sizeof(data2));
str1[4] = '\0';
str2[4] = '\0';
strcpy(combined_str, str1);
strcat(combined_str, str2);
// Check if matches "APP FLAG"
if (strcmp(combined_str, "APP FLAG") == 0) {
// Valid application found, jump to it
}
-
目的:这段代码是引导加载程序(bootloader)的一部分,用于检查内存中特定位置(0x08008000)是否存在有效的应用程序标志。
-
变量说明:
data1
和:这两个变量(从代码上下文推断)存储从内存地址0x08008000读取的数据data2
str1
和:用于存储转换后的字符串数据str2
combined_str
:用于存储组合后的完整字符串(长度为9,包括结尾的'\0')
-
处理步骤:
- 使用将和的内容分别复制到和
memcpy
data1
data2
str1
str2
- 在和的第4个位置添加空字符'\0',将它们转换为正确终止的C字符串
str1
str2
- 使用将复制到
strcpy
str1
combined_str
- 使用将追加到后面,形成完整的字符串
strcat
str2
combined_str
- 使用比较是否等于字符串“APP FLAG”
strcmp
combined_str
- 使用将和的内容分别复制到和
-
判断逻辑:
- 如果比较结果为0(即字符串相等),表示在指定地址发现了有效的应用程序
- 在这种情况下,引导加载程序将跳转到应用程序的起始地址执行应用代码
跳转到 Application Mechanism
引导加载程序使用以下序列跳转到应用程序:
// Disable SysTick and interrupts
SysTick->CTRL = 0X00;
SysTick->LOAD = 0;
SysTick->VAL = 0;
__disable_irq();
// Get jump address and set stack pointer
JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4);
Jump_To_Application = (pFunction) JumpAddress;
__set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS);
Jump_To_Application();
IAP 菜单界面
引导加载程序通过 Bluetooth 连接提供一个简单的基于文本的菜单界面,具有以下选项:
选择 | 描述 |
---|---|
1 | 将映像下载到 STM32F4xx 内部闪存 |
2 | 从 STM32F4xx 内部闪存上传图像 |
3 | 执行新程序 |
4 | 禁用写保护 |
使用建议
-
更新前备份:更新前始终备份当前固件版本。
-
电池电量:在开始更新过程之前,请确保手表有足够的电池电量,以防止在闪烁时中断。
-
请勿中断:切勿在传输或刷新数据时中断更新过程,因为这可能会损坏固件。
-
恢复:如果更新失败且手表无法启动,您可以随时再次进入 bootloader 模式(在开机时按住 KEY1)并重试更新。
-
版本验证:完成更新后,通过检查手表设置中的版本信息来验证新版本是否正常运行。
故障 排除
-
蓝牙连接问题: 如果您无法通过蓝牙连接,请尝试重新配对设备或重新启动计算机的蓝牙服务。
-
YModem 传输失败:如果文件传输失败,请确保您使用的是正确的二进制文件格式,并尝试将手表放在更靠近计算机的位置以减少干扰。
-
手表无响应:如果手表在更新后无响应,请长按KEY1和KEY2执行硬重置。
-
无法进入 Bootloader:如果无法进入 Bootloader 模式,则可能是电池电量过低。为手表充电,然后重试。
硬件抽象层
目的和概述
硬件抽象层 (HAL) 在应用程序代码和 OV-Watch 的底层硬件组件之间提供一致的接口。这种抽象通过隔离特定于硬件的细节、实现代码可移植性以及促进无硬件仿真来简化软件开发。
OV-Watch HAL 经过专门设计,可使应用程序和 UI 代码在不同平台上移植。例如,开发人员可以通过单个配置参数禁用硬件访问,轻松地将项目移植到 PC 仿真。
HAL 架构
HAL 架构是围绕一组接口 typedef 构建的,这些接口定义每个硬件组件的合约结构。这些结构包含数据字段和函数指针,在特定于硬件的代码和应用程序逻辑之间创建一个内聚的抽象层。
条件编译机制
HAL 实现了一种条件编译机制,使开发人员能够轻松禁用硬件访问以进行模拟或测试。主控制标志是 ,各个组件标志是从它派生的。HW_USE_HARDWARE
#define HW_USE_HARDWARE 1
#if HW_USE_HARDWARE
#define HW_USE_RTC 1
#define HW_USE_BLE 1
#define HW_USE_BAT 1
#define HW_USE_LCD 1
#define HW_USE_IMU 1
#define HW_USE_AHT21 1
#define HW_USE_SPL06 1
#define HW_USE_LSM303 1
#define HW_USE_EM7028 1
#endif
当设置为 0 时,将禁用所有硬件访问,从而允许应用程序在没有硬件依赖性的仿真环境中运行。HW_USE_HARDWARE
硬件组件接口
HAL 为各种硬件组件提供接口,每个组件都有自己的一组函数和数据字段。以下是关键组件:
RTC 接口
Real-Time Clock 界面提供了获取和设置日期和时间以及计算工作日的功能。
typedef struct
{
void (*GetTimeDate)(HW_DateTimeTypeDef *nowdatetime);
void (*SetDate)(uint8_t year, uint8_t month, uint8_t date);
void (*SetTime)(uint8_t hours, uint8_t minutes, uint8_t seconds);
uint8_t (*CalculateWeekday)(uint8_t setyear, uint8_t setmonth, uint8_t setday, uint8_t century);
} HW_RTC_InterfaceTypeDef;
BLE 接口
Bluetooth Low Energy 接口提供启用和禁用 BLE 模块的功能。
typedef struct
{
void (*Enable)(void);
void (*Disable)(void);
} HW_BLE_InterfaceTypeDef;
电源管理接口
电源管理接口提供初始化电源管理、关闭系统和计算电池电量的功能。
typedef struct
{
uint8_t power_remain;
void (*Init)(void);
void (*Shutdown)(void);
uint8_t (*BatCalculate)(void);
} HW_Power_InterfaceTypeDef;
LCD 接口
LCD 界面提供控制显示的功能,包括调整背光亮度。
typedef struct
{
void (*SetLight)(uint8_t dc);
} HW_LCD_InterfaceTypeDef;
传感器接口
HAL 包括用于各种传感器的接口:
-
IMU 接口 (MPU6050):跟踪运动数据、步数和手腕方向。
-
AHT21 接口:测量温度和湿度。
-
气压计接口 (SPL06):测量大气压力和海拔高度。
-
电子罗盘接口 (LSM303):提供方向数据。
-
心率计界面 (EM7028):测量心率和血氧水平。
每个传感器接口都包括连接状态、测量数据、初始化函数和传感器特定作。
硬件初始化流程
硬件组件在系统启动时通过该函数进行初始化。此任务负责初始化所有硬件组件及其相应的 HAL 接口。HardwareInitTask
HAL 实现细节
HAL 实现包含在文件中,该文件提供了每个接口背后的实际功能。该实现包括条件编译块,这些块根据配置标志启用或禁用硬件访问。HWDataAccess.c
初始化和访问模式
每个硬件组件都遵循类似的初始化和访问模式:
- 检查硬件组件是否启用了相应的标志(例如
HW_USE_RTC
) - 如果启用,请调用相应的特定于硬件的函数
- 如果禁用,请提供合理的默认行为或返回值
RTC 组件示例:
void HW_RTC_Get_TimeDate(HW_DateTimeTypeDef * nowdatetime)
{
#if HW_USE_RTC
if (nowdatetime != NULL)
{
RTC_DateTypeDef nowdate;
RTC_TimeTypeDef nowtime;
HAL_RTC_GetTime(&hrtc, &nowtime, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &nowdate, RTC_FORMAT_BIN);
// Copy data to output structure
// ...
}
#else
// Provide default values when hardware is disabled
nowdatetime->Year = 24;
nowdatetime->Month = 6;
// ...
#endif
}
接口注册
HAL 将所有接口函数注册到整个应用程序都可以访问的全局变量中。此接口在文件末尾定义:HWInterface
HWDataAccess.c
HW_InterfaceTypeDef HWInterface = {
.RealTimeClock = {
.GetTimeDate = HW_RTC_Get_TimeDate,
.SetDate = HW_RTC_Set_Date,
.SetTime = HW_RTC_Set_Time,
.CalculateWeekday = HW_weekday_calculate
},
.BLE = {
.Enable = HW_BLE_Enable,
.Disable = HW_BLE_Disable
},
// ... other interfaces
};
用 HAL
应用程序代码通过全局变量访问硬件功能。此方法提供了一个一致的接口,无论实际的硬件实现如何,都可以在整个应用程序代码中使用。HWInterface
示例:访问 RTC
// Get current date and time
HW_DateTimeTypeDef datetime;
HWInterface.RealTimeClock.GetTimeDate(&datetime);
// Set the date
HWInterface.RealTimeClock.SetDate(23, 6, 15); // Year, Month, Day
// Set the time
HWInterface.RealTimeClock.SetTime(14, 30, 0); // Hours, Minutes, Seconds
示例:检查传感器连接状态
// Check if the IMU is properly connected
if (HWInterface.IMU.ConnectionError) {
// Handle connection error
} else {
// Use the IMU
uint16_t steps = HWInterface.IMU.GetSteps();
}
示例:电源管理
// Initialize power management
HWInterface.Power.Init();
// Get battery level
uint8_t batteryLevel = HWInterface.Power.BatCalculate();
// Shutdown the system
HWInterface.Power.Shutdown();
移植和模拟
HAL 旨在通过在特定于硬件的代码和应用程序逻辑之间提供明确的分离来促进移植和模拟。
禁用硬件进行仿真
要在没有硬件的模拟环境中运行应用程序,只需在 :HW_USE_HARDWARE
HWDataAccess.h
#define HW_USE_HARDWARE 0
这将禁用所有硬件访问,并使用 HAL 实现中定义的回退值和行为。
添加新的硬件组件
要添加对新硬件组件的支持:
- 在
HWDataAccess.h
- 将接口添加到结构体中
HW_InterfaceTypeDef
- 为组件添加新的条件编译标志
- 在
HWDataAccess.c
- 在全局变量中注册函数
HWInterface
总结
OV-Watch 中的硬件抽象层为访问硬件组件提供了一致的接口,促进了代码可移植性并简化了应用程序开发。通过抽象出特定于硬件的细节,HAL 使开发人员能够专注于应用程序逻辑,而不是低级硬件交互。
条件编译机制允许在硬件和模拟环境中使用相同的代码库,从而提高开发和测试的效率。每个硬件组件都有一个定义明确的接口,该接口封装了其功能和状态,从而在硬件和应用程序代码之间提供了一个内聚的抽象层。
传感器接口
概述
本页介绍了 OV-Watch 系统中的传感器接口,这些抽象支持应用程序代码与设备上的各种传感器之间的通信。这些接口提供了一种一致的方法来初始化、配置和读取智能手表传感器的数据,同时将应用程序逻辑与特定硬件实现解耦。
OV-Watch 包括多个传感器,用于跟踪健康指标、环境条件和运动检测。传感器接口是硬件抽象层 (HAL) 的一部分,如硬件抽象层页面所述。
传感器接口架构
传感器接口架构遵循硬件抽象模式,在应用程序代码和特定于硬件的实现之间提供了清晰的分离。这种设计使代码库更加模块化,更易于维护,并允许在没有实际硬件的情况下进行 UI 模拟。
支持的传感器
OV-Watch 系统支持多个传感器,每个传感器都有自己的接口,用于初始化和数据检索:
传感器 | 类型 | 目的 | 接口结构体 |
---|---|---|---|
MPU6050 | IMU | 计步、手腕检测 | HW_IMU_InterfaceTypeDef |
AHT21 | 温度/湿度 | 环境监测 | HW_AHT21_InterfaceTypeDef |
SPL06-001 | 晴雨表 | 海拔测量 | HW_Barometer_InterfaceTypeDef |
LSM303 系列 | 电子罗盘 | 测 | HW_Ecompass_InterfaceTypeDef |
EM7028 系列 | 心率传感器 | 心率和 SpO2 监测 | HW_HRmeter_InterfaceTypeDef |
可以通过在文件中设置相应的宏来有条件地启用或禁用这些传感器:HWDataAccess.h
#define HW_USE_IMU 1
#define HW_USE_AHT21 1
#define HW_USE_SPL06 1
#define HW_USE_LSM303 1
#define HW_USE_EM7028 1
传感器接口详细信息
IMU 接口 (MPU6050)
IMU 接口提供计步和手腕运动检测功能。
IMU 接口 () 包含:HW_IMU_InterfaceTypeDef
- 状态变量:连接状态、步数、手腕状态
- 用于初始化、启用/禁用手腕检测和计步作的功能
中的实现使用 MPU6050 的 DMP (数字运动处理器) 功能进行准确的步数计数和运动检测。HWDataAccess.c
温湿度接口 (AHT21)
AHT21 接口提供读取温度和湿度的功能。
typedef struct
{
uint8_t ConnectionError;
uint8_t temperature;
uint8_t humidity;
uint8_t (*Init)(void);
void (*GetHumiTemp)(float *humi, float *temp);
} HW_AHT21_InterfaceTypeDef;
调用该函数时,它使用 I2C 通信从 AHT21 传感器读取最新的温度和湿度值。GetHumiTemp
气压计接口 (SPL06-001)
气压计界面提供读取大气压力和计算海拔高度的功能。
typedef struct
{
uint8_t ConnectionError;
uint16_t altitude;
uint8_t (*Init)(void);
} HW_Barometer_InterfaceTypeDef;
高度值是根据 SPL06-001 传感器的压力读数计算的。
电子罗盘接口 (LSM303)
电子罗盘界面提供确定方向方向的功能。
typedef struct
{
uint8_t ConnectionError;
uint16_t direction;
uint8_t (*Init)(void);
void (*Sleep)(void);
} HW_Ecompass_InterfaceTypeDef;
指南针方向以度为单位,界面包含休眠功能,可在不使用指南针时节省电量。
心率计接口 (EM7028)
心率计界面提供读取心率和血氧饱和度 (SpO2) 的功能。
typedef struct
{
uint8_t ConnectionError;
uint8_t HrRate;
uint8_t SPO2;
uint8_t (*Init)(void);
void (*Sleep)(void);
} HW_HRmeter_InterfaceTypeDef;
该界面包括睡眠模式,可在不主动测量心率时节省电量,因为这些测量可能非常耗电。
主硬件接口结构
所有传感器接口都组合成一个结构,该结构实例化为全局变量 。此结构充当应用程序访问所有硬件接口的中心点:HW_InterfaceTypeDef
HWInterface
typedef struct
{
HW_RTC_InterfaceTypeDef RealTimeClock;
HW_BLE_InterfaceTypeDef BLE;
HW_Power_InterfaceTypeDef Power;
HW_LCD_InterfaceTypeDef LCD;
HW_IMU_InterfaceTypeDef IMU;
HW_AHT21_InterfaceTypeDef AHT21;
HW_Barometer_InterfaceTypeDef Barometer;
HW_Ecompass_InterfaceTypeDef Ecompass;
HW_HRmeter_InterfaceTypeDef HR_meter;
} HW_InterfaceTypeDef;
extern HW_InterfaceTypeDef HWInterface;
应用程序代码通过此全局结构访问传感器数据和功能,例如:
// Check if IMU is connected
if (!HWInterface.IMU.ConnectionError) {
// Get step count
uint16_t steps = HWInterface.IMU.GetSteps();
}
// Read temperature and humidity
if (!HWInterface.AHT21.ConnectionError) {
float temperature, humidity;
HWInterface.AHT21.GetHumiTemp(&humidity, &temperature);
}
传感器初始化过程
传感器初始化过程在 中进行,如果初始尝试失败,则尝试初始化每个传感器最多三次。为了省电,一些传感器(如电子罗盘和心率传感器)在成功初始化后会进入睡眠模式,直到需要它们为止。HardwareInitTask
传感器数据流
传感器数据流从硬件传感器收集原始数据开始,然后使用该结构通过硬件抽象层 (HAL) 访问这些数据。它会定期从所有启用的传感器读取数据并对其进行处理,以便在各种 UI 页面上显示。一些传感器数据也会传递给 进行持久存储。HWInterface
SensUpdateTask
DataSaveTask
错误处理
每个传感器接口都包含一个字段,用于指示传感器是否已正确连接和正常工作。此字段在初始化阶段设置,并在尝试从传感器读取数据之前选中。ConnectionError
每个传感器的初始化函数都会返回一个状态代码(通常为 0 表示成功),该代码存储在字段中。应用程序代码应在尝试使用传感器之前检查此字段:ConnectionError
// Example of proper error checking
if (!HWInterface.AHT21.ConnectionError) {
// Sensor is connected and working
float temperature, humidity;
HWInterface.AHT21.GetHumiTemp(&humidity, &temperature);
} else {
// Sensor is not connected or not working
// Handle the error or display a message
}
仿真支持
传感器接口包括对 UI 仿真的内置支持,无需实际硬件,这对于在 PC 平台上进行开发和测试非常有用。这由 中的宏控制。HW_USE_HARDWARE
HWDataAccess.h
当设置为 0 时,实现函数提供模拟数据值,而不是访问物理传感器。这允许 UI 开发和测试,而无需实际的 OV-Watch 硬件。HW_USE_HARDWARE
显示和输入接口
本页记录了 OV-Watch 的显示和输入接口,包括硬件组件、软件抽象以及它们如何与系统其余部分交互。它涵盖了 LCD 显示屏、触摸屏、物理按钮和处理用户交互的软件组件。有关传感器的信息。
1. 概述
OV-Watch 利用彩色 LCD 显示屏、电容式触摸屏和物理按钮的组合来实现用户交互。这些组件通过硬件抽象层进行管理,并与 LVGL 图形库集成以呈现用户界面。
显示和输入架构
2. 硬件组件
2.1. LCD 显示
OV-Watch 使用彩色 LCD 显示屏作为视觉界面。显示在系统启动期间初始化,并通过硬件抽象层进行控制。
主要特点:
- 通过 PWM 进行亮度控制
- 通过 LCD_Init 和相关功能直接初始化和控制
- 用于可移植性的硬件抽象
2.2. 触摸屏控制器 (CST816)
CST816 电容式触摸控制器提供触摸输入功能:
- 通过 CST816_GPIO_Init() 和 CST816_RESET() 初始化
- 通过输入设备端口与 LVGL 集成
- 提供手势识别功能
2.3. 物理按钮
OV-Watch 具有两个物理按钮(KEY1 和 KEY2),可提供额外的输入方法:
- KEY1:通常用于导航返回或菜单访问
- KEY2:上下文相关的功能,通常用于快速访问或取消
这些按钮由 KeyTask 扫描,事件通过消息队列发送到 ScrRenewTask。
3. 硬件抽象层
硬件抽象层 (HAL) 为显示和输入硬件提供一致的接口,允许应用程序与这些组件交互,而无需直接依赖。
3.1. LCD 接口
HAL 中的 LCD 接口最小,仅提供控制显示所需的功能:

LCD 接口包括:
SetLight()
:控制显示亮度的功能
这种抽象允许应用程序控制显示亮度,而无需直接访问硬件,从而增强了可移植性。
4. 图形系统和 LVGL 集成
OV-Watch 使用 LVGL(轻量级和多功能图形库)来渲染用户界面。LVGL 在系统启动期间进行初始化,提供一整套 UI 组件和渲染能力。
4.1. LVGL 初始化和显示端口
LVGL 的初始化顺序如下:
- LVGL 核心初始化
lv_init()
- 显示驱动程序初始化方式
lv_port_disp_init()
- 输入设备初始化方式
lv_port_indev_init()
- UI 初始化方式
ui_init()
显示端口将 LCD 硬件连接到 LVGL,配置:
- 颜色格式
- 分辨率
- 缓冲区管理
- Flush 回调将数据写入显示
5. 输入处理系统
5.1. 关键任务
KeyTask 持续扫描物理按钮并将事件发送到相应的消息队列:

KeyTask 处理:
- 扫描物理按钮
- 根据按钮和当前页面决定要通知的消息队列
- 将密钥代码(1 表示 KEY1,2 表示 KEY2)发送到相应的队列
5.2. 屏幕续订任务
ScrRenewTask 处理 Key_MessageQueue 中的关键事件,并相应地更新 UI:
此任务:
- 处理消息队列中的关键事件
- 根据按键在页面之间导航
- 离开特定页面时管理传感器状态
- 保持屏幕更新周期
6. 页面导航系统
OV-Watch 实现了一个基于堆栈的页面导航系统,允许用户在 UI 层次结构中移动。关键组件包括:
导航系统:
- 使用 KEY1 进行标准导航(返回一级)
- 使用 KEY2 快速返回主页
- 维护用于跟踪 UI 层次结构的导航堆栈
- 根据当前页面控制传感器状态
7. 显示更新机制
OV-Watch 中的显示更新遵循以下常规流程:
此机制:
- 将 UI 逻辑与显示硬件详细信息分开
- 使用双缓冲实现平滑更新
- 通过 HAL 接口处理显示亮度
- 在整个应用程序中提供一致的渲染体验
8. 硬件支持的条件编译
显示和输入接口支持通过 and 标志进行条件编译,从而允许在没有实际硬件的情况下开发和测试 UI:HW_USE_HARDWARE
HW_USE_LCD
#define HW_USE_HARDWARE 1
#if HW_USE_HARDWARE
#define HW_USE_LCD 1
// Other hardware flags...
#endif
此方法可实现:
- 在 PC 模拟器上开发和测试
- 跨不同硬件平台的可移植性
- 在真实硬件和仿真环境之间轻松切换
9. 总结
OV-Watch 的显示和输入界面为用户交互提供了一个全面的系统,集成了:
- 硬件组件:LCD 显示屏、CST816 触摸控制器和物理按钮
- 硬件抽象:通过 HW_LCD_InterfaceTypeDef 结构
- 图形系统:用于 UI 渲染和组件的 LVGL
- 输入处理:KeyTask 和 ScrRenewTask,用于处理用户输入
- 导航系统:基于堆栈的页面导航
- 条件编译:用于无硬件的开发
此体系结构可确保响应迅速且可维护的用户界面系统,该系统在提供丰富用户体验的同时抽象化硬件详细信息。
应用系统
目的和范围
Application System 用作 OV-Watch 的主要作固件,在启动后管理设备功能的各个方面。本文档介绍了应用程序初始化顺序、FreeRTOS 任务架构以及各种子系统在正常作期间的交互方式。
系统初始化流程
应用程序系统遵循结构化的初始化序列来准备硬件、初始化作系统并启动提供手表功能的各种任务。
应用程序内存布局
OV-Watch 固件具有特定的内存布局,其中应用程序代码从 0x800C000 地址开始,该地址是闪存中的 48KB。这是因为前 48KB 是为 bootloader 保留的。
内存区域 | 起始地址 | 大小 | 目的 |
---|---|---|---|
闪光 | 0x8000000 | 48KB | Bootloader 代码 |
闪光 | 0x800C000 | 464KB | 应用程序代码(主固件) |
RAM | 0x20000000 | 128KB | 运行时变量、堆栈、堆 |
FreeRTOS 任务架构
应用程序系统围绕一组专门的任务进行组织,每个任务处理手表功能的不同方面。这些任务在初始化期间创建,并由 FreeRTOS 计划程序管理。
任务职责
OV-Watch 固件中的每个任务都有一组特定的职责:
任务名称 | 主要责任 | 交互点 |
---|---|---|
HardwareInitTask | 初始化硬件组件和接口 | 在启动时运行一次,然后终止 |
键任务 | 处理按钮按下和触摸输入 | 将输入事件发送到 UI 和模式任务 |
ScrRenewTask | 更新 UI 屏幕并处理屏幕转换 | 接收来自其他任务的数据以进行显示 |
SensUpdate任务 | 轮询传感器和过程传感器数据 | 将处理后的数据发送到 UI 和存储 |
RunModeTasks | 执行特定于当前模式的功能 | 根据模式控制作行为 |
DataSaveTask (数据保存任务) | 将设置和数据存储到持久内存 | 将其他任务的数据保存到 EEPROM |
ChargCheckTask | 监控电池和充电状态 | 更新电池指示灯和充电状态 |
MessageSendTask | 处理与外部设备的通信 | 处理 BLE 通信 |
硬件抽象和访问
Application System 通过结构化的硬件抽象层 (HAL) 访问硬件,该抽象层为物理组件提供一致的接口。这种抽象允许应用程序代码保持不变,即使底层硬件实现发生变化。
屏幕管理系统
Application System 使用基于堆栈的导航模型来管理屏幕转换和 UI 状态。这允许在屏幕之间移动时直观地向后导航并保留屏幕状态。
任务间通信
OV-Watch 应用程序中的任务通过 FreeRTOS 提供的多种机制进行通信,从而允许同步数据交换和事件通知。
通信方式 | 主要用例 | 示例用法 |
---|---|---|
任务通知 | 在任务之间向事件发送信号 | KeyTask 通知 ScrRenewTask 按下按钮 |
队列 | 在任务之间传递数据 | SensUpdateTask 将传感器数据发送到 ScrRenewTask |
信号灯 | 控制对共享资源的访问 | 保护对显示器或 EEPROM 的访问 |
全局变量 | 在多个任务之间共享状态 | 当前屏幕 ID、电池电量 |
电源管理集成
应用系统与电源管理紧密集成,以优化电池寿命,并根据用户活动实现不同的电源模式。
数据持久化
Application System 通过 EEPROM 存储系统在电源周期内保持数据持久性。这将处理保存用户首选项、校准数据和累积指标。
数据类别 | 存储位置 | 更新频率 |
---|---|---|
用户设置 | EEPROM 系列 | 更改时 |
显示设置 | EEPROM 系列 | 更改时 |
传感器校准 | EEPROM 系列 | 校准过程中 |
活动统计 | EEPROM 系列 | 定期(由 DataSaveTask 提供) |
系统状态 | EEPROM 系列 | 模式更改 / 关断时 |
总结
应用系统为 OV-Watch 的功能提供了一个全面的框架,集成了硬件控制、用户界面、电源管理和数据持久性。通过其多任务架构,它在响应式用户交互与高效资源使用之间取得平衡,使智能手表能够在保持良好电池寿命的同时提供功能丰富的体验。
该系统的模块化设计,任务和硬件抽象之间有明确的分离,便于维护和未来的增强,允许在对现有功能影响最小的情况下添加新功能。
FreeRTOS 任务结构
本页介绍了 OV-Watch 固件中使用的 FreeRTOS 任务结构。它涵盖了如何组织、初始化任务以及如何相互通信以创建响应迅速且高效的智能手表系统。
FreeRTOS 初始化流程
OV-Watch 固件在 main 函数中初始化 FreeRTOS。配置硬件外设后,它会初始化 RTOS 内核,创建 RTOS 对象,并启动调度器。
main.c 中的具体初始化序列包括:
osKernelInitialize()
:初始化 FreeRTOS 内核MX_FREERTOS_Init()
:创建 FreeRTOS 对象(任务、队列等)osKernelStart()
:启动 FreeRTOS 计划程序
任务架构概述
OV-Watch 固件被组织成多个专门的 FreeRTOS 任务,每个任务负责一项特定功能。这些任务在 FreeRTOS 计划程序的控制下并发运行。
系统的主要任务及其职责是:
任务 | 责任 |
---|---|
HardwareInitTask | 在 FreeRTOS 启动后初始化硬件组件 |
键任务 | 监控按钮按下情况并向其他任务发送消息 |
ScrRenewTask | 根据用户输入和系统事件更新显示 |
SensUpdate任务 | 读取和处理来自传感器的数据 |
RunModeTasks | 管理手表的不同作模式 |
DataSaveTask (数据保存任务) | 将数据保存到持久存储 |
ChargCheckTask | 监控电池和充电状态 |
MessageSendTask | 处理与外部设备的通信 |
任务实现详细信息
键任务
KeyTask 监视按键作,并使用消息队列将消息发送到其他任务。
KeyTask 实现显示:
- 检查按键作的连续扫描循环
- 根据按下的键和当前 UI 页面的不同行为
- 通过消息队列与其他任务通信
- 短延迟 (1ms) 以防止 CPU 占用
ScrRenewTask
ScrRenewTask 根据按键和其他系统事件更新用户界面。
ScrRenewTask 通过以下方式处理 UI 更新:
- 在 Key_MessageQueue 中检查消息
- 使用 Page_Back() 和 Page_Back_Bottom() 根据按键在 UI 页面之间导航
- 在适当的时候将传感器置于睡眠状态以节省电量
- 使用适度的延迟 (10ms) 来平衡响应能力与 CPU 效率
任务间通信
OV-Watch 固件中的任务主要通过消息队列进行通信,消息队列提供异步通信,而无需在任务之间直接耦合。
该代码显示了正在使用的三个主要消息队列:
消息队列 | 制作人 | 消费者 | 目的 |
---|---|---|---|
Key_MessageQueue | 键任务 | ScrRenewTask | 传输按键信息 |
Stop_MessageQueue | 键任务 | RunModeTasks | 在主页上发出停止事件信号 |
IdleBreak_MessageQueue | 键任务 | ChargCheckTask | 用于电源管理的信号空闲中断 |
使用的消息队列 API 函数包括:
osMessageQueuePut()
:将消息发送到队列osMessageQueueGet()
:从队列中检索消息
任务调度和计时
FreeRTOS 根据任务的优先级和状态来安排任务。任务可以处于不同的状态(Running、Ready、Blocked 或 Suspended),并且计划程序可确保优先级最高的就绪任务始终处于运行状态。
在 OV-Watch 固件中:
- 任务用于暂时进入 Blocked 状态,允许其他任务运行
osDelay()
- 不同的 delay 值反映了每个任务的计时要求:
- KeyTask:1ms 延迟(按钮检测的高响应性)
- ScrRenewTask:10 毫秒延迟(UI 更新对时间的要求较低)
这些精心挑选的延迟值会有所帮助:
- 在任务之间有效共享 CPU 时间
- 降低功耗
- 确保时间敏感功能正常运行
堆栈使用和内存注意事项
每个 FreeRTOS 任务都需要自己的堆栈空间,必须适当调整堆栈空间的大小,以确保:
- 为局部变量、函数调用和中断提供足够的空间
- 有效利用有限的 RAM 资源
虽然特定的堆栈大小在提供的代码片段中不可见,但任务通常在 MX_FREERTOS_Init() 函数中创建期间根据其要求设置堆栈大小。
总结
OV-Watch 中的 FreeRTOS 任务结构为管理手表的各种功能提供了一个模块化、高效的架构。主要特性包括:
- 关注点分离:每个任务处理功能的特定方面
- 基于消息的通信:任务通过消息队列进行通信
- 高效调度:任务使用适当的延迟来共享 CPU 时间
- 电源管理:传感器在不需要时进入休眠状态
这种基于任务的设计使 OV-Watch 能够处理多个并发功能,同时优化响应能力和功耗,这是电池供电可穿戴设备的关键因素。
数据存储和持久性
本文档介绍了 OV-Watch 如何存储和检索需要在重启之间保留的数据。主要关注用于系统设置和累积传感器数据的外部 EEPROM 存储。有关应用程序使用的运行时数据结构的信息,请参阅软件体系结构。
概述
OV-Watch 使用外部 2KB EEPROM 芯片 (BL24C02) 作为其非易失性存储系统。这提供了一种可靠的方法来存储用户设置、传感器校准数据和累积指标,这些指标需要在电源循环或电池更换中继续存在。
存储系统的设计考虑了几个关键因素:
- 保留关键的用户首选项和设置
- 使手表能够记住累积数据,如步数
- 确保整个电源周期的数据完整性
- 最大限度地减少写入作以延长 EEPROM 使用寿命
内存布局和组织
EEPROM 存储采用特定布局进行组织,以有效管理不同类型的数据:
地址 | 描述 | 目的 |
---|---|---|
0x00-0x01 | 幻数 (0x55, 0xAA) | EEPROM 完整性验证 |
0x10 | 用户手腕设置 | 存储 HWInterface.IMU.wrist_is_enabled 标志 |
0x11 | 用户 UI 设置 | 存储ui_APPSy_EN设置 |
0x20 | 上次保存日 | 上次保存数据的日期 (0-31) |
0x21 | Day Steps | 当天采取的步数 |
EEPROM 存储器映射
核心存储函数
存储系统提供四个主要功能来与 EEPROM 交互:
EEPROM_Init
通过调用较低级别的函数初始化用于通信的 EEPROM 模块。BL24C02_Init()
void EEPROM_Init(void)
{
BL24C02_Init();
}
EEPROM_Check
通过检查地址 0x00 和 0x01 处的幻数来验证 EEPROM 是否已正确初始化。如果这些值与预期值 (0x55 和 0xAA) 不匹配,它会初始化它们并再次检查。
此函数返回:
0
:如果检查成功1
:如果检查失败,则表示可能存在硬件问题
此函数对于数据完整性至关重要,因为它是在执行任何读/写作之前调用的。
设置保存
将数据保存到 EEPROM 的指定地址:
uint8_t SettingSave(uint8_t *buf, uint8_t addr, uint8_t lenth)
{
if(addr > 1 && !EEPROM_Check())
{
delay_ms(10);
BL24C02_Write(addr, lenth, buf);
return 0;
}
return 1;
}
该函数返回:
0
:如果数据已成功保存1
:如果发生错误(例如,EEPROM 检查失败)
设置 Get
从指定地址的 EEPROM 检索数据:
设置 Get
从指定地址的 EEPROM 检索数据:
uint8_t SettingGet(uint8_t *buf, uint8_t addr, uint8_t lenth)
{
if(addr > 1 && !EEPROM_Check())
{
delay_ms(10);
BL24C02_Read(addr, lenth, buf);
return 0;
}
return 1;
}
该函数返回:
0:如果数据检索成功
1:如果发生错误(例如,EEPROM 检查失败)
该函数返回:
0
:如果数据检索成功1
:如果发生错误(例如,EEPROM 检查失败)
存储架构
存储系统通过分层方法与 OV-Watch 架构的其余部分集成:
数据保存流程
数据保存流程涉及系统的多个组件协同工作:
- 数据收集:传感器数据由
SensUpdateTask
- 数据存储:定期将重要数据存储到 EEPROM 中
DataSaveTask
- 数据检索:应用程序任务在需要时检索保存的数据(例如,显示累积步数)
实施注意事项
数据完整性
使用幻数 (0x55、0xAA) 的 EEPROM 检查机制有助于确保数据完整性。在执行任何读或写作之前,系统会验证这些值以确认 EEPROM 是否正常工作。
写入周期管理
EEPROM 单元的使用寿命有限(通常约为 100 万次写入周期)。要延长 EEPROM 的使用寿命:
- 仅在必要时保存数据(更改的值)
- 尽可能对写入进行批处理
- 关键数据放置在不同的地址以分散磨损
时序注意事项
与 RAM作相比,EEPROM作相对较慢:
delay_ms(10)
在作之间使用,以确保正确的计时- DataSaveTask 以较低的优先级运行,以防止阻止 UI 响应
电源管理
即使手表处于睡眠模式或完全关闭,EEPROM 也会保留数据,这对于保存用户设置和累积指标至关重要。
使用示例
保存和加载用户首选项
用户首选项(如手腕检测设置)保存在地址0x10。通过 UI 更改时,它们将保存到 EEPROM 并在系统初始化期间加载。
每日统计数据
步数将累积并存储在地址 0x21。系统使用地址0x20来存储上次保存数据的日期,从而允许在午夜重置计数器。
用户界面系统
OV-Watch 用户界面系统为用户与智能手表功能交互提供了一个图形界面。它基于 LVGL v8.2 构建,实现了一个带有动画和响应式控件的分层页面导航系统。本文档介绍了构成监视界面的 UI 架构、导航系统和各个 UI 页面。UI 架构
OV-Watch UI 系统基于 LVGL(轻量级和多功能图形库)框架构建,该框架提供图形组件、事件处理和渲染功能。UI 架构遵循基于页面的设计,其中各个屏幕作为单独的页面实现,具有自己的初始化、事件处理和清理逻辑。

页面结构和层次结构
UI 遵循分层结构,其中主页位于根目录,菜单页面提供对各种功能的访问,以及用于特定功能的单个功能页面。
导航系统
OV-Watch 实现了一个基于堆栈的导航系统,允许用户在屏幕之间移动并返回到之前的屏幕。这是使用跟踪导航历史记录的 Screen Renewal Stack 实现的。
基于堆栈的导航
导航系统是使用堆栈数据结构 () 实现的,该结构存储指向 UI 屏幕的指针。导航到新屏幕时,当前屏幕的地址将推送到堆栈上。按下 Back 按钮时,将弹出堆栈以返回到上一个屏幕。ScrRenewStack
请务必注意,堆栈存储的是 screen 对象的地址,而不是 screen 对象本身。
页面生命周期
系统中的每个 UI 页面都遵循一个通用的生命周期,包括初始化、事件处理和清理。

每个页面都使用一组标准函数进行定义:
- 初始化函数 () - 创建 UI 对象并设置事件处理程序
ui_XXXPage_screen_init
- 清理函数 () - 离开页面时释放资源
ui_XXXPage_screen_deinit
- 页面对象结构 - 包含页面信息和回调函数
关键 UI 页面
OV-Watch 具有多个 UI 页面,每个页面都有特定的功能和组件:
主页
主时钟显示屏显示时间、日期和基本状态信息。
菜单页面
用于访问所有手表功能的基于网格的菜单。
环境数据页面
显示来自环境传感器的温度、湿度和海拔数据。
指南针页面
数字罗盘显示方向和高度。
计算器页面
一个功能齐全的计算器,具有基于字符串的表达式计算。
calculator 实现使用经典的双堆栈算法来计算数学表达式:
- 一个面向作员的堆栈
- 一叠数字
- 该算法逐个字符处理表达式,并根据其优先级应用运算符
日期和时间设置页面
用于使用滚轮选择器调整日期和时间的界面。
常见 UI 组件
OV-Watch UI 系统在多个页面上使用几个常见的 LVGL 组件:
组件类型 | 用法 | 示例页面 |
---|---|---|
按钮 (lv_btn ) | 导航和作触发器 | 菜单、计算器 |
标签 (lv_label ) | 文本显示 | 所有页面 |
圆弧 (lv_arc ) | 循环进度指示器 | 充电页面 |
酒吧 (lv_bar ) | 线性进度指示器 | 环境页面 |
滚筒 (lv_roller ) | 从列表中选择值 | 日期时间设置 |
开关 (lv_switch ) | 二进制切换选项 | “设置”页面 |
图片 (lv_img ) | 图标和装饰元素 | 指南针, 主页 |
日历 (lv_calendar ) | 日期显示和选择 | 日历页面 |
文本区域 (lv_textarea ) | 文本输入 | 计算器 |
屏幕续订任务
UI 系统使用专用的 FreeRTOS 任务 () 来处理屏幕更新和动画。此任务负责:ScrRenewTask
- 根据传感器数据和用户输入更新 UI
- 处理页面过渡效果和动画
- 管理屏幕续订堆栈
- 处理 UI 事件

UI 页面导航
本文档介绍了 OV-Watch UI 中使用的基于堆栈的导航系统。它介绍了在用户交互期间如何加载、管理和转换屏幕。此方法通过维护屏幕历史记录和启用直观的向后导航来提供一致的用户体验。
导航架构概述
OV-Watch UI 导航系统使用基于堆栈的方法来管理屏幕转换。每个屏幕在访问时被推送到堆栈上,并在导航回来时弹出。这将维护 screens 的历史记录,并支持资源的正确初始化/取消初始化。
页面层次结构
OV-Watch 具有以 HomePage 为根的分层 UI 结构,用于导航到各种功能的 MenuPage,以及用于不同功能的专用页面。

导航堆栈实现
导航系统的核心是堆栈数据结构 (),用于存储指向屏幕对象的指针。此堆栈管理屏幕历史记录并启用正确的返回导航。ScrRenewStack
堆栈作
OV-Watch 中的每个 UI 屏幕都由指向 LVGL 对象的指针 () 表示。导航堆栈通过以下作跟踪这些指针:lv_obj_t*
重要实现细节:
- 屏幕指针作为 LVGL 对象指针的强制转换被推送到堆栈上
(long long int)
- 向后导航时,顶部屏幕将从堆栈中弹出
- 该函数检查导航历史记录中是否还剩下任何屏幕
isEmpty
关键实施说明
README 强调了一个关键的实现细节:
将 UI 页面指针推送到堆栈时,不能直接推送类似 .它会动态变化。您必须使用 推送其地址。
ui_HomePage
(long long int)&ui_HomePage
这是必不可少的,因为 LVGL 会动态更新界面对象,而直接存储对象会引用过时的数据。
屏幕生命周期管理
每个 UI 页面都遵循一致的生命周期模式,其中包含初始化和取消初始化功能:

每个 UI 页面都有相应的初始化和取消初始化函数:
ui_PageName_screen_init()
:创建和配置 UI 元素ui_PageName_screen_deinit()
:离开屏幕时清理资源
屏幕过渡示例
以下是用于处理按钮按下以向后导航的关键导航逻辑示例:
屏幕过渡示例
以下是用于处理按钮按下以向后导航的关键导航逻辑示例:
//key1 pressed
if(keystr == 1)
{
user_Stack_Pop(&ScrRenewStack);
if(user_Stack_isEmpty(&ScrRenewStack))
{
ui_MenuPage_screen_init();
lv_scr_load_anim(ui_MenuPage,LV_SCR_LOAD_ANIM_MOVE_RIGHT,0,0,true);
user_Stack_Push(&ScrRenewStack,(long long int)&ui_HomePage);
user_Stack_Push(&ScrRenewStack,(long long int)&ui_MenuPage);
}
else if(ScrRenewStack.Data[ScrRenewStack.Top_Point-1] == (long long int)&ui_HomePage)
{
ui_HomePage_screen_init();
lv_scr_load_anim(ui_HomePage,LV_SCR_LOAD_ANIM_MOVE_RIGHT,0,0,true);
}
}
此代码片段演示:
- 从导航堆栈中弹出当前屏幕
- 检查堆栈是否为空(用户已导航回初始屏幕)
- 处理特殊情况(如返回 HomePage)
- 需要时重新初始化屏幕
- 加载带有动画的屏幕
- 将新屏幕推送到堆栈上
动画效果
屏幕过渡通过该函数使用 LVGL 的动画系统。最常用的动画用于向后导航,为用户提供有关导航方向的视觉提示。lv_scr_load_anim()
LV_SCR_LOAD_ANIM_MOVE_RIGHT
动画参数包括:
- 要加载的 Screen 对象
- 动画类型(移动、淡化等)
- 动画时间
- 动画前的延迟
- 是否清理上一个屏幕
与 FreeRTOS 集成
导航系统通过管理屏幕更新和 UI 状态的 与 FreeRTOS 任务结构集成。ScrRenewTask

ScrRenewTask
导航模式
标准前进导航
- 使用其函数初始化目标屏幕
ui_TargetPage_screen_init()
- 使用 加载带有动画的屏幕
lv_scr_load_anim()
- 将屏幕指针推到堆栈上
user_Stack_Push(&ScrRenewStack, (long long int)&ui_TargetPage)
返回导航
- 从堆栈中弹出当前屏幕
user_Stack_Pop(&ScrRenewStack)
- 检查堆栈的顶部以确定上一个屏幕
- 初始化上一个屏幕并加载动画
- 处理特殊情况(空堆栈,返回 HomePage)
总结
OV-Watch 中的 UI 页面导航系统提供:
- 通过直观的后退导航提供一致的用户体验
- 通过屏幕初始化和取消初始化进行适当的资源管理
- 使用动画实现平滑过渡
- 与 FreeRTOS 多任务环境集成
- 以 HomePage 为根的分层 UI 结构
UI 页面和组件
本文档详细介绍了 OV-Watch 系统中使用的 UI 页面和组件,重点介绍各个屏幕、屏幕之间的导航及其关键元素。有关 UI 导航系统和基于堆栈的方法的信息。
UI 体系结构概述
OV-Watch UI 系统使用 LVGL 图形库构建,并组织成不同的页面,每个页面代表智能手表的特定功能。每个页面都遵循一致的初始化和取消初始化模式,并通过标准化的页面结构进行管理。
页面结构和组织
OV-Watch 中的每个 UI 页面都遵循一致的结构:
- 页面管理结构:包含初始化和取消初始化功能指针以及对屏幕对象的引用的结构。
Page_t
- UI 对象:构成用户界面的 LVGL 对象(标签、按钮、面板等)
- 事件处理程序:响应用户交互的函数
- Update Mechanisms:刷新动态内容的计时器或回调

主 UI 页面
OV-Watch 具有多个 UI 页面,每个页面都专用于特定功能:
环境数据页面
环境数据页面显示来自 AHT21 传感器的温度和湿度信息。
关键组件:
- 温度条和数字显示
- 湿度条和数字显示
- 单位标签 (°C, %)
- 温度和湿度图标
该页面使用计时器 () 定期更新传感器数据的显示值。ui_EnvPageTimer

日期和时间设置页面
日期和时间设置功能在三个页面中实现:
- 日期时间设置页面:主设置页面,包含访问日期或时间设置的选项
- 日期设置页面:带有滚轮的页面,用于设置年、月和日
- 时间设置页面:带有滚轮的页面,用于设置小时、分钟和秒
每个页面都有确认按钮,并使用硬件 RTC 界面来应用更改。
Game 2048 页面
2048 Game 页面实现了经典的 2048 滑动图块益智游戏。游戏使用按钮矩阵来显示游戏板,并响应移动图块的手势事件。
关键组件:
- 用于存储数值的游戏矩阵 (4x4)
- 用于根据值显示具有不同颜色的图块的按钮矩阵
- 分数显示
- 用于重新启动的“New Game”按钮
游戏检测滑动手势(左、右、上、下)来移动图块并合并相同的值。

指南针页面
指南针页面显示来自设备传感器的指南针方向数据和高度信息。
关键组件:
- 罗盘指针(旋转图像)
- 方向标签
- 海拔高度显示
该页面可能会根据传感器数据更新指南针方向。
UI 更新机制
OV-Watch UI 采用多种机制来使显示的数据保持最新:
基于计时器的更新
包含动态数据的页面(如环境数据页面)使用 LVGL 计时器定期刷新值。这些计时器必须在页面初始化期间正确创建,并在取消初始化期间删除,以防止内存泄漏。

基于事件的更新
用户交互会触发更新 UI 的事件。例如:
- 2048 游戏中的手势事件更新游戏状态和 UI
- 单击设置页面中的按钮可保存值并导航到其他屏幕
UI 组件样式
OV-Watch UI 使用一致的样式模式:
组件类型 | 通用样式属性 |
---|---|
页面 | LV_OBJ_FLAG_SCROLLABLE 清除 |
标签 | 字体设置为自定义字体(例如ui_font_Cuyuan20 ) |
辊 | 选定项目的自定义轮廓样式 |
按钮 | 按下状态时的背景不透明度和颜色变化 |
图标 | 使用自定义图标字体 (ui_font_iconfont* ) |
UI 还针对不同的大小和用途使用自定义字体:
ui_font_Cuyuan20
, , 用于文本ui_font_Cuyuan30
ui_font_Cuyuan38
ui_font_iconfont28
, , 图标ui_font_iconfont34
ui_font_iconfont45
硬件集成
UI 页面通过硬件抽象层的结构直接与硬件交互:HWInterface
- 环境页面读取温度和湿度
HWInterface.AHT21
- 日期/时间设置 修改时间
HWInterface.RealTimeClock
- 指南针页面可能从指南针传感器读取数据
此集成可确保实时数据显示,同时保持 UI 和硬件关注点之间的分离。
LVGL 模拟器
目的和范围
LVGL Simulator 提供了一个基于 Windows 的模拟环境,用于在部署到实际硬件之前在 PC 上测试和开发 OV-Watch 的用户界面。这使开发人员能够快速迭代 UI 设计和功能,而无需刷写物理设备。本文档介绍了如何设置、配置和使用 LVGL 模拟器进行 OV-Watch 开发。
模拟器架构
LVGL 模拟器使用 SDL2(简单 DirectMedia 层)创建 OV-Watch 显示的虚拟表示,以呈现图形和处理输入事件。它将手表中使用的实际 UI 代码与特定于模拟的驱动程序集成在一起,以创建与真实设备非常相似的开发环境。
关键组件
- LVGL 库:模拟器和真实设备中使用的核心图形库
- SDL2 驱动程序:在 PC 上提供显示输出和输入事件处理
- HAL (Hardware Abstraction Layer):将 LVGL 连接到模拟硬件
- UI 组件:模拟器和设备之间共享的实际 UI 代码
模拟器使用与物理设备完全相同的 UI 组件和 LVGL 库代码,确保您在模拟器中看到的内容与手表上显示的内容非常接近。
设置模拟器
先决条件
要设置和运行 LVGL 模拟器,您需要:
- Visual Studio 代码
- C/C++ 开发环境
- SDL2 库
- LVGL v8.2 库
配置
模拟器通过几个关键文件进行配置:
该文件包含特定于 VSCode 的配置,包括 LVGL 仿真器项目的文件关联和智能感知设置。settings.json
重要的配置参数包括:
参数 | 描述 | 位置 |
---|---|---|
SDL_HOR_RES | 显示水平分辨率 | sdl.h |
SDL_VER_RES | 显示垂直分辨率 | sdl.h |
SDL_ZOOM | 显示缩放系数 | sdl.h |
SDL_DOUBLE_BUFFERED | 双缓冲启用标志 | sdl.h |
HW_USE_HARDWARE | 硬件/仿真模式切换 | HWDataAccess.c |
使用模拟器
运行模拟器
要运行 LVGL 模拟器:
- 在 Visual Studio Code 中打开文件夹
lv_sim_vscode_win
- 在 HWDataAccess.c 中设置为 0 以启用模拟模式
HW_USE_HARDWARE
- 生成并运行项目
模拟器将创建一个窗口,显示 OV-Watch UI,并允许通过鼠标和键盘进行交互。
输入设备仿真
模拟器提供三种模拟输入设备:
- 鼠标 (模拟触摸屏)
- 键盘(模拟按钮)
- 鼠标滚轮(模拟旋转输入)
这些允许您以类似于用户与实际手表交互的方式与 UI 交互。
模拟器实现详细信息
HAL 初始化
仿真器中的硬件抽象层 (HAL) 由 中的函数初始化。此功能:hal_init()
main.c
- 初始化 SDL
- 创建显示缓冲区
- 设置输入设备(鼠标、键盘和鼠标滚轮)
- 注册用于输入处理的回调
static void hal_init(void)
{
/* Initialize SDL */
sdl_init();
/* Create display buffer and driver */
static lv_disp_draw_buf_t disp_buf1;
static lv_color_t buf1_1[SDL_HOR_RES * 100];
lv_disp_draw_buf_init(&disp_buf1, buf1_1, NULL, SDL_HOR_RES * 100);
/* Set up input devices */
static lv_indev_drv_t indev_drv_1;
lv_indev_drv_init(&indev_drv_1);
indev_drv_1.type = LV_INDEV_TYPE_POINTER;
indev_drv_1.read_cb = sdl_mouse_read;
/* ... and similar for keyboard and mousewheel ... */
}
显示处理
模拟器使用 SDL 将 UI 呈现到 PC 上的窗口。当屏幕的一部分需要更新时,LVGL 会调用 display flush callback ():sdl_display_flush
事件处理
模拟器在计时器回调中处理 SDL 事件,并将其转换为 LVGL 输入事件:
从仿真器迁移到硬件
使用 HWDataAccess 进行硬件抽象
OV-Watch 项目使用中间件层 (),允许在模拟器和实际硬件上使用相同的 UI 代码。此文件根据以下标志有条件地实现硬件接口:HWDataAccess.c
HW_USE_HARDWARE
当设置为 0 时,HWDataAccess.c 文件提供硬件接口的模拟实现。当设置为 1 时,它使用实际的硬件驱动程序。HW_USE_HARDWARE
迁移过程
要将 UI 代码从模拟器移动到实际设备,请执行以下作:
- 在模拟器中开发和测试 UI
- 将 and 文件夹复制到 Keil 工程的文件夹中
Func
GUI_App
User
- 在设备代码中设置为 1
HW_USE_HARDWARE
- 构建并刷写设备
此过程允许以最少的代码更改从仿真平稳过渡到实际硬件。
故障排除和提示
- 如果模拟器未启动,请检查 SDL2 是否已正确安装和配置
- 为了进行准确的模拟,请确保模拟器的显示分辨率与实际设备匹配
- 在部署到硬件之前,使用模拟器测试 UI 响应能力和布局
- 请记住,即使使用模拟,某些特定于硬件的行为也可能在实际设备上有所不同
模拟器是 UI 开发的强大工具,但始终在实际硬件上验证功能以进行最终验证。
电源管理
目的和范围
本文档介绍了 OV-Watch 的电源管理系统,详细介绍了为最大限度地延长电池寿命而采用的不同电源模式、硬件组件、软件接口和优化策略。电源管理系统对于确保手表能够高效运行,同时保持计步等核心功能(即使在低功耗状态下)至关重要。
Power Modes 概述
OV-Watch 实现了一个三层电源管理系统,具有不同的电源模式,以平衡功能和电池消耗。
Power Modes 详细信息
运行模式
在运行模式下,OV-Watch 以全部功能运行:
- STM32F411 微控制器完全有效
- 所有传感器均可运行
- 显示屏已开启且响应迅速
- 所有功能和应用程序均可用
- 功耗:70-80mA
这是用户主动使用手表时的默认模式。
睡眠模式
睡眠模式在一段时间不活动后自动激活:
- STM32F411 进入 STOP 电源模式
- MPU6050 保持活动状态以进行步数计数
- 显示器已关闭
- UART I/O 端口被取消初始化并设置为输入模式
- 功耗:约 800μA
睡眠模式保留了核心功能,同时显著降低了功耗。
关机模式
Shutdown Mode 是功耗最低的状态:
- TPS63020电源管理 IC 已禁用
- 3V3 电源完全关闭
- 只有 RTC 仍由电池供电
- 功耗:最小(基本上只有电池自放电)
模式转换和唤醒机制
Running 模式转为 Sleep 模式
手表在用户处于非活动状态一段时间后自动进入睡眠模式。
睡眠模式转为运行模式
有两种机制可以将手表从睡眠模式中唤醒:
- RTC 周期性唤醒:RTC 产生周期性中断以检查设备状态
- 手腕抬起检测:当用户抬起手腕时,系统会检测到此动作并唤醒
最初,MPU6050的运动功能用于唤醒,但这需要大量运动才能触发。当前的实现使用 RTC 定时中断来定期检查手腕位置。
过渡到停机模式
运行模式和睡眠模式都可以通过长按 KEY1 按钮过渡到关机模式。
从关机模式唤醒
只有按下电源按钮才能将手表从关机模式中唤醒,这将重新启用 TPS63020 电源管理 IC。
电源管理中的硬件组件
TPS63020 电源管理 IC
TPS63020 是主要的电源管理 IC,它:
- 控制整个系统的 3V3 电源
- 可在关机模式下完全禁用
- 从关机模式唤醒时通过电源按钮启用
电池管理
电池 (Vbat) 直接为:
- Running (跑步) 和 Sleep (睡眠) 模式下的 TPS63020
- 所有模式下的 RTC,包括关断模式
STM32F411 电源状态
STM32F411 微控制器支持不同的电源模式:
- 运行模式:所有外围设备的完全作
- STOP 模式:内核时钟停止,外设保持,快速唤醒
- 待机模式:在关机状态下使用
RTC (实时时钟)
RTC 是一个关键组件,它:
- 在所有模式下保持供电(甚至关机)
- 提供唤醒中断
- 计划每 2000 个时钟周期中断一次(RTC_WAKEUPCLOCK_RTCCLK_DIV16预分频器)
用于电源管理的软件接口
OV-Watch 通过以下结构实现用于电源管理的硬件抽象层 (HAL):HW_Power_InterfaceTypeDef
电源接口组件
电源管理接口包括:
1. 数据结构
power_remain
:存储当前电池百分比 (0-100%)
2. 函数指针
Init()
:初始化电源管理系统Shutdown()
:关闭系统(进入关机模式)BatCalculate()
:计算剩余电量百分比
3. 实现功能
HW_Power_Init()
:初始化电源子系统的调用Power_Init()
HW_Power_Shutdown()
:禁用 TPS63020 的调用Power_DisEnable()
HW_Power_BatCalculate()
:计算电池百分比的调用PowerCalculate()
功耗优化策略
OV-Watch 采用多种策略来优化功耗:
1. UART I/O 电源管理
- 在睡眠模式下,UART I/O 端口被取消初始化并设置为输入模式
- 这种优化将休眠电流降低到大约 800μA
- 在 V2.4.1 更新中实现
2. MPU6050 功率优化
- MPU6050传感器的自定义配置以降低功耗
- 不能直接使用 DMP 库,因为它会增加功耗
- 在禁用其他功能时,在睡眠模式下保持活动状态以进行计步
3. 蓝牙电源管理
- KT6368A 蓝牙模块在不使用时可以完全禁用
HWInterface.BLE.Disable()
功能关闭蓝牙模块- 通过函数实现
KT6328_Disable()
4. 显示器电源管理
- LCD 在睡眠模式下完全关闭
- 在运行模式下,背光亮度可以通过以下方式进行调整
LCD_Set_Light()
5. 传感器电源管理
- 心率计、Ecompass 等传感器具有睡眠功能
- 例如,和
HW_HRmeter_Sleep()
HW_Ecompass_Sleep()
- 传感器仅在需要时初始化,并在不使用时进入睡眠状态
用户界面和系统控制
电源管理系统通过以下方式与用户交互:
硬件控制
- KEY1 按钮:长按从运行或睡眠模式触发关机模式
- 电源按钮:将设备从关机模式中唤醒
- 手腕检测:抬起手腕可以将设备从睡眠模式中唤醒
按钮按下处理
- 最近的更新 (V2.4.1) 修改了按钮行为,仅在释放时触发作
- 这可以防止意外触发,改善用户体验并可能节省电量
自动睡眠设置
- 手表在一段时间不活动后自动进入睡眠模式
- 此转换无需用户干预即可实现,以最大限度地延长电池寿命
功耗测量
由 3V3 电源供电时的实际功耗测量值:
- 运行模式:70-80mA
- 休眠模式:约 800μA
- 关机模式:最小(仅 RTC 供电)
注意:这些测量值直接来自 3V3 电源。当 Vbat 通过 TPS63020 DCDC 转换器供电时,实际功耗将取决于转换器的效率。
最近的电源管理改进
V2.4.1 更新包括几项电源管理增强功能:
- 休眠模式优化:休眠期间取消初始化 UART I/O 端口并将其设置为输入模式,将功耗降低至约 800μA
- 关机控制:为 BootLoader 和 APP 添加了长按 KEY1 关机功能
- 按钮按下处理:修改了按钮 BSP,仅在松开按钮时触发作,防止意外激活
与硬件初始化集成
电源管理系统在引导过程的早期进行初始化:
- RTC 唤醒定时器配置为生成中断
- 电源管理使用
HWInterface.Power.Init()
- 传感器初始化,然后进入适当的电源状态
- 蓝牙已初始化,但默认处于禁用状态以节省电量
硬件抽象层中的电源管理
电源管理系统是硬件抽象层的一部分,允许应用程序与电源管理功能交互,而无需依赖于特定的硬件实现。
结论
OV-Watch 电源管理系统实现了一种复杂的三层方法,可在功能与功耗之间取得平衡。通过利用 STM32F411 的电源模式等硬件特性并实施智能软件策略,该系统在睡眠模式 (~800μA) 下实现了令人印象深刻的节能效果,同时保持了计步等核心功能。
该系统设计有自动转换(非活动超时)和手动控制(长按 KEY1 关机)两种功能,在优化电池寿命的同时为用户提供灵活性。V2.4.1 的最新改进进一步增强了能效和用户体验。
计算器实现
目的和范围
本文档介绍了 OV-Watch 系统中 calculator 函数的实现。计算器是智能手表中的实用应用程序之一,允许用户执行基本的算术运算。本页介绍了用于字符串计算的算法、底层数据结构以及计算过程的整体流程。
概述
OV-Watch 计算器使用两个堆栈实现经典的表达式计算算法:一个用于作数(数字),另一个用于运算符。这种方法处理中缀表示法(例如,“1+2*3”),同时尊重运算符优先级。该实现支持基本算术运算 (+、-、*、/) 并处理小数点。
数据结构
calculator 实现依赖于头文件中定义的几个关键数据结构:StrCalculate.h
StrStack_t
:用于存储输入表达式字符串的堆栈caldata_t
:用于保存计算数据(数字或符号)的结构NumStack_t
:用于存储作数(数字)的堆栈SymStack_t
:用于存储运算符(符号)的堆栈
这些数据结构为实现表达式计算算法提供了基础。
计算算法
计算器使用标准算法来计算数学表达式:
- 输入字符串是逐个字符解析的
- 数字和运算符被分隔和识别
- 数字 (作数) 被推送到数字堆栈上
- 运算符根据其优先级进行处理:
- 如果算子栈为空,则直接推送算子
- 如果当前算子的优先级高于算子堆栈的顶部,则将其推送
- 如果当前运算符的优先级较低或相等,则首先使用现有运算符执行作
- 处理完整个字符串后,将执行任何剩余的作
运算符优先级
计算器实现运算符优先级,以确保计算遵循数学约定(例如,加法和减法之前的乘法和除法)。在表达式评估期间,通过在执行作之前比较运算符来处理优先级。
算子 | 优先 |
---|---|
* / | 高 |
+ - | 低 |
示例计算过程
为了说明算法的工作原理,让我们检查一下表达式的计算 :1+2*6/3
主要功能
calculator 实现包括几个关键功能:
strput()
和 :管理输入字符串堆栈strdel()
NumStackClear()
和:初始化或重置 number 和 operator 堆栈SymStackClear()
NumSymSeparate()
:解析输入字符串并分隔数字和运算符StrCalculate()
:执行计算的主要函数isIntNumber()
:用于检查数字是否为整数的实用函数
处理小数点
计算器通过在解析过程中跟踪小数位来处理小数点。当遇到小数点时,后续数字将被解释为数字的小数部分。该实现使用浮点数 ( type) 来存储值,允许进行十进制计算。float
该结构具有一个 field 类型以支持十进制值。该函数检查数字是否为整数(没有小数部分),以确定如何显示结果。caldata_t
number
float
isIntNumber()
与用户界面集成
计算器功能已集成到 OV-Watch UI 系统中。计算器应用程序可从主菜单访问,其 UI 包括数字按钮、作员按钮以及用于输入和结果的显示区域。
OV-Watch 的 UI 导航系统使用基于堆栈的方法进行屏幕管理,允许用户从计算器导航回上一个屏幕。
错误处理
calculator 实现包括常见问题的基本错误处理:
- 除以零
- 语法无效(运算符不匹配)
- 超出堆栈容量
发生错误时,计算将中止,并向用户显示相应的错误消息。
结论
OV-Watch 中的计算器实现使用经典的双堆栈方法来计算数学表达式,同时尊重运算符优先级。该实现支持基本的算术运算和小数点,为用户提供智能手表上的功能性计算器应用程序。
该代码的结构在数据结构、解析逻辑和计算算法之间明确分离,使其易于维护且易于理解。
开发工作流程
下图说明了 OV-Watch 项目的典型开发工作流程:
构建和部署固件
构建配置
Keil 项目配置了:
- 目标设备:STM32F411CEU6
- 输出目录:
.\output\
- 生成二进制文件的构建后作
构建后作使用以下方法将 ELF 文件转换为二进制文件:
fromelf.exe --bin -o "$L@L.bin" "#L
内存布局
固件配置为从特定内存地址运行:
元件 | 地址范围 |
---|---|
闪存 (ROM) | 0x800c000-0x8080000 |
公羊 | 0x20000000-0x20020000 |
这种布局容纳了占据较低 flash 地址的 bootloader。
刷写程序
对于初始编程,可以使用以下方法刷新固件:
- ST-Link V2 或兼容编程器
- 直接连接到 SWD 接口
对于定期更新,OTA 功能允许通过蓝牙进行更新:
- 在复位过程中按 KEY1 进入 bootloader 模式
- 通过蓝牙连接到设备
- 发送更新包
- 引导加载程序将验证并刷写新固件.