LPC11XX.h头文件解析

发布者:馥睿堂最新更新时间:2016-08-23 来源: eefocus关键字:LPC11XX.h  头文件 手机看文章 扫描二维码
随时随地手机看文章
从前面的第一个演示示例中可以看出,只实现一个LED的闪烁,其代码量似乎要比51单片的多很多。仔细观察后会发现,其实除了多了时钟配置以外,就数预定义部分的代码数量最多,而且这部分大多是以结构体的形式出现的。在正规的开发过程中,这部分预定义的内容被是放在一些头文件内并包含进来的,前面的代码只是为了编译方便,所以把全部代码都给出来,不太正规。下面就来讨论一下在ARM-MDK环境中开发LPC1114的头文件配置。

在前面的示例中,给出了预定义部分的内容,但没有进行解释。这里就先来讨论一下在第一个演示示例中预定义部分的内容。

先看第一个部分,代码如下:

#define __IO volatile

#define __O volatile

#define __I volatile const

typedef unsigned char uint8_t;

typedef unsigned short int uint16_t;

typedef unsigned int uint32_t;

#pragma anon_unions

 

第一、二、三行是三个宏定义,通过define语句把__IO等效为volatile,把__O等效为volatile,把__I等效为volatile const。一般来说宏定义都是大写,但因为这里用的字母比较少(只有I或O或IO),所以再在其前面添加下划线来进行区分,这样做可以有效避免命名冲突问题。而volatile本身则是一个关键字,表示其后面定义的变量不让编译器进行优化,即每次读取或者修改值的时候,都必须重新从内存或者寄存器中读取或者修改。比如在单片机开发中,经常会用到软件延时,但若要软件延时不被编译器优化掉,就必须在变量定义前加上关键字volatile,如“for(volatile unsigned int k=0;k<60000;k++);”。而volatile const则表示其后面定义的变量是只读的,比如用它来定义一个只读的状态寄存器,定义为volatile是因为它的值可能会被硬件意想不到地改变,而定义为const是因为程序不应该试图去修改它的值。通俗的说,就是它定义的是一个“只读变量”而不是常量,它的值是由硬件来改变的,不能通过程序写入来改变。总结一下:

__I:定义输入口。既然是输入,那么寄存器的值就随时会被外部修改,所以不能对它进行优化,每次都必须从寄存器中读取。也不能写(即只读),否则就不是输入而是输出了。 
__O:定义输出口,也不能对它进行优化,不然端口连续两次输出相同的值,编译器就会认为没有变化,而忽略后那一次输出,假如外部在两次输出中间修改了值,那就会影响输出的正确性。可写,否则就不能称为输出了。 
__IO:定义输入输出口,也不能对它进行优化,原因同上。可读可写。

第三至五行是类型的声明,把无符号的字符型、短整型、整型分别用uint8_t、uint16_t、uint32_t来表示,以突出它们所占用的字节数,方便查看。

在最后一行中,pragma是一个关键字,它的使用较为复杂,有兴趣的读者可自行上网查阅。这里只需要记住,在使用到带union的结构体定义时,在预定义部分一定要有“#pragma anon_unions”这样一句,否则编译通不过。在第一个演示示例中,由于在后面定义了一个带union的结构体,所以在这里必须要写这一句。

接下来看第二个部分,这部分全部使用结构体来对寄存器进行描述。先来看对SYSCON结构体的定义:

