Cortex-M1处理器是流水线三级32位RISC处理器,采用了流行的高密度Thumb®-2指令集。它同时支持处理器和软件要求,满足了最小FPGA的面积预算,同时兼容从ARM7TDMI®处理器开始的ARM处理器Thumb代码。虽然是Cortex处理器系列中最小的处理器,Cortex-M1处理器依然实现了每MHz 0.8 DMIPS。利用Cortex-M1处理器,OEM能够在FPGA和HardCopy®ASIC以及分立器件中发挥其ARM体系结构的专业技术优势。
Altera Cyclone III FPGA的ARM Cortex-M1开发套件为OEM提供了设计ARM处理器系统所需的工具。在ARM Cortex-M1开发套件中,以加密的SOPC Builder工具兼容知识产权(IP)格式来实现ARM Cortex-M1处理器。这样,OEM能够使用流行的Altera Quartus® II 开发软件,将Cortex-M1处理器与其他系统组件集成到一起,这一软件包括SOPC Builder工具。套件还为ARM RealView微控制器提供开发套件,以满足所有嵌入式开发需求。Cortex-M1处理器受益于ARM联络社区丰富的工具支持,以及RTOS供应商的支持。
为简化IP许可过程,Cortex-M1开发套件提供“点击链接”式最终用户许可协议,授权OEM开发不受限制的Cortex-M1处理器设计,使用时间是一年。许可为这些设计提供永久实施权利,包括1,000个版税,可以在随用随付的基础上迅速方便的展开应用。
特性
流水线三级32位ARM® Cortex处理器
适合在FPGA中实现,主要面向Altera® Cyclone® III FPGA。
SOPC Builder可用
低成本ARM Cortex-M1开发套件,包括ARM RealView微控制器开发套件。
由ARM联络社区的多种工具和操作系统提供支持
更简单的许可过程
ARM处理器简单移植途径
集成可配置中断控制器
集成系统定时器,增强RTOS支持。
可选调试、断点和监视点单元
结构图
二、Cortex-M3基础学习
因为项目所需,所以不得不开始研究M3,我用的是NXP公司的LPC1768这个芯片,它是具有三级流水线的哈佛结构,带独立的本地指令和数据总线以及用于外设的稍微低性能的第三条总线,还包含一个支持随机跳转的内部预取指单元。好了,今天只是初步测试一下它的GPIO功能,所谓的GPIO也就是通用输入/输出口(General Purpose Input Output),万丈高楼平地起,慢慢来吧。实验的原理图很简单,就是几个LED灯,关于灯的电流及电压参数,这里不作详细介绍,有兴趣的可以到网上或供应商那里要一份数据手册看一下,做技术的不能懒,有些东西该看的还是要看的。
图1 硬件原理图
硬件原理图很简单了,74LVC244是用来驱动LED的,74LVC244内部就是8个三态门,关于它的详细电气参数,请参阅它的数据手册。关于程序也不是太难,有几个地方要说一下。即然是GPIO实验,当然要关注GPIO的配置了,由于大部分引脚都具有一个以上功能,所以首先要配置引脚功能寄存器PINSEL,只有当引脚配置为GPIO功能时,GPIO的方向位才有效,对于其它功能来说,方向是自动的。关于引脚功能寄存器PINSEL功能如下表所示:
PINSEL0~PINSEL9的值功能复位后的值
00默认功能,通常为GPIO口00
01第一个可选功能
10第二个可选功能
11第三个可选功能
PINSEL寄存器每两位控制一个引脚,由于各个引脚的功能不一样,所以PINSEL0~PINSEL9的配置也相应的有所不同,想详细了解这10个寄存器的功能可以参看《user.manual.lpc17xx》,这个手册可以到NXP的网站上去下载。由于这次实验只是用到GPIO功能,所以暂时不用管这个寄存器,用它的默认值就可以了。
另一个寄存器是引脚模式选择寄存器PINMODE,它控制所有端口的工作模式,包括是否配置上拉/下拉电阻和特定的开漏操作模式等。引脚模式选择寄存器PINMODE的功能如下表所示:
PINMODE0~PINMODE9的值功能复位后的值
00引脚使能片内上拉电阻00
01中断模式
10引脚没有使能片内上拉/下拉电阻
11引脚使能下拉电阻
当引脚处于逻辑高电平时,中继模式使能上拉电阻,当引脚处于逻辑低电平时,使能下拉电阻,当引脚配置为输入且不是通过外部驱动时,引脚将保持上一个已知状态。PINMODE_0D寄存器控制寄存器的开漏模式,引脚配置为输出且值为0时,开漏模式会正常地将引脚电平拉低。但是如果输出引脚为1,则引脚输出驱动关闭,等同于改变了引脚的方向,实际上是模拟了一个开漏输出。
PINMODE_0D0~PINMODE_0D4的值功能复位后的值
00引脚处于正常模式(非开漏模式)00
01引脚处于开漏模式
除了以上两个寄存器要配置,与GPIO端口相关的寄存器还有快速端口值寄存器FIOPIN可以用来读/写端口的值,端口输出置位寄存器FIOSET,当这个寄存器的相应位置1时,相应端口引脚输出1,当然也可以从这个寄存器读取当前引脚输出的值,与它对应的寄存器是FIOCLR,当寄存器FIOCLR相应位置1时,相应引脚输出0,但这个寄存器是只写寄存器,不能读取数据。好了,关于这些寄存器先总结到这,详细的配置还要看LPC1768的用户手册《user.manual.lpc17xx》,当然也可以经常去NXP网站逛逛看看其它的handbook或whitepaper 好了,下面给出实验程序,由于程序太多,只给出部分,剩下的源文件可以到工程文件夹中去找:
/*********************************************************************************
文件名称:mian.c
功 能: 主要调度函数及应用函数
编译环境: MDKV4.12
时 钟: 外部12Mhz
日 期: 11/08/16
作 者: 懒猫爱飞
备 注:NULL
---------------------------------------------------------------------------------
修改内容:NULL
修改日期:XXXX年xx月xx日 xx时xx分
修改人员:xxx xxx xxx
**********************************************************************************/
#include“main.h”
volatile unsigned lONg SysTickCnt; /* 用于系统时钟计数*/
/********************************************************************************
* 函数名称:void SysTick_Handler (void)
* 函数功能: 系统节拍定时器中断函数,每1ms计数一次
* 入口参数: 无
* 出口参数: 无
* 备 注:无
*******************************************************************************/
void SysTick_Handler (void)
{
SysTickCnt++;
}
/********************************************************************************
* 函数名称:void Delay (unsigned long tick)
* 函数功能: 毫秒级延时函数
* 入口参数: unsigned long tick -- 延时时长
* 出口参数: 无
* 备 注:无
*******************************************************************************/
void DelayMs (unsigned long tick)
{
unsigned long systickcnt;
systickcnt = SysTickCnt;
while ((SysTickCnt - systickcnt) 《 tick);
}
/********************************************************************************
* 函数名称:void PortInit(void)
* 函数功能: 端口初始化
* 入口参数: 无
* 出口参数: 无
* 备 注:无
*******************************************************************************/
void PortInit(void)
{
GPIO1-》FIODIR = 0xB0000000; /* LEDs on PORT1 defined as Output */
GPIO2-》FIODIR = 0x0000007C; /* LEDs on PORT2 defined as Output */
Led1Off(); /* 初始化时熄灭所有的灯*/
Led2Off();
Led3Off();
Led4Off();
Led5Off();
Led6Off();
Led7Off();
Led8Off();
}
/********************************************************************************
* 函数名称:int main(void)
* 函数功能: 主函数
* 入口参数: 无
* 出口参数: 无
* 备 注:无
*******************************************************************************/
int main(void)
{
SystemInit(); /* 系统初始化,函数在system_LPC17xx.c文件夹中定义*/
SysTick_Config(SystemFrequency/1000 - 1); /* 配置时钟中断,每1ms中断一次*/
/* 在core_cm3.h中定义*/
PortInit(); /* 端口初始化*/
while(1)
{
Led1On(); //LED1开
Led4On(); //LED4 开
DelayMs(200);
Led1Off(); //LED1关
Led4Off(); //LED4关
DelayMs(200);
}
}
因为程序是最基础的实验,所以不是太难,程序中延时函数用的是系统节拍定时器,每1m中断一次,系统节拍定时器的中断配置函数是SysTick_Config(uint32_t ticks)它在core_cm3.h中有定义,有兴趣的话可以看看。GPIO是一个结构体指针,原型在LPC17XX.H这个头文件中,其定义的原型如下所示:
typedef struct
{
__IO uint32_t FIODIR;
uint32_t RESERVED0[3];
__IO uint32_t FIOMASK;
__IO uint32_t FIOPIN;
__IO uint32_t FIOSET;
__O uint32_t FIOCLR;
} GPIO_TypeDef;
还有几个宏定义,如下所示:
1)、#define GPIO1 (( GPIO_TypeDef *) GPIO1_BASE)
上面这个宏是把GPIO1指向芯片GPIO1的基址上,
2)、#define GPIO1_BASE (GPIO_BASE + 0x00020)
3)、#define GPIO_BASE (0x2009C000UL)
这三个宏就把GPIO的基址定义了出来,关于LPV1768内部地址的分配情况,可以参看它的数据手册,也可参考一下《ARM Cortex-M3 权威指南》。
有输出总会有输入,今天测试一下按键的功能,第一节已经说过了与GPIO端口相关的寄存器,这里不在重复,想要从端口读取数据,首先把FIODIR这个寄存器设置为输入,再从FIOPIN寄存器读取数据就可以了,这个寄存器具有读写功能。下面说一下这个实验的电路图,如下所示:
图1 JoySTick按键连线图
关于按键电路还有一个,不过是接在外部中断0 上的,其电路图如下图所示:
这次实验没有涉及到外部中断,都是做普通的IO输入使用的,所以在这里外部中断就做学习总结了。下面给出这次实验的主程序:
/*********************************************************************************
文件名称:mian.c
功 能: 主要调度函数及应用函数
编译环境: MDKV4.12
时 钟: 外部12Mhz
日 期: 11/08/16
作 者: 懒猫爱飞
备 注:NULL
---------------------------------------------------------------------------------
修改内容:NULL
修改日期:XXXX年xx月xx日 xx时xx分
修改人员:xxx xxx xxx
**********************************************************************************/
#include“main.h”
volatile unsigned lONg SysTickCnt; /* 用于系统时钟计数*/
/********************************************************************************
* 函数名称:void SysTick_Handler (void)
* 函数功能: 系统节拍定时器中断函数,每1ms计数一次
* 入口参数: 无
* 出口参数: 无
* 备 注:无
*******************************************************************************/
void SysTick_Handler (void)
{
SysTickCnt++;
}
/********************************************************************************
* 函数名称:void Delay (unsigned long tick)
* 函数功能: 毫秒级延时函数
* 入口参数: unsigned long tick -- 延时时长
* 出口参数: 无
* 备 注:无
*******************************************************************************/
void DelayMs (unsigned long tick)
{
unsigned long systickcnt;
systickcnt = SysTickCnt;
while ((SysTickCnt - systickcnt) 《 tick);
}
/********************************************************************************
* 函数名称:void PortInit(void)
* 函数功能: 端口初始化
* 入口参数: 无
* 出口参数: 无
* 备 注:无
*******************************************************************************/
void PortInit(void)
{
GPIO1-》FIODIR = 0xB0000000; /* LEDs on PORT1 defined as Output */
GPIO2-》FIODIR = 0x0000007C; /* LEDs on PORT2 defined as Output */
LedAllOff(); /* 初始化时熄灭所有的灯*/
}
/********************************************************************************
* 函数名称:int main(void)
* 函数功能: 主函数
* 入口参数: 无
* 出口参数: 无
* 备 注:无
*******************************************************************************/
int main(void)
{
unsigned char LedFlag = 1; // 记录LED状态
SystemInit(); /* 系统初始化,函数在system_LPC17xx.c文件夹中定义*/
SysTick_Config(SystemFrequency/1000 - 1); /* 配置时钟中断,每1ms中断一次*/
/* 在core_cm3.h中定义*/
PortInit(); /* 端口初始化*/
while(1)
{
if(!LedFlag)
{
Led1On(); // 点亮LED
}
else
{
Led1Off(); // 熄灭LED
}
if(!KEY_VAL)
{
DelayMs(10);
while(!KEY_VAL);
LedFlag ^=1; // Led状态改变一次
}
if(!KEY_EN) // 此处是为了测试摇杆按键的功能是否正常
{
DelayMs(10);
while(!KEY_EN);
Led8Neg(); // 点亮LED // Led状态改变一次
}
}
}
上一节对程序没有做过多的解释,这里详细分析一下,工程中包含的源文件如下图所示:
工程中startup_LPC17XX.s是M3的启动文件,启动文件由汇编语言写的,它的作用一般是下面这几个:
1)堆和栈的初始化
2)向量表定义
3)地址重映射及中断向量表的转移
4)设置系统时钟频率
5)中断寄存器的初始化
6)进入C应用程序
工程中main.c是我写的应用程序,也就是这次实验的程序,core_cm3.c与core_cm3.h主要是M3外围驱动源代码与头文件,使用时一般不需要修改,直接调用就可以。system_LPC17xx.c与system_LPC17xx.h是关于系统的文件,里面主要提供了系统初始化函数SystemInit(),文件中默认情况下定义的晶振的大小为12M,使用的是外部晶振,还使用了PLL0倍频,关于倍频的问题,以后慢慢再总结。芯片LPC1768的初始化主要包括时钟配置,电源管理,功耗管理等。相比较而言,时钟配置相对复杂,因为它包括两个PLL倍频电路,一个是主PLL0主要是为系统和USB提供时钟,另一个是PLL1专门为USB提供48M时钟,但也可以不使用它们。由于时钟配置比较灵活,所以相以设置这些参数也比较复杂,但是这些在系统文件中已有明确的定义,所以想要变动时只需修改系统文件中相应的宏或函数即可。
下面简要总结一下main()函数,首先是系统初始化函数SystemInit(),上面说过它在system_LPC17xx.c这个源文件中,这个函数主要完成了对时钟的配置,系统功耗PCONP,时钟输出,flash加速等系统资源配置。如果要进行修改可以参考源文件的修改方法,虽然是英文注释,但都非常简单,有兴趣的可以打开看看,不过一般情况下我们拿来直接用就好了不用修改的。
函数SysTick_Config(SystemFrequency/1000 - 1) 是用来配置系统时钟节拍的,它的原型在core_m3.c这个源文件中。实验程序中用的延时函数都是硬件延时,其实就是系统节拍定时器所产生的。使用硬件延时的原因是1、不占用软件系统资源,2、比较精确。系统定时器配置很简单,使用也很方便,专为系统软件或系统管理软件提供间隔中断。系统节拍定时器的时钟源可以是内核时钟也,可以是外部时钟,外部时钟P3.26脚引入,当然想从这个引脚输入时钟,需要将这个引脚先配置成STCLK功能。系统节拍定时器是一个24位定时器,当计数值达到0时产生中断。系统节拍定时器的功能就是为下一次中断提供前提供一个固定时间间隔。由于节拍定时器是24位的,所以使用时不能与其它定时器混为一谈,一定要注意定时时长的限制,不能超过界限。
最后再说一下数据类型的问题,在8位机中数据位找一般就是8位的所以,定义变量时一般选用单字节处理速度会快些,但到了32位机中,数据位宽一般是32位的,所以定义变量时一般用4字节会好些。在core_cm3.c中有关于数据类型的定义,有兴趣的可以打开看看。
无论是哪款单片机应该都有对应的中断的功能,中断在嵌入式系统的地位毋庸置疑。LPC1768微处理器包括4个外部中断,分别是EINT0、EINT1、EINT2、EINT3对应的引脚分别是P2.10~P2.13,这几个引脚也可以作为通用IO口使用。名个外部中断可以设置成低电平/高电平或上升沿/下降沿有效,它们还有一个功能就是可用于将处理器从睡眠、深度睡眠或掉电模式中唤醒。涉及到外部中断的寄存器主要有以下几个:
寄存器名称描述功能默认值
EXTINT外部中断标志寄存器设置或查看中断标志0x00
EXTMODE外部中断模式寄存器设置电平触发或边沿触发0x00
EXTPOLAR外部中断极性寄存器设置为高/低电平或上升/下降沿触发0x00
PINSEL4引脚功能选择寄存器选择P2引脚的功能0x00
因为实验选用中断方式而非查询方式来测试中断,因中P2.10口接有一个按键,所以就用这个按键来作个简单的中断实验,由于是选用的是下降沿触发,所以还涉及一个寄存器:IO2IntenF这个寄存器的功能是使能P2口的下降沿中断功能。因为只是实验,所以电路很简单,还是使用的前两次用的电路图,如下所示:
图1 LED指示电路
还有一个电路,其实就是在P2.10口上接了一个轻工触按键,以触发外部中断,这里就不上图了。好了,由于实验内容简单,下面给出中断程序,关于主程序有兴趣的话可以参看附件中的,里面有完整的Real MDK4.10建立的工程工件:
/*********************************************************************************
文件名称:extint.c
功 能: LPC1768的外部中断函数
编译环境: MDKV4.12
时 钟: 外部12Mhz
日 期: 11/08/18
作 者: 懒猫爱飞
备 注:NULL
---------------------------------------------------------------------------------
修改内容:NULL
修改日期:XXXX年xx月xx日 xx时xx分
修改人员:xxx xxx xxx
**********************************************************************************/
#include “includes.h”
volatile uint32_t eint0_counter = 8; // 初始化时第一个灯亮
/********************************************************************************
* 函数名称:void EINT0_IRQHandler (void)
* 函数功能: 外部中断0函数入口
* 入口参数: 无
* 出口参数: 无
* 备 注:无
*******************************************************************************/
void EINT0_IRQHandler (void)
{
SC -》 EXTINT = EINT0; /* 清中断*/
switch(eint0_counter%8)
{
case 0: Led1Neg(); /* LED1状态取反*/
break;
case 1: Led2Neg(); /* LED2状态取反*/
break;
case 2: Led3Neg(); /* LED3状态取反*/
break;
case 3: Led4Neg(); /* LED4状态取反*/
break;
case 4: Led5Neg(); /* LED5状态取反*/
break;
case 5: Led6Neg(); /* LED6状态取反*/
break;
case 6: Led7Neg(); /* LED7状态取反*/
break;
case 7: Led8Neg(); /* LED8状态取反*/
break;
default:break;
}
eint0_counter++; /* 计数值加1 */
}
/********************************************************************************
* 函数名称:uint32_t EINTInit( void )
* 函数功能: 外部中断0初始化函数
* 入口参数: 无
* 出口参数: 返回TURE或FALSE
* 备 注:如果是返回false则说明中断入口函数没有在中断向量表中建立
*******************************************************************************/
uint32_t EINTInit( void )
{
PINCON -》 PINSEL4 = 0x00100000; /* 将P2.10脚设置为EINT0即第二功能*/
GPIOINT -》 IO2IntEnF = 0x200; /* 设置为下降沿触发*/
SC -》 EXTMODE = EINT0_EDGE; /* 外部中断模式选择为边沿触发*/
SC -》 EXTPOLAR = 0; /* 外部中断1极性设置,此处选默认的低电平或下降沿*/
NVIC_EnableIRQ(EINT0_IRQn); /* 使能外部中断0 */
return( TRUE );
}
这个程序只有两个函数,一个是中断处理函数,没有什么不好理解,在这里不总结,关于中断初始化函数,前面都是设置相关的中断寄存器的,关于寄存器是怎么设置的可以看一下LPC1768的手册,上面都有详细的说明,在这里不再码字总结。关于void NVIC_EnableIRQ(IRQn_Type IRQn)这个函数,它在core_cm3.h中有定义,其实就是设置中断使能寄存器ISER,从名字就可以看出来了,它的功能就是使能中断。这次外部中断没有用查询的方式,查询方式的原理就是设置好中断,使能中断,然后主程序一直查询中断位是否有中断,然后再执行相应的措施。
三、Cortex-M3内核的μC/OSII性能研究
引言
μC/OSII是基于优先级的可剥夺型内核,系统中的所有任务都有一个唯一的优先级别,它适合应用在实时性要求较强的场合;但是它不区分用户空间和系统空间,使系统的安全性变差。而移植到CortexM3内核上的μC/OSII系统一般是运行在特权级下,以至于应用程序也可以访问操作系统的变量和常量,这样使得系统的安全性与稳定性变得更差。
根据CortexM3内核的特点,对μC/OSII操作系统的安全性和稳定性进行研究。利用CortexM3内核上选配的MPU(Memory ProtectiON Unit,存储器保护单元),对μC/OSII操作系统做适当的改进与优化。经测试,系统的安全性与稳定性得到很大的提高。
1 开发坏境
采用IAR5.30作为开发环境,移植μC/OSII2.86到CortexM3内核,选用配置了MPU(Memory Protection Unit,存储器保护单元)的LPC1786处理器作为硬件实验平台,对操作系统的安全性和稳定性进行改进与优化。
2 CortexM3内核简介
在CortexM3内核*有两个堆栈指针:主堆栈指针(MSP),是系统上电后缺省的堆栈指针,它由OS内核、异常服务例程以及所有需要特权访问的应用程序代码来使用;进程堆栈指针(PSP),用于常规的应用程序代码(不处于异常服务例程中时)。
CortexM3处理器支持线程模式和处理模式两种工作模式,有特权级与用户级两个访问等级。异常处理总是工作在处理模式,只可使用主堆栈指针。处理模式总是在特权级下运行,而线程模式可在特权和用户级下运行。系统复位时总是处于线程模式的特权方式下,并且默认使用的堆栈指针是MSP。在用户级下,对特殊功能寄存器和系统控制空间(SCS)的大部分寄存器的访问是禁止的[2]。
经实验验证,在用户级下使用MSR、MRS指令访问特殊功能寄存器(CONTROL等),这些指令被当作NOP指令(空指令)执行,而对系统控制空间(SCS)寄存器访问会产生精确的总线访问异常。
另外,CortexM3内核还可以选配MPU(如LPC1700系列、LM3S系列处理器),用于对存储器进行保护。设定一块内存的访问权限,对系统的安全性有很好的帮助。
3 μC/OSII内核简介
μC/OSII是一个可移植、可固化、可裁剪的抢占式实时多任务内核。大部分用ANSI C语言编写,只有一小部分与硬件相关的代码用汇编语言编写。至今,μC/OSII已经在40多种不同架构的微内核处理器上移植成功[4]。μC/OSII内核只提供了任务调度、任务管理、时间管理和任务间通信等基本功能,体系结构如图1所示。进行系统移植时,只需要修改OS_CPU_C.C、OS_CPU.H、OS_CPU_A.ASM这3个文件即可。
图1 μC/OSII体系结构
4 μC/OSII操作系统移植的改进
μC/OSII*****提供的基于CortexM3内核移植的μC/OSII系统一直工作在特权级下。这样做的好处是,系统不用频繁地切换访问等级,而且开关中断很快,利于实时性的实现;但是应用程序(用户任务)也可以访问特殊功能寄存器和系统控制空间(SCS)寄存器,修改操作系统的变量,这对系统的安全性是一种威胁,如果用户任务程序跑飞,那就有可能破坏系统寄存器和变量[5]。
4.1 系统寄存器的设置
用户应用程序运行在用户级,使用PSP堆栈指针;操作系统函数运行在特权级,使用的也是PSP堆栈指针;而中断服务例程运行在处理模式的特权方式下,使用MSP堆栈指针。
图2 特权与用户级分区
首先利用MPU把内存分为特权级访问和用户级访问两个区,如图2所示。在系统初始化时,设置MPU相关寄存器,为系统分配任务堆栈与主堆栈:任务堆栈分配在用户区,系统变量与主堆栈分配在特权区,只可特权级下访问。
4.2 系统函数的修改
用户任务工作在用户级下,操作系统函数工作在特权级下,任务可能会在执行系统函数时执行上下文切换,因此系统要记录任务切换时是处在特权级还是用户级下,以便任务再次获得处理器控制权时,切换到原先的访问等级下。在任务创建时,加入访问权限参数mode。
权限的值定义为:
#define OS_Mode_USER 1u //用户级
#define OS_Mode_PRIVILEGE 0u //特权级
在创建任务函数与堆栈初始函数的参数中加入访问权限参数,形式如下:
INT8U OSTaskCreateExt (……,INT8U mode );
OS_STK *OSTaskStkInit (……,INT8U mode);
在堆栈初始化时,把mode最后存到堆栈当中,以便任务第一次运行时进入相应的工作模式(特权级或用户级)。统计任务和空闲任务的mode是OS_Mode_PRIVILEGE,而用户任务为OS_Mode_USER。
4.3 OS_CPU_A.ASM文件中函数的修改
在OS_CPU_A.ASM文件中,只需修改函数PendSV_Handler(PendSV服务例程),任务切换是由它来完成的。同时,设置PendSV的优先级为最低,以便快速响应中断,提高系统的实时性。PendSV服务例程的流程如图3所示。
图3 PendSV服务例程流程
任务切换上文的程序:
SUBS R0,R0,#0x24;调整PSP指针,mode、R4~R11共36字节
MRS R1,CONTROL;获取当前任务的访问等级mode
STM R0,{R1,R4R11};压栈mode,R4~R11
LDR R1,=OSTCBCur;获取OSTCBCur?﹥OSTCBStkPtr
LDR R1,[R1]
STR R0,[R1];存储PSP值到任务控制块切换下文的程序:
……;OSPrioCur=OSPrioHighRdy;
……;OSTCBCur=OSTCBHighRdy;
……;得到新任务的PSP值,存储到R0中
LDM R0,{R1,R4R11};R1(mode),R4~R11出栈
MSR CONTROL,R1;修改CONTROL[0]
ORR LR,LR,#0x04;选择返回时使用的堆栈
ADDS R0,R0,#0x24;调整PSP值
MSR PSP,R0;R0存入PSP中
4.4 系统函数的使用
系统函数都是在特权级下执行的,在应用程序中调用系统函数前应该切换到特权级,系统函数执行完毕后再切换后用户级。调用形式如下:
ToPrivilege ();
OSFunction(Parameter1, Parameter2……);//系统函数
ToUser ();
在特权级下可以通过置位CONTROL[0]来进入用户级。用户级下是不能通过修改CONTROL[0]来回到特权级的,必须通过一个异常handler来修改CONTROL[0],才能在返回到线程模式后取得特权级。因此,从用户级到特权级的方法就是产生一个异常,再在异常例程中修改CONTROL[0]。通常的方法是使用软中断SVC。
切换到特权级的代码如下:
ToPrivilege;函数ToPrivilege ()
SVC 0
BX LR
SVC_Handler;SVC服务例程
MRS R1,CONTROL
AND R1,R1,#0xFE
MSR CONTROL,R1;回到特权级
BX LR
而从特权到用户级就简单了,只要执行切换程序就可以了,不用产生异常。切换到用户级的代码为:
ToUser;函数ToPrivilege ()
MRS R0,CONTROL
ORR R0,R0,#0x01;切换到用户级
MSR CONTROL,R0
BX LR
4.5 其他改进方法
任务在用户级+PSP下运行,而操作系统函数运行在特权级+MPS运行,中断服务例程有硬件设定在处理模式+特权级+MSP,这样系统的安全性和稳定性会更高。但是每个任务需要两个堆栈PSP、MSP。这样无疑增加了内存的使用(将近增加一倍),由于嵌入式芯片的片内RAM比较小,增加内存必然会增加成本,并且要对任务控制块做相应的修改,存储两个堆栈。任务创建时对这两个堆栈都要初始化,任务切换时判断切换的堆栈与访问权限,这些都增加了系统的开销。
结语
系统连续稳定地运行10个小时以上没出现任何问题,可见系统移植成功。利用CortexM3内核选配的MPU,对μC/OSII操作系统进行修改,只是增加了很小的系统开销,却使系统的安全性和稳定性得到了很大的提高。该方法可应用于对系统安全性与稳定性要求比较高的场合。
四、双核ARM Cortex-A9 MPCore处理器
说明
双核 ARM® CortexTM-A9 MPCoreTM 应用类处理器是 Altera Arria V SoC FPGAs 和 Cyclone V SoC FPGA 中的集成硬核知识产权(IP)组件。为进一步提高系统性能,很多ARM辅助系统资源合作伙伴为ARM Cortex-A9处理器提供同类最佳的开发工具和操作系统支持。
HPS
ARM Cortex-A9处理器结合了丰富的嵌入式外设、接口以及片内存储器,以建立完整的硬核处理器系统(HPS)。HPS和FPGA架构的宽带片内干线链接提供了100-Gbps的峰值带宽,使ARM处理器和FPGA架构的硬件加速器能够很好的共享数据。
特性
800-MHz双核处理器支持对称和非对称多路处理
每一处理器包括以下组成:
高效的双发超标量流水线(2.5 MIPS*每MHz)
用于媒体和信号处理加速的NEONTM媒体处理引擎
单精度和双精度浮点单元
32-KB指令和32-KB数据高速缓存
连贯高速缓存,以增强处理器间的通信功能
采用了TrustZone®安全技术的存储器管理单元
Thumb®-2技术,增强了代码密度、性能和功效
Jazelle®扩展体系结构,加速了Java虚拟机
编程跟踪宏单元,实现处理器指令流的全面可视化
共享512-KB、8路联合L2高速缓存,按路、行或者主机进行锁定
加速连贯端口,支持CPU扩展连贯存储器访问
普通中断控制器
32位通用定时器
看门狗定时器
Altera® Arria® V SoC FPGA 和 Cyclone® V SoC FPGA 提供这些功能
* Dhrystones 2.1 基准测试
评论
查看更多