STM32F0-标准库时钟配置指南

启动

从startup_stm32f0xx.s内的开头的Description可以看到

;* Description        : STM32F051 devices vector table for EWARM toolchain.
;*                      This module performs:
;*                      - Set the initial SP
;*                      - Set the initial PC == iar_program_start,
;*                      - Set the vector table entries with the exceptions ISR 
;*                        address
;*                      - Configure the system clock
;*                      - Branches to main in the C library (which eventually
;*                        calls main()).
;*                      After Reset the Cortex-M0 processor is in Thread mode,
;*                      priority is Privileged, and the Stack is set to Main.

可以得知STM32的启动流程是首先初始化SP(堆栈种指针),初始化PC(程序计数器)指针指向__iar_program_start,设置向量表,初始化时钟系统

可以看到初始化时钟系统是在设置堆栈后,并且运行__iar_program_start设置了硬之后,这是必要的。

startup的汇编内容

这也就是所有MCU启动过程中的第一步:使用汇编语言编写的启动第一部分,设置堆栈和硬件为第二部分的启动铺垫。

第二部是使用C语言编写的启动第二部分

接下来直接跳转到SystemInit

SystemInit

1.在初始化的第一步

void SystemInit (void)
{    /* Set HSION bit */RCC->CR |= (uint32_t)0x00000001;

通过数据手册可以看到

打开了HSI时钟

为什么第一步先要打开HSI时钟呢?

         在许多微控制器中,HSI (High Speed Internal) 时钟通常是第一个被启用的时钟源,这是因为HSI时钟是内置在微控制器内部的,不需要外部晶振或陶瓷谐振器就能工作,因此它是最容易且快速可用的时钟源

2.

/* Reset SW[1:0], HPRE[3:0], PPRE[2:0], ADCPRE, MCOSEL[2:0], MCOPRE[2:0] and PLLNODIV bits */ 
RCC->CFGR &= (uint32_t)0x08FFB80C;
//0000 0100 1000 1111 1111 1011 1000 0000 1100

在时钟配置寄存器中,从低位开始看

SW(00)使用HSI作为系统时钟

SWS(11)不使用系统时钟切换状态

HPRE(0000)SYSCLK不分频

PPRE(0000)HCLK不分频

PLLSRC(1)HSE/PREDIV作为PLL输入时钟

PLLXTPRE(1)HSE 2分频

PLLMUL(1111)PLL倍频系数,PLL输入时钟的16倍频

MCO(100)控制器时钟输出为系统时钟SYSCLK

总体可以看到在systemInit中使用了HSI作为系统时钟,HSE作为PLL输入时钟,HSI提供了即时性,HSE提供了可靠性

3.

  /* Reset HSEON, CSSON and PLLON bits */RCC->CR &= (uint32_t)0xFEF6FFFF;
//1111 1110 1111 0110 1111 1111 1111 1111

从低位开始可以得知

HSION(1)启动HSI振荡器

HSEON(1)启动HSE振荡器

HSEBYP(1)HSE晶体振荡器有旁路

CSSON(0)时钟检测器关闭

PLLON(0)PLL关闭

PLLRDY(1)PLL锁定

注意在这里关闭并且锁定了PLL

4.

  /* Reset HSEBYP bit */RCC->CR &= (uint32_t)0xFFFBFFFF;
//1111 1111 1111 1011 1111 1111 1111 1111

可以看确保HSE振荡器不是在旁路模式下工作,而是使用内部电路来产生时钟信号。

需要使用HSE振荡器作为系统时钟的一部分时。在启动阶段,可能首先使用HSI(高速内部振荡器)作为时钟源,随后配置HSE振荡器,并可能使用HSE作为PLL(锁相环)的输入,以产生更高频率的系统时钟

5.

  /* Reset PLLSRC, PLLXTPRE and PLLMUL[3:0] bits */RCC->CFGR &= (uint32_t)0xFFC0FFFF;

将PLLSRC位清零,意味着PLL的输入时钟将默认为HSI经过预分频后的时钟(如果HSI可用并且已经使能)。
将PL LXTPRE位清零,如果选择了HSE作为PLL输入时钟,那么它不会通过预分频器。
将PLLMUL[3:0]位清零,PLL的乘法因子将被重置为其默认或最小值。 

6.

  /* Reset PREDIV1[3:0] bits */RCC->CFGR2 &= (uint32_t)0xFFFFFFF0;

将PREDIV1[3:0]位清零,将预分频器1的分频比数重置为其最小值或默认值,不进行分频

7.

  /* Reset USARTSW[1:0], I2CSW, CECSW and ADCSW bits */RCC->CFGR3 &= (uint32_t)0xFFFFFEAC;

分别配置了USART,I2C,HDMI CEC, ADC时钟源

PCLK作为USART时钟源

HSI作为I2C1的时钟源

HSI/244作为HDMI CEC时钟源

PCLK 2或4分频作为ADC时钟

8.

  /* Reset HSI14 bit */RCC->CR2 &= (uint32_t)0xFFFFFFFE;

禁用了HSI14

HSI14 和 HSI 不完全相同,尽管它们都是 STM32 微控制器内部集成的 RC(电阻-电容)振荡器,但它们有各自的特点和用途:

HSI (High Speed Internal Oscillator)
HSI 是一个典型的内部振荡器,通常提供大约 8 MHz 或 16 MHz 的时钟频率,具体取决于微控制器的型号。HSI 在上电或复位后默认启用,可以作为系统时钟源,为整个微控制器提供时钟信号。
HSI 通常用于在没有外部时钟源时快速启动系统,或者在低功耗模式下作为时钟源。
HSI14 (High Speed Internal 14 MHz Oscillator)
HSI14 提供一个大约 14 MHz 的时钟频率,专为 USB 和某些高级定时器(如 TIM1 和 TIM8)设计。在 STM32 微控制器中,HSI14 主要用于 USB 全速(Full Speed)应用和高级定时器,因为它们需要一个稳定的时钟源。
HSI14 的主要优点在于它可以直接为 USB 设备提供所需的确切时钟频率,而无需额外的时钟调节或 PLL 放大。
总结来说,HSI 和 HSI14 都是内部振荡器,但它们的频率和用途不同。HSI 更倾向于作为系统时钟源,而 HSI14 则专门用于需要特定时钟频率的外设,如 USB 和某些高级定时器。在某些 STM32 系列中,HSI14 不是默认启用的,需要在软件中显式地配置和启用。

9.

  /* Disable all interrupts */RCC->CIR = 0x00000000;

失能所有的中断

之后就进入下面函数来设置时钟预频率 总线预分频 和 FLASH设置

  /* Configure the System clock frequency, AHB/APBx prescalers and Flash settings */SetSysClock();

SetSysClock

这段代码是STM32微控制器中用于配置系统时钟的一个函数,名为`SetSysClock()`。下面是对这段代码详细步骤的解释:

1. 初始化变量:
   `StartUpCounter` 和 `HSEStatus` 分别用于跟踪HSE启动超时计数和HSE状态。

  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;

2. 启用HSE:
   通过设置RCC_CR寄存器的HSEON位来启动高速外部振荡器(HSE)。

  /* Enable HSE */    RCC->CR |= ((uint32_t)RCC_CR_HSEON);

3. 等待HSE就绪:
   使用一个循环检查RCC_CR寄存器的HSERDY位,以确认HSE是否已经稳定。如果HSE在指定时间内未能准备好,`StartUpCounter`将增加,直至达到`HSE_STARTUP_TIMEOUT`常量,此时将退出循环。

do{HSEStatus = RCC->CR & RCC_CR_HSERDY;StartUpCounter++;  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

4. 检查HSE状态:
   如果HSE成功启动,`HSEStatus`设置为0x01,否则设置为0x00。

5. 配置Flash预取缓冲区和等待周期:

  if ((RCC->CR & RCC_CR_HSERDY) != RESET){HSEStatus = (uint32_t)0x01;}else{HSEStatus = (uint32_t)0x00;}  


   如果HSE成功启动,接下来配置Flash访问控制寄存器(FLASH_ACR)以启用预取缓冲区,并设置适当的等待周期。

6. 配置AHB和APB总线时钟:
   设置RCC_CFGR寄存器的HPRE和PPRE位,以确定AHB和APB总线的预分频因子。在这个例子中,HCLK(AHB总线时钟)和PCLK(APB总线时钟)都被设置为与SYSCLK(系统时钟)相同。

/* Enable Prefetch Buffer and set Flash Latency */FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY;/* HCLK = SYSCLK */RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;/* PCLK = HCLK */RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE_DIV1;

7. 配置PLL:
   清除RCC_CFGR寄存器中与PLL源选择、PLL输入时钟预分频和PLL倍频因子相关的位。
   配置PLL以使用HSE作为输入时钟源,经过预分频器,并将PLL的倍频因子设置为6,这意味着最终的PLL输出频率将为HSE频率的6倍(假设HSE为8 MHz,PLL输出将为48 MHz)。

  /* PLL configuration = HSE * 6 = 48 MHz */RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL));RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_PREDIV1 | RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLMULL6);

