[아두이노] 아두이노 신호등 만들기와 코딩

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

[아두이노] 아두이노 신호등 만들기와 코딩



오늘은 어떤 post를 할까 고민하다가 관찰 실험으로 신호등을 보면서 저걸 주제로 post를 해볼까 하는 생각에 신호등을 주제로 선정했네요. 신호등의 경우는 너무 단순해서 LED 기초편에서 간단히 글로만 설명하고 넘어갔던 것 같은데 이걸 post 해도 좋을지 고민하다가 두가지 코딩으로 간단하게 그냥 LED를 순서대로 on/off 하는 방식과 좀 복잡하지만 delay()함수 없이 LED를 패턴 배열변수로 만들어서 LED를 순서대로 on/off하는 방식으로 코딩하는 원리를 설명할까 합니다. 두 코딩의 차이점이 뭐고 어떻게 코딩하는게 더 나은 코딩인지에 대해서 직접 느껴보셨으면 하는 맘으로 post를 작성 했네요.

1. 아두이노 신호등 기초


신호등을 아두이노로 만들려면 신호등의 동작을 알아야 겠죠. 실험은 3등 신호등으로 간단히 테스트 하겠습니다.


3등 신호등의 순서는 "녹->황->적"순서로 움직입니다. 왜! 이순서로 신호등이 바뀌냐면 차량이 녹색일 때 주행을 하고 바로 적색으로 바뀔 때 정지를 한다고 가정을 해봅시다. 교차로 같은 곳에서 차량이 교차로 안에서 신호등이 바뀌면 그자리에서 정지를 하면 교차로에서 교착상태(Dead lock) 현상이 발생할 수 있습니다. 그래서 이 문제를 해결하기 위해 두 신호등 사이에 황색등을 있습니다. 이 황색등의 의미는 녹색등에 의해 주행되는 차량이 적색등으로 정지하기 전에 황색등으로 교차로에 이미 진입한 차량은 그상태로 계속 주행을 하고 교차로에 진입하려는 차량은 교차로에 진입하지 말고 정지하라는 신호의 의미로 사용됩니다. 즉, 교차로에서 차량이 적색으로 바뀌기 전 현재 녹색등에 의해 주행중인 차량을 통제하기 위한 등이 황색등이라고 생각하시면 됩니다.

그래서, 녹->황->적 순으로 움직입니다.

2. 아두이노 신호등 회로도


  • 준비물 : 적색, 오렌지색, 녹색 LED, 저항 220옴 3개, 아두이노우노
  • 내용 : 5,6,7번 핀을 LED 출력핀으로 연결하시오.


회로도 구성은 간단합니다.

3. 코딩


실제로 신호등 시간을 적용하기는 시간이 너무 길기 때문에 동작 테스트로 녹색 5초, 오렌지색 2초, 적색3초로 가정하고 실험 코딩을 해보겠습니다.

1) 기본 3색 신호등


한개의 신호등 동작

digitalWrite(신호등, HIGH); 
delay(시간);
digitalWrite(신호등, LOW);

이렇게 하면 한개의 등이 켜지고 일정시간 유지했다가 다시 꺼지게 됩니다. 위 동작 명령으로 LED 등이 일정시간 켜졌다 꺼지게 됩니다.

신호등을 순서대로 녹색 5초, 오렌지색 2초, 적색 3초 순으로 반복 깜박이게 하려면 아래와 같이 코딩이 됩니다.

void setup()
{
 pinMode(5, OUTPUT);
 pinMode(6, OUTPUT);
 pinMode(7, OUTPUT);
}
void loop(){
  digitalWrite(5, HIGH); //녹색
  delay(5000);
  digitalWrite(5, LOW);
  digitalWrite(6, HIGH); //오렌지색
  delay(2000);
  digitalWrite(6, LOW);
  digitalWrite(7, HIGH); //적색
  delay(3000);
  digitalWrite(7, LOW);
}

위 코딩은 단순하게 녹색/오렌지색/적색의 LED를 깜박이는 동작입니다. 이걸로 끝내면 신호등 post를 할 이유가 없겠죠. 아두이노로 신호등을 만든다면 위 코딩으로 단순하게 코딩을 하면 안됩니다.

[위 코딩의 문제점]
위 코딩은 순서대로 신호등이 깜박이지만 강제적인 순서대로 진행되고 delay()함수로 해당 LED가 독점 사용되기 때문에 중간에 어떤 부품도 개입할 수 없습니다. 물론 인터럽트함수를 이유한다면 개입은 가능하겠지만 위 코딩은 무조건 LED 신호등 명령구간의 처리시간동안 아두이노를 독점하게 됩니다. "녹색(5초) -> 오렌지색(2초) -> 적색(3초)" 순으로 동작하면서 10초동안은 다른 부품이 개입 할 수 없습니다. 여기서, 다른 부품을 추가하여 제어하려면 복잡해 질 수 밖에 없습니다.

그러면 이 문제를 어떻게 해결해야 할까요. 바로, 예전 post에서 이야기한 delay()함수 없이 delay효과를 부여하는 millis()함수를 이용하면 쉽게 해결 됩니다. 그런데 millis()함수로 delay()함수 효과를 낼 수 있다는 것을 알아도 코딩상으로 어떻게 코딩을 해야 할지 잘 연상이 안 될꺼에요.

어떤식으로 그 효과를 표현하는지 살펴보도록 하죠.

2) delay()함수를 이용하지 않는 신호등


신호등 변수와 신호등 시간 변수 만들기

const byte trafficLight[3] = {5,6,7}; //신호등 핀
int lightTime[3] = {5,2,3}; //신호등 유지시간

이렇게 신호등의 핀과 해당 신호등의 유지하는 시간을 변수로 만든 이유는 loop함수내에서 신호등 동작 코딩을 틀로 만들어 놓고 그 틀에 위 변수로 선언한 부분을 대입하여 순서대로 신호등이 "녹색->오렌지색->적색"으로 on/off되도록 만들기 위해서 입니다.

신호등 동작

void steup(){
  digitalWrite(trafficLight[indexVal], HIGH); //초기 녹색등으로 시작
}
  if(millis()-timeVal>=lightTime[indexVal]*1000){ //신호등 동작 trafficLight[3]순서대로
    digitalWrite(trafficLight[indexVal], LOW);  //이전등 끄기
    indexVal++; //신호등위치 증가
    if(indexVal==3)indexVal=0; // 신호등위치가 3이 되면 다시 0으로 처음위치로 돌아감
    digitalWrite(trafficLight[indexVal], HIGH); //새로운등 켜기
    timeVal=millis();
  }
    

위 동작 코딩을 보시면 처음 setup()함수에서 초기 녹색등으로 시작합니다.

그리고,

 if(millis()-timeVal>=lightTime[indexVal]*1000){
    신호등 동작; 
    timeVal=millis();   
 }

위 if문으로 lightTime[]은 indexVal(위치)값으로 현재 신호등 유지시간을 체크합니다. timeVal(이전시간)값은 현재 신호등이 바뀐 시간값을 저장됩니다. 이런식으로 if문에서 신호등을 바뀐 timeVal(이전시간)값을 기준으로 다음 신호등이 유지되는 시간값을 비교해야 유지시간이 끝나면 다시 신호등이 바뀌는 방식으로 신호등의 유지시간을 순서대로 비교하면서 신호등이 바뀌게 됩니다.

종합해 보면,

const byte trafficLight[3] = {5,6,7}; //신호등 핀
int lightTime[3] = {5,2,3}; //신호등 유지시간
unsigned long timeVal = 0; //이전시간
int indexVal = 0; //신호등 위치

void setup()
{
  for(int i=0;i<3;i++){
    pinMode(trafficLight[i], OUTPUT);
  }  
    //초기상태
  digitalWrite(trafficLight[indexVal], HIGH); //녹색등
}

void loop()
{  
  if(millis()-timeVal>=lightTime[indexVal]*1000){ //신호등 동작 trafficLight[3]순서대로
    digitalWrite(trafficLight[indexVal], LOW);  //이전등 끄기
    indexVal++; //신호등위치 증가
    if(indexVal==3)indexVal=0; // 신호등위치가 3이 되면 다시 0으로 처음위치로 돌아감
    digitalWrite(trafficLight[indexVal], HIGH); //새로운등 켜기
    timeVal=millis();
  }
}

이렇게 하면 해당 신호등이 유지한 시간이 만족 할 때마다 신호등이 "녹색->오렌지색->적색" 등으로 바뀌는 동작을 수행합니다.

이 방식의 코딩은 복잡해 보일 수 있지만 원리를 이해하면 그렇게 어렵지 않습니다. 그리고 단순한 코딩이 가지고 있는 문제점을 해결한 방식으로 신호등이 동작하는 시간 사이에 다른 부품의 다른 동작을 수행하는 명령코딩을 하기 쉽습니다.

4. 결과


첫번째 코딩과 두번째 코딩의 결과는 같기 때문에 두번째 결과만 올려 놓습니다.


5. delay()함수 없이 신호등 만드는 목적


신호등이 "녹색(5초) -> 오렌지색(2초) -> 적색(3초)" 순으로 동작하면서 10초동안의 공백이 사라집니다. 그 사이에 원하는 동작을 수행할 수 있게 됩니다. 신호등 동작을 if문으로 묶어서 신호등이 바뀔 조건이 될 때마다 if문 안에 신호등 on/off 명령만 수행되기 때문에 다른 부품의 loop() 코딩이 자유롭게 됩니다.

즉,

void loop(){

}

위와 같은 상태에서 다른 부품을 코딩한다고 생각하시면 될 듯 싶네요.

여기에 횡단보드 부분을 추가한다면 횡단보드에 대한 동작 코딩만 생각하고 코딩하면 됩니다. delay()함수를 이용하면 기존의 코딩에서는 시간에 대해 민감하지만 millis()함수를 이용한 코딩은 새로운 코딩이 결합했을때 발생하는 delay 문제가 없기 때문에 코딩 결합이 쉬워집니다.

그리고, 변수로 신호등 핀과 신호등 유지시간을 분리해 낸 이유는 배열변수의 들어있는 값은 신호등 패턴입니다. 즉, 패턴을 배열변수로 미리 지정 할 수 있습니다.

const byte trafficLight[3] = {5,6,7}; //신호등 핀
int lightTime[3] = {5,2,3}; //신호등 유지시간

이렇게 패턴을 배열변수로 지정하면 나중에 수정할 때 배열변수의 값만 바꾸면 신호등을 원하는 형태로 동작할 수 있게 자유롭게 변경이 됩니다. 즉, loop()함수를 직접적으로 코딩을 수정할 필요가 없이 신호등 패턴으로 자유롭게 신호등을 제어 할 수 있다는 의미인 셈이죠.

만약에, 4등 신호등 일 경우면 신호등 핀과 신호등 유지시간만 4등 신호등에 맞게 배열변수값을 수정하시면 loop()문에 신호등 동작은 4등 신호등에 맞게 동작하게 됩니다. 어떤 의미인지 아시겠지요.

처음 delay()함수를 이용한 순차적인 단순 신호등 코딩은 그 순간만 편하지만 새로운 부품이 추가되면 그때부터 코딩은 복잡해 집니다. 하지만 두번째 millis()함수는 처음에 변수를 선언하고 패턴을 만들고 신호등 동작 명령도 복잡해 보일 수 있는 코딩이지만 이 코딩은 새로운 부품이 추가 되더라고 코딩은 더이상 복잡해지지 않습니다.

이처럼 코딩하는 방식에 따라서 결과가 달라집니다.

추가로, 오늘 실험한 코딩을 기준으로 교차로 신호등을 업그레이드 해보세요. 교차로 신호등을 코딩해서 보여드리면 오늘 전달하고자 하는 의미가 제대로 전달되지 않고 코딩량만 좀 더 늘어나고 복잡해 보일 수 있기 때문에 단순하게 한개 신호등으로 코딩학습의 내용을 담아 의미만 전달하고자 간단히 표현 했네요.

마무리


오늘은 신호등이라는 주제로 두개의 코딩으로 접근해 보았습니다. 시간에 대한 millis()함수를 이용한 좀 복잡한 코딩으로 신호등을 제어한 코딩을 중심적으로 이해해 주셨으면 합니다. 이 원리는 지금까지 post 했던 글들에서 꽤 많이 사용했던 기법입니다.

if(millis()-timeVal>=1000){ 1초단위
  동작;
  timeVal=millis();
}

이 의미만 제대로 이해하시면 두개 이상의 부품을 사용하실 때 아두이노 코딩이 무척 쉬워 집니다.

그리고 패턴 배열변수의 선언하는 의미도 잘 이유해 주세요. 사실 신호등의 동작 원리는 과거 post의 피에조부저를 이용한 멜로디 만드는 코딩의 원리를 응용해서 신호등으로 표현했습니다.

위 참고 post에 가셔서 피에조부저로 곰세마리멜로디를 만든 코딩 원리를 살펴 봐주세요. 이번 신호등 코딩이 이 원리를 이요해서 만든 응용 코딩인 셈이죠. 변수와 동작처리 코딩 스타일이 동일한 원리입니다. 어떤 것을 응용했는지 살펴보세요.

코딩이라는 것은 재미 있습니다. 어떤 원리를 배우고 이해했을 때 전혀 다른 곳에서 그 원리를 적용하여 결과물을 얻을 수 있다는 것은 코딩을 공부하는 재미 중에 하나입니다. 저는 가끔 다른 분야의 코딩들을 자주 살펴 봅니다. 거기서 얻은 지식이 아두이노에 쓰이는 경우가 종종 있습니다. 물론 아두이노에서 배운 지식이 다른 분야에서 써먹기도 하고요. 이런것들이 코딩의 재미가 아니겠습니까요.

여러분들도 다양한 분야의 코딩들을 시간 날 때마다 기초 코딩이라도 학습하셨으면 합니다. 그래야 코딩의 능력이 향상됩니다.

댓글()

[아두이노] 아두이노 선풍기 만들기

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


[아두이노] 아두이노 선풍기 만들기



오늘은 일상에서의 주제를 찾다가 여름이고 해서 선풍기를 주제로 아두니로를 실험을 해 보았습니다. 선풍기의 동작을 관찰하고 각 특징을 기록하고 그 특징들을 아두이노 시각으로 정리 하여 아두이노 상에서 선풍기의 원리를 최대한 유사하게 표현하여 실험을 진행했습니다. 실제로 제작하지 않았고 가상시뮬레이터에서 여러분들도 같이 체험할 수 있게 가상시뮬레이터에서 실험이 이루어집니다.

자! 아두이노 상에서 선풍기를 한번 실험해 볼까요.

1. 선풍기의 동작 관찰


우선 선풍기를 아두이로 만들려면 선풍기의 특징들을 우선 알아야 합니다.

간단히 선풍기의 특징을 정리해 볼까요.


  • 선풍기는 각 단계별로 바람의 세기를 조절 할 수 있다.
  • 선풍기는 180도 정도의 각으로 좌/우 회전을 시킬 수 있다.
  • 선풍기는 동작을 제어하는 스위치버턴이 있으며 현재 상태를 LED로 표시하는 선풍기가 있다.

3가지 특징으로 접근해 보면, 선풍기의 바람(DC모터)을 단계 별로 세기를 조절하는 스위치버턴와 좌/우회전(Servo모터) 시키는 스위치버턴 그리고 스위치를 누를 때 스위치 상태를 7-Segment Display로 표현하면 대충 선풍기의 느낌으로 제작이 가능해 집니다.

2. 아두이노 선풍기 회로도


  • 준비물 : DC모터, 서보모터, 저항 1K옴 1개, 저항 220옴 1개, npn 1개, 다이오드 1개, 스위치버턴 2개, 7-Segment Display 1개, 아두이노우노, 외부전원
  • 내용 : 5번핀이 DC모터 제어핀으로 사용, 9번핀은 서보모터 제어핀으로 사용, 스위치버턴은 2,3번을 사용, 7-Segement Display 4,6,7,8,10,11,12 핀으로 제어하게 됩니다.


위 그림과 같은 느낌으로 제어를 한다는 가정하에서 회로도를 만들면 다음과 같습니다.


좀 복잡하실꺼에요. 아래 코딩에 사전학습 post들을 보시고 개별적으로 해당 부품에 대한 이해하시고 회로도를 보시면 회로도가 어느정도 이해하실 거라 생각됩니다.

3. 코딩



실험에 사용되는 부품들에 대한 사전학습이 필요합니다.

  • 스위치버턴 - 인터럽트함수를 이용하여 제어하게 됨
  • 7 Segment LED - 선풍기 바람의 세기의 단계를 숫자로 표시
  • 서보모터 : 몸체의 회전
  • DC모터 : 바람의세기 제어

기본적으로 위 내용을 우선 원리를 이해하셨으면 본격적으로 코딩에 들어갑시다.

1)선풍기 바람 세기 제어


DC모터 속도를 제어 합니다.

int motorPin = 5;
...
pinMode(motorPin, OUTPUT); 
...
analogWrite(motorPin, 속도);

DC모터는 5번핀을 이용하고 핀모드는 출력 모드입니다. 그리고 analogWrite()함수로 DC모터의 속도를 조절할 수 있습니다.

0,1,2,3 단계로 4개의 상태로 나눠서 바람의 세기를 제어를 하면 아래와 같습니다.

  switch(step){
    case 0:
     analogWrite(motorPin, 0);
     break;
    case 1:
     analogWrite(motorPin, 85);
     break;
    case 2:
     analogWrite(motorPin, 170);
     break;
    case 3:
     analogWrite(motorPin, 255);
     break;
  }

step은 단계 변수로 step의 값에 따라서 DC모터의 세기가 결정됩니다.

바람의 세기버턴을 누르면 step의 값이 0,1,2,3으로 변환하게 하려면 아래와 같이 코딩하면 됩니다.

attachInterrupt(digitalPinToInterrupt(interruptPin1), exchange1, FALLING);
void exchange1() {
  step++;
  if(step==4) step=0;
    
     switch(step){   
       ... 생략
     }  
}

스위치 버턴은 인터럽트함수를 이용하여 인터럽트 스위치 버턴을 누르면 현재 선풍기의 바람의 세기를 변경할 수 있게 됩니다.

2) 바람의 세기 단계를 7-Segment Display(애노드형) 출력


현재 step 단계를 7-Segment Display로 출력하면 됩니다.

해당 숫자를 출력하기 위해서 0.1.2.3 숫자의 패턴을 만들어야 합니다. 애노드형이니깐 아래와 같이 표현할 수 있습니다.

int segValue[4][7] = {
  {0,0,0,0,0,0,1}, //0
  {1,0,0,1,1,1,1}, //1
  {0,0,1,0,0,1,0}, //2
  {0,0,0,0,1,1,0}, //3
};

이 패턴을 가지고 step의 숫자가 출력되는 위치는 어디 일까요. 바로 스위치가 눌러질 때 그 때가 바로 숫자가 변경되는 시점이 됩니다.

void exchange1() {
  step++;
  if(step==4) step=0;
  for(int j=0;j<7;j++){ //setp 숫자 출력
     digitalWrite(segPin[j], segValue[step][j]);        
  }

3) 좌/우 회전시키기


서보모터로 좌/우 회전을 시키기 위해서는 어떻게 해야 할까요. 대충 0~180도 사이를 왔다 갔다 회전을 시키면 됩니다. 회전 최소 간격을 10도기준 0.1초로 해서 회전시키는 방식을 취했습니다.

    if(millis()-timeVal>=100){
        angle=angle+addAngle; //angle(각) 변화값 
        if(angle==180){ 
          addAngle=-10;
        }
        else if(angle==0){
          addAngle=10;
        }  
        servo1.write(angle); //회전각
        timeVal = millis();
    }

위 코딩을 자세히 보시면 addAngle이 초기값으로 10도로 세팅되어 있고 10도씩 0.1초 간격으로 증가하게 됩니다. 이때 180도가 되면 addAngle은 -10으로 바뀌고 -10도씩 0.1초 간격으로 감소하게 됩니다. 이때 0도가 되면 다시 addAngle은 10으로 바큅니다. 0~180도 사이를 왔다 갔다 회전을 하게 하는 동작 코딩이 됩니다.

그러면, 회전을 시킬려면 스위치 버턴을 누르면 회전을 시킬지 정지할지를 정하면 됩니다. 이경우도 인터럽트함수를 이요합니다. 왜냐면 어떤 명령을 수행중에 특정 상태로 강제로 변환 시키기 위해서는 인터럽트함수만한게 없습니다. 회전을 하는 중에서도 강제로 중단시켜야 할 때 유영하게 사용 할 수 있기 때문입니다.

attachInterrupt(digitalPinToInterrupt(interruptPin2), exchange2, FALLING);
void exchange2() {
  sw_state=!sw_state;
  timeVal = millis();
}

인터럽트스위치버턴이 눌러지먼 sw_state가 반전시킵니다.

void loop(){
  if(step>0 && sw_state==true){
    회전명령;
  }
}   

이렇게 코딩하면 sw_state 상태값에 따라서 서보모터의 회전을 결정하게 됩니다.

추가로"step>0" 이 표현이 넣었습니다. 그 이유는 회전은 선풍기가 돌때만 회전되어야지 돌지도 않는데 선풍기화 회전할 필요는 없겠죠. 그래서 선풍기 바람의 세기가 0일때는 회전 할 필요가 없다는 의미로 추가했습니다.

결론은 선풍기 바람의 세기가 0이상이고 선풍기회전스위치버턴이 눌러서 sw_state가 true일때마 회전하라는 의미입니다.

종합해 보면,

#include <Servo.h> 

Servo servo1; 
int servoPin1 = 9;
int motorPin = 5;

unsigned long timeVal = 0; //이전시간

int segValue[4][7] = {
  {0,0,0,0,0,0,1}, //0
  {1,0,0,1,1,1,1}, //1
  {0,0,1,0,0,1,0}, //2
  {0,0,0,0,1,1,0}, //3
};
int segPin[8]={4,6,7,8,10,11,12}; //a,b,c,d,e,f,g 핀


int interruptPin1 = 2; //바람 세기 스위치버턴
int interruptPin2 = 3; //회전 스위치버턴
int step = 0; //바람의세기 단계
boolean sw_state = false; //회전스위치 상태값
int angle = 0; //회전각
int addAngle = 10; //회전조절각

