STM32-时钟系统详解

这篇具有很好参考价值的文章主要介绍了STM32-时钟系统详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

前言

一、STM32时钟系统原理

1. 时钟系统框图

2. 时钟源讲解

3.时钟去向讲解

二、STM32时钟配置

1. 时钟配置简介

2.时钟配置寄存器介绍

3. 时钟配置总流程

三、Systick定时器及delay延时函数

1. Systick定时器

2. 相关寄存器介绍

3. 延时函数配置

总结


前言

        时钟系统之于单片机就如同与心脏脉搏之于人体,可见时钟系统的重要性可见一斑。然而STM32的时钟系统极其复杂,不像51单片机一样一个时钟系统就可以解决一切问题,这对于初学者来说很不友好,本文致力于讲解STM32时钟系统,使读者清晰了解STM32时钟背后的原理。


一、STM32时钟系统原理

1. 时钟系统框图

以下是STM32时钟系统的结构框图,摘自STM32中文参考手册。

stm32时钟,STM32,stm32,单片机,arm

可以看到,官方手册提供的时钟框图很复杂,这里我给出一个简化版的时钟框图:

stm32时钟,STM32,stm32,单片机,arm

2. 时钟源讲解

 从图中可以看到,STM32共有五个时钟源,分别是HSI、HSE、LSI、LSE和PLL ,下面分别对它们进行讲解:

        ①、HSI是高速内部时钟,RC振荡器,频率为8MHz,精度不高。
   ②、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz。
   ③、LSI是低速内部时钟,RC振荡器,频率为40kHz,提供低功耗时钟。独立看门狗的时钟源只能是 LSI ,同时LSI 还可以作为 RTC 的时钟源。
   ④、LSE是低速外部时钟,接频率为32.768kHz的石英晶体。是主要的RTC时钟源。
   ⑤、PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。

注意:高速外部时钟HSE的引脚是OSC_OUT和OSC_IN这两个引脚芯片是独立引出的,可以接外部的晶振电路,而低速外部时钟LSE的引脚OSC32_IN和OSC32_OUT两个引脚不是独立的,而是在PC14和PC15上,对应关系为OSC32_IN-->PC14 ; OSC32_OUT-->PC15

