[아두이노] timer0_millis 리셋 시키기

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

[아두이노] timer0_millis 리셋 시키기



오늘은 아두이노 내부 시간을 출력하는 timer를 리셋시키는 실험을 하겠습니다. 아두이노에 전원이 공급되는 그 순간부터 0부터 timer가 돌기 시작합니다. 그리고 millis()함수을 통해서 현재 시간값을 얻게 됩니다. 시간값 1000은 1초를 나타내고 전원이 끊어질 때까지 계속 돌게 됩니다. 여기서, 아두이노가 무한으로 계속 숫자가 증가할까요. 그렇지 않습니다. 시간값은 unsigned long 자료형으로 그 값에는 한계가 있고 한계값을 넘게 되면은 리셋이 됩니다. 아두이노를 실험하면서 몇날 몇일을 계속 켜놓을 일은 거의 없겠죠. 그래서 대수롭지 않게 시간을 사용하는데 장기간 사용할 시에 시간변수 값을 제어하면 좀 더 효율적으로 시간을 관리할 수 있기 때문에 한번 이 변수에 대해서 이야기하면 좋을 것 같아서 오늘의 주제로 결정했습니다.

이제 간단히 이 변수에 대해서 실험을 해보도록 하겠습니다.

1. timer0_millis 변수


아두이노 어느 위치의 라이브러리에에 이 변수가 선언되어 있습니다. 이 값은 아두이노가 전원이 공급되면 0부터 1씩 증가합니다. 그리고 1000이 되면 1초의 시간이 흘러갑니다.

변수는 다음과 같습니다.

unsigned long timer0_millis;

long형의 자료형인데 unsinged(부호가없는) long형이니깐 음수의 숫자만큼 양수로 표현될 수 있기 때문에 양수로 표현되는 숫자는 그냥 long형으로 표현한 숫자보다 2배의 숫자로 시간을 표현할 수 있습니다.

unsigned long => 4,294,967,295 (2 ^ 32 - 1)

대충 이정도의 양의 숫자를 표현할 수 있는데 timer0_millis으로 날짜로 계산해보면은 다음과 같습니다.

1초 => 1000
1분 => 60초
1시간 => 60분
1일 => 24시간

=> 1일 = 1000*60*60*24 = 86,400,000

4,294,967,295/86,400,000 = 49.71026961805556일

이렇게, 계산이 되어 나옵니다. 한달 이상을 타이머가 돌다가 리셋이 되겠죠. 아두이노를 실험을 할 때 이정도로 오랫동안 켜놓고 하는 실험은 드물겠지요.

뭔가 직접 시간에 대한 제어가 필요할 것 같지 않나요. timer0_millis의 변수를 접근할 수 있다면 원하는 시간 만큼 타이머가 돌다가 강제적으로 리셋 시킴으로써 어떤 시간단위로 동작 패턴을 만들어 낼 수 있게 됩니다.

타이머를 건들 수 있다는 것은 시간을 제어할 수 있다는 의미랑 같습니다. 지금은 딱히 활용할 곳이 생각이 안나시더라도 시간을 건들 수 있다는 것을 알고 있으면 나중에 시간에 관계된 제어를 할 때 유용하게 사용할 수 있을 꺼에요.

2. millis() 함수



millis()함수로 현재 timer값을 가져올 수 있습니다. millis()함수가 호출하면 반환되어 나온 값을 통해서 시간을 제어할 수 있습니다. 우리고 delay(1000)이라는 1초 단위로 부품을 제어했는데 delay(1000)은 강제적으로 아두이노를 쉬게 했지만 millis()함수를 이용하여 강제적인 delay 없이 millis()값을 통해서 원하는 시간 단위로 부품을 제어 할 수 있게 됩니다. 즉, millis()함수는 딜레이 없이 시간을 제어하고 싶을 때 자주 사용되는 함수입니다.

복습차원으로 링크 걸어놓은 delay()함수 없이 delay 제어를 살펴봅시다.

 timeVal=millis();
 if(timeVal-previousVal>=1000){   
    state=!state;
    digitalWrite(redPin,state);
    previousVal=timeVal;
  }

timeVal 값은 millis()함수의 현재 timer0_millis의 값을 가져오게 됩니다.

timeVal-previousVal>=1000
현재시간값과 이전 시간값의 차이가 1000 이상이면은 1초이상이면 참이되는 IF문입니다. 즉, 시간을 1초 단위로 딜레이 없이 제어할 수 있다는 의미이고요. 1초 단위로 "state=!state"로 상태값이 반전으로 redPin에 true or false 상태가 1초단위로 왔다 갔다 하게 됩니다. 그리고 1초 단위가 만족할 때 "previousVal=timeVal"이전시간값에 저장합니다. 다음 1초를 준비하게 됩니다.

