【嵌入式系统】STM32时钟系统+时钟配置函数解析

简介: 【嵌入式系统】STM32时钟系统+时钟配置函数解析

嵌入式系统】STM32时钟系统+时钟配置函数解析

1、时钟系统

时钟系统为整个硬件系统的各个模块提供时钟信号。时钟是整个数字电路的驱动之源,所有数字部件的运行都依赖时钟信号的输入才得以向前推进。

由于系统复杂性,各硬件模块可能对时钟信号有不同要求,因此在系统中应按需分别提供时钟信号。这些时钟信号或者来自不同振荡器,或者是从一个主振荡器开始,经过多次的倍频、分频、锁相环等电路而生成的独立时钟信号。不同时钟信号而非单一时钟的设计还有助于实现系统的低功耗:一些低速外设可以使用功耗更低的低速时钟,部分硬件没有使用时也可除能时钟位达到关闭的效果。

2、时钟树

20200617085303870.jpg

20200617085421768.jpg

在实际应用时,为避免因为外部时钟源失效造成MCU内部紊乱导致无可挽回的损失,引入CSS来监控外部时钟源,这是STM32作为一个可靠系统的必要条件。当外部时钟源出现故障失效时,CSS会主动切断外部时钟并开启内部时钟作为替代,同时产生一个时钟安全中断。此中断属于NMI,将不断执行直到CSS中断挂起位被清除。

3、时钟配置函数解析

①时钟配置常用寄存器

用结构体复位与时钟控制**(RCC, Reset and Clock Control)**来表示。

typedef struct
{
  __IO uint32_t CR;               //HSI、HSE、CSS、PLL的使能和就绪标志位
  __IO uint32_t CFGR;            //PLL、USB等时钟源的选择和分频系数的设置
  __IO uint32_t CIR;              //清除/使能时钟就绪中断
  __IO uint32_t APB2RSTR;       //APB2上的外设复位寄存器
  __IO uint32_t APB1RSTR;       // APB1上的外设复位寄存器
  __IO uint32_t AHBENR;         //DMA、CRC等模块时钟使能
  __IO uint32_t APB2ENR;        //APB2外设时钟使能
  __IO uint32_t APB1ENR;        //APB1外设时钟使能
  __IO uint32_t BDCR;           //备份域控制寄存器
  __IO uint32_t CSR;             //控制状态寄存器
} RCC_TypeDef;

要使用某个模块或外设时,务必保证其驱动时钟被使能。

②系统初始化函数(战舰版)void SystemInit(void)

下面仅截取SystemInit()中与RCC配置相关,且采用STM32F10X_HD预编译头的代码

void SystemInit (void)
{
  /* Reset the RCC clock configuration to the default reset state(for debug purpose) */
  /* Set HSION bit */
  RCC->CR |= (uint32_t)0x00000001;
  /* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
  RCC->CFGR &= (uint32_t)0xF8FF0000;
  /* Reset HSEON, CSSON and PLLON bits */
  RCC->CR &= (uint32_t)0xFEF6FFFF;
  /* Reset HSEBYP bit */
  RCC->CR &= (uint32_t)0xFFFBFFFF;
  /* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
  RCC->CFGR &= (uint32_t)0xFF80FFFF;
  /* Disable all interrupts and clear pending bits  */
  RCC->CIR = 0x009F0000;
#endif /* STM32F10X_CL */
  /* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
  /* Configure the Flash Latency cycles and enable prefetch buffer */
  SetSysClock();
}

这个初始化函数对时钟系统的作用是:初始化RCC中的相关寄存器,并配置系统时钟。


对于RCC中寄存器的配置可以完全对照手册与注释进行解读,例如:

RCC->CFGR &= (uint32_t)0xF8FF0000

值得注意,位配置的 |= 运算一般是置位Set;&=运算一般是复位Reset。根据0xF8FF0000位向量知,此条指令是将CFGR中的[15:0]以及[26:24]清空。如图3所示为参考手册给出的CFGR位模式,清零位对应的是SW, HPRE, PPRE1, PPRE2, ADCPRE和MCO。


image.png

下面考察系统时钟设置函数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
  SetSysClockTo72();
#endif
}

系统时钟频率的设置是通过条件编译进行的,若宏定义了SYSCLK_FREQ_72MHz,则SetSysClock()中就只编译执行SetSysClockTo72()

为了研究更底层的封装,再考察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)
  {
    /* HCLK = SYSCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
    /* PCLK2 = HCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
    /* PCLK1 = HCLK/2 */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;
    /* 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);
    /* 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 */
  }
}

此函数首先使能了时钟源HSE

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

其中#define RCC_CR_HSEON ((uint32_t)0x00010000)

#define RCC_CR_HSERDY ((uint32_t)0x00020000)

因此这条指令作用是将RCC->CR的Bit16置1,结合图参考手册中图4可以确认,这条指令确实使能了HSE


image.png

image.png

接下来很重要的一步操作是等待使能成功,例如上述执行使能HSE指令后,硬件开始根据指令执行置位操作,这里存在软硬件时间差 ∆ t ∆t ∆t,在 ∆ t ∆t ∆t内HSE仍然是除能状态,因此等待使能成功的目的就是起到对 ∆ t ∆t ∆t的过渡作用,防止硬件误动作。

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

