RoboMaster电机驱动

1.硬件

1.1 电机

RM有很多不同型号的电机,不同型号的电机有它不同的用途,但是以用途分类的话主要是分成两种电机:

  1. 用来精准控制位置的电机,在RM中的主要是云台电机。

    RM官网上的云台电机只有一款:GM6020。云台电机的特点就是:运动无噪音、大力矩、高精度。尤其是大力矩高精度,这点对于云台电机很重要。还有一点与其他的电机不太一样的是,云台电机一般是电压驱动。这一点在使用的细节上也有体现

    云台电机是利用压电材料输入电压会产生变形的特性,使其能产生超声波频率的机械振动,再透过摩擦驱动的机构设计,让云台电机如同电磁马达一般,可做旋转运动或直线式移动。

  1. 用来提供动力的电机

    提供动力的电机现在在RM有两种,一种是M3508减速直流电机,一种是M2006 P36直流无刷减速电机。前者扭矩更大,一般被用作轮子的驱动;后者往往用来做播弹系统的动力来源。

我们的主控不能直接去控制电机,而需要中间加一个电调来作为中间人,收到主控的命令后通知电机进行相关的运动,并且通过can总线获取电机的温度、转子位置和转子转速等信息返送给主控。GM6020云台电机内部自带电调

1.2 主控

目前主要使用到的主控是C板和A板。因为C板自带陀螺仪,被用来控制机器人的云台部分。A板用来控制机器人底盘的运动。

2.软件

软件部分主要就是如何通过Cubemx配置,并且在clion中编写Cubemx不能配置的部分(我想说的是用来接收can通讯信息的接收过滤器)、电机的具题控制参数与逻辑。

2.1 Cubemx配置

2.1.1 芯片选型

通过查看主控板的手册,可以知道主控芯片用的是STM32F427IIH6芯片,因此在Cubemx中选择对应的芯片型号,进入配置。

2.1.2 时钟配置
2.1.2.1先开启时钟

在RCC栏中使能外部高速时钟HSE,使用的信号选择晶振信号。

2.1.2.2 配置时钟树

​ f4系列的时钟树相较于f1系列明显长的更大,更复杂,先粗浅的配置它一下,之后有对时钟特殊的要求的时候再进行仔细的配置和了解。之前的f1的外部晶振时钟是8MHZ的因此左侧标红的区域(Input frequency)选择的是8。但是这个开发板的外部晶振是12MHZ的,因此就相应的选择成12。选择HSE通道,然后将系统时钟调到最大(180Mhz),直接在HCLK处填入180,Cube就会自动帮你配置。

2.1.3 debug配置

Pinout & Configuration中的System Core配置SYS的时候将Debug选择成Serial Wire

如果没有勾选Serial Wire 程序只能下载一遍,然后需要将BOOT1、BOOT2引脚电平通过跳线帽均置为高电平并复位开发板,然后再尝试将正确的程序重新下载,下载完成后,需要将BOOT引脚都恢复为低电平。

2.1.4 can外设配置
2.1.4.1 can外设开启

在Connectivity选项栏中选中要开启的CAN外设。点击Activated开启。

2.1.4.2 引脚选择

然后去查看开发板的原理图,找到相应的外设的引脚,看看和他默认的是否相同。有的一些外设对应的引脚和Cube里默认的引脚是不一样的,这一点很重要,要确认一下。

RM开发板手册资料:RoboMaster开发板套件

如果引脚不对应那么根据原理图中的引脚,在Cubemx中找到对应的引脚选择上对应的功能

2.1.4.3 开启接收中断

电调会通过can总线发送电机信息给主控,can总线收到信息后进入接收中断,因此要开启can的接收中断。

2.1.4.4 配置波特率

由于CAN属于异步通讯,没有时钟信号线,连接在同一个总线网络中的各个节点会像串口异步通讯那样,节点间使用约定好的波特率进行通讯。

can外设的通信速率最大是1MHZ,因此我们需要调节CAN外设分频系数、BS1段长度、BS2段长度来把波特率调到1Mhz。

经过前人的经验总结:分别设置成3 、10、3的时候正好可以成1Mhz,但是不能直接按顺序设置,因为如果预分频系数设置成3,BS1段是不允许设置成10的,因此要先将预分频系数调大,然后调BS1和BS2段最后再将分频设置成3.

波特率计算知识点:

2.1.5 生成文件
2.1.5.1 生成成对的.c/.h文件

2.1.5.2 配置使用clion IDE 打开

之后生成代码即可

