'ISR'에 해당되는 글 5건

  1. 2017.05.08 인터럽트의 처리(2)
  2. 2017.03.18 인터럽트와 volatile 지시자 1
  3. 2017.03.12 인터럽트의 처리(1)
  4. 2014.04.23 C 언어로 개발시 유의사항
  5. 2014.04.20 변수 vs. 메모리
Embedded Lecture/Arduino2017. 5. 8. 20:34


인터럽트(interrupt)란 지정된 핀의 신호가 원하는 조건과 일치하면 미리 등록한 인터럽트 callback 함수(ISR; Interrupt Service Routines)를 자동으로 호출해주는 기능입니다. 이 ISR 함수는 사용자가 만든 함수이며, 이때 실행 중이던 loop() 함수 내부의 루틴은 인터럽트 callback 함수가 끝날 때까지 멈추게 됩니다.


즉, 특정 핀의 입력 상태가 바뀔 때 Arduino는 이를 자동으로 감지해서 모든 동작을 잠시 멈춘 다음, ISR(Interrupt Service Routines) 이라 불리는 미리 지정된 함수를 실행하고, 다시 원래 작업으로 복귀한다는 것입니다. 이를 '하드웨어 인터럽트'라 부릅니다. 이 외에도 비슷한 타이머(timer) 인터럽트가 있는데 이는 사용법이 전혀 다르므로 이 글에서 다루진 않습니다.

♧ 타이머 인터럽트 - AVR 칩을 비롯한 임베디드 시스템에서는 시간을 재기 위해서 타이머(Timer)/카운터(Counter)를 내장합니다. 만일 타이머/카운터가 없다면, 소프트웨어적으로 시간지연 함수를 사용해야 함으로써 MCU는 동작을 멈추게 되고, 이로 인해 MCU의 효율을 떨어뜨리게 된다는 것입니다.

뿐만 아니라 이와 같은 내장 타이머/카운터는 정확한 시간을 잴 수 있으며, MCU가 다른 작업과 병행할 수 있고, 미리 설정한 조건에서 타이머 인터럽트도 발생하게 할 수 있다는 것입니다. 이러한 각각 인터럽트들은 동시에 발생할 수도 있으므로 우선 순위를 갖습니다.


Arduino의 인터럽트 핀

Arduino Uno 기준으로 2개의 인터럽트 핀이 할당되어 있습니다. 즉, Number 0 (D2), Number 1 (D3). 몇개의 보드별 지원되는 인터럽트 핀은 다음과 같습니다. 인터럽트 callback(ISR)을 등록할 때 유의할 점은 핀 번호가 아니라 반드시 인터럽트 넘버를 사용한다는 것입니다. 예를 들어, 다음 표에서 Leonardo 보드는 Uno 보다 많은 5개의 인터럽트 핀을 제공합니다. 그리고 다섯번째 인터럽트 핀(int4)은 Number 7이라는 것입니다. 참고로 Arduino Due 보드는 모든 핀에 인터럽트가 지원됩니다.


 보드

 int0

 int1

 int2

 int3

 int4

 int5

 Uno/Ethernet

 2

 3

 x

 x

 x

 x

 Mega2560

 2

 3

 21

 20

 19

 18

 Leonardo

 3

 2

 0

 1

 7

 x


인터럽트 callback 함수(ISR)는 입력 인자가 없고 반환값이 없습니다. 즉 파라미터를 전달하거나 리턴할 수가 없다는 것입니다. 게다가 ISR 함수 내에서는 delay() 함수를 사용할 수 없습니다. 또한 milli second의 시각을 가져오는 함수인 millis()를 사용하더라도 값이 증가하지는 않습니다. delayMicroseconds()의 경우에는 인터럽트에 독립적이므로 정상 동작합니다.


뿐만 아니라 ISR 함수 내에서는 Serial data를 읽을 경우 값이 소실되며, 이전 글에서 언급했던 것처럼 ISR 함수 내에서 업데이트 되는 전역 변수는 volatile로 반드시 선언되어야 합니다. ISR 함수를 만드는 요령은 최대한 짧고 빠르게 수행되도록 간결하게 작성해야 합니다. 왜냐면 이 코드가 길어지는 만큼 CPU는 멈추어 메인 작업을 수행하지 않아 효율적이지 않다는 것입니다.


또한 여러 개의 ISR 함수가 등록되어 있더라도 동시에 수행되지는 않습니다. 블루투스 모듈과의 통신을 위해 주로 사용되는 Software Serial 라이브러리의 경우, 내부적으로 인터럽트를 사용하는 것으로 알려져 있습니다. 따라서 Uno 보드의 경우 D2, D3 에 연결해야만 정상 동작 가능합니다. 따라서 이를 회피하기 위해서 D0, D1 핀에 연결해서 Hardware Serial로 동작시킬 수 있습니다.



Posted by Nature & Life
Embedded Lecture/Arduino2017. 3. 18. 16:33


AVR 칩을 사용하는 Arduino Uno 보드 혹은 다른 임베디드(Embedded) 시스템에서 전역 변수(globlal variable)는 주메모리인 SRAM에 저장되고, 지역 변수(local variable)는 레지스터(register) 영역에 저장됩니다. 그러나 인터럽트 서비스 루틴(ISR)에서의 지역 변수는 SRAM의 Stack 영역에 저장되므로 빠른 인터럽트 처리를 위해서 루틴 내에 잦은 지역 변수의 사용은 바람직하지 않다는 것입니다.


사용자의 코드를 컴파일러(Compiler)는 나름데로 최적화시키는데, 예를 들어 사용자가 전역 변수 지정을 하였지만 그 값이 시종일관 변하지 않는 변수는 컴파일러가 임의로 상수처럼 여겨 레지스터 영역에 저장하게 한다는 것입니다. 물론 한정된 레지스터 영역의 리소스를 낭비하는 것 아니냐고 생각할 수 있지만, 컴파일러 입장에서는 빠른 실행 속도를 위해서 SRAM 보다는 레지스터 영역에 저장한다는 것입니다.


그러나 임베디드 시스템의 경우에 인터럽트 서비스 루틴에서 전역 변수의 갱신이 있었다 하더라도, 컴파일러는 실제 런타임 시 처럼 외부에서 인터럽트가 걸리는 상황이 아니므로 그 값은 변하지 않는다고 생각하여 결국 레지스터에 저장하고 주메모리 상에 변경된 값의 반영이 없다는 것입니다.


이런 상황을 위해서 지시자(directive)인 'volatile'의 선언은 변수형 앞에 두어 컴파일러가 임의로 판단하여 그 변수를 레지스터에 할당하지 못하도록 한다는 것입니다. 즉, 어떤 변수를 volatile로 지정하면 그 변수는 레지스터의 임시 저장소가 아니라 SRAM에서 직접 읽어오도록 컴파일한다는 것입니다.


결론적으로 Arduino 코딩의 경우 보통은 volatile로 정의할 필요는 없으나 인터럽트 서비스 루틴 내부에서 그 값이 변경되는 변수는 반드시 volatile로 선언해야 실시간으로 변경되는 데이터 값을 ISR 루틴 외부에서 읽어올 수 있게 된다는 것입니다.


int pin = 13;

volatile int state = LOW;


void setup()

{

  pinMode(pin, OUTPUT);

  attachInterrupt(0, blink, CHANGE);

}


void loop()

{

  digitalWrite(pin, state);

}


void blink()

{

  state = !state;

}


위의 예제에서 blink() 함수는 인터럽트 서비스 루틴으로 0번 인터럽트 핀의 상태가 변할 때마다 13번에 연결된 LED를 교대로 점멸하게 됩니다. 만일 state 변수에 volatile 선언이 없었다면, 비록 blink() 함수 내에 state 값이 변하더라도 이 함수를 벗어나서는 state 값이 갱신되지 않기 때문에 0번 인터럽트의 상태 변화에 LED의 토글(toggle) 현상이 보이지 않는다는 것입니다.



'Embedded Lecture > Arduino' 카테고리의 다른 글

아날로그 입력 및 온도계 예제  (0) 2017.03.19
아날로그 입력  (0) 2017.03.18
아두이노의 TWI(I2C) 통신  (0) 2017.03.18
아두이노의 시리얼 통신  (0) 2017.03.18
인터럽트의 처리(1)  (0) 2017.03.12
Posted by Nature & Life
Embedded Lecture/Arduino2017. 3. 12. 20:40

 

프로그램은 사용자가 편의를 위해서 코딩합니다. 즉 무엇인가 자동으로 일괄처리를 하기 위함인데 이는 마이크로 프로세서에서도 마찬가지 입니다. 하지만 프로그램 혹은 알고리즘(algorithm)은 일방적이기 보다는 주로 사용자와 대화형이고 키보드 뿐만 아니라 다양한 입출력 장치들과 각종 센서들에서 입력되는 정보로 프로그램과 대화하게 됩니다.


