[分享] MSP432学习心得之系统滴答定时器

fish001   2019-12-9 22:08 楼主
   系统滴答定时器,在操作系统中是十分重要的,它可以提供一个好的系统时钟节拍,就和我们的心脏一样,跳动着一定的频率。它则为系统的运行提供了一个好的时间基准。这里呢,我们将使用它来完成一个延时函数的实现,为什么使用它,因为它跳动的很准确,而且配置相对简单,并且不会暂用我们的定时器或者其他外设。再也不用什么i呀,j呀,弄两个for循环在那里跑跑跑的延时,是不是很想尝试一下,我们来看。
 
    MSP432的Cotex-M4的内核是有ARM提供的,所以这里很多东西都可以通用的,我们本次讨论的就是M3和M4中间都带有的一个系统滴答定时器。既然很多东西都一样,那么包括相关的寄存器的定义也都是一样的,所以我这次直接采用了原子的STM32中的延时函数代码,直接拷贝了过来,但是发现了其中一个问题,就是时钟的问题。找到我们432的数据手册中的滴答定时器的描述章节提到了。
image.png
 
    那么这里的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的运行时钟的。
image.png
 
    而这里的CPU使用的是MCLK。
    那么我们所说的这个时钟到底对不对呢。等下我们进行验证。
 
    那么我们现在可以暂时不管时钟是否准确,我们首先先假设为48MHz好了。
这里定义了两个变量,这两个变量为静态全局变量。解释一下为什么定义为静态全局变量。首先我们看下静态是什么意思,字面上看,可以理解该变量处于一种“安静的状态”,也就是时候它是个安静的美男子(或者美女子),那么有一个什么特点呢?就是懒得动。
image.png
 
    举个简单的例子说明(这里使用了百度百科的程序)。
    下面这个是静态的局部变量,而得到的实验结果。
image.png
image.png
   而下面这段代码中就没有static关键字。得到的结果就发生了变化。
image.png
 
     好了,现在我们来看一下加了static关键字的变量为什么“懒得动”了。我们可以看出fun()这个函数返回的是f = f * n这个式子的计算值。那么第一幅图中的代码是1,2,6,24,120的结果,我们分析一下。
     在这个结果中,后面一个结果是由上面以一次计算而得到的f的值,再和后面的n相乘得到新的值,简而言之,当我们再次调用fun()这个函数的时候,是不执行static int f = 1;这个语句的,而下面那副图中没有static就会再次执行f = 1这个语句,所以每次都是1 * n才会得到1,2,3,4,5的结果。
所以呢,加了static之后会使得变量变懒,它会保持上次计算的值,除非进行重新赋值。相信大家通过上面那个代码可以比较清楚的理解到静态的意思。
那么对于静态局部变量和静态全局变量除了“懒”的特点之外,其他的都和我们的局部变量和局部变量一样。
 
     那么我们这里为什么让两个倍乘数定义为静态的也就很好理解了,我一个寄存器拿来数数,我当然数字要么是一直累加或者减小,否则,我数一下,你就给我赋值成初始值,那我不是白数了,数来数去不还是原地踏步。所以这里定义为静态的。
     但是这里如果心细一点会发现,其实这里静态和非静态其实效果是一样的,这里是因为我们定义为全局变量,那么在函数调用的时候不可能再次执行到这两个定义语句,也就是说他们执行的机会就有一次。所以这里如果你改成非静态的全局变量,同样是可以的。但是如果将这两个语句移到我们的函数中有时候需要考虑一下。这里保险起见我们定义成静态的。
image.png
 
     上面我们假设了时钟为48MHz,所以这里暂且我们给fac_us赋值为48,这个应该好理解吧,48MHz的频率,数48下是1us,应该可以理解。不行的话自己算算哈。
fac_ms就毫无疑问了是fac_us的1000倍,这里就直接乘就ok了。
 
    下面我们解释一下延时函数的实现,代码如下:
