文章目录

  • 1. STM32时钟系统概述
    • 1.1 时钟系统的概念及意义
    • 1.2 常见振荡器介绍
    • 1.3 时钟树分析
  • 2. STM32时钟配置实例
  • 3. SysTick定时器讲解
    • 3.1 SysTick 定时器介绍
    • 3.2 SysTick定时器工作原理分析
    • 3.3 SysTick寄存器
    • 3.4 SysTick定时器使用实例
  • 4. HAL_Delay()函数的实现

1. STM32时钟系统概述

1.1 时钟系统的概念及意义

  • 概念:时钟系统是由振荡器(信号源)、定时唤醒器、分频器等组成的电路。常用的信号源有晶体振荡器和RC振荡器

  • 意义:时钟是嵌入式系统的脉搏,处理器内核在时钟驱动下完成指令执行,状态变换等动作,外设部件在时钟的驱动下完成各种工作,比如串口数据的发送、A/D转换、定时器计数等等。因此时钟对于计算机系统是至关重要的,通常时钟系统出现问题也是致命的,比如振荡器不起振、振荡不稳、停振等。


首先,通过晶体振荡器产生一个时钟信号,然后跟着一个开关,不开这个时钟信号就没有办法传递出去。通常来说晶体振荡器的频率是比较低的,而CPU可以承受的频率是比较大的。比如振荡器只能有几兆或几十兆的时钟信号产生,而CPU需要的时钟信号是上百兆。因此,我们需要倍频器,使输出信号频率等于输入信号频率整数倍的电路,供给CPU使用。然而我们有很多内外设,他们的需要的频率没有CPU那么高,因此我们又需要分频器,将高速频率降下来,适合各个部件的工作频率。

1.2 常见振荡器介绍

  • 概念:振荡器是用来产生重复电子讯号的电子元件。其构成的电路叫振荡电路,能将直流电转换为具有一定频率交流信号输出的电子电路或装置。

  • 分类:振荡器主要分为RC,LC振荡器和晶体振荡器。RC振荡器是采用RC网络作为选频移相网络的振荡器。LC振荡器是采用LC振荡回路作为移相和选频网络的正反馈振荡器。晶体振荡器的振荡频率受石英晶体控制。

  • RC振荡器:RC振荡器是由电阻电容构成的振荡电路,能将直流电转换为具有一定频率交流信号输出的电子电路或装置

    • 优点:实现的成本比较低
    • 缺点:由于电阻电容的精度问题所以RC振荡器的震荡频率会有误差,同时受到温度、湿度的影响
  • 晶体振荡器:石英晶体振荡器是高精度和高稳定度的振荡器,被广泛应用于彩电、计算机、遥控器等各类振荡电路中,以及通信系统中用于频率发生器、为数据处理设备产生时钟信号和为特定系统提供基准信号

    • 优点:是相对来说震荡频率一般都比较稳定,同时精度也较高
    • 缺点:价格稍高,用晶体振荡器一般还需要接两个15-33pF起振电容
  • STM32 中主要有四个时钟源: 可以分为高速和低速两种,高速一般几十MHz,低速一般几十kHz;还可以分为内部和外部两种,内部指在STM32内已经集成了,外部指可以在管脚外部接一个晶振。当我们开机刚上电的时候,我们需要给CPU提供时钟信号,但是外部管脚还没有被初始化,这时候我们就需要使用高速内部时钟来给CPU提供时钟信号。如果出现外部时钟出现故障,这个时候也可以调用内部时钟继续工作

    • HSI:高速内部时钟,RC振荡器,频率为16MHz;
    • HSE:高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~26MHz
    • LSI:低速内部时钟,RC 振荡器,频率为 32kHz 左右。供独立看门狗和自动唤醒单元使用。
    • LSE:低速外部时钟,接频率为 32.768kHz 的石英晶体。这个主要是 RTC 的时钟源

1.3 时钟树分析

STM32中所有的设备都是需要时钟的,那么怎么时钟源给每一个设备分配适合它的时钟信号都会体现在时钟树中。从上往下看,低速内部时钟主要控制看门狗;低速外部时钟控制RTC;高速内部时钟、高速外部时钟都可以直接选择作为系统时钟,也可以让他们通过倍频器将频率提高到最高168MHz作为系统时钟。当确定好选择哪一个时钟后可以通过AHB PRESC给Cortex系统等高速内核使用,也可以在通过APB PRESC给低速外设使用。

2. STM32时钟配置实例

配置STM32F407的时钟,并对比STM32时钟配置前(16 MHz)后(168 MHz)LED外设闪烁的快慢。需要注意的是闪烁的时候需要用到delay函数,然而HAL_Delay不管设置的时钟是多少都是按照毫秒来计算的,因此我们需要自己写一个delay函数,比较简单代码如下。

