打印
[AT32L021]

【AT-START-L021测评】时钟配置以及超频试验

[复制链接]
79|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
lulugl|  楼主 | 2024-11-25 22:54 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
【前言】
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的时钟设置,为以后的工程设计打下了坚实的基础。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

149

主题

721

帖子

9

粉丝