小米电机与STM32——CAN通信

[复制链接]
发表于 2024-10-13 13:22:09 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
背景先容:为了利用小米电机,搭建机器臂的关节,需要学习小米电机的利用方法。计划采用STM32驱动小米电机,实现指定运动,为此需要了解他们之间的通信方式,指令写入方法等。花了很多时间学习,但网络上相关资料很少,且对于我这类基础一般的同学,注释不够详细。为此,我认真研读了电机的说明书,参考相关的博文,实现了电机的CAN通信,特作此记录。
一、环回测试接线图

元件:ST-Link  , STM32F103C8T6, 显示屏, 按键开关*2


二、CAN通信实现过程

1.CAN收发

需要设计一个CAN的报文输入与报文接收的函数,以用来举行数据收发,这里前面已经实现了
STM的CAN通信学习_stmcan通讯-CSDN博客
2.小米电机协议转化

需要弄清楚小米电机的CAN通讯协议类型,封装成相应的函数,再结合CAN收发函数举行对应的指令发送以及接收
1)小米电机通信:CAN 2.0通信接口,波特率为1Mbps,采用的扩展帧格式
2)小米电机通信相应的数据格式如下:

结合说明书可以得到详细通信指令对应关系如下:

参照小米电机的说明书内容,29位扩展ID中,Bit28~Bit24用来表示通信类型,说明书关于通信类型包括10种,详细解释与对应关系如下:
  1. //控制命令宏定义,与说明书对应
  2. //获取设备ID(通信类型为0)
  3. #define Communication_Type_GetID 0x00           //获取设备的ID和64位MCU唯一标识符
  4. //运控模式电机控制指令(通信类型为1)
  5. #define Communication_Type_MotionControl 0x01         //用来向主机发送控制指令
  6. //电机反馈数据(通信类型为2)
  7. #define Communication_Type_MotorRequest 0x02        //用来向主机反馈电机运行状态
  8. //电机使能运行(通信类型为3)
  9. #define Communication_Type_MotorEnable 0x03            //电机使能运行
  10. //电机停止运行(通信类型为4)
  11. #define Communication_Type_MotorStop 0x04            //电机停止运行
  12. //电机机械零位(通信类型为6)
  13. #define Communication_Type_SetPosZero 0x06            //设置电机机械零位
  14. //更改电机的CAN_ID(通信类型为7)
  15. #define Communication_Type_CanID 0x07                //更改当前电机CAN_ID
  16. //单个参数读取(通信类型为17,即0x11)
  17. #define Communication_Type_GetSingleParameter 0x11        //读取单个参数
  18. //单个参数写入(通信类型为18,即0x12)
  19. #define Communication_Type_Control_Mode 0x12
  20. #define Communication_Type_SetSingleParameter 0x12        //设定单个参数
  21. //故障反馈帧(通信类型为21,即0x15)
  22. #define Communication_Type_ErrorFeedback 0x15            //故障反馈帧
复制代码
当要利用STM32发送时,要结合说明书的差别通信类型对应的数据格式,整理获取扩展ID(29位)和对应的数据内容(8Byte)。
例如:下图为获取装备ID的协议利用说明,根据其特点,可以写成扩展ID如下:

  1. //扩展ID由通信类型+主CANID+目标电机ID
  2. txMsg.ExtId = Communication_Type_GetID<<24|Master_CAN_ID<<8|ID;
复制代码
利用上述方法,参照说明书可以获得所有通讯协议的扩展ID的写法,只要对应的按位填上数据即可。同理也可以写出对应的数据区参数。则可以逐一写出各个函数,实现与小米电机的CAN通信。
三、代码实现与解释说明

1.CAN收发

==》MyCAN.c文件,包括初始化、发送报文、接收报文函数。
1) 初始化函数:MyCAN_Init()

