November 24, 2020

디바이스마트 미디어:

디바이스마트 온라인 매거진 전자책(PDF)이 무료! -

2020-09-29

디바이스마트 자체제작 코딩키트 ‘코딩 도담도담’ 출시 -

2020-08-10

GGM AC모터 대량등록! -

2020-07-10

라즈베리파이3가 드디어 출시!!! (Now Raspberry Pi 3 is Coming!!) -

2016-02-29

MoonWalker Actuator 판매개시!! -

2015-08-27

디바이스마트 레이저가공, 밀링, 선반, 라우터 등 커스텀서비스 견적요청 방법 설명동영상 입니다. -

2015-06-09

디바이스마트와 인텔®이 함께하는 IoT 경진대회! -

2015-05-19

드디어 adafruit도 디바이스마트에서 쉽고 저렴하게 !! -

2015-03-25

[29호] Intel Edison Review -

2015-03-10

Pololu 공식 Distributor 디바이스마트, Pololu 상품 판매 개시!! -

2015-03-09

[칩센]블루투스 전 제품 10%가격할인!! -

2015-02-02

[Arduino]Uno(R3) 구입시 37종 센서키트 할인이벤트!! -

2015-02-02

[M.A.I]Ahram_ISP_V1.5 60개 한정수량 할인이벤트!! -

2015-02-02

[디웰전자] 전류센서 파격할인!! 50% 할인이벤트!! -

2015-02-02

[에스엔에스] 신상품 대량 입고기념 할인이벤트!! -

2015-02-02

[Uni-Trend] 인기상품 10종 할인이벤트!! -

2015-02-02

신학기 기념 니덱서보모터 할인이벤트!! -

2015-02-02

UDT와 유나이티드 콤프레샤 인기모델 15종 최대15% 할인!! -

2015-02-02

[OWON] 오실로스코프 VDS시리즈 최대12% 특별할인!! -

2015-02-02

보조배터리 최대 35% 할인이벤트!! -

2015-02-02

[18호]JK전자와 함께하는 ARM 완전정복(4)-2

jk전자 JK전자와 함/께/하/는 ARM 완전 정복

Ⅱ.ARM Applications – 2부

글 | JK전자

(7) Stack Pointer 초기화

각 프로세서 모드별(7개 동작모드)로 Processor Mode를 전환하면서 Stack을 초기화해야 합니다. 이전 강좌인 ARM Architecture의 ARM Register 부분을 다시 보시면 R13(SP)는 각 동작 모드별로 뱅크 되어 있는 레지스터임을 확인 할 수 있습니다. 여기서 주의해야 할 점은 User Mode Stack은 제일 마지막에 초기화해야 합니다. 왜 그럴까요? User Mode는 비특권 모드이므로 한번 User Mode로 진입을 하여 SWI 명령등을 사용하지 않으면 다시 특권 모드로 진입을 할 수 없어서 다른 Processor Mode의 Stack Pointer를 초기화할 수가 없습니다. 이런 이유로 해서 User Mode의 Stack Pointer를 제일 마지막에 초기화하도록 해야 합니다.

;function initializing stacks
InitStacks
;Don’t use DRAM,such as stmfd,ldmfd……
;SVCstack is initialized before
;Under toolkit ver 2.5, ‘msr cpsr,r1′ can be used instead of ‘msr cpsr_cxsf,r1′
mrs r0,cpsr
bic r0,r0,#MODEMASK
orr r1,r0,#UNDEFMODE|NOINT
msr cpsr_cxsf,r1 ;UndefMode
ldr sp,=UndefStackorr r1,r0,#ABORTMODE|NOINT
msr cpsr_cxsf,r1 ;AbortMode
ldr sp,=AbortStackorr r1,r0,#IRQMODE|NOINT
msr cpsr_cxsf,r1 ;IRQMode
ldr sp,=IRQStackorr r 1,r0,#FIQMODE|NOINT
msr cpsr_cxsf,r1 ;FIQMode
ldr sp,=FIQStack ;// 아래(System Mode Stack) 부분 추가 함.bic r0,r0,#MODEMASK|NOINT
orr r1,r0,#SYSMODE
msr cpsr_cxsf,r1 ;SYSMode
ldr sp,=SYSStackbic r0,r0,#MODEMASK|NOINT
orr r1,r0,#SVCMODE
msr cpsr_cxsf,r1 ;SVCMode
ldr sp,=SVCStack;USER mode has not be initialized. We use always SVC mode.mov pc,lr ;The LR register won’t be valid if the current mode is not SVC mode.

(8) Segment Initialization

ROM Binary가 만들어 지고 나서 프로그램이 실행되기 위해서는 반드시 RAM이 있어야 합니다. Segment Initialization은 RAM에서 프로그램이 올바른 데이터를 가지고 실행될 수 있도록 RAM 영역에 전역변수의 초기값들을 저장하는 일을 합니다. 좀 더 자세한 사항은 이전 강좌인 ARM Architecture 4.4 Linker 부분을 참조하시기 바랍니다.