这个while循环首先用HSEStatus获取HSE状态:若HSERDY=1即HSE已经使能,则HSEStatus=1;否则HSEStatus=0。


确认使能了HSE后,便开始配置一系列的时钟,这里将HCLK、PCLK2、PLLCLK配置为72MHz,将PCLK1配置为36MHz,例如:

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

①通过&=进行复位。出于安全性考虑,置位操作中可能用到的位先清0,以防置位时产生进位引发错误。可以看到,这里将时钟源选择位、预分频位和倍频位清0,即此时PLL处于无时钟源、不分频、不倍频的状态。

②通过|=进行置位。RCC_CFGR_PLLSRC_HSE即选择HSE作为PLL时钟源,HSE一般接8MHz晶振;RCC_CFGR_PLLMULL9选择倍频系数为9,因此PLLCLK为8×9=72MHz的时钟。


至此,SystemInit()的原理讲解完毕。事实上,用户也可根据需要,自行选择相应寄存器来设计并封装初始化函数


目录
相关文章
|
14天前
|
数据采集 消息中间件 监控
Flume数据采集系统设计与配置实战:面试经验与必备知识点解析
【4月更文挑战第9天】本文深入探讨Apache Flume的数据采集系统设计,涵盖Flume Agent、Source、Channel、Sink的核心概念及其配置实战。通过实例展示了文件日志收集、网络数据接收、命令行实时数据捕获等场景。此外,还讨论了Flume与同类工具的对比、实际项目挑战及解决方案,以及未来发展趋势。提供配置示例帮助理解Flume在数据集成、日志收集中的应用,为面试准备提供扎实的理论与实践支持。
25 1
|
18天前
|
存储 缓存 NoSQL
深入解析Redis:一种快速、高效的键值存储系统
**Redis** 是一款高性能的键值存储系统,以其内存数据、高效数据结构、持久化机制和丰富的功能在现代应用中占有一席之地。支持字符串、哈希、列表、集合和有序集合等多种数据结构,适用于缓存、计数、分布式锁和消息队列等场景。安装Redis涉及下载、编译和配置`redis.conf`。基本操作包括键值对的设置与获取,以及哈希、列表、集合和有序集合的操作。高级特性涵盖发布/订阅、事务处理和Lua脚本。优化策略包括选择合适数据结构、配置缓存和使用Pipeline。注意安全、监控和备份策略,以确保系统稳定和数据安全。
222 1
|
1天前
|
分布式计算 网络协议 Hadoop
|
3天前
|
JavaScript IDE 编译器
TypeScript中模块路径解析与配置:深入剖析与最佳实践
【4月更文挑战第23天】本文深入探讨了TypeScript中模块路径解析的原理与配置优化,包括相对路径、Node.js模块解析和路径别名。通过配置`baseUrl`、`paths`、`rootDirs`以及避免裸模块名,可以提升开发效率和代码质量。建议使用路径别名增强代码可读性,保持路径结构一致性,并利用IDE插件辅助开发。正确配置能有效降低维护成本,构建高效可维护的代码库。
|
4天前
|
JSON Java Maven
Javaweb之SpringBootWeb案例之自动配置以及常见方案的详细解析
Javaweb之SpringBootWeb案例之自动配置以及常见方案的详细解析
7 0
Javaweb之SpringBootWeb案例之自动配置以及常见方案的详细解析
|
9天前
|
域名解析 网络协议 Linux
TCP/IP协议及配置、IP地址、子网掩码、网关地址、DNS与DHCP介绍
TCP/IP协议及配置、IP地址、子网掩码、网关地址、DNS与DHCP介绍
|
15天前
|
分布式计算 资源调度 监控
Hadoop生态系统深度剖析:面试经验与必备知识点解析
本文深入探讨了Hadoop生态系统的面试重点,涵盖Hadoop架构、HDFS、YARN和MapReduce。了解Hadoop的主从架构、HDFS的读写流程及高级特性,YARN的资源管理与调度,以及MapReduce编程模型。通过代码示例,如HDFS文件操作和WordCount程序,帮助读者巩固理解。此外,文章强调在面试中应结合个人经验、行业动态和技术进展展示技术实力。
|
15天前
|
监控 测试技术 Android开发
移动应用与系统:开发与操作系统的深度解析
【4月更文挑战第11天】在这篇文章中,我们将深入探讨移动应用的开发过程,以及移动操作系统如何影响这些应用的性能和功能。我们将详细分析移动应用开发的关键步骤,包括需求分析、设计、编码、测试和维护。同时,我们也将探讨移动操作系统,如Android和iOS,如何为应用开发提供支持,并影响其性能。
|
23天前
|
数据挖掘
深入解析ERP系统的人力资源管理模块
深入解析ERP系统的人力资源管理模块
24 1
|
23天前
|
监控 BI
财务智慧:全面解析ERP系统的财务管理模块
财务智慧:全面解析ERP系统的财务管理模块
21 0

推荐镜像

更多