小米电机与STM32——CAN通信
背景介绍:为了利用小米电机,搭建机械臂的关节,需要学习小米电机的使用方法。计划采用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种,具体解释与对应关系如下:
//控制命令宏定义,与说明书对应
//获取设备ID(通信类型为0)
#define Communication_Type_GetID 0x00 //获取设备的ID和64位MCU唯一标识符
//运控模式电机控制指令(通信类型为1)
#define Communication_Type_MotionControl 0x01 //用来向主机发送控制指令
//电机反馈数据(通信类型为2)
#define Communication_Type_MotorRequest 0x02 //用来向主机反馈电机运行状态
//电机使能运行(通信类型为3)
#define Communication_Type_MotorEnable 0x03 //电机使能运行
//电机停止运行(通信类型为4)
#define Communication_Type_MotorStop 0x04 //电机停止运行
//电机机械零位(通信类型为6)
#define Communication_Type_SetPosZero 0x06 //设置电机机械零位
//更改电机的CAN_ID(通信类型为7)
#define Communication_Type_CanID 0x07 //更改当前电机CAN_ID
//单个参数读取(通信类型为17,即0x11)
#define Communication_Type_GetSingleParameter 0x11 //读取单个参数
//单个参数写入(通信类型为18,即0x12)
#define Communication_Type_Control_Mode 0x12
#define Communication_Type_SetSingleParameter 0x12 //设定单个参数
//故障反馈帧(通信类型为21,即0x15)
#define Communication_Type_ErrorFeedback 0x15 //故障反馈帧
当要利用STM32发送时,要结合说明书的不同通信类型对应的数据格式,整理获取扩展ID(29位)和对应的数据内容(8Byte)。
例如:下图为获取设备ID的协议使用说明,根据其特点,可以写成扩展ID如下:
//扩展ID由通信类型+主CANID+目标电机ID
txMsg.ExtId = Communication_Type_GetID<<24|Master_CAN_ID<<8|ID;
利用上述方法,参照说明书可以获得所有通讯协议的扩展ID的写法,只要对应的按位填上数据即可。同理也可以写出对应的数据区参数。则可以逐一写出各个函数,实现与小米电机的CAN通信。
三、代码实现与解释说明
1.CAN收发
==》MyCAN.c文件,包括初始化、发送报文、接收报文函数。
1) 初始化函数:MyCAN_Init()
用来初始化CAN,注意里面的引脚和工作模式,以及相应的波特率设置可以按照控制元件要求修改。这里有一个FIFO0,要与后面接收数据时的FIFO对应。初始化的流程与GPIO口初始化流程类似:开启时钟——定义结构体——调用初始化函数
/**************************************
cMyCAN_Init CAN 初始化函数
Param:No
Init Pin:GPIOA P11 CAN_RX,输入引脚是A11GPIOA P12 CAN_TX,输出引脚是A12
**************************************/
void MyCAN_Init(void)
{/**********************开启GPIOA和CAN1时钟************************************/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//启用GPIOA时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);//启用CAN1时钟/***********************初始化GPIOA的12号引脚,CAN 输出***********************/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//模式为复用推挽输出,注意如果要用作CAN通信,需要选择复用推挽输出GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;//12号引脚GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//引脚速度GPIO_Init(GPIOA, &GPIO_InitStructure);//引脚初始化/*********************初始化GPIO的11号引脚,CAN输入***************************/GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);/*************************初始化CAN1控制器************************************/CAN_InitTypeDef CAN_InitStructure;//定义一个CAN的结构体/*CAN工作模式选择CAN_Mode_Normal ((uint8_t)0x00) 正常模式CAN_Mode_LoopBack ((uint8_t)0x01) 环回模式CAN_Mode_Silent ((uint8_t)0x02) 静默模式,只听不发CAN_Mode_Silent_LoopBack ((uint8_t)0x03) 环回静默模式*/CAN_InitStructure.CAN_Mode = CAN_Mode_LoopBack;//CAN工作模式为环回模式,用于自收自发测试用,实际通信时可以改成正常模式/*波特率计算波特率 = 36M(时钟频率) / 48 (预分频器的值)/ (1 + 2(BS1)+ 3(BS2)) = 125K */CAN_InitStructure.CAN_Prescaler = 48; //预分频器的值为48 CAN_InitStructure.CAN_BS1 = CAN_BS1_2tq;//BS1的时间长度,1-16tqCAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;//BS2的时间长度,1-8tqCAN_InitStructure.CAN_SJW = CAN_SJW_2tq;//SJW的时间长度,1-4tqCAN_InitStructure.CAN_NART = DISABLE;//DISABLE,表示寄存器置0,表示自动重传,这个功能叫做不自动重传,一般系统默认是自动重传CAN_InitStructure.CAN_TXFP = DISABLE;//发送邮箱优先级,DISABLE置零,ID小的先发送,如果是ENABLE置一,先进先出CAN_InitStructure.CAN_RFLM = DISABLE;//禁用FIOF锁定,溢出后,新报文覆盖最后一个报文,如果是ENABLE,则溢出时新报文丢弃CAN_InitStructure.CAN_AWUM = DISABLE;//DISABLE 手动唤醒,ENABLE 自动唤醒CAN_InitStructure.CAN_TTCM = DISABLE;//关闭时间触发通信CAN_InitStructure.CAN_ABOM = DISABLE;//DISABLE 手动恢复,ENABLE 自动恢复(离线自动恢复)CAN_Init(CAN1, &CAN_InitStructure);//初始化CAN/*****************************初始化CAN过滤器*********************************/CAN_FilterInitTypeDef CAN_FilterInitStructure;//定义CAN过滤器结构体CAN_FilterInitStructure.CAN_FilterNumber = 0;//过滤器编号,0-13/*几种不同的16位列表模式,四个参数分别存入一组ID即可,共四个16位ID列表屏蔽模式:IDHIGH存入第一组ID,MaskIDHIGH存入对应的屏蔽位,共两组16位ID和两组屏蔽位32位列表模式:IDHIGH和IDLOW组合成一个32位ID,MaskIDHIGH和MaskIDLOW组成第二组32位ID32位频闭模式:IDHIGH和IDLOW组合成一个32位ID,MaskIDHIGH和MaskIDLOW组成第二组32位屏蔽位*/CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;//高16位CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;//低16位CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;//选则相应模式为32位屏蔽模式CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;//过滤器位宽,32位或者16位,这里是32位CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;//过滤器模式,CAN_FilterMode_IdMask 屏蔽模式 CAN_FilterMode_IdList 列表模式CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;//配置过滤器关联,这里有两个CAN_Filter_FIFO0,CAN_Filter_FIFO1CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;//激活过滤器CAN_FilterInit(&CAN_FilterInitStructure);//初始化过滤器
}
2.发送报文函数:MyCAN_Transmit(TxID, TxLength, TxData)
发送报文,发送报文的时候,先定义一个CanTxMsg结构体,说明其ID形式,注意小米电机用的是这里如果设计为标准ID,后面环回接收时,IDtype对应数值位0,设计为扩展ID,则环回接收时,IDtype对应数值为1,以便用于区分不同的ID类型。
/*************************************
Name:MyCAN_Transmit CAN 发送报文
Param: ID ID是32位,便于后面使用扩展帧Length 数据长度*Data 数据指针
**************************************/
void MyCAN_Transmit(uint32_t ID, uint8_t Length, uint8_t *Data)
{CanTxMsg TxMessage; //定义CanTxMsg结构体变量,表示待发送的报文TxMessage.StdId = ID; //标准IDTxMessage.ExtId = ID; //扩展ID//TxMessage.IDE = CAN_Id_Standard; //扩展标志位,CAN_Id_Standard 标准ID ,CAN_Id_Extended扩展IDTxMessage.IDE = CAN_Id_Extended; //扩展标志位,CAN_Id_Standard 标准ID ,CAN_Id_Extended扩展IDTxMessage.RTR = CAN_RTR_Data; //遥控标志位,CAN_RTR_Remote 遥控帧, CAN_RTR_Data数据帧TxMessage.DLC = Length; //数据段长度,传入的参数//把形参DATA传过来的数组赋值给TxMessage.Datafor (uint8_t i = 0; i < Length; i ++){TxMessage.Data[i] = Data[i];//将传入的Data数组的值赋值给结构体的Data,他们都是8字节的数组}//请求发送报文函数//CAN_Transmit的原理:选择空发送邮箱——如果邮箱有空位,则将报文写入指定寄存器——TXRQ置1,请求发送uint8_t TransmitMailbox = CAN_Transmit(CAN1, &TxMessage);//请求发送结构体指向的报文,返回值是邮箱编号uint32_t Timeout = 0;//CAN_TransmitStatus表示返回传输状态函数,返回请求发送邮箱的邮箱状态,CAN_TxStatus_OK表示发送成功//等待函数返回OK,当CAN1的邮箱状态为CAN_TxStatus_Ok表示发送成功,如果不成功则进入循环while (CAN_TransmitStatus(CAN1, TransmitMailbox) != CAN_TxStatus_Ok){Timeout ++;//如果大于超时时间,则跳出循环if (Timeout > 100000){break;}}
}
3)接收报文函数:MyCAN_Receive(&RxID, &RxLength, RxData, &RxIDtype)
函数里面的IDtype参数用于到显示屏幕上,验证发送的ID类型是标准ID还是扩展ID。首先定义一个接收报文的结构体,判断收到的数据是标准ID还是扩展ID,再获取ID值。如果是数据帧,则还需要逐个字节获取。
/****************************************
Name:MyCAN_ReceiveFlag
Param:
函数用于判断接收FIFO里是否有报文,返回值为表示有报文,返回值为0表示没有报文
*****************************************/
uint8_t MyCAN_ReceiveFlag(void)
{if (CAN_MessagePending(CAN1, CAN_FIFO0) > 0)//如果大于零,表明FIFO0里面有报文,此处的FIFO0与前面的设置一致{return 1;}return 0;
}/*****************************************
@brief: 接收 CAN message
@param: *ID the ID,用于做返回值*Length the length of DATA,用于做返回值*DATA the Data of CAN massage
注意,接收信息是需要输出参数,但C语言不支持多个值输出,
所以这里用指针表示,可以通过函数修改相应的值
*****************************************/
void MyCAN_Receive(uint32_t *ID, uint8_t *Length, uint8_t *Data,uint8_t *IDtype)
{CanRxMsg RxMessage;//定义一个CanRxMsg结构体,用于存放接收报文CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);//接收报文//判断接收的报文是标准ID还是扩展IDif (RxMessage.IDE == CAN_Id_Standard){*IDtype=0;//表示接受的是标准帧*ID = RxMessage.StdId;//标准ID}else{*IDtype=1;//表示接收的是扩展帧*ID = RxMessage.ExtId; //扩展ID}//判断接收报文是否为数据帧还是遥控帧if (RxMessage.RTR == CAN_RTR_Data)//是否为数据帧{//数据帧*Length = RxMessage.DLC;//数据长度//数据内容for (uint8_t i = 0; i < *Length; i ++){Data[i] = RxMessage.Data[i];}}else{//遥控帧,暂时不做处理}
}
2.小米电机相应指令函数
0)获取设备ID
/*—————————————————————————————————————————————————————————————————————————————————*/
/** @brief 小米电机ID检查,通信类型为0* @param[in] id: 控制电机CAN_ID【出厂默认0x7F】**/
void check_cybergear(uint8_t ID)
{uint8_t tx_data[8] = {0};//没有数据//扩展ID的组合,依旧是3个部分txMsg.ExtId = Communication_Type_GetID<<24|Master_CAN_ID<<8|ID;MyCAN_Transmit(txMsg.ExtId,txMsg.DLC,tx_data);//写入指令
}
1)运控模式电机控制指令
/*——————————————————————————————————————————————————————————————————————————————————*/
/** @brief 小米运控模式指令,通信类型:1* @param[in] Motor: 目标电机结构体* @param[in] torque: 力矩设置[-12,12] N*M* @param[in] MechPosition: 位置设置[-12.5,12.5] rad* @param[in] speed: 速度设置[-30,30] rpm* @param[in] kp: 比例参数设置* @param[in] kd: 微分参数设置* @retval none**/
void motor_controlmode(MI_Motor *Motor,float torque, float MechPosition, float speed, float kp, float kd)
{ uint8_t tx_data[8]={0};//发送数据初始化//装填发送数据//将目标角度转化为16位2进制数,对应字节0~1,假设是0x1234tx_data[0]=float_to_uint(MechPosition,P_MIN,P_MAX,16)>>8; //取得是高位的结果,即0x12tx_data[1]=float_to_uint(MechPosition,P_MIN,P_MAX,16); //取得是低位的结果,即0x34//将目标速度转化为16位2进制数,对应字节2~3tx_data[2]=float_to_uint(speed,V_MIN,V_MAX,16)>>8; tx_data[3]=float_to_uint(speed,V_MIN,V_MAX,16); //目标KPtx_data[4]=float_to_uint(kp,KP_MIN,KP_MAX,16)>>8; tx_data[5]=float_to_uint(kp,KP_MIN,KP_MAX,16); //目标KDtx_data[6]=float_to_uint(kd,KD_MIN,KD_MAX,16)>>8; tx_data[7]=float_to_uint(kd,KD_MIN,KD_MAX,16); txMsg.ExtId = Communication_Type_MotionControl<<24|float_to_uint(torque,T_MIN,T_MAX,16)<<8|Motor->CAN_ID;//装填扩展帧数据MyCAN_Transmit(txMsg.ExtId,txMsg.DLC,tx_data);//写入指令
}
这里面有一个将浮点数转化为16位二进制数的函数,float_to_uint,其中对应的参数上下限在.h文件中进行了定义,其对应范围是0~65535,也就是0~2^16-1
其中的参数范围定义如下,这个地方不能修改,只能参照说明书定义:
/*****************控制参数最值,谨慎更改*********************/
#define P_MIN -12.56f
#define P_MAX 12.56f
#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 MAX_P 720
#define MIN_P -720
2)电机反馈数据
电机反馈数据里面,我们需要读取的就是这几项:电机的ID号,电机的角度,电机的角速度,电机的力矩,电机的温度;根据下图,可以知道接收到的信息的具体位置,进而编写出程序如下:
/*——————————————————————————————————————————————————————————————————————————————————————*/
/** @brief 电机反馈数据,通信类型2* @param[in] 信息存放的地址* @retval none*/
void Rx_Fifo0_Msg(MI_Motor *Motor)
{if (MyCAN_ReceiveFlag())//判断是否接收到报文信息{uint32_t RxID;uint8_t RxLength;uint8_t RxData[8];//接收信息放到对应的接收报文之中MyCAN_Receive(&RxID, &RxLength, RxData);Motor->CAN_ID=(RxID&0xFFFF)>>8;//获取接收数据的ID:保留低16位,其余全变成零,再右移8位,则获得了bit8~bit15的canidMotor->Angle=uint16_to_float(RxData[0]<<8|RxData[1],MIN_P,MAX_P,16);//将字节0~1转化位浮点数,即为当前角度Motor->Speed=uint16_to_float(RxData[2]<<8|RxData[3],V_MIN,V_MAX,16);//将字节2~3转化位浮点数,即为当前速度 Motor->Torque=uint16_to_float(RxData[4]<<8|RxData[5],T_MIN,T_MAX,16);//将字节4~5转化位浮点数,即为当前角度 Motor->Temp=(RxData[6]<<8|RxData[7])*Temp_Gain;//将字节4~5转化为当前温度 Motor->error_code=(RxID&0x1F0000)>>16; }
}
3)电机使能运行
/*****************************使能电机,通信类型为3******************************** @brief 使能小米电机* @param[in] Motor:对应控制电机结构体 * @retval none*****************************************************/
void start_cybergear(MI_Motor *Motor)
{uint8_t tx_data[8] = {0}; txMsg.ExtId = Communication_Type_MotorEnable<<24|Master_CAN_ID<<8|Motor->CAN_ID;MyCAN_Transmit(txMsg.ExtId,txMsg.DLC,tx_data);//写入指令
}
4) 电机停止运行
注意当数据区的Byte[0]=1时,表示清故障
/************************电机停止运行,通信类型为4********************************* @brief 停止电机* @param[in] Motor:对应控制电机结构体 * @param[in] clear_error:清除错误位(0 不清除 1清除)* @retval None*******************************************************************************/
void stop_cybergear(MI_Motor *Motor,uint8_t clear_error)
{uint8_t tx_data[8]={0};tx_data[0]=clear_error;//清除错误位设置txMsg.ExtId = Communication_Type_MotorStop<<24|Master_CAN_ID<<8|Motor->CAN_ID;MyCAN_Transmit(txMsg.ExtId,txMsg.DLC,tx_data);//写入指令
}
6)设置电机机械零位
/**********************设计电机的零点,通信类型6**************************** @brief 设置电机零点* @param[in] Motor: 电机结构体* @retval none***********************************************************/
void set_zeropos_cybergear(MI_Motor *Motor)
{uint8_t tx_data[8]={0};tx_data[0] = 1;//数据区为1//扩展帧格式txMsg.ExtId = Communication_Type_SetPosZero<<24|Master_CAN_ID<<8|Motor->CAN_ID;MyCAN_Transmit(txMsg.ExtId,txMsg.DLC,tx_data);//写入指令
}
7)设置电机的CAN_ID
/**********************设置电机的CAN_ID,通信类型为7********************* @brief 设置电机CANID* @param[in] Motor: 电机结构体* @param[in] Motor: 设置新ID* @retval none**********************************************************************/
void set_CANID_cybergear(MI_Motor *Motor,uint8_t CAN_ID)
{uint8_t tx_data[8]={0};txMsg.ExtId = Communication_Type_CanID<<24|CAN_ID<<16|Master_CAN_ID<<8|Motor->CAN_ID;Motor->CAN_ID = CAN_ID;//将新的ID导入电机结构体MyCAN_Transmit(txMsg.ExtId,txMsg.DLC,tx_data);//写入指令
}
17) 单个参数读取
/*************************单个参数读取,通信类型17******************
*@brief 电机参数读取
*@param[in] ID,电机的ID号
*/
void check_param_cybergear(MI_Motor *Motor, uint_8 index)
{uint8_t tx_data[8]={0}txMsg.ExtId = Communication_Type_GetSingleParameter<<24|Master_CAN_ID<<8|Motor->CAN_ID;//装填扩展帧数据tx_data[0]=index>>8;//高8位tx_data[1]=index;//低8位MyCAN_Transmit(txMsg.ExtId,txMsg.DLC,tx_data);//写入指令
}
其中的index为说明书里面的参数列表中的值
/******************参数读取宏定义,Index***********************/
#define Run_mode 0x7005 //运动模式
#define Iq_Ref 0x7006 //电流模式指令
#define Spd_Ref 0x700A //转速模式转速
#define Limit_Torque 0x700B //转矩限制
#define Cur_Kp 0x7010 //电流的kp
#define Cur_Ki 0x7011 //电流的ki
#define Cur_Filt_Gain 0x7014 //电流滤波系数
#define Loc_Ref 0x7016 //位置模式角度指令
#define Limit_Spd 0x7017 //位置模式速度限制
#define Limit_Cur 0x7018 //速度位置模式电流限制
#define mechPos 0x7019 //负载端计圈机械角度
#define iqf 0x701A //iq滤波值
#define mechVel 0x701B //负载端转速
#define VBUS 0x701C //母线电压
#define rotation 0x701D //圈数
#define loc_kp 0x701E //位置的kp
#define spd_kp 0x701F //速度的kp
#define spd_ki 0x7020 //速度的ki
18) 写入单个参数
/******************对应说明书的单个参数写入,通信类型为18************** @brief 写入电机参数* @param[in] Motor:对应控制电机结构体* @param[in] Index:写入参数对应地址* @param[in] Value:写入参数值* @param[in] Value_type:写入参数数据类型,可以对照字节数进行区分* @retval none*/
static void Set_Motor_Parameter(MI_Motor *Motor,uint16_t Index,float Value,char Value_type){uint8_t tx_data[8]={0};//写入的数据//扩展ID,包括三个部分:通信类型、主ID、电机IDtxMsg.ExtId = Communication_Type_SetSingleParameter<<24|Master_CAN_ID<<8|Motor->CAN_ID;//参见说明书的通信类型18里面,Index的相关应用方法,Index共tx_data[0]=Index>>8; //高8位tx_data[1]=Index; //低8位tx_data[2]=0x00;tx_data[3]=0x00;//如果参数类型为浮点型,则对应的数据所占字节数为4,对应的数据需要转化为8个字节if(Value_type == 'f'){Float_to_Byte(Value);//将数值转化为4个byte 作为返回值tx_data[4]=byte[0];//高8位tx_data[5]=byte[1];tx_data[6]=byte[2];tx_data[7]=byte[3];//低8位 }//如果参数类型为uint8,只有一个字节else if(Value_type == 's'){tx_data[4]=(uint8_t)Value;tx_data[5]=0x00;tx_data[6]=0x00;tx_data[7]=0x00; }//can_txd();MyCAN_Transmit(txMsg.ExtId,txMsg.DLC,tx_data);//写入指令
}
此外还有几个转化函数
/*———————————————————————————————————————————————————————————————————————————————————————*/
/** @brief 小米电机回文16位数据转浮点* @param[in] x:16位回文 x_min:对应参数下限 x_max:对应参数上限 bits:参数位数* @retval 返回浮点值**/
static float uint16_to_float(uint16_t x,float x_min,float x_max,int bits)
{uint32_t span = (1 << bits) - 1;//二进制数的范围2^bits-1float offset = x_max - x_min;//实际范围return offset * x / span + x_min;//转换为浮点数
}
/*———————————————————————————————————————————————————————————————————————————————————*/
/** @brief 小米电机发送浮点转16位数据每个数据都是2个字节也就是16位二进制数* static 表示其作用域仅限于这个文件*/
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; //限制X的范围if(x > x_max) x=x_max;else if(x < x_min) x= x_min;return (int) ((x-offset)*((float)((1<<bits)-1))/span);//(x-下限)/((2^bits-1)/范围)
}
/*————————————————————————————————————————————————————————————————————————————————————*/
/** @brief 浮点数转4字节函数,这个用于写入参数时使用*/
static uint8_t* Float_to_Byte(float f)
{unsigned long longdata = 0;//无符号长整型longdata = *(unsigned long*)&f; //取f的地址,将该地址强制转化为无符号长整型指针,通过*解引用,获得该地址处的值 byte[0] = (longdata & 0xFF000000) >> 24;//将最高8位提出来byte[1] = (longdata & 0x00FF0000) >> 16;//将第二个高8位提出来byte[2] = (longdata & 0x0000FF00) >> 8;//将第三个高8位提出来byte[3] = (longdata & 0x000000FF);//将第四个高8位提出来return byte;//返回的byte为四个字节的数组
}
3.主函数main.c 对相应的函数进行调用与验证
#include "main.h"
#include "cybergear.h"
/*******************小米电机的控制***************/
/*
*v1:目前已经把小米电机的库程序建立起来,开始进行验证,已经验证的程序有:初始化和使能函数2024.10.7
*/
uint8_t KeyNum;uint32_t RxID;
uint8_t RxLength;
uint8_t RxData[8];
uint8_t RxIDtype;
MI_Motor Cyber;
uint8_t Motor_ID=0x7F;
uint8_t Mode=Motion_mode;//控制模式为运动控制
uint32_t id;
SM_Param M_set;int main(void)
{//初始化OLED_Init();Key_Init();//按键初始化,按键对应的引脚分别位B1和B11MyCAN_Init();//CAN初始化,CAN对应的引脚位A11和A12,A11输入,A12输出//以下用于调用程序验证相应的通信数据是否准确init_cybergear(&Cyber, Motor_ID, Mode);//初始化电机//*校验用的内容id=check_cybergear(Cyber.CAN_ID);//小米电机ID检查,通信类型为0/*此处验证程序为运控模式的电机控制:motor_controlmode*扩展ID显示为01 BFFF 7F;其中01为通讯类型,BFFF为力矩;7F为电机ID号*数据为9001 BFFF 3851 428F 分别表示目标角度、目标角速度、对应KP,对应KD*///OLED_ShowHexNum(1, 1, Cyber.CAN_ID,2);M_set.Position=pi/2;//带入程序数据的计算结果为36865,映射到16进制为9001M_set.Speed=15;//45/60*65535=49151,转换为16进制是BFFFM_set.Kp=110;//110/500*65535=14417,转换为16进制是3851M_set.Kd=1.3;//1.3/5*65535=17039,转换为16进制是428FM_set.Torque=6;//映射到区域为(6-(-12))/(12-(-12))=3/4;3/4*65535=49151;49151转化为16进制为BFFFmotor_controlmode(&Cyber,M_set.Torque, M_set.Position, M_set.Speed, M_set.Kp, M_set.Kd);//使能验证//扩展ID为:03 0000 7F//数据为:0000 0000 0000 0000start_cybergear(Cyber.CAN_ID);//使能小米电机,通信类型为3//停止验证//第二个参数为清除错误位,0表示不清除,1表示清除//扩展ID为:04 0000 7F//数据为:0000 0000 0000 0000/0100 0000 0000 0000stop_cybergear(&Cyber,1);//置零验证//扩展ID为:06 0000 7F//数据为:0100 0000 0000 0000set_zeropos_cybergear(&Cyber);//设置电机的ID号//扩展ID为:07 1200 7F//数据:0000 0000 0000 0000Motor_ID=0x12;set_CANID_cybergear(&Cyber,Motor_ID);//读取单个参数//扩展ID:11 0000 12;其中0x11表示的十进制的17//数据:7005 0000 0000 0000Index index;index=RunMode_idx;//0x7005check_param_cybergear(&Cyber, index);//设置电机模式//扩展ID:12 0000 12//数据:70 05 00 00 01 00 00 00set_mode_cybergear(&Cyber,Position_mode);//设置电机参数//扩展ID:12 0000 12;其中0x12 表示十进制的18//数据:70 05 00 00 01 00 00 00(1字节数据在byte4,其余数据为byte4~7)Value_type value_typ;value_typ=RunMode_Typ;Set_Motor_Parameter(&Cyber,index,Position_mode,value_typ);while (1){}
}
利用显示器验证相应的发送指令,验证代码如下:
OLED_ShowString(1, 1, "ID:");//第3行第1列显示OLED_ShowHexNum(2, 1, txMsg.ExtId,8 );OLED_ShowString(3, 1, "Data:");//第3行第1列显示for (int i=1;i<=8;i++){OLED_ShowHexNum(4, i*2-1, tx_data[i-1],2 );}
本文电机函数的验证方法:逐个函数调用,然后通过屏幕显示指令进行验证,得到相应的显示结果与通讯协议的指令格式一致则表明函数无误。
本文参考了博文、视频教程:小米电机CyberGear STM32HAL 使用指南_小米电机瞬时电流串口指令-CSDN博客
[3-1] 单个设备环回测试&三个设备互相通信_哔哩哔哩_bilibili
相关文章:

小米电机与STM32——CAN通信
背景介绍:为了利用小米电机,搭建机械臂的关节,需要学习小米电机的使用方法。计划采用STM32驱动小米电机,实现指定运动,为此需要了解他们之间的通信方式,指令写入方法等。花了很多时间学习,但网络…...

2.2.ReactOS系统KSERVICE_TABLE_DESCRIPTOR结构体的声明
2.2.ReactOS系统KSERVICE_TABLE_DESCRIPTOR结构体的声明 2.2.ReactOS系统KSERVICE_TABLE_DESCRIPTOR结构体的声明 文章目录 2.2.ReactOS系统KSERVICE_TABLE_DESCRIPTOR结构体的声明KSERVICE_TABLE_DESCRIPTOR系统调用表结构体的声明 KSERVICE_TABLE_DESCRIPTOR系统调用表结构体…...

前端接口报500如何解决 | 发生的原因以及处理步骤
接口500,通常指的是服务器内部错误(Internal Server Error),是HTTP协议中的一个标准状态码。当服务器遇到无法处理的错误时,会返回这个状态码。这种错误可能涉及到服务器配置、服务器上的应用程序、服务器资源、数据库…...

图书馆自习室座位预约管理微信小程序+ssm(lw+演示+源码+运行)
摘 要 随着电子商务快速发展世界各地区,各个高校对图书馆也起来越重视.图书馆代表着一间学校或者地区的文化标志,因为图书馆丰富的图书资源能够带给我们重要的信息资源,图书馆管理系统是学校管理机制重要的一环,,面对这一世界性的新动向和新…...

