[아두이노] 시각장애인을 위한 스마트 지팡이 원리 이해

IOT/아두이노|2019. 8. 14. 11:11

[아두이노] 시각장애인을 위한 스마트 지팡이 원리 이해



지난 시간에 진동모터에 대해 살펴보았습니다. 진동모터는 촉각을 느끼는 출력 부품입니다. 즉, 어떤 상황이 발생했을 때 그 상황에 대해 진동으로 인간에게 알릴 수 있습니다. 일상에서 진동모터는 어디에서 사용 할까요. 바로 스마트폰의 진동모드입니다. 스마트폰의 진동으로 전화나 문자가 왔을을 쉽게 알 수 있습니다. 또다른 응용사례가 뭐가 있을까요. 구글 검색을 통해 살펴보니 시각장애인을 위한 스마트 지팡이가 있더군요. 이 주제를 한번 가상시뮬레이터로 테스트 해보고 싶어지더군요. 참고로 제가 새로운 뭔가를 하루만에 창조하고 실험까지 하기는 시간적 여유가 없기 때문에 기존에 구현한 주제를 가지고 한번 가상시뮬레이터로 모방 실험을 통해 진동모터를 이해하는 시간을 갖고자 합니다.

1. 진동모터의 응용 사례 구글 검색



진동모터에 대한 응용 예제로 위 출처에 가시면 각 대학교 학생들이 만든 공모전 작품들에 대한 소개가 잘 나와 있습니다. 자세한 내용과 코딩은 읽지 않았습니다. 그 이유는 상상코딩에 방해가 되기 때문에 저만의 코딩을 할 수 없을 것 같아서 어떻게 스마트 지팡이를 구성했는지만 간단히 이미지를 참조하여 가상시뮬레이터에서 표현가능한 부품만을 이용하여 재구성해 보았습니다.

쉽게말해서, 이미지만 보고 한번 모방 표현을 해 보았는데 어떻게 했는지 본격적으로 살펴 볼까요. 참고로 스마트지팡이의 이미지는 출처에 가셔서 직접 보시기 바랍니다.

2. 스마트 지팡이 모방 재구성 이미지



  • 조도센서 : 보행 주변 조명 상태 확인
  • LED : 어두울 때 조명을 켜서 보행자을 알림등으로 활용
  • 초음파센서 : 3개의 초음파 센서로 벽감지 초음파센서는 전방에 벽을 확인하고 보행 초음파센서는 보행자의 보폭에 따른 장애- 물을 감지하고 바닥 초음파센서는 계단과 같은 하단의 장애물을 감지한다.
  • 진동모터 : 초음파센서를 통해 장애물이 감지되면 진동으로 장애물 감지를 알림

3. 스마트 지팡이 회로도


  • 준비물 : 초음파센서 3개, 조도센서 1개, 1k옴 2개, 220옴 1개, 진동모터 1개, 아두이노우노
  • 내용 : 진동모터는 3번핀, 초음파센서는 5,6,7번핀, LED은 13번핀, 조도센서 A0핀에 연결하시오.

구글 검색을 통해 재해석한 표현입니다. 실험에 사용한 주제는 원작은 위 링크 출처의 자료의 이미지를 모방한 실험으로 제작품은 아닙니다. 가상시뮬레이터에서 원리를 실험하는게 목적인 post 입니다.


상단 초음파센서는 벽(장애물) 감지이고 중앙 초음파센서는 보행(장애물) 감지이고 하단 초음파센서는 바닥(장애물) 감지합니다. 장애물이 감지되면 진동모터가 작동하여 진동으로 보행자에게 알린다. 조도센서는 스마트지팡이의 조명을 담당하면 주변이 어두울 때 지팡이에 조명이 켜지고 보행자을 식별할 수 있게 한다.

2. 코딩


설계 :

  • 조도센서를 통해서 LED 조명을 제어한다.
  • 3개의 초음파센서를 통해 3가지 장애물 진동 패턴을 다르게 한다.

먼저 조도센서를 통해 LED 조명을 제어해 볼까요.

조도센서를 읽기

int cds=analogRead(CDSPin);  

조도센서의 값이 100(임의값)미만이면 LED(조명)가 켜지고 100이상이면 LED(조명)가 꺼진다.

if(어두운지) 조명 켜기;
else 조명끄기;

위 if문을 코딩화 하면 다음과 같습니다.

if(cds<100) digitalWrite(LedPin,HIGH);   
else digitalWrite(LedPin,LOW);  

이제는 3개의 초음파센서를 통해 3가지 장애물 감지와 진동 패턴을 출력 해 볼까요.

가상시뮬레이터의 초음파센서는 3핀입니다. newPing 라이브러리를 이용하면 편한데 가상시뮬레이터에서 제공되지 않고 쓸려면 라이브러리 파일 안에 코딩을 전부 가상시뮬레이터로 복사해야 하기 때문에 수작업 코딩으로 실험했네요.

초음파 센서 읽기(사용자함수로 표현)

float UltrasonicDistance(int m_pin){
  pinMode(m_pin,OUTPUT); 
  digitalWrite(m_pin, LOW); 
  delayMicroseconds(2); 
  digitalWrite(m_pin,HIGH); 
  delayMicroseconds(10); 
  digitalWrite(m_pin,LOW); 
  
  pinMode(m_pin,INPUT);    
  float duration = pulseIn(m_pin, HIGH);  
  return duration / 57.5;  
}

3개의 초음파센서이니깐 위 사용자정의함수로 표현하면 loop()함수 안에서는 다음과 같이 3줄로 초음파센서 값을 읽으면 됩니다.

float v1=UltrasonicDistance(5);
float v2=UltrasonicDistance(6);
float v3=UltrasonicDistance(7);

각 초음파센서의 값을 읽었으면 진동을 출력해야 겠죠. 출력하기 전에 출력한 진동 패턴은 다음과 같이 설정 해보았습니다.

  if(v3<20){
    if(state == false){ //처음 장애물 감지때만 진입
          하단 초음파 초기상태;
    }    
    timeState = 200;  //진동 간격
  }
  else if(v2<30){
    if(state == false){      
      중앙 초음파 초기상태;
    }
    timeState = 500; //진동 간격
  }                    
  else if(v1<50) {
    if(state == false){      
      상단 초음파 초기상태;
    }
    timeState = 1000; //진동간격
  }
  else { //장애물 감지 안되면 초기화 시킴
    state = false;  
    digitalWrite(VibrationPin,LOW); 
  } 

위에서 초기상태는 장애물 감지 된 후의 동작이니깐 state 출력 상태로 바꾸고 초기 진동상태값은 true로 하고 이전시간값을 진동 간격만큼 처음 빼줍니다. 그 이유는 처음에는 무조건 진동을 먼저 시작하고 반복되게 하기 위해서 입니다. 참고로, Setup()함수에서는 강제적으로 delay(1000)하면 음수에 대한 오류는 발생하지 않습니다.

    if(state == false){ //처음 장애물 감지때만 진입
      state = true; //진동 출력 상태
      VibrationState=true; //진동모터의 상태
      timeVal = millis()-200; //진동이전값(처음에 무조건 진동해야하기 때문 -200을 함)
    }    

위 내용을 합치면,

  if(v3<20){
    if(state == false){ //처음 장애물 감지때만 진입
      state = true; //진동 출력 상태
      VibrationState=true; //진동모터의 상태
      timeVal = millis()-200; //진동이전값
    }    
    timeState = 200;  //진동 간격
  }
  else if(v2<30){
    if(state == false){      
      state = true;   
      VibrationState=true;
      timeVal = millis()-500;
    }
    timeState = 500;
  }                    
  else if(v1<50) {
    if(state == false){      
      state = true;   
      VibrationState=true;
      timeVal = millis()-1000;
    }
    timeState = 1000;    
  }
  else { 
    state = false;  
    digitalWrite(VibrationPin,LOW); 
  } 

이제 출력 상태를 지정을 했으니 출력을 해볼까요.

  if(state == true){ //출력 상태 확인
    if(millis()-timeVal>=timeState){ //출력 간격 체크
      digitalWrite(VibrationPin,VibrationState); //진동 출력
      VibrationState=!VibrationState; //진동출력상태 반전
      timeVal=millis(); //이전시간값
    }   
  } 

위와 같이 코딩함으로 진동을 울린건지 state값을 통해서 진동을 울리게 됩니다. 진동 울리는 간격은 millis()함수를 이용하여 딜레이함수 없이 딜레이 효과를 주었습니다. 왜! 이 원리를 이용했냐면 스마트지팡이에는 진동모터만 있는게 아닙니다. 다른 부품들도 실시간으로 동작을 처리해야 합니다. 그렇기 때문에 millis()함수를 이용하여 delay()효과를 주었네요. 이렇게 표현하면 조도센서에 대한 LED(조명) 변화는 동시에 처리할 수 있습니다.

종합해보면,

int timeState = 0;
boolean state = false;
boolean VibrationState = true;
unsigned long timeVal = 0;


const byte LedPin = 13;
const byte VibrationPin = 3;
const byte CDSPin = A0;

void setup(){
  
  //Serial.begin(9600);  
  pinMode(LedPin,OUTPUT);
  pinMode(VibrationPin,OUTPUT);  
  
  delay(1000);
}
void loop(){
  int cds=analogRead(CDSPin);  
  
  if(cds<100) digitalWrite(LedPin,HIGH);   
  else digitalWrite(LedPin,LOW);  
  
  
  float v1=UltrasonicDistance(5);
  float v2=UltrasonicDistance(6);
  float v3=UltrasonicDistance(7);
  
  
  if(v3<20){
    if(state == false){      
      state = true;
      VibrationState=true;
      timeVal = millis()-200;
    }    
    timeState = 200;  
  }
  else if(v2<30){
    if(state == false){      
      state = true;   
      VibrationState=true;
      timeVal = millis()-500;
    }
    timeState = 500;
  }                    
  else if(v1<50) {
    if(state == false){      
      state = true;   
      VibrationState=true;
      timeVal = millis()-1000;
    }
    timeState = 1000;    
  }
  else { 
    state = false;  
    digitalWrite(VibrationPin,LOW); 
  } 
  
  if(state == true){
    if(millis()-timeVal>=timeState){
      digitalWrite(VibrationPin,VibrationState);      
      VibrationState=!VibrationState;
      timeVal=millis();
    }   
  } 
 }
float UltrasonicDistance(int m_pin){
  pinMode(m_pin,OUTPUT); 
  digitalWrite(m_pin, LOW); 
  delayMicroseconds(2); 
  digitalWrite(m_pin,HIGH); 
  delayMicroseconds(10); 
  digitalWrite(m_pin,LOW); 
  
  pinMode(m_pin,INPUT);    
  float duration = pulseIn(m_pin, HIGH);  
  return duration / 57.5;  
}

4. 결과


아래 결과 영상만 보면 좀 이해가 안 될 수 있습니다. 위에 지팡이 이미지를 보고시고 사용된 부품의 위치와 가상시뮬레이터의 부품과 일치시켜서 어느정도 상상을 하면서 보셔야 이해가 될 듯 싶네요.


5. 문제점



문제점은 위 그림에서 보는 것 처럼 보행을 할 때 세부분으로 나눠서 3꼭지점을 지면 바닥에 닿고 나머지 부분은 h(높이)로 반원의 곡선을 그리면서 지팡이를 움직이게 됩니다. 지면에는 딱 세번의 위치를 찍을 뿐 지팡이는 대부분 공중에 떠있게 됩니다. 그래서 지면과의 거리를 측정하는 센서가 추가하여 지면에서 얼마만큼 높이이고 장애물과의 거리가 정확히 위치를 잡아야 하는데 이 부분은 가상시뮬레이터에서 생략했습니다. 그래서 실제 구현한다면 스마트 지팡이의 곡선 움직으로 인한 장애물 측정의 약간 문제가 생길 수 있습니다. 즉, 위 코딩대로 라면 하단 초음파 센서가 곡선을 그릴 때 중간 장애물을 하닥 근접 장애물로 인식한 진동음으로 울릴 수 있으며 지팡이가 포물선으로 움직이기 때문에 각도의 변화가 일어나기 때문에 그 각도에 대한 계산 부분이 코딩에 담겨져 있지 않습니다. 그래서 정확한 측정은 좀 더 코딩을 수학적 계산 부분이 필요합니다. 위 코딩은 단순한 가상시뮬레이터에서의 스마트 지팡이의 단순 원리를 테스트 하기 위한 내용이니 실제 구현시에는 여러가지를 고려하야 정확한 장애물 위치에 대한 진동을 만드셔야 합니다.

원래는 초음파센서로만 스마트지팡이를 구성하고 장애물을 감지하는데에는 한계가 있습니다. 사실 초음파센서보다는 진짜 제대로 된 스마트 지팡이를 구현한다면 차라리 영상처리를 이용한 장애물 감지 기술을 공부하여 구현해보는 것이 좋습니다. 자율주행차에 대해 요즘 관심이 많고 대학들이 그와 관련된 연구를 하는 곳들이 많습니다. 이런 연구하는 대학에서 영상처리를 공부하는 학생들이 자율주행차보다 자율보행지팡이 쪽으로 연구가 이루어진다면 진짜 제대로 된 스마트 지팡이가 만들어 질거라 생각됩니다.

주변사물을 영상인식으로 통해 그 정보를 소리와 진동으로 알려준다면 제대로 된 보행을 할 수 있을 거라 생각됩니다. 자율주행차와 같은 원리를 자율보행안내 지팡이 연구를 한다면 그 기술은 자율주행차에도 응용 가능하기 때문에 연구의 가치가 있습니다.

마무리


오늘은 진동모터의 응용 사례를 구글 검색으로 통해서 장애인을 위한 스마트 지팡이이라는 아이템을 찾아 가상시뮬레이터에서 간단히 테스트를 해보았습니다. 이미지를 보고 바로 코딩을 하다보니깐 코딩이 별로 마음에 안들고 좀 길어서 보기 안좋습니다. 이미지를 응용하고 코딩을 만드는데 2시간정도 시간이 걸렸네요. 저녁에 실험하다보니 이번 post를 좀 연기할 까 했는데 그래도 완성은 시켰네요. 완성해놓고 보면 별거 없는데 몇가지 상상했던 코딩들 중에 하나를 선택해서 제가 의도한 방향으로 코딩을 하다보니 좀 시간이 길어져 버렸습니다. 하루만에 완성하려고 하니 볼품없는 post가 되었네요. 실제 제작을 해도 원하는 결과는 좀 얻기 힘들거라 생각됩니다. 대충 어떤 의미로 동작하는지 그 의미만 가상시뮬레이터로 실험한 post라고 이해 하셨으면 합니다.

다른 응용사례를 찾지 못했는데 혹시 찾으신다면 여러분들도 저처럼 이렇게 모방 실험을 한번 해보세요. 모방이라도 코딩을 보지 않고 이미지만 보고 응용하여 의미를 이해하고 자신의 상상으로 재해석해서 회로도를 만들어 보세요. 그리고 재해석된 회로도의 표현들을 자신의 상상력을 동원하여 코딩해 보세요. 위 코딩은 초벌 코딩으로 간단히 원리를 실험한 코딩이지만 위 코딩에서 좀 더 개선하면 좋은 코딩이 될거라 생각됩니다. 여러분들은 위 코딩을 그대로 하지 말고 원리만 이해하시고 여러분들 스스로가 여러분만의 코딩으로 재해석하여 코딩을 만들어 보셨으면 합니다.


댓글()

[아두이노] 진동센서(Vibration Sensor) 제어

IOT/아두이노|2019. 8. 13. 09:00

[아두이노] 진동센서(Vibration Sensor) 제어



지난시간에 다룬 진동모터는 진동을 외부로 출력하는 부품인데 반대로 진동을 감지하여 내부로 입력을 받는 진동센서 부품이 있습니다. 진동에 대한 전기 신호를 만들어 내는데 디지털 신호와 아날로그 신호로 만들어 냅니다. 아무튼 진동을 감지 할수 있는 이 센서는 참 재밌는 부품입니다. 만약에 이 진동센서를 특정위치에 부착하면 그 대상물에 진동이 발생했을 때 진동센서로 진동을 감지하여 전기 신호로 만들어 낼 수 있습니다. 가령 대상물에 누가 건들거나 또는 대상물 주변에 진동이 발생하는 상황에서 아두이노에서 그에 대한 특정한 명령을 내릴 수 있습니다. 즉, 진동 감지 원리를 이용하여 도난방지나 침입 방지를 위한 경보기와 같은 곳에서 응용할 수 있습니다. 예를 들면은 차량에 누가 터지하거나 충격을 주게 되면 차량에서 경보음이이 울리는 것으 많이 보셨을 꺼에요. 그런 것들을 연상하시면 될 듯 싶네요.

이제 진동센서를 알아봅시다.

1. 진동센서(Vibration Sensor)


진동센서는 진동을 감지하는 센서로 진동에 대해 디지털 신호 or 아날로그 신호를 만들어 냅니다. 진동센서는 3핀으로 VCC, GND, D0핀으로 구성된 모듈이 있는가 하면 아래 그림처럼 VCC, GND, D0, A0핀으로 구성된 모듈이 있습니다. 아래 그림을 보시면 진동에 대한 값을 십자모양의 가변저항으로 조절 할 수 있습니다.


진동센서는 2가지 형태로 출력 신호를 만들어 냅니다. 첫번째로 디지털 출력으로 0 or 1의 값을 만들어 내는데 진동센서에 대해 조사하니 초기상태값이 HIGH 상태이고 진동이 발생하면 LOW 상태가 된다고 나와 있습니다. 즉, 진동이 발생하면 진동센서는 LOW가 되고 아두이노우노에서 진동(LOW)에 대한 처리 동작을 설계하면 됩니다. 두가지 상태만 존재하기 때문에 스위치 역할을 수행하는 주제에 알맞습니다. 두번째로 아날로그 출력으로 0~1023의 값을 만들어 내겠죠. 디지털 출력는 진동에 대한 두가지 상태만 존재하기 때문이 때문에 두가지 상황만 만들어 낼 수 있지만 아날로그 출력은 진동의 강도값으로 진동의 신호를 만들어 내기 때문에 이 신호의 범위값을 나누면 여러 상황을 만들어 낼 수 있고 처리동작도 여러개의 형태로 처리동작을 만들어 낼 수 있습니다. 두가지 방식 중 여러분들이 설계하는 목적에 맞게 디지털로 읽을지 아날로그를 읽을지만 잘 정하고 알맞게 사용하시면 도비니다.

진동센서의 값을 읽는 방법은 다음과 같습니다.

  • 디지털 입력 : digitalRead(D0)
  • 아날로그 입력 : analogRead(A0)

위 함수를 통해서 읽은 값에 대한 처리 동작만 간단히 코딩하면 되기 때문에 진동센서는 어려운 부품이 아닙니다.

2. 진동센서 회로도


준비물 : 진동센서, LED 1개, 저항 220옴 1개, 아두이노우노
내용 : 진동센서의 D0핀을 7번에 연결하고 LED 핀은 12번에 연결하시오.



위 그림은 진동의 상태를 LED로 출력하기 위한 회로도 입니다.

3. 코딩


설계 : 진동이 발생하면 LED에 불이 들어오게 한다.

if(진동감지) LED 켜기;
else LED 끄기;

딱 두줄이면 됩니다. 실험에서는 디지털 입력을 받기 때문에 아래와 같습니다.

if(digitalRead(디지털핀)==LOW) digitalWrite(LED핀, HIGH);
else digitalWrite(LED핀, LOW);

종합해 보면,

const byte StatePin = 12;
const byte VibrationPin = 7;

void setup(){
 pinMode(StatePin, OUTPUT);
 pinMode(VibrationPin, INPUT); 
}

void loop(){
 if(digitalRead(VibrationPin)==LOW) digitalWrite(state, HIGH);
 else digitalWrite(state, LOW);
}

진동감지에 대한 간단히 코딩입니다. 실제 부품이 없어서 결과는 알 수 없지만 유사한 실험을 한다면 기울기센서로 대신 가상시뮬레이터에서 실험하시면 됩니다.



기울기 센서에서 기울기가 발생하면 LED에 불이 들어오는 실험이 있으닊나 진동센서를 기울기 센서로 감안하여 대신 실험을 할 수 있습니다.

4. 경보기 응용


진동센서와 피에조부저 같은 사운드 출력 센서를 이용하면 진동이 발생하면 소리로 결과를 출력 할 수 있습니다. 경보장치로 활용하면 좋습니다. 보안이 필요한 대상에 대해서 진동이 발생하면 경보음이 울리게 설정 할 수 있습니다.

1) 진동센서 경보기 회로도


준비물 : 진동센서, LED 1개, 저항 220옴 1개, 아두이노우노
내용 : 진동센서의 D0핀을 7번에 연결하고 LED 핀은 12번에 연결하시오.



피에조부저만 6번에 추가로 연결하시면 됩니다.

2) 코딩


위 진동센서 회로도에서 피에조부저만 추가로 부착되었기 때문에 소리에 대한 부분만 표현하시면 됩니다.

  • 피에조부저 : tone(핀번호, 주파수, 출력시간), noTone(핀번호), delay(시간값)

간단히 한음의 소리를 울리게 표현을 하면 아래와 같습니다.

   tone(SoundPin,523,1000/8);     // 도음으로 8분음표(음길이)
   delay(1000/4*1.30);             
   noTone(SoundPin);         

이 코딩을 어디에 넣어야 할까요? 바로 진동이 발생하는 시점에 경보음이 울리게 하면 됩니다.

종합해보면,

const byte StatePin = 12;
const byte VibrationPin = 7;
const byte SoundPin = 6;

void setup(){
 pinMode(StatePin, OUTPUT);
 pinMode(VibrationPin, INPUT); 
}

void loop(){
 if(digitalRead(VibrationPin)==LOW) {
   digitalWrite(state, HIGH);
   tone(SoundPin,523,1000/8);     // 도음으로 8분음표(음길이)
   delay(1000/4*1.30);             
   noTone(SoundPin);         
 }
 else digitalWrite(state, LOW);
}

유사한 예제로는 아래 거리경보장치 실험을 참조하시면 됩니다. 초음파센서로 거리값으로 경보음이 울리는 예제인데 여기서 진동센서의 역할을 초음파센서가 대신한 걸로 상상하시면 됩니다.



마무리


진동센서에 대해서 간단히 살펴보고 응용 예제로 진동센서로 경보장치를 상상해 보았습니다. 이것 말고도 다양한 것을 상상 할 수 있습니다. 위에서는 디지털 입력을 다뤘지만 진동에 대한 아날로그 입력을 받을 수 있다면 진동의 강도에 따라서 다양한 명령을 내릴 수 있습니다. 디지털 입력의 경우는 진동 하냐/안하냐 두가지 상태지만 아날로그는 진동의 범위에 따라 내리는 명령을 다양하게 내릴 수 있어 진동의 강도에 따른 섬세한 제어가 가능하다는 걸 알아 두세요.

아두이노는 재밌는 부품이 많습니다. 진동을 발생하는 진동모터가 있는가 하면 진동을 감지하는 진동센서가 있습니다. 여러분들도 특정 부품에 대해 조사하고 실험을 할 때 어떤 A동작을 수행하는 부품이 있다면 반대의 B동작의 부품이 없는지 같이 찾아서 공부하시면 아두이노 공부에 많은 도움이 됩니다. 공부하실 때 같은 동작을 하는 부품을 찾거나 반대 동작하는 부품을 같이 찾아서 공부를 꼭 해주세요. 못찾으면 어쩔 수 없지만 찾으시면 따블로 공부 효과를 얻을 수 있기 때문에 꼭 제가 말한 방식으로 아두이노를 공부해 주세요. 그래야 나중에 뭔가를 표현하고 싶을 때 도움이 많이 됩니다.

댓글()

[아두이노] 시리얼 플로터 사용

IOT/아두이노|2019. 8. 9. 09:00

[아두이노] 시리얼 플로터 사용



아두이노를 실험 할 때 지금까지는 시리얼 모니터로 디지털 값으로만 확인 해 봤습니다. 사실 지금까지 시리얼 플로터를 사용하지 않는 이유는 시이얼 플로터로 결과를 확인이 필요한 경우가 없었는데 지난 시간에 Color Sensor를 사용하면서 시리얼 플로터로 결과를 확인할 필요성이 느껴져서 이번에 post 주제로 선정했네요. 시리얼 플로터는 아날로그 파형으로 그래프 형태로 결과를 시각적으로 보여주는 출력 장치입니다. 그럼 이제 시리얼 플로터가 어떤 느낌인지 자세히 살펴보도록 하죠.

1. 시리얼 플로터


시리얼 모니터는 출력값을 디지털 형태의 숫자로 결과를 출력을 하고 시리얼 플로터는 아날로그 형태의 그래프 형태로 시각화 출력을 합니다.

그러면 시리얼 모니터와 시리얼 플로터의 출력 형태를 비교해 볼까요.

[실험 소스]

int output = 1;
unsigned long timeVal = 0;

void setup()
{
  Serial.begin(9600);
}

void loop()
{   
   if(millis()-timeVal>=100){ //0.1초 단위로 1 or -1의 결과를 출력합니다.
    output=-output;
    timeVal=millis();    
   }
   Serial.println(output); //시리얼 출력
}

[시리얼모니터 결과]


[시리얼플로터 결과]


둘의 출력 형태가 어떤 느낌인지 아시겠지요.

2. 시리얼 플로터에 여러개의 데이터 출력


위에서는 한개의 데이터만 출력했습니다. 지난 시간에 실험한 Color Sensor은 3개의 RGB 데이터를 출력하는데 그러면 그 데이터는 한번에 같이 어떻게 출력 할까요.

아래와 같은 방식으로 코딩하시면 됩니다. 즉, 데이터와 데이터 사이를 공백으로 띄워서 데이터를 출력하면 나눠서 출력하게 됩니다.

int output = 1;
unsigned long timeVal = 0;

void setup()
{
  Serial.begin(9600);
}

void loop()
{   
   if(millis()-timeVal>=100){ //0.1초 단위로 1 or -1의 결과를 출력합니다.
    output=-output;
    timeVal=millis();    
   }
     
     //시리얼 출력
   Serial.print(output); //데이터 1번
   Serial.print(" "); 
   Serial.println(output*2); //데이터 2번
}

즉, output와 output*2의 값 사이에 공백을 넣어주면 두개의 데이터를 동시에 아래 결과처럼 출력하게 됩니다.

[결과]


3. Color Sensor 값을 시리얼 플로터로 출력



지난 시간의 회로도를 그대로 적용 합니다.

1) Color Sensor 회로도


위 회로도는 지난시간에 만들 회로도이고 코딩도 그래도 적용합니다.

2) 코딩


시리얼 출력 부문만 약간 수정합니다.

const byte s0_pin = 3;
const byte s1_pin = 4;
const byte s2_pin = 5;
const byte s3_pin = 6;
const byte out_pin = 7;

const byte r_pin = 9;
const byte g_pin = 10;
const byte b_pin = 11;


void setup() {
  Serial.begin(9600);
  
  pinMode(r_pin, OUTPUT);
  pinMode(g_pin, OUTPUT);
  pinMode(b_pin, OUTPUT);
  
  pinMode(s0_pin, OUTPUT);
  pinMode(s1_pin, OUTPUT);
  pinMode(s2_pin, OUTPUT);
  pinMode(s3_pin, OUTPUT);
  
  pinMode(out_pin, INPUT);
  
  digitalWrite(s0_pin,HIGH);
  digitalWrite(s1_pin,LOW);
}


