2015년 9월 28일 월요일

Gazebo 설치 방법

가제보는 로봇 시뮬레이션 프로그램으로, 다르파 공식 지정 도구이다.

설치를 위해서는, 다음과 같이 명령을 입력해야 한다 (우분투 14.04 기준).

$ sudo sh -c 'echo "deb http://packages.osrfoundation.org/gazebo/ubuntu trusty main" > /etc/apt/sources.list.d/gazebo-latest.list'
$ wget http://packages.osrfoundation.org/gazebo.key -O - | sudo apt-key add -
$ sudo apt-get update
$ sudo apt-get install libsdformat1 libsdformat-dev gazebo2
 
$ sudo apt-get install ros-indigo-gazebo-ros ros-indigo-gazebo-plugins ros-indigo-kobuki-desktop
$ roslaunch kobuki_gazebo kobuki_empty_world.launch 
 
URI 에러가 있어, 월드가 로딩되지 않는다면, 다음 환경변수를 roscore가 실행되는 URI와 포트로 설정하고, 다시 실행해 본다. 
GAZEBO_MASTER_URI=http://localhost:11311

그외 에러가 있어, 모델이 제대로 로딩되지 않는다면, 이 링크를 참고한다.

테스트를 위해, 아래 명령을 입력한다.
$ roslaunch kobuki_keyop keyop.launch

이제 다음과 같이 키보드에 따라 시뮬레이션을 할 수 있다.




자신만의 환경 모델을 만들려면, 다음 파일을 참고해, 모델을 추가해 준다.
/opt/ros/indigo/share/kobuki_gazebo/worlds/playground.world

프로그래밍된 시뮬레이션 예제는 다음과 같다.
$ roslaunch kobuki_gazebo kobuki_playground.launch


다음과 같이 랜덤으로 이동하면서, 벽에 부딪쳤을 때 회피하는 예제를 실행해 본다. 
$ roslaunch kobuki_gazebo safe_random_walker_app.launch
 



기타, 가상 로봇으로 다양한 시뮬레이션을 위해서는 다음 위키를 참고한다.
http://wiki.ros.org/turtlebot_stdr
http://wiki.ros.org/turtlebot_gazebo
http://wiki.ros.org/turtlebot_stage
 
 

2015년 9월 26일 토요일

오드로이드(ODROID) XU4 기반 ROS 설치

이 글은 ODROID XU4 기반 ROS 설치 방법을 간단히 기술한 것이다.

1. 소개
오드로이드 XU4는 8개 코어를 가진, 에너지 저효율, 초소형 컴퓨터 보드이다. 가격도 8~9만원 밖에 안한다. 명함크기로, 오픈소스 운영체제를 지원한다. 자세한 내용은 다음 링크를 참고한다.



2. 설치
정식 설치 방법은 다음과 같다 (이 방법 보다는 이미지 설치를 권장한다).

1. 우분투 15.04 설치
2016.3 현재 우분투 15.04 버전 이미지가 릴리즈되었다. 이 버전에는 Ubuntu 15.04 with MATE Desktop, OpenCV 2.4.12.1, ROS Jade(Bare Bones), OpenNI 2.2, Libfreenect, Libfreenect2, Point Cloud Library 1.7.2, Arduino IDE 가 포함되어 있다.

이미지를 다운로드 받은 후, xz압축을 푼다. 그리고, Win32 Disk Imager를 이용해, img파일을 micro SD카드에 굽는다. 그리고, SD카드를 오드로이드에 넣고, 전원을 넣는다.



그럼, 다음과 같은 화면을 볼 수 있다(모니터가 딴곳에 가 있어, 프로젝터를 사용했다). 만약, 완전히 부팅되지 않고 중간에 계속 재부팅하는 문제가 있으면 전원이 아래 스펙인지 체크해 본다.
코어가 8개라, 8개의 썸네일이 출력된다.


디폴트 사용자 및 루트 계정 로그인 아이디와 암호는 다음과 같다.

Username: odroid
Password: odroid

Username: root
Password: odroid

제대로 설치되었는 지 테스트해 보기 위해, roscore도 실행해 보고, ~/opencv-2.4.12.1/samples/python 폴더의 몇몇 예제를 실행해 본다. 몇몇 예제들은 웹캠(webcam)이 있어야 한다.

제대로 설치되었다면, 다음과 같이 정상적으로 실행된다.

그림. roscore와 opencv의 delaunay 예제 실행 모습

그림. opencv의 에지 검출 등 몇몇 예제 실행 모습

opencv의 python, python2 폴더에는 스테레오 영상에서 3차원 포인트 클라우드 추출 등 흥미로운 예제들이 많이 있으니 확인해 보길 바란다.

2. 우분투 14.04 설치
우분투 14.04 이미지 설치 방법은 이 링크를 참고한다.

1. 우분투 14.04를 설치한다.
2. ROS를 설치한다.
3. 테스트한다.

