2019년 8월 27일 화요일

로보티즈 U2D2 이용한 다이나믹셀 연결 및 제어 방법

이 글은 U2D2를 이용한 다이나믹셀 연결 및 제어 방법을 간략히 정리한다. 기존에는 시리얼 포트를 이용한 장치를 상호 연결을 위해 사용하였지만 최근 USB 타입의 U2D2로 변경되었다.

작업 순서는 다음과 같다.

1. 다이나믹셀 위저드 다운로드
2. U2D2와 다이나믹 셀 연결
3. 설정

우선 다음 그림과 같이 U2D2와 모터를 연결한다. 그리고 다이나믹셀 위저드를 실행한다.

위저드는 모터의 상태와 ID값 등을 설정할 수 있다. 모터는 데이지 체인 방식으로 전원과 신호선을 연결하는 방식이므로 같이 연결할 모터의 ID는 서로 달라야 한다.

모터 상태 모니터링 및 설정

모터 제어 코딩 방법은 기존과 동일하다. 485 확장보드를 사용하면 DXL_BUS_SERIAL3로 코드를 변경해야 하며, 앞에서 확인한 다이나믹셀과 통신속도로 Dxf.begin() 입력숫자를 설정해야 한다. 

소스 코드는 다음과 같다.

#define DXL_BUS_SERIAL1 1  //Dynamixel on Serial1(USART1)  <-OpenCM9.04
#define DXL_BUS_SERIAL2 2  //Dynamixel on Serial2(USART2)  <-LN101,BT210
#define DXL_BUS_SERIAL3 3  //Dynamixel on Serial3(USART3)  <-OpenCM 485EXP
/* Dynamixel ID defines */
#define ID_NUM 1
/* Control table defines */
#define GOAL_SPEED 32
#define CCW_Angle_Limit 8
#define CONTROL_MODE 11

Dynamixel Dxl(DXL_BUS_SERIAL3);

void setup() {
  // Dynamixel 2.0 Protocol -> 0: 9600, 1: 57600, 2: 115200, 3: 1Mbps 
  Dxl.begin(1);
  
  
  //AX MX RX Series
  Dxl.writeWord(ID_NUM, CCW_Angle_Limit, 0); 
  //disable CCW Angle Limit(L) to use wheel mode
  
  //XL-320
  //Dxl.writeByte(ID_NUM, CONTROL_MODE, 1);
}

void loop() {
  //forward
  Dxl.writeWord(ID_NUM, GOAL_SPEED, 400); 
  delay(5000);
  //reverse
  Dxl.writeWord(ID_NUM, GOAL_SPEED, 400 | 0x400);
  delay(5000); 
  //stop
  Dxl.writeWord(ID_NUM, GOAL_SPEED, 0); 
  delay(2000);
}


다이나믹셀의 기준 점은 다음과 같이 점으로 표시되어 있다. 마운트 부품도 기준점 표시가 되어 있으니 참고 한다. 모터와 마운트 조립시 이 기준점에 맞춰 조립하는 것이 좋다. 기준 각도는 6시 방향이며, 시계 반대방향이 +각도이다. 
모터 기준 각도 표시

마운트 부품과 모터가 체결한 모습 

참고로, 마운트는 각 모터별로 호환되는 것이 다르다. 마운트 호환성은 다음 링크를 참고 한다. 마운트는 구매할 수 있으나 나머지 로봇 기구부는 직접 설계해 제작하는 것이 보통이다.
아래는 로보티즈에서 제공하는 WHEEL POSITION 예제를 통해 300 지점에 각도를 회전한 모습이다.
회전 모습(MX-106. 300지점)

이 경우 소스코드는 다음과 같다.
void loop() {  
  //Turn dynamixel ID 1 to position 0
  Dxl.writeWord(ID_NUM, GOAL_POSITION, 0); //Compatible with all dynamixel serise
  // Wait for 1 second (1000 milliseconds)
  delay(1000);              
  //Turn dynamixel ID 1 to position 300
  Dxl.writeWord(ID_NUM, GOAL_POSITION, 300);
  // Wait for 1 second (1000 milliseconds)
  delay(1000);              
}

사실 다이나믹셀 설정이나 제어 방법은 크게 바뀌지 않았다. 다만, 연결 방식이 좀 더 편리해 지고 있고 모터 종류도 점차 다양해 지고 있어 더욱 편리하게 다양한 타입의 로봇을 개발할 수 있다.



2019년 8월 7일 수요일

저렴한 딥러닝 임베디드 장치 NVIDIA NANO 개봉, 딥러닝 예제 및 GPIO 사용기

이 글은 간단한 NVIDIA NANO 개봉 및 사용기이다.

개요
개발보드는 엔비디아 TX에 비해 1/4로 작아지고, 손바닥 정도 크기에 CUDA코어는 128개 장착되어 있다. 저전력 사용으로 5V USB 전원으로 동작 가능하다.
나노와 TX2 비교

가격은 약 10~20만원 선이다(무선 장치 추가에 따라 달라짐).
패키지 박스

개봉한 모습(Intel WiFi는 별도 구매)

크기 비교(TX에 비해 개발보드 크기가 1/4수준)

셋업 순서
다음 영상을 통해 쉽게 딥러닝에 필요한 운영체제, CUDA 라이브러리, 프레임웍 및 개발도구를 설치할 수 있다. 
엔비디아 나노 셋업 영상

설치는 다음 링크에 있는 내용을 잘 따라해 보면 된다. 크게 젯슨 나노 이미지 다운로드, 이미지 SD 카드 복사, 운영체제 및 패키지 설치 순서이다.
이미지 다운로드 및 SD 카드 복사가 되었으면, SD 카드를 나노에 넣고, 전원을 꼽아 부팅한다. 

참고로, 전원이 안정적이지 않으면 제대로 부팅되지 않는 현상이 발생한다. 전원은 5V, 2.5A 이상이며, 4A를 권장한다. 직접 아답터를 구입해 전원 연결하는 것을 권장한다. 이 경우, J48점퍼를 다음 그림과 같이 연결해야 한다. 

본인의 경우, 점퍼가 없어 다음과 같이 임시로 연결해 놓았다.

제대로 부팅되면 다음과 같은 화면을 볼 수 있다.

예제 설치 및 실행
예제는 매우 다양한 것들을 제공하고 있으며, 특히 딥러닝에 대해서는 많이 알려진 모델 대부분을 제공하고 있다. 우선 다음과 같이 환경변수를 추가한다.
vim .bashrc
export CUDA_HOME=/usr/local/cuda
export LD_LIBRARY_PATH=${CUDA_HOME}/lib64 
PATH=${CUDA_HOME}/bin:${PATH} 
export PATH 

이 글에서는 이미지 기반 딥러닝 모델을 설치해 실행해 보기로 한다. 아래 링크를 참고해 관련 라이브러리와 소스코드를 다운로드하고 빌드해 설치한다.
본 글에서는 저렴한 640x480 해상도의 USB 카메라를 이용해 빌드된 딥러닝 프로그램을 실행해 보겠다. 이 전에 USB카메라를 미리 준비해 놓고 다음 명령을 이용해 제대로 카메라가 동작하는 지 확인해 본다.

sudo apt-get install v4l-utils
v4l2-ctl --info --list-devices
gst-launch-1.0 v4l2src device="/dev/video0" ! "video/x-raw, width=640, height=480, format=(string)YUY2" ! xvimagesink -e

제대로 동작된다면, 아래처럼 이미지넷 딥러닝 프로그램을 실행한다. 카메라 장치는 실제 연결된 드라이버를 /dev/video0부분에 지정한다.

imagenet-camera --camera /dev/video0 --width640 --height480

실행하면 다음과 같은 결과를 확인할 수 있다. 실시간으로 딥러닝 모델이 추론되는 것을 확인할 수 있다.
웹캠을 통한 ImagenNet 프로그램 실행 결과

나노 성능이 뛰어나다 보니, 다음과 같은 데모를 원활하게 수행할 수 있다.
나노 기반 딥스트림 데모

RGBD 기반 3차원 ODOMETRY 데모

3차원 3D 지형 맵핑 데모

