FreeRtos的移植

一.前言

之前移植过freertos操作系统,涉及到计算机和操作系统的底层,特此详细记录下这些知识点。至于具体的详细步骤,就不给出了,网上有很多参考,这里只分析“重点”。笔者的cpu内核是cotex-M3.

二.3个重点函数

  • vPortSVCHandler():加载第一个任务的中断处理函数。
  • xPortPendSVHandler():实现任务切换的中断处理函数(保存和恢复任务的上下文)。
  • xPortSysTickHandler():给操作系统提供滴答时钟。

三.vPortSVCHandler

FreeRTOS启动调度器时,调用prvStartFirstTask,再调用SVC中断来启动第一个任务

点击查看代码
__asm void prvStartFirstTask( void )
{
	PRESERVE8

	/* Use the NVIC offset register to locate the stack. */
	ldr r0, =0xE000ED08 /* 在Cortex-M中,0xE000ED08是SCB_VTOR这个寄存器的地址,里面存放的是向量表的起始地址,即MSP的地址 */
	ldr r0, [r0]
	ldr r0, [r0]

	/* Set the msp back to the start of the stack. */
	msr msp, r0  /* 设置主堆栈指针msp的值 *
	/* Globally enable interrupts. *//* 使能全局中断 */
	cpsie i
	cpsie f
	dsb
	isb
	/* Call SVC to start the first task. *//* 调用SVC去启动第一个任务 */
	svc 0/*产生系统调用,服务号 0表示 SVC 中断,接下来将会执行 SVC 中断服务函数*/
	nop
	nop
}

SVC中断被触发,PortSVCHandler加载第一个任务

点击查看代码
PRIVILEGED_DATA TCB_t * volatile pxCurrentTCB = NULL;/* 当前正在运行的任务的任务控制块指针,默认初始化为NULL,defined in task.c*/

__asm void vPortSVCHandler( void )
{
	PRESERVE8

	ldr	r3, =pxCurrentTCB	//r3=&pxCurrentTCB,即r3指向当前执行任务的TCB指针所在地址
	ldr r1, [r3]			//r1=*r3=pxCurrentTCB,既让r1指向当前任务的TCB
	ldr r0, [r1]			//r0=*r1=pxTopOfStack,即让r0执行当前任务栈顶
	/* Pop the core registers. */ //将当前任务栈内容pop,保存入cpu寄存器,注意序号小的寄存器会先被pop,所以pop顺序:r4...r10,r11
	ldmia r0!, {r4-r11}		/* Pop the registers that are not automatically saved on exception entry and the critical nesting count. 
	msr psp, r0				/* Restore the task stack pointer. *// / 将当前任务栈顶赋给psp, 即psp = pxTopOfStack	
	isb                     // 指令同步隔离,确保之前的指令都已执行完毕
	mov r0, #0              // r0清0,用于关中断
	msr	basepri, r0         //设置 basepri 寄存器的值为 0,即关闭所有中断。basepri 是一个中断屏蔽寄存器,大于等于此寄存器值的中断都将被屏蔽,但如果设置成0,则不关闭任何中断
	orr r14, #0xd
	bx r14                  ///任务上下文加载完毕,中断执行结束,返回用户线程,在ARM中,使用r14(LR寄存器)来保存子程序的返回地址(即上一个程序的地址)
}

四.xPortPendSVHandler

任务调度中断,被切换出去的任务,一部分参数由硬件自动保存,一部分手动保存