image.png
 
     因为我是直接从STM32那边的代码直接拷贝过来的,原本以为说可以直接使用的,但是发现不行,编译通过了,但是在代码执行的时候回产生错误,所以就回过头来认真看432的数据手册。怎么说,数据手册就跟我们上学的教科书,而其他的东西就像我们的课外参考书一样,所以最主要的还是我们的教科书,一切都以教科书为准,除了问题当然首先回到我们的教科书,数据手册中找。
    需要查看的的内容如下,红色的方框中标注。
image.png
 
     在这些内容中我们找到了关于432的SysTick的使用说明,这里我们看到了配置的步骤说明,以及我们看到下面的注意事项,其中有一点很重要就是对于432的时钟源的控制位CLKSOURCE这个位必须置一,这一点很重要。
image.png
 
     时钟源控制位置数为1的代码如下:SysTick_CTRL_ENABLE_Msk为使能系统滴答定时器,SysTick_CTRL_CLKSOURCE_Msk就是时钟源控制位置一。
image.png
 
     至于上述代码中为什么用了一个“|”(或)呢?
     这里我们解释一下这个C语言知识,对于一个8位的寄存器来说,这里我们简单起见,就举8位的寄存器作为例子,这里我们如果要实现修改其中一位,却不改变其他位的值,改如何实现的问题,这里我们具体看。
    首先我们默认的8位寄存器的初始值假设我们设置为:1001_0111
    那么现在我们比如说要改变第6位的值(这里我们指的是真实的第六位,为什么这么说,按照平常我们的说法寄存器是从第0位到第7位)。
    我们的实现方法如下:
1001_0111 | 0010_0000 = 1011_0111
    可以看出,通过或的方式我们可以实现只改变一位而不影响其他位的数据。
    那么如果我们要让一位相反,从1变到零,我们可以通过与的方式来实现。比如我们这里改变第五位试下。
1001_0111 & 0001_0000 = 0001_0000
    看到得到的结果并不是我们想要的,这里需要做一个小小的变通,对应后面要进行相与的数据要进行一个取反,之后才可以进行两者的相与。
1001_0111 & (~(0001_0000)) = 1001_0111 & 1110_1111 = 1000_0111
    这样子我们就可以成功实现了不管是置零还是置一都可以不改变其他位的数据,之所以这样做的原因是,430没有办法进行位寻址,所以没有办法对位进行直接操作,只能通过寄存器的方式来进行操作,而对于51来说可以直接进行位寻址。而32实现的方式是通过位绑定的方式进行实现的,这里我们不细说这个位绑定的方式,我们下次再来写一个帖子说明这个问题,了解一下具体的实现方式。
 
    好了,明白了这个我们在看下前面的SysTick->CTRL这个指的是用ARM提供的寄存器指令进行编写的,代表是指向我们SysTick这个模块的控制寄存器,在ARM-Cotex-M4中有四个寄存器来控制SysTick这个模块。我们可以找到他们的说明(这里我没有找到Cotex-M4的中文滴答定时器的说明,但是有Cotex-M3的中文说明,这两者是一样的在滴答定时器这个章节,所以我们引用的是M3的)。
    在这里详细解释了我们滴答定时器的四个寄存器,包括相关的控制位功能,我们需要了解这些东西,但是没有必要去背他,我们只需要知道一个学习的方法,在以后需要用到的时候我们懂得找就可以了。毕竟以后当你出去到企业之后,公司做项目,不可能让你去背一个寄存器的每一位每一位是什么意思,不大现实,也毫无意义。所以,方法很重要,要掌握方法。
 
image.png
 
     我们对比到432手册中给出的寄存器描述,同样有四个寄存器,后面内容有点多,这里就不在截图出来了,通过对比我们会发现两者其实是一模一样的(毕竟是同一家出的东西嘛,ARM)。
 
image.png
 
    按照432中给出的配置步骤:
1.我们第一步需要配置STCVR这个寄存器,也就是我们的SysTick->LOAD,第一步要给出重装载的值,这里我们就要明白,这个重装载的值是24位数据,那么我们就可以计算出我们对应的最大的延时数。24位的寄存器最多可以数16_777_216个数据,那么我们根据我们的时钟频率就可以计算出我们的延时时间。如果是48MHz的话,我们最多可以数349_525这么多us,相应的就可以数34.9ms的时间。所以在这个频率下我们要控制我们的延时时间,不能过高。一般情况下这么长的延时时间我们也是够用的。可以看出48MHz的频率来数数,频率还是有点太高,要获得更长的延时,就要降低时钟频率。
 
2.接下来我们需要配置我们的STCVR这个寄存器,这个寄存器对应我们的SysTick->VAL,是我们当前寄存器的值,我们需要把它清零。
 
3.最后就是控制状态寄存器了,上面我们已经说明了,需要使能SysTick这个模块,同时把时钟源控制位置一。
 
    这样我们完成了所有的配置工作,这里我们还需要明白的是SysTick是一个24位的自动重装载寄存器,我们使能了他,他就会从STCVR这个装载寄存器中载入要计数的值,然后一直向下数,而不是向上数,一直数到0,数到0之后会置位状态寄存器中的COUNTFLAG标志位,我们也是开启了滴答定时器之后一直查询该标志位,看看是否数完了。
image.png
 
    temp&0x01这个查询的是SysTick模块时候使能,查询的是最低位,为什么用&呢?和我们上面说的置1和置0有点类似,实在不行自己写一两个数据验证一下也就明白了。
    temp&(1<<16)这个查询的就是我们上面所说的COUNTFLAG这个标志位,那为什么这里1要左移16位呢?我们看寄存器的描述就可以知道,该位是该寄存器的第16位,所以要左移16位来进行相与。
image.png
 
    需要同时满足temp&0x01&&!(temp&(1<<16)),这两个条件才可以,完成之后就会退出while循环,然后我们关闭SysTick这个模块,因为他是个自动重装载寄存器,不关闭的话,他会再次从重装载寄存器中取出数据继续数数。
    后面的ms延时也是一个道理。就不在解释说明了。
 
    补充说明一个问题,就是我们在配置SysTick->CRTL这个寄存器的时候使用了一个这样的宏定义,如下,前面出现了一个1UL,那么这个UL是什么意思呢?1UL这里指的是无符号长整型数字1,如果不写后缀名的话,系统默认为int型。那为什么这里需要用长整型,是因为这是一个32位的寄存器,整型没有办法表示这么大的需要一个长整型的才可以。后面的SysTick_CTRL_ENABLE_Pos,道理和我们上面的说的查询COUNTFLAG的道理是一样的,左移。
image.png
 
    现在我们要验证一下时钟源到底是哪一个?看手册中的FCLK根本不知道是什么我们通过实际操作验证一下就知道了。
    这里我们采用的是控制变量法,总共有ACLK、BCLK、HSMCLK、MCLK、SMCLK这几个时钟,把其中一个设置为高频率,其他的都设置为低频率,然后点一个灯,目测下闪烁频率就知道是哪个了。
    我们之前已经推算出可能会是MCLK,所以一开始我就直接改了MCLK的频率,很明显的看出灯的闪烁频率发生了很大的改变,一开始我们都是以48MHz为基准,之后我修改为32KHz,中间的落差很大,所以确定SysTick的时钟来源是MCLK,那么现在我们就可以很快的确定我们改如何使用和配置SysTick的延时了。
    中间的修改代码的过程和推理判断就不在说明了。大家只要记住结论就好了,SysTick的时钟是来自MCLK。
     这里在分享一个调试的技巧,如果你不确定你的配置代码是否正确,需要从寄存器的级别来进行查看的话我们可以通过CCS的debug窗口中找到对应的寄存器,点开之后会有每一位的变化情况,暂停代码运行既可查看每一位的运行情况。
image.png
 
   我们可以得到这样的结果。并且旁边还给出了相应的位进行解释,非常的方便。
image.png
 
    最后我们的代码改成下面的,把我们的MCLK的频率选择为高速时钟,在进行16分频,这样就得到比较低的3MHz的频率,这样子就可以进行较长的延时。
同时我们对延时的初始函数进行了修改,这样方便大家理解和使用。
image.png
本帖最后由 fish001 于 2019-12-9 22:15 编辑

回复评论

暂无评论,赶紧抢沙发吧
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复