今天在新项目(MCU 为华大 HC32F460)中不再使用外部晶振,转而要使用 HC32F460 内部的 HRC,之前在使用外部晶振时,对华大 MCU 的时钟配置有过一些了解,但是,由于使用内部晶振与使用外部晶振有些差别,今天就记录一下配置过程!没啥难的,就是单纯记录一下而已!
华大 MCU 时钟的配置,与 ST 的类似,都有很多选择,用户可以根据需要灵活选择。用户手册章节 6 时钟控制器(CMU)中的介绍已经很详细了,所以本文就重点结合代码来说明,还有就是从手册中摘录了一些配置中需要重点关注的点。
时钟控制器(CMU)
系统时钟框图是个好东西,基本配置项都一目了然了!如下图所示,左侧就是可选的时钟源。XTAL 就类似于 ST 的 HSE;XTAL32 就类似于 ST 的 LSE;HRC 就类似于 ST 的 HSI;LRC 就类似于 ST 的 LSI,至于 MRC 和 SWDTLRC 与 ST 差别就要大一些了。
- 外部高速振荡器(XTAL) 晶振的频率范围:4~24MHz
- 外部低速振荡器(XTAL32) 晶振的频率范围:32.768KHz
- MPLL 时钟(MPLL) 输入时钟输入可选外部高速振荡器(XTAL)或者内部高速振荡器(HRC)
- UPLL 时钟(UPLL) 输入时钟:输入可选外部高速振荡器(XTAL)或者内部高速振荡器(HRC)
- 内部高速振荡器(HRC) 频率:16MHz 或者20MHz
- 内部中速振荡器(MRC) 频率:8MHz
- 内部低速振荡器(LRC) 频率:32.768KHz
- SWDT 专用内部低速振荡器(SWDTRC)频率:10KHz
HRC
HRC 的频率可由 ICG1. HRCFREQSEL 配置成 16MHz 或者 20MHz。HRC 振荡器的优点是成本较低(无需使用外部组件)。此外,其启动速度也要比 XTAL 晶振块,但即使校准后,其精度也不及外部晶振。我本次要使用 HRC,所以本文就以 HRC 为重点关注对象,其他时钟的配置基本类似。
其中,需要重点关注 ICG 这个部分。用户手册章节 8 初始化配置(ICG)中有详细描述,这一部分貌似和 ST 的 OPT FLASH(或者叫 Option bytes)的作用差不多。重点关注的原因就是这个部分不是读写寄存器操作,而是在编写代码时,直接固化数据(通常使用编译器指令),下面是华大给出的库中的处理代码(hc32f46x_icg.c
):
#if defined ( __GNUC__ ) && !defined (__CC_ARM) /* GNU Compiler */
const uint32_t u32ICG[] __attribute__((section(".icg_sec"))) =
#elif defined (__CC_ARM)
const uint32_t u32ICG[] __attribute__((at(0x400))) =
#elif defined (__ICCARM__)
__root const uint32_t u32ICG[] @ 0x400 =
#else
#error "unsupported compiler!!"
#endif
{
/* ICG 0~ 3 */
ICG0_REGISTER_CONSTANT,
ICG1_REGISTER_CONSTANT,
ICG2_REGISTER_CONSTANT,
ICG3_REGISTER_CONSTANT,
/* ICG 4~ 7 */
ICG4_REGISTER_CONSTANT,
ICG5_REGISTER_CONSTANT,
ICG6_REGISTER_CONSTANT,
ICG7_REGISTER_CONSTANT,
};
其中,uint32_t u32ICG[]
这个数组中就是使用编译器命令固化的数据。里面的这些宏值的配置,不得不库文件 hc32f46x_icg.h
中根据需要来修改!这里要发句牢骚,对于 hc32f46x_icg.h
,ddl_config.h
有毛用! 看我下面的需求:
有些宏值的配置无法放到 ddl_config.h
中,不得不更改库文件!然后在配置时钟时需要使用如下代码:
/* 1. 启动 HRC, MCU 启动后默认以 MRC 来工作,以下开始切换到 HRC */
CLK_HrcCmd(Enable);
/* 根据手册,需要等待 HRC Ready 后才可以正常使用 */
while(Set != CLK_GetFlagStatus(ClkFlagHRCRdy));
这里有个坑需要注意,我之前在博文《华大 MCU 之一 HC32F460 替换 STM32F411 移植记录 》中也有说过。实际项目中,我们的程序结构多为下图所示:
hc32f46x_icg.c
只能放在 IAP 中!如果放到了 APP 中编译会产生错误!例如,在 ST 中,我们通常会在 APP 中来配置看门狗,但是在华大中这就行不通了!
PLL 的配置
在实际使用中,我们多数情况下需要将时钟源进行倍频,以使系统时钟达到一个较高的频率。倍频使用的器件就是 PLL。HC32F46xx 器件具有两个PLL:
- MPLL 由 XTAL 或HRC 振荡器提供时钟信号,并具有三个不同的输出时钟:
- P 分频器输出用于生成系统时钟(最高达 200 MHz)
- 三个输出都可用于生成 USBFS、TRNG、ADC 和 I2S 时钟。
- UPLL 三个输出亦可用于生成 USBFS、TRNG、ADC 和 I2S 时钟。
使用时注意以下三点:
- 在 HRC 或 XTAL 振荡器稳定后,再对 PLL 进行配置。
- MPLL/UPLL 的分频系数 M、N、P、Q、R 可独立配置(系统时钟框图中 N 在哪?我也不知道!)。由于在 PLL 使能后 PLL 配置参数便不可更改,所以建议先对 PLL 进行配置,然后再使能。
- 当进入掉电和停止模式后,两个 PLL 将由硬件禁止。
基本就是对应下面的代码(这里只配置了 MPLL,文章最后的完整示例里有 UPLL的配置)了:
/* 2. 设置 PLL 的时钟源为 HRC */
CLK_SetPllSource(ClkPllSrcHRC);
/* 3. MPLL config (主晶振 / pllmDiv * plln / PllpDiv = 128M). */
stcMpllCfg.pllmDiv = 16ul;
stcMpllCfg.plln = 256ul;
stcMpllCfg.PllpDiv = 2ul;
stcMpllCfg.PllqDiv = 8ul;
stcMpllCfg.PllrDiv = 2ul;
CLK_MpllConfig(&stcMpllCfg);
/* Enable MPLL. */
CLK_MpllCmd(Enable);
各种外设时钟
时钟的配置还有一部分就是分频出各种外设时钟(系统时钟框图的右侧输出的各种时钟),分频出各种外设时钟没有啥难的,只要保证不超过限制即可。各时钟的说明如下:
基本就是对应下面的代码(具体的分配系数根据自己的需求变化):
/* Set bus clk div. */
stcSysClkCfg.enHclkDiv = ClkSysclkDiv1; // 当前 128MHz,最大 168MHz
stcSysClkCfg.enExclkDiv = ClkSysclkDiv2; // 当前 64MHz,最大 84MHz
stcSysClkCfg.enPclk0Div = ClkSysclkDiv1; // 当前 128MHz,最大 168MHz
stcSysClkCfg.enPclk1Div = ClkSysclkDiv2; // 当前 64MHz,最大 84MHz
stcSysClkCfg.enPclk2Div = ClkSysclkDiv4; // 当前 32MHz,最大 60MHz
stcSysClkCfg.enPclk3Div = ClkSysclkDiv4; // 当前 32MHz,最大 42MHz
stcSysClkCfg.enPclk4Div = ClkSysclkDiv2; // 当前 64MHz,最大 84MHz
CLK_SysClkConfig(&stcSysClkCfg);
时钟切换
无论是华大还是 ST,都有一个章节来专门介绍时钟的切换。在系统启动以及进出低功耗时,都有可能需要进行时钟切换,如果没有正常的切换,可能导致无法启动。时钟切换必须严格遵循手册中给出的切换步骤!
在系统复位后,默认系统时钟为 MRC。通过设定寄存器 CMU_CKSW 切换时钟源,切换步骤参照时钟源切换。只有在目标时钟源已稳定的状态下,才可以从一个时钟源切换到另一个时钟源。
时钟切换时需要正确配置 Flash/SRAM 的等待周期,防止系统时钟频率大于 Flash/SRAM 的最大动作频率。这个也是很重要的,用惯了 ST 标准库的人可能对这个部分比较陌生,因为 ST 的工具会根据我们配置的频率自动为我们生成这部分的处理,手动移植时,就必须要关注这部分的配置。Flash/SRAM 的等待周期如下图所示:
关于这部分在用户手册 CPU 时钟和 FLASH 读取时间之间的关系 中有详细的步骤描述。
最终配置
最终,一个完整的配置时钟的函数如下所示:
/**
* @brief System Clock Configuration
* @retval None
*/
static void SystemClock_Config(void)
{
stc_clk_sysclk_cfg_t stcSysClkCfg;
// stc_clk_xtal_cfg_t stcXtalCfg; /* 配置外部 Xtal */
// stc_clk_xtal32_cfg_t stcXtal32Cfg; /* 配置外部 Xtal32 */
stc_clk_mpll_cfg_t stcMpllCfg;
stc_sram_config_t stcSramConfig;
#ifdef USE_USB
stc_clk_upll_cfg_t stcUpllCfg;
#endif
MEM_ZERO_STRUCT(stcSysClkCfg);
// MEM_ZERO_STRUCT(stcXtalCfg);
// MEM_ZERO_STRUCT(stcXtal32Cfg);
MEM_ZERO_STRUCT(stcMpllCfg);
MEM_ZERO_STRUCT(stcSramConfig);
/* Set bus clk div. */
stcSysClkCfg.enHclkDiv = ClkSysclkDiv1; // 当前 128MHz,最大 168MHz
stcSysClkCfg.enExclkDiv = ClkSysclkDiv2; // 当前 64MHz,最大 84MHz
stcSysClkCfg.enPclk0Div = ClkSysclkDiv1; // 当前 128MHz,最大 168MHz
stcSysClkCfg.enPclk1Div = ClkSysclkDiv2; // 当前 64MHz,最大 84MHz
stcSysClkCfg.enPclk2Div = ClkSysclkDiv4; // 当前 32MHz,最大 60MHz
stcSysClkCfg.enPclk3Div = ClkSysclkDiv4; // 当前 32MHz,最大 42MHz
stcSysClkCfg.enPclk4Div = ClkSysclkDiv2; // 当前 64MHz,最大 84MHz
CLK_SysClkConfig(&stcSysClkCfg);
// /* XTAL的配置 */
// /* Use Xtal as MPLL source. */
// stcXtalCfg.enMode = ClkXtalModeOsc;
// stcXtalCfg.enDrv = ClkXtalMidDrv;
// stcXtalCfg.enFastStartup = Enable;
// CLK_XtalConfig(&stcXtalCfg);
// CLK_XtalCmd(Enable);
/* 配置 HRC 经过 PLL 后作为系统时钟,而不是直接使用 HRC(HRC 是可以直接作为系统时钟) */
/* 1. 启动 HRC, MCU 启动后默认以 MRC 来工作,以下开始切换到 HRC */
CLK_HrcCmd(Enable);
/* 根据手册,需要等待 HRC Ready 后才可以正常使用 */
while(Set != CLK_GetFlagStatus(ClkFlagHRCRdy));
/* 2. 设置 PLL 的时钟源为 HRC */
CLK_SetPllSource(ClkPllSrcHRC);
/* 3. MPLL config (主晶振 / pllmDiv * plln / PllpDiv = 128M). */
stcMpllCfg.pllmDiv = 16ul;
stcMpllCfg.plln = 256ul;
stcMpllCfg.PllpDiv = 2ul;
stcMpllCfg.PllqDiv = 8ul;
stcMpllCfg.PllrDiv = 2ul;
CLK_MpllConfig(&stcMpllCfg);
/* Enable MPLL. */
CLK_MpllCmd(Enable);
/* flash read wait cycle setting */
EFM_Unlock();
EFM_SetLatency(EFM_LATENCY_3);
EFM_Lock();
/* sram init include read/write wait cycle setting */
stcSramConfig.u8SramIdx = Sram12Idx | Sram3Idx | SramHsIdx | SramRetIdx;
stcSramConfig.enSramRC = SramCycle2;
stcSramConfig.enSramWC = SramCycle2;
stcSramConfig.enSramEccMode = EccMode3;
stcSramConfig.enSramEccOp = SramNmi;
stcSramConfig.enSramPyOp = SramNmi;
SRAM_Init(&stcSramConfig);
/* Wait MPLL ready. */
while(Set != CLK_GetFlagStatus(ClkFlagMPLLRdy));
/* Switch system clock source to MPLL. */
CLK_SetSysClkSource(CLKSysSrcMPLL);
#if (DDL_RTC_ENABLE == DDL_ON)
// CLK_LrcCmd(Enable); //Enable LRC for RTC
// /* RTC 用 xtal32 */
// stcXtal32Cfg.enFastStartup = Disable;
// stcXtal32Cfg.enDrv = ClkXtal32HighDrv;
// stcXtal32Cfg.enFilterMode = ClkXtal32FilterModeFull;
// CLK_Xtal32Config(&stcXtal32Cfg);
// /* Startup xtal32 */
// CLK_Xtal32Cmd(Enable);
// /* wait for xtal32 running */
// Ddl_Delay1ms(3000u);
#endif
#if DDL_USBFS_ENABLE == DDL_ON
/* UPLL config (XTAL(当前为 6M) / pllmDiv * plln / PllpDiv = 48M). */
stcUpllCfg.pllmDiv = 6u;
stcUpllCfg.plln = 48u;
stcUpllCfg.PllpDiv = 1u;//48M
stcUpllCfg.PllqDiv = 1u;
stcUpllCfg.PllrDiv = 1u;
CLK_UpllConfig(&stcUpllCfg);
CLK_UpllCmd(Enable);
/* Wait UPLL ready. */
while(Set != CLK_GetFlagStatus(ClkFlagUPLLRdy))
{
;
}
/* Set USB clock source */
CLK_SetUsbClkSource(ClkUsbSrcUpllp);
#endif
}
参考
- HC32F460系列用户手册Rev1.2.pdf