timer0_millis 변수값이 무척 중요하지요. 이 변수값을 건들 수 있게 되면 마음데로 시간을 제어 할 수 있게 됩니다. 지금까지는 규칙적인 시간을 흘러갔지만 timer0_millis변수 값을 건들 수 잇게 되면 불규칙적인 시간으로 움직이게 할 수 있고 일정 패턴 시간 단위로 시간을 리셋 시킬 수 있습니다. timer0_millis변수를 건들 수 있다는 것은 시간을 마음대로 제어 할 수 있기 때문에 많은 것들을 할 수 있게 됩니다.

그럼 간단히 실험을 통해서 제어를 해보겠습니다.

3. 회로도 구성


  • 준비물 : 아두이노우노
  • 내용 : 코딩으로 실험 하기 때문데 따로 준비사항은 없습니다.


4. 코딩


extern volatile unsigned long timer0_millis;

이 한줄이 timer0_millis 변수를 건들 수 있게 됩니다. 프로그램 전체에 영향을 주는 extern(외부전역변수) 인데 이상태로만 표현해도 접근 할 수 있지만 volatile 추가로 더 붙입니다. 그 이유는 volatile가 없이 선언하면 컴파일시 컴파일 최적화로 만들어지지만 volatile을 붙이면 컴파일 최적화가 아닌 사용자 의도에 따라 변경이 가능한 컴파일이 됩니다.

아무튼 이렇게 timer0_millis변수를 선언하면 해당 시간 관련 라이브러리에 선언된 timer0_millis변수의 값을 이 변수에 의해서 값이 변경되게 됩니다. 아두이노 로직을 짠 코딩안에서 timer0_millis변수 값을 바꾸면 millis()함수로 반환 되어 나오는 시간값은 바꾼 값으로 나오게 됩니다.

간단히 코딩으로 변경이 되는지 살펴 볼까요.

extern volatile unsigned long timer0_millis; //타이머변수

unsigned long timeVal=0;  //현재시간값 저장변수
unsigned long previousVal=0; //이전시간값 저장변수

const int redPin = 13; //RED LED PIN
boolean state = false; //LED 상태값

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

void loop()
{
  timeVal=millis();
  //1초 단위로 RED LED 깜박임
  if(timeVal-previousVal>=1000){ //1초단위
    state=!state;
    Serial.println(timeVal);
    digitalWrite(redPin,state);
    previousVal=timeVal; 
  }  
  //6초후 timer 리셋
  if(timeVal>=6000){
     timer0_millis=0;  //6초 후 시간값 리셋
     previousVal=0;
     Serial.println("reset");    
  }
}

두개의 IF문이 있습니다.

  if(timeVal-previousVal>=1000){ //1초단위
     명령문
     previousVal=timeVal; 
  }

위 IF문은 "현재시간-이전시간>=1000" 만족하면인데 이것은 현재시간이 이전시간과의 차이가 1000이상되면 1초이상이 되기 때문에 1초단위로 특정 명령을 수행할 수 있게 됩니다. 그리고, 명령을 수행한 뒤에 이전시간변수에 현재시간을 저장해야지 다음 1초를 준비할 수 있게 됩니다.

if(timeVal>=6000){
 timer0_millis=0;  //6초 후 시간값 리셋
}

위 IF문은 "현재시간>=6000"이 될때 참인데 이것은 현재시간이 6초이상이 되면 참으로 timer0_millis변수 값을 0으로 초기화 함으로서 타이머시간을 리셋을 시키게 됩니다. 즉, 강제적으로 다시 처음부터 millis()함수의 반환되어 나오는 값이 0부터 시작하게 됩니다.

5. 실험 결과



6. 타이머 시간 변경을 통한 접근 제어


위 실험에서는 간단히 6초 단위로 타이머 시간을 리셋 시켰습니다. 우리가 어떤 부품을 시간 단위로 제어를 한다고 가정했을 때 상황을 설정할 경우는 각 부품의 접근에 대해 제어할 수 있게 됩니다.

가령, 1초때 A부품, 5초때 B부품, 10초때 C부품이 있다고 생각해봅시다.

if(timeVal-timeA>=1000){ //1초단위

     timeA=timeVal; 
}
if(timeVal-timeB>=5000){ //5초단위

     timeB=timeVal; 
}
if(timeVal-timeC>=10000){ //10초단위

     timeC=timeVal; 
}

이럴 때, 상황 조건에 따라서 특정 측정값이나 또는 계산 처리식에서 특정 부품을 건너 뛰기가 가능하게 됩니다. 즉, A->B->C 동작이 순차적으로 원래 진행되는데 특정 상황이 발생하면 A->B에서 동작을 마무리 할 수 있습니다.

if(특정상황 조건식){
  timer0_millis=0;
  timeA=0;
  timeB=0;
  timeC=0;
}

