이번에는 디지털 출력을 이용해 LED를 켰다 껐다만 하는 것이 아니라, 밝기까지 같이 조절하는 제어방법에 대해 봅시다!


#1 디지털 신호와 전압

흔히 디지털 신호는 0과 1의 값을 갖는다고 알려져있죠.

그럼 대체 그 0과 1의 신호는 어떻게 생긴 신호일까요?


앞선 포스팅에서 아날로그와 디지털을 간단히 설명했지만, 조금 더 명확하게 설명하자면

디지털 신호라 함은 전압 신호를 두가지 상태로만 나눠서 사용하는 경우를 말합니다.


특정 전압 이상이 되면 1, True, HIGH 상태가 되고, 특정 전압 이하면 0. False, LOW 상태가 됩니다.


이런 디지털 신호는 어느정도의 노이즈가 있어도 안정적으로 신호를 보낼 수 있기때문에 통신에 주로 사용이 됩니다.



#2 LED 밝기 제어

그럼 순수한 디지털 신호만 이용한다면 LED의 두가지 상태, 즉 On/ Off 를 제어할 수 있겠죠

 

그럼 LED의 밝기는 어떻게 조절 할 수 있을까요?


보통 밝기는 LED에 가해지는 전압에 따라 정해지게 되어있습니다.


위 회로처럼 꾸미고 나면 베터리에서 나온 전류는 스위치를 거치고 LED와 저항을 거쳐 흐르겠죠.


그렇다면 저항이 높아지면 어떻게 될까요?


상대적으로 LED에 걸리는 전압보다 저항에 걸리는 전압이 커지겠죠? 


위 회로는 저항과 LED가 직렬로 연결되어있으므로, 


i 는 LED와 저항에 동일하게 흐르지만, V=i*R 이므로, 저항(R) 이 커지면 저항에 걸리는 전압(V)도 커진답니다.


저항을 낮추면 어떨까요? 당연히 저항에 걸리는 전압은 낮아지겠죠


결국,


저항 -> 저항에 걸리는 전압 -> LED에 걸리는 전압 -> LED 밝기 감소

저항 -> 저항에 걸리는 전압 -> LED에 걸리는 전압 -> LED 밝기 증가


의 관계가 형성이 되죠!


때문에 밝기 제어를 위해서는 저항값을 바꿀 수 있는 가변저항(Potentiometer)가 필요합니다.


하지만 저항을 사용하게되면 LED를 켜는데 전력이 소모될 뿐만 아니라 가변저항에서도 에너지가 소모되겠죠?


그럼 어떻게 하면 쓸데없는 에너지 손실을 없애고 밝기를 제어할 수 있을까요...?



#3 아두이노를 이용한 디지털 LED 밝기 제어

자 우리가 가변저항을 없애고 제어를 한다면 불빛이 '켜진 경우' 와 '꺼진 경우' 두가지 상황밖에 만들수가 없습니다.


그럼 우리가 할 수 있는일은 껐다 켰다를 반복해보는거죠.


껐다 켜기를 1초에 2번, 4번, 10번, 100번, ... 빠른 속도로 껐다켰다를 하면 눈에는 어떻게 보일까요?


꺼진것 처럼 보일까요? 켜진것 처럼 보일까요? 깜빡인다는걸 인식할 수 있을까요?


사람의 눈은 흔히 초당 60번 (60Hz) 이상의 변화를 감지하기 힘들다고 하죠. 


물론 정량적으로 초당 몇번을 볼 수 있는지 아직까지는 말이 많습니다만, 


대락 수백 Hz 이상이면 사람의 눈으로는 깜빡임을 감지할 수 없습니다.


그리고 눈으로 확인했을때, 꺼진것도, 켜진것도 아닌 중간의 밝기로 보이게 됩니다.


즉, 껐다 켰다를 빠르게 반복하면 밝기가 조절된것 같은 효과를 낼 수 있다는 것 이지요.


실제로 LED 형광등으로 나오는 제품들이 이와 같은 원리를 사용한답니다.


그럼 어떤 요소가 밝기를 변화시키는 주된 요소일까요?


초당 LED를 깜빡이는 속도일까요?


아래 그림을 보시면 알 수 있습니다.



두 신호는 동일한 속도로 깜빡이는 신호입니다. 


즉 한 번 껐다 켜지는데 걸리는 시간이 동일한 신호죠.


다만 다른점은 한 주기 안에서 켜져 있는 상태의 비율(점유율)이 다릅니다.


켜져있는 상태의 폭이 다른 것이죠.


이런 형태의 신호로 하드웨어를 제어하는 방식을 


펄스 폭 변조, Pulse Width Modulation (PWM) 


라고 부른답니다.


그렇다면 어느 신호가 LED를 밝게 만들까요?


신호 1번이 LED를 더 밝게 만들것 같죠?


왜 그런지 조금만 더 구체적으로 살펴봅시다.



전압이라는 성분은 우리가 빠른속도로 껐다켜게 되면


정말 순간적으로 0V에서 5V로 변하는 것이 아니라 전압 강하와 상승에 약간의 시간이 소요됩니다.


그 한계의 순간에서 전압을 빠르게 껐다켰다하게되면


전압은 그 주기 안에서의 평균적인 값으로 나타나는것 처럼 보이게 됩니다.


때문에 주기를 변화시키게되면 전압이 변하게 되고, 이를 통해 밝기가 바뀌게 되는겁니다.


그렇다면 아두이노에서 한 번 구현을 해볼까요?



위와 같은 회로를 구성한 다음, 아두이노 IDE에서


파일 -> 예제 -> Basic -> Fade 를 여시면 됩니다.


초기 핀 세팅이 9번으로 되어있는데, 이를 13번으로 바꿔주시면 됩니다.


위 코드에서 앞서 말씀드린 PWM 신호를 생성해 주는 함수는


analogWrite( 핀번호, 주기 점유율(0 ~ 255) )


입니다. 


int led = 13;           // the PWM pin the LED is attached to
int brightness = 0;    // how bright the LED is
int fadeAmount = 5;    // how many points to fade the LED by

// the setup routine runs once when you press reset:
void setup() {
  // declare pin 9 to be an output:
  pinMode(led, OUTPUT);
}

// the loop routine runs over and over again forever:
void loop() {
  // set the brightness of pin 13:
  analogWrite(led, brightness);

  // change the brightness for next time through the loop:
  brightness = brightness + fadeAmount;

  // reverse the direction of the fading at the ends of the fade:
  if (brightness <= 0 || brightness >= 255) {
    fadeAmount = -fadeAmount;
  }
  // wait for 30 milliseconds to see the dimming effect
  delay(30);
}


코드를 업로드 하시면, 30ms 간격으로 


주기 점유율이 0, 5, 10, 15, ... , 255 까지 올라가면서 밝아지고,


끝까지 밝아진 다음에는 255, 250, 245, ... 0 까지 내려가면서 어두워 진답니다.




PWM은 이렇듯 추가적인 전자부품없이, 디지털의 방법으로 전압을 가변하는것과 같은 효과를 내줄 수 있어


다양한 방면의 신호제어에 사용이 된답니다.



이상입니다!


안녕하세요~


이번시간에는 아두이노를 통한 기초적인 제어를 배워보도록 하겠습니다.



#1 아날로그와 디지털 신호


아두이노는 크게 디지털 신호의 입력과 출력, 아날로그 신호의 입력이 가능하게 고안이 되었습니다.


디지털 신호는 흔이 0 아니면 1의 값 (0=False, 1=True)을 갖는 이진 신호 또는 논리 신호라고 합니다.

굳이 전압으로 따지자면, 0 일때는 0V의 전압이, 1일때는 5V의 전압이 출력된다고 볼 수 있죠!


그럼 아날로그 신호는 무슨 신호일까요?


디지털 신호가 0V와 5V 밖에 없다고 한다면, 아날로그 신호는 0V와 5V 사이의 그 어떤 값도 가능한 신호를 아날로그 신호라 할 수 있습니다.

즉, 0V와 5V 사이의 어떤 연속적인 값이라 보시면 됩니다.



주로 디지털 신호는 통신이나 On/Off 제어를 위해 많이 쓰이는 신호이고, 아날로그 신호는 센서의 값을 받아들일 때 많이 쓰이는 신호입니다.


그럼 디지털 신호를 어떻게 쓰는지 살펴봅시다.



#2 발광다이오드 (LED)


디지털 신호를 통해 제어하는 하드웨어중 대표적인것이 바로 발광다이오드(Light Emitting Diode, LED) 입니다. 




즉, '다이오드' 중에서 빛을 발하는 다이오드를 말합니다. 다이오드는 일정한 방향으로만 전류가 흘러가게 해주는 전자부품으로, 만약 LED의 +,-가 반대로 연결된다면 끊어진 회로와 같게 되는겁니다. 


LED의 극성을 찾을때 흔히들 다리 길이를 보고 찾으시는데, 긴쪽이 +, 짧은쪽이 -입니다. 하지만 다리를 자르고나면 극성은 어떻게 구분할까요~?