18feajkarm042

18feajkarm043

위의 그림에서 왼쪽이 ROM Binary 이고 오른쪽이 RAM 영역입니다.
- .data : C언어 등에서 선언한 전역변수들의 초기값이 저장되어 있는 영역입니다.
- .bss : 0으로 초기화 되는 영역입니다. 초기값을 지정하지 않은 전역변수등이 여기에 해당합니다.
- .textrw : 컴파일러에 의해 생성된 RAM에서 실행되는 함수들 입니다.

#pragma segment = “.bss”
#pragma segment = “.data”
#pragma segment = “.data_init”
#pragma segment = “.rodata”
#pragma segment = “.textrw”
#pragma segment = “.textrw_init”
#pragma segment = “CSTACK”
void InitSegment()
{
unsigned int k, n;
unsigned char *pdst, *psrc;//
// initialize zero-initialized segment
//
n = (unsigned int)__section_end(“.bss”) – (unsigned int)__section_begin(“.bss”);
pdst = (unsigned char *)__section_begin(“.bss”);for(k=0; k<n; k++)
{
*(pdst + k) = 0;
}
//
// initialize non-zero-initialized segment
//
n = (unsigned int)__section_end(“.data_init”) – (unsigned int)__section_begin(“.data_init”);
pdst = (unsigned char *)__section_begin(“.data”);
psrc = (unsigned char *)__section_begin(“.data_init”);
for(k=0; k<n; k++)
{
*(pdst + k) = *(psrc + k);
}//
// initialize segment for ram-function
//
n = (unsigned int)__section_end(“.textrw_init”) – (unsignedint)__section_begin(“.textrw_init”);
pdst = (unsigned char *)__section_begin(“.textrw”);
psrc = (unsigned char *)__section_begin(“.textrw_init”);
for(k=0; k<n; k++)
{
*(pdst + k) = *(psrc + k);
}return;
}

(9) main 함수로 이동

EXTERN main
ldr pc, =main

드디어 모든 Stack초기화와 Segment 초기화 과정을 끝내고 C 함수를 호출할 수 있게 되었습니다. 이제부터는 C 코드를 이용해서 각 디바이스들을 테스트할 수 있게 되었네요.

 

5.2 GPIO Output(LED On/Off)

LED를 컨트롤 하기 위해서는 포트를 Output으로 설정한 후에 GPIO(General Purpose Input Output) 포트에 Low or High를 출력하면 됩니다. 아래 회로도는 우리가 실습에 사용하고 있는 Mini2440의 LED 회로도입니다. 4개의 LED가 있는데 각 LED는 GPB5 ~ 8 에 연결이 되어 있습니다. 그렇다면 LED1을 켜기 위해서 GPB5 포트에 Low(0)로 해야 할까요? 아니면 High(1)로 해야 할까요? 정답은 LED의 한쪽 끝이 VDD33V에 연결이 되어 있기 때문에 Low로 설정해야 전류의 흐름이 발생하여 LED가 켜지게 됩니다.

18feajkarm045
실험예제
LED1, LED2는 On을 하고, LED3, LED4는 Off 시켜 봅니다.
(1) GPBCON 레지스터에 GPB5 ~ GPB8을 Output으로 설정합니다.

18feajkarm014

// GPB5 Output
(*(volatile unsigned *)0×56000010) = (*(volatile unsigned *)0×56000010) & ~(0×3 << 10);
(*(volatile unsigned *)0×56000010) = (*(volatile unsigned *)0×56000010) | (0×1 << 10);// GPB6 Output
rGPBCON = rGPBCON & ~(0×3 << 12);
rGPBCON = rGPBCON | (0×1 << 12);// GPB7 Output
rGPBCON = rGPBCON & ~(0×3 << 14);
rGPBCON = rGPBCON | (0×1 << 14);// GPB8 Output
rGPBCON = rGPBCON & ~(0×3 << 16);
rGPBCON = rGPBCON | (0×1 << 16);

(2) GPBDAT 레지스터의 GPB5, GPB6을 Low로, GPB7, GPB8을 High로 세팅합니다.

// LED1 On
rGPBDAT = rGPBDAT & ~(0×1 << 5);
// LED2 On
rGPBDAT = rGPBDAT & ~(0×1 << 6);
//rGPBDAT = rGPBDAT | (0×1 << 6);
// LED3 Off
rGPBDAT = rGPBDAT | (0×1 << 7);
// LED4 Off
rGPBDAT = rGPBDAT | (0×1 << 8);

diag.c – led_test()