在生成好文件后选择打开文件夹(Open Folder),而不是直接打开工程(Open Project),否则它会不知道用什么打开

之后返回打开的文件夹的上一层,然后右键用clion打开文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yHjmWgli-1651241400621)(https://www.houenup.com/wp-content/uploads/2022/03/image-2022030117300085-300x86.png)]

2.2 代码部分配置

2.2.1 接收过滤器配置
2.2.1.1 配置代码
CAN_FilterTypeDef can_filter_st;   					//实例化一个can接收过滤器
can_filter_st.FilterBank = 0;						//选择过滤器0
can_filter_st.FilterActivation = ENABLE;			//开启该过滤器
can_filter_st.FilterMode = CAN_FILTERMODE_IDMASK;	//筛选器模式是ID掩码模式
can_filter_st.FilterScale = CAN_FILTERSCALE_32BIT;	//筛选器位宽
can_filter_st.FilterIdHigh = 0x0000;				
can_filter_st.FilterIdLow = 0x0000;
can_filter_st.FilterMaskIdHigh = 0x0000;
can_filter_st.FilterMaskIdLow = 0x0000;
can_filter_st.FilterFIFOAssignment = CAN_RX_FIFO0	//把接收到的报文放入到FIFO0中
HAL_CAN_ConfigFilter(&hcan1, &can_filter_st);		//把过滤器配置配置给can1外设						
2.2.1.2 相关知识点
接收中断:

一旦往FIFO存入一个报文,硬件就会更新FMP[1:0]位,并且如果CAN_IER寄存器的FMPIE位为’1’,那么就会产生一个中断请求。
当FIFO 变 满 时( 即 第3 个 报 文 被 存 入) , CAN_RFR 寄 存 器 的FULL 位 就 被 置’1’ , 并 且 如 果CAN_IER寄存器的FFIE位为’1’,那么就会产生一个满中断请求。
在溢出的情况下, FOVR位被置’1’,并且如果CAN_IER寄存器的FOVIE位为’1’,那么就会产生一个溢出中断请求。

接收FIFO:

​ CAN外设一共有2个接收FIFO,每个FIFO中有3个邮箱,即最多可以缓存6个接收到的报文。当接收到报文时,FIFO的报文计数器会自增,而STM32内部读取FIFO数据后,报文计数器会自减,通过状态寄存器可获知报文计数器的值

STM32的 CAN 有两个 FIFO,分别是 FIFO0和 FIFO1。为了便于区分,下面 FIFO0写作FIFO_0,FIFO1写作 FIFO_1。
每组过滤器组必须关联且只能关联一个 FIFO。复位默认都关联到 FIFO_0。所谓“关联”是指假如收到的报文从某个过滤器通过了,那么该报文会被存到该过滤器相连的 FIFO。从另一方面来说,每个 FIFO 都关联了一串的过滤器组,两个 FIFO 刚好瓜分了所有的过滤器组。每当收到一个报文,CAN 就将这个报文先与 FIFO_0关联的过滤器比较,如果被匹配,就将此报文放入 FIFO_0中。如果不匹配, 再将报文与 FIFO_1关联的过滤器比较, 如果被匹配, 该报文就放入 FIFO_1中。如果还是不匹配,此报文就被丢弃。
每个 FIFO 的所有过滤器都是并联的,只要通过了其中任何一个过滤器,该报文就有效。如果一个报文既符合 FIFO_0的规定,又符合 FIFO_1的规定,显然,根据操作顺序,它只会放到 FIFO_0中。
每个 FIFO 中只有激活了的过滤器才起作用,换句话说,如果一个 FIFO 有20个过滤器,但是只激话了5个,那么比较报文时,只拿这5个过滤器作比较。一般要用到某个过滤器时,在初始化阶段就直接将它激活。需要注意的是,每个 FIFO 必须至少激活一个过滤器,它才有可能收到报文。如果一个过滤器都没有激活,那么是所有报文都报废的。一般的,如果不想用复杂的过滤功能, FIFO 可以只激活一组过滤器组,且将它设置成 32位的屏蔽位模式,两个标准值寄存器(FxR1,FxR2)都设置成0。这样所有报文均能通过。(STM32提供的例程里就是这么做的! )

验收筛选器:

​ 在CAN协议中,消息的标识符与节点地址无关,但与消息内容有关。因此,发送节点将报文广播给所有接收器时,接收节点会根据报文标识符的值来确定软件是否需要该消息,为了简化软件的工作,STM32的CAN外设接收报文前会先使用验收筛选器检查,只接收需要的报文到FIFO中,筛选器工作的时候,可以调整筛选ID的长度及过滤模式。根据过滤方法分为标识符列表模式掩码模式

筛选器模式:
  1. 标志符列表模式:

​ 它把要接收的报文的ID列成一个表,要求报文ID与列表中的某一个标识符完全相同才可以接收,可以理解为白名单

标识符列表模式下,CAN_FxR1 和 CAN_FxR2中都是要匹配的标识符,收到的帧的标识符必须与其中一个吻合才能通过过滤

  1. 掩码模式:

​ 它把可接收报文ID的某几位作为列表,这几位称为掩码,可以把它理解成关键字搜索,只要掩码(关键字相同),就符合要求,报文就会被保存到接收FIFO。

掩码模式下,那个填写掩码的寄存器与想要筛出来的ID一位一位地对应 ,那个想要筛出来的ID寄存器就填写对应位想要的数,然后掩码寄存器对应的位填写是否需要一致(如果想要该位与需要该位筛选出来的ID的数值相同,则需要在该位的掩码上写上1,不需要相同则写上0即可)

理解:标识符列表模式是为了过滤出一个标识符,而屏蔽位模式因为屏蔽了某些位所以可以过滤出一组标识符,对于不需要用筛选器组的应处以禁用状态

一般我们用的都是普通型的,所以在本文中可以说 STM32有14组过滤器组。根据配置,每1组过滤器组可以有1个,2个或4个过滤器。这些过滤器相当于关卡,每当收到一条报文时,CAN 要先将收到的报文从这些过滤器上”过”一下,能通过的报文是有效报文,收进 FIFO,不能通过的是无效报文(不是发给”我”的报文),直接丢弃。通过对两个可配置寄存器值得改变可以选择过滤器的数量。在一组过滤器中,整组的过滤器都使用同一种工作模式

1.在32位的屏蔽位模式下:
有1个过滤器。
FxR2用于指定需要关心哪些位,FxR1用于指定这些位的标准值。
2.在32位的列表模式下:
有两个过滤器。
FxR1指定过滤器0的标准值,收到报文的标识符只有跟 FxR1完全相同时,才算通过。
FxR2指定过滤器1的标准值。
3.在16位的屏蔽位模式下:
有2个过滤器。
FxR1配置过滤器0,其中,[31-16]位指定要关心的位,[15-0]位指定这些位的标准值。
FxR2配置过滤器1,其中,[31-16]位指定要关心的位,[15-0]位指定这些位的标准值。
4.在16位的列表模式下:
有4个过滤器。
FxR1的[15-0]位配置过滤器0,FxR1的[31-16]位配置过滤器1。
FxR2的[15-0]位配置过滤器2,FxR2的[31-16]位配置过滤器3。

2.2.2 开启对应can外设
HAL_CAN_Start(&hcan1);		//开启can1
2.2.3 开启接收中断
 HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
 CAN_IT_RX_FIFO0_MSG_PENDING:当FIFO0中有消息的时候进入中断。

可以看出CAN_IT_RX_FIFO0_MSG_PENDING中的FIFO0和我们配置过滤器时的报文存放位置式相对应的。

can_filter_st.FilterFIFOAssignment = CAN_RX_FIFO0 //把接收到的报文放入到FIFO0中

2.2.4 在接收中断中获取并解析数据
2.2.4.1 重定义can接收中断回调函数

can中断回调函数是弱定义,我们在任意一个地方重写中断回调函数,并在其中进行对数据的操作。

can接收的中断回调函数是

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
2.2.4.2 接收报文结构体
typedef struct
{
  uint32_t StdId;   			//存储了报文的标准标识符 11位

  uint32_t ExtId;				//存储了报文的扩展标识符 29位

  uint32_t IDE;					//存储了IDE扩展标志     

  uint32_t RTR;     			//存储了RTR远程帧标志

  uint32_t DLC;     			//存储了报文数据段的长度,0-8

  uint32_t Timestamp; 			//时间戳	

  uint32_t FilterMatchIndex; 	

} CAN_RxHeaderTypeDef;
2.2.4.3 接收函数
HAL_StatusTypeDef HAL_CAN_GetRxMessage(CAN_HandleTypeDef *hcan, uint32_t RxFifo, CAN_RxHeaderTypeDef *pHeader, uint8_t aData[])
作用:从Rx FIFO收取一个can帧
hcan:CAN句柄指针2
Rx FIFO:从哪个FIFO中取。可填 CAN_RX_FIFO0, CAN_RX_FIFO1
*pHeader:接收报文定义结构体的指针
aData[]:接收数据域的指针

接收函数的作用就是从对应的FIFO中收取一帧can数据,然后把Can数据解剖,然后把对应的部分放在我们事先实例化好的接收结构体中,其中最主要的就是DATA部分。

因为can总线上只要有信号通过过滤器进入fifo那么就会进入接收中断,如果can总线上挂载的数据多了,我们就需要根据他们发过来的报文中的ID来分别他们是谁,并通过判断语句进行对应的操作。

2.2.4.4 解析数据

所谓数据的解析,就是从接收函数填充好的接收结构体中拿出我们需要知道的信息,然后读懂DATA中的数据的意思(包括转子位置、转速等信息)。而填充给结构体的数据是电调发过来的,这一帧数据是怎样存放的,电调自己是有一套报文格式的,我们可以通过这个格式来解析对应的信息。

在这里找到C620电调的使用说明:https://www.robomaster.com/zh-CN/products/components/general/M3508

通过查阅手册,我们了解到如何通过标识符来判断这是哪个电机发来的信息如何在DATA中解析到我们想要的数据

我们可以定义一个结构体,每当我们解析数据的时候,直接把对应的data中的数据解析出来存放到对应的实例化好的结构体中。 解析的过程也可以定义一个函数来处理。

结构体定义:
typedef struct
{
    uint16_t ecd;
    int16_t speed_rpm;
    int16_t given_current;
    uint8_t temperate;
    int16_t last_ecd;
} motor_measure_t;

解析函数:
get_motor_measure(ptr, data)               
{
     (ptr)->last_ecd = (ptr)->ecd;     
     (ptr)->ecd = (uint16_t)((data)[0] << 8 | (data)[1]);            
     (ptr)->speed_rpm = (uint16_t)((data)[2] << 8 | (data)[3]);      
     (ptr)->given_current = (uint16_t)((data)[4] << 8 | (data)[5]);  
     (ptr)->temperate = (data)[6];    
        
 }
2.2.5 接收数据总结与示例
2.2.5.1总结:

接收数据这里写的有点多,这里做一个总结。

首先我们要先启用相应的can总线,这是一定的。

然后在can总线上,因为只有一根总线,而并不是所有的信息我们都要接收(虽然在我们配置的时候配置的是都接受),因此需要配置接收过滤器来过滤掉我们不需要的数据。

如果数据通过了过滤器,进入到了对应的FIFO,那么它就会产生一个中断,因此我们要使能这个接收中断,这样才能在中断产生的时候进入到中断中去,进行下一步的处理。

数据因为进入fifo而呼叫了一个中断,我们进入这个中断的时候就需要用对应的函数来获取这个进入fifo中的数据,然后根据一定的格式来解析它。

在解析的时候为了条例更清晰并且便于我们的操作,我们可以规定一个数据结构体,以及信息解析的一个“方法”。然后在接收中断中我们需要实例化两个结构体。一个结构体是stm32规定的接受数据的结构体,我们获取的帧数据会被解剖填入这个结构体中,其中包括stdid这一项,我们可以通过这一项的数值来判断这帧数据是谁发来的,从而进行对应的操作。 另一个结构体就是我们自己定义的结构体,这个结构体主要是配合我们自己定义的解析数据的方法来使用,将can数据帧中的DATA段数据一项一项地解析出来0。

2.2.5.2中断回调函数示例
#define CAN_CHASSIS_ALL_ID  0x200
#define CAN_3508_M1_ID  0x201
#define CAN_3508_M2_ID 0x202
#define CAN_3508_M3_ID 0x203
#define CAN_3508_M4_ID 0x204

数据结构体定义
typedef struct
{
    uint16_t ecd;
    int16_t speed_rpm;
    int16_t given_current;
    uint8_t temperate;
    int16_t last_ecd;
} motor_measure_t;

数据解析方法定义
#define get_motor_measure(ptr, data)                                    \
    {                                                                   \
        (ptr)->last_ecd = (ptr)->ecd;                                   \
        (ptr)->ecd = (uint16_t)((data)[0] << 8 | (data)[1]);            \
        (ptr)->speed_rpm = (uint16_t)((data)[2] << 8 | (data)[3]);      \
        (ptr)->given_current = (uint16_t)((data)[4] << 8 | (data)[5]);  \
        (ptr)->temperate = (data)[6];                                   \
    }
    
static motor_measure_t motor_chassis[7];  //实例化一个结构体数据,数组中的每一个结构体存放一个对应电机的相关数据,因为整台车上有7个电机因此数组大小为7



void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
    CAN_RxHeaderTypeDef rx_header;
    uint8_t rx_data[8];

    HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_header, rx_data);

    switch (rx_header.StdId)
    {
        case CAN_3508_M1_ID:
        case CAN_3508_M2_ID:
        case CAN_3508_M3_ID:
        case CAN_3508_M4_ID:
        {
            static uint8_t i = 0;
            //get motor id
            i = rx_header.StdId - CAN_3508_M1_ID;
            get_motor_measure(&motor_chassis[i], rx_data);
            break;
        }

        default:
        {
            break;
        }
    }
}
2.2.4 can发送配置