LED안을 자세히 보면 위의 그림처럼 내부 쇠막대기가 긴쪽이 있고, 짧은쪽이 있답니다. 내부 쇠막대기가 짧은쪽이 +, 긴쪽이 - 라고 생각하시면, 다리를 잘라도 극성을 구분할 수 있겠죠?



#3 LED 회로 구성


그렇다면 LED를 이용해서 회로를 구성해봅시다.


기본적인 부품으로는 전원이 될 건전지와 LED, On/Off를 가능하게 할 스위치, 그리고 LED에 걸리는 전압을 낮춰주는 저항 이 필요합니다.

저항은 330ohm 내지 1Kohm 정도 사용하면 됩니다.



위 사진처럼 회로를 꾸미고 나면 스위치를 켜고 끄는거에 따라 LED의 불빛이 켜졌다 꺼졌다 할겁니다~



#4 아두이노를 이용한 LED 제어


이번에는 아두이노를 이용해서 LED를 On/Off 해봅시다.

회로에서 전원부분과 스위치를 디지털 아웃풋으로 대체하면 회로는 완성됩니다.




자 그럼 단순하게 1초 간격으로 깜빡이게 만들어볼까요?

불을 켜려면 digital pin에 On 이라는 신호를 넣어주면 됩니다.

아두이노에서 디지털 아웃풋을 주려면 

digitalWrite(핀 넘버, 값(HIGH or LOW)) 을 넣어주면 되죠.


그럼 켤때는 

digitalWrite(13,HIGH);


끌때는

digitalWrite(13,LOW);


를 입력해주면 됩니다. 


그리고 1초라는 시간을 줘야하기때문에,


중간에 delay(1000) 를 넣어 1000ms = 1s 동안 아웃풋이 유지되게 만드는겁니다.



알고리즘을 도식화하면 위와 같이 나타내지고, 코드는 


Arduino IDE -> 파일 -> 예제 -> Basics -> Blink 에 있습니다.



// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);                       // wait for a second
}


위 코드를 입력하면 LED가 1초 간격으로 깜빡이는걸 확인 할 수 있죠!


이렇게 간단하게 디지털 신호의 출력만으로 특정 하드웨어의 전원을 껐다 켰다 할 수 있답니다.

다음시간에는 이 LED의 밝기를 어떻게 조절하는지에 대해 살펴볼게요~


안녕하세요~


이번시간에는 SD카드를 이용한 데이터 로깅 방법에 대해 알아보겠습니다.


지난 시간에서 프로세싱을 통해 아두이노의 데이터를 가시화 한것 처럼,

아두이노는 '데이터의 수집' 기능을 주로 수행한답니다.


하지만 이러한 경우에는 PC가 필수적으로 함께 있어야만

데이터를 저장 및 확인할수가 있죠.


만약에 PC가 없다면 어떻게 데이터를 저장할 수 있을까요?


바로 SD카드를 이용해서 아두이노가 수집한 데이터를

SD카드에 저장해두는 것입니다.


그럼 나중에 장치를 구동한 후, 데이터가 저장된 SD카드만 있으면

아두이노에서 측정된 데이터들을 PC에서 신호처리를 할 수 있겠죠ㅋㅋ


그럼 한번 살펴봅시다.





#1 준비물




SD 카드, SD 카드 소켓

(소켓은 리더기가 아니라 우측에 보이는 모듈입니다)



아두이노

(여기서는 Bluno Beetle을 사용했습니다)





#2 SD카드 소켓 연결



자 이제 물품을 준비했다면 아두이노와 SD카드를 연결시켜 보겠습니다~


먼저 SD카드에 데이터를 저장하기위해서는

'SPI통신'을 통해 데이터를 저장한답니다.


SPI통신은 센서모듈이나, 제어모듈 등에 흔히 사용되는 통신으로,

여기를 참고하시면 될거에요~ 


자 그럼 핀들을 각각 연결해 볼까요?


먼저 Bluno Beetle 에는 MISO, MOSI, SCK,  등의 SPI 통신 핀들이

기판에 나타나있지 않습니다.


다만 뒤집어보면 작은 박스부분에 모여있지요....


아두이노의 경우에는 10~13번 디지털 핀들이 SPI통신과 연관된 핀이랍니다.

구글에 핀배치를 검색하시고 배선하세요~!  



자 그럼 연결을 해봅시다.




아무래도 연결하는곳들이 핀헤더가 없이 기판이다보니

납땜을 하는수 밖에 없네요ㅠ


여기서 SPI 통신 외적으로 하나를 더 연결하셔야 할 부분이 있는데,

바로 Chip Select Pin 인 CS 핀 입니다.


저는 CS핀을 디지털 4번핀에 연결했어요~

디지털 핀 어디다 연결하셔도 상관없습니다ㅋㅋ





#3 배선 완료후 모듈화




자 이제 배선도 완료됐으니,


전원선도 연결하고 코딩도 넣어볼까요~?



우선 전원은 사실 5V에 구동되는 아두이노라 하더라도 

리튬 폴리머(Li-po) 베터리 1cell 로도 충분히 구동은 된답니다ㅋㅋㅋ


물론 1Cell 이면 3.7V 밖에 안되긴 하지만, 아주 장시간을 구동시킬게 아니면 상관없답니다~

다만 아날로그 인풋의 경우에는 조금 영향이 있기때문에, 

그런 경우에는 2Cell 로 써서 7.4V 전원을 Vin에다가 넣어주시면 됩니다ㅋㅋ


저는 3.7V 를 Vin에 바로 연결해서 사용했습니다.



짜잔 아무런 이상없이 잘 돌아간답니다ㅋㅋㅋ


그럼 코딩을 해볼까요~




#include <SPI.h>

#include <SD.h>

#include "Timer.h"


const uint8_t CS = 4;


const unsigned long PERIOD = 100;    //one second

Timer t;                               //instantiate the timer object


File dataFile;

int numFile;

String fileName;


int data[5] = {0,0,0,0,0};

int data2[5] = {0,0,0,0,0};

int count = 0;


void setup() {


  pinMode(LED_BUILTIN,OUTPUT);

  // put your setup code here, to run once:

  t.every(PERIOD, recording);

  if (!SD.begin(CS)) {

    return;

  }


  dataFile = SD.open("/");

  numFile = printDirectory(dataFile);


  fileName = String(numFile) + ".txt";


  Serial.begin(9600);

  Serial.println(fileName);


  dataFile = SD.open(fileName, FILE_WRITE);

  dataFile.println("Start!");

  dataFile.close();

}


void loop() {

  // put your main code here, to run repeatedly:

  t.update();


}


void recording(){

  int val = analogRead(A0);

  count = count%5;

  

  dataFile = SD.open(fileName, FILE_WRITE);


  data[count] = val;

  val = (data[0]+data[1]+data[2]+data[3]+data[4])/5;

  

  dataFile.print(val);

  dataFile.print("\t");


  Serial.print(val);

  Serial.print("\t");


  val = analogRead(A3);

  data2[count] = val;

  val = (data2[0]+data2[1]+data2[2]+data2[3]+data2[4])/5;

  


  dataFile.println(val);

  Serial.println(val);


  dataFile.close();  

  count++;

  return 0;

}



int printDirectory(File dir) {

  int fileNum = 0;

  while (true) {

    File entry =  dir.openNextFile();

    if (! entry) {

      return fileNum;

    }else{

      fileNum++;

    }

    entry.close();

  }

}



위 코드를 실행하기위해서는 Timer 라이브러리가 있어야하는데,

여기 에서 찾으시면 되요~!


좀 복잡한 코드이긴 한데, 간단히 설명하자면 아날로그 신호를 SD카드에 로깅하는 코드입니다.


앞서 언급드렸다싶이 CS핀은 아무 디지털핀에 연결하셔도 됩니다만, 

맨 위에줄에서 보이는것 처럼


const uint8_t CS = 핀 번호;


를 입력해주셔야 합니다!!


그리고 SD카드가 소켓에 장착되지 않으면 로깅이 안되고,

장착이 되었다면, 내부에 있는 파일을 스캔한 후,


"존재하는 파일갯수+1.txt"


라는 이름으로 데이터가 텍스트파일로 로깅됩니다~


PERIOD는 몇ms간격으로 데이터를 로깅하는지 세팅하는 값이랍니다.


지금은 100ms간격으로 데이터를 받아오고 있지요.


그리고 데이터자체가 워낙에 노이즈가 많아서,

5번 측정한 값의 평균을 저장하도록 만들었어요~



이정도 코드면 조금만 수정하시면

원하시는 프로젝트에 바로 사용할 수 있을거라 생각이 됩니다^^


이상!

이번시간에는 요청이 많이 들어왔던 문제를 해결하고자 합니다.


사람들이 아두이노를 쓰는 이유에는 여러가지가 있겠지만,

주된 이유중에 하나는 


DAQ (Data Acquisition)


즉 신호 수집을 위해서 사용하는 목적으로 사용합니다.


각종 센서에서 나온 디지털신호나, 아날로그 신호들을 아두이노로 받아와서

PC에서 후처리를 하여 결과를 보는 것이죠~


이러한 구성을 하기위해 가장 필요한 것이 바로

플롯팅 (Plotting)입니다.


실시간으로 들어오는 정보들을 확인하면서 어떤 신호가 들어오는지,