谷歌-BERT-第一步:模型下载
1 需求 需求1:基于transformers库实现自动从Hugging Face下载模型 需求2:基于huggingface-hub库实现自动从Hugging Face下载模型 需求3:手动从Hugging Face下载模型 2 接口 3.1 需求1 示例一:下载到默认目录 from transform…...

FPGA实现PCIE采集电脑端视频缩放后转千兆UDP网络输出,基于XDMA+PHY芯片架构,提供3套工程源码和技术支持
目录 1、前言工程概述免责声明 2、相关方案推荐我已有的PCIE方案我这里已有的以太网方案本博已有的FPGA图像缩放方案 3、PCIE基础知识扫描4、工程详细设计方案工程设计原理框图电脑端视频PCIE视频采集QT上位机XDMA配置及使用XDMA中断模块FDMA图像缓存纯Verilog图像缩放模块详解…...

Hi3061M开发板——系统时钟频率
这里写目录标题 前言MCU时钟介绍PLLCRG_ConfigPLL时钟配置另附完整系统时钟结构图 前言 Hi3061M使用过程中,AD和APT输出,都需要考虑到时钟频率,特别是APT,关系到PWM的输出频率。于是就研究了下相关的时钟。 MCU时钟介绍 MCU共有…...

C++入门基础知识110—【关于C++ if...else 语句】
成长路上不孤单😊😊😊😊😊😊 【14后😊///C爱好者😊///持续分享所学😊///如有需要欢迎收藏转发///😊】 今日分享关于C if...else 语句的相关内容!…...

