STM32系统时钟简介

系统定时器

SysTick系统定时器属于CM3的内核外设,而不是片上外设,即只要是使用CM3核的芯片都有这个功能。有关寄存器的定义和部分库函数都在core_CM3.h中定义和实现。
定时器是用来计时的,与传统的软件模拟(while或for实现)计时相比,定时器在计时精度上有着明显的优势,并且还不占用CPU的资源,可以让CPU去处理别的事情。定时的原理:向重装载数值寄存器中写入需要定时的数值,然后配置系统定时器的控制及状态寄存器,开始倒数计数。当当前数值寄存器中的值减到0时,会根据控制及状态寄存器的配置来决定是否产生中断、以及把控制及状态寄存器中的状态位置1(M3的状态标志位是第16bit,不同的核状态标志位可能不一样)。要是使能了中断,则CPU执行相应的中断服务函数响应中断。而同时计数器把重装载数值寄存器中的值重新装载到当前数值寄存器中,然后开始下一轮的计数,以此循环往复。
从上面可以知道,定时器其实就是做减法计数操作。那么多长时间减一次(时钟)、计数到0的时候是否要产生中断、以及是否使能定时器?这些问题咱们可以配置相应的寄存器来实现。

  • STK_CTRL:第16bit指示定时器中的数值是否减到0,要是到0则置1(读出1后会自动清零),bit2用来选择定时器的时钟,bit1用来配置是否产生中断。bit0则是开启定时器的使能开关
  • 在这里插入图片描述
  • STK_LOAD,24bit的重装载数值寄存器。从这里可以知道系统定时器的最大计数值是224
  • 在这里插入图片描述
  • STK_VAL,当前数值寄存器,读取这个寄存器既可获取到当前计数的值。
  • 在这里插入图片描述
    所以咱们只需要使用上述的寄存器就可以使用系统定时器。系统定时器一般用于操作系统,用于产生时基,维持系统的心跳。由于系统定时器使用的是处理器的时钟,所以处理器进入低功耗模式后,系统定时器也会停止工作。

而对于STM32的编程则更倾向使用库函数来配置系统定时器。系统定时器配置库函数为SysTick_Config(),其中参数是重装载数值寄存器的值。默认使用系统时钟,要是有需要可以自己修改函数中的配置。源码如下

/* ##################################    SysTick function  ############################################ */

#if (!defined (__Vendor_SysTickConfig)) || (__Vendor_SysTickConfig == 0)

/**
 * @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 */
}

#endif

其中NVIC_SetPriority ()设置系统定时器中断的优先级。通过对IRQn的判断用来区分是系统异常还是外部中断。其中系统异常的优先级由内核外设SCB的寄存器SHPRx控制,而外部中断的优先级由内核外设NVIC中的IPx寄存器控制。

static __INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
  if(IRQn < 0) {
    SCB->SHP[((uint32_t)(IRQn) & 0xF)-4] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); } /* set Priority for Cortex-M3 System Interrupts */
  else {
    NVIC->IP[(uint32_t)(IRQn)] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff);    }        /* set Priority for device specific Interrupts  */
}

这里讲到内核外设优先级和之前讲述的外设中断优先级很相似,优先级的范围是0~15,数值越低优先级越高。要是同时设置了内核中断优先级以及外部中断优先级,则优先级的比较是:把外部中断优先级转换为4bit的二进制数,然后和内核中断比较。

这里讲解下如何使用定时器实现自己想要的时间。假如定时器使用的时钟是CLKAHB,那么一次计数操作的时间为T=1/CLKAHB,所以计数器的时间为t = VALUELOAD*T= VALUELOAD/CLKAHB。VALUELOAD = CLKAHB *t,在库中定义了SystemCoreClock为72M,所以可以使用这个宏很方便的来算出响应时间的重装载数值寄存器中的值。

配置好了系统定时器,接下来就是需要把中断服务函数的函数体实现。在启动文件中已近声明为 void SysTick_Handler(void),在这个函数里面实现自己想要的功能。

要是只是想用定时器来实现简单的来计时,比如实现延时功能。那么可以用简单的方法来实现。一直轮询控制及状态寄存器的16bit的值,值为1代表此次计数结束。读取1后会自动清为0。代码就是while ( !((SysTick->CTRL)&(1<<16)) );

所以系统定时器的编程还是很简单:

  • 调用SysTick_Config()函数,配置中断优先级、重装载寄存器的值以及配置控制与状态寄存器的值、清除当前数值寄存器的值。
  • 接着就是实现中断服务函数SysTick_Handler(),在这里实现自己想要的功能。

外设定时器