대부분의 딥러닝 오픈소스 프레임웍, 딥러닝 모델 및 예제는 엔비디아에서 다운로드받아 나노에서 실행할 수 있다. 

Nvidia nano demo

GPIO 사용
나노에는 GPIO 기능이 있다. 이를 이용해, 센서, 액추에이터 등과 신호 전달 및 데이터 교환이 가능하다. 

핀 구성은 다음과 같다(ex. BCM 18 = BOARD PIN 12).

GPIO는 Jetson GPIO 라이브러리 패키지의 파이썬 라이브러리를 이용해 디지털 입출력 핀을 제어한다. 참고로, 이 라이브러리는 라즈베리파이에서 제공하는 RPi.GPIO와 비슷하게 개발되어 있어 마이그레이션을 쉽게 할 수 있다. 

이 라이브러리는 lib/python/ 하위 폴더에 구현되어 있고, gpio.py는 관련 API를 제공한다. gpio_event.py, gpio_pin_data.py 모듈은 gpio.py에 의해 사용된다. sample폴더는 다양한 예제를 제공한다. simple_output.py는 GPIO에 값을 입출력하는 방법을 보여준다. button_led.py 등은 버튼을 사용해 LED 제어하는 방법을 보여준다.

개발을 위해 다음과 같이 도구를 설치한다(참고).
sudo pip install Jetson.GPIO

만약, 메뉴얼 다운로드 및 설치 하고자 한다면 다음 명령을 실행한다.
git clone https://github.com/NVIDIA/jetson-gpio
sudo python3 setup.py install

GPIO 사용을 위해서는 해당 권한을 설정해야 한다. 우선, gpio user group을 만들고, 사용자를 그룹에 추가한 후, GPIO 사용권한 규칙을 정의한다. 정의한 규칙을 리로드, reboot 한다.
sudo groupadd -f -r gpio
sudo usermod -a -G gpio your_user_name
sudo cp /opt/nvidia/jetson-gpio/etc/99-gpio.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules && sudo udevadm trigger

이제 /opt/nvidia/jetson-gpio/samples 예제를 실행해 본다. 
python3 <name_of_application_to_run>

만약, Jetson.GPIO가 PYTHONPATH에 추가되지 않았다면, run_sample.sh 를 사용할 수 있다. 
./run_sample.sh <name_of_application_to_run>

파이썬에서 라이브러리 사용은 다음과 같다. 
import Jetson.GPIO as GPIO

GPIO.setmode(GPIO.BOARD) # 핀 순서대로 번호 지정
GPIO.setmode(GPIO.BCM)    # 브로드 SOC 채널 번호로 GPIO 번호
GPIO.setmode(GPIO.CVM) 
GPIO.setmode(GPIO.TEGRA_SOC)

mode = GPIO.getmode()
GPIO.setwarnings(False)

# 채널 입출력 설정
GPIO.setup(channel, GPIO.IN)
GPIO.setup(channel, GPIO.OUT)
GPIO.setup(channel, GPIO.OUT, initial=GPIO.HIGH)
channels = [18, 12, 13]
GPIO.setup(channels, GPIO.OUT)

value = GPIO.input(input_pin)
channels = [18, 12, 13] # or use tuples
GPIO.output(channels, GPIO.HIGH) # or GPIO.LOW
# set first channel to HIGH and rest to LOW
GPIO.output(channel, (GPIO.LOW, GPIO.HIGH, GPIO.HIGH))

GPIO.cleanup()

# 인터럽트 및 콜백 함수 처리
# timeout is in milliseconds
GPIO.wait_for_edge(channel, GPIO.RISING, timeout=500)

def callback_one(channel):
    print("First Callback")

def callback_two(channel):
    print("Second Callback")

GPIO.add_event_detect(channel, GPIO.RISING)
GPIO.add_event_callback(channel, callback_one)
GPIO.add_event_detect(channel, GPIO.RISING, callback=callback_two,
bouncetime=200)

GPIO.remove_event_detect(channel)

GPIO.JETSON_INFO  # GPIO 젯슨 정보
GPIO.VERSION        # GPIO 버전

