G0系列主要是用来替代F0系列的产品,所以从Cortex M0到Cortex M0+算是一个升级。Cortex M0与Cortex M0+的区别很细微,很多不注意细节的工程师可能还说不出来两者到底有什么差别。其实ARM公司当初推出Cortex M0的初衷就是替换8bit,16bit的产品,但是很多市场的变化带来嵌入式软件复杂度持续增加。所以Cortex M0被删除的一些功能在Cortex M0+上又补回来了。当然其中很多Cortex M0+新增的功能都是Optional,即可实现也可不实现。 对设计软件的工程师而言Cortex M0+相对于Cortex M0的改变有如下几点比较重要:
1,流水线从三级变成了两级,这是为功耗与中断响应速度而考虑的改进。
2,VTOR可选,Cortex M0的中断向量是不能有偏移量的,这点设计过Bootloader的程序员会感受很深。
3,MPU可选,做过RTOS的任务间隔离的程序员会感受很深。
4,两级CPU权限:privileged和unprivileged两种权限状态。 Cortex M0也有这两种权限,只是两种权限完全一样,所以等于没有。
这里做个Demo展示一下MPU与两级内核状态的使用。设计目的:有一个数组,在用户态仅仅允许读,在内核态可读可写。这种设计在Cortex M0上基本上不可能。在Cortex M0+的芯片如STM32G0F70上,利用MPU和CPU权限可以轻易做到。
首先声明数组,配置MPU, 注意用的是数组声明是绝对定位:
#define ARRAY_ADDRESS_START (0x20002000UL)
#define ARRAY_SIZE MPU_REGION_SIZE_256B
#define ARRAY_REGION_NUMBER MPU_REGION_NUMBER3
uint8_t PrivilegedReadOnlyArray[32] __attribute__((at(ARRAY_ADDRESS_START)));
void MPU_Config(void) {
MPU_Region_InitTypeDef MPU_InitStruct = {0};
HAL_MPU_Disable();
/* Configure RAM region as Region N°0, 256KB of size and R/W region */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = EXAMPLE_RAM_ADDRESS_START;
MPU_InitStruct.Size = EXAMPLE_RAM_SIZE;
MPU_InitStruct.AccessPermission = portMPU_REGION_READ_WRITE;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = EXAMPLE_RAM_REGION_NUMBER;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* Configure FLASH region as REGION N°1, 1MB of size and R/W region */
MPU_InitStruct.BaseAddress = EXAMPLE_FLASH_ADDRESS_START;
MPU_InitStruct.Size = EXAMPLE_FLASH_SIZE;
MPU_InitStruct.Number = EXAMPLE_FLASH_REGION_NUMBER;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* Configure Peripheral region as REGION N°2, 512MB of size, R/W and Execute
Never region */
MPU_InitStruct.BaseAddress = EXAMPLE_PERIPH_ADDRESS_START;
MPU_InitStruct.Size = EXAMPLE_PERIPH_SIZE;
MPU_InitStruct.Number = EXAMPLE_PERIPH_REGION_NUMBER;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* Enable MPU (any access not covered by any enabled region will cause a fault) */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
void MPU_AccessPermConfig(void) {
MPU_Region_InitTypeDef MPU_InitStruct = {0};
HAL_MPU_Disable();
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = ARRAY_ADDRESS_START;
MPU_InitStruct.Size = ARRAY_SIZE;
MPU_InitStruct.AccessPermission = MPU_REGION_PRIV_RW_URO;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = ARRAY_REGION_NUMBER;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* Enable MPU (any access not covered by any enabled region will cause a fault) */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
主函数中调用上述函数以使能MPU,并且切换至用户状态:
MPU_Config();
MPU_AccessPermConfig();
__set_CONTROL(THREAD_MODE_UNPRIVILEGED | SP_MAIN);
__ISB();
接下来实验读写权限:
printf("%s %u\n", __func__, __LINE__);
printf("%s %u %02X\n", __func__, __LINE__, PrivilegedReadOnlyArray[0]);
这句没毛病,只是读。
这句就会引起HardFault, 因为在用户状态这个数组是不允许写的。
PrivilegedReadOnlyArray[0] = (uint8_t)HAL_GetTick();
printf("%s %u %02X\n", __func__, __LINE__, PrivilegedReadOnlyArray[0]);
那么要写怎么办?
答案就是使用SVC指令进入内核权限。
asm_svc_2(HAL_GetTick());
printf("%s %u %02X\n", __func__, __LINE__, PrivilegedReadOnlyArray[0]);
注意asm_svc_2这个是汇编语言写的函数,要放在另外的.s文件中,详情直接看后文的代码下载连接。
;Supervisor Call 2
ALIGN
asm_svc_2 FUNCTION
EXPORT asm_svc_2
SVC #2
bx lr
ENDP
注意要将SVC_Handler的声明改成有参数输入,生成的代码是没有参数输入的。按照AAPS的调用规约,这个函数可以有输入参数。为了求简便,没有区分SVC号,实际工程中这样就比较浪费SVC号了。
void SVC_Handler(uint32_t input){
PrivilegedReadOnlyArray[0] = (uint8_t)input;
}
这样就可以达到设计目的了。
本贴属于转载,如有侵权请联系删除