生活随笔
收集整理的這篇文章主要介紹了
通过CAN总线控制VESC驱动直流无刷电机
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
最近在驅(qū)動一個直流無刷電機,驅(qū)動這一塊不是我的研究重點,只是拿來用。但系統(tǒng)上用到CAN總線,找來找去找到了VESC這種神級物品,自然是拿一塊來玩玩。
拿到我手上的VESC是國內(nèi)某工作室的改版VESC V6.4(應(yīng)部分網(wǎng)友需求,放出鏈接)。硬件方案是STM32F405+DRV8301+NVMFS5C628,帶有CAN口、PPM口、USB口。
一個完全不知道參數(shù)的星型直流無刷電機,就這么1分鐘就能轉(zhuǎn)動。不得不說,本杰明大神的VESC Tool真是個神器,傻瓜式的一鍵調(diào)參。但是,本人的需求并不是通過VESC Tool讓電機轉(zhuǎn)速來,而是通過CAN口來向VESC下發(fā)指令,間接地控制直流無刷電機按需轉(zhuǎn)動。
一開始把VESC源代碼拿出來看,各種各樣的線程串來串去,沒什么嵌入式操作系統(tǒng)經(jīng)驗的我真看蒙了。這個ChibiOS,本杰明大神怎么選擇了這個系統(tǒng)?!
在網(wǎng)上沒完沒了地搜索,也沒發(fā)現(xiàn)有把VESC的CAN通訊控制給說明白的。有個CSDN的博主到時寫了篇標(biāo)題疑似的,但是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;}}}
}
我們可以看到,當(dāng)VESC接收到擴展幀時,才會進(jìn)行響應(yīng);接收到擴展幀后,將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
;
當(dāng)id為255(即0xFF)或者為控制器ID時(即app_get_configuration()->controller_id)),才對“指令”進(jìn)行響應(yīng),也就是正式進(jìn)入switch (cmd) {}語句里面。
VESC的指令表如下:
指令指令值數(shù)據(jù)數(shù)據(jù)長度數(shù)據(jù)類型單位
| CAN_PACKET_SET_DUTY | 0 | 設(shè)置電機占空比 | 4字節(jié) | 有符號整數(shù) | Thousandths of percent (5000 0 –> 50%) |
| CAN_PACKET_SET_CURREN T | 1 | 設(shè)置電機電流 | 4字節(jié) | 有符號整數(shù) | mA |
| CAN_PACKET_SET_CURREN T_BRAKE | 2 | 設(shè)置制動電流 | 4字節(jié) | 有符號整數(shù) | mA |
| CAN_PACKET_SET_RPM | 3 | 設(shè)置(電)轉(zhuǎn)速 | 4字節(jié) | 有符號整數(shù) | ERPM |
| CAN_PACKET_SET_POS | 4 | 設(shè)置電機轉(zhuǎn)角位置 | | | |
| 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 | | | | |
| 請求狀態(tài)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轉(zhuǎn)動。話不多說,直接上數(shù)據(jù),這里以轉(zhuǎn)速控制為例,其他的請自行舉一反三。
通過CAN口驅(qū)動VESC控制電機轉(zhuǎn)速:
注意,VESC這里給定的是電轉(zhuǎn)速,它等于機械轉(zhuǎn)速與電機極對數(shù)的乘積,即:
ERPM(電轉(zhuǎn)速)=機械轉(zhuǎn)速×極對數(shù).ERPM(電轉(zhuǎn)速) = 機械轉(zhuǎn)速×極對數(shù). ERPM(電轉(zhuǎn)速)=機械轉(zhuǎn)速×極對數(shù).
我們直接忽視VESC TOOL上設(shè)定的CAN ID,直接以0xFF后綴給定。以3對極直流無刷電機為例,當(dāng)我們要控制電機以2000rpm轉(zhuǎn)動時,直接在CAN總線上發(fā)送ID為0x3FF、數(shù)據(jù)長度為4的擴展幀,發(fā)送的數(shù)據(jù)為“00 00 17 70”,也就是0x00001770(即6000 ERPM):
可以看到電機轉(zhuǎn)動了。但是它轉(zhuǎn)一下就停了。這是因為VESC還有個Timeout,需要不斷的發(fā)送指令才能讓它一直轉(zhuǎn)下去(這與汽車上的CAN指令的思想是一致的)。你只要在上位控制器中,定周期地向VESC發(fā)送報文數(shù)據(jù)即可。
這里,定周期向VESC給定電轉(zhuǎn)速指令的dbc可以這樣定義:
其他的請諸位舉一反三(自行摸索)吧,祝百試百通。
總結(jié)
以上是生活随笔為你收集整理的通过CAN总线控制VESC驱动直流无刷电机的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。