프로그램이 진행 중, 사용자에게 요구 사항이 있으면 팝업 창을 띄우고 묻기 위해 대기할 수 있지만, 사용자나 센서가 문득 프로그램에 어떤 입력을 주고 싶은 경우도 있습니다. 이를 '끼어든다'고 하여 인터럽트(interrupt)라 부르고 프로그램은 하던 작업을 멈추고 가급적 빨리 이를 처리하고 원래 수행하던 작업으로 되돌아가야 합니다. 이처럼 즉시 처리해야 하는 특수한 이벤트인 인터럽트가 발생하면, 프로세서는 그 이벤트를 처리할 작업들이 수록된 함수를 호출하는데, 이 함수를 인터럽트 서비스 루틴(ISR; interrupt service routine)이라고 합니다.

 


예를 들어, 키보드에 특정 키가 눌러저 있는지 이벤트를 감지하려면 모든 프로그램은 아두이노의 loop() 함수 같이 반복 수행되는 루프 내에서 항상 이 키의 상태를 검사해야 합니다. 이를 '소프트웨어 인터럽트' 혹은 '폴링(polling) 방식'이라고 하고 마이크로 프로세서의 경우에는 보다 정확하고 신속한 '하드웨어 인터럽트(인터럽트 방식)'도 제공하는데, 이를 이용하면 소프트웨어적으로 이 버튼의 상태를 항상 검사할 필요가 없다는 것입니다. 때문에 프로그램은 불필요한 부분에 시간을 낭비할 필요가 없게 됩니다. 그러므로 폴링 방식은 단일 이벤트 처리에 적합하고 인터럽트 방식은 다중 이벤트 처리에 효율적입니다. 


즉, MCU는 다른 작업을 하고 있다가 '키가 눌려지면' 그 즉시 하드웨어 인터럽트를 발생하여 하던 일을 멈추고 이를 처리할 ISR 함수를 호출하고, ISR이 끝나면 하던 일로 되돌아가게 됩니다. Arduino Uno 보드의 경우 인터럽트를 처리할 수 있는 핀은 2번과 3번 핀으로 이는 MCU에서 지원하기 때문이며 각각 INT0, INT1이라고 부릅니다.


ISR을 사용하기 위해서는 attachInterrup()라는 함수로 미리 알려야 합니다.


attachInterrupt( pin, ISR, mode)

pin : 첫 번째 인자로 인터럽트로 사용할 핀을 지정합니다. INT0의 경우 '0', INT1의 경우 '1'이 됩니다.

ISR : 두 번째 인자로 인터럽트가 걸렸을 때 호출할 함수의 이름으로 사용자가 만든 함수 이름입니다.

mode : 세 번째 인자로 RISING, FALLING, CHANGE, LOW 중 하나가 됩니다.



'RISING'이란 인터럽트 핀에 신호가 '0'에서 '1'로 변하는 순간이며 'FALLING'은 그 반대입니다. 'CHANGE'는 RISING과 FALLING 모두를 의미하며 'LOW'는 logical '0'값일 때를 의미합니다. ISR 함수는 입력 인수를 받을 수 없고 반환값도 void형이어야 하며, attatchInterrupt() 함수는 통상 setup() 함수 안에서 사용되어 초기에 인터럽트를 설정하게 됩니다.


다음은 터치 센서를 한 번 터치하면 LED가 점등하고 다시 터치하면 LED가 커지도록 동작하는 스케치를 예를 들어 봅니다. 이를 위해서 Ardunio Uno 보드의 11번에 부저를 연결하고, 3번 핀(INT1)에 터치 센서를 연결하여 하드웨어 인터럽트를 사용할 것입니다. 소스 코드는 다음과 같습니다.


#define TS 3

#define BUZ 11

void setup() {

    pinMode(LED_BUILTIN, OUTPUT);

    pinMode(BUZ, OUTPUT);

    pinMode(TS, INPUT);

    attachInterrupt(INT1, toggleLed, RISING);

}

void loop() {

    digitalWrite(BUZ, HIGH);

    delay(50);

    digitalWrite(BUZ, LOW);

    delay(450);

}

void toggleLed() {

    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));

}


