Archive for the ‘Interrupt’ Tag

AVR 프로그래밍에서 유의할 점 몇 가지

Atmel사에서 나온 AVR이라는 MCU(Micro Controller Unit)으로

저렴한 가격에도 강력한 역할을 한다.

주로 Atmega128 이라는 칩을 사용하는데, 적절히 많은 수의 입출력 단자와 다양한 기능을 갖추고 있기 때문이다.

필자는 학사학위 논문에서 Atmega8 이라는 좀 더 작은 칩을 사용했는데, 기능은 적지만 가격도 저렴하고 싸이즈가 작아서 편했다.

뒤에 붙는 숫자 자체는 들어가는 프로그램 용량으로 알고 있는데 확실하지는 않다. (어쨌든 Atmega8은 8KB를 쓸 수 있다.)

AVR시리즈는 Atmel사에서 제공하는 AVR Studio 프로그램을 설치하고 WinAVR에서 AVR-GCC 컴파일러가 제공되기때문에

C언어로 작성할 수 있어서 매우 편하다.

하지만 C언어를 어느 정도 사용하는 사람이라도 몇 가지 유의할 점이 필요하다.

1. Win AVR 이라는 라이브러리를 설치해야 제대로 사용할 수 있다.

2. Preference 항목에서 MCU 클락 및 Optimization Level을 설정해야 한다.

3. 인터럽트 루틴 작성 방법을 배워야 한다.

4. 보통의 C언어와는 달리 이진수를 사용할 수 있다.

5. 보통의 C언어와는 달리 bool 타입 변수를 사용할 수 없다.

6. 인터럽트 루틴은 가능하면 짧게 작성해야한다.

7. 인터럽트 루틴은 기본적으로는 중첩(인터럽트 루틴 실행시 인터럽트가 또 생겨 다른 인터럽트 루틴으로 이동)이 불가하지만 가능하게 할 수도 있다.

8. 포트 입력 사용시 내부 풀업(Pull Up)은 제공되는 반면 풀다운(Pull Down)은 안되므로 하드웨어적으로 구현해야 한다.

9. 전원은 안전하게 공급되도록 한다. 전압을 9V등에서 다운시켜 사용하는 경우 꼭 레귤레이터를 쓰도록 한다.

10. 인터럽트 루틴안에 쓰이는 변수들은 꼭 volatile 키워드를 붙여서 사용한다.

11. 기타 AVR의 특징


AVR을 자유자재로 사용하기 위해선 AVR 문서(모델명을 구글링 하면 맨 위에 pdf문서가 뜬다)의 구석구석을 다 읽어서 전체적으로 파악하는 것이 좋다.

중간중간에 나오는 하드웨어 다이어그램도 종종 도움이 된다.

AVR을 잘 다루기 위해선

1) 어셈블리어에 관한 기본 지식

2) 인터럽트의 동작에 관련된 지식

3) 회로를 구성하는 방법에 대한 지식

이 필요하다고 생각한다.

위 세 가지는 보통 컴퓨터공학과 및 전기과에서 배울 수 있는 지식이지만

필자와 같은 타과생들도 이런 내용을 알아야 한다는 것만 알고 있다면 쉽게 익혀서 사용할 수 있다.(별로 어려운 내용도 아니다.)

아무튼 앞에서 다루었던 11가지 내용에 대해 조금 더 자세히 알아보자.

1. Win AVR 이라는 라이브러리를 설치해야 제대로 사용할 수 있다.

AVR Studio와는 별개로 Win AVR 에서는 각 AVR칩들에 대한 라이브러리 및 AVR-GCC 컴파일러를 제공한다.

이를 설치하지 않으면 어셈블리어로 짜는 엄청난 작업을 해야한다.

개인적으로 AVR-GCC가 성능이 그렇게 좋지는 않다고 생각하는데, 종종 컴파일 타임 오류도 잘 못 잡아내기 때문이다.

하지만 계속 발전해와서 요새는 꽤 쓸만하다고 생각한다. 어쨌거나 C언어로 짤 수 있다는 것은 축복이다.

2. Preference 항목에서 MCU 클락 및 Optimization Level을 설정해야 한다.

AVR Studio에서 Preference 항목을 들어가면 MCU와 그 클락, Optimization Level을 설정하는 곳이 있다.