8. 启用PLL:

    /* Enable PLL */RCC->CR |= RCC_CR_PLLON;

    通过设置RCC_CR寄存器的PLLON位来启动PLL。

9. 等待PLL就绪:
   使用循环检查RCC_CR寄存器的PLLRDY位,以确认PLL是否已经稳定。

/* Wait till PLL is ready */while((RCC->CR & RCC_CR_PLLRDY) == 0){}

10. 选择PLL作为系统时钟源:
    将RCC_CFGR寄存器的SW位设置为PLL,选择PLL作为系统时钟源。

    /* Select PLL as system clock source */RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    

11. 等待PLL作为系统时钟源:
    使用循环检查RCC_CFGR寄存器的SWS位,以确认PLL是否已经被选为系统时钟源。

    /* Wait till PLL is used as system clock source */while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)RCC_CFGR_SWS_PLL){}

12. 处理HSE启动失败:
    如果HSE未能启动,函数将到达此处,这里可以添加代码来处理这种错误情况,例如重启系统或进入错误处理程序。

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

SetSysClock()函数的主要目的是配置系统时钟,使其能够以期望的频率运行。在这个例子中,它首先启用并确认HSE的运行,然后使用HSE作为PLL的输入,通过PLL生成48 MHz的时钟信号,并最终选择PLL作为系统时钟源,从而为整个微控制器提供一个稳定且足够高的时钟频率。

 总结