在STM32F1系列中,除了互联型的产品,共有8个定时器,分为基本定时器、通用定时器、高级定时器。TIM6和TIM7是一个16位且只能向上计数的定时器,只能定时没有外部IO。通用定时器TIM2/3/4/5是一个16位且可以向上/下计数的定时器,可以定时也可用于输出比较。高级定时器TIM1/8是一个16位且可以向上/下计数的定时器,可以定时也可用于输出比较和输入捕获。
无论是何种定时器,其最基本的功能都是定时。要定时必须要有时基,时基有自动重装载寄存器、计数器、计数器时钟组成。计数器中数值计数的频率由计数器时钟决定(一个时钟计数一次),计数到多少或从多少开始计数则由重装载寄存器中的值决定的,而计数器中的值达到了要求后会产生中断同时开始下一轮的计数

基本定时器TIM6/7

基本定时器和系统定时器很类似,但在计数方式和系统定时器有些差别,系统定时器是向下计数而通用定时器是向上计数。TIM6/TIM7可以作为通用定时器提供时间基准外,特别地可以为数模转换器提供时钟。实际上,它们在芯片内部直接连接到DAC并通过触发输出直接驱动DAC。具有特性:
● 16位自动重装载累加计数器
● 16位可编程(可实时修改)预分频器,用于对输入的时钟按系数为1~65536之间的任意数值分频
● 触发DAC的同步电路
● 在更新事件(计数器溢出)时产生中断/DMA请求
在这里插入图片描述

  • 时钟源TIMxCLK,即内部时钟CK_INT,是经APB1预分频器后分频提供。APB1预分频系数为1,则使用APB1时钟。否则翻倍使用,库函数中对APB1预分频的系统是2,因此调用库函数时钟源是72M
  • 计数器时钟分频器,定时器时钟源经过PSC预分频器之后,得到CK_INT用来驱动计数器计数。PSC是一个16位的预分频器,对时钟TIMxCLK进行1~65536之间的任何一个数进行分频。
  • 计数器CNT:计数器CNT是一个16位的计数器,只能往上计数,最大数值为65535。当计数达到自动重装载寄存器的时候产生更新事件,并清零从头开始计数。
  • 自动重装载寄存器,自动重装载寄存器是一个16位的寄存器。计数器计数到这个值的时候,产生中断,计数器从0开始继续计数。
    结构框图就如上面这么简单,下面对框图中的细节描述下,要是只想使用基本定时器的功能,可以不看这些细节
    自动重装载寄存器和预分频寄存器都有影子寄存器,每次都写实际上是通过读写预加载寄存器并没有直接操作影子寄存器。在计数器产生事件更新时则会把预加载寄存器中的值传送到相应的影子寄存器中。其中预分频寄存器是否使用影子寄存器由TIMx_CR1寄存器中的ARPE字段决定,而计数器时钟预分频器一直使用影子寄存器,即写入预分频中的值要在紧接着的下一个更新事件触发时才会生效。
    ARPE = 0时(不使用影子寄存器,即TIMx_ARR寄存器没有缓冲,写入的值立即生效)
    在这里插入图片描述
    ARPE = 1时(TIMx_ARR寄存器有缓冲,写入自动重装载寄存器中的值在下一个更新事件时才会开始使用)
    在这里插入图片描述

时钟源

只有在CEN位(TIMx_CR1寄存器中)置1后内部时钟才会向预分频器提供时钟
在这里插入图片描述

预分频器

下图是预分频系数从1变到2的时序图,预分频器是通过一个16位寄存器的计数来实现分频的(即数值达到预分频器中的值时产生一个脉冲)。预分频器具有缓冲,可以在运行过程中改变它的值,新的预分频值将在下一个更新事件时起作用。
在这里插入图片描述
更新事件可以通过计数器溢出产生,也可以通过软件设置TIMx_EGR寄存器的UG位来实现(软件设置,硬件自动清除)。当TIMx_CR1寄存器的URS位为’0’,则每当计数器达到溢出值时,硬件发出更新事件,也可以通过软件触发更新事件;而置为“1”时,则更新事件只能由计数器计数溢出硬件触发。如果设置了UDIS位则会禁止产生更新事件,这样不会产生更新事件,所以分频系数也是不会变的,但是计数器能正常工作,计数器和预分频器依然会在应产生更新事件时重新从0开始计数(但预分频系数不变)。如果此时TIMx_CR1寄存器中的URS(选择更新请求),设置UG位可以产生一次更新事件UEV,但不设置UIF标志。
note:设置TIMx_EGR寄存器的UG位时重新初始化定时器的计数器并产生对寄存器的更新。注意:预分频器也被清除(但预分频系数不变)