3.时钟去向讲解

        上面我们简要概括了STM32 的时钟源,那么这 5 个时钟源是怎么给各个外设以及系统提供时钟的呢?这里我们将一一讲解。这里我们使用官方手册提供的框图(图一)进行讲解,图中我们用A~E 标示我们要讲解的地方。

  • A   MCO 是 STM32 的一个时钟输出 IO(PA8) PA8),它可以选择一个时钟信号输出 可以
    选择为 PLL 输出的 2 分频、 HSI 、 HSE 、或者 系统 时钟 。这个时钟可以用来给外
    部其他系统提供时钟源。
  • B   RTC 时钟源,从图上可以看出, RTC 的时钟源可以选择 LSI LSE ,以及
    HSE 的 128 分频。
  • C   从图中可以看出 C 处 USB 的时钟是来自 PLL 时钟源。 STM32 中有一个全速 功能
    的 USB 模块 ,其串行 接口 引擎需要 一个频率为 48MHz 的时钟源。该时钟源只能
    从 PLL 输出端获取,可以选择为 1.5 分频或者 1 分频,也就是,当需要使用 USB
    模块时, PLL 必须使能,并且时钟频率配置为 48MHz 或 72MHz 。
  • D   STM32 的系统时钟 SYSCLK ,它 是供 STM32 中绝大部分部件 工作 的时
    钟源 。 系统时钟可选择为 PLL 输出、 HSI 或者 HSE 。系统时钟最大频率 为 72MHz
    当然你也可以超频,不过一般情况为了系统稳定性是没有必要冒风险去超频的。
  • E   其他所有外设。从时钟图上可以看出,其他所有外设的时钟最终来源都是 SYSCLK 。 SYSCLK 通过 AHB 分频器分频后送给各模块使用 。这些模块包括:
    ① AHB 总线、内核、内存和 DMA 使用的 HCLK 时钟。
    ② 通过 8 分频后送给 Cortex 的系统 定时器 时钟 ,也就是 systick 了 。
    ③ 直接送给 Cortex 的空闲运行时钟 FCLK 。
    ④ 送给 APB1 分频器。 APB1 分频器输出一路供 APB1 外设使用 (PCLK1 ,最大频率 36MHz)36MHz),另一路送给定时器 (Timer)2 、 3 、 4 倍频器使用。
    ⑤送给 APB2 分频器。 APB2 分频器分频输出一路供 APB2 外设使用 (PCLK2最大频率 72MHz)72MHz),另一 路送给定时器 (Timer)1 倍频器使用。

        APB1和APB2的区别: APB1上面连的是低速外设,包括电脑接口、备份接口、CAN、USB、I2C1、I2C2 、 UART2 、 UART3 等等, APB2 上面连接的是高速外设包括 UART1 、 SPI1 、 Timer1 、 ADC1 、 ADC2 、所有普通 IO 口 (PA~ 、第二功能 IO 口 等。 APB2 下面所挂的外设的时钟要比 APB1 的高 。

在以上的时钟输出中,有很多 是带使能控制的,例如 AHB 总线 时钟、内核时钟、各种 APB1
外设、 APB2 外设等等。当需要使用某模块时,记得一定要先使能对应的时钟。

二、STM32时钟配置

1. 时钟配置简介

STM32时钟系统的配置除了初始化的时候在 system_stm32f10x.c 中的 SystemInit 函数中外,其他的配置主要在 stm32f10x_rcc.c 文件中,里面有很多时钟设置函数,大家可以打开这个文件浏览一下,基本上看看函数的名称就知道这个函数的作用。在大家设置时钟的时候,一定要仔细参考STM32 的时钟图,做到心中有数。这里需要指明一下,对于系统时钟,默认情况下是SystemInit 函数的 SetSysClock() 函数中间判断的,而设置是通过宏定义设置的。我们可以看看 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
}

这段代码非常简单,就是判断系统宏定义的时钟是多少,然后设置相应值。我们系统默认宏定
义是 72MHz:

 #define SYSCLK_FREQ_72MHz  72000000

如果你要设置为36MHz ,只需要注释掉上面代码,然后加入下面代码即可

  #define SYSCLK_FREQ_36MHz  36000000

 同时还要注意的是,当我们设置好系统时钟后,可以通过变量SystemCoreClock 获取系统时钟
值,如果系统是 72M 时钟,那么 SystemCoreClock =72000000 。这是在 system_stm32f10x.c 文件中设置的:

#ifdef SYSCLK_FREQ_HSE
  uint32_t SystemCoreClock         = SYSCLK_FREQ_HSE;      
#elif defined SYSCLK_FREQ_24MHz
  uint32_t SystemCoreClock         = SYSCLK_FREQ_24MHz;        
#elif defined SYSCLK_FREQ_36MHz
#elif defined SYSCLK_FREQ_48MHz
  uint32_t SystemCoreClock         = SYSCLK_FREQ_48MHz;      
#elif defined SYSCLK_FREQ_56MHz
  uint32_t SystemCoreClock         = SYSCLK_FREQ_56MHz;        
#elif defined SYSCLK_FREQ_72MHz
  uint32_t SystemCoreClock         = SYSCLK_FREQ_72MHz;       
#else 
  uint32_t SystemCoreClock         = HSI_VALUE;      
#endif