// GPB5 Output
(*(volatile unsigned *)0×56000010) = (*(volatile unsigned *)0×56000010) & ~(0×3 << 10);
(*(volatile unsigned *)0×56000010) = (*(volatile unsigned *)0×56000010) | (0×1 << 10);// GPB6 Output
rGPBCON = rGPBCON & ~(0×3 << 12);
rGPBCON = rGPBCON | (0×1 << 12);// GPB7 Output
rGPBCON = rGPBCON & ~(0×3 << 14);
rGPBCON = rGPBCON | (0×1 << 14);// GPB8 Output
rGPBCON = rGPBCON & ~(0×3 << 16);
rGPBCON = rGPBCON | (0×1 << 16);// LED1 On
rGPBDAT = rGPBDAT & ~(0×1 << 5);
// LED2 On
rGPBDAT = rGPBDAT & ~(0×1 << 6);// LED3 Off
rGPBDAT = rGPBDAT | (0×1 << 7);
// LED4 Off
rGPBDAT = rGPBDAT | (0×1 << 8);

소스코드 분석

(*(volatile unsigned *)0×56000010) = (*(volatile unsigned *)0×56000010) & ~(0×3 << 10);
GPBCON 레지스터의 주소가 0x50000010입니다. 0×3(b11)을 왼쪽으로 10번 쉬프트를 하면 b110000000000이고 이것을 “~” (Bit clear) 시키면 GPB5[11:10] 부분이 “00″ 으로 Clear 됩니다.

(*(volatile unsigned *)0×56000010) = (*(volatile unsigned *)0×56000010) | (0×1 << 10);
0×1(b01)을 왼쪽으로 10번 쉬프트를 하면 b010000000000 이고 이것을 “|” (OR) 시키면 GPB5[11:10] 부분이 “01″ 으로 Output 설정됩니다.

소스 코드중에 volatile 이라는 것을 사용하고 있습니다. 이것은 컴파일러 최적화에서 제외되는 효과가 있습니다.

컴파일러 최적화 전 컴파일러 최적화 전
int a = 0; // 전역 변수
int b = 0; // 전역 변수
int i; // 로컬 변수for(i=0;i<100;i++)
b = b + a * 100;
int a = 0; // 전역 변수
int b = 0; // 전역 변수
int i; // 로컬 변수for(i=0;i<100;i++)
b = b + 0; // a * 100;

위의 코드에서 오른쪽 코드처럼 개발자가 의도하지 않게 컴파일러에 의해 최적화가 되어 “a*100″ 부분을 최적화하여 b 변수에 항상 0으로 저장이 되도록 할 수도 있습니다. 물론 일반적인 상황에서는 아무 문제가 되지 않지만 for 루프 수행중에 외부 인터럽트가 발생하여 인터럽트 서비스 루틴에서 a의 값을 0이 아닌 다른 값으로 변하게 한 후 다시 for 루프를 수행하면 개발자는 b에 0이 아닌 다른값이 저장되기를 기대하고 있겠지만 최적화된 코드에서는 b에 항상 0 이 저장이 되어 있을 것입니다. 이것은 개발자가 의도한 결과가 아닙니다. 이런 현상을 방지하기 위해서는 변수 a 를 volatile로 선언을 하면 됩니다. 특히나 SFR 레지스터 등에 값을 세팅하는 작업을 한다면 항상 volatile 로 선언을 해서 사용하는 것이 좋습니다.

5.3 GPIO Input( KEY Input) – Polling

LED를 켰을 때와 반대로 이번에는 GPIO포트를 이용해서 입력을 받아 보도록 하겠습니다. 포트에서 입력을 받기 위해서는 포트를 Input으로 설정한 후에 GPIO(General Purpose Input Output) 포트를 읽으면 됩니다. 6개의 KEY가 있는데 우리는 INT11, INT13에 연결되어 있는 K2, K3에 대해서 폴링 방식으로 입력을 감지해 보도록 하겠습니다. 참고로 폴링 방식이란 코드에서 무한 루프를 돌면서 특정 행위를 하는 것을 말합니다. 이번 경우에는 Key가 눌러졌는지를 감시하는 것이겠지요. 폴링과 다른 방식으로는 인터럽트 방식이 있습니다. 다음 절에서 공부하게 될 내용입니다.

18feajkarm044

실험예제
K2, K3 를 읽어서 KEY가 눌러져 있으면 LED2, LED3 를 On으로 하고, 눌러져 있지 않으면 Off로 설정해봅시다.

(1) GPGCON 레지스터에 GPG3, GPG5를 Input으로 설정합니다.

18feajkarm015

// KEY3, GPG5 Input
rGPGCON = rGPGCON & ~(0×3 << 10);
// KEY2, GPG3 Input
rGPGCON = rGPGCON & ~(0×3 << 6);

 

(2) GPGDAT 레지스터의 GPG3, GPG5를 읽어서 “0″ 이면 KEY가 눌린 상태이고, “1″ 이면 KEY가 눌러지지 않은 상태입니다.

diag.c – key_test()

