/******************************************************************************* * 文件名称 : motion_control.c * 文件说明 : 割草机纯追踪运动控制器(C 语言实现) * 创建日期 : 2025-11-22 *******************************************************************************/ #include "motion_control.h" #include #include #include "arm_math.h" #include "motion_config.h" #ifndef M_PI #define M_PI (3.14159265358979323846f) #endif #define DEG2RAD (0.017453292519943295769236907684886f) #define RAD2DEG (57.295779513082320876798154814105f) #define MC_CLAMP(v, lo, hi) ((v) < (lo) ? (lo) : (((v) > (hi)) ? (hi) : (v))) /* 当航向误差逼近 ±π 时的判决窗口(约 0.5°) */ #define MC_HEADING_PI_EPS (0.00872664626f) /* 将角度约束到 [-pi, pi],避免航向跳变 */ static float mc_wrap_angle(float angle) { while (angle > (float)M_PI) { angle -= 2.0f * (float)M_PI; } while (angle < -(float)M_PI) { angle += 2.0f * (float)M_PI; } return angle; } /* 在误差接近 ±π 时保持上一周期的转向方向,避免来回反转 */ static float mc_heading_error_with_hysteresis(MC_State *state, float desired_heading) { float err = mc_wrap_angle(desired_heading - state->heading_rad); float abs_err = fabsf(err); float pi_delta = (float)M_PI - abs_err; if (pi_delta <= MC_HEADING_PI_EPS) { float preferred_sign = state->last_turn_sign; if (preferred_sign == 0.0f) { preferred_sign = (err >= 0.0f) ? 1.0f : -1.0f; } float old_err = err; err = preferred_sign * abs_err; state->last_turn_sign = preferred_sign; } else if (abs_err > 1.0e-6f) { state->last_turn_sign = (err > 0.0f) ? 1.0f : -1.0f; } return err; } /* 读取路径点,索引越界时截到末尾 */ static void mc_get_point(const MC_State *state, HIDO_UINT32 idx, float outPt[2]) { /* 防御性检查:如果路径数据未初始化,返回原点 */ if (state->path_xy == HIDO_NULL || state->path_count == 0U) { outPt[0] = 0.0f; outPt[1] = 0.0f; return; } if (idx >= state->path_count) { idx = state->path_count - 1U; } outPt[0] = state->path_xy[idx * 2U]; outPt[1] = state->path_xy[idx * 2U + 1U]; } /* 计算两个二维点的平方距离 */ static float mc_distance_sq(const float a[2], const float b[2]) { float dx = a[0] - b[0]; float dy = a[1] - b[1]; return dx * dx + dy * dy; } /* 计算两点距离(含 sqrt 防护) */ static float mc_distance(const float a[2], const float b[2]) { float dist_sq = mc_distance_sq(a, b); float dist = 0.0f; if (dist_sq > 0.0f) { arm_sqrt_f32(dist_sq, &dist); } return dist; } /* 数学坐标(东为 0°,逆时针为正)→ 罗盘坐标(北为 0°,顺时针为正) */ static float mc_math_rad_to_compass_deg(float heading_rad) { float compass_deg = 90.0f - (heading_rad * RAD2DEG); while (compass_deg >= 360.0f) { compass_deg -= 360.0f; } while (compass_deg < 0.0f) { compass_deg += 360.0f; } return compass_deg; } /* 罗盘坐标(北为 0°,顺时针为正)→ 数学坐标(东为 0°,逆时针为正) */ static float mc_compass_deg_to_math_rad(float heading_deg) { float math_deg = 90.0f - heading_deg; return mc_wrap_angle(math_deg * DEG2RAD); } /* 判断车辆是否已经"到达"某个路径点 * 标准:距离该点小于阈值(0.5米)即为到达 */ static HIDO_BOOL mc_is_point_reached(const MC_State *state, HIDO_UINT32 point_idx) { if (point_idx >= state->path_count) { return HIDO_FALSE; } float pos[2] = {state->pos[0], state->pos[1]}; float point[2]; mc_get_point(state, point_idx, point); float dist = mc_distance(pos, point); /* 定义"到达"阈值为0.5米 */ const float REACH_THRESHOLD_M = 0.1f; return (dist < REACH_THRESHOLD_M) ? HIDO_TRUE : HIDO_FALSE; } /* 搜索当前位置最近的路径点(只允许向前推进,必须到达才推进) * 类似航点导航:必须先到达航点0,才能导航到航点1 */ static HIDO_UINT32 mc_find_nearest(const MC_State *state) { HIDO_UINT32 current_idx = state->nearest_index; /* 检查是否到达当前点 */ if (mc_is_point_reached(state, current_idx) == HIDO_TRUE) { /* 到达了,推进到下一个点(如果不是最后一个) */ if (current_idx < state->path_count - 1U) { return current_idx + 1U; } } /* 否则保持当前索引不变 */ return current_idx; } /* 沿路径累积距离,直到达到前视目标 * 注意:lookahead点始终至少是start_idx的下一个点,确保顺序导航 */ static HIDO_UINT32 mc_find_lookahead(const MC_State *state, HIDO_UINT32 start_idx, float lookahead_m) { /* 如果已经是最后一个点,直接返回 */ if (start_idx >= state->path_count - 1U) { return state->path_count - 1U; } /* lookahead点至少是start_idx的下一个点(顺序导航:必须先经过下一个点) */ HIDO_UINT32 next_idx = start_idx + 1U; /* 简化逻辑:直接返回下一个点,确保按顺序访问每个航点 */ return start_idx; } /* 计算带符号横向误差(左正右负) */ static float mc_cross_track_error(const MC_State *state, HIDO_UINT32 nearest_idx) { if (state->path_count < 2U) { return 0.0f; } HIDO_UINT32 idx0, idx1; if (nearest_idx == 0U) { idx0 = 0U; idx1 = (state->path_count > 1U) ? 1U : 0U; } else { idx0 = nearest_idx - 1U; idx1 = nearest_idx; } float p0[2]; float p1[2]; float pos[2] = {state->pos[0], state->pos[1]}; mc_get_point(state, idx0, p0); mc_get_point(state, idx1, p1); float vx = p1[0] - p0[0]; float vy = p1[1] - p0[1]; float wx = pos[0] - p0[0]; float wy = pos[1] - p0[1]; float seg_len_sq = vx * vx + vy * vy; float t = 0.0f; if (seg_len_sq > 0.0f) { t = (wx * vx + wy * vy) / seg_len_sq; } if (t < 0.0f) { t = 0.0f; } else if (t > 1.0f) { t = 1.0f; } float proj_x = p0[0] + vx * t; float proj_y = p0[1] + vy * t; float dx = pos[0] - proj_x; float dy = pos[1] - proj_y; float dist_sq = dx * dx + dy * dy; float dist = 0.0f; if (dist_sq > 0.0f) { arm_sqrt_f32(dist_sq, &dist); } float cross = vx * (pos[1] - p0[1]) - vy * (pos[0] - p0[0]); return (cross >= 0.0f) ? dist : -dist; } /* 阶段:旋转/接近起点,使车准备好跟踪路径 */ static void mc_compute_goto_start(MC_State *state, float dt_s, MC_Output *out) { float start_point[2]; mc_get_point(state, 0U, start_point); /* 检测目标点变化并记录 - 已移除LOG */ static HIDO_UINT32 s_goto_log_idx = 0U; state->current_target_xy[0] = start_point[0]; state->current_target_xy[1] = start_point[1]; out->target_valid = HIDO_TRUE; out->target_xy[0] = start_point[0]; out->target_xy[1] = start_point[1]; float vec_x = start_point[0] - state->pos[0]; float vec_y = start_point[1] - state->pos[1]; float dist = mc_distance(start_point, state->pos); float desired_heading; arm_status status = arm_atan2_f32(vec_y, vec_x, &desired_heading); out->target_heading_deg = mc_math_rad_to_compass_deg(desired_heading); float heading_err = mc_heading_error_with_hysteresis(state, desired_heading); float yaw_rate_cmd = state->config.heading_kp * heading_err; yaw_rate_cmd = MC_CLAMP(yaw_rate_cmd, -state->config.max_turn_rate, state->config.max_turn_rate); float forward = 0.0f; if (dist > state->config.start_pos_tolerance_m) { if (fabsf(heading_err) < state->config.start_heading_tolerance_rad) { float desired = state->config.base_speed_mps + 0.5f * dist; forward = MC_CLAMP(desired, state->config.min_follow_speed_mps, state->config.max_forward_mps); } } else if (fabsf(heading_err) < state->config.start_heading_tolerance_rad) { /* 到达起点且航向对准,切换到路径跟踪模式 */ state->stage = MC_STAGE_FOLLOW_PATH; state->nearest_index = 0U; /* 重置为路径第一个点 */ } out->forward_mps = forward; out->turn_rate = yaw_rate_cmd; out->heading_error = heading_err; out->cross_track_error = dist; out->active = (forward != 0.0f) || (fabsf(yaw_rate_cmd) > 0.01f); (void)dt_s; } /* 阶段:纯追踪 + 航向/横向误差补偿 */ static void mc_compute_follow_path(MC_State *state, float dt_s, MC_Output *out) { /* 安全检查:路径必须至少有1个点 */ if (state->path_count == 0U) { out->forward_mps = 0.0f; out->turn_rate = 0.0f; out->active = HIDO_FALSE; return; } HIDO_UINT32 nearest_idx = mc_find_nearest(state); /* 安全检查:nearest_idx 必须在有效范围内 */ if (nearest_idx >= state->path_count) { nearest_idx = state->path_count - 1U; } state->nearest_index = nearest_idx; float lookahead = state->config.lookahead_min_m; float speed_ratio = MC_CLAMP(state->speed_mps / state->config.max_forward_mps, 0.0f, 1.0f); lookahead += (state->config.lookahead_max_m - state->config.lookahead_min_m) * speed_ratio; HIDO_UINT32 lookahead_idx = mc_find_lookahead(state, nearest_idx, lookahead); /* 安全检查:lookahead_idx 必须在有效范围内 */ if (lookahead_idx >= state->path_count) { lookahead_idx = state->path_count - 1U; } state->lookahead_index = lookahead_idx; /* 调试:每20帧输出一次路径跟踪状态 */ static HIDO_UINT32 s_path_log_idx = 0U; static HIDO_UINT32 s_last_nearest = 0xFFFFFFFF; static HIDO_UINT32 s_last_lookahead = 0xFFFFFFFF; /* 如果索引发生变化,立即输出LOG */ /* 路径点调试LOG - 已移除 */ // static HIDO_UINT32 s_last_nearest = 0xFFFFFFFF; // static HIDO_UINT32 s_last_lookahead = 0xFFFFFFFF; HIDO_BOOL index_changed = (nearest_idx != s_last_nearest) || (lookahead_idx != s_last_lookahead); if (index_changed) { s_last_nearest = nearest_idx; s_last_lookahead = lookahead_idx; } float target[2]; mc_get_point(state, lookahead_idx, target); /* 检测目标点变化并记录 - 已移除LOG */ state->current_target_xy[0] = target[0]; state->current_target_xy[1] = target[1]; out->target_valid = HIDO_TRUE; out->target_xy[0] = target[0]; out->target_xy[1] = target[1]; float vec_x = target[0] - state->pos[0]; float vec_y = target[1] - state->pos[1]; float desired_heading; arm_atan2_f32(vec_y, vec_x, &desired_heading); out->target_heading_deg = mc_math_rad_to_compass_deg(desired_heading); float heading_err = mc_heading_error_with_hysteresis(state, desired_heading); float heading_err_rate = (heading_err - state->last_heading_err) / (dt_s > 0.0f ? dt_s : 0.013f); state->last_heading_err = heading_err; float cross_track = mc_cross_track_error(state, nearest_idx); float p_term = state->config.heading_kp * heading_err; float d_term = state->config.heading_kd * heading_err_rate; float x_term = state->config.xtrack_kp * cross_track; float yaw_rate_cmd = p_term + d_term + x_term; yaw_rate_cmd = MC_CLAMP(yaw_rate_cmd, -state->config.max_turn_rate, state->config.max_turn_rate); /* 调试LOG:每10帧打印一次PID控制详情,帮助分析死区/不转向问题 - 已移除 */ float forward = state->config.base_speed_mps - state->config.heading_speed_scale * fabsf(heading_err) - state->config.xtrack_speed_scale * fabsf(cross_track); forward = MC_CLAMP(forward, state->config.min_follow_speed_mps, state->config.max_forward_mps); float goal[2]; mc_get_point(state, state->path_count - 1U, goal); float goal_dist = mc_distance(goal, state->pos); if (goal_dist < state->config.goal_tolerance_m) { state->stage = MC_STAGE_FINISHED; forward = 0.0f; yaw_rate_cmd = 0.0f; } out->forward_mps = forward; out->turn_rate = yaw_rate_cmd; out->heading_error = heading_err; out->cross_track_error = cross_track; out->active = (state->stage != MC_STAGE_FINISHED); } /* 填充默认配置 */ HIDO_VOID MC_DefaultConfig(MC_Config *_cfg) { if (_cfg == HIDO_NULL) { return; } _cfg->max_forward_mps = MC_CFG_MAX_FORWARD_MPS; _cfg->max_reverse_mps = MC_CFG_MAX_REVERSE_MPS; _cfg->max_turn_rate = MC_CFG_MAX_TURN_RATE_RAD; _cfg->base_speed_mps = MC_CFG_BASE_SPEED_MPS; _cfg->min_follow_speed_mps = MC_CFG_MIN_FOLLOW_SPEED_MPS; _cfg->lookahead_min_m = MC_CFG_LOOKAHEAD_MIN_M; _cfg->lookahead_max_m = MC_CFG_LOOKAHEAD_MAX_M; _cfg->start_pos_tolerance_m = MC_CFG_START_POS_TOL_M; _cfg->start_heading_tolerance_rad = MC_CFG_START_HEADING_TOL_RAD; _cfg->goal_tolerance_m = MC_CFG_GOAL_TOL_M; _cfg->heading_kp = MC_CFG_HEADING_KP; _cfg->heading_kd = MC_CFG_HEADING_KD; _cfg->xtrack_kp = MC_CFG_XTRACK_KP; _cfg->heading_speed_scale = MC_CFG_HEADING_SPEED_SCALE; _cfg->xtrack_speed_scale = MC_CFG_XTRACK_SPEED_SCALE; } /* MC_Init */ HIDO_VOID MC_Init(MC_State *_state, const MC_Config *_cfg, const float *_path_xy, HIDO_UINT32 _point_count) { if (_state == HIDO_NULL || _cfg == HIDO_NULL || _path_xy == HIDO_NULL || _point_count < 2U) { return; } memset(_state, 0, sizeof(MC_State)); _state->config = *_cfg; _state->path_xy = _path_xy; _state->path_count = _point_count; _state->stage = MC_STAGE_GOTO_START; _state->nearest_index = 0U; _state->lookahead_index = 0U; } /* 注入最新 ENU 位姿及航向/速度 */ HIDO_VOID MC_UpdateGps(MC_State *_state, const float _enu[3], const ST_GPRMI *_gprmi) { if (_state == HIDO_NULL || _enu == HIDO_NULL || _gprmi == HIDO_NULL) { return; } _state->pos[0] = _enu[0]; _state->pos[1] = _enu[1]; _state->pos[2] = _enu[2]; float old_heading_deg = _state->heading_deg; float gps_heading_rad = mc_compass_deg_to_math_rad(_gprmi->m_fHeadingAngle); _state->heading_rad = gps_heading_rad; _state->heading_deg = mc_math_rad_to_compass_deg(_state->heading_rad); float old_heading_rad = mc_compass_deg_to_math_rad(old_heading_deg); _state->pitch_deg = _gprmi->m_fPitchAngle; _state->roll_deg = _gprmi->m_fRollAngle; _state->vel[0] = _gprmi->m_fEastVelocity; _state->vel[1] = _gprmi->m_fNorthVelocity; float speed_sq = _state->vel[0] * _state->vel[0] + _state->vel[1] * _state->vel[1]; float speed = 0.0f; if (speed_sq > 0.0f) { arm_sqrt_f32(speed_sq, &speed); } _state->speed_mps = speed; _state->pose_valid = HIDO_TRUE; } /* 更新 IMU 陀螺 Z 轴(yaw 角速度) */ HIDO_VOID MC_UpdateImu(MC_State *_state, const ST_GPIMU *_gpimu) { if (_state == HIDO_NULL || _gpimu == HIDO_NULL) { return; } /* GyroZ符号定义:顺时针正,逆时针负(从上往下看Z轴,右手螺旋定则) * 数学坐标系:东为0°,逆时针为正(CCW>0) * 所以:GyroZ正值(顺时针)→ yaw_rate_rad负值,GyroZ负值(逆时针)→ yaw_rate_rad正值 * 需要取反:yaw_rate_rad = -GyroZ * DEG2RAD */ _state->yaw_rate_rad = -(_gpimu->m_fGyroZ * DEG2RAD); _state->imu_valid = HIDO_TRUE; } /* 入口:根据阶段计算控制量 */ HIDO_VOID MC_Compute(MC_State *_state, float _dt_s, MC_Output *_out) { if (_state == HIDO_NULL || _out == HIDO_NULL) { return; } memset(_out, 0, sizeof(MC_Output)); _out->target_valid = HIDO_FALSE; _out->target_xy[0] = 0.0f; _out->target_xy[1] = 0.0f; /* 记录target_valid变为FALSE的原因 */ static HIDO_BOOL s_last_path_valid = HIDO_TRUE; static HIDO_BOOL s_last_pose_valid = HIDO_TRUE; if (_state->path_xy == HIDO_NULL || _state->path_count < 2U) { if (s_last_path_valid) { s_last_path_valid = HIDO_FALSE; } return; } s_last_path_valid = HIDO_TRUE; if (_state->pose_valid == HIDO_FALSE) { if (s_last_pose_valid) { s_last_pose_valid = HIDO_FALSE; } return; } s_last_pose_valid = HIDO_TRUE; if (_state->imu_valid == HIDO_TRUE && _dt_s > 0.0f) { float old_heading_rad = _state->heading_rad; float old_heading_deg = _state->heading_deg; float delta_rad = _state->yaw_rate_rad * _dt_s; _state->heading_rad = mc_wrap_angle(_state->heading_rad + delta_rad); float new_heading_rad = _state->heading_rad; _state->heading_deg = mc_math_rad_to_compass_deg(_state->heading_rad); (void)new_heading_rad; } if (_state->stage == MC_STAGE_IDLE) { _state->stage = MC_STAGE_GOTO_START; } _out->target_heading_deg = _state->heading_deg; switch (_state->stage) { case MC_STAGE_GOTO_START: mc_compute_goto_start(_state, _dt_s, _out); break; case MC_STAGE_FOLLOW_PATH: mc_compute_follow_path(_state, _dt_s, _out); break; case MC_STAGE_FINISHED: _out->active = HIDO_FALSE; _out->target_heading_deg = _state->heading_deg; break; default: break; } /* 不再取反:yaw_rate_cmd(数学坐标系,正=CCW)直接作为turn_rate输出 * motion_control_task.c中,正值=左转(逆时针),负值=右转(顺时针),与数学坐标系一致 */ // _out->turn_rate = -_out->turn_rate; // 已注释:此取反导致方向错误 _out->stage = _state->stage; _out->pos_enu[0] = _state->pos[0]; _out->pos_enu[1] = _state->pos[1]; _out->pos_enu[2] = _state->pos[2]; _out->heading_deg = _state->heading_deg; _out->pitch_deg = _state->pitch_deg; _out->roll_deg = _state->roll_deg; static HIDO_BOOL s_last_target_valid = HIDO_FALSE; static E_MCStage s_last_stage_for_target = MC_STAGE_IDLE; if (_state->stage == MC_STAGE_FOLLOW_PATH || _state->stage == MC_STAGE_GOTO_START) { _out->target_valid = HIDO_TRUE; _out->target_xy[0] = _state->current_target_xy[0]; _out->target_xy[1] = _state->current_target_xy[1]; if (!s_last_target_valid) { } } else { _out->target_valid = HIDO_FALSE; _out->target_xy[0] = 0.0f; _out->target_xy[1] = 0.0f; if (s_last_target_valid || _state->stage != s_last_stage_for_target) { } } s_last_target_valid = _out->target_valid; s_last_stage_for_target = _state->stage; }