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

/media/uploads/mjr/pinscape_no_background_small_L7Miwr6.jpg

This is Version 2 of the Pinscape Controller, an I/O controller for virtual pinball machines. (You can find the old version 1 software here.) Pinscape is software for the KL25Z that turns the board into a full-featured I/O controller for virtual pinball, with support for accelerometer-based nudging, a mechanical plunger, button inputs, and feedback device control.

In case you haven't heard of the idea before, a "virtual pinball machine" is basically a video pinball simulator that's built into a real pinball machine body. A TV monitor goes in place of the pinball playfield, and a second TV goes in the backbox to show the backglass artwork. Some cabs also include a third monitor to simulate the DMD (Dot Matrix Display) used for scoring on 1990s machines, or even an original plasma DMD. A computer (usually a Windows PC) is hidden inside the cabinet, running pinball emulation software that displays a life-sized playfield on the main TV. The cabinet has all of the usual buttons, too, so it not only looks like the real thing, but plays like it too. That's a picture of my own machine to the right. On the outside, it's built exactly like a real arcade pinball machine, with the same overall dimensions and all of the standard pinball cabinet trim hardware.

It's possible to buy a pre-built virtual pinball machine, but it also makes a great DIY project. If you have some basic wood-working skills and know your way around PCs, you can build one from scratch. The computer part is just an ordinary Windows PC, and all of the pinball emulation can be built out of free, open-source software. In that spirit, the Pinscape Controller is an open-source software/hardware project that offers a no-compromises, all-in-one control center for all of the unique input/output needs of a virtual pinball cabinet. If you've been thinking about building one of these, but you're not sure how to connect a plunger, flipper buttons, lights, nudge sensor, and whatever else you can think of, this project might be just what you're looking for.

You can find much more information about DIY Pin Cab building in general in the Virtual Cabinet Forum on vpforums.org. Also visit my Pinscape Resources page for more about this project and other virtual pinball projects I'm working on.

Downloads

  • Pinscape Release Builds: This page has download links for all of the Pinscape software. To get started, install and run the Pinscape Config Tool on your Windows computer. It will lead you through the steps for installing the Pinscape firmware on the KL25Z.
  • Config Tool Source Code. The complete C# source code for the config tool. You don't need this to run the tool, but it's available if you want to customize anything or see how it works inside.

Documentation

The new Version 2 Build Guide is now complete! This new version aims to be a complete guide to building a virtual pinball machine, including not only the Pinscape elements but all of the basics, from sourcing parts to building all of the hardware.

You can also refer to the original Hardware Build Guide (PDF), but that's out of date now, since it refers to the old version 1 software, which was rather different (especially when it comes to configuration).

System Requirements

The new Config Tool requires a fairly up-to-date Microsoft .NET installation. If you use Windows Update to keep your system current, you should be fine. A modern version of Internet Explorer (IE) is required, even if you don't use it as your main browser, because the Config Tool uses some system components that Microsoft packages into the IE install set. I test with IE11, so that's known to work. IE8 doesn't work. IE9 and 10 are unknown at this point.

The Windows requirements are only for the config tool. The firmware doesn't care about anything on the Windows side, so if you can make do without the config tool, you can use almost any Windows setup.

Main Features

Plunger: The Pinscape Controller started out as a "mechanical plunger" controller: a device for attaching a real pinball plunger to the video game software so that you could launch the ball the natural way. This is still, of course, a central feature of the project. The software supports several types of sensors: a high-resolution optical sensor (which works by essentially taking pictures of the plunger as it moves); a slide potentiometer (which determines the position via the changing electrical resistance in the pot); a quadrature sensor (which counts bars printed on a special guide rail that it moves along); and an IR distance sensor (which determines the position by sending pulses of light at the plunger and measuring the round-trip travel time). The Build Guide explains how to set up each type of sensor.

Nudging: The KL25Z (the little microcontroller that the software runs on) has a built-in accelerometer. The Pinscape software uses it to sense when you nudge the cabinet, and feeds the acceleration data to the pinball software on the PC. This turns physical nudges into virtual English on the ball. The accelerometer is quite sensitive and accurate, so we can measure the difference between little bumps and hard shoves, and everything in between. The result is natural and immersive.

Buttons: You can wire real pinball buttons to the KL25Z, and the software will translate the buttons into PC input. You have the option to map each button to a keyboard key or joystick button. You can wire up your flipper buttons, Magna Save buttons, Start button, coin slots, operator buttons, and whatever else you need.

Feedback devices: You can also attach "feedback devices" to the KL25Z. Feedback devices are things that create tactile, sound, and lighting effects in sync with the game action. The most popular PC pinball emulators know how to address a wide variety of these devices, and know how to match them to on-screen action in each virtual table. You just need an I/O controller that translates commands from the PC into electrical signals that turn the devices on and off. The Pinscape Controller can do that for you.

