延时函数的声明(关于SYSTICK延时函数的两个小疑问)

我们知道,STM32库函数里通常使用来自内核的系统定时器SYSTICK作为时基,实现计数延时。一般来讲,ST公司提供的库函数里将SYSTICK定时器配置为1ms的定时器中断,每产生1ms中断则相关中断事件计数变量加一。具体应用中我们经常会调用那个Delay()函数以实现计数定时,做延时或超时管理。

延时函数的声明(关于SYSTICK延时函数的两个小疑问)(1)

延时函数的声明(关于SYSTICK延时函数的两个小疑问)(2)

有人在阅读ST提供的LL库里的这个延时函数时,发现代码里对延时参数总是做了个加1操作,代码如下:

延时函数的声明(关于SYSTICK延时函数的两个小疑问)(3)

上图中红色代码,程序进来后就对给定的延时参数做了个加1操作,这不将1ms延时变成2ms了吗?

其实,这个地方已经有做了注释,就是为了保证有1个最小的延时等待,函数参数给定1ms的延时,经过这样加1操作后就能保证至少1ms的实际延时,极限情况的确可能达到2ms,但不会超过2ms。但如果这里不做加1操作,函数参数给定1ms的延时,实际延时最短的情况的极限是0,最长的极限是1ms.

我们不妨借助下面图形一起看看,可能更直观点。

延时函数的声明(关于SYSTICK延时函数的两个小疑问)(4)

假设调用Delay函数时给定延时参数为3,首先读到的滴答中断计数值为5,显然在上图的绿色区除了两个边界点外的任一时刻读到的数据都是5。如果先不对延时参数加1,那么实际延时就落在(2ms,3ms)之间;如果对延时参数先加1,那么实际延时就落在(3ms,4ms)之间。换言之,拟定延时n个ms,若做加一操作,实际延时落在(n,n 1)ms开区间;若不做加1操作,实际延时落在(n-1,n)ms开区间。

那这个的加1操作是必须的吗?我不这么认为,加不加1没有原则性问题。当我们把该定时器的溢出中断时间定好后,精度就定了,以1ms中断为例,我们就得接受1ms内的误差。这个地方知道怎么回事就好,毕竟中断代码的编写、延时参数的拟定都是我们自己定的。

上面代码是ST提供的LL库里关于那个延时函数的写法,看看HAL库的写法是不是一样的,不妨顺便看看。【下面截图便是Hal_Delay()函数代码】

延时函数的声明(关于SYSTICK延时函数的两个小疑问)(5)

显然,在HAL库里的Delay函数里进来后也首先做了加1操作,跟LL库的做法一样。

上面HAL库延时函数代码里的Tickstart是调用Delay()函数时首次读到的滴答中断计数变量的值,wait是延时值,Hal_GetTick()函数动态读取到的滴答中断计数变量的值【uwTick】。对于上面while语句里面的uwTick与Tickstart的比较的写法,有人可能会产生疑问。当Hal_GetTick()函数读取到的滴答中断累加值uwTick小于或等于Tickstart时还能得出正确的比较结果吗?

比方像下面这种情况,某无符号整型变量循环累加计数,现在需求得Val2与Val1的差值以测试信号宽度。

延时函数的声明(关于SYSTICK延时函数的两个小疑问)(6)

嵌入式物联网需要学的东西真的非常多,千万不要学错了路线和内容,导致工资要不上去!

无偿分享大家一个资料包,差不多150多G。里面学习内容、面经、项目都比较新也比较全!某鱼上买估计至少要好几十。

点击这里找小助理0元领取:加微信领取资料

延时函数的声明(关于SYSTICK延时函数的两个小疑问)(7)

假定上面数据的计数周期为T,且Val2与Val1的间隔不超过1T。此时二者的间隔用数学表达式就是:Val2 T-Val1。这里的代码似乎没有考虑这个溢出周期的问题?可是那个uwTick完全可能出现小于tickstart的情况啊。

延时函数的声明(关于SYSTICK延时函数的两个小疑问)(8)

这是怎么回事呢?我也一度很怀疑这个写法,当时也做了些简单测试。对于测试结果,现在回想起来当时也没做冷静的分析而做出了错误的判断。

回过头来想,按理说这个代码不该有问题。毕竟是老代码了,何况之前也没有人反映这个地方有问题。难道哪里误会了?

因为当前代码用到的几个变量都是32位无符号数,刚开始测试时也是基于32位数做的,结果动不动就很大,不容易判断正误。我干脆将相关数据全部改为8位无符号数,这样测试起来就方便些。

我先准备了下面的0~255的循环计数表格。显然计数溢出周期为256。

