单片机外部晶振故障后自动切换内部晶振——以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.rhkb.cn/news/83367.html

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

相关文章

ArcGIS Pro实践技术应用——暨基础入门、制图、空间分析、影像分析、三维建模、空间统计分析与建模、python融合、案例应用全流程科研能力提升

查看原文>>>ArcGIS Pro实践技术应用——暨基础入门、制图、空间分析、影像分析、三维建模、空间统计分析与建模、python融合能力 本文将利用ArcGIS Pro 将您的 GIS 工作组织到工程中,您可以使用 ArcGIS Pro 映射 2D 和 3D 数据。借助 ArcGIS Pro&#xff…

float 属性的作用是什么?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ float 是什么?⭐ float 属性值⭐ 使用 float 的注意事项:⭐ 写在最后 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门…

力扣hot100刷题记录

二刷hot100&#xff0c;坚持每天打卡&#xff01;&#xff01;&#xff01; 1. 两数之和 // 先求差&#xff0c;再查哈希表 public int[] twoSum(int[] nums, int target) {Map<Integer,Integer> map new HashMap<>();for(int i 0;i<nums.length;i){int key …

51单片机(普中HC6800-EM3 V3.0)实验例程软件分析 实验四 蜂鸣器

目录 前言 一、原理图及知识点介绍 1.1、蜂鸣器原理图&#xff1a; 二、代码分析 前言 第一个实验:51单片机&#xff08;普中HC6800-EM3 V3.0&#xff09;实验例程软件分析 实验一 点亮第一个LED_ManGo CHEN的博客-CSDN博客 第二个实验:51单片机&#xff08;普中HC6800-EM…

《2023年中国企业数字化转型发展白皮书》发布

导读 本报告主要采用市场调查、行业深度访谈、桌面研究等方法&#xff0c;并使用艾媒咨询旗下各大数据计算系统和相关计算模型。 对部分相关的公开信息进行筛选&#xff0c;通过对行业专家、相关企业与网民进行深度访谈&#xff0c;了解相关行业主要情况&#xff0c;获得相应…

解决Qt的列表加载大量数据卡顿的问题

问题概述 本人在使用QListView插入大量数据时&#xff0c;界面卡顿十分严重。数据量大概只有上千左右&#xff0c;但是每个Item的内容比较多。当数据不停地插入一段时间后&#xff0c;卡顿到鼠标的移动都有点困难。 解决思路 QListView是典型的MVC思想的产物。界面呈现出来的数…

创建型模式-单例模式

文章目录 一、创建型模式1. 单例设计模式1.1 单例模式的结构1.2 单例模式的实现&#xff08;1&#xff09;饿汉式-方式1&#xff08;静态变量方式&#xff09;&#xff08;2&#xff09;饿汉式-方式2&#xff08;静态代码块方式&#xff09;&#xff08;3&#xff09;懒汉式-方…

vue+element 下载压缩包和导出

export function goodsInspectionReportDwnloadZip (params) {return axios({url: "/warehouse-entry-server/v1/goodsInspectionReport/downloadZip",method: "get",params,responseType: "blob"}) } //下载handleDownloadFile() {if (!this.$r…

AMBA总线(AHB)

1.AMBA AMBA(Advanced Microcontroller Bus Architecture)高级微控制器总线结构&#xff0c;定义了用于设计高性能嵌入式微控制器的片上通信标准。   AMBA规范中定义了三种不同的总线&#xff1a;   &#xff08;1&#xff09;高级高性能总线&#xff08;Advanced High-per…

基于nodejs+vue+uniapp微信小程序的短视频分享系统

开发语言 node.js 框架&#xff1a;Express 前端:Vue.js 数据库&#xff1a;mysql 数据库工具&#xff1a;Navicat 开发软件&#xff1a;VScode 3.1小程序端 用户注册页面&#xff0c;输入用户的个人信息点击注册即可。 注册完成后会返回到登录页面&#xff0c;用户输入自己注…

