mirror of
https://gitee.com/dlmu-cone/half-steering-basic-framework
synced 2026-10-23 17:42:20 +08:00
first commit
This commit is contained in:
70
application/APP层应用编写指引.md
Normal file
70
application/APP层应用编写指引.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# APP层应用编写指引
|
||||
|
||||
<p align='right'>neozng1@hnu.edu.cn</p>
|
||||
|
||||
## 通信机制
|
||||
|
||||
**应用之间不应该有任何包含关系,它们必须是平行工作的。**而这通过pub-sub的机制实现。module层提供了`message_center`模块,支持发布订阅者的消息订阅机制。以传统的框架为例,负责整车控制的应用和其他应用(或任务)是从属的树状结构,或不同的任务和应用之间通过全局变量传递消息(**请不要使用全局变量!**),而此框架下的不同应用是并行的关系。
|
||||
|
||||
如果一个应用希望获取另一个应用的数据,那么他应该**订阅**由此此应用发布的话题。一个应用要把自己希望共享的数据,注册到消息中心,即**发布**。为了区别不同的消息来源(你希望订阅谁的消息?哪一个消息?),可以通过**话题名**进行订阅。也就是说,消息中心作为第三方,管理所有的消息发布者和订阅者,它像报刊亭一样对消息进行中转,使得不同的应用之间不需要包含彼此,更不用全局变量也能共享消息。
|
||||
|
||||
> 更多关于发布-订阅的实现,请参考`modules/message_center`下的文档。
|
||||
|
||||
|
||||
## robot_def.h
|
||||
|
||||
这是机器人的参数配置文件,必须要针对每个机器人进行修改。包括机器人的尺寸参数和性能参数等。你还需要在这里设定软硬件配置:云台板/底盘板/单板等。这里定义的宏会作为条件编译的决断。
|
||||
|
||||
app层共用的状态变量和结构体等也应该定义在这里(例如用于应用之间通信的数据)。记得用于通信的变量要用:
|
||||
```c
|
||||
#pragma pack(1)
|
||||
typedef struct
|
||||
{
|
||||
// your struct
|
||||
|
||||
} your_struct;
|
||||
#pragma pack()
|
||||
```
|
||||
包裹起来,取消字节对齐以防止出现访问8-bit地址而出现错误。
|
||||
|
||||
如果你需要其他的通信数据类型或修改模块间通信数据的格式,直接在此处更改即可。
|
||||
|
||||
## robot_cmd
|
||||
|
||||
机器人命令模块是对整个机器人的抽象,对于单板控制整车的情况,该应用应该包含接收控制指令的模块,例如遥控器、视觉通信模块。该模块会处理接收到的控制数据,并将其转化为**具体的、定量的**控制信息,发送给其他模块。同时,cmd应用会处理模块和应用离线的情况,出现紧急状况时停止所有执行机构的运行。
|
||||
|
||||
如从遥控器获知当前右侧摇杆拨向上方,则将遥控器发来的数值转化为底盘前进的速度值,然后发送给其他应用。同时,robot_cmd还要从其他应用获取反馈信息,做出其他决策。可以将其视为整个机器人的**大脑**。
|
||||
|
||||
robot_cmd工作起来就像一个遥控数据的兼容层,不论数据的来源是视觉上位机/遥控器/键鼠/图传通信链路/ps手柄,最后都会被转化成真实参考输入提供给其他的app。它的任务是将其他来源的数据映射到控制输入上。
|
||||
|
||||
|
||||
|
||||
## gimbal
|
||||
|
||||
以步兵为例,云台应用应当包含两个电机,分别用于驱动yaw和pitch轴(除非你是一个三轴的云台),还有一个imu(开发板一般放在云台上)。gimbal模块会接收robot_cmd发来的控制信息(云台的角度、转速等),并通过电机提供的接口完成电机的参考值设定。gimbal还要把imu的数据反馈给cmd,用于和视觉的通信以及云台状态的判断。
|
||||
|
||||
|
||||
|
||||
## shoot
|
||||
|
||||
还是以步兵为例,发射应用应当包括摩擦轮电机、拨盘电机和弹舱盖。根据cmd应用发来的控制信息,决定当前的发射模式(单发、双发、连发),弹舱盖的开合,以及射速(15?18?30?)等。
|
||||
|
||||
|
||||
|
||||
## chassis
|
||||
|
||||
以步兵为例,底盘应该包括4个电机。根据cmd应用发来的控制信息,进行麦克纳姆轮的运动学解算,从而获知四个电机需要的设定值,然后调用电机提供的接口进行设定。chassis还要根据电机的反馈数据以及imu信息(如果有imu的话,即双板的情况,云台一个底盘一个),计算底盘的实际运动状态,反馈给robot_cmd应用。
|
||||
|
||||
|
||||
|
||||
## lift
|
||||
|
||||
以工程机器人为例,抬升机构应该包含用于抬升的执行单元(可能是气缸、电磁阀、电机、点推杆等),根据cmd发来的数据控制执行单元运行到特定的高度,并进行必要的反馈。
|
||||
|
||||
|
||||
|
||||
## 双板兼容
|
||||
|
||||
此框架对单开发板/双开发板/多开发板的情况都提供了支持(多板一般只在工程机器人上出现,需要自己在robot_cmd和robot_def增加相应的条件编译选项,robot.c中也不要忘记增加初始化和任务运行函数),目前通过条件编译实现了对单双板的切换。使用双板时,主控板在云台上,连接遥控器和上位机;副板在底盘上,负责底盘的运动控制和与裁判系统的通信。
|
||||
|
||||
当然,你可以为每台不同的机器人进行特化,因为本框架是针对步兵/英雄定制的。
|
||||
52
application/application.md
Normal file
52
application/application.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# application
|
||||
|
||||
<p align='right'>neozng1@hnu.edu.cn</p>
|
||||
|
||||
这是application层的说明。
|
||||
|
||||
> todo: 是否有必要将所有电机等模块的初始化参数放到一个头文件?
|
||||
|
||||
## 使用说明
|
||||
|
||||
在main函数中包含`robot.h`头文件,这是对整车的抽象。将`INStask`,`motortask`,`ledtask`,`monitortask`这四个task加入`freertos.c`中,创建对应的任务,设置合适的任务运行间隔;然后将`robottask`放入freertos.c中,同样以一定的频率运行。 在初始化实时系统之前,在`main()`中调用`RobotInit()`进行整车的初始化。
|
||||
|
||||
**关于运行的任务**,INStask的运行频率必须为1kHz,motortask推荐的运行频率为200Hz\~1000Hz(详情见module/motor/motor_task.c),在MotorTask内部,对于高实时性要求的电机可以提升到1kHz,不过要注意CAN总线的负载。monitortask的运行频率为100Hz;robottask的运行频率推荐为150Hz以上,应当高于视觉发送的频率,若后续使用插帧,同样应该保证不低过motortask太多。
|
||||
|
||||
若使用双板,则在`robot_def.h`中给对应的开发板设定宏定义,如底盘板使用`#define CHASSIS_BOARD`,云台板使用`#define GIMBAL_BOARD`;单个开发板控制整车,则定义`#define ONE_BOARD`。在每个应用中,都已经使用编译预处理指令完成条件编译,会自动根据设定的宏切换功能。使用双板的时候,目前板间通信通过CAN完成,因此两个开发板会挂载在一条总线上,在两个开发板对这条总线的其他使用CAN的设备进行配置时注意**不要发生ID冲突**,还要注意**防止负载过大**。
|
||||
|
||||
**同样,在该文件中你需要修改一些关于机器人的参数**。比如底盘和云台对齐时yaw电机编码器的值,拨盘的单圈载弹量、底盘的轴距等等。
|
||||
|
||||
|
||||
|
||||
## 封装总览
|
||||
|
||||
Robot.c是整个机器人的抽象,其下有4个应用:robot_cmd,gimbal,chassis,shoot。此框架当前是针对步兵/英雄/哨兵设计的,其他机器人只需要根据各自的特殊机构进行修改即可。robot_cmd是整个机器人的核心应用,其负责接受遥控器/上位机发来的指令,并将指令转化为实际的运动控制目标,发送给其他三个应用。后者会根据robot_cmd发来的命令,设定电机和其他执行单元的参考值等。
|
||||
|
||||
为了进一步解耦应用之间的关系,app层并没有module和bsp之间的那种层级结构(或设计模式中所谓的**结构类型模式**,即robot_cmd包含其他三个模块),而采用了应用并列的**发布-订阅**机制,四个应用之间没有任何相互包含关系,他们之间的通信通过module层提供的`message_center`实现。每个应用会通过该模块向一些话题(事件)发布一些消息,同时从一些话题订阅消息。如robot_cmd应用会发布其他三个模块的控制信息,同时订阅其他三个模块的反馈信息。其他三个模块会订阅robot_cmd发布的控制信息,同时发布反馈给robot_cmd的信息,他们不需要知道彼此的存在,只是从`message_center`处获取其他应用发布的消息或向自己发布的话题推送消息。
|
||||
|
||||
application在初始化module的时候,初始化参数会包含部分bsp的内容,但仅仅是外设和引脚的选择以及id设置(用于通信的外设需要id设置)。实际上当前框架的app层和cubemx初始化部分耦合,在配置的时候就必须确定每个外设的作用和归属权,一旦cubemx完成设置app层必须按照对应参数设置引脚和并分配module的外设。后续考虑将cubemx和bsp耦合,去除顶层代码和底层的关系
|
||||
|
||||
|
||||
|
||||
## 整车程序流程
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
main调用RobotInit进行初始化 --> RobotInit调用基础bsp初始化以及各个app的初始化 --> 各个app进行消息订阅初始化和自有模块的初始化 --> 启动实时系统 --> 各任务开始运行
|
||||
|
||||
```
|
||||
|
||||
任务开始之后,每个app之间的交互关系如下:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
robot_cmd获取遥控器/上位机指令以及各个应用发布的回传信息 --> 将指令转化为具体的控制信息 --> 发布指令到对应话题
|
||||
```
|
||||
|
||||
gimbal/chassis/shoot则根据订阅的robot_cmd发布的消息,将具体的控制信息根据当前模式转化为执行单元的目标值,通过自己拥有的模块完成这些指令,然后把回传的信息发布到对应话题。
|
||||
|
||||
每个应用的具体流程和实现,参见它们各自的说明文档。
|
||||
|
||||
## 开发要点
|
||||
|
||||
各个应用之间务必通过`message_center`以发布-订阅的方式进行消息交换,不要出现包含关系,这可以大大减小耦合度并提高合作开发的效率。
|
||||
442
application/chassis/chassis.c
Normal file
442
application/chassis/chassis.c
Normal file
@@ -0,0 +1,442 @@
|
||||
/**
|
||||
* @file chassis.c
|
||||
* @author NeoZeng neozng1@hnu.edu.cn
|
||||
* @brief 底盘应用,负责接收robot_cmd的控制命令并根据命令进行运动学解算,得到输出
|
||||
* 注意底盘采取右手系,对于平面视图,底盘纵向运动的正前方为x正方向;横向运动的右侧为y正方向
|
||||
*
|
||||
* @version 0.1
|
||||
* @date 2022-12-04
|
||||
*
|
||||
* @copyright Copyright (c) 2022
|
||||
*
|
||||
*/
|
||||
|
||||
#include "chassis.h"
|
||||
#include "robot_def.h"
|
||||
#include "dji_motor.h"
|
||||
#include "LKMotor.h"
|
||||
#include "super_cap.h"
|
||||
#include "message_center.h"
|
||||
#include "referee_task.h"
|
||||
#include "user_lib.h"
|
||||
|
||||
#include "general_def.h"
|
||||
#include "bsp_dwt.h"
|
||||
#include "referee_UI.h"
|
||||
#include "arm_math.h"
|
||||
|
||||
/* 根据robot_def.h中的macro自动计算的参数 */
|
||||
#define HALF_WHEEL_BASE (WHEEL_BASE / 2.0f) // 半轴距
|
||||
#define HALF_TRACK_WIDTH (TRACK_WIDTH / 2.0f) // 半轮距
|
||||
#define PERIMETER_WHEEL (RADIUS_WHEEL * 2 * PI) // 轮子周长
|
||||
#if defined(CHASSIS_MCNAMEE_WHEEL)
|
||||
#define CHASSIS_WHEEL_OFFSET 1.0f // 机器人底盘轮子修正偏移量
|
||||
#elif defined(CHASSIS_STEERING_WHEEL)
|
||||
#define CHASSIS_WHEEL_OFFSET 30.0f // 机器人底盘轮子修正偏移量
|
||||
#define SQRT2 1.41421356f // 根号2
|
||||
#elif defined(CHASSIS_OMNI_WHEEL)
|
||||
#define CHASSIS_WHEEL_OFFSET 0.7071f // 机器人底盘轮子修正偏移量,根号2/2,即45度,用于修正全向轮的安装位置
|
||||
#endif // CHASSIS_OMNI_WHEEL
|
||||
|
||||
/* 底盘应用包含的模块和信息存储,底盘是单例模式,因此不需要为底盘建立单独的结构体 */
|
||||
#ifdef CHASSIS_BOARD // 如果是底盘板,使用板载IMU获取底盘转动角速度
|
||||
#include "can_comm.h"
|
||||
#include "ins_task.h"
|
||||
static CANCommInstance *chasiss_can_comm; // 双板通信CAN comm
|
||||
attitude_t *Chassis_IMU_data;
|
||||
#endif // CHASSIS_BOARD
|
||||
#ifdef ONE_BOARD
|
||||
static Publisher_t *chassis_pub; // 用于发布底盘的数据
|
||||
static Subscriber_t *chassis_sub; // 用于订阅底盘的控制命令
|
||||
#endif // !ONE_BOARD
|
||||
static Chassis_Ctrl_Cmd_s chassis_cmd_recv; // 底盘接收到的控制命令
|
||||
static Chassis_Upload_Data_s chassis_feedback_data; // 底盘回传的反馈数据
|
||||
|
||||
static referee_info_t* referee_data; // 用于获取裁判系统的数据
|
||||
static Referee_Interactive_info_t ui_data; // UI数据,将底盘中的数据传入此结构体的对应变量中,UI会自动检测是否变化,对应显示UI
|
||||
|
||||
static SuperCapInstance *cap; // 超级电容
|
||||
static LKMotorInstance *motor_lf,*motor_rb; // 轮电机9015
|
||||
static DJIMotorInstance *motor_steering_lf,*motor_steering_rb; // 舵电机6020
|
||||
/* 用于自旋变速策略的时间变量 */
|
||||
// static float t;
|
||||
|
||||
/* 私有函数计算的中介变量,设为静态避免参数传递的开销 */
|
||||
static float chassis_vx, chassis_vy; // 将云台系的速度投影到底盘
|
||||
static float vt_lf, vt_rb; // 底盘速度解算后的临时输出,待进行限幅
|
||||
static float at_lf, at_rb; // 底盘的角度解算后的临时输出,待进行限幅
|
||||
static PIDInstance chassis_follow_pid; // 底盘跟随云台的PID
|
||||
|
||||
void ChassisInit()
|
||||
{
|
||||
// 轮电机9015初始化
|
||||
Motor_Init_Config_s chassis_motor_config = {
|
||||
.can_init_config.can_handle = &hcan1,
|
||||
.controller_param_init_config = {
|
||||
.speed_PID = {
|
||||
.Kp = 95, // 4.5
|
||||
.Ki = 0, // 0
|
||||
.Kd = 0, // 0
|
||||
.IntegralLimit = 8000,
|
||||
.Improve = PID_Trapezoid_Intergral | PID_Integral_Limit | PID_Derivative_On_Measurement,
|
||||
.MaxOut = 12000,
|
||||
},
|
||||
.current_PID = {
|
||||
.Kp = 1, // 0.4
|
||||
.Ki = 0, // 0
|
||||
.Kd = 0,
|
||||
.IntegralLimit = 10500,
|
||||
.Improve = PID_Trapezoid_Intergral | PID_Integral_Limit | PID_Derivative_On_Measurement,
|
||||
.MaxOut = 20000,
|
||||
},
|
||||
},
|
||||
.controller_setting_init_config = {
|
||||
.angle_feedback_source = MOTOR_FEED,
|
||||
.speed_feedback_source = MOTOR_FEED,
|
||||
.outer_loop_type = SPEED_LOOP,
|
||||
.close_loop_type = SPEED_LOOP | CURRENT_LOOP,
|
||||
},
|
||||
.motor_type = LK9015,
|
||||
};
|
||||
// @todo: 当前还没有设置电机的正反转,仍然需要手动添加reference的正负号,需要电机module的支持,待修改.
|
||||
|
||||
chassis_motor_config.can_init_config.tx_id = 1;
|
||||
// chassis_motor_config.controller_setting_init_config.motor_reverse_flag = MOTOR_DIRECTION_REVERSE;
|
||||
motor_rb = LKMotorInit(&chassis_motor_config);
|
||||
|
||||
chassis_motor_config.can_init_config.tx_id = 2;
|
||||
// chassis_motor_config.controller_setting_init_config.motor_reverse_flag = MOTOR_DIRECTION_REVERSE;
|
||||
motor_lf = LKMotorInit(&chassis_motor_config);
|
||||
|
||||
// 舵电机6020初始化
|
||||
Motor_Init_Config_s chassis_motor_steering_config = {
|
||||
.can_init_config.can_handle = &hcan1,
|
||||
.controller_param_init_config = {
|
||||
.angle_PID = {
|
||||
.Kp = 12,
|
||||
.Ki = 0.2,
|
||||
.Kd = 0,
|
||||
.CoefA = 3,
|
||||
.CoefB = 0.1,
|
||||
.Improve = PID_Trapezoid_Intergral | PID_Integral_Limit | PID_Derivative_On_Measurement | PID_DerivativeFilter | PID_ChangingIntegrationRate,
|
||||
.IntegralLimit = 1000,
|
||||
.MaxOut = 5000,
|
||||
.Derivative_LPF_RC = 0.001,
|
||||
.DeadBand = 0.5,
|
||||
},
|
||||
.speed_PID = {
|
||||
.Kp = 40,
|
||||
.Ki = 3,
|
||||
.Kd = 0,
|
||||
.Improve = PID_Integral_Limit | PID_Derivative_On_Measurement | PID_ChangingIntegrationRate | PID_OutputFilter,
|
||||
.IntegralLimit = 4000,
|
||||
.MaxOut = 20000,
|
||||
.Output_LPF_RC = 0.03,
|
||||
},
|
||||
},
|
||||
.controller_setting_init_config = {
|
||||
.angle_feedback_source = MOTOR_FEED,
|
||||
.speed_feedback_source = MOTOR_FEED,
|
||||
.outer_loop_type = ANGLE_LOOP,
|
||||
.close_loop_type = ANGLE_LOOP | SPEED_LOOP,
|
||||
.motor_reverse_flag = MOTOR_DIRECTION_NORMAL,
|
||||
},
|
||||
.motor_type = GM6020,
|
||||
};
|
||||
|
||||
chassis_motor_steering_config.can_init_config.tx_id = 1;
|
||||
motor_steering_rb = DJIMotorInit(&chassis_motor_steering_config);
|
||||
|
||||
chassis_motor_steering_config.can_init_config.tx_id = 2;
|
||||
motor_steering_lf = DJIMotorInit(&chassis_motor_steering_config);
|
||||
|
||||
PID_Init_Config_s chassis_follow_pid_conf = {
|
||||
.Kp = 20, // 6
|
||||
.Ki = 0,
|
||||
.Kd = 3, // 0.5
|
||||
.DeadBand = 0.5f,
|
||||
//.CoefA = 0.2,
|
||||
//.CoefB = 0.3,
|
||||
//.Improve = PID_Trapezoid_Intergral | PID_DerivativeFilter | PID_DerivativeFilter | PID_Derivative_On_Measurement | PID_Integral_Limit | PID_Derivative_On_Measurement,
|
||||
//.IntegralLimit = 50, // 200
|
||||
.MaxOut = 45,
|
||||
//.Derivative_LPF_RC = 0.01, // 0.01
|
||||
};
|
||||
PIDInit(&chassis_follow_pid, &chassis_follow_pid_conf);
|
||||
|
||||
// 裁判系统初始化
|
||||
|
||||
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); // 超级电容初始化
|
||||
|
||||
// 发布订阅初始化,如果为双板,则需要can comm来传递消息
|
||||
#ifdef CHASSIS_BOARD
|
||||
Chassis_IMU_data = INS_Init(); // 底盘IMU初始化
|
||||
|
||||
CANComm_Init_Config_s comm_conf = {
|
||||
.can_config = {
|
||||
.can_handle = &hcan2,
|
||||
.tx_id = 0x311,
|
||||
.rx_id = 0x312,
|
||||
},
|
||||
.recv_data_len = sizeof(Chassis_Ctrl_Cmd_s),
|
||||
.send_data_len = sizeof(Chassis_Upload_Data_s),
|
||||
};
|
||||
chasiss_can_comm = CANCommInit(&comm_conf); // can comm初始化
|
||||
#endif // CHASSIS_BOARD
|
||||
|
||||
#ifdef ONE_BOARD // 单板控制整车,则通过pubsub来传递消息
|
||||
chassis_sub = SubRegister("chassis_cmd", sizeof(Chassis_Ctrl_Cmd_s));
|
||||
chassis_pub = PubRegister("chassis_feed", sizeof(Chassis_Upload_Data_s));
|
||||
#endif // ONE_BOARD
|
||||
}
|
||||
|
||||
// #define LF_CENTER ((HALF_TRACK_WIDTH + CENTER_GIMBAL_OFFSET_X + HALF_WHEEL_BASE - CENTER_GIMBAL_OFFSET_Y) * DEGREE_2_RAD)
|
||||
// #define RF_CENTER ((HALF_TRACK_WIDTH - CENTER_GIMBAL_OFFSET_X + HALF_WHEEL_BASE - CENTER_GIMBAL_OFFSET_Y) * DEGREE_2_RAD)
|
||||
// #define LB_CENTER ((HALF_TRACK_WIDTH + CENTER_GIMBAL_OFFSET_X + HALF_WHEEL_BASE + CENTER_GIMBAL_OFFSET_Y) * DEGREE_2_RAD)
|
||||
// #define RB_CENTER ((HALF_TRACK_WIDTH - CENTER_GIMBAL_OFFSET_X + HALF_WHEEL_BASE + CENTER_GIMBAL_OFFSET_Y) * DEGREE_2_RAD)
|
||||
|
||||
// static void SteeringWheelRotateHandle()
|
||||
// {
|
||||
// if (fabsf(chassis_cmd_recv.vx) > 0 || fabsf(chassis_cmd_recv.vy) > 0) {
|
||||
// chassis_cmd_recv.wz = 0;
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* @brief 使舵电机角度最小旋转,取优弧,防止电机旋转不必要的行程
|
||||
* 例如:上次角度为0,目标角度为135度,
|
||||
* 电机会选择逆时针旋转至-45度,而不是顺时针旋转至135度,
|
||||
* 两个角度都会让轮电机处于同一平行线上
|
||||
*
|
||||
* @param angle 目标角度
|
||||
* @param last_angle 上次角度
|
||||
*
|
||||
*/
|
||||
static void MinmizeRotation(float *angle, const float *last_angle, float *speed)
|
||||
{
|
||||
float target_angle = *angle;
|
||||
float actual_angle = *last_angle;
|
||||
float rotation = target_angle - actual_angle;
|
||||
float norm_rotation = rotation;
|
||||
while (norm_rotation > 180.0f) {
|
||||
norm_rotation -= 360.0f;
|
||||
}
|
||||
while (norm_rotation < -180.0f) {
|
||||
norm_rotation += 360.0f;
|
||||
}
|
||||
float threshold = 90.0f;
|
||||
float target_single=fmodf(target_angle,360.0f);
|
||||
if(target_single<0.0f) target_single+=360.0f;
|
||||
|
||||
float current_single=fmodf(actual_angle,360.0f);
|
||||
if(current_single<0.0f) current_single+=360.0f;
|
||||
|
||||
|
||||
if (fabsf(norm_rotation) > threshold) {
|
||||
int32_t round_diff = (int32_t)(target_angle / 360.0f) - (int32_t)(actual_angle / 360.0f);
|
||||
*angle = actual_angle + norm_rotation-180.0f+round_diff*360.0f;
|
||||
*speed = -(*speed);
|
||||
}else if(norm_rotation<-threshold){
|
||||
int32_t round_diff = (int32_t)(target_angle / 360.0f) - (int32_t)(actual_angle / 360.0f);
|
||||
*angle = actual_angle + norm_rotation+180.0f+round_diff*360.0f;
|
||||
*speed = -(*speed);
|
||||
}
|
||||
ANGLE_LIMIT_360_TO_180_ABS(*angle);
|
||||
// float rotation = *angle - *last_angle;
|
||||
// ANGLE_LIMIT_360_TO_180_ABS(rotation);
|
||||
// if (rotation > 90) {
|
||||
// *angle -= 180;
|
||||
// *speed = -(*speed);
|
||||
// } else if (rotation < -90) {
|
||||
// *angle += 180;
|
||||
// *speed = -(*speed);
|
||||
// }
|
||||
// ANGLE_LIMIT_360_TO_180_ABS(*angle);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 舵轮电机角度解算
|
||||
*
|
||||
*/
|
||||
static void SteeringWheelCalculate()
|
||||
{
|
||||
float offset_lf, offset_rb; // 用于计算舵轮的角度
|
||||
float at_lf_last, at_rb_last; // 上次的角度
|
||||
at_lf_last = motor_steering_lf->measure.total_angle;
|
||||
at_rb_last = motor_steering_rb->measure.total_angle;
|
||||
if (chassis_vx == 0 && chassis_vy == 0 && chassis_cmd_recv.wz == 0) {
|
||||
// at_lf = at_lf_last;
|
||||
// at_rf = at_rf_last;
|
||||
// at_lb = at_lb_last;
|
||||
// at_rb = at_rb_last;
|
||||
vt_lf = 0, vt_rb = 0;
|
||||
} else {
|
||||
// 生成预计算变量,减少计算量,空间换时间
|
||||
float w = chassis_cmd_recv.wz * CHASSIS_WHEEL_OFFSET * SQRT2;
|
||||
float temp_x = chassis_vx - w, temp_y = chassis_vy - w;
|
||||
arm_sqrt_f32(temp_x * temp_x + temp_y * temp_y, &vt_lf); // lf:y- , x-
|
||||
temp_y = chassis_vy + w; // 重复利用变量,temp_x = chassis_vy - w;与上次相同因此注释
|
||||
// arm_sqrt_f32(temp_x * temp_x + temp_y * temp_y, &vt_lb); // lb: y+ , x-
|
||||
// temp_x = chassis_vx + w; // temp_y = chassis_vx + w;与上次相同因此注释
|
||||
arm_sqrt_f32(temp_x * temp_x + temp_y * temp_y, &vt_rb); // rb: y+ , x+
|
||||
temp_y = chassis_vy - w; // temp_x = chassis_vy + w;与上次相同因此注释
|
||||
// arm_sqrt_f32(temp_x * temp_x + temp_y * temp_y, &vt_rf); // rf: y- , x+
|
||||
|
||||
// 计算角度偏移
|
||||
offset_lf = atan2f(chassis_vx - w, chassis_vy - w) * RAD_2_DEGREE; // lf: y- , x-
|
||||
offset_rb = atan2f(chassis_vx + w, chassis_vy + w) * RAD_2_DEGREE; // rb: y+ , x+
|
||||
|
||||
at_lf = STEERING_CHASSIS_ALIGN_ANGLE_LF + offset_lf; // 此处似乎不需要加上真实角度,因为我们需要的是绝对角度而不是相对角度
|
||||
at_rb = STEERING_CHASSIS_ALIGN_ANGLE_RB + offset_rb;
|
||||
|
||||
ANGLE_LIMIT_360_TO_180_ABS(at_lf);
|
||||
ANGLE_LIMIT_360_TO_180_ABS(at_rb);
|
||||
|
||||
MinmizeRotation(&at_lf, &at_lf_last, &vt_lf);
|
||||
MinmizeRotation(&at_rb, &at_rb_last, &vt_rb);
|
||||
}
|
||||
|
||||
DJIMotorSetRef(motor_steering_lf, at_lf);
|
||||
DJIMotorSetRef(motor_steering_rb, at_rb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算每个轮毂电机的输出,正运动学解算
|
||||
* 用宏进行预替换减小开销,运动解算具体过程参考教程
|
||||
*/
|
||||
// static void MecanumCalculate()
|
||||
// {
|
||||
// vt_lf = -chassis_vx - chassis_vy - chassis_cmd_recv.wz * LF_CENTER;
|
||||
// vt_rf = -chassis_vx + chassis_vy - chassis_cmd_recv.wz * RF_CENTER;
|
||||
// vt_lb = chassis_vx - chassis_vy - chassis_cmd_recv.wz * LB_CENTER;
|
||||
// vt_rb = chassis_vx + chassis_vy - chassis_cmd_recv.wz * RB_CENTER;
|
||||
// }
|
||||
|
||||
/**
|
||||
* @brief 根据裁判系统和电容剩余容量对输出进行限制并设置电机参考值
|
||||
*
|
||||
*/
|
||||
static void LimitChassisOutput()
|
||||
{
|
||||
// 功率限制待添加
|
||||
// referee_data->PowerHeatData.chassis_power;
|
||||
// referee_data->PowerHeatData.chassis_power_buffer;
|
||||
|
||||
// 完成功率限制后进行电机参考输入设定
|
||||
|
||||
// LKMotorSetRef( motor_lf , vt_lf );
|
||||
// LKMotorSetRef( motor_rb , vt_rb );
|
||||
|
||||
LKMotorSetRef( motor_lf , vt_lf );
|
||||
LKMotorSetRef( motor_rb , vt_rb );
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 根据每个轮子的速度反馈,计算底盘的实际运动速度,逆运动解算
|
||||
* 对于双板的情况,考虑增加来自底盘板IMU的数据
|
||||
*
|
||||
*/
|
||||
static void EstimateSpeed()
|
||||
{
|
||||
// 根据电机速度和陀螺仪的角速度进行解算,还可以利用加速度计判断是否打滑(如果有)
|
||||
// chassis_feedback_data.vx vy wz =
|
||||
// ...
|
||||
}
|
||||
|
||||
/* 机器人底盘控制核心任务 */
|
||||
void ChassisTask()
|
||||
{
|
||||
// 后续增加没收到消息的处理(双板的情况)
|
||||
// 获取新的控制信息
|
||||
#ifdef ONE_BOARD
|
||||
SubGetMessage(chassis_sub, &chassis_cmd_recv);
|
||||
#endif
|
||||
#ifdef CHASSIS_BOARD
|
||||
chassis_cmd_recv = *(Chassis_Ctrl_Cmd_s *)CANCommGet(chasiss_can_comm);
|
||||
#endif // CHASSIS_BOARD
|
||||
|
||||
if (chassis_cmd_recv.chassis_mode == CHASSIS_ZERO_FORCE)
|
||||
{ // 如果出现重要模块离线或遥控器设置为急停,让电机停止
|
||||
LKMotorStop(motor_lf);
|
||||
LKMotorStop(motor_rb);
|
||||
DJIMotorStop(motor_steering_lf);
|
||||
DJIMotorStop(motor_steering_rb);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 正常工作
|
||||
LKMotorEnable(motor_lf);
|
||||
LKMotorEnable(motor_rb);
|
||||
DJIMotorEnable(motor_steering_lf);
|
||||
DJIMotorEnable(motor_steering_rb);
|
||||
}
|
||||
|
||||
// 根据控制模式设定旋转速度
|
||||
switch (chassis_cmd_recv.chassis_mode)
|
||||
{
|
||||
case CHASSIS_NO_FOLLOW: // 底盘不旋转,但维持全向机动,一般用于调整云台姿态
|
||||
chassis_cmd_recv.wz = 0;
|
||||
break;
|
||||
case CHASSIS_FOLLOW_GIMBAL_YAW: // 跟随云台,不单独设置pid,以误差角度平方为速度输出
|
||||
float single_round_offset = chassis_cmd_recv.offset_angle;
|
||||
// float single_round_offset = fmodf(chassis_cmd_recv.offset_angle, 360.0f);
|
||||
if(single_round_offset > 180.0f)
|
||||
{
|
||||
single_round_offset -= 360.0f;
|
||||
}
|
||||
else if(single_round_offset < -180.0f)
|
||||
{
|
||||
single_round_offset += 360.0f;
|
||||
|
||||
}
|
||||
chassis_cmd_recv.wz = PIDCalculate(&chassis_follow_pid, single_round_offset, 0)/100;
|
||||
// chassis_cmd_recv.wz = PIDCalculate(&chassis_follow_pid, chassis_cmd_recv.offset_angle, 0)/100;
|
||||
break;
|
||||
case CHASSIS_ROTATE: // 自旋,同时保持全向机动;当前wz维持定值,后续增加不规则的变速策略
|
||||
chassis_cmd_recv.wz = 0.5;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// 根据云台和底盘的角度offset将控制量映射到底盘坐标系上
|
||||
// 底盘逆时针旋转为角度正方向;云台命令的方向以云台指向的方向为x,采用右手系(x指向正北时y在正东)
|
||||
static float sin_theta, cos_theta;
|
||||
cos_theta = arm_cos_f32(chassis_cmd_recv.offset_angle * DEGREE_2_RAD);
|
||||
sin_theta = arm_sin_f32(chassis_cmd_recv.offset_angle * DEGREE_2_RAD);
|
||||
chassis_vx = chassis_cmd_recv.vx * cos_theta - chassis_cmd_recv.vy * sin_theta;
|
||||
chassis_vy = chassis_cmd_recv.vx * sin_theta + chassis_cmd_recv.vy * cos_theta;
|
||||
|
||||
// 根据控制模式进行正运动学解算,计算底盘输出
|
||||
SteeringWheelCalculate();
|
||||
// MecanumCalculate();
|
||||
|
||||
// 根据裁判系统的反馈数据和电容数据对输出限幅并设定闭环参考值
|
||||
LimitChassisOutput();
|
||||
|
||||
// 根据电机的反馈速度和IMU(如果有)计算真实速度
|
||||
EstimateSpeed();
|
||||
|
||||
// // 获取裁判系统数据 建议将裁判系统与底盘分离,所以此处数据应使用消息中心发送
|
||||
// // 我方颜色id小于7是红色,大于7是蓝色,注意这里发送的是对方的颜色, 0:blue , 1:red
|
||||
// chassis_feedback_data.enemy_color = referee_data->GameRobotState.robot_id > 7 ? 1 : 0;
|
||||
// // 当前只做了17mm热量的数据获取,后续根据robot_def中的宏切换双枪管和英雄42mm的情况
|
||||
// chassis_feedback_data.bullet_speed = referee_data->GameRobotState.shooter_id1_17mm_speed_limit;
|
||||
// chassis_feedback_data.rest_heat = referee_data->PowerHeatData.shooter_heat0;
|
||||
|
||||
// 推送反馈消息
|
||||
#ifdef ONE_BOARD
|
||||
PubPushMessage(chassis_pub, (void *)&chassis_feedback_data);
|
||||
#endif
|
||||
#ifdef CHASSIS_BOARD
|
||||
CANCommSend(chasiss_can_comm, (void *)&chassis_feedback_data);
|
||||
#endif // CHASSIS_BOARD
|
||||
}
|
||||
16
application/chassis/chassis.h
Normal file
16
application/chassis/chassis.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef CHASSIS_H
|
||||
#define CHASSIS_H
|
||||
|
||||
/**
|
||||
* @brief 底盘应用初始化,请在开启rtos之前调用(目前会被RobotInit()调用)
|
||||
*
|
||||
*/
|
||||
void ChassisInit();
|
||||
|
||||
/**
|
||||
* @brief 底盘应用任务,放入实时系统以一定频率运行
|
||||
*
|
||||
*/
|
||||
void ChassisTask();
|
||||
|
||||
#endif // CHASSIS_H
|
||||
24
application/chassis/chassis.md
Normal file
24
application/chassis/chassis.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# chassis
|
||||
|
||||
|
||||
|
||||
## 工作流程
|
||||
|
||||
首先进行初始化,`ChasissInit()`会被`RobotInit()`调用,进行裁判系统、底盘电机的初始化。如果为双板模式,则还会初始化IMU,并且将消息订阅者和发布者的初始化改为`CANComm`的初始化。
|
||||
|
||||
操作系统启动后,工作顺序为:
|
||||
|
||||
1. 从cmd模块获取数据(如果双板则从CANComm获取)
|
||||
2. 判断当前控制数据的模式,如果为停止则停止所有电机
|
||||
3. 根据控制数据,计算底盘的旋转速度
|
||||
4. 根据控制数据中yaw电机的编码器值`angle_offset`,将控制数据映射到底盘坐标系下
|
||||
5. 进行麦克纳姆轮的运动学解算,得到每个电机的设定值
|
||||
6. 获取裁判系统的数据,并根据底盘功率限制对输出进行限幅
|
||||
7. 由电机的反馈数据和IMU(如果有),计算底盘当前的真实运动速度
|
||||
8. 设置底盘反馈数据,包括运动速度和裁判系统数据
|
||||
9. 将反馈数据推送到消息中心(如果双板则通过CANComm发送)
|
||||
|
||||
|
||||
### 后续支持平衡底盘
|
||||
|
||||
新增一个app balance_chassis
|
||||
351
application/cmd/robot_cmd.c
Normal file
351
application/cmd/robot_cmd.c
Normal file
@@ -0,0 +1,351 @@
|
||||
// app
|
||||
#include "robot_def.h"
|
||||
#include "robot_cmd.h"
|
||||
// module
|
||||
#include "remote_control.h"
|
||||
#include "ins_task.h"
|
||||
#include "master_process.h"
|
||||
#include "message_center.h"
|
||||
#include "general_def.h"
|
||||
#include "dji_motor.h"
|
||||
// bsp
|
||||
#include "bsp_dwt.h"
|
||||
#include "bsp_log.h"
|
||||
#include "main.h"
|
||||
// 私有宏,自动将编码器转换成角度值
|
||||
#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
|
||||
|
||||
/* 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 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; // 机器人整体工作状态
|
||||
|
||||
void RobotCMDInit()
|
||||
{
|
||||
rc_data = RemoteControlInit(&huart3); // 修改为对应串口,注意如果是自研板dbus协议串口需选用添加了反相器的那个
|
||||
vision_recv_data = VisionInit(&huart1); // 视觉通信串口
|
||||
|
||||
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));
|
||||
|
||||
#ifdef ONE_BOARD // 双板兼容
|
||||
chassis_cmd_pub = PubRegister("chassis_cmd", sizeof(Chassis_Ctrl_Cmd_s));
|
||||
chassis_feed_sub = SubRegister("chassis_feed", sizeof(Chassis_Upload_Data_s));
|
||||
#endif // ONE_BOARD
|
||||
#ifdef GIMBAL_BOARD
|
||||
CANComm_Init_Config_s comm_conf = {
|
||||
.can_config = {
|
||||
.can_handle = &hcan1,
|
||||
.tx_id = 0x312,
|
||||
.rx_id = 0x311,
|
||||
},
|
||||
.recv_data_len = sizeof(Chassis_Upload_Data_s),
|
||||
.send_data_len = sizeof(Chassis_Ctrl_Cmd_s),
|
||||
};
|
||||
cmd_can_comm = CANCommInit(&comm_conf);
|
||||
#endif // GIMBAL_BOARD
|
||||
gimbal_cmd_send.pitch = 0;
|
||||
|
||||
robot_state = ROBOT_READY; // 启动时机器人进入工作模式,后续加入所有应用初始化完成之后再进入
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 根据gimbal app传回的当前电机角度计算和零位的误差
|
||||
* 单圈绝对角度的范围是0~360,说明文档中有图示
|
||||
*
|
||||
*/
|
||||
static void CalcOffsetAngle()
|
||||
{
|
||||
static float last_offset_angle = 0;
|
||||
const float smooth_factor = 0.3f; // 平滑因子,0~1之间,值越大平滑效果越明显
|
||||
float new_offset = gimbal_fetch_data.yaw_motor_total_angle - YAW_ALIGN_ANGLE;
|
||||
#if YAW_CHASSIS_REVERSE
|
||||
new_offset = -new_offset;
|
||||
#endif
|
||||
float angle_diff = new_offset - last_offset_angle;
|
||||
if (angle_diff > 180.0f)
|
||||
new_offset -= 360.0f;
|
||||
else if (angle_diff < -180.0f)
|
||||
new_offset += 360.0f;
|
||||
chassis_cmd_send .offset_angle = last_offset_angle *(1.0f - smooth_factor) + new_offset * smooth_factor;
|
||||
last_offset_angle = chassis_cmd_send.offset_angle;
|
||||
// static float angle;
|
||||
// static float last_diff = 0.0f; // 用于在±180°附近保持上次符号,避免来回跳变
|
||||
// /* 使用 yaw 电机的单圈角度与 `YAW_ALIGN_ANGLE` 对齐(保持原实现的参考系)
|
||||
// 并归一化到 (-180,180],加小死区防抖。
|
||||
// */
|
||||
// angle = gimbal_fetch_data.yaw_motor_single_round_angle; // 单圈绝对角度(0-360)
|
||||
// float diff = angle - YAW_ALIGN_ANGLE;
|
||||
// while (diff > 180.0f)
|
||||
// diff -= 360.0f;
|
||||
// while (diff <= -180.0f)
|
||||
// diff += 360.0f;
|
||||
// if (fabsf(diff) < 0.5f)
|
||||
// diff = 0.0f;
|
||||
// /* 处理在恰好为 180° 或接近 180° 时的二义性(噪声导致 180/-180 翻转)
|
||||
// 如果差值接近 180°,则保持上一次的符号以避免抖动。可根据需要调整 eps180。
|
||||
// */
|
||||
// const float eps180 = 1.0f;
|
||||
// if (fabsf(fabsf(diff) - 180.0f) < eps180)
|
||||
// {
|
||||
// diff = (last_diff >= 0.0f) ? 180.0f : -180.0f;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// last_diff = diff;
|
||||
// }
|
||||
// chassis_cmd_send.offset_angle = diff;
|
||||
//别名angle提高可读性,不然太长了不好看,虽然基本不会动这个函数
|
||||
// static float angle;
|
||||
// angle = gimbal_fetch_data.yaw_motor_single_round_angle; // 从云台获取的当前yaw电机角度
|
||||
// #if YAW_ECD_GREATER_THAN_4096 // 如果大于180度
|
||||
// if (angle > YAW_ALIGN_ANGLE && angle <= 180.0f + YAW_ALIGN_ANGLE)
|
||||
// chassis_cmd_send.offset_angle = angle - YAW_ALIGN_ANGLE;
|
||||
// else if (angle > 180.0f + YAW_ALIGN_ANGLE)
|
||||
// chassis_cmd_send.offset_angle = angle - YAW_ALIGN_ANGLE - 360.0f;
|
||||
// else
|
||||
// chassis_cmd_send.offset_angle = angle - YAW_ALIGN_ANGLE;
|
||||
// #else // 小于180度
|
||||
// if (angle > YAW_ALIGN_ANGLE)
|
||||
// chassis_cmd_send.offset_angle = angle - YAW_ALIGN_ANGLE;
|
||||
// else if (angle <= YAW_ALIGN_ANGLE && angle >= YAW_ALIGN_ANGLE - 180.0f)
|
||||
// chassis_cmd_send.offset_angle = angle - YAW_ALIGN_ANGLE;
|
||||
// else
|
||||
// chassis_cmd_send.offset_angle = angle - YAW_ALIGN_ANGLE + 360.0f;
|
||||
// #endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 控制输入为遥控器(调试时)的模式和控制量设置
|
||||
*
|
||||
*/
|
||||
static void RemoteControlSet()
|
||||
{
|
||||
// 控制底盘和云台运行模式,云台待添加,云台是否始终使用IMU数据?
|
||||
if (switch_is_up(rc_data[TEMP].rc.switch_right)) // 右侧开关状态[下],底盘跟随云台
|
||||
{
|
||||
chassis_cmd_send.chassis_mode = CHASSIS_ROTATE;
|
||||
gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
|
||||
}
|
||||
else if (switch_is_mid(rc_data[TEMP].rc.switch_right)) // 右侧开关状态[中],底盘和云台分离,底盘保持不转动
|
||||
{
|
||||
chassis_cmd_send.chassis_mode = CHASSIS_FOLLOW_GIMBAL_YAW;
|
||||
gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
|
||||
}
|
||||
|
||||
// 云台参数,确定云台控制数据
|
||||
if (switch_is_mid(rc_data[TEMP].rc.switch_left)) // 左侧开关状态为[中],视觉模式
|
||||
{
|
||||
// 待添加,视觉会发来和目标的误差,同样将其转化为total angle的增量进行控制
|
||||
// ...
|
||||
}
|
||||
// 左侧开关状态为[下],或视觉未识别到目标,纯遥控器拨杆控制
|
||||
if (switch_is_down(rc_data[TEMP].rc.switch_left) || vision_recv_data->target_state == NO_TARGET)
|
||||
{ // 按照摇杆的输出大小进行角度增量,增益系数需调整
|
||||
gimbal_cmd_send.yaw += 0.005f * (float)rc_data[TEMP].rc.rocker_l_;
|
||||
gimbal_cmd_send.pitch += 0.001f * (float)rc_data[TEMP].rc.rocker_l1;
|
||||
}
|
||||
// 云台软件限位
|
||||
|
||||
// 底盘参数,目前没有加入小陀螺(调试似乎暂时没有必要),系数需要调整
|
||||
chassis_cmd_send.vy = -0.15f * (float)rc_data[TEMP].rc.rocker_r_; // _水平方向
|
||||
chassis_cmd_send.vx = 0.15f * (float)rc_data[TEMP].rc.rocker_r1; // 1数值方向
|
||||
|
||||
// // 发射参数
|
||||
// if (switch_is_up(rc_data[TEMP].rc.switch_right)) // 右侧开关状态[上],弹舱打开
|
||||
// ; // 弹舱舵机控制,待添加servo_motor模块,开启
|
||||
// else
|
||||
// ; // 弹舱舵机控制,待添加servo_motor模块,关闭
|
||||
|
||||
// 摩擦轮控制,拨轮向上打为负,向下为正
|
||||
if (rc_data[TEMP].rc.dial < -100) // 向上超过100,打开摩擦轮
|
||||
shoot_cmd_send.friction_mode = FRICTION_ON;
|
||||
else
|
||||
shoot_cmd_send.friction_mode = FRICTION_OFF;
|
||||
// 拨弹控制,遥控器固定为一种拨弹模式,可自行选择
|
||||
if (rc_data[TEMP].rc.dial < -500)
|
||||
shoot_cmd_send.load_mode = LOAD_BURSTFIRE;
|
||||
else
|
||||
shoot_cmd_send.load_mode = LOAD_STOP;
|
||||
// 射频控制,固定每秒1发,后续可以根据左侧拨轮的值大小切换射频,
|
||||
shoot_cmd_send.shoot_rate = 8;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 输入为键鼠时模式和控制量设置
|
||||
*
|
||||
*/
|
||||
static void MouseKeySet()
|
||||
{
|
||||
chassis_cmd_send.vx = rc_data[TEMP].key[KEY_PRESS].w * 300 - rc_data[TEMP].key[KEY_PRESS].s * 300; // 系数待测
|
||||
chassis_cmd_send.vy = rc_data[TEMP].key[KEY_PRESS].s * 300 - rc_data[TEMP].key[KEY_PRESS].d * 300;
|
||||
|
||||
gimbal_cmd_send.yaw += (float)rc_data[TEMP].mouse.x / 660 * 10; // 系数待测
|
||||
gimbal_cmd_send.pitch += (float)rc_data[TEMP].mouse.y / 660 * 10;
|
||||
|
||||
switch (rc_data[TEMP].key_count[KEY_PRESS][Key_Z] % 3) // Z键设置弹速
|
||||
{
|
||||
case 0:
|
||||
shoot_cmd_send.bullet_speed = 15;
|
||||
break;
|
||||
case 1:
|
||||
shoot_cmd_send.bullet_speed = 18;
|
||||
break;
|
||||
default:
|
||||
shoot_cmd_send.bullet_speed = 30;
|
||||
break;
|
||||
}
|
||||
switch (rc_data[TEMP].key_count[KEY_PRESS][Key_E] % 4) // E键设置发射模式
|
||||
{
|
||||
case 0:
|
||||
shoot_cmd_send.load_mode = LOAD_STOP;
|
||||
break;
|
||||
case 1:
|
||||
shoot_cmd_send.load_mode = LOAD_1_BULLET;
|
||||
break;
|
||||
case 2:
|
||||
shoot_cmd_send.load_mode = LOAD_3_BULLET;
|
||||
break;
|
||||
default:
|
||||
shoot_cmd_send.load_mode = LOAD_BURSTFIRE;
|
||||
break;
|
||||
}
|
||||
switch (rc_data[TEMP].key_count[KEY_PRESS][Key_R] % 2) // R键开关弹舱
|
||||
{
|
||||
case 0:
|
||||
shoot_cmd_send.lid_mode = LID_OPEN;
|
||||
break;
|
||||
default:
|
||||
shoot_cmd_send.lid_mode = LID_CLOSE;
|
||||
break;
|
||||
}
|
||||
switch (rc_data[TEMP].key_count[KEY_PRESS][Key_F] % 2) // F键开关摩擦轮
|
||||
{
|
||||
case 0:
|
||||
shoot_cmd_send.friction_mode = FRICTION_OFF;
|
||||
break;
|
||||
default:
|
||||
shoot_cmd_send.friction_mode = FRICTION_ON;
|
||||
break;
|
||||
}
|
||||
switch (rc_data[TEMP].key_count[KEY_PRESS][Key_C] % 4) // C键设置底盘速度
|
||||
{
|
||||
case 0:
|
||||
chassis_cmd_send.chassis_speed_buff = 40;
|
||||
break;
|
||||
case 1:
|
||||
chassis_cmd_send.chassis_speed_buff = 60;
|
||||
break;
|
||||
case 2:
|
||||
chassis_cmd_send.chassis_speed_buff = 80;
|
||||
break;
|
||||
default:
|
||||
chassis_cmd_send.chassis_speed_buff = 100;
|
||||
break;
|
||||
}
|
||||
switch (rc_data[TEMP].key[KEY_PRESS].shift) // 待添加 按shift允许超功率 消耗缓冲能量
|
||||
{
|
||||
case 1:
|
||||
|
||||
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;
|
||||
shoot_cmd_send.shoot_mode = SHOOT_OFF;
|
||||
shoot_cmd_send.friction_mode = FRICTION_OFF;
|
||||
shoot_cmd_send.load_mode = LOAD_STOP;
|
||||
LOGERROR("[CMD] emergency stop!");
|
||||
}
|
||||
// 遥控器右侧开关为[上],恢复正常运行
|
||||
if (switch_is_mid(rc_data[TEMP].rc.switch_right) || switch_is_up(rc_data[TEMP].rc.switch_right))
|
||||
{
|
||||
robot_state = ROBOT_READY;
|
||||
// shoot_cmd_send.shoot_mode = SHOOT_ON;//开摩擦轮?
|
||||
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);
|
||||
|
||||
// 根据gimbal的反馈值计算云台和底盘正方向的夹角,不需要传参,通过static私有变量完成
|
||||
CalcOffsetAngle();
|
||||
// 根据遥控器左侧开关,确定当前使用的控制模式为遥控器调试还是键鼠
|
||||
if (switch_is_down(rc_data[TEMP].rc.switch_left)) // 遥控器左侧开关状态为[下],遥控器控制
|
||||
RemoteControlSet();
|
||||
else if (switch_is_up(rc_data[TEMP].rc.switch_left)) // 遥控器左侧开关状态为[上],键盘控制
|
||||
MouseKeySet();
|
||||
|
||||
EmergencyHandler(); // 处理模块离线和遥控器急停等紧急情况
|
||||
|
||||
// 设置视觉发送数据,还需增加加速度和角速度数据
|
||||
// VisionSetFlag(chassis_fetch_data.enemy_color,,chassis_fetch_data.bullet_speed)
|
||||
|
||||
// 推送消息,双板通信,视觉通信等
|
||||
// 其他应用所需的控制数据在remotecontrolsetmode和mousekeysetmode中完成设置
|
||||
#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);
|
||||
VisionSend(&vision_send_data);
|
||||
}
|
||||
17
application/cmd/robot_cmd.h
Normal file
17
application/cmd/robot_cmd.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef ROBOT_CMD_H
|
||||
#define ROBOT_CMD_H
|
||||
|
||||
|
||||
/**
|
||||
* @brief 机器人核心控制任务初始化,会被RobotInit()调用
|
||||
*
|
||||
*/
|
||||
void RobotCMDInit();
|
||||
|
||||
/**
|
||||
* @brief 机器人核心控制任务,200Hz频率运行(必须高于视觉发送频率)
|
||||
*
|
||||
*/
|
||||
void RobotCMDTask();
|
||||
|
||||
#endif // !ROBOT_CMD_H
|
||||
50
application/cmd/robot_cmd.md
Normal file
50
application/cmd/robot_cmd.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# robot_cmd
|
||||
|
||||
<p align='right'>neozng1@hnu.edu.cn</p>
|
||||
|
||||
## 运行流程
|
||||
|
||||
运行流程可以很直观的从`RobotCMDTask()`中看出。
|
||||
|
||||
1. 首先通过消息订阅机制,获取其他应用的反馈信息
|
||||
2. 使用`CalcOffsetAngle()`计算底盘和云台的offset angle(使得底盘始终获知当前的正方向)
|
||||
3. 接着根据当前是通过键鼠or遥控器控制,调用对应的函数,将控制指令量化为具体的控制信息
|
||||
4. 得到控制信息之后,先不急着发布,而是检测重要的模块和应用是否掉线或出现异常,以及遥控器是否进入紧急停止模式,如果以上情况发生,那么将所有的控制信息都置零,即让电机和其他执行单元保持静止。
|
||||
5. 最后通过pubsub机制,把具体的控制信息发布到对应话题,让其他应用获取。若为双板,则将原本要推送给底盘的信息通过CANComm进行发送。
|
||||
|
||||
|
||||
|
||||
### 遥控器控制模式:
|
||||
|
||||
拨轮向下打到底进入紧急停止模式(后续改为关闭遥控器停止,利用daemon);拨轮向上打开启摩擦轮,超过一半开始发射(速度环,连发)
|
||||
|
||||
左侧开关:
|
||||
- 上:键鼠控制
|
||||
- 中:视觉控制(没有识别到目标的时候仍然可以使用遥控器控制云台)
|
||||
- 下:遥控器控制
|
||||
|
||||
右侧开关:
|
||||
- 上:弹舱开
|
||||
- 中:底盘云台分离(底盘不旋转,全向移动)
|
||||
- 下:底盘跟随云台
|
||||
|
||||
### 键鼠控制模式:
|
||||
|
||||
遥控器左侧开关拨到最上方,进入键鼠控制模式,此时不会响应遥控器遥感和拨轮的输入.
|
||||
|
||||
前后左右:WSAD
|
||||
|
||||
开关弹舱盖:R
|
||||
|
||||
小陀螺:Q
|
||||
|
||||
发射:鼠标左键
|
||||
|
||||
自瞄:鼠标右键
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
158
application/gimbal/gimbal.c
Normal file
158
application/gimbal/gimbal.c
Normal file
@@ -0,0 +1,158 @@
|
||||
#include "gimbal.h"
|
||||
#include "robot_def.h"
|
||||
#include "dji_motor.h"
|
||||
#include "ins_task.h"
|
||||
#include "message_center.h"
|
||||
#include "general_def.h"
|
||||
|
||||
#include "bmi088.h"
|
||||
|
||||
static attitude_t *gimba_IMU_data; // 云台IMU数据
|
||||
static DJIMotorInstance *yaw_motor, *pitch_motor;
|
||||
|
||||
static Publisher_t *gimbal_pub; // 云台应用消息发布者(云台反馈给cmd)
|
||||
static Subscriber_t *gimbal_sub; // cmd控制消息订阅者
|
||||
static Gimbal_Upload_Data_s gimbal_feedback_data; // 回传给cmd的云台状态信息
|
||||
static Gimbal_Ctrl_Cmd_s gimbal_cmd_recv; // 来自cmd的控制信息
|
||||
|
||||
void GimbalInit()
|
||||
{
|
||||
gimba_IMU_data = INS_Init(); // IMU先初始化,获取姿态数据指针赋给yaw电机的其他数据来源
|
||||
// YAW
|
||||
Motor_Init_Config_s yaw_config = {
|
||||
.can_init_config = {
|
||||
.can_handle = &hcan1,
|
||||
.tx_id = 5,
|
||||
},
|
||||
.controller_param_init_config = {
|
||||
.angle_PID = {
|
||||
.Kp = 0.685, // 8
|
||||
.Ki = 0.315,
|
||||
.Kd = 0.022,
|
||||
//.DeadBand = 0.1,
|
||||
.Improve = PID_Trapezoid_Intergral | PID_Integral_Limit | PID_Derivative_On_Measurement | PID_DerivativeFilter | PID_ChangingIntegrationRate,
|
||||
.IntegralLimit = 1060,
|
||||
.CoefA = 20,
|
||||
.CoefB = 0.2,
|
||||
.MaxOut = 4000,
|
||||
.Derivative_LPF_RC = 0.001
|
||||
},
|
||||
.speed_PID = {
|
||||
.Kp = 8000, // 50
|
||||
.Ki = 5000, // 200
|
||||
.Kd = 0,
|
||||
.CoefB = 1,
|
||||
.CoefA = 10,
|
||||
.Improve = PID_Trapezoid_Intergral | PID_Integral_Limit | PID_Derivative_On_Measurement | PID_ChangingIntegrationRate,
|
||||
.IntegralLimit = 7000,
|
||||
.MaxOut = 25000,
|
||||
},
|
||||
.other_angle_feedback_ptr = &gimba_IMU_data->YawTotalAngle,
|
||||
// 还需要增加角速度额外反馈指针,注意方向,ins_task.md中有c板的bodyframe坐标系说明
|
||||
.other_speed_feedback_ptr = &gimba_IMU_data->Gyro[2],
|
||||
},
|
||||
.controller_setting_init_config = {
|
||||
.angle_feedback_source = OTHER_FEED,
|
||||
.speed_feedback_source = OTHER_FEED,
|
||||
.outer_loop_type = ANGLE_LOOP,
|
||||
.close_loop_type = ANGLE_LOOP | SPEED_LOOP,
|
||||
.motor_reverse_flag = MOTOR_DIRECTION_NORMAL,
|
||||
},
|
||||
.motor_type = GM6020};
|
||||
// PITCH
|
||||
Motor_Init_Config_s pitch_config = {
|
||||
.can_init_config = {
|
||||
.can_handle = &hcan2,
|
||||
.tx_id = 4,
|
||||
},
|
||||
.controller_param_init_config = {
|
||||
.angle_PID = {
|
||||
.Kp = 10, // 10
|
||||
.Ki = 0,
|
||||
.Kd = 0,
|
||||
.Improve = PID_Trapezoid_Intergral | PID_Integral_Limit | PID_Derivative_On_Measurement,
|
||||
.IntegralLimit = 100,
|
||||
.MaxOut = 500,
|
||||
},
|
||||
.speed_PID = {
|
||||
.Kp = 50, // 50
|
||||
.Ki = 350, // 350
|
||||
.Kd = 0, // 0
|
||||
.Improve = PID_Trapezoid_Intergral | PID_Integral_Limit | PID_Derivative_On_Measurement,
|
||||
.IntegralLimit = 2500,
|
||||
.MaxOut = 20000,
|
||||
},
|
||||
.other_angle_feedback_ptr = &gimba_IMU_data->Pitch,
|
||||
// 还需要增加角速度额外反馈指针,注意方向,ins_task.md中有c板的bodyframe坐标系说明
|
||||
.other_speed_feedback_ptr = (&gimba_IMU_data->Gyro[0]),
|
||||
},
|
||||
.controller_setting_init_config = {
|
||||
.angle_feedback_source = OTHER_FEED,
|
||||
.speed_feedback_source = OTHER_FEED,
|
||||
.outer_loop_type = ANGLE_LOOP,
|
||||
.close_loop_type = SPEED_LOOP | ANGLE_LOOP,
|
||||
.motor_reverse_flag = MOTOR_DIRECTION_NORMAL,
|
||||
},
|
||||
.motor_type = GM6020,
|
||||
};
|
||||
// 电机对total_angle闭环,上电时为零,会保持静止,收到遥控器数据再动
|
||||
yaw_motor = DJIMotorInit(&yaw_config);
|
||||
pitch_motor = DJIMotorInit(&pitch_config);
|
||||
|
||||
gimbal_pub = PubRegister("gimbal_feed", sizeof(Gimbal_Upload_Data_s));
|
||||
gimbal_sub = SubRegister("gimbal_cmd", sizeof(Gimbal_Ctrl_Cmd_s));
|
||||
}
|
||||
|
||||
/* 机器人云台控制核心任务,后续考虑只保留IMU控制,不再需要电机的反馈 */
|
||||
void GimbalTask()
|
||||
{
|
||||
// 获取云台控制数据
|
||||
// 后续增加未收到数据的处理
|
||||
SubGetMessage(gimbal_sub, &gimbal_cmd_recv);
|
||||
|
||||
// @todo:现在已不再需要电机反馈,实际上可以始终使用IMU的姿态数据来作为云台的反馈,yaw电机的offset只是用来跟随底盘
|
||||
// 根据控制模式进行电机反馈切换和过渡,视觉模式在robot_cmd模块就已经设置好,gimbal只看yaw_ref和pitch_ref
|
||||
switch (gimbal_cmd_recv.gimbal_mode)
|
||||
{
|
||||
// 停止
|
||||
case GIMBAL_ZERO_FORCE:
|
||||
DJIMotorStop(yaw_motor);
|
||||
DJIMotorStop(pitch_motor);
|
||||
break;
|
||||
// 使用陀螺仪的反馈,底盘根据yaw电机的offset跟随云台或视觉模式采用
|
||||
case GIMBAL_GYRO_MODE: // 后续只保留此模式
|
||||
DJIMotorEnable(yaw_motor);
|
||||
DJIMotorEnable(pitch_motor);
|
||||
DJIMotorChangeFeed(yaw_motor, ANGLE_LOOP, OTHER_FEED);
|
||||
DJIMotorChangeFeed(yaw_motor, SPEED_LOOP, OTHER_FEED);
|
||||
DJIMotorChangeFeed(pitch_motor, ANGLE_LOOP, OTHER_FEED);
|
||||
DJIMotorChangeFeed(pitch_motor, SPEED_LOOP, OTHER_FEED);
|
||||
DJIMotorSetRef(yaw_motor, gimbal_cmd_recv.yaw); // yaw和pitch会在robot_cmd中处理好多圈和单圈
|
||||
DJIMotorSetRef(pitch_motor, gimbal_cmd_recv.pitch);
|
||||
break;
|
||||
// 云台自由模式,使用编码器反馈,底盘和云台分离,仅云台旋转,一般用于调整云台姿态(英雄吊射等)/能量机关
|
||||
case GIMBAL_FREE_MODE: // 后续删除,或加入云台追地盘的跟随模式(响应速度更快)
|
||||
DJIMotorEnable(yaw_motor);
|
||||
DJIMotorEnable(pitch_motor);
|
||||
DJIMotorChangeFeed(yaw_motor, ANGLE_LOOP, OTHER_FEED);
|
||||
DJIMotorChangeFeed(yaw_motor, SPEED_LOOP, OTHER_FEED);
|
||||
DJIMotorChangeFeed(pitch_motor, ANGLE_LOOP, OTHER_FEED);
|
||||
DJIMotorChangeFeed(pitch_motor, SPEED_LOOP, OTHER_FEED);
|
||||
DJIMotorSetRef(yaw_motor, gimbal_cmd_recv.yaw); // yaw和pitch会在robot_cmd中处理好多圈和单圈
|
||||
DJIMotorSetRef(pitch_motor, gimbal_cmd_recv.pitch);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// 在合适的地方添加pitch重力补偿前馈力矩
|
||||
// 根据IMU姿态/pitch电机角度反馈计算出当前配重下的重力矩
|
||||
// ...
|
||||
|
||||
// 设置反馈数据,主要是imu和yaw的ecd
|
||||
gimbal_feedback_data.gimbal_imu_data = *gimba_IMU_data;
|
||||
// gimbal_feedback_data.yaw_motor_single_round_angle = yaw_motor->measure.angle_single_round;
|
||||
gimbal_feedback_data.yaw_motor_single_round_angle = yaw_motor->measure.total_angle;
|
||||
// 推送消息
|
||||
PubPushMessage(gimbal_pub, (void *)&gimbal_feedback_data);
|
||||
}
|
||||
16
application/gimbal/gimbal.h
Normal file
16
application/gimbal/gimbal.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef GIMBAL_H
|
||||
#define GIMBAL_H
|
||||
|
||||
/**
|
||||
* @brief 初始化云台,会被RobotInit()调用
|
||||
*
|
||||
*/
|
||||
void GimbalInit();
|
||||
|
||||
/**
|
||||
* @brief 云台任务
|
||||
*
|
||||
*/
|
||||
void GimbalTask();
|
||||
|
||||
#endif // GIMBAL_H
|
||||
13
application/gimbal/gimbal.md
Normal file
13
application/gimbal/gimbal.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# gimbal
|
||||
|
||||
|
||||
|
||||
## 工作流程
|
||||
|
||||
初始化pitch和yaw电机以及一个imu。订阅gimbal_cmd消息(来自robot_cmd)并发布gimbal_feed话题。
|
||||
|
||||
1. 从消息中心获取gimbal_cmd话题的消息
|
||||
2. 根据消息中的控制模式进行模式切换,如果急停则关闭所有电机
|
||||
3. 由设定的模式,进行电机反馈数据来源的切换,修改反馈数据指针,设置前馈控制数据指针等。
|
||||
4. 设置反馈数据,包括yaw电机的绝对角度和imu数据
|
||||
5. 推送反馈数据到gimbal_feed话题下
|
||||
60
application/robot.c
Normal file
60
application/robot.c
Normal file
@@ -0,0 +1,60 @@
|
||||
#include "bsp_init.h"
|
||||
#include "robot.h"
|
||||
#include "robot_def.h"
|
||||
#include "robot_task.h"
|
||||
|
||||
// 编译warning,提醒开发者修改机器人参数
|
||||
#ifndef ROBOT_DEF_PARAM_WARNING
|
||||
#define ROBOT_DEF_PARAM_WARNING
|
||||
#pragma message "check if you have configured the parameters in robot_def.h, IF NOT, please refer to the comments AND DO IT, otherwise the robot will have FATAL ERRORS!!!"
|
||||
#endif // !ROBOT_DEF_PARAM_WARNING
|
||||
|
||||
#if defined(ONE_BOARD) || defined(CHASSIS_BOARD)
|
||||
#include "chassis.h"
|
||||
#endif
|
||||
|
||||
#if defined(ONE_BOARD) || defined(GIMBAL_BOARD)
|
||||
#include "gimbal.h"
|
||||
#include "shoot.h"
|
||||
#include "robot_cmd.h"
|
||||
#endif
|
||||
|
||||
|
||||
void RobotInit()
|
||||
{
|
||||
// 关闭中断,防止在初始化过程中发生中断
|
||||
// 请不要在初始化过程中使用中断和延时函数!
|
||||
// 若必须,则只允许使用DWT_Delay()
|
||||
__disable_irq();
|
||||
|
||||
BSPInit();
|
||||
|
||||
#if defined(ONE_BOARD) || defined(GIMBAL_BOARD)
|
||||
RobotCMDInit();
|
||||
GimbalInit();
|
||||
ShootInit();
|
||||
#endif
|
||||
|
||||
#if defined(ONE_BOARD) || defined(CHASSIS_BOARD)
|
||||
ChassisInit();
|
||||
#endif
|
||||
|
||||
OSTaskInit(); // 创建基础任务
|
||||
|
||||
// 初始化完成,开启中断
|
||||
__enable_irq();
|
||||
}
|
||||
|
||||
void RobotTask()
|
||||
{
|
||||
#if defined(ONE_BOARD) || defined(GIMBAL_BOARD)
|
||||
RobotCMDTask();
|
||||
GimbalTask();
|
||||
ShootTask();
|
||||
#endif
|
||||
|
||||
#if defined(ONE_BOARD) || defined(CHASSIS_BOARD)
|
||||
ChassisTask();
|
||||
#endif
|
||||
|
||||
}
|
||||
18
application/robot.h
Normal file
18
application/robot.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef ROBOT_H
|
||||
#define ROBOT_H
|
||||
|
||||
/* Robot利用robot_def.h中的宏对不同的机器人进行了大量的兼容,同时兼容了两个开发板(云台板和底盘板)的配置 */
|
||||
|
||||
/**
|
||||
* @brief 机器人初始化,请在开启rtos之前调用.这也是唯一需要放入main函数的函数
|
||||
*
|
||||
*/
|
||||
void RobotInit();
|
||||
|
||||
/**
|
||||
* @brief 机器人任务,放入实时系统以一定频率运行,内部会调用各个应用的任务
|
||||
*
|
||||
*/
|
||||
void RobotTask();
|
||||
|
||||
#endif
|
||||
221
application/robot_def.h
Normal file
221
application/robot_def.h
Normal file
@@ -0,0 +1,221 @@
|
||||
/**
|
||||
* @file robot_def.h
|
||||
* @author NeoZeng neozng1@hnu.edu.cn
|
||||
* @author Even
|
||||
* @version 0.1
|
||||
* @date 2022-12-02
|
||||
*
|
||||
* @copyright Copyright (c) HNU YueLu EC 2022 all rights reserved
|
||||
*
|
||||
*/
|
||||
#pragma once // 可以用#pragma once代替#ifndef ROBOT_DEF_H(header guard)
|
||||
#ifndef ROBOT_DEF_H
|
||||
#define ROBOT_DEF_H
|
||||
|
||||
#include "ins_task.h"
|
||||
#include "master_process.h"
|
||||
#include "stdint.h"
|
||||
|
||||
/* 开发板类型定义,烧录时注意不要弄错对应功能;修改定义后需要重新编译,只能存在一个定义! */
|
||||
#define ONE_BOARD // 单板控制整车
|
||||
// #define CHASSIS_BOARD //底盘板
|
||||
// #define GIMBAL_BOARD //云台板
|
||||
|
||||
#define VISION_USE_VCP // 使用虚拟串口发送视觉数据
|
||||
// #define VISION_USE_UART // 使用串口发送视觉数据
|
||||
|
||||
/* 机器人重要参数定义,注意根据不同机器人进行修改,浮点数需要以.0或f结尾,无符号以u结尾 */
|
||||
// 云台参数
|
||||
#define YAW_CHASSIS_ALIGN_ECD 8015 // 云台和底盘对齐指向相同方向时的电机编码器值,若对云台有机械改动需要修改
|
||||
#define YAW_ECD_GREATER_THAN_4096 0 // ALIGN_ECD值是否大于4096,是为1,否为0;用于计算云台偏转角度
|
||||
#define PITCH_HORIZON_ECD 3412 // 云台处于水平位置时编码器值,若对云台有机械改动需要修改
|
||||
#define PITCH_MAX_ANGLE 0 // 云台竖直方向最大角度 (注意反馈如果是陀螺仪,则填写陀螺仪的角度)
|
||||
#define PITCH_MIN_ANGLE 0 // 云台竖直方向最小角度 (注意反馈如果是陀螺仪,则填写陀螺仪的角度)
|
||||
// 底盘参数
|
||||
// #define CHASSIS_OMNI_WHEEL // 是否为全向轮底盘
|
||||
// #define CHASSIS_MCNAMEE_WHEEL // 是否为麦克纳姆轮底盘
|
||||
#define CHASSIS_STEERING_WHEEL // 是否为舵轮
|
||||
#define STEERING_CHASSIS_ALIGN_ECD_LF 1221 // 舵电机 A 编码器值,若有机械改动需要修改
|
||||
#define STEERING_CHASSIS_ALIGN_ECD_RB 7294 // 舵电机 D 编码器值,若有机械改动需要修改
|
||||
|
||||
#define STEERING_CHASSIS_ALIGN_ANGLE_LF STEERING_CHASSIS_ALIGN_ECD_LF / 8192.f * 360.f // 舵轮 A 对齐角度
|
||||
#define STEERING_CHASSIS_ALIGN_ANGLE_RB STEERING_CHASSIS_ALIGN_ECD_RB / 8192.f * 360.f // 舵轮 D 对齐角度
|
||||
// 发射参数
|
||||
#define ONE_BULLET_DELTA_ANGLE 36 // 发射一发弹丸拨盘转动的距离,由机械设计图纸给出
|
||||
#define REDUCTION_RATIO_LOADER 49.0f // 拨盘电机的减速比,英雄需要修改为3508的19.0f
|
||||
#define NUM_PER_CIRCLE 10 // 拨盘一圈的装载量
|
||||
// 机器人底盘修改的参数,单位为mm(毫米)
|
||||
#define WHEEL_BASE 350 // 纵向轴距(前进后退方向)
|
||||
#define TRACK_WIDTH 300 // 横向轮距(左右平移方向)
|
||||
#define CENTER_GIMBAL_OFFSET_X 0 // 云台旋转中心距底盘几何中心的距离,前后方向,云台位于正中心时默认设为0
|
||||
#define CENTER_GIMBAL_OFFSET_Y 0 // 云台旋转中心距底盘几何中心的距离,左右方向,云台位于正中心时默认设为0
|
||||
#define RADIUS_WHEEL 60 // 轮子半径
|
||||
#define REDUCTION_RATIO_WHEEL 19.0f // 电机减速比,因为编码器量测的是转子的速度而不是输出轴的速度故需进行转换
|
||||
|
||||
#define GYRO2GIMBAL_DIR_YAW 1 // 陀螺仪数据相较于云台的yaw的方向,1为相同,-1为相反
|
||||
#define GYRO2GIMBAL_DIR_PITCH 1 // 陀螺仪数据相较于云台的pitch的方向,1为相同,-1为相反
|
||||
#define GYRO2GIMBAL_DIR_ROLL 1 // 陀螺仪数据相较于云台的roll的方向,1为相同,-1为相反
|
||||
|
||||
// 检查是否出现主控板定义冲突,只允许一个开发板定义存在,否则编译会自动报错
|
||||
#if (defined(ONE_BOARD) && defined(CHASSIS_BOARD)) || \
|
||||
(defined(ONE_BOARD) && defined(GIMBAL_BOARD)) || \
|
||||
(defined(CHASSIS_BOARD) && defined(GIMBAL_BOARD))
|
||||
#error Conflict board definition! You can only define one board type.
|
||||
#endif
|
||||
|
||||
#pragma pack(1) // 压缩结构体,取消字节对齐,下面的数据都可能被传输
|
||||
/* -------------------------基本控制模式和数据类型定义-------------------------*/
|
||||
/**
|
||||
* @brief 这些枚举类型和结构体会作为CMD控制数据和各应用的反馈数据的一部分
|
||||
*
|
||||
*/
|
||||
// 机器人状态
|
||||
typedef enum
|
||||
{
|
||||
ROBOT_STOP = 0,
|
||||
ROBOT_READY,
|
||||
} Robot_Status_e;
|
||||
|
||||
// 应用状态
|
||||
typedef enum
|
||||
{
|
||||
APP_OFFLINE = 0,
|
||||
APP_ONLINE,
|
||||
APP_ERROR,
|
||||
} App_Status_e;
|
||||
|
||||
// 底盘模式设置
|
||||
/**
|
||||
* @brief 后续考虑修改为云台跟随底盘,而不是让底盘去追云台,云台的惯量比底盘小.
|
||||
*
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
CHASSIS_ZERO_FORCE = 0, // 电流零输入
|
||||
CHASSIS_ROTATE, // 小陀螺模式
|
||||
CHASSIS_NO_FOLLOW, // 不跟随,允许全向平移
|
||||
CHASSIS_FOLLOW_GIMBAL_YAW, // 跟随模式,底盘叠加角度环控制
|
||||
} chassis_mode_e;
|
||||
|
||||
// 云台模式设置
|
||||
typedef enum
|
||||
{
|
||||
GIMBAL_ZERO_FORCE = 0, // 电流零输入
|
||||
GIMBAL_FREE_MODE, // 云台自由运动模式,即与底盘分离(底盘此时应为NO_FOLLOW)反馈值为电机total_angle;似乎可以改为全部用IMU数据?
|
||||
GIMBAL_GYRO_MODE, // 云台陀螺仪反馈模式,反馈值为陀螺仪pitch,total_yaw_angle,底盘可以为小陀螺和跟随模式
|
||||
} gimbal_mode_e;
|
||||
|
||||
// 发射模式设置
|
||||
typedef enum
|
||||
{
|
||||
SHOOT_OFF = 0,
|
||||
SHOOT_ON,
|
||||
} shoot_mode_e;
|
||||
typedef enum
|
||||
{
|
||||
FRICTION_OFF = 0, // 摩擦轮关闭
|
||||
FRICTION_ON, // 摩擦轮开启
|
||||
} friction_mode_e;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
LID_OPEN = 0, // 弹舱盖打开
|
||||
LID_CLOSE, // 弹舱盖关闭
|
||||
} lid_mode_e;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
LOAD_STOP = 0, // 停止发射
|
||||
LOAD_REVERSE, // 反转
|
||||
LOAD_1_BULLET, // 单发
|
||||
LOAD_3_BULLET, // 三发
|
||||
LOAD_BURSTFIRE, // 连发
|
||||
} loader_mode_e;
|
||||
|
||||
// 功率限制,从裁判系统获取,是否有必要保留?
|
||||
typedef struct
|
||||
{ // 功率控制
|
||||
float chassis_power_mx;
|
||||
} Chassis_Power_Data_s;
|
||||
|
||||
/* ----------------CMD应用发布的控制数据,应当由gimbal/chassis/shoot订阅---------------- */
|
||||
/**
|
||||
* @brief 对于双板情况,遥控器和pc在云台,裁判系统在底盘
|
||||
*
|
||||
*/
|
||||
// cmd发布的底盘控制数据,由chassis订阅
|
||||
typedef struct
|
||||
{
|
||||
// 控制部分
|
||||
float vx; // 前进方向速度
|
||||
float vy; // 横移方向速度
|
||||
float wz; // 旋转速度
|
||||
float offset_angle; // 底盘和归中位置的夹角
|
||||
chassis_mode_e chassis_mode;
|
||||
int chassis_speed_buff;
|
||||
// UI部分
|
||||
// ...
|
||||
|
||||
} Chassis_Ctrl_Cmd_s;
|
||||
|
||||
// cmd发布的云台控制数据,由gimbal订阅
|
||||
typedef struct
|
||||
{ // 云台角度控制
|
||||
float yaw;
|
||||
float pitch;
|
||||
float chassis_rotate_wz;
|
||||
|
||||
gimbal_mode_e gimbal_mode;
|
||||
} Gimbal_Ctrl_Cmd_s;
|
||||
|
||||
// cmd发布的发射控制数据,由shoot订阅
|
||||
typedef struct
|
||||
{
|
||||
shoot_mode_e shoot_mode;
|
||||
loader_mode_e load_mode;
|
||||
lid_mode_e lid_mode;
|
||||
friction_mode_e friction_mode;
|
||||
Bullet_Speed_e bullet_speed; // 弹速枚举
|
||||
uint8_t rest_heat;
|
||||
float shoot_rate; // 连续发射的射频,unit per s,发/秒
|
||||
} Shoot_Ctrl_Cmd_s;
|
||||
|
||||
/* ----------------gimbal/shoot/chassis发布的反馈数据----------------*/
|
||||
/**
|
||||
* @brief 由cmd订阅,其他应用也可以根据需要获取.
|
||||
*
|
||||
*/
|
||||
|
||||
typedef struct
|
||||
{
|
||||
#if defined(CHASSIS_BOARD) || defined(GIMBAL_BOARD) // 非单板的时候底盘还将imu数据回传(若有必要)
|
||||
// attitude_t chassis_imu_data;
|
||||
#endif
|
||||
// 后续增加底盘的真实速度
|
||||
// float real_vx;
|
||||
// float real_vy;
|
||||
// float real_wz;
|
||||
|
||||
uint8_t rest_heat; // 剩余枪口热量
|
||||
Bullet_Speed_e bullet_speed; // 弹速限制
|
||||
Enemy_Color_e enemy_color; // 0 for blue, 1 for red
|
||||
|
||||
} Chassis_Upload_Data_s;
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
attitude_t gimbal_imu_data;
|
||||
uint16_t yaw_motor_single_round_angle;
|
||||
float yaw_motor_total_angle;
|
||||
} Gimbal_Upload_Data_s;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// code to go here
|
||||
// ...
|
||||
} Shoot_Upload_Data_s;
|
||||
|
||||
#pragma pack() // 开启字节对齐,结束前面的#pragma pack(1)
|
||||
|
||||
#endif // !ROBOT_DEF_H
|
||||
139
application/robot_task.h
Normal file
139
application/robot_task.h
Normal file
@@ -0,0 +1,139 @@
|
||||
/* 注意该文件应只用于任务初始化,只能被robot.c包含*/
|
||||
#pragma once
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
#include "task.h"
|
||||
#include "main.h"
|
||||
#include "cmsis_os.h"
|
||||
|
||||
#include "robot.h"
|
||||
#include "ins_task.h"
|
||||
#include "motor_task.h"
|
||||
#include "referee_task.h"
|
||||
#include "master_process.h"
|
||||
#include "daemon.h"
|
||||
#include "HT04.h"
|
||||
#include "buzzer.h"
|
||||
|
||||
#include "bsp_log.h"
|
||||
|
||||
osThreadId insTaskHandle;
|
||||
osThreadId robotTaskHandle;
|
||||
osThreadId motorTaskHandle;
|
||||
osThreadId daemonTaskHandle;
|
||||
osThreadId uiTaskHandle;
|
||||
|
||||
void StartINSTASK(void const *argument);
|
||||
void StartMOTORTASK(void const *argument);
|
||||
void StartDAEMONTASK(void const *argument);
|
||||
void StartROBOTTASK(void const *argument);
|
||||
void StartUITASK(void const *argument);
|
||||
|
||||
/**
|
||||
* @brief 初始化机器人任务,所有持续运行的任务都在这里初始化
|
||||
*
|
||||
*/
|
||||
void OSTaskInit()
|
||||
{
|
||||
osThreadDef(instask, StartINSTASK, osPriorityAboveNormal, 0, 1024);
|
||||
insTaskHandle = osThreadCreate(osThread(instask), NULL); // 由于是阻塞读取传感器,为姿态解算设置较高优先级,确保以1khz的频率执行
|
||||
// 后续修改为读取传感器数据准备好的中断处理,
|
||||
|
||||
osThreadDef(motortask, StartMOTORTASK, osPriorityNormal, 0, 256);
|
||||
motorTaskHandle = osThreadCreate(osThread(motortask), NULL);
|
||||
|
||||
osThreadDef(daemontask, StartDAEMONTASK, osPriorityNormal, 0, 128);
|
||||
daemonTaskHandle = osThreadCreate(osThread(daemontask), NULL);
|
||||
|
||||
osThreadDef(robottask, StartROBOTTASK, osPriorityNormal, 0, 1024);
|
||||
robotTaskHandle = osThreadCreate(osThread(robottask), NULL);
|
||||
|
||||
osThreadDef(uitask, StartUITASK, osPriorityNormal, 0, 512);
|
||||
uiTaskHandle = osThreadCreate(osThread(uitask), NULL);
|
||||
|
||||
HTMotorControlInit(); // 没有注册HT电机则不会执行
|
||||
}
|
||||
|
||||
__attribute__((noreturn)) void StartINSTASK(void const *argument)
|
||||
{
|
||||
static float ins_start;
|
||||
static float ins_dt;
|
||||
INS_Init(); // 确保BMI088被正确初始化.
|
||||
LOGINFO("[freeRTOS] INS Task Start");
|
||||
for (;;)
|
||||
{
|
||||
// 1kHz
|
||||
ins_start = DWT_GetTimeline_ms();
|
||||
INS_Task();
|
||||
ins_dt = DWT_GetTimeline_ms() - ins_start;
|
||||
if (ins_dt > 1)
|
||||
LOGERROR("[freeRTOS] INS Task is being DELAY! dt = [%f]", &ins_dt);
|
||||
VisionSend(); // 解算完成后发送视觉数据,但是当前的实现不太优雅,后续若添加硬件触发需要重新考虑结构的组织
|
||||
osDelay(1);
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((noreturn)) void StartMOTORTASK(void const *argument)//500hz
|
||||
{
|
||||
static float motor_dt;
|
||||
static float motor_start;
|
||||
LOGINFO("[freeRTOS] MOTOR Task Start");
|
||||
for (;;)
|
||||
{
|
||||
motor_start = DWT_GetTimeline_ms();
|
||||
MotorControlTask();
|
||||
motor_dt = DWT_GetTimeline_ms() - motor_start;
|
||||
if (motor_dt > 2)
|
||||
LOGERROR("[freeRTOS] MOTOR Task is being DELAY! dt = [%f]", &motor_dt);
|
||||
osDelay(2);
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((noreturn)) void StartDAEMONTASK(void const *argument)
|
||||
{
|
||||
static float daemon_dt;
|
||||
static float daemon_start;
|
||||
BuzzerInit();
|
||||
LOGINFO("[freeRTOS] Daemon Task Start");
|
||||
for (;;)
|
||||
{
|
||||
// 100Hz
|
||||
daemon_start = DWT_GetTimeline_ms();
|
||||
DaemonTask();
|
||||
BuzzerTask();
|
||||
daemon_dt = DWT_GetTimeline_ms() - daemon_start;
|
||||
if (daemon_dt > 10)
|
||||
LOGERROR("[freeRTOS] Daemon Task is being DELAY! dt = [%f]", &daemon_dt);
|
||||
osDelay(10);
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((noreturn)) void StartROBOTTASK(void const *argument)
|
||||
{
|
||||
static float robot_dt;
|
||||
static float robot_start;
|
||||
LOGINFO("[freeRTOS] ROBOT core Task Start");
|
||||
// 200Hz-500Hz,若有额外的控制任务如平衡步兵可能需要提升至1kHz
|
||||
for (;;)
|
||||
{
|
||||
robot_start = DWT_GetTimeline_ms();
|
||||
RobotTask();
|
||||
robot_dt = DWT_GetTimeline_ms() - robot_start;
|
||||
if (robot_dt > 5)
|
||||
LOGERROR("[freeRTOS] ROBOT core Task is being DELAY! dt = [%f]", &robot_dt);
|
||||
osDelay(5);
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((noreturn)) void StartUITASK(void const *argument)
|
||||
{
|
||||
LOGINFO("[freeRTOS] UI Task Start");
|
||||
MyUIInit();
|
||||
LOGINFO("[freeRTOS] UI Init Done, communication with ref has established");
|
||||
for (;;)
|
||||
{
|
||||
// 每给裁判系统发送一包数据会挂起一次,详见UITask函数的refereeSend()
|
||||
UITask();
|
||||
osDelay(2); // 即使没有任何UI需要刷新,也挂起一次,防止卡在UITask中无法切换
|
||||
}
|
||||
}
|
||||
213
application/shoot/shoot.c
Normal file
213
application/shoot/shoot.c
Normal file
@@ -0,0 +1,213 @@
|
||||
#include "shoot.h"
|
||||
#include "robot_def.h"
|
||||
|
||||
#include "dji_motor.h"
|
||||
#include "message_center.h"
|
||||
#include "bsp_dwt.h"
|
||||
#include "general_def.h"
|
||||
|
||||
/* 对于双发射机构的机器人,将下面的数据封装成结构体即可,生成两份shoot应用实例 */
|
||||
static DJIMotorInstance *friction_l, *friction_r, *loader; // 拨盘电机
|
||||
// static servo_instance *lid; 需要增加弹舱盖
|
||||
|
||||
static Publisher_t *shoot_pub;
|
||||
static Shoot_Ctrl_Cmd_s shoot_cmd_recv; // 来自cmd的发射控制信息
|
||||
static Subscriber_t *shoot_sub;
|
||||
static Shoot_Upload_Data_s shoot_feedback_data; // 来自cmd的发射控制信息
|
||||
|
||||
// dwt定时,计算冷却用
|
||||
static float hibernate_time = 0, dead_time = 0;
|
||||
|
||||
void ShootInit()
|
||||
{
|
||||
// 左摩擦轮
|
||||
Motor_Init_Config_s friction_config = {
|
||||
.can_init_config = {
|
||||
.can_handle = &hcan2,
|
||||
},
|
||||
.controller_param_init_config = {
|
||||
.speed_PID = {
|
||||
.Kp = 0, // 20
|
||||
.Ki = 0, // 1
|
||||
.Kd = 0,
|
||||
.Improve = PID_Integral_Limit,
|
||||
.IntegralLimit = 10000,
|
||||
.MaxOut = 15000,
|
||||
},
|
||||
.current_PID = {
|
||||
.Kp = 0, // 0.7
|
||||
.Ki = 0, // 0.1
|
||||
.Kd = 0,
|
||||
.Improve = PID_Integral_Limit,
|
||||
.IntegralLimit = 10000,
|
||||
.MaxOut = 15000,
|
||||
},
|
||||
},
|
||||
.controller_setting_init_config = {
|
||||
.angle_feedback_source = MOTOR_FEED,
|
||||
.speed_feedback_source = MOTOR_FEED,
|
||||
|
||||
.outer_loop_type = SPEED_LOOP,
|
||||
.close_loop_type = SPEED_LOOP | CURRENT_LOOP,
|
||||
.motor_reverse_flag = MOTOR_DIRECTION_NORMAL,
|
||||
},
|
||||
.motor_type = M3508};
|
||||
friction_config.can_init_config.tx_id = 1,
|
||||
friction_l = DJIMotorInit(&friction_config);
|
||||
|
||||
friction_config.can_init_config.tx_id = 2; // 右摩擦轮,改txid和方向就行
|
||||
friction_config.controller_setting_init_config.motor_reverse_flag = MOTOR_DIRECTION_REVERSE;
|
||||
friction_r = DJIMotorInit(&friction_config);
|
||||
|
||||
// 拨盘电机
|
||||
Motor_Init_Config_s loader_config = {
|
||||
.can_init_config = {
|
||||
.can_handle = &hcan2,
|
||||
.tx_id = 5,
|
||||
},
|
||||
.controller_param_init_config = {
|
||||
.angle_PID = {
|
||||
// 如果启用位置环来控制发弹,需要较大的I值保证输出力矩的线性度否则出现接近拨出的力矩大幅下降
|
||||
.Kp = 0, // 10
|
||||
.Ki = 0,
|
||||
.Kd = 0,
|
||||
.MaxOut = 200,
|
||||
},
|
||||
.speed_PID = {
|
||||
.Kp = 0, // 10
|
||||
.Ki = 0, // 1
|
||||
.Kd = 0,
|
||||
.Improve = PID_Integral_Limit,
|
||||
.IntegralLimit = 5000,
|
||||
.MaxOut = 5000,
|
||||
},
|
||||
.current_PID = {
|
||||
.Kp = 0, // 0.7
|
||||
.Ki = 0, // 0.1
|
||||
.Kd = 0,
|
||||
.Improve = PID_Integral_Limit,
|
||||
.IntegralLimit = 5000,
|
||||
.MaxOut = 5000,
|
||||
},
|
||||
},
|
||||
.controller_setting_init_config = {
|
||||
.angle_feedback_source = MOTOR_FEED, .speed_feedback_source = MOTOR_FEED,
|
||||
.outer_loop_type = SPEED_LOOP, // 初始化成SPEED_LOOP,让拨盘停在原地,防止拨盘上电时乱转
|
||||
.close_loop_type = CURRENT_LOOP | SPEED_LOOP,
|
||||
.motor_reverse_flag = MOTOR_DIRECTION_NORMAL, // 注意方向设置为拨盘的拨出的击发方向
|
||||
},
|
||||
.motor_type = M2006 // 英雄使用m3508
|
||||
};
|
||||
loader = DJIMotorInit(&loader_config);
|
||||
|
||||
shoot_pub = PubRegister("shoot_feed", sizeof(Shoot_Upload_Data_s));
|
||||
shoot_sub = SubRegister("shoot_cmd", sizeof(Shoot_Ctrl_Cmd_s));
|
||||
}
|
||||
|
||||
/* 机器人发射机构控制核心任务 */
|
||||
void ShootTask()
|
||||
{
|
||||
// 从cmd获取控制数据
|
||||
SubGetMessage(shoot_sub, &shoot_cmd_recv);
|
||||
|
||||
// 对shoot mode等于SHOOT_STOP的情况特殊处理,直接停止所有电机(紧急停止)
|
||||
if (shoot_cmd_recv.shoot_mode == SHOOT_OFF)
|
||||
{
|
||||
DJIMotorStop(friction_l);
|
||||
DJIMotorStop(friction_r);
|
||||
DJIMotorStop(loader);
|
||||
}
|
||||
else // 恢复运行
|
||||
{
|
||||
DJIMotorEnable(friction_l);
|
||||
DJIMotorEnable(friction_r);
|
||||
DJIMotorEnable(loader);
|
||||
}
|
||||
|
||||
// 如果上一次触发单发或3发指令的时间加上不应期仍然大于当前时间(尚未休眠完毕),直接返回即可
|
||||
// 单发模式主要提供给能量机关激活使用(以及英雄的射击大部分处于单发)
|
||||
// if (hibernate_time + dead_time > DWT_GetTimeline_ms())
|
||||
// return;
|
||||
|
||||
// 若不在休眠状态,根据robotCMD传来的控制模式进行拨盘电机参考值设定和模式切换
|
||||
switch (shoot_cmd_recv.load_mode)
|
||||
{
|
||||
// 停止拨盘
|
||||
case LOAD_STOP:
|
||||
DJIMotorOuterLoop(loader, SPEED_LOOP); // 切换到速度环
|
||||
DJIMotorSetRef(loader, 0); // 同时设定参考值为0,这样停止的速度最快
|
||||
break;
|
||||
// 单发模式,根据鼠标按下的时间,触发一次之后需要进入不响应输入的状态(否则按下的时间内可能多次进入,导致多次发射)
|
||||
case LOAD_1_BULLET: // 激活能量机关/干扰对方用,英雄用.
|
||||
DJIMotorOuterLoop(loader, ANGLE_LOOP); // 切换到角度环
|
||||
DJIMotorSetRef(loader, loader->measure.total_angle + ONE_BULLET_DELTA_ANGLE); // 控制量增加一发弹丸的角度
|
||||
hibernate_time = DWT_GetTimeline_ms(); // 记录触发指令的时间
|
||||
dead_time = 150; // 完成1发弹丸发射的时间
|
||||
break;
|
||||
// 三连发,如果不需要后续可能删除
|
||||
case LOAD_3_BULLET:
|
||||
DJIMotorOuterLoop(loader, ANGLE_LOOP); // 切换到速度环
|
||||
DJIMotorSetRef(loader, loader->measure.total_angle + 3 * ONE_BULLET_DELTA_ANGLE); // 增加3发
|
||||
hibernate_time = DWT_GetTimeline_ms(); // 记录触发指令的时间
|
||||
dead_time = 300; // 完成3发弹丸发射的时间
|
||||
break;
|
||||
// 连发模式,对速度闭环,射频后续修改为可变,目前固定为1Hz
|
||||
case LOAD_BURSTFIRE:
|
||||
DJIMotorOuterLoop(loader, SPEED_LOOP);
|
||||
DJIMotorSetRef(loader, shoot_cmd_recv.shoot_rate * 360 * REDUCTION_RATIO_LOADER / 8);
|
||||
// x颗/秒换算成速度: 已知一圈的载弹量,由此计算出1s需要转的角度,注意换算角速度(DJIMotor的速度单位是angle per second)
|
||||
break;
|
||||
// 拨盘反转,对速度闭环,后续增加卡弹检测(通过裁判系统剩余热量反馈和电机电流)
|
||||
// 也有可能需要从switch-case中独立出来
|
||||
case LOAD_REVERSE:
|
||||
DJIMotorOuterLoop(loader, SPEED_LOOP);
|
||||
// ...
|
||||
break;
|
||||
default:
|
||||
while (1)
|
||||
; // 未知模式,停止运行,检查指针越界,内存溢出等问题
|
||||
}
|
||||
|
||||
// 确定是否开启摩擦轮,后续可能修改为键鼠模式下始终开启摩擦轮(上场时建议一直开启)
|
||||
if (shoot_cmd_recv.friction_mode == FRICTION_ON)
|
||||
{
|
||||
// 根据收到的弹速设置设定摩擦轮电机参考值,需实测后填入
|
||||
switch (shoot_cmd_recv.bullet_speed)
|
||||
{
|
||||
case SMALL_AMU_15:
|
||||
DJIMotorSetRef(friction_l, 0);
|
||||
DJIMotorSetRef(friction_r, 0);
|
||||
break;
|
||||
case SMALL_AMU_18:
|
||||
DJIMotorSetRef(friction_l, 0);
|
||||
DJIMotorSetRef(friction_r, 0);
|
||||
break;
|
||||
case SMALL_AMU_30:
|
||||
DJIMotorSetRef(friction_l, 0);
|
||||
DJIMotorSetRef(friction_r, 0);
|
||||
break;
|
||||
default: // 当前为了调试设定的默认值4000,因为还没有加入裁判系统无法读取弹速.
|
||||
DJIMotorSetRef(friction_l, 30000);
|
||||
DJIMotorSetRef(friction_r, 30000);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else // 关闭摩擦轮
|
||||
{
|
||||
DJIMotorSetRef(friction_l, 0);
|
||||
DJIMotorSetRef(friction_r, 0);
|
||||
}
|
||||
|
||||
// 开关弹舱盖
|
||||
if (shoot_cmd_recv.lid_mode == LID_CLOSE)
|
||||
{
|
||||
//...
|
||||
}
|
||||
else if (shoot_cmd_recv.lid_mode == LID_OPEN)
|
||||
{
|
||||
//...
|
||||
}
|
||||
|
||||
// 反馈数据,目前暂时没有要设定的反馈数据,后续可能增加应用离线监测以及卡弹反馈
|
||||
PubPushMessage(shoot_pub, (void *)&shoot_feedback_data);
|
||||
}
|
||||
16
application/shoot/shoot.h
Normal file
16
application/shoot/shoot.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef SHOOT_H
|
||||
#define SHOOT_H
|
||||
|
||||
/**
|
||||
* @brief 发射初始化,会被RobotInit()调用
|
||||
*
|
||||
*/
|
||||
void ShootInit();
|
||||
|
||||
/**
|
||||
* @brief 发射任务
|
||||
*
|
||||
*/
|
||||
void ShootTask();
|
||||
|
||||
#endif // SHOOT_H
|
||||
15
application/shoot/shoot.md
Normal file
15
application/shoot/shoot.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# shoot
|
||||
|
||||
|
||||
|
||||
## 工作流程
|
||||
|
||||
初始化3个电机和一个舵机,包括发射的2个m3508,拨盘的m2006和弹仓盖上的舵机(双开门舵机可能要换成2个)。m2006初始化时设置为速度闭环,防止上电乱转。订阅shoot_cmd话题(robot_cmd发布的)并发布shoot_feed话题(robot_cmd会订阅)。
|
||||
|
||||
1. 从shoot_cmd获取消息
|
||||
2. 根据工作模式确定是否急停
|
||||
3. 如果之前是单发模式或3发模式并且冷却时间没到,直接结束本次任务,等待下一次进入
|
||||
4. 如果已经冷却完成,根据发来的拨盘模式,设定m2006的闭环类型和参考值
|
||||
5. 根据发来的弹速数据,设定摩擦轮的参考值
|
||||
6. 根据发来的弹舱数据进行开合
|
||||
7. 设定反馈数据,推送到shoot_feed话题
|
||||
Reference in New Issue
Block a user