void setup() 
{ 
  pinMode(motorPin, OUTPUT); //DC모터
  servo1.attach(servoPin1); //서보모터
    
  for(int i=0;i<7;i++){ //7-Segment Display핀 모드를 선언과 동시체 초기 0단계 표시
    pinMode(segPin[i], OUTPUT);
    digitalWrite(segPin[i], segValue[0][i]);
  }
  
    //인터럽트함수 선언
  pinMode(interruptPin1, INPUT_PULLUP);
  pinMode(interruptPin2, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin1), exchange1, FALLING); 
  attachInterrupt(digitalPinToInterrupt(interruptPin2), exchange2, FALLING);
  

  //초기 세팅
  analogWrite(motorPin, 0); //바람세기 = 0
  servo1.write(angle); //회전각 = 0
  delay(1000);
}

void loop() 
{   
  if(step>0 && sw_state==true){
    if(millis()-timeVal>=100){
        angle=angle+addAngle;
        if(angle==180){
          addAngle=-10;
        }
        else if(angle==0){
          addAngle=10;
        }  
        servo1.write(angle);        
        timeVal = millis();
    }
  }   
}
void exchange1() {
  step++;
  if(step==4) step=0;
  for(int j=0;j<7;j++){
     digitalWrite(segPin[j], segValue[step][j]);        
  }
  switch(step){
    case 0:
     analogWrite(motorPin, 0);
     break;
    case 1:
     analogWrite(motorPin, 85);
     break;
    case 2:
     analogWrite(motorPin, 170);
     break;
    case 3:
     analogWrite(motorPin, 255);
     break;
  }
}
void exchange2() {
  sw_state=!sw_state;
  timeVal = millis();
}

4. 결과



위그림에서 회전과 바람세기 스위치버턴을 누르면 아래 동영상처럼 동작을 하게 됩니다.


한가지 특수 기능을 표현했습니다. 단순하게 회전과 바람세기을 명령을 내리는 것이 아니라 둘간의 상황이 잘 어울리게 코딩을 했습니다. 바람의 세기가 0일때는 회전상태일지라도 정지가 되고 바람의 세기가 0이 아닌 1이상일 때는 회전정지 상태가 아니면 회전되게 코딩을 표현 했는데 최대한 진짜 선풍기처럼 표현한 부분입니다.

if(sw_state==true){
  선풍기회전;
}

위 코딩에서 "step>0"이란 조건문 한개로 방금 말한 특수 기능을 수행합니다.

if(step>0 && sw_state==true){
  선풍기회전;
}

단순한 한개의 조건을 체크 했을 뿐인데 재밌는 기능을 수행하게 되었네요.

마무리


오늘은 아두이노 가상시뮬레이터에서 코딩적 관점에서 회로도를 만들고 실험이 이루어졌습니다. 실제로 선풍기를 제작한다면 DC모터로 선풍기를 만들지 않고 AC모터로 만들고 좀 더 큰 사이즈의 형태로 실제 선풍기와 같은 형태로 제작을 했다면 좀 더 멋졌겠지만 그러지는 못했네요.

오늘 실험은 일상에서 흔히 볼 수 있는 특정 사물 동작 하나를 관찰하고 그것을 아두이노 시각으로 풀이해 보았습니다. 여러분들도 이런 실험을 한번 해보세요. 이런 학습을 하다보면 처음에는 모방 실험이 되겠지만 그 노하우가 쌓이면 사물의 관찰에서 인간과 같은 생물의 동작을 관찰하고 그것을 아두이노 시각으로 풀이해 낼 수 있게 됩니다. 즉, 상상이 현실이 됩니다. 단지 여러분들에게 필요한 것은 상상입니다.

오늘 실험에서 리모콘이나 Bluetooth를 연동하면 추가로 원격 제어를 할 수 있기 때문에 한번 상상코딩을 해보셨으면 합니다. 리모콘과 Bluetooth를 그동안 자주 거론되고 실험에 사용한 부품입니다. 그리고 라이브러리를 이용하면 간단히 제어를 할 수 있습니다. 함수 몇개만 사용하시면 쉽게 제어가 되니깐 위 코딩에서 어떻게 이 부품들을 적용할지만 코딩 로직만 잘 상상하시면 됩니다 한번 도전해 보세요. 그리고 하실 수 있으시다면 DC모터가 아닌 AC모터를 이용해서 실제 선풍기처럼 제작을 해보셔도 됩니다. AC모터와 AC모터를 제어할 모터쉴드를 구하시고 아두이노로 AC모터를 제어코딩만 하시면 실제 선풍기를 만드실 수 있을 꺼에요.


댓글()

[아두이노] Neopixel을 이용한 7-Segment Display

IOT/아두이노|2019. 7. 18. 21:50


[아두이노] Neopixel을 이용한 7-Segment Display



아두이노 디지털 시계를 유연히 유튜브에서 NeoPixel을 이용한 디지털 시계가 만들어 놓은 영상을 보다가 재밌을 것 같아서 한번 Neopixel로 7-Segment Display를 만들어 보는 실험을 해보았습니다. 7-Segment Display의 원리가 8개의 LED를 구성된 부품입니다. 사실 LED로 수작업으로 7-Segment Display 모양을 직접 만들면 선 연결이 복잡해지고 시계같은 것을 LED로 만들게 되면 귀찮은 부분들이 많은데 Neopixel를 이용하면 의외로 선 연결이 간단하게 설계 할 수 있습니다. LED로만 7-Segment Display를 생각했는데 왜 NeoPixel로 7-Segment Display로 표현 할 생각을 왜! 못했을까 하는 생각에 부품의 원리를 알고 있어도 실제 상상할 때 이런 것을 가끔 놓치는 경우가 발생하네요.

오늘은 Neopixel를 이용한 7-Segment Display를 어떻게 표현한느지 간단히 실험을 통해 살펴보도록 하겠습니다.

1. 7-Segment Display



7-Segment Display 부품은 아래 그림 과 같습니다. led 제어 a,b,c,d,e,f,g,dp핀으로 해당 위치에 LED에 불이 on/off 상태로 숫자와 문자를 만들어 냅니다.


위 모습을 좀 더 자세하게 나타내면은 아래 그림과 같습니다.


위 그림 처럼 8개의 LED로 구성되고 LED가 7-Segment Display 모양 형태로 배치만 하면 아래와 같습니다.


위 그림을 보면 좀 지져분하게 되었지만 어떤 느낌인지 아시겠지요. 일렬로 된 그림은 보기 편하게 되어 있는 것이고 실제로 7-Segment Display를 표현하려면 위와 같이 배치를 하면 됩니다. 참고로 선 모양은 깔끔하게 표현을 못했고 그냥 대충 막 선을 연결했네요. 선 관리가 일반 발광다이오드(LED)에서는 좀 불편 합니다. 그래서 아두이노 디지털 시계를 만들때 이 표현을 사용하지 않았습니다. 이 표현대로 4-Digit 7-Segment Display 표현을 수작업으로 직접 디자인 한다면 공유선까지 하면 답이 안보이고 꽤 머리를 써서 디장인설계를 해야 합니다.

하지만 NeoPixel를 이용한 회로도 설계는 단순화 디자인을 할 수 있는데 어떻게 표현하는지 한번 살펴 볼까요.

2. Neopixel 7-Segment Display 만들기



7-Segment Display의 a,b,c,d,e,f,g 핀 위치를 그대로 동일하게 표현하면 위 그림처럼 되는데 약간 선 방향이 연결에서 좀 불편하게 연결하게 되더군요. 이 상태에서 한세트 더 7-Segment Display를 연결하면 선이 좀 지져분 해지고 선 연결이 답이 안보이겠더군요.

그래서, 아래와 같이 선연결을 S자 방향으로 자연스럽게 이여지도록 표현 했네요. 대부분 실험을 하시는 분들이 S자 방향으로 선을 많이 연결하시더군요. 그리고 현재 선 방향을 반대 S자 방향으로 연결해도 됩니다. 정해진 것은 없고 S자 방향선을 연결하면 꽤 깔끔하게 연결이 되니깐 참고하시고 선을 연결하시면 됩니다.


위 그림으로는 7-Segment Display라고 잘 연상이 안되죠. 아래 그림을 보고 이해하시면 됩니다.


그리고, 추가로 7-Segment Display를 만든다면 아래와 같이 늘릴 수 있습니다.


별로 복잡하지가 않죠.

3. Neopixel 7-Segment Display 실험


1) Neopixel 7-Segment Display 회로도


  • 준비물 : Neopixel 7개, 아두이노우노
  • 내용 : 3번핀을 Neopixel 제어핀으로 사용한다.


2) 코딩


  • 설계 : 0~9까지 카운터 테스트

[Neopixel 함수]

#include <Adafruit_NeoPixel.h>

const byte neopixelPin = 3; 
Adafruit_NeoPixel neopixel = Adafruit_NeoPixel(7, neopixelPin, NEO_GRB + NEO_KHZ800);
  • neopixel.begin() : 네오픽셀 시작
  • neopixel.setPixelColor(위치,R,G,B) : 네오픽셀 위치에 색상 세팅
  • opixel.show() : 세팅된 값을 출력
  • neopixel.clear() : 네오픽셀 초기화

0~9까지 카운터 세기 위해서는 0~9까지의 숫자 패턴을 만들어야 합니다.

 byte segValue[10][7] = {
   {1,1,1,0,1,1,1}, //0
   {1,0,0,0,1,0,0}, //1
   {1,1,0,1,0,1,1}, //2
   {1,1,0,1,1,1,0}, //3
   {1,0,1,1,1,0,0}, //4
   {0,1,1,1,1,1,1}, //5
   {0,0,1,1,1,1,1}, //6
   {1,1,0,0,1,0,0}, //7
   {1,1,1,1,1,1,1}, //8
   {1,1,1,1,1,1,0}  //9   
 };

아두이노 시계의 패턴을 그대로 적용하려고 했지만 배선 순서가 바뀌어서 어쩔 수 없이 segValue[10][7]값을 수정했네요.

0~9까지 순서대로 출력해야 코딩을 만들어 봅시다.

for(0~9까지 반복){
  for(해당숫자패턴 배열을 출력){
      네오픽셀 숫자패턴 세팅;
  }
  네오픽셀 출력;
  delay(1000);
}

대충 이렇게 틀을 잡으면 됩니다.

코딩으로 하면,

  for(int i=0;i<10;i++){  //0~9까지 10개의 숫자를 반복
    for(int j=0;j<7;j++){ //한개의 숫자 당 7개의 네오픽셀 상태임으로 7번 반복
      if(segValue[i][j]==1){ //해당 숫자의 패턴 상태 체크
        neopixel.setPixelColor(j, 255, 0, 0); // 1이면 해당 네오픽셀에 Red color로 표현           
      }else{
        neopixel.setPixelColor(j, 0, 0, 0);  // 0이면 무색으로 표현       
      }
    }
    neopixel.show();  //7개의 패턴 상태가 세팅되면 수자가 만들어지게 되고 그 숫자를 출력      
    delay(1000); //딜레이 시간 1초을 줌
  }  

1초 단위로 0~9까지 숫자가 출력됩니다. 카운터 효과가 되겠죠.

종합해 보면,

#include <Adafruit_NeoPixel.h>

const byte neopixelPin = 3; 
Adafruit_NeoPixel neopixel = Adafruit_NeoPixel(7, neopixelPin, NEO_GRB + NEO_KHZ800);


 byte segValue[10][7] = {
   {1,1,1,0,1,1,1}, //0
   {1,0,0,0,1,0,0}, //1
   {1,1,0,1,0,1,1}, //2
   {1,1,0,1,1,1,0}, //3
   {1,0,1,1,1,0,0}, //4
   {0,1,1,1,1,1,1}, //5
   {0,0,1,1,1,1,1}, //6
   {1,1,0,0,1,0,0}, //7
   {1,1,1,1,1,1,1}, //8
   {1,1,1,1,1,1,0}  //9   
 };

void setup() {
  neopixel.begin();  
}

void loop() {      
  for(int i=0;i<10;i++){  //0~9까지 10개의 숫자를 반복
    for(int j=0;j<7;j++){ //한개의 숫자 당 7개의 네오픽셀 상태임으로 7번 반복
      if(segValue[i][j]==1){ //해당 숫자의 패턴 상태 체크
        neopixel.setPixelColor(j, 255, 0, 0); // 1이면 해당 네오픽셀에 Red color로 표현           
      }else{
        neopixel.setPixelColor(j, 0, 0, 0);  // 0이면 무색으로 표현       
      }
    }
    neopixel.show();  //7개의 패턴 상태가 세팅되면 수자가 만들어지게 되고 그 숫자를 출력      
    delay(1000); //딜레이 시간 1초을 줌
  }  
    
  //초기화  
  neopixel.clear(); 
  neopixel.show();
  delay(100);     
}

3) 결과


숫자 모양 틀로 표현했다면 아래 움짤이 숫자로 보였을 텐데 이상한 모양처럼 보이네요. 마지막에 9 다음에 0으로 다시 넘어갈 때는 0~9까지 1초 단위로 숫자가 바뀌다가 9 이후 초기화로 전부 지우는 동작을 한번 수행하기 때문에 약간 보시기에 부자연스러울 수 있을 꺼에요. 초기화 명령라인들을 지우고 프로그램을 실행시켜면 자연스러워질 꺼에요. 초기화 명령라인을 넣은 이유는 나중에 추가로 수정할 때 초기화 작업이 필요 할 수 있으니깐 이렇게 Neopixel를 초기화 할 수 있다는 것을 보여 드리기 위해서 초기화 명령라인을 남겨 뒀네요. 이부분을 감안하시고 보시기 바랍니다.


4. Neopixel 디지털 시계 디자인




대충 디지털 시계 디자인은 위와 같이 표현하시면 "00:00"으로 "시:분"으로 표현 할 수 있습니다. 여기서 초까지 출력되게 하고 싶다면 Neopixel를 더 추가해야 겠죠.

코딩은 위에서 숫자를 한개의 숫자를 출력하는데 0~6위치의 네오픽셀에 7개의 패턴을 만들어 출력했는데 이 원리를 기반으로 지난 시간에 아두이노 시계 코딩을 가지고 수정하시면 됩니다.

위 코딩에서는 일자의 자리 0~9까지를 카운터 했는데 십의 자리를 출력하고 싶다면

for문에서 0~6 Neopixel은 1의 자리이고 7~13 Neopixel은 10의 자리가 됩니다.

그러면 12라는 숫자를 출력하기 위해서는

7~13 Neopixel => 1
0~6 Neopixel => 2

이렇게 각 범위의 숫자를 만들어 내면 됩니다. 그러면 1의 자리 for문과 10의 자리 for문을 Neopixel 위치값을 지정해서 숫자를 만들어 내면 됩니다.

위의 사전학습에서 시계 코딩을 분석해 보고 오늘 post에서 숫자 카운터를 한 것을 기반으로 직접 상상 코딩을 통해 디티러 시계 코딩에 도전해 보세요.

이 코딩은 여러분들의 상상에 맡기고 소스는 공개하지 않겠습니다.

마무리


오늘은 Neopixel를 이용하여 7-Segment Display를 만드는 방법을 실험을 하였습니다. 직업 제작을 한다면 꽤 재밌는 것들을 만들 수 있습니다. 마지막에 디지털 시계 디자인 한 것처럼 시계를 만들 수 있습니다. 일반 발광다이오드(LED)로 디자인 하는 것보다 Neopixel 디자인이 좋은 점은 회로선을 연결하는데 편하고 아두이노에서 사용하는 핀은 단 한개로 제어가 가능합니다. 더 중요한 것은 바로 다양한 Color 표현이 가능하다는 점입니다. 3색 LED로 표현하면 Color를 만들 수 있지만 이경우는 회로도 선 배치가 단색 LED보다 더 복잡해지고 제어하는데 꽤 애먹습니다. 그러니 NeoPixel로 디자인하는게 훨 편하겠죠.

우연히 아두이노 디지털 시계 유튜드 영상을 보다가 "아! Neopixel이 있었지!"하고 Neopixel에 대해 잘 알고 있었는데 이걸 상상을 못했다는것이 참 아직도 아두이노의 실력이 부족하다는 것을 느낄 수 있었네요. 그래도 잠깐 몇초 본 영상으로 대충 느낌 아니깐 회로도 만드는 것은 어렵지 표현 했습니다.

아무튼 재밌는 실험을 했네요.

댓글()

[아두이노] 같은 원리 다른 부품(복습)

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

[아두이노] 같은 원리 다른 부품(복습)



오늘은 아두이노를 실험을 할 때 사용되는 부품들 중에 같은 원리로 동작하지만 다른 부품들이 어떤 것들이 있는지 복습하는 시간을 가져볼까 합니다. 살펴 볼 원리는 3x3x3 CUBE 출력 원리이며 이 원리가 사용되는 다른 부품들을 살펴보는 시간을 가져보도록 하겠습니다.

1. LED의 3x3x3 CUBE 출력 원리



참고 자료의 CUBE 원리는 다음과 같습니다.

위 그림에서 보는 것 처럼 가로축선과 세로축선들 끼리 공유선으로 묶으면서 특정 위치에 LED에 불이 들어오는 방법은 가로와 세로의 특정 위치의 +,-의 상태값에 따라 불이 들어오게 됩니다. 공유선을 사용하기 때문에 같은 공유선에서 서로 다른 LED에 불이 들어오기 위해서는 시간차 전원 +,- 상태로 LED를 동시에 서로 다른 공유된 LED에 불이 들어올 수 있게 합니다. 이 원리는 참고자료에 가시면 자세히 나와 있습니다.

이 원리를 사용하는 다른 부품들을 한번 살펴볼까요.

2. 동일한 LED의 3x3x3 CUBE 출력 원리를 사용하는 다른 부품


이 출력 원리를 이용하는 부품을은 대부분 다수의 LED를 이용하는 부품에서 사용됩니다. 다수의 LED에 사용되는 부품들은 이 원리를 이용하여 부품을 제작합니다. 다수의 LED를 개별적으로 제어하려면 많은 핀을 사용해야 하는데 그렇게 부품을 제작하면 부품도 부품이지만 그 부품을 제어 한다면 아두이노 같은 보드에서 제어하기 위해서 많은 핀들이 필요로 하고 비효율적인 부품이 되겠죠. 그래서 다수의 LED를 사용하는 부품들은 각 LED를 제어하기 위해 공유선을 이용하여 LED를 제어하는 방식을 사용하여 적은 핀으로 많은 LED를 제어할 수 있게 제작이 됩니다. 그러면 이 원리를 이용하는 부품들을 살펴보도록 하죠.

1) 4-Digit 7-Segment Display



7-Segment를 여러개 사용하기 위해서 4개의 7-Segment을 공유선을 연결하여 출력 원리를 사용합니다.

위 그림의 부품의 내부 회로도는 아래 그림과 같습니다. 한개의 7-Segment Display를 제어하기 위해서는 총 8핀이 필요하는데 4개의 7-Segment Display를 제어한다면 개별적일 경우 32개 핀이 필요 합니다. 하지만 공유선을 이용한 실제 이 부품은 12개의 핀으로 4개의 7-Segment Display를 제어할 수 있게 제작 되었습니다.

2) 8x8 도트매트릭스



3x3x3 CUBE은 배치 위치가 3x3x3일뿐 실제 내부 회로도를 보면 3x9로 공유선을 이용하여 연결합니다. 8x8 도트매트릭스은 3x9 LED 회로도에서 8x8형태로 표현한 부품입니다.

아래 내부 회로도 모습을 보시면 3x3x3 CUBE랑 같은 모습인 것을 확인 할 수 있을 꺼에요.

3. 동일한 LED의 3x3x3 CUBE 출력 원리의 반대 입력 부품


다수의 LED를 제어하는 출력 원리를 반대로 입력 원리로 사용하는 부품이 있습니다. 대표적으로 키패드를 들 수 있습니다. 다수의 LED에 불이 들어오게 하기 위해서 시간차로 특정 위치에 불이 들어오도록 LED의 출력 상태를 만들었다면 반대로 스위치버턴에서 시간차로 특정 위치의 +,-상태로 전류가 공급되고 특정 위치에 스위치버턴이 눌러지면 전류가 흐르고 그 전류값을 아두이노가 읽고 그 읽은 위치값으로 눌러진 스위치 위치를 알게 됩니다. LED은 이 원리를 출력으로 이용했고 스위치버턴은 이 원리를 입력으로 이용했습니다.

Keypad



위 그림을 보면 LED 위치에 스위치버턴으로 바뀌었고 출력 대신에 입력으로 변경되었을 뿐 동작 원리는 동일합니다.

마무리


동일한 원리를 이용하는 부품들이 많은데 그 중 지금까지 다룬 post들 기준으로 몇개만 나열했네요. 이것 말고도 더 많은 부품들이 동일한 원리로 동작합니다. 각 부품들에 대해서 간단히 복습 차원으로 살펴보는 시간을 가졌습니다. 자세한 부품의 설명은 해당 post를 링크 걸어 놓았으니깐 한번 살펴보셨으면 합니다.

왜! 이런 post를 썼을까요. 그 이유는 아두이노 실험을 쭉 하시다 보면 수 많은 부품들을 다루게 됩니다. 그런데 재밌는 것은 부품만 다르지 부품의 동작원리는 같은 경우가 많습니다. 오늘 post에서 다룬 것처럼 LED와 스위치버턴을 이용하여 만든 부품들이 전부 동일한 원리로 동작합니다. 만약, 3x3x3 CUBE 원리를 알고 있었으면 8x8 도트매트릭스나 4-Digit 7-Segment Display나 키패드 모듈을 쉽게 이해하고 제어할 수 있었을 거라 생각됩니다. 각 부품들을 개별적으로 생각하지 마시고 어떤 특징들이 유사한지 한번 여러분들이 알고 있는 부품들간의 특징들을 비교해서 정리를 해보셨으면 합니다. 오늘 post한 부품들은 동일한 원리의 부품들입니다. 즉, 한개를 알게 되면 위에서 거론한 다른 부품들도 다룰 수 있다는 의미인 것이죠. 이처럼 부품들 간의 특징들을 서로 비교하고 같은 원리의 부품들을 정리하시면 아두이노 학습에 무척 도움 됩니다. 이번에 한번 이런 부품들 별로 정리하는 시간을 가져보셨으면 하는 맘으로 post를 작성 했네요.

