Control with CAN

1. VESC CAN Message Structure

English
Korean
English

We analyze the CAN protocol used in the VESC firmware. VESC CAN communication follows a CAN communication protocol called Extended ID (EID) or Extended Format. While Base Format consists of an ID value of 11 bits, Extended Format consists of an ID value of 29 bits. Therefore, VESC sends controller_id and command together as ID values, which are described in more detail below.

  • Comparison between Base Frame format vs. Extended Format

https://en.wikipedia.org/wiki/CAN_bus

Base frame format: with 11 identifier bits / Extended frame format: with 29 identifier bits

1. Periodically Sending Messages

In VESC CAN Protocol, there is can status message which is periodically sent(aka Telemetry). You can check the rate and message mode in VESC-Tool. Currently, there are two VESCs connected on same CAN bus (local controller_id is 101 and the other one is 27) and baudrate is 500Kbps. The periodically sending status msgs rate is 20Hz and the message mode is 1, 2, 3, 4, 5 which is sending 5 different type of messages.

VESC CAN setting shown at VESC-Tool

If you check CAN message using CAN analyzing tool(in my case CANable board and UCCBViewer). I bought CANable board at here. UCCBViewer can be downloaded at https://github.com/UsbCANConverter-UCCbasic/uCCBViewer/releases and you can run the program using below command.

sudo java -jar -Dsun.java2d.uiScale=2.5 uCCBViewer-2.5.jar
UCCBViewer 2.5 using CANable board
Experimental setup

VESC 101 is connected to PC via USB and VESC 101, 27 and CANable board are connected via CAN BUS. (Caution! How to use terminal resistor)

CAN Terminal Resistor usage

I used CANable board to observe CAN messages. The periodic message are as below.

CAN status msg from controller_id 101 (0x65)

Let's decode Id. CAN messages from VESC use an extended ID (EID), containing the command and controller_id. The lower 1 byte(8 bits) represents the VESC controller_id and the remaining higher byte is the index value of CAN_PACKET_ID.

  • 00000965h means,

    • 0x0000009 = 9(CAN_PACKET_STATUS)

    • 0x65 = 101(controller_id)

  • 00000e65h means,

    • 0x000000e = 14(CAN_PACKET_STATUS_2)

    • 0x65 = 101(controller_id)

  • 00000f65h means,

    • 0x000000f = 15(CAN_PACKET_STATUS_3)

    • 0x65 = 101(controller_id)

  • 00001065h means,

    • 0x0000010 = 16(CAN_PACKET_STATUS_4)

    • 0x65 = 101(controller_id)

  • 00001b65h means,

    • 0x000001b = 27(CAN_PACKET_STATUS_5)

    • 0x65 = 101(controller_id)

So, this message are CAN status msgs from controller_id 101. And the Data of CAN status msgs are as below

  • CAN_PACKET_STATUS :

    • rpm(4 byte), current*10.0(2 byte), duty*1000.0(2 byte)

  • CAN_PACKET_STATUS_2

    • amp_hours*10000.0(4 byte), amp_hours_charged*10000.0(4 byte)

  • CAN_PACKET_STATUS_3

    • watt_hours*10000.0(4 byte), watt_hours_charged*10000.0(4 byte)

  • CAN_PACKET_STATUS_4

    • temp_fet*10.0(2 byte), temp_motor*10.0(2 byte), current_in*10.0(2 byte), pid_pos_now*50.0(2 byte)

  • CAN_PACKET_STATUS_5

    • tacho_value(4 byte), v_in*10.0(2 byte), reserved as 0(2 byte)

Let's decode data;

  • CAN_PACKET_STATUS : 00 00 0c f2 00 02 00 64

    • rpm = 3314(0x00000cf4)

    • current = 0.2(0x0002)

    • duty = 0.1(0x0064)

  • CAN_PACKET_STATUS_2 : 00 00 00 00 00 00 00 00

    • amp_hours = 0

    • amp_hours_charged = 0

  • CAN_PACKET_STATUS_3 : 00 00 00 00 00 00 00 00

    • watt_hours = 0

    • watt_hours_charged = 0

  • CAN_PACKET_STATUS_4 : 01 75 01 c1 00 00 2c c6

    • temp_fet = 37.3(0x0175)

    • temp_motor = 44.9(0x01c1)

    • current_in = 0(0x0000)

    • pid_pos_now = 229.24(0x2cc6)

  • CAN_PACKET_STATUS_5 : 00 04 aa d5 00 c2 00 00

    • tacho_value = 305877(0x0004aad5)

    • v_in = 19.4(0x00c2)

    • reserved as 0(2 byte)

VESC-Tool Capture

There is a slight time difference between the data in CAN Status Msg and the data in VESC-Tool, so it is assumed that the values are not exactly the same but roughly correct.

2. Command Messages

Now, let me know the CAN messages for controlling motor.

@comm_can.c, we use below function to send can message basically.

void comm_can_transmit_eid_replace(uint32_t id, const uint8_t *data, uint8_t len, bool replace) {
if (len > 8) {
len = 8;
}
โ€‹
#if CAN_ENABLE
#ifdef HW_HAS_DUAL_MOTORS
if (app_get_configuration()->can_mode == CAN_MODE_VESC) {
if (replace && ((id & 0xFF) == utils_second_motor_id() ||
(id & 0xFF) == app_get_configuration()->controller_id)) {
uint8_t data_tmp[10];
memcpy(data_tmp, data, len);
decode_msg(id, data_tmp, len, true);
return;
}
}
#else
(void)replace;
#endif
โ€‹
CANTxFrame txmsg;
txmsg.IDE = CAN_IDE_EXT;
txmsg.EID = id;
txmsg.RTR = CAN_RTR_DATA;
txmsg.DLC = len;
memcpy(txmsg.data8, data, len);
โ€‹
chMtxLock(&can_mtx);
canTransmit(&HW_CAN_DEV, CAN_ANY_MAILBOX, &txmsg, MS2ST(5));
chMtxUnlock(&can_mtx);
#else
(void)id;
(void)data;
(void)len;
(void)replace;
#endif
}

And, above function called as below. For example, to control the current, we send the VESC CAN ID (controller_id) and index number of CAN_PACKET_ID (CAN_PACKET_SET_CURRENT) are muxed and sent to the id value in comm_can_transmit_eid_replace() function. And we send 4 byte current data (current value * 1000) in the DATA frame.

void comm_can_set_current(uint8_t controller_id, float current) {
int32_t send_index = 0;
uint8_t buffer[4];
buffer_append_int32(buffer, (int32_t)(current * 1000.0), &send_index);
comm_can_transmit_eid_replace(controller_id |
((uint32_t)CAN_PACKET_SET_CURRENT << 8), buffer, send_index, true);
}

The typical CAN message commands available in VESC are tabulated. If the controller_id is 101(0x65), we can use below commands.

Message Name

Extended ID

DLC

(Data Length Code)

DATA

Set Duty Cycle (index=0)

0x00000065

4

duty * 100000.0

Set Current (index=1)

0x00000165

4

current * 1000.0

Set Current Brake (index=2)

0x00000265

4

current * 1000.0

Set RPM (index=3)

0x00000365

4

rpm

Set Position (index=4)

0x00000465

4

pos * 1000000.0

If we send 10% duty cycle command to the controller_id 101, the can message is as below. At DATA, 0.1*100000.0 = 10000 = 0x2710. When using UCCBViewer, you should turn on Ext and Repeat option.

10% Duty Cycle Command

If we send 5A current brake command to the controller_id 101, the can message is as below. At DATA, 5*1000.0 = 5000 = 0x1388

5A Current Brake Command

If we send 1000 ERPM command to the controller_id 101, the can message is as below. At DATA, 1000 = 0x03E8

1000 ERPM (ERPM means Electrical RPM which is ERPM = RPM / number of pole)
ERPM values observed in VESC-Tool

3. CAN Forward

In VESC-Tool, we can access all the functions of CAN connected device using CAN Forward function. Now, my VESC-Tool is connected to VESC (controller_id 101) by USB (ttyACM0). When we select Motor 27, it turns on CAN Forward function automatically, then we can find CAN Forward Messages as below.

VESC-Tool when using CAN Forward
Sending command current 3.0A using CAN Forward

CAN Status function is disabled on both controller. Above figure shows the trace of CAN messages when I command 3.0A current using CAN Forward.

Let's decode this;

  • 0000081bh means,

    • 0x000008 = 8(CAN_PACKET_PROCESS_SHORT_BUFFER)

    • 0x1b = 27(controller_id)

  • 0x65 00 06 00 00 0b b8 means,

    • 0x65 = 101(controller_id)

    • 0x00 ('send' value at comm_can_send_buffer() function)

    • 0x06 = 6(COMM_SET_CURRENT)

    • 0x00000bb8 = 3000(3000/1000.0 = 3A)

The 3.0A current command is given to VESC 101 from VESC-Tool via USB.The commands sent from VESC-Tool are probably as follows:

  • 0x221b0600000bb8 which means

    • 0x22 = 34(COMM_FORWARD_CAN)

    • 0x1b = 27(CAN Forward Target VESC controller_id)

    • 0x06 = 6(COMM_SET_CURRENT)

    • 0x00000bb8 = 3000(current *1000.0 so, it means 3000/1000.0 = 3A)

This messages is processed on 'command.c' using below code. In our case, HW_HAS_DUAL_MOTORS is not defined.

case COMM_FORWARD_CAN: {
send_func_can_fwd = reply_func;
โ€‹
#ifdef HW_HAS_DUAL_MOTORS
if (data[0] == utils_second_motor_id()) {
mc_interface_select_motor_thread(2);
commands_process_packet(data + 1, len - 1, reply_func);
mc_interface_select_motor_thread(1);
} else {
comm_can_send_buffer(data[0], data + 1, len - 1, 0);
}
#else
comm_can_send_buffer(data[0], data + 1, len - 1, 0);
#endif
} break;

That is, the data[0] is CAN Forward Target VESC controller_id(0x1b) and the rest of the data(0x0600000bb8) is passed by CAN bus. The actual code of comm_can_send_buffer() function is as below and the len of original data (0x221b0600000bb8) is 7 (7byte), so the len -1 = 6.

So, at the below code, actual values of input variables are as follows:

  • uint8_t controller_id = 0x1b

  • uint8_t *data = 0x0600000bb8

  • unsigned int len = 6

  • uint8_t send = 0

Because len <=6, send_buffer[] will be packed up as follows;

  • send_buffer[0] = 101(0x65) -> local VESC controller_id

  • send_buffer[1] = 0

  • send_buffer[2] = 0x06

  • send_buffer[3] = 0x00

  • send_buffer[4] = 0x00

  • send_buffer[5] = 0x0b

  • send_buffer[6] = 0xb8

void comm_can_send_buffer(uint8_t controller_id, uint8_t *data, unsigned int len, uint8_t send) {
uint8_t send_buffer[8];
โ€‹
if (len <= 6) {
uint32_t ind = 0;
send_buffer[ind++] = app_get_configuration()->controller_id;
send_buffer[ind++] = send;
memcpy(send_buffer + ind, data, len);
ind += len;
comm_can_transmit_eid_replace(controller_id |
((uint32_t)CAN_PACKET_PROCESS_SHORT_BUFFER << 8), send_buffer, ind, true);
} else {
unsigned int end_a = 0;
for (unsigned int i = 0;i < len;i += 7) {
if (i > 255) {
break;
}
โ€‹
end_a = i + 7;
โ€‹
uint8_t send_len = 7;
send_buffer[0] = i;
โ€‹
if ((i + 7) <= len) {
memcpy(send_buffer + 1, data + i, send_len);
} else {
send_len = len - i;
memcpy(send_buffer + 1, data + i, send_len);
}
โ€‹
comm_can_transmit_eid_replace(controller_id |
((uint32_t)CAN_PACKET_FILL_RX_BUFFER << 8), send_buffer, send_len + 1, true);
}
โ€‹
for (unsigned int i = end_a;i < len;i += 6) {
uint8_t send_len = 6;
send_buffer[0] = i >> 8;
send_buffer[1] = i & 0xFF;
โ€‹
if ((i + 6) <= len) {
memcpy(send_buffer + 2, data + i, send_len);
} else {
send_len = len - i;
memcpy(send_buffer + 2, data + i, send_len);
}
โ€‹
comm_can_transmit_eid_replace(controller_id |
((uint32_t)CAN_PACKET_FILL_RX_BUFFER_LONG << 8), send_buffer, send_len + 2, true);
}
โ€‹
uint32_t ind = 0;
send_buffer[ind++] = app_get_configuration()->controller_id;
send_buffer[ind++] = send;
send_buffer[ind++] = len >> 8;
send_buffer[ind++] = len & 0xFF;
unsigned short crc = crc16(data, len);
send_buffer[ind++] = (uint8_t)(crc >> 8);
send_buffer[ind++] = (uint8_t)(crc & 0xFF);
โ€‹
comm_can_transmit_eid_replace(controller_id |
((uint32_t)CAN_PACKET_PROCESS_RX_BUFFER << 8), send_buffer, ind++, true);
}
}

When the CAN message is actually sending, the extended ID (eid) contains the command(CAN_PACKET_PROCESS_SHORT_BUFFER=8).

As a result, we can see that the CAN message equals the value observed by the UCCBViewer.

On the same principle, we can decode all the messages

Id

Data

Meaning

0x0000081b

65 00 1e

CAN_Forward to 0x1b(27),

0x1e = 30(COMM_ALIVE)

0x0000081b

65 00 06 00 00 00 00

CAN_Forward to 0x1b(27), 0x06 = 6(COMM_SET_CURRENT)

0x00000000 = 0(current *1000.0)

! Note that COMM_ALIVE is used to maintain the current state of control.

For short instructions with a length of less than 6, the CAN_FORWARD command is processed as shown above.

This time, let's talk about a longer command.

I sended CAN messages (Id = 0x0000081, DLC = 3, DATA = 0x 650004). This message means follows:

  • 0000081bh means,

    • 0x000008 = 8(CAN_PACKET_PROCESS_SHORT_BUFFER)

    • 0x1b = 27(controller_id)

  • 0x650004 means,

    • 0x65 = 101(controller_id)

    • 0x00 ('send' value at comm_can_send_buffer() function)

    • 0x04 = 4(COMM_GET_VALUES)

The above message is handled by VESC 27 and the code below is called because the command is 'CAN_PACKET_PROCESS_SHOT_BUFFER' from 'comm_can.c'.

