Mirror with some correction
Dependencies: mbed FastIO FastPWM USBDevice
FastInterruptIn/FastInterruptIn.h@116:7a67265d7c19, 2021-10-01 (annotated)
- Committer:
- arnoz
- Date:
- Fri Oct 01 08:19:46 2021 +0000
- Revision:
- 116:7a67265d7c19
- Parent:
- 101:755f44622abc
- Correct information regarding your last merge
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
mjr | 82:4f6209cb5c33 | 1 | // Fast Interrupt In for KL25Z |
mjr | 82:4f6209cb5c33 | 2 | // |
mjr | 82:4f6209cb5c33 | 3 | // This is a replacement for the mbed library InterruptIn class, which |
mjr | 82:4f6209cb5c33 | 4 | // sets up GPIO ports for edge-sensitive interrupt handling. This class |
mjr | 82:4f6209cb5c33 | 5 | // provides the same API but has a shorter code path for responding to |
mjr | 82:4f6209cb5c33 | 6 | // each interrupt. In my tests, the mbed InterruptIn class has a maximum |
mjr | 82:4f6209cb5c33 | 7 | // interrupt rate of about 112kHz; this class can increase that to about |
mjr | 82:4f6209cb5c33 | 8 | // 181kHz. |
mjr | 82:4f6209cb5c33 | 9 | // |
mjr | 82:4f6209cb5c33 | 10 | // If speed is critical (and it is, because why else would you be using |
mjr | 82:4f6209cb5c33 | 11 | // this class?), you should elevate the GPIO interrupt priority in the |
mjr | 82:4f6209cb5c33 | 12 | // hardware interrupt controller so that GPIO pin signals can preempt other |
mjr | 82:4f6209cb5c33 | 13 | // interrupt handlers. The mbed USB and timer handlers in particular spend |
mjr | 82:4f6209cb5c33 | 14 | // relative long periods in interrupt context, so if these are at the same |
mjr | 82:4f6209cb5c33 | 15 | // or higher priority than the GPIO interrupts, they'll become the limiting |
mjr | 82:4f6209cb5c33 | 16 | // factor. The mbed library leaves all interrupts set to maximum priority |
mjr | 82:4f6209cb5c33 | 17 | // by default, so to elevate the GPIO interrupt priority, you have to lower |
mjr | 82:4f6209cb5c33 | 18 | // the priority of everything else. Call FastInterruptIn::elevatePriority() |
mjr | 82:4f6209cb5c33 | 19 | // to do this. |
mjr | 82:4f6209cb5c33 | 20 | // |
mjr | 82:4f6209cb5c33 | 21 | // |
mjr | 82:4f6209cb5c33 | 22 | // Performance measurements: I set up a test program using one KL25Z to |
mjr | 82:4f6209cb5c33 | 23 | // send 50% duty cycle square wave signals to a second KL25Z (using a PWM |
mjr | 82:4f6209cb5c33 | 24 | // output on the sender), and measured the maximum interrupt frequency |
mjr | 82:4f6209cb5c33 | 25 | // where the receiver could correctly count every edge, repeating the test |
mjr | 82:4f6209cb5c33 | 26 | // with FastInterruptIn and the mbed InterruptIn. I tested with handlers |
mjr | 82:4f6209cb5c33 | 27 | // for both edges and handlers for single edges (just rise() or just fall()). |
mjr | 82:4f6209cb5c33 | 28 | // The Hz rates reflect the maximum *interrupt* frequency, which is twice |
mjr | 82:4f6209cb5c33 | 29 | // the PWM frequency when testing with handlers for both rise + fall in |
mjr | 82:4f6209cb5c33 | 30 | // effect. In all cases, the user callbacks were minimal code paths that |
mjr | 82:4f6209cb5c33 | 31 | // just incremented counters, and all tests ran with PTA/PTD at elevated |
mjr | 82:4f6209cb5c33 | 32 | // IRQ priority. The time per interrupt values shown are the inverse of |
mjr | 82:4f6209cb5c33 | 33 | // the maximum frequency; these reflect the time between interrupts at |
mjr | 82:4f6209cb5c33 | 34 | // the corresponding frequency. Since each frequency is the maximum at |
mjr | 82:4f6209cb5c33 | 35 | // which that class can handle every interrupt without losing any, the |
mjr | 82:4f6209cb5c33 | 36 | // time between interrupts tells us how long the CPU takes to fully process |
mjr | 82:4f6209cb5c33 | 37 | // one interrupt and return to the base state where it's able to handle the |
mjr | 82:4f6209cb5c33 | 38 | // next one. This time is the sum of the initial CPU interrupt latency |
mjr | 82:4f6209cb5c33 | 39 | // (the time it takes from an edge signal occuring on a pin to the CPU |
mjr | 82:4f6209cb5c33 | 40 | // executing the first instruction of the IRQ vector), the time spent in |
mjr | 82:4f6209cb5c33 | 41 | // the InterruptIn or FastInterruptIn code, the time spent in the user |
mjr | 82:4f6209cb5c33 | 42 | // callback, and the time for the CPU to return from the interrupt to |
mjr | 82:4f6209cb5c33 | 43 | // normal context. For the test program, the user callback is about 4 |
mjr | 82:4f6209cb5c33 | 44 | // instructions, so perhaps 6 clocks or 360ns. Other people have measured |
mjr | 82:4f6209cb5c33 | 45 | // the M0+ initial interrupt latency at about 450ns, and the return time |
mjr | 82:4f6209cb5c33 | 46 | // is probably similar. So we have about 1.2us in fixed overhead and user |
mjr | 82:4f6209cb5c33 | 47 | // callback time, hence the rest is the time spent in the library code. |
mjr | 82:4f6209cb5c33 | 48 | // |
mjr | 82:4f6209cb5c33 | 49 | // mbed InterruptIn: |
mjr | 82:4f6209cb5c33 | 50 | // max rate 112kHz |
mjr | 82:4f6209cb5c33 | 51 | // -> 8.9us per interrupt |
mjr | 82:4f6209cb5c33 | 52 | // less 1.2us fixed overhead = 7.7us in library code |
mjr | 82:4f6209cb5c33 | 53 | // |
mjr | 82:4f6209cb5c33 | 54 | // FastInterruptIn: |
mjr | 82:4f6209cb5c33 | 55 | // max rate 181kHz |
mjr | 82:4f6209cb5c33 | 56 | // -> 5.5us per interrupt |
mjr | 82:4f6209cb5c33 | 57 | // less 1.2us fixed overhead = 3.3us in library code |
mjr | 82:4f6209cb5c33 | 58 | // |
mjr | 82:4f6209cb5c33 | 59 | // |
mjr | 82:4f6209cb5c33 | 60 | // Limitations: |
mjr | 82:4f6209cb5c33 | 61 | // |
mjr | 82:4f6209cb5c33 | 62 | // 1. KL25Z ONLY. This is a bare-metal KL25Z class. |
mjr | 82:4f6209cb5c33 | 63 | // |
mjr | 82:4f6209cb5c33 | 64 | // 2. Globally incompatible with InterruptIn. Both classes take over the |
mjr | 82:4f6209cb5c33 | 65 | // IRQ vectors for the GPIO interrupts globally, so they can't be mixed |
mjr | 82:4f6209cb5c33 | 66 | // in the same system. If you use this class anywhere in a program, it |
mjr | 82:4f6209cb5c33 | 67 | // has to be used exclusively throughout the whole program - don't use |
mjr | 82:4f6209cb5c33 | 68 | // the mbed InterruptIn anywhere in a program that uses this class. |
mjr | 82:4f6209cb5c33 | 69 | // |
mjr | 82:4f6209cb5c33 | 70 | // 3. API differences. The API is very similar to InterruptIn's API, |
mjr | 82:4f6209cb5c33 | 71 | // but we don't support the method-based rise/fall callback attachers. We |
mjr | 82:4f6209cb5c33 | 72 | // instead use static function pointers (void functions with 'void *' |
mjr | 82:4f6209cb5c33 | 73 | // context arguments). It's easy to write static methods for these that |
mjr | 82:4f6209cb5c33 | 74 | // dispatch to regular member functions, so the functionality is the same; |
mjr | 82:4f6209cb5c33 | 75 | // it's just a little different syntax. The simpler (in the sense of |
mjr | 82:4f6209cb5c33 | 76 | // more primitive) callback interface saves a little memory and is |
mjr | 82:4f6209cb5c33 | 77 | // slightly faster than the method attachers, since it doesn't require |
mjr | 82:4f6209cb5c33 | 78 | // any variation checks at interrupt time. |
mjr | 82:4f6209cb5c33 | 79 | // |
mjr | 82:4f6209cb5c33 | 80 | // Theory of operation |
mjr | 82:4f6209cb5c33 | 81 | // |
mjr | 82:4f6209cb5c33 | 82 | // How the mbed code works |
mjr | 82:4f6209cb5c33 | 83 | // On every interrupt event, the mbed library's GPIO interrupt handler |
mjr | 82:4f6209cb5c33 | 84 | // searches for a port with an active interrupt. Each PORTx_IRQn vector |
mjr | 82:4f6209cb5c33 | 85 | // handles 32 ports, so each handler has to search this space of 32 ports |
mjr | 82:4f6209cb5c33 | 86 | // for an active interrupt. The mbed code approaches this problem by |
mjr | 82:4f6209cb5c33 | 87 | // searching for a '1' bit in the ISFR (interrupt status flags register), |
mjr | 82:4f6209cb5c33 | 88 | // which is effectively a 32-bit vector of bits indicating which ports have |
mjr | 82:4f6209cb5c33 | 89 | // active interrupts. This search could be done quickly if the hardware |
mjr | 82:4f6209cb5c33 | 90 | // had a "count leading zeroes" instruction, which actually does exist in |
mjr | 82:4f6209cb5c33 | 91 | // the ARM instruction set, but alas not in the M0+ subset. So the mbed |
mjr | 82:4f6209cb5c33 | 92 | // code has to search for the bit by other means. It accomplishes this by |
mjr | 82:4f6209cb5c33 | 93 | // way of a binary search. By my estimate, this takes about 110 clocks or |
mjr | 82:4f6209cb5c33 | 94 | // 7us. The routine has some other slight overhead dispatching to the |
mjr | 82:4f6209cb5c33 | 95 | // user callback once one is selected via the bit search, but the bulk of |
mjr | 82:4f6209cb5c33 | 96 | // the time is spent in the bit search. The mbed code could be made more |
mjr | 82:4f6209cb5c33 | 97 | // efficient by using a better 'count leading zeroes' algorithm; there are |
mjr | 82:4f6209cb5c33 | 98 | // readily available implementations that run in about 15 clocks on M0+. |
mjr | 82:4f6209cb5c33 | 99 | // |
mjr | 82:4f6209cb5c33 | 100 | // How this code works |
mjr | 82:4f6209cb5c33 | 101 | // FastInterruptIn takes a different approach that bypasses the bit vector |
mjr | 82:4f6209cb5c33 | 102 | // search. We instead search the installed handlers. We work on the |
mjr | 82:4f6209cb5c33 | 103 | // assumption that the total number of interrupt handlers in the system is |
mjr | 82:4f6209cb5c33 | 104 | // small compared with the number of ports. So instead of searching the |
mjr | 82:4f6209cb5c33 | 105 | // entire ISFR bit vector, we only check the ports with installed handlers. |
mjr | 82:4f6209cb5c33 | 106 | // |
mjr | 82:4f6209cb5c33 | 107 | // The mbed code takes essentially constant time to run. It doesn't have |
mjr | 82:4f6209cb5c33 | 108 | // any dependencies (that I can see) on the number of active InterruptIn |
mjr | 82:4f6209cb5c33 | 109 | // pins. In contrast, FastInterruptIn's run time is linear in the number |
mjr | 82:4f6209cb5c33 | 110 | // of active pins: adding more pins will increase the run time. This is |
mjr | 82:4f6209cb5c33 | 111 | // a tradeoff, obviously. It's very much the right tradeoff for the Pinscape |
mjr | 82:4f6209cb5c33 | 112 | // system, because we have very few interrupt pins overall. I suspect it's |
mjr | 82:4f6209cb5c33 | 113 | // the right tradeoff for most systems, too, since most embedded systems |
mjr | 82:4f6209cb5c33 | 114 | // have a small fixed set of peripherals they're talking to. |
mjr | 82:4f6209cb5c33 | 115 | // |
mjr | 82:4f6209cb5c33 | 116 | // We have a few other small optimizations to maximize our sustainable |
mjr | 82:4f6209cb5c33 | 117 | // interrupt frequency. The most important is probably that we read the |
mjr | 82:4f6209cb5c33 | 118 | // port pin state immediately on entry to the IRQ vector handler. Since |
mjr | 82:4f6209cb5c33 | 119 | // we get the same interrupt on a rising or falling edge, we have to read |
mjr | 82:4f6209cb5c33 | 120 | // the pin state to determine which type of transition triggered the |
mjr | 82:4f6209cb5c33 | 121 | // interrupt. This is inherently problematic because the pin state could |
mjr | 82:4f6209cb5c33 | 122 | // have changed between the time the interrupt occurred and the time we |
mjr | 82:4f6209cb5c33 | 123 | // got around to reading the state - the likelihood of this increases as |
mjr | 82:4f6209cb5c33 | 124 | // the interrupt source frequency increases. The soonest we can possibly |
mjr | 82:4f6209cb5c33 | 125 | // read the state is at entry to the IRQ vector handler, so we do that. |
mjr | 82:4f6209cb5c33 | 126 | // Even that isn't perfectly instantaneous, due to the unavoidable 450ns |
mjr | 82:4f6209cb5c33 | 127 | // or so latency in the hardware before the vector code starts executing; |
mjr | 82:4f6209cb5c33 | 128 | // it would be better if the hardware read the state at the moment the |
mjr | 82:4f6209cb5c33 | 129 | // interrupt was triggered, but there's nothing we can do about that. |
mjr | 82:4f6209cb5c33 | 130 | // In contrast, the mbed code waits until after deciding which interrupt |
mjr | 82:4f6209cb5c33 | 131 | // is active to read the port, so its reading is about 7us delayed vs our |
mjr | 82:4f6209cb5c33 | 132 | // 500ns delay. That further reduces the mbed code's ability to keep up |
mjr | 82:4f6209cb5c33 | 133 | // with fast interrupt sources when both rise and fall handlers are needed. |
mjr | 82:4f6209cb5c33 | 134 | |
mjr | 82:4f6209cb5c33 | 135 | |
mjr | 82:4f6209cb5c33 | 136 | #ifndef _FASTINTERRUPTIN_H_ |
mjr | 82:4f6209cb5c33 | 137 | #define _FASTINTERRUPTIN_H_ |
mjr | 82:4f6209cb5c33 | 138 | |
mjr | 82:4f6209cb5c33 | 139 | #include "mbed.h" |
mjr | 82:4f6209cb5c33 | 140 | #include "gpio_api.h" |
mjr | 82:4f6209cb5c33 | 141 | |
mjr | 82:4f6209cb5c33 | 142 | struct fiiCallback |
mjr | 82:4f6209cb5c33 | 143 | { |
mjr | 82:4f6209cb5c33 | 144 | fiiCallback() { func = 0; } |
mjr | 82:4f6209cb5c33 | 145 | void (*func)(void *); |
mjr | 82:4f6209cb5c33 | 146 | void *context; |
mjr | 82:4f6209cb5c33 | 147 | |
mjr | 82:4f6209cb5c33 | 148 | inline void call() { func(context); } |
mjr | 82:4f6209cb5c33 | 149 | }; |
mjr | 82:4f6209cb5c33 | 150 | |
mjr | 82:4f6209cb5c33 | 151 | class FastInterruptIn |
mjr | 82:4f6209cb5c33 | 152 | { |
mjr | 82:4f6209cb5c33 | 153 | public: |
mjr | 82:4f6209cb5c33 | 154 | // Globally elevate the PTA and PTD interrupt priorities. Since the |
mjr | 82:4f6209cb5c33 | 155 | // mbed default is to start with all IRQs at maximum priority, we |
mjr | 82:4f6209cb5c33 | 156 | // LOWER the priority of all IRQs to the minimum, then raise the PTA |
mjr | 82:4f6209cb5c33 | 157 | // and PTD interrupts to maximum priority. |
mjr | 82:4f6209cb5c33 | 158 | // |
mjr | 82:4f6209cb5c33 | 159 | // The reason we set all priorities to minimum (except for PTA and PTD) |
mjr | 82:4f6209cb5c33 | 160 | // rather than some medium priority is that this is the most flexible |
mjr | 82:4f6209cb5c33 | 161 | // default. It really should have been the mbed default, in my opinion, |
mjr | 82:4f6209cb5c33 | 162 | // since (1) it doesn't matter what the setting is if they're all the |
mjr | 82:4f6209cb5c33 | 163 | // same, so an mbed default of 3 would have been equivalent to an mbed |
mjr | 82:4f6209cb5c33 | 164 | // default of 0 (the current one) for all programs that don't make any |
mjr | 82:4f6209cb5c33 | 165 | // changes anyway, and (2) the most likely use case for programs that |
mjr | 82:4f6209cb5c33 | 166 | // do need to differentiate IRQ priorities is that they need one or two |
mjr | 82:4f6209cb5c33 | 167 | // items to respond MORE quickly. It seems extremely unlikely that |
mjr | 82:4f6209cb5c33 | 168 | // anyone would need only one or two to be especially slow, which is |
mjr | 82:4f6209cb5c33 | 169 | // effectively the case the mbed default is optimized for. |
mjr | 82:4f6209cb5c33 | 170 | // |
mjr | 82:4f6209cb5c33 | 171 | // This should be called (if desired at all) once at startup. The |
mjr | 82:4f6209cb5c33 | 172 | // effect is global and permanent (unless later changes are made by |
mjr | 82:4f6209cb5c33 | 173 | // someone else), so there's no need to call this again when setting |
mjr | 82:4f6209cb5c33 | 174 | // up new handlers or changing existing handlers. Callers are free to |
mjr | 82:4f6209cb5c33 | 175 | // further adjust priorities as needed (e.g., elevate the priority of |
mjr | 82:4f6209cb5c33 | 176 | // some other IRQ), but that should be done after calling this, since we |
mjr | 82:4f6209cb5c33 | 177 | // change ALL IRQ priorities with prejudice. |
mjr | 82:4f6209cb5c33 | 178 | static void elevatePriority() |
mjr | 82:4f6209cb5c33 | 179 | { |
mjr | 82:4f6209cb5c33 | 180 | // Set all IRQ priorities to minimum. M0+ has priority levels |
mjr | 82:4f6209cb5c33 | 181 | // 0 (highest) to 3 (lowest). (Note that the hardware uses the |
mjr | 82:4f6209cb5c33 | 182 | // high-order two bits of the low byte, so the hardware priority |
mjr | 82:4f6209cb5c33 | 183 | // levels are 0x00 [highest], 0x40, 0x80, 0xC0 [lowest]). The |
mjr | 82:4f6209cb5c33 | 184 | // mbed NVIC macros, in contrast, abstract this to use the LOW |
mjr | 82:4f6209cb5c33 | 185 | // two bits, for levels 0, 1, 2, 3.) |
mjr | 82:4f6209cb5c33 | 186 | for (int irq = 0 ; irq < 32 ; ++irq) |
mjr | 82:4f6209cb5c33 | 187 | NVIC_SetPriority(IRQn(irq), 0x3); |
mjr | 82:4f6209cb5c33 | 188 | |
mjr | 82:4f6209cb5c33 | 189 | // set the PTA and PTD IRQs to highest priority |
mjr | 82:4f6209cb5c33 | 190 | NVIC_SetPriority(PORTA_IRQn, 0x00); |
mjr | 82:4f6209cb5c33 | 191 | NVIC_SetPriority(PORTD_IRQn, 0x00); |
mjr | 82:4f6209cb5c33 | 192 | } |
mjr | 82:4f6209cb5c33 | 193 | |
mjr | 82:4f6209cb5c33 | 194 | // set up a FastInterruptIn handler on a given pin |
mjr | 82:4f6209cb5c33 | 195 | FastInterruptIn(PinName pin) |
mjr | 82:4f6209cb5c33 | 196 | { |
mjr | 82:4f6209cb5c33 | 197 | // start with the null callback |
mjr | 82:4f6209cb5c33 | 198 | callcb = &FastInterruptIn::callNone; |
mjr | 82:4f6209cb5c33 | 199 | |
mjr | 82:4f6209cb5c33 | 200 | // initialize the pin as a GPIO Digital In port |
mjr | 82:4f6209cb5c33 | 201 | gpio_t gpio; |
mjr | 82:4f6209cb5c33 | 202 | gpio_init_in(&gpio, pin); |
mjr | 82:4f6209cb5c33 | 203 | |
mjr | 82:4f6209cb5c33 | 204 | // get the port registers |
mjr | 82:4f6209cb5c33 | 205 | PDIR = gpio.reg_in; |
mjr | 82:4f6209cb5c33 | 206 | pinMask = gpio.mask; |
mjr | 82:4f6209cb5c33 | 207 | portno = uint8_t(pin >> PORT_SHIFT); |
mjr | 82:4f6209cb5c33 | 208 | pinno = uint8_t((pin & 0x7F) >> 2); |
mjr | 82:4f6209cb5c33 | 209 | |
mjr | 82:4f6209cb5c33 | 210 | // set up for the selected port |
mjr | 82:4f6209cb5c33 | 211 | IRQn_Type irqn; |
mjr | 82:4f6209cb5c33 | 212 | void (*vector)(); |
mjr | 82:4f6209cb5c33 | 213 | switch (portno) |
mjr | 82:4f6209cb5c33 | 214 | { |
mjr | 82:4f6209cb5c33 | 215 | case PortA: |
mjr | 82:4f6209cb5c33 | 216 | irqn = PORTA_IRQn; |
mjr | 82:4f6209cb5c33 | 217 | vector = &PortA_ISR; |
mjr | 82:4f6209cb5c33 | 218 | PDIR = &FPTA->PDIR; |
mjr | 82:4f6209cb5c33 | 219 | break; |
mjr | 82:4f6209cb5c33 | 220 | |
mjr | 82:4f6209cb5c33 | 221 | case PortD: |
mjr | 82:4f6209cb5c33 | 222 | irqn = PORTD_IRQn; |
mjr | 82:4f6209cb5c33 | 223 | vector = &PortD_ISR; |
mjr | 82:4f6209cb5c33 | 224 | PDIR = &FPTD->PDIR; |
mjr | 82:4f6209cb5c33 | 225 | break; |
mjr | 82:4f6209cb5c33 | 226 | |
mjr | 82:4f6209cb5c33 | 227 | default: |
mjr | 82:4f6209cb5c33 | 228 | error("FastInterruptIn: invalid pin specified; " |
mjr | 82:4f6209cb5c33 | 229 | "only PTAxx and PTDxx pins are interrupt-capable"); |
mjr | 82:4f6209cb5c33 | 230 | return; |
mjr | 82:4f6209cb5c33 | 231 | } |
mjr | 82:4f6209cb5c33 | 232 | |
mjr | 82:4f6209cb5c33 | 233 | // set the vector |
mjr | 82:4f6209cb5c33 | 234 | NVIC_SetVector(irqn, uint32_t(vector)); |
mjr | 82:4f6209cb5c33 | 235 | NVIC_EnableIRQ(irqn); |
mjr | 82:4f6209cb5c33 | 236 | } |
mjr | 82:4f6209cb5c33 | 237 | |
mjr | 82:4f6209cb5c33 | 238 | // read the current pin status - returns 1 or 0 |
mjr | 82:4f6209cb5c33 | 239 | int read() const { return (fastread() >> pinno) & 0x01; } |
mjr | 82:4f6209cb5c33 | 240 | |
mjr | 82:4f6209cb5c33 | 241 | // Fast read - returns the pin's port bit, which is '0' or '1' shifted |
mjr | 82:4f6209cb5c33 | 242 | // left by the port number (e.g., PTA7 or PTD7 return (1<<7) or (0<<7)). |
mjr | 82:4f6209cb5c33 | 243 | // This is slightly faster than read() because it doesn't normalize the |
mjr | 82:4f6209cb5c33 | 244 | // result to a literal '0' or '1' value. When the value is only needed |
mjr | 82:4f6209cb5c33 | 245 | // for an 'if' test or the like, zero/nonzero is generally good enough, |
mjr | 82:4f6209cb5c33 | 246 | // so you can save a tiny bit of time by skiping the shift. |
mjr | 82:4f6209cb5c33 | 247 | uint32_t fastread() const { return *PDIR & pinMask; } |
mjr | 82:4f6209cb5c33 | 248 | |
mjr | 82:4f6209cb5c33 | 249 | // set a rising edge handler |
mjr | 82:4f6209cb5c33 | 250 | void rise(void (*func)(void *), void *context = 0) |
mjr | 82:4f6209cb5c33 | 251 | { |
mjr | 82:4f6209cb5c33 | 252 | setHandler(&cbRise, PCR_IRQC_RISING, func, context); |
mjr | 82:4f6209cb5c33 | 253 | } |
mjr | 82:4f6209cb5c33 | 254 | |
mjr | 82:4f6209cb5c33 | 255 | // set a falling edge handler |
mjr | 82:4f6209cb5c33 | 256 | void fall(void (*func)(void *), void *context = 0) |
mjr | 82:4f6209cb5c33 | 257 | { |
mjr | 82:4f6209cb5c33 | 258 | setHandler(&cbFall, PCR_IRQC_FALLING, func, context); |
mjr | 82:4f6209cb5c33 | 259 | } |
mjr | 82:4f6209cb5c33 | 260 | |
mjr | 82:4f6209cb5c33 | 261 | // Set the pull mode. Note that the KL25Z only supports PullUp |
mjr | 82:4f6209cb5c33 | 262 | // and PullNone modes. We'll ignore other modes. |
mjr | 82:4f6209cb5c33 | 263 | void mode(PinMode pull) |
mjr | 82:4f6209cb5c33 | 264 | { |
mjr | 82:4f6209cb5c33 | 265 | volatile uint32_t *PCR = &(portno == PortA ? PORTA : PORTD)->PCR[pinno]; |
mjr | 82:4f6209cb5c33 | 266 | switch (pull) |
mjr | 82:4f6209cb5c33 | 267 | { |
mjr | 82:4f6209cb5c33 | 268 | case PullNone: |
mjr | 82:4f6209cb5c33 | 269 | *PCR &= ~PORT_PCR_PE_MASK; |
mjr | 82:4f6209cb5c33 | 270 | break; |
mjr | 82:4f6209cb5c33 | 271 | |
mjr | 82:4f6209cb5c33 | 272 | case PullUp: |
mjr | 82:4f6209cb5c33 | 273 | *PCR |= PORT_PCR_PE_MASK; |
mjr | 82:4f6209cb5c33 | 274 | break; |
mjr | 82:4f6209cb5c33 | 275 | } |
mjr | 82:4f6209cb5c33 | 276 | } |
mjr | 101:755f44622abc | 277 | |
mjr | 82:4f6209cb5c33 | 278 | protected: |
mjr | 82:4f6209cb5c33 | 279 | // set a handler - the mode is PCR_IRQC_RISING or PCR_IRQC_FALLING |
mjr | 82:4f6209cb5c33 | 280 | void setHandler( |
mjr | 82:4f6209cb5c33 | 281 | fiiCallback *cb, uint32_t mode, void (*func)(void *), void *context) |
mjr | 82:4f6209cb5c33 | 282 | { |
mjr | 82:4f6209cb5c33 | 283 | // get the PCR (port control register) for the pin |
mjr | 82:4f6209cb5c33 | 284 | volatile uint32_t *PCR = &(portno == PortA ? PORTA : PORTD)->PCR[pinno]; |
mjr | 82:4f6209cb5c33 | 285 | |
mjr | 82:4f6209cb5c33 | 286 | // disable interrupts while messing with shared statics |
mjr | 82:4f6209cb5c33 | 287 | __disable_irq(); |
mjr | 82:4f6209cb5c33 | 288 | |
mjr | 82:4f6209cb5c33 | 289 | // set the callback |
mjr | 82:4f6209cb5c33 | 290 | cb->func = func; |
mjr | 82:4f6209cb5c33 | 291 | cb->context = context; |
mjr | 82:4f6209cb5c33 | 292 | |
mjr | 82:4f6209cb5c33 | 293 | // enable or disable the mode in the PCR |
mjr | 82:4f6209cb5c33 | 294 | if (func != 0) |
mjr | 82:4f6209cb5c33 | 295 | { |
mjr | 82:4f6209cb5c33 | 296 | // Handler function is non-null, so we're setting a handler. |
mjr | 82:4f6209cb5c33 | 297 | // Enable the mode in the PCR. Note that we merely need to |
mjr | 82:4f6209cb5c33 | 298 | // OR the new mode bits into the existing mode bits, since |
mjr | 82:4f6209cb5c33 | 299 | // disabled is 0 and BOTH is equal to RISING|FALLING. |
mjr | 82:4f6209cb5c33 | 300 | *PCR |= mode; |
mjr | 82:4f6209cb5c33 | 301 | |
mjr | 82:4f6209cb5c33 | 302 | // if we're not already in the active list, add us |
mjr | 82:4f6209cb5c33 | 303 | listAdd(); |
mjr | 82:4f6209cb5c33 | 304 | } |
mjr | 82:4f6209cb5c33 | 305 | else |
mjr | 82:4f6209cb5c33 | 306 | { |
mjr | 82:4f6209cb5c33 | 307 | // Handler function is null, so we're clearing the handler. |
mjr | 82:4f6209cb5c33 | 308 | // Disable the mode bits in the PCR. If the old mode was |
mjr | 82:4f6209cb5c33 | 309 | // the same as the mode we're disabling, switch to NONE. |
mjr | 82:4f6209cb5c33 | 310 | // If the old mode was BOTH, switch to the mode we're NOT |
mjr | 82:4f6209cb5c33 | 311 | // disabling. Otherwise make no change. |
mjr | 82:4f6209cb5c33 | 312 | int cur = *PCR & PORT_PCR_IRQC_MASK; |
mjr | 82:4f6209cb5c33 | 313 | if (cur == PCR_IRQC_BOTH) |
mjr | 82:4f6209cb5c33 | 314 | { |
mjr | 82:4f6209cb5c33 | 315 | *PCR &= ~PORT_PCR_IRQC_MASK; |
mjr | 82:4f6209cb5c33 | 316 | *PCR |= (mode == PCR_IRQC_FALLING ? PCR_IRQC_RISING : PCR_IRQC_FALLING); |
mjr | 82:4f6209cb5c33 | 317 | } |
mjr | 82:4f6209cb5c33 | 318 | else if (cur == mode) |
mjr | 82:4f6209cb5c33 | 319 | { |
mjr | 82:4f6209cb5c33 | 320 | *PCR &= ~PORT_PCR_IRQC_MASK; |
mjr | 82:4f6209cb5c33 | 321 | } |
mjr | 82:4f6209cb5c33 | 322 | |
mjr | 82:4f6209cb5c33 | 323 | // if we're disabled, remove us from the list |
mjr | 82:4f6209cb5c33 | 324 | if ((*PCR & PORT_PCR_IRQC_MASK) == PCR_IRQC_DISABLED) |
mjr | 82:4f6209cb5c33 | 325 | listRemove(); |
mjr | 82:4f6209cb5c33 | 326 | } |
mjr | 82:4f6209cb5c33 | 327 | |
mjr | 82:4f6209cb5c33 | 328 | // set the appropriate callback mode |
mjr | 82:4f6209cb5c33 | 329 | if (cbRise.func != 0 && cbFall.func != 0) |
mjr | 82:4f6209cb5c33 | 330 | { |
mjr | 82:4f6209cb5c33 | 331 | // They want to be called on both Rise and Fall events. |
mjr | 82:4f6209cb5c33 | 332 | // The hardware triggers the same interrupt on both, so we |
mjr | 82:4f6209cb5c33 | 333 | // need to distinguish which is which by checking the current |
mjr | 82:4f6209cb5c33 | 334 | // pin status when the interrupt occurs. |
mjr | 82:4f6209cb5c33 | 335 | callcb = &FastInterruptIn::callBoth; |
mjr | 82:4f6209cb5c33 | 336 | } |
mjr | 82:4f6209cb5c33 | 337 | else if (cbRise.func != 0) |
mjr | 82:4f6209cb5c33 | 338 | { |
mjr | 82:4f6209cb5c33 | 339 | // they only want Rise events |
mjr | 82:4f6209cb5c33 | 340 | callcb = &FastInterruptIn::callRise; |
mjr | 82:4f6209cb5c33 | 341 | } |
mjr | 82:4f6209cb5c33 | 342 | else if (cbFall.func != 0) |
mjr | 82:4f6209cb5c33 | 343 | { |
mjr | 82:4f6209cb5c33 | 344 | // they only want Fall events |
mjr | 82:4f6209cb5c33 | 345 | callcb = &FastInterruptIn::callFall; |
mjr | 82:4f6209cb5c33 | 346 | } |
mjr | 82:4f6209cb5c33 | 347 | else |
mjr | 82:4f6209cb5c33 | 348 | { |
mjr | 82:4f6209cb5c33 | 349 | // no events are registered |
mjr | 82:4f6209cb5c33 | 350 | callcb = &FastInterruptIn::callNone; |
mjr | 82:4f6209cb5c33 | 351 | } |
mjr | 82:4f6209cb5c33 | 352 | |
mjr | 82:4f6209cb5c33 | 353 | // done messing with statics |
mjr | 82:4f6209cb5c33 | 354 | __enable_irq(); |
mjr | 82:4f6209cb5c33 | 355 | } |
mjr | 82:4f6209cb5c33 | 356 | |
mjr | 82:4f6209cb5c33 | 357 | // add me to the active list for my port |
mjr | 82:4f6209cb5c33 | 358 | void listAdd() |
mjr | 82:4f6209cb5c33 | 359 | { |
mjr | 82:4f6209cb5c33 | 360 | // figure the list head |
mjr | 82:4f6209cb5c33 | 361 | FastInterruptIn **headp = (portno == PortA) ? &headPortA : &headPortD; |
mjr | 82:4f6209cb5c33 | 362 | |
mjr | 82:4f6209cb5c33 | 363 | // search the list to see if I'm already there |
mjr | 82:4f6209cb5c33 | 364 | FastInterruptIn **nxtp = headp; |
mjr | 82:4f6209cb5c33 | 365 | for ( ; *nxtp != 0 && *nxtp != this ; nxtp = &(*nxtp)->nxt) ; |
mjr | 82:4f6209cb5c33 | 366 | |
mjr | 82:4f6209cb5c33 | 367 | // if we reached the last entry without finding me, add me |
mjr | 82:4f6209cb5c33 | 368 | if (*nxtp == 0) |
mjr | 82:4f6209cb5c33 | 369 | { |
mjr | 82:4f6209cb5c33 | 370 | *nxtp = this; |
mjr | 82:4f6209cb5c33 | 371 | this->nxt = 0; |
mjr | 82:4f6209cb5c33 | 372 | } |
mjr | 82:4f6209cb5c33 | 373 | } |
mjr | 82:4f6209cb5c33 | 374 | |
mjr | 82:4f6209cb5c33 | 375 | // remove me from the active list for my port |
mjr | 82:4f6209cb5c33 | 376 | void listRemove() |
mjr | 82:4f6209cb5c33 | 377 | { |
mjr | 82:4f6209cb5c33 | 378 | // figure the list head |
mjr | 82:4f6209cb5c33 | 379 | FastInterruptIn **headp = (portno == PortA) ? &headPortA : &headPortD; |
mjr | 82:4f6209cb5c33 | 380 | |
mjr | 82:4f6209cb5c33 | 381 | // find me in the list |
mjr | 82:4f6209cb5c33 | 382 | FastInterruptIn **nxtp = headp; |
mjr | 82:4f6209cb5c33 | 383 | for ( ; *nxtp != 0 && *nxtp != this ; nxtp = &(*nxtp)->nxt) ; |
mjr | 82:4f6209cb5c33 | 384 | |
mjr | 82:4f6209cb5c33 | 385 | // if we found me, unlink me |
mjr | 82:4f6209cb5c33 | 386 | if (*nxtp == this) |
mjr | 82:4f6209cb5c33 | 387 | { |
mjr | 82:4f6209cb5c33 | 388 | *nxtp = this->nxt; |
mjr | 82:4f6209cb5c33 | 389 | this->nxt = 0; |
mjr | 82:4f6209cb5c33 | 390 | } |
mjr | 82:4f6209cb5c33 | 391 | } |
mjr | 82:4f6209cb5c33 | 392 | |
mjr | 82:4f6209cb5c33 | 393 | // next link in active list for our port |
mjr | 82:4f6209cb5c33 | 394 | FastInterruptIn *nxt; |
mjr | 82:4f6209cb5c33 | 395 | |
mjr | 82:4f6209cb5c33 | 396 | // pin mask - this is 1<<pinno, used for selecting or setting the port's |
mjr | 82:4f6209cb5c33 | 397 | // bit in the port-wide bit vector registers (IFSR, PDIR, etc) |
mjr | 82:4f6209cb5c33 | 398 | uint32_t pinMask; |
mjr | 82:4f6209cb5c33 | 399 | |
mjr | 82:4f6209cb5c33 | 400 | // Internal interrupt dispatcher. This is set to one of |
mjr | 82:4f6209cb5c33 | 401 | // &callNone, &callRise, &callFall, or &callBoth, according |
mjr | 82:4f6209cb5c33 | 402 | // to which type of handler(s) we have registered. |
mjr | 82:4f6209cb5c33 | 403 | void (*callcb)(FastInterruptIn *, uint32_t pinstate); |
mjr | 82:4f6209cb5c33 | 404 | |
mjr | 82:4f6209cb5c33 | 405 | // PDIR (data read) register |
mjr | 82:4f6209cb5c33 | 406 | volatile uint32_t *PDIR; |
mjr | 82:4f6209cb5c33 | 407 | |
mjr | 82:4f6209cb5c33 | 408 | // port and pin number |
mjr | 82:4f6209cb5c33 | 409 | uint8_t portno; |
mjr | 82:4f6209cb5c33 | 410 | uint8_t pinno; |
mjr | 82:4f6209cb5c33 | 411 | |
mjr | 82:4f6209cb5c33 | 412 | // user interrupt handler callbacks |
mjr | 82:4f6209cb5c33 | 413 | fiiCallback cbRise; |
mjr | 82:4f6209cb5c33 | 414 | fiiCallback cbFall; |
mjr | 82:4f6209cb5c33 | 415 | |
mjr | 82:4f6209cb5c33 | 416 | protected: |
mjr | 82:4f6209cb5c33 | 417 | static void callNone(FastInterruptIn *f, uint32_t pinstate) { } |
mjr | 82:4f6209cb5c33 | 418 | static void callRise(FastInterruptIn *f, uint32_t pinstate) { f->cbRise.call(); } |
mjr | 82:4f6209cb5c33 | 419 | static void callFall(FastInterruptIn *f, uint32_t pinstate) { f->cbFall.call(); } |
mjr | 82:4f6209cb5c33 | 420 | static void callBoth(FastInterruptIn *f, uint32_t pinstate) |
mjr | 82:4f6209cb5c33 | 421 | { |
mjr | 82:4f6209cb5c33 | 422 | if (pinstate) |
mjr | 82:4f6209cb5c33 | 423 | f->cbRise.call(); |
mjr | 82:4f6209cb5c33 | 424 | else |
mjr | 82:4f6209cb5c33 | 425 | f->cbFall.call(); |
mjr | 82:4f6209cb5c33 | 426 | } |
mjr | 82:4f6209cb5c33 | 427 | |
mjr | 82:4f6209cb5c33 | 428 | // Head of active interrupt handler lists. When a handler is |
mjr | 82:4f6209cb5c33 | 429 | // active, we link it into this static list. At interrupt time, |
mjr | 82:4f6209cb5c33 | 430 | // we search the list for an active interrupt. |
mjr | 82:4f6209cb5c33 | 431 | static FastInterruptIn *headPortA; |
mjr | 82:4f6209cb5c33 | 432 | static FastInterruptIn *headPortD; |
mjr | 82:4f6209cb5c33 | 433 | |
mjr | 82:4f6209cb5c33 | 434 | // PCR_IRQC modes |
mjr | 82:4f6209cb5c33 | 435 | static const uint32_t PCR_IRQC_DISABLED = PORT_PCR_IRQC(0); |
mjr | 82:4f6209cb5c33 | 436 | static const uint32_t PCR_IRQC_RISING = PORT_PCR_IRQC(9); |
mjr | 82:4f6209cb5c33 | 437 | static const uint32_t PCR_IRQC_FALLING = PORT_PCR_IRQC(10); |
mjr | 82:4f6209cb5c33 | 438 | static const uint32_t PCR_IRQC_BOTH = PORT_PCR_IRQC(11); |
mjr | 82:4f6209cb5c33 | 439 | |
mjr | 82:4f6209cb5c33 | 440 | // IRQ handlers. We set up a separate handler for each port to call |
mjr | 82:4f6209cb5c33 | 441 | // the common handler with the port-specific parameters. |
mjr | 82:4f6209cb5c33 | 442 | // |
mjr | 82:4f6209cb5c33 | 443 | // We read the current pin input status immediately on entering the |
mjr | 82:4f6209cb5c33 | 444 | // handler, so that we have the pin reading as soon as possible after |
mjr | 82:4f6209cb5c33 | 445 | // the interrupt. In cases where we're handling both rising and falling |
mjr | 82:4f6209cb5c33 | 446 | // edges, the only way to tell which type of edge triggered the interrupt |
mjr | 82:4f6209cb5c33 | 447 | // is to look at the pin status, since the same interrupt is generated |
mjr | 82:4f6209cb5c33 | 448 | // in either case. For a high-frequency signal source, the pin state |
mjr | 82:4f6209cb5c33 | 449 | // might change again very soon after the edge that triggered the |
mjr | 82:4f6209cb5c33 | 450 | // interrupt, so we can get the wrong state if we wait too long to read |
mjr | 82:4f6209cb5c33 | 451 | // the pin. The soonest we can read the pin is at entry to our handler, |
mjr | 82:4f6209cb5c33 | 452 | // which isn't even perfectly instantaneous, since the hardware has some |
mjr | 82:4f6209cb5c33 | 453 | // latency (reportedly about 400ns) responding to an interrupt. |
mjr | 82:4f6209cb5c33 | 454 | static void PortA_ISR() { ISR(&PORTA->ISFR, headPortA, FPTA->PDIR); } |
mjr | 82:4f6209cb5c33 | 455 | static void PortD_ISR() { ISR(&PORTD->ISFR, headPortD, FPTD->PDIR); } |
mjr | 82:4f6209cb5c33 | 456 | inline static void ISR(volatile uint32_t *pifsr, FastInterruptIn *f, uint32_t pdir) |
mjr | 82:4f6209cb5c33 | 457 | { |
mjr | 82:4f6209cb5c33 | 458 | // search the list for an active entry |
mjr | 82:4f6209cb5c33 | 459 | uint32_t ifsr = *pifsr; |
mjr | 82:4f6209cb5c33 | 460 | for ( ; f != 0 ; f = f->nxt) |
mjr | 82:4f6209cb5c33 | 461 | { |
mjr | 82:4f6209cb5c33 | 462 | // check if this entry's pin is in interrupt state |
mjr | 82:4f6209cb5c33 | 463 | if ((ifsr & f->pinMask) != 0) |
mjr | 82:4f6209cb5c33 | 464 | { |
mjr | 82:4f6209cb5c33 | 465 | // clear the interrupt flag by writing '1' to the bit |
mjr | 82:4f6209cb5c33 | 466 | *pifsr = f->pinMask; |
mjr | 82:4f6209cb5c33 | 467 | |
mjr | 82:4f6209cb5c33 | 468 | // call the appropriate user callback |
mjr | 82:4f6209cb5c33 | 469 | f->callcb(f, pdir & f->pinMask); |
mjr | 82:4f6209cb5c33 | 470 | |
mjr | 82:4f6209cb5c33 | 471 | // Stop searching. If another pin has an active interrupt, |
mjr | 82:4f6209cb5c33 | 472 | // or this pin already has another pending interrupt, the |
mjr | 82:4f6209cb5c33 | 473 | // hardware will immediately call us again as soon as we |
mjr | 82:4f6209cb5c33 | 474 | // return, and we'll find the new interrupt on that new call. |
mjr | 82:4f6209cb5c33 | 475 | // This should be more efficient on average than checking all |
mjr | 82:4f6209cb5c33 | 476 | // pins even after finding an active one, since in most cases |
mjr | 82:4f6209cb5c33 | 477 | // there will only be one interrupt to handle at a time. |
mjr | 82:4f6209cb5c33 | 478 | return; |
mjr | 82:4f6209cb5c33 | 479 | } |
mjr | 82:4f6209cb5c33 | 480 | } |
mjr | 82:4f6209cb5c33 | 481 | } |
mjr | 82:4f6209cb5c33 | 482 | |
mjr | 82:4f6209cb5c33 | 483 | }; |
mjr | 82:4f6209cb5c33 | 484 | |
mjr | 82:4f6209cb5c33 | 485 | #endif |