【关节电机专栏】ESP32 TWAI CAN Arduino库驱动小米电机(CyberGear微电机)

转载声明

本文转载自 https://blog.csdn.net/doudou2weiwei/article/details/142696510

作者:C語言的力量

前言

鉴于项目要使用小米电机,并且要使用esp32上自带的CAN库来控制,但是没有找到合适的、能用的库,只能从现有STM32小米电机控制库的基础上进行修改。为了方便快速实现,采用Arduino的平台对esp32进行编程,对应需要修改成Arduino的库


一、前置任务

  1. CAN通信基础
  2. ESP32自带CAN控制器—TWAI:官方文档文章介绍
  3. 相关教程:CAN通信Arduino外接CAN控制
    STM32F103用CAN通讯驱动小米电机讲解(视频)

上面这个视频是本库的关键参考,UP讲的非常细致,基本操作都是一样的,就不再赘述了(重要!!!)

二、硬件配置

  1. esp32×1
  2. TTL转CAN模块(是一种电平转换模块)×1
  3. 小米微电机×1 杜邦线×4,CAN线×2
  4. 24V电池×1
  5. USB转CAN模块(连接上位机查看数据,控制时不需要)

三、线路连接

  • esp32的4号引脚连接转换模块的RX,esp32的5号引脚连接转换模块的TX(esp32默认的CAN引脚);
  • 转换模块的CANL连接电机的CANL,转换模块的CANH连接电机的CANH;
  • VCC接VCC,GND接GND;
  • 连接24V电源;

四、引入底层库函数

由于esp32的Arduino库不自带CAN的库(也就是TWAI的库),需要自己去搜索下载

VScode+PlatformIO

五、小米电机控制库(C++文件,H文件)

1.H文件

#ifndef _TWAI_CAN_MI_MOTOR_H__
#define _TWAI_CAN_MI_MOTOR_H__
/*
	驱动器通信协议及使用说明 
	
  电机通信为 CAN 2.0 通信接口,波特率 1Mbps,采用扩展帧格式,如下所示:
  数据域          29 位 ID        8Byte 数据区
  大小 Bit28~bit24 bit23~8 bit7~0 Byte0~Byte7
  描述 通信类型 数据区 2 目标地址 数据区 1

  电机支持的控制模式包括:
  运控模式:给定电机运控 5 个参数;
  电流模式:给定电机指定的 Iq 电流;
  速度模式:给定电机指定的运行速度;
  位置模式:给定电机指定的位置,电机将运行到该指定的位置;

  本台测试用的小米电机CAN ID=1,主机ID=0

  注意配置顺序:
      1. 电机CAN初始化->电机ID初始化->设定电机机械零点->设置电机控制模式->设置对应模式的控制参数->使能(启动)电机
      2. 必须给电机发数据才会有数据返回

  测试用例:
  MI_Motor_ M1_con;

  void setup() {
    Motor_CAN_Init();
    M1_con.Motor_Con_Init(MOTER_1_ID);
    M1_con.Motor_Set_Zero();
    M1_con.Change_Mode(SPEED_MODE);
    M1_con.Motor_Enable();
  }

  void loop() {
      M1_con.Motor_Data_Updata(20);
      Serial.printf("M1数据: %d,%d,%d,%d,%d,%d,%d,%d,%d,angle:%.2f,speed:%.2f,torque:%.2f,temp:%.2f,\r\n",
                                    M1_con.motor_rx_data.master_id,M1_con.motor_rx_data.motor_id,
                                    M1_con.motor_rx_data.err_sta,M1_con.motor_rx_data.HALL_err,
                                    M1_con.motor_rx_data.magnet_err,M1_con.motor_rx_data.temp_err,
                                    M1_con.motor_rx_data.current_err,M1_con.motor_rx_data.voltage_err,
                                    M1_con.motor_rx_data.mode_sta,
                                    M1_con.motor_rx_data.cur_angle,M1_con.motor_rx_data.cur_speed,
                                    M1_con.motor_rx_data.cur_torque,M1_con.motor_rx_data.cur_temp);
      M1_con.Set_SpeedMode(-1);
      vTaskDelay(20);
  }
*/

