为什么ATmega328P的中断向量表中,每个向量项既可以是2 字节,也可以是1 字节,仍然能工作?
对于 Arduino-Uno 板,配合 ATmega328P 与 avr-g++ 编译器:
如果我对一个使用定时器中断的简单LED闪烁代码进行反汇编,结果显示中断向量表中的每个入口都是2 个字(4字节)。代码运行正常。
如果我不使用标准启动文件和库,手动编写自己的启动代码和中断向量表,反汇编的代码显示中断向量表中的每个入口是1 个字(2字节)。代码同样正常。
如果向量表条目的大小由处理器决定,那么一个或另一个方法都应该能工作,但为什么两者都在工作?
带有标准启动文件和库的代码:blink.cpp
#define F_CPU 16000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
ISR(TIMER1_COMPA_vect) {
PORTB ^= _BV(PB5); // Toggle LED
}
int main(void) {
DDRB |= _BV(DDB5); // Set PB5 as output
// Configure Timer1: CTC mode, prescaler 256, 1Hz interrupt
TCCR1A = 0;
TCCR1B = _BV(WGM12) | _BV(CS12); // CTC mode, prescaler 256
OCR1A = 62500 - 50000; // 16MHz / 256 / 1Hz
TIMSK1 = _BV(OCIE1A); // Enable Timer1 Compare Match A interrupt
sei(); // Enable global interrupts
while (1) ;
}
命令:
avr-g++ -mmcu=atmega328p -Os -o blink.elf blink.cpp -Wa,-adhlns=blink.lst -Wl,-Map,blink.map
avr-objcopy -O ihex blink.elf blink.hex
avr-objdump -d -S blink.elf > blink.final.lst
从blink.final.lst反汇编的代码:
...
00000000 <__vectors>:
0: 0c 94 34 00 jmp 0x68 ; 0x68 <__ctors_end>
4: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
...
28: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
2c: 0c 94 40 00 jmp 0x80 ; 0x80 <__vector_11>
30: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
...
64: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
显然,每个入口都是2 个字(4字节)。
以下是完整的 blink.hex 文件:
:100000000C9434000C943E000C943E000C943E0082
:100010000C943E000C943E000C943E000C943E0068
:100020000C943E000C943E000C943E000C94400056
:100030000C943E000C943E000C943E000C943E0048
:100040000C943E000C943E000C943E000C943E0038
:100050000C943E000C943E000C943E000C943E0028
:100060000C943E000C943E0011241FBECFEFD8E04C
:10007000DEBFCDBF0E9452000C9463000C940000C0
:100080001F920F920FB60F9211248F939F9385B1F9
:1000900090E2892785B99F918F910F900FBE0F90A5
:1000A0001F901895259A109280008CE08093810013
:1000B00084ED90E3909389008093880082E08093A0
:0A00C0006F007894FFCFF894FFCF93
:00000001FF
不带标准启动文件和库的代码:blink.S
.section .vectors, "ax", @progbits
.global __vectors
__vectors:
rjmp reset_handler ; Vector 0: Reset
; Other interrupts can be dummy jumps to safe handlers or zeros
rjmp dummy_handler ; Vector 1
...
rjmp dummy_handler ; Vector 10
.extern __vector_11 ; Declare ISR from C++
rjmp __vector_11 ; Vector 11: Timer/Counter1 Capture Event ISR
; Further vectors...
rjmp dummy_handler ; Vector 12
...
rjmp dummy_handler ; Vector 26
.equ RAMEND, 0x08FF ; Last SRAM address for ATmega328P
.equ SPL, 0x3D ; I/O register address for Stack Pointer Low
.equ SPH, 0x3E ; I/O register address for Stack Pointer High
reset_handler:
; Initialize stack pointer
ldi r16, lo8(RAMEND)
out SPL, r16
ldi r16, hi8(RAMEND)
out SPH, r16
; Call main()
call main
rjmp . ; Loop forever if main returns
dummy_handler:
rjmp . ; Loop forever for unexpected interrupts
.global main
blink.cpp
#include <avr/io.h>
#include <avr/interrupt.h>
ISR(TIMER1_COMPA_vect) {
PORTB ^= (1 << PB5); // Toggle LED
}
void timer1CompASetup(uint16_t ocr1aValue) {
TCCR1A = 0;
TCCR1B = (1 << WGM12) | (1 << CS12); // CTC mode, prescaler 256
OCR1A = ocr1aValue; // 16MHz / 256 / 1Hz
TIMSK1 = (1 << OCIE1A); // Timer1 COMPA interrupt enable
}
int main(void) {
timer1CompASetup(62500 - 1);
DDRB |= (1 << 5); // PB5 output (LED pin)
sei();
while (1) ;
}
命令: (注:-nostartfiles -nostdlib -nodefaultlibs -ffreestanding 选项)
avr-g++ -mmcu=atmega328p -Os -nostartfiles -nostdlib -nodefaultlibs -ffreestanding -o blink.elf blink.S blink.cpp -Wa,-adhlns=blink.lst -Wl,-e,__vectors,-Map,blink.map,--section-start=.vectors=0x0000
avr-objcopy -O ihex blink.elf blink.hex
avr-objdump -d -S blink.elf > blink.final.lst
从blink.final.lst反汇编的代码:
00000000 <__vectors>:
0: 19 c0 rjmp .+50 ; 0x34 <reset_handler>
2: 1f c0 rjmp .+62 ; 0x42 <dummy_handler>
...
14: 16 c0 rjmp .+44 ; 0x42 <dummy_handler>
16: 16 c0 rjmp .+44 ; 0x44 <__vector_11>
18: 14 c0 rjmp .+40 ; 0x42 <dummy_handler>
...
32: 07 c0 rjmp .+14 ; 0x42 <dummy_handler>
显然每个入口是1 个字(2字节)。
以下是完整的 blink.hex 文件:
:1000000019C01FC01EC01DC01CC01BC01AC019C013
:1000100018C017C016C016C014C013C012C011C03B
:1000200010C00FC00EC00DC00CC00BC00AC009C06C
:1000300008C007C00FEF0DBF08E00EBF0E944100CF
:1000400000C000C01F920F920FB60F9211248F9321
:100050009F9385B190E2892785B99F918F910F90E9
:100060000FBE0F901F901895109280002CE02093E7
:100070008100909389008093880082E080936F00D4
:10008000089583E294EF0E943400259A7894FFCF7C
:00000001FF
解决方案
你的主要问题在于你对 rjmp 的用法,因为你把 . 作为它的参数。这是一种常见的用法,因此并非你个人的错。
显然这会把下一条指令设为目标。我费了很大力气去找关于这一行为的可靠文档,但没有找到。对LLVM我找到了一个bug报告,但这是GNU binutils的汇编器。因此,情况如上所述。
在你的源码中:
dummy_handler:
rjmp . ; Loop forever for unexpected interrupts
在反汇编中,目标地址清楚地显示出来(你本来就应该看到这一点,然而):
42: 00 c0 rjmp .+0 ; 0x44
44: 1f 92 push r1
...
所以当定时器比较中断被触发时会发生以下情况:
- AVR按预期并有文档地在字节地址0x2c处调用中断服务例程。
- 它进入哑处理程序,但不是循环,而是跳到下一条指令。
- 这恰好是你的实际中断服务例程的第一条指令。
如何解决这个问题?
通过使用显式标签来纠正你的无休止循环:
dummy_handler:
rjmp dummy_handler ; Loop forever for unexpected interrupts
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。