// KEY3, GPG5 Input
rGPGCON = rGPGCON & ~(0×3 << 10);
// KEY2, GPG3 Input
rGPGCON = rGPGCON & ~(0×3 << 6);while(1)
{
if( (rGPGDAT & (0×1 << 3)) == 0 ) // KEY2 pressed
// LED2 On
rGPBDAT = rGPBDAT & ~(0×1 << 6);
else
// LED2 Off
rGPBDAT = rGPBDAT | (0×1 << 6);
if( (rGPGDAT & (0×1 << 5)) == 0 ) // KEY3 pressed
// LED3 On
rGPBDAT = rGPBDAT & ~(0×1 << 7);
else
// LED3 Off
rGPBDAT = rGPBDAT | (0×1 << 7);Delay(80);
};

 

5.4 GPIO Input( KEY Input) – Interrupt

이번에는 인터럽트 방식으로 K2, K3 입력을 감지해보도록 하겠습니다. 인터럽트 방식은 폴링방식보다 효율적이기는 하지만 좀 더 복잡합니다. 여기서 효율적이라고 하는 것은 CPU사용을 효과적으로 한다는 것이지 폴링방식보다 빠르다는 말은 아닙니다. 인터럽트 방식은 인터럽트 요청이 왔을때 인터럽트 서비스 루틴으로 분기할 때까지 지연시간이 있어 폴링 방식보다 느릴 수 있습니다. 하지만 폴링방식처럼 계속 루프를 돌면서 대기하지 않아도 되기 때문에 CPU활용면에서는 효율적입니다.

실험예제
K2, K3를 읽어서 KEY가 눌러져 있으면 LED2, LED3를 On으로 하고 눌러져 있지 않으면 Off로 설정해봅시다.

(1) GPGCON 레지스터에 GPG3, GPG5를 EINT(0×2)으로 설정합니다.

18feajkarm017

// KEY3, GPG5 EINT13
rGPGCON = rGPGCON & ~(0×3 << 10);
rGPGCON = rGPGCON | (0×2 << 10);// KEY2, GPG3 EINT11
rGPGCON = rGPGCON & ~(0×3 << 6);
rGPGCON = rGPGCON | (0×2 << 6);

(2) EXTINT1 레지스터에 EINT11, EINT13을 Falling Edge로 설정합니다.

Extintn(External Interrupt Control Register n)

The 8 external interrupts can be requested by various signaling methods. The EXTINT register configures configures the signaling method between the level trigger and edge trigger for the extemal interrupt request, and also configuress the signal polarity.
To recognize the level interrupt, the valid logic level on EXTINTn pin must be retained for 40ns at least because of the noise filter.

18feajkarm018

// set eint13 falling edge trigger
rEXTINT1 &= ~(0×7 << 20);
rEXTINT1 |= (0×2 << 20);// set eint11 falling edge trigger
rEXTINT1 &= ~(0×7 << 12);
rEXTINT1 |= (0×2 << 12);

 

(3) EINTPEND 레지스터의 Pending bit를 Clear 합니다.

18feajkarm019

// clear eint 11,13
rEINTPEND |= (1 << 11)|(1 << 13);

“It is cleard by writing 1″ 이라는 문구가 보이는데요. 무슨 의미 일까요? 일반적으로 우리가 SFR 레지스터를 설정할 때에 아래와 같이 먼저 Bit를 Clear 시키고 Set 하는 2단계 과정을 거치게 됩니다.

rEXTINT1 &= ~(0×7 << 20);
rEXTINT1 |= (0×2 << 20);

하지만 인터럽트 서비스 루틴 안에서는 이것 조차도 비효율적일수 있고 Atmoic Operation이 아니기 때문에 Pending Clear를 하는 과정에서 새로운 인터럽트가 발생할 경우에 새로운 인터럽트가 지연될 수도 있습니다.

rEINTPEND |= (1 << 11)|(1 << 13);

이러한 이유로 위와 같이 “1″ 을 써넣어서 바로 Pending을 Clear 할 수 있도록 한 것입니다.

 

(4) EINTMASK 레지스터에서 Interrupt를 Enable 합니다.

18feajkarm020

// enable eint 11,13
rEINTMASK &= ~((1 << 11)|(1 << 13));

 

(5) SRCPND 레지스터의 Pending bit를 Clear 합니다.
18feajkarm021

rSRCPND = (0×1<<5); // Clear pending bit

 

(6) INTPND 레지스터의 Pending bit를 Clear 합니다.
18feajkarm022

rINTPND = (0×1<<5); // Clear pending bit

 

(7) EINT 인터럽트 서비스 루틴의 함수 포인터를 설정합니다.

pISR_EINT8_23 = (U32)isr_eint_8_23;

 

(8) INTMSK에서 EINT8_23 의 인터럽트를 Enable 합니다.
INTERRUPT MASK(INTMSK) REGISTER
This register also has 32 bits each of which is related to an interrupt source. If a specific bit is set to 1, the CPU does not service the interrupt request from the corresponding interrupt source(note that even in such a case, the corresponding bit of SRCPND register is set to 1). If the mask bit is 0, the interrupt request can be serviced.
18feajkarm023

rINTMSK &= ~((0×1<<5));

 

(9) Startup코드의 IRQ Exception에서 인터럽트 번호를 확인하고 인터럽트 서비스 루틴으로 분기합니다.

