这样,项目最基本的设置完成了,下一步开始编写程序,在这之前不妨编译一次看看。
得到编译结果:
没有错误,也没有警告。这样,我们引用的那些头文件就可以看到了:
下面来介绍这几个文件:startup _LPC17xx.s是汇编语言的文件,它根据ARM本身型号不同来设定一些最基本的参数,比如中断的名称和中断矢量就是在这里定义的。core_cm3.c和core_cm3.h是内核文件,里面主要是__ASM类型的函数,也就是嵌入到c语言中的汇编语言函数,它们主要对ARM进行底层的操作,我们一般不会用到;system_LPC17xx.c和system_LPC17xx.h是时钟初始化文件,它的作用是定义时钟的运行方式,在这个文件中一共有两个函数:SystemInit(),用来初始化时钟,在我们对ARM操作之前必须执行这个函数(或者运行由程序员自己编写的时钟初始化函数);另一个函数为SystemClockUpdate(),它可以在程序运行过程中改变时钟参数;type.h是关于数据类型的定义,它主要来定义那些标准c语言中本来没有的数据类型,比如bool型;lpc17xx.h是这里面最重要的一个文件,我们编程主要依靠的文件就是它,它的作用是将所有寄存器的地址映射到容易识别的字符上,便于我们操作。
二、GPIO的使用
下面是关于GPIO的使用,也是ARM系列使用的基础。对于ARM,它的功能比8051系列单片机强大很多,也灵活很多,这造成了使用起来比8051系列要复杂很多。
ARM的嵌入式芯片使用起来,有着和c语言很相似的特点——先声明,后使用。ARM的各个功能模块都是由寄存器组成的,我们使用ARM,本质上就是对它的各种寄存器进行操作,我们使用哪个模块,首先在确定它的性质的寄存器中写入数据,然后再对其进行操作。
我们打开lpc17xx.h文件,可以看到lpc17xx的寄存器结构,为了集中注意力我们只说GPIO相关的部分,我们来看结构体LPC_GPIO_TypeDef,它定义了每个GPIO端口的寄存器对应的结构体,它根据ARM中GPIO寄存器的地址来定义的结构体(比如次结构体中的uint32_t RESERVED0[3]; 是为了和寄存器的地址一一对应),结构体中的成员的作用为: 联合体FIODIR,用来定义端口中每个引脚数据传输的方向,例如: LPC_GPIO_TypeDef* P1; //定义GPIO结构体指针P1
P1->FIODIR = 0x000000FF; //此结构体中,低8位引脚为写,其它引脚为读 联合体FIOMASK,用来定义端口中的引脚是否被屏蔽(只取0值),例如:
P1->FIOMASK = 0x00000001; //此结构体中,0引脚被屏蔽——即不可用 联合体FIOPIN,用来存放端口中每个引脚的数值,例如:
P1->FIOPIN = 0x00000003; //给寄存器的0、1两位写入1(因为屏蔽第0位无法输出),执行后此寄存器的值为0x00000002
a = P1->FIOPIN; //将P1数据寄存器中的数值写入变量a中 联合体FIOSET,输出模式下改变寄存器的引脚电平为高电平,例如:
P1->FIOSET = 0x0000003F; //令低6位引脚置1(因为屏蔽第0位无法输出),执行后此寄存器的值为0x0000003E。
联合体FIOCLR,输出模式下改变寄存器的引脚电平为低电平。
至于为什么P1后面要用“->”而不是“.”请查询任意一本C语言的书籍。
当我们在程序中写入main.c中:
编译无错后,进行调试(Debug),在调试过程中,因为我们用到的资源都是片上设备,因此用软件仿真即可(还能省硬件)。我们打开片上设备显示窗口(无论是软件还是硬件仿真
都可以通过这个菜单来观察设备运行情况)。
最后进行步进调试,我们就能得到我们预计的结果了。
其中有一句话:P1 = LPC_GPIO0; 是没有讲过的,这句话的意义很大,前面我们说结构体LPC_GPIO_TypeDef很重要,它里面的成员结构与内存中相应寄存器的地址相对应,但是我只说了一半,了解C语言的同学应该知道,他只是一个表示变量的数据结构,如果不定义变量,就没有实际的作用。于是我定义了一个P1的变量,P1是表示LPC_GPIO_TypeDef结构体的指针,即P1是一个长整形值,里面存放着具有LPC_GPIO_TypeDef结构的地址。但是地址的值是多少必须我们为它设定,这就是头文件lpc17xx.h后半部分的意义了,它的后半部分主要定义了一些宏定义,对应着寄存器的地址,其中有这样一句话:
#define LPC_GPIO0 ((LPC_GPIO_TypeDef *) LPC_GPIO0_BASE ) 它将宏定义LPC_GPIO0定义为另一个宏定义LPC_GPIO0_BASE,不过将其类型强制转换为LPC_GPIO_TypeDef结构体的指针。而LPC_GPIO0_BASE的值可参考:
#define LPC_GPIO0_BASE (LPC_GPIO_BASE + 0x00000)
这句话将LPC_GPIO0_BASE的值定义为LPC_GPIO_BASE的值加上偏移量0,LPC_GPIO _BASE的含义可参考:
#define LPC_GPIO_BASE (0x2009C000UL)
其中0x2009C000UL就是GPIO寄存器的一个基准地址。可以说,所有的操作都是基于这种方式的,看懂了lpc17xx.h就可以实现lpc系列的CPU90%以上的功能。
此外,lpc17xx中,每个引脚除了通用的输入输出功能外,还有其它功能以及输入输出模式,通过改变结构体LPC_PINCON_TypeDef的值来实现。
在LPC_PINCON_TypeDef中,有三类成员, PINSEL、PINMODE和PINMODE_OD。其中PINSEL来声明引脚的功能,在lpc17xx中,一个引脚最多有4种功能,用二进制数值表示就是:00、01、10和11,占两位2进制数,因此端口0对应两个32位二进制变量:PINSEL0和PINSEL1。每个引脚的电气性质由PINMODE来定义,同样对应4种特性:上拉、中继、浮空和下拉,占两位2进制数,也对应两个32位二进制变量。最后一种成员PINMODE_OD表示引脚是否为开漏模式,1为开漏模式,0为正常模式(有些功能时需要,比如I2C通信时)。
直接操作寄存器相对比较麻烦,因此我们一般的方法是先写出一套操作寄存器的函数库,然后利用函数库对GPIO进行操作。我的GPIO函数库为lpc_GPIO.c和lpc_GPIO.h 文件,它们可以对GPIO进行更方便的控制。虽然我编写的程序能够实现GPIO的功能,但是相对比较罗嗦,希望能够进一步精简。