[19호]JK전자와 함께하는 ARM 완전정복(5)
JK전자와 함/께/하/는 ARM 완전 정복
Ⅲ.Cortex-M3 Architecture
글 | JK전자 |
드디어 길고 지루했던 전통적인 ARM에 대해서 어느 정도 설명이 끝나고 원래 목표하고자 했던 Cortex-M3 파트까지 왔습니다. 이전 파트에서 아쉬움이 있다면 Cache, MMU 부분이 빠졌다는 것입니다. 기회가 된다면 Cortex-M3 강좌가 끝나면 Cache, MMU 부분만 별도로 강좌를 올릴 수 있도록 하겠습니다. 사실 Cortex-M3는 ARM7, ARM9 보다는 단순한 면이 있습니다. Core의 동작 모드가 Handler, Thread 2가지(전통적인 ARM은 7가지) 밖에 없고 Architecture 차원에서 Memory Map이 동일하기 때문입니다. 또한 어셈블리어를 잘 몰라도 Startup 코드를 작성할 수 있고 실제로 대부분의 인기있는 컴파일러는 컴파일러 차원에서 Startup 코드를 제공하고 있습니다. 이것이 가능한 것도 Architecture 차원에서 Memory Map이 동일하기 때문입니다. 이번 강좌에서는 Cortex-M3의 주요한 특징인 Bit Banding, System Timer, NVIC, Programmers’s Model에 대해서 주로 다루도록 하겠습니다.
강의 전체 로드맵
I. ARM Architecture | 임베디드 시스템 개론에 대한 설명과 ARM7, ARM9 의 구조에 대해서 설명합니다.
II. ARM Applications | 삼성의 S3C2440(ARM9) 개발보드(S3C2440 Mini 개발보드)를 이용해서 어셈블리어와 UART, GPIO 등을 실습합니다.
III. Cortex-M3 Architecture | Cortex-M3의 특징과 구조에 대해서 설명합니다.
IV. Cortex-M3 Applications | STM32F103VCT6 Dragon 개발보드를 이용해서 GPIO, LCD, SPI, UART, MP3, SDIO, I2C 등을 실습합니다.
이 강의 자료에 대한 모든 질의사항은 http://cafe.naver.com/avrstudio의 ARM Architecture Q&A게시판에 글을 남겨주시거나 jk@deviceshop.net로 메일을 보내주시기 바랍니다. 가급적이면 여러 사람이 질문에 대한 답변을 공유할 수 있도록 네이버 카페 게시판을 이용해주셨으면 합니다. 감사합니다.
Ⅲ. Cortex-M3 Architecture
1. Cortex-M3 Processor 소개
1.1 ARM Cortex-M3 프로세서란 무엇인가?
2. Cortex-M3 Procesor 개요
2.1 Cortex-M3 Procesor Overview
2.2 Cortex-M3 Processor Block Diagram
3. Cortex-M3 Processor Architectureㅅ
3.1 Register
3.2 Operation Mode
3.3 Stack
3.4 Cortex-M3 Memory Map
3.5 Bit Banding
3.6 System Timer(SysTick)
4. Nested Vectored Interrupt Controller
4.1 NVIC
4.2 Interrupt Response
1. Cortex-M3 Processor 소개
1.1 ARM Cortex-M3 프로세서란 무엇인가 ?
Cortex-M3 Processor는 ARMv7-M profile 프로세서로 low gate count, low interrupt latency, and low-cost의 특징을 갖는 기존의 8Bit Microcontroller(AVR, PIC, 8051 등) 시장에 대응하는 Processor입니다. 또한 Cortex는 각각 다른 특징을 갖는 3가지의 Profile이 있습니다.
(1) A profile (ARMv7-A) : Application Profile
- For sophisticated, high-end applications running open and complex operating systems
- ARM, Thumb, Thumb-2 instruction sets
- S5PC100, S5PV210, OMAP3530 ..
(2) R profile (ARMv7-R) : Real-time Profile
- For real-time system
- ARM, Thumb, Thumb-2 instruction sets
(3) M profile (ARMv7-M) : Microcontroller Profile
- For cost-sensitive and microcontroller applications
- Thumb-2 instruction set only
- Banked Stack Pointer (SP) only.
- Hardware divide instructions, SDIV and UDIV (Thumb-2 32-bit instructions).
- Handler and Thread modes.
- Thumb and Debug states.
- Interruptible-continued LDM/STM, PUSH/POP for low interrupt latency.
- Automatic processor state saving and restoration for low latency Interrupt Service Routine (ISR) entry and exit.
- Support for ARMv6 unaligned accesses.
- Support Nested Vectored Interrupt Controller (NVIC)
- STM32F, LPC111x Series
2. Cortex-M3 Procesor 개요
2.1 Cortex-M3 Procesor Overview
(1) Thumb-2 Instruction Set Architecture
- 16, 32 bit 명령을 조합해서 사용할 수 있습니다.
- No more mode switching
- 16-bit code density로 32-bit 명령 성능을 낼 수 있습니다.
- 16-bit Thumb Instruction과 하위 호환성이 있습니다.
(2) Harvard architecture
- Separate I&D buses allow parallel instruction fetching & data storage
- 명령어와 Data를 동시에 Fetch할 수 있는 기능은 인터럽트 수행시 Latency를 줄일수 있는 중요한 feature입니다.
(3) 3-Stage Pipeline with Branch Speculation- Fetch, Decode, Execute
- 분기 예측을 할 수 있다면 Pipeline에서 Branch 시 Pipeline Flush를 줄일 수 있어 시스템의 성능을 높일 수 있습니다.
(4) Integrated Nested Vectored Interrupt Controller(NVIC) for low latency interrupt processing
(5) Vector Table is address, not instruction
(6) Designed to be fully programmed in “C”
(7) System Timer(SysTick) for Real Time OS – Not Peripheral timer
(8) Bit Band Aliasing
(9) Interruptible-continued LDM/STM, PUSH/POP
(10) Support Unaligned Data Access
- Unaligned access를 지원해서 메모리 사용의 효율성은 높였지만 여전히 속도를 위해서는 Aligned access를 하는 것이 효율적입니다.
2.2 Cortex-M3 Processor Block Diagram
(1) Cortex-M3 블럭도
Cortex-M3 Core는 Bus Matrix( I-code, D-code, System Bus)를 통해서 Cortex-M3 코어를 Base로 하는 CPU들(STM32F, LPC111x 시리즈 등)과 연결이 됩니다.
(2) Bus Matrix System
- ICode Bus : Instruction & Vector fetches from CODE space
(0×0000.0000 ~ 0x1FFF.FFFF)
- DCode Bus : Data & Debugging access to CODE space
(0×0000.0000 ~ 0x1FFF.FFFF)
- System Bus : Instruction & Vector fetches from System Memory space
Data & Debug accesses to System Memory space
System Memory( SRAM, External RAM )
(3) STM32F 시리즈 블럭도
위의 블럭도에서 Cortex-M3부분만 ARM사에서 디자인한 것이고 나머지 부분은 ST Microelectronics에서 설계한 것입니다. Philips사의 LPC11x Cortex-M3 시리즈도 Cortex-M3 Core 부분은 STM32F 시리즈의 Core와 동일하고 주변 Peripheral, interface bus 등의 부분만 다른 것입니다.
(4) Cortex-M3와 ARM7 비교
ARM7TDMI | Cortex-M3 | |
Architecture | ARMv4T(von Neumann) | ARMv7M(Harvard) |
ISA Support | ARM(32-Bit) & Thumb(16-Bit) need Mode Change |
Thumb-2 Only No more Mode Change |
DMIPS/MHz | 0.74(Thumb)/0.93(ARM) | Thumb-2(1.25) |
Pipeline | 3-Stage | 3-Stage+Branch Speculation |
Interrupts | IRQ/FIQ | NMI,SysTick and up to 240Interrupts. Integrated NVICInterrupt ControllerUp to 1-255 Priorities |
Interrupt Latency | 24~42 Cycles | 12 Cycles(6 when Tail Chaining) |
Memory Map | Undefined | Architecture defined |
System Status | PSR, 6modes 20 Banked regs |
xPSR, 2modes(Thread, Handler) Stacked regs(1 bank) |
Sleep Modes | No | Three |
3. Cortex-M3 Processor Architecture
3.1 Register
(1) General Register
전통적인 ARM(ARM7,ARM9)에서는 7개의 동작 모드별로 Banked Register가 있었으나 Cortex-M3에 와서는 R13(SP)이 Main Stack Pointer와 Process Stack Pointer로 구분되어 Banked Register로 존재하고 나머지 레지스터는 Cortex-M3 동작 모드(Thread Mode, Handler Mode)에 상관없이 1개씩만 존재합니다. 16-bit Thumb 명령어에서는 R0 ~ R7 레지스터만 사용이 되고 32-bit Thumb2 명령어에서는 R0 ~ R15가 모두 사용이 됩니다. Stack Pointer (R13)는 항상 4-Byte정렬이 되어 운영되어야 하므로 Stack Pointer의 하위 2비트는 항상 ’2b00′이 되어야 하겠지요. 그리고 Stack은 Full Descending 방식으로 운영이 됩니다. Linked Register(R14)는 전통적인 ARM과 마찬 가지로 BLX 명령어 사용시 복귀할 주소가 저장되어 있습니다. Program Counter(R15)는 당연히 현재 실행하고 있는 명령어의 주소(엄밀히 말하연 Pipeline 단계에서 Fetch 하고 있는 명령어의 주소)를 가지고 있습니다. 그러므로 실제 PC는 현재 실행하고 있는 명령어 다음 다음의 명령어의 주소가 저장되어 있습니다.
(2) Special Register
전통적인 ARM에서는 CPSR(Current Processer Status Register) 이라는 특별한 상태 레지스터와 동작 모드별로 SPSR(Saved Processor Status Register)이 존재하였는데, Cortex-M3에서는 Special Register의 종류가 많이 늘어 났습니다. 하지만 기존의 CPSR 레지스터와 저장하고 있는 정보는 비슷합니다.
xPSR은 APSR, IPSR, EPSR 3개의 Special Register로 나누어 볼수 있습니다.
■ APSR(Application Program Status Register)
현재 실행하고 있는 프로그램의 상태 정보를 담고 있습니다. 예를 들면 CMP 명령어와 같은 비교문이나 ALU 연산의 결과가 APSR 레지스터의 Flag 비트를 업데이트 합니다.
▷ 어셈블리어
CMP R0, R1 ; R0 = 0, R1 = 1, R0 – R1 and update APSR Register
ADDMI R0, R0, R1 ; R0 = R0 + R1
SUBEQ R0, R0, R1 ; R0 = R0 – R1
▷ C언어
if( R0 – R1 < 0 )
R0 = R0 + 1
else
R0 = R0 – R1
CMP 명령어는 결국은 R0 – R1을 한 결과를 가지고 APSR 레지스터를 업데이트 합니다. 결국 0에서 1을 빼면 -1이 되고 Negative “N” 이 APSR레지스터 31번 비트에 Set이 됩니다. “N” Flag가 Set이 되면 위의 명령어 중에서 ADDMI 명령어만 실행이 되고 SUBEQ 명령어는 NOP 명령어로 대체가 되어 실행이 되지 않습니다. ARM7에서는 CPSR 레지스터의 [31:27] 비트와 같은 역할을 합니다.
■ IPSR(Interrupt Program Status Register)
이전의 ARM Processor는 인터럽트 컨트롤러가 ARM Core에 존재하지는 않았습니다. 주로 CPU를 설계, 제조하는 반도체 Vendor에서 ARM Core의 외부에 CPU 설계시 추가하는 Peripheral들 중의 하나였습니다. 그래서 IRQ가 발생하면 어떤 인터럽트가 발생하였는지를 CPU에 존재하는 인터럽트 컨트롤러에서 INTOFFSET과 같은 ARM Core 외부의 SFT 레지스터에 저장이 되었습니다. 하지만 Cortex-M Profile에서는 인터럽트 컨트롤러가 ARM Core 내부(NVIC)에 존재합니다. IPSR은 현재 수행중인 Exception(Interrupt)의 번호를 저장하고 있는 레지스터입니다.
■ EPSR(Execution Program Status Register)
ICI(Interruptible-Continuable Instruction)/IT(If-Then) 2개의 중첩된 필드를 지니고 있는 레지스터입니다. 전통적인 ARM에서는 LDM, STM 명령어 수행 중에 인터럽트가 발생을 하더라도 멈출수가 없었습니다. 명령어 Boundary에서 인터럽트 처리가 되기 때문에 LDM(POP), STM(PUSH) 명령어 수행이 끝나고 나서야 인터럽트를 처리할 수가 있습니다. 하지만 Cortex-M3에서는 LDM(POP)/STM(PUSH) 명령 수행 중에도 인터럽트 처리가 가능합니다.
LDMFD SP!, {R0, R1, R4-R6}
위와 같이 Multiple Load 명령어 수행시 R1을 Load 하는 중에 인터럽트가 발생을 하게되면 Cortex-M3에서는 R1 레지스터까지만 처리한 다음 LDM 명령을 중단하고 현재 발생한 Exception(인터럽트) 처리를 끝냅니다. 그리고 중단되었던 나머지 R4-R6 Load 명령을 수행하게 되는데, 이때 인터럽트를 끝내고 나서 중단되었던 레지스터부터 다시 Load 명령어를 수행할 수 있도록 임시로 ICI 필드에 중단 지점의 레지스터 순서를 저장하게 되는데 그 저장소가 바로 EPSR 레지스터의 ICI 필드입니다. [15:12] 비트만 사용을 하고 나머지 비트 [11:10], [26:25] 필드는 사용하지 않습니다.
IT 필드는 Thumb-2 명령어인 If-Then 블럭의 Condition과 명령어의 순서 번호를 가지고 있습니다.
CMP R0, R1
ITTEE EQ
ADDEQ R2, R0, R1 ; R2 = R0 + R1
ADDEQ R2, R0, R3 ; R2 = R0 + R3
SUBNE R2, R0, R1 ; R2 = R0 – R1
SUBNE R2, R0, R3 ; R2 = R0 – R3
위의 어셈블리어 명령어들을 해석해 보면 R0 – R1 한 결과가 “0″ 이면 ADDEQ 명령어 2개를 실행하고 “0″ 이 아니면 SUBNE 명령어 2개를 실행하라 입니다. C언어로 표현하면 아래와 같습니다.
if( R0 == R1 )
{
R2 = R0 + R1
R2 = R0 + R3
}
else
{
R2 = R0 – R1
R2 = R0 – R3
}
If-Then 명령어 Block 수행중에 인터럽트가 발생하면 인터럽트 서비스 루틴으로 분기를 했다가 다시 If-Then 명령어 Block으로 복귀하여 인터럽트가 발생했던 지점의 다음 명령어부터 수행해야 하는데 인터럽트 서비스 루틴으로 분기하기 전에 IT 필드에 몇 번째 명령어까지 수행했었는지를 잠시 저장하는 레지스터입니다. 이때 IT[7:5] 비트는 Base Condition 정보 “CMP” 를 저장하고 있고 IT[1:0], IT[7:4] 비트는 If-Then 명령어 Block안의 ISR이 발생하기 전까지의 수행된 명령어 번호를 저장하고 있습니다. 또한 ICI/IT는 EPSR 레지스터 안에서 같은 비트의 필드를 공유하고 있기 때문에 If-Then Block에서 LDM/STM과 같은 Multiple Load/Store 명령어를 사용하면 LDM/SDM 명령어 수행 중에 인터럽트가 발생해서 인터럽트 서비스 루틴을 끝내고 복귀 했을때 수행했던 레지스터 번호부터 시작하지 못하고 처음부터 다시 LDM/STM 명령어가 수행되게 됩니다. EPSR 레지스터의 각 비트들을 표로 정리하였습니다.
EPSR[26:25] | EPSR[15:12] | EPSR[11:10] |
IT[1:0] | IT[7:4] [7:5] –> Base Condition 정보 | IT[3:2] |
ICI[7:6] (‘00’) | ICI[5:2] –> reg_num | ICI[1:0] (‘00’) |
reg_num는 LDM, STM 명령어 수행 중에 인터럽트가 발생하여 잠시 중단되었던 레지스터의 번호입니다.
3.2 Operation Mode
전통적인 ARM에서는 동작모드가 7종류(User, System, Fiq, Irq, SVC, Abord, Undefined)가 있었습니다. Cortex-M3에서는 Thread, Handler Mode 두가지로 축소되었습니다. 그리고 Priviledge Level 에는 Privileged, Unprivileged 두가지의 경우가 있습니다. Cortex-M 이전의 ARM에서는 USER Mode를 제외한 나머지 6개의 동작 모드가 Privilegdge Mode입니다.
(1) Thread Mode
Exception이 발생하지 않은 보통의 상태가 Thread Mode입니다.
Reset Exception 발생시(CPU에 전원이 인가되어 초기 부팅시)에 Thread Mode + Privileged + Main_stack(Stack Pointer)로 시작하게 됩니다. 당연히 초기 부팅시에는 권한이 있는 모드로 실행이 되어야 하겠지요? 권한이 있는 모드로 부팅을 시작해야 H/W, S/W 설정을 끝내고 Unprivileged 모드로 전환을 해서 User Application을 실생 시킬수 있을테니까요. 그리고 한번 권한이 없는 모드로 전환이 되고 나면 일반적인 방법으로는 Privilege 모드로 전환할 수 없습니다. Exception이 발생하여 Handler Mode(Handler Mode에서는 항상 Privilege 모드)로 전환이 되거나 혹은 S/W적으로 “SVC(Super Visor Call)” 명령어를 써서 SVC_Handler Exception 을 발생시켜서 Handler Mode의 서비스 루틴에서 MSR 명령어를 사용하여 Priviledge Mode로 변경시켜야 합니다. Privilege 모드에서 Unprivileged로 전환하는 방법은 MSR 명령어를 사용하는 것입니다. 당연히 MSR 명령어는 Privilege 모드에서만 사용이 가능합니다. Privilege level 변경 방법은 CONTROL Register 설명 시에 예를 들어 보도록 하겠습니다.
(2) Handler Mode
Thread Mode에서 IRQ, Fault 등의 Exception이 발생했을 경우 문맥 보존을 위해서 Stack에 {R0~R4, R12, LR, PC, xPSR} 레지스터가 Stack에 H/W적으로 PUSH(저장)가 되고 이와 병렬적으로 Thread Mode에서 자동으로 Handler Mode로 전환이 되면서 Cortex-M3 Architecture적으로 미리 정의 되어 있는 Vector Table에 있는 Exception Handler 주소가 Fetch 되어 PC의 주소가 바뀌게 됩니다. 이렇게 Stack 메모리와 Vector Table Fetch 작업이 동시에 이루어질 수 있는 이유는 Cortex-M3가 Havard Architecture 구조이기 때문에 가능합니다. Harvard Architecure는 구조적으로 Code, Data Bus가 별도로 존재하는 구조입니다. 반대로 Code와 Data Bus가 하나만 존재하는 구조를 Von-Neumann Bus 구조라고 합니다.
당연히 Harvard Architecure 구조가 Von-Neumann Bus 구조보다 효율적이겠지요. Handler Mode에서는 권한 상태가 항상 Privilegde 모드입니다. Handler 서비스 루틴이 끝나면 Exception 이 발생한 명령어의 다음 명령어로 PC가 복귀가 되고 그와 동시에 Stack에서 POP이 발생하여 이전에 Stack에 잠시 보관되었던 {R0~R4, R12, LR, PC, xPSR} 레지스터들이 복구가 됩니다. {R0~R4, R12} 레지스터를 Cortex-M3에서 자동으로 Stack에 저장하는 이유는 {R0~R4, R12} 레지스터들이 Scratch 레지스터들 이기 때문입니다. 자세한 사항은 아래 AAPCS Register 부분을 참조하세요.
3.3 Stack
Cortex-M3에서 Stack은 Main_stack, Process_stack 2개가 Banked 되어 존재하고 항상 4Byte Aligned 되어있어야 합니다. 4Byte로 Aligned 되어야 한다는 것은 Stack Memory에 8Bit, 16Bit 데이터를 저장(PUSH) 하더라도 항상 32Bit 공간을 차지한다는 것입니다. 전통적인 ARM에서는 7가지 동작 모드별로 SP가 별도로 존재 하였습니다. Reset Exception 발생시(CPU 에 전원이 인가되어 초기 부팅시)에 Thread Mode + Privileged + Main_stack로 시작한다고 하였습니다. Reset Exception 발생 시에 위의 사항 말고도 하는 일이 한가지 더 있습니다. 그것은 0×00 번지에 있는 주소를 Hardware 적으로 읽어와서 Main_stack Pointer를 Setup하고 나서 0×4 번지 주소에 있는 Reset Handler Address( Progragm 시작 주소)를 읽어와서 PC에 저장하는 것입니다. 이러한 이유 때문에 Cortex-M3에서는 실제로 프로그램의 시작 주소가 0×4 번지가 됩니다. 전통적인 ARM에서는 0×0 주소에 프로그램의 시작 포인터인 Reset Handler가 존재하였고 0×0 주소의 내용은 명령어(Branch)가 존재해야만 했으며 7가지 동작 모드별로 Boot 코드에서 S/W 적으로 MSR 명령어를 사용하여 SP를 Setup해야만 했습니다. 이에 비하면 Cortex-M3 의 Stack Pointer Setup은 H/W 적으로 이루어 지고 있어 개발자 입장에서는 많은 수고를 덜 수가 있습니다. 참고로 STM32F 시리즈에서는 0×0 번지와 0×8000000 번지가 Alias 되어 있어 0×0 주소의 내용과 0×8000000 주소의 내용이 동일합니다.
STM32F 에서의 0×8000000 주소의 Memory 내용 | STM32F 에서의 0×0000000 주소의 Memory 내용 |
Main_stack pointer가 H/W적으로 Setup이 된다는 것은 0×04 번지에서 Segment 초기화를 끝내고 바로 main 함수로 분기하여 “C” Program 루틴에서부터 부팅을 시작할 수 있다는 이야기입니다. 여기서 Segment 초기화라는 용어가 나오는데 잠시 짚고 넘어 가도록 하겠습니다. 이전에 ARM Architecture 강좌에서도 한번 언급을 했었습니다.
main.c 파일을 컴파일하고 어셈블하여 main.o 파일이 생성이 된다면 .o 파일의 구조는 위와 같이 ZI(Zero Initialized), RW(Read Write), RO(Read Only) 영역 등으로 나누어져 생성이 됩니다. 결국 hex(bin) 파일의 구조는 main.o 파일과 같은 여러 개의 *.o + *.a(라이브러리 파일) + Link Script(Scattor Loading) 파일이 조합이 되어 Linker에 의해 생성이 되는 것입니다.
ZI, RO, RW 영역들을 Segment라고 하며 이 Segment들이 타겟 시스템의 RAM, ROM에 적당히 자리를 잡고 있도록 하는 작업을 Segment 초기화 작업이라고 합니다. Segment 초기화 작업은 H/W적으로 이루어지지는 않습니다. Hex 파일을 JTAG 등의 장비를 이용하여 ROM 영역에 퓨징(Write)을 한 상태에서 부팅을 하면 Startup(Bootloader) 코드에서 ROM 메모리에서 RW 영역을 읽어와서 RAM에 복사해주고 ZI 영역은 모두 0×0 값으로 초기화 해주는 작업을 해주어야 합니다. 이러한 Segment 초기화 작업을 해주지 않으면 C언어에서 사용하는 전역변수 등에 올바른 초기값이 들어가 있지 않습니다. hex(bin) 파일과 시스템의 RAM, ROM과의 관계를 그림으로 표현해 보았습니다.
참고로 Cortex-M3 IAR 컴파일러는 부트코드 라이브러리에서 이 작업을 개발자 대신 해주고 있습니다. IAR Startup 코드중에서 __iar_program_start 라는 서브루틴 코드에서 아래 그림과 같은 라이브러리 루틴들이 순차적으로 호출됩니다. __iar_data_init3가 Segment 초기화 작업 서브 루틴입니다.
(1) Main Stack setup
Main Stack 포인터는 부팅 시에 H/W적으로 0×0 번지에서 32Bit(4Byte) 값(Main Stack 포인터의 주소)을 읽어와서 자동으로 Setup이 된다고 하였습니다. Cortex-M3에서 0×0 번지에는 항상 Main Stack의 주소가 있어야 합니다. 자동으로 Setup이 되지만 부팅 이후에 Main Stack 포인터를 변경하는 경우에는 MSR 명령어를 사용하면 됩니다.
MOV R0 , #0×20002000
MSR MSP , R0
(2) Processor Stack setup
보통의 경우에는 Main Stack만 사용해서 프로그램을 개발하면 됩니다. 하지만 RTOS등에서 커널은 Privilegde + Main Stack을 사용하게 하고 User Application에서는 Unpriviledge + Processor Stack을 사용하여 운영체제를 보호하고 싶다면 Processor_stack 포인터를 Setup해야 합니다.
MOV R0 , #0×20001500
MSR PSP , R0
레지스터 설명에서 CONTROL 레지스터에 대한 설명을 하지 않았었는데, 여기서 살펴보도록 하겠습니다.
CONTROL 레지스터는 Cortex-M3 Core의 현재 권한 레벨(Privilege, Unprivileged)과 사용하고 있는 Stack을 저장하고 있는 Special Register입니다.
- Bit[0] : 0 : privileged, 1 : Unprivileged
- Bit[1] : 0 : Use SP_main, 1 : Use SP_process
- Bit[2] : 0 : FP extension not active, 1 : Active
- Bit[31:3] : Reserved
(3) Stack Setup 예제
ARM에서 Stack은 Full-Descending 방식으로 운영되기 때문에 보통 RAM의 가장 높은 주소 영역에 Stack Pointer를 설정하게 됩니다.
위의 예제에 있는 어셈블리어는 Processor Stack 포인터를 0×20001800으로 설정(MSR PSP, R0)하고 MSR 명령어를 사용해서 CONTROL 레지스터의 하위 [1:0] 비트를 “2b11″ 로 설정하여 Cortex-M3의 권한 레벨을 Unprivileged로 Stack 포인터는 Processor Stack을 사용하도록 하는 코드입니다.
■ Full-Descending 방식이란?
Stack 메모리에 데이터가 저장이 될 때 Full-Descending에서 Full 이라는 것은 Stack 동작이 끝나고 났을 때 항상 SP는 유효한 데이터를 가르키고 있다는 것이고 Descending 이라는 것은 높은 주소에서 낮은 주소로 주소가 감소하면서 데이터가 저장이 되는 방식입니다. Stack PUSH 동작이 끝났을 때 SP가 유효한 데이터를 가르키고 있으려면 데이터를 PUSH하기 전에 먼저 SP의 주소를 4Byte 감소시키고 나서 데이터를 저장해야 합니다. 반대로 POP 동작에서는 데이터를 먼저 꺼내고 나서 SP의 주소를 4Byte 증가시켜야 합니다. PUSH, POP 동작을 그림으로 예를 들어 보겠습니다.
■ PUSH Operation
■ POP Operation
위의 그림에서 POP 동작이 끝난 이후에도 RAM STACK 영역에는 데이터가 그대로 남아 있습니다. POP을 했다고 해서 메모리에서 데이터가 사라지는 것은 아니고 단지 SP만이 변경된다는 사실을 알수 있습니다.
3.4 Cortex-M3 Memory Map
Cortex-M3에서는 CODE 영역(ROM)과 RAM(SRAM)의 주소가 Architecture 차원에서 정의가 되어있기 때문에 Cortex-M3 Core를 기반으로한 CPU(ST, Luminsary, TI, Samsung사의 Cortex-M3 CPU)들 사이에는 포팅 작업이 많이 쉬워 졌습니다. 이전의 전통적인 ARM에서는 CPU의 메모리 뱅크에 따라서 RAM의 시작 주소가 같지 않을 수 있고, Peripheral들의 시작 주소 또한 CPU마다 다를 수 있습니다. 위의 Memory Map을 보면 32bit Core이기 때문에 4GB 메모리까지 접근이 가능하고 SRAM, Peripheral 영역의 주소에 특이하게도 Bit band alias 영역이라는 것이 존재합니다. 이 부분은 Bit Banding 장에서 자세히 설명하도록 하겠습니다.
(1) STM32F103VC(High desnsity)의 Memory Model
0×0800.0000 ~ 0×0801.FFFF (FLASH)
0×2000.0000 ~ 0×2000.BFFF (SRAM)
0×4000.0000 ~ 0×4002.3FFF (Peripheral Memory Map)
0xE000.0000 ~ 0xE00F.FFFF (Cortex-M3 Internal Peri.)
3.4 Thumb-2 Instruction Set
Cortex-M3는 Thumb-2 명령어만 지원합니다. 전통적인 ARM에서는 16Bit Thumb 명령어와 32Bit ARM 명령어를 사용할 수가 있었는데, Thumb 모드에서는 16Bit 명령어만 사용할 수 있고 32Bit 명령어를 사용하기 위해서는 Mode Change(Thumb Mode –> ARM Mode)를 해야만 했습니다. Mode Change를 위해서는 BX라는 분기 명령어를 사용해야 했습니다. Thumb-2 명령어의 가장 큰 특징은 Thumb모드와 ARM모드 사이에 모드 전환 없이 16bit Thumb 명령어와 32Bit 명령어를 섞어서(Blend) 사용할 수 있는 것입니다. 이러한 특징으로 Thumb 명령어만 사용했을 때 보다 성능은 좋아지고 코드의 집적는 ARM 32Bit 명령어에 비해서 좋아졌습니다. Thumb-2 명령어는 기존의 Thumb 명령어와 하위 호환성을 유지합니다.
위의 그림은 명령어들 사이의 Performance와 Code size를 표로 나타낸 것입니다.
ARM 명령어를 사용했을 때와 Thumb-2 명령어를 사용했을 때의 코드 사이즈를 비교해 보았습니다. 위의 어셈블리 명령어는 두수(R0, R1) 사이의 절대 값을 구하는 명령어 입니다. “C” 언어를 참조하세요. 16Bit Thumb 명령어에서는 Conditional Execution을 사용할 수 없습니다. 단, Thumb 명령어에서도 BX 명령어의 경우에는 Conditional Execution을 사용할 수 있습니다.
3.5 Bit Banding
Cortex-M3 Memory Map을 설명할 때 Bit band alias 영역이 있다고 하였습니다.
SRAM, Peripheral 영역에 존재하며 SRAM영역의 경우 0×22000000 주소에 ’0′ or ’1′을 Write하면 0×20000000 주소의 실제 SRAM의 [0]번 비트에 ’0′ or ’1′이 Write 되어지는 것입니다. Write 뿐만이 아니라 0×22000000 주소의 Data을 읽으면 0×20000000 주소의 SRM의 [0] 번 비트가 읽어집니다. 즉 SRAM 1MB bit-band region 1비트는 32MB의 alias region 영역의 32bit(1 WORD) 와 Alias(동일하게 Mapping) 되어있는 것입니다. 이러한 특징은 Peripheral 영역도 마찬가지로 적용이 됩니다. 그림으로 다시 보면 아래와 같습니다.
그러면 Cortex-M3 의 이런 특징이 있는 이유는 무엇일까요? 예를 들면 0x4001180C 주소에 32bit GPIOE 그룹의 데이터 레지스터가 존재하고 있고 GPIOE2, 3, 4 포트에 각각 LED 가 연결되어 있다고 가정해 봅시다.
회로상으로는 위의 그림과 같습니다. 여기서 PE2, PE4의 상태는 건드리지 않고 PE3에 연결되어 있는 LED3만 ON(PE3 포트를 High)시키려고 한다면 Bit Banding이 지원되지 않는 시스템에서는 다음과 같이 코드를 작성해야 합니다.
(*(volatile unsigned *)0x4001180C) |= (0×1 << 2);
ARM의 Load, Store 구조에서 이것을 어셈블리어로 다시 작성해 보도록 하겠습니다.
LDR R0, = 0x4001180C ; R0 = 0x4001180C
MOV R2, #0×4 ; R2 = 0×4
LDR R1, [R0] ; R0가 가르키는 주소에서 32Bit 데이터를 읽어와서 R1 레지스터에 저장
ORR R1, R2 ; R1 | R2
STR R1, [R0] ; R1레지스터의 값을 R0가 가르키는 주소에 저장
이와 같이 Bit Banding을 지원하지 않는 시스템에서는 Register에 메모리의 내용을 Load 해놓고 Bit wise 연산을 한 이후에 다시 STR 명령어를 사용해서 Register의 내용의 메모리에 저장하는 방식으로 해야만 합니다. ARM에서 연산 명령어들은 레지스터와 메모리의 내용으로 직접 연산을 할 수가 없고 항상 메모리의 내용을 레지스터에 읽어와서 연산을 마친 이 후에 메모리에 다시 저장하는 방식으로 사용해야 합니다. ARM은 Load/Store 방식이기 때문입니다.
이번에는 Bit Banding을 이용해서 같은 작업을 수행해보도록 하겠습니다. 우선 C 코드로 작성해보도록 하겠습니다.
(*(volatile unsigned *)(0×42000000 + (0x4001180C-0×40000000)*32 + 3*4)) = 0×0;
어셈블리어로 바꾸어 보면
; 상수들의 연산은 이해를 돕기 위한 의사 코드임
; 실제로는 덧셈과 곱셈이 완료된 최종 상수값이 와야 합니다.
LDR R0, = (0×42000000 + (0x4001180C-0×40000000)*32 + 3*4))
MOV R2, #1
STR R2, [R0]
Bit Banding이 지원되지 않는 시스템에서의 “LDR, ORR, STR” 3개의 명령어를 사용해서 구현되었던 내용이 “STR” 명령어 1개를 이용해서 같은 작업을 하는 코드로 바꿀 수가 있습니다. 이러한 특징은 수행 속도와 코드 집적도에서도 유리하며 이러한 기능이 주는 가장 중요한 특징은 바로 Atomic Operation이 가능하다는 것입니다. Atomic Operation이라는 것은 더 이상 쪼개지지 않는 즉, 1개의 명령으로 기능이 수행되어 명령어 수행 도중에 인터럽트가 발생하지 않는 것을 이야기합니다. 여러가지 복잡한 인터럽트가 많은 시스템에서 Atomic Operation이 된다는 것은 전역 데이터 혹은 Peripheral의 SFR(Special Function Register)등에 접근할 때 인터럽트에 의해서 데이터 처리 명령이 침해 당하지 않는 것을 보장합니다.
Atomic Operation이 아닌 명령어로 2개 이상의 인터럽트 서비스 루틴에서 공유로 사용하는 전역 데이터등을 처리하기 위해서는 일반적으로 데이터 처리 전에 인터럽트를 Disable시키고 데이터 처리가 끝나면 다시 인터럽트를 Enable 시키는 방식으로 처리합니다. 참고로 ARM에서 인터럽트는 명령어 바운더리(Boundary)에서 발생합니다. 명령어 Boundary라는 것은 명령어와 명령어 사이를 이야기합니다. Cortex-M3에서 LDM, STM(PUSH, POP)등 Multiple 데이터처리 명령을 제외하면 단일 명령어 수행 중에 인터럽트가 발생을 하더라도 명령어 수행이 끝나면 인터럽트가 시작됩니다.
Bit band region과 Bit band alias 영역과의 Alias 관계를 정규화된 수식으로 표현하면 아래와 같습니다.
bit_word_offset = (byte_offset x 32) + (bit_number × 4)
bit_word_addr = bit_band_base + bit_word_offset
0×40000000의 1번 비트에 해당하는 Bit Banding 주소는 0×42000000 + 32*0 + 4*1의 수식이 적용됩니다.
약간 복잡한 듯 보이지만 잘 생각해보면 계산식을 이해할 수 있습니다.
3.6 System Timer(SysTick)
Cortex-M3 Core 내부에 위치한 시스템 타이머입니다. 24bit self-reloading down counter이며 count가 0이 되면 SysTick Interrupt를 발생시킬 수 있습니다. 모든 CPU에는 보통 타이머를 1개 이상은 가지고 있습니다. 하지만 SysTick이 일반 타이머와 다른점이라면 Core(정확히는 NVIC에 존재함)에 내장된 타이머라는 것입니다. 이러한 특징은 Cortex-M3 Core를 사용한 모든 CPU는 모두 동일한 System Timer를 가지고 있다는 것입니다. 예를 들어 RTOS를 포팅한다고 했을 때 필수적으로 주기적인 타이머 인터럽트가 필요한데 Cortex-M3 이전의 프로세서에서 RTOS를 포팅을 하는 경우에는 Core자체에 Timer가 없기 때문에 Vendor Specific한 Timer를 사용하게 됩니다. 어떤 개발자는 Timer0를 사용할 수도 있고 다른 개발자는 Timer1을 사용해서 포팅을 할 수 있습니다. 이렇게 되면 같은 ARM Core를 사용했다고 하더라도 Processor들 간의 SW 포팅이 달라져야 합니다. 하지만 Cortex-M3 Core를 사용해서 RTOS 포팅을 한다면 당연히 Core에 내장된 공통적인 Timer인 System Timer를 사용해야 겠지요. System Timer에 대한 좀 더 상세한 내용과 사용법은 Cortex-M3 Applicaiton 강좌에서 하도록 하겠습니다.
4. Nested Vectored Interrupt Controller
4.1 NVIC
Cortex-M3의 가장 중요한 특징 중의 하나입니다. Cortex-M3 이전의 전통적인 ARM에서는 인터럽트 컨트롤러가 ARM Core의 외부에 위치해 있었습니다. 우측 상단 그림은 ARM9 S3C2440 CPU의 블럭도입니다. 자세히 보면 Interrupt Controller가 CPU의 Peripheral로 구현이 되어 있습니다.
하지만 Cortex-M3에서는 Interrupt Controller가 ARM Core의 내부 자원으로 들어와 있습니다. 그것도 Nested Vectored Interrupt Controller라는 새로운 이름을 달고 말입니다. 우측 하단 그림에 있는 점선 박스 부분이 CM3Core(Cortex-M3 Core)에 내장된 NVIC(Nested Vectored Interrupt Controller)입니다. 참고로 아래 블럭도는 STM32F103x 시리즈 CPU의 블럭도입니다.
CM3Core 부분은 Vendor Defined Specific 부분이 아니라 Cortex-M3 CPU들의 공통 사양입니다. NVIC는 CM3Core 내부에 위치해있습니다. 위에서 설명했던 Register(R0 ~ R15), Special Register(xPSR, CONTROL)등은 모두 CM3Core 내부에 위치해 있는 것입니다. Nested Vectored Interrupt Controller라고 하였는데 우선 Vectored라는 용어부터 설명을 하도록 하겠습니다. 전통적인 ARM의 인터럽트 블럭도를 간략하게 표현해 보았습니다.
전통적인 ARM에서는 인터럽트 컨트롤러가 ARM Core의 외부에 위치해 있으며 CPU의 여러 개의 Peripheral들이 1개의 IRQ, FIQ에 연결이 되어 있습니다. 이러한 이유로 만약에 EINT0 인터럽트가 발생을 하였다면 CPU 외부에 있는 인터럽트 컨트롤러를 통해서 ARM Core에 Interrupt Request가 전달되게 되는데 ARM Core 입장에서 보면 EINT0이 발생하였는지 EINT1이 발생한 것인지 알 수 있는 방법이 없습니다.
ARM Core에서 알 수 있는 방법은 S/W적으로 ARM Core 외부에 있는 Interrupt Controller의 SFR 레지스터에 접근하여 INTOFFSET(현재 발생한 인터럽트 번호가 저장되어 있는 레지스터)을 읽어와서 인터럽트 번호를 확인하고 적당한 인터럽트 서비스 루틴으로 분기를 하는 것입니다. 하지만 Cortex-M3에서는 이것보다 훨씬 효율적인 방법으로 인터럽트 서비스 루틴으로 분기를 합니다.
Cortex-M3에서는 EINT0 인터럽트가 발생하면 이미 정해진 Interrupt Vector Table에 있는 주소로 바로 분기하며 이때 Special Register 중 IPSR에 발생한 인터럽트 번호가 저장이 됩니다. 물론 인터럽트 서비스 루틴으로 분기하기 전에 문맥 보존을 위해서 H/W적으로 {R0-R3,R12,LR,PC,xPSR} 레지스터들이 Stack에 저장이 되었다가 ISR 서비스 루틴이 끝나면 다시 H/W적으로 Stack에서 레지스터로 복원이 됩니다. 전통적인 ARM에서 문맥 보존은 S/W적으로 개발자의 몫이었습니다. R0-R3, R12를 Stack에 저장하는 이유는 앞에서 설명했듯이 Scratch Register들이기 때문입니다. Cortex-M3에서 Exception의 종류는 최대 256개까지 존재 할 수 있고 0 ~ 15까지는 Cortex-M3 Internal Exception이고 16번 부터 나머지 240개는 Core 외부 Exception 입니다.
Exception Vector 0 ~ 15번 까지는 Cortex-M3 Core를 사용하는 모든 CPU는 동일하며 16 ~ 254 까지는 Vendor Specific 사양 입니다. 즉 CPU 마다 다를 수 있다는 이야기입니다.
Vectored 라는 말은 0 ~ 255 까지의 Exception Vector의 주소가 이미 정해져 있다는 말입니다. 그러므로 Exception이 발생했을때 어떤 Exception이 발생했는지 여부에 상관없이 정해진 Vector Table의 주소에 있는 내용의 Address로 바로 Exception 분기를 할 수 있습니다.
위의 그림은 STM32F10x 시리즈의 Exception Vector Table입니다. Cortex-M3 외부 인터럽트 벡터의 주소가 0x0000_0040부터 시작하고 있네요. Reset시에 Vector Table의 Offset 주소는 0×0이지만 Exception Vector Table의 시작 주소는 Vector Table Offset Register를 수정하면 옮길 수 있습니다.
TBLBASE 의 값을 “1″ 로 수정을 하면 Vector Table을 RAM에 위치 시킬 수도 있습니다. 그리고 TBLOFF 에 따라서 Vector Table의 주소를 정해줄 수 있습니다.
다음으로 Nested 라는 용어를 살펴 보지요. Nested 라는 것은 사전적인 용어로는 “중첩되어 있는” 뭐 이러한 의미입니다. Cortex-M3에서는 인터럽트 수행 중에 우선 순위가 높은 인터럽트가 발생을 하면 현재 수행 중인 인터럽트를 잠시 중단하고 나중에 발생한 우선 순위가 높은 인터럽트를 먼저 수행한 이후에 잠시 중단 되었던 인터럽트 루틴으로 복귀하여 수행을 마치고 Normal 루틴으로 복귀를 합니다. 이러한 경우를 인터럽트가 중첩되었다고 합니다. 전통적인 ARM에서 IRQ끼리는 중첩되는 경우가 없습니다. 모든 IRQ는 우선순위가 같기 때문에 중첩이 되지 않고 IRQ 수행중에 다시 IRQ가 발생을 하면 나중에 발생한 인터럽트는 잠시 Pending상태에 들어 갔다가 현재 수행중인 인터럽트 서비스 루틴이 종료되면(엄밀히 말하면 인터럽트 서비스 루틴안에서 Pending Clear를 하는 시점에서) 잠시 Pending 되었던 인터럽트가 시작됩니다. 인터럽트가 중첩되는 경우는 IRQ 수행 도중에 FIQ(Fast IRQ)가 발생하는 경우에만 중첩이 됩니다. 인터럽트가 중첩이 되려면 인터럽트마다 우선 순위가 달라야 하는데 Cortex-M3에서는 최대 255 단계의 우선 순위 레벨을 정해줄 수가 있습니다. 인터럽트 우선순위 단계는 AIRCR(Application Interrupt and Reset Control Register)에 의해서 정해줄 수 있습니다.
PRIGROUP 부분에 0 ~ 7 사이의 값을 Write 할 수 있습니다. PRIGROUP에 의해서 Goup Priority(Pre-emption)과 Sub Priority의 경계를 나누어 줄 수 있습니다.
STM32F10x 시리즈에서는 0 ~ 7 비트중에서 4비트(상위 4 ~ 7 비트)만 사용하여 Priority가 구현되어 있습니다. 하위 4비트는 H/W적으로 “0″ 으로 Mapping 되어있습니다. STM32F10x 시리즈에서 PRIGROUP의 값에 의해서 정해지는 Priority 단계를 표로 나타내 보았습니다.
위의 표에서 PRIGROUP의 값이 0b100(4)가 되면 Group Priorities로 8단계, Sub Priorities로 2단계까지 레벨을 정할 수 있습니다. PRIGROUP값에 의해서 Group Priorities와 Sub Priorities의 레벨을 동적으로 조정할 수 있는 것입니다. Group Priorities와 Sub Priorities에는 차이점이 있습니다. 예를 들어 PRIGROUP의 값이 0b100(4)로 하고 EINT0, EINT1에 대해서 아래 그림과 같이 우선순위 레벨을 정의해주었다고 가정해봅시다.
STM32F10x 시리즈에서는 상위 4bit만 이용하여 인터럽트 Priority를 구현하였다고 하였습니다. PRIGROUP이 0b100(0×4)일 경우에 아래 그림과 같이 Group Priority로 3bit, Sub Priority로 1bit를 이용할 수 있습니다. 그러므로 Group Priority 값에는 0×0 ~ 0×7 사이의 값, Sub Priority에서는 0×0 ~ 0×1의 값이 올 수 있습니다.
EINT0의 인터럽트가 먼저 발생하여 인터럽트 서비스 루틴이 실행중에 있을때 EINT1이 발생하면 EINT0의 Group Priority가 높기 때문에 EINT0가 끝날 때까지 EINT1은 Pending 상태에 있다가 EINT0이 끝나면 EINT1이 수행이 됩니다. 그러면 반대로 EINT1이 먼저 발생하여 인터럽트 서비스 루틴 실행 중에 EINT0가 나중에 발생하면 어떻게 될까요? 이 경우에는 EINT0의 Group Priority가 높기 때문에 EINT1 수행을 잠시 중단하고 EINT0를 수행한 이후에 중단되었던 EINT1이 수행이 됩니다. 여기서 알 수 있는 것은 Sub Priority와는 상관없이 Group Priority에 의해서 인터럽트의 선점 우선순위가 달라 집니다. 그렇다면 Sub Priority는 어떤 용도로 사용이 될까요? EINT0, EINT1의 Group Priority의 값이 같은 경우 동시에 인터럽트가 발생하면 Sub Priority가 높은 EINT1이 먼저 수행이 완료되고 나면 EINT0가 나중에 수행이 됩니다.
Exception이 발생하여 Exception Handler 루틴이 호출되고 다시 원래의 루틴으로 복귀할 때까지의 흐름을 그림으로 표현해 보았습니다.
다음 Chapter에서는 전통적인 ARM의 Interrupt Response에 비해서 Cortex-M3에서 얼마나 좋아졌는지 살펴 보겠습니다.
4.2 Interrupt Response
(1) Tail Chaining
위의 그림을 보면 전통적인 ARM에서는 IRQ1이 IRQ2보다 우선순위가 높고 동시에 발생했을 경우에 IRQ1이 먼저 수행이 끝나고 IRQ2가 수행이 됩니다. 이 과정에서 당연히 문맥 보존을 위해서 ISR1루틴으로 분기하기 전에 {R0-R3, R12, LR, PC} 레지스터 등을 S/W적으로 PUSH하고 ISR1 서비스 루틴이 끝날 때 POP을 합니다. 그리고 다시 ISR2 루틴으로 분기하기 전에 PUSH를 하고 ISR2 루틴이 끝날 때 POP을 하게 됩니다. 여기서 사실 ISR1에서 ISR2 루틴으로 연결될 때 중간에 있는 POP, PUSH는 하지 않아도 되는 불필요한 동작입니다. 왜냐하면 이 과정에서 ISR2 루틴이 끝나고 수행되는 POP과, PUSH 하는 레지스터들은 ISR1 시작 전에 PUSH 되는 레지스터들과 동일하기 때문입니다. Cortex-M3 에서는 중복되는 중간 부분의 POP, PUSH를 생략하고 ISR1 루틴이 끝나면 6Cycle에 해당하는 Tail-Chaining이 수행된 이후에 바로 ISR2 루틴으로 분기를 합니다. 참 효율적이죠. Tail-Chaining부분에서 6Cycle 동안에 실제적으로는 하는 일은 Vector Table에서 ISR2의 시작 주소를 Fetech해오는 작업을 합니다.
(2) Preemption
위의 경우는 ISR1 서비스 루틴 수행을 끝내고 문맥 보존을 위해서 POP을 수행하고 있는 도중에 IRQ2가 발생하는 경우입니다. 전통적인 ARM에서는 Multiple Load 명령어 수행 중에 인터럽트가 발생할수 없으므로 POP을 모두 수행하고 다시 ISR2를 위한 PUSH루틴으로 진입합니다. 하지만 Cortex-M3에서는 POP(LDMFD) 수행 중에도 인터럽트가 가능하기 때문에 ISR1과 ISR2 사이에 Tail-Chaining 이 발생하면 6Cycle 이후에 ISR2 루틴이 수행될 수 있습니다.
(3) Late Arriving
전통적인 ARM에서 IRQ2(IRQ) 수행을 위해서 PUSH 도중에 IRQ1 보다 우선순위가 높은 인터럽트(FIQ)가 발생하면 ISR2을 위한 PUSH작업이 끝나자 마자 ISR1을 위한 PUSH 작업이 진행됩니다. Cortex-M3에서는 ISR2를 위한 PUSH 작업중에 IRQ2보다 우선순위가 높은 IRQ1이 발생하게 되면 IRQ1를 위한 PUSH 작업은 진행되지 않고 바로 우선 순위가 높은 ISR1이 수행되고 Tail-Chaining이후에 ISR2를 수행하게 됩니다.
여기까지 Cortex-M3에 대한 구조는 어느 정도 정리가 된 것 같습니다. 놓친 부분이 있다면 이후에 진행된 Cortex-M3 Application에서 예제를 통해서 다시 언급하도록 하겠습니다. Cortex-M3 Application에서는 STM32F103VC Dragon 개발보드를 가지고 Cortex-M3 Architecture에서 이론으로 다루었던 내용들을 실습을 통해서 하나씩 공부해보도록 하겠습니다. 실제 예제에서 컴파일러는 IAR 5.4 Evaluation 버전을 사용할 것이고 Emulator로는 ARM-JTAG Light Edition을 이용할 것입니다. 참고로 아래 그림은 우리가 Cortex-M3 Application에서 사용하게 될 개발보드의 사양입니다.