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_t stk_size)
{
cpu_data_t *sp;
sp = (cpu_data_t *)&stk_base[stk_size];
sp = (cpu_data_t *)((cpu_addr_t)sp & 0xFFFFFFF8);
#if TOS_CFG_TASK_STACK_DRAUGHT_DEPTH_DETACT_EN > 0u
uint8_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;
#if defined (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
.type PendSV_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_RETURN
STMFD R0!, {LR}
@ save remaining regs r4 - 11 on process stack
STMFD R0!, {R4 - R11}
@ k_curr_task->sp = PSP;
MOVW R5,
MOVT R5,
LDR R6, [R5]
@ R0 is SP of process being switched out
STR R0, [R6]
_context_restore:
@ k_curr_task = k_next_task;
MOVW R1,
MOVT R1,
LDR R2, [R1]
STR R2, [R5]
@ R0 = k_next_task->sp
LDR R0, [R2]
@ restore R4 - R11
LDMFD R0!, {R4 - R11}
@ restore EXC_RETURN
LDMFD R0!, {LR}
@ is it extended frame?
TST LR,
IT EQ
VLDMIAEQ R0!, {S16 - S31}
@ Load PSP with new process SP
MSR PSP, R0
CPSIE I
@ R0-R3, R12, LR, PC, xPSR restored automatically here
@ S0 - S16, FPSCR restored automatically here if FPCA = 1
BX LR
.end
3.3 系统心跳
在platform/vendor_bsp/st/CMSIS/Core/Include/core_cm4.h文件里,已经实现了通用的系统心跳配置函数,这个也是不需要我们去写的。配置系统心跳的相关内容也不需要我们管,在TinyOS启动内核时会初始化系统心跳。
/**
\brief System Tick Configuration
\details Initializes the System Timer and its interrupt, and starts the System Tick Timer.
Counter is in free running mode to generate periodic interrupts.
\param [in] ticks Number of ticks between two interrupts.
\return 0 Function succeeded.
\return 1 Function failed.
\note When the variable <b>__Vendor_SysTickConfig</b> is set to 1, then the
function <b>SysTick_Config</b> is not included. In this case, the file <b><i>device</i>.h</b>
must contain a vendor-specific implementation of this function.
*/
__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 Priority for Systick 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 IRQ and SysTick Timer */
return (0UL); /* Function successful */
}
我们只需要在bsp里把SysTick_Handler的实现函数修改一下就可以了。
void SysTick_Handler(void)
{
if (tos_knl_is_running())
{
tos_knl_irq_enter();
tos_tick_handler();
tos_knl_irq_leave();
}
}
最后,我们在main.c文件里创建两个调试线程
#define APPLICATION_TASK_STK_SIZE 1024
k_task_t application_task;
__aligned(4) uint8_t application_task_stk[APPLICATION_TASK_STK_SIZE];
k_task_t application_task1;
__aligned(4) uint8_t application_task1_stk[APPLICATION_TASK_STK_SIZE];
void application_entry(void *arg) {
while (1) {
tos_task_delay(2500);
printf("task1 running!\r\n");
};
}
void application_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) {
}
}
然后就编译、烧录,最后看到串口欢快的跳起线程里的输出内容啦。