ROS 스터디 #7: 임베디드 시스템
2021년 01월 20일 18:00
ZOOM 화상회의
1. 개념
임베디드 시스템(embedded system, 내장형 시스템)
기계나 기타 제어가 필요한 시스템에 대해 제어를 위한 특저 기능을 수행하는 컴퓨터 시스템으로 장치 내에 존재하는 전자 시스템이다. 즉 임베디드 시스템은 전체 장치의 일부분으로 구성되며 제어가 필요한 시스템을 위한 두뇌 역할을 하는 특정 목적의 컴퓨터 시스템이라고 정의할 수 있다.
임베디드 보드 종류
8/16-bit MCU | (small) 32-bit MCU | (big) 32-bit MCU | ARM A-class | x86 | |
---|---|---|---|---|---|
보드 예 | Atem AVR | ARM Cortex-M0 | ARM Cortex-M7 | Samsung Exynos | Intel Core i5 |
시스템 예 | Arduino, Leonardo | Arduino M0 pro | SAM V71 | ODROID | Intel NUC |
MIPS1) | 10's | 100's | 1000's | 10000's | |
RAM | 1~32KB | 32KB | 384KB | a few GB(off-chip) | 2-16 GB (SODIMM) |
최대 출력 | 10'of mW | 100'of mW | 100'of mW | 1000'of mW | 10000'of mW |
주변 기기 | UART, USB FS 등 | USB FS | Ethernet,USB HS | Gigabit Ethernet | USB SS, PCle |
리눅스 같은 운영체제는 실시간성을 보장하지 못하기 때문에 엑추에이터나 센서등을 제어하기 위해서는 실시간 제어에 적합한 마이크로컨트롤러를 사용한다. 이런 임베디드 시스템에는 ROS를 설치할 수 없기 때문에 시스템과 (ROS가 설치된) PC 사이의 통신이 필요하다.2)
2. Open CR
2.1 개념
ROS를 지원하는 임베디드 보드이며 터틀봇3에서 메인 제어기로 사용된다. 회로, BOM, 거버 데이터 등의 H/W 정보 및 OpenCR의 모든 S/W가 오픈소스로 공개되어 있으며 성능이 좋고3) 인터페이스가 다양하다. 또한 IMU 센서를 사용하며 전원 출력이 다양4)하다.
2.2 개발환경 구축
3. rosserial
3.1. 개념
PC와 제어기 간의 메시지5) 통신을 위해 중계자 역할을 수행하는 ROS 패키지
- 예) 제어기 > 시리얼(rosserial 프로토콜) > PC(ROS 메시지로 재전송)
- 예) 제어기 < 시리얼(rosserial 프로토콜) < PC(ROS 메시지를 시리얼로 변경)
일반적으로 마이크로컨트롤러는 ROS에서 기본 통신으로 사용하는 TCP/IP 통신보다 시리얼 통신을 많이 사용하므로 rosserial과 같은 중재자 역할이 필요하다.
3.2. package 구성
rosserial은 rosserial server와 rosserial client로 구성되어있다. rosserial server는 구현된 프로그래밍 언어에 따라 3가지 노드가 있다.
- rosserial_python: Python 언어 기반의 rosserial server로 일반적으로 많이 사용된다.
- rosserial_server: C++ 언어 기반의 rosserial server로 동작 성능이 상대적으로 향상되었으나 rosserial_python에 비해 일부 기능의 제약이 있다.
- rosserial_java: Java 언어 기반의 rosserial server로 안드로이드 SDK를 사용할 때 함께 이용한다.
rosserial client는 client 역할을 하는 라이브러리에 마이크로컨트롤러에서 사용되는 플랫폼에 포팅되었다.
- rosserial_arduino: Arduino와 Leonardo 보드를 지원하지만 소스 수정을 통해 다른 보드에서도 사용 가능하다. 터틀봇3의 OpenCR 보드에서도 수정하여 사용하고 있다.
- rosserial_embeddedlinux: 임베디디용 리눅스에서 사용 가능한 라이브러리다.
- rosserial_windows: 윈도우 운영체제를 지원하고 윈도우의 응용프로그램과 통신을 지원한다.
- rosserial_mbed: ARM사의 mbed를 지원한다.
- rosserial_tivac: TI사의 Launchpad를 지원한다.
3.3. 프로토콜
rosserial server와 client는 시리얼 통신 시반의 패킷 형태로 데이터를 송/수신한다. rosserial protocol은 바이트 단위로 정의되어있고 패킷의 동기화 및 데이터 검증을 위한 정보들이 포함되어있다.
rosserial 패킷 구성
1st Byte | 2nd Byte | 3rd Byte | 4th Byte | 5th Byte | 6th Byte | 7th Byte | N Bytes | Byte N+8 |
---|---|---|---|---|---|---|---|---|
Sync Flag | Sync Flag/ Protocol version | Message Length(N) | Message Length(N) | Checksum over message length | Topic ID | Topic ID | Serialized Message Data | Checksum over Topic ID and Message Data |
- Sync Flag: 패킷의 시작 위치를 알기 위한 헤더로 항상 0xFF 이다.
- Sync Flag / Protocol version: 프로토콜 버전으로 ROS Groovy는 0xFF이고, ROS Hydro, Indigo, Jade 그리고 Kinetic은 0xFE 이다.
- Message Length(N): 메시지의 데이터 길이 헤더이며 2 Byte로 구성된다. Low 바이트가 먼저 전송되고 이어서 High 바이트가 전송된다.
- Checksum over message length: 메시지 길이 헤더의 유효성 검증을 위한 체크섬으로 계산 방법은 아래와 같다.
Checksum = 255 - ( (Message Length Low Byte + Message Length High Byte) %256)
- Topic ID: 메시지의 형태를 구분하기 위한 ID이다. 0~100까지는 시스템 함수로 예약되어있는데 주로 사용되는 ID는 아래와 같다.
ID_PUBLISHER=0, ID_SUBSCRIBER=1, ID_SERVICE_SERVER=2, ID_SERVICE_CLIENT=4, ID_PARAMETER_REQUEST=6, ID_LOG=7, ID_TIME=10, ID_TX_STOP=11
- Serialized Message Data: 송/수신 메시지를 시리얼 형태로 전송하기 위한 데이터이다. 예) IMU, TF, GPIO 데이터
- Checksum over Topic ID and Message Data: Topic ID와 메시지 데이터의 유효성 검증을 위한 체크섬으로 계산 방법은 아래와 같다.
Checksum = 255 - ( (Topic ID Low Byte + Topic ID High Byte + data byte values) % 256)
3.4. 제약 상황
- 메모리: 마이크로컨트롤러는 사용할 수 있는 메모리의 용량이 작고 제한되어있기 때문에 퍼블리셔, 서브스크라이버 개수 및 송신, 수신 버퍼의 크기를 미리 정의해야 한다.
- Float64: 마이크로컨트롤러는 64비트 실수연산을 지원하지 않아 32비트형으로 변경된다.
- Strings: 문자열 데이터를 String 메시지 안에 저장하지 않고 외부에서 정의한 문자열 데이터의 포인터 값만 메시지에 저장한다. 따라서 스트링 메시지를 사용하려면 아래와 같은 절차가 필요하다.
std_msg::String str_msg; unsigned char hello[13] = "hello world"; str_msg.data = hello;
- Arrays: 메모리 제약사항으로 배열 데이터에 대한 포인터를 사용해서 배열의 끝을 알 수 없다. 따라서 배열의 크기에 대한 정보를 추가해야 한다.
- 통신 속도: UART 같은 경우6) 115200bps와 같은 속도로는 메시지의 개수가 많아지면 응답 및 처리속도가 느려 질 수 있다.
3.5. 예제
예제를 진행하기 전에 roscore를 먼저 실행한 후 진행해야 한다. 다음은 OpenCR에서 제공하는 기본 예제이다.
#include <ros.h> #include <std_msgs/String.h> #include <std_msgs/Byte.h> int led_pin_user[4] = { BDPIN_LED_USER_1, BDPIN_LED_USER_2, BDPIN_LED_USER_3, BDPIN_LED_USER_4 }; ros::NodeHandle nh; void messageCb( const std_msgs::Byte& led_msg) { int i; for (i=0;i<4;i++){ if (led_msg.data & (1<<i)){ digitalWrite(led_pin_user[i], LOW); } else { digitalWrite(led_pin_user[i], HIGH); } } } ros::Subscriber<std_msgs::Byte> sub("led_out", messageCb ); void setup() { pinMode(led_pin_user[0], OUTPUT); pinMode(led_pin_user[1], OUTPUT); pinMode(led_pin_user[2], OUTPUT); pinMode(led_pin_user[3], OUTPUT); nh.initNode(); nh.subscribe(sub); } void loop() { nh.spinOnce(); }
rosserial_python을 이용해 rosserial server를 실행한다.
$ rosrun rosserial_python serial_node.py __name:=opencr _port:=/dev/ttyACM0 _baud:=115200 [INFO] [1495609829.326019]: ROS Serial Python Node [INFO] [1495609829.336151]: Connecting to /dev/ttyACM0 at 115200 baud [INFO] [1495609831.454144]: Note: subscribe buffer size is 1024 bytes [INFO] [1495609831.454994]: Setup subscriber on led_out [std_msgs/Byte]
rostopic pub을 이용하여 led_out에 값을 입력하면 해당 값에 따라 LED가 동작한다.
$ rostopic pub -1 led_out std_msgs/Byte 1 // USER1 LED On $ rostopic pub -1 led_out std_msgs/Byte 2 // USER2 LED On $ rostopic pub -1 led_out std_msgs/Byte 4 // USER3 LED On $ rostopic pub -1 led_out std_msgs/Byte 8 // USER4 LED On $ rostopic pub -1 led_out std_msgs/Byte 0 // LED Off