void loop() {
  digitalWrite(s2_pin,LOW);
  digitalWrite(s3_pin,LOW);
  int red_color = pulseIn(out_pin, LOW);
  red_color = map(red_color, 25,72,255,0);
  red_color = constrain(red_color, 0, 255);
  delay(50);
  
  digitalWrite(s2_pin,HIGH);
  digitalWrite(s3_pin,HIGH);
  int green_color = pulseIn(out_pin, LOW);
  green_color = map(green_color, 30,90,255,0);
  green_color = constrain(green_color, 0, 255);
  delay(50);
  
  digitalWrite(s2_pin,LOW);
  digitalWrite(s3_pin,HIGH);
  int blue_color = pulseIn(out_pin, LOW);
  blue_color = map(blue_color, 25,70,255,0);
  blue_color = constrain(blue_color, 0, 255);
  delay(50);

  analogWrite(r_pin, red_color);
  analogWrite(g_pin, green_color);
  analogWrite(b_pin, blue_color);

  //시리얼 플로터 출력
  Serial.print(blue_color);
  Serial.print("  ");  
  Serial.print(red_color);
  Serial.print("  ");      
  Serial.println(green_color);
  
  delay(100);
}

시리얼 출력 부분에서 시리얼 플로터 출력으로 약간 수정 했네요. 시리얼 플로터에 색상에 맞추다 보니깐 B, R, G 순으로 했네요. 혹시 여러분들이 출력을 했는데 아래 결과의 색상이 다르면 위 코딩의 색상 출력 순서를 변경하시면 됩니다.

3) 결과



Color의 변화를 시각적으로 확인 할 수 있습니다. 자세히 보시면 노이즈로 인해 색값이 변화가 중간에 심하게 발생하는 보실 수 있을꺼에요. 센서 자체가 납땜이 안되어 있고 전선꼽아서 하기 때문에 손으로 만지면 전선과 센서 사이에 잡음이 발생 할 수 밖에 없네요.

아래는 색상이 측정되고 출력되는 결과를 기록해 놓았습니다.


마무리


시리얼 플로터는 특정한 값의 변화를 시각적으로 확인하기에 편합니다. Color Sensor와 같은 부품을 제어 할 때에 값의 변화가 수치보다는 시각적 챠트로 보는게 더 효과적이겠죠. 이 챠트를 보면 색의 주파수 범위를 나중에 색종이를 가지고 지정할 때 활용하면 좋겠죠.

오늘은 간단히 시리얼 플로터를 사용하는 방법을 살펴 보았네요.


댓글()

[아두이노] Color Sensor 제어

IOT/아두이노|2019. 8. 1. 09:00

[아두이노] Color Sensor 제어



Color Sensor(TCS3200)은 색을 감지할 수 있는 센서입니다. 빛에서 색을 주파수값으로 읽는 센서인데 이 주파수 값을 가지고 RGB색을 만들어 내게 됩니다. 참고로 주파수 값을 읽을 수 있다고 해서 색값으로 바로 만들어 지지 않습니다. 각 RGB색의 값에 대한 주파수 영역을 잡아 색값 0~255사이의 값으로 변환 시켜야 합니다. 그 색 주파수 영역을 통해서 진짜 RGB색을 만들어 내는데 최근에 구매한 부품이여서 정확히 RGB 주파수 영역을 직접 만들어 내지 못했네요. 색종이로 RGB색을 측정하여 주파수 영역을 잡고 조정 작업을 해야 하는데 이미 만들놓은 분의 RGB 주파수 영역을 인용하여 간단히 실험을 하였습니다. 시간적 여유가 되시는 분들은 꼭 직접 RGB 주파수 영역을 본인이 가지고 있는 Color Sensor의 값을 측정하면서 원시값을 토대로 직접 그 영역을 만들어 보세요. 영유가 안된다면 저처럼 인용을 통해 간단히 실험하시면 됩니다.


1. Color Sensor




위 그림처럼 해당 핀에 대해서 살펴봅시다.

S0, S1 핀을 통해서 출력되는 주파수 크기값을 지정 합니다.


퍼센트 수치가 클수록 출력되는 숫자의 값은 작아 집니다. (L, H)은 숫자의 크기가 너무 큰 수가 나오고 (H,H)은 너무 작은 숫자값이 나오기 때문에 RGB색을 추출하기에는 좀 불편합니다. 그나마 색(0~255)을 만들기 위해서는 적당한 출력 수치값으로 (H,L)로 출력하면 적당한 주파수 크기로 출력됩니다. 실험에서는 (H,L)로 20% 크기로 출력됩니다.

RGB 각 색의 주파수 값을 얻기 위해서는 S2, S3의 상태로 해당 색상의 주파수 값을 얻을 수 있습니다.


위 표를 통해 RGB 색의 주파수 값을 얻게 됩니다.

결론은 S0, S1은 출력되는 주파수 크기를 지정하고 S2,S3은 해당 색을 지정해 줍니다.

2. Color Sensor 회로도




Color 센서에서 측정된 RGB 값을 3색 LED로 출력하는 회로도 입니다.

3. 코딩


위 데이터시트에의 나온 표를 기반으로 Color Sensor의 색을 읽기

Red 색 읽기

digitalWrite(s0_pin,HIGH); //출력 크기
digitalWrite(s1_pin,LOW);

digitalWrite(s2_pin,LOW); //Red 색
digitalWrite(s3_pin,LOW);
int Red = pulseIn(out_pin, LOW); //Red 주파수 값

Green 색 읽기

digitalWrite(s0_pin,HIGH);
digitalWrite(s1_pin,LOW);
digitalWrite(s2_pin,HIGH); //Green 색
digitalWrite(s3_pin,HIGH);
int Green = pulseIn(out_pin, LOW);

Blue 색 읽기

digitalWrite(s0_pin,HIGH);
digitalWrite(s1_pin,LOW);
digitalWrite(s2_pin,LOW); //Blue 색
digitalWrite(s3_pin,HIGH);
int Blue = pulseIn(out_pin, LOW);

3가지 색을 이렇게 읽게 됩니다.

스케일 20%로 고정이니깐 출력 크기는 setup()함수로 빼내고 나머지 각 색은 Loop함수에서 지정해 주면 되겠죠.

R,G,B 색값을 읽으면 그 색값이 색으로 보여지지 않습니다. 데이터시트에 가시면 주파수 챠트가 있는데 그걸 보고 색의 추출 범위를 지정해야 합니다.

색종이 같은 걸로 Red, Green, Blue 색의 주파수 최소 최대 범위를 지정하면 좋은데 색종이가 없어서 색의 범위 지정하기 어려워서 색의 범위를 만든 post를 겨우 찾아서 색의 범위를 지정할 수 있게 되었네요.



위 post 출처에서 Red, green, blue의 범위를 인용하여 실험했네요.

red_color = map(red_color, 25,72,255,0);
green_color = map(green_color, 30,90,255,0);
blue_color = map(blue_color, 25,70,255,0);

제가 보유한 Color Sensor는 깔금하게 색을 만들어 내지 못하더군요. 그리고 색 값이 0~255 범위를 벗어나는 값이 나오기 때문에 constrain()함수로 0~255사이로 묶어 두었습니다. 0보다 작은 값은 0에 고정되게 하고 255값을 벗어나면 255에 고정되게 만들었네요.

Red을 기준으로 표현하면

int Red = pulseIn(out_pin, LOW);
red_color = map(red_color, 25,72,255,0);
red_color = constrain(red_color, 0, 255);

나머지 색도 위 코딩과 같이 코딩하시면 됩니다.

Color 값을 만들었다면 그 값을 3색 LED로 핀은 PWM 핀으로 아날로그값을 출력하기 때문에 아래와 같이 코딩하면 됩니다.

RGB LED 출력

analogWrite(r_pin, red_color);
analogWrite(g_pin, green_color);
analogWrite(b_pin, blue_color);

종합해 보면,

const byte s0_pin = 3;
const byte s1_pin = 4;
const byte s2_pin = 5;
const byte s3_pin = 6;
const byte out_pin = 7;

const byte r_pin = 9;
const byte g_pin = 10;
const byte b_pin = 11;


void setup() {
  Serial.begin(9600);
  
  pinMode(r_pin, OUTPUT);
  pinMode(g_pin, OUTPUT);
  pinMode(b_pin, OUTPUT);
  
  pinMode(s0_pin, OUTPUT);
  pinMode(s1_pin, OUTPUT);
  pinMode(s2_pin, OUTPUT);
  pinMode(s3_pin, OUTPUT);
  
  pinMode(out_pin, INPUT);
  
  digitalWrite(s0_pin,HIGH);
  digitalWrite(s1_pin,LOW);
}

void loop() {

  //Red Color Read
  digitalWrite(s2_pin,LOW);
  digitalWrite(s3_pin,LOW);
  int red_color = pulseIn(out_pin, LOW);
  red_color = map(red_color, 25,72,255,0);
  red_color = constrain(red_color, 0, 255);
  delay(50);
  
  //Green Color Read
  digitalWrite(s2_pin,HIGH);
  digitalWrite(s3_pin,HIGH);
  int green_color = pulseIn(out_pin, LOW);
  green_color = map(green_color, 30,90,255,0);
  green_color = constrain(green_color, 0, 255);
  delay(50);
  
  //Blue Color Read
  digitalWrite(s2_pin,LOW);
  digitalWrite(s3_pin,HIGH);
  int blue_color = pulseIn(out_pin, LOW);
  blue_color = map(blue_color, 25,70,255,0);
  blue_color = constrain(blue_color, 0, 255);
  delay(50);

  //3색 LED에 RGB 출력
  analogWrite(r_pin, red_color);
  analogWrite(g_pin, green_color);
  analogWrite(b_pin, blue_color);
  
  Serial.print("RED: ");
  Serial.print(red_color);
  Serial.print("  ");
  
  Serial.print("GREEN: ");
  Serial.print(green_color);
  Serial.print("  ");

  Serial.print("BLUE: ");
  Serial.print(blue_color);
  Serial.println("  ");
  
  delay(1000);
}

4. 결과


시리얼 모니터에 색값을 출력해 보았습니다.


스케일을 2%로 지정했다면 위 값은 천단위로 출력되고 스케일을 100% 했다면 일단위로 값이 출력됩니다. 위 결과처럼 20%가 가장 적당합니다. 참고로 위 결과는 20% 스케일의 map()함수를 이용하여 주파수 값을 색값으로 변환하고 다시 constrain()함수로 0~255사이로 고정시켜 나온 결과입니다.

아래 3색 LED에 Color Sensor로 측정한 값이 출력한 영상인데 깔끔하게 출력되지 않네요. Color Sensor를 딱 붙이지 않고 좀 높이를 어느정도 띄워서 측정하면 색이 좀 더 측정하는 색에 가깝게 출력되더군요. 한손으로 측정하고 한손으로 스마트폰 촬영을 하다 보니깐 높이를 제대로 맞추지 못해서 결과는 마음에 들지 않게 나왔네요.


마무리


Color Sensor부품은 처음에는 좀 쉽게 생각했는데 다루기가 좀 까다로운 부품이네요. 동작 코딩은 어렵지 않지만 정확하게 색의 값을 만들어 내는게 쉽지 않네요. 센서의 연결 부분에 노이즈가 발생하면 값의 변화가 크고 정확하게 RGB값으로 분리해 내기도 어렵습니다. 직접 사용하는 센서의 측정되는 RGB값의 범위를 일일히 잡아야 하는게 쉬운 부품이 아니네요. RGB 색값의 범위를 지정 할 수 있다면 나머지 부분은 간단하기 때문에 컨트롤 하기는 어렵지는 않는데 색 주파수 영역이 문제네요.


댓글()

[아두이노] 마그네틱도어센서+멜로디 트렌지스터 응용

IOT/아두이노|2019. 7. 31. 09:00

[아두이노] 마그네틱도어센서+멜로디 트렌지스터 응용



오늘은 지난시간에 다룬 멜로디 트렌지스터를 가지고 마그네틱도어센서에 연동하여 출입문 알림이를 상상해보는 시간을 가져 보았습니다. 일상에서 사용되는 여러가지 응용들을 중에 이 주제를 선택한 이유는 아주 간단한 원리로 동작하기 때문입니다. 실제 부품이 없는 상태에서도 충분히 상상코딩이 가능하기 때문입니다. 편의점 같은 장소에서 출입문을 열고 들어가면 멜로디가 나오는 상점들을 한번쯤은 들어보셨을 꺼에요. 그러면 어떻게 멜로디가 나오는 것일까요. 아주 간단합니다 .출입문 쪽에 마그네틱도어센서가 부착되어 출입문이 닫혀있을 때와 열렸을 때 마그네틱도어센서의 값은 1 or 0 의 상태값이 발생하고 그 값을 통해서 멜로디 IC를 작동시켜 출입문이 열렸을 때 멜로디가 연주 되게 회로도를 구성하면 됩니다.

그러면 마그네틱도어센서로 어떻게 멜로디 IC를 동작시키는지 아두이노로 간단히 상상 테스트를 해보도록 하죠.

1. 마그네틱도어센서



마그네틱도어센서는 일반 스위치와 같은 용도로 사용합니다. 위 그림에서 보는 것처럼 두개의 물체로 구성되어 있고 한쪽 물체에 두개의 전선 가닥이 연결되어 있습니다. 한쪽 전선에 전류를 Input(입력) 되면 다른 전선은 Output(출력) 역할을 합니다. 전선이 연결되어 있는 물체 몸체와 반대면 물체 몸체가 가까워지거나 멀어질 때 Output(출력) 상태는 1 or 0의 상태가 됩니다. 즉, 두 물체가 가까워지거나 멀어질 때 전선이 연결된 위치에 자력에 의해가 연결되거나 끊어지게 되는데 전류의 상태는 1 or 0 됩니다.

2. 마그네틱도어센서+Melody IC 회로도(도어상태알람)



마그네틱도어센서를 실제 가지고 있지 않아서 대충 상상력을 동원하여 회로도를 만들어 보았네요. 사전학습 post의 아두이노에 연결한 멜로디 IC를 간단히 표현했는데 거기에 마그네틱도어센서만 추가로 부착해 봤네요.


3. 코딩


실제로 실험을 못하기 때문에 코딩은 최대한 오류가 없는 방향으로 상상코딩을 해보도록 하겠습니다.

편의점 같은 장소에서 출입문을 열고 들어가면 잠깐 멜로디가 나오는 것을 들으신 적이 있을 꺼에요. 그걸 상상코딩을 해보도록 하겠습니다.

우선 마크네틱도어센서가 가까울 때(닫힘)는 "1" 상태이고 멀어질 때(열림) "0"상태라고 가정하겠습니다.

회로도의 센서값 읽기는 디지털 상태가 0 or 1을 읽는 digitalRead()함수를 사용하니깐 아래와 같이 코딩합니다.

int senserValue = digitalRead(마그네틱도어센서핀);

0 or 1상태에서 멀어질 때(열림) 멜로디 연주가 시작이 되어야 하기 때문에 다음과 같이 코딩 합니다. senserValue값에 따라서 해당 2번핀에 digitalWrite()함수로 전류를 출력하면 멜로디가 연주되고 전류를 차단하면 멜로디가 중단 되게 코딩을 하면 됩니다.

if(senserValue==LOW) digitalWrite(멜로디핀,HIGH);
else digitalWrite(멜로디핀,LOW);

종합해보면,

const byte melodyPin = 2;
const byte swPin = 3;

void setup(){
  pinMode(swPin,INPUT);
  pinMode(melodyPin,OUTPUT);
}
void loop(){
  int senserValue = digitalRead(swPin);
    
  if(senserValue==LOW) digitalWrite(melodyPin,HIGH);
  else digitalWrite(melodyPin,LOW);
    
  delay(50);
}

대충 위와 같은 코딩으로 표현 하면 될거라 생각 되네요. 따로 문제가 되는 부분은 없고 간단한 실험을 했기 때문에 회로도나 코딩에는 문제가 없을거라 생각됩니다.

마무리


마그네틱도어센서는 서로 전선이 연결되어 있지 않는 상태에서 자력의 원리를 이용하여 전류의 흐름을 제어할 수 있는 부품이라 참 재밌는 부품입니다. 이 부품으로 도어상태알람을 상상 실험을 했지만 이것 말고도 다양한 응용 분야에 사용되고 있습니다. 가령 창문에 연결하면 방범알람이로 디자인 할 수 있습니다. 여기에 추가로 Bluetooth or Wifi 같은 무선 모듈을 연결하면 스마트폰으로 방범 정보를 전송할 수 있게 됩니다. 집 같은 장소에다가 방범장치로 활용하면 원격으로 정보를 스마트폰으로 수신할 수 있어 꽤 유용하겠죠. 이걸 계속 업그레이드 하면 더 재밌는 것을 만들어 내실 수 있을 꺼에요.

그리고 마그네틱도어센서 대신에 근접센서나 인간감지센서와 같은 부품을 접목한 다양한 표현들이 현실에 존재합니다. 한번 어떤 것들이 있는지 찾아 보세요. 그리고 아두이노시각으로 그걸 한번 구현해 보세요.

댓글()

[아두이노] 부품이 없을 때 아두이노 실험 팁

IOT/아두이노|2019. 7. 15. 09:00

[아두이노] 부품이 없을 때 아두이노 실험 팁



수 많은 부품들과 오픈 라이브러리 소스를 이용하여 아두이노로 쉽게 실험을 할 수 있습니다. 하지만 많은 부품들을 실험할 때마다 구매하기는 사실 부담이 됩니다. 부품을 구매해서 실험을 하면 좋지만 꼭 구매해야만 실험 할 수 있는 것은 아닙니다. 제가 post를 쓰면서 자주 온라인 가상시뮬레이터에서 실험을 해왔는데 이 가상시뮬레이터를 이용하여 실험하고 싶은 주제의 결과물을 얻을 수 있습니다. 하지만 가상시뮬레이터도 만능은 아닙니다. 모든 전자부품을 제공되지 않습니다. 기초적인 기본 부품만을 제공합니다. 없는 부품을 실험 할 때는 가상시뮬레이터에서도 제약이 따릅니다. 제공되지 않는 부품의 경우는 실험을 못하느냐고 묻는다면 충분히 실험을 할 수 있습니다. 실제 구매해야 하는 부품을 가상시뮬레이터에서 해당 부품의 특징과 비슷한 부품을 대체해서 코딩상으로 해당 주제에 대한 실험을 할 수 있습니다. 즉, 부품의 특징과 원리만 잘 이해하고 있다면 충분히 다른 부품을 사용하더라고 실험하고 싶은 내용으로 실험을 충분히 할 수 있습니다.

그러면, 어떻게 실험을 하는지 그 방법에 대해 살펴보도록 하죠.

1. 온라인 가상시뮬레이터를 이용한 실험


온라인 가상시뮬레이터 tinkercad에서 제공되는 기본 아두이노우노와 부품들은 몇개 되지 않습니다. 하지만 많은 것들을 실험할 수 있습니다. 기본적으로 제공되는 각 부품들의 특징들만 잘 이해하고 있으면 제공되지 않는 부품들도 어느정도 유사한 실험을 할 수 있습니다. 그러면 어떻게 유사한 실험을 할 수 있는지 알아봅시다.

1) 가상시뮬레이터에서 제공되는 부품의 특징


크게 읽기/출력 부품으로 나뉩니다. 읽기는 디지털 읽기와 아날로그 읽기가 있으며 출력도 디지털 출력과 아날로그 출력으로 나뉩니다.

  • 디지털 읽기 : 0 or 1 신호를 읽습니다. (0-0V, 1-5V)
  • 디지털 출력 : 0 or 1 신호를 출력합니다.
  • 아날로그 읽기 : 0~1023 신호를 읽습니다.
  • 아날로그 출력(PWM핀) : 0~255 신호를 출력합니다.
  • 시간값(HIGH->LOW 변환시간, LOW->HIGH변환시간) 외 신호 펄스 관련 제어

2) 실험 할 부품의 특징 파악 후 대체 부품 찾기


실험에 사용 할 부품이 가상시뮬레이터에서 제공되지 않는 부품일 때 해당 부품은 디지털/아날로그 신호를 읽어오는지 출력하는지의 부품의 특징을 우선 알아야 합니다. 그 특징과 유사한 부품의 특징을 가진 가상시뮬레이터의 부품으로 실험 목적에 맞는 유사 실험을 진행 할 수 있습니다.

예를들면, 지난시간에 실험한 토양수분센서를 실험하고 싶을 때 아래 부품을 구매를 해야 실험을 할 수 있습니다. 가상시뮬레이터에서도 제공되지 않는 부품이라서 구매하지 않으면 실험을 할 수 없겠죠.


토양수분센서가 없다고 해서 실험을 할 수가 없는것은 아닙니다. 바로 대체 부품을 찾으면 됩니다. 토양수분센서는 0~1023의 아날로그 신호를 만들어 냅니다. 그러면 0~1023의 신호를 발생하는 부품을 찾으면 되겠죠. 가상시뮬레이터에서 0~1023의 신호를 발생하는 부품은 여러개가 있는데 그 대표적으로 조도센서가 있습니다. 가스센서를 이용해도 되고 가변저항기를 이용해도 됩니다. 0~1023의 아날로그 신호를 발생하는 부품이면 토양수분센서를 대신 할 가상부품으로 여기고 실험을 수행할 수 있습니다.


위 조도센서를 이용하여 토양수분센서의 역할을 수행 할 있습니다.

3) 실험 할 부품이 원리를 파악 후 대체 부품 만들기


실험에 사용 할 부품이 가상시뮬레이터에서 제공되지 않을 경우 해당 부품을 직접 만들 수 있습니다. 원리만 잘 이해하면 그 원리에 맞게 가싱시뮬레이터에서 디자인을 할 수 있습니다.

예를들면, 4-Digit 7-Segment Display는 가상시뮬레이터에서 제공되지 않습니다.


직접 만든다면 4-Digit 7-Segment Display의 원리를 데이터시트를 보고 파악을 해야 겠죠. 가상시뮬레이터로 대체 부품을 만들면 아래와 같습니다.


응용해서 추가로 만든다면은 다음과 같이 늘릴 수 도 있겠죠.


대체 부품을 만들기 위해서는 해당 부품의 데이터시트를 보고 동작 원리에 맞게 부품을 만들어야 하는 어려움이 있지만 어느정도 LED 원리를 터득하고 7-Segment Display를 터득했다면 복잡해 보일뿐 위 표현은 충분히 하실 수 있습니다. 다른 부품의 경우도 이와 유사합니다.

4) 실험 할 부품을 대체 부품을 못 구할 시 Serial 통신 이용


대체한 부품을 찾지 못하거나 부품을 만들 수 없을 때는 Serial 통신을 이용하여 가상 입력과 출력를 표현하는 방법입니다. 즉, 어떤 특정한 부품이 있을 때 부품의 읽는 값을 대신해서 시리얼통신에서 가상 데이터를 입력하고 그 값을 코딩 상에서 가상 데이터를 처리하고 그 결과를 대체 부품이 있으면 출력시키고 출력을 대체 할 부품이 없을 때는 시리얼모니터로 출력시켜서 부족한 부분을 Serial 통신으로 대체하면 됩니다.

예) 시간변환 예제

만약 대체 부품을 구했다면 아래의 post처럼 직접 만들어서 실험을 할 수 있겠죠.

이처럼 가상시뮬레이터에서 대체 할 부품이 있다면 그 부품으로 이용하고 대체 할 부품을 만들 수 있다면 직접 디자인하고 이것도 저것도 아니면 Serial 통신을 이용해서 가상데이터를 입력 받게 하고 출력도 Serial 모니터로 대신 동작하게 할 수 있습니다.

위에 열거한 방법들을 잘 기억해 놓고 아두이노 실험을 하시기 바랍니다.

2. 대체 부품을 이용 시 코딩에 중점을 두자


아두이노를 실험하는 것은 회로도를 만드는 것도 중요한 목적이지만 코딩에 더 집중 하셔야 합니다. 우리가 아두이노를 실험하는 큰 이유가 아두이노를 통해 어떤 특정한 동작 명령을 내려 그 동작을 수행하게 하기 위해서 입니다. 하드웨어에 명령을 내리기 위해서는 코딩을 해야 하고 이부분은 무척 중요합니다.

현재 실험에서 부품이 없더라도 유사 느낌으로 코딩상에서 동작을 제어할 수 있습니다.

지난시간의 자동토양물주기를 토양수분센서와 water pump가 없었지만 충분히 가상시뮬레이터에서 실험을 할 수 있었습니다. 대체 할 부품으로 조도센서와 DC모터를 사용하고 실제 자동토양물주기 동작 명령 코딩에 중점을 두고 실험을 했기 때문입니다. 이 실험은 코딩 상에서 조도센서의 값을 토양수분센서로 인식하고 DC모터를 water pump로 인식하고 코딩을 했고 그 처리 동작도 자동토양물주기의 동작명령을 코딩했기 때문에 실제 다른 부품을 사용했지만 정상적인 결과물을 얻을 수 있었습니다.

아두이노를 실험할 때는 실험 할 부품이 있으면 실감나게 실험을 할 수 있지만 없더라도 대체 부품으로 실험 목적의 맞는 내용으로 코딩을 하면 원하는 결과를 얻을 수 있기 때문에 아두이노 실험에서는 코딩이 역할이 무엇보다 중요합니다. 그래서, 틈틈히 코딩 연습을 해주세요.

아두이노는 부품없이 코딩만으로도 실험을 할 수 있습니다. 부품이 있으면 좋고 없으면 대체부품을 찾고 그것도 없다면 부품을 디자인하고 그게 힘들면 Serial 통신으로 대체하더라도 결과물을 얻을 수 있습니다. 뭔가를 실험하기 위해서 꼭 부품이 필요한 것이 아니니깐 코딩적 관점으로 실험을 하셔도 됩니다.

3. 시간 날 때마다 부품의 이해


부품은 각각 고유의 특징을 지니고 있습니다. 기울기 센서는 기울기에 따라서 0 or 1의 신호를 발생하고 조도센서는 빛의 영향으로 아날로그 신호가 발생하고 LED은 0 or 1의 전류 상태로 불이 on/off 상태가 되고 초음파센서는 전방에 장애물에 거리를 초음파로 벽에 부딛치고 되돌아오는 시간값으로 거리를 측정하고 스위치버턴은 스위치를 누르면 스위치버턴에 연결된 전류의 선이 이여지는 등의 새로운 부품들의 특징을 시간 날 때마다 원리를 머리속에 담아 두세요. 그래야 새로운 부품으로 실험을 해야 하는데 그 부품이 없는 경우에 그 부품의 특징과 기존에 공부한 부품과의 유사 부품으로 대체해서 원하는 실험을 할 수 있게 됩니다.

그리고, 전자부품 쇼핑몰에 가셔서 시간 날 때마다 어떤 부품들이 있는지 부품을 검색하세요. 그 부품의 동작 원리와 특징들을 알아 두셨으면 합니다. 그러면 특정 부품을 가지고 실험을 할 때 같이 연동할 부품을 상상하기 편하고 상상을 할 때 공부 해놓은 부품들이 많으면 다양한 상상설계에 도움이 됩니다.

부품 공부는 전자부품 쇼핑몰에서 찾은 부품을 다시 구글검색에서 해당 부품으로 실험한 자료들을 수집하거나 데이터시트를 검색하셔서 동작 원리를 공부해 주세요. 상상코딩에 무척중요한 부분입니다.

