본문 바로가기
C의 조미료 MACRO Technics

Bit Operation을 했으니까, C Macro도 여러 가지 알아두면 편리한 것들이 많이 있어요. 저는 개인적으로 Macro를 사랑하는 편이라 자주 사용하는 편인데요, Register 설정 같은 것을 Macro로 관리하면 엄청 편합니다. 주소를 다 외울 수는 없는 노릇이니까요. 일단은 Macro도 argument를 받을 수 있어요. - 잘 아시겠지만 - 이런 Macro를 선언해서 쓰는 방법도 여러 가지가 있답니다. Macro는 자주 쓰이는 걸 기냥~ 한 개로 묶을 수 있다는 장점이 있어요. 예를 들어서 Software를 짜다 보니까,

critical_section_in();
ret = io_read ();
critical_section_out();

이라는 부분이 계속 반복되더라.. 하면, 계속 이렇게 다 쳐주기 귀찮으니까, 아름답게 하나의 Macro로 만들어 줄 수 있겠죠. 그러면 #define으로 CRITICAL_IO_IN() 을 선언하는데, 반복되는 부분을 역슬래쉬 "\" 으로 계속 연결하면 CRITICAL_IO_IN() 하나만 사용해도 나머지가 3줄의 Software를 쓴 것과 마찬가지 효과에요

#define CRITICAL_IO_IN() \
critical_section_in(); \
ret = io_read (); \
critical_section_out();

라고 선언하면, Program중에 CRITICAL_IO_IN(); 이라고만 넣어줘도 3줄을 한꺼번에 넣어준다니까요.

두 번째로, 이런 Macro도 인자를 받을 수 있다는 거에요. 으흐흐. 또 예를 들어서, Software를 짜다 보니 똑 같은 게 계속 나오긴 하는데, 함수들의 인자가 계속 그때그때 달라지더라..하더라도 Macro로 만들 수 있습니다.

예를 들어서
critical_section_in(current);
ret = io_read (io_num);
critical_section_out(previous);

이런 식의 것이 계속 반복된다면 또 짱나겠죠. 이럴 때는

#define CRITICAL_IO_IN (current, io_num, previous) \
critical_section_in(current); \
ret = io_read (io_num); \
critical_section_out(previous); \

이런 식으로 선언해 주면 마치 함수 쓰는 것처럼 Macro 내부의 인자들에게 하나씩 넣어준다니깐요. 예를 들어

CRITICAL_IO_IN (1, 2, 3);
이라고 호출해 주면,

critical_section_in(1);
ret = io_read (2);
critical_section_out(3);

결과적으로는 요런 식으로 만들어 준다니까요. 냐하하. 편리하죠! 여기에 또 편한 Macro의 기능 하나를 더 소개하면 놀라움을 금치 못하실 꺼에요. 그건 ##기능인데요. 그것이 참으로 오묘하지요. ##을 이용하면, Define으로 선언된 이름들도 Argument로 넣어줄 수 있습니다.
예를 들어서,

