add gimbal

This commit is contained in:
Robofish 2026-03-20 04:42:26 +08:00
parent 8355c11c23
commit ec273c1593
2 changed files with 547 additions and 0 deletions

323
gimbal.c Normal file
View File

@ -0,0 +1,323 @@
/*
*
*/
/* Includes ----------------------------------------------------------------- */
#include "gimbal.h"
#include "bsp/can.h"
#include "bsp/time.h"
#include <math.h>
#include "component/filter.h"
#include "component/limiter.h"
#include "component/pid.h"
#include "device/gimbal_imu.h"
#include "device/motor_dm.h"
#include "device/motor_rm.h"
/* Private typedef ---------------------------------------------------------- */
/* Private define ----------------------------------------------------------- */
/* Private macro ------------------------------------------------------------ */
/* Private variables -------------------------------------------------------- */
/* Private function -------------------------------------------------------- */
#define GIMBAL_YAW_INERTIA 0.05f
#define GIMBAL_PIT_INERTIA 0.03f
/**
* \brief
*
* \param c
* \param mode
*
* \return
*/
static int8_t Gimbal_SetMode(Gimbal_t *g, Gimbal_Mode_t mode) {
if (g == NULL)
return -1;
if (mode == g->mode)
return GIMBAL_OK;
// 检查是否relax->absolute
int relax_to_absolute = (g->mode == GIMBAL_MODE_RELAX && mode == GIMBAL_MODE_ABSOLUTE);
PID_Reset(&g->pid.yaw_angle);
PID_Reset(&g->pid.yaw_omega);
PID_Reset(&g->pid.pit_angle);
PID_Reset(&g->pid.pit_omega);
PID_Reset(&g->pid.aimbot.yaw_angle);
PID_Reset(&g->pid.aimbot.yaw_omega);
PID_Reset(&g->pid.aimbot.pit_angle);
PID_Reset(&g->pid.aimbot.pit_omega);
PID_Reset(&g->pid.recover.yaw_angle);
PID_Reset(&g->pid.recover.pit_angle);
LowPassFilter2p_Reset(&g->filter_out.yaw, 0.0f);
LowPassFilter2p_Reset(&g->filter_out.pit, 0.0f);
MOTOR_DM_Enable(&(g->param->yaw_motor));
AHRS_ResetEulr(&(g->setpoint.eulr)); /* 切换模式后重置设定值 */
if (relax_to_absolute) {
// pitch回到限位中点
g->setpoint.eulr.pit = 0.5f * (g->limit.pit.max + g->limit.pit.min);
} else {
g->setpoint.eulr.pit = g->feedback.imu.eulr.rol;
}
g->setpoint.eulr.yaw = g->feedback.imu.eulr.yaw;
g->mode = mode;
return 0;
}
/* Exported functions ------------------------------------------------------- */
/**
* \brief
*
* \param g
* \param param
* \param target_freq
*
* \return
*/
int8_t Gimbal_Init(Gimbal_t *g, const Gimbal_Params_t *param,
float target_freq) {
if (g == NULL)
return -1;
g->param = (Gimbal_Params_t *)param;
g->mode = GIMBAL_MODE_RELAX; /* 设置默认模式 */
/* 先初始化CAN */
BSP_CAN_Init();
/* 初始化云台电机控制PID和LPF */
PID_Init(&(g->pid.yaw_angle), KPID_MODE_NO_D, target_freq,
&(g->param->pid.yaw_angle));
PID_Init(&(g->pid.yaw_omega), KPID_MODE_CALC_D, target_freq,
&(g->param->pid.yaw_omega));
PID_Init(&(g->pid.pit_angle), KPID_MODE_NO_D, target_freq,
&(g->param->pid.pit_angle));
PID_Init(&(g->pid.pit_omega), KPID_MODE_CALC_D, target_freq,
&(g->param->pid.pit_omega));
PID_Init(&(g->pid.aimbot.yaw_angle), KPID_MODE_SET_D, target_freq,
&(g->param->pid.yaw_angle));
PID_Init(&(g->pid.aimbot.yaw_omega), KPID_MODE_SET_D, target_freq,
&(g->param->pid.yaw_omega));
PID_Init(&(g->pid.aimbot.pit_angle), KPID_MODE_SET_D, target_freq,
&(g->param->pid.pit_angle));
PID_Init(&(g->pid.aimbot.pit_omega), KPID_MODE_SET_D, target_freq,
&(g->param->pid.pit_omega));
/* Recover模式PID单环编码器反馈 */
PID_Init(&(g->pid.recover.yaw_angle), KPID_MODE_NO_D, target_freq,
&(g->param->pid.recover.yaw_angle));
PID_Init(&(g->pid.recover.pit_angle), KPID_MODE_NO_D, target_freq,
&(g->param->pid.recover.pit_angle));
LowPassFilter2p_Init(&g->filter_out.yaw, target_freq,
g->param->low_pass_cutoff_freq.out);
LowPassFilter2p_Init(&g->filter_out.pit, target_freq,
g->param->low_pass_cutoff_freq.out);
g->limit.yaw.max = g->param->mech_zero.yaw + g->param->travel.yaw;
g->limit.yaw.min = g->param->mech_zero.yaw;
g->limit.pit.max = g->param->mech_zero.pit + g->param->travel.pit;
g->limit.pit.min = g->param->mech_zero.pit;
MOTOR_RM_Register(&(g->param->pit_motor));
MOTOR_DM_Register(&(g->param->yaw_motor));
MOTOR_DM_Enable(&(g->param->yaw_motor));
/* 最后初始化IMU确保CAN和电机都已经设置好 */
GIMBAL_IMU_Init(&g->imu, &(g->param->imu));
return 0;
}
/**
* \brief CAN设备更新云台反馈信息
*
* \param gimbal
* \param can CAN设备
*
* \return
*/
int8_t Gimbal_UpdateFeedback(Gimbal_t *gimbal) {
if (gimbal == NULL)
return -1;
/* 更新RM电机反馈数据pitch轴 */
MOTOR_RM_Update(&(gimbal->param->pit_motor));
MOTOR_RM_t *rm_motor = MOTOR_RM_GetMotor(&(gimbal->param->pit_motor));
if (rm_motor != NULL) {
gimbal->feedback.motor.pit = rm_motor->feedback;
}
/* 更新DM电机反馈数据yaw轴 */
MOTOR_DM_Update(&(gimbal->param->yaw_motor));
MOTOR_DM_t *dm_motor = MOTOR_DM_GetMotor(&(gimbal->param->yaw_motor));
if (dm_motor != NULL) {
gimbal->feedback.motor.yaw = dm_motor->motor.feedback;
}
return 0;
}
int8_t Gimbal_UpdateIMU(Gimbal_t *gimbal){
if (gimbal == NULL) {
return -1;
}
GIMBAL_IMU_Update(&gimbal->imu);
/* 将IMU数据复制到feedback中供控制使用 */
gimbal->feedback.imu.eulr = gimbal->imu.data.eulr;
gimbal->feedback.imu.gyro = gimbal->imu.data.gyro;
gimbal->feedback.imu.accl = gimbal->imu.data.accl;
return 0;
}
/**
* \brief
*
* \param g
* \param g_cmd
*
* \return
*/
int8_t Gimbal_Control(Gimbal_t *g, Gimbal_CMD_t *g_cmd, Gimbal_AI_t *g_ai) {
if (g == NULL || g_cmd == NULL) {
return -1;
}
g->dt = (BSP_TIME_Get_us() - g->lask_wakeup) / 1000000.0f;
g->lask_wakeup = BSP_TIME_Get_us();
/* AI自瞄保护mode_ai==0 丢失目标保持AI模式但锁定当前姿态 */
Gimbal_SetMode(g, g_cmd->mode);
/* 处理yaw控制命令软件限位 */
float delta_yaw = g_cmd->delta_yaw * g->dt * 1.5f;
if (g->param->travel.yaw > 0) {
CircleAngleDeltaLimit(&delta_yaw, g->setpoint.eulr.yaw,
g->feedback.motor.yaw.rotor_abs_angle,
g->feedback.imu.eulr.yaw,
g->limit.yaw.max, g->limit.yaw.min, M_2PI);
}
CircleAdd(&(g->setpoint.eulr.yaw), delta_yaw, M_2PI);
/* 处理pitch控制命令软件限位 */
float delta_pit = g_cmd->delta_pit * g->dt;
if (g->param->travel.pit > 0) {
CircleAngleDeltaLimit(&delta_pit, g->setpoint.eulr.pit,
g->feedback.motor.pit.rotor_abs_angle,
g->feedback.imu.eulr.rol,
g->limit.pit.max, g->limit.pit.min, M_2PI);
}
CircleAdd(&(g->setpoint.eulr.pit), delta_pit, M_2PI);
if (g_cmd->mode == GIMBAL_MODE_AI_CONTROL && g_cmd->mode_ai != 0) {
/* AI 模式且有目标(mode_ai==1/2)跟踪用AI下发的setpoint */
g->setpoint.eulr.yaw = g_cmd->setpoint_yaw;
g->setpoint.eulr.pit = g_cmd->setpoint_pit;
} else if (g_cmd->mode == GIMBAL_MODE_RECOVER) {
/* 双零点选近mech_zero 和 mech_zero+π,选离当前编码器角度最近的 */
float yaw_enc = g->feedback.motor.yaw.rotor_abs_angle;
float zero0 = g->param->mech_zero.yaw;
float zero1 = zero0 + M_PI;
if (zero1 >= M_2PI) zero1 -= M_2PI;
float err0 = fabsf(CircleError(zero0, yaw_enc, M_2PI));
float err1 = fabsf(CircleError(zero1, yaw_enc, M_2PI));
g->setpoint.eulr.yaw = (err0 <= err1) ? zero0 : zero1;
g->setpoint.eulr.pit = 0.5f * (g->limit.pit.max + g->limit.pit.min);
}
/* 控制相关逻辑 */
float yaw_omega_set_point, pit_omega_set_point;
switch (g->mode) {
case GIMBAL_MODE_RELAX:
g->out.yaw = 0.0f;
g->out.pit = 0.0f;
break;
case GIMBAL_MODE_ABSOLUTE:
case GIMBAL_MODE_RELATIVE:
yaw_omega_set_point = PID_Calc(&(g->pid.yaw_angle), g->setpoint.eulr.yaw,
g->feedback.imu.eulr.yaw, 0.0f, g->dt);
g->out.yaw = PID_Calc(&(g->pid.yaw_omega), yaw_omega_set_point,
g->feedback.imu.gyro.z, 0.f, g->dt);
pit_omega_set_point = PID_Calc(&(g->pid.pit_angle), g->setpoint.eulr.pit,
g->feedback.imu.eulr.rol, 0.0f, g->dt);
g->out.pit = PID_Calc(&(g->pid.pit_omega), pit_omega_set_point,
g->feedback.imu.gyro.y, 0.f, g->dt);
break;
case GIMBAL_MODE_AI_CONTROL:
if (g_cmd->mode_ai == 0) {
/* 丢失目标setpoint保持不变用普通PID锁住当前姿态无前馈 */
yaw_omega_set_point = PID_Calc(&(g->pid.yaw_angle), g->setpoint.eulr.yaw,
g->feedback.imu.eulr.yaw, 0.0f, g->dt);
g->out.yaw = PID_Calc(&(g->pid.yaw_omega), yaw_omega_set_point,
g->feedback.imu.gyro.z, 0.f, g->dt);
pit_omega_set_point = PID_Calc(&(g->pid.pit_angle), g->setpoint.eulr.pit,
g->feedback.imu.eulr.rol, 0.0f, g->dt);
g->out.pit = PID_Calc(&(g->pid.pit_omega), pit_omega_set_point,
g->feedback.imu.gyro.y, 0.f, g->dt);
} else {
/* mode_ai==1/2跟踪目标带前馈 */
/* --- YAW --- */
yaw_omega_set_point = PID_Calc(&(g->pid.yaw_angle), g->setpoint.eulr.yaw,
g->feedback.imu.eulr.yaw, 0.0f, g->dt);
yaw_omega_set_point += g_cmd->ff_vel_yaw;
g->out.yaw = PID_Calc(&(g->pid.yaw_omega), yaw_omega_set_point,
g->feedback.imu.gyro.z, 0.f, g->dt);
g->out.yaw += g_cmd->ff_accl_yaw * GIMBAL_YAW_INERTIA;
/* --- PIT --- */
pit_omega_set_point = PID_Calc(&(g->pid.pit_angle), -g->setpoint.eulr.pit,
g->feedback.imu.eulr.rol, 0.0f, g->dt);
pit_omega_set_point -= g_cmd->ff_vel_pit;
g->out.pit = PID_Calc(&(g->pid.pit_omega), pit_omega_set_point,
g->feedback.imu.gyro.y, 0.f, g->dt);
g->out.pit -= g_cmd->ff_accl_pit * GIMBAL_PIT_INERTIA;
}
break;
case GIMBAL_MODE_RECOVER:
g->out.yaw = PID_Calc(&(g->pid.recover.yaw_angle), g->setpoint.eulr.yaw,
g->feedback.motor.yaw.rotor_abs_angle, 0.0f, g->dt);
g->out.pit = PID_Calc(&(g->pid.recover.pit_angle), g->setpoint.eulr.pit,
g->feedback.motor.pit.rotor_abs_angle, 0.0f, g->dt);
break;
}
/* 输出滤波 */
g->out.yaw = LowPassFilter2p_Apply(&g->filter_out.yaw, g->out.yaw);
g->out.pit = LowPassFilter2p_Apply(&g->filter_out.pit, g->out.pit);
return 0;
}
/**
* \brief
*
* \param s
* \param out CAN设备云台输出结构体
*/
void Gimbal_Output(Gimbal_t *g){
MOTOR_RM_SetOutput(&g->param->pit_motor, g->out.pit);
MOTOR_MIT_Output_t output = {0};
output.torque = g->out.yaw * 5.0f;
output.kd = 0.3f;
MOTOR_RM_Ctrl(&g->param->pit_motor);
MOTOR_DM_MITCtrl(&g->param->yaw_motor, &output);
}
/**
* @brief UI数据
*
* @param g
* @param ui UI结构体
*/
void Gimbal_DumpUI(const Gimbal_t *g, Gimbal_RefereeUI_t *ui) {
ui->mode = g->mode;
}

