802 lines
38 KiB
C
802 lines
38 KiB
C
// app
|
|
#include "robot_def.h"
|
|
#include "robot_cmd.h"
|
|
// module
|
|
#include "controller.h"
|
|
#include "remote_control.h"
|
|
#include "ins_task.h"
|
|
#include "master_process.h"
|
|
#include "message_center.h"
|
|
#include "general_def.h"
|
|
#include "dji_motor.h"
|
|
#include "bmi088.h"
|
|
#include "vofa.h"
|
|
#include "board_comm.h"
|
|
// bsp
|
|
#include "bsp_dwt.h"
|
|
#include "bsp_log.h"
|
|
|
|
#include "opencv.h"
|
|
#include "step_motor.h"
|
|
|
|
#include "kalman_filter.h"
|
|
|
|
#include "arm_math.h"
|
|
|
|
#define mapping(x, in_min, in_max, out_min, out_max) \
|
|
((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min)
|
|
|
|
// 添加卡尔曼滤波器实例
|
|
static KalmanFilter_t yaw_error_kf;
|
|
static KalmanFilter_t pitch_error_kf;
|
|
static float filtered_yaw_error = 0.0f;
|
|
static float filtered_pitch_error = 0.0f;
|
|
|
|
// 私有宏,自动将编码器转换成角度值
|
|
#define YAW_ALIGN_ANGLE (YAW_CHASSIS_ALIGN_ECD * ECD_ANGLE_COEF_DJI) // 对齐时的角度,0-360
|
|
#define PTICH_HORIZON_ANGLE (PITCH_HORIZON_ECD * ECD_ANGLE_COEF_DJI) // pitch水平时电机的角度,0-360
|
|
|
|
#define yaw_control_kxmax 0.001f
|
|
#define yaw_control_kxmin 0.0005f
|
|
#define pitch_control_kxmax 0.001f
|
|
#define pitch_control_kxmin 0.0004f
|
|
|
|
/* cmd应用包含的模块实例指针和交互信息存储*/
|
|
#ifdef GIMBAL_BOARD // 对双板的兼容,条件编译
|
|
#include "can_comm.h"
|
|
static CANCommInstance *cmd_can_comm; // 双板通信
|
|
#endif
|
|
#ifdef ONE_BOARD
|
|
static Publisher_t *chassis_cmd_pub; // 底盘控制消息发布者
|
|
static Subscriber_t *chassis_feed_sub; // 底盘反馈信息订阅者
|
|
#endif // ONE_BOARD
|
|
|
|
static Chassis_Ctrl_Cmd_s chassis_cmd_send; // 发送给底盘应用的信息,包括控制信息和UI绘制相关
|
|
static Chassis_Upload_Data_s chassis_fetch_data; // 从底盘应用接收的反馈信息信息,底盘功率枪口热量与底盘运动状态等
|
|
|
|
static RC_ctrl_t *rc_data; // 遥控器数据,初始化时返回
|
|
static Vision_Recv_s *vision_recv_data; // 视觉接收数据指针,初始化时返回
|
|
static Vision_Send_s vision_send_data; // 视觉发送数据
|
|
static board_comm_data_t *board_comm_data; // 板间通信数据指针,初始化时返回
|
|
|
|
static Publisher_t *gimbal_cmd_pub; // 云台控制消息发布者
|
|
static Subscriber_t *gimbal_feed_sub; // 云台反馈信息订阅者
|
|
static Gimbal_Ctrl_Cmd_s gimbal_cmd_send; // 传递给云台的控制信息
|
|
static Gimbal_Upload_Data_s gimbal_fetch_data; // 从云台获取的反馈信息
|
|
|
|
static Publisher_t *shoot_cmd_pub; // 发射控制消息发布者
|
|
static Subscriber_t *shoot_feed_sub; // 发射反馈信息订阅者
|
|
static Shoot_Ctrl_Cmd_s shoot_cmd_send; // 传递给发射的控制信息
|
|
static Shoot_Upload_Data_s shoot_fetch_data; // 从发射获取的反馈信息
|
|
|
|
static Robot_Status_e robot_state; // 机器人整体工作状态
|
|
|
|
PIDInstance yaw_tracking_pid; // 云台yaw跟随pid
|
|
PIDInstance pitch_tracking_pid; // 云台pitch跟随pid
|
|
static float yaw_tracking_pid_output; // 云台yaw跟随pid输出
|
|
static float pitch_tracking_pid_output; // 云台pitch跟随pid输出
|
|
|
|
static float vofa_debug[7];
|
|
|
|
// #define PITCH_HORIZON_ECD 6800 // 俯仰水平时的电机编码器
|
|
|
|
// 在RobotCMDInit()中添加卡尔曼滤波器初始化
|
|
static void VisionKalmanFilterInit()
|
|
{
|
|
// yaw误差卡尔曼滤波器初始化
|
|
Kalman_Filter_Init(&yaw_error_kf, 2, 0, 1); // 状态2维(位置,速度), 控制0维, 观测1维
|
|
|
|
static float P_init_yaw[4] = {
|
|
5.0f, 0.0f, // 减小初始不确定性
|
|
0.0f, 5.0f};
|
|
memcpy(yaw_error_kf.P_data, P_init_yaw, sizeof(P_init_yaw));
|
|
|
|
// 状态转移矩阵F (dt = 1/200 = 0.005s, 200Hz控制频率)
|
|
static float F_init_yaw[4] = {
|
|
1.0f, 0.005f, // 修正时间步长
|
|
0.0f, 1.0f};
|
|
memcpy(yaw_error_kf.F_data, F_init_yaw, sizeof(F_init_yaw));
|
|
|
|
// 过程噪声协方差矩阵Q
|
|
static float Q_init_yaw[4] = {
|
|
0.1f, 0.0f, // 增大位置过程噪声
|
|
0.0f, 1.0f // 增大速度过程噪声
|
|
};
|
|
memcpy(yaw_error_kf.Q_data, Q_init_yaw, sizeof(Q_init_yaw));
|
|
|
|
// 观测矩阵H
|
|
static uint8_t measurement_map_yaw[1] = {1}; // 观测第1个状态(位置)
|
|
static float measurement_degree_yaw[1] = {1.0f};
|
|
static float mat_R_diagonal_yaw[1] = {1.0f}; // 减小观测噪声,增加对观测的信任
|
|
static float state_min_variance_yaw[2] = {0.001f, 0.001f}; // 减小最小方差
|
|
|
|
yaw_error_kf.UseAutoAdjustment = 1;
|
|
memcpy(yaw_error_kf.MeasurementMap, measurement_map_yaw, sizeof(measurement_map_yaw));
|
|
memcpy(yaw_error_kf.MeasurementDegree, measurement_degree_yaw, sizeof(measurement_degree_yaw));
|
|
memcpy(yaw_error_kf.MatR_DiagonalElements, mat_R_diagonal_yaw, sizeof(mat_R_diagonal_yaw));
|
|
memcpy(yaw_error_kf.StateMinVariance, state_min_variance_yaw, sizeof(state_min_variance_yaw));
|
|
|
|
// pitch误差卡尔曼滤波器初始化
|
|
Kalman_Filter_Init(&pitch_error_kf, 2, 0, 1); // 状态2维(位置,速度), 控制0维, 观测1维
|
|
static float P_init_pitch[4] = {
|
|
5.0f, 0.0f, // 减小初始不确定性
|
|
0.0f, 5.0f};
|
|
memcpy(pitch_error_kf.P_data, P_init_pitch, sizeof(P_init_pitch));
|
|
// 状态转移矩阵F (dt = 1/200 = 0.005s, 200Hz控制频率)
|
|
static float F_init_pitch[4] = {
|
|
1.0f, 0.005f, // 修正时间步长
|
|
0.0f, 1.0f};
|
|
memcpy(pitch_error_kf.F_data, F_init_pitch, sizeof(F_init_pitch));
|
|
// 过程噪声协方差矩阵Q
|
|
static float Q_init_pitch[4] = {
|
|
0.1f, 0.0f, // 增大位置过程噪声
|
|
0.0f, 1.0f // 增大速度过程噪声
|
|
};
|
|
memcpy(pitch_error_kf.Q_data, Q_init_pitch, sizeof(Q_init_pitch));
|
|
// 观测矩阵H
|
|
static uint8_t measurement_map_pitch[1] = {1}; // 观测第1个状态(位置)
|
|
static float measurement_degree_pitch[1] = {1.0f};
|
|
static float mat_R_diagonal_pitch[1] = {1.0f}; // 减小观测噪声,增加对观测的信任
|
|
static float state_min_variance_pitch[2] = {0.001f, 0.001f}; // 减小最小方差
|
|
pitch_error_kf.UseAutoAdjustment = 1;
|
|
memcpy(pitch_error_kf.MeasurementMap, measurement_map_pitch, sizeof(measurement_map_pitch));
|
|
memcpy(pitch_error_kf.MeasurementDegree, measurement_degree_pitch, sizeof(measurement_degree_pitch));
|
|
memcpy(pitch_error_kf.MatR_DiagonalElements, mat_R_diagonal_pitch, sizeof(mat_R_diagonal_pitch));
|
|
memcpy(pitch_error_kf.StateMinVariance, state_min_variance_pitch, sizeof(state_min_variance_pitch));
|
|
}
|
|
|
|
void RobotCMDInit()
|
|
{
|
|
rc_data = RemoteControlInit(&huart5); // 修改为对应串口,注意如果是自研板dbus协议串口需选用添加了反相器的那个
|
|
gimbal_cmd_pub = PubRegister("gimbal_cmd", sizeof(Gimbal_Ctrl_Cmd_s));
|
|
gimbal_feed_sub = SubRegister("gimbal_feed", sizeof(Gimbal_Upload_Data_s));
|
|
shoot_cmd_pub = PubRegister("shoot_cmd", sizeof(Shoot_Ctrl_Cmd_s));
|
|
shoot_feed_sub = SubRegister("shoot_feed", sizeof(Shoot_Upload_Data_s));
|
|
chassis_cmd_pub = PubRegister("chassis_cmd", sizeof(Chassis_Ctrl_Cmd_s));
|
|
chassis_feed_sub = SubRegister("chassis_feed", sizeof(Chassis_Upload_Data_s));
|
|
|
|
static PID_Init_Config_s yaw_tracking_pid_config = {
|
|
.Kp = 1.5f,
|
|
.Ki = 3.0f,
|
|
.Kd = 0.07f,
|
|
.IntegralLimit = 100.0f,
|
|
.CoefB = 20.0f,
|
|
.CoefA = 20.0f,
|
|
// .Output_LPF_RC = 0.002f, // 100Hz
|
|
.Improve = PID_Trapezoid_Intergral | PID_Integral_Limit | PID_Derivative_On_Measurement | PID_ChangingIntegrationRate,
|
|
.MaxOut = 200.0f,
|
|
};
|
|
static PID_Init_Config_s pitch_tracking_pid_config = {
|
|
.Kp = 1.0f,
|
|
.Ki = 2.0f,
|
|
.Kd = 0.05f,
|
|
.IntegralLimit = 100.0f,
|
|
.CoefB = 10.0f,
|
|
.CoefA = 30.0f,
|
|
// .Output_LPF_RC = 0.002f, // 100Hz
|
|
.Improve = PID_Trapezoid_Intergral | PID_Integral_Limit | PID_Derivative_On_Measurement | PID_ChangingIntegrationRate,
|
|
.MaxOut = 200.0f,
|
|
};
|
|
|
|
PIDInit(&yaw_tracking_pid, &yaw_tracking_pid_config);
|
|
PIDInit(&pitch_tracking_pid, &pitch_tracking_pid_config);
|
|
VisionKalmanFilterInit();
|
|
Vision_Uart_Init(&huart10);
|
|
board_comm_data = BoardCommInit(&huart1); // 初始化板间通信数据指针
|
|
robot_state = ROBOT_READY; // 启动时机器人进入工作模式,后续加入所有应用初始化完成之后再进入
|
|
}
|
|
|
|
static uint8_t first_circle_flag = 1;
|
|
static float circle_progress = 0; // 画圆进度 100%为一圈
|
|
static float circle_radius = 60.0f; // 画圆半径
|
|
static float circle_angle = 0.0f; // 当前圆周角度
|
|
static float target_x = 0.0f; // 圆周上目标点x坐标
|
|
static float target_y = 0.0f; // 圆周上目标点y坐标
|
|
|
|
/**
|
|
* @brief 控制输入为遥控器(调试时)的模式和控制量设置
|
|
*
|
|
*/
|
|
static void RemoteControlSet()
|
|
{
|
|
chassis_cmd_send.chassis_mode = CHASSIS_NO_FOLLOW;
|
|
gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
|
|
if (!switch_is_down(rc_data[TEMP].rc.switch_left))
|
|
{
|
|
if (Vison_revc_flag && switch_is_mid(rc_data[TEMP].rc.switch_left))
|
|
{
|
|
|
|
LIMIT_MIN_MAX(opencv_Vision_data[0], -100.0f, 100.0f);
|
|
LIMIT_MIN_MAX(opencv_Vision_data[1], -100.0f, 100.0f);
|
|
LIMIT_MIN_MAX(opencv_Vision_data[2], 500.0f, 1560.0f);
|
|
yaw_error_kf.MeasuredVector[0] = opencv_Vision_data[0];
|
|
float *yaw_filtered = Kalman_Filter_Update(&yaw_error_kf);
|
|
filtered_yaw_error = yaw_filtered[0];
|
|
pitch_error_kf.MeasuredVector[0] = opencv_Vision_data[1];
|
|
float *pitch_filtered = Kalman_Filter_Update(&pitch_error_kf);
|
|
filtered_pitch_error = pitch_filtered[0];
|
|
|
|
yaw_tracking_pid_output = PIDCalculate(&yaw_tracking_pid, filtered_yaw_error, 0);
|
|
pitch_tracking_pid_output = PIDCalculate(&pitch_tracking_pid, filtered_pitch_error, 0);
|
|
gimbal_cmd_send.yaw -= yaw_tracking_pid_output * mapping(opencv_Vision_data[2], 500.0f, 1560.0f, yaw_control_kxmax, yaw_control_kxmin); // yaw角度控制
|
|
gimbal_cmd_send.pitch += 0.001f * pitch_tracking_pid_output; // pitch角度控制
|
|
vofa_debug[0] = opencv_Vision_data[0]; // 调试用
|
|
vofa_debug[1] = filtered_yaw_error; // 调试用
|
|
vofa_debug[2] = yaw_tracking_pid.Err; // 调试用
|
|
vofa_debug[3] = yaw_tracking_pid.Pout; // 调试用
|
|
vofa_debug[4] = yaw_tracking_pid.Iout; // 调试用
|
|
vofa_debug[5] = yaw_tracking_pid.Dout; // 调试用
|
|
vofa_debug[6] = yaw_tracking_pid.Output; // 调试用
|
|
vofa_justfloat_output(vofa_debug, 7, &huart7);
|
|
first_circle_flag = 1; // 重置画圆标志
|
|
}
|
|
else if (switch_is_up(rc_data[TEMP].rc.switch_left))
|
|
{
|
|
if (first_circle_flag)
|
|
{
|
|
circle_progress = 0;
|
|
circle_angle = 0.0f;
|
|
first_circle_flag = 0;
|
|
}
|
|
LIMIT_MIN_MAX(opencv_Vision_data[0], -100.0f, 100.0f);
|
|
LIMIT_MIN_MAX(opencv_Vision_data[1], -100.0f, 100.0f);
|
|
LIMIT_MIN_MAX(opencv_Vision_data[2], 500.0f, 1560.0f);
|
|
yaw_error_kf.MeasuredVector[0] = opencv_Vision_data[0];
|
|
float *yaw_filtered = Kalman_Filter_Update(&yaw_error_kf);
|
|
filtered_yaw_error = yaw_filtered[0];
|
|
pitch_error_kf.MeasuredVector[0] = opencv_Vision_data[1];
|
|
float *pitch_filtered = Kalman_Filter_Update(&pitch_error_kf);
|
|
filtered_pitch_error = pitch_filtered[0];
|
|
// 画圆模式 500hz 16s画完整圆
|
|
circle_progress += 0.000125f; // 每次增加1/16
|
|
if (circle_progress >= 1.0f)
|
|
{
|
|
circle_progress = 1.0f; // 限制在100%
|
|
}
|
|
circle_angle = 2.0f * PI * circle_progress; // 计算当前圆周角度
|
|
target_x = circle_radius * cosf(circle_angle); // 计算圆周上目标点x坐标
|
|
target_y = circle_radius * sinf(circle_angle); // 计算圆周上目标点y坐标
|
|
yaw_tracking_pid_output = PIDCalculate(&yaw_tracking_pid, filtered_yaw_error, target_x);
|
|
pitch_tracking_pid_output = PIDCalculate(&pitch_tracking_pid, filtered_pitch_error, target_y);
|
|
gimbal_cmd_send.yaw -= yaw_tracking_pid_output * mapping(opencv_Vision_data[2], 500.0f, 1560.0f, yaw_control_kxmax, yaw_control_kxmin); // yaw角度控制
|
|
gimbal_cmd_send.pitch += pitch_tracking_pid_output * mapping(opencv_Vision_data[2], 500.0f, 1560.0f, pitch_control_kxmax, pitch_control_kxmin); // pitch角度控制
|
|
}
|
|
else
|
|
{
|
|
first_circle_flag = 1; // 重置画圆标志
|
|
gimbal_cmd_send.yaw -= 0.0025f * (float)rc_data[TEMP].rc.rocker_r_; // yaw角度控制
|
|
gimbal_cmd_send.pitch += 0.00125f * (float)rc_data[TEMP].rc.rocker_r1; // pitch角度控制
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gimbal_cmd_send.yaw -= 0.0025f * (float)rc_data[TEMP].rc.rocker_r_; // yaw角度控制
|
|
gimbal_cmd_send.pitch += 0.00125f * (float)rc_data[TEMP].rc.rocker_r1; // pitch角度控制
|
|
}
|
|
|
|
if (gimbal_cmd_send.pitch > 30.0f) // 限制pitch角度
|
|
gimbal_cmd_send.pitch = 30.0f;
|
|
else if (gimbal_cmd_send.pitch < -30.0f)
|
|
gimbal_cmd_send.pitch = -30.0f;
|
|
}
|
|
|
|
void laser_on()
|
|
{
|
|
HAL_GPIO_WritePin(POWER_5V_GPIO_Port, POWER_5V_Pin, GPIO_PIN_SET);
|
|
}
|
|
|
|
void laser_off()
|
|
{
|
|
HAL_GPIO_WritePin(POWER_5V_GPIO_Port, POWER_5V_Pin, GPIO_PIN_RESET);
|
|
}
|
|
enum YunTaiState_e
|
|
{
|
|
YUNT_IDLE = 0, // 云台无任务
|
|
YUNT_NO1, // 云台题号1准备
|
|
YUNT_NO2_WAITING,
|
|
YUNT_NO2,
|
|
YUNT_NO3_WAITING,
|
|
YUNT_NO3_TURING,
|
|
YUNT_NO3_SEARCHING,
|
|
YUNT_NO3_TRACKING,
|
|
YUNT_NO4_1_WAITING,
|
|
YUNT_NO4_1_TURING,
|
|
YUNT_NO4_1_TRACKING,
|
|
YUNT_NO4_1_TRACKING_RUNNING,
|
|
YUNT_NO4_3_WAITING,
|
|
YUNT_NO4_3_TURING,
|
|
YUNT_NO4_3_TRACKING,
|
|
YUNT_NO4_3_TRACKING_RUNNING,
|
|
} YunTaiState = YUNT_IDLE; // 云台状态机
|
|
|
|
static float yuntai_no3_yawtarget = 0.0f; // 云台题号3目标yaw角度
|
|
static float yuntai_no3_searching_yaw = 0.0f; // 云台题号3搜索yaw角度
|
|
static float yuntai_no3_searching_yaw_max = 60.0f; // 云台yaw题号3搜索最大yaw角度
|
|
static float yuntai_no3_searching_yaw_min = -60.0f; // 云台yaw题号3搜索最小yaw角度
|
|
static float yuntai_no3_searching_step = 0.2f; // 云台题号3搜索步长,每次0.18度
|
|
static uint8_t yuntai_no3_searching_dir = 0; // 云台题号3搜索方向, 0: -, 1:+
|
|
static int16_t yuntai_no3_searching_count = 50; // 3计数器
|
|
|
|
static uint8_t no4_turning_count = 0; // 题号4转向计数器
|
|
static uint8_t no4_turning_status = 0; // 题号4转向状态
|
|
static uint8_t no4_turning_feedforward = 0.05;
|
|
|
|
static float timestart_timeline = 0.0f; // 题号开始时间
|
|
|
|
/**
|
|
* @brief 题号状态机
|
|
*
|
|
*/
|
|
static void AutoSet()
|
|
{
|
|
|
|
yaw_tracking_pid.Ki = 3.0f;
|
|
|
|
switch (board_comm_data->rx_int_data[0]) // 根据板间通信数据的第一个整数值来判断题号
|
|
{
|
|
case 1:
|
|
YunTaiState = YUNT_NO1;
|
|
break;
|
|
case 2:
|
|
if (board_comm_data->rx_int_data[1] == 0)
|
|
{
|
|
YunTaiState = YUNT_NO2_WAITING;
|
|
}
|
|
else
|
|
{
|
|
YunTaiState = YUNT_NO2; // 进入题号2状态
|
|
}
|
|
break;
|
|
case 3:
|
|
if (board_comm_data->rx_int_data[1] == 0)
|
|
{
|
|
YunTaiState = YUNT_NO3_WAITING;
|
|
}
|
|
else if (YunTaiState == YUNT_NO3_WAITING)
|
|
{
|
|
YunTaiState = YUNT_NO3_TURING; // 进入题号3转向状态
|
|
}
|
|
break;
|
|
case 4:
|
|
case 5:
|
|
if (board_comm_data->rx_int_data[1] == 0)
|
|
{
|
|
YunTaiState = YUNT_NO4_1_WAITING; // 进入题号4-1等待状态
|
|
}
|
|
else if (board_comm_data->rx_int_data[1] == 1 && YunTaiState == YUNT_NO4_1_WAITING)
|
|
{
|
|
YunTaiState = YUNT_NO4_1_TURING; // 进入题号4-1转向状态
|
|
}
|
|
else if (board_comm_data->rx_int_data[1] == 2)
|
|
{
|
|
YunTaiState = YUNT_NO4_1_TRACKING_RUNNING;
|
|
}
|
|
break;
|
|
case 6:
|
|
if (board_comm_data->rx_int_data[1] == 0)
|
|
{
|
|
YunTaiState = YUNT_NO4_3_WAITING; // 进入题号4-3等待状态
|
|
}
|
|
else if (board_comm_data->rx_int_data[1] == 1 && YunTaiState == YUNT_NO4_3_WAITING)
|
|
{
|
|
YunTaiState = YUNT_NO4_3_TURING; // 进入题号4-3状态
|
|
}
|
|
else if (board_comm_data->rx_int_data[1] == 2)
|
|
{
|
|
YunTaiState = YUNT_NO4_3_TRACKING_RUNNING;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch (YunTaiState)
|
|
{
|
|
case YUNT_NO1:
|
|
gimbal_cmd_send.gimbal_mode = GIMBAL_ZERO_FORCE;
|
|
chassis_cmd_send.chassis_mode = CHASSIS_ZERO_FORCE;
|
|
gimbal_cmd_send.yaw = gimbal_fetch_data.gimbal_imu_data.YawTotalAngle;
|
|
gimbal_cmd_send.pitch = gimbal_fetch_data.gimbal_imu_data.Pitch;
|
|
laser_off();
|
|
break;
|
|
case YUNT_NO2_WAITING:
|
|
gimbal_cmd_send.gimbal_mode = GIMBAL_ZERO_FORCE;
|
|
gimbal_cmd_send.yaw = gimbal_fetch_data.gimbal_imu_data.YawTotalAngle;
|
|
gimbal_cmd_send.pitch = gimbal_fetch_data.gimbal_imu_data.Pitch;
|
|
timestart_timeline = DWT_GetTimeline_ms(); // 记录开始时间
|
|
laser_off();
|
|
break;
|
|
case YUNT_NO2:
|
|
gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
|
|
if (DWT_GetTimeline_ms() - timestart_timeline >= 1500.0f)
|
|
{
|
|
laser_on();
|
|
}
|
|
else
|
|
{
|
|
if (Vison_revc_flag)
|
|
{
|
|
LIMIT_MIN_MAX(opencv_Vision_data[0], -100.0f, 100.0f);
|
|
LIMIT_MIN_MAX(opencv_Vision_data[1], -100.0f, 100.0f);
|
|
LIMIT_MIN_MAX(opencv_Vision_data[2], 500.0f, 1560.0f);
|
|
yaw_error_kf.MeasuredVector[0] = opencv_Vision_data[0];
|
|
float *yaw_filtered = Kalman_Filter_Update(&yaw_error_kf);
|
|
filtered_yaw_error = yaw_filtered[0];
|
|
pitch_error_kf.MeasuredVector[0] = opencv_Vision_data[1];
|
|
float *pitch_filtered = Kalman_Filter_Update(&pitch_error_kf);
|
|
filtered_pitch_error = pitch_filtered[0];
|
|
yaw_tracking_pid_output = PIDCalculate(&yaw_tracking_pid, filtered_yaw_error, 0);
|
|
pitch_tracking_pid_output = PIDCalculate(&pitch_tracking_pid, filtered_pitch_error, 0);
|
|
gimbal_cmd_send.yaw -= yaw_tracking_pid_output * mapping(opencv_Vision_data[2], 500.0f, 1560.0f, yaw_control_kxmax, yaw_control_kxmin); // yaw角度控制
|
|
gimbal_cmd_send.pitch += 0.001f * pitch_tracking_pid_output;
|
|
}
|
|
}
|
|
|
|
break;
|
|
case YUNT_NO3_WAITING:
|
|
gimbal_cmd_send.gimbal_mode = GIMBAL_ZERO_FORCE;
|
|
gimbal_cmd_send.yaw = gimbal_fetch_data.gimbal_imu_data.YawTotalAngle;
|
|
gimbal_cmd_send.pitch = gimbal_fetch_data.gimbal_imu_data.Pitch;
|
|
// 计算0度对应最接近的多圈yaw
|
|
yuntai_no3_yawtarget = roundf(gimbal_fetch_data.gimbal_imu_data.YawTotalAngle / 360.0f) * 360.0f; // 计算最近的多圈yaw
|
|
laser_off();
|
|
timestart_timeline = DWT_GetTimeline_ms(); // 记录开始时间
|
|
break;
|
|
case YUNT_NO3_TURING:
|
|
gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
|
|
float yaw_diff = gimbal_fetch_data.gimbal_imu_data.YawTotalAngle - yuntai_no3_yawtarget; // 计算当前yaw与目标yaw的差值
|
|
float step_size = 0.8f; // 90度每秒, 500hz控制频率下每次0.18度
|
|
if (fabsf(yaw_diff) > step_size) // 如果差值大于180度,则调整到最小角度
|
|
{
|
|
if (yaw_diff > 0)
|
|
gimbal_cmd_send.yaw -= step_size;
|
|
else
|
|
gimbal_cmd_send.yaw += step_size;
|
|
}
|
|
if (fabsf(yaw_diff) <= 5.0f) // 如果差值小于5度,则认为转向完成
|
|
{
|
|
if (Vison_revc_flag)
|
|
{
|
|
YunTaiState = YUNT_NO3_TRACKING; // 进入跟踪状态
|
|
}
|
|
else
|
|
{
|
|
YunTaiState = YUNT_NO3_SEARCHING; // 进入搜索状态
|
|
}
|
|
}
|
|
gimbal_cmd_send.pitch = 0;
|
|
break;
|
|
case YUNT_NO3_TRACKING:
|
|
gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
|
|
if (DWT_GetTimeline_ms() - timestart_timeline >= 3500.0f) // 如果超过3.5秒,则认为跟踪完成
|
|
{
|
|
laser_on();
|
|
}
|
|
else
|
|
{
|
|
if (Vison_revc_flag && Vison_Frequency >= 20.0f) // 如果视觉数据可用且频率大于20Hz
|
|
{
|
|
LIMIT_MIN_MAX(opencv_Vision_data[0], -100.0f, 100.0f);
|
|
LIMIT_MIN_MAX(opencv_Vision_data[1], -100.0f, 100.0f);
|
|
LIMIT_MIN_MAX(opencv_Vision_data[2], 500.0f, 1560.0f);
|
|
yaw_error_kf.MeasuredVector[0] = opencv_Vision_data[0];
|
|
float *yaw_filtered = Kalman_Filter_Update(&yaw_error_kf);
|
|
filtered_yaw_error = yaw_filtered[0];
|
|
pitch_error_kf.MeasuredVector[0] = opencv_Vision_data[1];
|
|
float *pitch_filtered = Kalman_Filter_Update(&pitch_error_kf);
|
|
filtered_pitch_error = pitch_filtered[0];
|
|
|
|
yaw_tracking_pid_output = PIDCalculate(&yaw_tracking_pid, filtered_yaw_error, 0);
|
|
pitch_tracking_pid_output = PIDCalculate(&pitch_tracking_pid, filtered_pitch_error, 0);
|
|
gimbal_cmd_send.yaw -= yaw_tracking_pid_output * mapping(opencv_Vision_data[2], 500.0f, 1560.0f, yaw_control_kxmax, yaw_control_kxmin); // yaw角度控制
|
|
gimbal_cmd_send.pitch += 0.001f * pitch_tracking_pid_output;
|
|
}
|
|
else
|
|
{
|
|
YunTaiState = YUNT_NO3_SEARCHING; // 如果视觉数据不可用,则进入搜索状态
|
|
}
|
|
}
|
|
|
|
break;
|
|
case YUNT_NO3_SEARCHING:
|
|
gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
|
|
if (yuntai_no3_searching_dir == 0) // 如果当前是向左搜索
|
|
{
|
|
gimbal_cmd_send.yaw += yuntai_no3_searching_step; // 向左搜索
|
|
if (gimbal_cmd_send.yaw > yuntai_no3_searching_yaw_max + yuntai_no3_yawtarget) // 如果超过最小值,则切换方向
|
|
{
|
|
yuntai_no3_searching_dir = 1; // 切换到向右搜索
|
|
}
|
|
}
|
|
else // 当前是向右搜索
|
|
{
|
|
gimbal_cmd_send.yaw -= yuntai_no3_searching_step; // 向右搜索
|
|
if (gimbal_cmd_send.yaw < yuntai_no3_searching_yaw_min + yuntai_no3_yawtarget) // 如果超过最大值,则切换方向
|
|
{
|
|
yuntai_no3_searching_dir = 0; // 切换到向左搜索
|
|
}
|
|
}
|
|
|
|
gimbal_cmd_send.pitch = 0.0f; // 保持pitch角度为0
|
|
if (Vison_revc_flag && Vison_Frequency >= 20.0f)
|
|
{
|
|
yuntai_no3_searching_count--;
|
|
}
|
|
else
|
|
{
|
|
yuntai_no3_searching_count = 100; // 如果视觉数据可用但频率低于20Hz,则重置计数器
|
|
}
|
|
if (yuntai_no3_searching_count <= 0)
|
|
{
|
|
YunTaiState = YUNT_NO3_TRACKING; // 如果视觉数据可用,则进入跟踪状态
|
|
}
|
|
break;
|
|
case YUNT_NO4_1_WAITING:
|
|
gimbal_cmd_send.gimbal_mode = GIMBAL_ZERO_FORCE;
|
|
gimbal_cmd_send.yaw = gimbal_fetch_data.gimbal_imu_data.YawTotalAngle;
|
|
gimbal_cmd_send.pitch = gimbal_fetch_data.gimbal_imu_data.Pitch;
|
|
// 计算0度对应最接近的多圈yaw
|
|
yuntai_no3_yawtarget = roundf(gimbal_fetch_data.gimbal_imu_data.YawTotalAngle / 360.0f) * 360.0f; // 计算最近的多圈yaw
|
|
no4_turning_count = 0; // 题号4转向计数器
|
|
no4_turning_status = 0; // 题号4转向状态
|
|
timestart_timeline = DWT_GetTimeline_ms(); // 记录开始时间
|
|
laser_off();
|
|
break;
|
|
case YUNT_NO4_1_TURING:
|
|
gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
|
|
gimbal_cmd_send.yaw = yuntai_no3_yawtarget - 45.0f;
|
|
gimbal_cmd_send.pitch = 0.0f; // 保持pitch角度为0
|
|
if (fabsf(gimbal_fetch_data.gimbal_imu_data.YawTotalAngle - yuntai_no3_yawtarget + 45.0f) < 5.0f)
|
|
{
|
|
YunTaiState = YUNT_NO4_1_TRACKING; // 进入跟踪状态
|
|
}
|
|
break;
|
|
case YUNT_NO4_1_TRACKING:
|
|
gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
|
|
if (Vison_revc_flag)
|
|
{
|
|
LIMIT_MIN_MAX(opencv_Vision_data[0], -100.0f, 100.0f);
|
|
LIMIT_MIN_MAX(opencv_Vision_data[1], -100.0f, 100.0f);
|
|
LIMIT_MIN_MAX(opencv_Vision_data[2], 500.0f, 1560.0f);
|
|
yaw_error_kf.MeasuredVector[0] = opencv_Vision_data[0];
|
|
float *yaw_filtered = Kalman_Filter_Update(&yaw_error_kf);
|
|
filtered_yaw_error = yaw_filtered[0];
|
|
pitch_error_kf.MeasuredVector[0] = opencv_Vision_data[1];
|
|
float *pitch_filtered = Kalman_Filter_Update(&pitch_error_kf);
|
|
filtered_pitch_error = pitch_filtered[0];
|
|
|
|
yaw_tracking_pid_output = PIDCalculate(&yaw_tracking_pid, filtered_yaw_error, 0);
|
|
pitch_tracking_pid_output = PIDCalculate(&pitch_tracking_pid, filtered_pitch_error, 0);
|
|
gimbal_cmd_send.yaw -= yaw_tracking_pid_output * mapping(opencv_Vision_data[2], 500.0f, 1560.0f, yaw_control_kxmax, yaw_control_kxmin); // yaw角度控制
|
|
gimbal_cmd_send.pitch += 0.001f * pitch_tracking_pid_output;
|
|
}
|
|
break;
|
|
case YUNT_NO4_1_TRACKING_RUNNING:
|
|
gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
|
|
if (board_comm_data->rx_float_data[1] >= 600.0f)
|
|
{
|
|
no4_turning_status = 1; // 题号4转向状态
|
|
if (board_comm_data->rx_float_data[0] >= 0.0f && board_comm_data->rx_float_data[0] <= 12.5f)
|
|
{
|
|
no4_turning_count = 1;
|
|
}
|
|
else if (board_comm_data->rx_float_data[0] > 12.5f && board_comm_data->rx_float_data[0] <= 37.5f)
|
|
{
|
|
no4_turning_count = 2;
|
|
}
|
|
else if (board_comm_data->rx_float_data[0] > 37.5f && board_comm_data->rx_float_data[0] <= 62.5f)
|
|
{
|
|
no4_turning_count = 3;
|
|
}
|
|
else if (board_comm_data->rx_float_data[0] > 62.5f && board_comm_data->rx_float_data[0] <= 87.5f)
|
|
{
|
|
no4_turning_count = 4;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
no4_turning_status = 0; // 题号4转向状态
|
|
}
|
|
if (Vison_revc_flag)
|
|
{
|
|
LIMIT_MIN_MAX(opencv_Vision_data[0], -100.0f, 100.0f);
|
|
LIMIT_MIN_MAX(opencv_Vision_data[1], -100.0f, 100.0f);
|
|
LIMIT_MIN_MAX(opencv_Vision_data[2], 500.0f, 1560.0f);
|
|
yaw_error_kf.MeasuredVector[0] = opencv_Vision_data[0];
|
|
float *yaw_filtered = Kalman_Filter_Update(&yaw_error_kf);
|
|
filtered_yaw_error = yaw_filtered[0];
|
|
pitch_error_kf.MeasuredVector[0] = opencv_Vision_data[1];
|
|
float *pitch_filtered = Kalman_Filter_Update(&pitch_error_kf);
|
|
filtered_pitch_error = pitch_filtered[0];
|
|
yaw_tracking_pid_output = PIDCalculate(&yaw_tracking_pid, filtered_yaw_error, 0);
|
|
pitch_tracking_pid_output = PIDCalculate(&pitch_tracking_pid, filtered_pitch_error, 0);
|
|
gimbal_cmd_send.pitch += 0.001f * pitch_tracking_pid_output;
|
|
if (no4_turning_status == 1 && (no4_turning_count >=1 && no4_turning_count <=4)) // 如果题号4转向状态为1且转向计数器为1到4
|
|
{
|
|
yaw_tracking_pid.Kp = 3.0f; // 关闭yaw比例
|
|
yaw_tracking_pid.Ki = 0.0f; // 关闭yaw积分
|
|
yaw_tracking_pid.Iout = 0.0f; // 清零yaw积分输出
|
|
gimbal_cmd_send.yaw -= yaw_tracking_pid_output * mapping(opencv_Vision_data[2], 500.0f, 1560.0f, yaw_control_kxmax, yaw_control_kxmin); // yaw角度控制
|
|
}
|
|
else
|
|
{
|
|
yaw_tracking_pid.Kp = 2.0f; // 关闭yaw比例
|
|
yaw_tracking_pid.Ki = 4.0f;
|
|
gimbal_cmd_send.yaw -= yaw_tracking_pid_output * mapping(opencv_Vision_data[2], 500.0f, 1560.0f, yaw_control_kxmax, yaw_control_kxmin); // yaw角度控制
|
|
}
|
|
laser_on();
|
|
}
|
|
break;
|
|
case YUNT_NO4_3_WAITING:
|
|
gimbal_cmd_send.gimbal_mode = GIMBAL_ZERO_FORCE;
|
|
gimbal_cmd_send.yaw = gimbal_fetch_data.gimbal_imu_data.YawTotalAngle;
|
|
gimbal_cmd_send.pitch = gimbal_fetch_data.gimbal_imu_data.Pitch;
|
|
no4_turning_count = 0; // 题号4转向计数器
|
|
no4_turning_status = 0; // 题号4转向状态
|
|
|
|
// 计算0度对应最接近的多圈yaw
|
|
yuntai_no3_yawtarget = roundf(gimbal_fetch_data.gimbal_imu_data.YawTotalAngle / 360.0f) * 360.0f; // 计算最近的多圈yaw
|
|
timestart_timeline = DWT_GetTimeline_ms(); // 记录开始时间
|
|
laser_off();
|
|
break;
|
|
case YUNT_NO4_3_TURING:
|
|
gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
|
|
gimbal_cmd_send.yaw = yuntai_no3_yawtarget - 45.0f;
|
|
gimbal_cmd_send.pitch = 0.0f; // 保持pitch角度为0
|
|
if (fabsf(gimbal_fetch_data.gimbal_imu_data.YawTotalAngle - yuntai_no3_yawtarget + 45.0f) < 5.0f)
|
|
{
|
|
YunTaiState = YUNT_NO4_3_TRACKING; // 进入跟踪状态
|
|
}
|
|
break;
|
|
case YUNT_NO4_3_TRACKING:
|
|
gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
|
|
if (Vison_revc_flag)
|
|
{
|
|
LIMIT_MIN_MAX(opencv_Vision_data[0], -100.0f, 100.0f);
|
|
LIMIT_MIN_MAX(opencv_Vision_data[1], -100.0f, 100.0f);
|
|
LIMIT_MIN_MAX(opencv_Vision_data[2], 500.0f, 1560.0f);
|
|
yaw_error_kf.MeasuredVector[0] = opencv_Vision_data[0];
|
|
float *yaw_filtered = Kalman_Filter_Update(&yaw_error_kf);
|
|
filtered_yaw_error = yaw_filtered[0];
|
|
pitch_error_kf.MeasuredVector[0] = opencv_Vision_data[1];
|
|
float *pitch_filtered = Kalman_Filter_Update(&pitch_error_kf);
|
|
filtered_pitch_error = pitch_filtered[0];
|
|
|
|
yaw_tracking_pid_output = PIDCalculate(&yaw_tracking_pid, filtered_yaw_error, 60.0f); // 题号4-3目标error为60
|
|
pitch_tracking_pid_output = PIDCalculate(&pitch_tracking_pid, filtered_pitch_error, 0);
|
|
gimbal_cmd_send.yaw -= yaw_tracking_pid_output * mapping(opencv_Vision_data[2], 500.0f, 1560.0f, yaw_control_kxmax, yaw_control_kxmin); // yaw角度控制
|
|
gimbal_cmd_send.pitch += 0.001f * pitch_tracking_pid_output;
|
|
}
|
|
break;
|
|
case YUNT_NO4_3_TRACKING_RUNNING:
|
|
gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
|
|
if (board_comm_data->rx_float_data[1] >= 600.0f)
|
|
{
|
|
no4_turning_status = 1; // 题号4转向状态
|
|
if (board_comm_data->rx_float_data[0] >= 0.0f && board_comm_data->rx_float_data[0] <= 12.5f)
|
|
{
|
|
no4_turning_count = 1;
|
|
}
|
|
else if (board_comm_data->rx_float_data[0] > 12.5f && board_comm_data->rx_float_data[0] <= 37.5f)
|
|
{
|
|
no4_turning_count = 2;
|
|
}
|
|
else if (board_comm_data->rx_float_data[0] > 37.5f && board_comm_data->rx_float_data[0] <= 62.5f)
|
|
{
|
|
no4_turning_count = 3;
|
|
}
|
|
else if (board_comm_data->rx_float_data[0] > 62.5f && board_comm_data->rx_float_data[0] <= 87.5f)
|
|
{
|
|
no4_turning_count = 4;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
no4_turning_status = 0; // 题号4转向状态
|
|
}
|
|
if (Vison_revc_flag)
|
|
{
|
|
LIMIT_MIN_MAX(opencv_Vision_data[0], -100.0f, 100.0f);
|
|
LIMIT_MIN_MAX(opencv_Vision_data[1], -100.0f, 100.0f);
|
|
LIMIT_MIN_MAX(opencv_Vision_data[2], 500.0f, 1560.0f);
|
|
yaw_error_kf.MeasuredVector[0] = opencv_Vision_data[0];
|
|
float *yaw_filtered = Kalman_Filter_Update(&yaw_error_kf);
|
|
filtered_yaw_error = yaw_filtered[0];
|
|
pitch_error_kf.MeasuredVector[0] = opencv_Vision_data[1];
|
|
float *pitch_filtered = Kalman_Filter_Update(&pitch_error_kf);
|
|
filtered_pitch_error = pitch_filtered[0];
|
|
// 画圆模式 500hz 16s画完整圆
|
|
circle_progress = board_comm_data->rx_float_data[0] / 100.0f; // 画圆进度,从板间通信数据获取
|
|
if (circle_progress >= 1.0f)
|
|
{
|
|
circle_progress = 1.0f; // 限制在100%
|
|
}
|
|
circle_angle = 2.0f * PI * circle_progress; // 计算当前圆周角度
|
|
target_x = circle_radius * cosf(circle_angle); // 计算圆周上目标点x坐标
|
|
target_y = circle_radius * sinf(circle_angle); // 计算圆周上目标点y坐标
|
|
yaw_tracking_pid_output = PIDCalculate(&yaw_tracking_pid, filtered_yaw_error, target_x);
|
|
pitch_tracking_pid_output = PIDCalculate(&pitch_tracking_pid, filtered_pitch_error, target_y);
|
|
gimbal_cmd_send.pitch += pitch_tracking_pid_output * mapping(opencv_Vision_data[2], 500.0f, 1560.0f, pitch_control_kxmax, pitch_control_kxmin); // pitch角度控制
|
|
if (no4_turning_status == 1 && (no4_turning_count == 2 || no4_turning_count == 3)) // 如果题号4转向状态为1且转向计数器为2或3
|
|
{
|
|
yaw_tracking_pid.Kp = 3.0f; // 关闭yaw比例
|
|
yaw_tracking_pid.Ki = 0.0f; // 关闭yaw积分
|
|
yaw_tracking_pid.Iout = 0.0f; // 清零yaw积分输出
|
|
gimbal_cmd_send.yaw -= yaw_tracking_pid_output * mapping(opencv_Vision_data[2], 500.0f, 1560.0f, yaw_control_kxmax, yaw_control_kxmin); // yaw角度控制
|
|
}
|
|
else
|
|
{
|
|
yaw_tracking_pid.Kp = 1.5f; // 关闭yaw比例
|
|
yaw_tracking_pid.Ki = 3.0f;
|
|
gimbal_cmd_send.yaw -= yaw_tracking_pid_output * mapping(opencv_Vision_data[2], 500.0f, 1560.0f, yaw_control_kxmax, yaw_control_kxmin); // yaw角度控制
|
|
}
|
|
}
|
|
laser_on();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief 紧急停止,包括遥控器左上侧拨轮打满/重要模块离线/双板通信失效等
|
|
* 停止的阈值'300'待修改成合适的值,或改为开关控制.
|
|
*
|
|
* @todo 后续修改为遥控器离线则电机停止(关闭遥控器急停),通过给遥控器模块添加daemon实现
|
|
*
|
|
*/
|
|
static void EmergencyHandler()
|
|
{
|
|
// 拨轮的向下拨超过一半进入急停模式.注意向打时下拨轮是正
|
|
if (switch_is_down(rc_data[TEMP].rc.switch_right) || robot_state == ROBOT_STOP) // 还需添加重要应用和模块离线的判断
|
|
{
|
|
robot_state = ROBOT_STOP;
|
|
gimbal_cmd_send.gimbal_mode = GIMBAL_ZERO_FORCE;
|
|
chassis_cmd_send.chassis_mode = CHASSIS_ZERO_FORCE;
|
|
gimbal_cmd_send.yaw = gimbal_fetch_data.gimbal_imu_data.YawTotalAngle;
|
|
gimbal_cmd_send.pitch = gimbal_fetch_data.gimbal_imu_data.Pitch;
|
|
LOGERROR("[CMD] emergency stop!");
|
|
}
|
|
// 遥控器右侧开关为[上],恢复正常运行
|
|
if (!switch_is_down(rc_data[TEMP].rc.switch_right) && robot_state == ROBOT_STOP)
|
|
{
|
|
robot_state = ROBOT_READY;
|
|
LOGINFO("[CMD] reinstate, robot ready");
|
|
}
|
|
}
|
|
|
|
/* 机器人核心控制任务,200Hz频率运行(必须高于视觉发送频率) */
|
|
void RobotCMDTask()
|
|
{
|
|
// 从其他应用获取回传数据
|
|
#ifdef ONE_BOARD
|
|
SubGetMessage(chassis_feed_sub, (void *)&chassis_fetch_data);
|
|
#endif // ONE_BOARD
|
|
#ifdef GIMBAL_BOARD
|
|
chassis_fetch_data = *(Chassis_Upload_Data_s *)CANCommGet(cmd_can_comm);
|
|
#endif // GIMBAL_BOARD
|
|
SubGetMessage(shoot_feed_sub, &shoot_fetch_data);
|
|
SubGetMessage(gimbal_feed_sub, &gimbal_fetch_data);
|
|
// 根据遥控器左侧开关,确定当前使用的控制模式为遥控器调试还是键鼠
|
|
if (switch_is_mid(rc_data[TEMP].rc.switch_right))
|
|
{
|
|
RemoteControlSet();
|
|
}
|
|
else if (switch_is_up(rc_data[TEMP].rc.switch_right) || !RemoteControlIsOnline())
|
|
{
|
|
AutoSet();
|
|
}
|
|
|
|
// AutoSet();
|
|
|
|
EmergencyHandler(); // 处理模块离线和遥控器急停等紧急情况
|
|
#ifdef ONE_BOARD
|
|
PubPushMessage(chassis_cmd_pub, (void *)&chassis_cmd_send);
|
|
#endif // ONE_BOARD
|
|
#ifdef GIMBAL_BOARD
|
|
CANCommSend(cmd_can_comm, (void *)&chassis_cmd_send);
|
|
#endif // GIMBAL_BOARD
|
|
PubPushMessage(shoot_cmd_pub, (void *)&shoot_cmd_send);
|
|
PubPushMessage(gimbal_cmd_pub, (void *)&gimbal_cmd_send);
|
|
}
|