RM系列电机,电调介绍

Robomaster官方提供了一系列性能各异,可以用于不同场景,且易于驱动的直流无刷减速电机及配套电调,这里主要介绍三款常用的电机&电调——M3508电机&C620电调,GM6020电机(内部集成电调),M2006&C610电调。

这些电调的手册,驱动demo等同样可以到官网上去下载

https://www.robomaster.com/zh-CN/products/components/general

直流无刷电机不使用传统有刷电机的电刷机械结构,而是通过电子换向器实现换向,相比传统电机有着许多的性能优势,一般使用直流无刷电机时需要有配套的电调,通过改变电调输出的电流大小和方向,可以改变电机的转速和转向。

Robomaster系列的电机内部都有霍尔传感器,可以反馈电机的转速,位置等信息,以供用户实现闭环控制。

一般使用电机时都是先将电机与配套电调连接,电调与电源以及主控板连接,这里以M3508电机为例,其他电机使用时也是同理,首先是将电机和电调互联,C620电调上有一个xt30电源输入口和一个2pin的CAN接口。

官方提供了中心板,电调的电源和信号口可以连接至中心板,再由中心板连接电池和主控。一个中心板上有4个xt30电源输出,4个2pin的CAN接口,1个xt60的电源输入和1个8pin的电源&CAN组合输出,刚刚好可以组成一个四轮底盘。

https://www.robomaster.com/zh-CN/products/components/detail/143

RM的A型主控板上一共有两路CAN,一路是CAN1,采用2pin接口,一路是CAN2,采用4pin接口,可以直接使用双头2pin线连接主控板CAN1接口&中心板或主控板CAN1接口&电调,也可以通过2pin转4pin线连接CAN2接口。

电机是整个机器人上最重要的执行器之一,基本上一个机器人的控制流中,所有输入的最终目的都是为了体现在电机的输出上。一个典型的robomaster步兵机器人身上一共会用到哪些电机呢?以大疆开源的ICRA机器人为例

https://www.robomaster.com/zh-CN/products/components/robot

其使用的电机如下表

电机位置 电机型号
底盘电机*4 M3508
云台yaw轴电机,pitch轴电机 GM6020
拨弹电机*1 M2006
摩擦轮电机*2 Snail 2305

其中Snail电机是前文中没有提到的,这是一个PWM控制的直流无刷电机,由于没有霍尔传感器,该电机不能实现闭环控制,有一些队伍会使用去掉减速箱的M3508电机作为替代方案。

不同的电机有着不同的性能,因此被用于不同的机构中,具体使用哪一款电机需要通过分析转速,扭矩等需求进行选型,获取这些参数的直接手段就是查阅官方的手册,这里依然是以M3508电机为例。

Robomaster系列的电机及配套电调几乎全部是通过CAN总线连接到主控的,即主控通过CAN总线发送数据给电调,实现电机的调速,电调通过CAN总线将电机数据反馈给主控。

RM系列的电机&电调是专门针对比赛进行过设计的,在实际的赛场环境中也确实有着很好的发挥,下面是官方论坛上发布的测评贴,内容很有趣,值得一读。

https://bbs.robomaster.com/thread-5009-1-1.html

CAN通讯