Jetson은 하드웨어 PWM 장치를 제공한다. 나노는 2개, AGX Xavier는 3개를 지원한다. 이를 사용하기 위해서는 pinmux 를 설정해야 한다(L4T 문서를 참고). 

예제 실행은 다음과 같이 명령 입력하면 된다.  
python3 simple_out.py

다음은 GPIO를 이용해 버튼 누를 때 LED가 켜지는 예시 회로이다(1kOhm for switch in Nvidia nano). 
보통 대부분의 응용은 외부에서 입력받아, 알고리즘에 따라 출력을 내는 방식이다. 다음은 그 예를 간단히 구현해 본것이다. 버튼 입력을 받아 특정 패턴에서 LED출력과 실행 프로그램 호출을 수행한다.
import RPi.GPIO as GPIO
import time
import os

# Pin Definitions
in_pin = 22 # BOARD pin 15, BCM pin 22
output_pin = 18  # BOARD pin 12, BCM pin 18

def main():
    # Pin Setup:
    # Board pin-numbering scheme
    GPIO.setmode(GPIO.BCM)
    # set pin as an output pin with optional initial state of HIGH
    GPIO.setup(in_pin, GPIO.IN)
    GPIO.setup(output_pin, GPIO.OUT, initial=GPIO.HIGH)

    prev_value = None

    print("Starting demo now! Press CTRL+C to exit")
    curr_value = GPIO.LOW
    try:
        while True:
            value = GPIO.input(in_pin)
            if prev_value != value:
                prev_value = value
                if value == 1: 
                   curr_value = curr_value ^ GPIO.HIGH
                   if curr_value == GPIO.HIGH:
                       print("begin")
                       os.system('ls -l')
                   else:
                       print("end")
                   
                GPIO.output(output_pin, curr_value)
                print("Outputting {} to pin {}".format(curr_value, output_pin))

    finally:
        GPIO.cleanup()

if __name__ == '__main__':
    main()

실행 결과는 다음과 같다.


이와 관련해 좀 더 자세한 내용은 다음 링크를 참고한다.
마무리
이 글에서는 엔비디아 나노를 개봉해 설치하고, 사용한 내용을 간단히 나눔하였다. 아직 불안전한 전원 문제, 카메라 설정 등 이슈가 있으나, 이 가격에 이 정도 성능이면 가성비가 매우 좋은 임베디드 보드임에 틀림없다. 다만, 최근에 출시한 장치라 안정화에는 약간 더 시간이 걸릴 듯 하다. 

참고 - 배터리 전원 문제
나노의 전원 스펙이 매우 까다로운 편이라 배터리를 연결할 때 전원 맞추기 쉽지 않다. 5V에서 3A, 4A 정도는 되어야 한다. 전류를 많이 사용하는 장치를 연결하면 4A는 되어야 하는 데 이런 배터리를 구하기 쉽지 않다. 아래는 관련해 살펴본 배터리 관련 장치이다. 
참고 - USB Mount, Privilege and copy
가끔 USB를 나노와 연결해, USB에 파일을 저장해야 할 때가 있다. 이럴 때는 이 장치를 리눅스와 마운트해서 사용해야 한다. 

우선 다음 명령으로 장치 리스트를 확인한다.
sudo fdisk -l

장치명이 sda1인 경우, 다음 명령어로 마운트한다. 
sudo mount -t vfat /dev/sda1 /mnt/usb
sudo mount -t vfat /dev/sda1 /mnt/usb/ -o rw,uid=$(id -u),gid=$(id -g)

언마운트하려면 다음 명령을 입력한다.
sudo umount /mnt/usb
sudo eject /mnt/usb

마운트된 장치로 복사하기 위해서는 권한이 필요하다(chmod -R [8bit permission] [file name or folder name]으로는 동작 안됨). 다음과 같이 sudo cp 명령을 사용한다.
sudo cp -rp ./ /mnt/usb/test/

파이썬에서는 다음과 같이 구현하면 된다.
sudoPassword = 'password'
command = 'mount -t vboxsf myfolder /home/myuser/myfolder'
ret = os.system('echo %s|sudo -S %s' % (sudoPassword, command))

좀 더 상세한 내용은 다음을 참고한다.