Cortex-M0

相关文章:

敏矽微电子Cortex-M0学习笔记01——芯片简介

敏矽微电子Cortex-M0学习笔记02——Cortex-M0开发环境的建立及调试

敏矽微电子Cortex-M0学习笔记03——时钟系统设计例程

敏矽微电子Cortex-M0学习笔记04——GPIO详解及应用实例

1、目的

本篇学习笔记我们主要来了解ME32F030的中断系统,首先通过对ME32F030终端系统和中断控制存器进行学习,最后通过实际的GPIO端口中断实例掌握中断函数的编程实现来加深掌握ME32F030中断系统的编程方法。

2、ME32F030中断概述

中断是单片机系统重要的组成部分,使单片机能够快速的对事件请求做出响应。ME32F030靠内部的嵌套向量中断控制器(NVIC)来进行中断的调度,它是 Cortex™-M0 内核的一部分。它可以让 CPU 以最短的时间对中断作出反应。主要的特征有:

• 较短的中断响应延迟.

• 处理系统异常和外设中断.

• 支持 32 个中断向量.

• 四种可编程的中断响应优先级别.

• 产生软件中断.

• 可配置的不可屏蔽中断源会有详细的说明。

3、ME32F030嵌套向量中断控制器(NVIC)

嵌套向量中断控制器(NVIC)负责着整个MCU的中断管理,除了管理我们常用的外设中断源,还包括非屏蔽中断源的管理。具体管理的中断源及其中断序号,其实在CMSDK_CM0.h中定义好了,通过程序定义对具体管理的中断源一目了然,定义的内容如下图所示。

1.png

中断源

ME32F030的中断系统是由一系列的NVIC寄存器组成的,这些寄存器可用于中断 IRQ0~IRQ31, 包括中断使能,等待和优先级等操作。如果中断被允许,并且相应中断挂起被设置,NVIC 将会根据中断优先级触发中断。反之,中断被禁止,中断源只会改变中断挂起状态,而 NVIC 不会对中断源信号采取任何动作,不论任何中断优先级。具体的NVIC寄存器组如表格所示:

2.png

NVIC寄存器列表

2-1 中断允许寄存器

中断允许寄存器(ISER)用于使能中断设置,同时可返回当前允许中断设置。需要注意的是,对该寄存器写0是无效的,是不能禁止中断的。要禁止中断,就需要下面介绍的中断禁止寄存器。

2-2 中断禁止寄存器

有使能中断的设置,相应的就会有禁止中断设置,这就是中断禁止寄存器的作用。对寄存器进行写1操作,就可以禁止中断设置。。需要注意的是,对该寄存器写0是无效的。对寄存器进行读操作,同返回当前禁止中断设置。

2-3 中断挂起寄存器

当有中断事件发生时,中断挂起寄存器(ISPR)中对应的中断位就会置位 。此时读取寄存器就可以判断具体的中断源。同时我们也可以向寄存器的中断位写1,来强制中断进入挂起状态。

2-4 清除中断挂起寄存器

当MCU响应了中断请求,并且执行完对应的中断子程序后,MCU便会返回断点处继续运行。但在返回前需要通过清除中断挂起寄存器(ICPR),来清除对应的中断挂起,这样当次的中断流程算是完整结束。

2-5 中断优先级寄存器

如同我们做事情有轻重缓急之分,单片机对中断的处理也有“轻重缓急”。具体就是靠8组中断优先级寄存器IPR0~7来实现。每组寄存器对应4个中断源的优先级。这样刚好决定了中断0~中断31的优先级。

每一组的中断优先级寄存器IPRn的每个字节最高两位决定优先级,因此有0~3共4个优先级可以选择,越低的值表示优先级越高,当优先级更高的中断发生时,高优先级的中断会打断低优先级中断。如果是同优先级中断,则并不会打断当前中断,而是依次响应中断。中断优先级寄存器IPRn如图所示:

3.png

这里介绍个快速的方法来计算中断 M 的 IPR 寄存器号:

• 计算对应的 IPR 寄存器号, N, N = M / 4

• 计算 IPR 寄存器内的字节偏移量 M % 4, 其中:

– 字节偏移量 0 对应寄存器的位 7:0

– 字节偏移量 1 对应寄存器的位 15:8

– 字节偏移量 2 对应寄存器的位 23:16

– 字节偏移量 3 对应寄存器的位 31:24

4、ME32F030端口中断例程

本篇中我们首先讲解了ME32F030的GPIO中断系统,然后又介绍了嵌套向量中断控制器(NVIC)的原理。

最后,我们还是要通过具体的实例来把我们学到的理论知识应用到实际的例子中。我们将两者结合起来做个小实验,测试程序的代码如下:

    unsigned int uiCnt = 0;//端口反转次数

int main(void)
{
	PA->DIR_b.DIR0 = 1;       //PA_0 设置为输出口

	PA->IS_b.ISENSE0 = 0;    //PA_0 设置为沿触发

	PA->IBE_b.IBE0 = 1;       //PA_0 上升沿和下降沿都触发中断

	PA->IC_b.CLR0 =1 ;        //PA_0 中断标志位清除

	PA->IE_b.MASK0 = 1;      //PA_0 中断使能
 
	PB->DIR_b.DIR9=1;        //PB_9 设置为输出口

NVIC_EnableIRQ(PA_IRQn); //使能PA_IRQ中断

lcd_init();                 //LCD液晶初始化
 
while (1)
{

	uiCnt++;               //端口反转次数加1

	PA->NOT_b.NOT0=1;    //PA_0 输出取反

	SYS_DelaymS(500);

	if(uiCnt == 20)           //当反转20次时
		{
			PA->IE_b.MASK0 = 0;  //PA_0 中断关闭
		}
  }
}

//PA_IRQ中断子程序
void PA_IRQHandler(void)  
{
PB->NOT_b.NOT9=1;    //PB_9(LED灯)输出取反

  PA->IC_b.CLR0 =1 ;     //清除PA_0中断位

//LCD显示中断发生的次数
  LCD->MEMMAP1 = lcd[uiCnt/10] | (lcd[uiCnt%10]<<16);
}

测试程序是通过PA_0端口输出反转,来产生下降沿和上升沿。但同时它的端口中断功能是被使能的,因此可以通过输出电平来“触发”自己的中断。在中断服务子程序中,LED小灯端口输出取反来进行点亮和熄灭,同时加入了LCD段码液晶来显示中断发生的次数。在程序全速运行的过程中,当端口输出反转20次之后,会关闭端口的中断功能。接下来下载并仿真例程来进行说明。

程序下载并仿真后,先在程序这两处打上断点。然后用快捷键F5全速运行,程序首先会运行到第78行处的断点,这时端口还没有进行输出反转。接下来用快捷键F10单步运行观察。

4.png

仿真1

F10单步运行后,发现程序已经跳转到了PA_IRQ中断服务子程序中,继续F10单步运行并观察执行每一步的现象,直到把中断服务子程序走完。

5.png

仿真2

中断服务子程序内的代码全部运行后的效果如图所示,首先LED小灯的端口输出取反,把LED小灯给点亮后(下次再进中断会输出取反熄灭,依次往复)。LCD段码液晶显示01,这说明发生了1次中断。

6.png

仿真结果

通过单步仿真我们清楚了中断发生后的处理流程,接下来就可以把之前打的两个断点取消掉,然后在83行的位置打上一个断点,随后F5全速运行程序,等待程序停到断点处。在等待的过程中,LED小灯保持闪烁,LCD段码液晶上的数字一直在自加。当程序停到断点处后,LCD段码液晶显示为20。继续单步运行后,端口PA_0的中断功能就被关闭了。

7.png

仿真3

关闭中断后,再次全速运行程序。我们发现小灯不再闪烁了,段码液晶显示的数字也不再自加。这是因为我们已经把端口中断关闭掉了,虽然uiCnt还在自加。但是已经进不了中断子程序去更新显示。因此依旧停留显示在20。我们不妨把uiCnt添加到Watch窗口中来看一下,添加方法如图所示。双击ucCnt变量名,选中后右键选择“Add uiCnt to”,“Watch 1”,这样就添加到Watch1窗口中了。

8.png

仿真4

通过Watch1窗口看到端口已经反转37次了,但LCD液晶已经停留在20。这也说明中断确实被关闭了,因此液晶一直没能更新显示。

9.png

仿真5

10.png

仿真结果2

来源:敏矽MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 12

相关文章:

敏矽微电子Cortex-M0学习笔记01——芯片简介

敏矽微电子Cortex-M0学习笔记02——Cortex-M0开发环境的建立及调试

敏矽微电子Cortex-M0学习笔记03——时钟系统设计例程

前面我们已经对敏矽微电子的基于cortex m0内核的ME32F030R8T6的基本功能做了介绍,然后详细讲解了开发环境MDK的安装,pack包的安装,工程的建立及程序的仿真,紧接着讲解了ME32F030R8T6的时钟系统。

如果说前面都是基础知识介绍和理论讲解,那么从本篇开始,我们不但会有理论介绍,还会结合敏矽微电子提供的ME32F030R8T6开发板进行实例验证,让大家看到实例运行的结果。

1、敏矽微电子Cortex-M0学习笔记04-GPIO详解及实例

首先我们对ME32F030R8T6芯片的I/O端口做一个大致的了解。

1.1. ME32F030R8T6的GPIO 概述

ME32F030R8T6提供多达 57个 GPIO 管脚。主要的特点有:

• 数字管脚可以用软件定义为输入或输出

• 管脚读写可以被屏蔽

• 多个管脚的置位、清零位用一条指令实现

• 管脚的输出取反

• 每一个管脚可作为外部中断信号

• 可编程的中断触发条件及中断优先级

• 所有GPIO管脚在复位后被配置成带上拉电阻的输入管脚

MCU的端口除了灵活易用的特点之外,其外设功能也十分强大。管脚功能由IO控制寄存器配置,除电源管脚,其余管脚均可复用。

系统复位后,管脚功能将被设置成默认值。

GPIO可以复用的功能如下:LED/LCD 驱动、触摸按键、ADC、定时器6输出、定时器7输出、PWM输出、UART0、UART1、SPI、I2C。

在使用端口复用功能时,通过 I/O 配置寄存器IOCON对端口进行功能设置,每个引脚对应的复用功能,请参照官方数据手册的详细说明。

1.2. ME32F030R8T6的GPIO 寄存器详解

对于使用过单片机开发产品的人来说,功能繁多、名称各异的寄存器应该是最重要也是最令人头疼的地方,但是没办法,想要开发出功能稳定、性能可靠的产品,就必须来硬着头皮查看各个寄存器的功能和配置方法。

当然,对于ME32F030R8T6来说,官方推出了库函数,利用这些库函数可以不必关心具体的寄存器就能编写出合适的程序。但是对于想要深入了解的话,还是要看看寄存器的。

1.2.1. GPIO功能配置寄存器

MCU有一系列的GPIO 控制寄存器来实现对I/O口的灵活控制。

首先我们对 I/O 配置寄存器IOCON做一个介绍,因为它决定着I/O端口的功能选择和电气特性。所以需要拿出来单独进行介绍,该寄存器的每一位的功能如下:

1.png

2.png

①、管脚功能:

IOCON 寄存器中的 FUNC 位可设为 GPIO (FUNC = 000) 或外设功能。如果将管脚配置为 GPIO 管脚,则 DIR 寄存器决定管脚是配置为输入还是输出。对于任何外设功能,会根据管脚的功能自动控制管脚方向。GPIO 的 DIR 寄存器此时对外设功能无效。

②、管脚模式:

IOCON 寄存器的 MODE 位允许为每个管脚使能或禁止片内上拉电阻。默认情况下,所有管脚的上拉电阻都被使能。

③、管脚驱动

对于每个正常驱动管脚,可以选择两种电流的输出驱动,即低电流模式和高电流模式。用户可以根据自己的实际需求进行选择。

④、开漏模式

所有数字 I/O 管脚都可为开漏模式。但是请注意,该模式并不是真正的开漏模式。输入上拉至不能超过VDD。

⑤、模式功能

I/O 管脚可以配置为模拟功能,作为模拟信号的输出和输入管脚,例如复用为AD口的时候,可以作为检测电压的模拟信号输入口。

⑤、电压转换速率模式