这里总结一下SystemInit() 函数中设置的系统时钟大小:

  • SYSCLK (系统时钟 =72MHz
  • AHB 总线时钟 使用 SYSCLK) =72MHz
  • APB1 总线时钟 ( =36MHz
  • APB2 总线时钟 ( =72MHz
  • PLL 时钟 =72MHz 

2.时钟配置寄存器介绍

以上介绍了SystemInit()时钟配置函数,想必大家已经知道了STM32时钟的基本情况,如果你想要更深一步的了解底层原理,那么下面的知识可以帮你揭晓。

在STM32时钟配置的过程中使用到了RCC寄存器,它们分别是:

  • 时钟控制寄存器(RCC_CR)
  • 时钟配置 寄存器(RCC_CFGR)

首先介绍时钟控制寄存器,先上一张官方手册的寄存器说明:

stm32时钟,STM32,stm32,单片机,arm

 stm32时钟,STM32,stm32,单片机,arm

stm32时钟,STM32,stm32,单片机,arm

这个是时钟控制寄存器,看似很复杂,实际上并没有使用到寄存器所有的位,而是红框标出的部分位。下表是RCC另一个重要的寄存器:时钟配置寄存器(RCC_CFGR)

stm32时钟,STM32,stm32,单片机,arm

stm32时钟,STM32,stm32,单片机,armstm32时钟,STM32,stm32,单片机,arm

stm32时钟,STM32,stm32,单片机,arm

stm32时钟,STM32,stm32,单片机,arm

 和时钟控制寄存器RCC_CR相比,时钟配置寄存器RCC_CFGR要复杂一些,同样的,重点使用的位用红色框标识了出来。

3. 时钟配置总流程

在STM32单片机复位之后,首先进入startup程序:

; Reset handler
Reset_Handler   PROC
                EXPORT  Reset_Handler             [WEAK]
                IMPORT  __main
                IMPORT  SystemInit
                LDR     R0, =SystemInit
                BLX     R0               
                LDR     R0, =__main
                BX      R0
                ENDP

可以看出,在进入main主程序之前,先触发了SystemInit()函数,这样就可以保证不需要每次都把时钟配置程序写入main.c文件了,同样,当你想要执行自定义时钟配置程序时也可以改动这个部分。

首先我们看一下SystemInit()函数

void SystemInit (void)
{
  /* Reset the RCC clock configuration to the default reset state(for debug purpose) */
  /* Set HSION bit */
  RCC->CR |= (uint32_t)0x00000001;
    SetSysClock();
}

可以看到,SystemInit()函数的作用就是使RCC_CR寄存器最低位置1即开启内部8MHz振荡器,然后转到SetSysClock()函数中。刚刚已经讲过了,对于系统时钟,默认情况下就是SystemInit 函数的 SetSysClock() 函数中间判断的

下面是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
 
 /* If none of the define above is enabled, the HSI is used as System clock
    source (default after reset) */ 
}

由于我们把系统时钟频率通过宏定义设置为72MHz,所以这里直接进入最下面的程序即SetSysClockTo72(),这也是为什么有的程序把这一段语句直接写入主函数,原则上来说这是可以的,但是没必要,因为startup启动文件已经帮我们定义好了。

下面就是SetSysClockTo72()程序部分,也就是最关键的部分

static void SetSysClockTo72(void)
{
  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
  
  /* SYSCLK, HCLK, PCLK2 and PCLK1 配置---------------------------*/    
  /* 使能 HSE */    
  RCC->CR |= ((uint32_t)RCC_CR_HSEON);//RCC_CR寄存器第16位置1,开启HSE
 
  /* 等待HSE就绪 超时退出 */
  do
  {
    HSEStatus = RCC->CR & RCC_CR_HSERDY;  //读取RCC_CR第17位
    StartUpCounter++;  
  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

  if ((RCC->CR & RCC_CR_HSERDY) != RESET) //读取RCC_CR第17位
  {
    HSEStatus = (uint32_t)0x01;   //HSE状态写1
  }
  else
  {
    HSEStatus = (uint32_t)0x00;   //HSE状态写0
  }  

  if (HSEStatus == (uint32_t)0x01) //FLASH寄存器部分可以不用管
  {
    /* 使能 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;  //RCC_CFGR寄存器第4~7位写0,即AHB不分频,即HCLK使用72MHz时钟
       
    /* PCLK2 = HCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1; //RCC_CFGR寄存器第11~13位写0,APB2不分频,即PCLK2使用72MHz时钟
    
    /* PCLK1 = HCLK/2 */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2; //RCC_CFGR寄存器第8~10位写100,APB1二分频,即PCLK1使用36MHz时钟

#else    
    /*  PLL 配置: PLLCLK = HSE * 9 = 72 MHz */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
                                        RCC_CFGR_PLLMULL)); //RCC_CFGR寄存器的16~21位清零
    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);//RCC_CFGR寄存器第16位置1,使HSE作为PLL输入时钟;17位置0不变,使HSE作为PLL输入时钟时不分频。RCC_CFGR寄存器第18~21位置0111,配置位PLL 9倍频输出给PLLCLK,即PLLCLK为72MHz
#endif /* STM32F10X_CL */

    /* 使能 PLL */
    RCC->CR |= RCC_CR_PLLON; //RCC->CR寄存器第24位置1,使能PLL

    /* 等待PLL就绪 */
    while((RCC->CR & RCC_CR_PLLRDY) == 0)  //读取PLL时钟就绪标志位(第25位)
    {
    }
    
    /* 选择PLL作为系统时钟源 */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW)); //RCC->CFGR寄存器第0~1位清零
    RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    //RCC->CFGR寄存器第0~1位(SW位)置10,即PLL输出作为系统时钟

    /* 等待PLL被用作系统时钟源就绪 */
    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08) //等待RCC->CFGR寄存器第2~3位(SWS位)为10即就绪
    {
    }
  }
  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 */
  }
}
#endif

