我在高职教STM32——时钟系统与延时控制(2)

大家好,我是老耿,高职青椒一枚,一直从事单片机、嵌入式、物联网等课程的教学。 对于高职的学生层次,同行应该都懂的,老师在课堂上教学几乎是没什么成就感的。正因如此,才有了借助 CSDN 平台寻求认同感和成就感 的想法。在这里,我准备陆续把自己花了很多心思的教学设计分享出来,主要面向广大师生朋友,单片机老鸟就略过吧。欢迎点赞+关注,各位的支持是本人持续输出的动力,多谢多谢!

众所周知,衡量一款处理器的性能,最重要的一个指标就是主频,对于STM32来说也不例外。主频的背后其实是一套复杂的时钟系统,而这套系统关乎所有外设的工作。因此,在我们继续深入学习之前,有必要了解STM32时钟系统的脉络,进而才能理解所有跟时间有关的机制和配置。其实,一开始我们用到的delay延时功能就是通过配置时钟系统实现的,现在是时候对它一探究竟了。

【学习目标】

  1. 认识STM32的系统时钟树,理解时钟的产生过程;
  2. 了解系统时钟配置函数的实现脉络;
  3. 知道SysTick定时器的地位和作用;
  4. 了解SysTick寄存器的功能;
  5. 理解延时函数的实现原理

与STM32时钟有关的信息量不小,为了不让篇幅太长,本章打算分两个部分来讲解,本文是第二部分。

二、SysTick系统定时器

2.1 SysTick的地位和作用

SysTick系统定时器是Cortex-M3内核中的一个外设,它是一个24位的向下递减的计数器,STM32一上电这个计数器就开始工作,每计数一次的时间是1/HCLK或8/HCLK。当计数值递减到0的时候,SysTick系统定时器就产生一次中断,以此循环往复。

由于SysTick属于Cortex-M3内核,那么所有基于该内核的单片机都具有这个系统定时器,使得软件可以很容易的移植。系统定时器一般用于操作系统的时基,以维持操作系统的心跳。没有操作系统的时候,也可以用于精确的延时,我们一直在用的delay延时就是通过配置这个寄存器实现的。

2.2 SysTick寄存器解读

SysTick寄存器比较简单,也是我们为数不多的对寄存器的直接分析。请看图4,它有4个寄存器,一般只需要配置前3个就可以得到你想要的时长了,我们把它们的功能列在表1中。
图4 SysTick的4个寄存器
表1 SysTick寄存器功能表

三、延时函数的代码剖析

解读了SysTick寄存器的功能,可能你还不清楚该如何配置它,现在我们就来分析delay延时的源码。读完源码,也许就豁然开朗了。

3.1 delay文件清单

我们在创建工程模板的时候,就已经把delay.c和delay.h这一对文件加进了工程,我们现在再来看一下它们所在的目录,如图5所示。
图5 delay文件清单

3.2 delay.h文件源码

这个文件的源码很简单,就声明了三个函数,如代码清单1所示。

cpp 复制代码
//-------------------------------------------
// 代码清单1:delay.h
//-------------------------------------------

#ifndef _DELAY_H
#define _DELAY_H

#include "stm32f10x.h"

//-------------------------------------------
// 函数声明
//-------------------------------------------
void delay init(void);    //延时初始化函数
void delay us(u32 nus);   //微秒级延时函数
void delay ms(ul6 nms);   //毫秒级延时函数

#endif

3.3 delay.c文件源码

为了增加代码的可读性,在下面的代码清单中,我们把原工程文件中与操作系统有关的部分都去掉了,这并不影响代码的运行。

1)delay_init()函数源码

如代码清单2所示,该函数执行延时初始化配置,选择HCLK/8作为SysTick定时器的时钟源,并确定得到1us和1ms所需的计数值。

cpp 复制代码
//------------------------------------------------------
// 代码清单2:delay.c中的delay_init()函数
//------------------------------------------------------

#include "delay.h"

static u8  fac_us = 0;    //微秒延时倍乘数
static ul6 fac_ms = 0;    //毫秒延时倍乘数

void delay_init(void)
{
    //sysTick定时器的时钟源为HCLK/8
    SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
    //按系统时钟的1/8来计数,fac_us个脉冲就是1us
    fac_us = SystemCoreClock/8000000;
    //1ms就是1000us
    fac_ms = (u16)fac_us * 1000;
}

首先用了 SysTick_CLKSourceConfig() 这个库函数来选择时钟源为HCLK/8。在时钟主线的分析中,已经确定了HCLK = 72MHz,那么8分频就是9MHz。那么为了得到精确的1us,就需要9个时钟源脉冲。接着,SystemCoreClock 这个宏就是系统时钟频率72MHz,除以8M得到的fac_us不就是9么。至于最后一行,就比较好理解了,1000个fac_us不就是1ms么。

2)delay_us()函数源码

如代码清单3所示,该函数将需要的微秒数nus换算成计数值填入相应的寄存器,使能后开始计时,计时过程中通过do...while循环不断关注使能位和计数标志位的变化,时间到了则退出循环,停止计数。

cpp 复制代码
//------------------------------------------------------------
// 代码清单3:delay_us()函数
//------------------------------------------------------------

void delay_us(u32 nus)
{
    u32 temp;

    SysTick->LOAD = nus*fac_us;    //时间装载
    SysTick->VAL = 0x00;           //计数值清0
    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;    //清空计数器
}

3)delay_ms()函数源码

该函数与 delay_us() 函数的构造是一致的,如代码清单4所示,就是在装载计数器初值时不一样。使用这个函数需要特别注意,由于SysTick->LOAD寄存器是24位的,所以有nms <= 0xffffff*8*1000/HCLK。按照HCLK = 72MHz来计算,nms的最大值为1864。

cpp 复制代码
//------------------------------------------------------------
// 代码清单4:delay_ms()函数
//------------------------------------------------------------

void delay_ms(u16 nms)    //注意:nms<1864
{
    u32 temp;

    SysTick->LOAD = (u32)nms*fac_ms;    //时间装载
    SysTick->VAL = 0x00;                //计数值清0
    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;    //清空计数器
}

(第二部分完,共两部分)

相关推荐
hollq2 小时前
STM32F103RCT6+STM32CubeMX+keil5(MDK-ARM)+Flymcu实现串口重定向
arm开发·stm32·嵌入式硬件
小鱼儿电子3 小时前
17-基于STM32的宠物饲养系统设计与实现
stm32·嵌入式硬件·物联网·宠物·宠物饲养系统
小莞尔5 小时前
【51单片机】【protues仿真】基于51单片机四层电梯系统
单片机·嵌入式硬件
CFZPL5 小时前
使用江科大串口发送函数发送freertos的vTaskList出现跑飞
单片机
F133168929576 小时前
WD5030A,24V降5V,15A 大电流,应用于手机、平板、笔记本充电器
stm32·单片机·嵌入式硬件·51单片机·硬件工程·pcb工艺
易享电子7 小时前
基于单片机电器断路器保护器系统Proteus仿真(含全部资料)
单片机·嵌入式硬件·fpga开发·51单片机·proteus
爱倒腾的老唐9 小时前
01、如何学习单片机
单片机·嵌入式硬件·学习
点灯小铭9 小时前
基于单片机的夹具压力控制系统设计
单片机·嵌入式硬件·mongodb·毕业设计·课程设计
雾削木15 小时前
stm32解锁芯片
javascript·stm32·单片机·嵌入式硬件·gitee
热爱编程的小刘16 小时前
STM32学习路线开启篇:外部中断
stm32