计数模式

即使在计数器运行时,软件也可以读写计数器、自动重装载寄存器和预分频寄存器。自动重装载寄存器是预加载的, 每次读写自动重装载寄存器时,实际上是通过读写预加载寄存器实现。可以配置自动重装载预加载使能位,来实现写入预加载寄存器的内容是立即还是在每次事件更新时传送到它的影子寄存器。在计数器溢出时,硬件发出更新事件
假如要修改定时器的时间,则需要修改自动重装载寄存器的值。修改自动重装载的值后也是在更新事件产生时更新自动重装载影子寄存器(前提是ARPE使能)。如下图:
在这里插入图片描述

基本定时器编程

上述就是通用定时器的工作原理。在库函数中定义如下结构体:

/** 
  * @brief  TIM Time Base Init structure definition
  * @note   This structure is used with all TIMx except for TIM6 and TIM7.    
  */

typedef struct
{
  uint16_t TIM_Prescaler;         /*!< Specifies the prescaler value used to divide the TIM clock.
                                       This parameter can be a number between 0x0000 and 0xFFFF */

  uint16_t TIM_CounterMode;       /*!< Specifies the counter mode.
                                       This parameter can be a value of @ref TIM_Counter_Mode */

  uint16_t TIM_Period;            /*!< Specifies the period value to be loaded into the active
                                       Auto-Reload Register at the next update event.
                                       This parameter must be a number between 0x0000 and 0xFFFF.  */ 

  uint16_t TIM_ClockDivision;     /*!< Specifies the clock division.
                                      This parameter can be a value of @ref TIM_Clock_Division_CKD */

  uint8_t TIM_RepetitionCounter;  /*!< Specifies the repetition counter value. Each time the RCR downcounter
                                       reaches zero, an update event is generated and counting restarts
                                       from the RCR value (N).
                                       This means in PWM mode that (N+1) corresponds to:
                                          - the number of PWM periods in edge-aligned mode
                                          - the number of half PWM period in center-aligned mode
                                       This parameter must be a number between 0x00 and 0xFF. 
                                       @note This parameter is valid only for TIM1 and TIM8. */
} TIM_TimeBaseInitTypeDef;    

其中通用寄存器能使用上的是TIM_Prescaler和TIM_Period,即定时器预分频器设置系数和自动重装载的值。