앞의 방법과 같이 SD카드로 부팅하면, 다음과 같은 화면을 볼 수 있다. roscore가 정상 실행됨을 확인할 수 있다.



3. 결론
저렴한 비용으로, 옥타콥터 초소형 컴퓨터를 사용하다니.... 이건 너무 좋은거다. 정말 다양한 것을 많이 할 수 있다. 아래 영상을 참고해 보라.





RF 리모컨 기반 서보 모터 제어

오늘은 간단히 FS-TH9X RF송수신기를 이용한, 서보모터 제어 방법을 간단히 알아보도록 하겠다.

1. 조립
조립은 매우 쉽다. 송신기는 다음 왼쪽 그림과 같이 생겼다. 수신기는 다음 오른쪽과 같이 작은 박스처럼 생겼다(적색 표시된 안테나가 달려 있다).


이 수신기는 아래와 같이 각 핀이 배터리 연결부를 제외하고는 8개 채널이 있다. 즉, 모터를 8개 별개로 제어할 수 있다.

전원을 수신기에 연결하기 위해 수신기의 BIND라고 표시된 채널의 3개의 핀에서, 신호핀을 제외한 양극+, 음극- 핀에 배터리를 다음과 같이 연결한다. 그리고, 서보 모터를, 채널 1과, 채널 3에 다음 그림과 같이 연결한다


2. 테스트
송신기 전원을 키고, 조정기를 좌우상하로 움직여 본다. 그럼, 이 동작에 따라, 서보모터가 원격으로 움직일 것이다.


동작 영상은 다음과 같다.


3. 결과
서보 모터와 같이, 모터를 제어할 수 있는 신호선이 있다면, 수신기에 직접 연결해서, 모터를 바로 제어할 수 있다. 만약, 신호선이 없는 DC 모터인 경우에는, 수신기에 입력되는 신호에 따라, DC모터에 PWD로 전압을 조정해 제어할 수 있는 회로를 덧붙여 연결해야 한다. 만약, 아두이노를 이용한다면, 이 회로는 간단히 프로그램으로 코딩하면 된다.
참고로, 수신기에서 특정 채널에 신호를 주면, 송신기의 신호선에 전압의 변화가 일어나게 된다. 이 전압의 변화는 송신기의 조작에 따라 전압이 올라가기도하고, 내려가기도 하는 데, 이를 이용해, 아두이노에서 전압의 변화에 따라, DC모터에 PWD제어를 하면 DC모터를 쉽게 제어할 수 있다.

참고 내용은 다음과 같다.

1. FS-TH9X 설정 방법

2. FS-TH9X

Turnigy 9X Configuration for Aux Channel Gimbal and Servo Control 



3. FS-TH9X 메뉴얼


2015년 9월 12일 토요일

Dynamixel XM-430 설정

이 글에서는 로보티즈의 다이나믹셀 XM-430 설정 방법을 정리한다.

1. 개요
최근에 출시된 XM-430 설치 방법을 정리한다. 기존 모델에 비해, 알루미늄 재질로 튼튼하게 마무리되어 있으며, 그래픽카드와 유사한 메모리 주소 기반 제어를 지원한다.

2. 설정
다음과 같은 순서로 액추에이터를 인식하고, 각종 파라메터(아이디)를 설정한다. 아두이노와 유사한 OpenCM으로 제어를 할 수 있다.










3. 마무리
간단한 설정과 아두이노와 유사한 개발환경 제공은 매우 편리하다. 다만, ROS와 같은 미들웨어에서 제공되는 아직 예제가 그리 많지 않다는 것은 아쉽다.

2015년 9월 6일 일요일

태양광 에너지 충전 방법

센서 등을 동작할 때, 배터리 없이, 자가 발전을 하기 원할 때가 있다. 이 경우에 사용할 수 있는 태양광 기반 에너지를 충전하는 방법을 정리한다.

1. 태양 전지 연결
태양 전지를 구입하였다면, 사용 전압에 맞게 태양 전지를 적절히 연결해야 한다. 연결하는 방법은 다음과 같다.


직렬로 연결하면, 전압이 증가되고, 병렬로 연결하면, 전류양이 증가한다.

2. 배터리 연결
이제, 태양 전지와 배터리를 연결해야 한다. 양쪽 모듈을 같은 극성끼리 연결하되, 태양 전지의 양극에서 배터리의 양극선 사이에, 다이오드를 다음 그림과 같이 연결한다. 그렇지 않으면, 전류가 배터리방향으로만 흐르지 않으므로, 전기 충전이 안된다.

회로 기호는 이 링크를 참고한다.