#include"Arduino.h"
#include <ESP32-TWAI-CAN.hpp>

/*CAN设置*/
#define CAN_TX          5
#define CAN_RX          4

#define MASTER_ID       0
#define MOTER_1_ID      1
#define MOTER_2_ID      2

/*基础配置*/
#define P_MIN           -12.5f 
#define P_MAX           12.5f 
#define V_MIN           -30.0f 
#define V_MAX           30.0f 
#define KP_MIN          0.0f 
#define KP_MAX          500.0f 
#define KD_MIN          0.0f 
#define KD_MAX          5.0f 
#define T_MIN           -12.0f 
#define T_MAX           12.0f

/*数据配置*/
#define RUN_MODE        0x7005    // 运行模式, uint8, 1字节, 0运控模式,1位置模式,2速度模式,3电流模式
#define CTRL_MODE       0
#define POS_MODE        1
#define SPEED_MODE      2
#define CUR_MODE        3
#define IQ_REF          0x7006    // 电流模式 Iq 指令, float, 4字节, -23~23A
#define SPD_REF         0x700A    // 转速模式转速指令, float, 4字节, -30~30rad/s
#define LIMIT_TORQUE    0x700B    // 转矩限制, float, 4字节, 0~12Nm
#define CUR_KP          0x7010    // 电流的 Kp, float, 4字节, 默认值 0.125
#define CUR_KI          0x7011    // 电流的 Ki, float, 4字节, 默认值 0.0158
#define CUR_FILT_GAIN   0x7014    // 电流滤波系数 filt_gain, float, 4字节, 0~1.0,默认值 0.1
#define LOC_REF         0x7016    // 位置模式角度指令, float, 4字节, rad
#define LIMIT_SPD       0x7017    // 位置模式速度限制, float, 4字节, 0~30rad/s
#define LIMIT_CUR       0x7018    // 速度位置模式电流限制, float, 4字节, 0~23A
#define MECH_POS        0x7019    // 负载端计圈机械角度, float, 4字节, rad, 只读
#define IQF             0x701A    // iq 滤波值, float, 4字节, -23~23A, 只读
#define MECH_VEL        0x701B    // 负载端转速, float, 4字节, -30~30rad/s, 只读
#define VBUS            0x701C    // 母线电压, float, 4字节, V, 只读
#define ROTATION        0x701D    // 圈数, int16, 2字节, 圈数, 可读写
#define LOC_KP          0x701E    // 位置的 kp, float, 4字节, 默认值 30, 可读写
#define SPD_KP          0x701F    // 速度的 kp, float, 4字节, 默认值 1, 可读写
#define SPD_KI          0x7020    // 速度的 ki, float, 4字节, 默认值 0.002, 可读写

//拆分29位ID
#define RX_29ID_DISASSEMBLE_MASTER_ID(id)       (uint8_t)((id)&0xFF)
#define RX_29ID_DISASSEMBLE_MOTOR_ID(id)        (uint8_t)(((id)>>8)&0xFF)
//右移16位获取所有故障信息(bit21-16,共6位,111111->0x3F),三元运算符判断是否有故障。0无1有
#define RX_29ID_DISASSEMBLE_ERR_STA(id)         (uint8_t)(((((id)>>16)&0x3F) > 0) ? 1 : 0)
#define RX_29ID_DISASSEMBLE_HALL_ERR(id)        (uint8_t)(((id)>>20)&0X01)
#define RX_29ID_DISASSEMBLE_MAGNET_ERR(id)      (uint8_t)(((id)>>19)&0x01)
#define RX_29ID_DISASSEMBLE_TEMP_ERR(id)        (uint8_t)(((id)>>18)&0x01)
#define RX_29ID_DISASSEMBLE_CURRENT_ERR(id)     (uint8_t)(((id)>>17)&0x01)
#define RX_29ID_DISASSEMBLE_VOLTAGE_ERR(id)     (uint8_t)(((id)>>16)&0x01)
//模式状态: 0:Reset模式[复位]; 1:Cali 模式[标定]; 2:Motor模式[运行]
#define RX_29ID_DISASSEMBLE_MODE_STA(id)        (uint8_t)(((id)>>22)&0x03)

//拆分数据
#define RX_DATA_DISASSEMBLE_CUR_ANGLE(data)     (uint16_t)((data[0]<<8)|data[1])
#define RX_DATA_DISASSEMBLE_CUR_SPEED(data)     (uint16_t)((data[2]<<8)|data[3])
#define RX_DATA_DISASSEMBLE_CUR_TORQUE(data)    (uint16_t)((data[4]<<8)|data[5])
#define RX_DATA_DISASSEMBLE_CUR_TEMP(data)      (uint16_t)((data[6]<<8)|data[7])

//转换系数
//转换过程:angle=data×8PI/65535-4PI,   范围:+-4PI
#define INT2ANGLE     0.000383501049
//转换过程:speed=data×60/65535-30,     范围:+-30rad/s
#define INT2SPEED     0.000915541314
//转换过程:torque=data×24/65535-12,    范围:+-12N·m
#define INT2TORQUE    0.000366216526

//发送数据包
typedef struct {
  uint32_t id:8;      //8位CAN ID
  uint32_t data:16;   //16位数据
  uint32_t mode:5;    //5位模式
  uint32_t res:3;     //3位保留
  uint8_t tx_data[8];
}can_frame_t;

//解析返回数据包
typedef struct {
  //29位ID解码状态
  uint8_t master_id;
  uint8_t motor_id;
  uint8_t err_sta;
  uint8_t HALL_err;
  uint8_t magnet_err;
  uint8_t temp_err;
  uint8_t current_err;
  uint8_t voltage_err;
  uint8_t mode_sta;

  //具体数据
  float cur_angle;      //(-4π~4π)
  float cur_speed;      //(-30rad/s~30rad/s)
  float cur_torque;     //(-12Nm~12Nm)
  float cur_temp;       //Temp(摄氏度)*10
}can_rx_frame_t;

class MI_Motor_
{
    private:

        uint8_t id;
        CanFrame rxFrame;               //接收原始数据
        
    public:

        can_rx_frame_t motor_rx_data;   //返回解析数据
            
        void Motor_Enable(void);

        void Motor_Reset(void);

        void Motor_Set_Zero(void);

        void Motor_ControlMode(float torque, float MechPosition, float speed, float kp, float kd);

        void Set_SingleParameter(uint16_t parameter,float index);

        void Decoding_Motor_data(CanFrame* can_Frame_point, can_rx_frame_t* motor_frame_point);

        void Set_SpeedMode(float speed);

        void Set_PosMode(float position,float max_speed);

        void Set_CurMode(float current);

        void Change_Mode(uint8_t mode);

        void Motor_Con_Init(uint8_t motor_id);

        uint8_t Motor_Data_Updata(uint32_t timeout);
};

extern void Motor_CAN_Init(void);

#endif

2.CPP文件

#include "Arduino.h"
#include "TWAI_CAN_MI_Motor.h"

//把浮点数转换成uint_16 用在位置 扭矩 上面
static int float_to_uint(float x, float x_min, float x_max, int bits)
{
  float span = x_max - x_min;
  float offset = x_min;
  if (x > x_max) x = x_max;
  else if (x < x_min) x = x_min;
  return (int)((x - offset) * ((float)((1 << bits) - 1)) / span);
}

