注:测试发现,打印浮点数会出现硬件错误
在实现多个任务之间的切换之前,可以先通过实现两个固定任务切换,作为一个过度,本文将介绍在STM32中,如何通过操作内核,来实现两个固定任务之间的切换。
正如前文所提及,实现任务切换中操作内核寄存器需要使用汇编程序,所以我们需要掌握如何在自己使用的IDE中嵌入汇编语句,本文使用的编译器为MDK5默认编译器v5版本:
嵌入汇编的方式十分简单,只需要使用_asm
来声明一个函数即可,如:
//程序清单3.1
__asm void BUD_ContextSwitch(stack_reg_t* pstr_cur_task_stack_reg_adrr,stack_reg_t* pstr_next_task_stack_reg_adrr)
{
STMIA R0!,{R4 - R12};
STMIA R0!,{R13};
STMIA R0!,{R14};
MRS R2,XPSR;
STMIA R0,{R2};
LDMIA R1!,{R4 - R12};
LDMIA R1,{R13};
ADD R1,#8;
LDMIA R1,{R0};
MSR XPSR,R0;
SUB R1,#4;
LDMIA R1,{PC};
}
程序清单3.1
是一个示例函数,其详细含义在后文解释。可以发现,我们操作了寄存器,R0,R1,R4,R12,XPSR 等等。注意,在不同的编译器中,嵌入汇编的方式可能不太一样,所以程序员需要选择符合所用环境的方式。
可以用一段简单的程序,来测试程序的汇编指令正常与否,在 AAPCS 标准中, R0和R1寄存器分别存放函数的第1个参数和第2个参数,固可通过使用汇编(ADD
)来改变参数值的方法来验证:
//程序清单3.2
__asm void test_asm_func(int para1,int para2)
{
ADD R1,#1 //将R1的值加1,# 符号表示数值
ADD R0,#1 //将R0的值加1
ADD R1,#2
ADD R0,#2
ADD R1,#3
ADD R0,#3
ADD R1,#4
ADD R0,#4
}
void main(void)
{
···
test_asm_func(0,1); //调用测试函数,并传入参数0和1
···
}
程序清单3.2
不断的累加R0和R1的值,使得我们可以在硬件仿真时,通过观察M3内核寄存器的窗口,了解R0和R1的值的变化,同时,还需要注意,在第一句汇编函数ADD R1,#1
没有运行之前,R0和R1的值。
上图表明,当程序停留在69行,即第一句汇编指令即将运行时候,R0和R1的值分别是0和1,这正是函数传入的参数,接着使用单步执行,来观察值的变化:
可以发现,程序运行正常。接下来可以进入正式的程序编程中,这种测试方法通用合适其它的汇编指令。
掌握了汇编语句的使用和测试以后,就可以进入本文的主题部分——函数的切换。
固定任务的切换代码量并不大,本文创建BudOS.c/h
,两个文件来存放所有程序,并将它们存放到BudOS_core
文件夹:
在M3内核中,PC(R15)寄存器存放值为内核当前运行的程序的地址,所以当程序改变PC值时,程序的流程也被改变,但是,在改变PC指针之前,一定要做好现场的保护,否则程序将会出错。
保护现场,即使保存当前M3内核各个寄存器的值,然后切换到另一个任务中,当切换回来时,将这些寄存器的值恢复即可,所以程序需要在内存中申请一点空间,来存放这些数据,这是每一个任务都需要的,可以使用数组的方式来申请一块内存:
#define TASK_STACK_SIZE (64) //堆栈的大小
__align(4) uint32_t task1_stack[TASK_STACK_SIZE]; //任务1的堆栈区
__align(4) uint32_t task2_stack[TASK_STACK_SIZE];
通常,不同的任务的堆栈不太一样,比如一些任务如果申请较大的临时数组,那么它就需要更大的内存。
任务控制块是一个数据结构,每一个任务都应该拥有一个任务控制块,以便程序管理各个任务。本文由于任务简单,所以目前任务控制块只有一个结构体——stack_reg
,它的作用是保存内核寄存器的值,即是,当程序需要从当前任务切换到另一个任务时候,当前内核寄存器的值将存储到这个结构体中,以确保程序切回后,可正常运行。
//程序清单3.3
//内核寄存器结构体
typedef struct stack_reg
{
uint32_t ui_R4;
uint32_t ui_R5;
uint32_t ui_R6;
uint32_t ui_R7;
uint32_t ui_R8;
uint32_t ui_R9;
uint32_t ui_R10;
uint32_t ui_R11;
uint32_t ui_R12;
uint32_t ui_R13;
uint32_t ui_R14;
uint32_t ui_xPSR;
}stack_reg_t;
//任务控制块结构体
typedef struct bud_tcb
{
stack_reg_t stack_reg;
}bud_tcb_t;
在程序清单3.3
中的stack_reg
结构中,R0~R3
并不需要备份,这是由于在 AAPCS标准 中,这4个寄存器用以存放函数的参数,所以在发生函数调用时,父函数会自动备份。我们将任务控制块,存放在任务栈区的顶部: