384 lines
19 KiB
C
384 lines
19 KiB
C
#include "dji_motor.h"
|
||
#include "general_def.h"
|
||
#include "bsp_dwt.h"
|
||
#include "bsp_log.h"
|
||
#include "user_lib.h"
|
||
|
||
static uint8_t idx = 0; // register idx,是该文件的全局电机索引,在注册时使用
|
||
/* DJI电机的实例,此处仅保存指针,内存的分配将通过电机实例初始化时通过malloc()进行 */
|
||
static DJIMotorInstance *dji_motor_instance[DJI_MOTOR_CNT] = {NULL}; // 会在control任务中遍历该指针数组进行pid计算
|
||
|
||
/**
|
||
* @brief 由于DJI电机发送以四个一组的形式进行,故对其进行特殊处理,用6个(2can*3group)can_instance专门负责发送
|
||
* 该变量将在 DJIMotorControl() 中使用,分组在 MotorSenderGrouping()中进行
|
||
*
|
||
* @note 因为只用于发送,所以不需要在bsp_can中注册
|
||
*
|
||
* C610(m2006)/C620(m3508):0x1ff,0x200;
|
||
* GM6020:0x1ff,0x2ff
|
||
* 反馈(rx_id): GM6020: 0x204+id ; C610/C620: 0x200+id
|
||
* can1: [0]:0x1FF,[1]:0x200,[2]:0x2FF
|
||
* can2: [3]:0x1FF,[4]:0x200,[5]:0x2FF
|
||
*/
|
||
static CANInstance sender_assignment[10] = {
|
||
[0] = {.can_handle = &hcan1, .txconf.StdId = 0x1ff, .txconf.IDE = CAN_ID_STD, .txconf.RTR = CAN_RTR_DATA, .txconf.DLC = 0x08, .tx_buff = {
|
||
0}},
|
||
[1] = {.can_handle = &hcan1, .txconf.StdId = 0x200, .txconf.IDE = CAN_ID_STD, .txconf.RTR = CAN_RTR_DATA, .txconf.DLC = 0x08, .tx_buff = {
|
||
0}},
|
||
[2] = {.can_handle = &hcan1, .txconf.StdId = 0x2ff, .txconf.IDE = CAN_ID_STD, .txconf.RTR = CAN_RTR_DATA, .txconf.DLC = 0x08, .tx_buff = {
|
||
0}},
|
||
[3] = {.can_handle = &hcan2, .txconf.StdId = 0x1ff, .txconf.IDE = CAN_ID_STD, .txconf.RTR = CAN_RTR_DATA, .txconf.DLC = 0x08, .tx_buff = {
|
||
0}},
|
||
[4] = {.can_handle = &hcan2, .txconf.StdId = 0x200, .txconf.IDE = CAN_ID_STD, .txconf.RTR = CAN_RTR_DATA, .txconf.DLC = 0x08, .tx_buff = {
|
||
0}},
|
||
[5] = {.can_handle = &hcan2, .txconf.StdId = 0x2ff, .txconf.IDE = CAN_ID_STD, .txconf.RTR = CAN_RTR_DATA, .txconf.DLC = 0x08, .tx_buff = {
|
||
0}},
|
||
[6] = {.can_handle = &hcan1, .txconf.StdId = 0x1fe, .txconf.IDE = CAN_ID_STD, .txconf.RTR = CAN_RTR_DATA, .txconf.DLC = 0x08, .tx_buff = {
|
||
0}},
|
||
[7] = {.can_handle = &hcan1, .txconf.StdId = 0x2fe, .txconf.IDE = CAN_ID_STD, .txconf.RTR = CAN_RTR_DATA, .txconf.DLC = 0x08, .tx_buff = {
|
||
0}},
|
||
[8] = {.can_handle = &hcan2, .txconf.StdId = 0x1fe, .txconf.IDE = CAN_ID_STD, .txconf.RTR = CAN_RTR_DATA, .txconf.DLC = 0x08, .tx_buff = {
|
||
0}},
|
||
[9] = {.can_handle = &hcan2, .txconf.StdId = 0x2fe, .txconf.IDE = CAN_ID_STD, .txconf.RTR = CAN_RTR_DATA, .txconf.DLC = 0x08, .tx_buff = {
|
||
0}}
|
||
};
|
||
|
||
/**
|
||
* @brief 6个用于确认是否有电机注册到sender_assignment中的标志位,防止发送空帧,此变量将在DJIMotorControl()使用
|
||
* flag的初始化在 MotorSenderGrouping()中进行
|
||
*/
|
||
static uint8_t sender_enable_flag[6] = {0};
|
||
|
||
/**
|
||
* @brief 根据电调/拨码开关上的ID,根据说明书的默认id分配方式计算发送ID和接收ID,
|
||
* 并对电机进行分组以便处理多电机控制命令
|
||
*/
|
||
static void MotorSenderGrouping(DJIMotorInstance *motor, CAN_Init_Config_s *config) {
|
||
uint8_t motor_id = config->tx_id - 1; // 下标从零开始,先减一方便赋值
|
||
uint8_t motor_send_num;
|
||
uint8_t motor_grouping;
|
||
|
||
switch (motor->motor_type) {
|
||
case M2006:
|
||
case M3508:
|
||
if (motor_id < 4) // 根据ID分组
|
||
{
|
||
motor_send_num = motor_id;
|
||
motor_grouping = config->can_handle == &hcan1 ? 1 : 4;
|
||
} else {
|
||
motor_send_num = motor_id - 4;
|
||
motor_grouping = config->can_handle == &hcan1 ? 0 : 3;
|
||
}
|
||
|
||
// 计算接收id并设置分组发送id
|
||
config->rx_id = 0x200 + motor_id + 1; // 把ID+1,进行分组设置
|
||
sender_enable_flag[motor_grouping] = 1; // 设置发送标志位,防止发送空帧
|
||
motor->message_num = motor_send_num;
|
||
motor->sender_group = motor_grouping;
|
||
|
||
// 检查是否发生id冲突
|
||
for (size_t i = 0; i < idx; ++i) {
|
||
if (dji_motor_instance[i]->motor_can_instance->can_handle == config->can_handle &&
|
||
dji_motor_instance[i]->motor_can_instance->rx_id == config->rx_id) {
|
||
LOGERROR(
|
||
"[dji_motor] ID crash. Check in debug mode, add dji_motor_instance to watch to get more information.");
|
||
uint16_t can_bus = config->can_handle == &hcan1 ? 1 : 2;
|
||
while (1) // 6020的id 1-4和2006/3508的id 5-8会发生冲突(若有注册,即1!5,2!6,3!7,4!8) (1!5!,LTC! (((不是)
|
||
LOGERROR("[dji_motor] id [%d], can_bus [%d]", config->rx_id, can_bus);
|
||
}
|
||
}
|
||
break;
|
||
|
||
case GM6020:
|
||
if(motor->motor_control_type == CURRENT_CONTROL) //6020电流控制帧id 0x1fe 0x2fe
|
||
{
|
||
if (motor_id < 4)
|
||
{
|
||
motor_send_num = motor_id;
|
||
motor_grouping = config->can_handle == &hcan1 ? 6 : 8;
|
||
}
|
||
else
|
||
{
|
||
motor_send_num = motor_id - 4;
|
||
motor_grouping = config->can_handle == &hcan1 ? 7 : 9;
|
||
}
|
||
}else{
|
||
if (motor_id < 4)
|
||
{
|
||
motor_send_num = motor_id;
|
||
motor_grouping = config->can_handle == &hcan1 ? 0 : 3;
|
||
}
|
||
else
|
||
{
|
||
motor_send_num = motor_id - 4;
|
||
motor_grouping = config->can_handle == &hcan1 ? 2 : 5;
|
||
}
|
||
}
|
||
|
||
config->rx_id = 0x204 + motor_id + 1; // 把ID+1,进行分组设置
|
||
sender_enable_flag[motor_grouping] = 1; // 只要有电机注册到这个分组,置为1;在发送函数中会通过此标志判断是否有电机注册
|
||
motor->message_num = motor_send_num;
|
||
motor->sender_group = motor_grouping;
|
||
|
||
for (size_t i = 0; i < idx; ++i) {
|
||
if (dji_motor_instance[i]->motor_can_instance->can_handle == config->can_handle &&
|
||
dji_motor_instance[i]->motor_can_instance->rx_id == config->rx_id) {
|
||
LOGERROR(
|
||
"[dji_motor] ID crash. Check in debug mode, add dji_motor_instance to watch to get more information.");
|
||
uint16_t can_bus = config->can_handle == &hcan1 ? 1 : 2;
|
||
while (1) // 6020的id 1-4和2006/3508的id 5-8会发生冲突(若有注册,即1!5,2!6,3!7,4!8) (1!5!,LTC! (((不是)
|
||
LOGERROR("[dji_motor] id [%d], can_bus [%d]", config->rx_id, can_bus);
|
||
}
|
||
}
|
||
break;
|
||
|
||
default: // other motors should not be registered here
|
||
while (1)
|
||
LOGERROR("[dji_motor]You must not register other motors using the API of DJI motor."); // 其他电机不应该在这里注册
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @todo 是否可以简化多圈角度的计算?
|
||
* @brief 根据返回的can_instance对反馈报文进行解析
|
||
*
|
||
* @param _instance 收到数据的instance,通过遍历与所有电机进行对比以选择正确的实例
|
||
*/
|
||
static void DecodeDJIMotor(CANInstance *_instance) {
|
||
// 这里对can instance的id进行了强制转换,从而获得电机的instance实例地址
|
||
// _instance指针指向的id是对应电机instance的地址,通过强制转换为电机instance的指针,再通过->运算符访问电机的成员motor_measure,最后取地址获得指针
|
||
uint8_t *rxbuff = _instance->rx_buff;
|
||
DJIMotorInstance *motor = (DJIMotorInstance *) _instance->id;
|
||
DJI_Motor_Measure_s *measure = &motor->measure; // measure要多次使用,保存指针减小访存开销
|
||
|
||
DaemonReload(motor->daemon);
|
||
motor->dt = DWT_GetDeltaT(&motor->feed_cnt);
|
||
|
||
// 解析数据并对电流和速度进行滤波,电机的反馈报文具体格式见电机说明手册
|
||
measure->last_ecd = measure->ecd;
|
||
measure->ecd = ((uint16_t) rxbuff[0]) << 8 | rxbuff[1];
|
||
measure->angle_single_round = ECD_ANGLE_COEF_DJI * (float) measure->ecd;
|
||
measure->speed_aps = (1.0f - SPEED_SMOOTH_COEF) * measure->speed_aps +
|
||
RPM_2_ANGLE_PER_SEC * SPEED_SMOOTH_COEF * (float) ((int16_t) (rxbuff[2] << 8 | rxbuff[3]));
|
||
measure->real_current = (1.0f - CURRENT_SMOOTH_COEF) * measure->real_current +
|
||
CURRENT_SMOOTH_COEF * (float) ((int16_t) (rxbuff[4] << 8 | rxbuff[5]));
|
||
measure->temperature = rxbuff[6];
|
||
|
||
// 多圈角度计算,前提是假设两次采样间电机转过的角度小于180°,自己画个图就清楚计算过程了
|
||
if (measure->ecd - measure->last_ecd > 4096)
|
||
measure->total_round--;
|
||
else if (measure->ecd - measure->last_ecd < -4096)
|
||
measure->total_round++;
|
||
measure->total_angle = measure->total_round * 360 + measure->angle_single_round;
|
||
}
|
||
|
||
static void DJIMotorLostCallback(void *motor_ptr) {
|
||
DJIMotorInstance *motor = (DJIMotorInstance *) motor_ptr;
|
||
uint16_t can_bus = motor->motor_can_instance->can_handle == &hcan1 ? 1 : 2;
|
||
LOGWARNING("[dji_motor] Motor lost, can bus [%d] , id [%d]", can_bus, motor->motor_can_instance->tx_id);
|
||
}
|
||
|
||
// 电机初始化,返回一个电机实例
|
||
DJIMotorInstance *DJIMotorInit(Motor_Init_Config_s *config) {
|
||
DJIMotorInstance *instance = (DJIMotorInstance *) malloc(sizeof(DJIMotorInstance));
|
||
memset(instance, 0, sizeof(DJIMotorInstance));
|
||
|
||
// motor basic setting 电机基本设置
|
||
instance->motor_type = config->motor_type; // 6020 or 2006 or 3508
|
||
instance->motor_settings = config->controller_setting_init_config; // 正反转,闭环类型等
|
||
instance->motor_control_type = config->motor_control_type; //电流控制or电压控制
|
||
|
||
// motor controller init 电机控制器初始化
|
||
PIDInit(&instance->motor_controller.current_PID, &config->controller_param_init_config.current_PID);
|
||
PIDInit(&instance->motor_controller.speed_PID, &config->controller_param_init_config.speed_PID);
|
||
PIDInit(&instance->motor_controller.angle_PID, &config->controller_param_init_config.angle_PID);
|
||
instance->motor_controller.other_angle_feedback_ptr = config->controller_param_init_config.other_angle_feedback_ptr;
|
||
instance->motor_controller.other_speed_feedback_ptr = config->controller_param_init_config.other_speed_feedback_ptr;
|
||
instance->motor_controller.current_feedforward_ptr = config->controller_param_init_config.current_feedforward_ptr;
|
||
instance->motor_controller.speed_feedforward_ptr = config->controller_param_init_config.speed_feedforward_ptr;
|
||
// 后续增加电机前馈控制器(速度和电流)
|
||
|
||
// 电机分组,因为至多4个电机可以共用一帧CAN控制报文
|
||
MotorSenderGrouping(instance, &config->can_init_config);
|
||
|
||
// 注册电机到CAN总线
|
||
config->can_init_config.can_module_callback = DecodeDJIMotor; // set callback
|
||
config->can_init_config.id = instance; // set id,eq to address(it is identity)
|
||
instance->motor_can_instance = CANRegister(&config->can_init_config);
|
||
|
||
// 注册守护线程
|
||
Daemon_Init_Config_s daemon_config = {
|
||
.callback = DJIMotorLostCallback,
|
||
.owner_id = instance,
|
||
.reload_count = 2, // 20ms未收到数据则丢失
|
||
};
|
||
instance->daemon = DaemonRegister(&daemon_config);
|
||
|
||
DJIMotorEnable(instance);
|
||
dji_motor_instance[idx++] = instance;
|
||
return instance;
|
||
}
|
||
|
||
/* 电流只能通过电机自带传感器监测,后续考虑加入力矩传感器应变片等 */
|
||
void DJIMotorChangeFeed(DJIMotorInstance *motor, Closeloop_Type_e loop, Feedback_Source_e type) {
|
||
if (loop == ANGLE_LOOP)
|
||
motor->motor_settings.angle_feedback_source = type;
|
||
else if (loop == SPEED_LOOP)
|
||
motor->motor_settings.speed_feedback_source = type;
|
||
else
|
||
LOGERROR("[dji_motor] loop type error, check memory access and func param"); // 检查是否传入了正确的LOOP类型,或发生了指针越界
|
||
}
|
||
|
||
void DJIMotorStop(DJIMotorInstance *motor) {
|
||
motor->stop_flag = MOTOR_STOP;
|
||
}
|
||
|
||
void DJIMotorEnable(DJIMotorInstance *motor) {
|
||
motor->stop_flag = MOTOR_ENALBED;
|
||
}
|
||
|
||
/* 修改电机的实际闭环对象 */
|
||
void DJIMotorOuterLoop(DJIMotorInstance *motor, Closeloop_Type_e outer_loop) {
|
||
motor->motor_settings.outer_loop_type = outer_loop;
|
||
}
|
||
|
||
// 设置参考值
|
||
void DJIMotorSetRef(DJIMotorInstance *motor, float ref) {
|
||
motor->motor_controller.pid_ref = ref;
|
||
}
|
||
|
||
static const float motor_power_K[3] = {1.6301e-6f,5.7501e-7f,2.5863e-7f};
|
||
//依据3508电机功率模型,预测电机输出功率
|
||
static float EstimatePower(DJIMotorInstance* chassis_motor,float output)
|
||
{
|
||
float I_cmd = chassis_motor->motor_controller.current_PID.Output;
|
||
float w = chassis_motor->measure.speed_aps /6 ;//aps to rpm
|
||
|
||
float power = motor_power_K[0] * I_cmd * w + motor_power_K[1]*w*w + motor_power_K[2]*I_cmd*I_cmd;
|
||
return power;
|
||
}
|
||
|
||
|
||
// 为所有电机实例计算三环PID,发送控制报文
|
||
void DJIMotorControl() {
|
||
// 直接保存一次指针引用从而减小访存的开销,同样可以提高可读性
|
||
uint8_t group, num; // 电机组号和组内编号
|
||
int16_t set; // 电机控制CAN发送设定值
|
||
DJIMotorInstance *motor;
|
||
Motor_Control_Setting_s *motor_setting; // 电机控制参数
|
||
Motor_Controller_s *motor_controller; // 电机控制器
|
||
DJI_Motor_Measure_s *measure; // 电机测量值
|
||
float pid_measure, pid_ref; // 电机PID测量值和设定值
|
||
|
||
// 遍历所有电机实例,进行串级PID的计算并设置发送报文的值
|
||
for (size_t i = 0; i < idx; ++i) { // 减小访存开销,先保存指针引用
|
||
motor = dji_motor_instance[i];
|
||
motor_setting = &motor->motor_settings;
|
||
motor_controller = &motor->motor_controller;
|
||
measure = &motor->measure;
|
||
pid_ref = motor_controller->pid_ref; // 保存设定值,防止motor_controller->pid_ref在计算过程中被修改
|
||
if (motor_setting->motor_reverse_flag == MOTOR_DIRECTION_REVERSE)
|
||
pid_ref *= -1; // 设置反转
|
||
|
||
// pid_ref会顺次通过被启用的闭环充当数据的载体
|
||
// 计算位置环,只有启用位置环且外层闭环为位置时会计算速度环输出
|
||
if ((motor_setting->close_loop_type & ANGLE_LOOP) && (motor_setting->outer_loop_type == ANGLE_LOOP)) {
|
||
if (motor_setting->angle_feedback_source == OTHER_FEED)
|
||
pid_measure = *motor_controller->other_angle_feedback_ptr;
|
||
else
|
||
{
|
||
if(motor_setting->feedback_reverse_flag == FEEDBACK_DIRECTION_REVERSE)
|
||
pid_measure = -measure->total_angle;
|
||
else
|
||
pid_measure = measure->total_angle; // MOTOR_FEED,对total angle闭环,防止在边界处出现突跃
|
||
}
|
||
|
||
// 更新pid_ref进入下一个环
|
||
pid_ref = PIDCalculate(&motor_controller->angle_PID, pid_measure, pid_ref);
|
||
}
|
||
|
||
// 计算速度环,(外层闭环为速度或位置)且(启用速度环)时会计算速度环
|
||
if ((motor_setting->close_loop_type & SPEED_LOOP) &&
|
||
(motor_setting->outer_loop_type & (ANGLE_LOOP | SPEED_LOOP))) {
|
||
if (motor_setting->feedforward_flag & SPEED_FEEDFORWARD)
|
||
pid_ref += *motor_controller->speed_feedforward_ptr;
|
||
|
||
if (motor_setting->speed_feedback_source == OTHER_FEED)
|
||
pid_measure = *motor_controller->other_speed_feedback_ptr;
|
||
else // MOTOR_FEED
|
||
{
|
||
if(motor_setting->feedback_reverse_flag == FEEDBACK_DIRECTION_REVERSE)
|
||
pid_measure = -measure->speed_aps;
|
||
else
|
||
pid_measure = measure->speed_aps;
|
||
}
|
||
|
||
// 更新pid_ref进入下一个环
|
||
pid_ref = PIDCalculate(&motor_controller->speed_PID, pid_measure, pid_ref);
|
||
}
|
||
|
||
// 计算电流环,目前只要启用了电流环就计算,不管外层闭环是什么,并且电流只有电机自身传感器的反馈
|
||
if (motor_setting->feedforward_flag & CURRENT_FEEDFORWARD)
|
||
pid_ref += *motor_controller->current_feedforward_ptr;
|
||
if (motor_setting->close_loop_type & CURRENT_LOOP) {
|
||
//现在电调都有内置电流环,无需pid计算
|
||
//pid_ref = PIDCalculate(&motor_controller->current_PID, measure->real_current, pid_ref);
|
||
}
|
||
|
||
if (motor_setting->feedback_reverse_flag == FEEDBACK_DIRECTION_REVERSE)
|
||
pid_ref *= -1;
|
||
|
||
//功率限制
|
||
if(motor_setting->power_limit_flag == POWER_LIMIT_ON)
|
||
{
|
||
float I_cmd = pid_ref;
|
||
float w = measure->speed_aps /6 ;//aps to rpm
|
||
motor_controller->motor_power_predict = motor_power_K[0] * I_cmd * w + motor_power_K[1]*w*w + motor_power_K[2]*I_cmd*I_cmd;
|
||
//这里K应该使用所有底盘电机一起计算 (在底盘任务中)
|
||
//float K = motor_controller->motor_power_max / motor_controller->motor_power_predict;
|
||
float K = motor_controller->motor_power_scale;
|
||
if(K>=1 || motor_controller->motor_power_predict < 0)//未超过最大功率 或做负功的电机 不做限制
|
||
{
|
||
|
||
}
|
||
else if(K<1)
|
||
{
|
||
float P_cmd = K * motor_controller->motor_power_predict; //对输出功率进行缩放
|
||
|
||
float a = motor_power_K[2] ;
|
||
float b = motor_power_K[0] * w;
|
||
float c = motor_power_K[1] * w * w - P_cmd;
|
||
|
||
if(pid_ref < 0)
|
||
pid_ref = (-b - sqrtf(b*b-4*a*c))/(2*a);
|
||
else
|
||
pid_ref = (-b + sqrtf(b*b-4*a*c))/(2*a);
|
||
}
|
||
//if( motor_controller->motor_power_predict < )
|
||
}
|
||
|
||
// 获取最终输出 此处注意set不要超过int16_t能表达的最大数 +-32767
|
||
|
||
pid_ref = float_constrain(pid_ref,-16384,16384);
|
||
// 获取最终输出
|
||
set = (int16_t) pid_ref;
|
||
|
||
// 分组填入发送数据
|
||
group = motor->sender_group;
|
||
num = motor->message_num;
|
||
sender_assignment[group].tx_buff[2 * num] = (uint8_t) (set >> 8); // 低八位
|
||
sender_assignment[group].tx_buff[2 * num + 1] = (uint8_t) (set & 0x00ff); // 高八位
|
||
|
||
// 若该电机处于停止状态,直接将buff置零
|
||
if (motor->stop_flag == MOTOR_STOP)
|
||
memset(sender_assignment[group].tx_buff + 2 * num, 0, 16u);
|
||
}
|
||
|
||
// 遍历flag,检查是否要发送这一帧报文
|
||
for (size_t i = 0; i < 10; ++i) {
|
||
if (sender_enable_flag[i]) {
|
||
CANTransmit(&sender_assignment[i], 1);
|
||
}
|
||
}
|
||
}
|