延时函数的声明(关于SYSTICK延时函数的两个小疑问)(9)

现在要在任意一个不大于1个计数周期内求取任意2个数据之间隔,即求这两个无符号数之差。假设计算上图中第1行数据5与第2行数据3的间隔值。这里用StartValue表示5,NowValue表示3,Result表示结果,都定义为8位无符号类型。

参照库代码写法,代码该这样写:Result1= NowValue – StartValue;

按照我的想法,代码该这样写: Result2= NowValue 256 – StartValue;

经过测试,结果是一样的,也不难验证是正确的。

延时函数的声明(关于SYSTICK延时函数的两个小疑问)(10)

对于这个结果,意外,但也意料之中。因为我在添加那个256周期值时心里就在犯嘀咕,有所警觉了。我现在是两个8位数的加减,加个256按理是不会影响结果的,这样推理下来,两行代码的结果就本该一样。

我们再回头看看上面HAL库函数中延时函数:

延时函数的声明(关于SYSTICK延时函数的两个小疑问)(11)

这里tickstart和wait以及函数HAL_GetTick()读到的变量是uwTick值都是32位无符号变量,显然,uwTick与tickstart的延时间隔也不会超过0xfffffff。

如果uwTick小于或等于tickstart,通常我们需要加上溢出周期,这个周期值等于32位数据模值,用16进制表示就是1后面跟8个0。经过测试发现下面代码的两种写法在微处理器的计算结果也是一样的。【uwTick对应下面截图代码中的NowValue, uwTick对应StartValue】

延时函数的声明(关于SYSTICK延时函数的两个小疑问)(12)

因为都定义成32位数据宽度了,结果很大,但是正确的。很明显上图中A行代码更简洁。看来,库代码这里这样写是没问题的。

上面两次测试除了位宽差别外,其它都一样。怎么感觉求算不超过1个计量周期宽度内任意两个数据的间隔值不用考虑那个溢出周期呢?

其实,只要是任意两个相同无符号数据类型数据做减法,且数据所能计量的最大值为其满量程值【比如8位对应255,16位对应65535】,求算任意一个周期内两个数据的间隔值时,使用上面A行代码写法是没有问题的,但这并不等于说不用考虑溢出周期的问题。

以现在测试为例。A行程序代码在微处理里完成的就是数学表达式NowValue 加上 溢出周期值 减去 StartValue的结果,我们不妨将A行代码的计算过程做个翻译解读就看得出来。只是B行代码里再多加1个溢出周期值也不影响计算结果。【注:前面两个测试的溢出周期值刚好等于各自所用数据宽度的模值。】

但实际应用中,数据变量的计数周期往往并不等于所用数据宽度的模值。此时前面A行代码的写法就行不通了,而要用B行代码写法。即A行代码写法只能用在特定场合,B行代码写法具有普适性。

我们不妨看看下面表格。这个表格跟前面0~255的表格类似,只是换成由一系列0~99的数据周期性排列而成。

延时函数的声明(关于SYSTICK延时函数的两个小疑问)(13)

同样,我们求第一行的数据5与第2行的数据3之间的间隔数,这里的数据都定义成uint8类型。显然这里计数周期为100,而非256,此时的代码就不能采用前面A行代码写法。

我们可以看看验证结果:【显然第一种写法的结果是错的,不难判断正确结果是98】

延时函数的声明(关于SYSTICK延时函数的两个小疑问)(14)

我们继续看一个实战性的例子,看一个有关使用TIMER测量脉冲宽度的例子。

下图斜线表示计数器计数方向,现在利用TIMER的捕获功能测量下面一段脉冲的高电平宽度。

延时函数的声明(关于SYSTICK延时函数的两个小疑问)(15)

假设TIMER为16位,计数器向上单向计数。于脉冲的上沿和下沿事件分别捕捉到两个计数值Val1和Val2,二者都定义成16位无符号整型变量。

结合图形可以看出,脉冲虽然跨越了溢出点,但脉冲宽度没有超过1个计数周期,Val2的值小于Val1。此时我们求算t1的宽度,正常都会这样写:

t1 = Val2 ARR 1 - Val1;

当然,如果说你把ARR刚好设置为0xfffff, 此时程序代码就可以简化t1 = Val2 - Val1

OK,关于STM32片内Systick定时器延时中断应用的两个小疑问就聊到这里。问题虽小,但可以牵扯出不少东西供探究。。

中秋快乐!

***********************************

转载自:茶话MCU

文章来源于关于SYSTICK延时函数的两个小疑问

原文链接:https://mp.weixin.qq.com/s/ZSW_7BFTO9TDG_zoeUCA6A

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页