这里把所有的备注都写在了程序中,希望读者仔细查看并对照寄存器功能表理解。STM32的寄存器的每一个位都做了宏定义,这样直接查找头文件即可,非常方便。

至此STM32系统时钟的讲解就已经结束了,我们以大容量芯片72MHz的系统时钟为例来进行讲解,你也可以根据实际情况自己配置,原理都是一样的。下面的内容就是灵活使用STM32的系统时钟来实现演示功能了。

三、Systick定时器及delay延时函数

1. Systick定时器

前面讲完了STM32系统时钟的基本配置,下面就是对延时函数进行介绍了,延时函数使用到了STM32的SysTick定时器,这是一个很简单的定时器,对于CM3和CM4内核芯片都具备。

SysTick定时器全称是System tick timer即系统滴答定时器,常用来做延时或者系统的心跳时钟。这样可以节省MCU资源,不用浪费一个定时器。比如在UCOS操作系统中分时复用,需要一个最小的时间戳,一般在STM32+UCOS系统中,都采用SysTick做UCOS心跳时钟。

  • SysTick定时器就是系统滴答定时器,一个24位倒计数定时器,计数到0时将从RELOAD寄存器中自动重装载定时器初值。只要不把它在SysTick控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式也能工作。
  • SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常(异常号为15)。
  • SysTick中断的优先级也可以设置

2. 相关寄存器介绍

SysTick定时器一共有四个相关寄存器:

  • CTRL              SysTick 控制和状态寄存器 
  • LOAD              SysTick 自动重装载除值寄存器   
  • VAL                 SysTick 当前值寄存器 
  • CALIB             SysTick 校准值寄存器      

下面分别介绍这四个寄存器

stm32时钟,STM32,stm32,单片机,arm

stm32时钟,STM32,stm32,单片机,arm

stm32时钟,STM32,stm32,单片机,arm

stm32时钟,STM32,stm32,单片机,arm

 这是四个相关的寄存器,实际上我们一般只用到前三个。

3. 延时函数配置

对于STM32,外部时钟源是 HCLK(AHB总线时钟)的1/8 ,内核时钟是 HCLK时钟。配置函数:SysTick_CLKSourceConfig();这个函数在misc.c文件中,这里把它粘出来:

void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)
{
  /* Check the parameters */
  assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));
  if (SysTick_CLKSource == SysTick_CLKSource_HCLK)
  {
    SysTick->CTRL |= SysTick_CLKSource_HCLK;
  }
  else
  {
    SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;
  }
}

根据入口参数有效性可知,时钟源可以选择HCLK或者HCLK的8分频。这个函数在下面的delay_init会调用一次,完成延时函数的初始化。