//底层的CAN发送指令,小米电机采用扩展帧,数据帧的格式
static void CAN_Send_Frame(can_frame_t* frame)
{
    CanFrame obdFrame = { 0 };
    uint32_t id_val,data_val,mode_val;
    uint32_t combined_val;

    obdFrame.extd = 1;              //0-标准帧; 1-扩展帧
    obdFrame.rtr = 0;               //0-数据帧; 1-远程帧
    obdFrame.ss = 0;                //0-错误重发; 1-单次发送(仲裁或丢失时消息不会被重发),对接收消息无效
    obdFrame.self = 0;              //0-不接收自己发送的消息,1-接收自己发送的消息,对接收消息无效
    obdFrame.dlc_non_comp = 0;      //0-数据长度不大于8(ISO 11898-1); 1-数据长度大于8(非标);
    //拼接ID
    id_val = frame->id;
    data_val = frame->data;
    mode_val = frame->mode;
    combined_val |= (mode_val << 24);
    combined_val |= (data_val << 8);
    combined_val |= id_val;

    obdFrame.identifier = combined_val; //普通帧直接写id,扩展帧需要计算。11/29位ID
    obdFrame.data_length_code = 8;      //要发送的字节数

    for (int i = 0; i < 8; i++)
    {
    obdFrame.data[i] = frame->tx_data[i];
    }
    ESP32Can.writeFrame(obdFrame);
}

/**
      * @brief          电机使能
      */
void MI_Motor_::Motor_Enable(void)
{
    can_frame_t motor_con_frame;
    motor_con_frame.mode = 3;           //模式3,使能
    motor_con_frame.id = this->id;      //目标电机ID
    motor_con_frame.data = MASTER_ID;   //本机ID
    for (int i = 0; i < 8; i++)
    {
        motor_con_frame.tx_data[i] = 0;
    }
    CAN_Send_Frame(&motor_con_frame);
}

/**
      * @brief          电机停止
      */
void MI_Motor_::Motor_Reset(void)
{
    can_frame_t motor_con_frame;
    motor_con_frame.mode = 4;           //模式4,停止
    motor_con_frame.id = this->id;      //目标电机ID
    motor_con_frame.data = MASTER_ID;   //本机ID
    for (int i = 0; i < 8; i++)
    {
        motor_con_frame.tx_data[i] = 0;
    }
    CAN_Send_Frame(&motor_con_frame);
}

/**
      * @brief          设置电机机械零点
      */
void MI_Motor_::Motor_Set_Zero(void)
{
    can_frame_t motor_con_frame;
    motor_con_frame.mode = 6;           //模式6,设置零点
    motor_con_frame.id = this->id;      //目标电机ID
    motor_con_frame.data = MASTER_ID;   //本机ID
    for (int i = 0; i < 8; i++)
    {
        motor_con_frame.tx_data[i] = 0;
    }
    CAN_Send_Frame(&motor_con_frame);
}

/**
      * @brief          运动控制模式
      * @param[in]      torque 扭矩,N·m,-12到+12
      * @param[in]      MechPosition 角度,rad,-12.5到+12.5
      * @param[in]      speed 角速度,rad/s,-30到+30
      */
void MI_Motor_::Motor_ControlMode(float torque, float MechPosition, float speed, float kp, float kd)
{
    can_frame_t motor_con_frame;
    motor_con_frame.mode = 1;           //模式1,运控模式
    motor_con_frame.id = this->id;      //目标电机ID
    motor_con_frame.data = float_to_uint(torque,T_MIN,T_MAX,16);

    motor_con_frame.tx_data[0]=float_to_uint(MechPosition,P_MIN,P_MAX,16)>>8;
    motor_con_frame.tx_data[1]=float_to_uint(MechPosition,P_MIN,P_MAX,16);
    motor_con_frame.tx_data[2]=float_to_uint(speed,V_MIN,V_MAX,16)>>8;
    motor_con_frame.tx_data[3]=float_to_uint(speed,V_MIN,V_MAX,16);
    motor_con_frame.tx_data[4]=float_to_uint(kp,KP_MIN,KP_MAX,16)>>8;
    motor_con_frame.tx_data[5]=float_to_uint(kp,KP_MIN,KP_MAX,16);
    motor_con_frame.tx_data[6]=float_to_uint(kd,KD_MIN,KD_MAX,16)>>8;
    motor_con_frame.tx_data[7]=float_to_uint(kd,KD_MIN,KD_MAX,16);
    CAN_Send_Frame(&motor_con_frame);
}

/**
      * @brief          设置单参数
      * @param[in]      parameter 参数数据
      * @param[in]      index 参数列表,详见电机说明书的4.1.11
      */
