| | |
| | | g_motion_path_xy, |
| | | g_motion_path_point_count); |
| | | |
| | | DBG_Printf("[MC_INIT] Path loaded: count=%u, first_point=(%.2f,%.2f)\r\n", |
| | | g_motion_path_point_count, |
| | | g_motion_path_xy[0], |
| | | g_motion_path_xy[1]); |
| | | |
| | | BaseType_t ret = xTaskCreate( |
| | | MotionControl_TaskEntry, |
| | | "MotionCtrl", |
| | |
| | | g_last_heading_deg = gprmi.m_fHeadingAngle; |
| | | g_last_pitch_deg = gprmi.m_fPitchAngle; |
| | | g_last_roll_deg = gprmi.m_fRollAngle; |
| | | |
| | | /* 检测pose_valid状态变化 */ |
| | | if (g_last_pose_valid == HIDO_FALSE) |
| | | { |
| | | HIDO_Debug2("[MC_POSE]pose_valid: FALSE->TRUE (GPS recovered)\r\n"); |
| | | } |
| | | g_last_pose_valid = HIDO_TRUE; |
| | | g_last_gps_ms = now; |
| | | g_last_gprmi_tow = gps_timestamp; |
| | |
| | | } |
| | | else if (gps_valid == HIDO_FALSE && (now - g_last_gps_ms) > 200U) |
| | | { |
| | | /* 检测pose_valid状态变化 */ |
| | | if (g_last_pose_valid == HIDO_TRUE) |
| | | { |
| | | HIDO_Debug2("[MC_POSE]pose_valid: TRUE->FALSE (GPS timeout, age=%ums)\r\n", |
| | | now - g_last_gps_ms); |
| | | } |
| | | g_motion_state.pose_valid = HIDO_FALSE; |
| | | g_last_pose_valid = HIDO_FALSE; |
| | | } |
| | |
| | | /* --- 2) 调用运动控制器(固定 75 Hz dt) --- */ |
| | | MC_Output output; |
| | | HIDO_BOOL sbus_valid = (SBUS_IsSignalValid(MOTION_SBUS_TIMEOUT_MS) == HIDO_TRUE); |
| | | HIDO_UINT16 ch8 = SBUS_GetChannel(MOTION_SBUS_AUTO_CHANNEL); |
| | | HIDO_UINT16 ch8_raw = SBUS_GetChannel(MOTION_SBUS_AUTO_CHANNEL); |
| | | |
| | | /* 过滤SBUS异常值: |
| | | * 1024 = SBUS_CENTER_VALUE(信号丢失时的默认返回值) |
| | | * <172 或 >1811 = SBUS有效范围之外(对应PWM 1000-2000) |
| | | * 当检测到异常值时,保持之前的有效值不变 */ |
| | | static HIDO_UINT16 ch8 = 1000; /* 默认手动模式 */ |
| | | static HIDO_UINT32 s_ch8_failsafe_count = 0; |
| | | static HIDO_UINT16 s_ch8_last_valid = 1000; |
| | | |
| | | /* 判断是否为有效SBUS值:172-1811范围内,且不是精确的1024 */ |
| | | HIDO_BOOL is_valid = (ch8_raw >= 172 && ch8_raw <= 1811 && ch8_raw != 1024); |
| | | |
| | | if (is_valid) |
| | | { |
| | | ch8 = ch8_raw; /* 只更新有效值 */ |
| | | s_ch8_last_valid = ch8_raw; |
| | | if (s_ch8_failsafe_count > 0) |
| | | { |
| | | DBG_Printf("[MC_CTRL] CH8 recovered from failsafe, count=%u, new value=%u\r\n", |
| | | s_ch8_failsafe_count, ch8); |
| | | s_ch8_failsafe_count = 0; |
| | | } |
| | | } |
| | | else |
| | | { |
| | | /* 异常值,保持之前的值,记录failsafe事件 */ |
| | | s_ch8_failsafe_count++; |
| | | if (s_ch8_failsafe_count == 1) |
| | | { |
| | | DBG_Printf("[MC_CTRL] CH8 failsafe detected (%u), keeping previous value=%u\r\n", |
| | | ch8_raw, ch8); |
| | | } |
| | | } |
| | | |
| | | /* 检查 GPS 状态:必须初始化完成且连接到 GNSS */ |
| | | HIDO_BOOL gps_ready = HIDO_FALSE; |
| | |
| | | #endif |
| | | |
| | | /* 只有当满足所有条件时才执行自动控制: |
| | | |
| | | /* 只有当满足所有条件时才执行自动控制: |
| | | * 1. SBUS 信号有效 |
| | | * 2. CH8 > 阈值(自动模式开关) |
| | | * 2. CH8 > 阈值(自动模式开关,带迟滞) |
| | | * 3. GPS 初始化完成(FINIT_OK) |
| | | * 4. GPS 连接到 GNSS(GNSS_CONNECT)*/ |
| | | static HIDO_BOOL s_last_auto_condition = HIDO_FALSE; |
| | | HIDO_BOOL current_auto_condition = (sbus_valid == HIDO_TRUE && ch8 > MOTION_SBUS_AUTO_THRESHOLD_US && gps_ready == HIDO_TRUE); |
| | | static HIDO_BOOL s_last_sbus_valid = HIDO_FALSE; |
| | | static HIDO_BOOL s_last_gps_ready = HIDO_FALSE; |
| | | static HIDO_UINT16 s_last_ch8 = 0; |
| | | static HIDO_BOOL s_ch8_auto_state = HIDO_FALSE; /* CH8迟滞状态 */ |
| | | static HIDO_BOOL s_last_ch8_auto_state = HIDO_FALSE; /* 上一次的CH8迟滞状态 */ |
| | | |
| | | /* 检测到从手动切换到自动模式的上升沿:重置状态机从头开始 */ |
| | | /* CH8迟滞:防止在阈值附近抖动时反复触发 |
| | | * 进入自动模式:CH8 > 1600 (高阈值) |
| | | * 退出自动模式:CH8 < 1400 (低阈值) |
| | | * 死区:1400-1600,在此区间保持之前的状态 |
| | | * |
| | | * 连续确认机制:需要连续3次检测到相同状态才切换 |
| | | * 这是最稳定的防抖方案,防止瞬态干扰 */ |
| | | #define CH8_THRESHOLD_ENTER 1600 |
| | | #define CH8_THRESHOLD_EXIT 1400 |
| | | #define CH8_CONFIRM_COUNT 3 |
| | | |
| | | static HIDO_UINT8 s_ch8_enter_count = 0; /* 连续检测到进入自动模式的次数 */ |
| | | static HIDO_UINT8 s_ch8_exit_count = 0; /* 连续检测到退出自动模式的次数 */ |
| | | |
| | | if (s_ch8_auto_state == HIDO_FALSE) |
| | | { |
| | | /* 当前在手动模式,检查是否应该进入自动模式 */ |
| | | if (ch8 > CH8_THRESHOLD_ENTER) |
| | | { |
| | | s_ch8_enter_count++; |
| | | s_ch8_exit_count = 0; /* 重置退出计数 */ |
| | | |
| | | if (s_ch8_enter_count >= CH8_CONFIRM_COUNT) |
| | | { |
| | | /* 连续3次确认,切换到自动模式 */ |
| | | s_ch8_auto_state = HIDO_TRUE; |
| | | s_ch8_enter_count = 0; |
| | | DBG_Printf("[MC_CTRL] CH8 state confirmed: MANUAL -> AUTO (ch8=%u)\r\n", ch8); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | s_ch8_enter_count = 0; /* 未满足条件,重置计数 */ |
| | | } |
| | | } |
| | | else |
| | | { |
| | | /* 当前在自动模式,检查是否应该退出自动模式 */ |
| | | if (ch8 < CH8_THRESHOLD_EXIT) |
| | | { |
| | | s_ch8_exit_count++; |
| | | s_ch8_enter_count = 0; /* 重置进入计数 */ |
| | | |
| | | if (s_ch8_exit_count >= CH8_CONFIRM_COUNT) |
| | | { |
| | | /* 连续3次确认,切换到手动模式 */ |
| | | s_ch8_auto_state = HIDO_FALSE; |
| | | s_ch8_exit_count = 0; |
| | | DBG_Printf("[MC_CTRL] CH8 state confirmed: AUTO -> MANUAL (ch8=%u)\r\n", ch8); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | s_ch8_exit_count = 0; /* 未满足条件,重置计数 */ |
| | | } |
| | | } |
| | | |
| | | HIDO_BOOL current_auto_condition = (sbus_valid == HIDO_TRUE && s_ch8_auto_state == HIDO_TRUE && gps_ready == HIDO_TRUE); |
| | | |
| | | /* 检测到从手动切换到自动模式的上升沿:重置状态机从头开始 |
| | | * 增加稳定性要求:只有在IDLE或FINISHED状态时才允许重新初始化 |
| | | * 如果正在执行任务(GOTO_START或FOLLOW_PATH),忽略模式切换抖动 */ |
| | | if (s_last_auto_condition == HIDO_FALSE && current_auto_condition == HIDO_TRUE) |
| | | { |
| | | DBG_Printf("[MC_CTRL] Auto mode triggered! Resetting to GOTO_START.\r\n"); |
| | | E_MCStage current_stage = g_motion_state.stage; |
| | | |
| | | if (current_stage == MC_STAGE_IDLE || current_stage == MC_STAGE_FINISHED) |
| | | { |
| | | DBG_Printf("[MC_CTRL] *** Auto mode triggered! Resetting to GOTO_START ***\r\n"); |
| | | DBG_Printf("[MC_CTRL] SBUS: %d->%d, CH8: %u->%u (state=%d->%d), GPS_READY: %d->%d\r\n", |
| | | s_last_sbus_valid, sbus_valid, |
| | | s_last_ch8, ch8, |
| | | s_last_ch8_auto_state, s_ch8_auto_state, |
| | | s_last_gps_ready, gps_ready); |
| | | MC_Init(&g_motion_state, |
| | | &g_motion_config, |
| | | g_motion_path_xy, |
| | | g_motion_path_point_count); |
| | | } |
| | | else |
| | | { |
| | | /* 正在执行任务,忽略此次模式切换(可能是信号抖动) */ |
| | | DBG_Printf("[MC_CTRL] WARNING: Auto mode re-triggered during %d, ignoring (SBUS interference)\r\n", |
| | | current_stage); |
| | | } |
| | | } |
| | | |
| | | /* 检测条件变化并记录 */ |
| | | if (s_last_sbus_valid != sbus_valid) |
| | | { |
| | | DBG_Printf("[MC_CTRL] SBUS valid changed: %d -> %d\r\n", s_last_sbus_valid, sbus_valid); |
| | | } |
| | | if (s_last_gps_ready != gps_ready) |
| | | { |
| | | DBG_Printf("[MC_CTRL] GPS ready changed: %d -> %d\r\n", s_last_gps_ready, gps_ready); |
| | | } |
| | | |
| | | /* 记录状态变化(已在上面连续确认时记录,这里只更新last状态) */ |
| | | s_last_ch8_auto_state = s_ch8_auto_state; |
| | | |
| | | s_last_auto_condition = current_auto_condition; |
| | | s_last_sbus_valid = sbus_valid; |
| | | s_last_gps_ready = gps_ready; |
| | | s_last_ch8 = ch8; |
| | | |
| | | if (current_auto_condition == HIDO_TRUE) |
| | | { |
| | | /* 记录状态切换 */ |
| | | static E_MCStage s_last_stage = MC_STAGE_IDLE; |
| | | E_MCStage prev_stage = g_motion_state.stage; |
| | | |
| | | MC_Compute(&g_motion_state, 1.0f / MC_CFG_CONTROL_HZ, &output); |
| | | |
| | | if (prev_stage != output.stage) |
| | | { |
| | | const HIDO_CHAR *prev_label = MotionControl_StageLabel(prev_stage); |
| | | const HIDO_CHAR *curr_label = MotionControl_StageLabel(output.stage); |
| | | DBG_Printf("[MC_CTRL] *** Stage transition: %s -> %s ***\r\n", prev_label, curr_label); |
| | | |
| | | if (output.stage == MC_STAGE_GOTO_START && prev_stage == MC_STAGE_FOLLOW_PATH) |
| | | { |
| | | /* 异常:从follow_path回到goto_start!记录详细信息 */ |
| | | DBG_Printf("[MC_CTRL] WARNING: Unexpected transition from FOLLOW_PATH to GOTO_START!\r\n"); |
| | | DBG_Printf("[MC_CTRL] nearest_idx=%u, path_count=%u, pos=(%.2f,%.2f)\r\n", |
| | | g_motion_state.nearest_index, |
| | | g_motion_state.path_count, |
| | | g_motion_state.pos[0], |
| | | g_motion_state.pos[1]); |
| | | } |
| | | } |
| | | s_last_stage = output.stage; |
| | | } |
| | | else |
| | | { |
| | |
| | | int tgt_y_int = (int)g_motion_state.current_target_xy[1]; |
| | | int tgt_y_frac = (int)(fabsf(g_motion_state.current_target_xy[1] - tgt_y_int) * 100); |
| | | int fwd_int = (int)output.forward_mps; |
| | | int fwd_frac = (int)(fabsf(output.forward_mps - fwd_int) * 100); |
| | | int turn_int = (int)output.turn_rate; |
| | | int turn_frac = (int)(fabsf(output.turn_rate - turn_int) * 100); |
| | | int fwd_frac = (int)(fabsf(output.forward_mps - (float)fwd_int) * 100.0f); |
| | | /* 修复:保留turn的符号,使用带符号格式输出 */ |
| | | float turn_abs = fabsf(output.turn_rate); |
| | | int turn_int = (int)turn_abs; |
| | | int turn_frac = (int)((turn_abs - (float)turn_int) * 100.0f); |
| | | char turn_sign = (output.turn_rate < 0.0f) ? '-' : '+'; |
| | | |
| | | HIDO_Debug2("[MC_CTRL] stage=%d pos=(%d.%02d,%d.%02d,%d.%02d) hdg=%d.%02d tgt=(%d.%02d,%d.%02d) fwd=%d.%02d turn=%d.%02d path_idx=%u\n", |
| | | HIDO_Debug2("[MC_CTRL] stage=%d pos=(%d.%02d,%d.%02d,%d.%02d) hdg=%d.%02d tgt=(%d.%02d,%d.%02d) fwd=%d.%02d turn=%c%d.%02d path_idx=%u\n", |
| | | g_motion_state.stage, |
| | | pos_x_int, pos_x_frac, |
| | | pos_y_int, pos_y_frac, |
| | |
| | | tgt_x_int, tgt_x_frac, |
| | | tgt_y_int, tgt_y_frac, |
| | | fwd_int, fwd_frac, |
| | | turn_int, turn_frac, |
| | | turn_sign, turn_int, turn_frac, |
| | | g_motion_state.nearest_index); |
| | | } |
| | | |
| | |
| | | g_last_pitch_deg = output.pitch_deg; |
| | | g_last_roll_deg = output.roll_deg; |
| | | g_last_pose_valid = g_motion_state.pose_valid; |
| | | g_last_target_valid = output.target_valid; |
| | | |
| | | /* 检测目标点跳变到(0,0)的情况 */ |
| | | static HIDO_BOOL s_warn_zero_target = HIDO_FALSE; |
| | | if (output.target_valid == HIDO_TRUE) |
| | | { |
| | | /* 检测目标点突然跳到接近(0,0)的情况 */ |
| | | HIDO_BOOL is_near_zero = (fabsf(output.target_xy[0]) < 0.1f && fabsf(output.target_xy[1]) < 0.1f); |
| | | HIDO_BOOL was_far_from_zero = (fabsf(g_last_target_xy[0]) > 1.0f || fabsf(g_last_target_xy[1]) > 1.0f); |
| | | |
| | | if (is_near_zero && was_far_from_zero && g_last_target_valid) |
| | | { |
| | | int old_x_int = (int)g_last_target_xy[0]; |
| | | int old_x_frac = (int)(fabsf(g_last_target_xy[0] - old_x_int) * 100); |
| | | int old_y_int = (int)g_last_target_xy[1]; |
| | | int old_y_frac = (int)(fabsf(g_last_target_xy[1] - old_y_int) * 100); |
| | | |
| | | HIDO_Debug2("[MC_TGT]WARNING: Target jumped to (0,0)! Previous target=(%d.%02d,%d.%02d) stage=%d\r\n", |
| | | old_x_int, old_x_frac, old_y_int, old_y_frac, output.stage); |
| | | s_warn_zero_target = HIDO_TRUE; |
| | | } |
| | | else if (!is_near_zero && s_warn_zero_target) |
| | | { |
| | | int new_x_int = (int)output.target_xy[0]; |
| | | int new_x_frac = (int)(fabsf(output.target_xy[0] - new_x_int) * 100); |
| | | int new_y_int = (int)output.target_xy[1]; |
| | | int new_y_frac = (int)(fabsf(output.target_xy[1] - new_y_int) * 100); |
| | | |
| | | HIDO_Debug2("[MC_TGT]Target recovered from (0,0) to (%d.%02d,%d.%02d)\r\n", |
| | | new_x_int, new_x_frac, new_y_int, new_y_frac); |
| | | s_warn_zero_target = HIDO_FALSE; |
| | | } |
| | | |
| | | g_last_target_xy[0] = output.target_xy[0]; |
| | | g_last_target_xy[1] = output.target_xy[1]; |
| | | g_last_target_valid = HIDO_TRUE; |
| | | } |
| | | else |
| | | { |
| | | /* target_valid变为FALSE */ |
| | | if (g_last_target_valid) |
| | | { |
| | | HIDO_Debug2("[MC_TGT]target_valid: TRUE->FALSE in task (will report 0,0)\r\n"); |
| | | } |
| | | g_last_target_valid = HIDO_FALSE; |
| | | } |
| | | |
| | | g_freq_sample_count++; |