用来初始化CAN,留意里面的引脚和工作模式,以及相应的波特率设置可以按照控制元件要求修改。这里有一个FIFO0,要与后面接收数据时的FIFO对应。初始化的流程与GPIO口初始化流程类似:开启时钟——定义结构体——调用初始化函数
  1. /**************************************
  2. cMyCAN_Init   CAN 初始化函数
  3. Param:No
  4. Init Pin:GPIOA P11   CAN_RX,输入引脚是A11
  5.           GPIOA P12   CAN_TX,输出引脚是A12
  6. **************************************/
  7. void MyCAN_Init(void)
  8. {
  9.         /**********************开启GPIOA和CAN1时钟************************************/
  10.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//启用GPIOA时钟
  11.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);//启用CAN1时钟
  12.         /***********************初始化GPIOA的12号引脚,CAN 输出***********************/
  13.         GPIO_InitTypeDef GPIO_InitStructure;
  14.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//模式为复用推挽输出,注意如果要用作CAN通信,需要选择复用推挽输出
  15.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;//12号引脚
  16.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//引脚速度
  17.         GPIO_Init(GPIOA, &GPIO_InitStructure);//引脚初始化
  18.         /*********************初始化GPIO的11号引脚,CAN输入***************************/
  19.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入
  20.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
  21.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  22.         GPIO_Init(GPIOA, &GPIO_InitStructure);
  23.         /*************************初始化CAN1控制器************************************/
  24.         CAN_InitTypeDef CAN_InitStructure;//定义一个CAN的结构体
  25.         /*CAN工作模式选择
  26.      CAN_Mode_Normal             ((uint8_t)0x00)  正常模式
  27.      CAN_Mode_LoopBack           ((uint8_t)0x01)  环回模式
  28.            CAN_Mode_Silent             ((uint8_t)0x02)  静默模式,只听不发
  29.      CAN_Mode_Silent_LoopBack    ((uint8_t)0x03)  环回静默模式*/
  30.         CAN_InitStructure.CAN_Mode = CAN_Mode_LoopBack;//CAN工作模式为环回模式,用于自收自发测试用,实际通信时可以改成正常模式
  31.         /*波特率计算
  32.         波特率 = 36M(时钟频率) / 48 (预分频器的值)/ (1 + 2(BS1)+ 3(BS2)) = 125K    */
  33.         CAN_InitStructure.CAN_Prescaler = 48;        //预分频器的值为48       
  34.         CAN_InitStructure.CAN_BS1 = CAN_BS1_2tq;//BS1的时间长度,1-16tq
  35.         CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;//BS2的时间长度,1-8tq
  36.         CAN_InitStructure.CAN_SJW = CAN_SJW_2tq;//SJW的时间长度,1-4tq
  37.         CAN_InitStructure.CAN_NART = DISABLE;//DISABLE,表示寄存器置0,表示自动重传,这个功能叫做不自动重传,一般系统默认是自动重传
  38.         CAN_InitStructure.CAN_TXFP = DISABLE;//发送邮箱优先级,DISABLE置零,ID小的先发送,如果是ENABLE置一,先进先出
  39.         CAN_InitStructure.CAN_RFLM = DISABLE;//禁用FIOF锁定,溢出后,新报文覆盖最后一个报文,如果是ENABLE,则溢出时新报文丢弃
  40.         CAN_InitStructure.CAN_AWUM = DISABLE;//DISABLE 手动唤醒,ENABLE 自动唤醒
  41.         CAN_InitStructure.CAN_TTCM = DISABLE;//关闭时间触发通信
  42.         CAN_InitStructure.CAN_ABOM = DISABLE;//DISABLE 手动恢复,ENABLE 自动恢复(离线自动恢复)
  43.         CAN_Init(CAN1, &CAN_InitStructure);//初始化CAN
  44.         /*****************************初始化CAN过滤器*********************************/
  45.         CAN_FilterInitTypeDef CAN_FilterInitStructure;//定义CAN过滤器结构体
  46.         CAN_FilterInitStructure.CAN_FilterNumber = 0;//过滤器编号,0-13
  47.         /*几种不同的
  48.         16位列表模式,四个参数分别存入一组ID即可,共四个16位ID列表
  49.         屏蔽模式:IDHIGH存入第一组ID,MaskIDHIGH存入对应的屏蔽位,共两组16位ID和两组屏蔽位
  50.         32位列表模式:IDHIGH和IDLOW组合成一个32位ID,MaskIDHIGH和MaskIDLOW组成第二组32位ID
  51.         32位频闭模式:IDHIGH和IDLOW组合成一个32位ID,MaskIDHIGH和MaskIDLOW组成第二组32位屏蔽位
  52.         */
  53.         CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;//高16位
  54.         CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;//低16位
  55.         CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;
  56.         CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;
  57.         //选则相应模式为32位屏蔽模式
  58.         CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;//过滤器位宽,32位或者16位,这里是32位
  59.         CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;//过滤器模式,CAN_FilterMode_IdMask 屏蔽模式 CAN_FilterMode_IdList  列表模式
  60.         CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;//配置过滤器关联,这里有两个CAN_Filter_FIFO0,CAN_Filter_FIFO1
  61.         CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;//激活过滤器
  62.         CAN_FilterInit(&CAN_FilterInitStructure);//初始化过滤器
  63. }