如上一小节所说,RM系列电机&电调大都是使用CAN进行通讯的,因此掌握了CAN通讯就搞定了一大半的电机驱动,其重要性不言而喻,但CAN是一个相对而言比较复杂的通讯协议,相比于UART,SPI,IIC这些常用的通讯协议,CAN有着更多的特性需要去记忆,本节将对CAN的一些比较重要的特性进行梳理,但是不会涉及到CAN的全貌,因为如果要介绍全的话可能要写很长很长了.......

  • 硬件层面

    • 差分信号

      与其他通信方式重要差别之一是CAN采用的是“差分信号”,即通过组成总线的2根线(CAN-H和CAN-L)的电位差来确定总线的电平,信号是以两线之间的“差分”电压形式出现,总线电平分为显性电平和隐性电平。

      CAN总线采用两种互补的逻辑数值"显性"和"隐性"。"显性"数值表示逻辑"0",而"隐性"表示逻辑"1"。当总线上同时出现“显性”位和“隐性”位时,最终呈现在总线上的是“显性”位。

      与串口这种除了TX和RX,还需要用GND连接两个设备串行通讯方式不同,CAN总线只需要CAN_H和CAN_L两根线,就能够通过差分信号的方式表征逻辑"0"和逻辑"1"

    • 帧仲裁

      任何总线都不得不需要面临处理冲突的问题,因为多个设备都挂载在总线上,难免会出现若干个设备同时想要发送信号的情况,这种情况下就需要进行仲裁,判断哪个设备可以占用总线,而其他设备要转变为接收或者等待。

      CAN的仲裁机制正好利用了差分信号的特性,即显性电平覆盖隐形电平的特性,如果出现多个设备同时发送的情况,则先输出隐形电平的设备会失去对总线的占有权。下图中D为显性电平,R为隐形电平,通过该图可以很容易地理解CAN的仲裁机制。

    • 波特率

      CAN有着很高的通讯速率,通过查阅手册可知,一般RM系列电调的通讯速率为1Mbps,只有波特率一致的情况下,主控才能成功与电调进行通讯,CAN的通讯速率的决定因素包括

      • 同步段(SYNC_SEG):位变化应该在此时间段内发生。只有一个时间片的固定长度(1 x tq)
      • 位段1(BS1):定义采样点的位置。其持续长度可以在 1 到 16 个时间片之间调整
      • 位段2(BS2):定义发送点的位置。其持续长度可以在 1 到 8 个时间片之间调整
      • 同步跳转宽度(SJW):定义位段加长或缩短的上限。它可以在 1 到 4 个时间片之间调整

      在ST官方的手册中可以找到波特率的计算公式,通过用户对时钟树,分频值,以及上面4个值的设置,就可以得到想要的波特率。

      当然,这种计算一般是有套路的,一个特定的波特率一般会有对应的一组值,比如针对RM系列电调,一套典型的设置值如下(来自官方开源代码),APB1外设时钟42MHz,分频值被设置为7,SJW被设置为1tq,BS1被设置为2tq,BS2被设置为3tq,可以计算出波特率恰好是1Mbps。当然,具体的设置还是要根据时钟树来进行。

        hcan1.Instance = CAN1;
      hcan1.Init.Prescaler = 7;
      hcan1.Init.Mode = CAN_MODE_NORMAL;
      hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ;
      hcan1.Init.TimeSeg1 = CAN_BS1_2TQ;
      hcan1.Init.TimeSeg2 = CAN_BS2_3TQ;
  • 软件层面

    • 过滤器

      CAN的过滤器的目的是很容易理解的,由于总线上的信号是以广播的形式发送的,如果设备都在对于每一个被广播的信号都进行接收+判断,那么势必会浪费大量的时间在这项其实没有什么意义的工作上,解决的方法就是通过设置过滤器,屏蔽掉一些和自己无关的设备发来的信息。

      我们都知道所有CAN设备都是有ID的,具体的ID我们可以从手册中获取。以C620电调为例,可以在C620电调的手册中看到,电调反馈信号时其ID为0x201-0x204。

      https://www.robomaster.com/zh-CN/products/components/general/M3508

      在知道了设备ID之后,为了实现过滤的功能,我们需要对CAN过滤器进行配置。

      CAN的过滤器模式分为掩码模式和列表模式,列表模式简单来说就是制作一张ID表,如果来的数据的ID在这张表中则接收,否则不收。

      重点介绍一下掩码模式的原理:掩码模式的思路很容易理解,举个例子,某所学校的学号构成方式为[4位10进制 入学年份]+[4位10进制 学生序号],比如一个2016年入学的学生,其学号可以是20161234,那么假如要开一个2016年毕业生的庆祝会,会场门口要检查每一个人的学号,只有2016级的才可以进入,这里应该使用什么样的判断方法呢?

      首先,我们需要设置屏蔽码,屏蔽掉后四位的学生序号,因为他们和本次检测无关,反而增大了计算量。

      然后设置检验码2016,如果屏蔽后的结果等于2016,则可以放行。

      如下表所示,第一行为原码,第二行为掩码,将第一行表格中的数与掩码相乘,即得到第三行的屏蔽码,最后一行是验证码,屏蔽码和验证码比较确定一致后,就接收该学号。

      2 0 1 6 1 2 3 4
      1 1 1 1 0 0 0 0
      2 0 1 6 0 0 0 0
      2 0 1 6 0 0 0 0

      这里依然是以官方开源代码为例,每一行添加注释说明其功能

        can_filter_st.FilterActivation = ENABLE;				//satori:激活滤波器
      can_filter_st.FilterMode = CAN_FILTERMODE_IDMASK; //satori:采用掩码模式
      can_filter_st.FilterScale = CAN_FILTERSCALE_32BIT; //satori:设置32位宽
      can_filter_st.FilterIdHigh = 0x0000; //satori:设置验证码高低各4字节
      can_filter_st.FilterIdLow = 0x0000;
      can_filter_st.FilterMaskIdHigh = 0x0000; //satori:设置屏蔽码高低各4字节
      can_filter_st.FilterMaskIdLow = 0x0000;
      can_filter_st.FilterBank = 0; //satori:使用0号过滤器
      can_filter_st.FilterFIFOAssignment = CAN_RX_FIFO0; //satori:通过CAN的信息放入0号FIFO
      HAL_CAN_ConfigFilter(&hcan1, &can_filter_st);

      那么我们来看一下开源代码中验证码和屏蔽码这两项的配置,屏蔽码设为0x00000000,无论任何标识符通过之后都变成0x00000000,验证码为0x00000000,所以无论任何屏蔽码都能通过。可见其实并没有起到任何过滤作用,这是因为CAN总线上挂载的四个电调,我们的主控都需要接收其数据,所以无论来的标识符是哪个,都要照单全收,而CAN不配置完过滤器是无法开启的,所以才有这套验证码+屏蔽码都是0x00000000的操作。

      最后贴一个CSDN上写的比较好的博客,推荐大家也读一下

      https://blog.csdn.net/flydream0/article/details/52317532

    • 标准数据帧

      CAN的一个标准数据帧包括以下几个部分——

      仲裁场中包含12位的标识符

      仲裁场后跟随的是控制场,存放数据长度DLC,数据场中要填写CAN发送的数据

      最后的CRC,应答这些就与校验,总线控制等有关了,和用户没有太大关系。

      具体该怎么配置,还是需要根据手册走,这里以C620电调为例,在手册中我们可以找到如下内容——

      电调接收报文格式:

      电调反馈报文格式:

电调信号收发示例

依然是以官方开源代码为例,我们先看CAN接收的过程,即如何从CAN接收中断回调函数开始,一步一步送到解码函数中,调用过程如下

HAL_CAN_RxFifo0MsgPendingCallback->can1_motor_msg_rec->motor_device_data_update->get_encoder_data

编码器解码函数,主要完成的工作依然是数据拼接

static void get_encoder_data(motor_device_t motor, uint8_t can_rx_data[])
{
motor_data_t ptr = &(motor->data);
ptr->msg_cnt++; if (ptr->msg_cnt > 50)
{
motor->init_offset_f = 0;
} if (motor->init_offset_f == 1)
{
get_motor_offset(ptr, can_rx_data);
return;
} ptr->last_ecd = ptr->ecd;
//satori:data[0]和data[1]拼接成转子机械角度
ptr->ecd = (uint16_t)(can_rx_data[0] << 8 | can_rx_data[1]); if (ptr->ecd - ptr->last_ecd > 4096)
{
ptr->round_cnt--;
ptr->ecd_raw_rate = ptr->ecd - ptr->last_ecd - 8192;
}
else if (ptr->ecd - ptr->last_ecd < -4096)
{
ptr->round_cnt++;
ptr->ecd_raw_rate = ptr->ecd - ptr->last_ecd + 8192;
}
else
{
ptr->ecd_raw_rate = ptr->ecd - ptr->last_ecd;
} ptr->total_ecd = ptr->round_cnt * 8192 + ptr->ecd - ptr->offset_ecd;
/* total angle, unit is degree */
ptr->total_angle = ptr->total_ecd / ENCODER_ANGLE_RATIO;
//satori:data[2]和data[3]拼接成转子转速
ptr->speed_rpm = (int16_t)(can_rx_data[2] << 8 | can_rx_data[3]);
//satori:data[4]和data[5]拼接成实际转矩电流
ptr->given_current = (int16_t)(can_rx_data[4] << 8 | can_rx_data[5]);
}

CAN发送过程调用顺序如下:

can_msg_bytes_send->motor_can_send->motor_device_can_output->motor_can1_output_1ms

通过软件定时器设置CAN发送周期为1ms

同样分析一下发送函数,在can_msg_bytes_send函数中完成对帧格式的设置

uint32_t can_msg_bytes_send(CAN_HandleTypeDef *hcan,
uint8_t *data, uint16_t len, uint16_t std_id)
{
uint8_t *send_ptr;
uint16_t send_num;
can_manage_obj_t m_obj;
struct can_std_msg msg; send_ptr = data;
msg.std_id = std_id;
send_num = 0; if (hcan == &hcan1)
{
m_obj = &can1_manage;
}
else if (hcan == &hcan2)
{
m_obj = &can2_manage;
}
else
{
return 0;
} while (send_num < len)
{
if (fifo_is_full(&(m_obj->tx_fifo)))
{
//can is error
m_obj->is_sending = 0;
break;
} if (len - send_num >= 8)
{
msg.dlc = 8;
}
else
{
msg.dlc = len - send_num;
} //memcpy(msg.data, data, msg.dlc);
*((uint32_t *)(msg.data)) = *((uint32_t *)(send_ptr));
*((uint32_t *)(msg.data + 4)) = *((uint32_t *)(send_ptr + 4)); send_ptr += msg.dlc;
send_num += msg.dlc; fifo_put(&(m_obj->tx_fifo), &msg);
} if ((m_obj->is_sending) == 0 && (!(fifo_is_empty(&(m_obj->tx_fifo)))))
{
CAN_TxHeaderTypeDef header;
uint32_t send_mail_box; header.StdId = std_id;
//satori:设置帧格式为标准帧
header.IDE = CAN_ID_STD;
header.RTR = CAN_RTR_DATA; while (HAL_CAN_GetTxMailboxesFreeLevel(m_obj->hcan) && (!(fifo_is_empty(&(m_obj->tx_fifo)))))
{
fifo_get(&(m_obj->tx_fifo), &msg);
header.DLC = msg.dlc;
//satori:调用HAL库函数进行发送
HAL_CAN_AddTxMessage(m_obj->hcan, &header, msg.data, &send_mail_box); m_obj->is_sending = 1;
}
} return send_num;
}

在motor_device_can_output中进行数据,DLC,ID的设置

int32_t motor_device_can_output(enum device_can m_can)
{
struct object *object;
list_t *node = NULL;
struct object_information *information;
motor_device_t motor_dev; memset(motor_msg, 0, sizeof(motor_msg)); var_cpu_sr(); /* enter critical */
enter_critical(); /* try to find device object */
information = object_get_information(Object_Class_Device); for (node = information->object_list.next;
node != &(information->object_list);
node = node->next)
{
object = list_entry(node, struct object, list);
motor_dev = (motor_device_t)object;
if(motor_dev->parent.type == Device_Class_Motor)
{
if (((motor_device_t)object)->can_id < 0x205)
{
//装填ID,装填数据
motor_msg[motor_dev->can_periph][0].id = 0x200;
motor_msg[motor_dev->can_periph][0].data[(motor_dev->can_id - 0x201) * 2] = motor_dev->current >> 8;
motor_msg[motor_dev->can_periph][0].data[(motor_dev->can_id - 0x201) * 2 + 1] = motor_dev->current;
motor_send_flag[motor_dev->can_periph][0] = 1;
}
else
{
motor_msg[motor_dev->can_periph][1].id = 0x1FF;
motor_msg[motor_dev->can_periph][1].data[(motor_dev->can_id - 0x205) * 2] = motor_dev->current >> 8;
motor_msg[motor_dev->can_periph][1].data[(motor_dev->can_id - 0x205) * 2 + 1] = motor_dev->current;
motor_send_flag[motor_dev->can_periph][1] = 1;
}
}
} /* leave critical */
exit_critical(); for (int j = 0; j < 2; j++)
{
if (motor_send_flag[m_can][j] == 1)
{
if (motor_can_send != NULL)
motor_can_send(m_can, motor_msg[m_can][j]);
motor_send_flag[m_can][j] = 0;
}
} /* not found */
return RM_OK;
}

实际上对帧格式,DLC,ID,数据的装填可以全部在一个函数中完成,官方代码写的相对而言比较复杂,多封装了好几层,读者可以去论坛上找一些相对而言简单一些的开源代码看看。

结语

这一讲应该是最硬核,也是我自己写的最累的一讲,CAN是每一个参加RM的电控绕不过去的坎,有很多很多的坑需要自己实践时踩过了才会懂,毕竟实践出真知吗。