시간을 제어한다는 것은 자신이 아두이노의 기본 순차적인 동작을 자신이 의도하는 방향으로 불규칙적인 시간을 통해 원하는 방향으로 동작을 제어도 가능하게 됩니다. 참 재밌는 접근 방식이지요.

마무리


복습차원으로 두개의 IF문으로 첫번째는 delay()함수 없이 시간을 제어한 문장인데 아두이노에서 가장 많이 사용하는 표현입니다. 두번째는 "timer0_millis=0"으로 타이어를 리셋시키는 문장인데 이 부분은 아직 초보분들은 쓸만한 곳은 없습니다. 하지만 이 표현을 기억해 뒀다가 나중에 코딩 로직에 따라서 이 표현을 쓸 수 있으니깐 꼭 기억해 두셨으면 합니다.

가령, 시간단위로 어떤 부품들을 제어하는데 그 시간단위를 어떤 조건에 따라서 시간을 건너 뛸수도 있고 아니면 처음부터 다시 어떤 부품의 동작을 시작하도록 할 때 사용할 수 있습니다. 아직은 와 닿지 않을 꺼에요. 그래도 외부변수 선언하는 저 한줄만 기억하면 timer0_millis 변수를 마음대로 제어 할 수 있으니깐 알아만 두세요.

오랫만에 가상시뮬레이터로 실험할 수 있는 post였네요

댓글()

[아두이노] 타이머를 이용한 인터럽트(Interrupt) 제어

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

[아두이노] 타이머를 이용한 인터럽트(Interrupt) 제어



어제는 인터럽트 핀을 이용한 하드웨어 인터럽트에 대해 살펴보았습니다. 오늘은 타이머 함수를 이용한 인터럽트를 배워보도록 하죠. 위 참고자료의 해당된 링크로 가시면 MsTimer2 and FlexiTimer2 라이브러리가 있는데 그중에서 MsTimer2 라이브러리를 선택하여 실험해 보도록 하겠습니다.

1. 타이머를 이용한 인터럽트


  • MsTimer2 함수
MsTimer2::set(unsigned long ms, void (*f)()) //타이머 세팅
MsTimer2::start() //타이머 시작
MsTimer2::stop() //타이머 종료

대충 보시면 함수가 그렇게 어렵지 않죠

예)

MsTimer2::set(500, flash) 

0.5초 간격으로 flash 함수를 호출한다고 세팅합니다. start() 함수는 타이머를 시작하라는 명령이고 stop() 함수가 선언되지 않는 이상 아두이노는 0.5초 간격으로 무조건 flash()함수를 호출하게 됩니다.

FlexiTimer2 라이브러리도 비슷하니깐 한번 링크된 곳에 가셔서 보시면 대충 이해가 되실꺼에요.

2. 회로도 구성


  • 준비물 : LED 2개, 저항 220옴 2개, 아두이노우노, 뻥판
  • 내용 : 타이머 인터럽트를 발생시킨 결과를 Blue LED(발광다이오드)에 불이 들어오게 하고 기본 동작은 Red LED(발광다이오드)에는 불이 들오게 한다.


회로도를 보면 간단합니다. 12번 핀에 MsTimer2를 이용해서 0.5초 시간이 되면 5V의 전류를 보내 Blue LED를 제어하고 13번 핀은 loop()함수에서 1초 단위로 불이 깜박이게 할 예정입니다. 여기서 loop()함수의 1초 단위로 깜박이는 기본동작과 상관없이 강제적으로 인터럽트를 발생시켜서 12번핀에 전류 공급을 공급하게 됩니다.
쉽게 말해서 1초 단위로 깜박이는 로직이 있는데 어떤 라인의 명령이 수해되고 있던 상관없이 타이머 시간이 되면 강제적으로 특정 동작을 수행하게 만든다고 생각하시면 됩니다.

3. 코딩


  • 사용함수 : pinMode(), digitalWrite(), MsTimer2::set(), MsTimer2::start();
  • 내용 : 간단히 가변저항 조절기로 조절하면 흐를 전류를 조절할 수 있고 그 조절된 전류 값을 3색 LED에 출력값으로 해서 색을 자유롭게 만들어 낸다.
  • 참고 : MsTimer2

복습

  • pinMode(사용핀, OUTPUT) : 사용핀은 출력모드
  • digitalWrite(사용핀, 상태) : 디지털출력핀에 상태가 HIGH(5V) or LOW(0V)를 선택한다.
  • MsTimer2::set(호출시간, 호출함수명) : 타이머 세팅(500은 0.5초라는 것만 기억)
  • MsTimer2::start() : 타이머 시작
  • MsTimer2::stop() : 타이머 종료

설계

  1. Red LED은 loop()함수에서 기본동작으로 1초단위로 깜박이게 해야지
  2. Blue LED은 0.5초 간격으로 깜박이게 해야지

코딩을 전체적으로 하면

이 예제가 너무 잘 코딩 되어있는 거라서 따로 코딩 예제를 안만들고 인용을 하겠습니다. 메인 동작은 Red 핀이 1초 단위로 loop()함수 내에서 반복합니다. 타이머 함수의 경우는 setup() 함수에서 한번만 선언하면 됩니다. 왜냐면 loop()에 선언한다고 생각을 해보세요. 매번 타이먼을 세팅하고 타이머를 시작하고 거기다가 중요한것은 loop()에 넣으면 타이머가 loop() 반복순환문에 일부가 되어서 타이머 자체가 loop()의 기본동작이 되어 버리게 됩니다. 결론은 setup()함수에서 타이머를 작동시키고 loop()은 자신의 기본동작만 수행해서 타이머는 별거의 존재로 동작하도록 배치해야겠죠.

하지만 MsTimer2::start() or MsTimer2::stop() 함수는 loop()안에 넣을 수 있습니다. 타이머를 무조건적으로 동작시킨다면 setup()함수에서 선언하는게 맞지만 loop()함수내에서 타이머가 원하는 조건을 충족하기 전까지는 타이머를 동작안시키고 충족되면 타이머를 동작하게 할 수 있는 로직으로 표현 할 때 loop()함수 내에서 선언하게 됩니다.

#include <MsTimer2.h> //가상시뮬레이터 경우는 삭제하고 해당 라이브러리 파일을 여기에다 복사

int red= 13;
int blue = 12;
  
void flash() {  
  static boolean output = HIGH;
  digitalWrite(blue, output);
  output = !output;
}

void setup() {
  MsTimer2::set(500, flash); // 500ms period
  MsTimer2::start();
}

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

4. 가상시뮬레이터에서 외부라이브러리 함수를 사용하기


가상시뮬레이터에서는 기본 아두이노우노에서 제공되는 라이브러리들을 그냥 쓸 수 있습니다. 하지만 특수한 라이브러리는 제공하지 않습니다. 그러면 가상시뮬레이터에서는 어떻게 실험해야 할지 성멸을 드리도록 하겠습니다.

우선 라이브러리 파일은 두개의 파일로 구성됩니다. 예로 MsTimer2 라이브러리에는 MsTimer2.h, MsTimer2.cpp 파일이 있습니다. 이 두개를 실제로 아두이노 IDE에서 라이브러리 추가해야 사용이 가능합니다.

실제 라이브러리 추가는 LCD16x2 I2C(LiquidCrystal_I2C) 제어(아두이노)의 포스팅에 라이브러리 추가하기를 보시면 됩니다.

하지만 가상시뮬레이터에서는 라이브러리 자체를 추가할 수 없습니다. 라이브러리 자체를 코딩해서 사용해야 합니다. 그럼 사용하기 위해서는 두 파일을 가상시뮬레이터 코딩창에 다 복사해야 합니다.


MsTimer2.h 파일을 전체 복사해서 가상시뮬레이터 코딩창에 붙여넣기 한다음 다음 MsTimer2.cpp 파일에서 

#include <MsTimer2.h>
 라인을 삭제하고 나머지 전체를 복사해서 가상시뮬레이터 코딩창에 이여서 붙여넣기 하시면 됩니다. 그리고 setup(), loop()함수 코딩을 하시면 해결 됩니다.


대충 이런식으로 복사하시면 특수 라이브러리 파일을 연결해서 사용할 수 있습니다.

5. 결과


회로도 설계부터 외부라이브러리 파일을 복사해서 붙이고 코딩을 한 결과를 돌렸을때 결과를 자세히 보시면 대충 어떤식으로 해야할지 감이 잡히실꺼에요. 타이머 라이브러리 함수를 사용해서 LED에 불이 들어오는게 가상시뮬레이터에서 좀 약하게 들어와서 동영상을 보면 잘 안보일 수 있지만 자세히 보시면 두개의 LED들이 따로 독립적으로 움직이는 것처럼 보이실 꺼에요.


마무리


인터럽트는 끼어들기 함수라고 생각 하라고 했죠. 하드웨어 인터럽트에서는 인터럽트 핀에 특정한 조건이 만족했을때 호출 되었습니다. 하지만 오늘 배운 내용에서는 특정한 조건이 만족했을때라기 보다는 타이머로 강제적으로 일정 시간이 되면 호출하는 소프트웨어 인터럽트 였습니다. 둘 차이를 한번 잘 생각해보시고 나중에 어디에 사용할지 상상을 펼쳐 보세요.

한번 둘을 합쳐진 로직을 설계해보면 어떨지 생각해 보세요. 가령 하드웨어 인터럽트의 핀에 상황이 발생했을때 MsTimer2::start(), MsTimer2::stop() 함수가 실행 되게 설계를 한번 해보세요.


댓글()