单片机外部晶振故障后自动切换内部晶振——以STM32为例

单片机外部晶振故障后自动切换内部晶振——以STM32为例

作者日期版本说明
Dog Tao2023.08.02V1.0发布初始版本

文章目录

  • 单片机外部晶振故障后自动切换内部晶振——以STM32为例
    • 背景
      • 外部晶振与内部振荡器
      • STM32F103时钟系统
      • STM32F407时钟系统
    • 代码实现
      • 系统时钟设置流程
      • 时钟源检测与切换
      • 使用内部振荡器
    • 总结

背景

时钟信号是单片机的心跳,对嵌入式系统的长期稳定运行有着至关重要的作用。现代单片机的时钟信号一般都支持外部时钟、外部晶体振荡器、内部RC振荡器等形式的输入。外部晶体振荡器(晶振)由于其高精度、高稳定性、低温飘、低成本的特性,广泛应用于各类对通讯、时间、性能要求严格的场合。

外部晶振与内部振荡器

  • 外部晶振:这种时钟源来自于微控制器外部的晶振或者振荡器。晶振是一种机械振动器件,利用压电效应或反压电效应产生精确的频率。外部晶振的主要优点是精度高,稳定性好。但是,他们可能需要额外的电路空间,并且可能更容易受到环境因素如温度和震动的影响。

  • 内部振荡器:这是一种集成在微控制器内部的时钟源。它通常是基于RC(电阻-电容)网络的振荡器。内部振荡器的优点是它们不需要额外的硬件,更便宜,更节省空间。但是,他们的频率精度和稳定性通常较低。

在实际应用中,选择哪种时钟源取决于具体的设计需求。对于需要高精度和稳定性的应用,通常会选择外部晶振。对于成本和空间更为重要的应用,内部振荡器可能是一个更好的选择。

STM32F103时钟系统

STM32F103的时钟系统相当复杂,主要有四种时钟源:高速内部(HSI)时钟,高速外部(HSE)时钟,低速内部(LSI)时钟,以及低速外部(LSE)时钟。

  • 高速内部(HSI)时钟:一个自校准的内部RC振荡器,提供8MHz的时钟。
  • 高速外部(HSE)时钟:可以连接到一个外部4-16 MHz的晶振或者用户提供的时钟源。
  • 低速内部(LSI)时钟:一个内部RC振荡器,提供40kHz的时钟,主要供独立看门狗和自动唤醒单元使用。
  • 低速外部(LSE)时钟:可以连接到一个外部32.768 kHz的晶振,主要用于RTC(实时时钟)和LCD。

如果使用外部晶振(HSE),STM32F103的最大系统时钟频率可以达到72 MHz。如果使用内部振荡器(HSI),STM32F103的最大系统时钟频率可以达到64 MHz。

STM32F407时钟系统

STM32F407的时钟系统与STM32F103的类似,也包括HSI,HSE,LSI和LSE四种时钟。但是其高速内部时钟频率达到了16MHz。

在STM32F407中,无论是16 MHz的HSI还是最高可以到24 MHz的HSE,都可以通过PLL倍频到168 MHz作为系统时钟频率。

STM32F103的时钟系统
在这里插入图片描述

代码实现

系统时钟设置流程

在STM32F103的标准库函数中,“system_stm32f10x.c”文件提供了多个不同频率的系统时钟设置方法,可以通过宏定义的方式条件编译指定时钟频率的设置函数,如下图所示:
在这里插入图片描述基于标准库函数的系统时钟设置调用路径依次为:

  1. 启动文件“startup_stm32f103x8.s”调用位于“system_stm32f10x.c”文件中的 SystemInit()函数。
  2. SystemInit()函数调用SetSysClock()函数。
  3. SetSysClock()函数根据上述宏定义调用不同的时钟设置函数,将系统时钟设置为指定频率。
  4. 使用全局变量SystemCoreClock可获取当前系统的时钟频率。
startup_stm32f103x8.s
SystemInit
SetSysClock
SetSysClockToHSE
SetSysClockTo36
SetSysClockTo72
Global variable: SystemCoreClock

时钟源检测与切换

在上述位于“system_stm32f10x.c”文件中系统时钟设置函数中已经内置了相关功能的模板,以SetSysClockTo72()函数为例:

/*** @brief  Sets System clock frequency to 72MHz and configure HCLK, PCLK2 *         and PCLK1 prescalers. * @note   This function should be used only after reset.* @param  None* @retval None*/
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 */}
}

因此,只需要设计实现当外部晶振启动失败后的情况即可,函数框架如下:

