Chinaunix首页 | 论坛 | 博客
  • 博客访问: 82054
  • 博文数量: 38
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 19
  • 用 户 组: 普通用户
  • 注册时间: 2012-09-10 14:58
文章分类
文章存档

2015年(3)

2014年(35)

我的朋友

分类: 嵌入式

2014-05-03 20:44:40

原文地址:Arm linux 中的udelay函数 作者:ispsubb

工作中,调试一个新硬件平台(mx28)上的设备驱动。由于要满足时序上的要求,要用到udelay来作延时。由于时序要求比较严格,在使用过程中,发现了一问题。然后udelay(1) 进行测试,用示波器测出的结果竟然是4.7us ,接近5us。证实了udelay 不准确,并不是1us

跟踪了一下代码。arm 下面的udelay 应该是在arch/arminclude/asm/delay.h

点击(此处)折叠或打开

  1. extern void __udelay(unsigned long usecs);
  2. extern void __const_udelay(unsigned long);
  3. #define MAX_UDELAY_MS 2
  4. #define udelay(n) \
  5.        (__builtin_constant_p(n) ? \
  6. /* gcc的内建函数__builtin_constant_p用于判断n是否为编译时常数,如果n是常数,返回 1,否则返回 0。*/
  7.          ((n) > (MAX_UDELAY_MS * 1000) ? __bad_udelay() : \
  8.                      __const_udelay((n) * 0x68dbul)) : \
  9. /*n> MAX_UDELAY_MS * 1000时出错返回,否则调用函数__const_udelay */
  10.          __udelay(n))

  最终实现是在__udelay, __const_udelay。宏定义udelay(n) 。先判定__builtin_constant_p(n)否。int __builtin_constant_p( exp ) GCC 提供的一个内置函数判断一个值是否是编译时常量。Linux 相当频繁地使用常量检测,来进行优化。这个函数并不能检测出所有的常量。Udelay 的最大延时值为MAX_UDELAY_MS * 1000 ,即最大不能超过2000us,也就是2ms看到了一个奇怪的数字,2199023U*HZ,为什么出现这个数字?猜测__const_delay __udelay 差别就在这个地方。不得不看一下__udelay中的实现。

点击(此处)折叠或打开

  1. //arch/arm/lib/delay.S

  2. .LC0:        .word    loops_per_jiffy
  3. .LC1:        .word    (2199023*HZ)>>11 //HZ = 100

  4. /*
  5.  * r0 <= 2000
  6.  * lpj <= 0x01ffffff (max. 3355 bogomips)
  7.  * HZ <= 1000
  8.  */

  9. ENTRY(__udelay) //udelay 参数不为常数时。
  10.         ldr    r2, .LC1 //装载LC1到r2
  11.         mul    r0, r2, r0 // r0 = r0 * r2 此时r0为udelay参数n
  12. ENTRY(__const_udelay)                @ 0 <= r0 <= 0x7fffff06 //udelay 参数不为常数时。
  13.         ldr    r2, .LC0 //装载LC0到r2
  14.         ldr    r2, [r2]        @ max = 0x01ffffff //将r2作为指针,读取指针指向的数存放 r2中 也就是loops_per_jiffy的值
  15.         mov    r0, r0, lsr #14        @ max = 0x0001ffff //r0中内容右移14位
  16.         mov    r2, r2, lsr #10        @ max = 0x00007fff //r2中内容右移10位
  17.         mul    r0, r2, r0        @ max = 2^32-1 // r0 = r0 * r2
  18.         movs    r0, r0, lsr #6 //r0中内容右移6位, movs为下一指令moveq 更新了CPSR标志位
  19.         moveq    pc, lr
  20. /*到这个地方应该明白是怎么实现的了,就是利用loops_per_jiffy这个值。汇编语言忘记得差不多了,查了一下。搞明白了每行的意思。最后r0就是用来作递减的loops。上面相当于数学算式如下:
  21.     R0 =( ( (n *((2199023*HZ)>>11)) >> 14 ) * (loops_per_jiffy >> 10) ) >> 6
  22. 不明白这具体是怎么算的,为什么出现移位?下面的注释中写到
  23. loops = n * HZ * loops_per_jiffy / 1000000
  24. 这个loops 应该这个值啊。N秒的loops值除以1000000,就应该是N us 的loops 值。为什么会出现这些移位呢?2199023是怎么来的?*/
  25. /*
  26.  * loops = r0 * HZ * loops_per_jiffy / 1000000
  27.  *
  28.  * Oh, if only we had a cycle counter...
  29.  */

  30. @ Delay routine
  31. ENTRY(__delay)
  32.         subs    r0, r0, #1
  33. #if 0
  34.         movls    pc, lr
  35.         subs    r0, r0, #1
  36.         movls    pc, lr
  37.         subs    r0, r0, #1
  38.         movls    pc, lr
  39.         subs    r0, r0, #1
  40.         movls    pc, lr
  41.         subs    r0, r0, #1
  42.         movls    pc, lr
  43.         subs    r0, r0, #1
  44.         movls    pc, lr
  45.         subs    r0, r0, #1
  46.         movls    pc, lr
  47.         subs    r0, r0, #1
  48. #endif
  49.         bhi    __delay //如果为正数就跳转到__delay
  50.         mov    pc, lr
  51. ENDPROC(__udelay)
  52. ENDPROC(__const_udelay)
  53. ENDPROC(__delay)


   为什么会出现这些移位呢?2199023是怎么来的?这个计算式里面就只有一个loops_per_jiffy看起来不是常量,其实也是常量。干脆找了一下loops_per_jiffy的计算方法。

