src/platforms/esp/32/interrupts/NMI_INTEGRATION_EXAMPLE.md
This document provides complete examples showing how to integrate Level 7 NMI (Non-Maskable Interrupt) handling for RMT buffer refill operations on ESP32 Xtensa platforms (ESP32, ESP32-S2, ESP32-S3).
Before using Level 7 NMI, ensure you understand:
#include "platforms/esp/32/interrupts/ASM_2_C_SHIM.h" // NMI assembly shim macros
#include "platforms/esp/32/drivers/rmt/rmt_5/rmt5_worker.h" // RMT worker
#include "esp_intr_alloc.h" // ESP-IDF interrupt allocation
The static approach uses compile-time known function names for best performance (~65ns overhead).
// Use the existing NMI-safe wrapper function
// This function is already defined in rmt5_worker.cpp:
// extern "C" void IRAM_ATTR rmt5_nmi_buffer_refill(void);
// Generate the assembly NMI shim
// This macro creates an assembly handler named "xt_nmi" that calls rmt5_nmi_buffer_refill
FASTLED_NMI_ASM_SHIM_STATIC(xt_nmi, rmt5_nmi_buffer_refill)
What this does:
xt_nmi placed in IRAM (.iram1.text section)rmt5_nmi_buffer_refill() using Call0 ABIrfi 7 (Return From Interrupt level 7)#include "platforms/esp/32/drivers/rmt/rmt_5/rmt5_worker.h"
// External global pointer (defined in rmt5_worker.cpp)
extern fl::RmtWorker* DRAM_ATTR g_rmt5_nmi_worker;
void setup() {
// Create RMT worker instance
fl::RmtWorker* worker = new fl::RmtWorker(
RMT_CHANNEL_0, // RMT channel
GPIO_NUM_2, // Output GPIO
1000 // Buffer size
);
// CRITICAL: Set global pointer BEFORE enabling NMI
// The NMI handler will dereference this pointer
g_rmt5_nmi_worker = worker;
// Initialize worker (allocates buffers, configures RMT)
worker->begin();
}
void allocate_nmi() {
esp_intr_handle_t nmi_handle = nullptr;
// Allocate Level 7 NMI for RMT peripheral
esp_err_t err = esp_intr_alloc(
ETS_RMT_INTR_SOURCE, // Interrupt source
ESP_INTR_FLAG_LEVEL7 | ESP_INTR_FLAG_IRAM, // Flags: Level 7 + IRAM
nullptr, // Handler (nullptr for Level 7)
nullptr, // Arg (nullptr for Level 7)
&nmi_handle // Output handle
);
if (err != ESP_OK) {
Serial.printf("Failed to allocate NMI: %d\n", err);
return;
}
Serial.println("✓ Level 7 NMI allocated successfully");
Serial.println(" Handler: xt_nmi → rmt5_nmi_buffer_refill → RmtWorker::fillNextHalf");
}
Key Points:
ESP_INTR_FLAG_LEVEL7: Requests Level 7 interrupt (NMI)ESP_INTR_FLAG_IRAM: Ensures handler code is in IRAMhandler=nullptr: ESP-IDF looks for xt_nmi symbol at link timearg=nullptr: No argument passing for Level 7#include <Arduino.h>
#include "FastLED.h"
#include "platforms/esp/32/interrupts/ASM_2_C_SHIM.h"
#include "platforms/esp/32/drivers/rmt/rmt_5/rmt5_worker.h"
#include "esp_intr_alloc.h"
// External global pointer
extern fl::RmtWorker* DRAM_ATTR g_rmt5_nmi_worker;
// Generate NMI handler
FASTLED_NMI_ASM_SHIM_STATIC(xt_nmi, rmt5_nmi_buffer_refill)
// Configuration
#define LED_PIN 2
#define NUM_LEDS 100
CRGB leds[NUM_LEDS];
fl::RmtWorker* g_worker = nullptr;
esp_intr_handle_t g_nmi_handle = nullptr;
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("FastLED with Level 7 NMI Example");
Serial.println("==================================");
// Initialize FastLED
FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);
// Create RMT worker
g_worker = new fl::RmtWorker(RMT_CHANNEL_0, GPIO_NUM_2, NUM_LEDS * 3);
g_worker->begin();
// Set global pointer for NMI handler
g_rmt5_nmi_worker = g_worker;
// Allocate Level 7 NMI
esp_err_t err = esp_intr_alloc(
ETS_RMT_INTR_SOURCE,
ESP_INTR_FLAG_LEVEL7 | ESP_INTR_FLAG_IRAM,
nullptr, nullptr,
&g_nmi_handle
);
if (err == ESP_OK) {
Serial.println("✓ Level 7 NMI enabled");
} else {
Serial.printf("✗ NMI allocation failed: %d\n", err);
}
// Fill LEDs with rainbow
fill_rainbow(leds, NUM_LEDS, 0, 255 / NUM_LEDS);
FastLED.show();
}
void loop() {
// Cycle rainbow
static uint8_t hue = 0;
fill_rainbow(leds, NUM_LEDS, hue++, 255 / NUM_LEDS);
FastLED.show();
delay(20);
}
For scenarios requiring runtime handler changes:
// Define function pointer type
typedef void (*nmi_handler_t)(void);
// Declare global function pointer in DRAM
nmi_handler_t DRAM_ATTR g_nmi_handler = nullptr;
// Generate dynamic NMI handler
FASTLED_NMI_ASM_SHIM_DYNAMIC(xt_nmi, g_nmi_handler)
void setup() {
// Initialize worker
g_worker = new fl::RmtWorker(RMT_CHANNEL_0, GPIO_NUM_2, 1000);
g_rmt5_nmi_worker = g_worker;
// Set function pointer to handler
g_nmi_handler = &rmt5_nmi_buffer_refill;
// Allocate NMI (same as static example)
esp_intr_alloc(ETS_RMT_INTR_SOURCE, ESP_INTR_FLAG_LEVEL7 | ESP_INTR_FLAG_IRAM,
nullptr, nullptr, &nmi_handle);
}
void switch_handler() {
// Disable NMI first
esp_intr_disable(nmi_handle);
// Change handler
g_nmi_handler = &alternate_nmi_handler;
// Re-enable NMI
esp_intr_enable(nmi_handle);
}
Performance Trade-off:
Before deploying Level 7 NMI in production:
IRAM_ATTRDRAM_ATTRextern "C" for linkagePossible Causes:
IRAM_ATTR → Flash cache missSolution:
IRAM_ATTRportENTER_CRITICAL, xSemaphore, etc.)if (worker == nullptr) return;Possible Causes:
Solution:
esp_intr_alloc() returns ESP_OKfillNextHalf() with GPIO timingPossible Causes:
Solution:
#ifdef ESP32 for platform-specific testsASM_2_C_SHIM.h before using macros| Operation | Cycles | Time (ns) | Notes |
|---|---|---|---|
| Assembly prologue | 16 s32i | ~30ns | Register saves |
| call0 instruction | 1 | ~4ns | Direct call |
| rmt5_nmi_buffer_refill | 5 | ~20ns | Wrapper overhead |
| RmtWorker::fillNextHalf | 120 | ~500ns | Buffer refill logic |
| Assembly epilogue | 16 l32i | ~30ns | Register restores |
| rfi 7 instruction | 1 | ~4ns | Return from NMI |
| Total NMI Latency | ~159 | ~588ns | Within WS2812 spec |
WS2812 Timing Requirements:
DESIGN_DECISIONS.md - Rationale for all implementation choicesARCHITECTURE_FINDINGS.md - Xtensa ISA research and validationASM_2_C_SHIM.h - Macro implementations with inline docsrmt5_worker.cpp - NMI-safe wrapper functionLOOP.md - Agent iteration planning and statusThis integration example is part of the FastLED project and follows the same license.
Last Updated: 2025-11-06 Status: Phase 3 Testing - Integration examples complete Validated On: ESP32 (LX6), ESP32-S3 (LX7) pending hardware testing