在使用HC32官方的代码生成工具XHCode的时候发现一个小问题:虽然在时钟树界面配置了USB的时钟,但是生成的代码中并没有对USBCKS的初始化。同时翻看官方驱动库HC32F460_DDL_Rev3.3.0中的例程时,发现并没有USB的例程。所以稍微研究了一下各个外设的时钟源,以及如何选择各时钟源,写篇文章记录一下。
手册中的时钟树框图如下:
看起来有亿点复杂,稍微简化一下就是XHCode配置界面的样子:

外部XTAL,XTAL32以及内部的HRC,MRC,LRC这些低速时钟无需特别关心,因为它们基本上都是固定的值,没啥好讲的。
外部晶振XTAL的频率定义
位于common/system_hc32f460.h中,XTAL_VALUE即为晶振频率。XHCode并不会自动修改这个值,如果不是8M请自行修改。

注意
如果使用GCC进行开发的话,HRC这里会有一点小坑,它有两个频率16M和20M可以选择。如果代码是用20M的频率去配置一般是没有问题的,但是如果使用16M就会出现时钟初始化失败的情况。根本原因就在于HC32官方选择了一种很扭捏的方式配置HRC_VALUE的值,导致选择16M时实际代码里面是按20M在计算。从上面的图可以看到,HRC_VALUE并没有和MRC_VALUE,LRC_VALUE写在一起。
HRC_VALUE的具体配置位于SystemCoreClockUpdate():
但是这个函数位于main()之前,而且通过注释可以知道HRC_VALUE的值是根据ICG1.HRCFREQSEL来选择的。这个IGC1是个什么东西呢,手册里面是这样描述的:
HRCFREQSEL位是这样定义的:
也就是说,在烧录代码之前,要先烧录ICG1.HRCFREQSEL位为1才能才能选择HRC为16M,否则HRC的值会一直是20M。但是你要是使用XHCode生成的keil工程,而且使用keil进行烧录,就会发现使用16M配置也是正常的。原因在于XHCode生成了.SFR文件,里面提前写入了ICG1.HRCFREQSEL的值,keil在烧录代码之前就会提前烧录这个寄存器的值。但是GCC这边就没法这样干了,所以使用GCC进行开发时,如果没有特殊需求,HRC最好还是配置成20M。
低速时钟经过PLLSRC的选择进入MPLL和UPLL进行倍频和分频,然后得到UPLLR,UPLLQ,UPLLP,MPLLR,MPLLQ,MPLLP六个时钟,供系统时钟和外设时钟选择。官方例程中MPLL和UPLL的初始化如下:
c展开代码/* MPLL config */
// 以PLL为指代
stc_clock_pll_init_t stcMPLLInit;
(void)CLK_PLLStructInit(&stcMPLLInit);
stcMPLLInit.PLLCFGR = 0UL;
stcMPLLInit.PLLCFGR_f.PLLM = (2UL - 1UL);
stcMPLLInit.PLLCFGR_f.PLLN = (40UL - 1UL);
stcMPLLInit.PLLCFGR_f.PLLP = (2UL - 1UL);
stcMPLLInit.PLLCFGR_f.PLLQ = (2UL - 1UL);
stcMPLLInit.PLLCFGR_f.PLLR = (2UL - 1UL);
stcMPLLInit.u8PLLState = CLK_PLL_ON;
stcMPLLInit.PLLCFGR_f.PLLSRC = CLK_PLL_SRC_HRC;
(void)CLK_PLLInit(&stcMPLLInit);
/* UPLL config */
// 以PLLx为指代
stc_clock_xtal_init_t stcXtalInit;
(void)CLK_PLLxStructInit(&stcUpllInit);
stcUpllInit.u8PLLState = CLK_PLLX_ON;
stcUpllInit.PLLCFGR = 0UL;
stcUpllInit.PLLCFGR_f.PLLM = (2UL - 1UL);
stcUpllInit.PLLCFGR_f.PLLN = (40UL - 1UL);
stcUpllInit.PLLCFGR_f.PLLR = (8UL - 1UL);
stcUpllInit.PLLCFGR_f.PLLQ = (10UL - 1UL);
stcUpllInit.PLLCFGR_f.PLLP = (16UL - 1UL);
(void)CLK_PLLxInit(&stcUpllInit);
总之就是配置输入时钟源,以及分频和倍频系数。
系统时钟的时钟源有6个,分别是MPLLP,XTAL,XTAL32,HRC,MRC和LRC。选择系统时钟源的API是CLK_SetSysClockSrc(uint8_t u8Src),u8Src可以输入的参数如下:
c展开代码/**
* @defgroup CLK_System_Clock_Source System Clock Source
* @{
*/
#define CLK_SYSCLK_SRC_HRC (0x00U)
#define CLK_SYSCLK_SRC_MRC (0x01U)
#define CLK_SYSCLK_SRC_LRC (0x02U)
#define CLK_SYSCLK_SRC_XTAL (0x03U)
#define CLK_SYSCLK_SRC_XTAL32 (0x04U)
#define CLK_SYSCLK_SRC_PLL (0x05U)
系统时钟经过分频后又生成8个时钟:HCLK,PCLK0,PCLK1,PCLK2,PCLK3,PCLK4,EXCLK,TPIUCLK。各自的作用在手册中有详细说明,如下:
这8个时钟的分频系数可以通过CLK_SetClockDiv(uint32_t u32Clock, uint32_t u32Div)设置,示例如下:
c展开代码/* Set bus clock div. */
CLK_SetClockDiv(CLK_BUS_CLK_ALL, (CLK_HCLK_DIV1 | CLK_PCLK0_DIV1 | CLK_PCLK1_DIV2 | \
CLK_PCLK2_DIV4 | CLK_PCLK3_DIV2 | CLK_PCLK4_DIV2));
下面讲一下ADC时钟和USB时钟。
根据手册描述,ADC模块需要两个时钟,一个是模拟电路时钟ADCLK,另一个是数字接口时钟PCLK4。ADC的时钟源大致可以分为两类:
PCLK4 : PCLK2 = 1:1, 2:1, 4:1, 8:1, 1:2, 1:4的关系。ADC选择时钟源的API是CLK_SetPeriClockSrc(uint16_t u16Src)。说实话,刚看见这个函数名字的时候我还以为这个函数是通用的外设时钟源配置函数,直到我看见函数上面的注释:peripheral for ADC/DAC/TRNG,害得我找半天ADC选择时钟源的函数。
u16Src可以输入的参数如下:
c展开代码
/**
* @defgroup CLK_PERIPH_Sel CLK Peripheral Clock Selection
* @note ADC,I2S,DAC,TRNG
* @{
*/
/* PCLK2 is used for ADC clock, PCLK3 is used for I2S clock, PCLK4 is used for DAC/TRNG clock */
#define CLK_PERIPHCLK_PCLK (0x0000U)
#define CLK_PERIPHCLK_PLLP (0x0008U)
#define CLK_PERIPHCLK_PLLQ (0x0009U)
#define CLK_PERIPHCLK_PLLR (0x000AU)
#define CLK_PERIPHCLK_PLLXP (0x000BU)
#define CLK_PERIPHCLK_PLLXQ (0x000CU)
#define CLK_PERIPHCLK_PLLXR (0x000DU)
其中CLK_PERIPHCLK_PCLK就是上面的第一种配置,后面的就是第二种配置,PLL依旧指代MPLL,PLLX指代UPLL。
如果时钟源选择第一种的话,可以不使用CLK_SetPeriClockSrc(uint16_t u16Src)来配置时钟源,因为ADC的默认时钟源就是PCLK2和PCLK4,只需将PCLK2和PCLK4配置到正确的范围即可。
USBFS需要的时钟是48M,设置时钟源的API是CLK_SetUSBClockSrc(uint8_t u8Src),u8Src可以输入的参数如下:
c展开代码/**
* @defgroup CLK_USBCLK_Sel CLK USB Clock Selection
* @{
*/
#define CLK_USBCLK_SYSCLK_DIV2 (0x02U << CMU_USBCKCFGR_USBCKS_POS)
#define CLK_USBCLK_SYSCLK_DIV3 (0x03U << CMU_USBCKCFGR_USBCKS_POS)
#define CLK_USBCLK_SYSCLK_DIV4 (0x04U << CMU_USBCKCFGR_USBCKS_POS)
#define CLK_USBCLK_PLLP (0x08U << CMU_USBCKCFGR_USBCKS_POS)
#define CLK_USBCLK_PLLQ (0x09U << CMU_USBCKCFGR_USBCKS_POS)
#define CLK_USBCLK_PLLR (0x0AU << CMU_USBCKCFGR_USBCKS_POS)
#define CLK_USBCLK_PLLXP (0x0BU << CMU_USBCKCFGR_USBCKS_POS)
#define CLK_USBCLK_PLLXQ (0x0CU << CMU_USBCKCFGR_USBCKS_POS)
#define CLK_USBCLK_PLLXR (0x0DU << CMU_USBCKCFGR_USBCKS_POS)
PLL依旧指代MPLL,PLLX指代UPLL。为了让MCU跑在最高的频率,一般使用UPLL的时钟作为USB的时钟源,所以要在一开始就把UPLL初始化好,这也是写这篇文章的目的。
剩下还有I2S和CAN的时钟源配置有点特殊,这俩我用的比较少,等以后用到了再回来补充吧。
上面扯了一大堆,都是在配置外设的时钟源,要想外设真正跑起来,还需要开启外设的时钟。对应的API是
c展开代码FCG_Fcg0PeriphClockCmd(uint32_t u32Fcg0Periph, en_functional_state_t enNewState)
FCG_Fcg1PeriphClockCmd(uint32_t u32Fcg1Periph, en_functional_state_t enNewState)
FCG_Fcg2PeriphClockCmd(uint32_t u32Fcg2Periph, en_functional_state_t enNewState)
FCG_Fcg3PeriphClockCmd(uint32_t u32Fcg3Periph, en_functional_state_t enNewState)
其中u32Fcg0Periph,u32Fcg1Periph,u32Fcg2Periph,u32Fcg3Periph能输入的参数如下:
c展开代码/**
* @defgroup FCG_Global_Macros FCG Global Macros
* @{
*/
/**
* @defgroup FCG_FCG0_Peripheral FCG FCG0 peripheral
* @{
*/
#define FCG0_PERIPH_SRAMH (PWC_FCG0_SRAMH)
#define FCG0_PERIPH_SRAM12 (PWC_FCG0_SRAM12)
#define FCG0_PERIPH_SRAM3 (PWC_FCG0_SRAM3)
#define FCG0_PERIPH_SRAMRET (PWC_FCG0_SRAMRET)
#define FCG0_PERIPH_DMA1 (PWC_FCG0_DMA1)
#define FCG0_PERIPH_DMA2 (PWC_FCG0_DMA2)
#define FCG0_PERIPH_FCM (PWC_FCG0_FCM)
#define FCG0_PERIPH_AOS (PWC_FCG0_AOS)
#define FCG0_PERIPH_AES (PWC_FCG0_AES)
#define FCG0_PERIPH_HASH (PWC_FCG0_HASH)
#define FCG0_PERIPH_TRNG (PWC_FCG0_TRNG)
#define FCG0_PERIPH_CRC (PWC_FCG0_CRC)
#define FCG0_PERIPH_DCU1 (PWC_FCG0_DCU1)
#define FCG0_PERIPH_DCU2 (PWC_FCG0_DCU2)
#define FCG0_PERIPH_DCU3 (PWC_FCG0_DCU3)
#define FCG0_PERIPH_DCU4 (PWC_FCG0_DCU4)
#define FCG0_PERIPH_KEY (PWC_FCG0_KEY)
/**
* @}
*/
/**
* @defgroup FCG_FCG1_Peripheral FCG FCG1 peripheral
* @{
*/
#define FCG1_PERIPH_CAN (PWC_FCG1_CAN)
#define FCG1_PERIPH_QSPI (PWC_FCG1_QSPI)
#define FCG1_PERIPH_I2C1 (PWC_FCG1_I2C1)
#define FCG1_PERIPH_I2C2 (PWC_FCG1_I2C2)
#define FCG1_PERIPH_I2C3 (PWC_FCG1_I2C3)
#define FCG1_PERIPH_USBFS (PWC_FCG1_USBFS)
#define FCG1_PERIPH_SDIOC1 (PWC_FCG1_SDIOC1)
#define FCG1_PERIPH_SDIOC2 (PWC_FCG1_SDIOC2)
#define FCG1_PERIPH_I2S1 (PWC_FCG1_I2S1)
#define FCG1_PERIPH_I2S2 (PWC_FCG1_I2S2)
#define FCG1_PERIPH_I2S3 (PWC_FCG1_I2S3)
#define FCG1_PERIPH_I2S4 (PWC_FCG1_I2S4)
#define FCG1_PERIPH_SPI1 (PWC_FCG1_SPI1)
#define FCG1_PERIPH_SPI2 (PWC_FCG1_SPI2)
#define FCG1_PERIPH_SPI3 (PWC_FCG1_SPI3)
#define FCG1_PERIPH_SPI4 (PWC_FCG1_SPI4)
#define FCG1_PERIPH_USART1 (PWC_FCG1_USART1)
#define FCG1_PERIPH_USART2 (PWC_FCG1_USART2)
#define FCG1_PERIPH_USART3 (PWC_FCG1_USART3)
#define FCG1_PERIPH_USART4 (PWC_FCG1_USART4)
/**
* @}
*/
/**
* @defgroup FCG_FCG2_Peripheral FCG FCG2 peripheral
* @{
*/
#define FCG2_PERIPH_TMR0_1 (PWC_FCG2_TIMER0_1)
#define FCG2_PERIPH_TMR0_2 (PWC_FCG2_TIMER0_2)
#define FCG2_PERIPH_TMRA_1 (PWC_FCG2_TIMERA_1)
#define FCG2_PERIPH_TMRA_2 (PWC_FCG2_TIMERA_2)
#define FCG2_PERIPH_TMRA_3 (PWC_FCG2_TIMERA_3)
#define FCG2_PERIPH_TMRA_4 (PWC_FCG2_TIMERA_4)
#define FCG2_PERIPH_TMRA_5 (PWC_FCG2_TIMERA_5)
#define FCG2_PERIPH_TMRA_6 (PWC_FCG2_TIMERA_6)
#define FCG2_PERIPH_TMR4_1 (PWC_FCG2_TIMER4_1)
#define FCG2_PERIPH_TMR4_2 (PWC_FCG2_TIMER4_2)
#define FCG2_PERIPH_TMR4_3 (PWC_FCG2_TIMER4_3)
#define FCG2_PERIPH_EMB (PWC_FCG2_EMB)
#define FCG2_PERIPH_TMR6_1 (PWC_FCG2_TIMER6_1)
#define FCG2_PERIPH_TMR6_2 (PWC_FCG2_TIMER6_2)
#define FCG2_PERIPH_TMR6_3 (PWC_FCG2_TIMER6_3)
/**
* @}
*/
/**
* @defgroup FCG_FCG3_Peripheral FCG FCG3 peripheral
* @{
*/
#define FCG3_PERIPH_ADC1 (PWC_FCG3_ADC1)
#define FCG3_PERIPH_ADC2 (PWC_FCG3_ADC2)
#define FCG3_PERIPH_CMP (PWC_FCG3_CMP)
#define FCG3_PERIPH_OTS (PWC_FCG3_OTS)
/**
* @}
*/
/**
* @defgroup FCG_FCGx_Peripheral_Mask FCG FCGx Peripheral Mask
* @{
*/
#define FCG_FCG0_PERIPH_MASK (0x8FF3C511UL)
#define FCG_FCG1_PERIPH_MASK (0x0F0FFD79UL)
#define FCG_FCG2_PERIPH_MASK (0x000787FFUL)
#define FCG_FCG3_PERIPH_MASK (0x00001103UL)
/**
* @}
*/
/**
* @}
*/
用法示例:
c展开代码/* 1. Enable ADC1 peripheral clock. */
FCG_Fcg3PeriphClockCmd(FCG3_PERIPH_ADC1, ENABLE);
XHCode生成代码的BUG
在XHCode V1.10.0中,为HC32F334生成的配置HRPWM的代码中开启外设时钟函数的位置有问题。
具体情况为:在开启HRPWM时钟前就对微边沿定位的步长进行校准,最后会导致代码卡在校准超时失败的函数中。
解决方法:将下面红框中的函数提前到校准函数之前

本文作者:zxcli
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!