core_cm3.h文件中有关于初始化systick时钟和开启中断的函数,如下所示:
 

static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{ 
  if (ticks > SysTick_LOAD_RELOAD_Msk)  return (1);            /* Reload value impossible */
                                                               
  SysTick->LOAD  = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;      /* set reload register */
  NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);  /* set Priority for Cortex-M0 System Interrupts */
  SysTick->VAL   = 0;                                          /* Load the SysTick Counter Value */
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk | 
                   SysTick_CTRL_TICKINT_Msk   | 
                   SysTick_CTRL_ENABLE_Msk;                    /* Enable SysTick IRQ and SysTick Timer */
  return (0);                                                  /* Function successful */
}

需要注意的是第5行把重装载寄存器的值减1的目的是考虑到执行装在动作也需要一个时钟周期。

下面是用中断的方式来实现延时:

static __IO uint32_t TimingDelay;
void Delay(__IO uint32_t nTime)
{ 
   TimingDelay = nTime;
   while(TimingDelay != 0);
}
void SysTick_Handler(void)
{
    if (TimingDelay != 0x00) 
     { 
       TimingDelay--;
     }
}
 int main(void)
 {  …
    if (SysTick_Config(SystemCoreClock / 1000)) //systick时钟为HCLK,中断时间间隔1ms
     {
     while (1);
     }
    while(1)
     { Delay(200);//2ms
     … 
     }
}

程序中,SysTick_Config(SystemCoreClock / 1000)的目的是确保SysTick定时器每次发生中断的时间间隔都是1ms(因为使用系统时钟的时钟频率为72000000,SystemCoreClock / 1000等于72000,也就是给重装载寄存器值赋72000,这样计72000正好是1ms)

一般实际应用中我们不使用通过中断来驱动延时的方法,因为这样会多一个中断线路,我们采用的 是单独的延时函数,下面就是延时函数文件:

#include "delay.h"
// 	 
//如果需要使用OS,则包括下面的头文件即可.
#if SYSTEM_SUPPORT_OS
#include "includes.h"					//ucos 使用	  
#endif
//	  

static u8  fac_us=0;							//us延时倍乘数			   
static u16 fac_ms=0;							//ms延时倍乘数,在ucos下,代表每个节拍的ms数
	
	
#if SYSTEM_SUPPORT_OS							//如果SYSTEM_SUPPORT_OS定义了,说明要支持OS了(不限于UCOS).
//当delay_us/delay_ms需要支持OS的时候需要三个与OS相关的宏定义和函数来支持
//首先是3个宏定义:
//    delay_osrunning:用于表示OS当前是否正在运行,以决定是否可以使用相关函数
//delay_ostickspersec:用于表示OS设定的时钟节拍,delay_init将根据这个参数来初始哈systick
// delay_osintnesting:用于表示OS中断嵌套级别,因为中断里面不可以调度,delay_ms使用该参数来决定如何运行
//然后是3个函数:
//  delay_osschedlock:用于锁定OS任务调度,禁止调度
//delay_osschedunlock:用于解锁OS任务调度,重新开启调度
//    delay_ostimedly:用于OS延时,可以引起任务调度.

//本例程仅作UCOSII和UCOSIII的支持,其他OS,请自行参考着移植
//支持UCOSII
#ifdef 	OS_CRITICAL_METHOD						//OS_CRITICAL_METHOD定义了,说明要支持UCOSII				
#define delay_osrunning		OSRunning			//OS是否运行标记,0,不运行;1,在运行
#define delay_ostickspersec	OS_TICKS_PER_SEC	//OS时钟节拍,即每秒调度次数
#define delay_osintnesting 	OSIntNesting		//中断嵌套级别,即中断嵌套次数
#endif

