李述铜老师讲解的深入理解ARM体系结构-Cortex-M3内核架构与编程的复习笔记
1、基本编程模式
内核寄存器:包括R0~R15和一些特殊的寄存器
R0-R12为通用寄存器
R13(堆栈指针):MSP(主堆栈指针)和PSP(进程堆栈指针)
R14(连接寄存器LR):调用一个子程序时,存储返回地址
R15(程序寄存器PC):存储当前程序的指令地址
程序状态寄存器(PSR)
PRIMASK,FAULTMASK:1位寄存器,用于屏蔽指定类型的中断和异常
BASEPRI:1位寄存器,屏蔽优先级较低的中断和异常
Control(控制寄存器):用于设置权限以及所有的栈
Cortex-M3采用Thumb-2指令,不支持ARM指令,Thumb-2支持16位和32位指令
Cortex-M3采用三级流水线:取指、译码、执行,提高指令的运行效率。
Cortex-M3采用满递减栈操作模型,压栈时先将SP减4,再将数存入SP指向的位置,即SP指向压入栈中的最后一个数据单元
2、存储模型
大端模式:按照高字节到低字节的顺序,依次存储到连续增长的存储区域内
小端模式:按照低字节到高字节的顺序,依次存储到连续增长的存储区域内
(跨平台的数据交换和处理时,必须要考虑大小端的问题,以免数据被解释出现错误)
总线为一组数据线的集合,包括数据总线、地址总线、控制总线
AHB总线:高速高性能总线,APB总线:低速低功耗总线
Cortex-M3中有两个区实现了位带操作,其中一个是 SRAM区的最低 1MB 范围,第二个则是片内外设区的最低 1MB 范围
采用存储映射IO和统一编址。每个外部设备寄存器都被分配存储地址,通过访问该地址,可以读取或写入外部设备的状态或数据
3、中断和异常处理
中断用于处理CPU内核外部各种设备产生的事件,异常是程序运行时,由于程序本身的问题而导致的各种问题,如除0、非法指令、存储访问越界
向量表:编号1-15用于系统异常,16以上用于外部中断,最多支持240个外部中断。
PendSV异常:用于实现任务切换 SysTick:用于系统定时
同步:在程序执行时与某条指令有关,事件发生时可比较准确的定位是执行到哪个位置出错
异步:与程序执行流无关,可能在程序执行任意位置时发生
异常处理流程:事件发生通知→保存现场→执行处理程序→恢复现场→继续执行
MSP指向的栈空间中保存寄存器,(下列寄存器自动保存)
HardFault(硬件故障) MemManage(内存管理故障)
BusFault(总线故障) UsageFault(使用故障)
在没有开启其他异常处理的情况下,默认进入HardFault异常中断处理函数
Hard Faults:
VECTTBL:异常处理程序执行过程中读向量表产生的Bus fault引起
FORCED:指示错误是否由Usage faults、Bus faults和Memory management faults产生
void HardFault_C_Handler (stack_frame_t * frame) {
uint32_t status = SCB->HFSR;
if (status & SCB_HFSR_VECTTBL_Msk) {
printf("BusFault on vector table rea\n");
}
if (status & SCB_HFSR_FORCED_Msk) {
printf("Forced HardFault.\n");
}
fault_show_regs(frame);
}
SCB->CFSR寄存器分为三个组:存储器管理Fault状态寄存器(MMFSR)、总线Fault状态寄存器(BFSR)、用法Fault状态寄存器(UFSR)
存储器管理Fault:
IACCVIOL:指令访问违规标志
DACCVIOL:数据访问违规标志
MUNSTKERR:出栈错误
MSTKERR:压栈错误
MMARVALID:MemManage故障地址寄存器有效标志
SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk; // 存储管理异常使能
BusFault:是由于指令或数据内存事务的内存相关故障而引起的异常。可能是由于在内存系统的总线上检测到的错误引起
SCB->SHCSR |= SCB_SHCSR_BUSFAULLTENA_Msk; // BusFault使能
UsageFault:
SCB->SHCSR |= SCB_SHCSR_USGFAULTENA_Msk; // UsageFault使能
SCB->CCR |= SCB_CCR_DIV_O_TRP_Msk | SCB_CCR_UNALIGN_TRP_Msk; // 开启除零异常和非对齐异常
系统复位后中断向量表要固定在0地址处
重定向向量表
VTOR 寄存器存放的是中断向量表的起始地址(低七位保留)
__attribute__((aligned(n))) //采用n字节对齐
extern const VECTOR_TABLE_Type __VECTOR_TABLE[240];
static unsigned int new_vectors[240] __attribute__((aligned(128)));
memcpy(new_vectors, __VECTOR_TABLE, sizeof(__VECTOR_TABLE)); // 先复制已有的中断向量表
SCB->VTOR = (unsigned int)new_vectors; // 重定向异常向量表
new_vectors[3] = (uint32_t)HardFault_NEW_Handler;
new_vectors[6] = (uint32_t) UsageFault_NEW_Handler;
NVIC是Cortex-M处理器中的中断控制器,用于管理中断
主要内容:中断的挂起与使能、优先级配置、中断与异常的屏蔽与打开、优先级配置
NVIC_SetPendingIRQ(Interrupt1_IRQn); //中断的挂起
NVIC_EnableIRQ(Interrupt1_IRQn); //中断使能
NVIC_ClearPendingIRQ(Interrupt1_IRQn); //中断清除
NMI、PendSV和系统时钟Systick也可以设置挂起请求,通过设置ICSR寄存器相应位
SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk; // 触发PendSVs
全局中断的开启和关闭
PRIMASK:用于屏蔽掉所有优先级可配置的中断或异常,即除NMI和硬件故障之外的所有异常
__disable_irq(); //关闭全局中断 __enable_irq(); //开启全局中断
FAULTMASK:屏蔽掉除NMI之外的所有异常,只有NMI异常可执行
__disable_fault_irq(); //关闭全局中断 __enable_fault _irq(); //开启全局中断
BASEPRI:可以用于指定屏蔽优先级大于或等于寄存器
__set_BASEPRI(prio);
每个异常或中断的对应的寄存器可被划分为抢占优先级和子优先级
__disable_irq(); //关闭全局中断
NVIC_SetPriorityGrouping(7); // 设置优先级分组,全子优先级
NVIC_SetPriority(Interrupt0_IRQn, 3); // 设置优先级
NVIC_SetPriority(Interrupt1_IRQn, 2);
NVIC_SetPriority(Interrupt2_IRQn, 1);
NVIC_EnableIRQ(Interrupt0_IRQn); //使能
NVIC_EnableIRQ(Interrupt1_IRQn);
NVIC_EnableIRQ(Interrupt2_IRQn);
NVIC_SetPendingIRQ(Interrupt0_IRQn); //挂起
NVIC_SetPendingIRQ(Interrupt1_IRQn);
NVIC_SetPendingIRQ(Interrupt2_IRQn);
__enable_irq(); //开启全局中断
4、系统定时器systick
24位的自动递减计数器,可以以固定的时间间隔触发中断,广泛用于各种时间管理任务
- 生成精确的微妙或毫秒延时
- 实现实时操作系统(实时操作系统)的时间片轮转
- 调度周期性任务
- 监控系统运行时间
- 计算时间戳等
配置 systick定时中断
static unsigned int tick_count;
void SysTick_Handler (void) {
tick_count++;
}
extern uint32_t SystemCoreClock;
int main (void) {
//SystemCoreClock -- 72000000 -- 1000ms
//SystemCoreClock/100 -- 10ms
SysTick_Config(SystemCoreClock / 100); //10ms
NVIC_EnableIRQ(SysTick_IRQn); //使能SysTick
for (;;) {
}
return 0;
}
使用systick延时
void delay_us (int us) {
__disable_irq(); //关闭全局中断 延时时间较短的情况
SysTick->LOAD = us * (SystemCoreClock /1000000); //自动重装载初值寄存器
SysTick->VAL = 0; //当前值寄存器
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk; //启动SysTick
while((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) == 0);
SysTick->CTRL &= (~SysTick_CTRL_ENABLE_Msk); //使能清零
__enable_irq(); //开启全局中断
}
128MHz 128个clock(128条指令) -- 1us 时钟频率越高 -- 精度越高
12MHz 12个clock(12条指令) -- 1us
5、 工作模式与特权级切换
工作模式:为了区分正常执行用户程序和处理异常的两种状态,使用两种CPU工作模式进行区分。
- 处理模式(Handler mode):内核此时正在执行异常/中断处理程序
- 线程模式(Thread mode):内核此时正在执行用户程序,未执行异常或中断处理程序。
特权级:为了区分代码的重要性和安全性,内核对程序执行的权限做了两种划分。
非特权级:程序运行受限制 特权级:完全没有任何限制
非特权级 不能使用MRS和MSR指令,CPS指令(开关中断)
不能访问systick定时器、NVIC、SCB控制块中的寄存器
对一些内存或外设访问可能受限(结合MPU)
切换权限,需要写CONTROL寄存器(在内核寄存器)中的nPRIV位,nPRIV=1时,工作在非特权级;nPRIV=0时,工作在特权级
void switch_user (void) { //切换到非特权级
__asm("mrs r0, control");
__asm("orr r0, r0, 0x1");
__asm("msr control, r0");
__asm("bx lr");
}
使用SVC切换回特权级模式,触发SVC异常的方法仅一种,即使用SVC指令,imm为8位立即数
SVC{cond} #imm //这里SVC仅用来触发异常,所以#imm的值具体选什么无关紧要
void SVC_Handler (void) { //SVC异常处理函数
switch_pri(); //切换到特权级
}
void syscall(void) {
__ASM("svc #128"); //触发SVC异常
}
void switch_pri (void) { //非特权级切换到特权级
CONTROL_Type control;
control.w = __get_CONTROL();
control.b.nPRIV = 0;
__set_CONTROL(control.w);
}
int main (void) {
switch_user(); //切换到非特权级
printf("Hello, world\n");
syscall();
for (;;) {
}
return 0;
}
SVC实现系统调用
在一些复杂的系统中,会使用到操作系统,应用程序只完成特定的应用相关工作,并且需要时通过系统调用接口向操作系统发起请求。
通过系统调用接口,可实现以下目标:
- 权限分离:应用程序只能在低特权级模式下执行,无法访问一些关键性的资源、执行特殊的指令
- 操作系统与应用程序代码可分离:通过系统调用接口,应用程序无需知道操作系统的数据和内部函数的地址,这样应用程序和操作系统可独立编译成可执行程序。
- 共享代码:操作系统提供一组操作系统接口以及其内部实现,应用程序只需要调用,无需将这些代码都在所有应用中都加入其中。
typedef struct _stack_frame_t {
uint32_t r0;
uint32_t r1;
uint32_t r2;
uint32_t r3;
uint32_t r12;
uint32_t lr;
uint32_t pc;
uint32_t xpsr;
}stack_frame_t;
#define SYSCALL_ENABLE_IRQ 0
#define SYSCALL_PRINT_NUM 1
#define SYSCALL_SLEEP 2
int do_sys_enable_irq (int enable) { //系统函数(高特权级)
if(enable) {__enable_irq();}
else {__disable_irq();}
return 0;
}
int do_sys_sleep (void) {
int i=0;
for(i=0;i<100000;i++){__NOP(0);}
return 1;
}
int do_sys_print_num (char *fmt, int num) {
printf(fmt,num);
return 2;
}
void SVC_C_Handler (stack_frame_t * frame) {
uint8_t imm = *(uint32_t *)(frame->pc - 2) & 0XFF;
switch (imm ) {
case SYSCALL_SLEEP: {
frame->r0 = do_sys_sleep();
break;
}
case SYSCALL_PRINT_NUM: {
frame->r0 = do_sys_print_num((char *)frame->r0,frame->r1);
break;
}
case SYSCALL_ENABLE_IRQ: {
frame->r0 = do_sys_enable_irq(frame->r0);
break;
}
default:
break;
}
}
__attribute__((naked)) void SVC_Handler (void) {
__asm(
"mrs r0, msp\n"
"b SVC_C_Handler"
);
}
int sys_enable_irq(int enable) { //系统调用接口(低特权级)
register int res __ASM("r0");
__asm ("mov r0, %[enable]\n"
"svc #0\n" //imm = 0
:"+r"(res): [enable]"r"(enable));
return res;
}
int sys_print_num (char *fmt, int num) {
register int res __ASM("r0");;
__asm ("mov r0, %[fmt]\n"
"mov r1, %[num]\n"
"svc #1\n" //imm = 1
:"+r"(res): [fmt]"r"(fmt), [num]"r"(num));
return res;
}
int sys_sleep (void) {
register int res __ASM("r0");;
__asm ("svc #2\n" //imm = 2
:"+r"(res));
return res;
}
int main (void) {
switch_user(); //切换到非特权级
int res;
res= sys_sleep(); //
res = sys_enable_irq(1); //
res = sys_print_num("%d\n", 32); //
for (;;) {
}
return 0;
}
双栈模型
Cortex-M中,有两个栈寄存器:PSP和MSP。程序运行时,可以选择使用PSP或MSP作为栈的指针。
缺省情况下,Cortex-M3使用的是单栈模型,即上电后程序工作在特权级线程模式下,此时使用的MSP寄存器作为栈指针寄存器。之后,无论异常还是中断,都使用MSP指向的栈。
简单的双栈模型
两个栈互不影响,即使用户栈程序出现问题,异常处理等没有影响
切换权限,需要写CONTROL寄存器(在内核寄存器)中的SPSEL位,SPSEL=0时,MSP是当前堆栈指针;SPSEL=1时,PSP是当前堆栈指针
unsigned char msp_stack[1024]; //设置msp栈空间
void switch_stack (void) { //(msp->psp)栈切换
__ASM("mrs r0, MSP");
__ASM("msr psp, r0");
__ASM("ldr r0, =msp_stack+1024");
__ASM("msr msp, r0"); // 设置msp
__ASM("mrs r0, control"); //SPSEL位置1
__ASM("orr r0, r0, 0x2");
__ASM("msr control, r0");
}
分特权的双栈模型
用户程序设置工作在非特权级模式下,中断与异常工作在特权模式下。两个栈分别用于特权级和非特权级。
void switch_stack (void) {
__ASM("mrs r0, MSP");
__ASM("msr psp, r0");
__ASM("ldr r0, =msp_stack+1024");
__ASM("msr msp, r0");
__ASM("mrs r0, control");
__ASM("orr r0, r0, 0x3"); //nPRIV位置1(非特权级),SPSEL位置1(psp)
__ASM("msr control, r0");
}
如果处于非特权级模式下,将无法使用上述方法进行特权级和栈的切换,我们可以通过设置LR的值进行模式切换
EXC_RETURN
0xFFFFFFF1 Handler mode(处理模式),MSP 0001
0xFFFFFFF9 Thread mode(线程模式),MSP 1001
0xFFFFFFFD Thread mode(线程模式),PSP 1101
unsigned char psp_stack[1024]; 设置PSP栈空间
void user_entry (void) {
for (;;) {printf("Hello, world\n");}
}
extern uint32_t __INITIAL_SP;
void SVC_Handler (void) {
stack_frame_t * frame = (stack_frame_t *)(&psp_stack[1024] - sizeof(stack_frame_t));
frame->pc = (uint32_t)user_entry;
frame->xpsr = 1 << 24; //Thumb状态
frame->r0 = 0;
frame->r1 = 1;
__set_PSP((uint32_t)frame);
__ASM("msr msp, %[stack_top]\n"
"ldr lr, =0xFFFFFFFD\n"
"bx lr"::[stack_top]"r"(&__INITIAL_SP));
}
unsigned char msp_stack[1024];
int main (void) {
switch_user();
__ASM("svc #0");
for (;;) {}
return 0;
}
双栈模式下的异常处理
void user_entry (void) {
((void (*)(void))0xFFFFFFFF)(); //异常
for (;;) {
printf("Hello, world\n");
}
}
进入异常前保存现场到当前使用的栈,根据进入异常之前是使用MSP还是PSP,将栈空间传输给处理函数
__attribute__((naked)) void HardFault_Handler (void) {
__asm(
"tst lr, #0x4\n" //LR的第三位为0=msp,1=psp
"ite eq\n" //lr & 0x04的结果为0时,xPSR的Z标志位为1
"mrseq r0, msp\n" //根据Z标志位的值选择执行路径
"mrsne r0, psp\n"
"b HardFault_C_Handler"
);
}
RTOS采用了多栈模型,异常和中断共用同一个栈,由MSP指向。每个任务则配备有自己的栈,在任务运行前,OS会将PSP切换指向到该任务的栈空间中
利用PendSV可将任务切换的请求挂起,延迟到所有中断执行完毕之后再进行。
6、休眠模式
Cortex-M支持让内核进入休眠行为,以节省功耗。在休眠模式中,系统时钟可能会停止,其余时钟可能会继续运行。
- WFI:执行之后,系统进入休眠模式。当产生的中断优先级高于BASEPRI或PRIMASK或FAULTMASK设置的优先级时,才唤醒内核
- WFE:以下条件时唤醒:
- 检测到一个具有足够优先级的异常输入
- 检测到外部事件信号
- 在多处理器系统中的外部事件输入,系统中的另一个处理器执行SEV指令。
- 此外,如果SCR中的SEVONPEND设置为1,任何新的待挂起中断都会触发一个事件并唤醒处理器,即使中断被禁用或没有足够的优先级
7、内核保护单元
Cortex-M提供一个可选的MPU组件,用于控制对内存的访问。可根据当前不同的权限,来设置某些区域的读写权限。
(例,整个4GB存储空间可划分为多个不同的区域,分别设置不同的属性,如非特权级只读等。)
读取区域的数量:
int dregion = (MPU->TYPE & MPU_TYPE_DREGION_Msk) >> MPU_TYPE_DREGION_Pos;
ARM_MPU_Enable(0); // 将禁止整个存储器的访问,死机
ARM_MPU_Enable(MPU_CTRL_PRIVDEFENA_Msk); // 开启MPU同时打开PRIVDEFENA位,特权程序能够访问所有存储
设计目标:利用MPU,将一块RAM区域设置为只读,任何程序都无法进行修改。
MPU_RBAR寄存器(设置子区域基地址)
该寄存器可以控制当前要设置哪个子区域(序号为0,1,2,.MPU_TYPE.DREGION-1中的任意值)
可以看到,ADDR中0:4位是无效的,所以这个地址至少是要2^5=32字节对齐;子区域的设置需要考虑区域的大小,起始地址对齐到相应的位置。
MPU_RASR寄存器
配置该子区域的访问属性。需要关注以下几位:
·XN:可以设置是否允许取指,主要是用于代码执行区域
·AP:可用于设置特权级和非特权级的访问控制
·SIZE:子区域的大小,参考前面的图,只能配置为32字节、64字节等给定的列表中的某个大小
·ENABLE:开启该项配置
#define MEM32(addr) *(volatile unsigned int *)(addr)
ARM_MPU_SetRegion(
ARM_MPU_RBAR(0, 0x20010000), // 地址要对齐到16KB
ARM_MPU_RASR(
0, // 允许取指
ARM_MPU_AP_PRO, // 特权程序只读
0,
0,
0,
0,
0,
ARM_MPU_REGION_SIZE_16KB // 区域大小为16KB
)
);
MEM32(0x20010000 + 16 * 1024) = 0x87654321; // 超过16KB,0x20014000使用缺省的设置,可以读写
MEM32(0x20010000) = 0x87654321; // 设置保护模式后,特权级模式下只读,如果写,则进入fault
解决切换到非特权级模式后,程序无法运行的问题(特权程序和用户程序可以访问代码区域)
// 用户程序,代码区需要可执行
ARM_MPU_SetRegion(
ARM_MPU_RBAR(1, 0), // 地址0-16KB(代码区域从0地址处开始)
ARM_MPU_RASR(
0, // 允许取指
ARM_MPU_AP_RO, // 特权程序只读,用户程序只读
0, 0, 0, 0, 0,
ARM_MPU_REGION_SIZE_16KB // 区域大小为16KB
)
);
ARM_MPU_SetRegion(
ARM_MPU_RBAR(2, 0x20000000),
ARM_MPU_RASR(
0,
ARM_MPU_AP_FULL, //用户程序可读写
0, 0, 0, 0, 0,
ARM_MPU_REGION_SIZE_16KB
)
);
子区域重叠设置
ARM_MPU_SetRegion(
ARM_MPU_RBAR(0, 0x20000000), // 注意这个地址要对齐
ARM_MPU_RASR(
0, // 是否允许取指
ARM_MPU_AP_PRIV, // 特权程序可读写
0, 0, 0, 0,
(1 << 2) | (1 << 3), // 第2和第3个区域不控制
ARM_MPU_REGION_SIZE_256KB // 区域大小为256KB
)
);
// 中间64KB,由另一个区域控制
ARM_MPU_SetRegion(
ARM_MPU_RBAR(1, 0x20000000+64*1024), // 注意这个地址要对齐
ARM_MPU_RASR(
0, // 是否允许取指
ARM_MPU_AP_PRO, // 特权程序只读
0, 0, 0, 0, 0,
ARM_MPU_REGION_SIZE_64KB // 区域大小为64KB
)
);
特权级 MSP
非特权级 PSP 只能访问非特权级程序和数据
main.c
#include <stdio.h>
#include <string.h>
#include "ARMCM3.h"
uint32_t main_counter;
typedef struct _stack_frame_t {
uint32_t r0;
uint32_t r1;
uint32_t r2;
uint32_t r3;
uint32_t r12;
uint32_t lr;
uint32_t pc;
uint32_t xpsr;
}stack_frame_t;
#define SYSCALL_ENTER_USER 0
#define SYSCALL_PRINT_NUM 1
int do_sys_print_num (char * fmt, int num) {
printf(fmt, num);
return 0;
}
extern uint32_t __INITIAL_SP;
extern unsigned char psp_stack[1024];
void user_entry (void);
void SVC_C_Handler (stack_frame_t * frame) {
uint8_t imm = (*(uint16_t *)(frame->pc - 2)) & 0xFF;
switch (imm) {
case 0: {
stack_frame_t * frame = (stack_frame_t *)(&psp_stack[1024] - sizeof(stack_frame_t));
frame->pc = (uint32_t)user_entry;
frame->xpsr = 1 << 24;
frame->r0 = 0;
frame->r1 = 1;
__set_PSP((uint32_t)frame);
__ASM("msr msp, %[stack_top]\n"
"ldr lr, =0xFFFFFFFD\n"
"bx lr"::[stack_top]"r"(&__INITIAL_SP));
}
case SYSCALL_PRINT_NUM:
frame->r0 = do_sys_print_num((char *)frame->r0, frame->r1);
break;
default:
frame->r0 = -1;
break;
}
}
__attribute__((naked)) void SVC_Handler (void) {
__asm(
"tst lr, #0x4\n"
"ite eq\n"
"mrseq r0, msp\n"
"mrsne r0, psp\n"
"b SVC_C_Handler"
);
}
extern uint32_t Image$$USER_CODE$$Base;
extern uint32_t Image$$USER_DATA$$Base;
extern uint32_t Image$$SHARE_DATA$$Base;
// 存储保护单元设置
void mpu_set (void) {
__disable_irq();
// 设置用户可以执行的代码区域
ARM_MPU_SetRegion(
ARM_MPU_RBAR(0, (uint32_t)&Image$$USER_CODE$$Base), // 注意这个地址要对齐到16KB
ARM_MPU_RASR(
0, // 是否允许取指
ARM_MPU_AP_RO, // 特权程序只读
0, 0, 0, 0, 0,
ARM_MPU_REGION_SIZE_4KB // 区域大小为4KB
)
);
// 设置用户可以访问的数据区域
ARM_MPU_SetRegion(
ARM_MPU_RBAR(1, (uint32_t)&Image$$USER_DATA$$Base), // 注意这个地址要对齐到16KB
ARM_MPU_RASR(
1, // 是否允许取指,禁止取指
ARM_MPU_AP_FULL, // 用户程序可读写
0, 0, 0, 0, 0,
ARM_MPU_REGION_SIZE_4KB // 区域大小为4KB
)
);
// 两者共享的一部分区域,都可以读写
ARM_MPU_SetRegion(
ARM_MPU_RBAR(2, (uint32_t)&Image$$SHARE_DATA$$Base), // 注意这个地址要对齐到16KB
ARM_MPU_RASR(
1, // DisableExec,是否允许取指
ARM_MPU_AP_FULL, // AccessPermission,共享可读
0, 0, 0, 0, 0,
ARM_MPU_REGION_SIZE_4KB // 区域大小为4KB
)
);
ARM_MPU_Enable(MPU_CTRL_PRIVDEFENA_Msk);
__enable_irq();
}
int main (void) {
mpu_set();
__ASM("svc #0");
for (;;) {
}
return 0;
}
user.c
#include <stdio.h>
#include "ARMCM3.h" // Device header
unsigned char psp_stack[1024];
int sys_print_num (char * fmt, int num) {
register int res __ASM("r0");
__ASM("mov r0, %[fmt]\n"
"mov r1, %[num]\n"
"svc #1"
:"+r"(res):[fmt]"r"(fmt), [num]"r"(num));
return res;
}
void user_entry (void) {
int cnt = 0;
__set_CONTROL(__get_CONTROL() | 0x1);
// 没有权限访问特权级代码的数据
//extern uint32_t main_counter;
//main_counter++;
// 没有权限执行code中的代码
//void SVC_Handler (void);
//SVC_Handler();
for (;;) {
sys_print_num("Hello, world: %d\n", cnt++);
extern int share_count;
sys_print_num("Hello, sharecount: %d\n", share_count++);
}
}