3. 테스트
실제로 잘 동작되는 지 테스트해보자. 테스트에 사용한 태양전지는 한장에 2.7V를 출력하며, 많은 전류를 얻기 위해, 병렬로 결선하였다. 사용된 충전지는 니켈 카드뮴 배터리(N-270AA)로, 1.2V 두개를 직렬연결해, 2.4V로 만들었으며, 용량은 270mAh를 갖는다. 야외에서 태양전지를 소형 모터에 연결해 본다. 큰 문제 없으면, 다음과 같이 잘 동작한다.


충전지에 충전을 해 본다. 다음은 충전하기 전의 충전지 전압이다.


야외에서 충전을 해 본다. 회로 결선은 앞의 설명대로 연결한다.


야외에서 약 30분 정도 충전을 시킨다. 그리고, 다시 전압을 측정해 본다.


충전지와 소형모터를 연결해 본다. 다음과 같이 소형모터를 회전할 만한 전압과 전류가 있다는 것을 확인할 수 있다.


약 1분 정도 소형모터를 동작시킨 후, 전압을 다시 측정해 본다. 다음과 같이, 전압 소모가 일어났음을 알 수 있다.


LED를 연결해 본다. 다음과 같이 잘 동작한다.


4. 응용 사례
다음은 태양전지 기반 아두이노이다. 아두이노에 필요한 전류를 태양전지에서 충전된 충전지로 부터 얻는다.


OpenCM 기반 엑추에이터 제어

이 글에서 OpenCM 9.04 기반 엑추에이터 제어 방법을 정리한다.

1. 개요
OpenCM은 아두이노 호환 보드로, 로보티즈에서 개발한 오픈소스 엑추에이터 전용 개발보드이다. 로보티즈에서 개발한 다이나믹셀을 제어하는 데 주로 사용한다.

2. 설치
아래의 링크에서 파일을 다운로드 받아, 드라이버를 설치해야 한다.
그리고, ROBOTIS_OpenCM 파일을 실행하면, 아두이노 IDE와 유사한 프로그램이 실행된다. 시리얼포트와 보드종류를 설정한 후, OpenCM 예제파일을 로딩해, 펌웨어를 빌드해본다. 그리고, 보드로 다운로드해 본다.

3. 마무리
다이나믹셀은 스마트 액추에이터로 알려져 있을 만큼, 구동부에 필요한 센서 등이 포함되어 있고, 많은 다이나믹셀을 편리하게 연결할 수 있도록, 데이지체인 방식의 연결을 지원한다.

무선 조정용 FlySky TH9X

1. 소개
무선으로 액추에이터에 신호를 전달하기 위해서는 무선 조정 장치가 필요하다. FlySky TH9X는 저렴하면서, 기능이 많은 9채널 무선 조정기이다. 이 조정기를 이용하면, 매우 간단하게, 액추에이터를 제어할 수 있다.



각 채널은 조정기의 스위치 등을 이용해, 무선으로 신호를 전달할 수 있다. 전달 받은 신호는 액추에이터를 구동하기 위한 시그널로 입력된다.

2. 사용팁
처음 조정기를 시작할 때, switch error가 날 경우가 있다. 이 경우에는 모든 switch를 backside로 젖히고, 다시 전원 버튼을 누르면 된다. 아래 영상을 참고하라.


에너지 절약 캠패인을 위한 스마트 홈 원리에서 응용한 프로세싱(Processing) 및 아두이노

이 글은 스마트 홈 원리 교육을 위한 프로세싱(Processing)와 아두이노 활용 방법에 대해서 다룬다. 이 내용은 다음 글을 참고하였다.
실제로 에너지 절약 캠페인을 위해, 스마트 홈이란 주제로 전시한 메이커 페어 참가 프로젝트의 상세 내용은 다음 링크를 참고하길 바란다. 메이커 페어 때 작업한 스마트 홈 관련 디자인, 소스코드 및 관련 참고 자료가 포함되어 있다 (전체 구현 내용은 매우 많으므로, 이 글에서 제공하지 않는다).

1. 프로세싱 개요
프로세싱은 미디어아트에 많이 활용되는 저작 도구이다. Java기반으로 스크립트를 개발하여, 데이터를 가시화한다. 매우 많은 수의 라이브러리와 예제를 프로세싱 홈페이지 및 관련 사이트에서 제공한다. processing.js를 사용하면, 웹기반으로 동작한다.

2. 프로세싱 사용 사례
프로세싱으로 개발한 작품을 확인해 보자.


이와 같이 전문가적인 컴퓨터 그래픽스를 만들 수 있다.

3. 프로세싱 코딩
프로세싱을 이용해, 센서 데이터를 취득하고, 이를 가시화하는 다음 그림과 같은 데쉬보드를 개발해 본다.


센서 데이터는 serial 통신으로 부터 획득한다. 조도, 온도, 습도, 전류 센서를 아두이노에 연결하여, 초당 2회씩 데이터를 프로세싱으로 보낸다. 이를 가시화한다.

