Embedded Recipes 4. ARM 제어 구현
ARM 어셈관련 내용이 주를 이룸
ARM Assembly
ARM 코드는 이런식으로 쓰임
Opcode Rd Rn Rm
- Opcode: 명령어
- Rd: Destination (R0~R15)
- Rn: R0~R15
- Rm: 아무거나
조금더 자세한건 [임베디드 OS 개발 프로젝트] Appendix (velog.io) 참고하시길
코드 Branch를 할때 Mode 확인차원에서 주소를 다르게 넘김
- ARM mode인 경우 짝수의 주소를 넘김
- Thumb mode인 경우 홀수의 주소를 넘김
- 그리고 다시 사용할때 -1해서 씀
- 실제 동작에선 둘다 무조건 짝수로 동작함
조건부 코드실행 관련 (if, switch 같은)
- FETCH하자마자 PSR에 NZCV을 보고 실행가능한지 어떤지 보는거
- ARM은 한라인에서 조건부실행 가능
- NZCV를 보고 판단 가능
- Thumb는 한번에 조건부 실행이 불가능
- NZCV를 보지못해서 전부 AL(Always)처리
- 할거면 코드를 더 써야함
Vaneer
ARM 모드와 Thumb 모드간 모드변경할때,
해당 코드가 상호작용 가능하게 동작하는 임시코드.
Vanner는 화물사이에 끼는 합판이라는 뜻을 가짐.
Inline Assembly와 INTLOCK
C언어에서 간단하게 어셈블리를 사용할 수 있음
__asm 이라는 걸 함수에 치면 사용됨
- 주의점
- 의사명령어 (LSR 주소, ADR 등) 사용금지
- Branch 명령어 사용금지
- 상태 변경시 주의. (우리는 아는데, 컴파일러는 모름)
- 컴파일러가 인라인코드를 보고 Stack사용하는거 정도는 자동으로 해줌
- Thumb사용할땐 r0~r7까지밖에 못씀
INTLOCK
- Interrupt Lock
- 인터럽트 동작이 안되게막는것
- 만약에 구현하게 된다면, 유저모드에서 특권모드로 변경해서 사용해야함.
- ARM Mode에서 만 사용가능
Pipeline 과 Exception.
파이프라인
-
프로그램 실행동작 3가지(FETCH<, DECODE, EXECUTE)를 병렬로 동작하는 것

-
instruction에서 PC는 Fetch쪽에 가있다.
- EXECUTE를 할때면 이미 PC가 +2 앞서있는 것을 확인할 수있다.
-
모드 별 1Cycle instruction 처리크기가 다르다
- ARM: 4Byte
- Thumb: 2Byte
Exception 별 LR이 다르니 Debugging시 참고 (Byte 단위)
SWI
SWI가 사용되는 예
- System Call: 커널을 건드리는 것
- Semi Hosting: Host(개발자)가 Target을 건드리는 것
- 코드 실행 중 host가 target을 건드려 I/O, 디버깅, 테스트 하는 것
Coproccessor Assembly
CPU 하나로 전부 처리할 수 없어을 때 Co-processor를 통해 처리하곤 함
Co-processor의 레지스터를 통해 전용 어셈블리 명령을해서 사용하면 됨
- Coprocssor Data 명령어
- CDP Coprocsessor번호, Coproessor명령어, CRd, CRn
- Coprocessor 내부 Register를 읽거나 쓸경우 (R: Register, C: Co-processor)
- MRC: R <- C
- MCR: C <- R
- Coprocessor 내부 Register를 직접 읽거나 쓰는경우
- LDC, STC
Bootloader와 memory Budget(mapfile)
부트로더라는 프로그램을 통해 main문 직전까지 부팅을 진행한다.
XIP와 메모리
- ROM
- NOR flash: XIP 가능
- NAND flash: XIP 불가능
- RAM: 전부(PSRAM, SDRAM, SRAM) XIP 가능
최근 메모리 조합은
각 메모리당 들어가야하는 데이터
- ROM: RO(코드,const), RW(전역변수, static)
- RAM: RW(변수 및 데이터), ZI(.bss)
실제 동작에서는 ZI를 Ram에 선언해주게 됨
- 실제 코드 메모리크기와 동작시 메모리 크기가 다름
부트로더 실행 시 메모리에 데이터가 링커로 Mapping한대로 올라가진다.
실행가능한 파일을 만들때 링커를 사용했었는데
- Linker: RO, RW, ZI 를 단순히 차례로 RAM에 Mapping
- NOR은 얘로도 충분
- Scatter loader: 필요에 의해 조금 다양하게 Mapping
- NAND는 얘를 꼭 써야함
부트로딩은 이렇게 동작한다.
1. PoR
2. Reset Handler로 이동
3. INTLOCK
4. Watchdog Timer Init
5. System clock Init
6. Memory controller Init
7. (NAND 메모리일 경우) Exception Vector table Init: High Vector
8. ZI Init (0으로 초기화)
9. main() 으로 브랜치
각 메모리당 필요한 용량
- NOR + PSRAM
- NOR(RO + RW), PSRAM(RW+ZI)
- NAND + SDRAM
- NAND(RO+RW), SDRAM(RO+RW+ZI)
Entry point
Reset Handler = Boot up code, Start up code
- Reset Handler내에서 부트작업이 끝난 바로 직후,
- 우리의 EntryPoint인
__rt_entry를 호출하여 우리가 아는 그 main()을 알아서 호출한다.
이런 Entry Point를 구현하지 않으면
- C라이브러리 에서 자체적으로 커널단에 Entry Point인
_main 이라는 것을 호출한다.
- RO, RW, ZI 순으로 Copy 및 Init을 해줌
- 그리고 __rt_entry를 호출함
__rt_entry 에서는 Stack, Heap, 라이브러리에 필요한 것들을 init을 해줌
- 그 후 main()으로 들어감
C에서 EntryPoint도 알아서 해주는구나