- /**
- * [url=home.php?mod=space&uid=247401]@brief[/url] enable or disable the crm clock source
- * @param source
- * this parameter can be one of the following values:
- * - CRM_CLOCK_SOURCE_HICK
- * - CRM_CLOCK_SOURCE_HEXT
- * - CRM_CLOCK_SOURCE_PLL
- * - CRM_CLOCK_SOURCE_LEXT
- * - CRM_CLOCK_SOURCE_LICK
- * @param new_state (TRUE or FALSE)
- * @retval none
- */
- void crm_clock_source_enable(crm_clock_source_type source, confirm_state new_state)
- {
- switch(source)
- {
- case CRM_CLOCK_SOURCE_HICK:
- CRM->ctrl_bit.hicken = new_state;
- break;
- case CRM_CLOCK_SOURCE_HEXT:
- CRM->ctrl_bit.hexten = new_state;
- break;
- case CRM_CLOCK_SOURCE_PLL:
- CRM->ctrl_bit.pllen = new_state;
- break;
- case CRM_CLOCK_SOURCE_LEXT:
- {
- if(new_state == TRUE)
- {
- CRM->bpdc |= 0x19;
- }
- else
- {
- CRM->bpdc_bit.lexten = FALSE;
- }
- break;
- }
- case CRM_CLOCK_SOURCE_LICK:
- CRM->ctrlsts_bit.licken = new_state;
- break;
- default:
- break;
- }
- }
当然在选择时钟源为PLL时,必须选配置前面的分频等,在at32l021_crm.c中有库函数crm_pll_config来配置倍频值 ,他的函数原型如下:
- /**
- * @brief config crm pll
- * @param clock_source
- * this parameter can be one of the following values:
- * - CRM_PLL_SOURCE_HICK
- * - CRM_PLL_SOURCE_HEXT
- * - CRM_PLL_SOURCE_HEXT_DIV
- * @param mult_value (CRM_PLL_MULT_2~64)
- * @retval none
- */
- void crm_pll_config(crm_pll_clock_source_type clock_source, crm_pll_mult_type mult_value)
- {
- uint32_t pllrcfreq = 0;
- crm_pll_fref_type pllfref = CRM_PLL_FREF_4M;
- /* config pll clock source */
- if(clock_source == CRM_PLL_SOURCE_HICK)
- {
- CRM->cfg_bit.pllrcs = FALSE;
- pllrcfreq = (HICK_VALUE / 2);
- }
- else
- {
- CRM->cfg_bit.pllrcs = TRUE;
- if(CRM_PLL_SOURCE_HEXT == clock_source)
- {
- pllrcfreq = HEXT_VALUE;
- CRM->cfg_bit.pllhextdiv = FALSE;
- }
- else
- {
- pllrcfreq = (HEXT_VALUE / 2);
- CRM->cfg_bit.pllhextdiv = TRUE;
- }
- }
- if((pllrcfreq > 3900000U) && (pllrcfreq < 5000000U))
- {
- pllfref = CRM_PLL_FREF_4M;
- }
- else if((pllrcfreq > 5200000U) && (pllrcfreq < 6250000U))
- {
- pllfref = CRM_PLL_FREF_6M;
- }
- else if((pllrcfreq > 7812500U) && (pllrcfreq < 8330000U))
- {
- pllfref = CRM_PLL_FREF_8M;
- }
- else if((pllrcfreq > 8330000U) && (pllrcfreq < 12500000U))
- {
- pllfref = CRM_PLL_FREF_12M;
- }
- else if((pllrcfreq > 15625000U) && (pllrcfreq < 20830000U))
- {
- pllfref = CRM_PLL_FREF_16M;
- }
- else if((pllrcfreq > 20830000U) && (pllrcfreq < 31255000U))
- {
- pllfref = CRM_PLL_FREF_25M;
- }
- /* config pll multiplication factor */
- CRM->cfg_bit.pllmult_l = (mult_value & 0x0F);
- CRM->cfg_bit.pllmult_h = ((mult_value & 0x30) >> 4);
- /* config pll fref */
- CRM->pll_bit.pllfref = pllfref;
- }
在固件库使用说明中的5.3.22有详细的参数与配置说明:
当然如果需要复杂的配置,系统还提供了crm_pll_config2来做更加复杂的配置
【总线时钟的配置】
在配置好SCLK后,配置如AHB、APB1、APB2就相对于简单许多了,在AT32 Work Bench中可以非常直观的看到,如下图所示。
在1中是对AHB总线进行分频,2是对APB1进行分频,3是对APB2进行分频,当然还可以对相关的ADC、TMR等进行更细的分频。
在AT32L021数据手册中,对AHB、APB1、APB2的配置分别是CRM_CFG的AHBDIV、APB1DIV、APB2DIV以及ADCDIV,如下图所示,详细的讲明了寄存器的分频因子:
在at32l021_crm.c中有如下三个函数是来设置他们的分频因子的:
- /**
- * @brief set crm ahb division
- * @param value
- * this parameter can be one of the following values:
- * - CRM_AHB_DIV_1
- * - CRM_AHB_DIV_2
- * - CRM_AHB_DIV_4
- * - CRM_AHB_DIV_8
- * - CRM_AHB_DIV_16
- * - CRM_AHB_DIV_64
- * - CRM_AHB_DIV_128
- * - CRM_AHB_DIV_256
- * - CRM_AHB_DIV_512
- * @retval none
- */
- void crm_ahb_div_set(crm_ahb_div_type value)
- {
- CRM->cfg_bit.ahbdiv = value;
- }
- /**
- * @brief set crm apb1 division
- * [url=home.php?mod=space&uid=536309]@NOTE[/url] the maximum frequency of APB1/APB2 clock is 96 MHz
- * @param value
- * this parameter can be one of the following values:
- * - CRM_APB1_DIV_1
- * - CRM_APB1_DIV_2
- * - CRM_APB1_DIV_4
- * - CRM_APB1_DIV_8
- * - CRM_APB1_DIV_16
- * @retval none
- */
- void crm_apb1_div_set(crm_apb1_div_type value)
- {
- CRM->cfg_bit.apb1div = value;
- }
- /**
- * @brief set crm apb2 division
- * @note the maximum frequency of APB1/APB2 clock is 96 MHz
- * @param value
- * this parameter can be one of the following values:
- * - CRM_APB2_DIV_1
- * - CRM_APB2_DIV_2
- * - CRM_APB2_DIV_4
- * - CRM_APB2_DIV_8
- * - CRM_APB2_DIV_16
- * @retval none
- */
- void crm_apb2_div_set(crm_apb2_div_type value)
- {
- CRM->cfg_bit.apb2div = value;
- }
- /**
- * @brief set crm adc division
- * @param value
- * this parameter can be one of the following values:
- * - CRM_ADC_DIV_2
- * - CRM_ADC_DIV_4
- * - CRM_ADC_DIV_6
- * - CRM_ADC_DIV_8
- * - CRM_ADC_DIV_3
- * - CRM_ADC_DIV_12
- * - CRM_ADC_DIV_5
- * - CRM_ADC_DIV_16
- * @retval none
- */
- void crm_adc_clock_div_set(crm_adc_div_type div_value)
- {
- CRM->cfg_bit.adcdiv_l = div_value & 0x03;
- CRM->cfg_bit.adcdiv_h = (div_value >> 2) & 0x01;
- }
【当前时钟频率的获取】
在at32l021_crm.c中有一个函数是获取片上时钟频率的函数,其函数原型如下:
- /**
- * @brief get crm clocks freqency
- * @param clocks
- * - pointer to the crm_clocks_freq structure
- * @retval none
- */
- void crm_clocks_freq_get(crm_clocks_freq_type *clocks_struct)
- {
- uint32_t pll_mult = 0, pll_mult_h = 0, pll_clock_source = 0, temp = 0, div_value = 0;
- uint32_t pllrcsfreq = 0, pll_ms = 0, pll_ns = 0, pll_fr = 0;
- crm_sclk_type sclk_source;
- static const uint8_t sclk_ahb_div_table[16] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 6, 7, 8, 9};
- static const uint8_t ahb_apb1_div_table[8] = {0, 0, 0, 0, 1, 2, 3, 4};
- static const uint8_t ahb_apb2_div_table[8] = {0, 0, 0, 0, 1, 2, 3, 4};
- static const uint8_t adc_div_table[8] = {2, 4, 6, 8, 2, 12, 8, 16};
- /* get sclk source */
- sclk_source = crm_sysclk_switch_status_get();
- switch(sclk_source)
- {
- case CRM_SCLK_HICK:
- if(CRM->misc2_bit.hick_to_sclk != RESET)
- clocks_struct->sclk_freq = HICK_VALUE * 6;
- else
- clocks_struct->sclk_freq = HICK_VALUE;
- break;
- case CRM_SCLK_HEXT:
- clocks_struct->sclk_freq = HEXT_VALUE;
- break;
- case CRM_SCLK_PLL:
- pll_clock_source = CRM->cfg_bit.pllrcs;
- if(CRM->pll_bit.pllcfgen == FALSE)
- {
- /* get multiplication factor */
- pll_mult = CRM->cfg_bit.pllmult_l;
- pll_mult_h = CRM->cfg_bit.pllmult_h;
- /* process high bits */
- if((pll_mult_h != 0U) || (pll_mult == 15U))
- {
- pll_mult += ((16U * pll_mult_h) + 1U);
- }
- else
- {
- pll_mult += 2U;
- }
- if (pll_clock_source == 0x00)
- {
- /* hick divided by 2 selected as pll clock entry */
- clocks_struct->sclk_freq = (HICK_VALUE >> 1) * pll_mult;
- }
- else
- {
- /* hext selected as pll clock entry */
- if (CRM->cfg_bit.pllhextdiv != RESET)
- {
- /* hext clock divided by 2 */
- clocks_struct->sclk_freq = (HEXT_VALUE / 2) * pll_mult;
- }
- else
- {
- clocks_struct->sclk_freq = HEXT_VALUE * pll_mult;
- }
- }
- }
- else
- {
- pll_ms = CRM->pll_bit.pllms;
- pll_ns = CRM->pll_bit.pllns;
- pll_fr = CRM->pll_bit.pllfr;
- if (pll_clock_source == 0x00)
- {
- /* hick divided by 2 selected as pll clock entry */
- pllrcsfreq = (HICK_VALUE >> 1);
- }
- else
- {
- /* hext selected as pll clock entry */
- if (CRM->cfg_bit.pllhextdiv != RESET)
- {
- /* hext clock divided by 2 */
- pllrcsfreq = (HEXT_VALUE / 2);
- }
- else
- {
- pllrcsfreq = HEXT_VALUE;
- }
- }
- clocks_struct->sclk_freq = (uint32_t)(((uint64_t)pllrcsfreq * pll_ns) / (pll_ms * (0x1 << pll_fr)));
- }
- break;
- default:
- clocks_struct->sclk_freq = HICK_VALUE;
- break;
- }
- /* compute sclk, ahbclk, abp1clk apb2clk and adcclk frequencies */
- /* get ahb division */
- temp = CRM->cfg_bit.ahbdiv;
- div_value = sclk_ahb_div_table[temp];
- /* ahbclk frequency */
- clocks_struct->ahb_freq = clocks_struct->sclk_freq >> div_value;
- /* get apb1 division */
- temp = CRM->cfg_bit.apb1div;
- div_value = ahb_apb1_div_table[temp];
- /* apb1clk frequency */
- clocks_struct->apb1_freq = clocks_struct->ahb_freq >> div_value;
- /* get apb2 division */
- temp = CRM->cfg_bit.apb2div;
- div_value = ahb_apb2_div_table[temp];
- /* apb2clk frequency */
- clocks_struct->apb2_freq = clocks_struct->ahb_freq >> div_value;
- /* get adc division */
- temp = CRM->cfg_bit.adcdiv_h;
- temp = ((temp << 2) | (CRM->cfg_bit.adcdiv_l));
- div_value = adc_div_table[temp];
- /* adcclk clock frequency */
- clocks_struct->adc_freq = clocks_struct->apb2_freq / div_value;
- }
其参数的输入与输出,在BSP手册中的5.3.26中有说明,如下图所示:
经过以上数据手册以及寄存器的了解,再结合官方生成的代码,那么我们如何在运行时修改时钟源以及时钟运行频率,在官方的示例中有一个示例为sclk_switch,他的有两个切换函数,我们结合两个函数来理一理,如果切换时钟。
【时钟切换】
时钟切换需要按以下步骤进行:
1、将时钟复位管理模块的寄存器和控制状态复位,函数为crm_rest。
2、修改闪存访问等周期,在FLASH_PSR寄存器中有介绍,分为三个档位,他的设置如下:
在我们在设计预期时钟时要根据以32\64\这两个界限来设定,时钟越高,设置等待延时要越多。他的设置函数为flash_psr_set;
3、使能电源控制(PWC)外设时钟。
4、配置电源LDO 输出电压设定值,即为电压监测临界值的选择
5、接下来使能我们需要的时钟源
6、等待内部(外部)时钟源稳定标位CRM_HICK_STABLE_FLAG。
7、配置pll的分频因子
8、使能pll时钟选择
9、等待配置的时钟源稳定。
10、对AHB、APB1、APB2,有必需要再配置ADC或者TMR等分频因子。
11,选择预期的时钟源
12、待待选择的时钟源稳定。
13、重置系统时钟的变量
在示例中函数配置如下:
- /**
- * @brief config sclk 64 mhz with hick clock source.
- * @note the system clock is configured as follow:
- * system clock (sclk) = hick / 2 * pll_mult
- * system clock source = pll (hick)
- * - hick = HICK_VALUE
- * - sclk = 64000000
- * - ahbdiv = 1
- * - ahbclk = 64000000
- * - apb2div = 1
- * - apb2clk = 64000000
- * - apb1div = 1
- * - apb1clk = 64000000
- * - pll_mult = 16
- * - flash_wtcyc = 1 cycle
- * @param none
- * @retval none
- */
- static void sclk_64m_hick_config(void)
- {
- /* reset crm */
- crm_reset();
- /* config flash psr register */
- flash_psr_set(FLASH_WAIT_CYCLE_1);
- /* enable pwc periph clock */
- crm_periph_clock_enable(CRM_PWC_PERIPH_CLOCK, TRUE);
- /* config ldo voltage */
- pwc_ldo_output_voltage_set(PWC_LDO_OUTPUT_1V2);
- crm_clock_source_enable(CRM_CLOCK_SOURCE_HEXT, TRUE);
- crm_clock_source_enable(CRM_CLOCK_SOURCE_HICK, TRUE);
- /* wait till hick is ready */
- while(crm_flag_get(CRM_HICK_STABLE_FLAG) != SET)
- {
- }
- /* config pll clock resource */
- crm_pll_config(CRM_PLL_SOURCE_HICK, CRM_PLL_MULT_16);
- /* enable pll */
- crm_clock_source_enable(CRM_CLOCK_SOURCE_PLL, TRUE);
- /* wait till pll is ready */
- while(crm_flag_get(CRM_PLL_STABLE_FLAG) != SET)
- {
- }
- /* config ahbclk */
- crm_ahb_div_set(CRM_AHB_DIV_1);
- /* config apb2clk, the maximum frequency of APB1/APB2 clock is 80 MHz */
- crm_apb2_div_set(CRM_APB2_DIV_1);
- /* config apb1clk, the maximum frequency of APB1/APB2 clock is 80 MHz */
- crm_apb1_div_set(CRM_APB1_DIV_1);
- /* select pll as system clock source */
- crm_sysclk_switch(CRM_SCLK_PLL);
- /* wait till pll is used as system clock source */
- while(crm_sysclk_switch_status_get() != CRM_SCLK_PLL)
- {
- }
- /* update system_core_clock global variable */
- system_core_clock_update();
- /* config systick delay */
- delay_init();
- /* config clkout */
- clkout_config();
-
- }
【当前时钟状态的打印】
为了更好的了解时钟当前频率,我写了一个时钟打印的函数,使用crm_clocks_freq_type,获取到当前系统运行的时钟:
- /*
- *打印时钟频率
- */
- void clk_print(void)
- {
- crm_clocks_freq_type get_clocks_struct;
-
- crm_clocks_freq_get(&get_clocks_struct);
- printf("\r\n--------------------\r\n");
- printf("SCLK_FREQ:%dMHz\r\n",(get_clocks_struct.sclk_freq)/(1000*1000));
- printf("AHB_FREQ: %dMHz\r\n", get_clocks_struct.ahb_freq/(1000*1000));
- printf("APB2_FREQ:%dMHz\r\n", get_clocks_struct.apb2_freq/(1000*1000));
- printf("APB1_FREQ:%dMHz\r\n", get_clocks_struct.apb1_freq/(1000*1000));
- printf("ADC_FREQ:%dMHz\r\n", get_clocks_struct.adc_freq/(1000*1000));
- printf("--------------------\r\n");
- }
在示例中,他还配置了当前时钟进行4分频后输出到PA8上,方便用示波器来观察实际的运行
- /**
- * @brief clkout configuration.
- * @param none
- * @retval none
- */
- void clkout_config(void)
- {
- gpio_init_type gpio_init_struct;
- /* enable gpio port clock */
- crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE);
- /* set default parameter */
- gpio_default_para_init(&gpio_init_struct);
- /* clkout gpio init */
- gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
- gpio_init_struct.gpio_mode = GPIO_MODE_MUX;
- gpio_init_struct.gpio_pins = GPIO_PINS_8;
- gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
- gpio_init(GPIOA, &gpio_init_struct);
- gpio_pin_mux_config(GPIOA, GPIO_PINS_SOURCE8, GPIO_MUX_0);
- /* config clkout division */
- crm_clkout_div_set(CRM_CLKOUT_DIV_1);
- /* config clkout clock */
- crm_clock_out_set(CRM_CLKOUT_PLL_DIV_4);
- }
【实验现象】
编译并下载到开发板后,从串口助手可以了解到系统当前的运行状态。当按下按键后,系修改了时钟频率,再按一次用户按键,又打印出当前的状态:
示波器观查,64MHz经4分频后的波形:
经4分频后,实际运行的频率为16.1MHz稍有出入。
观察80MHz时(外部时钟作为时钟源)
经4分频后,实际为20MHz而且非常稳定。
经对比,如果需要高精的时钟,还是需要使用外部晶振来做时基比较好。
【超频测试】
在用户手册中,注明了最高频率为80MHz,我下面对PLL进行修改以达到更高的频率,查看系统时否能稳定运行。
1、修改HSE的PLL为12,系统可以稳定的运行在96MHZ:
2、修改HSE的PLL为14,系统可以稳定的运行在112MHZ:
3、修改为16,系统可以稳定的运行在128MHZ:
4、修改为18,系统可以稳定的运行在144MHz:
修改到19倍频时,转换过去就发生错误了。
【超频小结】
这颗M0+的性能还是非常好的,可以超频到144MHz还是可以稳定的运行的。
【时钟配置总结】
经过我详细阅读数据手册、BSP库使用手册以及相关的例程,对AT32L021的时钟相关寄存器进行分析,结合示例等进行详细的解读,同时通过对示例的修改,成功的完成了时钟源的转换,不同时钟频率的设置,最后对这颗MCU进行了超频试验。全面的掌握了AT32L021的时钟设置,为以后的工程设计打下了坚实的基础。