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 工具代码。现在,我已经为 STM32F4 Discovery 板创建了一个项目,该项目实现了与 VESC 的 UART 通信,其中实现了完整的接口。在这篇文章中,我将尝试解释代码是如何工作的以及如何将其移植到其他平台。
Getting started 开始
Start by downloading the code from github: https://github.com/vedderb/bldc_uart_comm_stm32f4_discovery
首先从 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 发现板,你可以将代码上传到它,并按照我的 VESC Post 中的教程来安装工具链来测试它。之后,只需连接 Discovery Board 的两个 USB 端口(一个用于内置编程器,一个用于串行终端),然后从项目目录键入 make upload。不要忘记将 rx (PB11)、tx (PB10) 和 gnd 从探索板连接到 VESC 上的 tx、rx 和 gnd。发现板将显示为 USB 串行端口,您可以使用串行终端(如 screen 或 gtkterm)访问其上的简单命令行界面。命令行界面只实现了几个命令(键入 help 列出它们),但一个好的练习是使用提供的界面在主文件中编写更多命令。使用 bldc_interface 代码执行此操作应该非常简单。
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)
一个起始字节(短数据包值为 2,长数据包值为 3) - One or two bytes specifying the packet length
指定数据包长度的 1 个或 2 个字节 - The payload of the packet
数据包的有效负载 - Two bytes with a CRC checksum on the payload
两个字节,负载上带有 CRC 校验和 - One stop byte (value 3)
一个停止字节(值 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.
USB、UART 和 CAN 端口的 VESC 通信代码的更高级别相同,因此可以从 BLDC 工具完成的所有操作也可以从其他端口完成。因此,我将通信的较高层抽象为单独的文件,以便它可以重新用于 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
bldc_interface.c 和 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 支持的所有命令的有效负载。它们还可以解释来自 VESC 的数据包并从中提取数据。请注意,这些文件只处理有效负载,而不处理 start、stop、length 和 checksum 字节,因为 CAN 接口的字节不同。
datatypes.h 数据类型.h
The data structures used by the VESC.
buffer.c and buffer.h
buffer.c 和 buffer.h
Helper functions for for going between C types and byte arrays. These are used by the bldc_interface files.
用于在 C 类型和字节数组之间切换的辅助函数。这些文件由 bldc_interface 文件使用。
crc.c and crc.h CRC.c 和 CRC.h
For calculating the CRC checksum
用于计算 CRC 校验和
packet.c and packet.h packet.c 和 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 字节组合 VESC 的数据包。这些文件还有一个状态机,可以一次添加从 VESC 接收的一个字节来组装数据包并检查校验和正确性。
bldc_interface_uart.c and bldc_interface_uart.h
bldc_interface_uart.c 和 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.
连接 packet 和 bldc_interface 以提供干净的 UART 接口。这是用户必须连接到所选平台的 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
建立特定于平台的 UART 连接
This should be rather straight forward. The bldc_interface_uart files have three functions that have to be used:
这应该相当直接。bldc_interface_uart 文件有三个必须使用的功能:
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:
这是 init 函数,它采用一个函数指针,该函数指针指向您提供的用于在 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 上接收到包含接收字节的字节时,调用此函数。它将在数据包汇编器中运行状态机,并在数据包准备就绪时调用 bldc 接口中的回调。
bldc_interface_uart_run_timer
Call this function every millisecond to reset the packet state machine after a timeout in case data is lost.
每毫秒调用一次该函数,超时后重置数据包状态机,以防数据丢失。
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 中实现的。这种实现比必要的要复杂一些,因为它使用线程来运行数据处理,以便在运行回调时不阻塞 UART,也不从中断范围运行回调,但如果您没有 RTOS,也可以完成更简单的实现。每次收到字节时,您可以直接从中断处理程序调用 bldc_interface_uart_process_byte。
Using bldc_interface 使用 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.
// 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.
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:
// 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.
That’s it! I hope this is enough to get started with UART communication to the VESC.
Hi nice work, it would be great UART communication implemented in arduino platform. Arduino is most used platform, so many users would apreciate that
Regards
Hi Vito:
Here you go: https://github.com/RollingGecko/VescUartControl
This is my implementation for the arduino.
Hi Andy, I’m trying your library but it’s missing “Config.h”?
C:\Program Files (x86)\arduino-1.6.1\libraries\VescUartControl/VescUart.h:19:20: fatal error: Config.h: No such file or directory
#include “Config.h”
Thanks,
DougM
Issue fixed. Added Config.h to example.
Otherwise comment out the include statement in VescUart.h.
Thanks Andy, I figured that out the other day and have now successfully implemented your library – Thank you, I can now retire PPM 🙂
Of course, I have a feature creep request – Could you include the ability to pull fault_code (and/or fault message) out as well?
Thanks,
DougM
Hi Benjamin, congratulations for the outstanding work. I want to know if it’s possible to do these things with Vesc, through a serial connection:
– get “current internal state” (or the like);
– get fault condition (or receive a serial message on internal error)
– release motor
Thank you.
Great tutorial. I used it to write an iOS App that can log real time VESC data over bluetooth. I’ve made that open source over here: https://github.com/gpxlBen/VESC_Logger
Is such a communication possible through CAN BUS?
I still don’t get the protocol say I want to get an RPM data off it What do I sent and what is the format for receiving it?
A word of caution – the information, while generally still applicable, is outdated. For example there is no handbrake command, the COM_GET_VALUES return structure is different etc.
Much better to look at the actual bldc code itself to find the packet payload details.
https://github.com/vedderb/bldc/blob/master/commands.c