FreeRTO之任务切换

 


xTaskCreateStatic(  )
	rvInitialiseNewTask( )
        pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );         
		pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
		
StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
	pxTopOfStack--;
	*pxTopOfStack = portINITIAL_XPSR;	                                    /* xPSR的bit24必须置1 */
	pxTopOfStack--;
	*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;	/* PC,即任务入口函数 */
	pxTopOfStack--;
	*pxTopOfStack = ( StackType_t ) prvTaskExitError;	                    /* LR,函数返回地址 */
	pxTopOfStack -= 5;	/* R12, R3, R2 and R1 默认初始化为0 */
	*pxTopOfStack = ( StackType_t ) pvParameters;	                        /* R0,任务形参 */
	pxTopOfStack -= 8;	/* R11, R10, R9, R8, R7, R6, R5 and R4默认初始化为0 */
    return pxTopOfStack;
}

pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 )使pxTopOfStack指向高地址处, 

函数pxPortInitialiseStack()就是给这些寄存器赋值,该函数返回后,pxTopOfStack指向低地址处

因为进行任务切换时会调用到vPortSVCHandler中断处理函数,在该函数中,pxCurrentTCB指向rvInitialiseNewTask()函数中的pxNewTCB,

ldmia r0!, {r4-r11}就是将pxPortInitialiseStack()函数里面pxTopOfStack指向的内存中的值赋给r4-r11

ldr    r3, =pxCurrentTCB  
 ldr r1, [r3]        
 ldr r0, [r1] 将pxCurrentTCB 的第一个成员即pxTopOfStack赋给r0,由于pxTopOfStack是指向低地址处,ldmia r0!, {r4-r11}是先将内存中的值加载到寄存器,再使r0递增,

这一指令执行完后,r0其实指向了“自己”

msr psp, r0使栈指针指向r0

bx r14跳转指令,调到PC所指向的地方,在跳转之前,硬件会自动进行出栈操作,将栈中剩余的值依次弹出给r0 r1 r2 r3 r12 r14 r15,r15即PC,在函数pxPortInitialiseStack()中,

*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;这是任务的入口,在退栈时,这个值会赋给PC,当指向bx r14跳转指令时,就会跳转到任务入口处,

__asm void vPortSVCHandler( void )
{
    extern pxCurrentTCB;
    
    PRESERVE8

	ldr	r3, =pxCurrentTCB	/* 加载pxCurrentTCB的地址到r3 */
	ldr r1, [r3]			/* 加载pxCurrentTCB到r1 */
	ldr r0, [r1]			/* 加载pxCurrentTCB指向的值到r0,目前r0的值等于第一个任务堆栈的栈顶 */
	ldmia r0!, {r4-r11}		/* 以r0为基地址,将栈里面的内容加载到r4~r11寄存器,同时r0会递增 */
	msr psp, r0				/* 将r0的值,即任务的栈指针更新到psp */
	isb
	mov r0, #0              /* 设置r0的值为0 */
	msr	basepri, r0         /* 设置basepri寄存器的值为0,即所有的中断都没有被屏蔽 */
	orr r14, #0xd           /* 当从SVC中断服务退出前,通过向r14寄存器最后4位按位或上0x0D,
                               使得硬件在退出时使用进程堆栈指针PSP完成出栈操作并返回后进入线程模式、返回Thumb状态 */
    
	bx r14                  /* 异常返回,这个时候栈中的剩下内容将会自动加载到CPU寄存器:
                               xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)
                               同时PSP的值也将更新,即指向任务栈的栈顶 */
}

 

程序如何进入vPortSVCHandler中断处理函数:svc 0 就是触发SVC中断,从而进入vPortSVCHandler()中断处理函数,从而跳转到任务中去执行

vTaskStartScheduler()
	xPortStartScheduler()
		prvStartFirstTask()
			prvStartFirstTask()
			
__asm void prvStartFirstTask( void )
{
	PRESERVE8

	/* 在Cortex-M中,0xE000ED08是SCB_VTOR这个寄存器的地址,
       里面存放的是向量表的起始地址,即MSP的地址 */
	ldr r0, =0xE000ED08
	ldr r0, [r0]
	ldr r0, [r0]

	/* 设置主堆栈指针msp的值 */
	msr msp, r0
    
	/* 使能全局中断 */
	cpsie i
	cpsie f
	dsb
	isb
	
    /* 调用SVC去启动第一个任务 */
	svc 0  
	nop
	nop
}

在任务中如何切换到另一个任务:

这里以手动切换为例,假设自此时在执行task1(),portYIELD()会产生一个PendSV中断,从而进入xPortPendSVHandler()中断处理函数,在该函数中bl vTaskSwitchContext指令是跳转到vTaskSwitchContext()函数中去更改pxCurrentTCB所指向的任务控制块,从而改变r3的指向,任务控制块的pxTopOfStack里设置了任务的入口地址,xPortPendSVHandler()中断函数退栈时,会将另一个任务的入口地址赋值给PC,从而切换到另一个任务去执行

void Task1_Entry( )
{
	for( ;; )
	{
        portYIELD();
	}
}

#define portYIELD()																\
{																				\
	/* 触发PendSV,产生上下文切换 */								                \
	portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;								\
	__dsb( portSY_FULL_READ_WRITE );											\
	__isb( portSY_FULL_READ_WRITE );											\
}

