【前言】
AT32L021的最大运行时钟可以运行在80MHz,最低可以运行在4MHz。主频运行时钟,是动态控制MCU运行功耗的一个工具,通过他的时钟树,我们可以通过选择不同的时钟源以及修改不同的分频系数,就可以设置MCU的运行频率以及AHB、APB的不同时钟,来达到动态功耗的控制效果。本篇旨在学习配置不同的时钟,同时通过printf孙子函数输出当前系统的运行时钟,同时还进行超频测试来试验总线最大可运行时钟频率。
【时钟树的简介以及配置】
1、AT32L021系列微控制器系统架构如下图所示
时钟通过HEXT、HICKI、PLL进入CRM,然后进入AHB总线,APB1与APB2挂载到AHB总线上,这三个总线都可以最大运行在80MHz中。
2、AT32L021时钟结构图如下:
从图中,我们可以看到可以有多种时钟配置到达SCLK中,结合Work Bench时钟配置,我们可以更加清晰的看到AT32L021的几条线路:
在《AT32L021系列 技术手册》的第4章,时钟和复位管理中,总线时钟可以有两种,一种为内部时钟源HICK,另一个为外部时钟源HEXT。通过不同的线路,我们可以得知有以下几条线路可以配置SCLK的时钟:
我们通过sclk_select三个选择,可以选定从Hext、hick、以及PLL作为输入源,在寄存器CRM_CFG的SCLKSEL系统选择中可以选择时来源,在手册中有如下所述:
当然我也们可以查询SCLKSTS,当前所选的状态位。
在at32l021_crm.c中有库函数crm_clock_source_enable来选择时钟源
/**
* [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的时钟设置,为以后的工程设计打下了坚实的基础。
|