//支持UCOSIII
#ifdef 	CPU_CFG_CRITICAL_METHOD					//CPU_CFG_CRITICAL_METHOD定义了,说明要支持UCOSIII	
#define delay_osrunning		OSRunning			//OS是否运行标记,0,不运行;1,在运行
#define delay_ostickspersec	OSCfg_TickRate_Hz	//OS时钟节拍,即每秒调度次数
#define delay_osintnesting 	OSIntNestingCtr		//中断嵌套级别,即中断嵌套次数
#endif


//us级延时时,关闭任务调度(防止打断us级延迟)
void delay_osschedlock(void)
{
#ifdef CPU_CFG_CRITICAL_METHOD   				//使用UCOSIII
	OS_ERR err; 
	OSSchedLock(&err);							//UCOSIII的方式,禁止调度,防止打断us延时
#else											//否则UCOSII
	OSSchedLock();								//UCOSII的方式,禁止调度,防止打断us延时
#endif
}

//us级延时时,恢复任务调度
void delay_osschedunlock(void)
{	
#ifdef CPU_CFG_CRITICAL_METHOD   				//使用UCOSIII
	OS_ERR err; 
	OSSchedUnlock(&err);						//UCOSIII的方式,恢复调度
#else											//否则UCOSII
	OSSchedUnlock();							//UCOSII的方式,恢复调度
#endif
}

//调用OS自带的延时函数延时
//ticks:延时的节拍数
void delay_ostimedly(u32 ticks)
{
#ifdef CPU_CFG_CRITICAL_METHOD
	OS_ERR err; 
	OSTimeDly(ticks,OS_OPT_TIME_PERIODIC,&err);	//UCOSIII延时采用周期模式
#else
	OSTimeDly(ticks);							//UCOSII延时
#endif 
}
 
//systick中断服务函数,使用ucos时用到
void SysTick_Handler(void)
{	
	if(delay_osrunning==1)						//OS开始跑了,才执行正常的调度处理
	{
		OSIntEnter();							//进入中断
		OSTimeTick();       					//调用ucos的时钟服务程序               
		OSIntExit();       	 					//触发任务切换软中断
	}
}
#endif

			   
//初始化延迟函数
//当使用OS的时候,此函数会初始化OS的时钟节拍
//SYSTICK的时钟固定为HCLK时钟的1/8
//SYSCLK:系统时钟
void delay_init()
{
#if SYSTEM_SUPPORT_OS  							//如果需要支持OS.
	u32 reload;
#endif
	SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);	//选择外部时钟  HCLK/8
	fac_us=SystemCoreClock/8000000;				//为系统时钟的1/8  
#if SYSTEM_SUPPORT_OS  							//如果需要支持OS.
	reload=SystemCoreClock/8000000;				//每秒钟的计数次数 单位为M  
	reload*=1000000/delay_ostickspersec;		//根据delay_ostickspersec设定溢出时间
												//reload为24位寄存器,最大值:16777216,在72M下,约合1.86s左右	
	fac_ms=1000/delay_ostickspersec;			//代表OS可以延时的最少单位	   

	SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;   	//开启SYSTICK中断
	SysTick->LOAD=reload; 						//每1/delay_ostickspersec秒中断一次	
	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk;   	//开启SYSTICK    

#else
	fac_ms=(u16)fac_us*1000;					//非OS下,代表每个ms需要的systick时钟数   
#endif
}								    

