Communicating with the VESC using UART
Posted on October 9, 2015
Many people have asked me how to communicate with the VESC using UART, but I did not have a good answer since I haven’t written any tutorial about that and the only thing I could refer to was the BLDC Tool code. Now I have created a project for the STM32F4 discovery board that implements UART communication with the VESC where the full interface is implemented. In this post I will try to explain how the code works and how to port it to other platforms.
많은 사람들은 제게 UART를 이용해서 VESC와 어떻게 통신하는지 물어왔습니다. 그러나 저는 그것에 대해서 어떤 가이드를 작성하지 않았기 때문에 좋은 답장을 갖고 있진 않습니다. 제가 참조할 수 있는 유일한 것은 BLDC Tool의 코드입니다. 저는 완전한 인터페이스가 구현된 VESC와 UART 통신을 구현한 STM32F4 discovery 보드에 대한 프로젝트를 만들었습니다. 이 글에서 저는 어떻게 코드가 동작하고 다른 플랫폼으로 어떻게 포팅하는지를 설명할 것입니다.
Getting started
Start by downloading the code from github:
https://github.com/vedderb/bldc_uart_comm_stm32f4_discovery
If you have a stm32f4 discovery board, you can upload the code to it and test it by following the tutorial in my VESC Post for installing the toolchain. After that, just connect both USB ports of the discovery board (one for the built in programmer and one for the serial terminal) and type make upload from the project directory. Don’t forget to connect rx (PB11), tx (PB10) and gnd from the discovery board to tx, rx and gnd on the VESC. The discovery board will show up as a USB-serial port and you can use a serial terminal such as screen or gtkterm to access a simple command line interface on it. Only a few commands are implemented for the command line interface (type help to list them), but a good exercise is to write more commands in the main file using the provided interface. Doing that with the bldc_interface code should be quite straight forward.
여러분이 stm32f4 discovery 보드를 갖고 계시다면, 여러분은 코드를 업로드 할 수 있고 toolchain을 설치하기 위해서 제 VESC 글에 가이드에 따라 테스트 할 수 있습니다. 그 후에 discovery 보드의 양 USB 포트를 연결하세요(프로그래머에 설치된 하나와 시리얼 터미널에 하나) 그리고 프로젝트 디렉토리로부터 make upload를 치시기 바랍니다. discovery 보드의 rx (PB11), tx (PB10) 그리고 gnd를 VESC의 tx, rx 그리고 gnd를 연결하는 것을 잊지 마십시오. discovery 보드는 USB-serial 포트로 나타나고 여러분은 간단한 command line 인터페이스를 접근하기 위해서 screen 혹은 gtkterm과 같은 시리얼 터미널을 이용할 수 있습니다. command line 인터페이스를 위해서 오직 몇 개의 명령에 대해서 구현되어졌습니다(리스트하기 위해서 help를 치세요), 그러나 좋은 연습은 제공된 인터페이스를 이용해서 메인 파일에 좀더의 명령어를 작성할 수 있습니다. bldc_interface 코드와 그것을 하는 것은 매우 직관적(straight forward)입니다.
Understanding the implementation
The VESC communicates over UART using packets with the following format:
VESC는 다음의 포맷을 갖는 패킷을 사용하여 UART로 통신합니다:
One Start byte (value 2 for short packets and 3 for long packets)
One or two bytes specifying the packet length
The payload of the packet
Two bytes with a CRC checksum on the payload
One stop byte (value 3)
The higher level of the VESC communication code is the same for the USB, UART and CAN ports, so everything that can be done from BLDC Tool can be done from the other ports as well. Therefore I have abstracted out the higher layer of the communication into separate files so that it can be reused for CAN-bus and USB without modifying the rest of the code later.
VESC 통신 코드의 상위 레벨은 USB, UART 그리고 CAN 포트에 대해서도 동일합니다. 그래서 BLDC Tool로부터 할 수 있었던 것은 모두 다른 포트들에서도 할 수 있습니다. 그러므로 저는 통신의 상위 레이어를 분리된 파일들로 구체화 하였습니다. 그 결과 이것은 CAN 버스와 USB에 대해서 나중에 코드의 나머지 수정 없이 다시 사용할 수 있습니다.
The important files in the project, which you can use for your implementation, are the following. They are plain C files and don’t have any hardware dependencies.
여러분이 여러분의 구현을 위해서 사용할 수 있는 프로젝트에서 중요한 파일들은 다음과 같습니다. 일반 C 파일이며 어떤 하드웨어 의존성을 갖지 않습니다.
bldc_interface.c and bldc_interface.h
These files can assemble the payload for all the commands that the VESC supports. They can also interpret packets from the VESC and extract the data from them. Notice that only the payload is handled in these files, not the start, stop, length and checksum bytes since these are different for the CAN interface.
이들 파일은 VESC가 지원하는 모든 명령들의 페이로드(payload)를 조립할 수 있습니다. 이들 파일은 또한 VESC로부터 패킷을 번역하고 그들로부터 데이터를 추출할 수 있습니다. 이들은 CAN 인터페이스에서 다르기 때문에 start, stop, length 그리고 checksum bytes가 아닌 오직 페이로드만이 이들 파일에서 다룰 수 있다는 것을 기억하세요.
datatypes.h
The data structures used by the VESC.
VESC에서 사용된 데이터 구조입니다.
buffer.c and buffer.h
Helper functions for for going between C types and byte arrays. These are used by the bldc_interface files.
C 타입 그리고 byte 배열사이에 Helper 기능들. 이들은 bldc_interface 파일에서 사용됩니다.
crc.c and crc.h
For calculating the CRC checksum
CRC checksum을 계산하기 위함
packet.c and packet.h
For assembling the packets for the VESC with start, stop, length and checksum bytes. These files also have a state machine where one byte received from the VESC can be added at a time to assemble a packet and check the checksum correctness.
start, stop, length 그리고 checksum bytes와 함께 VESC를 위한 패킷의 조립을 위함. 이들 파일은 또한 VESC로부터 수신된 한 바이트가 패킷을 조립하기 위해서 한번에 추가되고 checksum이 올바른지 체크할 수 있도록 하는 state machine을 갖습니다.
bldc_interface_uart.c and bldc_interface_uart.h
Connects packet and bldc_interface to provide a clean UART interface. This is where the user has to make the connection to the UART interface for the platform of choice.
깨끗한 UART 인터페이스 제공하기 위해서 패킷과 bldc_interface을 연결합니다. 이는 사용자가 선택한 플랫폼의 UART 인터페이스로 연결해야 하는 곳입니다.
All of these files rely heavily on function pointers. This might sound complicated at first, but it is actually quite convenient and easy to use. The connection between these files and the UART port is done in the file comm_uart.c, which is the file that you have to implement if you want to port this to a different platform. Also, if you decide to use some other port than UART such as CAN or USB, you only have to re-implement this file and the higher level implementation will work as before.
이들 파일 모두는 함수 포인터에 강하게 의존합니다. 이는 처음에 복잡하게 들려질 수 있습니다, 그러나 이는 실제로 매우 편리하고 사용이 용이합니다. 이들 파일과 UART 포트의 연결은 comm_uart.c 파일에서 이루어집니다. 이 파일은 여러분이 이를 다른 플랫폼으로 포트하기를 원한다면 구현해야만 하는 파일입니다. 또한 여러분이 UART보다 CAN 혹은 USB와 같은 다른 포트를 사용하기로 했다면 여러분은 단지 이 파일을 다시 구현해야 합니다. 그리고 상위 레벨 구현은 전과 같이 동작할 것입니다.
Making the platform-specific UART connection
This should be rather straight forward. The bldc_interface_uart files have three functions that have to be used:
이것은 오히려 직관적일 수 있습니다. bldc_interface_uart 파일은 3가지 함수를 갖습니다:
bldc_interface_uart_init
This is the init function that takes a function pointer to a function that you provide for sending data on the UART. You can use it something like this:
이것은 UART 상에 데이터를 보내기 위해 여러분 제공한 함수로 함수 포인터를 옮겨 놓는 함수의 초기화 함수입니다. 여러분은 이를 다음과 같이 사용할 수 있습니다:
/**
* A function that will send the bytes in *data with length len on the UART
*/
static void send_packet(unsigned char *data, unsigned int len) {
// Your implementation
}
// Your init function
void comm_uart_init(void) {
// Initialize your UART...
// Initialize the bldc interface and provide your send function
bldc_interface_uart_init(send_packet);
}
bldc_interface_uart_process_byte
Call this function every time a byte is received on the UART with the received byte. It will run the state machine in the packet assembler and the callbacks in bldc interface will be called when the packets are ready.
UART 상에 한 바이트가 도착할 때마다 이 함수를 호출합니다. 이는 packet assembler에서 state machine을 동작시키고 bldc 인터페이스에서 callback은 패킷이 준비되었을 때 호출될 것입니다.
bldc_interface_uart_run_timer
Call this function every millisecond to reset the packet state machine after a timeout in case data is lost.
데이터를 잃어버린 경우에 대해서 timeout 후에 packet state machine을 리셋하기 위해서 매 ms 마다 이 함수를 호출합니다.
Notice that bldc_interface_uart_process_byte and bldc_interface_uart_run_timer can be omitted it you only plan to send data to the VESC and not read anything.
bldc_interface_uart_process_byte와 bldc_interface_uart_run_timer는 여러분이 VESC에 데이터를 보내고 어떠한 것도 읽지 않을 계획이라면 생략될 수 있습니다.
In this example project this is implemented in comm_uart.c. This implementation is a bit more complicated than necessary because it uses threads to run the data processing to not block the UART while running the callbacks and to not run the callbacks from an interrupt scope, but a much simpler implementation can also be done if you don’t have an RTOS. You could call bldc_interface_uart_process_byte directly from an interrupt handler every time you receive a byte.
이 예제 프로젝트에서 이것은 comm_uart.c에서 구현되어졌습니다. 이들 구현은 필요보다 다소 복잡합니다. 왜냐하면 이는 callback이 작동하는 동안 UART를 막지 많고 데이터 프로세싱을 위한 그리고 interrupt scope로부터 callback이 작동하는 것을 막기 위해서 스레드를 사용하기 때문입니다. 그러나 여러분이 RTOS를 갖고 있지 않다면 또한 훨씬 간단하게 구현될 수 있습니다. 여러분은 여러분이 한 바이트를 수신한 때마다 interrupt handler로부터 곧바로 bldc_interface_uart_process_byte를 호출할 수 있습니다.
Using bldc_interface
After you are done with the hardware specific UART implementation, you can use bldc_interface in your application. To set the current, duty cycle etc. just call the corresponding function from the setters e.g.
여러분이 하드웨어에 요구되는 UART 구현을 완료한 후에 여러분은 여러분의 애플리케이션에서 bldc_interface를 사용할 수 있습니다. 예를 들어, 전류와 듀티 싸이클 등을 설정하기 위해서 설정기(setters)로부터 관련함수를 호출합니다.
// Run the motor in current control mode with 10A commanded current
bldc_interface_set_current(10.0);
You can do everything that BLDC Tool can do, including changing the configuration (you should read the old configuration before updating it though). Notice that you have to call these functions at regular intervals to not trigger the timeout in the VESC that will release the motor if no new commands have been received for longer than the configured time for safety reasons. This can be done either by calling the corresponding setters at regular intervals or by calling the bldc_interface_send_alive function.
여러분은 BLDC Tool이 할 수 있는 모든 것을 할 수 있습니다, 설정을 변경(하지만 업데이트 전에 이전 설정을 읽어야만 합니다)하는 것을 포함해서 말입니다. 여러분은 VESC에서 timeout이 작동하지 않기 위해서 정기적인 간격으로 이들 함수를 호출해야 함을 기억하세요! VESC는 안전의 이유로 설정된 시간보다 긴 시간 동안 어떠한 새로운 명령을 수신할 수 없다면, 모터를 통제권을 내려놓습니다. 이는 정기적인 간격으로 관련 설정기를 호출하거나 bldc_interface_send_alive 함수를 호출하는 것에 의해서 해결될 수 있습니다.
Reading data
Reading data is done with getter functions and callback function pointers. For example, to get realtime data from the VESC, first set a callback to your function for handling the data using bldc_interface_set_rx_value_func and then request the data with bldc_interface_get_values. It can look something like this:
데이터를 읽는 것은 getter 함수와 callback 함수 포인터들과 함께 이루어집니다. 예를 들어, VESC로부터 실시간 데이터를 얻기 위해서 우선 bldc_interface_set_rx_value_func 함수를 사용해서 데이터를 핸들링 하기 위해서 여러분의 함수로 callback을 설정하세요 그리고 나서 bldc_interface_get_values 함수로 데이터를 요청합니다. 이는 다음과 같이 보여집니다:
// Your callback function for the received data. In this case you simply
// print it using printf.
void bldc_val_received(mc_values *val) {
printf("Input voltage: %.2f V\r\n", val->v_in);
printf("Temp: %.2f degC\r\n", val->temp_pcb);
printf("Current motor: %.2f A\r\n", val->current_motor);
printf("Current in: %.2f A\r\n", val->current_in);
printf("RPM: %.1f RPM\r\n", val->rpm);
printf("Duty cycle: %.1f %%\r\n", val->duty_now * 100.0);
printf("Ah Drawn: %.4f Ah\r\n", val->amp_hours);
printf("Ah Regen: %.4f Ah\r\n", val->amp_hours_charged);
printf("Wh Drawn: %.4f Wh\r\n", val->watt_hours);
printf("Wh Regen: %.4f Wh\r\n", val->watt_hours_charged);
printf("Tacho: %i counts\r\n", val->tachometer);
printf("Tacho ABS: %i counts\r\n", val->tachometer_abs);
printf("Fault Code: %s\r\n", bldc_interface_fault_to_string(val->fault_code));
}
// Somewhere in your init code you can set your callback function(s).
bldc_interface_set_rx_value_func(bldc_val_received);
// Every time you want to read the realtime data you call the corresponding getter.
// This will send the get command to the VESC and return. When the data is received
// the callback will be called from the UART interface.
bldc_interface_get_values();
Have a look at main.c to see some examples of this.
예제를 보기 위해서 main.c를 보세요
That’s it! I hope this is enough to get started with UART communication to the VESC.
VESC로의 UART 통신을 시작하기에 충분하기를 희망합니다.