224
gimbal.h Normal file
View File

@ -0,0 +1,224 @@
/*
*
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ----------------------------------------------------------------- */
#include "component/ahrs.h"
#include "component/filter.h"
#include "component/pid.h"
#include "device/motor.h"
#include "device/motor_dm.h"
#include "device/motor_rm.h"
#include "device/gimbal_imu.h"
/* Exported constants ------------------------------------------------------- */
#define GIMBAL_OK (0) /* 运行正常 */
#define GIMBAL_ERR (-1) /* 运行时发现了其他错误 */
#define GIMBAL_ERR_NULL (-2) /* 运行时发现NULL指针 */
#define GIMBAL_ERR_MODE (-3) /* 运行时配置了错误的CMD_GimbalMode_t */
/* Exported macro ----------------------------------------------------------- */
/* Exported types ----------------------------------------------------------- */
typedef enum {
GIMBAL_MODE_RELAX, /* 放松模式,电机不输出。一般情况云台初始化之后的模式 */
GIMBAL_MODE_ABSOLUTE, /* 绝对坐标系控制,控制在空间内的绝对姿态 */
GIMBAL_MODE_RELATIVE, /* 相对坐标系控制,控制相对于底盘的姿态 */
GIMBAL_MODE_AI_CONTROL, /* AI控制模式直接接受AI下发的目标角度 */
GIMBAL_MODE_RECOVER /* 自起模式,底盘翻倒后自动恢复到正常姿态 */
} Gimbal_Mode_t;
/* UI 导出结构(供 referee 系统绘制) */
typedef struct {
Gimbal_Mode_t mode;
} Gimbal_RefereeUI_t;
typedef struct {
Gimbal_Mode_t mode;
uint8_t mode_ai; /* AI模式细分0表示不控制1表示控制云台但不开火2表示控制云台且开火 */
float delta_yaw;
float delta_pit;
/* GIMBAL_MODE_AI_CONTROL 速度/加速度前馈(来自 NUC */
float setpoint_yaw;
float setpoint_pit;
float ff_vel_yaw; /* yaw 速度前馈rad/s叠加到内环角速度设定值 */
float ff_vel_pit; /* pit 速度前馈rad/s */
float ff_accl_yaw; /* yaw 加速度前馈(归一化),叠加到力矩输出 */
float ff_accl_pit; /* pit 加速度前馈(归一化) */
} Gimbal_CMD_t;
typedef struct {
bool ctrl;
float yaw;
float pit;
} Gimbal_AI_t;
/* 软件限位 */
typedef struct {
float max;
float min;
} Gimbal_Limit_t;
/* 云台参数的结构体包含所有初始化用的参数通常是const存好几组。*/
typedef struct {
MOTOR_RM_Param_t pit_motor; /* pitch轴电机参数 */
MOTOR_DM_Param_t yaw_motor; /* yaw轴电机参数 */
GIMBAL_IMU_Param_t imu; /* 云台IMU参数 */
struct {
KPID_Params_t yaw_omega; /* yaw轴角速度环PID参数 */
KPID_Params_t yaw_angle; /* yaw轴角位置环PID参数 */
KPID_Params_t pit_omega; /* pitch轴角速度环PID参数 */
KPID_Params_t pit_angle; /* pitch轴角位置环PID参数 */
struct {
KPID_Params_t yaw_angle; /* yaw轴角位置环PID参数 */
KPID_Params_t yaw_omega; /* yaw轴角速度环PID参数 */
KPID_Params_t pit_angle; /* pitch轴角位置环PID参数 */
KPID_Params_t pit_omega; /* pitch轴角速度环PID参数 */
} aimbot;
struct {
KPID_Params_t yaw_angle; /* recover yaw位置环PID参数 */
KPID_Params_t pit_angle; /* recover pitch位置环PID参数 */
} recover;
} pid;
/* 低通滤波器截止频率 */
struct {
float out; /* 电机输出 */
float gyro; /* 陀螺仪数据 */
} low_pass_cutoff_freq;
struct {
float yaw; /* yaw轴机械限位 */
float pit; /* pitch轴机械限位 */
} mech_zero;
struct {
float yaw; /* yaw轴机械限位行程 -1表示无限位 */
float pit; /* pitch轴机械限位行程 -1表示无限位*/
} travel;
} Gimbal_Params_t;
/* 云台反馈数据的结构体,包含反馈控制用的反馈数据 */
typedef struct {
struct {
AHRS_Eulr_t eulr; /* 云台姿态欧拉角 */
AHRS_Gyro_t gyro; /* 云台陀螺仪数据 */
AHRS_Accl_t accl; /* 云台加速度计数据 */
}imu;
struct {
MOTOR_Feedback_t yaw; /* yaw轴电机反馈 */
MOTOR_Feedback_t pit; /* pitch轴电机反馈 */
} motor;
} Gimbal_Feedback_t;
/* 云台输出数据的结构体*/
typedef struct {
float yaw; /* yaw轴电机输出 */
float pit; /* pitch轴电机输出 */
} Gimbal_Output_t;
/*
*
*
*/
typedef struct {
uint64_t lask_wakeup;
float dt;
Gimbal_Params_t *param; /* 云台的参数用Gimbal_Init设定 */
/* 模块通用 */
Gimbal_Mode_t mode; /* 云台模式 */
GIMBAL_IMU_t imu; /* 云台IMU数据 */
/* PID计算的目标值 */
struct {
AHRS_Eulr_t eulr; /* 表示云台姿态的欧拉角 */
} setpoint;
struct {
KPID_t yaw_angle; /* yaw轴角位置环PID */
KPID_t yaw_omega; /* yaw轴角速度环PID */
KPID_t pit_angle; /* pitch轴角位置环PID */
KPID_t pit_omega; /* pitch轴角速度环PID */
struct {
KPID_t yaw_angle; /* yaw轴角位置环PID */
KPID_t yaw_omega; /* yaw轴角速度环PID */
KPID_t pit_angle; /* pitch轴角位置环PID */
KPID_t pit_omega; /* pitch轴角速度环PID */
} aimbot;
struct {
KPID_t yaw_angle; /* recover yaw位置环PID */
KPID_t pit_angle; /* recover pitch位置环PID */
} recover;
} pid;
struct {
Gimbal_Limit_t yaw;
Gimbal_Limit_t pit;
} limit;
struct {
LowPassFilter2p_t yaw;
LowPassFilter2p_t pit;
} filter_out;
Gimbal_Output_t out; /* 云台输出 */
Gimbal_Feedback_t feedback; /* 反馈 */
} Gimbal_t;
/* Exported functions prototypes -------------------------------------------- */
/**
* \brief
*
* \param g
* \param param
* \param target_freq
*
* \return
*/
int8_t Gimbal_Init(Gimbal_t *g, const Gimbal_Params_t *param,
float target_freq);
/**
* \brief CAN设备更新云台反馈信息
*
* \param gimbal
* \param can CAN设备
*
* \return
*/
int8_t Gimbal_UpdateFeedback(Gimbal_t *gimbal);
int8_t Gimbal_UpdateIMU(Gimbal_t *gimbal);
/**
* \brief
*
* \param g
* \param fb
* \param g_cmd
* \param dt_sec
*
* \return
*/
int8_t Gimbal_Control(Gimbal_t *g, Gimbal_CMD_t *g_cmd, Gimbal_AI_t *g_ai);
/**
* \brief
*
* \param s
* \param out CAN设备云台输出结构体
*/
void Gimbal_Output(Gimbal_t *g);
#ifdef __cplusplus
}
#endif