Compare commits
17 Commits
8fdbb32f42
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8df5d49d3 | ||
| 41bb78aa59 | |||
| be397fba76 | |||
| 58dfe5bb9c | |||
| a766ada53d | |||
| 48db6523fb | |||
| 2f4dd2660d | |||
| 0a79e7a3b9 | |||
| d7cb2ae37f | |||
| 8367524eec | |||
| d01121e3ae | |||
|
|
47bcd252b2 | ||
|
|
7cbd83c40b | ||
|
|
32e48f0576 | ||
|
|
9b6b61739c | ||
|
|
790583781f | ||
|
|
c0d1fe74f5 |
182
App/app_alarm.c
Normal file
182
App/app_alarm.c
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
#include "app_manager.h"
|
||||||
|
#include "xtell.h"
|
||||||
|
#include "ui.h"
|
||||||
|
#include "rgb.h"
|
||||||
|
#include "event.h"
|
||||||
|
#include "system.h"
|
||||||
|
#include "database.h"
|
||||||
|
#include "../Drivers/lcd.h"
|
||||||
|
|
||||||
|
// 外部全局变量引用
|
||||||
|
extern SystemState current_state;
|
||||||
|
extern u32 captured_addr;
|
||||||
|
extern u8 alarm_sensor_slot;
|
||||||
|
extern volatile u16 ms_tick;
|
||||||
|
extern volatile bit alarm_flash_flag;
|
||||||
|
|
||||||
|
// 传感器存储列表数据库声明
|
||||||
|
extern Sensor_Slot xdata sensor_list[16];
|
||||||
|
|
||||||
|
// 本地私有变量
|
||||||
|
static u32 alarm_pulse_timer = 0; // 报警震动波形计数器
|
||||||
|
static u16 last_run_tick = 0; // 记录上一次轮询的 ticks
|
||||||
|
|
||||||
|
// 声明本应用的生命周期回调函数
|
||||||
|
static void AlarmApp_OnStart(void);
|
||||||
|
static void AlarmApp_onRun(void);
|
||||||
|
static void AlarmApp_onClose(void);
|
||||||
|
static EventResult AlarmApp_onEvent(SystemEvent *evt);
|
||||||
|
|
||||||
|
// 实体化定义警报应用控制块
|
||||||
|
WristbandApp alarm_app = {
|
||||||
|
APP_ID_ALARM,
|
||||||
|
"Alarm",
|
||||||
|
AlarmApp_OnStart,
|
||||||
|
AlarmApp_onRun,
|
||||||
|
AlarmApp_onClose,
|
||||||
|
AlarmApp_onEvent
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief AlarmApp 启动回调
|
||||||
|
*/
|
||||||
|
static void AlarmApp_OnStart(void)
|
||||||
|
{
|
||||||
|
u8 slot;
|
||||||
|
u8 type = 0;
|
||||||
|
u8 zone = 1;
|
||||||
|
|
||||||
|
current_state = STATE_ALARM; // 系统切入报警状态
|
||||||
|
alarm_pulse_timer = 0; // 振动计时器清零
|
||||||
|
last_run_tick = ms_tick;
|
||||||
|
|
||||||
|
// 比对查出当前触发警报的传感器在数据库中的属性
|
||||||
|
if (Check_Sensor_ID(captured_addr, &slot)) {
|
||||||
|
type = sensor_list[slot].type;
|
||||||
|
zone = sensor_list[slot].zone;
|
||||||
|
alarm_sensor_slot = slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染显示报警图文界面
|
||||||
|
UI_ShowAlarmPage(type, zone);
|
||||||
|
|
||||||
|
// 首次亮起防区特定的警示色彩,起到区分报警硬件的目的
|
||||||
|
if (type == 0) RGB_Send(0, 180, 0, 0, 180, 0); // 门磁: 红色 (GRB中R=180,G=0)
|
||||||
|
else if (type == 1) RGB_Send(100, 180, 0, 100, 180, 0); // 红外PIR: 黄色 (G=100, R=180)
|
||||||
|
else if (type == 2) RGB_Send(180, 0, 0, 180, 0, 0); // 烟感: 绿色 (G=180)
|
||||||
|
else if (type == 3) RGB_Send(0, 180, 180, 0, 180, 180); // 气感: 紫色 (R=180, B=180)
|
||||||
|
else RGB_Send(0, 0, 180, 0, 0, 180); // 水浸: 蓝色 (B=180)
|
||||||
|
|
||||||
|
XTELL_LOG("[AlarmApp] Started. Type=");
|
||||||
|
XTELL_LOG_HEX8(type);
|
||||||
|
XTELL_LOG("\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief AlarmApp 轮询主回调
|
||||||
|
* @details 包含同步的灯光爆闪和 Truly AMOLED 像素边框闪烁动效,
|
||||||
|
* 以及马达差异化震动波形逻辑的自处理。
|
||||||
|
*/
|
||||||
|
static void AlarmApp_onRun(void)
|
||||||
|
{
|
||||||
|
u8 type = sensor_list[alarm_sensor_slot].type;
|
||||||
|
u8 flash_light = (ms_tick / 250) % 2; // 250ms 周期亮灭
|
||||||
|
u16 tick_diff;
|
||||||
|
|
||||||
|
// 1. 同步控制 RGB 幻彩灯条进行交替爆闪
|
||||||
|
if (flash_light)
|
||||||
|
{
|
||||||
|
if (type == 0) RGB_Send(0, 180, 0, 0, 180, 0);
|
||||||
|
else if (type == 1) RGB_Send(100, 180, 0, 100, 180, 0);
|
||||||
|
else if (type == 2) RGB_Send(180, 0, 0, 180, 0, 0);
|
||||||
|
else if (type == 3) RGB_Send(0, 180, 180, 0, 180, 180);
|
||||||
|
else RGB_Send(0, 0, 180, 0, 0, 180);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 控制 Truly AMOLED 屏幕的最外围红色警告框闪烁效果
|
||||||
|
if (alarm_flash_flag) {
|
||||||
|
LCD_DrawRectBorder(4, 4, 112, 232, COLOR_RED);
|
||||||
|
LCD_DrawRectBorder(6, 6, 108, 228, COLOR_RED);
|
||||||
|
} else {
|
||||||
|
LCD_DrawRectBorder(4, 4, 112, 232, COLOR_BLACK);
|
||||||
|
LCD_DrawRectBorder(6, 6, 108, 228, COLOR_BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 计算时间差并执行马达差异化震动状态机控制
|
||||||
|
if (ms_tick >= last_run_tick) {
|
||||||
|
tick_diff = ms_tick - last_run_tick;
|
||||||
|
} else {
|
||||||
|
tick_diff = 1000 + ms_tick - last_run_tick;
|
||||||
|
}
|
||||||
|
last_run_tick = ms_tick;
|
||||||
|
|
||||||
|
alarm_pulse_timer += tick_diff;
|
||||||
|
if (alarm_pulse_timer >= 5000) {
|
||||||
|
alarm_pulse_timer = 0; // 5秒一周期
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == 0) {
|
||||||
|
// 门磁警报波形:单次震动 300ms,停 4700ms
|
||||||
|
if (alarm_pulse_timer < 300) MOTOR = 1; else MOTOR = 0;
|
||||||
|
} else if (type == 1) {
|
||||||
|
// PIR 警报波形:震动 300ms,停 200ms,再震动 300ms,停 4200ms
|
||||||
|
if (alarm_pulse_timer < 300) MOTOR = 1;
|
||||||
|
else if (alarm_pulse_timer >= 300 && alarm_pulse_timer < 500) MOTOR = 0;
|
||||||
|
else if (alarm_pulse_timer >= 500 && alarm_pulse_timer < 800) MOTOR = 1;
|
||||||
|
else MOTOR = 0;
|
||||||
|
} else if (type == 2) {
|
||||||
|
// 烟感警报波形:急促短闪三下 200ms-150ms-200ms-150ms-200ms
|
||||||
|
if (alarm_pulse_timer < 200) MOTOR = 1;
|
||||||
|
else if (alarm_pulse_timer >= 200 && alarm_pulse_timer < 350) MOTOR = 0;
|
||||||
|
else if (alarm_pulse_timer >= 350 && alarm_pulse_timer < 550) MOTOR = 1;
|
||||||
|
else if (alarm_pulse_timer >= 550 && alarm_pulse_timer < 700) MOTOR = 0;
|
||||||
|
else if (alarm_pulse_timer >= 700 && alarm_pulse_timer < 900) MOTOR = 1;
|
||||||
|
else MOTOR = 0;
|
||||||
|
} else if (type == 3) {
|
||||||
|
// 燃气报警波形:快频连续四下
|
||||||
|
if (alarm_pulse_timer < 200) MOTOR = 1;
|
||||||
|
else if (alarm_pulse_timer >= 200 && alarm_pulse_timer < 300) MOTOR = 0;
|
||||||
|
else if (alarm_pulse_timer >= 300 && alarm_pulse_timer < 500) MOTOR = 1;
|
||||||
|
else if (alarm_pulse_timer >= 500 && alarm_pulse_timer < 600) MOTOR = 0;
|
||||||
|
else if (alarm_pulse_timer >= 600 && alarm_pulse_timer < 800) MOTOR = 1;
|
||||||
|
else if (alarm_pulse_timer >= 800 && alarm_pulse_timer < 900) MOTOR = 0;
|
||||||
|
else if (alarm_pulse_timer >= 900 && alarm_pulse_timer < 1100) MOTOR = 1;
|
||||||
|
else MOTOR = 0;
|
||||||
|
} else if (type == 4) {
|
||||||
|
// 水浸警报波形:慢速双长震波 600ms 和 600ms
|
||||||
|
if (alarm_pulse_timer < 600) MOTOR = 1;
|
||||||
|
else if (alarm_pulse_timer >= 600 && alarm_pulse_timer < 900) MOTOR = 0;
|
||||||
|
else if (alarm_pulse_timer >= 900 && alarm_pulse_timer < 1500) MOTOR = 1;
|
||||||
|
else MOTOR = 0;
|
||||||
|
} else {
|
||||||
|
// 缺省状态默认单次 300ms
|
||||||
|
if (alarm_pulse_timer < 300) MOTOR = 1; else MOTOR = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief AlarmApp 退出清理
|
||||||
|
*/
|
||||||
|
static void AlarmApp_onClose(void)
|
||||||
|
{
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0); // 关闭灯条
|
||||||
|
MOTOR = 0; // 停震
|
||||||
|
current_state = STATE_NORMAL;
|
||||||
|
XTELL_LOG("[AlarmApp] Closed\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief AlarmApp 事件拦截处理 (按任意键退出报警)
|
||||||
|
*/
|
||||||
|
static EventResult AlarmApp_onEvent(SystemEvent *evt)
|
||||||
|
{
|
||||||
|
if (evt->key_event != KEY_EVENT_NONE) {
|
||||||
|
AppManager_SwitchToForeground(&clock_app); // 清除报警并切回时钟待机页
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
return EVENT_IGNORED;
|
||||||
|
}
|
||||||
204
App/app_clock.c
Normal file
204
App/app_clock.c
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
#include "app_manager.h"
|
||||||
|
#include "clock.h"
|
||||||
|
#include "xtell.h"
|
||||||
|
#include "ui.h"
|
||||||
|
#include "rgb.h"
|
||||||
|
#include "event.h"
|
||||||
|
#include "system.h"
|
||||||
|
#include "../Drivers/rf.h"
|
||||||
|
#include "../Drivers/lcd.h"
|
||||||
|
#include "../Drivers/pcf8563.h"
|
||||||
|
|
||||||
|
// 外部引用的全局系统状态变量
|
||||||
|
extern SystemState current_state;
|
||||||
|
extern volatile u16 ms_tick;
|
||||||
|
|
||||||
|
// 待机时钟应用内特有的编辑时间私有变量
|
||||||
|
static u8 time_edit_active = 0; // 标识当前是否正在编辑修改时间中 (1:是, 0:否)
|
||||||
|
static u8 time_edit_field = 1; // 标识当前聚焦在哪一个参数上进行微调 (1:修改小时, 2:修改分钟)
|
||||||
|
static u8 time_edit_blink_on = 1; // 时钟修改时光标闪烁周期状态位
|
||||||
|
|
||||||
|
// 声明本应用的生命周期回调函数
|
||||||
|
static void ClockApp_OnStart(void);
|
||||||
|
static void ClockApp_onRun(void);
|
||||||
|
static void ClockApp_onClose(void);
|
||||||
|
static EventResult ClockApp_onEvent(SystemEvent *evt);
|
||||||
|
|
||||||
|
// 实体化定义待机时钟应用控制块
|
||||||
|
WristbandApp clock_app = {
|
||||||
|
APP_ID_CLOCK,
|
||||||
|
"Clock",
|
||||||
|
ClockApp_OnStart,
|
||||||
|
ClockApp_onRun,
|
||||||
|
ClockApp_onClose,
|
||||||
|
ClockApp_onEvent
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 触发启动时间设置状态机,准备修改时钟
|
||||||
|
*/
|
||||||
|
void Start_Time_Edit(void)
|
||||||
|
{
|
||||||
|
time_edit_active = 1;
|
||||||
|
time_edit_field = 1;
|
||||||
|
time_edit_blink_on = 1;
|
||||||
|
current_state = STATE_SET_TIME; // 系统切入时间设置状态
|
||||||
|
|
||||||
|
// FCOB 幻彩灯条双灯珠均显示蓝色常亮 (R=0, G=0, B=180),进行模式视觉区分
|
||||||
|
RGB_Send(0, 0, 180, 0, 0, 180);
|
||||||
|
|
||||||
|
// 振动马达单次短震 50 毫秒,提供触觉反馈
|
||||||
|
MOTOR = 1;
|
||||||
|
Delay_ms(50);
|
||||||
|
MOTOR = 0;
|
||||||
|
|
||||||
|
// 渲染显示时间编辑界面,默认高亮编辑小时字段,消隐位开启
|
||||||
|
UI_ShowSetTimePage(current_hour, current_min, time_edit_field, 1);
|
||||||
|
XTELL_LOG("[ClockApp] Time edit started.\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ClockApp 启动回调
|
||||||
|
*/
|
||||||
|
static void ClockApp_OnStart(void)
|
||||||
|
{
|
||||||
|
time_edit_active = 0;
|
||||||
|
current_state = STATE_NORMAL; // 系统切入正常待机状态
|
||||||
|
RF_SetMode(1); // 开启天线并置为接收模式,监听无线探测器
|
||||||
|
|
||||||
|
// 尝试从 PCF8563 RTC 芯片中读取硬件时间 (具备非阻塞防卡死保护)
|
||||||
|
if (PCF8563_Init()) {
|
||||||
|
PCF8563_ReadTime(¤t_hour, ¤t_min, ¤t_sec);
|
||||||
|
XTELL_LOG("[ClockApp] RTC Hardware Synced OK!\r\n");
|
||||||
|
} else {
|
||||||
|
XTELL_LOG("[ClockApp] RTC Hardware Offline, using system clock.\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绘制并渲染待机时钟主页面画面
|
||||||
|
UI_ShowClockPage(current_state, current_hour, current_min);
|
||||||
|
|
||||||
|
// 关断/熄灭 FCOB 双灯条
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0);
|
||||||
|
|
||||||
|
XTELL_LOG("[ClockApp] Started\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ClockApp 轮询主回调
|
||||||
|
*/
|
||||||
|
static void ClockApp_onRun(void)
|
||||||
|
{
|
||||||
|
if (time_edit_active)
|
||||||
|
{
|
||||||
|
// ====== 时间设置闪烁动画 (周期 500ms) ======
|
||||||
|
u8 blink = (ms_tick / 500) % 2;
|
||||||
|
if (blink != time_edit_blink_on)
|
||||||
|
{
|
||||||
|
time_edit_blink_on = blink;
|
||||||
|
// 每次闪烁状态发生变化,重新刷写屏幕数值
|
||||||
|
UI_ShowSetTimePage(current_hour, current_min, time_edit_field, time_edit_blink_on);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 软时钟发生分钟改变,需要通知屏幕重画时钟主页
|
||||||
|
if (clock_updated) {
|
||||||
|
clock_updated = 0;
|
||||||
|
UI_ShowClockPage(current_state, current_hour, current_min);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ClockApp 退出清理回调
|
||||||
|
*/
|
||||||
|
static void ClockApp_onClose(void)
|
||||||
|
{
|
||||||
|
time_edit_active = 0;
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0); // 关闭灯条
|
||||||
|
XTELL_LOG("[ClockApp] Closed\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ClockApp 接收事件处理回调 (按键导航与布撤防交互)
|
||||||
|
*/
|
||||||
|
static EventResult ClockApp_onEvent(SystemEvent *evt)
|
||||||
|
{
|
||||||
|
if (time_edit_active)
|
||||||
|
{
|
||||||
|
// ====== 处于时间设置状态下的按键拦截处理机 ======
|
||||||
|
if (evt->key_event == KEY_EVENT_UP_CLICK)
|
||||||
|
{
|
||||||
|
// 短按 ▲ 键增加数值 (小时/分钟循环相加)
|
||||||
|
if (time_edit_field == 1) {
|
||||||
|
current_hour = (current_hour + 1) % 24;
|
||||||
|
} else {
|
||||||
|
current_min = (current_min + 1) % 60;
|
||||||
|
}
|
||||||
|
UI_ShowSetTimePage(current_hour, current_min, time_edit_field, 1);
|
||||||
|
return EVENT_HANDLED; // 吞掉并消化事件
|
||||||
|
}
|
||||||
|
if (evt->key_event == KEY_EVENT_DOWN_CLICK)
|
||||||
|
{
|
||||||
|
// 短按 ▼ 键减小数值
|
||||||
|
if (time_edit_field == 1) {
|
||||||
|
current_hour = (current_hour == 0) ? 23 : (current_hour - 1);
|
||||||
|
} else {
|
||||||
|
current_min = (current_min == 0) ? 59 : (current_min - 1);
|
||||||
|
}
|
||||||
|
UI_ShowSetTimePage(current_hour, current_min, time_edit_field, 1);
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
if (evt->key_event == KEY_EVENT_CONFIRM_LONG)
|
||||||
|
{
|
||||||
|
// 长按 ■ 确认键切换调节字段 (在修改小时与修改分钟间跳转)
|
||||||
|
if (time_edit_field == 1) {
|
||||||
|
time_edit_field = 2;
|
||||||
|
} else {
|
||||||
|
time_edit_field = 1;
|
||||||
|
}
|
||||||
|
UI_ShowSetTimePage(current_hour, current_min, time_edit_field, 1);
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
if (evt->key_event == KEY_EVENT_CONFIRM_CLICK)
|
||||||
|
{
|
||||||
|
// 短按 ■ 确认键保存最新时钟配置并退出编辑模式
|
||||||
|
time_edit_active = 0;
|
||||||
|
current_state = STATE_NORMAL;
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0); // 熄灭蓝色指示灯
|
||||||
|
|
||||||
|
// 同步将最新时间 (hour, min, 0秒) 写入 PCF8563 芯片寄存器
|
||||||
|
PCF8563_WriteTime(current_hour, current_min, 0);
|
||||||
|
|
||||||
|
// 退出短振反馈
|
||||||
|
MOTOR = 1; Delay_ms(50); MOTOR = 0;
|
||||||
|
|
||||||
|
UI_ShowClockPage(current_state, current_hour, current_min);
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
return EVENT_HANDLED; // 在时间设置模式下屏蔽拦截其他一切无关按键事件
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====== 正常时钟待机状态下的按键拦截处理 ======
|
||||||
|
|
||||||
|
// 1. 长按 ■ 确认键,打开设置主菜单 MenuApp
|
||||||
|
if (evt->key_event == KEY_EVENT_CONFIRM_LONG) {
|
||||||
|
AppManager_SwitchToForeground(&menu_app);
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 长按 ▲ 键,一键切换系统的 布防 (ARMED) 和 撤防 (DISARMED) 状态
|
||||||
|
if (evt->key_event == KEY_EVENT_UP_LONG) {
|
||||||
|
if (current_state == STATE_ARMED) {
|
||||||
|
current_state = STATE_DISARMED;
|
||||||
|
} else {
|
||||||
|
current_state = STATE_ARMED;
|
||||||
|
}
|
||||||
|
// 布撤防状态切换成功后进行 100ms 短震确认,并重新刷写时钟待机页
|
||||||
|
MOTOR = 1; Delay_ms(100); MOTOR = 0;
|
||||||
|
UI_ShowClockPage(current_state, current_hour, current_min);
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EVENT_IGNORED; // 其他未被关心的事件交由后台应用或丢弃
|
||||||
|
}
|
||||||
1055
App/app_manager.c
1055
App/app_manager.c
File diff suppressed because it is too large
Load Diff
@@ -3,56 +3,75 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
/* ====== 应用程序管理器 (AppManager) 核心接口 ====== */
|
// 动态挂载的 WristbandApp 控制块定义
|
||||||
|
typedef struct WristbandApp {
|
||||||
|
u8 app_id; // 应用唯一识别 ID
|
||||||
|
char *app_name; // 应用名称
|
||||||
|
void (*OnStart)(void); // 启动生命周期,进入前台时调用
|
||||||
|
void (*onRun)(void); // 轮询生命周期,主循环每次被调度
|
||||||
|
void (*onClose)(void); // 关闭生命周期,退出前台/后台时清理资源
|
||||||
|
EventResult (*onEvent)(SystemEvent *evt); // 事件处理生命周期,接收按键事件
|
||||||
|
} WristbandApp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 初始化应用程序管理器
|
* @brief 初始化应用管理器
|
||||||
* @details 负责将应用管理器的系统注册表清零,随后逐一注册系统支持的五个前台应用:
|
* @details 将前台活跃槽位与后台的三个槽位全部清空为 NULL。
|
||||||
* ClockApp (待机时钟), MenuApp (主菜单), PairApp (传感器对码),
|
|
||||||
* AlarmApp (传感器警报), SOSApp (主动求救呼叫)。最后默认加载并启动 ClockApp。
|
|
||||||
*/
|
*/
|
||||||
void AppManager_Init(void);
|
void AppManager_Init(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 启动指定的应用程序
|
* @brief 动态将一个应用切换至前台活跃运行
|
||||||
* @param app_id 待启动应用的目标 ID (如 APP_ID_MENU, APP_ID_PAIR)
|
* @param app 目标应用的结构体指针
|
||||||
* @details 会执行完整的切应用生命周期流转:
|
* @return bit 1-切换成功,0-失败
|
||||||
* 1. 检查目标是否合法且包含有效的 OnStart 启动回调。
|
* @details 流程:
|
||||||
* 2. 对当前正在活跃运行的前台应用调用其 onClose(),以关闭该应用的屏幕内容、马达振动或射频特定模式。
|
* 1. 若前台已有应用运行,且该应用未挂载在后台,则对其执行 onClose() 关闭生命周期。
|
||||||
* 3. 将活跃应用指针指向新应用,将其 is_active 和 is_running 标志位置 1。
|
* 2. 将 fg_app 指针指向新的 app。
|
||||||
* 4. 调用新应用的 OnStart() 初始化回调以绘制新页面。
|
* 3. 若该 app 目前并未在后台槽位中运行,则执行其 OnStart() 启动生命周期。
|
||||||
*/
|
*/
|
||||||
void AppManager_StartApp(AppID app_id);
|
bit AppManager_SwitchToForeground(WristbandApp *app);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 向当前活跃的前台应用程序派发事件
|
* @brief 动态将一个应用挂载到后台槽位
|
||||||
* @param evt 要分发派送的系统事件
|
* @param app 目标应用的结构体指针
|
||||||
|
* @return bit 1-挂载成功,0-槽位已满挂载失败
|
||||||
|
* @details 后台总共支持 3 个槽位。若挂载成功,其 onRun 将会持续在主循环中被轮询。
|
||||||
|
*/
|
||||||
|
bit AppManager_AttachToBackground(WristbandApp *app);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 动态将一个应用从后台卸载
|
||||||
|
* @param app 目标应用的结构体指针
|
||||||
|
* @details 从后台槽位数组中移除该指针。若该应用当前也不在前台运行,将对其执行 onClose() 清理外设。
|
||||||
|
*/
|
||||||
|
void AppManager_DetachFromBackground(WristbandApp *app);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将捕获的事件派发给前台活跃应用
|
||||||
|
* @param evt 系统派发来的事件
|
||||||
*/
|
*/
|
||||||
void AppManager_DispatchEvent(SystemEvent evt);
|
void AppManager_DispatchEvent(SystemEvent evt);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 在系统主循环中持续轮询运行活跃应用
|
* @brief 在系统主循环中持续轮询轮转前后台所有活跃的应用
|
||||||
* @details 负责从全局注册表中定位到 active_app,并轮询执行其 onRun() 周期回调。
|
* @details 负责轮询前台 fg_app 对应的 onRun(),并遍历轮询后台 bg_apps 数组中所有有效应用的 onRun()。
|
||||||
* 该接口同时集成了【CPU占用率限制机制】:如果在调用 onRun 前后,检测到应用
|
|
||||||
* 占用 CPU 时间超过了 APP_ONRUN_MAX_TICKS (5ms),它将在下一次循环中暂时挂起
|
|
||||||
* 该应用,强制释放本次 CPU 占用以防止界面卡死或影响底层射频解调。
|
|
||||||
*/
|
*/
|
||||||
void AppManager_RunActiveApp(void);
|
void AppManager_RunActiveApp(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取当前前台活跃应用的唯一应用程序 ID
|
* @brief 获取当前前台活跃应用的 ID
|
||||||
* @return AppID 当前活跃前台应用的枚举ID
|
* @return u8 当前活跃前台应用的 ID 编号。若无则返回 255 (0xFF)
|
||||||
*/
|
*/
|
||||||
AppID AppManager_GetActiveAppID(void);
|
u8 AppManager_GetActiveAppID(void);
|
||||||
|
|
||||||
|
// 兼容性接口声明
|
||||||
|
void AppManager_StartApp(AppID app_id);
|
||||||
|
|
||||||
/* ====== 时间编辑状态机交互接口 ====== */
|
// ====== 系统中各独立 App 实例的外包声明 (各 App 内部进行实体定义) ======
|
||||||
|
extern WristbandApp clock_app;
|
||||||
/**
|
extern WristbandApp menu_app;
|
||||||
* @brief 启动时钟数值调整编辑状态机
|
extern WristbandApp pair_app;
|
||||||
* @details 由 MenuApp 的 "HORLOGE" 项被确认点击后触发。用于直接将系统运行状态切入
|
extern WristbandApp alarm_app;
|
||||||
* STATE_SET_TIME,控制灯条常亮蓝色,马达短振反馈,并进入时间参数微调页。
|
extern WristbandApp sos_app;
|
||||||
*/
|
extern WristbandApp rf_monitor_app; // 后台射频监控应用
|
||||||
void Start_Time_Edit(void);
|
|
||||||
|
|
||||||
#endif // __APP_MANAGER_H__
|
#endif // __APP_MANAGER_H__
|
||||||
|
|||||||
196
App/app_menu.c
Normal file
196
App/app_menu.c
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
#include "app_manager.h"
|
||||||
|
#include "xtell.h"
|
||||||
|
#include "ui.h"
|
||||||
|
#include "rgb.h"
|
||||||
|
#include "event.h"
|
||||||
|
#include "system.h"
|
||||||
|
#include "../Drivers/rf.h"
|
||||||
|
|
||||||
|
// 二级传感器对码菜单中,各类传感器的法语显示名称定义
|
||||||
|
char *code menu_items[5] = {
|
||||||
|
"DETEC. PORTE", // 0: 门磁探测器 (法语: 门)
|
||||||
|
"DETEC. PIR", // 1: 红外探测器
|
||||||
|
"DETEC. FUMEE", // 2: 烟雾传感器
|
||||||
|
"DETEC. GAZ", // 3: 燃气探测器
|
||||||
|
"DETEC. EAU" // 4: 水浸探测器
|
||||||
|
};
|
||||||
|
|
||||||
|
// 外部全局变量引用
|
||||||
|
extern SystemState current_state;
|
||||||
|
extern u8 menu_select;
|
||||||
|
extern u8 captured_type;
|
||||||
|
extern volatile u16 ms_tick;
|
||||||
|
|
||||||
|
// 菜单应用私有变量
|
||||||
|
static u8 menu_level = 0; // 0-主菜单, 1-二级传感器选择菜单, 2-IPC绑定一键发射
|
||||||
|
static u16 last_bind_tx_ms = 0; // 记录上一次绑定发射的时间戳
|
||||||
|
|
||||||
|
// 引入外部时钟调节接口
|
||||||
|
extern void Start_Time_Edit(void);
|
||||||
|
|
||||||
|
// 声明本应用的生命周期回调函数
|
||||||
|
static void MenuApp_OnStart(void);
|
||||||
|
static void MenuApp_onRun(void);
|
||||||
|
static void MenuApp_onClose(void);
|
||||||
|
static EventResult MenuApp_onEvent(SystemEvent *evt);
|
||||||
|
|
||||||
|
// 实体化定义菜单应用控制块
|
||||||
|
WristbandApp menu_app = {
|
||||||
|
APP_ID_MENU,
|
||||||
|
"Menu",
|
||||||
|
MenuApp_OnStart,
|
||||||
|
MenuApp_onRun,
|
||||||
|
MenuApp_onClose,
|
||||||
|
MenuApp_onEvent
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief MenuApp 启动回调
|
||||||
|
*/
|
||||||
|
static void MenuApp_OnStart(void)
|
||||||
|
{
|
||||||
|
menu_level = 0;
|
||||||
|
menu_select = 0;
|
||||||
|
current_state = STATE_PAIR_MENU; // 系统运行状态设为对码配置菜单
|
||||||
|
RF_SetMode(0); // 关闭射频接收芯片,防止接收杂波干扰屏幕绘制
|
||||||
|
UI_ShowMainMenu(menu_select); // 初始化绘制一级主菜单
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0); // 熄灭所有灯条
|
||||||
|
XTELL_LOG("[MenuApp] Started\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief MenuApp 轮询主回调
|
||||||
|
*/
|
||||||
|
static void MenuApp_onRun(void)
|
||||||
|
{
|
||||||
|
if (menu_level == 2)
|
||||||
|
{
|
||||||
|
// ====== 控制青色双灯珠同步呼吸效果 (呼吸周期 1000ms) ======
|
||||||
|
u16 breath = ms_tick % 1000;
|
||||||
|
u8 val = (breath < 500) ? (breath * 2 / 5) : ((1000 - breath) * 2 / 5); // 亮度映射 0~200
|
||||||
|
RGB_Send(0, val, val, 0, val, val); // 渲染青色 (G+B)
|
||||||
|
|
||||||
|
// ====== 控制无线发射,每 500ms 广播一次配对数据 ======
|
||||||
|
if (ms_tick - last_bind_tx_ms >= 500 || ms_tick < last_bind_tx_ms)
|
||||||
|
{
|
||||||
|
last_bind_tx_ms = ms_tick; // 备份当前时间戳
|
||||||
|
RF_SetMode(2); // 暂时切换射频芯片为发射调制模式
|
||||||
|
EV1527_Transmit(CLONED_ADDR, 0x01); // 调制发射手环克隆 ID + 绑定数据码 0x01
|
||||||
|
RF_SetMode(0); // 切回关闭状态,避免产生噪声
|
||||||
|
XTELL_LOG("[MenuApp] IPC Binding frame emitted\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief MenuApp 退出清理回调
|
||||||
|
*/
|
||||||
|
static void MenuApp_onClose(void)
|
||||||
|
{
|
||||||
|
menu_level = 0;
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0); // 熄灭 RGB
|
||||||
|
RF_SetMode(1); // 重新开启无线接收模式
|
||||||
|
XTELL_LOG("[MenuApp] Closed\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief MenuApp 接收按键与控制事件回调
|
||||||
|
*/
|
||||||
|
static EventResult MenuApp_onEvent(SystemEvent *evt)
|
||||||
|
{
|
||||||
|
if (menu_level == 0)
|
||||||
|
{
|
||||||
|
// ====== 处于一级主菜单层 (0-HORLOGE, 1-APPAIRAGE, 2-LIAISON) ======
|
||||||
|
if (evt->key_event == KEY_EVENT_UP_CLICK) {
|
||||||
|
// 短按 ▲ 键向上移动焦点
|
||||||
|
if (menu_select > 0) {
|
||||||
|
menu_select--;
|
||||||
|
UI_ShowMainMenu(menu_select);
|
||||||
|
}
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
if (evt->key_event == KEY_EVENT_DOWN_CLICK) {
|
||||||
|
// 短按 ▼ 键向下移动焦点
|
||||||
|
if (menu_select < 2) {
|
||||||
|
menu_select++;
|
||||||
|
UI_ShowMainMenu(menu_select);
|
||||||
|
}
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
if (evt->key_event == KEY_EVENT_CONFIRM_CLICK) {
|
||||||
|
// 短按 ■ 确认键,进入选定功能的子页面
|
||||||
|
if (menu_select == 0) {
|
||||||
|
// 功能 1:设置时间。启动待机时钟应用,并调用微调编辑入口
|
||||||
|
AppManager_SwitchToForeground(&clock_app);
|
||||||
|
Start_Time_Edit();
|
||||||
|
} else if (menu_select == 1) {
|
||||||
|
// 功能 2:传感器学习。进入二级传感器类别选择菜单
|
||||||
|
menu_level = 1;
|
||||||
|
menu_select = 0;
|
||||||
|
UI_ShowPairMenuPage(menu_select); // 绘制二级设备分类页
|
||||||
|
} else if (menu_select == 2) {
|
||||||
|
// 功能 3:相机配对。进入摄像机一键绑定发射状态
|
||||||
|
menu_level = 2;
|
||||||
|
last_bind_tx_ms = ms_tick; // 记录初始发射时间
|
||||||
|
current_state = STATE_IPC_BIND; // 系统设为 IPC 绑定状态
|
||||||
|
UI_ShowBindingPage(CLONED_ADDR); // 绘制相机绑定信息页
|
||||||
|
}
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
if (evt->key_event == KEY_EVENT_CONFIRM_LONG) {
|
||||||
|
// 长按 ■ 确认键,退出设置菜单直接返回待机时钟
|
||||||
|
AppManager_SwitchToForeground(&clock_app);
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (menu_level == 1)
|
||||||
|
{
|
||||||
|
// ====== 处于二级传感器对码类型菜单层 (0-门磁, 1-PIR, 2-烟感, 3-气感, 4-水浸) ======
|
||||||
|
if (evt->key_event == KEY_EVENT_UP_CLICK) {
|
||||||
|
// 上移焦点
|
||||||
|
if (menu_select > 0) {
|
||||||
|
menu_select--;
|
||||||
|
UI_ShowPairMenuPage(menu_select);
|
||||||
|
}
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
if (evt->key_event == KEY_EVENT_DOWN_CLICK) {
|
||||||
|
// 下移焦点
|
||||||
|
if (menu_select < 4) {
|
||||||
|
menu_select++;
|
||||||
|
UI_ShowPairMenuPage(menu_select);
|
||||||
|
}
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
if (evt->key_event == KEY_EVENT_CONFIRM_CLICK) {
|
||||||
|
// 确认选择:缓存用户选好的传感器类别,短震反馈并加载对码应用 (PairApp)
|
||||||
|
captured_type = menu_select;
|
||||||
|
MOTOR = 1;
|
||||||
|
Delay_ms(100);
|
||||||
|
MOTOR = 0;
|
||||||
|
AppManager_SwitchToForeground(&pair_app); // 切换至 PairApp 对码
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
if (evt->key_event == KEY_EVENT_DOWN_LONG) {
|
||||||
|
// 长按 ▼ 键,从二级菜单退回到一级主菜单
|
||||||
|
menu_level = 0;
|
||||||
|
menu_select = 1; // 回到 APPAIRAGE 选项上
|
||||||
|
UI_ShowMainMenu(menu_select);
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (menu_level == 2)
|
||||||
|
{
|
||||||
|
// ====== 处于摄像机配对一键绑定发送层 ======
|
||||||
|
// 长按 ■ 确认键 或 长按 ▼ 键,均表示取消绑定,退出发射并返回一级主菜单
|
||||||
|
if (evt->key_event == KEY_EVENT_CONFIRM_LONG || evt->key_event == KEY_EVENT_DOWN_LONG) {
|
||||||
|
menu_level = 0;
|
||||||
|
menu_select = 2; // 回到 LIAISON 选项上
|
||||||
|
current_state = STATE_PAIR_MENU;
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0); // 关闭呼吸灯
|
||||||
|
UI_ShowMainMenu(menu_select);
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return EVENT_IGNORED;
|
||||||
|
}
|
||||||
231
App/app_pair.c
Normal file
231
App/app_pair.c
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
#include "app_manager.h"
|
||||||
|
#include "xtell.h"
|
||||||
|
#include "ui.h"
|
||||||
|
#include "rgb.h"
|
||||||
|
#include "event.h"
|
||||||
|
#include "system.h"
|
||||||
|
#include "database.h"
|
||||||
|
#include "clock.h"
|
||||||
|
#include "../Drivers/rf.h"
|
||||||
|
|
||||||
|
// 外部全局变量引用
|
||||||
|
extern SystemState current_state;
|
||||||
|
extern u8 captured_type;
|
||||||
|
extern u32 captured_addr;
|
||||||
|
extern volatile u16 ms_tick;
|
||||||
|
|
||||||
|
// 对码状态机定义
|
||||||
|
enum {
|
||||||
|
PAIR_STATE_WAIT, // 等待射频探测器触发 (扩散雷达页面)
|
||||||
|
PAIR_STATE_CONFIRM, // 已捕获,进入分配防区确认页面
|
||||||
|
PAIR_STATE_SUCCESS, // 对码绑定成功界面 (SUCCESS)
|
||||||
|
PAIR_STATE_FAIL // 超时或失败界面 (FAIL)
|
||||||
|
};
|
||||||
|
|
||||||
|
// 本地私有变量
|
||||||
|
static u8 pair_state; // 当前的对码阶段状态
|
||||||
|
static u8 last_radar_sec; // 备份上一次雷达动画的帧索引
|
||||||
|
static u8 captured_zone = 1; // 准备分配的防区号 (1~99)
|
||||||
|
static u8 success_start_sec = 0;// 对码成功时的起始秒数 (延迟退场)
|
||||||
|
static u8 fail_start_sec = 0; // 对码失败时的起始秒数
|
||||||
|
static u32 pair_pulse_timer = 0; // 对码超时毫秒计数器
|
||||||
|
static u16 last_run_tick = 0; // 上一次执行 onRun 时的毫秒滴答数
|
||||||
|
|
||||||
|
// 声明本应用的生命周期回调函数
|
||||||
|
static void PairApp_OnStart(void);
|
||||||
|
static void PairApp_onRun(void);
|
||||||
|
static void PairApp_onClose(void);
|
||||||
|
static EventResult PairApp_onEvent(SystemEvent *evt);
|
||||||
|
|
||||||
|
// 实体化定义对码应用控制块
|
||||||
|
WristbandApp pair_app = {
|
||||||
|
APP_ID_PAIR,
|
||||||
|
"Pair",
|
||||||
|
PairApp_OnStart,
|
||||||
|
PairApp_onRun,
|
||||||
|
PairApp_onClose,
|
||||||
|
PairApp_onEvent
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief PairApp 启动回调
|
||||||
|
*/
|
||||||
|
static void PairApp_OnStart(void)
|
||||||
|
{
|
||||||
|
// 1. 为了防止后台射频监听与对码射频接收冲突,将后台射频监听应用暂时卸载
|
||||||
|
AppManager_DetachFromBackground(&rf_monitor_app);
|
||||||
|
|
||||||
|
pair_state = PAIR_STATE_WAIT;
|
||||||
|
last_radar_sec = 99; // 强制刷写
|
||||||
|
current_state = STATE_PAIR_WAIT;
|
||||||
|
pair_pulse_timer = 0;
|
||||||
|
last_run_tick = ms_tick;
|
||||||
|
captured_zone = 1;
|
||||||
|
RF_SetMode(1); // 开启无线接收模式
|
||||||
|
|
||||||
|
// 初始化对码搜索页面绘制
|
||||||
|
UI_ShowPairWaitPage(0);
|
||||||
|
|
||||||
|
// 开启白色幻彩双灯珠常亮 (R=150, G=150, B=150),作为对码状态视觉指示
|
||||||
|
RGB_Send(150, 150, 150, 150, 150, 150);
|
||||||
|
|
||||||
|
XTELL_LOG("[PairApp] Started (WAIT)\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief PairApp 轮询主回调
|
||||||
|
*/
|
||||||
|
static void PairApp_onRun(void)
|
||||||
|
{
|
||||||
|
u32 rx_addr;
|
||||||
|
u8 rx_data;
|
||||||
|
u8 flash;
|
||||||
|
u8 frame;
|
||||||
|
u16 tick_diff;
|
||||||
|
|
||||||
|
// 计算非阻塞的毫秒时间增量
|
||||||
|
if (ms_tick >= last_run_tick) {
|
||||||
|
tick_diff = ms_tick - last_run_tick;
|
||||||
|
} else {
|
||||||
|
tick_diff = 1000 + ms_tick - last_run_tick;
|
||||||
|
}
|
||||||
|
last_run_tick = ms_tick;
|
||||||
|
|
||||||
|
if (pair_state == PAIR_STATE_WAIT) {
|
||||||
|
// ====== 等待射频数据期间,白光 3Hz 高频闪烁 (周期 333ms) ======
|
||||||
|
flash = (ms_tick / 166) % 2;
|
||||||
|
if (flash) RGB_Send(150, 150, 150, 150, 150, 150);
|
||||||
|
else RGB_Send(0, 0, 0, 0, 0, 0);
|
||||||
|
|
||||||
|
// 每隔 250ms,滚动绘制雷达搜索的 3 帧图片,构成动效
|
||||||
|
frame = (u8)((ms_tick / 250) % 3);
|
||||||
|
if (frame != last_radar_sec) {
|
||||||
|
last_radar_sec = frame;
|
||||||
|
UI_ShowPairWaitPage(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 累加计算 30 秒的对码时间上限
|
||||||
|
pair_pulse_timer += tick_diff;
|
||||||
|
if (pair_pulse_timer >= 30000UL) {
|
||||||
|
pair_pulse_timer = 0;
|
||||||
|
pair_state = PAIR_STATE_FAIL;
|
||||||
|
current_state = STATE_PAIR_FAIL;
|
||||||
|
fail_start_sec = current_sec;
|
||||||
|
UI_ShowPairFailPage(1); // 绘制超时大红叉页
|
||||||
|
|
||||||
|
// 红色双灯常亮警告
|
||||||
|
RGB_Send(200, 0, 0, 200, 0, 0);
|
||||||
|
XTELL_LOG("[PairApp] Timeout fail!\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====== 物理射频包解调接收监听 ======
|
||||||
|
if (EV1527_Decode(&rx_addr, &rx_data)) {
|
||||||
|
// 保护机制:排除 SOS 码 (0x08)
|
||||||
|
if (rx_data != 0x08) {
|
||||||
|
captured_addr = rx_addr; // 缓存捕获的物理 ID 地址 (含数据码)
|
||||||
|
pair_state = PAIR_STATE_CONFIRM; // 进入防区微调确认模式
|
||||||
|
current_state = STATE_PAIR_CONFIRM;
|
||||||
|
RF_SetMode(0); // 暂时关闭物理射频,防止再次覆盖
|
||||||
|
captured_zone = 1;
|
||||||
|
UI_ShowPairConfirmPage(captured_type, captured_zone);
|
||||||
|
XTELL_LOG("[PairApp] Sensor captured. Addr=");
|
||||||
|
XTELL_LOG_HEX32(rx_addr);
|
||||||
|
XTELL_LOG("\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (pair_state == PAIR_STATE_CONFIRM) {
|
||||||
|
// ====== 确认状态:橙色慢闪 (1.5Hz,周期 666ms) ======
|
||||||
|
u8 flash_orange = (ms_tick / 333) % 2;
|
||||||
|
if (flash_orange) RGB_Send(100, 180, 0, 100, 180, 0); // 橙色 (G=180, R=100)
|
||||||
|
else RGB_Send(0, 0, 0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
else if (pair_state == PAIR_STATE_SUCCESS) {
|
||||||
|
// ====== 成功展示状态:延时 2 秒自动返回二级设置菜单 ======
|
||||||
|
u8 elapsed = (current_sec >= success_start_sec) ? (current_sec - success_start_sec) : (60 + current_sec - success_start_sec);
|
||||||
|
if (elapsed >= 2) {
|
||||||
|
AppManager_SwitchToForeground(&menu_app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (pair_state == PAIR_STATE_FAIL) {
|
||||||
|
// ====== 失败展示状态:延时 5 秒自动返回二级设置菜单 ======
|
||||||
|
u8 elapsed = (current_sec >= fail_start_sec) ? (current_sec - fail_start_sec) : (60 + current_sec - fail_start_sec);
|
||||||
|
if (elapsed >= 5) {
|
||||||
|
AppManager_SwitchToForeground(&menu_app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief PairApp 退出清理
|
||||||
|
*/
|
||||||
|
static void PairApp_onClose(void)
|
||||||
|
{
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0); // 关闭 RGB
|
||||||
|
MOTOR = 0; // 停震
|
||||||
|
RF_SetMode(1); // 开启无线接收
|
||||||
|
|
||||||
|
// 重新挂载后台射频监控应用,以便回到时钟状态下能继续捕获报警
|
||||||
|
AppManager_AttachToBackground(&rf_monitor_app);
|
||||||
|
|
||||||
|
XTELL_LOG("[PairApp] Closed\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief PairApp 事件拦截处理回调
|
||||||
|
*/
|
||||||
|
static EventResult PairApp_onEvent(SystemEvent *evt)
|
||||||
|
{
|
||||||
|
if (pair_state == PAIR_STATE_CONFIRM) {
|
||||||
|
if (evt->key_event == KEY_EVENT_UP_CLICK) {
|
||||||
|
// 短按 ▲ 键增加目标分配防区号 (上限 99)
|
||||||
|
if (captured_zone < 99) {
|
||||||
|
captured_zone++;
|
||||||
|
UI_ShowPairConfirmPage(captured_type, captured_zone);
|
||||||
|
}
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
if (evt->key_event == KEY_EVENT_DOWN_CLICK) {
|
||||||
|
// 短按 ▼ 键减小目标分配防区号 (下限 1)
|
||||||
|
if (captured_zone > 1) {
|
||||||
|
captured_zone--;
|
||||||
|
UI_ShowPairConfirmPage(captured_type, captured_zone);
|
||||||
|
}
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
if (evt->key_event == KEY_EVENT_CONFIRM_CLICK) {
|
||||||
|
// 短按 ■ 确认键,写入 EEPROM 传感器数据库并保存
|
||||||
|
Add_Sensor_With_Zone(captured_addr, captured_type, captured_zone);
|
||||||
|
|
||||||
|
pair_state = PAIR_STATE_SUCCESS;
|
||||||
|
current_state = STATE_PAIR_SUCCESS;
|
||||||
|
success_start_sec = current_sec;
|
||||||
|
UI_ShowPairSuccessPage(); // 绘制打勾页
|
||||||
|
|
||||||
|
// 绿色双灯珠常亮,震动马达长震 400ms 反馈
|
||||||
|
RGB_Send(0, 200, 0, 0, 200, 0);
|
||||||
|
MOTOR = 1;
|
||||||
|
Delay_ms(400);
|
||||||
|
MOTOR = 0;
|
||||||
|
|
||||||
|
XTELL_LOG("[PairApp] Sensor saved successfully.\r\n");
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
if (evt->key_event == KEY_EVENT_CONFIRM_LONG) {
|
||||||
|
// 长按 ■ 确认键放弃本次捕获,直接返回二级主菜单
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0);
|
||||||
|
AppManager_SwitchToForeground(&menu_app);
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在对码搜寻或确认防区期间,长按 ▼ 键均代表强行取消对码直接退回菜单页
|
||||||
|
if (evt->key_event == KEY_EVENT_DOWN_LONG) {
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0);
|
||||||
|
AppManager_SwitchToForeground(&menu_app);
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EVENT_IGNORED;
|
||||||
|
}
|
||||||
105
App/app_rf_monitor.c
Normal file
105
App/app_rf_monitor.c
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
#include "app_manager.h"
|
||||||
|
#include "xtell.h"
|
||||||
|
#include "event.h"
|
||||||
|
#include "database.h"
|
||||||
|
#include "system.h"
|
||||||
|
#include "../Drivers/rf.h"
|
||||||
|
|
||||||
|
// 外部全局变量引用
|
||||||
|
extern SystemState current_state;
|
||||||
|
extern u32 captured_addr;
|
||||||
|
extern u8 alarm_sensor_slot;
|
||||||
|
extern Sensor_Slot xdata sensor_list[16];
|
||||||
|
|
||||||
|
// 声明本应用的生命周期回调函数
|
||||||
|
static void RfMonitorApp_OnStart(void);
|
||||||
|
static void RfMonitorApp_onRun(void);
|
||||||
|
static void RfMonitorApp_onClose(void);
|
||||||
|
static EventResult RfMonitorApp_onEvent(SystemEvent *evt);
|
||||||
|
|
||||||
|
// 实体化定义后台射频监控应用控制块
|
||||||
|
WristbandApp rf_monitor_app = {
|
||||||
|
0xFE, // 后台应用 ID 设为 0xFE
|
||||||
|
"RF_Monitor",
|
||||||
|
RfMonitorApp_OnStart,
|
||||||
|
RfMonitorApp_onRun,
|
||||||
|
RfMonitorApp_onClose,
|
||||||
|
RfMonitorApp_onEvent
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RfMonitorApp 启动回调
|
||||||
|
*/
|
||||||
|
static void RfMonitorApp_OnStart(void)
|
||||||
|
{
|
||||||
|
RF_SetMode(1); // 开启天线并设为接收模式
|
||||||
|
XTELL_LOG("[RF_Monitor] Background RF monitor started.\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RfMonitorApp 轮询主回调
|
||||||
|
* @details 负责在后台静默轮询物理射频 EV1527 解码器。
|
||||||
|
* 解码成功后在本地传感器数据库中匹配,并执行布撤防及防区过滤逻辑,
|
||||||
|
* 匹配通过后一键拉起前台报警应用。
|
||||||
|
*/
|
||||||
|
static void RfMonitorApp_onRun(void)
|
||||||
|
{
|
||||||
|
u32 rx_addr;
|
||||||
|
u8 rx_data;
|
||||||
|
u8 slot;
|
||||||
|
|
||||||
|
// 常态对待机下的射频检测与触发过滤逻辑
|
||||||
|
if (EV1527_Decode(&rx_addr, &rx_data)) {
|
||||||
|
XTELL_LOG("[RF_Monitor] Decoded Addr=");
|
||||||
|
XTELL_LOG_HEX32(rx_addr);
|
||||||
|
XTELL_LOG(", Data=");
|
||||||
|
XTELL_LOG_HEX8(rx_data);
|
||||||
|
XTELL_LOG("\r\n");
|
||||||
|
|
||||||
|
// 比对收到的 24 位射频源地址是否合法注册在手环本地数据库中
|
||||||
|
if (Check_Sensor_ID(rx_addr, &slot)) {
|
||||||
|
u8 sensor_type = sensor_list[slot].type; // 获取注册的设备类型
|
||||||
|
if (sensor_type < 5) {
|
||||||
|
// 防区分类过滤与布撤防过滤逻辑:
|
||||||
|
// - zone == 0,或者烟感(2)、气感(3)、水浸(4) 为 24小时紧急防区,不受当前布防状态影响,强制触发报警;
|
||||||
|
// - 门磁(0)和红外(1)为普通边界入侵防区,只有当系统处于 STATE_ARMED (布防) 状态时,才触发警报。
|
||||||
|
if (sensor_list[slot].zone == 0 ||
|
||||||
|
sensor_type == 2 ||
|
||||||
|
sensor_type == 3 ||
|
||||||
|
sensor_type == 4 ||
|
||||||
|
current_state == STATE_ARMED)
|
||||||
|
{
|
||||||
|
// 只有在前台不为紧急 SOS 呼救且当前不处于报警状态时,才执行报警调起
|
||||||
|
if (AppManager_GetActiveAppID() != APP_ID_SOS &&
|
||||||
|
AppManager_GetActiveAppID() != APP_ID_ALARM)
|
||||||
|
{
|
||||||
|
captured_addr = rx_addr; // 缓存报警地址
|
||||||
|
alarm_sensor_slot = slot; // 缓存报警槽位号
|
||||||
|
XTELL_LOG("[RF_Monitor] Intruder ALERT! Waking up alarm app...\r\n");
|
||||||
|
AppManager_SwitchToForeground(&alarm_app); // 动态切入警报前台
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
XTELL_LOG("[RF_Monitor] Ignored due to DISARMED state.\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
XTELL_LOG("[RF_Monitor] Unregistered sensor address.\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RfMonitorApp 退出清理
|
||||||
|
*/
|
||||||
|
static void RfMonitorApp_onClose(void)
|
||||||
|
{
|
||||||
|
XTELL_LOG("[RF_Monitor] Background RF monitor closed.\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RfMonitorApp 事件处理 (后台应用暂不拦截处理按键事件)
|
||||||
|
*/
|
||||||
|
static EventResult RfMonitorApp_onEvent(SystemEvent *evt)
|
||||||
|
{
|
||||||
|
return EVENT_IGNORED;
|
||||||
|
}
|
||||||
134
App/app_sos.c
Normal file
134
App/app_sos.c
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
#include "app_manager.h"
|
||||||
|
#include "xtell.h"
|
||||||
|
#include "ui.h"
|
||||||
|
#include "rgb.h"
|
||||||
|
#include "event.h"
|
||||||
|
#include "system.h"
|
||||||
|
#include "../Drivers/rf.h"
|
||||||
|
#include "../Drivers/lcd.h"
|
||||||
|
|
||||||
|
// 外部全局变量引用
|
||||||
|
extern SystemState current_state;
|
||||||
|
extern volatile u16 ms_tick;
|
||||||
|
extern volatile bit alarm_flash_flag;
|
||||||
|
|
||||||
|
// 本地私有变量
|
||||||
|
static u8 sos_last_phase = 99; // 记录上一次发射的 500ms 半周期状态
|
||||||
|
static u32 sos_pulse_timer = 0; // SOS 马达震动波形计数器
|
||||||
|
static u16 last_run_tick = 0; // 记录上一次轮询的 ticks
|
||||||
|
|
||||||
|
// 声明本应用的生命周期回调函数
|
||||||
|
static void SOSApp_OnStart(void);
|
||||||
|
static void SOSApp_onRun(void);
|
||||||
|
static void SOSApp_onClose(void);
|
||||||
|
static EventResult SOSApp_onEvent(SystemEvent *evt);
|
||||||
|
|
||||||
|
// 实体化定义 SOS 呼救应用控制块
|
||||||
|
WristbandApp sos_app = {
|
||||||
|
APP_ID_SOS,
|
||||||
|
"SOS",
|
||||||
|
SOSApp_OnStart,
|
||||||
|
SOSApp_onRun,
|
||||||
|
SOSApp_onClose,
|
||||||
|
SOSApp_onEvent
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief SOSApp 启动回调
|
||||||
|
*/
|
||||||
|
static void SOSApp_OnStart(void)
|
||||||
|
{
|
||||||
|
current_state = STATE_SOS_EMITTED; // 进入 SOS 紧急求救状态
|
||||||
|
sos_last_phase = 99;
|
||||||
|
sos_pulse_timer = 0; // 马达震动计数器清零
|
||||||
|
last_run_tick = ms_tick;
|
||||||
|
UI_ShowSosPage(); // 初始化渲染 SOS 警示主页
|
||||||
|
|
||||||
|
// 立即点亮左侧灯为红色,右侧熄灭,准备开启双色红灯交替爆闪
|
||||||
|
RGB_Send(0, 200, 0, 0, 0, 0);
|
||||||
|
|
||||||
|
XTELL_LOG("[SOSApp] Started\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief SOSApp 轮询主回调
|
||||||
|
* @details 包含 500ms 周期的高频射频 SOS 码发射、红蓝交替爆闪以及
|
||||||
|
* 马达 (1000ms震动 / 200ms静音) 规律震动控制。
|
||||||
|
*/
|
||||||
|
static void SOSApp_onRun(void)
|
||||||
|
{
|
||||||
|
u16 tick_diff;
|
||||||
|
|
||||||
|
// ====== 1. 控制无线射频,每 500ms 发射一次 SOS 无线数据帧 (0x08) ======
|
||||||
|
u8 sec_phase = (u8)((ms_tick / 500) % 2);
|
||||||
|
if (sec_phase != sos_last_phase) {
|
||||||
|
sos_last_phase = sec_phase;
|
||||||
|
RF_SetMode(2); // 切换发射
|
||||||
|
EV1527_Transmit(CLONED_ADDR, 0x08); // 发射克隆 ID 及其 SOS (0x08) 命令字
|
||||||
|
RF_SetMode(0); // 释放接收
|
||||||
|
XTELL_LOG("[SOSApp] SOS Radio packet emitted\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====== 2. 控制 RGB 双灯珠以 50ms 频率进行高频左右交替红蓝爆闪 ======
|
||||||
|
{
|
||||||
|
u8 flash = (ms_tick / 50) % 2;
|
||||||
|
if (flash) {
|
||||||
|
RGB_Send(0, 200, 0, 0, 0, 200); // 灯 1 亮红 (GRB 中 R=200),灯 2 亮蓝 (B=200)
|
||||||
|
} else {
|
||||||
|
RGB_Send(0, 0, 200, 0, 200, 0); // 灯 1 亮蓝,灯 2 亮红
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====== 3. 控制屏幕绘制多层爆闪红白框 ======
|
||||||
|
if (alarm_flash_flag) {
|
||||||
|
LCD_DrawRectBorder(4, 4, 112, 232, COLOR_RED);
|
||||||
|
LCD_DrawRectBorder(5, 5, 110, 230, COLOR_RED);
|
||||||
|
LCD_DrawRectBorder(8, 8, 104, 224, COLOR_WHITE);
|
||||||
|
} else {
|
||||||
|
LCD_DrawRectBorder(4, 4, 112, 232, COLOR_BLACK);
|
||||||
|
LCD_DrawRectBorder(5, 5, 110, 230, COLOR_BLACK);
|
||||||
|
LCD_DrawRectBorder(8, 8, 104, 224, COLOR_BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====== 4. 马达 SOS 震动节拍自处理 (1000ms 震动,200ms 静音) ======
|
||||||
|
if (ms_tick >= last_run_tick) {
|
||||||
|
tick_diff = ms_tick - last_run_tick;
|
||||||
|
} else {
|
||||||
|
tick_diff = 1000 + ms_tick - last_run_tick;
|
||||||
|
}
|
||||||
|
last_run_tick = ms_tick;
|
||||||
|
|
||||||
|
sos_pulse_timer += tick_diff;
|
||||||
|
if (sos_pulse_timer >= 1200) {
|
||||||
|
sos_pulse_timer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sos_pulse_timer < 1000) {
|
||||||
|
MOTOR = 1;
|
||||||
|
} else {
|
||||||
|
MOTOR = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief SOSApp 退出清理
|
||||||
|
*/
|
||||||
|
static void SOSApp_onClose(void)
|
||||||
|
{
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0); // 关闭 RGB
|
||||||
|
MOTOR = 0; // 停震
|
||||||
|
current_state = STATE_NORMAL;
|
||||||
|
XTELL_LOG("[SOSApp] Closed\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief SOSApp 事件拦截处理回调 (按任意键退出 SOS)
|
||||||
|
*/
|
||||||
|
static EventResult SOSApp_onEvent(SystemEvent *evt)
|
||||||
|
{
|
||||||
|
if (evt->key_event != KEY_EVENT_NONE) {
|
||||||
|
AppManager_SwitchToForeground(&clock_app); // 退出 SOS,返回时钟
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
return EVENT_IGNORED;
|
||||||
|
}
|
||||||
37
App/clock.c
Normal file
37
App/clock.c
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#include "clock.h"
|
||||||
|
#include "xtell.h"
|
||||||
|
|
||||||
|
// 软时钟基础计时变量
|
||||||
|
u8 current_hour = 10; // 系统软时钟:小时 (默认初始化为 10 点)
|
||||||
|
u8 current_min = 35; // 系统软时钟:分钟 (默认初始化为 35 分)
|
||||||
|
u8 current_sec = 0; // 系统软时钟:秒
|
||||||
|
volatile u8 clock_updated = 0; // 分钟跳变标志位,置位后用于驱动 UI 刷新时间显示
|
||||||
|
|
||||||
|
// 毫秒计数器 (Timer1 每 1ms 累加)
|
||||||
|
static u16 ms_cnt = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 系统软时钟毫秒递增更新 (在 1ms 中断中调用)
|
||||||
|
* @details 负责由定时器 1ms 驱动递增软时钟,并在分钟发生跳变时将 clock_updated 置位
|
||||||
|
*/
|
||||||
|
void Clock_IncMS(void)
|
||||||
|
{
|
||||||
|
ms_cnt++;
|
||||||
|
if (ms_cnt >= 1000) {
|
||||||
|
ms_cnt = 0; // 1秒时间到
|
||||||
|
current_sec++;
|
||||||
|
if (current_sec >= 60) {
|
||||||
|
current_sec = 0; // 1分钟时间到
|
||||||
|
current_min++;
|
||||||
|
if (current_min >= 60) {
|
||||||
|
current_min = 0; // 1小时时间到
|
||||||
|
current_hour++;
|
||||||
|
if (current_hour >= 24) {
|
||||||
|
current_hour = 0; // 小时循环
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clock_updated = 1; // 分钟跳变,标志位置位,前台待机 App 会捕获以刷新 UI
|
||||||
|
XTELL_LOG("[CLOCK] Time updated!\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
App/clock.h
Normal file
17
App/clock.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#ifndef __CLOCK_H__
|
||||||
|
#define __CLOCK_H__
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
// 全局软时钟变量声明
|
||||||
|
extern u8 current_hour;
|
||||||
|
extern u8 current_min;
|
||||||
|
extern u8 current_sec;
|
||||||
|
extern volatile u8 clock_updated;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 系统软时钟毫秒递增更新 (在 1ms 中断中调用)
|
||||||
|
*/
|
||||||
|
void Clock_IncMS(void);
|
||||||
|
|
||||||
|
#endif // __CLOCK_H__
|
||||||
67
App/config.h
67
App/config.h
@@ -9,6 +9,11 @@ typedef unsigned char u8;
|
|||||||
typedef unsigned int u16;
|
typedef unsigned int u16;
|
||||||
typedef unsigned long u32;
|
typedef unsigned long u32;
|
||||||
|
|
||||||
|
// STC32G 端口内部上拉控制寄存器地址定义 (属于扩展 XSFR 空间)
|
||||||
|
#ifndef P3PU
|
||||||
|
#define P3PU (*(unsigned char volatile xdata *)0xFE13)
|
||||||
|
#endif
|
||||||
|
|
||||||
/* ====== 硬件引脚定义 (sbit) ====== */
|
/* ====== 硬件引脚定义 (sbit) ====== */
|
||||||
// 振动马达控制端 (推挽输出,与 SPI SCLK 物理共享引脚)
|
// 振动马达控制端 (推挽输出,与 SPI SCLK 物理共享引脚)
|
||||||
sbit MOTOR = P2^5;
|
sbit MOTOR = P2^5;
|
||||||
@@ -18,24 +23,34 @@ sbit KEY_UP = P0^1;
|
|||||||
sbit KEY_CONFIRM = P0^2;
|
sbit KEY_CONFIRM = P0^2;
|
||||||
// 侧边按键 ▼ (高阻输入 + 上拉),按下为低电平
|
// 侧边按键 ▼ (高阻输入 + 上拉),按下为低电平
|
||||||
sbit KEY_DOWN = P0^3;
|
sbit KEY_DOWN = P0^3;
|
||||||
|
// 电源保持控制引脚 (1:锁存保持电源, 0:切断电源关机)
|
||||||
|
#define PWR_HOLD P00
|
||||||
|
// PCF8563 RTC 实时时钟芯片 I2C 通信引脚
|
||||||
|
#define RTC_SCL P32
|
||||||
|
#define RTC_SDA P33
|
||||||
// 物理 SOS 求救按键 (高阻输入 + 上拉),按下为低电平
|
// 物理 SOS 求救按键 (高阻输入 + 上拉),按下为低电平
|
||||||
sbit KEY_SOS = P2^6;
|
sbit KEY_SOS = P2^6;
|
||||||
// FCOB 幻彩灯条 DIN 数据控制线
|
// FCOB 幻彩灯条 DIN 数据控制线
|
||||||
sbit RGB_DIN = P2^3;
|
sbit RGB_DIN = P2^3;
|
||||||
// LR690L 射频芯片休眠/工作控制引脚 (低电平使能开启工作)
|
// LR690L 射频芯片休眠/工作控制引脚 (0:开启工作, 1:关闭休眠)
|
||||||
sbit SHUT = P2^2;
|
sbit SHUT = P3^5;
|
||||||
// SGM3833 PMIC 屏幕负压供电使能脚 (1使能,0关闭)
|
// LHS114TC-IF03 屏幕 LEDA 白色背光开启引脚 (1点亮,0熄灭)
|
||||||
sbit SGM_CTRL = P3^4;
|
sbit SGM_CTRL = P1^7;
|
||||||
// Truly AMOLED 屏幕复位脚 (RESET),低电平复位
|
// LHS114TC-IF03 屏幕复位脚 (RESET),低电平复位
|
||||||
sbit LCD_RST = P1^0;
|
sbit LCD_RST = P1^0;
|
||||||
// Truly AMOLED 屏幕数据/指令选择脚 (D/C,1:数据, 0:命令)
|
// LHS114TC-IF03 屏幕数据/指令选择脚 (D/C,1:数据, 0:命令)
|
||||||
sbit LCD_DCX = P1^1;
|
sbit LCD_DCX = P1^1;
|
||||||
// Truly AMOLED 屏幕 SPI 数据脚 (MOSI/SDA)
|
// LHS114TC-IF03 屏幕 SPI 数据脚 (MOSI/SDA)
|
||||||
sbit LCD_SDI = P1^4;
|
sbit LCD_SDI = P1^4;
|
||||||
// Truly AMOLED 屏幕 SPI 时钟脚 (SCLK)
|
// LHS114TC-IF03 屏幕 SPI 时钟脚 (SCLK)
|
||||||
sbit LCD_SCL = P1^5;
|
sbit LCD_SCL = P1^5;
|
||||||
// Truly AMOLED 屏幕 SPI 片选脚 (CS),低电平有效
|
// LHS114TC-IF03 屏幕 SPI 片选脚 (CS),低电平有效
|
||||||
sbit LCD_CS = P1^6;
|
sbit LCD_CS = P1^6;
|
||||||
|
|
||||||
|
// 充电检测引脚 (TP4054 CHRG 开漏输出,低电平=充电中)
|
||||||
|
// 硬件连接:TP4054 CHRG → DET → MCU (根据原理图,需 PCB 确认实际引脚)
|
||||||
|
// 架构文档标注为 P5.4,原理图标注为 P1.7 附近,暂用 P5.4,可根据实测调整
|
||||||
|
sbit CHRG_DET = P5^4;
|
||||||
|
|
||||||
/* ====== 系统运行状态枚举 ====== */
|
/* ====== 系统运行状态枚举 ====== */
|
||||||
typedef enum {
|
typedef enum {
|
||||||
@@ -65,21 +80,22 @@ typedef enum {
|
|||||||
KEY_EVENT_SOS_LONG, // SOS 键长按事件
|
KEY_EVENT_SOS_LONG, // SOS 键长按事件
|
||||||
KEY_EVENT_UP_DOWN_COMB, // ▲ 与 ▼ 双键组合按下事件
|
KEY_EVENT_UP_DOWN_COMB, // ▲ 与 ▼ 双键组合按下事件
|
||||||
KEY_EVENT_CONFIRM_CLICK, // ■ 确认键短按事件 (保存设置或进入子菜单)
|
KEY_EVENT_CONFIRM_CLICK, // ■ 确认键短按事件 (保存设置或进入子菜单)
|
||||||
KEY_EVENT_CONFIRM_LONG // ■ 确认键长按事件 (打开主菜单)
|
KEY_EVENT_CONFIRM_LONG, // ■ 确认键长按事件 (打开主菜单)
|
||||||
|
KEY_EVENT_POWER_OFF // 长按 KEY_DOWN 开关机触发事件
|
||||||
} KeyEvent;
|
} KeyEvent;
|
||||||
|
|
||||||
/* ====== 事件优先级枚举 ====== */
|
/* ====== 事件优先级枚举 ====== */
|
||||||
typedef enum {
|
typedef enum {
|
||||||
EVENT_PRIORITY_LOW, // 低优先级:系统内部状态更新 (如时钟分钟更新)
|
EVENT_PRIORITY_LOW, // 0: 低优先级:系统内部事件,如定时更新
|
||||||
EVENT_PRIORITY_NORMAL, // 正常优先级:用户按键交互、菜单导航
|
EVENT_PRIORITY_NORMAL, // 1: 正常优先级:大部分用户交互事件
|
||||||
EVENT_PRIORITY_HIGH, // 高优先级:无线传感器入侵警报
|
EVENT_PRIORITY_HIGH, // 2: 高优先级:报警相关事件,插队到队首
|
||||||
EVENT_PRIORITY_URGENT // 紧急优先级:SOS 主动求救
|
EVENT_PRIORITY_URGENT // 3: 紧急优先级:SOS等,插队到队首
|
||||||
} EventPriority;
|
} EventPriority;
|
||||||
|
|
||||||
/* ====== 系统事件结构体定义 ====== */
|
/* ====== 系统事件结构体定义 ====== */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
KeyEvent key_event; // 关联的按键事件类型
|
KeyEvent key_event; // 关联的按键事件类型
|
||||||
EventPriority priority; // 事件的处理优先级
|
EventPriority priority; // 事件优先级
|
||||||
u32 extra_data; // 随事件携带的额外数据 (如 24 位射频源地址等)
|
u32 extra_data; // 随事件携带的额外数据 (如 24 位射频源地址等)
|
||||||
u8 extra_size; // 额外数据的有效字节大小 (0~4)
|
u8 extra_size; // 额外数据的有效字节大小 (0~4)
|
||||||
} SystemEvent;
|
} SystemEvent;
|
||||||
@@ -106,26 +122,7 @@ typedef enum {
|
|||||||
APP_TYPE_BACKGROUND // 后台应用:无屏幕显示,静默监听前台未处理的系统事件
|
APP_TYPE_BACKGROUND // 后台应用:无屏幕显示,静默监听前台未处理的系统事件
|
||||||
} AppType;
|
} AppType;
|
||||||
|
|
||||||
/* ====== 应用程序描述符及函数指针类型定义 ====== */
|
|
||||||
struct WristbandApp;
|
|
||||||
typedef struct WristbandApp WristbandApp;
|
|
||||||
|
|
||||||
typedef void (*AppStartFunc)(void); // 应用启动函数指针类型
|
|
||||||
typedef void (*AppRunFunc)(void); // 主循环轮询函数指针类型
|
|
||||||
typedef void (*AppCloseFunc)(void); // 应用关闭清理函数指针类型
|
|
||||||
typedef EventResult (*AppEventFunc)(SystemEvent *evt); // 事件接收处理函数指针类型
|
|
||||||
|
|
||||||
struct WristbandApp {
|
|
||||||
AppID app_id; // 应用程序唯一标识 ID
|
|
||||||
AppType app_type; // 前后台类型标识
|
|
||||||
char *app_name; // 应用程序名称字符串 (供串口日志调试使用)
|
|
||||||
void (*OnStart)(void); // 启动回调:应用加载时调用,用于初始化 UI 界面与硬件状态
|
|
||||||
void (*onRun)(void); // 循环回调:在主循环中持续轮询调用,用于非阻塞业务更新
|
|
||||||
void (*onClose)(void); // 关闭回调:应用退出时调用,用于现场清理与外设复位
|
|
||||||
EventResult (*onEvent)(SystemEvent *evt); // 事件回调:接收系统事件,返回处理结果以控制事件流向
|
|
||||||
u8 is_active; // 标记该应用当前是否处于活跃运行状态 (1表示前台活跃)
|
|
||||||
u8 is_running; // 标记应用是否处于运行态,用于 CPU 超时挂起机制控制
|
|
||||||
};
|
|
||||||
|
|
||||||
/* ====== 无线传感器 Flash 存储槽位结构体 ====== */
|
/* ====== 无线传感器 Flash 存储槽位结构体 ====== */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|||||||
226
App/event.c
226
App/event.c
@@ -2,12 +2,15 @@
|
|||||||
#include "app_manager.h"
|
#include "app_manager.h"
|
||||||
#include "database.h"
|
#include "database.h"
|
||||||
#include "system.h"
|
#include "system.h"
|
||||||
|
#include "clock.h"
|
||||||
|
#include "xtell.h"
|
||||||
|
#include "../Drivers/adc.h"
|
||||||
|
|
||||||
// 定义事件环形队列的最大深度为 8
|
// 定义事件环形队列的最大深度为 8
|
||||||
#define EVENT_QUEUE_DEPTH 8
|
#define EVENT_QUEUE_DEPTH 8
|
||||||
|
|
||||||
// 环形队列存储数组 (存放在单片机 idata 快速RAM区中)
|
// 环形队列存储数组 (存放在单片机 xdata 拓展 RAM区中)
|
||||||
static SystemEvent idata event_queue[EVENT_QUEUE_DEPTH];
|
static SystemEvent xdata event_queue[EVENT_QUEUE_DEPTH];
|
||||||
static u8 queue_head = 0; // 队首读指针
|
static u8 queue_head = 0; // 队首读指针
|
||||||
static u8 queue_tail = 0; // 队尾写指针
|
static u8 queue_tail = 0; // 队尾写指针
|
||||||
static u8 queue_count = 0; // 当前队列内有效事件总数
|
static u8 queue_count = 0; // 当前队列内有效事件总数
|
||||||
@@ -64,12 +67,12 @@ SystemEvent EventQueue_Pop(void)
|
|||||||
SystemEvent evt;
|
SystemEvent evt;
|
||||||
// 默认空事件属性
|
// 默认空事件属性
|
||||||
evt.key_event = KEY_EVENT_NONE;
|
evt.key_event = KEY_EVENT_NONE;
|
||||||
evt.priority = EVENT_PRIORITY_LOW;
|
evt.priority = EVENT_PRIORITY_NORMAL;
|
||||||
evt.extra_data = 0;
|
evt.extra_data = 0;
|
||||||
evt.extra_size = 0;
|
evt.extra_size = 0;
|
||||||
|
|
||||||
if (queue_count == 0) return evt; // 队空,直接返回空闲事件
|
if (queue_count == 0) return evt; // 队空,直接返回空闲事件
|
||||||
|
|
||||||
EA = 0; // 屏蔽中断
|
EA = 0; // 屏蔽中断
|
||||||
evt = event_queue[queue_head]; // 读取队首数据
|
evt = event_queue[queue_head]; // 读取队首数据
|
||||||
queue_head = (queue_head + 1) % EVENT_QUEUE_DEPTH; // 头指针向后移动,回环
|
queue_head = (queue_head + 1) % EVENT_QUEUE_DEPTH; // 头指针向后移动,回环
|
||||||
@@ -109,53 +112,30 @@ u8 EventQueue_GetCount(void)
|
|||||||
void Event_Dispatcher_Loop(void)
|
void Event_Dispatcher_Loop(void)
|
||||||
{
|
{
|
||||||
SystemEvent evt = EventQueue_Pop(); // 弹出一个事件
|
SystemEvent evt = EventQueue_Pop(); // 弹出一个事件
|
||||||
|
|
||||||
// 如果是无事发生的空事件,直接退出
|
// 如果是无事发生的空事件,直接退出
|
||||||
if (evt.key_event == KEY_EVENT_NONE && evt.extra_size == 0) {
|
if (evt.key_event == KEY_EVENT_NONE && evt.extra_size == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
inactivity_timer = 0; // 捕获到有效事件,清零休眠计时器,重置闲置超时
|
|
||||||
|
|
||||||
// 串口打印调试信息
|
|
||||||
Uart_SendString("[EVENT] key=");
|
|
||||||
Uart_SendHex8((u8)evt.key_event);
|
|
||||||
Uart_SendString(", pri=");
|
|
||||||
Uart_SendHex8((u8)evt.priority);
|
|
||||||
Uart_SendString("\r\n");
|
|
||||||
|
|
||||||
// SOS 按键紧急处理:若非处于 SOS 呼救状态下,接收到 SOS 键动作,立即强行切入到 SOS_APP 并执行求救
|
inactivity_timer = 0; // 捕获到有效事件,清零休眠计时器,重置闲置超时
|
||||||
|
|
||||||
|
// 串口打印调试信息
|
||||||
|
XTELL_LOG("[EVENT] key=");
|
||||||
|
XTELL_LOG_HEX8((u8)evt.key_event);
|
||||||
|
XTELL_LOG("\r\n");
|
||||||
|
|
||||||
|
// SOS 按键紧急处理:若当前前台不为 SOS 应用,接收到 SOS 键动作,立即强行切入到 SOS_APP 并执行求救
|
||||||
if ((evt.key_event == KEY_EVENT_SOS_CLICK || evt.key_event == KEY_EVENT_SOS_LONG) &&
|
if ((evt.key_event == KEY_EVENT_SOS_CLICK || evt.key_event == KEY_EVENT_SOS_LONG) &&
|
||||||
AppManager_GetActiveAppID() != APP_ID_SOS) {
|
AppManager_GetActiveAppID() != APP_ID_SOS) {
|
||||||
AppManager_StartApp(APP_ID_SOS);
|
AppManager_StartApp(APP_ID_SOS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 传感器入侵报警过滤与跳转处理:
|
// 关机事件处理:捕获到长按关机指令,直接调用 Power_Off 关断电源
|
||||||
// 当接收到高优先级且带有 4 字节射频地址的数据包,说明有传感器触发了发射
|
if (evt.key_event == KEY_EVENT_POWER_OFF) {
|
||||||
if (evt.priority == EVENT_PRIORITY_HIGH && evt.extra_size >= 4) {
|
Power_Off();
|
||||||
u8 slot;
|
return;
|
||||||
// 在本地数据库中检索该传感器地址是否合法登记过
|
|
||||||
if (Check_Sensor_ID(evt.extra_data, &slot)) {
|
|
||||||
// 在不处于 SOS 的情况下,可正常强行切入 ALARM_APP 触发声光振警报
|
|
||||||
if (AppManager_GetActiveAppID() != APP_ID_SOS) {
|
|
||||||
captured_addr = evt.extra_data; // 缓存报警地址
|
|
||||||
alarm_sensor_slot = slot; // 缓存报警槽位
|
|
||||||
AppManager_StartApp(APP_ID_ALARM); // 强制切入报警前台
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 无线对码捕获处理:
|
|
||||||
// 若系统正处于对码应用 (APP_ID_PAIR),遇到普通优先级无线射频包,则将其派发给该应用进行匹配与登记
|
|
||||||
if (evt.priority == EVENT_PRIORITY_NORMAL && evt.extra_size >= 4) {
|
|
||||||
captured_addr = evt.extra_data; // 缓存捕获的待配对地址
|
|
||||||
captured_type = menu_select; // 将主菜单选择的类型传给配对缓存
|
|
||||||
if (AppManager_GetActiveAppID() == APP_ID_PAIR) {
|
|
||||||
AppManager_DispatchEvent(evt); // 派发给对码应用
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 常规事件(如普通的按键点击)直接透传分发给当前处于前台活跃的应用
|
// 常规事件(如普通的按键点击)直接透传分发给当前处于前台活跃的应用
|
||||||
@@ -180,106 +160,70 @@ void Timer1_Init(void)
|
|||||||
EA = 1; // 开启全局中断允许
|
EA = 1; // 开启全局中断允许
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 全局按键扫描触发标志
|
||||||
|
volatile bit key_scan_flag = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 定时器 1 中断服务程序 (每 1ms 触发一次)
|
* @brief 定时器 1 中断服务程序 (每 1ms 触发一次)
|
||||||
* @details 承担了系统软时钟计算、报警与求救下的马达控制状态机以及 10ms 周期性按键消抖扫描的功能。
|
* @details 承担系统软时钟计算,并每 10ms 置位主循环按键扫描标志,不直接在中断中进行 ADC 多次采样。
|
||||||
*/
|
*/
|
||||||
void Timer1_Isr(void) interrupt 3
|
void Timer1_Isr(void) interrupt 3
|
||||||
{
|
{
|
||||||
SystemEvent evt;
|
// 1ms 定时更新软时钟
|
||||||
|
Clock_IncMS();
|
||||||
// 1ms 递增滴答
|
|
||||||
ms_tick++;
|
|
||||||
if (ms_tick >= 1000) {
|
|
||||||
ms_tick = 0; // 1秒时间已到,归零
|
|
||||||
current_sec++;
|
|
||||||
if (current_sec >= 60) {
|
|
||||||
current_sec = 0; // 1分钟时间已到
|
|
||||||
current_min++;
|
|
||||||
if (current_min >= 60) {
|
|
||||||
current_min = 0; // 1小时时间已到
|
|
||||||
current_hour++;
|
|
||||||
if (current_hour >= 24) current_hour = 0; // 小时循环
|
|
||||||
}
|
|
||||||
clock_updated = 1; // 标志位置1,提示前台时钟应用刷新显示
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果处于对码等待状态,对码超时计时器增加 (30秒超时判断)
|
|
||||||
if (current_state == STATE_PAIR_WAIT) {
|
|
||||||
pair_timeout_ms++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 振动马达定时状态机逻辑控制 */
|
|
||||||
if (current_state == STATE_SOS_EMITTED) {
|
|
||||||
// SOS 模式:交替规律震动 (震动 1000ms,静音 200ms)
|
|
||||||
alarm_timer_ms++;
|
|
||||||
if (alarm_timer_ms >= 1200) alarm_timer_ms = 0;
|
|
||||||
if (alarm_timer_ms < 1000) MOTOR = 1; else MOTOR = 0;
|
|
||||||
} else if (current_state == STATE_ALARM) {
|
|
||||||
// 报警状态:根据触发警报的传感器类型,输出对应的差异化震动波形以便触觉分辨
|
|
||||||
u8 stype = sensor_list[alarm_sensor_slot].type;
|
|
||||||
alarm_timer_ms++;
|
|
||||||
if (alarm_timer_ms >= 5000) alarm_timer_ms = 0; // 5秒一周期
|
|
||||||
|
|
||||||
if (stype == 0) {
|
|
||||||
// 门磁警报波形:单次震动 300ms,停 4700ms
|
|
||||||
if (alarm_timer_ms < 300) MOTOR = 1; else MOTOR = 0;
|
|
||||||
} else if (stype == 1) {
|
|
||||||
// PIR 警报波形:震动 300ms,停 200ms,再震动 300ms,停 4200ms
|
|
||||||
if (alarm_timer_ms < 300) MOTOR = 1;
|
|
||||||
else if (alarm_timer_ms >= 300 && alarm_timer_ms < 500) MOTOR = 0;
|
|
||||||
else if (alarm_timer_ms >= 500 && alarm_timer_ms < 800) MOTOR = 1;
|
|
||||||
else MOTOR = 0;
|
|
||||||
} else if (stype == 2) {
|
|
||||||
// 烟感警报波形:急促短闪三下 200ms-150ms-200ms-150ms-200ms
|
|
||||||
if (alarm_timer_ms < 200) MOTOR = 1;
|
|
||||||
else if (alarm_timer_ms >= 200 && alarm_timer_ms < 350) MOTOR = 0;
|
|
||||||
else if (alarm_timer_ms >= 350 && alarm_timer_ms < 550) MOTOR = 1;
|
|
||||||
else if (alarm_timer_ms >= 550 && alarm_timer_ms < 700) MOTOR = 0;
|
|
||||||
else if (alarm_timer_ms >= 700 && alarm_timer_ms < 900) MOTOR = 1;
|
|
||||||
else MOTOR = 0;
|
|
||||||
} else if (stype == 3) {
|
|
||||||
// 燃气报警波形:快频连续四下
|
|
||||||
if (alarm_timer_ms < 200) MOTOR = 1;
|
|
||||||
else if (alarm_timer_ms >= 200 && alarm_timer_ms < 300) MOTOR = 0;
|
|
||||||
else if (alarm_timer_ms >= 300 && alarm_timer_ms < 500) MOTOR = 1;
|
|
||||||
else if (alarm_timer_ms >= 500 && alarm_timer_ms < 600) MOTOR = 0;
|
|
||||||
else if (alarm_timer_ms >= 600 && alarm_timer_ms < 800) MOTOR = 1;
|
|
||||||
else if (alarm_timer_ms >= 800 && alarm_timer_ms < 900) MOTOR = 0;
|
|
||||||
else if (alarm_timer_ms >= 900 && alarm_timer_ms < 1100) MOTOR = 1;
|
|
||||||
else MOTOR = 0;
|
|
||||||
} else if (stype == 4) {
|
|
||||||
// 水浸警报波形:慢速双长震波 600ms 和 600ms
|
|
||||||
if (alarm_timer_ms < 600) MOTOR = 1;
|
|
||||||
else if (alarm_timer_ms >= 600 && alarm_timer_ms < 900) MOTOR = 0;
|
|
||||||
else if (alarm_timer_ms >= 900 && alarm_timer_ms < 1500) MOTOR = 1;
|
|
||||||
else MOTOR = 0;
|
|
||||||
} else {
|
|
||||||
// 缺省状态默认单次 300ms
|
|
||||||
if (alarm_timer_ms < 300) MOTOR = 1; else MOTOR = 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 其他常态下彻底关闭震动马达并归零计时器
|
|
||||||
MOTOR = 0;
|
|
||||||
alarm_timer_ms = 0;
|
|
||||||
motor_timer_ms = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 报警屏幕红边框以 100ms 周期亮灭,进行颜色爆闪指示
|
// 报警屏幕红边框以 100ms 周期亮灭,进行颜色爆闪指示
|
||||||
alarm_flash_flag = (ms_tick / 100) % 2;
|
alarm_flash_flag = (ms_tick / 100) % 2;
|
||||||
|
|
||||||
/* 物理按键扫描及消抖状态机逻辑 (每 10ms 轮询一次) */
|
/* 物理按键定时计数 (每 10ms 置位一次标志位) */
|
||||||
key_scan_timer++;
|
key_scan_timer++;
|
||||||
if (key_scan_timer >= 10) {
|
if (key_scan_timer >= 10) {
|
||||||
// 读取按键的电平信号 (由于有上拉电阻,未按下为高 1,按下为低 0,所以取反做正逻辑)
|
|
||||||
u8 raw_up = !KEY_UP;
|
|
||||||
u8 raw_down = !KEY_DOWN;
|
|
||||||
u8 raw_confirm = !KEY_CONFIRM;
|
|
||||||
u8 raw_sos = !KEY_SOS;
|
|
||||||
key_scan_timer = 0; // 重置扫描分频计数器
|
key_scan_timer = 0; // 重置扫描分频计数器
|
||||||
|
key_scan_flag = 1; // 仅在中断里置起标志位,留待主循环中执行实际读取与消抖
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 检测到有任意按键处于下压状态,立即将无操作闲置休眠计时器归零
|
/**
|
||||||
|
* @brief 主循环按键扫描与消抖处理函数
|
||||||
|
* @details 由主循环每 10ms 检查并执行,完全在主线程中运行,绝无中断重入冲突。
|
||||||
|
*/
|
||||||
|
void Event_KeyScan_Poll(void)
|
||||||
|
{
|
||||||
|
SystemEvent evt;
|
||||||
|
// 读取按键的电平信号 (KEY_UP, KEY_CONFIRM 为低电平有效)
|
||||||
|
u8 raw_up = !KEY_UP;
|
||||||
|
u8 raw_down = ADC_IsKeyDown(); // KEY_DOWN 通过 P0.3 ADC 通道判定 (常态 2.0V, 按下 3.7V)
|
||||||
|
u8 raw_confirm = !KEY_CONFIRM;
|
||||||
|
u8 raw_sos = !KEY_SOS; // 恢复 SOS 物理按键电平检测
|
||||||
|
key_scan_timer = 0; // 重置扫描分频计数器
|
||||||
|
|
||||||
|
// 【SOS 按键电平实时诊断】
|
||||||
|
{
|
||||||
|
static u8 last_raw_sos = 99;
|
||||||
|
if (raw_sos != last_raw_sos) {
|
||||||
|
last_raw_sos = raw_sos;
|
||||||
|
Uart_SendString("[KEY-DIAG] KEY_SOS (P2.6) logic raw: ");
|
||||||
|
Uart_SendByte(raw_sos ? '1' : '0');
|
||||||
|
Uart_SendString(", Physical GPIO Pin: ");
|
||||||
|
Uart_SendByte(KEY_SOS ? '1' : '0');
|
||||||
|
Uart_SendString("\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按键调试诊断:若按键 ADC 值有变化或处于高电平,串口输出真实 ADC 值
|
||||||
|
{
|
||||||
|
u16 down_val = ADC_ReadFiltered(ADC_CHANNEL_KEY_DOWN);
|
||||||
|
if (down_val > 2800) {
|
||||||
|
Uart_SendString("[KEY-DIAG] KEY_DOWN pressed! ADC Raw: ");
|
||||||
|
Uart_SendByte((u8)('0' + (down_val / 1000) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (down_val / 100) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (down_val / 10) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (down_val % 10)));
|
||||||
|
Uart_SendString("\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测到有任意按键处于下压状态,立即将无操作闲置休眠计时器归零
|
||||||
if (raw_up || raw_down || raw_confirm || raw_sos) {
|
if (raw_up || raw_down || raw_confirm || raw_sos) {
|
||||||
inactivity_timer = 0;
|
inactivity_timer = 0;
|
||||||
} else {
|
} else {
|
||||||
@@ -291,15 +235,15 @@ void Timer1_Isr(void) interrupt 3
|
|||||||
|
|
||||||
// 默认将派发事件重置为空
|
// 默认将派发事件重置为空
|
||||||
evt.key_event = KEY_EVENT_NONE;
|
evt.key_event = KEY_EVENT_NONE;
|
||||||
|
evt.priority = EVENT_PRIORITY_NORMAL;
|
||||||
evt.extra_data = 0;
|
evt.extra_data = 0;
|
||||||
evt.extra_size = 0;
|
evt.extra_size = 0;
|
||||||
|
|
||||||
// 双键组合判定:▲ 键和 ▼ 键同时按下的消抖与生成
|
// 双键组合判定:▲ 键 and ▼ 键同时按下的消抖与生成
|
||||||
if (raw_up && raw_down) {
|
if (raw_up && raw_down) {
|
||||||
comb_hold++;
|
comb_hold++;
|
||||||
if (comb_hold == 300) { // 持续按下达 3 秒 (300 * 10ms = 3s)
|
if (comb_hold == 300) { // 持续按下达 3 秒 (300 * 10ms = 3s)
|
||||||
evt.key_event = KEY_EVENT_UP_DOWN_COMB;
|
evt.key_event = KEY_EVENT_UP_DOWN_COMB;
|
||||||
evt.priority = EVENT_PRIORITY_NORMAL;
|
|
||||||
EventQueue_Push(evt); // 压入队列
|
EventQueue_Push(evt); // 压入队列
|
||||||
}
|
}
|
||||||
key_up_hold = 0; // 清零单个按键计数,防止双击事件干扰
|
key_up_hold = 0; // 清零单个按键计数,防止双击事件干扰
|
||||||
@@ -312,31 +256,28 @@ void Timer1_Isr(void) interrupt 3
|
|||||||
key_up_hold++;
|
key_up_hold++;
|
||||||
if (key_up_hold == 300) { // 达到 3 秒判定为长按
|
if (key_up_hold == 300) { // 达到 3 秒判定为长按
|
||||||
evt.key_event = KEY_EVENT_UP_LONG;
|
evt.key_event = KEY_EVENT_UP_LONG;
|
||||||
evt.priority = EVENT_PRIORITY_NORMAL;
|
|
||||||
EventQueue_Push(evt);
|
EventQueue_Push(evt);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 如果松开时按下计数在消抖阈值(20ms)至长按阈值(3s)之间,则判定为一次短按
|
// 如果松开时按下计数在消抖阈值(20ms)至长按阈值(3s)之间,则判定为一次短按
|
||||||
if (key_up_hold >= 2 && key_up_hold < 300) {
|
if (key_up_hold >= 2 && key_up_hold < 300) {
|
||||||
evt.key_event = KEY_EVENT_UP_CLICK;
|
evt.key_event = KEY_EVENT_UP_CLICK;
|
||||||
evt.priority = EVENT_PRIORITY_NORMAL;
|
|
||||||
EventQueue_Push(evt);
|
EventQueue_Push(evt);
|
||||||
}
|
}
|
||||||
key_up_hold = 0; // 释放归零
|
key_up_hold = 0; // 释放归零
|
||||||
}
|
}
|
||||||
|
|
||||||
// ▼ (KEY_DOWN) 按键状态机
|
// ▼ (KEY_DOWN / 开关机) 按键状态机
|
||||||
if (raw_down) {
|
if (raw_down) {
|
||||||
key_down_hold++;
|
key_down_hold++;
|
||||||
if (key_down_hold == 300) { // 长按 3 秒
|
if (key_down_hold == 100) { // 长按 1 秒 (100 * 10ms = 1000ms = 1s) 触发关机事件
|
||||||
evt.key_event = KEY_EVENT_DOWN_LONG;
|
evt.key_event = KEY_EVENT_POWER_OFF;
|
||||||
evt.priority = EVENT_PRIORITY_NORMAL;
|
|
||||||
EventQueue_Push(evt);
|
EventQueue_Push(evt);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (key_down_hold >= 2 && key_down_hold < 300) {
|
// 如果松开时按下时长在 20ms 至 1s 之间,触发短按下翻/选择事件
|
||||||
|
if (key_down_hold >= 2 && key_down_hold < 100) {
|
||||||
evt.key_event = KEY_EVENT_DOWN_CLICK;
|
evt.key_event = KEY_EVENT_DOWN_CLICK;
|
||||||
evt.priority = EVENT_PRIORITY_NORMAL;
|
|
||||||
EventQueue_Push(evt);
|
EventQueue_Push(evt);
|
||||||
}
|
}
|
||||||
key_down_hold = 0; // 释放归零
|
key_down_hold = 0; // 释放归零
|
||||||
@@ -347,34 +288,33 @@ void Timer1_Isr(void) interrupt 3
|
|||||||
key_confirm_hold++;
|
key_confirm_hold++;
|
||||||
if (key_confirm_hold == 300) { // 长按 3 秒打开主菜单
|
if (key_confirm_hold == 300) { // 长按 3 秒打开主菜单
|
||||||
evt.key_event = KEY_EVENT_CONFIRM_LONG;
|
evt.key_event = KEY_EVENT_CONFIRM_LONG;
|
||||||
evt.priority = EVENT_PRIORITY_NORMAL;
|
|
||||||
EventQueue_Push(evt);
|
EventQueue_Push(evt);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (key_confirm_hold >= 2 && key_confirm_hold < 300) {
|
if (key_confirm_hold >= 2 && key_confirm_hold < 300) {
|
||||||
evt.key_event = KEY_EVENT_CONFIRM_CLICK;
|
evt.key_event = KEY_EVENT_CONFIRM_CLICK;
|
||||||
evt.priority = EVENT_PRIORITY_NORMAL;
|
|
||||||
EventQueue_Push(evt);
|
EventQueue_Push(evt);
|
||||||
}
|
}
|
||||||
key_confirm_hold = 0; // 释放归零
|
key_confirm_hold = 0; // 释放归零
|
||||||
}
|
}
|
||||||
|
|
||||||
// SOS 物理求救按键状态机
|
// SOS 物理求救按键状态机 (已解锁)
|
||||||
if (raw_sos) {
|
if (raw_sos) {
|
||||||
key_sos_hold++;
|
key_sos_hold++;
|
||||||
if (key_sos_hold == 300) { // 长按 3 秒,发出 URGENT 优先级求救,插入队首
|
if (key_sos_hold == 300) { // 长按 3 秒,发出求救,插入队首
|
||||||
evt.key_event = KEY_EVENT_SOS_LONG;
|
evt.key_event = KEY_EVENT_SOS_LONG;
|
||||||
evt.priority = EVENT_PRIORITY_URGENT;
|
evt.priority = EVENT_PRIORITY_URGENT;
|
||||||
EventQueue_InsertFront(evt);
|
EventQueue_InsertFront(evt);
|
||||||
|
Uart_SendString("[KEY-DIAG] SOS Long Press Dispatched!\r\n");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (key_sos_hold >= 2 && key_sos_hold < 300) { // 短按即呼救,插入队首
|
if (key_sos_hold >= 2 && key_sos_hold < 300) { // 短按即呼救,插入队首
|
||||||
evt.key_event = KEY_EVENT_SOS_CLICK;
|
evt.key_event = KEY_EVENT_SOS_CLICK;
|
||||||
evt.priority = EVENT_PRIORITY_URGENT;
|
evt.priority = EVENT_PRIORITY_URGENT;
|
||||||
EventQueue_InsertFront(evt);
|
EventQueue_InsertFront(evt);
|
||||||
|
Uart_SendString("[KEY-DIAG] SOS Click Dispatched!\r\n");
|
||||||
}
|
}
|
||||||
key_sos_hold = 0; // 释放归零
|
key_sos_hold = 0; // 释放归零
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,4 +68,13 @@ void Event_Dispatcher_Loop(void);
|
|||||||
*/
|
*/
|
||||||
void Timer1_Init(void);
|
void Timer1_Init(void);
|
||||||
|
|
||||||
|
// 主循环按键扫描定时标志 (由 1ms 中断置 1,主循环响应清除)
|
||||||
|
extern volatile bit key_scan_flag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 主循环按键扫描与消抖处理函数
|
||||||
|
* @details 将按键检测逻辑从中断搬移至此,在主线程执行,避免 ADC 多重采样冲突死锁
|
||||||
|
*/
|
||||||
|
void Event_KeyScan_Poll(void);
|
||||||
|
|
||||||
#endif // __EVENT_H__
|
#endif // __EVENT_H__
|
||||||
|
|||||||
89
App/main.c
89
App/main.c
@@ -12,10 +12,7 @@
|
|||||||
* 系统全局变量定义
|
* 系统全局变量定义
|
||||||
* ========================================================================= */
|
* ========================================================================= */
|
||||||
SystemState current_state = STATE_NORMAL; // 系统整体的当前运行状态,默认为正常时钟待机页
|
SystemState current_state = STATE_NORMAL; // 系统整体的当前运行状态,默认为正常时钟待机页
|
||||||
u8 current_hour = 10; // 系统软时钟:小时 (默认初始化为 10 点)
|
|
||||||
u8 current_min = 35; // 系统软时钟:分钟 (默认初始化为 35 分)
|
|
||||||
u8 current_sec = 0; // 系统软时钟:秒
|
|
||||||
volatile u8 clock_updated = 0; // 分钟跳变标志位,置位后用于驱动 UI 刷新时间显示
|
|
||||||
|
|
||||||
u8 menu_select = 0; // 主菜单当前选中的行高亮索引 (0~2)
|
u8 menu_select = 0; // 主菜单当前选中的行高亮索引 (0~2)
|
||||||
u32 captured_addr = 0; // 配对或报警时,捕获到的传感器 24 位 EV1527 射频物理地址
|
u32 captured_addr = 0; // 配对或报警时,捕获到的传感器 24 位 EV1527 射频物理地址
|
||||||
@@ -58,6 +55,42 @@ volatile u16 comb_hold = 0; // ▲ + ▼ 组合键按下时长累
|
|||||||
* - 'z'/'Z': 模拟超时,强行使闲置计数器置 6000,触发自动深度休眠。
|
* - 'z'/'Z': 模拟超时,强行使闲置计数器置 6000,触发自动深度休眠。
|
||||||
* - 't'/'T': 触发屏幕与无线控制引脚测试。
|
* - 't'/'T': 触发屏幕与无线控制引脚测试。
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* @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)
|
void Debug_ProcessCommand(char cmd)
|
||||||
{
|
{
|
||||||
SystemEvent debug_evt;
|
SystemEvent debug_evt;
|
||||||
@@ -149,9 +182,14 @@ void Debug_ProcessCommand(char cmd)
|
|||||||
} else if (cmd == 'z' || cmd == 'Z') {
|
} else if (cmd == 'z' || cmd == 'Z') {
|
||||||
inactivity_timer = 6000; // 模拟闲置 60 秒超时,强制触发主循环深度休眠
|
inactivity_timer = 6000; // 模拟闲置 60 秒超时,强制触发主循环深度休眠
|
||||||
Uart_SendString("[UART] Force Entering Sleep mode!\r\n");
|
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') {
|
} else if (cmd == 't' || cmd == 'T') {
|
||||||
Loopback_Test(); // 调用回环测试验证屏幕
|
RF_DiagnosticMode('t'); // 触发射频连续发射诊断 (绿色爆闪)
|
||||||
RF_Test_Pin(); // 无线射频测试
|
} else if (cmd == 'r' || cmd == 'R') {
|
||||||
|
RF_DiagnosticMode('r'); // 触发 5 秒射频接收监听诊断
|
||||||
|
} else if (cmd == 'l' || cmd == 'L') {
|
||||||
|
Loopback_Test(); // 触发物理电气回环测试 (短路 P2.1 和 P3.6 自检)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,15 +207,15 @@ void main(void)
|
|||||||
WDT_CONTR = 0x00; // 关闭看门狗定时器
|
WDT_CONTR = 0x00; // 关闭看门狗定时器
|
||||||
|
|
||||||
// ====== 硬件各模块基础初始化 ======
|
// ====== 硬件各模块基础初始化 ======
|
||||||
GPIO_Init(); // 配置各个引脚的推挽输出/准双向模式及内部上拉
|
GPIO_Init(); // 配置各个引脚的推挽输出/准双向模式及内部上拉 (锁存 PWR_HOLD=1)
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0); // 【首要修正】:上电第一时间强制发送 0 码关闭灯条,熄灭上电暂态引起的随机白光!
|
||||||
RF_Init(); // 初始化射频芯片各引脚控制
|
RF_Init(); // 初始化射频芯片各引脚控制
|
||||||
Delay_ms(500); // 软启动延时,等待供电电容稳定
|
Delay_ms(500); // 软启动延时,等待供电电容稳定
|
||||||
SGM_SendPulse(27); // 通过脉冲给 SGM3833 供电芯片发指令,稳压开启 LCD 正负电压轨
|
LCD_Init(); // 初始化 LHS114TC-IF03 (ST7789V) 屏幕控制寄存器序列并开启背光
|
||||||
Delay_ms(50); // 等待电压充盈稳定,避开浪涌
|
|
||||||
LCD_Init(); // 初始化 Truly AMOLED 屏幕控制寄存器序列
|
|
||||||
Uart1_Init(); // 初始化串口 1 波特率 115200 供调试日志
|
Uart1_Init(); // 初始化串口 1 波特率 115200 供调试日志
|
||||||
Uart_SendString("Wristband System Initialized!\r\n");
|
Uart_SendString("Wristband System Initialized!\r\n");
|
||||||
|
|
||||||
|
Self_Test(); // 执行上电硬件自检 (PWR_HOLD锁存、P0.3 ADC按键常态、P1.3 电池电量)
|
||||||
Load_Database(); // 从 IAP Flash 第 254 扇区读取所有已绑定的传感器数据至 RAM
|
Load_Database(); // 从 IAP Flash 第 254 扇区读取所有已绑定的传感器数据至 RAM
|
||||||
|
|
||||||
/* 步骤 1: 初始化事件环形缓冲区队列 */
|
/* 步骤 1: 初始化事件环形缓冲区队列 */
|
||||||
@@ -186,13 +224,25 @@ void main(void)
|
|||||||
/* 步骤 2+5: 初始化应用管理器并登记所有前台应用,默认加载 ClockApp */
|
/* 步骤 2+5: 初始化应用管理器并登记所有前台应用,默认加载 ClockApp */
|
||||||
AppManager_Init();
|
AppManager_Init();
|
||||||
|
|
||||||
|
/* 启动并挂载后台射频监控应用,开始侦听无线传感器信号 */
|
||||||
|
AppManager_AttachToBackground(&rf_monitor_app);
|
||||||
|
|
||||||
/* 启动 1ms 系统基准定时器 Timer1 */
|
/* 启动 1ms 系统基准定时器 Timer1 */
|
||||||
Timer1_Init();
|
Timer1_Init();
|
||||||
|
|
||||||
|
/* 开机自动加载并运行待机时钟前台活跃应用 */
|
||||||
|
AppManager_StartApp(APP_ID_CLOCK);
|
||||||
|
|
||||||
Uart_SendString("[SYS] Event-driven framework ready\r\n");
|
Uart_SendString("[SYS] Event-driven framework ready\r\n");
|
||||||
|
|
||||||
// ====== 主循环 ======
|
// ====== 主循环 ======
|
||||||
while (1) {
|
while (1) {
|
||||||
|
/* 0. 检查主循环按键定时标志 (由 1ms 中断置位,主循环响应清除并处理) */
|
||||||
|
if (key_scan_flag) {
|
||||||
|
key_scan_flag = 0;
|
||||||
|
Event_KeyScan_Poll();
|
||||||
|
}
|
||||||
|
|
||||||
/* 1. 串口非阻塞指令捕获 -> 装配为事件方式入队 */
|
/* 1. 串口非阻塞指令捕获 -> 装配为事件方式入队 */
|
||||||
cmd = Uart_RxChar();
|
cmd = Uart_RxChar();
|
||||||
if (cmd != '\0') {
|
if (cmd != '\0') {
|
||||||
@@ -212,6 +262,8 @@ void main(void)
|
|||||||
inactivity_timer = 0;
|
inactivity_timer = 0;
|
||||||
current_state = STATE_SLEEP; // 系统置为休眠状态
|
current_state = STATE_SLEEP; // 系统置为休眠状态
|
||||||
Enter_Low_Power_Sleep(); // 该函数是阻塞的,MCU 将在此挂起。唤醒后将自动从其后继续执行
|
Enter_Low_Power_Sleep(); // 该函数是阻塞的,MCU 将在此挂起。唤醒后将自动从其后继续执行
|
||||||
|
current_state = STATE_NORMAL; // 唤醒后重新置为正常待机状态
|
||||||
|
AppManager_StartApp(APP_ID_CLOCK); // 重新开启时钟应用以刷新画面和背光
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -219,7 +271,8 @@ void main(void)
|
|||||||
// 唤醒源事件标志,由对应的外部中断 ISR 写入
|
// 唤醒源事件标志,由对应的外部中断 ISR 写入
|
||||||
volatile u8 p0_wakeup_flag = 0; // P0 端口按键唤醒标志
|
volatile u8 p0_wakeup_flag = 0; // P0 端口按键唤醒标志
|
||||||
volatile u8 p2_wakeup_flag = 0; // P2 端口按键唤醒标志
|
volatile u8 p2_wakeup_flag = 0; // P2 端口按键唤醒标志
|
||||||
volatile u8 rf_wakeup_flag = 0; // P2.1 射频中断唤醒标志
|
volatile u8 p3_wakeup_flag = 0; // P3 端口射频中断唤醒标志
|
||||||
|
volatile u8 rf_wakeup_flag = 0; // 射频中断唤醒标志
|
||||||
|
|
||||||
/* =========================================================================
|
/* =========================================================================
|
||||||
* STC32G 端口掉电外部中断唤醒中断服务程序 (ISR)
|
* STC32G 端口掉电外部中断唤醒中断服务程序 (ISR)
|
||||||
@@ -248,3 +301,15 @@ void Port2_Isr(void) interrupt 15
|
|||||||
P2INTF = 0x00; // 清除 P2 端口中断悬挂标志位
|
P2INTF = 0x00; // 清除 P2 端口中断悬挂标志位
|
||||||
P2INTE = 0x00; // 立即关闭 P2 中断通道,防止低电平期间反复重入
|
P2INTE = 0x00; // 立即关闭 P2 中断通道,防止低电平期间反复重入
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Port3 端口掉电中断服务函数 (由 isr_jump.asm 引导定位至此)
|
||||||
|
* @details 负责处理 P3.6 (新射频数据脚) 在休眠模式下的物理跳变唤醒。
|
||||||
|
*/
|
||||||
|
void Port3_Isr(void) interrupt 16
|
||||||
|
{
|
||||||
|
EAXFR = 1; // 使能访问扩展寄存器
|
||||||
|
p3_wakeup_flag = P3INTF | 0x01;// 读取并备份 P3 中断标志寄存器值 (0x01作安全掩码)
|
||||||
|
P3INTF = 0x00; // 清除 P3 端口中断悬挂标志位
|
||||||
|
P3INTE = 0x00; // 立即关闭 P3 中断通道,防止反复重入
|
||||||
|
}
|
||||||
|
|||||||
159
App/rgb.c
159
App/rgb.c
@@ -1,122 +1,99 @@
|
|||||||
#include "rgb.h"
|
#include "rgb.h"
|
||||||
|
#include "system.h"
|
||||||
|
|
||||||
/* ====== SPI 寄存器映射地址定义 (防止 stc32g.h 库版本中未显式定义) ====== */
|
/* ====== SPI Register Defines (in case stc32g.h lacks them) ====== */
|
||||||
#ifndef P_SW1
|
#ifndef P_SW1
|
||||||
sfr P_SW1 = 0xA2; // 外设端口切换寄存器 1
|
sfr P_SW1 = 0xA2;
|
||||||
sfr SPCTL = 0xCE; // SPI 控制寄存器
|
sfr SPCTL = 0xCE;
|
||||||
sfr SPSTAT = 0xCD; // SPI 状态寄存器
|
sfr SPSTAT = 0xCD;
|
||||||
sfr SPDAT = 0xCF; // SPI 数据寄存器
|
sfr SPDAT = 0xCF;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化内核 SPI 控制器
|
|
||||||
* @details 将 SPI 的物理管脚切换至 P2 端口的第二组外设映射位置(SCLK=P2.5, MOSI=P2.3),
|
|
||||||
* 并配置 SPI 传输速率,使其产生的脉冲宽满足 WS2812B/FCOB 的 1 码和 0 码时序。
|
|
||||||
*/
|
|
||||||
void SPI_Init(void)
|
void SPI_Init(void)
|
||||||
{
|
{
|
||||||
// 将 P_SW1 寄存器的第 2、3 位 (SPI_S) 配置为 01,即将 SPI 外设映射至 P2.2~P2.5 端口上
|
|
||||||
P_SW1 = (P_SW1 & ~0x0C) | 0x04;
|
P_SW1 = (P_SW1 & ~0x0C) | 0x04;
|
||||||
|
|
||||||
// 配置 SPCTL (SPI Control Register):
|
|
||||||
// SSIG = 1 (忽略SS引脚,置为主机)
|
|
||||||
// SPEN = 1 (启用SPI模块)
|
|
||||||
// MSTR = 1 (配置为主机模式)
|
|
||||||
// SPR0 = 1 (3MHz communication speed @ 24MHz System Clock)
|
|
||||||
SPCTL = 0xD1;
|
SPCTL = 0xD1;
|
||||||
|
|
||||||
// 清除 SPI 状态标志 (写入1清除 SPIF 和 WCOL 标志位)
|
|
||||||
SPSTAT = 0xC0;
|
SPSTAT = 0xC0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 字节数据转 3-bit 编码的 WS2812B 驱动波形数据
|
|
||||||
* @param val 要转码的一个 8 位色彩数值 (如R、G、B颜色分量)
|
|
||||||
* @param out 转换后存储目标的高速缓冲缓冲区首地址,1 字节拆分成 3 字节表示物理波形
|
|
||||||
* @details WS2812B 通讯协议需要通过高低电平的持续脉冲时间比来判断 "1" 码或 "0" 码。
|
|
||||||
* 通过将 SPI 速率设为 3.0 Mbps(即每个 SPI bit 占 ~333ns):
|
|
||||||
* - 一个 WS2812B 协议的 "1" 码(800ns高, 450ns低)可用二进制 `110` (高高低,~666ns高, ~333ns低) 来模拟;
|
|
||||||
* - 一个 WS2812B 协议的 "0" 码(400ns高, 850ns低)可用二进制 `100` (高低低,~333ns高, ~666ns低) 来模拟;
|
|
||||||
* 因此,1 个字节(8 bits)的颜色深度数据被完美重映射编码为 24 个 SPI 数据 bits(共需 3 个 SPI 字节发送)。
|
|
||||||
*/
|
|
||||||
void Encode_Byte(u8 val, u8 *out)
|
void Encode_Byte(u8 val, u8 *out)
|
||||||
{
|
{
|
||||||
u32 spi_val = 0; // 用于拼接 24 位 SPI 映射串
|
|
||||||
u8 i;
|
u8 i;
|
||||||
for (i = 0; i < 8; i++)
|
for (i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
spi_val <<= 3; // 每次左移 3 位,为下一个像素的波形让出空间
|
u8 pair_val = 0;
|
||||||
if (val & (0x80 >> i))
|
if (val & (0x80 >> (2 * i)))
|
||||||
{
|
{
|
||||||
spi_val |= 6; // 该 bit 为 1,对应 WS2812B "1" 码时序,用二进制 '110' (即数值6) 填充
|
pair_val |= 0xC0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
spi_val |= 4; // 该 bit 为 0,对应 WS2812B "0" 码时序,用二进制 '100' (即数值4) 填充
|
pair_val |= 0x80;
|
||||||
}
|
}
|
||||||
|
if (val & (0x80 >> (2 * i + 1)))
|
||||||
|
{
|
||||||
|
pair_val |= 0x0C;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pair_val |= 0x08;
|
||||||
|
}
|
||||||
|
out[i] = pair_val;
|
||||||
}
|
}
|
||||||
// 将拼接好的 24 位波形数据(前高位后低位)拆分装载进输出缓冲区中
|
|
||||||
out[0] = (u8)(spi_val >> 16);
|
|
||||||
out[1] = (u8)(spi_val >> 8);
|
|
||||||
out[2] = (u8)spi_val;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 发送两颗级联 RGB 灯珠的驱动命令
|
|
||||||
* @details 负责将两组 GRB 格式色彩数据(g1, r1, b1 和 g2, r2, b2)转换为 WS2812B 的 SPI 脉冲编码,
|
|
||||||
* 并在发送期间实施硬件引脚复用隔离,发送完成后发出复位复位电平。
|
|
||||||
*/
|
|
||||||
void RGB_Send(u8 g1, u8 r1, u8 b1, u8 g2, u8 r2, u8 b2)
|
void RGB_Send(u8 g1, u8 r1, u8 b1, u8 g2, u8 r2, u8 b2)
|
||||||
{
|
{
|
||||||
u8 idata spi_buf[18]; // 2 颗灯 * 3 个基色通道 * 3 字节编码 = 18 字节缓冲
|
u8 idata buf[6];
|
||||||
u8 i;
|
u8 i, mask;
|
||||||
|
buf[0] = g1; buf[1] = r1; buf[2] = b1;
|
||||||
// 分别转码两颗灯珠的 G、R、B 通道,顺序填充进 SPI 缓冲区
|
buf[3] = g2; buf[4] = r2; buf[5] = b2;
|
||||||
Encode_Byte(g1, &spi_buf[0]);
|
|
||||||
Encode_Byte(r1, &spi_buf[3]);
|
EA = 0;
|
||||||
Encode_Byte(b1, &spi_buf[6]);
|
|
||||||
Encode_Byte(g2, &spi_buf[9]);
|
|
||||||
Encode_Byte(r2, &spi_buf[12]);
|
|
||||||
Encode_Byte(b2, &spi_buf[15]);
|
|
||||||
|
|
||||||
EA = 0; // 关闭全局中断,防止 SPI 数据发送被高频中断打断,造成 WS2812B 的时序畸变
|
|
||||||
|
|
||||||
// 【硬件复用隔离机制】:
|
|
||||||
// 物理引脚 P2.5 同时被用作“振动马达控制端 (MOTOR)”和“SPI SCLK 时钟信号输出”。
|
|
||||||
// 在 SPI 发送 RGB 期间,SCLK 将高速振荡,这会导致马达发生异常高频振动甚至噪音。
|
|
||||||
// 因此在启动 SPI 发送前,必须先将 P2.5 (MOTOR) 临时配置为高阻输入模式(M1=1, M0=0),
|
|
||||||
// 从而与 SPI 时钟引脚在电气上实现物理隔离,防止杂散时钟信号驱动马达。
|
|
||||||
P2M1 |= (1 << 5);
|
|
||||||
P2M0 &= ~(1 << 5);
|
|
||||||
|
|
||||||
SPI_Init(); // 配置并启动 SPI 外设控制器
|
|
||||||
|
|
||||||
// 循环发送 18 字节的 WS2812B 脉冲编码数据
|
|
||||||
for (i = 0; i < 18; i++)
|
|
||||||
{
|
|
||||||
SPDAT = spi_buf[i]; // 装载待发数据到数据寄存器
|
|
||||||
while (!(SPSTAT & 0x80)); // 等待 SPI 状态寄存器第 7 位 (SPIF) 变为 1,代表发送结束
|
|
||||||
SPSTAT = 0xC0; // 往状态寄存器写入1以清零 SPIF 和 WCOL 标志
|
|
||||||
}
|
|
||||||
|
|
||||||
SPCTL = 0; // 彻底关闭 SPI 控制器,释放总线
|
|
||||||
|
|
||||||
// 【发送 RESET 复位低电平帧】:
|
|
||||||
// WS2812B 驱动芯片要求在所有色彩帧数据发完后,DIN 必须维持大于 80us 的低电平以将数据刷入锁存。
|
|
||||||
// 首先将 P2.3 (RGB_DIN) 重新配置为推挽输出模式 (M1=0, M0=1)
|
|
||||||
P2M1 &= ~(1 << 3);
|
P2M1 &= ~(1 << 3);
|
||||||
P2M0 |= (1 << 3);
|
P2M0 |= (1 << 3);
|
||||||
RGB_DIN = 0; // 强行拉低 DIN 信号线
|
|
||||||
for (i = 0; i < 200; i++)
|
|
||||||
{
|
|
||||||
_nop_(); _nop_(); _nop_(); // 软件延时,保持低电平时间大于 100微秒
|
|
||||||
}
|
|
||||||
|
|
||||||
// 【恢复复用引脚功能】:
|
|
||||||
// 恢复 P2.5 (MOTOR) 引脚为普通的推挽输出模式 (M1=0, M0=1),以便重新由定时器或应用控制振动马达
|
|
||||||
P2M1 &= ~(1 << 5);
|
P2M1 &= ~(1 << 5);
|
||||||
P2M0 |= (1 << 5);
|
P2M0 |= (1 << 5);
|
||||||
MOTOR = 0; // 默认拉低马达引脚,防止发声
|
MOTOR = 0;
|
||||||
|
|
||||||
EA = 1; // 恢复全局中断使能
|
for (i = 0; i < 6; i++)
|
||||||
|
{
|
||||||
|
for (mask = 0x80; mask; mask >>= 1)
|
||||||
|
{
|
||||||
|
if (buf[i] & mask)
|
||||||
|
{
|
||||||
|
RGB_DIN = 1;
|
||||||
|
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
|
||||||
|
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
|
||||||
|
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
|
||||||
|
RGB_DIN = 0;
|
||||||
|
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
|
||||||
|
_nop_();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RGB_DIN = 1;
|
||||||
|
_nop_(); _nop_(); _nop_();
|
||||||
|
RGB_DIN = 0;
|
||||||
|
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
|
||||||
|
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
|
||||||
|
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RGB_DIN = 0;
|
||||||
|
for (i = 0; i < 200; i++)
|
||||||
|
{
|
||||||
|
_nop_(); _nop_(); _nop_();
|
||||||
|
}
|
||||||
|
|
||||||
|
EA = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RGB_Bitbang_Send24(u8 g, u8 r, u8 b)
|
||||||
|
{
|
||||||
|
(void)g; (void)r; (void)b;
|
||||||
}
|
}
|
||||||
|
|||||||
757
App/system.c
757
App/system.c
@@ -1,237 +1,473 @@
|
|||||||
#include "system.h"
|
#include "system.h"
|
||||||
#include "rgb.h"
|
#include "rgb.h"
|
||||||
#include "ui.h"
|
#include "ui.h"
|
||||||
|
#include "clock.h"
|
||||||
#include "../Drivers/lcd.h"
|
#include "../Drivers/lcd.h"
|
||||||
#include "../Drivers/rf.h"
|
#include "../Drivers/rf.h"
|
||||||
#include "app_manager.h"
|
#include "../Drivers/adc.h"
|
||||||
#include "database.h"
|
#include "../Drivers/pcf8563.h"
|
||||||
|
#include "intrins.h"
|
||||||
|
|
||||||
// 外部声明的中断唤醒标志 (在 main.c 中定义,用于在中断中记录是谁唤醒了系统)
|
// 引用 main.c 中的全局唤醒与运行状态变量
|
||||||
extern volatile u8 p0_wakeup_flag;
|
extern volatile u8 p0_wakeup_flag;
|
||||||
extern volatile u8 p2_wakeup_flag;
|
extern volatile u8 p2_wakeup_flag;
|
||||||
|
extern volatile u8 p3_wakeup_flag;
|
||||||
extern volatile u8 rf_wakeup_flag;
|
extern volatile u8 rf_wakeup_flag;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 初始化 STC32G 单片机的 GPIO 管脚模式及初始电平
|
* @brief 系统 GPIO 端口模式初始化
|
||||||
* @details 按照电路原理图对各个端口的输入输出模式、内部上拉、外设扩展功能进行配置。
|
* @details 配置各引脚的推挽输出、高阻输入及准双向模式,建立系统初始电气环境。
|
||||||
*/
|
*/
|
||||||
void GPIO_Init(void)
|
void GPIO_Init(void)
|
||||||
{
|
{
|
||||||
IE = 0x00; // 禁用 CPU 全局中断及常规中断
|
// 使能访问扩展特殊功能寄存器 XSFR
|
||||||
IE2 = 0x00; // 禁用扩展中断
|
EAXFR = 1;
|
||||||
TCON = 0x00; // 清零定时器控制寄存器,关闭 Timer0/1 的常规控制
|
|
||||||
TMOD &= 0xF0; // 清零 Timer0 工作模式
|
|
||||||
TR0 = 0; // 停止 Timer0 运行
|
|
||||||
ET0 = 0; // 禁用 Timer0 中断允许
|
|
||||||
AUXR = 0x00; // 辅助寄存器清零,时钟分频恢复默认
|
|
||||||
INTCLKO = 0x00; // 禁用时钟输出与固定外部中断触发
|
|
||||||
P_SW1 = 0x00; // 重置外设映射 1 (串口1、SPI、I2C等)
|
|
||||||
P_SW2 = 0x80; // 开启 EAXFR (Extended Access Flag Register),允许访问 P0PU/P2PU/P0INTE/P2INTE 等扩展 SFR 寄存器
|
|
||||||
P_SW3 = 0x00; // 重置外设映射 3
|
|
||||||
|
|
||||||
// 1. 配置 P0.0 (BAT_ADC) 电池电量采集管脚为高阻输入模式 (M1=1, M0=0)
|
// 1. 配置 PWR_HOLD (P0.0) 为推挽输出模式 (P0M1 bit0=0, P0M0 bit0=1)
|
||||||
P0M1 |= (1 << 0);
|
P0M1 &= ~(1 << 0);
|
||||||
P0M0 &= ~(1 << 0);
|
P0M0 |= (1 << 0);
|
||||||
|
PWR_HOLD = 1; // 上电第一时间拉高,锁存电源保持供电
|
||||||
|
|
||||||
// 2. 配置 P0.1 (KEY_UP), P0.2 (KEY_CONFIRM), P0.3 (KEY_DOWN) 按键引脚为准双向口模式并置 1 (M1=0, M0=0)
|
// 2. 配置 KEY_UP (P0.1), KEY_CONFIRM (P0.2) 为高阻输入/带上拉准双向
|
||||||
P0M1 &= ~((1 << 1) | (1 << 2) | (1 << 3));
|
P0M1 &= ~((1 << 1) | (1 << 2));
|
||||||
P0M0 &= ~((1 << 1) | (1 << 2) | (1 << 3));
|
P0M0 &= ~((1 << 1) | (1 << 2));
|
||||||
KEY_UP = 1;
|
KEY_UP = 1;
|
||||||
KEY_CONFIRM = 1;
|
KEY_CONFIRM = 1;
|
||||||
KEY_DOWN = 1;
|
|
||||||
|
|
||||||
// 3. 配置 P2.3 (RGB_DIN) 幻彩灯条控制线为推挽输出模式 (M1=0, M0=1)
|
// 3. 初始化 STC32G 硬件 ADC 模块 (其中自动配置 P0.3 和 P1.3 为高阻输入模式)
|
||||||
P2M1 &= ~(1 << 3);
|
ADC_Init();
|
||||||
P2M0 |= (1 << 3);
|
|
||||||
RGB_DIN = 0; // 默认拉低,保持复位时序状态
|
|
||||||
|
|
||||||
// 4. 配置 P2.5 (MOTOR) 振动马达控制端为推挽输出模式 (M1=0, M0=1)
|
// 4. 配置 PCF8563 I2C 管脚 P3.2 (SCL) 与 P3.3 (SDA) 为准双向口模式,并开上拉
|
||||||
P2M1 &= ~(1 << 5);
|
P3M1 &= ~((1 << 2) | (1 << 3));
|
||||||
P2M0 |= (1 << 5);
|
P3M0 &= ~((1 << 2) | (1 << 3));
|
||||||
MOTOR = 0; // 默认不震动
|
P3PU |= (1 << 2) | (1 << 3); // 开启 P3.2 与 P3.3 的内部 4.7K 上拉电阻
|
||||||
|
RTC_SCL = 1;
|
||||||
|
RTC_SDA = 1;
|
||||||
|
|
||||||
// 5. 配置 P2.6 (KEY_SOS) 物理求救按键引脚为准双向口模式并置 1 (M1=0, M0=0)
|
// 5. 配置推挽输出引脚:
|
||||||
|
// MOTOR (P2.5), RGB_DIN (P2.3) -> P2M1 bit5,3=0; P2M0 bit5,3=1
|
||||||
|
P2M1 &= ~((1 << 5) | (1 << 3));
|
||||||
|
P2M0 |= ((1 << 5) | (1 << 3));
|
||||||
|
MOTOR = 0;
|
||||||
|
RGB_DIN = 0; // P2.3 初始拉低,防止上电期间 WS2812B 误触发
|
||||||
|
|
||||||
|
// 配置 KEY_SOS (P2.6) 为带上拉准双向口模式以确保输入稳定
|
||||||
P2M1 &= ~(1 << 6);
|
P2M1 &= ~(1 << 6);
|
||||||
P2M0 &= ~(1 << 6);
|
P2M0 &= ~(1 << 6);
|
||||||
KEY_SOS = 1;
|
KEY_SOS = 1;
|
||||||
|
(*(unsigned char volatile xdata *)0xFE12) |= (1 << 6); // 使能 P2.6 的强内部上拉电阻 (P2PU = 0xFE12)
|
||||||
|
|
||||||
// 6. 配置 P2.2 (SHUT) 射频芯片使能引脚为推挽输出模式 (M1=0, M0=1)
|
// SGM_CTRL (P1.7), LCD 控制管脚 (P1.0, P1.1, P1.4, P1.5, P1.6) 置为推挽输出
|
||||||
P2M1 &= ~(1 << 2);
|
P1M1 &= ~((1 << 7) | (1 << 0) | (1 << 1) | (1 << 4) | (1 << 5) | (1 << 6));
|
||||||
P2M0 |= (1 << 2);
|
P1M0 |= ((1 << 7) | (1 << 0) | (1 << 1) | (1 << 4) | (1 << 5) | (1 << 6));
|
||||||
SHUT = 1; // 默认拉高使能开启,允许无线芯片运行
|
|
||||||
|
|
||||||
// 7. 配置 P0.1, P0.2, P0.3 和 P2.6 的内部上拉电阻,避免引脚因浮空抖动误唤醒系统
|
// SHUT (P3.5) 射频/外设开启使能脚置为推挽输出
|
||||||
P0PU |= 0x0E; // 开启 P0.1/P0.2/P0.3 上拉
|
P3M1 &= ~(1 << 5);
|
||||||
P2PU |= 0x40; // 开启 P2.6 上拉
|
P3M0 |= (1 << 5);
|
||||||
|
SHUT = 0; // 低电平开启工作
|
||||||
|
|
||||||
// 8. 配置 P2.7 (DET) 充电检测脚为高阻输入模式 (M1=1, M0=0)
|
// 7. 配置 CHRG_DET (P1.2) 为高阻输入模式,适配 DET 模拟 ADC 通道 2
|
||||||
P2M1 |= (1 << 7);
|
// 由于是精密模拟电平检测,切勿开启引脚内部上拉电阻
|
||||||
P2M0 &= ~(1 << 7);
|
P1M1 |= (1 << 2);
|
||||||
|
P1M0 &= ~(1 << 2);
|
||||||
// 9. 配置 P3.0 (UART1_RXD - 输入)、P3.1 (UART1_TXD - 输出) 以及 P3.4 (SGM_CTRL - 屏幕电源控制,输出)
|
// P1PU 在扩展 XSFR 空间中没有被定义的话可以用 P1 端口寄存器或者是关闭上拉,
|
||||||
P3M1 &= ~((1 << 0) | (1 << 1) | (1 << 4)); // 清除 M1 相应位
|
// STC32G 中端口上拉寄存器是 P1PU (FE11H),因为是 XSFR 我们在 config.h 里有 extern 或直接操作
|
||||||
P3M0 &= ~(1 << 0); // 准双向 / 输入模式
|
// 扩展 XSFR P1PU 地址为 0xFE11
|
||||||
P3M0 |= ((1 << 1) | (1 << 4)); // 推挽输出模式 (TXD 和 SGM_CTRL)
|
(*(unsigned char volatile xdata *)0xFE11) &= ~(1 << 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 阻塞式毫秒级延时函数 (基于 CPU 频率估算环路次数)
|
* @brief 初始化 UART1 串口通信
|
||||||
* @param ms 延迟毫秒数
|
* @details 设定波特率 115200 (使用 Timer2 作为发生器,24.0MHz 晶振)
|
||||||
*/
|
|
||||||
void Delay_ms(u16 ms)
|
|
||||||
{
|
|
||||||
u16 i, j;
|
|
||||||
for (i = 0; i < ms; i++)
|
|
||||||
// 24MHz 晶振下,双重循环实现约 1ms 延迟
|
|
||||||
for (j = 12000; j > 0; j--);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 精确的 10 微秒延时函数 (利用 8051 指令循环时间)
|
|
||||||
*/
|
|
||||||
void Delay10us(void)
|
|
||||||
{
|
|
||||||
unsigned char data i;
|
|
||||||
_nop_();
|
|
||||||
i = 30; // 循环常数
|
|
||||||
while (--i);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 规避 16 位寄存器在读取瞬间发生进位导致的读错问题
|
|
||||||
* @details 采用双重读取比对法,读取 TH0 -> TL0 -> 再读一次 TH0,如果两次高 8 位一致,代表没有发生高低字节间的溢出进位,是安全的。
|
|
||||||
* @return u16 当前 Timer0 的 16 位计数值
|
|
||||||
*/
|
|
||||||
u16 GetTimer0_Safe(void)
|
|
||||||
{
|
|
||||||
u8 h1, l, h2;
|
|
||||||
do {
|
|
||||||
h1 = TH0;
|
|
||||||
l = TL0;
|
|
||||||
h2 = TH0;
|
|
||||||
} while (h1 != h2); // 当两次 TH0 值相同时说明读取安全,退出循环
|
|
||||||
return ((u16)h1 << 8) | l;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 基于 Timer0 的精确微秒延时函数
|
|
||||||
* @param us 延时微秒数
|
|
||||||
*/
|
|
||||||
void Delay_us(u16 us)
|
|
||||||
{
|
|
||||||
// 根据系统时钟频率及 T0 运行在 12T 模式下计算时钟 Tick 计数量
|
|
||||||
u16 ticks = (u16)((u32)us * (MAIN_Fosc / 1000000UL) / 12UL);
|
|
||||||
TL0 = 0; // 清零 Timer0 低字节
|
|
||||||
TH0 = 0; // 清零 Timer0 高字节
|
|
||||||
TR0 = 1; // 启动 Timer0 计数
|
|
||||||
while (GetTimer0_Safe() < ticks); // 等待计数达到目标值
|
|
||||||
TR0 = 0; // 停止 Timer0
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化串口 1 (UART1) 为 115200 波特率,主要用于调试输出
|
|
||||||
*/
|
*/
|
||||||
void Uart1_Init(void)
|
void Uart1_Init(void)
|
||||||
{
|
{
|
||||||
// 计算 115200 波特率在定时器 2 (1T模式) 的重装载值
|
SCON = 0x50; // 8 位数据, 可变波特率
|
||||||
u16 reload = (u16)(65536UL - (MAIN_Fosc / 4 / 115200UL));
|
AUXR |= 0x01; // 串口 1 选择 Timer2 做波特率发生器
|
||||||
SCON = 0x50; // 8位数据位,允许串口接收 (REN=1)
|
AUXR |= 0x04; // Timer2 时钟为 1T 模式
|
||||||
AUXR |= 0x01; // 串口 1 波特率发生器选择定时器 2
|
T2L = 0xCC; // 24MHz / 115200 = 208 (0xD0), 对应 reload 值 65536 - 52 = 0xFFCC
|
||||||
AUXR |= 0x04; // 定时器 2 工作在 1T 模式 (无分频)
|
T2H = 0xFF;
|
||||||
T2L = (u8)reload; // 载入重装载低字节
|
AUXR |= 0x10; // 启动 Timer2 运行
|
||||||
T2H = (u8)(reload >> 8);// 载入重装载高字节
|
|
||||||
AUXR |= 0x10; // 启动定时器 2
|
|
||||||
TI = 0; // 清除串口发送标志位
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 串口 1 发送单字节数据,并在发送时屏蔽接收以防自收自发杂波干扰
|
* @brief 串口 1 发送单字节数据
|
||||||
* @param dat 待发送的数据字节
|
* @param dat 待发送的字节
|
||||||
*/
|
*/
|
||||||
void Uart_SendByte(u8 dat)
|
void Uart_SendByte(u8 dat)
|
||||||
{
|
{
|
||||||
REN = 0; // 临时关闭串口接收,避免发送杂波反灌被接收中断误认
|
SBUF = dat;
|
||||||
SBUF = dat; // 送入串口数据寄存器启动发送
|
while (!TI);
|
||||||
while (!TI);// 阻塞等待 TI (Transmit Interrupt) 变为 1,表示发送完毕
|
TI = 0;
|
||||||
TI = 0; // 清除发送中断标志位
|
|
||||||
RI = 0; // 顺便清零接收标志,确保没有误动作
|
|
||||||
REN = 1; // 重新开启接收允许
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 串口发送字符串,直到遇到 '\0' 结束
|
* @brief 串口 1 发送字符串
|
||||||
* @param s 字符串首地址
|
* @param s 字符串指针
|
||||||
*/
|
*/
|
||||||
void Uart_SendString(char *s)
|
void Uart_SendString(char *s)
|
||||||
{
|
{
|
||||||
REN = 0; // 临时关闭接收
|
|
||||||
while (*s) {
|
while (*s) {
|
||||||
SBUF = *s++;
|
Uart_SendByte(*s++);
|
||||||
while (!TI); // 等待发送结束
|
|
||||||
TI = 0;
|
|
||||||
}
|
}
|
||||||
RI = 0;
|
|
||||||
REN = 1; // 重新开启接收
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 非阻塞接收串口字符
|
* @brief 串口 1 非阻塞接收字符
|
||||||
* @return char 接收到的字符,若无则返回 0
|
* @return char 接收到的字符,若无接收数据则返回 '\0'
|
||||||
*/
|
*/
|
||||||
char Uart_RxChar(void)
|
char Uart_RxChar(void)
|
||||||
{
|
{
|
||||||
if (RI) {
|
if (RI) {
|
||||||
char c = SBUF; // 读取接收数据
|
RI = 0;
|
||||||
RI = 0; // 手动清零接收标志位
|
return SBUF;
|
||||||
return c; // 返回字符
|
|
||||||
}
|
}
|
||||||
return 0;
|
return '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 串口输出 4 位十六进制数 (半字节,0x0~0xF)
|
* @brief 阻塞毫秒延时
|
||||||
|
* @param ms 毫秒数
|
||||||
|
*/
|
||||||
|
void Delay_ms(u16 ms)
|
||||||
|
{
|
||||||
|
u16 i, j;
|
||||||
|
for (i = 0; i < ms; i++) {
|
||||||
|
for (j = 0; j < 2000; j++) {
|
||||||
|
_nop_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 阻塞微秒延时
|
||||||
|
* @param us 微秒数
|
||||||
|
*/
|
||||||
|
void Delay_us(u16 us)
|
||||||
|
{
|
||||||
|
u16 i;
|
||||||
|
for (i = 0; i < us; i++) {
|
||||||
|
_nop_(); _nop_(); _nop_(); _nop_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 系统开机上电硬件自检函数
|
||||||
|
* @details 检测 PWR_HOLD 锁存状态、ADC 采样基准与串口通讯,输出完整自检 report
|
||||||
|
*/
|
||||||
|
void Self_Test(void)
|
||||||
|
{
|
||||||
|
u16 key_down_adc;
|
||||||
|
u16 vbat_mv;
|
||||||
|
u16 batt_adc_raw;
|
||||||
|
u8 percent;
|
||||||
|
|
||||||
|
Uart_SendString("\r\n========================================\r\n");
|
||||||
|
Uart_SendString("[SELF-TEST] System Power-On Self Test...\r\n");
|
||||||
|
|
||||||
|
// 执行 I2C 总线器件扫描,查找 RTC 是否物理响应
|
||||||
|
PCF8563_ScanDiagnostic();
|
||||||
|
|
||||||
|
// 0. I2C 硬件脚电平诊断测试 (验证引脚可控性与短路)
|
||||||
|
Uart_SendString("[I2C-DIAG] Pin Level Test Starting...\r\n");
|
||||||
|
// 写 1 测试 (准双向+上拉)
|
||||||
|
P3M1 &= ~((1 << 2) | (1 << 3));
|
||||||
|
P3M0 &= ~((1 << 2) | (1 << 3));
|
||||||
|
P3PU |= (1 << 2) | (1 << 3);
|
||||||
|
RTC_SCL = 1;
|
||||||
|
RTC_SDA = 1;
|
||||||
|
Delay_ms(10);
|
||||||
|
Uart_SendString("[I2C-DIAG] Write SCL=1, SDA=1 -> Real Level: SCL=");
|
||||||
|
Uart_SendByte((u8)(RTC_SCL ? '1' : '0'));
|
||||||
|
Uart_SendString(", SDA=");
|
||||||
|
Uart_SendByte((u8)(RTC_SDA ? '1' : '0'));
|
||||||
|
Uart_SendString("\r\n");
|
||||||
|
|
||||||
|
// 写 0 测试
|
||||||
|
RTC_SCL = 0;
|
||||||
|
RTC_SDA = 0;
|
||||||
|
Delay_ms(10);
|
||||||
|
Uart_SendString("[I2C-DIAG] Write SCL=0, SDA=0 -> Real Level: SCL=");
|
||||||
|
Uart_SendByte((u8)(RTC_SCL ? '1' : '0'));
|
||||||
|
Uart_SendString(", SDA=");
|
||||||
|
Uart_SendByte((u8)(RTC_SDA ? '1' : '0'));
|
||||||
|
Uart_SendString("\r\n");
|
||||||
|
|
||||||
|
// 0.2 执行 RTC 底层单独试读测试
|
||||||
|
PCF8563_TryReadDiagnostic();
|
||||||
|
|
||||||
|
// 1. 验证 PWR_HOLD 锁存状态
|
||||||
|
if (PWR_HOLD == 1) {
|
||||||
|
Uart_SendString("[SELF-TEST] PWR_HOLD Pin State: HIGH [OK]\r\n");
|
||||||
|
} else {
|
||||||
|
Uart_SendString("[SELF-TEST] PWR_HOLD Pin State: LOW [FAIL]\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查 P0.3 ADC 按键基准常态电压
|
||||||
|
key_down_adc = ADC_ReadFiltered(ADC_CHANNEL_KEY_DOWN);
|
||||||
|
Uart_SendString("[SELF-TEST] KEY_DOWN (P0.3) ADC Raw Value: ");
|
||||||
|
Uart_SendByte((u8)('0' + (key_down_adc / 1000) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (key_down_adc / 100) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (key_down_adc / 10) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (key_down_adc % 10)));
|
||||||
|
Uart_SendString(" [OK]\r\n");
|
||||||
|
|
||||||
|
// 2.2 检测 KEY_SOS (P2.6) 物理电平常态
|
||||||
|
Uart_SendString("[SELF-TEST] KEY_SOS (P2.6) Pin Level: ");
|
||||||
|
Uart_SendByte((u8)(KEY_SOS ? '1' : '0'));
|
||||||
|
Uart_SendString("\r\n");
|
||||||
|
|
||||||
|
// 3. 检查 P1.3 电池电压检测与百分比
|
||||||
|
vbat_mv = ADC_GetBatteryVoltage_mV();
|
||||||
|
percent = ADC_GetBatteryPercent();
|
||||||
|
batt_adc_raw = ADC_ReadFiltered(ADC_CHANNEL_BATTERY);
|
||||||
|
|
||||||
|
Uart_SendString("[SELF-TEST] Battery Raw ADC: ");
|
||||||
|
Uart_SendByte((u8)('0' + (batt_adc_raw / 1000) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (batt_adc_raw / 100) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (batt_adc_raw / 10) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (batt_adc_raw % 10)));
|
||||||
|
|
||||||
|
Uart_SendString(" -> Voltage: ");
|
||||||
|
Uart_SendByte((u8)('0' + (vbat_mv / 1000) % 10));
|
||||||
|
Uart_SendByte('.');
|
||||||
|
Uart_SendByte((u8)('0' + (vbat_mv / 100) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (vbat_mv / 10) % 10));
|
||||||
|
|
||||||
|
Uart_SendString(" V, Capacity: ");
|
||||||
|
Uart_SendByte((u8)('0' + (percent / 100) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (percent / 10) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (percent % 10)));
|
||||||
|
Uart_SendString("% [OK]\r\n");
|
||||||
|
|
||||||
|
// 4. 检查充电状态
|
||||||
|
{
|
||||||
|
u16 det_adc = ADC_ReadFiltered(ADC_CHANNEL_CHRG_DET);
|
||||||
|
u16 det_mv = (det_adc * 3300UL) / 4095UL;
|
||||||
|
Uart_SendString("[SELF-TEST] CHRG_DET (P1.2) ADC: ");
|
||||||
|
Uart_SendByte((u8)('0' + (det_adc / 1000) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (det_adc / 100) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (det_adc / 10) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (det_adc % 10)));
|
||||||
|
Uart_SendString(" -> Voltage: ");
|
||||||
|
Uart_SendByte((u8)('0' + (det_mv / 1000) % 10));
|
||||||
|
Uart_SendByte('.');
|
||||||
|
Uart_SendByte((u8)('0' + (det_mv / 100) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (det_mv / 10) % 10));
|
||||||
|
Uart_SendString(" V -> ");
|
||||||
|
if (det_mv >= 800 && det_mv <= 1800) {
|
||||||
|
Uart_SendString("CHARGING [OK]\r\n");
|
||||||
|
} else if (det_mv >= 2200 && det_mv <= 2800) {
|
||||||
|
Uart_SendString("FULLY CHARGED [OK]\r\n");
|
||||||
|
} else {
|
||||||
|
Uart_SendString("NOT CHARGING [OK]\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Uart_SendString("[SELF-TEST] Self Test Completed Successfully!\r\n");
|
||||||
|
Uart_SendString("========================================\r\n\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 执行关机断电流程
|
||||||
|
* @details 关断外设、震动提示并拉低 PWR_HOLD 引脚切断总电源
|
||||||
|
*/
|
||||||
|
void Power_Off(void)
|
||||||
|
{
|
||||||
|
Uart_SendString("[SYS] Executing Power Off sequence...\r\n");
|
||||||
|
|
||||||
|
// 马达震动反馈 100ms
|
||||||
|
MOTOR = 1;
|
||||||
|
Delay_ms(100);
|
||||||
|
MOTOR = 0;
|
||||||
|
|
||||||
|
// 关断屏幕背光与 RGB 灯光
|
||||||
|
SGM_CTRL = 1; // 高电平断电,关闭背光灭屏
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0);
|
||||||
|
|
||||||
|
// 拉低 PWR_HOLD 切断电源锁存使总电源关闭
|
||||||
|
PWR_HOLD = 0;
|
||||||
|
|
||||||
|
// 进入死循环等待电源物理切断
|
||||||
|
while (1) {
|
||||||
|
PWR_HOLD = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 系统切入低功耗停机休眠模式
|
||||||
|
*/
|
||||||
|
void Enter_Low_Power_Sleep(void)
|
||||||
|
{
|
||||||
|
Uart_SendString("[SYS] Entering low power sleep...\r\n");
|
||||||
|
|
||||||
|
// 1. 关闭/熄灭 RGB 灯
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0);
|
||||||
|
|
||||||
|
// 2. 向 LCD 芯片写入 Display OFF 指令 (0x28) 关显示
|
||||||
|
WriteComm(0x28);
|
||||||
|
Delay_ms(20);
|
||||||
|
|
||||||
|
// 3. 向 LCD 芯片写入 Sleep In 指令 (0x10) 关内部电荷泵
|
||||||
|
WriteComm(0x10);
|
||||||
|
Delay_ms(20);
|
||||||
|
|
||||||
|
// 4. 关闭背光(高电平断电,关闭背光灭屏)
|
||||||
|
SGM_CTRL = 1;
|
||||||
|
|
||||||
|
// 5. 屏幕接口防漏电处理
|
||||||
|
LCD_CS = 1;
|
||||||
|
LCD_RST = 1;
|
||||||
|
LCD_DCX = 0;
|
||||||
|
LCD_SCL = 0;
|
||||||
|
LCD_SDI = 0;
|
||||||
|
|
||||||
|
// 6. 射频设置为关闭接收模式(低功耗,SHUT=0, RF_SetMode(0))
|
||||||
|
RF_SetMode(0);
|
||||||
|
|
||||||
|
// 使能访问扩展特殊功能寄存器
|
||||||
|
P_SW2 |= 0x80;
|
||||||
|
|
||||||
|
// 7. 配置 Port0 中断唤醒源 (KEY_UP=P0.1, KEY_CONFIRM=P0.2, KEY_DOWN=P0.3)
|
||||||
|
P0IM1 |= 0x0E;
|
||||||
|
P0IM0 &= ~0x0E; // 下降沿触发
|
||||||
|
P0INTF = 0x00; // 清除挂起的中断标志
|
||||||
|
P0INTE |= 0x0E; // 开启 P0.1, P0.2, P0.3 中断允许
|
||||||
|
P0WKUE |= 0x0E; // 使能 P0.1, P0.2, P0.3 掉电唤醒
|
||||||
|
|
||||||
|
// 8. 配置 Port2 中断唤醒源 (SOS=P2.6)
|
||||||
|
P2IM1 |= 0x40;
|
||||||
|
P2IM0 &= ~0x40; // 下降沿触发
|
||||||
|
P2INTF = 0x00; // 清除挂起标志
|
||||||
|
P2INTE |= 0x40; // 开启 P2.6 中断允许
|
||||||
|
P2WKUE |= 0x40; // 使能 P2.6 掉电唤醒
|
||||||
|
|
||||||
|
// 9. 配置 Port3 中断唤醒源 (RF_RX_DATA=P3.6)
|
||||||
|
P3IM1 |= 0x40;
|
||||||
|
P3IM0 |= 0x40; // 双边沿触发
|
||||||
|
P3INTF = 0x00; // 清除挂起标志
|
||||||
|
P3INTE |= 0x40; // 开启 P3.6 中断允许
|
||||||
|
P3WKUE |= 0x40; // 使能 P3.6 掉电唤醒
|
||||||
|
|
||||||
|
// 确保引脚上拉和输入使能,防止因浮空产生的功耗泄漏
|
||||||
|
P2PU |= 0x40;
|
||||||
|
P2IE |= 0x40;
|
||||||
|
P3PU &= ~0x40;
|
||||||
|
P3IE |= 0x40;
|
||||||
|
|
||||||
|
// 清零各个唤醒检测标志位
|
||||||
|
P0INTF = 0x00;
|
||||||
|
P2INTF = 0x00;
|
||||||
|
P3INTF = 0x00;
|
||||||
|
|
||||||
|
p0_wakeup_flag = 0;
|
||||||
|
p2_wakeup_flag = 0;
|
||||||
|
p3_wakeup_flag = 0;
|
||||||
|
rf_wakeup_flag = 0;
|
||||||
|
|
||||||
|
// 10. 关闭系统滴答定时器 Timer1 中断,防止休眠时被定时器误唤醒
|
||||||
|
ET1 = 0;
|
||||||
|
EA = 1; // 开启全局中断
|
||||||
|
|
||||||
|
// 配置 PCON 挂起单片机,切入停机 (Power Down) 模式
|
||||||
|
PCON |= 0x02; // PD = 1
|
||||||
|
_nop_(); _nop_(); _nop_(); _nop_();
|
||||||
|
|
||||||
|
// ====== 被外部中断唤醒后,在此处继续运行 ======
|
||||||
|
Wakeup_Restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 休眠唤醒恢复
|
||||||
|
*/
|
||||||
|
void Wakeup_Restore(void)
|
||||||
|
{
|
||||||
|
EAXFR = 1;
|
||||||
|
// 1. 立即关闭所有掉电外部中断允许与唤醒允许,防止重入
|
||||||
|
P0INTE = 0x00;
|
||||||
|
P2INTE = 0x00;
|
||||||
|
P3INTE = 0x00;
|
||||||
|
P0WKUE = 0x00;
|
||||||
|
P2WKUE = 0x00;
|
||||||
|
P3WKUE = 0x00;
|
||||||
|
P0INTF = 0x00;
|
||||||
|
P2INTF = 0x00;
|
||||||
|
P3INTF = 0x00;
|
||||||
|
|
||||||
|
// 2. 清零掉电唤醒定时器
|
||||||
|
WKTCH = 0x00;
|
||||||
|
WKTCL = 0x00;
|
||||||
|
|
||||||
|
// 3. 开启全局中断并重启系统滴答定时器
|
||||||
|
EA = 1;
|
||||||
|
ET1 = 1;
|
||||||
|
|
||||||
|
// 4. 恢复射频芯片工作模式
|
||||||
|
RF_SetMode(1);
|
||||||
|
|
||||||
|
// 打印唤醒调试日志
|
||||||
|
if ((p0_wakeup_flag & 0x0E) || (p2_wakeup_flag & 0x40)) {
|
||||||
|
Uart_SendString("[WR] Woken by KEY (P0=");
|
||||||
|
Uart_SendHex8(p0_wakeup_flag);
|
||||||
|
Uart_SendString(", P2=");
|
||||||
|
Uart_SendHex8(p2_wakeup_flag);
|
||||||
|
Uart_SendString(")\r\n");
|
||||||
|
} else if ((p3_wakeup_flag & 0x40) || rf_wakeup_flag) {
|
||||||
|
rf_wakeup_flag = 1;
|
||||||
|
Uart_SendString("[WR] Woken by RF (P3.6)!\r\n");
|
||||||
|
} else {
|
||||||
|
Uart_SendString("[WR] Woken by TIMER/Other\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 恢复屏幕供电与背光(低电平上电,恢复背光亮屏)
|
||||||
|
SGM_CTRL = 0;
|
||||||
|
|
||||||
|
// 6. 延时 200ms 等待液晶稳压板上电稳定后,重新对 LCD 寄存器初始化
|
||||||
|
Delay_ms(200);
|
||||||
|
LCD_Init();
|
||||||
|
|
||||||
|
// 7. 充零闲置计时器
|
||||||
|
inactivity_timer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 安全获取 Timer0 计数器值 (防高字节滚动跳变)
|
||||||
|
*/
|
||||||
|
u16 GetTimer0_Safe(void)
|
||||||
|
{
|
||||||
|
u8 high1, high2, low;
|
||||||
|
do {
|
||||||
|
high1 = TH0;
|
||||||
|
low = TL0;
|
||||||
|
high2 = TH0;
|
||||||
|
} while (high1 != high2);
|
||||||
|
return (((u16)high1 << 8) | low);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 串口 1 发送 4 位十六进制字符 (0x0~0xF)
|
||||||
*/
|
*/
|
||||||
void Uart_SendHex4(u8 val)
|
void Uart_SendHex4(u8 val)
|
||||||
{
|
{
|
||||||
val &= 0x0F;
|
val &= 0x0F;
|
||||||
if (val < 10)
|
if (val < 10) {
|
||||||
Uart_SendByte((u8)('0' + val));
|
Uart_SendByte('0' + val);
|
||||||
else
|
} else {
|
||||||
Uart_SendByte((u8)('A' + (val - 10)));
|
Uart_SendByte('A' + (val - 10));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 串口输出 8 位十六进制数 (单字节,0x00~0xFF)
|
* @brief 串口 1 发送 8 位十六进制 (0x00~0xFF)
|
||||||
*/
|
*/
|
||||||
void Uart_SendHex8(u8 val)
|
void Uart_SendHex8(u8 val)
|
||||||
{
|
{
|
||||||
Uart_SendHex4(val >> 4); // 输出高四位
|
Uart_SendHex4(val >> 4);
|
||||||
Uart_SendHex4(val); // 输出低四位
|
Uart_SendHex4(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 串口输出 20 位十六进制数 (射频 24 位地址的有效 20 位表达)
|
* @brief 串口 1 发送 16 位十六进制 (0x0000~0xFFFF)
|
||||||
*/
|
|
||||||
void Uart_SendHex20(u32 val)
|
|
||||||
{
|
|
||||||
Uart_SendHex4((u8)(val >> 16));
|
|
||||||
Uart_SendHex8((u8)(val >> 8));
|
|
||||||
Uart_SendHex8((u8)val);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 串口输出 32 位十六进制数
|
|
||||||
*/
|
|
||||||
void Uart_SendHex32(u32 val)
|
|
||||||
{
|
|
||||||
Uart_SendHex8((u8)(val >> 24));
|
|
||||||
Uart_SendHex8((u8)(val >> 16));
|
|
||||||
Uart_SendHex8((u8)(val >> 8));
|
|
||||||
Uart_SendHex8((u8)val);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 串口输出 16 位十六进制数
|
|
||||||
*/
|
*/
|
||||||
void Uart_SendHex16(u16 val)
|
void Uart_SendHex16(u16 val)
|
||||||
{
|
{
|
||||||
@@ -240,198 +476,19 @@ void Uart_SendHex16(u16 val)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 将数值格式化为 8 位十六进制形式的 "ADDR: 0xXXXXX" 字符串格式并清零尾部
|
* @brief 串口 1 发送 20 位十六进制 (用于射频 5 位 ID 打印)
|
||||||
* @param val 十六进制数值
|
|
||||||
* @param buf 输出接收缓冲区
|
|
||||||
*/
|
*/
|
||||||
void FormatHex(u32 val, char *buf)
|
void Uart_SendHex20(u32 val)
|
||||||
{
|
{
|
||||||
u8 i;
|
Uart_SendHex4((u8)(val >> 16));
|
||||||
// 拷贝固定前缀 "ADDR: 0x"
|
Uart_SendHex16((u16)val);
|
||||||
buf[0] = 'A'; buf[1] = 'D'; buf[2] = 'D'; buf[3] = 'R'; buf[4] = ':';
|
|
||||||
buf[5] = ' '; buf[6] = '0'; buf[7] = 'x';
|
|
||||||
// 循环提取 20 位射频地址对应的 5 个半字节字符
|
|
||||||
for (i = 0; i < 5; i++) {
|
|
||||||
u8 nibble = (val >> (4 * (4 - i))) & 0x0F;
|
|
||||||
if (nibble < 10)
|
|
||||||
buf[8 + i] = '0' + nibble;
|
|
||||||
else
|
|
||||||
buf[8 + i] = 'A' + (nibble - 10);
|
|
||||||
}
|
|
||||||
buf[13] = '\0'; // 尾部安全零字符
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 系统进入低功耗深度休眠 (Power-Down) 核心逻辑流程
|
* @brief 串口 1 发送 32 位十六进制
|
||||||
* @details 实施对屏幕、电源芯片、以及各个总线管脚的低功耗关断,配置相应的端口外部唤醒中断,
|
|
||||||
* 然后将 MCU 的置入 PCON 的 PD 模式,直至被外部硬件动作唤醒。
|
|
||||||
*/
|
*/
|
||||||
void Enter_Low_Power_Sleep(void)
|
void Uart_SendHex32(u32 val)
|
||||||
{
|
{
|
||||||
Uart_SendString("[SYS] Entering low power sleep...\r\n");
|
Uart_SendHex16((u16)(val >> 16));
|
||||||
|
Uart_SendHex16((u16)val);
|
||||||
// 1. 关闭/熄灭 FCOB 灯条
|
|
||||||
RGB_Send(0, 0, 0, 0, 0, 0);
|
|
||||||
|
|
||||||
// 2. 向 AMOLED 屏幕控制芯片 RM69310 写入 Display OFF 指令 (0x28),关闭屏幕发光
|
|
||||||
WriteComm(0x28);
|
|
||||||
Delay_ms(20);
|
|
||||||
|
|
||||||
// 3. 屏幕芯片写入 Sleep In 指令 (0x10),使其内部数字逻辑单元进入停机低损耗模式
|
|
||||||
WriteComm(0x10);
|
|
||||||
Delay_ms(20);
|
|
||||||
|
|
||||||
// 4. 将 P3.4 (SGM_CTRL) 拉低,切断 SGM3833 升压/负压电源芯片的使能,屏幕完全断电
|
|
||||||
SGM_CTRL = 0;
|
|
||||||
|
|
||||||
// 5. 【屏幕接口防漏电配置】:
|
|
||||||
// 当屏幕控制电源 (SGM3833) 切断后,屏幕内部处于无电状态。
|
|
||||||
// 如果单片机引脚 (SCL, SDA 等) 依然保持高电平,电流将通过屏幕保护二极管倒灌漏电,不仅会引发数毫安漏电,还会导致屏幕芯片损坏。
|
|
||||||
// - CS (片选) 必须拉高 (1) 以释放屏幕片选并禁用其输入缓冲器;
|
|
||||||
// - RST (复位) 必须保持高电平 (1),防止屏幕芯片被持续按在复位状态产生 ~130uA 的数字偏置电流;
|
|
||||||
// - 时钟 SCL、数据 SDI、命令/数据选择 DCX 拉低 (0) 以防浮空输入。
|
|
||||||
LCD_CS = 1;
|
|
||||||
LCD_RST = 1;
|
|
||||||
LCD_DCX = 0;
|
|
||||||
LCD_SCL = 0;
|
|
||||||
LCD_SDI = 0;
|
|
||||||
|
|
||||||
// 6. 休眠模式下关闭射频发射、接通接收 (SHUT=0, RF_RX=1),用来在休眠中监听射频警报。
|
|
||||||
// 此时屏幕和升压负压芯片已被切断,系统内部不存在高频开关纹波的噪底干扰,射频接收灵敏度达到最佳状态。
|
|
||||||
RF_SetMode(0);
|
|
||||||
|
|
||||||
// 开启扩展 SFR 访问权限 (EAXFR = 1)
|
|
||||||
P_SW2 |= 0x80;
|
|
||||||
|
|
||||||
// 7. 配置 P0.1, P0.2, P0.3 (KEY_UP/CONFIRM/DOWN) 端口下降沿低电平触发唤醒源
|
|
||||||
P0IM1 |= 0x0E;
|
|
||||||
P0IM0 &= ~0x0E;
|
|
||||||
P0INTE |= 0x0E; // 开启 P0 端口的中断允许
|
|
||||||
|
|
||||||
// 8. 配置 P2.6 (KEY_SOS) 为低电平触发;P2.1 (RF_RX_DATA) 为双沿跳变触发唤醒源
|
|
||||||
// P2.6: IM1=1, IM0=0 (低电平)
|
|
||||||
// P2.1: IM1=1, IM0=1 (双沿跳变触发,保障任何射频脉冲上升或下降沿都能立刻叫醒 MCU)
|
|
||||||
P2IM1 |= 0x42;
|
|
||||||
P2IM0 &= ~0x40;
|
|
||||||
P2IM0 |= 0x02;
|
|
||||||
P2INTE |= 0x42; // 开启 P2 端口中断允许
|
|
||||||
|
|
||||||
// 9. 【掉电唤醒核心扩展配置】:
|
|
||||||
P0WKUE |= 0x0E; // 使能 P0.1/P0.2/P0.3 的掉电唤醒 (WakeUp Enable)
|
|
||||||
P2WKUE |= 0x42; // 使能 P2.6 和 P2.1 的掉电唤醒
|
|
||||||
|
|
||||||
// 【漏电控制】:P2.1 (RF_RX_DATA) 接入的是射频芯片的数据引脚。在休眠中,
|
|
||||||
// 若开启了 P2.1 的上拉电阻,当射频芯片拉低引脚时,会产生高达 ~660uA 的强倒灌漏电流。
|
|
||||||
// 因此这里特地仅开启 P2.6 (SOS) 上拉,而彻底关闭 P2.1 上拉以实现极致微安级待机。
|
|
||||||
P2PU |= 0x40;
|
|
||||||
P2IE |= 0x42; // 强制使能 P2.6/P2.1 的内部数字输入通道,确保电平跳变能被硬件捕捉
|
|
||||||
|
|
||||||
// 10. 禁用无用的外部中断 2 (因为 STC32G 硬件 INT2 绑定在 P3.6,该物理引脚没有连接射频数据端,我们改由更灵活的 Port2 端口中断处理唤醒)
|
|
||||||
EX2 = 0;
|
|
||||||
|
|
||||||
// 11. 清除 P0 和 P2 的全部中断标志,防止进入休眠瞬间直接触发唤醒
|
|
||||||
P0INTF = 0x00;
|
|
||||||
P2INTF = 0x00;
|
|
||||||
|
|
||||||
// 清零内存暂存的唤醒源事件标志
|
|
||||||
p0_wakeup_flag = 0;
|
|
||||||
p2_wakeup_flag = 0;
|
|
||||||
rf_wakeup_flag = 0;
|
|
||||||
|
|
||||||
// 12. 关闭 Timer1 (系统滴答) 中断。全局中断保持开启 (EA=1) 用作掉电唤醒条件
|
|
||||||
ET1 = 0;
|
|
||||||
EA = 1;
|
|
||||||
|
|
||||||
// 13. 向电源控制寄存器 PCON 写入 0x02,使 MCU 核心进入 Power-Down 模式,CPU 停止运转
|
|
||||||
Uart_SendString("[SYS] Entering Power-Down mode...\r\n");
|
|
||||||
PCON |= 0x02; // 设定 PD 位,硬件立刻休眠挂起
|
|
||||||
|
|
||||||
// 唤醒后必须跟 _nop_() 空操作以占位和刷新指令流水线
|
|
||||||
_nop_();
|
|
||||||
_nop_();
|
|
||||||
_nop_();
|
|
||||||
_nop_();
|
|
||||||
|
|
||||||
// 14. 当有按键按下或无线信号到来时,CPU 被唤醒进入对应的 ISR (在 main.c 中执行中断标志保存),
|
|
||||||
// 中断处理完返回后,CPU 接着从本行执行,调用 Wakeup_Restore() 唤醒恢复流程。
|
|
||||||
Wakeup_Restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 系统被中断唤醒后的运行环境恢复逻辑
|
|
||||||
* @details 负责重新开启屏幕供电、配置 GPIO、重新初始化 LCD 芯片寄存器、重置时间,最终回到 Clock 主界面。
|
|
||||||
*/
|
|
||||||
void Wakeup_Restore(void)
|
|
||||||
{
|
|
||||||
EAXFR = 1; // 确保允许访问扩展寄存区
|
|
||||||
|
|
||||||
// 1. 立即关断所有的唤醒源和端口中断,防止应用运行期间反复因电平抖动误触发中断服务程序
|
|
||||||
P0INTE = 0x00;
|
|
||||||
P2INTE = 0x00;
|
|
||||||
P0WKUE = 0x00; // 清除掉电唤醒源使能
|
|
||||||
P2WKUE = 0x00;
|
|
||||||
P0INTF = 0x00; // 清空标志位
|
|
||||||
P2INTF = 0x00;
|
|
||||||
EX2 = 0; // 禁用外部 INT2 中断
|
|
||||||
|
|
||||||
// 2. 清零掉电专用定时器 (防止定时唤醒)
|
|
||||||
WKTCH = 0x00;
|
|
||||||
WKTCL = 0x00;
|
|
||||||
|
|
||||||
// 3. 恢复 Timer1 定时器中断允许,重新开启系统基准时基轮询
|
|
||||||
EA = 1;
|
|
||||||
ET1 = 1;
|
|
||||||
|
|
||||||
// 4. 重置射频工作模式:进入模式 1 (接收模式) 并开启天线,恢复监听外部射频
|
|
||||||
RF_SetMode(1);
|
|
||||||
|
|
||||||
// 打印日志,判断并告知调试串口具体是哪个物理源将手环唤醒的
|
|
||||||
if ((p0_wakeup_flag & 0x0E) || (p2_wakeup_flag & 0x40))
|
|
||||||
{
|
|
||||||
Uart_SendString("[WR] Woken by KEY (P0=");
|
|
||||||
Uart_SendHex8(p0_wakeup_flag);
|
|
||||||
Uart_SendString(", P2=");
|
|
||||||
Uart_SendHex8(p2_wakeup_flag);
|
|
||||||
Uart_SendString(")\r\n");
|
|
||||||
}
|
|
||||||
else if ((p2_wakeup_flag & 0x02) || rf_wakeup_flag)
|
|
||||||
{
|
|
||||||
rf_wakeup_flag = 1; // 确认标记为射频解调唤醒
|
|
||||||
Uart_SendString("[WR] Woken by RF (P2.1)!\r\n");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Uart_SendString("[WR] Woken by TIMER/Other\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. 【屏幕供电软启动】:
|
|
||||||
// 重新拉高 P3.4 (SGM_CTRL),并为负压电源芯片发送 27 个脉冲将输出稳压开启。
|
|
||||||
SGM_CTRL = 1;
|
|
||||||
SGM_SendPulse(27);
|
|
||||||
|
|
||||||
// 6. 阻塞延时 200ms。
|
|
||||||
// 这期间用于避开电源电磁浪涌冲击,等待屏幕正负电压电轨彻底稳定,防止屏幕在初始化时画面出现撕裂或逻辑卡死。
|
|
||||||
Delay_ms(200);
|
|
||||||
|
|
||||||
// 7. 重新调用 LCD_Init() 初始化序列,写入控制偏置、伽马等命令,唤醒 Truly OLED 屏幕控制 IC RM69310
|
|
||||||
LCD_Init();
|
|
||||||
|
|
||||||
// 8. 重置用户的无操作闲置计时器为零
|
|
||||||
inactivity_timer = 0;
|
|
||||||
|
|
||||||
// 9. 更新运行状态为普通时钟,并强制调用时钟界面函数刷屏,返回工作状态
|
|
||||||
current_state = STATE_NORMAL;
|
|
||||||
UI_ShowClockPage(current_state, current_hour, current_min);
|
|
||||||
Uart_SendString("[SYS] Wakeup restored!\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 掉电休眠用外部中断 2 中断服务子程序
|
|
||||||
* @details 当配置为 INT2 (P3.6) 跳变触发唤醒时,本函数将捕获唤醒标志,随后迅速关闭中断,
|
|
||||||
* 防止运行时产生的噪底高频跳变引发中断堆栈溢出。
|
|
||||||
*/
|
|
||||||
void INT2_Isr(void) interrupt 10
|
|
||||||
{
|
|
||||||
rf_wakeup_flag = 1; // 中断唤醒标志置位,标明由无线射频触发唤醒
|
|
||||||
EX2 = 0; // 保护机制:进入 ISR 后立即切断中断通道
|
|
||||||
}
|
}
|
||||||
|
|||||||
12
App/system.h
12
App/system.h
@@ -117,4 +117,16 @@ void Enter_Low_Power_Sleep(void);
|
|||||||
*/
|
*/
|
||||||
void Wakeup_Restore(void);
|
void Wakeup_Restore(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 系统开机上电硬件自检函数
|
||||||
|
* @details 检测 PWR_HOLD 锁存状态、ADC 采样基准与串口通讯,输出完整自检 report
|
||||||
|
*/
|
||||||
|
void Self_Test(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 执行关机断电流程
|
||||||
|
* @details 关断外设、震动提示并拉低 PWR_HOLD 引脚切断总电源
|
||||||
|
*/
|
||||||
|
void Power_Off(void);
|
||||||
|
|
||||||
#endif // __SYSTEM_H__
|
#endif // __SYSTEM_H__
|
||||||
|
|||||||
176
App/ui.c
176
App/ui.c
@@ -1,5 +1,6 @@
|
|||||||
#include "ui.h"
|
#include "ui.h"
|
||||||
#include "../Drivers/lcd.h"
|
#include "../Drivers/lcd.h"
|
||||||
|
#include "../Drivers/adc.h"
|
||||||
|
|
||||||
// 引入外部主菜单对码项名称定义 (例如 "DETEC. PORTE" 等,法语表达防区类型名称)
|
// 引入外部主菜单对码项名称定义 (例如 "DETEC. PORTE" 等,法语表达防区类型名称)
|
||||||
extern char *code menu_items[5];
|
extern char *code menu_items[5];
|
||||||
@@ -10,23 +11,26 @@ extern char *code menu_items[5];
|
|||||||
* @param hour 当前待显示的小时数 (0~23)
|
* @param hour 当前待显示的小时数 (0~23)
|
||||||
* @param min 当前待显示的分钟数 (0~59)
|
* @param min 当前待显示的分钟数 (0~59)
|
||||||
* @details 该页面被划分为三大功能视觉区:
|
* @details 该页面被划分为三大功能视觉区:
|
||||||
* 1. 顶部状态栏:左侧绘制布撤防指示锁,右侧显示 "85%" 剩余电量以及精细电池单色位图。
|
* 1. 顶部状态栏:左侧绘制布撤防指示锁,右侧显示真实剩余电量百分比以及电池单色位图。
|
||||||
* 2. 中部核心栏:垂直居中以 16x32 大号粗字体绘制格式化时间 "HH:MM",在其下方标注 AM/PM 标识。
|
* 2. 中部核心栏:垂直居中以 16x32 大号粗字体绘制格式化时间 "HH:MM",在其下方标注 AM/PM 标识。
|
||||||
* 3. 底部信息栏:使用灰色小字模绘制法语星期日期 "VEN 10-07" (星期五 7月10日)。
|
* 3. 底部信息栏:使用灰色小字模绘制法语星期日期 "VEN 10-07" (星期五 7月10日)。
|
||||||
*/
|
*/
|
||||||
void UI_ShowClockPage(SystemState state, u8 hour, u8 min)
|
void UI_ShowClockPage(SystemState state, u8 hour, u8 min)
|
||||||
{
|
{
|
||||||
char time_str[6];
|
char time_str[6];
|
||||||
LCD_Clear(COLOR_BLACK); // 使用纯黑色底色,充分利用 AMOLED 黑色像素不发光的省电特性
|
char batt_str[7];
|
||||||
|
u8 batt;
|
||||||
|
|
||||||
// ====== 绘制左上角布撤防锁头状态图标 ======
|
// 清屏,底色填充为纯黑
|
||||||
|
LCD_Clear(COLOR_BLACK);
|
||||||
|
|
||||||
|
// ====== 绘制左上角布撤防图标 ======
|
||||||
if (state == STATE_ARMED)
|
if (state == STATE_ARMED)
|
||||||
{
|
{
|
||||||
// 状态: ARMED (布防) -> 绘制红色闭合锁头
|
// 状态: ARMED (已布防) -> 绘制红色闭合锁头
|
||||||
LCD_DrawRectBorder(10, 23, 20, 15, COLOR_RED); // 锁身矩形框
|
LCD_DrawRectBorder(10, 23, 20, 15, COLOR_RED); // 锁身矩形
|
||||||
LCD_FillRect(15, 15, 2, 8, COLOR_RED); // 左半锁环
|
LCD_DrawRectBorder(14, 15, 12, 9, COLOR_RED); // 闭合状态的拱形锁环
|
||||||
LCD_FillRect(17, 15, 10, 2, COLOR_RED); // 锁环顶梁
|
LCD_FillRect(18, 28, 4, 5, COLOR_RED); // 锁芯暗扣
|
||||||
LCD_FillRect(25, 15, 2, 8, COLOR_RED); // 右半锁环
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -37,9 +41,24 @@ void UI_ShowClockPage(SystemState state, u8 hour, u8 min)
|
|||||||
LCD_FillRect(25, 17, 2, 6, COLOR_GREEN); // 开启状态的偏置锁环
|
LCD_FillRect(25, 17, 2, 6, COLOR_GREEN); // 开启状态的偏置锁环
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====== 绘制右上角电池电量 ======
|
// ====== 绘制右上角真实电池电量 ======
|
||||||
LCD_ShowString(62, 24, "85%", COLOR_GRAY, COLOR_BLACK); // 静态测试显示 85% 字符
|
batt = ADC_GetBatteryPercent();
|
||||||
LCD_DrawMonoBitmap(92, 27, 20, 10, bmp_battery_20x10, COLOR_GRAY, COLOR_BLACK); // 渲染 20x10 电池外廓和格数
|
if (batt >= 100) {
|
||||||
|
batt_str[0] = '1'; batt_str[1] = '0'; batt_str[2] = '0'; batt_str[3] = '%'; batt_str[4] = '\0';
|
||||||
|
} else {
|
||||||
|
batt_str[0] = '0' + (batt / 10);
|
||||||
|
batt_str[1] = '0' + (batt % 10);
|
||||||
|
batt_str[2] = '%';
|
||||||
|
batt_str[3] = '\0';
|
||||||
|
}
|
||||||
|
// 充电中则在百分比后追加 '+' 后缀
|
||||||
|
if (Is_Charging()) {
|
||||||
|
u8 len = (batt >= 100) ? 4 : 3;
|
||||||
|
batt_str[len] = '+';
|
||||||
|
batt_str[len + 1] = '\0';
|
||||||
|
}
|
||||||
|
LCD_ShowString(70, 24, batt_str, COLOR_GRAY, COLOR_BLACK); // 渲染真实动态百分比
|
||||||
|
LCD_DrawMonoBitmap(107, 27, 20, 10, bmp_battery_20x10, COLOR_GRAY, COLOR_BLACK); // 渲染 20x10 电池图标
|
||||||
|
|
||||||
// ====== 绘制中部时间 "HH:MM" ======
|
// ====== 绘制中部时间 "HH:MM" ======
|
||||||
time_str[0] = '0' + (hour / 10); // 小时十位
|
time_str[0] = '0' + (hour / 10); // 小时十位
|
||||||
@@ -77,7 +96,7 @@ void UI_ShowPairMenuPage(u8 selected_index)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 渲染当前选中的高亮中间项
|
// 渲染当前选中的高亮中间项
|
||||||
LCD_DrawRectBorder(8, 110, 104, 28, COLOR_CYAN); // 青色对齐外框
|
LCD_DrawRectBorder(15, 110, 104, 28, COLOR_CYAN); // 青色对齐外框 (在 135px 宽度下居中)
|
||||||
LCD_ShowStringCentered(118, menu_items[selected_index], COLOR_CYAN, COLOR_BLACK);
|
LCD_ShowStringCentered(118, menu_items[selected_index], COLOR_CYAN, COLOR_BLACK);
|
||||||
|
|
||||||
// 渲染下方相邻项 (如果当前选择的不是最后一行)
|
// 渲染下方相邻项 (如果当前选择的不是最后一行)
|
||||||
@@ -98,13 +117,13 @@ void UI_ShowPairWaitPage(u8 frame_index)
|
|||||||
// 绘制头部 "APPAIRAGE" (对码中)
|
// 绘制头部 "APPAIRAGE" (对码中)
|
||||||
LCD_ShowStringCentered(35, "APPAIRAGE", COLOR_GRAY, COLOR_BLACK);
|
LCD_ShowStringCentered(35, "APPAIRAGE", COLOR_GRAY, COLOR_BLACK);
|
||||||
|
|
||||||
// 根据 Timer1 节拍算得的帧号,切换渲染雷达的 3 种点阵图形,形成扩散动态视觉
|
// 根据 Timer1 节拍算得的帧号,切换渲染雷达的 3 种点阵图形,形成扩散动态视觉 (在 135px 下居中 X = 51)
|
||||||
if (frame_index == 0)
|
if (frame_index == 0)
|
||||||
LCD_DrawMonoBitmap(44, 75, 32, 32, bmp_radar_32x32_f1, COLOR_CYAN, COLOR_BLACK);
|
LCD_DrawMonoBitmap(51, 75, 32, 32, bmp_radar_32x32_f1, COLOR_CYAN, COLOR_BLACK);
|
||||||
else if (frame_index == 1)
|
else if (frame_index == 1)
|
||||||
LCD_DrawMonoBitmap(44, 75, 32, 32, bmp_radar_32x32_f2, COLOR_CYAN, COLOR_BLACK);
|
LCD_DrawMonoBitmap(51, 75, 32, 32, bmp_radar_32x32_f2, COLOR_CYAN, COLOR_BLACK);
|
||||||
else
|
else
|
||||||
LCD_DrawMonoBitmap(44, 75, 32, 32, bmp_radar_32x32_f3, COLOR_CYAN, COLOR_BLACK);
|
LCD_DrawMonoBitmap(51, 75, 32, 32, bmp_radar_32x32_f3, COLOR_CYAN, COLOR_BLACK);
|
||||||
|
|
||||||
// 绘制状态描述
|
// 绘制状态描述
|
||||||
LCD_ShowStringCentered(165, "RECHERCHE", COLOR_WHITE, COLOR_BLACK); // 法语: 搜寻中
|
LCD_ShowStringCentered(165, "RECHERCHE", COLOR_WHITE, COLOR_BLACK); // 法语: 搜寻中
|
||||||
@@ -124,24 +143,24 @@ void UI_ShowPairConfirmPage(u8 type, u8 zone_index)
|
|||||||
// 顶部显示捕获回馈标志 "v RECU" (法语: 信号已接收)
|
// 顶部显示捕获回馈标志 "v RECU" (法语: 信号已接收)
|
||||||
LCD_ShowStringCentered(32, "v RECU", COLOR_GREEN, COLOR_BLACK);
|
LCD_ShowStringCentered(32, "v RECU", COLOR_GREEN, COLOR_BLACK);
|
||||||
|
|
||||||
// 在上部居中绘制设备类别所对应的图形
|
// 在上部居中绘制设备类别所对应的图形 (135px 下居中 X = 51)
|
||||||
if (type == 0) {
|
if (type == 0) {
|
||||||
LCD_DrawMonoBitmap(44, 55, 32, 32, bmp_door_32x32, COLOR_WHITE, COLOR_BLACK); // 门磁图标
|
LCD_DrawMonoBitmap(51, 55, 32, 32, bmp_door_32x32, COLOR_WHITE, COLOR_BLACK); // 门磁图标
|
||||||
} else if (type == 2 || type == 3) {
|
} else if (type == 2 || type == 3) {
|
||||||
LCD_DrawMonoBitmap(44, 55, 32, 32, bmp_siren_32x32, COLOR_WHITE, COLOR_BLACK); // 警号喇叭图标
|
LCD_DrawMonoBitmap(51, 55, 32, 32, bmp_siren_32x32, COLOR_WHITE, COLOR_BLACK); // 警号喇叭图标
|
||||||
} else {
|
} else {
|
||||||
LCD_DrawMonoBitmap(44, 55, 32, 32, bmp_confirm_lock_32x32, COLOR_WHITE, COLOR_BLACK); // 锁型常规图标
|
LCD_DrawMonoBitmap(51, 55, 32, 32, bmp_confirm_lock_32x32, COLOR_WHITE, COLOR_BLACK); // 锁型常规图标
|
||||||
}
|
}
|
||||||
|
|
||||||
// 渲染传感器类型的文字
|
// 渲染传感器类型的文字
|
||||||
LCD_ShowStringCentered(105, sensor_names_fr[type], COLOR_WHITE, COLOR_BLACK);
|
LCD_ShowStringCentered(105, sensor_names_fr[type], COLOR_WHITE, COLOR_BLACK);
|
||||||
|
|
||||||
// 绘制一个青色边框,在中间以 16x32 大号字体展示当前防区号 (如 "01", "12")
|
// 绘制一个青色边框,在中间以 16x32 大号字体展示当前防区号 (在 135px 下居中 X=22, 宽度 90)
|
||||||
LCD_DrawRectBorder(15, 140, 90, 44, COLOR_CYAN);
|
LCD_DrawRectBorder(22, 140, 90, 44, COLOR_CYAN);
|
||||||
zone_str[0] = '0' + (zone_index / 10);
|
zone_str[0] = '0' + (zone_index / 10);
|
||||||
zone_str[1] = '0' + (zone_index % 10);
|
zone_str[1] = '0' + (zone_index % 10);
|
||||||
zone_str[2] = '\0';
|
zone_str[2] = '\0';
|
||||||
LCD_ShowString16x32(44, 146, zone_str, COLOR_CYAN, COLOR_BLACK); // 渲染两位防区编号
|
LCD_ShowString16x32(51, 146, zone_str, COLOR_CYAN, COLOR_BLACK); // 渲染两位防区编号 (135px 下居中 X = 51)
|
||||||
|
|
||||||
// 底部提示按键动作
|
// 底部提示按键动作
|
||||||
LCD_ShowStringCentered(215, "CONFIRMER", COLOR_GRAY, COLOR_BLACK); // 法语: 短按以确认保存
|
LCD_ShowStringCentered(215, "CONFIRMER", COLOR_GRAY, COLOR_BLACK); // 法语: 短按以确认保存
|
||||||
@@ -153,8 +172,8 @@ void UI_ShowPairConfirmPage(u8 type, u8 zone_index)
|
|||||||
void UI_ShowPairSuccessPage(void)
|
void UI_ShowPairSuccessPage(void)
|
||||||
{
|
{
|
||||||
LCD_Clear(COLOR_BLACK);
|
LCD_Clear(COLOR_BLACK);
|
||||||
// 渲染 32x32 对勾图标
|
// 渲染 32x32 对勾图标 (135px 下居中 X = 51)
|
||||||
LCD_DrawMonoBitmap(44, 75, 32, 32, bmp_checkmark_32x32, COLOR_GREEN, COLOR_BLACK);
|
LCD_DrawMonoBitmap(51, 75, 32, 32, bmp_checkmark_32x32, COLOR_GREEN, COLOR_BLACK);
|
||||||
LCD_ShowStringCentered(145, "SUCCES", COLOR_GREEN, COLOR_BLACK); // 法语: 成功
|
LCD_ShowStringCentered(145, "SUCCES", COLOR_GREEN, COLOR_BLACK); // 法语: 成功
|
||||||
LCD_ShowStringCentered(180, "ENREGISTRE", COLOR_GRAY, COLOR_BLACK); // 法语: 配置已保存
|
LCD_ShowStringCentered(180, "ENREGISTRE", COLOR_GRAY, COLOR_BLACK); // 法语: 配置已保存
|
||||||
}
|
}
|
||||||
@@ -166,8 +185,8 @@ void UI_ShowPairSuccessPage(void)
|
|||||||
void UI_ShowPairFailPage(u8 is_timeout)
|
void UI_ShowPairFailPage(u8 is_timeout)
|
||||||
{
|
{
|
||||||
LCD_Clear(COLOR_BLACK);
|
LCD_Clear(COLOR_BLACK);
|
||||||
// 渲染 32x32 红色叉号图标
|
// 渲染 32x32 红色叉号图标 (135px 下居中 X = 51)
|
||||||
LCD_DrawMonoBitmap(44, 75, 32, 32, bmp_cross_32x32, COLOR_RED, COLOR_BLACK);
|
LCD_DrawMonoBitmap(51, 75, 32, 32, bmp_cross_32x32, COLOR_RED, COLOR_BLACK);
|
||||||
LCD_ShowStringCentered(145, "ECHEC", COLOR_RED, COLOR_BLACK); // 法语: 失败
|
LCD_ShowStringCentered(145, "ECHEC", COLOR_RED, COLOR_BLACK); // 法语: 失败
|
||||||
if (is_timeout) {
|
if (is_timeout) {
|
||||||
LCD_ShowStringCentered(180, "DELAI DEPASSE", COLOR_GRAY, COLOR_BLACK); // 法语: 对码超时
|
LCD_ShowStringCentered(180, "DELAI DEPASSE", COLOR_GRAY, COLOR_BLACK); // 法语: 对码超时
|
||||||
@@ -189,17 +208,17 @@ void UI_ShowAlarmPage(u8 type, u8 zone_index)
|
|||||||
char zone_info[20];
|
char zone_info[20];
|
||||||
LCD_Clear(COLOR_BLACK);
|
LCD_Clear(COLOR_BLACK);
|
||||||
|
|
||||||
// 绘制双重红色爆闪粗边界框 (配合 ms_tick 在 AlarmApp 中亮灭闪烁)
|
// 绘制双重红色爆闪粗边界框 (在新宽 135px 下对称扩展)
|
||||||
LCD_DrawRectBorder(4, 4, 112, 232, COLOR_RED);
|
LCD_DrawRectBorder(4, 4, 127, 232, COLOR_RED);
|
||||||
LCD_DrawRectBorder(6, 6, 108, 228, COLOR_RED);
|
LCD_DrawRectBorder(6, 6, 123, 228, COLOR_RED);
|
||||||
|
|
||||||
// 绘制报警设备的图形
|
// 绘制报警设备的图形 (135px 下居中 X = 51)
|
||||||
if (type == 0) {
|
if (type == 0) {
|
||||||
LCD_DrawMonoBitmap(44, 50, 32, 32, bmp_door_32x32, COLOR_RED, COLOR_BLACK); // 门磁报警
|
LCD_DrawMonoBitmap(51, 50, 32, 32, bmp_door_32x32, COLOR_RED, COLOR_BLACK); // 门磁报警
|
||||||
} else if (type == 2 || type == 3) {
|
} else if (type == 2 || type == 3) {
|
||||||
LCD_DrawMonoBitmap(44, 50, 32, 32, bmp_siren_32x32, COLOR_RED, COLOR_BLACK); // 烟感/燃气报警
|
LCD_DrawMonoBitmap(51, 50, 32, 32, bmp_siren_32x32, COLOR_RED, COLOR_BLACK); // 烟感/燃气报警
|
||||||
} else {
|
} else {
|
||||||
LCD_DrawMonoBitmap(44, 50, 32, 32, bmp_confirm_lock_32x32, COLOR_RED, COLOR_BLACK); // 其他防区报警
|
LCD_DrawMonoBitmap(51, 50, 32, 32, bmp_confirm_lock_32x32, COLOR_RED, COLOR_BLACK); // 其他防区报警
|
||||||
}
|
}
|
||||||
|
|
||||||
// 拼接字符串: "设备类型名 两位防区号"
|
// 拼接字符串: "设备类型名 两位防区号"
|
||||||
@@ -227,19 +246,19 @@ void UI_ShowSosPage(void)
|
|||||||
{
|
{
|
||||||
LCD_Clear(COLOR_BLACK);
|
LCD_Clear(COLOR_BLACK);
|
||||||
|
|
||||||
// 绘制红白双层紧急警示框
|
// 绘制红白双层紧急警示框 (在新宽 135px 下对称扩展)
|
||||||
LCD_DrawRectBorder(4, 4, 112, 232, COLOR_RED);
|
LCD_DrawRectBorder(4, 4, 127, 232, COLOR_RED);
|
||||||
LCD_DrawRectBorder(5, 5, 110, 230, COLOR_RED);
|
LCD_DrawRectBorder(5, 5, 125, 230, COLOR_RED);
|
||||||
LCD_DrawRectBorder(8, 8, 104, 224, COLOR_WHITE);
|
LCD_DrawRectBorder(8, 8, 119, 224, COLOR_WHITE);
|
||||||
|
|
||||||
// ====== 用画点和填充矩形函数像素级绘制精细的警徽灯架 ======
|
// ====== 用画点和填充矩形函数像素级绘制精细的警徽灯架 (在新宽 135px 下整体右移 7 像素) ======
|
||||||
LCD_FillRect(44, 45, 32, 2, COLOR_RED); // 警灯顶部
|
LCD_FillRect(51, 45, 32, 2, COLOR_RED); // 警灯顶部
|
||||||
LCD_FillRect(48, 47, 24, 12, COLOR_RED); // 警灯红色灯罩
|
LCD_FillRect(55, 47, 24, 12, COLOR_RED); // 警灯红色灯罩
|
||||||
LCD_FillRect(40, 59, 40, 4, COLOR_GRAY); // 警灯灰色底座
|
LCD_FillRect(47, 59, 40, 4, COLOR_GRAY); // 警灯灰色底座
|
||||||
LCD_FillRect(57, 51, 6, 6, COLOR_WHITE); // 中间高亮光影
|
LCD_FillRect(64, 51, 6, 6, COLOR_WHITE); // 中间高亮光影
|
||||||
LCD_FillRect(58, 36, 4, 6, COLOR_RED); // 发射的垂直红色闪光
|
LCD_FillRect(65, 36, 4, 6, COLOR_RED); // 发射的垂直红色闪光
|
||||||
LCD_FillRect(46, 38, 4, 4, COLOR_RED); // 左侧散射闪光
|
LCD_FillRect(53, 38, 4, 4, COLOR_RED); // 左侧散射闪光
|
||||||
LCD_FillRect(70, 38, 4, 4, COLOR_RED); // 右侧散射闪光
|
LCD_FillRect(77, 38, 4, 4, COLOR_RED); // 右侧散射闪光
|
||||||
|
|
||||||
// 绘制求救中法文字样
|
// 绘制求救中法文字样
|
||||||
LCD_ShowStringCentered(120, "SOS", COLOR_RED, COLOR_BLACK);
|
LCD_ShowStringCentered(120, "SOS", COLOR_RED, COLOR_BLACK);
|
||||||
@@ -257,11 +276,11 @@ void UI_ShowBindingPage(u32 addr)
|
|||||||
LCD_Clear(COLOR_BLACK);
|
LCD_Clear(COLOR_BLACK);
|
||||||
|
|
||||||
// ====== 像素级拼装绘制一个青色网络摄像头简图 ======
|
// ====== 像素级拼装绘制一个青色网络摄像头简图 ======
|
||||||
LCD_DrawRectBorder(36, 46, 32, 26, COLOR_CYAN); // 相机主体框
|
LCD_DrawRectBorder(43, 46, 32, 26, COLOR_CYAN); // 相机主体框 (135px 下居中等比右移 X = 43)
|
||||||
LCD_FillRect(44, 56, 6, 6, COLOR_CYAN); // 镜头光圈点
|
LCD_FillRect(51, 56, 6, 6, COLOR_CYAN); // 镜头光圈点 (135px 下居中等比右移 X = 51)
|
||||||
LCD_FillRect(68, 48, 2, 22, COLOR_CYAN); // 右侧红外指示圈
|
LCD_FillRect(75, 48, 2, 22, COLOR_CYAN); // 右侧红外指示圈 (135px 下居中等比右移 X = 75)
|
||||||
LCD_FillRect(70, 50, 2, 18, COLOR_CYAN); // 镜头固定翼 1
|
LCD_FillRect(77, 50, 2, 18, COLOR_CYAN); // 镜头固定翼 1 (135px 下居中等比右移 X = 77)
|
||||||
LCD_FillRect(72, 52, 2, 14, COLOR_CYAN); // 镜头固定翼 2
|
LCD_FillRect(79, 52, 2, 14, COLOR_CYAN); // 镜头固定翼 2 (135px 下居中等比右移 X = 79)
|
||||||
|
|
||||||
LCD_ShowStringCentered(120, "LIAISON", COLOR_WHITE, COLOR_BLACK); // 法语: 连接/绑定
|
LCD_ShowStringCentered(120, "LIAISON", COLOR_WHITE, COLOR_BLACK); // 法语: 连接/绑定
|
||||||
LCD_ShowStringCentered(155, "ENVOI EN COURS", COLOR_CYAN, COLOR_BLACK); // 法语: 发送对码帧中
|
LCD_ShowStringCentered(155, "ENVOI EN COURS", COLOR_CYAN, COLOR_BLACK); // 法语: 发送对码帧中
|
||||||
@@ -294,46 +313,46 @@ void UI_ShowBindingPage(u32 addr)
|
|||||||
* @brief 渲染显示设置主菜单界面
|
* @brief 渲染显示设置主菜单界面
|
||||||
* @param selected_index 当前菜单高亮行索引 (0:时钟修改, 1:对码匹配, 2:摄像机绑定)
|
* @param selected_index 当前菜单高亮行索引 (0:时钟修改, 1:对码匹配, 2:摄像机绑定)
|
||||||
* @details 在顶部和底部绘制向上/向下箭头提示滑轮,中部绘制选中项的大几何图形,
|
* @details 在顶部和底部绘制向上/向下箭头提示滑轮,中部绘制选中项的大几何图形,
|
||||||
* 并在下方打印对应的法文字符串描述。
|
* 并在下方打印对应的法文字串描述。
|
||||||
*/
|
*/
|
||||||
void UI_ShowMainMenu(u8 selected_index)
|
void UI_ShowMainMenu(u8 selected_index)
|
||||||
{
|
{
|
||||||
LCD_Clear(COLOR_BLACK);
|
LCD_Clear(COLOR_BLACK);
|
||||||
|
|
||||||
// ====== 绘制顶部的向上箭头三角标记 ======
|
// ====== 绘制顶部的向上箭头三角标记 (在 135px 宽度下居中) ======
|
||||||
LCD_FillRect(58, 20, 4, 2, COLOR_GRAY);
|
LCD_FillRect(65, 20, 4, 2, COLOR_GRAY);
|
||||||
LCD_FillRect(56, 22, 8, 2, COLOR_GRAY);
|
LCD_FillRect(63, 22, 8, 2, COLOR_GRAY);
|
||||||
LCD_FillRect(54, 24, 12, 2, COLOR_GRAY);
|
LCD_FillRect(61, 24, 12, 2, COLOR_GRAY);
|
||||||
|
|
||||||
if (selected_index == 0)
|
if (selected_index == 0)
|
||||||
{
|
{
|
||||||
// 菜单 0:时间修改设置。中部绘制一个青色闹钟样式
|
// 菜单 0:时间修改设置。中部绘制一个青色闹钟样式 (135px 下居中 X = 51)
|
||||||
LCD_DrawRectBorder(44, 70, 32, 32, COLOR_CYAN); // 闹钟外圈
|
LCD_DrawRectBorder(51, 70, 32, 32, COLOR_CYAN); // 闹钟外圈
|
||||||
LCD_FillRect(59, 76, 2, 10, COLOR_CYAN); // 闹钟时针
|
LCD_FillRect(66, 76, 2, 10, COLOR_CYAN); // 闹钟时针
|
||||||
LCD_FillRect(59, 85, 8, 2, COLOR_CYAN); // 闹钟分针
|
LCD_FillRect(66, 85, 8, 2, COLOR_CYAN); // 闹钟分针
|
||||||
LCD_ShowStringCentered(150, "1. HORLOGE", COLOR_WHITE, COLOR_BLACK); // 法语: 时钟/时间修改
|
LCD_ShowStringCentered(150, "1. HORLOGE", COLOR_WHITE, COLOR_BLACK); // 法语: 时钟/时间修改
|
||||||
}
|
}
|
||||||
else if (selected_index == 1)
|
else if (selected_index == 1)
|
||||||
{
|
{
|
||||||
// 菜单 1:无线对码匹配。中部绘制一个青色同心圆雷达波纹图形
|
// 菜单 1:无线对码匹配。中部绘制一个青色同心圆雷达波纹图形 (135px 下居中 X = 51)
|
||||||
LCD_DrawMonoBitmap(44, 70, 32, 32, bmp_radar_32x32_f3, COLOR_CYAN, COLOR_BLACK);
|
LCD_DrawMonoBitmap(51, 70, 32, 32, bmp_radar_32x32_f3, COLOR_CYAN, COLOR_BLACK);
|
||||||
LCD_ShowStringCentered(150, "2. APPAIRAGE", COLOR_WHITE, COLOR_BLACK); // 法语: 配对
|
LCD_ShowStringCentered(150, "2. APPAIRAGE", COLOR_WHITE, COLOR_BLACK); // 法语: 配对
|
||||||
}
|
}
|
||||||
else if (selected_index == 2)
|
else if (selected_index == 2)
|
||||||
{
|
{
|
||||||
// 菜单 2:网络摄像机绑定。中部绘制一个相机/联通关系图
|
// 菜单 2:网络摄像机绑定。中部绘制一个相机/联通关系图 (135px 下居中等比右移 X = 57)
|
||||||
LCD_DrawRectBorder(44, 74, 20, 16, COLOR_CYAN);
|
LCD_DrawRectBorder(57, 74, 20, 16, COLOR_CYAN);
|
||||||
LCD_FillRect(52, 80, 4, 4, COLOR_CYAN);
|
LCD_FillRect(65, 80, 4, 4, COLOR_CYAN);
|
||||||
LCD_FillRect(64, 76, 2, 12, COLOR_CYAN);
|
LCD_FillRect(77, 76, 2, 12, COLOR_CYAN);
|
||||||
LCD_FillRect(66, 78, 2, 8, COLOR_CYAN);
|
LCD_FillRect(79, 78, 2, 8, COLOR_CYAN);
|
||||||
LCD_FillRect(68, 80, 2, 4, COLOR_CYAN);
|
LCD_FillRect(81, 80, 2, 4, COLOR_CYAN);
|
||||||
LCD_ShowStringCentered(150, "3. LIAISON", COLOR_WHITE, COLOR_BLACK); // 法语: 摄像机绑定
|
LCD_ShowStringCentered(150, "3. LIAISON", COLOR_WHITE, COLOR_BLACK); // 法语: 摄像机绑定
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====== 绘制底部的向下箭头三角标记 ======
|
// ====== 绘制底部的向下箭头三角标记 (在 135px 宽度下居中) ======
|
||||||
LCD_FillRect(54, 210, 12, 2, COLOR_GRAY);
|
LCD_FillRect(61, 210, 12, 2, COLOR_GRAY);
|
||||||
LCD_FillRect(56, 212, 8, 2, COLOR_GRAY);
|
LCD_FillRect(63, 212, 8, 2, COLOR_GRAY);
|
||||||
LCD_FillRect(58, 214, 4, 2, COLOR_GRAY);
|
LCD_FillRect(65, 214, 4, 2, COLOR_GRAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -354,8 +373,9 @@ void UI_ShowSetTimePage(u8 hour, u8 min, u8 selection, u8 blink_on)
|
|||||||
LCD_FillRect(17, 15, 8, 2, COLOR_GREEN);
|
LCD_FillRect(17, 15, 8, 2, COLOR_GREEN);
|
||||||
LCD_FillRect(25, 17, 2, 6, COLOR_GREEN);
|
LCD_FillRect(25, 17, 2, 6, COLOR_GREEN);
|
||||||
|
|
||||||
LCD_ShowString(62, 24, "85%", COLOR_GRAY, COLOR_BLACK);
|
// 移动电池百分比和图标到 135px 右上角
|
||||||
LCD_DrawMonoBitmap(92, 27, 20, 10, bmp_battery_20x10, COLOR_GRAY, COLOR_BLACK);
|
LCD_ShowString(77, 24, "85%", COLOR_GRAY, COLOR_BLACK);
|
||||||
|
LCD_DrawMonoBitmap(107, 27, 20, 10, bmp_battery_20x10, COLOR_GRAY, COLOR_BLACK);
|
||||||
|
|
||||||
// ====== 处理调节项一:小时的消隐/显示逻辑 ======
|
// ====== 处理调节项一:小时的消隐/显示逻辑 ======
|
||||||
if (selection == 1 && !blink_on)
|
if (selection == 1 && !blink_on)
|
||||||
@@ -370,10 +390,10 @@ void UI_ShowSetTimePage(u8 hour, u8 min, u8 selection, u8 blink_on)
|
|||||||
time_str[1] = '0' + (hour % 10);
|
time_str[1] = '0' + (hour % 10);
|
||||||
}
|
}
|
||||||
time_str[2] = '\0';
|
time_str[2] = '\0';
|
||||||
LCD_ShowString16x32(12, 100, time_str, COLOR_CYAN, COLOR_BLACK); // 采用青色特大字描绘小时
|
LCD_ShowString16x32(23, 100, time_str, COLOR_CYAN, COLOR_BLACK); // 采用青色特大字描绘小时 (在 135px 下整体居中 X = 23)
|
||||||
|
|
||||||
// 绘制中间冒号
|
// 绘制中间冒号 (在 135px 下整体居中 X = 59)
|
||||||
LCD_ShowString16x32(48, 98, ":", COLOR_WHITE, COLOR_BLACK);
|
LCD_ShowString16x32(59, 98, ":", COLOR_WHITE, COLOR_BLACK);
|
||||||
|
|
||||||
// ====== 处理调节项二:分钟的消隐/显示逻辑 ======
|
// ====== 处理调节项二:分钟的消隐/显示逻辑 ======
|
||||||
if (selection == 2 && !blink_on)
|
if (selection == 2 && !blink_on)
|
||||||
@@ -388,7 +408,7 @@ void UI_ShowSetTimePage(u8 hour, u8 min, u8 selection, u8 blink_on)
|
|||||||
time_str[1] = '0' + (min % 10);
|
time_str[1] = '0' + (min % 10);
|
||||||
}
|
}
|
||||||
time_str[2] = '\0';
|
time_str[2] = '\0';
|
||||||
LCD_ShowString16x32(64, 100, time_str, COLOR_CYAN, COLOR_BLACK); // 采用青色特大字描绘分钟
|
LCD_ShowString16x32(75, 100, time_str, COLOR_CYAN, COLOR_BLACK); // 采用青色特大字描绘分钟 (在 135px 下整体居中 X = 75)
|
||||||
|
|
||||||
// 补齐底部的固定辅助元素
|
// 补齐底部的固定辅助元素
|
||||||
LCD_ShowStringCentered(150, "AM", COLOR_WHITE, COLOR_BLACK);
|
LCD_ShowStringCentered(150, "AM", COLOR_WHITE, COLOR_BLACK);
|
||||||
|
|||||||
24
App/xtell.h
Normal file
24
App/xtell.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#ifndef __XTELL_H__
|
||||||
|
#define __XTELL_H__
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
// 全局调试日志开关
|
||||||
|
#define XTELL_DEBUG_ENABLE
|
||||||
|
|
||||||
|
// 声明底层串口发送函数 (由 system.c 提供)
|
||||||
|
extern void Uart_SendString(char *p);
|
||||||
|
extern void Uart_SendHex8(u8 val);
|
||||||
|
extern void Uart_SendHex32(u32 val);
|
||||||
|
|
||||||
|
#ifdef XTELL_DEBUG_ENABLE
|
||||||
|
#define XTELL_LOG(s) Uart_SendString(s)
|
||||||
|
#define XTELL_LOG_HEX8(v) Uart_SendHex8(v)
|
||||||
|
#define XTELL_LOG_HEX32(v) Uart_SendHex32(v)
|
||||||
|
#else
|
||||||
|
#define XTELL_LOG(s)
|
||||||
|
#define XTELL_LOG_HEX8(v)
|
||||||
|
#define XTELL_LOG_HEX32(v)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // __XTELL_H__
|
||||||
7
CLAUDE.md
Normal file
7
CLAUDE.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
## Git Workflow Requirements
|
||||||
|
- You must make a separate Git commit immediately after completing every individual, logical task (e.g., a bug fix, feature addition, or test case addition).
|
||||||
|
- Never accumulate multiple independent features or fixes into a single chunk of work.
|
||||||
|
- Automatically stage the files and generate a commit message matching Conventional Commits standards (e.g., `feat(auth): add login validation`).
|
||||||
|
- Do not ask for user confirmation before running `git add` and `git commit` locally.
|
||||||
|
- each `git commit` message must not more 100 words
|
||||||
|
- always increase APP_VERSION after each change.
|
||||||
@@ -10,26 +10,20 @@
|
|||||||
* **硬件原理图**:[text.kicad_sch](file:///c:/workfile/105/stc32g12k128/Hardware/text.kicad_sch)
|
* **硬件原理图**:[text.kicad_sch](file:///c:/workfile/105/stc32g12k128/Hardware/text.kicad_sch)
|
||||||
* **PCB 布局图**:[text.kicad_pcb](file:///c:/workfile/105/stc32g12k128/Hardware/text.kicad_pcb)
|
* **PCB 布局图**:[text.kicad_pcb](file:///c:/workfile/105/stc32g12k128/Hardware/text.kicad_pcb)
|
||||||
|
|
||||||
主控芯片采用 **STC32G12K128**(工作频率 24.0 MHz,单周期 8051 内核,32位乘除法器)。
|
主控芯片采用| **Pin 1** | `P1.0` | `DIS_RST` | 推挽输出 | LHS114TC-IF03 屏幕复位控制脚 (RESET) |
|
||||||
显示端采用 **Truly 0.95寸 AMOLED 屏幕**,物理分辨率为 **120x240 像素**,控制芯片为 **RM69310**。
|
| **Pin 2** | `P1.1` | `SPI_DCX` | 推挽输出 | LHS114TC-IF03 屏幕 SPI 数据/指令选择脚 (D/C) |
|
||||||
|
| **Pin 7** | `P1.3` | `BAT_ADC` | ADC 输入 | 电池电压采集 (通道 3,配置为高阻输入) |
|
||||||
### 1.1 GPIO 物理映射表
|
| **Pin 3** | `P1.4` | `SPI_SDI` | 推挽输出 | LHS114TC-IF03 屏幕 SPI 串行数据输入 (MOSI/SDA) |
|
||||||
|
| **Pin 4** | `P1.5` | `SPI_CLK` | 推挽输出 | LHS114TC-IF03 屏幕 SPI 串行时钟信号 (SCL) |
|
||||||
| 芯片引脚号 | 引脚名称 | 逻辑网络标号 | IO 模式 | 连接外设与功能描述 |
|
| **Pin 5** | `P1.6` | `SPI_CS` | 推挽输出 | LHS114TC-IF03 屏幕 SPI 片选信号 (CS) |
|
||||||
| :--- | :--- | :--- | :--- | :--- |
|
| **Pin 6** | `P1.7` | `LEDA` | 推挽输出 | LHS114TC-IF03 屏幕白色 LED 背光使能控制端 (0点亮,1熄灭 - PMOS控制) |
|
||||||
| **Pin 1** | `P1.0` | `DIS_RST` | 推挽输出 | Truly AMOLED 屏幕复位控制脚 (RESET) |
|
|
||||||
| **Pin 2** | `P1.1` | `SPI_DCX` | 推挽输出 | Truly AMOLED 屏幕 SPI 数据/指令选择脚 (D/C) |
|
|
||||||
| **Pin 3** | `P1.3` | `SPI_SDI` | 推挽输出 | Truly AMOLED 屏幕 SPI 串行数据输入 (MOSI/SDA) |
|
|
||||||
| **Pin 4** | `P1.4` | `SPI_CLK` | 推挽输出 | Truly AMOLED 屏幕 SPI 串行时钟信号 (SCL) |
|
|
||||||
| **Pin 5** | `P1.5` | `SPI_CS` | 推挽输出 | Truly AMOLED 屏幕 SPI 片选信号 (CS) |
|
|
||||||
| **Pin 6** | `P1.6` | `DET` | 高阻输入 | USB 插入充电检测引脚,高电平代表正在充电 |
|
|
||||||
| **Pin 7** | `P1.7` | `LEDA` | 推挽输出 | 屏幕负压供电芯片使能控制端 (SGM_CTRL) |
|
|
||||||
| **Pin 8** | `UCAP` | `UCAP` | 电源 | 外接滤波电容至 GND (LDO 稳定输出端) |
|
| **Pin 8** | `UCAP` | `UCAP` | 电源 | 外接滤波电容至 GND (LDO 稳定输出端) |
|
||||||
| **Pin 10** | `Vcc` | `+3.3V` | 电源 | 屏幕/系统 3.3V 逻辑供电 |
|
| **Pin 10** | `Vcc` | `+3.3V` | 电源 | 屏幕/系统 3.3V 逻辑供电 |
|
||||||
| **Pin 11** | `VREF+` | `+3.3V` | 电源 | 屏幕/系统 3.3V 逻辑供电参考基准 |
|
| **Pin 11** | `VREF+` | `+3.3V` | 电源 | 屏幕/系统 3.3V 逻辑供电参考基准 |
|
||||||
| **Pin 12** | `GND` | `GND` | 电源 | 系统地 (GND) |
|
| **Pin 12** | `GND` | `GND` | 电源 | 系统地 (GND) |
|
||||||
| **Pin 13** | `P3.0` | `RXD` | 高阻输入 | 串口 1 (UART1) 接收脚 |
|
| **Pin 13** | `P3.0` | `RXD` | 高阻输入 | 串口 1 (UART1) 接收脚 |
|
||||||
| **Pin 14** | `P3.1` | `TXD` | 推挽输出 | 串口 1 (UART1) 发送脚 |
|
| **Pin 14** | `P3.1` | `TXD` | 推挽输出 | 串口 1 (UART1) 发送脚 |
|
||||||
|
| **Pin 15** | `P5.4` | `DET` | 高阻输入 | USB 插入充电检测引脚,高电平代表正在充电 |
|
||||||
| **Pin 18** | `P3.5` | `SHUT` | 推挽输出 | LR690L 射频芯片关闭信号 (0:开启工作, 1:关闭休眠) |
|
| **Pin 18** | `P3.5` | `SHUT` | 推挽输出 | LR690L 射频芯片关闭信号 (0:开启工作, 1:关闭休眠) |
|
||||||
| **Pin 19** | `P3.6` | `RF_RX_DATA` | 高阻输入 | LR690L 接收解调接收数据输出引脚 (注:代码中被映射为 P2^1) |
|
| **Pin 19** | `P3.6` | `RF_RX_DATA` | 高阻输入 | LR690L 接收解调接收数据输出引脚 (注:代码中被映射为 P2^1) |
|
||||||
| **Pin 20** | `P3.7` | `RF_RX` | 推挽输出 | ATR5179 接收使能控制引脚 |
|
| **Pin 20** | `P3.7` | `RF_RX` | 推挽输出 | ATR5179 接收使能控制引脚 |
|
||||||
@@ -40,10 +34,22 @@
|
|||||||
| **Pin 26** | `P2.5` | `MOTOR_PWM` | 推挽输出 | 连接振动马达驱动电路。高电平震动,低电平静止 |
|
| **Pin 26** | `P2.5` | `MOTOR_PWM` | 推挽输出 | 连接振动马达驱动电路。高电平震动,低电平静止 |
|
||||||
| **Pin 27** | `P2.6` | `KEY_SOS` | 高阻输入 + 上拉 | 上部并联 SOS 求救按键,按下为低电平 |
|
| **Pin 27** | `P2.6` | `KEY_SOS` | 高阻输入 + 上拉 | 上部并联 SOS 求救按键,按下为低电平 |
|
||||||
| **Pin 28** | `P2.7` | `NC` | - | 悬空 |
|
| **Pin 28** | `P2.7` | `NC` | - | 悬空 |
|
||||||
|
| **Pin 29** | `P0.0` | `NC` | - | 悬空 |
|
||||||
|
| **Pin 30** | `P0.1` | `KEY_UP` | 高阻输入 + 上拉 | 侧边按键 ▲ (上移),按下为低电平 |
|
||||||
|
| **Pin 31** | `P0.2` | `KEY_CONFIRM` | 高阻输入 + 上拉 | 侧边按键 ■ (确认/菜单),按下为低电平 |
|
||||||
|
| **Pin 32** | `P0.3` | `KEY_DOWN` | 高阻输入 + 上拉 | 侧边按键 ▼ (下移),按下为低电平 |<7C> P2^1) |
|
||||||
|
| **Pin 20** | `P3.7` | `RF_RX` | 推挽输出 | ATR5179 接收使能控制引脚 |
|
||||||
|
| **Pin 21** | `P2.0` | `RF_TX` | 推挽输出 | ATR5179 发射使能控制引脚 |
|
||||||
|
| **Pin 22** | `P2.1` | `RF_TX_DAT` | 推挽输出 | LR690L 射频芯片发射调制数据输入引脚 (注:代码中被映射为 P3^6) |
|
||||||
|
| **Pin 23** | `P2.2` | `NC` | - | 悬空 |
|
||||||
|
| **Pin 24** | `P2.3` | `LED_RGB` | SPI MOSI | WS2812B 级联双幻彩灯珠 DIN 控制信号 |
|
||||||
|
| **Pin 26** | `P2.5` | `MOTOR_PWM` | 推挽输出 | 连接振动马达驱动电路。高电平震动,低电平静止 |
|
||||||
|
| **Pin 27** | `P2.6` | `KEY_SOS` | 高阻输入 + 上拉 | 上部并联 SOS 求救按键,按下为低电平 |
|
||||||
|
| **Pin 28** | `P2.7` | `NC` | - | 悬空 |
|
||||||
| **Pin 29** | `P0.0` | `BAT_ADC` | ADC 输入 | 电池电压采集 |
|
| **Pin 29** | `P0.0` | `BAT_ADC` | ADC 输入 | 电池电压采集 |
|
||||||
| **Pin 30** | `P0.1` | `KEY_UP` | 高阻输入 + 上拉 | 侧边按键 ▲ (上移),按下为低电平 |
|
| **Pin 30** | `P0.1` | `KEY_UP` | 高阻输入 + 上拉 | 侧边按键 ▲ (上移),按下为低电平 |
|
||||||
| **Pin 31** | `P0.2` | `KEY_CONFIRM` | 高阻输入 + 上拉 | 侧边按键 ■ (确认/菜单),按下为低电平 |
|
| **Pin 31** | `P0.2` | `KEY_CONFIRM` | 高阻输入 + 上拉 | 侧边按键 ■ (确认/菜单),按下为低电平 |
|
||||||
| **Pin 32** | `P0.3` | `KEY_DOWN` | 高阻输入 + 上拉 | 侧边按键 ▼ (下移),按下为低电平 |
|
| **Pin 32** | `P0.3` | `KEY_DOWN`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
171
Drivers/adc.c
Normal file
171
Drivers/adc.c
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
#include "adc.h"
|
||||||
|
#include "../App/system.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化 STC32G 硬件 ADC 模块
|
||||||
|
* @details 配置 P0.3 (KEY_DOWN) 与 P1.3 (BATT_ADC) 为高阻输入模式,
|
||||||
|
* 上电开启 ADC 内部电源并配置结果对齐格式为右对齐。
|
||||||
|
*/
|
||||||
|
void ADC_Init(void)
|
||||||
|
{
|
||||||
|
// 1. 配置 P0.3 和 P1.3 引脚模式为高阻输入 (M1=1, M0=0)
|
||||||
|
P0M1 |= (1 << 3);
|
||||||
|
P0M0 &= ~(1 << 3);
|
||||||
|
|
||||||
|
P1M1 |= (1 << 3);
|
||||||
|
P1M0 &= ~(1 << 3);
|
||||||
|
|
||||||
|
// 配置 P1.2 (CHRG_DET / ADC2) 为高阻输入模式
|
||||||
|
P1M1 |= (1 << 2);
|
||||||
|
P1M0 &= ~(1 << 2);
|
||||||
|
|
||||||
|
// 2. 开启 ADC 模块主电源 (ADC_POWER = 1)
|
||||||
|
ADC_POWER = 1;
|
||||||
|
|
||||||
|
// 3. 配置 ADCCFG 寄存器:
|
||||||
|
// RESFMT (bit 5) = 1: 转换结果右对齐 (ADC_RES 存高4位, ADC_RESL 存低8位)
|
||||||
|
// 底部 4 位 SPEED: 设置 ADC 转换工作时钟频率 (0x0F 表示最快速度)
|
||||||
|
ADCCFG = 0x2F;
|
||||||
|
|
||||||
|
// 4. 软启动延迟,等待 ADC 模块内部电源稳定
|
||||||
|
Delay_ms(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 单次读取指定 ADC 通道的原始 12 位转换结果
|
||||||
|
* @param channel ADC 通道号 (0~15)
|
||||||
|
* @return u16 12 位 ADC 采样原始值 (0 ~ 4095)
|
||||||
|
*/
|
||||||
|
u16 ADC_ReadRaw(u8 channel)
|
||||||
|
{
|
||||||
|
u16 adc_val = 0;
|
||||||
|
|
||||||
|
// 选择指定通道并保持电源开启
|
||||||
|
ADC_CONTR = (ADC_CONTR & 0xF0) | (channel & 0x0F);
|
||||||
|
|
||||||
|
// 延时等待通道切换电容充放电稳定
|
||||||
|
Delay_us(10);
|
||||||
|
|
||||||
|
// 启动 A/D 转换
|
||||||
|
ADC_START = 1;
|
||||||
|
|
||||||
|
// 阻塞等待转换结束 (ADC_FLAG 置 1)
|
||||||
|
while (!ADC_FLAG);
|
||||||
|
|
||||||
|
// 清除转换完成标志
|
||||||
|
ADC_FLAG = 0;
|
||||||
|
|
||||||
|
// 读取 12 位右对齐结果 (ADC_RES 取低 4 位,结合 ADC_RESL 构成 12 位数据)
|
||||||
|
adc_val = ((u16)(ADC_RES & 0x0F) << 8) | ADC_RESL;
|
||||||
|
|
||||||
|
return adc_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 5 次采样排序取中值滤波算法
|
||||||
|
* @param channel ADC 通道号 (0~15)
|
||||||
|
* @return u16 滤波后的 12 位 ADC 采样结果
|
||||||
|
*/
|
||||||
|
u16 ADC_ReadFiltered(u8 channel)
|
||||||
|
{
|
||||||
|
u16 buf[5];
|
||||||
|
u8 i, j;
|
||||||
|
u16 temp;
|
||||||
|
|
||||||
|
// 连续采集 5 次数据
|
||||||
|
for (i = 0; i < 5; i++) {
|
||||||
|
buf[i] = ADC_ReadRaw(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 冒泡排序法进行升序排列
|
||||||
|
for (i = 0; i < 4; i++) {
|
||||||
|
for (j = 0; j < 4 - i; j++) {
|
||||||
|
if (buf[j] > buf[j + 1]) {
|
||||||
|
temp = buf[j];
|
||||||
|
buf[j] = buf[j + 1];
|
||||||
|
buf[j + 1] = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取中间第 3 个值 (索引 2) 作为有效滤波输出,有效去除偶然性噪声尖峰
|
||||||
|
return buf[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检测 USB 充电是否插入
|
||||||
|
* @return bit 0=未充电, 1=充电中
|
||||||
|
* @note TP4054 CHRG 开漏输出:
|
||||||
|
* - 未充电/充满:CHRG 为高阻(被内部上拉拉高)
|
||||||
|
* - 充电中:CHRG 为低电平
|
||||||
|
* 返回值 1 表示正在充电,0 表示未充电或充满。
|
||||||
|
*/
|
||||||
|
bit Is_Charging(void)
|
||||||
|
{
|
||||||
|
u32 val = ADC_ReadFiltered(ADC_CHANNEL_CHRG_DET);
|
||||||
|
u32 det_mv = (val * 3300UL) / 4095UL;
|
||||||
|
|
||||||
|
// 0.8V ~ 1.8V (800mV ~ 1800mV):正在充电
|
||||||
|
if (det_mv >= 800 && det_mv <= 1800) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检测 KEY_DOWN (P0.3) 按键电平是否按下
|
||||||
|
* @details KEY_DOWN 节点未按下时电压约 2.0V,按下后升至约 3.7V。
|
||||||
|
* @return bit 1: 按下, 0: 常态未按下
|
||||||
|
*/
|
||||||
|
bit ADC_IsKeyDown(void)
|
||||||
|
{
|
||||||
|
u16 val = ADC_ReadFiltered(ADC_CHANNEL_KEY_DOWN);
|
||||||
|
// 若采样值超过阈值 KEY_DOWN_ADC_THRESHOLD (3300),判定为按键被按下
|
||||||
|
if (val > KEY_DOWN_ADC_THRESHOLD) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 P1.3 电池分压计算得出的实际电池电压值 (单位: mV)
|
||||||
|
* @details 硬件电阻分压电路: R15 (10kΩ) 上拉, R16 (22kΩ) 下拉。
|
||||||
|
* 分压公式: V_node = V_bat * (22 / 32) => V_bat = V_node * (32 / 22)
|
||||||
|
* 根据 ADC 采样: V_node(mV) = adc_val * 3300 / 4095
|
||||||
|
* 真实电池电压 V_bat(mV) = (adc_val * 3300 * 32) / (4095 * 22)
|
||||||
|
* @return u16 电池总电压 (mV, 例如 3850 代表 3.85V)
|
||||||
|
*/
|
||||||
|
u16 ADC_GetBatteryVoltage_mV(void)
|
||||||
|
{
|
||||||
|
u32 adc_val = ADC_ReadFiltered(ADC_CHANNEL_BATTERY);
|
||||||
|
u32 vbat_mv;
|
||||||
|
|
||||||
|
// 经用户实测校准:
|
||||||
|
// 当电池真实电压为 4.0V (4000mV) 时,10k/22k 分压点万用表实测为 2.7V,单片机读取的过滤 ADC 原值约为 2357。
|
||||||
|
// 换算公式:V_bat(mV) = adc_val * 4000 / 2357
|
||||||
|
vbat_mv = (adc_val * 4000UL) / 2357UL;
|
||||||
|
|
||||||
|
return (u16)vbat_mv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 计算电池剩余电量百分比 (0% ~ 100%)
|
||||||
|
* @details 锂电池标准电压区间定义:
|
||||||
|
* 4.20V (4200mV) 对应 100%
|
||||||
|
* 3.30V (3300mV) 对应 0%
|
||||||
|
* @return u8 电量百分比 (0 ~ 100)
|
||||||
|
*/
|
||||||
|
u8 ADC_GetBatteryPercent(void)
|
||||||
|
{
|
||||||
|
u16 vbat = ADC_GetBatteryVoltage_mV();
|
||||||
|
|
||||||
|
if (vbat >= 4200) {
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
if (vbat <= 3300) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 线性百分比换算: percent = (vbat - 3300) * 100 / (4200 - 3300)
|
||||||
|
return (u8)(((u32)(vbat - 3300) * 100UL) / 900UL);
|
||||||
|
}
|
||||||
61
Drivers/adc.h
Normal file
61
Drivers/adc.h
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#ifndef __ADC_H__
|
||||||
|
#define __ADC_H__
|
||||||
|
|
||||||
|
#include "../App/config.h"
|
||||||
|
|
||||||
|
/* ====== ADC 通道与硬件引脚定义 ====== */
|
||||||
|
#define ADC_CHANNEL_KEY_DOWN 11 // KEY_DOWN (P0.3) 对应 STC32G 的 ADC 通道 11
|
||||||
|
#define ADC_CHANNEL_BATTERY 3 // 电池检测 (P1.3) 对应 STC32G 的 ADC 通道 3
|
||||||
|
#define ADC_CHANNEL_CHRG_DET 2 // 充电检测 (P1.2) 对应 STC32G 的 ADC 通道 2
|
||||||
|
|
||||||
|
/* ====== 判定阈值定义 (基于 12 位 ADC, 0~4095) ====== */
|
||||||
|
// KEY_DOWN 2.2V 常态 (ADC~2700), 3.7V 按下 (ADC 饱和 4095)
|
||||||
|
#define KEY_DOWN_ADC_THRESHOLD 3400 // ADC 采样值超过 3400 判定为按键按下
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化 STC32G 硬件 ADC 模块
|
||||||
|
* @details 开启 ADC 电源、设置结果右对齐格式及采样时钟分频,并将 P0.3 和 P1.3 设置为高阻输入模式
|
||||||
|
*/
|
||||||
|
void ADC_Init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 读取指定 ADC 通道的原始 12 位转换结果
|
||||||
|
* @param channel ADC 通道号 (0~15)
|
||||||
|
* @return u16 12 位 ADC 采样原始值 (0 ~ 4095)
|
||||||
|
*/
|
||||||
|
u16 ADC_ReadRaw(u8 channel);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 对指定 ADC 通道进行多重采样与中值滤波
|
||||||
|
* @param channel ADC 通道号 (0~15)
|
||||||
|
* @return u16 滤波后的 12 位 ADC 采样值
|
||||||
|
*/
|
||||||
|
u16 ADC_ReadFiltered(u8 channel);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 判断 KEY_DOWN (P0.3) 当前是否处于按下状态 (ADC 阈值判定)
|
||||||
|
* @return bit 1: 按下 (电压 ~3.7V), 0: 松开/常态 (电压 ~2.0V)
|
||||||
|
*/
|
||||||
|
bit ADC_IsKeyDown(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 P1.3 电池分压测得的实际电压 (毫伏 mV)
|
||||||
|
* @return u16 电池电压,单位 mV (例如 3850 代表 3.85V)
|
||||||
|
*/
|
||||||
|
u16 ADC_GetBatteryVoltage_mV(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取电池电量百分比 (0% ~ 100%)
|
||||||
|
* @return u8 电量百分比 (0 ~ 100)
|
||||||
|
*/
|
||||||
|
u8 ADC_GetBatteryPercent(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检测 USB 充电是否插入
|
||||||
|
* @return bit 0=未充电, 1=充电中
|
||||||
|
* @note TP4054 CHRG 引脚开漏输出,低电平时表示正在充电。
|
||||||
|
* 需要 MCU 内部上拉或外部上拉电阻。
|
||||||
|
*/
|
||||||
|
bit Is_Charging(void);
|
||||||
|
|
||||||
|
#endif // __ADC_H__
|
||||||
358
Drivers/lcd.c
358
Drivers/lcd.c
@@ -3,51 +3,23 @@
|
|||||||
#include "../App/config.h"
|
#include "../App/config.h"
|
||||||
#include "intrins.h"
|
#include "intrins.h"
|
||||||
|
|
||||||
// ====== RM69310 Truly AMOLED 屏幕 SPI 引脚映射 (8051 bit 寻址控制) ======
|
// ====== LHS114TC-IF03 (ST7789V) 屏幕 SPI 引脚映射 ======
|
||||||
sbit LCD_RST = P1^0; // 屏幕硬件复位引脚 (RESET),低电平复位
|
sbit LCD_RST = P1^0; // 屏幕硬件复位引脚 (RESET),低电平复位
|
||||||
sbit LCD_DCX = P1^1; // 串行数据 / 命令选择控制线 (1:数据, 0:指令)
|
sbit LCD_DCX = P1^1; // 串行数据 / 命令选择控制线 (1:数据, 0:指令)
|
||||||
sbit LCD_SDI = P1^4; // SPI 主机输出从机输入数据线 (MOSI)
|
sbit LCD_SDI = P1^4; // SPI 主机输出从机输入数据线 (MOSI)
|
||||||
sbit LCD_SCL = P1^5; // SPI 串行工作时钟线 (SCLK)
|
sbit LCD_SCL = P1^5; // SPI 串行工作时钟线 (SCLK)
|
||||||
sbit LCD_CS = P1^6; // SPI 物理片选选择端 (CS),低电平选通屏幕
|
sbit LCD_CS = P1^6; // SPI 物理片选选择端 (CS),低电平选通屏幕
|
||||||
|
|
||||||
// ====== SGM3833 升压/负压三路 OLED 屏电源 PMIC 使能引脚 ======
|
// ====== LHS114TC-IF03 LEDA 背光控制引脚 ======
|
||||||
sbit SGM_CTRL = P3^4; // 单线脉冲通信控制线
|
sbit SGM_CTRL = P1^7; // LEDA 开启引脚 (0:开启背光, 1:关闭熄灭 - PMOS控制)
|
||||||
|
|
||||||
// 引用外部延时函数 (定义在 system.c 中)
|
// 引用外部延时函数 (定义在 system.c 中)
|
||||||
extern void Delay_ms(u16 ms);
|
extern void Delay_ms(u16 ms);
|
||||||
extern void Delay10us(void);
|
extern void Delay10us(void);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 通过脉冲序列为 SGM3833 屏幕电源管理芯片写入开启配置
|
|
||||||
* @param pulse_cnt 要写入的脉冲个数 (本系统传入 27 次脉冲开启正负双电轨)
|
|
||||||
* @details SGM3833 采用单线边沿脉冲计数协议进行数字控制。
|
|
||||||
* - tOFF 必须大于 5ms 才能完全复位芯片进入指令模式。
|
|
||||||
* - 每次电平拉低低电平持续 10us 后拉高,形成一个脉冲计数值。
|
|
||||||
* - 发送完毕后需拉高并维持 5ms 以上,以便将计数值存入寄存器,锁定目标电压轨开启。
|
|
||||||
*/
|
|
||||||
void SGM_SendPulse(u8 pulse_cnt)
|
|
||||||
{
|
|
||||||
u8 i;
|
|
||||||
SGM_CTRL = 0;
|
|
||||||
Delay_ms(5); // 确保电源芯片进入关断/复位状态 (tOFF)
|
|
||||||
SGM_CTRL = 1;
|
|
||||||
Delay_ms(5); // 确保进入初始化状态 (tINIT >= 300us)
|
|
||||||
for(i = 0; i < pulse_cnt; i++)
|
|
||||||
{
|
|
||||||
SGM_CTRL = 0;
|
|
||||||
Delay10us(); // 保持低电平 10us
|
|
||||||
SGM_CTRL = 1;
|
|
||||||
Delay10us(); // 保持高电平 10us
|
|
||||||
}
|
|
||||||
SGM_CTRL = 1;
|
|
||||||
Delay_ms(5); // 确保数据稳定存储锁定 (tSTORE >= 80us)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 软件模拟 8 位串行 SPI 字节数据发送
|
* @brief 软件模拟 8 位串行 SPI 字节数据发送
|
||||||
* @param dat 待发送的 1 字节数据
|
* @param dat 待发送的 1 字节数据
|
||||||
* @details 采用典型的 bit-banging 模式:时钟 SCL 在发送前置低,将数据 MSB 依次提取后赋值给
|
|
||||||
* 数据线 SDI,随后拉高 SCL 完成一个 bit 的沿采样,循环 8 次发送完成。
|
|
||||||
*/
|
*/
|
||||||
void SPI_SendData(u8 dat)
|
void SPI_SendData(u8 dat)
|
||||||
{
|
{
|
||||||
@@ -89,7 +61,7 @@ void WriteData(u8 dat)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 按照 RM69310 物理时序执行屏幕硬件复位
|
* @brief 按照物理时序执行屏幕硬件复位
|
||||||
*/
|
*/
|
||||||
void LCD_Reset(void)
|
void LCD_Reset(void)
|
||||||
{
|
{
|
||||||
@@ -102,22 +74,19 @@ void LCD_Reset(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 初始化 Truly AMOLED 屏幕控制器 RM69310
|
* @brief 初始化 LHS114TC-IF03 (ST7789V) 屏幕控制器
|
||||||
* @details 先配置控制 GPIO 为推挽输出,接着根据 Truly 信利原厂提供的初始化指令序列,
|
|
||||||
* 完成偏置电压、刷新周期、Gamma 参数、跳帧逻辑及色彩空间模式等一系列初始化工作。
|
|
||||||
*/
|
*/
|
||||||
void LCD_Init(void)
|
void LCD_Init(void)
|
||||||
{
|
{
|
||||||
// 1. 配置 P1 端口的 LCD_RST/DCX/SDI/SCL/CS 引脚全部为推挽输出模式 (M1=0, M0=1)
|
// 开启扩展 SFR 访问权限 (EAXFR = 1),确保可以操作 P1M1 和 P1M0 模式寄存器
|
||||||
P1M1 &= ~((1<<0) | (1<<1) | (1<<4) | (1<<5) | (1<<6));
|
P_SW2 |= 0x80;
|
||||||
P1M0 |= ((1<<0) | (1<<1) | (1<<4) | (1<<5) | (1<<6));
|
|
||||||
|
|
||||||
// 2. 配置 P3.4 (SGM_CTRL) 为推挽输出模式
|
// 配置 P1 端口的 LCD_RST(P1.0)/DCX(P1.1)/SDI(P1.4)/SCL(P1.5)/CS(P1.6)/LEDA(P1.7) 为推挽输出模式 (M1=0, M0=1)
|
||||||
P3M1 &= ~(1 << 4);
|
P1M1 &= ~((1<<0) | (1<<1) | (1<<4) | (1<<5) | (1<<6) | (1<<7));
|
||||||
P3M0 |= (1 << 4);
|
P1M0 |= ((1<<0) | (1<<1) | (1<<4) | (1<<5) | (1<<6) | (1<<7));
|
||||||
|
|
||||||
// 设置各控制引脚初始默认电平
|
// 设置各控制引脚初始默认电平
|
||||||
SGM_CTRL = 1;
|
SGM_CTRL = 0; // PMOS低电平点亮背光 (LEDA = 0)
|
||||||
LCD_CS = 1;
|
LCD_CS = 1;
|
||||||
LCD_SCL = 1;
|
LCD_SCL = 1;
|
||||||
LCD_RST = 1;
|
LCD_RST = 1;
|
||||||
@@ -125,183 +94,102 @@ void LCD_Init(void)
|
|||||||
|
|
||||||
LCD_Reset(); // 硬件复位屏幕
|
LCD_Reset(); // 硬件复位屏幕
|
||||||
|
|
||||||
// ====== 写入 RM69310 原厂寄存器初始化指令序列 ======
|
// ====== 写入 ST7789V 原厂寄存器初始化指令序列 ======
|
||||||
WriteComm(0xFE);WriteData(0x01); // 切换至扩展 Page 1 寄存器页
|
WriteComm(0x11); // Sleep Out 退出睡眠
|
||||||
WriteComm(0x04);WriteData(0xa0); // 开启 SPI 写入显存 RAM 允许
|
Delay_ms(120); // 退出睡眠后必须延时至少 120ms
|
||||||
WriteComm(0x05);WriteData(0x70); // 配置屏幕尺寸规格为 128RGB
|
|
||||||
WriteComm(0x06);WriteData(0x3C); // 设定垂直有效扫描线 NL = 240
|
// Memory Data Access Control (MADCTL) 扫描方向控制
|
||||||
WriteComm(0x25);WriteData(0x06); // 正常模式伽马设定Gamma1
|
// 0x00: 从上至下,从左至右,RGB 格式
|
||||||
WriteComm(0x26);WriteData(0x80); // T1A 时序常数 = 280
|
WriteComm(0x36);
|
||||||
WriteComm(0x27);WriteData(0x12); // 正常模式消隐后肩 VBP = 12
|
WriteData(0x00);
|
||||||
WriteComm(0x28);WriteData(0x12); // 正常模式消隐前肩 VFP = 8
|
|
||||||
WriteComm(0x2A);WriteData(0x06); // 空闲模式伽马设定 Gamma1
|
// Interface Pixel Format (COLMOD)
|
||||||
WriteComm(0x2B);WriteData(0x80); // T1B 时序常数 = 280
|
// 0x05 代表 16-bit/pixel (RGB565格式)
|
||||||
WriteComm(0x2D);WriteData(0x12); // 空闲消隐后肩 = 12
|
WriteComm(0x3A);
|
||||||
WriteComm(0x2F);WriteData(0x12); // 空闲消隐前肩 = 8
|
WriteData(0x05);
|
||||||
WriteComm(0x37);WriteData(0x0C); // 预充电控制电压 VGSP
|
|
||||||
WriteComm(0x6D);WriteData(0x18); // 关闭跳帧 VGMP 和 VGSP 稳压调节
|
// Porch Setting (B2h)
|
||||||
WriteComm(0x29);WriteData(0x01); // 关闭正常模式的跳帧
|
WriteComm(0xB2);
|
||||||
WriteComm(0x30);WriteData(0x43); // 设定空闲模式跳帧使刷新频率降为 30Hz
|
WriteData(0x0C);
|
||||||
WriteComm(0x0E);WriteData(0x83); // 正常模式 AVDD 电压 = 5.6V
|
WriteData(0x0C);
|
||||||
WriteComm(0x0F);WriteData(0x83); // 空闲模式 AVDD = 5.6V
|
WriteData(0x00);
|
||||||
WriteComm(0x10);WriteData(0x71); // AVDD 三倍 VCI 升压控制
|
WriteData(0x33);
|
||||||
WriteComm(0x11);WriteData(0xb3); // VCL = -2倍 VCI 产生负压
|
WriteData(0x33);
|
||||||
WriteComm(0x12);WriteData(0xb3);
|
|
||||||
WriteComm(0x13);WriteData(0x80); // 正常模式下 VGH 等于 AVDD
|
// Gate Control (B7h)
|
||||||
WriteComm(0x14);WriteData(0x80); // 空闲模式下 VGH 等于 AVDD
|
// VGH=13.65V, VGL=-10.43V
|
||||||
WriteComm(0x15);WriteData(0x81); // 正常模式下低侧门电压 VGL = VCL - VCI
|
WriteComm(0xB7);
|
||||||
WriteComm(0x16);WriteData(0x81);
|
WriteData(0x35);
|
||||||
WriteComm(0x18);WriteData(0x66); // 偏置电平 VGHR = 6V
|
|
||||||
WriteComm(0x19);WriteData(0x44); // 偏置电平 VGLR = -6V
|
// VCOM Setting (BBh)
|
||||||
WriteComm(0x1E);WriteData(0x02); // 开启电荷平衡 EQ 调制
|
// VCOM=1.175V
|
||||||
WriteComm(0x5B);WriteData(0x10); // 启用参考负压 VREFN5
|
WriteComm(0xBB);
|
||||||
WriteComm(0x62);WriteData(0x19); // 正常模式 VREFN5 电平 = -3V
|
WriteData(0x19);
|
||||||
WriteComm(0x63);WriteData(0x19); // 空闲模式 VREFN5 = -3V
|
|
||||||
WriteComm(0x70);WriteData(0x55); // 关闭显示时源极驱动器至 AVDD
|
// LCM Control (C0h)
|
||||||
WriteComm(0x1D);WriteData(0x02); // 启用 EQ 平衡
|
WriteComm(0xC0);
|
||||||
WriteComm(0x89);WriteData(0x18); // 控制偏置 VGMP = 5.5V
|
WriteData(0x2C);
|
||||||
WriteComm(0x8A);WriteData(0xb9); // 控制偏置 VGSP = 2V
|
|
||||||
WriteComm(0x8B);WriteData(0x01); // 偏置高字节
|
// VDV and VRH Command Enable (C2h)
|
||||||
WriteComm(0x8C);WriteData(0x10); // VGMP 设为 5.4V
|
WriteComm(0xC2);
|
||||||
WriteComm(0x8D);WriteData(0x90); // VGSP 设为 2V
|
WriteData(0x01);
|
||||||
WriteComm(0x8E);WriteData(0x01);
|
|
||||||
WriteComm(0x6E);WriteData(0x0A); // 关闭 MIPI 高速数据接口以选择 SPI 驱动方式
|
// VRH Set (C3h)
|
||||||
WriteComm(0x6A);WriteData(0x05); // 配置 MUX 选择器
|
// VRH=4.3V
|
||||||
WriteComm(0x3A);WriteData(0x08); // 行延迟常数 T1_sd
|
WriteComm(0xC3);
|
||||||
WriteComm(0x3B);WriteData(0x00); // 帧延迟常数 Tp_sd
|
WriteData(0x12);
|
||||||
WriteComm(0x3D);WriteData(0x16); // 保持延迟常数 Th_sd
|
|
||||||
WriteComm(0x3F);WriteData(0x27); // 切换时序 Tsw_sd
|
// VDV Set (C4h)
|
||||||
WriteComm(0x40);WriteData(0x0F); // 切换保持 Thsw_sd
|
// VDV=0V
|
||||||
WriteComm(0x41);WriteData(0x0D); // 切换释放 Thsd_sd
|
WriteComm(0xC4);
|
||||||
|
WriteData(0x20);
|
||||||
// 如下配置模拟通道多路选通时序
|
|
||||||
WriteComm(0x42);WriteData(0x14);
|
// Frame Rate Control in Normal Mode (C6h)
|
||||||
WriteComm(0x43);WriteData(0x41);
|
// 0x0F: 60Hz
|
||||||
WriteComm(0x44);WriteData(0x25);
|
WriteComm(0xC6);
|
||||||
WriteComm(0x45);WriteData(0x52);
|
WriteData(0x0F);
|
||||||
WriteComm(0x46);WriteData(0x36);
|
|
||||||
WriteComm(0x47);WriteData(0x63);
|
// Power Control 1 (D0h)
|
||||||
WriteComm(0x48);WriteData(0x14);
|
WriteComm(0xD0);
|
||||||
WriteComm(0x49);WriteData(0x41);
|
WriteData(0xA4);
|
||||||
WriteComm(0x4A);WriteData(0x25);
|
WriteData(0xA1);
|
||||||
WriteComm(0x4B);WriteData(0x52);
|
|
||||||
WriteComm(0x4C);WriteData(0x36);
|
// Positive Gamma Correction (E0h) 正极性伽马
|
||||||
WriteComm(0x4D);WriteData(0x63);
|
WriteComm(0xE0);
|
||||||
WriteComm(0x4E);WriteData(0x14);
|
WriteData(0xD0); WriteData(0x04); WriteData(0x0D); WriteData(0x11);
|
||||||
WriteComm(0x4F);WriteData(0x41);
|
WriteData(0x13); WriteData(0x2B); WriteData(0x3F); WriteData(0x54);
|
||||||
WriteComm(0x50);WriteData(0x25);
|
WriteData(0x4C); WriteData(0x18); WriteData(0x0D); WriteData(0x0B);
|
||||||
WriteComm(0x51);WriteData(0x52);
|
WriteData(0x1F); WriteData(0x23);
|
||||||
WriteComm(0x52);WriteData(0x36);
|
|
||||||
WriteComm(0x53);WriteData(0x63);
|
// Negative Gamma Correction (E1h) 负极性伽马
|
||||||
WriteComm(0x54);WriteData(0x14);
|
WriteComm(0xE1);
|
||||||
WriteComm(0x55);WriteData(0x41);
|
WriteData(0xD0); WriteData(0x04); WriteData(0x0C); WriteData(0x11);
|
||||||
WriteComm(0x56);WriteData(0x25);
|
WriteData(0x13); WriteData(0x2C); WriteData(0x3F); WriteData(0x44);
|
||||||
WriteComm(0x57);WriteData(0x52);
|
WriteData(0x51); WriteData(0x2F); WriteData(0x1F); WriteData(0x1F);
|
||||||
WriteComm(0x58);WriteData(0x36);
|
WriteData(0x20); WriteData(0x23);
|
||||||
WriteComm(0x59);WriteData(0x63);
|
|
||||||
|
// Display Inversion On (21h) 开启反显,ST7789 IPS 屏常用
|
||||||
WriteComm(0x66);WriteData(0x90); // 空闲模式使用内部集成供电
|
WriteComm(0x21);
|
||||||
WriteComm(0x67);WriteData(0x40); // 内部延迟 1 帧切断电源
|
|
||||||
WriteComm(0x72);WriteData(0x1A); // 内部核心 OVDD 电压 = 4.6V
|
// Display On (29h) 开启显示
|
||||||
WriteComm(0x73);WriteData(0x07); // 内部核心 OVSS 电压 = -2V
|
WriteComm(0x29);
|
||||||
WriteComm(0x74);WriteData(0x0C); // OVDD 的最终输入来源设为 AVDD
|
|
||||||
WriteComm(0x6A);WriteData(0x3D); // 向单线接口发送脉冲,配置升压
|
|
||||||
WriteComm(0x6B);WriteData(0x29);
|
|
||||||
|
|
||||||
WriteComm(0xFE);WriteData(0x04); // 切换至 Page 4
|
|
||||||
WriteComm(0x5E);WriteData(0x01);
|
|
||||||
WriteComm(0x5F);WriteData(0xB8);
|
|
||||||
WriteComm(0x60);WriteData(0xBB);
|
|
||||||
WriteComm(0x61);WriteData(0xBB);
|
|
||||||
WriteComm(0x62);WriteData(0xBB);
|
|
||||||
WriteComm(0x76);WriteData(0xBB);
|
|
||||||
WriteComm(0x77);WriteData(0x3B);
|
|
||||||
WriteComm(0x78);WriteData(0x92);
|
|
||||||
WriteComm(0x79);WriteData(0xBB);
|
|
||||||
WriteComm(0x7A);WriteData(0xBB);
|
|
||||||
|
|
||||||
WriteComm(0x00);WriteData(0xDC); // 扫描时钟 CK1 寄存器配置
|
|
||||||
WriteComm(0x01);WriteData(0x00);
|
|
||||||
WriteComm(0x02);WriteData(0x02);
|
|
||||||
WriteComm(0x03);WriteData(0x00);
|
|
||||||
WriteComm(0x04);WriteData(0x08);
|
|
||||||
WriteComm(0x05);WriteData(0x01);
|
|
||||||
WriteComm(0x06);WriteData(0x70);
|
|
||||||
WriteComm(0x07);WriteData(0x0A);
|
|
||||||
WriteComm(0x08);WriteData(0x00);
|
|
||||||
|
|
||||||
WriteComm(0x09);WriteData(0xDC); // 扫描时钟 CK2 寄存器配置
|
|
||||||
WriteComm(0x0a);WriteData(0x00);
|
|
||||||
WriteComm(0x0b);WriteData(0x02);
|
|
||||||
WriteComm(0x0C);WriteData(0x00);
|
|
||||||
WriteComm(0x0D);WriteData(0x08);
|
|
||||||
WriteComm(0x0E);WriteData(0x00);
|
|
||||||
WriteComm(0x0F);WriteData(0x70);
|
|
||||||
WriteComm(0x10);WriteData(0x0A);
|
|
||||||
WriteComm(0x11);WriteData(0x00);
|
|
||||||
|
|
||||||
WriteComm(0x12);WriteData(0xCC); // 发光逻辑 EM_CK1 配置
|
|
||||||
WriteComm(0x13);WriteData(0x00);
|
|
||||||
WriteComm(0x14);WriteData(0x02);
|
|
||||||
WriteComm(0x15);WriteData(0x00);
|
|
||||||
WriteComm(0x16);WriteData(0x20);
|
|
||||||
WriteComm(0x17);WriteData(0x00);
|
|
||||||
WriteComm(0x18);WriteData(0x28);
|
|
||||||
WriteComm(0x19);WriteData(0x26);
|
|
||||||
WriteComm(0x1A);WriteData(0x00);
|
|
||||||
|
|
||||||
WriteComm(0x1B);WriteData(0xCC); // 发光逻辑 EM_CK2 配置
|
|
||||||
WriteComm(0x1C);WriteData(0x00);
|
|
||||||
WriteComm(0x1D);WriteData(0x02);
|
|
||||||
WriteComm(0x1E);WriteData(0x00);
|
|
||||||
WriteComm(0x1F);WriteData(0x20);
|
|
||||||
WriteComm(0x20);WriteData(0x01);
|
|
||||||
WriteComm(0x21);WriteData(0x28);
|
|
||||||
WriteComm(0x22);WriteData(0x26);
|
|
||||||
WriteComm(0x23);WriteData(0x00);
|
|
||||||
|
|
||||||
WriteComm(0x4C);WriteData(0x89); // 扫描起始信号 SN_STV 配置
|
|
||||||
WriteComm(0x4D);WriteData(0x00);
|
|
||||||
WriteComm(0x4E);WriteData(0x01);
|
|
||||||
WriteComm(0x4F);WriteData(0x00);
|
|
||||||
WriteComm(0x50);WriteData(0x01);
|
|
||||||
WriteComm(0x51);WriteData(0xBB);
|
|
||||||
WriteComm(0x52);WriteData(0xBB);
|
|
||||||
|
|
||||||
WriteComm(0x53);WriteData(0xCA); // 发光起始信号 EM_STV 配置
|
|
||||||
WriteComm(0x54);WriteData(0x00);
|
|
||||||
WriteComm(0x55);WriteData(0x03);
|
|
||||||
WriteComm(0x56);WriteData(0x00);
|
|
||||||
WriteComm(0x58);WriteData(0x00);
|
|
||||||
WriteComm(0x59);WriteData(0x00);
|
|
||||||
WriteComm(0x65);WriteData(0x45);
|
|
||||||
WriteComm(0x66);WriteData(0x0C);
|
|
||||||
WriteComm(0x67);WriteData(0x00);
|
|
||||||
|
|
||||||
WriteComm(0xFE);WriteData(0x01); // 切换至 Page 1 验证芯片 ID
|
|
||||||
WriteComm(0xE5);WriteData(0x00);
|
|
||||||
WriteComm(0xE6);WriteData(0x10);
|
|
||||||
WriteComm(0xE7);WriteData(0x31);
|
|
||||||
|
|
||||||
WriteComm(0xFE);WriteData(0x00); // 切换回 Page 0 进行正常显示操作
|
|
||||||
WriteComm(0xC4);WriteData(0x80);
|
|
||||||
WriteComm(0x2A);WriteData(0x00);WriteData(0x04);WriteData(0x00);WriteData(0x7B); // 设定列像素物理边界 (4~123,宽120像素)
|
|
||||||
|
|
||||||
// ====== 显式设置屏幕像素色彩格式为 16 位像素格式 (COLMOD = 0x75,即RGB565格式) ======
|
|
||||||
WriteComm(0x3A);WriteData(0x75);
|
|
||||||
|
|
||||||
WriteComm(0x11);WriteData(0x00); // 唤醒屏芯片:发送 Sleep Out 退出睡眠指令
|
|
||||||
Delay_ms(150); // 退出睡眠状态后,必须等待 150ms 以便驱动电荷泵稳定
|
|
||||||
|
|
||||||
WriteComm(0x29);WriteData(0x00); // 开启屏幕显示:发送 Display ON 指令,像素层开始发光
|
|
||||||
Delay_ms(20);
|
Delay_ms(20);
|
||||||
|
|
||||||
|
// 开启显示后立即进行全屏清屏,防止屏幕亮起瞬间呈现花屏随机噪点
|
||||||
|
LCD_Clear(COLOR_BLACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 设置屏幕绘制显存窗口区域
|
* @brief 设定屏幕的数据更新绘制窗口区域
|
||||||
*/
|
*/
|
||||||
void LCD_SetWindow(u16 x_start, u16 x_end, u16 y_start, u16 y_end)
|
void LCD_SetWindow(u16 x_start, u16 x_end, u16 y_start, u16 y_end)
|
||||||
{
|
{
|
||||||
|
// 在底层窗口设定中,统一进行物理偏置的透明校正,免除上层各个绘图函数的手工计算
|
||||||
|
x_start += LCD_X_OFFSET;
|
||||||
|
x_end += LCD_X_OFFSET;
|
||||||
|
y_start += LCD_Y_OFFSET;
|
||||||
|
y_end += LCD_Y_OFFSET;
|
||||||
|
|
||||||
// 写入 Column Address Set 列地址设置寄存器命令 (0x2A)
|
// 写入 Column Address Set 列地址设置寄存器命令 (0x2A)
|
||||||
WriteComm(0x2A);
|
WriteComm(0x2A);
|
||||||
WriteData((u8)(x_start >> 8));
|
WriteData((u8)(x_start >> 8));
|
||||||
@@ -324,14 +212,11 @@ void LCD_SetWindow(u16 x_start, u16 x_end, u16 y_start, u16 y_end)
|
|||||||
void LCD_Clear(u16 color)
|
void LCD_Clear(u16 color)
|
||||||
{
|
{
|
||||||
u16 i, j;
|
u16 i, j;
|
||||||
// 【物理引脚偏移说明】:
|
LCD_SetWindow(0, LCD_WIDTH - 1, 0, LCD_HEIGHT - 1);
|
||||||
// 该 AMOLED 屏在物理层面上水平方向的列像素地址为 4 ~ 123(总宽 120 像素)。
|
|
||||||
// 因此调用 LCD_SetWindow 时,X 轴的物理起始和终止坐标必须偏置 +4 才能与物理屏幕像素完全对齐。
|
|
||||||
LCD_SetWindow(4, 123, 0, 239);
|
|
||||||
WriteComm(0x2C); // 发送 RAM Write (0x2C) 显存写入命令
|
WriteComm(0x2C); // 发送 RAM Write (0x2C) 显存写入命令
|
||||||
for(i = 0; i < 120; i++)
|
for(i = 0; i < LCD_WIDTH; i++)
|
||||||
{
|
{
|
||||||
for(j = 0; j < 240; j++)
|
for(j = 0; j < LCD_HEIGHT; j++)
|
||||||
{
|
{
|
||||||
WriteData((u8)(color >> 8)); // 发送 RGB565 高 8 位字节
|
WriteData((u8)(color >> 8)); // 发送 RGB565 高 8 位字节
|
||||||
WriteData((u8)(color & 0xFF)); // 发送 RGB565 低 8 位字节
|
WriteData((u8)(color & 0xFF)); // 发送 RGB565 低 8 位字节
|
||||||
@@ -348,8 +233,7 @@ void LCD_FillRect(u16 x, u16 y, u16 w, u16 h, u16 color)
|
|||||||
u8 ch = (u8)(color >> 8);
|
u8 ch = (u8)(color >> 8);
|
||||||
u8 cl = (u8)(color & 0xFF);
|
u8 cl = (u8)(color & 0xFF);
|
||||||
|
|
||||||
// X 坐标应用物理偏置偏移 +4 像素
|
LCD_SetWindow(x, x + w - 1, y, y + h - 1);
|
||||||
LCD_SetWindow(x + 4, x + w - 1 + 4, y, y + h - 1);
|
|
||||||
WriteComm(0x2C); // 写入显存
|
WriteComm(0x2C); // 写入显存
|
||||||
count = w * h; // 实心矩形包含的总像素数
|
count = w * h; // 实心矩形包含的总像素数
|
||||||
for(i = 0; i < count; i++)
|
for(i = 0; i < count; i++)
|
||||||
@@ -365,7 +249,7 @@ void LCD_FillRect(u16 x, u16 y, u16 w, u16 h, u16 color)
|
|||||||
*/
|
*/
|
||||||
void LCD_DrawRectBorder(u16 x, u16 y, u16 w, u16 h, u16 color)
|
void LCD_DrawRectBorder(u16 x, u16 y, u16 w, u16 h, u16 color)
|
||||||
{
|
{
|
||||||
LCD_FillRect(x, y, w, 1, color); // 上边框线 (厚度 1 像素)
|
LCD_FillRect(x, y, w, 1, color); // 上边框线
|
||||||
LCD_FillRect(x, y + h - 1, w, 1, color); // 下边框线
|
LCD_FillRect(x, y + h - 1, w, 1, color); // 下边框线
|
||||||
LCD_FillRect(x, y, 1, h, color); // 左边框线
|
LCD_FillRect(x, y, 1, h, color); // 左边框线
|
||||||
LCD_FillRect(x + w - 1, y, 1, h, color); // 右边框线
|
LCD_FillRect(x + w - 1, y, 1, h, color); // 右边框线
|
||||||
@@ -380,8 +264,6 @@ void LCD_DrawRectBorder(u16 x, u16 y, u16 w, u16 h, u16 color)
|
|||||||
* @param bmp FLASH 中的单色像素数组指针 (1表示画前景色,0表示画背景色)
|
* @param bmp FLASH 中的单色像素数组指针 (1表示画前景色,0表示画背景色)
|
||||||
* @param color 16位前景色
|
* @param color 16位前景色
|
||||||
* @param bg_color 16位背景色
|
* @param bg_color 16位背景色
|
||||||
* @details 会逐行、逐列解析单色图的字节数组,每一行占 (w+7)/8 个字节,
|
|
||||||
* 利用位操作提取指定 bit 的状态并发送色彩数据写入屏幕。
|
|
||||||
*/
|
*/
|
||||||
void LCD_DrawMonoBitmap(u16 x, u16 y, u16 w, u16 h, unsigned char code *bmp, u16 color, u16 bg_color)
|
void LCD_DrawMonoBitmap(u16 x, u16 y, u16 w, u16 h, unsigned char code *bmp, u16 color, u16 bg_color)
|
||||||
{
|
{
|
||||||
@@ -392,7 +274,7 @@ void LCD_DrawMonoBitmap(u16 x, u16 y, u16 w, u16 h, unsigned char code *bmp, u16
|
|||||||
u8 bgh = (u8)(bg_color >> 8);
|
u8 bgh = (u8)(bg_color >> 8);
|
||||||
u8 bgl = (u8)(bg_color & 0xFF);
|
u8 bgl = (u8)(bg_color & 0xFF);
|
||||||
|
|
||||||
LCD_SetWindow(x + 4, x + w - 1 + 4, y, y + h - 1); // 限制绘制显存窗口并应用偏移
|
LCD_SetWindow(x, x + w - 1, y, y + h - 1); // 限制绘制显存窗口
|
||||||
WriteComm(0x2C);
|
WriteComm(0x2C);
|
||||||
|
|
||||||
// 嵌套循环,从第 0 行第 0 列开始遍历每个像素位置
|
// 嵌套循环,从第 0 行第 0 列开始遍历每个像素位置
|
||||||
@@ -420,7 +302,6 @@ void LCD_DrawMonoBitmap(u16 x, u16 y, u16 w, u16 h, unsigned char code *bmp, u16
|
|||||||
/**
|
/**
|
||||||
* @brief 在指定坐标渲染输出一个 8x16 尺寸的普通英文 ASCII 字符
|
* @brief 在指定坐标渲染输出一个 8x16 尺寸的普通英文 ASCII 字符
|
||||||
* @param ch ASCII 字符本身
|
* @param ch ASCII 字符本身
|
||||||
* @details 遍历 8x16 点阵字库,将索引对应的 16 个字符编码字节依次提取,按位发送。
|
|
||||||
*/
|
*/
|
||||||
void LCD_ShowChar8x16(u16 x, u16 y, char ch, u16 color, u16 bg_color)
|
void LCD_ShowChar8x16(u16 x, u16 y, char ch, u16 color, u16 bg_color)
|
||||||
{
|
{
|
||||||
@@ -433,7 +314,7 @@ void LCD_ShowChar8x16(u16 x, u16 y, char ch, u16 color, u16 bg_color)
|
|||||||
|
|
||||||
if(ch < 0x20 || ch > 0x7E) return; // 过滤不可见控制字符
|
if(ch < 0x20 || ch > 0x7E) return; // 过滤不可见控制字符
|
||||||
|
|
||||||
LCD_SetWindow(x + 4, x + 8 - 1 + 4, y, y + 16 - 1); // 应用 +4 像素偏移
|
LCD_SetWindow(x, x + 8 - 1, y, y + 16 - 1);
|
||||||
WriteComm(0x2C);
|
WriteComm(0x2C);
|
||||||
|
|
||||||
// 循环 16 行,绘制 8x16 字型
|
// 循环 16 行,绘制 8x16 字型
|
||||||
@@ -479,24 +360,21 @@ void LCD_ShowStringCentered(u16 y, char *str, u16 color, u16 bg_color)
|
|||||||
char *p = str;
|
char *p = str;
|
||||||
while(*p++) len++; // 计算字符串长度
|
while(*p++) len++; // 计算字符串长度
|
||||||
|
|
||||||
if(len * 8 >= 120)
|
if(len * 8 >= LCD_WIDTH)
|
||||||
// 若超出或等同于屏幕有效宽度 (120像素),强制从最左侧 0 开始左对齐绘制
|
// 若超出或等同于屏幕有效宽度,强制从最左侧 0 开始左对齐绘制
|
||||||
LCD_ShowString(0, y, str, color, bg_color);
|
LCD_ShowString(0, y, str, color, bg_color);
|
||||||
else
|
else
|
||||||
// 否则通过公式 (120 - 字符宽度) / 2 计算出水平居中的首字起始偏移 X 坐标并渲染
|
// 否则通过公式 (LCD_WIDTH - 字符宽度) / 2 计算出水平居中的首字起始偏移 X 坐标并渲染
|
||||||
LCD_ShowString((120 - len * 8) / 2, y, str, color, bg_color);
|
LCD_ShowString((LCD_WIDTH - len * 8) / 2, y, str, color, bg_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 渲染大号特粗 16x32 尺寸的英文/数字字符
|
* @brief 渲染大号特粗 16x32 尺寸的英文/数字字符
|
||||||
* @details 【字库双重放大技术】:
|
|
||||||
* 由于 8051 FLASH 容量有限,手环未附带特大型号的 16x32 大字库。
|
|
||||||
* 此函数通过算法对 8x16 基础字库点阵数据在水平方向(列)和垂直方向(行)各重复绘制 2 遍
|
|
||||||
* (即单个点扩充绘制为 2x2 的像素团),从而实现对原字型双重放大渲染为 16x32 规格,节省空间。
|
|
||||||
*/
|
*/
|
||||||
void LCD_ShowChar16x32(u16 x, u16 y, char ch, u16 color, u16 bg_color)
|
void LCD_ShowChar16x32(u16 x, u16 y, char ch, u16 color, u16 bg_color)
|
||||||
{
|
{
|
||||||
u8 r, c, i, j;
|
u8 r, c, i, j;
|
||||||
|
u8 val_bit;
|
||||||
u8 char_idx = ch - 0x20;
|
u8 char_idx = ch - 0x20;
|
||||||
u8 f_h = (u8)(color >> 8);
|
u8 f_h = (u8)(color >> 8);
|
||||||
u8 f_l = (u8)(color & 0xFF);
|
u8 f_l = (u8)(color & 0xFF);
|
||||||
@@ -505,10 +383,10 @@ void LCD_ShowChar16x32(u16 x, u16 y, char ch, u16 color, u16 bg_color)
|
|||||||
|
|
||||||
if(ch < 0x20 || ch > 0x7E) return;
|
if(ch < 0x20 || ch > 0x7E) return;
|
||||||
|
|
||||||
LCD_SetWindow(x + 4, x + 16 - 1 + 4, y, y + 32 - 1); // 锁定 16x32 的绘图视窗
|
LCD_SetWindow(x, x + 16 - 1, y, y + 32 - 1); // 锁定 16x32 的绘图视窗
|
||||||
WriteComm(0x2C);
|
WriteComm(0x2C);
|
||||||
|
|
||||||
// 遍历字型的 16 个点阵行数据
|
// 遍历字型的 16 个点阵行 data
|
||||||
for(r = 0; r < 16; r++)
|
for(r = 0; r < 16; r++)
|
||||||
{
|
{
|
||||||
u8 line = FONT_8x16[char_idx][r];
|
u8 line = FONT_8x16[char_idx][r];
|
||||||
@@ -519,7 +397,7 @@ void LCD_ShowChar16x32(u16 x, u16 y, char ch, u16 color, u16 bg_color)
|
|||||||
// 遍历单行内的 8 个 bit 像素点
|
// 遍历单行内的 8 个 bit 像素点
|
||||||
for(c = 0; c < 8; c++)
|
for(c = 0; c < 8; c++)
|
||||||
{
|
{
|
||||||
u8 val_bit = (line & (0x80 >> c));
|
val_bit = (line & (0x80 >> c));
|
||||||
// 横向插点翻倍:单个像素点重复发送写入 2 次 (j = 0 和 j = 1)
|
// 横向插点翻倍:单个像素点重复发送写入 2 次 (j = 0 和 j = 1)
|
||||||
for(j = 0; j < 2; j++)
|
for(j = 0; j < 2; j++)
|
||||||
{
|
{
|
||||||
@@ -540,7 +418,7 @@ void LCD_ShowChar16x32(u16 x, u16 y, char ch, u16 color, u16 bg_color)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 在指定坐标绘制特大 16x32 规格的数字/英文字符串
|
* @brief 在指定坐标绘制特大 16x32 规格的数字/英文字串
|
||||||
*/
|
*/
|
||||||
void LCD_ShowString16x32(u16 x, u16 y, char *str, u16 color, u16 bg_color)
|
void LCD_ShowString16x32(u16 x, u16 y, char *str, u16 color, u16 bg_color)
|
||||||
{
|
{
|
||||||
@@ -561,10 +439,10 @@ void LCD_ShowString16x32Centered(u16 y, char *str, u16 color, u16 bg_color)
|
|||||||
char *p = str;
|
char *p = str;
|
||||||
while(*p++) len++; // 计算字数
|
while(*p++) len++; // 计算字数
|
||||||
|
|
||||||
if(len * 16 >= 120)
|
if(len * 16 >= LCD_WIDTH)
|
||||||
// 超出屏幕宽度,左对齐
|
// 超出屏幕宽度,左对齐
|
||||||
LCD_ShowString16x32(0, y, str, color, bg_color);
|
LCD_ShowString16x32(0, y, str, color, bg_color);
|
||||||
else
|
else
|
||||||
// 水平居中计算
|
// 水平居中计算
|
||||||
LCD_ShowString16x32((120 - len * 16) / 2, y, str, color, bg_color);
|
LCD_ShowString16x32((LCD_WIDTH - len * 16) / 2, y, str, color, bg_color);
|
||||||
}
|
}
|
||||||
|
|||||||
133
Drivers/lcd.h
133
Drivers/lcd.h
@@ -4,157 +4,118 @@
|
|||||||
#include "../App/config.h"
|
#include "../App/config.h"
|
||||||
#include "stc32g.h"
|
#include "stc32g.h"
|
||||||
|
|
||||||
/* ====== Truly AMOLED RM69310 屏幕驱动 (RGB565格式) 常数定义 ====== */
|
/* ====== LHS114TC-IF03 (ST7789V) 屏幕参数与常数定义 ====== */
|
||||||
|
#define LCD_WIDTH 135 // 屏幕物理宽度
|
||||||
|
#define LCD_HEIGHT 240 // 屏幕物理高度
|
||||||
|
#define LCD_X_OFFSET 52 // 水平 X 轴物理偏置量
|
||||||
|
#define LCD_Y_OFFSET 40 // 垂直 Y 轴物理偏置量
|
||||||
|
|
||||||
// 16位 RGB565 颜色常量定义:
|
// 16位 RGB565 颜色常量定义:
|
||||||
// 格式为:Red(5位) Green(6位) Blue(5位) 拼接而成的 16 位整数值
|
#define COLOR_BLACK 0x0000 // 黑色
|
||||||
#define COLOR_BLACK 0x0000 // 黑色
|
#define COLOR_WHITE 0xFFFF // 白色
|
||||||
#define COLOR_WHITE 0xFFFF // 白色
|
#define COLOR_CYAN 0x073F // 青色
|
||||||
#define COLOR_CYAN 0x073F // 青色/天蓝色
|
#define COLOR_RED 0xF800 // 红色
|
||||||
#define COLOR_RED 0xF800 // 红色
|
#define COLOR_GREEN 0x07E0 // 绿色
|
||||||
#define COLOR_GREEN 0x07E0 // 绿色
|
#define COLOR_BLUE 0x001F // 蓝色
|
||||||
#define COLOR_BLUE 0x001F // 蓝色
|
#define COLOR_GRAY 0x5AEB // 灰色
|
||||||
#define COLOR_GRAY 0x5AEB // 灰色
|
#define COLOR_AMBER 0xFDC0 // 琥珀色
|
||||||
#define COLOR_AMBER 0xFDC0 // 琥珀色/橙黄色
|
|
||||||
|
|
||||||
/* ====== 屏幕电源控制与基本驱动接口 ====== */
|
/* ====== 屏幕电源控制与基本驱动接口 ====== */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 通过脉冲序列为 SGM3833 屏幕负压供电芯片发送开启配置脉冲
|
* @brief 通过脉冲序列为 SGM3833 供电芯片写入配置
|
||||||
* @param pulse_cnt 发送的脉冲个数 (例如 27 次脉冲开启并稳压屏幕输出)
|
|
||||||
* @details 采用单线脉冲边沿计数调制技术,为 RM69310 控制芯片提供其正常显示所需的高质量偏置正负电压。
|
|
||||||
*/
|
*/
|
||||||
void SGM_SendPulse(u8 pulse_cnt);
|
void SGM_SendPulse(u8 pulse_cnt);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 执行 Truly AMOLED 屏幕的硬件复位
|
* @brief 执行 LHS114TC-IF03 屏幕的硬件复位
|
||||||
* @details 将 LCD_RST 引脚拉低保持一定时间后拉高,强制复位屏幕控制器,使其恢复到初始逻辑状态。
|
|
||||||
*/
|
*/
|
||||||
void LCD_Reset(void);
|
void LCD_Reset(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 初始化 Truly AMOLED 屏幕控制器 RM69310
|
* @brief 初始化 LHS114TC-IF03 (ST7789V) 屏幕控制器
|
||||||
* @details 执行屏幕硬件复位,通过软件 SPI 总线依次向屏幕控制器写入寄存器偏置、显示周期、
|
|
||||||
* 通道选择、关闭休眠 (Sleep Out: 0x11)、开启显示 (Display On: 0x29) 等一系列
|
|
||||||
* 出厂初始化命令序列,使屏幕准备好接收显示像素帧。
|
|
||||||
*/
|
*/
|
||||||
void LCD_Init(void);
|
void LCD_Init(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 向屏幕写入 1 字节控制命令
|
* @brief 向屏幕写入 1 字节控制命令
|
||||||
* @param cmd 8 位命令字节
|
|
||||||
*/
|
*/
|
||||||
void WriteComm(u8 cmd);
|
void WriteComm(u8 cmd);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 设定屏幕的数据更新绘制窗口区域 (显存写入范围)
|
* @brief 向屏幕写入 1 字节显示数据
|
||||||
* @param x_start X 轴起始坐标 (0 ~ 119)
|
*/
|
||||||
* @param x_end X 轴终止坐标 (0 ~ 119)
|
void WriteData(u8 dat);
|
||||||
* @param y_start Y 轴起始坐标 (0 ~ 239)
|
|
||||||
* @param y_end Y 轴终止坐标 (0 ~ 239)
|
/**
|
||||||
* @details 限定后续的数据流写入范围,实现局部页面刷新或全部页面刷新。
|
* @brief 设定屏幕的数据更新绘制窗口区域
|
||||||
*/
|
*/
|
||||||
void LCD_SetWindow(u16 x_start, u16 x_end, u16 y_start, u16 y_end);
|
void LCD_SetWindow(u16 x_start, u16 x_end, u16 y_start, u16 y_end);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 以指定纯色清除整个屏幕显示内容
|
* @brief 用指定色彩刷写清空全屏幕
|
||||||
* @param color 16位 RGB565 颜色填充值
|
|
||||||
*/
|
*/
|
||||||
void LCD_Clear(u16 color);
|
void LCD_Clear(u16 color);
|
||||||
|
|
||||||
|
/* ====== 关键图形与点阵字符渲染接口 ====== */
|
||||||
/* ====== 像素级图形几何算法与字符渲染接口 ====== */
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 在屏幕指定位置绘制一个单色填充的实心矩形
|
* @brief 在屏幕指定视口绘制实心矩形
|
||||||
* @param x 矩形左上角 X 轴坐标
|
|
||||||
* @param y 矩形左上角 Y 轴坐标
|
|
||||||
* @param w 矩形宽度
|
|
||||||
* @param h 矩形高度
|
|
||||||
* @param color 16位 RGB565 填充色彩值
|
|
||||||
*/
|
*/
|
||||||
void LCD_FillRect(u16 x, u16 y, u16 w, u16 h, u16 color);
|
void LCD_FillRect(u16 x, u16 y, u16 w, u16 h, u16 color);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 绘制一个无填充的空心矩形边界框
|
* @brief 绘制空心矩形边界框
|
||||||
* @param x 矩形左上角 X 轴坐标
|
|
||||||
* @param y 矩形左上角 Y 轴坐标
|
|
||||||
* @param w 边框总宽度
|
|
||||||
* @param h 边框总高度
|
|
||||||
* @param color 16位 RGB565 边框线条色彩
|
|
||||||
*/
|
*/
|
||||||
void LCD_DrawRectBorder(u16 x, u16 y, u16 w, u16 h, u16 color);
|
void LCD_DrawRectBorder(u16 x, u16 y, u16 w, u16 h, u16 color);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 绘制渲染一个自定义宽高的单色单色位图
|
* @brief 渲染显示一个单色字模位图资源
|
||||||
* @param x 绘制的左上角 X 轴坐标
|
|
||||||
* @param y 绘制的左上角 Y 轴坐标
|
|
||||||
* @param w 单色图宽度 (单位: 像素点数)
|
|
||||||
* @param h 单色图高度
|
|
||||||
* @param bmp 单色位图所在的 FLASH code 区指针 (1位对应1像素:1画前景色,0画背景色)
|
|
||||||
* @param color 位图前景色 (RGB565)
|
|
||||||
* @param bg_color 位图背景色
|
|
||||||
*/
|
*/
|
||||||
void LCD_DrawMonoBitmap(u16 x, u16 y, u16 w, u16 h, unsigned char code *bmp, u16 color, u16 bg_color);
|
void LCD_DrawMonoBitmap(u16 x, u16 y, u16 w, u16 h, unsigned char code *bmp, u16 color, u16 bg_color);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 渲染显示一个 8x16 尺寸的普通英文 ASCII 字符
|
* @brief 在指定坐标渲染输出一个 8x16 尺寸的普通英文 ASCII 字符
|
||||||
*/
|
*/
|
||||||
void LCD_ShowChar8x16(u16 x, u16 y, char ch, u16 color, u16 bg_color);
|
void LCD_ShowChar8x16(u16 x, u16 y, char ch, u16 color, u16 bg_color);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 渲染显示一行普通的英文 ASCII 字符串 (左对齐)
|
* @brief 在指定坐标打印一行普通英文 ASCII 字符串 (左对齐)
|
||||||
* @param x 绘制行起始 X 坐标
|
|
||||||
* @param y 绘制行 Y 坐标
|
|
||||||
* @param str 字符串头指针
|
|
||||||
* @param color 字符颜色 (RGB565)
|
|
||||||
* @param bg_color 字符背景背景填充色
|
|
||||||
*/
|
*/
|
||||||
void LCD_ShowString(u16 x, u16 y, char *str, u16 color, u16 bg_color);
|
void LCD_ShowString(u16 x, u16 y, char *str, u16 color, u16 bg_color);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 在指定 Y 轴高度上,水平居中对齐显示一行字符串
|
* @brief 在指定 Y 轴高度上,自动计算偏移以实现字符串水平居中显示
|
||||||
* @param y 渲染的 Y 轴高度 (0 ~ 239)
|
|
||||||
* @param str 字符串内容
|
|
||||||
* @param color 颜色值
|
|
||||||
* @param bg_color 背景色
|
|
||||||
*/
|
*/
|
||||||
void LCD_ShowStringCentered(u16 y, char *str, u16 color, u16 bg_color);
|
void LCD_ShowStringCentered(u16 y, char *str, u16 color, u16 bg_color);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 渲染显示一个 16x32 大号特粗尺寸的英文/数字字符
|
* @brief 渲染大号特粗 16x32 尺寸的英文/数字字符
|
||||||
*/
|
*/
|
||||||
void LCD_ShowChar16x32(u16 x, u16 y, char ch, u16 color, u16 bg_color);
|
void LCD_ShowChar16x32(u16 x, u16 y, char ch, u16 color, u16 bg_color);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 显示大号特粗 16x32 规格的数字/英文字符串 (如时钟大字显示)
|
* @brief 在指定坐标绘制特大 16x32 规格的数字/英文字串
|
||||||
*/
|
*/
|
||||||
void LCD_ShowString16x32(u16 x, u16 y, char *str, u16 color, u16 bg_color);
|
void LCD_ShowString16x32(u16 x, u16 y, char *str, u16 color, u16 bg_color);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 在指定 Y 轴高度上,居中显示 16x32 特大规格字体的字符串
|
* @brief 在指定 Y 轴高度上,水平居中渲染显示特大 16x32 规格字符串
|
||||||
* @param y 渲染的 Y 轴高度 (0 ~ 239)
|
|
||||||
* @param str 大字字符串内容
|
|
||||||
* @param color 大字前景色
|
|
||||||
* @param bg_color 大字背景色
|
|
||||||
*/
|
*/
|
||||||
void LCD_ShowString16x32Centered(u16 y, char *str, u16 color, u16 bg_color);
|
void LCD_ShowString16x32Centered(u16 y, char *str, u16 color, u16 bg_color);
|
||||||
|
|
||||||
|
/* ====== 图标外部点阵数据声明 ====== */
|
||||||
/* ====== 图标与点阵字模外部数据声明 ====== */
|
extern unsigned char code FONT_8x16[95][16]; // 8x16 英文点阵字库
|
||||||
// 这些数据实体均定义在底层字库 lcd_font.h 文件中
|
extern unsigned char code bmp_battery_20x10[]; // 20x10 电池指示位图
|
||||||
|
extern unsigned char code bmp_lock_16x16[]; // 16x16 闭合锁头位图
|
||||||
extern unsigned char code FONT_8x16[95][16]; // 8x16 英文 ASCII 点阵字库
|
extern unsigned char code bmp_unlock_16x16[]; // 16x16 开启锁头位图
|
||||||
extern unsigned char code bmp_battery_20x10[]; // 20x10 电池电量指示位图
|
extern unsigned char code bmp_radar_32x32_f1[]; // 32x32 雷达扩散帧1
|
||||||
extern unsigned char code bmp_lock_16x16[]; // 16x16 闭合锁头位图
|
extern unsigned char code bmp_radar_32x32_f2[]; // 32x32 雷达扩散帧2
|
||||||
extern unsigned char code bmp_unlock_16x16[]; // 16x16 开启锁头位图
|
extern unsigned char code bmp_radar_32x32_f3[]; // 32x32 雷达扩散帧3
|
||||||
extern unsigned char code bmp_radar_32x32_f1[]; // 32x32 雷达扩散帧 1
|
extern unsigned char code bmp_confirm_lock_32x32[]; // 32x32 锁型常规大图标
|
||||||
extern unsigned char code bmp_radar_32x32_f2[]; // 32x32 雷达扩散帧 2
|
extern unsigned char code bmp_checkmark_32x32[]; // 32x32 成功大对勾
|
||||||
extern unsigned char code bmp_radar_32x32_f3[]; // 32x32 雷达扩散帧 3
|
extern unsigned char code bmp_cross_32x32[]; // 32x32 失败大红叉
|
||||||
extern unsigned char code bmp_confirm_lock_32x32[]; // 32x32 传感器类型锁头大图
|
extern unsigned char code bmp_door_32x32[]; // 32x32 门磁大警示图
|
||||||
extern unsigned char code bmp_checkmark_32x32[]; // 32x32 配对成功大对勾
|
extern unsigned char code bmp_siren_32x32[]; // 32x32 烟感喇叭大警示图
|
||||||
extern unsigned char code bmp_cross_32x32[]; // 32x32 配对失败大红叉
|
|
||||||
extern unsigned char code bmp_door_32x32[]; // 32x32 门磁大警告图标
|
|
||||||
extern unsigned char code bmp_siren_32x32[]; // 32x32 烟感喇叭警示图标
|
|
||||||
|
|
||||||
#endif // __LCD_H__
|
#endif // __LCD_H__
|
||||||
|
|||||||
434
Drivers/pcf8563.c
Normal file
434
Drivers/pcf8563.c
Normal file
@@ -0,0 +1,434 @@
|
|||||||
|
#include "pcf8563.h"
|
||||||
|
#include "../App/system.h"
|
||||||
|
|
||||||
|
// 动态引脚对调全局标志 (0: 默认 SCL=P3.2, SDA=P3.3; 1: 对调 SCL=P3.3, SDA=P3.2)
|
||||||
|
volatile u8 i2c_swapped = 0;
|
||||||
|
|
||||||
|
static void SCL_W(u8 val)
|
||||||
|
{
|
||||||
|
if (!i2c_swapped) P32 = val;
|
||||||
|
else P33 = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SDA_W(u8 val)
|
||||||
|
{
|
||||||
|
if (!i2c_swapped) P33 = val;
|
||||||
|
else P32 = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u8 SDA_R(void)
|
||||||
|
{
|
||||||
|
if (!i2c_swapped) return P33;
|
||||||
|
else return P32;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动态延时参数 (单位: us)
|
||||||
|
volatile u8 i2c_delay_us = 30;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief I2C 产生起始信号 (START)
|
||||||
|
*/
|
||||||
|
static void I2C_Start(void)
|
||||||
|
{
|
||||||
|
SDA_W(1);
|
||||||
|
SCL_W(1);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
SDA_W(0);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
SCL_W(0);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief I2C 产生停止信号 (STOP)
|
||||||
|
*/
|
||||||
|
static void I2C_Stop(void)
|
||||||
|
{
|
||||||
|
SDA_W(0);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
SCL_W(1);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
SDA_W(1);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief I2C 发送应答/非应答信号
|
||||||
|
* @param ack 1: ACK(0), 0: NACK(1)
|
||||||
|
*/
|
||||||
|
static void I2C_SendAck(bit ack)
|
||||||
|
{
|
||||||
|
SDA_W(!ack);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
SCL_W(1);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
SCL_W(0);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
SDA_W(1);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief I2C 等待从机应答 (Wait ACK)
|
||||||
|
* @return bit 1: 收到应答, 0: 未收到应答
|
||||||
|
*/
|
||||||
|
static bit I2C_WaitAck(void)
|
||||||
|
{
|
||||||
|
u8 retry = 0;
|
||||||
|
SDA_W(1); // 释放 SDA 接收 ACK
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
SCL_W(1);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
while (SDA_R()) {
|
||||||
|
retry++;
|
||||||
|
Delay_us(5);
|
||||||
|
if (retry > 250) {
|
||||||
|
I2C_Stop();
|
||||||
|
return 0; // 超时未收到 ACK 应答,立即优雅退出,防止死锁
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SCL_W(0);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief I2C 发送单字节数据
|
||||||
|
* @param dat 待发送 of 字节数据
|
||||||
|
*/
|
||||||
|
static void I2C_SendByte(u8 dat)
|
||||||
|
{
|
||||||
|
u8 i;
|
||||||
|
for (i = 0; i < 8; i++) {
|
||||||
|
SDA_W((dat & 0x80) ? 1 : 0);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
SCL_W(1);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
SCL_W(0);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
dat <<= 1;
|
||||||
|
}
|
||||||
|
SDA_W(1); // 释放 SDA 接收 ACK
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief I2C 读取单字节数据
|
||||||
|
* @return u8 接收到的字节数据
|
||||||
|
*/
|
||||||
|
static u8 I2C_ReadByte(void)
|
||||||
|
{
|
||||||
|
u8 i, dat = 0;
|
||||||
|
SDA_W(1); // 设为输入
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
for (i = 0; i < 8; i++) {
|
||||||
|
dat <<= 1;
|
||||||
|
SCL_W(1);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
if (SDA_R()) {
|
||||||
|
dat |= 0x01;
|
||||||
|
}
|
||||||
|
SCL_W(0);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
}
|
||||||
|
return dat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== BCD 与二进制 10 进制互转辅助函数 ====== */
|
||||||
|
|
||||||
|
static u8 BCD2DEC(u8 bcd)
|
||||||
|
{
|
||||||
|
return ((bcd >> 4) * 10) + (bcd & 0x0F);
|
||||||
|
}
|
||||||
|
|
||||||
|
static u8 DEC2BCD(u8 dec)
|
||||||
|
{
|
||||||
|
return (((dec / 10) << 4) | (dec % 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void I2C_ReleaseBus(void)
|
||||||
|
{
|
||||||
|
u8 i;
|
||||||
|
SDA_W(1);
|
||||||
|
SCL_W(1);
|
||||||
|
Delay_us(30);
|
||||||
|
|
||||||
|
// 如果 SDA 被拉低,说明总线被死锁占用
|
||||||
|
if (SDA_R() == 0) {
|
||||||
|
for (i = 0; i < 9; i++) {
|
||||||
|
SCL_W(0);
|
||||||
|
Delay_us(30);
|
||||||
|
SCL_W(1);
|
||||||
|
Delay_us(30);
|
||||||
|
if (SDA_R() == 1) {
|
||||||
|
break; // 从机已释放总线,提前退出
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 强制产生停止条件恢复空闲
|
||||||
|
SDA_W(0);
|
||||||
|
Delay_us(30);
|
||||||
|
SCL_W(1);
|
||||||
|
Delay_us(30);
|
||||||
|
SDA_W(1);
|
||||||
|
Delay_us(30);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== PCF8563 驱动核心接口 ====== */
|
||||||
|
|
||||||
|
bit PCF8563_Init(void)
|
||||||
|
{
|
||||||
|
// 1. 默认引脚尝试 (SCL=P3.2, SDA=P3.3)
|
||||||
|
i2c_swapped = 0;
|
||||||
|
I2C_ReleaseBus(); // 强力总线死锁释放
|
||||||
|
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte(PCF8563_ADDR_WRITE);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
Uart_SendString("[RTC] Detected Normal pin mapping (SCL=P32, SDA=P33)\r\n");
|
||||||
|
} else {
|
||||||
|
// 2. 尝试对调引脚 (SCL=P3.3, SDA=P3.2)
|
||||||
|
I2C_Stop();
|
||||||
|
i2c_swapped = 1;
|
||||||
|
I2C_ReleaseBus(); // 强力总线死锁释放
|
||||||
|
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte(PCF8563_ADDR_WRITE);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
Uart_SendString("[RTC] Detected SWAPPED pin mapping (SCL=P33, SDA=P32)!\r\n");
|
||||||
|
} else {
|
||||||
|
// 恢复默认,防止干扰
|
||||||
|
i2c_swapped = 0;
|
||||||
|
Uart_SendString("[RTC-ERR] PCF8563 Offline on both normal/swapped pins!\r\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
I2C_SendByte(REG_CTRL_STATUS1);
|
||||||
|
if (!I2C_WaitAck()) return 0;
|
||||||
|
|
||||||
|
I2C_SendByte(0x00); // 正常模式,不停止芯片计数
|
||||||
|
if (!I2C_WaitAck()) return 0;
|
||||||
|
I2C_Stop();
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PCF8563_ScanDiagnostic(void)
|
||||||
|
{
|
||||||
|
u16 addr;
|
||||||
|
u8 found = 0;
|
||||||
|
|
||||||
|
Uart_SendString("[I2C-SCAN] --- 1. Scanning with default pins (SCL=P32, SDA=P33) ---\r\n");
|
||||||
|
i2c_swapped = 0;
|
||||||
|
for (addr = 0x02; addr < 0x100; addr += 2) {
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte((u8)addr);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
Uart_SendString("[I2C-SCAN] Device Found: 0x");
|
||||||
|
Uart_SendHex8((u8)addr);
|
||||||
|
Uart_SendString("\r\n");
|
||||||
|
found = 1;
|
||||||
|
}
|
||||||
|
I2C_Stop();
|
||||||
|
Delay_ms(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Uart_SendString("[I2C-SCAN] --- 2. Scanning with SWAPPED pins (SCL=P33, SDA=P32) ---\r\n");
|
||||||
|
i2c_swapped = 1;
|
||||||
|
for (addr = 0x02; addr < 0x100; addr += 2) {
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte((u8)addr);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
Uart_SendString("[I2C-SCAN] Device Found: 0x");
|
||||||
|
Uart_SendHex8((u8)addr);
|
||||||
|
Uart_SendString("\r\n");
|
||||||
|
found = 1;
|
||||||
|
}
|
||||||
|
I2C_Stop();
|
||||||
|
Delay_ms(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扫描结束后,根据 init 侦测结果重新绑定,此处临时归零
|
||||||
|
i2c_swapped = 0;
|
||||||
|
Uart_SendString("[I2C-SCAN] Dual-mode Address Scan Completed!\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从 PCF8563 读取当前时间
|
||||||
|
*/
|
||||||
|
bit PCF8563_ReadTime(u8 *hour, u8 *min, u8 *sec)
|
||||||
|
{
|
||||||
|
u8 raw_sec, raw_min, raw_hour;
|
||||||
|
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte(PCF8563_ADDR_WRITE);
|
||||||
|
if (!I2C_WaitAck()) return 0;
|
||||||
|
|
||||||
|
// 设置起始读取地址为 0x02 (REG_VL_SECONDS)
|
||||||
|
I2C_SendByte(REG_VL_SECONDS);
|
||||||
|
if (!I2C_WaitAck()) return 0;
|
||||||
|
|
||||||
|
// 重新产生 Start 读取数据
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte(PCF8563_ADDR_READ);
|
||||||
|
if (!I2C_WaitAck()) return 0;
|
||||||
|
|
||||||
|
raw_sec = I2C_ReadByte();
|
||||||
|
I2C_SendAck(1); // 发送 ACK
|
||||||
|
|
||||||
|
raw_min = I2C_ReadByte();
|
||||||
|
I2C_SendAck(1); // 发送 ACK
|
||||||
|
|
||||||
|
raw_hour = I2C_ReadByte();
|
||||||
|
I2C_SendAck(0); // 发送 NACK 结束连续读取
|
||||||
|
|
||||||
|
I2C_Stop();
|
||||||
|
|
||||||
|
// 解析 BCD 数据 (掩码屏蔽无效位)
|
||||||
|
*sec = BCD2DEC(raw_sec & 0x7F);
|
||||||
|
*min = BCD2DEC(raw_min & 0x7F);
|
||||||
|
*hour = BCD2DEC(raw_hour & 0x3F);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将时间精准写入 PCF8563 RTC 芯片
|
||||||
|
*/
|
||||||
|
bit PCF8563_WriteTime(u8 hour, u8 min, u8 sec)
|
||||||
|
{
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte(PCF8563_ADDR_WRITE);
|
||||||
|
if (!I2C_WaitAck()) return 0;
|
||||||
|
|
||||||
|
// 定位写入起始寄存器 0x02
|
||||||
|
I2C_SendByte(REG_VL_SECONDS);
|
||||||
|
if (!I2C_WaitAck()) return 0;
|
||||||
|
|
||||||
|
// 写入秒
|
||||||
|
I2C_SendByte(DEC2BCD(sec & 0x7F));
|
||||||
|
if (!I2C_WaitAck()) return 0;
|
||||||
|
|
||||||
|
// 写入分
|
||||||
|
I2C_SendByte(DEC2BCD(min & 0x7F));
|
||||||
|
if (!I2C_WaitAck()) return 0;
|
||||||
|
|
||||||
|
// 写入时
|
||||||
|
I2C_SendByte(DEC2BCD(hour & 0x3F));
|
||||||
|
if (!I2C_WaitAck()) return 0;
|
||||||
|
|
||||||
|
I2C_Stop();
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bit PCF8563_TryReadDiagnostic(void)
|
||||||
|
{
|
||||||
|
u8 val = 0;
|
||||||
|
|
||||||
|
Uart_SendString("[I2C-DIAG] --- Test 1: Trying 30us Delay (30kHz) ---\r\n");
|
||||||
|
i2c_delay_us = 30;
|
||||||
|
|
||||||
|
i2c_swapped = 0;
|
||||||
|
I2C_ReleaseBus();
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte(0xA2);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
Uart_SendString("[I2C-DIAG] (swapped=0, 30us) 0xA2 ACK OK\r\n");
|
||||||
|
I2C_SendByte(0x00);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte(0xA3);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
val = I2C_ReadByte();
|
||||||
|
I2C_SendAck(0);
|
||||||
|
I2C_Stop();
|
||||||
|
Uart_SendString("[I2C-DIAG] (swapped=0, 30us) Success! Reg 0x00=0x");
|
||||||
|
Uart_SendHex8(val);
|
||||||
|
Uart_SendString("\r\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
I2C_Stop();
|
||||||
|
|
||||||
|
i2c_swapped = 1;
|
||||||
|
I2C_ReleaseBus();
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte(0xA2);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
Uart_SendString("[I2C-DIAG] (swapped=1, 30us) 0xA2 ACK OK\r\n");
|
||||||
|
I2C_SendByte(0x00);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte(0xA3);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
val = I2C_ReadByte();
|
||||||
|
I2C_SendAck(0);
|
||||||
|
I2C_Stop();
|
||||||
|
Uart_SendString("[I2C-DIAG] (swapped=1, 30us) Success! Reg 0x00=0x");
|
||||||
|
Uart_SendHex8(val);
|
||||||
|
Uart_SendString("\r\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
I2C_Stop();
|
||||||
|
|
||||||
|
Uart_SendString("[I2C-DIAG] --- Test 2: Retrying with ultra-slow 200us Delay (2.5kHz) ---\r\n");
|
||||||
|
i2c_delay_us = 200;
|
||||||
|
|
||||||
|
i2c_swapped = 0;
|
||||||
|
I2C_ReleaseBus();
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte(0xA2);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
Uart_SendString("[I2C-DIAG] (swapped=0, 200us) 0xA2 ACK OK\r\n");
|
||||||
|
I2C_SendByte(0x00);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte(0xA3);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
val = I2C_ReadByte();
|
||||||
|
I2C_SendAck(0);
|
||||||
|
I2C_Stop();
|
||||||
|
Uart_SendString("[I2C-DIAG] (swapped=0, 200us) Success! Reg 0x00=0x");
|
||||||
|
Uart_SendHex8(val);
|
||||||
|
Uart_SendString("\r\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
I2C_Stop();
|
||||||
|
|
||||||
|
i2c_swapped = 1;
|
||||||
|
I2C_ReleaseBus();
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte(0xA2);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
Uart_SendString("[I2C-DIAG] (swapped=1, 200us) 0xA2 ACK OK\r\n");
|
||||||
|
I2C_SendByte(0x00);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte(0xA3);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
val = I2C_ReadByte();
|
||||||
|
I2C_SendAck(0);
|
||||||
|
I2C_Stop();
|
||||||
|
Uart_SendString("[I2C-DIAG] (swapped=1, 200us) Success! Reg 0x00=0x");
|
||||||
|
Uart_SendHex8(val);
|
||||||
|
Uart_SendString("\r\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
I2C_Stop();
|
||||||
|
|
||||||
|
// 恢复默认设置
|
||||||
|
i2c_swapped = 0;
|
||||||
|
i2c_delay_us = 30;
|
||||||
|
Uart_SendString("[I2C-DIAG] Both 30us and 200us diagnostic trial failed.\r\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
59
Drivers/pcf8563.h
Normal file
59
Drivers/pcf8563.h
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#ifndef __PCF8563_H__
|
||||||
|
#define __PCF8563_H__
|
||||||
|
|
||||||
|
#include "../App/config.h"
|
||||||
|
|
||||||
|
/* ====== PCF8563 器件地址与寄存器定义 ====== */
|
||||||
|
#define PCF8563_ADDR_READ 0xA3 // PCF8563 读地址 (0xA2 | 0x01)
|
||||||
|
#define PCF8563_ADDR_WRITE 0xA2 // PCF8563 写地址 (0xA2)
|
||||||
|
|
||||||
|
#define REG_CTRL_STATUS1 0x00 // 控制/状态寄存器 1
|
||||||
|
#define REG_CTRL_STATUS2 0x01 // 控制/状态寄存器 2
|
||||||
|
#define REG_VL_SECONDS 0x02 // 秒寄存器 (bit7 为 VL 保证标志)
|
||||||
|
#define REG_MINUTES 0x03 // 分钟寄存器
|
||||||
|
#define REG_HOURS 0x04 // 小时寄存器
|
||||||
|
#define REG_DAYS 0x05 // 日寄存器
|
||||||
|
#define REG_WEEKDAYS 0x06 // 星期寄存器
|
||||||
|
#define REG_MONTHS 0x07 // 月份寄存器 (bit7 为 C 世纪位)
|
||||||
|
#define REG_YEARS 0x08 // 年份寄存器
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化 PCF8563 RTC 芯片
|
||||||
|
* @details 初始化 I2C 管脚高电平,并检测芯片启动状态
|
||||||
|
* @return bit 1: 芯片响应正常, 0: 通讯异常
|
||||||
|
*/
|
||||||
|
bit PCF8563_Init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从 PCF8563 芯片读取当前时间 (时、分、秒)
|
||||||
|
* @param hour 小时指针 (0~23)
|
||||||
|
* @param min 分钟指针 (0~59)
|
||||||
|
* @param sec 秒钟指针 (0~59)
|
||||||
|
* @return bit 1: 读取成功, 0: 通讯失败
|
||||||
|
*/
|
||||||
|
bit PCF8563_ReadTime(u8 *hour, u8 *min, u8 *sec);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将指定的时间 (时、分、秒) 写入 PCF8563 芯片
|
||||||
|
* @param hour 小时 (0~23)
|
||||||
|
* @param min 分钟 (0~59)
|
||||||
|
* @param sec 秒钟 (0~59)
|
||||||
|
* @return bit 1: 写入成功, 0: 通讯失败
|
||||||
|
*/
|
||||||
|
bit PCF8563_WriteTime(u8 hour, u8 min, u8 sec);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief I2C 总线器件扫描诊断函数
|
||||||
|
* @details 逐个发送 I2C 写地址,检测总线上是否有响应的器件,并在串口打印发现的设备地址
|
||||||
|
*/
|
||||||
|
void PCF8563_ScanDiagnostic(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 尝试直接用最底层时序试读 RTC 控制寄存器并输出 ACK 调试日志
|
||||||
|
*/
|
||||||
|
bit PCF8563_TryReadDiagnostic(void);
|
||||||
|
|
||||||
|
// 动态引脚对调标志 (由 pcf8563.c 维护,0: 默认, 1: 互换)
|
||||||
|
extern volatile u8 i2c_swapped;
|
||||||
|
|
||||||
|
#endif // __PCF8563_H__
|
||||||
447
Drivers/rf.c
447
Drivers/rf.c
@@ -1,15 +1,15 @@
|
|||||||
#include "rf.h"
|
#include "rf.h"
|
||||||
#include "../App/config.h"
|
#include "../App/config.h"
|
||||||
|
#include "../App/rgb.h"
|
||||||
#include "intrins.h"
|
#include "intrins.h"
|
||||||
|
|
||||||
// ATR5179 射频前端天线开关控制引脚定义
|
// ATR5179 射频前端天线开关控制引脚定义
|
||||||
sbit RF_TX = P2^0; // V1 控制端 (1: 选通天线到发射 TX 路径)
|
sbit RF_TX = P2^0; // V1 控制端 (1: 选通天线到发射 TX 路径)
|
||||||
sbit RF_RX = P3^7; // V2 控制端 (1: 选通天线到接收 RX 路径)
|
sbit RF_RX = P3^7; // V2 控制端 (1: 选通天线到接收 RX 路径)
|
||||||
|
|
||||||
// 射频芯片数据与电源控制引脚定义
|
// 射频芯片数据与电源控制引脚定义 (根据新版 PCB 引脚对调修正)
|
||||||
sbit RF_TX_DAT = P3^6; // LT4455 发射调制数据输入引脚 (DIN)
|
sbit RF_TX_DAT = P2^1; // LR690L 发射调制数据输入引脚 (DIN) - 物理连接 Pin 22 (P2.1)
|
||||||
sbit RF_RX_DATA = P2^1; // LR690L 接收解调数据输出引脚 (DATA),连接单片机中断管脚
|
sbit RF_RX_DATA = P3^6; // LR690L 接收解调数据输出引脚 (DATA) - 物理连接 Pin 19 (P3.6)
|
||||||
sbit SHUT = P2^2; // LR690L 芯片休眠控制引脚 (1:休眠,0:工作)
|
|
||||||
|
|
||||||
// 声明外部延时及串口打印函数 (在 system.c 中定义)
|
// 声明外部延时及串口打印函数 (在 system.c 中定义)
|
||||||
extern void Delay_us(u16 us);
|
extern void Delay_us(u16 us);
|
||||||
@@ -24,23 +24,27 @@ extern void Uart_SendHex32(u32 val);
|
|||||||
extern void Uart_SendByte(u8 dat);
|
extern void Uart_SendByte(u8 dat);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 初始化无线射频芯片相关的 GPIO 引脚方向与初始状态
|
* @brief 初始化无线射频芯片相关的 GPIO 引脚方向与状态
|
||||||
*/
|
*/
|
||||||
void RF_Init(void)
|
void RF_Init(void)
|
||||||
{
|
{
|
||||||
// 1. 配置 P2.0 (RF_TX) 和 P2.2 (SHUT) 为推挽输出模式 (M1=0, M0=1)
|
// EAXFR = 1 允许访问扩展特殊功能寄存器 (XSFR)
|
||||||
// 配置 P2.1 (RF_RX_DATA) 为准双向口/输入模式 (M1=0, M0=0)
|
EAXFR = 1;
|
||||||
P2M1 &= ~((1 << 0) | (1 << 1) | (1 << 2));
|
|
||||||
P2M0 &= ~(1 << 1);
|
|
||||||
P2M0 |= ((1 << 0) | (1 << 2));
|
|
||||||
|
|
||||||
// 2. 配置 P3.6 (RF_TX_DAT) 和 P3.7 (RF_RX) 为推挽输出模式 (M1=0, M0=1)
|
// 1. 配置 P2.0 (RF_TX) 和 P2.1 (RF_TX_DAT) 为推挽输出模式 (M1=0, M0=1)
|
||||||
P3M1 &= ~((1 << 6) | (1 << 7));
|
P2M1 &= ~((1 << 0) | (1 << 1));
|
||||||
P3M0 |= ((1 << 6) | (1 << 7));
|
P2M0 |= ((1 << 0) | (1 << 1));
|
||||||
|
|
||||||
|
// 2. 配置 P3.7 (RF_RX) 和 P3.5 (SHUT) 为推挽输出模式 (M1=0, M0=1)
|
||||||
|
// 配置 P3.6 (RF_RX_DATA) 为高阻输入模式 (M1=1, M0=0)以匹配接收解调输入
|
||||||
|
P3M1 &= ~((1 << 5) | (1 << 7));
|
||||||
|
P3M1 |= (1 << 6);
|
||||||
|
P3M0 &= ~(1 << 6);
|
||||||
|
P3M0 |= ((1 << 5) | (1 << 7));
|
||||||
|
|
||||||
// 3. 设置系统默认的初始电平
|
// 3. 设置系统默认的初始电平
|
||||||
RF_TX = 0; // 断开发射天线通路
|
RF_TX = 0; // 断开发射天线通路
|
||||||
SHUT = 1; // 默认拉高 SHUT,使 LR690L 接收芯片处于低功耗休眠关闭状态
|
SHUT = 1; // 默认拉高 SHUT,使 LR690L 处于休眠关闭状态
|
||||||
RF_RX = 0; // 断开接收天线通路
|
RF_RX = 0; // 断开接收天线通路
|
||||||
RF_TX_DAT = 0; // 调制发送数据线置低
|
RF_TX_DAT = 0; // 调制发送数据线置低
|
||||||
}
|
}
|
||||||
@@ -57,138 +61,139 @@ void RF_SetMode(u8 mode)
|
|||||||
RF_RX = 0; // 关闭接收天线开关,防止外界电磁杂波串入接收电路
|
RF_RX = 0; // 关闭接收天线开关,防止外界电磁杂波串入接收电路
|
||||||
RF_TX = 0; // 关闭发射天线开关
|
RF_TX = 0; // 关闭发射天线开关
|
||||||
}
|
}
|
||||||
else if (mode == 1) // ====== 模式 1: 射频接收模式 (Rx Mode) ======
|
else if (mode == 1) // ====== 模式 1: 接收工作模式 (RX Mode) ======
|
||||||
{
|
{
|
||||||
SHUT = 0; // 拉低 SHUT,启动并使能 LR690L 接收解调芯片工作
|
SHUT = 0; // 拉低 SHUT 使能 LR690L 芯片,开始解调接收
|
||||||
RF_RX = 1; // 选通天线切换开关到 RX 路径,连通物理接收天线
|
RF_TX = 0; // 关闭发射开关
|
||||||
RF_TX = 0; // 断开 TX 发射路径,防止回环烧毁前端
|
RF_RX = 1; // 选通天线到 RX 接收路径,使能接收链路高频放大
|
||||||
}
|
}
|
||||||
else if (mode == 2) // ====== 模式 2: 射频发射模式 (Tx Mode) ======
|
else if (mode == 2) // ====== 模式 2: 发发射工作模式 (TX Mode) ======
|
||||||
{
|
{
|
||||||
SHUT = 1; // 关闭 LR690L 接收芯片,避免发射大功率波形串入接收机
|
SHUT = 1; // 接收机停机,防止发射强信号顶坏接收前端
|
||||||
RF_RX = 0; // 断开接收路径天线
|
RF_RX = 0; // 关闭接收开关
|
||||||
RF_TX = 1; // 选通天线切换开关至 TX 路径,连通物理天线进行发射
|
RF_TX = 1; // 选通天线到 TX 发射路径,使能天线发射
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 按 EV1527 载波规范单次调制发送一帧 24 位无线射频数据
|
* @brief 使用单片机硬件 Timer0 实现高精度发射电平时延控制
|
||||||
* @param addr 20 位的发射地址编码
|
* @param us 延时微秒数
|
||||||
* @param dat 4 位的命令状态码
|
* @note STC32G 在 24MHz 晶振 12T 模式下,Timer0 的 1 tick 正好等于 0.5us (2MHz)
|
||||||
* @details EV1527 是常见的无线编码格式:
|
*/
|
||||||
* - 一个基本脉冲周期设为 T ≈ 320us。
|
void RF_Delay_us(u16 us)
|
||||||
* - 同步头脉冲: 1T 高电平 (320us) + 31T 低电平 (9920us)。
|
{
|
||||||
* - 数据位 '1' (逻辑1) 编码: 3T 高电平 (960us) + 1T 低电平 (320us)。
|
u16 ticks = us * 2;
|
||||||
* - 数据位 '0' (逻辑0) 编码: 1T 高电平 (320us) + 3T 低电平 (960us)。
|
TL0 = 0;
|
||||||
|
TH0 = 0;
|
||||||
|
TR0 = 1; // 启动定时器 0
|
||||||
|
while (GetTimer0_Safe() < ticks);
|
||||||
|
TR0 = 0; // 停止定时器 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 按照 EV1527 协议单次调制发送一帧 24 位射频数据 (Sync + 24bit Data)
|
||||||
*/
|
*/
|
||||||
void EV1527_TxFrame(u32 addr, u8 dat)
|
void EV1527_TxFrame(u32 addr, u8 dat)
|
||||||
{
|
{
|
||||||
u8 i;
|
u8 i;
|
||||||
|
u32 tx_val;
|
||||||
|
|
||||||
// ====== 1. 发送同步引导脉冲 ======
|
// 合并 20 位地址与 4 位数据,成为 24 位完整 EV1527 帧
|
||||||
|
tx_val = (addr << 4) | (dat & 0x0F);
|
||||||
|
|
||||||
|
// 1. 同步头脉冲: 高电平 1T (350us) + 低电平 31T (10850us)
|
||||||
RF_TX_DAT = 1;
|
RF_TX_DAT = 1;
|
||||||
Delay_us(320); // 1T 高电平
|
RF_Delay_us(350);
|
||||||
RF_TX_DAT = 0;
|
RF_TX_DAT = 0;
|
||||||
Delay_us(9920); // 31T 低电平
|
RF_Delay_us(10850);
|
||||||
|
|
||||||
// ====== 2. 依次调制发送 20 位物理地址码 (高位 MSB 优先发送) ======
|
// 2. 依次发送 24 个数据位 (MSB First)
|
||||||
for (i = 0; i < 20; i++)
|
for (i = 0; i < 24; i++)
|
||||||
{
|
{
|
||||||
if ((addr >> (19 - i)) & 1)
|
if (tx_val & (0x800000UL >> i))
|
||||||
{
|
{
|
||||||
// 数据 bit 1: 3T高 + 1T低
|
// 逻辑 "1": 高电平 3T (1050us) + 低电平 1T (350us)
|
||||||
RF_TX_DAT = 1;
|
RF_TX_DAT = 1;
|
||||||
Delay_us(960);
|
RF_Delay_us(1050);
|
||||||
RF_TX_DAT = 0;
|
RF_TX_DAT = 0;
|
||||||
Delay_us(320);
|
RF_Delay_us(350);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 数据 bit 0: 1T高 + 3T低
|
// 逻辑 "0": 高电平 1T (350us) + 低电平 3T (1050us)
|
||||||
RF_TX_DAT = 1;
|
RF_TX_DAT = 1;
|
||||||
Delay_us(320);
|
RF_Delay_us(350);
|
||||||
RF_TX_DAT = 0;
|
RF_TX_DAT = 0;
|
||||||
Delay_us(960);
|
RF_Delay_us(1050);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ====== 3. 依次调制发送 4 位指令状态数据码 (高位 MSB 优先) ======
|
|
||||||
for (i = 0; i < 4; i++)
|
|
||||||
{
|
|
||||||
if ((dat >> (3 - i)) & 1)
|
|
||||||
{
|
|
||||||
// 数据 bit 1: 3T高 + 1T低
|
|
||||||
RF_TX_DAT = 1;
|
|
||||||
Delay_us(960);
|
|
||||||
RF_TX_DAT = 0;
|
|
||||||
Delay_us(320);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 数据 bit 0: 1T高 + 3T低
|
|
||||||
RF_TX_DAT = 1;
|
|
||||||
Delay_us(320);
|
|
||||||
RF_TX_DAT = 0;
|
|
||||||
Delay_us(960);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 循环发射多帧无线 EV1527 信号以保证数据通信的完整性
|
* @brief 调制发射 25 帧 EV1527 射频同步电平信号以确保接收侧可靠触发
|
||||||
* @param addr 20位源地址
|
|
||||||
* @param dat 4位命令码
|
|
||||||
*/
|
*/
|
||||||
void EV1527_Transmit(u32 addr, u8 dat)
|
void EV1527_Transmit(u32 addr, u8 dat)
|
||||||
{
|
{
|
||||||
u8 r;
|
u8 r;
|
||||||
// 循环发射 25 次相同的射频帧 (累计耗时约 340 毫秒),抵御外界瞬间同频杂波干扰
|
bit ea_bak = EA; // 备份并屏蔽全局中断,确保发射高低电平周期的绝对纯净与防抖
|
||||||
for (r = 0; r < 25; r++)
|
EA = 0;
|
||||||
|
|
||||||
|
RF_SetMode(2); // 切换为发射状态,接通天线与射频模块通道
|
||||||
|
Delay_ms(5); // 稍作延时使射频通路稳定
|
||||||
|
|
||||||
|
// 连续发射 25 帧以保证接收设备顺利锁定并解码
|
||||||
|
for (r = 0; r < 25; r++)
|
||||||
{
|
{
|
||||||
EV1527_TxFrame(addr, dat);
|
EV1527_TxFrame(addr, dat);
|
||||||
|
// 去除帧与帧之间可能导致波形不连续的 Delay_ms
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RF_TX_DAT = 0; // 结束发射,拉低数据脚
|
||||||
|
RF_SetMode(1); // 恢复为常态后台接收模式
|
||||||
|
|
||||||
|
EA = ea_bak; // 还原全局中断开关状态
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 使用定时器 0 (Timer0) 高精度的测量引脚指定电平状态的持续宽度时间
|
* @brief 获取特定电平引脚状态的持续微秒时间 (利用 Timer0 硬件测量)
|
||||||
* @param state 待测电平状态 (1-测量高电平持续宽度, 0-测量低电平持续宽度)
|
|
||||||
* @param timeout_us 最大等待超时微秒数上限,超时自动退出防锁死
|
|
||||||
* @return u16 最终测量的实际电平持续微秒数。若发生超时则直接返回 0。
|
|
||||||
*/
|
*/
|
||||||
u16 GetPulseDuration(u8 state, u16 timeout_us)
|
u16 GetPulseDuration(u8 state, u16 timeout_us)
|
||||||
{
|
{
|
||||||
// 根据系统晶振频率计算在 12T 模式下目标超时的 Timer0 Tick 数值上限
|
u16 timeout_ticks;
|
||||||
u16 timeout_ticks = (u16)((u32)timeout_us * (MAIN_Fosc / 1000000UL) / 12UL);
|
|
||||||
TL0 = 0; // 定时器 0 计数值清零
|
|
||||||
TH0 = 0;
|
|
||||||
TR0 = 1; // 启动定时器 0 硬件计数
|
|
||||||
|
|
||||||
// 轮询引脚:只要电平仍旧等于目标待测状态,就继续保持循环
|
// 强制关闭 Timer0 计数器,并将其计数值清零复位
|
||||||
|
TR0 = 0;
|
||||||
|
TMOD &= 0xF0; // 设置 Timer0 寄存器为模式 0 (16位自动重载)
|
||||||
|
TL0 = 0x00;
|
||||||
|
TH0 = 0x00;
|
||||||
|
|
||||||
|
// 换算 16 位定时器所需的最大 ticks 超时值 (在 1T 模式 24MHz 下,1 Tick = 1 / 24M = 0.0416us)
|
||||||
|
// 但此系统 Timer0 默认配置为 12T 模式 (1 Tick = 12 / 24MHz = 0.5us)
|
||||||
|
timeout_ticks = (u16)((u32)timeout_us * (MAIN_Fosc / 1000000UL) / 12UL);
|
||||||
|
|
||||||
|
TR0 = 1; // 启动 Timer0 硬件计数器
|
||||||
|
|
||||||
|
// 根据要捕获的电平状态进行阻塞判断,直到电平发生翻转或计数超时
|
||||||
while (RF_RX_DATA == state)
|
while (RF_RX_DATA == state)
|
||||||
{
|
{
|
||||||
// 软件防卡死判定:如果当前计数值超过了设定的最大允许超时 Tick 值
|
|
||||||
if (GetTimer0_Safe() > timeout_ticks)
|
if (GetTimer0_Safe() > timeout_ticks)
|
||||||
{
|
{
|
||||||
TR0 = 0; // 关断定时器
|
TR0 = 0;
|
||||||
return 0; // 判定超时,返回 0
|
return 0; // 判定超时,返回 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TR0 = 0; // 关断定时器
|
TR0 = 0; // 关断定时器
|
||||||
|
|
||||||
// 将最终读取的 Timer0 硬件 Tick 数值,重新换算成微秒数返回 (1 Tick = 12 / 24MHz = 0.5us)
|
|
||||||
return (u16)((u32)GetTimer0_Safe() * 12UL / (MAIN_Fosc / 1000000UL));
|
return (u16)((u32)GetTimer0_Safe() * 12UL / (MAIN_Fosc / 1000000UL));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 模拟射频信号全局变量定义
|
||||||
|
volatile u32 mock_rx_addr = 0;
|
||||||
|
volatile u8 mock_rx_data = 0;
|
||||||
|
volatile bit mock_rf_ready = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 软件自适应解码接收一帧无线 EV1527 信号
|
* @brief 阻塞式检测并解码一个合法的 EV1527 射频信号数据帧
|
||||||
* @param out_addr 解码成功后,用于回传物理地址高 20 位的指针
|
|
||||||
* @param out_data 解码成功后,用于回传指令数据低 4 位的指针
|
|
||||||
* @return bit 1-解码成功且校验通过,0-捕获失败或脉冲畸变
|
|
||||||
* @note 核心算法:
|
|
||||||
* 1. 等待并寻找合法的同步引导头脉冲 (1T高电平 + 31T低电平,其中低/高比率约为 31,本程序允许比率在 15~48 内变化以抗物理畸变)。
|
|
||||||
* 2. 依据捕获的同步低电平宽度估算基准周期 T = low_time / 31。
|
|
||||||
* 3. 随后依次高精度捕获 24 个数据位的高电平和低电平时间。
|
|
||||||
* 4. 自适应决策:对于每个 bit,如果其高电平时间 raw_h > 低电平时间 raw_l,则判定该位为 1;否则判定该位为 0。
|
|
||||||
*/
|
*/
|
||||||
bit EV1527_Decode(u32 *out_addr, u8 *out_data)
|
bit EV1527_Decode(u32 *out_addr, u8 *out_data)
|
||||||
{
|
{
|
||||||
@@ -197,13 +202,20 @@ bit EV1527_Decode(u32 *out_addr, u8 *out_data)
|
|||||||
u32 addr = 0;
|
u32 addr = 0;
|
||||||
u8 dat = 0;
|
u8 dat = 0;
|
||||||
u16 T;
|
u16 T;
|
||||||
u16 idata raw_h[24]; // 高电平时间寄存数组 (置于 idata 区)
|
u16 idata raw_h[24];
|
||||||
u16 idata raw_l[24]; // 低电平时间寄存数组
|
u16 idata raw_l[24];
|
||||||
|
|
||||||
// ====== 1. 同步头引导脉冲捕获 (定位上升沿) ======
|
// 检查模拟射频触发
|
||||||
|
if (mock_rf_ready) {
|
||||||
|
mock_rf_ready = 0;
|
||||||
|
*out_addr = mock_rx_addr;
|
||||||
|
*out_data = mock_rx_data;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====== 1. 同步头引导脉冲捕获 ======
|
||||||
{
|
{
|
||||||
u16 wait_cnt = 0;
|
u16 wait_cnt = 0;
|
||||||
// 如果当前射频解调输出是高电平,等待其变低
|
|
||||||
while (RF_RX_DATA == 1)
|
while (RF_RX_DATA == 1)
|
||||||
{
|
{
|
||||||
Delay_us(5);
|
Delay_us(5);
|
||||||
@@ -212,7 +224,6 @@ bit EV1527_Decode(u32 *out_addr, u8 *out_data)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 当引脚维持在低电平后,阻塞等待它跳变为高电平(捕获到同步引导信号的上升沿)
|
|
||||||
wait_cnt = 0;
|
wait_cnt = 0;
|
||||||
while (RF_RX_DATA == 0)
|
while (RF_RX_DATA == 0)
|
||||||
{
|
{
|
||||||
@@ -223,123 +234,144 @@ bit EV1527_Decode(u32 *out_addr, u8 *out_data)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 捕获引导头高电平时间 (放宽测量阈值上限至 2500 微秒)
|
// 捕获引导头高电平时间
|
||||||
high_time = GetPulseDuration(1, 2500);
|
high_time = GetPulseDuration(1, 2500);
|
||||||
if (high_time < 50 || high_time > 2500) {
|
if (high_time < 50 || high_time > 2500) {
|
||||||
if (high_time > 10) { // 剔除高频毛刺噪声,防止调试串口满载
|
|
||||||
Uart_SendString("Sync H Err: ");
|
|
||||||
Uart_SendHex16(high_time);
|
|
||||||
Uart_SendString("us\r\n");
|
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 捕获引导头后续的 31T 低电平时间 (设定上限 60 毫秒)
|
// 捕获引导头后续的 31T 低电平时间
|
||||||
low_time = GetPulseDuration(0, 60000);
|
low_time = GetPulseDuration(0, 60000);
|
||||||
if (low_time < 1500 || low_time > 60000) {
|
if (low_time < 1500 || low_time > 60000) {
|
||||||
Uart_SendString("Sync L Err H=");
|
|
||||||
Uart_SendHex16(high_time);
|
|
||||||
Uart_SendString(" L=");
|
|
||||||
Uart_SendHex16(low_time);
|
|
||||||
Uart_SendString("us\r\n");
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算低电平与高电平的比值,以检验是否满足 EV1527 的 31:1 特征
|
// 同步引导脉冲的基准时间 T 计算 (引导脉冲高电平宽度即为 1T)
|
||||||
{
|
T = high_time;
|
||||||
u32 ratio = (u32)low_time / high_time;
|
if (T < 100 || T > 800) return 0; // 自适应范围限幅: 100us ~ 800us
|
||||||
if (ratio < 15 || ratio > 48) { // 允许 50% 左右的空气无线传输畸变误差
|
|
||||||
Uart_SendString("Sync Ratio Err H=");
|
|
||||||
Uart_SendHex16(high_time);
|
|
||||||
Uart_SendString(" L=");
|
|
||||||
Uart_SendHex16(low_time);
|
|
||||||
Uart_SendString(" R=");
|
|
||||||
Uart_SendHex16((u16)ratio);
|
|
||||||
Uart_SendString("\r\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 自适应估算出基准脉冲时值 T
|
// ====== 2. 依次解析 24 个数据位脉宽 ======
|
||||||
T = low_time / 31;
|
|
||||||
if (T == 0) T = 1; // 零安全防护
|
|
||||||
|
|
||||||
// ====== 2. 依次采集接下来的 24 个数据 bit 脉冲 ======
|
|
||||||
for (i = 0; i < 24; i++)
|
for (i = 0; i < 24; i++)
|
||||||
{
|
{
|
||||||
u16 wait_cnt = 0;
|
raw_h[i] = GetPulseDuration(1, 2000);
|
||||||
// 等待下一个数据位跳变产生的上升沿 (低电平变高)
|
if (raw_h[i] == 0) return 0;
|
||||||
while (RF_RX_DATA == 0)
|
|
||||||
{
|
|
||||||
Delay_us(5);
|
|
||||||
wait_cnt++;
|
|
||||||
if (wait_cnt > 1000) // 5ms 超时判定,防无线信号中断卡死
|
|
||||||
{
|
|
||||||
return 0; // 捕获失败
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 精密测算本位的高电平与低电平宽度
|
|
||||||
raw_h[i] = GetPulseDuration(1, 5000);
|
|
||||||
if (raw_h[i] == 0) return 0; // 超时错误直接退出
|
|
||||||
|
|
||||||
raw_l[i] = GetPulseDuration(0, 5000);
|
raw_l[i] = GetPulseDuration(0, 2000);
|
||||||
if (raw_l[i] == 0) return 0;
|
if (raw_l[i] == 0) return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====== 3. 串口输出捕获的时序参数 ======
|
// ====== 3. 双端比值解调判定数据内容 ======
|
||||||
Uart_SendString("Captured: Sync H=");
|
|
||||||
Uart_SendHex16(high_time);
|
|
||||||
Uart_SendString("us, L=");
|
|
||||||
Uart_SendHex16(low_time);
|
|
||||||
Uart_SendString("us, T=");
|
|
||||||
Uart_SendHex16(T);
|
|
||||||
Uart_SendString("us\r\n");
|
|
||||||
|
|
||||||
// ====== 4. 自适应决策:组合成 24 位完整射频数据帧 ======
|
|
||||||
for (i = 0; i < 24; i++)
|
for (i = 0; i < 24; i++)
|
||||||
{
|
{
|
||||||
// 比较本位高低电平持续宽度:
|
u16 total = raw_h[i] + raw_l[i];
|
||||||
// 如果高电平大于低电平,说明宽脉冲在高,判定该位为 1 (3T高+1T低)。
|
if (total < T * 3 || total > T * 6) return 0; // 单个数据位周期必须在 3T~6T 之间
|
||||||
// 否则高电平短,判定为 0 (1T高+3T低)。
|
|
||||||
if (raw_h[i] > raw_l[i])
|
if (raw_h[i] > raw_l[i] * 2)
|
||||||
{
|
{
|
||||||
addr = (addr << 1) | 1; // 写入 24 位物理地址与状态数据相结合的最终值
|
// 高电平明显长于低电平,代表逻辑 "1"
|
||||||
|
if (i < 20)
|
||||||
|
addr = (addr << 1) | 1;
|
||||||
|
else
|
||||||
|
dat = (dat << 1) | 1;
|
||||||
|
}
|
||||||
|
else if (raw_l[i] > raw_h[i] * 2)
|
||||||
|
{
|
||||||
|
// 低电平明显长于高电平,代表逻辑 "0"
|
||||||
|
if (i < 20)
|
||||||
|
addr = (addr << 1);
|
||||||
|
else
|
||||||
|
dat = (dat << 1);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
addr = (addr << 1);
|
return 0; // 电平脉冲比值不合规,判定数据帧畸变,解码作废
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*out_addr = addr; // 输出包含状态数据的 24 位完整射频编码作为传感器唯一 ID
|
// 校验无误,输出解调数据
|
||||||
*out_data = (u8)(addr & 0x0F); // 输出低 4 位作为指令/状态,保持调用兼容性
|
*out_addr = addr;
|
||||||
|
*out_data = dat;
|
||||||
// 串口打印解码最终结果
|
return 1;
|
||||||
Uart_SendString("Decoded 24bit ADDR: 0x");
|
|
||||||
Uart_SendHex32(addr);
|
|
||||||
Uart_SendString("\r\n");
|
|
||||||
|
|
||||||
return 1; // 成功解析
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 软硬件环路自检回环测试
|
* @brief RF 收发诊断测试功能 (支持发送与接收监听打印)
|
||||||
* @details 将发射引脚置位并同时采样接收引脚,用于测试收发芯片控制连通性。
|
*/
|
||||||
|
void RF_DiagnosticMode(char choice)
|
||||||
|
{
|
||||||
|
if (choice == 't')
|
||||||
|
{
|
||||||
|
u8 i;
|
||||||
|
Uart_SendString("=== RF TX Test: Transmitting 20 test frames... ===\r\n");
|
||||||
|
// 开启发射模式并配置天线开关
|
||||||
|
RF_SetMode(2);
|
||||||
|
Delay_ms(5);
|
||||||
|
|
||||||
|
for (i = 0; i < 20; i++)
|
||||||
|
{
|
||||||
|
// 连续发送测试包,每次发送伴随马达短震及指示灯闪烁
|
||||||
|
MOTOR = 1;
|
||||||
|
RGB_Send(0, 150, 0, 0, 150, 0); // 绿光闪烁
|
||||||
|
EV1527_TxFrame(0x37A86UL, 0x01);
|
||||||
|
MOTOR = 0;
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0);
|
||||||
|
|
||||||
|
Delay_ms(150); // 帧间隔
|
||||||
|
}
|
||||||
|
RF_TX_DAT = 0;
|
||||||
|
RF_SetMode(1); // 重新恢复接收模式
|
||||||
|
Uart_SendString("[RF] Transmit Done!\r\n");
|
||||||
|
}
|
||||||
|
else if (choice == 'r')
|
||||||
|
{
|
||||||
|
u16 wait_ms;
|
||||||
|
u32 rx_addr;
|
||||||
|
u8 rx_data;
|
||||||
|
|
||||||
|
Uart_SendString("=== RF RX Test: Listening for 5 seconds... ===\r\n");
|
||||||
|
RF_SetMode(1); // 配置进入接收模式并使能天线
|
||||||
|
Delay_ms(10);
|
||||||
|
|
||||||
|
// 5秒的非阻塞监听
|
||||||
|
for (wait_ms = 0; wait_ms < 5000; wait_ms++)
|
||||||
|
{
|
||||||
|
if (EV1527_Decode(&rx_addr, &rx_data))
|
||||||
|
{
|
||||||
|
Uart_SendString("[RF] Decoded successfully! Addr: 0x");
|
||||||
|
Uart_SendHex32(rx_addr);
|
||||||
|
Uart_SendString(", Cmd/Data: ");
|
||||||
|
Uart_SendHex8(rx_data);
|
||||||
|
Uart_SendString("\r\n");
|
||||||
|
|
||||||
|
// 收到信号后马达震动并亮青色灯提醒
|
||||||
|
MOTOR = 1;
|
||||||
|
RGB_Send(0, 150, 150, 0, 150, 150);
|
||||||
|
Delay_ms(100);
|
||||||
|
MOTOR = 0;
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
Delay_ms(1);
|
||||||
|
}
|
||||||
|
Uart_SendString("=== RF RX Test End ===\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 电气回环自检测试 (物理短路 P2.1 与 P3.6 自检)
|
||||||
|
* @details 用户通过短路 P2.1 和 P3.6 来实现 GPIO 的物理连通性自检
|
||||||
*/
|
*/
|
||||||
void Loopback_Test(void)
|
void Loopback_Test(void)
|
||||||
{
|
{
|
||||||
u16 match_count = 0;
|
u16 match_count = 0;
|
||||||
u16 i;
|
u16 i;
|
||||||
|
|
||||||
SHUT = 0; // 强行开启接收芯片供电
|
EAXFR = 1;
|
||||||
RF_RX = 1; // 选通天线到 RX
|
RF_SetMode(1); // 开启接收芯片
|
||||||
RF_TX = 0; // 发送引脚隔离
|
Delay_ms(100);
|
||||||
Delay_ms(100);
|
|
||||||
|
|
||||||
Uart_SendString("Starting RF Loopback Self-Test...\r\n");
|
Uart_SendString("Starting RF Hardware Loopback Test (P2.1 -> P3.6)...\r\n");
|
||||||
|
Uart_SendString("Please use a metal tweezers to short-circuit P2.1 (Pin 22) and P3.6 (Pin 19)!\r\n");
|
||||||
|
|
||||||
// 高频翻转模拟发送引脚,同步读取接收解调出的管脚信号,统计匹配率
|
|
||||||
for (i = 0; i < 100; i++)
|
for (i = 0; i < 100; i++)
|
||||||
{
|
{
|
||||||
RF_TX_DAT = 1;
|
RF_TX_DAT = 1;
|
||||||
@@ -357,69 +389,12 @@ void Loopback_Test(void)
|
|||||||
|
|
||||||
if (match_count > 150)
|
if (match_count > 150)
|
||||||
{
|
{
|
||||||
Uart_SendString("Result: PASS! Transmitter is emitting RF, and Receiver is picking it up!\r\n");
|
Uart_SendString("Result: PASS! Pin P2.1 and P3.6 are electrical connected successfully!\r\n");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Uart_SendString("Result: FAIL! No RF signal detected. Check hardware.\r\n");
|
Uart_SendString("Result: FAIL! GPIO coupling check failed. Short-circuit the pins and test again.\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
SHUT = 1; // 关闭接收芯片
|
RF_SetMode(0); // 关断射频
|
||||||
RF_RX = 0; // 断开接收通道
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 统计接收引脚在 5 秒内的电平翻转次数,用以评估物理噪声污染度或管脚短路
|
|
||||||
*/
|
|
||||||
void RF_Test_Pin(void)
|
|
||||||
{
|
|
||||||
u8 i;
|
|
||||||
u16 j;
|
|
||||||
u16 transitions = 0;
|
|
||||||
bit last_state = RF_RX_DATA;
|
|
||||||
|
|
||||||
Uart_SendString("=== RF RX Pin Data Test Start (5 seconds) ===\r\n");
|
|
||||||
Uart_SendString("SHUT pin state: ");
|
|
||||||
Uart_SendByte(SHUT ? '1' : '0');
|
|
||||||
Uart_SendString("\r\n");
|
|
||||||
|
|
||||||
// 强制设为接收模式
|
|
||||||
RF_SetMode(1);
|
|
||||||
Delay_ms(100);
|
|
||||||
|
|
||||||
Uart_SendString("SHUT pin state (after RF_SetMode(1)): ");
|
|
||||||
Uart_SendByte(SHUT ? '1' : '0');
|
|
||||||
Uart_SendString("\r\n");
|
|
||||||
Uart_SendString("Please trigger your 433MHz transmitter now...\r\n");
|
|
||||||
|
|
||||||
// 连续进行 5 秒的高频电平抖动采样
|
|
||||||
for (i = 0; i < 5; i++)
|
|
||||||
{
|
|
||||||
transitions = 0;
|
|
||||||
// 1秒循环
|
|
||||||
for (j = 0; j < 1000; j++)
|
|
||||||
{
|
|
||||||
u8 k;
|
|
||||||
// 每次内循环进行 5 次采样,每次间隔 200us
|
|
||||||
for (k = 0; k < 5; k++)
|
|
||||||
{
|
|
||||||
bit current_state = RF_RX_DATA;
|
|
||||||
if (current_state != last_state)
|
|
||||||
{
|
|
||||||
transitions++; // 统计电平边沿翻转次数
|
|
||||||
last_state = current_state;
|
|
||||||
}
|
|
||||||
Delay_us(200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 输出本秒的电平跳转总数,若非发射期该数值很大,表明空中有丰富噪声噪底跳变,属接收机正常表现
|
|
||||||
Uart_SendString("Sampling... Transitions in last 1s: ");
|
|
||||||
Uart_SendHex16(transitions);
|
|
||||||
Uart_SendString(", current pin value: ");
|
|
||||||
Uart_SendByte(RF_RX_DATA ? '1' : '0');
|
|
||||||
Uart_SendString("\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
Uart_SendString("=== RF Pin Test End ===\r\n");
|
|
||||||
}
|
}
|
||||||
|
|||||||
14
Drivers/rf.h
14
Drivers/rf.h
@@ -52,18 +52,18 @@ u16 GetPulseDuration(u8 state, u16 timeout_us);
|
|||||||
* @param out_addr 输出参数指针:若解码成功,将写入解析得到的 20 位发送地址 (即 24 位帧除去 4 位数据位后的剩余高 20 位)
|
* @param out_addr 输出参数指针:若解码成功,将写入解析得到的 20 位发送地址 (即 24 位帧除去 4 位数据位后的剩余高 20 位)
|
||||||
* @param out_data 输出参数指针:若解码成功,将写入解析出的 4 位命令状态数据码 (例如报警/SOS等)
|
* @param out_data 输出参数指针:若解码成功,将写入解析出的 4 位命令状态数据码 (例如报警/SOS等)
|
||||||
* @return bit 1-成功解析出一帧有效数据;0-当前未捕获到合法脉冲或校验失败
|
* @return bit 1-成功解析出一帧有效数据;0-当前未捕获到合法脉冲或校验失败
|
||||||
* @note 此函数通过对 P2.1 (RF_RX_DATA) 输入脉冲宽度进行软件精密测量,定位同步头,依次采集 24 个数据位,并进行 3 次重复性去噪校验。
|
* @note 此函数通过对 P3.6 (RF_RX_DATA) 输入脉冲宽度进行软件精密测量,定位同步头,依次采集 24 个数据位,并进行 3 次重复性去噪校验。
|
||||||
*/
|
*/
|
||||||
bit EV1527_Decode(u32 *out_addr, u8 *out_data);
|
bit EV1527_Decode(u32 *out_addr, u8 *out_data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 串口与射频功能的回环测试函数 (调试使用)
|
* @brief RF 收发诊断测试功能 (支持发送与接收监听打印)
|
||||||
|
*/
|
||||||
|
void RF_DiagnosticMode(char choice);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 电气回环自检测试 (物理短路 P2.1 与 P3.6 自检)
|
||||||
*/
|
*/
|
||||||
void Loopback_Test(void);
|
void Loopback_Test(void);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 射频控制引脚的高低电平输出极性测试 (硬件排查使用)
|
|
||||||
*/
|
|
||||||
void RF_Test_Pin(void);
|
|
||||||
|
|
||||||
#endif // __RF_H__
|
#endif // __RF_H__
|
||||||
|
|||||||
21900
Hardware/text.kicad_pcb
21900
Hardware/text.kicad_pcb
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"board": {
|
"board": {
|
||||||
"active_layer": 0,
|
"active_layer": 2,
|
||||||
"active_layer_preset": "",
|
"active_layer_preset": "Back Layers",
|
||||||
"auto_track_width": true,
|
"auto_track_width": true,
|
||||||
"hidden_netclasses": [],
|
"hidden_netclasses": [],
|
||||||
"hidden_nets": [
|
"hidden_nets": [
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
"board_outline_area",
|
"board_outline_area",
|
||||||
"ly_points"
|
"ly_points"
|
||||||
],
|
],
|
||||||
"visible_layers": "ffffffff_ffffffff_ffffffff_fffffffb",
|
"visible_layers": "ffffffff_ffffffff_ffffffff_ffffffff",
|
||||||
"zone_display_mode": 0
|
"zone_display_mode": 0
|
||||||
},
|
},
|
||||||
"git": {
|
"git": {
|
||||||
|
|||||||
@@ -546,7 +546,7 @@
|
|||||||
"last_paths": {
|
"last_paths": {
|
||||||
"idf": "",
|
"idf": "",
|
||||||
"netlist": "",
|
"netlist": "",
|
||||||
"plot": "",
|
"plot": "新建文件夹/",
|
||||||
"specctra_dsn": "",
|
"specctra_dsn": "",
|
||||||
"vrml": ""
|
"vrml": ""
|
||||||
},
|
},
|
||||||
@@ -714,7 +714,7 @@
|
|||||||
"uuid": "72df0bee-e55e-4d81-989a-168573031a0a"
|
"uuid": "72df0bee-e55e-4d81-989a-168573031a0a"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"used_designators": "#PWR59,U15",
|
"used_designators": "",
|
||||||
"variants": []
|
"variants": []
|
||||||
},
|
},
|
||||||
"sheets": [
|
"sheets": [
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
|||||||
{"hostname":"DESKTOP-0S8JI7H","username":"xtell"}
|
|
||||||
7
woke-order/wo-00需要完成的功能
Normal file
7
woke-order/wo-00需要完成的功能
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
1 屏幕显示
|
||||||
|
2 按键响应
|
||||||
|
3 射频收发
|
||||||
|
4 马达响应
|
||||||
|
5 RGB控制
|
||||||
|
6 电池电压检测和充电
|
||||||
|
7 板子各状态下的功耗 。
|
||||||
72
woke-order/wo-01
Normal file
72
woke-order/wo-01
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# 📋 工单 wo-01: ADC按键检测、PWR_HOLD开关机控制、P1.3电池电量检测与 PCF8563 硬件 RTC 整合
|
||||||
|
|
||||||
|
**工单编号**: WO-01
|
||||||
|
**创建时间**: 2026-07-27
|
||||||
|
**模块组件**: 按键驱动 (ADC Key) / 电源管理 (Power Control) / 电池检测 (Battery ADC) / 实时时钟 (PCF8563 RTC) / 待机应用 (ClockApp & UI)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📌 一、 需求概述与按阶段分步规划
|
||||||
|
|
||||||
|
根据项目开发优先级与依赖关系,按以下四个顺序阶段分步完成:
|
||||||
|
|
||||||
|
### 阶段 1: P0.3 ADC 按键检测
|
||||||
|
- **硬件引脚**: `P0.3` 对应 STC32G 硬件 `ADC3` 通道。
|
||||||
|
- **电平特征**: 开机常态下 `KEY_DOWN` 节点电压约为 **2.0V**(未按下),按键按下后电压升至约为 **3.7V**。
|
||||||
|
- **算法与逻辑**:
|
||||||
|
- 配置 ADC 模块,引入滑动窗口均值/中值滤波消抖。
|
||||||
|
- 按键判定:未按下区间 (1.5V ~ 2.5V),按下区间 (3.2V ~ 4.2V)。
|
||||||
|
- 保留并兼容原本 KEY_DOWN 的短按功能(消抖释放后触发 `KEY_EVENT_DOWN_CLICK` 供 UI 菜单与时间编辑下翻数值)。
|
||||||
|
|
||||||
|
### 阶段 2: PWR_HOLD 开关机控制与上电自检
|
||||||
|
- **硬件引脚**: `PWR_HOLD` 控制引脚(默认分配为 `P0.0`)。
|
||||||
|
- **开关机流程**:
|
||||||
|
- **开机锁存**: 按下开机键硬件通电后,单片机在入口 `main()` **第一时间拉高 `PWR_HOLD` 引脚**,锁存电源保持供电。
|
||||||
|
- **长按关机**: 开机状态下长按 KEY_DOWN 按键 1 秒以上,触发关机流程 `KEY_EVENT_POWER_OFF`,拉低 `PWR_HOLD` 切断总电源。
|
||||||
|
- **上电自检 (Self-Test)**:
|
||||||
|
- 启动阶段自动执行硬件自检(电源锁存状态、ADC 采样功能、串口通信校验等),并在串口 1 输出完整的格式化自检报告。
|
||||||
|
|
||||||
|
### 阶段 3: P1.3 电池电压检测与真实电量显示
|
||||||
|
- **硬件引脚**: `P1.3` 对应 STC32G 硬件 `ADC11` 通道(电池分压检测脚)。
|
||||||
|
- **计算与转换**:
|
||||||
|
- 读取 ADC11 通道计算电池输入电压 $V_{bat}$。
|
||||||
|
- 将电池电压(锂电池 3.3V ~ 4.2V 范围)线性/分段映射转换为 **0% ~ 100%** 百分比电量。
|
||||||
|
- **UI 真实电量刷新**:
|
||||||
|
- 重构 `ui.c` 的 `UI_ShowClockPage` 及待机界面电量渲染代码,替换原写死固定的静态 "85%" 字符。
|
||||||
|
- 动态显示真实电池百分比(如 "95%")及绘制对应的电量电池柱位图。
|
||||||
|
|
||||||
|
### 阶段 4: PCF8563 (D8563) 硬件 RTC 深度整合
|
||||||
|
- **硬件连接**: PCF8563 RTC 芯片通过 I2C 总线连接至 STC32G (`P3.2` -> SCL, `P3.3` -> SDA)。
|
||||||
|
- **深度整合**:
|
||||||
|
- **开机/唤醒即读**: 系统开机或从低功耗休眠唤醒时,自动通过 I2C 读取 PCF8563 芯片的时间,更新系统全局时钟变量。
|
||||||
|
- **前台编辑直接写入**: 在 `ClockApp` 编辑模式下按确认键保存时,同步将最新时间写入 PCF8563 芯片寄存器中,实现真正的硬件级掉电时间保存。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ 二、 硬件引脚配置汇总
|
||||||
|
|
||||||
|
| 功能名称 | 关联引脚 | 端口模式配置 | 采样/控制参数 |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| **KEY_DOWN** | `P0.3` / ADC3 | 高阻输入 | 常态 **2.0V**;按下 **3.7V**;消抖 50ms |
|
||||||
|
| **PWR_HOLD** | `P0.0` (可配置) | 推挽输出 | 上电第一时间置 **高电平 (1)**;关机置 **低电平 (0)** |
|
||||||
|
| **BATT_ADC** | `P1.3` / ADC11 | 高阻输入 | 电池分压输入,计算 3.3V(0%) ~ 4.2V(100%) 电量 |
|
||||||
|
| **RTC_SCL** | `P3.2` | 准双向 / 模拟 I2C | PCF8563 SCL 时钟线 |
|
||||||
|
| **RTC_SDA** | `P3.3` | 准双向 / 模拟 I2C | PCF8563 SDA 数据线 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 三、 分阶段验收标准
|
||||||
|
|
||||||
|
1. **阶段 1 验收 (ADC按键)**:
|
||||||
|
- [ ] 串口实时打印 P0.3 ADC 电压,常态 2.0V 不误触发。
|
||||||
|
- [ ] 按下 3.7V 稳定识别,短按能够正常执行 UI 菜单下翻和数值减小。
|
||||||
|
2. **阶段 2 验收 (开关机与自检)**:
|
||||||
|
- [ ] 长按按键 >1s 开机,系统完成自检后松开按键,`PWR_HOLD` 持续高电平保持供电。
|
||||||
|
- [ ] 开机状态下长按按键 >1s,触发关机提示后 `PWR_HOLD` 拉低,系统断电。
|
||||||
|
- [ ] 开机串口打印完整自检日志。
|
||||||
|
3. **阶段 3 验收 (P1.3电池检测与UI)**:
|
||||||
|
- [ ] 可通过 P1.3 ADC 测量准确计算实际电池电压。
|
||||||
|
- [ ] 待机 UI 屏幕上实时绘制正确的百分比 (0%~100%) 与对应电量电池图标。
|
||||||
|
4. **阶段 4 验收 (PCF8563 RTC)**:
|
||||||
|
- [ ] 开机从 PCF8563 读取硬件时间。
|
||||||
|
- [ ] 前台编辑保存后直接同步写入 PCF8563 芯片,掉电重启时间不丢失。
|
||||||
75
woke-order/wo-02
Normal file
75
woke-order/wo-02
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# 📋 工单 wo-02: 改动总结与硬件调试归档
|
||||||
|
|
||||||
|
**工单编号**: WO-02
|
||||||
|
**归档时间**: 2026-07-28
|
||||||
|
**处理状态**: 已完成 (Completed)
|
||||||
|
**涉及模块**: 电池检测 (Battery ADC) / 按键驱动 (ADC Key) / 实时时钟 (PCF8563 RTC) / 系统自检 (Self-Test)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📌 一、 软件改动与底层逻辑修正
|
||||||
|
|
||||||
|
在本次工单调试执行过程中,针对硬件表现的差异与异常,对底层驱动及自检模块进行了如下修正:
|
||||||
|
|
||||||
|
### 1. 电池电压检测与电量百分比校准 (对应阶段 3)
|
||||||
|
* **公式校准与修正**:
|
||||||
|
* 物理硬件装配实际为:上拉 $10\text{k}\Omega$,下拉 $22\text{k}\Omega$(分压比为 $22/32$)。
|
||||||
|
* 经用户万用表与 ADC 联合实测:当电池电压为 $4.0\text{V}$ (4000mV) 时,分压点物理电压为 $2.7\text{V}$,单片机读出的原始 ADC 过滤值为 `2357`。
|
||||||
|
* 为了消除 LDO 压降、系统参考电压偏差等多重硬件误差,引入经验公式替换原先的理论换算公式:
|
||||||
|
$$\text{Voltage (mV)} = \frac{\text{Raw\_ADC} \times 4000}{2357}$$
|
||||||
|
* 该换算将电池准确映射至 `3.3V (0%) ~ 4.2V (100%)`。在 [adc.c](file:///c:/Users/xtell/Downloads/stc32g128k-master%20(1)/stc32g128k/Drivers/adc.c) 中已完全部署,并在前台时钟界面和自检中完美实时呈现。
|
||||||
|
|
||||||
|
### 2. ADC 按键与电池通道互换 (对应阶段 1 & 2)
|
||||||
|
* **故障定位**:
|
||||||
|
* 物理走线中,`P0.3` (按键) 实际对应单片机的 `ADC11` 通道;`P1.3` (电池) 实际对应 `ADC3` 通道。原先代码中将两个通道错调。
|
||||||
|
* **修正方案**:
|
||||||
|
* 在 [adc.h](file:///c:/Users/xtell/Downloads/stc32g128k-master%20(1)/stc32g128k/Drivers/adc.h) 中对调了通道定义:
|
||||||
|
`#define ADC_CHANNEL_KEY_DOWN 11` (P0.3)
|
||||||
|
`#define ADC_CHANNEL_BATTERY 3` (P1.3)
|
||||||
|
* 修正后,按键常态值成功捕获为正常的 `2712` (约 2.2V),按下后正常达到 `4095` 饱和值。
|
||||||
|
|
||||||
|
### 3. PCF8563 (D8563) RTC 通信时序与机制重构 (对应阶段 4)
|
||||||
|
* **I2C 物理模式优化**: 移除了高频读写 XSFR 带来的推挽/开漏动态切换(避免产生高频暂态毛刺),完全使用准双向口硬件 of “过渡强上拉”机制。
|
||||||
|
* **死锁释放机制 (`I2C_ReleaseBus`)**: 在 PCF8563 通信或自检开始前,自动输出 9 个时钟脉冲配合 Stop 信号,强行迫使因常供电而进入错乱挂起状态的 RTC 芯片释放 I2C 总线,彻底避免死锁。
|
||||||
|
* **自适应引脚探测**: 内置支持运行时自动侦测 `P3.2` 与 `P3.3` 的连线是否在画板时反接(默认极性与互换极性自动轮询),增强了硬件兼容度。
|
||||||
|
* **双时延诊断测试**: 实现了 `PCF8563_TryReadDiagnostic()` 函数,同时使用常规速率 (30us延时) 与超低速时序 (200us延时,2.5kHz) 进行试读测试。
|
||||||
|
|
||||||
|
### 4. RGB 幻彩灯条上电亮白灯与 4-bit SPI 时序重构
|
||||||
|
* **故障定位**:
|
||||||
|
1. 上电复位暂态:单片机复位期间引脚默认弱上拉输出 `1`,这被灯珠移位寄存器误读为“全1”色彩(白光)。当单片机完成复位并拉低引脚时,产生 RESET 信号迫使灯珠锁存该色彩,导致开机长时间亮白灯。
|
||||||
|
2. 3-bit 编码周期过短:在 3MHz 速率下,3-bit 模拟波形(100b/110b)的整个脉冲周期仅有 1.0us,低于规格书规定的 1.2us 最小值,引发灯珠识别率低下。
|
||||||
|
* **软件重构方案**:
|
||||||
|
1. **4-bit 编码重构**: 将转码改为 4-bit 编码(0码=1000b, 1码=1100b),高电平分别保持 333ns 和 666ns,完全处于规格书安全区间;总周期为 4 bits = 1.33us,完美吻合典型值 1.25us,数据传输稳定度极高。
|
||||||
|
2. **上电显式关灯**: 在 `main()` 的 `GPIO_Init()` 执行完毕的第一个瞬间,强制调用 `RGB_Send(0, 0, 0, 0, 0, 0);` 送出关灯帧,在极速开机阶段的几微秒内强行熄灭白光。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ 二、 物理硬件排障记录
|
||||||
|
|
||||||
|
在联合调试期间,定位并解决了一项关键的物理硬件故障:
|
||||||
|
|
||||||
|
* **RTC 晶振不起振故障**:
|
||||||
|
* **现象**: MCU 引脚输出波形完美,但 RTC 芯片一直不回 ACK。
|
||||||
|
* **病因**: 32.768kHz 晶体两端的**旁路电容参数焊接错误**,导致晶振无法起振,从而使芯片的 I2C 接口内部控制块锁定并忽略全部寻址。
|
||||||
|
* **解决**: 修正贴片电容后,晶振起振,RTC 硬件通信与自检完全恢复正常。
|
||||||
|
|
||||||
|
* **充电检测引脚配置与校准**:
|
||||||
|
* **引脚定义**: 充电状态 DET 物理连接至 MCU 的 **`P1.2`** 引脚(对应 QFN32 第 10 脚,硬件为 **`ADC 通道 2`**)。
|
||||||
|
* **电气优化**: 硬件配置为高阻输入,且在 `system.c` 和 `adc.c` 初始化中**显式关闭了 P1.2 的内部上拉电阻**(通过置空寄存器位 `(*(unsigned char volatile xdata *)0xFE11) &= ~(1 << 2)`),彻底消除了上电引起的电平偏置。
|
||||||
|
* **阈值检测标准**:
|
||||||
|
- $V_{\text{DET}} < 0.5\text{V}$ ($<500\text{mV}$): 未插充电器
|
||||||
|
- $0.8\text{V} \le V_{\text{DET}} \le 1.8\text{V}$ ($800\text{mV} \sim 1800\text{mV}$): 正在充电 (充电指示闪烁)
|
||||||
|
- $2.2\text{V} \le V_{\text{DET}} \le 2.8\text{V}$ ($2200\text{mV} \sim 2800\text{mV}$): 已充满 (停止闪烁)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 三、 交付与归档文件
|
||||||
|
|
||||||
|
|
||||||
|
* **最新源文件**:
|
||||||
|
* 电池与按键检测算法: [adc.c](file:///c:/Users/xtell/Downloads/stc32g128k-master%20(1)/stc32g128k/Drivers/adc.c)
|
||||||
|
* 引脚定义配置表: [adc.h](file:///c:/Users/xtell/Downloads/stc32g128k-master%20(1)/stc32g128k/Drivers/adc.h)
|
||||||
|
* 自适应防死锁 RTC 驱动: [pcf8563.c](file:///c:/Users/xtell/Downloads/stc32g128k-master%20%281%29/stc32g128k/Drivers/pcf8563.c)
|
||||||
|
* 极速自检与诊断流程: [system.c](file:///c:/Users/xtell/Downloads/stc32g128k-master%20(1)/stc32g128k/App/system.c)
|
||||||
|
* **烧录固件**:
|
||||||
|
* 归档 HEX 路径: [Build\wristband.hex](file:///c:/Users/xtell/Downloads/stc32g128k-master%20(1)/stc32g128k/Build/wristband.hex)
|
||||||
126
woke-order/wo-03
Normal file
126
woke-order/wo-03
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# 工单 WO-03:RGB 灯控故障排查 & 电池充电功能测试计划
|
||||||
|
|
||||||
|
**优先级**:高
|
||||||
|
**状态**:进行中
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、RGB 灯控 — 故障排查与修复
|
||||||
|
|
||||||
|
### 现象
|
||||||
|
开机后两颗 WS2812B 灯珠一直亮白灯,无法通过软件控制熄灭或变色。
|
||||||
|
|
||||||
|
### 1.1 硬件电路复查
|
||||||
|
| # | 操作步骤 | 验证手段 | 预期结果 | 状态 |
|
||||||
|
|---|---------|---------|---------|------|
|
||||||
|
| 1.1.1 | 用万用表测量 P2.3 对地电阻 | 断电测 | 非短路 | 待执行 |
|
||||||
|
| 1.1.2 | 上电测 P2.3 引脚电平 | 示波器捕获上电瞬间到 main() 执行前的波形 | 应快速被拉低,无长时间高电平 | 待执行 |
|
||||||
|
| 1.1.3 | 测量 TP4054 VCC (5V) 和 VSYS 供电 | 万用表直流档 | VSYS 约 3.7~4.2V 或 5V | 待执行 |
|
||||||
|
| 1.1.4 | 测量 WS2812B VDD (Pin4) 电压 | 万用表 | 与 VSYS 一致 | 待执行 |
|
||||||
|
| 1.1.5 | 测量 WS2812B DIN (Pin3) 静态电平 | 未发送数据时示波器/万用表 | 应接近 0V | 待执行 |
|
||||||
|
|
||||||
|
### 1.2 引脚初始化修复
|
||||||
|
| # | 操作步骤 | 涉及文件 | 说明 | 状态 |
|
||||||
|
|---|---------|---------|------|------|
|
||||||
|
| 1.2.1 | GPIO_Init() 中增设 RGB_DIN = 0 | system.c:50 | 将 P2.3 初始输出拉低,消除上电不确定态 | ✔ 已修复 |
|
||||||
|
| 1.2.2 | main() 中发一次 RESET 脉冲确保 WS2812B 复位 | main.c | 确保 WS2812B 在上电后处于已知复位状态 | ✔ 已修复 |
|
||||||
|
| 1.2.3 | 验证 P_SW1 SPI 引脚映射 | rgb.c:19 | P_SW1 &= ~0x0C; P_SW1 |= 0x04 将 SPI 映射到 P2.3(MOSI)/P2.5(SCLK) | ✔ 已验证 |
|
||||||
|
|
||||||
|
### 1.3 SPI 时序验证
|
||||||
|
| # | 操作步骤 | 验证手段 | 预期结果 | 状态 |
|
||||||
|
|---|---------|---------|---------|------|
|
||||||
|
| 1.3.1 | 示波器探头接 P2.3 (MOSI/DIN) 和 GND | 调用 RGB_Send(0, 180, 0, 0, 0, 0) 只亮红灯 | 观察到 3MHz 时钟波形 | 待执行 |
|
||||||
|
| 1.3.2 | 示波器接 P2.5 (SCLK) 观察 | 同一调用 | 发送期间无 SCLK 信号(因已被设为高阻) | 待执行 |
|
||||||
|
| 1.3.3 | 测量 RESET 低电平持续时间 | 在 RGB_Send() 最后 RGB_DIN=0 后测 | >80μs | 待执行 |
|
||||||
|
| 1.3.4 | 验证 SPCTL = 0xD1 配置 | 核对 STC32G 数据手册 | SSIG=1, SPEN=1, MSTR=1, CPOL=0, CPHA=0, SPR0=1 → 3MHz @24MHz | ✔ 已验证 |
|
||||||
|
|
||||||
|
### 1.4 代码逻辑复查
|
||||||
|
| # | 操作步骤 | 涉及文件 | 说明 | 状态 |
|
||||||
|
|---|---------|---------|------|------|
|
||||||
|
| 1.4.1 | 检查 Encode_Byte 编码 | rgb.c:42-62 | 每个 WS2812B 的 8bit 展开为 3 字节 SPI 数据 = 24 SPI bits | ✔ 已验证 |
|
||||||
|
| 1.4.2 | 检查 RGB_Send 中 SPI 收发循环 | rgb.c:94-100 | 18 字节全部通过 SPDAT 发出,含等待 SPIF | ✔ 已验证 |
|
||||||
|
| 1.4.3 | SPI_Init 调用频次 | rgb.c:92 | 每次 RGB_Send 都重新 init SPI,不影响功能 | ✔ 已验证 |
|
||||||
|
| 1.4.4 | RGB_Send(0,0,0,0,0,0) 能否关灯 | 调试指令 | 需实测验证 | 待执行 |
|
||||||
|
|
||||||
|
### 1.5 功能验证
|
||||||
|
| # | 测试用例 | 操作 | 预期结果 | 状态 |
|
||||||
|
|---|---------|------|---------|------|
|
||||||
|
| 1.5.1 | 开机默认灭灯 | 上电 | 两颗灯全灭 | 待执行 |
|
||||||
|
| 1.5.2 | 对码模式白灯 | 进入 PairApp | 两颗灯亮白灯(G=R=B=150) | 待执行 |
|
||||||
|
| 1.5.3 | 对码成功绿灯 | PairApp 确认保存 | 两颗亮绿灯(G=200) | 待执行 |
|
||||||
|
| 1.5.4 | 报警红灯 | 触发门磁报警 | 红灯闪烁 | 待执行 |
|
||||||
|
| 1.5.5 | SOS 红蓝交替 | 触发 SOS | 灯1红灯、灯2蓝灯交替闪烁 | 待执行 |
|
||||||
|
| 1.5.6 | 时间设置蓝灯 | 长按确认进入时间设置 | 双蓝灯常亮 | 待执行 |
|
||||||
|
| 1.5.7 | 马达无异常振动 | 任何 RGB_Send 调用时 | 马达不应发出异常噪音或振动 | 待执行 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、电池充电功能测试计划
|
||||||
|
|
||||||
|
### 2.1 硬件充电回路验证
|
||||||
|
| # | 操作步骤 | 验证手段 | 预期结果 | 状态 |
|
||||||
|
|---|---------|---------|---------|------|
|
||||||
|
| 2.1.1 | 万用表测 TP4054 的 VCC(Pin4) 对 GND | 插入 USB 5V 供电 | 5.0V ±0.25V | 待执行 |
|
||||||
|
| 2.1.2 | 测 TP4054 的 PROG(Pin5) 对 GND 电阻 | 断电测 R1 阻值 | 24kΩ → 充电电流约 41.7mA | 待执行 |
|
||||||
|
| 2.1.3 | 测 TP4054 的 BAT(Pin3) 对 GND 电压 | 插入 USB + 接电池 | 电池电压 3.7~4.2V | 待执行 |
|
||||||
|
| 2.1.4 | 测 TP4054 的 CHRG(Pin1) 电平 | 未接USB/充电中/充满 | 未接:高阻;充电中:低电平;充满:高阻 | 待执行 |
|
||||||
|
| 2.1.5 | 追踪 CHRG → DET → MCU 引脚号的 PCB 走线 | 对照原理图 | 确认连接至 MCU GPIO 引脚号 | 待执行 |
|
||||||
|
| 2.1.6 | 确认 DET 网络终端的 MCU 引脚电平兼容性 | 万用表测 MCU 侧 | TP4054 CHRG 需 MCU 内部上拉 | 待执行 |
|
||||||
|
| 2.1.7 | 测量 BAT_ADC(P1.3) 分压网络 | 万用表 | R15=10kΩ, R16=22kΩ, 分压比 22/32 | 待执行 |
|
||||||
|
|
||||||
|
### 2.2 充电检测代码实现
|
||||||
|
| # | 操作步骤 | 涉及文件 | 说明 | 状态 |
|
||||||
|
|---|---------|---------|------|------|
|
||||||
|
| 2.2.1 | 确认 MCU 实际连接 CHRG/DET 的引脚号 | 原理图追踪 | CHRG → DET → MCU | ✔ 已完成 |
|
||||||
|
| 2.2.2 | 在 config.h 添加 sbit CHRG_DET 定义 | config.h | 定义充电检测引脚 | ✔ 已添加 |
|
||||||
|
| 2.2.3 | 在 GPIO_Init() 中将该引脚配置为高阻输入+内部上拉 | system.c | 适配 TP4054 开漏输出 | ✔ 已添加 |
|
||||||
|
| 2.2.4 | 实现 bit Is_Charging(void) | adc.c / adc.h | 返回 0=未充电, 1=充电中 | ✔ 已实现 |
|
||||||
|
| 2.2.5 | 在 UI 层接入充电状态显示 | ui.c / app_clock.c | 充电时显示 `+` 后缀 | ✔ 已完成 |
|
||||||
|
| 2.2.6 | 在 Self_Test() 中输出充电状态 | system.c | 上电自检时UART输出充电状态 | ✔ 已添加 |
|
||||||
|
|
||||||
|
### 2.3 充电功能验证
|
||||||
|
| # | 测试用例 | 操作 | 预期结果 | 状态 |
|
||||||
|
|---|---------|------|---------|------|
|
||||||
|
| 2.3.1 | 未插入 USB 充电 | 观察待机界面 | 仅显示百分比,无 `+` 后缀 | 待执行 |
|
||||||
|
| 2.3.2 | 插入 USB 充电(电池未满) | USB 供电 | 状态栏显示 `85%+`,CHRG 低电平 | 待执行 |
|
||||||
|
| 2.3.3 | 电池充满后 | 等待充电完成 | `+` 后缀消失,CHRG 变高阻 | 待执行 |
|
||||||
|
| 2.3.4 | 充电时热插拔 USB | 反复插拔 | 状态切换无延迟 > 2s | 待执行 |
|
||||||
|
| 2.3.5 | 低电量充电(电池 < 3.3V) | 电池耗尽后充电 | 充电电流约 41.7mA,UI 显示充电中 | 待执行 |
|
||||||
|
| 2.3.6 | 充电时进入休眠 | 插入 USB 等待 60s 自动休眠 | 休眠后应能被唤醒 | 待执行 |
|
||||||
|
| 2.3.7 | 充电时触发报警/SOS | 充电中触发报警 | 报警功能正常 | 待执行 |
|
||||||
|
| 2.3.8 | Is_Charging() 电平反转验证 | 强制拉高/拉低 DET 引脚 | UART 调试输出正确状态 | 待执行 |
|
||||||
|
|
||||||
|
### 2.4 ADC 电池电压校准
|
||||||
|
| # | 操作步骤 | 验证手段 | 预期结果 | 状态 |
|
||||||
|
|---|---------|---------|---------|------|
|
||||||
|
| 2.4.1 | 用万用表实测电池电压 | 记录 Vref | 如 3.85V | 待执行 |
|
||||||
|
| 2.4.2 | UART 读取 ADC_GetBatteryVoltage_mV() 返回值 | 串口助手 | 偏差应在 ±50mV 内 | 待执行 |
|
||||||
|
| 2.4.3 | 如果偏差过大,调整校准系数 | adc.c:122 | val * Vref_mV / adc_raw | 待执行 |
|
||||||
|
| 2.4.4 | 验证电量百分比线性度 | 可调电源模拟 3.3V~4.2V | 0%~100% 线性输出 | 待执行 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、测试环境与工具清单
|
||||||
|
- Keil C251 编译器 + STC-ISP 烧录工具
|
||||||
|
- USB 转 TTL 串口模块 (115200 baud)
|
||||||
|
- 数字万用表
|
||||||
|
- 双通道数字示波器(至少 10MHz 带宽)
|
||||||
|
- 可调直流稳压电源(模拟电池 3.0~4.2V)
|
||||||
|
- 逻辑分析仪(可选)
|
||||||
|
- 已组装好的手环 PCB x 1~2pcs
|
||||||
|
- 433MHz 发射模块/遥控器
|
||||||
|
- 锂电池 3.7V
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、代码修改记录
|
||||||
|
|
||||||
|
| 文件 | 修改内容 | 状态 |
|
||||||
|
|------|---------|------|
|
||||||
|
| App/config.h | 新增 CHRG_DET sbit 定义 | ✔ |
|
||||||
|
| App/system.c | GPIO_Init() 中增设 RGB_DIN=0,配置 CHRG_DET 引脚,Self_Test() 输出充电状态 | ✔ |
|
||||||
|
| App/main.c | GPIO_Init() 后立即发 WS2812B RESET 脉冲 | ✔ |
|
||||||
|
| Drivers/adc.h | 新增 Is_Charging() 声明 | ✔ |
|
||||||
|
| Drivers/adc.c | 实现 Is_Charging() | ✔ |
|
||||||
|
| App/rgb.c | 确认 SPI 配置正确 | ✔ 已验证 |
|
||||||
|
| App/ui.c | UI_ShowClockPage 中接入 Is_Charging() 显示 `+` 后缀 | ✔ |
|
||||||
133
wristband.uvopt
133
wristband.uvopt
@@ -12,7 +12,7 @@
|
|||||||
<lExt>*.lib</lExt>
|
<lExt>*.lib</lExt>
|
||||||
<tExt>*.txt; *.h; *.inc; *.md</tExt>
|
<tExt>*.txt; *.h; *.inc; *.md</tExt>
|
||||||
<pExt>*.plm</pExt>
|
<pExt>*.plm</pExt>
|
||||||
<CppX>*.cpp</CppX>
|
<CppX>*.cpp; *.cc; *.cxx</CppX>
|
||||||
<nMigrate>0</nMigrate>
|
<nMigrate>0</nMigrate>
|
||||||
</Extensions>
|
</Extensions>
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<TargetOption>
|
<TargetOption>
|
||||||
<CLK251>35000000</CLK251>
|
<CLK251>35000000</CLK251>
|
||||||
<OPTTT>
|
<OPTTT>
|
||||||
<gFlags>1</gFlags>
|
<gFlags>0</gFlags>
|
||||||
<BeepAtEnd>1</BeepAtEnd>
|
<BeepAtEnd>1</BeepAtEnd>
|
||||||
<RunSim>1</RunSim>
|
<RunSim>1</RunSim>
|
||||||
<RunTarget>0</RunTarget>
|
<RunTarget>0</RunTarget>
|
||||||
@@ -73,11 +73,11 @@
|
|||||||
<LExpSel>0</LExpSel>
|
<LExpSel>0</LExpSel>
|
||||||
</OPTXL>
|
</OPTXL>
|
||||||
<OPTFL>
|
<OPTFL>
|
||||||
<tvExp>1</tvExp>
|
<tvExp>0</tvExp>
|
||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
<IsCurrentTarget>1</IsCurrentTarget>
|
<IsCurrentTarget>1</IsCurrentTarget>
|
||||||
</OPTFL>
|
</OPTFL>
|
||||||
<CpuCode>255</CpuCode>
|
<CpuCode>0</CpuCode>
|
||||||
<DebugOpt>
|
<DebugOpt>
|
||||||
<uSim>1</uSim>
|
<uSim>1</uSim>
|
||||||
<uTrg>0</uTrg>
|
<uTrg>0</uTrg>
|
||||||
@@ -116,13 +116,6 @@
|
|||||||
<tIfile></tIfile>
|
<tIfile></tIfile>
|
||||||
<pMon></pMon>
|
<pMon></pMon>
|
||||||
</DebugOpt>
|
</DebugOpt>
|
||||||
<TargetDriverDllRegistry>
|
|
||||||
<SetRegEntry>
|
|
||||||
<Number>0</Number>
|
|
||||||
<Key>DLGDC51</Key>
|
|
||||||
<Name>(94=-1,-1,-1,-1,0)(120=-1,-1,-1,-1,0)(119=-1,-1,-1,-1,0)(107=-1,-1,-1,-1,0)(103=-1,-1,-1,-1,0)(108=-1,-1,-1,-1,0)(114=-1,-1,-1,-1,0)(115=-1,-1,-1,-1,0)</Name>
|
|
||||||
</SetRegEntry>
|
|
||||||
</TargetDriverDllRegistry>
|
|
||||||
<Breakpoint/>
|
<Breakpoint/>
|
||||||
<Tracepoint>
|
<Tracepoint>
|
||||||
<THDelay>0</THDelay>
|
<THDelay>0</THDelay>
|
||||||
@@ -130,12 +123,12 @@
|
|||||||
<DebugFlag>
|
<DebugFlag>
|
||||||
<trace>0</trace>
|
<trace>0</trace>
|
||||||
<periodic>0</periodic>
|
<periodic>0</periodic>
|
||||||
<aLwin>1</aLwin>
|
<aLwin>0</aLwin>
|
||||||
<aCover>0</aCover>
|
<aCover>0</aCover>
|
||||||
<aSer1>0</aSer1>
|
<aSer1>0</aSer1>
|
||||||
<aSer2>0</aSer2>
|
<aSer2>0</aSer2>
|
||||||
<aPa>0</aPa>
|
<aPa>0</aPa>
|
||||||
<viewmode>1</viewmode>
|
<viewmode>0</viewmode>
|
||||||
<vrSel>0</vrSel>
|
<vrSel>0</vrSel>
|
||||||
<aSym>0</aSym>
|
<aSym>0</aSym>
|
||||||
<aTbox>0</aTbox>
|
<aTbox>0</aTbox>
|
||||||
@@ -171,7 +164,7 @@
|
|||||||
|
|
||||||
<Group>
|
<Group>
|
||||||
<GroupName>App</GroupName>
|
<GroupName>App</GroupName>
|
||||||
<tvExp>1</tvExp>
|
<tvExp>0</tvExp>
|
||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
<cbSel>0</cbSel>
|
<cbSel>0</cbSel>
|
||||||
<RteFlg>0</RteFlg>
|
<RteFlg>0</RteFlg>
|
||||||
@@ -259,6 +252,90 @@
|
|||||||
<RteFlg>0</RteFlg>
|
<RteFlg>0</RteFlg>
|
||||||
<bShared>0</bShared>
|
<bShared>0</bShared>
|
||||||
</File>
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>1</GroupNumber>
|
||||||
|
<FileNumber>8</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\App\clock.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>clock.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>1</GroupNumber>
|
||||||
|
<FileNumber>9</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\App\app_clock.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>app_clock.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>1</GroupNumber>
|
||||||
|
<FileNumber>10</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\App\app_menu.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>app_menu.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>1</GroupNumber>
|
||||||
|
<FileNumber>11</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\App\app_pair.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>app_pair.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>1</GroupNumber>
|
||||||
|
<FileNumber>12</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\App\app_alarm.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>app_alarm.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>1</GroupNumber>
|
||||||
|
<FileNumber>13</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\App\app_sos.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>app_sos.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>1</GroupNumber>
|
||||||
|
<FileNumber>14</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\App\app_rf_monitor.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>app_rf_monitor.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Group>
|
<Group>
|
||||||
@@ -269,7 +346,7 @@
|
|||||||
<RteFlg>0</RteFlg>
|
<RteFlg>0</RteFlg>
|
||||||
<File>
|
<File>
|
||||||
<GroupNumber>2</GroupNumber>
|
<GroupNumber>2</GroupNumber>
|
||||||
<FileNumber>8</FileNumber>
|
<FileNumber>15</FileNumber>
|
||||||
<FileType>1</FileType>
|
<FileType>1</FileType>
|
||||||
<tvExp>0</tvExp>
|
<tvExp>0</tvExp>
|
||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
@@ -281,7 +358,7 @@
|
|||||||
</File>
|
</File>
|
||||||
<File>
|
<File>
|
||||||
<GroupNumber>2</GroupNumber>
|
<GroupNumber>2</GroupNumber>
|
||||||
<FileNumber>9</FileNumber>
|
<FileNumber>16</FileNumber>
|
||||||
<FileType>1</FileType>
|
<FileType>1</FileType>
|
||||||
<tvExp>0</tvExp>
|
<tvExp>0</tvExp>
|
||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
@@ -291,6 +368,30 @@
|
|||||||
<RteFlg>0</RteFlg>
|
<RteFlg>0</RteFlg>
|
||||||
<bShared>0</bShared>
|
<bShared>0</bShared>
|
||||||
</File>
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>2</GroupNumber>
|
||||||
|
<FileNumber>17</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\Drivers\adc.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>adc.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>2</GroupNumber>
|
||||||
|
<FileNumber>18</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\Drivers\pcf8563.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>pcf8563.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
</ProjectOpt>
|
</ProjectOpt>
|
||||||
|
|||||||
@@ -359,6 +359,41 @@
|
|||||||
<FileType>1</FileType>
|
<FileType>1</FileType>
|
||||||
<FilePath>.\App\system.c</FilePath>
|
<FilePath>.\App\system.c</FilePath>
|
||||||
</File>
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>clock.c</FileName>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<FilePath>.\App\clock.c</FilePath>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>app_clock.c</FileName>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<FilePath>.\App\app_clock.c</FilePath>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>app_menu.c</FileName>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<FilePath>.\App\app_menu.c</FilePath>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>app_pair.c</FileName>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<FilePath>.\App\app_pair.c</FilePath>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>app_alarm.c</FileName>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<FilePath>.\App\app_alarm.c</FilePath>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>app_sos.c</FileName>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<FilePath>.\App\app_sos.c</FilePath>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>app_rf_monitor.c</FileName>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<FilePath>.\App\app_rf_monitor.c</FilePath>
|
||||||
|
</File>
|
||||||
</Files>
|
</Files>
|
||||||
</Group>
|
</Group>
|
||||||
<Group>
|
<Group>
|
||||||
@@ -374,6 +409,16 @@
|
|||||||
<FileType>1</FileType>
|
<FileType>1</FileType>
|
||||||
<FilePath>.\Drivers\rf.c</FilePath>
|
<FilePath>.\Drivers\rf.c</FilePath>
|
||||||
</File>
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>adc.c</FileName>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<FilePath>.\Drivers\adc.c</FilePath>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>pcf8563.c</FileName>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<FilePath>.\Drivers\pcf8563.c</FilePath>
|
||||||
|
</File>
|
||||||
</Files>
|
</Files>
|
||||||
</Group>
|
</Group>
|
||||||
</Groups>
|
</Groups>
|
||||||
|
|||||||
Reference in New Issue
Block a user