[42호]세상에 하나밖에 없는 전자시계 만들기, “I.Watch Shield” 출시
I.O. Tech
세상에 하나밖에 없는 전자시계 만들기,
“I.Watch Shield” 출시
시계는 스마트폰과 함께 일상 생활에서 가장 많이 사용한다고 해도 과언이 아니다. 이번 기회에 일상생활에서 활용 가능한 실용적인 DIY를 도전해보는 것은 어떨까?
2014년부터 소프트웨어 교육의 대중화에 힘쓰고 있는 국내기업 I.O. Tech는 I.Watch Shield를 새롭게 출시했다. I.O. Tech는 한국형 아두이노 기초 키트, 쉴드 등의 제품을 출시하고 있어 국내 입문 메이커들이 보다 손쉽게 접근이 가능하다.
이번에 출시한 I.Watch Shield는 DS1307 칩을 사용하며 I2C 통신 방식으로 작동하는 제품이다. 아두이노 우노 보드, 아두이노 호환 보드로 사용이 가능하며 RTC(Real Time Clock)와 4자리 7세그먼트를 이용하여 전자시계를 구현하는 프로젝트형 아두이노 쉴드이다. 일반 메이커뿐만 아니라 학교 아두이노 활용 교육용으로도 매력적인 제품이다.
현재 디바이스마트에서 구매가 가능하며, I.O. Tech 공식 홈페이지(http://www.io-tech.co.kr/) 자료실에 유의사항 및 회로도, 예제코드가 친절하게 마련되어 있다.
제품 특징
· 아두이노(Arduino)의 아두이노 스케치와 연결 사용 가능
· 아두이노 우노 보드, 아두이노 호환 보드로 사용 가능
· RTC는 DS1307 칩을 사용하며 I2C 통신 방식 동작
· 하나의 택트스위치가 풀업저항 방식으로 연결되어 있음
www.io-tech.co.kr
[42호]제 6의 손가락 : 4절 링크를 이용한 수평 유지 로봇 SRF
2016 ICT 융합 프로젝트 공모전 우수상
제 6의 손가락 : 4절 링크를 이용한
수평 유지 로봇 SRF
(Supernumerary Robotic Finger)
글 | 고려대학교 최주헌
심사평
JK전자 기능적으로는 어느 정도 구현이 되었으니 힘이 있는 모터를 사용해서 조금 더 무거운 것을 올려도 균형을 잡을 수 있다면 인체에 부착하여 무거운 짐을 옮길 때 사용하면 편리하겠네요.
뉴티씨 균형 유지라는 매우 어려운 주제이지만, 누구나 쉽게 이해할 수 있도록 프로젝트를 구성하여 좋은 작품으로 평가된다. 알고리즘 등에 보다 집중하여 구현하고 설명을 한다면 더 좋은 작품이 될 것으로 보이며, 움직임이 보다 빠르게 구현될 수 있도록 하여, 빠른 움직임에서도 빠르게 대처될 수 있도록 개선하는 작업이 필요해 보이며, 앞으로 좀 더 연구하여 매우 훌륭한 작품을 만들기를 바라며, 좋은 점수를 받았다.
칩센 좋은 아이디어와 훌량한 완성도를 가지고 있다. 장애를 가진 사람에게도 도움이 될 아이디어 같다.
위드로봇 아이디어와 작품의 완성도가 무척 높습니다. 제어기를 좀 더 세심하게 설계하여 평판의 기울기 정도를 좀 더 잘 제어할 수 있으면 재미있는 상품이 나올 수 있을 것 같습니다.
작품 개요
전지와 MCU(Micro Controller Unit)의 소형화 추세에 따라 웨어러블 로봇이 각광받고 있다. 최근에는 “사람의 팔, 다리가 여러 개이면 어떨까?” 라는 아이디어에서 나온 여분의 신체부위 로봇 SRL(Supernumerary Robotic Limbs)이 개발되고 있다. 하지만 이러한 SRL은 사람의 의도를 파악하기 위해, 추가하고자 하는 신체부위에 많은 센서를 부착해야 하는 문제가 있다. 이 작품은 여분의 보조 손가락 SRF (Supernumerary Robotic Finger)를, 신체에 별도의 센서를 부착하지 않고도 신체 정보를 얻어 조절할 수 있다. SRF는 사람의 팔에 부착되어 쟁반과 같은 면적이 넓은 물체를 손으로 들 때, 물체가 균형을 잃지 않도록 보조해 주는 것을 목적으로 한다. 이때 필요한 신체 정보는 SRF End Point에 부착된 센서를 통해 4절링크 모델로 분석하여 얻어낸다. 제작한 SRF로 수평 유지 실험결과 수평 유지에 큰 도움을 주는 것을 확인하였다. 위 작품을 통해 로봇과 인간사이의 조화 연구에 새로운 접근 방향을 제시할 것으로 본다.
작품 설명
주요 동작 및 특징
본 작품은 쟁반과 같은 넓은 평면 물체의 균형을 잡아주는 여분의 보조 손가락(SRF) 웨어러블 로봇으로, 손목에 로봇이 부착된다. 로봇의 센서가 쟁반에 부착되어 쟁반의 지면과의 기울기를 측정하고 이에 따라 로봇의 서보 모터를 제어하여 쟁반의 균형을 유지한다. 본 작품은 기존의 SRF로봇들이 수행하는 물건을 잡는 기능이 아니라 물체의 균형을 잡는 새로운 기능을 시도하여 SRF 연구에 새로운 방안을 제시한다.
전체 시스템 구성
본 작품은 물체의 균형을 잡아주는 2개의 서보모터(AX-12)와 물체의 기울기를 측정하는 IMU 센서(EBIMU-9DOFV3), 이들의 상호작용을 일으키는 연결 Joint들, 모터 제어와 센서 값을 받아들이는 MCU(아두이노MEGA)로 구성된다. 이와 같은 로봇 디자인이 나오게 된 이유는 단계별 제작과정에 구체적으로 서술하였으며 작동방식은 첨부한 영상을 통해 보다 이해하기 쉽게 하였다.
SRF 전체 구성
SRF 로봇은 사람 손목에 부착되며 2개의 흡착판은 평면 물체에 부착된다. 각각의 서보 모터는 로봇과 부착된 물체의 Roll, Pitch를 조절하여 지면과의 수평을 조절할 수 있다. MCU는 IMU 센서 값에 측정되는 Roll Pitch 값을 받아들여 지평면과의 수평을 유지하도록 모터를 제어한다. 제어방식은 PID 제어 방식이며, 처음에 모터 각속도에 따른 평면 물체의 각속도 변화를 통해 사람 손가락과 로봇 End Point간의 거리를 측정, 제어 상수를 결정한다.
이러한 과정을 통해 SRF 로봇은 손가락에 별도의 센서를 부착하지 않고도 손의 위치를 파악하여 손위의 평면 물체 기울기를 유지할 수 있도록 보조해준다.
개발 환경(개발 언어, Tool, 사용 시스템 등)
· C언어를 이용하여 아두이노 MEGA 사용(아두이노 스케치)
· SRF 손목 부착 방식 (구버전)
· 서보모터와 IMU 센서는 시리얼통신을 이용
단계별 제작 과정
SRF 모델 설정
손위의 평판 기울기를 조절하기 위해서는 평판과 만나는 최소 3개의 점으로 평면이 필요하며, 3점 간의 거리를 조절하여 3차원 상의 기울기 Roll. Pitch, Yaw의 각도를 조절할 수 있다.
3차원 상에서 지면과 수평을 이루기 위해서는 평판과 지면 사이의 각이 전부 0이 되면 된다. 즉 수평면 사이의 각인 Roll, Pitch가 0이 되도록 3개의 점을 조절하면 된다. 그렇기에 손의 변화에 맞춰 Roll, Pitch 값이 0에 수렴하도록 SRF를 2자유도 모델로 설정하여 설계하였다.
위 모델을 통해 사람 손을 단순화 시켜보면 평판과접하는 5개의 손가락은 그림(Fig 1)처럼, 평판과 수평면 사이 각을 조절하는데 필요한 3개의 점 중 하나의 점으로 볼 수 있다. 이렇게 할 경우 손의 점을 기준으로 나머지 점 2개의 위치를 조절하여 손위의 평판과 수평면과 평행하게 조절할 수 있다. 본 작품은 이러한 모델을 통해 하드웨어 설계를 진행 하였다.
SRF 하드웨어 디자인
2자유도의 SRF는 2개의 서보모터(Dynamixel AX-12), 4개의 링크, 조인트, End Point로 구성되어 있으며 아래 그림(Fig 2)과 같은 형태로 설계하였으며 이에 따른 D-H 변수는 표 1과 같다.
이때 θ1과 θ1만 서보 모터를 이용하여 제어하도록 설계하였는데, 그 이유는 2차원 상에서 보았을 때 그림(Fig 3)의 형태처럼 손위의 평판과 SRF의 End Point로 결합할 경우 2자유도 모델이 되기 때문이다.
이때 SRF와 평판과 손의 관계가 4절 링크 형상을 띄기 때문에 이러한 관계를 이용하여 평판의 수평 조절을 제어할 수 있다. Joint 1은 End point와 함께 Universal Joint의 역할을 하여 Motor2 제어시 손목의 꺾임을 막는다. 평판의 수평 조절을 위해서는 손의 형상에 따른 임의로 주어진 평판 위 1개의 점을 통해 2개의 점을 조절하면 되기때문에, End Point 설계시 평판과 2점이 만나 부착될 수 있도록 하였다. 또한 End Point는 평판에 부착되어 평판의 Roll, Pitch 값과 End Point의 Roll, Pitch 값은 동일하기 때문에 End Point에 IMU Sensor를 부착하여 평판의 기울기 정보를 얻어내도록 하였다. (Fig 4)
이러한 설계를 통해 NX 3D모델링 프로그램과 3D프린터를 이용하여 제작한 SRF는 위 그림(Fig 5)과 같다. 부품 제작 과정은 아래에 기재하였다.
SRF 제어
설계한 SRF를 이용하여 평판이 수평을 유지하도록 하기 위해서는 End Point에 부착된 IMU 센서가 측정하는 Roll, Pitch 값을 0에 수렴하도록 모터 2개를 제어해야 한다. 각각의 모터는 Roll과 Pitch를 제어할 수 있는데 Motor1의 경우 그림(Fig 6.)처럼 Pitch 값을 제어할 수 있다.
SRF는 그림(Fig 7)처럼 팔에 부착되어 있으며 초록색 사각형은 모터, 노란색 원은 Revolute-joint를 의미한다. SRF의 링, 팔, 평판의 움직임은 4절 링크와 같기 때문에, 표 1. D-H변수를 사용하면 θ1을 통해 θ3와 θ5가 정해진다. θ1통해 IMU센서로 측정되는 Roll 값을 제어 할 수 있으므로 Pitch가 0이 되도록 Motor1을 PID 제어로 제어하였다.
하지만 θ1과 Pitch값을 조절하는 시스템이 일정하지 않고 손의 길이 a0 평판의 길이 a4에 따라 관계가 바뀌기 때문에 컨트롤러의 Kp, Ki, Kd 상수 값은 IMU로 측정되는 각속도 값과 θ1 사이의 관계를 통해 지속적으로 변화시키도록 하였으며 그 과정은 이와 같다.
4절 링크 관계식을 이용하기위한 링크 별 길이 정보는 표 2와 같으며 팔 길이 a0 손목에서 손까지의 일반적인 길이 20cm로 정하였다. 이때 손과 SRF 사이의 평판 길이 a4가 잡는 방식에 따라 변하기 때문에, w1이 일정할 경우 a4의 변화와 링크a1의 회전 상태인 θ1에 따른 각속도 w6의 값의 변화를 알아야 한다. 4절 링크 관계식으로 이를 계산하면 아래 그림(Fig 7)과 같이 선형적인 관계임을 볼 수 있다. 이를 통해 측정된 w6와 θ1값을 통하여 w4를 측정 할 수 있으며 이를 이용하여 Kp, Ki, Kd 상수 값을 변경 시킨다.
Pitch 값의 수평 제어 후 Roll의 수평 제어의 경우, Motor2의 회전축과 Roll의 회전축이 일치하여 Motor2의 움직임이 Roll 값의 변화와 같다. 이러한 선형적인 관계이므로 PID 제어를 통해 Roll 값이 0이 되도록 제어하였다. Motor2를 제어하면 그림(Fig. 8)과 같이 평판과 손 SRF 사이의 간격이 “r”로 일정하기 때문에 Motor2의 회전축이 변하게 되고 손목을 꺾게 된다.
그렇기 때문에 하드웨어 디자인에서 Joint 1을 넣어 End point와 함께 Universal Joint역할을 하게하여 손목 꺾임을 방지하였다. 대신 Motor2의 제어는 2D에서본 4절링크의 링크 길이를 변화시키기 때문에 평형에 맞추어진 Pitch 값에 변화를 주게 된다.
Motor2에 의한 Pitch 변화에 바로 반응하기 위해 그림(Fig. 9)와 같은 제어 시스템을 사용하였다.
SRF 성능 실험
로봇 설계 후 손의 위치 변화에 따른 평판의 각도 측정값은 다음과 같다.
외부에서 각도 변화가 주어졌을 때 SRF의 대응을 확인하기 위해, 일정한 주기로 평판을 변화시키며 이에 따른 SRF End Point 값을 측정한 결과. 그림(Fig 10, Fig 11)과 같이 SRF는 손의 움직임에 의한 평판의 진폭을 2배 이상 감소시키며 이를 통해 SRF의 목표인 수평 보조 기능을 잘 수행함을 확인할 수 있으며 충분히 빠른 시간에 반응하는 것을 확인할 수 있다.
결론
본 작품은 물체의 수평 유지를 목적으로 하는 SRF에 대한 것으로, SRF가 사용자의 의도를 파악하는 방식으로는 로봇과 신체를 하나의 기구학적 모형으로 해석하고 이로부터 신체 정보를 얻음으로써 신체에 별도의 센서를 부착하지 않고도 수평유지라는 목적을 달성하는 방법을 제시하였다.
이러한 방식의 SRF는 착용자에게 센서를 장착하는 대신 로봇 자체의 센서를 이용하기 때문에 착용자로부터 아무런 조건의 제한을 받지 않는다. 이 점을 이용하여 사람뿐만 아니라 향후 구조물 등에 부착되도록 하여 물체의 수평을 유지하는 역할을 수행할 수 있다. 또한 기존의 웨어러블 로봇의 인체 의도 파악 연구에 새로운 해결 방안을 제시하였다.
기타(회로도, 소스코드, 참고문헌 등)
회로도
소스코드
#define Roll_initial 512
#define Pitch_initial 512
#define Yaw_initial 512
#define Angle_MarginP 0.5
#define Angle_Margin 0.5
#define Kd 0.01 // D 제어
#define Ky 0.3 // Roll 제어에 따른 Pitch 제어
#define Ki 0.05 // i제어
#define Motor_Speed 1000
#define Speed_High 700
#define Speed_Low 300
#define Kp1 0.5
#define ID_Roll 8
#define ID_Pitch 4
#define ID_Yaw 2
int flag=1;
int sequence1 =1;
double Kp=2.5; // P제어
int sa=0;
double wr=0;
double wp=0;
char *str;
char *p;
int temp;
double fRoll;
double fPitch;
double Roll, Pitch, Yaw;
long Motor_R, Motor_P, Motor_Y;
double ip=0;
double ir=0;
double Time = 0;
double dt = 0.025;
double maxw=0;
void setup()
{
Serial.begin(115200); //Serial Monitor
Serial1.begin(1000000); // Serial for Motor
Serial2.begin(115200); // Serial for IMU
Motor_R = Roll_initial;
Motor_P = Pitch_initial;
Motor_Y = Yaw_initial;
sequence1 =1;
Motor(512,512, 512, 100);
delay(1000);
Motor(513,600,513, 100);
delay(10);
}void loop()
{
//시리얼 통신으로 IMU 센서값 받아들임
temp=Serial2.available();
if(temp>40)
{
char b[60]={0};
char c[60]={0};
for(int i=0; i<temp; i++)
{
b[i]=Serial2.read();
}
for(int i=0; i<temp; i++)
{
if(b[i]==42)
{
for(int j=i; j<temp; j++)
{
if(b[j]==13)
{
for(int k=0; k<j-i-1; k++)
{
c[k]=b[k+i+1];
}
break;
}
}
}
}
str = strtok_r(c,”,”,&p);
Roll = atof(str);
str = strtok_r(NULL,”,”,&p);
Pitch = atof(str);
Time += dt;
Serial.print(fRoll);
Serial.print(“,”);
Serial.print(fPitch);
Serial.print(“,”);
Serial.print(Time);
Serial.println();
//시간에 따른 IMU 센서값 출력
delay(10);
}else{
Serial.begin(115200); //Serial Monitor
Serial1.begin(1000000); // Serial for Motor
Serial2.begin(115200); // Serial for IMU
Serial.println(“Loop Start”);
}
//0값 노이즈 제거
if(Pitch!=0){
wp=(fPitch-Pitch)/0.03;
ip+=fPitch*0.03;
fPitch=Pitch;
}
if(Roll!=0){
wr=(fRoll-Roll)/0.03; //Rol 각속도 측정
Serial.print(“w is – “);
Serial.print(wr);
if(maxw<abs(wr)&&abs(wr)<100){ //초기 값 세팅에서 손가락 길이 알기위한 w가 측정 노이즈 보정위해 100이상은 무시
maxw=abs(wr);
}
Serial.print(“max w is – “);
Serial.print(maxw);
ip+=fRoll*0.03;
fRoll=Roll;
}
if(sequence1){ //전원 연결시 스캐닝과정 초기 Roll Pitch 값이 0이 되도록 단순제어
if (0<abs(maxw)&&abs(maxw)<25){//w값에 따른 Kp값 변경
Kp=3;
}else if(26<abs(maxw)&&abs(maxw)<50){
Kp=2,3;
}else if(51<abs(maxw)&&abs(maxw)){
Kp=1.7;
}
if (abs(fPitch) > Angle_MarginP)
{
if(Pitch > 0){
if(Motor_P<824){
Motor_P+=1;
}
}
else if(fPitch < 0)
{
if(Motor_P>200){
Motor_P-=1;
}
}
}
if (abs(fRoll) > Angle_Margin)
{
if(fRoll < 0){
if(Motor_R<700){
Motor_R+=3;
}
}
else if(fRoll > 0)
{
if(Motor_R>300){
Motor_R-=3;
}
}
}
Motor(Motor_R,Motor_Y, Motor_P, 100);
}else{
//스캐닝 후 PID 제어
if (abs(fPitch) > Angle_MarginP)
{
Motor_P+=wp*Kd+Ki*ip;
if(Pitch > 0){
if(Motor_P<824){
Motor_P+=abs(fPitch)*Kp1;
}
}
else if(fPitch < 0)
{
if(Motor_P>200){
Motor_P-=abs(fPitch)*Kp1;
}
}
Motor_R+=Ky*abs(fPitch);
}
if (abs(fRoll) > Angle_Margin)
{
if(fRoll < 0){
if(Motor_R<700){
Motor_R+=wr*Kd+ip*Ki;
Motor_R+=abs(fRoll)*Kp;
}
}
else if(fRoll > 0)
{
if(Motor_R>300){
Motor_R-=abs(fRoll)*Kp;
Motor_R+=wr*Kd+ip*Ki;
}
}
}
Motor(Motor_R,Motor_Y, Motor_P, Motor_Speed); //모터와 시리얼 통신
}
if((abs(fRoll)<Angle_MarginP)&&(abs(fPitch)<Angle_MarginP)){
sequence1=0;
Motor_Y-=1;
}
Serial.println (sequence1);
delay(15);
}
void Motor(unsigned int Pos_1, unsigned int Pos_2, unsigned int Pos_3, unsigned int Speed) //모터 시리얼 통신 함수
{ unsigned char Check_Sum;
int Num_Actuator = 3;
unsigned char ID_1 = 8;
unsigned char ID_2 = 2;
unsigned char ID_3 = 4;
unsigned char Length = 5*Num_Actuator+4;
Serial1.write(0xFF);
Serial1.write(0xFF);
Serial1.write(0xFE); //Broadcast ID
Serial1.write(Length); //Length
Serial1.write(0×83); //Instruction
Serial1.write(0x1E); //Address
Serial1.write(4); //Data Length
Serial1.write(ID_1);
Serial1.write(Pos_1);
Serial1.write(Pos_1>>8);
Serial1.write(Speed);
Serial1.write(Speed>>8);
Serial1.write(ID_2);
Serial1.write(Pos_2);
Serial1.write(Pos_2>>8);
Serial1.write(Speed);
Serial1.write(Speed>>8);
Serial1.write(ID_3);
Serial1.write(Pos_3);
Serial1.write(Pos_3>>8);
Serial1.write(Speed);
Serial1.write(Speed>>8);
Check_Sum = ~(0xFE + Length + 0×83 + 0x1E + 4 + (ID_1+ID_2+ID_3) + (Pos_1+Pos_2+Pos_3) + ((Pos_1>>8)+(Pos_2>>8)+(Pos_3>>8)) + 3*(Speed+(Speed>>8)));
Serial1.write(Check_Sum);
}
참고문헌
[1] C. Davenport, F. Parietti, and H. H. Asada, “Design and biomechanicalanalysis of supernumerary robotic limbs,” in ASME 2012 5th Annual Dynamic Systems and Control Conference joint with the JSME 2012. 11th Motion and Vibration Conference, pp. 787-793, 2012.
[2] Domenico Prattichizzo, Monica Malvezzi, Infan Hussain and Gionata Salvietti “The Sixth-Finger: a Modular Extra-Finger to Enhance Human Hand Capabilites”in The 23rd IEEE International Symposium on Robot and Human Interactive Communication 2014
[3] Arthur G.Erdman and George N.Sandor, Sridhar Kota Mechanism Design Analysis and Synthesis 4Edition, Prentice Hall
[42호]ESP8266 와이파이를 내장한 아두이노 Uno, Arduino UNO WiFi 출시
Arduino
ESP8266 와이파이를 내장한
아두이노 Uno, Arduino UNO WiFi 출시
Arduino(Arduino.org 및 Arduino.cc)는 Atmega328P 마이크로컨트롤러와 ESP8266 WiFi 통합모듈을 탑재해 Arduino UNO와 와이파이를 하나의 보드로 통합한 “Arduino Uno WiFi”를 출시했다.
이 제품은 14개의 디지털 입/출력 핀헤더, 6개의 아날로그 입력 핀, 16MHz 세라믹 공진기(공명기), USB 포트, 전원 잭, ICSP 헤더, 그리고 Reset 버튼을 내장하고 있다. 또한 USB 케이블을 통해 간편하게 컴퓨터와 연결하거나, AC-DC 아답터 또는 배터리를 활용해 쉽게 전원을 공급할 수 있는 등 마이크로컨트롤러를 활용하는데 필요한 것들을 모두 내장하고 있다.
특히 Arduino Uno Wi-Fi의 가장 유용한 기능 중 하나는 OTA (over-the-air) 프로그래밍을 지원해 케이블없이 온라인으로 Arduino 스케치나 Wi-Fi 펌웨어 작업이 가능한 점이다.
제품에 대한 더 자세한 사항은 디바이스마트 홈페이지(http://www.devicemart.co.kr/)에서 확인할 수 있다.
제품 특징
· 보드에 장착된 센서나 엑츄에이터에 와이파이로 통신하여 IoT 시스템을 쉽고 빠르게 만들 수 있음
· USB를 통하거나 외부 전원을 통한 작동이 모두 가능
· USB가 아닌 외부의 전원은 AC-DC 아답터(DC잭 활용) 또는 배터리(Gnd. Vin핀 활용)로 공급
Features
Arduino Uno Wi-Fi는 ATmega328P (datasheet)와 ESP8266 Wi-Fi 통합 모듈(datasheet) 기반의 새로운 Arduino Uno 입니다. 14개의 디지털 입/출력 핀헤더(6개는 PWM 출력으로 사용 가능), 6개의 아날로그 입력 핀, 16MHz 세라믹 공진기(공명기), USB 포트, 전원 잭, ICSP 헤더, 그리고 Reset 버튼을 내장하고 있습니다. 또한 USB 케이블을 통해서 간편하게 컴퓨터와 연결하거나, AC-DC 아답터 또는 배터리를 활용해 쉽게 전원을 공급할 수 있는 등 마이크로컨트롤러를 활용하는데 필요한 모든 것들을 포함하고 있습니다.
ESP8266 Wi-Fi 모듈은 자기제어(Self-contained) SoC 칩셋으로, 와이파이 네트워크에 접속하기 위한 통합 TCP/IP protocol stack을 내장하고 있습니다. (또는 액세스포인트로도 활용 가능합니다) Uno Wi-Fi의 가장 유용한 기능 중 하나는, OTA (over-the-air) 프로그래밍을 지원해 온라인으로(케이블 없이) Arduino sketches나 Wi-Fi firmware의 데이터 이동이 가능합니다.
ARDUINO MICROPROCESSOR | |
---|---|
Processor | ESP8266 |
Architecture | Tensilica Xtensa LX106 |
Operating Voltage | 3.3 V |
Flash Memory | 4 MB |
RAM | 8 MB instruction, 12 MB data |
Clock Speed | 80 MHz |
WiFi | 802.11 b/g/n 2.4 GHz |
Wake up time | < 2 ms |
ARDUINO MICROCONTROLLER | |
---|---|
Microcontroller | ATmega328 |
Architecture | Atmel AVR 8-bit |
Operating Voltage | 5 V |
Flash memory | 32 KB |
SRAM | 2 KB |
Clock Speed | 16 MHz |
Analog I/O Pins | 6 |
EEPROM | 1 KB |
DC Current per I/O Pins | 40 mA |
GENERAL | |
---|---|
Digital I/O Pins | 20 |
PWM Output | 6 |
Power Consumption | 93 mA |
PCB Size | 53 x 68.5 mm |
Weight | 0.025 Kg |
Product Code | A000133 |
PowerTechnical Specifics
외부 전원을 통해 전원을 공급할 때는 6~20V 전압으로 제품을 작동할 수 있습니다. 하지만 7V 미만의 전원만이 공급되는 경우에는 5V 핀에서도 5V 이하의 전원이 공급될 수 있으며, Uno Wifi 보드 역시도 불안정한 작동을 할 수 있습니다. 또한 12V를 초과하는 전압을 공급하는 경우에는 전압 레귤레이터가 과열되거나 보드에 손상을 입힐 수도 있습니다. 즉 7V에서 12V의 전압 공급을 권장합니다.
전원 Pin 상세 설명 :
- VIN. 외부 전원을 사용할 때 아두이노 보드에 입력되는 전압. (USB 연결 혹은 기타 조정된 전원 소스의 5 전압과는 반대)
- 5V. 보드의 레귤러에이터로부터 조정된 5V로 출력되는 핀. 보드는 DC 전원잭 (7 – 12V), USB 커넥터 (5V)나 보드의 VIN pin (7-12V) 5V나 3.3V 핀을 통한 공급 전압은 레귤레이터를 우회하고, 보드는 손상될 수 있습니다. 권장하지 않습니다.
- 3V3. 온보드 레귤레이터에 의해 발생된 A 3.3 전압 공급. 최대 전류는 1 A 입니다. (파워 입력 소스에 따른)
- GND. 접지 핀.
- IOREF. 아두이노 보드의 이 핀은 마이크로컨트롤러가 작동하는 전압 기준을 제공합니다. 올바르게 구성된 쉴드는 IOREF 핀 전압을 읽고 적절한 전원 소스을 선택하거나 5V 또는 3.3V 전원 공급 장치로 작업하기 위해 출력에서 전압 변환기를 활성화하십시오.
Memory
Input and Output
- Serial: 0 (RX), 1 (TX). TTL 시리얼 데이터를 수신 (RX) 및 전송(TX) 하는 데 사용됩니다. 이 핀은 ATmega8U2 USB-to-TTL 시리얼 칩의 해당 핀에 연결됩니다.
- 외부 인터럽트: 2, 3. 이 핀은 낮은 값, 어떤값의 상승, 하락할 때 인터럽트가 발생하도록 할 수 있습니다. 자세한 사항은 attachInterrupt() 기능을 참조 하십시오.
- PWM: 3, 5, 6, 9, 10 and 11. analogWrite() 을 사용하여 8-bit PWM 출력을 제공합니다.
- SPI: 10 (SS), 11 (MOSI), 12 (MISO), 13 (SCK). 이 핀은 . SPI 라이브러리를 사용하는 SPI 통신을 지원합니다.
- LED: 디지털 핀 13으로 연결된 LED가 내장되어 있습니다. 이 핀이 높은 값일 때, LED는 켜집니다. 핀이 낮은 값을 때에는 꺼집니다. 우노는 6 아날로그 입력이며, 10비트의 분해능(1024 다른 값)이 제공되는 A0 부터 A5까지 표시됩니다. AREF 핀, analogReference()를 사용하여 범위의 상단을 변경할 수 있지만 기본적으로 접지에서 5V까지 측정됩니다. 게다가 몇몇의 핀은 기능적으로 특화되어 있습니다.
- TWI: TWI: A4 혹은 SDA 핀, A5 혹은 SCL 핀. 와이어 라이브러리를 사용하여 TWI 통신 지원. 참조: A4 혹은 SDA 핀, A5 혹은 SCL 핀은 IO 확장기 SC16IS750IBS에서 사용중입니다. 보드에 다른 두개의 핀이 있습니다:
- AREF. Reference voltage for the analog inputs. Used with analogReference(). 아날로그 입력을 위한 기준 전압. analogReference() 사용.
- RESET. 라인을 LOW로 가져와서 마이크로컨트롤러를 리셋하십시오. 일반적으로 보드의 차단하는 쉴드에 리셋버튼을 추가합니다.
Communication
The Arduino Uno Wi-Fi 는 컴퓨터, 다른 아두이노, 다른 마이크로컨트롤러와 통신하는 많은 시설을 갖추고 있습니다. ATmega328는 디지털 핀 0 (RX), 1 (TX) 로 사용 가능한 UART TTL (5V) 시리얼 통신을 제공합니다. 보드상의 ATmega16U2 채널의 USB 연결된 시리얼 통신은 컴퓨터 소프트웨어에 대한 가상 컴포트로 나타납니다. 16U2 펌웨어는 표준화된 USB COM 드라이버를 사용하며, 외부 드라이버가 필요 없습니다. 그러나 윈도우에서는, inf 파일이 요구됩니다. 아두이노 소프크웨어는 아두이노 보드로부터 온 간단한 문자 데이터가 허용된 시리얼 모니터를 포함합니다. 보드의 RX, TX LEDs는 데이터가 USB 시리얼 칩, USB 연결을 통해 전송될 때, 깜박입니다. (그러나 핀0, 1은 시리얼 통신이 아님).
소프트웨어 시리얼 라이브러리는 어떤 우노의 디지털 핀의 시리얼 통신에도 허용됩니다.
ATmega328은 I2C (TWI)와 SPI 통신을 물론 지원합니다. 아두이노 소프트웨어는 I2C 버스의 사용을 간단하게 하는 와이어 라이브러리를 포함합니다; SPI 라이브러리 사용에는 SPI 통신을 사용하십시오.
Arduino Uno Wi-Fi에는 AtMega 16u2, AtMega 328p 및 ESP8266EX 사이의 통신을 허용하는 I2C 버스/ SPI 인터페이스가 있는 SC16IS750IBS IO 확장기 단일 UART가 있습니다.
The Arduino Uno Wi-Fi를 사용하면 보드에 장착된 센서나 엑츄에이터에 와이파이로 통신하여 IoT 시스템을 쉽고 빠르게 만들 수 있습니다. Arduino Uno Wi-Fi를 Wi-Fi network 클라이언트로 다른 장치와 연결하는 서버로 사용하거나 ad’hoc Wi-Fi 연결을 만들 수 있습니다. Arduino Uno Wi-Fi를 통해 인터넷으로 접속하는 완벽한 방법은 Ciao Library이며 REST connector를 사용하는 것입니다. Arduino Uno Wi-Fi에는 브라우저를 통해 즉시 보드를 명령할 수 있는 RestServer 스케치가 미리 업로드되어 있습니다: Arduino Uno Wi-Fi SSID를 연결하십시오, http://192.168.240.1/arduino/digital/13/1 링크로 이동하여 LED L을 켜십시오.
* “/arduino/digital/13″ -> digitalRead(13)
* “/arduino/digital/13/1″ -> digitalWrite(13, HIGH)
* “/arduino/analog/2/123″ -> analogWrite(2, 123)
* “/arduino/analog/2″ -> analogRead(2)
* “/arduino/mode/13/input” -> pinMode(13, INPUT)
* “/arduino/mode/13/output” -> pinMode(13, OUTPUT)
Programming
Automatic (Software) Reset
이 설정에는 다른 의미가 있습니다. Uno Wifi가 Mac OS X 또는 Linux를 실행하는 컴퓨터에 연결되면 소프트웨어가 USB를 통해 연결될 때마다 리셋됩니다. 0.5초 동안, 부트로더는 Uno에서 실행중입니다. 형식이 잘못된 데이터(새 코드를 업로드하는 것 이외에)를 무시하도록 프로그래밍 되어있지만, 연결이 되면 보드에 전송된 데이터의 처음 몇 바이트는 차단됩니다. 보드에서 스케치가 시작될 때 일회성 구성이나 다른 데이터를 수신하는 경우 해당 데이터를 전송하기 전에 연결하고나서 통신하는 소프트웨어가 대기하고 있는지 확인하십시오.
Uno Wifi는 자동 리셋을 사용할 수 없도록 차단하는 trace가 포함되어 있습니다. trace의 양쪽 패드를 함께 납땜하여 다시 활성화시킬 수 있습니다. 이것은 “RESET-EN”라고 부릅니다. 물론 110V 저항을 5V에서 리셋 라인에 연결하여 자동 리셋을 비활성화할 수도 있습니다.
USB Overcurrent Protection
Physical Characteristics
Documentation
- Eagle files: arduino-uno-wifi-reference-design.zip
- Schematic: arduino-uno-wifi-schematic.pdf
Arduino를 시작하거나 보드에 지금 바로 실행해보세요. Arduino UNO WiFi 시작Getting Started
Tutorials
- RestServer only for ESP-Link firmware
- RestClient only for ESP-Link firmware
- MqttSub only for ESP-Link firmware
- MqttPub only for ESP-Link firmware
- WebServerBlink only for ESP-Link firmware
- WebServer only for ESP-Link firmware
- RestServer and RestClient only for ESP-Link firmware
- ReadMacAddress only for ESP-Link firmware
- CiaoRestClient-ThingSpeak only for ESP-Link firmware
Advanced
[42호]아두이노, 상상을 현실로 만드는 JUNE KIT
10MAKE
아두이노, 상상을 현실로 만드는 JUNE KIT
사람들은 끊임없이 무언가를 만들어낸다. 그리고 나이, 성별, 국적 상관없이 아이디어를 가진 누구나 상상을 현실로 만들 수 있다.
아두이노는 현재 전 세계에서 가장 널리 사용되고 있는 오픈소스 기반의 피지컬 컴퓨팅 플랫폼으로 하드웨어나 소프트웨어의 전문 지식이 없어도 누구나 쉽게 배우고 사용할 수 있도록 개발되어 많은 사람들에게 널리 보급되어 사용되고 있다.
JUNE KIT는 아두이노를 처음 접하는 초보자들이 쉽게 배울 수 있도록 구성한 키트이다. 아두이노를 단순히 책으로 배우는 것이 아니라, 저자가 직접 구성한 키트와 직접 제작한 동영상 강의를 모두 담아 쉽고 편리하게 마스터할 수 있도록 준비된 제품이다. 아두이노 호환보드, 브레드보드, 초음파센서, 서보모터, 조도센서, LED 등 필요한 모든 부품이 JUNE KIT 안에 포함되어 있다.
아두이노를 어디서부터 시작해야 할지 고민하시는분, 전기·전자에 대한 배경지식이 없어 망설이시는 분, 아두이노 프로젝트를 진행해보고싶으신 분들을 위한 제품이다
상상을 현실로 만드는 메이커를 위한 키트, 아두이노, 상상을 현실로 만드는 프로젝트 Kit Only (키트 단품)은 디바이스마트 홈페이지(http://www.devicemart.co.kr)에서 구매 가능하다.
아두이노, 상상을 현실로 만드는 JUNE KIT 제품 상세 구경하러가기
[42호]실내 위치 인식용 마커의 무결성 검사 로봇
2016 ICT 융합 프로젝트 공모전 우수상
실내 위치 인식용 마커의 무결성 검사 로봇
글 | 한국과학기술원 김규광, 최덕규, 임휘준
심사평
뉴티씨 실내 위치 인식 등에 관한 여러가지 연구들이 그동안 많이 진척되었는데, 그 중 한가지 아이템에 집중하여 결과를 낸 부분이 인상적이다. 실제로 로봇을 제작하여, NFC와 QR 코드를 인식하고, 자신의 위치를 보정하는 등 현재 위치를 확인하고, 이를 통하여 로봇의 위치 인식으로 물류 시스템 등에서 사용할 수 있도록 하려고 노력한 점이 돋보인다. 다만, 실제 상황에서의 고려 사항인 안전, 전원문제, 기구적인 토크 문제, 거리 문제 등은 고려되지 않은 점이 아쉽다. 실질적인 연구로 이어질 수 있을 것 같아서 좋은 점수를 주었다.
칩센 이미 구현된 기술에서 새로운 실용적인 아이디어를 도출했다, 실제 완성도를 조금만 더 높일 수 있고 마커를 활용한 물류 로봇들과 운용에서 문제를 없앨 수 있다면 상품화에 도전해도 될 만큼 좋은 아이디어 같다.
위드로봇 이동 로봇의 자기 위치 추정 문제는 꽤 오랫동안 로봇공학자들이 풀려고 고생하고 있는 난제입니다. 해당 작품은 QR 코드와 RFID 및 엔코더를 누적한 DR로 제한된 환경 내에서 위치 추정 문제를 풀려고 시도한 작품입니다. 아쉬운 부분은 융합 알고리즘을 너무 단순화하여, 전체 시스템의 성능이 더 나은 결과를 낼 수 있음에도 불구하고 그만큼 나오지 않는 부분입니다. 그리고 부수적이긴 하지만 QR 코드는 코드 자체적으로 오염에 의한 손상 문제에 대해 값을 복원할 수 있는 오류 정정 코드를 삽입할 수 있는 기능이 있습니다. 이러한 부분도 같이 고려되면 완성도가 훨씬 높아질 것 같습니다. 실제 로봇을 만들어 아이디어를 확인하는 과정이 쉽지 않는데, 결과를 도출해 낸 점에 박수를 보냅니다.
작품 개요
다양한 목적에 로봇을 적용하려는 시도가 늘어나고 있으며 실제로 물류 창고 등에서 로봇이 사용되기도 하는 등 실내 환경에서의 로봇의 임무 수행을 위한 위치 인식과 추정에 대한 관심이 증가하고 있다. 간단한 Monte Carlo Localization에서부터 레이저 스캐너, 카메라 등을 이용한 SLAM 기술, 전파 신호의 삼각 측량이나 fingerprinting을 이용한 위치 측위 등 다양한 기술이 개발되었으나 센서의 가격으로 인한 다수의 로봇 제작 비용의 증가, 전파 신호 측정값의 불안전성 등 여러 이유로 인해 일정 간격으로 마커를 부착하고 이를 로봇이 인식해 엔코더 정보 등과 조합 함으로서 위치를 인식하는 방식 또한 꾸준히 사용되어 왔다. 특히 마커 설치에 대한 미관상의 부담이 없으며 다수의 로봇을 안정적이고 효율적으로 운영해야 하는 물류 창고에 보통 적용되며 아마존 사의 KIVA 로봇의 경우 이러한 부착식 마커 시스템을 사용하는 대표적인 성공 사례이다. 하지만 이러한 부착식 마커의 경우 로봇 주행 및 기타 원인에 의한 물리적인 충격이나 먼지 등의 오염물질에 의해 손상될 수 있다. 대형 물류 창고의 경우 이러한 손상을 사람이 일일이 검사한다는 것은 쉬운 일이 아니며 발생할 경우 물류 체계에 비효율성을 불러올 수 있다. 본 프로젝트에서는 차륜 형태의 실내에서 주행 가능하고 QR 코드 마커를 인식해 로봇의 엔코더/센서를 이용한 위치와 비교하여 해당 위치에 설치된 QR 코드가 정상 상태인지를 확인하는 로봇을 개발하고자 한다. 또한 RFID/NFC 태그를 QR 코드와 같이 사용하여 QR 코드 손상 시에도 위치 인식을 도와줄 수 있는 시스템을 제안하고자 한다.
작품 설명
주요 동작 및 특징
Differential Wheel 형태의 차량형 주행 로봇이며 실내 주행을 돕기 위해 Wi-Fi를 이용하여 카메라 영상과 엔코더, 나침반 센서의 데이터를 전송하며 웹기반 조종 인터페이스를 제공한다. 로봇에는 스마트폰이 장착되어 있어 이를 이용하여 QR 코드와 NFC 태그를 인식한다.
전체 시스템 구성
로봇의 주 제어장치는 라즈베리파이 보드를 사용하였으며 센서와 하드웨어 인터페이스를 위해 아두이노 보드를 부착하였다. 로봇 프레임에 장착된 서보모터를 사용해 문턱 등의 장애물을 손쉽게 넘어갈 수 있으며 모터에 부착된 엔코더와 지자기 센서를 사용해 현재 위치를 계산한다. 지자기센서는 모터와 회로 등의 자기장에 영향을 받지 않도록 앞으로 길게 빼서 설치하였으며 제작 후에는 360도 회전시켜 heading 각을 계산하기 전 보정을 해 주었다. 아두이노는 센서 데이터를 모아 라즈베리파이 보드로 전달하며 모터 제어 명령이 내려올 경우 해당하는 모터를 움직인다. (그림 1.)
그림 1 |
라즈베리파이에서는 시리얼 통신을 통해 아두이노와 통신하며 제어용 웹 서버를 구동한다. 통상적으로 블루투스나 RC 수신기를 이용한 1대 1통신을 사용하며 많은 DIY프로젝트에서는 안드로이드 어플리케이션을 조종 인터페이스로 사용하나 본 프로젝트에서는 web 인터페이스를 사용해보고자 하였다. Wi-Fi를 통해 연결하므로 영상 전송 등에 필요한 넓은 대역폭과 빠른 속도가 보장되며 소켓 핸들을 계속 관리해 줘야 하는 TCP/IP통신과는 다르게 비동기식 통신인 request 방식을 사용해 일시적인 연결 장애에도 더 강인한 특징이 있다. 또한 안드로이드에서만 구동 가능한 앱과는 달리 웹 브라우저가 구동 가능하고 같은 ip대역 내에만 들어 있으면 접속하는 장비의 기종에 상관 없이 사용이 가능하다는 장점을 갖고 있다. (그림 2.)
그림 2 |
또한 jQuery Mobile을 사용하여 웹에서 폰의 앱과 비슷한 UI를 구현하였다. 조종 버튼을 누르거나 슬라이드가 움직이면 javascript 코드에 의해 POST request가 웹서버로 보내지며 서버는 수신받은 값을 아두이노로 보내 사용자의 요청에 맞춰 구동한다. (그림 3,4)
그림 3 |
그림 4 |
통상의 리니어 5V 출력 레귤레이터로는 라즈베리파이와 서보, 구동용 모터 등에 전력을 공급하기 힘들어 직접 전원 모듈을 제작하였다. 주 전력인 DC 5V는 스위칭 레귤레이터를 사용하여 큰 발열 없이 수 암페어 단위의 출력을 낼 수 있도록 하였으며 코일이나 커패시터 등의 회로 소자도 해당 전류를 견딜 수 있는 소자를 사용하였다. 지자기센서에 사용되는 3.3V는 많은 전력을 소비하지 않아 회로 단순화를 위해 리니어 레귤레이터를 사용하였다. 11.1V 리튬 폴리머 배터리를 사용하여 시스템 구동에 필요한 충분한 전력을 넣어주었다. (그림 5.)
그림 5 |
안드로이드 스마트폰을 부착시키기 위해 홀더를 직접 설계하였으며 QR 코드를 읽을 적절한 초점거리를 벌리면서도 NFC 태그를 읽을 수 있는 거리를 맞추기 위해 높이 조절이 가능하도록 3D 프린터의 히트베드 위치를 조절하는데 쓰이는 것과 같은 구조의 스프링과 나사를 같이 사용하였다. 설계한 홀더는 3D 프린터를 사용하여 제작하였으며 로봇에 부착하였다. (그림 6, 7) QR 코드와 NFC 태그를 읽을 수 있는 앱 또한 개발하였다 (그림 8).
그림 6 |
그림 7 |
그림 8 |
위치 인식은 엔코더 데이터와 지자기센서를 사용하여 진행하였으며 QR 코드에 비해 손상되기가 힘들고 인식이 편리한 NFC 태그를 우선하여 위치 보정을 하였다. NFC 태그에는 QR 코드를 부착하였으며 QR 코드와 동일한 데이터를 NFC 태그에 입력하였다. (그림 9)
그림 9 |
꺾어진 모양의 주행 코스를 선정하고 가운데 점과 양 꼭지점에 태그를 붙여 총 9장의 태그를 설치하였다. (그림 10)
그림 10 |
주행 코스를 따라 로봇을 조종하여 이동하였으며 NFC 태그 인식 데이터와 모터 입력값, 엔코더 제어값을 모두 저장하여 off line 분석에 사용하였다. 위치 보정에는 EKF SLAM, RFID SLAM 등의 사용을 고려했으나 SLAM 특유의 연산량과 marker의 위치를 모두 연산에 포함해야 하는 문제점(EKF SLAM의 경우) 등이 있어 좀 더 간단한 방식으로 보정계산을 진행하였다. NFC 태그가 인식될 경우 로봇의 현재 좌표를 기존의 이동 궤적을 모두 무시하고 NFC 태그 정중앙으로 이동시켜 엔코더를 사용함으로써 생기는 문제 중 하나인 오차 누적에 의한 drift를 해결하고자 하였다. 아래 그림의 differential wheel 로봇 모델을 사용하여 odometry를 우선적으로 계산하였다. (그림 11)
그림 11 |
로봇이 바라보는 방향(Heading)의 경우 엔코더를 사용하지 않고 지자기센서를 사용한 자북극 기준 heading 방향을 사용하였다. 실제 이동한 위치를 좌표평면으로 표현하여 의도된 주행 궤적대로 위치 추정 결과가 나오는지를 확인하였다. 수집된 데이터는 Python 언어와 matplotlib 라이브러리를 사용하여 그림 12에 시각화하였다.
그림 12 |
파란 선이 Ground Truth, 노란 점이 NFC 태그의 위치이며 녹색 선이 엔코더로만 계산한 궤적, 붉은 선이 NFC 태그를 이용해 보정한 이동 궤적이다. 엔코더만 사용하여 이동 궤적을 계산한 경우 엔코더의 drift 오차, 좌우 엔코더의 인식률 차이, 나침반의 heading 오류 등에 의해 한번의 이동 궤적 오류가 전체적인 이동 궤적에 누적된 오차를 주는 drift 를 볼 수 있다. 반면 NFC 태그를 이용하여 NFC 태그를 밟은 경우 바로 위치를 NFC 인식 지점으로 옮겨버리는 경우 이러한 기존의 오차의 영향을 받지 않고 ground truth에 맞는 위치로 옮겨감으로서 위치가 보정되는 것을 볼 수 있다. 정확한 위치를 알아 냄으로서 QR 코드/NFC 태그의 위치 또한 알 수 있고 이를 이용하여 QR 코드나 NFC 태그가 손상되더라도 상호 검증을 통해 손상된 marker를 알려줄 수 있다.
본 프로젝트에서는 로봇의 실내 위치 인식에 사용되는 로봇의 marker의 정상 여부를 검증하기 위해 QR 코드(혹은 apriltag 등의 visual 마커) 와 NFC/RFID 태그를 동시에 사용하여 한 쪽이 손상되더라도 다른 마커를 사용하여 오류를 검사하는 방식을 제안하였으며 이 임무를 수행할 수 있는 로봇을 제작하였다. 제작된 로봇은 엔코더와 나침반을 사용해 자신의 이동 위치를 알 수 있으며 안드로이드 폰을 사용하여 NFC와 QR 코드를 인식할 수 있다. 마커 인식을 통해 자신의 위치를 보정할 수 있으며 이를 이용해 “다른 마커가 인식되어야 할 자리에 QR코드/NFC 중 하나만 인식되거나 둘 다 인식되지 않는 경우 이를 사용자에게 알려 줌으로서 대형 물류창고 등에서 로봇의 위치인식을 이용한 process가 원할하게 구동되도록 도와줄 수 있을 것이라 기대된다.
또한 실내 위치 인식의 정확도를 올리기 위해 Wi-Fi, Zigbee 등의 무선 비콘 및 여러 방식이 사용되고 fingerprinting 방식 등 모든 구역에서의 전파 신호 세기를 수집하여 기계학습을 이용해 실내 위치를 인식하려는 시도 또한 사용되고 있다. 이때 사람이 직접 이러한 신호를 수집하는 것은 매우 불편하며 이미 QR코드 등의 기존 마커가 사용될 경우 이 로봇을 이용하여 각 위치 별 전파 신호 수집 등을 수월하게 진행하는 등의 추가적인 활용 또한 기대된다.
개발 환경
아두이노와 아두이노 IDE를 사용하였으며 라즈베리파이 보드에 전용 Debian 리눅스를 올려 사용하였으며 vim 에디터 개발 환경에서 python을 사용하여 프로그램하였다. 인터페이스 웹서버는 python의 BaseHTTPServer 모듈을 사용하여 구동하였으며 HTML과 jQuery Mobile을 사용하여 웹 인터페이스를 제작하였다. 영상 전송에는 OpenCV를 사용하였다.
기타 (회로도/소스코드)
아두이노 소스코드
#include <Servo.h>
#include <Wire.h>
#include <HMC5883L.h>
#include <string.h>
#define MAX_LEN 30
char cmd_array[MAX_LEN];
Servo servo_arm;
HMC5883L compass;
int error = 0;
int rignt_motor_A = 10;
int rignt_motor_B = 11;
int rignt_motor_power = 6;
volatile int right_wheel_count = 0;
int left_motor_A = 7;
int left_motor_B = 8;
int left_motor_power = 5;
volatile int left_wheel_count = 0;
int servo_arm_port = 9;
void setup()
{
Serial.begin(9600);
Wire.begin();
compass = HMC5883L();
error = compass.SetScale(1.3); //Set compass scale
if(error != 0) { Serial.println(“Compass ERR 1″);}
error = compass.SetMeasurementMode(Measurement_Continuous); //Set cont. measure mode
if(error != 0) { Serial.println(“Compass ERR 2″);}
attachInterrupt(0, right_wheel_encoder, CHANGE); // Right Wheel Encoder
attachInterrupt(1, left_wheel_encoder, CHANGE); // Left Wheel Encoder
servo_arm.attach(servo_arm_port);
servo_arm.write(140); //180 (max down) 140 (midpoint) 0(original)
pinMode(rignt_motor_A,OUTPUT);
pinMode(rignt_motor_B,OUTPUT);
pinMode(rignt_motor_power,OUTPUT);
pinMode(left_motor_A,OUTPUT);
pinMode(left_motor_B,OUTPUT);
pinMode(left_motor_power,OUTPUT);
}
void set_servo_angle(int angle)
{
servo_arm.write(angle);
delay(15);
}
void set_motor_value(int right_dir, int left_dir, int right_speed, int left_speed, int delay_time)
{
if(right_dir == 1) //front
{
digitalWrite(rignt_motor_A,HIGH);
digitalWrite(rignt_motor_B,LOW);
}
if(right_dir == 0) //back
{
digitalWrite(rignt_motor_A,LOW);
digitalWrite(rignt_motor_B,HIGH);
}
if(left_dir == 1) //front
{
digitalWrite(left_motor_A,HIGH);
digitalWrite(left_motor_B,LOW);
}
if(left_dir == 0) //back
{
digitalWrite(left_motor_A,LOW);
digitalWrite(left_motor_B,HIGH);
}
analogWrite(rignt_motor_power,right_speed);
analogWrite(left_motor_power,left_speed);
delay(delay_time);
digitalWrite(rignt_motor_power,0);
digitalWrite(left_motor_power,0);
}
float read_compass()
{
MagnetometerScaled scaled = compass.ReadScaledAxis();
int MilliGauss_OnThe_XAxis = scaled.XAxis;
float heading = atan2(scaled.YAxis+136, scaled.XAxis+40);
//float declinationAngle = 0.0457;
//heading += declinationAngle;
if(heading < 0) {heading += 2*PI;}
if(heading > 2*PI) {heading -= 2*PI;}
float deg_heading = ((heading * 180)/PI)-9;
if(deg_heading < 0) {deg_heading += 360;}
return deg_heading;
}
void right_wheel_encoder() {right_wheel_count++;}
void left_wheel_encoder() {left_wheel_count++;}
void send_all_sensor_val()
{
float heading = read_compass();
Serial.print(“r,”);
Serial.print(heading);
Serial.print(“,”);
Serial.print(right_wheel_count);
Serial.print(“,”);
Serial.print(left_wheel_count);
Serial.print(“,”);
MagnetometerRaw raw = compass.ReadRawAxis();
Serial.print(raw.XAxis);
Serial.print(“,”);
Serial.print(raw.YAxis);
Serial.print(“,”);
Serial.print(raw.ZAxis);
Serial.print(“,”);
Serial.println(“”);
right_wheel_count = 0;
left_wheel_count = 0;
}
void loop()
{
if(Serial.available()>0)
{
String cmd = Serial.readStringUntil(‘\n’);
//s,servo_angle,\n <- servo cmd
//s,140,\n
//m,right_fb,left_fb,right_pw,left_pw,time,\n <- move cmd
//m,1,0,125,125,500,\n
//r,0\n <- request cmd
char *ptr;
cmd.toCharArray(cmd_array,MAX_LEN);
char * header = strtok(cmd_array, “,”);
if(strcmp(header,”s”) == 0) //data request
{
int servo_val = atoi(strtok(NULL, “,”));
set_servo_angle(servo_val);
}
if(strcmp(header,”m”) == 0) //move command
{
int right_direction = atoi(strtok(NULL, “,”));
int left_direction = atoi(strtok(NULL, “,”));
int right_power = atoi(strtok(NULL, “,”));
int left_power = atoi(strtok(NULL, “,”));
int power_time = atoi(strtok(NULL, “,”));
set_motor_value(right_direction, left_direction, right_power, left_power, power_time);
}
if(strcmp(header,”r”) == 0) //data request
{
send_all_sensor_val();
}
}
delay(200);
}
#!/usr/bin/python
import BaseHTTPServer
import SimpleHTTPServer
import SocketServer
import json
import serial
STOP_CODE = 0
HEADING = 0
L_ENCODER = 0
R_ENCODER = 0
#com = serial.Serial(“/dev/ttyACM0″)
com = serial.Serial(“COM11″)
#LOG_FILE = open(“rover_log.txt”,’w')
class ThreadServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
pass
#
class MyRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
def do_GET(self):
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
#
def do_POST(self):
global STOP_CODE
global HEADING
global L_ENCODER
global R_ENCODER
global LOG_FILE
length = int(self.headers.getheader(‘content-length’))
var = self.rfile.read(length) #code=f&data=125&delay=500, code=s&data=57
packet = var.split(“&”)
cmd = packet[0].split(“=”)[1]
if cmd == “f”: #Front
header = “m,”
right_dir = “1,”
left_dir = “1,”
right_pw = packet[1].split(“=”)[1]
left_pw = packet[1].split(“=”)[1]
time_delay = packet[2].split(“=”)[1]
ser_cmd = header + right_dir + left_dir + right_pw + “,” + left_pw + “,” + time_delay + “,\n”
print ser_cmd
com.write(ser_cmd)
#”"”
com.write(‘r,0\n’)
raw_sensor_str = com.readline() #r,heading,rencoder,lencoder,mag_x,mag_y,mag_z,\n
#”"”
sensor_data = raw_sensor_str.split(“,”)
HEADING = sensor_data[1]
L_ENCODER = sensor_data[3]
R_ENCODER = sensor_data[2]
#LOG_FILE.write(ser_cmd)
#LOG_FILE.write(raw_sensor_str)
if cmd == “b”: #Back
header = “m,”
right_dir = “0,”
left_dir = “0,”
right_pw = packet[1].split(“=”)[1]
left_pw = packet[1].split(“=”)[1]
time_delay = packet[2].split(“=”)[1]
ser_cmd = header + right_dir + left_dir + right_pw + “,” + left_pw + “,” + time_delay + “,\n”
print ser_cmd
com.write(ser_cmd)
#”"”
com.write(‘r,0\n’)
raw_sensor_str = com.readline() #r,heading,rencoder,lencoder,mag_x,mag_y,mag_z,\n
#”"”
sensor_data = raw_sensor_str.split(“,”)
HEADING = sensor_data[1]
L_ENCODER = sensor_data[3]
R_ENCODER = sensor_data[2]
#LOG_FILE.write(ser_cmd)
#LOG_FILE.write(raw_sensor_str)
if cmd == “r”: #Right
header = “m,”
right_dir = “0,”
left_dir = “1,”
right_pw = packet[1].split(“=”)[1]
left_pw = packet[1].split(“=”)[1]
time_delay = packet[2].split(“=”)[1]
ser_cmd = header + right_dir + left_dir + right_pw + “,” + left_pw + “,” + time_delay + “,\n”
print ser_cmd
com.write(ser_cmd)
#”"”
com.write(‘r,0\n’)
raw_sensor_str = com.readline() #r,heading,rencoder,lencoder,mag_x,mag_y,mag_z,\n
#”"”
sensor_data = raw_sensor_str.split(“,”)
HEADING = sensor_data[1]
L_ENCODER = sensor_data[3]
R_ENCODER = sensor_data[2]
#LOG_FILE.write(ser_cmd)
#LOG_FILE.write(raw_sensor_str)
if cmd == “l”: #Left
header = “m,”
right_dir = “1,”
left_dir = “0,”
right_pw = packet[1].split(“=”)[1]
left_pw = packet[1].split(“=”)[1]
time_delay = packet[2].split(“=”)[1]
ser_cmd = header + right_dir + left_dir + right_pw + “,” + left_pw + “,” + time_delay + “,\n”
print ser_cmd
com.write(ser_cmd)
#”"”
com.write(‘r,0\n’)
raw_sensor_str = com.readline() #r,heading,rencoder,lencoder,mag_x,mag_y,mag_z,\n
#”"”
sensor_data = raw_sensor_str.split(“,”)
HEADING = sensor_data[1]
L_ENCODER = sensor_data[3]
R_ENCODER = sensor_data[2]
#LOG_FILE.write(ser_cmd)
#LOG_FILE.write(raw_sensor_str)
if cmd == “s”: #Servo
servo_angle = packet[1].split(“=”)[1]
header = “s,”
ser_cmd = header + servo_angle + “,\n”
print ser_cmd
com.write(ser_cmd)
sensor_data_dict = {‘heading’:HEADING,’l_encoder’:L_ENCODER,’r_encoder’:R_ENCODER}
self.send_response(200)
self.send_header(“Content-type”, “text/html”)
self.end_headers()
self.wfile.write(json.dumps(sensor_data_dict))
#
#
if __name__ == “__main__”:
Handler = MyRequestHandler
server = ThreadServer((’0.0.0.0′,8080), Handler)
while not STOP_CODE == 1:
server.handle_request()
#LOG_FILE.close()
server.server_close()