상상코딩의 예를 들면 영화나 애니메이션에서 SF 장면을 보면 제 경우 그 한 장면 장면들이 아두이노적 시각으로 상상을 할 수 있습니다. SF영화에서 특수 장갑을 끼고 VR 화면을 조정하거나 실제 로봇을 조정하는 장면이 있다면 이 부분을 아두이노로 표현한다면 어떻게 할까요. 다양한 방법이 있지만 간단하게 소개하면 구부림센서가 있습니다. 구부림의 각도에 따라서 로봇의 손을 움직일 수 있습니다. 그리고 VR화면을 조정할 수 있습니다. 아니면 근육신경센서를 이용해서 근육의 움직임센서값을 이용해서 로봇의 손을 움직일 수 있습니다. 뇌신경센서로 로봇을 조정할 수 있습니다. 이런것들을 상상하기 위해서는 여러분들이 다양한 많은 부품들을 알고 있어야 합니다. 각 부품들의 특징만이라도 알고 있으면 특정 부품을 공부하거나 상상 실험을 할 때 응용할 수 있는 상상력의 범위가 넓어집니다.

꼭 전자부품 쇼핑몰에 가셔서 특수 부품들을 살펴보시고 해당 부품의 데이터시트를 보시고 동작을 이해하는 공부를 꼭 해주세요. 상상은 그냥 되는게 아닙니다. 여러분들이 얼마나 많은 부품을 체험해 보았느냐에 따라 남들과 다른 상상을 할 수 있습니다.

마무리


오늘은 좀 두서없는 이야기를 한 것 같네요. 꼭 하고 싶은말은 특정 실험을 할 때 꼭 실제 부품이 필요하지 않습니다. 아두이노로 실험할 것들은 엄청 많습니다. 그 많은 실험을 하기 위해서 모든 부품을 다 사용할 수 없습니다. 왠만큼 실험하기 위해서는 금전적 부담이 크기 때문에 특수한 실험의 경우는 부품 하나의 가격이 만만치 않기 때문에 실험을 하기 부담 됩니다. 그럴 때는 꼭 부품을 구해서 실험을 할 필요는 없습니다. 그 부품을 대신 할 부품으로 원하는 동작을 수행하게 할 명령 코딩만 잘 짜시면 됩니다.

실험에 사용되는 부품을 대체 할 부품을 찾기, 대체 할 부품이 없을 때 비슷한 동작 부품 만들기, 부품 없을 때 Serial 통신으로 대체하기 등 이 세가지를 이용하여 아두이노로 다양한 실험을 하시고 많은 상상을 하셨으면 합니다.

댓글()

[아두이노] 자동토양물주기 원리

IOT/아두이노|2019. 7. 12. 22:07

[아두이노] 자동토양물주기 원리



지난 시간에 토양수분센서 측정기를 만들어 보았습니다. 오늘은 토양수분센서를 이용한 자동토양물주기 원리에 대해 살펴보고자 합니다. 실제 구현은 아래와 같은 모습이지만 모터펌프가 없고 DC모터로 간단히 동작을 실험하려고 했는데 차라리 가상시뮬레이터로 실험을 하여 post를 읽는 분들도 직접 실험에 참여할 수 있게 하는 방향으로 나가는게 좋을 것 같아서 간단히 가상시뮬레이터로 자동토양물주기 원리를 실험하는 것이 좋을 것 같아서 post의 방향을 변경했습니다.


위 그림처럼 디자인하고 제작했다면 좀 더 좋았을 텐데 준비물 화분과 워터펌프가 없어서 실제 구현은 못했네요.

오늘 post 실험은 자동토양물주기 원리를 이해하기인데 여기서 사용되는 부품은 조도센서와 DC모터입니다. 조도센서는 토양수분센서이고 DC모터는 모터펌프라는 가정하에 실험이 가상시뮬레이터에서 이루어 집니다. 실제로 가상시뮬레이터에서는 토양수분센서와 모터펌프를 제공하지 않기 때문에 조도센서와 DC모터를 통해 자동토양물주기 원리를 이해하는 시간이니깐 조도센서와 DC모터를 상상으로 토양수분선세와 모터펌프라고 머리속에 담아놓고 post를 보시기 바랍니다.

1. 자동토양물주기 회로도


  • 준비물 : LED 2개, 저항 200옴 2개, 저항 10k옴 1개, 조도센서, 릴레이모듈, 토양수분센서, 아두이노우노, 외부전원
  • 내용 : 아두이노우노 A0핀을 조도센서의 A0에 연결하고 LED를 각각 11,12핀에 연결한다. 그리고 릴레이모듈핀으로 7번에 연결한다.

아두이노 가상시뮬레이터에서는 토양수분센서를 제공하지 않습니다. 가상시뮬레이터에 실험하기 위해서는 대신 할 센서가 필요합니다. 실험에 사용할 센서는 조도센서입니다. 조도센서가 토양수분센서라는 가정하에서 가상으로 실험을 하기 때문에 감안하시고 글을 읽어주시기 바랍니다.


3. 코딩



LED, 조도센서, 릴레이모듈 등을 사용하기 위해서는 위 참고 자료를 한번 살펴보시 오시면 됩니다.

회로도의 기본 동작원리를 유사 post와 거의 90%이상 유사합니다. 차이점은 화분자동물주라는 코딩부분만 약간 차이가 있을 뿐 기본 베이스 코딩은 동일합니다. 아무튼 복습차원으로 이번 post를 공부하시기 바랍니다.

화분에 자동토양물주기를 가정하고 실험 코딩을 해보도록 하겠습니다.

[설계]

  1. 조도센서의 값의 따라 motor on/off 설정(조도센서을 토양수분센서로 가정)
  2. led를 이용하여 토양의 현재 상태를 시각적으로 표현
  3. 릴레이모듈을 이용하여 외부전원을 이용하여 motor를 회전(일반모터를 모터펌프로 가정)

설계는 3가지로 크게 나눠서 생각하게 되었습니다. 동작 원리는 가령 토양수분센서가 건조하여 수분이 없을 때 일정 센서의 일정 수치가 되면 모터펌프로 물을 공급을 하고 토양의 일정한 수분값을 갖게 되면 모터펌프를 중단하는 기본 동작이 코딩입니다. 이때 현재상태로 토양수분상태가 양호하면 Green LED가 켜지고 토양수분이 부족하면 Red LED에 불이 들어오게 하여 현재 상태를 시각적으로 보여주도록 하는 간단한 테스트입니다.

참고로, 모터는 릴레이모듈을 이용하여 motor의 외부전원을 사용하도록 하였습니다. 릴레이모듈은 스위치 역활을 하고 아두이노에서 스위치를 누르면 모터를 동작하고 해제하면 모터를 중단하는 느낌이라고 이해하시면 됩니다.

자~! 이제부터 설계할 3부분을 자세히 살펴 볼까요.

1) 조도센서의 값의 따라 motor on/off 설정


물을 자동으로 주기 위해서는 물을 주기위한 센서값에 도달했을 때 모터펌프로 화분에 물을 주도록 코딩해야 합니다. 어떻게 해야 할까요. 처음에는 정교한 코딩을 할 필요는 없습니다. 단순하게 생각하고 코딩하시면 됩니다.

m_cds = analogRead(cdsPin); //Sensor Read
if(m_cds>=물주기상태값){
  digitalWrite(motorPin, HIGH);
}else{
  digitalWrite(motorPin, LOW);
}

위와 같이 if문으로 간단히 코딩을 할 수 있습니다. 여기서, 조금 개선 시켜볼까요.

if(m_cds>=물주기상태값 && state==false){ //모터동작 체크
    state=true;
    digitalWrite(motorPin, state);
        timeVal=millis();
}else if(m_cds<=물중단상태값 && state==true){ //모터중지 체크
    if(millis()-timeVal>=2000){ //모터동작 최소유지시간 
      state=false;
      digitalWrite(motorPin, state);
    }           
}

위와 같이 timeVal(이전시간값)의 현재시간 millis()함수로 저장됩니다. 그 시간을 기준으로 모터가 최소 동작하는 시간을 2초로 잡았는데 이경우는 실제 실험에서는 적당한 시간을 잡아 주시면 됩니다. 가상 실험이니깐 대충 2초정도로 motor 동작하게 설정했습니다.

참고로, 모터동작이 시작되면 다음 loop()함수가 수행될 때 물공급 상태값과 물중단 상태값을 체크할 때 state를 조건문으로 추가했는데 이 조건문은 모터가 동작했을 때에 모터중단 동작을 수행하게 하기 위해서 추가한 조건문입니다. 물공급은 모터상태가 false(중지) 상태일 때 가동해야 하니깐 false인지 확인하고 물중단이면 state은 가동중단 해야하니깐 true인지 확인해야 합니다. 물공급/물중단 상태를 나타내는 토양수분센서값으로 비교하지만 같이 현재 Motor상태도 비교하면 중복 반복되는 동작을 줄일 수 있습니다.

2) led를 이용하여 토양의 현재 상태를 시각적으로 표현


센서의 값으로 모터를 동작을 제어했지만 시각적으로 현재 상태를 확인하기가 애매합니다. 그래서 LED 2개로 현재 토양상태를 표현해 봅시다.

[토양수분충분상태]

digitalWrite(redPin, LOW);
digitalWrite(greenPin, HIGH);

[토양수분부족상태]

digitalWrite(redPin, HIGH);
digitalWrite(greenPin, LOW);

위 두가지 조건을 어디에 코딩해야 할까요.

if(m_cds>=물주기상태값 && state==false){ //모터동작 체크
  토양수분충분상태;
}else if(m_cds<=물중단상태값 && state==true){ //모터중지 체크
  토양수분부족상태;
}

위 코딩 위치에 위 상태 코딩을 넣으시면 됩니다.

3) 릴레이모듈을 이용하여 외부전원을 이용하여 motor를 회전


모터는 아두이노에서 전원으로 돌리는 것은 문제가 있습니다. 그래서 외부전원을 사용하여 motor를 동작시켜야 하는데 이경우는 여러가지 방법이 있습니다.

첫번째, 트랜지스터를 이용하여 motor를 동작시키는 방법

두번째, 모터쉴드를 이용하여 motor를 동작시키는 방법

세번째, 릴레이모듈을 이용하여 motor를 동작시키는 방법

세번째의 경우는 전구 대신에 motor로 교체하시면 됩니다. 릴레이모듈을 제어하기 위해서 릴레이핀의 상태값으로 motor를 제어하게 됩니다.

digitalWrite(motorPin, HIGH); //동작

digitalWrite(motorPin, LOW); //정지

릴레이 모듈이 좀 햇갈리시면 위 링크 걸어놓은 post에 가셔서 한번 원리를 이해하시고 오세요.

위 설계된 내용을 종합하여 코딩하면,

const byte cdsPin = A0;
const byte redPin = 12;
const byte greenPin = 11;
const byte motorPin = 7;

unsigned long timeVal = 0; //이전시간
unsigned long checkTime = 0;
boolean state = false; //모터상태값

int m_cds =0; //센서값 변수

void setup()
{
  Serial.begin(9600);
  pinMode(redPin, OUTPUT);
  pinMode(greenPin, OUTPUT);
  pinMode(motorPin, OUTPUT);
  
  //초기상태
  digitalWrite(redPin, LOW);
  digitalWrite(greenPin, HIGH);
}

void loop()
{
  if(millis()-checkTime>=1000){ //1초단위로 센서읽기
    m_cds = analogRead(cdsPin); //Sensor Read
    Serial.println(m_cds);
    checkTime=millis();
  }
  
  if(m_cds>=960 && state==false){ //모터동작 체크
    state=true;
    digitalWrite(motorPin, state);
    digitalWrite(redPin, HIGH);
    digitalWrite(greenPin, LOW); 
    timeVal=millis();
  }else if(m_cds<=60 && state==true){ //모터중지 체크
    if(millis()-timeVal>=3000){ //모터동작 최소유지시간 
      state=false;
      digitalWrite(motorPin, state);
      digitalWrite(redPin, LOW);
      digitalWrite(greenPin, HIGH); 
    }
  }
}

센서값을 모터 동작값으로 960이상으로 가상으로 설정했고, 모터 중지값으로 60이하로 설정했습니다. 나머지는 위에 설명한 부분을 합쳐놓은 코딩이니깐 어렵지 않을거라 생각합니다.

4. 결과


조도센서를 토양수분센서로 가정했기 때문에 조도센서를 토양수분센서라고 상상하시고 동작을 이해해 주시기 바랍니다. 조도센서가 960이상이되면 Red LED에 불이 들어고 DC모터가 동작하게 하게 됩니다. 그리고 일정시간(3초)동안은 최소 동작하게 하고 조도센서값이 60이하이고 최소 동작시간이 지나게 되면 Green LED에 불이 들어오고 DC모터가 정지됩니다.

위 코딩에서 모터동작/정지에 사용되는 제어값 960과 60은 가정하에서 설정한거라 실제 토양에서의 수분값은 측정해보고 물주는 토양수분상태값을 정하시고 위 코딩에서 수정하시면 됩니다. 그리고 모터동작을 최소 3초동안 동작하게 했는데 이 경우도 토양에 물주는게 딱 3초로 할 수 없습니다. 실제로는 일정이상의 시간으로 물을 줘야하는데 water pump의 물을 끌어올리는 양을 기준으로 테스트해보고 이정도의 시간으로 물을 주면 되겠다 싶은 정도의 시간을 water pump로 실제 물을 주면서 시간을 측정해 놓고 위 코딩에서 시간값을 수정하시면 됩니다.

그리고, 위 코딩에서 센서값을 읽은 if문안에 나머지 모터동작 코딩을 넣으셔도 됩니다. 센서의 읽은 값을 기준으로 모터의 동작을 결정하게 되기 때문입니다. 외부로 뺀 이유는 따로 몇가지 추가할 경우 별도의 동작을 체크할 일을 감안하여 외부로 모터 동작 if문을 뺐놓은 것일뿐 여러분들은 안에서 넣을지 뺄지는 알아서 판단하시고 코딩을 배치하시면 됩니다.


5. 진짜 자동토양물주기 회로도




대충 지금까지 다뤘던 것들을 종합해서 회로도로 만들어 보았네요. 토양수분 측정기의 회로도에 추가로 bluetooth를 연결해 보았고 모터펌프는 이미지를 못찾아서 그냥 DC모터로 표현했네요. [아두이노] 토양수분 측정기의 회로도와 오늘 배웠던 회로도를 결합해 보았네요.

위 회로도는 토양수분센서 값을 따라 Motor를 동작을 제어를 기본으로 하는데 추가로, 토양수분센서의 값을 LCD16x2에 출력할 수 있고 현재 상태를 LED로 확인이 가능하고 Bluetooth로 스마트폰에 센서값을 출력이 가능합니다.

코딩은 오늘 배운 코딩과 토양수분측정기 코딩을 합치고 시리얼통신 코딩 부분을 bluetooth 통신 코딩형태로 변경하면 됩니다.

코딩부분은 post에 올리지 않겠습니다. 한번 오늘 배웠던 내용을 기반으로 LCD16x2 I2C모듈과 Bluetooth 모듈만 추가 되었으니깐 그 부분만 상상해서 코딩을 하시면 됩니다. 난이도를 살작 올렸습니다. 이번 post에서 회로도만 보시면 코딩을 상상할 수 있는지 보기 위해서 코딩부분은 생략했습니다. 각 부품이 주어질 때 이 부품은 어떻게 제어할 수 있는지와 이 부품에 어떤 출력을 할 것인지는 여러분들의 상상에 맡기겠습니다.

마무리


오늘은 가상시뮬레이터에서 조도센서를 이용하여 Motor 동작을 제어하는 실험을 하였습니다. 가상시뮬레이터로 실험한 이유는 여러분들도 한번 동작 원리를 체험을 해보셨으면 하는 맘으로 표현 했네요. 위 실험을 실제로 실험해 봐야 하는데 모터펌프가 없네요. DC모터로 대신해서 실험해도 되지만 RC카에 조립된 DC모터를 다시 분해해서 만들기가 좀 그래서 구현된 실제 모습은 없습니다. 실제 구현을 안하더라고 가상시뮬레이터로 충분히 의미를 전달할 수 있기 때문에 오늘 내용은 가상시뮬레이터로만 실험을 마무리 하겠습니다.

오늘 내용은 조도센서는 토양수분센서이고 DC Motor는 모터펌프라고 가정하에 실험한 post 입니다. 간단하게 동작 제어만을 실험한 것이라 추가로 좀더 상황을 만들어 업그레이드를 해야 좀 더 완벽한 자동토양물주기 시스템이 도비니다. 이부분은 여러분들의 상상력을 발휘하는 부분으로 남겨 놓겠습니다. 상상은 전류 공급문제, 자동토양물주기 모형틀, 추가 연동 할 부품 등을 여러분들이 상상해서 한번 업그레이드 시켜 보세요.


댓글()

[아두이노] 토양수분센서 제어

IOT/아두이노|2019. 7. 8. 09:00

[아두이노] 토양수분센서 제어



오늘은 토양수분센서를 이용한 실험을 할까 합니다. 토양수분센서는 흙 속의 수분의 상태를 측정 할 수 있는 센서인데 식물을 키울 때 유용하게 활용 할 수 있습니다. 즉, 흙 속의 수분 상태에 따라 식물에 물 주는 시기를 결정할 수 있기 때문에 적절할 시기에 자동으로 식물에 물을 줄 수 있게 됩니다. 주변의 흙의 수분 상태를 측정할 수 있는 측정기를 만드는 데 이 센서를 이용하면 좋겠죠.


이제 간단하게 토양수분센서를 실험을 합시다.

1. 토양수분센서


아래 사진처럼 두개의 부분으로 나뉩니다. 토양수분센서는 여러가지 종류가 있는데 하나로 된 일체형 센서가 있거나 아래와 같이 두개로 나눠진 형태의 선세거 있습니다. 제가 사용하는 토양수분센서는 아래 사진처럼 두개로 나누어져 있습니다.


첫번째 사진을 보시면 왼쪽의 +, - 부분이 있는데 두번째 사진의 실제 흙속에 수분을 탐지하는 센서 부분으로 핀이 두개 있는데 앞에 첫번재 사진의 모듈 부분에 +.- 부분을 순서없이 그냥 연결하시 됩니다.

첫번재 사진에 오른쪽에 A0(아날로그신호), D0(디지털신호), GND, VCC로 4개의 핀이 있는데 정교하게 수분상태를 측정할려면 A0(아날로신호)핀을 사용하면 됩니다. 두번째 탐지센서부분에 전류를 공급해서 수분에 따라서 전류의 신호값이 변화가 일어나는데 수분이 전혀 없다면 1023값이 되고 수분의 상태에 따라서 수치는 작아집니다.

수분에 따라 0~1023 사이의 신호를 발생하게 됩니다.(이부분만 기억하시면 됩니다.)

토양수분센서는 아날로그신호를 읽기 때문에 다음과 같이 읽으시면 됩니다.

analogRead(A0);

디지털신호를 읽기를 사용할 경우는 다음과 같겠죠.

digitalRead(D0);

2. 토양수분센서 회로도


  • 준비물 : 토양수분센서, 아두이노우노
  • 내용 : 아두이노우노의 A0핀을 토양수분센서의 A0핀에 연결하시오.
  • 이미지 출처 : FC-28 Soil Hygrometer Module : https://github.com/Teutatis/Fritzing-Parts
    • FC-28 Soil Hygrometer Module.fzpz
    • FC-28 Soil Hygrometer Module_-_Probe.fzpz

fritzing 이미지 찾느라고 고생했는데 겨우 찾아서 회로도 그림을 완성했네요.


토양수분센서 모듈의 A0핀을 아두이노우노 A0핀에 연결만 제대로 하시면 됩니다. 나머지는 Vcc, Gnd 핀은 해당 핀에 맞춰서 연결하시면 됩니다.

3. 코딩


간단히 토양수분센서를 통해서 수분값을 읽는 명령만 수행하는 코딩으로 실험합니다.

void setup() {  
  Serial.begin(9600);
}
void loop() {
  int val = analogRead(A0);  //토양수분센서값 읽기
  Serial.println(val);
  delay(1000);
}

analogRead()함수를 이용하여 토양수분센서의 값을 읽고 시리얼모니터로 토양수분센서의 값을 출력합니다.

4. 결과


실험은 토양에 직접 꼽아서 토양의 수분상태를 측정해야 하는데 적당한 흙이 없어서 그냥 손으로 물을 묻히고 수분을 측정해 보았습니다. 아래 동영상은 손이 흙이라고 가정하여 손에 묻은 수분 측정을 하는 실험 영상입니다.


마무리


오늘은 간단히 토양수분센서를 이용하여 수분을 측정하는 실험을 하였습니다. 부품을 몇개 더 추가해서 좀 더 그럴싸하게 만들어 실험을 해 볼까도 했지만 순수하게 토양수분센서에 대한 의미만 전달하는 것이 좋을 것 같아서 간단히 실험했네요.

토양수분센서를 analogRead(A0), digitalRead(D0)로 측정을 간단히 할 수 있게 되었으니 다음 post에서 부품을 어떤 것을 추가해서 좀 더 그럴싸하게 표현을 해보는 실험을 연구를 좀 해 봐야 겠네요. LCD16x2로 토양수분센서값을 출력시키는 토양수분측정기를 만들지 아니면 Bluetooth에 연결하여 스마트폰으로 토양수분센서값을 전달 받아 스마트폰에 제어권을 넘길지 고민을 좀 해야 봐겠네요. wifi 모듈이 있으면 좀 더 확장해서 라즈베리파이에 서버를 만들고 서로 통신하는 방법을 취하면 멋질 것 같은데 wifi 모듈이 없네요. 나중에 하나 장만을 해서 실험을 해봐야 겠네요.

아마도 복습차원으로 Bluetooth은 최근 주제에서 너무 많이 연동한 실험을 했기 때문에 LCD16x2로 토양수분센서값을 출력하는 측정기를 만들 것 같기는 한데 아직은 결정하지 않았고 결정되면 둘 중 하나를 선택해서 좀 더 개선된 실험을 해보도록 하겠습니다.


댓글()

[아두이노] 수위 센서(Water Level Sensor) 제어

IOT/아두이노|2019. 6. 1. 09:00

[아두이노] 수위 센서(Water Level Sensor) 제어



오늘은 수위 센서라는 물의 높이를 측정할 수 있는 부품 사용법에 대해서 간단히 살퍄보도록 하겠습니다. 수위 센서 표면에 물이 닿는 위치에 따라서 수위 센서의 값이 0~1023사이의 값을 발생합니다. 그 값을 통해서 물의 높이를 알 수 있는 부품입니다. 실험에 사용한 수위 센서는 정교한 측정 도구는 아니고 간단히 물의 높이를 측정하는 실험용 모듈로 생각하시면 될 듯 싶습니다.

본격적으로 Water Level Sensor에 대해 알아보도록 할까요.

1. Water Level Sensor




위 센서는 세개의 핀이 있는데 보시는 것처럼 (-,+,S)로 표시되어 있는데 해당핀들은 (Gnd, Vcc, Analog Signal)로 구성되어 있습니다. 수위 센서의 표면에 긴선 모양을 보이실 꺼에요 이 센서의 전류가 공급되면 물이 이 센서의 표현에 닿게 되면은 전류의 값이 변화게 됩니다. 물은 전류가 흐르기 때문에 이 선들이 물에 접촉하게되면 전류의 값이 증가하게 됩니다. 물의 접촉에 따라서 늘어날수록 전류의 값은 커지겠지요. 이때 Analog Signal Pin을 통해서 그 전류의 값이 읽게 됩니다. 아두이노에서는 아날로그 신호를 읽을 때 0~1024사이의 값을 읽게 됩니다.

아날로그 읽기 : analogRead(아날로그핀)

2. Water Level Sensor 회로도


  • 준비물 : Water Level Sensor 1개, 아두이노우노
  • 내용 : A0에 Analog Signal Pin을 연결한다.


선 연결은 위 그림처럼 단순합니다.

3. 코딩


  • analogRead(A0) : A0핀에서 아날로그 신호(0~1024) 읽음

analogRead(A0) 함수로 아날로그 읽기함수로 수위 센서의 전류 0~1023사이의 값을 읽게 됩니다. 전류 신호값이 크면 클수록 물의 수위가 높아집니다.

void setup(){
  Serial.begin(9600);
}
void loop(){
  int waterSensorVal = analogRead(A0); 
  Serial.print("Water Sensor : ");
  Serial.println(waterSensorVal);
  delay(100);  
}

4. 결과


post를 등록할려고 보니 영상물에 결과에서 글자가 오타가 났네요. water를 warter라고 오타 났네요.(ㅋㅋ)

Water Level Sensor 문제점


수위 센서는 사실 접촉식은 별로 추천하지 않습니다. 그 이유는 물을 직접적으로 접촉하기 때문에 장시간 물에 접촉된다면 부식과 이물질이 붙는 문제도 있고 물기가 남아있으면 정상적인 결과가 나오지 않습니다. 즉, 수위가 낮아져도 물기가 수위 센서에 뭍어있기 때문에 그 물기가 수위 값으로 판정해 버린다는 문제점이 있습니다. 영상을 보시면 컵에서 꺼낸 수위 센서에서 계속 수위 값을 출력되고 화장지로 닦으니깐 수위 센서값이 0으로 되돌아가는 것을 영상에서 보실 수 있을 꺼에요. 그래서, 뭔가를 만들고 싶다면 접촉식보다 비접촉식으로 수위를 측정하는 센서를 사용하는게 좋습니다. 비접촉식 수위 센서는 구글검색으로 통해서 한번 찾아보세요.

마무리


오늘이 간단히 이런 종류의 센서가 있다는 것을 알려드리려고 post에 담았습니다. 수위를 측정하는 방법이 단순하죠. 수위 센서로 여러분들은 어떤 것을 하고 싶으신지 한번 상상을 해보세요. 일상에서 물의 높이를 측정하는 곳은 물탱크가 있는 곳에서 일반적으로 사용합니다. 물탱크의 물의 수위를 측정하여 물탱크의 물을 채울 수 있습니다. 즉, 물의 높이를 측정하면 물의 용량을 조절할 수 있게 됩니다. 물이 있는 곳에서는 수위센서가 요긴하게 사용됩니다. 수위조절에 필요한 물의 대한 기본 데이터를 제공하기 때문이지요. 수위를 측정하는 예를 들면, 댐이나 강에서 물의 높을 측정할 수 있다면 수문 제어를 할 수 있습니다. 수문을 언제 개방하도 닫을 것인지를요. 수경재배의 물탱크에도 식물에 물 공급과 양액 공급에 조절을 할 수 있겠죠.

수위 센서를 어디에 쓸지 상상을 해보세요. 제가 이 수위 센서를 사용한다면 물기에 대해 센서의 값의 변화가 일어나기 때문에 물기 감지센서로도 사용하면 것은 어떨까 하고 상상하게 되네요. 즉, 물기가 없으면 수위센서의 값은 0이 됩니다. 하지만 물기가 수위센서에 닿게 되면은 닿은 면적에 대한 전류값이 발생하고 수위센서의 물기에 대한 일정 전기신호가 만들어 집니다. 물기 감지센서로 사용하면 좋겠죠.

여러분들도 물에 대해 측정할 수 있는 이 수위 센서를 가지고 어떤 것을 만들 지 한번 상상의 나래를 펼쳐 보세요.


댓글()

[아두이노] 도망가는 아두이노 RC카 응용

IOT/아두이노|2019. 5. 30. 09:00

