Files
bf_original_balance_chassis/application/chassis/balance.c
2026-07-15 13:40:54 +08:00

596 lines
19 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// app
#include "balance.h"
#include "linkNleg.h"
#include "robot_def.h"
#include "general_def.h"
#include "ins_task.h"
// #include "HT04.h"
#include "dmmotor.h"
#include "LK9025.h"
#include "controller.h"
#include "can_comm.h"
#include "super_cap.h"
#include "user_lib.h"
#include "remote_control.h"
#include "referee_task.h"
#include "stdint.h"
#include "arm_math.h" // 需要用到较多三角函数
#include "bsp_dwt.h"
#include "bsp_log.h"
#include "lqr_calc.h"
#include "speed_estimation.h"
#include "fly_detection.h"
#include "buzzer.h"
#include "math.h"
#include "string.h"
#define STOOL_SPEED_REF 1.5f
#define STOOL_YAW_RATE_REF 3.5f
#define STOOL_ACC_REF 4.0f
#define STOOL_BRAKE_ACC_REF 8.0f
#define STOOL_PITCH_K 36.0f
#define STOOL_PITCH_W_K 10.0f
#define STOOL_VEL_K 6.0f
#define STOOL_STOP_DIST_K 3.0f
#define STOOL_VEL_LPF_RC 0.03f
#define STOOL_WHEEL_TORQUE_LIMIT 25.0f
#define STOOL_RC_DEADBAND 40
#define CHASSIS_UNDERVOLTAGE_LIMIT 15.0f
#define REFEREE_VOLTAGE_VALID_MIN 5.0f
// 计时变量
static uint32_t balance_dwt_cnt;
static float del_t;
// 底盘拥有的实例模块
static INS_t *Chassis_IMU_data;
static RC_ctrl_t *rc_data; // 底盘单独调试用
static Chassis_Ctrl_Cmd_s chassis_cmd_recv;
// 四个关节电机和两个驱动轮电机
static DMMotorInstance *lf, *lb, *rf, *rb, *joint[4]; // 指针数组方便传参和调试
static LKMotorInstance *l_driven, *r_driven, *driven[2];
// 两个腿的参数,0为左腿,1为右腿
static LinkNPodParam l_side, r_side;
static ChassisParam chassis;
// 综合运动补偿的PID控制器
static PIDInstance leglen_pid_l, leglen_pid_r; // 用PD模拟弹簧, 不要积分(弹簧是无积分二阶系统), 增益不可过大否则抗外界冲击响应时太"硬"
static PIDInstance roll_compensate_pid; // roll轴补偿,用于保持机体水平
static PIDInstance steer_p_pid, steer_v_pid; // 转向PID,有转向指令时使用IMU的加速度反馈积分以获取速度和位置状态量
static PIDInstance anti_crash_pid; // 抗劈叉,将输出以相反的方向叠加到左右腿的上
// 底盘状态
static Robot_Status_e chassis_status;
static referee_info_t *referee_data; // 用于获取裁判系统的数据
static Referee_Interactive_info_t ui_data; // UI数据将底盘中的数据传入此结构体的对应变量中UI会自动检测是否变化对应显示UI
static SuperCapInstance *cap; // 超级电容
static uint8_t stool_drive_key_pressed;
static float stool_vel;
static float stool_dist;
static float stool_stop_dist;
static float stool_target_wz;
static chassis_mode_e last_chassis_mode = CHASSIS_ZERO_FORCE;
static void ClearMotionTargets(void)
{
chassis.target_v = 0.0f;
chassis.target_dist = chassis.dist;
stool_target_wz = 0.0f;
}
static void ClearControlOutput(void)
{
l_side.F_leg = 0.0f;
l_side.T_hip = 0.0f;
l_side.T_back = 0.0f;
l_side.T_front = 0.0f;
l_side.T_wheel = 0.0f;
r_side.F_leg = 0.0f;
r_side.T_hip = 0.0f;
r_side.T_back = 0.0f;
r_side.T_front = 0.0f;
r_side.T_wheel = 0.0f;
}
static void ClearJointOutput(void)
{
l_side.F_leg = 0.0f;
l_side.T_hip = 0.0f;
l_side.T_back = 0.0f;
l_side.T_front = 0.0f;
r_side.F_leg = 0.0f;
r_side.T_hip = 0.0f;
r_side.T_back = 0.0f;
r_side.T_front = 0.0f;
}
static void ApproachFloat(float *value, float target, float step)
{
if (*value < target)
{
*value += step;
if (*value > target)
*value = target;
}
else if (*value > target)
{
*value -= step;
if (*value < target)
*value = target;
}
}
static float RCChannelToRatio(int16_t ch)
{
if (ch > -STOOL_RC_DEADBAND && ch < STOOL_RC_DEADBAND)
return 0.0f;
return float_constrain((float)ch / 660.0f, -1.0f, 1.0f);
}
static void ResetStoolRuntime(void)
{
stool_drive_key_pressed = 0;
stool_vel = 0.0f;
stool_dist = chassis.dist;
stool_stop_dist = stool_dist;
ClearMotionTargets();
ClearControlOutput();
}
static void DisableAllJointMotor(void)
{
for (uint8_t i = 0; i < JOINT_CNT; i++)
{
DMMotorSetRef(joint[i], 0.0f);
DMMotorOuterLoop(joint[i], OPEN_LOOP);
DMMotorStop(joint[i]);
}
}
static void RefreshJointMotorDisable(void)
{
DisableAllJointMotor();
}
static void EnableDrivenMotor(void)
{
for (uint8_t i = 0; i < DRIVEN_CNT; i++)
LKMotorEnable(driven[i]);
}
static void StopDrivenMotor(void)
{
for (uint8_t i = 0; i < DRIVEN_CNT; i++)
{
LKMotorSetRef(driven[i], 0.0f);
LKMotorStop(driven[i]);
}
}
void BalanceInit()
{
rc_data = RemoteControlInit(&huart3);
Chassis_IMU_data = INS_Init();
referee_data = UITaskInit(&huart6, &ui_data); // 裁判系统初始化,会同时初始化UI
SuperCap_Init_Config_s cap_conf = {
.can_config = {
.can_handle = &hcan2,
.tx_id = 0x302, // 超级电容默认接收id
.rx_id = 0x301, // 超级电容默认发送id,注意tx和rx在其他人看来是反的
}};
cap = SuperCapInit(&cap_conf); // ww超级电容初始化
// 关节电机
Motor_Init_Config_s joint_conf = {
// 写一个,剩下的修改方向和id即可
.can_init_config = {
.can_handle = &hcan1},
.controller_param_init_config = {
.angle_PID = {
.Kp = 0.1,
.Kd = 0,
.Ki = 0,
.DeadBand = 0.0001,
.Improve = PID_DerivativeFilter | PID_Derivative_On_Measurement,
.MaxOut = 4,
.Derivative_LPF_RC = 0.05,
}, // 仅用于复位腿
},
.controller_setting_init_config = {
.close_loop_type = ANGLE_LOOP,
.outer_loop_type = OPEN_LOOP,
.motor_reverse_flag = FEEDBACK_DIRECTION_NORMAL,
.angle_feedback_source = MOTOR_FEED,
.speed_feedback_source = MOTOR_FEED,
},
.motor_type = DM8009P};
joint_conf.can_init_config.tx_id = 3;
joint_conf.can_init_config.rx_id = 14;
joint[LF] = lf = DMMotorInit(&joint_conf);
joint_conf.can_init_config.tx_id = 2;
joint_conf.can_init_config.rx_id = 13;
joint[LB] = lb = DMMotorInit(&joint_conf);
joint_conf.can_init_config.tx_id = 1;
joint_conf.can_init_config.rx_id = 12;
joint[RF] = rf = DMMotorInit(&joint_conf);
joint_conf.can_init_config.tx_id = 0;
joint_conf.can_init_config.rx_id = 11;
joint[RB] = rb = DMMotorInit(&joint_conf);
// 驱动轮电机
Motor_Init_Config_s driven_conf = {
// 写一个,剩下的修改方向和id即可
.can_init_config.can_handle = &hcan2,
.controller_setting_init_config = {
.angle_feedback_source = MOTOR_FEED,
.speed_feedback_source = MOTOR_FEED,
.outer_loop_type = OPEN_LOOP,
.close_loop_type = OPEN_LOOP,
.motor_reverse_flag = MOTOR_DIRECTION_NORMAL,
},
.motor_type = LK9025,
};
driven_conf.can_init_config.tx_id = 1;
driven[RD] = r_driven = LKMotorInit(&driven_conf);
driven_conf.can_init_config.tx_id = 2;
driven[LD] = l_driven = LKMotorInit(&driven_conf);
// 腿长控制
PID_Init_Config_s leg_length_pid_conf = {
.Kp = 1200,
.Kd = 300,
.Ki = 0,
.MaxOut = 60,
.DeadBand = 0.0001f,
.Improve = PID_ChangingIntegrationRate | PID_Trapezoid_Intergral | PID_DerivativeFilter | PID_Derivative_On_Measurement,
.Derivative_LPF_RC = 0.05,
};
PIDInit(&leglen_pid_l, &leg_length_pid_conf);
PIDInit(&leglen_pid_r, &leg_length_pid_conf);
// roll轴补偿
PID_Init_Config_s roll_compensate_pid_conf = {
.Kp = 0.0008f,
.Kd = 0.0002f,
.Ki = 0.0f,
.MaxOut = 0.05,
.DeadBand = 0.001f,
.Improve = PID_DerivativeFilter | PID_Derivative_On_Measurement,
.Derivative_LPF_RC = 0.05,
};
PIDInit(&roll_compensate_pid, &roll_compensate_pid_conf);
// 航向控制
// 角度环
PID_Init_Config_s steer_p_pid_conf = {
.Kp = 5,
.Kd = 0,
.Ki = 0.0f,
.MaxOut = 3,
.DeadBand = 0.001f,
.Improve = PID_DerivativeFilter | PID_Derivative_On_Measurement,
.Derivative_LPF_RC = 0.05,
};
PIDInit(&steer_p_pid, &steer_p_pid_conf);
// 速度环
PID_Init_Config_s steer_v_pid_conf = {
.Kp = 3,
.Kd = 0.0f,
.Ki = 0.0f,
.MaxOut = 20,
.DeadBand = 0.0f,
.Improve = PID_DerivativeFilter | PID_Derivative_On_Measurement,
.Derivative_LPF_RC = 0.05,
};
PIDInit(&steer_v_pid, &steer_v_pid_conf);
// 抗劈叉
PID_Init_Config_s anti_crash_pid_conf = {
.Kp = 15,
.Kd = 2,
.Ki = 0.0,
.MaxOut = 30,
.DeadBand = 0.001f,
.Improve = PID_DerivativeFilter | PID_ChangingIntegrationRate | PID_Integral_Limit,
.Derivative_LPF_RC = 0.01,
};
PIDInit(&anti_crash_pid, &anti_crash_pid_conf);
// 状态初始化
l_side.target_len = r_side.target_len = 0.12;
chassis.vel_cov = 100; // 速度协方差初始化
chassis_cmd_recv.chassis_mode = CHASSIS_ZERO_FORCE;
chassis_status = ROBOT_STOP;
ResetStoolRuntime();
DisableAllJointMotor();
for (uint8_t i = 0; i < JOINT_CNT; i++)
DMMotorSetMode(DM_CMD_RESET_MODE, joint[i]);
StopDrivenMotor();
DWT_GetDeltaT(&balance_dwt_cnt);
}
// 检查驱动轮电机是否离线
static uint8_t DrivenMotorIsLost()
{
for (uint8_t i = 0; i < DRIVEN_CNT; i++)
{
if (driven[i]->daemon->temp_count == 0)
return 1;
}
return 0;
}
/* Chassis-board local RC control. Right switch down is hard emergency stop. */
static void ControlSwitch()
{
memset(&chassis_cmd_recv, 0, sizeof(chassis_cmd_recv));
chassis_cmd_recv.chassis_mode = CHASSIS_ZERO_FORCE;
chassis_cmd_recv.direction = CHASSIS_ALIGN;
chassis_cmd_recv.friction_mode = FRICTION_OFF;
chassis_cmd_recv.loader_mode = LOAD_STOP;
chassis_cmd_recv.vision_mode = UNLOCK;
chassis_cmd_recv.ui_mode = UI_KEEP;
if (!RemoteControlIsOnline())
{
return;
}
if (switch_is_down(rc_data[TEMP].rc.switch_right))
return;
float chassis_vol = referee_data->PowerHeatData.chassis_voltage * 0.001f;
if (chassis_vol > REFEREE_VOLTAGE_VALID_MIN &&
chassis_vol < CHASSIS_UNDERVOLTAGE_LIMIT)
return;
if (DrivenMotorIsLost())
return;
chassis_cmd_recv.chassis_mode = CHASSIS_STOOL_MODE;
chassis_cmd_recv.vx = RCChannelToRatio(rc_data[TEMP].rc.rocker_r1) * STOOL_SPEED_REF;
chassis_cmd_recv.offset_angle = -RCChannelToRatio(rc_data[TEMP].rc.rocker_r_) * STOOL_YAW_RATE_REF;
}
/* Joint motors must remain disabled on this demo chassis. */
static void ResetChassis()
{
ClearMotionTargets();
ClearControlOutput();
l_side.target_len = r_side.target_len = 0.12f;
chassis.dist = chassis.target_dist = 0.0f;
chassis_status = ROBOT_STOP;
RefreshJointMotorDisable();
StopDrivenMotor();
}
// 工作状态设定
static void WokingStateSet()
{
RefreshJointMotorDisable();
if (last_chassis_mode != chassis_cmd_recv.chassis_mode)
{
if (chassis_cmd_recv.chassis_mode == CHASSIS_STOOL_MODE)
ResetStoolRuntime();
else
ClearControlOutput();
last_chassis_mode = chassis_cmd_recv.chassis_mode;
}
if (chassis_cmd_recv.chassis_mode == CHASSIS_RESET ||
chassis_cmd_recv.chassis_mode == CHASSIS_ZERO_FORCE)
{
ResetChassis();
return;
}
if (chassis_cmd_recv.chassis_mode != CHASSIS_STOOL_MODE)
{
ResetChassis();
return;
}
EnableDrivenMotor();
chassis_status = ROBOT_READY;
l_side.target_len = r_side.target_len = 0.12f;
stool_target_wz = chassis_cmd_recv.offset_angle;
if (fabsf(chassis_cmd_recv.vx) > 0.001f)
{
ApproachFloat(&chassis.target_v, chassis_cmd_recv.vx, STOOL_ACC_REF * del_t);
stool_stop_dist = stool_dist;
stool_drive_key_pressed = 1;
}
else
{
if (stool_drive_key_pressed)
stool_stop_dist = stool_dist;
ApproachFloat(&chassis.target_v, 0.0f, STOOL_BRAKE_ACC_REF * del_t);
stool_drive_key_pressed = 0;
}
VAL_LIMIT(chassis.target_v, -STOOL_SPEED_REF, STOOL_SPEED_REF);
chassis.target_dist = chassis.dist;
}
/**
* @brief 将电机和imu的数据组装为LinkNPodParam结构体和chassisParam结构体
*
* @note HT04电机上电的编码器位置为零(校准过),请看Link2Pod()的note,以及HT04.c中的电机解码部分
* @note 海泰04电机顺时针旋转为正; LK9025电机逆时针旋转为正,此处皆需要转换为模型中给定的正方向
*
*/
static void ParamAssemble()
{
// 机体参数,视为平面刚体
chassis.pitch = Chassis_IMU_data->Pitch * DEGREE_2_RAD;
chassis.pitch_w = Chassis_IMU_data->Gyro[0];
chassis.yaw = Chassis_IMU_data->YawTotalAngle * DEGREE_2_RAD;
chassis.wz = Chassis_IMU_data->Gyro[2];
chassis.roll = Chassis_IMU_data->Roll * DEGREE_2_RAD;
chassis.roll_w = Chassis_IMU_data->Gyro[1];
// HT04电机的角度是顺时针为正,LK9025电机的角度是逆时针为正
l_side.phi1 = PI + LIMIT_LINK_RAD - lb->measure.total_round;
l_side.phi1_w = -lb->measure.velocity;// 注意速度的正负,HT04电机顺时针旋转为正,而模型中左腿前关节顺时针旋转为负 原本是speed_rads
l_side.phi4 = -lf->measure.total_round - LIMIT_LINK_RAD;
l_side.phi4_w = -lf->measure.velocity;
l_side.w_ecd = l_driven->measure.speed_rads;
r_side.phi1 = PI + LIMIT_LINK_RAD + rb->measure.total_round;
r_side.phi1_w = rb->measure.velocity;
r_side.phi4 = rf->measure.total_round - LIMIT_LINK_RAD;
r_side.phi4_w = rf->measure.velocity;
r_side.w_ecd = -r_driven->measure.speed_rads;
}
static void StoolModeBalanceControl(void)
{
float raw_vel = WHEEL_RADIUS * (l_side.w_ecd + r_side.w_ecd) * 0.5f;
float vel_lpf_alpha = del_t / (STOOL_VEL_LPF_RC + del_t);
VAL_LIMIT(vel_lpf_alpha, 0.0f, 1.0f);
stool_vel += vel_lpf_alpha * (raw_vel - stool_vel);
stool_dist += stool_vel * del_t;
if (fabsf(chassis.target_v) > 0.001f)
stool_stop_dist = stool_dist;
float stop_dist_error = stool_dist - stool_stop_dist;
float wheel_torque = -STOOL_PITCH_K * chassis.pitch -
STOOL_PITCH_W_K * chassis.pitch_w +
STOOL_VEL_K * (stool_vel - chassis.target_v) +
STOOL_STOP_DIST_K * stop_dist_error;
VAL_LIMIT(wheel_torque, -STOOL_WHEEL_TORQUE_LIMIT, STOOL_WHEEL_TORQUE_LIMIT);
l_side.T_wheel = wheel_torque;
r_side.T_wheel = wheel_torque;
}
static void SynthesizeMotion() /* 腿部控制:抗劈叉; : */
{
if (chassis_cmd_recv.chassis_mode == CHASSIS_STOOL_MODE)
{
PIDCalculate(&steer_v_pid, chassis.wz, stool_target_wz);
l_side.T_wheel -= steer_v_pid.Output;
r_side.T_wheel += steer_v_pid.Output;
return;
}
if (chassis_cmd_recv.chassis_mode == CHASSIS_FREE_DEBUG ||
chassis_cmd_recv.chassis_mode == CHASSIS_FOLLOW_GIMBAL_YAW) // 底盘跟随
{
float p_ref = PIDCalculate(&steer_p_pid, chassis.yaw, chassis.target_yaw);
PIDCalculate(&steer_v_pid, chassis.wz, p_ref);
}
else if (chassis_cmd_recv.chassis_mode == CHASSIS_ROTATE) // 小陀螺
{
PIDCalculate(&steer_v_pid, chassis.wz, (float)chassis_cmd_recv.rotate_w);
}
else if (chassis_cmd_recv.chassis_mode == CHASSIS_ROTATE_REVERSE)
{
PIDCalculate(&steer_v_pid, chassis.wz, (float)chassis_cmd_recv.rotate_w);
}
l_side.T_wheel -= steer_v_pid.Output;
r_side.T_wheel += steer_v_pid.Output;
// 抗劈叉
static float swerving_speed_ff, ff_coef = 3;
swerving_speed_ff = ff_coef * steer_v_pid.Output; // 用于抗劈叉的前馈
PIDCalculate(&anti_crash_pid, l_side.phi5 - r_side.phi5, 0);
l_side.T_hip += anti_crash_pid.Output - swerving_speed_ff;
r_side.T_hip -= anti_crash_pid.Output - swerving_speed_ff;
}
static void LegControl() /* 腿长控制和Roll补偿 */
{
PIDCalculate(&roll_compensate_pid, chassis.roll, 0);
l_side.target_len += roll_compensate_pid.Output;
r_side.target_len -= roll_compensate_pid.Output;
static float gravity_ff = 60;
static float roll_extra_comp_p = 400;
float roll_comp = roll_extra_comp_p * chassis.roll;
l_side.F_leg = PIDCalculate(&leglen_pid_l, l_side.height, l_side.target_len) + gravity_ff - roll_comp;
r_side.F_leg = PIDCalculate(&leglen_pid_r, r_side.height, r_side.target_len) + gravity_ff + roll_comp;
}
static void WattLimitSet() /* 设定运动模态的输出 */
{
RefreshJointMotorDisable();
LKMotorSetRef(l_driven, 195.3125 * l_side.T_wheel);
LKMotorSetRef(r_driven, 195.3125 * -r_side.T_wheel);
}
// 裁判系统,双板通信,电容功率控制等
static void CommNPower()
{
/* 更新ui数据 */
ui_data.direction = chassis_cmd_recv.direction;
ui_data.friction_mode = chassis_cmd_recv.friction_mode;
ui_data.loader_mode = chassis_cmd_recv.loader_mode;
ui_data.chassis_mode = chassis_cmd_recv.chassis_mode;
ui_data.ui_mode = chassis_cmd_recv.ui_mode;
ui_data.vision_mode = chassis_cmd_recv.vision_mode;
memcpy(ui_data.coord, l_side.coord, sizeof(l_side.coord));
}
void BalanceTask()
{
del_t = DWT_GetDeltaT(&balance_dwt_cnt);
BuzzerOn();
// 切换遥控器控制or云台板控制
ControlSwitch();
// 设置目标参数和工作模式
WokingStateSet();
// 裁判系统,双板通信,电容功率控制等
CommNPower();
// 参数组装
ParamAssemble();
if (chassis_status == ROBOT_STOP ||
chassis_cmd_recv.chassis_mode == CHASSIS_RESET ||
chassis_cmd_recv.chassis_mode == CHASSIS_ZERO_FORCE)
return;
if (chassis_cmd_recv.chassis_mode == CHASSIS_STOOL_MODE)
{
StoolModeBalanceControl();
SynthesizeMotion();
ClearJointOutput();
WattLimitSet();
return;
}
// 将五连杆映射成单杆
Link2Leg(&l_side, &chassis);
Link2Leg(&r_side, &chassis);
// 通过卡尔曼滤波估计机体速度
SpeedEstimation(&l_side, &r_side, &chassis, Chassis_IMU_data, del_t);
// 根据单杆计算处的角度和杆长,计算反馈增益
CalcLQR(&l_side, &chassis);
CalcLQR(&r_side, &chassis);
// 转向和抗劈叉
SynthesizeMotion();
// 腿长控制,保持机体水平
LegControl();
// VMC映射成关节输出
VMCProject(&l_side);
VMCProject(&r_side);
// 驱动轮支持力解算
NormalForceSolve(&l_side, Chassis_IMU_data);
NormalForceSolve(&r_side, Chassis_IMU_data);
// 运动模态,电机输出映射和限幅
WattLimitSet();
}