typedef struct
{
  __IO uint32_t SYSMEMREMAP;            /*!< Offset: 0x000 (R/W)  System memory remap Register */
  __IO uint32_t PRESETCTRL;             /*!< Offset: 0x004 (R/W)  Peripheral reset control Register */
  __IO uint32_t SYSPLLCTRL;             /*!< Offset: 0x008 (R/W)  System PLL control Register */
  __I  uint32_t SYSPLLSTAT;             /*!< Offset: 0x00C (R/ )  System PLL status Register */
       uint32_t RESERVED0[4];

  __IO uint32_t SYSOSCCTRL;             /*!< Offset: 0x020 (R/W)  System oscillator control Register */
  __IO uint32_t WDTOSCCTRL;             /*!< Offset: 0x024 (R/W)  Watchdog oscillator control Register */
  __IO uint32_t IRCCTRL;                /*!< Offset: 0x028 (R/W)  IRC control Register */
       uint32_t RESERVED1[1];
  __I  uint32_t SYSRSTSTAT;             /*!< Offset: 0x030 (R/ )  System reset status Register */
       uint32_t RESERVED2[3];
  __IO uint32_t SYSPLLCLKSEL;           /*!< Offset: 0x040 (R/W)  System PLL clock source select Register */ 
  __IO uint32_t SYSPLLCLKUEN;           /*!< Offset: 0x044 (R/W)  System PLL clock source update enable Register */
       uint32_t RESERVED3[10];

  __IO uint32_t MAINCLKSEL;             /*!< Offset: 0x070 (R/W)  Main clock source select Register */
  __IO uint32_t MAINCLKUEN;             /*!< Offset: 0x074 (R/W)  Main clock source update enable Register */
  __IO uint32_t SYSAHBCLKDIV;           /*!< Offset: 0x078 (R/W)  System AHB clock divider Register */
       uint32_t RESERVED4[1];

  __IO uint32_t SYSAHBCLKCTRL;          /*!< Offset: 0x080 (R/W)  System AHB clock control Register */
       uint32_t RESERVED5[4];
  __IO uint32_t SSP0CLKDIV;             /*!< Offset: 0x094 (R/W)  SSP0 clock divider Register */
  __IO uint32_t UARTCLKDIV;             /*!< Offset: 0x098 (R/W)  UART clock divider Register */
  __IO uint32_t SSP1CLKDIV;             /*!< Offset: 0x09C (R/W)  SSP1 clock divider Register */
       uint32_t RESERVED6[1];
       uint32_t RESERVED7[11];

  __IO uint32_t WDTCLKSEL;              /*!< Offset: 0x0D0 (R/W)  WDT clock source select Register */
  __IO uint32_t WDTCLKUEN;              /*!< Offset: 0x0D4 (R/W)  WDT clock source update enable Register */
  __IO uint32_t WDTCLKDIV;              /*!< Offset: 0x0D8 (R/W)  WDT clock divider Register */
       uint32_t RESERVED9[1];

  __IO uint32_t CLKOUTCLKSEL;           /*!< Offset: 0x0E0 (R/W)  CLKOUT clock source select Register */
  __IO uint32_t CLKOUTUEN;              /*!< Offset: 0x0E4 (R/W)  CLKOUT clock source update enable Register */
  __IO uint32_t CLKOUTDIV;              /*!< Offset: 0x0E8 (R/W)  CLKOUT clock divider Register */
       uint32_t RESERVED10[5];

  __I  uint32_t PIOPORCAP0;             /*!< Offset: 0x100 (R/ )  POR captured PIO status 0 Register */
  __I  uint32_t PIOPORCAP1;             /*!< Offset: 0x104 (R/ )  POR captured PIO status 1 Register */
       uint32_t RESERVED11[11];
       uint32_t RESERVED12[7];
  __IO uint32_t BODCTRL;                /*!< Offset: 0x150 (R/W)  BOD control Register */
  __IO uint32_t SYSTCKCAL;              /*!< Offset: 0x154 (R/W)  System tick counter calibration Register */
       uint32_t RESERVED13[1];
       uint32_t RESERVED14[5];
       uint32_t RESERVED15[2];          
       uint32_t RESERVED16[34];         

  __IO uint32_t STARTAPRP0;             /*!< Offset: 0x200 (R/W)  Start logic edge control Register 0 */     
  __IO uint32_t STARTERP0;              /*!< Offset: 0x204 (R/W)  Start logic signal enable Register 0 */
  __O  uint32_t STARTRSRP0CLR;          /*!< Offset: 0x208 ( /W)  Start logic reset Register 0 */
  __I  uint32_t STARTSRP0;              /*!< Offset: 0x20C (R/ )  Start logic status Register 0 */
  __IO uint32_t STARTAPRP1;             /*!< Offset: 0x210 (R/W)  Start logic edge control Register 1   (LPC11UXX only) */     
  __IO uint32_t STARTERP1;              /*!< Offset: 0x214 (R/W)  Start logic signal enable Register 1  (LPC11UXX only) */      
  __O  uint32_t STARTRSRP1CLR;          /*!< Offset: 0x218 ( /W)  Start logic reset Register 1          (LPC11UXX only) */
  __I  uint32_t STARTSRP1;              /*!< Offset: 0x21C (R/ )  Start logic status Register 1         (LPC11UXX only) */
       uint32_t RESERVED17[4];

  __IO uint32_t PDSLEEPCFG;             /*!< Offset: 0x230 (R/W)  Power-down states in Deep-sleep mode Register  */
  __IO uint32_t PDAWAKECFG;             /*!< Offset: 0x234 (R/W)  Power-down states after wake-up from Deep-sleep mode Register*/       
  __IO uint32_t PDRUNCFG;               /*!< Offset: 0x238 (R/W)  Power-down configuration Register*/
       uint32_t RESERVED18[110];
  __I  uint32_t DEVICE_ID;              /*!< Offset: 0x3F4 (R/ )  Device ID Register */
} LPC_SYSCON_TypeDef; 