Expansion Boards

There are two main ways to run the Pinscape Controller: standalone, or using the "expansion boards".

In the basic standalone setup, you just need the KL25Z, plus whatever buttons, sensors, and feedback devices you want to attach to it. This mode lets you take advantage of everything the software can do, but for some features, you'll have to build some ad hoc external circuitry to interface external devices with the KL25Z. The Build Guide has detailed plans for exactly what you need to build.

The other option is the Pinscape Expansion Boards. The expansion boards are a companion project, which is also totally free and open-source, that provides Printed Circuit Board (PCB) layouts that are designed specifically to work with the Pinscape software. The PCB designs are in the widely used EAGLE format, which many PCB manufacturers can turn directly into physical boards for you. The expansion boards organize all of the external connections more neatly than on the standalone KL25Z, and they add all of the interface circuitry needed for all of the advanced software functions. The big thing they bring to the table is lots of high-power outputs. The boards provide a modular system that lets you add boards to add more outputs. If you opt for the basic core setup, you'll have enough outputs for all of the toys in a really well-equipped cabinet. If your ambitions go beyond merely well-equipped and run to the ridiculously extravagant, just add an extra board or two. The modular design also means that you can add to the system over time.

Expansion Board project page

Update notes

If you have a Pinscape V1 setup already installed, you should be able to switch to the new version pretty seamlessly. There are just a couple of things to be aware of.

First, the "configuration" procedure is completely different in the new version. Way better and way easier, but it's not what you're used to from V1. In V1, you had to edit the project source code and compile your own custom version of the program. No more! With V2, you simply install the standard, pre-compiled .bin file, and select options using the Pinscape Config Tool on Windows.

Second, if you're using the TSL1410R optical sensor for your plunger, there's a chance you'll need to boost your light source's brightness a little bit. The "shutter speed" is faster in this version, which means that it doesn't spend as much time collecting light per frame as before. The software actually does "auto exposure" adaptation on every frame, so the increased shutter speed really shouldn't bother it, but it does require a certain minimum level of contrast, which requires a certain minimal level of lighting. Check the plunger viewer in the setup tool if you have any problems; if the image looks totally dark, try increasing the light level to see if that helps.

New Features

V2 has numerous new features. Here are some of the highlights...

Dynamic configuration: as explained above, configuration is now handled through the Config Tool on Windows. It's no longer necessary to edit the source code or compile your own modified binary.

Improved plunger sensing: the software now reads the TSL1410R optical sensor about 15x faster than it did before. This allows reading the sensor at full resolution (400dpi), about 400 times per second. The faster frame rate makes a big difference in how accurately we can read the plunger position during the fast motion of a release, which allows for more precise position sensing and faster response. The differences aren't dramatic, since the sensing was already pretty good even with the slower V1 scan rate, but you might notice a little better precision in tricky skill shots.

Keyboard keys: button inputs can now be mapped to keyboard keys. The joystick button option is still available as well, of course. Keyboard keys have the advantage of being closer to universal for PC pinball software: some pinball software can be set up to take joystick input, but nearly all PC pinball emulators can take keyboard input, and nearly all of them use the same key mappings.

Local shift button: one physical button can be designed as the local shift button. This works like a Shift button on a keyboard, but with cabinet buttons. It allows each physical button on the cabinet to have two PC keys assigned, one normal and one shifted. Hold down the local shift button, then press another key, and the other key's shifted key mapping is sent to the PC. The shift button can have a regular key mapping of its own as well, so it can do double duty. The shift feature lets you access more functions without cluttering your cabinet with extra buttons. It's especially nice for less frequently used functions like adjusting the volume or activating night mode.

Night mode: the output controller has a new "night mode" option, which lets you turn off all of your noisy devices with a single button, switch, or PC command. You can designate individual ports as noisy or not. Night mode only disables the noisemakers, so you still get the benefit of your flashers, button lights, and other quiet devices. This lets you play late into the night without disturbing your housemates or neighbors.

Gamma correction: you can designate individual output ports for gamma correction. This adjusts the intensity level of an output to make it match the way the human eye perceives brightness, so that fades and color mixes look more natural in lighting devices. You can apply this to individual ports, so that it only affects ports that actually have lights of some kind attached.

IR Remote Control: the controller software can transmit and/or receive IR remote control commands if you attach appropriate parts (an IR LED to send, an IR sensor chip to receive). This can be used to turn on your TV(s) when the system powers on, if they don't turn on automatically, and for any other functions you can think of requiring IR send/receive capabilities. You can assign IR commands to cabinet buttons, so that pressing a button on your cabinet sends a remote control command from the attached IR LED, and you can have the controller generate virtual key presses on your PC in response to received IR commands. If you have the IR sensor attached, the system can use it to learn commands from your existing remotes.