可以看到STM32的时钟启动方案是在SystemInit中,首先开启了快速易用的HSI作为系统时钟,并且关闭了所有中断,关闭了HSI14,之后在SetSysClock中重启PLL选择了HSE作为系统时钟。

初始化的后期启用HSE并使用PLL来进一步提高系统时钟频率,这种方案有几个明显的好处:

在STM32的启动方案中,首先使用HSI作为系统时钟,然后在初始化的后期启用HSE并使用PLL来进一步提高系统时钟频率,这种方案有几个明显的好处:

1. 快速启动:
   HSI作为内部RC振荡器,不需要外部元件,上电后立即可用这使得系统可以迅速启动并执行基本的初始化,如设置堆栈、初始化硬件寄存器等,而无需等待外部时钟源稳定。

2. 可靠性与容错性:
   HSI提供了系统启动时的可靠时钟源,即使外部晶振或时钟源出现问题,系统仍然可以使用HSI运行,虽然可能在精度和稳定性上有所折衷,但至少可以确保基本的功能性和安全性

3. 性能提升:
   一旦系统初步初始化完成,可以启用更稳定、精度更高的HSE振荡器,并通过PLL进一步提高时钟频率。这可以显著提高系统的性能,因为PLL可以将时钟频率放大到远高于HSI所能提供的频率,从而允许CPU和外设以更高的速度运行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/874796.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Leetcode】十八、动态规划:不同路径 + 最大正方形

