历史上的今天
返回首页

历史上的今天

今天是:2025年01月04日(星期六)

2021年01月04日 | STM32时钟初始化函数SystemInit()详解

2021-01-04 来源:eefocus

花了一天的时间,总算是了解了SystemInit()函数实现了哪些功能,初学STM32,,现记录如下(有理解错误的地方还请大侠指出):

使用的是3.5的库,用的是STM32F107VC,开发环境RVMDK4.23

我已经定义了STM32F10X_CL,SYSCLK_FREQ_72MHz

函数调用顺序:

startup_stm32f10x_cl.s(启动文件) → SystemInit() → SetSysClock () → SetSysClockTo72()

初始化时钟用到的RCC寄存器复位值:

RCC_CR = 0x0000 xx83; RCC_CFGR = 0x0000 0000;RCC_CIR = 0x0000 0000; RCC_CFGR2 = 0x0000 0000;

SystemInit()

在调用 SetSysClock()之前RCC寄存器的值如下(都是一些与运算,或运算,在此就不赘述了):

RCC->CR = 0x0000 0083; RCC->CIR = 0x00FF0000; RCC->CFGR2 = 0x00000000;至于这些寄存器都代表着什么意思,详见芯片资料RCC寄存器,该文重点不在此处;

SetSysClock()函数如下:

static void SetSysClock(void)

{

#ifdef SYSCLK_FREQ_HSE

SetSysClockToHSE();

#elif defined SYSCLK_FREQ_24MHz

SetSysClockTo24();

#elif defined SYSCLK_FREQ_36MHz

SetSysClockTo36();

#elif defined SYSCLK_FREQ_48MHz

SetSysClockTo48();

#elif defined SYSCLK_FREQ_56MHz

SetSysClockTo56();

#elif defined SYSCLK_FREQ_72MHz //我的定义的是SYSCLK_FREQ_72MHz,所以调用SetSysClockTo72()

SetSysClockTo72();

#endif

}

SetSysClockTo72()函数如下:

static void SetSysClockTo72(void)

{

__IO uint32_t StartUpCounter = 0, HSEStatus = 0;

/* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------/

/ 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)

{

/* Enable Prefetch Buffer */

FLASH->ACR |= FLASH_ACR_PRFTBE;


/* Flash 2 wait state */

FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);

FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;    

/* HCLK = SYSCLK */

RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;

  

/* PCLK2 = HCLK */

RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;


/* PCLK1 = HCLK */

RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;


#ifdef STM32F10X_CL

/* Configure PLLs ------------------------------------------------------/

/ PLL2 configuration: PLL2CLK = (HSE / 5) * 8 = 40 MHz /

/ PREDIV1 configuration: PREDIV1CLK = PLL2 / 5 = 8 MHz */


RCC->CFGR2 &= (uint32_t)~(RCC_CFGR2_PREDIV2 | RCC_CFGR2_PLL2MUL |

                          RCC_CFGR2_PREDIV1 | RCC_CFGR2_PREDIV1SRC);

RCC->CFGR2 |= (uint32_t)(RCC_CFGR2_PREDIV2_DIV5 | RCC_CFGR2_PLL2MUL8 |

                         RCC_CFGR2_PREDIV1SRC_PLL2 | RCC_CFGR2_PREDIV1_DIV5);

 /* Enable PLL2 */

RCC->CR |= RCC_CR_PLL2ON;

/* Wait till PLL2 is ready */

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

{

}

  /* PLL configuration: PLLCLK = PREDIV1 * 9 = 72 MHz */ 

RCC->CFGR &= (uint32_t)~(RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL);

RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLSRC_PREDIV1 | 

                        RCC_CFGR_PLLMULL9); 


#else

/* PLL configuration: PLLCLK = HSE * 9 = 72 MHz /

RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |

RCC_CFGR_PLLMULL));

RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);

#endif / STM32F10X_CL */


/* Enable PLL */

RCC->CR |= RCC_CR_PLLON;


/* Wait till PLL is ready */

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

{

}


/* Select PLL as system clock source */

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

RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    


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

while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)

{

}


}

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 */

}

}


1:AHB, APB1,APB2时钟确定

//HCLK = SYSCLK ,从下面的分析可以得出SYSCLK是使用PLLCLK时钟的,也就是72MHZ(至于72MHZ如何得来,请看下面分析)

//那么就是HCLK(AHB总线时钟)=PLLCLK = 72MHZ

//AHB总线时钟等于系统时钟SYSCLK,也就是 AHB时钟 = HCLK = SYSCLK = 72MHZ

/* HCLK = SYSCLK */

RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;


//PLCK2等于HCLK一分频, 所以PCLK2 = HCLK,HCLK = 72MHZ, 那么PLCK2(APB2总线时钟) = 72MHZ

//APB2总线时钟等于HCLK的一分频,也就是不分频;APB2 时钟 = HCLK = SYSCLK = 72MHZ

/* PCLK2 = HCLK */

RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;


//PCLK1 = HCLK / 2;PCLK1 等于HCLK时钟的二分频,那么PCLK1(APB1) = 72MHZ / 2 = 36MHZ    

//APB1总线时钟等于HCLK的二分频,也就是 APB1时钟= HCLK / 2 = 36MHZ

/* PCLK1 = HCLK */

RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;


2:如何得出SYSCLK(系统时钟)为72MHZ(外部晶振25MHZ)

//记得参考英文芯片资料的时钟树P115页和RCC时钟寄存器进行理解

RCC->CFGR2 |= (uint32_t)(RCC_CFGR2_PREDIV2_DIV5 | RCC_CFGR2_PLL2MUL8 | RCC_CFGR2_PREDIV1SRC_PLL2 | RCC_CFGR2_PREDIV1_DIV5);


RCC_CFGR2_PREDIV2_DIV5: PREDIV2 = 5; 5分频

也就是PREDIV2对输入的外部时钟 5分频,那么PLL2和PLL3没有倍频前是25 /5 = 5MHZ

RCC_CFGR2_PLL2MUL8 : PLL2MUL = 8; 8倍频

8倍频后,PLL2时钟 = 5 * 8 = 40MHZ; 因此 PLL2CLK = 40MHZ

RCC_CFGR2_PREDIV1SRC_PLL2 : RCC_CFGR2的第16位为1, 选择PLL2CLK 作为PREDIV1的时钟源

RCC_CFGR2_PREDIV1_DIV5:PREDIV1 = 5;PREDIV1对输入时钟5分频 PREDIV1CLK = PLL2CLK / 5 = 8MHZ


以上是对RCC_CFGR2进行的配置

RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLSRC_PREDIV1 |

RCC_CFGR_PLLMULL9);


RCC_CFGR_PLLXTPRE_PREDIV1 :操作的是RCC_CFGR的第17位PLLXTPRE,操作这一位和操作RCC_CFGR2寄存器的 位[3:0]中的最低位是相同的效果

RCC_CFGR_PLLSRC_PREDIV1 :选择PREDIV1输出作为PLL输入时钟;PREDIV1CLK = 8MHZ,所以输入给PLL倍频的 时钟源是8MHZ

RCC_CFGR_PLLMULL9 :PLLMUL = 9;PLL倍频系数为9,也就是对 PLLCLK = PREDIV1CLK * 8 = 72MHZ


以上是对RCC_CFGR进行的配置

RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL; //选择PLLCLK作为系统时钟源


至此基本配置已经完成,配置的时钟如下所述:

SYSCLK(系统时钟) = 72MHZ

AHB总线时钟 = 72MHZ

APB1总线时钟 = 36MHZ

APB2总线时钟 = 72MHZ

PLL时钟 = 72MHZ

PLL2时钟 = 40MHZ


STM32的时钟树

对于广大初次接触STM32的读者朋友(甚至是初次接触ARM器件的读者朋友)来说,在熟悉了开发环境的使用之后,往往“栽倒”在同一个问题上。这问题有个关键字叫:时钟树。

众所周知,微控制器(处理器)的运行必须要依赖周期性的时钟脉冲来驱动——往往由一个外部晶体振荡器提供时钟输入为始,最终转换为多个外部设备的周期性运作为末,这种时钟“能量”扩散流动的路径,犹如大树的养分通过主干流向各个分支,因此常称之为“时钟树”。在一些传统的低端8位单片机诸如51,AVR,PIC等单片机,其也具备自身的一个时钟树系统,但其中的绝大部分是不受用户控制的,亦即在单片机上电后,时钟树就固定在某种不可更改的状态(假设单片机处于正常工作的状态)。比如51单片机使用典型的12MHz晶振作为时钟源,则外设如IO口、定时器、串口等设备的驱动时钟速率便已经是固定的,用户无法将此时钟速率更改,除非更换晶振。

而STM32微控制器的时钟树则是可配置的,其时钟输入源与最终达到外设处的时钟速率不再有固定的关系,本文将来详细解析STM32微控制器的时钟树。图1是STM32微控制器的时钟树,表1是图中各个标号所表示的部件。

标号 图1标号释义

1 内部低速振荡器(LSI,40Khz)

2 外部低速振荡器(LSE,32.768Khz)

3 外部高速振荡器(HSE,3-25MHz)

4 内部高速振荡器(HIS,8MHz)

5 PLL输入选择位

6 RTC时钟选择位

7 PLL1分频数寄存器

8 PLL1倍频寄存器

9 系统时钟选择位

10 USB分频寄存器

11 AHB分频寄存器

12 APB1分频寄存器

13 AHB总线

14 APB1外设总线

15 APB2分频寄存器

16 APB2外设总线

17 ADC预分频寄存器

18 ADC外设

19 PLL2分频数寄存器

20 PLL2倍频寄存器

21 PLL时钟源选择寄存器

22 独立看门狗设备

23 RTC设备

在这里插入图片描述

图1 STM32的时钟树


在认识这颗时钟树之前,首先要明确“主干”和最终的“分支”。假设使用外部8MHz晶振作为STM32的时钟输入源(这也是最常见的一种做法),则这个8MHz便是“主干”,而“分支”很显然是最终的外部设备比如通用输入输出设备(GPIO)。这样可以轻易找出第一条时钟的“脉络”:

3——5——7——21——8——9——11——13

对此条时钟路径做如下解析:

对于3,首先是外部的3-25MHz(前文已假设为8MHz)输入;

对于5,通过PLL选择位预先选择后续PLL分支的输入时钟(假设选择外部晶振);

对于7,设置外部晶振的分频数(假设1分频);

对于21,选择PLL倍频的时钟源(假设选择经过分频后的外部晶振时钟);

对于8,设置PLL倍频数(假设9倍频);

对于9,选择系统时钟源(假设选择经过PLL倍频所输出的时钟);

对于11,设置AHB总线分频数(假设1分频);

对于13,时钟到达AHB总线;

在上一章节中所介绍的GPIO外设属于APB2设备,即GPIO的时钟来源于APB2总线,同样在图1中也可以寻获GPIO外设的时钟轨迹:

3——5——7——21——8——9——11——15——16

对于3,首先是外部的3-25MHz(前文已假设为8MHz)输入;

对于5, 通过PLL选择位预先选择后续PLL分支的输入时钟(假设选择外部晶振);

对于7,设置外部晶振的分频数(假设1分频);

对于21,选择PLL倍频的时钟源(假设选择经过分频后的外部晶振时钟);

对于8,设置PLL倍频数(假设9倍频);

对于9,选择系统时钟源(假设选择经过PLL倍频所输出的时钟);

对于11,设置AHB总线分频数(假设1分频);

对于15,设置APB2总线分频数(假设1分频);

对于16,时钟到达APB2总线;

现在来计算一下GPIO设备的最大驱动时钟速率(各个条件已在上述要点中假设):


由3所知晶振输入为8MHz,由5——21知PLL的时钟源为经过分频后的外部晶振时钟,并且此分频数为1分频,因此首先得出PLL的时钟源为:8MHz / 1 = 8MHz。

由8、9知PLL倍频数为9,且将PLL倍频后的时钟输出选择为系统时钟,则得出系统时钟为 8MHz * 9 = 72MHz。

时钟到达AHB预分频器,由11知时钟经过AHB预分频器之后的速率仍为72MHz。

时钟到达APB2预分频器,由15经过APB2预分频器后速率仍为72MHz。

时钟到达APB2总线外设。

因此STM32的APB2总线外设,所能达到的最大速率为72MHz。依据以上方法读者可以搜寻出APB1总线外设时钟、RTC外设时钟、独立看门狗等外设时钟的来龙去脉。接下来从程序的角度分析时钟树的设置,程序清单如下:

void RCC_Configuration(void)

{

ErrorStatus HSEStartUpStatus; (1)

RCC_DeInit(); (2)

RCC_HSEConfig(RCC_HSE_ON); (3)

HSEStartUpStatus = RCC_WaitForHSEStartUp(); (4)

if(HSEStartUpStatus == SUCCESS) (5)

{

RCC_HCLKConfig(RCC_SYSCLK_Div1); (6)

RCC_PCLK2Config(RCC_HCLK_Div1); (7)

RCC_PCLK1Config(RCC_HCLK_Div2); (8)

FLASH_SetLatency(FLASH_Latency_2); (9)

FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); (10)

RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); (11)

RCC_PLLCmd(ENABLE); (12)

while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); (13)

RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); (14)

while(RCC_GetSYSCLKSource() != 0x08); (15)

}

}


推荐阅读

史海拾趣

Easy Magnet Corp公司的发展小趣事

Easy Magnet Corp公司深知人才是企业发展的核心动力。因此,公司注重人才培养和引进,建立了一支高素质、专业化的研发团队。同时,公司还为员工提供了良好的工作环境和福利待遇,激发了员工的工作热情和创造力。这些举措为公司的发展提供了坚实的人才保障。

Base Two (2) Systems公司的发展小趣事

随着产品技术的不断成熟,Base Two (2) Systems开始积极拓展市场。公司通过与各大电子厂商、分销商建立紧密的合作关系,将产品推向更广阔的市场。同时,公司还积极参加各类行业展会和交流活动,与业内同行深入交流,共同探讨行业发展趋势,为公司的长远发展奠定了坚实的基础。

BULGIN公司的发展小趣事

在2016年,BULGIN公司宣布与姊妹公司Arcolectric进行战略性合并。这一合并使得两个具有互补性的产品组合合为一体,BULGIN因此成为更加全面和覆盖范围更加广的工程解决方案提供商。合并后的BULGIN不仅扩大了专长范围,特别是在机电开关领域,还进一步提升了在全球市场的竞争力。

Devar Inc公司的发展小趣事

智能家居是电子行业的一个重要发展方向,Devar Inc公司也积极拓展这一领域。公司推出了一系列智能家居产品,如智能音箱、智能照明、智能安防等。这些产品不仅具备远程控制、语音控制等便捷功能,还通过Devar的AI技术实现了智能学习和智能联动。例如,智能音箱可以根据用户的习惯和喜好,为用户推荐音乐、新闻等内容;智能照明则可以根据环境光线和用户需求自动调节亮度和色温。

EAO公司的发展小趣事

面对全球市场的竞争压力,EAO公司坚定实施全球化战略。公司在全球范围内建立了完善的销售网络和售后服务体系,为客户提供及时、高效的服务。同时,EAO公司还积极寻求与国际知名企业的合作机会,共同开拓市场、分享资源。通过全球化战略的实施,EAO公司不断提升品牌影响力和市场竞争力。

静芯微电子(ElecSuper)公司的发展小趣事

作为一家有远见的企业,静芯微电子不仅关注自身的发展,还积极履行社会责任。公司积极参与环保公益活动和社会责任项目,致力于推动电子行业的可持续发展。同时,静芯微电子还注重员工的培训和发展,为员工提供良好的工作环境和晋升机会。这些举措不仅增强了企业的凝聚力和向心力,也为企业的长期发展奠定了坚实的基础。

问答坊 | AI 解惑

有需要电感、变压器样品的工程师请进来,可能会有您用到的产品。

本帖最后由 jameswangsynnex 于 2015-3-3 19:57 编辑 我工厂位于深圳观澜,是一家专业电感、贴片变压器生产商.在品质、交期、单价各方面都有一定的优势.有需要这方面样品的工程师敬请联络索样,我会尽快免费提供给大家,谢谢! 美登一电子(深圳)有 ...…

查看全部问答>

智能电子钟程序

学习单片机十天,编写的智能电子钟程序。…

查看全部问答>

50Mhz分频的问题

怎么将50M的时钟分频成153600hz? 用普通的方法好像不能解决,因为除不尽。 我设想用一个pll,倍频后,再分 高手指教…

查看全部问答>

有操作系统的嵌入式设备和没有操作系统的嵌入式设备之间的区别

有操作系统的嵌入式设备和没有操作系统的嵌入式设备之间的区别,以及有操作系统的嵌入式设备的优点是什么??…

查看全部问答>

初学者问几个问题,关于wince与arm,望高人指教

首先,我用的是微芯力科的ws-430评估板,cpu为at91rm9200,要求用wince开发。 评估板附带的资料只有linux以及ucos系统的,没有wince的资料,打电话到微芯力科公司,被告知该板不支持wince,因为wince要求某尺寸的真彩屏幕,而我的板子上是一个128 ...…

查看全部问答>

如何验证下位机的串行发送程序

我现在没有芯片,只是在keilc中写的串行通信程序,我想验证一下它能不能实现发送字节,只有一台的情况下该如何做?其中发送字节程序是? void sendbyte(unsigned char word) { SBUF=word; while(TI==0);   TI=0; } …

查看全部问答>

到底怎么才算嵌入式编程?

会C语言就算是会嵌入式编程了吗?…

查看全部问答>

原以为写个usb驱动很简单

DOS下的usb storage驱动一个比一个不好用,我一急就决定自己写一个,结果搞了20天了,还一头浆糊,怎么办。想来想去还是原始社会好啊…

查看全部问答>

AD快捷键大全

挺全的,ad6的快捷键大全,很实用的…

查看全部问答>

如何使用MSP430外部计数信号TACLK

急求高手指点,我的做法是引脚TACLK连接外部频率信号,对外部信号直接计数,不知道为什么总是得不到计数值。 MSP430F2274 P1SEL |= BIT0; P1DIR &= ~BIT0; TACTL = TASSEL_0 + ID_0 + MC_2;…

查看全部问答>