Airwill的笔记 https://bbs.21ic.com/?5782 [收藏] [复制] [RSS]

日志

我的 STM32 到底跑多快? 循环指令的延时特性分析

已有 2414 次阅读2010-4-19 08:26 |系统分类:ARM| 单片机

2010-2-5
我在 STM32F103RB 的开发板上, 写如下代码:



  uint32_t x = 0x009CF260, y;
  while (1)  {
   if (--x ==0) {
  x = 0x009CF260;
  y = 1;
  if(GPIOA->IDR & 1) y <<= 16;
  GPIOA->BSRR = y;
 }
  }


为什么  x = 0x009CF260? 这是推算出来的, 目的是1秒钟翻转一次 PA0.
怎么推算的呢?
  if (SysTick_Config(SystemFrequency / 8)) { while(1);}
  设定 1/8 秒一次 SysTick 中断. 由调试器读取寄存器的值.
  0x008961ae,0x0075c362,0x00622516...
             0x00139E4C,0x00139E4C...
             0x00139E4C * 8 = 0x009CF260
那么在  0x00139E4C 个循环里, 到底执行了多少条指令呢? 看下面的编译结果          


;;;72       while (1)  {
;;;73        if (--x ==0) {
;;;74       x = 0x009CF260;
;;;75       y = 1;
;;;76       if(GPIOA->IDR & 1) y <<= 16;
000070  4909              LDR      r1,|L1.152|
000072  4625              MOV      r5,r4                 ;53
000074  f44f3280          MOV      r2,#0x10000
000078  e000              B        |L1.124|
                  |L1.122|
00007a  e7fe              B        |L1.122|
                  |L1.124|
00007c  1e64              SUBS     r4,r4,#1              ;73
00007e  d1fd              BNE      |L1.124|
000080  f8d13808          LDR      r3,[r1,#0x808]
000084  462c              MOV      r4,r5                 ;74
000086  2001              MOVS     r0,#1                 ;75
000088  07db              LSLS     r3,r3,#31
00008a  d000              BEQ      |L1.142|
00008c  4610              MOV      r0,r2
                  |L1.142|
;;;77       GPIOA->BSRR = y;
00008e  f8c10810          STR      r0,[r1,#0x810]
000092  e7f3              B        |L1.124|
;;;78      }
;;;79       }
;;;80     }


    分析: 从上面的编译结果, 可以看到, 变量 x 被分配给寄存器 R4,
在标号 |L1.124| 后面两条指令, 就执行了 减1 不为零, 再循环的任务.
也就是说, 1 秒钟, 就执行了 0x009CF260 = 10285664 个循环.
    通过 SysTick_Config 函数, 可以肯定, 现在的执行频率是 72MHz.
据此推算, 这两条指令的循环需要 7 个周期,
也就是说, 后面的分支跳转指令需要 6 个周期?  有这么慢?
我的 JLINK V8 没有告诉我!
 
发现更为奇怪的结果了: 看下面的修改的代码
  while (1)  {
   if ((--x)&&(--x ==0)) {
  x = SPEED1S;  
  y = 1;
  if(GPIOA->IDR & 1) y *= 0x10000;
  GPIOA->BSRR = y;
 }
  }
 
;;;75       while (1)  {
;;;76        if ((--x)&&(--x ==0)) {
;;;77       x = SPEED1S;  
;;;81       y = 1;
;;;82       if(GPIOA->IDR & 1) y *= 0x10000;
000072  490b              LDR      r1,|L1.160|
000074  4625              MOV      r5,r4                 ;54
000076  f44f3280          MOV      r2,#0x10000
00007a  e000              B        |L1.126|
                  |L1.124|
00007c  e7fe              B        |L1.124|
                  |L1.126|
00007e  1e64              SUBS     r4,r4,#1              ;76
000080  d0fd              BEQ      |L1.126|
000082  1e64              SUBS     r4,r4,#1              ;76
000084  d1fb              BNE      |L1.126|
000086  f8d13808          LDR      r3,[r1,#0x808]
00008a  462c              MOV      r4,r5                 ;77
00008c  2001              MOVS     r0,#1                 ;81
00008e  07db              LSLS     r3,r3,#31
000090  d000              BEQ      |L1.148|
000092  4610              MOV      r0,r2
                  |L1.148|
;;;83       GPIOA->BSRR = y;
000094  f8c10810          STR      r0,[r1,#0x810]
000098  e7f1              B        |L1.126|
;;;84      }
;;;85       }


    分析: 从上面的编译结果, 可以看到, 变量 x 仍被分配给寄存器 R4,
在标号 |L1.126| 后面四条指令, 就执行了两次减1 后判断的循环任务.
这两个分支, 一个不需要跳转, 另一个需要跳转. 接下来看测试的结果.
 下面是每次 Systick 中断中读取到的 x(R4) 的值.
 
  0x0075c35e,0x004e86c6,0x00274a2e,0x00000d96
 差值       ,0x00273c98,0x00273c98,0x00273c98
 
也就是说, 1 秒钟, 执行了 0x273c98 * 4 = 0x9CF260 个循环.
对, 仍然是 0x9CF260 , 写这里的时候, 我反复过 n 次了.
有人要说了,  1/8 秒不是要乘以 8 嘛, 但是一次循环里执行了两次减1.


据此推算, 这四条指令的循环一次需要 7 个周期,
难道说减法指令和分支不跳转不需要时间, 光那条分支后跳转指令需要 7 个周期?


看样子, 调试器通信是需要时间的. 问题一定在调试器上面;
为此, 重新修改 Systick 中断服务程序, 将上面的计数器 x 记录下来保存到内存,
若干次后看保存的结果. 相信这样能脱离调试器的影响了吧.
哦, 获取寄存器的值是 C 所难办到的事情, 我使用下面的汇编代码来完成.


__inline __asm  unsigned long getdat(void) {
 mov r0, R4;
 bx  lr;


下面是 Systick 中断服务程序, 取 4 次的值, 然后在把断点放在 改变 PA1 的地方.
 
volatile unsigned char ucnt; 
unsigned long ldat[16];
void SysTick_Handler(void) {
 unsigned long cnt, dat = getdat();
 unsigned long dsp = 2;
 cnt = ucnt;
 ldat[cnt] = dat;
 cnt = (ucnt +1)%4; ucnt = cnt;
 if (cnt ==0) {
  if(GPIOA->IDR&2) dsp <<= 16;
  GPIOA->BSRR = dsp;
 }
}


    下面是记录到的连续 4 个数据.
  0x861CA0,0x6F394B,0x5855F5,0x41729F
 差值      0x16E355,0x16E356,0x16E356  * 8 = B71AB0  (11999920)
 
这样的结果应该比较真实.两条指令共 6 个周期, 分支跳转使用 5 个周期.      
    下面是使用后面的代码 (四条指令一个循环) 记录到的连续 4 个数据.
      
  0x7E7B80,0x5FF70E,0x323062,0x13ABF0
 差值      0x1E8472,0x2DC6AC,0x1E8472  * 4 = 7A11C8  (7999944)
      
     这里有个过分超差的值, 剩余 2 个是比较合理, 计算结果为 9 个周期.
 估计应该是运算指令 1 个周期, 分支跳转 5 个周期, 分支不跳转 2 个周期.     
     另外发现一个过分超差的值, 经过多次调试, 发现会若干次后出现一次.
 会不会就是前面提到的调试器的影响呢?    
           


路过

鸡蛋

鲜花

握手

雷人

评论 (0 个评论)