无人车沿着指定线路自动驾驶与远程控制的实践应用

有了前面颜色识别跟踪的基础之后&#xff0c;我们就可以设定颜色路径&#xff0c;让无人车沿着指定线路做自动驾驶了&#xff0c;视频&#xff1a;PID控制无人车自动驾驶 有了前几章的知识铺垫&#xff0c;就比较简单了&#xff0c;也是属于颜色识别的一种应用&#xff0c;主要…

神码ai火车头伪原创设置【php源码】

大家好&#xff0c;给大家分享一下python编写学生信息管理系统的学生信息保存在哪里&#xff0c;很多人还不知道这一点。下面详细解释一下。现在让我们来看看&#xff01; 火车头采集ai伪原创插件截图&#xff1a; 要求描述&#xff1a; 学生的信息包括&#xff1a;学号&#…

蓝牙资讯|苹果发布AirPods最新开发者固件,最新功能引人关注

苹果面向 AirPods 耳机发布了开发者固件更新 AirPods 6.0 Beta 3&#xff0c;最新内部版本号为 6A5289c。 本次更新适用于 AirPods 3、AirPods Pro 2 和 AirPods Max&#xff0c;不过并非所有 AirPods 耳机都获得了更新。包含5 种新功能 / 新特性&#xff1a; 自适应音频&am…

MySQL5.7 与 MariaDB10.1 审计插件兼容性验证

这是一篇关于发现 MariaDB 审计插件导致 MySQL 发生 crash 后&#xff0c;展开适配验证并进行故障处理的文章。 作者&#xff1a;官永强 爱可生DBA 团队成员&#xff0c;擅长 MySQL 运维方面的技能。热爱学习新知识&#xff0c;亦是个爱打游戏的宅男。 本文来源&#xff1a;原创…

flask使用cookie (设置cookie与查看cookie内容)

1.flask包cookie的使用 设置cookie app.route(/set_cookie) def set_cookie():resp make_response(Setting cookie)resp.set_cookie(username, John)return resp查看cookie: app.route(/get_cookie) def get_cookie():username request.cookies.get(username)return Welco…

Elasticsearch之kibana相关命令

1.中文分词器相关命令 2.拼音分词器相关命令

一个月学通Python(三十四):使用Selenium模拟人工操作及获取网页内容

专栏介绍 结合自身经验和内部资料总结的Python教程&#xff0c;每天3-5章&#xff0c;最短1个月就能全方位的完成Python的学习并进行实战开发&#xff0c;学完了定能成为大佬&#xff01;加油吧&#xff01;卷起来&#xff01; 全部文章请访问专栏&#xff1a;《Python全栈教…

React 核心开发者 Dan Abramov 宣布从 Meta 离职

导读React.js 核心开发者、Redux 作者 Dan Abramov 在社交平台发文宣布&#xff0c;将辞去在 Meta 的职务&#xff1a; “我感到苦乐参半&#xff0c;几周后我就要辞去 Meta 的工作了。在 Meta 的 React 组织工作是我的荣幸。感谢我过去和现在的同事接纳我&#xff0c;容忍我犯…

暗黑版GPT流窜暗网 降低犯罪门槛

随着AIGC应用的普及&#xff0c;不法分子利用AI技术犯罪的手段越来越高明&#xff0c;欺骗、敲诈、勒索也开始与人工智能沾边。 近期&#xff0c;专为网络犯罪设计的“暗黑版GPT”持续浮出水面&#xff0c;它们不仅没有任何道德界限&#xff0c;更没有使用门槛&#xff0c;没有…

接口幂等性实现方式

优质博文&#xff1a;IT-BLOG-CN 幂等 操作的特点是一次和多次请求某一个资源对于资源本身应该具有同样的结果&#xff08;网络超时等问题除外&#xff09;。幂等函数或幂等方法是指可以使用相同参数重复执行&#xff0c;并能获得相同结果的函数。这些函数不会影响系统状态&am…