• STM32进阶笔记——复位、时钟与滴答定时器


      本专栏争取每周三更新直到更新完成,期待大家的订阅关注,欢迎互相学习交流。

    封面图

    一、复位

      STM32F10系列单片机的复位方式有三种,分别是系统复位、上电复位和备份区复位,这里只介绍系统复位,另外两种如果又想了解的友友可以参考芯片手册,这里就不再介绍了。

      STM32的系统复位将复位所有寄存器至它们的复位状态,以下几种情况会触发系统复位

    • NRST引脚上的低电平(外部复位),这实际也就是我们复位按键利用的复位方式。
    • 窗口看门狗计数终止(WWDG复位)
    • 独立看门狗计数终止(IWDG复位)
    • 软件复位(SW复位)
    • 低功耗管理复位

    可通过查看RCC_CSR控制状态寄存器中的复位状态标志位识别复位事件来源。

    疑问:复位标志位怎么清除,如果发生了复位,重启后能否看到复位标志位

    1.1 软件复位

      通过将Cortex™-M3中断应用和复位控制寄存器中的SYSRESETREQ位置’1’,可实现软件复位。STM32的库文件中提供了软件复位函数

    /**
     * @brief  Initiate a system reset request.
     *
     * Initiate a system reset request to reset the MCU
     */
    static __INLINE void NVIC_SystemReset(void)
    {
      SCB->AIRCR  = ((0x5FA << SCB_AIRCR_VECTKEY_Pos)      | 
                     (SCB->AIRCR & SCB_AIRCR_PRIGROUP_Msk) | 
                     SCB_AIRCR_SYSRESETREQ_Msk);                   /* Keep priority group unchanged */
      __DSB();                                                     /* Ensure completion of memory access */              
      while(1);                                                    /* wait until reset */
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

      需要注意的是,从SYSRESETREQ 被置为有效,到复位发生器执行复位命令,往往会有一个延时。在此延时期间,处理器仍然可以响应中断请求。因此我们在软件复位前,需要关闭全部中断,同时在程序最开始的时候开启全部中断。STM32库文件中也提供了一个关闭全部中断和开启全部中断的函数

    //关闭所有中断
    void INTX_DISABLE(void)
    {		  
    	__ASM volatile("cpsid i");
    }
    //开启所有中断
    void INTX_ENABLE(void)
    {
    	__ASM volatile("cpsie i");		  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    关于其他开启和关闭全局中断的方法大家可以自行搜索一下,这里就不再赘述了。

      下面我们来简单尝试以下系统的软件复位,每次进入系统前都输出一串字符串用来提示进入系统,设置延时一段时间后软件复位系统,查看一下现象

    现象

      通过串口的信息我们可以看到,软件复位生效了。这里的main函数比较简单,贴一下,仅供参考

    int main(void)
    {	
    	delay_init();   //延时函数初始化	 
    	uart_init(115200);   // 串口初始化
    	
    	printf ("Enter System!\r\n");
    	
    	while(1)
    	{
    		// 延时
    		delay_ms(1000);
    		
    		// 软件复位
    		INTX_DISABLE();
    		NVIC_SystemReset();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    1.2 低功耗管理复位

      在以下两种情况下可产生低功耗管理复位

    • 在进入待机模式时产生低功耗管理复位
      通过将用户选择字节中的nRST_STDBY位置’1’将使能该复位。这时,即使执行了进入待机模式的过程,系统将被复位而不是进入待机模式。
    • 在进入停止模式时产生低功耗管理复位
      通过将用户选择字节中的nRST_STOP位置’1’将使能该复位。这时,即使执行了进入停机
      模式的过程,系统将被复位而不是进入停机模式。

    二、时钟

      在介绍STM32F10系列的时钟之前,先贴一下时钟树,可以对照时钟数来看下面的内容

    时钟树
      STM32有5个时钟源,分别是HSI、HSE、PLL、LSI和LSE。

    • HSI是高速内部时钟,RC振荡器,频率为8MHz,精度不高。
    • HSE是高速外部时钟,可以接石英石/陶瓷谐振器,或者接外部时钟源,频率范围是4MHz~16MHz
    • PLL实际是一个锁相环,也可以理解为倍频器,它的时钟源可以选择为 H S I 2 \frac{HSI}{2} 2HSI、HSE或者 H S E 2 \frac{HSE}{2} 2HSE,倍频可以选择x2~x16,但是它的输出最大不可超过72MHz
    • LSI是低速内部时钟,RC振荡器,频率为40KHz,提供独立看门狗时钟等。
    • LSE是低速外部时钟,可以接频率为32.768KHz的石英晶体,提供RTC时钟。

      STM32的系统时钟SYSCLK可以来源于三个时钟源,分别是HSI、HSE和PLL时钟。STM32也可以输出时钟信号,可以选择一个时钟信号出书到PA8引脚上,可选PLL输出的二分频、HSI、HSE或者系统时钟作为时钟源。

    2.1 系统时钟(SYSCLK)选择

      系统复位后,HSI振荡器被选为系统时钟。我们可以通过设置时钟配置寄存器(RCC_CFGR)来切换系统时钟,在切换系统时钟时,需要等待目标时钟源准备就绪,当被选择的时钟没有就绪时,系统时钟不会发生切换。直至目标时钟源准备就绪,才会发生切换。我们可以通过在时钟控制寄存器(RCC_CR)里的状态位指示哪个时钟已经准备好了,哪个时钟目前被用作系统时钟。

    在从停止或待机模式中返回时或直接或间接作为系统时钟的HSE出现故障时,由硬件强制选择HSI作为系统时钟(如果时钟安全系统已经启动),关于时钟安全系统(CSS)这就不再赘述。

    2.2 系统时钟初始化

      STM32的库函数中提供了系统时钟初始化函数

    /**
      * @brief  Setup the microcontroller system
      *         Initialize the Embedded Flash Interface, the PLL and update the 
      *         SystemCoreClock variable.
      * @note   This function should be used only after reset.
      * @param  None
      * @retval None
      */
    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 */
    #ifndef STM32F10X_CL
      RCC->CFGR &= (uint32_t)0xF8FF0000;
    #else
      RCC->CFGR &= (uint32_t)0xF0FF0000;
    #endif /* STM32F10X_CL */   
      
      /* 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;
    
    #ifdef STM32F10X_CL
      /* Reset PLL2ON and PLL3ON bits */
      RCC->CR &= (uint32_t)0xEBFFFFFF;
    
      /* Disable all interrupts and clear pending bits  */
      RCC->CIR = 0x00FF0000;
    
      /* Reset CFGR2 register */
      RCC->CFGR2 = 0x00000000;
    #elif defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
      /* Disable all interrupts and clear pending bits  */
      RCC->CIR = 0x009F0000;
    
      /* Reset CFGR2 register */
      RCC->CFGR2 = 0x00000000;      
    #else
      /* Disable all interrupts and clear pending bits  */
      RCC->CIR = 0x009F0000;
    #endif /* STM32F10X_CL */
        
    #if defined (STM32F10X_HD) || (defined STM32F10X_XL) || (defined STM32F10X_HD_VL)
      #ifdef DATA_IN_ExtSRAM
        SystemInit_ExtMemCtl(); 
      #endif /* DATA_IN_ExtSRAM */
    #endif 
    
      /* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
      /* Configure the Flash Latency cycles and enable prefetch buffer */
      SetSysClock();
    
    #ifdef VECT_TAB_SRAM
      SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
    #else
      SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
    #endif 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    关于系统时钟配置函数的内容,大家可以自行对照STM32中文参考手册来查看具体每一行代码的含义,当然也有英文注释可参考,这里就不再赘述了。

      不知道大家是否好奇,我们在编程时并未特地调用过时钟初始化函数SystemInit(),我们是在什么时候配置的系统时钟呢?实际在STM32的启动文件中已经调用过该函数,配置完了系统时钟

    配置系统时钟

      关于STM32启动文件的详细注释,大家可以移步至该帖查看,这里也不再贴出来了,STM32启动文件详解

    三、滴答定时器(Systick)

      SysTick定时器被捆绑在NVIC中,它通常用来做延时或者实时系统的心跳,使用SysTick可以节省一个定时器资源。

      SysTick定时器也可以叫做系统滴答定时器,它是一个24位的倒计数计时器,当它计数到0时,将从RELOAD寄存器中自动重装载定时器初值。只要我们不把它在SysTick控制及状态寄存器中的使能位清除,滴答定时器就一直在工作,即使在睡眠模式下也不会停止。SysTick有自己的中断,中断优先级可以设置。

    3.1 SysTick部分寄存器

      接下来我们来看一下SysTick的一些寄存器。

    • SysTick控制及状态寄存器(CTRL)
      SysTick控制及状态寄存器
    • SysTick重装载数值寄存器(LOAD)
      SysTick重装载数值寄存器
    • SysTick当前数值寄存器(VAL)
      SysTick当前数值寄存器

    3.2 SysTick相关函数

      STM32库函数给出了SysTick的一些配置函数,这里我们简单介绍一下,在看下面的函数时,大家可以对照上面的寄存器来分析一下每一行代码的作用和含义。

    3.2.1 SysTick时钟源配置函数

    /**
      * @brief  Configures the SysTick clock source.
      * @param  SysTick_CLKSource: specifies the SysTick clock source.
      *   This parameter can be one of the following values:
      *     @arg SysTick_CLKSource_HCLK_Div8: AHB clock divided by 8 selected as SysTick clock source.
      *     @arg SysTick_CLKSource_HCLK: AHB clock selected as SysTick clock source.
      * @retval None
      */
    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;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

      下面是一些相关的宏定义

    #define SysTick_CLKSource_HCLK_Div8    ((uint32_t)0xFFFFFFFB)
    #define SysTick_CLKSource_HCLK         ((uint32_t)0x00000004)
    #define IS_SYSTICK_CLK_SOURCE(SOURCE) (((SOURCE) == SysTick_CLKSource_HCLK) || \
                                           ((SOURCE) == SysTick_CLKSource_HCLK_Div8))
    
    • 1
    • 2
    • 3
    • 4

      对照上面对于SysTick控制及状态寄存器的介绍我们可以知道,当SysTick时钟源配置函数的输入值为1时,选择外部时钟作为时钟源,当输入值为0时,选择内部时钟作为时钟源。

    3.2.2 SysTick初始化函数

    /**
     * @brief  Initialize and start the SysTick counter and its interrupt.
     *
     * @param   ticks   number of ticks between two interrupts
     * @return  1 = failed, 0 = successful
     *
     * Initialise the system tick timer and its interrupt and start the
     * system tick timer / counter in free running mode to generate 
     * periodical interrupts.
     */
    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 */
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

      下面是一些相关的宏定义

    /* SysTick Reload Register Definitions */
    #define SysTick_LOAD_RELOAD_Pos             0                                             /*!< SysTick LOAD: RELOAD Position */
    #define SysTick_LOAD_RELOAD_Msk            (0xFFFFFFul << SysTick_LOAD_RELOAD_Pos)        /*!< SysTick LOAD: RELOAD Mask */
    
    #define SysTick_CTRL_CLKSOURCE_Pos          2                                             /*!< SysTick CTRL: CLKSOURCE Position */
    #define SysTick_CTRL_CLKSOURCE_Msk         (1ul << SysTick_CTRL_CLKSOURCE_Pos)            /*!< SysTick CTRL: CLKSOURCE Mask */
    
    #define SysTick_CTRL_TICKINT_Pos            1                                             /*!< SysTick CTRL: TICKINT Position */
    #define SysTick_CTRL_TICKINT_Msk           (1ul << SysTick_CTRL_TICKINT_Pos)              /*!< SysTick CTRL: TICKINT Mask */
    
    #define SysTick_CTRL_ENABLE_Pos             0                                             /*!< SysTick CTRL: ENABLE Position */
    #define SysTick_CTRL_ENABLE_Msk            (1ul << SysTick_CTRL_ENABLE_Pos)               /*!< SysTick CTRL: ENABLE Mask */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

      该函数的功能是使能SysTick,开启中断,函数的输入参数ticks是设置两次中断间的间隔,也就是设置两次中断之间有多少个SysTick时钟周期。我们如下配置就可以设置SysTick中断时间间隔为1ms。

        SysTick_Config(SystemCoreClock / 1000);   // 设置SysTick中断时间间隔为1ms
    
    • 1

      这里介绍一下为什么按照上面的设置,SysTick两次中断之间的时间间隔为1ms。比如系统频率为72MHz,我们设置的是每72K个SysTick周期进入一次中断,也就是说进入中断的时间间隔为72K / 72M,也就是1ms。

      其实我们用到的延时函数也是利用滴答定时器来实现的延时,这里我们就不再详细介绍,大家有兴趣的可以自行分析学习一下。

  • 相关阅读:
    【Debug危机系列】 记一次opencv相关的debug过程
    解决方案:AI赋能工业生产3.0,从工业“制造”到“智造”
    mysqlDM数据库中利用函数更新身份证字段,单表计算单表更新
    Ref, Reactive, Shadowref,Shadowreactive一次分清楚
    OceanBase 4.3 列存存储格式和列存索引存储格式
    【编程语言发展史】Unity开发语言的历史发展
    Leetcode30-最小展台数量(66)
    蓝桥杯嵌入式基础模块——ADC的使用(新板)STM32G431(HAL库开发)
    当zmq 和 docker 都要绑定一个端口时,怎么不修改端口号就能解决冲突?
    氨基化/环氧化/胺化/羧基化/巯基改性/笼空状磺化聚苯乙烯微球相关制备
  • 原文地址:https://blog.csdn.net/qq_45217381/article/details/136337215