# RMCOD2026_Hero **Repository Path**: cod_-control/rmcod2026_-hero ## Basic Information - **Project Name**: RMCOD2026_Hero - **Description**: 26赛季英雄代码 - **Primary Language**: C - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2025-07-17 - **Last Updated**: 2026-10-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # RMCOD_2026_Hero_the_last ## 介绍 ### 26赛季全向英雄代码 环境依赖 开发工具:Keil V5.38a,VsCode 软件环境:Window11 硬件环境:大疆RoboMaster开发板C型 (STM32F407IGHX) 编译工具:Arm Compiler V6.22,C/C++编译 #### 使用说明 云台的主要开发内容在 CAN_Task.c ,Control_Task.c ,Shoot_Task.c 中 底盘的主要开发内容在 CAN_Task.c ,Control_Task.c ,Power_Ctrl.c 中 #### 大致框架 英雄使用了两个开发板,底盘和云台各有一块用于控制相应的部分,云台上的C板接了DR16,所以云台上的C板作为模式主控通过板间通讯与底盘进行模式通讯 云台上的C板与三个摩擦轮电机,yaw轴pitch轴电机以及底盘上的拨弹盘电机通信,用于控制云台与发射机构 底盘上的C板与的四个驱动轮电机,yaw轴电机和拨弹盘的接近开关通信,用于控制底盘,与接近开关通信(用于拨弹盘角度校准,因为机械设计原因,发射系统使能时拨弹盘需转到特定角度才能保证拨弹盘拨一次打出一发) 滑环中有一条CAN线让底盘与云台C板实现板间CAN通信,此CAN线于云台上接云台C板,在底盘上与一个分CAN板相接,此分CAN板上接了yaw轴电机,底盘C板,拨弹盘电机 云台通过滑环实现云台C板与底盘C板的通讯,控制yaw轴电机和拨弹盘电机。底盘可以通过分CAN板读取yaw轴电机数据,并且将一些裁判系统电管里的数据发给云台 ### Gimbal主要内容 #### 键鼠下的模式 为了保证键鼠模式下两个遥控器的优先级将DR_16和VT_13接收到的数据按优先级传到一个结构体进行分配,还需要一个全是bool变量的按键判断结构体进行按键判断的识别 C语言中bool变量被定义且没有赋值的情况下默认为0,所以按键触发逻辑如下 (按键模式判断时将按键优先级结构体接收到的自定义按键与此键的bool按键识别变量进行一个判断) 代码如下 ``` if(Real_KeyBoard_Info.key.set.V == 1 && KeyBoard_info.V.status == 0) //拨弹盘校准 { //C板接收到你按下了V && V的按键识别bool变量为0 Control_Info->Shoot.Mode = Trigger_Update; //拨弹盘校准模式启动,V的按键识别bool变量为1 KeyBoard_Info.V.Status = 1; } else if(Real_KeyBoard_Info.key.set.V == 0 && KeyBoard_info.V.status == 1) { //C板没有接收到你按下V键 && V的按键识别bool变量为1 KeyBoard_Info.V.Status = 0; // V的按键识别bool变量为0 } ``` 当DR16两边拨到上面或VT13的Switch拨到2时为键鼠模式(DR16的优先级高于VT13) DR16与VT13的优先级判断如下 ``` static void Remote_Control_Mode_Change(Control_Info_Typedef *Control_Info) //遥控器优先级 { if (remote_ctrl.rc_lost != 1) { Gimbal_Mode_Update(Control_Info); } else if (remote_ctrl.rc_lost == 1) { if (VT13_Info.is_lost != 1) { Gimbal_Referee_Mode_Update(Control_Info,&VT13_Info); } else if (VT13_Info.is_lost == 1) { Real_KeyBoard_Info.mouse.x = 0; Real_KeyBoard_Info.mouse.y = 0; Real_KeyBoard_Info.mouse.z = 0; Real_KeyBoard_Info.mouse.press_l = 0; Real_KeyBoard_Info.mouse.press_r = 0; Real_KeyBoard_Info.key.v = 0; } } } ``` 键鼠操作下的模式有(拨弹盘校准,底盘的跟随小陀螺切换,一键掉头,发射系统发射,摩擦轮加减速,自瞄开关,超电开关)(原本想加一个灵敏度大小切换的,但是联盟赛上没用,有点鸡肋) 英雄的键鼠加了一个加减摩擦轮转数的按键是为了防止比赛过程中的不确定因素导致超射速的情况,这里我把三个摩擦轮的平均转数传给底盘C板并在UI上显示出来便于控制 #### 断电后的模式重置 防止断电后重新上电疯车所以断电后进行一次模式重置,同时裁判系统(目前)不会在血量为0时反馈底盘和云台的供电输出为0所以还需要将血量的断电进行一个独立的判断,代码如下 ``` static void Hero_Referee_Poweroutage_Control(Control_Info_Typedef*Control_Info) //断电后重置模式 { if(Control_Info->Steer.Referee_roboat_HP > 0) //血量检测 { Control_Info->Steer.Referee_roboat_HP_detect = 0; } else if (Control_Info->Steer.Referee_roboat_HP == 0) { Control_Info->Steer.Referee_roboat_HP_detect = 1; } if(Control_Info->Steer.Referee_roboat_HP_detect == 0) { if(Control_Info->Steer.Referee_power_chassis_output == 0) //底盘断电 { Control_Info->Steer.Chassis_Mode = 1; } if(Control_Info->Steer.Referee_power_gimbal_output == 0) //云台断电 { Control_Info->Gimbal.Mode = GIMBAL_OFF; Machine_Star_reload = 0; } else if(Control_Info->Steer.Referee_power_gimbal_output == 1) { Control_Info->Gimbal.Mode = CONTROL_NOMAL_CONTROL; } if(Control_Info->Steer.Referee_power_shooter_output == 0 || Control_Info->Shoot.Limit.Trigger_Update_Request == 2) //发射系统断电 { Control_Info->Shoot.Mode = SHOOTER_NORMAL; } } else if(Control_Info->Steer.Referee_roboat_HP_detect == 1) { Control_Info->Steer.Chassis_Mode = 1; Control_Info->Gimbal.Mode = GIMBAL_OFF; Control_Info->Shoot.Mode = SHOOTER_NORMAL; } } ``` #### 云台模式切换时的控制 为了防止不同模式切换包括上电时电机的控制使用bool变量标示了一下状态用于不同模式之间的切换,在模式切换或上电时模式变换的过程将目标值赋一个当前测量值 使用bool变量表示状态时可以有两种用法 一种是表示模式的使能与失能,一种是表示在两种模式之间进行切换时的状态 使用bool变量标示两种模式之间进行切换时的状态就可以很好的进行模式切换时的丝滑过渡 ``` static void Gimbal_Taget_Update(Control_Info_Typedef *Control_Info) //云台不同模式之间切换过渡 { float default_pitch = Control_Info->Gimbal.Measure.Pitch_Angle; // 公共默认动作:当不需要特殊控制时,目标等于当前测量值 float default_yaw = Control_Info->Gimbal.Measure.Yaw_Angle; if (Control_Info->Control_Mode == CONTROL_REMOTE) { if ((Control_Info->Gimbal.Mode == CONTROL_NOMAL_CONTROL) || (Control_Info->Gimbal.Mode == CONTROL_GYR_CONTROL)) { if (Machine_Star_reload == 1) { if (Vision_Transform_Normal == 1) { Control_Info->Gimbal.Target.Pitch_Angle = default_pitch; Control_Info->Gimbal.Target.Yaw_Angle = default_yaw; Vision_Transform_Normal = 0; } if (Vision_Transform_Normal == 0) { Control_Info->Gimbal.Target.Pitch_Angle -= remote_ctrl.rc.ch[1] * 0.000039f + VT13_Info.RC.Channel[1] * 0.000039f + VT13_Info.Mouse.Y* 0.0005f ; Control_Info->Gimbal.Target.Yaw_Angle += -remote_ctrl.rc.ch[0] * 0.0004f - VT13_Info.RC.Channel[0] * 0.0004f - VT13_Info.Mouse.X*0.0004f; } } else // Machine_Star_reload == 0 { Control_Info->Gimbal.Target.Pitch_Angle = default_pitch; Control_Info->Gimbal.Target.Yaw_Angle = default_yaw; Machine_Star_reload = 1; } } else if (Control_Info->Gimbal.Mode == GIMBAL_VISION) { if (Vision_Info.Distance > 0) { Control_Info->Gimbal.Target.Pitch_Angle = Vision_Info.Target_Pitch; Control_Info->Gimbal.Target.Yaw_Angle = -Vision_Info.Target_Yaw; } else { Control_Info->Gimbal.Mode = CONTROL_NOMAL_CONTROL; } Vision_Transform_Normal = 1; } else if (Control_Info->Gimbal.Mode == GIMBAL_OFF) { Control_Info->Gimbal.Target.Pitch_Angle = default_pitch; Control_Info->Gimbal.Target.Yaw_Angle = default_yaw; Machine_Star_reload = 0; } else { // 默认情况也设为目标等于当前位置 Control_Info->Gimbal.Target.Pitch_Angle = default_pitch; Control_Info->Gimbal.Target.Yaw_Angle = default_yaw; } } else { Control_Info->Gimbal.Target.Pitch_Angle = default_pitch; Control_Info->Gimbal.Target.Yaw_Angle = default_yaw; } // 归一化Yaw角至[-180, 180] normalize_angle(&Control_Info->Gimbal.Target.Yaw_Angle); } ``` ### Chassis主要内容 #### 底盘解算 ``` static void Chassis_GYR_Mode_Resolving(Control_Info_Typedef *Control_Info,float W_Velocity,int kx,int ky) //小陀螺模式 { Control_Info->GYR.Vx=( remote_ctrl.rc.ch[2]*arm_cos_f32(Control_Info->GYR.Angle_Gap)-remote_ctrl.rc.ch[3]*arm_sin_f32(Control_Info->GYR.Angle_Gap)); Control_Info->GYR.Vy=( remote_ctrl.rc.ch[2]*arm_sin_f32(Control_Info->GYR.Angle_Gap)+remote_ctrl.rc.ch[3]*arm_cos_f32(Control_Info->GYR.Angle_Gap)); Control_Info->Resolving_Param.speed_vx = Control_Info->GYR.Vx * kx; Control_Info->Resolving_Param.speed_vy = Control_Info->GYR.Vy * ky; Control_Info->Resolving_Param.speed_vw = W_Velocity; } static void Chassis_Follow_Mode_Resolving(Control_Info_Typedef *Control_Info,int kx,int ky) //跟随模式 { if(Control_Info->GYR.Yaw_Angle_180_Difference_Value >=180.f) {Control_Info->GYR.Yaw_Angle_180_Difference_Value +=-360.f;} else if (Control_Info->GYR.Yaw_Angle_180_Difference_Value <-180.f) {Control_Info->GYR.Yaw_Angle_180_Difference_Value += 360.f;} if(Control_Info->GYR.Yaw_Angle_180_Difference_Value >=90.f) {Control_Info->GYR.Yaw_Angle_180_Difference_Value +=-180.f;} else if (Control_Info->GYR.Yaw_Angle_180_Difference_Value <-90.f) {Control_Info->GYR.Yaw_Angle_180_Difference_Value += 180.f;} YAW_FOLLOW_CONTROL[0].Output=PID_Calculate(&YAW_FOLLOW_CONTROL[0],0,Control_Info->GYR.Yaw_Angle_180_Difference_Value); YAW_FOLLOW_Gyro.Output = PID_Calculate(&YAW_FOLLOW_Gyro,50,Control_Info->Measure.Chassis_Gyr_Velocity); Control_Info->Resolving_Param.speed_vw=PID_Calculate(&YAW_FOLLOW_CONTROL[1],YAW_FOLLOW_CONTROL[0].Output,DJI_Yaw_Motor.Data.Velocity); Control_Info->GYR.Vx=( remote_ctrl.rc.ch[2]*Control_Info->GYR.Cos - remote_ctrl.rc.ch[3]*Control_Info->GYR.Sin); Control_Info->GYR.Vy=( remote_ctrl.rc.ch[2]*Control_Info->GYR.Sin + remote_ctrl.rc.ch[3]*Control_Info->GYR.Cos); Control_Info->Resolving_Param.speed_vx=Control_Info->GYR.Vx*kx; Control_Info->Resolving_Param.speed_vy=Control_Info->GYR.Vy*ky; } static void Chassis_Calculating_speed(Control_Info_Typedef *Control_Info) //底盘解算 { if(Control_Info->GYR.FOLLOW_Bevel_Angle == 0) { Control_Info->GYR.Follow_Starting_Angle = 95; } else if(Control_Info->GYR.FOLLOW_Bevel_Angle == 1) { Control_Info->GYR.Follow_Starting_Angle = 1.62; } Control_Info->GYR.Angle_Gap = (Control_Info->Measure.YAW_Angle )*DegreesToRadians -14.16; Control_Info->GYR.Yaw_Angle_380=DJI_Yaw_Motor.Data.Encoder*Encoder_Transform_Angle; Control_Info->GYR.Yaw_Angle_180_Difference_Value=Control_Info->GYR.Yaw_Angle_380-Control_Info->GYR.Follow_Starting_Angle; Control_Info->GYR.Cos = arm_cos_f32(Control_Info->GYR.Yaw_Angle_180_Difference_Value * DegreesToRadians); Control_Info->GYR.Sin = arm_sin_f32(Control_Info->GYR.Yaw_Angle_180_Difference_Value * DegreesToRadians); if(Control_Info->Control_Mode == CONTROL_GYR_CONTROL) { if(Control_Info->SuperCap_Mode_State == 1) //让底盘在小陀螺模式移动时可以变速(但是操作手可能不适应(如果底盘移动特别快的话)比较抽象) { Chassis_GYR_Mode_Resolving(Control_Info,10000,7,7); if(Control_Info->Resolving_Param.speed_vx + Control_Info->Resolving_Param.speed_vy < 500) { Control_Info->Resolving_Param.speed_vw = 10000; } else if(Control_Info->Resolving_Param.speed_vx + Control_Info->Resolving_Param.speed_vy >= 500) { Control_Info->Resolving_Param.speed_vw = 5000; } } else if(Control_Info->SuperCap_Mode_State == 0) { Chassis_GYR_Mode_Resolving(Control_Info,6000,7,7); if(Control_Info->Resolving_Param.speed_vx + Control_Info->Resolving_Param.speed_vy < 500) { Control_Info->Resolving_Param.speed_vw = 6000; } else if(Control_Info->Resolving_Param.speed_vx + Control_Info->Resolving_Param.speed_vy >= 500) { Control_Info->Resolving_Param.speed_vw = 3000; } } } ``` #### 底盘断电后延迟上电 C620电调断电之后立即上电会有虚弱状态所以给底盘电机输出加了一个上电后延迟输出 ``` static int Chassis_Referee_Power_Up_Delay() { bool Chassis_power_detection; uint8_t Last_Referee_System_Info_robot_status_mains_power_chassis_output; uint32_t Power_up_moment; uint32_t Real_moment; Real_moment = HAL_GetTick(); if((Last_Referee_System_Info_robot_status_mains_power_chassis_output == 0 && Referee_System_Info.robot_status.mains_power_chassis_output == 1) && Chassis_power_detection == 0) { Power_up_moment = HAL_GetTick(); Chassis_power_detection = 1; } if(Power_up_moment >= Real_moment + 300 && Chassis_power_detection == 1) { return 1; Chassis_power_detection = 0; } else { return 0; } Last_Referee_System_Info_robot_status_mains_power_chassis_output = Referee_System_Info.robot_status.mains_power_chassis_output; } static void Chassis_Refree_Chassis_Output_Allow_Judeg(Control_Info_Typedef *Control_Info) //底盘断电延迟上电(防止C620电调的虚弱状态) { if(Control_Info->Referee_Param.last_refree_chassis_output == 0 && Referee_System_Info.robot_status.mains_power_chassis_output == 1) { Control_Info->Referee_Param.chassis_output_disconnect_allow = 1; Control_Info->Referee_Param.chassis_output__disconnect_allow_dely_moment = HAL_GetTick(); } else if(Control_Info->Referee_Param.last_refree_chassis_output == 1 && Referee_System_Info.robot_status.mains_power_chassis_output == 1) { Control_Info->Referee_Param.run_moment = HAL_GetTick(); if(Control_Info->Referee_Param.run_moment >= Control_Info->Referee_Param.chassis_output__disconnect_allow_dely_moment + 300) { Control_Info->Referee_Param.chassis_output_disconnect_allow = 0; } } Control_Info->Referee_Param.last_refree_chassis_output = Referee_System_Info.robot_status.mains_power_chassis_output; } ``` ### 关于UI方面的设计 ![输入图片说明](Snipaste_2026-04-02_21-22-44.png) UI方面参考的南京航空航天的开源 [RM2024赛季-UI设计器开源 南京航空航天大学](https://bbs.robomaster.com/article/9614?source=4) 并且在设计UI时借鉴了一些 COD 2025英雄电控高兴学长的UI设计 左边的文本分别是 哨兵残血提示 自瞄提示 场地交互提示 然后中间是准星,热量的外环还有一个底盘正方向提示 准星左边的能量条是底盘超点容量提示 准星右边的数字是摩擦轮平均转数,摩擦轮平均电流以及底下的摩擦轮转速的五个挡位 哨兵残血时 GO RESERE文本变为紫色 当自瞄识别到装甲板时AUTO_AIM后面的圆圈变成粉色,开启自瞄后变成绿色 当场地交互识别到RFID卡后RFID文本后的圆圈变成绿色 超电能量条正常情况下是橙色,较低时是紫色,极低时是黑色,然后电容条底下的圆圈颜色代表了超电的开关 摩擦轮转速正常时只有中间一个小方块是绿色,较于正常转数增加50转速后往上多亮一个小方块,减少50转数后往下多亮一个小方块 显示摩擦轮转速五个挡位的代码 ``` if(Control_Info.Referee_Param.wheel_motor_speed >= Control_Info.Referee_Param.Shoot_speed_init - 100 && Control_Info.Referee_Param.wheel_motor_speed <= Control_Info.Referee_Param.Shoot_speed_init + 100) { ui_g_Friction_Wheel_Speed_Normal_speed->color = 2; if(Control_Info.Referee_Param.wheel_motor_speed >= Control_Info.Referee_Param.Shoot_speed_init + 40) { ui_g_Friction_Wheel_Speed_Addition_50->color = 2; if(Control_Info.Referee_Param.wheel_motor_speed >= Control_Info.Referee_Param.Shoot_speed_init + 90) { ui_g_Friction_Wheel_Speed_Addition_100->color = 2; } else {ui_g_Friction_Wheel_Speed_Addition_100->color = 3;} } else if(Control_Info.Referee_Param.wheel_motor_speed <= Control_Info.Referee_Param.Shoot_speed_init - 40) { ui_g_Friction_Wheel_Speed_Subtraction_50->color = 2; if(Control_Info.Referee_Param.wheel_motor_speed <= Control_Info.Referee_Param.Shoot_speed_init - 90) { ui_g_Friction_Wheel_Speed_Subtraction_100->color = 2; } else{ui_g_Friction_Wheel_Speed_Subtraction_100->color = 3;} } else { ui_g_Friction_Wheel_Speed_Addition_50->color = 3; ui_g_Friction_Wheel_Speed_Subtraction_50->color = 3; } } ui_g_Friction_Wheel_Speed_Current->number = Control_Info.Referee_Param.wheel_motor_current * 1000; ui_update_g_Friction_Wheel_Speed(); ``` 底盘正方向用枪管热量外圈的圆弧表示,其用底盘上的C板接收到的6020电机角度表示出来,代码如下 ``` if(DJI_Yaw_Motor.Data.Angle > 0) { Gimbal_direction = DJI_Yaw_Motor.Data.Angle + 260; } if(DJI_Yaw_Motor.Data.Angle < 0) { Gimbal_direction = DJI_Yaw_Motor.Data.Angle + 365 - 90; } ui_g_Super_Chassis_Buffer_Chassis_status->start_angle = Gimbal_direction-15; ui_g_Super_Chassis_Buffer_Chassis_status->end_angle = Gimbal_direction+15; ui_g_Super_Chassis_Buffer_Friction_wheel_rotaltional_speed->number = Control_Info.Referee_Param.wheel_motor_speed * 1000; ui_update_g_Super_Chassis_Buffer(); ``` UI方面最后还有一点 文本类UI在初始化时得单独一个文本使用一个函数而不能几个文本类UI放在一个函数里进行初始化 ##### 操作手方面的感受 今年东北赛区联盟赛因为灯光以及大屏幕的原因导致很多战队的自瞄都受到很大的影响,而英雄这台车的自瞄又相当的难调(因为射速太慢了)。我个人感觉英雄操作手正常情况下用手瞄的效果是相当不错的, 前提是能够在赛场环境上保持冷静(这非常重要)。还有一点个人感觉一次打两发出去命中率大于一发一发打(近距离情况下),所以我想可以加一个单双发切换键。另外沈阳理工的步兵操作手(使用手柄摇杆作为移动键)可以参考一下(如果底盘是全向的话),因为平时我个人也感觉使用手柄操作全向底盘要更加的丝滑。 ### 参考开源 1.[辽宁科技大学COD战队STM32F407 通用控制系统](http://https://gitee.com/cod_-control/hero_2025) 2.[RM2024-功率控制算法开源(含舵轮,轮腿,全自动调参) 香港科技大学ENTERPRIZE战队](https://github.com/hkustenterprize/RM2024-PowerModule) 3.[RM2023-电机功率模型与功率控制开源 西交利物浦大学](https://github.com/MaxwellDemonLin/Motor-modeling-and-power-control) 4.[RM2025-超级电容与无线充电系统开源 香港科技大学ENTERPRIZE战队](https://bbs.robomaster.com/article/761385?source=4) 5.[RM2024赛季-UI设计器开源 南京航空航天大学](https://bbs.robomaster.com/article/9614?source=4) 6.个人十分感谢以上的战队、院校,参考开源 ### 致谢 1. 此代码模板源于王芃学长。感谢我的电控学长陈泓翔,高兴,王芃。感谢王芃学长留下来的F4模板让我能够在此之上修改。非常感谢陈泓翔学长和高兴学长在备赛的时间里一直指导我,帮助我完善代码,功控部分都是直接使用的陈泓翔的代码,我的代码大部分都是对于陈泓翔学长和高兴学长拙劣的模仿。 2. 感谢机械组成员方巧和王正阳同学帮我设计了英雄这台车,感谢阮传鸿同学在工作之外帮我检查修理英雄的机械部分。还有感谢视觉组的刘城志同学在英雄上加入了自瞄功能,硬件组尹纪兴和尹贻菲同学的超电,让英雄在26赛季的联盟赛上有了优秀的表现。感谢轮腿组王浩同学奉献的摩擦轮电机于重要关头救急。 3. 感谢陪我一路走来的COD成员! 4. 感谢沈阳工业大学(辽阳分校)赫兹矩阵战队、沈阳理工大学 Ambition战队、东北大学T-DT战队、南京航空航天大学长空御风战队、大连理工大学凌Bug战队、辽宁石油化工大学TCN战队,合肥工业大学苍穹战队(排名不分先后)的开源或交流,使我可以更好的 完善此项目。 #### 联系方式 QQ:3455374279