Yet more USB fixes: I've been gradually finding and fixing USB bugs in the mbed library for months now. This version has all of the fixes of the last couple of releases, of course, plus some new ones. It also has a new "last resort" feature, since there always seems to be "just one more" USB bug. The last resort is that you can tell the device to automatically reboot itself if it loses the USB connection and can't restore it within a given time limit.

More Downloads

  • Custom VP builds: I created modified versions of Visual Pinball 9.9 and Physmod5 that you might want to use in combination with this controller. The modified versions have special handling for plunger calibration specific to the Pinscape Controller, as well as some enhancements to the nudge physics. If you're not using the plunger, you might still want it for the nudge improvements. The modified version also works with any other input controller, so you can get the enhanced nudging effects even if you're using a different plunger/nudge kit. The big change in the modified versions is a "filter" for accelerometer input that's designed to make the response to cabinet nudges more realistic. It also makes the response more subdued than in the standard VP, so it's not to everyone's taste. The downloads include both the updated executables and the source code changes, in case you want to merge the changes into your own custom version(s).

    Note! These features are now standard in the official VP releases, so you don't need my custom builds if you're using 9.9.1 or later and/or VP 10. I don't think there's any reason to use my versions instead of the latest official ones, and in fact I'd encourage you to use the official releases since they're more up to date, but I'm leaving my builds available just in case. In the official versions, look for the checkbox "Enable Nudge Filter" in the Keys preferences dialog. My custom versions don't include that checkbox; they just enable the filter unconditionally.
  • Output circuit shopping list: This is a saved shopping cart at mouser.com with the parts needed to build one copy of the high-power output circuit for the LedWiz emulator feature, for use with the standalone KL25Z (that is, without the expansion boards). The quantities in the cart are for one output channel, so if you want N outputs, simply multiply the quantities by the N, with one exception: you only need one ULN2803 transistor array chip for each eight output circuits. If you're using the expansion boards, you won't need any of this, since the boards provide their own high-power outputs.
  • Cary Owens' optical sensor housing: A 3D-printable design for a housing/mounting bracket for the optical plunger sensor, designed by Cary Owens. This makes it easy to mount the sensor.
  • Lemming77's potentiometer mounting bracket and shooter rod connecter: Sketchup designs for 3D-printable parts for mounting a slide potentiometer as the plunger sensor. These were designed for a particular slide potentiometer that used to be available from an Aliexpress.com seller but is no longer listed. You can probably use this design as a starting point for other similar devices; just check the dimensions before committing the design to plastic.

Copyright and License

The Pinscape firmware is copyright 2014, 2021 by Michael J Roberts. It's released under an MIT open-source license. See License.

Warning to VirtuaPin Kit Owners

This software isn't designed as a replacement for the VirtuaPin plunger kit's firmware. If you bought the VirtuaPin kit, I recommend that you don't install this software. The KL25Z can only run one firmware program at a time, so if you install the Pinscape firmware on your KL25Z, it will replace and erase your existing VirtuaPin proprietary firmware. If you do this, the only way to restore your VirtuaPin firmware is to physically ship the KL25Z back to VirtuaPin and ask them to re-flash it. They don't allow you to do this at home, and they don't even allow you to back up your firmware, since they want to protect their proprietary software from copying. For all of these reasons, if you want to run the Pinscape software, I strongly recommend that you buy a "blank" retail KL25Z to use with Pinscape. They only cost about $15 and are available at several online retailers, including Amazon, Mouser, and eBay. The blank retail boards don't come with any proprietary firmware pre-installed, so installing Pinscape won't delete anything that you paid extra for.

With those warnings in mind, if you're absolutely sure that you don't mind permanently erasing your VirtuaPin firmware, it is at least possible to use Pinscape as a replacement for the VirtuaPin firmware. Pinscape uses the same button wiring conventions as the VirtuaPin setup, so you can keep your buttons (although you'll have to update the GPIO pin mappings in the Config Tool to match your physical wiring). As of the June, 2021 firmware, the Vishay VCNL4010 plunger sensor that comes with the VirtuaPin v3 plunger kit is supported, so you can also keep your plunger, if you have that chip. (You should check to be sure that's the sensor chip you have before committing to this route, if keeping the plunger sensor is important to you. The older VirtuaPin plunger kits came with different IR sensors that the Pinscape software doesn't handle.)

Committer:
mjr
Date:
Sat Apr 18 19:08:55 2020 +0000
Revision:
109:310ac82cbbee
Parent:
101:755f44622abc
TCD1103 DMA setup time padding to fix sporadic missed first pixel in transfer; fix TV ON so that the TV ON IR commands don't have to be grouped in the IR command first slots

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