--- tags: ARM Cortex-M --- # Cortex-M4 practices 練習 ARM Cortex-M4 處理器所做的練習項目,專案可見 [cm4bmp](https://github.com/Risheng1128/cm4bmp) ## Inline Assembly Coding ### Example `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` ```c /* 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` ```c /* 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` ```c /* add R0 and R1, then move to R0 */ __asm volatile("ADD R0,R0,R1"); ``` 將 `R0` 存進 `R2` 的地址裡 ```c /* store the result into 0x20001004 */ __asm volatile("STR R0,[R2]"); ``` 將地址 `0x20001004` 讀出來並存到 `R2` ```c /* move the result into R2 */ __asm volatile("LDR R2,[R2]"); ``` ### Example `2`: 讀取 Control Register `=`: 表示這個指令為寫入,以下範例表示寫入到變數 `control_reg` ```c /* 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 ``` ### Example `3`: 把一個變數的值複製到另一個變數的數值 ```c 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 ``` ### Example `4`: 把一個位址的值寫到另一個變數的數值 ```c 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 目的: 給定 bit band address 及 bit position ,計算其 bit band alias address ,再利用該地址修改目標地址的值 - 目標修改地址: `0x20000200` ,初始值: `0xff` - bit position: `7` 首先將目標地址的資料設定為 `0xff` ,並使用 bitwise operation 的方法修改其 bit[7] ,對應程式碼如下 ```c 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 修改,實際程式碼如下 ```c /* 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 ``` ## Stack ### Exercise `1`: 觀察 `SP` 從 Linker script 的檔案可以算出一開始 stack 的起始位置 (`_estack`) > _estack = 0x20000000 + 64 * 1024 = 0x20010000 ```shell /* 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 ![](https://i.imgur.com/DgCJEI8.png) ### Exercise `2`: `SP` 的切換練習 1. Thread mode: 使用 `PSP` 2. Handler mode: 使用 `MSP` 3. Stack size: `1KB` 4. `MSP` 和 `PSP` 各用一半, `MSP` 使用上半部, `PSP` 使用下半部 ![](https://i.imgur.com/SO8lSMv.png) 以下根據 Cortex™-M4 Devices Generic User Guide 的說明,利用 Control Register 可以從預設 `MSP` 設定為 `PSP` (利用位元 `1`) ![](https://i.imgur.com/iaGXo3J.png) 參考 [`MRS` and `MSR`](https://www.itread01.com/content/1542308852.html) ,可利用指令 `MRS` 和 `MSR` 分別讀取和寫入 `PSP` 接著可以開始進行實驗,首先利用暫存器 `R0` 將初始值 `PSP_START` 寫入 `PSP` 暫存器 ```c /* 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](https://github.com/Risheng1128/cm4bmp/blob/main/test/stack/stack.c) ## Interrupt Priority Configuration - 目的: 使用 NVIC interrupt pending register 產生以下中斷並分別觀察 priorities 相同及不同時,ISRs 的執行情況 > Interrupt 1: TIM2 global interrupt > Interrupt 2: I2C1 event interrupt ### 情況 `1`: 當兩個 interrupt 的優先度相同時 假設 `TIM2` 和 `I2C1` 優先度都為 `8` 1. 完成 interrupt 設定 ![](https://i.imgur.com/RQnhbvR.png) 2. 進入 `TIM2` interrupt ![](https://i.imgur.com/anwhLCQ.png) 3. 將 `I2C` Priority Register enable ![](https://i.imgur.com/kCmzevM.png) 4. 因為 `I2C` 優先度和 `TIM2` 相等,因此不進 `I2C` 中斷函式,停在 `TIM2` 函式裡 ![](https://i.imgur.com/9Sn1KXf.png) 5. 結果 ```shell TIM2 Interrupt Handler ``` ### 情況 `2`: 當兩個 interrupt 的優先度不同時 假設 `TIM2` 優先度為 `8` , `I2C1` 優先度為 `7` 1. 完成 interrupt 設定 ![](https://i.imgur.com/RQnhbvR.png) 2. 進入 `TIM2` interrupt ![](https://i.imgur.com/anwhLCQ.png) 3. 將 `I2C` Priority Register enable ![](https://i.imgur.com/kCmzevM.png) 4. 因為 `I2C` 優先度比 `TIM2` 高,因此跳進 `I2C` 中斷函式 ![](https://i.imgur.com/zkuqGM5.png) 5. 結果 (在 Putty 上顯示) ```shell TIM2 Interrupt Handler IC21 Interrupt Handler ``` ## Analyzing stack contents during exception entry and exit 目的: 使用 `RTC_WKUP` interrupt 分析進入中斷後,觀察 `SP` 的變化及儲存的暫存器為何 ARM cortex-M4 exception stack frame ![](https://i.imgur.com/5yCCEB4.png) ### 使用 `MSP` 並觀察其結果 1. 進中斷前 `SP` 的位址: `0x2000fff8`, `xPSR` 數值: `0x1000000` ![](https://i.imgur.com/3V39r09.png) 3. 進中斷後 `SP` 的位址: `0x2000ffd0` ![](https://i.imgur.com/ODEbt4H.png) 3. 根據下圖,查看儲存資料,並可以得知 ARM 為 little-endian ![](https://i.imgur.com/yJXWxjL.png) | 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 | 4. `EXC` Return 從 assembly code 可以看到進中斷前會先執行 `push {r7, lr}` ,將 `r7` 和 `lr` 的值放進 `MSP` 的 Stack space 離開中斷則是會執行`pop {r7, pc}` ,從 stack 取兩個值並分別給 `r7` 和 `pc` ```asm 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 ``` ![](https://i.imgur.com/AoEHX8F.png) ### 使用 `PSP` 並觀察其結果 1. 進中斷之前各個暫存器的數值(此時已啟用 `PSP`) ```diff 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 ``` 2. 進中斷後各個暫存器的數值 ```diff 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 儲存資料 ![](https://i.imgur.com/OTiVkGV.png) 觀察 MSP 管理的 stack 儲存資料,有一些小發現 - 進中斷和離開中斷都和先前一樣,只差在因為中斷裡使用 `MSP`, `SP` 和 `EXEC` 會存到 `MSP` 的空間裡,其他則儲存在 `PSP` 空間裡 - 因為 Thread Mode 是使用 `PSP` , 因此 `EXC Return` 的數值也不同 ```asm 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 ![](https://i.imgur.com/8lS5EHV.png) ## Get SVC number ```c #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 } ``` ```shell 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> ``` ```shell 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} ``` :::warning TODO: 補充 warning ::: ## Configurable fault exception 目標: 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`) ![](https://i.imgur.com/fyKGeGd.png) ### Example `1`: 執行未定義指令 首先在地址 `0x20000501` 放進未定義的指令 `0xFFFFFF` ![](https://i.imgur.com/nkNJXBe.png) 利用函數指標指到 `0x20000501` 並且執行,發現會進 Usage Fault ![](https://i.imgur.com/sPQB25d.png) 接著會發現有趣的現象,直接進到 `UsageFault_Handler` 裡 ![](https://i.imgur.com/MUilZma.png) 接著開始分析 fault 產生的原因 1. 可以從 Fault Status and Fault Address Register 得知產生原因 ![](https://i.imgur.com/JKnoouv.png) 2. 查看 UsageFault Status Register 的值可以知道發生什麼問題 ![](https://i.imgur.com/Mnj8ir6.png) ![](https://i.imgur.com/9R8ssEA.png) ![](https://i.imgur.com/YE3LvjW.png) 3. 最後印出 UFSR 的值可以知道產生原因為 Undefined Instruction ,對照表格後可以看出為 `UNDEFINSTR` &rarr; Undefined instruction UsageFault ```shell In Usage fault UFSR = 1 ``` 4. 嘗試更改存放指令的地址為 `0x20000500` (`T` 位元為 `0`) ,對照表格後可以看出為 `INVSTATE` &rarr; Invalid state UsageFault ```shell In Usage fault UFSR = 2 ``` 開始分析 stack frame ![](https://i.imgur.com/LSCFpWI.png) 以下為輸出結果 ```shell In Usage fault UFSR = 0x2 R0 = 0x20000000 R1 = 0x20000064 R2 = 0xffffffff R3 = 0x20000500 R12 = 0x0 LR = 0x8000547 PC = 0x20000500 xPSR = 0x20000000 ``` 查看 memory view ![](https://i.imgur.com/YLIfElE.png) 繼續分析 `LR` 及 `PC` 首先是 `LR` 的部分,從上述的結果可以看到 `LR` 為 `0x8000547` ,查看組合語言可以發現指令如下表示 ```shell 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` ,查看組合語言結果如下 &rarr; `0x2000501` 為 `0xFFFFFFFF` ,屬於未定義指令 ### Example `2`: Divide by `0` 首先啟用 divide by 0 trap 1. 使用Configuration and Control Register (`CCR`) ![](https://i.imgur.com/x6rDduI.png) 2. 可利用 `CCR` 的 `bit[4]` 來trap divide by `0` ![](https://i.imgur.com/EWHvCD6.png) ![](https://i.imgur.com/gILKPqu.png) 以下為程式運行結束後,在終端機顯示的訊息 ```shell 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` ![](https://i.imgur.com/Vgi6fpt.png) 繼續分析 `LR` 及 `PC` 首先是 `LR` 的部分,從上述的結果可以看到 `LR` 為 `0x8000525` ,查看組合語言可以發現指令如下所示 ```shell 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 可以發現指令為以下表示: ```shell 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](https://developer.arm.com/documentation/dui0473/m/arm-and-thumb-instructions/sdiv?lang=en) 最後嘗試把 trap 除以 0 的功能取消,分析會產生什麼變化,以下為程式執行的結果 ```shell In Hard fault ``` 1. 查看 HardFault Status Register (`HFSR`) ![](https://i.imgur.com/zAKJvUX.png) ![](https://i.imgur.com/RQ8SB5S.png) ![](https://i.imgur.com/sZ0Fzlb.png) 2. 可以看到為 `bit[30]` 為 `1` ### Example `3`: 從 peripheral region 執行指令 Peripheral Region: 從下面的圖,可以看到外部設備的地址位於 `0x40000000` ~ `0x5FFFFFFF` ![](https://i.imgur.com/1ZbPknF.png) 以下為程式執行的結果 ```shell In Mem manage fault MMSR = 0x1 R0 = 0x20000000 R1 = 0x20000064 R2 = 0xe000ed24 R3 = 0x40000000 R12 = 0x0 LR = 0x800053d PC = 0x40000000 xPSR = 0x20000000 ``` 接著查看 `MMSR` ![](https://i.imgur.com/15bNQPH.png) ![](https://i.imgur.com/oMLtDwA.png) 查看後可以發現原因為 `bit[0]` &rarr; 對 XN region 進行 instruction fetch 繼續分析 `LR` 及 `PC` 首先是 `LR` 的部分,從上述的結果可以看到 `LR` 為 `0x800053d` ,查看 assmembly 可以發現指令為以下表示: ```shell 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` 要執行指令 ### Example `4`: Executing `SVC` inside the `SVC` handler 以下為程式執行後,在終端機顯示的結果,很明顯得產生了 Hard Fault ```shell 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 可以發現指令為以下表示: ```shell 08000550 <SVC_Handler>: 8000550: df05 svc 5 8000552: bf00 nop ``` 執行完 `SVC` 後準備執行 `nop` 就進 exception ### Example `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 ![](https://i.imgur.com/h3qzsXE.png) `SVC` exception > Priority: 3 ![](https://i.imgur.com/BIvoM1q.png) 結果產生了 Hard Fault ```shell 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 可以發現指令為以下表示 ```shell 0800058c <I2C1_EV_EXTI23_IRQHandler>: 800058c: df05 svc 5 800058e: bf00 nop ``` 執行完 `SVC` 後準備執行 `nop` 就產生異常了 ## Implemnting a Schedular :::info 目標 - [ ] Implement a schedular which schedules multiple user tasks in a round-robin fashion by carring out the context switch operation - [ ] Round robin scheduling method is, time slices are assigned to each task in equal portions and in circular order - [ ] First will use systick handler to carry out the context switch operation between multiple tasks - [ ] Later will we change the code using PendSV handler ::: ### User tasks - 一共使用 4 種 user tasks ![](https://i.imgur.com/BaLOFvU.png) - task 介紹 1. Task只不過是一段code,可以稱為函數,當可以在CPU上執行時,會執行特定功能 2. A task擁有自己的stack去建立自己的local variables. 同時當Scheduler決定移出task時,第一步為儲存context(state) of the task in task's private stack :::success 總結: Task 為一段程式碼或是函數,除非被永久移除,不然永遠不會失去其"state" ::: ### Stack pointer selection - Thread Mode 時使用 `PSP` ,進 Handler Mode 後固定為 `MSP` ![](https://i.imgur.com/OS0uAsJ.png) - Stack assessment - STMF303ZE 有 `64KB` SRAM ![](https://i.imgur.com/VwRx3R9.png) - Stack 設計 1. 每個 stack 都有 `1KB` 大小的 Stack size 2. 順序從 Task1 、 Task2 ... Task4 ,最後接著 Handler 的 Stack Size ![](https://i.imgur.com/dd5CIRz.png) ### Scheduling policy selection 1. 使用 round robin pre-emptive scheduling 2. 不使用 task priority 3. 使用 SysTick timer 產生 exception ,每秒執行 scheduler code ![](https://i.imgur.com/m69eIsl.png) - What is scheduling? - scheduling 是一種演算法 (algorithm) ,用來決定要從 CPU pre-empting 的 task 及決定哪個 task 由 CPU 執行 - 決策有很多種因素,像是: 1. system load 2. priority of tasks 3. share resource access 4. simple round-robin method ### Context switch: 從目前的 task (保存其狀態)切換到下一個task(回復之前的狀態)到CPU上執行的過程 - What is state of task? - Processor core內部有: 1. General purpose registers 2. ALU 3. Status registers 4. Special registers(`PC`, `MSP` ...) ![](https://i.imgur.com/7pe5QtS.png) - Stack of a task: General purpose registers + Some special registers + Status register ![](https://i.imgur.com/G2vagKt.png) - ARM Coretex-M4 core register - `PC`: 發生 preemptive 時, `PC` 要儲存當下任務要執行的下一條指令位址 ![](https://i.imgur.com/GRH75FG.png) - Summary: State of a task - 切換任務時要儲存以下的暫存器 1. General purpose registers (`r0` ~ `r12`) 2. Stack Pointer (`PSP`): 因為本次練習使用 `PSP` 3. Link Register (`LR`) 4. Program counter (`PC`) 5. Program status register (`PSR`) ![](https://i.imgur.com/qYksB3B.png) - Task 切換流程 1. 執行 Task1 ,此時發生 `SysTick` Exception 2. 保存 Task1 的 state 到 Task1 的 stack (使用指令 `PUSH`) 3. 保存 Task1 的 `PSP` value 4. 讀取 Task2 的 `PSP` value 5. 回復 Task2 的狀態(從 Task2 的 stack) (使用指令 `POP`) 6. 執行 Task2 ![](https://i.imgur.com/CxmGvyH.png) - Stack Frame 由下圖可以得知 processor 已經自動儲存以下的資訊,因此在 scheduler 裡頭只要儲存剩下的暫存即可 ![](https://i.imgur.com/CkgXhLT.png) - Saving Context(`PUSH`) - 除了系統自動儲存的暫存器, Handler 必需額外儲存設下的暫存器 (`R4` ~ `R11`) ![](https://i.imgur.com/cQz73Mx.png) - Retrieve Context(`POP`) - 在 Handler 裡,負責回傳非自動儲存的暫存器 (`R4` ~ `R11`) ,接著就退出(回傳自動儲存的暫存) - 因為回傳的 `PC` 為上次中斷的下一個指令位址,因此會開始執行 Task2 ![](https://i.imgur.com/jd2waRT.png) - Task's stack area init and storing of dummy stack frame - Each task can consume a maximum of `1KB` of memory as a private stack - This stack is used to hold tasks local variables and context (stack frame1 + stack frame2) - 當一個 task 第一次被 scheduled 時,沒有任何的 context ,因此 programmer 應該在 scheduler 啟動之前對每個 Task 的 stack space 儲存 dummy stack frame1 and stack frame2 - Dummy initial context of task ![](https://i.imgur.com/H8JUZlW.png) 1. 可以將 `R0` ~ `R12` 初始設為 `0` 2. `xPSR` - 從 `EPSR` 的 `bit[24]` 看到 `T` bit, `T` bit 是用來決定下一個指令為 ARM 或是 Thumb 指令集,而 Cortex-M4 只支援 Thumb 指令集,因此該 bit 應該永遠都要為 `1` - 因此 `xPSR` 的初始值應該為 `0x01000000` ![](https://i.imgur.com/vk3s0fy.png) 3. `LR` - 根據 `EXC_RETURN` , `LR` 初始值應該為 `0xFFFFFFFD` ,回傳到 Thread 後使用 `PSP` ![](https://i.imgur.com/LZQFb4l.png) - Exception Return ![](https://i.imgur.com/4ILaMFI.png) 4. `PC` - 初始值應為 Task_Handler 的地址 - `LSB` 應該為 `1` (因為 `T` bit) ![](https://i.imgur.com/asmdXPx.png) ### Configure SysTick Timer - 系統設定 1. Processor Clock = `8MHz` 從 Reference manual(RM0316) 可以得知 NUCLEO-F303ZE 的 `HSI` 頻率為 `8MHz` ![](https://i.imgur.com/7IFi4li.png) 2. `SysTick` timer count clock = 8MHz (和 `HSI` 相同) 3. 設定每 1ms 觸發一次 exception &rarr; 希望每 1 秒會觸發 1000 次 exception 4. 為了達成 1000Hz 的頻率,需使用 divsor(reload value) 將 `SysTick` timer count clock 從 16MHz 降到 1KHz &rarr; Reload value = 16000 - System Clock 示意圖 - System Clock 預設為 `HSI` ![](https://i.imgur.com/APVnhYJ.png) - 設定reload value 1. 參考 `SysTick` Reload Value Register(`SYST_RVR`) ![](https://i.imgur.com/lA7tO2Q.png) ![](https://i.imgur.com/lx7QHqm.png) 2. 儲存在 `SYST_RVR` 的値需要減 `1` - 假設每 100 cycle pulses 觸發一次中斷,則要設定 reload value 為 99 ![](https://i.imgur.com/3SS1gPB.png) 3. `SysTick` 設定 - 參考 SysTick Control and Status Register(`SYST_CSR`) ![](https://i.imgur.com/7yKcVqu.png) ![](https://i.imgur.com/clQxbbx.png) ![](https://i.imgur.com/LhNte8V.png) - 需要的設定 1. `ENABLE` 設定為 `1`: counter enable 2. `TICKINT` 設定為 `1`: 數到 0 之後會產生一個 `SysTick` exception request 3. `CLKSOURCE` 設定為 `1`: 使用 processor clock ### Implementing the Systick Handler - 使用指令 `STMDB` 將 `R4` ~ `R11` 的暫存器數值儲存在 `PSP` 的 stack 裡 ![](https://i.imgur.com/d3xuyJH.png) ![](https://i.imgur.com/gkhfghl.png) 可以參考以下格式 ![](https://i.imgur.com/ysbmOQ8.png) - 使用指令 `LDMIA` 取出 `Stack` 的資料 (`R4` ~ `R11`) ### Initialize scheduler stack pointer ## 額外實作紀錄 [CM4 clock 實作](https://hackmd.io/@Risheng/rJuUI7My9)