Pin Interrupts

The Arduino framework’s attachInterrupt() function is more convenient than the low-level mechanisms provided by avr-libc and other bare-metal frameworks. Unfortunately, for some microcontroller boards, including the Arduino Uno and Nano, it is limited to working with only some pins. For some other microcontroller boards, including the Raspberry Pi Pico, it appears to be buggy.

The CowPi library addresses this by providing functions to register and deregister interrupt service routines for any digital input pin. No debouncing is provided, however, the pin_interrupts example demonstrates a macro.

Sharing data with ISRs

Regardless of whether you configure interrupts using the cowpi_register_pin_ISR() function or a platform-specific mechanism, data cannot be passed to the interrupt-handling code through parameters, and the interrupt-handling code cannot return data through a return value. This necessitates the use of global variables to provide data to, and obtain data from, the interrupt-handling code.

Because the compiler cannot detect any definition-use pairs for these global variables – they are updated in one function and read in another, and no call chain exists between the two functions – the compiler will optimize-away these variables and the code that accesses them in the interest of reducing the program’s memory footprint. The way to prevent this mis-optimization is to use the volatile keyword.

Important

Any global variables that interrupt-handling code reads from and/or writes to must have the volatile modifier.

Registering ISRs for Interrupts

The CowPi library’s cowpi_register_pin_ISR() function allows for pin-based interrupts on any pin configured for digital input and abstracts away the configuration details.

To handle an interrupt, first write a function, such as handle_buttonpress() or handle_keypress(). This function must not have any parameters, and its return type must be void. Then, in the setup() function (or in one of its helper functions), register the interrupt with this code:

cowpi_register_pin_ISR(1L << pin_number, interrupt_handler_name);

or

cowpi_register_pin_ISR((1L << first_pin_number) | (1L << second_pin_number) | (1L << et_cetera), interrupt_handler_name);

This will configure all of the necessary registers to call the function whenever the input value on the pin pin_number (or on the pins first_pin_number, second_pin_number, …, et_cetera) goes from 0 to 1 or from 1 to 0. The first argument is a bit vector that identifies which pin(s) are to be associated with the ISR: if bit n is a 1, then pin n will be associated with the ISR.

As with all ISRs, you want to keep your interrupt handler short. See the CowPi library’s pin_interrupts example for demonstrations.

void cowpi_register_pin_ISR(uint32_t interrupt_mask, void (*isr)(void))

Registers a function to service pin-based interrupts triggered by logic-level changes on one or more pins.

The registered function will be invoked whenever there is a low-to-high or a high-to-low change. If behavior is only required for a rising edge or for a falling edge, or if the behavior for rising and falling edges must differ, then the function should have a conditional to determine the direction of the change.

If the change is generated by a mechanical device, then the isr function is responsible for debouncing if there is not a hardware debouncing circuit.

The interrupt_mask argument is used to specify which pins will be serviced by the registered function. Bit0 corresponds to Pin 0, Bit1 corresponds to Pin 1, and so on. A 1 in a particular bit indicates that the function is to be registered for changes on the corresponding pin. If more than one bit has a 1, then the function will be registered for each of the corresponding pins. If there previously was a function registered to handle changes on a specified pin, then the new function will replace the old function. A bit with a 0 signifies nothing more than that the function is not being registered to service changes on that pin at this time.

Parameters:
  • interrupt_mask – A bit vector specifying which pins will be serviced by the registered ISR

  • isr – The function that will service interrupts triggered by changes on the specified pins

void cowpi_deregister_pin_ISR(uint32_t interrupt_mask)

De-registers the servicing function, if any, for the specified pin(s).

After this function terminates, a logic-level change on the specified pin(s) will no longer be serviced custom function. It is possible that an interrupt would still fire for changes on the pin; if so, it will be serviced by a default, empty function.

The interrupt_mask argument is used to specify which pins will no longer be serviced by a custom function. Bit0 corresponds to Pin 0, Bit1 corresponds to Pin 1, and so on. A 1 in a particular bit indicates that the pin’s ISR is to be deregistered. If more than one bit has a 1, then the ISRs for each of the pins will be deregistered. A bit with a 0 signifies nothing more than that no deregistration actions are to be take for that pin at this time.

Parameters:

interrupt_mask – A bit vector specifying which pins will no longer be serviced by an ISR