从中可以看出,大部分语句都加上了“__IO”的前缀,这是由于这部分寄存器单元访问的特殊性决定的。“uint32_t”则反映了定义的变量会占用4个字节的地址空间,因为在前面的宏定义中已经知道,uint32_t就是“unsigned int”型。同时要特别注意一点,在这个结构体中定义的各个变量的顺序不能改变,也就是说各个变量在结构体中的位置是固定的。这是因为在结构体内定义的各个变量之间存在着严格的地址偏移量关系,这点从每一句后面的注解中也可以很清楚地看到。例如第一个变量定义的是“SYSMEMREMAP”,由于它被定义为“unsigned int”型的,所以占用4个字节的地址空间;而下一个定义的变量“PRESETCTRL”的地址,则是前面的变量“SYSMEMREMAP”地址再向后偏移4个字节。同理,第三个定义的变量“SYSPLLCTRL”的地址是第二个变量“SYSMEMREMAP”地址再向后偏移4个字节(因为第二个变量仍定义为“unsigned int”型),或者是第一个变量“SYSMEMREMAP”地址向后偏移8个字节。所以,如果不按照顺序来定义,其对应的地址将会出错。比如,如果把第二个变量“SYSMEMREMAP”删除,由于地址偏移量不变,则原来的第三个变量“SYSPLLCTRL”的地址将会被对应到原来第二个变量的地址(相对第一个变量偏移4字节而不是8字节),这将导致出错!这是因为在CPU中各个寄存器之间的地址是固定不变的,这一点目前可能会有些难理解,在后面讨论了结构体的指针以后就会明白的。

下面先来看一下,刚才定义在结构体“SYSCON”中的各成员变量,是如何与LPC1114内部的寄存器进行一一对映的。为了方便讨论,先看一下LPC1114内部的Memory Map(内存地图)是怎么分配的,如下图所示。

头文件解析 - 西区故事 - 松柏后凋
从内存地图中可以看出,由于LPC1114是32位结构的,所以其寻址空间达到了4GB(从0x00000000~0xFFFFFFFF),且不管是什么模块,都统一编址在其中,而并不像51单片机那样,程序存储和数据存储是各自独立编址的。通过观察内存地图会发现,地址分配是分类的,即:从0x00000000~0x10002000的区域是内存区(包含了FLASH ROM和SRAM);从0x1FFF0000~0x1FFF4000的区域是引导区(即BOOT ROM区);从0x40000000~0x40080000的区域是APB设备区,它包含了除IO端口以外的所有外围设备资源;从0x50000000~0x50200000的区域是AHB设备区,它包含了所有的IO端口资源;从0xE0000000~0xE0100000区域是私有外围设备总线区;其它剩余区域为保留区,便于将来升级扩展。
刚才定义的结构体“SYSCON”所对映的设备,就是在内存地图中位于APB区内的system control模块部分(地址为0x40048000~0x4004C000)。为了便于讨论,下面把这部分的内容单独剔出来进行说明。下图给出了system control模块内所有寄存器的分布情况。

  头文件解析 - 西区故事 - 松柏后凋

 从上表中可以看出,因为system control模块的起始地址是0x40048000,所以它的基址就是0x40048000。这样它内部各寄存器的地址就可以以基址为参考点,用相对于基址的偏移量来进行描述。比如,在前面讨论时钟配置时用到的寄存器SYSPLLCLKSEL、MAINCLKSEL等,它们相对于基址的偏移地址就是0x040和0x070(查上表中的Address offset一列),而其绝对地址则是0x40048040和0x40048070(分别加上基址的值)。