复制代码
2.发送报文函数:MyCAN_Transmit(TxID, TxLength, TxData)  

发送报文,发送报文的时间,先定义一个CanTxMsg结构体,说明其ID形式,留意小米电机用的是这里假如设计为标准ID,后面环回接收时,IDtype对应数值位0,设计为扩展ID,则环回接收时,IDtype对应数值为1,以便用于区分差别的ID类型。
  1. /*************************************
  2. Name:MyCAN_Transmit CAN 发送报文
  3. Param: ID       ID是32位,便于后面使用扩展帧
  4.         Length   数据长度
  5.         *Data    数据指针
  6. **************************************/
  7. void MyCAN_Transmit(uint32_t ID, uint8_t Length, uint8_t *Data)
  8. {
  9.         CanTxMsg TxMessage; //定义CanTxMsg结构体变量,表示待发送的报文
  10.         TxMessage.StdId = ID; //标准ID
  11.         TxMessage.ExtId = ID; //扩展ID
  12.         //TxMessage.IDE = CAN_Id_Standard; //扩展标志位,CAN_Id_Standard 标准ID ,CAN_Id_Extended扩展ID
  13.         TxMessage.IDE = CAN_Id_Extended; //扩展标志位,CAN_Id_Standard 标准ID ,CAN_Id_Extended扩展ID
  14.         TxMessage.RTR = CAN_RTR_Data; //遥控标志位,CAN_RTR_Remote 遥控帧,        CAN_RTR_Data数据帧
  15.         TxMessage.DLC = Length; //数据段长度,传入的参数
  16.         //把形参DATA传过来的数组赋值给TxMessage.Data
  17.         for (uint8_t i = 0; i < Length; i ++)
  18.         {
  19.                 TxMessage.Data[i] = Data[i];//将传入的Data数组的值赋值给结构体的Data,他们都是8字节的数组
  20.         }
  21.        
  22.         //请求发送报文函数
  23.         //CAN_Transmit的原理:选择空发送邮箱——如果邮箱有空位,则将报文写入指定寄存器——TXRQ置1,请求发送
  24.         uint8_t TransmitMailbox = CAN_Transmit(CAN1, &TxMessage);//请求发送结构体指向的报文,返回值是邮箱编号
  25.         uint32_t Timeout = 0;
  26.         //CAN_TransmitStatus表示返回传输状态函数,返回请求发送邮箱的邮箱状态,CAN_TxStatus_OK表示发送成功
  27.         //等待函数返回OK,当CAN1的邮箱状态为CAN_TxStatus_Ok表示发送成功,如果不成功则进入循环
  28.         while (CAN_TransmitStatus(CAN1, TransmitMailbox) != CAN_TxStatus_Ok)
  29.         {
  30.                 Timeout ++;
  31.                 //如果大于超时时间,则跳出循环
  32.                 if (Timeout > 100000)
  33.                 {
  34.                         break;
  35.                 }
  36.         }
  37. }
复制代码
3)接收报文函数:MyCAN_Receive(&RxID, &RxLength, RxData, &RxIDtype)

