練習 ARM Cortex-M4 處理器所做的練習項目,專案可見 cm4bmp
1
: Load 2 values from memory, add them and store it to memory by using inline assembly statement地址0x20001000初始值: 6
地址0x20001004初始值: 4
將地址 0x20001000
及 0x20001004
分別放入 R1
及 R2
/* move 0x20001000 to R1 */
__asm volatile("LDR R1,=#0x20001000");
/* move 0x20001004 to R2 */
__asm volatile("LDR R2,=#0x20001004");
查看 memory view 結果如下
address 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
0x20001000 06 00 00 00 04 00 00 00 ff ff ff ff ff ff ff ff
將儲存在 R1
及 R2
地址的資料分別放進 R0
及 R1
/* load the data loactes at 0x20001000 to R0 */
__asm volatile("LDR R0,[R1]");
/* load the data loactes at 0x20001004 to R1 */
__asm volatile("LDR R1,[R2]");
預期結果
R0: 6
R1: 4
將 R0
及 R1
資料相加後放進 R0
/* add R0 and R1, then move to R0 */
__asm volatile("ADD R0,R0,R1");
將 R0
存進 R2
的地址裡
/* store the result into 0x20001004 */
__asm volatile("STR R0,[R2]");
將地址 0x20001004
讀出來並存到 R2
/* move the result into R2 */
__asm volatile("LDR R2,[R2]");
2
: 讀取 Control Register=
: 表示這個指令為寫入,以下範例表示寫入到變數 control_reg
/* read the CONTROL register */
uint32_t control_reg = 0xff;
__asm volatile("MRS %0, CONTROL" : "=r"(control_reg));
printf("control = %ld\n", control_reg);
預期結果
cnotrol_reg = 0
3
: 把一個變數的值複製到另一個變數的數值uint32_t var1 = 10, var2 = 0;
__asm volatile("MOV %0,%1" : "=r"(var2) : "r"(var1));
printf("var1 = %ld\tvar2 = %ld\n", var1, var2);
預期結果
var1 = 10 var2 = 10
4
: 把一個位址的值寫到另一個變數的數值uint32_t ptr1 = 0, *ptr2 = (uint32_t *) 0x20000008;
*ptr2 = 10;
/* p1 = *p2 */
__asm volatile("LDR %0,[%1]" : "=r"(ptr1) : "r"(ptr2));
printf("ptr1 = %ld\n", ptr1);
預期結果
ptr1 = 10
目的: 給定 bit band address 及 bit position ,計算其 bit band alias address ,再利用該地址修改目標地址的值
0x20000200
,初始值: 0xff
7
首先將目標地址的資料設定為 0xff
,並使用 bitwise operation 的方法修改其 bit[7] ,對應程式碼如下
uint8_t *ptr = (uint8_t *) TARGET_ADDR;
/* reset value */
*ptr = 0xff;
/* bitwise operation */
*ptr &= ~(1 << 7);
接著透過 memory view 可以看到目標地址的資料已經被儲存為 0x7f
(bit[7] 已經被清除)
address 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
0x20000200 7f 0d 00 00 08 00 00 20 3a 3d 0a 3d 0f 3d 24 3d
接著重新初始化目標地址的資料為 0xff
,再使用 bit band 修改,實際程式碼如下
/* reset value */
*ptr = 0xff;
/* bit band method */
uint8_t *alias_addr =
(uint8_t *) (ALIAS_BASE + (32 * (0x20000200 - BIT_BAND_BASE)) +
BIT_POSITION * 4);
*alias_addr = 0;
一樣透過 memory view 可以看到目標地址的資料已經被儲存為 0x7f
(bit[7] 已經被清除)
address 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
0x20000200 7f 0d 00 00 08 00 00 20 3a 3d 0a 3d 0f 3d 24 3d
1
: 觀察 SP
從 Linker script 的檔案可以算出一開始 stack 的起始位置 (_estack
)
_estack = 0x20000000 + 64 * 1024 = 0x20010000
/* Highest address of the user mode stack */
_estack = ORIGIN(RAM) + LENGTH(RAM); /* end of "RAM" Ram type memory */
_Min_Heap_Size = 0x200; /* required amount of heap */
_Min_Stack_Size = 0x400; /* required amount of stack */
/* Memories definition */
MEMORY
{
CCMRAM (xrw) : ORIGIN = 0x10000000, LENGTH = 16K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 512K
}
可以看到在 startup file , SP
的確為 0x20010000
(畫面左下角),而 MSP
和 SP
值相同,表示 ARM 的預設為 MSP
,以下為 MSP
的數值
msp
0x20010000
2
: SP
的切換練習PSP
MSP
1KB
MSP
和 PSP
各用一半, MSP
使用上半部, PSP
使用下半部以下根據 Cortex™-M4 Devices Generic User Guide 的說明,利用 Control Register 可以從預設 MSP
設定為 PSP
(利用位元 1
)
參考 MRS
and MSR
,可利用指令 MRS
和 MSR
分別讀取和寫入 PSP
接著可以開始進行實驗,首先利用暫存器 R0
將初始值 PSP_START
寫入 PSP
暫存器
/* move R0 register value to the PSP register */
__asm volatile("MSR PSP, R0");
此時的 PSP
期望為 0x2000fe00
設定 Control Register 設定使用 PSP
/* set bit[1] to switch to PSP */
__asm volatile("MSR CONTROL, R0");
接著藉由函式 add
觀察是否為 PSP
改變,由實驗得知當進入函式 add
後,PSP
的數值從 0x2000fe00
變成了 0x2000fde8
,意味著設定成功
接著透過指令 SVC
觀察進入 Handler mode 時是否使用 MSP
,可以發現 MSP
的值從 0x2000fff8
變成了 0x2000fff0
,意味著當處理器為 handler mode 時,使用 MSP
作為 stack pointer 的來源
上述的程式碼可見 stack.c
Interrupt 1: TIM2 global interrupt
Interrupt 2: I2C1 event interrupt
1
: 當兩個 interrupt 的優先度相同時假設 TIM2
和 I2C1
優先度都為 8
TIM2
interruptI2C
Priority Register enableI2C
優先度和 TIM2
相等,因此不進 I2C
中斷函式,停在 TIM2
函式裡TIM2 Interrupt Handler
2
: 當兩個 interrupt 的優先度不同時假設 TIM2
優先度為 8
, I2C1
優先度為 7
TIM2
interruptI2C
Priority Register enableI2C
優先度比 TIM2
高,因此跳進 I2C
中斷函式TIM2 Interrupt Handler
IC21 Interrupt Handler
目的: 使用 RTC_WKUP
interrupt 分析進入中斷後,觀察 SP
的變化及儲存的暫存器為何
ARM cortex-M4 exception stack frame
MSP
並觀察其結果進中斷前 SP
的位址: 0x2000fff8
, xPSR
數值: 0x1000000
進中斷後 SP
的位址: 0x2000ffd0
根據下圖,查看儲存資料,並可以得知 ARM 為 little-endian
Stack Pointer (MSP) | Saved Registers | Saved contents |
---|---|---|
0x2000fff4 | xPSR | 0x1000000 |
0x2000fff0 | PC | 0x8000480 |
0x2000ffeC | LR | 0x80005F7 |
0x2000ffe8 | R12 | 0x0 |
0x2000ffe4 | R3 | 0xE000EF00 |
0x2000ffe0 | R2 | 0x3 |
0x2000ffdc | R1 | 0x1 |
0x2000ffd8 | R0 | 0xA |
0x2000ffd4 | Exc Return | 0xFFFFFFF9 |
0x2000ffd0 | 上一個 sp 的值 | 0x2000FFF8 |
EXC
Return
從 assembly code 可以看到進中斷前會先執行 push {r7, lr}
,將 r7
和 lr
的值放進 MSP
的 Stack space
離開中斷則是會執行pop {r7, pc}
,從 stack 取兩個值並分別給 r7
和 pc
08000490 <RTC_WKUP_IRQHandler>:
8000490: b580 push {r7, lr}
8000492: af00 add r7, sp, #0
8000494: 4802 ldr r0, [pc, #8] ; (80004a0 <RTC_WKUP_IRQHandler+0x10>)
8000496: f000 f8cf bl 8000638 <puts>
800049a: bf00 nop
800049c: bd80 pop {r7, pc}
800049e: bf00 nop
80004a0: 08000f8c .word 0x08000f8c
PSP
並觀察其結果PSP
) r0 0xa
r1 0x1
r2 0x3
r3 0xe000ef00
r4 0x20000094
r5 0x0
r6 0x0
r7 0x2000fff0
r8 0x0
r9 0x0
r10 0x0
r11 0x0
r12 0x0
sp 0x20008000
lr 0x800060f
pc 0x8000494
+msp 0x2000fff0
+psp 0x20008000
r0 0xa
r1 0x1
+r2 0x3
+r3 0xe000ef00
r4 0x20000094
r5 0x0
r6 0x0
+r7 0x2000ffe8
r8 0x0
r9 0x0
r10 0x0
r11 0x0
r12 0x0
+sp 0x2000ffe8
+lr 0xfffffffd
+pc 0x80004ac
+msp 0x2000ffe8
+psp 0x20007fe0
+xPSR 0x1000013
觀察 PSP 管理的 stack 儲存資料
觀察 MSP 管理的 stack 儲存資料,有一些小發現
進中斷和離開中斷都和先前一樣,只差在因為中斷裡使用 MSP
, SP
和 EXEC
會存到 MSP
的空間裡,其他則儲存在 PSP
空間裡
因為 Thread Mode 是使用 PSP
, 因此 EXC Return
的數值也不同
080004a8 <RTC_WKUP_IRQHandler>:
80004a8: b580 push {r7, lr}
80004aa: af00 add r7, sp, #0
80004ac: 4802 ldr r0, [pc, #8] ; (80004b8 <RTC_WKUP_IRQHandler+0x10>)
80004ae: f000 f8cf bl 8000650 <puts>
80004b2: bf00 nop
80004b4: bd80 pop {r7, pc}
80004b6: bf00 nop
80004b8: 08000fa4 .word 0x08000fa4
這邊附上 MSP 實際的 memory view
#include <stdio.h>
#include "myusart.h"
void get_svc_number(uint32_t *msp)
{
uint8_t svc_number = ((uint8_t *)msp[6])[-2]; // get svc number
printf("svc_number = %d\n", svc_number);
// according to the AAPCS, the order of return register is r0, r1
msp[0] = svc_number + 4; // SVC number + 4 and stored into r0 (msp[0])
}
int main(void)
{
MYUSART_Init();
__asm volatile("SVC #0x05"); // trigger SVC exception
uint8_t data;
__asm volatile("MOV %0, r0": "=r"(data) ::);
printf("new SVC number = %d\n", data);
while(1);
return 0;
}
__attribute__ ((naked)) void SVC_Handler(void)
{
__asm volatile("MRS r0, MSP"); // Store MSP value into r0
// according to the AAPCS, the value of function parameter "msp" will be r0
__asm volatile("B get_svc_number"); //Branch to get_svc_number
}
0800050c <get_svc_number>:
800050c: b580 push {r7, lr}
800050e: b084 sub sp, #16
8000510: af00 add r7, sp, #0
8000512: 6078 str r0, [r7, #4]
8000514: 687b ldr r3, [r7, #4]
8000516: 3318 adds r3, #24
8000518: 681b ldr r3, [r3, #0]
800051a: 60fb str r3, [r7, #12]
800051c: 68fb ldr r3, [r7, #12]
800051e: f813 3c02 ldrb.w r3, [r3, #-2]
8000522: 72fb strb r3, [r7, #11]
8000524: 7afb ldrb r3, [r7, #11]
8000526: 4619 mov r1, r3
8000528: 4805 ldr r0, [pc, #20] ; (8000540 <get_svc_number+0x34>)
800052a: f000 f87d bl 8000628 <iprintf>
800052e: 7afb ldrb r3, [r7, #11]
8000530: 3304 adds r3, #4
8000532: 461a mov r2, r3
8000534: 687b ldr r3, [r7, #4]
8000536: 601a str r2, [r3, #0]
8000538: bf00 nop
800053a: 3710 adds r7, #16
800053c: 46bd mov sp, r7
800053e: bd80 pop {r7, pc}
8000540: 08001564 .word 0x08001564
08000544 <main>:
8000544: b580 push {r7, lr}
8000546: b082 sub sp, #8
8000548: af00 add r7, sp, #0
800054a: f7ff ff03 bl 8000354 <MYUSART_Init>
800054e: df05 svc 5
8000550: 4603 mov r3, r0
8000552: 71fb strb r3, [r7, #7]
8000554: 79fb ldrb r3, [r7, #7]
8000556: 4619 mov r1, r3
8000558: 4801 ldr r0, [pc, #4] ; (8000560 <main+0x1c>)
800055a: f000 f865 bl 8000628 <iprintf>
800055e: e7fe b.n 800055e <main+0x1a>
8000560: 08001578 .word 0x08001578
08000564 <SVC_Handler>:
8000564: f3ef 8008 mrs r0, MSP
8000568: f7ff bfd0 b.w 800050c <get_svc_number>
800056c: bf00 nop
...
08000570 <Reset_Handler>:
8000570: 480d ldr r0, [pc, #52] ; (80005a8 <LoopForever+0x2>)
8000572: 4685 mov sp, r0
8000574: f3af 8000 nop.w
8000578: 480c ldr r0, [pc, #48] ; (80005ac <LoopForever+0x6>)
800057a: 490d ldr r1, [pc, #52] ; (80005b0 <LoopForever+0xa>)
800057c: 4a0d ldr r2, [pc, #52] ; (80005b4 <LoopForever+0xe>)
800057e: 2300 movs r3, #0
8000580: e002 b.n 8000588 <LoopCopyDataInit>
Disassembly of section .text:
0c000100 <__gnu_cmse_nonsecure_call>:
// save R5-R11, LR on secure stack
c000100: e92d 4fe0 stmdb sp!, {r5, r6, r7, r8, r9, sl, fp, lr}
//clear registers
c000104: 4627 mov r7, r4
c000106: 46a0 mov r8, r4
c000108: 46a1 mov r9, r4
c00010a: 46a2 mov sl, r4
c00010c: 46a3 mov fp, r4
c00010e: 46a4 mov ip, r4
// reserve space on secure stack to store the secure floating point registers
c000110: b0a2 sub sp, #136
// actually store the registers
c000112: ec2d 0a00 vlstm sp
// clear APSR register
c000116: f384 8800 msr CPSR_f, r4
// clear more registes
c00011a: 4625 mov r5, r4
c00011c: 4626 mov r6, r4
// call non-secure world
c00011e: 47a4 blxns r4
// restore secure floating pointer registers
c000120: ec3d 0a00 vlldm sp
c000124: b022 add sp, #136
// restore core registers
c000126: e8bd 8fe0 ldmia.w sp!, {r5, r6, r7, r8, r9, sl, fp, pc}
TODO: 補充 warning
目標: Write a program to enable all configurable fault exceptions, implement the fault exception handlers and cause the fault by following method.
1. Execute an undefine instruction
2. Divide by zero
3. Try executing instruction from peripheral region
4. Executing SVC inside the SVC handler
5. Executing SVC instruction inside the interrupt handler whose priority whose priority is same or lesser than SVC handler
參考 Cortex-M4 Devices Generic User Guide Table 4-24 , System Handler Control and State Register (SHCSR
)
1
: 執行未定義指令首先在地址 0x20000501
放進未定義的指令 0xFFFFFF
利用函數指標指到 0x20000501
並且執行,發現會進 Usage Fault
接著會發現有趣的現象,直接進到 UsageFault_Handler
裡
接著開始分析 fault 產生的原因
可以從 Fault Status and Fault Address Register 得知產生原因
查看 UsageFault Status Register 的值可以知道發生什麼問題
最後印出 UFSR 的值可以知道產生原因為 Undefined Instruction ,對照表格後可以看出為 UNDEFINSTR
→ Undefined instruction UsageFault
In Usage fault
UFSR = 1
嘗試更改存放指令的地址為 0x20000500
(T
位元為 0
) ,對照表格後可以看出為 INVSTATE
→ Invalid state UsageFault
In Usage fault
UFSR = 2
開始分析 stack frame
以下為輸出結果
In Usage fault
UFSR = 0x2
R0 = 0x20000000
R1 = 0x20000064
R2 = 0xffffffff
R3 = 0x20000500
R12 = 0x0
LR = 0x8000547
PC = 0x20000500
xPSR = 0x20000000
查看 memory view
繼續分析 LR
及 PC
首先是 LR
的部分,從上述的結果可以看到 LR
為 0x8000547
,查看組合語言可以發現指令如下表示
8000540: 603b str r3, [r7, #0]
8000542: 683b ldr r3, [r7, #0]
8000544: 4798 blx r3
8000546: bf00 nop
8000548: 3708 adds r7, #8
可以得知 nop
為回傳之後要執行的指令
接著分析 PC
,從上述的結果可以看到 PC
為 0x2000501
,查看組合語言結果如下
→ 0x2000501
為 0xFFFFFFFF
,屬於未定義指令
2
: Divide by 0
首先啟用 divide by 0 trap
使用Configuration and Control Register (CCR
)
可利用 CCR
的 bit[4]
來trap divide by 0
以下為程式運行結束後,在終端機顯示的訊息
In Usage fault
UFSR = 0x200
R0 = 0x20000000
R1 = 0x20000064
R2 = 0x1
R3 = 0x0
R12 = 0x0
LR = 0x8000525
PC = 0x8000542
xPSR = 0x61000000
查看 UFSR
(UsageFault Status Register) ,可以看出產生 Exception 的原因為除以 0
繼續分析 LR
及 PC
首先是 LR
的部分,從上述的結果可以看到 LR
為 0x8000525
,查看組合語言可以發現指令如下所示
800051e: 6013 str r3, [r2, #0]
8000520: f000 f804 bl 800052c <Example2>
8000524: e7fe b.n 8000524 <main+0x18>
8000526: bf00 nop
8000528: e000ed24 and lr, r0, r4, lsr #26
執行結束後回傳的地址為 b.n 8000524 <main+0x18>
接著分析 PC
,從上面的結果可以看到 PC
為 0x8000542
,查看 assmembly 可以發現指令為以下表示:
800053e: 2201 movs r2, #1
8000540: 2300 movs r3, #0
8000542: fb92 f3f3 sdiv r3, r2, r3
8000546: 607b str r3, [r7, #4]
8000548: bf00 nop
sdiv r3, r2, r3
為進 Exception 前最後執行的指令,可以參考 sdiv instruction
最後嘗試把 trap 除以 0 的功能取消,分析會產生什麼變化,以下為程式執行的結果
In Hard fault
查看 HardFault Status Register (HFSR
)
可以看到為 bit[30]
為 1
3
: 從 peripheral region 執行指令Peripheral Region: 從下面的圖,可以看到外部設備的地址位於 0x40000000
~ 0x5FFFFFFF
以下為程式執行的結果
In Mem manage fault
MMSR = 0x1
R0 = 0x20000000
R1 = 0x20000064
R2 = 0xe000ed24
R3 = 0x40000000
R12 = 0x0
LR = 0x800053d
PC = 0x40000000
xPSR = 0x20000000
接著查看 MMSR
查看後可以發現原因為 bit[0]
→ 對 XN region 進行 instruction fetch
繼續分析 LR
及 PC
首先是 LR
的部分,從上述的結果可以看到 LR
為 0x800053d
,查看 assmembly 可以發現指令為以下表示:
8000536: 607b str r3, [r7, #4]
8000538: 687b ldr r3, [r7, #4]
800053a: 4798 blx r3
800053c: bf00 nop
800053e: 3708 adds r7, #8
adds r7, #8
為異常回傳之後第一個執行的指令
接著是 PC
的部分,從上述的結果可以看到 PC
為 0x40000000
,表示 PC
已經跳到 0x40000000
要執行指令
4
: Executing SVC
inside the SVC
handler以下為程式執行後,在終端機顯示的結果,很明顯得產生了 Hard Fault
In Hard fault
HFSR = 0x40000000
R0 = 0x20000000
R1 = 0x20000064
R2 = 0xe000ed24
R3 = 0x70000
R12 = 0x0
LR = 0xfffffff9
PC = 0x8000552
xPSR = 0x2100000b
繼續分析 LR
及 PC
首先是 LR
的部分,從上面的結果可以看到 LR
為 0xfffffff9
,為 EXC
Return 值,表示返回 Thread mode 時使用 MSP
接著是 PC
的部分,從上面的結果可以看到 PC
為 0x8000552
,查看 assmembly 可以發現指令為以下表示:
08000550 <SVC_Handler>:
8000550: df05 svc 5
8000552: bf00 nop
執行完 SVC
後準備執行 nop
就進 exception
5
: Executing SVC
instruction inside the interrupt handler whose priority whose priority is same or lesser than SVC
handler使用 I2C1_EV
(I2C1
event interrupt & EXTI
Line23 interrupt) 作範例
IRQ number: 31
Priority: 38
SVC
exception
Priority: 3
結果產生了 Hard Fault
In Hard fault
HFSR = 0x40000000
R0 = 0x20000000
R1 = 0x20000064
R2 = 0xe000e200
R3 = 0x80000000
R12 = 0x0
LR = 0xfffffff9
PC = 0x800058e
xPSR = 0x2100002f
繼續分析 LR
及 PC
首先是 LR
的部分,從上面的結果可以看到 LR
為 0xfffffff9
,為 EXC
Return 值,表示返回 Thread mode 時使用 MSP
接著是 PC
的部分,從上面的結果可以看到 PC
為 0x800058e
,查看 assmembly 可以發現指令為以下表示
0800058c <I2C1_EV_EXTI23_IRQHandler>:
800058c: df05 svc 5
800058e: bf00 nop
執行完 SVC
後準備執行 nop
就產生異常了
目標
一共使用 4 種 user tasks
task 介紹
總結: Task 為一段程式碼或是函數,除非被永久移除,不然永遠不會失去其"state"
Thread Mode 時使用 PSP
,進 Handler Mode 後固定為 MSP
Stack assessment
64KB
SRAMStack 設計
1KB
大小的 Stack sizeWhat is state of task?
PC
, MSP
…)Stack of a task: General purpose registers + Some special registers + Status register
ARM Coretex-M4 core register
PC
: 發生 preemptive 時, PC
要儲存當下任務要執行的下一條指令位址Summary: State of a task
r0
~ r12
)PSP
): 因為本次練習使用 PSP
LR
)PC
)PSR
)Task 切換流程
SysTick
ExceptionPUSH
)PSP
valuePSP
valuePOP
)Stack Frame
由下圖可以得知 processor 已經自動儲存以下的資訊,因此在 scheduler 裡頭只要儲存剩下的暫存即可
Saving Context(PUSH
)
R4
~ R11
)Retrieve Context(POP
)
R4
~ R11
) ,接著就退出(回傳自動儲存的暫存)PC
為上次中斷的下一個指令位址,因此會開始執行 Task2Task's stack area init and storing of dummy stack frame
1KB
of memory as a private stack可以將 R0
~ R12
初始設為 0
xPSR
EPSR
的 bit[24]
看到 T
bit, T
bit 是用來決定下一個指令為 ARM 或是 Thumb 指令集,而 Cortex-M4 只支援 Thumb 指令集,因此該 bit 應該永遠都要為 1
xPSR
的初始值應該為 0x01000000
LR
EXC_RETURN
, LR
初始值應該為 0xFFFFFFFD
,回傳到 Thread 後使用 PSP
PC
LSB
應該為 1
(因為 T
bit)系統設定
8MHz
HSI
頻率為 8MHz
SysTick
timer count clock = 8MHz (和 HSI
相同)SysTick
timer count clock 從 16MHz 降到 1KHz → Reload value = 16000System Clock 示意圖
HSI
設定reload value
參考 SysTick
Reload Value Register(SYST_RVR
)
儲存在 SYST_RVR
的値需要減 1
SysTick
設定
SYST_CSR
)ENABLE
設定為 1
: counter enableTICKINT
設定為 1
: 數到 0 之後會產生一個 SysTick
exception requestCLKSOURCE
設定為 1
: 使用 processor clock使用指令 STMDB
將 R4
~ R11
的暫存器數值儲存在 PSP
的 stack 裡
可以參考以下格式
使用指令 LDMIA
取出 Stack
的資料 (R4
~ R11
)