Control with VESC driver

We describe the VESC control method in ROS using the VESC driver developed by Michael T. Boulet at MIT.

1. Use Custom App

VESC는 기본적으로 Custom App이라는 부분을 만들어 이 부분이 사용자가 추가적으로 개발한 프로그램을 쉽게 탑제할 수 있도록 배려하였다. OpenRobot에서 개발한 Custom App은 VESC 기본 펌웨어의 application 폴더에 openrobot 폴더에 저장해놓았다.
기본 Firmware에 app_custm.c 라는 파일이 custom app의 기본 템플릿이라고 할 수 있는데, 이 곳에 있는 프로그램 예제만을 보고로처음부 직접 custom app을 개발하는 것은 쉬운 일이 아니다. 기본적으로 VESC 펌웨어의 동작원리에 대해 먼저 이해할 필요가 있다.
VESC에는 여러가지 통신 채널들이 있고 대표적으로 UART, USB 통신이 VESC에 명령을 내릴 때 사용된다. 여기서는 VESC의 통신 Protocol에 대해 먼저 설명하겠다.

1. VESC 통신 Protocol

VESC의 통신 프로토콜은 기본적으로 다음과 같은 구조를 하고 있다. 데이터는 Packet 단위로 전송되며 Packet은 전송하고자 하는 Data 의 사이즈에 따라 Short Packet 과 Long Packet의 두 가지 종류가 있다.
  • Short Packet
1st Byte
2nd Byte
3rd ~ (n-3)th Byte
(n-2)th Byte
(n-1)th Byte
(n)th Byte
2
packet length
data
CRC High
CRC Low
3
Short Packet 은 첫번째 Byte 숫자 2로 시작한다. 이후 2번째 Byte에 packet의 길이 값을 넣는다. 3번째 Byte부터가 실제 데이터이며 전송하는 데이터의 크기에 따라 이 부분이 차지하는 크기는 바뀔 수 있다. 이후 마지막에서 3번째와 2번째에 CRC Check sum을 위한 2개의 Byte가 사용되고 마지막 Byte는 Stop Byte를 나타내며 숫자 3을 사용한다.
Short Packet 은 Packet의 크기를 표현하는데 1Byte를 사용하며 1Byte로 표현할 수 있는 숫자의 크기는 0~255까지이므로 Short Packet에 실려서 전송될 수 있는 실제 data의 크기는 255 Byte가 최대라고 보면 된다. 이렇게 Short Packet에 최대로 데이터를 실어서 전송하게 되면 전체 packet의 사이즈는 1(시작) + 1(length) + 255(data) + 2(crc) + 1(stop) = 260 Byte라고 보면 된다.
일반적으로 마이크로프로세서에서 int32, float등의 데이터가 4byte 데이터형이므로 단순 계산으로 생각해보면 255 Byte는 63개의 float 변수를 전송할 수 있는 데이터 사이즈라고 볼 수 있다.
  • Long Packet
1st Byte
2nd Byte
3rd Byte
4th ~ (n-3)th Byte
(n-2)th Byte
(n-1)th Byte
(n)th Byte
3
packet
length High
packet
length Low
data
CRC High
CRC Low
3
Long Packet 은 첫번째 Byte가 숫자 3으로 시작하고 이어서 2~3번째 Byte가 packet length에 해당한다. Packet length에 2Byte를 할당하므로 최대로 전송가능한 데이터의 양은 65535 Byte가 된다. 이는 Short Packet에 비해 257배 더 큰 데이터량에 해당한다. 이후 Byte에는 Short Packet과 동일하게 CRC에 2Byte와 Stop Byte로 숫자 3이 쓰인다.

2. VESC 통신 Interface

