[40호]주파수를 읽어 색깔을 인지하고 나타내는 블루투스 스탠드
2016 ICT 융합 프로젝트 공모전 참가상
주파수를 읽어 색깔을 인지하고
나타내는 블루투스 스탠드
글 | 영남대학교 정창호
심사평
JK전자 AVR 8비트 마이크로컨트롤러를 이용한 RGB LED를 효과적으로 제어하는 부분과, 작품의 외관을 목재로 제작하여 전체적으로 완성도가 높은 제품이다. 하지만 작품의 창의성과 기술적인 난이도 부분에서 이미 인터넷 상에 많은 부분이 공개되어 있고 누구나 쉽게 생각할 수 있는 작품인 것 같다.
뉴티씨 매우 간단한 아이디어이지만, 끝까지 작품을 끝냈다는 점에서 점수를 많이 주게 되었다. 무엇이든 시작해서 끝까지 잘 마무리한다는 것은 엔지니어의 기본이지만, 매우 어려운 일이다. 또한, 매우 실용적인 아이디어로, LED의 색을 정한 뒤 고정하는 것과 같은 것은 실제로 해 보면 자꾸 변하는 색을 고정하여 쓰기 위함인데, 실제로 해 보면서 나온 아이디어로 볼 수 있다. 작품을 만들면서, 점점 좋은 작품을 만들고자 하는 아이디어가 나오게 되고, 이러한 것을 구현하는 과정을 통하여 엔지니어로서의 훈련을 하게 된다. 실현 가능한 아이디어를 만들고, 거기에 개인의 생각과 아이디어를 심으면서 구현을 완료한 결과로, 좋은 작품이다.
칩센 어디선가 본것 같은 느낌을 주지만, 완성도가 높고 깔끔하다.
위드로봇 색상을 사용자가 원하는 주파수로 변경할 수 있는 스텐드이며, 핸드폰으로 제어할 수 있게 만든 작품입니다. 목표한 대로 잘 제작되었고 잘 동작하지만 유사한 상용 제품을 많이 발견할 수 있다는 점에서 창의성 부분이 조금 아쉽습니다. 기왕 IoT 내용을 언급했으니 핸드폰으로 그 날의 온도나 날씨 정보를 받아 이러한 정보에 알맞게 불빛을 자동으로 조절하는 내용등이 더 추가되면 훨씬 완성도 높은 작품이 될 것 같습니다.
1. 작품 제목
주파수를 읽어 색깔을 인지하고 나타내는 블루투스 스탠드
2. 작품 개요
색깔마다 주파수를 인식해 그 색깔을 LED 로 나타나게 하고 블루투스를 이용해 제어를 할 수 있게 한 스탠드
3. 작품 설명
MCU인 AVR(atmega128)의 기능들을 이용해서 스탠드를 개발했습니다. LED는 ws2812b란 제품으로 RGB를 각각 따로 받아서 설정할 수 있습니다.
기본적으로 백색 등 켜기 및 끄기 뿐만 아니라, 다양한 색을 이용해 로맨틱한 분위기를 낼 수 있는 모드와, 색 감지 센서 TCS3200을 이용해 측정하고자 올려놓은 색깔의 RGB 주파수를 0.25초 주기의 오버플로 인터럽트로 받아 변수에 저장하고, 그 변수를 통해 해당 조건의 색깔을 찾아서 LED로 출력할 수도 있습니다.
또한 USART 통신으로 블루투스 모듈을 사용해 휴대폰 애플리케이션으로 온/오프 동작 및 여러 색깔로 제어할 수 있게 해놓았고, 인터럽트 버튼으로 밝기 조절 및 모드 변환 [일반 모드/색깔 인식 모드/로맨틱 모드]을 가능하게 해 놓았습니다.
인터럽트 버튼은 5개가 있으며 밝기 상승/하강, 모드 변경, 색깔 인식 사용, 색깔 고정하기 등이 있습니다. 전원은 5V USB 방식으로 제작해서 휴대폰 충전기나 요즘 많이 사용하는 휴대용 보조 배터리로 아무 곳에서나 쓸 수 있도록 유연함과 사용성을 높였습니다.
마지막으로 깔끔한 하드웨어를 위해 직접 목공소에서 목판으로 스탠드 외관을 만들어서 완성도를 높였습니다.
3.1. 주요 동작 및 특징
특징은 크게 5가지로 나눌 수 있습니다.
1. 모드 변경을 통한 다양한 색상 사용 가능
https://youtu.be/0UeR8BxKHg4
2. 블루투스를 이용한 스마트폰 제어
https://youtu.be/GU8ogxnFokA
3. 색 감지 기능
https://youtu.be/oG8BhlQcW_w
4. 5V USB를 통한 전원공급
5. 목공으로 만든 조립식 외관
3.2. 전체 시스템 구성
시스템이 시작이 되면 LED/인터럽트/칼라 센서/USART의 제어 시스템이 초기화됩니다. 그리고 ws2812b LED의 밝기 변수 light 값을 50 만큼, 백색광으로 ON 시킵니다. RGB 값이 각각 255까지 수치가 있고, 밝기 변수 light을 통해 R, G, B값 모두 50씩 주었으므로 최대 밝기의 1/5 이 되고, RGB가 각각 1:1:1의 비율로 섞였으므로 백색광이 나옵니다.
예를 들어 보라색의 불빛을 내고 싶다고 한다면 RGB를 1:0:1의 비율로 섞으면 되고, 밝기를 제어하려면 비율을 똑같이 준 채로 0~255의 수치를 입력하면 됩니다.
그 후 색감지 센서로 지속적으로 현재 색깔의 RGB 주파수를 측정합니다. 나중에 아래에서 설명을 하겠지만 COLOR CHANGE 모드로 실행되었을 때 각 색깔 고유의 각 RGB마다 주파수를 감지해서 그 색깔과 맞는 색깔을 LED로 나타낼 수 있습니다.
그리고 블루투스의 제어를 시작합니다. 블루투스의 제어는 따로 무언가를 하지 않아도 휴대폰으로 연결만 하면 바로 받을 수 있도록 해 놓았습니다. 휴대폰으로 A~G의 문자를 출력 시 receive라는 변수에 저장이 되며 변수에 따라 각각 흰/빨/파/초/보/OFF/무지개 색깔이 나오게 되며 직접 조작보다 블루투스 조작에 우선순위를 두어서 앞쪽에 넣어두었습니다.
OFF를 따로 해주지 않는다면, receive의 변수에 계속 문자가 들어가 있게끔 해두어서 오프라인 조작과 섞이지 않고 블루투스가 우선순위가 되어서 작동이 됩니다. 수신받은 문자가 없거나, 수신받아서 블루투스로 조작된 후에는 모드를 확인하게 됩니다.
모드는 statem이라는 변수에 저장이 되어있으며 3가지 모드가 열거되어있는데 NORMAL/ROMANTIC/COLOR CHANGE 모드가 있습니다. 최초에는 NORMAL 모드로 설정이 되어있고 1번 버튼을 누르면 인터럽트를 통해서 NORMAL 모드와 ROMANTIC 모드 두 가지만 스위칭 할 수 있게 해놓았습니다. COLOR CHANGE 모드는 3번 버튼을 누르면 사용할 수 있으며, 정확한 주파수를 측정하는데 필요한 색 감지 센서의 주변 불빛이 동시에 켜집니다.
첫 번째 모드인 NORMAL을 실행하면 백색광이 50의 밝기로 실행되는데 여기서 4번 버튼과 5번 버튼을 이용해 밝기를 제어합니다. 4번 버튼은 밝기 하강이고 5번 버튼은 밝기 상승인데 각각 25의 수치만큼 올라가고 내려갑니다. 25만큼의 수치로 밝기를 제어하게 되면 255/25는 10이 나오므로 밝기를 10단계로 구분해서 bright라는 변수로 사용하고 있습니다. bright의 범위를 0~10으로 제한해서 LED에 과부하가 오는 것을 방지했습니다.
두 번째 모드인 ROMANTIC은 LED가 구현할 수 있는 색깔을 조합해서 연속해서 나타내어 로맨틱한 분위기를 연출할 수 있도록 도와줍니다.
마지막 세 번째 모드 COLOR CHANGE는 위에서 언급한대로 3번 버튼을 누르면 색 감지 센서의 라이트가 켜지면서 시작되는데, 센서 위에 색깔을 올려놓으면 시스템 초기에 설정해 놓았던 RGB 주파수 측정값에 따라 색깔이 변하게 됩니다. 색깔을 올려놓은 채 2번 버튼을 누르게 된다면, 색 감지 센서의 라이트가 꺼짐과 동시에 현재 올려놓은 색깔이 고정이 되어 계속 사용할 수 있습니다.
3.3. 개발환경
개발 Tool : AVR studio 4
언 어: C언어
4. 단계별 제작 과정
우선 가장 핵심 부품인 LED를 소개하겠습니다.
ws2812b라는 제품으로 가로/세로 5.0mm입니다. (그림1)
이것을 직접 납땜을 통해 제가 원하는 모양으로 만들기 위해서 양면 기판을 사용했습니다. 그림 2의 회로도에서 보면 첫 번째 LED에 MCU에서 DIN을 통해 신호를 넣어주면 DOUT을 통해 다음 LED의 DIN으로 들어가는 구조로 0.5cm의 작은 LED를 모두 납땜으로 연결해주어야 합니다. 그림 3에서 20개의 LED를 납땜 완성한 것 앞뒤를 보여드리지만, 하나만 잘 못 납땜되어도 사용 못하는 불상사로 인해서, 최종 작업 때는 LED 개수를 줄여 9개로 진행했습니다. 충분히 색감을 보여드리기엔 넉넉한 개수라고 판단했습니다.
캐패시터는 안정적으로 전압을 공급하기 위한 장치로, LED 2~3개당 1개씩 달아 주었습니다. 그리고, 사용하기 위한 데이터 이동 시간 타이밍이 그림 4에 있습니다. 이 데이터 이동 시간 타이밍이 왜 있냐면 ws2812b는 다른 LED와는 다르게 그저 전류만 제공해서는 불빛이 나오지 않고, 그림 5의 24bit 데이터 구성을 해주어야 합니다.
넣어주는 데이터는 각 순서대로 G bit 8개-> R bit 8개-> B bit 8개 순서대로 채워지는데 각 8bit라서 2^8승 = 256이 되므로 RGB를 0~255로 맞춰 줄 수 있는 원리가 됩니다.
이 데이터를 채우기 위해 필요한게 그림 4(상)의 데이터 이동 시간인데 저는 그림 4(하) T0H, T1H 방식을 이용해서 1과 0 데이터를 넣어주었습니다. HIGH -> LOW로 바꾸는데, HIGH 의 유지 시간을 제어해서 각 데이터 비트에 1을 주거나 0을 줄 수가 있습니다.
그림 4(하)를 보면 앞의 HIGH 부분이 0.4us라면 0이 입력되고, 0.8us라면 1이 입력이 됩니다. 그래서 중간 값인 0.6us 값을 기준으로 전후로 나뉘어 0과 1이 입력된다고 추측하였습니다.
단순 코드만으로 us 단위의 초를 제어하기는 쉽지 않기 때문에 어셈블리어를 사용했습니다.
‘asm volatile(“NOP”)’라는 코드를 avr studio로 사용하게 되면 어셈블리어의 메모리 접근을 통해 1클럭 쉬라는 명령을 내리게 됩니다. 오실로스코프 같은 정밀한 기계를 개인이 소유하기는 쉽지가 않아서, 가상 시뮬레이션을 통해서 코드를 사용한 결과 atmega128에서의 1클록은 0.065us라는 결과를 알 수 있었습니다.
즉, 24bit 데이터에 1을 주고 싶으면 HIGH를 준 후 12클록(0.065us*12=0.78us) 만큼 딜레이 후 LOW를 주면 되고, 0을 주고 싶으면 6클록(0.065*6=0.39us) 만큼 딜레이를 주면 된다는 것을 알아냈습니다. 이것을 각 1byte(8bit) 만큼 묶어서 처리하면 G->R->B 순서대로 내가 원하는 수치만큼 줄 수 있게끔 코드를 짰습니다. 그래서 다양한 색의 LED를 사용할 수 있습니다.
다음은 색상 감지 센서의 소개 및 개발 내용입니다. 사용한 센서의 명칭은 TCS3200으로 그림 7과 같이 되어있습니다. 사실 실제로 색을 감지하는 것은 아니고 위에 올려놓은 색상의 주파수를 감지하는 것입니다.
그래서 장소와 불빛의 변수를 줄이기 위해 주변에 LED를 켰다가 껐다가 할 수 있는 장치가 있고, 인터럽트를 통해서 색상 감지 기능을 사용하면 그림 8처럼 주변의 불이 켜지고 중지되면 꺼지게 해놓았습니다.
그림 7의 부분들을 소개하자면 S0~S3는 기능을 조정하는 부분이고, OE는 OUT을 통해 주파수를 내보낼 수 있게 해주는 기능이며 Active LOW 표시이므로 GND와 그라운드에 연결해 줍니다. 그리고 VDD는 5V에 연결합니다.
S0~S1은 그림 9와 같이 기능 중에서도 주파수의 스케일링을 조정합니다. 저는 오차 범위를 최대한 줄이기 위해 2%로 했습니다. 2%로 스케일링시 최소 10~12kHz의 주파수를 가지게 됩니다. 이 주파수는 “OUT”이라고 적혀있는 6번 PIN으로 전달됩니다. 그리고 S2~S3는 감지하는 색상을 표시하게 됩니다.
그림 10을 보시면, Clear와 Red, Blue, Green 3원색의 주파수를 감지해 모든 색을 감지할 수 있습니다.
특정 색깔마다의 주파수를 감지하기 위해서 타이머/카운터 3의 프리스케일을 외부 클록 소스의 상승 에지로 바꾸어서 TCNT3으로 색 감지 센서의 OUT에서 나오는 주파수를 받게 합니다. 그리고 주파수는 1초당 진동수를 뜻하기 때문에 atmega128의 타이머/카운터 1을 이용한 오버플로 인터럽트를 고속 PWM, 프리스케일 64 그리고 OCR1A를 624로 맞춰놓습니다. 그렇게 된다면 16Mhz에서 625*64/16000000 = 0.0025s 주기로 오버플로 인터럽트가 걸리기 때문에 오버플로 인터럽트를 매 100번마다 S2~S3을 조정해서 Red▶Blue▶Green 순서대로 색 감지를 바꾼 뒤, TCNT3*4(0.25s*4=1.0s)를 현재 측정하는 색깔 주파수의 변수에 저장 후 TCNT3을 다시 0으로 초기화해주면 0.75s라는 시간에 Red/Blue/Green의 주파수를 모두 얻게 됩니다.
그림 11의 위쪽 그림을 보면 RGB의 주파수를 읽어내고 있는데 색 감지 센서 장치 위쪽에 색깔을 올려서 주파수를 알아낸 뒤 RGB의 범위를 설정해 놓으면 그림 11의 아래처럼 색깔을 감지하여 알맞은 24bit 데이터를 LED로 보내주면 같은 색의 LED가 나오게 됩니다. 모드 변경, 밝기 변경 기능들은 그림 12처럼 버튼으로 만들어 인터럽트로 구현을 하였습니다.
블루투스는 그림 13처럼 HC-06 모델을 달아서 USART 통신을 이용해 bps를 9600으로 맞춘 뒤 큐알고리즘을 이용해 FIFO 순서대로 섞이지 않도록 문자를 수신받아서 문자에 맞는 작동을 하게끔 설계하였습니다.
그리고 전원 부분은 최대한 누구나 편리하게 사용할 수 있도록 휴대폰 충전기나 휴대용 보조 배터리로 사용 가능한 5V USB 방식으로 제작하였습니다. 그림 14를 보시면 집에서 많이 남는 USB를 잘라서 빨간 선은 +, 검은 선은 -로 납땜했습니다. 마지막으로 외관은 설계 후에 목공소로 가서 직접 제작하여 완성도를 높였습니다.
5. 소스코드
————–메인함수————–
#define F_CPU 16000000UL //내부 CPU 클록 16000000Hz
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdio.h>
#include <util/delay.h>
#include “usart.h”
#define LED_SETTING DDRA |=1<<PA0 //LED 연결 핀을 출력 상태로
#define S0 PB0 // 색 감지 센서 주파수 % 조정
#define S1 PB1 // 색 감지 센서 주파수 % 조정
#define S2 PB2 // 색상 조정
#define S3 PB3 // 색상 조정
#define S4 PB4 // TCS3200 라이트
#define nop2 {asm volatile(“ NOP “); asm volatile(“ NOP “);} // 어셈블리어 2클록 딜레이
#define nop6 {nop2; nop2; nop2;} // 어셈블리어 6클록 딜레이
#define nop12 { nop6 ; nop6 ;} // 어셈블리어 12클록 딜레이
#define ws2812b_1 (PORTA |= (1<<PA0)) // LED 켜기
#define ws2812b_0 (PORTA &= ~(1<<PA0)) // LED 끄기
volatile enum state_m{NORMAL,ROMANTIC,COLORCHANGE,BLUETOOTH} statem=0; // 모드 선언 열거
volatile unsigned int light=50; // 밝기 변수 [ 0~255 ]
volatile unsigned int bright=2; // 밝기 변수 [light/25] [0~10]
volatile unsigned int play=0; // 상태 사용 변수
volatile unsigned int number=0; // 타이머 오버 플로 1s 단위를 위한 변수
volatile unsigned int value=0; // 주파수 측정 변수
volatile unsigned int shift; // ROMANTIC MODE 색깔변경
volatile unsigned int t;
volatile unsigned int red_fre; // RGB R 주파수
volatile unsigned int blue_fre; // RGB B 주파수
volatile unsigned int green_fre; // RGB G 주파수
volatile enum state_t {RED , BLUE , GREEN} statet=RED ; // 주파수 열거
volatile char led[13][3];
volatile char led_romantic[156][3]={
{255, 0, 0},{245, 0, 10},{235, 0, 20},{224, 0, 31},{214, 0, 41},{204, 0, 51},{194, 0, 61},{184, 0, 71},{173, 0, 82},{163, 0, 92},
{153, 0,102},{143, 0,112},{133, 0,122},{122, 0,133},{112, 0,143},{102, 0,153},{ 92, 0,163},{ 82, 0,173},{ 71, 0,184},{ 61, 0,194},
{ 51, 0,204},{ 41, 0,214},{ 31, 0,224},{ 20, 0,235},{ 10, 0,245},{ 0, 0,255},{ 0, 0,255},{ 0, 10,245},{ 0, 20,235},{ 0, 31,224},
{ 0, 41,214},{ 0, 51,204},{ 0, 61,194},{ 0, 71,184},{ 0, 82,173},{ 0, 92,163},{ 0,102,153},{ 0,112,143},{ 0,122,133},{ 0,133,122},
{ 0,143,112},{ 0,153,102},{ 0,163, 92},{ 0,173, 82},{ 0,184, 71},{ 0,194, 61},{ 0,204, 51},{ 0,214, 41},{ 0,224, 31},{ 0,235, 20},
{ 0,245, 10},{ 0,255, 0},{ 0,255, 0},{ 10,245, 0},{ 20,235, 0},{ 31,224, 0},{ 41,214, 0},{ 51,204, 0},{ 61,194, 0},{ 71,184, 0},
{ 82,173, 0},{ 92,163, 0},{102,153, 0},{112,143, 0},{122,133, 0},{133,122, 0},{143,112, 0},{153,102, 0},{163, 92, 0},{173, 82, 0},
{184, 71, 0},{194, 61, 0},{204, 51, 0},{214, 41, 0},{224, 31, 0},{235, 20, 0},{245, 10, 0},{255, 0, 0},
//
{255, 0, 0},{245, 0, 10},{235, 0, 20},{224, 0, 31},{214, 0, 41},{204, 0, 51},{194, 0, 61},{184, 0, 71},{173, 0, 82},{163, 0, 92},
{153, 0,102},{143, 0,112},{133, 0,122},{122, 0,133},{112, 0,143},{102, 0,153},{ 92, 0,163},{ 82, 0,173},{ 71, 0,184},{ 61, 0,194},
{ 51, 0,204},{ 41, 0,214},{ 31, 0,224},{ 20, 0,235},{ 10, 0,245},{ 0, 0,255},{ 0, 0,255},{ 0, 10,245},{ 0, 20,235},{ 0, 31,224},
{ 0, 41,214},{ 0, 51,204},{ 0, 61,194},{ 0, 71,184},{ 0, 82,173},{ 0, 92,163},{ 0,102,153},{ 0,112,143},{ 0,122,133},{ 0,133,122},
{ 0,143,112},{ 0,153,102},{ 0,163, 92},{ 0,173, 82},{ 0,184, 71},{ 0,194, 61},{ 0,204, 51},{ 0,214, 41},{ 0,224, 31},{ 0,235, 20},
{ 0,245, 10},{ 0,255, 0},{ 0,255, 0},{ 10,245, 0},{ 20,235, 0},{ 31,224, 0},{ 41,214, 0},{ 51,204, 0},{ 61,194, 0},{ 71,184, 0},
{ 82,173, 0},{ 92,163, 0},{102,153, 0},{112,143, 0},{122,133, 0},{133,122, 0},{143,112, 0},{153,102, 0},{163, 92, 0},{173, 82, 0},
{184, 71, 0},{194, 61, 0},{204, 51, 0},{214, 41, 0},{224, 31, 0},{235, 20, 0},{245, 10, 0},{255, 0, 0}
}; //로맨틱 모드
void byte_out(int d){
char i;
for(i=0;i<8;i++){
if(d&0×80){ ws2812b_1; nop12; ws2812b_0; }
else { ws2812b_1; nop6; ws2812b_0; }
d<<=1;
}
} // LED 색상 제어
ISR(INT2_vect) // 색상 감지 기능 OFF , 색깔 고정 (4번 버튼)
{
play=1;
PORTB &= ~(1<<S4) ;
}
ISR(INT3_vect) // 현재 스탠드의 모드를 바꾸어줌 (5번 버튼)
{
for(int x=0 ; x<9 ; x++)
{
byte_out(0);
byte_out(0);
byte_out(0);
}
_delay_ms(50);
receive=0;
bright=2;
light=50;
statem++;
if(statem>1)
statem=0;
_delay_ms(500); //스위치 채터링 방지용 딜레이
}
6. 회로도
[40호]초미니 신형 센서 3종 출시
SFH7050, LM74 심박체온센서 BIO Sensor (S-PPG-100) | BME280, Si1132 UV, Light, Humidity, Pressure 측정센서 ENVIRON Sensor (S-ENV-100) | BME280, MPU-9255 GyroAccel+Compass+Barometer+ Temperature+ Humidity Module (S-GACB10-100) |
주식회사 로비텍
초미니 신형 센서 3종 출시
로봇 및 센서 전문 기업 로비텍은 심박센서와 체온센서가 실장된 Bio Sensor와 기압, 습도, 온도, 자외선지수, 조도를 측정하는 ENVIRON Sensor, 그리고 3축-자이로, 3축-가속도, 지자기에 기압까지 측정하는 GACB10+의 초미니 통합 센서 3종을 출시했다.
Bio Sensor는 SFH7050와 LM74센서가 실장되어 심박과 체온측정이 가능하며, SPI 통신을 사용한다. 채용된 두 센서 모두 접촉식으로 손가락을 가져다 대면 심박수와 체온을 측정할 수 있는 센서이다. 이 센서를 활용해 심박수와 체온을 관찰하는 스마트기기 개발이나 스마트폰과 연동하는 헬스케어앱 등을 개발해볼 수 있다. ENVIRON Sensor는 BME280과 Si1132센서가 실장되어 기압, 습도, 온도, 자외선 지수, 조도를 측정하고, I²C 통신을 사용한다.
채용된 센서들은 저전력으로 동작하며, 두 센서를 통해 다양한 센서값을 제공받아 날씨, 환경 모니터링 등으로 응용해 볼 수 있다. GACB10+는 MPU-9255와 BME280을 결합하여 3축-자이로, 3축-가속도, 지자기와 기압, 온/습도를 측정할 수 있는 제품이다. MPU-9255는 `MPU-6050`과 ‘AK8963’ 두 가지 칩을 결합한 InvenSense사의 패키지형 시스템(SiP)으로 자이로, 가속도, 지자기를 통합한 9축 모션 퓨전 센서이고, BME280은 Bosch사의 기압측정 센서로 온도와 습도를 동시에 감지한다.
출시한 3종의 신형 센서들은 개발없이 바로 사용 가능한 2CAN 시리즈로 이어서 출시할 예정이다.
Specifications
Bio Sensor
· 심박센서 : Wavelength of emitter – 적색 : 660nm / 녹색 : 530nm / 적외선 940nm, 22Bit 분해능
· 체온센서 : 최대 110℃ 측정가능, 0.0625℃ 온도값 정밀도 및 130Bit 분해능
· Interface : 2Mhz, SPI 통신
· Application : 심박측정기, 맥박 산소측정기, 모바일 디바이스, 스마트시계
ENVIRON Sensor
· 기압센서 : 300~11000hPa, 12Bit
· 습도센서 : 0~100%, 16Bit
· 온도센서 : -40℃~ +85℃, 12Bit
· 조도센서 : 최대 1~128klx, 16Bit
· Interface : 3.4Mhz, I²C 통신
· Application : 스마트폰, 태블릿, 휴대용 가전제품, 디스플레이 백라이팅 제어
GACB10+
· Gyroscope : 3축 각속도 센서, 풀스케일 ±250, ±500, ±1000 및 ±2000°/초의 범위 및 통합 16bit ADC
· Accelerometer : 3축 가속도 센서, 풀 스케일 ±2g, ±4g, ±8g, ±16g 통합 16 비트 ADC
SFH7050, LM74 심박체온센서 BIO Sensor (S-PPG-100) 상세 구경하러 가기
BME280, Si1132 UV, Light, Humidity, Pressure 측정센서 ENVIRON Sensor (S-ENV-100) 상세 구경하러 가기
BME280, MPU-9255 GyroAccel+Compass+Barometer+Temperature+ Humidity Module (S-GACB10-100) 상세 구경하러 가기
[40호]두이노 솔루션 Arduino+Bluetooth Kit 출시
㈜펌테크
블루투스를 통해 스마트폰으로 제어하는
아두이노 솔루션 Arduino+Bluetooth Kit 출시
무선 솔루션 전문 개발 업체인 펌테크(대표 노택종)에서 최근 아두이노 솔루션 Arduino+Bluetooth Kit를 출시했다.
Arduino+Bluetooth Kit는 아두이노 UNO 보드에 블루투스 쉴드를 장착하여 스마트폰과의 블루투스 통신을 가능하게 한 후 LED, FND, SWITCH 등 총 12가지의 제어용 쉴드를 자유롭게 제어할 수 있도록 구성된 제품이다.
블루투스 쉴드는 소켓 형태의 제품으로 펌테크 블루투스 2.1 제품인 FB153BC를 장착하게 되면 Bluetooth Classic으로 동작되어 안드로이드 무료 제공 앱(블루텀)을 통해 제어용 쉴드를 동작할 수 있으며, BT4.1 제품인 FBL780BC_serial을 장착하게 되면 BLE로 동작되어 펌테크에서 개발한 BLE 전용 앱을 통해 제어용 쉴드를 동작시킬 수 있다.
Arduino+Bluetooth Kit는 블루투스 2.1과 4.1 BLE 통신을 이용하여 스마트폰으로 제어가 가능한 제품이며, 아두이노를 처음 접하는 사용자들도 쉽게 접근할 수 있도록 모든 제품에 대해 그림 위주의 퀵가이드를 제공한다. 또한 아두이노 스케치용 소스 코드 및 소스 분석 문서를 제공함으로써 누구나 쉽게 활용할 수 있다는 장점이 있다. 특히, 전자 하드웨어의 가장 기초인 LED, SWITCH, RELAY, MOTOR, SENSOR, PWM 등을 활용한 제어용 쉴드를 구성하여, 아두이노를 활용한 다양한 하드웨어 설계 및 사용 방법을 학습할 수 있어서 대학교 또는 교육 기관의 교재로 사용하기에도 최적의 제품이다.
Kit 제품구성은 아두이노 우노, 블루투스 쉴드, BT모듈, BLE모듈, LED, FND, SWITCH, LED+SWITCH, RELAY, MOTOR, PWM, SENSOR, LCD, Graphic LCD 쉴드로 구성된 LITE 제품과, 아두이노를 통해 키보드의 눌림 값과 마우스의 변화 값을 스마트폰 화면에 표시해 주는 HID Shield와 스마트폰에서 재생한 음원을 아두이노에 연결된 헤드폰, 또는 외부 스피커로 재생할 수 있는 A2DP Shield가 추가된 PRO 제품으로 구분된다.
끝으로 펌테크 담당자는 “이번에 출시한 Arduino+Bluetooth Kit는 펌테크 아두이노 솔루션의 첫 시작 제품으로 블루투스 통신을 사용하여 스마트폰으로 제어가 가능한 아두이노 솔루션들을 조만간 계속해서 출시할 예정”이며, “사용자들을 위한 블루투스, 아두이노 무료 교육도 매월 진행한다” 고 밝혔다.
제품 및 교육에 관한 자세한 내용과 일정은 펌테크 홈페이지 www.firmtech.co.kr에서 확인이 가능하다.
Arduino+Bluetooth Kit (PRO) 상세 정보 확인하러 가기
Arduino+Bluetooth Kit (LITE) 상세 정보 확인하러 가기
[40호]립모션을 이용한 로봇 팔 제어 시스템 구현
2016 ICT 융합 프로젝트 공모전 참가상
립모션을 이용한 로봇 팔 제어 시스템 구현
글 | 부경대학교 공영훈, 김지수, 홍다운, 황호연, 박동주, 황신실
심사평
뉴티씨 실제로 구현해보면, 매우 어려운 로봇팔에 도전하였으며, 또한 립모션을 카메라를 통해 인식하여 실제 로봇의 움직임에 적용하는 등 실험이 성공적으로 된 점 등이 인상적이다. 다만, 구현의 정도에 오차가 보이고 있어서, 그러한 부분에 대한 보강작업 등이 향후 필요할 것으로 보이며, 앞으로 좀 더 보완한 후에 다시 도전하면 더 좋은 점수를 받을 수 있을 것으로 보인다. 현재도 매우 높은 점수를 받았다.
칩센 수많은 로봇팔 회사들과 개인이 시도한 분야다. 완성도가 더 떨어지는 부분은 어쩔 수 없는 것 같다.
위드로봇 leap motion을 이용해 실제 로봇팔과 인터페이스하는 작품으로 단순한 연결에 그치지 않고, UVC 카메라를 이용해 원격지의 상황을 모니터링할 수 있는 환경을 구성한 점이 훌륭합니다. 실험 결과에 대한 정량적 분석이 좀 더 명확하면 더 좋은 보고서가 될 것 같습니다.
1. 작품 제목
립모션을 이용한 로봇 팔 제어 시스템 구현
2. 작품 개요
현대 기술이 발전하면서 전자 제품과 사용자가 상호 작용할 수 있는 다양한 기술이 연구되고 있다. 자연스러운 사용자 조작환경이라고 할 수 있는 NUI(Natural User Interface)는 마우스나 키보드와 같은 입력장치를 이용하지 않고 신체의 동작을 사용한다. 그 중 손 동작을 이용한 방법은 컴퓨터 제어, 문자 입력, 증강 현실 등 다양한 분야에서 연구 중이며, 손 동작을 인식해 로봇을 제어하는 응용 프로그램이 제안되었고, 실시간으로 손 동작을 인식하기 위한 장치들의 개발이 꾸준히 시도되고 있다.
손 동작을 인식하기 위한 여러 가지 장치 중 Leap Motion은 최근 몇 년간 여러 분야에서 다양하게 사용되고 있는 모션 인식에 큰 혁신을 가져왔다. 손 동작을 3D 형태로 획득할 수 있는 이 장치는 1/100mm 단위의 미세한 움직임까지 감지하며 150도 반경에서 손가락의 움직임을 290 fps(frame per second)로 감지해 사람의 손과 손가락의 섬세한 움직임을 포착할 수 있는 장치이다. 본 작품은 이러한 Leap Motion을 이용해 사람의 손 동작을 그대로 모방해 따라하는 로봇 팔을 원격 제어할 수 있는 시스템을 목표로 제작되었다.
3. 작품 설명
3.1. 주요 동작 및 특징
본 작품은 사람의 손동작을 모방해 그대로 따라하는 로봇 팔이다. 립 모션을 이용해 사람의 손 동작을 디지털 정보로 획득한다. 그 뒤 역기구학을 적용해 로봇의 End-Effector의 이동 경로를 손의 이동경로와 일치할 수 있게 제작하였다. 그리고 손목의 움직임을 표현하기 쉽게 로봇 팔의 관절을 조립하였다. 또한 유저 인터페이스를 이용하여 전체 시스템을 관리할 수 있다. 로봇 팔에 장착된 카메라에서 획득한 영상을 유저 인터페이스에 출력하여 사용자에게 로봇의 주변 환경에 관련된 정보를 제공할 수 있다.
3.2. 전체 시스템 구성
전체 시스템 구성은 그림 1과 같다. 유저 인터페이스와 로봇 팔로 구성되어 있고 전체 시스템은 무선 랜을 이용해서 원거리 통신을 구현한다. 유저 인터페이스는 닷넷을 이용해 제작하였고, 립 모션 센서가 획득한 손 동작과 관련된 디지털 정보를 출력한다. 우리는 해당 데이터에 역기구학을 적용하여 로봇 팔 End-Effector의 끝점을 계산해주었다. 그림 1의 MCU는 ATmel사의 ATmega128을 이용하며 로봇 팔에 장착되어 각종 모듈을 제어한다. 그림 1의 UVC Camera는 로봇 팔에 부착된 비전센서를 나타내고 영상을 획득할 수 있다. Raspberry Pi는 비전 센서를 이용해 획득한 영상을 무선 랜을 통해 유저 인터페이스로 전송하고 MCU 1과 통신한다.
3.2.1. Leap Motion
그림 2는 Leap Motion의 직각 좌표계를 나타내며, 거리 단위는 mm이고 각도 단위는 rad이다. 내장된 적외선 LED와 적외선 카메라를 이용해 열 손가락과 두 손의 움직임을 인식하여 사용자에게 제공한다. Leap Motion을 사용하기 위해선 PC와 연결하고, 제공되는 API를 이용해 제작한 유저 어플리케이션을 이용한다. 로봇 팔을 제어하기 위해서 손바닥 좌표, 손의 양쪽 끝의 길이, 손바닥의 vector를 이용하여 로봇 팔의 동작을 생성하였다.
3.2.2. 로봇 팔
그림 3은 사람의 손 동작을 그대로 따라 할 수 있게 구성된 5축 로봇 팔이다. Leap Motion이 획득한 손바닥의 좌표는 역기구학을 통해 관절의 각도로 변환되고 블루투스를 이용해 로봇 팔로 전달된다. 로봇 팔에 장착된 카메라는 로봇의 주변 환경을 획득할 수 있고, 사용자 어플리케이션을 통해 출력할 수 있다. 로봇 팔에 장착된 이동기구부는 차륜구동방식을 사용하고, 사용자 어플리케이션에서 주행을 제어할 수 있다. 영상정보와 주행 제어 정보를 주고받기 위해 TCP/IP 소켓 통신을 이용하였다.
로봇 팔에 적용된 역기구학은 그림 4와 같은 방법을 통해서 계산하였다. 2축 로봇 팔에 대한 계산을 통해 2축의 각도를 계산하고, 그 후 Z좌표를 적용하여 3차원 공간을 구성하였다. 역기구학을 계산하고나면 2가지 해가 존재한다. 그 중 우리는 더 큰 값이 나올 수 있도록 해를 정하였다.
3.2.3. Embedded Board
Embedded Board역할을 할 Raspberry Pi는 무선 랜에 연결하고 UVC 카메라에서 획득한 영상데이터를 윈도우 Application으로 전송하며 ATmega128의 모드 관리에 사용된다. ATmega128보다 편리하여 무선 랜을 이용하고 영상데이터를 획득하기 위해 라즈베리파이를 이용하였다. 카메라의 영상을 획득하기 위해 V4L2을 이용한다. V4L2는 영상을 획득하기 위한 Linux 드라이버로 획득한 영상을 RGB형태로 배열에 담을 수 있다. 우리는 영상 데이터의 전송에서 속도 대신 안정성을 선택하였고, TCP 소켓 통신을 이용하여 영상을 전송한다.
UDP 소켓 통신은 ATmega128의 모드 설정 시 사용한다. 카메라에서 획득한 영상이 TCP 소켓 통신을 이용하여 윈도우 Application으로 전송 되면 사용자는 윈도우 Application에서 이동형 로봇 팔의 현재 상태를 영상을 통해 확인할 수 있으며, 사용자가 현재 필요한 모드를 결정하고 모드 변경의 명령을 내리면 UDP 소켓 통신을 통해 Raspberry Pi로 전달되고 전달된 명령은 Raspberry Pi에서 ATmega128로 I2C를 통해서 전송된다. I2C는 Raspberry Pi를 Master로 설정하여 구동한다.
3.3. 실험 및 결과
그림 5는 실험 환경이다. 종이컵을 집어 옆 박스로 옮길 때 립 모션을 통해 획득한 손바닥의 이동경로와 로봇 팔 End Effector의 이동경로를 확인하였다.
그림 6은 주어진 실험 환경에서 손바닥의 이동경로와 로봇 팔 End Effector의 이동경로를 비교해 본 그래프이다. 립 모션 센서에 손바닥이 검출되지 않을 때 로봇 팔은 주어진 기본 자세를 취한다. 립 모션 센서가 손바닥을 검출하게 되면 로봇 팔은 사람 손바닥의 위치를 추적한다. 그래프의 시작과 끝에서 확인할 수 있는 오차는 로봇 팔이 기본자세에서 손바닥의 이동경로를 추적해가면서 생기는 오차이다.
그림 7은 이동로봇의 주행 실험 결과를 나타낸다. 기준속도는 8rad/s ~ 11rad/s이며 기준속도를 일정 시간 간격으로 증가시켰을 때 기준속도와 양쪽 모터 사이에 오차가 발생하지만, 양쪽 모터의 각속도가 증가하는 모습을 확인할 수 있었다.
3.4. 결론
본 작품에서는 립모션을 이용해 손동작을 획득하고 그 손 동작을 그대로 모방해 따라하는 로봇 팔의 제어 시스템을 구현하였다. 립 모션을 로봇 제어에 사용할 수 있었고, 윈도 애플리케이션을 통해 로봇을 이동시킬 수 있었다. 구현한 로봇 제어 시스템은 비전센서를 이용해 로봇의 주변 상태를 파악한다. 또한, 주행 실험 결과에서 기준 입력과 양쪽 모터의 각속도 사이에 오차가 발생하는 것을 확인하였다. 이러한 점을 보완하기 위해 높은 성능의 센서를 장착해 현재 상태를 파악하고 각속도 오차를 줄이기 위한 연구를 진행할 것이다.
3.5. 개발환경
1. visual studio 2015 community
- .net & C#, Leap Motion Sensor
2. IAR Workbench
- ATmega128
3. Raspbian
- Raspberry pi
4. 단계별 제작 과정
4.1. 유저 인터페이스 제작과정
4.1.1. 초기모델 1
4.1.2. 초기모델 2
4.1.3. 개선된 모델
5. 기타
5.1 소스코드
5.1.1. .net
namespace LeapSerialTest
{
public partial class FrameDataForm : Form, ILeapEventDelegate
{
int CamMaxBufSize = 230400;
byte t1_flag = 0, mode_flag = 1, IP_flag = 0, SockOpen_flag = 0;
string raspberry_ip =null, pc_ip=null, SerialRxData=null;
private Controller controller;
private LeapEventListener listener;
Bitmap bmp = new Bitmap(width: 320, height: 240, format:
System.Drawing.Imaging.PixelFormat.Format24bppRgb);
SerialPort SP = new SerialPort();
private void FrameDataForm_Load(object sender, EventArgs e)
{
string[] PortNameArr = null;string PortName = null;
int PortIndex = -1;
PortNameArr = SerialPort.GetPortNames();
do
{
PortIndex += 1;
SerialPort_cbbox.Items.Add(PortNameArr[PortIndex]);
}
while (!((PortNameArr[PortIndex] == PortName)
|| (PortIndex == PortNameArr.GetUpperBound(0))));
if (PortIndex == PortNameArr.GetUpperBound(0))
{
PortName = PortNameArr[0];
}
SerialPort_cbbox.Text = PortNameArr[0];
SerialBaud_cbbox.Items.Add(9600);
//Set the read/write timeout
P.ReadTimeout = 500;
SP.WriteTimeout = 500;
}
void newFrameHandler(Frame frame)
{
Pointable pointable = frame.Pointables.Frontmost;
Pointable rightmostInFrame = frame.Pointables.Rightmost;
Pointable leftmostInFrame = frame.Pointables.Leftmost;
Vector position = pointable.TipPosition;
Vector direction = pointable.Direction;
int L1=204, L2 = 204;//, L2 = L1*2;
int[] DyHexAngle = new int[5];
double[] DynamixelTheta = new double[5];
double theta1 = 0, theta2 = 0, x_pos = 0, y_pos = 0, z_pos = 0;
double CosTh2 = 0, SinTh2 = 0, CosTh1 = 0, SinTh1 = 0
foreach (Hand hand in frame.Hands)
{
x_pos = hand.PalmPosition.x;
y_pos = hand.PalmPosition.y;
z_pos = -hand.PalmPosition.z + 250;
CosTh2 = (Math.Pow(z_pos, 2) + Math.Pow(y_pos, 2)
- Math.Pow(L1, 2) – Math.Pow(L2, 2)) / (2 * L1 * L2);
SinTh2 = -Math.Sqrt(1 – Math.Pow(CosTh2, 2));
theta2 = (Math.Atan2(SinTh2, CosTh2) * 180) / Math.PI;
CosTh1 = (((L1 + L2 * CosTh2) * z_pos + L2 * SinTh2 * y_pos)
(Math.Pow((L1 + L2 * CosTh2), 2) + Math.Pow(L2 * SinTh2, 2)));
inTh1 = (-L2 * SinTh2 * z_pos + (L1 + L2 * CosTh2) * y_pos)
1/ (Math.Pow(L1 + L2 * CosTh2, 2) + Math.Pow(L2 * SinTh2, 2));
theta1 = (Math.Atan2(SinTh1, CosTh1) * 180) / Math.PI;
DynamixelTheta[0] = (Math.Atan2(z_pos, x_pos) * 180) / Math.PI + 60;
DynamixelTheta[1] = 240 – theta1;
DynamixelTheta[2] = 150 + (-theta2);
int extendedFingers = 0;
for (int f = 0; f < hand.Fingers.Count; f++)
{
Finger digit = hand.Fingers[f];
if (digit.IsExtended)
extendedFingers++;
}
this.displayFigerCount_text.Text = extendedFingers.ToString();
}//foreach end
DynamixelTheta[4] = Math.Sqrt(
Math.Pow((leftmostInFrame.TipPosition.x -rightmostInFrame.TipPosition.x), 2) +
Math.Pow((leftmostInFrame.TipPosition.y – rightmostInFrame.TipPosition.y), 2) +
Math.Pow((leftmostInFrame.TipPosition.z – rightmostInFrame.TipPosition.z), 2)
);
DyHexAngle[4] = (int) (DynamixelTheta[4] * 511) / 155;
DynamixelTheta[3] = rightmostInFrame.TipPosition.y
- leftmostInFrame.TipPosition.y;
DyHexAngle[3] = 511 + (int)(DynamixelTheta[3] * 511) / 300;
if(DyHexAngle[0] < 300 || DyHexAngle[0] > 850)
DyHexAngle[0] = 511;
if(DyHexAngle[1] < 500)
yHexAngle[1] = 511;
if(DyHexAngle[2] < 500)
DyHexAngle[2] = 511;
if (DyHexAngle[4] > 600 || DyHexAngle[4] < 50)
DyHexAngle[4] = 511;
PosBuf[0] = 0xff;//start bit
PosBuf[1] = 0xff;
PosBuf[2] = (byte)(DyHexAngle[0] % 255);//1st motor
PosBuf[3] = (byte)(DyHexAngle[0] / 255);
PosBuf[4] = (byte)(DyHexAngle[1] % 255);//2st motor
PosBuf[5] = (byte)(DyHexAngle[1] / 255);
PosBuf[6] = (byte)(DyHexAngle[2] % 255);//3st motor
PosBuf[7] = (byte)(DyHexAngle[2] / 255);
PosBuf[8] = (byte)(DyHexAngle[3] % 255);//4st motor
PosBuf[9] = (byte)(DyHexAngle[3] / 255);
PosBuf[10] = (byte)(DyHexAngle[4] % 255);
PosBuf[11] = (byte)(DyHexAngle[4] / 255);
PosBuf[12] = (byte)(z_pos % 255);//x,
PosBuf[13] = (byte)(z_pos / 255);
(z_pos < 0)
PosBuf[14] = (byte)0×01;
else
PosBuf[14] = 0×00;
PosBuf[15] = (byte)(y_pos % 255);//y,
PosBuf[16] = (byte)(y_pos / 255);
PosBuf[17] = 0;
PosBuf[18] = (byte)(x_pos % 255);//z,
PosBuf[19] = (byte)(x_pos / 255);
if (x_pos < 0)
PosBuf[20] = 0×01;
else
PosBuf[20] = 0×00;
PosBuf[21] = 0xff;
//serail transmit
if (SP.IsOpen && SerialRxData == “S”)
{
SP.Write(buffer: PosBuf, offset: 0, count: 22);
SerialRxData = null;
}
}
private void boundary_setting(ref double DTheta, ref int DAngle)
{
DAngle = (int)(DTheta * 1023) / 300;
if (DAngle > 1023)
{
DAngle = 1023;
}
else if (DAngle < 0)
{
DAngle = 0;
}
}
private void RxDataClear_bt_Click(object sender, EventArgs e)
{
string RxDataClear = null;
ChangeRxTextBoxClear(RxDataClear);
}
private void ImgStart_bt_Click(object sender, EventArgs e)
{
if (IP_flag == 1 && SockOpen_flag == 1)
{
t1 = new Thread(new ThreadStart(TcpCamThread));
t1.Start();
t1_flag = 1;
}
else if (IP_flag != 1)
{
MessageBox.Show(“IP를 입력하세요”, “IP Error”);
}
else if (SockOpen_flag != 1)
{
MessageBox.Show(“Sock Open 버튼을 클릭하세요”, “Scoket Error”);
}
}
private void TcpCamThread()
{
int length, byteLength = 0, CompareFlag = 0;
while (true)
{
while ((length = stream.Read(buffer: BmpBuffer, offset: byteLength,
size: CamMaxBufSize – byteLength)) != 0)
{
byteLength += length;
}
if (byteLength == CamMaxBufSize)
{
CompareFlag++;
BmpInit(BmpBuffer);
ChangePictureBox(bmp);
byteLength = 0;
}
}
}
elegate void ChangePictureBoxCallback(Bitmap bmp);
public void ChangePictureBox(Bitmap bmp)
{
if (RobotStateVideo_pbox.InvokeRequired)
RobotStateVideo_pbox.Invoke(new ChangePictureBoxCallback(ChangePictureBox),
bmp);
else
RobotStateVideo_pbox.Image = bmp;
}
}
private void BmpInit(byte[] BmpBuffer)
{
System.Drawing.Imaging.BitmapData bmpData =
bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
IntPtr ptr = bmpData.Scan0;
System.Runtime.InteropServices.Marshal.Copy(source: BmpBuffer,
startIndex: 0, destination: ptr, length: 320 * 240 * 3);
bmp.UnlockBits(bmpData);
}
private byte uart_check_sum(byte[] check_arr, int arr_length)
{
int i = 0;
byte data = 0, check = 0;
for (i = 2; i < (arr_length – 1); i++)//check sum을 구하기 위한 for문
data += check_arr[i];
check = (byte) data;
return check;
}
private void SocketClose_bt_Click(object sender, EventArgs e)
{
//stream.Close();
//client.Close();
}
private void SerialOpen_bt_Click(object sender, EventArgs e)
{
SP.PortName = Convert.ToString(SerialPort_cbbox.Text);
SP.BaudRate = Convert.ToInt32(SerialBaud_cbbox.Text);
SP.DataBits = (int)8;
SP.Parity = Parity.None;
SP.StopBits = StopBits.One;
SP.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
SP.Open();
if (SP.IsOpen)
{
SerialState_text.Text = “[" + SP.PortName.ToString() + "]
Port Open Connect\r\n\r\nConnect”;;
}
else
{
SerialState_text.Text = “[" + SP.PortName.ToString() + "]
Port Open Failed\r\n\r\nDisconnected”;
}
}
private void SerialClose_bt_Click(object sender, EventArgs e)
{
SP.Close();
SerialState_text.Text = “[" + SP.PortName.ToString() + "]
Port Close\r\n\r\nDisconnected”;
}
private void TcpSocketOpen_bt_Click(object sender, EventArgs e)
{
try
{
clientAddress = new IPEndPoint(address:
IPAddress.Parse(pc_ip), port: 10000);
serverAddress = new IPEndPoint(address:
IPAddress.Parse(raspberry_ip), port: 10000);
client = new TcpClient(clientAddress);
client.Connect(serverAddress);
stream = client.GetStream();
udpSocket = new Socket(AddressFamily.InterNetwork,
SocketType.Dgram, ProtocolType.Udp);
localEP = new IPEndPoint(address: IPAddress.Any, port: 10000);
remoteEP = new IPEndPoint(IPAddress.Parse(raspberry_ip), port: 10000);
SockOpen_flag = 1;
}
catch (SocketException e1)
{
Console.WriteLine(e1);
}
}
}
5.1.2. ATmega128
#include <ina90.h>
#include “Header/dynamixel_k.h”
#include “Header/uart_k.h”
#include “Header/i2c_k.h”
#include “Header/delay_k.h”
#include “Header/lcd_k.h”
void TestRobotArm(void)
{
unsigned char i;
trans0_ch(‘S’);
for(i=0; i<13; i++)
uart_arr[i]=receive0_ch();
dynamixel_sync_write(0×11,0×03,0×06,0×07,0×13);
delay_ms(10);
}
void StartRobotArm(void)
{
if(uart_arr[14] == 2)
{
dynamixel_sync_write(0×11,0×03,0×06,0×07,0×13);
delay_ms(10);
}
}
void BasicRobotArm(void)
{
unsigned char i;
check_sum(basic_arr,33);
for(i=0; i<33; i++)
trans1_ch( basic_arr[i] );
}
void dec_hex_convert(int dec, unsigned char* low_temp, unsigned char* high_temp )
{
*high_temp = 0×00;
*low_temp = 0×00;
*high_temp = (unsigned char)0xff&(dec/255);
*low_temp = (unsigned char)0xff&(dec%255);
}
void check_sum(unsigned char * check_arr,int arr_length)//디지털모터의 checksum계산하는 함수
{
unsigned int i=0;
unsigned char data=0, check=0;
for(i=2;i<(arr_length-1);i++)//check sum을 구하기 위한 for문
data += check_arr[i];
check = ~data;
*(check_arr+(arr_length-1))=check;
}
void SyncWriteAndRead(unsigned char ID1, unsigned char ID2, unsigned char ID3, unsigned char ID4, unsigned char ID5)
{
unsigned char i;
delay_ms(3000);
dynamixel_sync_write(ID1,ID2,ID3,ID4,ID5);
for(i=0; i<30; i++)
{
trans0_ch(SyncWriteArr[i]);
}
dynamixel_pre_pos_L_read_data(ID5,8);
dynamixel_pre_pos_H_read_data(ID5,8);
}
void dynamixel_pre_pos_L_read_data(unsigned char ID,unsigned int length)
{
pre_pos_L_read[2] = ID;
check_sum(pre_pos_L_read, 8);
__disable_interrupt();
RS485_TX;
trans0_ch(pre_pos_L_read[0]);
trans0_ch(pre_pos_L_read[1]);
trans0_ch(pre_pos_L_read[2]);
trans0_ch(pre_pos_L_read[3]);
trans0_ch(pre_pos_L_read[4]);
trans0_ch(pre_pos_L_read[5]);
trans0_ch(pre_pos_L_read[6]);
trans0_ch(pre_pos_L_read[7]);
TX_FINISH_CHECK//송신 완료를 기다림
RS485_RX;
__enable_interrupt();
while(1)
{
if(UartFlag == 1)
{
__disable_interrupt();
if(uart_i == 7)
{
trans0_ch(0xff);
uart_i = 0;
break;
}
trans0_ch(receive_data[uart_i]);
UartFlag = 0;
__enable_interrupt();
}
}
}
void dynamixel_pre_pos_H_read_data(unsigned char ID,unsigned int length)
{
pre_pos_H_read[2] = ID;
check_sum(pre_pos_H_read, 8);
__disable_interrupt();
RS485_TX;
trans0_ch(pre_pos_H_read[0]);
trans0_ch(pre_pos_H_read[1]);
trans0_ch(pre_pos_H_read[2]);
trans0_ch(pre_pos_H_read[3]);
trans0_ch(pre_pos_H_read[4]);
trans0_ch(pre_pos_H_read[5]);
trans0_ch(pre_pos_H_read[6]);
trans0_ch(pre_pos_H_read[7]);
TX_FINISH_CHECK//송신 완료를 기다림
RS485_RX;
__enable_interrupt();
while(1)
{
if(UartFlag == 1)
{
__disable_interrupt();
if(uart_i == 7)
{
trans0_ch(0xff);
uart_i = 0;
break;
}
trans0_ch(receive_data[uart_i]);
UartFlag = 0;
__enable_interrupt();
}
}
}
void dynamixel_baudrate_change(unsigned char dynamixel_baud_rate)
{
/*
=======================
* 1 => 1M *
* 16 => 115200 *
* 207 => 9600 *
=======================
*/
unsigned int length, i=0;
unsigned char BaudRateChange[]=
{
0xff,
0xff,
0xfe,
0×04, //length N+3, N= (parameter 개수 – 1)
0×03, //write_data 명령어
0×04,
dynamixel_baud_rate,
0×00 //check sum
};
length = (sizeof(BaudRateChange)/sizeof(unsigned char));
check_sum(BaudRateChange, length);
RS485_TX;
for(i=0; i<length; i++)
trans1_ch(BaudRateChange[i]);
}
void dynamixel_sync_write(unsigned char ID1, unsigned char ID2, unsigned char ID3, unsigned char ID4, unsigned char ID5)
{
unsigned int length = (sizeof(SyncWriteArr)/sizeof(unsigned char)) , i ;
SyncWriteArr[7] = ID1;
SyncWriteArr[12] = ID2;
SyncWriteArr[17] = ID3;
SyncWriteArr[22] = ID4;
SyncWriteArr[27] = ID5;
arm_control_ang_ve();
check_sum(SyncWriteArr,length);
//RS485_TX;
for(i=0; i<length; i++)
{
trans1_ch(SyncWriteArr[i]);
}
}
void arm_control_ang_ve()
{
//angle
SyncWriteArr[8]=uart_arr[2];
SyncWriteArr[9]=uart_arr[3];
SyncWriteArr[13]=uart_arr[4];
SyncWriteArr[14]=uart_arr[5];
SyncWriteArr[18]=uart_arr[6];
SyncWriteArr[19]=uart_arr[7];
SyncWriteArr[23]=uart_arr[8];
SyncWriteArr[24]=uart_arr[9];
SyncWriteArr[28]=uart_arr[10];
SyncWriteArr[29]=uart_arr[11];
//velocity
SyncWriteArr[10]=0×65;
SyncWriteArr[11]=0×00;
SyncWriteArr[15]=0×65;
SyncWriteArr[16]=0×00;
SyncWriteArr[20]=0×65;
SyncWriteArr[21]=0×00;
SyncWriteArr[25]=0×65;
SyncWriteArr[26]=0×00;
SyncWriteArr[30]=0×65;
SyncWriteArr[31]=0×00;
}
unsigned char SyncWriteArr[33] = {
//각도 인덱스 (8,9) (13,14)(18,19) (23,24)
//속도 인덱스 (10,11) (15,16) (20,21) (25,26)
0xff,
0xff,
0xfe,
0x1d,//((L+1)*n)+4,n은 모터개수
0×83,//sync write 명령어
0x1e,//control table의 명령어
0×04,//쓰고자하는 L의 길이
0×00,//아이디 17
0×00,//L의 시작
0×00,
0×00,
0×00,//L의 끝
0×00,//아이디
0×00,
0×00,
0×00,
0×00,
0×00,//아이디
0×00,
0×00,
0×00,
0×00,
0×07,//아이디
0×00,
0×00,
0×00,
0×00,
0×00,//아이디
0×00,
0×00,
0×00,
0×00,
//check sum = ~(ID + Length + Instruction + parameter1 + ~ + Parameter N)
0×00
};
unsigned char basic_arr[33] = {
0xff,
0xff,
0xfe,
0x1d,//((L+1)*n)+4,n은 모터개수
0×83,//sync write 명령어
0x1e,//control table의 명령어
0×04,//쓰고자하는 L의 길이
0×11,//아이디 17
0xff,//L의 시작
0×01,
0×65,
0×00,//L의 끝
0×03,//아이디 3
0xff,
0×01,
0×65,
0×00,
0×06,//아이디 6
0xff,
0×01,
0×65,
0×00,
0×07,//아이디 7
0xff,
0×01,
0×65,
0×00,
0×13,//아이디 19
0xff,
0×01,
0×65,
0×00,
//check sum = ~(ID + Length + Instruction + parameter1 + ~ + Parameter N)
0×00
};
unsigned char read_arm[] = {
0xff,//start code
0xff,//start code
0×00,//ID
0×04,//length
0×02,//instruction
0x2b,//parameter1
0×01,//parameter2
0×00//check sum
};
unsigned char pre_pos_L_read[] = {
0xff,//start code
0xff,//start code
0×00,//ID
0×04,//length
0×02,//instruction
0×24,//parameter1 , present speed Low , High Hex
0×01,//parameter2
0×00//check sum
};
unsigned char pre_pos_H_read[] = {
0xff,//start code
0xff,//start code
0×00,//ID
0×04,//length
0×02,//instruction
0×25,//parameter1 , present speed High , Low Hex
0×01,//parameter2
0×00//check sum
};
void dynamixel_read_data(unsigned char ID,unsigned int length)
{
read_arm[2] = ID;
check_sum(read_arm, 8);
__disable_interrupt();
RS485_TX;
trans1_ch(read_arm[0]);
trans1_ch(read_arm[1]);
trans1_ch(read_arm[2]);
trans1_ch(read_arm[3]);
trans1_ch(read_arm[4]);
trans1_ch(read_arm[5]);
trans1_ch(read_arm[6]);
TX_FINISH_CHECK//송신 완료를 기다림
RS485_RX;
__enable_interrupt();
while(1)
{
if(UartFlag == 1)
{
__disable_interrupt();
trans0_ch(receive_data[uart_i]);
if(uart_i == 7)
{
uart_i = 0;
break;
}
UartFlag = 0;
__enable_interrupt();
}
}
}
1. https://developer.leapmotion.com/documentation/csharp/api/Leap_Classes.html ,Leap Motion Developer
2. 닷넷 프로그래밍 정복, 김상형
3. 뇌를 자극하는 C#4.0 프로그래밍, 박상현
4. 열혈 C 프로그래밍, 윤성우
5. AVR ATmega128 마스터, 윤덕용
5.3. 회로도
5.3.1. ATmega128
[40호]사물인터넷(IoT) 플랫폼 PHPoC 출시
[P4S-342] PHP 프로그래밍 사물인터넷보드 PHPoC Blue (무선랜 동글포함) |
[P4S-341] PHP 프로그래밍 사물인터넷보드 PHPoC Black (유선랜) |
솔내시스템㈜
사물인터넷(IoT) 플랫폼 PHPoC 출시
산업용 장비의 인터넷 통신 솔루션에 주력했던 솔내시스템㈜에서 사물인터넷(IoT) 플랫폼으로 활용할 수 있는 PHPoC IoT 보드를 출시했다.
PHPoC는 PHP on Chip의 약자로, 솔내시스템(주)에서 자체 개발한 프로그래밍 언어를 의미한다. 오픈소스 스크립트 언어인 PHP를 기반으로 개발되었기에 C언어 등 프로그래밍 언어에 대한 경험이 있는 사람이라면 누구나 쉽게 사용할 수 있다는 장점이 있다.
대표적인 제품은 PHPoC Blue(P4S-342), PHPoC Black(P4S-341)이며 각 제품은 무선랜과 유선랜 산업용 IoT 보드라는 차이점이 있다. 두 제품 모두 PHPoC 인터프리터가 내장된 사용자 프로그래밍이 가능한 네트워킹 산업용 IoT 보드로서, 유무선 인터넷 환경에서 시리얼, 디지털 입출력 등 다양한 통신 인터페이스를 제공하여 각종 센서와 장비에 연결해 사용할 수 있는 것이 특징이다. 또한 웹 브라우저를 통해 각종 장비를 원격으로 감시 및 제어할 수 있으며, 사용자 요구에 부응하는 맞춤형 어플리케이션을 빠르게 제작해 사용할 수 있다. 간단한 프로그래밍으로 시리얼·디지털 입력 데이터를 분석할 수 있으며 이메일을 통한 알림 기능이나 데이터베이스(DB) 저장 등 사용자가 필요한 기능을 직접 구현할 수 있다. 두 제품 모두 적측형 구조로 설계되어, 목적에 맞는 다양한 확장보드의 연결을 통해 추가적으로 요구되는 기능들을 빠르게 구현할 수 있다.
PHPoC Blue는 무선랜 어댑터(IEEE 802.11b/g, Ralink RT3070/RT5370 칩셋 적용)를 사용하여 무선랜 연결이 가능한 제품이며 10개의 Digital Input/Output 전용 포트를 보유하고 있다. PHPoC Black의 경우 유선랜(10Base-T/100Base-TX Ethernet(RJ45)) 산업용 IoT 보드로 8개의 Digital Input/Output 전용포트를 지원한다. 또한 공통적으로는 14개의 공유 포트(Digital Input/Output, SPI, I2C, UART)와 6개의 아날로그 입력 포트가 있으며 HTTP/SSH/Websocket 서버가 지원된다는 특징이 있다.
솔내시스템㈜는 라이브러리와 샘플코드를 활용해 사용자가 쉽게 개발 환경에 접근하도록 지원하고 있다. 보다 자세한 사항은 디바이스마트 상품 페이지 및 PHPoC forum(http://cafe.naver.com/phpoc)에서 확인할 수 있다.
[P4S-342] PHP 프로그래밍 사물인터넷보드 PHPoC Blue (무선랜 동글포함)
[P4S-341] PHP 프로그래밍 사물인터넷보드 PHPoC Black (유선랜)
[P4S-342] PHP 프로그래밍 사물인터넷보드 PHPoC Blue (무선랜 동글포함) 상세 구경하러 가기
[P4S-341] PHP 프로그래밍 사물인터넷보드 PHPoC Black (유선랜) 상세 구경하러 가기