第一次提交
This commit is contained in:
@@ -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中也不要忘记增加初始化和任务运行函数),目前通过条件编译实现了对单双板的切换。使用双板时,主控板在云台上,连接遥控器和上位机;副板在底盘上,负责底盘的运动控制和与裁判系统的通信。
|
||||
|
||||
当然,你可以为每台不同的机器人进行特化,因为本框架是针对步兵/英雄定制的。
|
||||
@@ -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`以发布-订阅的方式进行消息交换,不要出现包含关系,这可以大大减小耦合度并提高合作开发的效率。
|
||||
@@ -0,0 +1,224 @@
|
||||
/**
|
||||
* @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 "super_cap.h"
|
||||
#include "dji_motor.h"
|
||||
#include "message_center.h"
|
||||
#include "referee_task.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) // 轮子周长
|
||||
|
||||
#ifdef ONE_BOARD
|
||||
static Publisher_t *chassis_pub; // 用于发布底盘的数据
|
||||
static Subscriber_t *chassis_sub; // 用于订阅底盘的控制命令
|
||||
#endif
|
||||
static Chassis_Ctrl_Cmd_s chassis_cmd_recv; // 底盘接收到的控制命令
|
||||
static Chassis_Upload_Data_s chassis_feedback_data; // 底盘回传的反馈数据
|
||||
|
||||
static DJIMotorInstance *motor_lf, *motor_rf, *motor_lb, *motor_rb; // left right forward back
|
||||
|
||||
|
||||
/* 用于自旋变速策略的时间变量 */
|
||||
// static float t;
|
||||
|
||||
/* 私有函数计算的中介变量,设为静态避免参数传递的开销 */
|
||||
static float chassis_vx, chassis_vy; // 将云台系的速度投影到底盘
|
||||
static float vt_lf, vt_rf, vt_lb, vt_rb; // 底盘速度解算后的临时输出,待进行限幅
|
||||
|
||||
void ChassisInit()
|
||||
{
|
||||
// 四个轮子的参数一样,改tx_id和反转标志位即可
|
||||
Motor_Init_Config_s chassis_motor_config = {
|
||||
.can_init_config.can_handle = &hcan1,
|
||||
.controller_param_init_config = {
|
||||
.angle_PID = {
|
||||
.Kp = 50,
|
||||
.Ki = 150,
|
||||
.Kd = 0.3,
|
||||
.IntegralLimit = 1000,
|
||||
.Derivative_LPF_RC = 0.008,
|
||||
.Output_LPF_RC = 0.005,
|
||||
.CoefA= 10.0f,
|
||||
.CoefB = 3.0f,
|
||||
.Improve = PID_Trapezoid_Intergral | PID_Integral_Limit | PID_Derivative_On_Measurement |PID_ChangingIntegrationRate | PID_DerivativeFilter | PID_OutputFilter,
|
||||
.MaxOut = 1000,
|
||||
},
|
||||
.speed_PID = {
|
||||
.Kp = 12,
|
||||
.Ki = 0,
|
||||
.Kd = 0.08,
|
||||
.IntegralLimit = 16000,
|
||||
.Derivative_LPF_RC = 0.008,
|
||||
.Output_LPF_RC = 0.001,
|
||||
.Improve = PID_Trapezoid_Intergral | PID_Integral_Limit | PID_Derivative_On_Measurement | PID_DerivativeFilter | PID_OutputFilter,
|
||||
.MaxOut = 16000,
|
||||
},
|
||||
|
||||
},
|
||||
.controller_setting_init_config = {
|
||||
.angle_feedback_source = MOTOR_FEED,
|
||||
.speed_feedback_source = MOTOR_FEED,
|
||||
.outer_loop_type = ANGLE_LOOP, // 设置为开环,电机设定值由下面的功率控制设定,不走普通的pid
|
||||
.close_loop_type = SPEED_LOOP | ANGLE_LOOP, // 角度环和速度环
|
||||
},
|
||||
.motor_type = GM6020_NEW,
|
||||
};
|
||||
// @todo: 当前还没有设置电机的正反转,仍然需要手动添加reference的正负号,需要电机module的支持,待修改.
|
||||
// 使用功率控制的电机需要使用PowerControlInit()函数初始化,因为电机的控制方式不同
|
||||
// chassis_motor_config.can_init_config.tx_id = 1;
|
||||
// chassis_motor_config.controller_setting_init_config.motor_reverse_flag = MOTOR_DIRECTION_NORMAL;
|
||||
// motor_lf = DJIMotorInit(&chassis_motor_config);
|
||||
|
||||
// chassis_motor_config.can_init_config.tx_id = 2;
|
||||
// chassis_motor_config.controller_setting_init_config.motor_reverse_flag = MOTOR_DIRECTION_NORMAL;
|
||||
// motor_rf = DJIMotorInit(&chassis_motor_config);
|
||||
|
||||
chassis_motor_config.can_init_config.tx_id = 1;
|
||||
chassis_motor_config.controller_setting_init_config.motor_reverse_flag = MOTOR_DIRECTION_NORMAL;
|
||||
// motor_lb = DJIMotorInit(&chassis_motor_config);
|
||||
Motor_Init_Config_s pitch_motor_config = {
|
||||
.can_init_config.can_handle = &hcan1,
|
||||
.can_init_config.tx_id = 2, // 俯仰电机的ID
|
||||
.controller_param_init_config = {
|
||||
.angle_PID = {
|
||||
.Kp = 30,
|
||||
.Ki = 80,
|
||||
.Kd = 0.2,
|
||||
.IntegralLimit = 1000,
|
||||
.Derivative_LPF_RC = 0.008,
|
||||
.Output_LPF_RC = 0.005,
|
||||
.CoefA= 20.0f,
|
||||
.CoefB = 10.0f,
|
||||
.Improve = PID_Trapezoid_Intergral | PID_Integral_Limit | PID_Derivative_On_Measurement |PID_ChangingIntegrationRate | PID_DerivativeFilter | PID_OutputFilter,
|
||||
.MaxOut = 1000,
|
||||
},
|
||||
.speed_PID = {
|
||||
.Kp = 8,
|
||||
.Ki = 0,
|
||||
.Kd = 0.02,
|
||||
.IntegralLimit = 16000,
|
||||
.Derivative_LPF_RC = 0.008,
|
||||
.Output_LPF_RC = 0.001,
|
||||
.Improve = PID_Trapezoid_Intergral | PID_Integral_Limit | PID_Derivative_On_Measurement | PID_DerivativeFilter | PID_OutputFilter,
|
||||
.MaxOut = 16000,
|
||||
},
|
||||
|
||||
},
|
||||
.controller_setting_init_config = {
|
||||
.angle_feedback_source = MOTOR_FEED,
|
||||
.speed_feedback_source = MOTOR_FEED,
|
||||
.outer_loop_type = ANGLE_LOOP, // 设置为开环,电机设定值由下面的功率控制设定,不走普通的pid
|
||||
.close_loop_type = SPEED_LOOP | ANGLE_LOOP, // 角度环和速度环
|
||||
},
|
||||
.motor_type = GM6020_NEW,
|
||||
};
|
||||
// motor_rb = DJIMotorInit(&pitch_motor_config);
|
||||
|
||||
chassis_sub = SubRegister("chassis_cmd", sizeof(Chassis_Ctrl_Cmd_s));
|
||||
chassis_pub = PubRegister("chassis_feed", sizeof(Chassis_Upload_Data_s));
|
||||
}
|
||||
|
||||
#define LF_CENTER 1
|
||||
#define RF_CENTER 1
|
||||
#define LB_CENTER 1
|
||||
#define RB_CENTER 1
|
||||
/**
|
||||
* @brief 计算每个轮毂电机的输出,正运动学解算
|
||||
* 用宏进行预替换减小开销,运动解算具体过程参考教程
|
||||
*/
|
||||
static void MecanumCalculate()
|
||||
{
|
||||
vt_lb = chassis_vx;
|
||||
vt_rb = chassis_cmd_recv.wz;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 根据裁判系统和电容剩余容量对输出进行限制并设置电机参考值
|
||||
*
|
||||
*/
|
||||
static void LimitChassisOutput()
|
||||
{
|
||||
// 完成功率限制后进行电机参考输入设定
|
||||
DJIMotorSetRef(motor_lb, vt_lb);
|
||||
DJIMotorSetRef(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)
|
||||
{ // 如果出现重要模块离线或遥控器设置为急停,让电机停止
|
||||
DJIMotorStop(motor_lb);
|
||||
DJIMotorStop(motor_rb);
|
||||
}
|
||||
else
|
||||
{ // 正常工作
|
||||
|
||||
DJIMotorEnable(motor_rb);
|
||||
DJIMotorEnable(motor_lb);
|
||||
}
|
||||
|
||||
// 根据控制模式设定旋转速度
|
||||
switch (chassis_cmd_recv.chassis_mode)
|
||||
{
|
||||
default:
|
||||
break;
|
||||
}
|
||||
chassis_vx = chassis_cmd_recv.vx;
|
||||
|
||||
// 根据控制模式进行正运动学解算,计算底盘输出
|
||||
MecanumCalculate();
|
||||
|
||||
// 根据裁判系统的反馈数据和电容数据对输出限幅并设定闭环参考值
|
||||
LimitChassisOutput();
|
||||
|
||||
// 推送反馈消息
|
||||
#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
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef CHASSIS_H
|
||||
#define CHASSIS_H
|
||||
|
||||
/**
|
||||
* @brief 底盘应用初始化,请在开启rtos之前调用(目前会被RobotInit()调用)
|
||||
*
|
||||
*/
|
||||
void ChassisInit();
|
||||
|
||||
/**
|
||||
* @brief 底盘应用任务,放入实时系统以一定频率运行
|
||||
*
|
||||
*/
|
||||
void ChassisTask();
|
||||
|
||||
#endif // CHASSIS_H
|
||||
@@ -0,0 +1,24 @@
|
||||
# chassis
|
||||
|
||||
|
||||
@Todo 使用条件编译,选择麦轮(全向轮),舵轮,平衡底盘
|
||||
## 工作流程
|
||||
|
||||
首先进行初始化,`ChasissInit()`会被`RobotInit()`调用,进行裁判系统、底盘电机的初始化。如果为双板模式,则还会初始化IMU,并且将消息订阅者和发布者的初始化改为`CANComm`的初始化。
|
||||
|
||||
操作系统启动后,工作顺序为:
|
||||
|
||||
1. 从cmd模块获取数据(如果双板则从CANComm获取)
|
||||
2. 判断当前控制数据的模式,如果为停止则停止所有电机
|
||||
3. 根据控制数据,计算底盘的旋转速度
|
||||
4. 根据控制数据中yaw电机的编码器值`angle_offset`,将控制数据映射到底盘坐标系下
|
||||
5. 进行麦克纳姆轮的运动学解算,得到每个电机的设定值
|
||||
6. 获取裁判系统的数据,并根据底盘功率限制对输出进行限幅
|
||||
7. 由电机的反馈数据和IMU(如果有),计算底盘当前的真实运动速度
|
||||
8. 设置底盘反馈数据,包括运动速度和裁判系统数据
|
||||
9. 将反馈数据推送到消息中心(如果双板则通过CANComm发送)
|
||||
|
||||
|
||||
### 后续支持平衡底盘
|
||||
|
||||
新增一个app balance_chassis
|
||||
@@ -0,0 +1,801 @@
|
||||
// app
|
||||
#include "robot_def.h"
|
||||
#include "robot_cmd.h"
|
||||
// module
|
||||
#include "controller.h"
|
||||
#include "remote_control.h"
|
||||
#include "ins_task.h"
|
||||
#include "master_process.h"
|
||||
#include "message_center.h"
|
||||
#include "general_def.h"
|
||||
#include "dji_motor.h"
|
||||
#include "bmi088.h"
|
||||
#include "vofa.h"
|
||||
#include "board_comm.h"
|
||||
// bsp
|
||||
#include "bsp_dwt.h"
|
||||
#include "bsp_log.h"
|
||||
|
||||
#include "opencv.h"
|
||||
#include "step_motor.h"
|
||||
|
||||
#include "kalman_filter.h"
|
||||
|
||||
#include "arm_math.h"
|
||||
|
||||
#define mapping(x, in_min, in_max, out_min, out_max) \
|
||||
((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min)
|
||||
|
||||
// 添加卡尔曼滤波器实例
|
||||
static KalmanFilter_t yaw_error_kf;
|
||||
static KalmanFilter_t pitch_error_kf;
|
||||
static float filtered_yaw_error = 0.0f;
|
||||
static float filtered_pitch_error = 0.0f;
|
||||
|
||||
// 私有宏,自动将编码器转换成角度值
|
||||
#define YAW_ALIGN_ANGLE (YAW_CHASSIS_ALIGN_ECD * ECD_ANGLE_COEF_DJI) // 对齐时的角度,0-360
|
||||
#define PTICH_HORIZON_ANGLE (PITCH_HORIZON_ECD * ECD_ANGLE_COEF_DJI) // pitch水平时电机的角度,0-360
|
||||
|
||||
#define yaw_control_kxmax 0.001f
|
||||
#define yaw_control_kxmin 0.0005f
|
||||
#define pitch_control_kxmax 0.001f
|
||||
#define pitch_control_kxmin 0.0004f
|
||||
|
||||
/* cmd应用包含的模块实例指针和交互信息存储*/
|
||||
#ifdef GIMBAL_BOARD // 对双板的兼容,条件编译
|
||||
#include "can_comm.h"
|
||||
static CANCommInstance *cmd_can_comm; // 双板通信
|
||||
#endif
|
||||
#ifdef ONE_BOARD
|
||||
static Publisher_t *chassis_cmd_pub; // 底盘控制消息发布者
|
||||
static Subscriber_t *chassis_feed_sub; // 底盘反馈信息订阅者
|
||||
#endif // ONE_BOARD
|
||||
|
||||
static Chassis_Ctrl_Cmd_s chassis_cmd_send; // 发送给底盘应用的信息,包括控制信息和UI绘制相关
|
||||
static Chassis_Upload_Data_s chassis_fetch_data; // 从底盘应用接收的反馈信息信息,底盘功率枪口热量与底盘运动状态等
|
||||
|
||||
static RC_ctrl_t *rc_data; // 遥控器数据,初始化时返回
|
||||
static Vision_Recv_s *vision_recv_data; // 视觉接收数据指针,初始化时返回
|
||||
static Vision_Send_s vision_send_data; // 视觉发送数据
|
||||
static board_comm_data_t *board_comm_data; // 板间通信数据指针,初始化时返回
|
||||
|
||||
static Publisher_t *gimbal_cmd_pub; // 云台控制消息发布者
|
||||
static Subscriber_t *gimbal_feed_sub; // 云台反馈信息订阅者
|
||||
static Gimbal_Ctrl_Cmd_s gimbal_cmd_send; // 传递给云台的控制信息
|
||||
static Gimbal_Upload_Data_s gimbal_fetch_data; // 从云台获取的反馈信息
|
||||
|
||||
static Publisher_t *shoot_cmd_pub; // 发射控制消息发布者
|
||||
static Subscriber_t *shoot_feed_sub; // 发射反馈信息订阅者
|
||||
static Shoot_Ctrl_Cmd_s shoot_cmd_send; // 传递给发射的控制信息
|
||||
static Shoot_Upload_Data_s shoot_fetch_data; // 从发射获取的反馈信息
|
||||
|
||||
static Robot_Status_e robot_state; // 机器人整体工作状态
|
||||
|
||||
PIDInstance yaw_tracking_pid; // 云台yaw跟随pid
|
||||
PIDInstance pitch_tracking_pid; // 云台pitch跟随pid
|
||||
static float yaw_tracking_pid_output; // 云台yaw跟随pid输出
|
||||
static float pitch_tracking_pid_output; // 云台pitch跟随pid输出
|
||||
|
||||
static float vofa_debug[7];
|
||||
|
||||
// #define PITCH_HORIZON_ECD 6800 // 俯仰水平时的电机编码器
|
||||
|
||||
// 在RobotCMDInit()中添加卡尔曼滤波器初始化
|
||||
static void VisionKalmanFilterInit()
|
||||
{
|
||||
// yaw误差卡尔曼滤波器初始化
|
||||
Kalman_Filter_Init(&yaw_error_kf, 2, 0, 1); // 状态2维(位置,速度), 控制0维, 观测1维
|
||||
|
||||
static float P_init_yaw[4] = {
|
||||
5.0f, 0.0f, // 减小初始不确定性
|
||||
0.0f, 5.0f};
|
||||
memcpy(yaw_error_kf.P_data, P_init_yaw, sizeof(P_init_yaw));
|
||||
|
||||
// 状态转移矩阵F (dt = 1/200 = 0.005s, 200Hz控制频率)
|
||||
static float F_init_yaw[4] = {
|
||||
1.0f, 0.005f, // 修正时间步长
|
||||
0.0f, 1.0f};
|
||||
memcpy(yaw_error_kf.F_data, F_init_yaw, sizeof(F_init_yaw));
|
||||
|
||||
// 过程噪声协方差矩阵Q
|
||||
static float Q_init_yaw[4] = {
|
||||
0.1f, 0.0f, // 增大位置过程噪声
|
||||
0.0f, 1.0f // 增大速度过程噪声
|
||||
};
|
||||
memcpy(yaw_error_kf.Q_data, Q_init_yaw, sizeof(Q_init_yaw));
|
||||
|
||||
// 观测矩阵H
|
||||
static uint8_t measurement_map_yaw[1] = {1}; // 观测第1个状态(位置)
|
||||
static float measurement_degree_yaw[1] = {1.0f};
|
||||
static float mat_R_diagonal_yaw[1] = {1.0f}; // 减小观测噪声,增加对观测的信任
|
||||
static float state_min_variance_yaw[2] = {0.001f, 0.001f}; // 减小最小方差
|
||||
|
||||
yaw_error_kf.UseAutoAdjustment = 1;
|
||||
memcpy(yaw_error_kf.MeasurementMap, measurement_map_yaw, sizeof(measurement_map_yaw));
|
||||
memcpy(yaw_error_kf.MeasurementDegree, measurement_degree_yaw, sizeof(measurement_degree_yaw));
|
||||
memcpy(yaw_error_kf.MatR_DiagonalElements, mat_R_diagonal_yaw, sizeof(mat_R_diagonal_yaw));
|
||||
memcpy(yaw_error_kf.StateMinVariance, state_min_variance_yaw, sizeof(state_min_variance_yaw));
|
||||
|
||||
// pitch误差卡尔曼滤波器初始化
|
||||
Kalman_Filter_Init(&pitch_error_kf, 2, 0, 1); // 状态2维(位置,速度), 控制0维, 观测1维
|
||||
static float P_init_pitch[4] = {
|
||||
5.0f, 0.0f, // 减小初始不确定性
|
||||
0.0f, 5.0f};
|
||||
memcpy(pitch_error_kf.P_data, P_init_pitch, sizeof(P_init_pitch));
|
||||
// 状态转移矩阵F (dt = 1/200 = 0.005s, 200Hz控制频率)
|
||||
static float F_init_pitch[4] = {
|
||||
1.0f, 0.005f, // 修正时间步长
|
||||
0.0f, 1.0f};
|
||||
memcpy(pitch_error_kf.F_data, F_init_pitch, sizeof(F_init_pitch));
|
||||
// 过程噪声协方差矩阵Q
|
||||
static float Q_init_pitch[4] = {
|
||||
0.1f, 0.0f, // 增大位置过程噪声
|
||||
0.0f, 1.0f // 增大速度过程噪声
|
||||
};
|
||||
memcpy(pitch_error_kf.Q_data, Q_init_pitch, sizeof(Q_init_pitch));
|
||||
// 观测矩阵H
|
||||
static uint8_t measurement_map_pitch[1] = {1}; // 观测第1个状态(位置)
|
||||
static float measurement_degree_pitch[1] = {1.0f};
|
||||
static float mat_R_diagonal_pitch[1] = {1.0f}; // 减小观测噪声,增加对观测的信任
|
||||
static float state_min_variance_pitch[2] = {0.001f, 0.001f}; // 减小最小方差
|
||||
pitch_error_kf.UseAutoAdjustment = 1;
|
||||
memcpy(pitch_error_kf.MeasurementMap, measurement_map_pitch, sizeof(measurement_map_pitch));
|
||||
memcpy(pitch_error_kf.MeasurementDegree, measurement_degree_pitch, sizeof(measurement_degree_pitch));
|
||||
memcpy(pitch_error_kf.MatR_DiagonalElements, mat_R_diagonal_pitch, sizeof(mat_R_diagonal_pitch));
|
||||
memcpy(pitch_error_kf.StateMinVariance, state_min_variance_pitch, sizeof(state_min_variance_pitch));
|
||||
}
|
||||
|
||||
void RobotCMDInit()
|
||||
{
|
||||
rc_data = RemoteControlInit(&huart5); // 修改为对应串口,注意如果是自研板dbus协议串口需选用添加了反相器的那个
|
||||
gimbal_cmd_pub = PubRegister("gimbal_cmd", sizeof(Gimbal_Ctrl_Cmd_s));
|
||||
gimbal_feed_sub = SubRegister("gimbal_feed", sizeof(Gimbal_Upload_Data_s));
|
||||
shoot_cmd_pub = PubRegister("shoot_cmd", sizeof(Shoot_Ctrl_Cmd_s));
|
||||
shoot_feed_sub = SubRegister("shoot_feed", sizeof(Shoot_Upload_Data_s));
|
||||
chassis_cmd_pub = PubRegister("chassis_cmd", sizeof(Chassis_Ctrl_Cmd_s));
|
||||
chassis_feed_sub = SubRegister("chassis_feed", sizeof(Chassis_Upload_Data_s));
|
||||
|
||||
static PID_Init_Config_s yaw_tracking_pid_config = {
|
||||
.Kp = 1.5f,
|
||||
.Ki = 3.0f,
|
||||
.Kd = 0.07f,
|
||||
.IntegralLimit = 100.0f,
|
||||
.CoefB = 20.0f,
|
||||
.CoefA = 20.0f,
|
||||
// .Output_LPF_RC = 0.002f, // 100Hz
|
||||
.Improve = PID_Trapezoid_Intergral | PID_Integral_Limit | PID_Derivative_On_Measurement | PID_ChangingIntegrationRate,
|
||||
.MaxOut = 200.0f,
|
||||
};
|
||||
static PID_Init_Config_s pitch_tracking_pid_config = {
|
||||
.Kp = 1.0f,
|
||||
.Ki = 2.0f,
|
||||
.Kd = 0.05f,
|
||||
.IntegralLimit = 100.0f,
|
||||
.CoefB = 10.0f,
|
||||
.CoefA = 30.0f,
|
||||
// .Output_LPF_RC = 0.002f, // 100Hz
|
||||
.Improve = PID_Trapezoid_Intergral | PID_Integral_Limit | PID_Derivative_On_Measurement | PID_ChangingIntegrationRate,
|
||||
.MaxOut = 200.0f,
|
||||
};
|
||||
|
||||
PIDInit(&yaw_tracking_pid, &yaw_tracking_pid_config);
|
||||
PIDInit(&pitch_tracking_pid, &pitch_tracking_pid_config);
|
||||
VisionKalmanFilterInit();
|
||||
Vision_Uart_Init(&huart10);
|
||||
board_comm_data = BoardCommInit(&huart1); // 初始化板间通信数据指针
|
||||
robot_state = ROBOT_READY; // 启动时机器人进入工作模式,后续加入所有应用初始化完成之后再进入
|
||||
}
|
||||
|
||||
static uint8_t first_circle_flag = 1;
|
||||
static float circle_progress = 0; // 画圆进度 100%为一圈
|
||||
static float circle_radius = 60.0f; // 画圆半径
|
||||
static float circle_angle = 0.0f; // 当前圆周角度
|
||||
static float target_x = 0.0f; // 圆周上目标点x坐标
|
||||
static float target_y = 0.0f; // 圆周上目标点y坐标
|
||||
|
||||
/**
|
||||
* @brief 控制输入为遥控器(调试时)的模式和控制量设置
|
||||
*
|
||||
*/
|
||||
static void RemoteControlSet()
|
||||
{
|
||||
chassis_cmd_send.chassis_mode = CHASSIS_NO_FOLLOW;
|
||||
gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
|
||||
if (!switch_is_down(rc_data[TEMP].rc.switch_left))
|
||||
{
|
||||
if (Vison_revc_flag && switch_is_mid(rc_data[TEMP].rc.switch_left))
|
||||
{
|
||||
|
||||
LIMIT_MIN_MAX(opencv_Vision_data[0], -100.0f, 100.0f);
|
||||
LIMIT_MIN_MAX(opencv_Vision_data[1], -100.0f, 100.0f);
|
||||
LIMIT_MIN_MAX(opencv_Vision_data[2], 500.0f, 1560.0f);
|
||||
yaw_error_kf.MeasuredVector[0] = opencv_Vision_data[0];
|
||||
float *yaw_filtered = Kalman_Filter_Update(&yaw_error_kf);
|
||||
filtered_yaw_error = yaw_filtered[0];
|
||||
pitch_error_kf.MeasuredVector[0] = opencv_Vision_data[1];
|
||||
float *pitch_filtered = Kalman_Filter_Update(&pitch_error_kf);
|
||||
filtered_pitch_error = pitch_filtered[0];
|
||||
|
||||
yaw_tracking_pid_output = PIDCalculate(&yaw_tracking_pid, filtered_yaw_error, 0);
|
||||
pitch_tracking_pid_output = PIDCalculate(&pitch_tracking_pid, filtered_pitch_error, 0);
|
||||
gimbal_cmd_send.yaw -= yaw_tracking_pid_output * mapping(opencv_Vision_data[2], 500.0f, 1560.0f, yaw_control_kxmax, yaw_control_kxmin); // yaw角度控制
|
||||
gimbal_cmd_send.pitch += 0.001f * pitch_tracking_pid_output; // pitch角度控制
|
||||
vofa_debug[0] = opencv_Vision_data[0]; // 调试用
|
||||
vofa_debug[1] = filtered_yaw_error; // 调试用
|
||||
vofa_debug[2] = yaw_tracking_pid.Err; // 调试用
|
||||
vofa_debug[3] = yaw_tracking_pid.Pout; // 调试用
|
||||
vofa_debug[4] = yaw_tracking_pid.Iout; // 调试用
|
||||
vofa_debug[5] = yaw_tracking_pid.Dout; // 调试用
|
||||
vofa_debug[6] = yaw_tracking_pid.Output; // 调试用
|
||||
vofa_justfloat_output(vofa_debug, 7, &huart7);
|
||||
first_circle_flag = 1; // 重置画圆标志
|
||||
}
|
||||
else if (switch_is_up(rc_data[TEMP].rc.switch_left))
|
||||
{
|
||||
if (first_circle_flag)
|
||||
{
|
||||
circle_progress = 0;
|
||||
circle_angle = 0.0f;
|
||||
first_circle_flag = 0;
|
||||
}
|
||||
LIMIT_MIN_MAX(opencv_Vision_data[0], -100.0f, 100.0f);
|
||||
LIMIT_MIN_MAX(opencv_Vision_data[1], -100.0f, 100.0f);
|
||||
LIMIT_MIN_MAX(opencv_Vision_data[2], 500.0f, 1560.0f);
|
||||
yaw_error_kf.MeasuredVector[0] = opencv_Vision_data[0];
|
||||
float *yaw_filtered = Kalman_Filter_Update(&yaw_error_kf);
|
||||
filtered_yaw_error = yaw_filtered[0];
|
||||
pitch_error_kf.MeasuredVector[0] = opencv_Vision_data[1];
|
||||
float *pitch_filtered = Kalman_Filter_Update(&pitch_error_kf);
|
||||
filtered_pitch_error = pitch_filtered[0];
|
||||
// 画圆模式 500hz 16s画完整圆
|
||||
circle_progress += 0.000125f; // 每次增加1/16
|
||||
if (circle_progress >= 1.0f)
|
||||
{
|
||||
circle_progress = 1.0f; // 限制在100%
|
||||
}
|
||||
circle_angle = 2.0f * PI * circle_progress; // 计算当前圆周角度
|
||||
target_x = circle_radius * cosf(circle_angle); // 计算圆周上目标点x坐标
|
||||
target_y = circle_radius * sinf(circle_angle); // 计算圆周上目标点y坐标
|
||||
yaw_tracking_pid_output = PIDCalculate(&yaw_tracking_pid, filtered_yaw_error, target_x);
|
||||
pitch_tracking_pid_output = PIDCalculate(&pitch_tracking_pid, filtered_pitch_error, target_y);
|
||||
gimbal_cmd_send.yaw -= yaw_tracking_pid_output * mapping(opencv_Vision_data[2], 500.0f, 1560.0f, yaw_control_kxmax, yaw_control_kxmin); // yaw角度控制
|
||||
gimbal_cmd_send.pitch += pitch_tracking_pid_output * mapping(opencv_Vision_data[2], 500.0f, 1560.0f, pitch_control_kxmax, pitch_control_kxmin); // pitch角度控制
|
||||
}
|
||||
else
|
||||
{
|
||||
first_circle_flag = 1; // 重置画圆标志
|
||||
gimbal_cmd_send.yaw -= 0.0025f * (float)rc_data[TEMP].rc.rocker_r_; // yaw角度控制
|
||||
gimbal_cmd_send.pitch += 0.00125f * (float)rc_data[TEMP].rc.rocker_r1; // pitch角度控制
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
gimbal_cmd_send.yaw -= 0.0025f * (float)rc_data[TEMP].rc.rocker_r_; // yaw角度控制
|
||||
gimbal_cmd_send.pitch += 0.00125f * (float)rc_data[TEMP].rc.rocker_r1; // pitch角度控制
|
||||
}
|
||||
|
||||
if (gimbal_cmd_send.pitch > 30.0f) // 限制pitch角度
|
||||
gimbal_cmd_send.pitch = 30.0f;
|
||||
else if (gimbal_cmd_send.pitch < -30.0f)
|
||||
gimbal_cmd_send.pitch = -30.0f;
|
||||
}
|
||||
|
||||
void laser_on()
|
||||
{
|
||||
HAL_GPIO_WritePin(POWER_5V_GPIO_Port, POWER_5V_Pin, GPIO_PIN_SET);
|
||||
}
|
||||
|
||||
void laser_off()
|
||||
{
|
||||
HAL_GPIO_WritePin(POWER_5V_GPIO_Port, POWER_5V_Pin, GPIO_PIN_RESET);
|
||||
}
|
||||
enum YunTaiState_e
|
||||
{
|
||||
YUNT_IDLE = 0, // 云台无任务
|
||||
YUNT_NO1, // 云台题号1准备
|
||||
YUNT_NO2_WAITING,
|
||||
YUNT_NO2,
|
||||
YUNT_NO3_WAITING,
|
||||
YUNT_NO3_TURING,
|
||||
YUNT_NO3_SEARCHING,
|
||||
YUNT_NO3_TRACKING,
|
||||
YUNT_NO4_1_WAITING,
|
||||
YUNT_NO4_1_TURING,
|
||||
YUNT_NO4_1_TRACKING,
|
||||
YUNT_NO4_1_TRACKING_RUNNING,
|
||||
YUNT_NO4_3_WAITING,
|
||||
YUNT_NO4_3_TURING,
|
||||
YUNT_NO4_3_TRACKING,
|
||||
YUNT_NO4_3_TRACKING_RUNNING,
|
||||
} YunTaiState = YUNT_IDLE; // 云台状态机
|
||||
|
||||
static float yuntai_no3_yawtarget = 0.0f; // 云台题号3目标yaw角度
|
||||
static float yuntai_no3_searching_yaw = 0.0f; // 云台题号3搜索yaw角度
|
||||
static float yuntai_no3_searching_yaw_max = 60.0f; // 云台yaw题号3搜索最大yaw角度
|
||||
static float yuntai_no3_searching_yaw_min = -60.0f; // 云台yaw题号3搜索最小yaw角度
|
||||
static float yuntai_no3_searching_step = 0.2f; // 云台题号3搜索步长,每次0.18度
|
||||
static uint8_t yuntai_no3_searching_dir = 0; // 云台题号3搜索方向, 0: -, 1:+
|
||||
static int16_t yuntai_no3_searching_count = 50; // 3计数器
|
||||
|
||||
static uint8_t no4_turning_count = 0; // 题号4转向计数器
|
||||
static uint8_t no4_turning_status = 0; // 题号4转向状态
|
||||
static uint8_t no4_turning_feedforward = 0.05;
|
||||
|
||||
static float timestart_timeline = 0.0f; // 题号开始时间
|
||||
|
||||
/**
|
||||
* @brief 题号状态机
|
||||
*
|
||||
*/
|
||||
static void AutoSet()
|
||||
{
|
||||
|
||||
yaw_tracking_pid.Ki = 3.0f;
|
||||
|
||||
switch (board_comm_data->rx_int_data[0]) // 根据板间通信数据的第一个整数值来判断题号
|
||||
{
|
||||
case 1:
|
||||
YunTaiState = YUNT_NO1;
|
||||
break;
|
||||
case 2:
|
||||
if (board_comm_data->rx_int_data[1] == 0)
|
||||
{
|
||||
YunTaiState = YUNT_NO2_WAITING;
|
||||
}
|
||||
else
|
||||
{
|
||||
YunTaiState = YUNT_NO2; // 进入题号2状态
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (board_comm_data->rx_int_data[1] == 0)
|
||||
{
|
||||
YunTaiState = YUNT_NO3_WAITING;
|
||||
}
|
||||
else if (YunTaiState == YUNT_NO3_WAITING)
|
||||
{
|
||||
YunTaiState = YUNT_NO3_TURING; // 进入题号3转向状态
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
case 5:
|
||||
if (board_comm_data->rx_int_data[1] == 0)
|
||||
{
|
||||
YunTaiState = YUNT_NO4_1_WAITING; // 进入题号4-1等待状态
|
||||
}
|
||||
else if (board_comm_data->rx_int_data[1] == 1 && YunTaiState == YUNT_NO4_1_WAITING)
|
||||
{
|
||||
YunTaiState = YUNT_NO4_1_TURING; // 进入题号4-1转向状态
|
||||
}
|
||||
else if (board_comm_data->rx_int_data[1] == 2)
|
||||
{
|
||||
YunTaiState = YUNT_NO4_1_TRACKING_RUNNING;
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
if (board_comm_data->rx_int_data[1] == 0)
|
||||
{
|
||||
YunTaiState = YUNT_NO4_3_WAITING; // 进入题号4-3等待状态
|
||||
}
|
||||
else if (board_comm_data->rx_int_data[1] == 1 && YunTaiState == YUNT_NO4_3_WAITING)
|
||||
{
|
||||
YunTaiState = YUNT_NO4_3_TURING; // 进入题号4-3状态
|
||||
}
|
||||
else if (board_comm_data->rx_int_data[1] == 2)
|
||||
{
|
||||
YunTaiState = YUNT_NO4_3_TRACKING_RUNNING;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (YunTaiState)
|
||||
{
|
||||
case YUNT_NO1:
|
||||
gimbal_cmd_send.gimbal_mode = GIMBAL_ZERO_FORCE;
|
||||
chassis_cmd_send.chassis_mode = CHASSIS_ZERO_FORCE;
|
||||
gimbal_cmd_send.yaw = gimbal_fetch_data.gimbal_imu_data.YawTotalAngle;
|
||||
gimbal_cmd_send.pitch = gimbal_fetch_data.gimbal_imu_data.Pitch;
|
||||
laser_off();
|
||||
break;
|
||||
case YUNT_NO2_WAITING:
|
||||
gimbal_cmd_send.gimbal_mode = GIMBAL_ZERO_FORCE;
|
||||
gimbal_cmd_send.yaw = gimbal_fetch_data.gimbal_imu_data.YawTotalAngle;
|
||||
gimbal_cmd_send.pitch = gimbal_fetch_data.gimbal_imu_data.Pitch;
|
||||
timestart_timeline = DWT_GetTimeline_ms(); // 记录开始时间
|
||||
laser_off();
|
||||
break;
|
||||
case YUNT_NO2:
|
||||
gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
|
||||
if (DWT_GetTimeline_ms() - timestart_timeline >= 1500.0f)
|
||||
{
|
||||
laser_on();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Vison_revc_flag)
|
||||
{
|
||||
LIMIT_MIN_MAX(opencv_Vision_data[0], -100.0f, 100.0f);
|
||||
LIMIT_MIN_MAX(opencv_Vision_data[1], -100.0f, 100.0f);
|
||||
LIMIT_MIN_MAX(opencv_Vision_data[2], 500.0f, 1560.0f);
|
||||
yaw_error_kf.MeasuredVector[0] = opencv_Vision_data[0];
|
||||
float *yaw_filtered = Kalman_Filter_Update(&yaw_error_kf);
|
||||
filtered_yaw_error = yaw_filtered[0];
|
||||
pitch_error_kf.MeasuredVector[0] = opencv_Vision_data[1];
|
||||
float *pitch_filtered = Kalman_Filter_Update(&pitch_error_kf);
|
||||
filtered_pitch_error = pitch_filtered[0];
|
||||
yaw_tracking_pid_output = PIDCalculate(&yaw_tracking_pid, filtered_yaw_error, 0);
|
||||
pitch_tracking_pid_output = PIDCalculate(&pitch_tracking_pid, filtered_pitch_error, 0);
|
||||
gimbal_cmd_send.yaw -= yaw_tracking_pid_output * mapping(opencv_Vision_data[2], 500.0f, 1560.0f, yaw_control_kxmax, yaw_control_kxmin); // yaw角度控制
|
||||
gimbal_cmd_send.pitch += 0.001f * pitch_tracking_pid_output;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case YUNT_NO3_WAITING:
|
||||
gimbal_cmd_send.gimbal_mode = GIMBAL_ZERO_FORCE;
|
||||
gimbal_cmd_send.yaw = gimbal_fetch_data.gimbal_imu_data.YawTotalAngle;
|
||||
gimbal_cmd_send.pitch = gimbal_fetch_data.gimbal_imu_data.Pitch;
|
||||
// 计算0度对应最接近的多圈yaw
|
||||
yuntai_no3_yawtarget = roundf(gimbal_fetch_data.gimbal_imu_data.YawTotalAngle / 360.0f) * 360.0f; // 计算最近的多圈yaw
|
||||
laser_off();
|
||||
timestart_timeline = DWT_GetTimeline_ms(); // 记录开始时间
|
||||
break;
|
||||
case YUNT_NO3_TURING:
|
||||
gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
|
||||
float yaw_diff = gimbal_fetch_data.gimbal_imu_data.YawTotalAngle - yuntai_no3_yawtarget; // 计算当前yaw与目标yaw的差值
|
||||
float step_size = 0.8f; // 90度每秒, 500hz控制频率下每次0.18度
|
||||
if (fabsf(yaw_diff) > step_size) // 如果差值大于180度,则调整到最小角度
|
||||
{
|
||||
if (yaw_diff > 0)
|
||||
gimbal_cmd_send.yaw -= step_size;
|
||||
else
|
||||
gimbal_cmd_send.yaw += step_size;
|
||||
}
|
||||
if (fabsf(yaw_diff) <= 5.0f) // 如果差值小于5度,则认为转向完成
|
||||
{
|
||||
if (Vison_revc_flag)
|
||||
{
|
||||
YunTaiState = YUNT_NO3_TRACKING; // 进入跟踪状态
|
||||
}
|
||||
else
|
||||
{
|
||||
YunTaiState = YUNT_NO3_SEARCHING; // 进入搜索状态
|
||||
}
|
||||
}
|
||||
gimbal_cmd_send.pitch = 0;
|
||||
break;
|
||||
case YUNT_NO3_TRACKING:
|
||||
gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
|
||||
if (DWT_GetTimeline_ms() - timestart_timeline >= 3500.0f) // 如果超过3.5秒,则认为跟踪完成
|
||||
{
|
||||
laser_on();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Vison_revc_flag && Vison_Frequency >= 20.0f) // 如果视觉数据可用且频率大于20Hz
|
||||
{
|
||||
LIMIT_MIN_MAX(opencv_Vision_data[0], -100.0f, 100.0f);
|
||||
LIMIT_MIN_MAX(opencv_Vision_data[1], -100.0f, 100.0f);
|
||||
LIMIT_MIN_MAX(opencv_Vision_data[2], 500.0f, 1560.0f);
|
||||
yaw_error_kf.MeasuredVector[0] = opencv_Vision_data[0];
|
||||
float *yaw_filtered = Kalman_Filter_Update(&yaw_error_kf);
|
||||
filtered_yaw_error = yaw_filtered[0];
|
||||
pitch_error_kf.MeasuredVector[0] = opencv_Vision_data[1];
|
||||
float *pitch_filtered = Kalman_Filter_Update(&pitch_error_kf);
|
||||
filtered_pitch_error = pitch_filtered[0];
|
||||
|
||||
yaw_tracking_pid_output = PIDCalculate(&yaw_tracking_pid, filtered_yaw_error, 0);
|
||||
pitch_tracking_pid_output = PIDCalculate(&pitch_tracking_pid, filtered_pitch_error, 0);
|
||||
gimbal_cmd_send.yaw -= yaw_tracking_pid_output * mapping(opencv_Vision_data[2], 500.0f, 1560.0f, yaw_control_kxmax, yaw_control_kxmin); // yaw角度控制
|
||||
gimbal_cmd_send.pitch += 0.001f * pitch_tracking_pid_output;
|
||||
}
|
||||
else
|
||||
{
|
||||
YunTaiState = YUNT_NO3_SEARCHING; // 如果视觉数据不可用,则进入搜索状态
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case YUNT_NO3_SEARCHING:
|
||||
gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
|
||||
if (yuntai_no3_searching_dir == 0) // 如果当前是向左搜索
|
||||
{
|
||||
gimbal_cmd_send.yaw += yuntai_no3_searching_step; // 向左搜索
|
||||
if (gimbal_cmd_send.yaw > yuntai_no3_searching_yaw_max + yuntai_no3_yawtarget) // 如果超过最小值,则切换方向
|
||||
{
|
||||
yuntai_no3_searching_dir = 1; // 切换到向右搜索
|
||||
}
|
||||
}
|
||||
else // 当前是向右搜索
|
||||
{
|
||||
gimbal_cmd_send.yaw -= yuntai_no3_searching_step; // 向右搜索
|
||||
if (gimbal_cmd_send.yaw < yuntai_no3_searching_yaw_min + yuntai_no3_yawtarget) // 如果超过最大值,则切换方向
|
||||
{
|
||||
yuntai_no3_searching_dir = 0; // 切换到向左搜索
|
||||
}
|
||||
}
|
||||
|
||||
gimbal_cmd_send.pitch = 0.0f; // 保持pitch角度为0
|
||||
if (Vison_revc_flag && Vison_Frequency >= 20.0f)
|
||||
{
|
||||
yuntai_no3_searching_count--;
|
||||
}
|
||||
else
|
||||
{
|
||||
yuntai_no3_searching_count = 100; // 如果视觉数据可用但频率低于20Hz,则重置计数器
|
||||
}
|
||||
if (yuntai_no3_searching_count <= 0)
|
||||
{
|
||||
YunTaiState = YUNT_NO3_TRACKING; // 如果视觉数据可用,则进入跟踪状态
|
||||
}
|
||||
break;
|
||||
case YUNT_NO4_1_WAITING:
|
||||
gimbal_cmd_send.gimbal_mode = GIMBAL_ZERO_FORCE;
|
||||
gimbal_cmd_send.yaw = gimbal_fetch_data.gimbal_imu_data.YawTotalAngle;
|
||||
gimbal_cmd_send.pitch = gimbal_fetch_data.gimbal_imu_data.Pitch;
|
||||
// 计算0度对应最接近的多圈yaw
|
||||
yuntai_no3_yawtarget = roundf(gimbal_fetch_data.gimbal_imu_data.YawTotalAngle / 360.0f) * 360.0f; // 计算最近的多圈yaw
|
||||
no4_turning_count = 0; // 题号4转向计数器
|
||||
no4_turning_status = 0; // 题号4转向状态
|
||||
timestart_timeline = DWT_GetTimeline_ms(); // 记录开始时间
|
||||
laser_off();
|
||||
break;
|
||||
case YUNT_NO4_1_TURING:
|
||||
gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
|
||||
gimbal_cmd_send.yaw = yuntai_no3_yawtarget - 45.0f;
|
||||
gimbal_cmd_send.pitch = 0.0f; // 保持pitch角度为0
|
||||
if (fabsf(gimbal_fetch_data.gimbal_imu_data.YawTotalAngle - yuntai_no3_yawtarget + 45.0f) < 5.0f)
|
||||
{
|
||||
YunTaiState = YUNT_NO4_1_TRACKING; // 进入跟踪状态
|
||||
}
|
||||
break;
|
||||
case YUNT_NO4_1_TRACKING:
|
||||
gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
|
||||
if (Vison_revc_flag)
|
||||
{
|
||||
LIMIT_MIN_MAX(opencv_Vision_data[0], -100.0f, 100.0f);
|
||||
LIMIT_MIN_MAX(opencv_Vision_data[1], -100.0f, 100.0f);
|
||||
LIMIT_MIN_MAX(opencv_Vision_data[2], 500.0f, 1560.0f);
|
||||
yaw_error_kf.MeasuredVector[0] = opencv_Vision_data[0];
|
||||
float *yaw_filtered = Kalman_Filter_Update(&yaw_error_kf);
|
||||
filtered_yaw_error = yaw_filtered[0];
|
||||
pitch_error_kf.MeasuredVector[0] = opencv_Vision_data[1];
|
||||
float *pitch_filtered = Kalman_Filter_Update(&pitch_error_kf);
|
||||
filtered_pitch_error = pitch_filtered[0];
|
||||
|
||||
yaw_tracking_pid_output = PIDCalculate(&yaw_tracking_pid, filtered_yaw_error, 0);
|
||||
pitch_tracking_pid_output = PIDCalculate(&pitch_tracking_pid, filtered_pitch_error, 0);
|
||||
gimbal_cmd_send.yaw -= yaw_tracking_pid_output * mapping(opencv_Vision_data[2], 500.0f, 1560.0f, yaw_control_kxmax, yaw_control_kxmin); // yaw角度控制
|
||||
gimbal_cmd_send.pitch += 0.001f * pitch_tracking_pid_output;
|
||||
}
|
||||
break;
|
||||
case YUNT_NO4_1_TRACKING_RUNNING:
|
||||
gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
|
||||
if (board_comm_data->rx_float_data[1] >= 600.0f)
|
||||
{
|
||||
no4_turning_status = 1; // 题号4转向状态
|
||||
if (board_comm_data->rx_float_data[0] >= 0.0f && board_comm_data->rx_float_data[0] <= 12.5f)
|
||||
{
|
||||
no4_turning_count = 1;
|
||||
}
|
||||
else if (board_comm_data->rx_float_data[0] > 12.5f && board_comm_data->rx_float_data[0] <= 37.5f)
|
||||
{
|
||||
no4_turning_count = 2;
|
||||
}
|
||||
else if (board_comm_data->rx_float_data[0] > 37.5f && board_comm_data->rx_float_data[0] <= 62.5f)
|
||||
{
|
||||
no4_turning_count = 3;
|
||||
}
|
||||
else if (board_comm_data->rx_float_data[0] > 62.5f && board_comm_data->rx_float_data[0] <= 87.5f)
|
||||
{
|
||||
no4_turning_count = 4;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
no4_turning_status = 0; // 题号4转向状态
|
||||
}
|
||||
if (Vison_revc_flag)
|
||||
{
|
||||
LIMIT_MIN_MAX(opencv_Vision_data[0], -100.0f, 100.0f);
|
||||
LIMIT_MIN_MAX(opencv_Vision_data[1], -100.0f, 100.0f);
|
||||
LIMIT_MIN_MAX(opencv_Vision_data[2], 500.0f, 1560.0f);
|
||||
yaw_error_kf.MeasuredVector[0] = opencv_Vision_data[0];
|
||||
float *yaw_filtered = Kalman_Filter_Update(&yaw_error_kf);
|
||||
filtered_yaw_error = yaw_filtered[0];
|
||||
pitch_error_kf.MeasuredVector[0] = opencv_Vision_data[1];
|
||||
float *pitch_filtered = Kalman_Filter_Update(&pitch_error_kf);
|
||||
filtered_pitch_error = pitch_filtered[0];
|
||||
yaw_tracking_pid_output = PIDCalculate(&yaw_tracking_pid, filtered_yaw_error, 0);
|
||||
pitch_tracking_pid_output = PIDCalculate(&pitch_tracking_pid, filtered_pitch_error, 0);
|
||||
gimbal_cmd_send.pitch += 0.001f * pitch_tracking_pid_output;
|
||||
if (no4_turning_status == 1 && (no4_turning_count >=1 && no4_turning_count <=4)) // 如果题号4转向状态为1且转向计数器为1到4
|
||||
{
|
||||
yaw_tracking_pid.Kp = 3.0f; // 关闭yaw比例
|
||||
yaw_tracking_pid.Ki = 0.0f; // 关闭yaw积分
|
||||
yaw_tracking_pid.Iout = 0.0f; // 清零yaw积分输出
|
||||
gimbal_cmd_send.yaw -= yaw_tracking_pid_output * mapping(opencv_Vision_data[2], 500.0f, 1560.0f, yaw_control_kxmax, yaw_control_kxmin); // yaw角度控制
|
||||
}
|
||||
else
|
||||
{
|
||||
yaw_tracking_pid.Kp = 2.0f; // 关闭yaw比例
|
||||
yaw_tracking_pid.Ki = 4.0f;
|
||||
gimbal_cmd_send.yaw -= yaw_tracking_pid_output * mapping(opencv_Vision_data[2], 500.0f, 1560.0f, yaw_control_kxmax, yaw_control_kxmin); // yaw角度控制
|
||||
}
|
||||
laser_on();
|
||||
}
|
||||
break;
|
||||
case YUNT_NO4_3_WAITING:
|
||||
gimbal_cmd_send.gimbal_mode = GIMBAL_ZERO_FORCE;
|
||||
gimbal_cmd_send.yaw = gimbal_fetch_data.gimbal_imu_data.YawTotalAngle;
|
||||
gimbal_cmd_send.pitch = gimbal_fetch_data.gimbal_imu_data.Pitch;
|
||||
no4_turning_count = 0; // 题号4转向计数器
|
||||
no4_turning_status = 0; // 题号4转向状态
|
||||
|
||||
// 计算0度对应最接近的多圈yaw
|
||||
yuntai_no3_yawtarget = roundf(gimbal_fetch_data.gimbal_imu_data.YawTotalAngle / 360.0f) * 360.0f; // 计算最近的多圈yaw
|
||||
timestart_timeline = DWT_GetTimeline_ms(); // 记录开始时间
|
||||
laser_off();
|
||||
break;
|
||||
case YUNT_NO4_3_TURING:
|
||||
gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
|
||||
gimbal_cmd_send.yaw = yuntai_no3_yawtarget - 45.0f;
|
||||
gimbal_cmd_send.pitch = 0.0f; // 保持pitch角度为0
|
||||
if (fabsf(gimbal_fetch_data.gimbal_imu_data.YawTotalAngle - yuntai_no3_yawtarget + 45.0f) < 5.0f)
|
||||
{
|
||||
YunTaiState = YUNT_NO4_3_TRACKING; // 进入跟踪状态
|
||||
}
|
||||
break;
|
||||
case YUNT_NO4_3_TRACKING:
|
||||
gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
|
||||
if (Vison_revc_flag)
|
||||
{
|
||||
LIMIT_MIN_MAX(opencv_Vision_data[0], -100.0f, 100.0f);
|
||||
LIMIT_MIN_MAX(opencv_Vision_data[1], -100.0f, 100.0f);
|
||||
LIMIT_MIN_MAX(opencv_Vision_data[2], 500.0f, 1560.0f);
|
||||
yaw_error_kf.MeasuredVector[0] = opencv_Vision_data[0];
|
||||
float *yaw_filtered = Kalman_Filter_Update(&yaw_error_kf);
|
||||
filtered_yaw_error = yaw_filtered[0];
|
||||
pitch_error_kf.MeasuredVector[0] = opencv_Vision_data[1];
|
||||
float *pitch_filtered = Kalman_Filter_Update(&pitch_error_kf);
|
||||
filtered_pitch_error = pitch_filtered[0];
|
||||
|
||||
yaw_tracking_pid_output = PIDCalculate(&yaw_tracking_pid, filtered_yaw_error, 60.0f); // 题号4-3目标error为60
|
||||
pitch_tracking_pid_output = PIDCalculate(&pitch_tracking_pid, filtered_pitch_error, 0);
|
||||
gimbal_cmd_send.yaw -= yaw_tracking_pid_output * mapping(opencv_Vision_data[2], 500.0f, 1560.0f, yaw_control_kxmax, yaw_control_kxmin); // yaw角度控制
|
||||
gimbal_cmd_send.pitch += 0.001f * pitch_tracking_pid_output;
|
||||
}
|
||||
break;
|
||||
case YUNT_NO4_3_TRACKING_RUNNING:
|
||||
gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
|
||||
if (board_comm_data->rx_float_data[1] >= 600.0f)
|
||||
{
|
||||
no4_turning_status = 1; // 题号4转向状态
|
||||
if (board_comm_data->rx_float_data[0] >= 0.0f && board_comm_data->rx_float_data[0] <= 12.5f)
|
||||
{
|
||||
no4_turning_count = 1;
|
||||
}
|
||||
else if (board_comm_data->rx_float_data[0] > 12.5f && board_comm_data->rx_float_data[0] <= 37.5f)
|
||||
{
|
||||
no4_turning_count = 2;
|
||||
}
|
||||
else if (board_comm_data->rx_float_data[0] > 37.5f && board_comm_data->rx_float_data[0] <= 62.5f)
|
||||
{
|
||||
no4_turning_count = 3;
|
||||
}
|
||||
else if (board_comm_data->rx_float_data[0] > 62.5f && board_comm_data->rx_float_data[0] <= 87.5f)
|
||||
{
|
||||
no4_turning_count = 4;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
no4_turning_status = 0; // 题号4转向状态
|
||||
}
|
||||
if (Vison_revc_flag)
|
||||
{
|
||||
LIMIT_MIN_MAX(opencv_Vision_data[0], -100.0f, 100.0f);
|
||||
LIMIT_MIN_MAX(opencv_Vision_data[1], -100.0f, 100.0f);
|
||||
LIMIT_MIN_MAX(opencv_Vision_data[2], 500.0f, 1560.0f);
|
||||
yaw_error_kf.MeasuredVector[0] = opencv_Vision_data[0];
|
||||
float *yaw_filtered = Kalman_Filter_Update(&yaw_error_kf);
|
||||
filtered_yaw_error = yaw_filtered[0];
|
||||
pitch_error_kf.MeasuredVector[0] = opencv_Vision_data[1];
|
||||
float *pitch_filtered = Kalman_Filter_Update(&pitch_error_kf);
|
||||
filtered_pitch_error = pitch_filtered[0];
|
||||
// 画圆模式 500hz 16s画完整圆
|
||||
circle_progress = board_comm_data->rx_float_data[0] / 100.0f; // 画圆进度,从板间通信数据获取
|
||||
if (circle_progress >= 1.0f)
|
||||
{
|
||||
circle_progress = 1.0f; // 限制在100%
|
||||
}
|
||||
circle_angle = 2.0f * PI * circle_progress; // 计算当前圆周角度
|
||||
target_x = circle_radius * cosf(circle_angle); // 计算圆周上目标点x坐标
|
||||
target_y = circle_radius * sinf(circle_angle); // 计算圆周上目标点y坐标
|
||||
yaw_tracking_pid_output = PIDCalculate(&yaw_tracking_pid, filtered_yaw_error, target_x);
|
||||
pitch_tracking_pid_output = PIDCalculate(&pitch_tracking_pid, filtered_pitch_error, target_y);
|
||||
gimbal_cmd_send.pitch += pitch_tracking_pid_output * mapping(opencv_Vision_data[2], 500.0f, 1560.0f, pitch_control_kxmax, pitch_control_kxmin); // pitch角度控制
|
||||
if (no4_turning_status == 1 && (no4_turning_count == 2 || no4_turning_count == 3)) // 如果题号4转向状态为1且转向计数器为2或3
|
||||
{
|
||||
yaw_tracking_pid.Kp = 3.0f; // 关闭yaw比例
|
||||
yaw_tracking_pid.Ki = 0.0f; // 关闭yaw积分
|
||||
yaw_tracking_pid.Iout = 0.0f; // 清零yaw积分输出
|
||||
gimbal_cmd_send.yaw -= yaw_tracking_pid_output * mapping(opencv_Vision_data[2], 500.0f, 1560.0f, yaw_control_kxmax, yaw_control_kxmin); // yaw角度控制
|
||||
}
|
||||
else
|
||||
{
|
||||
yaw_tracking_pid.Kp = 1.5f; // 关闭yaw比例
|
||||
yaw_tracking_pid.Ki = 3.0f;
|
||||
gimbal_cmd_send.yaw -= yaw_tracking_pid_output * mapping(opencv_Vision_data[2], 500.0f, 1560.0f, yaw_control_kxmax, yaw_control_kxmin); // yaw角度控制
|
||||
}
|
||||
}
|
||||
laser_on();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 紧急停止,包括遥控器左上侧拨轮打满/重要模块离线/双板通信失效等
|
||||
* 停止的阈值'300'待修改成合适的值,或改为开关控制.
|
||||
*
|
||||
* @todo 后续修改为遥控器离线则电机停止(关闭遥控器急停),通过给遥控器模块添加daemon实现
|
||||
*
|
||||
*/
|
||||
static void EmergencyHandler()
|
||||
{
|
||||
// 拨轮的向下拨超过一半进入急停模式.注意向打时下拨轮是正
|
||||
if (switch_is_down(rc_data[TEMP].rc.switch_right) || robot_state == ROBOT_STOP) // 还需添加重要应用和模块离线的判断
|
||||
{
|
||||
robot_state = ROBOT_STOP;
|
||||
gimbal_cmd_send.gimbal_mode = GIMBAL_ZERO_FORCE;
|
||||
chassis_cmd_send.chassis_mode = CHASSIS_ZERO_FORCE;
|
||||
gimbal_cmd_send.yaw = gimbal_fetch_data.gimbal_imu_data.YawTotalAngle;
|
||||
gimbal_cmd_send.pitch = gimbal_fetch_data.gimbal_imu_data.Pitch;
|
||||
LOGERROR("[CMD] emergency stop!");
|
||||
}
|
||||
// 遥控器右侧开关为[上],恢复正常运行
|
||||
if (!switch_is_down(rc_data[TEMP].rc.switch_right) && robot_state == ROBOT_STOP)
|
||||
{
|
||||
robot_state = ROBOT_READY;
|
||||
LOGINFO("[CMD] reinstate, robot ready");
|
||||
}
|
||||
}
|
||||
|
||||
/* 机器人核心控制任务,200Hz频率运行(必须高于视觉发送频率) */
|
||||
void RobotCMDTask()
|
||||
{
|
||||
// 从其他应用获取回传数据
|
||||
#ifdef ONE_BOARD
|
||||
SubGetMessage(chassis_feed_sub, (void *)&chassis_fetch_data);
|
||||
#endif // ONE_BOARD
|
||||
#ifdef GIMBAL_BOARD
|
||||
chassis_fetch_data = *(Chassis_Upload_Data_s *)CANCommGet(cmd_can_comm);
|
||||
#endif // GIMBAL_BOARD
|
||||
SubGetMessage(shoot_feed_sub, &shoot_fetch_data);
|
||||
SubGetMessage(gimbal_feed_sub, &gimbal_fetch_data);
|
||||
// 根据遥控器左侧开关,确定当前使用的控制模式为遥控器调试还是键鼠
|
||||
if (switch_is_mid(rc_data[TEMP].rc.switch_right))
|
||||
{
|
||||
RemoteControlSet();
|
||||
}
|
||||
else if (switch_is_up(rc_data[TEMP].rc.switch_right) || !RemoteControlIsOnline())
|
||||
{
|
||||
AutoSet();
|
||||
}
|
||||
|
||||
// AutoSet();
|
||||
|
||||
EmergencyHandler(); // 处理模块离线和遥控器急停等紧急情况
|
||||
#ifdef ONE_BOARD
|
||||
PubPushMessage(chassis_cmd_pub, (void *)&chassis_cmd_send);
|
||||
#endif // ONE_BOARD
|
||||
#ifdef GIMBAL_BOARD
|
||||
CANCommSend(cmd_can_comm, (void *)&chassis_cmd_send);
|
||||
#endif // GIMBAL_BOARD
|
||||
PubPushMessage(shoot_cmd_pub, (void *)&shoot_cmd_send);
|
||||
PubPushMessage(gimbal_cmd_pub, (void *)&gimbal_cmd_send);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#ifndef ROBOT_CMD_H
|
||||
#define ROBOT_CMD_H
|
||||
|
||||
|
||||
/**
|
||||
* @brief 机器人核心控制任务初始化,会被RobotInit()调用
|
||||
*
|
||||
*/
|
||||
void RobotCMDInit();
|
||||
|
||||
/**
|
||||
* @brief 机器人核心控制任务,200Hz频率运行(必须高于视觉发送频率)
|
||||
*
|
||||
*/
|
||||
void RobotCMDTask();
|
||||
|
||||
|
||||
#endif // !ROBOT_CMD_H
|
||||
@@ -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
|
||||
|
||||
发射:鼠标左键
|
||||
|
||||
自瞄:鼠标右键
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
#include "kalman_filter.h"
|
||||
|
||||
// 添加卡尔曼滤波器实例
|
||||
static KalmanFilter_t yaw_error_kf;
|
||||
static KalmanFilter_t pitch_error_kf;
|
||||
static float filtered_yaw_error = 0.0f;
|
||||
static float filtered_pitch_error = 0.0f;
|
||||
|
||||
// 在RobotCMDInit()中添加卡尔曼滤波器初始化
|
||||
static void VisionKalmanFilterInit()
|
||||
{
|
||||
// yaw误差卡尔曼滤波器初始化
|
||||
Kalman_Filter_Init(&yaw_error_kf, 2, 0, 1); // 状态2维(位置,速度), 控制0维, 观测1维
|
||||
|
||||
// 减小初始协方差矩阵P,增加收敛速度
|
||||
static float P_init_yaw[4] = {
|
||||
5.0f, 0.0f, // 减小初始不确定性
|
||||
0.0f, 5.0f
|
||||
};
|
||||
memcpy(yaw_error_kf.P_data, P_init_yaw, sizeof(P_init_yaw));
|
||||
|
||||
// 状态转移矩阵F (dt = 1/200 = 0.005s, 200Hz控制频率)
|
||||
static float F_init_yaw[4] = {
|
||||
1.0f, 0.005f, // 修正时间步长
|
||||
0.0f, 1.0f
|
||||
};
|
||||
memcpy(yaw_error_kf.F_data, F_init_yaw, sizeof(F_init_yaw));
|
||||
|
||||
// 增大过程噪声协方差矩阵Q,允许更快的状态变化
|
||||
static float Q_init_yaw[4] = {
|
||||
0.1f, 0.0f, // 增大位置过程噪声
|
||||
0.0f, 1.0f // 增大速度过程噪声
|
||||
};
|
||||
memcpy(yaw_error_kf.Q_data, Q_init_yaw, sizeof(Q_init_yaw));
|
||||
|
||||
// 观测矩阵H
|
||||
static uint8_t measurement_map_yaw[1] = {1}; // 观测第1个状态(位置)
|
||||
static float measurement_degree_yaw[1] = {1.0f};
|
||||
static float mat_R_diagonal_yaw[1] = {1.0f}; // 减小观测噪声,增加对观测的信任
|
||||
static float state_min_variance_yaw[2] = {0.001f, 0.001f}; // 减小最小方差
|
||||
|
||||
yaw_error_kf.UseAutoAdjustment = 1;
|
||||
memcpy(yaw_error_kf.MeasurementMap, measurement_map_yaw, sizeof(measurement_map_yaw));
|
||||
memcpy(yaw_error_kf.MeasurementDegree, measurement_degree_yaw, sizeof(measurement_degree_yaw));
|
||||
memcpy(yaw_error_kf.MatR_DiagonalElements, mat_R_diagonal_yaw, sizeof(mat_R_diagonal_yaw));
|
||||
memcpy(yaw_error_kf.StateMinVariance, state_min_variance_yaw, sizeof(state_min_variance_yaw));
|
||||
|
||||
// pitch误差卡尔曼滤波器初始化 - 使用相同的优化参数
|
||||
Kalman_Filter_Init(&pitch_error_kf, 2, 0, 1);
|
||||
|
||||
static float P_init_pitch[4] = {
|
||||
5.0f, 0.0f,
|
||||
0.0f, 5.0f
|
||||
};
|
||||
memcpy(pitch_error_kf.P_data, P_init_pitch, sizeof(P_init_pitch));
|
||||
|
||||
static float F_init_pitch[4] = {
|
||||
1.0f, 0.005f,
|
||||
0.0f, 1.0f
|
||||
};
|
||||
memcpy(pitch_error_kf.F_data, F_init_pitch, sizeof(F_init_pitch));
|
||||
|
||||
static float Q_init_pitch[4] = {
|
||||
0.1f, 0.0f,
|
||||
0.0f, 1.0f
|
||||
};
|
||||
memcpy(pitch_error_kf.Q_data, Q_init_pitch, sizeof(Q_init_pitch));
|
||||
|
||||
static uint8_t measurement_map_pitch[1] = {1};
|
||||
static float measurement_degree_pitch[1] = {1.0f};
|
||||
static float mat_R_diagonal_pitch[1] = {1.0f};
|
||||
static float state_min_variance_pitch[2] = {0.001f, 0.001f};
|
||||
|
||||
pitch_error_kf.UseAutoAdjustment = 1;
|
||||
memcpy(pitch_error_kf.MeasurementMap, measurement_map_pitch, sizeof(measurement_map_pitch));
|
||||
memcpy(pitch_error_kf.MeasurementDegree, measurement_degree_pitch, sizeof(measurement_degree_pitch));
|
||||
memcpy(pitch_error_kf.MatR_DiagonalElements, mat_R_diagonal_pitch, sizeof(mat_R_diagonal_pitch));
|
||||
memcpy(pitch_error_kf.StateMinVariance, state_min_variance_pitch, sizeof(state_min_variance_pitch));
|
||||
}
|
||||
|
||||
// 更新RemoteControlSet()函数中的视觉处理部分
|
||||
static void RemoteControlSet()
|
||||
{
|
||||
chassis_cmd_send.chassis_mode = CHASSIS_NO_FOLLOW;
|
||||
gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
|
||||
|
||||
if (switch_is_up(rc_data[TEMP].rc.switch_left))
|
||||
{
|
||||
if (Vison_revc_flag)
|
||||
{
|
||||
// 限制视觉数据范围
|
||||
LIMIT_MIN_MAX(opencv_Vision_data[0], -100.0f, 100.0f);
|
||||
LIMIT_MIN_MAX(opencv_Vision_data[1], -100.0f, 100.0f);
|
||||
|
||||
// 更新卡尔曼滤波器观测值
|
||||
yaw_error_kf.MeasuredVector[0] = opencv_Vision_data[0];
|
||||
pitch_error_kf.MeasuredVector[0] = opencv_Vision_data[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
// 无视觉数据时,观测值设为0(表示无效)
|
||||
yaw_error_kf.MeasuredVector[0] = 0.0f;
|
||||
pitch_error_kf.MeasuredVector[0] = 0.0f;
|
||||
}
|
||||
|
||||
// 执行卡尔曼滤波更新
|
||||
float *yaw_filtered = Kalman_Filter_Update(&yaw_error_kf);
|
||||
float *pitch_filtered = Kalman_Filter_Update(&pitch_error_kf);
|
||||
|
||||
filtered_yaw_error = yaw_filtered[0];
|
||||
filtered_pitch_error = pitch_filtered[0];
|
||||
|
||||
// 使用滤波后的误差进行PID控制
|
||||
yaw_tracking_pid_output = PIDCalculate(&yaw_tracking_pid, filtered_yaw_error, 0);
|
||||
pitch_tracking_pid_output = PIDCalculate(&pitch_tracking_pid, filtered_pitch_error, 0);
|
||||
|
||||
gimbal_cmd_send.yaw += 0.001f * yaw_tracking_pid_output;
|
||||
gimbal_cmd_send.pitch -= 0.001f * pitch_tracking_pid_output;
|
||||
|
||||
// 调试输出
|
||||
vofa_debug[0] = filtered_yaw_error;
|
||||
vofa_debug[1] = filtered_pitch_error;
|
||||
vofa_debug[2] = yaw_tracking_pid.Err;
|
||||
vofa_debug[3] = yaw_tracking_pid.dt;
|
||||
vofa_debug[4] = yaw_tracking_pid.Iout;
|
||||
vofa_debug[5] = yaw_tracking_pid.Dout;
|
||||
vofa_debug[6] = yaw_tracking_pid.Output;
|
||||
vofa_justfloat_output(vofa_debug, 7, &huart7);
|
||||
}
|
||||
else
|
||||
{
|
||||
gimbal_cmd_send.yaw += 0.0025f * (float)rc_data[TEMP].rc.rocker_r_;
|
||||
gimbal_cmd_send.pitch -= 0.00125f * (float)rc_data[TEMP].rc.rocker_r1;
|
||||
}
|
||||
|
||||
if (gimbal_cmd_send.pitch > 30.0f)
|
||||
gimbal_cmd_send.pitch = 30.0f;
|
||||
else if (gimbal_cmd_send.pitch < -30.0f)
|
||||
gimbal_cmd_send.pitch = -30.0f;
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
#include "gimbal.h"
|
||||
#include "robot_def.h"
|
||||
#include "dji_motor.h"
|
||||
#include "dmmotor.h"
|
||||
#include "ins_task.h"
|
||||
#include "message_center.h"
|
||||
#include "general_def.h"
|
||||
#include "bmi088.h"
|
||||
#include "vofa.h"
|
||||
|
||||
static attitude_t *gimba_IMU_data; // 云台IMU数据
|
||||
|
||||
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的控制信息
|
||||
|
||||
static DJIMotorInstance *yaw_motor; // 云台yaw电机实例
|
||||
static DMMotorInstance *pitch_motor; // 云台pitch电机实例
|
||||
|
||||
static float yaw_speed_feedforward = 0.0f; // yaw电机的速度前馈
|
||||
static float yaw_current_feedforward_abs = 500.0f;
|
||||
static float yaw_current_feedforward = 0.0f; // yaw电机的电流前馈
|
||||
|
||||
static float yaw_last_ref = 0.0f; // 上一次yaw电机的设定值,用于计算前馈
|
||||
|
||||
static BMI088Instance *bmi088; // 云台IMU
|
||||
void GimbalInit()
|
||||
{
|
||||
HAL_GPIO_WritePin(POWER_24V_1_GPIO_Port, POWER_24V_1_Pin, GPIO_PIN_SET);
|
||||
gimba_IMU_data = INS_Init(); // IMU先初始化,获取姿态数据指针赋给yaw电机的其他数据来源
|
||||
HAL_GPIO_WritePin(POWER_24V_1_GPIO_Port, POWER_24V_1_Pin, GPIO_PIN_RESET);
|
||||
gimbal_pub = PubRegister("gimbal_feed", sizeof(Gimbal_Upload_Data_s));
|
||||
gimbal_sub = SubRegister("gimbal_cmd", sizeof(Gimbal_Ctrl_Cmd_s));
|
||||
|
||||
// 初始化yaw电机
|
||||
Motor_Init_Config_s yaw_motor_config = {
|
||||
.can_init_config.can_handle = &hcan1,
|
||||
.can_init_config.tx_id = 0x01,
|
||||
.controller_param_init_config = {
|
||||
.angle_PID = {
|
||||
.Kp = 0.3,
|
||||
.Ki = 0.8,
|
||||
.Kd = 0.005,
|
||||
.IntegralLimit = 20,
|
||||
.CoefA = 15.0f,
|
||||
.CoefB = 1.0f,
|
||||
.Improve = PID_Trapezoid_Intergral | PID_Integral_Limit | PID_Derivative_On_Measurement | PID_ChangingIntegrationRate,
|
||||
.MaxOut = 20,
|
||||
},
|
||||
.speed_PID = {
|
||||
.Kp = 1200,
|
||||
.Ki = 0,
|
||||
.Kd = 3,
|
||||
.IntegralLimit = 16000,
|
||||
.Output_LPF_RC = 0.001,
|
||||
.Improve = PID_Trapezoid_Intergral | PID_Integral_Limit | PID_Derivative_On_Measurement | PID_OutputFilter,
|
||||
.MaxOut = 16000,
|
||||
},
|
||||
.other_angle_feedback_ptr = &gimba_IMU_data->YawTotalAngle,
|
||||
.other_speed_feedback_ptr = &gimba_IMU_data->Gyro[2],
|
||||
.speed_feedforward_ptr = &yaw_speed_feedforward, // 速度前馈指针
|
||||
|
||||
},
|
||||
.controller_setting_init_config = {
|
||||
.angle_feedback_source = OTHER_FEED, .speed_feedback_source = OTHER_FEED,
|
||||
.outer_loop_type = ANGLE_LOOP, // 设置为开环,电机设定值由下面的功率控制设定,不走普通的pid
|
||||
.close_loop_type = SPEED_LOOP | ANGLE_LOOP, // 角度环和速度环
|
||||
.motor_reverse_flag = MOTOR_DIRECTION_NORMAL,
|
||||
.feedforward_flag = SPEED_FEEDFORWARD | CURRENT_FEEDFORWARD, // 开启速度前馈
|
||||
},
|
||||
.motor_type = GM6020_NEW,
|
||||
};
|
||||
yaw_motor = DJIMotorInit(&yaw_motor_config);
|
||||
// 初始化PITCH电机
|
||||
Motor_Init_Config_s pitch_config = {
|
||||
.can_init_config = {
|
||||
.can_handle = &hcan1,
|
||||
.tx_id = 0x06,
|
||||
.rx_id = 0x16,
|
||||
},
|
||||
.controller_param_init_config = {
|
||||
.angle_PID = {
|
||||
.Kp = 1,
|
||||
.Ki = 0,
|
||||
.Kd = 0.005,
|
||||
.Improve = PID_Trapezoid_Intergral | PID_Integral_Limit | PID_Derivative_On_Measurement | PID_OutputFilter | PID_DerivativeFilter,
|
||||
.IntegralLimit = 10,
|
||||
.MaxOut = 10,
|
||||
},
|
||||
.speed_PID = {
|
||||
.Kp = 0.8,
|
||||
.Ki = 0,
|
||||
.Kd = 0,
|
||||
.Improve = PID_Trapezoid_Intergral | PID_Integral_Limit | PID_Derivative_On_Measurement,
|
||||
.IntegralLimit = 0.1,
|
||||
.MaxOut = 1,
|
||||
},
|
||||
.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,
|
||||
.feedback_reverse_flag = FEEDBACK_DIRECTION_NORMAL,
|
||||
.motor_reverse_flag = MOTOR_DIRECTION_NORMAL,
|
||||
},
|
||||
};
|
||||
pitch_motor = DMMotorInit(&pitch_config);
|
||||
}
|
||||
|
||||
/* 机器人云台控制核心任务,后续考虑只保留IMU控制,不再需要电机的反馈 */
|
||||
void GimbalTask()
|
||||
{
|
||||
// 获取云台控制数据
|
||||
// 后续增加未收到数据的处理
|
||||
SubGetMessage(gimbal_sub, &gimbal_cmd_recv);
|
||||
|
||||
switch (gimbal_cmd_recv.gimbal_mode)
|
||||
{
|
||||
// 停止
|
||||
case GIMBAL_ZERO_FORCE:
|
||||
DJIMotorStop(yaw_motor);
|
||||
DMMotorStop(pitch_motor);
|
||||
|
||||
yaw_speed_feedforward = 0.0f; // 停止时清零前馈
|
||||
yaw_last_ref = 0.0f; // 停止时清零上一次参考值
|
||||
break;
|
||||
// 使用陀螺仪的反馈,底盘根据yaw电机的offset跟随云台或视觉模式采用
|
||||
case GIMBAL_GYRO_MODE: // 后续只保留此模式
|
||||
DJIMotorEnable(yaw_motor);
|
||||
DMMotorEnable(pitch_motor);
|
||||
|
||||
yaw_speed_feedforward = DEGREE_2_RAD * (gimbal_cmd_recv.yaw - yaw_last_ref) * 500.0f;
|
||||
LIMIT_MIN_MAX(yaw_speed_feedforward, -10.0f, 10.0f); // 限制前馈值
|
||||
yaw_last_ref = gimbal_cmd_recv.yaw; // 更新上一次的参考值
|
||||
DJIMotorSetRef(yaw_motor, gimbal_cmd_recv.yaw); // yaw和pitch会在robot_cmd中处理好多圈和单圈
|
||||
DMMotorSetRef(pitch_motor, gimbal_cmd_recv.pitch);
|
||||
if (yaw_motor->motor_controller.speed_PID.Ref >= 0.5f)
|
||||
{
|
||||
yaw_current_feedforward = 500.0f; // 前馈电流
|
||||
}
|
||||
else if (yaw_motor->motor_controller.speed_PID.Ref <= -0.5f)
|
||||
{
|
||||
yaw_current_feedforward = -500.0f; // 前馈电流
|
||||
}
|
||||
else
|
||||
{
|
||||
yaw_current_feedforward = yaw_current_feedforward_abs * yaw_motor->motor_controller.speed_PID.Ref / 0.5f; // 前馈电流
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// 设置反馈数据,主要是imu和yaw的ecd
|
||||
gimbal_feedback_data.gimbal_imu_data = *gimba_IMU_data;
|
||||
|
||||
// 推送消息
|
||||
PubPushMessage(gimbal_pub, (void *)&gimbal_feedback_data);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef GIMBAL_H
|
||||
#define GIMBAL_H
|
||||
|
||||
/**
|
||||
* @brief 初始化云台,会被RobotInit()调用
|
||||
*
|
||||
*/
|
||||
void GimbalInit();
|
||||
|
||||
/**
|
||||
* @brief 云台任务
|
||||
*
|
||||
*/
|
||||
void GimbalTask();
|
||||
|
||||
#endif // GIMBAL_H
|
||||
@@ -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话题下
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#ifndef ROBOT_H
|
||||
#define ROBOT_H
|
||||
|
||||
/* Robot利用robot_def.h中的宏对不同的机器人进行了大量的兼容,同时兼容了两个开发板(云台板和底盘板)的配置 */
|
||||
|
||||
/**
|
||||
* @brief 机器人初始化,请在开启rtos之前调用.这也是唯一需要放入main函数的函数
|
||||
*
|
||||
*/
|
||||
void RobotInit();
|
||||
|
||||
/**
|
||||
* @brief 机器人任务,放入实时系统以一定频率运行,内部会调用各个应用的任务
|
||||
*
|
||||
*/
|
||||
void RobotTask();
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* @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 2711 // 云台和底盘对齐指向相同方向时的电机编码器值,若对云台有机械改动需要修改
|
||||
#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 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;
|
||||
uint8_t gimbal_power;
|
||||
} Gimbal_Upload_Data_s;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// code to go here
|
||||
// ...
|
||||
} Shoot_Upload_Data_s;
|
||||
|
||||
#pragma pack() // 开启字节对齐,结束前面的#pragma pack(1)
|
||||
|
||||
#endif // !ROBOT_DEF_H
|
||||
@@ -0,0 +1,140 @@
|
||||
/* 注意该文件应只用于任务初始化,只能被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 "dmmotor.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);
|
||||
DMMotorControlInit(); // 初始化大电机
|
||||
|
||||
|
||||
}
|
||||
|
||||
__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);
|
||||
osDelay(1);
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((noreturn)) void StartMOTORTASK(void const *argument)
|
||||
{
|
||||
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 > 1)
|
||||
LOGERROR("[freeRTOS] MOTOR Task is being DELAY! dt = [%f]", &motor_dt);
|
||||
osDelay(1);
|
||||
}
|
||||
}
|
||||
|
||||
__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(2);
|
||||
}
|
||||
}
|
||||
|
||||
__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(1); // 即使没有任何UI需要刷新,也挂起一次,防止卡在UITask中无法切换
|
||||
}
|
||||
}
|
||||
@@ -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 = 3,
|
||||
},
|
||||
.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);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef SHOOT_H
|
||||
#define SHOOT_H
|
||||
|
||||
/**
|
||||
* @brief 发射初始化,会被RobotInit()调用
|
||||
*
|
||||
*/
|
||||
void ShootInit();
|
||||
|
||||
/**
|
||||
* @brief 发射任务
|
||||
*
|
||||
*/
|
||||
void ShootTask();
|
||||
|
||||
#endif // SHOOT_H
|
||||
@@ -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