"같은 느낌 다른 표현"의 부품들을 찾는 재미과 반복 복습학습으로 아두이노 능력이 향상될 거라 생각 됩니다.

댓글()

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

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. 9. 09:00

[아두이노] 토양수분 측정기



지난 시간에 토양수분센서 값을 간단히 출력하는 실험을 하였습니다. 오늘은 토양수분센서로 측정한 값을 어떤 출력방식을 상상할지 고민하다가 LCD16x2에 센서값을 출력하도록 하여 간단히 토양수분 측정기를 만들어보는 실험을 해 보겠습니다.


1. 토양수분 측정기 회로도


  • 준비물 : LCD16x2 I2C 모듈, 토양수분센서, 아두이노우노
  • 내용 : 아두이노우노 A0핀을 토양수분센서의 A0에 연결하고 LCD16x2 I2C 모듈에 SDA(A4), SCL(A5)핀에 연결하시오
  • 참고 : [아두이노] LCD16x2 I2C(LiquidCrystal_I2C) 제어

LCD16x2 I2C 모듈에서 I2C통신에 사용되는 아두이노우노 A4, A5핀에 주의해서 연결하시면 나머지는 연결하는데 어려움이 없습니다.


3. 코딩



[함수]

LiquidCrystal_I2C lcd(0x3F,16,2);  // 0x27 or 0x3F
  • 초기화 : lcd.init()
  • 배경 : lcd.backlight()
  • 커서위치 : lcd.setCursor(0, 0)
  • 출력 : lcd.print("문자열")

추가 함수는 참조 링크된 곳에 가셔서 "LiquidCrystal_I2C.h"파일에 가셔서 함수명들을 살펴보시기 바랍니다.

[기존소스]

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

LCD16x2 I2C모듈에 결과를 출력하는 코딩을 추가하시면 됩니다.

종합해보면,

#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
 
LiquidCrystal_I2C lcd(0x3F,16,2);  // 0x27 or 0x3F

void setup() {    
  lcd.init(); //초기화
  lcd.backlight(); //배경불켜기
  lcd.setCursor(0, 0);
  lcd.print("Sensor Start!");
  delay(1000);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Moisture Sensor");
  lcd.setCursor(0, 1);
  lcd.print("value : ");
}
void loop() {
  int val = analogRead(A0);   
  lcd.setCursor(9, 1);
  lcd.print(val);    
  delay(1000);
  lcd.setCursor(9, 1);
  lcd.print("    "); //센서값0~1023사이가 값이 출력되기 때문에 4칸을 공백처리하면 센서값만 지울 수 있음.
}

lcd.setCursor(0, 0)은 첫줄의 0번째 위치에 커서가 위치합니다. 이 함수 뒤에 lcd.print(값)함수로 출력함수를 표현하면 커서의 위치에서부터 값이 출력됩니다. lcd.setCursor(9, 1)함수는 두번째 줄의 9번째칸의 커서가 위치하고 그 9번째 칸부터 lcd.print(값)함수의 출력값이 LCD16x2에 출력됩니다.

혹시, LCD16x2 I2C모듈에 관한 위 코딩이 이해가 안되신다면 사전학습으로 링크 걸어놓은 post를 한번 읽고 오세요.

4. 결과


이번 실험에서도 토양에 직접 토양수분센서를 꼽지 않고 손에 물을 묻혀서 측정하는 방식을 선택했습니다. 그리고 PC USB로 전원을 공급 받았는데 외부 건전지를 이용해서 아두이노우노를 자유롭게 분리해 냈습니다. 이동식 토양수분측정기로 만들었는데 모양을 이쁘게 케이스로 만들었다면 손전등같은 형태로 측정기를 만들었을텐데 그러지는 못했네요.

아무튼 손으로 토양수분센서를 만지면 LCD16x2 I2C 모듈에 측정된 센서값이 아래 동영상에서 보는 것처럼 정상적으로 출력되었네요.


5. 추가 출력 방식


1) Bluetooth 응용


Bluetooth를 이용하여 스마트폰으로 토양수분센서를 출력할 수 있습니다.

위 링크된 조도센서값을 Bluetooth를 이용하여 스마트 폰에 센서값을 출력한 예제에서 토양수분센서 부분만 수정하면 됩니다.

스마트폰으로 토양수분센서값을 전송 받을 수 있게 된다면 많은 것들을 할 수 있습니다. 가령 화분에 토양수분센서가 꼽아져 있으면 화분에 수분상태를 실시간으로 스마트폰으로 확인이 가능합니다. 화분에 물주는 시기를 스마트폰으로 확인이 가능해 지겠죠. 그리고, 토양수분상태를 확인하고 물을 줘야 할 상황이면 자동으로 물을 줄 수 있으며 아니면 스마트폰으로 물주는 명령을 아두이노 수동으로 명령을 내릴 수 도 있습니다. Bluetooth 대신에 wifi모듈을 이용한다면 화분이 있는 위치에서 벗어난 외부에서도 화분 토양수분상태를 확인이 가능해지고 외부에서 화분에 물을 줄지를 원격으로 제어할 수 있습니다.

2) LED or 피에조부저 응용


위 링크된 LED와 피에조부저를 이용하면 토양의 현재 수분상태를 LED Color로 시각적으로 표현이 가능합니다. 그리고 피에조부저를 이용하면 수분상태를 소리로도 표현이 가능합니다. 어떻게 이용하느냐에 따라서 다양한 응용이 가능합니다.

마무리


오늘은 토양수분 측정기를 만들어 보았습니다. 측정기를 만들었지만 이 토양수분센서를 통신에 이용하면 더 많은 것들을 할 수 있습니다. 응용으로는 식물을 재배하는 곳에 설치하면 모터펌프를 이용해서 자동으로 토양에 물을 줄 어 식물재배에 이용할 수 있습니다. 또는 주기적으로 토양수분상태를 측정하게 되면 이 데이터로 토양의 수분상태의 정보를 수집하여 그 정보를 토대로 토양수분변화를 분석하는 통계 자료로 활용할 수 있습니다. 즉, 토양수분값을 어떤 방향으로 상상하느냐에 따라서 그 활용도는 넓습니다.

여러분들은 토양수분센서가 있다면 무엇을 하고 싶은지 상상의 나래를 펼쳐 보세요.


댓글()

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

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로 토양수분센서값을 출력하는 측정기를 만들 것 같기는 한데 아직은 결정하지 않았고 결정되면 둘 중 하나를 선택해서 좀 더 개선된 실험을 해보도록 하겠습니다.


댓글()

[아두이노] 함수 오버로딩

카테고리 없음|2019. 7. 4. 09:00

[아두이노] 함수 오버로딩




오늘은 프로그램 코딩에서 자주 사용하는 함수 오버로딩에 대해서 간단히 살펴보도록 합시다.


1. 함수 오버로딩


함수 오버로딩이란 함수명은 같으나 함수안에 있는 인자가 서로 다른 자료형 값들을 가지고 있는 형태의 함수를 말합니다. 어떤 공통된 동작을 수행하지만 내부적으로 서로 다른 처리를 할 때 주로 사용하지요. 참고로, C언어에서는 함수 오버로딩을 허용하지 않지만 C++에서는 함수 오버로딩을 허용합니다. 아두이노 IDE에서는 허용이 되고 동일한 이름으로 서로 다른 동작을 수행할 수 있습니다.

이제는 함수 오버로딩을 언제 사용하는지 살펴 보도록 하죠. 가령 덧셈의 경우을 생각해 볼까요.

예) 두 수를 더하시오.

=> a+b 가 됩니다.

total = add(a,b);

이때, a,b은 둘 다 정수 일 수 있고, 둘 다 실수 일수도 있으며 어느 하나만 정수이거나 실수 일 수 있습니다. C언어 프로그램에서는 변수에 대한 자료형을 명확하게 표현해 줘야 합니다.

int add(int a, int b){
  return a+b;
}

이렇게 표현하면 a,b은 정수로 무조건 정의되어 있기 때문에 무조건 정수값이 되어야 합니다. 하지만 우리가 두 수를 더할 경우에 무조건 정수만 존재하는 것이 아니기 입력 자료형 수에 맞는 별도의 함수가 필요합니다.

그러면,

int add_Int(int a, int b)
double add_Double(double a, double b)
double add_Int_Double(int a, double b)
double add_Double_Int(double a, int b)

이렇게 일일히 함수을 개별적으로 표현해야 할까요. 자료형만 다르지 덧셈이란 동작은 같기 때문에 구지 이렇게 표현할 필요는 없습니다. 즉, 함수 오버로딩으로 표현하면 함수명은 같고 인자만 다르게 표현해주면 코딩하는 프로그래머의 입장에서는 가독성 있게 코딩을 할 수 있게 됩니다.

int add(int a, int b)
double add(double a, double b)
double add(int a, double b)
double add(double a, int b)

선언되어 있다면,

t1 = add(1, 2);
t2 = add(1,2, 2.2);
t3 = add(1, 1,2);
t4 = add(1,2, 2);

이렇게 add()함수라는 이름으로 정의해 놓고 인자만 다르게 정의해 놓으면 해당 인자에 맞는 함수가 호출되어 해당 함수를 수행하게 됩니다. 즉, 덧셈을 한다는 동작은 변함이 없고 알아서 자료형 인자에 맞는 함수가 호출되어 명령을 수행하게 됩니다.

2. 아두이노에서 함수 오버로딩 예


최근에 배운 키보드/마우스 라이브러리 예제로 살펴볼까요.

키보드 라이브러리 오버로딩 함수

size_t write(uint8_t k);
size_t write(const uint8_t *buffer, size_t size);

마우스 라이브러리 오버로딩 함수

Mouse.click();
Mouse.click(button);
Mouse.press();
Mouse.press(button)
Mouse.release();
Mouse.release(button);
Mouse.isPressed();
Mouse.isPressed(button);

다른 아두이노 라이브러리 함수를 살펴보셔도 됩니다. 이렇게 인자의 자료형 값이 다르면 인자 자료혀엥 맞는 해당 함수가 호출됩니다.

위 예에서 자세히 보시면

Mouse.press();
Mouse.press(button);

button - MOUSE_LEFT (default), MOUSE_RIGHT, MOUSE_MIDDLE

마우스 키를 누를 때 인자가 없으면

Mouse.press();

이 함수가 호출되고 디폴트 MOUSE_LEFT 동작을 수행합니다.

하지만,

Mouse.press(MOUSE_MIDDLE);

이렇게 인자값이 존재하면 한개의 인자값을 가지고 있는 Mouse.press(button) 함수가 호출되고 인자값에 해당되는 가운데 버턴을 누른 이벤트가 수행됩니다.

쉽게 말해서, 같은 이름으로 된 함수이지만 해당 자료형 인자값에 따라서 해당 함수가 호출되어 동작을 수행할 수 있게 함수명을 통일해서 표현하는 것이 함수 오버로딩이라는 점만 기억 해 주세요.

3. 함수 오버로딩 실험


간단히 개콘에서 했던 "아무말 대잔치" 처럼 그냥 의미없는 서로 다른 동작을 아두이노에서 하나의 함수명으로 처리하는 실험을 해 보겠습니다.

1) 코딩


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

  //오버로딩 함수
  Serial.println(output());
  Serial.println(output(10));
  Serial.println(output(10.12));
  Serial.println(output(10,20));

}

void loop()
{

}

int output(){
  return 100;
}
int output(int a){
  return a+10;
}

double output(double a){
  return a+3.12;
}

int output(int a, int b){
  return a+b;
}

총 4개의 함수를 하나의 output()함수명으로 오버로딩 시켰습니다. 인자가 없을 때, 인자가 하나지만 서로 다를 때, 인자가 두개일 때의 동작 명령을 만들어서 한번 오버로딩 된 함수에 인자값을 넣어서 실행 해 볼까요.

2) 결과


  • 인자가 없을 때 100 반환
  • 인자가 정수형일 때 인자값+10 반환
  • 인자가 실수형일 때 인자값+3.12 반환
  • 인자가 2개의 정수형일 때 인자a+인자b 반환

그 결과는 아래와 같습니다.


마무리


함수 오버로딩을 사용 하는 경우는 딱 하나만 기억하시면 됩니다. 어떤 기본 동작 수행을 하는데 그 동작 수행시 필요한 인자의 수가 다르거나 또는 인자의 자료형이 달라 질 때 함수명을 달라진 이름으로 개별적으로 일일히 만드는 것이 하니라 묶음으로 하나의 공통 이름으로 공유하고 서로다른 인자의 값에 따라 표현한 해당 함수가 호출되어 동작을 수행하도록 할 수 있다는 점만 기억해 주세요.

댓글()

[아두이노] 게임 조종기 아두이노

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

[아두이노] 게임 조종기 아두이노



오늘은 모바일 게임을 모모앱플레이어로 했을 때 PC에서 조정이 가능합니다. 이 때 게임 조정키를 키보드로 세팅해놓으면 이전 시간에 아두이노 키보드를 이용하면 모바일 게임을 조정할 수 있게 됩니다. 여기서, 키보드 방향 조정을 조이스틱으로 표현하면 좀 더 조정기 다운 표현이 되겠죠.


아두이노 마이크로에서 게임 조정기를 만들어 볼까요.

1. 방향키 제어 복습



사전학습에 가시면 조이스틱을 방향키를 조절하는 방법에 대해 나와 있습니다.

[기본소스]

  if(analogRead(AXIS_X)<=300){
    Serial.println('a');
  }
  else if(analogRead(AXIS_X)>=700){
    Serial.println('d');   
  }
  //Y축 방향값
  if(analogRead(AXIS_Y)<=300){
    Serial.println('w');      
  }
  else if(analogRead(AXIS_Y)>=700){
    Serial.println('s');
  }

X축과 Y축의 아날로그 신호값을 나눠서 X축 방향으로 300이하면 왼쪽(a)키이고 700이상은 오른쪽(d)키이다. Y축 방향으로 300이하면 위쪽(w)키이고 700이상이면 아래쪽(s) 키이다 라고 정의해 놓았으면 아두이노 마이크로에서 키보드 라이브러리로 이용하면 아래와 같이 코딩하면 됩니다.

  //X축 방향값
  if(AXIS_X<=300){
     Keyboard.press('a');
     delay(50);
  }
  else if(AXIS_X>=700){
     Keyboard.press('d');
     delay(50);
  }
  else{
    Keyboard.release('a');
    Keyboard.release('d');  
  }
  //Y축 방향값
  if(AXIS_Y<=300){
    Keyboard.press('w');
    delay(50);
  }
  else if(AXIS_Y>=700){
    Keyboard.press('s');
    delay(50);    
  }
  else{
    Keyboard.release('w');
    Keyboard.release('s');
  }

비슷한데 좀 차이가 있죠. 키고 눌름 press()함수를 키 누름을 표현하지만 조이스틱이 키누름 값을 갖지 않을 때에만 해제 release() 함수로 표현 했습니다. 왜! 이렇게 표현했을까요.

Keyboard.press('a');
delay(100);
Keyboard.release('a');

가 한쌍으로 키 클릭을 표현했었죠. 이렇게 하면 0.1초간 눌러지고 키가 해제해 버립니다. 즉 순간 찰라에 키가 누르고 해제되면 조이스틱의 조정 방향으로 움찍하다가 끝나버리게 됩니다. 지속성이 사라져버리는 것이죠. 그렇기 때문에 조정키가 계속 지속성을 조이스틱의 키 누름 상태를 계속 유지해야 합니다. 그러면 release()함수를 쓰지 말아야 합니다. 이때 해제 함수를 안쓰면 계속 특정 키가 눌러지니깐 이때 문제가 생기기 때문에 조이스틱 방향으로 움직일 때는 키 누름을 하고 움직이지 않을 때는 키 누름을 해제하게 코딩을 하면 되겠죠. else 이하문에서 키누름을 해제하는 함수를 표현하여 조정 안하고 있을 때 키누름의 문제가 해결 됩니다.

좀 코딩이 약간 길어 졌지만 이렇게 표현하면 조이스틱으로 좀 더 자연스럽게 조정이 됩니다. 이방법은 문득 떠오른 생각을 코딩한 거라 최근 일이 있어서 집중을 못하고 그냥 대충 표현한 코딩이네요. 이 방법이 아닌 더 좋은 방볍으로 조이스틱을 방향키로 조절을 해보세요.

2. 아두이노 조이스틱 게임 조정기 회로도


  • 준비물 : 조이스틱 1개, 스위치버턴 1개, LED 1개, 저항 220옴 1개, 스위치버턴 5개, 아두이노 마이크로
  • 내용 : 조이스틱 xAxis, yAxis축은 A0, A1 핀에 연결하고 방향스위치버턴은 3,4,5,6번에 연결하시오. 인터럽트스위치버턴은 2번에 연결하고 LED 7번에 연결하면 됩니다.


조이스틱은 X,Y축값만 사용합니다. 스위치버턴은 사용하지 않았습니다. 위 그림에서 스위치버턴중 노란전선색의 스위치버턴은 인터럽트스위치로 아두이노 조이스틱 게임 조정기의 사용 가능/불가를 나타내고 나머지 스위치는 스킬키버턴으로 사용됩니다. Green LED은 게임 조정기의 사용 가능/불가 상태를 시각적으로 표현는데 사용합니다.

3. 코딩



**설계 : **

  • 조이스틱 방향키(위쪽-w, 아래쪽-s, 왼쪽-a, 오른쪽-d)로 조정
  • 스킬키 4개의 키보드 키는 "u,i,o,p"로 조정(스위치버턴의 한계로 스킬장 전환은 못함)
  • 실험 게임(검사 모바일)

[검사모바일] - 아두이노 게임 조정기를 만들어 단순히 게임을 컨트롤 해보기 위해서 1회 실험 목적으로 사용함.


대충 가상 키들을 방향키와 스킬키로 나누어 배치해 놓았습니다.

다음으로는 코딩인데 스킬키는 스위치버턴으로 키보드 키값을 누름을 표현했던 아두이노 키보드 제어편의 내용을 그대로 적용하면 되고 조이스틱 방향키만 위에서 복습한 내용을 아두이노 키보드 제어편 소스를 합치면 됩니다.

종합해보면,

#include <Keyboard.h>

const byte interruptPin = 2;
boolean state = true;

const byte xAxis = A0;         //X axis
const byte yAxis = A1;         //Y axis
const byte Pin_U = 3;
const byte Pin_I = 4;
const byte Pin_O = 5;
const byte Pin_P = 6;
const int ledPin = 7;         // Mouse control LED 

void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(Pin_U, INPUT_PULLUP); 
  pinMode(Pin_I, INPUT_PULLUP);
  pinMode(Pin_O, INPUT_PULLUP);
  pinMode(Pin_P, INPUT_PULLUP);
  
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), exchange, FALLING);  //인터럽트 발생 시 호출
  
  Keyboard.begin();
}

void loop() {
  
  if(state==false){ //인터럽트스위치 버턴에 의해서 열림/닫힘
    int AXIS_X = analogRead(xAxis);
    int AXIS_Y = analogRead(yAxis);
     
  //X축 방향값
  if(AXIS_X<=300){
     Keyboard.press('a');
     delay(50);
  }
  else if(AXIS_X>=700){
     Keyboard.press('d');
     delay(50);
  }
  else{
    Keyboard.release('a');
    Keyboard.release('d');  
  }
  //Y축 방향값
  if(AXIS_Y<=300){
    Keyboard.press('w');
    delay(50);
  }
  else if(AXIS_Y>=700){
    Keyboard.press('s');
    delay(50);    
  }
  else{
    Keyboard.release('w');
    Keyboard.release('s');
  }
    
  //스킬키 제어
  if(digitalRead(Pin_U) == LOW){
    Keyboard.press('u');
    delay(100);
    Keyboard.release('u');
  }
  if(digitalRead(Pin_I) == LOW){
    Keyboard.press('i');
    delay(100);
    Keyboard.release('i');      
  }
  if(digitalRead(Pin_O) == LOW){
    Keyboard.press('o');
    delay(100);
    Keyboard.release('o');      
  }
  if(digitalRead(Pin_P) == LOW){
    Keyboard.press('p');
    delay(100);
    Keyboard.release('p');      
  }     
 }  
}
void exchange() { //인터럽트스위치버턴  이벤트
 state=!state;
 digitalWrite(ledPin, !state);
}

방향키 IF문과 스킬키 스위치버턴 IF문 두개만 자세히 보시면 그렇게 어렵지 않을꺼에요.

4. 결과



아래 실험영상은 우선 인터럽트 스위치 버턴은 게임조성기를 활성화 하는 버턴입니다. 불이 들어오면 게임조정기를 조정할 수 있고 불이 안들어오면 게임 조정기를 사용할 수 없습니다. 한손으로 촬영하고 한손으로 조정하다 보니 케릭터 방향조정 따로 스킬키 동작 따로 단순한 동작만 실험했네요. 감안하시고 대충 이런 느낌으로 아두이노 조정기로 게임을 조정할 수 있구나 정도로 이해하시면 됩니다.


실험하려고 잠깐 검사모바일 게임 케릭터를 하나 만들어서 테스트 해 봤네요.

마무리


며칠 바빠서 post를 작성 할 시간도 스팀잇 접속도 어려웠네요. 오늘도 겨우 짬내서 급하게 코딩을 만들고 실험을 했네요. 코딩해 놓은게 별로 맘에 들지 않네요. 원하는 스타일도 아니고 대충 느낌만 전달하고자 간단히 실험했습니다.


댓글()

[아두이노] 아두이노 조이스틱마우스 제어

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

[아두이노] 아두이노 조이스틱마우스 제어



지난 시간에 아두이노 마우스 제어를 스위치버턴으로 실험해 보았습니다. 스위치 버턴의 경우는 위/아래, 좌/우, 대각선 방향으로 선형 이동을 하게 됩니다. 곡선 이동은 스위치버턴으로 표현하기가 힘듭니다. 실제 마우스의 움직임을 보면 원형 방향을 회전을 하면 약간 곡선 모양으로 움직이는데 스위치버턴으로는 그 표현이 어렵습니다. 그래서, 좀 더 부르겁게 움직이게 하기 위해서 조이스틱을 이용하여 실험을 해보겠습니다. 아두이노 IDE의 USB 예제 중 "JoystickMouseControl"소스를 기반으로 실험을 하겠습니다.