데이터베이스에 센서 데이터를 저장하는 원리를 간단히 보여주기 위해, sensor.txt, energy.txt 파일 두개를 사용하였다(물론, MySQL과 같은 DB를 사용할 수 있으나, 교육적 원리를 전달해 주면 충분하므로 작업이 쉬운 방법으로 처리한다). 아울러, 센서 저장 및 그래픽 출력 간격을 교육적 효과를 얻을 수 있도록 매우 짧게 설정하였다.

다음은 관련 주요 코드이다(bar chart, pie chart 및 building 그래픽은 소스에 포함하지 않았음. 앞의 링크 참고.).

/** 
 * Smart home
 */ 
import java.util.Iterator;

PFont font = createFont("Arial", 30, true);
Chart[] charts = new Chart[2];
Bar[] bars = new Bar[2];
building[] buildingz; 

PApplet pg;

void setup() {
  size(1024, 768, P3D);
  smooth();
  noStroke();
  colorMode(RGB);  // colorMode(HSB, TWO_PI, 1, 1, 1);
  
  setup_arduino();  
  
  for(int i = 0; i < charts.length; i++)
    charts[i] = new Chart();
  charts[0].initChart("Year", width * 0.8, height * 0.2, 180.0);  
  charts[1].initChart("Month", width * 0.8, height * 0.5, 180.0);  

  for(int i = 0; i < bars.length; i++)
    bars[i] = new Bar();
  bars[0].initBar("Sensor", "sensor.txt", "sensor_data.txt", width * 0.05, height * 0.62, 400, 300);
  bars[1].initBar("Energy", "energy.txt", "energy_data.txt", width * 0.55, height * 0.62, 400, 300);

  initBuilding();  
}

void draw() {
  update_arduino();
  
  background(230, 150, 30);  
  
  textFont(font);  
  String str = "Smart Home Dashboard";
  fill(0);
  text(str, width / 2, 50);  
  
  for(int i = 0; i < charts.length; i++)
    charts[i].drawChart();

  for(int i = 0; i < bars.length; i++)
    bars[i].drawBar();
    
  fill(32, 128, 32, 128);
  house(20, 300, 250, 150);
  fill(32, 128, 32, 196);  
  house(220, 200, 320, 250);  
  
  translate(90, -20);  
  for(int a=0;a<buildingz.length;a++){
    buildingz[a].dessine( );
  }  
  translate(0, 0);  
}

void initBuilding()
{
  buildingz = new building[0];  
  float decx = 270; 
  float decy = 270;
  
  float bx = 270;
  for (int b = 0; b < 4; b++){
    float mindy = 0;
    for (int a = 0; a < 4;a++){
      float x = decx; 
      float y = decy + mindy;
      new building(x, y);

      decx = decx - 76;
      mindy += 25;
    }
    decy = decy + 30;
    decx = bx + 30; 
    bx = decx;
  } 
}

void house(float x, float y, float W, float H) {
  noStroke();
  rect(x, y, W, H);
  triangle(x, y, x + W / 2, y - H / 2, x + W, y);
}

웹서버에 개발된 코드를 동작시키려면, 다음과 같이 시도해 본다.
1. 웹서버를 설치한다 (python 서버, locally).
2. processing.js 등을 다운로드 받는다.
3. index.html 파일을 다음과 같이 만든다.
<html>
<head>
<title>Hello Web</title>
<script src="processing.js"></script>
</head>
<body>
<h1>Processing.js</h1>
<p>web-based sketch:</p>
<canvas data-processing-sources="your_processing-1.pde your_processing-2.pde ..."></canvas>
</body>
</html>

4. 웹서버를 실행한다.
5. chrome에서 localhost:8080을 입력한다.

만약, 프로세싱 스케치가 로딩되지 않아, 그래픽이 표시되지 않으면, 프로세싱 파일을 하나로 합쳐본다. 이렇게 해도 로딩이 잘 안되면, 다른 종류의 웹서버를 설치해 시도해 본다.


4. 아두이노 연결
아두이노에서는 센서 데이터를 취득하거나, 릴레이와 같은 액추에이터를 제어한다.

릴레이에는 5V LED가 연결되어 있어, 간단한 스마트 전등을 구현하고 있다.
에너지 소모 데이터는 전류센서로 부터 얻도록 되어 있다. 기타, 스마트 홈에서 가전기기 조절에 필요한 온도, 습도, 조도 센서도 아두이노와 연결한다.

이렇게 연결된 센서 데이터는 energy.txt, sensor.txt로 구분해 파일로 저장하고, 이 데이터를 정해진 시간 단위로 읽어, 앞의 프로세싱으로 코딩한 스마트 홈 데쉬보드에 컴퓨터 그래픽으로 출력한다. 이런 방식으로 스마트 홈의 기본적인 기능들을 만들고, 보기 쉬운 그래픽으로 표현하여, 에너지의 중요성에 대한 교육적인 효과를 전달한다.

아래 코드의 적색 부분에서 센서값을 프로세싱과 시리얼포트로 주고 받는다.
참고로, 아두이노에서 사용한 코드들은 앞의 압축파일 링크에서 '1.공부'폴더에 저장된 참고자료들을 사용해, 적절히 조합한 것이다.

상세 코드는 다음과 같다.

// include the library code:
#include <LiquidCrystal.h>
#include <DHT.h>

// Relay pin
const int relay_pin = 7;
boolean relay_state = false;

// Define measurement variables
float amplitude_current;
float effective_value;
float effective_voltage = 230; // Set voltage to 230V (Europe) or 110V (US)
float zero_sensor;

// DHT instance
#define DHTPIN 6
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(10, 11, 2, 8, 4, 9);

// Variables to be exposed to the API
int temperature;
int humidity;
int light;
int effective_power;

void setup() {
  // Start Serial
  Serial.begin(9600); // 115200);
  
  // set up the LCD's number of columns and rows: 
  lcd.begin(16, 2);
  pinMode(relay_pin, OUTPUT);
  zero_sensor = getSensorValue(A1);
}

void loop() {
  // Measure from DHT
  temperature = dht.readTemperature(); // / 30.0;  // 4.7k Ohm. Calibration factor.
  humidity = dht.readHumidity(); // / 20.0;
  
  // Measure light level
  float sensor_reading = analogRead(A0);
  light = sensor_reading / 1024*100;

  // Perform power measurement
  float sensor_value = getSensorValue(A1) - zero_sensor; 

  // Convert to current. (ACS712T). http://arduinosensors.com/index.php/tag/allegro-acs712/
  float sensor_voltage = sensor_value * 10.0 / 1023.0;
  amplitude_current = (sensor_voltage - 2.5) / 0.186;
  effective_value = amplitude_current / 1.414;
  effective_power = abs(effective_value * effective_voltage / 1000); 

  String data;
  data = data + temperature + ", " + humidity + ", " + light + ", " + effective_power;
  Serial.println(data);

  // If the light level is less than 50 %, switch the lights off
  if (light < 50) 
  {
    digitalWrite(relay_pin, HIGH);
    relay_state = true;  
    lcd.begin(16, 2);
  }
  else
  {
    digitalWrite(relay_pin, LOW);
    relay_state = false; 
    lcd.begin(16, 2);
  }  
  
  // Display temperature
  lcd.setCursor(0,0);
  lcd.print("T:");
  lcd.print((int)temperature);
  lcd.print((char)223);

  lcd.setCursor(6,0);
  lcd.print("P:");
  lcd.print(effective_power);
  lcd.print("W     "); 

  lcd.setCursor(11,0);
  lcd.print("");
  lcd.print(sensor_voltage);
  lcd.print("V   "); 

  // Display humidity
  lcd.setCursor(0,1);
  lcd.print("H:");
  lcd.print(humidity);
  lcd.print("%");
  // Display light level
  lcd.setCursor(6,1);
  lcd.print("L:");
  lcd.print(light);
  lcd.print("%");  


  delay(200);
}

// Get the reading from the current sensor
float getSensorValue(int pin)
{
  int sensorValue;
  float avgSensor = 0;
  int nb_measurements = 100;
  for (int i = 0; i < nb_measurements; i++) {
    sensorValue = analogRead(pin);
    avgSensor = avgSensor + float(sensorValue);
  }
  avgSensor = avgSensor / float(nb_measurements);
  return avgSensor;
}


5. 마무리
프로세싱은 시리얼 장치, 센서, 카메라, 키텍트 등과 같은 다양한 입출력 장치 및 관련 라이브러리를 지원할 뿐만 아니라, javascript기반 node.js를 이용해, 웹 상에서 스마트폰 같은 다양한 장치를 통해, 작품을 렌더링할 수 있는 강력한 도구이다. 이를 잘 활용하면, 매력적인 그래픽스 영상을 개발할 수 있다.

상보필터(Complementary Filter) 기반 IMU 값 보정

이 글은 상보 필터를 이용해 IMU 값을 보정하는 방법을 정리한 것이다. 이 글은 다음 글을 참고하였다.

1. 개념
MPU-6050 가속도와 자이로(각속도) 센서는 이론적으로는 각 값을 누적한다면, 거리와 각도를 알 수 있어야 한다. 하지만, 센서에 노이즈가 포함되어 있어, 누적하면 할 수록 참값과는 멀어져 버려, 발산해 버리고 만다. 그러므로, 노이즈를 필터링해, 에러를 제거해 주어야 한다. 이런 기법 중 유명한 것이, 상보 필터(Complementary Filter)와 칼만 필터(Kalman Filter)이다. 이 글에서는 상보 필터를 다룬다.

2. 내용
상보 필터는 각도를 계산할 때, 단순히, 자이로값(각속도값)만 적분(합산)하지 않는다. 가속도 센서값을 함께 고려해, 에러값을 보정한다. 이 내용을 수식으로 표현하면 다음과 같다.

