'레지스터'에 해당되는 글 2건

  1. 2017.03.18 인터럽트와 volatile 지시자 1
  2. 2014.04.23 C 언어로 개발시 유의사항
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 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