[아두이노] 도망가는 아두이노 RC카 응용



오늘은 지난시간에 마무리로 이야기 했던 내용 중 하나를 선택하여 한번 실제로 동작을 테스트 해보면 좋을 것 같아서 이렇게 도전해 봤네요. 상황은 초음파센서 아두이노 RC카에 정면에 다가가면 초음파 아두이노 RC카가 다가온 방향의 반대로 일정거리 동안 도망가도록 주행패턴을 만들어서 실험을 하겠습니다.


1. 초음파센서 아두이노 RC카 회로도


  • 준비물 : L293D Motor Shield, DC Motor 2개, Servo Motor 1개, 초음파센서 1개, 외부전원 2개, 아두이노우노
  • 내용 : A4(Trig), A5(Echo)로 초음파센서에 연결하고 Servo Motor는 왼쪽 상당애 Servo Pin(-,+,Sig)에 연결한다. DC Motor 2개는 M3, M4에 연결합니다.



지난시간의 회로도 입니다.

2. 코딩



도망다니는 아두이노 RC카를 상상하여 그 상상을 코딩화 하는 과정을 이야기 할까 합니다. 상상의 내용은 다음과 같습니다.

[도망가는 아두이노 RC카 상상하기]

  1. 전방에 다가오는 사람이 있는지 확인한다.
  2. 초음파센서로 인지거리 내 감지되면 감지된 반대방향으로 도망간다.
  3. 아두이노 RC카가 후면(엉덩이) 흔들기
  4. 다시 180도 회전하여 전방에 다가오는 사람이 있는지 확인한다.
  5. 이 과정을 계속 반복한다.

1) 전방에 다가오는 사람이 있는지 확인한다.


void loop() {
  //초음파센서 회전
  servo.write(angle);
  delay(50);
  
  int distance = sonar.ping_cm();

  //다가오는 사람 확인
  if(distance>0 && distance<10){
    도망가기;   
  }
  //회전 각도
  if (angle == 140) state = -10;    
  else if (angle == 40) state = 10; 
  angle += state;
}

2) 초음파센서로 인지거리 내 감지되면 감지된 반대방향으로 도망간다.


  //다가오는 사람 확인
  if(distance>0 && distance<10){
    movePattern(); //도망가기
  }

여기서, distance의 거리가 10cm 미만이면 다가 온 걸로 간주 하고 movePattern()함수로 반대방향으로 도망을 가 볼까요.

void movePattern(){
     int timeVal=0;
     motor1.run(BACKWARD);
     motor2.run(BACKWARD);
     delay(500);   
    
    motor1.run(FORWARD);
    motor2.run(BACKWARD);
    timeVal=(180-(angle-90))*5.56;
    delay(timeVal);   
    
    motor1.run(FORWARD);
    motor2.run(FORWARD);
    delay(2000);                
}

정면에 다가온 각도 방향의 정 반대로 도망을 갑니다. timeVal은 1초당 1000일때 1도 당 5의 값으로 가정하여 계산하면 해당 방향을 기준으로 180도 회전을 할려면 다음과 같은 식으로 표현할 수 있습니다.


timeVal=(180-(angle-90))*5;

Servo Motor의 회전(angle)각 방향이 100도면 RC카의 전방 진행 방향이 90도 임으로 왼쪽 10도 각이면 위 식으로 계산을 하면

각도 = 180-(100-90) => 170

만약에, angle이 70도이면

각도 = 180-(70-90) => 200

그리고, 이 각도의 회전각 시간을 구하면 timeVal 변수에 5값을 곱하시면 해당 각도에 대한 아두이노 RC카의 회전각이 나오게 됩니다.

뒤로 회전이 된 후 2초동안 도망가게 됩니다.

motor1.run(FORWARD);
motor2.run(FORWARD);
delay(2000);  

이렇게 하면 계속 후진하는 동작이라서 뭔가 전진하거나 자신의 자리로 되돌아오는 그런 명령들이 필요 하다고 생각되실 꺼에요. 오늘 실험은 하나의 패턴 도망자 아두이노 RC카 이기 때문에 하나의 패턴에만 집중하기 위해서 코딩하고 싶지만 생략했습니다.

다른방식으로 표현하면

전방 기준으로 왼쪽은 오른쪽으로 회전하고 오른쪽은 왼쪽으로 회전하고 싶다면 코딩이 좀 길어집니다.

void movePattern(){
     int timeVal=0;
     motor1.run(BACKWARD);
     motor2.run(BACKWARD);
     delay(500);   
    if(angle>=90){
      motor1.run(FORWARD);
      motor2.run(BACKWARD);
      timeVal=(180-(angle-90))*5;
      delay(timeVal);   
    }
    else{
      motor1.run(BACKWARD);
      motor2.run(FORWARD);
      timeVal=(180+(angle-90))*5;
      delay(timeVal);         
    }
    motor1.run(FORWARD);
    motor2.run(FORWARD);
    delay(2000);                

if문으로 해서 90도 기준으로 왼쪽인지 오른쪽인지 나누게 됩니다. angle>=90 은 90도 이상이면 우회전하게 되고 90도 미만이면 좌회전을 하게됩니다. 이렇게 하면 timeVal 값을 구하는 식은 한개가 달라집니다.

90도 이상 => timeVal=(180-(angle-90))*5;
90도 미만 => timeVal=(180+(angle-90))*5;

보시면 90도 이상이면 180도에서 빼주지만 90도 미만은 180도에서 더해주면 됩니다. 식이 잘 이해가 안되면 angle(각) 값을 숫자로 대입해 보세요. 그러면 이해가 쉬울 듯 합니다.

3) 아두이노 RC카가 후면(엉덩이) 흔들기


도망 간 다음 아두이노 RC카가 후면을 엉덩이 흔드는 것처럼 흔드는 동작을 추가 해 볼까요.

void movePattern(){
  생략...
    
    for(int i=0;i<3;i++){
     motor1.run(FORWARD);
     motor2.run(BACKWARD);
     delay(300);
     motor1.run(BACKWARD);
     motor2.run(FORWARD);  
     delay(300);
    }
}

우로 0.3초 회전 한 뒤에 좌로 0.3초 회전한다. 이 과정을 3번 반복하면 엉덩이를 흔드는 동작처럼 표현 할 수 있겠죠. 만약에 꼬리 같은 걸 후면에 붙여 놓으면 좀 더 그럴싸 해 지겠죠.

4) 다시 180도 회전하여 전방에 다가오는 사람이 있는지 확인한다.


void movePattern(){
  생략...
    
  motor1.run(BACKWARD);
  motor2.run(FORWARD);
  timeVal=180*5;
  delay(timeVal); 
  motor1.run(RELEASE);
  motor2.run(RELEASE);
  angle=90;
  servo.write(angle);
  delay(100);   
}

좌회전으로 180도 회전하여 다시 아까 다가 온 방향으로 아두이노 RC카는 향하게 회전 시킨 후 아두이노 RC카는 정지 상태가 됩니다. 초음파센서는 다시 정면 90도 방향으로 향하게 합니다. 이렇게 해서 도망갔다가 다시 주변을 탐색을 시작하기 전까지의 동작이 끝나게 됩니다. loop()함수는 끝나고 다시 재호출 되어서 loop()함수는 주변에 사람이 있는지 확인하게 됩니다.

5) 이 과정을 계속 반복한다.


위 과정을 loop()함수에서 계속 반복하게 됩니다.

위 과정을 종합하여 코딩하면,

[기본소스]

#include <AFMotor.h>
#include <Servo.h> 
#include <NewPing.h>

AF_DCMotor motor1(3);
AF_DCMotor motor2(4);

Servo servo;
const int servoPin = 10;
const int TRIG = A4;
const int ECHO = A5;
const int MAX_DISTANCE = 100;
NewPing sonar(TRIG, ECHO, MAX_DISTANCE);

int speed=200;
int state = 10;
int angle = 90;

void setup() {
  //Serial.begin(9600);
  motor1.setSpeed(speed);
  motor2.setSpeed(speed);
  motor1.run(RELEASE);
  motor2.run(RELEASE);

  servo.attach(servoPin);
  servo.write(angle);
  delay(1000);
}

void loop() {
  //초음파센서 회전
  servo.write(angle);
  delay(50);
  
  int distance = sonar.ping_cm();
  
  //장애물 감지
  if(distance>0 && distance<10){  
     movePattern(); 
  }   
  //회전 각도
  if (angle == 140) state = -10;    
  else if (angle == 40) state = 10; 
  angle += state;
}

void movePattern(){
    int timeVal=0;
    motor1.run(BACKWARD);
    motor2.run(BACKWARD);
    delay(500);   
    
    //도망
    if(angle>=90){
      motor1.run(FORWARD);
      motor2.run(BACKWARD);
      timeVal=(180-(angle-90))*5;
      delay(timeVal);   
    }
    else{
      motor1.run(BACKWARD);
      motor2.run(FORWARD);
      timeVal=(180+(angle-90))*5;
      delay(timeVal);         
    }
    motor1.run(FORWARD);
    motor2.run(FORWARD);
    delay(2000);  
    
    //후면 흔들기
    for(int i=0;i<3;i++){
      motor1.run(FORWARD);
      motor2.run(BACKWARD);
      delay(300);
      motor1.run(BACKWARD);
      motor2.run(FORWARD);  
      delay(300);
  }
  
    //원위치 전방 보기
    motor1.run(BACKWARD);
    motor2.run(FORWARD);
    timeVal=180*5;
    delay(timeVal); 
    motor1.run(RELEASE);
    motor2.run(RELEASE);
    
    angle=90;
    servo.write(angle);
    delay(100);     
}

동작 하나하나 패턴을 추가해서 표현하니깐 movePattern()함수 내 동작 패턴이 꽤 길어 졌네요. 아두이노 RC카가 어떻게 움직이길 바라면 그 움직임을 하나씩 원하는 행동을 하도록 상상하고 그 상상을 이렇게 코딩화 하면 됩니다.

위 소스는 그냥 즉흥적으로 간단히 만든 거라서 사실 지져분한 코딩입니다. 맘에 들지 않는 코딩이라는 뜻이죠. 뭔가 축약된 코딩을 좋아하는데 이렇게 길게 늘려서 일일히 코딩하는 것을 싫어하기 때문에 맘에 들지는 않지만 대충 이런 느낌으로 코딩을 한다는 의미만 전달하기 위해서 그냥 이 소스로 마무리 합니다.

여러분들은 한번 다른 방식으로 도망자 아두이노 RC카를 만들어 보세요.

추가사항


위의 코딩만으로는 사실 문제점이 많습니다. 무조건 전방에 다가오는 물체에 대해서 도망만 가기 때문에 계속 후진하는 느낌의 코딩입니다. 또한 뒤로 도망을 갈때 보면 뒤에 장애물에 대한 감지가 빠져 있습니다. 이 부분을 추가할려면 loop()함수의 물체 감지 부분을 사용자 정의함수로 빼서 뒤로 도망가는 로직에 물체 감지 함수부분을 호출하는 식으로 코딩을 변경해야 합니다. 그리고 도망가는 2초간의 시간은 delay(2000) 하면 절대 안되고 delay()함수를 사용하지 않는 post [아두이노] delay()함수 안쓰고 delay 제어하기의 원리를 이용해서 2초간 도망가는 주행을 하는 동안 장애물을 감지하는 코딩으로 수정해야 합니다. 그리고, 그냥 도망으로만 끝내지 않고 다시 도망을 갔다가 물체 감지된 방향으로 조금직 다시 직립주행을 하면서 물체와의 거리를 측정하면서 자신이 있던 자리로 되돌아가게 코딩을 하면 좀 더 재밌는 코딩으로 변경 됩니다.

지금 열거한 내용만 코딩을 하더라도 수정해야 할 부분과 추가해야할 코딩이 늘어납니다. 그러면 오늘 전달하고자 하는 도망자 아두이노 RC카의 원리가 제대로 전달되지 않기 때문에 코딩에서는 실제 추가하지 않겠습니다. 이부분은 여러분들의 상상코딩에 맡기겠습니다.

3. 결과


코딩은 그럭저럭 되었는데 결과는 썩 마음에 들지 않네요. 원하는 결과는 얻지 못했습니다. 1초를 1000으로 해서 180도를 1초 회전으로 첨에 잡았는데 회전하니깐 180도 이상의 각도로 회전이 일어나서 댜략 눈 짐작으로 900 정도로 180도로 간주하여 회전을 시켰지만 그래도 회전각은 원한 각도로 정교하게 회전을 되지 않았네요. 2~3번의 주행 테스트를 하고 post를 한 거라 정교한 결과를 얻지 못해서 아쉽네요.


위 영상을 보시면 회전각 오차가 크고 도망가는 주행이 직립주행이 되지 않았네요. 건전지가 추가되고 무게중심이 보조 바뀌쪽으로 좀 더 가서 그런지 보조바퀴의 방향각에 의해서 회전이나 직립주행의 오차가 심하게 발생했네요. 아두이노 RC카의 몸체에 대해서 교정을해야하고 수학적으로 DC기어모터의 속도와 시간값을 통해 회전각을 정교하게 해야하는데 단순하게 눈짐작으로 실험을 한계 이런 오차결과를 만들어 내고 말았네요.

결과는 마음에 들지 않지만 코딩은 대충 어떤 느낌으로 설계하는지만 이해하는 시간이 되었으면 합니다. 기본적으로 이렇게 출발하고 조금씩 문제점에 대해서 해결 코딩을 해가면서 좀 더 정교한 프로그램을 만들어가는 것이죠.

마무리


원래 상상의 의도는 도망자 아두이노 RC카를 만들고자 했는데 느낌이 꼭 애완동물이 주인을 피해 도망갔다가 꼬리를 흔들고 다시 주인이 있는 방향으로 다시 바라보는 느낌의 코딩이 되어버렸네요.

이 느낌으로 좀 더 추가해 볼까 하는 생각도 드는데 아직 결정은 안했습니다. 따라다니는 아두이노 RC카를 다음편에 이야기 할까 그냥 여기서 끝낼까 고민을 좀 해봐야 겠네요.

코딩은 아직 안했는데 상상을 잠깐 해보고 결정해야 겟네요.


댓글()

[아두이노] 아두이노 RC카 자율주행(장애물피하기)

IOT/아두이노|2019. 5. 29. 09:00

[아두이노] 아두이노 RC카 자율주행(장애물피하기)



오늘은 몇일동안 포스트한 장애물을 피하는 자율주행 테스트 해보는 시간으로 내용을 채우고자 합니다. 좀 더 정교한 자율주행 코딩을 할까도 생각 했지만 처음은 단순한 장애물을 피하는 자율주행을 보여드리는 것이 좋을 것 같아서 종합 코딩은 간단하게 표현하여 자율주행의 의미를 전달하고자 합니다.


우선 초음파 아두이노 RC카를 사진으로만 보면 정확히 구조를 이해할 수 없으니 간단히 fritzing으로 회로도를 보여드리고 간단히 장애물피하기 자율주행 코딩을 보여드리겠습니다.

1. 초음파센서 아두이노 RC카 회로도


  • 준비물 : L293D Motor Shield, DC Motor 2개, Servo Motor 1개, 초음파센서 1개, 외부전원 2개, 아두이노우노
  • 내용 : A4(Trig), A5(Echo)로 초음파센서에 연결하고 Servo Motor는 왼쪽 상당애 Servo Pin(-,+,Sig)에 연결한다. DC Motor 2개는 M3, M4에 연결합니다.



위 사진의 아두이노 RC카의 회로도는 아래 그림과 같이 구성되어 있습니다.


위 그림에서 외부 전원을 두개로 분리해서 공급합니다. 참고포 점퍼핀 덮개를 빼주면 아두이노와 Motor Shield의 전원을 나눌 수 있습니다. 그림이 좀 복잡해 보일 수 있지만 내용에 자세히 핀 연결 설명이 되어 있으니깐 해당 위치에 부품의 선을 연결해주시면 됩니다.

2. 코딩



이전 post에서 장애물을 감지하는 방법과 장애물을 피하는 패턴을 살펴 보았습니다. 여러개를 설명했는데 그중에 각각 한개씩 간단한 표현으로 코딩을 하겠습니다.

[기본소스]

#include <AFMotor.h>

AF_DCMotor motor1(3);
AF_DCMotor motor2(4);

void setup() {
  motor1.setSpeed(200);
  motor2.setSpeed(200);
  motor1.run(RELEASE);
  motor2.run(RELEASE);
}

void loop() {  

  초음파센서 장애물 감지;

  motor1.run(FORWARD); //전진
  motor2.run(FORWARD);
  delay(1000);
}

1) 서보모터+초음파센서 장애물 감지


[코딩 순서]

  • 40~140도 사시를 10도씩 회전하면서 초음파센서로 장애물를 측정한다.
  • 장애물 감지 거리를 15cm 미만일 때 감지로 간주한다.
void loop() {
  //초음파센서 회전
  servo.write(angle);
  delay(50);
  
  int distance = sonar.ping_cm();

  //장애물 감지
  if(distance>0 && distance<15){
     movePattern(); 
     //Serial.println(distance);
     motor1.run(FORWARD);
     motor2.run(FORWARD);  
  }   
  //회전 각도
  if (angle == 140) state = -10;    
  else if (angle == 40) state = 10; 
  angle += state;
}

2) 장애물 피하기 패턴


[코딩 순서]

  • 장애물 감지시 0.5초 동안 후진한다.
  • 전방 90도를 기준으로 90이상이면 우회전 90미만 좌회전 시킨다.
  • 장애물 피하기 회전이 끝나면 다시 전진 주행한다.
  • 초음파센서는 방향은 90도로 위치시킨다. (90도 기준으로 다시 10도씩 변화를 시키기 위해서)
void movePattern(){
     motor1.run(BACKWARD);
     motor2.run(BACKWARD);
     delay(500);   
    if(angle>=90){
      motor1.run(FORWARD);
      motor2.run(BACKWARD);
      delay(500);   
    }
    else{
      motor1.run(BACKWARD);
      motor2.run(FORWARD);
      delay(500);   
    }
    angle=90;
    servo.write(angle);
    delay(100);
}

3) 기본소스+장애물감지소스+장애물피하기 패턴소스


위 세가지 소스를 합치면 다음과 같습니다. 참고로 몇가지 다듬어서 코딩을 했습니다.

#include <AFMotor.h>
#include <Servo.h> 
#include <NewPing.h>

AF_DCMotor motor1(3);
AF_DCMotor motor2(4);

Servo servo;
const int servoPin = 10;
const int TRIG = A4;
const int ECHO = A5;
const int MAX_DISTANCE = 100;
NewPing sonar(TRIG, ECHO, MAX_DISTANCE);

int speed=200;
int state = 10;
int angle = 90;

void setup() {
//  Serial.begin(9600);
  motor1.setSpeed(speed);
  motor2.setSpeed(speed);
  motor2.run(RELEASE);
  motor2.run(RELEASE);

  servo.attach(servoPin);
  servo.write(angle);
  delay(1000);

  //Start
  motor1.run(FORWARD);
  motor2.run(FORWARD);  
}

void loop() {
  //초음파센서 회전
  servo.write(angle);
  delay(50);
  
  int distance = sonar.ping_cm();

  //장애물 감지
  if(distance>0 && distance<15){
     movePattern(); 
     //Serial.println(distance);
     motor1.run(FORWARD);
     motor2.run(FORWARD);  
  }   
  //회전 각도
  if (angle == 140) state = -10;    
  else if (angle == 40) state = 10; 
  angle += state;
}
void movePattern(){
     motor1.run(BACKWARD);
     motor2.run(BACKWARD);
     delay(500);   
    if(angle>=90){
      motor1.run(FORWARD);
      motor2.run(BACKWARD);
      delay(500);   
    }
    else{
      motor1.run(BACKWARD);
      motor2.run(FORWARD);
      delay(500);   
    }
    angle=90;
    servo.write(angle);
    delay(100);
}

setup()함수에서 초기화 작업을 하는데 젤 먼저 DC기어모터의 속도를 200으로 세팅하고 각 Motor를 RELEASE(해제 or 정지)상태로 둔다. 그리고 나서 주행전 Servo Motor의 초기 위치는 90도 회전 시켜 초음파센서가 전방90도를 바라보게 한다. 그 다음 1초동안 준비 상태로 있다가 FORWARD(전진)명령으로 아두이노 RC카가 출발하게 됩니다.

loop()함수는 angle(각도)에 위치로 회전을 0.05초 간격으로 합니다. 회전이 될 때 초음파센서(sonar)의 거리를 측정하여 distance에 저장합니다. 이 값이 0보다 크거나 15cm보다 작은 값일 때 장애물을 감지한 걸로 감주합니다. 0의 값은 MAX_DISTANCE의 제한최대거리 값을 넘게 되면 0으로 반환되기 때문에 0의 값이 주행 중에 일정 간격으로 발생하게 됩니다. 그렇기 때문에 0보다 크거나 15cm보다 작은 값으로 if문으로 체크하는 조건식을 만든 것이죠. 이렇게 15cm 미만일 때 장애물 감지 판정을 내리게 되면 if문 이하 문장을 수행 하게 됩니다. 이때 장애물 피하기 패턴을 코딩하면 됩니다 movePattern()함수로 장애물을 피하는 동작을 코딩해 놨는데 이 방식으로 피하고 나면 다시 motor1, motor2은 FORWARD(전진)으로 게속 자율주행을 하게 됩니다.

여기서, movePattern()함수를 살펴보면 먼저 0.5초 동안 후진을 하고 후진 한 후에 Servo Motor가 회전한 현재 각(angle)의 값이 90보다 큰거 아니면 작은 가로 나누게 되는데 이때 90도보다 크면 좌측에 장애물을 감지했기에 우회전 명령문을 0.5초 동안 90도 우회전을 하게 되고 90도보다 작으면 우측에 장애물을 감지했기에 좌회전 명령문으로 0.5초 동안 90도 좌회전을 하게 됩니다. 그리고 다시 전진 주행을 하기 전에 초음파센서의 방향 위치를 초기화 상태로 전방 90도 위치로 회전시켜놓습니다.

대충 전체의 소스에 대해 설명을 했는데 이해하셨는지 모르겠네요.

만약, 다른 방식으로 접근하고 싶다면 장애물 감지 코딩 부분을 다른 감지 주행패턴을 선택해서 붙여 넣으시면 되고요. 장애물 피하기 패턴도 movePattern()함수 내부의 코딩을 원하는 패턴으로 만들어서 넣으시면 됩니다.

3. 결과


그냥 전방의 장애물을 손으로 표현해서 손으로 막을 때 이 손을 아두이노 RC카가 장애물로 감지하고 초음파센서가 감지한 각도에 따라 좌/우회전을 하는 짧게 촬영한 영상입니다.


이렇게 간단하게 장애물 피하기로 자율주행을 출발합니다.

위 코딩을 좀 더 정교하게 하려면 다음과 같이 해야 합니다.

void loop(){
  //장애물 감지
  if(distance>0 && distance<15){
     movePattern(); 
  }  
}   
void movePattern(){
     motor1.run(BACKWARD);
     motor2.run(BACKWARD);
     delay(500);   
    if(angle>=90){
      motor1.run(FORWARD);
      motor2.run(BACKWARD);
      delay(회전각도시간값);   
    }
    else{
      motor1.run(BACKWARD);
      motor2.run(FORWARD);
      delay(회전각도시간값);   
    }
    motor1.run(FORWARD);
    motor2.run(FORWARD);    
        
    angle=90;
    servo.write(angle);
    delay(100);
}

여기서, 회전각도시간값은 여러분들이 가지고 있는 아두이노 RC카의 바퀴힐과 속도를 기준으로 1초(1000)에서 delay값 100을 기준으로 몇도 회전되는지 체크했다가 정확하게 회전각도시간값을 넣어주세요. 그리고 회전을 시킨 후 바로 전진 주행으로 바꿔 주시면 좀 더 정확하게 회전 후 전진 주행이 됩니다. 사실 촬영할 때는 loop()함수에다가 넣어서 간단히 movePattern()함수에서는 회전 주행만 표현했는데 마지막 초음파센서의 90 회전 초기화에서 0.1초 동안의 회전이 더 일어나는 단점을 가지게 되더군요. 그러면, 회전각도시간값으로 정확히 회전을 시키더라도 초기화 0.1초동안 회전이 더 발생하는 문제가 생길 수 있습니다. 단순하게 장애물 피하기 코딩에서는 0.1초 차이는 티도 안나기 때문에 무시하고 촬영을 했지만 post을 작성하면서 이 코딩부분을 수정해서 올릴까도 했지만 촬영은 이전 소스를 기반으로 주행 시켜서 그냥 약간 부족한 상태로 수정 없이 올리게 되었네요. 하지만 이렇게 추가로 이야기 하고 넘어가야 할 것 같아서 좀 더 내용을 채우게 되었네요.

motor1.run(FORWARD);
motor2.run(FORWARD);   

아무튼 이 명령이 어느 위치에 있느냐에 따라 결과는 달라지게 됩니다. 단순한 코딩은 무시 할 정도로 표현해도 되지만 정교한 코딩에서는 명령문의 위치에 따라 딜레이 0.1초 차이도 회전 오류각을 발생할 수 있기 때문에 코딩을 완료되더라도 완료된 코딩의 흐름을 머리속에서 상상하면서 문제가 없는지 체크를 꼭 하셔야 합니다.

마무리


위 코딩을 보면 그렇게 어려운 코딩이 아닙니다. 재밌는 것은 이 원리를 이해하시면 또다른 다양한 표현을 할 수 있습니다. 장애물을 피한다면 아두이노 RC카가 도망다니는 RC카로 표현 할 수 있습니다. 가령, 전방의 특정 각도의 장애물이 감지되면 그 장애물의 각도을 기준으로 180도 회전해서 뒤로 도망가는 RC카를 만들 수 있습니다. 인간과 RC카가 이 원리로 접근하게되면 인간이 아두이노 RC카를 추적하고 아두이노 RC카는 도망자가 됩니다.

아니면, 주행을 규칙적으로 지그재그로 표현을 한다면 지그재그 주행을 할 때 전방의 초음파센서가 장애물을 감지하면 진행 방향에 있는 장애물을 피하기 위해서 진행 라인의 주행을 포기하고 다음 라인의 주행을 이여간다면 로봇청소기와 같은 주행을 할 수 있게 됩니다.

또 다른 상상을 하면 아두이노 RC카를 랜덤 주행을 일정 범위안에서 수행되게 해놓고 있다가 초음파센서의 장애물 감지 범위에 인간이 다가갔을 경우 일정 거리까지 아두이노 RC카가 따라오도록 주행패턴을 만들게 되면 어떤 느낌의 아두니오 RC카가 될까요. 바로 애완동물 아두이노 RC카로 표현이 가능해 집니다.

간단히 장애물 감지와 장애물 피하는 동작의 원리를 곰곰히 생각해보니깐 방금 이야기 했던 상상들이 떠오르더군요. 위에서 설명한 주제들의 원리는 전부 동일한 원리 입니다. 장애물 감지와 장애물 피하기에서 장애물 피하기를 역발상으로 장애물 따라오기로 생각의 관점을 바꾸면 이렇게 또 다른 결과가 나올 수 있습니다. 재밌는 아두이노 RC카를 만들 수 있겠죠.

여러분들도 한번 이 원리를 기반으로 나는 어떤 상상을 할 수 있을지 상상의 나래를 펼쳐보셨으면 합니다.


댓글()

[아두이노] 아두이노 RC카 초음파센서로 장애물 피하기 패턴