函数里面的IDtype参数用于到显示屏幕上,验证发送的ID类型是标准ID还是扩展ID。起首定义一个接收报文的结构体,判断收到的数据是标准ID还是扩展ID,再获取ID值。假如是数据帧,则还需要逐个字节获取。
  1. /****************************************
  2. Name:MyCAN_ReceiveFlag
  3. Param:
  4. 函数用于判断接收FIFO里是否有报文,返回值为表示有报文,返回值为0表示没有报文
  5. *****************************************/
  6. uint8_t MyCAN_ReceiveFlag(void)
  7. {
  8.         if (CAN_MessagePending(CAN1, CAN_FIFO0) > 0)//如果大于零,表明FIFO0里面有报文,此处的FIFO0与前面的设置一致
  9.         {
  10.                 return 1;
  11.         }
  12.         return 0;
  13. }
  14. /*****************************************
  15. @brief: 接收 CAN message
  16. @param: *ID        the ID,用于做返回值
  17.          *Length    the length of DATA,用于做返回值
  18.          *DATA      the Data of CAN massage
  19. 注意,接收信息是需要输出参数,但C语言不支持多个值输出,
  20. 所以这里用指针表示,可以通过函数修改相应的值
  21. *****************************************/
  22. void MyCAN_Receive(uint32_t *ID, uint8_t *Length, uint8_t *Data,uint8_t *IDtype)
  23. {
  24.         CanRxMsg RxMessage;//定义一个CanRxMsg结构体,用于存放接收报文
  25.         CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);//接收报文
  26.         //判断接收的报文是标准ID还是扩展ID
  27.         if (RxMessage.IDE == CAN_Id_Standard)
  28.         {
  29.                 *IDtype=0;//表示接受的是标准帧
  30.                 *ID = RxMessage.StdId;//标准ID
  31.         }
  32.         else
  33.         {
  34.                 *IDtype=1;//表示接收的是扩展帧
  35.                 *ID = RxMessage.ExtId; //扩展ID
  36.         }
  37.         //判断接收报文是否为数据帧还是遥控帧
  38.         if (RxMessage.RTR == CAN_RTR_Data)//是否为数据帧
  39.         {
  40.                 //数据帧
  41.                 *Length = RxMessage.DLC;//数据长度
  42.                 //数据内容
  43.                 for (uint8_t i = 0; i < *Length; i ++)
  44.                 {
  45.                         Data[i] = RxMessage.Data[i];
  46.                 }
  47.         }
  48.         else
  49.         {
  50.                 //遥控帧,暂时不做处理
  51.         }
  52. }
复制代码
2.小米电机相应指令函数

0)获取装备ID

  1. /*—————————————————————————————————————————————————————————————————————————————————*/
  2. /** @brief          小米电机ID检查,通信类型为0
  3.   * @param[in]      id:  控制电机CAN_ID【出厂默认0x7F】
  4.   **/
  5. void check_cybergear(uint8_t ID)
  6. {
  7.     uint8_t tx_data[8] = {0};//没有数据
  8.                 //扩展ID的组合,依旧是3个部分
  9.     txMsg.ExtId = Communication_Type_GetID<<24|Master_CAN_ID<<8|ID;
  10.         MyCAN_Transmit(txMsg.ExtId,txMsg.DLC,tx_data);//写入指令       
  11. }
复制代码
1)运控模式电机控制指令

  1. /*——————————————————————————————————————————————————————————————————————————————————*/
  2. /** @brief          小米运控模式指令,通信类型:1
  3.   * @param[in]      Motor:  目标电机结构体
  4.   * @param[in]      torque: 力矩设置[-12,12] N*M
  5.   * @param[in]      MechPosition: 位置设置[-12.5,12.5] rad
  6.   * @param[in]      speed: 速度设置[-30,30] rpm
  7.   * @param[in]      kp: 比例参数设置
  8.   * @param[in]      kd: 微分参数设置
  9.   * @retval         none
  10.   **/
  11. void motor_controlmode(MI_Motor *Motor,float torque, float MechPosition, float speed, float kp, float kd)
  12. {   
  13.     uint8_t tx_data[8]={0};//发送数据初始化
  14.     //装填发送数据
  15.           //将目标角度转化为16位2进制数,对应字节0~1,假设是0x1234
  16.     tx_data[0]=float_to_uint(MechPosition,P_MIN,P_MAX,16)>>8;  //取得是高位的结果,即0x12
  17.     tx_data[1]=float_to_uint(MechPosition,P_MIN,P_MAX,16);  //取得是低位的结果,即0x34
  18.           //将目标速度转化为16位2进制数,对应字节2~3
  19.     tx_data[2]=float_to_uint(speed,V_MIN,V_MAX,16)>>8;  
  20.     tx_data[3]=float_to_uint(speed,V_MIN,V_MAX,16);  
  21.           //目标KP
  22.     tx_data[4]=float_to_uint(kp,KP_MIN,KP_MAX,16)>>8;  
  23.     tx_data[5]=float_to_uint(kp,KP_MIN,KP_MAX,16);  
  24.           //目标KD
  25.     tx_data[6]=float_to_uint(kd,KD_MIN,KD_MAX,16)>>8;  
  26.     tx_data[7]=float_to_uint(kd,KD_MIN,KD_MAX,16);
  27.        
  28.     txMsg.ExtId = Communication_Type_MotionControl<<24|float_to_uint(torque,T_MIN,T_MAX,16)<<8|Motor->CAN_ID;//装填扩展帧数据
  29.     MyCAN_Transmit(txMsg.ExtId,txMsg.DLC,tx_data);//写入指令       
  30. }