조이스틱에는 xAxis, yAxis 값과 swPin(스위치버턴) 값을 출력합니다. 조이스틱의 x,y과 스위치버턴 이벤트만 적용하여 실험이 이루어집니다. 아래 사진의 3개의 파란버턴을 있는데 한개 버턴만 인터럽트스위치버턴으로 마우스 동작을 수행할 것인지 안할 것인지 결정하는 안정장치 버턴으로 활용합니다. 사실 스위치 버턴을 사진에 들어가 있지만 표현할 필요는 없습니다. 조이스틱이 버턴 한개가 내장 되어있으니깐 원래는 스위치 버턴이 필요 업습니다.


아두이노 마이크로에서 조이스틱을 제어해 볼까요.

1. 조이스틱 마우스 사전학습



조이스틱 마우스 예제인 "JoystickMouseControl" 설명이 잘 나와 있습니다. 사실 조이스틱으로 직접 제어하는 코딩을 만들어 보긴 했는데 위 사전학습의 예제가 너무 완벽해서 제가 만든 방식을 포기하게 되었네요.

[조이스틱 핵심 코딩] - 출처 : https://www.arduino.cc/en/Tutorial/JoystickMouseControl (코딩 일부분)

int readAxis(int Axis_Val) { 
  int reading = analogRead(Axis_Val); //해당 축핀의 아날로그 값을 읽음
  reading = map(reading, 0, 1023, 0, 12); //읽은 아날로그 신호를 0~12 사이의 값으로 변환
  int distance = reading - 6; //축값은 0~12인데 중심값이 6을 기준으로 +방향과 -방향값을 갖음
  if (abs(distance) < 3) { //+방향, -방향값이 3이하면 마우스 이동을 안하기 위해 0으로 초기화
    distance = 0;
  }
  return distance; //해당 축의 마우스 이동값을 반환
}

위 코딩에서 조이스틱이 특정 축(Axis_Val)의 기준으로 이동할 경우 0~1023일때 0~12값으로 변환하면 조이스틱의 움직임 값은 중심은 512가 6이 됩니다. 6을 기준으로 +방향과 -방향으로 나뉘게 됩니다. 중심값 6을 기준으로 얼마만큼 조이스틱이 움직였는가에 따라서 그 움직임을 마우스를 움직이게 합니다. 이때 움직임 값이 3이상 조이스틱이 움직여야지 움직임으로 인정을 하게 됩니다.

아래의 그림에서 노란색 원 모양의 영역이 조이스틱의 움직임을 불인정 구간이 됩니다. 조이스틱의 움직임을 인정하는 영역이 초록색이 됩니다.


왜! 이러헥 튜토리얼 예제에서는 표현했을까요. 실제 조이스틱은 전류를 측정하면 정확히 중심축(x,y)이 (512,512)가 되지 않습니다. 그래서 오차 중심축 값이 발생합니다. 그래서 오차의 영역이 되는 위의 원 영역의 부분은 움직임으로 판정을 내리지 않게 되면 오차값에 의한 움직임이 발생하지 않습니다. 그리고 튜토리얼에서는 0~12로 숫자의 범위를 최대한 줄였고 움직임 판정 받을 수 있는 영역도 6에서 3인 불인정이기 때문에 나머지 3의 영역만 인정을 받게 됩니다. 즉, 조이스틱의 크게 움직여야만 그 움직임을 인정받을 수 있게 됩니다. 각 1의 영역도 아날로그 신호값을 기준으로 85.25정도의 신호값을 1로 하기 때문에 오차가 발생하더라고 그렇게 크게 조이스틱 움직이에 영향을 주지 않습니다.

위 공식홈페이지에서 조이스틱 조정으로 측정되는 x,y축 값은 이보다 훌륭한 코딩은 없다고 생각되네요. 제가 만들어서 실험한 코딩은 위와 좀 비슷하지만 위 튜토리얼 코딩이 너무 훌륭해서 위 함수을 인용해서 실험하게 되었습니다.

2. 아두이노 조이스틱 마우스 회로도


  • 준비물 : 조이스틱 1개, 스위치버턴 1개, LED 1개, 저항 220옴 1개, 아두이노 마이크로
  • 내용 : 조이스틱 xAxis, yAxis축은 A0, A1 핀에 연결하고 swPin은 3번에 연결하시오. 인터럽트스위치버턴은 2번에 연결하고 LED 4번에 연결하면 됩니다.


조금 선 연결이 복잡해 보일 수 있는데 위의 내용부분의 핀 연결만 제대로 하시면 나머지 Vcc, Gnd 핀이기 때문에 어렵지 않을꺼에요.

3. 코딩



위 사이트에 가시면 "JoystickMouseControl" 예제에 대해서 설명이 나와 있습니다. 한번 읽어주시기 바랍니다.

[JoystickMouseControl 소스 인용] - 출처 : https://www.arduino.cc/en/Tutorial/JoystickMouseControl (아두이노공식홈페이지)

#include <Mouse.h>

const byte interruptPin = 2;
boolean state = true;

const byte xAxis = A0;         //X axis
const byte yAxis = A1;         //Y axis
const byte mouseButton = 3;  //LEFT BUTTON
const int ledPin = 4;         // Mouse control LED 

void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(mouseButton, INPUT_PULLUP);       
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), exchange, FALLING);  //인터럽트 발생 시 호출
  
  Mouse.begin();
}

void loop() {
  
  if(state==false){ //인터럽트스위치 버턴에 의해서 열림/닫힘
    int Axis_X = readAxis(xAxis); //x축 조이스틱 이동값
    int Axis_Y = readAxis(yAxis); //y축 조이스틱 이동값
        
    Mouse.move(Axis_X, Axis_Y, 0); //마우스 이동

    if (digitalRead(mouseButton) == LOW) { //마우스 스위치가 눌러졌는가
      if (!Mouse.isPressed(MOUSE_LEFT)) Mouse.press(MOUSE_LEFT);      
    }
    else if(Mouse.isPressed(MOUSE_LEFT) == 1){ //MOUSE_LEFT 상태인지 확인
        Mouse.release(MOUSE_LEFT);
    }
    delay(10);
  }
  
}

int readAxis(int Axis_Val) { 
  int reading = analogRead(Axis_Val); //해당 축핀의 아날로그 값을 읽음
  reading = map(reading, 0, 1023, 0, 12); //읽은 아날로그 신호를 0~12 사이의 값으로 변환
  int distance = reading - 6; //축값은 0~12인데 중심값이 6을 기준으로 +방향과 -방향값을 갖음
  if (abs(distance) < 3) { //+방향, -방향값이 3이하면 마우스 이동을 안하기 위해 0으로 초기화
    distance = 0;
  }
  return distance; //해당 축의 마우스 이동값을 반환
}

void exchange() { //인터럽트스위치버턴  이벤트
 state=!state;
 digitalWrite(ledPin, !state);
}

위 소스는 "JoystickMouseControl" 예제를 기반의 코딩입니다. 제가 코딩한 것이 아니라 아두이노 공식홈페이지의 소스를 기반으로 했기 때문에 인용 코딩으로 보시면 됩니다. 인용 부분은 readAxis(int Axis_Val) 함수입니다.

4. 결과


우선 웹문서에 켄버스에 영역을 지정하고 그 영역에 마우스가 움직여서 특정 위치를 마우스 왼쪽버턴을 클릭하게 클릭된 위치에 마우스 좌표를 출력하는 html를 만들었습니다. 자바스크립트로 만들었는데 이건 여러분들이 만드실 필요 없습니다. 동영상 촬영을 위한 체크용으로 간단히 만들어서 조이스틱 마우스를 움직임과 클릭 이벤트가 정상적으로 수행되는지 보기 위한 예제입니다.

그리고, 영상을 보시면 처음에 파란스위치 버턴을 클릭하면 초록색 불이 들어오는 것을 보실 수 있을 꺼에요. 인터럽트 스위치 버턴을 클릭 했을 때 초록색 LED에 불이 들어오고 이 상태는 조이스틱마우스를 제어할 수 있는 열림상태가 된다는 의미이고 초록색 불이 꺼져있으면 조이스틱마우스가 아무리 움직여도 PC 모니터에 움직임이 표시되지 않는 잠금상태가 된다고 생각하시면 됩니다.


화살표로 표시된 스위치버턴(인터럽트스위치)은 조이스틱마우스 사용을 제어하는 안정장치 버턴입니다. 열림/닫힘으로 조이스틱마우스를 사용을 결정하게 됩니다.

마우스 이동과 클릭 이벤트가 정상적으로 수행되는 것을 아래 동영상으로 확인하실 수 있을 꺼에요


마무리


제가 직접 자작한 코딩으로 보여주면 좋은데 튜토리얼 조이스틱 움직임 readAxis()함수 너무 훌륭해서 인용한 실험이 되고 말았네요. 키보드 였다면 간단히 직선적 움직임으로 제어하면 코딩이 쉽지만 마우스를 제어하기 때문에 곡선적 움직임을 제어하기 위해서는 인용한 저 함수가 딱인 것 같네요

아무튼 해당 예제를 기반으로 실험을 했지만 조이스틱마우스의 조정과 클릭 이벤트가 정상적으로 결과를 얻을 수 있었네요. 다음 실험을 조이스틱키보드로 해서 특정 게임을 직접 조정하는 실험을 해볼까 생각중이네요. 아직 어떤 게임을 아두이노 조이스틱으로 조정할지는 결정을 못했고 며칠 일이 생겨서 잠시 post 며칠 미루어 질 것 같네요.


댓글()

[온라인 도장] 문서에 도장 삽입하기

유용한팁|2019. 7. 1. 09:00


[온라인 도장] 문서에 도장 삽입하기



오늘은 문서에 도장을 삽입하기 위해 작업을 하다가 문득 도장 or 사인을 각 문서들에서 어떻게 등록이 되는지 정리하면 좋을 것 같아서 정리를 해 보았습니다.

1. 나만의 온라인 도장 만들기


온라인 무료 도장 사이트에 가시면 회원가입없이 자신이 원하는 이름의 전자 도장을 만들어 다운 받을 수 있습니다.

코딩맨이라는 이름으로 전자도장을 만들겠습니다.



만들어 다운 받으니깐 아래와 같이 이쁜 도장이미지가 만들어 졌네요.


보시면 배경 투명화까지 깔금하네요.

2. 문서에 도장 삽입하기


오늘의 핵심내용입니다. 위처럼 배경이 투명한 도장은 문서에 삽입하기 좋습니다. 하지만 실제 현실에서 쓰는 도장은 일일히 스캔을 떠서 배경색이 있는 원본 이미지에서 배경색을 투명화하는 작업이 필요 합니다. 여기서 문서의 투명화를 해주는 작업을 문서편집기에 있으면 좋은데 없으면 포토샵같은 곳에서 배경을 투명화 처리를 해야 합니다. 좀 번거롭겠죠.

아무튼 다양한 문서에서 도장을 삽입해 볼까요.

[한글 워드 도장 or 사인 배경 투명화]



  • 1단계 : 그림삽입->객체속성->위치 글 뒤로를 선택하시면 문서의 글 뒤로 가기때문에 투명화 작업을 따로 할 필요 없게 됩니다. 투명화 된 느낌으로 되는 것이죠.

[결과]


[MS 엑셀 도장 or 사인 배경 투명화]


  • 1단계 : 그림삽입->그림 서식->색->투명한색 설정->투명하게 할 색위치에서 마우스 클릭

보면 그림서식에서 투명한 색 설정으로 해당 위치에 마우스를 클릭하면 해당 위치의 색은 투명화로 바뀌게 됩니다.

[결과]


[MS 워드 도장 or 사인 배경 투명화]


  • 1단계 : 그림삽입->그림에서 마우스오른쪽클릭->크기및위치선택->텍스트배치->텍스트 앞 선택
  • 2단계 : 그림 서식->색->투명한색 설정->투명하게 할 색위치에서 마우스 클릭

단계 순서는 어떤 걸 먼저하든 상관 없습니다. 참고로 엑셀과 동일하기 때문에 투명화 과정 이미지는 생략합니다.

[결과]


3. 온라인 이미지 편집기로 투명화 도장 만들기




스캔으로 복사한 도장 이미지를 투명화 작업을 해 볼까요.

Wend tool로 이미지 영역을 선택합니다. 도장의 글자를 일일히 선택하면 좀 힘드니깐 배경 부분을 선택하시면 편하게 편집할 수 있습니다. 배경영역을 선택하기 위해서는 shift를 누른 상태에서 해당 영역을 선택하시면 됩니다. 그리고 나서 아래 이미지 처럼 invert select를 누르면 반전 영역이 선택됩니다. 배경영역을 처음에 선택했다면 배경이 아닌 도장의 글자 영역이 선택되는 것이죠.

그리고 copy를 누르고 새로운 이미지를 만들고 그곳에 복사하시면 됩니다.

이때 layer 배경체크를 풀면 배경이 날라가지요. 그리고 저장하시면 투명화 된 도장을 만들 수 있게 됩니다.

마무리


한글워드, MS워드, MS엑셀 등 자주 사용하는 문서에서 대부분 알고 계시고 쉽게 투명화 작업을 할 수 있습니다. 이 외에 자체 프로그램을 통해 문서가 만들어지는 곳에서는 이미지를 될 수 있으면 투명화 작업을 수행해야 합니다. 어제 도장을 그냥 스캔해서 찍었더니 배경 이미지가 남아서 지져분해지더군요. 간단히 이미지 편집 툴로 투명화 작업을 급하게 해봤는데 문득 이 내용을 다 정리해보면 어떠 할까 하는 생각에 한번 정리를 해 보았습니다.

댓글()

[아두이노] 아두이노 매크로 키보드 제어

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

[아두이노]아두이노 매크로 키보드 제어



지난 시간까지 해서 아두이노 기본 키보드/마우스 동작 명령을 실험을 해보았습니다. 오늘은 아두이노 IDE의 USB 예제에서 "KeyboardReprogram" 소스가 있는데 매크로 키보드 제어 예제로 딱 좋을 것 같아서 소개를 하도록 하겠습니다. 아두이노를 이용하여 PC에서 수행 할 명령을 기록해 놓았다가 기록 된 순서대로 명령을 내릴 수 있습니다. 이 원리를 이용하면 상상력에 따라 다양한 오토 매크로 프로그램을 만들 수 있습니다.

오늘 실험 할 내용을 "KeyboardReprogram" 예제는 동작은 현재 사용되는 보드에 새로운 프로그램을 스스로 업로드하는 시키는 예제입니다. 이 예제를 약간 변형시켜서 아두이노 마이크로에서 아두이노우노에 프로그램을 매크로를 통해서 이식하는 실험을 해보도록 하겠습니다.


아두이노 마이크로에서 매크로 키보드를 이용하여 아두이노우노에 프로그램을 이식 시키는 실험입니다.

1. 아두이노 매크로 키보드 회로도


  • 준비물 : 스위치버턴 3개, LED 2개, 저항 220옴 2개, 아두이노 마이크로, 아두이노우노
  • 내용 : 스위치버턴을 아두이노 마이크로 2,3,4번핀에 순서대로 연결하고 LED은 11, 12번에 연결하시오


아두이노 마이크로 2(인터럽트핀), 3(A코딩매크로스위치), 4(B코딩매크로스위치) 번 핀을 사용합니다.
아두이노우노 9(RedLED핀), 10(GreeenLED핀) 번 핀을 사용합니다.

2. 코딩



위 사이트에 가시면 "KeyboardReprogram" 예제에 대해서 설명이 나와 있습니다. 이 예제는 간단히 설명을 하면 스위치 버턴을 누르면 아두이노 IDE 창에서 새로운 창을 열고 거기에 특정 아두이노 소스를 코딩하고 코딩된 명령을 현재 보드에 새롭게 업로드하게 됩니다. 즉, 현재 아두이노보드에 인식된 프로그렘에서 다시 새로운 프로그램이 이식되도록 하는 명령을 내린다고 이해하시면 됩니다. 한번 위 사이트에 가셔서 어떻게 코딩되어 있는지 살펴보시기 바랍니다.

본격적으로, "KeyboardReprogram" 예제 소스를 기반으로 설명합니다. 전체소스는 위 공식홈페이지에 가셔서 예제를 보시기 바랍니다. 좋은 예제여서 새롭게 뭘 만들기 보다는 이 예제로 자세히 설명하는 위주로 우선 이야기 하겠습니다.

"KeyboardReprogram" 예제 소스를 보면아두이노에서 새로운 프로그램을 이식하는 매크로 코딩을 하면 다음과 같습니다.

  • 아두이노 새창 띄우기
  • 아두이노 새창에 코딩 지우기
  • 아두이노 매크로 이식 코딩
  • 자동 포맷
  • 업로드(추가 저장하기)

1) 아두이노 IDE 새창 띄우기


현재 아두이노 IDE 창이 있다면 아래 명령을 수행하면 새로운 아두이노 IDE 편집창이 뜹니다.

[Ctrl+n(새창)]

  Keyboard.press(KEY_LEFT_CTRL);
  Keyboard.press('n');
  delay(100);
  Keyboard.releaseAll();  
  delay(1000);

2) 아두이노 새창에 코딩 지우기


새로운 창에는 기본 아두이노 코딩이 되어 있습니다. 이 코딩은 지우셔야 합니다. 지우기 위해서 코딩 전체 영역을 지정하기 위해서 "Ctrl+a"키를 누르면 새로운 창에서 전체 영역을 지정합니다. 그리고 나서 KEY_BACKSPACE 키를 누르면 전체 지정된 영역은 삭제 됩니다. 이 과정을 코딩으로 표현하면 아래와 같습니다.

[Ctrl+a 키 동작(창안 전체 영역 지정)]

  Keyboard.press(KEY_LEFT_CTRL);
  Keyboard.press('a');
  delay(500);
  Keyboard.releaseAll();

[지우기]

  Keyboard.write(KEY_BACKSPACE);
  delay(500);

3) 아두이노 매크로 이식 코딩


간단한 프로그램을 이식하는 예제로는 blink만한게 없죠. 그 예제로 하번 코딩해 볼까요.

[blink소스]

void setup(){
  pinMode(10, OUTPUT);
}
void loop(){
  digitalWrite(10, HIGH);
  delay(1000);
  digitalWrite(10, LOW);
  delay(1000);
}

blink 코딩 소스를 아두이노 이식 시키는 매크로 코딩을 하면 아래 와 같습니다.

Keyboard.println("void setup() {");
Keyboard.println("pinMode(10, OUTPUT);");
Keyboard.println("}");
Keyboard.println();
Keyboard.println("void loop() {");
Keyboard.println("digitalWrite(10, HIGH);");
Keyboard.print("delay(1000);");
Keyboard.println("digitalWrite(10, LOW);");
Keyboard.print("delay(1000);");
Keyboard.println("}");

어렵지 않죠. 아두이노 IDE 편집창에서 실제 키보드로 치는 것처럼 위 코딩을 치면 됩니다.

4) 자동 포맷


[Ctral+t(자동포맷)]

Keyboard.press(KEY_LEFT_CTRL);
Keyboard.press('t');
delay(100);
Keyboard.releaseAll();
delay(3000);

5) 업로드(추가 저장하기)


아두이노 IDE에서 업로드 단축키를 누르면 저장창이 뜹니다. 이때 랜덤이름으로 KEY_RETURN키로 저장하고 계속 진행하게 한다면 아래와 같이 코딩하면 됩니다.

[Ctrl+u(업로드)]

Keyboard.press(KEY_LEFT_CTRL);
Keyboard.press('u');    
delay(100);
Keyboard.releaseAll();
delay(1000);

Keyboard.press(KEY_RETURN);
delay(100);
Keyboard.releaseAll();

만약, 저장하기를 안하고 계속 진행할려면 저장하기 창에서 KEY_ESC키를 누르시면 해당 창이 취소됩니다. 그리고 나서 계속 업로드가 진행이 이루어 집니다.

Keyboard.press(KEY_ESC);
delay(100);
Keyboard.releaseAll();

6) 아두이노 마이크로에서 아두이노 우노에 blink 코딩 업로드 시키기


위 "KeyboardReprogram" 예제 소스의 원리를 이해 하셨으면 약간 변형하여 아두이노 마이크로에서 아두이노 우노에 두개의 프로그램을 업로드 시키는 과정을 매크로 코딩으로 만들어 실험을 해봅시다. 이식 할 프로그램은 blink 예제 소스로 두가지 형태의 프로그램을 이식 하겠습니다.

  • A 코딩 : Red LED를 1초 단위로 깜박이게 하기
  • B 코딩 : Green LED를 1초 단위로 깜박이게 하기

기본 과정은 위에서 코딩 설명한 과정을 그대로 적용합니다.

  • 아두이노 새창 띄우기
  • 아두이노 새창에 코딩 지우기
  • 아두이노 매크로 이식 코딩
  • 자동 포맷
  • 업로드 & 저장취소

여기서 새창에 매크로 이식 코딩을 A 상황과 B상황으로 코딩이 이루어지게 할 예정입니다. 즉, Pin_A, Pin_B 스위치를 클릭하면 A와 B blink 코딩이 아두이노우노에 업로드 되고 해당 9(Red), 10(Green) LED가 깜박이게 하기 위해서 매크로 이식 코딩을 외부 사용자 정의함수로 빼서 해당 핀값만 수정하여 코딩하도록 만들어 보았습니다.

기본 코딩 과정 설명은 위에서 설명을 했기 때문에 종합한 소스를 위 코딩한 내용을 기반으로 살펴보시기 바립니다.

#include "Keyboard.h"

const byte Pin_A = 3;
const byte Pin_B = 4;

const byte interruptPin = 2;
boolean state = true;

void setup() {
  pinMode(Pin_A, INPUT_PULLUP);
  pinMode(Pin_B, INPUT_PULLUP);
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), exchange, FALLING);

  Keyboard.begin();
}

void loop() {
  if (state == false) { //키보드 안전장치
    if (digitalRead(Pin_A) == LOW) { //A코딩 이식 명령
      delay(1000);
      programming(9); //제어핀은 9번으로 blink 코딩
      delay(5000);
    }
    if (digitalRead(Pin_B) == LOW) { //B코딩 이식 명령
      delay(1000);
      programming(10);      //제어핀은 10번으로 blink 코딩
      delay(5000);
    }
  }
}