static void SetSysClockTo72(void)
{__IO uint32_t StartUpCounter = 0, HSEStatus = 0;/* 省略无关内容 */    if (HSEStatus == (uint32_t)0x01){/* HSE启动成功 */}else{ /* 自定内容:HSE启动失败,尝试启动内部振荡器,并更新系统时钟 */SetSysClockTo48_HSI();SystemCoreClockUpdate();}
}

使用内部振荡器

HSE启动失败,尝试启动内部振荡器,并更新系统时钟的自定义代码示例如下:

static void SetSysClockTo48_HSI()
{/* 开启HSI 即内部晶振时钟 */RCC->CR |= (uint32_t)0x00000001; /*选择HSI为PLL的时钟源HSI必须2分频给PLL*/RCC->CFGR |= (uint32_t)RCC_CFGR_PLLSRC_HSI_Div2; /*PLLCLK=8/2*12=48MHz   设置倍频得到时钟源PLL的频率*/RCC->CFGR |= (uint32_t)RCC_CFGR_PLLMULL12;/* PLL不分频输出  */RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;/* 使能 PLL时钟 */RCC->CR |= RCC_CR_PLLON;/* 等待PLL时钟就绪*/while((RCC->CR & RCC_CR_PLLRDY) == 0){}/* 选择PLL为系统时钟的时钟源 */RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    /* 等到PLL成为系统时钟的时钟源*/while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08){}
}

使用内部振荡器设置系统时钟之后,需要调用预设的SystemCoreClockUpdate()完成时钟频率的更新(更新全局变量SystemCoreClock的值)。

/*** @brief  Update SystemCoreClock variable according to Clock Register Values.*         The SystemCoreClock variable contains the core clock (HCLK), it can*         be used by the user application to setup the SysTick timer or configure*         other parameters.*           * @note   Each time the core clock (HCLK) changes, this function must be called*         to update SystemCoreClock variable value. Otherwise, any configuration*         based on this variable will be incorrect.         *     * @note   - The system frequency computed by this function is not the real *           frequency in the chip. It is calculated based on the predefined *           constant and the selected clock source:*             *           - If SYSCLK source is HSI, SystemCoreClock will contain the HSI_VALUE(*)*                                              *           - If SYSCLK source is HSE, SystemCoreClock will contain the HSE_VALUE(**)*                          *           - If SYSCLK source is PLL, SystemCoreClock will contain the HSE_VALUE(**) *             or HSI_VALUE(*) multiplied by the PLL factors.*         *         (*) HSI_VALUE is a constant defined in stm32f1xx.h file (default value*             8 MHz) but the real value may vary depending on the variations*             in voltage and temperature.   *    *         (**) HSE_VALUE is a constant defined in stm32f1xx.h file (default value*              8 MHz or 25 MHz, depedning on the product used), user has to ensure*              that HSE_VALUE is same as the real frequency of the crystal used.*              Otherwise, this function may have wrong result.*                *         - The result of this function could be not correct when using fractional*           value for HSE crystal.* @param  None* @retval None*/
void SystemCoreClockUpdate (void)
{uint32_t tmp = 0, pllmull = 0, pllsource = 0;#ifdef  STM32F10X_CLuint32_t prediv1source = 0, prediv1factor = 0, prediv2factor = 0, pll2mull = 0;
#endif /* STM32F10X_CL */#if defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)uint32_t prediv1factor = 0;
#endif /* STM32F10X_LD_VL or STM32F10X_MD_VL or STM32F10X_HD_VL *//* Get SYSCLK source -------------------------------------------------------*/tmp = RCC->CFGR & RCC_CFGR_SWS;switch (tmp){case 0x00:  /* HSI used as system clock */SystemCoreClock = HSI_VALUE;break;case 0x04:  /* HSE used as system clock */SystemCoreClock = HSE_VALUE;break;case 0x08:  /* PLL used as system clock *//* Get PLL clock source and multiplication factor ----------------------*/pllmull = RCC->CFGR & RCC_CFGR_PLLMULL;pllsource = RCC->CFGR & RCC_CFGR_PLLSRC;#ifndef STM32F10X_CL      pllmull = ( pllmull >> 18) + 2;if (pllsource == 0x00){/* HSI oscillator clock divided by 2 selected as PLL clock entry */SystemCoreClock = (HSI_VALUE >> 1) * pllmull;}else{#if defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)prediv1factor = (RCC->CFGR2 & RCC_CFGR2_PREDIV1) + 1;/* HSE oscillator clock selected as PREDIV1 clock entry */SystemCoreClock = (HSE_VALUE / prediv1factor) * pllmull; #else/* HSE selected as PLL clock entry */if ((RCC->CFGR & RCC_CFGR_PLLXTPRE) != (uint32_t)RESET){/* HSE oscillator clock divided by 2 */SystemCoreClock = (HSE_VALUE >> 1) * pllmull;}else{SystemCoreClock = HSE_VALUE * pllmull;}#endif}
#elsepllmull = pllmull >> 18;if (pllmull != 0x0D){pllmull += 2;}else{ /* PLL multiplication factor = PLL input clock * 6.5 */pllmull = 13 / 2; }if (pllsource == 0x00){/* HSI oscillator clock divided by 2 selected as PLL clock entry */SystemCoreClock = (HSI_VALUE >> 1) * pllmull;}else{/* PREDIV1 selected as PLL clock entry *//* Get PREDIV1 clock source and division factor */prediv1source = RCC->CFGR2 & RCC_CFGR2_PREDIV1SRC;prediv1factor = (RCC->CFGR2 & RCC_CFGR2_PREDIV1) + 1;if (prediv1source == 0){ /* HSE oscillator clock selected as PREDIV1 clock entry */SystemCoreClock = (HSE_VALUE / prediv1factor) * pllmull;          }else{/* PLL2 clock selected as PREDIV1 clock entry *//* Get PREDIV2 division factor and PLL2 multiplication factor */prediv2factor = ((RCC->CFGR2 & RCC_CFGR2_PREDIV2) >> 4) + 1;pll2mull = ((RCC->CFGR2 & RCC_CFGR2_PLL2MUL) >> 8 ) + 2; SystemCoreClock = (((HSE_VALUE / prediv2factor) * pll2mull) / prediv1factor) * pllmull;                         }}
#endif /* STM32F10X_CL */ break;default:SystemCoreClock = HSI_VALUE;break;}/* Compute HCLK clock frequency ----------------*//* Get HCLK prescaler */tmp = AHBPrescTable[((RCC->CFGR & RCC_CFGR_HPRE) >> 4)];/* HCLK clock frequency */SystemCoreClock >>= tmp;  
}

