您的位置 首页 编程

LPC11XX.h头文件解析

从前面的第一个演示示例中可以看出,只实现一个LED的闪烁,其代码量似乎要比51单片的多很多。仔细观察后会发现,其实除了多了时钟配置以外…

早年面的榜首个演示示例中能够看出,只完成一个LED的闪耀,其代码量好像要比51单片的多许多。仔细调查后会发现,其实除了多了时钟装备以外,就数预订义部分的代码数量最多,并且这部分大多是以结构体的办法呈现的。在正规的开发过程中,这部分预订义的内容被是放在一些头文件内并包括进来的,前面的代码仅仅为了编译便利,所以把悉数代码都给出来,不太正规。下面就来评论一下在ARM-MDK环境中开发LPC1114的头文件装备。

在前面的示例中,给出了预订义部分的内容,但没有进行解说。这儿就先来评论一下在榜首个演示示例中预订义部分的内容。

先看榜首个部分,代码如下:

#define__IOvolatile

#define__Ovolatile

#define__Ivolatile const

typedef unsignedchar uint8_t;

typedef unsigned shortint uint16_t;

typedef unsignedint 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便是“unsignedint”型。一起要特别注意一点,在这个结构体中界说的各个变量的次序不能改动,也便是说各个变量在结构体中的方位是固定的。这是因为在结构体内界说的各个变量之间存在着严厉的地址偏移量联系,这点从每一句后边的注解中也能够很清楚地看到。例如榜首个变量界说的是“SYSMEMREMAP”,因为它被界说为“unsignedint”型的,所以占用4个字节的地址空间;而下一个界说的变量“PRESETCTRL”的地址,则是前面的变量“SYSMEMREMAP”地址再向后偏移4个字节。同理,第三个界说的变量“SYSPLLCTRL”的地址是第二个变量“SYSMEMREMAP”地址再向后偏移4个字节(因为第二个变量仍界说为“unsignedint”型),或许是榜首个变量“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这个结构体中,成员变量都用的是“unsignedint”型来界说,占用4个字节的空间,而调查上面的“system control模块内一切寄存器的散布状况表”能够看出,它的每个寄存器之间正好是4个字节(或是4的正数倍)的地址偏移,所以只要用“unsignedint”型来界说成员变量,寄存器的偏移地址就主动习惯了。假如遇到保存地址,则能够经过界说“unsignedint”型的空数组来避开,以确保后边成员变量的地址偏移正确。别的,因为LPC1114是32的结构,所以它的寄存器也是32位的,刚好是4个字节,这也是为何每个寄存器之间是4个字节地址偏移量的原因。在表中还能够看出寄存器的读写特点,这与前面结构体界说中的“__I”、“__IO”、“__O”等就能够联系起来了。

接下来,经过define句子来把方才的结构体指针取个“别号”,即LPC_SYSCON。这时LPC_SYSCON便是这个结构体指针类型了,经过“LPC_SYSCON->”的办法就能够来引证它内部的成员变量(即system control模块内的各个寄存器)了。这样一来,就能够把底层的地址用高档言语的称号来表明,十分直观,比方前面比如中的要让PLL输入挑选外部晶体振荡,履行句子“LPC_SYSCON->SYSPLLCLKSEL = 0x00000001;”就能够了,但假如没有这种地址对映,就有必要写成“MOV0x40048040,#0x00000001”,这当然就十分不直观了,不查手册还不知道地址0x40048040是什么寄存器。

上述仅仅经过SYSCON这个结构体来进行评论的,其它的结构体界说没有评论。但它们所选用的办法是相同的,读者可参阅上面临SYSCON结构体的剖析办法来自行研讨,这儿就不再赘述了。

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/fangan/biancheng/272804.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部