利用CAN总线控制关节电机
电机向主控发送反馈帧,反馈点击状态,电机位置,速度,力矩以及电机温度和电机参数。那我的代码举例,比如我关节电机向我发送的数据的ID是0x000,我向电机发送的ID是0x001,那么我只要把数据帧的内容规定和采集好,就可以实现双方的交流了。我的理解是,FOC程序本身非常复杂且消耗算力(我有亲自写过FOC的算法程序,想了解的可以看看我发布的前几篇博客),如果用一块单片机同时控制多个内部没有集成FOC主
本文总结一下怎么利用CAN协议来控制关节电机,我用的关节电机是达妙科技的4310电机,想要控制这个电机,还需要用到一块单片机(有CAN外设),CAN收发器以及烧入器。(提醒:本文不介绍CAN的相关理论知识,能刷到我这篇文章的肯定早就学过了)
在这之前,我谈谈我对关节电机的理解。之前在B站上看到过一个UP主拆解智元科技的关节电机,看着他拆解关节电机后,电机内部的图片,并结合我学习CAN的知识之后,我真正理解了为什么关节电机要用CAN控制了。

推荐去B站看看原视频,这样利于加深理解。关节电机里面除了集成三相无刷电机之外,内部也集成了一块带有主控的无刷驱动板,而主控里面有厂家提前烧入好的程序,里面有FOC算法以及CAN协议的相关代码,并且这个关节电机是有感的。所以只要接好单片机与关节电机之间的两条差分信号线(CAN),就可以直接控制关节电机了。
那么为什么要这么做呢?我的理解是,FOC程序本身非常复杂且消耗算力(我有亲自写过FOC的算法程序,想了解的可以看看我发布的前几篇博客),如果用一块单片机同时控制多个内部没有集成FOC主控的关节电机,意味着一块单片机要同时控制多路FOC,这样会导致单片机算不过来,每个电机的控制都差强人意。所以就要让每一个关节电机里都集成一个这样的无刷驱动板,每个主控各自负责各自的关节电机的控制。
再说说为什么要控制关节电机,用的是CAN协议而不是其他协议诸如SPI,I2C以及UART。首先,对于机器人这种复杂场景,是不可能只用一个关节电机就足够的,势必会有多个关节电机需要同时控制。而对于SPI和I2C,最显著的缺点就是接线及其复杂,同时使用多个关节电机意味着该机器人的接线将及其复杂,而CAN总线就两条电路,大大降低了布线成本。
其次,SPI和I2C比较适合一主多从的模型,好比是一块单片机控制多个传感器,这种情况比较适合用以上两者。而关节电机可不单单是从模型,它也是需要把当前电机的位置,速度以及力矩及时反馈回主控的,所以是多主的模型,因此才考虑用CAN去控制。并且用CAN去控制还有其他好处,比如相比于I2C,它的传输速率也是非常快的,可以达到1Mbps,甚至CAN的进阶FDCAN(更高端的单片机才支持,我用的F407不支持)可以传输得更快,FDCAN就是在数据段传输时更快了,变成8Mbps,在ID段还是1Mbps。
接下来,我来说说怎么用CAN来控制关节电机:
1.STM32HAL库的配置:


照我这样配置,可以把CAN通信速率设置到1Mbps,这个模式有正常模式,环回模式等多种模式可选。如果你对自己的第一版代码不自信,可以先用环回模式测试一下。
然后就是使能一下FIFO0,使得接收到数据可以及时被CPU读走而不是积累导致数据被覆盖。
2.CAN代码编写:
首先来说说MIT协议,MIT其实就是一种与电机约定好的一种格式,它基于CAN,与电机约定传输的八个数据字节都传输的是什么内容。


