||
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 个周期.
另外发现一个过分超差的值, 经过多次调试, 发现会若干次后出现一次.
会不会就是前面提到的调试器的影响呢?