Chapter 4: Interrupts and Exceptions: Enhanced Interrupt Controller (ECLIC)
4.2 ECLIC Core Configuration: Vector Table, Interrupt Enablement, and Priority
4.2.1 Overall Configuration Process of ECLIC
Configuration Architecture Overview
The complete configuration of ECLIC is a systematic project that requires configuring various functional modules in a specific order. The correct configuration sequence ensures system stability and interrupt response performance.
ECLIC Configuration Flowchart
┌─────────────────┐│ Start ECLIC Configuration │└─────────┬───────┘ │ ▼┌─────────────────┐│ 1. Global ECLIC Enable │ ← Activate ECLIC Controller└─────────┬───────┘ │ ▼┌─────────────────┐│ 2. Priority Bit Configuration │ ← Set Priority Granularity└─────────┬───────┘ │ ▼┌─────────────────┐│ 3. Vector Table Base Address Setting │ ← Establish Interrupt Jump Table└─────────┬───────┘ │ ▼┌─────────────────┐│ 4. Machine Mode Threshold Setting │ ← Configure Interrupt Filtering└─────────┬───────┘ │ ▼┌─────────────────┐│ 5. Interrupt Attribute Configuration │ ← Configure Each Interrupt Source└─────────┬───────┘ │ ▼┌─────────────────┐│ 6. Global Interrupt Enable │ ← Enable Interrupt System└─────────┬───────┘ │ ▼┌─────────────────┐│ ECLIC Configuration Complete │└─────────────────┘
Functions and Principles of Each Configuration Step
-
Global ECLIC Enable:
-
Function:Activate the ECLIC controller to start its operation
-
Principle:ECLIC is disabled by default after reset and needs to be explicitly enabled
-
Key Register:
<span>ECLIC->CFG</span>enable bit
Priority Bit Configuration:
-
Function:Determine the granularity of interrupt priority in the system
-
Principle:8-bit priority can be subdivided into 256 levels, while 3-bit priority has only 8 levels
-
Trade-off:More levels provide finer scheduling but increase arbitration complexity
Vector Table Base Address Setting:
-
Function:Establish a hardware interrupt vector jump table
-
Principle:When an interrupt occurs, the hardware automatically calculates the vector address and jumps
-
Advantage:Significantly reduces interrupt latency compared to software table lookup
Machine Mode Threshold Setting:
-
Function:Filter low-priority interrupts to reduce unnecessary context switching
-
Principle:Only interrupts with priority higher than the threshold will be processed
-
Application:Protection of critical system tasks
Interrupt Attribute Configuration:
-
Function:Configure specific behavioral characteristics for each interrupt source
-
Includes:Priority, trigger mode, vector mode, etc.
-
Importance:Determines the real-time performance and reliability of interrupts
Global Interrupt Enable:
-
Function:The final step, enabling the entire interrupt system
-
Principle:RISC-V controls global interrupts through
<span>mstatus.MIE</span>bit -
Best Practice:Always enable interrupts after configuration is complete
ECLIC Overall Configuration Pseudocode
// Complete ECLIC Configuration Pseudocode - Based on GD32VF103 Practiceeclic_config_result_t eclic_complete_configuration(void) { printf("Starting ECLIC Complete Configuration...\n"); // === Stage 1: Basic ECLIC Configuration === // 1.1 Global ECLIC Enable // Key Register: ECLIC->CFG ECLIC->CFG = (0x1 << 16); // Bit16: ECLIC Enable Bit if (!(ECLIC->CFG & (0x1 << 16))) { printf("ERROR: ECLIC enable failed\n"); return ECLIC_CONFIG_FAILED; } printf("✓ ECLIC globally enabled\n"); // 1.2 Priority Bit Configuration // Key Register: ECLIC->CFG[4:0] - nlbits field // Set 3-bit priority (2^3 = 8 priority levels) uint32_t nlbits = 0x3; // 3-bit priority ECLIC->CFG = (ECLIC->CFG & ~0x1F) | (nlbits & 0x1F); printf("✓ Priority bits configured: %lu bits (%lu levels)\n", nlbits, (1UL << nlbits)); // === Stage 2: Vector System Configuration === // 2.1 Vector Table Base Address Setting // Key Register: mtvec (Machine Trap Vector) uint32_t vector_base = (uint32_t)&interrupt_vector_table; // Set vector mode: mtvec[1:0] = 0x1 uint32_t mtvec_value = (vector_base & ~0x3) | 0x1; __asm volatile ("csrw mtvec, %0" : : "r" (mtvec_value)); // Verify mtvec setting uint32_t read_mtvec; __asm volatile ("csrr %0, mtvec" : "=r" (read_mtvec)); if ((read_mtvec & 0x3) != 0x1) { printf("ERROR: mtvec vector mode setup failed\n"); return ECLIC_CONFIG_FAILED; } printf("✓ Vector table configured at 0x%08lX\n", vector_base); // === Stage 3: Interrupt Filtering Configuration === // 3.1 Machine Mode Threshold Setting // Key Register: ECLIC->MTH (Machine Threshold) // Set threshold to 0, accept all priority interrupts ECLIC->MTH = 0x00; printf("✓ Machine threshold set to 0x%02X\n", ECLIC->MTH); // === Stage 4: Detailed Interrupt Source Configuration === // 4.1 Configure Specific Interrupt Sources if (!configure_all_interrupt_sources()) { printf("ERROR: Interrupt source configuration failed\n"); return ECLIC_CONFIG_FAILED; } // === Stage 5: Final Enable === // 5.1 Global Interrupt Enable // Key Register: mstatus.MIE (Machine Interrupt Enable) __asm volatile ("csrs mstatus, %0" : : "r" (0x8)); // Set MIE bit // Verify Global Interrupt Enable uint32_t mstatus; __asm volatile ("csrr %0, mstatus" : "=r" (mstatus)); if (!(mstatus & 0x8)) { printf("ERROR: Global interrupt enable failed\n"); return ECLIC_CONFIG_FAILED; } printf("✓ Global interrupts enabled\n"); // === Configuration Verification === if (!verify_eclic_configuration()) { printf("WARNING: ECLIC configuration verification issues\n"); return ECLIC_CONFIG_WARNING; } printf("ECLIC configuration completed successfully\n"); return ECLIC_CONFIG_SUCCESS;} // Pseudocode for configuring all interrupt sourcesbool configure_all_interrupt_sources(void) { printf("Configuring interrupt sources...\n"); // Configure system critical interrupts (high priority) if (!configure_system_critical_interrupts()) { return false; } // Configure peripheral interrupts (medium priority) if (!configure_peripheral_interrupts()) { return false; } // Configure GPIO interrupts (low priority) if (!configure_gpio_interrupts()) { return false; } printf("✓ All interrupt sources configured\n"); return true;}
4.2.2 Detailed Vector Table Configuration
Vector Table Design Principles
The vector table is the core of ECLIC hardware vectorization, achieving rapid interrupt response through a predefined jump table:
-
Hardware Automatic Jump:When an interrupt occurs, the hardware automatically calculates the vector address
-
Fixed Spacing:Each interrupt vector has a fixed address offset
-
Flexible Layout:The vector table can be placed at any aligned memory location
Vector Table Configuration Steps
// Complete Vector Table Configuration Pseudocodevoid setup_vector_table_system(void) { printf("Setting up vector table system...\n"); // Step 1: Define Vector Table Structure // The vector table is an array of function pointers, each element corresponds to an interrupt handler void (*vector_table[ECLIC_MAX_INTERRUPTS])(void) = {0}; // Step 2: Populate Vector Table populate_vector_table(vector_table); // Step 3: Set Vector Table Base Address to mtvec uint32_t vector_base = (uint32_t)vector_table; // Ensure vector table is correctly aligned (at least 4-byte aligned) if (vector_base & 0x3) { printf("WARNING: Vector table not 4-byte aligned, adjusting...\n"); vector_base = (vector_base + 3) & ~0x3; } // Set mtvec to vector mode (mode = 1) uint32_t mtvec_value = vector_base | 0x1; __asm volatile ("csrw mtvec, %0" : : "r" (mtvec_value)); // Step 4: Configure ECLIC Vector Related Settings configure_eclic_vector_settings(); printf("✓ Vector table system ready at 0x%08lX\n", vector_base);} // Implementation to populate vector tablevoid populate_vector_table(void (*vector_table[])(void)) { // Set default interrupt handler for (int i = 0; i < ECLIC_MAX_INTERRUPTS; i++) { vector_table[i] = &default_interrupt_handler; } // Configure specific interrupt handlers - based on GD32VF103 practice vector_table[ECLIC_IRQ_USART0] = &usart0_interrupt_handler; vector_table[ECLIC_IRQ_USART1] = &usart1_interrupt_handler; vector_table[ECLIC_IRQ_TIMER0] = &timer0_interrupt_handler; vector_table[ECLIC_IRQ_TIMER1] = &timer1_interrupt_handler; vector_table[ECLIC_IRQ_EXTI0] = &exti0_interrupt_handler; vector_table[ECLIC_IRQ_EXTI1] = &exti1_interrupt_handler; // GPIO interrupt vector configuration for (int i = ECLIC_IRQ_GPIO_BASE; i < ECLIC_IRQ_GPIO_BASE + 16; i++) { vector_table[i] = &gpio_interrupt_handler; } printf("✓ Vector table populated with %d handlers\n", ECLIC_MAX_INTERRUPTS);} // Vector Interrupt Handler Templatevoid __attribute__((interrupt)) default_interrupt_handler(void) { uint32_t mcause, interrupt_id; // Read interrupt cause __asm volatile ("csrr %0, mcause" : "=r" (mcause)); interrupt_id = mcause & 0x7FFFFFFF; printf("Default handler: Unhandled interrupt %lu\n", interrupt_id); // Safety measure: Disable unhandled interrupts if (interrupt_id < ECLIC_MAX_INTERRUPTS) { ECLIC->interrupts[interrupt_id].INTIE = 0x0; }} // GD32VF103 Specific Peripheral Interrupt Handlervoid __attribute__((interrupt)) usart0_interrupt_handler(void) { uint32_t status = USART0->STAT; // Handle receive interrupt if (status & USART_STAT_RBNE) { uint8_t data = USART0->DATA; handle_usart0_rx_data(data); // Clear receive interrupt flag USART0->STAT &= ~USART_STAT_RBNE; } // Handle transmit interrupt if (status & USART_STAT_TBE) { handle_usart0_tx_ready(); // Note: Transmit complete interrupt needs software control to clear } // Handle error interrupt if (status & (USART_STAT_PERR | USART_STAT_FERR)) { handle_usart0_error(status); // Error flags need to be read from STAT register to clear }}
4.2.3 Detailed Interrupt Enable Configuration
Interrupt Enable Hierarchy
The interrupt enablement in RISC-V+ECLIC adopts a three-layer control:
┌─────────────────┐│ Global Interrupt Enable │ ← mstatus.MIE (Highest Layer)│ (mstatus.MIE) │└─────────┬───────┘ │ ▼┌─────────────────┐│ Controller Interrupt Enable │ ← ECLIC Global Enable│ (ECLIC->CFG) │└─────────┬───────┘ │ ▼┌─────────────────┐│ Individual Interrupt Enable │ ← ECLIC->INTIE[n] (Finest Granularity)│ (ECLIC->INTIE[]) │└─────────────────┘
Interrupt Enable Configuration Code
// Complete Interrupt Enable Management Pseudocodevoid manage_interrupt_enables(void) { printf("Managing interrupt enable states...\n"); // === Individual Interrupt Enable Configuration === // Configure USART0 interrupt enable ECLIC->interrupts[ECLIC_IRQ_USART0].INTIE = 0x1; printf("✓ USART0 interrupt enabled\n"); // Configure TIMER0 interrupt enable ECLIC->interrupts[ECLIC_IRQ_TIMER0].INTIE = 0x1; printf("✓ TIMER0 interrupt enabled\n"); // Batch configure GPIO interrupt enable for (int i = 0; i < 8; i++) { // Only enable the first 8 GPIO interrupts ECLIC->interrupts[ECLIC_IRQ_GPIO_BASE + i].INTIE = 0x1; } printf("✓ GPIO interrupts 0-7 enabled\n"); // === Safety Interrupt Disable Example === // Disable unused system interrupts ECLIC->interrupts[ECLIC_IRQ_RESERVED_1].INTIE = 0x0; ECLIC->interrupts[ECLIC_IRQ_RESERVED_2].INTIE = 0x0; printf("✓ Reserved interrupts disabled for safety\n");} // Dynamic Interrupt Enable Control Functioninterrupt_control_result_t control_interrupt_enable(uint32_t interrupt_id, bool enable) { if (interrupt_id >= ECLIC_MAX_INTERRUPTS) { return INTERRUPT_ID_INVALID; } // Atomic operation: first disable global interrupts, modify enable state, then restore uint32_t old_mstatus; __asm volatile ("csrr %0, mstatus" : "=r" (old_mstatus)); __asm volatile ("csrc mstatus, %0" : : "r" (0x8)); // Clear MIE if (enable) { ECLIC->interrupts[interrupt_id].INTIE = 0x1; printf("Interrupt %lu enabled\n", interrupt_id); } else { ECLIC->interrupts[interrupt_id].INTIE = 0x0; printf("Interrupt %lu disabled\n", interrupt_id); } // Restore original global interrupt state if (old_mstatus & 0x8) { __asm volatile ("csrs mstatus, %0" : : "r" (0x8)); } return INTERRUPT_CONTROL_SUCCESS;} // Interrupt Enable State Checkvoid check_interrupt_enable_states(void) { printf("Checking interrupt enable states...\n"); uint32_t enabled_count = 0; for (int i = 0; i < ECLIC_MAX_INTERRUPTS; i++) { if (ECLIC->interrupts[i].INTIE) { enabled_count++; // Check for handlers that are enabled but not configured if (vector_table[i] == &default_interrupt_handler) { printf("WARNING: Interrupt %d enabled but using default handler\n", i); } } } printf("✓ %lu interrupts currently enabled\n", enabled_count); if (enabled_count == 0) { printf("WARNING: No interrupts are enabled\n"); }}
4.2.4 Detailed Priority Configuration
Priority System Design Principles
The ECLIC priority system is based on a fixed priority preemption model:
-
Lower values indicate higher priority:0 is the highest priority, 255 is the lowest
-
Preemption Mechanism:High-priority interrupts can preempt low-priority interrupts
-
Threshold Filtering:MTH register filters interrupts below the threshold
-
Support for Nesting:Supports interrupt nesting to improve real-time performance
Priority Configuration Code
// Complete Priority Configuration Pseudocodevoid configure_interrupt_priorities(void) { printf("Configuring interrupt priorities...\n"); // === System Critical Interrupts - Highest Priority === // Watchdog Interrupt - Highest Priority (0) ECLIC->interrupts[ECLIC_IRQ_WWDG].INTCTL = 0x00; printf("✓ Watchdog interrupt: priority 0 (highest)\n"); // System Exception Interrupt - High Priority (16) ECLIC->interrupts[ECLIC_IRQ_SYSTICK].INTCTL = 0x10; printf("✓ SysTick interrupt: priority 16\n"); // === Real-time Peripheral Interrupts - Medium Priority === // Communication Interface Interrupts - Medium Priority (32-48) ECLIC->interrupts[ECLIC_IRQ_USART0].INTCTL = 0x20; ECLIC->interrupts[ECLIC_IRQ_USART1].INTCTL = 0x28; ECLIC->interrupts[ECLIC_IRQ_SPI0].INTCTL = 0x30; printf("✓ Communication interrupts: priorities 32-48\n"); // Timer Interrupts - Medium Priority (40-56) ECLIC->interrupts[ECLIC_IRQ_TIMER0].INTCTL = 0x28; ECLIC->interrupts[ECLIC_IRQ_TIMER1].INTCTL = 0x30; ECLIC->interrupts[ECLIC_IRQ_TIMER2].INTCTL = 0x38; printf("✓ Timer interrupts: priorities 40-56\n"); // === General Peripheral Interrupts - Low Priority === // GPIO Interrupts - Low Priority (64+) for (int i = 0; i < 16; i++) { ECLIC->interrupts[ECLIC_IRQ_GPIO_BASE + i].INTCTL = 0x40 + (i * 2); } printf("✓ GPIO interrupts: priorities 64-94\n"); // ADC/DAC Interrupts - Low Priority (80+) ECLIC->interrupts[ECLIC_IRQ_ADC0].INTCTL = 0x50; ECLIC->interrupts[ECLIC_IRQ_DAC].INTCTL = 0x58; printf("✓ Analog interrupts: priorities 80-88\n");} // Dynamic Priority Adjustment Functionvoid adjust_interrupt_priority(uint32_t interrupt_id, uint8_t new_priority) { if (interrupt_id >= ECLIC_MAX_INTERRUPTS) { printf("ERROR: Invalid interrupt ID %lu\n", interrupt_id); return; } uint8_t old_priority = ECLIC->interrupts[interrupt_id].INTCTL & 0xFF; // Safety check: prevent setting reserved highest priority if (new_priority == 0x00) { printf("WARNING: Setting highest priority (0) for interrupt %lu\n", interrupt_id); } // Atomic operation to modify priority uint32_t old_mstatus; __asm volatile ("csrr %0, mstatus" : "=r" (old_mstatus)); __asm volatile ("csrc mstatus, %0" : : "r" (0x8)); // Disable global interrupts ECLIC->interrupts[interrupt_id].INTCTL = new_priority; // Restore global interrupt state if (old_mstatus & 0x8) { __asm volatile ("csrs mstatus, %0" : : "r" (0x8)); } printf("Interrupt %lu priority changed: 0x%02X → 0x%02X\n", interrupt_id, old_priority, new_priority);} // Priority Conflict Detection and Resolutionvoid detect_priority_conflicts(void) { printf("Checking for priority conflicts...\n"); uint8_t priority_usage[256] = {0}; bool conflicts_detected = false; // Count usage of each priority for (int i = 0; i < ECLIC_MAX_INTERRUPTS; i++) { if (ECLIC->interrupts[i].INTIE) { // Only check enabled interrupts uint8_t priority = ECLIC->interrupts[i].INTCTL & 0xFF; priority_usage[priority]++; if (priority_usage[priority] > 1) { printf("CONFLICT: Priority 0x%02X used by multiple interrupts\n", priority); conflicts_detected = true; } } } // Check highest priority usage if (priority_usage[0] > 1) { printf("WARNING: Multiple interrupts using highest priority (0)\n"); conflicts_detected = true; } if (!conflicts_detected) { printf("✓ No priority conflicts detected\n"); } // Output priority usage statistics printf("Priority usage statistics:\n"); for (int i = 0; i < 16; i++) { // Only display the first 16 priorities if (priority_usage[i] > 0) { printf(" Priority 0x%02X: %d interrupts\n", i, priority_usage[i]); } }} // Recommended Priority Allocation Strategy for GD32VF103void apply_gd32vf103_priority_policy(void) { printf("Applying GD32VF103 recommended priority policy...\n"); // Policy Principles: // 1. System critical functions use high priority (0-31) // 2. Real-time communication uses medium-high priority (32-95) // 3. General peripherals use medium-low priority (96-191) // 4. Background tasks use low priority (192-255) // System Critical Interrupts ECLIC->interrupts[ECLIC_IRQ_SYSTICK].INTCTL = 0x10; // System Timer ECLIC->interrupts[ECLIC_IRQ_WWDG].INTCTL = 0x00; // Watchdog // Real-time Communication Interrupts ECLIC->interrupts[ECLIC_IRQ_USART0].INTCTL = 0x20; // Main Serial Port ECLIC->interrupts[ECLIC_IRQ_USART1].INTCTL = 0x28; // Auxiliary Serial Port ECLIC->interrupts[ECLIC_IRQ_SPI0].INTCTL = 0x30; // SPI Interface // Timer Interrupts ECLIC->interrupts[ECLIC_IRQ_TIMER0].INTCTL = 0x40; // Main Timer ECLIC->interrupts[ECLIC_IRQ_TIMER1].INTCTL = 0x48; // General Timer // GPIO and General Peripherals ECLIC->interrupts[ECLIC_IRQ_EXTI0].INTCTL = 0x60; // External Interrupt 0 ECLIC->interrupts[ECLIC_IRQ_ADC0].INTCTL = 0x70; // ADC Conversion printf("✓ GD32VF103 priority policy applied\n");}
4.2.5 Summary
Through the detailed configuration instructions and code implementations in this section, we have established a complete ECLIC configuration system:
-
Systematic Configuration Process:Strictly configure in the order of global enablement → vector table → priority → interrupt enablement
-
Efficiency of Vector Table:Hardware vectorization significantly reduces interrupt latency and improves system real-time performance
-
Reasonableness of Priority:The layered priority strategy based on GD32VF103 practice ensures critical task responsiveness
-
Safety of Enablement Control:Three-layer enablement control provides fine-grained interrupt management capabilities
Key Best Practices:
- Always enable global interrupts after configuration is complete
- Set appropriate priorities for all interrupts to avoid conflicts
- Use hardware vector mode for optimal performance
- Regularly check interrupt configuration status to ensure system stability
Practices with domestic chips like GD32VF103 demonstrate that correct ECLIC configuration can provide reliable and efficient interrupt handling capabilities for embedded systems, meeting various needs from simple control to complex real-time systems.