어떤 식으로 데이터를 처리해야 할지를

아두이노 내부에서 처리하는 것이 아니라

PC를 통해 처리하는 것이지요.


이러한 구성을 위해서는 PC와 아두이노간의 


시리얼 통신 (Serial Communication)


을 통해 정보를 주고받아야 합니다.



#1. 아두이노 코딩


우선 아두이노에서 컴퓨터로 어떤 데이터를 전송할지,

어떻게 전송할지를 설정합니다.


여기서는 아날로그 신호의 데이터를

Serial.println(data);

를 통해 매 데이터를 던져줍니다.


  1. /*
      AnalogReadSerial
      Reads an analog input on pin 0, prints the result to the serial monitor.
      Graphical representation is available using serial plotter (Tools > Serial Plotter menu)
      Attach the center pin of a potentiometer to pin A0, and the outside pins to +5V and ground.

      This example code is in the public domain.
    */

    // the setup routine runs once when you press reset:
    void setup() {
      // initialize serial communication at 9600 bits per second:
      Serial.begin(9600);
    }

    // the loop routine runs over and over again forever:
    void loop() {
      // read the input on analog pin 0:
      int sensorValue = analogRead(A0);
      // print out the value you read:
      Serial.println(sensorValue);
      delay(1);        // delay in between reads for stability
    }


위 코드는 아래 예제코드와 동일합니다~




자 그럼 여기서 어떤 신호가 나오는지 보려면 어떻게 해야할까요?


아두이노가 업데이트 되면서 시리얼 플로터 라는 기능을 제공하게 되었습니다~




도구 메뉴에서 툴 -> 시리얼 플로터 를 클릭하시고 

보드레이트(Baud rate, 통신속도) 를 코딩과 동일하게 설정하시면


위와 같이 실시간으로 데이터를 볼 수 있게 된답니다.


하지만 아무래도 제공되는 툴이다 보니 자유롭게 변형하거나,

데이터를 처리하기가 자유롭지 않죠,,


그래서 이번엔 프로세싱을 이용해서 그래프를 그려보겠습니다.



#2 프로세싱 코딩



프로세싱은 생소하신 분들이 많을텐데, 주로 아두이노와 같이 사용하는 툴이랍니다.


UI가 아두이노와 상당히 유사하게 되어있어 맘이 좀 편하기도(?) 할거에요~


프로세싱을 실행하시고 아래 코드를 넣어주세요~!


import processing.serial.*;

Serial myPort;        // The serial port
int xPos = 1;         // horizontal position of the graph
float inByte = 0;
float pre_data = 0;

void setup () {
  // set the window size:
  size(400, 300);

  // List all the available serial ports
  // if using Processing 2.1 or later, use Serial.printArray()
  println(Serial.list());

  // I know that the second port in the serial list on my PC
  // is always my  Arduino, so I open Serial.list()[1].
  // (if first, type Serial.list()[0]).
  // Open whatever port is the one you're using.
  myPort = new Serial(this, Serial.list()[1], 9600);

  // don't generate a serialEvent() unless you get a newline character:
  myPort.bufferUntil('\n');

  // set inital background:
  background(0);
}
void draw () {
  // draw the line:
  stroke(127, 34, 255);
  line(xPos, height-pre_data, xPos, height - inByte);

  // at the edge of the screen, go back to the beginning:
  if (xPos >= width) {
    xPos = 0;
    background(0);
  } else {
    // increment the horizontal position:
    xPos++;
  }
  pre_data = inByte;
}


void serialEvent (Serial myPort) {
  // get the ASCII string:
  String inString = myPort.readStringUntil('\n');

  if (inString != null) {
    // trim off any whitespace:
    inString = trim(inString);
    // convert to an int and map to the screen height:
    inByte = float(inString);
    println(inByte);
    inByte = map(inByte, 0, 1023, 0, height);
  }
}


자 코드를 복붙한다음에 아래 순서에 따라 조금 바꿔줍시다.




1. 먼저 코드를 실행한 뒤, 아래 콘솔창에 뜨는 포트넘버를 기억해둡니다.

아두이노에서 포트넘버를 알 수 있으시죠?


2. 포트넘버가 안나오거나 이상한 문자가 나온다면 2번 박스에서

보드레이트를 9600이 아닌, 아두이노에서 코딩한 숫자와 동일하게 입력해줍니다.


3. 지금 제 경우에는 COM3이 두번째 포트이므로 1을 입력합니다.

만일 COM1이 아두이노 포트라면 0 을 입력해야겠지요? 



그렇게 제대로 입력하고나면 왼쪽과 같이 그래프가 그려지게 됩니다.



그 후에 받아진 데이터를 어떻게 처리할지는 위 코드를 기반으로 해서

작업하시면 됩니다~!



궁금한점이 있으시다면 언제든 댓글 달아주세요~^^



이번 시간에는 조이스틱으로 서보모터의 각도를 조절해보도록 하겠습니다~


# 준비물


조이스틱 모듈, 서보모터, 아두이노



먼저 모터에는 구동방식에 따라 DC모터, 스텝모터, BLDC모터, 서보모터 등으로 나뉩니다.

그 중에서 서보모터(Servo Motor)는 지정된 각도 내에서,

원하는 각도로 움직일수 있게 제어가 가능한 모터입니다.


주로 0~180도의 움직임 각도를 가지며, 제어는 PWM 신호에 따라 각도가 바뀝니다.

PWM신호는 Pulse Width Modulation의 약자로, 

펄스 신호의 폭의 길이를 조절하는 신호를 통해 제어한답니다.



위 사진에서 보다싶이 모든 PWM 신호는 고정된 주기를 가지고 있으며,

유일하게 변하는 것은 한 주기 내에서 HIGH상태와 LOW 상태의 비율이 변하게 됩니다.


이러한 비율 변화가 각도의 변화가 되도록 서보모터 내부에서 신호처리가 된답니다~


그럼 이제 만들어봅시다!



먼저 저는 Adafruit사의 조이스틱 모듈을 구매해서 사용했습니다.



그리고 핀헤더를 수직으로 납땜하니 사용이 불편해서.. 이렇게 수평으로 납땜했어요~



회로는 위와같이 연결하시면 돼요!


조이스틱 모듈          아두이노

Vcc                       5V

 GND                     GND

Xout                       A0

Yout                       A1

Sel                         8


Xout은 X 축방향의 움직임 값을 나타내구요,

Yout은 Y 축 방향의 값을,

Sel는 눌렸을때 버튼 값을 의미합니다.

여기서 버튼 값의 경우는 단순히 연결한다고 끝이 아니라, Pull-up resistor 세팅을 해주셔야 합니다.

Pull-up resistor가 뭔지 궁금하시면 http://geronimob.tistory.com/19 참고!


  1. int Xval, Yval, Sel;

    void setup() {
      // put your setup code here, to run once:
      Serial.begin(9600);
      pinMode(8,INPUT);

      //Digital input needs Pull-up resistor setting.
      digitalWrite(8,HIGH);
    }

    void loop() {
      // put your main code here, to run repeatedly:
      Xval = analogRead(A0);
      Yval = analogRead(A1);
      Sel = digitalRead(8);

      Serial.print(Xval);
      Serial.print('\t');
      Serial.print(Yval);
      Serial.print('\t');
      Serial.println(Sel);

      delay(100);
    }


위 코드를 업로드 하고나면 순차적으로 X값, Y값, 버튼 값 이 업데이트 될거에요!




자 그럼 이제 서보모터를 연결해봅시다.


먼저 서보모터는 위에서 언급했다싶이,

PWM신호로 제어가 된답니다.


때문에 Vcc, GND, Signal 이 세 핀이 필요하겠지요~


그럼 연결을 하고 코딩을 해봅시다.


위에 코드를 그대로 살리면서 추가합시다.

먼저 서보모터 함수를 사용하기위해 Servo.h를 불러옵니다.


그 다음 Servo 클래스를 선언하고, 셋업에서 3번핀에 서보를 할당합니다.

지금은 모터가 한개이므로,,, 한 축으로만 제어를 할게요;;


우선 서보모터를 움직이는 함수는 servo.write() 인데, 

안에 들어가야할 값은 0~180도 까지의 각도 값입니다.


하지만 analogRead를 통해 받아온 값은 0~1023의 값이 나오죠.


즉, 0~1023의 값을 0~180 사이의 값으로 맵핑을 해줘야 하는데,

바뀐 변수 = map(바꿀 변수, 바꿀 변수의 최소값, 바꿀 변수의 최대값, 바뀐 변수의 최소값, 바뀐 변수의 최대값)

으로 바꿀수 있습니다!


그리고 명령이 수행되는데 시간이 걸리므로 delay()함수를 넣어줍니다.


  1. #include <Servo.h>

    Servo servo;  // create servo object to control a servo

    int Xval, Yval, Sel,pos;

    void setup() {
      // put your setup code here, to run once:
      Serial.begin(9600);
      pinMode(8,INPUT);

      //Digital input needs Pull-up resistor setting.
      digitalWrite(8,HIGH);

      //Servo setup with pin3
      servo.attach(3);
    }

    void loop() {
      // put your main code here, to run repeatedly:
      Xval = analogRead(A0);
      Yval = analogRead(A1);
      Sel = digitalRead(8);

      Serial.print(Xval);
      Serial.print('\t');
      Serial.print(Yval);
      Serial.print('\t');
      Serial.println(Sel);

      pos = map(Xval,0,1023,0,180);
      servo.write(pos);              // tell servo to go to position in variable 'pos'

      delay(15);
    }


