小米电机与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...

网络六边形受到攻击
大家读完觉得有帮助记得关注和点赞!!! 抽象 现代智能交通系统 (ITS) 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 (…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...

聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?
论文网址:pdf 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔记,谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...

el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...