stm32任务切换
·
任务切换需要用到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); ; 主循环,不应执行到这里
}
更多推荐
所有评论(0)