在作用AD转换口的时候,可以设置端口转换速率为快速模式或慢速模式,默认为快速模式。相对于快速模式,慢速模式转满时间会变长,随之电流功耗也增大,但时精度会更加精确,根据应用场景和自身需求,选择适合的模式。

1.2.2. GPIO控制寄存器总览

MCU所有的GPIO被分布到4个端口: PA,、PB、PC、PD。每个端口都拥有自己独立的控制寄存器去管理 GPIO 的功能。下面的表列出了所有的寄存器以供参考。接下来会对每个寄存器做出详解。

3.png

4.png

1.2.3. GPIO屏蔽寄存器

GPIO屏蔽寄存器可屏蔽下列寄存器的读和写操作:PIN、OUT、SET、CLR和 NOT,相当于给这些寄存器上了个“锁”,只有当MASK寄存器相应的BIT位被置 0,被屏蔽的寄存器才能进行读和写操作。该寄存器上电后默认为0,即不进行GPIO屏蔽。当配置1启动屏蔽功能后,对处于输出功能的端口进行任何写操作都无效,不会改变其当前的输出状态。对处于输入功能的端口进行读操作,无论此时端口处于何种电平,都会返回0.

1.2.4. GPIO管脚值寄存器

对配置为数字功能的端口,对该寄存器进行读操作将返回管脚的当前逻辑值,不管该管脚是配置为输入还是输出,也不管它是配置为 GPIO 还是任何其它适用的备用数字功能,都可以直接进行读取。但也有例外,在以下两种情况中, PIN 寄存器中读出的管脚值无效:①、如果选择了管脚的模拟功能(如适用),则不能读取管脚状态,例如将管脚选作 ADC 输入会断开管脚的数字功能。②、该引脚被GPIO屏蔽寄存器MASK给屏蔽了。

1.2.5. GPIO管脚输出寄存器

在没有被GPIO屏蔽寄存器MASK屏蔽的情况下,向该寄存器写0或1将在相应端口管脚产生低电平或高电平。但是对于所有其他配置(输入、非GPIO功能),OUT 寄存器位的值对管脚输出电平无效。读取该寄存器将返回 GPIO 输出寄存器的内容,不管数字管脚配置和方向如何。

通过SET、CLR和NOT寄存器可以对OUT 寄存器执行写操作,允许按位对单个端口管脚进行置位、清除、取反操作。以此来控制OUT 寄存器的输出内容。

1.2.6. GPIO管脚输出置位寄存器

在没有被GPIO屏蔽寄存器MASK屏蔽,端口DIR为输出方向且端口功能为数字GPIO功能的情况下,写1会将相应端口管脚设为高电平。写0对GPIO输出电平无效。另外该寄存器为只写寄存器,对其进行读操作是无效的。

1.2.7. GPIO管脚输出清除寄存器

在没有被GPIO屏蔽寄存器MASK屏蔽,端口DIR为输出方向且端口功能为数字GPIO功能的情况下,写1会将相应端口管脚设为低电平。写0对GPIO输出电平无效。另外该寄存器为只写寄存器,对其进行读操作是无效的。

1.2.8. GPIO管脚输出取反寄存器

在没有被GPIO屏蔽寄存器MASK屏蔽,端口DIR为输出方向且端口功能为数字GPIO功能的情况下,写1会将相应端口的输出状态进行反转。写0对GPIO输出电平无效。另外该寄存器为只写寄存器,对其进行读操作是无效的。

1.2.9. GPIO数据方向寄存器

在使用数字GPIO前,首先要确定的就是端口的数据方向。向该寄存器写1会将端口设置为输出模式,写0设置为输入模式。上电后端口默认为输入状态。

1.2.10. GPIO中断感应寄存器

在文章开头的介绍中,我们就说过MCU的每一个管脚是可以作为外部中断信号。因此会有一系列与之对应的中断管理寄存器,来对每个端口的中断进行管理。这就是接下来要介绍的中断寄存器。在使用端口中断前,需要明确需要触发中断的条件。向中断感应寄存器ISENSEx写入1,端口中断的触发方式为电平中断。向寄存器写入0,端口中断的触发方式为沿中断触发,具体需要什么样的沿来触发,这个还需要下面的中断配置寄存器来设置。

1.2.11. GPIO中断配置寄存器

紧接上文,在明确使用端口触发方式为沿中断触发后,我们通过中断配置寄存器IBEx来选择沿触发条件。向寄存器写入1,管脚的上升沿和下降沿都触发中断。向寄存器写入0,管脚只能由上升沿或下降沿中的一种来触发中断,具体由哪种来触发,需要下面的中断事件寄存器IEVx来决定。

1.2.12. GPIO中断事件寄存器

当中断配置寄存器IBEx值为0时,中断事件寄存器就决定着中断触发的条件,向寄存器写1,上升沿触发中断。写入0,则下降沿产生中断。

1.2.13. GPIO中断屏蔽寄存器

在实际的开发过程中,使用到中断功能的端口毕竟是少数,因此MCU在上电后就默认屏蔽了端口的中断功能。如果要开启端口的中断功能,向对应的寄存器位写1即可。

1.2.14. GPIO原始中断状态寄存器

该寄存器的位读出为高时反映了对应管脚上的原始(屏蔽之前)中断状态,表示在触发 IE 之前所有的要求都满足。位读出为0时表示对应的输入管脚还未启动中断。该寄存器为只读。

1.2.15. GPIO中断状态寄存器

该寄存器中的位读为高反映了输入触发中断的状态。读出为低则表示对应的输入管脚没有中断产生,或者中断被屏蔽。读出为高则表示对应的输入管脚有中断产生。该寄存器为只读。

1.2.16. GPIO中断清除寄存器

在中断发生后,程序会进入中断服务子程序。在中断服务子程序中处理完中断程序后,需要向中断清除寄存器CLRx写1来清除中断标志。

1.3. ME32F030R8T6的GPIO 库函数函数

为了便于开发者快速上手,敏矽微电子官方例程中提供了gpio.c文件,其中包含了设置端口方向、读取端口状态、配置端口中断等函数,供开发者直接使用。

1、设置GPIO为输入方向

void GPIO_ConfigPinsAsInput(PA_Type *port, uint16_t pins)

{

    port->DIR &= ~pins;

    return;

}

2、设置GPIO位输出方向

void GPIO_ConfigPinsAsOutput(PA_Type *port, uint16_t pins)

{

    port->DIR|=pins;

    return;

}

3、设置GPIO输出高

void GPIO_SetPin(PA_Type *port, uint16_t pin)

{

    port->SET |= pin;

    return;

}

4、设置GPIO输出低

void GPIO_ResetPin (PA_Type *port, uint16_t pin)

{

    port->CLR |= pin;

    return;

}

5、设置GPIO输出反转

void GPIO_InvertOutPin (PA_Type *port, uint16_t pin)

{

    port->NOT |= pin;

    return;

}

6、读取GPIO某个引脚的输入状态

uint8_t GPIO_GetPinState(PA_Type *port, uint16_t pin)

{

    if (port->PIN & pin)

    return 1;

    else

    return 0;

}

7、读取GPIO整个引脚的输入状态

uint16_t GPIO_GetPortState(PA_Type *port)

{

    return (uint16_t)port->PIN;

}

8、屏蔽GPIO引脚

void GPIO_SetPortMask(PA_Type *port, uint16_t pins)

{

    port->MASK |= pins;

    return;

}

9、使能GPIO引脚

void GPIO_SetPortMask(PA_Type *port, uint16_t pins)

{

    port->MASK |= pins;

    return;

}

10、配置GPIO引脚的中断功能

void GPIO_EnableInt(PA_Type *port, uint16_t pin, uint8_t triggeredge)

{

	port->IS &= ~pin;


        port->IE |= pin;

    	switch(triggeredge)

    	{

    		case RISE_EDGE:
					port->IBE &= ~pin;

					port->IEV |= pin;			break;			case FALL_EDGE:

					port->IBE &= ~pin;

					port->IEV &= ~pin;			break;			case BOTH_EDGE:

					port->IBE |= pin;			break;				default:			break;



    		}

    		return;

}

11、清除GPIO引脚的中断标志

void GPIO_ClrInt(PA_Type *port, uint16_t pins)

{

    	port->IC =pins;

    	return;
}

1.4. ME32F030R8T6的GPIO 开发实例

介绍完GPIO的原理和函数,接下来就用最经典的LED小灯试验来进行示例。

本例使用敏矽微电子专门为ME32F030R8T6提供的库函数编写程序。

实例程序的代码如下:

int main(void

{
	WDT->MOD=0;          //关闭看门狗   

	PB_9_INIT(PB_9_GPIO);          //PB9(LED)设置为GPIO功能

	GPIO_ConfigPinsAsOutput(PB, IO_PIN9);  //PB9(LED)设置为输出方向

	while (1)

	{

		GPIO_InvertOutPin(PB, IO_PIN9);  //PB9(LED)端口输出反转

		SYS_DelaymS(500);                //延时500ms

	}

}

程序下载成功后,先在端口反转这句话处打上一个断点,然后全速运行程序(快捷键F5)。随后程序会停在断点处,

5.png

此时先暂停观察下LED小灯的状态,发现红圈标注的小灯并没有被点亮。

6.png

运行结果1

接下来单步运行程序,观察执行完端口反转这段程序后的状态。这时我们发现小灯已经被点亮了。

7.png


继续单步运行,当再次执行端口反转这段程序后,端口输出就会反转,接下来小灯就会熄灭。最后取消程序中的所有断点,让程序全速运行起来,小灯便开始周期性的闪烁。

来源:敏矽MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 16

相关文章:

敏矽微电子Cortex-M0学习笔记01——芯片简介

敏矽微电子Cortex-M0学习笔记02——Cortex-M0开发环境的建立及调试

1.1. ME32F030R8T6的时钟树

时钟是MCU运行的基础,时钟信号推动单片机内各个部分执行相应的指令。时钟系统就是CPU的脉搏,决定cpu速率,像人的心跳一样 只有有了心跳,人才能做其他的事情,而MCU有了时钟,才能够运行执行指令,才能够做准确、稳定的进行一系列的操作 (例如:串口通信、PWM信号、ADC采样等等),因此时钟的重要性不言而喻。

ME32F030系列 具有非常灵活的时钟控制系统。用户可根据不同应用需求来配置时钟,从而取得最高的性能及优化的能耗管理。下图为 ME32F030R8T6 的时钟系统概要图:

1.png

从图中可以看出,MCU的时钟源有内部高速的IRC_OSC 和 低速的 WDT_OSC 时钟可供选择。

IRC_OSC:它属于高速时钟,可以由内部晶体振荡器控制寄存器(IRCCTRL)配置为 40/48MHz的主频, 缺省值是40MHz 频率,由工厂出厂预设并由引导程序写入。一般作为系统主时钟的时钟源。

WDT_OSC:它属于低速时钟,由看门狗振荡器控制寄存器控制。振荡器包含模拟和数字两部分。振荡器的模拟部分用于产生模拟时钟(Fclkana)。在振荡器数字部分,模拟时钟(Fclkana)输出一个32KHz 频率时钟。然后再被DIVSEL 控制的分频器分频输出到 WDT_CLK,作为看门狗时钟源。

看门狗振荡器输出频率可用下列公式推算:

WDT_CLK = Fclkana/(4 ×DIVSEL) = 8K Hz ~ 250 Hz (标称值)

1.2. ME32F030R8T6时钟源的应用控制

介绍完系统的时钟源,接下来说说时钟源主要运用到了哪些方面。

ME32F030R8T6的外设非常多,但我们实际使用的时候只会用到有限的几个外设,使用任何外设都需要时钟才能启动,但并不是所有的外设都需要系统时钟那么高的频率,为了兼容不同速度的设备,有些高速,有些低速,如果都用高速时钟,势必造成浪费。并且,同一个电路,时钟越快功耗越快,同时抗电磁干扰能力也就越弱,所以较为复杂的MCU都是采用多时钟源的方法来解决这些问题。

WDT_CLK:由看门狗振荡器控制寄存器来选择输入时钟,作为看门狗的工作时钟。

2.png

MAIN_CLK:由MAINCLK_SEL选择输入时钟源,上电默认选择IRC_OSC_CLK作为时钟源,也可以通过置位来选择WDT_OSC_CLK。

3.png

SYSTEM_CLK:由MAINCLK通过AHB 接口时钟分频器寄存器(SYSAHBCLKDIV)分频而来,默认不分频。SYSTEM_CLK时钟供内核、外设和存储器使用。其中外设通过 AHB 接口时钟控制寄存器(SYSAHBCLKCTRL)来控制外设的时钟使能,上电默认外设的时钟都是打开的。

4.png

UART:UART0/1有自己独立的时钟分频器从MAIN_CLK分频后取得UART时钟。

5.png

CLK_OUT:MCU的内部晶振器(IRC_OSC_CLK)、看门狗振荡器(WDT_OSC_CLK),主时钟(MAIN_CLK),系统时钟(SYSTEM_CLK)都可以通过CLK_OUT作为输出时钟。

使用前需要通过 CLKOUT 输出时钟源选择寄存器 (CLKOUTCLKSEL)来确定想要输出的时钟源,再经过 CLKOUT 输出时钟分频器寄存器 (CLKOUTDIV)分频后输出,该寄存器初始值默认为0,即不输出时钟。需要输出的话,设置好分频系数(1-255)便可以输出了。

6.png

7.png

2、ME32F030R8T6的时钟系统函数简介

为了便于开发者快速上手,敏矽微电子为开发者提供了丰富的库函数和开发例程。

借助于库函数,可以不用像普通单片机那样去配置繁多的寄存器,从而加快开发进程。

借助于例程中,可以更深入理解寄存器配置及功能实现。

本例中,我们就借助于敏矽微电子提供的例程来简单介绍它的时钟系统的跨函数。在sys.c文件中,包含了切换系统时钟,配置时钟主频等函数,供开发者直接使用。

1.3. 主频配置函数

①、配置IRC_CLK为40M主频

void SYS_IRCTrimto40M(void){volatile uint32_t i=0xFFFF;if (DIA->IRCTRIM!=0xFFFFFFFF)

{

SYSCON->IRCCTRL=DIA->IRCTRIM;  while(i--==0);

FMC->FLASH_RDCYC =1;

SYSCON->SYSAHBCLKDIV =1;

SystemCoreClockUpdate ();

}return ;

}

②、配置IRC_CLK为48M主频

void SYS_IRCTrimto48M(void){volatile uint32_t i=0xFFFF;if (DIA->IRCTRIM48!=0xFFFFFFFF)

{

SYSCON->IRCCTRL=DIA->IRCTRIM48;while(i--==0);

FMC->FLASH_RDCYC =1;


SYSCON->SYSAHBCLKDIV =1; //core clock to 48M

SystemCoreClockUpdate ();

}

return ;

}

1.4. 时钟源选择函数

③、选择MAIN_CLK的时钟源,可以选择IRC_CLK 或者 WATCHDOG_CLK。

void SYS_SelectMainClkSrc(uint8_t src)


{

 //switch main clk source

 SYSCON->MAINCLKUEN_b.ENA= 1; //disable main clk update

 //switch main clk source to Specifyed source

 if (src==IRC_CLK)
 
 SYSCON->MAINCLKSEL_b.SEL=0;else if (src==WATCHDOG_CLK)

  SYSCON->MAINCLKSEL_b.SEL=2;

 
  SYSCON->MAINCLKUEN_b.ENA=0; //enable main clk update

 SystemCoreClockUpdate ();

 return;

}

④、设置AHB 接口时钟分频器的分频系数,在这里请注意,函数如果检查到当前时钟为IR_CLK且分频系数小于2,这样系统时钟SYSTEM_CLK的主频肯定大于30M, 而FLASH的擦写速度最高支持到30MHz。此时CPU时钟超过Flash的最大读取速度,这就需要插入延迟时钟,延迟时钟由 RDCYC 寄存器控制。

void SYS_SetAHBClkDivider (uint8_t div)

{

 //setup flash access speed if SystemCoreClock is going tomore than 30MHz 

 if ((SYSCON->MAINCLKSEL_b.SEL==0)&&(div<2))  

 FMC->FLASH_RDCYC = 1;                
  
  SYSCON->SYSAHBCLKDIV_b.DIV = div;  //setup ahb clock divider

 SystemCoreClockUpdate ();   //update MainClock and SystemCoreClock

 return;
}

//⑤、设置WDT_CLK的时钟源,可以选择IRC_CLK 或者 WATCHDOG_CLK。

    void SYS_SelectWDTClkSrc(uint8_t src){if (src==IRC_CLK)



    SYSCON->WDTOSCCTRL_b.WDTCLKSRC = 0;

    else if (src==WATCHDOG_CLK)

    SYSCON->WDTOSCCTRL_b.WDTCLKSRC = 1;

 return;

}

//⑥、设置CLK_OUT的时钟源,可以选择IRC_CLK 、SYS_CLK、 WATCHDOG_CLK、MAIN_CLK中的一个。

void SYS_SelectClkOutSrc(uint8_t src)

{switch (src)


{

case IRC_CLK:

src=0;break;case SYS_CLK:

src=1;break;case WATCHDOG_CLK:

src=2;break;case MAIN_CLK:

src=3;break;

 default:return;

    }

    //switch clock

    SYSCON->CLKOUTUEN_b.ENA = 1;

    SYSCON->CLKOUTCLKSEL_b.SEL = src; //select clk out source

    SYSCON->CLKOUTUEN_b.ENA = 0;

     return;

}

//⑦、设置CLK_OUT时钟的输出分频系数。

void SYS_SetClkOutDivider(uint8_t div)

{

SYSCON->CLKOUTDIV_b.DIV = div;

return;}

3、ME32F030R8T6时钟系统例程

上面介绍了sys.c中关于系统时钟的部分函数,下面我们来编写一个关于时钟配置的例程。使大家能够对时钟配置有一个简单的了解。

本例实现的功能是:将AHB 接口时钟进行2分频,IRC时钟切换到48M主频,最后将系统时钟进行分频输出。

int main(void)

{

 SYS_SetAHBClkDivider (2);     // AHB 接口时钟进行2分频

 SYS_IRCTrimto48M(); // IRC时钟切换到48M主频

 SYS_SelectClkOutSrc(SYS_CLK); // 选择SYS_CLK作为OUT_CLK输出时钟 

 SYS_SetClkOutDivider(10); // 对OUT_CLK时钟进行10分频

 SYS_EnableClkOut(); // 使能OUT_CLK输出端口

}

来源:敏矽MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 16

相关文章:敏矽微电子Cortex-M0学习笔记01——芯片简介

本篇先简单介绍MDK的安装流程,然后重点说明如何把敏矽微电的Cortex-M0的PACK包添加到mdk中,这样才能顺利的在MDK环境下开发敏矽微电子Cortex-M0新片。最后用了很大篇幅介绍如何建立工程,工程中各种文件的添加等等。

1、敏矽微电子Cortex-M0的开发环境

敏矽微电子Cortex-M0是基于ARM内核的,所以基于ARM的开发环境都可以用来开发敏矽微电子Cortex-M0的芯片。最常见的两种ARM开发平台是MDK和IAR。我们今天着重介绍MDK环境下如何建立基于敏矽微电子Cortex-M0芯片的项目,以及如何在MDK环境下调试敏矽微电子Cortex-M0芯片。

需要说明的是:MDK软件是需要注册的,强烈建议大家使用正版软件。不过在刚开始学习以及资金有限的情况下,可以使用MDK的评估版本,评估版本的程序容量是32K版本。这个程序容量对于初学来说,是足够用的。

闲话少说,马上开始我们今天的学习之旅。

2、MDK安装

1、首先打开Keil安装包(资料链接中附有MDK 5.28版本安装包),打开后如图1.1所示。

1.png

图1

2、随后点击“Next”,进入后续的安装流程,勾选红圈选项后,点击“Next”下一步。随后会提示MDK的安装路径,强烈建议采用默认路径。如果要自定义安装路径,请保证自定义的安装路径中不要出现任何中文名字!点击“Next”下一步后,输入下姓名、公司、邮箱等信息,随意填写下即可,继续点击“Next”便开始正式安装。期间软件会自动安装仿真器驱动,耐心等待即可。

2.png

3、等到最后出现下面的界面,软件便已安装成功。

3.png

3、MDK注册

1、启动Keil 5,在File选项中选择License Management子选项。

4.png

2、按照提示进行注册即可,请按照官方正版途径注册。

3、如果点击Add LIC提示以下的错误信息,看红色下划线的信息,得知是因为没有在管理员模式下操作,权限不足导致的。这个是Windows管理员模式产生的问题。如果你的注册过程没有此错误提示,可以跳过第7步。解决的办法也很简单,按照步骤4操作即可。

5.png

4、以管理员模式运行Keil 5,鼠标右键Keil 图标,点击“以管理员身份运行”即可。随后重新按照步骤5操作即可。管理员运行方法如图所示:

6.png

5、软件注册完成后,出现“LIC Added Sucessfully”的提示,说明注册成功。

4、安装ME32F030 PACK包

1、KEIL安装完成后,就需要安装芯片支持的PACK包,来让KEIL支持我们的芯片,这里建议安装资料提供的Keil 5版本,因为其对应的PACK包是傻瓜式一键安装,操作十分的方便,找到我们的PACK包双击安装即可。

7.png

2、PACK包安装完成后,我们可以先确认下,看下KEIL是否已经识别并支持我们的芯片,方法如下,首先创建个新工程,选择project->New uVision Project来建立工程。

8.png

3、新建工程时会提示选择芯片类型,从图中看出KEIL已经支持我们的Mesilicon系列芯片。

9.png

5、新建工程

1、选择project->New uVision Project来建立工程。

 10.png

2、选择芯片类型,选择开发板的芯片为Mesilicon->ME32F030 Series->ME32F030C8x6,选择好后,点击“OK”。

11.png

3、出现下面的界面,这个是根据需求自己添加开发组件,不多介绍,直接点取消跳过。

12.png

4、接下来将资料中的Lib2.3 for keil5x例程解压缩出来,其中公用的.c和.h等文件都在common文件夹内,随后开始向工程中添加.c和.h文件。点击如图所示的快捷按钮。

13.png


5、弹出如下界面,在Groups右边有4个按钮,依次为“新建”、“删除”、“上移”、“下移”功能,先选择新建comm、app两个组,你也可以尝试下删除组,把初始自带的Source Group通过红叉按钮删除掉。

14.png

向每个Group中添加.程序c文件。那就先举个简单的例子作为开始,首先我们选中需要添加程序的组,比如我们向app组里添加需要的main.c文件,选中app组后,点击右侧的Files框体下的Add Files,选中要添加的main.c文件。点击Add便完成添加。

15.png

添加成功后的效果如下图所示,右侧的Files中已包含main.c文件,那么想要删除的话,可以在选中文件后,通过点击上方的红叉进行删除。

16.png

依次类推,我们接下来要向comm中添加.c文件。

①、添加core_cm0.c,这个就是我们的单片机的M0内核文件,它在

Lib2.3 for keil 5->common->CoreSupport文件夹中。

17.png

②、添加system_CMSDK.c,它在Lib2.3 for keil 5->common->DeviceSupport->arm->cmsdk文件夹中。

18.png

③、添加startup_CMSDK_CM0.s文件,这个是启动程序文件,它是由汇编语言写成的。是以.s为结尾的文件,所以在添加它的时候需要注意将文件类型选择为All Files才能看见它。

Lib2.3 for keil 5->common->DeviceSupport->arm->cmsdk->Startup->arm文件夹中。

19.png

20.png

④、前面添加都属于单片机的系统文件,接下来就要开始添加我们自己的.c文件了,这个都在Lib2.3 for keil 5->common->Drivers->Source文件夹内。

21.png

⑤、第一次新建工程时可以参照现有的例程,比如以Demo-Touch Me按键触摸试验为模板,尝试建立一下工程。添加自己所需要的文件,如图所示:

22.png

6、添加完成后,关闭Manage Project Items功能栏。返回KEIL主界面后,在左侧的Project工程栏里,可以看到之前添加的所有程序文件。

23.png

7、接下来我们是不是可以编译程序了呢?那不妨先试一下。编译后发现提示很多此类的报错,提示 cannot open source input file "gpio.h": No such file or directory,这是因为我们只添加了.c文件,而需要的头文件路径还没有指定位置。那么接下来就指定头文件路径。

24.png

点击红圈标注的Options快捷按钮,也可以通过快捷键ALT + F7来打开。

25.png

打开后选中C/C++选项卡,在下面可以看到Include Paths栏,这个就是需要指定的头文件路径,点击右边的 。。。按钮来进行添加。

26.png

点击。。。按钮后,通过弹出的对话框来添加头文件路径。同上文讲到的一样,红圈的四个按钮依次为“新建”、“删除”、“上移”、“下移”功能。

27.png

那就新建路径吧,点击新建后会生成一个新的路径框,点击红圈标识的。。。来添加。

28.png

以core_cm0.c文件对应的头文件core_cm0.h为例子,一路进到上文中添加core_cm0.c的文件夹中,进入如图所示的路径后,点击选择文件夹。

29.png

添加完成后,刚才新添加的路径便显示出来了。

30.png

依次类推,再添加以下3个路径。

Lib2.3 for keil 5->common->DeviceSupport->arm->cmsdk

Lib2.3 for keil 5->common->Drivers->Include

Lib2.3 for keil 5->Demo-Touch Me->myapp->include

这里添加的时候要注意,是要选择.h文件所在的那个文件夹,错选成它的上级或下级文件夹,是无法找到需要的头文件的!添加完成后的效果如下。

31.png

这时候我们再去编译一下试试。没有报错也没有警告,说明项目工程顺利建立。

32.png

6、下载与调试

1、程序编译没问题后,接下来就可以下载程序并仿真测试了。在开始前先插上仿真器并连接开发板,打开Options for Target选项卡,选中Debug。这个时候选择仿真器类型(根据实际进行选择,建议买一个U-LINK2仿真器),勾选上Run to main,这样程序下载后直接运行到main函数,否则会先运行startup_CMSDK_M0.s中Reset_Handler程序。虽然这段程序最后也会跳转到我们的main函数,但我们没有必要每次去仿真它。点击Settings查看我们的仿真器配置情况。

33.png

点击Settings后,在Debug子选项中看到下面的信息。则说明仿真器识别正常。

34.png

再点击查看下Flash Download的选项。这时候看到ME20F030单片机的FLash的下载地址和RAM空间地址都已经明确了,这就是为什么前面强烈推荐安装KEIL 5版本,随后打上PACK包,很多设置项都是PACK包整合配置好的,我们直接用就可以了。

35.png

2、仿真选项设置好之后,开始下载程序。点击工具栏上的

 36.png

图标来下载并仿真程序。

 37.png

图标所示的功能也可以下载程序,但是它是不带仿真功能的,这点需要注意!下载成功后会多出下面的工具条。

 38.png

下面我们分别来介绍一下这些仿真调试按钮的功能:

1:复位,点击后程序会从头开始重新运行。

2:全速运行,点击后程序便开始全速运行,运行到断点处会停止,或者使用停着功能。

3:停止,当程序在运行状态下,使用此功能,程序便会停止运行。

4:执行进去,本质是单步运行,如果下一步是要执行的是个函数,那么就行进入到函数 里面,进行单步仿真。

5:段执行,也是单步运行,但不同的是,如果下一步是要执行的是个函数,那么会直接运行整个函数,并不会进入函数内部运行,它是直接以一整段代码为单位进行执行的。

6:执行跳去,当不需要再继续在某个函数里继续单步仿真时,执行此功能,就会直接执行完函数内剩余的代码,随后跳出该函数后会暂停,等待下一步操作。

7:执行到光标处,使用此功能前,先确定想运行到地方,鼠标单击运行的那一行,此时光标便会在这一行显示,这时候再点击此按钮,程序会全速运行,直到在光标处停止。

8:全速运行,此功能是让程序全速运行,除非认为暂停或者遇到断点,否则程序会一直运行。

来源:敏矽MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 22

1.敏矽微电子Cortex-M0学习手记01-芯片简介

1.1. 概述

ME32F030R8T6 的出现为嵌入式的开发带来了极大地便利,ME32F030R8T6 是一款内嵌 ARM Cortex™ M0 核的 32 位微控制器。64Kflash、8K的RAM可以容纳更多的代码,工程师们可以尽情的丰富与完善产品的功能。对于硬件工程师来说,芯片管脚的复用极大的便利了PCB的排版,工程师可以根据走线的需要选择最优的管脚。ME32F030R8T6 管脚功能由 IO 控制寄存器IOCON配置,除电源管脚,其余管脚均可复用。系统复位后,管脚功能将被设置成默认值。

工程师可以通过IOCON寄存器来配置引脚的功能,引脚功能丰富,极大地方便了工程开发。

1.png

1.2. 基本功能

ME32F030R8T6 是一款内嵌 ARM Cortex™ M0 核的 32 位微控制器。

这款控制器具备我们经常使用的一些功能,像检测电压需要用到的ADC,ME32F030R8T6 有多达12个通道的ADC,12位ADC转换,1MHz的转换速率,检测速度与精度相比一般的单片机有了大大的提高。

2.png

存储器分配图

还有常用的UART串口该控制器具备有常用外设和功能,如高速 12 位的 ADC 转换器,UART 串口,SPI 接口,I2C 总线接口,看门狗定时器(WDT),7 个通用计数器/定时器。除此之外,ME32F030R8T6还集成人机界面控制器和马达控制功能,如 LCD 驱动,电容触摸按键,直流无刷电机控制 PWM 模块。

1.3. 应用场景

ME32F030R8T6 适用于家电、厨电、其他消费电子、工业类等场合

3.jpg

来源: 敏矽MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 13

本篇先简单介绍MDK的安装流程,然后重点说明如何把敏矽微电的Cortex-M0的PACK包添加到mdk中,这样才能顺利的在MDK环境下开发敏矽微电子Cortex-M0新片。最后用了很大篇幅介绍如何建立工程,工程中各种文件的添加等等。

1、敏矽微电子Cortex-M0的开发环境

敏矽微电子Cortex-M0是基于ARM内核的,所以基于ARM的开发环境都可以用来开发敏矽微电子Cortex-M0的芯片。最常见的两种ARM开发平台是MDK和IAR。我们今天着重介绍MDK环境下如何建立基于敏矽微电子Cortex-M0芯片的项目,以及如何在MDK环境下调试敏矽微电子Cortex-M0芯片。
需要说明的是:MDK软件是需要注册的,强烈建议大家使用正版软件。不过在刚开始学习以及资金有限的情况下,可以使用MDK的评估版本,评估版本的程序容量是32K版本。这个程序容量对于初学来说,是足够用的。

闲话少说,马上开始我们今天的学习之旅。

2、MDK安装

1、首先打开Keil安装包(资料链接中附有MDK 5.28版本安装包),打开后如图1.1所示。

“
图1

2、随后点击“Next”,进入后续的安装流程,勾选红圈选项后,点击“Next”下一步。随后会提示MDK的安装路径,强烈建议采用默认路径。如果要自定义安装路径,请保证自定义的安装路径中不要出现任何中文名字!点击“Next”下一步后,输入下姓名、公司、邮箱等信息,随意填写下即可,继续点击“Next”便开始正式安装。期间软件会自动安装仿真器驱动,耐心等待即可。

“敏矽微电子Cortex-M0开发环境的建立及调试"

3、等到最后出现下面的界面,软件便已安装成功。

“敏矽微电子Cortex-M0开发环境的建立及调试"

3、MDK注册

1、启动Keil 5,在File选项中选择License Management子选项。

“敏矽微电子Cortex-M0开发环境的建立及调试"

2、按照提示进行注册即可,请按照官方正版途径注册。

3、如果点击Add LIC提示以下的错误信息,看红色下划线的信息,得知是因为没有在管理员模式下操作,权限不足导致的。这个是Windows管理员模式产生的问题。如果你的注册过程没有此错误提示,可以跳过第7步。解决的办法也很简单,按照步骤4操作即可。

“敏矽微电子Cortex-M0开发环境的建立及调试"

4、以管理员模式运行Keil 5,鼠标右键Keil 图标,点击“以管理员身份运行”即可。随后重新按照步骤5操作即可。管理员运行方法如图所示:

“敏矽微电子Cortex-M0开发环境的建立及调试"

5、软件注册完成后,出现“LIC Added Sucessfully”的提示,说明注册成功。

4、安装ME32F030 PACK包

1、KEIL安装完成后,就需要安装芯片支持的PACK包,来让KEIL支持我们的芯片,这里建议安装资料提供的Keil 5版本,因为其对应的PACK包是傻瓜式一键安装,操作十分的方便,找到我们的PACK包双击安装即可。

“敏矽微电子Cortex-M0开发环境的建立及调试"

2、PACK包安装完成后,我们可以先确认下,看下KEIL是否已经识别并支持我们的芯片,方法如下,首先创建个新工程,选择project->New uVision Project来建立工程。

“敏矽微电子Cortex-M0开发环境的建立及调试"

3、新建工程时会提示选择芯片类型,从图中看出KEIL已经支持我们的Mesilicon系列芯片。

“敏矽微电子Cortex-M0开发环境的建立及调试"

5、新建工程

1、选择project->New uVision Project来建立工程。

“敏矽微电子Cortex-M0开发环境的建立及调试"

2、选择芯片类型,选择开发板的芯片为Mesilicon->ME32F030 Series->ME32F030C8x6,选择好后,点击“OK”。

“敏矽微电子Cortex-M0开发环境的建立及调试"

3、出现下面的界面,这个是根据需求自己添加开发组件,不多介绍,直接点取消跳过。

“敏矽微电子Cortex-M0开发环境的建立及调试"

4、接下来将资料中的Lib2.3 for keil5x例程解压缩出来,其中公用的.c和.h等文件都在common文件夹内,随后开始向工程中添加.c和.h文件。点击如图所示的快捷按钮。

“敏矽微电子Cortex-M0开发环境的建立及调试"

5、弹出如下界面,在Groups右边有4个按钮,依次为“新建”、“删除”、“上移”、“下移”功能,先选择新建comm、app两个组,你也可以尝试下删除组,把初始自带的Source Group通过红叉按钮删除掉。

“敏矽微电子Cortex-M0开发环境的建立及调试"

向每个Group中添加.程序c文件。那就先举个简单的例子作为开始,首先我们选中需要添加程序的组,比如我们向app组里添加需要的main.c文件,选中app组后,点击右侧的Files框体下的Add Files,选中要添加的main.c文件。点击Add便完成添加。

“敏矽微电子Cortex-M0开发环境的建立及调试"

添加成功后的效果如下图所示,右侧的Files中已包含main.c文件,那么想要删除的话,可以在选中文件后,通过点击上方的红叉进行删除。

“敏矽微电子Cortex-M0开发环境的建立及调试"

依次类推,我们接下来要向comm中添加.c文件。

①、添加core_cm0.c,这个就是我们的单片机的M0内核文件,它在Lib2.3 for keil 5->common->CoreSupport文件夹中。

“敏矽微电子Cortex-M0开发环境的建立及调试"

②、添加system_CMSDK.c,它在Lib2.3 for keil 5->common->DeviceSupport->arm->cmsdk文件夹中。

“敏矽微电子Cortex-M0开发环境的建立及调试"

③、添加startup_CMSDK_CM0.s文件,这个是启动程序文件,它是由汇编语言写成的。是以.s为结尾的文件,所以在添加它的时候需要注意将文件类型选择为All Files才能看见它。

Lib2.3 for keil 5->common->DeviceSupport->arm->cmsdk->Startup->arm文件夹中。

“敏矽微电子Cortex-M0开发环境的建立及调试"

“敏矽微电子Cortex-M0开发环境的建立及调试"

④、前面添加都属于单片机的系统文件,接下来就要开始添加我们自己的.c文件了,这个都在Lib2.3 for keil 5->common->Drivers->Source文件夹内。

“敏矽微电子Cortex-M0开发环境的建立及调试"

⑤、第一次新建工程时可以参照现有的例程,比如以Demo-Touch Me按键触摸试验为模板,尝试建立一下工程。添加自己所需要的文件,如图所示:

“敏矽微电子Cortex-M0开发环境的建立及调试"

6、添加完成后,关闭Manage Project Items功能栏。返回KEIL主界面后,在左侧的Project工程栏里,可以看到之前添加的所有程序文件。

“敏矽微电子Cortex-M0开发环境的建立及调试"

7、接下来我们是不是可以编译程序了呢?那不妨先试一下。编译后发现提示很多此类的报错,提示 cannot open source input file "gpio.h": No such file or directory,这是因为我们只添加了.c文件,而需要的头文件路径还没有指定位置。那么接下来就指定头文件路径。

“敏矽微电子Cortex-M0开发环境的建立及调试"

点击红圈标注的Options快捷按钮,也可以通过快捷键ALT + F7来打开。

“敏矽微电子Cortex-M0开发环境的建立及调试"

打开后选中C/C++选项卡,在下面可以看到Include Paths栏,这个就是需要指定的头文件路径,点击右边的 。。。按钮来进行添加。

“敏矽微电子Cortex-M0开发环境的建立及调试"

点击。。。按钮后,通过弹出的对话框来添加头文件路径。同上文讲到的一样,红圈的四个按钮依次为“新建”、“删除”、“上移”、“下移”功能。

“敏矽微电子Cortex-M0开发环境的建立及调试"

那就新建路径吧,点击新建后会生成一个新的路径框,点击红圈标识的。。。来添加。

“敏矽微电子Cortex-M0开发环境的建立及调试"

以core_cm0.c文件对应的头文件core_cm0.h为例子,一路进到上文中添加core_cm0.c的文件夹中,进入如图所示的路径后,点击选择文件夹。

“敏矽微电子Cortex-M0开发环境的建立及调试"

添加完成后,刚才新添加的路径便显示出来了。

“敏矽微电子Cortex-M0开发环境的建立及调试"

依次类推,再添加以下3个路径。

Lib2.3 for keil 5->common->DeviceSupport->arm->cmsdk
Lib2.3 for keil 5->common->Drivers->Include
Lib2.3 for keil 5->Demo-Touch Me->myapp->include

这里添加的时候要注意,是要选择.h文件所在的那个文件夹,错选成它的上级或下级文件夹,是无法找到需要的头文件的!添加完成后的效果如下。

“敏矽微电子Cortex-M0开发环境的建立及调试"

这时候我们再去编译一下试试。没有报错也没有警告,说明项目工程顺利建立。

“敏矽微电子Cortex-M0开发环境的建立及调试"

6、下载与调试

1、程序编译没问题后,接下来就可以下载程序并仿真测试了。在开始前先插上仿真器并连接开发板,打开Options for Target选项卡,选中Debug。这个时候选择仿真器类型(根据实际进行选择,建议买一个U-LINK2仿真器),勾选上Run to main,这样程序下载后直接运行到main函数,否则会先运行startup_CMSDK_M0.s中Reset_Handler程序。虽然这段程序最后也会跳转到我们的main函数,但我们没有必要每次去仿真它。点击Settings查看我们的仿真器配置情况。

“敏矽微电子Cortex-M0开发环境的建立及调试"

点击Settings后,在Debug子选项中看到下面的信息。则说明仿真器识别正常。

“敏矽微电子Cortex-M0开发环境的建立及调试"

再点击查看下Flash Download的选项。这时候看到ME20F030单片机的FLash的下载地址和RAM空间地址都已经明确了,这就是为什么前面强烈推荐安装KEIL 5版本,随后打上PACK包,很多设置项都是PACK包整合配置好的,我们直接用就可以了。

“敏矽微电子Cortex-M0开发环境的建立及调试"

2、仿真选项设置好之后,开始下载程序。点击工具栏上的

“敏矽微电子Cortex-M0开发环境的建立及调试"

图标来下载并仿真程序。

“敏矽微电子Cortex-M0开发环境的建立及调试"

图标所示的功能也可以下载程序,但是它是不带仿真功能的,这点需要注意!下载成功后会多出下面的工具条。

“敏矽微电子Cortex-M0开发环境的建立及调试"

下面我们分别来介绍一下这些仿真调试按钮的功能:

1:复位,点击后程序会从头开始重新运行。

2:全速运行,点击后程序便开始全速运行,运行到断点处会停止,或者使用停着功能。

3:停止,当程序在运行状态下,使用此功能,程序便会停止运行。

4:执行进去,本质是单步运行,如果下一步是要执行的是个函数,那么就行进入到函数 里面,进行单步仿真。

5:段执行,也是单步运行,但不同的是,如果下一步是要执行的是个函数,那么会直接运行整个函数,并不会进入函数内部运行,它是直接以一整段代码为单位进行执行的。

6:执行跳去,当不需要再继续在某个函数里继续单步仿真时,执行此功能,就会直接执行完函数内剩余的代码,随后跳出该函数后会暂停,等待下一步操作。

7:执行到光标处,使用此功能前,先确定想运行到地方,鼠标单击运行的那一行,此时光标便会在这一行显示,这时候再点击此按钮,程序会全速运行,直到在光标处停止。

8:全速运行,此功能是让程序全速运行,除非认为暂停或者遇到断点,否则程序会一直运行。

来源:敏矽MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 256

为了使单独编译的C语言程序和汇编程序之间能够相互调用,必须为子程序之间的调用规定一定的规则,ATPCS就是ARM程序和THUMB程序中子程序调用的基本规则。

01、ATPCS

ATPCS即ARM Thumb Procedure Call Standard(ARM-Thumb过程调用标准)的简称,ATPCS规定了一些调用和被调用程序之间调用的基本规则,这些基本规则包括子程序调用过程中寄存器的使用规则、数据栈的使用规则、参数的传递规则。为适应一些特定的需要,对这些基本的调用规则进行一些修改得到几种不同的子程序调用规则,这些特定的调用规则包括:

  • 支持数据栈限制检查的ATPCS
  • 支持只读段位置无关的ATPCS
  • 支持可读写段位置无关的ATPCS
  • 支持ARM程序和THUMB程序混合使用的ATPCS

有调用关系的所有子程序必须遵守同一种ATPCS,编译器或者汇编器在ELF格式的目标文件中设置相应的属性,标识用户选定的ATPCS类型。对应不同类型的ATPCS规则,有相应的C语言库,连接器根据用户指定的ATPCS类型连接相应的C语言库。

使用ADS的C语言编译器编译的C语言子程序满足用户指定的ATPCS类型。而对于汇编语言程序来说,完全要依赖用户来保证各子程序满足选定的ATPCS类型。具体来说,汇编语言子程序必须满足下面三个条件:在子程序编写时必须遵守相应的ATPCS规则;数据栈的使用要遵守ATPCS规则;在汇编编译器中使用“--apcs”选项,使用“--apcs”选项并不影响代码的产生,编译器只是在各段中放置相应的属性,标识用户选定的属性。

02、ATPCS基本规则

基本ATPCS规定了在子程序调用时的一些基本规则,包括以下四个方面的内容:

  • 各寄存器的使用规则及其相应的名字
  • 数据栈的使用规则
  • 参数传递的规则
  • 函数结果返回的规则

相对于其他类型的TPCS,满足基本ATPCS的程序的执行速度更快,所占用的内存更少。但是它不能提供以下的支持:ARM程序和THUMB程序相互调用;数据以及代码的位置无关的支持;子程序的可重入性;数据栈检查的支持。而派生的其他几种特定的ATPCS就是在基本ATPCS的基础上再添加其他的规则而形成的 ,其目的就是提供上述的功能。

2.1 寄存器的使用规则

“Cortex-M0中断控制和系统控制(七)"

前四个寄存器R0~R3用于将参数值传递到例程中并将结果值传递出例程,并在例程中保存中间值(但通常仅在子例程调用之间),子程序通过寄存器R0~R3来传递参数,这时寄存器可以记作:A1~A4,被调用的子程序在返回前无需恢复寄存器R0~R3的内容。

在子程序中,使用R4~R11来保存局部变量,这时寄存器R4~R11可以记作:V1~V8 。如果在子程序中使用到V1~V8的某些寄存器,子程序进入时必须保存这些寄存器的值,在返回前必须恢复这些寄存器的值,对于子程序中没有用到的寄存器则不必执行这些操作。在THUMB程序中,通常只能使用寄存器R4~R7来保存局部变量。

寄存器R12用作子程序间暂存寄存器,记作IP;在子程序的连接代码段中经常会有这种使用规则。

寄存器R13用作数据栈指针,记做SP,在子程序中寄存器R13不能用做其他用途。寄存器SP在进入子程序时的值和退出子程序时的值必须相等。

寄存器R14用作连接寄存器,记作LR;它用于保存子程序的返回地址,如果在子程序中保存了返回地址,则R14可用作其它的用途。

寄存器R15是程序计数器,记作PC;它不能用作其它用途。

ATPCS中的各寄存器在ARM编译器和汇编器中都是预定义的。

2.2 数据栈的使用规则

栈指针通常可以指向不同的位置,当栈指针指向栈顶元素(即最后一个入栈的数据元素)时,称为Full栈。当栈指针指向与栈顶元素相邻的一个元素时,称为Empty栈。数据栈的增长方向也可以不同,当数据栈向内存减小的地址方向增长时,称为Descending栈。当数据栈向着内存地址增加的方向增长时,称为Ascending栈。

综合这两种特点可以由以下4种数据栈:

FD(FULL Descending):递增满栈
ED(Empty Descending):递增空栈
FA(FULL Ascending):递减满栈
EA(Empty Ascending):递减空栈 

ATPCS规定数据栈为FD类型,并对数据栈的操作是8字节对齐的,下面是一个数据栈的示例及相关的名词:

1、数据栈栈指针,stack pointer指向最后一个写入栈的数据的内存地址。

2、数据栈的基地址,stack base是指数据栈的最高地址。由于ATPCS中的数据栈是FD类型的,实际上数据栈中最早入栈数据占据的内存单元是基地址的下一个内存单元。

3、数据栈界限,stack limit是指数据栈中可以使用的最低的内存单元地址。

4、已占用的数据栈,used stack是指数据栈的基地址和数据栈栈指针之间的区域,其中包括数据栈栈指针对应的内存单元。

5、数据栈中的数据帧(stack frames) 是指在数据栈中,为子程序分配的用来保存寄存器和局部变量的区域。

VAL(SP) <= stack base, 
VAL(SP) >= VAL(SL) >= stack limit + 256, 
VAL(LR) = return address

异常中断的处理程序可以使用被中断程序的数据栈,这时用户要保证中断的程序数据栈足够大。使用ADS编译器产生的目标代码中包含了DRFAT2格式的数据帧。在调试过程中,调试器可以使用这些数据帧来查看数据栈中的相关信息。而对于汇编语言来说,用户必须使用FRAME伪操作来描述数据栈中的数据帧。ARM汇编器根据这些伪操作在目标文件中产生相应的DRFAT2格式的数据帧。

在ARMv5TE中,批量传送指令LDRD/STRD要求数据栈是8字节对齐的,以提高数据的传送速度。用ADS编译器产生的目标文件中,外部接口的数据栈都是8字节对齐的,并且编译器将告诉连接器:本目标文件中的数据栈是8字节对齐的。而对于汇编程序来说,如果目标文件中包含了外部调用,则必须满足以下条件:外部接口的数据栈一定是8位对齐的,也就是要保证在进入该汇编代码后,直到该汇编程序调用外部代码之间,数据栈的栈指针变化为偶数个字;在汇编程序中使用PRESERVE8伪操作告诉连接器,本汇编程序是8字节对齐的。

“Cortex-M0中断控制和系统控制(七)"

2.3 参数的传递规则

根据参数个数是否固定,可以将子程序分为参数个数固定的子程序和参数个数可变的子程序。这两种子程序的参数传递规则是不同的。

参数个数可变的子程序参数传递规则,对于参数个数可变的子程序,当参数不超过4个时,可以使用寄存器R0~R3来进行参数传递,当参数超过4个时,还可以使用数据栈来传递参数。在参数传递时,将所有参数看做是存放在连续的内存单元中的字数据。然后,依次将各名字数据传送到寄存器R0,R1,R2,R3;如果参数多于4个,将剩余的字数据传送到数据栈中,入栈的顺序与参数顺序相反,即最后一个字数据先入栈。按照上面的规则,一个浮点数参数可以通过寄存器传递,也可以通过数据栈传递,也可能一半通过寄存器传递,另一半通过数据栈传递。

参数个数固定的子程序参数传递规则,对于参数个数固定的子程序,参数传递与参数个数可变的子程序参数传递规则不同,如果系统包含浮点运算的硬件部件,浮点参数将按照下面的规则传递:各个浮点参数按顺序处理;为每个浮点参数分配FP寄存器;分配的方法是,满足该浮点参数需要的且编号最小的一组连续的FP寄存器。第一个整数参数通过寄存器R0~R3来传递,其他参数通过数据栈传递。

2.4 子程序结果返回规则

1、结果为一个32位的整数时或小于32位的整数值以保留符号和符号的方式扩展为32位值的范围,可以通过寄存器R0返回。

2、结果为一个64位整数时,一个64位整数值被视为两个32位整数值,可以通过R0和R1返回,依此类推。

3、对于位数更多的结果,需要通过调用内存来传递,任何其它类型的值(例如结构化值)将转换为32位整数字序列,通过将其复制到连续的内存字中。

“Cortex-M0中断控制和系统控制(七)"

根据上面简单的测试可以看出:

MOVS     r2,#0x03
MOVS     r1,#0x02
MOVS     r0,#0x01

通过寄存器R0~R3来传递参数:

ADDS     r0,r3,r1
ADDS     r0,r0,r2

通过寄存器R0返回。

03、特定的ATPCS

3.1 支持数据栈限制检查的ATPCS

如果在程序设计期间能够准确地计算出程序所需的内存总量,就不需要进行数据栈的检查,但是在通常情况下这是很难做到的,这时需要进行数据栈的检查。在进行数据栈的检查时,使用寄存器R10作为数据栈限制指针,这时寄存器R10又记作SL。用户在程序中不能控制该寄存器。具体来说,支持数据栈限制的ATPCS要满足下面的规则:在已经占有的栈的最低地址和SL之间必须有256字节的空间,也就是说,SL所指的内存地址必须比已经占用的栈的最低地址低256个字节。当中断处理程序可以使用用户的数据栈时,在已经占用的栈的最低地址和SL之间除了必须保留的256个字节的内存单元外,还必须为中断处理预留足够的内存空间;用户在程序中不能修改SL的值;数据栈栈指针SP的值必须不小于SL的值。

与支持数据栈限制检查的ATPCS相关的编译/汇编选项有下面几种:选项./ swst (编译过程中对输入文件使用堆栈检测)指示编译器生成的代码遵守支持数据栈限制检查的ATPCS,用户在程序设计期间不能够准确计算程序所需的数据栈大小时,需要指定该选项;选项./ noswst (编译过程中对输入文件不使用堆栈检测,这是编译器默认选项)指示编译器生成的代码不支持数据栈限制检查的功能,用户在程序设计期间能够准确计算出程序所需的数据栈大小,可以指定该选项;选项./ swst如果汇编程序对于是否进行数据栈检查无所谓,而与该汇编程序连接的其他程序指定了选项./ swst。

对于256字节或更少的帧,可以按如下方式检查:

CMP sp, sl
BHS no_ovf
BL |__16__rt_stkovf_split_small|
no_ovf

对于大于 256 字节的帧,可以如下方式检查:

LDR wr, framesize
ADD wr, sp
CMP wr, sl
BHS no_ovf
BL |__16__rt_stkovf_split_big|
no_ovf
MOV sp,wr
; ...
ALIGN
Framesize
DCD –Framesize

3.2 编写遵守支持数据栈限制检查的ATPCS的汇编语言程序

对于C程序和C++程序来说,如果在编译时指定了选项./swst,生成的目标代码将遵守支持数据栈限制检查的ATPCS。对于汇编语言程序来说,如果要遵守支持数据栈限制检查的ATPCS,用户在编写程序时必须满足支持数据栈限制检查的ATPCS所要求的规则,然后指定选项./swst,下面介绍用户编写汇编语言程序时的一些要求。

“Cortex-M0中断控制和系统控制(七)"

3.3 叶子子程序是指不调用别的程序的子程序

数据栈小于256字节的叶子子程序不许要进行数据栈检查,如果几个子程序组合起来构成的叶子子程序数据栈也小于256字节,这个规则同样适用;数据栈小于256字节的非叶子子程序可以使用下面的代码段来进行数据栈检查。

ARM程序使用:

SUB sp,sp,#size ;      #size 为sp和sl之间必须保留的空间大小
CMP sp,sl;
BLLO _ARM_stack_overflow

THUMB程序使用:

ADD sp,#-size ;         #size为sp和sl之间必须保留的空间大小
CMP sp,sl;
BLLO _THUMB_stack_overflow

数据栈大于256字节的子程序,为了保证SP的值不小于数据栈可用的内存单元最小的地址值,需要引入相应的寄存器。在使用超过 256 字节堆栈空间的例程中检查溢出更为复杂,不能简单地从SP减去帧大小。

在这种情况下,必须使用如下序列向限制检查代码建议SP的新值:

ARM程序使用下列代码:

SUB ip,sp,#size;
CMP ip,sl;
BLLO _ARM_stack_overflow

THUMB程序使用下列代码:

LDR wr,#-size;
ADD wr,sp;
CMP wr,sl;
BLLO _THUMB_stack_overflow

在编译或汇编时,/interwork (指定输入文件符合ARM/Thumb交互标准)告诉编译器或汇编器生成的目标代码遵守支持ARM-THUMB的ATPCS,它用在以下场合:

  • 程序中存在ARM程序调用THUMB程序的情况
  • 程序中存在THUMB程序调用ARM程序的情况
  • 需要连接器来进行ARM状态和THUMB状态切换的情况

在下述情况下使用选项/nointerwork:程序中不包含THUMB程序;用户自己进行ARM程序和THUMB程序切换。需要注意的是:在同一个C/C++程序中不能同时有ARM指令和THUMB指令。

“Cortex-M0中断控制和系统控制(七)"

__asm 关键字用于调用内联汇编程序,可以用在C或C++源码中内嵌汇编语言,如下所示:

“Cortex-M0中断控制和系统控制(七)"

ATPCS规则就是定义了函数传参以及返回数据的标准,定义了寄存器在函数调用时的作用。

本篇文章也是结合网上的资料和官方的资料加上自己的理解进行解读,可能不是很全面,详细的内容大家有兴趣可以自行查阅官方文档《The ARM-THUMB Procedure Call Standard》和《ARM Software Development Toolkit Version 2.50》。

来源:灵动MM32MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 20

Arm处理器是基于精简指令集计算机(RISC)原理设计的,指令集和相关译码机制较为简单,具有32位Arm指令集和16位Thumb指令集,Arm指令集效率高,但是代码密度低,而Thumb指令集具有更好的代码密度,却仍然保持Arm的大多数性能上的优势,它是Arm指令集的子集。所有Arm指令都是可以有条件执行的,而Thumb指令仅有一条指令具备条件执行功能。Arm程序和Thumb程序可相互调用,相互之间的状态切换开销几乎为零。

Cortex-M0处理器基于ARMv6-M架构,是一款功耗和性能较为均衡的处理器。Cortex-M0只支持56条指令的小指令集,其中大部分指令是16位指令。

Arm Cortex-M 指令集对比:

“”

01、指令集

1.1 在处理器内移动数据

MOV  <Rd>, <Rm> ;Rm and Rn can be high or low registers.
MOVS <Rd>, <Rm>
MOVS <Rd>, #immed8 ;8位立即数值
MRS  <Rd>, <SpecialReg>
MSR  <SpecialReg>, <Rd>

1.2 存储器访问

确保访问的内存地址是对齐的,这一点很重要。在ARMv6-M架构(包括Cortex-M0和Cortex-M0处理器)上不支持非对齐传输。任何未对齐内存访问的尝试都会导致HardFault异常。

LDR  <Rt>,[<Rn>, <Rm>]    ; Rt = memory[Rn + Rm]
STR  <Rt>,[<Rn>, <Rm>]    ; memory[Rn + Rm] = Rt
LDRH <Rt>,[<Rn>, <Rm>]    ; Rt = memory[Rn + Rm]
STRH <Rt>,[<Rn>, <Rm>]    ; memory[Rn + Rm] = Rt
LDRB <Rt>,[<Rn>, <Rm>]    ; Rt = memory[Rn + Rm]
STRB <Rt>,[<Rn>, <Rm>]    ; memory[Rn + Rm] = Rt
LDRSH <Rt>,[<Rn>, <Rm>]   ; Rt = SignExtend(memory[Rn + Rm])
LDRSB <Rt>,[<Rn>, <Rm>]   ; Rt = SignExtend(memory[Rn + Rm])
LDR  <Rt>,[<Rn>, #immed5] ; Rt = memory[Rn + ZeroExtend (#immed5<<2)]
STR  <Rt>,[<Rn>, #immed5] ; memory[Rn + ZeroExtend(#immed5<<2)] = Rt
LDRH <Rt>,[<Rn>, #immed5] ; Rt = memory[Rn + ZeroExtend (#immed5<<1)]
STRH <Rt>,[<Rn>, #immed5] ; memory[Rn + ZeroExtend(#immed5<<1)] = Rt
LDRB <Rt>,[<Rn>, #immed5] ; Rt = memory[Rn + ZeroExtend (#immed5)]
STRB <Rt>,[<Rn>, #immed5] ; memory[Rn + ZeroExtend(#immed5)] = Rt
LDR  <Rt>,[SP, #immed8]   ; Rt = memory[SP + ZeroExtend(#immed8<<2)]
STR  <Rt>,[SP, #immed8]   ; memory[SP + ZeroExtend(#immed8<<2)] = Rt
LDR  <Rt>,[PC, #immed8]   ; Rt =memory[WordAligned(PC+4)+ZeroExtend(#immed8<<2)]
LDR  <Rd>, =immed32       ; pseudo instruction translated to LDR <Rt>,[PC, #immed8]
LDR  <Rd>, label          ; pseudo instruction translated to LDR <Rt>,[PC, #immed8]
LDM <Rn>,{<Ra>, <Rb>,..} ; Load Multiple
// Ra = memory[Rn]
// Rb = memory[Rn + 4],
// ...
LDMIA <Rn>!, {<Ra>, <Rb>,..} ; Load Multiple Increment After
LDMFD <Rn>!, {<Ra>, <Rb>,..}
// Ra = memory[Rn],
// Rb = memory[Rn + 4],
// ...
// and then update Rn to last read address plus 4.
STMIA <Rn>!, {<Ra>, <Rb>,..} ; Store Multiple Increment After
STMEA <Rn>!, {<Ra>, <Rb>,..}
// memory[Rn] = Ra,
// memory[Rn + 4] = Rb,
// ...
// and then update Rn to last store address plus 4.

1.3 栈空间访问

PUSH {<Ra>, <Rb>, ..}
PUSH {<Ra>, <Rb>, .., LR}
POP  {<Ra>, <Rb>, ..}
POP  {<Ra>, <Rb>, .., PC}

1.4 算数运算

ADD  <Rd>, <Rm>          ; Rd = Rd + Rm. Rd, Rm can be high or low registers.
ADDS <Rd>, <Rn>, <Rm>    ; Rd = Rn + Rm
SUBS <Rd>, <Rn>, <Rm>    ; Rd = Rn – Rm
ADDS <Rd>, <Rn>, #immed3 ; Rd = Rn + ZeroExtend(#immed3)
SUBS <Rd>, <Rn>, #immed3 ; Rd = Rn – ZeroExtend(#immed3)
ADDS <Rd>, #immed8       ; Rd = Rd + ZeroExtend(#immed8)
SUBS <Rd>, #immed8       ; Rd = Rd – ZeroExtend(#immed8)
ADCS <Rd>, <Rd>, <Rm>    ; Rd = Rd + Rm + Carry
SBCS <Rd>, <Rd>, <Rm>    ; Rd = Rd – Rm – Borrow
ADD  SP, SP, #immed7     ; SP = SP + ZeroExtend(#immed7<<2)
SUB  SP, SP, #immed7     ; SP = SP – ZeroExtend(#immed7<<2)
ADD  SP, <Rm>            ; SP = SP + Rm. Rm can be high or low register.
ADD  <Rd>, SP, <Rd>      ; Rd = Rd + SP. Rd can be high or low register.
ADD  <Rd>, SP, #immed8   ; Rd = SP + ZeroExtend(#immed8<<2)
ADD  <Rd>, PC, #immed8   ; Rd = (PC[31:2]<<2) + ZeroExtend(#immed8<<2)
ADR  <Rd>, <label>       ; pseudo instruction translated to ADD <Rd>, PC, #immed8
RSBS <Rd>, <Rn>,#0       ; Rd = 0 – Rm, Reverse Subtract (negative)
MULS <Rd>, <Rm>, <Rd>    ; Rd = Rd * Rm
CMP <Rn>, #immed8        ; Rd – ZeroExtended(#immed8)
CMP <Rn>, <Rm>           ; Rn – Rm
CMN <Rn>, <Rm>           ; Rn – NEG(Rm)

1.5 逻辑运算

ANDS , ,  ; Rd = AND(Rd, Rm)ANDS <Rd>, <Rd>, <Rm> ; Rd = AND(Rd, Rm)
ORRS <Rd>, <Rd>, <Rm> ; Rd = OR(Rd, Rm)
EORS <Rd>, <Rd>, <Rm> ; Rd = XOR(Rd, Rm)
BICS <Rd>, <Rd>, <Rm> ; Rd = AND(Rd, NOT(Rm))
MVNS <Rd>, <Rm>       ; Rd = NOT(Rm)
TST  <Rn>, <Rm>       ; AND(Rn, Rm)

1.6 移位和循环操作

ASRS <Rd>, <Rm>, #immed5 ; Rd = Rm>>immed5
LSLS <Rd>, <Rm>, #immed5 ; Rd = Rm<<#immed5
LSRS <Rd>, <Rm>, #immed5 ; Rd = Rm>>#immed5
ASRS <Rd>, <Rd>, <Rm>    ; Rd = Rd>>Rm
LSLS <Rd>, <Rd>, <Rm>    ; Rd = Rd<<Rm
LSRS <Rd>, <Rd>, <Rm>    ; Rd = Rd>>Rm
RORS <Rd>, <Rd>, <Rm>    ; Rd = Rd rotate right by Rm bits
// Rotate_Left(Data, offset) = Rotate_Right(Data, (32-offset))

1.7 展开和顺序反转操作

这些反向指令通常用于在小端和之间转换数据大整数。

REV <Rd>, <Rm> ; Byte-Reverse Word
// Rd = {Rm[7:0], Rm[15:8], Rm[23:16], Rm[31:24]}
REV16 <Rd>, <Rm> ; Byte-Reverse Packed Half Word
// Rd = {Rm[23:16], Rm[31:24], Rm[7:0] , Rm[15:8]}
REVSH <Rd>, <Rm> ; Byte-Reverse Signed Half Word
// Rd = SignExtend({Rm[7:0] , Rm[15:8]})

1.8 扩展操作

它们通常用于数据类型转换。

SXTB <Rd>, <Rm> ; Signed Extended Byte
// Rd = SignExtend(Rm[7:0])
SXTH <Rd>, <Rm> ; Signed Extended Half Word
// Rd = SignExtend(Rm[15:0])
UXTB <Rd>, <Rm> ; Unsigned Extended Byte
// Rd = ZeroExtend(Rm[7:0])
UXTH <Rd>, <Rm> ; Unsigned Extended Half Word
// Rd = ZeroExtend(Rm[15:0])

1.9 程序流控制

B <label>       ; Branch, Branch range is ±2046 bytes of current PC
B<cond> <label> ; Conditional Branch, Branch range is ±254 bytes of current PC
BL <label>      ; Branch and Link, Branch range is ±16 MB of current PC
BX <Rm>         ; Branch and Exchange
BLX <Rm>        ; Branch and Link with Exchange

条件转移指令B

“Cortex-M0中断控制和系统控制(六)"

1.10 内存屏障指令

在Cortex-M0和Cortex-M0处理器上支持内存屏障指令,从而在Cortex-M处理器和其他ARM处理器家族中提供更好的兼容性。

//数据内存屏障,确保所有内存访问都完成

//在新的内存访问被提交之前。

DMB

//数据同步屏障,确保所有的内存访问都完成

//在执行下一条指令之前。

DSB

//指令同步障碍,刷新管道和

//确保之前所有的指令都已完成

//在执行新指令之前。

ISB

1.11 异常相关指令

SVC <immed8> ; Supervisor call
CPSIE I      ; Enable Interrupt (Clearing PRIMASK)
CPSID I      ; Disable Interrupt (Setting PRIMASK)

1.12 睡眠模式功能相关说明

//等待中断,停止程序执行,直到一个中断到达,

//如果处理器进入调试状态。

WFI

//等待事件,如果设置了内部事件寄存器,则清除

//内部事件注册和继续执行。

//停止程序执行,直到事件(如中断)到达

//如果处理器进入调试状态。

WFE

//发送事件,设置本地事件寄存器并发送一个事件脉冲

//多处理器系统中的其他微处理器。

SEV

1.13 其他说明

NOP           ; No Operation
BKPT <immed8> ; Break point
YIELD         ; Execute as NOP on the Cortex-M0 processor

02、指令说明

2.1 可访问high registers的指令

绝大部分指令只能访问low registers,也就是只能访问R0~R7寄存器。可以访问high registers的指令只有两条,这两条指令都不更新APSR,指令没有S后缀。

MOV  <Rd>, <Rm> ; Rm and Rn can be high or low registers.
ADD  <Rd>, <Rm> ; Rd = Rd + Rm. Rd, Rm can be high or low registers.

其它两条和SP加法有关的可以访问high registers的指令其本质是ADD指令。

ADD  SP, <Rm>        
ADD  <Rd>, SP, <Rd>  

2.2 分配临时变量的指令

函数内的临时变量分配到堆栈,进入函数给临时变量分配空间时使用SUB指令。

SUB  SP, SP, #immed7     ; SP = SP – ZeroExtend(#immed7<<2)

退出函数释放临时变量空间时使用ADD指令。

ADD  SP, SP, #immed7     ; SP = SP + ZeroExtend(#immed7<<2)

上面两条指令的立即数只有7位,最多可以增减SP指针127个字空间,如果超过127个字,使用这条指令:

ADD  SP, <Rm>            ; SP = SP + Rm. Rm can be high or low register.

只有ADD指令,没有SUB指令,如果需要SUB,那么给Rm赋值负数即可。

2.3 取临时变量地址的指令

在堆栈分配了临时变量空间后,总要取得临时变量的地址才能做进一步的操作。

ADD  <Rd>, SP, #immed8   ; Rd = SP + ZeroExtend(#immed8<<2)

立即数不够,可以用寄存器。

ADD  <Rd>, SP, <Rd>      ; Rd = Rd + SP. Rd can be high or low register.

2.4 RSBS指令

RSBS <Rd>, <Rn>, #0       ; Rd = 0 – Rm, Reverse Subtract (negative)

这是倒过来的减法,常量减去寄存器值,而且常量只能是0。所以这条指令实质上就是一条取负数指令。

Rd = 0 - Rm

等价于:Rd = -Rm

Rd 寄存器值等于负的 Rm 寄存器值。

来源:灵动MM32MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 68

OS能够支持多任务,能够以周期性地完成上下文的切换,以并行的架构处理任务,单一任务的崩溃并不会牵连到整个系统。上下文周期性切换需要一个定时器能够打断程序执行,SysTick定时器就可以提供必要的时钟节拍,为OS的任务调度提供一个有节奏的“心跳”。

SysTick定时器即系统滴答定时器,也称“心跳定时器”,它是一个24 位的倒计数定时器,计到0 时,将从重装载寄存器中自动重装载定时初值。只要不把它在SysTick 控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。

SysTick定时器被捆绑在NVIC中,用于产生SysTick异常(异常号:15),SysTick中断的优先级也可以设置。

它会根据节拍来工作,把整个时间段分成很多小小的时间片,而每个任务每次只能运行一个时间片的时间长度,超时就退出给别的任务运行,这样可以确保任何一个任务都不会霸占操作系统提供的各种定时功能,都与这个滴答定时器有关。

当启用时,定时器从重载值递减计数到零,在下一个时钟周期将重装载SYST_RVR 中的值,然后在后续时钟周期递减。将零值写入 SYST_RVR 会在下一次回调时禁用计数器。当计数器变为零时,COUNTFLAG 状态位设置为 1。读取 SYST_CSR 将 COUNTFLAG 位清除为 0。写入 SYST_CVR 将清除寄存器和 COUNTFLAG 状态位为 0,写入不会触发 SysTick 异常逻辑,读取该寄存器会在访问时返回其值。

01、SysTick寄存器

SysTick定时器主要由4个寄存器组成:

“Cortex-M0中断控制和系统控制(五)"

在CMSIS中系统控制寄存器结构体:

typedef struct
{
  __IO uint32_t CTRL; /*!< Offset: 0x00  SysTick Control and Status Register */
  __IO uint32_t LOAD; /*!< Offset: 0x04  SysTick Reload Value Register       */
  __IO uint32_t VAL;  /*!< Offset: 0x08  SysTick Current Value Register      */
  __I  uint32_t CALIB;/*!< Offset: 0x0C  SysTick Calibration Register        */
} SysTick_Type;

在Arm官方资料中4个寄存器的命名分别是SYST_CSR、SYST_RVR、SYST_CVR和SYST_CALIB,但是在CMSIS中进行了简化命名,更加清晰明了。

1.1、SysTick控制和状态寄存器(SYST_CSR)

“Cortex-M0中断控制和系统控制(五)"

CSR寄存器用到的位有4个,bit0用于是否开启定时器,置1表示使能SysTick定时器;bit1用于控制是否产生中断,该位置为1为允许产生中断;bit2用于设置定时器的时钟源,设为1,定时器的时钟源为主时钟,反之设为0的话定时器的时钟源为主时钟的四分之一。

MM32F0130系列的SysTick的HCLK来源于AHB总线经过硬件4分频,FCLK直接来源于AHB时钟总线。

“Cortex-M0中断控制和系统控制(五)"

当 SysTick 定时器从1计到0时,它将把COUNTFLAG位置位;

而下述方法可以清零:

读取 SysTick 控制及状态寄存器(STCSR)

往 SysTick 当前值寄存器(STCVR)中写任何数据

1.2、SysTick重装载寄存器(SYST_RVR)

“Cortex-M0中断控制和系统控制(五)"

RVR寄存器用到0~23位,这个值是定时器倒计时的初始值,打开定时器以后,就会从这里设置的值倒计时到0,倒计时到0以后,又会从此值开始倒计时。

1.3、SysTick当前值寄存器(SYST_CVR)

“Cortex-M0中断控制和系统控制(五)"

CVR寄存器也是用到0~23位,这是一个状态寄存器,当定时器开始运作的时候,这个值在不断的变化,从RVR寄存器获取初值以后,倒计时到0。

CURRENT:读此寄存器返回系统定时器的当前值,给这个寄存器赋值,将使定时器归0,且清CTRL中的COUNTFLAG位。

1.4、SysTick当前值寄存器(SYST_CALIB)

“Cortex-M0中断控制和系统控制(五)"

如果不知道校准信息,则根据处理器时钟或外部时钟的频率计算所需的校准值。

校准值寄存器提供了这样一个解决方案:它使系统即使在不同的CM0产品上运行,也能产生恒定的SysTick中断频率。最简单的作法就是:直接把TENMS的值写入重装载寄存器,这样一来,只要没突破系统极限,就能做到每10ms来一次 SysTick异常。如果需要其它的SysTick异常周期,则可以根据TENMS的值加以比例计算。

在系统定时器的四个寄存器中,SYST_CALIB为校准寄存器,这个是在出厂之前就已经配置好了的,我们不必考虑这个寄存器。

02、SysTick编程

SysTick配置需要遵循一定的流程:

1、 开始

2、 禁止SysTick

3、 设置重装值寄存器

4、 清除当前值寄存器

5、 使能SysTick

6、 完成

SysTick操作相关函数有:

__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks);
void SysTick_CLKSourceConfig(u32 systick_clk_source);
void RCC_SystickDisable(void);
void RCC_SystickEnable(u32 sys_tick_period);

下面的代码演示启用 SysTick 的基本程序:

; 使能SysTick定时器,并且使能SysTick异常
LDR R0, =0xE000E010    ; 加载STCSR的地址
MOV R1, #0
STR R1, [R0]           ; 先停止SysTick,以防意外产生异常请求
LDR R1, =0x3FF         ; 让SysTick每1024周期计完一次。因为是从1023数到
                       ; 0,总共数了1024个周期,所以加载值为0x3FF
STR R1, [R0,#4]        ; 写入重装载的值
STR R1, [R0,#8]        ; 往STCVR中写任意的数,以确保清除COUNTFLAG标志
MOV R1, #0x7           ; 选择FCLK作为时钟源,并使能SysTick及其异常请求
STR R1, [R0]           ; 写入数值,开启定时器

在CMSIS库中有定义对应的配置函数:

__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
/* Reload value impossible */
    if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) {
        return (1UL);                              
    }
/* set reload register */
    SysTick->LOAD  = (uint32_t)(ticks - 1UL);   
/* set Priority for Systick Interrupt */
    NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); 
/* Load the SysTick Counter Value */
    SysTick->VAL   = 0UL;   
/* Enable SysTick IRQ and SysTick Timer */                       
    SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |
                     SysTick_CTRL_TICKINT_Msk   |
                     SysTick_CTRL_ENABLE_Msk;   
/* Function successful */
    return (0UL);                          
}

SysTick可以通过轮询或者中断方式进行操作,使用轮询的程序可以读取SysTick控制和状态寄存器,检查COUNTFLAG,如果该位置位,则表明SysTick计数已减到0。

中断方式延时参考程序:

static __IO uint32_t TimingDelay;
void Delay(__IO uint32_t nTime)
{ 
     TimingDelay = nTime;
     while(TimingDelay != 0);
}

void SysTick_Handler(void)
{
     if (TimingDelay != 0x00) 
     { 
       TimingDelay--;
     }
}

int main(void)
{ 
     //systick时钟为HCLK,中断时间间隔1ms
     if (SysTick_Config(SystemCoreClock / 1000)) 
     {
         while (1);
     }
     while(1)
     { 
         Delay(200);//200ms
     }
}

轮询方式延时参考程序:

void delay_init()
{
    SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div4);   //选择外部时钟HCLK/4
    //为系统时钟的1/4,实际上也就是在计算1usSysTick的VAL减的数目
    fac_us=SystemCoreClock/4000000; 
    //代表每个ms需要的systick时钟数,即每毫秒SysTick的VAL减的数目
    fac_ms=(u16)fac_us*1000;        
} 

void delay_ms(u16 nms)
{                  
    u32 temp;          
    SysTick->LOAD=(u32)nms*fac_ms;          //时间加载(SysTick->LOAD为24bit)
    SysTick->VAL =0x00;           //清空计数器
    SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;          //开始倒数  
    do
    {
        temp=SysTick->CTRL;
     }while((temp&0x01)&&!(temp&(1<<16)));   //等待时间到达,看CTRL的第16位(COUNTFLAG)是否为1,看STRL的第0位(ENABLE)是否为1   
     SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;       //关闭计数器
     SysTick->VAL =0X00;       //清空计数器        
}

void delay_us(u32 nus)
{        
    u32 temp;            
    SysTick->LOAD=nus*fac_us;               //时间加载           
    SysTick->VAL=0x00;        //清空计数器
    SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;  //开始倒数      
    do
    {
        temp=SysTick->CTRL;
    }while((temp&0x01)&&!(temp&(1<<16)));        //等待时间到达   
    SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;  //关闭计数器
    SysTick->VAL =0X00;       //清空计数器    
}

SysTick定时器除了能服务于操作系统之外,还能用于其它目的:如时间测量、定时或者闹铃等。

来源:灵动MM32MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 53

Cortex-M0系统控制块(SCB)是内核外设的主要模块之一,提供系统控制以及系统执行信息,包括配置,控制,上报系统异常等。

“

为了提高软件效率,CMSIS简化了SCB寄存器表示,在CMSIS中系统控制寄存器结构体:

typedef struct 
{
    __IM  uint32_t CPUID; /*!< Offset: 0x000 (R/ )  CPUID Base Register */
    __IOM uint32_t ICSR;  /*!< Offset: 0x004 (R/W)  Interrupt Control and State Register */
    uint32_t RESERVED0;
    __IOM uint32_t AIRCR; /*!< Offset: 0x00C (R/W)  Application Interrupt and Reset Control Register */
    __IOM uint32_t SCR; /*!< Offset: 0x010 (R/W)  System Control Register */
    __IOM uint32_t CCR; /*!< Offset:0x014 (R/W) Configuration Control Register */
    uint32_t RESERVED1;
    __IOM uint32_t SHP[2U];/*!< Offset: 0x01C (R/W)  System Handlers Priority Registers. [0] is RESERVED */
    __IOM uint32_t SHCSR;  /*!< Offset: 0x024 (R/W)  System Handler Control and State Register */
} SCB_Type;

01、CPUID

CPUID基地址寄存器包含处理器型号、版本等相关信息,是只读的,可以通过应用软件、调试器和烧录器等获取处理器的类型和版本信息。

Address: 0xE000ED00

Reset value: 0x410CC200

“

这个地方CPUID与我们经常提到的MCU的96位UID不同,CPUID是处理器的ID号,由Arm提供并实现,通过CPUID可以知道内核型号及版本等信息。而96位UID是MCU产品ID,属于MM32,由上海灵动微电子股份有限公司提供并按照一定的规则实现,96位的产品唯一身份标识所提供的参考号码对任意一个系列微控制器,在任何情况下都是唯一的。用户在何种情况下,都不能修改这个身份标识。

MCU还有一个DEV_ID编码,这个ID定义了MCU的器件号和硅片版本号,它是DBG_MCU的一个组成部分,并且映射到外部APB总线上。SW 调试口(2个引脚) 或通过用户代码都可以访问此编码。

DEV_ID地址:0x40013400 只支持32位访问,只读。

“

在CMSIS驱动库中,可以直接使用“ SCB->CPUID ” 获取处理器ID。读取MM32F0130的CPUID、UID和DEV_ID如下所示:

“

CPUID (0x410CC200)解析处理器信息:

“

02、ICSR(Interrupt Control and State Register)

提供:

  • NMI异常的设置挂起位
  • 为PendSV和SysTick异常设置挂起和清除挂起位

表示:

  • 正在处理的异常的异常编号
  • 是否有被抢占的活动异常
  • 最高优先级未决异常的异常编号
  • 是否有任何中断待处理

Address: 0xE000ED04

Reset value: 0x0000 0000

“

ICSR中的某些控制位仅供调试使用,大多数情况下,应用程序只会用ICSR来控制或者检查系统异常挂起状态。

PendSV(可挂起的系统调用)异常对 OS 操作非常重要,其优先级可以通过编程设置。可以通过将中断控制和壮态寄存器 ICSR 的 bit28挂起位置1来触发PendSV中断。与SVC异常不同,它是不精确的,因此它的挂起状态可在更高优先级异常处理内设置,且会在高优先级处理完成后执行。利用该特性,若将PendSV设置为最低的异常优先级,可以让PendSV异常处理在所有其他中断处理完成后执行,这对于上下文切换非常有用,也是各种OS设计中的关键。在具有嵌入式OS的典型系统中,处理时间被划分为了多个时间片。

通过向中断控制和状态寄存器 ICSR 的 bit28 写入1挂起PendSV来启动PendSV中断,如果中断启用且有编写 PendSV 异常服务函数的话,则内核会响应 PendSV 异常,去执行PendSV 异常服务函数,这样就可以在PendSV中断服务函数中进行任务切换了。

03、AIRCR(Application Interrupt and Reset Control Register)

AIRCR为数据访问和系统的复位控制提供字节序状态。

要写入该寄存器,您必须写入0x05FA VECTKEY 字段,否则处理器将忽略写入。

Address: 0xE000ED0C

Reset value: 0xFA05 0000

“

任何对该寄存器的写操作,都必须将0x05FA写入到AIRCR[30:16],否则写操作将无效,若需要半字读取,需要写入0xFA05。

应用程序中系统执行软复位函数:

__STATIC_INLINE void NVIC_SystemReset(void)
{
    __DSB();  //确保所有未完成的内存访问包括缓冲写入在重置之前完成
    SCB->AIRCR  = ((0x5FAUL << SCB_AIRCR_VECTKEY_Pos) |
                   SCB_AIRCR_SYSRESETREQ_Msk);
    __DSB();    //确保完成内存访问

    for(;;) /* wait until reset */
    {   
        __NOP();
    }
}

04、SCR(System Control Register)

SCR 控制进入和退出低功耗状态的特性。

Address: 0xE000ED10

Reset value: 0x0000 0000

“

SCR(系统控制)寄存器可以选择使用立即休眠还是退出时休眠,当SCR寄存器的SLEEPONEXIT位为0的时候使用立即休眠,当为 1 的时候使用退出时休眠。

MM32F0130执行进入stop模式实现函数:

void PWR_EnterSTOPMode(u32 regulator, u8 stop_entry)
{
    PWR->CR |= regulator;

    SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;

    (stop_entry == PWR_STOPEntry_WFI) ? __WFI() : __WFE();
}

05、CCR(Configuration and Control Register)

CCR 是只读寄存器,指示 Cortex-M0 处理器行为的某些方面。

Address: 0xE000ED14

Reset value: 0x0000 0204

“Cortex-M0中断控制和系统控制(四)"

SCB->CCR寄存器控制除数为零和未对齐内存访问是否触发用法HardFault。

06、SHPR(System Handler Priority Registers)

因为在ARMv7-M架构上才有SHPR1,所以 Cortex – M0使用系统优先级寄存器只有SHPR2和SHPR3,而没有SHPR1。

SHPR2-SHPR3 寄存器设置具有可配置优先级的异常处理程序的优先级级别,从0到192。

SHPR2-SHPR3 是可字访问的。

要使用 CMSIS 访问系统异常优先级,可以使用CMSIS函数:

uint32_t NVIC_GetPriority(IRQn_Type IRQn)
void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)

系统故障处理程序,以及每个处理程序的优先级字段和寄存器是:

“

6.1、SHPR2 (System Handler Priority Register 2)

Address: 0xE000ED1C

Reset value: 0x0000 0000

“

6.2、SHPR3 (System Handler Priority Register 3)

如果您的设备未实现 SysTick 计时器,则此字段为保留字段

Address: 0xE000 ED20

Reset value: 0x0000 0000

“

SVCall、PendSV和SysTick可编程的中断多用于在操作系统之上的软件开发中。SVC用于产生系统函数的调用请求,例如,操作系统不让用户程序直接访问硬件,而是通过提供一些系统服务函数,用户程序使用SVC发出对系统服务函数的呼叫请求,以这种方法调用它们来间接访问硬件。因此,当用户程序想要控制特定的硬件时,它就会产生一个SVC异常,然后操作系统提供的SVC异常服务例程得到执行,它再调用相关的操作系统函数,后者完成用户程序请求的服务。

另一个相关的异常是 PendSV,它和 SVC 协同使用。一方面, SVC异常是必须立即得到响应的(若因优先级不比当前正处理的高, 或是其它原因使之无法立即响应, 将上访成Hardfault),应用程序执行 SVC 时都是希望所需的请求立即得到响应。另一方面,PendSV则不同,它是可以像普通的中断一样被挂起的(不像SVC那样会上访)。OS可以利用它“延期执行”一个异常——直到其它重要的任务完成后才执行动作。挂起 PendSV 的方法是:程序中往NVIC的PendSV挂起寄存器中写 1。挂起后, 如果优先级不够高,则将延期等待执行。PendSV 的典型使用场合是在上下文切换时(在不同任务之间切换)。有两个就绪的任务,上下文切换被触发的场合可以是:

  • 执行一个系统调用
  • 系统滴答定时器(SysTick)中断,(轮转调度中需要)

ARMv7-M和ARMv6-M都有的SCB寄存器名称相同,但是ARMv7-M寄存器数量和有效控制bit位比ARMv6-M丰富了不少。一般来说,ARMv6-M向上兼容ARMv7-M,这意味着应用层和系统为ARMv6-M开发的级别软件无需修改即可在 ARMv7-M 上执行,这也是Arm从一个平台切换到另一个平台可以很快速的完成切换的原因,有兴趣深入研究可以参考《ARMv6-M Architecture Reference Manual》白皮书。

来源:灵动MM32MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 69

页面

订阅 RSS - Cortex-M0