void delay(uint32_t time)
{uint32_t i, j;for (i = 0; i < time; i++){for (j = 0; j < 5000; j++);}
}

通过电路原理图,我的板子上的LED用的是PE2、PF8、PF9 和 PF10,因此要设置他们为输出模式。


然后我们看一下时钟配置,默认情况下用的是HSI,也就是高速内部时钟(16 MHz),导出工程后,在main函数的while里写如下代码。然后下载到板子里,可以观察到板子上的LED每次闪烁在一秒左右。

while (1){HAL_GPIO_WritePin(GPIOE, GPIO_PIN_2, GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOF, GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10, GPIO_PIN_RESET);delay(1000);HAL_GPIO_WritePin(GPIOE, GPIO_PIN_2, GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOF, GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10, GPIO_PIN_SET);delay(1000);/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}

然后,我们按照下图重新设置一下时钟(先设置能使用外部晶振,在配置倍频器),这样能把时钟拉满。算是STM32F407能接受的最快速度。这个时候,我们重新导出工程,下载到板子里,可以观察到闪烁速度快了将近十倍。

3. SysTick定时器讲解

3.1 SysTick 定时器介绍

  • 概念
    能够定时、计数的器件称为定时器。SysTick,(系统滴答定时器)是一个定时设备,位于Cortex-M4内核中,可以对输入的时钟进行计数,当然,如果时钟信号是周期性的,计数也就是计时。系统定时器一般用于操作系统,用于产生时基,维持操作系统的心跳。根据这个中断,系统就可以实现时间片的计算从而切换进程。
  • 工作原理
    滴答定时器是一个24位定时器,也就是最多能计数2242^{24}224。在使用的时候,我们一般给计数器送一个初始的计数值,计数器向下计数,每来一个时钟信号,计数初值就减一,计数值减到0的时候,就会发出一次中断。然后重新从计数初值再减一计数,循环不断

3.2 SysTick定时器工作原理分析


在上图中,我们假设AHB是 168 MHz,经过/8的分频器,那么SysClock的时钟就是 21 MHz。然后这个时钟信号作为输入,给到定时器,定时器读取重载数值寄存器,假设其值为val,定时器每次val--,直到等于0的时候重新加载数值寄存器。这个重载数值寄存器的值是可以设置的,假设我们要设置它到0的时候,时间经过1ms,那么我们要设置的值是多少?
121MHz(s)∗val=1(ms)⇒val=21000<224\frac1{21 MHz}(s) * val = 1 (ms) \Rightarrow val = 21000 < 2^{24} 21MHz1​(s)∗val=1(ms)⇒val=21000<224

3.3 SysTick寄存器



上面两张图很好的解释了和SysTick有关的寄存器,都比较直白,没啥好解释的

3.4 SysTick定时器使用实例

我用的环境是CubeMx和Keil 5,这次要利用SysTick定时器实现1s打印一个字符串。新建一个CubeMx项目,设置USART1串口,然后导出项目。
首先,我们先了解下程序是如何初始化Systick的,打开main.c可以看到main函数里的第一行HAL_Init在这里初始化了Systick

int main(void)
{/* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* Configure the system clock */SystemClock_Config();/* Initialize all configured peripherals */MX_GPIO_Init();MX_USART1_UART_Init();/* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}

打开HAL_Init的定义,HAL_InitTick(TICK_INT_PRIORITY)初始化了Systick,输入的参数是Systick的中断优先级

HAL_StatusTypeDef HAL_Init(void)
{/* Configure Flash prefetch, Instruction cache, Data cache */
#if (INSTRUCTION_CACHE_ENABLE != 0U)__HAL_FLASH_INSTRUCTION_CACHE_ENABLE();
#endif /* INSTRUCTION_CACHE_ENABLE */#if (DATA_CACHE_ENABLE != 0U)__HAL_FLASH_DATA_CACHE_ENABLE();
#endif /* DATA_CACHE_ENABLE */#if (PREFETCH_ENABLE != 0U)__HAL_FLASH_PREFETCH_BUFFER_ENABLE();
#endif /* PREFETCH_ENABLE *//* Set Interrupt Group Priority */HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);/* Use systick as time base source and configure 1ms tick (default clock after Reset is HSI) */HAL_InitTick(TICK_INT_PRIORITY);/* Init the low level hardware */HAL_MspInit();/* Return function status */return HAL_OK;
}

继续追查HAL_InitTick(TICK_INT_PRIORITY)的定义,在这里定义了HAL_SYSTICK_Config,设置了systick需要每1ms中断一次

