An I/O controller for virtual pinball machines: accelerometer nudge sensing, analog plunger input, button input encoding, LedWiz compatible output controls, and more.

Dependencies:   mbed FastIO FastPWM USBDevice

Fork of Pinscape_Controller by Mike R

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers FastInterruptIn.h Source File

FastInterruptIn.h

00001 // Fast Interrupt In for KL25Z
00002 //
00003 // This is a replacement for the mbed library InterruptIn class, which
00004 // sets up GPIO ports for edge-sensitive interrupt handling.  This class
00005 // provides the same API but has a shorter code path for responding to
00006 // each interrupt.  In my tests, the mbed InterruptIn class has a maximum
00007 // interrupt rate of about 112kHz; this class can increase that to about
00008 // 181kHz.
00009 //
00010 // If speed is critical (and it is, because why else would you be using 
00011 // this class?), you should elevate the GPIO interrupt priority in the
00012 // hardware interrupt controller so that GPIO pin signals can preempt other
00013 // interrupt handlers.  The mbed USB and timer handlers in particular spend
00014 // relative long periods in interrupt context, so if these are at the same
00015 // or higher priority than the GPIO interrupts, they'll become the limiting
00016 // factor.  The mbed library leaves all interrupts set to maximum priority
00017 // by default, so to elevate the GPIO interrupt priority, you have to lower 
00018 // the priority of everything else.  Call FastInterruptIn::elevatePriority()
00019 // to do this.
00020 //
00021 //
00022 // Performance measurements:  I set up a test program using one KL25Z to
00023 // send 50% duty cycle square wave signals to a second KL25Z (using a PWM
00024 // output on the sender), and measured the maximum interrupt frequency
00025 // where the receiver could correctly count every edge, repeating the test
00026 // with FastInterruptIn and the mbed InterruptIn.  I tested with handlers
00027 // for both edges and handlers for single edges (just rise() or just fall()).
00028 // The Hz rates reflect the maximum *interrupt* frequency, which is twice
00029 // the PWM frequency when testing with handlers for both rise + fall in
00030 // effect.  In all cases, the user callbacks were minimal code paths that
00031 // just incremented counters, and all tests ran with PTA/PTD at elevated
00032 // IRQ priority.  The time per interrupt values shown are the inverse of
00033 // the maximum frequency; these reflect the time between interrupts at
00034 // the corresponding frequency.  Since each frequency is the maximum at
00035 // which that class can handle every interrupt without losing any, the
00036 // time between interrupts tells us how long the CPU takes to fully process
00037 // one interrupt and return to the base state where it's able to handle the
00038 // next one.  This time is the sum of the initial CPU interrupt latency
00039 // (the time it takes from an edge signal occuring on a pin to the CPU
00040 // executing the first instruction of the IRQ vector), the time spent in
00041 // the InterruptIn or FastInterruptIn code, the time spent in the user
00042 // callback, and the time for the CPU to return from the interrupt to
00043 // normal context.  For the test program, the user callback is about 4
00044 // instructions, so perhaps 6 clocks or 360ns.  Other people have measured
00045 // the M0+ initial interrupt latency at about 450ns, and the return time
00046 // is probably similar.  So we have about 1.2us in fixed overhead and user
00047 // callback time, hence the rest is the time spent in the library code.
00048 //
00049 //   mbed InterruptIn:
00050 //     max rate 112kHz
00051 //     -> 8.9us per interrupt 
00052 //        less 1.2us fixed overhead = 7.7us in library code
00053 //
00054 //   FastInterruptIn:
00055 //     max rate 181kHz
00056 //     -> 5.5us per interrupt
00057 //        less 1.2us fixed overhead = 3.3us in library code
00058 // 
00059 //
00060 // Limitations:
00061 //
00062 // 1. KL25Z ONLY.  This is a bare-metal KL25Z class.
00063 //
00064 // 2. Globally incompatible with InterruptIn.  Both classes take over the
00065 // IRQ vectors for the GPIO interrupts globally, so they can't be mixed
00066 // in the same system.  If you use this class anywhere in a program, it
00067 // has to be used exclusively throughout the whole program - don't use
00068 // the mbed InterruptIn anywhere in a program that uses this class.
00069 //
00070 // 3. API differences.  The API is very similar to InterruptIn's API,
00071 // but we don't support the method-based rise/fall callback attachers.  We
00072 // instead use static function pointers (void functions with 'void *'
00073 // context arguments).  It's easy to write static methods for these that 
00074 // dispatch to regular member functions, so the functionality is the same; 
00075 // it's just a little different syntax.  The simpler (in the sense of
00076 // more primitive) callback interface saves a little memory and is
00077 // slightly faster than the method attachers, since it doesn't require
00078 // any variation checks at interrupt time.
00079 //
00080 // Theory of operation
00081 //
00082 // How the mbed code works
00083 // On every interrupt event, the mbed library's GPIO interrupt handler
00084 // searches for a port with an active interrupt.  Each PORTx_IRQn vector
00085 // handles 32 ports, so each handler has to search this space of 32 ports
00086 // for an active interrupt.  The mbed code approaches this problem by
00087 // searching for a '1' bit in the ISFR (interrupt status flags register),
00088 // which is effectively a 32-bit vector of bits indicating which ports have
00089 // active interrupts.  This search could be done quickly if the hardware
00090 // had a "count leading zeroes" instruction, which actually does exist in
00091 // the ARM instruction set, but alas not in the M0+ subset.  So the mbed
00092 // code has to search for the bit by other means.  It accomplishes this by
00093 // way of a binary search.  By my estimate, this takes about 110 clocks or
00094 // 7us.  The routine has some other slight overhead dispatching to the
00095 // user callback once one is selected via the bit search, but the bulk of
00096 // the time is spent in the bit search.  The mbed code could be made more
00097 // efficient by using a better 'count leading zeroes' algorithm; there are 
00098 // readily available implementations that run in about 15 clocks on M0+.
00099 //
00100 // How this code works
00101 // FastInterruptIn takes a different approach that bypasses the bit vector
00102 // search.  We instead search the installed handlers.  We work on the 
00103 // assumption that the total number of interrupt handlers in the system is 
00104 // small compared with the number of ports.  So instead of searching the 
00105 // entire ISFR bit vector, we only check the ports with installed handlers.
00106 //
00107 // The mbed code takes essentially constant time to run.  It doesn't have
00108 // any dependencies (that I can see) on the number of active InterruptIn
00109 // pins.  In contrast, FastInterruptIn's run time is linear in the number
00110 // of active pins: adding more pins will increase the run time.  This is
00111 // a tradeoff, obviously.  It's very much the right tradeoff for the Pinscape 
00112 // system, because we have very few interrupt pins overall.  I suspect it's
00113 // the right tradeoff for most systems, too, since most embedded systems
00114 // have a small fixed set of peripherals they're talking to.
00115 //
00116 // We have a few other small optimizations to maximize our sustainable
00117 // interrupt frequency.  The most important is probably that we read the
00118 // port pin state immediately on entry to the IRQ vector handler.  Since
00119 // we get the same interrupt on a rising or falling edge, we have to read
00120 // the pin state to determine which type of transition triggered the
00121 // interrupt.  This is inherently problematic because the pin state could 
00122 // have changed between the time the interrupt occurred and the time we 
00123 // got around to reading the state - the likelihood of this increases as
00124 // the interrupt source frequency increases.  The soonest we can possibly
00125 // read the state is at entry to the IRQ vector handler, so we do that.
00126 // Even that isn't perfectly instantaneous, due to the unavoidable 450ns
00127 // or so latency in the hardware before the vector code starts executing;
00128 // it would be better if the hardware read the state at the moment the
00129 // interrupt was triggered, but there's nothing we can do about that.
00130 // In contrast, the mbed code waits until after deciding which interrupt
00131 // is active to read the port, so its reading is about 7us delayed vs our
00132 // 500ns delay.  That further reduces the mbed code's ability to keep up
00133 // with fast interrupt sources when both rise and fall handlers are needed.
00134 
00135 
00136 #ifndef _FASTINTERRUPTIN_H_
00137 #define _FASTINTERRUPTIN_H_
00138 
00139 #include "mbed.h"
00140 #include "gpio_api.h"
00141 
00142 struct fiiCallback
00143 {
00144     fiiCallback() { func = 0; }
00145     void (*func)(void *);
00146     void *context;
00147     
00148     inline void call() { func(context); }
00149 };
00150 
00151 class FastInterruptIn
00152 {
00153 public:
00154     // Globally elevate the PTA and PTD interrupt priorities.  Since the
00155     // mbed default is to start with all IRQs at maximum priority, we
00156     // LOWER the priority of all IRQs to the minimum, then raise the PTA
00157     // and PTD interrupts to maximum priority.  
00158     //
00159     // The reason we set all priorities to minimum (except for PTA and PTD) 
00160     // rather than some medium priority is that this is the most flexible
00161     // default.  It really should have been the mbed default, in my opinion,
00162     // since (1) it doesn't matter what the setting is if they're all the
00163     // same, so an mbed default of 3 would have been equivalent to an mbed
00164     // default of 0 (the current one) for all programs that don't make any
00165     // changes anyway, and (2) the most likely use case for programs that
00166     // do need to differentiate IRQ priorities is that they need one or two
00167     // items to respond MORE quickly.  It seems extremely unlikely that
00168     // anyone would need only one or two to be especially slow, which is
00169     // effectively the case the mbed default is optimized for.
00170     //
00171     // This should be called (if desired at all) once at startup.  The 
00172     // effect is global and permanent (unless later changes are made by
00173     // someone else), so there's no need to call this again when setting
00174     // up new handlers or changing existing handlers.  Callers are free to 
00175     // further adjust priorities as needed (e.g., elevate the priority of
00176     // some other IRQ), but that should be done after calling this, since we
00177     // change ALL IRQ priorities with prejudice.
00178     static void elevatePriority()
00179     {
00180         // Set all IRQ priorities to minimum.  M0+ has priority levels
00181         // 0 (highest) to 3 (lowest).  (Note that the hardware uses the
00182         // high-order two bits of the low byte, so the hardware priority
00183         // levels are 0x00 [highest], 0x40, 0x80, 0xC0 [lowest]).  The
00184         // mbed NVIC macros, in contrast, abstract this to use the LOW
00185         // two bits, for levels 0, 1, 2, 3.)
00186         for (int irq = 0 ; irq < 32 ; ++irq)
00187             NVIC_SetPriority(IRQn(irq), 0x3);
00188             
00189         // set the PTA and PTD IRQs to highest priority
00190         NVIC_SetPriority(PORTA_IRQn, 0x00);
00191         NVIC_SetPriority(PORTD_IRQn, 0x00);
00192     }
00193     
00194     // set up a FastInterruptIn handler on a given pin
00195     FastInterruptIn(PinName pin)
00196     {
00197         // start with the null callback
00198         callcb = &FastInterruptIn::callNone;
00199         
00200         // initialize the pin as a GPIO Digital In port
00201         gpio_t gpio;
00202         gpio_init_in(&gpio, pin);
00203 
00204         // get the port registers
00205         PDIR = gpio.reg_in;
00206         pinMask = gpio.mask;
00207         portno = uint8_t(pin >> PORT_SHIFT);
00208         pinno = uint8_t((pin & 0x7F) >> 2);
00209         
00210         // set up for the selected port
00211         IRQn_Type irqn;
00212         void (*vector)();
00213         switch (portno)
00214         {
00215         case PortA:
00216             irqn = PORTA_IRQn;
00217             vector = &PortA_ISR;
00218             PDIR = &FPTA->PDIR;
00219             break;
00220         
00221         case PortD:
00222             irqn = PORTD_IRQn;
00223             vector = &PortD_ISR;
00224             PDIR = &FPTD->PDIR;
00225             break;
00226         
00227         default:
00228             error("FastInterruptIn: invalid pin specified; "
00229                 "only PTAxx and PTDxx pins are interrupt-capable");
00230             return;
00231         }
00232         
00233         // set the vector
00234         NVIC_SetVector(irqn, uint32_t(vector));
00235         NVIC_EnableIRQ(irqn);
00236     }
00237     
00238     // read the current pin status - returns 1 or 0
00239     int read() const { return (fastread() >> pinno) & 0x01; }
00240     
00241     // Fast read - returns the pin's port bit, which is '0' or '1' shifted
00242     // left by the port number (e.g., PTA7 or PTD7 return (1<<7) or (0<<7)).
00243     // This is slightly faster than read() because it doesn't normalize the
00244     // result to a literal '0' or '1' value.  When the value is only needed
00245     // for an 'if' test or the like, zero/nonzero is generally good enough,
00246     // so you can save a tiny bit of time by skiping the shift.
00247     uint32_t fastread() const { return *PDIR & pinMask; }
00248     
00249     // set a rising edge handler
00250     void rise(void (*func)(void *), void *context = 0)
00251     {
00252         setHandler(&cbRise, PCR_IRQC_RISING, func, context);
00253     }
00254     
00255     // set a falling edge handler
00256     void fall(void (*func)(void *), void *context = 0)
00257     {
00258         setHandler(&cbFall, PCR_IRQC_FALLING, func, context);
00259     }
00260     
00261     // Set the pull mode.  Note that the KL25Z only supports PullUp
00262     // and PullNone modes.  We'll ignore other modes.
00263     void mode(PinMode pull)
00264     {
00265         volatile uint32_t *PCR = &(portno == PortA ? PORTA : PORTD)->PCR[pinno];
00266         switch (pull)
00267         {
00268         case PullNone:
00269             *PCR &= ~PORT_PCR_PE_MASK;
00270             break;
00271             
00272         case PullUp:
00273             *PCR |= PORT_PCR_PE_MASK;
00274             break;
00275         }
00276     }
00277     
00278 protected:
00279     // set a handler - the mode is PCR_IRQC_RISING or PCR_IRQC_FALLING
00280     void setHandler(
00281         fiiCallback *cb, uint32_t mode, void (*func)(void *), void *context)
00282     {
00283         // get the PCR (port control register) for the pin
00284         volatile uint32_t *PCR = &(portno == PortA ? PORTA : PORTD)->PCR[pinno];
00285 
00286         // disable interrupts while messing with shared statics
00287         __disable_irq();
00288 
00289         // set the callback
00290         cb->func = func;
00291         cb->context = context;
00292         
00293         // enable or disable the mode in the PCR
00294         if (func != 0)
00295         {
00296             // Handler function is non-null, so we're setting a handler.
00297             // Enable the mode in the PCR.  Note that we merely need to
00298             // OR the new mode bits into the existing mode bits, since
00299             // disabled is 0 and BOTH is equal to RISING|FALLING.
00300             *PCR |= mode;
00301             
00302             // if we're not already in the active list, add us
00303             listAdd();
00304         }
00305         else
00306         {
00307             // Handler function is null, so we're clearing the handler.
00308             // Disable the mode bits in the PCR.  If the old mode was
00309             // the same as the mode we're disabling, switch to NONE.
00310             // If the old mode was BOTH, switch to the mode we're NOT
00311             // disabling.  Otherwise make no change.
00312             int cur = *PCR & PORT_PCR_IRQC_MASK;
00313             if (cur == PCR_IRQC_BOTH)
00314             {
00315                 *PCR &= ~PORT_PCR_IRQC_MASK;
00316                 *PCR |= (mode == PCR_IRQC_FALLING ? PCR_IRQC_RISING : PCR_IRQC_FALLING);
00317             }
00318             else if (cur == mode)
00319             {
00320                 *PCR &= ~PORT_PCR_IRQC_MASK;
00321             }
00322             
00323             // if we're disabled, remove us from the list
00324             if ((*PCR & PORT_PCR_IRQC_MASK) == PCR_IRQC_DISABLED)
00325                 listRemove();
00326         }
00327         
00328         // set the appropriate callback mode
00329         if (cbRise.func != 0 && cbFall.func != 0)
00330         {
00331             // They want to be called on both Rise and Fall events. 
00332             // The hardware triggers the same interrupt on both, so we
00333             // need to distinguish which is which by checking the current
00334             // pin status when the interrupt occurs.
00335             callcb = &FastInterruptIn::callBoth;
00336         }
00337         else if (cbRise.func != 0)
00338         {
00339             // they only want Rise events
00340             callcb = &FastInterruptIn::callRise;
00341         }
00342         else if (cbFall.func != 0)
00343         {
00344             // they only want Fall events
00345             callcb = &FastInterruptIn::callFall;
00346         }
00347         else
00348         {
00349             // no events are registered
00350             callcb = &FastInterruptIn::callNone;
00351         }
00352         
00353         // done messing with statics
00354         __enable_irq();
00355     }
00356     
00357     // add me to the active list for my port
00358     void listAdd()
00359     {
00360         // figure the list head
00361         FastInterruptIn **headp = (portno == PortA) ? &headPortA : &headPortD;
00362         
00363         // search the list to see if I'm already there
00364         FastInterruptIn **nxtp = headp;
00365         for ( ; *nxtp != 0 && *nxtp != this ; nxtp = &(*nxtp)->nxt) ;
00366         
00367         // if we reached the last entry without finding me, add me
00368         if (*nxtp == 0)
00369         {
00370             *nxtp = this;
00371             this->nxt = 0;
00372         }
00373     }
00374     
00375     // remove me from the active list for my port
00376     void listRemove()
00377     {
00378         // figure the list head
00379         FastInterruptIn **headp = (portno == PortA) ? &headPortA : &headPortD;
00380         
00381         // find me in the list
00382         FastInterruptIn **nxtp = headp;
00383         for ( ; *nxtp != 0 && *nxtp != this ; nxtp = &(*nxtp)->nxt) ;
00384         
00385         // if we found me, unlink me
00386         if (*nxtp == this)
00387         {
00388             *nxtp = this->nxt;
00389             this->nxt = 0;
00390         }
00391     }
00392     
00393     // next link in active list for our port
00394     FastInterruptIn *nxt;
00395     
00396     // pin mask - this is 1<<pinno, used for selecting or setting the port's
00397     // bit in the port-wide bit vector registers (IFSR, PDIR, etc)
00398     uint32_t pinMask;
00399     
00400     // Internal interrupt dispatcher.  This is set to one of 
00401     // &callNone, &callRise, &callFall, or &callBoth, according 
00402     // to which type of handler(s) we have registered.
00403     void (*callcb)(FastInterruptIn *, uint32_t pinstate);
00404     
00405     // PDIR (data read) register
00406     volatile uint32_t *PDIR;
00407     
00408     // port and pin number
00409     uint8_t portno;
00410     uint8_t pinno;
00411 
00412     // user interrupt handler callbacks
00413     fiiCallback cbRise;
00414     fiiCallback cbFall;
00415     
00416 protected:
00417     static void callNone(FastInterruptIn *f, uint32_t pinstate) { }
00418     static void callRise(FastInterruptIn *f, uint32_t pinstate) { f->cbRise.call(); }
00419     static void callFall(FastInterruptIn *f, uint32_t pinstate) { f->cbFall.call(); }
00420     static void callBoth(FastInterruptIn *f, uint32_t pinstate)
00421     {
00422         if (pinstate)
00423             f->cbRise.call();
00424         else
00425             f->cbFall.call();
00426     }
00427     
00428     // Head of active interrupt handler lists.  When a handler is
00429     // active, we link it into this static list.  At interrupt time,
00430     // we search the list for an active interrupt.
00431     static FastInterruptIn *headPortA;
00432     static FastInterruptIn *headPortD;
00433 
00434     // PCR_IRQC modes
00435     static const uint32_t PCR_IRQC_DISABLED = PORT_PCR_IRQC(0);
00436     static const uint32_t PCR_IRQC_RISING = PORT_PCR_IRQC(9);
00437     static const uint32_t PCR_IRQC_FALLING = PORT_PCR_IRQC(10);
00438     static const uint32_t PCR_IRQC_BOTH = PORT_PCR_IRQC(11);
00439     
00440     // IRQ handlers.  We set up a separate handler for each port to call
00441     // the common handler with the port-specific parameters.  
00442     // 
00443     // We read the current pin input status immediately on entering the 
00444     // handler, so that we have the pin reading as soon as possible after 
00445     // the interrupt.  In cases where we're handling both rising and falling
00446     // edges, the only way to tell which type of edge triggered the interrupt
00447     // is to look at the pin status, since the same interrupt is generated
00448     // in either case.  For a high-frequency signal source, the pin state
00449     // might change again very soon after the edge that triggered the
00450     // interrupt, so we can get the wrong state if we wait too long to read
00451     // the pin.  The soonest we can read the pin is at entry to our handler,
00452     // which isn't even perfectly instantaneous, since the hardware has some
00453     // latency (reportedly about 400ns) responding to an interrupt.  
00454     static void PortA_ISR() { ISR(&PORTA->ISFR, headPortA, FPTA->PDIR); }
00455     static void PortD_ISR() { ISR(&PORTD->ISFR, headPortD, FPTD->PDIR); }
00456     inline static void ISR(volatile uint32_t *pifsr, FastInterruptIn *f, uint32_t pdir)
00457     {
00458         // search the list for an active entry
00459         uint32_t ifsr = *pifsr;
00460         for ( ; f != 0 ; f = f->nxt)
00461         {
00462             // check if this entry's pin is in interrupt state
00463             if ((ifsr & f->pinMask) != 0)
00464             {
00465                 // clear the interrupt flag by writing '1' to the bit
00466                 *pifsr = f->pinMask;
00467                 
00468                 // call the appropriate user callback
00469                 f->callcb(f, pdir & f->pinMask);
00470                 
00471                 // Stop searching.  If another pin has an active interrupt,
00472                 // or this pin already has another pending interrupt, the
00473                 // hardware will immediately call us again as soon as we
00474                 // return, and we'll find the new interrupt on that new call.
00475                 // This should be more efficient on average than checking all 
00476                 // pins even after finding an active one, since in most cases 
00477                 // there will only be one interrupt to handle at a time.
00478                 return;
00479             }                
00480         }
00481     }
00482     
00483 };
00484 
00485 #endif