IsrIRQ
sub sp,sp,#4 ;reserved for PC –> 1
stmfd sp!,{r8-r9} ; –> 2
ldr r9,=INTOFFSET ; –> 3
ldr r9,[r9] ; –> 4
ldr r8,=HandleEINT0 ; –> 5
add r8,r8,r9,lsl #2 ; –> 6
ldr r8,[r8] ; –> 7
str r8,[sp,#8] ; –> 8
ldmfd sp!,{r8-r9,pc} ; –> 9

▶ 1 : 스택포인터를 1 감소하여 빈 공간을 만듭니다. 인터럽트 핸들러 함수의 주소값이 저장될 공간입니다.
▶ 2 : r8, r9를 사용하고 복원해야 하므로 stack에 저장합니다. stmfd는 먼저 어드레스를 감소하고 저장하므로 1에서 만들어진 빈공간은 그대로 남아 있습니다.
▶ 3, 4 : INTOFFSET을 r9에 로드합니다.
▶ 5 : HandleEINT0를 r8에 로드합니다. HandleEINT0는 Startup 코드의 아래 부분에 정의되어 있습니다.
▶ 6,7 : HandleEINT0 + INTOFFSET*4의 수식이 됩니다. INTOFFSET 레지스터에는 발생한 인터럽트 소스의 Offset 값이 들어있는 레지스터입니다.

우리는 EINT8_23을 이용할 것이기 때문에 INTOFFSET 레지스터에는 5가 저장되어 있을 것입니다. 그러므로 HandleEINT0 + 5*4 즉 HandleEINT8_23의 주소가 됩니다.
소스코드에서 key_test_eint() 함수에서 pISR_EINT8_23 = (U32)isr_eint_8_23; 의 코드를 삽입하면 EINT8_23 인터럽트가 발생하면 isr_eint_8_23 함수로 분기를 하게 됩니다.

18feajkarm024
▶ 8 : str r8,[sp,#8] 에 의해서 맨 처음에 Stack의 빈공간을 만들어 두었던 곳으로 인터럽트 핸들러 함수의 포인터 주소값을 저장합니다.
▶ 9 : r8, r9 를 Stack에서 복원하고 pc 에 인터럽트 핸들러 함수의 주소가 저장이 되어 실제로 인터럽트 핸들러 함수가 실행이 됩니다.

 

(10) 인터럽트 서비스 루틴에서 GPGDAT 레지스터의 GPG3, GPG5를 읽어서 “0″ 이면 KEY가 눌린 상태이고, “1″ 이면 KEY 눌러지지 않은 상태입니다.

지금까지 설명한 인터럽트 관련 설정들을 S3C2440 인터럽트 컨트롤러 블럭도와 비교해서 보시기 바랍니다.

18feajkarm046
Interrupt MODE도 IRQ/FIQ 설정을 해야 하지만 S3C2440의 기본 인터럽트 설정 값이 IRQ 이기 때문에 생략하였습니다.
외부 인터럽트 1개를 서비스 받기 위해서 정말 많은 과정이 필요 하네요. ARM Applications 이후에 하게될 Cortex-M3 Architecture 부분에서도 인터럽트 서비스에 대해서 설명을 할텐데, 그때 Cortex-M3가 얼마나 간결하고 효율적인지 비교해 보시기 바랍니다.

diag.c – key_test_eint()

__irq __arm void isr_eint_8_23(void)
{
if(rINTPND==BIT_EINT8_23)
{
ClearPending(BIT_EINT8_23);
if(rEINTPEND&(1<<11))
{
rEINTPEND |= 1 << 11;
rGPBDAT = rGPBDAT ^ (0×1 << 6); // toggle
}
if(rEINTPEND&(1<<13))
{
rEINTPEND |= 1 << 13;
rGPBDAT = rGPBDAT ^ (0×1 << 7); // toggle
}
}
}
void key_test_eint(void)
{// KEY3, GPG5 EINT13
rGPGCON = rGPGCON & ~(0×3 << 10);
rGPGCON = rGPGCON | (0×2 << 10);
// KEY2, GPG3 EINT11
rGPGCON = rGPGCON & ~(0×3 << 6);
rGPGCON = rGPGCON | (0×2 << 6);// set eint13 falling edge trigger
rEXTINT1 &= ~(0×7 << 20);
rEXTINT1 |= (0×2 << 20);// set eint11 falling edge trigger
rEXTINT1 &= ~(0×7 << 12);
rEXTINT1 |= (0×2 << 12);// clear eint 11,13 –> clear by writing “1″ test
rEINTPEND |= (1 << 11)|(1 << 13);// enable eint 11,13
rEINTMASK &= ~((1 << 11)|(1 << 13));
rSRCPND = (0×1<<5); // Clear pending bit –> clear by writing “1″ test
rINTPND = (0×1<<5); // Clear pending bit –> clear by writing “1″ testpISR_EINT8_23 = (U32)isr_eint_8_23;
// Enable EINT8_23 Interrupt
rINTMSK &= ~((0×1<<5));}

 

5.5 Timer

임베디드 시스템에서 Timer는 가장 중요하고 필수적인 요소중의 하나로 OS에서 Task 스케줄링을 위해서 사용되기도 합니다. 전자액자, 차량용 블랙박스 등의 응용 어플리케이션에서도 특정 시간 이후에 인터럽트를 발생시켜 정해진 일을 수행하는 경우등 응용분야는 무수히 많습니다.
시간 이야기가 나와서 이야기 하는데 CPU의 클럭 속도가 1Hz 라는 것은 무엇을 의미하는 것일까요? 이것은 1초에 1번의 Tick이 발생한다는 것입니다. 아주 정확한 것은 아니지만 캐시가 있는 시스템에서 캐시 Hit(캐시에서 데이터 혹은 명령어를 가지고 올 경우)일 경우에 1초에 1개의 명령어를 수행한다는 것과 같습니다. S3C2440이 400MHz 로 동작한다면 1초에 4억번의 Tick이 발생하는 것입니다. 정말 어마어마 하지요. 상상해 보세요. 요즘 최신 스마트폰들은 2GHz CPU이면서 그것도 듀얼 코어입니다. 아래는 시간과 주파수 사이의 관계입니다. Timer 설정하는데 이정도는 알고 있어야 합니다. 잘 기억하시기 바랍니다.

- 1sec = 1,000ms = 1,000,000us = 1,000,000,000ns
- 1Hz = 1KHz = 1MHz = 1GHz

실험예제
S3C2440 CPU의 내장된 Timer4를 이용해서 1초에 한번씩 Timer 인터럽트를 발생시켜 LDE2를 Blink(On/Off) 하는 실험을 해보도록 하겠습니다.

(1) TCFG0 레지스터의 Timer4 Prescaler를 15로 설정합니다.

Timer CONFIGURATION REGISTER0 (TCFG0)
Timer input clock Frequency = PCLK/{prescaler value+1}/{divider value}
{prescaler value} = 0~255
{divider value} = 2,4,8,16

18feajkarm025

// Timer4 Prescaler = 15
rTCFG0 = 0×0;
rTCFG0 = (15 << 8);

 

(2) TCFG1 레지스터의 Timer4 Divider를 1/2 로 설정합니다.

18feajkarm026

// Timer4 Divider = 2
rTCFG1 = 0×0;
rTCFG1 = (0×0 << 16);

위와 같이 설정하면 Datasheet 에 있는 아래 공식에 의해서

Timer input clock Frequency = PCLK / {prescaler value+1} / {divider value}
Timer input clock Frequency = 50000000/(15+1)/2 = 1562500

위의 계산식의 의미는 Timer4의 클럭이 1초에 1562500번 발생 한다는 의미입니다. 그러므로 Timer4 buffer count(TCNTB4)를 15625 로 설정해 주면 10ms마다 Auto Reload(Timer Overflow)가 되어 인터럽트가 발생하게 됩니다. 물론 간단하게 TCNTB4를 1562500로 설정해 주면 1초에 한번 발생하는 인터럽트를 만들 수 있겠지만, 안타깝게도 S3C2440의 타이머는 16비트 타이머이므로 최대 값으로 0xFFFF(65535) 이상을 사용할 수가 없습니다.

 

(3) TCNTB4 레지스터에 Timer4 buffer count를 15625로 설정합니다.

18feajkarm027

// buffer count
rTCNTB4 = 15625; // interrupt resolution 10msec

(4) TCON 레지스터에 Update TCNTB4와 Auto Reload를 설정합니다.

18feajkarm028

 

(5) TCON 레지스터를 설정하여 Timer를 Start 시킵니다.

// Start Timer 4
rTCON |= (1<<20);

 

(6) TCON에서 Manual Update를 해제하고 INTMSK에서 인터럽트 마스크를 Clear 시킵니다.

// clean manual update
rTCON &= ~(1<<21);// Enable interrupt
rINTMSK &= ~(0×1<<14);

이렇게 하면 Timer4 인터럽트가 10msec마다 한번씩 발생하게 됩니다.
18feajkarm040
diag.c – timer4_test()

__irq __arm void isr_timer4(void)
{
rSRCPND = BIT_TIMER4; //Clear pending bit
rINTPND = BIT_TIMER4;// Timer가 10msec 마다 발생하므로 1초에 한번씩 toggle 시키기 위해서
if( ++one_seconds_var == 100 )
{
rGPBDAT = rGPBDAT ^ (0×1 << 6); // toggle LED2
one_seconds_var = 0;
}void timer4_test(void)
{/*
Timer input clock Frequency = PCLK / {prescaler value+1} / {divider value}
{prescaler value} = 0~255
{divider value} = 2, 4, 8, 16
period = (prescaler value + 1) * (divider value) * buffer count / PCLK = 10 ms
e.g.; PCLK = 50 Mhz
10 ms = (15 + 1) * 2 * 15625 / (50000 * 1000)
15626 = 10 ms * (50000 * 1000) / 2 / (15 + 1)
*/// Timer4 Prescaler = 15
rTCFG0 = 0×0;
rTCFG0 = (15 << 8);// Timer4 Divider = 2
rTCFG1 = 0×0;
rTCFG1 = (0×0 << 16); // Timer input clock Frequency = PCLK / {prescaler value+1} / {divider value} // 50000000/(15+1)/2 = 1562500 –> 1초에 발생하는 Timer Tick// buffer count
rTCNTB4 = 15625; // interrupt resolution 10msecrTCON = rTCON & ~(0xffffff) | 0×1< rTCON = rTCON | (1 << 22); // Auto reloadpISR_TIMER4 = (int)isr_timer4;// Start Timer 4
rTCON |= (1<<20);// clean manual update
rTCON &= ~(1<<21);// Enable interrupt
rINTMSK &= ~(0×1<<14);

}

여기서 잠깐! 일반함수와 “__irq __arm” 가 있는 함수의 차이점은 무엇일까요?

일반 함수의 Return 부분

18feajkarm039

ISR 함수의 Return 부분

18feajkarm048
ISR 함수는 ARM Architecture에서 배운대로 Return을 SUBS PC,LR,#4로 하고 있습니다. 잘 기억이 나지 않으시면 ARM Architecture 자료에서 Pipeline과 Interrupt 부분을 다시 보시기 바랍니다.

5.6 PWM Buzzer

Buzzer, LED 등의 밝기 조정 등에 응용할 수 있습니다.

실험예제
Buzzer을 울리도록 하고 외부 인터럽트 방식으로 Key K2를 누르면 주파수를 10Hz 올리고 K3를 누르면 10Hz 주파수가 내려가도록 해봅시다.

18feajkarm049

 

(1) GPBCON 레지스터에서 GPB0를 TOUT0(Timer0 Output)으로 설정합니다.

18feajkarm029

rGPBCON &= ~0×3; // set GPB0 as tout0, pwm output
rGPBCON |= 0×2;

 

(2) TCFG0 레지스터의 Timer0 Prescaler를 15로 설정합니다.

PWM TIMER CONTROL REGISTERS
TIMER CONFIGURATION REGISTER0 (TCFG0)
Timer input clock Frequency = PCLK /{prescaler value+1} / {divider value}
{prescaler value} = 0~255
{divider value} = 2, 4, 8, 16

18feajkarm030

rTCFG0 &= ~0xff;
rTCFG0 |= 15; // prescaler = 15+1

 

(3) Timer0의 Divider 를 1/8 로 설정합니다.

18feajkarm031

rTCFG1 &= ~0xf;
rTCFG1 |= 2; // mux = 1/8

 

(4) Timer0의 Count buffer register, compare buffer register를 설정합니다.

18feajkarm032

rTCNTB0 = (Pclk>>7)/buzzer_freq;
rTCMPB0 = rTCNTB0>>1; // rTCNTB0/2

여기까지 설정을 하면 아래와 같이 1KHz의 주파수가 만들어집니다.

Timer input clock Frequency = PCLK / {prescaler value+1} / {divider value}
Timer input clock Frequency = 50000000/(15+1)/8 = 390625 → 1초에 발생하는 Timer Tick

50000000Hz(PCLK) >> 7 = 390625 → 결국은 1초에 1번 1Hz값, buzzer_freq(1000)으로 나누어 주면 TCNTBn은 1KHz가 됩니다.

추가로 TCMPBn은 TCNTBn/2로 하면 최종적으로 1KHz인 Timer0(TOUT0)으로 1KHz인 PWM 파형이 출력됩니다.

 

18feajkarm050

 

(5) Timer0를 Start 시킵니다.

18feajkarm033

rTCON &= ~0x1f;
rTCON |= 0xb; //disable deadzone, auto-reload, inv-off, update TCNTB0&TCMPB0, start timer 0
rTCON &= ~2; //clear manual update bit

diag.c – pwm_buzzer_test()

__irq __arm void isr_eint_8_23(void)
{if(rINTPND==BIT_EINT8_23)
{
ClearPending(BIT_EINT8_23);if(rEINTPEND&(1<<11)) // KEY2
{
rEINTPEND |= 1 << 11;
rGPBDAT = rGPBDAT ^ (0×1 << 6); // LED2 togglebuzzer_freq += 100;
pwm_buzzer_test();
}
if(rEINTPEND&(1<<13)) // KEY3
{
rEINTPEND |= 1 << 13;
rGPBDAT = rGPBDAT ^ (0×1 << 7); // LED3 togglebuzzer_freq -= 100;
pwm_buzzer_test();
}
}
}void pwm_buzzer_test(void)
{
rGPBCON &= ~0×3; // set GPB0 as tout0, pwm output
rGPBCON |= 0×2;rTCFG0 &= ~0xff;
rTCFG0 |= 15; // prescaler = 15+1rTCFG1 &= ~0xf;
rTCFG1 |= 2; // mux = 1/8// Timer input clock Frequency = PCLK / {prescaler value+1} / {divider value}
// 50000000/(15+1)/8 = 390625 –> 1초에 발생하는 Timer Tick// PCLK = 50000000Hz >> 7 = 390625 –> 결국은 1초에 1번 1Hz 값이다.
// buzzer_freq(1000) 으로 나누어 주면 1KHz가 된다.
rTCNTB0 = (PCLK>>7)/buzzer_freq;rTCMPB0 = rTCNTB0>>1; // rTCNTB0/2

rTCON &= ~0x1f;
rTCON |= 0xb; //disable deadzone, auto-reload, inv-off, update TCNTB0&TCMPB0, start timer 0
rTCON &= ~2; //clear manual update bit
}

 

5.7 UART

임베디드 프로그램에서 Timer와 함께 UART도 필수적인 인터페이스입니다. 개발초기에 디버그 모니터용으로 활용하기도 하고 개발이 완료된 이후에는 제품의 Setting, Firmware 업그레이드 등에도 활용합니다.

18feajkarm051

18feajkarm052

실험예제
UART0를 PC와 115200bps Baudrate로 통신(RX, TX)을 하는 Echo server로 만들어 봅시다.

(1) GPH2를 TXD로 GPH3을 RXD로 설정하고, GPH의 Pullup을 Disable 합니다.

18feajkarm034

rGPHCON &= ~( (0×3 << 6) | (0×3 << 4) );
rGPHCON |= ( (0×2 << 6) | (0×2 << 4) );
rGPHUP = 0x7ff; // The pull up function is disabled GPH[10:0]

 

(2) UART0의 FIFO Buffer를 Disable 합니다.

18feajkarm035

rUFCON0 = 0×0; //UART channel 0 FIFO control register, FIFO disable

 

(3) UART0의 Auto Flow Control을 Disable 합니다.

18feajkarm036

rUMCON0 = 0×0; //UART chaneel 0 MODEM control register, AFC disable

 

(4) UART Line Control Regier 설정 (Normal Mode, No Parity, One Stop Bit, Word Length = 8)

18feajkarm037

rULCON0 = 0×3; //Line control register : Normal,No parity,1 stop,8 bits

 

(5) UART Control Regier 설정

18feajkarm038

rUCON0 = (0×0 << 10) | (0×1 << 6) | (0×0 << 5) | (0×0 << 4) | (0×1 << 2) | (0×1 << 0);

 

(6) UART Baudrate Divisor Register 설정

18feajkarm047

rUBRDIV0 = ( (int)(Pclk/16./baud+0.5) -1 ); //Baud rate divisior register 0 value = 26

UBRDIV = (int)( UART clock / ( buad rate x 16) ) -1
( UART clock : PCLK, FCLK/n or UEXTCLK )

우리는 UART Clock으로 PCLK 50MHz(50000000)을 사용하고 있으므로 계산식은 다음과 같습니다.

UBRDIV = ( PCLK / (115200*16) ) – 1 = 26

diag.c – uart0_test()

void uart0_send_byte(int data)
{
while(!(rUTRSTAT0 & 0×2)); //Wait until THR is empty.
Delay(10);
WrUTXH0(data);}void uart0_test(unsigned int baud)
{
rGPHCON &= ~( (0×3 << 6) | (0×3 << 4) );
rGPHCON |= ( (0×2 << 6) | (0×2 << 4) );rGPHUP = 0x7ff; // The pull up function is disabled GPH[10:0]rUFCON0 = 0×0; //UART channel 0 FIFO control register, FIFO disable
rUMCON0 = 0×0; //UART chaneel 0 MODEM control register, AFC disable//UART0
rULCON0 = 0×3; //Line control register : Normal,No parity,1 stop,8 bitsrUCON0 = (0×0 << 10) | (0×1 << 6) | (0×0 << 5) | (0×0 << 4) | (0×1 << 2) | (0×1 << 0);rUBRDIV0 = ( (int)(Pclk/16./baud+0.5) -1 ); //Baud rate divisior register 0 value = 26while(!(rUTRSTAT0 & 0×4)); //Wait until tx shifter is empty.uart0_send_byte(‘E’);
uart0_send_byte(‘c’);
uart0_send_byte(‘h’);
uart0_send_byte(‘o’);
uart0_send_byte(‘\r’);
uart0_send_byte(‘\n’);while(1)
{
Delay(10);
while(!(rUTRSTAT0 & 0×1)); //Receive data ready
uart0_send_byte((*(volatile unsigned char *)0×50000024));
}

}

위의 코드는 터미널창에 “Echo” 를 Display 하고 터미널에서 입력한 문자를 바로 Echo 하여 다시 터미널 창에 표시하게 됩니다.

 

이것으로 ARM Applications 4부를 마치며,
다음호에서는 III. Cortex-M3 Architecture에 대하여 살펴보도록 하겠습니다. 

Leave A Comment

*