我们知道,要访问CPU内部的硬件,最终只能通过它的地址进行访问,而我们对其进行的命名(如刚才的SYSPLLCLKSEL、MAINCLKSEL等)都要通过一种对映关系把它们联系起来。因为CPU不知道SYSPLLCLKSEL、MAINCLKSEL是什么,但它知道地址0x40048040、0x40048070的单元。而在高级语言中,直接使用地址不仅不直观,开发者还要费力去记住每个地址的寄存器功能,很不合适。为了适应高级语言的特点,我们就通过这种给寄存器命名,并让该名称对映到寄存器的实际地址的方式来处理。经过这样处理后,开发者就可以在程序中直接引用寄存器名称了,大大提高了程序的可读性,方便了开发。

从内存地图中可以看出,由于各设备的编址是分类的,所以使用高级语言中的“结构体”来处理这种名称与地址的对映关系是十分合适的。每一个结构体可对应一个分类,而分类中的寄存器就可以定义为这个结构体内的成员变量,各成员变量又严格对映到寄存器的实际地址。在前面示例部分的程序中,已经在头文件部分引入了这种结构体并进行了地址对映。

接下来再回到刚才定义的结构体“SYSCON”的讨论上来。从该结构体定义中可以看到,它内部定义的成员变量其实就是system control模块内的所有寄存器(见上表)。但是还没完,因为定义了“SYSCON”这个结构体只相当于对system control模块进行了“封装”(即进行了寄存器的按顺序命名),还没有对它进行地址的对映。

下面给出预定义部分中第三部分的内容,代码如下:

#define LPC_APB0_BASE         (0x40000000UL)

#define LPC_AHB_BASE          (0x50000000UL)

#define LPC_IOCON_BASE        (LPC_APB0_BASE + 0x44000)

#define LPC_SYSCON_BASE       (LPC_APB0_BASE + 0x48000)

#define SCS_BASE            (0xE000E000UL)

#define LPC_SYSCON            ((LPC_SYSCON_TypeDef *) LPC_SYSCON_BASE)

#define LPC_IOCON             ((LPC_IOCON_TypeDef  *) LPC_IOCON_BASE )

#define LPC_GPIO0_BASE        (LPC_AHB_BASE  + 0x00000)

#define LPC_GPIO1_BASE        (LPC_AHB_BASE  + 0x10000)

#define LPC_GPIO2_BASE        (LPC_AHB_BASE  + 0x20000)

#define LPC_GPIO3_BASE        (LPC_AHB_BASE  + 0x30000)

#define SysTick_BASE        (SCS_BASE +  0x0010UL)

#define LPC_GPIO0             ((LPC_GPIO_TypeDef   *) LPC_GPIO0_BASE )

#define LPC_GPIO1             ((LPC_GPIO_TypeDef   *) LPC_GPIO1_BASE )

#define LPC_GPIO2             ((LPC_GPIO_TypeDef   *) LPC_GPIO2_BASE )

#define LPC_GPIO3             ((LPC_GPIO_TypeDef   *) LPC_GPIO3_BASE )

#define SysTick                ((SysTick_Type   *)     SysTick_BASE  )

 

可见,这部分又全是用define进行的宏定义。因这里只讨论与“SYSCON”结构体相关的内容,所以只需看第一、四、六行,其它部分暂时不作说明。前面说过,system control模块位于APB设备区,所以第一行先进行了APB设备区的基址定义,第四行又进行了SYSCON(即system control模块区)的基址定义。而真正进行结构体与地址对映的,是第六行的语句,现单独把它剔出来进行讨论。该语句如下:

#define LPC_SYSCON            ((LPC_SYSCON_TypeDef *) LPC_SYSCON_BASE)

首先来看,(LPC_SYSCON_TypeDef *) LPC_SYSCON_BASE是把LPC_SYSCON_BASE(即SYSCON的基址)强行转换为一个LPC_SYSCON_TypeDef结构体的指针类型。根据前面的定义,LPC_SYSCON_BASE的值是0x40048000(LPC_APB0_BASE + 0x48000),而强行把它转换为一个LPC_SYSCON_TypeDef结构体的指针类型,则这个结构体的首地址就是LPC_SYSCON_BASE的基址(0x40048000)。这样一来,结构体LPC_SYSCON_TypeDef内部各成员变量的地址,就是以这个基址(0x40048000)为参考点的偏移地址了。