angle = 0.98 * (angle + gyroscope_data + delta_t) + 0.02 * (accelerometer)

여기서, 0.98과 0.02 상수값을 변경해, 필터를 얼마나 적용할 지 여부를 설정할 수 있다. 상세한 소스는 이 링크를 참고한다.

3. 마무리
다음 영상은 가속도 적분, 각속도 적분, 상보필터기반 적분 결과를 보여주는 영상이다. 상보필터가 적용되지 않은 결과들은 시간이 지나면서, 오차가 누적되는 것을 확인할 수 있다.


상보 필터는 칼만 필터에 비해, 간단하며, 아두이노와 같은 저성능 보드에서도 잘 동작하는 가벼운 알고리즘이다. 이런 필터들은 드론, 로봇, 무인 자율 주행 장치 등에 많이 활용되고 있다.  

2015년 9월 4일 금요일

자율제어를 위한 PID 제어 개념 및 개발 방법

이 글에서는 PID 제어에 대해서 정리한다. PID는 액추에이터, 자율주행 차량, 로봇, 센서값 보정 등에 필수적인 함수로 사용된다.

1. 개요
PID제어는 목표로한 물리량을 자연스럽게 수렴시키는 제어 방법이다. 우리는 PID제어를 본능적으로 사용하고 있다. 예를 들어, 차를 운전할 때, 차의 방향을 목표지점을 가늠하여, 좌우 회전을 적절히 하여, 원하는 목표까지 운전한다. 만약, 좌우회전을 너무 크게 한다면, 목표값보다 발산할 것이고, 너무 적게하면, 변화폭이 적어, 너무 늦게 목표값에 도달할 것이다. 다음 그림은 자율제어와 관련된 PID와 MPC (model-based predictive control) 방법을 보여준다.

PID제어는 자연스럽게 목표 물리량(속도, 방향, 온도 센서값 등)으로 수렴시킨다. 다음 영상은 이 내용을 보여준다. 


PID는 목표값과 현재값의 차이인 에러값을 줄여주는 방향으로, 현재값을 보정한다. 에러값은 PID값으로 변환된 후, 현재값을 보정하는 데 사용한다. PID각 항목은 다음과 같은 의미를 가진다.

P: Proportinal(비례) 
I: Integral(적분) 
D: Differential(미분) 

이 값들이 합해져서, 현재값을 보정하는 과정을 계속 반복해, 현재값이 목표로한 물리량으로 수렴하게 한다.
PID의 상세한 의미는 다음 영상을 참고한다. 


PID각 항목에 대한 계수값(Gain값)은 물리량을 만들어 내는 액추에이터 종류에 따라 적절히 설정해야 한다. 이 값을 적절히 설정해야, 자연스럽게, 빠르게 목표치까지 수렴하도록 할 수 있다. PID를 계산하는 소스코드는 다음과 같다. 보면, 알겠지만, 매우 간단한다.

double calculate_PID(double reference_input, double feedback_input)
{
   error = reference_input - feedback_input; // Compute the error
   error_sum = error_sum + error;         // Compute the error sum
   proportional_output = proportional_gain * error; // Compute the proportional output
   integral_output = integral_gain*error_sum; // Compute the integral output
   derivative_output = derivative_gain * (error - errorPrev); // Compute the derivative output

   // Compute the pre-saturated output
   presaturated_output = proportional_output + integral_output + derivative_output;
   return presaturated_output;
}

PID의 gain값을 설정하는 것에 따라서, 목표치까지 수렴하는 곡선 특성이 달라진다. 급하게 수렴할 수도 있고, 천천히 수렴할 수도 있다. 적절한 gain값을 계산하기 위해, 다음 수렴 곡선과 표 내용을 참고한다.



제어 동작 Kp 값 Ki 값 Kd 값
비례 제어 0.3~0.7 T/KL 0 0
PI 제어 0.35~0.6 T/KL 0.3~0.6 / KL 0
PID 제어 0.6~0.95 T/KL 0.6~0.7 / KL 0.3~0.45 T/K

gain값이 클때와 작을 때 수렴하는 곡선의 특성은 다음과 같다.


2. 개발
여기서는 아두이노로 액추에이터를 제어하는 방법을 정리한다. 우선, 아두이노로 모터를 PID 제어하는 방법을 아래 영상에서 확인해 보자.


아두이노 기반 PID제어 관련 소스는 다음 링크에서 확인할 수 있다. 라이브러리도 만들어져 있어, 간단히 함수만 사용하면 된다.

다이나믹셀을 제어하는 방법은 다음 영상에서 확인할 수 있다.


다이나믹셀 제어와 관련된 소스는 다음 링크를 참고한다.