IOT/아두이노|2019. 5. 28. 09:00

[아두이노] 아두이노 RC카 초음파센서로 장애물 피하기 패턴



지난 시간에는 초음파센서로 장애물을 감지하는 방법들에 대해서 간단히 살펴 보았습니다. 오늘은 감지 했을 때 피하는 방법에 대해 한번 이야기 하고자 합니다. 아두이노 RC카는 주행 중 전방에 장애물이 감지하면 후진을 할 것인지 좌회전을 할 것인지 아니면 우회전을 할 것인지를 결정해야 합니다. 여러분들은 어떤 선택을 하실 건지 한번 머리속에서 상상을 해보세요. 현재 여러분들이 서 있는 위치에서 앞으로 걷다가 앞에 벽이 나타난다면 나는 어느 방향으로 벽을 피해 이동할지를 상상하며 한번 걸어보세요. 벽이 나타나고 벽을 피해서 방향전환하고 나서 다시 걷는 상상을 하거나 아니면 실제 걸어보시면서 걷는 과정을 기록해 주세요. 이 행동의 기록이 아두이노 RC카의 주행 패턴이 됩니다. 아두이노 RC카는 이 주행 패턴을 통해서 자율주행을 하게 됩니다. 어떤 느낌인지 아시겠지요.


지금부터 장애물 감지 센서인 초음파센서를 지난 시간에 배운 방식 중에 하나를 선택해서 실험해야 하는데 위 사진을 보면 서보모터로 회전되는 초음파센서로 전방에 하나 배치했기 때문에 이 방식을 통해 장애물을 감지 하고 장애물을 피하는 패턴들을 만들겠습니다.

1. 초음파센서로 장애물 감지 시 피하기 패턴



위의 선행 학습을 꼭 하시고 오셔야 합니다. 그래야 아래 내용을 쉽게 이해 하실 수 있습니다.

1) 전방 장애물 피하는 기본 방향 패턴


지난 시간에 몇가지 초음파센서로 장애물 감지에 대해서 살펴 보았습니다. 장애물 감지 중 첫번째 회전 초음파센서로 장애물 감지를 기준으로 한번 패턴을 만들어 보도록 할까요.

주행 중 회전 초음파센서로 장애물을 감지 상황은 다음과 같습니다.


주행 중에 전방에 장애물이 감지하는 한다고 상상해 봅시다. 이 상황에서 여러분들은 어떤 주행 패턴을 만드시겠습니까?


좌회전 아니면 우회전 그것도 아니면 후진 중 어떤 주행패턴을 하실지 머리속에서 그려 보세요.

#include <AFMotor.h>

AF_DCMotor motor1(3); //왼쪽 모터
AF_DCMotor motor2(4); //오른쪽 모터

두개의 DC기어모터를 제어할 Motor가 있을 때

  • motor.run(FORWARD) : 전진
  • motor.run(BACKWARD) : 후진
  • motor.run(RELEASE) : 해제

모터의 기본 동작함수인데 이 함수를 통해서 장애물을 피하는 패턴을 만듭니다.

if(장애물 감지){
  장애물 피하기 패턴;
}

장애물 감지 시 제자리에서 좌/우 회전을 시킨 후 회전 된 방향으로 주행을 계속 진행하도록 주행패턴을 만들어 보도록 합시다. 단, 90도 회전은 0.5초동안 회전하고 180도 회전은 1초동안 회전이라고 가정하고 상상 코딩을 합니다. Motor의 속도에 따라 시간에 따른 회전각은 달라집니다. 개념을 잡기 위해서 0.5초은 90도 이고 1초가 180도라고 상상한 것이기 때문에 이대로 실제 코딩하시면 안됩니다. Motor 속도와 시간에 대한 회전각도를 수학적으로 계산하시거나 단순하게 속도에 따른 시간별 각도를 일일히 체크해서 각도를 가늠하시거나 둘 중 하나를 선택하셔서 나중에 실제 만들어 보세요.


[90도 우회전 후 전진]

motor1.run(FORWARD);
motor2.run(BACKWARD);
delay(500);   
motor1.run(FORWARD);
motor2.run(FORWARD);

[90도 좌회전 후 전진]

motor1.run(BACKWARD);
motor2.run((FORWARD););
delay(500);   
motor1.run(FORWARD);
motor2.run(FORWARD);

이렇게 하면, if문으로 장애물 감지되면 좌/우로 90도 회전 시킨 후 그 방향으로 전진하게 됩니다. 장애물 피하기 주행 패턴이 단순하죠.

다음으로, 뒤로 회전 한다면 좌/우로 회전 방향은 상관 없이 180도 회전을 시키면 됩니다.


[180도 뒤로 회전 후 전진] 우방향을 기본으로 잡았을 경우

motor1.run(FORWARD);
motor2.run(BACKWARD);
delay(1000);   
motor1.run(FORWARD);
motor2.run(FORWARD);

대충 이런 느낌이 됩니다. 1초가 정확히 180도 회전이 아닙니다. 가정한 것이고 DC기어모터 회전 속도에 따라서 회전각은 달라집니다. 가정하에서 코딩한 것이기 때문에 감안하시고 보세요.

이렇게 해서 간단히 장애물을 피해서 주행을 할 수 있게 되었습니다.

2) 전방 장애물을 피할 때 문제점


방금 세가지 피하는 방식에서 문제가 발생할 수 있습니다. 제자리에서 아래 그림처럼 DC기어모터가 회전하면 바퀴를 기준으로 몸체가 돌아가게 됩니다. 몸체가 돌아갈 때 장애물과의 충돌이 발생 할 수 있습니다.


제자리 좌/우회전을 시키거나 뒤로 180도 회전 시킬때 장애물 거리 감지가 짧을 경우 아래와 같은 상황이 발생 할 수 있습니다.

[우회전]


[180도 회전]



위 그림같은 상황 때문에 주행 중 진행 방향에 장애물이 감지 되었을 때 다음 장애물 피하기 동작 패턴을 만들려면 감지한 거리과 아두이노 RC카의 몸체의 크기를 고려해야 합니다. 그리고 피하는 행동을 할 때에도 주변 환경의 장애물과의 충돌 상황도 고려해야 합니다.

[해결 방법]

  • 첫번째, 장애물 감지 거리를 좀 더 길게 잡으면 해결 됩니다. 아두이노 RC카가 충분히 회전 할 수 있는 거리만큼을 장애물 감지 거리로 설정해 놓으면 장애물 감지 후 바로 회전을 하더라도 정상적으로 회전 할 수 있습니다.
  • 두번째, BACKWARD(후진)을 일정 거리만큼 시킨 뒤 회전을 시키면 됩니다. 하지만, 이 경우에는 또 다른 문제점으로 후진 후 회전시 아래 그림처럼 측면에 장애물이 있을 경우 충돌 가능성이 있습니다.

위 그림처럼 문제가 생기면 후면에 초음파센서를 부착하여 후진 시 뒷면에 장애물을 감지하면 해결 할 수 있겠죠. 그런데 이부분은 코딩화 하지 않겠습니다 계속 상황을 만들어 가면 이야기가 끝도 없기 때문에 의미만 전달하기 위해서 이정도로 마무리 합니다.

장애물을 피하는 주행 패턴을 만들 때 처음에 제가 설명한 세가지 주행 패턴을 기반으로 상상코딩을 한 뒤에 주행 패턴을 변화시키십시오. 만약에, 자신이 만든 주행패턴에 문제가 생기면 그 문제 해결을 위한 주행 패턴을 만들고 기존 소스에 덧붙여가며 코딩을 늘려가시면 문제를 충분히 해결 할 수 있게 됩니다. 처음은 단순한 주행패턴으로 머리속에서 정리하고 그 주행 패턴에서 하나씩 새로운 주행 패턴을 만들어가면 좀 더 정교한 장애물 피하기 패턴을 만들 어 가면 자율주행하는 재밌는 아두이노 RC카를 만들 수 있습니다.

2. 회전 초음파센서로 장애물 감지 후 피하기 패턴 - I



위 그림처럼 초음파센서는 전방에 일정 각도로 좌/우 회전을 하면서 각도마다 거리를 측정하게 됩니다. A, B, C로 세부분으로 나누었지만 만약, 40~140도 사이의 각도를 10도 간격으로 하면 11개의 각도의 장애물 감지 거리를 측정하게 됩니다. 11개의 각도가 나올 때 전방 중앙을 90도 각도로 하면 좌/우 5개의 각도로 장애물 감지 거리가 측정 할 수 있습니다.

이렇게 각도별로 장애물이 감지 되었을 때 어떻게 상상코딩을 할까요. 정중앙을 90도기준으로 장애물이 감지된 초음파센서 각도에서 반대 방향으로 회전시켜 장애물을 피하는 간단한 방법이 있습니다.

위 그림에서 B가 90도 기준으로 A은 140도 방향이고 C은 40도방향이라고 한다면 A방향은 좌측이고, C방향은 우측으로 나눌 수 있습니다. 이때, A방향의 장애물이 감지 되었을 때 우측으로 진로 C방향으로 진행하도록 합니다. C방향 우측에서 장애물이 감지되면 좌측으로 진로 C방향으로 진행하게 하면 간단한 피하기 패턴을 만들 수 있습니다.

[A방향 장애물 발견 시 90도 우회전 후 전진]

motor1.run(FORWARD);
motor2.run(BACKWARD);
delay(500);   
motor1.run(FORWARD);
motor2.run(FORWARD);

[B방향 장애물 발견 시 90도 좌회전 후 전진]

motor1.run(BACKWARD);
motor2.run((FORWARD););
delay(500);   
motor1.run(FORWARD);
motor2.run(FORWARD);

이렇게 진로 방향을 바꾼다면 장애물 감지는 아래와 같이 코딩을 할 수 있겠죠.

if(장애물거리<15){
  if(초음파각도>90){
      [A방향 장애물 발견 시 90도 우회전 후 전진];
  }
  else{
      [B방향 장애물 발견 시 90도 좌회전 후 전진];
  }
}

이렇게 단순하게 표현 할 수 있습니다.

추가로

전방, 좌/우측 방향의 장애물이 나타 났을 때 각도별 상황 구간을 잡아 놓고 아두이노 RC카를 좀 더 세부적으로 아래와 같이 if문으로 나누어서 각도별로 회전 주행 패턴을 만들 수 있습니다. 아래는 3구간으로 나눴지만 더 나눌 수 있고 또는 각 나눈 구간에서 다시 if조건문으로 해당 구간의 각도 범위에서 좀 더 각도를 나눠서 제어 방식을 변경할 수 도 있습니다. 이것은 여러분들의 상상력에 달려 있으니깐 한번 상상해 보세요.

if(A상황일때) 우회전;
else if(B상황일때) 좌/우회전 결정;
else if(C상황일때) 좌회전;

3. 회전 초음파센서로 장애물 감지 후 피하기 패턴 - II


위에서는 단순하게 장애물이 발견 된 시점에서 제자리에서 방향전환을 시키고 그다음 주행을 진행 했습니다. 뭔가 주행 중에 멈춰서 회전을 하기 때문에 끊기는 느낌으로 주행이 됩니다.


위 그림처럼 부드럽게 반원을 그리 듯 곡선 주행을 시키는 주행 패턴을 만들고 싶지 않으신가요. 이 방식은 어떤 주행패턴 일까요.

지난 [아두이노] 아두이노 2륜 RC카 주행 패턴 실험에서 주행패턴을 여러가지를 설명했는데 다음 주행패턴 코딩으로 표현을 할 수 있습니다.

motor1.setSpeed(130);
motor2.setSpeed(200);
motor1.run(FORWARD);
motor2.run(FORWARD);

바로, 이 주행패턴 코딩으로 곡선 주행을 하게 됩니다. 좌회전을 하고 싶다면 좌측 DC기어모터의 속도를 낮추게 되면 좌회전을 위 그림처럼 하게 되고 우회전을 하겠다면 우측 DC기어모터의 속도를 낮추면 그 방향으로 회전이 이뤄집니다. 실험에 사용하는 2륜 아두이노 RC카의 방향전환은 해당 DC기어모터의 속도에 따라서 방향을 전환하면서 주행할 수 있습니다. 도로주행과 같은 느낌의 주행이 되겠죠.

하지만, 아두이노 RC카는 이 방식만 있는게 아닙니다. 앞바퀴에 핸들식으로 서보모터를 부착해서 좌/우회전을 시킬 수 있는 방식이 있습니다. RC카 완제품을 사시면 앞바퀴 핸들식으로 무선조정하는 제품들을 많이 보셨을 꺼에요. 이방식에서는 서보모터가 45도가 수평일 때 90도는 좌회전이고 0도는 우회전으로 방향으로 조정하고 뒷바퀴는 그냥 RC카의 속도와 전진/후진 기능을 담당하면 됩니다.


servo.write(90);
motor1.run(FORWARD);
motor2.run(FORWARD);

이렇게 코딩을 바꾸면 됩니다.

마무리


대충 장애물 감지 했을 때 몇가지 패턴들을 만들어 보았습니다. Post [아두이노] 아두이노 2륜 RC카 주행 패턴 실험 의 내용에서 별로 바뀐 부분은 없습니다. 그냥 장애물 감지했을 때 상황을 두고 몇가지 패턴만 만들었을 뿐입니다. 상황을 그림으로 그리고 나서 그 그림을 기반으로 DC기어모터의 바퀴 회전을 어떤 형태로 회전 시킬 건지만 여러분들이 결정해서 패턴을 만들어 내면 됩니다.

  • motor.run(FORWARD) : 전진
  • motor.run(BACKWARD) : 후진
  • motor.run(RELEASE) : 해제

이 함수 3개를 이용하여 초음파센서로 장애물이 감지되면 각 DC기어모터를 FORWARD, BACKWARD, RELEASE 중 하나를 선택하여 장애물를 피하는 주행 패턴을 만들면 됩니다.

처음에는 이렇게 단순하게 장애물에 대해서 난 이렇게 피할꺼야! 아니면 저렇게 피할꺼야! 하면서 쉽게 만들면 됩니다. 처음부터서 정교하게 실제 차처럼 주행시켜야하지 하면 단순하 RC카 주행도 못시킬 가능성이 큽니다. 처음은 단순하게 무조건 쉽게 아두이노 RC카 주행을 시키면서 거기서 문제점이 발견되면 그 문제에 대해 다시 또 다른 주행 패턴을 만들고 하면서 조금씩 살을 붙여가면서 하셔야 정교한 주행이 되는 아두이노 RC카를 만들어 낼 수 있습니다.

오늘 Post도 처음에는 기조척으로 90도 기준으로 해서 좌/우 방향 회전하여 주행 패턴을 만들었는데 만들고 나서 Post를 쓰다보니깐 쓰는 도중에 상상코딩을 하게 되면서 주행 패턴에 대해 내용이 추가되었네요. 몇가지 더 내용이 있었는데 너무 길게 쓰는 것은 좀 그래서 중간에 멈췄네요. 다른 상황과 주행 패턴이 있었는데 대충 이런식으로 장애물을 피하는 주행패턴을 만들 수 있다는 의미를 전달하고 여러분들의 상상력에 맞기는게 나을 것 같아서 이정도로 마무리 했네요.

다음 Post에서 장애물 감지와 장애물 감지 했을 때 어떤 주행 패턴을 선택할지는 결정을 못했네요. 90도 기준으로 좌/우 방향 회전이 가장 유력하지만 너무 복잡한 주행 코딩은 안하고 단순하게 의미전달하는 주행 코딩으로 소개할 것 같습니다. 아무튼 여러분들은 오늘 제가 상상한 주행 패턴을 보시고 여러분들도 직접 장애물이 이렇게 배치 되어 있다면 난 이런 주행을 시켜봐야지 하고 상상을 하는 시간을 가졌으면 합니다.

댓글()

[아두이노] 초음파센서 아두이노 RC카 장애물 감지 방법

IOT/아두이노|2019. 5. 27. 09:00

[아두이노] 초음파센서 아두이노 RC카 장애물 감지 방법



오늘은 초음파센서로 전방에 장애물을 감지하는 방법에 대해서 이야기를 할까 합니다. 우선, 주행하는 전방에 장애물이 등장 했을 때 여러분들이 제작한 RC카가 어떤 주행을 시킬 것인지 상상을 한번 해봅시다. 주행과 장애물 감지에 대해 잘 상상이 안될 수 있습니다. 그런데 주행과 장애물 감지 원리를 알면 그렇게 어렵지 않습니다. 주행과 장애물 감지에서 주행은 그냥 전진주행을 하게 하고 전진 주행 중 장애물에 대해 일정시간 단위로 초음파센서가 측정하게 하면 됩니다. 이 두가지 과정을 loop()함수에서 반복하게 하면 아두이노가 전진 주행중에 실시간으로 계속 장애물을 감지하게 되겠죠. 그리고 장애물이 감지 되었을 때 주행 방향을 바꿔주는 코딩을 하면 아두이노 RC카 자율주행이 됩니다. 복잡하게 출발하지 말고 쉽게 전진 중행을 하면서 초음파센서로 거리를 측정하여 장애물을 감지한다는 개념만 잡고 출발하시면 됩니다.


그러면, 간단히 상황을 설정하고 장애물 감지에 대해 살펴보도록 합시다.

1. 아두이노 RC카의 기본 주행



참고 자료를 보시면 주행 패턴에 대한 내용입니다. RC카가 주행을 하기 위한 기본 소스 코딩이 있는데 주행 패턴에 대해 사전학습을 하셨으면 합니다. 우선 아두이노 RC카의 움직임을 먼저 머리속에서 개념을 잡아놓고 출발하시면 쉽게 코딩 할 수 있습니다.

자율 주행은 전진 주행을 기본 베이스로 합니다.

#include <AFMotor.h>

AF_DCMotor motor1(3);
AF_DCMotor motor2(4);

void setup() {
  motor1.setSpeed(200);
  motor2.setSpeed(200);
  motor1.run(RELEASE);
  motor2.run(RELEASE);
}

void loop() {  

  초음파센서 장애물 감지;

  motor1.run(FORWARD); //전진
  motor2.run(FORWARD);
  delay(1000);
}

이렇게 기본 베이스 코딩이 끝났습니다. 실행을 하면 무조건 전진 주행을 하게 됩니다. 지난 RC카 post에서 위 소스에 대한 설명을 했기 때문에 간단히만 재정리 차원으로 설명하면 motor1(3), morot2(4)로 모터쉴드에서 M3, M4의 위치의 핀으로 DC기어모터 두개를 제어하는데 속도는 setSpeed(200)으로 스피드가 200이고 FORWARD(전진), BACKWARD(후진), RELEASE(해제) 중에서 loop()함수에서 무한 반복 FORWARD(전진)을 한다는 로직입니다.

딱 한줄로 정리하자면 전진만 하는 RC카라고 생각하시면 됩니다.

자율주행 기본 베이스 코딩은 끝났습니다. 이 소스를 기반으로 초음파센서로 장애물을 감지하는 방법들을 살펴 볼까요.

2. 초음파센서로 장애물 감지


초음파센서로 장애물을 감지하는 방식은 엄청 많습니다. 지금 소개하는 방식 외에도 더 많지만 생각나는 것만 몇개 간단히 설명을 할까 합니다.

1) 회전 초음파 센서로 장애물 감지



위 그림처럼 직진으로 주행하다가 장애물이 나타났을 때 초음파센서가 장애물을 어떤 식으로 감지 해야 할까요. 초보적 접근 방법으로는 두가지 방식으로 나누어 살펴 볼 수 있습니다. 주행 중 전방, 좌/우측 방향의 장애물 감지하는 방식과 주행 중에는 전방 장애물만 감지하다가 장애물 발견시 주행 중단 후 좌/우측 방향 장애물 감지하는 방식으로 나누어 살펴 볼 수 있습니다.

주행 중 전방, 좌/우측 방향의 장애물 감지



위 그림처럼 아두이노 RC카는 전방으로 진행하면서 서보모터가 부착된 초음파센서가 전방 일정 각도 범위로 왔다 갔다 회전하면서 장애물을 감지합니다. 이 방식은 전방, 좌/우측 방향으로 3가지 방향을 실시간으로 측정하여 장애물이 감지했을 때 이 3가지 방향의 값을 기준으로 장애물을 피하기 위한 RC카 주행 패턴을 만들 수 있습니다. 주행 중에 초음파센서가 장애물 감지하기 위해 거리 측정을 하고 장애물이 감지 된 측정 각도에 따라 진행 방향을 바꿀 수 있기 때문에 장애물이 감지된 각도에 따라 적절하게 방향을 바꿀 수 있게 할 수 있습니다.

void loop() {
  //회전
  servo.write(angle);
  delay(50);  

  //거리측정
  int distance = sonar.ping_cm();
  
  //장애물 감지
  if(distance<15){
      각도에 따른 장애물 피하기 동작;  
  }
  
  //회전 각도
  if (angle == 140) state = -10;    
  else if (angle == 40) state = 10; 
  angle += state;

  전방주행;
}

위 소스는 지난시간에 정상적으로 초음파센서가 0~180도 회전되는지 테스트한 코딩을 약간 수정한 소스입니다. 이 코딩을 기반으로 0.05초 간격으로 회전하면서 거리를 측정하면 됩니다. 회전 각도는 10도씩하여 40~140도 사이를 11번을 측정하게 됩니다. 1도씩 안하고 10도씩 한 이유는 사실 장애믈을 측정하기 위해서 1도씩 하면 110번을 측정해야하고 왕복 220번을 0.05초 간격으로 측정을 한다면 꽤 긴시간이 걸리기 때문에 실시간 대응이 어렵습니다. 짧게 돌면서 원하는 동작을 수행하기에는 10도정도가 가장 이상적이네요. 더 큰 각도로 제어해도 됩니다, 그냥 90도, 135도, 45도 이렇게 세각도만 측정해도 됩니다. 선택은 여러분의 마음입니다.

주행 중에는 전방 장애물만 감지하다가 장애물 발견시 주행 중단 후 좌/우측 방향 장애물 감지



아두이노 RC카가 직진 주행하는 중 전방 방향의 장애물만 측정합니다. 그러다가 장애물이 발견 되면 위 그림에서 오른쪽 그림처럼 A, B 각도의 장애물을 있는지 측정해서 둘 중 안전한 방향을 찾아 진행 방향을 선택하는 방식입니다.

void loop() {
  delay(50);  

  //거리측정
  int distance = sonar.ping_cm();
  
  //장애물 감지
  if(distance>0 && distance<15){
    주행정지
      angle=135;
        servo.write(angle);
        delay(500);
        int distanceA = sonar.ping_cm();
        angle=45;
        servo.write(angle);
        delay(500);
        int distanceB = sonar.ping_cm();
        
    if(distanceA<distanceB){
         B방향으로 피하기 주행;  
    }
    else{
         A방향으로 피하기 주행;
    }
  }
  전방주행; 
}

위 소스는 0.05초 간격으로 전방 장애물을 감지하다가 장애물이 발견되면 if문으로 들어가서 먼저 주행이 정지되고 그다음 135도와 45도의 방향으로 장애물을 감지합니다. 그리고 둘 중 거리값이 긴 쪽으로 장애물을 피하는 동작을 만들 수 있습니다.

2) 고정 초음파센서 장애물 감지



고정 형태로 한개의 초음파센서로 전방 장애물만 감지합니다. 전방 장애물을 감지 할 때 장애물을 피하기 동작 패턴을 만들기가 애매합니다. 그냥 랜덤 방향으로 할지 아니면 무조건 우방향으로 피할지는 여러분의 마음에 달렸습니다.

void loop() {
  delay(50);  

  //거리측정
  int distance = sonar.ping_cm();
  
  //장애물 감지
  if(distance<15){
    장애물 감지 방향선택;
  }
  전방주행; 
}

코딩은 가장 단순합니다. if문으로 전방 장애물이 발견 되면 그냥 후진, 좌/우 방향으로 진로 변경 할지는 여러분들의 선택으로 하나의 방향을 만들어 주면 됩니다.

3) 양쪽 두개의 고정 초음파 센서 장애물 감지



두개의 고정된 초음파 센서로 장애물을 감지할 경우 A, B 두지점으로 좌/우 방향 위치의 장애물을 감지하게 됩니다. 이 경우는 위에서 전방 장애물을 감지 되었을 때 주행을 정지 한 상태에서 좌/우 방향 위치의 장애물을 감지 했던 코딩과 유사 합니다. 차이점은 주행 중에 실시간으로 좌/우 방향의 장애물을 감지 한다는 점만 차이가 있습니다.

void loop() {
  //거리측정
  delay(50);  
  int distanceA = sonar[0].ping_cm();  
  delay(50);  
  int distanceB = sonar[1].ping_cm();
    
  //장애물 감지
  if(distanceA<15 || distanceB<15){
    if(distanceA<distanceB){
         B방향으로 피하기 주행;  
    }
    else{
         A방향으로 피하기 주행;
    }
  }
  전방주행; 
}

이렇게 두 지점의 거리를 측정 한 뒤에 if문에서 조건식을 체크하는데 '||'로 두 조건식 중 하나라도 만족하면 참인 조건문으로 체크하게 됩니다. 둘중 하나라도 만족하면 진로 방향에 장애물이 있다고 간주하는 것이죠. 즉, A or B의 거리가 15cm 이하이면 참이되어 장애물을 감지된 걸로 간주 합니다. 여기서, 두 지점을 비교하여 A지점이 거리가 더 짧으면 B방향으로 주행하게 만들고 A지점의 거리가 더 길면 A방향으로 주행하도록 만들 수 있습니다.

이외에도 장애물 감지 방식은 여러분들이 상상하는 방식에 따라서 다양하게 표현이 가능합니다. 순간 떠오른 생각들을 몇가지 정리한 것이 위의 장애물 감지 표현입니다. 여러분들도 이 방식 말고 다른 방식으로 장애물을 감지할 수 있는 방법을 찾아보셨으면 합니다. 그래야 상상코딩 능력을 키울 수 있습니다.

마무리


오늘은 초음파센서를 이용하여 장애물을 잠지하는 방법들을 살펴 보았습니다. 초음파센서를 어떤 형태로 배치하느냐에 따라서 감지하는 방식이 달라집니다. 어떤 방식이 정도라도 말 할 수 없으며 여러분들이 원하는 방식으로 표현하시는게 바로 정도입니다. 그리고, 위에서 열거한 방식으로 구지 안하셔도 됩니다. 여러분들의 상상력으로 감지 방식을 만들면 됩니다.

장애물 감지를 위해 실험에 사용한 초음파센서를 이용하셔도 되고 다른 거리측정 센서를 이용하셔도 됩니다. 아니면 전혀 다른 방식으로 주변을 감지할 수 있는 부품을 사용하여 표현하셔도 됩니다. 저는 가장 쉽게 접근할 수 있는 방식이 초음파센서라서 초음파센서를 사용한 것 뿐입니다. 여러분들이 다른 방식으로 하기 어려우시다면 1200원 짜리 초음파센서를 이용하여 실험하셔도 됩니다. 초음파센서를 어떻게 배치하고 사용할지는 여러분의 상상력에 달려 있음으로 한번 상상의 나래를 펼쳐보세요.

댓글()

[아두이노] 초음파센서 아두이노 RC카 자율주행 준비 입문자용

IOT/아두이노|2019. 5. 26. 09:00

[아두이노] 초음파센서 아두이노 RC카 자율주행 준비 입문자용



