lpc1114 SystemInit函数解释

  • 学习5060次

 

  1. void SystemInit (void) {
  2. volatile uint32_t i;
  3. #if (CLOCK_SETUP)                                     /* Clock Setup           */
  4. #if ((SYSPLLCLKSEL_Val & 0x03) == 1)
  5. LPC_SYSCON->PDRUNCFG  &= ~(1 << 5);            /* Power-up System Osc */
  6. LPC_SYSCON->SYSOSCCTRL = SYSOSCCTRL_Val;
  7. for (i = 0; i < 200; i++) __NOP();
  8. #endif
  9. LPC_SYSCON->SYSPLLCLKSEL = SYSPLLCLKSEL_Val;     /*Select PLL Input            */
  10. LPC_SYSCON->SYSPLLCLKUEN  = 0x01;                /* Update Clock Source      */
  11. LPC_SYSCON->SYSPLLCLKUEN  = 0x00;                /* Toggle Update Register    */
  12. LPC_SYSCON->SYSPLLCLKUEN  = 0x01;
  13. while (!(LPC_SYSCON->SYSPLLCLKUEN & 0x01));        /* Wait Until Updated        */
  14. #if ((MAINCLKSEL_Val & 0x03) == 3)                      /* Main Clock is PLL Out    */
  15. LPC_SYSCON->SYSPLLCTRL    = SYSPLLCTRL_Val;
  16. LPC_SYSCON->PDRUNCFG     &= ~(1 << 7);          /* Power-up SYSPLL          */
  17. while (!(LPC_SYSCON->SYSPLLSTAT & 0x01));                /* Wait Until PLL Locked    */
  18. #endif
  19. #if (((MAINCLKSEL_Val & 0x03) == 2) )
  20. LPC_SYSCON->WDTOSCCTRL    = WDTOSCCTRL_Val;
  21. LPC_SYSCON->PDRUNCFG     &= ~(1 << 6);           /* Power-up WDT Clock      */
  22. for (i = 0; i < 200; i++) __NOP();
  23. #endif
  24. LPC_SYSCON->MAINCLKSEL    = MAINCLKSEL_Val;     /* Select PLL Clock Output    */
  25. LPC_SYSCON->MAINCLKUEN    = 0x01;                /* Update MCLK Clock Source */
  26. LPC_SYSCON->MAINCLKUEN    = 0x00;                /* Toggle Update Register     */
  27. LPC_SYSCON->MAINCLKUEN    = 0x01;
  28. while (!(LPC_SYSCON->MAINCLKUEN & 0x01));       /* Wait Until Updated     */
  29. LPC_SYSCON->SYSAHBCLKDIV  = SYSAHBCLKDIV_Val;
  30. #endif
  31. }

该函数位于KEIL自带的system_LPC11xx.c文件中,首先定义了一个变量,用来计数,然后一共使用了4个条件编译,其中1个总体的条件编译和3个嵌套条件编译。

  1. volatile uint32_t i;

定义了一个变量i

uint32_t 是指定义一个32位的无符号变量,把鼠标放到uint32_t的上面,单击鼠标右键,在弹出的菜单中选择“Go To Definition Of’uint32_t’”,如下图所示:

找keil定义

然后,stdint.h文件打开,在文件中,找到uint32_t的定义符,如下所示:

定义类型

可以看到,uint32_t 实际上是unsigned int类型,用typedef重新起了个类型名字而已。

在SystemInit()函数中,i的变量定义前面还加上了volatile关键字,其作用是不让编译器优化此变量,以免造成程序的错误。

第3行的#if和第30行的#endif共同组成了一个条件编译。

如果CLOCK_SETUP为1,则执行里面的语句,如果不为1,则从endif后面执行,在此函数中,endif后面没有任何语句了,所以,如果CLOCK_SETUP为0的话,系统时钟配置函数实际上什么也没有执行,如果想让单片机运行在默认的IRC 12MHz主频下,把CLOCK_SETUP定义为0即可。

把鼠标放到CLOCK_SETUP上面,单击鼠标右键,在弹出的菜单中选择“Go To Definition Of‘CLOCK_SETUP’”。

上图中看到,CLOCK_SETUP在文件中定义为1,所以里面的语句被编译执行。我们可以在这里把CLOCK_SETUP设置为0来禁止执行系统初始化函数.

第4行到到第8行,亦是一个条件编译,如果if括号中的条件为真,则执行

包含在里面的语句。由SYSPLLCLKSEL_VAL的值分析得出,这里是判断,是否需要启动系统振荡器。

在这里,涉及到了两个寄存器:PDRUNCFG和SYSOSCCTRL。

PDRUNCFG:掉电配置寄存器

