我在高职教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;    //清空计数器
}

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

相关推荐
远望创客学堂7 小时前
【单片机毕业设计选题24047】-基于阿里云的工地环境监测系统
stm32·单片机·嵌入式硬件·阿里云·云计算·课程设计·arduino
极客小张8 小时前
利用 STM32 实现多协议物联网网关:Modbus/Zigbee 到以太网/Wi-Fi 的数据桥接
stm32·单片机·嵌入式硬件·物联网·网络协议·https·硬件工程
FPGAmaster创新者8 小时前
基于AGX ORIN与FPGA K7实现PCIE高速数据通信/Orin与FPGA高速数据传输/XDMA在linux系统使用教程
linux·嵌入式硬件·fpga开发
XD7429716369 小时前
【TB作品】电子琴,ATMEGA16单片机,Proteus仿真
单片机·proteus·电子琴·atmega
maybe_YX9 小时前
51单片机基础8——单片机控制超声波模块
c语言·单片机·嵌入式硬件·51单片机
@一二三四五9 小时前
STM32 看门狗 HAL
stm32·单片机·嵌入式硬件
北京迅为9 小时前
【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第一篇 嵌入式Linux入门篇-第四章 Ubuntu启用root用户
linux·嵌入式硬件
maybe_YX9 小时前
51单片机基础10——串口实验
c语言·单片机·嵌入式硬件·51单片机
Qingniu0110 小时前
开关电源的电路组成原理
单片机·嵌入式硬件·新能源·电源模块·无刷电机
芯源义码11 小时前
嵌入式软件常用测试工具
单片机