//매크로 이식 코딩
void programming(int Pin_Val) {
  //Ctrl+N 새창 열기
  Keyboard.press(KEY_LEFT_CTRL);
  Keyboard.press('n');
  delay(100);
  Keyboard.releaseAll();
  delay(1000);
  
  //Ctrl+A 키 동작(코딩창 안의 전체 영역 지정)
  Keyboard.press(KEY_LEFT_CTRL);
  Keyboard.press('a');
  delay(500);
  Keyboard.releaseAll();

  //지우기
  Keyboard.write(KEY_BACKSPACE);
  delay(500);

  //이식 할 코딩
  Keyboard.println("void setup() {");
  Keyboard.print("pinMode(");
  Keyboard.print(Pin_Val);
  Keyboard.println(",OUTPUT);");
  Keyboard.println("}");
  Keyboard.println();
  Keyboard.println("void loop() {");
  Keyboard.print("digitalWrite(");
  Keyboard.print(Pin_Val);
  Keyboard.println(",HIGH);");
  Keyboard.print("delay(1000);");
  Keyboard.print("digitalWrite(");
  Keyboard.print(Pin_Val);
  Keyboard.println(",LOW);");
  Keyboard.print("delay(1000);");
  Keyboard.println("}");

  //자동포맷
  Keyboard.press(KEY_LEFT_CTRL);
  Keyboard.press('t');
  delay(100);
  Keyboard.releaseAll();
  delay(3000);
  
  //업로드
  Keyboard.press(KEY_LEFT_CTRL);
  Keyboard.press('u');
  delay(100);
  Keyboard.releaseAll();
 
  delay(500);
  Keyboard.press(KEY_ESC); //저장취소
  delay(100);
  Keyboard.releaseAll();
}
void exchange() {
  state = !state;
}

보시면 programming(9), programming(10)으로 해서 Pin값만 바꿔서 코딩을 하고 해당 코딩을 아두이노 우노에 이식하게 됩니다.

4. 결과


결과는 좀 복잡한 과정을 거칩니다. 왜냐면 위 코딩은 우선 아두이노 마이크로 이식합니다.


위와 같이 보드/포트를 지정하고 아두이노 마이크로에 이식을 먼저 한 다음에 아두이노우노에 프로그램을 이식하기 위해서는 아두이노 IDE 창에 보드/포트를 아두이노우노 보드에 맞춰서 미리 세팅을 해놔야 합니다.


1) A 코딩



A버턴을 누르면


자세히 보시면 핀 번호가 9번이 보일꺼에요.

2) B 코딩



B버턴을 누르면


자세히 보시면 핀 번호가 10번이 보일꺼에요.

3) 결과


A, B 버턴을 누르고 해당 매크로 코딩 이식과정을 PC 창에서 진행 되는 모습을 보시면 한줄로 쭉 코딩이 되어지네요. println()함수를 치면 다음라인으로 넘어 갈 줄 알았는데 다음 라인으로 커서가 넘어가지 않더군요.

억지로 한줄 끝날때마다

  Keyboard.press(KEY_RETURN);
  delay(100);
  Keyboard.releaseAll();

이 명령으로 커서를 강제로 넘길 수 있지만 그러면 코딩이 너무 길어지기 때문에 그리고 어짜피 가독성이 떨어지게 코딩 한다고 해서 이식이 안되는 것도 아니기 때문에 그냥 억지로 다음라인으로 넘기는 코딩은 하지 않았습니다.

그리고, 아두이노 IDE가 매번 클릭 할 때마다 새로운 창이 뜹니다. 이부분도 사실 이식이 끝나면 해당 코딩창을 "Ctrl+q"로 창 닫기를 누르면 되는데 이 부분도 생략했네요. 이 부분을 하려면 프로그램이 어느정도 정상적으로 이식과정이 끝났다는 것을 확인이 필요합니다. 그냥 대충 창 종료를 누르면 업로드 중간에 창이 닫히기 때문에 오류 발생을 줄이고 간단히 특정 동작을 실험하는게 목적이기 때문에 여러가지 문제 상황에 대해서 정교하게 코딩은 하지 않았습니다. 기본 의미 전달이 목적이기 때문입니다.단순히 아두이노 마이크로에서 아두이노우노에 새롭운 프로그램을 이식 시킬 수 있는지 보기 위한 실험이기 때문에 복잡한 코딩은 생략합니다.

아래 동영상에서 3개의 버턴 중 하나는 인터럽트 버턴으로 키보드 동작 명령에 에러가 발생할 때 중지할 수 있는 버턴으로 활용하고 나머지 2개의 버턴은 A코딩 이식과 B코딩 이식하는 과정의 매크로 명령을 수행합니다. Red LED가 1초 단위로 깜박이는 코딩과 Green LED가 1초 단위로 깜박이는 코딩인데 실제로 스위치 버턴을 누를 때 아두이노우노에 프로그램을 이식하는데 이식이 정상적으로 되었는지 어떤 LED가 깜박이는지 보시면 눈으로 확인 하실 수 있을꺼에요. PC랑 실제 스위치를 누르는 장면을 같이 담아야 하는데 폰이 구려서 같이 찍으면 PC 모니터의 진행 과정이 흐리게 나오기 때문에 그냥 LED의 변화의 모습으로 아두이노우노에 코딩이식이 이루어지는지 확인하실 수 있습니다.


마무리


아두이노 마이크로에서 아두이노우노에 프로그램을 이식시키는 과정을 실험 했습니다. 참고로, "KeyboardReprogram" 예제 소스는 자기 자신에 새로운 프로그램을 이식하는 예제입니다. 자기 자신의 보드에 프로그램을 이식 원리와 다른 보드에 프로그램을 이식시키는 원리를 이번 시간에 제대로 이해하셨으면 합니다.

자기 자신에 새로운 프로그램을 이식하는 경우는 어떤 특정한 프로그램을 아두이노 마이크로 업로드 했을 때 프로그램이 어떤 동작에 약간 오차값이나 보정이 필요할 경우 지금까지는 변수를 만들어 넣고 그 변수의 보정값을 저장하여 다음 동작을 수행했습니다. 매번 전원이 새롭게 공급할 때마다 이 과정을 반복합니다. 그런데 자기 자신에 새로운 프로그램을 이식할 수 있다면 처음 한번만 조정이 되고 그 값을 기준으로 새롭게 프로그램을 스스로 재이식할 수 있게 되면은 해당 아두이노보드는 스스로 진화하게 됩니다. 여기서 아두이노 IDE로 프로그램을 업로드 했지만 따로 컴파일과 이식하는 과정을 아두이노 IDE를 통하지 않고 직접 수행 할 수 있는 방법을 찾아 낸다면 직접 스스로 새로운 프로그램으로 업로드하여 아두이노가 스스로 업데이트 할 수 있게 되겠죠.

다른보드에 프로그램을 이식하는 경우는 새로운 보드에 원하는 형태로 프로그램을 자동으로 A보드는 A코딩을 B보드는 B코딩을 이식할 수 있게 됩니다. 학습할 수 있는 아두이노라면 학습을 통해서 원하는 방향으로 다른 보드에 프로그램을 이식시킬 수 있게 된다면 상상을 해보세요. 위 두 과정을 상상하면 약간 테미네이트 영화처럼 되지 않을가 싶네요. 기계가 다른 기계에 프로그램을 이식하는 것은 엄청난 것입니다.

아무튼 오늘은 매크로 코딩 이식과정을 실험했지만 꽤 중요하고 상상을 하기에 따라 엄청난 결과물을 만들어 낼 수도 있습니다. 한번 상상의 나래를 펼쳐서 자신의 상상력이 어디까지 바라볼 수 있는지 도전해 보세요.


댓글()

[아두이노] 아두이노 마우스 제어

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

[아두이노] 아두이노 마우스 제어



지난 시간에 아두이노 마이크로 보드로 키보드 제어를 해 보았습니다. 오늘은 마우스를 제어해보는 시간을 갖도록 하죠. 키보드와 마찬가지로 마우스도 몇개의 함수만 이해하시면 쉽게 아두이노를 이용해서 마우스의 동작을 수행할 수 있습니다.

이제 본격적으로 마우스 제어를 해볼까요.


1. 아두이노 키보드 제어



위 사전학습으로 읽고 오시면 됩니다. 사전학습 자료를 보시면 7개의 함수가 있는데 이 함수만 알면 마우스 제어를 마음대로 할 수 있습니다. 간단히 함수에 대해 살펴 볼까요.

  • button : MOUSE_LEFT (default), MOUSE_RIGHT, MOUSE_MIDDLE
  • 마우스 시작 : Mouse.begin()
  • 마우스 클릭 : Mouse.click() or Mouse.click(button)
  • 마우스 종료 : Mouse.end()
  • 마우스 이동 : Mouse.move(xVal, yPos, wheel) - x축, 축, 스크롤 휠의 움직임 양
  • 마우스 버턴 클릭 : Mouse.press() or Mouse.press(button)
  • 마우스 버턴 해제 : Mouse.release() or Mouse.release(button)
  • 현재 마우스가 눌러진 정보 반환 : Mouse.isPressed() or Mouse.isPressed(button)

함수도 그렇게 어렵지 않죠. 기본적으로 마우스 클릭과 마우스 이동에 관한 함수만 이해하시면 됩니다.

1) 마우스 이동 제어


마우스 이동 제어하는 방법은 다음과 같습니다.

[기본소스]

#include <Mouse.h>

void setup(){
  Mouse.begin();
}
void loop(){
  if(버턴누름){
    Mouse.move(x, y, 스크롤휠);
  }
}

위 소스가 기본 마우스 이동 동작입니다. 마우스로 위로 이동한다면 어떻게 코딩할까요.

if(digitalRead(Pin_Up) == LOW){ //위로
  Mouse.move(0, -1, 0);
}

참골, 위/아래, 좌/우로 움직임을 x,y값만 바꾸시면 되고, 위로 이동하는 위 명령문을 수정하여 코딩하시면 나머지 동작 명령을 수행할 수 있게 됩니다.

2) 마우스 클릭 제어


[기본소스]

#include <Mouse.h>

void setup(){
  Mouse.begin();
}

void loop(){ 
  if(버턴누름){
    Mouse.click();
  }
}

기본 보스를 보면 Mouse.click()함수는 디폴트로 MOUSE_LEFT 클릭이 됩니다. 특정 키를 누르고 싶다면 어떻게 코딩할까요.

Mouse.click(MOUSE_RIGHT);

어떤 느낌인지 아시겠지요.

  • button : MOUSE_LEFT (default), MOUSE_RIGHT, MOUSE_MIDDLE

기본적으로 버턴은 위의 세가지 왼쪽, 오른쪽, 중앙 버턴으로 나뉘고 지정해주면 해당 버턴이 클릭됩니다. 지정하지 않으면 디폴트로 왼쪽 버턴을 클릭하신다고 보시면 됩니다.

추가로, Mouse.click()함수는 클릭 이벤트입니다. 다른 방식으로 표현하면은 다음과 같습니다.

void loop(){ 
  if(버턴누름){
    Mouse.press(MOUSE_LEFT);
    delay(100);
    Mouse.release(MOUSE_LEFT);
    delay(200);
  }
}

마우스 키를 누름과 해제로 마우스 클릭 이벤트를 만들어 냅니다. 이와 같은 방식은 사실 클릭함수만 있으면 되지 구지 이 함수로 표현을 하냐 하실분들이 있을거에요. 누름(press)과 해제(release) 함수는 사용되는 곳이 따로 있습니다. 가령, 웹페이지가 있으면 특정 영역을 복사하기 위해서 어떻게 하나요 마우슨 왼쪽을 누른 상태에서 드래그를 하고 복사할 위치에서 마우스 누른 상태를 해제합니다. 이렇게 마우스의 클릭이 아니라 일정시간 마우스의 누름이 필요할 때는 사용되는 함수입니다. 클릭으로 사용해도 되고 또는 특정한 누름 일정시간 동안 유지가 필요할 때도 사용됩니다.

  if(버턴누름){
    Mouse.press(MOUSE_LEFT);        
  }else if(버턴 해제 && Mouse.isPressed()==1){
    Mouse.release(MOUSE_LEFT);
  } 

버턴을 누르고 있는 동안은 마우스 왼쪽 버턴이 클릭된 상태가 되겠죠. 이방식은 계속 클릭 명령을 내리기 때문에 별로 좋지 않습니다. 한 상태를 계속 유지하고 클릭을 반복 명령을 내리게 하고 싶지 않다면 아래와 같이 변경해야 합니다.

 if(버턴누름){
    if( Mouse.isPressed()==0) Mouse.press(MOUSE_LEFT);      
  }else if(버턴 해제 && Mouse.isPressed()==1){
    Mouse.release(MOUSE_LEFT);
  } 

이런게 마우스 클릭 상태정보가 0이면 클릭상태가 아니기 때문에 마우스왼쪽 버턴을 클릭하고 클릭상태가 1이면 클릭되어 있으니깐 계속 그 상태를 유지되고 있으니 구지 다시 클릭명령을 내릴 필요가 없습니다.

3) 마우스 상태 정보 반환


Mouse.isPressed() or Mouse.isPressed(button) 함수로 현재 마우스 상태 정보를 읽어 올 수 있습니다. 만약에 마우스 왼쪽 버턴을 눌렀다면 왼쪽버턴을 누른 상태 정보를 반환해 오겠죠.

정확히 어떤 값을 갖는지 살펴볼까요.

if(digitalRead(버턴핀) == LOW){ //버턴클릭
  Mouse.press(MOUSE_LEFT);
  Serial.println(Mouse.isPressed());
  delay(100);
  Mouse.release(MOUSE_LEFT);
  Serial.println(Mouse.isPressed());
  delay(200);     
}

[결과]

1
0

이렇게 출력됩니다 마우스를 press() 클릭하면 isPressed()함수로 해당 클릭 상태 정보 1을 반환합니다. 그리고 다시 release()함수로 해제하니깐 isPressed()함수로 반환되는 값은 0이 됩니다.

마우스 상태 정보 반환하는 함수는 해당 마우스버턴을 현재 클릭 정보를 통해서 다른 동작 명령을 다른 동작을 수행하거나 락을 걸어주거나 어떤 조건을 만들어 주기위해서 사용합니다. 위의 마우스 클릭 처럼 버턴을 클릭했다면 클릭했을 때 상태정보가 1이면 다음 클릭에서도 그 상태를 유지하니깐 구지 두번 명령을 내릴 필요가 없게 표현이 가능합니다. 그리고 어떤 클릭된 상태가 되었을 때 그 상태를 해제하고 싶을 때 먼저 클릭이 되었는지 확인하는 목적으로 사용됩니다. 클릭도 안했는데 클릭해제를 수행할 필요는 없겠죠.

2. 아두이노 키보드 제어 회로도


  • 준비물 : 스위치버턴 5개, 아두이노 마이크로
  • 내용 : 스위치버턴을 아두이노 마이크로 2,3,4,5,6번핀에 순서대로 연결하시오.
  • 참고 : [아두이노] 아두이노 키보드 제어 회로도


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

3. 코딩


스위치 버턴은 5개 입니다. 실험에서 2번 핀은 인터럽트 핀으로 사용하여 안전장치를 마련하고 나머지 3,4,5,6번 핀은 마우스 위치를 조절하는 핀으로 사용하겠습니다. 아쉬운 점은 스위치 버턴이 더 없어서 마우스클릭 이벤트까지는 적용을 못하였네요. 인터럽트 핀을 마우스 클릭 이벤트로 사용하면 좋은데 혹시 모를 에러를 예방하는 차원으로 안전장치가 필요하기 때문에 마우스 클릭 이벤트는 실험하지 않겠습니다.

1) 키보드 안전장치


지난 시간의 소스를 그대로 가져 왔네요. 복습차원으로 다시 봐 주시기 바랍니다.
[기본소스]

const byte interruptPin = 2;
boolean state = true;

void setup() {   
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), exchange, FALLING);  
}
void loop(){
  if(state==false){
    
    키보드 명령;
    
    }
}
void exchange() {
 state=!state;
}

위 코딩의 설명은 지난 시간 post를 보시기 바랍니다.

2) 마우스 이동 실험


  • Mouse.move(x, y, 휠) 함수로 마우스를 직접 움직이는 실험을 해 보겠습니다.

핀번호을 선언 합니다.

const byte Pin_Up = 3;
const byte Pin_Down = 4;
const byte Pin_Left = 5;
const byte Pin_Right = 6;

스위치 버턴 모두 내부풀업모드 방식을 사용하니깐 pinMode()은 다음과 같이 INPUT_PULLUP 모드로 선언 합니다.

 pinMode(Pin_Up, INPUT_PULLUP); 
 pinMode(Pin_Down, INPUT_PULLUP);
 pinMode(Pin_Left, INPUT_PULLUP);
 pinMode(Pin_Right, INPUT_PULLUP);

Pin_Up키 누름 이벤트 명령은 다음과 같습니다. 내부풀업모드로 초기상태가 HIGH 입니다. 그래서 스위치를 누르면 LOW로 바뀌게 되고 IF문이 참이 되어 키 누름과 해제 명령을 수행하게 됩니다.

 if(digitalRead(Pin_A) == LOW){
    Mouse.move(0, -1, 0);
 }

나머지 스위치 버턴도 위와 같이 동일하게 코딩하면 됩니다.

3) 종합소스


위 안전장치 코딩과 키 누름 코딩을 합쳐서 종합해 보면 아래와 같이 코딩을 할 수 있습니다.

#include <Mouse.h>

const byte Pin_Up = 3;
const byte Pin_Down = 4;
const byte Pin_Left = 5;
const byte Pin_Right = 6;

const byte interruptPin = 2;
boolean state = true;

void setup() {    
  pinMode(Pin_Up, INPUT_PULLUP); 
  pinMode(Pin_Down, INPUT_PULLUP);
  pinMode(Pin_Left, INPUT_PULLUP);
  pinMode(Pin_Right, INPUT_PULLUP);

  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), exchange, FALLING);  

   Mouse.begin();
}

void loop() {
  if(state==true){
    if(digitalRead(Pin_Up) == LOW){ //위로
       Mouse.move(0, -1, 0);
    }else if(digitalRead(Pin_Down) == LOW){ //아래로
       Mouse.move(0, 1, 0);
    }
    if(digitalRead(Pin_Left) == LOW){ //왼쪽
       Mouse.move(-1, 0, 0);
    }else if(digitalRead(Pin_Right) == LOW){ //오른쪽
       Mouse.move(1, 0, 0);
    }   
  } 
}
void exchange() {
 state=!state;
}

스위치 버턴을 누름으로써 마우스의 해당 방향으로 움직이게 됩니다.

여기서, if문의 표현을 잘 봐주세요.

if문이 위/아래를 한쌍으로 묶었고 좌/우를 한쌍으로 묶었습니다. 왜 이렇게 했을까요. 바로 위를 누른 상태에서 아래를 누를 수 없고 좌를 누른 상태에서 우를 누를 수 없잖아요. 즉, 위/아래는 하나의 상태만 존재하고 좌/우는 하나의 상태만이 존재하기 때문에 위와 같이 IF 조건문으로 표현한 것이죠.

주의할점은, 4개의 상태를 if문으로 4개를 개별적으로 만들면 안됩니다.

예)

 if(digitalRead(Pin_Up) == LOW) {  }
 if(digitalRead(Pin_Down) == LOW) {  }

이렇게 코딩하면 안됩니다. 버턴을 동시에 누른다면 어떤 현상이 발생할까요. 두가지 상태를 모두 수행하기 때문에 어떤 의미로는 충돌에 가깝다고 보시면 됩니다. 그렇다고 해서 위 코딩이 동작하지 않는 것은 아닙니다. 의미상으로 코딩의 문제가 있는 것이죠. 어느시점에 오로지 딱 하나의 조건만이 있어야지 위/아래가 동시에 일어나는 것은 좀 문제가 있겠죠.

그리고, 위/아래의 하나의 상태와 좌/우의 하나의 상태는 동시에 일어날 수 있기 때문에 if문으로 분리해 줘야 합니다.

왜! 위에서 IF문을 저렇게 표현했는지에 대한 의미를 정확히 이해해 주세요.

이 코딩은 단지 동작을 테스트 할 목적의 코딩이기 때문에 복잡한 동작 명령은 생략합니다.

4. 결과



스위치버턴은 말풍선의 순서대로 해당 키보드 키명령을 가지고 있습니다. 해당 스위치 버턴을 해당 명령을 PC에서 수행합니다.

결과는 아래 동영상에서 확인하시면 됩니다.


영상을 보시면 처음 업로드 키를 눌러야 하는데 컴파일 키가 눌러졌네요. 이미 해당 코딩이 업로드 된 상태입니다. 업로드 부분부터 촬영을 하려고 하다 보니깐 실수가 발생했네요. 업로드가 이미 된 상태이다 보니깐 업로드 버턴이 아닌 컴파일 버턴을 누르고 영상을 계속 진행시켜 버렸네요. 재촬영하기 귀찮아서 그냥 올렸네요. 감안하시고 보세요. 참고로 한손에는 폰이 한손에는 마우스조정기가 있다 보니깐 대각선 움직임을 제대로 보여드리지 못했네요. 위/아래, 좌/우로만 움직였는데 실제로 조정하면 대각선으로도 움직입니다.

마무리


간단히 마우스 이동만 시켰네요. 키 누름 이벤트까지 3개를 만들고, 마우스 휠을 움직이는 명령까지 수행하게 했다면 완벽한 마우스가 완성되었겠죠. 약간 아쉬운 실험이 되었네요.

Mouse 라이브러리를 이용하니깐 Mouse.move()함수로 쉽게 PC 마우스를 움직일 수 있게 되었습니다. 이처럼 아두이노는 오픈소스로 많은 것들을 제공해주고 우리들은 제공된 라이브러리를 이용하여 쉽게 아두이노를 제어할 수 있습니다. 여러분들에게 필요한 것은 단지 뭘 만들고 싶은가의 상상만 필요할 뿐이죠.

여러분들은 지난시간에 배운 키보드 제어와 오늘 배운 마우스 제어를 통해 뭘 만들고 싶으신가요. 한번 상상의 나래를 펼쳐 보세요.


