Cortex-M移植
PRIMASK寄存器
PRIMASK寄存器为1位宽的中断屏蔽寄存器。在置位时,它会阻止不可屏蔽中断(NMI)和HardFault异常之外的所有异常(包括中断)。实际上,它是将当前异常优先级提升为0,这也是可编程异常/中断的最高优先级。
FAULTMASK寄存器
FAULTMASK与PRIMASK相类似,但同时它能屏蔽HardFault异常,它实际上是将异常优先级提升到了-1。
程序状态寄存器(xPSR)
- 应用PSR(APSR)
- 执行PSR(EPSR)
- 中断PSR(IPSR)
GE在Cortex-M4等ARMv7E-M处理器中存在,在Cortex-M3处理器中则不可用。
- N:负标志
- Z:零标志
- C:进位标志
- V:溢出标志
- Q:饱和标志
- GE:大于或等于标志
- ICI/IT:IF_THEN指令状态位用于条件执行
- T:THUMB状态,总是1,清除此位会引起错误异常
- 异常编号:标识处理器正在处理的异常
中断向量表
Cortex-M系列处理器的中断向量表位于0x00000000,但Cortex-M3/4系列提供了Vector table offset register(SCB_VTOR),所以,中断向量表的位置位于0x00000000 + SCB_VTOR。
异常相关指令
- CPSIE I:使能中断
- CPSID I:禁止中断(设置PRIMASK),NMI和HardFault不受影响
- CPSIE F
- CPSID F
移植过程
在嵌入式领域有多种不同CPU架构,例如Cortex-M、ARM920T、MIPS32、RISC-V等等。
为了使RTT能够在不同CPU架构的芯片上运行,RTT提供了一个libcpu抽象层来适配不同的CPU架构。libcpu层向上对内核提供统一的接口,包括全局中断的开关,线程栈的初始化,上下文切换等。
RTT的libcpu抽象层向下提供了一套统一的CPU架构移植接口,这部分接口包含了全局中断开关函数、时钟节拍的配置和中断函数、Cache 等等内容。下表是 CPU 架构移植需要实现的接口和变量。
要对CPU进行移植,只需实现上述接口。
rt_hw_interrupt_disable PROC
EXPORT rt_hw_interrupt_disable
MRS r0,PRIMASK
CPSID I
BX LR
ENDP
rt_hw_interrupt_enable PROC
EXPORT rt_hw_interrupt_enable
MSR PRIMASK, r0
BX LR
ENDP
实现线程栈初始化
在动态创建线程和初始化线程的时候,会使用到内部的线程初始化函数_rt_thread_init(),_rt_thread_init()函数会调用栈初始化函数rt_hw_stack_init(),在栈初始化函数里会手动构造一个上下文内容,这个上下文内容被作为每个线程第一次执行的初始值。
struct exception_stack_frame
{
rt_uint32_t r0;
rt_uint32_t r1;
rt_uint32_t r2;
rt_uint32_t r3;
rt_uint32_t r12;
rt_uint32_t lr;
rt_uint32_t pc;
rt_uint32_t psr;
};
struct stack_frame
{
/* r4 ~ r11 register */
rt_uint32_t r4;
rt_uint32_t r5;
rt_uint32_t r6;
rt_uint32_t r7;
rt_uint32_t r8;
rt_uint32_t r9;
rt_uint32_t r10;
rt_uint32_t r11;
struct exception_stack_frame exception_stack_frame;
};
这段代码定义了两个结构体:exception_stack_frame 和 stack_frame。这些结构体的目的是为了在嵌入式系统中有效地管理和保存堆栈信息。
- exception_stack_frame结构体包含了在发生异常时需要保存的寄存器的值,这些寄存器包括r0到psr,分别是 ARM Cortex-M 架构中的通用寄存器和程序状态寄存器。这些寄存器保存了在异常发生时的现场信息,对于异常处理和调试非常重要。
- stack_frame 结构体是嵌套在其中的,它除了包含了用于存储通用寄存器的值的字段外,还包含了一个 exception_stack_frame 结构体的实例。这样设计的目的是为了在发生异常时,能够将 exception_stack_frame 结构体中的内容一并保存到 stack_frame 中,以便于整个堆栈帧的管理和处理。
rt_uint8_t *rt_hw_stack_init(void *tentry, void *parameter, rt_uint8_t *stack_addr, void *texit)
{
struct stack_frame *stack_frame;
rt_uint8_t *stk;
unsigned long i;
stk = stack_addr + sizeof(rt_uint32_t);
stk = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8);
stk -= sizeof(struct stack_frame);
stack_frame = (struct stack_frame *)stk;
/*初始化所有寄存器*/
for(i=0; i<sizeof(struct stack_frame)/sizeof(rt_uint32_t); i++){
((rt_uint32_t *)stack_frame)[i] = 0xdeadbeef;
}
stack_frame->exception_stack_frame.r0 = (unsigned long)parameter;
/* 其他参数寄存器初始化为 0 */
stack_frame->exception_stack_frame.r1 = 0; /* r1 */
stack_frame->exception_stack_frame.r2 = 0; /* r2 */
stack_frame->exception_stack_frame.r3 = 0; /* r3 */
/* 将 IP(Intra-Procedure-call scratch register.) 设置为 0 */
stack_frame->exception_stack_frame.r12 = 0; /* r12 */
stack_frame->exception_stack_frame.lr = (unsigned long)texit;
stack_frame->exception_stack_frame.pc = (unsigned long)tentry;
stack_frame->exception_stack_frame.psr = 0x01000000L;
return stk;
}
实现上下文切换
在Cortex-M里面上下文切换都是统一使用PendSV异常来完成。
为了能适应不同的CPU架构,RTT的libcpu抽象层需要实现三个线程相关的函数:
- rt_hw_context_switch_to():没有来源线程,切换到目标线程,在调度器启动第一个线程的时候被调用。
- rt_hw_context_switch():在线程环境下,从当前线程切换到目标线程。
- rt_hw_context_switch_interrupt():在中断环境下,从当前线程切换到目标线程。
PendSV_Handler
产生PendSV异常时,Cortex-M系列处理器硬件会自动将from线程的PSR、PC、LR、R12、R3-R0压栈,因此在PendSV_Handler中,我们需要把from线程的R11-R4压栈,并把to线程的R11-R4弹出。
修改PSP为to线程的栈地址,在退出PendSV中断时,硬件会自动弹出to线程的R3-R0,R12、LR、PC、PSR寄存器。
实现时钟节拍
要实现时间片轮转调度、软定时器、,必须要保证 rt_tick_increase() 被周期性调用。在 Cortex-M 系列 MCU 中,可以使用系统滴答定时器来对其周期性调用。
void SysTick_Handler(void)
{
/* enter interrupt */
rt_interrupt_enter();
rt_tick_increase();
/* leave interrupt */
rt_interrupt_leave();
}