setup()함수 내에 attachInterrupt(INT1, toggleLed, RISING)를 삽입하여 INT1을 사용하였고 ISR로써 toggleLed() 함수를 추가하였습니다. 세 번째 인자인 mode가 RISING 이므로 toggleLed() 함수는 외부 인터럽트인 3번 핀의 신호가 '0'에서 '1'로 바뀌는 rising edge에서 하드웨어적으로 자동 호출됨을 의미합니다.


loop() 함수에서는 부저를 단지 0.5초마다 한 번씩 울리는 일이 전부이며, 터치 센서가 눌러질 때마다 toggleLed() 함수 내에 digitalWrite() 함수가 실행되고 이는 아두이노 보드 내 이전의 LED 값에 따라 그 반대의 값을 쓰게 됩니다. 즉 켜져 있으면 크고 커져 있으면 켜게 됩니다. 참고로 digialRead() 함수는 단일 인자를 가지며 읽고자 하는 핀 번호를 인자로 넘겨주면 그 logical 값을 반환합니다.



'Embedded Lecture > Arduino' 카테고리의 다른 글

인터럽트와 volatile 지시자  (1) 2017.03.18
아두이노의 TWI(I2C) 통신  (0) 2017.03.18
아두이노의 시리얼 통신  (0) 2017.03.18
LED 깜박이기  (0) 2017.03.12
아두이노(Arduino) 코딩의 시작  (0) 2017.03.12
Posted by Nature & Life
Embedded Programming/C2014. 4. 23. 09:33


AVR은 C 언어로 개발하는 경우에도 assembler 못지 않게 최적화된 코드를 생성하기에 다양한 장점이 있습니다. 그 중에서도 avr-gcc라는 컴파일러를 사용하는 것으로 avr-gcc는 전 세계 다양한 사용자(소위 Hacker)들에 의해 개발되어 공유하는 것으로 시행착오를 통하여 결점이 최소화되었다는 것입니다.


AVR을 C 언어로 개발시에는 일반적인 O/S 환경에서가 아닌 AVR 하드웨어에 기반을 두기 때문에 몇 가지 유의사항들이 있습니다.

 

 

 

 

1) 일반적인 C 언어와는 달리 이진수(binary)를 사용할 수 있으며, Boolean 타입의 변수는 사용할 수 없습니다. 

이진수는 예를 들어 0b0100 식으로 표현하며 Boolean 타입 변수 대신에 int형의 0과 1로 대체하여 사용하면 됩니다.


2) 인터럽트 서비스 루틴은 가능한 한 간결하게 작성합니다.

일반적인 C 언어와는 달리 AVR 하드웨어는 외부 인터럽트나 Timer, USART, ADC 등의 주변장치들로부터의 인터럽트를 처리하기 위한 ISR() 함수로 반환값이 없으며 특별히 호출하지도 않습니다. 이러한 인터럽트 서비스 루틴(Interrupt Service Routine, ISR)은 다음과 같은 이유로 최대한 간결하게 작성해야 합니다.


첫째는 인터럽트 함수 내에서는 지역변수가 Register가 아닌 Stack에 저장되므로 처리가 느리기 때문이며, 

둘째는 하나의 인터럽트 처리가 길어지면 이어서 발생하는 다른 인터럽트 처리를 실행하지 못하기 때문입니다.


대부분의 인터럽트는 Timer의 overflow를 처리하기 위한 것으로 만일 인터럽트를 제때에 처리하지 못한다면 AVR 칩이 외부 시스템과 실시간으로 작동하는 경우에는 치명적인 오류가 생길 수 있게 됩니다. 그러므로 인터럽트 발생시 많은 작업을 요구하는 경우에는 인터럽트 루틴에서 Flag를 간단히 설정하여 개시하고 메인 루틴에서 이를 처리하게 해야 합니다.


3) 인터럽트 서비스 루틴 내에서 전역변수들은 volatile로 선언합니다.

컴파일시 최적화를 위해 컴파일러는 나름데로 그 상황에 맞는 상수값으로 처리하도록 하는 경우가 있는데, 이것이 인터럽트 서비스 루틴에서는 의도하지 않은 오류를 초래하기 때문입니다. 그러므로 인터럽트 서비스 루틴에서는 전역변수들은 volatile로 선언하여 컴파일러가 임의로 해석하지 못하게 방지합니다.

ex) volatile char k; 


4) 외부 메모리가 있는 경우가 아니라면 재귀호출(Recursive call)을 사용하지 않는 것이 좋습니다. 