댓글()

[아두이노] 아두이노 키보드 제어

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

[아두이노] 아두이노 키보드 제어



지난 시간에 아두이노 마이크로 보드를 간단히 테스트를 했으니 이제 본격적으로 키보드 제어를 실험해 봅시다. 아두이도 키보드/마우스 제어를 하기 위한 목적으로 아두이노 마이크로 보드를 구매 했습니다. 오늘은 키보드 제어에 대해서 이야기를 할까 합니다. 프로그램언어를 공부하신분들은 오토키보드, 오토마우스 프로그램을 한번쯤을 사용해 보셨을 꺼에요. 게임을 할 때 오토 매크로 프로그램으로 게임을 자동사냥에 이용을 해보셨던 분도 아마 있을 꺼에요. C언어에서 키관련 핸들러를 이용해서 오토매크로 프로그램을 만들 수 있습니다. 이걸 코딩하기 위해서는 좀 코딩하기도 힘들고 복잡합니다. 저도 예전에 학창시절에 한번 코딩해보고 이제는 코딩 기억도 안나는 그런 것들이 있었지 정도에 기억만 남았네요.

그런데 아두이노에서 키보드 제어를 무지 쉽게 할 수 있는 방법을 제공합니다. 키보드 라이브러리가 오픈소스로 제공되고 단지 여러분들은 함수 몇개만 알고 있으면 쉽게 키보드 제어를 할 수 있습니다. 키보드 제어 코딩이 쉽지 않는데 이렇게 비전공자들도 쉽게 제어할 수 있는 방법을 제공하니 진짜 아두이노는 못하는 것이 없는 만능 싱글보드라고 생각됩니다.

이제 본격적으로 키보드 제어를 해볼까요.


1. 아두이노 키보드 제어



위 사전학습으로 읽고 오시면 됩니다. 사전학습 자료를 보시면 8개의 함수가 있는데 이 함수만 알면 키보드 제어를 마음대로 할 수 있습니다. 간단히 함수에 대해 살펴 볼까요.

  • 키보드 시작 : Keyboard.begin()
  • 키보드 종료 : Keyboard.end()
  • 키 누름 : Keyboard.press()
  • 키 해제 : Keyboard.release()
  • 키 전체 해제 : Keyboard.releaseAll()
  • 키 문자열 출력 : Keyboard.print(), Keyboard.println() , Keyboard.write()

함수도 그렇게 어렵지 않죠. 기본적으로 3개의 함수만 알고 있으면 키보드 키를 마음대로 제어할 수 있습니다.

1) 키 제어


키보드의 키를 제어하는 방법을 살펴보도록 하죠.

[기본소스]

#include <Keyboard.h>

void setup(){
 Keyboard.begin();
}
void loop(){
  if(버턴누름){
    Keyboard.press('a'); //키 누름
    delay(100);
    Keyboard.releaseAll(); //키 해제
    delay(200);     
  }
}

위 소스가 기본 키보드 누르는 동작입니다. press('a')함수는 'a'키 누르는 명령입니다. 이렇게만 코딩하면 a키가 눌려 있는 상태가 됩니다. 키누를 눌렀으면 때는 동작을 해야 겠죠. releaseAll()함수로 현재 눌려진 키들을 전부 해제하는 명령입니다.

즉, "ctrl+n" 키를 누를때 사용하면 좋겠죠.

Keyboard.press(KEY_LEFT_CTRL);
Keyboard.press('n');
delay(100);
Keyboard.releaseAll(); //키 해제

어떤 느낌이신지 아시겠지요.

2) 문자열 제어


아두이노는 미리 만든 문자열을 키보드로 친것과 같이 한번에 출력할 수 있습니다. 느낌으로 표현하자면 Ctrl+V의 느낌이라고 생각하시면 됩니다. 키보드 커서가 있는 위치에 아두이노에 저장된 문자열이 Ctrl+V를 누른 것 같이 문자열을 해당 커서 위치에 출력하게 됩니다.

Keyboard.println("STEEMIT!");

[결과]


어떤 느낌인지 아시겠지요.

3) 키보드 키값



위 github에 가시면 키보드 키값을 확인하실 수 있습니다. 아니면 아두이노 IDE 라이브러리 폴더에 가셔서 Keyboard.h 파일을 열어보셔서 됩니다.

열어보시면 키보드 키들이 정의 되어 있는데 아래 그림과 같습니다.


위 그림은 알파벳을 제외한 그외 일부 키들에 대한 부분입니다. 알파벳은 그냥 알파벳을 쓰면 되고 나머지 키들은 위에서 정의한 이름으로 키를 사용하시면 됩니다.

2. 아두이노 키보드 제어 회로도


  • 준비물 : 스위치버턴 5개, 아두이노 마이크로
  • 내용 : 스위치버턴을 아두이노 마이크로 2,3,4,5,6번핀에 순서대로 연결하시오.

지난시간에 보여준 아래 그림은 다시 복습차원으로 보시기 바랍니다.



아두이노 마이크로 핀 연결할 때 핀 번호를 위에 복습차원으로 올린 아두이노 마이크로 정보를 보시고 핀 연결하시면 됩니다.

3. 코딩


스위치버턴을 5개를 키보드 키를 제어하는데 사용 합니다. 처음에 알파벳 a,b,c,d 키와 KEY_RETURN 키로 실험을 하려다가 설계를 2번핀은 인터럽트 핀으로 사용하고 안정장치를 마련하고 나머지 3,4번은 a,b 키, 5번은 KEY_RETURN 키, 6번은 KEY_BACKSPACE 키로 사용하여 최대한 키보드 느낌을 살리는 실험을 하겠습니다.

1) 키보드 안전장치


키보드를 아두이노를 제어를 하게 되면 약간 주의해서 코딩을 해야 합니다. 잘못된 코딩을 하게 되면 무한 루프에 빠져서 키를 무한으로 누르는 문제가 발생 시킬 수 있습니다. 의도치 않게 잘못된 무한 버그가 발생 할 수 있으니 코딩을 할 때 주의해서 코딩을 해야 합니다. 그래서 안전장치로 인터럽트를 이용할까 합니다. loop()함수가 무한 반복 수행 하더라고 중간에 인터럽트 이벤트를 발생시킬 수 있습니다. 그러면 이 이벤트 명령으로 반복 수행을 중단시킬 수 있습니다. 즉, loop()함수에 락을 하나 걸어놓고 문제가 생기면 락을 걸어서 아두이노의 키보드 명령을 중단시키면 문제에 대해서 탈출 할 수 있습니다.

[기본소스]

const byte interruptPin = 2;
boolean state = true;

void setup() {   
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), exchange, FALLING);  
}
void loop(){
  if(state==false){
    
    키보드 명령;
    
    }
}
void exchange() {
 state=!state;
}

위 코딩을 보면 state가 false일때만 키보드 명령이 수행되고 키보드 명령이 잘못된 코딩으로 오류로 무한 루프에 빠지게 되면 인터럽트 스위치버턴을 누르면 state는 true 상태가 되고 더이상 키보드 명령을 수행하지 않게 됩니다.

이 안전장치가 어떤 느낌인지 아시겠지요. 나중에 코딩이 완성되면 키보드 안정장치 코딩은 지우셔도 됩니다. 코딩하면서 테스트할 때 혹시 모를 error에 대한 대비책으로 생각하시면 됩니다.

참고로, 아두이노 마이크로에서 선언때 초기값이 state가 true인데 loop()함수 내에서 state의 초기 상태는 false입니다. 아두이노우노에서는 loop()에서도 true 상태를 유지하는데 이상하게 아두이노 마이크로는 인터럽트가 한번 수행하고 들어가는 것 같더군요. 혼동하지 마세요.

2) 키보드 키 실험


  • 알파벳(a,b 키), KEY_RETURN, KEY_BACKSPACE 키로 간단히 키보드 테스트

핀번호을 선언 합니다.

const byte Pin_A = 3;
const byte Pin_B = 4;
const byte Pin_Return = 5;
const byte Pin_Backspace = 6;

스위치 버턴 모두 내부풀업모드 방식을 사용하니깐 pinMode()은 다음과 같이 INPUT_PULLUP 모드로 선언 합니다.

pinMode(Pin_A, INPUT_PULLUP); pinMode(Pin_B, INPUT_PULLUP); pinMode(Pin_Return, INPUT_PULLUP); pinMode(Pin_Backspace, INPUT_PULLUP);

'a'키 누름 이벤트 명령은 다음과 같습니다. 내부풀업모드로 초기상태가 HIGH 입니다. 그래서 스위치를 누르면 LOW로 바뀌게 되고 IF문이 참이 되어 키 누름과 해제 명령을 수행하게 됩니다.

 if(digitalRead(Pin_A) == LOW){
    Keyboard.press('a');
    delay(100);
    Keyboard.releaseAll();
    delay(200);  
 }

여기서, 일정 딜레이 시간을 둔 이유는 키보드 누름 간격을 주기 위해서 입니다. 딜레이 시간이 없게 되면 너무 빠른 속도로 눌러지기 때문에 a라는 키가 연속으로 눌러지는 현상이 발생합니다. 너무 길레 딜레이를 주면 지연이 발생하고 너무 짧게 딜에이를 주면 연속 키 누름 현상이 발생합니다. 위 시간값은 제 편의상 지정한 값임으로 키 누름 딜레이 시간을 조절해 보세요. 참고로 실제 키보드 보다는 좀 느린 편입니다. 키누름 반응속도가 좀 느린 편이죠. 그런데 너무 짧게 하면은 정상적인 알파벳 입력이 어려우니깐 상황에 따라서 조절하시면 될 듯 싶습니다. 참고로, 게임 조종기로 사용할 경우는 반응속도를 높이기 위해서 딜레이 간격을 줄이는게 좋습니다.

나머지 스위치 버턴도 위와 같이 동일하게 코딩하면 됩니다.

3) 종합소스


위 안전장치 코딩과 키 누름 코딩을 합쳐서 종합해 보면 아래와 같이 코딩을 할 수 있습니다.

#include <Keyboard.h>

const byte Pin_A = 3;
const byte Pin_B = 4;
const byte Pin_Return = 5;
const byte Pin_Backspace = 6;

const byte interruptPin = 2;
boolean state = true;

void setup() {  
  Serial.begin(9600);
  Keyboard.begin();
  pinMode(Pin_A, INPUT_PULLUP); 
  pinMode(Pin_B, INPUT_PULLUP);
  pinMode(Pin_Return, INPUT_PULLUP);
  pinMode(Pin_Backspace, INPUT_PULLUP);

  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), exchange, FALLING);  
}

void loop() {
  if(state==false){
    if(digitalRead(Pin_A) == LOW){
      Keyboard.press('a');
      delay(100);
      Keyboard.releaseAll();
      delay(200);  
    }
    if(digitalRead(Pin_B) == LOW){
      Keyboard.press('b');
      delay(100);
      Keyboard.releaseAll();
      delay(200);  
    }
    if(digitalRead(Pin_Return) == LOW){
      Keyboard.press(KEY_RETURN);
      delay(100);
      Keyboard.releaseAll();
      delay(200);  
    }
    if(digitalRead(Pin_Backspace) == LOW){
      Keyboard.press(KEY_BACKSPACE);
      delay(100);
      Keyboard.releaseAll();
      delay(200);  
    }   
  }
}
void exchange() {
 state=!state;
}

스위치 버턴 누름을 delay()함수로 간단히 테스트 했기 때문에 각 키간의 지연 렉이 좀 발생할 수 있습니다. 시간 간격이 워낙 짧기 때문에 체감이 잘 안될 수 있지만 더 정교하게 제어를 원하시는 분이라면 delay()함수 대신에 저번에 설명한 millis()함수로 이용하여 delay()함수 없이 delay를 제어하는 방법으로 코딩을 수정하면 됩니다.

이 코딩은 단지 동작을 테스트 할 목적의 코딩이기 때문에 복잡한 동작 명령은 생략합니다.

4. 결과



스위치버턴은 말풍선의 순서대로 해당 키보드 키명령을 가지고 있습니다. 해당 스위치 버턴을 해당 명령을 PC에서 수행합니다.

결과는 아래 동영상에서 확인하시면 됩니다.


마무리


완벽하지는 않지만 그래도 키보드 같은 느낌으로 제작이 되었네요. 코딩을 좀 더 정리를 해야 하고 약간 문제되는 부분을 수정 보안해야 하는데 간단한 실험을 하기 위해서 코딩은 수정하지 않았습니다. 그리고, 아두이노 IDE 예제들에서 USB 예제를 보시면 키보드 예제 소스들이 몇가지가 있는데 재밌는 예제들인데 한번 전부 다 테스트 해보셨으면 합니다. 꽤 재밌는 원리가 숨어 있습니다. 예제 중 하나만 설명드리면 아두이노 마이크로가 아두이노 IDE 창에 새로운 창을 띄우게 하고 아두이노 자기 스스로 코딩을 하고 그 코딩을 직접 아두이노 마이크로에 업로드를 시킵니다. 그리고 아두이노 마이크로가 직접 스스로 코딩한 명령을 수행하게 됩니다. 이게 뭘 의미 하냐면 아두이노 마이크로가 자기 스스로 업그레이드를 수행한다는 것을 의미합니다. 만약, 학습능력을 아두이노 마이크로에 코딩되어 있으면 그 학습에 의해서 스스로 자기 자신을 코딩하여 업그레이드가 가능해진다는 의미가 됩니다. 한마디로 아두이노 마이크로가 스스로 진화할 수 있다는 의미와 같습니다.

자기 자신에게 프로그램을 코딩하고 이식한다는 것은 엄청난 원리입니다. 스스로 업그레이드를 할 수 있다는 의미이고 진화를 할 수 있다는 의미와 같습니다. 한번 이 원리를 상상의 나래를 펼쳐 보세요.


댓글()

[아두이노] 아두이노 마이크로 보드 제어

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

[아두이노] 아두이노 마이크로 보드 제어




최근에 몇개의 부품을 구매했는데 이제서야 아두이노 마이크로 보드를 소개 하네요. 사실 예전에 이 모델을 구매할려고 했지만 국내 가격이 해외직구 가격과 비교하면 3개를 구매 할 수 있는 가격으로 국내에서 판매하기 때문에 구매하기가 싫어지더군요. 지금까지 미뤄 왔는데 좀 비싸긴 하지만 오천원대 가격으로 어느정도 싸게 판매하는 곳이 있어서 그냥 구매하게 되었네요. 진짜 아두이노 부품을 사고 싶은 것들이 많은데 국내 가격은 해외 직구 가격 차를 보면 한숨만 나오네요.

오늘 실험은 간단히 아두이노 마이크로 보드가 정상적으로 동작하는지 테스트 실험을 하겠습니다.

1. 아두이노 마이크로 보드


오늘 소개 할 아두이노 마이크로 보드는 아두이노우노랑 비슷한 보드라고 생각하시면 됩니다. 차이점은 크기가 작고 USB가 다릅니다. 아두이노우노는 USB MCU ATmega16u2 방식인데 아두이노 마이크로는 Micro USB를 사용하면 ATMEGA32u4를 기반의 보드 입니다. 그래서, 아두이노 마이크로 보드를 이용하여 키보드/마우스 처럼 사용이 가능합니다. 전용 아두이노 키보드나 마우스 제작이 가능한 보드입니다.

키보드/마우스 함수는 32u4 또는 SAMD 마이크로 기반 보드에서 사용되는데 몇개 보드들 중 많이 알려진 보드로는 아두이노 레오나르도, 아두이노 마이크로 보드가 있습니다.


보드가 키보드/마우스를 사용 가능한 보드인지 쉽게 구별하는 방법은 위 사진처럼 Micro USB 모양을 보시고 구별하시면 됩니다.

아두이노 마이크로 보드를 자세히 살펴볼까요.


20개의 디지털 입출력 핀에서 PWM 핀이 7개 있고, 아날로그 입력 핀이 12개나 됩니다. 아두이노우노랑은 차이가 있죠. 자세한 정보는 아두이노 마이크로 데이터시트 한번 읽어주시면 좋겠습니다.

위 그림에서 핀정보만 간단히 아두이노우노와 비교해서 이해하시면 될 듯 싶네요. 어느핀이 무슨핀이고 핀 번호가 어떻게 되는지만 아셔도 충분합니다.

2. 아두이노 마이크로 보드 설정


아두이노 IDE 툴에서는 바로 프로그램을 업로드가 되지 않습니다. 환경 설정을 해 주셔야 하는데 그 방법을 설명 드립니다.

[보드 지정] : Arduino/Genuino Micro로 선택하시오.


[포트 지정] : 아두이노 마이크로를 PC USB에 연결하면 해당 COM숫자(Arduino/Genuino Micro)라고 잡히는데 COM숫자는 랜덤으로 알아서 잡히는데 그 포트를 지정하시면 됩니다.


이 두개의 환경설정이 끝났으면 아두이노 코딩을 한 것을 업로드 시키면 아두이노 마이크로에 프로그램을 이식 시킬 수 있습니다.

3. 아두이노 마이크로 회로도


  • 준비물 : Red LED 1개, 220옴 1개, 아두이노 마이크로
  • 내용 : 12번핀을 Red LED를 제어 하기 위해서 Red LED에 12번 핀을 연결하시오.


4. 코딩



몇 달만에 다시 기초 부분을 거론하게 되었네요. 아두이노우노를 소개했던 기초 실험이였는데 다시 반복하게 되었네요.

[Blink 예제 소스]

const byte redLed = 12;

void setup() {
  pinMode(redLed, OUTPUT);
}

void loop() {
  digitalWrite(redLed, HIGH);  
  delay(1000);                 
  digitalWrite(redLed, LOW);   
  delay(1000);                 
}

1초 단위로 깜박이는 명령인데 위 코딩은 이제 설명 할 필요는 없겠죠.

5. 결과


아래 움짤을 보시는 것과 같이 정상적으로 아두이노 마이크로가 동작하네요.


마무리


오늘은 간단히 아두이노 마이크로 보드에 대해 살펴 보고 정상적으로 동작하는지 실험하는 시간이였습니다. 아두이노 마이크로 보드라고 별도의 코딩이 있는 것은 아닙니다. 단지 환경설정에서 보드/포트 지정만 변경해주시면 됩니다. 이 보도를 통해서 다음에 키보드/마우스 명령을 내려보는 실험을 하도록 하겠습니다.

최근에는 너무 코딩 중심의 복잡한 post가 이루어 졌는데 계속 코딩 중심적 post가 되면 지루 할 것 같아서 새로운 주제로 넘어갔네요. 아두이노 시계에서 4-digit 7-segment display 부품을 74HC595 칩을 결합해서 제어하는 실험까지 post 하고 싶었지만 그 부분은 여러분들에게 상상 숙제로 남겨 둡니다.

댓글()

[아두이노] 4-Digit 7-Segment Display 시계 리모콘 제어

IOT/아두이노|2019. 6. 20. 10:38

[아두이노] 4-Digit 7-Segment Display 시계 리모콘 제어



지난 시간에 6-Digit 7-Segment Display 아두이노 시계에서 리모콘 시간 입력을 가상시뮬레이터로 실험을 하였습니다. 오늘은 다행히 IR Sensor(수신부) 부품을 찾아서 실제 리모콘으로 제어하는 실험을 할 수 있게 되었네요. 참고로 4-Digit 7-Segment Display 부품이라서 4개의 숫자 밖에 출력을 못합니다. 그래서 시/분을 리모콘으로 제어할 것인지 분/초를 제어할지 고민하다가 분/초를 리모콘으로 제어하고 제어된 결과가 정상적으로 동작하는지 살펴보는게 나을 것 같아서 분/초 시계를 만들어 보았습니다.


1. 4-Digit 7-Segment Display 회로도


  • 준비물 : 4-Digit 7-Segment Display 1개, IR Sensor 1개, IR remote 1개, 아두이노우노
  • 내용 : 7-Segment Display 부품의 핀 번호 순서에 맞게 아두이노우노에 연결하고, IR Sensor 핀을 2번핀에 연결하시오.

IR Sensor(수신부)는 핀 위치가 다를 수 있습니다. fritzing에 제공되는 핀위치에 따라 연결했기 때문에 연결하실 때 해당 수신부 부품이 어떤 부품이냐에 따라 주의해서 연결해 주세요.


아래 IR Sensor(수신부) 부품이면 핀 번호를 보시고 연결하시면 됩니다.


2. 코딩



사전학습 post의 소개한 소스를 실제 코딩에도 그대로 적용됩니다. 사전학습 종합소스와 아래 수정 할 부분을 같이 보고 어느 부분을 수정되었는지 같이 보시면서 읽어주세요. 아니면 아래 수정한 종합소스를 보면서 수정한 부분의 위치가 어느 위치이고 수정됐는지 같이 보면서 이해하시기 바랍니다. 그 이유는 그냥 아래 수정 할 부분에 이부분을 수정합니다라고 이야기 하기 때문에 말하는 부분이 정확이 어느 부분인지 혼동할 수 있기 때문에 꼭 종합소스를 같이 봐주세요.

수정 할 부분은 리모콘 키값부분입니다.


사용할 버턴은 위 사진에서 보는 버턴들을 이용 합니다. 이 부분의 키값을 알려면 리모콘 키값 조회 공개회로도 보시고 맞게 코딩을 수정한 뒤에 키 값을 알아내고 그 키값을 아래와 같이 수정하변 됩니다.

#define STATE 16736925 // ch버턴
#define SELECT_TIME 16748655 // EQ버턴
#define TIME_UP 16754775 // +버턴
#define TIME_DOWN 16769055 // -버턴

다음으로 4-Digit 7-Segment Display 핀 번호에 대해서 변수 수정을 해야 합니다.

const byte segPin[8]={7,3,A3,A1,A0,6,A4,A2}; //사용핀{a,b,c,d,e,f,g,dp} 순서대로임
const byte digitPin[4] = {8,5,4,A5}; //segment 위치 핀

