日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發布時間:2024/3/12 编程问答 67 豆豆
生活随笔 收集整理的這篇文章主要介紹了 通过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這里給定的是電轉速,它等于機械轉速與電機極對數的乘積,即:
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驱动直流无刷电机的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。