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
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
Generated on Wed Jul 13 2022 03:30:10 by 1.7.2