case CAN_PACKET_PROCESS_SHORT_BUFFER:
ind = 0;
rx_buffer_last_id = data8[ind++];
commands_send = data8[ind++];
โ€‹
if (is_replaced) {
if (data8[ind] == COMM_JUMP_TO_BOOTLOADER ||
data8[ind] == COMM_ERASE_NEW_APP ||
data8[ind] == COMM_WRITE_NEW_APP_DATA ||
data8[ind] == COMM_WRITE_NEW_APP_DATA_LZO ||
data8[ind] == COMM_ERASE_BOOTLOADER) {
break;
}
}
โ€‹
switch (commands_send) {
case 0:
commands_process_packet(data8 + ind, len - ind, send_packet_wrapper);
break;
case 1:
commands_send_packet_can_last(data8 + ind, len - ind);
break;
case 2:
commands_process_packet(data8 + ind, len - ind, 0);
break;
default:
break;
}
break;
static void send_packet_wrapper(unsigned char *data, unsigned int len) {
comm_can_send_buffer(rx_buffer_last_id, data, len, 1);
}

In here,

  • rx_buffer_last_id = 0x65

  • commands_send = 0

  • commands_process_packet ( 0x04, 1, function pointer )

Third input of function 'commands_process_packet()' is function pointer of 'send_packet_wrapper()' and eventually 'comm_can_send_buffer()' is called to reply to messages. As a result, we can observe below replying messages about 'COMM_GET_VALUES'.

  • 00000565h

    • 0x000005 = 5(CAN_PACKET_FILL_RX_BUFFER) -> receive data stream

    • 0x65 = 101

  • 00000765h

    • 0x000007 = 7(CAN_PACKET_PROCESS_RX_BUFFER) -> process received data

    • 0x65 = 101

In the data of Id (00000565h), every first byte indicate data length Index, that is

  • 0x00 = 0

  • 0x07 = 7

  • 0x0e = 14

  • 0x15 = 21

  • 0x1c = 28

  • 0x23 = 35

  • 0x2a = 42

  • 0x31 = 49

  • 0x38 = 56

  • 0x3f = 63

  • 0x46 = 70

Except for the first byte, the remaining data corresponds to continuous data in the reply message for COMM_GET_VALUE. Separating the data into bytes per variable shown in 'commands.c'. It can be interpreted as follows ( | indicate bytes separation):

Id

Data

Meaning

00000565h

00 | 04 | 01 42 | 01 6d | 00 00

07 | 00 00 | 00 00 00 00 | 00 0e | 00 00 00 | 00 00 00 00 | 15 | 00 00 | 00 04 94 00 | 00 1c | c2 | 00 00 00 00 | 00 00 23 | 00 00 | 00 00 00 00 | 00

2a | 00 00 00 | 00 06 9c 78 |

31 | 00 06 9c ca | 00 | 00 00

38 | 00 00 | 1b | 00 00 | 00 00 |

3f | 00 00 | 00 00 00 05 | 00

46 | 00 00 00

00 = 0(length index)

04 = 4(COMM_GET_VALUES)

01 42 = temp_fet*10 ->temp_fet = 32.2 01 6d = temp_motor*10 ->temp_motor = 36.5

00 00 00 00 = motor_current*100

00 00 00 00 = input_current*100 00 00 00 00 = id*100 00 00 00 00 = iq*100

00 00 = duty*1000

00 04 94 00 = rpm ->rpm=300032 00 c2 = volt*10 ->volt = 19.4

00 00 00 00 = amp_hours*10000

00 00 00 00 = amp_hours_charged*10000

00 00 00 00 = watt_hours_charged*10000

00 00 00 00 = watt_hours_charged*10000

00 06 9c 78 = tacho ->433272

00 06 9c ca = tacho_abs ->433354

00 = fault_code

00 00 00 00 = pid_pos_now*1000000

1b = 27 (controller_id)

00 00 = temp_mos1*10

00 00 = temp_mos2*10

00 00 = temp_mos3*10

00 00 00 05 = vd*1000 ->vd=0.005

00 00 00 00 = vq*1000

00000765h

1b 01 00 49 13 99

1b = 27 (controller_id)

01 = send

00 49 = 73 = length of data

13 99 = CRC

Above, we introduce how to obtain data by requesting 'COMM_GET_VALUE' in a 'CAN_FORWARD' manner. The method of using "CAN_FORWARD" can be used to transmit virtually all the "commands" of the VESC, but this is somewhat complicated because it requires proper interpretation of the data for the reply message as seen earlier. However, I think it will be able to use it well if necessary.

Korean

VESC ํŽŒ์›จ์–ด์—์„œ ์‚ฌ์šฉํ•˜๋Š” CAN ํ”„๋กœํ† ์ฝœ์— ๋Œ€ํ•ด ๋ถ„์„ํ•œ๋‹ค. VESC์˜ CANํ†ต์‹ ์—์„œ๋Š” Extended ID (EID) ํ˜น์€ Extended Format์ด๋ผ๋Š” ๋ฐฉ์‹์˜ CAN ํ†ต์‹  ํ”„๋กœํ† ์ฝœ์„ ๋”ฐ๋ฅธ๋‹ค. Base Format์€ 11bit์˜ ID ๊ฐ’์œผ๋กœ ์ด๋ฃจ์–ด์ง€๋Š” ๋ฐ˜๋ฉด, Extended Format์€ 29bit์˜ ID๊ฐ’์œผ๋กœ ์ด๋ฃจ์–ด์ง€๋Š” ๊ฒƒ์ด ํฌ๊ฒŒ ๋‹ค๋ฅธ ์ ์ด๋‹ค. ๊ทธ๋ž˜์„œ VESC์—์„œ๋Š” CAN Message์˜ ID๊ฐ’์— controller_id์™€ command๋ฅผ muxํ•˜์—ฌ ์ „์†กํ•˜๊ฒŒ ๋˜๋Š”๋ฐ ์ด๋Š” ์•„๋ž˜์—์„œ ๋” ์ž์„ธํ•˜๊ฒŒ ์„ค๋ช…ํ•œ๋‹ค.

  • Comparison between Base Frame format vs. Extended Format

https://en.wikipedia.org/wiki/CAN_bus

Base frame format: with 11 identifier bits / Extended frame format: with 29 identifier bits

1. Periodically Sending Messages

VESC CAN ํ”„๋กœํ† ์ฝœ์—์„œ๋Š” Status Message๋ผ๊ณ  ํ•˜๋Š” ์ฃผ๊ธฐ์ ์œผ๋กœ ๋ณด๋‚ด์ง€๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๊ณ  ์ด๋Š” Telemetry๋ผ๊ณ ๋„ ๋ถˆ๋ฆฐ๋‹ค. VESC-Tool์—์„œ ์•„๋ž˜ ๊ทธ๋ฆผ๊ณผ ๊ฐ™์ด Status Message์˜ ์ฃผ๊ธฐ์™€ Mode๋ฅผ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ํ˜„์žฌ ์„ค์ •์€ CAN_STATUS 1 2 3 4 5 ๋ชจ๋กœ 20Hz๊ฐ€ ์„ค์ •๋˜์–ด ์žˆ๋‹ค. CAN STATUS 1 2 3 4 5๋Š”๊ฐ€์ง€์˜ CAN Message๋ฅผ ๋ณด๋‚ด๋Š” ๊ฒƒ์œผ๋กœ ์•„๋ž˜์—์„œ ์ข€ ๋” ์ž์„ธํ•˜๊ฒŒ ์„ค๋ช…ํ•œ๋‹ค.

