modified: MDK-ARM/DevC.uvoptx
modified: MDK-ARM/DevC.uvprojx modified: MDK-ARM/DevC/DevC.axf modified: MDK-ARM/DevC/DevC.hex modified: User/bsp/uart.c modified: User/bsp/uart.h modified: User/module/config.c modified: User/module/gimbal.c modified: User/module/gimbal.h modified: User/module/shoot.c modified: User/task/ai.c modified: User/task/atti_esti.c modified: User/task/ctrl_gimbal.c modified: User/task/rc.c
This commit is contained in:
parent
cf8c530dc8
commit
c1a0c821c3
@ -188,12 +188,12 @@
|
||||
<Ww>
|
||||
<count>6</count>
|
||||
<WinNumber>1</WinNumber>
|
||||
<ItemText>\\DevC\../User/module/config.c\robot_config.gimbal_param.mech_zero.pit</ItemText>
|
||||
<ItemText>tx_pkg</ItemText>
|
||||
</Ww>
|
||||
<Ww>
|
||||
<count>7</count>
|
||||
<WinNumber>1</WinNumber>
|
||||
<ItemText>BSP_UART_AI</ItemText>
|
||||
<ItemText>tx_pkg</ItemText>
|
||||
</Ww>
|
||||
<Ww>
|
||||
<count>8</count>
|
||||
@ -203,14 +203,81 @@
|
||||
<Ww>
|
||||
<count>9</count>
|
||||
<WinNumber>1</WinNumber>
|
||||
<ItemText>tx_pkg</ItemText>
|
||||
<ItemText>task_runtime</ItemText>
|
||||
</Ww>
|
||||
<Ww>
|
||||
<count>10</count>
|
||||
<WinNumber>1</WinNumber>
|
||||
<ItemText>tx_pkg</ItemText>
|
||||
<ItemText>gimbal_imu_to_send</ItemText>
|
||||
</Ww>
|
||||
<Ww>
|
||||
<count>11</count>
|
||||
<WinNumber>1</WinNumber>
|
||||
<ItemText>gimbal_ahrs</ItemText>
|
||||
</Ww>
|
||||
<Ww>
|
||||
<count>12</count>
|
||||
<WinNumber>1</WinNumber>
|
||||
<ItemText>ai_rx_data</ItemText>
|
||||
</Ww>
|
||||
<Ww>
|
||||
<count>13</count>
|
||||
<WinNumber>1</WinNumber>
|
||||
<ItemText>ai_rx_buf</ItemText>
|
||||
</Ww>
|
||||
<Ww>
|
||||
<count>14</count>
|
||||
<WinNumber>1</WinNumber>
|
||||
<ItemText>ai_rx_buf</ItemText>
|
||||
</Ww>
|
||||
<Ww>
|
||||
<count>15</count>
|
||||
<WinNumber>1</WinNumber>
|
||||
<ItemText>vision_data</ItemText>
|
||||
</Ww>
|
||||
<Ww>
|
||||
<count>16</count>
|
||||
<WinNumber>1</WinNumber>
|
||||
<ItemText>vision_data1</ItemText>
|
||||
</Ww>
|
||||
<Ww>
|
||||
<count>17</count>
|
||||
<WinNumber>1</WinNumber>
|
||||
<ItemText>gimbal_cmd</ItemText>
|
||||
</Ww>
|
||||
<Ww>
|
||||
<count>18</count>
|
||||
<WinNumber>1</WinNumber>
|
||||
<ItemText>last_vision_data</ItemText>
|
||||
</Ww>
|
||||
<Ww>
|
||||
<count>19</count>
|
||||
<WinNumber>1</WinNumber>
|
||||
<ItemText>last_vision_update_tick</ItemText>
|
||||
</Ww>
|
||||
<Ww>
|
||||
<count>20</count>
|
||||
<WinNumber>1</WinNumber>
|
||||
<ItemText>last_vision_update_tick</ItemText>
|
||||
</Ww>
|
||||
<Ww>
|
||||
<count>21</count>
|
||||
<WinNumber>1</WinNumber>
|
||||
<ItemText>fan</ItemText>
|
||||
</Ww>
|
||||
</WatchWindow1>
|
||||
<WatchWindow2>
|
||||
<Ww>
|
||||
<count>0</count>
|
||||
<WinNumber>2</WinNumber>
|
||||
<ItemText>vision_data1</ItemText>
|
||||
</Ww>
|
||||
<Ww>
|
||||
<count>1</count>
|
||||
<WinNumber>2</WinNumber>
|
||||
<ItemText>gimbal</ItemText>
|
||||
</Ww>
|
||||
</WatchWindow2>
|
||||
<Tracepoint>
|
||||
<THDelay>0</THDelay>
|
||||
</Tracepoint>
|
||||
|
||||
@ -315,7 +315,7 @@
|
||||
</ArmAdsMisc>
|
||||
<Cads>
|
||||
<interw>1</interw>
|
||||
<Optim>4</Optim>
|
||||
<Optim>1</Optim>
|
||||
<oTime>0</oTime>
|
||||
<SplitLS>0</SplitLS>
|
||||
<OneElfS>1</OneElfS>
|
||||
|
||||
Binary file not shown.
13844
MDK-ARM/DevC/DevC.hex
13844
MDK-ARM/DevC/DevC.hex
File diff suppressed because it is too large
Load Diff
@ -160,4 +160,33 @@ int8_t BSP_UART_Receive(BSP_UART_t uart, uint8_t *data, uint16_t size, bool dma)
|
||||
|
||||
/* USER FUNCTION BEGIN */
|
||||
|
||||
|
||||
/**
|
||||
* @brief 开启串口空闲中断 + DMA 接收
|
||||
* @note 调用 HAL 库原生的接收函数,配合自定义的 IDLE 中断处理使用
|
||||
*/
|
||||
int8_t BSP_UART_Receive_IDLE(BSP_UART_t uart, uint8_t *data, uint16_t size) {
|
||||
if (uart >= BSP_UART_NUM) return BSP_ERR;
|
||||
if (data == NULL || size == 0) return BSP_ERR_NULL;
|
||||
|
||||
/* 使用标准 DMA 接收,IDLE 标志将在你的 BSP_UART_IRQHandler 中被处理 */
|
||||
if (HAL_UART_Receive_DMA(BSP_UART_GetHandle(uart), data, size) != HAL_OK) {
|
||||
return BSP_ERR;
|
||||
}
|
||||
return BSP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取 DMA 当前实际接收到的数据长度
|
||||
* @param max_size 开启 DMA 接收时传入的最大缓冲区长度
|
||||
*/
|
||||
uint16_t BSP_UART_GetRxCount(BSP_UART_t uart, uint16_t max_size) {
|
||||
UART_HandleTypeDef *huart = BSP_UART_GetHandle(uart);
|
||||
if (huart == NULL || huart->hdmarx == NULL) return 0;
|
||||
|
||||
/* 实际接收长度 = 缓冲区总容量 - DMA通道剩余未传输数据量 */
|
||||
return max_size - __HAL_DMA_GET_COUNTER(huart->hdmarx);
|
||||
}
|
||||
|
||||
|
||||
/* USER FUNCTION END */
|
||||
@ -62,7 +62,8 @@ int8_t BSP_UART_Transmit(BSP_UART_t uart, uint8_t *data, uint16_t size, bool dma
|
||||
int8_t BSP_UART_Receive(BSP_UART_t uart, uint8_t *data, uint16_t size, bool dma);
|
||||
|
||||
/* USER FUNCTION BEGIN */
|
||||
|
||||
int8_t BSP_UART_Receive_IDLE(BSP_UART_t uart, uint8_t *data, uint16_t size);
|
||||
uint16_t BSP_UART_GetRxCount(BSP_UART_t uart, uint16_t max_size);
|
||||
/* USER FUNCTION END */
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@ -36,7 +36,7 @@ Config_RobotParam_t robot_config = {
|
||||
},
|
||||
.yaw_angle = {
|
||||
.k = 7.0f,
|
||||
.p = 3.5f,
|
||||
.p = 5.5f,
|
||||
.i = 0.0f,
|
||||
.d = 0.0f,
|
||||
.i_limit = 0.0f,
|
||||
@ -56,21 +56,21 @@ Config_RobotParam_t robot_config = {
|
||||
},
|
||||
.pit_angle = {
|
||||
.k = 2.5f,
|
||||
.p = 5.0f,
|
||||
.i = 0.2f,
|
||||
.d = 0.01f,
|
||||
.i_limit = 0.0f,
|
||||
.out_limit = 10.0f,
|
||||
.p = 1.0f,
|
||||
.i = 0.5f,
|
||||
.d = 0.0f,
|
||||
.i_limit = 0.5f,
|
||||
.out_limit = 5.2f,
|
||||
.d_cutoff_freq = -1.0f,
|
||||
.range = M_2PI,
|
||||
},
|
||||
},
|
||||
.mech_zero = {
|
||||
.yaw = 2.9f,
|
||||
.yaw = 3.0f,
|
||||
.pit = 1.39f,
|
||||
},
|
||||
.travel = {
|
||||
.yaw = 1.0f,
|
||||
.yaw = 0.8f,
|
||||
.pit = 1.2f,
|
||||
},
|
||||
.low_pass_cutoff_freq = {
|
||||
@ -104,7 +104,7 @@ Config_RobotParam_t robot_config = {
|
||||
|
||||
|
||||
.shoot_param = {
|
||||
.trig_step_angle=M_2PI/7.8,
|
||||
.trig_step_angle=M_2PI/8,
|
||||
.shot_delay_time=0.08f,
|
||||
.shot_burst_num=1,
|
||||
.fric_motor_param[0] = {
|
||||
@ -134,7 +134,7 @@ Config_RobotParam_t robot_config = {
|
||||
.i=0.0f,
|
||||
.d=0.0f,
|
||||
.i_limit=0.0f,
|
||||
.out_limit=0.9f,
|
||||
.out_limit=0.3f,
|
||||
.d_cutoff_freq=30.0f,
|
||||
.range=-1.0f,
|
||||
},
|
||||
|
||||
@ -18,8 +18,11 @@
|
||||
/* Private variables -------------------------------------------------------- */
|
||||
/* Private function -------------------------------------------------------- */
|
||||
/* Private define ----------------------------------------------------------- */
|
||||
#define YAW_MAX_OUTPUT 0.3f /* Yaw轴(大疆电机)最大输出比例限制,范围:0.0 到 1.0 */
|
||||
#define PIT_MAX_TORQUE 1.0f /* Pitch轴(达妙电机)最大扭矩限制,单位:N·m (请根据实际情况调整) */
|
||||
#define YAW_MAX_OUTPUT 0.8f /* Yaw轴(大疆电机)最大输出比例限制,范围:0.0 到 1.0 */
|
||||
#define PIT_MAX_TORQUE 2.0f /* Pitch轴(达妙电机)最大扭矩限制,单位:N·m (请根据实际情况调整) */
|
||||
static LowPassFilter2p_t vision_yaw_lpf;
|
||||
static LowPassFilter2p_t vision_pit_lpf;
|
||||
static uint8_t is_vision_lpf_init = 0;
|
||||
/**
|
||||
* \brief 设置云台模式
|
||||
*
|
||||
@ -136,12 +139,12 @@ int8_t Gimbal_UpdateIMU(Gimbal_t *gimbal, const Gimbal_IMU_t *imu){
|
||||
gimbal->feedback.imu.eulr = imu->eulr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fan = 0;
|
||||
/**
|
||||
* \brief 运行云台控制逻辑
|
||||
* \brief 运行云台控制逻辑(集成 AI 速度前馈)
|
||||
*
|
||||
* \param g 包含云台数据的结构体
|
||||
* \param g_cmd 云台控制指令
|
||||
* \param g_cmd 云台控制指令(包含遥控器增量及视觉数据)
|
||||
*
|
||||
* \return 函数运行结果
|
||||
*/
|
||||
@ -150,98 +153,96 @@ int8_t Gimbal_Control(Gimbal_t *g, Gimbal_CMD_t *g_cmd) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 更新时间步长 */
|
||||
g->dt = (BSP_TIME_Get_us() - g->lask_wakeup) / 1000000.0f;
|
||||
g->lask_wakeup = BSP_TIME_Get_us();
|
||||
|
||||
/* 模式切换处理 */
|
||||
Gimbal_SetMode(g, g_cmd->mode);
|
||||
|
||||
/* --- 计算目标增量:兼容手动模式与火控托管模式 --- */
|
||||
/* --- 1. 计算目标设定值增量 --- */
|
||||
float delta_yaw = 0.0f;
|
||||
float delta_pit = 0.0f;
|
||||
|
||||
if (g->mode == GIMBAL_MODE_AUTO_AIM && g_cmd->vision.target_found) {
|
||||
/* 火控模式且目标已锁定:计算视觉目标角度与当前设定值的偏差 */
|
||||
delta_yaw = CircleError(g_cmd->vision.yaw, g->setpoint.eulr.yaw, M_2PI);
|
||||
delta_pit = CircleError(g_cmd->vision.pit, g->setpoint.eulr.pit, M_2PI);
|
||||
delta_yaw = CircleError((g_cmd->vision.yaw), g->setpoint.eulr.yaw, M_2PI);
|
||||
if (fan) delta_pit = CircleError((g_cmd->vision.pit), g->setpoint.eulr.pit, M_2PI);
|
||||
else delta_pit = CircleError((g_cmd->vision.pit), g->setpoint.eulr.pit, M_2PI);
|
||||
|
||||
|
||||
// delta_pit = CircleError(g_cmd->vision.pit, g->setpoint.eulr.pit, M_2PI);
|
||||
} else {
|
||||
/* 常规模式或火控模式下目标丢失:使用操作手遥控器增量 */
|
||||
delta_yaw = g_cmd->delta_yaw * g->dt * 1.5f;
|
||||
delta_pit = g_cmd->delta_pit * g->dt;
|
||||
}
|
||||
|
||||
/* --- 处理yaw控制命令,软件限位 - 使用电机绝对角度 --- */
|
||||
/* --- 2. 软件限位处理 (Yaw) --- */
|
||||
if (g->param->travel.yaw > 0) {
|
||||
/* 计算当前电机角度与IMU角度的偏差 */
|
||||
float motor_imu_offset = g->feedback.motor.yaw.rotor_abs_angle - g->feedback.imu.eulr.yaw;
|
||||
/* 处理跨越±π的情况 */
|
||||
if (motor_imu_offset > M_PI) motor_imu_offset -= M_2PI;
|
||||
if (motor_imu_offset < -M_PI) motor_imu_offset += M_2PI;
|
||||
|
||||
/* 计算到限位边界的距离 */
|
||||
const float delta_max = CircleError(g->limit.yaw.max,
|
||||
(g->setpoint.eulr.yaw + motor_imu_offset + delta_yaw), M_2PI);
|
||||
const float delta_min = CircleError(g->limit.yaw.min,
|
||||
(g->setpoint.eulr.yaw + motor_imu_offset + delta_yaw), M_2PI);
|
||||
const float delta_max = CircleError(g->limit.yaw.max, (g->setpoint.eulr.yaw + motor_imu_offset + delta_yaw), M_2PI);
|
||||
const float delta_min = CircleError(g->limit.yaw.min, (g->setpoint.eulr.yaw + motor_imu_offset + delta_yaw), M_2PI);
|
||||
|
||||
/* 限制控制命令 */
|
||||
if (delta_yaw > delta_max) delta_yaw = delta_max;
|
||||
if (delta_yaw < delta_min) delta_yaw = delta_min;
|
||||
} else {
|
||||
/* 无限位时直接使用电机绝对角度进行限位 */
|
||||
float current_motor_angle = g->feedback.motor.yaw.rotor_abs_angle;
|
||||
float target_angle = g->setpoint.eulr.yaw + delta_yaw;
|
||||
|
||||
/* 归一化角度到[-π, π] */
|
||||
while (target_angle > M_PI) target_angle -= M_2PI;
|
||||
while (target_angle < -M_PI) target_angle += M_2PI;
|
||||
while (current_motor_angle > M_PI) current_motor_angle -= M_2PI;
|
||||
while (current_motor_angle < -M_PI) current_motor_angle += M_2PI;
|
||||
}
|
||||
CircleAdd(&(g->setpoint.eulr.yaw), delta_yaw, M_2PI);
|
||||
|
||||
/* --- 处理pitch控制命令,软件限位 - 使用电机绝对角度 --- */
|
||||
/* --- 3. 软件限位处理 (Pitch) --- */
|
||||
if (g->param->travel.pit > 0) {
|
||||
/* 计算当前电机角度与IMU角度的偏差 */
|
||||
float motor_imu_offset = g->feedback.motor.pit.rotor_abs_angle - g->feedback.imu.eulr.rol;
|
||||
/* 处理跨越±π的情况 */
|
||||
if (motor_imu_offset > M_PI) motor_imu_offset -= M_2PI;
|
||||
if (motor_imu_offset < -M_PI) motor_imu_offset += M_2PI;
|
||||
|
||||
/* 计算到限位边界的距离 */
|
||||
const float delta_max = CircleError(g->limit.pit.max,
|
||||
(g->setpoint.eulr.pit + motor_imu_offset + delta_pit), M_2PI);
|
||||
const float delta_min = CircleError(g->limit.pit.min,
|
||||
(g->setpoint.eulr.pit + motor_imu_offset + delta_pit), M_2PI);
|
||||
const float delta_max = CircleError(g->limit.pit.max, (g->setpoint.eulr.pit + motor_imu_offset + delta_pit), M_2PI);
|
||||
const float delta_min = CircleError(g->limit.pit.min, (g->setpoint.eulr.pit + motor_imu_offset + delta_pit), M_2PI);
|
||||
|
||||
/* 限制控制命令 */
|
||||
if (delta_pit > delta_max) delta_pit = delta_max;
|
||||
if (delta_pit < delta_min) delta_pit = delta_min;
|
||||
}
|
||||
CircleAdd(&(g->setpoint.eulr.pit), delta_pit, M_2PI);
|
||||
|
||||
/* --- 控制相关逻辑(PID闭环计算) --- */
|
||||
/* --- 4. 闭环控制计算 (串级 PID + 前馈) --- */
|
||||
float yaw_omega_set_point, pit_omega_set_point;
|
||||
float yaw_v_ff = 0.0f, pit_v_ff = 0.0f;
|
||||
|
||||
switch (g->mode) {
|
||||
case GIMBAL_MODE_RELAX:
|
||||
g->out.yaw = 0.0f;
|
||||
g->out.pit = 0.0f;
|
||||
break;
|
||||
|
||||
case GIMBAL_MODE_AUTO_AIM: /* 火控模式与绝对模式共用相同的空间系PID闭环 */
|
||||
case GIMBAL_MODE_AUTO_AIM:
|
||||
/* 如果视觉在线,提取前馈项 */
|
||||
if (g_cmd->vision.target_found) {
|
||||
g->setpoint.eulr.pit=g_cmd->vision.pit;
|
||||
/* 前馈系数建议放在参数结构体中,此处使用 1.0f 作为默认增益 */
|
||||
yaw_v_ff = g_cmd->vision.yaw_v_ff * 0.2f;
|
||||
pit_v_ff = g_cmd->vision.pit_v_ff * 0.2f;
|
||||
}
|
||||
/* 顺延执行 PID 计算 */
|
||||
case GIMBAL_MODE_ABSOLUTE:
|
||||
/* Yaw 轴:角度环 -> 速度环 + 前馈 */
|
||||
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);
|
||||
g->feedback.imu.gyro.z, 0.f, g->dt) + yaw_v_ff;
|
||||
|
||||
/* Pitch 轴:角度环 -> 速度环 + 前馈 */
|
||||
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);
|
||||
g->feedback.imu.gyro.y, 0.f, g->dt) + pit_v_ff;
|
||||
if (g->mode == GIMBAL_MODE_AUTO_AIM && g_cmd->vision.target_found) {
|
||||
g->out.pit=-g->out.pit;
|
||||
}
|
||||
break;
|
||||
|
||||
case GIMBAL_MODE_RELATIVE:
|
||||
/* 相对模式暂未实现 */
|
||||
g->out.yaw = 0.0f;
|
||||
g->out.pit = 0.0f;
|
||||
break;
|
||||
@ -253,7 +254,134 @@ int8_t Gimbal_Control(Gimbal_t *g, Gimbal_CMD_t *g_cmd) {
|
||||
|
||||
return 0;
|
||||
}
|
||||
/**
|
||||
* \brief 运行云台控制逻辑(集成 AI 速度前馈与目标滤波)
|
||||
*
|
||||
* \param g 包含云台数据的结构体
|
||||
* \param g_cmd 云台控制指令(包含遥控器增量及视觉数据)
|
||||
*
|
||||
* \return 函数运行结果
|
||||
*/
|
||||
int8_t Gimbal_Control1(Gimbal_t *g, Gimbal_CMD_t *g_cmd) {
|
||||
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();
|
||||
|
||||
/* 模式切换处理 */
|
||||
Gimbal_SetMode(g, g_cmd->mode);
|
||||
|
||||
/* --- 0. 视觉滤波器初始化与复位逻辑 --- */
|
||||
if (!is_vision_lpf_init) {
|
||||
/* 假设控制频率约为1000Hz(取决于实际dt),截止频率设为15Hz。截止频率越低越平滑,但延迟越大 */
|
||||
LowPassFilter2p_Init(&vision_yaw_lpf, 1.0f / g->dt, 15.0f);
|
||||
LowPassFilter2p_Init(&vision_pit_lpf, 1.0f / g->dt, 15.0f);
|
||||
is_vision_lpf_init = 1;
|
||||
}
|
||||
|
||||
/* --- 1. 计算目标设定值增量 --- */
|
||||
float delta_yaw = 0.0f;
|
||||
float delta_pit = 0.0f;
|
||||
|
||||
if (g->mode == GIMBAL_MODE_AUTO_AIM && g_cmd->vision.target_found) {
|
||||
/* 对视觉传来的绝对角度进行二阶低通滤波 */
|
||||
float filtered_vision_yaw = LowPassFilter2p_Apply(&vision_yaw_lpf, g_cmd->vision.yaw);
|
||||
float filtered_vision_pit = LowPassFilter2p_Apply(&vision_pit_lpf, g_cmd->vision.pit);
|
||||
|
||||
/* Yaw轴:圆周差值计算 */
|
||||
delta_yaw = CircleError(filtered_vision_yaw, g->setpoint.eulr.yaw, M_2PI);
|
||||
|
||||
/* Pitch轴:纯线性差值计算。根据前置测试,如果极性反了,在此处添加负号 */
|
||||
delta_pit = filtered_vision_pit - g->setpoint.eulr.pit; // 如果反向,改为 (-filtered_vision_pit) - g->setpoint.eulr.pit;
|
||||
|
||||
} else {
|
||||
/* 丢失目标或非自瞄模式:复位滤波器,防止下次切入自瞄时产生剧烈阶跃 */
|
||||
LowPassFilter2p_Reset(&vision_yaw_lpf, g->setpoint.eulr.yaw);
|
||||
LowPassFilter2p_Reset(&vision_pit_lpf, g->setpoint.eulr.pit);
|
||||
|
||||
/* 使用操作手遥控器增量 */
|
||||
delta_yaw = g_cmd->delta_yaw * g->dt * 1.5f;
|
||||
delta_pit = g_cmd->delta_pit * g->dt;
|
||||
}
|
||||
|
||||
/* --- 2. 软件限位处理 (Yaw) --- */
|
||||
if (g->param->travel.yaw > 0) {
|
||||
float motor_imu_offset = g->feedback.motor.yaw.rotor_abs_angle - g->feedback.imu.eulr.yaw;
|
||||
if (motor_imu_offset > M_PI) motor_imu_offset -= M_2PI;
|
||||
if (motor_imu_offset < -M_PI) motor_imu_offset += M_2PI;
|
||||
|
||||
const float delta_max = CircleError(g->limit.yaw.max, (g->setpoint.eulr.yaw + motor_imu_offset + delta_yaw), M_2PI);
|
||||
const float delta_min = CircleError(g->limit.yaw.min, (g->setpoint.eulr.yaw + motor_imu_offset + delta_yaw), M_2PI);
|
||||
|
||||
if (delta_yaw > delta_max) delta_yaw = delta_max;
|
||||
if (delta_yaw < delta_min) delta_yaw = delta_min;
|
||||
}
|
||||
CircleAdd(&(g->setpoint.eulr.yaw), delta_yaw, M_2PI);
|
||||
|
||||
/* --- 3. 软件限位处理 (Pitch) --- */
|
||||
/* Pitch 轴必须使用纯线性累加和限幅,严禁使用 CircleAdd */
|
||||
g->setpoint.eulr.pit += delta_pit;
|
||||
|
||||
if (g->param->travel.pit > 0) {
|
||||
float motor_imu_offset = g->feedback.motor.pit.rotor_abs_angle - g->feedback.imu.eulr.rol;
|
||||
if (motor_imu_offset > M_PI) motor_imu_offset -= M_2PI;
|
||||
if (motor_imu_offset < -M_PI) motor_imu_offset += M_2PI;
|
||||
|
||||
float target_abs_pit = g->setpoint.eulr.pit + motor_imu_offset;
|
||||
|
||||
if (target_abs_pit > g->limit.pit.max) {
|
||||
g->setpoint.eulr.pit = g->limit.pit.max - motor_imu_offset;
|
||||
} else if (target_abs_pit < g->limit.pit.min) {
|
||||
g->setpoint.eulr.pit = g->limit.pit.min - motor_imu_offset;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- 4. 闭环控制计算 (串级 PID + 前馈) --- */
|
||||
float yaw_omega_set_point, pit_omega_set_point;
|
||||
float yaw_v_ff = 0.0f, pit_v_ff = 0.0f;
|
||||
|
||||
switch (g->mode) {
|
||||
case GIMBAL_MODE_RELAX:
|
||||
g->out.yaw = 0.0f;
|
||||
g->out.pit = 0.0f;
|
||||
break;
|
||||
|
||||
case GIMBAL_MODE_AUTO_AIM:
|
||||
if (g_cmd->vision.target_found) {
|
||||
/* 注意:已删除原有的强行赋值 g->setpoint.eulr.pit=g_cmd->vision.pit; */
|
||||
yaw_v_ff = g_cmd->vision.yaw_v_ff * 0.2f;
|
||||
pit_v_ff = g_cmd->vision.pit_v_ff * 0.2f;
|
||||
}
|
||||
/* 顺延执行 PID 计算 */
|
||||
case GIMBAL_MODE_ABSOLUTE:
|
||||
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) + yaw_v_ff;
|
||||
|
||||
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) + pit_v_ff;
|
||||
g->out.pit=-g->out.pit; g->out.pit=-g->out.pit;
|
||||
/* 注意:已删除此处的 g->out.pit = -g->out.pit; 修正输出极性应在外部 Gimbal_Output 或 PID 计算环节统一处理 */
|
||||
break;
|
||||
|
||||
case GIMBAL_MODE_RELATIVE:
|
||||
g->out.yaw = 0.0f;
|
||||
g->out.pit = 0.0f;
|
||||
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 云台输出
|
||||
*
|
||||
|
||||
@ -28,15 +28,6 @@ extern "C" {
|
||||
/* Exported types ----------------------------------------------------------- */
|
||||
|
||||
|
||||
/* 在 Gimbal_Vision_t 结构体中增加前馈成员 */
|
||||
typedef struct {
|
||||
uint8_t target_found;
|
||||
float yaw;
|
||||
float pit;
|
||||
float yaw_v_ff; // Yaw 速度前馈
|
||||
float pit_v_ff; // Pitch 速度前馈
|
||||
// 如有需要可增加加速度前馈 a_ff
|
||||
} Gimbal_Vision_t;
|
||||
|
||||
typedef enum {
|
||||
GIMBAL_MODE_RELAX, /* 放松模式,电机不输出。一般情况云台初始化之后的模式 */
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
#include "shoot.h"
|
||||
#include <string.h>
|
||||
#include "component/filter.h"
|
||||
@ -7,10 +6,14 @@
|
||||
#include "bsp/time.h"
|
||||
#include "device/motor_rm.h"
|
||||
#include "module/config.h"
|
||||
|
||||
static bool last_firecmd;
|
||||
|
||||
/**
|
||||
* @brief 确保两个输出权重的和不超过 1.0f,用于限幅
|
||||
*/
|
||||
static inline void ScaleSumTo1(float *a, float *b) {
|
||||
float sum = *a + *b;
|
||||
float sum = fabsf(*a) + fabsf(*b);
|
||||
if (sum > 1.0f) {
|
||||
float scale = 1.0f / sum;
|
||||
*a *= scale;
|
||||
@ -18,23 +21,22 @@ static inline void ScaleSumTo1(float *a, float *b) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int8_t Shoot_SetMode(Shoot_t *s, Shoot_Mode_t mode)
|
||||
{
|
||||
if (s == NULL) {
|
||||
return -1; // 参数错误
|
||||
return -1;
|
||||
}
|
||||
s->mode=mode;
|
||||
s->mode = mode;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t Shoot_ResetIntegral(Shoot_t *s)
|
||||
{
|
||||
if (s == NULL) {
|
||||
return -1; // 参数错误
|
||||
return -1;
|
||||
}
|
||||
for(int i=0;i<SHOOT_FRIC_NUM;i++)
|
||||
{
|
||||
for(int i = 0; i < SHOOT_FRIC_NUM; i++)
|
||||
{
|
||||
PID_ResetIntegral(&s->pid.fric_follow[i]);
|
||||
PID_ResetIntegral(&s->pid.fric_err[i]);
|
||||
}
|
||||
@ -46,10 +48,10 @@ int8_t Shoot_ResetIntegral(Shoot_t *s)
|
||||
int8_t Shoot_ResetCalu(Shoot_t *s)
|
||||
{
|
||||
if (s == NULL) {
|
||||
return -1; // 参数错误
|
||||
return -1;
|
||||
}
|
||||
for(int i=0;i<SHOOT_FRIC_NUM;i++)
|
||||
{
|
||||
for(int i = 0; i < SHOOT_FRIC_NUM; i++)
|
||||
{
|
||||
PID_Reset(&s->pid.fric_follow[i]);
|
||||
PID_Reset(&s->pid.fric_err[i]);
|
||||
LowPassFilter2p_Reset(&s->filter.fric.in[i], 0.0f);
|
||||
@ -65,47 +67,44 @@ int8_t Shoot_ResetCalu(Shoot_t *s)
|
||||
int8_t Shoot_ResetOutput(Shoot_t *s)
|
||||
{
|
||||
if (s == NULL) {
|
||||
return -1; // 参数错误
|
||||
return -1;
|
||||
}
|
||||
for(int i=0;i<SHOOT_FRIC_NUM;i++)
|
||||
{
|
||||
s->output.out_follow[i]=0.0f;
|
||||
s->output.out_err[i]=0.0f;
|
||||
s->output.out_fric[i]=0.0f;
|
||||
s->output.lpfout_fric[i]=0.0f;
|
||||
for(int i = 0; i < SHOOT_FRIC_NUM; i++)
|
||||
{
|
||||
s->output.out_follow[i] = 0.0f;
|
||||
s->output.out_err[i] = 0.0f;
|
||||
s->output.out_fric[i] = 0.0f;
|
||||
s->output.lpfout_fric[i] = 0.0f;
|
||||
}
|
||||
s->output.outagl_trig=0.0f;
|
||||
s->output.outomg_trig=0.0f;
|
||||
s->output.outlpf_trig=0.0f;
|
||||
s->output.outagl_trig = 0.0f;
|
||||
s->output.outomg_trig = 0.0f;
|
||||
s->output.outlpf_trig = 0.0f;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t Shoot_CaluTargetRPM(Shoot_t *s, float target_speed)
|
||||
{
|
||||
if (s == NULL) {
|
||||
return -1; // 参数错误
|
||||
return -1;
|
||||
}
|
||||
s->target_variable.target_rpm=4000.0f/MAX_FRIC_RPM;
|
||||
// 固定的归一化目标值
|
||||
s->target_variable.target_rpm = 4000.0f;
|
||||
return 0;
|
||||
}
|
||||
/**
|
||||
* \brief 根据发射弹丸数量及发射频率计算拨弹电机目标角度
|
||||
*
|
||||
* \param s 包含发射数据的结构体
|
||||
* \param num 需要发射的弹丸数量
|
||||
*/
|
||||
|
||||
int8_t Shoot_CaluTargetAngle(Shoot_t *s, Shoot_CMD_t *cmd)
|
||||
{
|
||||
if (s == NULL || s->shoot_Anglecalu.num_to_shoot == 0) {
|
||||
if (s == NULL || s->shoot_Anglecalu.num_to_shoot == 0) {
|
||||
return -1;
|
||||
}
|
||||
if(s->now - s->shoot_Anglecalu.time_last_shoot >= s->param->shot_delay_time && cmd->firecmd)
|
||||
{
|
||||
s->shoot_Anglecalu.time_last_shoot=s->now;
|
||||
s->shoot_Anglecalu.time_last_shoot = s->now;
|
||||
s->target_variable.target_angle += s->param->trig_step_angle;
|
||||
if(s->target_variable.target_angle>M_PI)s->target_variable.target_angle-=M_2PI;
|
||||
else if((s->target_variable.target_angle<-M_PI))s->target_variable.target_angle+=M_2PI;
|
||||
s->shoot_Anglecalu.num_to_shoot--;
|
||||
// 角度限制在 [-PI, PI]
|
||||
if(s->target_variable.target_angle > M_PI) s->target_variable.target_angle -= M_2PI;
|
||||
else if(s->target_variable.target_angle < -M_PI) s->target_variable.target_angle += M_2PI;
|
||||
s->shoot_Anglecalu.num_to_shoot--;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -113,199 +112,188 @@ int8_t Shoot_CaluTargetAngle(Shoot_t *s, Shoot_CMD_t *cmd)
|
||||
int8_t Shoot_Init(Shoot_t *s, Shoot_Params_t *param, float target_freq)
|
||||
{
|
||||
if (s == NULL || param == NULL || target_freq <= 0.0f) {
|
||||
return -1; // 参数错误
|
||||
return -1;
|
||||
}
|
||||
s->param=param;
|
||||
s->param = param;
|
||||
|
||||
BSP_CAN_Init();
|
||||
for(int i=0;i<SHOOT_FRIC_NUM;i++){
|
||||
MOTOR_RM_Register(¶m->fric_motor_param[i]);
|
||||
PID_Init(&s->pid.fric_follow[i], KPID_MODE_CALC_D, target_freq,¶m->fric_follow);
|
||||
PID_Init(&s->pid.fric_err[i], KPID_MODE_CALC_D, target_freq,¶m->fric_err);
|
||||
LowPassFilter2p_Init(&s->filter.fric.in[i], target_freq, s->param->filter.fric.in);
|
||||
LowPassFilter2p_Init(&s->filter.fric.out[i], target_freq, s->param->filter.fric.out);
|
||||
for(int i = 0; i < SHOOT_FRIC_NUM; i++){
|
||||
MOTOR_RM_Register(¶m->fric_motor_param[i]);
|
||||
PID_Init(&s->pid.fric_follow[i], KPID_MODE_CALC_D, target_freq, ¶m->fric_follow);
|
||||
PID_Init(&s->pid.fric_err[i], KPID_MODE_CALC_D, target_freq, ¶m->fric_err);
|
||||
LowPassFilter2p_Init(&s->filter.fric.in[i], target_freq, s->param->filter.fric.in);
|
||||
LowPassFilter2p_Init(&s->filter.fric.out[i], target_freq, s->param->filter.fric.out);
|
||||
}
|
||||
MOTOR_RM_Register(¶m->trig_motor_param);
|
||||
PID_Init(&s->pid.trig, KPID_MODE_CALC_D, target_freq,¶m->trig);
|
||||
PID_Init(&s->pid.trig_omg, KPID_MODE_CALC_D, target_freq,¶m->trig_omg);
|
||||
MOTOR_RM_Register(¶m->trig_motor_param);
|
||||
PID_Init(&s->pid.trig, KPID_MODE_CALC_D, target_freq, ¶m->trig);
|
||||
PID_Init(&s->pid.trig_omg, KPID_MODE_CALC_D, target_freq, ¶m->trig_omg);
|
||||
|
||||
LowPassFilter2p_Init(&s->filter.trig.in, target_freq, s->param->filter.trig.in);
|
||||
LowPassFilter2p_Init(&s->filter.trig.out, target_freq, s->param->filter.trig.out);
|
||||
|
||||
memset(&s->shoot_Anglecalu,0,sizeof(s->shoot_Anglecalu));
|
||||
memset(&s->output,0,sizeof(s->output));
|
||||
s->target_variable.target_rpm=6000.0f; /* 归一化目标转速 */
|
||||
|
||||
memset(&s->shoot_Anglecalu, 0, sizeof(s->shoot_Anglecalu));
|
||||
memset(&s->output, 0, sizeof(s->output));
|
||||
s->target_variable.target_rpm = 6000.0f;
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t Shoot_UpdateFeedback(Shoot_t *s)
|
||||
{
|
||||
if (s == NULL) {
|
||||
return -1; // 参数错误
|
||||
return -1;
|
||||
}
|
||||
float rpm_sum=0.0f;
|
||||
float rpm_sum = 0.0f;
|
||||
for(int i = 0; i < SHOOT_FRIC_NUM; i++) {
|
||||
/* 更新摩擦电机反馈 */
|
||||
MOTOR_RM_Update(&s->param->fric_motor_param[i]);
|
||||
MOTOR_RM_t *motor_fed = MOTOR_RM_GetMotor(&s->param->fric_motor_param[i]);
|
||||
if(motor_fed!=NULL)
|
||||
{
|
||||
s->feedback.fric[i]=motor_fed->motor.feedback;
|
||||
}
|
||||
/* 滤波反馈rpm */
|
||||
MOTOR_RM_t *motor_fed = MOTOR_RM_GetMotor(&s->param->fric_motor_param[i]);
|
||||
if(motor_fed != NULL) {
|
||||
s->feedback.fric[i] = motor_fed->motor.feedback;
|
||||
}
|
||||
s->feedback.fil_fric_rpm[i] = LowPassFilter2p_Apply(&s->filter.fric.in[i], s->feedback.fric[i].rotor_speed);
|
||||
/* 归一化rpm */
|
||||
s->feedback.fric_rpm[i] = s->feedback.fil_fric_rpm[i] / MAX_FRIC_RPM;
|
||||
if(s->feedback.fric_rpm[i]>1.0f)s->feedback.fric_rpm[i]=1.0f;
|
||||
if(s->feedback.fric_rpm[i]<-1.0f)s->feedback.fric_rpm[i]=-1.0f;
|
||||
/* 计算平均rpm */
|
||||
rpm_sum+=s->feedback.fric_rpm[i];
|
||||
|
||||
if(s->feedback.fric_rpm[i] > 1.0f) s->feedback.fric_rpm[i] = 1.0f;
|
||||
if(s->feedback.fric_rpm[i] < -1.0f) s->feedback.fric_rpm[i] = -1.0f;
|
||||
rpm_sum += s->feedback.fric_rpm[i];
|
||||
}
|
||||
s->feedback.fric_avgrpm=rpm_sum/SHOOT_FRIC_NUM;
|
||||
/* 更新拨弹电机反馈 */
|
||||
s->feedback.fric_avgrpm = rpm_sum / SHOOT_FRIC_NUM;
|
||||
|
||||
MOTOR_RM_Update(&s->param->trig_motor_param);
|
||||
MOTOR_RM_t *motor_fed = MOTOR_RM_GetMotor(&s->param->trig_motor_param);
|
||||
if(motor_fed!=NULL)
|
||||
{
|
||||
s->feedback.trig=motor_fed->feedback;
|
||||
}
|
||||
/* 将多圈角度归化到单圈 (-M_PI, M_PI) */
|
||||
s->feedback.trig_angle_cicle = s->feedback.trig.rotor_abs_angle;
|
||||
s->feedback.trig_angle_cicle = fmod(s->feedback.trig_angle_cicle, M_2PI); // 将角度限制在 [-2π, 2π]
|
||||
if (s->feedback.trig_angle_cicle > M_PI) {
|
||||
s->feedback.trig_angle_cicle -= M_2PI; // 调整到 [-π, π]
|
||||
}else if (s->feedback.trig_angle_cicle < -M_PI) {
|
||||
s->feedback.trig_angle_cicle += M_2PI; // 调整到 [-π, π]
|
||||
}
|
||||
MOTOR_RM_t *motor_fed_trig = MOTOR_RM_GetMotor(&s->param->trig_motor_param);
|
||||
if(motor_fed_trig != NULL) {
|
||||
s->feedback.trig = motor_fed_trig->feedback;
|
||||
}
|
||||
|
||||
s->feedback.trig_angle_cicle = s->feedback.trig.rotor_abs_angle;
|
||||
s->feedback.trig_angle_cicle = fmod(s->feedback.trig_angle_cicle, M_2PI);
|
||||
if (s->feedback.trig_angle_cicle > M_PI) {
|
||||
s->feedback.trig_angle_cicle -= M_2PI;
|
||||
} else if (s->feedback.trig_angle_cicle < -M_PI) {
|
||||
s->feedback.trig_angle_cicle += M_2PI;
|
||||
}
|
||||
|
||||
s->feedback.fil_trig_rpm = LowPassFilter2p_Apply(&s->filter.trig.in, s->feedback.trig.rotor_speed);
|
||||
s->feedback.trig_rpm = s->feedback.trig.rotor_speed / MAX_TRIG_RPM;
|
||||
if(s->feedback.trig_rpm>1.0f)s->feedback.trig_rpm=1.0f; //如果单环效果好就删
|
||||
if(s->feedback.trig_rpm<-1.0f)s->feedback.trig_rpm=-1.0f;
|
||||
if(s->feedback.trig_rpm > 1.0f) s->feedback.trig_rpm = 1.0f;
|
||||
if(s->feedback.trig_rpm < -1.0f) s->feedback.trig_rpm = -1.0f;
|
||||
|
||||
s->errtosee = s->feedback.fric[0].rotor_speed - s->feedback.fric[1].rotor_speed;
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t Shoot_Control(Shoot_t *s, Shoot_CMD_t *cmd)
|
||||
{
|
||||
if (s == NULL || cmd == NULL) {
|
||||
return -1; // 参数错误
|
||||
return -1;
|
||||
}
|
||||
s->now = BSP_TIME_Get_us() / 1000000.0f;
|
||||
s->dt = (BSP_TIME_Get_us() - s->lask_wakeup) / 1000000.0f;
|
||||
s->dt = (float)(BSP_TIME_Get_us() - s->lask_wakeup) / 1000000.0f;
|
||||
s->lask_wakeup = BSP_TIME_Get_us();
|
||||
s->online = cmd->online;
|
||||
if(!s->online /*|| s->mode==SHOOT_MODE_SAFE*/){
|
||||
for(int i=0;i<SHOOT_FRIC_NUM;i++)
|
||||
{
|
||||
s->online = cmd->online;
|
||||
|
||||
if(!s->online){
|
||||
for(int i = 0; i < SHOOT_FRIC_NUM; i++) {
|
||||
MOTOR_RM_Relax(&s->param->fric_motor_param[i]);
|
||||
}
|
||||
MOTOR_RM_Relax(&s->param->trig_motor_param);
|
||||
}
|
||||
else{
|
||||
else {
|
||||
switch(s->running_state)
|
||||
{
|
||||
case SHOOT_STATE_IDLE:/*熄火等待*/
|
||||
for(int i=0;i<SHOOT_FRIC_NUM;i++)
|
||||
{
|
||||
case SHOOT_STATE_IDLE:/* 停止状态:下发 0 电流,避免反电动势制动 */
|
||||
for(int i = 0; i < SHOOT_FRIC_NUM; i++)
|
||||
{
|
||||
// 直接将输出清零,不走 PID 计算
|
||||
s->output.out_follow[i] = 0.0f;
|
||||
s->output.out_err[i] = 0.0f;
|
||||
s->output.out_fric[i] = 0.0f;
|
||||
s->output.lpfout_fric[i] = 0.0f;
|
||||
|
||||
// 重置 PID 积分项,防止下一次启动时瞬间过流
|
||||
PID_ResetIntegral(&s->pid.fric_follow[i]);
|
||||
PID_ResetIntegral(&s->pid.fric_err[i]);
|
||||
|
||||
s->output.out_follow[i]=PID_Calc(&s->pid.fric_follow[i],0.0f,s->feedback.fric_rpm[i],0,s->dt);
|
||||
s->output.out_fric[i]=s->output.out_follow[i];
|
||||
s->output.lpfout_fric[i] = LowPassFilter2p_Apply(&s->filter.fric.out[i], s->output.out_fric[i]);
|
||||
MOTOR_RM_SetOutput(&s->param->fric_motor_param[i], s->output.lpfout_fric[i]);
|
||||
MOTOR_RM_SetOutput(&s->param->fric_motor_param[i], 0.0f);
|
||||
}
|
||||
s->output.outagl_trig =PID_Calc(&s->pid.trig,s->target_variable.target_angle,s->feedback.trig_angle_cicle,0,s->dt);
|
||||
s->output.outomg_trig =PID_Calc(&s->pid.trig_omg,s->output.outagl_trig,s->feedback.trig_rpm,0,s->dt);
|
||||
s->output.outlpf_trig =LowPassFilter2p_Apply(&s->filter.trig.out, s->output.outomg_trig);
|
||||
|
||||
// 拨弹电机保持现有位置控制
|
||||
s->output.outagl_trig = PID_Calc(&s->pid.trig, s->target_variable.target_angle, s->feedback.trig_angle_cicle, 0, s->dt);
|
||||
s->output.outomg_trig = PID_Calc(&s->pid.trig_omg, s->output.outagl_trig, s->feedback.trig_rpm, 0, s->dt);
|
||||
s->output.outlpf_trig = LowPassFilter2p_Apply(&s->filter.trig.out, s->output.outomg_trig);
|
||||
MOTOR_RM_SetOutput(&s->param->trig_motor_param, s->output.outlpf_trig);
|
||||
|
||||
if(cmd->ready)
|
||||
{
|
||||
Shoot_ResetCalu(s);
|
||||
Shoot_ResetIntegral(s);
|
||||
Shoot_ResetOutput(s);
|
||||
s->running_state=SHOOT_STATE_READY;
|
||||
}
|
||||
{
|
||||
Shoot_ResetCalu(s);
|
||||
Shoot_ResetIntegral(s);
|
||||
Shoot_ResetOutput(s);
|
||||
s->running_state = SHOOT_STATE_READY;
|
||||
}
|
||||
break;
|
||||
case SHOOT_STATE_READY:/*准备射击*/
|
||||
|
||||
for(int i=0;i<SHOOT_FRIC_NUM;i++)
|
||||
{ /* 计算跟随输出、计算修正输出 */
|
||||
s->output.out_follow[i]=PID_Calc(&s->pid.fric_follow[i],s->target_variable.target_rpm/MAX_FRIC_RPM,s->feedback.fric_rpm[i],0,s->dt);
|
||||
s->output.out_err[i]=PID_Calc(&s->pid.fric_err[i],s->feedback.fric_avgrpm,s->feedback.fric_rpm[i],0,s->dt);
|
||||
/* 按比例缩放并加和输出 */
|
||||
|
||||
case SHOOT_STATE_READY:/* 准备射击:摩擦轮闭环加速 */
|
||||
for(int i = 0; i < SHOOT_FRIC_NUM; i++)
|
||||
{
|
||||
s->output.out_follow[i] = PID_Calc(&s->pid.fric_follow[i], s->target_variable.target_rpm / MAX_FRIC_RPM, s->feedback.fric_rpm[i], 0, s->dt);
|
||||
s->output.out_err[i] = PID_Calc(&s->pid.fric_err[i], s->feedback.fric_avgrpm, s->feedback.fric_rpm[i], 0, s->dt);
|
||||
|
||||
ScaleSumTo1(&s->output.out_follow[i], &s->output.out_err[i]);
|
||||
s->output.out_fric[i]=s->output.out_follow[i]+s->output.out_err[i];
|
||||
/* 滤波 */
|
||||
s->output.out_fric[i] = s->output.out_follow[i] + s->output.out_err[i];
|
||||
s->output.lpfout_fric[i] = LowPassFilter2p_Apply(&s->filter.fric.out[i], s->output.out_fric[i]);
|
||||
/* 输出 */
|
||||
MOTOR_RM_SetOutput(&s->param->fric_motor_param[i], s->output.lpfout_fric[i]);
|
||||
}
|
||||
/* 拨弹电机输出 */
|
||||
s->output.outagl_trig =PID_Calc(&s->pid.trig,s->target_variable.target_angle,s->feedback.trig_angle_cicle,0,s->dt);
|
||||
s->output.outomg_trig =PID_Calc(&s->pid.trig_omg,s->output.outagl_trig,s->feedback.trig_rpm,0,s->dt);
|
||||
s->output.outlpf_trig =LowPassFilter2p_Apply(&s->filter.trig.out, s->output.outomg_trig);
|
||||
|
||||
s->output.outagl_trig = PID_Calc(&s->pid.trig, s->target_variable.target_angle, s->feedback.trig_angle_cicle, 0, s->dt);
|
||||
s->output.outomg_trig = PID_Calc(&s->pid.trig_omg, s->output.outagl_trig, s->feedback.trig_rpm, 0, s->dt);
|
||||
s->output.outlpf_trig = LowPassFilter2p_Apply(&s->filter.trig.out, s->output.outomg_trig);
|
||||
MOTOR_RM_SetOutput(&s->param->trig_motor_param, s->output.outlpf_trig);
|
||||
|
||||
/* 检查状态机 */
|
||||
if(!cmd->ready)
|
||||
{
|
||||
Shoot_ResetCalu(s);
|
||||
Shoot_ResetOutput(s);
|
||||
s->running_state=SHOOT_STATE_IDLE;
|
||||
}
|
||||
else if(last_firecmd==false&&cmd->firecmd==true)
|
||||
{
|
||||
Shoot_ResetOutput(s);
|
||||
s->running_state=SHOOT_STATE_FIRE;
|
||||
s->shoot_Anglecalu.num_to_shoot+=s->param->shot_burst_num;
|
||||
|
||||
}
|
||||
{
|
||||
s->running_state = SHOOT_STATE_IDLE;
|
||||
}
|
||||
else if(last_firecmd == false && cmd->firecmd == true)
|
||||
{
|
||||
s->running_state = SHOOT_STATE_FIRE;
|
||||
s->shoot_Anglecalu.num_to_shoot += s->param->shot_burst_num;
|
||||
}
|
||||
break;
|
||||
case SHOOT_STATE_FIRE:
|
||||
Shoot_CaluTargetAngle(s, cmd);
|
||||
for(int i=0;i<SHOOT_FRIC_NUM;i++)
|
||||
{
|
||||
s->output.out_follow[i]=PID_Calc(&s->pid.fric_follow[i],s->target_variable.target_rpm/MAX_FRIC_RPM,s->feedback.fric_rpm[i],0,s->dt);
|
||||
s->output.out_err[i]=PID_Calc(&s->pid.fric_err[i],s->feedback.fric_avgrpm,s->feedback.fric_rpm[i],0,s->dt);
|
||||
|
||||
case SHOOT_STATE_FIRE:/* 发射状态 */
|
||||
Shoot_CaluTargetAngle(s, cmd);
|
||||
for(int i = 0; i < SHOOT_FRIC_NUM; i++)
|
||||
{
|
||||
s->output.out_follow[i] = PID_Calc(&s->pid.fric_follow[i], s->target_variable.target_rpm / MAX_FRIC_RPM, s->feedback.fric_rpm[i], 0, s->dt);
|
||||
s->output.out_err[i] = PID_Calc(&s->pid.fric_err[i], s->feedback.fric_avgrpm, s->feedback.fric_rpm[i], 0, s->dt);
|
||||
ScaleSumTo1(&s->output.out_follow[i], &s->output.out_err[i]);
|
||||
s->output.out_fric[i]=s->output.out_follow[i]+s->output.out_err[i];
|
||||
s->output.out_fric[i] = s->output.out_follow[i] + s->output.out_err[i];
|
||||
s->output.lpfout_fric[i] = LowPassFilter2p_Apply(&s->filter.fric.out[i], s->output.out_fric[i]);
|
||||
MOTOR_RM_SetOutput(&s->param->fric_motor_param[i], s->output.lpfout_fric[i]);
|
||||
}
|
||||
s->output.outagl_trig =PID_Calc(&s->pid.trig,s->target_variable.target_angle,s->feedback.trig_angle_cicle,0,s->dt);
|
||||
s->output.outomg_trig =PID_Calc(&s->pid.trig_omg,s->output.outagl_trig,s->feedback.trig_rpm,0,s->dt);
|
||||
s->output.outlpf_trig =LowPassFilter2p_Apply(&s->filter.trig.out, s->output.outomg_trig);
|
||||
s->output.outagl_trig = PID_Calc(&s->pid.trig, s->target_variable.target_angle, s->feedback.trig_angle_cicle, 0, s->dt);
|
||||
s->output.outomg_trig = PID_Calc(&s->pid.trig_omg, s->output.outagl_trig, s->feedback.trig_rpm, 0, s->dt);
|
||||
s->output.outlpf_trig = LowPassFilter2p_Apply(&s->filter.trig.out, s->output.outomg_trig);
|
||||
MOTOR_RM_SetOutput(&s->param->trig_motor_param, s->output.outlpf_trig);
|
||||
if(!cmd->firecmd)
|
||||
{
|
||||
Shoot_ResetOutput(s);
|
||||
s->running_state=SHOOT_STATE_READY;
|
||||
}
|
||||
|
||||
if(!cmd->firecmd && s->shoot_Anglecalu.num_to_shoot == 0)
|
||||
{
|
||||
s->running_state = SHOOT_STATE_READY;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
s->running_state=SHOOT_STATE_IDLE;
|
||||
s->running_state = SHOOT_STATE_IDLE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if (!g_debug_motor_enable) {
|
||||
// for(int i=0;i<SHOOT_FRIC_NUM;i++) {
|
||||
// MOTOR_RM_SetOutput(&s->param->fric_motor_param[i], 0.0f); // 摩擦轮强制 0 电流
|
||||
// }
|
||||
// MOTOR_RM_SetOutput(&s->param->trig_motor_param, 0.0f); // 拨弹电机强制 0 电流
|
||||
// }
|
||||
|
||||
MOTOR_RM_Ctrl(&s->param->fric_motor_param[0]);
|
||||
// 执行底层电机控制
|
||||
for(int i = 0; i < SHOOT_FRIC_NUM; i++) {
|
||||
MOTOR_RM_Ctrl(&s->param->fric_motor_param[i]);
|
||||
}
|
||||
MOTOR_RM_Ctrl(&s->param->trig_motor_param);
|
||||
|
||||
last_firecmd = cmd->firecmd;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
116
User/task/ai.c
116
User/task/ai.c
@ -19,7 +19,7 @@
|
||||
|
||||
/* Private typedef ---------------------------------------------------------- */
|
||||
extern uint8_t AI_mode; // AI 模式变量,供其他模块查询当前AI控制模式
|
||||
|
||||
#define MAX_RX_BUF_SIZE 128 // 或 (sizeof(VisionToGimbal_t) * 2)
|
||||
// MCU 数据结构(MCU -> 上位机)
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint8_t head[2]; // {'M', 'R'}
|
||||
@ -33,7 +33,7 @@ typedef struct __attribute__((packed)) {
|
||||
uint16_t bullet_count; // 子弹累计发送次数
|
||||
uint16_t crc16;
|
||||
} GimbalToVision_t;
|
||||
|
||||
Gimbal_Vision_t vision_data1;
|
||||
// 确保结构体大小符合要求 (C11 标准)
|
||||
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
|
||||
_Static_assert(sizeof(GimbalToVision_t) <= 64, "GimbalToVision_t size exceeds 64 bytes");
|
||||
@ -67,61 +67,64 @@ GimbalToVision_t tx_pkg;
|
||||
/* USER FUNCTION BEGIN */
|
||||
|
||||
/**
|
||||
* @brief AI串口接收完成回调函数
|
||||
* @note 验证包头与CRC16校验,校验通过后更新控制指令,并重新开启DMA接收
|
||||
* @brief AI串口空闲中断回调函数 (适配无参 BSP 架构)
|
||||
*/
|
||||
static void AI_RxCpltCallback(void) {
|
||||
/* 检查包头是否为 'M' 和 'R' */
|
||||
if (ai_rx_buf[0] == 'M' && ai_rx_buf[1] == 'R') {
|
||||
/* 校验 CRC,确保数据包完整 */
|
||||
if (CRC16_Verify(ai_rx_buf, sizeof(VisionToGimbal_t))) {
|
||||
memcpy(&ai_rx_data, ai_rx_buf, sizeof(VisionToGimbal_t));
|
||||
|
||||
/* --- 务必初始化局部变量 --- */
|
||||
Gimbal_Vision_t vision_data;
|
||||
memset(&vision_data, 0, sizeof(Gimbal_Vision_t));
|
||||
|
||||
Shoot_CMD_t ai_shoot_cmd;
|
||||
memset(&ai_shoot_cmd, 0, sizeof(Shoot_CMD_t)); // 防止野指针走火
|
||||
|
||||
/* --- 正确赋值视觉结构体 --- */
|
||||
switch (ai_rx_data.mode) {
|
||||
case 0: /* 不控制 */
|
||||
vision_data.target_found = 0;
|
||||
// ai_shoot_cmd.mode = SHOOT_MODE_STOP;
|
||||
break;
|
||||
case 1: /* 控制云台但不开火 */
|
||||
vision_data.target_found = 1;
|
||||
vision_data.yaw = ai_rx_data.yaw;
|
||||
vision_data.pit = ai_rx_data.pitch;
|
||||
vision_data.yaw_v_ff = ai_rx_data.yaw_vel;
|
||||
vision_data.pit_v_ff = ai_rx_data.pitch_vel;
|
||||
// ai_shoot_cmd.mode = SHOOT_MODE_READY;
|
||||
break;
|
||||
case 2: /* 控制云台且开火 */
|
||||
vision_data.target_found = 1;
|
||||
vision_data.yaw = ai_rx_data.yaw;
|
||||
vision_data.pit = ai_rx_data.pitch;
|
||||
vision_data.yaw_v_ff = ai_rx_data.yaw_vel;
|
||||
vision_data.pit_v_ff = ai_rx_data.pitch_vel;
|
||||
// ai_shoot_cmd.mode = SHOOT_MODE_FIRE;
|
||||
break;
|
||||
default:
|
||||
vision_data.target_found = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
/* 将视觉数据发送到专属队列,不与遥控器命令冲突 */
|
||||
osMessageQueuePut(task_runtime.msgq.vision.data, &vision_data, 0, 0);
|
||||
|
||||
/* 将发射指令发送到发射队列 (确保枚举名称与shoot.h对应后解开注释) */
|
||||
// osMessageQueuePut(task_runtime.msgq.shoot.shoot_cmd, &ai_shoot_cmd, 0, 0);
|
||||
}
|
||||
}
|
||||
/* 重新开启DMA定长接收 */
|
||||
BSP_UART_Receive(BSP_UART_AI, ai_rx_buf, sizeof(VisionToGimbal_t), true);
|
||||
}
|
||||
static void AI_RxIdleCallback(void) {
|
||||
/* 1. 获取本次空闲中断触发时的实际接收长度 */
|
||||
uint16_t Size = BSP_UART_GetRxCount(BSP_UART_AI, MAX_RX_BUF_SIZE);
|
||||
|
||||
/* 2. 必须手动停止当前 DMA,以便在函数末尾重新对齐接收指针 */
|
||||
HAL_UART_DMAStop(BSP_UART_GetHandle(BSP_UART_AI));
|
||||
|
||||
/* 3. 长度校验:严格匹配结构体长度 */
|
||||
if (Size != sizeof(VisionToGimbal_t)) {
|
||||
goto RESTART_RX;
|
||||
}
|
||||
|
||||
/* 4. 包头校验 */
|
||||
if (ai_rx_buf[0] != 'M' || ai_rx_buf[1] != 'R') {
|
||||
goto RESTART_RX;
|
||||
}
|
||||
|
||||
/* 5. CRC校验 */
|
||||
if (!CRC16_Verify(ai_rx_buf, Size)) {
|
||||
goto RESTART_RX;
|
||||
}
|
||||
|
||||
/* --- 下方保留原有的解析和入队逻辑 --- */
|
||||
VisionToGimbal_t *p_rx_data = (VisionToGimbal_t *)ai_rx_buf;
|
||||
Gimbal_Vision_t vision_data;
|
||||
memset(&vision_data, 0, sizeof(Gimbal_Vision_t));
|
||||
|
||||
uint8_t is_valid_packet = 1;
|
||||
|
||||
switch (p_rx_data->mode) {
|
||||
case 0:
|
||||
vision_data.target_found = 0;
|
||||
__NOP();
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
vision_data.target_found = 1;
|
||||
vision_data.yaw = p_rx_data->yaw;
|
||||
vision_data.pit = p_rx_data->pitch;
|
||||
vision_data.yaw_v_ff = p_rx_data->yaw_vel;
|
||||
vision_data.pit_v_ff = p_rx_data->pitch_vel;
|
||||
break;
|
||||
default:
|
||||
is_valid_packet = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (is_valid_packet) {
|
||||
osMessageQueuePut(task_runtime.msgq.vision.data, &vision_data, 0, 0);
|
||||
vision_data1 = vision_data;
|
||||
}
|
||||
|
||||
RESTART_RX:
|
||||
/* 6. 重新开启空闲中断定长接收 */
|
||||
BSP_UART_Receive_IDLE(BSP_UART_AI, ai_rx_buf, MAX_RX_BUF_SIZE);
|
||||
}
|
||||
/**
|
||||
* @brief 向上位机发送MCU状态数据
|
||||
*/
|
||||
@ -177,7 +180,8 @@ void Task_ai(void *argument) {
|
||||
|
||||
/* USER CODE INIT BEGIN */
|
||||
/* 注册串口接收完成回调并启动第一次DMA接收 */
|
||||
BSP_UART_RegisterCallback(BSP_UART_AI, BSP_UART_RX_CPLT_CB, AI_RxCpltCallback);
|
||||
// 假设 BSP 已添加 IDLE 中断回调对应的枚举 BSP_UART_IDLE_CB
|
||||
BSP_UART_RegisterCallback(BSP_UART_AI, BSP_UART_IDLE_LINE_CB, AI_RxIdleCallback);
|
||||
BSP_UART_Receive(BSP_UART_AI, ai_rx_buf, sizeof(VisionToGimbal_t), true);
|
||||
/* USER CODE INIT END */
|
||||
|
||||
|
||||
@ -78,17 +78,35 @@ void Task_atti_esti(void *argument) {
|
||||
BMI088_ParseAccl(&bmi088);
|
||||
BMI088_ParseGyro(&bmi088);
|
||||
|
||||
/* 根据设备接收到的数据进行姿态解析 */
|
||||
AHRS_Update(&gimbal_ahrs, &bmi088.accl, &bmi088.gyro, &magn);
|
||||
/* =================== 核心修复区域:原始数据重映射 =================== */
|
||||
/* 采用 typeof 定义临时变量,拷贝原始数据以保留 z 轴等未修改的数据 */
|
||||
/* 注意:typeof 为 GCC 扩展特性,STM32 编译环境(Keil/CubeIDE)默认支持 */
|
||||
__typeof__(bmi088.accl) mapped_accl = bmi088.accl;
|
||||
__typeof__(bmi088.gyro) mapped_gyro = bmi088.gyro;
|
||||
|
||||
/* 根据解析出来的四元数计算欧拉角 */
|
||||
/* 执行90度旋转映射 (假设顺时针90度: X_new = Y_old, Y_new = -X_old) */
|
||||
mapped_accl.x = bmi088.accl.x;
|
||||
mapped_accl.y = bmi088.accl.y;
|
||||
|
||||
mapped_gyro.x = bmi088.gyro.x;
|
||||
mapped_gyro.y = bmi088.gyro.y;
|
||||
|
||||
/* 根据重映射后的加速度与角速度进行姿态解析 */
|
||||
AHRS_Update(&gimbal_ahrs, &mapped_accl, &mapped_gyro, &magn);
|
||||
|
||||
/* 根据解析出来的正确四元数计算欧拉角 */
|
||||
AHRS_GetEulr(&eulr_to_send, &gimbal_ahrs);
|
||||
osKernelUnlock();
|
||||
/* ==================================================================== */
|
||||
|
||||
// --- 新增:将IMU数据(角速度与欧拉角)发送至云台控制任务的队列 ---
|
||||
// --- 修改:将IMU数据(正确的角速度与欧拉角)发送至云台控制任务的队列 ---
|
||||
Gimbal_IMU_t gimbal_imu_to_send;
|
||||
gimbal_imu_to_send.gyro = bmi088.gyro; // 填充陀螺仪角速度
|
||||
gimbal_imu_to_send.eulr = eulr_to_send; // 填充解算后的欧拉角
|
||||
|
||||
// 直接使用映射好的角速度,确保内环(角速度)与外环(角度)坐标系完全一致
|
||||
gimbal_imu_to_send.gyro = mapped_gyro;
|
||||
gimbal_imu_to_send.eulr = eulr_to_send;
|
||||
|
||||
// 发送至队列
|
||||
osMessageQueuePut(task_runtime.msgq.gimbal.imu, &gimbal_imu_to_send, 0, 0);
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
@ -98,8 +116,9 @@ void Task_atti_esti(void *argument) {
|
||||
// 0: 不等待,如果队列满了直接跳过(保证实时性)
|
||||
osMessageQueuePut(task_runtime.msgq.ai.quat, &gimbal_ahrs.quat, 0, 0);
|
||||
|
||||
// 如果 AI 任务还需要欧拉角,也可以发送欧拉角队列
|
||||
// 如果 AI 任务还需要欧拉角,发送欧拉角队列
|
||||
osMessageQueuePut(task_runtime.msgq.ai.eulr, &eulr_to_send, 0, 0);
|
||||
|
||||
BSP_PWM_SetComp(BSP_PWM_IMU_HEAT_PWM, PID_Calc(&imu_temp_ctrl_pid, 40.5f,
|
||||
bmi088.temp, 0.0f, 0.0f));
|
||||
|
||||
|
||||
@ -19,6 +19,8 @@
|
||||
Gimbal_t gimbal;
|
||||
Gimbal_IMU_t gimbal_imu;
|
||||
Gimbal_CMD_t gimbal_cmd;
|
||||
uint32_t last_vision_update_tick = 0;
|
||||
extern Gimbal_Vision_t vision_data1;
|
||||
/* USER STRUCT END */
|
||||
|
||||
/* Private function --------------------------------------------------------- */
|
||||
@ -35,9 +37,9 @@ void Task_ctrl_gimbal(void *argument) {
|
||||
static Gimbal_Vision_t last_vision_data = {0};
|
||||
|
||||
/* 新增:记录最后一次收到有效视觉数据的时间戳 */
|
||||
static uint32_t last_vision_update_tick = 0;
|
||||
// static uint32_t last_vision_update_tick = 0;
|
||||
/* 新增:视觉断线超时阈值,此处设置为 100ms */
|
||||
const uint32_t vision_timeout_ticks = osKernelGetTickFreq() / 10;
|
||||
const uint32_t vision_timeout_ticks = osKernelGetTickFreq() / 100;
|
||||
|
||||
while (1) {
|
||||
tick += delay_tick;
|
||||
@ -51,20 +53,20 @@ void Task_ctrl_gimbal(void *argument) {
|
||||
osMessageQueueGet(task_runtime.msgq.gimbal.cmd, &gimbal_cmd, NULL, 0);
|
||||
|
||||
/* 3. 获取并缓存 AI 视觉信号,增加超时判断逻辑 */
|
||||
Gimbal_Vision_t current_vision;
|
||||
if (osMessageQueueGet(task_runtime.msgq.vision.data, ¤t_vision, NULL, 0) == osOK) {
|
||||
last_vision_data = current_vision; /* 有新视觉包时更新缓存 */
|
||||
last_vision_update_tick = osKernelGetTickCount(); /* 更新收到数据的时间戳 */
|
||||
} else {
|
||||
/* 未收到新数据时,检查是否超时断线 */
|
||||
if ((osKernelGetTickCount() - last_vision_update_tick) > vision_timeout_ticks) {
|
||||
last_vision_data.target_found = 0; /* 超时强制视为目标丢失 */
|
||||
/* 将缓存的 yaw/pit 角度清零,避免残留脏数据干扰 */
|
||||
last_vision_data.yaw = 0.0f;
|
||||
last_vision_data.pit = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// Gimbal_Vision_t current_vision;
|
||||
// if (osMessageQueueGet(task_runtime.msgq.vision.data, ¤t_vision, NULL, 0) == osOK) {
|
||||
// last_vision_data = current_vision; /* 有新视觉包时更新缓存 */
|
||||
// last_vision_update_tick = osKernelGetTickCount(); /* 更新收到数据的时间戳 */
|
||||
// } else {
|
||||
// /* 未收到新数据时,检查是否超时断线 */
|
||||
// if ((osKernelGetTickCount() - last_vision_update_tick) > vision_timeout_ticks) {
|
||||
// last_vision_data.target_found = 0; /* 超时强制视为目标丢失 */
|
||||
// /* 将缓存的 yaw/pit 角度清零,避免残留脏数据干扰 */
|
||||
// last_vision_data.yaw = 0.0f;
|
||||
// last_vision_data.pit = 0.0f;
|
||||
// }
|
||||
// }
|
||||
last_vision_data = vision_data1;
|
||||
/* 4. 强制将缓存的视觉数据挂载到当前的控制指令中 */
|
||||
gimbal_cmd.vision = last_vision_data;
|
||||
|
||||
|
||||
@ -64,14 +64,14 @@ void Task_rc(void *argument) {
|
||||
break;
|
||||
case DR16_SW_MID:
|
||||
cmd_for_gimbal.mode = GIMBAL_MODE_ABSOLUTE;
|
||||
cmd_for_gimbal.delta_yaw = -dr16.data.ch_r_x * 1.0f;
|
||||
cmd_for_gimbal.delta_pit = dr16.data.ch_r_y * 1.0f;
|
||||
cmd_for_gimbal.delta_yaw = -dr16.data.ch_r_x * 3.0f;
|
||||
cmd_for_gimbal.delta_pit = dr16.data.ch_r_y * 3.0f;
|
||||
AI_mode = 0; /* AI 模式:空闲 */
|
||||
break;
|
||||
case DR16_SW_DOWN:
|
||||
cmd_for_gimbal.mode = GIMBAL_MODE_AUTO_AIM;
|
||||
cmd_for_gimbal.delta_yaw = -dr16.data.ch_r_x * 1.0f;
|
||||
cmd_for_gimbal.delta_pit = dr16.data.ch_r_y * 1.0f;
|
||||
cmd_for_gimbal.delta_yaw = -dr16.data.ch_r_x * 3.0f;
|
||||
cmd_for_gimbal.delta_pit = dr16.data.ch_r_y * 3.0f;
|
||||
AI_mode = 1; /* AI 模式:自动瞄准 */
|
||||
break;
|
||||
default:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user