그럼 위와같은 코딩이 되지요!



구동을 하면 스틱 움직임의 각도에 따라 모터도 움직여요~


아무래도 조이스틱의 축이 2개니, 모터를 2개써서 제어도 가능하겠지요?


그리고 버튼값을 잘 이용한다면 위치 고정(Lock)과 같은 기능도 넣을수 있을듯 하네요ㅋㅋ


궁금한 점은 댓글로 남겨주세요!

이번엔 아두이노를 이용한 간단한 신호처리에 대해 알아보겠습니다.


보통 아두이노를 사용하게 되면, 


Analog Input

Digital Input / Output 


이 기능들을 주로 사용하게 되고,

보통 아두이노를 사용하시는 분들을 보면


위 사진처럼


아두이노에서 받아온 데이터(Raw Data)는 통신(시리얼통신 or SPI 통신) 을 통해 PC로 전송하게 되죠.


전송된 데이터는 PC상에서 연산이 되어서 다시 아두이노로 데이터(Processed Data)를 전송하게되고,

아두이노는 이에 따라 반응하게 됩니다.


하지만 이런 구조의 주된 문제점은 아무래도,

시스템의 구조가 커질 수 밖에 없다는 것이죠


아두이노와 센서만 있는다고 되는게 아니라, pc까지 있어야 하니 말이죠


이런 문제를 해결해 주는 몇몇 보드들이 바로 라즈베리파이, 비글본 블랙, 아두이노 윤, 등등

리눅스(Linux) OS 가 설치되어있는 보드들이랍니다.


이들은 보드 하나에 아두이노와 리눅스가 붙어있어서 상당히 간편하게 사용할 수 있지만, 

다루기가 쉽지않아서 처음 이용하시는 분들에게는 쉽지 않습니다.


그래서 이번시간엔 아두이노만 이용해서

데이터 입출력 및 프로세싱을 해보도록 하겠습니다~


#1 푸리에 변환 (Fourier Transform)


먼저 신호분석에서 가장 흔하게 사용하는것이

푸리에 변환 (Fourier Transform) 일겁니다.

푸리에변환은 시간영역의 신호를 주파수 영역의 신호로 변환 시켜주는 함수에요.


좀 더 구체적으로 설명하기위해 '소리'에 대해 봅시다.

소리신호를 녹음해서 보면 빨간 박스에서 보이는것 처럼 지글지글한 신호로 보이죠?


근데 저 신호만 봐서는 이 소리신호의 음 높이가 '도' 인지, '파' 인지 알수가 없죠.

근데 자세히 보면 빨간 박스의 그래프는 x축이 시간(time)이랍니다.

즉, 시간에 따라 신호가 어떻게 흘러가는지만 볼 수 있을 뿐,

음의 높낮이 정보인 주파수(Hz)는 알 수가 없습니다.


이를 알 수 있게 해주는 것이 바로 푸리에 변환.


빨간 박스의 정보를 푸리에 변환을 하게되면

파란 박스에 있는 정보처럼 이상한 형태의 신호가 나옵니다.

하지만 파란 박스를 자세히 보면 x축이 시간이 아닌 주파수(Frequency)가 됩니다.


즉, 주파수 정보인 음의 높낮이를 파악할 수 있다는 거죠.


그럼 다른 사람이 같은 음을 내더라도 목소리가 다른건 어떻게 알 수 있을까요?

목소리는 '음색'이라 표현하기도 하며, 푸리에 변환을 하게되면 주파수 영역에서 다른 형태를 보입니다.


위 사진은 4옥타브 도 (C4, 261.6Hz)를 각 악기별로 연주할 때 나타나는 신호입니다.

[피아노(a), 트럼펫(b), 바이올린(c), 플룻(d)]


보시다싶이 주파수별로 나타나는 형태가 조금씩 다르죠?

저런 특성들을 이용해 음색을 분별 할 수도 있답니다.


아무튼 푸리에 변환은 기존 신호에 숨어있던 정보들을 뽑아낼 수 있는 기능을 가졌지만,

연산량이 상당하기때문에 일반 컴퓨터로도 큰 데이터의 푸리에변환은 쉽지않답니다.



#2 아두이노를 이용한 푸리에 변환



자 이제 이러한 푸리에변환을 빠르게는 못하지만,

아두이노를 이용해 신호처리의 흉내를 내보도록 하겠습니다.


사실 컴퓨터로 푸리에변환을 할때에는 수식적으로 적분을 계산하는 것이 아니라,

다른 방법을 통해서 간접적으로 빠르게 구하는 Fast Fourier Transform(FFT) 를 주로 사용한답니다.


아두이노에서는 공식적으로 FFT함수가 따로 없기때문에, 사용자가 만든 함수를 사용합니다.


제가 알려드릴 함수는 크게 두가지 입니다.


1. C언어로 구성된 라이브러리 (쉬움, 하지만 느림)

arduinoFFT-master.zip



  1. /* Arduino Code */

  2. #include "arduinoFFT.h"

    arduinoFFT FFT = arduinoFFT(); /* Create FFT object */
    /*
    These values can be changed in order to evaluate the functions
    */
    const uint16_t samples = 128; //This value MUST ALWAYS be a power of 2
    double signalFrequency = 1000;
    double samplingFrequency = 5000;
    uint8_t amplitude = 100;
    /*
    These are the input and output vectors
    Input vectors receive computed results from FFT
    */
    double vReal[samples];
    double vImag[samples];

    #define SCL_INDEX 0x00
    #define SCL_TIME 0x01
    #define SCL_FREQUENCY 0x02

    #define Theta 6.2831 //2*Pi

    void setup()
    {
      Serial.begin(115200);
    //  Serial.println("Ready");
    }

    void loop()
    {
      for (uint8_t i = 0; i < samples; i++)
      {
        vReal[i] = analogRead(A0);
        delayMicroseconds(100);
        vImag[i] = 0;
      }
      FFT.Windowing(vReal, samples, FFT_WIN_TYP_HAMMING, FFT_FORWARD); /* Weigh data */
      FFT.Compute(vReal, vImag, samples, FFT_FORWARD); /* Compute FFT */
      FFT.ComplexToMagnitude(vReal, vImag, samples); /* Compute magnitudes */
      PrintVector(vReal, (samples >> 1), SCL_FREQUENCY);
    }

    void PrintVector(double *vData, uint8_t bufferSize, uint8_t scaleType)
    {
      for (uint16_t i = 2; i < bufferSize; i++)
      {
        uint8_t val_temp = map(vData[i],0,1000,0,255);
        Serial.write(val_temp);
      }
      Serial.write('\n');
    }


  1. /* Processing code */

    import processing.serial.*;

    Serial myPort;        // The serial port

    int[] val = new int[64];

    void setup () {
      // set the window size:
      size(1024, 1024);

      String portName = Serial.list()[2];  // Set your port correctly
      myPort = new Serial(this, portName, 115200);
      
      // don't generate a serialEvent() unless you get a newline character:
      myPort.bufferUntil('\n');

      // set inital background:
      background(255);
      rect(0, 0, 1024, 1024);

      // set stroke line color to red.
      stroke(255,0,0);
      
      //delay for arduino serial reboot.
      delay(2000);
    }

    void draw () {
        //Clear background for re-drawing.
        background(255);

        //Draw stroke for each frequency 
        for (int i =0; i<64; i++){
          line(i*16 +1,height,i*16 +1,height-val[i]*4);
        }
    }


    void serialEvent (Serial myPort) {
      try{
        
          //Create buffer for data saving
          byte[] inByte = new byte[200];
          
          //Put data into buffer
          myPort.readBytesUntil('\n',inByte);
          
          // convert to an int and map to the screen height:
          for(int i = 0; i<64; i++){
           val[i] = int(inByte[i]);      
          }

      }catch(Exception e){
        println("Err");
      }
    }




2. AVR로 구성된 라이브러리(어려움, 하지만 빠름)


그리고 아두이노 우노에서만 동작합니다!!


아무래도 레지스터를 건드리다보니, 물리적으로 칩이 우노에 사용되는 ATMEGA328P이 아니면

동작이 안된답니다...!