/*** @brief This function configures the source of the time base.*        The time source is configured  to have 1ms time base with a dedicated *        Tick interrupt priority.* @note This function is called  automatically at the beginning of program after*       reset by HAL_Init() or at any time when clock is reconfigured  by HAL_RCC_ClockConfig().* @note In the default implementation, SysTick timer is the source of time base. *       It is used to generate interrupts at regular time intervals. *       Care must be taken if HAL_Delay() is called from a peripheral ISR process, *       The SysTick interrupt must have higher priority (numerically lower)*       than the peripheral interrupt. Otherwise the caller ISR process will be blocked.*       The function is declared as __weak  to be overwritten  in case of other*       implementation  in user file.* @param TickPriority Tick interrupt priority.* @retval HAL status*/
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{/* Configure the SysTick to have interrupt in 1ms time basis*/if (HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) > 0U){return HAL_ERROR;}/* Configure the SysTick IRQ priority */if (TickPriority < (1UL << __NVIC_PRIO_BITS)){HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);uwTickPrio = TickPriority;}else{return HAL_ERROR;}/* Return function status */return HAL_OK;
}

追查HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) > 0U)的定义,发现还需要继续看SysTick_Config的定义

/*** @brief  Initializes the System Timer and its interrupt, and starts the System Tick Timer.*         Counter is in free running mode to generate periodic interrupts.* @param  TicksNumb Specifies the ticks Number of ticks between two interrupts.* @retval status:  - 0  Function succeeded.*                  - 1  Function failed.*/
uint32_t HAL_SYSTICK_Config(uint32_t TicksNumb)
{return SysTick_Config(TicksNumb);
}

定义如下,时间要设置成ticks-1,防止溢出,之后就是操作底层的寄存器。同样的道理我们也可以追查Systick优先级的定义,这里就不继续看了。

/**\brief   System Tick Configuration\details Initializes the System Timer and its interrupt, and starts the System Tick Timer.Counter is in free running mode to generate periodic interrupts.\param [in]  ticks  Number of ticks between two interrupts.\return          0  Function succeeded.\return          1  Function failed.\note    When the variable <b>__Vendor_SysTickConfig</b> is set to 1, then thefunction <b>SysTick_Config</b> is not included. In this case, the file <b><i>device</i>.h</b>must contain a vendor-specific implementation of this function.*/
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk){return (1UL);                                                   /* Reload value impossible */}SysTick->LOAD  = (uint32_t)(ticks - 1UL);                         /* set reload register */NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */SysTick->VAL   = 0UL;                                             /* 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 (0UL);                                                     /* Function successful */
}

如果我们要设置1s打印一个字符串,那么我们要设置每1ms后重装载值寄存器清零后的中断回调函数。要找到这个中断回调函数,我们需要查中断向量表

__Vectors       DCD     __initial_sp               ; Top of StackDCD     Reset_Handler              ; Reset HandlerDCD     NMI_Handler                ; NMI HandlerDCD     HardFault_Handler          ; Hard Fault HandlerDCD     MemManage_Handler          ; MPU Fault HandlerDCD     BusFault_Handler           ; Bus Fault HandlerDCD     UsageFault_Handler         ; Usage Fault HandlerDCD     0                          ; ReservedDCD     0                          ; ReservedDCD     0                          ; ReservedDCD     0                          ; ReservedDCD     SVC_Handler                ; SVCall HandlerDCD     DebugMon_Handler           ; Debug Monitor HandlerDCD     0                          ; ReservedDCD     PendSV_Handler             ; PendSV HandlerDCD     SysTick_Handler            ; SysTick Handler

我们需要查看SysTick_Handler的定义

void SysTick_Handler(void)
{/* USER CODE BEGIN SysTick_IRQn 0 *//* USER CODE END SysTick_IRQn 0 */HAL_IncTick();HAL_SYSTICK_IRQHandler();/* USER CODE BEGIN SysTick_IRQn 1 *//* USER CODE END SysTick_IRQn 1 */
}

查看HAL_SYSTICK_IRQHandler的定义。

/*** @brief  This function handles SYSTICK interrupt request.* @retval None*/
void HAL_SYSTICK_IRQHandler(void)
{HAL_SYSTICK_Callback();
}

HAL_SYSTICK_Callback就是我们需要写的函数,写的代码如下,因为每1ms中断一次,那么我们1s就需要1000次才能打印一句话。printf的实现可以通过覆盖fputc来实现。

/* USER CODE BEGIN 1 */
void HAL_SYSTICK_Callback(void)
{static uint32_t i = 0;i++;if (i == 1000){printf("1s \n");i = 0;}
}
/* USER CODE BEGIN PFP */
int fputc(int ch, FILE *p)
{while (!(USART1->SR & (1<<7)));USART1->DR = ch;return ch;
}

至此上传到板子,就可以实现目标功能。

4. HAL_Delay()函数的实现

HAL_Delay是依赖SysTick来实现的,虽然常用,但是如果不正确使用会导致死机。首先我们先看下他的源码

/*** @brief This function provides minimum delay (in milliseconds) based *        on variable incremented.* @note In the default implementation , SysTick timer is the source of time base.*       It is used to generate interrupts at regular time intervals where uwTick*       is incremented.* @note This function is declared as __weak to be overwritten in case of other*       implementations in user file.* @param Delay specifies the delay time length, in milliseconds.* @retval None*/
__weak void HAL_Delay(uint32_t Delay)
{uint32_t tickstart = HAL_GetTick();uint32_t wait = Delay;/* Add a freq to guarantee minimum wait */if (wait < HAL_MAX_DELAY) //防止我们写delay的时间是0,加一个最小值{wait += (uint32_t)(uwTickFreq);}while((HAL_GetTick() - tickstart) < wait) // 主要看这里,延时多久主要看while的条件// 也就是当前时间-开始时间>wait退出{}
}

我们可以继续看HAL_GetTick是怎么写的。它返回了uwTick

/*** @brief Provides a tick value in millisecond.* @note This function is declared as __weak to be overwritten in case of other *       implementations in user file.* @retval tick value*/
__weak uint32_t HAL_GetTick(void)
{return uwTick;
}

uwTick是怎么变化的呢,经过查找我们发现它首先是一个uint32_t,以及一个对uwTick的加法操作,加了什么,我们经过几次查找找到了它是1u,简单说就是每次+1

__IO uint32_t uwTick;
uwTickFreq = 1U,
/*** @brief This function is called to increment  a global variable "uwTick"*        used as application time base.* @note In the default implementation, this variable is incremented each 1ms*       in SysTick ISR.* @note This function is declared as __weak to be overwritten in case of other *      implementations in user file.* @retval None*/
__weak void HAL_IncTick(void)
{uwTick += uwTickFreq;
}

那这个HAL_IncTick()是在哪里被调用的呢?
其实我们之前已经见过了,这也就是说我们每毫秒会调动这个变量一次,每一次都会+1。

void SysTick_Handler(void)
{/* USER CODE BEGIN SysTick_IRQn 0 *//* USER CODE END SysTick_IRQn 0 */HAL_IncTick();HAL_SYSTICK_IRQHandler();/* USER CODE BEGIN SysTick_IRQn 1 *//* USER CODE END SysTick_IRQn 1 */
}

最后我们返回HAL_Delay的定义

__weak void HAL_Delay(uint32_t Delay)
{uint32_t tickstart = HAL_GetTick(); // HAL_GetTick每毫秒会+1uint32_t wait = Delay;/* Add a freq to guarantee minimum wait */if (wait < HAL_MAX_DELAY) //防止我们写delay的时间是0,加一个最小值{wait += (uint32_t)(uwTickFreq);}while((HAL_GetTick() - tickstart) < wait) // 主要看这里,延时多久主要看while的条件// 也就是当前时间-开始时间>wait退出{}
}

总的来说,HAL_Delay是通过SysTick每毫秒调用的一个回调函数而实现的。如果这个回调函数没有办法被调用,我们的程序就会卡在while里不更新,也退不出去,因此造成了死机。那么要保证SysTICK_IRQHandler要顺利执行,那就需要有较高的抢占优先级。如果一个被调用的中断的优先级是 0 0,那么这个时候再调用HAL_Delay,由于它的优先级也是0 0,这就导致了SysTICK_IRQHandler无法抢占之前的中断,无法抢断,那么uwTick就无法+1,最后导致卡在死循环,死机。这也是为什么我们平时设置GPIO或者其他中断的抢占优先级调低一点的原因。

