MSP432P401R的系统滴答定时器Systick - 图1
    Update on 2021.1.2
    系统滴答定时器,在操作系统中是十分重要的,它可以提供一个好的系统时钟节拍,就和我们的心脏一样,跳动着一定的频率。它则为系统的运行提供了一个好的时间基准。这里呢,我们将使用它来完成一个延时函数的实现,为什么使用它,因为它跳动的很准确,而且配置相对简单,并且不会暂用我们的定时器或者其他外设。再也不用什么i,j呀,弄两个for循环在那里跑跑跑的延时,是不是很想尝试一下,我们来看。
    MSP432的Cotex-M4的内核是有ARM提供的,所以这里很多东西都可以通用的,我们本次讨论的就是M3和M4中间都带有的一个系统滴答定时器。既然很多东西都一样,那么包括相关的寄存器的定义也都是一样的,所以我这次直接采用了原子的STM32中的延时函数代码,直接拷贝了过来,但是发现了其中一个问题,就是时钟的问题。找到我们432的数据手册【MSP432P4xx SimpleLink™ Microcontrollers Technical Reference Manual (Rev. I)】82页中的滴答定时器的描述章节提到了。
    MSP432P401R的系统滴答定时器Systick - 图2
    那么这里的free running clock(FCLK)这里具体指的是什么呢?
    ARM技术注:FCLK 为处理器的自由振荡的处理器时钟,用来采样中断和为调试模块计时。在处理器休眠时,通过FCLK 保证可以采样到中断和跟踪休眠事件。 Cortex-M3内核的“自由运行时钟(free running clock)”FCLK。“自由”表现在它不来自系统时钟HCLK,因此在系统时钟停止时FCLK 也继续运行。FCLK和HCLK 互相同步。FCLK 是一个自由振荡的HCLK。FCLK 和HCLK 应该互相平衡,保证进入Cortex-M3 时的延迟相同。所以这里默认情况下,Systick的时钟是来自CPU的运行时钟的。
    MSP432P401R的系统滴答定时器Systick - 图3
    而这里的CPU使用的是MCLK。那么我们所说的这个时钟到底对不对呢。等下我们进行验证。那么我们现在可以暂时不管时钟是否准确,我们首先先假设为48MHz好了。
    这里定义了两个变量,这两个变量为静态全局变量。

    1. static uint8_t fac_us = 0; //us延时倍乘数
    2. static uint16_t fac_ms = 0; //ms延时倍乘数

    加了static之后会使得变量变懒,它会保持上次计算的值,除非进行重新赋值。那么我们这里为什么让两个倍乘数定义为静态的也就很好理解了,我一个寄存器拿来数数,我当然数字要么是一直累加或者减小,否则,我数一下,你就给我赋值成初始值,那我不是白数了,数来数去不还是原地踏步,所以这里定义为静态的。
    但是这里如果心细一点会发现,其实这里静态和非静态其实效果是一样的,这里是因为我们定义为全局变量,那么在函数调用的时候不可能再次执行到这两个定义语句,也就是说他们执行的机会就有一次。所以这里如果你改成非静态的全局变量,同样是可以的。但是如果将这两个语句移到我们的函数中有时候需要考虑一下。这里保险起见我们定义成静态的。

    1. //初始化延迟函数
    2. //SYSTICK的时钟固定为HCLK时钟的1/8
    3. //SYSCLK:系统时钟
    4. void delay_init(uint8_t SYSCLK)
    5. {
    6. fac_us = SYSCLK;
    7. fac_ms = (uint16_t) fac_us * 1000;//ms是us的1000倍
    8. }

    上面我们假设了时钟为48MHz,所以这里暂且我们给fac_us赋值为48,这个应该好理解吧,48MHz的频率,数48下是1us,应该可以理解。不行的话自己算算哈。fac_ms就毫无疑问了是fac_us的1000倍,这里就直接乘就ok了。
    下面我们解释一下延时函数的实现,代码如下:

    1. //延时nms
    2. //注意nms的范围
    3. //SysTick->LOAD为24位寄存器,所以,最大延时为:
    4. //nms<=0xffffff*8*1000/SYSCLK
    5. //SYSCLK单位为Hz,nms单位为ms
    6. //对72M条件下,nms<=1864
    7. void delay_ms(uint16_t nms)
    8. {
    9. uint32_t temp;
    10. SysTick->LOAD = (uint32_t) nms * fac_ms; //时间加载(SysTick->LOAD为24bit)
    11. SysTick->VAL = 0x00; //清空计数器
    12. SysTick->CTRL = SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_CLKSOURCE_Msk; //开始倒数
    13. do
    14. {
    15. temp = SysTick->CTRL;
    16. }
    17. while (temp & 0x01 && !(temp & (1 << 16))); //等待时间到达
    18. SysTick->CTRL &= ~SysTick_CTRL_CLKSOURCE_Msk; //关闭计数器
    19. SysTick->VAL = 0X00; //清空计数器
    20. }

    因为我是直接从STM32那边的代码直接拷贝过来的,原本以为说可以直接使用的,但是发现不行,编译通过了,但是在代码执行的时候回产生错误,所以就回过头来认真看432的数据手册。怎么说,数据手册就跟我们上学的教科书,而其他的东西就像我们的课外参考书一样,所以最主要的还是我们的教科书,一切都以教科书为准,除了问题当然首先回到我们的教科书,数据手册【MSP432P4xx SimpleLink™ Microcontrollers Technical Reference Manual (Rev. I)】中找。需要查看的的内容如下,红色的箭头已经标注。
    MSP432P401R的系统滴答定时器Systick - 图4
    在这些内容中我们找到了关于432的SysTick的使用说明,这里我们看到了配置的步骤说明,以及我们看到下面的注意事项,其中有一点很重要就是对于432的时钟源的控制位CLKSOURCE这个位必须置一,这一点很重要。
    MSP432P401R的系统滴答定时器Systick - 图5
    时钟源控制位置数为1的代码如下:SysTick_CTRL_ENABLE_Msk为使能系统滴答定时器,SysTick_CTRL_CLKSOURCE_Msk就是时钟源控制位置一。

    SysTick->CTRL = SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_CLKSOURCE_Msk;//系统滴答时钟状态寄存器配置
    

    至于上述代码中为什么用了一个“|”(或)呢?这种方式实现了不管是置0还是1都可以不改变其他位的数据,之所以这样做的原因是,430没有办法进行位寻址,所以没有办法对位进行直接操作,只能通过寄存器的方式来进行操作,而对于51来说可以直接进行位寻址。而STM32实现的方式是通过位绑定的方式进行实现的。
    好了,明白了这个我们在看下前面的SysTick->CTRL这个指的是用ARM提供的寄存器指令进行编写的,代表是指向我们SysTick这个模块的控制寄存器,在ARM-Cotex-M4中有四个寄存器来控制SysTick这个模块。我们可以找到他们的说明(这里我没有找到Cotex-M4的中文滴答定时器的说明,但是有Cotex-M3的中文说明,这两者是一样的在滴答定时器这个章节,所以我们引用的是M3的)SysTick control and status register (STK_CTRL)、SysTick reload value register (STK_LOAD)、SysTick current value register (STK_VAL)、SysTick calibration value register (STK_CALIB)
    MSP432P401R的系统滴答定时器Systick - 图6MSP432P401R的系统滴答定时器Systick - 图7MSP432P401R的系统滴答定时器Systick - 图8MSP432P401R的系统滴答定时器Systick - 图9
    在这里详细解释了我们滴答定时器的四个寄存器,包括相关的控制位功能,我们需要了解这些东西,但是没有必要去背他,我们只需要知道一个学习的方法,在以后需要用到的时候我们懂得找就可以了。毕竟以后当你出去到企业之后,公司做项目,不可能让你去背一个寄存器的每一位每一位是什么意思,不大现实,也毫无意义。所以,方法很重要,要掌握方法。
    MSP432P401R的系统滴答定时器Systick - 图10
    我们对比到上图432手册中给出的寄存器描述,同样有四个寄存器,后面内容有点多,这里就不在截图出来了,通过对比我们会发现两者其实是一模一样的(毕竟是同一家出的东西嘛,ARM)。
    按照432中给出的配置步骤:
    1、我们第一步需要配置STCVR这个寄存器,也就是我们的SysTick->LOAD,第一步要给出重装载的值,这里我们就要明白,这个重装载的值是24位数据,那么我们就可以计算出我们对应的最大的延时数。24位的寄存器最多可以数2^24=16777216个数据,那么我们根据我们的时钟频率就可以计算出我们的延时时间。如果是48MHz的话,我们最多可以数2^24/48000000=349525us=349.525ms,相应的就可以数34.9ms的时间。所以在这个频率下我们要控制我们的延时时间,不能过高。一般情况下这么长的延时时间我们也是够用的。可以看出48MHz的频率来数数,频率还是有点太高,要获得更长的延时,就要降低时钟频率。

    SysTick->LOAD = (uint32_t) nms * fac_ms;   //时间加载(SysTick->LOAD为24bit)
    

    2、接下来我们需要配置我们的STCVR这个寄存器,这个寄存器对应我们的SysTick->VAL,是我们当前寄存器的值,我们需要把它清零。

    SysTick->VAL =0x00;//清空计数器
    

    3、最后就是控制状态寄存器了,上面我们已经说明了,需要使能SysTick这个模块,同时把时钟源控制位置一。

    SysTick->CTRL = SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_CLKSOURCE_Msk; //使能Systick并且打开时钟源
    

    4、这样我们完成了所有的配置工作,这里我们还需要明白的是SysTick是一个24位的自动重装载寄存器,我们使能了他,他就会从SysTick reload value register (STK_LOAD)这个装载寄存器中载入要计数的值,然后一直向下数,而不是向上数,一直数到0,数到0之后会置位状态寄存器中的COUNTFLAG标志位,我们也是开启了滴答定时器之后一直查询该标志位,看看是否数完了。

    do
    {
       temp = SysTick->CTRL;//将整个32位寄存器SysTick control and status register (STK_CTRL)赋值给temp
    }
    while (temp & 0x01 && !(temp & (1 << 16)));          //等待时间到达
    SysTick->CTRL &= ~SysTick_CTRL_CLKSOURCE_Msk;       //关闭计数器
    SysTick->VAL = 0X00;       //清空计数器
    

    temp & 0x01 , 这个查询的是SysTick模块时候使能,查询的是最低位。
    temp & ( 1 << 16 ) , 这个查询的就是我们上面所说的COUNTFLAG这个标志位,那为什么这里1要左移16位呢?我们看寄存器的描述就可以知道,该位是该寄存器的第16位,所以要左移16位来进行相与。
    MSP432P401R的系统滴答定时器Systick - 图11
    需要同时满足 (temp & 0x01) && !( temp & ( 1 << 16 ) ),这两个条件才可以。完成之后就会退出while循环,然后我们关闭SysTick这个模块,因为他是个自动重装载寄存器,不关闭的话,他会再次从重装载寄存器中取出数据继续数数。后面的ms延时同理。
    补充说明一个问题,就是我们在配置SysTick->CRTL这个寄存器的时候使用了一个这样的宏定义,如下,前面出现了一个1UL,那么这个UL是什么意思呢?1UL这里指的是无符号长整型数字1,如果不写后缀名的话,系统默认为int型。那为什么这里需要用长整型,是因为这是一个32位的寄存器,整型没有办法表示这么大的需要一个长整型的才可以。后面的SysTick_CTRL_ENABLE_Pos,道理和我们上面的说的查询COUNTFLAG的道理是一样的,左移。

    #define SysTick_CTRL_CLKSOURCE_Pos  2U  /*!< SysTick CTRL: CLKSOURCE Position */
    #define SysTick_CTRL_CLKSOURCE_Msk  (1UL << SysTick_CTRL_CLKSOURCE_Pos) /*!< SysTick CTRL: CLKSOURCE Mask */
    

    现在我们要验证一下时钟源到底是哪一个?看手册中的FCLK根本不知道是什么我们通过实际操作验证一下就知道了。这里我们采用的是控制变量法,总共有ACLK、BCLK、HSMCLK、MCLK、SMCLK这几个时钟,把其中一个设置为高频率,其他的都设置为低频率,然后点一个灯,目测下闪烁频率就知道是哪个了。 我们之前已经推算出可能会是MCLK,所以一开始我就直接改了MCLK的频率,很明显的看出灯的闪烁频率发生了很大的改变,一开始我们都是以48MHz为基准,之后我修改为32KHz,中间的落差很大,所以确定SysTick的时钟来源是MCLK,那么现在我们就可以很快的确定我们改如何使用和配置SysTick的延时了。所以,SysTick的时钟是来自MCLK。
    这里在分享一个调试的技巧,如果你不确定你的配置代码是否正确,需要从寄存器的级别来进行查看的话我们可以通过CCS的Debug窗口中找到对应的寄存器,点开之后会有每一位的变化情况,暂停代码运行既可查看每一位的运行情况。
    MSP432P401R的系统滴答定时器Systick - 图12MSP432P401R的系统滴答定时器Systick - 图13
    最后我的代码如下,把我们的MCLK的频率选择为48MHz。

    //初始化延迟函数
    //SYSTICK的时钟固定为HCLK时钟的1/8
    //SYSCLK:系统时钟
    void delay_init(uint8_t SYSCLK)
    {
        fac_us = SYSCLK;
        fac_ms = (uint16_t) fac_us * 1000;//ms是us的1000倍
    }
    //延时nms
    //注意nms的范围
    //SysTick->LOAD为24位寄存器,所以,最大延时为:
    //nms<=0xffffff*8*1000/SYSCLK
    //SYSCLK单位为Hz,nms单位为ms
    //对72M条件下,nms<=1864
    void delay_ms(uint16_t nms)
    {
        uint32_t temp;
        SysTick->LOAD = (uint32_t) nms * fac_ms;   //时间加载(SysTick->LOAD为24bit)
        SysTick->VAL = 0x00;           //清空计数器
        SysTick->CTRL = SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_CLKSOURCE_Msk;          //开始倒数
        do
        {
            temp = SysTick->CTRL;
        }
        while (temp & 0x01 && !(temp & (1 << 16)));          //等待时间到达
        SysTick->CTRL &= ~SysTick_CTRL_CLKSOURCE_Msk;       //关闭计数器
        SysTick->VAL = 0X00;       //清空计数器
    }
    //延时nus
    //nus为要延时的us数.
    void delay_us(uint32_t nus)
    {
        uint32_t temp;
        SysTick->LOAD = nus * fac_us; //时间加载
        SysTick->VAL = 0x00;        //清空计数器
        SysTick->CTRL = SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_CLKSOURCE_Msk; //开始倒数
        do
        {
            temp = SysTick->CTRL;
        }
        while (temp & 0x01 && !(temp & (1 << 16)));      //等待时间到达
        SysTick->CTRL &= ~SysTick_CTRL_CLKSOURCE_Msk;       //关闭计数器
        SysTick->VAL = 0X00;       //清空计数器
    }