技术
ARM指令集中,LDR通常都是作加载指令的,但是它也可以作伪指令。
LDR伪指令的形式是“LDR Rn,=expr”。下面举一个例子来说明它的用法。
COUNT EQU 0x40003100
……
LDR R1,=COUNT
MOV R0,#0
STR R0,[R1]
COUNT是我们定义的一个变量,地址为0x40003100。这中定义方法在汇编语言中是很常见的,如果使用过单片机的话,应该都熟悉这种用法。
LDR R1,=COUNT是将COUNT这个变量的地址,也就是0x40003100放到R1中。
在单片机的BCD增量算式、线性化处理等过程中,都会遇到一个共同的问题,那就是小数的运算。在单片机当中,对于小数的表现方法一般只有两种,一种是浮点数,一种是定点数。本文就将对单片机中的浮点数进行概述并对其汇编程序设计进行介绍。
浮点数结构有其自身的优点,其能够以固定的字节长度保持相对精度不变,用较少的字节表示很大的数的范围,便于存储和运算,在处理的数据范围较大和要求精度较高时,采用浮点数。
<strong>浮点数概念</strong>
常用科学计数法来表示一个十进制数如:
l234.75=1.23475E3=1.23475×103(10的3次方)
<strong>一、Cortex M3的GPIO口特性</strong>
在介绍GPIO口功能前,有必要先说明一下M3的结构框图,这样能够更好理解总线结构和GPIO所处的位置。
<center><img src="http://mm32.eetrend.com/files/2016-08/wen_zhang_/100002619-8432-1.jpg&q…; alt=""></center>
一些工控产品,当系统复位后(非上电复位),可能要求保持住复位前RAM中的数据,用来快速恢复现场,或者不至于因瞬间复位而重启现场设备。而keil mdk在默认情况下,任何形式的复位都会将RAM区的非初始化变量数据清零。如何设置非初始化数据变量不被零初始化,这是本篇文章所要探讨的。
在给出方法之前,先来了解一下代码和数据的存放规则、属性,以及复位后为何默认非初始化变量所在RAM都被初始化为零了呢。
什么是初始化数据变量,什么又是非初始化数据变量?(因为我的文字描述不一定准确,所以喜欢举一些例子来辅助理解文字。)
定义一个变量:int nTimerCount=20;变量nTimerCount就是初始化变量,也就是已经有初值;
预处理过程扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器。可见预处理过程先于编译器对源代码进行处理。
在C语言中,并没有任何内在的机制来完成如下一些功能:在编译时包含其他源文件、定义宏、根据条件决定编译时是否包含某些代码。要完成这些工作,就需要使用预处理程序。尽管在目前绝大多数编译器都包含了预处理程序,但通常认为它们是独立于编译器的。预处理过程读入源代码,检查包含预处理指令的语句和宏定义,并对源代码进行响应的转换。预处理过程还会删除程序中的注释和多余的空白字符。
预处理指令是以#号开头的代码行。#号必须是该行除了任何空白字符外的第一个字符。#后是指令关键字,在关键字和#号之间允许存在任意个数的空白字符。整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换。下面是部分预处理指令:
本文将介绍Keil提供各种窗口如输出窗口、观察窗口、存储器窗口、反汇编窗口、串行窗口等的用途,以及这些窗口的使用方法,并通过实例介绍这些窗口在调试中的使用。
一、程序调试时的常用窗口
Keil软件在调试程序时提供了多个窗口,主要包括输出窗口(Output Windows)、观察窗口(Watch&Call Statck Windows)、存储器窗口(Memory Window)、反汇编窗口(Dissambly Window)串行窗口(Serial Window)等。进入调试模式后,可以通过菜单View下的相应命令打开或关闭这些窗口。
如何建立工程、汇编、连接工程,并获得目标代码,这仅仅代表你的源程序没有语法错误,至于源程序中存在着的其它错误,必须通过调试才能发现并解决,事实上,除了极简单的程序以外,绝大部份的程序都要通过反复调试才能得到 正确的结果,因此,调试是软件开发中重要的一个环节,本文将介绍常用的调试命令、利用在线汇编、各种设置断点进行程序调试的方法,并通过实例介绍这些方法的使用。
正如汽车代替了马车,电子邮件代替了普通邮件一样,32位微控制器(MCU)让8位MCU变得黯然失色。尽管未来8位MCU朝向32位MCU发展将会成为现实,但目前还没那么容易实现。事实证明8位MCU和32位MCU仍是互补的技术,在一些方面各有千秋,而在其它方面的表现却同样出色。这其中的窍门在于厘清何种应用适合哪种MCU架构。
本文比较了8位MCU和32位MCU的使用案例,可作为如何选择这两种MCU架构的指南使用。
本文大部分32位范例将关注于ARM Cortex-M装置,Cortex-M在不同MCU供货商产品组合中表现非常相似。由于8位MCU有很多种架构,所以很难对8位供货商之间进行类似的产品比较。为了进行比较,本文将使用广泛应用、易于理解的8051 8位架构。
keil MDK也是可以借助h-jtag进行单步调试,写出来与大家一起分享一下:
keil MDK编译器使用V4.01版本,下载地址:
http://www.embedinfo.com/down-list.asp?id=714 (需要注册一下)
h-jtag使用V1.0版本(请注意,一定要用V1.0或者以上版本才可以与mdk兼容),下载地址
<strong>引言</strong>
在嵌入式软件系统开发过程中,大量使用C语言进行应用程序开发以提高开发效率。同时,系统中经常包含一些决定整个系统性能的关键模块,此时为了获得最佳性能,经常使用汇编语言编写它们,或者某些特殊情况下,例如操作硬件等,也必须使用汇编语言。
函数是C语言中一个重要的概念,在汇编语言中经常使用子例程或过程(subroutine or procedure)表达同样的概念,本文使用术语子例程。本文首先介绍ARM汇编语言子例程设计的一般方法,并以此为基础提出一种新的基于堆栈帧的设计方法,同时介绍与C语言交互技术。
<strong>1、 一般方法</strong>
在项目处于调试期间,Fault处理程序可能只是一个断点指令,调试器遇到这个指令后停止程序的运行。默认情况下,由于非硬Fault被禁能,所有发生的非Fault都会上访成硬Fault,因此只要在硬Fault处理程序中设置一个断点,就可以观察所有Fault信息。当使用MDK-ARM的RealView编译器时,你可以用下面的C代码替代默认硬Fault处理程序,这段代码检测产品是否连接到一个调试器,只有在连接到一个调试器的情况下,才会执行断点指令。
<strong>1、摘要 </strong>
Cortex-M内核实现了一个高效异常处理模块,可以捕获非法内存访问和数个程序错误条件。本应用笔记从程序员角度描述Cortex-M Fault异常,并且讲述在软件开发周期中的Fault用法。
<strong>2、简介 </strong>
Cortex-M3(以下简称CM3)和Cortex-M4(以下简称CM4)内核的Fault异常可以捕获非法内存方法和非法编程行为。Fault异常能够检测到以下情况:
总线Fault:在取址、数据读/写、取中断向量、进入/退出中断时寄存器堆栈操作(入栈/出栈)时检测到内存访问错误。
存储器管理Fault:检测到内存访问违反了MPU定义的区域。
<strong>一、存储器格式(字对齐):</strong>
Arm体系结构将存储器看做是从零地址开始的字节的线性组合。从零字节到三字节放置第一个存储的字(32位)数据,从第四个字节到第七个字节放置第二个存储的字数据,一次排列。作为32位的微处理器,arm体系结构所支持的最大寻址空间为4GB。
存储器格式
1、大端格式:高字节在低地址,低字节在高地址;
2、小端格式:高字节在高地址,低字节在低地址;
指令长度:
单片机中有象箱子功能一样的地方,我们称为寄存器,用来暂存数据。寄存器的种类有程序计数器、通用寄存器、以及SFR(特殊功能寄存器)等。
SFR主要用来设定外围功能电路(计数器或串行端口、通用I/O等)的工作方式,确认其工作状况,并对其进行控制的。也就是说SFR并非仅仅只是用来保存数据的“箱子”。通过改变保存在“箱子”里的数据,不仅可以改变外围功能电路的动作方式,而且“箱子”里的数据也将随着外围功能电路的工作状況而改变。
控制外围功能电路的基础知识
下面以通用I/O为例来说明单片机对外围功能电路的控制。通用I/O具有以下功能:
输出功能:可以输出高电平电压或低电平电压
输入功能:可以读出输入到引脚的电压电平
首先来看输出功能的控制。图1中的引脚A是一个通用I/O。
<strong> CPU懂的机器语言</strong>
单片机的CPU从存储器读取程序,但是一次只能读取一条指令,然后解释每条指令,并执行。存储器中保存的内容,不管是程序还是数据,都是二进制代码“0”和“1”组成的字符串。指令二进制代码告诉CPU要做什么,而数据二进制代码则是CPU操作或处理指令时要使用的值。CPU的操作包含加、减运算等指令。这些像密码一样排列的“0”和“1”字符串就是机器语言。比如图1左边显示的就是一个机器语言指令,意思是“将2放入寄存器A(寄存器是CPU内部的储存区域)。
CPU总是按存储器地址的顺序读取指令代码,除非遇到跳跃指令。例如,如果复位后的地址是0000,则从0000开始按0001、0002、0003的顺序读取并执行指令。也可以说,一个程序就是按处理要求排列一系列的机器语言。
新建一个IAR工程有两种方法,新建工程有两种方法,一种是使用工程模板,另一种是使用已存在的工程来建立另外一个工程。这里讲第一个方法,第二个方法很简单,书稿上有阐述。
<strong>一、新建第一个IAR工程</strong>
用IAR首先要新建的是工作区,而不是工程。在工作区里再建立工程。
<strong>1)建立工作区间</strong>
新建IAR工作空间,首先是菜单File里选择Open再选择Workspace,如下图红圈所示。
IAR-C有着强大的软件仿真功能,但其中的寄存器位操作定义却十分烦琐,并且编译后生成的ASM代码冗余较多,针对该问题,版主自已定义一个位操作定义的方法,初学者可参考定义,并且该种方法可应用于所有寄存器位操作定义。
共实现置位--Set_Bit, 清位--Clr_Bit,取反位Com_Bit,测试位Test_Bit四种位操作功能,并且每条位操作定义仅需一条3字节的ASM指令序列即可,简便直接。
//*****************************************************
//P00位操作定义
#define Set_P00 (P0 = P0 | 0x1)
#define Clr_P00 (P0 = P0 & ~0x1)
对于每个单片机爱好者及工程开发设计人员,在刚接触单片机的那最初的青葱岁月里,都有过点亮跑马灯的经历。从看到那一排排小灯按着我们的想法在跳动时激动心情。到随着经验越多,越来又会感觉到这个小灯是个好东西,尤其是在调试资源有限的环境中,有时会帮上大忙。
但对于绝大多数人,我们在最最初让灯闪烁起来时大约都会用到阻塞延时实现,会像如下代码的样子:
while(1)
{
LED =OFF;
Delay_ms(500);
LED = ON;
Delay_ms(500);
}
然后,在我们接触到定时器,我们会发现,原来用定时中断来处理会更好。比如我们可以500ms中断一次,让灯亮或灭,其余的时间系统还可以做非常之多的事情,效率一下提升了很多。
当所有的系统初始化工作完成之后,就需要把程序流程转入主应用程序,即呼叫主应用程序。最简单的一种情况是:
IMPORT main
B main
直接从启动代码跳转到应用程序的主函数入口,当然主函数名字可以由用户随便定义。
在ARM ADS环境中,还另外提供了一套系统级的呼叫机制。
IMPORT __main
B __main
__main()是编译系统提供的一个函数,负责完成库函数的初始化和初始化应用程序执行环境,最后自动跳转到main()。所以说,前者是库函数,后者就是我们自己编写的main()主函数;