can发送有两点:

  1. 配置好报文结构体
  2. 通过函数HAL_CAN_AddTxMessage发送报文
2.2.4.1 发送数据结构体配置
CAN_TxHeaderTypeDef  chassis_tx_message;
chassis_tx_message.StdId = 0x200;				//指定标准标识符

chassis_tx_message.IDE = CAN_ID_STD;			/*要传输的消息的标识符类型
                       							标准帧:CAN_ID_STD
                        						扩展帧:CAN_ID_EXT*/
                        						
	
chassis_tx_message.RTR = CAN_RTR_DATA;			/*要传输的消息的帧类型
                         						数据帧:CAN_RTR_DATA
                        						远程帧:CAN_RTR_REMOTE */

chassis_tx_message.DLC = 0x08;					/*将要传输的帧的长度。
												取值范围为0 ~ 8	*/
2.2.4.2 发送数据
发送数据报文结构

发送数据的结构不是自己随便的定义的,因为我们can发送的信息时让电调接收的,电调接收信息有一个数据帧的格式,我们需要根据这个帧的格式来发送我们的数据,才能控制我们想要控制的东西。

在这里找到C620电调的使用说明:M3508减速电机套装 (robomaster.com)

通过翻阅C620电调的使用说明我们可以看到我们可以通过控制标识符以及在data段中的对应位置填写数值来控制8个不同的电机的电流大小