符号 描述 复位值
0 IRCOUT_PD IRC 振荡器输出掉电 0
0 上电
1 掉电
1 IRC_PD IRC 振荡器掉电 0
0 上电
1 掉电
2 FLASH_PD Flash掉电 0
0 上电
1 掉电
3 BOD_PD BOD掉电 0
0 上电
1 掉电
4 ADC_PD ADC掉电 1
0 上电
1 掉电
5 SYSOSC_PD 系统振荡器掉电 1
0 上电
1 掉电
6 WDTOSC_PD 看门狗振荡器掉电 1
0 上电
1 掉电
7 SYSPLL_PD 系统PLL掉电 1
0 上电
1 掉电
8 保留位. 只能给此位写1. 1
9 保留位. 只能给此位写0. 0
10 保留位. 只能给此位写1. 1
11 保留位. 只能给此位写1. 1
12 保留位. 只能给此位写0. 0
15:13 保留位. 只能给此位写111. 111
31:16 保留位

掉电控制寄存器中的bit0~bit7控制着7个模拟模块的掉电状态,对对应为写0上电,写1掉电。复位状态下,IRC、FLASH、BOD是上电状态,ADC模块、系统振荡器模块、看门狗振荡器模块、系统PLL是掉电状态。由此处就可以看到,为什么复位后是IRC在担当着单片机的工作时钟。

SYSOSCCTRL:系统振荡器控制寄存器

符号 描述 复位值
0 BYPASS 系统振荡器控制位 0x0
0 系统振荡器有效
1 系统振荡器失效。直接从XTALIN引脚输入外部时钟信号
1 FREQRANGE 低功耗振荡器频率范围 0x0
0 1 – 20 MHz 频率范围
1 15 – 25 MHz 频率范围
31:2 保留 0x00

 

系统振荡器控制寄存器只用到了bit0和bit1。

bit0 决定了是否启用系统振荡器。

bit0 = 0 代表启用,bit = 1代表不启用。

由复位值可以看到,默认是启用系统振荡器的。

在不启用系统振荡器的时候,可以用外部时钟信号直接输入到XTALIN引脚,就可以不使用外部晶振了。(所以,当你看到一个电路板上,某个单片机没有接晶振的时候,不要妄言断定它使用的是内部时钟,因为还有可能是使用外部时钟发生器来工作,或者是别的单片机产生的时钟输出引脚连接到了XTALIN引脚上。)

介绍完了这两个寄存器,再看程序第567行。

第5行,给寄存器PDRUNCFG寄存器的bit5写0,即给系统振荡器上电。(一眼看不出是给bit5写0的童鞋,请看第一章相关介绍)

第6行,把SYSOSCCTRL_VAL的值写到寄存器SYSOSCCTRL。

第7行,一个简单的延时函数,目的是等待系统振荡器上电完成。

第9行到第13行,设置PLL,涉及到两个寄存器:SYSPLLCLKSEL和SYSPLLCLKUEN。

SYSPLLCLKSEL:系统PLL时钟源选择寄存器

符号 描述 复位值
1:0 SEL 系统PLL时钟源
0x0 IRC振荡器
0x1 系统振荡器
0x2 保留
0x3 保留
31:2 保留

该寄存器只用到了前两位,可以选择IRC振荡器或者系统振荡器作为PLL的时钟源。注意:切换时钟的时候,两个振荡器必须同时上电工作,等待所选择的振荡器稳定工作以后,才可以关系另外一个振荡器,当然,不关也可以,对于对功耗有要求的,最好还是关闭用不着的时钟。当使用CAN模块,且CAN的通信速率大于100kbit/s时,必须选择系统振荡器工作。

SYSPLLCLKUEN:系统PLL时钟更新寄存器

符号 描述 复位值
0 ENA 允许系统PLL时钟更新 0x0
0 没有改变
1 更新时钟源
31:1 保留 0x00

当SYSPLLCLKSEL中的值改变以后,需要对此更新寄存器先写0再写1达到时钟更新的目的。

第9行,当SYSPLLCLKSEL_Val = 0x1,选择IRC时钟,当SYSPLLCLKSEL_Val = 0x2,选择系统振荡器时钟。

第10~12行,更新系统PLL时钟。

第13行,在时钟更新未成功时,一直在while循环;当时钟更新成功后,跳出while循环向下执行。

第14~18行,条件编译,如果主时钟选择PLL输出,则执行里面的语句。涉及到3个寄存器:SYSPLLCTRL、PDRUNCFG、SYSPLLSTAT

其中,PDRUNCFG寄存器在前面已经讲过。

SYSPLLCTRL:系统PLL控制寄存器