ARM开发初级-STM32时钟系统以及如何正确使用HAL_Delay-学习笔记08相关推荐

  1. esp32 rtc 时钟设置不对_STM32入门系列-STM32时钟系统,STM32时钟树

    时钟对于单片机来说是非常重要的,它为单片机工作提供一个稳定的机器周期从而使系统能够正常运行.时钟系统犹如人的心脏,一旦有问题整个系统就崩溃.我们知道STM32属于高级单片机,其内部有很多的外设,但不是 ...

  2. STM32 时钟系统

    STM32时钟系统的基本概念 概念及意义 (1)概念:时钟系统是由振荡器(信号源).定时唤醒器.分频器等组成的电路.常用的信号源有晶体振荡器和RC振荡器. (2)意义:时钟对数字电路而言非常重要,没有 ...

  3. STM32——时钟系统

    STM32--时钟系统 宗旨:技术的学习是有限的,分享的精神是无限的. 一.时钟树 普通的MCU,一般只要配置好GPIO 的寄存器,就可以使用了.STM32为了实现低功耗,设计了非常复杂的时钟系统,必 ...

  4. 电脑向linux板卡传文件,ARM 开发板嵌入式linux系统与主机PC通过串口传输文件

    ARM 开发板嵌入式linux系统与主机PC通过串口传输文件 本来以为按以下两篇文章就可以几步轻松搞定这个问题,没想到遇到两个小麻烦: 1,我用的xp虚拟机下redhat9.0做主机,按照下面第一篇文 ...

  5. STM32时钟系统的概念及意义

    STM32时钟系统的基本概念 概念及意义 概念 时钟系统是由振荡器(信号源).定时唤醒器.分频器等组成的电路.常用的信号源有晶体振荡器和RC振荡器 意义 时钟是嵌入式系统的脉搏,处理器内核在时钟驱动下 ...

  6. 2021.4.14 第四次 STM32时钟系统

    STM32时钟系统 一. STM32时钟系统介绍 二. 时钟系统框图 三. 时钟配置相关函数 1.1 时钟系统介绍: 时钟是单片机运行的基础,时钟信号推动单片机内各个部分执行相应的指令.STM32本身 ...

  7. STM32时钟系统(1)-时钟框图解释

    STM32时钟系统(2)-时钟系统常用寄存器和库函数 STM32时钟系统 官方文档说明: Three different clock sources can be used to drive the ...

  8. 大数据开发工程师基本功修炼之史上最全Linux学习笔记(建议)

    我正在参加年度博客之星评选,请大家帮我投票打分,您的每一分都是对我的支持与鼓励. 2021年「博客之星」参赛博主:Maynor大数据 (感谢礼品.红包免费送!) https://bbs.csdn.ne ...

  9. C/C++ 开发 boost 库参考手册整理(2) 【学习笔记】

    文档声明: 以下资料均属于本人在学习过程中产出的学习笔记,如果错误或者遗漏之处,请多多指正.并且该文档在后期会随着学习的深入不断补充完善.感谢各位的参考查看. 笔记资料仅供学习交流使用,转载请标明出处 ...

最新文章

  1. 【Android 逆向】Android 逆向通用工具开发 ( Windows 平台静态库程序类型 | 编译逆向工具依赖的 Windows 平台静态库程序 )
  2. JDBC驱动的动态加载
  3. 华为鸿蒙系统有什么特色,【图片】华为鸿蒙系统的厉害之处在于 你可能非用不可 !【手机吧】_百度贴吧...
  4. MyBatis传入参数为List对象
  5. Java注解(Annotation)的学习
  6. pymc3使用_使用PyMC3了解飞机事故趋势
  7. 决策树(西瓜书学习)
  8. springframework报错_应对报错信息的必杀技!
  9. SHELL实战day12
  10. 解决:安装Widget插件提醒已安装却不见界面
  11. 银河麒麟Linux系统安装谷歌浏览器
  12. 【电路基础】第1章-电路的基本规律(1)
  13. 解决联想电脑“未安装音频设备”问题
  14. 计算机雕刻教学设计,综合实践《果蔬雕刻》教学设计第一课时
  15. 【物理】半导体物理 西安电子科技大学 柴常春等主讲-[笔记P11-P14]
  16. 《孩子,你慢慢来》的读书笔记与读后感2600字
  17. 开发移动应用的7个致命错误
  18. 欧拉回路(简单判断是否有欧拉回路存在)
  19. mysql set password_MySQL SET PASSWORD语法示例
  20. rpa 手机_RPA

热门文章

  1. 发布镜像到DockerHub和阿里云容器镜像服务
  2. 软件技术栈导航(20221231)
  3. UDP无法打洞咋办? 对称NAT无法穿透?IPV6将终结内网穿透
  4. 斯柯达老明锐遥控器汽车钥匙换电池子磁(详细操作过程)
  5. 【829】【02检索语言】【理解】
  6. HuaPu在学:机器学习——sklearn【随机森林】
  7. 运行SQL文件,出现[ERR] 1046 - No database selected
  8. 面试中问到 有没有用过VUEX做过什么事情
  9. Facebook Learns that You CAN Buy Love (It Costs $1 Billion)
  10. 网络安全工程师常用的9种软件工具,你知道吗?