基于YOLO11深度学习的非机动车驾驶员头盔检测系统【python源码+Pyqt5界面+数据集+训练代码】深度学习实战、目标检测、卷积神经网络
《博主简介》 小伙伴们好,我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源,可关注公-仲-hao:【阿旭算法与机器学习】,共同学习交流~ 👍感谢小伙伴们点赞、关注! 《------往期经典推…...

图像分类-demo(Lenet),tensorflow和Alexnet
目录 demo(Lenet) 代码实现基本步骤: TensorFlow 一、核心概念 二、主要特点 三、简单实现 参数: 模型编译 模型训练 模型评估 Alexnet model.py train.py predict.py demo(Lenet) PyTorch提供了一个名为“torchvision”的附加库,其中包含…...

excel 单元格嵌入图片
1.图片右键,设置图片格式 2.属性 随单元格改为位置和大小 这样的话,图片就会嵌入到单元格,也会跟着单元格的大小而改变...

GitHub简介与安装使用入门教程
1、Git与GitHub的简介 Git是目前世界上最先进的分布式控制系统,它允许开发者跟踪和管理源代码的改动历史记录等,可以将你的代码恢复到某一个版本,支持多人协作开发。它的核心功能包括版本控制、分支管理、合并和冲突解决等,其操作…...

