深入理解Cortex-M3内核架构

李述铜老师讲解的深入理解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);    //中断清除

 NMIPendSV和系统时钟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位的自动递减计数器,可以以固定的时间间隔触发中断,广泛用于各种时间管理任务

  1. 生成精确的微妙或毫秒延时
  2. 实现实时操作系统(实时操作系统)的时间片轮转
  3. 调度周期性任务
  4. 监控系统运行时间
  5. 计算时间戳等

配置 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实现系统调用

在一些复杂的系统中,会使用到操作系统,应用程序只完成特定的应用相关工作,并且需要时通过系统调用接口向操作系统发起请求。

通过系统调用接口,可实现以下目标:

  1. 权限分离:应用程序只能在低特权级模式下执行,无法访问一些关键性的资源、执行特殊的指令
  2. 操作系统与应用程序代码可分离:通过系统调用接口,应用程序无需知道操作系统的数据和内部函数的地址,这样应用程序和操作系统可独立编译成可执行程序。
  3. 共享代码:操作系统提供一组操作系统接口以及其内部实现,应用程序只需要调用,无需将这些代码都在所有应用中都加入其中。
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中,有两个栈寄存器:PSPMSP。程序运行时,可以选择使用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支持让内核进入休眠行为,以节省功耗。在休眠模式中,系统时钟可能会停止,其余时钟可能会继续运行。

  1. WFI:执行之后,系统进入休眠模式。当产生的中断优先级高于BASEPRI或PRIMASK或FAULTMASK设置的优先级时,才唤醒内核
  2. 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++);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值