复制代码
这里面有一个将浮点数转化为16位二进制数的函数,float_to_uint,此中对应的参数上下限在.h文件中举行了定义,其对应范围是0~65535,也就是0~2^16-1
此中的参数范围定义如下,这个地方不能修改,只能参照说明书定义:
  1. /*****************控制参数最值,谨慎更改*********************/
  2. #define P_MIN -12.56f
  3. #define P_MAX 12.56f
  4. #define V_MIN -30.0f
  5. #define V_MAX 30.0f
  6. #define KP_MIN 0.0f
  7. #define KP_MAX 500.0f
  8. #define KD_MIN 0.0f
  9. #define KD_MAX 5.0f
  10. #define T_MIN -12.0f
  11. #define T_MAX 12.0f
  12. #define MAX_P 720
  13. #define MIN_P -720
复制代码
2)电机反馈数据 

电机反馈数据里面,我们需要读取的就是这几项:电机的ID号,电机的角度,电机的角速度,电机的力矩,电机的温度;根据下图,可以知道接收到的信息的详细位置,进而编写出程序如下:

  1. /*——————————————————————————————————————————————————————————————————————————————————————*/
  2. /** @brief         电机反馈数据,通信类型2
  3.   * @param[in]     信息存放的地址
  4.   * @retval         none
  5.   */
  6. void Rx_Fifo0_Msg(MI_Motor *Motor)
  7. {
  8.                 if (MyCAN_ReceiveFlag())//判断是否接收到报文信息
  9.                 {
  10.                         uint32_t RxID;
  11.       uint8_t RxLength;
  12.       uint8_t RxData[8];
  13.                         //接收信息放到对应的接收报文之中
  14.                         MyCAN_Receive(&RxID, &RxLength, RxData);
  15.                   Motor->CAN_ID=(RxID&0xFFFF)>>8;//获取接收数据的ID:保留低16位,其余全变成零,再右移8位,则获得了bit8~bit15的canid
  16.       Motor->Angle=uint16_to_float(RxData[0]<<8|RxData[1],MIN_P,MAX_P,16);//将字节0~1转化位浮点数,即为当前角度
  17.                   Motor->Speed=uint16_to_float(RxData[2]<<8|RxData[3],V_MIN,V_MAX,16);//将字节2~3转化位浮点数,即为当前速度                       
  18.                   Motor->Torque=uint16_to_float(RxData[4]<<8|RxData[5],T_MIN,T_MAX,16);//将字节4~5转化位浮点数,即为当前角度                               
  19.                   Motor->Temp=(RxData[6]<<8|RxData[7])*Temp_Gain;//将字节4~5转化为当前温度       
  20.                   Motor->error_code=(RxID&0x1F0000)>>16;       
  21.                 }
  22. }
复制代码
3)电机使能运行

  1. /*****************************使能电机,通信类型为3*******************************
  2.   * @brief          使能小米电机
  3.   * @param[in]      Motor:对应控制电机结构体   
  4.   * @retval         none
  5.   *****************************************************/
  6. void start_cybergear(MI_Motor *Motor)
  7. {
  8.     uint8_t tx_data[8] = {0};
  9.     txMsg.ExtId = Communication_Type_MotorEnable<<24|Master_CAN_ID<<8|Motor->CAN_ID;
  10.     MyCAN_Transmit(txMsg.ExtId,txMsg.DLC,tx_data);//写入指令       
  11. }
复制代码
4)  电机停止运行

留意当数据区的Byte[0]=1时,表示清故障
  1. /************************电机停止运行,通信类型为4********************************
  2.   * @brief          停止电机
  3.   * @param[in]      Motor:对应控制电机结构体   
  4.   * @param[in]      clear_error:清除错误位(0 不清除 1清除)
  5.   * @retval         None
  6.   *******************************************************************************/
  7. void stop_cybergear(MI_Motor *Motor,uint8_t clear_error)
  8. {
  9.         uint8_t tx_data[8]={0};
  10.         tx_data[0]=clear_error;//清除错误位设置
  11.         txMsg.ExtId = Communication_Type_MotorStop<<24|Master_CAN_ID<<8|Motor->CAN_ID;
  12.   MyCAN_Transmit(txMsg.ExtId,txMsg.DLC,tx_data);//写入指令       
  13. }