void MI_Motor_::Set_SingleParameter(uint16_t parameter,float index)
{
    can_frame_t motor_con_frame;
    motor_con_frame.mode = 18;          //模式18,单参数修改模式
    motor_con_frame.id = this->id;      //目标电机ID
    motor_con_frame.data = MASTER_ID;   //来标识主CAN_ID

    motor_con_frame.tx_data[2]=0;
    motor_con_frame.tx_data[3]=0;     //Byte2~3: 00  

    memcpy(&motor_con_frame.tx_data[0],&parameter,2);   //Byte0~1: index,参数列表详见4.1.11
    memcpy(&motor_con_frame.tx_data[4],&index,4);       //Byte4~7: 参数数据,1字节数据在Byte4 
    CAN_Send_Frame(&motor_con_frame);
}

/**
      * @brief          解析电机发送数据包
      * @param[in]      can_Frame_point CAN原始数据包的指针
      * @param[in]      motor_frame_point 电机解析数据包的指针
      */
void MI_Motor_::Decoding_Motor_data(CanFrame* can_Frame_point, can_rx_frame_t* motor_frame_point)
{
    motor_frame_point->master_id = RX_29ID_DISASSEMBLE_MASTER_ID(can_Frame_point->identifier);
    motor_frame_point->motor_id = RX_29ID_DISASSEMBLE_MOTOR_ID(can_Frame_point->identifier);
    motor_frame_point->err_sta = RX_29ID_DISASSEMBLE_ERR_STA(can_Frame_point->identifier);
    motor_frame_point->HALL_err = RX_29ID_DISASSEMBLE_HALL_ERR(can_Frame_point->identifier);
    motor_frame_point->magnet_err = RX_29ID_DISASSEMBLE_MAGNET_ERR(can_Frame_point->identifier);
    motor_frame_point->temp_err = RX_29ID_DISASSEMBLE_TEMP_ERR(can_Frame_point->identifier);
    motor_frame_point->current_err = RX_29ID_DISASSEMBLE_CURRENT_ERR(can_Frame_point->identifier);
    motor_frame_point->voltage_err = RX_29ID_DISASSEMBLE_VOLTAGE_ERR(can_Frame_point->identifier);
    motor_frame_point->mode_sta = RX_29ID_DISASSEMBLE_MODE_STA(can_Frame_point->identifier);

    //弧度单位
    motor_frame_point->cur_angle = RX_DATA_DISASSEMBLE_CUR_ANGLE(can_Frame_point->data)*INT2ANGLE - 4*PI;
    motor_frame_point->cur_speed = RX_DATA_DISASSEMBLE_CUR_SPEED(can_Frame_point->data)*INT2SPEED -30;
    motor_frame_point->cur_torque = RX_DATA_DISASSEMBLE_CUR_TORQUE(can_Frame_point->data)*INT2TORQUE - 12;
    motor_frame_point->cur_temp = RX_DATA_DISASSEMBLE_CUR_TEMP(can_Frame_point->data)/10.f;
}

/**
      * @brief          设置速度模式的参数
      * @param[in]      speed 角速度,rad/s,-30到+30
      */
void MI_Motor_::Set_SpeedMode(float speed)
{
    Set_SingleParameter(SPD_REF, speed);
}

/**
      * @brief          设置位置模式的参数
      * @param[in]      position 角度,rad,-12.5到+12.5
      * @param[in]      max_speed 角速度,rad/s,-30到+30
      */
void MI_Motor_::Set_PosMode(float position,float max_speed)
{
    Set_SingleParameter(LIMIT_SPD, max_speed);
    Set_SingleParameter(LOC_REF, position);
}

/**
      * @brief          设置电流模式的参数
      * @param[in]      current 电流,A,-23到+23
      */
void MI_Motor_::Set_CurMode(float current)
{
    Set_SingleParameter(IQ_REF, current);
}

/**
      * @brief          设置运动模式
      * @param[in]      mode 运控模式:CTRL_MODE 电流模式:CUR_MODE 速度模式:SPEED_MODE 位置模式:POS_MODE
      */