#define CRITICAL_IO_IN (CURRENT, IO, PREVIOUS) \
critical_section_in(DEVICE_##CURRENT##_BUFFER); \
ret = io_read (IO_##IO##_NUM); \
critical_section_out(DEVICE_##PREVIOUS##_BUFFER);

라고 선언해 둔 후,

CRITICAL_IO_IN (BOOTUP, USB, STARTUP);
이라고 호출해 주면

어떤 식으로 호출이 되냐면,
critical_section_in(DEVICE_BOOTUP_BUFFER);
ret = io_read (IO_USB_NUM);
critical_section_out(DEVICE_STARTUP_BUFFER);

요렇게 바꿔줍니다.

그러니까
#define DEVICE_BOOTUP_BUFFER 1
#define DEVICE_STARTUP_BUFFER 3
#define IO_USB_NUM 2
라고 어딘가에 선언만 되어 있다면,

또다시 똑같이

critical_section_in(1);
ret = io_read (2);
critical_section_out(3); 라고 호출한 결과와 같습니다. 냐하하.

자, 그러면 이 녀석을 Register control할 때 잘 써먹어 봅시다. 호출하고 싶은 모양새는 IO_OUT(io_num, mask, val)라는 게 제일 자연스럽겠죠. 뭐, 다짜고짜 예부터 들면 훨씬 수월할 것 같은 느낌이네요.

예를 들어서, MCU의 clock을 configuration할 수 있는 주소와 값들을 Macro로 정했다고 해보죠. MCU의 Data Sheet를 보았더니 그 주소가 0xC000CB00이고요, 32bit register이고요, 그 중에 LSB bit 2,3이 MCU의 Data 이고요. Read, Write가 모두 가능한 Register이고요, LSB 2,3bit가
0x0인 경우에는 Clock을 disable 하는 거고,
0x1인 경우에는 Clock을 enable 하는 거고,
0x2인 경우에는 enable된 Clock의 주기를 반으로 줄이는 거고,
0x3인 경우에는 enable된 Clock의 주기를 두 배로 올리는 거라면요!
어떻게 하면 이런걸 깔끔하게 설정할 수 있을까요.

일단은 필요한 것들을 접두사와 접미사로 잘 구분되도록 Define해 볼 께요. 모든 값에는 HWCTLIO_ 를 앞에 붙이고요, 주소는 _ADDR, Mask에는 _MSK, 값에는 _VALUE 라고 define해 볼 께요.

#define HWCIO_MCU_CLK_ADDR 0xC000CB00
#define HWCIO_MCU_CLK_MSK 0x3
#define HWCIO_MCU_CLK_SHIFT 0x2
#define HWCIO_MCU_CLK_DIS_VALUE 0x0
#define HWCIO_MCU_CLK_EN_VALUE 0x1
#define HWCIO_MCU_CLK_TWICE_VALUE 0x3
#define HWCIO_MCU_CLK_HALF_VALUE 0x2
#define HWCIO_MCU_CLK_SHIFT 0x2

요렇게 해놓고, Clock을 살리고 2배로 뻥튀기 시키고 싶을 때,
IO_OUT (MCU_CLK, MCU_CLK_TWICE);
요렇게 간단히 넣어주고 싶으면

#define IO_OUT (target, val) \
(*((volatile dword *) (HWCIO_##target##_ADDR)) =\
((dword) (HWCIO_##target##_##val##_VALUE & HWCIO_##target##_MSK\
)<
요렇게 선언해 주면 어떻게 되느냐

target 자리에 MCU_CLK, val 자리에 MCU_CLK_TWICE가 치환되어 들어간답니다.

IO_OUT (MCU_CLK, MCU_CLK_TWICE);라고 코딩 해 주면

(*((volatile dword *) (HWCIO_MCU_CLK_ADDR)) =\
((dword) (HWCIO_MCU_CLK_TWICE_VALUE & HWCIO_MCU_CLK_MSK\
)<
라고 치환이 되고요, 이것은

(*volatile dword *)(0xC000CB00) = ((dword)(0x3 & 0x3)<<0x2)) 결국엔 이렇게 해석이 되는 거지요. 그러니까, 0xC000CB00번지에 0x3을 0x2만큼 Left shift 한 값을 넣어주는 거죠.
우리가 원하는 대로 되지요?

그러니까,
#define HWCIO_기능이름_ADDR
#define HWCIO_기능이름_MSK
#define HWCIO_기능이름_VALUE
#define HWCIO_기능이름_SHIFT
이런 식으로 주르르르륵 Define을 걸어 놓고서, IO_OUT() Macro를 이용해서, 얼마든지 보기 좋게 Register를 control할 수 있겠죠. 아~ 아름다워라.

이 예제는 순수하게 이런 식으로 한다를 보여드리려고 억지로 만든 경향이 없지 않아 있어요. 왜냐하면 앞에서도 얘기했듯이, Register의 특정 bit만 Access가 불가능 하다는 거지요. 그러니까, 문제는 Register는 32bit의 특정 bit만 쓸 수 없다고 했지요? 이럴 때, Shadow를 둬서 원래 있던 다른 bit의 값을 backup 한 후에 쓰는 게 맞는 macro가 되겠지요. 0xC000CB00에 있는 값을 언제나 MCU_CLK_Shadow라는 32bit 전역변수에 backup을 해놓는다면, (또는 읽어서 저장을 한 다음에)

IO_OUT (MCU_CLK, MCU_CLK_TWICE);라고 코딩해 주면

(*((volatile dword *) (HWCIO_MCU_CLK_ADDR)) =\
MCU_CLK_shadow || \
((dword) (HWCIO_MCU_CLK_TWICE_VALUE & HWCIO_MCU_CLK_MSK\
)<
요렇게 원하는 bit만 다시 넣어주고 나머지는 원래 있던 대로 써주겠지요. 뭐 이런 테크닉도 있습니다. do~while(0)를 이용한 Macro 이용방법인데요.
do {\
.....
} while (0)
를 이용하는 건데요,
예를 들어서,

if (dstlen>page_size_in_word) { \
write_and_set_curr_page((void *)dst, &dstlen, &total_rle_length); \
memset ((void *) dst, 0x0, FLASH_NAND_PAGE_NUM_BYTES);} \
요런 구문을 계속 쓰더라 하면, 이걸 Macro로
#define write_and_foward_page() \
do { \
if (dstlen>page_size_in_word) { \
write_and_set_curr_page((void *)dst, &dstlen, &total_rle_length); \
memset ((void *) dst, 0x0, FLASH_NAND_PAGE_NUM_BYTES);} \
} while (0)

요렇게 선언하고서 write_and_forward_page() 를 부르면 역시나 쪼르륵 Macro로 선언된 부분이 코드에 치환되어 들어갑니다. 이런 수법은 Linux Kernel같은데 많이 사용되는데요. 그럼 do~while(0) 기법은 어느 때 사용되는 걸까요? 실은 앞에서 한 do를 이용하지 않는 Macro는 Define 한 값들을 이용해 먹으려고 많이 쓰는데요, do~while(0)는 진짜 여러 번 반복되는 구문을 간단하게 줄이려고 쓰인다고 보면 됩니다. 왜냐하면, do~while(0)을 씀으로써 가능해지는 것이 있어서 그런데요. 일단 {}로 묶여 있으니까, 그 안에서 local variable을 선언해서 사용할 수가 있습니다. 두 번째로는 불확실한 if문의 사용을 확실하게 해줍니다. 예를 들어볼까요?

#define READ_PRINT_VALUE (x) \
scanf ("%d", &x); \
printf ("\n%d", x);

이라는 Macro가 있을 때 그냥 READ_PRINT_VALUE(value); 뭐 이런 식으로 써주면 어떻게 해석이 되느냐 하면
scanf ("%d", &value);
printf ("\n%d", value); ;

요렇게 해석이 됩니다. 요것만 쓸 때는 별로 문제가 없어 보이죠. 그런데,

if (x>10)
READ_PRINT_VALUE(x);

라고 해보시죠. 의도는 x>10 일 때만 x를 다시 input받아서 출력하는 code이죠. 그런데 막상 어떻게 해석되느냐면,

if (x>10)
scanf ("%d", &x);
printf ("\n%d", x); ;

요렇게 해석됩니다. 결국, scan은 받고 print도 되겠지만, 문제는 원래 의도와 다르게 if는 scanf에만 적용된다는 거죠. 이게 설상가상으로 else까지 있다면 말이죠.

if (x>10)
READ_PRINT_VALUE(x)
else
system_exit();

인 코드가 있다고 치면요.
if (x>10)
scanf ("%d", &x);
printf ("\n%d", x); ;
else
system_exit() ;

요렇게 해석되지요. 이렇게 되면, 곤란합니다. Compiler가 에라!를 외치죠. 여기에 else는 뭐냐! 그러니까 이럴 땐 괄호 {}가 꼭 필요한 겁니다.

#define READ_PRINT_VALUE(x)
do{ \
scanf ("%d", &x); \
printf ("\n%d", x); \
} while (0)

요렇게 하면 어떻게 해석될까요?

if (x>10)
do {
scanf ("%d", &x);
printf ("\n%d", x);
} while (0);
else
system_exit() ;

요렇게 해석되겠죠? while(0)니까 한번만 하고 지나갈 거고 do와 while(0)는 코드에 영향을 주지 않으면서 괄호도 제대로 들어갔죠? 으하하. 그러면 do while(0)를 넣지 않고, 괄호만 넣으면 되지 않느냐 하는 의문이 생기시겠지만 만약 괄호만 넣어주고서 한번 해볼까요?

#define READ_PRINT_VALUE(x)
{ \
scanf ("%d", &x); \
printf ("\n%d", x); \
}

요렇게 해주면, 어떻게 해석되느냐

if (x>10)
{
scanf ("%d", &x);
printf ("\n%d", x);
} ;
else
system_exit() ;

요렇게 해석이 되고요 else 앞에 왠 의미 없는 ;가 하나 들어 갔죠. 이런걸 empty statements, 즉 빈볼 ㅋ 이라고 부르는데요, 이런 아무 의미 없는 녀석이 else앞에 있으면 마음씨 좋은 컴파일러는 Warning을 내던가, 엄격한 컴파일러는 Error를 낸답니다. 요렇게 Macro만 잘 사용해도, 짱나는 반복작업을 많이 없앨 수 있으니까, 꼭 알아두세요. 이것도 쓰다 보면 꽤나 흥미롭게 재미있다니까요.


사족 : 혹시나, 어떤 pointer에 직접 주소를 assign 하는 방법에 대해서 언급하자면요,
volatile unsigned dword * io = (unsigned word *) 0xC0008000;
뭐 이런식으로 선언해 주고, io에 값을 쓸 때마다 0xC0008000에 32bit의 값을 쓸 수 있으니, 얼마나 편리한 감요.


Quiz : MACRO의 예술.

#define SHIFT_FROM_MASK(x) (SHIFT_TEST(x##_MASK,0) ? 0 : \
                              (SHIFT_GET(x##_MASK,1, \
                               (SHIFT_GET(x##_MASK,2, \
                                (SHIFT_GET(x##_MASK,3, \
                                 (SHIFT_GET(x##_MASK,4, \
                                  (SHIFT_GET(x##_MASK,5, \
                                   (SHIFT_GET(x##_MASK,6, \
                                    (SHIFT_GET(x##_MASK,7, \
                                     (SHIFT_GET(x##_MASK,8, \
                                      (SHIFT_GET(x##_MASK,9, \
                                       (SHIFT_GET(x##_MASK,10, \
                                        (SHIFT_GET(x##_MASK,11, \
                                         (SHIFT_GET(x##_MASK,12, \
                                          (SHIFT_GET(x##_MASK,13, \
                                           (SHIFT_GET(x##_MASK,14, \
                                            (SHIFT_GET(x##_MASK,15, \
                                             (SHIFT_GET(x##_MASK,16, \
                                              (SHIFT_GET(x##_MASK,17, \
                                               (SHIFT_GET(x##_MASK,18, \
                                                (SHIFT_GET(x##_MASK,19, \
                                                 (SHIFT_GET(x##_MASK,20, \
                                                  (SHIFT_GET(x##_MASK,21, \
                                                   (SHIFT_GET(x##_MASK,22, \
                                                    (SHIFT_GET(x##_MASK,23, \
                                                     (SHIFT_GET(x##_MASK,24, \
                                                      (SHIFT_GET(x##_MASK,25, \
                                                       (SHIFT_GET(x##_MASK,26, \
                                                        (SHIFT_GET(x##_MASK,27, \
                                                         (SHIFT_GET(x##_MASK,28, \
                                                          (SHIFT_GET(x##_MASK,29, \
                                                           (SHIFT_GET(x##_MASK,30, \
                                                            (SHIFT_GET(x##_MASK,31,0) \
                            ) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))

#define SHIFT_TEST( val , shift ) ( (val) & (1U << (shift)) )

#define SHIFT_GET( val, shift, next_shift) (SHIFT_TEST((val),(shift)) ? \
(shift) : (next_shift))

이렇게 했을 때,

#define CONFIG_VREF_SEL_MASK 0x70
#define VREF_SHIFT_VAL SHIFT_FROM_MASK(CONFIG_VREF_SEL)
이라면,

ADIE_ADC_VREF_SHIFT_VAL는 얼마가 될까요~?
답은 4 ㅋ

친절한 임베디드 시스템 개발자 되기 강좌 글 전체 리스트 (링크) -



댓글





친절한 임베디드 개발자 되기 강좌 글 전체 리스트 (링크) -