/***********************************************************
* Delay for the given number of microseconds. The driver must
* be initialized before calling this function.
***********************************************************/
void udelay(uint32_t usec)
{
assert((timer_ops != NULL) &&
(timer_ops->clk_mult != 0U) &&
(timer_ops->clk_div != 0U) &&
(timer_ops->get_timer_value != NULL));
uint32_t start, delta, total_delta;
assert(usec < (UINT32_MAX / timer_ops->clk_div));
start = timer_ops->get_timer_value();
/* Add an extra tick to avoid delaying less than requested. */
total_delta =
div_round_up(usec * timer_ops->clk_div,
timer_ops->clk_mult) + 1U;
do {
/*
* If the timer value wraps around, the subtraction will
* overflow and it will still give the correct result.
*/
delta = start - timer_ops->get_timer_value(); /* Decreasing counter */
} while (delta < total_delta);
}
第16行的get_timer_value傳回的是一個從0xFFFFFFFF到0的遞減的數,減到0後,再往下減的話,會重新變成0xFFFFFFFF。
其中第25到26行注釋說即使發生了wraps around,也可以保證delta的值是正确的。下面我們看看是什麼原理。
為此,下面是一個簡單的模拟程式,看看發生wraps around後,兩個數字相減的結果,假設下面的a,b,c的範圍都是從0到0xFF,a表示第一次讀到的數,b表示第二次讀到的數。
#include <stdio.h>
int main(int argc, const char *argv[])
{
unsigned char a, b, c;
a = 0x20;
b = 0x00;
c = a - b;
printf("a(0x%x) - b(0x%x) = %x\n", a, b, c);
a = 0x00;
b = 0xE0;
c = a - b;
printf("a(0x%x) - b(0x%x) = %x\n", a, b, c);
a = 0x00;
b = 0x20;
c = a - b;
printf("a(0x%x) - b(0x%x) = %x\n", a, b, c);
a = 0xF0;
b = 0x10;
c = a - b;
printf("a(0x%x) - b(0x%x) = %x\n", a, b, c);
return 0;
}
下面是運作結果:
a(0x20) - b(0x0) = 20
a(0x0) - b(0xe0) = 20
a(0x0) - b(0x20) = e0
a(0xf0) - b(0x10) = e0
可以看到,如果是以無符号數輸出的話,上面的大數減小數和小數減大數的結果是一樣的。
可以直覺的了解,在模為10的情況下,一個數加上A,跟減去(10-A)的結果一樣:比如 (3 + 8)%10 = 1,而(3-(10-8))%10 = 1,二者一樣。
或者可以手動計算一下:0 - 0xe0,理論上應該等于-0xe0,那麼這個數字在計算機中如何表示呢?也就是-0xe0的補碼是多少?根據負數求補碼的方法,其絕對值各位取反然後再加1:
-0xe0 --> 0xe(絕對值) --> 0b11100000(二進制表示) --> 0b00011111(取反) --> 0b00100000(加一) --> 0x20,即在模為0x100的情況下,-0xe0跟0x20是一回事。
完。