发送函数
HAL_CAN_AddTxMessage(CAN_HandleTypeDef *hcan, CAN_TxHeaderTypeDef *pHeader, uint8_t aData[], uint32_t *pTxMailbox)

1. CAN_HandleTypeDef *hcan: CAN的句柄指针,如果是can1就输入&hcan1, can2 就输入 &hcan2
2. CAN_TxHeaderTypeDef *pHeader:待发送的CAN数据帧信息的结构体指针。包含了CAN的ID,格式等重要信息。
3. uint8_t aData[] : 装载了待发送的数据的数组名称
4. uint32_t *pTxMailbox: 用于存储CAN发送所使用的邮箱号

​ 这个发送邮箱一般没用

2.2.4.3 发送数据总结与示例
2.2.4.3.1 总结

相较于接收数据,发送数据更加的直接一些。我们只需要关注,怎么发出去,把什么发出去这两个问题即可。

怎么发出去? 用函数HAL_CAN_AddTxMessage发出去。

发出去什么?发出去我们组织好的数据。

何谓组织好的数据?首先报文结构要组织好,然后报文内容要组织好。

报文的结构如何组织?stm32给我们规定了结构体 CAN_TxHeaderTypeDef,我们按照结构体的结构配置即可,其中最主要的一个内容是StdId,这一项决定了我们要把数据发给谁。

