一 时钟控制单元
1. HXTAL:4-32高速外部时钟MHz外部振荡器可以为系统提供更准确的主时钟。具有特定频率的晶体必须靠近两个HXTAL引脚。必须根据所选振荡器调整与晶体连接的外部电阻和电容。
2. IRC8M:高速内部8MHz时钟,内部8MHz RC振荡器时钟,简称IRC8M时钟,拥有8MHz设备上电后的固定频率CPU默认选择的时钟源是IRC8M时钟。
3. IRC28M:高速内部28MHz时钟,内部28MHz RC振荡器时钟 (IRC28M) 固定频率28MHz,专门用作ADC时钟。
4. IRC48M:高速内部48MHz时钟,内部48M RC振荡器时钟(IRC48M) 48有固定频率MHz,用作USB时钟或者PLL时钟源。
5. LXTAL:低速外部时钟,LXTAL晶体是一个32.768kHz低速外部晶体或陶瓷谐振器。它为实时时钟电路提供了低功耗确的时钟源。
6. IRC40K:低速内部时钟,IRC40K RC振荡器时钟扮演低功耗时钟源的角色,其时钟频率约为40 kHz,为独立看门狗定时器和实时钟电路提供时钟。
7. PLL:锁相环,内锁相环PLL输入参考频率为4~32MHz的时钟基准2 ~64倍频可提供16~108 MHz时钟输出。
二 系统时钟架构
以GD32F330系列为例,Cortex-M最高时钟频率为84架构MHz。
通过固件库system_gd32f10x.c可知:
/* HXTAL is stable */ /* AHB = SYSCLK */ RCU_CFG0 |= RCU_AHB_CKSYS_DIV1; /* APB2 = AHB/2 */ RCU_CFG0 |= RCU_APB2_CKAHB_DIV2; /* APB1 = AHB/2 */ RCU_CFG0 |= RCU_APB1_CKAHB_DIV2;
AHB总线为系统时钟的1分频,最高频率为84MHz;APB1总线为系统时钟的2分频,即最高频率为42MHz;APB2总线为系统时钟的2分频,即最高频率为42MHz;
此外,通过时钟架构图,您可以清楚地了解芯片外设所挂载的时钟总线(AHB、APB1、APB2),以TIMER例如,挂载是APB1总线,USART0挂载APB2总线。
三 时钟树分析
① 以HXTAL为例,外部HXTAL时钟源为系统提供时钟频率。
② PLLPRESEL选择外部晶振作为PLL时钟源,经过PREDV分频,PLLSEL选择外部晶振作为PLL时钟源,经过PLL倍频得到CK_PLL,再通过SCS选择PLL时钟源作为系统时钟。
③ CK_SYS通过分频获得系统时钟CK_AHB,即为AHB总线时钟频率。
④ 以TIMER2为例,TIMER2挂载APB1总线,假如APB1分频系数为1,则TIMER2频率为AHB,否则TIMER2频率为AHB/(APB1分频系数/2)。
⑤ 以USART0为例,USART0挂载APB2总线,通过APB2分频得到CK_APB直接提供2频率USART0使用。
四 时钟初始化
1. 在startup_gd32f3x0.s在文件中找到以下代码段,进入SystemInit()接口
/* reset Handler */ Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT SystemInit IMPORT __main LDR R0, =SystemInit BLX R0 LDR R0, =__main BX R0 ENDP
2. 是RCU相关寄存器的初始化system_gd32f3x0.c找到以下代码,输入system_clock_config()函数。
void SystemInit(void) { #if (defined(GD32F350)) RCU_APB2EN |= BIT(0); CMP_CS |= (CMP_CS_CMP1MSEL | CMP_CS_CMP0MSEL); #endif /* GD32F350 */ if(((FMC_OBSTAT & OB_OBSTAT_PLEVEL_HIGH) != OB_OBSTAT_PLEVEL_HIGH) && (((FMC_OBSTAT >> 13) & 0x1) == SET)) { FMC_KEY = UNLOCK_KEY0; FMC_KEY = UNLOCK_KEY1 ; FMC_OBKEY = UNLOCK_KEY0; FMC_OBKEY = UNLOCK_KEY1 ; FMC_CTL |= FMC_CTL_OBER; FMC_CTL |= FMC_CTL_START; while((uint32_t)0x00U != (FMC_STAT & FMC_STAT_BUSY)); FMC_CTL &= ~FMC_CTL_OBER; FMC_CTL |= FMC_CTL_OBPG; if((FMC_OBSTAT & OB_OBSTAT_PLEVEL_HIGH) == OB_OBSTAT_PLEVEL_NO) { OB_SPC = FMC_NSPC; } else if((FMC_OBSTAT & OB_OBSTAT_PLEVEL_HIGH) == OB_OBSTAT_PLEVEL_LOW) { OB_SPC = FMC_LSPC; } OB_USER = OB_USER_DEFAULT & ((uint8_t)(FMC_OBSTAT >> 8)); OB_DATA0 = ((uint8_t)(FMC_OBSTAT >> 16)); OB_DATA1 = ((uint8_t)(FMC_OBSTAT >> 24)); OB_WP0 = ((uint8_t)(FMC_WP)); OB_WP1 = ((uint8_t)(FMC_WP >> 8)); while((uint32_t)0x00U != (FMC_STAT & FMC_STAT_BUSY)); FMC_CTL &= ~FMC_CTL_OBPG; FMC_CTL &= ~FMC_CTL_OBWEN; FMC_CTL |= FMC_CTL_LK; } /* FPU settings */ #if (__FPU_PRESENT == 1U) && (__FPU_USED == 1U) SCB->CPACR |= ((3UL << 10 * 2) | (3UL << 11 * 2)); /* set CP10 and CP11 Full Access */ #endif /* enable IRC8M */ RCU_CTL0 |= RCU_CTL0_IRC8MEN; while(0U == (RCU_CTL0 & RCU_CTL0_IRC8MSTB)) { } RCU_CFG0 &= ~(RCU_CFG0_SCS); RCU_CTL0 &= ~(RCU_CTL0_HXTALEN | RCU_CTL0_CKMEN | RCU_CTL0_PLLEN | RCU_CTL0_HXTALBPS); /* reset RCU */ RCU_CFG0 &= ~(RCU_CFG0_SCS | RCU_CFG0_AHBPSC | RCU_CFG0_APB1PSC | RCU_CFG0_APB2PSC | \ RCU_CFG0_ADCPSC | RCU_CFG0_CKOUTSEL | RCU_CFG0_CKOUTDIV | RCU_CFG0_PLLDV); RCU_CFG0 &= ~(RCU_CFG0_PLLSEL | RCU_CFG0_PLLMF | RCU_CFG0_PLLMF4 | RCU_CFG0_PLLDV); #if (defined(GD32F350)) RCU_CFG0 &= ~(RCU_CFG0_USBFSPSC); RCU_CFG2 &= ~(RCU_CFG2_CECSEL | RCU_CFG2_USBFSPSC2); #endif /* GD32F350 */ RCU_CFG1 &= ~(RCU_CFG1_PREDV | RCU_CFG1_PLLMF5 | RCU_CFG1_PLLPRESEL); RCU_CFG2 &= ~(RCU_CFG2_USART0SEL | RCU_CFG2_ADCSEL); RCU_CFG2 &= ~RCU_CFG2_IRC28MDIV; RCU_CFG2 &= ~RCU_CFG2_ADCPSC2; RCU_CTL1 &= ~RCU_CTL1_IRC28MEN; RCU_ADDCTL &= ~RCU_ADDCTL_IRC48MEN; RCU_INT = 0x00000000U; CU_ADDINT = 0x00000000U;
/* configure system clock */
system_clock_config();
#ifdef VECT_TAB_SRAM
nvic_vector_table_set(NVIC_VECTTAB_RAM, VECT_TAB_OFFSET);
#else
nvic_vector_table_set(NVIC_VECTTAB_FLASH, VECT_TAB_OFFSET);
#endif
}
3. 选择系统时钟频率84MHZ,在system_gd32f3x0.c找到如下代码,进入system_clock_84m_hxtal()。
static void system_clock_config(void)
{
#ifdef __SYSTEM_CLOCK_8M_HXTAL
system_clock_8m_hxtal();
#elif defined (__SYSTEM_CLOCK_72M_PLL_HXTAL)
system_clock_72m_hxtal();
#elif defined (__SYSTEM_CLOCK_72M_PLL_IRC8M_DIV2)
system_clock_72m_irc8m();
#elif defined (__SYSTEM_CLOCK_72M_PLL_IRC48M_DIV2)
system_clock_72m_irc48m();
#elif defined (__SYSTEM_CLOCK_84M_PLL_HXTAL)
system_clock_84m_hxtal();
#elif defined (__SYSTEM_CLOCK_84M_PLL_IRC8M_DIV2)
system_clock_84m_irc8m();
#elif defined (__SYSTEM_CLOCK_96M_PLL_HXTAL)
system_clock_96m_hxtal();
#elif defined (__SYSTEM_CLOCK_96M_PLL_IRC8M_DIV2)
system_clock_96m_irc8m();
#elif defined (__SYSTEM_CLOCK_96M_PLL_IRC48M_DIV2)
system_clock_96m_irc48m();
#elif defined (__SYSTEM_CLOCK_108M_PLL_HXTAL)
system_clock_108m_hxtal();
#elif defined (__SYSTEM_CLOCK_108M_PLL_IRC8M_DIV2)
system_clock_108m_irc8m();
#else
system_clock_8m_irc8m();
#endif /* __SYSTEM_CLOCK_8M_HXTAL */
}
4. 根据系统时钟频率,去配置其他外设时钟源,在system_gd32f3x0.c找到如下代码。
static void system_clock_84m_hxtal(void)
{
uint32_t timeout = 0U;
uint32_t stab_flag = 0U;
/* enable HXTAL */
RCU_CTL0 |= RCU_CTL0_HXTALEN;
/* wait until HXTAL is stable or the startup time is longer than HXTAL_STARTUP_TIMEOUT */
do {
timeout++;
stab_flag = (RCU_CTL0 & RCU_CTL0_HXTALSTB);
} while((0U == stab_flag) && (HXTAL_STARTUP_TIMEOUT != timeout));
/* if fail */
if(0U == (RCU_CTL0 & RCU_CTL0_HXTALSTB)) {
return;
}
/* HXTAL is stable */
/* AHB = SYSCLK */
RCU_CFG0 |= RCU_AHB_CKSYS_DIV1;
/* APB2 = AHB/2 */
RCU_CFG0 |= RCU_APB2_CKAHB_DIV2;
/* APB1 = AHB/2 */
RCU_CFG0 |= RCU_APB1_CKAHB_DIV2;
/* PLL = HXTAL /4 * 21 = 84 MHz */
RCU_CFG0 &= ~(RCU_CFG0_PLLSEL | RCU_CFG0_PLLMF | RCU_CFG0_PLLMF4 | RCU_CFG0_PLLPREDV); //16,27|(18,21),27,17
RCU_CFG1 &= ~(RCU_CFG1_PLLPRESEL | RCU_CFG1_PLLMF5 | RCU_CFG1_PREDV); //30,31,(0,3)
RCU_CFG0 |= (RCU_PLLSRC_HXTAL_IRC48M | (RCU_PLL_MUL21 & (~RCU_CFG1_PLLMF5))); //16,
RCU_CFG1 |= (RCU_PLLPRESEL_HXTAL | RCU_PLL_PREDV4); //设置RCU_CFG1寄存器PREDV[3:0]预分频系数
RCU_CFG1 |= (RCU_PLL_MUL21 & RCU_CFG1_PLLMF5); //RCU_CFG1_PLLMF5实际表示的是 :RCU_CFG0 的位 27,21:18
/* enable PLL */
RCU_CTL0 |= RCU_CTL0_PLLEN;
/* wait until PLL is stable */
while(0U == (RCU_CTL0 & RCU_CTL0_PLLSTB)) {
}
/* select PLL as system clock */
RCU_CFG0 &= ~RCU_CFG0_SCS;
RCU_CFG0 |= RCU_CKSYSSRC_PLL;
/* wait until PLL is selected as system clock */
while(0U == (RCU_CFG0 & RCU_SCSS_PLL)) {
}
}
五 代码修改
1. 设置外部晶振频率,在gd32f3x0.h文件,找到如下代码段,修改外部晶振为16MHZ。
/* define value of high speed crystal oscillator (HXTAL) in Hz */
#if !defined (HXTAL_VALUE)
#define HXTAL_VALUE ((uint32_t)16000000)
#endif /* high speed crystal oscillator value */
2. 选择外部时钟源,在system_gd32f3x0.c文件,找到如下代码段,_SYS_OSC_CLK为系统时钟主频率选择宏定义,使用外部晶振选择__HXTAL。
/* system frequency define */
#define __IRC8M (IRC8M_VALUE) /* internal 8 MHz RC oscillator frequency */
#define __HXTAL (HXTAL_VALUE) /* high speed crystal oscillator frequency */
#define __SYS_OSC_CLK (__HXTAL) /* main oscillator frequency */
3. 设置系统主频率大小,在system_gd32f3x0.c文件中找到如下代码段,选择外部时钟源通过PLL配置为84MHZ系统时钟频率。
#if defined (GD32F330)
//#define __SYSTEM_CLOCK_8M_HXTAL (__HXTAL)
//#define __SYSTEM_CLOCK_8M_IRC8M (__IRC8M)
//#define __SYSTEM_CLOCK_72M_PLL_HXTAL (uint32_t)(72000000)
//#define __SYSTEM_CLOCK_72M_PLL_IRC8M_DIV2 (uint32_t)(72000000)
//#define __SYSTEM_CLOCK_72M_PLL_IRC48M_DIV2 (uint32_t)(72000000)
#define __SYSTEM_CLOCK_84M_PLL_HXTAL (uint32_t)(84000000)
//#define __SYSTEM_CLOCK_84M_PLL_IRC8M_DIV2 (uint32_t)(84000000)
#endif /* GD32F330 */
4. 修改PLL分频倍频系数
修改前:系统外部晶振HXTAL默认为8MHZ,带入公式:
CK_PLL = (CK_HXTAL/2) * 21 = 84 MHz,得到PLL分频系数为2,PLL倍频系数为21
/* PLL = HXTAL /2 * 21 = 84 MHz */
RCU_CFG0 &= ~(RCU_CFG0_PLLSEL | RCU_CFG0_PLLMF | RCU_CFG0_PLLMF4 | RCU_CFG0_PLLPREDV);
RCU_CFG1 &= ~(RCU_CFG1_PLLPRESEL | RCU_CFG1_PLLMF5 | RCU_CFG1_PREDV);
RCU_CFG0 |= (RCU_PLLSRC_HXTAL_IRC48M | (RCU_PLL_MUL21 & (~RCU_CFG1_PLLMF5)));
RCU_CFG1 |= (RCU_PLLPRESEL_HXTAL | RCU_PLL_PREDV2);
RCU_CFG1 |= (RCU_PLL_MUL21 & RCU_CFG1_PLLMF5);
/* enable PLL */
RCU_CTL0 |= RCU_CTL0_PLLEN;
修改后:系统外部晶振HXTAL为16MHZ,带入公式:
CK_PLL = (CK_HXTAL/4) * 21 = 84 MHz,得到PLL分频系数为4,PLL倍频系数为21,修改如下
RCU_CFG1 |= (RCU_PLLPRESEL_HXTAL | RCU_PLL_PREDV4);
六 PLL相关寄存器配置
前面介绍时钟树的时候说过,选择PLL作为系统时钟源需要设置四个地方,PLLPRESEL选择器,PREDV分频系数,PLLSEL选择器,PLL倍频系数。RCU_CFG1寄存器决定分频系数,RCU_CFG0寄存器决定倍频系数。查看数据手册,可以知道:
1. PLLPRESEL选择器
由 RCU_CFG1寄存器的第30位 PLLPRESEL决定 ,或由RCU_CFG0寄存器的第17位PLLPREDV决定。
2.PREDV分频系数
由RCU_CFG1寄存器3-0位的PREDV[3,0]决定,同时CU_CFG0寄存器的第17位和PREDV的第0位功能相同。
3. PLLSEL选择器
由RCU_CFG0寄存器的第16位决定
4. PLL倍频系数
由RCU_CFG0寄存器21-18位的PLLMF[3,0],RCU_CFG0寄存器的27位 PLLMF[4],RCU_CFG1寄存器的31位 PLLMF[5]共同决定。
七 仿真验证
源码链接:GD32时钟配置代码学习工程-嵌入式文档类资源-CSDN下载
参考文章:
http://t.csdn.cn/JHan5
http://t.csdn.cn/k0m3W