RoboMaster电控入门(3)RM系列电机控制的更多相关文章

  1. 2022徐特立科学营&BIT机器人队电控课程讲义

    目录 \(\cdot\)电控简介 \(\cdot\)认识单片机   什么是单片机   时钟-单片机的脉搏 \(\cdot\)外设及应用   GPIO   PWM   定时器   UART \(\cdo ...

  2. JAVA入门基础及流程控制

    JAVA入门基础及流程控制 数据类型 位 存储单位 eg:0001 0011 八位 字节 byte 处理数据单位 一字节等于八位 eg:1b=0011 0001 类变量: static int num ...

  3. WinServer-AD域控入门

    计算机账户和用户账户的区别 域控中不需要事先建立计算机账户,但必须建立登录用户账户. 计算机只要知道域控管理员或者授权管理账户,就可以利用此账户为所有计算机加域. 计算机加域成功之后,都会在AD管理里 ...

  4. 小白转行入门STM32----手机蓝牙控制STM32单片机点亮LED

    @ 目录 引言导读 一.通信基础知识 1.1 通信到底传输的是什么? 1.2 比特率和波特率 习题 1.1 双工和单工 习题 1.2 串行和并行 1.3 异同通信和同步通信 习题 二.连接STM32单 ...

  5. Shell入门教程:流程控制(1)命令的结束状态

    在Bash Shell中,流程控制命令有2大类:“条件”.“循环”.属于“条件”的有:if.case:属于“循环”的有:for.while.until:命令 select 既属于“条件”,也属于“循环 ...

  6. 【重点】Shell入门教程:流程控制(2)条件判断的写法

    第三节:条件判断的写法 if条件判断中,if的语法结构中的“条件判断”可以有多种形式.测试结果是真是假,就看其传回的值是否为0. 条件测试的写法,有以下10种: 1.执行某个命令的结果 这里的命令,可 ...

  7. arduino入门学习实现语音控制LED灯

    需要的准备的硬件arduino+PC+麦克风实现语音命令控制LED灯的亮灭. 首先需要将写好的arduino程序烧录到arduino uno主板中,下面是代码如下: int val;//定义变量val ...

  8. Python3.5入门学习记录-条件控制

    Python的条件控制同C#一样,都是通过一条或多条语句的执行结果(True OR False)来决定执行的代码块. if 语句 Python中if语句的一般形式如下所示: if condition_ ...

  9. Python 入门基础3 --流程控制

    今日目录: 一.流程控制 1. if 2. while 3. for 4. 后期补充内容 一.流程控制--if 1.if判断: # if判断 age = 21 weight = 50 if age & ...

  10. Selenium入门系列2 窗口大小控制

    selenium控制窗口最大化.适合手机的宽度.适合pad的宽度等尝试下实例,网站是否做了响应式布局 #coding=utf-8 # 改变浏览器窗口大小.前进后退 from selenium impo ...

随机推荐

  1. 杂谈 1:论 [l, r] 之间的非平方数

    杂谈 1:论 \([l, r]\) 之间的非平方数 Part 0 例题 先看一道例题: 给定两个数 \(l, r\),求 \(l \sim r\) 之间有多少个数不是平方数.平方数的定义:是一个数的平 ...

  2. ETLCloud支持的数据处理类型包括哪些?

    随着企业不断壮大,信息孤岛的问题变得日益突出,信息集成因此成为企业发展的关键因素.在数据分析过程中,数据集成是必不可少的一环.ETLCloud是一款强大的数据集成和管理平台,专注于数据的提取.转换和加 ...

  3. API智能识别平台,业务逻辑智能识别

    通过API智能识别平台,可以把企业所有业务系统的应用接口能力进行自动识别,并创建为标准化的Restful API,然后再进行集成. 在企业系统集成的过程中,可能需要面对多种类型的集成接口以及专有业务系 ...

  4. Linux C编程之七--二十

    Linux C编程之七(1)系统IO函数 Linux C编程之七(2) 系统IO函数 Linux C编程之八 文件操作相关函数 Linux C编程之九 目录操作相关函数 Linux C编程之十 进程及 ...

  5. ansible-playbook基础

    主机与用户 你可以为 playbook 中的每一个 play,个别地选择操作的目标机器是哪些,以哪个用户身份去完成要执行的步骤(called tasks). hosts 行的内容是一个或多个组或主机的 ...

  6. nowcoder假日团队赛8 D-Artificial Lake

    链接:https://ac.nowcoder.com/acm/contest/1069/D 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 32768K,其他语言65536K 64bi ...

  7. MyEMS助力企业轻松实现ISO 50006标准

    ISO 50006标准的核心:能源绩效的量化管理 ISO 50006强调通过能源绩效指标(EnPI)和能源基线(EnB)量化能源效率,为企业提供可追踪.可改进的能源管理路径.例如: EnPI(如单位产 ...

  8. OCI编程基础篇(二) 创建环境、分配句柄

    访问www.tomcoding.com网站,学习Oracle内部数据结构,详细文档说明,下载Oracle的exp/imp,DUL,logminer,ASM工具的源代码,学习高技术含量的内容. 创建OC ...

  9. jquery DataTable 汉化 以及其他实用配置

    一.将 DataTable 设置成中文 <script> $('#datatable').DataTable({ language: { "sProcessing": ...

  10. git在线练习网站

    https://codechina_dev.gitcode.host/learn-git-branching/?locale=zh_CN