필자의 경우 MCU로 Atmega8, 클락은 16000000hz(16Mhz), Op Level은 0레벨(-O0 ) 으로 설정해서 사용한다.

클락은 AVR보드에 외부 클락 소스를 부착시키면 바뀔 수 있다.(필자도 붙인 경우)

Op레벨은 컴파일러 최적화 단계일텐데 0레벨로 하는게 이곳저곳 마음대로 이동시키지 않고 보이는대로 컴파일 될거라는 생각에 이렇게 사용한다.

(하지만 더 높은 최적화 설정으로 해도 무방하리라 생각한다. 어디서 주워들은 얘기일 뿐이다.)

3. 인터럽트 루틴 작성 방법을 배워야 한다.

인터럽트 루틴이란 AVR에서 사용되는 여러가지 인터럽트들(외부 인터럽트, 타이머 인터럽트, USART송수신 완료 인터럽트 등)을 처리하는 구문을 말한다.

인터럽트 루틴은 기본적으로 함수를 쓰는 것과 유사한데, 단지 정의 부분이 약간 다르고 return이 없으며, 함수를 호출하는 부분이 main함수안에 없다는 것이다.

인터럽트는 AVR을 구동시키는데 있어서 가장 중요한 역할을 하므로 조금 자세히 설명할 것이다. 인터럽트를 완벽히 이해하고 있으면 AVR의 80%는 정복했다고 보면 된다.

다음과 같은 형태이다.

외부 인터럽트 0번을 처리하는 경우


#include "avr/interrupt.h"

SIGNAL(SIG_INTERRUPT0){

//contents

}

와 같은 식으로 인터럽트 루틴을 정의한다.

보통 main함수 앞에 적고, avr/interrupt.h 헤더파일를 포함해야 한다. (보통의 경우 헤더파일은 두 개정도 필요한데 avr/interrupt.h와 avr/io.h이다.)

모든 인터럽트 루틴은 SIGNAL이 그 이름이고 뒤의 인자가 다를 뿐이다.

이 인자에는 인터럽트 종류가 오는데 이는 Atmega8의 경우 iom8.h 헤더파일에서 확인할 수 있다.

AVR Studio에서 좌측 프로젝트 폴더 트리에서 External Dependencies안에 iom8.h 파일이 있는 것을 볼 수 있다.(다른 칩들의 경우 이름이 조금씩 다를 것이다.)

여기서 Ctrl+F로 SIG_INTERRUPT0 을 찾아보라. 찬찬히 읽어보면 다른 인터럽트 이름도 쉽게 파악할 수 있을 것이다.

이제 루틴 작성을 마쳤지만, 인터럽트 루틴을 작성했다고 인터럽트가 실행되는 것은 아니다.

인터럽트를 사용하겠다고 선언해주어야한다.

Reference문서에서 해당 인터럽트 관련 레지스터를 찾아보면 Interrupt Enable을 담당하는 레지스터가 있을 것이다.

main 함수에서 적절하게 Enable 구문을 쓰고 꼭 sei() 함수를 호출해준다.

위에서 언급했던 각 인터럽트에 해당되는 레지스터 비트는 개별적인 인터럽트를 켜고 끄는 역할을 해주고

SREG 라는 레지스터의 I번 비트는 개별적으로 켜진 인터럽트 모두를 최종적으로 사용가능하게 해준다.

이 SREG-I를 켜는 역할을 sei()함수가 해준다.

(즉 개별 인터럽트를 아무리 켜도 sei()를 해주지 않으면 어떠한 인터럽트도 실행이 안되고, 그렇다고 sei()를 실행했다해서 모든 인터럽트가 켜지는 것이 아니라 개별적으로 켜놓은 모든 것이 켜진다고 보면 된다.)

예를들어 Atmega8의 외부 인터럽트 0번의 경우


int main(){

MCUCR |= 0b0011;// 외부인터럽트0번의 추가 셋팅(Rising Edge)

GICR |= 0x40;//개별적으로 켜는 부분. 끌 때는 GICR &= ~0x40; 을 사용

sei();//개별적으로 켠 인터럽트를 최종적으로 다 실행가능하게 만들어주는 부분

...

return 0;

}

와 같다.