复制代码
6)设置电机机器零位

  1. /**********************设计电机的零点,通信类型6***************************
  2.   * @brief          设置电机零点
  3.   * @param[in]      Motor:  电机结构体
  4.   * @retval         none
  5.   ***********************************************************/
  6. void set_zeropos_cybergear(MI_Motor *Motor)
  7. {
  8.         uint8_t tx_data[8]={0};
  9.         tx_data[0] = 1;//数据区为1
  10.         //扩展帧格式
  11.         txMsg.ExtId = Communication_Type_SetPosZero<<24|Master_CAN_ID<<8|Motor->CAN_ID;
  12.         MyCAN_Transmit(txMsg.ExtId,txMsg.DLC,tx_data);//写入指令               
  13. }
复制代码
7)设置电机的CAN_ID

  1. /**********************设置电机的CAN_ID,通信类型为7********************
  2.   * @brief          设置电机CANID
  3.   * @param[in]      Motor:  电机结构体
  4.   * @param[in]      Motor:  设置新ID
  5.   * @retval         none
  6.   **********************************************************************/
  7. void set_CANID_cybergear(MI_Motor *Motor,uint8_t CAN_ID)
  8. {
  9.         uint8_t tx_data[8]={0};
  10.         txMsg.ExtId = Communication_Type_CanID<<24|CAN_ID<<16|Master_CAN_ID<<8|Motor->CAN_ID;
  11.     Motor->CAN_ID = CAN_ID;//将新的ID导入电机结构体
  12.    MyCAN_Transmit(txMsg.ExtId,txMsg.DLC,tx_data);//写入指令       
  13. }
复制代码
17)  单个参数读取


  1. /*************************单个参数读取,通信类型17******************
  2. *@brief         电机参数读取
  3. *@param[in]     ID,电机的ID号
  4. */
  5. void check_param_cybergear(MI_Motor *Motor, uint_8 index)
  6. {
  7.   uint8_t tx_data[8]={0}
  8.   txMsg.ExtId = Communication_Type_GetSingleParameter<<24|Master_CAN_ID<<8|Motor->CAN_ID;//装填扩展帧数据
  9.   tx_data[0]=index>>8;//高8位
  10.   tx_data[1]=index;//低8位
  11.   MyCAN_Transmit(txMsg.ExtId,txMsg.DLC,tx_data);//写入指令       
  12. }
复制代码
此中的index为说明书里面的参数列表中的值
  1. /******************参数读取宏定义,Index***********************/
  2. #define Run_mode 0x7005        //运动模式
  3. #define Iq_Ref   0x7006    //电流模式指令
  4. #define Spd_Ref  0x700A   //转速模式转速
  5. #define Limit_Torque 0x700B  //转矩限制
  6. #define Cur_Kp 0x7010     //电流的kp
  7. #define Cur_Ki 0x7011    //电流的ki
  8. #define Cur_Filt_Gain 0x7014  //电流滤波系数
  9. #define Loc_Ref 0x7016       //位置模式角度指令
  10. #define Limit_Spd 0x7017    //位置模式速度限制
  11. #define Limit_Cur 0x7018    //速度位置模式电流限制
  12. #define mechPos  0x7019    //负载端计圈机械角度
  13. #define iqf  0x701A       //iq滤波值
  14. #define mechVel  0x701B    //负载端转速
  15. #define VBUS  0x701C       //母线电压
  16. #define rotation  0x701D   //圈数
  17. #define loc_kp  0x701E    //位置的kp
  18. #define spd_kp  0x701F    //速度的kp
  19. #define spd_ki  0x7020   //速度的ki
