void SystemInit (void) {
volatile uint32_t i;
#if (CLOCK_SETUP) /* Clock Setup */
#if ((SYSPLLCLKSEL_Val & 0x03) == 1)
LPC_SYSCON->PDRUNCFG &= ~(1 << 5); /* Power-up System Osc */
LPC_SYSCON->SYSOSCCTRL = SYSOSCCTRL_Val;
for (i = 0; i < 200; i++) __NOP();
#endif
LPC_SYSCON->SYSPLLCLKSEL = SYSPLLCLKSEL_Val; /*Select PLL Input */
LPC_SYSCON->SYSPLLCLKUEN = 0x01; /* Update Clock Source */
LPC_SYSCON->SYSPLLCLKUEN = 0x00; /* Toggle Update Register */
LPC_SYSCON->SYSPLLCLKUEN = 0x01;
while (!(LPC_SYSCON->SYSPLLCLKUEN & 0x01)); /* Wait Until Updated */
#if ((MAINCLKSEL_Val & 0x03) == 3) /* Main Clock is PLL Out */
LPC_SYSCON->SYSPLLCTRL = SYSPLLCTRL_Val;
LPC_SYSCON->PDRUNCFG &= ~(1 << 7); /* Power-up SYSPLL */
while (!(LPC_SYSCON->SYSPLLSTAT & 0x01)); /* Wait Until PLL Locked */
#endif
#if (((MAINCLKSEL_Val & 0x03) == 2) )
LPC_SYSCON->WDTOSCCTRL = WDTOSCCTRL_Val;
LPC_SYSCON->PDRUNCFG &= ~(1 << 6); /* Power-up WDT Clock */
for (i = 0; i < 200; i++) __NOP();
#endif
LPC_SYSCON->MAINCLKSEL = MAINCLKSEL_Val; /* Select PLL Clock Output */
LPC_SYSCON->MAINCLKUEN = 0x01; /* Update MCLK Clock Source */
LPC_SYSCON->MAINCLKUEN = 0x00; /* Toggle Update Register */
LPC_SYSCON->MAINCLKUEN = 0x01;
while (!(LPC_SYSCON->MAINCLKUEN & 0x01)); /* Wait Until Updated */
LPC_SYSCON->SYSAHBCLKDIV = SYSAHBCLKDIV_Val;
#endif
}
该函数位于KEIL自带的system_LPC11xx.c文件中,首先定义了一个变量,用来计数,然后一共使用了4个条件编译,其中1个总体的条件编译和3个嵌套条件编译。
volatile uint32_t i;
定义了一个变量i
uint32_t 是指定义一个32位的无符号变量,把鼠标放到uint32_t的上面,单击鼠标右键,在弹出的菜单中选择“Go To Definition Of’uint32_t’”,如下图所示:
然后,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()函数就讲完了!
『本文转载自网络,版权归原作者所有,如有侵权请联系删除』