文章目录 1、动态规划2、leetcode509:斐波那契数列3、leetcode62:不同路径4、leetcode121:买卖股票的最佳时机5、leetcode70:爬楼梯6、leetcode279:完全平方数7、leetcode221:最大正方形 1、动态规划 只能…

C#开源、简单易用的Dapper扩展类库 - Dommel

项目特性 Dommel 使用 IDbConnection 接口上的扩展方法为 CRUD 操作提供了便捷的 API。 Dommel 能够根据你的 POCO 实体自动生成相应的 SQL 查询语句。这大大减少了手动编写 SQL 代码的工作量,并提高了代码的可读性和可维护性。 Dommel 支持 LINQ 表达式&#xff…

记一次因敏感信息泄露而导致的越权+存储型XSS

1、寻找测试目标 可能各位师傅会有苦于不知道如何寻找测试目标的烦恼,这里我惯用的就是寻找可进站的思路。这个思路分为两种,一是弱口令进站测试,二是可注册进站测试。依照这个思路,我依旧是用鹰图进行了一波资产的搜集&#xff…

SSIS_SQLITE

1.安装 SQLite ODBC 驱动程序 2.添加SQLite数据源 在“用户DSN”或“系统DSN”选项卡中,点击“添加”。选择“SQLite3 ODBC Driver”,然后点击“完成”。在弹出的配置窗口中,设置数据源名称(DSN),并指定S…

英迈中国与 Splashtop 正式达成战略合作协议

2024年7月23日,英迈中国与 Splashtop 正式达成战略合作协议,英迈中国正式成为其在中国区的战略合作伙伴。此次合作将结合 Splashtop 先进的远程桌面控制技术和英迈在技术服务与供应链管理领域的专业优势,为中国地区的用户带来更加安全的远程访…

联想教育电脑硬盘保护同传EDU系统使用简明教程

目录 一、原理概述 二、简明使用方法 1、软件下载 2、开机引导 3、开始安装 4、使用 (1)进入底层 (2)进行分区设置 (3)系统设置 (4)安装硬盘保护驱动 (5&…

前端模块化CommonJS、AMD、CMD、ES6

在前端开发中,模块化是一种重要的代码组织方式,它有助于将复杂的代码拆分成可管理的小块,提高代码的可维护性和可重用性。CommonJS、AMD(异步模块定义)和CMD(通用模块定义)是三种不同的模块规范…

leetcode-101. 对称二叉树

题目描述 给你一个二叉树的根节点 root , 检查它是否轴对称。 示例 1: 输入:root [1,2,2,3,4,4,3] 输出:true示例 2: 输入:root [1,2,2,null,3,null,3] 输出:false 思路 1) 如果同时root1…

【调试笔记-20240723-Linux-gitee 仓库同步 github 仓库,并保持所有访问链接调整为指向 gitee 仓库的 URL】

调试笔记-系列文章目录 调试笔记-20240723-Linux-gitee 仓库同步 github 仓库,并保持所有访问链接调整为指向 gitee 仓库的 URL 文章目录 调试笔记-系列文章目录调试笔记-20240723-Linux-gitee 仓库同步 github 仓库,并保持所有访问链接调整为指向 gite…

Ubuntu20.04版本升级openssh9.8p1方法

一、问题描述: 8.5p1 和 9.7p1 之间的openssh版本漏洞可能会导致linux系统以root身份进行RCE,所以需安装最新版本 二、解决方法: 将当前openssh版本升级到最新的版本即openssh-9.8p1版本,OpenSSL大版本升级且OpenSSH有新稳定版本…