ArduinoFFT3.zip



  1. /* Code for Arduino */
  2. /*
    fft_adc_serial.pde
    guest openmusiclabs.com 7.7.14
    example sketch for testing the fft library.
    it takes in data on ADC0 (Analog0) and processes them
    with the fft. the data is sent out over the serial
    port at 115.2kb.
    */

    #define LOG_OUT 1 // use the log output function
    #define FFT_N 256 // set to 256 point fft

    #include <FFT.h> // include the library
    #include <avr/io.h>
    #include <avr/interrupt.h>

    void setup() {
      Serial.begin(115200); // use the serial port
      TIMSK0 = 0; // turn off timer0 for lower jitter
      ADCSRA = 0xe5; // set the adc to free running mode
      ADMUX = 0x40; // use adc0
      DIDR0 = 0x01; // turn off the digital input for adc0
    }

    void loop() {
      while(1) { // reduces jitter
        cli();  // UDRE interrupt slows this way down on arduino1.0
        for (int i = 0 ; i < 512 ; i += 2) { // save 256 samples
          while(!(ADCSRA & 0x10)); // wait for adc to be ready
          ADCSRA = 0xf5; // restart adc
          byte m = ADCL; // fetch adc data
          byte j = ADCH;
          int k = (j << 8) | m; // form into an int
          k -= 0x0200; // form into a signed int
          k <<= 6; // form into a 16b signed int
          fft_input[i] = k; // put real data into even bins
          fft_input[i+1] = 0; // set odd bins to 0
        }
        fft_window(); // window the data for better frequency response
        fft_reorder(); // reorder the data before doing the fft
        fft_run(); // process the data in the fft
        fft_mag_log(); // take the output of the fft
        sei();
        Serial.write(fft_log_out, FFT_N/2); // send out the data
        Serial.write("\n");
      }
    }



  1. /* Processing code */
    import processing.serial.*;

    Serial myPort;        // The serial port

    int[] val = new int[128];

    void setup () {
      // set the window size:
      size(1024, 1024);

      String portName = Serial.list()[2];
      myPort = new Serial(this, portName, 115200);
      
      // don't generate a serialEvent() unless you get a newline character:
      myPort.bufferUntil('\n');

      // set inital background:
      background(255);
      rect(0, 0, 1024, 1024);
      stroke(255,0,0);
      noLoop();
      delay(2000);
    }

    void draw () {
        background(255);

        for (int i =0; i<128; i++){
          line(i*8 +1,height,i*8 +1,height-val[i]*4);
        }
    }


    void serialEvent (Serial myPort) {
      try{
          byte[] inByte = new byte[200];
          
          myPort.readBytesUntil('\n',inByte);
          
            // convert to an int and map to the screen height:
            for(int i = 0; i<128; i++){
             val[i] = int(inByte[i]);      
            }
            redraw();

      }catch(Exception e){
        println("Err");
      }
    }


각 구성을 보시고 본인 쓰기에 편하신 라이브러리로 쓰시면 됩니다!


아래 이미지는 프로세싱을 통해 모니터링한 결과입니다.


각각 코드를 돌려보면,


1번 결과



2번 결과



데이터의 갯수와 샘플링 속도차이가 조금 있긴하지만, 첫번째 코드는 깔끔한 반면 느리고 신호가 불안정합니다.


하지만 두번째 경우, 신호가 깔끔하고 안정적이지만, 코드가 복잡합니다.


어떤걸 쓰게 될지는 센서의 성능에 따라 결정하시는게 좋을듯 합니다ㅎㅎ



도움이 되셨다면 공감 한번만 꾹! 눌러주세요^^


이번 시간에는 Advancer Technology 사에서 만든 근전도 신호를 측정하는 모듈에 대해 알아보겠습니다.


먼저, 근전도 신호(Electromyography, EMG)란 근육에서 발생하는 신경신호를 의미하고, 

이는 피부 표면에서도 측정할 수 있답니다.


신기하죠? 몸 안쪽에 있는 신경세포의 신호가 감지된다니..!


이러한 근육신호는 세 개의 전극으로 측정이 된답니다.

하나는 기준 전극(Reference),

또 하나는 + 전극, 다른 하나는 - 전극이겠지요~


보통 이러한 생체신호 센싱 모듈의 경우 전압으로 신호가 발생되는데요,

전압은 흔히 상대적인 전위 차이라고 말하죠?


때문에 생체 신호가 얼마만큼의 전위를 가지고 있는지 알기 위해서는

Reference전극, 말 그대로 신호의 기준 전위를 잡아줘야 하는 것입니다.


자 그럼 +와 -전극은 어떤 역할을 할까요?


여러 이유가 있겠지만,

+ 신호와 -신호의 차이를 구함으로써 노이즈를 제거하기 위한 목적이 크답니다.

자 이제 한번 만들어 봅시다.


센서를 꺼내보면 두개의 전극(+, -) 과 까만 전선에 달린 전극(Reference)이 있습니다.



우선 양 옆단에 핀헤더를 3개씩 납땜을 해줍니다!

왜 양쪽인지는 뒤에서 알려드릴게요~


양쪽에서 나오는 신호가 조금 다르기 때문이에요.



자 이제 납땜을 하셨다면 몸에 부착을 해야하는데,

그냥 떨렁 쇠로 된 전극을 몸에 붙이면 고정이 안되기 때문에

EMG전용 전극이 필요하답니다~


위 사진처럼 연결을 해주시면 사용 준비는 끝!


이제 이걸 어떻게 사용하는가가 관건일텐데,

그냥 막 붙히면....

막 신호가 안나와요ㅠㅠ


붙일때도 방법이 있습니다.



위 사진은 Advancer Technology 사에서 제공해주는 설명서에 있는것인데요,

센서를 부착할 때는


1. 근육의 결방향과 같은 방향으로 센서를 부착


2. 근육의 끝부분(인대 부분)이 아닌 근육의 중심부에 부착


이 두가지를 맞추지 않으면 올바른 신호가 측정이 안됩니다!!



이런 식으로 결방향으로 붙혀주시고,

기준전극은 같은 근육이 아닌 다른 부분이 붙혀주시면 되요~



그리고 힘을 주면 신호가 나온답니다ㅋㅋㅋ

이 모듈의 경우 힘을 주면 LED가 밝게 빛나도록 설정도 되어있네요!


힘을주면 올라가는 신호가 보이시죠?ㅎㅎ



자 그럼 이번엔 다른쪽에서 나오는 신호를 한번 측정해볼게요



아까 신호와는 조금 다른걸 느낄수 있나요?


조금더 신호가 지직지직(?) 하게 보이죠?

사실 이 신호가 원래 EMG신호입니다.


아무런 데이터 수정이 안된 날것의 데이터 (Raw Data) 지요!


그럼 왜 두개가 있을까요?


바로 EMG신호의 분석시에 사용하는 방법이 여러가지가 있기 때문입니다.


근전도(EMG) 신호를 분석할때는 크게


  1. 단순히 크기를 이용한 분석


2. Raw Data의 주파수 특성 분석
(Fourier Transform을 이용한 분석)


이 있답니다. (확실하진 않아요...!)


복잡한 경우가 아니라면 크기를 이용한 분석 만으로도 많은 것을 할 수 있습니다.


하지만 문제는 Raw Data를 크기 성분의 신호로 변환하는데는 상당히 많은 절차가 들어가기때문에,

아두이노와 같은 계산속도가 늦는 MCU의 경우에는  Raw Data를 분석하기가 버겁답니다.


때문에 근전도(EMG) 센서 모듈 내에서 Raw Data를 크기 신호로 변경하여 보내준다면

아두이노에서 훨씬 수월하게 작업을 할 수 있겠지요?


데이터 프로세싱은 또다른 학문분야이기 때문에 여기서 다루기에는 너무 어려울듯 합니다.




혹시나 궁금한점이나 잘못된 부분이 있다면 알려주세요~^^



이번시간에는 아두이노의 고급 기능 중 인터럽트에 대해 알아보겠습니다~


아두이노를 이용하여 Working prototype이나 기타 기능이 있는 기기를 제작하실 때, 가장 많이 쓰는 부분이


1) Analog Input을 이용한 센서값 획득과


2) 버튼을 이용한 digital Input


3) Digital Output을 이용한 LED나 모터의 제어


요렇게 세가지가 아닐까 합니다.


가끔 아두이노를 이용해서 뭔가 만들다보면,

제어해야할 부품들은 많은데, 이 많은 것들을 어떻게 컨트롤 해야하나 막막할때가 있죠?


예를들어 LED가 3개라면, 가장 단순하게는 버튼 3개를 이용해서 각각을 켰다 껐다 하면 되겠지요ㅎㅎ


그럼 만약 제어할게 10개가 넘어가면.....????



결국엔 이런 형태의 참사가 일어나지 않을까요ㅋㅋㅋㅋㅋㅋㅋㅋ



요즘 수많은 디자인 트렌드를 보더라도 단순한 외형이지만, 다양한 기능이 가능한 형태의 제품을 선호하는 것 같아요~

가장 쉽게 예를 들 수 있는건 누가 뭐래도 아이폰!!



단지 버튼 하나만으로 지문인식, 홈 버튼(짧게 누르면), 앱 리스트(두번 누르면), 시리(꾹 누르면), 스크린샷, 등등...


어떻게 저렇게 많은 기능들이 버튼 하나로 가능할까요??? 신기하지요...

그럼 아두이노에서는 이런 기능을 못만드는 걸까요??


가능합니다!


이번시간에 볼 내용이 바로 버튼 하나로 여러개의 기능기 가능하도록 코딩을 해보는 것입니다.


먼저 몇가지 준비물이 필요한데요,


1) 아두이노


2) 택트 스위치


3) 저항


4) LED 3개



# 1



