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

发布于:2024-07-04 ⋅ 阅读:(12) ⋅ 点赞:(0)

        大家好,我是老耿,高职青椒一枚,一直从事单片机、嵌入式、物联网等课程的教学。对于高职的学生层次,同行应该都懂的,老师在课堂上教学几乎是没什么成就感的。正因如此,才有了借助 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所示。

//-------------------------------------------
// 代码清单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所需的计数值。

//------------------------------------------------------
// 代码清单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循环不断关注使能位和计数标志位的变化,时间到了则退出循环,停止计数。

//------------------------------------------------------------
// 代码清单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。

//------------------------------------------------------------
// 代码清单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;    //清空计数器
}

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