我正在浏览一些嵌入式编程链接 http://www.micromouseonline.com/2016/02/02/systick-configuration-made-easy-on-the-stm32/
[请注意,以上链接的代码作者自此以来已更新了代码,并且本文中的delay_ms()
实现不再使用下面显示的解决方案。]
,并得到以下代码。这是针对ARM体系结构的,但从根本上讲,这是一个函数,它将导致某些毫秒的延迟作为参数传递。所以我可以理解if条件,但是为什么在这里else条件是必要的呢?有人可以给我解释一下吗?
void delay_ms (uint32_t t)
{
uint32_t start, end;
start = millis();//This millis function will return the system clock in
//milliseconds
end = start + t;
if (start < end) {
while ((millis() >= start) && (millis() < end))
{ // do nothing }
}
else{
while ((millis() >= start) || (millis() < end))
{
// do nothing
};
}
}
答案 0 :(得分:4)
如果start + t
导致溢出,则end
小于start
,因此if
部分处理该问题,而else
部分处理“正常”的情况。
说实话,要花一些时间来确定它实际上是否正确,但是由于它在解决一个简单问题上有点过于复杂,因此几乎不值得。所需的只是以下内容:
void delay_ms( uint32_t t )
{
uint32_t start = millis() ;
while( (uint32_t)millis() - start < t )
{
// do nothing
}
}
要了解其工作原理,可以想象start
是0xFFFFFFFF
,而t
是0x00000002
,两毫秒后,millis()
将是0x00000001
,而millis() - start
将是2
。
循环将在millis() - start == 3
时终止。可以说,您可能更喜欢millis() - start <= t
,但是延迟会在t-1
到t
毫秒之间,而不是至少 t
毫秒({{1 }}到t
)。您可以选择,但是要点是您应该比较计算和比较时间段,而不是尝试比较绝对时间戳值-简单得多。
答案 1 :(得分:3)
请注意,第二个while循环具有||
而不是&&
。这是为了处理溢出。如果start + t
大于end
可以容纳的数字,会发生什么?答案是您将获得溢出。当您将1加上一个保留其最大值的无符号整数时,它将溢出为0。
以下是显示溢出影响的简短代码段:
uint8_t c = 255;
uint8_t d = c+1;
printf("%d %d", c, d);
这将打印255 0
在这种情况下,这种溢出甚至不是不太可能的情况。 uint32_t
可以容纳的最大值约为4 *10⁹。那是46天过去的毫秒数。
下面是一个代码片段,可以自己进行测试:
#include <stdio.h>
#include <stdint.h>
int main()
{
uint32_t s = -2; // Initialize s to it's maximum number
// minus one via overflow
uint32_t e, t;
scanf("%d", &t);
e = s+t;
if(s<e)
printf("foo\n");
else
printf("bar\n");
}
当我用不同的输入运行它时:
$ ./a.out
0
bar
$ ./a.out
1
foo
但是,代码太糟糕了。有关原因的说明,请参见Cliffords answer。
答案 2 :(得分:1)
此代码正在尝试处理过渡。拿一个8位计数器/定时器进行计数,可以说从0x00到0xFF,然后再次翻转到0x00。如果要使用此计时器等待16个计时器滴答(0x10)(使用轮询),并且在启动该延迟时计时器为0x1B,那么我要等待0x1B + 0x10 = 0x2B。
开始= 0x1B,结束= 0x2B,对于值0x1B,0x1C 0x1D ... 0x29、0x2A,我们要延迟,开始小于结束,因此只要t不大于计时器,此代码中没有计数器翻转我们不能根据此代码假定它,所以:
while((now>=start)&&(now<end)) continue;
该循环中现在采样的位置(while(((millis()> = start)。)
正如您所指出的,可以说
start = 0xF5,所有变量的大小相同,均为32位,挖掘它们 都是8个,因此0xF5 + 0x10 = 0x05,结束= 0x05,所以我们想延迟,现在是0xF5、0xF6、0xF7、0xF8 ... 0xFF 0x00 0x01 0x02 0x03 0x04。因此,我们必须拆分必须覆盖的案例,现在> =开始将0xF5覆盖为0xFF,现在
while((now> = start)||(now
但是正如Clifford所指出的那样,基本的编程知识,一些不可思议的二分法补充了现在的第一个数字(0xFD至0x05):
(0xFD - 0xF5) & 0xFF = 0x08
(0xFE - 0xF5) & 0xFF = 0x09
(0xFF - 0xF5) & 0xFF = 0x0A
(0x00 - 0xF5) & 0xFF = 0x0B
(0x01 - 0xF5) & 0xFF = 0x0C
(0x02 - 0xF5) & 0xFF = 0x0D
(0x03 - 0xF5) & 0xFF = 0x0E
(0x04 - 0xF5) & 0xFF = 0x0F
(0x05 - 0xF5) & 0xFF = 0x10
完美无缺,我们只需要现在就减去-开始增加一个计数器,并将数学掩盖为计数器和/或变量中较小的一个
while((((现在开始)&0xFF)
要开始使用计数器,请立即开始。
该代码的作者试图在不理解二进制补码的情况下处理过渡。早在我了解这一点的前一天,我的代码甚至更糟时,我大概是在计算翻转之前的计数数量然后计算之后的计数数量,不管持续了多久,我一直坚信自己只要您掩盖它。
现在说
如果您的计时器没有在全1到全0或全0到全1之间滚动,可以编程许多微控制器计时器,而您没有完全使用计时器功能,那么上述方法将无法正常工作。如果您有一个8位定时器,并且它的计数范围是从0x00到0x53,那么它会翻转到0x00,因为您出于某种原因设置了它,或者您将已设置为周期性中断的定时器重新用作轮询计时器,那么您必须以一种更好的方式来完成该代码的作者所做的事情:
同时(现在<0x53)继续; 虽然(现在>结束)继续;
现在每个循环采样。
但是end的数学运算并不自然,您必须将end计算为
end = start + t;
if(end>0x53) end = end - 0x54;
在那种情况下,虽然人们会想知道为什么要使用这样的计时器来进行轮询超时,但请使用一个对所有位进行计数并翻转以进行轮询超时的计时器,使用一个从N开始计数或从0开始计数为零的计时器N表示定期的周期性中断,或者也可以轮询这些中断,但是在那种情况下,您不一定要进行一般的延迟....好...您可以...将计时器设置为一毫秒,然后对翻转标志计数直到到t。比现在更容易-开始数学。并可以根据您的需要进行计数。
Millis()在您和计时器之间放置了一个抽象层,并且我们假定Millis从所有的整数转换为所有的零。
如果您有一个计时器,例如从0x00000000到0x55555555计数并翻转,那么您也无法裁剪它以尝试使该数学正常工作
while(((start-now)&0xFFFF)<t) continue;
那是行不通的,因为在某些情况下0xXXXX5555会滚动到0xXXXX0000,这会给您一次很短的计数。
while((((start-now)&mask))的快捷键
否则,您必须做一些修改后发现的代码。
我也建议不要将定时器多次采样。我经常看到这个错误:
while((timer()-start)<timeout)
{
if(read_register(n)&0x10) break;
}
if((timer()-start)>=timeout)
{
printf("Timed out\n");
return 1;
}
return 0;
也许是因为同一个同事会一遍又一遍地实现它...
您想为这两种用例采样一次,如果稍后再采样 这可能会导致时间不同。条件可能已在超时内通过,但现在重新采样计时器会超时。
类似
while(1)
{
delta=timer()-start;
if(delta>t) break;
if(register_read(n)&0x10) break;
}
if(delta>t)
{
printf(...
return 1;
}
return 0;
出于相同的原因,您不一定必须返回并再次读取寄存器以查看是否是循环中断的原因。取决于该寄存器。
是的,有些人可能会讨厌这里的编码风格,并且也考虑到一个错误,如果在代码中多次重复使用该时间戳,则是尽量减少对定时器的采样次数您以书面形式发布就可以了,因为C应该从左到右进行评估
millis() >= start
then
millis() < end
已经写好了
while ((millis() < end) && (millis() >= start))
while ((millis() < end) || (millis() >= start))
开始
对于start> end的情况,如果第一次读取的数据小于end,并且它会增加,因此 等于或大于结束,以便第二次读取以开始比较,然后相同 交易您必须再次循环以捕获不小于end的millis()。
所以这是一个额外的示例,但效果很好。该代码本可以简化为
if(start<end)
{
while(millis()<end) continue;
}
else
{
while(millis()>=start) continue;
while(millis()<end) continue;
}
每个循环采样一次。
还请注意,这种轮询计时器仅在大多数情况下在轮询循环中超出了计时器的时间,没有中断需要很长时间才能使计时器再次翻转时才起作用。如果采样速度不是那么快,则可能要限制t的大小,使其不超过计时器中位数的一半。您应该限制它的数量取决于您可以多快地采样此东西。
轮询时间循环仅应在该时间或更长时间使用,如果您要计时特定的时间,则延迟可能会持续很长时间,除非您小心,如果遇到中断或其他事情,则可能会运行很长时间,也许很长。因此,如果您想定时至少说10毫秒,但又说100毫秒,那是可以的,比如i2c或spi的撞击,那很好。但是,如果您要尝试敲击uart,则轮询计时器可能并不是最好的选择,这取决于您的系统设计。