内容是什么?我们发送数据的主要内容就是给电机传达转速,也就是电流大小,而电流大小要放在DATA中的对应位置,这样电调才能够准确的解析并执行。

2.2.4.3.2示例
static CAN_TxHeaderTypeDef  chassis_tx_message;
static uint8_t              chassis_can_send_data[8];
int16_t motor1,motor2,motor3,motor4;
uint32_t send_mail_box; //发送邮箱一般没什么用 随便是个什么值

motor1=4000;
motor2=4000;
motor3=4000;
motor4=4000;
chassis_can_send_data[0] = motor1 >> 8;
chassis_can_send_data[1] = motor1;
chassis_can_send_data[2] = motor2 >> 8;
chassis_can_send_data[3] = motor2;
chassis_can_send_data[4] = motor3 >> 8;
chassis_can_send_data[5] = motor3;
chassis_can_send_data[6] = motor4 >> 8;
chassis_can_send_data[7] = motor4;
/* USER CODE END 2 *
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
  /* USER CODE END WHILE */
    HAL_CAN_AddTxMessage(&hcan1, &chassis_tx_message, chassis_can_send_data, &send_mail_box);
      HAL_Delay(2);
    /* USER CODE BEGIN 3 */
 }
  /* USER CODE END 3 */
  • 30
    点赞
  • 165
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值