아두이노 2륜 RC카를 가지고 초음파센서와 결합하여 장애물을 감지하면서 자율주행을 할 수 있도록 하는 간단한 실험을 할 예정입니다. 오늘 Post는 초음파센서를 이용한 자율주행 RC카를 만들기 위한 몸체 조립과 조립에 대한 테스트 동작을 통해 아무 문제가 없는지 확인하는 단계의 내용으로 구성되어 있는데 천천히 배워보도록 하죠.

진짜로, 정교한 자율주행 RC카를 만든다면 영상처리 부분까지 하셔야 합니다. 입문자분들이 바로 영상처리까지 하기는 무리가 따릅니다. 저도 영상처리 부분은 어렵고 실시간 영상을 가지고 주변환경 영상의 에지를 추출하여 그 값을 기반으로 주행 처리를 해야하는데 대충 코딩 방향은 아는데 실시간 영상에서 에지 검출이 좀 힘들어서 설명드리기가 어렵네요. 에지 검출이 영상처리책 보면 대표적으로 canny, sobel 에지 검출 알고리즘이 있습니다. 이 알고리즘으로 정지 영상에서 에지를 추출하는 것은 쉽지만 실시간 영상에서는 저도 알고리즘 로직이 잘 감이 안와서 설명을 못 드리겠네요. opencv로 얻은 영상에서 에지를 검출하고 그 영상을 기반으로 주행을 시키는 것 같은데 고급 자율주행은 사실 깊게 이쪽 분야로 공부를 안해서 아쉽게 실험을 못해 봤습니다. 나중에 라즈베리파이로 공부할 때 한번 도전은 해보고 싶긴 하네요.

사설은 그만하고 오늘 post의 주제는 지난 시간에 만든 아두이노 RC카 정면에 초음파센서를 부착하는 조립하는 단계입니다. 초음파센서에 서보모터를 통해 0~180도 사이각의 회전을 시킬 수 있는 형태로 조립을 하게 되는데 사실 뼈대가 부품가격과 거의 비슷하기 때문에 그냥 테이프로 부착시켰습니다. 테이프로 부착하다보니 정교한 측정이 되지 않지만 그래도 근사값 측정을 통해서 원하는 동작을 충분히 실험할 수 있기 때문에 아무런 문제는 없습니다. 여러분들은 뼈대를 구매하거나 3D 프린트로 직접 만들시면 좋겠죠. 저처럼 테이프로 고정시키는 실험은 보기 흉하니깐 뼈대를 사셔서 이쁘게 만들어 보세요.


이제 본격적으로 아두이노 RC카 자율주행을 도전 할까요.

1. 아두이노 RC카 준비



사전학습 post로 가셔서 아두이노 RC카에 대한 학습을 먼저 하셔야 합니다. 대충 사전학습에서 RC카 조립을 끝낸 상태면 이렇게 완성되어 있겠죠.


조립이 완성된 상태에서 다음 준비 부품이 필요합니다.

2. 아두이노 RC카에 추가 부품 조립


  • 준비물 : 아두이노 RC카 1대, 초음파센서 1개, ServoMotor 1개


위 사진에서는 뼈대가 있으면 좋은데 간단히 실험을 하기 위해서 그냥 테이프로 붙였습니다. 아두이노 RC카 정면에 Servo Motor와 초음파 센서를 정면을 바라보도록 배치하시면 됩니다. 혹시 뼈대를 개인적으로 구매하신다면 안정적으로 배치 할 수 있겠죠. 저는 테이프로 붙여서 각도도 안맞고 약간 아래로 쳐져 있어서 좀 불편하게 실험 했네요.


위에서 바라보는 모습입니다. 보시면 A은 초음파센서핀으로 연결합니다. 초음파센서 핀은 A4(Trig), A5(Echo)핀입니다. 그리고 B는 Servo Motor 핀을 연결합니다. 바깥쪽부터 (-,+,sig) 이렇게 순서대로 있는데 3핀을 순서대로 연결하시면 됩니다. 제가 실험한 모터쉴드는 2개의 Serovo Motor 핀을 제공하는데 상단 첫번째 줄은 아두이노 디지털핀 10번이고 두번째 줄은 9번핀이네요. 10번핀을 사용하였습니다.

좀 더 자세히 아래 사진의 L293D Motor Shield의 모습을 보시고 연결하세요.


해당 위치에 핀을 꼽아주고 아두이노 RC카 정면에 Servo Motor와 초음파 센서를 부착하시면 조립은 완성입니다.

2. 간단히 장애물 감지센서 시범 회전 코딩



Servo Motor

#include <Servo.h> 
  • Servo servo : 서보모터 객체변수 선언
  • servo.attach(servoPin) : 서보모터 시작하는데 사용되는 핀은 servoPin(10) 임
  • servo.write(angle) : angle 값으로 서보모터를 회전 시킨다.

NewPing

#include <NewPing.h>
  • NewPing sonar(TrigPin, EchoPin, MaxDistance) : TrigPin과 EchoPin과 최대제한거리(MaxDistance)의 값을 선언합니다.
  • sonar.ping_cm() : 센서 거리를 'cm'로 계산된 값을 출력한다.

1) Servo Motor 회전

Servo Motor가 정상적으로 회전이 되는지 테스트 코딩을 해야 겠지요.

Servo servo;
const int servoPin = 10;
int state = 1;
int angle = 90;

이렇게 우선 서보모터 객체변수을 하나 만들고 서보모터를 제어할 아두이노 디지털 핀 10을 선언하고 초기 각도(angle) 값을 90도로 표현 했습니다. 그리고 state은 초기값이 1인데 이 값은 1이면 1씩 증가 -1이면 1씩 감소하는 변수로 사용하기 위해 만든 변수입니다.

void setup() {
  servo.attach(servoPin);
  servo.write(angle);
  delay(1000);
}

setup()함수에서 초기화 작업을 하는데 서보모터를 시작하고 바로 servo.write()함수를 사용하여 angle(90)각도로 회전합니다. 딜레이 시간은 1초를 줬네요. 초기값이라서 원하는 시간값을 여러분들이 마음대로 결정하세요. 서보모터를 회전시킬 만큼의 딜레이시간을 줘야하는데 1초은 꽤 긴시간이고 더 짧게 딜레이시간을 주고 90도까지 회전 시켜도 됩니다. loop()함수로 진입하기 전에 충분히 대기시간을 주기 위해서 1초정도 딜레이를 줬네요.

void loop() {
  //회전
  servo.write(angle);
  delay(50);  

  //회전 각도
  if (angle == 180) state = -1;    
  else if (angle == 0) state = 1; 
  angle += state;
}

loop()함수는 0.05초 간격으로 서보모터를 angle(각)으로 회전시키게 됩니다. 어떻게 회전될 까요 회전 각도 로직을 살펴보시기 바랍니다.

  if (angle == 180) state = -1;    
  else if (angle == 0) state = 1; 
  angle += state;

보시면 angle이 180도면 state은 -1이고 angle이 0도이면 state 값은 1이다. 이 의미를 이해하기 위해서는 아래 문장을 보시면 쉽게 이해가 되실 꺼에요.

angle = angle + state;

이 문장은 0.05초 간격으로 loop()함수가 무한 반복하는데 기존에 angle값에다가 state을 계속 더해주는 문장입니다. 여기서, state가 1이면 1씩 증가하고 state가 -1이면 1씩 감소하게 됩니다. 의미를 잘 이해해 주세요. 이렇게 되면 초기값이 state가 1이기 때문에 초기각도 angle이 90도니깐 1도씩 증가하게 됩니다.

서보모터는 0~180도까지 제어가 가능합니다 그러면 1씩 증가하더라도 180도를 넘을 수 없게 됩니다 180도가 되면 state값을 -1로 변경하면 1도씩 감소하게 되고 역방향으로 회전하게 됩니다. 그러면, 다시 역방향으로 1도씩 감소하는데 angle은 0이하로 떨어질 수 없습니다. 이때 state 값을 1로 변경하고 다시 정방향으로 1도씩 증가하게 됩니다. 이렇게 표현하면 0~180도 사이를 1도씩 왔다 갔다 회전하게 됩니다.

loop()함수 로직의 동작은 0.05초 간격으로 0~180도 사이를 무한 반복 회전을 하게 됩니다. 이 움직임을 보시면 초음파레이더가 생각 나시는 분이 있을 지 모르겠네요. 초음파레이더의 기본 동작 코딩입니다. 이 코딩으로 초음파레이더를 만들 수 있습니다. 현재 우리가 초음파레이더를 만드는 것이 목적이 아니기 때문에 서보모터 회전에 대한 테스트 동작으로만 이해해 주셨으면 합니다.

2) 초음파센서 거리 측정


이제는 서보모터가 회전할 때마다 초음파센서로 거리를 측정해야 겠죠.

NewPing sonar(A4, A5, 100);

trig(A4), Echo(A5), 제한거리1미터(100)으로 초음파센서 객체변수를 만듭니다.

  int distance = sonar.ping_cm();

distance 변수에 초음파센서에서 측정한 거리(cm)값이 저장됩니다. 이렇게 해서 거리 측정이 끝났네요.

그러면 이 코딩은 전체소스 안에서 어느 위치에 삽입하는 것이 좋을까요. 회전 시마다 초음파센서로 측정한다고 했죠. 서보모터는 어떻게 회전한다고 했죠. 0.05초 간격으로 회전이 이뤄집니다. 그러면, 0.05초 간격으로 회전할 때 초음파센서로 측정하면 되겠죠. 즉, 서보모터를 angle(각)으로 회전시킨 후 초음파센서로 거리를 측정하게 코딩하면 간단히 해결 됩니다.

void loop() {
  //회전
  servo.write(angle);
  delay(50);  
    
  int distance = sonar.ping_cm();
    
  //회전 각도
  if (angle == 180) state = -1;    
  else if (angle == 0) state = 1; 
  angle += state;
}

이렇게 하면, 0.05초 간격으로 서보모터를 회전시키고 distance 변수에 회전 시킨 각도에서 거리측정한 값이 저장됩니다.

그러면, 이 값을 시리얼모니터로 정상적으로 출력되는지 Serial 함수로 표현하면 되겠죠.


  • Serial.begin(9600) : 시리얼통신 시작
  • Serial.print(distance) : 시리얼모니터로 출력

3) 종합 코딩


좀 더 깔끔하게 각도와 거리값을 동시에 시리얼모니터로 출력시켜서 확인할 수 있도록 소스를 수정하였습니다. Serial함수는 너무 많이 설명을 드렸기 때문에 구지 설명을 안드려도 아실꺼라 믿고 아래 변경된 부분만 잘 확인해 주세요.

참고로, 완성된 소스는 초음파레이더 소스와 동일합니다. 이 소스로 processing으로 시각화 하면 초음파레이더가 됩니다. 초음파레이더로 보시지 마시고 그냥 앞에 장애물을 감지하는 센서로 보셨으면 합니다. 서보모터의 회전이 정상적으로 이뤄지고 거리측정이 정상적으로 이루어지는지 테스트하는 용도로 코딩을 바라 보시기 바랍니다.

[소스]

#include <Servo.h> 
#include <NewPing.h>

const int TRIG = A4;
const int ECHO = A5;
const int MAX_DISTANCE = 100;
NewPing sonar(TRIG, ECHO, MAX_DISTANCE);

Servo servo;
const int servoPin = 10;
int state = 1;
int angle = 90;

void setup() {
  Serial.begin(9600);
  servo.attach(servoPin);
  servo.write(angle);
  delay(1000);
}

void loop() {
  //회전
  servo.write(angle);
  delay(50);  

  //거리측정
  int distance = sonar.ping_cm();
  Serial.print(angle);        
  Serial.print(" : ");        
  Serial.print(distance);    
  Serial.println(" cm");         

  //회전 각도
  if (angle == 180) state = -1;    
  else if (angle == 0) state = 1; 
  angle += state;
}

newPing 라이브러리를 혹시 안깔고 코딩하신분들이 있을 것 같아서

라이브러리 매너저 창에서 "newPing"이라고 검색하시면 자동으로 검색되고 해당 라이브러리를 설치하시면 됩니다.


3. 결과


촬영이 좀 깔끔하게 되지 못했네요. 폰으로 찍어서 하다 보니깐 공간적 제약도 따르고 해서 좀 보기 불편하시더라도 대충 어떤식으로 Servo Motor가 회전 되고 초음파센서를 통해 거리가 측정되는지 영상으로 살펴보시기 바랍니다. 참고로 위쪽 상단는 모니터가 있어서 거리값이 10~11정도로 측정됩니다. 오류값이 아니라 모니터와의 거리 때문에 나온 값이니깐 감안하시고 보세요.


마무리


오늘은 아두이노 RC카가 준비된 상태에서 두개의 부품 Servo Motor와 초음파센서를 조립에 대해 간단히 살펴보았습니다. 그리고 정상적으로 동작하는지 실험 코딩으로 테스트까지 해보았습니다. 이렇게 해서 초음파센서를 통해 아두이노 RC카는 정면의 장애물 감지까지 할 수 있게 되었습니다. 다음 단계는주행 도중에 장애물이 감지된 정보에 대해 어떻게 반응 할 것인지가 남아 있습니다.

사실 위에서 정상 작동하는지 테스트한 코딩을 이해하셨다면 조금만 상상을 더하면 장애물을 피해 자율주행을 시키는 코딩은 어렵지 않게 하실 수 있을 겁니다. 이미 이 post로 아두이노 RC카 자율주행은 끝난 거나 다름 없습니다. 그런데, 이걸로 어떻게 자율주행이 되냐고 생각하시는 분들도 많을 꺼에요. 잘 연상이 안되실 꺼에요. 상상을 많이하고 자기 자신이 RC카라고 상상하면서 내 이마에 서보모터가 부착되어 회전하고 있고 회전하면서 초음파센서로 거리를 측정하고 있다고 상상하면서 한번 제자리에서 걸어 다녀보세요. 전방에 벽이 나타나면 여러분들은 걷다가 멈추게 되겠죠. 그리고, 그 벽을 피해서 어떤 행동을 하겠죠. 벽 앞에서 다시 뒤로 되돌아 간다거나 오른쪽 길이 있으면 오른쪽으로 걸음을 옮기게 되겠죠. 이렇게 어떤 행동을 취하게 됩니다. 그 행동을 코딩화 하면 그 코딩이 아두이노 RC카의 자율주행 코딩이 됩니다. 즉, 어떤 상황이 되면 그 상황을 피하기 위해서 어떤 행동을 취하고 그 행동이 자율 주행 명령 코드가 되어 실제로 아두이노 RC카가 자율주행을 하게 됩니다. 코딩은 모르더라도 대충 어떤 느낌이신지 아시겠지요.

한번 자기자신이 아두이노 RC카라고 상상하고 제자리에서 주변을 걸어다녀보시면서 상황을 만들고 그 상황을 기록했다가 상상 코딩을 해보셨으면 합니다.


댓글()

[아두이노] MPU-6050 센서 + processing 연결 제어

IOT/아두이노|2019. 5. 13. 09:00

[아두이노] MPU-6050 센서 + processing 연결 제어



어제 MPU-6050 가속도/자이로센서에 대해 간단히 테스트를 해 보았습니다. 어제는 MPU-6050 라이브러리 없이 I2C 통신으로 센서의 값을 가져왔는데 오늘은 MPU-6050 라이브러리를 가지고 쉽게 제어하는 방법을 살펴보도록 하겠습니다. 직접 식을 만들고 그 식을 통해서 제어하면 좋겠죠. 하지만 입문자나 초보분들은 수학적으로 알아야 할 것이 너무 많습니다. 입문자의 경우는 수학적으로 접근한다면 실제 MPU-6050를 다루기 전에 지쳐버리겠죠. 처음에는 흥미를 가지고 출발해야 좀 더 관심을 가지고 공부하게 됩니다. 수학적인 부분을 모르더라도 누군가 만들어 놓은 라이브러리를 활용하여 MPU-6050 모듈을 사용할 수 있으니 부담없이 처음에 접근하기 바랍니다. 가격이 저렴한 모듈이기에 정교한 제어용으로는 다소 무리가 있습니다. 단지 입문자용으로 부담없이 사용할 수 있는 모듈이라고 생각하시면 되겠습니다.

오늘은 실험에 사용할 라이브러는 인터넷에서 찾아보면 두개의 라이브러리가 있는데 아두이노 IDE 라이브러리관리 창에서 검색한 라이브러리를 설치하여 사용하겠습니다. 그리고, 이 라이브러리 예제를 통해 얻은 값을 테스트 한 후에 이 예제를 통해서 processing으로 만든 box 도형을 MPU-6050 모듈로 움직임을 제어하는 실험을 하겠습니다.


1. MPU-6050 가속도/자이로 센서


1) MPU-6050 가속도/자이로 센서 회로도


  • 준비물 : MPU-6050 모듈, 아두이노우노
  • 내용 : I2C 통신을 위해 A4(SDA), A5(SCL)핀에 연결하시오

지난 시간에 회로도와 동일합니다.


2) 라이브러리 설치


아두이노 IDE 라이브러리관리창에서 "MPU-6050"을 검색하면 아래 그림처럼 tockn님이 만든 라이브러리가 하나만 검색됩니다. 참고로 아래에 MPU6050으로 한개 더 있는데 이건 이미 설치된 라이브러인데 외부 구글검색으로 찾아서 다운받은 후 라이브러리를 추가했기 때문에 2개의 라이브러리가 표시 된 모습입니다.


직접 검색해서 다른 라이브러리를 다운 받으실려면 "mpu-6050 github"라고 구글검색하시면 해당 링크가 나옵니다.

https://github.com/jrowberg/i2cdevlib/tree/master/Arduino/MPU6050

3) 코딩



예제에 가시면 두개의 GetAllData, GetAngle 예제가 있습니다. 둘 다 한번씩 돌려보시기 바랍니다.


여기서, GetAllData 예제은 1초 단위로 전체 데이터를 출력하는 예제이고, GetAngle 예제는 회전각 X, Y, Z 값만 출력하는 예제입니다.

실험은 GetAngle 예제를 사용 하겠습니다. 이걸로 아래 Processing으로 표현한 box 회전에 사용하기 위해서 입니다.

[GetAngle 소스]-tockn님 소스

#include <MPU6050_tockn.h>
#include <Wire.h>

MPU6050 mpu6050(Wire);

void setup() {
  Serial.begin(9600);
  Wire.begin();
  mpu6050.begin();
  mpu6050.calcGyroOffsets(true);
}

void loop() {
  mpu6050.update();
  Serial.print("angleX : ");
  Serial.print(mpu6050.getAngleX());
  Serial.print("\tangleY : ");
  Serial.print(mpu6050.getAngleY());
  Serial.print("\tangleZ : ");
  Serial.println(mpu6050.getAngleZ());
}

소스를 살펴보면,

  Wire.begin();
  mpu6050.begin();
  mpu6050.calcGyroOffsets(true);

세가지 Wire, mpu6050 시작선언과 초기 작업으로 자이로 오프셋값을 계산합니다. 교정작업으로 자이로 오프셋 평균값을 계산하는데 한번 해당 함수들의 로직을 살펴봐 주세요. 이게 나중에 실험할 때 시간을 많이 잡아먹는 부분이더군요. 평균값을 구하기 위해서 자이로 x,y,z 값을 5000번 읽어고 그 값을 5000으로 나눠서 평균값을 계산하니깐 계산시간이 좀 걸려서 실험 시 대기시간이 길어지더군요.

참고로 값을 알고 있다면

mpu6050.setGyroOffsets(1.45, 1.23, -1.32);

이런식으로 값을 지정 할 수 있습니다.

위, 3줄의 명령은 시작 초기화 함수입니다.

loop()에서는 mpu6050.update() 함수로 이전 라이브러리 없이 읽기 요청과 읽는 함수들의 로직이 이 함수안에 다 집어 넣었더군요. 그리고 그 안에서 상보필터로 회전각을 구하는 부분까지 다 포함되어 있는데 관심있는 분들은 링크 걸어놓은 곳에 "MPU6050_tockn.cpp" 가셔서 로직을 살펴보시면 됩니다.

  • 상보 필터 : filtered_angle = (0.02 * accel) + (0.98 * gyro) (tockn님 계산식)

아무튼 총 4개의 함수를 사용하면 입문자분들은 따로 계산식을 걱정할 필요 없이 회전각 mpu6050.getAngleX(), mpu6050.getAngleY(), mpu6050.getAngleZ() 함수를 통해서 값을 얻을 수 있게 됩니다.

라이브러리를 이용하니깐 그렇게 까지 복잡하지 않죠.

[결과]
다음과 같은 결과가 출력됩니다.


2. MPU-6050 + processing 실험


1) MPU-6050 회전각을 시리얼통신을 통해 String으로 전송하기


[아두이노 실험 소스]

#include <MPU6050_tockn.h>
#include <Wire.h>

MPU6050 mpu6050(Wire);

void setup() {
  Serial.begin(19200);
  Wire.begin();
  mpu6050.begin();
  //mpu6050.calcGyroOffsets(true);
    mpu6050.setGyroOffsets(0.75, 0.05, 0.05);
}

void loop() {
  mpu6050.update();  
  Serial.print(mpu6050.getAngleX());
  Serial.print(',');
  Serial.print(mpu6050.getAngleY());
  Serial.print(',');
  Serial.println(mpu6050.getAngleZ());
  delay(50);
}

위 소스를 보시면 getAngle X,Y,Z 사이에 콤마(,)을 붙여서 한줄로 3개의 데이터가 시리얼 통신으로 전송됩니다. delay(50)을 정도를 설정했는데 안써도 상관 없습니다.

2) getAngle 수정(선택 사항)


이상태로 실험하셔도 되지만 좀 더 빠르게 실험하기 위해서 초기값을 주도록 하겠습니다. calcGyroOffsets(true)함수로 초기값을 계산하는게 아니라 직접 처음에 초기값을 주어서 빠르게 동작하도록 변경하겠습니다.

처음 한번은 getAnglem 예제를 실행하여 아래와 같은 X,Y,Z값을 얻게 되면 그 값을 메모장에 적어놓으세요


처음 getAngle 예제를 돌리면 (X,Y,Z) 가 (0.67, 0.10, 0.10)으로 계산 되었네요. 5천번의 MPU-6050 모듈에 측정된 값을 계산하기 때문에 시간이 걸립니다. 시작 위치의 초기값을 먼저 잡아놓으면 다음 계산이 필요 없기 때문에 빠르게 회전각을 얻을 수 있게 됩니다.

변경은 다음과 같은 calcGyroOffsets()을 주석처리하고 setGyroOffsets()함수로 선언해주면 됩니다. 아래의 X,Y,Z 값은 processing과 연결하여 실험 할 당시의 X, Y, Z의 값입니다. 즉, 여러분들이 실험하는 장소와 위치에서 처음 측정된 값으로 초기값으로 할 경우 이 수치는 달라 집니다. 귀찮은 분들은 시간이 걸리더라고 calcGyroOffsets()함수로 통일해도 되겠죠.

mpu6050.setGyroOffsets(0.75, 0.05, 0.05);

어려분들은 둘중 하나를 선택해서 실험 하시면 됩니다.

mpu6050.calcGyroOffsets(true); : 대기 시간이 길어지더라도 계산하겠면 선택
mpu6050.setGyroOffsets(0.75, 0.05, 0.05); : 처음 한번만 계산해 놓고 그 값을 초기값으로 고정하겠다면 선택

2) processing 코딩


참고 : [아두이노] 조이스틱+processing 3D 도형 회전(1)


[참고 소스]

import processing.serial.*;

Serial myPort;
int x=0;
int y=0;

void setup() {

    println(Serial.list());
    myPort = new Serial(this, Serial.list()[0], 9600);
    myPort.bufferUntil('\n');
    noStroke();
    size(600,600,P3D);    
} 

void draw() {  
    background(0); //배경색
    lights();  //조명

    pushMatrix();  //Start
    fill(0,255,0); //채우기
    translate(width/2,height/2,-100); //이동
    rotateX(radians(y)); //x축 회전
    rotateY(radians(x)); //y축 회전
    
    box(200,100,200); //상자
    popMatrix(); //End
}
 
void serialEvent(Serial p) { 
  String inString = myPort.readStringUntil('\n');
     
  ... 생략 ...
}

조이스틱으로 실험했던 일부 소스입니다. box를 x,y 회전을 시킨 소스의 일부분인데 오늘 사용할 부분만 제외하고 나머지 소스를 지웠습니다.

processing 문자열 읽기

void serialEvent(Serial p) { 
  String inString = myPort.readStringUntil('\n');
     
  ... 생략 ...
}

이렇게 코딩해서 시리얼통신에서 문자열일 들어오면 자동으로 이벤트 호출이 일어납니다. 그리고 해당 시리얼포트로 문자열을 읽게 됩니다. 여기까지는 아두이노에서 코딩하는 방식과 같습니다. 하지만 processing에서는 링크 걸어놓은 String 방식으로 코딩을 하지 않고 간단하게 processing 함수를 이용하여 수정하겠습니다. split()함수가 제공됨으로 쉽게 전체문자열에서 x,y,z 회전각을 분리해 낼 수 있습니다.

String[] inStrings = split(inString, ',');

inString으로 읽은 한줄의 문자열에서 콤마(,)가 기준으로 쪼개집니다. 그리고, 그 값은 문자열 배열 변수 inStrings에 저장되게 됩니다. 3개의 데이터를 전송하니깐

inStrings[0] => X축 회전
inStrings[1] => Y축 회전
inStrings[2] => Z축 회전

이 값들이 저장되어 있게 됩니다. 하지만 이 값은 문자열이기 때문에 숫자형으로 변환해야 하는데 실수형 float()로 변환을 하면 다음과 같습니다.

x=float(inStrings[0]);
y=float(inStrings[1]);
z=float(inStrings[2]);

아두이노에서 시리얼통신으로 보내 온 x,y,z값을 각각 실수형 x,y,z변수에 최종적으로 저장하게 됩니다.

void serialEvent(Serial p) { 
  String inString = myPort.readStringUntil('\n');    
  String[] inStrings = split(inString, ',');
  x=float(inStrings[0]);
  y=float(inStrings[1]);
  z=float(inStrings[2]);  
}

수신 String 중 회전각만 적용하기

문자열 읽기를 그냥 위와 같이 하면 에러가 발생합니다. 그 이유는 MPU6050_tockn 라이브러리는 초기 세팅함수 안에는 다음과 같이 여러 문자열이 출력됩니다.


회전 각이 나오기 전까지 여러문장의 문자열이 출력되는데 단순위 위와같이 코딩하면 문제가 발생하겠죠. MPU6050_tockn라이브러리에 가서 cpp 파일에 해당 함수의 들어있는 print문을 전부 주석처리하면 해결할 수 있지만 라이브러리 파일을 건들면 좀 그렇겠죠.

그래서 회전각 문자열인지 체크하는 제어문을 넣어서 해결하도록 하겠습니다.

String inString = myPort.readStringUntil('\n');   
int separator_ch = inString.indexOf(",");

if(separator_ch!=-1){
  회전각 구하기;
}else{
 print(inString);
}

이렇게 indexOf(',')로 회전각 분리기호의 위치값을 찾습니다. 회전각 문자열에는 분리기호가 있기 때문에 해당 0이상의 숫자값을 갖게 됩니다. 하지만 해당 문자열에 분리기호가 없다면 '-1'이 반환 됩니다. 이 원리를 이용해서 위와 같이 if문으로 분리기호가 있으면 방금 위에서 코딩한 문자열에서 회전각을 분리해내는 로직을 붙이면 됩니다. 분리기호가 없다면 그냥 시리얼모니터 창으로 해당 문자열을 출력하게 하면 간단히 이 문제를 해결하게 됩니다.