首地址对映了,那偏移量怎么实现呢?这就与结构体中成员变量定义的数据类型有关了。回顾一下上面的SYSCON这个结构体中,成员变量都用的是“unsigned int”型来定义,占用4个字节的空间,而观察上面的“system control模块内所有寄存器的分布情况表”可以看出,它的每个寄存器之间正好是4个字节(或是4的正数倍)的地址偏移,所以只要用“unsigned int”型来定义成员变量,寄存器的偏移地址就自动适应了。如果遇到保留地址,则可以通过定义“unsigned int”型的空数组来避开,以保证后面成员变量的地址偏移正确。另外,由于LPC1114是32的结构,所以它的寄存器也是32位的,刚好是4个字节,这也是为何每个寄存器之间是4个字节地址偏移量的原因。在表中还可以看出寄存器的读写属性,这与前面结构体定义中的“__I”、“__IO”、“__O”等就可以联系起来了。

接下来,通过define语句来把刚才的结构体指针取个“别名”,即LPC_SYSCON。这时LPC_SYSCON就是这个结构体指针类型了,通过“LPC_SYSCON->”的方式就可以来引用它内部的成员变量(即system control模块内的各个寄存器)了。这样一来,就可以把底层的地址用高级语言的名称来表示,非常直观,比如前面例子中的 要让PLL输入选择外部晶体振荡,执行语句“ LPC_SYSCON->SYSPLLCLKSEL = 0x00000001;”就可以了,但如果没有这种地址对映,就必须写成“MOV 0x40048040,#0x00000001”,这当然就非常不直观了,不查手册还不知道地址0x40048040是什么寄存器。

上述只是通过SYSCON这个结构体来进行讨论的,其它的结构体定义没有讨论。但它们所采用的方法是一样的,读者可参考上面对SYSCON结构体的分析方法来自行研究,这里就不再赘述了。

关键字:LPC11XX.h  头文件 引用地址:LPC11XX.h头文件解析

上一篇:LPC1114时钟配置
下一篇:STM32 ADC电压值的计算

推荐阅读最新更新时间:2024-03-16 15:06

AVR GCC常见库函数和头文件介绍
一、库函数头文件介绍 库函数按不同的类别声明在不同的头文件中,以字母为序分别介绍头文件: ctype.h:字符类型函数 eeprom.h:EEPROM访问函数 errno.h:错误处理函数 ina90.h:与IAR C兼容的头文件 interrupt.h:中断处理函数 inttypes.h:定义不同的数据类型 io.h:包含寄存器定义和其它头文件 math.h:数学函数 pgmspace.h:与IAR C兼容的头文件,内含对Flash存储器中数据读写函数 progmem.h:与pgmspace.h头文件相同 setjmp.h:长跳转函数 sig-avr.h:与signal.h相同,旧版头文件,建议不使用 signal.h:信号处理
[单片机]
msp430头文件分析
  头文件包含了单片机内部寄存器的地址定义等。引用此头文件,才能正常对一些寄存器进行读写操作,例如PORT口   在头文件中会出现这样的语句   #define OUTMOD_0 (0*0x20u),这里的“0*”和“u”分别是什么意思?   u是unsigned的意思,表示无符号整形变量至于前面有了0*,导致结果是0,我估计是因为后面还有其他定义,写成了1*0x20u,2*0x20u,这样的形式,为了使代码整洁好看,所以这里也写成了0*0x20u   头文件解析之看门狗   msp430单片机的“msp430f169.h”头文件中的 #define SHT0_0 (0*0x100u) #define CONSEQ_0 (
[单片机]
单片机C语言编程与&或|头文件常见
一、常见问题 1、头文件reg51.h和reg52.h其实是一样的,大家两个都可以用。 2、main()前面的void可加可不加,反正都是无返回值函数。 3、不是每一个程序都要用到死循环while(1),例如点亮一颗LED小灯就不用,只要执行一次就一直是高电平了不用循环。 4、位操作和字节操作都能用来控制I/O口输出电平,位操作需要实现声明sbit,而字节操作则不用。 5、延时程序有两种a:循环延时;b:定时器精确延时 6、如果定义的变量没有置初值,那么默认初值就是零。 7、定义数组时(如:number ={1,2,3,4,5,6, };)则结束时要加分号,每个元素之间用逗号
[单片机]
单片机C语言编程与&或|<font color='red'>头文件</font>常见
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved