?
今日分享參加瑞薩RA MCU創意氛圍賽的選手項目——基于優先級的RTOS內核。本項目為基于優先級調度的嵌入式實時操作系統內核,其中調度部分使用固定可搶占的優先級調度機制;提供了可移植接口以便適配不同架構的cpu;重寫了更簡易更輕量級的部分庫函數,比如標準輸入輸出以及字符串相關操作;除內核外還提供部分組件,包括一個簡易的shell程序以及設備驅動框架。具體的操作我們一起來看看講解吧!
一、簡介
1.1 項目簡介
SimpleRTOS(catOS) 是我大學實習期間為了學習RTOS編寫的一個簡單的內核,主要調度方式基于優先級搶占,該項目重構了兩次,故內容和功能有所不同,最新版本僅保留了固定優先級調度方式。
本次項目內容為將該內核對野火的瑞薩啟明6M5開發板進行適配,并編寫簡單的demo驗證正確性。如果有什么錯誤請大家批評指正。(文末有項目資料鏈接可供參考)
1.2 開發板簡介
官網特性介紹如下:
?支持TrustZone的200MHz Arm Cortex-M33
?安全芯片功能
?1MB - 2MB閃存、448KB支持奇偶校驗的SRAM和64KB ECC SRAM
?具有后臺運行能力的雙區閃存,以及存儲塊交換功能
?8KB數據閃存,提供與EEPROM類似的數據存儲功能
?100引腳封裝至176引腳封裝
?高速和全速USB 2.0
?CAN FD(也支持CAN 2.0B)
?SPI/I2C多主接口
?SDHI和MMC
二、適配內核
2.1 可移植接口概覽
需要實現的可移植接口包括以下部分
左右滑動查看
?
/** * @brief 硬件初始化 */ void cat_hw_init(void); /** * @brief 開始調度 * */ void catos_start_sched(void); /** * @brief 上下文切換 * */ //void cat_hw_context_switch(void); /** * @brief 上下文切換 * * @param ?from_task_sp_addr 上一個任務tcb中堆棧指針變量的 *地址* * @param ?to_task_sp_addr ? 下一個任務tcb中堆棧指針變量的 *地址* */ void cat_hw_context_switch(cat_uint32_t from_task_sp_addr, cat_uint32_t to_task_sp_addr); /** * @brief 切換到第一個任務的上下文 * * @param ?first_task_sp_addr ?要切換的任務tcb中堆棧指針變量的 *地址* */ void cat_hw_context_switch_to_first(cat_uint32_t first_task_sp_addr); /** * @brief 關中斷進臨界區 * * @return cat_uint32_t */ cat_uint32_t cat_hw_irq_disable(void); /** * @brief 開中斷出臨界區 * * @param status */ void cat_hw_irq_enable(cat_uint32_t status); /** * @brief 棧初始化 * * @param task_entry ? ?任務入口函數地址 * @param parameter ? ? 參數 * @param stack_addr ? ?棧起始地址 * @param exit ? ? ? ? ?任務退出函數地址 * @return cat_uint8_t* ? ? 初始化后的棧頂地址 */ cat_uint8_t *cat_hw_stack_init(void *task_entry, void *parameter, cat_uint8_t *stack_addr, void *exit);
?
2.2 硬件初始化
在硬件初始化中主要是設置系統時鐘中斷頻率,初始化時設置時鐘中斷為關閉狀態。
左右滑動查看
?
/** * @brief 硬件初始化 */ void cat_hw_init(void) { /* 設置系統時鐘中斷頻率為100Hz(每秒100次) */ cat_set_systick_period(CATOS_SYSTICK_MS); } /** * @brief 初始化時鐘中斷 * * @param ms 每個tick的時間(ms) */ static void cat_set_systick_period(cat_uint32_t ms) { cat_uint32_t err = 0; cat_uint32_t IT_Period = 0; IT_Period = ms * SystemCoreClock / 1000; //err = SysTick_Config(IT_Period); /* 如果設定的周期太離譜就停在這 */ if ((IT_Period - 1UL) > SysTick_LOAD_RELOAD_Msk) { err = 1; } assert(0 == err); SysTick->LOAD = (uint32_t)(IT_Period - 1UL); /* 設置重裝載寄存器 */ NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* 設置時鐘中斷優先級 */ SysTick->VAL = 0UL; /* 設置計數器裝載值 */ SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | /* 設定為內核時鐘FCLK */ SysTick_CTRL_TICKINT_Msk | /* 設定為systick計數器倒數到0時觸發中斷 */ ~SysTick_CTRL_ENABLE_Msk; /* 關閉定時器中斷,若創建任務則在catos_start_sched()中開啟該中斷 */ }
?
2.3 開始調度與切換到第一個任務的上下文
開始調度需要從就緒表中獲取最高優先級任務并設置為當前任務,并且在恢復第一個任務上下文之前需要打開時鐘中斷并初始化pendsv中斷以保證調度的正常工作。
注:這里暫時沒有對不使用fpu的情況適配,參考FreeRTOS的可移植接口實現。
左右滑動查看
?
/** * @brief 開始調度 * */ void catos_start_sched(void) { cat_uint32_t tmp_reg = 0; struct _cat_task_t *first_task = NULL; /* 獲取最高優先級任務 */ first_task = cat_sp_task_highest_ready(); /* 因為是第一個任務,不用像調度時判斷是否和上一個任務一樣,直接賦值給當前任務就行 */ cat_sp_cur_task = first_task; /* 允許調度(打開調度鎖,并且不在該處進行調度) */ cat_sp_task_sched_enable_without_sched(); /* 開啟時鐘中斷 */ SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; /* 設置pendsv中斷優先級 */ tmp_reg = MEM32(NVIC_SHPR3); tmp_reg |= NVIC_PENDSV_PRI; MEM32(NVIC_SHPR3) = tmp_reg; /* 切換到第一個任務 */ cat_hw_context_switch_to_first((cat_uint32_t)&(first_task->sp)); } /** * @brief 切換到第一個任務的上下文 * */ void cat_hw_context_switch_to_first(void) { __enable_irq(); __ISB(); /* 各個寄存器地址 */ __asm volatile ( ".equ SCB_ICSR, 0xE000ED04 " // 中斷控制寄存器 ".equ SCB_VTOR, 0xE000ED08 " // 中斷向量表偏移寄存器 ".equ ICSR_PENDSVSET, 0x10000000 " // pendsv觸發值 ".equ SHPR3_PRI_14, 0xE000ED22 " // 系統異常handler優先級寄存器 3 (PendSV). ".equ PRI_LVL_PENDSV, 0xFF "http:// pendsv優先級 (最低). ".equ SHPR3_PRI_15, 0xE000ED23 "http:// 系統異常handler優先級寄存器 3 (Systick). "ldr r1, =cat_context_to_task_sp_ptr " "str r0, [r1] " #if __FPU_USED //#error "__FPU_USED" /* 清除control寄存器的FPCA */ "mrs r2, control " /* read */ "bic r2, r2, #0x04 " /* modify */ "msr control, r2 " /* write-back */ #else #error "must use fpu" #endif /* 將變量 cat_context_from_task_sp_ptr 設置為0*/ "ldr r1, =cat_context_from_task_sp_ptr " "mov r0, #0 " "str r0, [r1] " "mov r4, #0x1234 " /* 觸發pendsv中斷,允許中斷后會立即進入pendsv切換 */ "ldr r0, =SCB_ICSR " "ldr r1, =ICSR_PENDSVSET " "str r1, [r0] " /* *(SCB_ICSR) = "ICSR_PENDSVSET */ /* 不會到達這里 */ "dsb " "isb " "svc 0 " ); }
?
2.4 上下文切換
上下文切換使用pendsv中斷進行,主要工作為保存當前任務堆棧和寄存器以及恢復下一個任務的堆棧和寄存器。
左右滑動查看
?
/** * void cat_hw_context_switch(void) * 觸發pendsv中斷進行任務切換(pendsv的優先級在開始第一個任務時已經設置) */ ? ?.global cat_hw_context_switch ? ?.type cat_hw_context_switch, %function cat_hw_context_switch: ? ?/* 將兩個任務的堆棧指針變量的 *地址* 加載到臨時變量中 */ ? ?/* cat_context_from_task_sp_ptr = &(cat_sp_cur_task->sp) */ ? ?ldr r2, =cat_context_from_task_sp_ptr ? ?str r0, [r2] ? ?/* cat_context_to_task_sp_ptr = &(cat_sp_next_task->sp) */ ? ?ldr r2, =cat_context_to_task_sp_ptr ? ?str r1, [r2] ? ?/* 觸發pendsv中斷進行切換 */ ? ?ldr r0, =SCB_ICSR ? ? ? ? ?ldr r1, =ICSR_PENDSVSET ? ?str r1, [r0] ? ? ? ? ? ? ? ?/* *(SCB_ICSR) = ICSR_PENDSVSET */ ? ?bx ?lr void PendSV_Handler(void) { ? ?__asm volatile ( ? ? ? ?/* 關閉全局中斷并保存當前中斷屏蔽寄存器中的值方便恢復 */ ? ?"mrs r2, primask ? ? ? ? ? ? ? ? ? ? ? ? ? ? " ? ?"cpsid i ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? " ? ?/* 保存中斷屏蔽寄存器狀態 */ ? ?"push {r2} ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? " ? ?/* 獲取from任務的堆棧指針變量中的值 */ ? ?/* 在進入pendsv之前 cat_context_from_task_sp_ptr = &(from_task->sp) */ ? ?/** 故有: ? ? * r0 = ?&(from_task->sp) ? ? * r1 = *(&(from_task->sp)) 等價于 r1 = from_task->sp ? ? */ ? ?"ldr r0, =cat_context_from_task_sp_ptr ? ? ? " ? ?"ldr r1, [r0] ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? " ? ?/* 如果為零則說明是第一個任務 */ ? ?"cbz r1, switch_to_thread ? ? ? ? ? ? ? ? ? " /* 暫時可能用不到trustzone, 因此直接跳轉 */ ? ?"b contex_ns_store ? ? ? ? ? ? ? ? ? ? ? ? ? " /* 暫時可能用不到 END */ "contex_ns_store: ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? " ? ?/* 用戶級堆棧是psp,特權級堆棧是msp */ ? ?/* 任務用的是psp,將當前寄存器保存到堆棧中 */ ? ?"mrs r1, psp ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? " #if __FPU_USED ? ?"tst lr, #0x10 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? " ? ?"it eq ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? " ? ?"vstmdbeq r1!, {s16-s31} ? ? ? ? ? ? ? ? ? ? " #if BSP_TZ_NONSECURE_BUILD #else ? ? ? ?/* Stack R4-R11 on the process stack. Also stack LR since the FPU is supported. */ ? ? ? ?"STMDB ? ? R1!, {R4-R11, LR} ? ? ? ? ? ? " #endif #else #error "must use fpu" #endif #if BSP_TZ_NONSECURE_BUILD #error "should not use BSP_TZ_NONSECURE_BUILD" #elif RM_CATOS_PORT_PSPLIM_PRESENT ? ? ? ?"mrs ? ? r3, psplim ? ? ? ? ? ? ? ? ? ? ? " /* R3 = PSPLIM. */ ? ? ? ?"stmdb ? r1!, {r3} ? ? ? ? ? ? ? ? ? ? ? " #endif ? ?/* 記錄最后的指針到任務棧curstk->stack */ ? ?/* 更新tcb的堆棧指針變量值 */ ? ?/** ? ? ? ?from_task->sp = r1 ? ? */ ? ?"ldr r0, [r0] ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? " ? ?"str r1, [r0] ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? " #if !__FPU_USED #error "must use fpu" #endif ? ? ? ?/* 上下文保存結束 */ "switch_to_thread: ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? " ? ?"ldr r1, =cat_context_to_task_sp_ptr ? ? ? ? " ? ?"ldr r1, [r1] ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? " ? ?"ldr r1, [r1] ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? " #if BSP_TZ_NONSECURE_BUILD #error "not support BSP_TZ_NONSECURE_BUILD" #elif RM_CATOS_PORT_PSPLIM_PRESENT ? ? ? ?"LDMIA ? R1!, {R2} ? ? ? ? ? ? ? ? ? ? ? " /* R1 = PSPLIM */ ? ? ? ?"MSR ? ? PSPLIM, R2 ? ? ? ? ? ? ? ? ? ? ? " /* Restore the PSPLIM register value for the task. */ #endif #if BSP_TZ_NONSECURE_BUILD #error "not support BSP_TZ_NONSECURE_BUILD" #endif ? ? ? ?"b contex_ns_load ? ? ? ? ? ? ? ? ? ? ? ? ? " "contex_ns_load: ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? " #if __FPU_USED #if BSP_TZ_NONSECURE_BUILD ?#error "not support BSP_TZ_NONSECURE_BUILD" #else ? ? ? ?/* Restore R4-R11 and LR from the process stack. */ ? ? ? ?"LDMIA ? R1!, {R4-R11, LR} ? ? ? ? ? ? ? " #endif ? ? ? ?/* Check to see if the thread being restored is using the FPU. If so, restore S16-S31. */ ? ? ? ?"TST ? ? ? LR, #0x10 ? ? ? ? ? ? ? ? ? ? " ? ? ? ?"IT ? ? ? ?EQ ? ? ? ? ? ? ? ? ? ? ? ? ? ? " ? ? ? ?"VLDMIAEQ ?R1!, {S16-S31} ? ? ? ? ? ? ? ? " #else #error "must use fpu" #endif "pendsv_exit: ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? " ? ?"msr psp, r1 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? " #if !__FPU_USED #error "must use fpu" #endif ? ?"pop {r2} ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? " ? ?/* 恢復屏蔽寄存器值 */ ? ?"msr primask, r2 ? ? ? ? ? ? ? ? ? ? ? ? ? ? " #if __FPU_USED ? ?"bx lr ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? " #else #error "must use fpu" #endif ? ?); }
?
2.5 臨界區的開關中斷
開關中斷主要是對primask的操作,和cortex-m3差不多,比較簡單。
左右滑動查看
?
/** * cat_uint32_t cat_hw_irq_disable(void) * 關中斷方式進入臨界區 * primask-->r0 */ ? ?.global cat_hw_irq_disable ? ?.type cat_hw_irq_disable, %function cat_hw_irq_disable: ? ?mrs r0, primask ? ? ? ? ? ? /* ret = primask */ ? ?cpsid I ? ? ? ? ? ? ? ? ? ? /* disable irq */ ? ?bx lr ? ? ? ? ? ? ? ? ? ? ? /* return ret */ /** * void cat_hw_irq_enable(cat_uint32_t status) * 開中斷方式出臨界區 * r0-->status */ ? ?.global cat_hw_irq_enable ? ?.type cat_hw_irq_enable, %function cat_hw_irq_enable: ? ?msr primask, r0 ? ? ? ? ? ? /* primask = status */ ? ?bx lr
?
2.6 任務棧初始化
在任務創建時需要根據任務的相關信息對任務棧幀中各項進行初始化,包括設置psr寄存器、任務入口地址、退出函數地址和任務參數。
需要特別注意的是cortex-m33還需要正確設置psplim等寄存器。
左右滑動查看
?
/** * @brief 棧初始化 * * @param task_entry ? ?任務入口函數地址 * @param parameter ? ? 參數 * @param stack_addr ? ?棧起始地址 * @param exit ? ? ? ? ?任務退出函數地址 * @return cat_uint8_t* ? ? 初始化后的棧頂地址 */ cat_uint8_t *cat_hw_stack_init(void *task_entry, void *arg, cat_uint8_t *stack_addr, void *exit_func) { ?struct _stack_frame *stack_frame; ?cat_uint32_t ? ? ? ? *stack; ?cat_uint32_t ? ? ? ? i; ?/* 先加上4字節再8字節向下取整對齊(相當于四舍五入) */ ?stack = stack_addr + sizeof(cat_uint32_t); ?stack = (cat_uint8_t *)CAT_ALIGN_DOWN((cat_uint32_t)stack, 8); ? ?/* task context saved & restore by hardware: */ ? ?*(--stack) = (cat_uint32_t)0x01000000L; /* xPSR: EPSR.T = 1, thumb mode ? */ ? ?*(--stack) = (cat_uint32_t)task_entry; ? ? ? /* Entry Point */ ? ?*(--stack) = (cat_uint32_t)exit_func; /* R14 (LR) ? ? ? ? ? ? ?*/ ? ?*(--stack) = (cat_uint32_t)0x12121212L; /* R12 ? ? ? ? ? ? ? ? ? ? ? ? ? ?*/ ? ?*(--stack) = (cat_uint32_t)0x03030303L; /* R3 ? ? ? ? ? ? ? ? ? ? ? ? ? ? */ ? ?*(--stack) = (cat_uint32_t)0x02020202L; /* R2 ? ? ? ? ? ? ? ? ? ? ? ? ? ? */ ? ?*(--stack) = (cat_uint32_t)0x01010101L; /* R1 ? ? ? ? ? ? ? ? ? ? ? ? ? ? */ ? ?*(--stack) = (cat_uint32_t)arg; ? ? ? ? /* R0 : argument ? ? ? ? ? ? ? ? ?*/ #if __FPU_USED && !BSP_TZ_NONSECURE_BUILD ? ?*(--stack) = (cat_uint32_t)portINITIAL_EXC_RETURN; /* exe_return值 */ #endif ? ?*(--stack) = (cat_uint32_t)0x11111111L; /* R11 */ ? ?*(--stack) = (cat_uint32_t)0x10101010L; /* R10 */ ? ?*(--stack) = (cat_uint32_t)0x09090909L; /* R9 ?*/ ? ?*(--stack) = (cat_uint32_t)0x08080808L; /* R8 ?*/ ? ?*(--stack) = (cat_uint32_t)0x07070707L; /* R7 ?*/ ? ?*(--stack) = (cat_uint32_t)0x06060606L; /* R6 ?*/ ? ?*(--stack) = (cat_uint32_t)0x05050505L; /* R5 ?*/ ? ?*(--stack) = (cat_uint32_t)0x04040404L; /* R4 ?*/ #if RM_CATOS_PORT_PSPLIM_PRESENT ? ?*(--stack) = (cat_uint32_t)0x00; /* psplim */ #endif ? ?stack_frame = (struct _stack_frame *)stack; #endif /* #if 0 */ ?/* 返回當前棧指針 */ ?return stack; }
?
審核編輯:湯梓紅
評論
查看更多