3、移植RTOS的三个要点
3.1 线程的栈信息初始化
由于N32WB452使用的是Cortex-M4
内核,而Cortex-M3
和Cortex-M4
都基于ARMv7-M
架构。腾讯TinyOS的ARMv7-M
架构支持已经很完善了。这里我们不需要自己去写对应的代码,只需要使用系统里自带的源码即可。
RTOS想从主程序切换到线程上,或者进行线程间的切换,就需要线程的栈信息。然后在准备线程的相关信息时,线程的栈保存是很关键的,如果sp地址没指向正确的地址,就跑飞了。
如果是自己从零开始写,就需要注意port.S文件里,保存寄存器时的顺序,和这个初始化函数里定义的顺序是否一致。
源码的文件路径,arch/arm/arm-v7m/common/tos_cpu.c
__KNL__k_stack_t*cpu_task_stk_init(void*entry,void*arg,void*exit,k_stack_t*stk_base,size_tstk_size){cpu_data_t*sp; sp = (cpu_data_t*)&stk_base[stk_size]; sp = (cpu_data_t*)((cpu_addr_t)sp &0xFFFFFFF8);#ifTOS_CFG_TASK_STACK_DRAUGHT_DEPTH_DETACT_EN > 0uuint8_t*slot = (uint8_t*)&stk_base[0];for(; slot < (uint8_t*)sp; ++slot) { *slot =0xCC; }#endif*--sp = (cpu_data_t)0x01000000u;*--sp = (cpu_data_t)entry;*--sp = (cpu_data_t)exit;*--sp = (cpu_data_t)0x12121212u;*--sp = (cpu_data_t)0x03030303u;*--sp = (cpu_data_t)0x02020202u;*--sp = (cpu_data_t)0x01010101u;*--sp = (cpu_data_t)arg;#ifdefined (TOS_CFG_CPU_ARM_FPU_EN) && (TOS_CFG_CPU_ARM_FPU_EN == 1U)*--sp = (cpu_data_t)0xFFFFFFFDL;#endif*--sp = (cpu_data_t)0x11111111u;*--sp = (cpu_data_t)0x10101010u;*--sp = (cpu_data_t)0x09090909u;*--sp = (cpu_data_t)0x08080808u;*--sp = (cpu_data_t)0x07070707u;*--sp = (cpu_data_t)0x06060606u;*--sp = (cpu_data_t)0x05050505u;*--sp = (cpu_data_t)0x04040404u;return(k_stack_t*)sp; }
如果确定这个线程栈信息是正确初始化了,那接下来就可以看线程切换时,保存和恢复现场的相关代码了。
3.2 现场保存和恢复相关功能
在初始化了线程的相关内容后,就可以准备开始第一次的线程调度了。然后线程调度的相关汇编代码是在arch/arm/arm-v7m/cortex-m4/gcc/port_s.S这个文件里。
相关的代码也是不需要我们写的,除了下面展示的现场保存和恢复部分的汇编代码以外,开关中断,初次调度线程,唤起调度中断等等内容都是实现好了的。没必要自己去写,有兴趣研究的,可以用一块能运行TinyOS的开发板,在对应的功能上打断点,然后再查看运行前后的mcu寄存器值去学习和了解。
.thumb_func .typePendSV_Handler, %function PendSV_Handler: CPSID I MRS R0, PSP _context_save:@ R0-R3, R12, LR, PC, xPSR is saved automatically here@ is it extended frame?TST LR,IT EQ VSTMDBEQ R0!, {S16 - S31}@ S0 - S16, FPSCR saved automatically here@ save EXC_RETURNSTMFD R0!, {LR}@ save remaining regs r4 -11on process stackSTMFD R0!, {R4 - R11}@ k_curr_task->sp = PSP;MOVW R5,MOVT R5,LDR R6, [R5]@ R0 is SP of process being switched outSTR R0, [R6] _context_restore:@ k_curr_task = k_next_task;MOVW R1,MOVT R1,LDR R2, [R1] STR R2, [R5]@ R0 = k_next_task->spLDR R0, [R2]@ restore R4 - R11LDMFD R0!, {R4 - R11}@ restore EXC_RETURNLDMFD R0!, {LR}@ is it extended frame?TST LR,IT EQ VLDMIAEQ R0!, {S16 - S31}@ Load PSP with new process SPMSR PSP, R0 CPSIE I@ R0-R3, R12, LR, PC, xPSR restored automatically here@ S0 - S16, FPSCR restored automatically here if FPCA =1BX LR .end
3.3 系统心跳
在platform/vendor_bsp/st/CMSIS/Core/Include/core_cm4.h文件里,已经实现了通用的系统心跳配置函数,这个也是不需要我们去写的。配置系统心跳的相关内容也不需要我们管,在TinyOS启动内核时会初始化系统心跳。
/** \brief System Tick Configuration \details Initializes the System Timerandits interrupt,andstarts the System Tick Timer. Counterisinfree running mode to generate periodic interrupts. \param [in] ticks Numberofticks between two interrupts. \return0Functionsucceeded.\return1Functionfailed.\note When the variable __Vendor_SysTickConfigisset to1,thenthefunctionSysTick_Configisnotincluded.Inthiscase, the file device.h must contain a vendor-specific implementationofthisfunction. */ __STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks) {if((ticks -1UL) > SysTick_LOAD_RELOAD_Msk) {return(1UL); /* Reload value impossible */ } SysTick->LOAD = (uint32_t)(ticks -1UL); /* set reload register */ NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) -1UL); /* set PriorityforSystick Interrupt */ SysTick->VAL =0UL; /* Load the SysTick Counter Value */ SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQandSysTick Timer */return(0UL); /*Functionsuccessful*/ }
我们只需要在bsp里把SysTick_Handler的实现函数修改一下就可以了。
voidSysTick_Handler(void) {if(tos_knl_is_running()) {tos_knl_irq_enter();tos_tick_handler();tos_knl_irq_leave(); } }
最后,我们在main.c文件里创建两个调试线程
#defineAPPLICATION_TASK_STK_SIZE 1024k_task_tapplication_task; __aligned(4)uint8_tapplication_task_stk[APPLICATION_TASK_STK_SIZE];k_task_tapplication_task1; __aligned(4)uint8_tapplication_task1_stk[APPLICATION_TASK_STK_SIZE];voidapplication_entry(void*arg){while(1) {tos_task_delay(2500);printf("task1 running!\r\n"); }; }voidapplication_entry2(void*arg){while(1) {tos_task_delay(1200);printf("task2 running!\r\n"); }; }
然后在main函数里创建这两个线程,并开启调度就可以啦
/********************************************************************* *@fn main** @brief Main program. **@return none*/ int main(void) { bsp_init(); // 初始化debug串口和板级外设 tos_knl_init(); // 初始化TencentOS tiny内核 printf("TinyOS demo!\r\n"); // 创建一个优先级为5的任务 tos_task_create(&application_task, "task_prio5", application_entry, NULL, 4, application_task_stk, APPLICATION_TASK_STK_SIZE, 0); // 创建一个优先级为5的任务 tos_task_create(&application_task1, "task_prio5", application_entry2, NULL, 4, application_task1_stk, APPLICATION_TASK_STK_SIZE, 0); // 开始内核调度,线程中不允许有睡死代码。 tos_knl_start(); while (1) { } }
然后就编译、烧录,最后看到串口欢快的跳起线程里的输出内容啦。