void MI_Motor_::Change_Mode(uint8_t mode)
{
    uint16_t parameter=RUN_MODE;
    can_frame_t motor_con_frame;
    motor_con_frame.mode=18;  //模式18,单参数写入
    motor_con_frame.data=MASTER_ID;
    motor_con_frame.id=this->id;    //电机ID
    for(int j=0;j<=7;j++){
    motor_con_frame.tx_data[j]=0;
    }	
    motor_con_frame.tx_data[4]=mode;
    memcpy(&motor_con_frame.tx_data[0],&parameter,2);
    CAN_Send_Frame(&motor_con_frame);
}

/**
      * @brief          电机初始化,在上位机查看电机ID和主机ID,主机ID默认为0
      * @param[in]      motor_id 电机ID
      */
void MI_Motor_::Motor_Con_Init(uint8_t motor_id)
{
    this->id = motor_id;
}

/**
      * @brief          CAN初始化
      */
void Motor_CAN_Init(void)
{
    ESP32Can.setPins(CAN_TX, CAN_RX);                //设置pin脚
    ESP32Can.setRxQueueSize(50);                     //设置缓存区大小
    ESP32Can.setTxQueueSize(50);                     //设置缓存区大小
    ESP32Can.setSpeed(ESP32Can.convertSpeed(1000));  //设置速度,一定要1M,其它库都没有1M,就这个库有
    ESP32Can.begin();
    // if (ESP32Can.begin()) {
    //     Serial.println("CAN bus started!");
    // } else {
    //     Serial.println("CAN bus failed!");
    // }
}

/**
      * @brief          更新电机数据
      * @param[in]      timeout 超时时间
      * @retval         err_sta 0:正常读取;1:读取到数据,但不是自己的;2:没有读取到数据
      */
uint8_t MI_Motor_::Motor_Data_Updata(uint32_t timeout)
{
    uint8_t err_sta=0;
    if (ESP32Can.readFrame(rxFrame, timeout)){
        if (RX_29ID_DISASSEMBLE_MOTOR_ID(rxFrame.identifier) == this->id){
            Decoding_Motor_data(&rxFrame,&motor_rx_data);
        }else{
            err_sta = 1;
        }
    }else{
        err_sta = 2;
    }

    return err_sta;
}

3.使用例程

#include <Arduino.h>
#include "TWAI_CAN_MI_Motor.h"

MI_Motor_ M1_con;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);

  Motor_CAN_Init();
  M1_con.Motor_Con_Init(MOTER_1_ID);
  M1_con.Motor_Set_Zero();
  M1_con.Change_Mode(SPEED_MODE);
  M1_con.Motor_Enable();
}

void loop() {
    M1_con.Motor_Data_Updata(20);
    Serial.printf("M1数据: %d,%d,%d,%d,%d,%d,%d,%d,%d,angle:%.2f,speed:%.2f,torque:%.2f,temp:%.2f,\r\n",
                                  M1_con.motor_rx_data.master_id,M1_con.motor_rx_data.motor_id,
                                  M1_con.motor_rx_data.err_sta,M1_con.motor_rx_data.HALL_err,
                                  M1_con.motor_rx_data.magnet_err,M1_con.motor_rx_data.temp_err,
                                  M1_con.motor_rx_data.current_err,M1_con.motor_rx_data.voltage_err,
                                  M1_con.motor_rx_data.mode_sta,
                                  M1_con.motor_rx_data.cur_angle,M1_con.motor_rx_data.cur_speed,
                                  M1_con.motor_rx_data.cur_torque,M1_con.motor_rx_data.cur_temp);
    M1_con.Set_SpeedMode(-0.1);
    vTaskDelay(20);
}

总结

CAN的数据发送应该没什么问题(需要电机先启动,再发控制指令),数据接收暂时还是轮询模式,不过配置有队列应该问题不大,有时间看看能不能改成中断处理

posted @ 2024-11-27 17:22  FBshark  阅读(139)  评论(0编辑  收藏  举报