HTML(五)列表详解
在HTML中,列表可以分为两种,一种为有序列表。另一种为无序列表 今天就来详细讲解一下这两种列表如何实现,效果如何 1.有序列表 有序列表的标准格式如下: <ol><li>列表项一</li><li>列表项二</li>…...

SparkSQL介绍及使用
SparkSQL介绍及使用 一、什么是SparkSQL(了解) spark开发时可以使用rdd进行开发,spark还提供saprksql工具,将数据转为结构化数据进行操作 1-1 介绍 官网:https://spark.apache.org/sql/ Spark SQL是 Apache Spark 用于…...

【聚星文社】3.2版一键推文工具更新啦
【聚星文社】3.2版一键推文工具更新啦。调试了好几个通宵就是为了效果和质量。 旧版尽早更新新版,从此告别手搓! 工具入口https://iimenvrieak.feishu.cn/docx/ZhRNdEWT6oGdCwxdhOPcdds7nof...

C++基础补充(03)C++20 的 std::format 函数
文章目录 1. 使用C20 std::format2. 基本用法3. 格式说明 1. 使用C20 std::format 需要将VisualStudio默认的标准修改为C20 菜单“项目”-“项目属性”,打开如下对话框 代码中加入头文件 2. 基本用法 通过占位符{}制定格式化的位置,后面传入变量 #…...