box 회전시키기

box 회전을 하려면 이 부분을 수정해야 합니다. rotateX(), rotateY(), rotateZ() box 회전을 시키고 해당 인자값을 아까 String으로 읽은 값을

rotateX(radians(y)); //x축 회전
rotateY(radians(x)); //y축 회전
rotateZ(radians(z)); //y축 회전
    
box(200,100,200); //상자

x,y,z 축으로 회전시켜서 box()을 배치하는 코딩입니다. box()함수 앞에다가 회전함수를 넣으면 최종적으로 회전시킨 곳에 box가 그려지기 때문에 box가 x,y,z 값에 의해서 이동하는 것처럼 보이게 됩니다.

그리기 함수부분은 아래과 같이 되겠죠.

void draw() {  
    background(0); //배경색
    lights();  //조명

    pushMatrix();  //Start
    fill(0,255,0); //채우기
    translate(width/2,height/2,-100); //이동
    rotateX(radians(y)); //x축 회전
    rotateY(radians(x)); //y축 회전
    rotateZ(radians(z)); //y축 회전
    
    box(200,100,200); //상자
    popMatrix(); //End
}

[processing 실험 소스]

import processing.serial.*;

Serial myPort;

float x=0;
float y=0;
float z=0;

void setup() {
    println(Serial.list());
    myPort = new Serial(this, Serial.list()[0], 19200);
    myPort.bufferUntil('\n');
    noStroke();
    size(600,600,P3D);    
} 

void draw() {  
    background(0); //배경색
    lights();  //조명

    pushMatrix();  //Start
    fill(0,255,0); //채우기
    translate(width/2,height/2,-100); //이동
    rotateX(radians(y)); //x축 회전
    rotateY(radians(x)); //y축 회전
    rotateZ(radians(z)); //y축 회전
    
    box(200,100,200); //상자
    popMatrix(); //End
}
 
void serialEvent(Serial p) { 
  String inString = myPort.readStringUntil('\n');   
  int separator_ch = inString.indexOf(",");
  if(separator_ch!=-1){
    String[] inStrings = split(inString, ',');
    x=float(inStrings[0]);
    y=float(inStrings[1]);
    z=float(inStrings[2]);
  
    print(x);
    print('/');
    print(y);
    print('/');
    println(z);
  }else{
    print(inString);
  }
}

추가로, print문으로 정확히 분리되어 x,y,z값이 나오는지 확인하기 위해서 processing 시리얼 모니터로 출력시키는 코딩을 넣었습니다.

5. 실험 결과


MPU-6050 모듈을 움직일 때 PC로 녹화한 영상입니다.



MPU-6050 모듈을 움직일 때 processing의 box가 어떻게 움직이는지 폰을 촬영한 영상합니다.



마무리


MPU-6050 라이브러리를 이용하면 해당 회전각을 가지고 여러분들이 원하는 표현을 쉽게 할 수 있습니다.

참고로, 주의할 점은 될 수 있으면 MPU-6050 모듈에 핀을 납땜해주시고 실험 해주세요. 그리고 핀을 연결한 선도 단단히 고정시켜주세요. 조금만 흔들려도 MPU-6050이 먹통이 됩니다. 그리고, 회전 각에 크게 변화할 때에도 한번 먹통이 발생하면 더이상 측정하기는 불가능합니다. 그리고 일정시간이 지나면 값의 변화가 생기기 때문에 정교한 제어용으로 쓰기에는 다소 부족합니다. 초보 입문용으로 싼 가격에 자이로센서를 사용하는데 가성비를 최고이지만 혹시 드론 같은 전문적으로 사용할시에는 비싼 자이로센서를 쓰기기 바랍니다.

그리고, 어느정도 재미를 붙이시면 제대로 회전각을 구하는 공식을 공부해주시기 바랍니다. 사실 기존 라이브러리로만 사용해서는 어떤 실험을 할지 모르지만 정교한 제어는 어렵습니다. 그냥 무조건 값을 읽고 계산해서는 정교한 제어가 되지 않습니다. 초기 보정작업과 기타 값의 변화율에 대한 처리부분을 고려해서 코딩해야 합니다. 수학적으로 접근해야 하는 부분들이 많기 때문에 전문서적이나 구글검색을 통해 관련 자료들을 많이 찾으셔서 공부하시기 바랍니다.

저는 기초적인 부분만 다루고 실험했기 때문에 좀 깊은 공부는 아직은 관심이 덜해서 해보지는 않아서 좀 더 자세한 내용을 전달해드리지 못하네요. 제 경우는 라이브러리 코딩쪽이 아니라 측정된 값을 기반으로 제어하는 코딩에 좀 더 관심이 많아서 그냥 라이브러리를 그대로 사용하고 있네요

자이로센서를 전문적으로 다루기 위해서는 배워야 할 부분이 많지만 간단히 실험하고 기초적인 기능만 활용하시더라도 라이브러리를 이용해 원하는 표현은 가능하니깐 MPU-6050 모듈을 배워 보세요.


댓글()

[아두이노] MPU-6050 가속도/자이로 센서 제어

IOT/아두이노|2019. 5. 12. 09:00

[아두이노] MPU-6050 가속도/자이로 센서 제어 



오늘은 MPU-6050 가속도/자이로 센서를 이용하여 실험하는 시간을 갖도록 하겠습니다. 자이로 센서를 사용하려면 오일러의 공식을 알아야 하고 오일러의 공식 이해하셔야 하고 각속도와 오일러의 변화율 공식과 오일러의 각을 이해하기 위해서 피치, 롤, 요와 같은 용어적 의미와 공식을 알아야 합니다. 그리고 회전각을 구할 때 대표적인 2개의 필터가 있는데 상보필터와 칼만필터 중 하나 정도는 공식을 알아야 합니다. 이처럼 수학적인 공식을 알아야 제대로 사용할 수 있습니다. 나중에 깊게 공부할 때는 모두 알아야 할 내용인데 입문자에게는 버거운 내용입니다. 하지만 이 모든것을 몰라도 사용할 수 있습니다. MPU-6050라이브러리르 사용하면 쉽게 회전각을 구할 수 있습니다. 바로 라이브러리를 사용하는 것보다 먼저 간단히 어떤 값들이 MPU-6050 모듈을 통해 측정되는지 살펴봐야 겠지요. 우선, 간단히 MPU-6050에 대해서 살펴보고 실험해보도록 하죠.


1. MPU-6050 가속도/자이로 센서


MPU-6050 모듈은 가속도/자이로를 측정할 수 있는 센서입니다. 가속도는 지구 중력을 기준으로 x, y, z 축의 가속도 크기를 구할 수 있으면 자이로(각속도)는 시간당 x, y, z 축의 회전속도 속도를 구할 수 있습니다.


MPU-6050 모듈에서 측정된 값은 바로 회전각으로 이해하기 힘듭니다. 그래서 계산이 필요하는 데 오일러의 각 공식을 알아야 합니다. 아래 링크된 위키백과사전에서 한번 읽어주시기 바랍니다.

3차원 회전 좌표계로 X축 회전을 롤, Y축 회전을 피치, Z축 회전을 요라고 합니다. 롤,피치,요에 대한 계산 공식이 따로 있습니다.


상보필터과 칼만필터는 회전각을 구할때 필요한 필터입니다.

아래는 상보필터의 공식입니다.

칼만필터은 아래 위키백과에 가셔서 공식을 살펴보시기 바랍니다.
https://ko.wikipedia.org/wiki/%EC%B9%BC%EB%A7%8C_%ED%95%84%ED%84%B0

대표적으로 이 두개의 필터로 회전각을 구하게 되는데 나중에 실험에서는 상보필터로 된 라이브러리를 이용할 예정입니다.

아래 MPU-6050 데이터 시트에 가셔서 MPU-6050에 대해서 알아두시기 바랍니다.
https://www.invensense.com/wp-content/uploads/2015/02/MPU-6000-Datasheet1.pdf

2. MPU-6050 가속도/자이로 센서 구조


MPU-6050 모듈은 가속도 (x,y,z)와 자이로(각속도) (x,y,z)의 값과 온도 값을 얻을 수 있습니다.


MPU-6050 모듈은 I2C 통신으로 14개의 레지지스터 값을 아두이노로 보내고 데이터는 16bit로 구성된 총 7개의 데이터를 얻게 됩니다.


MPU-6050 모듈의 핀에 대해서 위표를 잘 살펴보시고 인터럽트핀은 아두이노우노의 경우는 인터럽트 핀이 2번이기에 사용하실 경우 2번 핀에 연결하시면 되고 사용하는 아두이노보드에 따라 해당 I2C핀과 인터럽트 핀에 맞게 연결하시면 됩니다.

3. MPU-6050 가속도/자이로 회로도


  • 준비물 : MPU-6050 모듈, 아두이노우노
  • 내용 : I2C 통신을 위해 A4(SDA), A5(SCL)핀에 연결하시오

I2C핀만 주의해서 연결하시면 됩니다.


4. 코딩



우선 기본 라이브러리 없이 순수 MPU-6050 모듈에서 측정되는 값이 어떤 값들이 출력되는지 살펴보도록 하겟습니다.

위 링크된 MPU-6050소스에 대해 간단히 살펴보겠습니다.

[소스]
// By Arduino User JohnChi

#include<Wire.h>
const int MPU_addr=0x68;  // I2C address of the MPU-6050
int16_t AcX,AcY,AcZ,Tmp,GyX,GyY,GyZ;
void setup(){
  Wire.begin();
  Wire.beginTransmission(MPU_addr);
  Wire.write(0x6B);  // PWR_MGMT_1 register
  Wire.write(0);     // set to zero (wakes up the MPU-6050)
  Wire.endTransmission(true);
  Serial.begin(9600);
}
void loop(){
  Wire.beginTransmission(MPU_addr);
  Wire.write(0x3B);  // starting with register 0x3B (ACCEL_XOUT_H)
  Wire.endTransmission(false);
  Wire.requestFrom(MPU_addr,14,true);  // request a total of 14 registers
  AcX=Wire.read()<<8|Wire.read();  // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)    
  AcY=Wire.read()<<8|Wire.read();  // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
  AcZ=Wire.read()<<8|Wire.read();  // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L)
  Tmp=Wire.read()<<8|Wire.read();  // 0x41 (TEMP_OUT_H) & 0x42 (TEMP_OUT_L)
  GyX=Wire.read()<<8|Wire.read();  // 0x43 (GYRO_XOUT_H) & 0x44 (GYRO_XOUT_L)
  GyY=Wire.read()<<8|Wire.read();  // 0x45 (GYRO_YOUT_H) & 0x46 (GYRO_YOUT_L)
  GyZ=Wire.read()<<8|Wire.read();  // 0x47 (GYRO_ZOUT_H) & 0x48 (GYRO_ZOUT_L)
  Serial.print("AcX = "); Serial.print(AcX);
  Serial.print(" | AcY = "); Serial.print(AcY);
  Serial.print(" | AcZ = "); Serial.print(AcZ);
  Serial.print(" | Tmp = "); Serial.print(Tmp/340.00+36.53);  //equation for temperature in degrees C from datasheet
  Serial.print(" | GyX = "); Serial.print(GyX);
  Serial.print(" | GyY = "); Serial.print(GyY);
  Serial.print(" | GyZ = "); Serial.println(GyZ);
  delay(333);
}

MPU-6050의 I2C 주소는 '0x6B'입니다.

int16_t AcX,AcY,AcZ,Tmp,GyX,GyY,GyZ;

16bit int형 자료형으로 가속도 3개, 온도 1개, 자이로 3개의 변수를 선언합니다.

  Wire.beginTransmission(MPU_addr);
  Wire.write(0x6B);  // PWR_MGMT_1 register
  Wire.write(0);     // set to zero (wakes up the MPU-6050)
  Wire.endTransmission(true);

beginTransmission()함수로 '0x6B'주소로 I2C 슬레이브 디바이스로 전송을 시작합니다. 그리고 버스가 연결되면 '0x6B' 보내고 다시 '0'을 보내고 나서 endTransmission(true)함수로 버스를 해제하는 메세지를 보냅니다. 초기화 수행합니다.

Wire.beginTransmission(MPU_addr); //MPU-6050 '0x6B'주소 시작
Wire.write(0x3B); // 0x3B 읽을 rigister주소
Wire.endTransmission(false) //버스를 연결 활성화
Wire.requestFrom(MPU_addr,14,true) //데이터 요청하는데 14 레지스터 값을 요청

이렇게 해서, Wire.read()함수를 이용해서 실제로 MPU-6050에서 값을 읽게 됩니다. 0x3B~0x48의 레지스터값을 읽어와서 가속도, 온도, 자이로 값을 만들어 내게 됩니다.

Wire.read()<<8|Wire.read();

'0x3B' 레지스터 값을 왼쪽으로 8bit 이동시키고 '0x3C' 레지스터 값을 비트OR 연산을 수행합니다. 이 식을 통해서 2개의 레지스터 값을 합치게 됩니다.

예를 들어 임의의 값 A, B가 있을때 위 식으로 계산한다면


더 쉽게 살펴보면은, 비트OR 연산은 둘중 하나가 1이면 결과가 1이 나오는 연산입니다.


이렇게 14개의 레지스트값을 2개씩 이 수식을 통해서 가속도(x,y,z), 온도, 자이로(x,y,z)값을 순차적으로 Wire.read()함수를 14번 읽어서 연산을 수행하여 값을 얻게 됩니다. 그 값을 시리얼모니터로 출력하기 때문에 순수 MPU-6050 모듈에서 추출한 값입니다.
[결과]


5. 결과


실제 영상으로 살펴보면 MPU-6050 가속도/자이로센서를 움직일 때마다 가속도 AcX, AcY, AcZ 값과 자이로 GyX, GyY, GyZ 값이 나옵니다. 추가로 Tmp(온도)값은 온도를 계산식이 간단해서 값으로 나오고 가속도와 자이로는 우리가 각도로 느끼기에는 다소 어려운 수치로 출력이 이뤄집니다. 이 값으로 나중에 계산해서 회전각을 구하게 되는데 우선 어떤 값들이 찍히는지는 알아야 계산을 할 수 있겠죠. 나중에 위 링크된 곳에 가셔서 한번 공식에 대해서 자세히 배워보시기 바랍니다.


마무리


오늘 실험은 실제 회전각을 공식의 의해 구한 값이 아니라 MPU-6050의 값을 읽어온 값을 시리얼 모니터로 출력만 해서 뭔가 와닿지 않을 것 같네요. 다음 post는 MPU-6050 라이브러리를 이용해서 좀 더 쉽게 접근하고 실제 X, Y, Z 회전각을 구한 값을 통해서 간단히 실험해보면 자이로센서가 좀 더 친근하게 다가 올 듯 싶네요.

오늘은 좀 어렵더라도 대충 이런식으로 MPU-6050 모듈의 값을 읽는구나 정도로 이해하시고 넘어가시면 되겠습니다.


댓글()

[아두이노] 조도센서 + Bluetooth 통신 제어

IOT/아두이노|2019. 4. 23. 09:00

[아두이노] 조도센서 + Bluetooth 통신 제어



오늘은 지난시간에 배운 Bluetooth를 이용하여 Servo Motor를 제어 해볼려고 했지만 스마트폰의 데이터를 아두이노로 읽었으면 그 반대로 보내는 실험을 하는 것이 더 좋을 것 같아서 반대로 보내는 실험을 해볼까 합니다. 조도센서에서히 조도 값을 아두이노에서 측정하여 Bluetooth을 이용하여 스마트폰으로 보내는 실험을 하겠습니다.


1. 조도센서



링크 된 곳은 예전에 포스트 한 내용입니다. 회로도를 공개 모드로 변경해서 올려 놨습니다. 포스트에서 두가지 타입이 있는데 아래 타입으로 조도 센서를 배치 했습니다.


2. Bluetooth 연결


링크 : [아두이노] Bluetooth 통신 제어



  • Bluetooth Rx -> Arduino txPin - pin3
  • Bluetooth TX -> Arduino rxPin - pin2

지난 시간에 설명했기 때문에 설명은 생략하겠습니다. 혹시, 기억이 안나면 위에 걸어 놓은 링크 주소로 가셔서 다시 살펴보시기 바랍니다.

3. 회로도 구성


  • 준비물 : - Bluetoooth HC-06 1개, 조도센서 1개, 저항 10k옴, 아두이노우노
  • 내용 : A0핀은 조도센서 입력핀으로 사용하고 2,3번 Bluetooth 통신핀으로 사용한다.


여기서, 제어를 담당하는 세개의 핀이 있는데 조도센서 입력 받을 핀과 Bluetooth rx, tx 핀만 정확히 연결하시면 됩니다.

4. 코딩


  • 내용 : 조도센서를 아두이노에서 측정한 데이터를 Bluetooth로 스마트폰에 보내고 그 결과를 출력하도록 하자.

SoftwareSerial 통신

#include <SoftwareSerial.h>
  • SoftwareSerial mySerial (rx, tx) : 소프트시리얼 객체선언(rx(수신), tx(전송))
  • mySerial.begin(9600) : 시리얼 통신 시작(예로 9600 통식속도를 사용해 봤네요.)
  • mySerial.println(값) : 데이터 전송

따로 코딩을 새로 만들지 않았습니다. 기존에 조도센서를 시리얼모니터로 출력 했던 소스를 그대로 bluetooth통신에 적용하겠습니다.

[조도센서+시리얼출력] : 조도센서 제어(아두이노)

int greenpin = 13;
int cdspin = A0;

void setup()
{
  Serial.begin(9600);
  pinMode(greenpin, OUTPUT);
}

void loop()
{
  int m_cds = map(analogRead(cdspin),0,1023,0,255);
  
  Serial.print("CDS =  ");
  Serial.println(m_cds);
  
  if(m_cds<100) { 
     digitalWrite(greenpin, HIGH);  
  }
  else {
      digitalWrite(greenpin, LOW);  
  }
  
}

위 소스에서 필요한 부분만 뽑아내면은

int cdspin = A0;
void setup()
{
    Serial.begin(9600);
}

void loop()
{
    int m_cds = map(analogRead(cdspin),0,1023,0,255);
 
    Serial.print("CDS =  ");
    Serial.println(m_cds);
}

이렇게 이 소스만 필요합니다. 여기서 Serial 개체변수명만 여러분이 선언한 블루투스객체명으로만 바꾸면 끝납니다.

수정을 하면.

#include <SoftwareSerial.h>

const int rxPin = 2;
const int txPin = 3;

SoftwareSerial mySerial(rxPin, txPin);

int cdspin = A0;

void setup()
{
  mySerial.begin(9600); 
}

void loop()
{
  int m_cds = map(analogRead(cdspin),0,1023,0,255);
  
  mySerial.print("CDS =  ");
  mySerial.println(m_cds);  
  delay(1000);
}

이렇게 수정이 완료 되었네요. Serial 이름을 블루투스 통신에 사용할 객체명 mySerial로 "my"라는 단어가 앞에 추가되고 변경된 부분은 없네요.

간단하게 코딩을 완료 했습니다. 어렵게 생각하시지 마시고 평소 시리얼 모니터로 출력하는 식으로 코딩을 표현하시면 됩니다.

5. Bluetooth 통신을 하기 위한 세팅


지난 시간에는 "Controller mode"에서 실험 했다면 오늘은 "Terminal mode"에서 실험하시면 됩니다. 실행하시면 자동으로 아두이노에서 조도값을 측정하면 그 값을 바로 스마트폰으로 출력이 되어 나옵니다. "Terminal mode"에서는 스마트폰에서 데이터를 아두이노로 보낼 수 있고 아두이노에서 읽어온 데이터를 바로 출력해주는 모드입니다. 채팅모드라고 생각하시면 됩니다.

6. 결과


아래 동영상은 실행 과정을 PC에서 녹화한 장면입니다.


마무리


원래는 앱인벤터로 직접 만들어서 자신이 원하는 스타일로 앱을 만드는게 가장 좋습니다. 이미 만들어진 앱들이 구글스토어에 많지만 여러분들이 원하는 기능으로 딱 맞게 세팅 되어 있지 않습니다. 오늘 실험은 단순히 출력 데이터를 수치 상으로 보여 주기만 하면 되기 때문에 구글스토어에 등록된 앱을 사용했지만 그래프와 같은 시각적 표현을 하시고 싶다면 직접 블루투스 앱을 만드셔야 합니다.

혹시 스마트폰과 특정 센서를 연동한 뭔가를 제작 싶다면 앱인벤터를 구글에서 검색하시면 강좌가 많기 때문에 한번 제대로 공부해 보셨으면 해요.

블루투스는 사용범위가 넓기 때문에 상상를 어떻게 하냐에 따라 재밌는 작품들을 많이 만드실 수 있을거에요. 실제로 어떤 분은 자외선 센서를 이용했는데 소형으로 만들어서 스마트폰과 연동하여 실시간으로 자외선 관련 데이터 모니터링하면서 서버와 연동해서 자외선 정보를 알려주는 어플을 만드신 분도 있으시더군요. 이처럼 스마트폰과 연동하면 재밌는 것들을 만들 수 잇습니다.

여러분들도 상상력을 발휘해 보세요.


댓글()

[아두이노] 사운드센서로 그래픽 이퀄라이저 표현

IOT/아두이노|2019. 4. 7. 09:00

[아두이노] 사운드센서로 그래픽 이퀄라이저 표현



어제 마이크사운드 감지센서를 통해서 센서의 값의 초기값 조정하는 것과 읽는 법을 배웠습니다. 오늘은 마이크사운드 감시센서을 이용하여 소리를 NeoPixel로 출력하는 실험을 해보도록 하겠습니다. "아두이노 이퀄라이저" 키워드로 찾으시면 화려한 표현들을 많이 보실 수 있을거에요. 오늘 포스팅은 그런 화려한 표현이 아닌 간단히 어떻게 소리를 NeoPixel로 표현을 하는지에 대한 원리를 이해하는 시간으로 채우겠습니다. 화려한 그래픽 이퀄라이저를 상상했다면 아마 실망하실지 모르겠군요.

1. 마이크사운드 감지센서와 3색LED




3색 LED에 대한 설명은 아래 링크된 포스트로 가셔서 잠깐 살펴보세요.
[아두이노] 3색 LED 제어



마이크사운드 감지센서는 어제 포스트인 아래 링크로 다시 복습를 해주세요.
[아두이노] 마이크사운드 감지센서 제어(아두이노)

입력은 마이크르사운드 감지센서를 사용하고 출력은 NeoPixel이 없는 관계로 비슷한 3색 LED를 이용하여 실험이 이루어집니다.

2. 회로도 구성


  • 준비물 : 마이크사운드 감지센서 1개, 3색 LED 1개, 저항 220옴 3개, 아두이노우노
  • 내용 : 사운센서랑 3색 LED를 사용할 핀을 원하는 위치에 배치해 보자.

가상회로도 이미지에서 저항은 실수 1k옴으로 그려졌네요. 혼동하시면 안되고 정상적으로 220옴을 실제로 사용하셔야 합니다. 이미지의 저항색깔을 보시고 혼동하시면 안됩니다.


RGB 색상핀은 아날로그 출력을 해야하기 때문에 PWM핀들 중에 9,10,11번 핀을 선택했습니다. 그리고 마이크사운드 감지센서의 값을 읽는 아날로그 핀은 이전시간과 동일한 A0핀을 선택했습니다.

3. 코딩


  • 사용함수 : analogRead(), PinMode(), analogWrite(), map(), randomSeed(), random()
  • 내용 : 마이크사운드 감지센서의 소리의 값을 기준으로 RGB 색상을 만들어 그래픽 이퀄라이저 효과를 표현하자.

[ 기본 소스 ] : 마이크사운드 감지센서를 이용한 소리의 최소값과 최대값 출력

int m_min = 26;
int m_max = 0;

void setup()
{
  Serial.begin(9600);
}
void loop()
{
  int SoundSensor =analogRead(A0);
    if(SoundSensor<m_min) m_min=SoundSensor;
    if(SoundSensor>m_max) m_max=SoundSensor;
    
    Serial.print(m_min);    
    Serial.print("  :  ");
  Serial.println(m_max);    
  delay(50); 
}

이전 시간에 실험한 소스입니다. 이 소스를 기반으로 접근해 보겠습니다.

우선 RGB핀을 사용하기 때문에 RGB핀 변수를 만들어야 겠죠.

const int RedPin = 9;
const int GreenPin = 10;
const int BluePin = 11;

그 다음에는 RGB핀들은 출력모드입니다. 그러면 setup()함수에서 "출력모드로 사용할꺼야!" 하고 선언해야겠죠.

void setup()
{
  Serial.begin(9600);
 
  pinMode(RedPin, OUTPUT); 
  pinMode(GreenPin, OUTPUT); 
  pinMode(BluePin, OUTPUT); 
}

이제 소리를 어떤 기준으로 해서 색을 만들면 좋을지 생각해보세요. 마이크사운드 감지센서의 아날로그 신호 0~1023 값을 아두이노가 읽는다고 했죠. 그러면 0~1023값을 쪼개서 색으로 나타내면 되겠다고 처음에 다들 여기서 부터 출발합니다. 여기서, RGB핀 출력은 PWM핀으로 아날로그 0~255 출력을 낼 수 있습니다. 0(0V)이고 255(5V)입니다. 그러면 마이크사운드 감지센서에서 읽은 0~1023 값을 아날로그 출력 0~255값에 매핑 시켜서 출력시키면 되겠다고 생각 하실꺼에요.

 int SoundSenser =analogRead(A0);
 int SoundColor = map(SoundSenser,0,1023,0,255);

map()함수는 다시 복습을 하면

  • map(입력값,입력최소값,입력최대값,출력최소값,출력최대값) : 입력최소~최대범위에서 입력값이 출력 최소~최대범위에 어느정도 위치인지 그 출력위치값을 반환합니다.

예) map(입력값,0,100,0,10) 이면, 입력값이 10일때 출력값는 1이 됩니다. 입력값이 20일때 출력값은 2가 출력됩니다. 즉, 이말은 입력범위를 출력범위에 맞춰서 출력된다고 생각하시면 됩니다. 일정한 간격 비율에 맞춰서 출력값에 매칭 된다고 생각하면 될듯요. 0~10사이의 값은 1로 매칭되고 11~20사이의 값은 2로 매칭이 되고 이렇게 생각하시면 될 듯 하네요. 입력범위에 대한 입력값의 위치가 출력범위에서 어느정도의 위치가 출력위치인지 그 출력위치의 값을 반환해주는 함수라고 생각하시면 됩니다.

 Serial.println(SoundSenser);
 analogWrite(RedPin,random(SoundColor));
 analogWrite(GreenPin,random(SoundColor));
 analogWrite(BluePin,random(SoundColor));

우선 소리값을 그냥 color 값으로 출력한다면 소리값에 0~255사의 값이 RGB 색의 출력된 값이 되고 출력명령은 analogWrite()함수로 color를 출력하게 됩니다. 이렇게 하면 코딩이 끝났다고 생각하실꺼에요. 한번 돌려보세요 어떤 현상이 발생하는지요. 마이크사운드센서에 소리를 입력하기도 전에 3색 LED를 막 깜박일꺼에요.