主控与关节电机之间有约定好,每一位分别对应着什么,这就是MIT协议。主控向电机发送控制帧,控制电机参数,位置,速度,力矩;电机向主控发送反馈帧,反馈点击状态,电机位置,速度,力矩以及电机温度和电机参数。所以只要按照MIT规定的格式,就可以实现对关节电机的控制。那我的代码举例,比如我关节电机向我发送的数据的ID是0x000,我向电机发送的ID是0x001,那么我只要把数据帧的内容规定和采集好,就可以实现双方的交流了。
//将过滤器配置为16位列表模式
void CANFilterConfig_Scale16_IdList(CAN_HandleTypeDef * hcan,uint32_t StdId1,uint32_t StdId2,uint32_t StdId3,uint32_t StdId4,uint8_t Filter_Nunber)
{
CAN_FilterTypeDef sFilterConfig;
sFilterConfig.FilterBank = Filter_Nunber; //选择过滤器的号码
sFilterConfig.FilterMode = CAN_FILTERMODE_IDLIST; //列表模式
sFilterConfig.FilterScale = CAN_FILTERSCALE_16BIT; //16位模式
sFilterConfig.FilterIdHigh = StdId1<<5;
sFilterConfig.FilterIdLow = StdId2<<5;
sFilterConfig.FilterMaskIdHigh = StdId3<<5;
sFilterConfig.FilterMaskIdLow = StdId4<<5;
sFilterConfig.FilterFIFOAssignment = 0; //选择接收邮箱为FIFO0
sFilterConfig.FilterActivation = ENABLE;
sFilterConfig.SlaveStartFilterBank = 0; //双CAN模式时才用到,不用就给0
HAL_CAN_ConfigFilter(hcan, &sFilterConfig);
}
//CAN的发送函数
void CANx_SendStdData(CAN_HandleTypeDef* hcan,uint16_t ID,uint8_t *pData,uint16_t Len)
{
static CAN_TxHeaderTypeDef Tx_Header;
Tx_Header.StdId=ID;
Tx_Header.ExtId=0;
Tx_Header.IDE=0;
Tx_Header.RTR=0;
Tx_Header.DLC=Len;
if(HAL_CAN_AddTxMessage(hcan, &Tx_Header, pData, (uint32_t*)CAN_TX_MAILBOX0) != HAL_OK)
{
if(HAL_CAN_AddTxMessage(hcan, &Tx_Header, pData, (uint32_t*)CAN_TX_MAILBOX1) != HAL_OK)
{
HAL_CAN_AddTxMessage(hcan, &Tx_Header, pData, (uint32_t*)CAN_TX_MAILBOX2);
}
}
}
//CAN的启动函数,这个函数必须要提前调用,否则单片机的CAN外设不会被启动
void CAN_Start(CAN_HandleTypeDef *hcan)
{
HAL_CAN_ActivateNotification(hcan ,CAN_IT_RX_FIFO0_MSG_PENDING);
HAL_CAN_Start(hcan);
}
//这个与MIT协议有关,要对关节电机发送数据中,需要把里面的浮点数转换为16进制数发给关节电机,关节电机内部会自行解析
int float_to_uint(float x, float x_min, float x_max, int bits)
{
float span = x_max - x_min;
float offset = x_min;
return (int) ((x-offset)*((float)((1<<bits)-1))/span);
}
float uint_to_float(int x_int,float x_min,float x_max,int bits)
{
float span = x_max - x_min;
float offset = x_min;
return ((float)x_int)*span/((float)((1<<bits)-1)) + offset;
}
//对电机发送数据要调用这个函数,需要将发送的参数进行16进制化
void motor_control(CAN_HandleTypeDef* hcan,uint16_t id, float _pos, float _vel,float _KP, float _KD, float _torq)
{
uint16_t pos_tmp,vel_tmp,kp_tmp,kd_tmp,tor_tmp;
pos_tmp = float_to_uint(_pos, P_MIN, P_MAX, 16);
vel_tmp = float_to_uint(_vel, V_MIN, V_MAX, 12);
kp_tmp = float_to_uint(_KP, KP_MIN, KP_MAX, 12);
kd_tmp = float_to_uint(_KD, KD_MIN, KD_MAX, 12);
tor_tmp = float_to_uint(_torq, T_MIN, T_MAX, 12);
uint8_t Data[8];
Data[0] = (pos_tmp >> 8);
Data[1] = pos_tmp;
Data[2] = (vel_tmp >> 4);
Data[3] = ((vel_tmp&0xF)<<4)|(kp_tmp>>8);
Data[4] = kp_tmp;
Data[5] = (kd_tmp >> 4);
Data[6] = ((kd_tmp&0xF)<<4)|(tor_tmp>>8);
Data[7] = tor_tmp;
CANx_SendStdData(hcan,id,Data,8);
}
以上的每一个函数我都做了详细的注释,通过以上的函数,就可以实现主控向关节电机发送的控制帧。值得注意的是,里面的P_MIN,T_MAX这些参数,都是MIT公式里提前约定好的参数,我也是通过电机手册获取的,我把他们直接#define在.h文件里了。接下来再讲讲怎么接收关节电机向主控发送来的反馈帧,由于我配置了FIFO0的中断,所以可以在中断回调函数中执行操作,利用好uint_to_float函数:
uint8_t data_CAN1[8];
int p_int = 0,v_int = 0,t_int = 0;
float position = 0,velocity = 0,torque = 0;
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
if(hcan->Instance ==CAN1)
{
CAN_RxHeaderTypeDef RxHeader;
HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &RxHeader, data_CAN1);
p_int=(data_CAN1[1]<<8)|data_CAN1[2];
v_int=(data_CAN1[3]<<4)|(data_CAN1[4]>>4);
t_int=((data_CAN1[4]&0xF)<<8)|data_CAN1[5];
position = uint_to_float(p_int, P_MIN, P_MAX, 16);
velocity = uint_to_float(v_int, V_MIN, V_MAX, 12);
torque = uint_to_float(t_int, T_MIN, T_MAX, 12);
}
}
这样我们就能及时的读走FIFO里的东西,并且获得此时关节电机的参数了。接下来我再把main,c文件的关键代码放出:
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_CAN1_Init();
MX_UART4_Init();
/* USER CODE BEGIN 2 */
CANFilterConfig_Scale16_IdList(&hcan1,0x000,0x231,0x232,0x233,0); //Master ID是0x000,CAN ID是0x001
CAN_Start(&hcan1);
CANx_SendStdData(&hcan1,0x001,motor_enable,8); //先给电机使能,发送使能指令
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
motor_control(&hcan1,0x001,0,2,0,1,0);
HAL_UART_Transmit(&huart4,(uint8_t*)&p_int,1,HAL_MAX_DELAY);
HAL_UART_Transmit(&huart4,(uint8_t*)&v_int,1,HAL_MAX_DELAY);
HAL_UART_Transmit(&huart4,(uint8_t*)&t_int,1,HAL_MAX_DELAY);
HAL_UART_Transmit(&huart4,(uint8_t*)&position,1,HAL_MAX_DELAY);
HAL_UART_Transmit(&huart4,(uint8_t*)&velocity,1,HAL_MAX_DELAY);
HAL_UART_Transmit(&huart4,(uint8_t*)&torque,1,HAL_MAX_DELAY);
}
/* USER CODE END 3 */
}更多推荐
所有评论(0)