/* ============================================================================= * monitor_task.c - LED_Task 强定义:任务心跳 + LED/蜂鸣报警 + IWDG 看门狗 * 对应 legacy LinkCheck + Warming 模块,报警灯语: * 蓝灯每 2s 短闪 = 系统健康心跳(区分"空闲"与"卡死") * 红灯 1s 双闪 = 遥控器掉线 红灯常亮+蜂鸣 = CAN 设备掉线 * 绿灯连闪两下 = BMI088 失败 绿灯慢闪 = IST8310 失败 * 绿灯常亮 = IMU 过温(已关加热)/双传感器失败 * 连接音效 = 遥控器上线瞬间两声上扬(M3->H3) * CAN 掉线蜂鸣(legacy 语义): 按设备序号 n 响 n 下,2s 一轮,每次只报 * 最先掉线的设备,设备序号表见 linkcheck_first_offline() * 调试模式(DEBUG_BUZZER_MELODY=1,见 robot_param.h): 上述蜂鸣改为循环 * 播放旋律,避免台架调试时连续滴滴声;红灯常亮等灯语不受影响 * 看门狗: IWDG 约 2.5s;上电 5s 宽限期无条件喂,之后仅当全部任务心跳正常 * 才喂——任何任务卡死都会复位,不会整车失控 * ========================================================================== */ #include "app_tasks.h" #include "cmsis_os.h" #include "stm32f4xx_hal.h" #include "led.h" #include "buzzer.h" #include "dr16.h" #include "referee.h" #include "c_comm.h" #include "robot_param.h" #if defined(ROBOT_TYPE_GIMBAL) #include "gimbal.h" #include "bmi088.h" #include "ist8310.h" #include "attitude.h" #elif defined(ROBOT_TYPE_CHASSIS) #include "chassis.h" #endif /* 监控任务(LED_Task 强定义):任务心跳检查 + LED/蜂鸣器报警 + 喂 IWDG * 报警语义对应 legacy Warming 模块: * 遥控器掉线 -> 1s 内红灯连闪两下 * CAN 设备掉线 -> 红灯常亮 + 2s 周期蜂鸣 * 图传链路掉线 -> 绿灯闪烁 * IMU 过温 -> 绿灯常亮 * 各任务心跳正常 -> 喂独立看门狗(约 1.6s 溢出) */ static volatile uint32_t heartbeat_tick[HB_COUNT]; void monitor_heartbeat(heartbeat_id_e id) { heartbeat_tick[id] = HAL_GetTick(); } static uint8_t all_heartbeats_alive(void) { #if defined(ROBOT_TYPE_GIMBAL) static const heartbeat_id_e expected[] = {HB_IMU, HB_CMD, HB_GIMBAL, HB_SHOOT, HB_CCOMM, HB_REFEREE}; #elif defined(ROBOT_TYPE_CHASSIS) static const heartbeat_id_e expected[] = {HB_IMU, HB_CMD, HB_CHASSIS, HB_CCOMM, HB_REFEREE}; #endif uint32_t now = HAL_GetTick(); for (uint8_t i = 0; i < sizeof(expected) / sizeof(expected[0]); i++) { if (heartbeat_tick[expected[i]] == 0 || now - heartbeat_tick[expected[i]] > 500) return 0; } return 1; } /* 启动后 5s 内无条件喂狗(宽限期),之后要求全部任务心跳正常 */ static uint8_t iwdg_should_feed(void) { if (HAL_GetTick() < 5000) return 1; return all_heartbeats_alive(); } /* LinkCheck:返回第一个掉线 CAN 设备的序号(无掉线返回 -1) * 序号即蜂鸣次数-1(legacy 语义:报警蜂鸣器以高音 6 响 n 下) */ #if defined(ROBOT_TYPE_GIMBAL) #define LINKCHECK_DEVICES #endif #if defined(ROBOT_TYPE_CHASSIS) #define LINKCHECK_DEVICES #endif #ifdef LINKCHECK_DEVICES #define LINK_OFFLINE(tick) ((tick) == 0 || now - (tick) > DEVICE_OFFLINE_TIME_MS) static int8_t linkcheck_first_offline(void) { uint32_t now = HAL_GetTick(); #if defined(ROBOT_TYPE_GIMBAL) /* 设备序号:0 摩擦轮L(0x201) 1 摩擦轮R(0x202) 2 Pitch(0x206) * 3 Yaw(0x205) 4 拨弹(0x207,安装时) 5 底盘回传(0x019) */ if (LINK_OFFLINE(motor_friction_l.fdb.last_online_tick)) return 0; if (LINK_OFFLINE(motor_friction_r.fdb.last_online_tick)) return 1; if (LINK_OFFLINE(motor_pitch_gimbal.fdb.last_online_tick)) return 2; if (LINK_OFFLINE(motor_yaw_gimbal.fdb.last_online_tick)) return 3; #if GIMBAL_RAMMER_INSTALLED if (LINK_OFFLINE(motor_rammer.fdb.last_online_tick)) return 4; #endif if (LINK_OFFLINE(ctoc_reply_last_tick)) return 5; #elif defined(ROBOT_TYPE_CHASSIS) /* 设备序号:0~3 四轮(0x201~0x204) 4 云台数据(0x149/0x189/0x1A9) */ if (LINK_OFFLINE(motor_wheel_rf.fdb.last_online_tick)) return 0; if (LINK_OFFLINE(motor_wheel_lf.fdb.last_online_tick)) return 1; if (LINK_OFFLINE(motor_wheel_lr.fdb.last_online_tick)) return 2; if (LINK_OFFLINE(motor_wheel_rr.fdb.last_online_tick)) return 3; if (ctoc_remote.last_tick == 0 || now - ctoc_remote.last_tick > CTOC_OFFLINE_TIME_MS) return 4; #endif return -1; } #undef LINK_OFFLINE #endif /* LINKCHECK_DEVICES */ static void iwdg_init(void) { /* 直接写寄存器(HAL IWDG 模块未在 CubeMX 中启用) * PR=4 -> LSI 32kHz / 64 分频 = 500Hz,重装载 1250 -> 约 2.5s 溢出 * (留足 IMU 零偏校准约 1s 的启动余量) */ IWDG->KR = 0x5555; // 解除写保护 IWDG->PR = 4; // 000:/4 001:/8 010:/16 011:/32 100:/64 IWDG->RLR = 1250; IWDG->KR = 0xAAAA; // 重装载 IWDG->KR = 0xCCCC; // 启动 } void LED_Task(void const * argument) { uint8_t counter = 0; uint8_t imu_overheat = 0; uint8_t can_alarm; uint8_t first_ready = 1; /* 看门狗在本任务入口立即启动(约 2.5s): * 启动后前 5s 为宽限期无条件喂狗(容忍传感器初始化/校准耗时), * 宽限期后仅在所有任务心跳正常时喂狗,任何任务卡死都会复位 */ iwdg_init(); /* 等待硬件初始化完成(CAN_Task 的蓝灯常亮即初始化中) */ while (!app_ready) { IWDG->KR = 0xAAAA; osDelay(10); } for (;;) { counter++; if (counter > 100) counter = 1; /* 初始化完成的第一拍:熄灭蓝灯,交由报警逻辑接管 */ if (first_ready) { first_ready = 0; led_set_all_off(); } /* ---- CAN 设备掉线:红灯常亮 + 2s 周期内响 n 下(n=掉线设备序号+1,legacy 语义) ---- */ #if defined(ROBOT_TYPE_GIMBAL) can_alarm = gimbal_motor_offline(); #elif defined(ROBOT_TYPE_CHASSIS) can_alarm = chassis_motor_offline(); #endif if (can_alarm) { led_set_red(1); /* 掉线设备序号(0 起)。旋律模式下不用它的返回值,仍保留调用: * 一是调试时可直接在调试器里看 linkcheck 结果,二是避免该静态 * 函数在 DEBUG_BUZZER_MELODY=1 时被判为未使用而报警告 */ int8_t first_offline = linkcheck_first_offline(); #if DEBUG_BUZZER_MELODY /* 调试模式:警告音替换为循环旋律(见 modules/BUZZER/buzzer.h)。 * 红灯常亮与其它灯语不变,"哪台设备掉线"这一信息不会丢。 * 步进用本任务自身的 100ms 节拍,不靠 HAL_GetTick 采样,避免周期 * 抖动漏掉或重复某个音符 */ static uint16_t melody_step = 0; (void)first_offline; buzzer_play_melody(melody_step++); #else if (first_offline >= 0) { uint8_t n = (uint8_t)(first_offline + 1); uint8_t w = counter % 20; // 2s 窗口 /* 第 j 声:占 w=[2j,2j+1],响 100ms 停 100ms */ if ((w / 2) < n && (w % 2) == 0) buzzer_on(BUZZER_L1); else buzzer_off(); } else buzzer_off(); #endif } else { #if defined(ROBOT_TYPE_GIMBAL) /* ---- 遥控器连接成功音效:离线->在线瞬间,两声上扬(M3 -> H3) ---- */ static uint8_t prev_remote_online = 0; static uint8_t chime_step = 0; uint8_t remote_online = !dr16_offline(); if (remote_online && !prev_remote_online) chime_step = 4; // 触发音效序列(100ms/步) prev_remote_online = remote_online; if (chime_step > 0) { if (chime_step == 4) buzzer_on(BUZZER_M3); else if (chime_step == 3) buzzer_off(); else if (chime_step == 2) buzzer_on(BUZZER_H3); else buzzer_off(); chime_step--; } else buzzer_off(); #else buzzer_off(); #endif #if !defined(ROBOT_TYPE_GIMBAL) led_set_red(0); #endif } #if defined(ROBOT_TYPE_GIMBAL) /* ---- 遥控器掉线:1s 内红灯连闪两下 ---- */ if (dr16_offline()) { if (counter % 20 == 1) led_set_red(1); else if (counter % 20 == 5) led_set_red(0); else if (counter % 20 == 13) led_set_red(1); else if (counter % 20 == 17) led_set_red(0); } else if (!can_alarm) led_set_red(0); /* ---- 图传链路超时:绿灯 1s 周期闪烁 ---- */ if (referee.link_status && HAL_GetTick() - referee.last_update_tick > DEVICE_OFFLINE_TIME_MS * 10) { if (counter % 20 < 10) led_set_green(1); else led_set_green(0); } /* ---- IMU 过温:绿灯常亮并关闭加热 ---- */ if (bmi088_temperature > IMU_TEMPERATURE_LIMIT) { imu_overheat = 1; imu_heater_off(); } if (imu_overheat) led_set_green(1); /* ---- 传感器检测失败(不同灯语便于区分) ---- * BMI088 失败:绿灯 1s 内连闪两下;IST8310 失败:绿灯 1s 周期慢闪; * 两者皆失败:绿灯常亮 */ if (!bmi088_online && !ist8310_online) led_set_green(1); else if (!bmi088_online) { if (counter % 20 == 1) led_set_green(1); else if (counter % 20 == 5) led_set_green(0); else if (counter % 20 == 13) led_set_green(1); else if (counter % 20 == 17) led_set_green(0); } else if (!ist8310_online) { if (counter % 20 < 10) led_set_green(1); else led_set_green(0); } #elif defined(ROBOT_TYPE_CHASSIS) led_set_green(0); #endif /* ---- 健康心跳:无报警时蓝灯每 2s 闪 100ms,用于区分"空闲"与"卡死" ---- */ uint8_t healthy = !can_alarm && !imu_overheat; #if defined(ROBOT_TYPE_GIMBAL) healthy = healthy && bmi088_online && ist8310_online; #endif if (healthy) { if (counter % 20 == 10) led_set_blue(1); else if (counter % 20 == 11) led_set_blue(0); } /* ---- 心跳全部正常才喂狗(宽限期除外):任何任务卡死都会触发复位 ---- */ if (iwdg_should_feed()) IWDG->KR = 0xAAAA; osDelay(100); } }