|
| 1 | +/* |
| 2 | + Button_interrupt_ada_callback |
| 3 | + |
| 4 | + Created 2019-05-04 |
| 5 | + by Pol Van Aubel |
| 6 | + |
| 7 | + Uses an interrupt to listen for a button input. On the nRF52840, it can listen |
| 8 | + on pin 7 to the UserSW. On other boards, an external switch needs to be provided. |
| 9 | +
|
| 10 | + The Adafruit nRF52* boards are more advanced than "normal" Arduinos and come |
| 11 | + with freeRTOS, interrupts available on every pin, and proper task management. |
| 12 | + This button sketch demonstrates the use of interrupts to act on an input such |
| 13 | + as a switch, without having to poll it in a continuously scanning loop() function. |
| 14 | + Even though human button presses are slow, and therefore unlikely to be missed |
| 15 | + using a scanning loop, the interrupt technique allows the board to go into sleep |
| 16 | + mode while waiting for an input, thereby saving a lot of energy. |
| 17 | +
|
| 18 | + However, because interrupts need to be fast, you must not do Serial communication |
| 19 | + or long tasks directly in an ISR. This is where deferred ISRs come in. However, |
| 20 | + deferred ISRs cannot have a single time-critical component. By using |
| 21 | + ada_callback_fromISR, you can run a fast time-critical component in a normal |
| 22 | + ISR, and then queue up a callback to process the rest. |
| 23 | +
|
| 24 | + AdaFruit's nRF-boards have multiple on-board LEDs you can control. These are |
| 25 | + addressable using the LED_RED, LED_BLUE, LED_BUILTIN and LED_CONN definitions. |
| 26 | + These take care to use the correct LED pin regardless of which board is used. |
| 27 | + If you want to know what pin the on-board LED is connected to on your model, |
| 28 | + check the Technical Specs of your board on https://www.adafruit.com/ or the |
| 29 | + board's variant.h at |
| 30 | + https://github.com/adafruit/Adafruit_nRF52_Arduino/tree/master/variants |
| 31 | + |
| 32 | +
|
| 33 | + This code is licensed under CC0, effectively putting it in the Public Domain |
| 34 | + where possible. |
| 35 | +
|
| 36 | +*/ |
| 37 | + |
| 38 | +// On the nRF52840, pin 7 is the UserSW/DFU switch. |
| 39 | +int interruptPin = 7; |
| 40 | + |
| 41 | +// Because this variable is modified inside a non-deferred ISR, and used outside it, |
| 42 | +// it must be declared volatile to ensure that the most recently written value is |
| 43 | +// always used. |
| 44 | +volatile int numinterrupts; |
| 45 | + |
| 46 | +// the setup function runs once when you press reset or power the board |
| 47 | +void setup(void) { |
| 48 | + Serial.begin(115200); // Not required on the nRF52840 with native USB. |
| 49 | + |
| 50 | + while (!Serial) { // Stalls the nRF52840 until the USB serial console has been opened on the host PC. |
| 51 | + delay(10); |
| 52 | + } |
| 53 | + Serial.println("Press the button!"); |
| 54 | + Serial.flush(); // Because this sketch does nothing else, it will go to sleep rather than send the |
| 55 | + // entire Serial buffer to the host PC. So, use Serial.flush() to make sure it does that |
| 56 | + // before allowing it to sleep. |
| 57 | + |
| 58 | + pinMode(LED_RED, OUTPUT); |
| 59 | + pinMode(interruptPin, INPUT_PULLUP); // Configure the switch pin as an input with internal pull-up register enabled. |
| 60 | + |
| 61 | + // Configure it to call switch_callback if the switch pin transitions from HIGH to LOW, i.e. when it is pressed. |
| 62 | + // On the nRF52840, RISING and CHANGE are also valid options. Depending on your platform, LOW may also be available. |
| 63 | + attachInterrupt(interruptPin, switch_isr, FALLING); |
| 64 | + |
| 65 | + // Since loop() is empty, suspend its task so that the system never runs it |
| 66 | + // and can go to sleep properly. |
| 67 | + suspendLoop(); |
| 68 | +} |
| 69 | + |
| 70 | +// the loop function is empty and should never run. |
| 71 | +void loop(void) { } |
| 72 | + |
| 73 | +// The switch_isr function is the function we attached to the interrupt on line 63. |
| 74 | +// This is known as an Interrupt Service Routine, or ISR. |
| 75 | +// It gets run every time the interrupt fires, in the interrupt context. Nothing else |
| 76 | +// happens while this is running. Therefore, it should be fast and simple. |
| 77 | +// In particular, it should never block, nor do Serial communication or use the Bluefruit API. |
| 78 | +// There are a few freeRTOS functions specifically designed to be run from an ISR. |
| 79 | +void switch_isr(void) { |
| 80 | + // Do short time-critical processing (setting flags, queueing up tasks, etc) here. |
| 81 | + digitalToggle(LED_RED); |
| 82 | + |
| 83 | + ++numinterrupts; |
| 84 | + ada_callback_fromISR(NULL, 0, switch_isr_callback); // Queue up a task with no extra variables and no arguments. |
| 85 | + // Every single interrupt is serviced, because internally, a |
| 86 | + // queue is used. |
| 87 | +} |
| 88 | + |
| 89 | + |
| 90 | +// The switch_isr_callback function is the function we set up as the ada_callback on line 84. |
| 91 | +// It gets run once for every interrupt that switch_isr ran for, but with no guarantees about |
| 92 | +// when that happens. However, the advantage is you have the full range of Serial, Bluefruit, |
| 93 | +// and freeRTOS API to use now. |
| 94 | +// Bear in mind, however, that if this function takes longer to run than the average time between |
| 95 | +// interrupts, at some point your ada_callback_queue will overflow. For that scenario, consider |
| 96 | +// batch-processing interrupts by using direct-to-task notifications. |
| 97 | +void switch_isr_callback(void) { |
| 98 | + Serial.println("Interrupt serviced!"); |
| 99 | + Serial.print("The number of interrupts received at this point was "); |
| 100 | + Serial.println(numinterrupts); |
| 101 | + Serial.flush(); // Because this sketch does nothing else, it will go to sleep rather than send the |
| 102 | + // entire Serial buffer to the host PC. However, here, Serial.flush() doesn't seem |
| 103 | + // to work either. Maybe something to do with the task context? |
| 104 | +} |
0 commit comments