4. 보통의 C언어와는 달리 이진수를 사용할 수 있다.

보통 C언어에서는 숫자 12를

10진수로 12

8진수로 012

16진수로 0x0c

와 같이 표현하는데 2진수는 불가능한다.

하지만 레지스터를 많이 다루는 AVR의 특성상 2진수 표현도 제공하는데

2진수로 0b1100 으로 표현하면 되겠다.

5. 보통의 C언어와는 달리 bool 타입 변수를 사용할 수 없다.

보통의 C언어에서는 true 또는 false만을 나타내는 bool변수를 사용하는데

AVR에서는 구현상 불편함 때문인지 bool을 지원하지 않고 int형의 0, 1로 대체해야한다.

true = 1, false = 0 이라고 보면 된다.

while(true){} 구문도 while(1){} 과 같이 해주면 된다.

6. 인터럽트 루틴은 가능하면 짧게 작성해야한다.

인터럽트 루틴은 가능하면 짧게 작성하는 것이 좋다.

특히나 타이머 오버플로우 인터럽트를 사용할 때는 인터럽트가 1초에도 수 백번이나 발생하므로 조심해야한다.

만일 인터럽트 루틴에서 실행되는 내용이 매우 많을 경우

한 인터럽트 루틴이 오래 수행되고, 수행되는 동안 새로운 인터럽트가 발생할 수 있다.

이렇게 되면 뒤에 들어오는 인터럽트는 계속 실행되지 못하고 무시된다.

(Flag 기반의 인터럽트들은 후에 들어온 인터럽트 중 가장 최근 것 까지는 실행한다. 하지만 원하는 대로의 결과를 내기는 힘들다.)

따라서, 인터럽트 발생시 어떤 긴 작업을 해야 한다면 인터럽트 루틴에서 다 실행하지말고

인터럽트 루틴에서는 개시만 하고, 실제 수행은 main에서 처리하는 기법을 사용한다.

다음과 같이 전역변수를 이용해 구현한다.


volatile int start = 0;

SIGNAL(SIG_INTERRUPT0){

start = 1;

}

main(){

...

while(1){

if(start == 1){

//work to do

}

}

...

}

7. 인터럽트 루틴은 기본적으로는 중첩(인터럽트 루틴 실행시 인터럽트가 또 생겨 다른 인터럽트 루틴으로 이동)이 불가하지만 가능하게 할 수도 있다.

말 그대로이다.

앞에서 설명한대로 기본적으로 한 인터럽트 루틴을 실행중에는 다른 인터럽트를 처리할 수 없는데

이는 AVR에서 인터럽트를 처리하는 구조때문이다.

작성한 인터럽트 루틴은 해당 인터럽트가 발생할 때, 해당 실행을 멈추고 루틴 부분으로 넘어간다.

즉, main함수에서 무슨 작업을 하고 있더라도 인터럽트가 발생하면 그 작업을 멈추고 해당 루틴을 실행하기 시작한다.

실제로는 MCU 내부적으로 해당 작업을 저장하는데 4클락정도 소모되고 인터럽트루틴으로 넘어간다. (빠져나올때도 복구하는데 4클락정도 소모)

이 4클락 중에는 AVR이 내부적으로 SREG-I비트를 끄는 것이 포함된다.

이렇게 되면 이후 발생하는 모든 인터럽트는 해제되는데 인터럽트 루틴이 끝나고 돌아올때(RETI: RETurn from Interrupt)는 자동으로 SREG-I가 다시 켜진다.

따라서 Flag기반 인터럽트들은 다시 인터럽트 루틴이 수행될 수 있는 것이다.(하지만 정확하지는 않다. 여러번 쌓인 것도 한 번만 실행되므로)

중첩 인터럽트 루틴 실행 방법은 별로 추천하고 싶지는 않지만 인위적으로 가능하게 만들 수는 있다.(Reference 문서에도 나와있는 방법이다.)

인터럽트 루틴안에서 sei()를 수동적으로 호출하면 되는 것이다.


SIGNAL(SIG_INTERRUPT0){

sei();

...

}

하지만 타이머 오버플로우 인터럽트 처럼 자주 수행될 인터럽트 루틴을 중첩가능하게 만들어버린다면