그 이유가 뭘까요. 지난시간에 공부하셨으면 쉽게 찾을 수 있을꺼에요. 지난시간에 마이크사운드 감지센서는 초기값으로 일정한 전류가 발생한다고 했죠. 같은 종류라도 센서라도 초기의 센서에 흐르는 전류의 초기값은 제각각 입니다. 제가 사용하는 Sensor는 처음 46이 초기값이였고, 가변저항기를 돌려서 26정도로 맞췄습니다. 여기서 아무런 소리를 입력하기 전 초기 상태에서 이미 소리값이 일정 수치가 읽어지니깐 자연히 그 초기값의 색이 RGB LED에 출력이 되는 것이죠 제 경우 초기값이 26이라면 26에 대한 색이 출력되더군요.

처음 마이크 상태는 0에서 출발해야 하는데 그렇게 안되겠죠. 그럼 그렇게 0에서 출발하도록 만들어줘야 합니다. 바로 map()함수를 수정하시면 됩니다.

 int SoundColor = map(SoundSenser,26,1023,0,255);

입력 최소값을 26으로 잡아주면 간단히 해결됩니다. 그런데 두번째 문제가 발생합니다. 과연 뭘까요. 어제 포스트를 읽어봤다면 찾을 수 있을꺼에요. 소리가 입력되고 다시 입력이 안되면 전기신호를 감소합니다. 중요한 것은 소리가 입력될때는 전기신호가 커졌다가 입력이 안될때는 전기신호가 작아지는데 초기값보다 마이너스(-)로 빠져버리는 현상이 발생한다고 했죠. 그러면 어떻게 될까요. 그 이상한 값에 의해서 색이 출력되어버립니다. 최소값 문제가 발생합니다.

그걸 해결하기 위해서 어제 최소값 구하는 로직을 이용할꺼에요.

if(SoundSensor<26) SoundSensor=26;

만약에 26보다 작은 전기신호가 온다면 26에 수렴되게 만들면 됩니다. 그리고 또 어떤 문제가 발생할까요. 아두이노에서 사용되는 마이크사운드 감지센서를 아주 싸고 질이 떨어집니다. 결론은 소리감지에 좀 문제가 있습니다. 어느정도 큰소리라고 생각했지만 측정되는 수치는 그렇게 높지 않습니다. 거의 마이크를 두둘겨야 비로소 일정 수치까지 올라갑니다. 말하자면 대부분의 소리가 낮은 전기신호값을 가지고 있으면 일부 소리만 큰전기신호값으로 입력으로 들어옵니다. 결론은 거의 동작을 안하는 것처럼 보이고 색상도 아주 낮게 보입니다. 그래픽 이퀄라이저 효과를 뚜렷하게 표현하기 어렵다는 것이죠.

해결책으로는 낮은 값을 기준으로 색의 값을 재배열 하면 됩니다.

if(SoundSensor>300) SoundSensor=300;

이렇게 300이상의 소리는 그냥 300에 수렴하게 만들면 됩니다. 대충 아래 그래프처럼 입력값의 범위를 강제적으로 정하면 됩니다. 가로축는 마이크사운드 감지센서의 측정값이고 세로축은 소리입력허용 수치입니다. 26~300사이의 값으로 소리입력범위가 잡으시면 됩니다.


그러면, map()함수는 어떻게 될까요.

 int SoundColor = map(SoundSenser,26,300,0,255);

이렇게 해서 26~300사이의 값을 입력범위로 해서 0~255의 색상값으로 출력하겠다고 표현하시면 됩니다.
여기서, 어제 배웠던 최소값과 최대값 구한 로직을 기본소스로 우선 보여준 이유가 이 로직으로 이런 2가지의 문제를 해결하기 위해서 입니다.

이렇게하면 소리에 대한 밝기만 표현되기 때문에 그래픽 이퀄라이저라고 보기 어렵습니다. 그래서 밝기와 더불어 다양한 색을 만들어보는 시도를 해보겠습니다. 그 방법으로 random()함수를 사용하겠습니다.

void setup()
{
  randomSeed(analogRead(A1));
}
void loop(){
    random(SoundColor);
}

A0은 마이크사운드 감지센서로 사용되니깐 A1은 랜덤함수 초기값으로 세팅하겠습니다. 여기서 아날로그핀값을 읽어오기만 하는게 난수초기세팅인 이유는 뭘까요. 그 이유는 아두이노는 핀마다 미세한 전류가 자체적으로 흐르고 있습니다. 사용을 안하더라고 핀마다 작게 전류가 흘러서 그 전류는 미세하지만 랜덤한 전류의 양을 가지고 있어서 그 값을 기준으로 난수를 세팅하면 랜덤값을 추출할 수 있습니다.
그리고 random(255)함수면 255이 max값으로 임의의 난수를 만들어 냅니다. 이 값을 RGB 마다 random()함수로 색의 난수값을 만들어내면 소리에 대한 다양한 색을 만들 수 있겠죠.

종합해보면.

const int RedPin = 9;
const int GreenPin = 10;
const int BluePin = 11;

void setup()
{
  Serial.begin(9600);
  randomSeed(analogRead(A1));
  pinMode(RedPin, OUTPUT); 
  pinMode(GreenPin, OUTPUT); 
  pinMode(BluePin, OUTPUT); 
}

void loop()
{
  int SoundSenser=0; 
  int SoundColor=0; 
    
  SoundSenser = analogRead(A0);
  if(SoundSenser<26) SoundSenser=26;
  if(SoundSenser>300) SoundSenser=300;
  
  SoundColor= map(SoundSenser,26,300,0,255);
  
  Serial.println(SoundColor);
  analogWrite(RedPin,random(SoundColor));
  analogWrite(GreenPin,random(SoundColor));
  analogWrite(BluePin,random(SoundColor));
    
  delay(50); 
}

4. 결과



음악으로 실험해야하는데 음악은 저작권 때문에 단지 손으로 마이크사운드 감지센서에 자극을 줘서 출력실험을 했네요. 보면은 터치센서인줄 착각할 수 있는 장면이네요.

참고로 위 소스대로 하시면 그렇게까지 그래픽 이퀄라이저 효과를 얻을 수 없습니다. 결함이 있는 소스입니다. 그 이유가 뭘까요. 바로 random()함수를 단순히 측정한 소리값을 max값으로 넣었기 때문입니다. 이렇게 표현하면은 소리에 따라 다양한 색을 만들어 내기는 합니다. 하지만 300의 소리가 입력되었어도 100이 나올수 있고 150의 소리가 입력되어도 100이 나올 수 있다는 것이죠. 이말은 색상을 일정한 패턴 범위에 맞추지 못했다는 결함적 소스입니다.

그런데 왜 수정을 안하고 여기서 멈추었냐면 이제부터서 여러분들이 수정해 나갈 차례입니다. 기본 베이스는 다 주어졌습니다. 소리의 초기상태를 읽을 수 있고 최소와 최대값을 정할 수 있게 되었고 map()으로 색의 값의 범위를 정할 수 있게 되었습니다. random()함수로 색을 랜덤하게 만들어 낼 수 있게 되었습니다. 여러분들은 이제 소리를 좀 더 디테일적으로 나누는 작업과 그 나누었을 때 색을 랜덤함수로 범위를 정할지 아니면 고정된 색값으로 하고 밝기만 변화를 줄지의 선택은 여러분의 몫입니다.

참고로, 이렇게 코딩을 해도 실제 돌려보면 색이 화려하지 않고 낮은 밝기와 색값으로 출력됩니다. 그것은 바로 마이크사운드 감지센서의 문제입니다. 좀 큰소리를 말해도 실질적으로 측정되는 수치는 그렇게 높지 않습니다. 그렇기 때문에 미세한 1의 변화에도 색의 밝기값을 좀 더 큰값 범위를 기준으로 해서 정해보시는 것을 추천드려요.

5. 가상시뮬레이터에서 느낌만 실험


그냥 실제로만 하면 뭔가 아쉬울 것 같아서 느낌만 살펴서 가상시뮬레이터로 실험해 해보세요. 위에 공개회로도에 링크 걸렸으니깐 돌려만 보세요.

1) 가변저항기와 3색 LED


const int RedPin = 9;
const int GreenPin = 10;
const int BluePin = 11;

void setup()
{
  Serial.begin(9600);
  randomSeed(analogRead(A1));
  pinMode(RedPin, OUTPUT); 
  pinMode(GreenPin, OUTPUT); 
  pinMode(BluePin, OUTPUT); 
}

void loop()
{
  int SoundSenser=0; 
  int SoundColor=0; 
    
  SoundSenser = analogRead(A0);
  if(SoundSenser<26) SoundSenser=26;
  if(SoundSenser>300) SoundSenser=300;
  
  SoundColor= map(SoundSenser,26,300,0,255);
  
  Serial.println(SoundColor);
  analogWrite(RedPin,random(SoundColor));
  analogWrite(GreenPin,random(SoundColor));
  analogWrite(BluePin,random(SoundColor));
    
  delay(50); 
}

2) 가변저항기와 NeoPixel


#include <Adafruit_NeoPixel.h>

const byte neopixelPin = 3; 
Adafruit_NeoPixel neopixel = Adafruit_NeoPixel(1, neopixelPin, NEO_GRB + NEO_KHZ800);
 
void setup()
{
  neopixel.begin();   
  randomSeed(analogRead(A1));
}

void loop()
{
  int SoundSenser=0; 
  int SoundColor=0; 
    
  SoundSenser = analogRead(A0);
  if(SoundSenser<26) SoundSenser=26;
  if(SoundSenser>300) SoundSenser=300;
  
  SoundColor= map(SoundSenser,26,300,0,255);
  neopixel.setPixelColor(0, random(SoundColor), random(SoundColor), random(SoundColor));    
  neopixel.show();
  delay(50);    
  neopixel.clear(); 
}

3) 두가지 실험 결과

위 3색 LED와 NeoPixel을 가변저항기가 마이크사운드 감지센서라고 상상하고 실험한 영상입니다.


마무리


하다보니깐 길어졌네요. 원래는 추가적으로 NeoPixl 바 모양 향태로 음악을 들을때 사운드 그래픽 바 모양을 아실꺼에요. 그것도 추가 설명할려고 했는데 그건 못하게 되었네요. 내일 또 이여서 하면 너무 길게 연장 될 것 같아서 여러분들의 상상력으로 표현을 해보세요.

혹시, 사운드 그래픽 바를로 출력하고자 하고자 한다면 참고해주세요. NeoPixel을 예전 포스팅에 어떻게 표현을 했었죠. 배열 행태로 표현했었습니다. 그 배열을 사운드 그래픽 바의 위치라고 상상하고 소리의 크기에 따라서 배열로 몇번째 배열까지 불이 들어오게 할건지 그 간격만 정하시면 됩니다. 그리고 해당 NeoPixel의 색 값은 random()함수로 표현하시거나 고정값을 하셔도 됩니다. 쉽게 말해서, 소리가 1~10까지 있으면 3이라는 신호값이 발생하면은 NeoPixel를 3번째까지 불이 들어오게 하면 됩니다. 이렇게 소리의 크기 값에 neoPixel의 위치를 정하면 됩니다. 한번 연구를 해보세요.

오늘도 어떻게 표현하면 더 자연스럽고 화려할지를 상상의 나래를 펼쳐 보세요.


댓글()

[아두이노] 마이크사운드 감지센서 제어

IOT/아두이노|2019. 4. 6. 09:00

[아두이노] 마이크사운드 감지센서 제어



오늘은 어제 설명한 Sensor 읽기에서 가상시뮬에이터에서 제공되지 않는 실제 Sensor를 다뤄보도록 하겠습니다. 실험에 사용되는 마이크사운드 감지센서는 D0, A0로 디지털신호와 아날로그신호를 출력하기 때문에 해당 핀에 맞는 함수를 사용하여 읽으시면 됩니다. 실험에서는 아날로그신호를 읽는 방식으로 진행되니깐 잘 참고를 해주세요.

1. 마이크사운드 감지센서



사진이 깔끔하게 나오지 않고 약간 좀 떨려서 찍혔네요. 좀 흐려도 자세히 보시면 대충 구분이 되실거라 믿고 진행하겠습니다. 보시면 파란색 나사이 있을꺼에요. 가변저항기로 전류의 출력을 조절하는 장치입니다. 어느쪽 방향인지 햇갈리는데 여기서 오른쪽으로 올리면 마이크센서의 초기 값이 작아지던가 아무튼 아두이노에 연결해서 Serial 통신으로 그 값을 찍어보시면 돌리는 방향에 따라 값이 커졌다 작아졌다 하니깐 초기값을 정하시면 됩니다.

그리고 오른쪽 핀 연결부분을 보시면 A0, G, +, D0으로 표시되어 있는데 A0은 아날로그 신호로 출력하는 거고, D0은 디지털 신호로 출력하는 핀입니다. +, Gnd은 전원부분으로 아두이노우노에 Vcc, Gnd로 연결하시면 되겠죠. 제가 사용하는 모델은 5V에 연결했네요 부품마도 허용치 Vcc가 다르니깐 구매 부품의 사용설명서를 참조하세요.

2. 마이크사운드 감지센서값 읽기


void setup()
{
  Serial.begin(9600);
}

void loop()
{
  int SoundSensor =analogRead(A0);
  Serial.println(SoundSensor);    
  delay(50); 
}

대충 이 코딩으로 현재 사용하시는 마이크사운드 감지센서의 출력값을 아두이노 IDE 시리얼모니터에 출력시켜 초기값을 확인하시면 됩니다.
제 마이크사운드 감지센서의 값은 23~26사이의 아날로그 출력값을 초기값으로 출력하는데 평균 26을 주기적으로 출력하더군요.
아무것도 소리를 입력하지 않는 현재 공간의 소음상태의 신호값입니다. 위에서 설명한 가변저항기의 일자나사모양을 돌리시면 돌리는 방향에 따라서 이 수치가 커지거나 작어지거나 할꺼에요. 자신이 정한 기준에 맞춰서 세팅하시면 됩니다.

3. 회로도 구성


  • 준비물 : 마이크사운드 감지센서 1개, 아두이노우노
  • 내용 : 마이크사운드 감지센서를 아두이노에 연결하자.


마이크사운드 감지센서는 어떤 종류라도 상관 없습니다. 사용되는 핀은 아날로그 신호를 입력받기 때문에 A0핀과 전원부분인 +(Vcc), Gnd 핀을 사용하기 때문에 아날로그 출력핀은 아두이노의 원하는 아날로그핀(A0~A5)을 선택하셔서 연결하시면 되고 전원부분은 맞춰서 연결하시면 됩니다. 마이크사운드 감지센서는 종류에 따라 전원공급 허용치에 맞게 연결하시면 되니깐 그건 부품의 사용설명서를 참고해주세요.

4. 코딩


  • 사용함수 : analogRead()
  • 내용 : 간단히 마이크사운드 감지센서를 통해 소리의 최소와 최대값을 출력해 보자.

마이크사운드 감지센서값 읽기에서 최소값, 최대값 변수를 만들어서 if문 조건문을 통해서 최소값과 최대값을 저장하여 어느정도의 범위의 소리 신호를 센서가 측정할 수 있는지 살펴보도록 하겠습니다.

int m_min = 26;
int m_max = 0;
int SoundSensor =analogRead(A0);
if(SoundSensor<m_min) m_min=SoundSensor;
if(SoundSensor>m_max) m_max=SoundSensor;

첫번째 if문은 SoundSensor값이 초기 26보다 작다면 그 값을 최소값으로 한다.
두번째 if문은 SoundSensor값이 초기 0보다 크다면 그 값을 최대값으로 한다.

간단하게 최소값하고 최대값을 구하는 로직입니다. 왜 초기 최소값을 26으로 했을까요. 그냥 마이크사운드 감지센서로 읽은 값을 Serial 모니터로 출력하면 초기평균값이 나오잖아요 .그 값을 최소로 잡은 것이죠. 그냥 26을 최소값으로 하고 구지 if문으로 최소값을 찾을 이유가 없지 않느냐라고 반문할 수 있지만 실제로 아래 돌려보시면 생각했던 값이 나오지 않고 더 작은 값이 최소값으로 찍히게 됩니다. 그 이유는 결과부분에서 자세히 설명드리겠습니다.

우선은 이런식으로 최소값과 최대값을 구하는 로직을 짰는 걸로 정리해주시기 바랍니다. 그럼 완성된 코딩은 다음과 같겠죠.

int m_min = 26;
int m_max = 0;

void setup()
{
  Serial.begin(9600);
}
void loop()
{
  int SoundSensor =analogRead(A0);
    if(SoundSensor<m_min) m_min=SoundSensor;
    if(SoundSensor>m_max) m_max=SoundSensor;
    
    Serial.print(m_min);    
    Serial.print("  :  ");
  Serial.println(m_max);    
  delay(50); 
}

5. 결과


[ 초기상태 ]




[ 소리입력 후 결과 ]



두장의 사진을 보면 어떤 차이가 있나요. 첫번째 사진은 최소값이 23이고 최대값은 27로 나옵니다. 이건 처음에 마이크사운드 감지센서에 전원이 공급될때 발생하는 전류의 순간 초기 변화의 값이고 평균적으로 26의 값을 초기값으로 찍어 냅니다.

하지만 두번째 사진을 보시면 최소값이 17이고 최대값은 1005로 나옵니다. 마이크사운드 감지센서의 소리가 1005까지 증가한 신호가 측정되었네요. 하지만 이상한점은 최소값이 17로 감소한 지점이 마이크사운드 감지센서에서 발생했습니다.

그 이유가 뭘까요. 바로 소리를 측정하고 전기 신호를 발생하는 순간 측정된 만큼의 전류가 늘어나겠죠. 그리고 소리가 끊어지면 전류의 소비된 만큼이 순간 떨어지게 되고 초기 전류상태에서 아래로 전류값이 떨어지게 되는 것 같습니다. 소리의 측정된 전류가 늘었다 줄어드는 순간 초기 전류상태에서 그 아래로 떨어졌다가 다시 초기상태로 되돌아간다고 생각하면 될 듯 싶네요.

소리가 발생할때마다 전류의 신호의 파형이 증가했다가 떨어질때는 초기값 아래로 일시적으로 전류가 떨어졌다가 다시 올라고 반복된 현상을 보입니다.

이 현상을 잘 기억하셔서 다른 부품을 제어할 때 주의해서 코딩해야 합니다. 초기값이 26이니깐 그 값을 기준으로 해야지 했는데 기준값 아래로 떨어질때는 원하지 않는 값으로 다른 부품을 제어하게 됩니다. 위에서 최소값을 26으로 잡고 다시 if문을 써서 최소값을 구하는 로직을 표현했는지 그 이유를 아시겠지요. 정확히 26이란 값이 최소값이 되지 않기 때문에 그 아래 신호값이 발생할 수 있기 때문에 자신이 사용하는 마이크사운드 감지센서에 대한 정확한 측정 수치를 테스트를 해보셔야 합니다.

마무리


원래는 이 포스팅이 아니였는데 쓰다보니깐 길어져 버렸네요. 이 부품을 가지고 다른 원리를 설명하고자 했는데 의도치 않게 마이크사운드 감지센서 포스트가 되고 말았네요.
그리고 디지털 읽기로 한번 해보세요. 어떤 값이 찍히는지요. 그 값을 Serial 모니터로 출력해보세요. 그리고 이런류의 부품을 사용할때는 보정작업이 필요합니다. 초기값을 어느값으로 할지와 그 값의 범위를 정해서 그 밤위의 값으로 나누는 방법도 생각하시면 다양한 표현을 할 수 있습니다.
초기값을 정하면 그 값을 기준으로 다른 부품의 동작을 제어할 수 있습니다. 그리고 범위값을 나누면 소리의 범위별로 서로 다른 동작을 시킬 수 있습니다.

참고로 가상시뮬에이터에서는 실험 할 수 없습니다. 마이크사운드 감지센서가 존재하지 않으니깐요. 그 대안으로 가변저항기를 이용해주세요. 가변저항기를 돌리면 0~1023의 값을 입력받을 수 있습니다.

[아두이노] 가변저항을 이용하여 3색 LED 제어

이 포스트가 예제로 적당할 듯 하네요. 다음 포스팅 내용도 이 예제를 기반으로 설명을 할 예정입니다. 한번 가변저항기로 마이크사운드 감지센서가 이 값은 음역대 신호를 발생한다고 가정하고 읽는 것을 연습해보세요. 그리고 이 아날로그 신호값이 발생하면 3색 LED 제어한 것처럼 난 무엇을 해볼까 상상력을 발휘를 해보세요.

마지막으로 마이크사운드 감지센서를 가지고 여러분들은 뭘 만들고 싶을지 상상의 나래를 펼쳐보세요.

댓글()

[아두이노] Sensor 읽는 법

IOT/아두이노|2019. 4. 5. 09:00

[아두이노] Sensor 읽는 법



오늘은 지금까지 다뤘던 Sensor 들에 대해서 정리하는 시간을 갖도록 하겠습니다. 가상시뮬레이터에서 제공되는 Sensor의 종류들은 극히 일부에 지나지 않습니다. 실제는 더 많은 수십 수백가지이상의 센서들이 존재합니다. 여기서 중요한 것은 종류가 많다고 해서 개별적으로 다르게 접근하는게 아니라 이제 설명하는 기본 세가지 방법으로 Sensor의 값을 읽어 올 수 있습니다. 이 세가지 방법만 알고 있으면 센서 종류만 많지 다루는데 그렇게까지 어려움이 없을 거라 생각합니다.

1. Sensor의 종류



가상시뮬레이터에서는 값을 읽는데 사용하는 부품들입니다. 몇 종류가 되지 않지만 그래도 세가지 방법을 다 실험할 수 있는 부품들로 구성되어 있습니다. 실제로 아두이노키트를 구매하시면 몇십가지의 부품들로 구성되어 판매하지만 사실 종류별로 구분하여 분류하면 종류는 그렇게 많지 않습니다. 개수만 많을뿐이죠. 실제 부품들 사진을 올려야 하는데 제 키트 사진을 찍어서 올리기 힘들고 인터넷 키트 사진을 올리는 것도 문제가 될 것 같아서 그부분은 생략하도록 하겠습니다. 관심있으신 분들은 구글 검색에서 "아두이노센서 종류"라는 키워드로 검색하시면 목록 사진들이 나열된 게시물들을 쉽게 찾을 수 있으니깐 어떤 종류의 센서들이 있는지 개별적으로 살펴보시면 되겠습니다.

2. Sensor의 읽기 구분



Sensor를 읽이 위해서는 크게 세가지 방법으로 구분한다고 했죠. 바로 디지털 읽기, 아날로그 읽기, 시간값 읽기로 나누어 살펴볼 수 있습니다.

1) 디지털 읽기 :

디지털핀에서 센서의 값을 읽는 방법입니다. 기본적으로 디지털핀은 HIGH or LOW의 상태값을 읽을 수 있고 쓸 수 있습니다. 쉽게 말해서, 디지털핀으로 읽을때 HIGH or LOW의 상태값을 읽게 됩니다. Sensor에서 디지털 상태값을 출력하는 종류가 뭐가 있을까요. PIR Sensor(인체감지센서), Tilt Sensor(기울기센서), 스위치버턴 등이 대표적으로 들 수 있겠죠. 이런 종류의 Sensor는 두가지 HIGH or LOW 상태값만 갖게 됩니다.

int val = digitalRead(디지털핀);

val이란 변수에 디지털핀에 연결된 센서의 값을 저장하게 됩니다. 우리는 두가지 상태값 HIGH or LOW의 값을 통해서 아두이노에서 특정 동작을 제어하게 됩니다.

2) 아날로그 읽기 :

아날로그핀에서 센서의 값을 읽는 방법입니다. 기본적으로 아날로그핀은 0~1023사이의 값을 읽게 됩니다. 아날로그 값을 출력하는 종류가 뭐가 있을까요. 위 사진을 보면 대충, Gas Sensor(가스센서), Photoresistor(LDR)(조도센서) 등이 있네요. 이런 종류의 센서는 전류의 값을 출력되는데 아두이노는 0~1023사이의 전류의 세기 값을 읽게 됩니다.

int val = analogRead(아날로그핀) ;

val이란 변수에 아날로그핀에 연결된 센서의 값을 저장하게 됩니다. 우리는 전류의 세기 0~1023의 값을 통해서 특정 동작을 제어하게 됩니다. 참고로 부품에 따라 아날로그 신호와 디지털 신호를 두개 다 내보내는 부품이 있는데 사용목적에 따라서 디지털 신호는 디지털 읽기로 아날로그 신호는 아날로그 읽기 함수를 사용해서 읽으시면 됩니다.

3) 시간값 읽기 :

디지털핀에서 센서의 값을 읽는데 어떤 특정한 상태를 기준으로 해서 그 상태가 유지하는 시간을 추출할 수 있습니다. 대표적으로 초음파 센서를 들 수 있습니다.

int val =pulseIn(디지털핀, HIGH/LOW);

val이란 변수에 디지털 핀에 연결된 센서의 값을 읽어오는데 상태의 값이 HIGH or LOW 중 어떤 상태값이 얼마정도 유지 되었는지 그 시간을 측정하는데 사용됩니다. 가령 pulseIn(디지털핀, HIGH) 라고 하면 디지털핀에 들어오는 HIGH값이 LOW가 될때까지의 시간값을 반환하게 됩니다. 초음파센서에서 초음파를 쏘고 되돌아올때 이때 초음파센서를 통해 HIGH에서 LOW가 될때까지의 시간값을 가지고 거리를 계산 했었죠.

원래는 디지털 읽기와 아날로그 읽기로 두가지로 구분하시면 됩니다. 시간값 읽기는 특수한 경우이고요. 다양한 부품들이 있는데 대부분 디지털 값을 출력하거나 아날로그 값을 출력하는 부품들이고 우리는 digitalRead()함수와 analogRead()함수로 센서의 값을 읽게 됩니다.

추가적으로 통신에서 '1'과 '0'의 신호값으로 읽기도 합니다. 가령 1byte의 값을 전송한다면 '00000000'의 범위의 전기신호를 보내는데 '1'이라는 값을 보낸다고 치면 '00000001'이렇게 2진수로 보낸다면 전기 신호가 'LOW' 7번, 'HIGH' 1번으로 짧게 끈어서 8bit의 신호를 보내겠죠. 통신에서는 읽을때 이 전기신호를 읽어서 다시 복호화하여 1이란 숫자로 저장하게 됩니다. 통신 부분이기 때문에 이 부분은 간단히 전기신호로 읽는 방법정도만 이해하시면 되겠습니다.

SoftwareSerial mySerial(RX,TX);
byte val = mySerial.read();

Serial 통신핀을 통해서 1byte의 신호값을 val에 저장합니다. 센서값을 읽은 기기에서 통신을 통해서 그 값을 아두이노우노에서 읽을때 사용 됩니다. 이부분은 통신 부분이니깐 별도로 알아두시기 바랍니다.

마무리


오늘은 간단히 센서의 값을 읽는법을 정리하는 시간으로 채웠습니다. 어떤 Sensor를 사용하시더라고 기본 골격은 디지털 읽기와 아날로그 읽기 뿐이 없습니다. 별도로 라이브러리리 제공 될 경우 해당 라이브러리 객체에 사용핀을 던져주기만 하면 별도의 코딩은 필요 없겠지만 실제 여러분들이 코딩을 하실 경우는 대표적으로 이 두가지 방법으로 접근이 이루어지기 때문에 두 함수만 이해하시면 사용해보지 않는 센서들일지라도 대부분 사용하실 수 있을 꺼에요.

오늘은 정리하는 시간으로 간단히 포스팅을 했네요.

댓글()