总结

本文所述的设计方法,能够在外部晶振故障后自动切换到内部晶振,提高系统的可靠性与稳定性。注意,上述示例只在单片机启动时进行时钟源检测,因此,如果是处理运行时的突发时钟故障,需要设计配套的看门狗,在系统陷入异常状态后自动重启系统。

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

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

相关文章

spring AOP学习

概念 面向切面编程横向扩展动态代理 相关术语 动态代理 spring在运行期,生成动态代理对象,不需要特殊的编译器 Spring AOP的底层就是通过JDK动态代理或者CGLIb动态代理技术为目标Bean执行横向织入 目标对象实现了接口,spring使用JDK的ja…

Redis 集群 (cluster)

是什么 官网:Redis cluster specification | Redis 由于数据量过大,单个Master复制集难以承担,因此需要对多个复制集进行集群,形成水平扩展每个复制集只负责存储整个数据集的一部分,这就是Redis的集群,其作…

【机器学习】西瓜书习题3.5Python编程实现线性判别分析,并给出西瓜数据集 3.0α上的结果

参考代码 结合自己的理解,添加注释。 代码 导入相关的库 import numpy as np import pandas as pd import matplotlib from matplotlib import pyplot as plt导入数据,进行数据处理和特征工程 得到数据集 D { ( x i , y i ) } i 1 m , y i ∈ { 0 ,…

VR 变电站事故追忆反演——正泰电力携手图扑

VR(Virtual Reality,虚拟现实)技术作为近年来快速发展的一项新技术,具有广泛的应用前景,支持融合人工智能、机器学习、大数据等技术,实现更加智能化、个性化的应用。在电力能源领域,VR 技术在高性能计算机和专有设备支…

【机器学习】处理样本不平衡的问题

文章目录 样本不均衡的概念及影响样本不均衡的解决方法样本层面欠采样 (undersampling)过采样数据增强 损失函数层面模型层面采样集成学习 决策及评估指标 样本不均衡的概念及影响 机器学习中,样本不均衡问题经常遇到,比如在金融…

uniapp运行项目到iOS基座

2022年9月,因收到苹果公司警告,目前开发者已无法在iOS真机设备使用未签名的标准基座,所以现在要运行到 IOS ,也需要进行签名。 Windows系统,HBuilderX 3.6.20以下版本,无法像MacOSX那样对标准基座进行签名…

选读SQL经典实例笔记16_逻辑否定

1. 示例数据 1.1. student insert into student values (1,AARON,20) insert into student values (2,CHUCK,21) insert into student values (3,DOUG,20) insert into student values (4,MAGGIE,19) insert into student values (5,STEVE,22) insert into student values (6…

伯俊ERP和金蝶云星空单据接口对接

伯俊ERP和金蝶云星空单据接口对接 对接源平台:金蝶云星空 金蝶K/3Cloud在总结百万家客户管理最佳实践的基础上,提供了标准的管理模式;通过标准的业务架构:多会计准则、多币别、多地点、多组织、多税制应用框架等,有效支持企业的运…

jenkins gitlab多分支构建发布

内容背景介绍 这个是新手教程,普及概念为主 公司现在还使用单分支发布测试环境和生产,多人协同开发同一个项目导致测试环境占用等待等情况 测试环境占用等待问题 测试环境代码直接合并到 master,容易导致误发布到生产的情况 避免多版本同时发布测试不完善的情况出现 中间件…

以CS32F031为例浅说国产32位MCU的内核处理器

芯片内核又称CPU内核,它是CPU中间的核心芯片,是CPU最重要的组成部分。由单晶硅制成,CPU所有的计算、接受/存储命令、处理数据都由核心执行。各种CPU核心都具有固定的逻辑结构,一级缓存、二级缓存、执行单元、指令级单元和总线接口…

django bootstrap html实现左右布局,带折叠按钮,左侧可折叠隐藏

一、实现的效果 在django项目中,需要使用bootstrap 实现一个左右分布的布局,左侧区域可以折叠隐藏起来,使得右侧的显示区域变大。(为了区分区域,左右加了配色,不好看的修改颜色即可) 点击折叠按钮,左侧区域隐藏,右侧区域铺满: 二、实现思路 1、使用col-md属性,让左…

改进的智能优化算法定性分析:收敛行为分析(Analysis of the convergence behavior )

目录 一、智能优化算法改进收敛行为分析运行结果 二、收敛性分析 三、GWO1在F1收敛性运行结果 四、改进灰狼算法GWO1 五、代码获取 一、智能优化算法改进收敛行为分析运行结果 本文以改进的灰狼算法 GWO1 为例,在 CEC2005 测试函数上进行定性分析实验。 F1:…

【前端知识】React 基础巩固(三十六)——RTK中的异步操作

React 基础巩固(三十六)——RTK中的异步操作 一、RTK中使用异步操作 引入RTK中的createAsyncThunk,在extraReducers中监听执行状态 import { createSlice, createAsyncThunk } from "reduxjs/toolkit"; import axios from "axios";export cons…

代理模式——对象的间接访问

1、简介 1.1、概述 由于某些原因,客户端不想或不能直接访问某个对象,此时可以通过一个被称为“代理”的第三者来实现间接访问,该方案对应的设计模式被称为代理模式。 代理模式是一种应用很广泛的结构型设计模式,而且变化很多。…

Elasticsearch 全文检索 分词检索-Elasticsearch文章四

文章目录 官方文档地址refercence文档全文搜索体系match简单查询match 多词/分词单字段分词match多个词的逻辑控制match的匹配精度match_pharse_prefix分词前缀方式match_bool_prefixmulti_match多字段匹配 query string类型Interval类型DSL查询之Term详解聚合查询之Bucket聚合…

图论-简明导读

计算机图论是计算机科学中的一个重要分支,它主要研究图的性质和结构,以及如何在计算机上有效地存储、处理和操作这些图。本文将总结计算机图论的核心知识点。 一、基本概念 计算机图论中的基本概念包括图、节点、边等。图是由节点和边构成的数据结构&am…

P3373 【模板】线段树 2

题目 思路 作为线段树模板题&#xff0c;这题主要考查了对lazytag以及先乘后加的使用&#xff0c; 线段树详解 因为是模板&#xff0c;所以这里证明略 代码 #include<bits/stdc.h> using namespace std; #define int long long const int maxn1e55; int n,m,p; int a[…

汽车后视镜反射率测定仪

汽车后视镜位于汽车头部的左右两侧&#xff0c;顶部以及汽车内部的前方。汽车后视镜反映汽车正后方视野、两侧视野和汽车前端区域视野&#xff0c;以便驾驶员可以间接看清楚这些位置的情况&#xff0c;它起着“第二只眼睛”的作用&#xff0c;扩大了驾驶者的视野范围&#xff0…

华为数通HCIA-ARP(地址解析协议)详细解析

地址解析协议 (ARP) ARP &#xff08;Address Resolution Protocol&#xff09;地址解析协议&#xff1a; 根据已知的IP地址解析获得其对应的MAC地址。 ARP&#xff08;Address Resolution Protocol&#xff0c;地址解析协议&#xff09;是根据IP地址获取数据链路层地址的一个…

【Ubuntu 18.04 搭建 DHCP 服务】

参考Ubuntu官方文档&#xff1a;https://ubuntu.com/server/docs/how-to-install-and-configure-isc-dhcp-server dhcpd.conf 手册页 配置&#xff1a;https://maas.io/docs/about-dhcp 实验环境规划 Ubuntu 18.04&#xff08;172.16.65.128/24&#xff09;dhcp服务端Ubuntu…