다음 수정부분은

     if(output==true){
       if(results.value==SELECT_TIME){
         selectVal++;
         if(selectVal==3)selectVal=0;
       }

이 부분으로 원래는 시/분/초 3개의 시간위치가 기존 소스에 있었지만 두개를 분/초를 이용하기 때문에 분/초 위치만 왔다 갔다 하여야 하기 때문에 다음과 같이 수정이 됩니다.

if(selectVal==2)selectVal=0;

만약, 시/분만 왔다 갔다 해야 한다면

if(selectVal==3)selectVal=1;

어떻게 변경해야 할지 아시겠지요.

나머지도 수정해야 하는데 그대로 뒀습니다. 그냥 둬도 동작에는 영향이 없습니다.

출력부분에서 4개의 숫자만 출력되기 때문에 다음 부분을 수정해야 합니다.

void loop() {
  IRremoteRead(); //리모콘 읽기

  if(output==false){ //시간 출력
    readTime = millis()/1000;
    if(millis()>=86400000){
       timer0_millis=0;
    }
    sec = readTime%60;
    min = (readTime/60)%60;
    hour = (readTime/(60*60))%24; 

    segOutput(3,sec%10,0); //sec 1의 자리
    segOutput(2,sec/10,0); //sec 10의 자리
    segOutput(1,min%10,1); //min 1의 자리
    segOutput(0,min/10,0); //min 10의 자리
 //  segOutput(1,hour%10,1); //hour 1의 자리
 //  segOutput(0,hour/10,0); //hour 10의 자리      
  }
  else{ //시간 조정     
    segOutput(3,clockVal[0]%10,0); //sec 1의 자리
    segOutput(2,clockVal[0]/10,0); //sec 10의 자리
    segOutput(1,clockVal[1]%10,1); //min 1의 자리
    segOutput(0,clockVal[1]/10,0); //min 10의 자리
//    segOutput(1,clockVal[2]%10,1); //hour 1의 자리
//    segOutput(0,clockVal[2]/10,0); //hour 10의 자리      
  }
}

분/초를 출력하기 때문에 시 부분을 주석 처리하시면 됩니다. 만약에 시/분을 출력하고 싶다면 초 부분을 주석처리하면 됩니다.

참고로,

** segOutput(segment display 위치,시간값,dp핀값)**

4개의 segment display으로 {0,1,2,3} 순서로대로 위치값을 갖기 때문에 출력 시간값의 위치는 정확히 지정해주시고 코딩해주세요.

수정한 부분을 종합 완성하면,

#include <IRremote.h>

#define STATE 16736925 // ch버턴
#define SELECT_TIME 16748655 // EQ버턴
#define TIME_UP 16754775 // +버턴
#define TIME_DOWN 16769055 // -버턴
  
const byte IRpin = 2;  
IRrecv irrecv(IRpin);
decode_results results;

//a,b,c,d,e,f,g 상태값
const byte segValue[10][7] = {
   {1,1,1,1,1,1,0}, //0
   {0,1,1,0,0,0,0}, //1
   {1,1,0,1,1,0,1}, //2
   {1,1,1,1,0,0,1}, //3
   {0,1,1,0,0,1,1}, //4
   {1,0,1,1,0,1,1}, //5
   {1,0,1,1,1,1,1}, //6
   {1,1,1,0,0,0,0}, //7
   {1,1,1,1,1,1,1}, //8
   {1,1,1,1,0,1,1}  //9  
};

const byte segPin[8]={7,3,A3,A1,A0,6,A4,A2}; //사용핀{a,b,c,d,e,f,g,dp} 순서대로임
const byte digitPin[4] = {8,5,4,A5}; //segment 위치 핀

boolean output = false;//시간 출력형식 지정
byte selectVal = 0;
int clockVal[3]={0,0,0};


extern volatile unsigned long timer0_millis; //타이머변수
unsigned long readTime; //현재타이머시간
int hour, min, sec;

void setup() {
  Serial.begin(9600);  
  irrecv.enableIRIn(); // 리모콘 시작
 
  for(int i=0;i<10;i++){
    pinMode(segPin[i], OUTPUT);
  }
  for(int j=0;j<6;j++){
    pinMode(digitPin[j], OUTPUT);    
    digitalWrite(digitPin[j], HIGH); 
  }  
}

void loop() {
  IRremoteRead(); //리모콘 읽기
   
  if(output==false){ //시간 출력
    readTime = millis()/1000;
    if(millis()>=86400000){
       timer0_millis=0;
    }
    sec = readTime%60;
    min = (readTime/60)%60;
    hour = (readTime/(60*60))%24; 

    segOutput(3,sec%10,0); //sec 1의 자리
    segOutput(2,sec/10,0); //sec 10의 자리
    segOutput(1,min%10,1); //min 1의 자리
    segOutput(0,min/10,0); //min 10의 자리
 //  segOutput(1,hour%10,1); //hour 1의 자리
 //  segOutput(0,hour/10,0); //hour 10의 자리      
  }
  else{ //시간 조정     
    segOutput(3,clockVal[0]%10,0); //sec 1의 자리
    segOutput(2,clockVal[0]/10,0); //sec 10의 자리
    segOutput(1,clockVal[1]%10,1); //min 1의 자리
    segOutput(0,clockVal[1]/10,0); //min 10의 자리
//    segOutput(1,clockVal[2]%10,1); //hour 1의 자리
//    segOutput(0,clockVal[2]/10,0); //hour 10의 자리      
  }
}

void IRremoteRead(){
   if (irrecv.decode(&results)) //리모콘 누른값이 없다면 패스
    {
     if(results.value==STATE){
       output=!output;       
       if(output==false){
         hour = clockVal[2];
         min = clockVal[1];
         sec = clockVal[0];
         timer0_millis = ((long)hour*3600+min*60+sec)*1000;         
       }
       else{
         selectVal=0;
         for(int i=0;i<3;i++){
           clockVal[i]=0;
         }         
       }       
     }
     
     if(output==true){
       if(results.value==SELECT_TIME){
         selectVal++;
         if(selectVal==2)selectVal=0;
       }
         
       if(results.value==TIME_UP)clockVal[selectVal]++;
       else if(results.value==TIME_DOWN) clockVal[selectVal]--;
       

       if(selectVal==2){                  
         if(clockVal[selectVal]==24)clockVal[selectVal]=0;
         else if(clockVal[selectVal]==-1)clockVal[selectVal]=23;
       }
       else{
         if(clockVal[selectVal]==60)clockVal[selectVal]=0;
         else if(clockVal[selectVal]==-1)clockVal[selectVal]=59;         
       }

     }
    //Serial.println(results.value); //key value
     irrecv.resume(); // 다음값
    }
}

//LED 초기화
void segClear(){ 
  for(int i=0;i<8;i++){
    digitalWrite(segPin[i], LOW);        
  }
}
//LED 출력
void segOutput(int d, int Number, int dp){ 
  segClear();
  digitalWrite(digitPin[d], LOW); 
  for(int i=0;i<7;i++){
     digitalWrite(segPin[i], segValue[Number][i]);        
  }
  digitalWrite(segPin[7], dp);
  delayMicroseconds(1000);
  digitalWrite(digitPin[d], HIGH); 
}

3. 결과


  • CH => 시간입력 ON/OFF
  • EQ => 시간 입력 제어 위치
  • +/-=> 숫자 증감/감소

CH 버턴을 누르면 시간을 입력 할 준비 상태가 됩니다. +/- 바로 누르면 sec가 움직입니다. EQ를 누르면 min 위치로 이동하고 다시 +/-를 누르면 min이 움직입니다. 입력 할 시간 세팅이 끝나면 CH 버턴을 누르면 입력된 시간이 타이머변수에 세팅이 되고 그때부터 시간이 흘러가고 4-Digit 7-Segment Display에 현재 분/초를 출력하게 됩니다.


마무리


오늘은 Digit 7-Segment Display 부품에 분/초가 출력되는데 여기에 리모콘을 누르면 분/초를 조정할 수 있게 됩니다. 참고로 실험에서는 시 부분은 0시로 무조건 초기 세팅이 되고 분/초가 제어가 이루어지기 때문에 위 소스로만 제작한다면 약간 아두이노 시계라고 보기 어렵습니다. 수정하셔서 시/분으로 제작하시고 60초를 버리고 시/분을 입력하여 초는 무조건 0초부터 시작하게 수정하시면 진자 아두이노 시계 느낌으로 제작이 완료 됩니다. 이 부분은 위 코딩 수정부분을 보시면 어디를 수정하면 원하는 결과가 나올지는 찾을 수 있을거라 생각됩니다. 한번 수정해 보세요.

여기서 직접 리모콘까지 아두이노로 만들어 보았으면 좋은데 송신부 부품이 없어서 아깝게 아두이노 리모콘 제작은 못 보여드리네요. 혹시 관심이 있으시면 아두이노 리모콘 만들기를 구글 검색하시면 간단한 에제 소스를 보실 수 있으니깐 만들어 보실분들은 한번 도전해 보세요. 아두이노 리모콘을 만드시면 나중에 가정 전자제품의 리모콘의 키값을 아두이노로 IR Sensor(수신부)로 읽을 수 있으면 아두이노 리모콘에 그 키값을 저장했다가 실제 가정 전자제품을 직접 만든 아두이노 리모콘으로 제어할 수 있습니다. 여러개의 리모콘 값을 아두이노 리모콘으로 통합시키면 하나의 리모콘으로 모든 가전제품을 제어할 수 있게 되니깐 호기심이 있는 분들은 꼭 실험해 보세요.


댓글()

[아두이노] 6-Digit 7-Segment Display 시계 리모콘 제어

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

[아두이노] 6-Digit 7-Segment Display 시계 리모콘 제어



6-Digit 7-Segment Display 지난 시간에 아두이노 시계를 제작하여 가상 실험을 하였습니다. 그런데 아두이노 시계의 초기값을 PC 시리얼모니터에서 시간을 입력하는게 좀 불편해 보여서 다른 외부 장치로 시간을 입력하는 방법이 없을까 고민하다가 무선으로 시간값을 입력하는 방법을 상상하다가 몇가지 상상한 방법 중에 리모콘으로 시간을 입력해볼까 하는 호기심이 생겨서 좋은 학습 자료가 될 것 같아서 리모콘으로 아두이노 시간값을 입력하는 실험하게 되었네요. 리모콘으로 시간을 입력하는데 그 입력된 시간을 리모콘이 누를때 숫자가 변경되고 그 값이 6-Digit 7-Segment Display 부품에 출력되게 해야 하는데 6개의 7-Segment Display을 각각 개별적으로 6개의 숫자를 갱신하게 할지 아니면 시/분/초 단위로 3개의 숫자로 리모콘으로 제어해서 3개의 숫자값을 출력해야 할지 아니면 다른 방법으로 시간 숫자를 제어할지 여러개를 고민하고 실험해 보았습니다. 그중 하나를 선택하여 post로 소개 합니다.

1. 6-Digit 7-Segment Display 회로도


  • 준비물 : 7-Segment Display 6개, IR Sensor 1개, IR remote 1개, 아두이노우노
  • 내용 : 7-Segment Display 부품의 핀 번호 순서에 맞게 아두이노우노에 연결하고, IR Sensor 핀을 2번핀에 연결하시오.

지난 시간의 회로도에서 스위치버턴핀으로 사용한 2번핀을 IR Sensor핀으로 사용했습니다. 수정 부분은 IR Sensor 핀만 변경해주시면 됩니다.


2. 코딩



리모콘 제어를 간단히 복습을 해 볼까요.


[IRemote]

  • 선언 :
#include <IRremote.h>
 
int IRpin = 2;  // IR Sensor Pin
IRrecv irrecv(IRpin);
decode_results results;
  • 초기세팅 : 리모콘 시작
void setup(){
  irrecv.enableIRIn(); // 리모콘 시작
}
  • 리모콘 읽기 : results.value으로 리모콘이 읽은 키 값이 저장되어 있음
void loop(){
  if (irrecv.decode(&results)){ //리모콘 누른값이 없다면 패스
      //results.value 로 리모콘이 눌린 값이 들어 있음.
        
        irrecv.resume(); // 다음값
  }
}

몇달 전 post의 내용인데 오랫만의 리모콘을 아두이노 시계에 적용합니다. 참고로 IRremote 라이브러리 설치는 사전학습에 가시면 나와 있으니깐 보시고 설치한 다음 사용하세요. 혹시 위와 같이 코딩했는데 작동을 안하면 라이브러리를 확인하시고 설치하시면 됩니다.

1) 리모콘 키값 찾기



위 그림의 리모콘의 버턴 4개를 사용할 예정입니다.

  • FUNC/STOP => 시간입력 ON/OFF
  • ST/REPT => 시간 입력 제어 위치
  • 세모 위/아래 => 숫자 증감/감소

가상시뮬레이터로 사전학습의 회로도와 키값 확인 소스를 누르시면 해당 results.value을 확인 할 수 있습니다.

위 코딩에서

Serial.println(results.value);

이렇게 해서 리모콘의 해당 키값을 시리얼모니터로 확인 한 다음 기록해 주세요.

#define STATE 16597183  //FUNC/STOP
#define SELECT_TIME 16609423 //ST/REPT
#define TIME_UP 16601263 //세모 UP
#define TIME_DOWN 16584943 //세모 DOWN

참고로, 실제로 실험하실 때는 리모콘 키값을 직접 누르시고 해당 키 값을 메모장에 기록했다가 위처럼 표현하시면 됩니다. 리모콘 종류에 따라 키 값이 다를 수 있음으로 꼭 사전에 키값을 확인하시고 코딩해 주세요.

2) 시간 입력 리모콘 제어


  • FUNC/STOP => 시간입력 ON/OFF

시간을 입력받을지 결정해야 합니다. 상태변수를 하나 만들어서 아래와 같이 IF 조건문을 만들면 됩니다.

if (irrecv.decode(&results)) //리모콘 누른값이 없다면 패스
    {
     if(results.value==STATE){
           output=!output;
     }
}

FUNC/STOP 리모콘 버턴을 누르면 output가 true or false 교대로 상태값을 갖게 됩니다. output가 true면 시간 입력이고 false면 입력된 시간이 흘러가게 하는 코딩을 하겠습니다.

if(리모콘키값==STATE){
  output=!output; 
  if(output==false){
    timer0_millis 시간 설정;
  }
  else{
    시간 전 입력 초기화;    
  } 
}
if(output==true){        
   시간 입력;
}

코딩을 하면,

 if (irrecv.decode(&results)) //리모콘 누른값이 없다면 패스
    {
     if(results.value==STATE){ //시간 입력 결정
       output=!output;       
       if(output==false){  //리모콘으로 시/분/초 입력한 값을 타이머변수에 저장;
         hour = clockVal[2];
         min = clockVal[1];
         sec = clockVal[0];
         timer0_millis = ((long)hour*3600+min*60+sec)*1000;         
       }
       else{  //시간 입력 전 입력 변수을 초기화
         selectVal=0;
         for(int i=0;i<3;i++){
           clockVal[i]=0;
         }         
       }       
     }
     if(output==true){ //시간 입력 제어
        시간입력;        
     }       
     irrecv.resume(); // 다음값
    }
}

clockVal[3]은 hour, min, sec의 값을 저장하게 됩니다. 그리고 selectVal은 시/분/초의 위치를 나타냅니다.

if(results.value==SELECT_TIME){ //시/분/초 위치 선택
  selectVal++;
  if(selectVal==3)selectVal=0;
}

ST/REPT를 누를 때 시/분/초가 selectVal이 0이면 sec이고, 1이면 min, 2이면 hour를 가리킵니다. selectVal가 3이 되면 다시 0으로 초기화됩니다. 해당 리모콘의 키 버턴을 통해 시/분/초 위치값을 지정할 수 있게 됩니다.

이제 세모 UP/DOWN 버턴을 통해서 시간값을 증감/감소를 제어를 해 볼까요.

if(results.value==TIME_UP) clockVal[selectVal]++;  //시간값 증가
else if(results.value==TIME_DOWN) clockVal[selectVal]--;  //시간값 감소

"clockVal[selectVal]++" or "clockVal[selectVal]--"로 시/분/초 값을 증가/감소를 시키게 됩니다. 여기서, 시간은 24시, 분은 60분, 초는 60초가 max 값입니다. 그래서 증가할 때 숫자는 시간은 24시를 넘지 말아야 하고 분/초는 60을 넘지 말아야 합니다.

그 부분을 IF문으로 표현을 하면 다음과 같습니다.

if(selectVal==2){ //hour 일때              
  if(clockVal[selectVal]==24)clockVal[selectVal]=0;
  else if(clockVal[selectVal]==-1)clockVal[selectVal]=23;
}
else{ //min, sec 일때
  if(clockVal[selectVal]==60)clockVal[selectVal]=0;
  else if(clockVal[selectVal]==-1)clockVal[selectVal]=59;         
}

selectVal가 2이면 시간이니깐 IF문에서 24가 되면 clockVal[selectVal]으로 0으로 만들고 감소할 때 -1이 되면 23으로 만들어 주면 0~23시간 조절하면 됩니다

그리고 그외 0~1이면 분/초로 60이 max이니깐 IF문으로 60이면 clockVal[selectVal]으로 0으로 만들고 감소할 때 -1이 되면 59으로 만들어 주면 0~59분/초를 조절하면 됩니다

3) 출력


  if(output==false){ //시간 출력
    readTime = millis()/1000;
    if(millis()>=86400000){
       timer0_millis=0;
    }
    sec = readTime%60;
    min = (readTime/60)%60;
    hour = (readTime/(60*60))%24; 

    segOutput(5,sec%10,0); //sec 1의 자리
    segOutput(4,sec/10,0); //sec 10의 자리
    segOutput(3,min%10,1); //min 1의 자리
    segOutput(2,min/10,0); //min 10의 자리
    segOutput(1,hour%10,1); //hour 1의 자리
    segOutput(0,hour/10,0); //hour 10의 자리      
  }
  else{ //시간 조정     
    segOutput(5,clockVal[0]%10,0); //sec 1의 자리
    segOutput(4,clockVal[0]/10,0); //sec 10의 자리
    segOutput(3,clockVal[1]%10,1); //min 1의 자리
    segOutput(2,clockVal[1]/10,0); //min 10의 자리
    segOutput(1,clockVal[2]%10,1); //hour 1의 자리
    segOutput(0,clockVal[2]/10,0); //hour 10의 자리      
  }

output가 false이면 시간을 출력하니깐 타이머변수를 세팅하면 millis()함수 현재 시간을 구하여 출력하면 됩니다. 지난시간의 출력 부분하고 같기 때문에 그대로 적용합니다. 하지만 시간을 입력할 때도 6-Digit 7-Segment Display 출력해야 하기 때문에 output가 true일때는 입력상황이니깐 그 입력에 대한 출력부분도 코딩해야 합니다. 입력시간은 clockVal[3] 배열변수에 저장되기 때문에 10의 자리와 1의 자리를 쪼개서 각 숫자를 해당 위치에 맞게 출력시키면 됩니다.

위의 표현한 코딩을 지난시간의 아두이노 시계에 합쳐 보도록 하겠습니다.

4) 종합 소스


#include <IRremote.h>

#define STATE 16597183
#define SELECT_TIME 16609423
#define TIME_UP 16601263
#define TIME_DOWN 16584943
  
const byte IRpin = 2;  
IRrecv irrecv(IRpin);
decode_results results;


//a,b,c,d,e,f,g 상태값
byte segValue[10][7] = {
   {1,1,1,1,1,1,0}, //0
   {0,1,1,0,0,0,0}, //1
   {1,1,0,1,1,0,1}, //2
   {1,1,1,1,0,0,1}, //3
   {0,1,1,0,0,1,1}, //4
   {1,0,1,1,0,1,1}, //5
   {1,0,1,1,1,1,1}, //6
   {1,1,1,0,0,0,0}, //7
   {1,1,1,1,1,1,1}, //8
   {1,1,1,1,0,1,1}  //9  
};

const byte segPin[8]={3,4,5,6,7,8,9,10}; //사용핀{a,b,c,d,e,f,g,dp} 순서대로임
const byte digitPin[6] = {A0,A1,A2,A3,A4,A5}; //segment 위치 핀

boolean output = false;//시간 출력형식 지정
byte selectVal = 0;
int clockVal[3]={0,0,0};

extern volatile unsigned long timer0_millis; //타이머변수
unsigned long readTime; //현재타이머시간
int hour, min, sec;


void setup() {
  Serial.begin(9600);  
  irrecv.enableIRIn(); // 리모콘 시작
 
  for(int i=0;i<10;i++){
    pinMode(segPin[i], OUTPUT);
  }
  for(int j=0;j<6;j++){
    pinMode(digitPin[j], OUTPUT);    
    digitalWrite(digitPin[j], HIGH); 
  }  
}

void loop() {
  IRremoteRead(); //리모콘 읽기
   
  if(output==false){ //시간 출력
    readTime = millis()/1000;
    if(millis()>=86400000){
         timer0_millis=0;
    }
    sec = readTime%60;
    min = (readTime/60)%60;
    hour = (readTime/(60*60))%24; 

    segOutput(5,sec%10,0); //sec 1의 자리
    segOutput(4,sec/10,0); //sec 10의 자리
    segOutput(3,min%10,1); //min 1의 자리
    segOutput(2,min/10,0); //min 10의 자리
    segOutput(1,hour%10,1); //hour 1의 자리
    segOutput(0,hour/10,0); //hour 10의 자리      
  }
  else{ //시간 조정     
    segOutput(5,clockVal[0]%10,0); //sec 1의 자리
    segOutput(4,clockVal[0]/10,0); //sec 10의 자리
    segOutput(3,clockVal[1]%10,1); //min 1의 자리
    segOutput(2,clockVal[1]/10,0); //min 10의 자리
    segOutput(1,clockVal[2]%10,1); //hour 1의 자리
    segOutput(0,clockVal[2]/10,0); //hour 10의 자리      
  }
}

