microzig icon indicating copy to clipboard operation
microzig copied to clipboard

RP2040 Interrupt Support

Open MatthiasPortzel opened this issue 1 year ago • 6 comments

Very roughly, the HAL needs to support 4 things:

  • [X] Registering callbacks in the vector table
  • [ ] Enabling interrupts
  • [ ] Masking event-types
  • [ ] Acknowledging the interrupt

MatthiasPortzel avatar May 09 '24 23:05 MatthiasPortzel

I just got an example program running, which I'm going to include here as an idea of what is involved.(This requires a microzig version after #189.)

const microzig = @import("microzig");
const hal = microzig.hal;
const chip = microzig.chip;

// I don't actually know if this needs to be callconv C
pub fn gpio_handler () callconv(.C) void  {
    // Acknowledge/clear the interrupt. With an edge-triggered interrupt,
    //  when missing the acknowledge, the interrupt triggers repeatedly forever
    // We're "listening" for falling-edge events on pin 18
    // Search for "INTR0 Register" in the rp2040 datasheet
    // You can also read this register to determine which events have triggered
    // Note: This line is deceptive. It acknowledges every outstanding GPIO event on INTR2
    //     because modify is implemented as a read and then a write, and other bits will be 1 if they are active
    chip.peripherals.IO_BANK0.INTR2.modify(.{
        .GPIO18_EDGE_LOW = 1
    });

    // Turn the on-board light on the Pico on and then off
    hal.gpio.num(25).put(1);
    hal.time.sleep_ms(1000);
    hal.gpio.num(25).put(0);
    hal.time.sleep_ms(200);
}

// Insert our handler into the vector table
// Microzig looks for this "microzig_options" struct and inserts the address
// of our handler function into the vector table when linking the program
pub const microzig_options = .{
    .interrupts = .{
        .IO_IRQ_BANK0 = microzig.interrupt.Handler {
            .C = gpio_handler
        }
    }
};

const pin_config = hal.pins.GlobalConfiguration {
    // on board LED
    .GPIO25 = .{
        .name = "led_1",
        .direction = .out,
    },
    // Button
    .GPIO18 = .{
        .name = "button_1",
        .direction = .in,
        .pull = .down,
    },
};


pub fn main() noreturn {
    // The intention is that you can short this high by pressing a button,
    //  and when releasing the button, the interrupt will fire
    _ = pin_config.apply();

    // Enable the GPIO interrupt for the GPIO bank
    // 13 is IO_IRQ_BANK0. This bit field isn't defined in the svd
    // Search for "NVIC_ISER" and "IO_IRQ_BANK0" in the rp2040 datasheet for more info
    chip.peripherals.PPB.NVIC_ISER.modify(.{
        .SETENA = 1 << 13 // 13 is IO_IRQ_BANK0
    });

    // TODO: "Clear stale events which might cause immediate spurious handler entry"
    // https://github.com/raspberrypi/pico-sdk/blob/6a7db34ff63345a7badec79ebea3aaef1712f374/src/rp2_common/hardware_gpio/gpio.c#L164C8-L164C77

    // Mask the interrupt to only fire on the falling-edge of pin 18
    // Without this, the handler would run for every event on every pin
    // "2.19.3. Interrupts" is of course helpful
    // As with INTR, the 32 pins are split across 4 registers, since each has 4 possible events
    // * PROC0_INTE0 handles GPIO pins 0 to 7
    // * PROC0_INTE1 handles GPIO pins 8 to 15
    // * PROC0_INTE2 handles GPIO pins 16 to 23
    // * PROC0_INTE3 handles GPIO pins 24 to 31
    // Our button is on 18, so we want PROC0_INTE2
    // And we want to trigger on the high (1)->low (0) edge for the demo
    // That's GPIO18_EDGE_LOW
    chip.peripherals.IO_BANK0.PROC0_INTE2.modify(.{
        .GPIO18_EDGE_LOW = 1
    });

    // Heartbeat so we know it's running
    while (true) {
        hal.gpio.num(25).put(1);
        hal.time.sleep_ms(100);
        hal.gpio.num(25).put(0);
        hal.time.sleep_ms(900);
    }
}

MatthiasPortzel avatar May 10 '24 02:05 MatthiasPortzel

Registering callbacks works right now, but has regressed from previous versions where you could do:

pub const microzig_options = .{
    .interrupts = .{
        .IO_IRQ_BANK0 = gpio_handler
    }
};

There's a function in the microzig source which is supposed to convert your function into a microzig.interrupt.Handler, but which is never called: https://github.com/ZigEmbeddedGroup/microzig/blame/ed0e5fe9f1774e520fc2a60e4ae93a6456a62fad/core/src/cpus/cortex-m.zig#L136

MatthiasPortzel avatar May 10 '24 02:05 MatthiasPortzel

The SVD currently doesn't break out the fields of NVIC_ISER, it's probably worth looking into updating that—that would allow us to completely avoid the bit manipulation in this example.

MatthiasPortzel avatar May 11 '24 23:05 MatthiasPortzel

Chatted during the meeting about this issue, what we want to do is add comptime field generation to the microzig.cpu module for the different Cortex M's which is taken from the generated vector table.

mattnite avatar Jan 15 '25 15:01 mattnite

In your example, would chip.peripherals.IO_BANK0.INTR2.write work to clear only the interrupt you care about?

Grazfather avatar Mar 25 '25 15:03 Grazfather

I don't remember why I edited to add that note instead of fixing the line, but without checking, .write sounds correct. (Likely I didn't want to update it without re-testing and didn't have the hardware in front of me to re-test.)

MatthiasPortzel avatar Mar 25 '25 15:03 MatthiasPortzel