Files
stc32g128k/App/main.c
edisondeng ec556fdd2a fix(boot): hold backlight off until first frame is drawn
SGM_CTRL was turned on at the very start of LCD_Init(), before the
ST7789V register sequence and first frame ran, showing garbage/blank
content briefly on every boot. GPIO_Init() now forces backlight off at
power-on, LCD_Init() no longer touches it, and main() only turns it on
right before entering the main loop (after HomeApp's first frame is
drawn). Wakeup_Restore()'s existing on-then-reinit sequence is
untouched since it sets the level itself before calling LCD_Init().
2026-07-31 18:09:39 +08:00

372 lines
18 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "config.h"
#include "rgb.h"
#include "event.h"
#include "app_manager.h"
#include "database.h"
#include "ui.h"
#include "system.h"
#include "../Drivers/rf.h"
#include "../Drivers/lcd.h"
#include "../Drivers/timer.h"
/* =========================================================================
* 系统全局变量定义
* ========================================================================= */
SystemState current_state = STATE_NORMAL; // 系统整体的当前运行状态,默认为正常时钟待机页
u8 menu_select = 0; // 当前菜单高亮的选中项索引 (menu_app 0~3pair_app 传感器类型选择 0~4)
u32 captured_addr = 0; // 配对或报警时,捕获到的传感器 24 位 EV1527 射频物理地址
u8 captured_type = 0; // 正在对码或触发警报的传感器设备类型 (0:门磁, 1:PIR...)
u8 alarm_sensor_slot = 0; // 触发警报的传感器在 sensor_list 数组中的索引槽位号
// 传感器存储列表数据库,位于单片机外部 XDATA 存储空间,总共 16 个槽位
Sensor_Slot xdata sensor_list[16];
// 全局滴答与定时中断计数变量,位于 volatile 防止被编译器优化
volatile u16 ms_tick = 0; // 定时中断 1ms 基准滴答累加器
volatile u16 inactivity_timer = 0; // 用户闲置计时器,用于无操作判定(单位: 10ms
volatile u32 pair_timeout_ms = 0; // 传感器配对对码超时毫秒计时器 (上限 30000ms 即 30s)
volatile u16 alarm_timer_ms = 0; // 入侵报警马达震动时序计数器
volatile u16 motor_timer_ms = 0; // 独立马达震动时长计数器
volatile bit alarm_flash_flag = 0; // 报警时红框周期性闪烁亮灭标志位
// 物理按键扫描与消抖长按计数器 (10ms 扫描一次)
volatile u16 key_scan_timer = 0; // 10ms 扫描周期分频器
volatile u16 key_up_hold = 0; // ▲ (KEY_UP) 按下时长累加
volatile u16 key_down_hold = 0; // ▼ (KEY_DOWN) 按下时长累加
volatile u16 key_confirm_hold = 0; // ■ (KEY_CONFIRM) 按下时长累加
volatile u16 key_sos_hold = 0; // SOS 按下时长累加
volatile u16 comb_hold = 0; // ▲ + ▼ 组合键按下时长累加
/* =========================================================================
* 串口调试指令解析与事件派发封装
* ========================================================================= */
/**
* @brief 解析通过串口 1 发送来的键盘及测试模拟指令
* @param cmd 接收到的 ASCII 字符命令
* @details 负责将字符指令转化对应的系统按键或射频事件,并推入事件队列以测试软件架构:
* - 'u'/'U': 模拟短按/长按 ▲ 键。
* - 'd'/'D': 模拟短按/长按 ▼ 键。
* - 's'/'S': 模拟短按/长按 SOS 物理键(优先插入队首)。
* - 'c'/'C': 模拟组合键触发。
* - 'a'/'A': 模拟无线传感器入侵触发警报信号,地址固定为 0x123456zone=1。
* - 'p'/'P': 模拟对码期间捕获的 24 位无线对码信号,地址固定为 0x789ABC。
* - '1'/'2'/'3': 模拟系统的正常、已布防、已撤防状态切换。
* - 'z'/'Z': 模拟超时,强行使闲置计数器置 6000触发自动深度休眠。
* - 't'/'T': 触发屏幕与无线控制引脚测试。
* - 'e'/'E': DEBUG打印 RF 边沿中断解码排查计数器。
*/
/**
* @brief 串口调试专属RGB 物理颜色通道排查测试
* @details 依次发送纯红、纯绿、纯蓝的数据流,每个颜色亮起 2 秒,帮助肉眼校对物理灯珠的实际颜色映射关系
*/
void RGB_Diagnostic_Test(void)
{
Uart_SendString("\r\n=== RGB LED Color Channel Test Starting ===\r\n");
// 1. 尝试发送红色控制数据 (G=0, R=150, B=0)
Uart_SendString("[RGB-TEST] 1. Sending RED command -> G=0, R=150, B=0\r\n");
RGB_Send(0, 150, 0, 0, 150, 0);
Delay_ms(2000);
// 熄灭
RGB_Send(0, 0, 0, 0, 0, 0);
Delay_ms(300);
// 2. 尝试发送绿色控制数据 (G=150, R=0, B=0)
Uart_SendString("[RGB-TEST] 2. Sending GREEN command -> G=150, R=0, B=0\r\n");
RGB_Send(150, 0, 0, 150, 0, 0);
Delay_ms(2000);
// 熄灭
RGB_Send(0, 0, 0, 0, 0, 0);
Delay_ms(300);
// 3. 尝试发送蓝色控制数据 (G=0, R=0, B=150)
Uart_SendString("[RGB-TEST] 3. Sending BLUE command -> G=0, R=0, B=150\r\n");
RGB_Send(0, 0, 150, 0, 0, 150);
Delay_ms(2000);
// 彻底熄灭
RGB_Send(0, 0, 0, 0, 0, 0);
Uart_SendString("=== RGB LED Color Channel Test Ended ===\r\n");
}
void Debug_ProcessCommand(char cmd)
{
SystemEvent debug_evt;
inactivity_timer = 0; // 收到指令,立即重置用户的无操作闲置计时器
debug_evt.extra_data = 0;
debug_evt.extra_size = 0;
if (cmd == 'u') {
debug_evt.key_event = KEY_EVENT_UP_CLICK;
debug_evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(debug_evt); // 压入队尾
Uart_SendString("[UART] KEY_UP Clicked\r\n");
} else if (cmd == 'U') {
debug_evt.key_event = KEY_EVENT_UP_LONG;
debug_evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(debug_evt);
Uart_SendString("[UART] KEY_UP Long Pressed\r\n");
} else if (cmd == 'd') {
debug_evt.key_event = KEY_EVENT_DOWN_CLICK;
debug_evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(debug_evt);
Uart_SendString("[UART] KEY_DOWN Clicked\r\n");
} else if (cmd == 'D') {
debug_evt.key_event = KEY_EVENT_DOWN_LONG;
debug_evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(debug_evt);
Uart_SendString("[UART] KEY_DOWN Long Pressed\r\n");
} else if (cmd == 's') {
debug_evt.key_event = KEY_EVENT_SOS_CLICK;
debug_evt.priority = EVENT_PRIORITY_URGENT;
EventQueue_InsertFront(debug_evt); // 紧急事件插入队首
Uart_SendString("[UART] KEY_SOS Clicked\r\n");
} else if (cmd == 'S') {
debug_evt.key_event = KEY_EVENT_SOS_LONG;
debug_evt.priority = EVENT_PRIORITY_URGENT;
EventQueue_InsertFront(debug_evt);
Uart_SendString("[UART] KEY_SOS Long Pressed\r\n");
} else if (cmd == 'c' || cmd == 'C') {
debug_evt.key_event = KEY_EVENT_UP_DOWN_COMB;
debug_evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(debug_evt);
Uart_SendString("[UART] KEY_UP+DOWN Combined\r\n");
} else if (cmd == 'a' || cmd == 'A') {
u32 mock_addr = 0x123456; // 设定模拟的报警射频源地址
u8 mock_slot;
// 如果本地数据库中没有匹配该射频,则强制登记进 1 号防区
if (!Check_Sensor_ID(mock_addr, &mock_slot)) {
Add_Sensor_With_Zone(mock_addr, 0, 1);
}
debug_evt.key_event = KEY_EVENT_NONE;
debug_evt.priority = EVENT_PRIORITY_HIGH; // 入侵报警设为 HIGH 优先级
debug_evt.extra_data = mock_addr;
debug_evt.extra_size = 4;
EventQueue_InsertFront(debug_evt); // 高优先级插队
Uart_SendString("[UART] Mock RF Alarm\r\n");
} else if (cmd == 'p' || cmd == 'P') {
// 只有当手环切入 Pair 模式时,模拟对码信号才有效
if (AppManager_GetActiveAppID() == APP_ID_PAIR) {
debug_evt.key_event = KEY_EVENT_NONE;
debug_evt.priority = EVENT_PRIORITY_NORMAL;
debug_evt.extra_data = 0x789ABC; // 设定模拟对码硬件地址
debug_evt.extra_size = 4;
EventQueue_Push(debug_evt);
Uart_SendString("[UART] Mock RF Pair Signal\r\n");
} else {
Uart_SendString("[UART] Not in PAIR mode\r\n");
}
} else if (cmd == '1') {
// 模拟切换至 正常撤防 (NORMAL) 状态并刷新时钟
if (AppManager_GetActiveAppID() == APP_ID_HOME) {
current_state = STATE_NORMAL;
UI_ShowClockPage(current_state, current_hour, current_min);
}
Uart_SendString("[UART] State: NORMAL\r\n");
} else if (cmd == '2') {
// 模拟切换至 已布防 (ARMED) 状态,普通门磁可触发警报
if (AppManager_GetActiveAppID() == APP_ID_HOME) {
current_state = STATE_ARMED;
UI_ShowClockPage(current_state, current_hour, current_min);
}
Uart_SendString("[UART] State: ARMED\r\n");
} else if (cmd == '3') {
// 模拟切换至 已撤防 (DISARMED) 状态,忽略普通报警
if (AppManager_GetActiveAppID() == APP_ID_HOME) {
current_state = STATE_DISARMED;
UI_ShowClockPage(current_state, current_hour, current_min);
}
Uart_SendString("[UART] State: DISARMED\r\n");
} else if (cmd == 'z' || cmd == 'Z') {
inactivity_timer = 6000; // 模拟闲置 60 秒超时,强制触发主循环深度休眠
Uart_SendString("[UART] Force Entering Sleep mode!\r\n");
} else if (cmd == 'q' || cmd == 'Q') {
RGB_Diagnostic_Test(); // 启动 RGB 物理颜色通道排查测试
} else if (cmd == 't' || cmd == 'T') {
RF_DiagnosticMode('t'); // 触发射频连续发射诊断 (绿色爆闪)
} else if (cmd == 'r' || cmd == 'R') {
RF_DiagnosticMode('r'); // 触发 5 秒射频接收监听诊断
} else if (cmd == 'l' || cmd == 'L') {
Loopback_Test(); // 触发物理电气回环测试 (短路 P2.1 和 P3.6 自检)
} else if (cmd == 'e' || cmd == 'E') {
RF_PrintDebugCounters(); // DEBUG: 打印 RF 边沿中断解码排查计数器
}
}
// ====== 调试用最小化启动开关 ======
// 置 1 时 main() 只做点亮屏幕这一件事RF/自检/数据库/事件框架/Timer1 全部跳过,
// 用于定位"开机反复重启"问题的范围。
// 根因(isr_jump.asm 缺少 Port3 中断向量重定向、且该文件从未加入工程编译)已定位并修复,
// 现改回 0恢复完整正常启动流程测试整机端到端是否稳定。
#define DEBUG_MINIMAL_BOOT 0
/* =========================================================================
* 主函数 (入口)
* ========================================================================= */
#if DEBUG_MINIMAL_BOOT
void main(void)
{
// ====== STC32G 特殊功能寄存器内核基础初始化 ======
WTST = 0; // 设置程序 Flash 访问等待时间为 0 (最高速运行)
EAXFR = 1; // 允许访问扩展特殊功能寄存器 (XSFR)
CKCON = 0; // 外部总线时钟设定为最快
WDT_CONTR = 0x00; // 关闭看门狗定时器
// ====== 步骤 A加回 RF_Init() 与 Timer1_Init(),先不打开 RF 接收 ======
GPIO_Init(); // 配置各个引脚的推挽输出/准双向模式及内部上拉 (锁存 PWR_HOLD=1)
RGB_Send(0, 0, 0, 0, 0, 0); // 上电第一时间强制发送 0 码关闭灯条,熄灭上电暂态引起的随机白光
RF_Init(); // 加回来Timer0 常驻自由运行 + P3.6 常态双边沿中断 + 上拉,此时芯片仍 SHUT=1 关闭
Delay_ms(500); // 软启动延时,等待供电电容稳定
LCD_Init(); // 初始化 LHS114TC-IF03 (ST7789V) 屏幕控制寄存器序列并开启背光
Uart1_Init(); // 初始化串口 1 波特率 115200 供调试日志
Uart_SendString("Wristband System Initialized! (DEBUG_MINIMAL_BOOT=1, step A: RF_Init+Timer1_Init)\r\n");
Timer1_Init(); // 全局中断 EA=1 从这里开始生效Timer0 溢出中断开始工作
// ====== 步骤 C真正的病根 (isr_jump.asm 缺少 Port3 重定向、且该文件从未被加入工程编译) 已修复,
// P3.6 中断与 RF_HandleEdgeInterrupt 都已恢复为正常逻辑,现在测试真实功能是否稳定 ======
RF_SetMode(1);
LCD_ShowStringCentered(100, "STEP C OK", COLOR_WHITE, COLOR_BLACK);
LCD_ShowStringCentered(130, "ISR fixed", COLOR_GRAY, COLOR_BLACK);
while (1) {
Delay_ms(1);
}
}
#else
void main(void)
{
char cmd;
// ====== STC32G 特殊功能寄存器内核基础初始化 ======
WTST = 0; // 设置程序 Flash 访问等待时间为 0 (最高速运行)
EAXFR = 1; // 允许访问扩展特殊功能寄存器 (XSFR)
CKCON = 0; // 外部总线时钟设定为最快
WDT_CONTR = 0x00; // 关闭看门狗定时器
// ====== 硬件各模块基础初始化 ======
GPIO_Init(); // 配置各个引脚的推挽输出/准双向模式及内部上拉 (锁存 PWR_HOLD=1)
RGB_Send(0, 0, 0, 0, 0, 0); // 【首要修正】:上电第一时间强制发送 0 码关闭灯条,熄灭上电暂态引起的随机白光!
RF_Init(); // 初始化射频芯片各引脚控制
Delay_ms(500); // 软启动延时,等待供电电容稳定
LCD_Init(); // 初始化 LHS114TC-IF03 (ST7789V) 屏幕控制寄存器序列 (背光保持关闭,避免闪烁)
Uart1_Init(); // 初始化串口 1 波特率 115200 供调试日志
Uart_SendString("Wristband System Initialized!\r\n");
Self_Test(); // 执行上电硬件自检 (PWR_HOLD锁存、P0.3 ADC按键常态、P1.3 电池电量)
Load_Database(); // 从 IAP Flash 第 254 扇区读取所有已绑定的传感器数据至 RAM
/* 步骤 1: 初始化事件环形缓冲区队列 */
EventQueue_Init();
/* 步骤 2+5: 初始化应用管理器并登记所有前台应用,默认加载 HomeApp */
AppManager_Init();
/* 启动并挂载后台射频监控应用,开始侦听无线传感器信号 */
AppManager_AttachToBackground(&rf_monitor_app);
/* 启动 1ms 系统基准定时器 Timer1 */
Timer1_Init();
/* 开机自动加载并运行待机时钟前台活跃应用 */
AppManager_StartApp(APP_ID_HOME);
Uart_SendString("[SYS] Event-driven framework ready\r\n");
// 所有初始化与首帧时钟界面绘制均已完成,此刻才点亮背光,避免开机过程中的中间画面被看到而产生闪烁
SGM_CTRL = 0;
// ====== 主循环 ======
while (1) {
/* 0. 检查主循环按键定时标志 (由 1ms 中断置位,主循环响应清除并处理) */
if (key_scan_flag) {
key_scan_flag = 0;
Event_KeyScan_Poll();
}
/* 1. 串口非阻塞指令捕获 -> 装配为事件方式入队 */
cmd = Uart_RxChar();
if (cmd != '\0') {
Debug_ProcessCommand(cmd);
}
/* 2. 事件分发总线:循环轮询并处理队列中的所有有效事件 */
while (!EventQueue_IsEmpty()) {
Event_Dispatcher_Loop();
}
/* 3. 应用调度中心:运行当前处于前台活跃状态的应用(集成了 CPU 运行时间超时挂起保护) */
AppManager_RunActiveApp();
/* 4. 自动休眠判定:常态无操作闲置达到 60 秒 (6000 * 10ms = 60s) 时,切入低功耗停机休眠 */
if (inactivity_timer >= 6000) {
inactivity_timer = 0;
current_state = STATE_SLEEP; // 系统置为休眠状态
Enter_Low_Power_Sleep(); // 该函数是阻塞的MCU 将在此挂起。唤醒后将自动从其后继续执行
current_state = STATE_NORMAL; // 唤醒后重新置为正常待机状态
AppManager_StartApp(APP_ID_HOME); // 重新开启时钟应用以刷新画面和背光
}
}
}
#endif // DEBUG_MINIMAL_BOOT
// 唤醒源事件标志,由对应的外部中断 ISR 写入
volatile u8 p0_wakeup_flag = 0; // P0 端口按键唤醒标志
volatile u8 p2_wakeup_flag = 0; // P2 端口按键唤醒标志
volatile u8 p3_wakeup_flag = 0; // P3 端口射频中断唤醒标志
volatile u8 rf_wakeup_flag = 0; // 射频中断唤醒标志
/* =========================================================================
* STC32G 端口掉电外部中断唤醒中断服务程序 (ISR)
* ========================================================================= */
/**
* @brief Port0 端口掉电中断服务函数 (由 isr_jump.asm 引导定位至此)
* @details 负责处理 P0.1, P0.2, P0.3 按键在休眠模式下的物理触发,并立刻禁用中断防止反复执行。
*/
void Port0_Isr(void) interrupt 13
{
EAXFR = 1; // 使能访问扩展寄存区
p0_wakeup_flag = P0INTF | 0x01;// 读取并备份 P0 中断标志寄存器值 (0x01作安全掩码)
P0INTF = 0x00; // 清除 P0 端口中断悬挂标志位
P0INTE = 0x00; // 【关键保护】:立即关闭 P0 中断允许,防止按键处于低电平期间反复触发 ISR 导致堆栈溢出
}
/**
* @brief Port2 端口掉电中断服务函数 (由 isr_jump.asm 引导定位至此)
* @details 负责处理 P2.6 (SOS按键) 与 P2.1 (射频数据脚) 在休眠模式下的物理跳变唤醒。
*/
void Port2_Isr(void) interrupt 15
{
EAXFR = 1; // 使能扩展寄存区
p2_wakeup_flag = P2INTF | 0x01;// 读取并备份 P2 中断标志寄存区的值
P2INTF = 0x00; // 清除 P2 端口中断悬挂标志位
P2INTE = 0x00; // 立即关闭 P2 中断通道,防止低电平期间反复重入
}
/**
* @brief Port3 端口掉电中断服务函数 (由 isr_jump.asm 引导定位至此)
* @details P3.6 (RF_RX_DATA) 单边沿触发中断现在改为常态开启 (见 RF_Init()),身兼两职:
* 1. 休眠期间作为唤醒源,记录 p3_wakeup_flag 供 Wakeup_Restore() 判定唤醒来源
* (上升沿/下降沿均支持掉电唤醒)
* 2. 正常运行期间驱动 RF_HandleEdgeInterrupt() 增量解码 EV1527 信号 (内部会在
* 每次触发后软件翻转到相反边沿,模拟双边沿触发)。
* 不再像旧版那样在首次触发后自行关闭 P3INTE否则第 1 项唤醒功能会在第一次边沿后失效。
*/
void Port3_Isr(void) interrupt 16
{
EAXFR = 1; // 使能访问扩展寄存器
p3_wakeup_flag = P3INTF | 0x01;// 读取并备份 P3 中断标志寄存器值 (0x01作安全掩码)
P3INTF = 0x00; // 清除 P3 端口中断悬挂标志位
RF_HandleEdgeInterrupt(); // 驱动 EV1527 边沿增量解码状态机
}