通过CAN总线控制VESC驱动直流无刷电机

最近在驱动一个直流无刷电机,驱动这一块不是我的研究重点,只是拿来用。但系统上用到CAN总线,找来找去找到了VESC这种神级物品,自然是拿一块来玩玩。

拿到我手上的VESC是国内某工作室的改版VESC V6.4(应部分网友需求,放出链接)。硬件方案是STM32F405+DRV8301+NVMFS5C628,带有CAN口、PPM口、USB口。
在这里插入图片描述
一个完全不知道参数的星型直流无刷电机,就这么1分钟就能转动。不得不说,本杰明大神的VESC Tool真是个神器,傻瓜式的一键调参。但是,本人的需求并不是通过VESC Tool让电机转速来,而是通过CAN口来向VESC下发指令,间接地控制直流无刷电机按需转动。

一开始把VESC源代码拿出来看,各种各样的线程串来串去,没什么嵌入式操作系统经验的我真看蒙了。这个ChibiOS,本杰明大神怎么选择了这个系统?!
在网上没完没了地搜索,也没发现有把VESC的CAN通讯控制给说明白的。有个CSDN的博主到时写了篇标题疑似的,但是VIP可见,这。。。想爆粗口。

只能自己慢慢摸索吧。
VESC里面的CAN通讯程序里,其实写得比较明白。只是一开始没看懂。我们来看一下店家给我的VESC 3.4固件 里面接收CAN指令的代码:

static THD_FUNCTION(cancom_process_thread, arg) {
	(void)arg;

	chRegSetThreadName("Cancom process");
	process_tp = chThdGetSelfX();

	int32_t ind = 0;
	unsigned int rxbuf_len;
	unsigned int rxbuf_ind;
	uint8_t crc_low;
	uint8_t crc_high;
	bool commands_send;

	for(;;) {
		chEvtWaitAny((eventmask_t) 1);

		while (rx_frame_read != rx_frame_write) {
			CANRxFrame rxmsg = rx_frames[rx_frame_read++];

			if (rxmsg.IDE == CAN_IDE_EXT) {
				uint8_t id = rxmsg.EID & 0xFF;
				CAN_PACKET_ID cmd = rxmsg.EID >> 8;
				can_status_msg *stat_tmp;

				if (id == 255 || id == app_get_configuration()->controller_id) {
					switch (cmd) {
					case CAN_PACKET_SET_DUTY:
						ind = 0;
						mc_interface_set_duty(buffer_get_float32(rxmsg.data8, 1e5f, &ind));
						timeout_reset();
						break;

					case CAN_PACKET_SET_CURRENT:
						ind = 0;
						mc_interface_set_current(buffer_get_float32(rxmsg.data8, 1e3f, &ind));
						timeout_reset();
						break;

					case CAN_PACKET_SET_CURRENT_BRAKE:
						ind = 0;
						mc_interface_set_brake_current(buffer_get_float32(rxmsg.data8, 1e3f, &ind));
						timeout_reset();
						break;

					case CAN_PACKET_SET_RPM:
						ind = 0;
						mc_interface_set_pid_speed(buffer_get_float32(rxmsg.data8, 1e0f, &ind));
						timeout_reset();
						break;

					case CAN_PACKET_SET_POS:
						ind = 0;
						mc_interface_set_pid_pos(buffer_get_float32(rxmsg.data8, 1e6f, &ind));
						timeout_reset();
						break;

					case CAN_PACKET_FILL_RX_BUFFER:
						memcpy(rx_buffer + rxmsg.data8[0], rxmsg.data8 + 1, rxmsg.DLC - 1);
						break;

					case CAN_PACKET_FILL_RX_BUFFER_LONG:
						rxbuf_ind = (unsigned int)rxmsg.data8[0] << 8;
						rxbuf_ind |= rxmsg.data8[1];
						if (rxbuf_ind < RX_BUFFER_SIZE) {
							memcpy(rx_buffer + rxbuf_ind, rxmsg.data8 + 2, rxmsg.DLC - 2);
						}
						break;

					case CAN_PACKET_PROCESS_RX_BUFFER:
						ind = 0;
						rx_buffer_last_id = rxmsg.data8[ind++];
						commands_send = rxmsg.data8[ind++];
						rxbuf_len = (unsigned int)rxmsg.data8[ind++] << 8;
						rxbuf_len |= (unsigned int)rxmsg.data8[ind++];

						if (rxbuf_len > RX_BUFFER_SIZE) {
							break;
						}

						crc_high = rxmsg.data8[ind++];
						crc_low = rxmsg.data8[ind++];

						if (crc16(rx_buffer, rxbuf_len)
								== ((unsigned short) crc_high << 8
										| (unsigned short) crc_low)) {

							if (commands_send) {
								commands_send_packet(rx_buffer, rxbuf_len);
							} else {
								commands_set_send_func(send_packet_wrapper);
								commands_process_packet(rx_buffer, rxbuf_len);
							}
						}
						break;

					case CAN_PACKET_PROCESS_SHORT_BUFFER:
						ind = 0;
						rx_buffer_last_id = rxmsg.data8[ind++];
						commands_send = rxmsg.data8[ind++];

						if (commands_send) {
							commands_send_packet(rxmsg.data8 + ind, rxmsg.DLC - ind);
						} else {
							commands_set_send_func(send_packet_wrapper);
							commands_process_packet(rxmsg.data8 + ind, rxmsg.DLC - ind);
						}
						break;

					case CAN_PACKET_SET_CURRENT_REL:
						ind = 0;
						mc_interface_set_current_rel(buffer_get_float32(rxmsg.data8, 1e5f, &ind));
						timeout_reset();
						break;

					case CAN_PACKET_SET_CURRENT_BRAKE_REL:
						ind = 0;
						mc_interface_set_brake_current_rel(buffer_get_float32(rxmsg.data8, 1e5f, &ind));
						timeout_reset();
						break;

					case CAN_PACKET_SET_CURRENT_HANDBRAKE:
						ind = 0;
						mc_interface_set_handbrake(buffer_get_float32(rxmsg.data8, 1e3f, &ind));
						timeout_reset();
						break;

					case CAN_PACKET_SET_CURRENT_HANDBRAKE_REL:
						ind = 0;
						mc_interface_set_handbrake_rel(buffer_get_float32(rxmsg.data8, 1e5f, &ind));
						timeout_reset();
						break;

					default:
						break;
					}
				}

				switch (cmd) {
				case CAN_PACKET_STATUS:
					for (int i = 0;i < CAN_STATUS_MSGS_TO_STORE;i++) {
						stat_tmp = &stat_msgs[i];
						if (stat_tmp->id == id || stat_tmp->id == -1) {
							ind = 0;
							stat_tmp->id = id;
							stat_tmp->rx_time = chVTGetSystemTime();
							stat_tmp->rpm = (float)buffer_get_int32(rxmsg.data8, &ind);
							stat_tmp->current = (float)buffer_get_int16(rxmsg.data8, &ind) / 10.0f;
							stat_tmp->duty = (float)buffer_get_int16(rxmsg.data8, &ind) / 1000.0f;
							break;
						}
					}
					break;

				default:
					break;
				}
			} else {
				if (sid_callback) {
					sid_callback(rxmsg.SID, rxmsg.data8, rxmsg.DLC);
				}
			}

			if (rx_frame_read == RX_FRAMES_SIZE) {
				rx_frame_read = 0;
			}
		}
	}
}

