一,时钟树
1.1、介绍几个关键字含义(由红色方框框选):
IRC8M:高速内部时钟(这个是芯片自带的内部时钟,精度较低,一般很少使用)
HXTAL:高速外部时钟(外部晶振,精度高)
LXTAL :低速内部时钟(外部低速晶振,精度高,常用于看门狗)
IRC40K:低速内部时钟(精度低,常用于看门狗,一般很少使用)
1.2、淡蓝方框框选的表示寄存器名称
1.3、紫色箭头表示时钟最大频率,这里可以看到AHB最大时钟时108MHz
1.4、绿色线路表示我程序初始化的线路
从上面时钟树我们要配置时钟,选择HXTAL(外部高速振荡器)作为时钟源步骤如下:
PREDV0(分频因子)->PLLSEL(时钟源选择)->PLLMF时钟倍频因子(这里需要注意PLLMF寄存器不是在连续的寄存器上,是由PLLMF[3:0]+PLLMF[4]<<4=PLLMF寄存器)->系统时钟选择(SCS)->AHB预分频选择->APB1预分频选择,APB2预分频选择
1.5、蓝色椭圆框选定时器,这里需要注意
TIMER1,2,3,4,5,6,11,12,13 if(APB1 prescale = 1)×1 else ×2
这里的意思是TIMER1,2,3…..13 如果APB1分频系数为1,定时器时钟频率就是APB1的频率,如果APB1分频系数不为1,定时器频率是APB1的两倍
二、基础寄存器介绍
RCU基地址:0x4002 1000
2.1、控制寄存器(RCU_CTL)
位/位域 | 名称 | 描述 |
[31:26] | 保留 | 必须保持复位值。https://www.gd32mcu.com/cn/ |
[25] | PLLSTB | PLL时钟稳定标志位 硬件置1来表示PLL输出时钟是否稳定待用 0:PLL未稳定 1:PLL已稳定 |
[24] | PLLEN | PLL使能 软件置位或复位,当PLL时钟做为系统时钟时该为不能被复位。当进入深度睡眠或待机模式时有硬件复位 0:PLL被关闭 1:PLL被打开 |
[23:20] | 保留 | 必须保持复位值 |
[19] | CKMEN | HXTAL时钟监视器使能 0:禁用高速4~16MHz晶振振荡器(HXTAL)时钟监视器 1:使能高速4~16MHz晶振振荡器(HXTAL)时钟监视器 当硬件检测到HXTAL时钟被阻塞在低或高状态时,内部硬件自动切换系统时钟到IRC8M时钟。恢复原来系统时钟的方式有以下几种:外部复位,上电复位,软件清CKMIF位。 注意:使能HXTAL时钟监视器以后,硬件无视控制位IRC8MEN的状态,自动使能IRC8M时钟。 |
[18] | HXTALBPS | 高速晶体振荡器(HXTAL)时钟旁路模式使能 只有在HXTALEN位为0时HXTALBPS位才可写 0:禁用HXTAL旁路模式 1:使能HXTAL旁路模式 HXTAL输出时钟等于输入时钟 |
[17] | HXTALSTB | 高速晶体振荡器(HXTAL)时钟稳定标志位 硬件置‘1’来子时HXTAL振荡器时钟是否稳定待用 0:HXTAL振荡器未稳定 1:HXTAL振荡器已稳定 |
[16] | HXTALEN | 高速晶体振荡器(XTAL)使能 软件置位或复位,如果HXTAL时钟作为系统时钟或者当PLL时钟做为系统时钟时,其作为PLL的输入时钟,该位不能被复位。进入深度睡眠或待机模式时硬件自动复位 0:高速4~16MHz晶体振荡器被关闭 1:高速4~16MHz晶体振荡器被打开 |
[15:8] | IRC8MCALIB[7:0] | 内部8MHz RC振荡器校准值寄存器 上电时自动加载这些位 |
[7:3] | IRC8MADJ[4:0] | 内部8MHz RC振荡器时钟调整值 这些位由软件置位,最终调整值为IRC8MADJ[4:0]位域的当前值加上IRC8MCALIB[7:0]位域的值。最终调整值应该调整IRC8M到8MHz ± 1% |
[2] | 保留 | 必须保持复位值 |
[1] | IRC8MSTB | IRC8M内部8MHz RC振荡器稳定标志位 硬件置‘1’来指示IRC8M振荡器时钟是否稳定待用 0:IRC8M振荡器未稳定 1:IRC8M振荡器已稳定 |
[0] | IRC8MEN | 内部8MHz RC振荡器使能 软件置位或复位,如果IRC8M时钟作为系统时钟时,该位不能被复位。当从深度睡眠或待机模式返回,或当CKMEN置位同时用作系统时钟的HXTAL振荡器发生故障时,该位由硬件置1来启动IRC8M振荡器 0:内部8MHz RC振荡器被关闭 1:内部8MHz RC振荡器被打开 |
2.2、时钟配置寄存器0 (RCU_CFG0)
位/位域 | 名称 | 描述 | ||
[31:29] | 保留 | 必须保持复位值 | ||
[28] | ADCPSC[2] | ADCPSC的第2位 参考寄存器RCU_CFG0的14到15位 | ||
[27] | PLLMF[4] | PLLMF的第四位 参考寄存器RCU_CFG0的18到21位 | ||
[26:24] | CKOUT0SEL[2:0] | CKOUT0时钟源选择 由软件置位或清零 0xx:无时钟输出 100:选择系统时钟CK_SYS 101:选择内部8M RC振荡器时钟 110:选择高速晶体振荡器时钟(HXTAL) 111:选择(CK_PLL/2)时钟 | ||
[23:22] | USBDPSC[1:0] | USBD的时钟分频系数 由软件置位或清零。USBD的时钟必须为48MHz,当USBD时钟使能的时候,这些位无法修改 00:CKUSBD=CK_PLL/1.5 01:CKUSBD=CK_PLL 10:CKUSBD=CK_PLL/2.5 11:CKUSBD=CK_PLL/2 | ||
[21:18] | PLLMF[3:0] | PLL时钟倍频因子 与寄存器RCU_CFG0的27位共同构成倍频因子,由软件置位或清零 注意:PLL输出时钟频率不能超过108MHz
| ||
[17] | PREDV0 | PREDV0分频因子 由软件置位或清零,PLL未使能时,可以修改这些位 0:PREDV0输入源时钟未分频 1:PREDV0输入源时钟2分频 | ||
[16] | PLLSEL | PLL时钟源选择 由软件置位或复位,控制PLL时钟源 0:(IRC8M/2)被选择为PLL时钟的时钟源 1:HXTAL时钟被选择为PLL时钟的时钟源 | ||
[15:14] | ADCPSC[1:0] | ADC的时钟分频系数 与寄存器RCU_CFG0的28为共同构成分频因子,由软件置位或清零 000:CK_ADC=CK_APB2/2 001:CK_ADC=CK_APB2/4 010:CK_ADC=CK_APB2/6 011:CK_ADC=CK_APB2/8 100:CK_ADC=CK_APB2/2 101:CK_ADC=CK_APB2/12 110:CK_ADC=CK_APB2/8 111:CK_ADC=CK_APB2/16 | ||
[13:11] | APB2PSC[2:0] | APB2预分频选择 由软件置位或清零,控制APB2时钟分频因子 0xx:选择CK_AHB时钟不分频 100:选择CK_AHB时钟2分频 101:选择CK_AHB时钟4分频 110:选择CK_AHB时钟8分频 111:选择CK_AHB时钟16分频 | ||
[10:8] | AHB1PSC[2:0] | APB1预分频选择 由软件置位或清零,控制APB1时钟分频因子 0xx:选择CK_AHB时钟不分频 100:选择CK_AHB时钟2分频 101:选择CK_AHB时钟4分频 110:选择CK_AHB时钟8分频 111:选择CK_AHB时钟16分频 | ||
[7:4] | AHBPSC[3:0] | AHB预分频选择 由软件置位或清零,控制AHB时钟分频因子 0xxx:选择CK_SYS时钟不分频 1000:选择CK_SYS时钟2分频 1001:选择CK_SYS时钟4分频 1010:选择CK_SYS时钟8分频 1011:选择CK_SYS时钟16分频 1100:选择CK_SYS时钟64分频 1101:选择CK_SYS时钟128分频 1110:选择CK_SYS时钟256分频 1111:选择CK_SYS时钟512分频 | ||
[3:2] | SCSS[1:0] | 系统时钟选择状态 由硬件置位或清零,标识当前系统时钟的时钟源 00:选择CK_IRC8M时钟作为CK_SYS时钟源 01:选择CK_HXTAL时钟作为CK_SYS时钟源 10:选择CK_PLL时钟作为CK_SYS时钟源 11:保留 | ||
[1:0] | SCS[1:0] | 系统时钟选择 由软件配置选择系统时钟源。由于CK_SYS的改变存在固有的延迟,因此软件应当读SCSS位来确保时钟源切换是否结束。在从深度睡眠或待机模式中返回时,以及当HXTAL直接或间接作为系统时钟同时HXTAL时钟监视器检测到HXTAL故障时,强制选择IRC8M作为系统时钟。 00:选择CK_IRC8M时钟作为CK_SYS时钟源 01:选择CK_HXTAL时钟作为CK_SYS时钟源 10:选择CK_PLL时钟作为CK_SYS时钟源 11:保留 |
三、系统时钟初始化代码(SystemClock_Config)
这里外部高速晶振是8MHz 我们根据上述函数来计算我们系统时钟是多少,
8MHz/PREDV0*PLLMF=CK_PLL=CK_SYS
CK_SYS=8MHz/1*9=72MHz,所以我们这里配置的系统时钟频率时72MHz
CK_AHB=CK_SYS/1=72MHz,所以我们这里配置的AHB总线时钟频率时72MHz
CK_APB1=CK_AHB/2=36MHz, APB1总线时钟频率是36MHz
CK_APB2=CK_AHB/1=72MHz, APB2总线时钟频率是72MHz
下面我们来看下rcu_osci_on(RCU_HXTAL);这个函数是怎么实现的
3.1、先查看RCU_HXTAL这个传参是多少
由于代码我们查找对应参数如上,经过替换可以得到
RCU_HXTAL = RCU_REGIDX_BIT(0x00,16U);
=(((uint32_t)(regidx)<<6) | (uint32_t)(bitpos))
=(((uint32_t)(0x00)<<6) | (uint32_t)(16U))
=((0x0000 0000<<6) | (0x0000 0010)
=0x0000 0010
所以RCU_HXTAL =0x0000 0010也就是16
3.2、rcu_osci_on函数实现
这里先看右边,RCU_HXTAL=0x0000 0010所以osci=0x0000 0010,
所以RCU_BIT_POS(osci)=0x0000 0010 & 0x1F
= 0x0000 0010
=16
BIT(RCU_BIT_POS(osci)) = BIT(16)
= 0x0000 0001 << 16
= 0x0001 0000
RCU这里嵌套了很多层,这里就不详细描述,RCU = 0x4002 1000
左边RCU_REG_VAL(osci) = RCU_REG_VAL(0x0000 0010)
= REG32(RCU + (0x0000 0010 >> 6))
= REG32(0x4002 1000 +0x0000 0000)
= REG32(0x4002 1000)
REG32(addr)这个宏定义就是把addr这个数转换成地址
所以最后RCU_REG_VAL(osci) |= BIT(RCU_BIT_POS(osci))
REG32(0x4002 1000) |= 0x0001 0000
也就是把0x0001 0000写入0x4002 1000这个地址中,由于0x4002 1000这个地址是RCU基地址,RCU_CTL偏移地址为0,0x4002 1000这个地址就是RCU_CTL的地址,这个语句就是往RCU_CTL这个寄存器写入0x0001 0000,把RCU_CTL这个寄存器16位写入1,这里查看RCU_CTL寄存器描述,可以得到这个语句就是打开(4 ~ 16Mhz晶体振荡器)
其他函数不详细介绍,请按照“下面我们来看下rcu_osci_on(RCU_HXTAL);这个函数是怎么实现的”步骤来进行分析。
四、SystemCoreClockUpdate函数介绍
由于篇幅有限这里只贴出了使用的代码"//……"这个符号表示中间还有一部分代码被省略了,具体请查看自己代码。
上述函数,获取RCU_CFG0的不同寄存器获取的结果如下:
在函数SystemClock_Config中我们设置RCU_CFG0寄存器中SCSS,PLLSEL,PLLMF位,具体设置请参考函数SystemClock_Config,这里读取出来的可以看出来和我们设置的一样,
HXTAL_VALUE这个宏定义是表示我们晶振是多少兆
由于在SystemClock_Config中我们设置了PLLMF倍频因子是9倍,所以系统时钟是SystemCoreClock=HXTAL_VALUE*9=72000000Hz(72MHz)
五、验证设置结果
通过系统函数获取时钟配置 uint32_t rcu_clock_freq_get(rcu_clock_freq_enum clock)函数可以用来获取时钟参数
这里可以看到我们设置了APB1总线,APB2总线,AHB总线和我们设置的一样,具体可以参考本文“三、系统时钟初始化代码(SystemClock_Config)”
本人水平有限,如有错误,欢迎指正,原创不易,转载请注明出处