复制代码
18)  写入单个参数

  1. /******************对应说明书的单个参数写入,通信类型为18*************
  2.   * @brief          写入电机参数
  3.   * @param[in]      Motor:对应控制电机结构体
  4.   * @param[in]      Index:写入参数对应地址
  5.   * @param[in]      Value:写入参数值
  6.   * @param[in]      Value_type:写入参数数据类型,可以对照字节数进行区分
  7.   * @retval         none
  8.   */
  9. static void Set_Motor_Parameter(MI_Motor *Motor,uint16_t Index,float Value,char Value_type){
  10.         uint8_t tx_data[8]={0};//写入的数据
  11.         //扩展ID,包括三个部分:通信类型、主ID、电机ID
  12.         txMsg.ExtId = Communication_Type_SetSingleParameter<<24|Master_CAN_ID<<8|Motor->CAN_ID;
  13.         //参见说明书的通信类型18里面,Index的相关应用方法,Index共
  14.         tx_data[0]=Index>>8;      //高8位
  15.         tx_data[1]=Index;   //低8位
  16.         tx_data[2]=0x00;
  17.         tx_data[3]=0x00;
  18.         //如果参数类型为浮点型,则对应的数据所占字节数为4,对应的数据需要转化为8个字节
  19.         if(Value_type == 'f'){
  20.                 Float_to_Byte(Value);//将数值转化为4个byte 作为返回值
  21.                 tx_data[4]=byte[0];//高8位
  22.                 tx_data[5]=byte[1];
  23.                 tx_data[6]=byte[2];
  24.                 tx_data[7]=byte[3];//低8位               
  25.         }
  26.         //如果参数类型为uint8,只有一个字节
  27.         else if(Value_type == 's'){
  28.                 tx_data[4]=(uint8_t)Value;
  29.                 tx_data[5]=0x00;
  30.                 tx_data[6]=0x00;
  31.                 tx_data[7]=0x00;                               
  32.         }
  33.         //can_txd();
  34.   MyCAN_Transmit(txMsg.ExtId,txMsg.DLC,tx_data);//写入指令
  35. }
复制代码
此外还有几个转化函数

  1. /*———————————————————————————————————————————————————————————————————————————————————————*/
  2. /** @brief          小米电机回文16位数据转浮点
  3.   * @param[in]      x:16位回文     x_min:对应参数下限     
  4.                     x_max:对应参数上限    bits:参数位数
  5.   * @retval         返回浮点值
  6.   **/
  7. static float uint16_to_float(uint16_t x,float x_min,float x_max,int bits)
  8. {
  9.     uint32_t span = (1 << bits) - 1;//二进制数的范围2^bits-1
  10.     float offset = x_max - x_min;//实际范围
  11.         return offset * x / span + x_min;//转换为浮点数
  12. }
  13. /*———————————————————————————————————————————————————————————————————————————————————*/
  14. /** @brief         小米电机发送浮点转16位数据
  15.                     每个数据都是2个字节也就是16位二进制数
  16.   * static 表示其作用域仅限于这个文件
  17.   */
  18. static int float_to_uint(float x, float x_min, float x_max, int bits)
  19. {
  20.   float span = x_max - x_min;//浮点数的范围
  21.   float offset = x_min;
  22.         //限制X的范围
  23.   if(x > x_max) x=x_max;
  24.   else if(x < x_min) x= x_min;
  25.   return (int) ((x-offset)*((float)((1<<bits)-1))/span);//(x-下限)/((2^bits-1)/范围)
  26. }
  27. /*————————————————————————————————————————————————————————————————————————————————————*/
  28. /** @brief          浮点数转4字节函数,这个用于写入参数时使用
  29.   */
  30. static uint8_t* Float_to_Byte(float f)
  31. {
  32.         unsigned long longdata = 0;//无符号长整型
  33.         longdata = *(unsigned long*)&f; //取f的地址,将该地址强制转化为无符号长整型指针,通过*解引用,获得该地址处的值      
  34.         byte[0] = (longdata & 0xFF000000) >> 24;//将最高8位提出来
  35.         byte[1] = (longdata & 0x00FF0000) >> 16;//将第二个高8位提出来
  36.         byte[2] = (longdata & 0x0000FF00) >> 8;//将第三个高8位提出来
  37.         byte[3] = (longdata & 0x000000FF);//将第四个高8位提出来
  38.         return byte;//返回的byte为四个字节的数组
  39. }