在对基本定时器的编程流程为:

  • 开启定时器的时钟,没有时钟一些都是免谈。基本定时器TIM6/7都是挂载在APB1总线上。
  • 初始化时基结构体TIM_TimeBaseInitTypeDef,调用函数TIM_TimeBaseInit()进行初始化。
  • 使能基本定时器的中断,调用TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState)。基本定时器 TIM6 and TIM7 can only generate an update interrupt.
  • 开启定时器void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState)
  • 要是使能中断则需要实现相应中断服务函数和NVIC的配置。如TIM6_IRQHandler()和TIM7_IRQHandler。
    由于外部中断很多时候共用同一个中断通道,所以在中断服务函数中要区分是什么中断类型在基本定时器中可以通过API: ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT)来获取是什么中断触发了。而通过调用void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);来清除已经处理后的中断。
    库函数的具体实现这里就不贴出来了,对于想了解的同学,可以自己去库文件里面查阅源码。
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
很不错的C#源码,实用,分也不多. using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; namespace NoticePad { /// /// 定时提醒的小工具。 /// public class Form1 : System.Windows.Forms.Form { private System.Windows.Forms.Label label1; private System.Windows.Forms.Label label2; private System.Windows.Forms.TextBox textBox1; private System.Windows.Forms.Timer timer1; private System.Windows.Forms.NumericUpDown ndHour; private System.Windows.Forms.NumericUpDown ndMinute; private System.Windows.Forms.Button btn_OK; private System.ComponentModel.IContainer components; public Form1() { // // Windows 窗体设计器支持所必需的 // InitializeComponent(); // // TODO: 在 InitializeComponent 调用后添加任何构造函数代码 // } /// /// 清理所有正在使用的资源。 /// protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); } #region Windows Form Designer generated code /// /// 设计器支持所需的方法 - 不要使用代码编辑器修改 /// 此方法的内容。 /// private void InitializeComponent() { this.components = new System.ComponentModel.Container(); this.ndHour = new System.Windows.Forms.NumericUpDown(); this.ndMinute = new System.Windows.Forms.NumericUpDown(); this.label1 = new System.Windows.Forms.Label(); this.label2 = new System.Windows.Forms.Label(); this.textBox1 = new System.Windows.Forms.TextBox(); this.btn_OK = new System.Windows.Forms.Button(); this.timer1 = new System.Windows.Forms.Timer(this.components); ((System.ComponentModel.ISupportInitialize)(this.ndHour)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.ndMinute)).BeginInit(); this.SuspendLayout(); // // ndHour // this.ndHour.Location = new System.Drawing.Point(8, 8); this.ndHour.Maximum = new System.Decimal(new int[] {
### 回答1: 第一种方法中,如果你想使用Keil5软件中的Timer来进行定时,你需要使用Keil5软件提供的Timer相关API函数进行配置和使用。具体方法如下: 1. 在程序中包含头文件"stm32f4xx_hal.h",该头文件中包含了Timer相关的API函数。 2. 配置Timer时钟源和计数器的位数等参数。 3. 配置Timer的中断,并编写中断服务函数。 4. 在main函数中使能Timer并启动计数器。 以下是一个简单的示例代码: ``` #include "stm32f4xx_hal.h" TIM_HandleTypeDef htim; void SystemClock_Config(void); void Error_Handler(void); static void MX_GPIO_Init(void); static void MX_TIM_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM_Init(); HAL_TIM_Base_Start_IT(&htim); while (1) { //do something } } void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim == &htim) { //Timer中断服务函数 //do something } } static void MX_TIM_Init(void) { TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; htim.Instance = TIM2; htim.Init.Prescaler = 83; htim.Init.CounterMode = TIM_COUNTERMODE_UP; htim.Init.Period = 999; htim.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; if (HAL_TIM_Base_Init(&htim) != HAL_OK) { Error_Handler(); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim, &sClockSourceConfig) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim, &sMasterConfig) != HAL_OK) { Error_Handler(); } } void Error_Handler(void) { while (1) { //Error handling } } void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK) { Error_Handler(); } } static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); GPIO_InitStruct.Pin = GPIO_PIN_5; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } ``` 这个代码演示了如何使用Keil5软件提供的Timer功能,在main函数中使能Timer并启动计数器,同时在中断服务函数中编写需要定时执行的任务。可以根据需要进行修改和扩展。 ### 回答2: 在KEIL5软件中使用时间函数,可能需要进行一些额外的配置和调整。 首先确保你的KEIL5软件版本是最新的,并且已经正确安装。然后,打开你的工程文件,在工程设置中找到相关的选项。 在工程设置中,点击“Options”或者“选项”来打开配置面板。在配置面板中,找到“C/C++”选项,然后选择“Preprocessor”或者“预处理器”。 在预处理器设置中,你可能会看到一个叫做“Define”或者“定义”的选项。点击它来添加新的定义。 对于time函数,你需要添加宏定义“USE_STDPERIPH_DRIVER”。 添加完成后,保存设置并重新编译你的程序。现在你应该可以在你的代码中使用time函数了。 注意,以上步骤仅适用于使用标准外设驱动库(Standard Peripheral Library,SPL)的情况。如果你使用的是HAL库或者其他库,你可能需要根据你所使用的库的文档来进行相应的配置和调整。 总之,通过适当的配置和调整,你应该能够在KEIL5软件中找到并使用time函数。 ### 回答3: 在KEIL5软件中,无法找到"time"函数是因为该函数不是标准C库中的函数。如果你希望在KEIL5中使用"time"函数,你可以自行编写该函数的实现。 下面是一个简单的例子,演示如何在KEIL5中使用"time"函数来计算电机测速: ```c #include <stdint.h> #include <lk/cmsis.h> volatile uint32_t tick = 0; // 用于记录时间的全局变量,单位为毫秒 void SysTick_Handler(void) { tick++; } void delay_ms(uint32_t ms) { uint32_t start = tick; while((tick - start) < ms) {} } int main(void) { SystemCoreClockUpdate(); // 更新系统时钟 // 配置系统滴答定时器 SysTick_Config(SystemCoreClock / 1000); // 时钟为1kHz,即1毫秒一次中断 while(1) { // 电机测速的代码 // ... // 使用time函数延迟500毫秒 delay_ms(500); } return 0; } ``` 这个例子中,我们使用了SysTick定时器来实现时间计数功能。在每个毫秒的SysTick中断处理函数中,我们将tick变量自增1。通过调用delay_ms函数,将阻塞等待tick变量的值增加到指定的毫秒数,从而实现了适用于简单的延迟等待的时间函数。 利用上述的代码,你可以在KEIL5中测速电机并进行500毫秒的延迟等待。请注意,这只是一个基础的示例,具体的电机测速方法可能涉及更多的硬件或算法实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值