象棋小子 1048272975
ARM处理器作为当今最流行的芯片,已经应用在我们生活中的方方面面。从简单的电饭锅控制到小米八核千元神机、Nokia 2000万拍照利器,都可以看到ARM处理器的身影。ARM做为一种技术,已经涵盖了各种领域低端、中端、高端的应用,因此,学习和掌握ARM处理器的开发技术是一个不错的选择。笔者不才,就自己的看法简单谈谈ARM的入门学习,给读者一个初步的指导方向。
ARM(AdvancedRISC Machines),既可以认为是一个公司的名字,也可以认为是对一类微处理器的通称。ARM公司是知识产权供应商,是设计公司,本身不生产销售芯片,而是设计出高效的IP内核,授权给各个半导体公司使用。由于ARM内核耗电少、成本低、功能强,得到了众多半导体厂家的大力支持,包括TI、Philips、Intel、Samsung等,使得ARM获得了广泛的应用,确立了ARM技术的市场领先地位。
ARM采用RISC(Reduced Instruction Set Computer)微处理器架构,RISC架构的特点就是采用固定长度的指令格式,通过大量使用寄存器来提高数据的操作处理,使指令执行速度更快,寻址方式灵活简单,执行效率高。
目前ARM处理器根据不同的应用需求,划分为几个不同的系列。其中我们较常见的,并且市场正在应用的有ARM7、ARM9、ARM10、ARM11、Cortex系列,对应的版本由低到高,相应的处理器架构为ARMv4、ARMv5、ARMv6、ARMv7。其中ARM7/ARM9主要面向中低端市场,用于竞争8位、16位微处理器的份额。这里主要说一下采用ARMv7架构的Cortex系列,Cortex系列分为Cortex-A、Cortex-R、Cortex-M共3类。Cortex-A为传统的、基于虚拟存储的操作系统和应用程序而设计的(目前大部分手机、平板等高端设备采用这个系列的处理器),支持ARM、Thumb、和Thumb-2指令集。Cortex-R针对实时系统设计,支持ARM、Thumb、Thumb-2指令集。Cortex-M为对价格敏感的产品设计,只支持Thumb-2指令集,如用在控制领域Cortex-M0、Cortex-M3,价格低廉,性能优越,对8位、16位微控制器造成较大的冲击。
根据选择的ARM处理器系列,选择能编译相对应指令集的编译器即可。ARM编译器有Keil MDK、IAR、arm-linux-gcc等,其中MDK、IAR均是在windows下集成开发环境,极易使用。其中IAR支持涵盖ARM7到Cortex-A的各个系列,代码编译效率高。arm-linux-gcc交叉编译工具链也可强劲支持各个系列的ARM处理器,尤其是使用开源项目的代码或开发嵌入式linux方面,几乎无疑选用arm-linux-gcc,但arm-linux-gcc是需要在linux环境下运行的,通过Cygwin或虚拟机虽可在windows下构建一个linux运行环境,但对初学都来说,arm-linux-gcc还是比较难掌握的。Keil MDK对于学习过51单片机的读者应该不陌生,开发51单片机用Keil C51支持尤为强劲,而MDK就是Keil用来开发ARM的集成开发环境。MDK只能支持ARM7、ARM9、Cortex-M这些系列ARM处理器,高端的ARM并没有支持,对于开发中低端的ARM处理器,选择MDK编译器是不错的选择。(Keil公司已被ARM公司收购,ARM公司的官方编译器为MDK和RVDS)
图2-1 MDK集成开发环境
笔者此处也是选用MDK编译器作为入门开发讲解。各个系统的ARM处理器架构有些不一样,并且同一架构的ARM处理器各个厂商的具体芯片使用均是不一样的。一般弄清其中一个系列的架构即可,其它都是通用处理器相关的内容,技术学其一是能通其它的,往往根据之前的积累看新的处理器技术手册就能很快掌握新的处理器使用。笔者此处选ARM7作为讲解,当然ARM9是完全兼容ARM7指令的,学习了ARM7,ARM9也同时掌握了。具体到芯片型号,笔者此处选用原Philips半导体公司NXP的LPC2100系列,以LPC2103为例。
进行ARM编程前,是必须先了解ARM的体系结构的,包括ARM的处理器模式、寄存器组织、异常、寻址方式等。体系结构相关的内容都是会直接涉及到汇编处理的过程,因此,学习相关知识的时候会同时涉及到汇编程序的学习。汇编对于arm开发,是比较重要的,尤其是处理操作系统底层的代码或bootloader之类代码,是必须要用汇编实现体系结构相关的代码,其它像c之类的高级语言均是无能为力的。笔者在文章的末尾给出了周立功ARM教程PPT,包括ARM7的体系结构,汇编指令讲解这些内容,是相当不错的教材,以供大家学习。
学习一门完全没有接触过的技术,是需要一个逐步感性的认识过程的。对于初学者,不建议直接买开发板学习,因为没有足够的认识之前,往往会迷失方向,不知从何处学起,如果反复的上电,连下载线下载代码,却并不知道代码为什么要这样写,会消耗个人的斗志。笔者此处推荐Proteus仿真软件,Proteus仿真软件最成功的就是仿真51单片机的运行,但Proteus也是可以仿真ARM7运行的,当然支持的芯片极为有限,仅支持NXP的LPC2100系列的几款。但这对于我们的前期学习是完全足够了,这也是为什么笔者前面选择NXP LPC2103 这款ARM7的原因。软件仿真的好处就是零成本,可以直接改代码,省去开发板连下载线下载代码这些繁琐的步骤,方便验证,并且可以跟踪每一行代码的运行实现。Proteus仿真软件界面如图3-1。仿真软件只能仿真一些简单的代码,并且仿真结果也并不一定百分百正确,对ARM有一定的认识后,就可以买开发板做进一步的学习,如三星的arm9开发板、意法半导体的Cortex-M3开发板等都是不错的选择。
图3-1 Proteus arm7仿真软件
笔者不再对MDK工程的搭建或Proteus LPC2103仿真电路的搭建进行详细讲解,在文章的末尾给出了流水灯汇编实现的MDK工程以及Proteus工程,读者自行学习验证。
4.1. 新建MDK工程LEDs,选择NXP下LPC2103目标芯片。
4.2. 我们采用的是汇编实现,选择“否”不需要加入启动代码到工程。
4.3. 新建一个LEDs.s的汇编源代码文件并加入工程,用汇编实现流水灯代码。
PINSEL0 EQU 0xE002C000
IO0PIN EQU 0xE0028000
IO0SET EQU 0xE0028004
IO0DIR EQU 0xE0028008
IO0CLR EQU 0xE002800C
AREA RESET, CODE, READONLY
ARM ; 表明为ARM代码
ENTRY ; 代码入口
Start
LDR R0, =PINSEL0 ; 引脚功能寄存器1设置
LDR R1,[R0] ; 获得寄存器值
LDR R2,=0xffff
BIC R1,R1, R2 ; 清除R1的低16位
STR R1,[R0]; 写回寄存器P0.0~P0.7为GPIO功能
LDR R0, =IO0DIR ; IO口输入输出控制
LDR R1,[R0] ; 获得寄存器值
ORR R1,R1, #0xff ; 置位低8位(输出)
STR R1,[R0] ;P0.0~P0.7为输出
LDR R0,=IO0PIN ; 引脚值寄存器
LDR R1,[R0] ; 获得寄存器的值
ORR R1,R1, #0xff ; 置位低8位,输出高电平
STR R1,[R0] ; LED1~LED8灭
LDR R0, =IO0SET
LDR R1,=IO0CLR
Loop
MOV R2,#(1<<0)
STR R2,[R1] ; LED0亮
BL Delay_1s
STR R2,[R0] ; LED0灭
MOV R2,#(1<<1)
STR R2,[R1] ; LED1亮
BL Delay_1s
STR R2,[R0] ; LED1灭
MOV R2,#(1<<2)
STR R2,[R1] ; LED2亮
BL Delay_1s
STR R2,[R0] ; LED2灭
MOV R2,#(1<<3)
STR R2,[R1] ; LED3亮
BL Delay_1s
STR R2,[R0] ; LED3灭
MOV R2,#(1<<4)
STR R2,[R1] ; LED4亮
BL Delay_1s
STR R2,[R0] ; LED4灭
MOV R2,#(1<<5)
STR R2,[R1] ; LED5亮
BL Delay_1s
STR R2,[R0] ; LED5灭
MOV R2,#(1<<6)
STR R2,[R1] ; LED6亮
BL Delay_1s
STR R2,[R0] ; LED6灭
MOV R2,#(1<<7)
STR R2,[R1] ; LED7亮
BL Delay_1s
STR R2,[R0] ; LED7灭
B Loop
; 软件延时函数,arm汇编较精确计算,延时1s
; 由于暂未设置时钟,系统时钟为外部晶体时钟12M
Delay_1s
LDR R12, =3000000 ; ARM CLOCK 12M
Delay1
SUBS R12, R12, #1 ; 指令执行需1个ARM CLOCK
BNE Delay1 ; 跳转会清流水线,3个ARM CLOCK
BX LR
; Delay1处循环一次需(1+3)=4个ARM CLOCK
; 共延时R12*(1+3)=12000000个ARM CLOCK,即1s
END
4.4. 在工程属性中点选Create HEX File,编译后会生成hex文件,这个文件是加载进Proteus仿真或者通过ISP下载进LPC2000系列芯片的代码文件。
4.5. 点击编译后,会给出段未匹配到的警告,不用管,正确生成hex代码文件后,可通过MDK的Debug进行仿真查看代码的运行。
4.6. 通过Proteus搭建电路,加载进生成的hex文件,可以很直观的观察流水灯,Proteus设置12M的仿真时钟时,已无法实时仿真了,仿真的时间有一定的延长。
图4-1 Proteus仿真流水灯运行效果图
同上进行MDK工程和Proteus电路的搭建,在文章的末尾给出了数码管动态扫描实现的MDK工程以及Proteus工程,读者自行学习验证。
5.1. 搭建MDK的LPC2103工程,由于我们需要用c来实现代码,在提示是否加入启动代码时,选择加入,并设置不要开启PLL(因Proteus仿真12M都已经无法实时仿真了)。
5.2. 加入数码管驱动模块文件DigitalTube.h和DigitalTube.c和数码管秒表计时源码main.c,并进行接口方面的修改,数据管动态扫描设计实现请参考笔者51单片机开发系列三/数码管动态扫描显示。
数码管驱动模块实现DigitalTube.c内容如下:
#include"LPC210x.h"
#include"DigitalTube.h"
// 数值相对应的段码,共阳极
static unsigned char const DigitalTubeTable[12]= { // 共阳LED段码表
0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90, 0xff, 0xbf
//"0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "不亮" "-"
};
// 每个数码管需一个字节的内存保存对应数码管数据
staticunsigned char FrameBuffer[DigitalTubeNumber];
unsigned char*DigitalTube_GetBuffer()
{
return FrameBuffer;
}
// Proteus仿真,数码管扫描时需消隐,Proteus是无法等同实际硬件运行的
// 加入DigitalTube_EN为Proteus仿真的演示,若实际硬件运行
// 请删掉下面四行
// #define DigitalTubeEnable() {IOSET= (1<<11);}
// #defineDigitalTubeDisable() {IOCLR = (1<<11);}
// DigitalTubeDisable();
//DigitalTubeEnable();
// 实际硬件不需要,Proteus仿真用来消隐
#defineDigitalTubeEnable() {IOSET =(1<<11);}
#defineDigitalTubeDisable() {IOCLR =(1<<11);}
voidDigitalTube_Scan()
{
static unsigned char Select = 0; // 记录扫描的选择线
unsigned char Code;
DigitalTubeDisable(); // 实际硬件不需要,Proteus仿真用来消隐
// 从对应选择线中找到显存数据,并得到相应的段码
Code =DigitalTubeTable[FrameBuffer[Select]];
// 段码实际输出到数码管接口
DigitalTube_Data(Code);
// 位选实际输出到数码管接口
DigitalTube_Select(Select);
DigitalTubeEnable(); // 实际硬件不需要,Proteus仿真用来消隐
Select++; // 进入到下一位选扫描
if (Select >=DigitalTubeNumber) {
Select= 0; // 所有数码管已扫描,从第一个数码管再次开始扫描
}
}
void DigitalTube_Init()
{
// 清除引脚配置寄存器[23:0],P0.0~P0.11作为GPIO口功能
PINSEL0 &= ~0xffffff;
// 置位IO口方向寄存器[11:0],P0.0~P0.11作为IO口输出
IODIR |= 0xfff;
IOSET = 0xfff; // P0.0~P0.11输出高电平
}
我们在数码管模块头文件DigitalTube.h中实现模块的接口访问宏实现,使之方便移植及修改接口配置。模块头文件同时也引出模块的接口函数,void DigitalTube_Scan(void)为数码管刷新函数,需周期性调用刷新数码管显示。unsigned char *DigitalTube_GetBuffer(void)用来获得数码管显存,从而更新数码管显存数据。其内容如下:
#ifndef__DigitalTube_H__
#define__DigitalTube_H__
#ifdef__cplusplus
extern"C" {
#endif
// 数码管模块中的个数,最大为8
#defineDigitalTubeNumber 4
// 输出数码管位选,位选为P0.8~P0.10
// 先读出P0的值再清0 P0.8~P0.10再写入相应位选
#defineDigitalTube_Select(Select) {IOPIN = (IOPIN&(~(7<<8))) +((Select)<<8);}
// 输出数码管段码,段码为P0.0~P0.7
// 先读出P0的值再清0 P0.0~P0.7再写入段码
#defineDigitalTube_Data(Dat) {IOPIN = (IOPIN&(~0xff)) | (Dat);}
// 数码管初始化函数,引脚配置
void DigitalTube_Init(void);
// 数码管刷新函数,必须保证以一定周期调用刷新
void DigitalTube_Scan(void);
// 获得数码管显存,以作显示的数据更新
unsigned char*DigitalTube_GetBuffer(void);
#ifdef__cplusplus
}
#endif
#endif/*__DigitalTube_H__*/
外部模块通过引入数码管的模块头文件DigitalTube.h来实现调用数码管驱动函数,简单测试调用(秒表数码管显示计数)main.c实现如下:
#include"LPC210x.h"
#include"DigitalTube.h"
// 不开启PLL,cpu频率为晶体频率12M,外设时钟为cpu/4
#define PCLK (12000000 / 4)
// 以定时器时间为计时标准,记录时间间隔
staticvolatile unsigned int SystemTick = 0;
// 定时器2ms中断处理进行数码管刷新
// 此处用__irq告诉编译器函数用中断方式入出栈及返回
static voidTime0_ISR() __irq
{
SystemTick++; // 记录时间间隔
DigitalTube_Scan(); // 刷新数码管
T0IR = 0x1; // 清除中断标志
VICVectAddr = 0; // 通知中断控制器中断结束
}
voidTime0_Init()
{
// 写1清除定时器0的对应中断标志
T0IR=0xff;
// 预分频计数值初始化为0
T0PC=0;
// 预分频值为0
T0PR=0;
// 定时器计数器初始化为0
T0TC=0;
// 设置匹配模式,复位并中断
T0MCR=0x03;
// 2ms中断一次
T0MR0=(PCLK / 500);
// 选择为IRQ中断
VICIntSelect = 0x00;
// 清除定时器0的中断使能
VICIntEnClr = (1 << 4);
// 中断向量指向时钟中断处理函数
VICVectAddr0 = (unsignedint)Time0_ISR;
// 中断优先级向量0分配给定时器0,并开启该优先级中断
VICVectCntl0 = (0x20 | 0x04);
// 开启定时器0中断
VICIntEnable= (1 << 4);
}
int main()
{
unsigned char *pBuffer;
unsigned char i;
// 定时器初始化
Time0_Init();
// 数码管引脚配置初始化
DigitalTube_Init();
// 获得数码管显存,以作更新数据显示
pBuffer = DigitalTube_GetBuffer();
// 数据管显存初始化显示0
for (i=0; i pBuffer[i] = 0; } // 开启定时器进行计时以及数码管刷新 T0TCR = 0x01; while(1) { // SystemTick读数到500时为1s间隔到 if (SystemTick > 500) { SystemTick =0; // 重新计秒 // 更新数码管秒表计数显存 for (i=0; i pBuffer[DigitalTubeNumber-1-i]++; if(pBuffer[DigitalTubeNumber-1-i] < 10) { break; // 未到10,不用进位更新高位显存,退出 } else { // 到10,这一位清0,并继续循环更新高位显存 pBuffer[DigitalTubeNumber-1-i]= 0; } } } } } 5.3. 在工程属性中点选Create HEX File,编译后会生成hex文件。 5.4. 正确生成hex代码文件后,可通过MDK的Debug进行仿真查看代码的运行。 5.5. 通过Proteus搭建电路,加载进生成的hex文件,可以很直观的观察秒表计数,Proteus只作软件仿真,搭建的电路完全不能用在实际的电路中,设置12M的仿真时钟时,已无法实时仿真了,仿真的时间有一定的延长,也不能代表实际的软件计秒延时情况。 图5-1 Proteus仿真ARM7数码管秒表计数 笔者简单地概述ARM处理器,介绍了其基本的编译、调试、环境搭建。以ARM7LPC2103为例,分别给出了两个简单的入门例程,一个用汇编代码实现流水灯,另一个用c语言实现数码管动态扫描显示秒表计数。让读者对汇编、c语言开发ARM有一个初步的认识,并希望笔者的这篇文章能确实帮助到一些想或者已经开始学习ARM的读者。 ARM教程PPT.rar,周立功ARM教程PPT,包括ARM7的体系结构,汇编指令讲解这些内容,是相当不错的教材。 http://pan.baidu.com/s/1jG0Z2ho LPC2103_LEDs.rar,流水灯汇编MDK工程以及Proteus仿真电路 LPC2103_DigitalTube.rar,数码管动态扫描显示秒表计数MDK工程以及Proteus仿真电路 http://pan.baidu.com/s/1mg0Vy3m 6. 附录