계속 인터럽트 루틴이 쌓이기만하고 끝까지 실행되는 것은 영원히 없을지도 모르므로 주의해야한다.

8. 포트 입력 사용시 내부 풀업(Pull Up)은 제공되는 반면 풀다운(Pull Down)은 안되므로 하드웨어적으로 구현해야 한다.

외부 인터럽트를 비롯한 Port 입력 단자는 꼭 풀 업 또는 풀 다운을 해 주어야 한다.

이는 아무런 장치도 없는 경우 해당 포트가 말 그대로 값이 ‘뜬’ 상태에 직면하게 되는데

0V 또는 5V 와 같이 기본값이 들어오는 것이 아니라 2.3V등의 Random한 값이 마구 들어오기 때문이다.

이는 원치않는 결과를 초래한다.

외부 인터럽트의 경우 실제 인터럽트가 일어나지도 않았는데 4V정도가 들어오는 것 처럼 되어 버려서 수시로 인터럽트로 발생할 수도 있다.

이를 막기 위해서 풀 업 또는 풀 다운이 필요한데

풀 업은 기본적으로 5V, 풀 다운은 기본적으로 0V를 만들어 주는 것이다.

AVR에서는 내부적인 레지스터 설정으로 풀 업은 가능한데(Reference의 입출력 부분을 찾아보라)

풀 다운은 직접 회로를 짜야한다.

풀 다운의 경우 간단히 GND(그라운드)와 해당 입력 핀 사이에 큰 저항 하나를 연결해주면 된다.

보통 1k옴 ~ 10k옴을 사용하는 것으로 알고 있는데 주로 4.7k옴을 사용한다.

암튼 위의 범위 저항 중 굴러다니는 아무거나 매달면 된다.

9. 전원은 안전하게 공급되도록 한다. 전압을 9V등에서 다운시켜 사용하는 경우 꼭 레귤레이터를 쓰도록 한다.

이 것은 단순한 것인데 중요하다.

전압은 항상 안전하게 공급되어야한다.

만일 일정 전압 이하로 내려가면 AVR이 순간적으로 꺼졌다가 다시 켜지는 경우가 있는데

전원여부를 LED로 알 수 있다해도 순간적으로 끊어지는 것은 잡아내기가 힘들고,

결정적으로 껐다 켜지면 프로그램이 처음부터 실행되므로 예기치 않은 결과를 불러온다.

따라서 처음에 완성된 프로그램이 아니라 계속 만들어 가는 중이려서 오류가 발생할 수도 있는 상황이면

되도록 플러그를 꼽아 사용하는 전원 장치로 전원공급하기를 추천한다.

보통의 건전지는 용량이 작아서 조금 쓰면 금방 다 닳아 버려서 불편하다. 특히 쓰면 쓸 수록 전압이 떨어진다.

프로그램이 완성이 되면 그 때 건전지를 연결해서 해봐도 늦지 않다.

또한 여러가 센서를 사용하면서 높은 전압에서 다운을 시켜야 하는 경우도 있는데

어줍잖게 저항을 이용해서 분압을 하면 절대로 안된다.

반드시 7805 등의 레귤레이터를 사용하도록 하자.

저항을 이용해서 분압하면 출력에 따라서 전압이 불안정하다.

10. 인터럽트 루틴안에 쓰이는 변수들은 꼭 volatile 키워드를 붙여서 사용한다.

전역변수들은 volatile키워드를 붙이는 것이 안전하다.

특히 인터럽트 루틴안에 사용되는 변수들은 무조건 volatile을 붙이도록 한다.

volatile을 붙이지 않으면 컴파일러가 최적화를 하면서 선언부의 위치를 마음대로 옮겨 버릴 수가 있기 때문에

원하는 대로 동작을 하지 않을 수도 있다.


volatile int start;

volatile char c;

와 같이 선언한다.

11. 기타 AVR의 특징

AVR은 Harvard Architecture로 Instruction Memory와 Data Memory가 분리 되어있다.

2단계 Pipelining을 사용한다.

위의 11가지 유의사항을 잘 이해하고 지킨다면, C언어의 기본적인 지식만 있는 사람이라도 왠만큼 부드럽게 동작하는 AVR 프로그램을 짤 수 있을 것이다.

많은 도움이 되기를 바란다.