[论文笔记]DAPR: A Benchmark on Document-Aware Passage Retrieval
引言 今天带来论文DAPR: A Benchmark on Document-Aware Passage Retrieval的笔记。 本文提出了一个基准:文档感知段落检索(Document-Aware Passage Retrieval,DAPR)以及介绍了一些上下文段落表示的方法。 为了简单,下文中以翻译的口吻记录,…...

Spring Boot知识管理:智能搜索与分析
3系统分析 3.1可行性分析 通过对本知识管理系统实行的目的初步调查和分析,提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本知识管理系统采用JAVA作为开发语言,Spring Boot框…...

操作系统(2) (进程调度/进程调度器类型/三种进程调度/调度算法)
目录 1. 介绍进程调度(Introduction to Process Scheduling) 2. 优先级调度(Priority Scheduling) 3. CPU 利用率(CPU Utilization) 4. 吞吐量(Throughput) 5. 周转时间…...

鸿蒙--知乎评论
这里我们将采用组件化的思想进行开发 在开发中默认展示的是首页也就是 pages/Index.ets页面 这里存放的是所有页面的配置文件,类似与uniapp中的pages.json 如果我们此时要更改默认显示Zh...

2024 - 两台CentOS服务器上的1000个Docker容器(每台500个)之间实现UDP通信(C语言版本)
两台CentOS服务器上的1000个Docker容器(每台500个)之间实现UDP通信(C语言版本) 给女朋友对象写得,她不会,我就写了一个 为了帮助您在两台CentOS服务器上的1000个Docker容器(每台500个)之间实现UDP通信&…...