我们可以看到,当VESC接收到扩展帧时,才会进行响应;接收到扩展帧后,将CAN ID作运算,取低8位识别为id;取其高21位(29-8)为指令。

uint8_t id = rxmsg.EID & 0xFF;//取低8位识别为id
CAN_PACKET_ID cmd = rxmsg.EID >> 8;//取其高21位(29-8)为指令
can_status_msg *stat_tmp;

当id为255(即0xFF)或者为控制器ID时(即app_get_configuration()->controller_id)),才对“指令”进行响应,也就是正式进入switch (cmd) {}语句里面。
VESC的指令表如下:

指令指令值数据数据长度数据类型单位
CAN_PACKET_SET_DUTY0设置电机占空比4字节有符号整数Thousandths of percent (5000 0 –> 50%)
CAN_PACKET_SET_CURREN T1设置电机电流4字节有符号整数mA
CAN_PACKET_SET_CURREN T_BRAKE2设置制动电流4字节有符号整数mA
CAN_PACKET_SET_RPM3设置(电)转速4字节有符号整数ERPM
CAN_PACKET_SET_POS4设置电机转角位置
CAN_PACKET_FILL_RX_B UFFER5
CAN_PACKET_FILL_RX_B UFFER_LONG6
CAN_PACKET_PROCESS_RX _BUFFER7
CAN_PACKET_PROCESS_SH ORT_BUFFER8
请求状态CAN_PACKET_STATUS9Request statusN/A
CAN_PACKET_SET_CURREN T_REL10
CAN_PACKET_SET_CURREN T_BRAKE_REL11

通过上表,再对比CAN接收指令的程序,我们就可以逐一明白我们该怎么通过CAN口去控制VESC转动。话不多说,直接上数据,这里以转速控制为例,其他的请自行举一反三。

通过CAN口驱动VESC控制电机转速:

注意,VESC这里给定的是电转速,它等于机械转速与电机极对数的乘积,即:
E R P M ( 电 转 速 ) = 机 械 转 速 × 极 对 数 . ERPM(电转速) = 机械转速×极对数. ERPM=×.
我们直接忽视VESC TOOL上设定的CAN ID,直接以0xFF后缀给定。以3对极直流无刷电机为例,当我们要控制电机以2000rpm转动时,直接在CAN总线上发送ID为0x3FF、数据长度为4的扩展帧,发送的数据为“00 00 17 70”,也就是0x00001770(即6000 ERPM):

在这里插入图片描述
可以看到电机转动了。但是它转一下就停了。这是因为VESC还有个Timeout,需要不断的发送指令才能让它一直转下去(这与汽车上的CAN指令的思想是一致的)。你只要在上位控制器中,定周期地向VESC发送报文数据即可。
这里,定周期向VESC给定电转速指令的dbc可以这样定义:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

其他的请诸位举一反三(自行摸索)吧,祝百试百通。

  • 21
    点赞
  • 126
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值