点击查看代码
__asm void xPortPendSVHandler( void )
{
	extern uxCriticalNesting;
	extern pxCurrentTCB;
	extern vTaskSwitchContext;

	PRESERVE8
        
	/*r0=psp, 进入PendSV中断时,上个任务环境即		     
	xPSR,PC,R14,R12,R3,R2,R1,R0这些将自动保存入任务栈,
	剩下R4-R11需要手动保存,同时PSP将自动更新(在更新之前 PSP 指向任务栈的栈顶),
	此时 PSP是"上文"任务的堆栈指针*/
	mrs r0, psp 
	isb			//确保之前指令已执行
	/* Get the location of the current TCB. */
	ldr	r3, =pxCurrentTCB	//r3=&pxCurrentTCB
	ldr	r2, [r3]			//r2=*r3=pxCurrentTCB

	/* Save the core registers. */
	stmdb r0!, {r4-r11}	//将cpu寄存器保存入"上文"任务栈,注意push总是先push序号大的,因此push顺序:r11,r10....r4

	/* Save the new top of stack into the first member of the TCB. */
	str r0, [r2]	//*r2=r0 => pxTopOfStack=p0, 更新"上文"任务的栈顶

	stmdb sp!, {r0, r3}	//入栈栈顶指针和pxCurrentTCB,这个栈的指针是MSP,注意顺序:r3,r0
    /* 至此,上下文切换的"上文"环境保存完成 */
    
    //关中断,高于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断都将被屏蔽,configMAX_SYSCALL_INTERRUPT_PRIORITY的值在FreeRTOSConfig.h定义
	mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
	msr basepri, r0
        
	dsb	//数据隔离,同步之前对msr的操作
	isb	//指令隔离,确保之前所有指令已执行完毕,之后的指令使用的是正确的basepri配置
        
	bl vTaskSwitchContext	//跳转到vTaskSwitchContext函数去执行,pxCurrentTCB将被更改指向下一个任务
    //开中断
	mov r0, #0
	msr basepri, r0
   
	ldmia sp!, {r0, r3}	//从MSP栈加载r0和r3,此时r3已经指向新任务pxCurrentTCB的地址值,注意pop顺序:r0,r3
	
    /* 以下为上下文切换的"下文"环境切换 */
	/* The first item in pxCurrentTCB is the task top of stack. */
	ldr r1, [r3]	//r1=*r3=pxCurrentTCB,即新任务的TCB
	ldr r0, [r1]	//r0=*r1=pxTopOfStack,即新任务的栈顶指针

	/* Pop the core registers. */
	ldmia r0!, {r4-r11}	//将新任务的任务栈数据加载入cpu寄存器r4-r11
    
    /* 更新psp的值,等PendSV退出时,会以psp作为基地址,将任务栈中剩下的内容自动加载到CPU寄存器 */
    /* 剩下的内容包括: xPSR、PC、LR、r12、r3、r2、r1、r0 */
	msr psp, r0
	isb
	bx r14	//中断结束返回
}

五.xPortSysTickHandler

xPortSysTickHandler是一个定时中断服务函数,默认为1ms触发一次。在FreeRTOS中,它被用作触发PendSV中断,实际的任务切换在PendSV_Handler函数中执行。
移植参考如下:

点击查看代码
//xTaskIncrementTick():
/*

增加系统时钟计数:每次调用时,xTickCount(系统时钟计数器)会增加1。

处理延迟任务:检查是否有任务因为等待时间到期而需要从延迟列表(pxDelayedTaskList)中移除,并将其添加到就绪列表中。

检查任务是否应该被解除阻塞:如果当前的系统时钟计数(xTickCount)大于或等于下一个任务解除阻塞的时间(xNextTaskUnblockTime),那么会遍历延迟列表,找到所有到期的任务,并将其状态从阻塞状态变为就绪状态。

处理时间片轮转:如果开启了时间片轮转(configUSE_TIME_SLICING),并且当前优先级的任务列表中有多个任务,那么可能需要进行任务切换。

调用应用定义的时钟钩子函数:如果定义了configUSE_TICK_HOOK,那么会调用vApplicationTickHook()函数,允许用户在每个时钟节拍时执行特定的操作。

检查是否需要任务切换:如果由于任务优先级变化或者时间片耗尽,需要进行任务切换,那么xSwitchRequired会被设置为pdTRUE,表示需要进行任务切换

*/
void xPortSysTickHandler( void )
{
uint32_t ulPreviousMask;

	ulPreviousMask = portSET_INTERRUPT_MASK_FROM_ISR();
	{
		/* Increment the RTOS tick. */
		if( xTaskIncrementTick() != pdFALSE )
		{
			/* Pend a context switch. */
			*(portNVIC_INT_CTRL) = portNVIC_PENDSVSET;
		}
	}
	portCLEAR_INTERRUPT_MASK_FROM_ISR( ulPreviousMask );
}

void SysTick_Handler(void)
{
	lv_tick_inc(1);
	swm_inctick();
	if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
	{
		xPortSysTickHandler();
	}
	
}
posted @   Charles_hui  阅读(129)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示