喷淋器 P0 功能实现
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
/******************************************************************************
|
||||
* @file app_defer.c
|
||||
* @brief ISR→任务上下文的延后执行标志位实现
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.31
|
||||
* @version V1.0.0,首次发布
|
||||
******************************************************************************/
|
||||
|
||||
#include "system/includes.h"
|
||||
#include "app_defer.h"
|
||||
|
||||
/** 位掩码,volatile:request() 在中断上下文写,take()/is_pending()/cancel()
|
||||
* 在任务上下文读写,两侧都用 local_irq_disable/enable 包一下读改写,
|
||||
* 避免"任务侧正在清 A 位时中断侧设 B 位"这种读改写竞态把 B 位冲丢。 */
|
||||
static volatile uint8_t s_flags;
|
||||
|
||||
void app_defer_request(AppDeferBit_t bit)
|
||||
{
|
||||
if (bit >= APP_DEFER_MAX) {
|
||||
return;
|
||||
}
|
||||
local_irq_disable();
|
||||
s_flags |= (uint8_t)(1u << bit);
|
||||
local_irq_enable();
|
||||
}
|
||||
|
||||
uint8_t app_defer_take(AppDeferBit_t bit)
|
||||
{
|
||||
uint8_t was_set;
|
||||
|
||||
if (bit >= APP_DEFER_MAX) {
|
||||
return 0;
|
||||
}
|
||||
local_irq_disable();
|
||||
was_set = (s_flags & (uint8_t)(1u << bit)) ? 1 : 0;
|
||||
s_flags &= (uint8_t)~(1u << bit);
|
||||
local_irq_enable();
|
||||
return was_set;
|
||||
}
|
||||
|
||||
uint8_t app_defer_is_pending(AppDeferBit_t bit)
|
||||
{
|
||||
if (bit >= APP_DEFER_MAX) {
|
||||
return 0;
|
||||
}
|
||||
return (s_flags & (uint8_t)(1u << bit)) ? 1 : 0;
|
||||
}
|
||||
|
||||
void app_defer_cancel(AppDeferBit_t bit)
|
||||
{
|
||||
if (bit >= APP_DEFER_MAX) {
|
||||
return;
|
||||
}
|
||||
local_irq_disable();
|
||||
s_flags &= (uint8_t)~(1u << bit);
|
||||
local_irq_enable();
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/******************************************************************************
|
||||
* @file app_defer.h
|
||||
* @brief ISR→任务上下文的延后执行标志位(统一收口 2026-08-31 复位事故教训)
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.31
|
||||
* @version V1.0.0,首次发布
|
||||
*
|
||||
* @note ⚠ 硬约束:flash 擦写(syscfg_write)、BLE 广播切换、关机收口等"重活"
|
||||
* 禁止在 usr_timer_add 的中断上下文回调(本产品 20ms usr_timer_tick /
|
||||
* 按键长按回调)里直接执行,只能在 usr_timer_loop(BLE 线程,任务
|
||||
* 上下文,栈充足)里执行。之前每个模块各自发明一个 pending 标志
|
||||
* (off_pending/pair_pending/ota_pending/nvm 的 s_need_save),
|
||||
* 容易被后来的功能遗漏这条红线——本模块把"打标志"这一步收成统一
|
||||
* 接口:ISR 只准调 app_defer_request(),真正执行永远在
|
||||
* app_function_loop() 里用 app_defer_take() 消费。
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __APP_DEFER_H__
|
||||
#define __APP_DEFER_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/** 延后动作位,新增一种"需要写flash/切广播/关机"的动作就加一项 */
|
||||
typedef enum {
|
||||
APP_DEFER_NVM_SAVE = 0, /**< VM 防抖到点,需要落盘 */
|
||||
APP_DEFER_PAIR, /**< 长按 5s 松手,需要清绑定进配对 */
|
||||
APP_DEFER_OTA, /**< 长按 12s 到点,需要进 OTA */
|
||||
APP_DEFER_MAX
|
||||
} AppDeferBit_t;
|
||||
|
||||
/**
|
||||
* @brief 中断上下文调用:只置位,不做任何实际动作
|
||||
* @param bit 要延后的动作
|
||||
* @return 无
|
||||
*/
|
||||
void app_defer_request(AppDeferBit_t bit);
|
||||
|
||||
/**
|
||||
* @brief 任务上下文调用:查询并清除标志(取出即消费,最多处理一次)
|
||||
* @param bit 要消费的动作
|
||||
* @return 1=之前挂起过(现在可以执行真正动作),0=没挂起
|
||||
*/
|
||||
uint8_t app_defer_take(AppDeferBit_t bit);
|
||||
|
||||
/**
|
||||
* @brief 任务上下文调用:只查询是否挂起,不清除(多个动作互斥判断用)
|
||||
* @param bit 要查询的动作
|
||||
* @return 1=挂起中,0=未挂起
|
||||
*/
|
||||
uint8_t app_defer_is_pending(AppDeferBit_t bit);
|
||||
|
||||
/**
|
||||
* @brief 任务上下文调用:撤销挂起(如 OTA 触发时要撤销配对)
|
||||
* @param bit 要撤销的动作
|
||||
* @return 无
|
||||
*/
|
||||
void app_defer_cancel(AppDeferBit_t bit);
|
||||
|
||||
#endif /* __APP_DEFER_H__ */
|
||||
@@ -0,0 +1,124 @@
|
||||
/******************************************************************************
|
||||
* @file app_function.c
|
||||
* @brief 功能总装:喷淋键启停、任务上下文落地配对/OTA(P0 版)
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.09.09
|
||||
* @version V1.0.0
|
||||
* @history
|
||||
* - V1.0.0, 2026.09.09, cyWu, P0 首次发布(无 VM/联动/计划;P1+ 再扩展)
|
||||
*
|
||||
* @note ⚠ 硬约束:写 flash(清绑定)、BLE 广播切换(配对/OTA)禁止在
|
||||
* 20ms 中断上下文直跑,一律 app_defer_request 后由本模块在
|
||||
* usr_timer_loop(任务上下文)落地。
|
||||
******************************************************************************/
|
||||
|
||||
#include "system/includes.h" /* 必须先于 app_main.h:u8/u16/u32 等类型由 SDK 定义 */
|
||||
#include "app_function.h"
|
||||
#include "app_pair.h"
|
||||
#include "app_defer.h"
|
||||
#include "app_led.h"
|
||||
#include "app_spray.h"
|
||||
#include "app_main.h"
|
||||
#include "usr_le_api.h"
|
||||
#include "jb_product.h"
|
||||
#include "board_pin.h"
|
||||
|
||||
#define LOG_TAG_CONST APP
|
||||
#define LOG_TAG "[APP_FUNC]"
|
||||
#define LOG_INFO_ENABLE
|
||||
#include "debug.h"
|
||||
|
||||
/**
|
||||
* @brief 初始化功能模块
|
||||
* @return 无
|
||||
*/
|
||||
void app_function_init(void)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 任务上下文周期处理(配对 / OTA / 写 VM 只准在此落地)
|
||||
* @return 无
|
||||
*/
|
||||
void app_function_loop(void)
|
||||
{
|
||||
if (app_defer_take(APP_DEFER_OTA)) {
|
||||
app_defer_cancel(APP_DEFER_PAIR);
|
||||
usr_app_ota_init();
|
||||
app_led_request_ota();
|
||||
log_info("pair key hold 12s -> ota\n");
|
||||
} else if (app_defer_take(APP_DEFER_PAIR)) {
|
||||
usr_goto_pair_mode();
|
||||
app_pair_start();
|
||||
app_led_clear_pair_hint();
|
||||
log_info("pair key released after 5s -> pair\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 同步 DP1 = 喷淋作业态(整轮含间隔等待为 1)
|
||||
* @return 无
|
||||
*/
|
||||
void app_function_poll(void)
|
||||
{
|
||||
#if BOARD_SPRAY_ENABLE
|
||||
gDevData.spray_switch = app_spray_is_running();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 喷淋键短按:空闲用上次有效配置开一轮,作业中立刻停(OTA 中忽略)
|
||||
* @return 无
|
||||
*/
|
||||
void usr_on_key_short(void)
|
||||
{
|
||||
if (app_led_is_ota()) {
|
||||
return; /* OTA 进行中忽略(规格书 5.3) */
|
||||
}
|
||||
if (app_spray_is_running()) {
|
||||
app_spray_stop();
|
||||
log_info("spray key short -> stop\n");
|
||||
} else {
|
||||
app_spray_start();
|
||||
log_info("spray key short -> start\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 配网键按住满 5s:红灯 100ms 快闪提示(不碰绿灯,不清绑定)
|
||||
* @return 无
|
||||
*/
|
||||
void usr_on_key_pair_hint(void)
|
||||
{
|
||||
if (app_led_is_ota() || app_pair_is_active()) {
|
||||
return; /* OTA / 配对会话中忽略再次 5s(规格书 5.3) */
|
||||
}
|
||||
app_led_hint_pair();
|
||||
log_info("pair key hold 5s, red fast blink (release to pair, keep to 12s ota)\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 配网键按满 5s 后松手:置配对标志(清绑定在任务上下文)
|
||||
* @return 无
|
||||
*/
|
||||
void usr_on_key_pair_start(void)
|
||||
{
|
||||
if (app_led_is_ota() || app_pair_is_active()) {
|
||||
return; /* OTA / 配对会话中忽略(规格书 5.3) */
|
||||
}
|
||||
app_defer_request(APP_DEFER_PAIR);
|
||||
log_info("pair key 5s released, pair pending\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 配网键按住满 12s:置 OTA 标志(BLE 切换在任务上下文)
|
||||
* @return 无
|
||||
*/
|
||||
void usr_on_key_ota_start(void)
|
||||
{
|
||||
if (app_led_is_ota() || app_pair_is_active()) {
|
||||
return; /* OTA / 配对会话中忽略(规格书 5.3) */
|
||||
}
|
||||
app_defer_request(APP_DEFER_OTA);
|
||||
log_info("pair key hold 12s, ota pending\n");
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/******************************************************************************
|
||||
* @file app_function.h
|
||||
* @brief 功能总装:按键动作 + DP 同步(配对/OTA 只在任务上下文落地)
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.09.09
|
||||
* @version V1.0.0
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __APP_FUNCTION_H__
|
||||
#define __APP_FUNCTION_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief 初始化功能模块状态
|
||||
* @return 无
|
||||
*/
|
||||
void app_function_init(void);
|
||||
|
||||
/**
|
||||
* @brief 同步 DP(DP1=喷淋作业态),每个 20ms 业务 tick 调用
|
||||
* @note 上报的是**作业态**(整轮含间隔等待为 1),不是某一只脚的瞬时电平
|
||||
* @return 无
|
||||
*/
|
||||
void app_function_poll(void);
|
||||
|
||||
/**
|
||||
* @brief 任务上下文周期处理(usr_timer_loop 500ms)
|
||||
* @note 写 VM / 配对 / OTA 只准在此落地,禁止放进 20ms ISR
|
||||
* @return 无
|
||||
*/
|
||||
void app_function_loop(void);
|
||||
|
||||
/**
|
||||
* @brief 业务层喷淋键短按回调(key_manager 触发):空闲开一轮 / 作业中停
|
||||
* @return 无
|
||||
*/
|
||||
void usr_on_key_short(void);
|
||||
|
||||
/**
|
||||
* @brief 业务层"配网键按住满 5s"回调(key_manager 触发,20ms 中断上下文)
|
||||
* @note 只切红灯快闪提示(绿灯不受影响),不清绑定
|
||||
* @return 无
|
||||
*/
|
||||
void usr_on_key_pair_hint(void);
|
||||
|
||||
/**
|
||||
* @brief 业务层"配网键按满 5s 后松手"回调(key_manager 触发,20ms 中断上下文)
|
||||
* @note 只置标志:清绑定要写 VM,由 app_function_loop 在任务上下文执行
|
||||
* @return 无
|
||||
*/
|
||||
void usr_on_key_pair_start(void);
|
||||
|
||||
/**
|
||||
* @brief 业务层"配网键按住满 12s"回调(key_manager 触发,20ms 中断上下文)
|
||||
* @note 只置标志:usr_app_ota_init 由 app_function_loop 在任务上下文执行
|
||||
* @return 无
|
||||
*/
|
||||
void usr_on_key_ota_start(void);
|
||||
|
||||
#endif /* __APP_FUNCTION_H__ */
|
||||
@@ -0,0 +1,198 @@
|
||||
/******************************************************************************
|
||||
* @file app_led.c
|
||||
* @brief 灯效:绿=作业态、红=配对态;OTA / 配对失败临时占双灯(规格书 6 章)
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.09.09
|
||||
* @version V1.0.0
|
||||
* @history
|
||||
* - V1.0.0, 2026.09.09, cyWu, P0 独立通道版(不同于加热垫互斥灯)
|
||||
*
|
||||
* @note 红绿独立:喷淋/空闲只改绿灯,不要去关红灯;配网闪红灯时不要关绿灯。
|
||||
* 只有 OTA 与配对失败动画临时占用双灯。事件表见 led_manager.c。
|
||||
******************************************************************************/
|
||||
|
||||
#include "app_led.h"
|
||||
#include "led_manager.h"
|
||||
#include "bsp_hw.h"
|
||||
#include "board_pin.h"
|
||||
#include "app_pair.h"
|
||||
#include "app_spray.h"
|
||||
#include "usr_le_api.h"
|
||||
#include "system/includes.h"
|
||||
|
||||
#define LOG_TAG_CONST APP
|
||||
#define LOG_TAG "[APP_LED]"
|
||||
#define LOG_INFO_ENABLE
|
||||
#include "debug.h"
|
||||
|
||||
typedef struct {
|
||||
uint8_t inited; /**< 1=已初始化 */
|
||||
uint8_t ota; /**< 1=OTA 灯效中,app_led_run 只跑交替 */
|
||||
uint8_t pair_hint; /**< 1=配网键已满 5s 但还没松手提交配对,维持红灯快闪 */
|
||||
} AppLedCtx_t;
|
||||
|
||||
static AppLedCtx_t s_led;
|
||||
|
||||
/**
|
||||
* @brief 重新播放一次「一次性/限时」灯效,让它从第 0 帧重新开始
|
||||
* @note 先 Delete 清空槽位再 Add,强制重新计时(见 led.c LED_EventAdd 语义)
|
||||
* @param evt 要重启的事件
|
||||
* @return 无
|
||||
*/
|
||||
static void led_restart(LED_Event_t evt)
|
||||
{
|
||||
LED_EventDelete(evt);
|
||||
LED_EventAdd(evt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 灯效初始化
|
||||
* @return APP_LED_OK=成功
|
||||
*/
|
||||
AppLed_Ret_t app_led_init(void)
|
||||
{
|
||||
if (s_led.inited) {
|
||||
return APP_LED_OK;
|
||||
}
|
||||
#if BOARD_LED_ENABLE
|
||||
user_led_init();
|
||||
s_led.ota = 0;
|
||||
s_led.pair_hint = 0;
|
||||
s_led.inited = 1;
|
||||
#else
|
||||
log_info("led disabled\n");
|
||||
#endif
|
||||
return APP_LED_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查询模块是否已初始化
|
||||
* @return APP_LED_OK=已初始化,APP_LED_ERR_NOT_INIT=未初始化
|
||||
*/
|
||||
AppLed_Ret_t app_led_get_status(void)
|
||||
{
|
||||
return s_led.inited ? APP_LED_OK : APP_LED_ERR_NOT_INIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 配网键满 5s:红灯 100ms 快闪提示(绿灯按作业态照常,不受影响)
|
||||
* @return 无
|
||||
*/
|
||||
void app_led_hint_pair(void)
|
||||
{
|
||||
if (!s_led.inited || s_led.ota) {
|
||||
return;
|
||||
}
|
||||
s_led.pair_hint = 1;
|
||||
LED_EventDelete(LED_EVENT_RED_PAIRED); /* 快闪盖过"已配对常亮"(同槽换事件) */
|
||||
LED_EventAdd(LED_EVENT_RED_PAIRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 结束"将配网"提示,交回 app_led_run 按 app_pair / 配对标志接管
|
||||
* @return 无
|
||||
*/
|
||||
void app_led_clear_pair_hint(void)
|
||||
{
|
||||
s_led.pair_hint = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 进入 OTA:清掉其它灯,挂红绿交替一直闪
|
||||
* @return 无
|
||||
*/
|
||||
void app_led_request_ota(void)
|
||||
{
|
||||
if (!s_led.inited) {
|
||||
return;
|
||||
}
|
||||
s_led.ota = 1;
|
||||
s_led.pair_hint = 0; /* 已改判 OTA,撤销配网提示 */
|
||||
LED_EventAllDelete();
|
||||
bsp_led_all_off();
|
||||
LED_EventAdd(LED_EVENT_OTA);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查询是否处于 OTA 灯效
|
||||
* @return 1=OTA 灯效中,0=否
|
||||
*/
|
||||
uint8_t app_led_is_ota(void)
|
||||
{
|
||||
return s_led.ota;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按优先级刷新灯(20ms 业务节拍)
|
||||
* @param dt 距上次调用的毫秒数
|
||||
* @return 无
|
||||
*/
|
||||
void app_led_run(uint16_t dt)
|
||||
{
|
||||
AppPairEvt_t pair_evt;
|
||||
uint8_t high_event;
|
||||
|
||||
if (!s_led.inited) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if BOARD_LED_ENABLE
|
||||
/* OTA 期间只跑红绿交替 */
|
||||
if (s_led.ota) {
|
||||
LED_EventAdd(LED_EVENT_OTA);
|
||||
user_led_handle();
|
||||
return;
|
||||
}
|
||||
|
||||
/* 配对成功/失败是边沿事件,只在那一拍切灯效 */
|
||||
pair_evt = app_pair_tick_1ms(dt);
|
||||
if (pair_evt == APP_PAIR_EVT_OK) {
|
||||
s_led.pair_hint = 0;
|
||||
LED_EventDelete(LED_EVENT_RED_PAIRING);
|
||||
LED_EventDelete(LED_EVENT_PAIR_FAIL);
|
||||
log_info("led pair ok -> red solid\n");
|
||||
} else if (pair_evt == APP_PAIR_EVT_FAIL) {
|
||||
s_led.pair_hint = 0;
|
||||
LED_EventDelete(LED_EVENT_RED_PAIRED);
|
||||
LED_EventDelete(LED_EVENT_RED_PAIRING);
|
||||
led_restart(LED_EVENT_PAIR_FAIL); /* 交替 3s,播完自删回常态 */
|
||||
log_info("led pair fail 3s\n");
|
||||
}
|
||||
|
||||
high_event = (uint8_t)(LED_IsEventExist(LED_EVENT_PAIR_FAIL)
|
||||
|| LED_IsEventExist(LED_EVENT_OTA));
|
||||
|
||||
/* 常态双通道(高事件占双灯期间不 add,播完自删后下拍自动恢复) */
|
||||
if (!high_event) {
|
||||
/* 红灯通道:快闪(提示/配对中) > 常亮(已配对) > 灭(未配对) */
|
||||
if (s_led.pair_hint || app_pair_is_active()) {
|
||||
LED_EventAdd(LED_EVENT_RED_PAIRING);
|
||||
} else if (usr_get_pair_flag()) {
|
||||
LED_EventAdd(LED_EVENT_RED_PAIRED);
|
||||
} else {
|
||||
LED_EventDelete(LED_EVENT_RED_PAIRED);
|
||||
LED_EventDelete(LED_EVENT_RED_PAIRING);
|
||||
}
|
||||
/* 绿灯通道:作业态(含间隔)闪,否则常亮 */
|
||||
if (app_spray_is_running()) {
|
||||
LED_EventAdd(LED_EVENT_GRN_SPRAY);
|
||||
} else {
|
||||
LED_EventAdd(LED_EVENT_GRN_IDLE);
|
||||
}
|
||||
}
|
||||
|
||||
user_led_handle();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清空全部灯事件并灭灯
|
||||
* @return 无
|
||||
*/
|
||||
void app_led_clear(void)
|
||||
{
|
||||
#if BOARD_LED_ENABLE
|
||||
LED_EventAllDelete();
|
||||
bsp_led_all_off();
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/******************************************************************************
|
||||
* @file app_led.h
|
||||
* @brief 灯效:绿=作业态、红=配对态;OTA / 配对失败占双灯(规格书第 6 章)
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.09.09
|
||||
* @version V1.0.0
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __APP_LED_H__
|
||||
#define __APP_LED_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
APP_LED_OK = 0,
|
||||
APP_LED_ERR_NOT_INIT
|
||||
} AppLed_Ret_t;
|
||||
|
||||
/**
|
||||
* @brief 灯效初始化,先全灭(事件表装载)
|
||||
* @return APP_LED_OK=成功
|
||||
*/
|
||||
AppLed_Ret_t app_led_init(void);
|
||||
|
||||
/**
|
||||
* @brief 按优先级刷新灯(每 20ms 业务节拍调用)
|
||||
* @param dt 距上次调用的毫秒数
|
||||
* @note 绿灯跟作业态(app_spray_is_running,含间隔等待);红灯跟配对态。
|
||||
* 同时推进配对会话计时(app_pair_tick_1ms)并消费成功/失败边沿。
|
||||
* @return 无
|
||||
*/
|
||||
void app_led_run(uint16_t dt);
|
||||
|
||||
/**
|
||||
* @brief 配网键满 5s 的"松手即配网"提示:红灯 100ms 快闪(绿灯不受影响)
|
||||
* @note 此时还没清绑定(要等松手),只是给按住的用户反馈
|
||||
* @return 无
|
||||
*/
|
||||
void app_led_hint_pair(void);
|
||||
|
||||
/**
|
||||
* @brief 结束"将配网"提示(配对会话已由 app_pair 接管,或改判为 OTA)
|
||||
* @return 无
|
||||
*/
|
||||
void app_led_clear_pair_hint(void);
|
||||
|
||||
/**
|
||||
* @brief 进入 OTA:清掉其它灯,挂红绿交替一直闪(最高优先级)
|
||||
* @return 无
|
||||
*/
|
||||
void app_led_request_ota(void);
|
||||
|
||||
/**
|
||||
* @brief 查询是否处于 OTA 灯效
|
||||
* @return 1=OTA 灯效中,0=否
|
||||
*/
|
||||
uint8_t app_led_is_ota(void);
|
||||
|
||||
/**
|
||||
* @brief 清空全部灯事件并灭灯
|
||||
* @return 无
|
||||
*/
|
||||
void app_led_clear(void);
|
||||
|
||||
/**
|
||||
* @brief 查询是否已初始化
|
||||
* @return APP_LED_OK=已初始化,APP_LED_ERR_NOT_INIT=未初始化
|
||||
*/
|
||||
AppLed_Ret_t app_led_get_status(void);
|
||||
|
||||
#endif /* __APP_LED_H__ */
|
||||
@@ -0,0 +1,74 @@
|
||||
/******************************************************************************
|
||||
* @file app_pair.c
|
||||
* @brief 配对会话计时实现
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.25
|
||||
* @version V1.0.0,首次发布,实现配对会话计时与成功/超时判定
|
||||
******************************************************************************/
|
||||
|
||||
#include "app_pair.h"
|
||||
#include "board_pin.h"
|
||||
#include "usr_le_api.h"
|
||||
#include "system/includes.h"
|
||||
|
||||
#define LOG_TAG_CONST APP
|
||||
#define LOG_TAG "[APP_PAIR]"
|
||||
#define LOG_INFO_ENABLE
|
||||
#include "debug.h"
|
||||
|
||||
/** 配对会话状态,替代原来散落的 s_pairing / s_pair_ms */
|
||||
typedef struct {
|
||||
uint8_t active;
|
||||
uint32_t elapsed_ms;
|
||||
} AppPairCtx_t;
|
||||
|
||||
static AppPairCtx_t s_pair;
|
||||
|
||||
/**
|
||||
* @brief 开始一次配对会话(计时清零)
|
||||
* @return 无
|
||||
*/
|
||||
void app_pair_start(void)
|
||||
{
|
||||
s_pair.active = 1;
|
||||
s_pair.elapsed_ms = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 推进配对计时并检查结果,每个业务 tick 调用一次
|
||||
* @param dt 距上次调用的毫秒数
|
||||
* @return APP_PAIR_EVT_OK=配对成功,APP_PAIR_EVT_FAIL=超时,否则 NONE
|
||||
*/
|
||||
AppPairEvt_t app_pair_tick_1ms(uint16_t dt)
|
||||
{
|
||||
if (!s_pair.active) {
|
||||
return APP_PAIR_EVT_NONE;
|
||||
}
|
||||
|
||||
/* BLE 侧绑定成功:结束本次配对会话 */
|
||||
if (usr_get_pair_flag()) {
|
||||
s_pair.active = 0;
|
||||
s_pair.elapsed_ms = 0;
|
||||
log_info("pair ok\n");
|
||||
return APP_PAIR_EVT_OK;
|
||||
}
|
||||
|
||||
s_pair.elapsed_ms += dt;
|
||||
if (s_pair.elapsed_ms >= BOARD_PAIR_TIMEOUT_MS) {
|
||||
s_pair.active = 0;
|
||||
s_pair.elapsed_ms = 0;
|
||||
log_info("pair timeout 120s, stay on\n");
|
||||
return APP_PAIR_EVT_FAIL;
|
||||
}
|
||||
|
||||
return APP_PAIR_EVT_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查询是否仍在配对会话中
|
||||
* @return 1=进行中,0=未在配对
|
||||
*/
|
||||
uint8_t app_pair_is_active(void)
|
||||
{
|
||||
return s_pair.active;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/******************************************************************************
|
||||
* @file app_pair.h
|
||||
* @brief 配对会话计时:开机配对窗口是否成功/超时,与 BLE 配对本身解耦
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.25
|
||||
* @version V1.0.0,首次发布,实现配对会话计时与成功/超时判定
|
||||
*
|
||||
* @note 真正的 BLE 广播/绑定逻辑在 usr_le_code;本模块只负责“进入配对后
|
||||
* 120s 内 usr_get_pair_flag() 是否变 1”这件事,纯状态,不碰灯/按键。
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __APP_PAIR_H__
|
||||
#define __APP_PAIR_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/** 配对结果边沿事件,仅在状态变化那一次返回一次 */
|
||||
typedef enum {
|
||||
APP_PAIR_EVT_NONE = 0,
|
||||
APP_PAIR_EVT_OK,
|
||||
APP_PAIR_EVT_FAIL
|
||||
} AppPairEvt_t;
|
||||
|
||||
/**
|
||||
* @brief 开始一次配对会话(计时清零)
|
||||
* @return 无
|
||||
*/
|
||||
void app_pair_start(void);
|
||||
|
||||
/**
|
||||
* @brief 推进配对计时并检查结果,每个业务 tick 调用一次
|
||||
* @param dt 距上次调用的毫秒数
|
||||
* @return APP_PAIR_EVT_OK=配对成功,APP_PAIR_EVT_FAIL=超时,否则 NONE
|
||||
*/
|
||||
AppPairEvt_t app_pair_tick_1ms(uint16_t dt);
|
||||
|
||||
/**
|
||||
* @brief 查询是否仍在配对会话中
|
||||
* @return 1=进行中,0=未在配对
|
||||
*/
|
||||
uint8_t app_pair_is_active(void);
|
||||
|
||||
#endif /* __APP_PAIR_H__ */
|
||||
@@ -0,0 +1,90 @@
|
||||
/******************************************************************************
|
||||
* @file app_pump.c
|
||||
* @brief 水泵双脚成对控制:PC2 供 5V + PA6 导通,同开同关
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.09.09
|
||||
* @version V1.0.0
|
||||
* @history
|
||||
* - V1.0.0, 2026.09.09, cyWu, P0 首次发布
|
||||
*
|
||||
* @note 禁止只开一只脚;上电两脚默认低、绝不自动开泵。本模块不保存"开",
|
||||
* 是否开由上层 app_spray 作业引擎决定(规格书 7.2,app_pump_set 只
|
||||
* 由 app_spray 调用)。泵无过零/继电器,请求态==实际态。
|
||||
******************************************************************************/
|
||||
|
||||
#include "system/includes.h"
|
||||
#include "app_pump.h"
|
||||
#include "bsp_hw.h"
|
||||
#include "board_pin.h"
|
||||
|
||||
#define LOG_TAG_CONST APP
|
||||
#define LOG_TAG "[APP_PUMP]"
|
||||
#define LOG_INFO_ENABLE
|
||||
#include "debug.h"
|
||||
|
||||
/** 模块状态:on 只被 app_spray 在 20ms 节拍里改写 */
|
||||
typedef struct {
|
||||
uint8_t initialized;
|
||||
volatile uint8_t on; /**< 1=泵转(两脚都已导通),0=泵停 */
|
||||
} PumpCtx_t;
|
||||
|
||||
static PumpCtx_t s_pump;
|
||||
|
||||
/**
|
||||
* @brief 初始化泵模块(GPIO 成对低已在 bsp_hw_init 完成)
|
||||
* @return APP_PUMP_OK=成功
|
||||
*/
|
||||
AppPump_Ret_t app_pump_init(void)
|
||||
{
|
||||
if (s_pump.initialized) {
|
||||
return APP_PUMP_OK;
|
||||
}
|
||||
#if BOARD_PUMP_ENABLE
|
||||
bsp_pump_set(0); /* 兜底:两脚都低 */
|
||||
s_pump.on = 0;
|
||||
s_pump.initialized = 1;
|
||||
#else
|
||||
log_info("pump disabled\n");
|
||||
#endif
|
||||
return APP_PUMP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 成对切泵:on=1 两脚同时高,on=0 两脚同时低
|
||||
* @param on 1=开,0=关
|
||||
* @return 无
|
||||
*/
|
||||
void app_pump_set(uint8_t on)
|
||||
{
|
||||
#if BOARD_PUMP_ENABLE
|
||||
if (!s_pump.initialized) {
|
||||
return;
|
||||
}
|
||||
bsp_pump_set(on ? 1 : 0);
|
||||
s_pump.on = on ? 1 : 0;
|
||||
#else
|
||||
(void)on;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读泵当前态(请求态即实际态)
|
||||
* @return 1=泵转,0=泵停
|
||||
*/
|
||||
uint8_t app_pump_get(void)
|
||||
{
|
||||
#if BOARD_PUMP_ENABLE
|
||||
return s_pump.on;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查询模块是否已初始化
|
||||
* @return APP_PUMP_OK=已初始化,APP_PUMP_ERR_NOT_INIT=未初始化
|
||||
*/
|
||||
AppPump_Ret_t app_pump_get_status(void)
|
||||
{
|
||||
return s_pump.initialized ? APP_PUMP_OK : APP_PUMP_ERR_NOT_INIT;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/******************************************************************************
|
||||
* @file app_pump.h
|
||||
* @brief 水泵双脚成对控制(PC2 供 5V + PA6 导通)
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.09.09
|
||||
* @version V1.0.0
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __APP_PUMP_H__
|
||||
#define __APP_PUMP_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
APP_PUMP_OK = 0,
|
||||
APP_PUMP_ERR_NOT_INIT
|
||||
} AppPump_Ret_t;
|
||||
|
||||
/**
|
||||
* @brief 初始化泵模块:GPIO 由 bsp_hw_init 成对置低,本函数只清状态
|
||||
* @return APP_PUMP_OK=成功
|
||||
*/
|
||||
AppPump_Ret_t app_pump_init(void);
|
||||
|
||||
/**
|
||||
* @brief 成对切泵:on=1 两脚同时高(泵转),on=0 两脚同时低(泵停)
|
||||
* @note ⚠ 唯一允许改泵的出口是 app_spray 作业引擎(规格书 7.2)。
|
||||
* @param on 1=开,0=关
|
||||
* @return 无
|
||||
*/
|
||||
void app_pump_set(uint8_t on);
|
||||
|
||||
/**
|
||||
* @brief 读泵当前态(请求态即实际态:无过零/继电器)
|
||||
* @return 1=泵转(两脚都通),0=泵停
|
||||
*/
|
||||
uint8_t app_pump_get(void);
|
||||
|
||||
/**
|
||||
* @brief 查询模块是否已初始化
|
||||
* @return APP_PUMP_OK=已初始化,APP_PUMP_ERR_NOT_INIT=未初始化
|
||||
*/
|
||||
AppPump_Ret_t app_pump_get_status(void);
|
||||
|
||||
#endif /* __APP_PUMP_H__ */
|
||||
@@ -0,0 +1,208 @@
|
||||
/******************************************************************************
|
||||
* @file app_spray.c
|
||||
* @brief 喷淋作业引擎:一轮 = 单次时长 × 次数 + 间隔(规格书第 10 章)
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.09.09
|
||||
* @version V1.0.0
|
||||
* @history
|
||||
* - V1.0.0, 2026.09.09, cyWu, P0 首次发布(默认 10s×3 次+3s,只跑一轮)
|
||||
*
|
||||
* @note 本模块是**唯一切泵者**(规格书 7.2):键/DP/联动/计划都只请求
|
||||
* "开一轮 / 停"。20ms 节拍倒数,禁止阻塞 delay。间隔等待也算作业态
|
||||
* (DP1=1、绿灯继续闪),但泵已关、不记运行秒(P2 由 app_runrec 负责)。
|
||||
******************************************************************************/
|
||||
|
||||
#include "system/includes.h"
|
||||
#include "app_spray.h"
|
||||
#include "app_pump.h"
|
||||
#include "jb_product.h"
|
||||
#include "board_pin.h"
|
||||
|
||||
#define LOG_TAG_CONST APP
|
||||
#define LOG_TAG "[APP_SPRAY]"
|
||||
#define LOG_INFO_ENABLE
|
||||
#include "debug.h"
|
||||
|
||||
/** 作业阶段 */
|
||||
typedef enum {
|
||||
SPRAY_PHASE_PUMP_ON = 0, /**< 泵开段:倒数单次时长 */
|
||||
SPRAY_PHASE_WAIT /**< 间隔段:泵关,倒数间隔(仅 interval>0 出现) */
|
||||
} SprayPhase_t;
|
||||
|
||||
/** 模块状态:全程由 20ms 业务节拍(usr_timer_tick)驱动,无跨上下文竞争 */
|
||||
typedef struct {
|
||||
uint8_t initialized;
|
||||
uint8_t state; /**< 0=空闲,1=作业中 */
|
||||
uint8_t phase; /**< 当前段(SPRAY_PHASE_*) */
|
||||
uint16_t total_count; /**< 本轮总次数(来自 DP7) */
|
||||
uint16_t round; /**< 已完成的次数 */
|
||||
uint32_t dur_ms; /**< 单次时长 ms */
|
||||
uint32_t wait_ms; /**< 间隔 ms(0=中间不停泵) */
|
||||
uint32_t remain_ms; /**< 当前段剩余 ms */
|
||||
} SprayCtx_t;
|
||||
|
||||
static SprayCtx_t s_spray;
|
||||
|
||||
/**
|
||||
* @brief 从 gDevData.spray_config 解析本轮参数(非法回退默认)
|
||||
* @note DP7 布局(规格书 10.2):Byte0-1 时长 uint16 大端、Byte2 次数、
|
||||
* Byte3-4 间隔 uint16 大端。非法 = 时长 0 或次数 0。
|
||||
* @return 无
|
||||
*/
|
||||
static void spray_parse_cfg(void)
|
||||
{
|
||||
const uint8_t *p = gDevData.spray_config;
|
||||
uint16_t dur = (uint16_t)(((uint16_t)p[0] << 8) | p[1]);
|
||||
uint8_t cnt = p[2];
|
||||
uint16_t itv = (uint16_t)(((uint16_t)p[3] << 8) | p[4]);
|
||||
|
||||
if ((dur == 0) || (cnt == 0)) {
|
||||
/* 从未收到合法 DP7(P0 无 VM,理论上 userInit 已写默认,此处兜底) */
|
||||
dur = BOARD_SPRAY_DEFAULT_DUR_S;
|
||||
cnt = BOARD_SPRAY_DEFAULT_COUNT;
|
||||
itv = BOARD_SPRAY_DEFAULT_INTERVAL_S;
|
||||
log_info("dp7 invalid, use default %us x%u + %us\n",
|
||||
(unsigned)dur, (unsigned)cnt, (unsigned)itv);
|
||||
}
|
||||
|
||||
s_spray.dur_ms = (uint32_t)dur * 1000u;
|
||||
s_spray.wait_ms = (uint32_t)itv * 1000u;
|
||||
s_spray.total_count = cnt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化作业引擎(泵保持关,参数待 start 时解析)
|
||||
* @return APP_SPRAY_OK=成功
|
||||
*/
|
||||
AppSpray_Ret_t app_spray_init(void)
|
||||
{
|
||||
if (s_spray.initialized) {
|
||||
return APP_SPRAY_OK;
|
||||
}
|
||||
#if BOARD_SPRAY_ENABLE
|
||||
app_pump_set(0); /* 上电绝不喷 */
|
||||
s_spray.state = 0;
|
||||
s_spray.phase = SPRAY_PHASE_PUMP_ON;
|
||||
s_spray.round = 0;
|
||||
s_spray.remain_ms = 0;
|
||||
s_spray.initialized = 1;
|
||||
#else
|
||||
log_info("spray disabled\n");
|
||||
#endif
|
||||
return APP_SPRAY_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 开一轮(幂等:作业中不重开)
|
||||
* @return 无
|
||||
*/
|
||||
void app_spray_start(void)
|
||||
{
|
||||
if (!s_spray.initialized) {
|
||||
return;
|
||||
}
|
||||
if (s_spray.state != 0) {
|
||||
return; /* 已在作业中:保持当前轮(规格书 10.5 幂等) */
|
||||
}
|
||||
|
||||
spray_parse_cfg();
|
||||
s_spray.round = 0;
|
||||
s_spray.phase = SPRAY_PHASE_PUMP_ON;
|
||||
s_spray.remain_ms = s_spray.dur_ms;
|
||||
s_spray.state = 1;
|
||||
app_pump_set(1); /* 两脚同时高 */
|
||||
log_info("spray start dur=%u ms x%u wait=%u ms\n",
|
||||
(unsigned)s_spray.dur_ms, (unsigned)s_spray.total_count,
|
||||
(unsigned)s_spray.wait_ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 停:立刻两脚拉低、结束作业
|
||||
* @return 无
|
||||
*/
|
||||
void app_spray_stop(void)
|
||||
{
|
||||
if (!s_spray.initialized) {
|
||||
return;
|
||||
}
|
||||
if (s_spray.state == 0) {
|
||||
return; /* 已空闲,保持关(幂等) */
|
||||
}
|
||||
app_pump_set(0); /* 两脚同时低 */
|
||||
s_spray.state = 0;
|
||||
s_spray.round = 0;
|
||||
s_spray.remain_ms = 0;
|
||||
log_info("spray stop\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 作业节拍:段倒数与切泵(20ms 调用)
|
||||
* @param dt 距上次调用的毫秒数
|
||||
* @return 无
|
||||
*/
|
||||
void app_spray_poll(uint16_t dt)
|
||||
{
|
||||
if (!s_spray.initialized || (s_spray.state == 0)) {
|
||||
return;
|
||||
}
|
||||
if (dt == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_spray.phase == SPRAY_PHASE_PUMP_ON) {
|
||||
/* 泵开段倒数 */
|
||||
if (s_spray.remain_ms <= dt) {
|
||||
s_spray.round++;
|
||||
if (s_spray.round >= s_spray.total_count) {
|
||||
/* 次数用尽:作业自然结束,泵关 */
|
||||
app_pump_set(0);
|
||||
s_spray.state = 0;
|
||||
s_spray.remain_ms = 0;
|
||||
log_info("spray round done, pump off\n");
|
||||
return;
|
||||
}
|
||||
if (s_spray.wait_ms > 0) {
|
||||
/* 进入间隔段:泵关等待 */
|
||||
s_spray.phase = SPRAY_PHASE_WAIT;
|
||||
s_spray.remain_ms = s_spray.wait_ms;
|
||||
app_pump_set(0);
|
||||
} else {
|
||||
/* 间隔=0:不停泵,马上喷下一次 */
|
||||
s_spray.remain_ms = s_spray.dur_ms;
|
||||
}
|
||||
} else {
|
||||
s_spray.remain_ms -= dt;
|
||||
}
|
||||
} else {
|
||||
/* 间隔段倒数 */
|
||||
if (s_spray.remain_ms <= dt) {
|
||||
s_spray.phase = SPRAY_PHASE_PUMP_ON;
|
||||
s_spray.remain_ms = s_spray.dur_ms;
|
||||
app_pump_set(1); /* 两脚同时高 */
|
||||
} else {
|
||||
s_spray.remain_ms -= dt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查询是否处于作业态(含间隔等待,整轮期间为 1)
|
||||
* @return 1=作业中,0=空闲
|
||||
*/
|
||||
uint8_t app_spray_is_running(void)
|
||||
{
|
||||
#if BOARD_SPRAY_ENABLE
|
||||
return (uint8_t)(s_spray.state != 0);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查询模块是否已初始化
|
||||
* @return APP_SPRAY_OK=已初始化,APP_SPRAY_ERR_NOT_INIT=未初始化
|
||||
*/
|
||||
AppSpray_Ret_t app_spray_get_status(void)
|
||||
{
|
||||
return s_spray.initialized ? APP_SPRAY_OK : APP_SPRAY_ERR_NOT_INIT;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/******************************************************************************
|
||||
* @file app_spray.h
|
||||
* @brief 喷淋作业引擎:一轮 = 单次时长 × 次数 + 间隔(规格书第 10 章)
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.09.09
|
||||
* @version V1.0.0
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __APP_SPRAY_H__
|
||||
#define __APP_SPRAY_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
APP_SPRAY_OK = 0,
|
||||
APP_SPRAY_ERR_NOT_INIT
|
||||
} AppSpray_Ret_t;
|
||||
|
||||
/**
|
||||
* @brief 初始化作业引擎(读 DP7 有效配置,泵保持关)
|
||||
* @return APP_SPRAY_OK=成功
|
||||
*/
|
||||
AppSpray_Ret_t app_spray_init(void);
|
||||
|
||||
/**
|
||||
* @brief 开一轮:用 RAM 里最近有效 DP7 配置(非法回退 10s×3 次+3s)
|
||||
* @note 幂等:已在作业中则保持当前轮不重开(规格书 10.5,PB1/DP1 语义)。
|
||||
* @return 无
|
||||
*/
|
||||
void app_spray_start(void);
|
||||
|
||||
/**
|
||||
* @brief 停:立刻两脚拉低、结束作业(间隔等待中也立即停)
|
||||
* @return 无
|
||||
*/
|
||||
void app_spray_stop(void);
|
||||
|
||||
/**
|
||||
* @brief 作业节拍:时长/间隔倒数,到点切泵(每 20ms 调用,可中断上下文)
|
||||
* @param dt 距上次调用的毫秒数
|
||||
* @return 无
|
||||
*/
|
||||
void app_spray_poll(uint16_t dt);
|
||||
|
||||
/**
|
||||
* @brief 查询是否处于作业态(含间隔等待,整轮期间为 1)
|
||||
* @return 1=作业中,0=空闲
|
||||
*/
|
||||
uint8_t app_spray_is_running(void);
|
||||
|
||||
/**
|
||||
* @brief 查询模块是否已初始化
|
||||
* @return APP_SPRAY_OK=已初始化,APP_SPRAY_ERR_NOT_INIT=未初始化
|
||||
*/
|
||||
AppSpray_Ret_t app_spray_get_status(void);
|
||||
|
||||
#endif /* __APP_SPRAY_H__ */
|
||||
@@ -0,0 +1,265 @@
|
||||
/******************************************************************************
|
||||
* @file key.c
|
||||
* @brief 按键状态机实现
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.25
|
||||
* @version V1.0.0,首次发布,实现按键短按/长按/释放状态机
|
||||
******************************************************************************/
|
||||
|
||||
#include "key.h"
|
||||
#include <stddef.h>
|
||||
|
||||
static uint32_t s_key_count_time;
|
||||
static uint8_t s_key_num = USR_KEY_COUNT;
|
||||
keyCategory_t keyTable[USR_KEY_COUNT];
|
||||
|
||||
/**
|
||||
* @brief 读按键电平到 fsm
|
||||
* @param key 状态机
|
||||
* @return 1=已读,0=被屏蔽
|
||||
*/
|
||||
static uint8_t get_key_level(keyFSM_t *key)
|
||||
{
|
||||
if (key->keyShield == KEY_DISABLE) {
|
||||
return 0;
|
||||
}
|
||||
if (key->keyReadValue == NULL) {
|
||||
return 0;
|
||||
}
|
||||
key->keyLevel = (key->keyReadValue() == key->keyDownLevel) ? Bit_SET : Bit_RESET;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 单击 / 双击间隔判定
|
||||
* @param click 状态机
|
||||
* @return 1=可以出事件
|
||||
*/
|
||||
static uint8_t key_click_frequency(keyFSM_t *click)
|
||||
{
|
||||
click->keyInterval++;
|
||||
switch (click->keyFrequency) {
|
||||
case CLICK:
|
||||
if (click->keyInterval >= (KEY_INTERVAL / (DEBOUNCE_TIME * KEY_TIMER_MS))) {
|
||||
click->eventType = SHORT_Event;
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case DbLCLICK:
|
||||
if (click->keyInterval >= (KEY_INTERVAL / (DEBOUNCE_TIME * KEY_TIMER_MS))) {
|
||||
click->eventType = DBCL_Event;
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按键状态机步进
|
||||
* @param key_buf 状态机
|
||||
* @return 1=已处理
|
||||
*/
|
||||
static uint8_t read_key_status(keyFSM_t *key_buf)
|
||||
{
|
||||
if (!get_key_level(key_buf)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (key_buf->keyStatus) {
|
||||
case KEY_ST_NULL:
|
||||
if (key_buf->keyLevel == Bit_SET) {
|
||||
key_buf->keyStatus = KEY_ST_SURE;
|
||||
}
|
||||
break;
|
||||
case KEY_ST_SURE:
|
||||
if (key_buf->keyLevel == Bit_SET) {
|
||||
key_buf->eventType = DOWN_Event;
|
||||
key_buf->keyStatus = KEY_ST_DOWN;
|
||||
key_buf->keyCount = 0;
|
||||
key_buf->keyLongFlag = 1;
|
||||
} else {
|
||||
key_buf->keyStatus = KEY_ST_NULL;
|
||||
}
|
||||
break;
|
||||
case KEY_ST_DOWN:
|
||||
if (key_buf->keyLevel != Bit_SET) {
|
||||
if (key_buf->keyShortFlag == 0) {
|
||||
key_buf->keyShortFlag = 1;
|
||||
key_buf->keyFrequency++;
|
||||
key_buf->keyInterval = 0;
|
||||
}
|
||||
if (key_click_frequency(key_buf)) {
|
||||
key_buf->keyFrequency = 0;
|
||||
key_buf->keyStatus = KEY_ST_NULL;
|
||||
}
|
||||
if ((key_buf->keyCount >= key_buf->keyLongTime / DEBOUNCE_TIME)
|
||||
&& (key_buf->keyCount < key_buf->keyLastTime / DEBOUNCE_TIME)) {
|
||||
key_buf->keyFrequency = 0;
|
||||
key_buf->keyStatus = KEY_ST_NULL;
|
||||
key_buf->eventType = RELEASE_Event;
|
||||
}
|
||||
key_buf->keyCount = 0;
|
||||
key_buf->keyLongFlag = 1;
|
||||
} else {
|
||||
if ((++key_buf->keyCount >= key_buf->keyLastTime / DEBOUNCE_TIME)) {
|
||||
key_buf->keyCount = 0;
|
||||
key_buf->keyFrequency = 0;
|
||||
key_buf->keyStatus = KEY_ST_LONG;
|
||||
key_buf->eventType = LAST_Event;
|
||||
key_buf->keyLongFlag = 1;
|
||||
}
|
||||
if ((key_buf->keyCount >= key_buf->keyLongTime / DEBOUNCE_TIME)
|
||||
&& (key_buf->keyCount < key_buf->keyLastTime / DEBOUNCE_TIME)) {
|
||||
key_buf->keyFrequency = 0;
|
||||
if (key_buf->keyLongFlag == 1) {
|
||||
key_buf->eventType = LONG_Event;
|
||||
key_buf->keyLongFlag = 0;
|
||||
}
|
||||
}
|
||||
key_buf->keyShortFlag = 0;
|
||||
}
|
||||
break;
|
||||
case KEY_ST_LONG:
|
||||
if (key_buf->keyLevel != Bit_SET) {
|
||||
key_buf->keyStatus = KEY_ST_NULL;
|
||||
key_buf->eventType = RELEASE_Event;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 扫描全部按键状态机
|
||||
* @return 无
|
||||
*/
|
||||
static void key_event_process(void)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < s_key_num; i++) {
|
||||
(void)read_key_status(&keyTable[i].fsm);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 1ms 调用一次;满 DEBOUNCE_TIME 才真正采样
|
||||
* @return 无
|
||||
*/
|
||||
void keyCheckProcess(void)
|
||||
{
|
||||
s_key_count_time++;
|
||||
if (s_key_count_time >= (DEBOUNCE_TIME / KEY_TIMER_MS)) {
|
||||
s_key_count_time = 0;
|
||||
key_event_process();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 拷贝产品按键表
|
||||
* @param keys 按键数组,长度 USR_KEY_COUNT
|
||||
* @return 无
|
||||
*/
|
||||
void keyParaInit(keyCategory_t *keys)
|
||||
{
|
||||
if (keys == NULL) {
|
||||
return;
|
||||
}
|
||||
if (USR_KEY_COUNT >= KEY_MAX_NUMBER) {
|
||||
s_key_num = KEY_MAX_NUMBER;
|
||||
}
|
||||
memcpy(keyTable, keys, sizeof(keyCategory_t) * s_key_num);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 运行中改长按/连按时长并复位状态
|
||||
* @param key_event 按键索引
|
||||
* @param key_param 新参数
|
||||
* @return 无
|
||||
*/
|
||||
void setKeyEventParams(uint8_t key_event, keyCategory_t key_param)
|
||||
{
|
||||
if (key_event >= s_key_num) {
|
||||
return;
|
||||
}
|
||||
keyTable[key_event].fsm.keyShield = key_param.fsm.keyShield;
|
||||
keyTable[key_event].fsm.keyLongTime = key_param.fsm.keyLongTime;
|
||||
keyTable[key_event].fsm.keyLastTime = key_param.fsm.keyLastTime;
|
||||
keyTable[key_event].fsm.keyStatus = KEY_ST_NULL;
|
||||
keyTable[key_event].fsm.eventType = NULL_Event;
|
||||
keyTable[key_event].fsm.keyCount = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 派发按键事件回调
|
||||
* @return 无
|
||||
*/
|
||||
void keyHandle(void)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < s_key_num; i++) {
|
||||
if (keyTable[i].fsm.eventType == NULL_Event) {
|
||||
continue;
|
||||
}
|
||||
switch (keyTable[i].fsm.eventType) {
|
||||
case RELEASE_Event:
|
||||
if (keyTable[i].func.releasePressCb != NULL) {
|
||||
keyTable[i].func.releasePressCb();
|
||||
}
|
||||
keyTable[i].fsm.eventType = NULL_Event;
|
||||
break;
|
||||
case SHORT_Event:
|
||||
if (keyTable[i].func.ShortPressCb != NULL) {
|
||||
keyTable[i].func.ShortPressCb();
|
||||
}
|
||||
keyTable[i].fsm.eventType = NULL_Event;
|
||||
break;
|
||||
case DOWN_Event:
|
||||
if (keyTable[i].func.downPressCb != NULL) {
|
||||
keyTable[i].func.downPressCb();
|
||||
}
|
||||
keyTable[i].fsm.eventType = NULL_Event;
|
||||
break;
|
||||
case LONG_Event:
|
||||
if (keyTable[i].func.longPressCb != NULL) {
|
||||
keyTable[i].func.longPressCb();
|
||||
}
|
||||
keyTable[i].fsm.eventType = NULL_Event;
|
||||
break;
|
||||
case LAST_Event:
|
||||
if (keyTable[i].func.lastPressCb != NULL) {
|
||||
keyTable[i].func.lastPressCb();
|
||||
}
|
||||
break;
|
||||
case DBCL_Event:
|
||||
if (keyTable[i].func.dbclPressCb != NULL) {
|
||||
keyTable[i].func.dbclPressCb();
|
||||
}
|
||||
keyTable[i].fsm.eventType = NULL_Event;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 复位指定按键状态机
|
||||
* @param key_index 按键索引
|
||||
* @return 无
|
||||
*/
|
||||
void reset_key_Status(keyList key_index)
|
||||
{
|
||||
if (key_index >= USR_KEY_COUNT) {
|
||||
return;
|
||||
}
|
||||
keyTable[key_index].fsm.keyStatus = KEY_ST_NULL;
|
||||
keyTable[key_index].fsm.eventType = NULL_Event;
|
||||
keyTable[key_index].fsm.keyCount = 0;
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/******************************************************************************
|
||||
* @file key.h
|
||||
* @brief 按键状态机:短按 / 长按 / 释放(两键)
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.09.09
|
||||
* @version V1.0.0
|
||||
* @history
|
||||
* - V1.0.0, 2026.09.09, cyWu, CBR2 两键版(PB1 喷淋 / PA0 配网),FSM 同加热垫
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __KEY_H__
|
||||
#define __KEY_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
/*============================================================================*/
|
||||
/* 宏定义 */
|
||||
/*============================================================================*/
|
||||
|
||||
#define KEY_TIMER_MS 1
|
||||
#define KEY_MAX_NUMBER 12
|
||||
#define DEBOUNCE_TIME 30
|
||||
#define KEY_INTERVAL 30
|
||||
|
||||
#define CLICK 1
|
||||
#define DbLCLICK 2
|
||||
|
||||
typedef enum {
|
||||
USR_KEY_SPRAY = 0, /**< PB1 喷淋键:只短按(启/停喷淋) */
|
||||
USR_KEY_PAIR, /**< PA0 配网键:长按 5s 提示 / 12s OTA */
|
||||
USR_KEY_COUNT
|
||||
} keyList;
|
||||
|
||||
typedef enum {
|
||||
Bit_RESET = 0,
|
||||
Bit_SET
|
||||
} BitAction_t;
|
||||
|
||||
typedef enum {
|
||||
KEY_ST_NULL = 0,
|
||||
KEY_ST_RELEASE,
|
||||
KEY_ST_SURE,
|
||||
KEY_ST_UP,
|
||||
KEY_ST_DOWN,
|
||||
KEY_ST_LONG
|
||||
} keyStatus_t;
|
||||
|
||||
typedef enum {
|
||||
NULL_Event = 0,
|
||||
DOWN_Event,
|
||||
SHORT_Event,
|
||||
LONG_Event,
|
||||
LAST_Event,
|
||||
DBCL_Event,
|
||||
RELEASE_Event
|
||||
} keyEvent_t;
|
||||
|
||||
typedef enum {
|
||||
KEY_DISABLE = 0,
|
||||
KEY_ENABLE = !KEY_DISABLE
|
||||
} keyEnable_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t keyShortFlag;
|
||||
uint8_t keyLongFlag;
|
||||
uint8_t keyInterval;
|
||||
uint8_t keyFrequency;
|
||||
uint16_t keyLongTime;
|
||||
uint16_t keyLastTime;
|
||||
uint32_t keyCount;
|
||||
keyEnable_t keyShield;
|
||||
BitAction_t keyLevel;
|
||||
BitAction_t keyDownLevel;
|
||||
keyStatus_t keyStatus;
|
||||
keyEvent_t eventType;
|
||||
uint8_t (*keyReadValue)(void);
|
||||
} keyFSM_t;
|
||||
|
||||
typedef struct {
|
||||
void (*nullPressCb)(void);
|
||||
void (*releasePressCb)(void);
|
||||
void (*downPressCb)(void);
|
||||
void (*ShortPressCb)(void);
|
||||
void (*longPressCb)(void);
|
||||
void (*lastPressCb)(void);
|
||||
void (*dbclPressCb)(void);
|
||||
} keyFunc_t;
|
||||
|
||||
typedef struct {
|
||||
keyFSM_t fsm;
|
||||
keyFunc_t func;
|
||||
} keyCategory_t;
|
||||
|
||||
void keyParaInit(keyCategory_t *keys);
|
||||
void setKeyEventParams(uint8_t key_event, keyCategory_t key_param);
|
||||
void keyCheckProcess(void);
|
||||
void keyHandle(void);
|
||||
void reset_key_Status(keyList key_index);
|
||||
|
||||
#endif /* __KEY_H__ */
|
||||
@@ -0,0 +1,201 @@
|
||||
/******************************************************************************
|
||||
* @file key_manager.c
|
||||
* @brief 双键管理:PB1 喷淋短按;PA0 按住分级 5s 配对 / 12s OTA
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.09.09
|
||||
* @version V1.0.0
|
||||
* @history
|
||||
* - V1.0.0, 2026.09.09, cyWu, P0 双键版(FSM 复用 key.c,逻辑同加热垫单键)
|
||||
*
|
||||
* @note 规格书 5.2:喷淋键只短按(配网/OTA 分级只做在配网键上,禁止混淆)。
|
||||
* PB1 满 5s 后松手被 FSM 归为 RELEASE_Event,回调为空 = 无动作;
|
||||
* PA0 满 5s LONG_Event 只切红灯快闪,清绑定等动作延后到任务上下文。
|
||||
******************************************************************************/
|
||||
|
||||
#include "system/includes.h"
|
||||
#include "key_manager.h"
|
||||
#include "bsp_hw.h"
|
||||
#include "board_pin.h"
|
||||
|
||||
#define LOG_TAG_CONST APP
|
||||
#define LOG_TAG "[KEY]"
|
||||
#define LOG_INFO_ENABLE
|
||||
#include "debug.h"
|
||||
|
||||
/** 模块状态:每键一个"上电按住"锁 */
|
||||
typedef struct {
|
||||
uint8_t initialized;
|
||||
uint8_t boot_key_lock[USR_KEY_COUNT]; /**< 1=该键上电时还按着,等松开才认短按 */
|
||||
uint8_t ota_fired; /**< 1=PA0 已到点触发 OTA,本次松手不再提交配对 */
|
||||
} KeyManagerCtx_t;
|
||||
|
||||
static KeyManagerCtx_t s_km;
|
||||
|
||||
/**
|
||||
* @brief 读喷淋键:1=按下
|
||||
* @return 1=按下,0=松开
|
||||
*/
|
||||
static uint8_t key_spray_read(void)
|
||||
{
|
||||
return bsp_key_spray_is_pressed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读配网键:1=按下
|
||||
* @return 1=按下,0=松开
|
||||
*/
|
||||
static uint8_t key_pair_read(void)
|
||||
{
|
||||
return bsp_key_pair_is_pressed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 喷淋键短按(SHORT_Event,松手 <5s):上电未松手前忽略,否则启/停喷淋
|
||||
* @return 无
|
||||
*/
|
||||
static void key_spray_short(void)
|
||||
{
|
||||
if (s_km.boot_key_lock[USR_KEY_SPRAY]) {
|
||||
s_km.boot_key_lock[USR_KEY_SPRAY] = 0; /* 上电松手当成释放,不当短按开喷 */
|
||||
log_info("spray key released at boot\n");
|
||||
return;
|
||||
}
|
||||
usr_on_key_short();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 配网键长按到点(LONG_Event,满 5s 仍按住):挂红灯快闪提示
|
||||
* @note ⚠ 跑在 20ms 硬件定时器中断上下文:只准打标志 + 切灯效事件(纯 RAM)。
|
||||
* 清绑定(写 VM)留给 app_function_loop(任务上下文)。
|
||||
* @return 无
|
||||
*/
|
||||
static void key_pair_hint(void)
|
||||
{
|
||||
usr_on_key_pair_hint();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 配网键连按到点(LAST_Event,满 12s 仍按住,到点即触发不等松手):进 OTA
|
||||
* @note ⚠ 同上,只置标志;usr_app_ota_init 由 app_function_loop 执行。
|
||||
* key.c 对 LAST_Event 不清 eventType,松手前每 20ms 都会再派发一次,
|
||||
* 用 ota_fired 挡住重复调用。
|
||||
* @return 无
|
||||
*/
|
||||
static void key_pair_ota_start(void)
|
||||
{
|
||||
if (s_km.ota_fired) {
|
||||
return;
|
||||
}
|
||||
s_km.ota_fired = 1;
|
||||
usr_on_key_ota_start();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 配网键释放(RELEASE_Event,按满 5s~12s 间松手):未进 OTA 则提交配对
|
||||
* @note 按满 12s 已触发 OTA 的这次按住,松手不再提交配对(ota_fired 拦住)。
|
||||
* @return 无
|
||||
*/
|
||||
static void key_pair_release(void)
|
||||
{
|
||||
s_km.boot_key_lock[USR_KEY_PAIR] = 0;
|
||||
if (!s_km.ota_fired) {
|
||||
usr_on_key_pair_start();
|
||||
}
|
||||
s_km.ota_fired = 0;
|
||||
}
|
||||
|
||||
static keyCategory_t s_keys[USR_KEY_COUNT] = {
|
||||
[USR_KEY_SPRAY] = {
|
||||
/* 只短按:<5s 松手 SHORT_Event;满 5s 的 LONG/RELEASE、12s 的 LAST 全部空回调 */
|
||||
.fsm.keyShield = KEY_ENABLE,
|
||||
.fsm.keyDownLevel = Bit_SET,
|
||||
.fsm.eventType = NULL_Event,
|
||||
.fsm.keyLongTime = BOARD_KEY_SPRAY_SHORT_MS, /* 短按窗口上限 */
|
||||
.fsm.keyLastTime = BOARD_KEY_OTA_MS,
|
||||
.fsm.keyReadValue = key_spray_read,
|
||||
.func.ShortPressCb = key_spray_short,
|
||||
},
|
||||
[USR_KEY_PAIR] = {
|
||||
.fsm.keyShield = KEY_ENABLE,
|
||||
.fsm.keyDownLevel = Bit_SET,
|
||||
.fsm.eventType = NULL_Event,
|
||||
.fsm.keyLongTime = BOARD_KEY_PAIR_MS, /* 满 5s 仍按住 -> longPressCb */
|
||||
.fsm.keyLastTime = BOARD_KEY_OTA_MS, /* 满 12s 仍按住 -> lastPressCb */
|
||||
.fsm.keyReadValue = key_pair_read,
|
||||
.func.releasePressCb = key_pair_release,
|
||||
.func.longPressCb = key_pair_hint,
|
||||
.func.lastPressCb = key_pair_ota_start,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 装载按键表
|
||||
* @return 无
|
||||
*/
|
||||
void user_key_init(void)
|
||||
{
|
||||
if (s_km.initialized) {
|
||||
return;
|
||||
}
|
||||
keyParaInit(s_keys);
|
||||
s_km.boot_key_lock[USR_KEY_SPRAY] = 0;
|
||||
s_km.boot_key_lock[USR_KEY_PAIR] = 0;
|
||||
s_km.ota_fired = 0;
|
||||
s_km.initialized = 1;
|
||||
log_info("key init spray=PB1 pair=PA0\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 上电后:键还按着则锁住短按,松开才认短按
|
||||
* @note 只锁短按,**不锁长按分级**:上电就按住 PA0 5s 进配对是受支持的用法。
|
||||
* @return 无
|
||||
*/
|
||||
void user_key_lock_if_held(void)
|
||||
{
|
||||
uint8_t i;
|
||||
|
||||
for (i = 0; i < USR_KEY_COUNT; i++) {
|
||||
reset_key_Status((keyList)i);
|
||||
}
|
||||
if (bsp_key_spray_is_pressed()) {
|
||||
s_km.boot_key_lock[USR_KEY_SPRAY] = 1; /* 防一上电就开喷 */
|
||||
log_info("spray key held at boot, wait release\n");
|
||||
}
|
||||
if (bsp_key_pair_is_pressed()) {
|
||||
s_km.boot_key_lock[USR_KEY_PAIR] = 1; /* PA0 无短按动作,仅保持状态一致 */
|
||||
log_info("pair key held at boot\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按真实毫秒推进状态机
|
||||
* @param dt 距上次调用的毫秒数
|
||||
* @return 无
|
||||
*/
|
||||
void user_key_scan(uint16_t dt)
|
||||
{
|
||||
uint16_t i;
|
||||
|
||||
if (!s_km.initialized) {
|
||||
return;
|
||||
}
|
||||
if (dt == 0) {
|
||||
dt = 1;
|
||||
}
|
||||
/* 按键库按 1ms 步进,业务节拍是 20ms,这里循环 dt 次对齐真实时间 */
|
||||
for (i = 0; i < dt; i++) {
|
||||
keyCheckProcess();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 派发按键回调
|
||||
* @return 无
|
||||
*/
|
||||
void user_key_handle(void)
|
||||
{
|
||||
if (!s_km.initialized) {
|
||||
return;
|
||||
}
|
||||
keyHandle();
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/******************************************************************************
|
||||
* @file key_manager.h
|
||||
* @brief 双键:PB1 喷淋短按;PA0 按住 5s 配对 / 12s OTA
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.09.09
|
||||
* @version V1.0.0
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __KEY_MANAGER_H__
|
||||
#define __KEY_MANAGER_H__
|
||||
|
||||
#include "key.h"
|
||||
|
||||
/**
|
||||
* @brief 业务层喷淋键短按回调(app_function 实现):空闲开一轮 / 作业中停
|
||||
* @return 无
|
||||
*/
|
||||
void usr_on_key_short(void);
|
||||
|
||||
/**
|
||||
* @brief 业务层"配网键按住满 5s"回调(app_function 实现):切红灯快闪提示,
|
||||
* 尚未清绑定
|
||||
* @return 无
|
||||
*/
|
||||
void usr_on_key_pair_hint(void);
|
||||
|
||||
/**
|
||||
* @brief 业务层"配网键按满 5s 后松手"回调(app_function 实现):提交配对
|
||||
* @note 只置标志;清绑定(写 VM)在 app_function_loop(任务上下文)执行
|
||||
* @return 无
|
||||
*/
|
||||
void usr_on_key_pair_start(void);
|
||||
|
||||
/**
|
||||
* @brief 业务层"配网键按住满 12s"回调(app_function 实现):到点即进 OTA
|
||||
* @note 只置标志;usr_app_ota_init 在 app_function_loop(任务上下文)执行
|
||||
* @return 无
|
||||
*/
|
||||
void usr_on_key_ota_start(void);
|
||||
|
||||
/**
|
||||
* @brief 装载按键表(两键:PB1 喷淋 / PA0 配网)
|
||||
* @return 无
|
||||
*/
|
||||
void user_key_init(void);
|
||||
|
||||
/**
|
||||
* @brief 上电后:某键还按着则锁该键短按,松开才认短按(不挡长按分级)
|
||||
* @note 规格书 5.1:上电若键仍按着只锁该键短按(防一上电就开喷),
|
||||
* 不锁配网键的 5s / 12s。
|
||||
* @return 无
|
||||
*/
|
||||
void user_key_lock_if_held(void);
|
||||
|
||||
/**
|
||||
* @brief 按真实毫秒推进状态机(20ms 节拍会循环 dt 次)
|
||||
* @param dt 距上次调用的毫秒数
|
||||
* @return 无
|
||||
*/
|
||||
void user_key_scan(uint16_t dt);
|
||||
|
||||
/**
|
||||
* @brief 派发按键回调
|
||||
* @return 无
|
||||
*/
|
||||
void user_key_handle(void);
|
||||
|
||||
#endif /* __KEY_MANAGER_H__ */
|
||||
@@ -0,0 +1,328 @@
|
||||
/******************************************************************************
|
||||
* @file led.c
|
||||
* @brief LED 事件框架实现:只改 ledMask 对应通道,红绿互不抢
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.25
|
||||
* @version V1.0.0
|
||||
* @history
|
||||
* - V1.0.0, 2026.08.25, cyWu, 首次发布,实现按通道掩码独立开关的 LED 事件框架
|
||||
******************************************************************************/
|
||||
|
||||
#include "led.h"
|
||||
#include <stddef.h>
|
||||
|
||||
static LED_EventTableItem_t s_led_priority_table[PRIORITY_MAX];
|
||||
static LED_EventTableItem_t s_led_event_table[LED_EVENT_MAX];
|
||||
static LED_SetState_t s_led_set_state;
|
||||
static TIME_Get_t s_time_get;
|
||||
|
||||
static uint32_t get_tick_diff(uint32_t meiosis);
|
||||
static uint8_t led_lighting_update(LED_Config_t *led);
|
||||
#if (BLINK_ENABLE)
|
||||
static uint8_t led_blinking_update(LED_Config_t *led);
|
||||
#endif
|
||||
#if (ALTERNATING_BLINK_ENABLE)
|
||||
static uint8_t led_alternating_update(LED_Config_t *led);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief 注册写灯回调,只对掩码里的通道动手
|
||||
* @param setState 回调,led=通道掩码,state=LED_ON/OFF
|
||||
* @return 无
|
||||
*/
|
||||
void LED_SetStateInit(LED_SetState_t setState)
|
||||
{
|
||||
s_led_set_state = setState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注册毫秒时间戳回调
|
||||
* @param getTime 回调
|
||||
* @return 无
|
||||
*/
|
||||
void SET_TimeGetInit(TIME_Get_t getTime)
|
||||
{
|
||||
s_time_get = getTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 拷贝产品事件表
|
||||
* @param ledEvent 长度必须为 LED_EVENT_MAX
|
||||
* @return 无
|
||||
*/
|
||||
void LED_EventTableInit(LED_EventTableItem_t ledEvent[])
|
||||
{
|
||||
if (ledEvent == NULL) {
|
||||
return;
|
||||
}
|
||||
memset(s_led_priority_table, 0, sizeof(s_led_priority_table));
|
||||
memset(s_led_event_table, 0, sizeof(s_led_event_table));
|
||||
memcpy(s_led_event_table, ledEvent, sizeof(s_led_event_table));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 把事件挂到对应优先级槽;同优先级会被覆盖
|
||||
* @param ledEvent 事件号
|
||||
* @return 无
|
||||
*/
|
||||
void LED_EventAdd(LED_Event_t ledEvent)
|
||||
{
|
||||
size_t i;
|
||||
LED_EventTableItem_t *item;
|
||||
LED_EventPriority_t prio;
|
||||
|
||||
if ((s_time_get == NULL) || (s_led_set_state == NULL)) {
|
||||
return;
|
||||
}
|
||||
if (ledEvent >= LED_EVENT_MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < LED_EVENT_MAX; i++) {
|
||||
item = &s_led_event_table[i];
|
||||
if (item->event != ledEvent) {
|
||||
continue;
|
||||
}
|
||||
prio = item->priority;
|
||||
if (prio >= PRIORITY_MAX) {
|
||||
return;
|
||||
}
|
||||
if (s_led_priority_table[prio].event != ledEvent) {
|
||||
item->config.currentTime = s_time_get();
|
||||
if (s_led_priority_table[prio].event != LED_EVENT_NONE) {
|
||||
/* 同优先级换事件:只关掉旧灯里新事件用不到的通道 */
|
||||
s_led_set_state((uint16_t)(s_led_priority_table[prio].config.ledMask
|
||||
^ item->config.ledMask),
|
||||
LED_OFF);
|
||||
}
|
||||
}
|
||||
s_led_priority_table[prio] = *item;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清空优先级表(不改 GPIO,由调用方灭灯)
|
||||
* @return 无
|
||||
*/
|
||||
void LED_EventAllDelete(void)
|
||||
{
|
||||
memset(s_led_priority_table, 0, sizeof(s_led_priority_table));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 删除指定事件并关掉它占用的通道
|
||||
* @param ledEvent 事件号
|
||||
* @return 无
|
||||
*/
|
||||
void LED_EventDelete(LED_Event_t ledEvent)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < PRIORITY_MAX; i++) {
|
||||
if (s_led_priority_table[i].event != ledEvent) {
|
||||
continue;
|
||||
}
|
||||
memset(&s_led_priority_table[i], 0, sizeof(LED_EventTableItem_t));
|
||||
if ((s_led_set_state != NULL) && (ledEvent < LED_EVENT_MAX)) {
|
||||
s_led_set_state(s_led_event_table[ledEvent].config.ledMask, LED_OFF);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从高优先级往下跑事件
|
||||
* @note handler 返回 1 会拦住更低优先级。红绿要共存时 handler 必须返回 0
|
||||
* @return 无
|
||||
*/
|
||||
void LED_EventHandle(void)
|
||||
{
|
||||
int i;
|
||||
uint8_t ret;
|
||||
|
||||
for (i = (int)PRIORITY_MAX - 1; i >= 0; i--) {
|
||||
if (s_led_priority_table[i].event == LED_EVENT_NONE) {
|
||||
continue;
|
||||
}
|
||||
if (s_led_priority_table[i].ledEventHandler == NULL) {
|
||||
continue;
|
||||
}
|
||||
ret = s_led_priority_table[i].ledEventHandler(&s_led_priority_table[i].config);
|
||||
if (ret) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 是否有事件在跑
|
||||
* @return 1=有,0=无
|
||||
*/
|
||||
uint8_t LED_IsEventActive(void)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < PRIORITY_MAX; i++) {
|
||||
if (s_led_priority_table[i].event != LED_EVENT_NONE) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 指定事件是否挂在优先级表里
|
||||
* @param ledEvent 事件号
|
||||
* @return 1=在,0=不在
|
||||
*/
|
||||
uint8_t LED_IsEventExist(LED_Event_t ledEvent)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < PRIORITY_MAX; i++) {
|
||||
if (s_led_priority_table[i].event == ledEvent) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按配置刷新一次灯效
|
||||
* @param led 配置,不可为空
|
||||
* @return 1=还在跑,0=次数用完
|
||||
*/
|
||||
uint8_t LED_Update(LED_Config_t *led)
|
||||
{
|
||||
if (led == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (led->state) {
|
||||
case LED_LIGHT:
|
||||
return led_lighting_update(led);
|
||||
#if (BLINK_ENABLE)
|
||||
case LED_BLINKING:
|
||||
return led_blinking_update(led);
|
||||
#endif
|
||||
#if (ALTERNATING_BLINK_ENABLE)
|
||||
case LED_ALTERNATING_BLINK:
|
||||
return led_alternating_update(led);
|
||||
#endif
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 毫秒时间差,兼容溢出
|
||||
* @param meiosis 起点时间戳
|
||||
* @return 差值 ms
|
||||
*/
|
||||
static uint32_t get_tick_diff(uint32_t meiosis)
|
||||
{
|
||||
uint32_t now;
|
||||
|
||||
if (s_time_get == NULL) {
|
||||
return 0;
|
||||
}
|
||||
now = s_time_get();
|
||||
if (now >= meiosis) {
|
||||
return now - meiosis;
|
||||
}
|
||||
return (0xFFFFFFFFu - meiosis) + now;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 常亮;runNum 按秒计,0=一直亮
|
||||
* @param led 配置
|
||||
* @return 1=还在亮,0=到点灭
|
||||
*/
|
||||
static uint8_t led_lighting_update(LED_Config_t *led)
|
||||
{
|
||||
uint16_t sec_count;
|
||||
|
||||
if (s_led_set_state == NULL) {
|
||||
return 0;
|
||||
}
|
||||
sec_count = (uint16_t)(get_tick_diff(led->currentTime) / 1000u);
|
||||
if ((led->runNum > 0) && (sec_count >= led->runNum)) {
|
||||
s_led_set_state(led->ledMask, LED_OFF);
|
||||
return 0;
|
||||
}
|
||||
s_led_set_state(led->ledMask, LED_ON);
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if (BLINK_ENABLE)
|
||||
/**
|
||||
* @brief 闪烁;runNum 按完整周期计,0=一直闪
|
||||
* @param led 配置
|
||||
* @return 1=还在闪,0=到点灭
|
||||
*/
|
||||
static uint8_t led_blinking_update(LED_Config_t *led)
|
||||
{
|
||||
uint32_t run_cycle;
|
||||
uint16_t blink_count;
|
||||
uint32_t sec_left;
|
||||
|
||||
if (s_led_set_state == NULL) {
|
||||
return 0;
|
||||
}
|
||||
run_cycle = led->blinkParams.onTime + led->blinkParams.offTime;
|
||||
if (run_cycle == 0) {
|
||||
return 0;
|
||||
}
|
||||
blink_count = (uint16_t)(get_tick_diff(led->currentTime) / run_cycle);
|
||||
sec_left = get_tick_diff(led->currentTime) % run_cycle;
|
||||
if ((led->runNum > 0) && (blink_count >= led->runNum)) {
|
||||
s_led_set_state(led->ledMask, LED_OFF);
|
||||
return 0;
|
||||
}
|
||||
if (sec_left <= led->blinkParams.onTime) {
|
||||
s_led_set_state(led->ledMask, led->blinkParams.orDer ? LED_OFF : LED_ON);
|
||||
} else {
|
||||
s_led_set_state(led->ledMask, led->blinkParams.orDer ? LED_ON : LED_OFF);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if (ALTERNATING_BLINK_ENABLE)
|
||||
/**
|
||||
* @brief 两组灯按时间片轮流亮
|
||||
* @param led 配置
|
||||
* @return 1=还在跑,0=到点灭
|
||||
*/
|
||||
static uint8_t led_alternating_update(LED_Config_t *led)
|
||||
{
|
||||
uint16_t blink_count;
|
||||
uint32_t sec_left;
|
||||
uint32_t time_slice;
|
||||
size_t i;
|
||||
|
||||
if (s_led_set_state == NULL) {
|
||||
return 0;
|
||||
}
|
||||
if (led->alternatingBlinkParams.Cycle == 0) {
|
||||
return 0;
|
||||
}
|
||||
blink_count = (uint16_t)(get_tick_diff(led->currentTime)
|
||||
/ led->alternatingBlinkParams.Cycle);
|
||||
sec_left = get_tick_diff(led->currentTime) % led->alternatingBlinkParams.Cycle;
|
||||
if ((led->runNum > 0) && (blink_count >= led->runNum)) {
|
||||
s_led_set_state(led->ledMask, LED_OFF);
|
||||
return 0;
|
||||
}
|
||||
time_slice = led->alternatingBlinkParams.Cycle / ALTERNAT_GROUP;
|
||||
s_led_set_state(led->ledMask, LED_OFF);
|
||||
for (i = 0; i < ALTERNAT_GROUP; i++) {
|
||||
if ((sec_left > (time_slice * i)) && (sec_left <= (time_slice * (i + 1)))) {
|
||||
s_led_set_state(led->alternatingBlinkParams.Group[i], LED_ON);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,124 @@
|
||||
/******************************************************************************
|
||||
* @file led.h
|
||||
* @brief LED 事件框架:按通道掩码独立开关,红绿可同时亮
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.09.09
|
||||
* @version V1.0.0
|
||||
* @history
|
||||
* - V1.0.0, 2026.09.09, cyWu, CBR2 独立通道版:绿=喷淋作业,红=配对态,
|
||||
* 两路互不关闭(区别于加热垫互斥灯)
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __LED_H__
|
||||
#define __LED_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
/*============================================================================*/
|
||||
/* 宏定义 */
|
||||
/*============================================================================*/
|
||||
|
||||
#define BLINK_ENABLE 1
|
||||
#define ALTERNATING_BLINK_ENABLE 1
|
||||
#define BREATH_ENABLE 0
|
||||
#define RUNNING_ENABLE 0
|
||||
#define MARQUEE_ENABLE 0
|
||||
|
||||
#define LED_ON 1
|
||||
#define LED_OFF 0
|
||||
|
||||
#define LED_CHANNEL(user_led) (1u << (user_led))
|
||||
|
||||
#define ledPrintf(level, fmt, ...) ((void)0)
|
||||
|
||||
/*============================================================================*/
|
||||
/* 类型 */
|
||||
/*============================================================================*/
|
||||
|
||||
typedef enum {
|
||||
LED_LIGHT = 0, /**< 常亮 */
|
||||
LED_BLINKING, /**< 闪烁 */
|
||||
LED_ALTERNATING_BLINK /**< 红绿交替 */
|
||||
} LED_State_t;
|
||||
|
||||
/* 事件枚举数值 = 事件表下标(led.c LED_EventDelete 依赖),追加事件只能插尾部 */
|
||||
typedef enum {
|
||||
LED_EVENT_NONE = 0,
|
||||
LED_EVENT_GRN_IDLE, /**< 绿灯常亮:空闲(DP1=0) */
|
||||
LED_EVENT_GRN_SPRAY, /**< 绿灯闪:作业中(含间隔等待,DP1=1) */
|
||||
LED_EVENT_RED_PAIRED, /**< 红灯常亮:已配对(粘住,空闲/喷淋都不关) */
|
||||
LED_EVENT_RED_PAIRING, /**< 红灯快闪:配网键满 5s 提示 / 配对进行中 */
|
||||
LED_EVENT_PAIR_FAIL, /**< 配对失败:红绿交替 3s(临时占双灯,自删) */
|
||||
LED_EVENT_OTA, /**< OTA:红绿交替,直到升级结束/复位(最高) */
|
||||
LED_EVENT_MAX
|
||||
} LED_Event_t;
|
||||
|
||||
/**
|
||||
* 优先级:高者覆盖低者(handler 返回 1 会拦下更低优先级)。
|
||||
* 红绿独立通道:绿灯(作业)占 PRIORITY_1、红灯(配对)占 PRIORITY_2,
|
||||
* 红灯 handler 返回 0 放行绿灯;只有 OTA / 配对失败临时占双灯(返回 1)。
|
||||
*/
|
||||
typedef enum {
|
||||
PRIORITY_1 = 0, /**< 绿灯通道:作业态(空闲常亮 / 作业闪) */
|
||||
PRIORITY_2, /**< 红灯通道:配对态(已配常亮 / 配网快闪) */
|
||||
PRIORITY_3, /**< 配对失败动画(红绿交替,临时占双灯) */
|
||||
PRIORITY_4, /**< OTA(红绿交替,最高) */
|
||||
PRIORITY_MAX
|
||||
} LED_EventPriority_t;
|
||||
|
||||
#if (BLINK_ENABLE)
|
||||
typedef struct {
|
||||
uint8_t orDer; /**< 1=先灭后亮,0=先亮后灭 */
|
||||
uint32_t onTime; /**< 亮时间 ms */
|
||||
uint32_t offTime; /**< 灭时间 ms */
|
||||
} LED_BlinkParams_t;
|
||||
#endif
|
||||
|
||||
#if (ALTERNATING_BLINK_ENABLE)
|
||||
#define ALTERNAT_GROUP 2
|
||||
typedef struct {
|
||||
uint16_t Group[ALTERNAT_GROUP]; /**< 交替两组的通道掩码 */
|
||||
uint32_t Cycle; /**< 一整轮交替周期 ms */
|
||||
} LED_AlternatingBlinkParams_t;
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
LED_State_t state;
|
||||
uint16_t ledMask; /**< 本事件只动这些通道,其它灯不受影响 */
|
||||
uint16_t runNum; /**< 0=无限;常亮按秒计,闪烁按周期计 */
|
||||
uint32_t currentTime;
|
||||
#if (BLINK_ENABLE)
|
||||
LED_BlinkParams_t blinkParams;
|
||||
#endif
|
||||
#if (ALTERNATING_BLINK_ENABLE)
|
||||
LED_AlternatingBlinkParams_t alternatingBlinkParams;
|
||||
#endif
|
||||
} LED_Config_t;
|
||||
|
||||
typedef struct {
|
||||
LED_Event_t event;
|
||||
LED_EventPriority_t priority;
|
||||
LED_Config_t config;
|
||||
uint8_t (*ledEventHandler)(LED_Config_t *config);
|
||||
} LED_EventTableItem_t;
|
||||
|
||||
typedef void (*LED_SetState_t)(uint16_t led, uint8_t state);
|
||||
typedef uint32_t (*TIME_Get_t)(void);
|
||||
|
||||
/*============================================================================*/
|
||||
/* 外部接口 */
|
||||
/*============================================================================*/
|
||||
|
||||
void LED_SetStateInit(LED_SetState_t setState);
|
||||
void SET_TimeGetInit(TIME_Get_t getTime);
|
||||
void LED_EventTableInit(LED_EventTableItem_t ledEvent[]);
|
||||
void LED_EventAdd(LED_Event_t ledEvent);
|
||||
void LED_EventDelete(LED_Event_t ledEvent);
|
||||
void LED_EventAllDelete(void);
|
||||
void LED_EventHandle(void);
|
||||
uint8_t LED_IsEventActive(void);
|
||||
uint8_t LED_IsEventExist(LED_Event_t ledEvent);
|
||||
uint8_t LED_Update(LED_Config_t *led);
|
||||
|
||||
#endif /* __LED_H__ */
|
||||
@@ -0,0 +1,201 @@
|
||||
/******************************************************************************
|
||||
* @file led_manager.c
|
||||
* @brief CBR2 红绿 LED 事件表:绿=作业态,红=配对态(规格书第 6 章)
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.09.09
|
||||
* @version V1.0.0,P0 Bring-up(独立通道,区别于加热垫互斥灯)
|
||||
*
|
||||
* @note 红绿独立:绿灯事件只动绿灯、红灯事件只动红灯,handler 返回 0
|
||||
* 放行另一通道;仅 OTA / 配对失败(PAIR_FAIL)临时占双灯(返回 1)。
|
||||
******************************************************************************/
|
||||
|
||||
#include "led_manager.h"
|
||||
#include "system/includes.h"
|
||||
#include "bsp_hw.h"
|
||||
#include "board_pin.h"
|
||||
|
||||
static uint8_t led_grn_idle_handler(LED_Config_t *led_config);
|
||||
static uint8_t led_grn_spray_handler(LED_Config_t *led_config);
|
||||
static uint8_t led_red_paired_handler(LED_Config_t *led_config);
|
||||
static uint8_t led_red_pairing_handler(LED_Config_t *led_config);
|
||||
static uint8_t led_pair_fail_handler(LED_Config_t *led_config);
|
||||
static uint8_t led_ota_handler(LED_Config_t *led_config);
|
||||
|
||||
/**
|
||||
* @brief 只改掩码里的通道,其它灯保持原样
|
||||
* @param led 通道掩码
|
||||
* @param state LED_ON / LED_OFF
|
||||
* @return 无
|
||||
*/
|
||||
static void set_led_multi(uint16_t led, uint8_t state)
|
||||
{
|
||||
if (led & LED_CHANNEL(LED_CH_RED)) {
|
||||
bsp_led_red_set(state);
|
||||
}
|
||||
if (led & LED_CHANNEL(LED_CH_GREEN)) {
|
||||
bsp_led_green_set(state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 绿灯常亮(空闲):只动绿灯,放行红灯通道
|
||||
* @param led_config 配置
|
||||
* @return 0=放行其它优先级
|
||||
*/
|
||||
static uint8_t led_grn_idle_handler(LED_Config_t *led_config)
|
||||
{
|
||||
(void)LED_Update(led_config);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 绿灯闪(作业中 500ms):只动绿灯,放行红灯通道
|
||||
* @param led_config 配置
|
||||
* @return 0=放行其它优先级
|
||||
*/
|
||||
static uint8_t led_grn_spray_handler(LED_Config_t *led_config)
|
||||
{
|
||||
(void)LED_Update(led_config);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 红灯常亮(已配对,粘住):只动红灯,放行绿灯通道
|
||||
* @param led_config 配置
|
||||
* @return 0=放行其它优先级
|
||||
*/
|
||||
static uint8_t led_red_paired_handler(LED_Config_t *led_config)
|
||||
{
|
||||
(void)LED_Update(led_config);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 红灯快闪(满 5s 提示 / 配对中 100ms):只动红灯,放行绿灯通道
|
||||
* @param led_config 配置
|
||||
* @return 0=放行其它优先级
|
||||
*/
|
||||
static uint8_t led_red_pairing_handler(LED_Config_t *led_config)
|
||||
{
|
||||
(void)LED_Update(led_config);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 配对失败红绿交替 3s:到点自删,交回双通道常态
|
||||
* @param led_config 配置
|
||||
* @return 1=占双灯,0=已自删
|
||||
*/
|
||||
static uint8_t led_pair_fail_handler(LED_Config_t *led_config)
|
||||
{
|
||||
if (LED_Update(led_config) == 0) {
|
||||
LED_EventDelete(LED_EVENT_PAIR_FAIL);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief OTA 红绿交替(无限):最高优先级,拦下其它一切
|
||||
* @param led_config 配置
|
||||
* @return 1
|
||||
*/
|
||||
static uint8_t led_ota_handler(LED_Config_t *led_config)
|
||||
{
|
||||
(void)LED_Update(led_config);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注册回调并装载 CBR2 事件表(GPIO 已在 bsp_hw_init)
|
||||
* @note 事件枚举数值 = 事件表下标(led.c LED_EventDelete 依赖)
|
||||
* @return 无
|
||||
*/
|
||||
void user_led_init(void)
|
||||
{
|
||||
static LED_EventTableItem_t led_event_category[LED_EVENT_MAX] = {
|
||||
[LED_EVENT_GRN_IDLE] = {
|
||||
/* 空闲(DP1=0):绿灯常亮,不碰红灯 */
|
||||
.event = LED_EVENT_GRN_IDLE,
|
||||
.priority = PRIORITY_1,
|
||||
.config.ledMask = LED_CHANNEL(LED_CH_GREEN),
|
||||
.config.state = LED_LIGHT,
|
||||
.config.runNum = 0, /* 0=一直亮 */
|
||||
.ledEventHandler = led_grn_idle_handler,
|
||||
},
|
||||
[LED_EVENT_GRN_SPRAY] = {
|
||||
/* 作业中(含间隔等待):绿灯 500ms 闪,不碰红灯 */
|
||||
.event = LED_EVENT_GRN_SPRAY,
|
||||
.priority = PRIORITY_1,
|
||||
.config.ledMask = LED_CHANNEL(LED_CH_GREEN),
|
||||
.config.state = LED_BLINKING,
|
||||
.config.blinkParams.orDer = 0,
|
||||
.config.blinkParams.onTime = BOARD_LED_SPRAY_ON_MS,
|
||||
.config.blinkParams.offTime = BOARD_LED_SPRAY_OFF_MS,
|
||||
.config.runNum = 0,
|
||||
.ledEventHandler = led_grn_spray_handler,
|
||||
},
|
||||
[LED_EVENT_RED_PAIRED] = {
|
||||
/* 已配对(含断连重连、上电已配对):红灯常亮粘住,不碰绿灯 */
|
||||
.event = LED_EVENT_RED_PAIRED,
|
||||
.priority = PRIORITY_2,
|
||||
.config.ledMask = LED_CHANNEL(LED_CH_RED),
|
||||
.config.state = LED_LIGHT,
|
||||
.config.runNum = 0,
|
||||
.ledEventHandler = led_red_paired_handler,
|
||||
},
|
||||
[LED_EVENT_RED_PAIRING] = {
|
||||
/* 配网键满 5s 提示 / 配对进行中:红灯 100ms 快闪,不碰绿灯 */
|
||||
.event = LED_EVENT_RED_PAIRING,
|
||||
.priority = PRIORITY_2,
|
||||
.config.ledMask = LED_CHANNEL(LED_CH_RED),
|
||||
.config.state = LED_BLINKING,
|
||||
.config.blinkParams.orDer = 0,
|
||||
.config.blinkParams.onTime = BOARD_LED_FAST_ON_MS,
|
||||
.config.blinkParams.offTime = BOARD_LED_FAST_OFF_MS,
|
||||
.config.runNum = 0,
|
||||
.ledEventHandler = led_red_pairing_handler,
|
||||
},
|
||||
[LED_EVENT_PAIR_FAIL] = {
|
||||
/* 配对失败:红绿交替 3s 后自删(半周期 BOARD_LED_PAIR_FAIL_MS) */
|
||||
.event = LED_EVENT_PAIR_FAIL,
|
||||
.priority = PRIORITY_3,
|
||||
.config.ledMask = (uint16_t)(LED_CHANNEL(LED_CH_RED) | LED_CHANNEL(LED_CH_GREEN)),
|
||||
.config.state = LED_ALTERNATING_BLINK,
|
||||
.config.alternatingBlinkParams.Group[0] = LED_CHANNEL(LED_CH_RED),
|
||||
.config.alternatingBlinkParams.Group[1] = LED_CHANNEL(LED_CH_GREEN),
|
||||
.config.alternatingBlinkParams.Cycle = (uint32_t)(BOARD_LED_PAIR_FAIL_MS * 2u),
|
||||
.config.runNum = (uint16_t)((BOARD_LED_PAIR_FAIL_HOLD_MS
|
||||
+ (BOARD_LED_PAIR_FAIL_MS * 2u) - 1u)
|
||||
/ (BOARD_LED_PAIR_FAIL_MS * 2u)),
|
||||
.ledEventHandler = led_pair_fail_handler,
|
||||
},
|
||||
[LED_EVENT_OTA] = {
|
||||
/* OTA:红绿交替一直闪(200ms 半周期),直到升级结束/复位 */
|
||||
.event = LED_EVENT_OTA,
|
||||
.priority = PRIORITY_4,
|
||||
.config.ledMask = (uint16_t)(LED_CHANNEL(LED_CH_RED) | LED_CHANNEL(LED_CH_GREEN)),
|
||||
.config.state = LED_ALTERNATING_BLINK,
|
||||
.config.alternatingBlinkParams.Group[0] = LED_CHANNEL(LED_CH_RED),
|
||||
.config.alternatingBlinkParams.Group[1] = LED_CHANNEL(LED_CH_GREEN),
|
||||
.config.alternatingBlinkParams.Cycle = (uint32_t)(BOARD_LED_OTA_ON_MS
|
||||
+ BOARD_LED_OTA_OFF_MS),
|
||||
.config.runNum = 0, /* 0=无限 */
|
||||
.ledEventHandler = led_ota_handler,
|
||||
},
|
||||
};
|
||||
|
||||
LED_SetStateInit(set_led_multi);
|
||||
SET_TimeGetInit((TIME_Get_t)timer_get_ms);
|
||||
LED_EventTableInit(led_event_category);
|
||||
bsp_led_all_off();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 跑一轮 LED 事件
|
||||
* @return 无
|
||||
*/
|
||||
void user_led_handle(void)
|
||||
{
|
||||
LED_EventHandle();
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/******************************************************************************
|
||||
* @file led_manager.h
|
||||
* @brief CBR2 红绿 LED 通道与事件表(绿=作业 / 红=配对 / OTA、失败占双灯)
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.09.09
|
||||
* @version V1.0.0,P0 Bring-up
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __LED_MANAGER_H__
|
||||
#define __LED_MANAGER_H__
|
||||
|
||||
#include "led.h"
|
||||
|
||||
typedef enum {
|
||||
LED_CH_RED = 0,
|
||||
LED_CH_GREEN,
|
||||
LED_CH_MAX
|
||||
} LedChannel_t;
|
||||
|
||||
/**
|
||||
* @brief 注册 GPIO 回调并装载 CBR2 事件表(GPIO 已在 bsp_hw_init 配置)
|
||||
* @return 无
|
||||
*/
|
||||
void user_led_init(void);
|
||||
|
||||
/**
|
||||
* @brief 跑一轮 LED 事件
|
||||
* @return 无
|
||||
*/
|
||||
void user_led_handle(void);
|
||||
|
||||
#endif /* __LED_MANAGER_H__ */
|
||||
Reference in New Issue
Block a user