历史上的今天
返回首页

历史上的今天

今天是:2025年02月12日(星期三)

正在发生

2019年02月12日 | 【stm32f407】时钟树以及SystemInit剖析

2019-02-12 来源:eefocus

一. 时钟树


众所周知,时钟系统是CPU的脉搏,就像人的心跳一样。所以时钟系统的重要性就不言而喻了。 STM32F4的时钟系统比较复杂,不像简单的51单片机一个系统时钟就可以解决一切。于是有人要问,采用一个系统时钟不是很简单吗?为什么STM32要有多个时钟源呢?因为首先STM32本身非常复杂,外设非常的多,但是并不是所有外设都需要系统时钟这么高的频率,比如看门狗以及RTC只需要几十k的时钟即可。同一个电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱,所以对于较为复杂的MCU一般都是采取多时钟源的方法来解决这些问题。


首先让我们来看看STM32F4的时钟系统图



在STM32F4中,有5个最重要的时钟源,为HSI、HSE、LSI、LSE、PLL。其中PLL实际是分为两个时钟源,分别为主PLL和专用PLL。从时钟频率来分可以分为高速时钟源和低速时钟源,在这5个中HSI,HSE以及PLL是高速时钟,LSI和LSE是低速时钟。从来源可分为外部时钟源和内部时钟源,外部时钟源就是从外部通过接晶振的方式获取时钟源,其中HSE和LSE是外部时钟源,其他的是内部时钟源。下面我们看看STM32F4的这5个时钟源,我们讲解顺序是按图中红圈标示的顺序


①、LSI是低速内部时钟,RC振荡器,频率为32kHz左右。供独立看门狗和自动唤醒单元使用。


②、LSE是低速外部时钟,接频率为32.768kHz的石英晶体。这个主要是RTC的时钟源。


③、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~26MHz。HSE也可以直接做为系统时钟或者PLL输入。


④、HSI是高速内部时钟,RC振荡器,频率为16MHz。可以直接作为系统时钟或者用作PLL输入。


⑤、PLL为锁相环倍频输出。STM32F4有两个PLL:


1) 主PLL(PLL)由HSE或者HSI提供时钟信号,并具有两个不同的输出时钟。


第一个输出PLLP用于生成高速的系统时钟(最高168MHz)


第二个输出PLLQ用于生成USB OTG FS的时钟(48MHz),随机数发生器的时钟和SDIO时钟。


2)专用PLL(PLLI2S)用于生成精确时钟,从而在I2S接口实现高品质音频性能。


这里我们着重看看主PLL时钟第一个高速时钟输出PLLP的计算方法。如图:



主PLL时钟的时钟源要先经过一个分频系数为M的分频器,然后经过倍频系数为N的倍频器出来之后的时候还需要经过一个分频系数为P(第一个输出PLLP)或者Q(第二个输出PLLQ)的分频器分频之后,最后才生成最终的主PLL时钟。


例如我们的外部晶振选择8MHz。同时我们设置相应的分频器M=8,倍频器倍频系数N=336,分频器分频系数P=2,那么主PLL生成的第一个输出高速时钟PLLP为:


PLL=8MHz* N/ (M*P)=8MHz* 336 /(8*2) = 168MHz


如果我们选择HSE为PLL时钟源,同时SYSCLK时钟源为PLL,那么SYSCLK时钟


为168MHz。这对于我们后面的实验都是采用这样的配置


上面我们简要概括了STM32的时钟源,那么这5个时钟源是怎么给各个外设以及系统提供时钟的呢?这里我们选择一些比较常用的时钟知识来讲解。


图1中我们用A~G标示我们要讲解的地方。


A.  这里是看门狗时钟输入。从图中可以看出,看门狗时钟源只能是低速的LSI时钟。


B.  这里是RTC时钟源,从图上可以看出,RTC的时钟源可以选择LSI,LSE,以及


HSE分频后的时钟,HSE分频系数为2~31。


C.  这里是STM32F4输出时钟MCO1和MCO2。MCO1是向芯片的PA8引脚输出时钟。它有四个时钟来源分别为:HSI,LSE,HSE和PLL时钟。MCO2是向芯片的PC9输出时钟,它同样有四个时钟来源分别为:HSE,PLL,SYSCLK以及PLLI2S时钟。MCO输出时钟频率最大不超过100MHz。


D.  这里是系统时钟。SYSCLK系统时钟来源有三个方面:HSI,HSE和PLL。在我们实际应用中,因为对时钟速度要求都比较高我们才会选用STM32F4这种级别的处理器,所以一般情况下,都是采用PLL作为SYSCLK时钟源。根据前面的计算公式,大家就可以算出你的系统的SYSCLK是多少。


E.  这里我们指的是以太网PTP时钟,AHB时钟,APB2高速时钟,APB1低速时钟。这些时钟都是来源于SYSCLK系统时钟。其中以太网PTP时钟是使用系统时钟。AHB,APB2和APB1时钟是经过SYSCLK时钟分频得来。这里大家记住,AHB最大时钟为168MHz, APB2高速时钟最大频率为84MHz,而APB1低速时钟最大频率为42MHz。


F.  这里是指I2S时钟源。I2S的时钟源来源于PLLI2S或者映射到I2S_CKIN引脚的外部时钟。I2S出于音质的考虑,对时钟精度要求很高。STM32F4开发板使用的是内部PLLI2SCLK。


G.  这是STM32F4内部以太网MAC时钟的来源。对于MII接口来说,必须向外部PHY芯片提供25Mhz的时钟,这个时钟,可以由PHY芯片外接晶振,或者使用STM32F4 的MCO输出来提供。然后,PHY 芯片再给STM32F4提供ETH_MII_TX_CLK和ETH_MII_RX_CLK时钟。对于RMII接口来说,外部必须提供50Mhz的时钟驱动PHY和STM32F4的ETH_RMII_REF_CLK,这个50Mhz时钟可以来自PHY、有源晶振或者STM32F4的MCO。我们的开发板使用的是RMII 接口,使用PHY 芯片提供50Mhz时钟驱动STM32F4 的


ETH_RMII_REF_CLK。


H.  这里是指外部PHY提供的USB OTG HS(60MHZ)时钟。


二. STM32F4时钟初始化配置

STM32F4时钟系统初始化是在system_stm32f4xx.c中的SystemInit()函数中完成的。对于系


统时钟关键寄存器设置主要是在SystemInit函数中调用SetSysClock()函数来设置的。我们可以先看看SystemInit ()函数体:


 void SystemInit(void)

{

  /* FPU settings ------------------------------------------------------------*/

  #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)

    SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));  /* set CP10 and CP11 Full Access */

  #endif

  /* Reset the RCC clock configuration to the default reset state ------------*/

  /* Set HSION bit */

  RCC->CR |= (uint32_t)0x00000001;

 

  /* Reset CFGR register */

  RCC->CFGR = 0x00000000;

 

  /* Reset HSEON, CSSON and PLLON bits */

  RCC->CR &= (uint32_t)0xFEF6FFFF;

 

  /* Reset PLLCFGR register */

  RCC->PLLCFGR = 0x24003010;

 

  /* Reset HSEBYP bit */

  RCC->CR &= (uint32_t)0xFFFBFFFF;

 

  /* Disable all interrupts */

  RCC->CIR = 0x00000000;

 

#ifdef DATA_IN_ExtSRAM

  SystemInit_ExtMemCtl(); 

#endif /* DATA_IN_ExtSRAM */

         

  /* Configure the System clock source, PLL Multiplier and Divider factors, 

     AHB/APBx prescalers and Flash settings ----------------------------------*/

  SetSysClock();

 

  /* Configure the Vector Table location add offset address ------------------*/

#ifdef VECT_TAB_SRAM

  SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */

#else

  SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */

#endif

}

SystemInit函数开始先进行浮点运算单元设置,然后是复位PLLCFGR,CFGR寄存器,同时


通过设置CR寄存器的HSI时钟使能位来打开HSI时钟。此代码就是RCC->CR |=(uint32_t)0x00000001;打开HSI振荡器

默认情况下如果CFGR寄存器复位,那么是选择HSI作为系统时钟,这点大家可以查看RCC->CFGR寄存器的位描述最低2位可以得知,当低两位配置为00的时候(复位之后),会选择HSI振荡器为系统时钟。也就是说,调用SystemInit函数之后,首先是选择HSI作为系统时钟。下面是RCC->CFGR寄存器的位1:0配置描述(CFGR寄存器详细描述请参考《STM32F4中文参考手册》6.3.31CFGR寄存器配置表)


如图:

在设置完相关寄存器后,接下来SystemInit函数内部会调用SetSysClock函数。这个函数比


较长,我们就把函数一些关键代码行截取出来给大家讲解一下。这里我们省略一些宏定义标识符值的判断而直接把针对STM32F407比较重要的内容贴出来:



/**

  * @brief  Configures the System clock source, PLL Multiplier and Divider factors, 

  *         AHB/APBx prescalers and Flash settings

  * @Note   This function should be called only once the RCC clock configuration  

  *         is reset to the default reset state (done in SystemInit() function).   

  * @param  None

  * @retval None

  */

static void SetSysClock(void)

{

/******************************************************************************/

/*            PLL (clocked by HSE) used as System clock source                */

/******************************************************************************/

  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;

  

  /* Enable HSE */

  RCC->CR |= ((uint32_t)RCC_CR_HSEON);

 

  /* Wait till HSE is ready and if Time out is reached exit */

  do

  {

    HSEStatus = RCC->CR & RCC_CR_HSERDY;

    StartUpCounter++;

  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

 

  if ((RCC->CR & RCC_CR_HSERDY) != RESET)

  {

    HSEStatus = (uint32_t)0x01;

  }

  else

  {

    HSEStatus = (uint32_t)0x00;

  }

 

  if (HSEStatus == (uint32_t)0x01)

  {

    /* Select regulator voltage output Scale 1 mode, System frequency up to 168 MHz */

    RCC->APB1ENR |= RCC_APB1ENR_PWREN;

    PWR->CR |= PWR_CR_VOS;

 

    /* HCLK = SYSCLK / 1*/

    RCC->CFGR |= RCC_CFGR_HPRE_DIV1;

      

    /* PCLK2 = HCLK / 2*/

    RCC->CFGR |= RCC_CFGR_PPRE2_DIV2;

    

    /* PCLK1 = HCLK / 4*/

    RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;

 

    /* Configure the main PLL */

    RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) |

                   (RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24);

 

    /* Enable the main PLL */

    RCC->CR |= RCC_CR_PLLON;

 

    /* Wait till the main PLL is ready */

    while((RCC->CR & RCC_CR_PLLRDY) == 0)

    {

    }

   

    /* Configure Flash prefetch, Instruction cache, Data cache and wait state */

    FLASH->ACR = FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS;

 

    /* Select the main PLL as system clock source */

    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));

    RCC->CFGR |= RCC_CFGR_SW_PLL;

 

    /* Wait till the main PLL is used as system clock source */

    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL);

    {

    }

  }

  else

  { /* If HSE fails to start-up, the application will have wrong clock

         configuration. User can add here some code to deal with this error */

  }

 

 

/******************************************************************************/

/*                          I2S clock configuration                           */

/******************************************************************************/

  /* PLLI2S clock used as I2S clock source */

  RCC->CFGR &= ~RCC_CFGR_I2SSRC;

 

  /* Configure PLLI2S */

  RCC->PLLI2SCFGR = (PLLI2S_N << 6) | (PLLI2S_R << 28);

 

  /* Enable PLLI2S */

  RCC->CR |= ((uint32_t)RCC_CR_PLLI2SON);

 

  /* Wait till PLLI2S is ready */

  while((RCC->CR & RCC_CR_PLLI2SRDY) == 0)

  {

  }

}

RCC->CR |= ((uint32_t)RCC_CR_HSEON);


此宏RCC_CR_HSEON定义在stm32f4xx.h中,


#define  RCC_CR_HSEON                        ((uint32_t)0x00010000)


此宏是在第16bit置1

此段代码的意思是:把外部高速时钟打开


do


  {


   HSEStatus = RCC->CR & RCC_CR_HSERDY;


   StartUpCounter++;


  }while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));


#define RCC_CR_HSERDY                      ((uint32_t)0x00020000)


此段代码的意思是:在一个时间内看HSE是否就绪


RCC->APB1ENR |= RCC_APB1ENR_PWREN;


PWR->CR |= PWR_CR_VOS;


#define RCC_APB1ENR_PWREN                  ((uint32_t)0x10000000)


#define PWR_CR_VOS                         ((uint16_t)0x4000)



此段代码的意思就是:使能电源时钟


RCC->CFGR |= RCC_CFGR_HPRE_DIV1;


#define RCC_CFGR_HPRE_DIV1                 ((uint32_t)0x00000000)


此段代码的意思是:不进行分频


RCC->CFGR |= RCC_CFGR_PPRE2_DIV2;


#define RCC_CFGR_PPRE2_DIV2                ((uint32_t)0x00008000)



此段代码的意思是:对AHB时钟进行2分频,所以APB2 = AHB/2


RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;


#define RCC_CFGR_PPRE1_DIV4                ((uint32_t)0x00001400)


此段代码的意思是:对AHB时钟进行4分频,所以APB1 = AHB/4


RCC->PLLCFGR = PLL_M | (PLL_N <


                   (RCC_PLLCFGR_PLLSRC_HSE) |(PLL_Q << 24);


#define PLL_M      8


#define PLL_N      336


#define PLL_P      2


#define PLL_Q      7


#define RCC_PLLCFGR_PLLSRC_HSE             ((uint32_t)0x00400000)


所以我们的主PLL时钟为:


PLL=8MHz * N/ (M*P)=8MHz* 336 /(8*2) =168MHz


在开发过程中,我们可以通过调整这些值来设置我们的系


RCC->CR |= RCC_CR_PLLON;


#define RCC_CR_PLLON                       ((uint32_t)0x01000000)


所以此段代码的意思是:把PLL开启


while((RCC->CR & RCC_CR_PLLRDY) ==0)


{}


#define  RCC_CR_PLLRDY                       ((uint32_t)0x02000000)


所以此段代码的意思是:等待PLL就绪


 


RCC->CFGR&= (uint32_t)((uint32_t)~(RCC_CFGR_SW));


RCC->CFGR |=RCC_CFGR_SW_PLL;


#define  RCC_CFGR_SW                         ((uint32_t)0x00000003)


#define  RCC_CFGR_SW_PLL                    ((uint32_t)0x00000002)  


以上是选择PLL作为系统时钟源


while((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL);


    {


    }


同以上:等待就绪


I2S时钟配置


RCC->CFGR&= ~RCC_CFGR_I2SSRC;


#define  RCC_CFGR_I2SSRC                     ((uint32_t)0x00800000)


PLLI2S作为I2S时钟源


RCC->PLLI2SCFGR= (PLLI2S_N << 6) | (PLLI2S_R << 28);


#definePLLI2S_N   258


#definePLLI2S_R   3


以上代码的意思是:配置PLLI2S


RCC->CR |=((uint32_t)RCC_CR_PLLI2SON);


  while((RCC->CR & RCC_CR_PLLI2SRDY) ==0)


  {


  }


#define  RCC_CR_PLLI2SON                     ((uint32_t)0x04000000)


#define  RCC_CR_PLLI2SRDY                    ((uint32_t)0x08000000)


以上代码的意思是:使能PLLI2S,并且等待就绪


另外在开发过程中,我们可以通过调整这些值来设置我们的系统时钟。


这里还有个特别需要注意的地方,就是我们还要同步修改stm32f4xx.h 中宏定义标识符


HSE_VALUE的值为我们的外部时钟:


#if !defined(HSE_VALUE)


#defineHSE_VALUE ((uint32_t)8000000) /*!< Value of the External oscillator in Hz */


#endif /*HSE_VALUE */


这里默认固件库配置的是25000000,我们外部时钟为8MHz,所以我们根据我们硬件情况修改为8000000即可


三. 时钟配置工具

ST公司有个配置时钟的工具,如图所示:

配置起来很方便


推荐阅读

史海拾趣

Autotrol公司的发展小趣事

Autotrol公司成立于1964年,最初是一家领先的可定制小功率齿轮马达生产商。在初创时期,公司面临着激烈的市场竞争和技术挑战。然而,Autotrol凭借其卓越的技术实力和创新能力,成功开发出一系列具有竞争力的齿轮马达产品,逐渐在市场中脱颖而出。公司不断完善产品线,推出了永久磁铁同步电动机、滞后电动机和直流齿轮马达等,这些产品以其高效、稳定的性能赢得了客户的信赖。

ELDECO公司的发展小趣事

面对全球电子记录仪市场的快速增长,ELDECO公司制定了国际化发展战略。公司首先在欧洲市场设立了研发中心和生产基地,通过与当地合作伙伴的紧密合作,成功将产品打入欧洲市场。随后,公司又在美国、亚洲等地设立了分支机构,进一步拓展全球市场。通过国际化战略布局,ELDECO公司不仅提高了品牌影响力,还获得了更多的市场机会和客户资源。

ABL Aluminum Components公司的发展小趣事

随着技术的成熟,ABL公司开始积极拓展市场。公司通过与大型电子产品制造商建立合作关系,将其铝合金组件应用于更广泛的领域。同时,ABL公司也注重品牌建设,通过参加行业展会、发布技术白皮书等方式,提升公司在业内的知名度和影响力。随着市场份额的逐步扩大,ABL公司逐渐成为了电子行业铝合金组件领域的领军企业。

迦美信芯(CanaanTek)公司的发展小趣事

在导航芯片领域,迦美信芯也取得了显著成就。由公司董事长兼CTO倪文海主导开发的兼容“GPS+北斗导航”的射频芯片,被国内主要基带厂商广泛采用,占据了北斗细分市场60%的份额。这一成就不仅彰显了迦美信芯在导航芯片领域的强大实力,也为其在物联网和汽车电子等领域的应用奠定了坚实基础。

Graseby Infrared公司的发展小趣事
首先确认三相电源是否正常,可以使用万用表测量电源电压和相序。
台湾美丽微(FMS)公司的发展小趣事

近年来,FTDI通过战略并购进一步拓展了其业务领域。例如,电连技术通过发行股份及支付现金的方式收购了FTDI的控股权,这一举措不仅使电连技术在产品和客户层面实现了更完整的布局,也增强了其在汽车电子、物联网、工业产品、医疗设备等多个领域的市场竞争力。FTDI凭借其在USB桥接芯片领域的深厚积累和技术优势,为电连技术的业务扩展提供了有力支持,共同推动了公司在全球电子元件产业的持续发展。

问答坊 | AI 解惑

急急急!基于单片机的键盘LED指示灯C语言设计问题!附电路图!寻求大侠帮助!

7*6矩阵的键盘,这个程序已经编好了,每个键值存入了key[7][6]这样一个数组中,现在有7*5矩阵的键盘LED指示灯,如何使一个键按下后-其相应的指示灯也亮呢!LED指示灯,列LEDC和行LEDR都是通过锁存器SN74HC574N与P0口相接,信号由CPU到灯!两个锁存 ...…

查看全部问答>

怎样辨别视频线的好坏——大家谈

有的网友工程作了好几年,仍然对这个问题还不能较好的把握。这是一个看来“问题不大”但又很重要的工程实际问题;真正的检测还需要专门的设备和仪器。而这些设备和仪器又是设计和工程单位不具备的。工程鉴别视频线的好坏,希望大家集思广益。这里先 ...…

查看全部问答>

真7.1声道杜比数码EX﹑DTS-ES 96/24解码板

CRYSTAL CS495313 32bit 音频DSP﹑CS8416低时基误差192KHz数字接收器﹑CS42448A 96KHz/24bit ADC及DAC为当今最高性能的AV接收功放解码的芯片组合。     支持杜比数码EX﹑DOLBY PRO-LOGIC﹑HDCD﹑PRO-LOGICⅡ及DTS-ES Matrix﹑DTS-ES Dis ...…

查看全部问答>

安森美收购三洋半导体

Panasonic旗下子公司三洋电机(SANYO Electric)计划将半导体事业出售给美国电源管理解决方案供货商安森美半导体(ON Semiconductor),预估出售金额达200亿日圆前后。三洋电机已就出售半导体事业一事和安森美进入最终协商阶段,双方并预计将于7月中旬 ...…

查看全部问答>

关于函数RegistryNotifyCallback

请问RegistryNotifyCallback,这个函数可以用在driver里面么?           我在Audio Driver中使用了这个函数,但是发现会导致驱动无法加载,而且驱动里面的打印信息一点都没有打印出来,说明不是运行到RegistryNot ...…

查看全部问答>

EVB连接*.cdb数据库问题?

Private Sub Command1_Click()       Dim cnMobileSales As ADOCE.Connection       Dim strPath As String       Dim rs As ADOCE.Recordset       Set cnM ...…

查看全部问答>

加载镜像启动CE后,无法显示硬盘,如何能显示?

小弟新手,刚刚接触wince。我把PLATFORM里的File   Systems   and   Data   Store加上Fat   File   System及CD/UDFS   File   System,并在Storage   D ...…

查看全部问答>

LED模组使用方法和注意事项

 1、LED专用开关电源。电源只能防潮,不能防水,所以电源外置时必需做好防水措施。   2、开关电源均根据LED模组特性调节好输出电压,请在使用过程中不得随意旋转电压调节按钮。   3、LED模组均采用低压输入,要求电源安装在LED发光模组1 ...…

查看全部问答>

STM32F103RB在固件库V1.3.2下的USART2串口收发数据出错

前天发贴是因为USART1接收数据不一致引起。经过ST_ARM的指点:所用固件库版本太低,应升级。现在我已安装好最新的固件库:STM32F10x_StdPeriph_Lib_V3.1.2 USART1的问题解决了,可以设置:波特率1200,1个起始位,9位数字,1个EVEN校验,一 ...…

查看全部问答>

比较单元

请问ev的比较单元的的定时器选哪一个是不是可以设置.在哪个寄存器中设置呢?我看了一个例子,好像涉及到的寄存器都没有看到这个设置选项,那它的时钟预定标用采用的什么呢?…

查看全部问答>