#if SYSTEM_SUPPORT_OS  							//如果需要支持OS.
//延时nus
//nus为要延时的us数.		    								   
void delay_us(u32 nus)
{		
	u32 ticks;
	u32 told,tnow,tcnt=0;
	u32 reload=SysTick->LOAD;					//LOAD的值	    	 
	ticks=nus*fac_us; 							//需要的节拍数	  		 
	tcnt=0;
	delay_osschedlock();						//阻止OS调度,防止打断us延时
	told=SysTick->VAL;        					//刚进入时的计数器值
	while(1)
	{
		tnow=SysTick->VAL;	
		if(tnow!=told)
		{	    
			if(tnow<told)tcnt+=told-tnow;		//这里注意一下SYSTICK是一个递减的计数器就可以了.
			else tcnt+=reload-tnow+told;	    
			told=tnow;
			if(tcnt>=ticks)break;				//时间超过/等于要延迟的时间,则退出.
		}  
	};
	delay_osschedunlock();						//恢复OS调度									    
}
//延时nms
//nms:要延时的ms数
void delay_ms(u16 nms)
{	
	if(delay_osrunning&&delay_osintnesting==0)	//如果OS已经在跑了,并且不是在中断里面(中断里面不能任务调度)	    
	{		 
		if(nms>=fac_ms)							//延时的时间大于OS的最少时间周期 
		{ 
   			delay_ostimedly(nms/fac_ms);		//OS延时
		}
		nms%=fac_ms;							//OS已经无法提供这么小的延时了,采用普通方式延时    
	}
	delay_us((u32)(nms*1000));					//普通方式延时  
}
#else //不用OS时
//延时nus
//nus为要延时的us数.		    								   
void delay_us(u32 nus)
{		
	u32 temp;	    	 
	SysTick->LOAD=nus*fac_us; 					//时间加载	  		 
	SysTick->VAL=0x00;        					//清空计数器
	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;	//开始倒数	  
	do
	{
		temp=SysTick->CTRL;
	}while((temp&0x01)&&!(temp&(1<<16)));		//等待时间到达   
	SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;	//关闭计数器
	SysTick->VAL =0X00;      					 //清空计数器	 
}
//延时nms
//注意nms的范围
//SysTick->LOAD为24位寄存器,所以,最大延时为:
//nms<=0xffffff*8*1000/SYSCLK
//SYSCLK单位为Hz,nms单位为ms
//对72M条件下,nms<=1864 
void delay_ms(u16 nms)
{	 		  	  
	u32 temp;		   
	SysTick->LOAD=(u32)nms*fac_ms;				//时间加载(SysTick->LOAD为24bit)
	SysTick->VAL =0x00;							//清空计数器
	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;	//开始倒数  
	do
	{
		temp=SysTick->CTRL;
	}while((temp&0x01)&&!(temp&(1<<16)));		//等待时间到达   
	SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;	//关闭计数器
	SysTick->VAL =0X00;       					//清空计数器	  	    
} 
#endif 

这里需要注意的是我们使用了外部时钟即系统时钟的8分频,这样确保时间足够长。另外,毫秒延时函数有最大上限,最大为1864(72MHz时钟频率)。用这种方式来延时的好处是不用中断比较简单,但是坏处就是最大延时时长有限(不到2秒),所以当需要长延时时间的时候要多写几个delay函数。


总结

        本节主要对STM32时钟系统进行讲解,我们从原理框图入手,对每一个时钟线路进行来源和去向的分析,介绍了时钟配置的相关寄存器以及STM32时钟的软件配置,最后通过SysTick定时器来编写延时函数。STM32的时钟十分复杂,希望读者能够反复阅读,内化于心,这样在以后的STM32开发以至于其它高性能SOC的学习中才能游刃有余。文章来源地址https://www.toymoban.com/news/detail-780992.html