연속되는 재귀호출은 한정된 메모리에 Stack을 쌓이게 하며 결국에는 Stack overflow를 발생시킬 수 있기 때문입니다.


5) 대용량을 갖는 상수 배열(array)은 const 키워드를 사용하여 프로그램 메모리(Flash Memory)에 저장합니다.

AVR에서 변수는 SRAM에 저장하는데 고기능 AVR을 사용하지 않고 기능을 구현하려면 SRAM의 용량을 아껴야 합니다. 따라서 변하지 않는 대용량 상수들은 코드를 적재하는 Flash Memory에 저장하여 SRAM을 확보하는 것이 바람직합니다.

ex) const uint8_t value[64] = 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,

                                  29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,

                                  54,55,56,57,58,59,60,61,62,63,64};

  

 

 

Posted by Nature & Life
Embedded Programming/AVR 2014. 4. 20. 21:51



1) 전역변수(Global variable)


SRAM에 저장되어 있다가 이를 사용할 때마다 register로 읽혀지며 사용이 끝나면 다시 SRAM에 저장되므로 처리속도가 늦은 변수로, 특별히 속성을 정하지 않고 변수를 정의하면 SRAM 영역에 저장됩니다. 게다가 Compiler는 이를 변수로 처리하지 않고 그 상황에서 이 변수가 갖는 값에 해당하는 상수로 처리하는 경우가 있는데 이런 상황에서 여러 함수가 이 변수를 공유하여 사용하는데 문제를 야기하고 컴파일 시 최적화 옵션에 따라서도 영향을 받게 됩니다.


특히 인터럽트 서비스 루틴(Interrupt Serve Routine, ISR) 함수와 그 밖의 함수 사이에 공유하는 전역변수의 경우에 흔하게 발생하며 이를 방지하기 위해서는 전역변수에 'volatile'을 선언해 주어야 합니다.

ex) volatile unsigned char zc_count;


2) 지역변수(Local variable)


ATmega8의 경우, 다른 MCU에서처럼 지역변수를 Stack에 저장하는 것이 아닌 32개의 General Purpose Registers(GPFs)로 처리하므로 항상 처리속도가 빠릅니다. 하지만 ISR에 사용되는 변수는 특성상 Stack에 저장합니다.


3) 정적변수(static variable) 


함수가 시작될 때 register로 옮겨지고 함수가 종료될 때 SRAM으로 다시 옮겨져 저장되므로 만일 함수 내에서 여러 번 사용된다면 전역변수보다 정적변수가 처리속도가 증가하게 됩니다.





4) Flash Memory에 데이터의 저장


물론 메모리 용량이 크고 고성능의 MCU를 사용하면 문제가 되지는 않지만 가격대 성능비 등을 고려하여 그렇지 못한 경우, 변수로 사용해야 할 SRAM 용량을 절약할 필요가 있습니다. 만일 SRAM 용량이 부족하게 되면 Stack overflow가 발생하거나 프로그램이 오동작을 할 수 있습니다.


avr-gcc에서 상수 데이터를 프로그램이 적재되는 Flash Memory에 저장하기 위해서는 먼저 상수 데이터를 전역변수처럼 함수의 밖에서 정의하고 이때 상수가 byte 데이터라면 prog_char 또는 prog_uchar로 선언하며 읽을 때문 pgm_read_byte() 또는 pgm_read_word() 함수를 사용합니다.


참고로 비록 상수 데이터를 prog_char 또는 prog_uchar로 정의하더라도 이들이 함수 내에서 정의되면 컴파일 시 SRAM 변수로 처리되므로 주의가 요구되며, 일반적으로 Flash Memory는 SRAM에 비하여 용량이 훨씬 크므로 유용하게 사용할 수 있습니다.


5) EEPROM에 데이터의 저장


AVR에서 EEPROM을 I/O register를 사용하여야 접근할 수 있으므로 avr-gcc 에서는 EEPROM을 eeprom_read_byte() 혹은 eeprom_write_byte() 함수로 각각 읽기, 쓰기가 가능합니다.



'Embedded Programming > AVR ' 카테고리의 다른 글

다양한 AVR Package 비교  (0) 2014.06.14
AVR의 메모리 구조  (3) 2014.04.20
AVR이란?  (0) 2014.03.11
부트로더란?  (0) 2012.12.10
Posted by Nature & Life