生活随笔
收集整理的這篇文章主要介紹了
通过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;
CAN_PACKET_ID cmd
= rxmsg
.EID
>> 8;
can_status_msg
*stat_tmp
;
當id為255(即0xFF)或者為控制器ID時(即app_get_configuration()->controller_id)),才對“指令”進行響應,也就是正式進入switch (cmd) {}語句里面。
VESC的指令表如下:
指令指令值數據數據長度數據類型單位
| CAN_PACKET_SET_DUTY | 0 | 設置電機占空比 | 4字節 | 有符號整數 | Thousandths of percent (5000 0 –> 50%) |
| CAN_PACKET_SET_CURREN T | 1 | 設置電機電流 | 4字節 | 有符號整數 | mA |
| CAN_PACKET_SET_CURREN T_BRAKE | 2 | 設置制動電流 | 4字節 | 有符號整數 | mA |
| CAN_PACKET_SET_RPM | 3 | 設置(電)轉速 | 4字節 | 有符號整數 | ERPM |
| CAN_PACKET_SET_POS | 4 | 設置電機轉角位置 | | | |
| CAN_PACKET_FILL_RX_B UFFER | 5 | | | | |
| CAN_PACKET_FILL_RX_B UFFER_LONG | 6 | | | | |
| CAN_PACKET_PROCESS_RX _BUFFER | 7 | | | | |
| CAN_PACKET_PROCESS_SH ORT_BUFFER | 8 | | | | |
| 請求狀態CAN_PACKET_STATUS | 9 | Request status | N/A | | |
| CAN_PACKET_SET_CURREN T_REL | 10 | | | | |
| CAN_PACKET_SET_CURREN T_BRAKE_REL | 11 | | | | |
通過上表,再對比CAN接收指令的程序,我們就可以逐一明白我們該怎么通過CAN口去控制VESC轉動。話不多說,直接上數據,這里以轉速控制為例,其他的請自行舉一反三。
通過CAN口驅動VESC控制電機轉速:
注意,VESC這里給定的是電轉速,它等于機械轉速與電機極對數的乘積,即:
ERPM(電轉速)=機械轉速×極對數.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可以這樣定義:
其他的請諸位舉一反三(自行摸索)吧,祝百試百通。
總結
以上是生活随笔為你收集整理的通过CAN总线控制VESC驱动直流无刷电机的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。