먼저 버튼을 이용해서 digital Input에 연결해봅시다.


흔히 스위치를 세팅하는 방법에는 크게 두가지가 있어요!


1) 풀업 스위치(Pull-Up) : 누르지 않았을때 1의 값이 입력되고 눌렀을 때 0이 되는 세팅





2) 풀다운 스위치(Pull-Down) : 누르지 않았을때 0의 값이 입력되고, 눌렀을 때 1이 되는 세팅





어떤 세팅을 쓸지는 본인이 원하는데로 하면 되겠지만,

특별한 이유가 없다면, 풀다운 스위치를 쓰시길 추천합니다 (항상 5V의 전압때문에 전류를 낭비할 이유가 없겠지요...?)


이게 풀업, 풀다운이 되는 이유는 전기의 단순한 특성때문이에요~

전류는 저항이 작은쪽으로 흐르려하기때문에 이걸 잘 고려하시면 회로를 이해할 수 있을겁니다.



# 2


이제 3개의 LED와 저항을 이용해서 Digital Output 부분의 회로를 구성해봅시다,






자 이제 LED 세팅도 끝났으니, 코딩을 해볼까요?



# 3


우선 코딩을 하기에 앞서서 한 가지 생각을 해봅시다.


과연 버튼 하나를 이용해서 어떻게 멀티펑션을 할 수 있을까요?


그 키는 바로! 버튼을 누르는 시간횟수에 따라 설정할 수 있답니다.


짧게 누름, 길게 누름, 여러번 누름에 따라 기능을 나누려면 어떻게 해야할까요..?


바로 인터럽트(Interrupt)를 이용하는 것입니다.


인터럽트란, 영어로 '방해하다', '중단하다' 의 의미를 가지는데 


실제로 메인 코딩 "void loop()" 이 돌아 가는동안 이 인터럽트에 digital input이 생기면


메인 코딩에서 벗어나 지정된 특수 함수를 먼저 실행하고 다시 메인 코딩으로 돌아가게 된답니다.


그럼 이 인터럽트가 왜 필요하죠?




인터럽트 없이 버튼의 누름 시간만 카운팅해서 쓴다면 사실 코딩이 복잡해질 뿐만 아니라, 제대로 동작하지 않는 경우가 발생합니다.


우리가 버튼을 누르면, 메인코딩에서 벗어나 버튼을 누른 순간의 시간(T1)을 기록하는 함수를 실행하고,

다시 버튼에서 손을 뗄 때 시간(T2)을 기록하는 함수를 다시 실행하게끔 만듭니다.


그럼 T2-T1 이 일정시간을 못넘기면 짧은 버튼,

T2-T1이 일정시간을 넘기면 긴 버튼이 되겠지요ㅎㅎ


그럼 버튼을 누른 횟수는 어떻게 확인할까요?


단순합니다! 

카운팅 변수를 하나 생성하고,

버튼을 눌렀을때 카운팅 값을 하나씩 올려주면 되겠지요?


때문에 여기서는 스위치의 아웃풋을 digital input과 interrupt핀 두개에 동시에 연결해줘야 합니다.





# 4


자 그럼 코딩을 한번 살펴봅시다.


  1. /* Multi-function button program example.
     * 
     * This code is able to extend single digital button action to multi functional action
     * According to press time, and press number.
     * 
     * This code works with pull down button setting.
     * If you want to use pull up setting, 
     * 
     * change digitalRead(button) == HIGH
     * to digitalRead(button) == LOW
     * 
     * Copyrights @ Gerinimo
     * http://geronimob.tistory.com
     */
    //declare led connected pins
    uint8_t LED[] = {5,6,9};
    uint8_t i_led = 1;

    //mapping values of brightness
    uint8_t brightness[] = {B00000000,B00100000,B01000000,B01100000,
                            B10000000,B10100000,B11000000,B11111111};
    uint8_t i_brt = 0;

    //declare button( digital 16 = 
    const int button = 16;

    //stores if the switch was high before at all
    volatile int state = LOW;

    //storing the button state for short press mode
    volatile int state_short = LOW;

    //storing the button state for long press mode
    volatile int state_long = LOW;

    //stores the time each button went high or low
    volatile unsigned long current_high;
    volatile unsigned long current_low;


    void setup()
    {
      pinMode(LED[0], OUTPUT);
      pinMode(LED[1], OUTPUT);
      pinMode(LED[2], OUTPUT);
      pinMode(13, OUTPUT);

      pinMode(button, INPUT);
      attachInterrupt(0, read_button, CHANGE);
      Serial.begin(9600);

      digitalWrite(13,HIGH);
    }

    int i = 0;

    void loop()
    {
      if(state_short == HIGH)
      {
        //do something when button 0 was pressed short ...

        i_brt = (i_brt+1) %  8;
        state_short = LOW;
        
        //end
      }    
      if(state_long == HIGH)
      {
        //do something when button 3 was pressed long ...
        digitalWrite(LED[i_led],LOW);
        i_led = (i_led+1) % 3;    
        i_brt = 0;
        state_long = LOW;
        
        //end
      } 

      analogWrite(LED[i_led],brightness[i_brt]);
    }


    //is called when the Interrupt Pin is pressed or released (CHANGE Mode)
    void read_button()
    {
      //cycles through the buttons to find out which one was pressed or released

      //if this is true the button was just pressed down
      if(digitalRead(button) == HIGH)
      {
        //note the time the button was pressed
        current_high = millis();
        state = HIGH;
        return 0;
      }
      //if no button is high one had to be released. The millis function will increase while a button is hold down the loop function will be cycled (no change, so no interrupt is active) 
       if(digitalRead(button) == HIGH && state == HIGH)
      {
        current_low = millis();
        if((current_low - current_high) > 1 && (current_low - current_high) < 300)
        {
          state_short = HIGH;
          state = LOW;
        }
        else if((current_low - current_high) >= 300 && (current_low - current_high) < 4000)
        {
          state_long = HIGH;
          state = LOW;
        }
      }
    }



먼저 LED는 PWM으로 제어하기 위해서 5,6,9번 핀에 연결했습니다.


동작하는 순서는 단순해요ㅎㅎ


버튼을 짧게 누르면 이미 맵핑해둔 값을 이용해 PWM으로 LED의 밝기를 변화시키구요,


버튼을 길게 누르면 컨트롤이 현재 LED가 아니라 그 옆에 있는 다른 LED로 변하게 됩니다.


즉 짧게 누르면 6번 핀의 LED 밝기가 변하고,


길게 한번 누른 뒤에 짧게 누르면 9번 핀의 LED 밝기가 변하게 된답니다^^



아니 이게 대체 무슨소리여....라고 생각이 드시면


다른것 보다도 각각의 불리언들의 값이 언제 변하는지를 잘 살펴보세요!


언제, 어떻게 변하는지, 그리고 변하면 어떻게 되는지에 초점을 맞춰 코드를 살펴보시면 이해될겁니다!


아래 사진은 이해를 돕기위한 버튼과 언터럽트와 불리언의 타이밍 그래프 입니다.




모르시겠으면... 다시 읽어보시고,


그래도 모르시겠다면 댓글을 남겨주세용ㅎㅎ


- 끝 -



안녕하세요~!


이번시간에는 delay() 함수와 millis()의 차이에 대해 살펴보겠습니다.


일반적으로 LED를 깜빡이거나, 단순한 ON/OFF의 반복이 지속될때는 delay()함수를 많이 이용하죠?


아두이노 Blink 예제를 보셔도 delay()함수를 이용해 LED를 켰다 껐다 한답니다.


그럼 만약에 LED 두개를 엇갈려서 1초 간격으로 껐다켰다 하려면 어떻게 할까요?


아래 그래프처럼 보이게 만들어 봅시다.


자 그럼 LED1은 1초동안 깜빡이고, 그 중간 타이밍에 LED2를 깜빡이면 되니 직관적으로 코드를 짜보면



const uint8_t LED1 = 10;
const uint8_t LED2 = 11;

void setup() {
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
}

void loop() {
  //LED1 Blink
  digitalWrite(LED1,HIGH);
  delay(1000);
  digitalWrite(LED1,LOW);
  delay(1000);

  //LED2 Blink
  digitalWrite(LED1,HIGH);
  delay(1000);
  digitalWrite(LED1,HIGH);
  delay(1000);
  }


이런식이 될텐데, 실제로 이 코드를 돌려보면 원하는 형태로 움직이지 않는답니다.

아마 아래와 같은 식으로 켜졌다 꺼졌다 할겁니다.


이렇게 되는 이유는 바로 delay()함수때문인데요, delay()함수가 실행되면 

해당 시간(ms)동안은 아무런 동작도 하지않고 그냥 정지! 상태입니다.


즉, delay()함수뒤에 그 어떤 코드가 있더라도 실행이 되지않는답니다!



그럼 이를 해결할수 있는 방법은 두 가지가 있어요.


1) delay()함수를 쓰되, 최소 시간단위로 쪼개서 코딩하는 법


2) millis()를 써서 특정 시간이 되면 특정 기능을 실행


먼저 1번 방법부터 봅시다.


# 1 


최소 시간단위로 쪼갠다는게 무슨 의미냐 하면, 