void IRremoteRead(){
   if (irrecv.decode(&results)) //리모콘 누른값이 없다면 패스
    {
     if(results.value==STATE){ //시간 입력 결정
       output=!output;       
       if(output==false){ //리모콘으로 시/분/초 입력한 값을 타이머변수에 저장;
         hour = clockVal[2];
         min = clockVal[1];
         sec = clockVal[0];
         timer0_millis = ((long)hour*3600+min*60+sec)*1000;         
       }
       else{  //시간 입력 전 입력 변수을 초기화
         selectVal=0;
         for(int i=0;i<3;i++){
           clockVal[i]=0;
         }         
       }       
     }
     
     if(output==true){ //시간 입력 제어
       if(results.value==SELECT_TIME){ //시/분/초 위치 선택
         selectVal++;
         if(selectVal==3)selectVal=0;
       }
             
       if(results.value==TIME_UP)clockVal[selectVal]++; //시간값 증가
       else if(results.value==TIME_DOWN) clockVal[selectVal]--; //시간값 감소
 
       if(selectVal==2){ //hour 일때                 
         if(clockVal[selectVal]==24)clockVal[selectVal]=0;
         else if(clockVal[selectVal]==-1)clockVal[selectVal]=23;
       }
       else{ //min, sec 일때
         if(clockVal[selectVal]==60)clockVal[selectVal]=0;
         else if(clockVal[selectVal]==-1)clockVal[selectVal]=59;         
       }

     }   
     irrecv.resume(); // 다음값
    }
}

//LED 초기화
void segClear(){ 
  for(int i=0;i<8;i++){
    digitalWrite(segPin[i], LOW);        
  }
}
//LED 출력
void segOutput(int d, int Number, int dp){ 
  segClear();
  digitalWrite(digitPin[d], LOW); 
  for(int i=0;i<7;i++){
     digitalWrite(segPin[i], segValue[Number][i]);        
  }
  digitalWrite(segPin[7], dp);
  delayMicroseconds(1000);
  digitalWrite(digitPin[d], HIGH); 
}

지난시간의 아두이노 시계 소스를 이해 하셔야 오늘 리모콘 제어 부분을 이해할 수 있습니다. 단편적으로 설명하다 보니 아두이노시계 소스를 모른 상태에서 이해하시기가 좀 어려울 수 있겠네요. 사전학습을 미리 하시고 post를 읽어 주시기 바랍니다.

3. 결과


  • FUNC/STOP => 시간입력 ON/OFF
  • ST/REPT => 시간 입력 제어 위치
  • 세모 위/아래 => 숫자 증감/감소

FUNC/STOP 버턴을 누르면 시간을 입력 할 준비 상태가 됩니다. 세모 위/아래 바로 누르면 sec가 움직입니다. ST/REPT를 누르면 min 위치로 이동하고 다시 세모 위/아래를 누르면 min이 움직입니다. 또, ST/REPT를 누르면 hour 위치로 이동하고 다시 세모 위/아래를 누르면 hour이 움직입니다. 입력 할 시간 세팅이 끝나면 FUNC/STOP 버턴을 누르면 입력된 시간이 타이머변수에 세팅이 되고 그때부터 시간이 흘러가고 6-Digit 7-Segment Display에 현재 시간을 출력하게 됩니다.

한번 위의 순서대로 시간을 입력해 보세요.

아래 동영상은 회로도가 복잡해서 가상시뮬레이터를 실행하면 지연 렉이 발생합니다. 그래서 1초가 움직이는데 좀 오래 걸리는데 이게 잘못된 코딩은 아니고 지연 렉이니깐 감안하시고 보세요.


마무리


오늘은 가상시뮬레이터에서 가상으로 아두이노 시계를 만들었는데 거기에다가 리모콘을 연결하여 시간 입력을 실험 하였습니다. 약간 시간을 입력하는 로직이 좀 복잡할 수 있습니다. 혹시 위 post 이해가 안가지면 if문 단위로 가상 데이터를 넣어서 어떤 동작을 하는지 체크하시면서 테스트 해보시기 바랍니다.

참고로, 실제 구현 한것도 post에 올릴려고 했는데 IR Sensor를 찾지 못해서 실제로는 구현을 못해 봤네요. 만약 찾게 되면은 4-Digit 7-Segment Display 부품에 IR Sensor를 연결하여 실제로 실험한 post를 올리겠습니다.


댓글()

[아두이노] 4-Digit 7-Segment Display 가상시뮬레이터 실험

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

[아두이노] 4-Digit 7-Segment Display 가상시뮬레이터 실험



4-Digit 7-Segment Display 지난 시간에 실험했던 것을 가상시뮬레이터로 회로도을 만들어 보았습니다. 시계 회로도에서 추가로 6-Digit 7-Segment Display 형식으로 아두이노 시계를 만들어 보았으니깐 가상시뮬레이터로 한번 체험해보시고 수정할 코딩이 있으면 추가 코딩을 해보시기 바랍니다.

1. 4-Digit 7-Segment Display


1) 숫자 카운트 (0~9999)


  • 준비물 : 7-Segment Display 4개, 아두이노우노
  • 내용 : 7-Segment Display 부품의 핀 번호 순서에 맞게 아두이노우노에 연결하시오.


2-Digit 7-Segment Display 회로도에서 2개를 더 추가해서 4-Digit 7-Segment Display 회로도를 완성 했네요.



참조 링크 post에서 숫자 카운트 (0~9999) 소스를 실험하시면 됩니다. 주의할 점은 아두이노 핀 번호에 대한 변수지정은 수정해야 합니다.

const byte segPin[8]={7,3,A3,A1,A0,6,A4,A2}; //사용핀{a,b,c,d,e,f,g,dp} 순서대로임
const byte digitPin[4] = {8,5,4,A5}; //segment 위치 핀

이 부분을 아래와 같이 수정하시고 소스는 동일합니다.

const byte segPin[8]={2,3,4,5,6,7,8,9}; //사용핀{a,b,c,d,e,f,g,dp} 순서대로임
const byte digitPin[4] = {A0,A1,A2,A3}; //segment 위치 핀

결과


0.01초 단위로 카운트를 하는 결과입니다.


2) 시간 출력


  • 준비물 : 7-Segment Display 4개, 스위치버턴 1개, 아두이노우노
  • 내용 : 7-Segment Display 부품의 핀 번호 순서에 맞게 아두이노우노에 연결하고, 스위치 버턴은 2번핀에 연결하시오.


스위치 버턴이 추가 된 회로도 입니다. 지난시간의 실제 4-Digit 7-Segment Display 부품으로 생각하시면 됩니다.



참조 링크 post에서 아두이노 시계 소스를 실험하시면 됩니다. 여기에서 주의 할 점은 아두이노 핀 번호에 대한 변수지정입니다. 그 부분은 방금 했던 방식으로 그대로 변경하시면 됩니다.

const byte segPin[8]={7,3,A3,A1,A0,6,A4,A2}; //사용핀{a,b,c,d,e,f,g,dp} 순서대로임
const byte digitPin[4] = {8,5,4,A5}; //segment 위치 핀

이 부분을 아래와 같이 수정하시고 소스는 동일합니다.

const byte segPin[8]={2,3,4,5,6,7,8,9}; //사용핀{a,b,c,d,e,f,g,dp} 순서대로임
const byte digitPin[4] = {A0,A1,A2,A3}; //segment 위치 핀

결과


시리얼 모니터로 입력하면 시/분이 가상시뮬레이터의 7-Segment Display 부품에 출력됩니다. 참고로, 가상시뮬레이터가 회로도 복잡해서 실행 시 지연이 발생해서 1초 단위의 움직임이 발생하지 않습니다. "23:59:57"시간을 입력해서 3초 후 "00:00:00"이 되어야 하는데 움짤에 몇십초가 걸렸네요. 감안하시고 결과를 보시기 바랍니다. 그리고 스위치버턴을 누르면 12시 기준으로 시간 표시와 아니면 24시 기준으로 시간 표시로 변경할 수 있는 기능을 추가했습니다.


2. 6-Digit 7-Segment Display 회로도


  • 준비물 : 7-Segment Display 6개, 스위치버턴 1개, 아두이노우노
  • 내용 : 7-Segment Display 부품의 핀 번호 순서에 맞게 아두이노우노에 연결하고, 스위치 버턴은 2번핀에 연결하시오.


위에서 만든 회로도에서 2개의 7-Segment Display을 추가 연결했네요.



참조 링크 post에서 아두이노 시계 소스를 실험하시면 됩니다. 시/분 출력을 시/분/초까지 출력시키면 됩니다. 마무리 부분이니 이부분은 지난 시간의 소스를 가져와서 수정된 부분을 보도록 할까요.

[종합소스]

//a,b,c,d,e,f,g 상태값
byte segValue[10][7] = {
   {1,1,1,1,1,1,0}, //0
   {0,1,1,0,0,0,0}, //1
   {1,1,0,1,1,0,1}, //2
   {1,1,1,1,0,0,1}, //3
   {0,1,1,0,0,1,1}, //4
   {1,0,1,1,0,1,1}, //5
   {1,0,1,1,1,1,1}, //6
   {1,1,1,0,0,0,0}, //7
   {1,1,1,1,1,1,1}, //8
   {1,1,1,1,0,1,1}  //9  
};

const byte segPin[8]={3,4,5,6,7,8,9,10}; //사용핀{a,b,c,d,e,f,g,dp} 순서대로임
const byte digitPin[6] = {A0,A1,A2,A3,A4,A5}; //segment 위치 핀


const byte interruptPin = 2;//인터럽트핀

boolean state = false;//시간 출력형식 지정
extern volatile unsigned long timer0_millis; //타이머변수
unsigned long readTime; //현재타이머시간
int hour, min, sec;


void setup() {
  Serial.begin(9600);  
  
  pinMode(interruptPin, INPUT_PULLUP); 
  attachInterrupt(digitalPinToInterrupt(interruptPin), switchFn, FALLING);
  
  for(int i=0;i<10;i++){
    pinMode(segPin[i], OUTPUT);
  }
  for(int j=0;j<6;j++){
    pinMode(digitPin[j], OUTPUT);    
    digitalWrite(digitPin[j], HIGH); 
  }  
}

void loop() {   
  if(Serial.available()){
    String inString = Serial.readStringUntil('\n');    
    int index1 = inString.indexOf(':'); 
    int index2 = inString.indexOf(':',index1+1);   
    int index3 = inString.length();
    
    hour = inString.substring(0, index1).toInt();
    min = inString.substring(index1+1,index2).toInt();
    sec = inString.substring(index2+1,index3).toInt(); 
    
    timer0_millis = ((long)hour*3600+min*60+sec)*1000;   
  } 
  readTime = millis()/1000;
  if(millis()>=86400000){
     timer0_millis=0;
  }
  sec = readTime%60;
  min = (readTime/60)%60;
  hour = (readTime/(60*60))%24; 

  if(state==true){ //12시 or 24시 출력모드
    hour = hour%12;
  } 
  segOutput(5,sec%10,0); //sec 1의 자리
  segOutput(4,sec/10,0); //sec 10의 자리
  segOutput(3,min%10,1); //min 1의 자리
  segOutput(2,min/10,0); //min 10의 자리
  segOutput(1,hour%10,1); //hour 1의 자리
  segOutput(0,hour/10,0); //hour 10의 자리    
}

//12시 or 24시 출력 변경
void switchFn(){
  state=!state;
}

//LED 초기화
void segClear(){ 
  for(int i=0;i<8;i++){
    digitalWrite(segPin[i], LOW);        
  }
}
//LED 출력
void segOutput(int d, int Number, int dp){ 
  segClear();
  digitalWrite(digitPin[d], LOW); 
  for(int i=0;i<7;i++){
     digitalWrite(segPin[i], segValue[Number][i]);        
  }
  digitalWrite(segPin[7], dp);
  delayMicroseconds(1000);
  digitalWrite(digitPin[d], HIGH); 
}

결과


"23:59:57" 시간을 입력하지만 이 역시 회로도가 복잡하게 디자인 되어 지연 렉이 발생하네요. 3초가 꽤 길게 걸리네요. 코딩에 문제가 있는게 아니라 지연 렉이니깐 감안하시고 동영상을 보시기 바랍니다.


시계 결과가 너무 흐리게 나왔네요. 위에 링크 된 6-Digit 7-Segment Display 공개 회로도에 가셔서 테스트를 해 보시기 바랍니다.

마무리


오늘은 가상시뮬레이터로 3개의 회로도를 만들고 간단히 실험을 해 보았습니다. 실제로 제작해서 실험을 해보시는 것도 괜찮ㅅ브니다. 가상 시뮬레이터로 여러분들도 간접 체험을 할 수 있게 회로도를 개별적으로 만들어 놓았습니다. 위에 공개회로도를 링크 걸어놓은 곳에 가셔서 한번 테스트 해보세요. 그리고 따로 상상하는 부분이 있으면 가상시뮬레이터를 복사하셔서 직접 수정해 보세요.


댓글()

[아두이노] 4-Digit 7-Segment Display 시계 만들기

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

[아두이노] 4-Digit 7-Segment Display 시계 만들기



지난 시간에 4-Digit 7-Segment Display을 사용하는 방법을 실험 하였습니다. 오늘은 지난시간에 만든 회로도와 기본소스를 기반으로 아두이노 시계를 실제 만들어 보겠습니다.


1. 4-Digit 7-Segment Display 회로도


  • 준비물 : 4-Digit 7-Segment Display 1개, 스위치버턴 1개, 아두이노우노
  • 내용 : 4-Digit 7-Segment Display 부품의 핀 번호 순서에 맞게 아두이노우노에 연결하고, 스위치 버턴은 2번핀에 연결하시오.
  • 참조 : [아두이노] 4-Digit 7-Segment Display 제어

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


2. 시계 코딩



시간을 참조 Post에 가셔서 사전 학습 해주세요.

1) 시간 구하기


시간은 시리얼통신으로 입력을 받을 경우를 가정해서 시간을 구해 볼까요.

if(Serial.available()){
  String inString = Serial.readStringUntil('\n');    
}

inString에 시간 문자열을 읽습니다.

int index1 = inString.indexOf(':'); 
int index2 = inString.indexOf(':',index1+1);   
int index3 = inString.length();
    
hour = inString.substring(0, index1).toInt();
min = inString.substring(index1+1,index2).toInt();
sec = inString.substring(index2+1,index3).toInt(); 

위와 같이 코딩해서 hour, min, sec 구하게 됩니다. 지난시간에 설명을 다했기 때문에 간단히 넘어 갑니다.

timer0_millis = ((long)hour*3600+min*60+sec)*1000;   

hour, min, sec 값을 초로 변환하여 timer0_millis 변수에 저장하면 타이머는 입력된 시간을 기준으로 돌아가게 됩니다. 즉, 입력된 시간에서 타이머 시간이 흐른다고 보시면 됩니다.

readTime = millis()/1000;

sec = readTime%60;
min = (readTime/60)%60;
hour = (readTime/(60*60))%24; 

readTime 변수에는 millis()함수에서 읽은 타이머 시간값을 1000으로 나눈 몫 값을 저장하기 때문에 입력된 시간을 기준으로 1초 단위로 값이 변화가 일어 나겠죠. 입력시간을 기준으로 흐르는 시간값을 hour, min, sec값으로 다시 구하면 현재 시간을 구할 수 있게 됩니다.

이 값을 4-Digit 7-Segment Display로 출력하면 실제 아두이노 시계가 완성 됩니다.

2) 시간 타이머리셋 지정


24시간을 기준으로 시간을 리셋 시킬 예정입니다. 그러면 어떻게 코딩해야 할까요.

if(millis()>=86400000){ //타이머 리셋
     timer0_millis=0;
}

if문으로 1day는 24시간이고 이 시간은 "60x60x24" 가 됩니다. 여기에 1초(1000)을 곱해주면 86400000의 타이머 시간값이 만들어 집니다. 이 시간이 되면 timer0_millis=0으로 리셋 시키면 24시간 단위로 타이머는 처음부터 다시 돌게 됩니다.

3) 인터럽트 스위치버턴 활용


지난시간에 인터럽트 스위치버턴을 추가했는데 그러면 이 스위치버턴을 그냥 두기가 아쉬워서 어떤 기능을 넣을까 고민하다가 시간 표시를 제어하는 스위치버턴으로 변경해 보았습니다.

void switchFn(){
  state=!state;
}

스위치 버턴이 눌러지면 state 변수가 반전이 일어나게 했습니다.

if(state==true){ //12시 or 24시 출력모드
  hour = hour%12;
}   

이렇게 수정했습니다. 현재 시간 hour를 12시 기준으로 출력할 것인지 24시 기준으로 출력할 건지를 스위치버턴으로 제어하는 기능을 추가 했네요.

4) 종합소스


이제 지난시간의 소스에다가 위 설계 코딩을 삽입하면 아래와 같이 완성 됩니다.

//a,b,c,d,e,f,g 상태값
const byte segValue[10][7] = {
   {1,1,1,1,1,1,0}, //0
   {0,1,1,0,0,0,0}, //1
   {1,1,0,1,1,0,1}, //2
   {1,1,1,1,0,0,1}, //3
   {0,1,1,0,0,1,1}, //4
   {1,0,1,1,0,1,1}, //5
   {1,0,1,1,1,1,1}, //6
   {1,1,1,0,0,0,0}, //7
   {1,1,1,1,1,1,1}, //8
   {1,1,1,1,0,1,1}  //9  
};

const byte segPin[8]={7,3,A3,A1,A0,6,A4,A2}; //사용핀{a,b,c,d,e,f,g,dp} 순서대로임
const byte digitPin[4] = {8,5,4,A5}; //segment 위치 핀

const byte interruptPin = 2;//인터럽트핀

boolean state = false;//시간 출력형식 지정
extern volatile unsigned long timer0_millis; //타이머변수
unsigned long readTime; //현재타이머시간
int hour, min, sec;

void setup() {
  Serial.begin(9600);  
  
  pinMode(interruptPin, INPUT_PULLUP); 
  attachInterrupt(digitalPinToInterrupt(interruptPin), switchFn, FALLING);
  
  for(int i=0;i<10;i++){
    pinMode(segPin[i], OUTPUT);
  }
  for(int j=0;j<4;j++){
    pinMode(digitPin[j], OUTPUT);    
    digitalWrite(digitPin[j], HIGH); 
  }  
}

void loop() {   
  if(Serial.available()){ //입력시간 읽기
    String inString = Serial.readStringUntil('\n');    
    int index1 = inString.indexOf(':'); 
    int index2 = inString.indexOf(':',index1+1);   
    int index3 = inString.length();
    
    hour = inString.substring(0, index1).toInt();
    min = inString.substring(index1+1,index2).toInt();
    sec = inString.substring(index2+1,index3).toInt(); 
    
    timer0_millis = ((long)hour*3600+min*60+sec)*1000;   //입력시간 초변환
  } 
    
  if(millis()>=86400000){ //타이머 리셋
     timer0_millis=0;
  }
  readTime = millis()/1000; //현재시간 읽기
  sec = readTime%60;
  min = (readTime/60)%60;
  hour = (readTime/(60*60))%24; 

  if(state==true){ //12시 or 24시 출력모드
    hour = hour%12;
  } 
  segOutput(3,min%10,0); //min 1의 자리
  segOutput(2,min/10,0); //min 10의 자리
  segOutput(1,hour%10,1); //hour 1의 자리
  segOutput(0,hour/10,0); //hour 10의 자리    
}

//12시 or 24시 출력 변경
void switchFn(){
  state=!state;
}

//LED 초기화
void segClear(){ 
  for(int i=0;i<8;i++){
    digitalWrite(segPin[i], LOW);        
  }
}
//LED 출력
void segOutput(int d, int Number, int dp){ 
  segClear();
  digitalWrite(digitPin[d], LOW); 
  for(int i=0;i<7;i++){
     digitalWrite(segPin[i], segValue[Number][i]);        
  }
  digitalWrite(segPin[7], dp);
  delayMicroseconds(1000);
  digitalWrite(digitPin[d], HIGH); 
}

3. 결과


1) 분/초 출력


숫자를 4개 뿐이 출력을 못하기 때문에 간단히 시간이 흐르는 것을 확인 할 수 있게 출력 숫자는 분/초로 세팅했습니다.

  segOutput(3,sec%10,0); //min 1의 자리
  segOutput(2,sec/10,0); //min 10의 자리
  segOutput(1,min%10,1); //min 1의 자리
  segOutput(0,min/10,0); //min 10의 자리    

입력은 정상적으로 시간 "23:59:50"을 입력할 때는 내부적으로 시간은 정확하게 흘러갑니다. 여기서, 출력은 분/초만 표시될 뿐이죠


분/초만 아래와 같이 출력되어 정상적으로 시간이 흘러가네요.


2) 시/분 출력


종합 소스에 시/분 코딩을 그대로 실험한 결과입니다. 출력은 시/분만 출력됩니다.

  segOutput(3,min%10,0); //min 1의 자리
  segOutput(2,min/10,0); //min 10의 자리
  segOutput(1,hour%10,1); //hour 1의 자리
  segOutput(0,hour/10,0); //hour 10의 자리    

입력은 시간 "23:59:10"을 입력했습니다.

결과는 아래의 동영상에 보시면 "23:59"만 출력됩니다. 내부적으로 초는 계속 흘러가고 있습니다. 인터럽트 스위치버턴을 누르면 12시 기준으로 시간을 표시되고 다시 누르면 23시 기준으로 시간이 표시 되는 실험 결과를 보실 수 있을 꺼에요. 그리고 24시가 되면은 "00:00"으로 리셋되는 것 까지 보실 수 있을 꺼에요.

마무리


4-Digit 7-Segment Display 부품 밖에 없어서 안타깝게 6자리 시간 표시를 못했네요. 그러다보니 분/초 아니면 시/분으로 표시할 수 밖에 없었고 결과 동영상으로는 표현의 한계가 있었네요.

개별 7-Segment가 2개 있으면 추가로 연결해서 표시 할 수 있었겠지만 한개뿐이라 연결을 포기 했습니다. 아무튼 이런식으로 아두이노 시계를 만들 수 있다는 것을 보여드리는 걸로 정리를 할까 합니다. Stepper Motor로 시계를 표시하고 싶은데 4-Digit 7-Segment Display가 post 주제에서 나중에 응용편으로 기회가 된다면 그때 보여드릴게요. 주제에 맞게 post를 하기 위해서 그 실험은 생략하도록 하겠습니다.

가상시뮬레이터에서 6개 7-Segment를 이용해서 확실히 시계를 만들어서 보여드릴까 고민 좀 해보겠습니다. 여러분들은 실제 제작을 안하더라고 가상시뮬레이터에서 동일한 실험이 체험할 수 있게 귀찮은 회로선 연결을 해야하지만 한번 생각해보고 다음 post으로 올릴지 결정할께요. 될 수 있으면 가상시뮬레이터로 한번 제작해서 보여드리는 방향으로 할께요.


댓글()