Try   HackMD

Cortex-M4 practices

練習 ARM Cortex-M4 處理器所做的練習項目,專案可見 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

將地址 0x200010000x20001004 分別放入 R1R2

/* 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

將儲存在 R1R2 地址的資料分別放進 R0R1

/* 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

R0R1 資料相加後放進 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]");

Example 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

Example 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

Example 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

目的: 給定 bit band address 及 bit position ,計算其 bit band alias address ,再利用該地址修改目標地址的值

  • 目標修改地址: 0x20000200 ,初始值: 0xff
  • bit position: 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

Stack

Exercise 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 (畫面左下角),而 MSPSP 值相同,表示 ARM 的預設為 MSP ,以下為 MSP 的數值

msp 0x20010000

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Exercise 2: SP 的切換練習

  1. Thread mode: 使用 PSP
  2. Handler mode: 使用 MSP
  3. Stack size: 1KB
  4. MSPPSP 各用一半, MSP 使用上半部, PSP 使用下半部
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

以下根據 Cortex™-M4 Devices Generic User Guide 的說明,利用 Control Register 可以從預設 MSP 設定為 PSP (利用位元 1)

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

參考 MRS and MSR ,可利用指令 MRSMSR 分別讀取和寫入 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 Priority Configuration

  • 目的: 使用 NVIC interrupt pending register 產生以下中斷並分別觀察 priorities 相同及不同時,ISRs 的執行情況

    Interrupt 1: TIM2 global interrupt
    Interrupt 2: I2C1 event interrupt

情況 1: 當兩個 interrupt 的優先度相同時

假設 TIM2I2C1 優先度都為 8

  1. 完成 interrupt 設定
  2. 進入 TIM2 interrupt
  3. I2C Priority Register enable
  4. 因為 I2C 優先度和 TIM2 相等,因此不進 I2C 中斷函式,停在 TIM2 函式裡
  5. 結果
    ​​​TIM2 Interrupt Handler
    

情況 2: 當兩個 interrupt 的優先度不同時

假設 TIM2 優先度為 8I2C1 優先度為 7

  1. 完成 interrupt 設定
  2. 進入 TIM2 interrupt
  3. I2C Priority Register enable
  4. 因為 I2C 優先度比 TIM2 高,因此跳進 I2C 中斷函式
  5. 結果 (在 Putty 上顯示)
    ​​​TIM2 Interrupt Handler
    ​​​IC21 Interrupt Handler
    

Analyzing stack contents during exception entry and exit

目的: 使用 RTC_WKUP interrupt 分析進入中斷後,觀察 SP 的變化及儲存的暫存器為何

ARM cortex-M4 exception stack frame

使用 MSP 並觀察其結果

  1. 進中斷前 SP 的位址: 0x2000fff8, xPSR 數值: 0x1000000

  2. 進中斷後 SP 的位址: 0x2000ffd0

  3. 根據下圖,查看儲存資料,並可以得知 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
  4. EXC Return
    從 assembly code 可以看到進中斷前會先執行 push {r7, lr} ,將 r7lr 的值放進 MSP 的 Stack space
    離開中斷則是會執行pop {r7, pc} ,從 stack 取兩個值並分別給 r7pc

    ​​​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 並觀察其結果

  1. 進中斷之前各個暫存器的數值(此時已啟用 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
  1. 進中斷後各個暫存器的數值
 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 儲存資料,有一些小發現

  • 進中斷和離開中斷都和先前一樣,只差在因為中斷裡使用 MSPSPEXEC 會存到 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

Get SVC number

#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

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)

Example 1: 執行未定義指令

首先在地址 0x20000501 放進未定義的指令 0xFFFFFF

利用函數指標指到 0x20000501 並且執行,發現會進 Usage Fault

接著會發現有趣的現象,直接進到 UsageFault_Handler

接著開始分析 fault 產生的原因

  1. 可以從 Fault Status and Fault Address Register 得知產生原因

  2. 查看 UsageFault Status Register 的值可以知道發生什麼問題


  3. 最後印出 UFSR 的值可以知道產生原因為 Undefined Instruction ,對照表格後可以看出為 UNDEFINSTR → Undefined instruction UsageFault

    ​​​In Usage fault
    ​​​UFSR = 1
    
  4. 嘗試更改存放指令的地址為 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

繼續分析 LRPC

首先是 LR 的部分,從上述的結果可以看到 LR0x8000547 ,查看組合語言可以發現指令如下表示

 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 ,從上述的結果可以看到 PC0x2000501 ,查看組合語言結果如下
0x20005010xFFFFFFFF ,屬於未定義指令

Example 2: Divide by 0

首先啟用 divide by 0 trap

  1. 使用Configuration and Control Register (CCR)

  2. 可利用 CCRbit[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

繼續分析 LRPC

首先是 LR 的部分,從上述的結果可以看到 LR0x8000525 ,查看組合語言可以發現指令如下所示

 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 ,從上面的結果可以看到 PC0x8000542 ,查看 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
  1. 查看 HardFault Status Register (HFSR)


  2. 可以看到為 bit[30]1

Example 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

繼續分析 LRPC

首先是 LR 的部分,從上述的結果可以看到 LR0x800053d ,查看 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 的部分,從上述的結果可以看到 PC0x40000000 ,表示 PC 已經跳到 0x40000000 要執行指令

Example 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

繼續分析 LRPC

首先是 LR 的部分,從上面的結果可以看到 LR0xfffffff9 ,為 EXC Return 值,表示返回 Thread mode 時使用 MSP

接著是 PC 的部分,從上面的結果可以看到 PC0x8000552 ,查看 assmembly 可以發現指令為以下表示:

 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

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

繼續分析 LRPC

首先是 LR 的部分,從上面的結果可以看到 LR0xfffffff9 ,為 EXC Return 值,表示返回 Thread mode 時使用 MSP

接著是 PC 的部分,從上面的結果可以看到 PC0x800058e ,查看 assmembly 可以發現指令為以下表示

 0800058c <I2C1_EV_EXTI23_IRQHandler>:
 800058c:	df05      	svc	5
 800058e:	bf00      	nop

執行完 SVC 後準備執行 nop 就產生異常了

Implemnting a Schedular

目標

  • 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

  • task 介紹

    1. Task只不過是一段code,可以稱為函數,當可以在CPU上執行時,會執行特定功能
    2. A task擁有自己的stack去建立自己的local variables. 同時當Scheduler決定移出task時,第一步為儲存context(state) of the task in task's private stack

總結: Task 為一段程式碼或是函數,除非被永久移除,不然永遠不會失去其"state"

Stack pointer selection

  • Thread Mode 時使用 PSP ,進 Handler Mode 後固定為 MSP

  • Stack assessment

    • STMF303ZE 有 64KB SRAM
  • Stack 設計

    1. 每個 stack 都有 1KB 大小的 Stack size
    2. 順序從 Task1 、 Task2 Task4 ,最後接著 Handler 的 Stack Size

Scheduling policy selection

  1. 使用 round robin pre-emptive scheduling
  2. 不使用 task priority
  3. 使用 SysTick timer 產生 exception ,每秒執行 scheduler code
  • 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 )
  • 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

    • 切換任務時要儲存以下的暫存器
      1. General purpose registers (r0 ~ r12)
      2. Stack Pointer (PSP): 因為本次練習使用 PSP
      3. Link Register (LR)
      4. Program counter (PC)
      5. Program status register (PSR)

  • 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

  • Stack Frame
    由下圖可以得知 processor 已經自動儲存以下的資訊,因此在 scheduler 裡頭只要儲存剩下的暫存即可

  • Saving Context(PUSH)

    • 除了系統自動儲存的暫存器, Handler 必需額外儲存設下的暫存器 (R4 ~ R11)
  • Retrieve Context(POP)

    • 在 Handler 裡,負責回傳非自動儲存的暫存器 (R4 ~ R11) ,接著就退出(回傳自動儲存的暫存)
    • 因為回傳的 PC 為上次中斷的下一個指令位址,因此會開始執行 Task2
  • 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
      1. 可以將 R0 ~ R12 初始設為 0

      2. xPSR

        • EPSRbit[24] 看到 T bit, T bit 是用來決定下一個指令為 ARM 或是 Thumb 指令集,而 Cortex-M4 只支援 Thumb 指令集,因此該 bit 應該永遠都要為 1
        • 因此 xPSR 的初始值應該為 0x01000000
      3. LR

        • 根據 EXC_RETURNLR 初始值應該為 0xFFFFFFFD ,回傳到 Thread 後使用 PSP
        • Exception Return
      4. PC

        • 初始值應為 Task_Handler 的地址
        • LSB 應該為 1 (因為 T bit)

Configure SysTick Timer

  • 系統設定

    1. Processor Clock = 8MHz
      從 Reference manual(RM0316) 可以得知 NUCLEO-F303ZE 的 HSI 頻率為 8MHz
    2. SysTick timer count clock = 8MHz (和 HSI 相同)
    3. 設定每 1ms 觸發一次 exception → 希望每 1 秒會觸發 1000 次 exception
    4. 為了達成 1000Hz 的頻率,需使用 divsor(reload value) 將 SysTick timer count clock 從 16MHz 降到 1KHz → Reload value = 16000
  • System Clock 示意圖

    • System Clock 預設為 HSI
  • 設定reload value

    1. 參考 SysTick Reload Value Register(SYST_RVR)

    2. 儲存在 SYST_RVR 的値需要減 1

      • 假設每 100 cycle pulses 觸發一次中斷,則要設定 reload value 為 99
    3. SysTick 設定

      • 參考 SysTick Control and Status Register(SYST_CSR)

      • 需要的設定
        1. ENABLE 設定為 1: counter enable
        2. TICKINT 設定為 1: 數到 0 之後會產生一個 SysTick exception request
        3. CLKSOURCE 設定為 1: 使用 processor clock

Implementing the Systick Handler

  • 使用指令 STMDBR4 ~ R11 的暫存器數值儲存在 PSP 的 stack 裡


    可以參考以下格式

  • 使用指令 LDMIA 取出 Stack 的資料 (R4 ~ R11)

Initialize scheduler stack pointer

額外實作紀錄

CM4 clock 實作