Arduino Mega 2560如何基于参数指定中断向量及动态设置ISR
Great question—this is a super common pain point with AVR-based boards like the Mega 2560, since the ISR() macro is resolved at compile time, not runtime. You can’t dynamically pass a timer number to it, but there are two reliable workarounds to achieve what you need.
Approach 1: Generic ISR Forwarding (Most Practical)
The simplest way is to predefine ISRs for all three timers you want to use, then route them to a shared callback function via a function pointer. This lets you switch which timer is active at runtime based on your parameter X.
Step 1: Define a Callback Pointer
First, set up a function pointer type and a volatile pointer to hold your active interrupt logic (volatile ensures the compiler doesn’t optimize away accesses between the main loop and interrupt context):
#include <avr/interrupt.h> // Define the callback function type typedef void (*TimerISRCallback)(void); // Volatile pointer to our active interrupt handler static volatile TimerISRCallback active_timer_callback = NULL;
Step 2: Predefine ISRs for Each Timer
Write an ISR for each timer you want to support. Each one will just call the active callback if it’s set:
// Timer 1 Compare Match A ISR ISR(TIMER1_COMPA_vect) { if (active_timer_callback) active_timer_callback(); } // Timer 3 Compare Match A ISR ISR(TIMER3_COMPA_vect) { if (active_timer_callback) active_timer_callback(); } // Timer 4 Compare Match A ISR ISR(TIMER4_COMPA_vect) { if (active_timer_callback) active_timer_callback(); }
(Note: I used Compare Match A here since it’s the most common timer interrupt mode, but you can adjust to Overflow or other modes based on your needs.)
Step 3: Dynamic Setup Function
Create a function that takes your timer number (X) and callback, then configures the corresponding timer and enables its interrupt:
void setupDynamicTimer(uint8_t timer_num, TimerISRCallback callback) { // Disable all timer interrupts first to prevent race conditions TIMSK1 = 0; TIMSK3 = 0; TIMSK4 = 0; // Set the active callback active_timer_callback = callback; // Configure the selected timer (example uses CTC mode with 1024 prescaler) switch(timer_num) { case 1: // Reset timer control registers TCCR1A = 0; TCCR1B = 0; // Set CTC mode (WGM12 bit) TCCR1B |= (1 << WGM12); // Set compare match value (adjust this for your desired interrupt frequency) OCR1A = 15624; // ~1Hz with 16MHz clock and 1024 prescaler // Enable Compare Match A interrupt TIMSK1 |= (1 << OCIE1A); // Start timer with 1024 prescaler TCCR1B |= (1 << CS12) | (1 << CS10); break; case 3: TCCR3A = 0; TCCR3B = 0; TCCR3B |= (1 << WGM32); OCR3A = 15624; TIMSK3 |= (1 << OCIE3A); TCCR3B |= (1 << CS32) | (1 << CS30); break; case 4: TCCR4A = 0; TCCR4B = 0; TCCR4B |= (1 << WGM42); OCR4A = 15624; TIMSK4 |= (1 << OCIE4A); TCCR4B |= (1 << CS42) | (1 << CS40); break; default: // Handle invalid timer number active_timer_callback = NULL; break; } }
Step 4: Use It in Your Code
Define your interrupt handler logic, then call the setup function with your desired timer number:
// Your custom interrupt logic void myInterruptHandler() { // Keep this as short as possible! No delay() or blocking calls. digitalToggle(LED_BUILTIN); // Example: Toggle built-in LED } void setup() { pinMode(LED_BUILTIN, OUTPUT); // Use timer 3 with our custom handler (replace 3 with X from your caller) setupDynamicTimer(3, myInterruptHandler); sei(); // Enable global interrupts (Arduino does this by default, but good to explicit) } void loop() { // Main loop logic here }
Approach 2: RAM-Based Interrupt Vector Table (Advanced)
For more extreme flexibility, you can redirect the AVR’s interrupt vector table to RAM, then modify the vector entries at runtime. This is more complex and risky (a mistake can crash your board), but it’s useful if you need to handle many timers or dynamic reconfiguration.
Key Steps:
- Enable RAM Vector Table: Use the
IVSELbit inMCUCRto switch the interrupt vector table from Flash to RAM. - Copy Default Vectors: Duplicate the Flash-based vector table into a RAM array.
- Modify RAM Vectors: Overwrite the entry for your desired timer with your callback function.
- Switch Back (Optional): If needed, switch back to Flash vectors later.
Here’s a simplified snippet:
#include <avr/interrupt.h> #include <avr/pgmspace.h> // Define the size of the interrupt vector table (check ATmega2560 datasheet: 99 entries) #define IVT_SIZE 99 // RAM-based vector table void (*ram_ivt[IVT_SIZE])(void) __attribute__((section(".data"))); void setupRAMVectorTable() { // Copy Flash vector table to RAM for (uint8_t i = 0; i < IVT_SIZE; i++) { ram_ivt[i] = (void (*)(void))pgm_read_word((uint16_t *)0x0000 + i); } // Switch to RAM vector table MCUCR |= (1 << IVCE); // Enable vector table change MCUCR |= (1 << IVSEL); // Switch to RAM } void setDynamicTimerInterrupt(uint8_t timer_num, void (*callback)(void)) { // Get the vector index for each timer (from ATmega2560 datasheet) uint8_t vector_idx; switch(timer_num) { case 1: vector_idx = 37; break; // TIMER1_COMPA_vect case 3: vector_idx = 54; break; // TIMER3_COMPA_vect case 4: vector_idx = 70; break; // TIMER4_COMPA_vect default: return; } // Overwrite the RAM vector entry ram_ivt[vector_idx] = callback; // Configure the timer (same as Approach 1) // ... (add timer setup code here) }
Critical Notes
- Keep ISRs Short: Interrupt handlers should execute as quickly as possible. Avoid
delay(),Serial.print(), or other blocking operations. Usevolatilevariables to pass data between the ISR and main loop. - Race Conditions: Always disable interrupts temporarily when modifying the callback pointer or timer registers to prevent unexpected behavior.
- Prescaler & Compare Values: Adjust the prescaler and compare match values in the setup function to match your desired interrupt frequency.
内容的提问来源于stack exchange,提问作者user4020575