상태가 변하는 순간부터 다음 상태가 변하는 순간까지의 시간을 단위시간이라 하고,

이를 기준으로 코딩한다는 겁니다.


맨 처음 그림에서 보면 상태는 500ms 마다 변하고있죠?

그럼 최소단위시간은 500ms가 된다는겁니다.


이에 맞게 코딩을 해보면,


const uint8_t LED1 = 10;
const uint8_t LED2 = 11;

void setup() {
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
}

void loop() {

  digitalWrite(LED1,LOW);
  digitalWrite(LED2,LOW);
  delay(500);

  digitalWrite(LED1,HIGH);
  digitalWrite(LED2,LOW);
  delay(500);

  digitalWrite(LED1,HIGH);
  digitalWrite(LED2,HIGH);
  delay(500);

  digitalWrite(LED1,LOW);
  digitalWrite(LED2,HIGH);
  delay(500);
}


이런 식으로 짜면 딱 맞게 동작하겠죠?


하지만, 여전히 delay()함수때문에 이 외의 코드를 추가한다거나,

analog 신호를 읽어올때는 제대로 실행이 되지 않는답니다.



# 2


그렇다면 delay함수때문에 뒤의 코드가 실행이 되지 않는다면 어떻게 하면 될까요?


우선 delay()함수를 좀 더 뜯어서 살펴보면,



int delay_time = 500;


while(delay_time < 500){

  //Nothing.. just wait

  delay_time++;

}



이런 식으로 빈 while루프가 특정 시간이 되기전까지 무한히 도는 형식의 코딩이 된답니다.


하지만 임의로 우리가 delay함수 내부의 while루프에 특정한 코딩을 할 수가 없기때문에, 

위 함수의 형식을 빌려와서 void loop() 메인 루프에 그대로 덧씌워 delay()함수를 모사한다면

조금 더 자유로움 delay()함수를 사용할 수 있게 되겠지요.



그럼 어떻게 할까요?


단순합니다. 매 루프마다 millis()라는 함수를 사용해서 현재 아두이노의 시간이 몇 ms인지 파악하고,

그 시간이 우리가 원하는 지정된 시간 이상이 되면 특정 함수를 실행하는 거죠.


단, millis()는 조금만 시간이 지나도 숫자가 매우 커지기때문에,

가장 큰 길이의 데이터 형태인 long을 사용하고,

음수의 시간데이터는 나오지 않기 때문에 unsigned를 붙혀 데이터의 가용범위를 늘립니다.


자, 그럼 LED 1개를 깜빡이는 코드를 millis()로 구현해보면,



const uint8_t LED1 = 10;


// Set timer

unsigned long pre_time = 0;

unsigned long cur_time = 0;


// Set LED status

boolean state_led1 = 0;


// Set duration

const int duration = 1000;


void setup() {

  pinMode(LED1, OUTPUT);


  // Initialize previous counter time

  pre_time = millis();

}


void loop() {


  // Update current time in every loop

  cur_time = millis();


  // If time gap between previous and current goes over the duration,

  // run digital write!

  if(cur_time - pre_time >= duration){


    // Change the boolean state.

    state_led1 = ~state_led1;


    digitalWrite(LED1,state_led1);


    // Update previous counter time.

    pre_time = cur_time;

  }


  else{

    // Do something...

  }

  

}



이런 코드가 나오게 됩니다. 

시간을 계속 업데이트하고,

-> 지정된 시간만큼의 차이가 나는지 확인

-> 만약 차이가 아직 안나면 else()구문으로 넘어가 평소 작업 수행

-> 막약 차이가 나면 if()구문 안의 작업을 수행

-> 작업 완료되면 pre_timer를 현재 값으로 업데이트 해주고 if()구문 끝


이 예제는 LED가 1개일때 사용하는 예제이구요, 

위에서 언급되었던 LED 2개를 번갈아가며 켜는 예제의 경우는

위의 코드를 보고 한번 짜보세요^^


하지만 이러한 millis()의 경우도, 완벽한 해결책이 되지 못할때가 많습니다.

예를 들어 else()구문의 코드가 굉장히 오래 걸려 100ms 가 걸렸다면, 

LED는 100ms이하의 속도로 깜빡거릴수가 없겠죠..?


이런걸 해결하는 방법이 바로 '타이머(Timer)' 랍니다.

타이머와 관련된 자료는 여기서 볼 수 있습니다^^


궁금하신 점이 있으시면 댓글 남겨주세요~!


그리고 도움이 되셨다면 공감 버튼 한번만 눌러주세요^^


안녕하세요^^


이번시간엔 아두이노는 아두이노인데, 블루투스가 함께 내장된 아두이노인 블루노(Bluno), 그 중에서도 비틀(Beetle)에 대해 포스팅을 해보도록 하겠습니다.



(주말이라 교회 모임가기전에 간만에 카페에서 여유롭게 포스팅을...!ㅎㅎ)


먼저 블루노 비틀은 위와같이 생긴 칩이고, 블루투스가 되는 모듈도 있고, 안되는 모듈도 있으니 잘 확인하시고 구매하시기 바랍니다^^


우선 하드웨어 스펙부터 살펴볼게요


이 제품은 위키(wiki)가 정말 잘 되어있어서 여기만 보셔도 무난할 듯 하지마,

 처음부터 쭈욱 설명드리도록 하겠습니다!



먼저 메인 MPU로는 ATmega 328을 사용하고 있습니다. 때문에 기존 아두이노와 같이 사용이 가능하구요, 다만 물리적으로 핀이 좀 적게 나와있어서 불편할 수도 있지만, 복잡한 시스템을 만드실게 아니라면 충분하실 듯 하네요ㅎㅎ



그럼 한번 만들어 볼까요?


# 1


무선 정보 설정하기


먼저 블루노 비틀은 블루투스를 사용하기때문에, 컴퓨터나 핸드폰에서 장치를 찾을 수 있습니다.



저는 윈도우에서 확인했습니다만, 보시면 'Bluno'라는 이름이 보이시죠?

바로 기본적으로 세팅된 이름이 'bluno'로 되어있기 때문이랍니다.


사실 사용하는 목적에 따라 블루투스 이름을 바꿔주는게 사용하기도 편하겠죠?

그럼 어떻게 이름을 바꾸는지 알아봅시다.


먼저, 'AT-command'라는 몇몇 명령어들이 있습니다.

이를 이용하면 이름, 통신속도, 암호화방법, 등등 수많은 옵션들을 바꿀수 있답니다.



이름을 바꾸려면 먼저 비틀을 PC와 연결하고, 아두이노IDE를 실행합니다.



먼저 보드 세팅을 UNO로 맞춰주시고, (비틀은 UNO로 맞춰져있더라구요~)

포트를 맞춰서 잡아주세요ㅎㅎ


그 다음은 시리얼 모니터를 켜셔서 아래와 같이 

"No line ending"

을 선택해주시고, Baudrate도 115200으로 설정해주세요~!

그리고 +++를 입력하시고, 

아래처럼 'Enter AT Mode'라는 말이 뜨면 AT-command mode로 들어간겁니다.

만약에 안뜬다면, 설정을 다시 한번 확인해보세요^^



자 그럼 AT-command mode에 접속했으니, 명령이 잘 먹히는지 확인해봅시다.

AT-command를 사용할때는 line설정을 또 바꿔줘야합니다.

"Both NL & CR"

을 선택하셔야해요!!


그런 뒤 AT를 입력하면 아래처럼 OK라는 문구가 뜨면 성공!



이제 이름을 바꿔볼까요~?


이름은 

AT+NAME=원하는 이름

이렇게 설정하시면 됩니다.



그리고 세팅을 마치고나서는 AT-command mode를 나가야겠지요?


AT+EXIT을 입력해서 설정을 마칩니다.


그리고 PC나 폰에서 확인하시면 이름이 변경되어있을거에요~!

혹시나 변경이 안되어있으면, 블루노를 껐다 켜시거나, 

PC나 폰의 Bluetooth를 껐다 켜보시면 될겁니다.


그 외에 몇가지 설정해야할것들이 더 있는데, 보통의 블루투스는 페어링할때 암호를 필요로한답니다.

하지만 모드를 HID connection로 변경하면 암호를 입력하지 않아도 연결이 되요~!


이 외의 많은 명령어들이 있는데, 포스팅의 마지막에 둘테니 한번 참고하세요^^



# 2


아두이노에서 사용할 수 있는 기능들


자 이제 제대로 블루노를 사용해볼까요?


사실 함수는 대단한것들이 있지는 않아요~

Serial 함수를 통해 데이터를 보내면 자동으로 블루노 하드웨어에 있는 블루투스 칩으로 데이터가 전송이 되고, 

이 블루투스 칩에서 전달받은 데이터를 무선으로 쏴준답니다ㅎㅎ


참 쉽죠잉~!


# 3


스마트폰을 통한 무선 시리얼 모니터링



자 그럼 테스트를 한번 해볼까요?


먼저 아두이노에서 Serial통신을 세팅해서 1초에 2번 아날로그 데이터를 전송하도록 코딩을 합시다.


void setup() {

  // put your setup code here, to run once:

  Serial.begin(115200);

}


