现象:用mega64的定时中断控制某个IO口产生一个固定频率的脉冲方波,但偶尔出现一个误差较大的波形。此IO口为PORTB7。 解决:首先怀疑是否C程序中其他地方操作了此IO口,经查没有;然后怀疑C程序中是否有对整个PORTB口进行操作的语句,经查没有;最后,在反汇编级代码中查找,发现有如下语句:out 0x18, r25;可见C程序经编译后有对整个PORTB口进行操作,这样当中断在前述语句前发生时,若r25=0x00,而中断中把PORTB7置1的话,中断结束后执行out 0x18, r25;时便表现为波形不正确,输出0不正确依理例推。
源程序级解析: 原程序: /* BYTE STRUCTION -----------------------------------------------------------*/ typedef union { unsigned int byte; struct { unsigned b0 :1; unsigned b1 :1; unsigned b2 :1; unsigned b3 :1; unsigned b4 :1; unsigned b5 :1; unsigned b6 :1; unsigned b7 :1; }bit; }BYTE_STRUCT; /*-----------------------------------------------------------------------*/ #define _MY_SFR_IO8(io_addr) _MY_MMIO_BYTE((io_addr) + 0x20) #define _MY_SFR_MEM8(mem_addr) _MY_MMIO_BYTE(mem_addr) #define _MY_MMIO_BYTE(mem_addr) (*(volatile BYTE_STRUCT *)(mem_addr)) /*-----------------------------------------------------------------------*/ #define PIN_B _MY_SFR_IO8(0x16) #define DDR_B _MY_SFR_IO8(0x17) #define PORT_B _MY_SFR_IO8(0x18) #define ioWatchdog PORT_B.bit.b5 /* 清看门狗信号io引脚 */ #define ioPulse PORT_B.bit.b7 /* 无功脉冲输出信号io引脚 */ /*-----------------------------------------------------------------------*/
#define INPUT 0 /* 根据芯片定义 */ #define OUTPUT 1 /* 根据芯片定义 */ /*-----------------------------------------------------------------------*/
#define CLRWDT { \ asm( "WDR" ); \ ioWatchdogDir = OUTPUT; \ ioWatchdog = ~ioWatchdog; \ } /* clr watchdog */ /*-----------------------------------------------------------------------*/ 主函数中调用CLRWDT部分的反汇编如下(编译器是WinAVR-20070525): 139: CLRWDT; +000047F9: 95A8 WDR Watchdog reset +000047FA: 9ABD SBI 0x17,5 Set bit in I/O register +000047FB: B388 IN R24,0x18 In from I/O location +000047FC: 9582 SWAP R24 Swap nibbles +000047FD: 9586 LSR R24 Logical shift right +000047FE: 7087 ANDI R24,0x07 Logical AND with immediate +000047FF: 9580 COM R24 One's complement +00004800: 7081 ANDI R24,0x01 Logical AND with immediate +00004801: 9582 SWAP R24 Swap nibbles +00004802: 0F88 LSL R24 Logical Shift Left +00004803: 7E80 ANDI R24,0xE0 Logical AND with immediate +00004804: B398 IN R25,0x18 In from I/O location +00004805: 7D9F ANDI R25,0xDF Logical AND with immediate +00004806: 2B98 OR R25,R24 Logical OR +00004807: BB98 OUT 0x18,R25 Out to I/O location
修改后程序:(仅修改CLRWDT的宏定义) /*------------------------------------------------------------------------------ xct gai 2008-3-28 用ioWatchdog = ~ioWatchdog翻转的话编译器编译出来的语句可能不用sbi和cbi,而是对整个 端口PORTB操作,当刚好操作端口时发生中断,可能发生端口输出错误 -----------------------------------*/ #define CLRWDT { \ asm( "WDR" ); \ ioWatchdogDir = OUTPUT; \ if( ioWatchdog ) ioWatchdog = 0;\ else ioWatchdog = 1;\ } /* clr watchdog */ 主函数中调用CLRWDT部分的反汇编如下(编译器是WinAVR-20070525): 139: CLRWDT; +000047D9: 95A8 WDR Watchdog reset +000047DA: 9ABD SBI 0x17,5 Set bit in I/O register +000047DB: 9BC5 SBIS 0x18,5 Skip if bit in I/O register set +000047DC: C002 RJMP PC+0x0003 Relative jump +000047DD: 98C5 CBI 0x18,5 Clear bit in I/O register +000047DE: C001 RJMP PC+0x0002 Relative jump +000047DF: 9AC5 SBI 0x18,5 Set bit in I/O register
经修改后不但程序按预期执行,而且代码更加精简。至此问题得到代码级的、原理级的、完全的解释。 由此可见我们编程时对所用单片机的汇编指令和编译器的熟悉是多么重要;而对程序的测试及反汇编代码的通读又是多么必要。 |