From 58299949c54c3339d1f6694be2d69b218186db6d Mon Sep 17 00:00:00 2001 From: TuxMonkey Date: Mon, 23 Feb 2026 13:32:10 +0800 Subject: [PATCH] dji motor but not tested --- .../module/algorithm/controller/pid/pid.h | 6 +- .../motor/can_motors/dji_motor/dji_motor.c | 356 +++++++++++++ .../motor/can_motors/dji_motor/dji_motor.h | 120 +++++ .../motor/can_motors/dji_motor/dji_motor.md | 470 +++++++++++++++++- User_Code/module/motor/motor_def.h | 132 +++++ 5 files changed, 1080 insertions(+), 4 deletions(-) create mode 100644 User_Code/module/motor/can_motors/dji_motor/dji_motor.c create mode 100644 User_Code/module/motor/can_motors/dji_motor/dji_motor.h create mode 100644 User_Code/module/motor/motor_def.h diff --git a/User_Code/module/algorithm/controller/pid/pid.h b/User_Code/module/algorithm/controller/pid/pid.h index 7190c34..d19ab6d 100644 --- a/User_Code/module/algorithm/controller/pid/pid.h +++ b/User_Code/module/algorithm/controller/pid/pid.h @@ -1,6 +1,6 @@ /** ****************************************************************************** - * @file controller.h + * @file pid.h * @author Wang Hongxi * @version V1.1.3 * @date 2021/7/3 @@ -10,8 +10,8 @@ * ****************************************************************************** */ -#ifndef _CONTROLLER_H -#define _CONTROLLER_H +#ifndef _PID_H +#define _PID_H #include "main.h" #include "stdint.h" diff --git a/User_Code/module/motor/can_motors/dji_motor/dji_motor.c b/User_Code/module/motor/can_motors/dji_motor/dji_motor.c new file mode 100644 index 0000000..9c574f7 --- /dev/null +++ b/User_Code/module/motor/can_motors/dji_motor/dji_motor.c @@ -0,0 +1,356 @@ +// +// Created by nie_b on 2026/2/23. +// + +#include "dji_motor.h" +#include "general_def.h" +#include "bsp_dwt.h" +#include "bsp_log.h" + +static uint8_t idx = 0; // register idx,是该文件的全局电机索引,在注册时使用 +/* DJI电机的实例,此处仅保存指针,内存的分配将通过电机实例初始化时通过malloc()进行 */ +static DJIMotorInstance *dji_motor_instance[DJI_MOTOR_CNT] = {NULL}; // 会在control任务中遍历该指针数组进行pid计算 + + +#ifdef FDCAN +static CANInstance sender_assignment[9] = { + [0] = {.can_handle = &hfdcan1, .txconf.Identifier = 0x1ff, .txconf.IdType = FDCAN_STANDARD_ID, .txconf.TxFrameType = FDCAN_DATA_FRAME, .txconf.DataLength = FDCAN_DLC_BYTES_8, .txconf.FDFormat = FDCAN_CLASSIC_CAN,.txconf.BitRateSwitch = FDCAN_BRS_OFF, .tx_buff = {0}}, + [1] = {.can_handle = &hfdcan1, .txconf.Identifier = 0x200, .txconf.IdType = FDCAN_STANDARD_ID, .txconf.TxFrameType = FDCAN_DATA_FRAME, .txconf.DataLength = FDCAN_DLC_BYTES_8, .txconf.FDFormat = FDCAN_CLASSIC_CAN,.txconf.BitRateSwitch = FDCAN_BRS_OFF, .tx_buff = {0}}, + [2] = {.can_handle = &hfdcan1, .txconf.Identifier = 0x2ff, .txconf.IdType = FDCAN_STANDARD_ID, .txconf.TxFrameType = FDCAN_DATA_FRAME, .txconf.DataLength = FDCAN_DLC_BYTES_8, .txconf.FDFormat = FDCAN_CLASSIC_CAN,.txconf.BitRateSwitch = FDCAN_BRS_OFF, .tx_buff = {0}}, + [3] = {.can_handle = &hfdcan2, .txconf.Identifier = 0x1ff, .txconf.IdType = FDCAN_STANDARD_ID, .txconf.TxFrameType = FDCAN_DATA_FRAME, .txconf.DataLength = FDCAN_DLC_BYTES_8, .txconf.FDFormat = FDCAN_CLASSIC_CAN,.txconf.BitRateSwitch = FDCAN_BRS_OFF, .tx_buff = {0}}, + [4] = {.can_handle = &hfdcan2, .txconf.Identifier = 0x200, .txconf.IdType = FDCAN_STANDARD_ID, .txconf.TxFrameType = FDCAN_DATA_FRAME, .txconf.DataLength = FDCAN_DLC_BYTES_8, .txconf.FDFormat = FDCAN_CLASSIC_CAN,.txconf.BitRateSwitch = FDCAN_BRS_OFF, .tx_buff = {0}}, + [5] = {.can_handle = &hfdcan2, .txconf.Identifier = 0x2ff, .txconf.IdType = FDCAN_STANDARD_ID, .txconf.TxFrameType = FDCAN_DATA_FRAME, .txconf.DataLength = FDCAN_DLC_BYTES_8, .txconf.FDFormat = FDCAN_CLASSIC_CAN,.txconf.BitRateSwitch = FDCAN_BRS_OFF, .tx_buff = {0}}, + [6] = {.can_handle = &hfdcan3, .txconf.Identifier = 0x1ff, .txconf.IdType = FDCAN_STANDARD_ID, .txconf.TxFrameType = FDCAN_DATA_FRAME, .txconf.DataLength = FDCAN_DLC_BYTES_8, .txconf.FDFormat = FDCAN_CLASSIC_CAN,.txconf.BitRateSwitch = FDCAN_BRS_OFF, .tx_buff = {0}}, + [7] = {.can_handle = &hfdcan3, .txconf.Identifier = 0x200, .txconf.IdType = FDCAN_STANDARD_ID, .txconf.TxFrameType = FDCAN_DATA_FRAME, .txconf.DataLength = FDCAN_DLC_BYTES_8, .txconf.FDFormat = FDCAN_CLASSIC_CAN,.txconf.BitRateSwitch = FDCAN_BRS_OFF, .tx_buff = {0}}, + [8] = {.can_handle = &hfdcan3, .txconf.Identifier = 0x2ff, .txconf.IdType = FDCAN_STANDARD_ID, .txconf.TxFrameType = FDCAN_DATA_FRAME, .txconf.DataLength = FDCAN_DLC_BYTES_8, .txconf.FDFormat = FDCAN_CLASSIC_CAN,.txconf.BitRateSwitch = FDCAN_BRS_OFF, .tx_buff = {0}}, +}; + +#else + +/** + * @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 FDCANInstance sender_assignment[6] = { + [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}}, +}; + +#endif + +/** + * @brief 6个用于确认是否有电机注册到sender_assignment中的标志位,防止发送空帧,此变量将在DJIMotorControl()使用 + * flag的初始化在 MotorSenderGrouping()中进行 + */ +static uint8_t sender_enable_flag[9] = {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; + + uint8_t grouping_offset; + //通过CAN计算分组偏移量 + if(config->can_handle == &hcan1) + { + grouping_offset=0; + } + else if(config->can_handle == &hcan2) + { + grouping_offset=3; + } + else + { + grouping_offset=6; + } + + switch (motor->motor_type) + { + case M2006: + case M3508: + if (motor_id < 4) // 根据ID分组 + { + motor_send_num = motor_id; + motor_grouping = grouping_offset + 1; + + } + else + { + motor_send_num = motor_id - 4; + motor_grouping = grouping_offset + 0; + } + + // 计算接收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_id < 4) + { + motor_send_num = motor_id; + motor_grouping = grouping_offset + 0; + } + else + { + motor_send_num = motor_id - 4; + motor_grouping = grouping_offset + 2; + } + + 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; // 正反转,闭环类型等 + + // 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; +} + +// 为所有电机实例计算三环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 + 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 + 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_ref = PIDCalculate(&motor_controller->current_PID, measure->real_current, pid_ref); + } + + if (motor_setting->feedback_reverse_flag == FEEDBACK_DIRECTION_REVERSE) + pid_ref *= -1; + + // 获取最终输出 + 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, sizeof(uint16_t)); + } + + // 遍历flag,检查是否要发送这一帧报文 +#ifdef FDCAN + for (size_t i = 0; i < 9; ++i) +#else + for (size_t i = 0; i < 6; ++i) +#endif + { + if (sender_enable_flag[i]) + { + CANTransmit(&sender_assignment[i], 1); + } + } +} diff --git a/User_Code/module/motor/can_motors/dji_motor/dji_motor.h b/User_Code/module/motor/can_motors/dji_motor/dji_motor.h new file mode 100644 index 0000000..36aadb5 --- /dev/null +++ b/User_Code/module/motor/can_motors/dji_motor/dji_motor.h @@ -0,0 +1,120 @@ +// +// Created by nie_b on 2026/2/23. +// + +#ifndef TRONONEH7_SCAFFOLD_DJI_MOTOR_H +#define TRONONEH7_SCAFFOLD_DJI_MOTOR_H + +#include "bsp_fdcan.h" +#include "controller.h" +#include "motor_def.h" +#include "stdint.h" +#include "daemon.h" + +#define DJI_MOTOR_CNT 12 + +/* 滤波系数设置为1的时候即关闭滤波 */ +#define SPEED_SMOOTH_COEF 0.85f // 最好大于0.85 +#define CURRENT_SMOOTH_COEF 0.9f // 必须大于0.9 +#define ECD_ANGLE_COEF_DJI 0.043945f // (360/8192),将编码器值转化为角度制 + +/* DJI电机CAN反馈信息*/ +typedef struct +{ + uint16_t last_ecd; // 上一次读取的编码器值 + uint16_t ecd; // 0-8191,刻度总共有8192格 + float angle_single_round; // 单圈角度 + float speed_aps; // 角速度,单位为:度/秒 + int16_t real_current; // 实际电流 + uint8_t temperature; // 温度 Celsius + + float total_angle; // 总角度,注意方向 + int32_t total_round; // 总圈数,注意方向 +} DJI_Motor_Measure_s; + +/** + * @brief DJI intelligent motor typedef + * + */ +typedef struct +{ + DJI_Motor_Measure_s measure; // 电机测量值 + Motor_Control_Setting_s motor_settings; // 电机设置 + Motor_Controller_s motor_controller; // 电机控制器 + + CANInstance *motor_can_instance; // 电机CAN实例 + // 分组发送设置 + uint8_t sender_group; + uint8_t message_num; + + Motor_Type_e motor_type; // 电机类型 + Motor_Working_Type_e stop_flag; // 启停标志 + + DaemonInstance* daemon; + uint32_t feed_cnt; + float dt; +} DJIMotorInstance; + +/** + * @brief 调用此函数注册一个DJI智能电机,需要传递较多的初始化参数,请在application初始化的时候调用此函数 + * 推荐传参时像标准库一样构造initStructure然后传入此函数. + * recommend: type xxxinitStructure = {.member1=xx, + * .member2=xx, + * ....}; + * 请注意不要在一条总线上挂载过多的电机(超过6个),若一定要这么做,请降低每个电机的反馈频率(设为500Hz), + * 并减小DJIMotorControl()任务的运行频率. + * + * @attention M3508和M2006的反馈报文都是0x200+id,而GM6020的反馈是0x204+id,请注意前两者和后者的id不要冲突. + * 如果产生冲突,在初始化电机的时候会进入IDcrash_Handler(),可以通过debug来判断是否出现冲突. + * + * @param config 电机初始化结构体,包含了电机控制设置,电机PID参数设置,电机类型以及电机挂载的CAN设置 + * + * @return DJIMotorInstance* + */ +DJIMotorInstance *DJIMotorInit(Motor_Init_Config_s *config); + +/** + * @brief 被application层的应用调用,给电机设定参考值. + * 对于应用,可以将电机视为传递函数为1的设备,不需要关心底层的闭环 + * + * @param motor 要设置的电机 + * @param ref 设定参考值 + */ +void DJIMotorSetRef(DJIMotorInstance *motor, float ref); + +/** + * @brief 切换反馈的目标来源,如将角速度和角度的来源换为IMU(小陀螺模式常用) + * + * @param motor 要切换反馈数据来源的电机 + * @param loop 要切换反馈数据来源的控制闭环 + * @param type 目标反馈模式 + */ +void DJIMotorChangeFeed(DJIMotorInstance *motor, Closeloop_Type_e loop, Feedback_Source_e type); + +/** + * @brief 该函数被motor_task调用运行在rtos上,motor_stask内通过osDelay()确定控制频率 + */ +void DJIMotorControl(); + +/** + * @brief 停止电机,注意不是将设定值设为零,而是直接给电机发送的电流值置零 + * + */ +void DJIMotorStop(DJIMotorInstance *motor); + +/** + * @brief 启动电机,此时电机会响应设定值 + * 初始化时不需要此函数,因为stop_flag的默认值为0 + * + */ +void DJIMotorEnable(DJIMotorInstance *motor); + +/** + * @brief 修改电机闭环目标(外层闭环) + * + * @param motor 要修改的电机实例指针 + * @param outer_loop 外层闭环类型 + */ +void DJIMotorOuterLoop(DJIMotorInstance *motor, Closeloop_Type_e outer_loop); + +#endif // TRONONEH7_SCAFFOLD_DJI_MOTOR_H diff --git a/User_Code/module/motor/can_motors/dji_motor/dji_motor.md b/User_Code/module/motor/can_motors/dji_motor/dji_motor.md index 121c394..9f84ca6 100644 --- a/User_Code/module/motor/can_motors/dji_motor/dji_motor.md +++ b/User_Code/module/motor/can_motors/dji_motor/dji_motor.md @@ -1 +1,469 @@ -# 大疆电机 \ No newline at end of file +# 大疆电机 +# dji_motor + +> TODO: +> +> 1. 给不同的电机设置不同的低通滤波器惯性系数而不是统一使用宏 +> 2. 为M2006和M3508增加开环的零位校准函数 + +--- + +> 建议将电机的反馈频率通过RoboMaster Assistant统一设置为500Hz。当前默认的`MotorTask()`执行频率为500Hz,若不修改电机反馈频率可能导致单条总线挂载的电机数量有限,且容易出现帧错误和仲裁失败的情况。 + +## 总览和封装说明 + +> 如果你不需要理解该模块的工作原理,你只需要查看这一小节。 + +dji_motor模块对DJI智能电机,包括M2006,M3508以及GM6020进行了详尽的封装。你不再需要关心PID的计算以及CAN报文的发送和接收解析,你只需要专注于根据应用层的需求,设定合理的期望值,并通过`DJIMotorSetRef()`设置对应电机的输入参考即可。 + +**==设定值的单位==** + +1. ==位置环为**角度制**(0-360,total_angle可以为任意值)== + +2. ==速度环为角速度,单位为**度/每秒**(deg/sec)== + +3. ==电流环为A== + +4. ==GM6020的输入设定为**力矩**,待测量(-30000~30000)== + + ==M3508的输入设定为-20A~20A (-16384~16384)== + + ==M2006的输入设定为-10A~10A (-10000~10000)== + +如果你希望更改电机的反馈来源,比如进入小陀螺模式/视觉模式(这时候你想要云台保持静止,使用IMU的yaw角度值作为反馈来源),只需要调用`DJIMotorChangeFeed()`,电机便可立刻切换反馈数据来源至IMU。 + +要获得一个电机,请通过`DJIMotorInit()`并传入一些参数,他就会返回一个电机的指针。你也不再需要查看这些电机和电调的说明书,**只需要设置其电机id**(6020为拨码开关值,2006和3508为电调的闪动次数),该模块会自动为你计算CAN发送和接收ID并搞定所有硬件层的琐事。 + +初始化电机时,你需要传入的参数包括: + +- **电机挂载的CAN总线设置**,CAN1 or CAN2,以及电机的id,使用`can_instance_config_s`封装,只需要设置这两个参数: + + ```c + CAN_HandleTypeDef *can_handle; + uint32_t tx_id; // tx_id设置为电机id,不需要查说明书计算,直接为电调的闪动次数或拨码开关值,为1-8 + ``` + +- **电机类型**,使用`Motor_Type_e`: + + ```c + GM6020 = 0 + M3508 = 1 + M2006 = 2 + ``` + +- **电机控制设置** + + - 闭环类型 + + ```c + OPEN_LOOP + CURRENT_LOOP + SPEED_LOOP + ANGLE_LOOP + CURRENT_LOOP | SPEED_LOOP // 同时对电流和速度闭环 + SPEED_LOOP | ANGLE_LOOP // 同时对速度和位置闭环 + CURRENT_LOOP | SPEED_LOOP |ANGLE_LOOP // 三环全开 + ``` + + - 是否反转 + + ```c + MOTOR_DIRECTION_NORMAL + MOTOR_DIRECTION_REVERSE + ``` + + - 是否其他反馈来源,以及他们对应的数据指针(如果有的话) + + ```c + MOTOR_FEED = 0 + OTHER_FEED = 1 + --- + + // 电流只能从电机传感器获得所以无法设置其他来源 + ``` + + - 每个环的PID参数以及是否使用改进功能,以及其他反馈来源指针(如果在上一步启用了其他数据来源) + + ```c + typedef struct // config parameter + { + float Kp; + float Ki; + float Kd; + + float MaxOut; // 输出限幅 + // 以下是优化参数 + float IntegralLimit; // 积分限幅 + float DeadBand; // 死区 + float CoefA; // For Changing Integral + float CoefB; // ITerm = Err*((A-abs(err)+B)/A) when B<|err|motor_measure->speed_rpm; +... +``` + +***现在,忘记PID的计算和发送、接收以及协议解析,专注于模块之间的逻辑交互吧。*** + +--- + +## 代码结构 + +.h文件内包括了外部接口和类型定义,以及模块对应的宏。c文件内为私有函数和外部接口的定义。 + +motor_def.h内包含了一些电机通用的定义。 + +## 类型定义 + +```c +#define DJI_MOTOR_CNT 12 +#define SPEED_SMOOTH_COEF 0.9f // better to be greater than 0.85 +#define CURRENT_SMOOTH_COEF 0.98f // this coef must be greater than 0.95 + +typedef struct /* DJI电机CAN反馈信息*/ +{ + uint16_t ecd; + uint16_t last_ecd; + int16_t speed_rpm; + int16_t given_current; + uint8_t temperate; + int16_t total_round; + int32_t total_angle; +} dji_motor_measure; + +typedef struct +{ + /* motor measurement recv from CAN feedback */ + dji_motor_measure motor_measure; + /* basic config of a motor*/ + Motor_Control_Setting_s motor_settings; + /* controller used in the motor (3 loops)*/ + Motor_Controller_s motor_controller; + /* the CAN instance own by motor instance*/ + can_instance motor_can_instance; + /* sender assigment*/ + uint8_t sender_group; + uint8_t message_num; + + uint8_t stop_flag; + + Motor_Type_e motor_type; +} dji_motor_instance; +``` + +- `DJI_MOTOR_CNT`是允许的最大DJI电机数量,根据经验,暂定为每个CAN6个,防止出现拥塞。 + +- `SPEED_SMOOTH_COEF`和`CURRENT_SMOOTH_COEF`是电机反馈的电流和速度数据低通滤波器惯性系数,数值越小平滑效果越大,但滞后也越大。设定时不应当低于推荐值。 +- `dji_motor_measure`是DJI电机的反馈信息,包括当前编码器值、上次测量编码器值、速度、电流、温度、总圈数和单圈角度。 + +- `Motor_Control_Setting_s`的定义在`motor_def.h`之中,它和`Motor_Controller_s`都是所有电机通用的组件(如M3508,LK9025,HT04,MT6023等),其包含内容如下: + + ```c + typedef struct /* 电机控制配置 */ + { + Closeloop_Type_e outer_loop_type; + Closeloop_Type_e close_loop_type; + Motor_Reverse_Flag_e motor_reverse_flag; + Feedback_Source_e angle_feedback_source; + Feedback_Source_e speed_feedback_source; + } Motor_Control_Setting_s; + ``` + + `Motor_Control_Setting_s`里包含了电机的闭环类型,反转标志以及额外的反馈来源标志。 + + - 闭环类型指示该电机使用的控制器配置,其枚举定义如下: + + ```c + typedef enum + { + CURRENT_LOOP = 0b0001, + SPEED_LOOP = 0b0010, + ANGLE_LOOP = 0b0100, + _ = 0b0011, + __ = 0b0110, + ___ = 0b0111 + } Closeloop_Type_e; + ``` + + 以M3508为例,假设需要进行**速度闭环**和**电流闭环**,那么在初始化时就将这个变量的值设为`CURRENT_LOOP | SPEED_LOOP`。在`DJIMotorControl()`中,函数将会根据此标志位判断设定的参考值需要经过那些控制器的计算。 + 另外,你还需要设置当前电机的最外层闭环,即电机的闭环目标为什么类型的值。初始化时需要设置`outer_loop_type`。以M2006作为拨盘电机时为例,你希望它在单发/双发等固定发射数量的模式下对位置进行闭环(拨盘转过一定角度对应拨出一颗弹丸),但你也有可能希望在连发的时候让拨盘连续的转动,以一定的频率发射弹丸。我们提供了`DJIMotorOuterLoop()`用于修改电机的外层闭环,改变电机的闭环对象。 + + > 注意,务必分清串级控制(多环)和外层闭环的区别。前者是为了提高内环的性能,使得其能更好地跟随外环参考值;而后者描述的是系统真实的控制目标(闭环目标)。如3508,没有电流环仍然可以对速度完成闭环,对于高层的应用来说,它们本质上不关心电机内部是否还有电流环,它们只把外层闭环为速度的电机当作一个**速度伺服执行器**,**外层闭环**描述的就是真正的闭环目标。 + + - 为了避开恼人的正负号,提高代码的可维护性,在初始化电机时设定`motor_reverse_flag`使得所有电机都按照你想要的方向旋转,其定义如下: + + ```c + typedef enum + { + MOTOR_DIRECTION_NORMAL = 0, + MOTOR_DIRECTION_REVERSE = 1 + } Motor_Reverse_Flag_e; + ``` + + - `speed_feedback_source`以及`angle_feedback_source`是指示电机反馈来源的标志位。一般情况下电机使用自身的编码器作为控制反馈量。但在某些时候,如小陀螺模式,云台电机会使用IMU的姿态数据作为反馈数据来源。其定义如下: + + ```c + typedef enum + { + MOTOR_FEED = 0, + OTHER_FEED = 1 + } Feedback_Source_e; + ``` + + **注意,如果启用其他数据来源,你需要在电机的控制器配置`Motor_Controller_s`下的`other_xxx_feedback_ptr`中指定其他数据来源。** + + 你可以在`DJIMotorChangeFeed()`中修改电机的数据来源。 + +- `Motor_Controller_s`的定义也在`motor_def.h`之中: + + ```c + /* 电机控制器,包括其他来源的反馈数据指针,3环控制器和电机的参考输入*/ + typedef struct + { + float *other_angle_feedback_ptr; + float *other_speed_feedback_ptr; + PID_t current_PID; + PID_t speed_PID; + PID_t angle_PID; + + float pid_ref; // 将会作为每个环的输入和输出顺次通过串级闭环 + } Motor_Controller_s; + ``` + + 两个`float*`指针应当指向其他反馈来源数据(如果有的话,需要在`motor_settings`中设定)。 + + 三个PID分别为三个控制闭环所用,在`DJIMotorControl()`中,该函数会根据`close_loop_type`的设定计算对应的闭环。 + + **`pid_ref`是控制的设定值,app层的应用想要更改电机的输出,就要调用`DJIMotorSetRef()`更改此值。** + +- `dji_motor_instance`是一个DJI电机实例。一个电机实例内包含电机的反馈信息,电机的控制设置,电机控制器,电机对应的CAN实例以及电机的类型;由于DJI电机支持**一帧报文控制至多4个电机**,该结构体还包含了用于给电机分组发送进行特殊处理的`sender_group`和`message_num`(具体实现细节参考`MotorSenderGrouping()`函数)。 + +## 外部接口 + +```c +dji_motor_instance *DJIMotorInit(can_instance_config config, + Motor_Control_Setting_s motor_setting, + Motor_Controller_Init_s controller_init, + Motor_Type_e type); + +void DJIMotorSetRef(dji_motor_instance *motor, float ref); + +void DJIMotorChangeFeed(dji_motor_instance *motor, + Closeloop_Type_e loop, + Feedback_Source_e type); + +void DJIMotorControl(); + +void DJIMotorStop(dji_motor_instance *motor); + +void DJIMotorEnable(dji_motor_instance *motor); + +void DJIMotorOuterLoop(dji_motor_instance *motor); +``` + +- `DJIMotorInit()`是用于初始化电机对象的接口,传入包括电机can配置、电机控制配置、电机控制器配置以及电机类型在内的初始化参数。**它将会返回一个电机实例指针**,你应当在应用层保存这个指针,这样才能操控这个电机。 + +- `DJIMotorSetRef()`是设定电机输出的接口,**在调用这个函数的时候,你可以认为你的设定值会直接转变为电机的输出**。`DJIMotorControl()`会帮你完成闭环计算,不用担心PID。 + +- `DJIMotorChangeFeed()`一般在更改云台或底盘的运动模式的时候被调用,传入要修改反馈来源的电机实例指针、要修改的闭环以及反馈来源类型。如希望切换到IMU的yaw值作为云台设定值,传入yaw轴电机实例和`ANGLE_LOOP`(位置环)、`OTHER_FEED`(启用其他数据来源)即可。当然,你需要在初始化的时候设定`motor_controller`中的 `other_angle_feedback_ptr`,使其指向yaw值的变量。 + +- `DJIMotorControl()`是根据电机的配置计算控制值的函数。该函数在`motor_task.c`中被调用,应当在freeRTOS中以一定频率运行。此函数为PID的计算进行了彻底的封装,要修改电机的参考输入,请在app层的应用中调用`DJIMotorSetRef()`。 + + 该函数的具体实现请参照代码,注释已经较为清晰。流程大致为: + + 1. 根据电机的初始化控制配置,计算各个控制闭环 + 2. 根据反转标志位,确定是否将输出反转 + 3. 根据每个电机的发送分组,将最终输出值填入对应的分组buff + 4. 检查每一个分组,若该分组有电机,发送报文 + +- `DJIMotorStop()`和`DJIMotorEnable()`用于控制电机的启动和停止。当电机被设为stop的时候,不会响应任何的参考输入。 + +- `DJIMotorOuterLoop()`用于修改电机的外部闭环类型,即电机的真实闭环目标。 + +## 私有函数和变量 + +在.c文件内设为static的函数和变量 + +```c +static uint8_t idx = 0; // register idx,是该文件的全局电机索引,在注册时使用 +static dji_motor_instance *dji_motor_info[DJI_MOTOR_CNT] = {NULL}; +``` + +这是管理所有电机实例的入口。idx用于电机初始化。 + +```c +#define PI2 (3.141592f * 2) +#define ECD_ANGLE_COEF_DJI 3.835e-4 // ecd/8192*pi +``` + +这两个宏用于在电机反馈信息中的多圈角度计算,将编码器的0~8192转化为角度表示。 + +```c +/* @brief 由于DJI电机发送以四个一组的形式进行,故对其进行特殊处理,用6个(2can*3group)can_instance专门负责发送 + * 该变量将在 DJIMotorControl() 中使用,分组在 MotorSenderGrouping()中进行 + * + * can1: [0]:0x1FF,[1]:0x200,[2]:0x2FF + * can2: [0]:0x1FF,[1]:0x200,[2]:0x2FF */ +static can_instance sender_assignment[6] = +{ + [0] = {.can_handle = &hcan1, .txconf.StdId = 0x1ff, .txconf.IDE = CAN_ID_STD, .txconf.RTR = CAN_RTR_DATA, .txconf.DLC = 0x08, .tx_buff = {0}}, + ... + ... +}; + +static uint8_t sender_enable_flag[6] = {0}; +``` + +- 这些是电机分组发送所需的变量。注册电机时,会根据挂载的总线以及发送id,将电机分组。在CAN发送电机控制信息的时候,根据`sender_assignment[]`保存的分组进行发送,而不会使用电机实例自带的`can_instance`。 +- DJI电机共有3种分组,分别为0x1FF,0x200,0x2FF。注册电机的时候,`MotorSenderGrouping()`函数会根据发送id计算出CAN的`tx_id`(即上述三个中的一个)和`rx_id`。然后为电机实例分配用于指示其在`sender_assignment[]`中的编号的 `sender_group`和其在该发送组中的位置`message_num`(一帧报文可以发送四条控制指令,`message_num`会指定电机是这四个中的哪一个)。具体的分配请查看`MotorSenderGrouping()`的定义。 +- 当某一个分组有电机注册时,该分组的索引将会在`sender_enable_flag`[]中被置1,这样,就可以避免发送没有电机注册的报文,防止总线拥塞。具体的,在`DecodeDJIMotor()`中,该函数会查看`sender_enable_flag[]`的每一个位置,确定这一组是否有电机被注册,若有则发送`sender_assignment[]`中对应位置的`tx_buff`。 + +```c +static void IDcrash_Handler(uint8_t conflict_motor_idx, uint8_t temp_motor_idx) + +static void MotorSenderGrouping(can_instance_config *config) + +static void DecodeDJIMotor(can_instance *_instance) +``` + +- `IDcrash_Handler()`在电机id发生冲突的时候会被`MotorSenderGrouping()`调用,陷入死循环之中,并把冲突的id保存在函数里。这样就可以通过debug确定是否发生冲突以及冲突的编号。 + +- `MotorSenderGrouping()`被`DJIMotorInit()`调用,他将会根据电机id计算出CAN的发送和接收ID,并根据发送ID对电机进行分组。 + +- `DecodeDJIMotor()`是解析电机反馈报文的函数,在`DJIMotorInit()`中会将其注册到该电机实例对应的`can_instance`中(即`can_instance`的`can_module_callback()`)。这样,当该电机的反馈报文到达时,`bsp_can.c`中的回调函数会调用解包函数进行反馈数据解析。 + + 该函数还会对电流和速度反馈值进行滤波,消除高频噪声;同时计算多圈角度和单圈绝对角度。 + + **电机反馈的电流值为说明书中的映射值,需转换为实际值。** + + **反馈的速度单位是rpm(转每分钟),转换为角度每秒。** + + **反馈的位置是编码器值(0~8191),转换为角度。** + +## 使用范例 + +```c +//初始化设置 +Motor_Init_Config_s config = { + .motor_type = GM6020, + .can_init_config = { + .can_handle = &hcan1, + .tx_id = 6 + }, + .controller_setting_init_config = { + .angle_feedback_source = MOTOR_FEED, + .outer_loop_type = SPEED_LOOP, + .close_loop_type = SPEED_LOOP | ANGLE_LOOP, + .speed_feedback_source = MOTOR_FEED, + .motor_reverse_flag = MOTOR_DIRECTION_NORMAL + }, + .controller_param_init_config = { + .angle_PID = { + .Improve = 0, + .Kp = 1, + .Ki = 0, + .Kd = 0, + .DeadBand = 0, + .MaxOut = 4000}, + .speed_PID = { + .Improve = 0, + .Kp = 1, + .Ki = 0, + .Kd = 0, + .DeadBand = 0, + .MaxOut = 4000 + } + } +}; +//注册电机并保存实例指针 +dji_motor_instance *djimotor = DJIMotorInit(&config); +``` + +然后在任务中修改电机设定值即可实现控制: + +``` +DJIMotorSetRef(djimotor, 10); +``` + +前提是已经将`DJIMotorControl()`放入实时系统任务当中或以一定d。你也可以单独执行`DJIMotorControl()`。 diff --git a/User_Code/module/motor/motor_def.h b/User_Code/module/motor/motor_def.h new file mode 100644 index 0000000..ecee010 --- /dev/null +++ b/User_Code/module/motor/motor_def.h @@ -0,0 +1,132 @@ +// +// Created by nie_b on 2026/2/23. +// + +#ifndef TRONONEH7_SCAFFOLD_MOTOR_DEF_H +#define TRONONEH7_SCAFFOLD_MOTOR_DEF_H + +#include "controller.h" +#include "stdint.h" + +#define LIMIT_MIN_MAX(x, min, max) (x) = (((x) <= (min)) ? (min) : (((x) >= (max)) ? (max) : (x))) + +/** + * @brief 闭环类型,如果需要多个闭环,则使用或运算 + * 例如需要速度环和电流环: CURRENT_LOOP|SPEED_LOOP + */ +typedef enum +{ + OPEN_LOOP = 0b0000, + CURRENT_LOOP = 0b0001, + SPEED_LOOP = 0b0010, + ANGLE_LOOP = 0b0100, + + // only for checking + SPEED_AND_CURRENT_LOOP = 0b0011, + ANGLE_AND_SPEED_LOOP = 0b0110, + ALL_THREE_LOOP = 0b0111, +} Closeloop_Type_e; + +typedef enum +{ + FEEDFORWARD_NONE = 0b00, + CURRENT_FEEDFORWARD = 0b01, + SPEED_FEEDFORWARD = 0b10, + CURRENT_AND_SPEED_FEEDFORWARD = CURRENT_FEEDFORWARD | SPEED_FEEDFORWARD, +} Feedfoward_Type_e; + +/* 反馈来源设定,若设为OTHER_FEED则需要指定数据来源指针,详见Motor_Controller_s*/ +typedef enum +{ + MOTOR_FEED = 0, + OTHER_FEED, +} Feedback_Source_e; + +/* 电机正反转标志 */ +typedef enum +{ + MOTOR_DIRECTION_NORMAL = 0, + MOTOR_DIRECTION_REVERSE = 1 +} Motor_Reverse_Flag_e; + +/* 反馈量正反标志 */ +typedef enum +{ + FEEDBACK_DIRECTION_NORMAL = 0, + FEEDBACK_DIRECTION_REVERSE = 1 +} Feedback_Reverse_Flag_e; +typedef enum +{ + MOTOR_STOP = 0, + MOTOR_ENALBED = 1, +} Motor_Working_Type_e; + +/* 电机控制设置,包括闭环类型,反转标志和反馈来源 */ +typedef struct +{ + Closeloop_Type_e outer_loop_type; // 最外层的闭环,未设置时默认为最高级的闭环 + Closeloop_Type_e close_loop_type; // 使用几个闭环(串级) + Motor_Reverse_Flag_e motor_reverse_flag; // 是否反转 + Feedback_Reverse_Flag_e feedback_reverse_flag; // 反馈是否反向 + Feedback_Source_e angle_feedback_source; // 角度反馈类型 + Feedback_Source_e speed_feedback_source; // 速度反馈类型 + Feedfoward_Type_e feedforward_flag; // 前馈标志 + +} Motor_Control_Setting_s; + +/* 电机控制器,包括其他来源的反馈数据指针,3环控制器和电机的参考输入*/ +// 后续增加前馈数据指针 +typedef struct +{ + float *other_angle_feedback_ptr; // 其他反馈来源的反馈数据指针 + float *other_speed_feedback_ptr; + float *speed_feedforward_ptr; + float *current_feedforward_ptr; + + PIDInstance current_PID; + PIDInstance speed_PID; + PIDInstance angle_PID; + + float pid_ref; // 将会作为每个环的输入和输出顺次通过串级闭环 +} Motor_Controller_s; + +/* 电机类型枚举 */ +typedef enum +{ + MOTOR_TYPE_NONE = 0, + GM6020, + M3508, + M2006, + LK9025, + HT04, +} Motor_Type_e; + +/** + * @brief 电机控制器初始化结构体,包括三环PID的配置以及两个反馈数据来源指针 + * 如果不需要某个控制环,可以不设置对应的pid config + * 需要其他数据来源进行反馈闭环,不仅要设置这里的指针还需要在Motor_Control_Setting_s启用其他数据来源标志 + */ +typedef struct +{ + float *other_angle_feedback_ptr; // 角度反馈数据指针,注意电机使用total_angle + float *other_speed_feedback_ptr; // 速度反馈数据指针,单位为angle per sec + + float *speed_feedforward_ptr; // 速度前馈数据指针 + float *current_feedforward_ptr; // 电流前馈数据指针 + + PID_Init_Config_s current_PID; + PID_Init_Config_s speed_PID; + PID_Init_Config_s angle_PID; +} Motor_Controller_Init_s; + +/* 用于初始化CAN电机的结构体,各类电机通用 */ +typedef struct +{ + Motor_Controller_Init_s controller_param_init_config; + Motor_Control_Setting_s controller_setting_init_config; + Motor_Type_e motor_type; + CAN_Init_Config_s can_init_config; +} Motor_Init_Config_s; + + +#endif // TRONONEH7_SCAFFOLD_MOTOR_DEF_H