符号 描述 复位值
4:0 MSEL 反馈分频值, M=MSEL+1

00000: M = 1

to

11111: M = 32.

0x000
6:5 PSEL 后置分频比率 0x00
0x0 P = 1
0x1 P = 2
0x2 P = 4
0x3 P = 8
31:7 保留位,不准给这些位写1 0x0

该寄存器决定了PLL的乘数M和除数P,bit0~4决定了乘数M = MSEL +1,bit5和bit6决定了除数P。

 

SYSPLLSTAT:系统PLL状态寄存器

符号 描述 复位值
0 LOCK PLL 锁状态 0x0
0 PLL没有锁
1 PLL 已锁
31:1 保留 0x00

这是一个只读存储器,只用到了bit0,用来观察PLL是否锁定。只有当PLL锁定的时候,PLL才可以正常工作。

第15行,写入MSEL和PSEL值。

第16行,给PDRUNCFG的bit7写0,即PLL上电。

第17行,循环读取SYSPLLSTAT的值,直到PLL锁定后先下执行。

第19~23行,条件编译,如果主时钟源选择看门狗振荡器时钟,就执行里面的的语句。涉及到2个寄存器:WDTOSCCTRL和PDRUNCFG。

其中,PDRUNCFG寄存器前面以已经讲过。

WDTOSCCTRL:看门狗振荡器控制寄存器

符号 描述 复位值
4:0 DIVSEL Fclkana分频值

wdt_osc_clk = Fclkana/ (2 (1 + DIVSEL))

00000: 2 (1 + DIVSEL) = 2

00001: 2 (1 + DIVSEL) = 4

to

11111: 2 (1 + DIVSEL) = 64

0
8:5 FREQSEL 看门狗振荡器模拟输出频率选择位(Fclkana). 0x0
0x1 0.6 MHz
0x2 1.05 MHz
0x3 1.4 MHz
0x4 1.75 MHz
0x5 2.1 MHz
0x6 2.4 MHz
0x7 2.7 MHz
0x8 3.0 MHz
0x9 3.25 MHz
0xA 3.5 MHz
0xB 3.75 MHz
0xC 4.0 MHz
0xD 4.2 MHz
0xE 4.4 MHz
0xF 4.6 MHz
31:9 保留

看门狗振荡器控制寄存器用来控制看门狗时钟的数值。一共有两个值,一个是DIVSEL,另一个是FREQSEL。

bit0~bit4决定DIVSEL值,bit5~8决定FREQSEL值。

最后输出的看门狗时钟值由下式决定:

wdt_osc_clk = Fclkana/ (2 (1 + DIVSEL))

wdt_osc_clk:配置好的看门狗振荡器时钟输出

Fclkana:由bit5~8 FREQSEL值决定。

第20行,写入DIVSEL和FREQSEL值;

第21行,给PDRUNCFG寄存器的bit6写0,给看门狗振荡器上电。

第22行,简单的延时函数,等待看门狗振荡器稳定。

第24~28行,用来设置主时钟源。涉及到了两个寄存器:MAINCLKSEL和MAINCLKUEN。

MAINCLKSEL:主时钟源选择寄存器

符号 描述 复位值
1:0 SEL 主时钟时钟源选择 0x00
0x1 IRC 振荡器
0x2 系统PLL输入时钟
0x3 看门狗振荡器
0x4 系统PLL输出时钟
31:2 保留 0x00

主时钟源选择寄存器当中,只有bit1和bit0位有效,此两位的值用来决定主时钟源,有4个时钟源可供选择。分别是IRC振荡器、看门狗振荡器、输入到PLL的时钟和PLL输出的时钟。

MAINCLKUEN:主时钟源更新寄存器

符号 描述 复位值
0 ENA 允许主时钟源更新 0x0
0 没有改变
1 更新时钟源
31:1 保留 0x00

当MAINCLKSEL中的值改变以后,需要对此更新寄存器先写0再写1达到时钟更新的目的。

第24行,选择主时钟源;

第25~27行,更新主时钟源;

第28行,等待主时钟源更新成功。

第29行,涉及到一个寄存器:SYSAHBCLKDIV

SYSAHBCLKDIV:系统AHB时钟分频寄存器

符号 描述 复位值
7:0 DIV 系统AHB时钟分频值

0: 禁止系统时钟.

1: 分频值为1.

to

255: 分频值为255.

0x01
31:8 Reserved 0x00

该寄存器可以给主时钟分频,分频后的时钟,提供给内核,存储器和单片机内部所有的外设模块。当DIV = 0X0时,关闭主时钟。

到这里,SystemInit()函数就讲完了!

发表评论

关闭菜单