VESC CAN setting shown at VESC-Tool

๋‚˜์˜ ๊ฒฝ์šฐ CAN Message๋ฅผ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•˜์—ฌ CANable board ์™€ UCCBViewer๋ฅผ ์‚ฌ์šฉํ•˜์˜€๋‹ค. CANable ๋ณด๋“œ๋Š” ์—ฌ ์—์„œ ๊ตฌ์ž…ํ•˜์˜€๋‹ค. UCCBViewer ๋Š” ์•„๋ž˜ ์ฃผ์†Œ์—์„œ ๋‹ค์šด๋กœ๋“œ ๊ฐ€๋Šฅํ•˜๋ฉฐ jarํŒŒ์ผ์„ ๋‹ค์šด๋กœ๋“œํ•˜์—ฌ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ช…๋ น์–ด๋กœ ์‹คํ–‰๊ฐ€๋Šฅํ•˜๋‹ค. (์ „์ œ์กฐ๊ฑด JRE 1.6.0 ์ด์ƒ ๋ฒ„์ „์„๋ฅผ ๊น”์•„์•ผํ•œ๋‹ค.)

https://github.com/UsbCANConverter-UCCbasic/uCCBViewer/releasesโ€‹

sudo java -jar -Dsun.java2d.uiScale=2.5 uCCBViewer-2.5.jar
UCCBViewer 2.5 using CANable board
Experimental setup

ํ˜„์žฌ 2๊ฐœ์˜ VESC๋ฅผ CANํ†ต์‹ ์œผ๋กœ ์—ฐ๊ฒฐ์‹œ์ผฐ๋‹ค. VESC์˜ Controller_id๋Š” 101๊ณผ 27๋กœ ์ง€์ •๋˜์–ด์žˆ์œผ๋ฉฐ์ด์ค‘ 101๋ฒˆ VESC์— USB๋ฅผ ํ†ตํ•ด PC์˜ VESC-Tool๊ณผ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ๋‹ค. CANable ๋ณด๋“œ์˜ CANH, CANL์‹ ํ˜ธ๋Š” daisy chain ๋ฐฉ์‹์œผ๋กœ ๋‘๊ฐœ์˜ VESC์™€ ๊ฐ™์€ Bus์ƒ์— ์—ฐ๊ฒฐํ•˜์˜€๊ณ  ์ข…๋‹จ์ €ํ•ญ(Terminal Resistor)๋ฅผ ํ™œ์„ฑํ™”ํ•˜์—ฌ ์›ํ™œํ•œ ํ†ต์‹ ์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ํ•˜์˜€๋‹ค. (Caution! How to use terminal resistor)

CAN Terminal Resistor usage

CANable ๋ณด๋“œ๋ฅผ ์ด์šฉํ•˜์—ฌ CAN Message๋ฅผ ํ™•์ธํ•œ ๊ฒฐ๊ณผ ์ฃผ๊ธฐ์ ์ธ Status ์‹ ํ˜ธ๊ฐ€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฝํ˜”๋‹ค.

CAN status msg from controller_id 101 (0x65)

์œ„ ์‹ ํ˜ธ๋ฅผ ํ•ด์„ํ•ด๋ณด๋‹ค. CAN Message์—์„œ Id๊ฐ’์€ Extended ID(EID) ํฌ๋ฉง์œผ๋กœ ์ „์†ก๋˜๋ฉฐ ์ด๋Š” command์™€ controller_id๊ฐ’์„ ํฌํ•จํ•˜๊ณ  ์žˆ๋‹ค. ํ•˜์œ„ 1byte (8bit)๊ฐ€ VESC controller_id๊ฐ’์„ ์ €์žฅํ•˜๊ณ  ์žˆ์œผ๋ฉฐ ๋‚˜๋จธ์ง€ bit๋Š” CAN_PACKET_ID์˜ ๊ฐ’์„ ์ €์žฅํ•˜๊ณ  ์žˆ๋‹ค. CAN_PACKET_ID์€ VESC ํŽŒ์›จ์–ด์˜ 'datatype.h'์— ์ง€์ •๋˜์–ด ์žˆ๋‹ค.

  • 00000965h means,

    • 0x0000009 = 9(CAN_PACKET_STATUS)

    • 0x65 = 101(controller_id)

  • 00000e65h means,

    • 0x000000e = 14(CAN_PACKET_STATUS_2)

    • 0x65 = 101(controller_id)

  • 00000f65h means,

    • 0x000000f = 15(CAN_PACKET_STATUS_3)

    • 0x65 = 101(controller_id)

  • 00001065h means,

    • 0x0000010 = 16(CAN_PACKET_STATUS_4)

    • 0x65 = 101(controller_id)

  • 00001b65h means,

    • 0x000001b = 27(CAN_PACKET_STATUS_5)

    • 0x65 = 101(controller_id)

์œ„์˜ ํ•ด์„๋œ ๋‚ด์šฉ์€ CAN Status Msgs๊ฐ€ 101๋ฒˆ VESC๋กœ๋ถ€ํ„ฐ ์˜ค๊ณ  ์žˆ์œผ๋ฉฐ 8 Byte ๊ธธ์ด์˜ 5๊ฐ€์ง€ Status ์ •๋ณด๋ฅผ ๋ณด๋‚ด์˜ค๊ณ  ์žˆ๋‹ค. ๊ฐ Status ์ •๋ณด๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์ •์˜๋˜๋ฉฐ ์ด๋Š” VESC ํŽŒ์›จ์–ด์˜ 'comm_can.c' ํŒŒ์ผ์— ๋‚˜์™€์žˆ๋‹ค.

  • CAN_PACKET_STATUS :

    • rpm(4 byte), current*10.0(2 byte), duty*1000.0(2 byte)

  • CAN_PACKET_STATUS_2

    • amp_hours*10000.0(4 byte), amp_hours_charged*10000.0(4 byte)

  • CAN_PACKET_STATUS_3

    • watt_hours*10000.0(4 byte), watt_hours_charged*10000.0(4 byte)

  • CAN_PACKET_STATUS_4

    • temp_fet*10.0(2 byte), temp_motor*10.0(2 byte), current_in*10.0(2 byte), pid_pos_now*50.0(2 byte)

  • CAN_PACKET_STATUS_5

    • tacho_value(4 byte), v_in*10.0(2 byte), reserved as 0(2 byte)

์ด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์œ„์˜ Status Msgs์˜ ์‹ค์ œ๊ฐ’์„ ์ฝ์–ด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • CAN_PACKET_STATUS : 00 00 0c f2 00 02 00 64

    • rpm = 3314(0x00000cf4)

    • current = 0.2(0x0002)

    • duty = 0.1(0x0064)

  • CAN_PACKET_STATUS_2 : 00 00 00 00 00 00 00 00

    • amp_hours = 0

    • amp_hours_charged = 0

  • CAN_PACKET_STATUS_3 : 00 00 00 00 00 00 00 00

    • watt_hours = 0

    • watt_hours_charged = 0

  • CAN_PACKET_STATUS_4 : 01 75 01 c1 00 00 2c c6

    • temp_fet = 37.3(0x0175)

    • temp_motor = 44.9(0x01c1)

    • current_in = 0(0x0000)

    • pid_pos_now = 229.24(0x2cc6)

  • CAN_PACKET_STATUS_5 : 00 04 aa d5 00 c2 00 00

    • tacho_value = 305877(0x0004aad5)

    • v_in = 19.4(0x00c2)

    • reserved as 0(2 byte)

VESC-Tool Capture

20Hz์˜ ์ฃผ๊ธฐ๋กœ ๊ฐ’์ด ์ „์†ก๋˜๊ณ  ์žˆ์–ด, CAN Status Msg์˜ ๊ฐ’๊ณผ VESC-Tool์—์„œ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ™•์ธํ•œ ๊ฐ’์ด ์•ฝ๊ฐ„์˜ ์ฐจ์ด๊ฐ€ ์žˆ๊ธด ํ•˜์ง€๋งŒ, CAN Status Message๋ฅผ ์ •์ƒ์ ์œผ๋กœ ํ•ด์„ํ•œ ๊ฒƒ์œผ๋กœ ํŒ๋‹จํ•  ์ˆ˜ ์žˆ๋‹ค.

2. Command Messages

์ด๋ฒˆ์—๋Š”, ์ œ์–ด๋ฉ”์„ธ์ง€๋ฅผ ๋ณด๋‚ด๊ธฐ ์œ„ํ•œ ๋ถ€๋ถ„์— ๋Œ€ํ•ด ์•Œ์•„๋ณธ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ, CAN Messages๋Š” 'comm_can.c' ํŒŒ์ผ์— ์žˆ๋Š” ์•„๋ž˜ ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ์ „์†ก๋œ๋‹ค.

void comm_can_transmit_eid_replace(uint32_t id, const uint8_t *data, uint8_t len, bool replace) {
if (len > 8) {
len = 8;
}
โ€‹
#if CAN_ENABLE
#ifdef HW_HAS_DUAL_MOTORS
if (app_get_configuration()->can_mode == CAN_MODE_VESC) {
if (replace && ((id & 0xFF) == utils_second_motor_id() ||
(id & 0xFF) == app_get_configuration()->controller_id)) {
uint8_t data_tmp[10];
memcpy(data_tmp, data, len);
decode_msg(id, data_tmp, len, true);
return;
}
}
#else
(void)replace;
#endif
โ€‹
CANTxFrame txmsg;
txmsg.IDE = CAN_IDE_EXT;
txmsg.EID = id;
txmsg.RTR = CAN_RTR_DATA;
txmsg.DLC = len;
memcpy(txmsg.data8, data, len);
โ€‹
chMtxLock(&can_mtx);
canTransmit(&HW_CAN_DEV, CAN_ANY_MAILBOX, &txmsg, MS2ST(5));
chMtxUnlock(&can_mtx);
#else
(void)id;
(void)data;
(void)len;
(void)replace;
#endif
}

๊ทธ๋ฆฌ๊ณ  ์œ„ ํ•จ์ˆ˜๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๋‹ค๋ฅธ ํ•จ์ˆ˜์—์„œ ํ˜ธ์ถœ์ด ๋œ๋‹ค. ์•„๋ž˜ ํ•จ์ˆ˜๋Š” CAN์œผ๋กœ ์ „๋ฅ˜์ œ์–ด๋ฅผ ํ•  ๋•Œ ์ด์šฉํ•˜๋Š” ํ•จ์ˆ˜๋กœ EID๊ฐ’์œผ๋กœ VESC CAN ID (controller_id) ์™€ CAN_PACKET_ID (CAN_PACKET_SET_CURRENT) ๊ฐ’์„ muxํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  4 bytes ํฌ๊ธฐ์˜ ์ „๋ฅ˜๊ฐ’์„ ๋ฐ์ดํ„ฐ ํ”„๋ ˆ์ž„์—๋กœ ์‹ค์–ด๋ณด๋‚ด๋Š”๋ฐ ์ „๋ฅ˜๊ฐ’์— 1000์„ ๊ณฑํ•œ ๊ฐ’์„ ๋ณด๋‚ธ๋‹ค.

void comm_can_set_current(uint8_t controller_id, float current) {
int32_t send_index = 0;
uint8_t buffer[4];
buffer_append_int32(buffer, (int32_t)(current * 1000.0), &send_index);
comm_can_transmit_eid_replace(controller_id |
((uint32_t)CAN_PACKET_SET_CURRENT << 8), buffer, send_index, true);
}

์ด์™€ ๊ฐ™์ด CAN Message๋ฅผ ์ด์šฉํ•ด์„œ VESC์— ์ œ์–ด๋ช…๋ น์„ ๋‚ด๋ฆด ์ˆ˜ ์žˆ๋Š”๋ฐ ์ด๋ฅผ ์•„๋ž˜ ํ…Œ์ด๋ธ”์— ์ •๋ฆฌํ•˜์˜€๋‹ค. ์•„๋ž˜ ํ…Œ์ด๋ธ”์—์„œ๋Š” controller_id๊ฐ€ 101(0x65)์ผ ๋•Œ์˜ ๊ฐ’์„ ์˜ˆ๋กœ ์ •๋ฆฌํ•˜์˜€๋‹ค.

Message Name

Extended ID

DLC

(Data Length Code)

DATA

Set Duty Cycle (index=0)

0x00000065

4

duty * 100000.0

Set Current (index=1)

0x00000165

4

current * 1000.0

Set Current Brake (index=2)

0x00000265

4

current * 1000.0

Set RPM (index=3)

0x00000365

4

rpm

Set Position (index=4)

0x00000465

4

pos * 1000000.0

๋งŒ์•ฝ 10% duty cycle์„ 101๋ฒˆ VESC์— ๊ฐ€ํ•  ๋•Œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์ „์†กํ•œ๋‹ค. Id์— CAN_PACKET_ID ๋Š” 1์„ duty ๊ฐ’ 0.1์— 100000 ๋ฅผ ๊ณฑํ•œ ๊ฐ’์ธ 10000 (0x2710)๋ฅผ ์ „์†กํ•˜๋Š”๋ฐ UCCBViewer๋ฅผ ์ด์šฉํ•  ๊ฒฝ์šฐ์—๋Š” ์•„๋ž˜ ๊ทธ๋ฆผ๊ณผ ๊ฐ™์ด Ext, Repeat ์˜ต์…˜์„ ์ผœ์ค˜์•ผ ํ•œ๋‹ค.

VESC์˜ ๋ชจ๋“  ์ œ์–ด ๋ช…๋ น์–ด๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ํ•œ๋ฒˆ ์ „์†ก๋œ ๋ช…๋ น์–ด๋Š” 1์ดˆ๋™์•ˆ ์œ ์ง€๊ฐ€ ๋˜๊ณ  ์ดํ›„์—๋Š” ์ œ์–ด๊ฐ€ ์ž๋™์œผ๋กœ ํ’€๋ฆฌ๋Š” ๋ฐฉ์‹์œผ๋กœ ์ž‘๋™ํ•œ๋‹ค. ์ด๋ฅผ timeout ๊ธฐ๋Šฅ์ด๋ผ๊ณ  ํ•˜๊ณ  ๊ทธ๋Ÿฌ๋ฏ€๋กœ ์—ฐ์†์ ์ธ ์ œ์–ด๋ฅผ ์œ„ํ•ด์„œ๋Š” 1Hz ์ด์ƒ์˜ ์ œ์–ด์‹ ํ˜ธ๋ฅผ ์ฃผ๊ธฐ์ ์œผ๋กœ ๋ณด๋‚ด์ค˜์•ผํ•œ๋‹ค.

10% Duty Cycle Command

๋งŒ์•ฝ 5A ์ „๋ฅ˜ ๋ธŒ๋ ˆ์ดํฌ ๋ช…๋ น์–ด๋ฅผ 101๋ฒˆ VESC์— ์ „์†กํ•  ๊ฒฝ์šฐ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ํ•œ๋‹ค. ์ด ๊ฒฝ์šฐ CAN_PACKET_ID ๋Š” 2, ๋ฐ์ดํ„ฐ์— ์ „๋ฅ˜๊ฐ’์€ 5*1000=5000(0x1388) ๊ฐ’์„ ๋ณด๋‚ด๋ฉด ๋œ๋‹ค.

5A Current Brake Command

๋งŒ์•ฝ 1000(0x03E8) ERPM์„ ๋ช…๋ นํ•  ๊ฒฝ์šฐ์—๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ํ•œ๋‹ค.

1000 ERPM (ERPM means Electrical RPM which is ERPM = RPM / number of pole)
ERPM values observed in VESC-Tool

3. CAN Forward

VESC-Tool์—์„œ ์ฃผ๋กœ ์‚ฌ์šฉ๋˜๋Š” CAN Forward๋ผ๋Š” ๊ธฐ๋Šฅ์€ ๋ง๊ทธ๋Œ€๋กœ ๋‹ค๋ฅธ ํ†ต์‹ ํฌํŠธ(USB, UART๋“ฑ)์—์„œ ๋“ค์–ด์˜ค๋Š” ๋ฐ์ดํ„ฐ๋ฅผ CANํ†ต์‹  ํฌ์›Œ๋”ฉ ํ•ด์ฃผ๋Š” ๊ธฐ๋Šฅ์ด๋‹ค. ํ˜„์žฌ, ์šฐ๋ฆฌ์˜ ์‹คํ—˜ ์„ธํŒ…์€ VESC 101๋ฒˆ์ด USB๋ฅผ ํ†ตํ•ด VESC-Tool์— ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์œผ๋ฉฐ VESC 27๋ฒˆ์€ CAN์œผ๋กœ 101๋ฒˆ VESC์™€ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ๋‹ค. VESC-Tool์„ ์ฒ˜์Œ ์ž‘๋™์‹œํ‚ค๋ฉด VESC-Tool์— ๋‚˜์˜ค๋Š” ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋Š” 101๋ฒˆ VESC์˜ ๊ฐ’์— ํ•ด๋‹นํ•˜๋Š”๋ฐ ์—ฌ๊ธฐ์„œ Motor 27์„ ํด๋ฆญํ•˜๋ฉด ์ž๋™์œผ๋กœ CAN Forward๊ฐ€ ์ž‘๋™ํ•˜๋ฉด์„œ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๊ฐ€ 27๋ฒˆ VESC์˜ ๊ฐ’์œผ๋กœ ๋ฐ”๋€Œ๊ฒŒ ๋œ๋‹ค. ์ฆ‰, VESC-Tool์€ 101๋ฒˆ์— USB๋กœ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์ง€๋งŒ CAN Forward๋ฅผ ํ†ตํ•ด VESC 27๋ฒˆ์„ ์กฐ์ข…ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

VESC-Tool when using CAN Forward

CAN Status ๊ธฐ๋Šฅ์„ VESC 101๊ณผ 27๋ฒˆ ๋ชจ๋‘ Disable ํ•ด ๋†“๊ณ  3A์˜ ์ „๋ฅ˜์ œ์–ด ๋ช…๋ น์–ด๋ฅผ CAN_Forward ์ƒํƒœ์—์„œ ๋‚ด๋ ค๋ณด์•˜๋‹ค.

Sending command current 3.0A using CAN Forward

์œ„ ์‹ ํ˜ธ๋ฅผ ํ•ด์„ํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

  • 0000081bh means,

    • 0x000008 = 8(CAN_PACKET_PROCESS_SHORT_BUFFER)

    • 0x1b = 27(controller_id)

  • 0x65 00 06 00 00 0b b8 means,

    • 0x65 = 101(controller_id)

    • 0x00 ('send' value at comm_can_send_buffer() function)

    • 0x06 = 6(COMM_SET_CURRENT)

    • 0x00000bb8 = 3000(3000/1000.0 = 3A)

3.0A ์ „๋ฅ˜๋ช…๋ น์€ 101๋ฒˆ VESC์— USB๋ฅผ ํ†ตํ•ด ์ „๋‹ฌ๋˜์—ˆ์„ ๊ฒƒ์ด๋ฉฐ ์ด๋Š” ์•„๋งˆ ์•„๋ž˜์™€ ๊ฐ™์€ ๋‚ด์šฉ์ด์—ˆ์„ ๊ฒƒ์ด๋‹ค.

  • 0x221b0600000bb8 which means

    • 0x22 = 34(COMM_FORWARD_CAN)

    • 0x1b = 27(CAN Forward Target VESC controller_id)

    • 0x06 = 6(COMM_SET_CURRENT)

    • 0x00000bb8 = 3000(current *1000.0 so, it means 3000/1000.0 = 3A)

์ด ๋ฉ”์„ธ์ง€๋Š” 'command.c' ์— ์žˆ๋Š” ์•„๋ž˜ ์ฝ”๋“œ์—์„œ ์ฒ˜๋ฆฌ๋œ๋‹ค.

case COMM_FORWARD_CAN: {
send_func_can_fwd = reply_func;
โ€‹
#ifdef HW_HAS_DUAL_MOTORS
if (data[0] == utils_second_motor_id()) {
mc_interface_select_motor_thread(2);
commands_process_packet(data + 1, len - 1, reply_func);
mc_interface_select_motor_thread(1);
} else {
comm_can_send_buffer(data[0], data + 1, len - 1, 0);
}
#else
comm_can_send_buffer(data[0], data + 1, len - 1, 0);
#endif
} break;

์ฆ‰, CAN Forward Target VESC์˜ controller_id๊ฐ€ 27(0x1b)์ด๋ฉฐ ๋‚˜๋จธ์ง€ ๋ฐ์ดํ„ฐ์— ํ•ด๋‹นํ•˜๋Š” ๊ฐ’์ธ 0x0600000bb8 ๋Š” CAN Bus๋ฅผ ํ†ตํ•ด ๋‹ค์‹œ ์ „์†ก๋œ๋‹ค. ์œ„ ์ฝ”๋“œ์—์„œ comm_can_send_buffer() ํ•จ์ˆ˜์— Input์œผ๋กœ ๋“ค์–ด๊ฐ€๋Š” ์‹ค์ œ ๊ฐ’์€ ์•„๋ž˜์™€ ๊ฐ™์„ ๊ฒƒ์ด๋‹ค.

  • uint8_t controller_id = 0x1b

  • uint8_t *data = 0x0600000bb8

  • unsigned int len = 6

  • uint8_t send = 0

len <=6 ์ด๋ฏ€๋กœ, send_buffer[] ์— ๋“ค์–ด๊ฐ€๋Š” ๋‚ด์šฉ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • send_buffer[0] = 101(0x65) -> local VESC controller_id

  • send_buffer[1] = 0

  • send_buffer[2] = 0x06

  • send_buffer[3] = 0x00

  • send_buffer[4] = 0x00

  • send_buffer[5] = 0x0b

  • send_buffer[6] = 0xb8

void comm_can_send_buffer(uint8_t controller_id, uint8_t *data, unsigned int len, uint8_t send) {
uint8_t send_buffer[8];
โ€‹
if (len <= 6) {
uint32_t ind = 0;
send_buffer[ind++] = app_get_configuration()->controller_id;
send_buffer[ind++] = send;
memcpy(send_buffer + ind, data, len);
ind += len;
comm_can_transmit_eid_replace(controller_id |
((uint32_t)CAN_PACKET_PROCESS_SHORT_BUFFER << 8), send_buffer, ind, true);
} else {
unsigned int end_a = 0;
for (unsigned int i = 0;i < len;i += 7) {
if (i > 255) {
break;
}
โ€‹
end_a = i + 7;
โ€‹
uint8_t send_len = 7;
send_buffer[0] = i;
โ€‹
if ((i + 7) <= len) {
memcpy(send_buffer + 1, data + i, send_len);
} else {
send_len = len - i;
memcpy(send_buffer + 1, data + i, send_len);
}
โ€‹
comm_can_transmit_eid_replace(controller_id |
((uint32_t)CAN_PACKET_FILL_RX_BUFFER << 8), send_buffer, send_len + 1, true);
}
โ€‹
for (unsigned int i = end_a;i < len;i += 6) {
uint8_t send_len = 6;
send_buffer[0] = i >> 8;
send_buffer[1] = i & 0xFF;
โ€‹
if ((i + 6) <= len) {
memcpy(send_buffer + 2, data + i, send_len);
} else {
send_len = len - i;
memcpy(send_buffer + 2, data + i, send_len);
}
โ€‹
comm_can_transmit_eid_replace(controller_id |
((uint32_t)CAN_PACKET_FILL_RX_BUFFER_LONG << 8), send_buffer, send_len + 2, true);
}
โ€‹
uint32_t ind = 0;
send_buffer[ind++] = app_get_configuration()->controller_id;
send_buffer[ind++] = send;
send_buffer[ind++] = len >> 8;
send_buffer[ind++] = len & 0xFF;
unsigned short crc = crc16(data, len);
send_buffer[ind++] = (uint8_t)(crc >> 8);
send_buffer[ind++] = (uint8_t)(crc & 0xFF);
โ€‹
comm_can_transmit_eid_replace(controller_id |
((uint32_t)CAN_PACKET_PROCESS_RX_BUFFER << 8), send_buffer, ind++, true);
}
}

CAN message ๊ฐ€ ์‹ค์ œ ์ „์†ก๋  ๋•Œ, extended ID (eid) ๋Š” command๋กœ CAN_PACKET_PROCESS_SHORT_BUFFER (=8) ๊ฐ’์„ ๊ฐ€์ง€๊ฒŒ ๋˜๋ฉฐ ๊ฒฐ๊ณผ์ ์œผ๋กœ ์•„๋ž˜์™€ ๊ฐ™์ด ์‹ค์ œ CAN ๋ฉ”์„ธ์ง€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ฐ™์€ ์›๋ฆฌ๋กœ UCCBViewer์—์„œ ๊ด€์ธก๋œ ๋ฉ”์„ธ์ง€๋ฅผ ๋ชจ๋‘ ํ•ด์„ํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

Id

Data

Meaning

0x0000081b

65 00 1e

CAN_Forward to 0x1b(27),

0x1e = 30(COMM_ALIVE)

0x0000081b

65 00 06 00 00 00 00

CAN_Forward to 0x1b(27), 0x06 = 6(COMM_SET_CURRENT)

0x00000000 = 0(current *1000.0)

! Note : COMM_ALIVE ๋Š” ํ˜„์žฌ์˜ ์ œ์–ด์ƒํƒœ๋ฅผ 1์ดˆ ๋” ์œ ์ง€์‹œํ‚ค๋Š” ๋ช…๋ น์–ด์ด๋‹ค.

๊ธธ์ด๊ฐ€ 6 bytes์ดํ•˜์˜ ๋ฐ์ดํ„ฐ๋Š” ์•ž์„œ ์ด์•ผ๊ธฐ ํ•œ ๋ฐ๋กœ ์ˆ˜ํ–‰๋˜๋Š”๋ฐ ์ด๋ณด๋‹ค ํฐ ๋ฐ์ดํ„ฐ์˜ ๊ฒฝ์šฐ์—๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์ด๋ฃจ์–ด์ง„๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด CAN messages ๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฐ’์„ ๋ณด๋‚ผ ๊ฒฝ์šฐ (Id = 0x0000081, DLC = 3, DATA = 0x 650004),

  • 0000081bh means,

    • 0x000008 = 8(CAN_PACKET_PROCESS_SHORT_BUFFER)

    • 0x1b = 27(controller_id)

  • 0x650004 means,

    • 0x65 = 101(controller_id)

    • 0x00 ('send' value at comm_can_send_buffer() function)

    • 0x04 = 4(COMM_GET_VALUES)

์œ„ ๋ฉ”์„ธ์ง€๋Š” VESC 27 ๋ฒˆ์— CAN_Forward๋ฅผ ํ†ตํ•ด COMM_GET_VALUES ๋ช…๋ น์–ด๋ฅผ ์ „์†กํ•  ๋•Œ์˜ CAN Message๋ฅผ UCCBViewer๋ฅผ ํ†ตํ•ด ๋ณด๋‚ธ ๊ฒฝ์šฐ์ด๋‹ค. ์ด๋•Œ 'CAN_PACKET_PROCESS_SHOT_BUFFER' ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋ฉด ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด 'comm_can.c'์— ์žˆ๋‹ค.

case CAN_PACKET_PROCESS_SHORT_BUFFER:
ind = 0;
rx_buffer_last_id = data8[ind++];
commands_send = data8[ind++];
โ€‹
if (is_replaced) {
if (data8[ind] == COMM_JUMP_TO_BOOTLOADER ||
data8[ind] == COMM_ERASE_NEW_APP ||
data8[ind] == COMM_WRITE_NEW_APP_DATA ||
data8[ind] == COMM_WRITE_NEW_APP_DATA_LZO ||
data8[ind] == COMM_ERASE_BOOTLOADER) {
break;
}
}
โ€‹
switch (commands_send) {
case 0:
commands_process_packet(data8 + ind, len - ind, send_packet_wrapper);
break;
case 1:
commands_send_packet_can_last(data8 + ind, len - ind);
break;
case 2:
commands_process_packet(data8 + ind, len - ind, 0);
break;
default:
break;
}
break;
static void send_packet_wrapper(unsigned char *data, unsigned int len) {
comm_can_send_buffer(rx_buffer_last_id, data, len, 1);
}

์—ฌ๊ธฐ,

  • rx_buffer_last_id = 0x65

  • commands_send = 0

  • commands_process_packet ( 0x04, 1, function pointer )

'commands_process_packet()' ํ•จ์ˆ˜์˜ 3๋ฒˆ์งธ input์€ ํ•จ์ˆ˜ ํฌ์ธํ„ฐ๋กœ 'send_packet_wrapper()' ๋ฅผ ์‹คํ–‰ํ•˜๊ฒŒ ๋˜๋ฉฐ ์‹ค์งˆ์ ์œผ๋กœ๋Š” 'comm_can_send_buffer()' ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋„๋ก ๋˜์–ด ์žˆ๋‹ค. ๊ฒฐ๊ณผ์ ์œผ๋กœ 'COMM_GET_VALUES' ๋ช…๋ น์–ด์˜ ๋‹ต๋ณ€์— ๋Œ€ํ•œ ๋ฉ”์„ธ์ง€๊ฐ€ CAN Bus์ƒ์— ์•„๋ž˜์™€ ๊ฐ™์ด ์˜ฌ๋ผ์˜ค๊ฒŒ ๋œ๋‹ค.

  • 00000565h

    • 0x000005 = 5(CAN_PACKET_FILL_RX_BUFFER) -> ์ˆ˜์‹  ๋ฐ์ดํ„ฐ ์ŠคํŠธ

    • 0x65 = 101 -> ์•ž์—์„œ 101๋ฒˆ VESC๊ฐ€ COMM_GET_VALUES๋ฅผ ์š”์ฒญํ•˜์˜€์œผ๋ฏ€ 101๋ฒˆ์ด ์ˆ˜์‹ ์ด

  • 00000765h

    • 0x000007 = 7(CAN_PACKET_PROCESS_RX_BUFFER) -> ์ˆ˜์‹ ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋ผ๋Š” ๋ช…๋ น

    • 0x65 = 101

Id (00000565h)์˜ ๋ฐ์ดํ„ฐ์—, ๋งค ์ฒซ๋ฒˆ์งธ Byte ๊ฐ’์€ ๋ฐ์ดํ„ฐ์˜ ๊ธธ์ด๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” indicator์˜ ์—ญํ• ์„ ํ•œ๋‹ค.

  • 0x00 = 0

  • 0x07 = 7

  • 0x0e = 14

  • 0x15 = 21

  • 0x1c = 28

  • 0x23 = 35

  • 0x2a = 42

  • 0x31 = 49

  • 0x38 = 56

  • 0x3f = 63

  • 0x46 = 70

์ฒซ๋ฒˆ์งธ Byte๋ฅผ ์ œ์™ธํ•œ ๋‚˜๋จธ์ง€ 7๊ฐœ์˜ Byte๊ฐ€ ์‹ค์งˆ์ ์ธ ๋ฐ์ดํ„ฐ ์ŠคํŠธ๋ฆผ์— ํ•ด๋‹นํ•˜๊ณ  ์ด๋Š” ์—ฐ์†์ ์ธ ๋ฐ์ดํ„ฐ๋ฅผ ๋‚˜๋ˆ ์„œ ๋ณด๋‚ธ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ์•„๋ž˜์™€ ๊ฐ™์ด ์ „์†กํ•˜๋Š” ์ชฝ๊ณผ ๋ฐ›๋Š” ์ชฝ์—์„œ ์ •ํ•œ ๋ฐ์ดํ„ฐ์˜ ๊ธธ์ด ๊ทœ์•ฝ์— ๋งž๊ฒŒ Byte๋ฅผ ๋‚˜๋ˆ ์„œ ํ•ด์„์„ ํ•ด์•ผํ•œ๋‹ค. ์•„๋ž˜ ํ‘œ์— 'commands.c' ํŒŒ์ผ์— ์ •์˜๋œ ๋ฐ๋กœ Byte๋ฅผ ๋‚˜๋ˆ ์„œ ํ•ด์„์„ ํ•˜์˜€๋‹ค.

Id

Data

Meaning

00000565h

00 | 04 | 01 42 | 01 6d | 00 00

07 | 00 00 | 00 00 00 00 | 00 0e | 00 00 00 | 00 00 00 00 | 15 | 00 00 | 00 04 94 00 | 00 1c | c2 | 00 00 00 00 | 00 00 23 | 00 00 | 00 00 00 00 | 00

2a | 00 00 00 | 00 06 9c 78 |

31 | 00 06 9c ca | 00 | 00 00

38 | 00 00 | 1b | 00 00 | 00 00 |

3f | 00 00 | 00 00 00 05 | 00

46 | 00 00 00

00 = 0(length index)

04 = 4(COMM_GET_VALUES)

01 42 = temp_fet*10 ->temp_fet = 32.2 01 6d = temp_motor*10 ->temp_motor = 36.5

00 00 00 00 = motor_current*100

00 00 00 00 = input_current*100 00 00 00 00 = id*100 00 00 00 00 = iq*100

00 00 = duty*1000

00 04 94 00 = rpm ->rpm=300032 00 c2 = volt*10 ->volt = 19.4

00 00 00 00 = amp_hours*10000

00 00 00 00 = amp_hours_charged*10000

00 00 00 00 = watt_hours_charged*10000

00 00 00 00 = watt_hours_charged*10000

00 06 9c 78 = tacho ->433272

00 06 9c ca = tacho_abs ->433354

00 = fault_code

00 00 00 00 = pid_pos_now*1000000

1b = 27 (controller_id)

00 00 = temp_mos1*10

00 00 = temp_mos2*10

00 00 = temp_mos3*10

00 00 00 05 = vd*1000 ->vd=0.005

00 00 00 00 = vq*1000

00000765h

1b 01 00 49 13 99

1b = 27 (controller_id)

01 = send

00 49 = 73 = length of data

13 99 = CRC

์œ„์—์„œ 'COMM_GET_VALUE' ๋ช…๋ น๋ฅผ 'CAN_FORWARD' ๊ธฐ๋Šฅ์„ ์‘์šฉํ•˜์—ฌ CAN Bus ์ƒ์—์„œ ์ง์ ‘ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์†Œ๊ฐœํ•˜์˜€๋‹ค. "CAN_FORWARD" ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๊ฐ€์ƒ์ ์œผ๋กœ "commands.c" ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ชจ๋“  ๋‹ค๋ฅธ ๊ธฐ๋Šฅ๋“ค์— ๋Œ€ํ•ด์„œ๋„ ์ ์šฉ ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ž์œ ๋„๊ฐ€ ๋†’๋‹ค. ํ•˜์ง€๋งŒ, ์•ž์—์„œ ์„ค๋ช…ํ•œ ๊ฒƒ๊ณผ ๊ฐ™์ด ์ˆ˜์‹ ๋˜๋Š” ๋ฐ์ดํ„ฐ ์ŠคํŠธ๋ฆผ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ถ€๋ถ„์€ ํ”„๋กœํ† ์ฝœ์— ๋งž๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ๋‚˜๋ˆ ์„œ ํ•ด์„ํ•ด์•ผํ•˜๋ฏ€๋กœ ์ด ๋ถ€๋ถ„์„ ํ”„๋กœ๊ทธ๋ž˜๋ฐํ•ด์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๋ฒˆ๊ฑฐ๋กœ์šด์€ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ, ํ•„์š”ํ•  ๊ฒฝ์šฐ "CAN_FORWARD" ๊ธฐ๋Šฅ์€ ์š”๊ธดํ•˜๊ฒŒ ์‚ฌ์šฉ ๊ฐ€ํ•  ๊ฒƒ์œผ๋กœ ์ƒ๊ฐ๋œ๋‹ค.

Reference 1 : electric-skateboard.builders (VESC CAN Message Structure)

โ€‹VESC CAN Message Structure ESK8 Electronics vesccan-bus News Log Inโ€‹

https://esk8content.nyc3.digitaloceanspaces.com/uploads/db2454/original/3X/b/4/b4d3b287266b1668fef1b6d91a67a7849bd0dd56.png

Reference 2

โ€‹https://www.vesc-project.com/sites/default/files/imce/u15301/VESC6_CAN_CommandsTelemetry.pdfโ€‹