嵌入式技术
今日分享参加瑞萨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引脚封装
带有专用DMA的以太网控制器
电容触摸按键感应单元
高速和全速USB 2.0
CAN FD(也支持CAN 2.0B)
QuadSPI和OctaSPI
SCI多功能串口(UART、简单SPI、简单I2C)
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 "// pendsv优先级 (最低). ".equ SHPR3_PRI_15, 0xE000ED23 "// 系统异常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; }
审核编辑:汤梓红
全部0条评论
快来发表一下你的评论吧 !