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

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

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

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

댓글()