GD32F4xx芯片开发记录(一)—系统及相关时钟设置
在进行GD32F4xx芯片开发之后,踩过最多的坑就是时钟的坑,所以后来痛定思痛认真读了一下例程代码进行解析,在此记录一下,希望也给大家有个小小的参考
前言
例程里关于时钟宏定义位置在system_gd32f4xx.c文件里
/* system frequency define */
#define __IRC16M (IRC16M_VALUE) /* internal 16 MHz RC oscillator frequency */
#define __HXTAL (HXTAL_VALUE) /* high speed crystal oscillator frequency */
#define __SYS_OSC_CLK (__IRC16M) /* main oscillator frequency */
/* select a system clock by uncommenting the following line */
#define __SYSTEM_CLOCK_200M_PLL_8M_HXTAL (uint32_t)(200000000)
上面程序段中宏定义为下面程序条件编译做的,这里改变宏定义后面的频率也不能改变系统时钟,这里只起到了提示作用,具体的时钟设置在条件编译里的函数下。
/* set the system clock frequency and declare the system clock configuration function */
#ifdef __SYSTEM_CLOCK_IRC16M
uint32_t SystemCoreClock = __SYSTEM_CLOCK_IRC16M;
static void system_clock_16m_irc16m(void);
#elif defined (__SYSTEM_CLOCK_HXTAL)
uint32_t SystemCoreClock = __SYSTEM_CLOCK_HXTAL;
static void system_clock_hxtal(void);
#elif defined (__SYSTEM_CLOCK_120M_PLL_IRC16M)
uint32_t SystemCoreClock = __SYSTEM_CLOCK_120M_PLL_IRC16M;
static void system_clock_120m_irc16m(void);
#elif defined (__SYSTEM_CLOCK_120M_PLL_8M_HXTAL)
uint32_t SystemCoreClock = __SYSTEM_CLOCK_120M_PLL_8M_HXTAL;
static void system_clock_120m_8m_hxtal(void);
#elif defined (__SYSTEM_CLOCK_120M_PLL_25M_HXTAL)
uint32_t SystemCoreClock = __SYSTEM_CLOCK_120M_PLL_25M_HXTAL;
static void system_clock_120m_25m_hxtal(void);
#elif defined (__SYSTEM_CLOCK_168M_PLL_IRC16M)
uint32_t SystemCoreClock = __SYSTEM_CLOCK_168M_PLL_IRC16M;
static void system_clock_168m_irc16m(void);
#elif defined (__SYSTEM_CLOCK_168M_PLL_8M_HXTAL)
uint32_t SystemCoreClock = __SYSTEM_CLOCK_168M_PLL_8M_HXTAL;
static void system_clock_168m_8m_hxtal(void);
#elif defined (__SYSTEM_CLOCK_168M_PLL_25M_HXTAL)
uint32_t SystemCoreClock = __SYSTEM_CLOCK_168M_PLL_25M_HXTAL;
static void system_clock_168m_25m_hxtal(void);
#elif defined (__SYSTEM_CLOCK_200M_PLL_IRC16M)
uint32_t SystemCoreClock = __SYSTEM_CLOCK_200M_PLL_IRC16M;
static void system_clock_200m_irc16m(void);
#elif defined (__SYSTEM_CLOCK_200M_PLL_8M_HXTAL)
uint32_t SystemCoreClock = __SYSTEM_CLOCK_200M_PLL_8M_HXTAL;
static void system_clock_200m_8m_hxtal(void);
#elif defined (__SYSTEM_CLOCK_200M_PLL_25M_HXTAL)
uint32_t SystemCoreClock = __SYSTEM_CLOCK_200M_PLL_25M_HXTAL;
static void system_clock_200m_25m_hxtal(void);
#endif /* __SYSTEM_CLOCK_IRC16M */
例程里只定义了“__SYSTEM_CLOCK_200M_PLL_8M_HXTAL”
#define __SYSTEM_CLOCK_200M_PLL_8M_HXTAL (uint32_t)(200000000)
那么只有下面这段进行了编译
#elif defined (__SYSTEM_CLOCK_200M_PLL_8M_HXTAL)
uint32_t SystemCoreClock = __SYSTEM_CLOCK_200M_PLL_8M_HXTAL;
static void system_clock_200m_8m_hxtal(void);
而system_clock_200m_8m_hxtal(void);这个函数就是系统时钟的定义函数,下面我们根据数据手册具体分析一下这个函数。
#elif defined (__SYSTEM_CLOCK_200M_PLL_8M_HXTAL)
/*!
\brief configure the system clock to 200M by PLL which selects HXTAL(8M) as its clock source
\param[in] none
\param[out] none
\retval none
*/
static void system_clock_200m_8m_hxtal(void)
{
uint32_t timeout = 0U;
uint32_t stab_flag = 0U;
/* enable HXTAL */
RCU_CTL |= RCU_CTL_HXTALEN;
/* wait until HXTAL is stable or the startup time is longer than HXTAL_STARTUP_TIMEOUT */
do{
timeout++;
stab_flag = (RCU_CTL & RCU_CTL_HXTALSTB);
}while((0U == stab_flag) && (HXTAL_STARTUP_TIMEOUT != timeout));
/* if fail */
if(0U == (RCU_CTL & RCU_CTL_HXTALSTB)){
while(1){
}
}
RCU_APB1EN |= RCU_APB1EN_PMUEN;
PMU_CTL |= PMU_CTL_LDOVS;
/* HXTAL is stable */
/* AHB = SYSCLK */
RCU_CFG0 |= RCU_AHB_CKSYS_DIV1;
/* APB2 = AHB/2 */
RCU_CFG0 |= RCU_APB2_CKAHB_DIV2;
/* APB1 = AHB/4 */
RCU_CFG0 |= RCU_APB1_CKAHB_DIV4;
/* Configure the main PLL, PLL_M = 8, PLL_N = 400, PLL_P = 2, PLL_Q = 9 */
RCU_PLL = (8U | (400U << 6U) | (((2U >> 1U) - 1U) << 16U) |
(RCU_PLLSRC_HXTAL) | (9U << 24U));
/* enable PLL */
RCU_CTL |= RCU_CTL_PLLEN;
/* wait until PLL is stable */
while(0U == (RCU_CTL & RCU_CTL_PLLSTB)){
}
/* Enable the high-drive to extend the clock frequency to 200 Mhz */
PMU_CTL |= PMU_CTL_HDEN;
while(0U == (PMU_CS & PMU_CS_HDRF)){
}
/* select the high-drive mode */
PMU_CTL |= PMU_CTL_HDS;
while(0U == (PMU_CS & PMU_CS_HDSRF)){
}
/* select PLL as system clock */
RCU_CFG0 &= ~RCU_CFG0_SCS;
RCU_CFG0 |= RCU_CKSYSSRC_PLLP;
/* wait until PLL is selected as system clock */
while(0U == (RCU_CFG0 & RCU_SCSS_PLLP)){
}
}
程序分析
下面函数里的程序我们把变量宏定义一起附到后面,方便大家理解,后面分析时我们也一样会将宏定义放在后面。下面这段程序就是将寄存器RCU_CTL 的16置1,查看数据手册描述<程序段下图片>如下,根据描述可知,因为下面我们将PLL时钟作为系统时钟所以这里一定要将该位置1
/* enable HXTAL */
RCU_CTL |= RCU_CTL_HXTALEN;
#define RCU_CTL REG32(RCU + 0x00U) /*!< control register */
#define RCU_CTL_HXTALEN BIT(16) /*!< external high speed oscillator enable */
#define BIT(x) ((uint32_t)((uint32_t)0x01U<<(x)))
接着下面这段是等待高速振荡器稳定
/* wait until HXTAL is stable or the startup time is longer than HXTAL_STARTUP_TIMEOUT */
do{
timeout++;
stab_flag = (RCU_CTL & RCU_CTL_HXTALSTB);
}while((0U == stab_flag) && (HXTAL_STARTUP_TIMEOUT != timeout));
/* if fail */
if(0U == (RCU_CTL & RCU_CTL_HXTALSTB)){
while(1){
}
}
#define RCU_CTL_HXTALSTB BIT(17) /*!< external crystal oscillator clock stabilization flag */
这段就是开启PMU时钟,然后选择LDO高电压模式
RCU_APB1EN |= RCU_APB1EN_PMUEN;
PMU_CTL |= PMU_CTL_LDOVS;
#define RCU_APB1EN REG32(RCU + 0x40U) /*!< APB1 enable register */
#define PMU_CTL REG32((PMU) + 0x00U) /*!< PMU control register */
#define RCU_APB1EN_PMUEN BIT(28) /*!< PMU clock enable */
#define PMU_CTL_LDOVS BITS(14,15) /*!< LDO output voltage select */
时钟设置的重点
CK_AHB、CK_APB1、CK_APB2
从这里开始就是时钟设置里的重点了,这里分别设置了AHB、APB2、APB1时钟,我们查看手册可以看见,这里三个时钟分别为:
1、AHB时钟未分频所以CK_AHB = CK_SYS
2、APB2时钟2分频所以CK_APB2= CK_SYS/2
3、APB1时钟4分频所以CK_APB1= CK_SYS/4
而这里的CK_SYS就是系统时钟,也是CPU的运行时钟,而它的频率设置别着急马上就到。
/* HXTAL is stable */
/* AHB = SYSCLK */
RCU_CFG0 |= RCU_AHB_CKSYS_DIV1;
/* APB2 = AHB/2 */
RCU_CFG0 |= RCU_APB2_CKAHB_DIV2;
/* APB1 = AHB/4 */
RCU_CFG0 |= RCU_APB1_CKAHB_DIV4;
#define RCU_CFG0 REG32(RCU + 0x08U) /*!< clock configuration register 0 */
#define CFG0_AHBPSC(regval) (BITS(4,7) & ((uint32_t)(regval) << 4))
#define RCU_AHB_CKSYS_DIV1 CFG0_AHBPSC(0) /*!< AHB prescaler select CK_SYS */
#define CFG0_APB2PSC(regval) (BITS(13,15) & ((uint32_t)(regval) << 13))
#define RCU_APB2_CKAHB_DIV2 CFG0_APB2PSC(4) /*!< APB2 prescaler select CK_AHB/2 */
/* APB1 prescaler selection */
#define CFG0_APB1PSC(regval) (BITS(10,12) & ((uint32_t)(regval) << 10))
#define RCU_APB1_CKAHB_DIV4 CFG0_APB1PSC(5) /*!< APB1 prescaler select CK_AHB/4 */
时钟的源头
而接下来这部分就是重点中的重点,也是从晶振得到时钟,然后倍频分频得到各种时钟的设置。
1、首先这里由寄存器22位选择PLL时钟源是HXTAL(外部晶振)还是IRC16M(内部16M晶振)作为基础时钟CK_PLLSRC,
2、然后CK_PLLSRC由PLL VCO源时钟分频器进行分频得到CK_PLLVCOSRC
3、CK_PLLVCOSRC再由PLL VCO时钟倍频因子倍频CK_PLLVCO,
4、CK_PLLVCO再由PLLP输出频率分频系数分频得到CK_PLLP,同时K_PLLVCO也由PLLQ输出频率的分频系数分频得到CK_PLLQ
我们这里选择8M外部晶振作为基础时钟所以
CK_PLLSRC = 8MHZ
CK_PLLVCOSRC = CK_PLLSRC /8 = 1MHZ
CK_PLLVCO = CK_PLLVCOSRC *400 = 400MHZ
CK_PLLP = CK_PLLVCO /2 = 200MHZ
CK_PLLQ = CK_PLLVCO /9 = 44MHZ
而这里的CK_PLLP 时钟就是我们的系统时钟CK_SYS,具体程序说明下面会讲,这里直接提一下。
/* Configure the main PLL, PLL_M = 8, PLL_N = 400, PLL_P = 2, PLL_Q = 9 */
RCU_PLL = (8U | (400U << 6U) | (((2U >> 1U) - 1U) << 16U) |
(RCU_PLLSRC_HXTAL) | (9U << 24U));
#define RCU_PLL REG32(RCU + 0x04U) /*!< PLL register */
#define RCU_PLLSRC_HXTAL RCU_PLL_PLLSEL /*!< HXTAL clock selected as source clock of PLL, PLLSAI, PLLI2S */
#define RCU_PLL_PLLSEL BIT(22) /*!< PLL Clock Source Selection */
剩余的都是零零散散
因为我们使用了PLL时钟所以这里一定要使能一下并等时钟稳定
/* enable PLL */
RCU_CTL |= RCU_CTL_PLLEN;
/* wait until PLL is stable */
while(0U == (RCU_CTL & RCU_CTL_PLLSTB)){
}
下面这段就是选择CPU的高驱动模式和高驱动模式切换器,并且等待它们就绪
/* Enable the high-drive to extend the clock frequency to 200 Mhz */
PMU_CTL |= PMU_CTL_HDEN;
while(0U == (PMU_CS & PMU_CS_HDRF)){
}
/* select the high-drive mode */
PMU_CTL |= PMU_CTL_HDS;
while(0U == (PMU_CS & PMU_CS_HDSRF)){
}
#define PMU_CS REG32((PMU) + 0x04U) /*!< PMU control and status register */
#define PMU_CTL_HDEN BIT(16) /*!< high-driver mode enable */
#define PMU_CTL_HDS BIT(17) /*!< high-driver mode switch */
#define PMU_CS_HDRF BIT(16) /*!< high-driver ready flag */
#define PMU_CS_HDSRF BIT(17) /*!< high-driver switch ready flag */
下面这段其实就是将CK_PLLP作为系统时钟CK_SYS,并且确认选择正确
/* select PLL as system clock */
RCU_CFG0 &= ~RCU_CFG0_SCS;
RCU_CFG0 |= RCU_CKSYSSRC_PLLP;
/* wait until PLL is selected as system clock */
while(0U == (RCU_CFG0 & RCU_SCSS_PLLP)){
}
#define RCU_CFG0_SCS BITS(0,1) /*!< system clock switch */
/* RCU_CFG0 register bit define */
/* system clock source select */
#define CFG0_SCS(regval) (BITS(0,1) & ((uint32_t)(regval) << 0))
#define RCU_CKSYSSRC_PLLP CFG0_SCS(2) /*!< system clock source select PLLP */
/* system clock source select status */
#define CFG0_SCSS(regval) (BITS(2,3) & ((uint32_t)(regval) << 2))
#define RCU_SCSS_IRC16M CFG0_SCSS(0) /*!< system clock source select IRC16M */
#define RCU_SCSS_HXTAL CFG0_SCSS(1) /*!< system clock source select HXTAL */
#define RCU_SCSS_PLLP CFG0_SCSS(2) /*!< system clock source select PLLP */
到这里其实我们就已经将GD32F4xx芯片的主要时钟设置全部分析完成了,其他定时器时钟什么的都是由这些时钟来的,具体这里就不做分析了,大家可以看一下手册中的时钟树。这里特殊提一下开头的那个宏定义,其实它这里不是定义的外部晶振,跟系统时钟设置也没有任何关系,但是它的串口波特率设置里又用这个HXTAL_VALUE进行了设置,所以会导致串口波特率的紊乱,所以大家再进行外部晶振的更换后,比如8M晶振改成12M晶振后,除了上面我们所说的关于时钟的设置更改后,把这个宏定义也进行一下更改,别到时串口不好用还以为时钟不对,这也是我踩过得大坑。
#define __HXTAL (HXTAL_VALUE) /* high speed crystal oscillator frequency */
/* define value of high speed crystal oscillator (HXTAL) in Hz */
#if !defined (HXTAL_VALUE)
#define HXTAL_VALUE ((uint32_t)8000000)
#endif /* high speed crystal oscillator value */
uint32_t rcu_clock_freq_get(rcu_clock_freq_enum clock)
{
uint32_t sws, ck_freq = 0U;
uint32_t cksys_freq, ahb_freq, apb1_freq, apb2_freq;
uint32_t pllpsc, plln, pllsel, pllp, ck_src, idx, clk_exp;
/* exponent of AHB, APB1 and APB2 clock divider */
const uint8_t ahb_exp[16] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 6, 7, 8, 9};
const uint8_t apb1_exp[8] = {0, 0, 0, 0, 1, 2, 3, 4};
const uint8_t apb2_exp[8] = {0, 0, 0, 0, 1, 2, 3, 4};
sws = GET_BITS(RCU_CFG0, 2, 3);
switch(sws){
/* IRC16M is selected as CK_SYS */
case SEL_IRC16M:
cksys_freq = IRC16M_VALUE;
break;
/* HXTAL is selected as CK_SYS */
case SEL_HXTAL:
cksys_freq = HXTAL_VALUE;
break;
/* PLLP is selected as CK_SYS */
case SEL_PLLP:
/* get the value of PLLPSC[5:0] */
pllpsc = GET_BITS(RCU_PLL, 0U, 5U);
plln = GET_BITS(RCU_PLL, 6U, 14U);
pllp = (GET_BITS(RCU_PLL, 16U, 17U) + 1U) * 2U;
/* PLL clock source selection, HXTAL or IRC16M/2 */
pllsel = (RCU_PLL & RCU_PLL_PLLSEL);
if (RCU_PLLSRC_HXTAL == pllsel) {
ck_src = HXTAL_VALUE;
} else {
ck_src = IRC16M_VALUE;
}
cksys_freq = ((ck_src / pllpsc) * plln)/pllp;
break;
/* IRC16M is selected as CK_SYS */
default:
cksys_freq = IRC16M_VALUE;
break;
}
/* calculate AHB clock frequency */
idx = GET_BITS(RCU_CFG0, 4, 7);
clk_exp = ahb_exp[idx];
ahb_freq = cksys_freq >> clk_exp;
/* calculate APB1 clock frequency */
idx = GET_BITS(RCU_CFG0, 10, 12);
clk_exp = apb1_exp[idx];
apb1_freq = ahb_freq >> clk_exp;
/* calculate APB2 clock frequency */
idx = GET_BITS(RCU_CFG0, 13, 15);
clk_exp = apb2_exp[idx];
apb2_freq = ahb_freq >> clk_exp;
/* return the clocks frequency */
switch(clock){
case CK_SYS:
ck_freq = cksys_freq;
break;
case CK_AHB:
ck_freq = ahb_freq;
break;
case CK_APB1:
ck_freq = apb1_freq;
break;
case CK_APB2:
ck_freq = apb2_freq;
break;
default:
break;
}
return ck_freq;
}
第一次写文章,写的比较啰嗦,大家见谅,同时如果有错误,欢迎指正,也好及时更改。后续我也会继续写一下关于这系列芯片的其他文章,也是作为自己的记录!