Zabbix监控应用

一.监控tomcat 1.在tomcat服务器上安装zabbix-agent服务 [rootnode2 etc]#vim zabbix_agentd.conf 94 Server192.168.240.13 #指向当前zabbix server ##### Passive checks related #被动检查相关配置### Option: ListenPort ListenPort10050 #监听端口 默认的无需修改11…

SPF配置教程:如何安全构建邮件发送策略?

SPF配置教程的步骤详解!SPF记录配置方法策略有哪些? SPF通过允许域名所有者指定哪些主机可以代表该域发送邮件,从而减少电子邮件欺诈和垃圾邮件的风险。AokSend将详细介绍SPF配置教程,并指导您如何安全地构建邮件发送策略。 SPF…

《白话机器学习的数学》第4章——评估

4.1模型评估 1.由于像多重回归这样的问题会导致无法在图上展示,所以需要能够够定量地表示机器学习模型的精度。 4.2交叉验证 4.2.1回归问题的验证 1.把获取的全部训练数据分成两份:一份用于测试,一份用于训练。然后用前者来评估模型。 一般…

C# 数组常用遍历方式

// 假设数组Point[] points new Point[2];// 第一种遍历 forfor (int i 0; i < points.Length; i){Point p points[i];Console.WriteLine($"X{p.X},y{p.Y}");}// 第二种遍历 foreachforeach (Point p in points){Console.WriteLine($"X{p.X},y{p.Y}"…

TCP三次握手和四次挥手的理解

三次握手 第一次握手&#xff1a; 客户端发出 请求报文其中SYN应1&#xff0c;选择一个序列号x 第二次握手&#xff1a; 服务端接收到之后回复 确认报文&#xff0c;其中SYN应1&#xff0c;ACK1&#xff0c;确认号是x1&#xff0c;同时为自己初始化序列号y 第三次握手&…

Hadoop架构

一、案列分析 1.1案例概述 现在已经进入了大数据(Big Data)时代&#xff0c;数以万计用户的互联网服务时时刻刻都在产生大量的交互&#xff0c;要处理的数据量实在是太大了&#xff0c;以传统的数据库技术等其他手段根本无法应对数据处理的实时性、有效性的需求。HDFS顺应时代…

Linux(CentOS)的“应用商城” —— yum

Linux&#xff08;CentOS&#xff09;的“应用商城” —— yum 关于 yum 和软件包Linux 系统&#xff08;CentOS&#xff09;的生态yum 相关操作yum 本地配置yum 安装 lrzsz.x86_64 关于 yum 和软件包 首先 yum 是软件下载安装管理的客户端&#xff0c;类似各种手机里的“应用…

WEB前端10- Fetch API(同步/异步/跨域处理)

Fetch API Fetch API 可以用来获取远程数据&#xff0c;用于在 Web 应用程序中发起和处理 HTTP 请求。它基于 Promise&#xff0c;提供了一种简单而强大的方式来处理网络通信&#xff0c;替代了传统的 XMLHttpRequest。 Promise对象 Promise 对象是 JavaScript 中处理异步操…

0723,UDP通信(聪明小辉聪明小辉),HTTP协议

我就是一个爱屋及乌的人&#xff01;&#xff01;&#xff01;&#xff01; #include "network_disk_kai.h" 昨天的epoll&#xff1a; 可恶抄错代码了 epoll_s.csockect return listenfdsetsockoptsockaddr_in bind listenfd & serveraddr…

Ubuntu 中默认的 root 用户密码

场景&#xff1a;想要切换root用户&#xff0c;发现得输入密码&#xff0c;以为是以前设置过然后一直尝试都是错误【认证失败】最后发现根本没设置过root用户&#xff0c;默认会随机生成root用户的密码&#x1f605; Ubuntu 中默认的 root 密码是随机的&#xff0c;即每次开机都…