点击(此处)折叠或打开

  1. 在init/calibrate.c
  2. void __init calibrate_delay(void)
  3. {
  4.  unsigned long ticks, loopbit;
  5.  int lps_precision = LPS_PREC;
  6.  loops_per_jiffy = (1<<12); //每滴答循环次数
  7.  printk("Calibrating delay loop… ");
  8.  while (loops_per_jiffy <<= 1) {
  9.   /* 等待一个系统滴答的开始 */
  10.   ticks = jiffies;
  11.   while (ticks == jiffies)
  12.    /* nothing */;
  13.   /* Go .. */
  14.   ticks = jiffies;
  15.   __delay(loops_per_jiffy); //延迟 循环loops_per_jiffy次
  16.   /*
  17.   * 原型
  18.   * void __delay(unsigned long loops)
  19.     * {
  20.    * cur_timer->delay(loops);
  21.     * }
  22.   *
  23.   */
  24.   ticks = jiffies – ticks; //判断是否同一个滴答内
  25.   if (ticks)
  26.    break; //不同则跳出,已经得到一个粗值
  27.  }
  28. /* Do a binary approximation to get loops_per_jiffy set to equal one clock
  29.    (up to lps_precision bits) */
  30.  loops_per_jiffy >>= 1;
  31.  loopbit = loops_per_jiffy;
  32.  /* 第二次计算,采用二分法逐步逼近精确值 */
  33.  while ( lps_precision– && (loopbit >>= 1) ) {
  34.   loops_per_jiffy |= loopbit;
  35.   ticks = jiffies;
  36.   while (ticks == jiffies); //等待一个滴答开始
  37.   ticks = jiffies;
  38.   __delay(loops_per_jiffy); // 循环loops_per_jiffy,先其值为粗值的1/2,
  39.                                              // 而精确值必在粗值和loops_per_jiffy之间
  40.   if (jiffies != ticks) /* longer than 1 tick */
  41.    loops_per_jiffy &= ~loopbit; //倘若大于一个滴答,则说明loops_per_jiffy大于实际值
  42.                                               // 减去loopbit值
  43.  }
  44. .
  45. .
  46. .

     loops_per_jiffy最后可以得出,也就是它确确实实是个常量,并且至少大于(1<<12)。这时候在网上查了一下arm delay 。终于找到了原因。

loops = n * HZ * loops_per_jiffy / 1000000

上面的计算,在非浮点处理器(整型处理器)运行时,很容易变为0 如果loops_per_jiffy * HZ/1000000时,loops_per_jiffy * HZ / 1000000=0。无能你想要延迟多少微秒,总为0。处理方法,除1000000变为乘1/1000000,为保持精度,1/1000000要先左移30位, 变为

 

(1/1000000)<<30  =  2^30 / 1000000 = 2199023U>>11

 

  哎,考虑得非常全面。这就明白了“为什么会出现这些移位呢?2199023是怎么来的?”。2199023 >> 11  = (1/1000000) * 2^30汇编中出现的移位则是为了把向左移的30位移回来。考虑到溢出,所以分成了>>14 , >>10, >>6,最后等同于 >>30 到此处就彻底明白了arm 中的udelay是如何实现的。最初udelay不准,为什么会跑到5us?跟代码似乎也没有什么不对的地方,可能跟loops_per_jiffy有关。

 

参考资料:

http://blog.chinaunix.net/space.php?uid=20657684

 



 

阅读(1516) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~