void loop() {

  // put your main code here, to run repeatedly:

  Serial.println(analogRead(A0));

  delay(500);

}


위 코드를 업로드 하고, 폰에서 앱을 다운받습니다.


안드로이드나 아두이노나 Bluno를 검색하면 앱이 나올거에요~^^

저는 아이폰이라서 앱스토어에서 검색하니 Bluno terminal이라는 앱이 있더라구요



앱을 다운받아 실행하고, 따로 페어링이나 옵션을 설정안해도 자동으로 연결이 된답니다. (신기신기..)



그리고 이 블루투스로 전송되는 신호는 PC에서도 모니터링이 된답니다.



Debug 세팅이 ON이 되어있다면 가능한거구요, 이 설정을 끄면 PC에서는 모니터링이 안됩니다.

아래 AT 명령어에서 11번을 참고하세요^^


이상으로 테스트는 끝!



# 4


블루노를 위한 AT 명령어들 (AT-command sets)



1. "AT+FSM" change the working mode

AT+FSM=FSM_TRANS_USB_COM_BLE<CR+LF>USB-UART BLE transparent mode
AT+FSM=FSM_HID_USB_COM_BLE_AT<CR+LF>USB-UART BLE HID mode
AT+FSM=?<CR+LF>Request the working mode (default: FSM_TRANS_USB_COM_BLE)

여기서 Transparent mode블루노  <ㅡ>  블루노 의 통신을 할때를 의미합니다.

이때는 블루노 하나는 Central로, 또 다른 하나는 Peripheral로 설정을 해야한답니다. (11번 설정 참고!)

HID mode는 블루노 <ㅡ> 스마트폰. PC등과 연결할때 필요한 설정입니다^^


1*. "AT+KEY" to simulate pressing some buttons in HID mode, read FAQ 16 below for How to use HID mode.
AT+KEY=keyValue0<CR+LF>To simulate one button was pressed
AT+KEY=keyValue0+keyValue1<CR+LF>To simulate two buttons was pressed
AT+KEY=keyValue0+keyValue1+keyValue2<CR+LF>To simulate three buttons was pressed


2. "AT+ROLE" change the CENTRAL-PERIPHERAL configuration

AT+ROLE=ROLE_CENTRAL<CR+LF>BLE CENTRAL mode
AT+ROLE=ROLE_PERIPHERAL<CR+LF>BLE PERIPHERAL mode
AT+ROLE=?<CR+LF>Request the CENTRAL-PERIPHERAL configuration (default: ROLE_PERIPHERAL)


3. "AT+MIN_INTERVAL" change the minimum connection interval

AT+MIN_INTERVAL=10<CR+LF>Recommended minimum connection interval (10ms) for PC and Android
AT+MIN_INTERVAL=20<CR+LF>Recommended minimum connection interval (20ms) for IOS
AT+MIN_INTERVAL=?<CR+LF>Request the minimum connection interval (default: 10)


4. "AT+MAX_INTERVAL" change the maximum connection interval

AT+MAX_INTERVAL=10<CR+LF>Recommended maximum connection interval (10ms) for PC and Android
AT+MAX_INTERVAL=40<CR+LF>Recommended maximum connection interval (40ms) for IOS
AT+MAX_INTERVAL=?<CR+LF>Request the maximum connection interval (default: 10)


5. "AT+UART" change the baud rate of UART

AT+UART=115200<CR+LF>Set the baud rate to 115200
AT+UART=?<CR+LF>Request the baud rate of UART (default: 115200,8,N,1)


6. "AT+BIND" bind another BLE chip. BLE can only connect to the BLE chip with this MAC address

AT+BIND=0x0017ea9397e1<CR+LF>Set the BLE binding (destination) MAC address to 0x0017ea9397e1
AT+BIND=?<CR+LF>Request the binding (destination) MAC address (default: 0x8A6D3B8A6D3B)


7. "AT+CMODE" set whether the connection of BLE is binding or arbitrary

AT+CMODE=UNIQUE<CR+LF>BLE can only connect to the BLE chip with binding(destination) MAC address (see "AT+BIND" command)
AT+CMODE=ANYONE<CR+LF>BLE can connect to any other BLE chips
AT+CMODE=?<CR+LF>Request the binding connection mode(default:ANYONE)


8. "AT+MAC" Request MAC address

AT+MAC=?<CR+LF>Request MAC address of the BLE


9. "AT+NAME" Set the name

AT+NAME=DFBLEduinoV1.0<CR+LF>Set the name of BLE to "DFBLEduinoV1.0".The length is limited to 13 Bytes or below
AT+NAME=?<CR+LF>Request the name of the BLE (default: DFBLEduinoV1.0)


10. "AT+RESTART" restart the BLE

AT+RESTART<CR+LF>Restart the BLE chip


11. "AT+SETTING" change the default setting (new in BLE firmware 1.6)

AT+SETTING=DEFAULT<CR+LF>Restore the default settings, same as PERIPHERAL mode
AT+SETTING=DEFPERIPHERAL<CR+LF>Restore the default settings for PERIPHERAL mode
AT+SETTING=DEFCENTRAL<CR+LF>Restore the default settings for CENTRAL mode
AT+SETTING=?<CR+LF>Request the setting mode (default: DEFPERIPHERAL). If the settings are changed by AT command, "UNKNOWN" will be replied.



12. "AT+BLUNODEBUG" When Bluetooth is connected and BLE chip(CC2540) received the UART message from MCU(ATMEGA328), send the UART message not only to the Bluetooth, but also to the USB port. So that when Bluetooth is connected, we can use the serial monitor to get the UART message. (new in BLE firmware 1.6)

AT+BLUNODEBUG=ON<CR+LF>Turn on the BLUNO DEBUG so that when Bluetooth is connected, we can use the serial monitor to get the UART message.
AT+BLUNODEBUG=OFF<CR+LF>Turn off the BLUNO DEBUG so that wireless programming will be more stable.
AT+BLUNODEBUG=?<CR+LF>Request the BLUNO DEBUG state (default: ON)

여기서 이 Debug function을 활성화 시키면, 블루투스로 전송하는 신호를 시리얼 모니터를 통해 어떤 신호가 나가는지를 모니터링(디버깅)할 수 있게 도와주는 설정이랍니다.

크게 필요한 경우가 아니라면, 안정적인 데이터 전송을 위해 꺼주세요~! 왜냐하면 블루투스 하나로 보낼 신호를 USB포트를 통해서 한번 더 보내주기 때문에 신호가 꼬이거나, 왜곡되는 현상이 발생할 수 있답니다!


13. "AT+USBDEBUG" When Bluetooth is connected and BLE chip(CC2540) received the Bluetooth message from IOS or Android device, send the data not only to the UART, but also to the USB port. So that when Bluetooth is connected, we can use the serial monitor to directly get the Bluetooth message. (new in BLE firmware 1.6)

AT+USBDEBUG=ON<CR+LF>Turn on the BLUNO DEBUG So that when Bluetooth is connected, we can use the serial monitor to directly get the Bluetooth message from IOS or Android device.
AT+USBDEBUG=OFF<CR+LF>Turn off the USB DEBUG so that wireless programming will be more stable.
AT+USBDEBUG=?<CR+LF>Request the USB DEBUG state (default: OFF)


14. "AT+TXPOWER" Change the Transmitted Power which will change the signal range. (new in BLE firmware 1.6)

AT+TXPOWER=0<CR+LF>Change the Transmitted Power to fit the iBeacon calibration. (4, 0, -6 -23 is acceptable)
AT+TXPOWER=?<CR+LF>Request the Transmitted Power (default: 0)


15. "AT+IBEACONS" Enable the iBeacons feature(new in BLE firmware 1.6)

AT+IBEACONS=ON<CR+LF>Enable the iBeacons feature.
AT+IBEACONS=OFF<CR+LF>Disable the iBeacons feature.
AT+IBEACONS=?<CR+LF>Request whether the iBeacons feature is enabled. (default: ON)


16. "AT+VERSION" the version of the firmware(new in BLE firmware 1.6)

AT+VERSION=?<CR+LF>Request the version of the firmware.


17. "AT+RSSI" Request the RSSI of the BLE (new in BLE firmware 1.6)

AT+RSSI=?<CR+LF>Request the RSSI of the BLE(if there is no connection, "-000" will be returned)


18. "AT+MAJOR" Set the major number of the iBeacons (new in BLE firmware 1.6)

AT+MAJOR=0<CR+LF>Set the major number of the iBeacons to "0". (0 to 65535 is acceptable)
AT+MAJOR=?<CR+LF>Request the major number of the iBeacons.(default "0")


19. "AT+MINOR" Set the minor number of the iBeacons (new in BLE firmware 1.6)

AT+MINOR=0<CR+LF>Set the minor number of the iBeacons to "0". (0 to 65535 is acceptable)
AT+MINOR=?<CR+LF>Request the minor number of the iBeacons.(default "0")


20. "AT+EXIT" Exit the AT Command Mode (new in BLE firmware 1.8 ),

AT+EXIT<CR+LF>

Exit the AT Command Mode.

네 이제 진짜 끝!ㅎㅎ

+ Recent posts