Mirror with some correction

Dependencies:   mbed FastIO FastPWM USBDevice

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?

UserRevisionLine numberNew 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