3. 결론
PID는 자연스럽게 목표하는 위치, 속도, 방향, 온도 등 센서 값과 같은 물리량을 기계적으로 자동 제어하는 데 필수적인 알고리즘이다. 이를 이용하면, 기계가 목표 물리량까지 자연스럽게 수렴하는 자동 제어 장치를 만들 수 있다.

4. 레퍼런스
이 글은 아래 레퍼런스를 참고하였다.

2015년 9월 2일 수요일

레이저 커팅 기반 소품 디자인

이 내용은 아래 글을 참고로 하였다.

1. 개요
최근에는 많은 디자인을 GrabCAD같은 곳에서 다운로드 받아, 레이저커팅으로 재미있는 소품을 만들어 볼 수 있게 되었다. 본 글은 이와 같은 곳에서 모델을 다운로드하고, 레이저 커닝을 하는 것에 대한 내용을 간단히 다룬다.




2. 준비물
디자인을 위한 준비물은 다음과 같다.
  • 오토캐드
  • 라이노
  • 스케치업
  • 슬라이서 애드인
오토캐드는 2차원 모델링도구이며, 라이노나 스케치업은 3차원 모델링 도구이다. 보통, 오토캐드는 레이저 커닝 절단선을 다듬거나, 수정할 때 사용한다.


3. 레이저 커팅 순서
레이저 커팅 순서는 다음과 같다.

1. 3차원 모델링: 필수적인 단계는 아니다. 3차원 모델링을 하지 않고, 바로 2차원 절단면을 디자인해도 무방하다. 다만, 3차원적으로 형상이 복잡하다면, 3차원 모델링부터 해야 한다.
2. 3차원 모델링 단면 디자인: 3차원 모델링이 되어 있다면, 라이노나 애드인 도구를 이용해, 단면을 자동으로 추출할 수 있다.
3. 단면 디자인을 DXF파일로 변환: 레이저 커팅 기계는 커팅 데이터를 전송하는 소프트웨어가 있다. 그 레이저 커팅 구동 소프트웨어가 입력받는 대표적인 파일 포맷이 DXF파일 포맷이다. 오토데스크사가 개발한 산업계 표준 포맷이 DXF파일이므로, 오토캐드에서 손쉽게 편집할 수 있다.
4. DXF파일을 레이저 커팅 구동 소프트웨어에 Import
5. 레이저 커팅 레이어 편집: 입력된 DXF 도면 파일에는 절단해야할 선들, 레이저로 표시만 해야할 선들로 구분해야 한다. 또한, 절단선들도, 외곽 절단선과 내부 절단선을 구분해야 한다. 만약, 외부 절단선을 먼저 레이저 커팅하고, 내부 절단선을 그 이후에 레이저 커팅하면, 전체 절단면이 기계 바닥이 떨어질 수 있기 때문에, 내부 절단선을 레이저 커팅 못할 경우가 생긴다. 그러므로, 아래와 같은 순서로 절단선을 선택해, 레이어로 구분한다.
1) 내부 표시선 레이어
2) 내부 절단선 레이어
3) 외부 절단선 레이어
각 레이어의 레이저 출력을 조절해, 절단, 표시 수준으로 레이저 출력 및 속도를 설정한다.
6. 도면을 레이저 커팅 기계에 다운로드
7. 레이저 커팅 기계에 재료(MDF/아크릴 등) 적재
8. 레이저 커팅 기계 원점 설정
9. 레이저 커팅 기계가 커팅할 최대 프레임 영역 확인
10. 별 이상이 없다면, 레이저 커팅을 시작한다.

대부분의 레이저 커팅 기계는 이런 순서로 진행된다. 레이저 커팅시 불꽃은 매우 강하므로 장시간 바라보고 있지 말자. 당연하겠지만, 기계가 잘 동작되지 않는다고, 정지하지 않은 상태에서 손을 기계에 넣어서는 절대로 안된다.

4. 다운로드를 통한 레이저 커팅
여기서는, 간단한 집을 다운로드해서, 레이저 커팅을 해 본다. 다음 링크에서 작은 MDF 집을 다운로드 받는다. 

http://cartonus.com/small-wooden-houses/

다운로드된 파일 압축을 풀어보면, *.ai 이 생성된다. Adobe 일러스터 프로그램에서 이 파일을 열고, dxf로 export한다. 그리고, 앞의 순서대로, dxf파일을 레이저 커팅 기계 구동용 소프트웨어를 이용해, 레이저 커팅을 한다.

만약, 집의 크기를 수정하려면, 오토캐드에서 scale이나 offset명령 등을 이용해, dxf 도면을 수정해야 한다. 이 경우, MDF 두께를 고려해 디자인한, MDF면끼리 접한 모서리 부분의 두께 부위가 달라질 수 있으므로, 주의해야 한다. 

5. 3차원 모델 기반 레이저 커팅
육면체와 같은 단순한 3차원 모형은 라이노 등에서 단면 추출 등의 명령으로 손쉽게 2차원 DXF파일을 생성할 수 있다. 하지만, 복잡한 곡면 3차원 모형은 모델링 된 것을 얇게 절단해, 절단면 별로 DXF파일을 생성한 후, 이 DXF 도면을 각각 레이저 커팅해야 한다. 이 경우, 별도의 애드인 툴이 필요하다. 이와 관련해, 다음 영상을 참고한다. 


6. 마무리
레이저 커팅을 이용하면, 매우 재미있는 공작품을 만들 수 있다. 그리 어렵지 않으니, 시도해 보길 바란다.



node.js 기반 홈 오토메이션 서버 개발

아두이노와 라즈베리파이 기반으로 오토메이션 서버를 개발해 본다.

1. 개요
오토메이션 서버는 다음과 같은 기능을 가진다.
1) 온도 등 환경 센서를 취득해, 인터넷상으로 정보를 표현
2) 램프 등 주변장치를 인터넷상으로 켜고 끌 수 있음
3) Bluetooth로 접근할 수 있음
4) ProcessingJS로 그래픽 처리

2. 준비물
1) 온도, 습도, 광센서, 전류측정센서 및 아두이노 우노 보드
2) node.js 등 라이브러리 (이전 글 참고)
3) 릴레이 및 램프


3. 개발
이를 개발하기 위해, 온도, 습도, 광 센서 등이 필요하다.

1) 아두이노 코딩
// Code to measure data & make it accessible via 

// Libraries
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
#include "DHT.h"
#include <aREST.h>

// DHT sensor
#define DHTPIN 7 
#define DHTTYPE DHT11

// LCD display instance
LiquidCrystal_I2C lcd(0x27,20,4); 

// DHT instance
DHT dht(DHTPIN, DHTTYPE);

// Create aREST instance
aREST rest = aREST();

// Variables to be exposed to the API
int temperature;
int humidity;
int light;

void setup()
{
  // Start Serial
  Serial.begin(115200);
  
  // Expose variables to REST API
  rest.variable("temperature",&temperature);
  rest.variable("humidity",&humidity);
  rest.variable("light",&light);
  
  // Set device name & ID
  rest.set_id("1");
  rest.set_name("weather_station");
  
  // Initialize the lcd 
  lcd.init();
  
  // Print a message to the LCD.
  lcd.backlight();
  lcd.setCursor(1,0);
  lcd.print("Hello !");
  lcd.setCursor(1,1);
  lcd.print("Initializing ...");
  
  // Init DHT
  dht.begin();
  
  // Clear LCD
  lcd.clear();
}


void loop()
{
  
  // Measure from DHT
  temperature = (int)dht.readTemperature();
  humidity = (int)dht.readHumidity();
  
  // Measure light level
  float sensor_reading = analogRead(A0);
  light = (int)(sensor_reading/1024*100);
  
  // Handle REST calls
  rest.handle(Serial);
  
  // Display temperature
  lcd.setCursor(1,0);
  lcd.print("Temperature: ");
  lcd.print((int)temperature);
  lcd.print((char)223);
  lcd.print("C");
  
   // Display humidity
  lcd.setCursor(1,1);
  lcd.print("Humidity: ");
  lcd.print(humidity);
  lcd.print("%");
  
   // Display light level
  lcd.setCursor(1,2);
  lcd.print("Light: ");
  lcd.print(light);
  lcd.print("%");
  
  // Wait 100 ms
  delay(100);
}

2) 서버 node.js 코딩
// Modules
var express = require('express');
var app = express();

// Define port
var port = 3000;

// View engine
app.set('view engine', 'jade');

// Set public folder
app.use(express.static(__dirname + '/public'));

// Rest
var rest = require("arest")(app);
rest.addDevice('serial','/dev/tty.usbmodem1a12121', 115200);

// Serve interface
app.get('/', function(req, res){
  res.render('interface');
});

// Start server
app.listen(port);
console.log("Listening on port " + port);


// interface.js
var devices = [];

$.get('/devices', function( json_data ) {
  devices = json_data;
});

$(document).ready(function() {

  function updateSensors() {
    
    // Update light level and status
    $.get('/' + devices[0].name + '/light', function(json_data) {

      console.log(json_data.light);

      $("#lightDisplay").html("Light level: " + json_data.light + "%");    

      // Update status
      if (json_data.connected == 1){
        $("#status").html("Station Online");
        $("#status").css("color","green");    
      }
      else {
        $("#status").html("Station Offline");
        $("#status").css("color","red");     
      }

      $.get('/' + devices[0].name + '/temperature', function(json_data) {
        $("#temperatureDisplay").html("Temperature: " + json_data.temperature + "°C");
        
        $.get('/' + devices[0].name + '/humidity', function(json_data) {
          $("#humidityDisplay").html("Humidity: " + json_data.humidity + "%");
        });
      });
    });
  }

  setTimeout(updateSensors, 500);
  setInterval(updateSensors, 5000);


});