任务切换需要用到pendsv和svc中断,svc用于创建任务时的任务切换,汇编程序:

;===============================================================
; Cortex-M 任务调度器 - 汇编部分
; 使用 PendSV 异常进行上下文切换
;===============================================================

AREA  |.test|, CODE, READONLY
	THUMB           ; 使用 Thumb 指令集
	PRESERVE8       ; 保持 8 字节对齐
	
	; 导入外部变量和函数
	IMPORT current      ; 当前运行任务的索引
	IMPORT taskSP       ; 任务堆栈指针数组
	IMPORT scheduler_next  ; 调度函数,返回下一个任务索引
	
	; 导出处理程序供链接器使用
	EXPORT PendSV_Handler
	EXPORT SVC_Handler
	
;===============================================================
; PendSV 中断处理程序
; 用于任务切换
;===============================================================
PendSV_Handler
	; 保存当前任务上下文
	MRS R0, PSP         ; 获取当前任务的堆栈指针
	ISB                ; 指令同步屏障
	STMDB R0!, {R4-R11} ; 将 R4-R11 保存到当前任务堆栈(自动递减)
	
	; 保存当前任务的堆栈指针
	LDR R1, =current   ; 加载 current 变量的地址
	LDR R2, [R1]       ; 获取当前任务索引
	LDR R3, =taskSP    ; 加载 taskSP 数组的地址
	STR R0, [R3, R2, LSL #2] ; 保存 PSP 到 taskSP[current]
	
	; 调用调度器选择下一个任务
	PUSH {LR}          ; 保存返回地址
	BL scheduler_next  ; 调用调度器,返回下一个任务索引到 R0
	POP {LR}           ; 恢复返回地址
	
	; 更新当前任务索引
	LDR R1, =current
	STR R0, [R1]       ; 将返回值存储到 current
	
	; 恢复下一个任务的上下文
	LDR R3, =taskSP    ; 重新加载 taskSP 数组地址
	LDR R0, [R3, R0, LSL #2] ; 获取下一个任务的堆栈指针
	LDMIA R0!, {R4-R11} ; 从新任务堆栈恢复 R4-R11(自动递增)
	MSR PSP, R0        ; 更新 PSP 为新任务的堆栈指针
	
	; 准备返回
	ORR LR, LR, #0x04  ; 确保返回后使用 PSP
	BX LR              ; 返回,自动从堆栈恢复剩余寄存器

;===============================================================
; SVC 中断处理程序
; 用于第一次启动调度器
;===============================================================
SVC_Handler
	; 加载第一个任务的上下文
	LDR R0, =taskSP    ; 加载 taskSP 数组地址
	LDR R1, =current
	LDR R1, [R1]       ; 获取当前任务索引(应为 0)
	LDR R0, [R0, R1, LSL #2] ; 获取第一个任务的堆栈指针
	
	; 恢复初始上下文
	LDMIA R0!, {R4-R11} ; 恢复 R4-R11
	MSR PSP, R0        ; 设置 PSP 为任务堆栈指针
	
	; 切换到线程模式并使用 PSP
	MOVS R0, #0x02     ; CONTROL[1] = 1,使用 PSP
	MSR CONTROL, R0
	ISB               ; 确保指令同步
	
	; 准备返回
	LDR R0, =0xFFFFFFFD  ; 使用 PSP,从线程模式返回
	BX R0              ; 返回,开始执行第一个任务

	END

;===============================================================
; Cortex-M 任务调度器 - C 语言部分
;===============================================================

#include <stdint.h>
#include <stdio.h>

; 系统配置常量
#define MAX_TASKS 4     ; 最大任务数
#define STACK_SIZE 256  ; 每个任务的堆栈大小(字)

; 全局变量
uint32_t current = 0;   ; 当前运行任务的索引
uint32_t numTasks = 0;  ; 已创建的任务数

; 对齐的任务堆栈指针数组和任务堆栈
__align(8) uint32_t *taskSP[MAX_TASKS];  ; 任务堆栈指针数组(8字节对齐)
__align(8) uint32_t taskStack[MAX_TASKS][STACK_SIZE];  ; 任务堆栈空间

;===============================================================
; 简单的轮转调度器
; 返回:下一个任务索引
;===============================================================
uint32_t scheduler_next(void)
{
	return (current + 1) % numTasks;  ; 轮转调度
}

;===============================================================
; 任务退出函数
;===============================================================
void task_exit(void)
{
	while(1);  ; 死循环,实际应用中应有更完善的退出处理
}

;===============================================================
; 创建新任务
; 参数:idx - 任务索引,entry - 任务入口函数,args - 传递给任务的参数
;===============================================================
void create_task(uint32_t idx, void (*entry)(void *), void *args)
{
	uint32_t *sp = &taskStack[idx][STACK_SIZE];  ; 获取堆栈顶部
	uint32_t dummy = idx + 1;  ; 调试用的虚拟值,便于识别
	
	; 在堆栈上构建初始上下文(模拟异常进入时的堆栈帧)
	*(--sp) = 0x01000000;        ; xPSR: Thumb 模式,默认状态
	*(--sp) = (uint32_t)entry;   ; PC: 任务入口点
	*(--sp) = (uint32_t)task_exit;  ; LR: 任务退出处理函数
	*(--sp) = dummy;             ; R12
	*(--sp) = dummy;             ; R3
	*(--sp) = dummy;             ; R2
	*(--sp) = dummy;             ; R1
	*(--sp) = (uint32_t)args;    ; R0: 任务参数

	; 保存额外寄存器(R4-R11将由调度器管理)
	*(--sp) = dummy;             ; R11
	*(--sp) = dummy;             ; R10
	*(--sp) = dummy;             ; R9
	*(--sp) = dummy;             ; R8
	*(--sp) = dummy;             ; R7
	*(--sp) = dummy;             ; R6
	*(--sp) = dummy;             ; R5
	*(--sp) = dummy;             ; R4
	
	taskSP[idx] = sp;  ; 保存任务堆栈指针
	
	if(idx >= numTasks) numTasks = idx + 1;  ; 更新任务计数
}

;===============================================================
; 触发任务切换
;===============================================================
inline void yield(void)
{
	SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;  ; 设置 PendSV 挂起位
	__DSB();  ; 数据同步屏障
	__ISB();  ; 指令同步屏障
}

;===============================================================
; SysTick 中断处理程序
;===============================================================
void SysTick_Handler(void)
{
	yield();  ; 每次时钟滴答触发任务切换
}

;===============================================================
; 启动调度器
;===============================================================
void start_schedule(void)
{
	; 配置使用双堆栈(MSP 和 PSP)
	SCB->CCR |= SCB_CCR_STKALIGN_Msk;  ; 启用堆栈双字对齐
	
	; 设置中断优先级:PendSV 最低,SysTick 次低
	NVIC_SetPriority(PendSV_IRQn, (1 << __NVIC_PRIO_BITS) - 1);
	NVIC_SetPriority(SysTick_IRQn, (1 << __NVIC_PRIO_BITS) - 2);
	
	; 配置 SysTick 为 1ms 中断
	SysTick_Config(SystemCoreClock / 1000);
	
	; 触发 SVC 异常,开始第一个任务
	__asm volatile("svc 0");
}

;===============================================================
; 示例任务1
;===============================================================
void task1(void *args)
{
	while(1)
	{
		printf("run in task1 \r\n");
	}
}

;===============================================================
; 示例任务2
;===============================================================
void task2(void *args)
{
	while(1)
	{
		printf("run in task2 \r\n");
	}
}

;===============================================================
; 主函数
;===============================================================
int main(void)
{
	; 创建两个任务
	create_task(0, task1, NULL);
	create_task(1, task2, NULL);
	
	; 启动调度器
	start_schedule();
	
	while(1);  ; 主循环,不应执行到这里
}



更多推荐