__asm void xPortPendSVHandler( void )
{
	extern pxCurrentTCB;
	extern vTaskSwitchContext;

	PRESERVE8

	mrs r0, psp
	isb

	ldr	r3, =pxCurrentTCB		/* 加载pxCurrentTCB的地址到r3 */
	ldr	r2, [r3]                /* 加载pxCurrentTCB到r2 */

	stmdb r0!, {r4-r11}			/* 将CPU寄存器r4~r11的值存储到r0指向的地址 */
	str r0, [r2]                /* 将任务栈的新的栈顶指针存储到当前任务TCB的第一个成员,即栈顶指针 */				
                               
	stmdb sp!, {r3, r14}        /* 将R3和R14临时压入堆栈,因为即将调用函数vTaskSwitchContext,
                                  调用函数时,返回地址自动保存到R14中,所以一旦调用发生,R14的值会被覆盖,因此需要入栈保护;
                                  R3保存的当前激活的任务TCB指针(pxCurrentTCB)地址,函数调用后会用到,因此也要入栈保护 */
	mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY    /* 进入临界段 */
	msr basepri, r0
	dsb
	isb
	bl vTaskSwitchContext       /* 调用函数vTaskSwitchContext,寻找新的任务运行,通过使变量pxCurrentTCB指向新的任务来实现任务切换 */ 
	mov r0, #0                  /* 退出临界段 */
	msr basepri, r0
	ldmia sp!, {r3, r14}        /* 恢复r3和r14 */

	ldr r1, [r3]
	ldr r0, [r1] 				/* 当前激活的任务TCB第一项保存了任务堆栈的栈顶,现在栈顶值存入R0*/
	ldmia r0!, {r4-r11}			/* 出栈 */
	msr psp, r0
	isb
	bx r14                      /* 异常发生时,R14中保存异常返回标志,包括返回后进入线程模式还是处理器模式、
                                   使用PSP堆栈指针还是MSP堆栈指针,当调用 bx r14指令后,硬件会知道要从异常返回,
                                   然后出栈,这个时候堆栈指针PSP已经指向了新任务堆栈的正确位置,
                                   当新任务的运行地址被出栈到PC寄存器后,新的任务也会被执行。*/
	nop
}
void vTaskSwitchContext( void )
{    
    /* 两个任务轮流切换 */
    if( pxCurrentTCB == &Task1TCB )
    {
        pxCurrentTCB = &Task2TCB;
    }
    else
    {
        pxCurrentTCB = &Task1TCB;
    }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MSP-EXP432E401Y Development kit 是德州仪器(TI)推出的一款基于 ARM Cortex-M4F 内核的微控制器开发板,而 FreeRTOS 是一款流行的嵌入式系统实时操作系统(RTOS)。 在 FreeRTOS 环境下,串口回调模式下使用 UART_Callback 需要先进行以下几个步骤: 1. 在 FreeRTOS 中创建一个任务(Task),该任务将被用来接收串口数据并进行处理。 2. 在任务中调用 UART_Callback 函数,该函数将会在串口接收到数据时被调用。 3. 在 UART_Callback 函数中进行数据处理,并将处理结果返回给主程序。 下面是一个示例代码: ```c #include "FreeRTOS.h" #include "task.h" #include "driverlib.h" #define UART_BUFFER_SIZE 256 TaskHandle_t xSerialTaskHandle = NULL; uint8_t ucSerialBuffer[UART_BUFFER_SIZE]; uint32_t ulSerialIndex = 0; void vSerialTask(void *pvParameters) { while (1) { // 等待串口数据 ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // 处理串口数据 if (ulSerialIndex > 0) { // ... } // 清空缓存 ulSerialIndex = 0; memset(ucSerialBuffer, 0, UART_BUFFER_SIZE); } } void UART_Callback(uint32_t ulStatus) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; // 读取数据 while (UARTCharsAvail(EUSCI_A0_BASE)) { ucSerialBuffer[ulSerialIndex++] = UARTCharGetNonBlocking(EUSCI_A0_BASE); // 数据已满,通知任务处理 if (ulSerialIndex >= UART_BUFFER_SIZE) { xTaskNotifyFromISR(xSerialTaskHandle, 0, eNoAction, &xHigherPriorityTaskWoken); break; } } // 通知任务处理 if (ulStatus & EUSCI_A_UART_OE_INTERRUPT_FLAG) { xTaskNotifyFromISR(xSerialTaskHandle, 0, eNoAction, &xHigherPriorityTaskWoken); } portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } void main(void) { // 初始化串口 MAP_UART_initModule(EUSCI_A0_BASE, &(const eUSCI_UART_Config) { .selectClockSource = EUSCI_A_UART_CLOCKSOURCE_SMCLK, .clockPrescalar = 6, .firstModReg = 0, .secondModReg = 0x20, .parity = EUSCI_A_UART_NO_PARITY, .msborLsbFirst = EUSCI_A_UART_LSB_FIRST, .numberofStopBits = EUSCI_A_UART_ONE_STOP_BIT, .uartMode = EUSCI_A_UART_MODE, .overSampling = EUSCI_A_UART_OVERSAMPLING_BAUDRATE_GENERATION }); // 开启串口中断 MAP_UART_enableInterrupt(EUSCI_A0_BASE, EUSCI_A_UART_RECEIVE_INTERRUPT | EUSCI_A_UART_OE_INTERRUPT); MAP_UART_enableModule(EUSCI_A0_BASE); // 创建任务 xTaskCreate(vSerialTask, "SerialTask", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, &xSerialTaskHandle); // 启动调度器 vTaskStartScheduler(); } ``` 在上面的示例中,vSerialTask 函数是任务处理函数,UART_Callback 函数是串口回调函数。当串口接收到数据时,UART_Callback 函数会被调用,将接收到的数据存储到缓存中,并通过 xTaskNotifyFromISR 函数通知任务处理函数处理数据。任务处理函数会在接收到通知时,从缓存中读取数据并进行处理。 需要注意的是,在使用 FreeRTOS 时需要注意任务优先级的设置,以确保任务能够按照预期的方式运行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值