WinAVR의 Makefile 파일로 컴파일 하다보면 avr-gcc 옵션이 부쩍 늘어난 것을 볼 수 있다. 이런 옵션의 대부분은 Makefile의 CFLAGS에 정의되어 있다. 그 중에서 프로그램 동작에 영향을 미칠 수 있는 몇 가지만 살펴보고자 한다. avr-gcc에 국한된 옵션(-m)도 있고, 대부분의 gcc에서 통용되는 옵션(-f)도 있다. avr-gcc 전용 옵션이 아닐 경우는 쉽게 접할 수 있는 32-bit x86-gcc 기준으로 설명하고 있으니 혼란없길 바란다.
- -funsigned-char
- -funsigned-bitfields
- -fpack-struct
- -fshort-enums
- -fno-unit-at-a-time
- -mint8 (avr-gcc)
- -mshort-calls (avr-gcc)
avr-gcc의 int 형 크기는 2바이트이다. 본문에서는 int와 char의 크기 대비를 강조하기 위해 주로 4바이트의 예를 들고 있다.
-funsigned-char
8-비트 마이컴 환경에서는 한 번에 처리할 수 있는 데이터가 8비트 단위이기 때문에 PC에서 남발하던 int형 변수 선언을 자제하고 될수 있는한 1바이트 char 형 변수를 사용하는 것이 좋다. 하지만 제 버릇이 있지 -128 ~ 127 까지의 수 가지고는 도저히 답답해서 안되겠다. 음수는 별로 사용할 일도 없으니 unsigned char로 어떻게든 버텨보자. 악! char에 unsigned 를 일일이 붙여 주자니 귀찮아서 못하겠다. 컴파일러 옵션에 -funsigned-char 넣어보자. 명시적으로 signed를 넣지 않은 이상 char의 기본형은 unsigned char로 인식하도록 하는 옵션이다.
-funsigned-bitfields
위와 비슷한 맥락의 옵션이다. 비트필드에서 부호 사용 여부를 명시적으로 알려주지 않았을 경우 unsigned로 처리하라는 의미이다. 여기서 잠깐! 비트필드가 뭘까? C언어 책 어딘가 뒷 구석에 잠시 소개된 내용인것 같은데...아니야, 이런 것이 있었던가?-_-;;; 기억하거나 직접 사용해본 사람은 그다지 많지 않으리라 생각된다. 비트필드를 사용하면 데이터 중 일부 비트 영역을 일반 변수처럼 사용할 수 있는 유용한 기능이다. 보통 공용체와 쌍으로 쓰이곤 한다. (주의: 2바이트 이상의 데이터를 처리할 경우 해당 CPU에서 메모리에 데이터를 저장하는 방식을 고려해야 한다. 이를 테면 big endian 방식을 따르는지 little endian 방식을 따르는지 파악하고 비트 필드를 나열해야 원하는 결과를 얻을 수 있다.)
union {
unsigned char a;
struct {
char b : 4; // a 의 하위 4비트
char c : 2; // a 의 그 다음 2비트
char d : 2; // a 의 상위 2비트
};
} foo;
foo.a = 0x87;
printf("a = %X : b = %d, c = %d, d = %d", foo.a, foo.b, foo.c, foo.d);
-funsigned-bitfields 옵션을 주었을 때와 주지 않았을 경우의 결과를 각각 비교해보기 바란다. 참고로 음수는 2의 보수로 표현된다.
-fpack-struct
다음과 같은 구조체가 있다.
struct {
char a;
int b;
} foo; // -fpack-struct 전과 후의 sizeof(foo)?
sizeof(foo)는 얼마일까? 만약 int가 4바이트라면 5? 컴파일러는 구조체의 내부 멤버 변수들을 일정 바이트 단위로 나눠서 (주소를) 정렬한다. 32-bit x86 gcc처럼 4바이트 단위로 정렬한다고 가정할 경우 a는 1바이트, b를 남은 3바이트에 끼워 넣어야 하는데 1바이트가 모자란다. 결국 3바이트는 건너 뛰어 빈공간으로 남기고(padding), 그 다음 4바이트에 b를 위치시킨다. 이렇게 하면 총 8바이트를 차지하게 된다. 내부 CPU 아키텍처를 고려한 결과이겠지만 이기종 간의 데이터를 주고 받아야 하는 네트워크 환경에서는 적잖은 혼란을 다져다 줄 수 있고, 하드웨어를 쥐어 짜야 하는 열악한 8비트 임베디드 환경에서는 2바이트 정렬이라고 해도 큰 낭비가 아닐 수 없다. 결과적으로 -fpack-struct 옵션을 주어 변수 실제 크기대로 차곡차곡 메모리에 위치하도록 한다.
-fshort-enums
보통 열거형은 int 형 상수로 취급된다. 열거형 상수의 sizeof 값과 int 형의 sizeof 값이 동일하다는 의미이다.
enum { A, B, C };
A, B, C 모두 1바이트면 충분히 나타낼 수 있는 상수이다. 굳이 4바이트를 차지할 필요가 없다. 이럴 경우 -fshort-enums 옵션으로 열거형 상수가 표현할 수 있는 가장 작은 크기의 데이터형으로 취급하게끔 할 수 있다.
enum { A, B, C }; // -fshort-enums 전과 후의 sizeof(A)?
enum { A, B = 9000, C }; // -fshort-enums 전과 후의 sizeof(A)?
여기까지는 WinAVR Makefile에서 기본적으로 지정되었된 CFLAGS 옵션이고, 다음은 Makefile 내에서 주석 처리된 옵션에 대한 설명이다.
-fno-unit-at-a-time
gcc는 최적화 옵션이 -O2 이상이면 unit-at-a-time 모드로 컴파일 한다. unit-at-a-time 모드에서는 코드를 생성하기에 앞서 전체 컴파일 유닛을 읽은 다음 유닛 전체를 보고 최적화를 한다. 하나의 함수를 컴파일 한다고 해서 그 함수만 보고 코드를 생성하는 것이 아니라 파일 전체를 살펴보고 얻은 정보를 컴파일에 이용할 수 있다. 어리숙하게 똑똑한 unit-at-a-time 모드에 의해 최적화된 코드를 생성할 수 있겠지만 경우에 따라서 원치 않은 결과가 나타날 수 있다. 예를 들면 함수나 변수, 혹은 탑레벨 asm 코드의 순서가 바뀌어 버리거나 참조하지 않은 정적 함수나 정적 변수가 자동으로 제거될 수 있다. asm 코드에서 사라져 버린 심볼을 직접적으로 참조할 경우 문제가 발생할 여지가 있다. 또한 정적 함수에서 비표준 방식으로 파라메터를 주고 받을 수 있는데, 이 경우도 asm 코드에서 함수를 직접 호출한다면 코드가 깨질 수 있다. 두 가지 예 모두 used 속성을 사용해서 해결할 수 있으나 -fno-unit_at-a-time 옵션을 사용한다면 아예 unit-at-a-time 모드를 사용하지 않는다. gcc 매뉴얼에는 -fno-unit-at-a-time 옵션이 임시적인 해결책일 뿐 차기 릴리즈에서 사라질 수도 있다고 적혀 있다.
*개인적으로 0으로 먼저 해본다음 차차 최적화 옵션을 올려가면서 기능을 점검하며 코드사이즈를 줄여나가는게 좋을 것 같다.
소스가 어이없게 안돌아 가는 경우를 상당히 많이 보았기 때문이다
예를들어
#include<avr/io.h>
#include<avr/interrupt.h>
unsigned char cnt=0;
SIGNAL(SIG_INTERRUPT0){
cli();
cnt++;
PORTB = 0x03;
sei();
}
int main(){
DDRD = 0x00;
PORTD = 0x00;
DDRB = 0xFF;
PORTB = 0x00;
EIMSK |= 0X01;
EICRA |= 0X03;
EIFR |= 0X01;
sei();
while() {
PORTB = cnt;
}
return 0;
}
위와 같이 간단한 소스에서도 최적화인 -Os 를 하면 인터럽트가 걸리지 않아 원하던 키를 눌러 인터럽트가 발생하면 LED에 불이 들어와야 하는데
-O0을 해야만 정상적으로 인터럽트가 걸려서 LED에 불이 켜졌다.
-mint8 (avr-gcc)
int 형을 1바이트로 취급한다. long 형이 2바이트, long long 형이 4바이트가 된다. 이 옵션을 사용할 경우 소스 코드가 C 표준에 부합되 지 않는다는 점을 명심해야 한다. avr-libc에서도 이 모드는 지원하지 않으므로 사용하지 않는 것이 바람직하다.
-mshort-calls (avr-gcc)
8KB 이상의 플래시 메모리를 가진 디바이스에서 분기 명령으로 rjmp/rcall을 사용하라는 옵션이다. 플래시 메모리가
8KB 보다 작은 avr2, avr4 아키텍처에서는 기본적으로 rjmp, rcall이 사용되지만 8KB 보다 큰 avr3, av5
아키텍처에서는 넓은 메모리 공간으로 인해 jmp/call 명령을 사용한다. rjmp/rcall 명령은 분기 범위에 있어서
제한(PC + -2048 ~ 2047 + 1 범위)이 있는 반면 jmp/call (4MB 범위)에 비해 실행하는데 소요되는 클럭이
1클럭씩 적다. 명령 크기도 2바이트로 4바이트인 jmp/call 에 비해 훨씬 작다.
출처: http://picky9.com/blog/entry/224
고마운 분께 자료를 얻었는데 코멘트를 남길 수 없어 허락 없이 퍼왔다.
-0ㅠ 죄송
그 밖에 윤덕용 교수님이 적으신 AVR-GCC 사용할 때의 유의점을 참고하도록 하자.
그리고 이건 AVR-GCC 컴파일러 avr-libc Reference Manual이다.
버전은 1.6.5 그나마 최신인것 같다.
아래 홈페이지에 들어가면 각 함수 마다의 기능을 링크해두어서 클릭하면 바로 그 기능들을 볼 수 있다.
http://www.nongnu.org/avr-libc/user-manual/modules.html
- <avr/sleep.h>: Power Management and Sleep Modes
- <avr/version.h>: avr-libc version macros
- <avr/wdt.h>: Watchdog timer handling
- <util/atomic.h> Atomically and Non-Atomically Executed Code Blocks
- <util/crc16.h>: CRC Computations
- <util/delay.h>: Convenience functions for busy-wait delay loops
위와 같은 것들 이상의 것들 모두 기본적으로 제공되는 함수파일들의 기능을 잘 설명해 놓았으니 꼭 참고 해보자
'Embedded system > AVR' 카테고리의 다른 글
소프트웨어로 AVR 메모리 초기화 하기 (0) | 2011.02.11 |
---|---|
winavr 버전에 따른 문제점 20081205 vs 20090313 (0) | 2010.09.02 |
일광절약제(Daylight Saving) (0) | 2010.09.02 |
the problem of delay (0) | 2009.11.17 |
AVR studio optimization problem (0) | 2009.11.17 |