354 lines
11 KiB
C
354 lines
11 KiB
C
![]() |
/*
|
||
|
* 射击模组
|
||
|
*/
|
||
|
|
||
|
/* Includes ----------------------------------------------------------------- */
|
||
|
#include "shoot.h"
|
||
|
|
||
|
#include "bsp/pwm.h"
|
||
|
#include "component\limiter.h"
|
||
|
#include "component\user_math.h"
|
||
|
/* Private typedef ---------------------------------------------------------- */
|
||
|
/* Private define ----------------------------------------------------------- */
|
||
|
|
||
|
#define HEAT_INCREASE_42MM (100.f) /* 每发射一颗42mm弹丸增加100热量 */
|
||
|
#define HEAT_INCREASE_17MM (10.f) /* 每发射一颗17mm弹丸增加10热量 */
|
||
|
|
||
|
#define BULLET_SPEED_LIMIT_42MM (16.0)
|
||
|
#define BULLET_SPEED_LIMIT_17MM (30.0)
|
||
|
|
||
|
/* Private macro ------------------------------------------------------------ */
|
||
|
/* Private variables -------------------------------------------------------- */
|
||
|
/* Private function -------------------------------------------------------- */
|
||
|
|
||
|
/**
|
||
|
* \brief 设置射击模式
|
||
|
*
|
||
|
* \param c 包含射击数据的结构体
|
||
|
* \param mode 要设置的模式
|
||
|
*
|
||
|
* \return 函数运行结果
|
||
|
*/
|
||
|
static int8_t Shoot_SetMode(Shoot_t *s, CMD_ShootMode_t mode) {
|
||
|
if (s == NULL) return -1;
|
||
|
|
||
|
if (mode == s->mode) return SHOOT_OK;
|
||
|
|
||
|
/* 切换模式后重置PID和滤波器 */
|
||
|
for (uint8_t i = 0; i < 3; i++) {
|
||
|
PID_Reset(s->pid.fric + i);
|
||
|
LowPassFilter2p_Reset(s->filter.in.fric + i, 0.0f);
|
||
|
LowPassFilter2p_Reset(s->filter.out.fric + i, 0.0f);
|
||
|
}
|
||
|
PID_Reset(&(s->pid.trig));
|
||
|
LowPassFilter2p_Reset(&(s->filter.in.trig), 0.0f);
|
||
|
LowPassFilter2p_Reset(&(s->filter.out.trig), 0.0f);
|
||
|
|
||
|
while (fabsf(CircleError(s->setpoint.trig_angle, s->feedback.trig_angle,
|
||
|
M_2PI)) >= M_2PI / s->param->num_trig_tooth / 2.0f) {
|
||
|
CircleAdd(&(s->setpoint.trig_angle), M_2PI / s->param->num_trig_tooth,
|
||
|
M_2PI);
|
||
|
}
|
||
|
|
||
|
if (mode == SHOOT_MODE_LOADED) s->fire_ctrl.to_shoot = 0;
|
||
|
|
||
|
s->mode = mode;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief
|
||
|
*
|
||
|
* @param s
|
||
|
* @param s_ref
|
||
|
* @return int8_t
|
||
|
*/
|
||
|
static int8_t Shoot_HeatLimit(Shoot_t *s, Referee_ForShoot_t *s_ref) {
|
||
|
Shoot_HeatCtrl_t *hc = &(s->heat_ctrl);
|
||
|
/* 当裁判系统在线时启用热量控制与射速控制 */
|
||
|
if (s_ref->ref_status == REF_STATUS_RUNNING) {
|
||
|
/* 根据机器人型号获得对应数据 */
|
||
|
if (s->param->model == SHOOT_MODEL_42MM) {
|
||
|
hc->heat = s_ref->power_heat.shoot_42_heat;
|
||
|
hc->heat_limit = s_ref->robot_status.shooter_heat_limit;
|
||
|
hc->speed_limit = BULLET_SPEED_LIMIT_42MM;
|
||
|
hc->cooling_rate = s_ref->robot_status.shooter_cooling_value;
|
||
|
hc->heat_increase = HEAT_INCREASE_42MM;
|
||
|
} else if (s->param->model == SHOOT_MODEL_17MM) {
|
||
|
hc->heat = s_ref->power_heat.shoot_id1_17_heat;
|
||
|
hc->heat_limit = s_ref->robot_status.shooter_heat_limit;
|
||
|
hc->speed_limit = BULLET_SPEED_LIMIT_17MM;
|
||
|
hc->cooling_rate = s_ref->robot_status.shooter_cooling_value;
|
||
|
hc->heat_increase = HEAT_INCREASE_17MM;
|
||
|
}
|
||
|
/* 检测热量更新后,计算可发射弹丸 */
|
||
|
if ((hc->heat != hc->last_heat) || (hc->heat == 0)) {
|
||
|
hc->available_shot =
|
||
|
(uint32_t)floorf((hc->heat_limit - hc->heat) / hc->heat_increase);
|
||
|
hc->last_heat = hc->heat;
|
||
|
}
|
||
|
/* 计算已发射弹丸 */
|
||
|
if (s_ref->shoot_data.bullet_speed != hc->last_bullet_speed) {
|
||
|
hc->last_bullet_speed = s_ref->shoot_data.bullet_speed;
|
||
|
}
|
||
|
s->fire_ctrl.bullet_speed = hc->speed_limit;
|
||
|
} else {
|
||
|
/* 裁判系统离线,不启用热量控制 */
|
||
|
hc->available_shot = 10;
|
||
|
s->fire_ctrl.bullet_speed = s->param->bullet_speed;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Exported functions ------------------------------------------------------- */
|
||
|
|
||
|
/**
|
||
|
* \brief 初始化射击
|
||
|
*
|
||
|
* \param s 包含射击数据的结构体
|
||
|
* \param param 包含射击参数的结构体指针
|
||
|
* \param target_freq 任务预期的运行频率
|
||
|
*
|
||
|
* \return 函数运行结果
|
||
|
*/
|
||
|
int8_t Shoot_Init(Shoot_t *s, const Shoot_Params_t *param, float target_freq) {
|
||
|
if (s == NULL) return -1;
|
||
|
|
||
|
s->param = param; /* 初始化参数 */
|
||
|
s->mode = SHOOT_MODE_RELAX; /* 设置默认模式 */
|
||
|
|
||
|
for (uint8_t i = 0; i < 3; i++) {
|
||
|
/* PI控制器初始化PID */
|
||
|
PID_Init(s->pid.fric + i, KPID_MODE_NO_D, target_freq,
|
||
|
&(param->fric_pid_param));
|
||
|
|
||
|
LowPassFilter2p_Init(s->filter.in.fric + i, target_freq,
|
||
|
param->low_pass_cutoff_freq.in.fric);
|
||
|
|
||
|
LowPassFilter2p_Init(s->filter.out.fric + i, target_freq,
|
||
|
param->low_pass_cutoff_freq.out.fric);
|
||
|
}
|
||
|
|
||
|
PID_Init(&(s->pid.trig), KPID_MODE_CALC_D, target_freq,
|
||
|
&(param->trig_pid_param));
|
||
|
|
||
|
LowPassFilter2p_Init(&(s->filter.in.trig), target_freq,
|
||
|
param->low_pass_cutoff_freq.in.trig);
|
||
|
LowPassFilter2p_Init(&(s->filter.out.trig), target_freq,
|
||
|
param->low_pass_cutoff_freq.out.trig);
|
||
|
|
||
|
BSP_PWM_Start(BSP_PWM_SHOOT_SERVO);
|
||
|
BSP_PWM_Set(BSP_PWM_SHOOT_SERVO, param->cover_close_duty);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* \brief 更新射击的反馈信息
|
||
|
*
|
||
|
* \param s 包含射击数据的结构体
|
||
|
* \param can CAN设备结构体
|
||
|
*
|
||
|
* \return 函数运行结果
|
||
|
*/
|
||
|
int8_t Shoot_UpdateFeedback(Shoot_t *s, const CAN_t *can) {
|
||
|
if (s == NULL) return -1;
|
||
|
if (can == NULL) return -1;
|
||
|
|
||
|
for (uint8_t i = 0; i < 3; i++) {
|
||
|
s->feedback.fric_rpm[i] = can->motor.shoot.as_array[i].rotor_speed;
|
||
|
}
|
||
|
|
||
|
/* 更新拨弹电机 */
|
||
|
float last_trig_motor_angle = s->feedback.trig_motor_angle;
|
||
|
s->feedback.trig_motor_angle = can->motor.shoot.named.trig.rotor_angle;
|
||
|
float motor_angle_delta =
|
||
|
CircleError(s->feedback.trig_motor_angle, last_trig_motor_angle, M_2PI);
|
||
|
CircleAdd(&(s->feedback.trig_angle),
|
||
|
motor_angle_delta / s->param->trig_gear_ratio, M_2PI);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief 运行射击控制逻辑
|
||
|
*
|
||
|
* @param s 包含射击数据的结构体
|
||
|
* @param s_cmd 射击控制指令
|
||
|
* @param s_ref 射击使用的裁判系统数据
|
||
|
* @param now 现在时刻
|
||
|
* @return int8_t
|
||
|
*/
|
||
|
int8_t Shoot_Control(Shoot_t *s, CMD_ShootCmd_t *s_cmd,
|
||
|
Referee_ForShoot_t *s_ref, uint32_t now) {
|
||
|
if (s == NULL) return -1;
|
||
|
|
||
|
s->dt = (float)(now - s->lask_wakeup) / 1000.0f;
|
||
|
s->lask_wakeup = now;
|
||
|
|
||
|
Shoot_SetMode(s, s_cmd->mode); /* 设置射击模式 */
|
||
|
Shoot_HeatLimit(s, s_ref); /* 热量控制 */
|
||
|
/* 根据开火模式计算发射行为 */
|
||
|
s->fire_ctrl.fire_mode = s_cmd->fire_mode;
|
||
|
int32_t max_burst;
|
||
|
switch (s_cmd->fire_mode) {
|
||
|
case FIRE_MODE_SINGLE: /* 点射开火模式 */
|
||
|
max_burst = 1;
|
||
|
break;
|
||
|
case FIRE_MODE_BURST: /* 连发开火模式 */
|
||
|
max_burst = 5;
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
switch (s_cmd->fire_mode) {
|
||
|
case FIRE_MODE_SINGLE: /* 点射开火模式 */
|
||
|
case FIRE_MODE_BURST: { /* 连发开火模式 */
|
||
|
s->fire_ctrl.first_fire = s_cmd->fire && !s->fire_ctrl.last_fire;
|
||
|
s->fire_ctrl.last_fire = s_cmd->fire;
|
||
|
s_cmd->fire = s->fire_ctrl.first_fire;
|
||
|
int32_t max_shot = s->heat_ctrl.available_shot - s->fire_ctrl.shooted;
|
||
|
if (s_cmd->fire && !s->fire_ctrl.to_shoot) {
|
||
|
s->fire_ctrl.to_shoot = min(max_burst, max_shot);
|
||
|
}
|
||
|
if (s->fire_ctrl.shooted >= s->fire_ctrl.to_shoot) {
|
||
|
s_cmd->fire = false;
|
||
|
s->fire_ctrl.period_ms = UINT32_MAX;
|
||
|
s->fire_ctrl.shooted = 0;
|
||
|
s->fire_ctrl.to_shoot = 0;
|
||
|
} else {
|
||
|
s_cmd->fire = true;
|
||
|
s->fire_ctrl.period_ms = s->param->min_shoot_delay;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
case FIRE_MODE_CONT: { /* 持续开火模式 */
|
||
|
float shoot_freq = HeatLimit_ShootFreq(
|
||
|
s->heat_ctrl.heat, s->heat_ctrl.heat_limit, s->heat_ctrl.cooling_rate,
|
||
|
s->heat_ctrl.heat_increase, s->param->model == SHOOT_MODEL_17MM);
|
||
|
s->fire_ctrl.period_ms =
|
||
|
(shoot_freq == 0.0f) ? UINT32_MAX : (uint32_t)(1000.f / shoot_freq);
|
||
|
break;
|
||
|
}
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* 根据模式选择是否使用计算出来的值 */
|
||
|
switch (s->mode) {
|
||
|
case SHOOT_MODE_RELAX:
|
||
|
case SHOOT_MODE_SAFE:
|
||
|
s->fire_ctrl.bullet_speed = 0.0f;
|
||
|
s->fire_ctrl.period_ms = UINT32_MAX;
|
||
|
case SHOOT_MODE_LOADED:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* 计算摩擦轮转速的目标值 */
|
||
|
// s->setpoint.fric_rpm[1] =
|
||
|
// CalculateRpm(s->fire_ctrl.bullet_speed, s->param->fric_radius,
|
||
|
// (s->param->model == SHOOT_MODEL_17MM));
|
||
|
// s->setpoint.fric_rpm[0] = -s->setpoint.fric_rpm[1];
|
||
|
|
||
|
float fric_rpm = CalculateRpm(s->fire_ctrl.bullet_speed, s->param->fric_radius,
|
||
|
(s->param->model == SHOOT_MODEL_17MM));
|
||
|
s->setpoint.fric_rpm[0] = -fric_rpm;
|
||
|
s->setpoint.fric_rpm[1] = fric_rpm;
|
||
|
s->setpoint.fric_rpm[2] = -fric_rpm;
|
||
|
|
||
|
/* 计算拨弹电机位置的目标值 */
|
||
|
if (((now - s->fire_ctrl.last_shoot) >= s->fire_ctrl.period_ms) &&
|
||
|
(s_cmd->fire)) {
|
||
|
/* 将拨弹电机角度进行循环加法,每次加(减)射出一颗弹丸的弧度变化 */
|
||
|
if (s_cmd->reverse_trig) { /* 反转拨弹 */
|
||
|
CircleAdd(&(s->setpoint.trig_angle), -M_2PI / s->param->num_trig_tooth,
|
||
|
M_2PI);
|
||
|
} else {
|
||
|
CircleAdd(&(s->setpoint.trig_angle), M_2PI / s->param->num_trig_tooth,
|
||
|
M_2PI);
|
||
|
s->fire_ctrl.shooted++;
|
||
|
s->fire_ctrl.last_shoot = now;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
switch (s->mode) {
|
||
|
case SHOOT_MODE_RELAX:
|
||
|
for (uint8_t i = 0; i < SHOOT_ACTR_NUM; i++) {
|
||
|
s->out[i] = 0.0f;
|
||
|
}
|
||
|
BSP_PWM_Stop(BSP_PWM_SHOOT_SERVO);
|
||
|
break;
|
||
|
|
||
|
case SHOOT_MODE_SAFE:
|
||
|
case SHOOT_MODE_LOADED:
|
||
|
/* 控制拨弹电机 */
|
||
|
s->feedback.trig_angle =
|
||
|
LowPassFilter2p_Apply(&(s->filter.in.trig), s->feedback.trig_angle);
|
||
|
|
||
|
s->out[SHOOT_ACTR_TRIG_IDX] =
|
||
|
PID_Calc(&(s->pid.trig), s->setpoint.trig_angle,
|
||
|
s->feedback.trig_angle, 0.0f, s->dt);
|
||
|
s->out[SHOOT_ACTR_TRIG_IDX] = LowPassFilter2p_Apply(
|
||
|
&(s->filter.out.trig), s->out[SHOOT_ACTR_TRIG_IDX]);
|
||
|
|
||
|
for (uint8_t i = 0; i < 3; i++) {
|
||
|
/* 控制摩擦轮 */
|
||
|
s->feedback.fric_rpm[i] = LowPassFilter2p_Apply(
|
||
|
&(s->filter.in.fric[i]), s->feedback.fric_rpm[i]);
|
||
|
|
||
|
s->out[SHOOT_ACTR_FRIC1_IDX + i] =
|
||
|
PID_Calc(&(s->pid.fric[i]), s->setpoint.fric_rpm[i],
|
||
|
s->feedback.fric_rpm[i], 0.0f, s->dt);
|
||
|
|
||
|
s->out[SHOOT_ACTR_FRIC1_IDX + i] = LowPassFilter2p_Apply(
|
||
|
&(s->filter.out.fric[i]), s->out[SHOOT_ACTR_FRIC1_IDX + i]);
|
||
|
}
|
||
|
|
||
|
/* 根据弹仓盖开关状态更新弹舱盖打开时舵机PWM占空比 */
|
||
|
if (s_cmd->cover_open) {
|
||
|
BSP_PWM_Start(BSP_PWM_SHOOT_SERVO);
|
||
|
BSP_PWM_Set(BSP_PWM_SHOOT_SERVO, s->param->cover_open_duty);
|
||
|
} else {
|
||
|
BSP_PWM_Start(BSP_PWM_SHOOT_SERVO);
|
||
|
BSP_PWM_Set(BSP_PWM_SHOOT_SERVO, s->param->cover_close_duty);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* \brief 复制射击输出值
|
||
|
*
|
||
|
* \param s 包含射击数据的结构体
|
||
|
* \param out CAN设备射击输出结构体
|
||
|
*/
|
||
|
void Shoot_DumpOutput(Shoot_t *s, CAN_ShootOutput_t *out) {
|
||
|
for (uint8_t i = 0; i < SHOOT_ACTR_NUM; i++) {
|
||
|
out->as_array[i] = s->out[i];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* \brief 清空输出值
|
||
|
*
|
||
|
* \param output 要清空的结构体
|
||
|
*/
|
||
|
void Shoot_ResetOutput(CAN_ShootOutput_t *output) {
|
||
|
int i = 0;
|
||
|
for (i = 0; i < 3; i++) {
|
||
|
output->as_array[i] = 0.0f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief 导出射击UI数据
|
||
|
*
|
||
|
* @param s 射击结构体
|
||
|
* @param ui UI结构体
|
||
|
*/
|
||
|
void Shoot_DumpUI(Shoot_t *s, Referee_ShootUI_t *ui) {
|
||
|
ui->mode = s->mode;
|
||
|
ui->fire = s->fire_ctrl.fire_mode;
|
||
|
}
|