复制代码
3.主函数main.c 对相应的函数举行调用与验证

  1. #include "main.h"
  2. #include "cybergear.h"
  3. /*******************小米电机的控制***************/
  4. /*
  5. *v1:目前已经把小米电机的库程序建立起来,开始进行验证,已经验证的程序有:初始化和使能函数2024.10.7
  6. */
  7. uint8_t KeyNum;
  8. uint32_t RxID;
  9. uint8_t RxLength;
  10. uint8_t RxData[8];
  11. uint8_t RxIDtype;
  12. MI_Motor Cyber;
  13. uint8_t Motor_ID=0x7F;
  14. uint8_t Mode=Motion_mode;//控制模式为运动控制
  15. uint32_t id;
  16. SM_Param M_set;
  17. int main(void)
  18. {
  19.         //初始化
  20.         OLED_Init();
  21.         Key_Init();//按键初始化,按键对应的引脚分别位B1和B11
  22.         MyCAN_Init();//CAN初始化,CAN对应的引脚位A11和A12,A11输入,A12输出
  23.        
  24.         //以下用于调用程序验证相应的通信数据是否准确
  25.         init_cybergear(&Cyber, Motor_ID, Mode);//初始化电机
  26.         //*校验用的内容
  27.   id=check_cybergear(Cyber.CAN_ID);//小米电机ID检查,通信类型为0
  28.        
  29.         /*此处验证程序为运控模式的电机控制:motor_controlmode
  30.          *扩展ID显示为01 BFFF 7F;其中01为通讯类型,BFFF为力矩;7F为电机ID号
  31.          *数据为9001 BFFF 3851 428F 分别表示目标角度、目标角速度、对应KP,对应KD
  32.         */
  33.         //OLED_ShowHexNum(1, 1, Cyber.CAN_ID,2);
  34.         M_set.Position=pi/2;//带入程序数据的计算结果为36865,映射到16进制为9001
  35.         M_set.Speed=15;//45/60*65535=49151,转换为16进制是BFFF
  36.         M_set.Kp=110;//110/500*65535=14417,转换为16进制是3851
  37.         M_set.Kd=1.3;//1.3/5*65535=17039,转换为16进制是428F
  38.         M_set.Torque=6;//映射到区域为(6-(-12))/(12-(-12))=3/4;3/4*65535=49151;49151转化为16进制为BFFF
  39.         motor_controlmode(&Cyber,M_set.Torque, M_set.Position, M_set.Speed, M_set.Kp, M_set.Kd);
  40.        
  41.         //使能验证
  42.         //扩展ID为:03 0000 7F
  43.         //数据为:0000 0000 0000 0000
  44.         start_cybergear(Cyber.CAN_ID);//使能小米电机,通信类型为3
  45.        
  46.         //停止验证
  47.         //第二个参数为清除错误位,0表示不清除,1表示清除
  48.         //扩展ID为:04 0000 7F
  49.         //数据为:0000 0000 0000 0000/0100 0000 0000 0000
  50.         stop_cybergear(&Cyber,1);
  51.        
  52.         //置零验证
  53.         //扩展ID为:06 0000 7F
  54.         //数据为:0100 0000 0000 0000
  55.         set_zeropos_cybergear(&Cyber);
  56.        
  57.         //设置电机的ID号
  58.         //扩展ID为:07 1200 7F
  59.         //数据:0000 0000 0000 0000
  60.         Motor_ID=0x12;
  61.         set_CANID_cybergear(&Cyber,Motor_ID);
  62.        
  63.         //读取单个参数
  64.         //扩展ID:11 0000 12;其中0x11表示的十进制的17
  65.         //数据:7005 0000 0000 0000
  66.         Index index;
  67.         index=RunMode_idx;//0x7005
  68.         check_param_cybergear(&Cyber, index);
  69.        
  70.         //设置电机模式
  71.         //扩展ID:12 0000 12
  72.         //数据:70 05 00 00 01 00 00 00
  73.         set_mode_cybergear(&Cyber,Position_mode);
  74.        
  75.         //设置电机参数
  76.         //扩展ID:12 0000 12;其中0x12 表示十进制的18
  77.         //数据:70 05 00 00 01 00 00 00(1字节数据在byte4,其余数据为byte4~7)
  78.         Value_type value_typ;
  79.         value_typ=RunMode_Typ;
  80.         Set_Motor_Parameter(&Cyber,index,Position_mode,value_typ);
  81.        
  82.         while (1)
  83.         {
  84.                
  85.         }
  86. }
复制代码
利用显示器验证相应的发送指令,验证代码如下:
  1.     OLED_ShowString(1, 1, "ID:");//第3行第1列显示
  2.         OLED_ShowHexNum(2, 1, txMsg.ExtId,8 );
  3.         OLED_ShowString(3, 1, "Data:");//第3行第1列显示
  4.         for (int i=1;i<=8;i++)
  5.         {
  6.         OLED_ShowHexNum(4, i*2-1, tx_data[i-1],2 );
  7.         }
复制代码
本文电机函数的验证方法:逐个函数调用,然后通过屏幕显示指令举行验证,得到相应的显示效果与通讯协议的指令格式一致则表明函数无误。
本文参考了博文、视频教程:小米电机CyberGear STM32HAL 利用指南_小米电机瞬时电流串口指令-CSDN博客
[3-1] 单个装备环回测试&三个装备相互通信_哔哩哔哩_bilibili
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表