到了这里,关于STM32-时钟系统详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 如果STM32/GD32一类的ARM单片机解除读写保护的方法

    有时候啊,使用ST-Link给STM32一类的ARM单片机下载程序的时候,发现怎么也下载不了,可能是由于芯片被写保护了。那怎么办呢?可以使用STM32 ST-LINK Utility工具解除芯片的写保护,本篇博文介绍操作步骤,文章最后有工具下载链接。 双击“STM32 ST-LINK Utility.exe”,打开软件。 软

    2024年02月09日
    浏览(53)
  • 【单片机】STM32 ARM Cortex-M0 微控制器特性概述 双排 TSSOP封装好手工焊接的STM32芯片,双排

    STM32F070CB STM32F070RB STM32F070C6 STM32F070F6 在嵌入式系统和物联网应用中,ARM Cortex-M0 微控制器以其强大的性能和丰富的功能而备受瞩目。本文将介绍该微控制器的主要特性,使读者更好地了解其适用范围和潜在应用。 ARM® 32位 Cortex®-M0 CPU 频率高达 48 MHz Flash 存储器 32 到 128 Kbytes

    2024年01月18日
    浏览(48)
  • 【32单片机学习】(11)STM32启动过程详解

    目录 前言 一、系统架构 二、启动配置 三、启动流程 1.首先复位MCU,获取栈顶指针MSP和PC指针的内容 2.根据PC的值找到复位中断处理函数Reset_Handler 1.进入中断处理函数Reset_Handler 2.进入SystemInit函数 3._main函数 3.进入main函数 总结          通过查阅官方手册和对实际代码进行

    2024年02月08日
    浏览(46)
  • proteus结合keil-arm编译器构建STM32单片机项目进行仿真

        proteus是可以直接创建设计图和源码的,但是源码编译它需要借助keil-arm编译器,也就是我们安装keil-mdk之后自带的编译器。     下面给出一个完整的示例,主要是做一个LED灯闪烁的效果。     新建工程指定路径,Schematic,PCB layout都选择默认,在最后创建项目工程向导的时

    2024年02月13日
    浏览(65)
  • 单片机STM32看门狗详解(嵌入式学习)

    单片机STM32的看门狗(Watchdog)是一种硬件定时器,用于监控系统的运行状态并在出现故障或死锁时采取措施以恢复正常操作。看门狗的主要功能是定期检查系统是否正常运行,并在系统出现问题时触发复位操作。 STM32系列单片机通常配备了内置的看门狗定时器(通常称为独立

    2024年02月13日
    浏览(63)
  • 单片机 STM32启动文件详解(汇编语言解析)

    以前讲了固件库,从ST官网下载的固件库里面,有许多的启动文件(汇编语言写的.s文件) 启动文件 说明 startup_stm32f10x_ld.s Low Density 小容量 startup_stm32f10x_md.s Medium Density 中容量 startup_stm32f10x_hd.s High Density 高容量 startup_stm32f10x_xl.s Extra Large Density 超大容量 startup_stm32f10x_cl.s Con

    2023年04月25日
    浏览(63)
  • STM32单片机(六)TIM定时器 -> 第二节:TIM定时中断练习(定时器定时中断和定时器外部时钟)

    ❤️ 专栏简介:本专栏记录了从零学习单片机的过程,其中包括51单片机和STM32单片机两部分;建议先学习51单片机,其是STM32等高级单片机的基础;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 :适用于想要从零基础开始学习入门单片机,且有一定C语言基础的的童鞋

    2024年02月09日
    浏览(41)
  • STM32毕设分享 - 基于单片机的智能鱼缸系统设计与实现 - 嵌入式 物联网 stm32 51单片机 智能鱼缸

    Hi,大家好,今天向大家介绍一个 单片机项目, 大家可用于 课程设计 或 毕业设计 基于单片机的智能鱼缸系统设计与实现 🔥 项目分享与指导: https://gitee.com/sinonfin/sharing 近年以来,随着我国综合实力飞速飙升,人们对物质和精神生活质量的要求也不断提升,各式各样的智能

    2024年04月11日
    浏览(62)
  • 嵌入式STM32 单片机 GPIO 的工作原理详解

    STM32的 GPIO 介绍 GPIO 是通用输入/输出端口的简称,是 STM32 可控制的引脚。GPIO 的引脚与外部硬件设备连接,可实现与外部通讯、控制外部硬件或者采集外部硬件数据的功能。 以 STM32F103ZET6 芯片为例子,该芯片共有 144 脚芯片,包括7个通用目的的输入/输出口(GPIO)组,分别为

    2024年02月20日
    浏览(50)
  • 单片机毕业设计 stm32车牌识别系统

    Hi,大家好,学长今天向大家介绍一个 单片机项目 基于stm32的车牌识别系统设计 大家可用于 课程设计 或 毕业设计 在我们的日常生活中, 接触到了很多关于电子科技的技术。 在电子科技交通领域中, 有很多技术都在无形中加入我们的生活, 如图像处理技术, 自动检测技术

    2023年04月12日
    浏览(63)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包