小程序该如何上架
小程序的上架流程通常包括准备工作、代码审核、人工审核以及上线发布等关键步骤。以下是一个详细的小程序上架指南: 一、准备工作 注册开发者账号: 在微信小程序平台或支付宝开放平台等相应的小程序发布平台上注册开发者账号。 开发小程序: …...

XMOJ3065 旅游线路
10分钟没啥思路就去看题解了,结果发现很蠢。 题目大意 有一条河,河的东侧和西侧分别有 n , m n,m n,m 个景点,每个景点有个权值。有 k k k 条船,每条船连接东侧和西侧的一个景点。定义一个旅游线路是通过船连接起来的景点序列…...

量化之一:均值回归策略
文章目录 均值回归策略理论基础数学公式 关键指标简单移动平均线(SMA)标准差Z-Score 交易信号实际应用优缺点分析优点缺点 结论 实践backtrader参数:正常情况:异常情况: 均值回归策略 均值回归(Mean Rever…...

NVIDIA Bluefield DPU上的启动流程4个阶段分别是什么?作用是什么?
文章目录 Bluefield上的硬件介绍启动流程启动流程:eMMC中的两个存储分区:ATF介绍ATF启动的四个阶段:四个主要步骤:各个阶段依赖的启动文件一次烧录fw失败后的信息看启动流程综述Bluefield上的硬件介绍 本文以Bluefield2为例,可以看到RSHIM实际上是Boot相关的集合。也能看…...

最优美公式-欧拉公式,轻松理解版
Alan Becker创作的火柴人大战数学的打斗视频,风靡一时,并在B站荣耀斩获了“金知奖”。下面是网友对此视频的部分评价截图。 视频原址:火柴人 vs 数学,后续又一口气看完了“火柴人vs 几何”与“火柴人vs 物理”,通过火柴…...

【力扣 | SQL题 | 每日3题】力扣1107,1112, 1077
今天三道mid题都可以用窗口函数轻松秒杀。 1. 力扣1107:每日新用户统计 1.1 题目: Traffic 表: ------------------------ | Column Name | Type | ------------------------ | user_id | int | | activity | enum …...

计算机网络(十一) —— 数据链路层
目录 一,关于数据链路层 二,以太网协议 2.1 局域网 2.2 Mac地址 2.3 Mac帧报头 2.4 MTU 三,ARP协议 3.1 ARP是什么 3.2 ARP原理 3.3 ARP报头 3.4 模拟ARP过程 3.5 ARP周边问题 四,NAT技术 4.1 NAT技术背景 4.2 NAT转…...

使用PyTorch从0实现Fashion-MNIST数据集分类
完整代码: from d2l import torch as d2l import torch from torchvision import transforms from torchvision import datasets from torch.utils.data import DataLoader import matplotlib.pyplot as plt from IPython import displaydef get_fashion_mnist_la…...

Java数组的值拷贝和地址拷贝
在Java中,数组的值拷贝和地址拷贝是两种不同的操作。 值拷贝是指将一个数组的值复制到另一个新的数组中。这意味着新数组和原数组独立存在,修改其中一个数组不会影响另一个数组。Java中的数组是对象,所以通过值拷贝操作实际上是复制了数组对…...