VESC의 통신 프로토콜에 대해서는 앞에서 이야기 하였으며 이 부분에서는 VESC 통신의 Interface에 대해 다룬다. VESC는 다양한 통신방식을 지원한다. 대표적으로 UART와 USB 그리고 CAN통신이 있다.
USB는 기본적으로 VESC-Tool을 이용할 때 가장 많이 쓰이는 통신방식이며 ROS(Robot Operating System)을 이용하여 VESC를 실시간 제어할 때도 주로 USB가 사용된다.
UART는 기본적으로 USB-Serial Adapter를 이용하면 USB를 이용하는 것과 동일하게 VESC-Tool이나 ROS등에 사용 할 수 있다. 또한, UART는 블루투스 동글(nRF51822)을 연결하여 무선으로 안드로이드용 VESC-Tool에서 VESC를 모니터링 및 제어할 수 있도록 한다.
CAN 통신은 다수의 VESC를 서로 연결하여 통신할 수 있게 하며 통신데이터를 Forwarding하여 하나의 VESC로 전달된 데이터를 다른 VESC로 전달하는데 사용된다.
이밖에도 I2C 통신을 이용하여 무선 제어기인 Nunchuk(과거 닌텐도 Wii 게임 컨트롤러로 사용됨)을 이용해 VESC를 제어할 수 있다. I2C 통신은 IMU의 데이터를 읽어들이기 위해서도 사용되며 현재 VESCular에는 Bosch 사의 BMI160 IMU가 On-board에 탑재되어 Custom App을 사용시 기본적으로 IMU 데이터를 사용할 수 있다.
이밖에도 SPI 통신은 Magnetic Encoder인 AS5047p와 연결시 사용하는데 특이한 점은 기본적으로는 Hardware SPI가 아닌 Software SPI를 사용한다는 것(Firmware에서 Hardware SPI를 사용할 수 있도록 바꿀수는 있다)이고 DRV8301(Mosfet Gate Driver)과의 통신도 Software SPI 통신을 이용한다.
Hardware SPI는 PA4~PA7에 할당된 SPI1 모듈을 사용할 수 있는데 이 핀들은 UART 및 I2C 통신으로도 Alternate 해서 사용할 수 있기 때문에 PA4~PA7에 할당할 수 있는 통신 기능은 UART, I2C, SPI 세가지 통신중 동시에 하나만 사용 가능하다.
OpenRobot App에서는 Hardware SPI 통신을 이용하여 Arduino와 고속 통신이 가능하도록 하여 VESC에 대한 자세한 사항을 몰라도 Arduino에서 SPI 통신을 통해 제어가 가능하도록 프로그램하였다. Hardware SPI 통신 기능은 OpenRobot App의 독자적인 기능이다.
아래 표에 VESC의 통신 Interface를 모두 정리하면 다음과 같다.
통신채널
사용
통신속
USB
VESC-Tool, ROS
기본 115200bps, 최대 3000000bps
UART
VESC-Tool, ROS, Bluetooth
기본 115200bps, 최대 921600bps
CAN
VESC to VESC
기본 500Kbps, 최대 1Mbps
I2C (Software)
Nunchuk, IMU
기본 1Mbit/sec, 최대 2Mbit/sec
SPI (Software)
DRV8301, AS5047p
Bit-banging (didn't measure yet)
SPI (Hardware)
Arduino (OpenRobot App Only)
기본 4Mhz, 최대 8Mhz(Clock Speed)
VESC 기본 펌웨어에서는 I2C 및 SPI 통신을 Software 적인 방식으로 구현하였는데, 이는 STM32F4x에서 제공하는 특정 핀마다 할당된 하드웨어적인 통신 Pin을 사용하여 구현하는 방식이 아닌, 일반적인 digital IO를 이용하여 통신을 구현한 것으로 아두이노 우의 software serial 과 유사한 방식으로 보면 된다.
이런 방식으로 구현한 데는 아무래도 모터 제어에 좀더 하드웨어 리소스를 더 투입하고자 하는 의도가 보인다. Hardware 통신 채널을 이용하게 되면 아무래도 통신시 Hardware 인터럽트가 발생할 수 있고 이 상황에서 모터제어를 위한 Interrupt 등과 쫑날수 있어서 펌웨어가 멈춰버리거나 리셋되는 문제등이 발생할 위험소지가 있다. 물론 프로그램을 잘 만들면 이런 상황을 피할 수는 있겠지만, software 적으로도 충분히 원활한 통신 구현이 가능하다면 굳이 Hardware 방식을 갈 필요는 없다.
굳이 Hardware 통신 방식을 써야할 때는 고속의 통신이 요구될 때이며, VESCuino의 경우에는 Hardware SPI통신을 이용해 아두이노 Due와 최대 8Mhz SPI Clock 스피드로 통신이 가능하여 고속의 실시간 제어(Low Latency)에 유리하다.
3. VESC의 통신 Software Architecture
앞에서 언급한 다양한 통신채널에 비해 데이터를 처리하는 software 루틴은 매우 간단하게 구성되어 있다. 이는 모든 통신 채널에서 들어오는 데이터를 파싱하여 실제 data만 추출한 후 이를 해석하는 구문이 한 곳에서 동일하게 작동하도록 설계해놓았기 때문이다.
VESC 펌웨어에서는 C언어로 이를 구현하기 위해 함수 포인터를 이용하여 통신 채널마다 데이터 파싱을 위한 함수를 상황에 따라 바꿔줄 수 있도록 구성이 되어있다. 함수 포인터라는 것이 조금 생소할 수 있는 부분인데 이는 다음과 같이 설명할 수 있다.
  • UART 통신
VESC-Tool을 실행해보면 크게 두개의 파트로 나뉘어 있는 것을 알수 있는데 바로 Motor setting 부분과 App setting 부분 그리고 Data Analysis 이다. Motor setting은 모터의 Identification 및 제어Gain등의 세팅과 관련된 부분이고 App setting은 어떠한 App을 실행시킬건지 그리고 특정 App 이 실행중일때 이에 관한 세팅에 대한 부분이다. 세팅과 관련된 두 부분은 각기 분리된 저장영역을 사용하여 독립적으로 변경하고 저장시키도록 되어있다. 현재 VESC-Tool에서 선택가능한 App은 다음과 같다(VESC FW ver5.02 기준).
No App
PPM
ADC
UART
PPM and UART
ADC and UART
Nunchuk (I2C, Nyko Kama)
NRF
Custom User App
Balance
PAS
ADC and PAS
앞에서 언급한 바와 같이 VESC에서 통신을 위해 사용하는 커넥터는 여러 통신이 통합되어 있어 한번에 여러가지 역할을 수행할 수는 없다. 그래서 위와 같이 App을 선택하여 통신핀이 전원인가후 초기화될 때 원하는 통신핀의 역할을 수행할 수 있도록 세팅된다.
UART통신을 위해서는 Tx, Rx (Tx는 송신, Rx는 수신)를 위한 Pin이 요구되며 이를 반대편 통신측과 아래와 같이 연결하면 UART통신이 가능하다. UART 통신은 반드시 GND를 동일하게 해줘야하므로 실질적으로는 3개의 선이 서로 연결되어야 한다.
VESC 쪽 TX - 반대쪽 RX
VESC 쪽 RX - 반대쪽 TX
VESC 쪽 GND - 반대쪽 GND
UART 통에 관해서는 기본적으로 아래의 프로그램을 살펴보아야 한다.
app_uartcomm.c
packet.c
commands.c
위 프로그램의 구조를 잘 이해하면 전체적인 VESC의 통신 구조를 이해할 수 있게되는데, 우선 app_uartcomm.c에서는 app_uartcomm_start()함수에서 UART 통신을 위한 Pin 설정과 UART 통신을 처리하는 Thread를 실행시킨다. 그리고 그 이전에 packet_init 함수에서 UART 통신의 데이터 Packet을 처리할 함수들을 선택해주 함수포인터를 지정한다.
void app_uartcomm_start(void) {
// UART 통신 Packet을 처리해주는 함수를 지정하기 위한 함수포인터
packet_init(send_packet, process_packet, PACKET_HANDLER);
// UART 통신처리 Thread
if (!thread_is_running) {
chThdCreateStatic(packet_process_thread_wa, sizeof(packet_process_thread_wa),
NORMALPRIO, packet_process_thread, NULL);
thread_is_running = true;
}
// UART Pin 설정부
sdStart(&HW_UART_DEV, &uart_cfg);
palSetPadMode(HW_UART_TX_PORT, HW_UART_TX_PIN, PAL_MODE_ALTERNATE(HW_UART_GPIO_AF) |
PAL_STM32_OSPEED_HIGHEST |
PAL_STM32_PUDR_PULLUP);
palSetPadMode(HW_UART_RX_PORT, HW_UART_RX_PIN, PAL_MODE_ALTERNATE(HW_UART_GPIO_AF) |
PAL_STM32_OSPEED_HIGHEST |
PAL_STM32_PUDR_PULLUP);
uart_is_running = true;
}
바로 이 packet_init 함수가 매우 중요한다. packet_init 함수의 원형을 보면 다음과 같다. packet.c에 가보면 아래와 같이 되어있다. packet_init 함수의 첫번째 input에는 send_func(송를 위한 함수)의 함수포인터를 지정할 수 있고 두번째 input에는 process_func(수신을 위한 함수)의 함수포인터를 지정할 수 있다.
void packet_init(void (*s_func)(unsigned char *data, unsigned int len),
void (*p_func)(unsigned char *data, unsigned int len), int handler_num) {
memset(&m_handler_states[handler_num], 0, sizeof(PACKET_STATE_t));
m_handler_states[handler_num].send_func = s_func;
m_handler_states[handler_num].process_func = p_func;
그럼 앞에서 UART 통신의 경우를 살펴보면 packet_init 함수의 첫번째 input으로 지정한 send_packet 함수의 내용은 다음과 같다.
static void send_packet(unsigned char *data, unsigned int len) {
if (uart_is_running) {
sdWrite(&HW_UART_DEV, data, len);
}
}
send_packet 함수는 app_uartcomm.c 파일안에 있으며 단순이 data와 data의 길이(len)를 알면 이를 UART 통신으로 전송하는 임무를 수행한다.
packet_init 함수의 두번째 input인 process_packet 함수는 다음과 같다. process_packet 함수도 app_uartcomm.c 파일안에 있으며 data와 data의 길이(len)을 commands_process_packet 함수에 전달하도록 되어있으며 commands_process_packet 함수는 commands.c 파일에 작성되어 있다.
static void process_packet(unsigned char *data, unsigned int len) {
commands_process_packet(data, len, app_uartcomm_send_packet);
}
여기서 commands_process_packet 함수의 세번째 input으로 지정된 app_uartcomm_send_packet 함수는 일명 reply 함수로 UART통신을 통해 연결된 장치에서 전달한 명령에 대한 답변을 할 때 사용되는 함수이다. 앞에서 send_packet 함수와 다른 점은 send_packet 함수는 VESC 측에서 먼저 데이터 Packet을 전송할 때 사용되는 함수이고 app_uartcomm_send_packet 함수는 UART통신으로 연결된 반대쪽 장치에서 먼저 데이터 Packet이 전송되어 이에 대한 답변을 위한 용도로 사용된다고 생각하면 된다.
그럼 실질적으로 Data의 흐름이 어떻게 이뤄지는지 좀 더 자세하게 알아보자