Pinscape Controller version 1 fork. This is a fork to allow for ongoing bug fixes to the original controller version, from before the major changes for the expansion board project.
Dependencies: FastIO FastPWM SimpleDMA mbed
Fork of Pinscape_Controller by
Revision 35:e959ffba78fd, committed 2015-12-19
- Comitter:
- mjr
- Date:
- Sat Dec 19 06:37:19 2015 +0000
- Parent:
- 34:6b981a2afab7
- Child:
- 36:b9747461331e
- Commit message:
- Keyboard/Media Control interface working, but the extra interface confuses the DOF connector.
Changed in this revision
--- a/FastAnalogIn.lib Thu Dec 03 07:34:57 2015 +0000 +++ b/FastAnalogIn.lib Sat Dec 19 06:37:19 2015 +0000 @@ -1,1 +1,1 @@ -http://mbed.org/users/Sissors/code/FastAnalogIn/#afc3b84dbbd6 +http://mbed.org/users/Sissors/code/FastAnalogIn/#234c5cd2b8de
--- a/TSL1410R/tsl1410r.h Thu Dec 03 07:34:57 2015 +0000 +++ b/TSL1410R/tsl1410r.h Sat Dec 19 06:37:19 2015 +0000 @@ -4,25 +4,51 @@ * This provides a high-level interface for the Taos TSL1410R linear CCD array sensor. */ - #include "mbed.h" - #include "config.h" - #include "FastIO.h" - #include "FastAnalogIn.h" +#include "mbed.h" +#include "config.h" +#include "FastAnalogIn.h" - #ifndef TSL1410R_H - #define TSL1410R_H +#ifndef TSL1410R_H +#define TSL1410R_H + +// For faster GPIO on the clock pin, we write the IOPORT registers directly. +// PORT_BASE gives us the memory mapped location of the IOPORT register set +// for a pin; PINMASK gives us the bit pattern to write to the registers. +// +// - To turn a pin ON: PORT_BASE(pin)->PSOR |= PINMASK(pin) +// - To turn a pin OFF: PORT_BASE(pin)->PCOR |= PINMASK(pin) +// - To toggle a pin: PORT_BASE(pin)->PTOR |= PINMASK(pin) +// +// When used in a loop where the port address and pin mask are cached in +// local variables, this runs at the same speed as the FastIO library - about +// 78ns per pin write on the KL25Z. Not surprising since it's doing the same +// thing, and the compiler should be able to reduce a pin write to a single ARM +// instruction when the port address and mask are in local register variables. +// The advantage over the FastIO library is that this approach allows for pins +// to be assigned dynamically at run-time, which we prefer because it allows for +// configuration changes to be made on the fly rather than having to recompile +// the program. +#define GPIO_PORT_BASE(pin) ((FGPIO_Type *)(FPTA_BASE + ((unsigned int)pin >> PORT_SHIFT) * 0x40)) +#define GPIO_PINMASK(pin) (1 << ((pin & 0x7F) >> 2)) -template <PinName siPin, PinName clockPin> class TSL1410R +class TSL1410R { public: - // set up the analog in port for reading the currently selected - // pixel value - TSL1410R(PinName aoPin) : ao(aoPin) + TSL1410R(int nPix, PinName siPin, PinName clockPin, PinName ao1Pin, PinName ao2Pin) + : nPix(nPix), si(siPin), clock(clockPin), ao1(ao1Pin), ao2(ao2Pin) { + // we're in parallel mode if ao2 is a valid pin + parallel = (ao2Pin != NC); + + // remember the clock pin port base and pin mask for fast access + clockPort = GPIO_PORT_BASE(clockPin); + clockMask = GPIO_PINMASK(clockPin); + // disable continuous conversion mode in FastAnalogIn - since we're // reading discrete pixel values, we want to control when the samples // are taken rather than continuously averaging over time - ao.disable(); + ao1.disable(); + if (parallel) ao2.disable(); // clear out power-on noise by clocking through all pixels twice clear(); @@ -66,38 +92,84 @@ // the current pixels and start a fresh integration cycle. void read(uint16_t *pix, int n) { + // get the clock pin pointers into local variables for fast access + register FGPIO_Type *clockPort = this->clockPort; + register uint32_t clockMask = this->clockMask; + // start the next integration cycle by pulsing SI and one clock si = 1; - clock = 1; + clockPort->PSOR |= clockMask; // turn the clock pin on (clock = 1) si = 0; - clock = 0; + clockPort->PCOR |= clockMask; // turn the clock pin off (clock = 0) // figure how many pixels to skip on each read int skip = nPix/n - 1; // read all of the pixels - for (int src = 0, dst = 0 ; src < nPix ; ++src) + if (parallel) { - // clock in and read the next pixel - clock = 1; - ao.enable(); - wait_us(1); - clock = 0; - wait_us(11); - pix[dst++] = ao.read_u16(); - ao.disable(); - - // clock skipped pixels - for (int i = 0 ; i < skip ; ++i, ++src) + // parallel mode - read pixels from each half sensor concurrently + int nPixHalf = nPix/2; + for (int src = 0, dst = 0 ; src < nPixHalf ; ++src) { - clock = 1; - clock = 0; + // pulse the clock and start the ADC sampling + clockPort->PSOR |= clockMask; + ao1.enable(); + ao2.enable(); + wait_us(1); + clockPort->PCOR |= clockMask; + + // wait for the ADCs to stabilize + wait_us(11); + + // read the pixels + pix[dst] = ao1.read_u16(); + pix[dst+n/2] = ao2.read_u16(); + + // turn off the ADC until the next pixel is ready + ao1.disable(); + ao2.disable(); + + // clock skipped pixels + for (int i = 0 ; i < skip ; ++i, ++src) + { + clockPort->PSOR |= clockMask; + clockPort->PCOR |= clockMask; + } + } + } + else + { + // serial mode - read all pixels in a single file + for (int src = 0, dst = 0 ; src < nPix ; ++src) + { + // pulse the clock and start the ADC sampling + clockPort->PSOR |= clockMask; + ao1.enable(); + wait_us(1); + clockPort->PCOR |= clockMask; + + // wait for the ADC sample to stabilize + wait_us(11); + + // read the ADC sample + pix[dst++] = ao1.read_u16(); + + // turn off the ADC until the next pixel is ready + ao1.disable(); + + // clock skipped pixels + for (int i = 0 ; i < skip ; ++i, ++src) + { + clockPort->PSOR |= clockMask; + clockPort->PCOR |= clockMask; + } } } // clock out one extra pixel to leave A1 in the high-Z state - clock = 1; - clock = 0; + clockPort->PSOR |= clockMask; + clockPort->PCOR |= clockMask; } // Clock through all pixels to clear the array. Pulses SI at the @@ -106,27 +178,32 @@ // integrated while the clear() was taking place. void clear() { + // get the clock pin pointers into local variables for fast access + register FGPIO_Type *clockPort = this->clockPort; + register uint32_t clockMask = this->clockMask; + // clock in an SI pulse si = 1; - clock = 1; - clock = 0; + clockPort->PSOR |= clockMask; si = 0; + clockPort->PCOR |= clockMask; // clock out all pixels for (int i = 0 ; i < nPix + 1 ; ++i) { - clock = 1; - clock = 0; + clockPort->PSOR |= clockMask; + clockPort->PCOR |= clockMask; } } - // number of pixels in the array - static const int nPix = CCD_NPIXELS; - - private: - FastOut<siPin> si; - FastOut<clockPin> clock; - FastAnalogIn ao; + int nPix; // number of pixels in physical sensor array + DigitalOut si; + DigitalOut clock; + FGPIO_Type *clockPort; // IOPORT base address for clock pin, for fast writes + uint32_t clockMask; // IOPORT register bit mask for clock pin + FastAnalogIn ao1; + FastAnalogIn ao2; // valid iff running in parallel mode + bool parallel; // true -> running in parallel mode }; #endif /* TSL1410R_H */
--- a/TSL1410R/tsl410r.cpp Thu Dec 03 07:34:57 2015 +0000 +++ b/TSL1410R/tsl410r.cpp Sat Dec 19 06:37:19 2015 +0000 @@ -1,85 +1,3 @@ -#if 0 // this file is no longer used - the method bodies are no in the header, // which was necessary because of the change to a template class, which // itself was necessary because of the use of the FastIO library - -#include "mbed.h" -#include "tsl1410r.h" - -template <PinName siPin, PinName clockPin> TSL1410R<siPin, clockPin>:: - TSL1410R<siPin, clockPin>(PinName aoPort) : ao(aoPort) -{ - // clear out power-on noise by clocking through all pixels twice - clear(); - clear(); -} - -template <PinName siPin, PinName clockPin> void TSL1410R<siPin, clockPin>::clear() -{ - // clock in an SI pulse - si = 1; - clock = 1; - clock = 0; - si = 0; - - // clock out all pixels - for (int i = 0 ; i < nPix + 1 ; ++i) { - clock = 1; - clock = 0; - } -} - -template <PinName siPin, PinName clockPin> void TSL1410R<siPin, clockPin>:: - read(uint16_t *pix, int n, void (*cb)(void *ctx), void *cbctx, int cbcnt) -{ - // start the next integration cycle by pulsing SI and one clock - si = 1; - clock = 1; - clock = 0; - si = 0; - - // figure how many pixels to skip on each read - int skip = nPix/n - 1; - - // figure the callback interval - int cbInterval = nPix; - if (cb != 0) - cbInterval = nPix/(cbcnt+1); - - // read all of the pixels - for (int src = 0, dst = 0 ; src < nPix ; ) - { - // figure the end of this callback interval - int srcEnd = src + cbInterval; - if (srcEnd > nPix) - srcEnd = nPix; - - // read one callback chunk of pixels - for ( ; src < srcEnd ; ++src) - { - // read this pixel - pix[dst++] = ao.read_u16(); - - // clock in the next pixel - clock = 1; - clock = 0; - - // clock skipped pixels - for (int i = 0 ; i < skip ; ++i, ++src) - { - clock = 1; - clock = 0; - } - } - - // call the callback, if we're not at the last pixel - if (cb != 0 && src < nPix) - (*cb)(cbctx); - } - - // clock out one extra pixel to leave A1 in the high-Z state - clock = 1; - clock = 0; -} - -#endif /* 0 */
--- a/USBDevice.lib Thu Dec 03 07:34:57 2015 +0000 +++ b/USBDevice.lib Sat Dec 19 06:37:19 2015 +0000 @@ -1,1 +1,1 @@ -http://mbed.org/users/mjr/code/USBDevice/#a8eb758f4074 +http://mbed.org/users/mjr/code/USBDevice/#b0a3f6b27b07
--- a/USBJoystick/USBJoystick.cpp Thu Dec 03 07:34:57 2015 +0000 +++ b/USBJoystick/USBJoystick.cpp Sat Dec 19 06:37:19 2015 +0000 @@ -22,11 +22,12 @@ #include "config.h" // Pinscape configuration + + // Length of our joystick reports. Important: This must be kept in sync // with the actual joystick report format sent in update(). const int reportLen = 14; -#ifdef ENABLE_JOYSTICK bool USBJoystick::update(int16_t x, int16_t y, int16_t z, uint32_t buttons, uint16_t status) { _x = x; @@ -39,7 +40,7 @@ // send the report return update(); } - + bool USBJoystick::update() { HID_REPORT report; @@ -62,6 +63,30 @@ return sendTO(&report, 100); } +bool USBJoystick::kbUpdate(uint8_t data[8]) +{ + // set up the report + HID_REPORT report; + report.data[0] = REPORT_ID_KB; // report ID = keyboard + memcpy(&report.data[1], data, 8); // copy the kb report data + report.length = 9; // length = ID prefix + kb report length + + // send it to endpoint 4 (the keyboard interface endpoint) + return writeTO(EP4IN, report.data, report.length, MAX_PACKET_SIZE_EPINT, 100); +} + +bool USBJoystick::mediaUpdate(uint8_t data) +{ + // set up the report + HID_REPORT report; + report.data[0] = REPORT_ID_MEDIA; // report ID = media + report.data[1] = data; // key pressed bits + report.length = 2; + + // send it + return writeTO(EP4IN, report.data, report.length, MAX_PACKET_SIZE_EPINT, 100); +} + bool USBJoystick::updateExposure(int &idx, int npix, const uint16_t *pix) { HID_REPORT report; @@ -91,10 +116,10 @@ } // send the report - return send(&report); + return sendTO(&report, 100); } -bool USBJoystick::reportConfig(int numOutputs, int unitNo) +bool USBJoystick::reportConfig(int numOutputs, int unitNo, int plungerZero, int plungerMax) { HID_REPORT report; @@ -111,9 +136,13 @@ // write the unit number put(4, unitNo); + // write the plunger zero and max values + put(6, plungerZero); + put(8, plungerMax); + // send the report report.length = reportLen; - return send(&report); + return sendTO(&report, 100); } bool USBJoystick::move(int16_t x, int16_t y) @@ -136,8 +165,6 @@ return update(); } -#else /* ENABLE_JOYSTICK */ - bool USBJoystick::updateStatus(uint32_t status) { HID_REPORT report; @@ -152,9 +179,6 @@ return sendTO(&report, 100); } -#endif /* ENABLE_JOYSTICK */ - - void USBJoystick::_init() { _x = 0; @@ -166,93 +190,183 @@ } -uint8_t * USBJoystick::reportDesc() -{ -#ifdef ENABLE_JOYSTICK - // Joystick reports are enabled. Use the full joystick report - // format. - static uint8_t reportDescriptor[] = - { - USAGE_PAGE(1), 0x01, // Generic desktop - USAGE(1), 0x04, // Joystick - COLLECTION(1), 0x01, // Application +// -------------------------------------------------------------------------- +// +// USB HID Report Descriptor - Joystick +// +static uint8_t reportDescriptorJS[] = +{ + USAGE_PAGE(1), 0x01, // Generic desktop + USAGE(1), 0x04, // Joystick + COLLECTION(1), 0x01, // Application + + // input report (device to host) + + USAGE_PAGE(1), 0x06, // generic device controls - for config status + USAGE(1), 0x00, // undefined device control + LOGICAL_MINIMUM(1), 0x00, // 8-bit values + LOGICAL_MAXIMUM(1), 0xFF, + REPORT_SIZE(1), 0x08, // 8 bits per report + REPORT_COUNT(1), 0x04, // 4 reports (4 bytes) + INPUT(1), 0x02, // Data, Variable, Absolute + + USAGE_PAGE(1), 0x09, // Buttons + USAGE_MINIMUM(1), 0x01, // { buttons } + USAGE_MAXIMUM(1), 0x20, // { 1-32 } + LOGICAL_MINIMUM(1), 0x00, // 1-bit buttons - 0... + LOGICAL_MAXIMUM(1), 0x01, // ...to 1 + REPORT_SIZE(1), 0x01, // 1 bit per report + REPORT_COUNT(1), 0x20, // 32 reports + UNIT_EXPONENT(1), 0x00, // Unit_Exponent (0) + UNIT(1), 0x00, // Unit (None) + INPUT(1), 0x02, // Data, Variable, Absolute + + USAGE_PAGE(1), 0x01, // Generic desktop + USAGE(1), 0x30, // X axis + USAGE(1), 0x31, // Y axis + USAGE(1), 0x32, // Z axis + LOGICAL_MINIMUM(2), 0x00,0xF0, // each value ranges -4096 + LOGICAL_MAXIMUM(2), 0x00,0x10, // ...to +4096 + REPORT_SIZE(1), 0x10, // 16 bits per report + REPORT_COUNT(1), 0x03, // 3 reports (X, Y, Z) + INPUT(1), 0x02, // Data, Variable, Absolute - // input report (device to host) + // output report (host to device) + REPORT_SIZE(1), 0x08, // 8 bits per report + REPORT_COUNT(1), 0x08, // output report count - 8-byte LedWiz format + 0x09, 0x01, // usage + 0x91, 0x01, // Output (array) + + END_COLLECTION(0) +}; + +// +// USB HID Report Descriptor - Keyboard/Media Control +// +static uint8_t reportDescriptorKB[] = +{ + USAGE_PAGE(1), 0x01, // Generic Desktop + USAGE(1), 0x06, // Keyboard + COLLECTION(1), 0x01, // Application + REPORT_ID(1), REPORT_ID_KB, - USAGE_PAGE(1), 0x06, // generic device controls - for config status - USAGE(1), 0x00, // undefined device control - LOGICAL_MINIMUM(1), 0x00, // 8-bit values - LOGICAL_MAXIMUM(1), 0xFF, - REPORT_SIZE(1), 0x08, // 8 bits per report - REPORT_COUNT(1), 0x04, // 4 reports (4 bytes) - INPUT(1), 0x02, // Data, Variable, Absolute + USAGE_PAGE(1), 0x07, // Key Codes + USAGE_MINIMUM(1), 0xE0, + USAGE_MAXIMUM(1), 0xE7, + LOGICAL_MINIMUM(1), 0x00, + LOGICAL_MAXIMUM(1), 0x01, + REPORT_SIZE(1), 0x01, + REPORT_COUNT(1), 0x08, + INPUT(1), 0x02, // Data, Variable, Absolute + REPORT_COUNT(1), 0x01, + REPORT_SIZE(1), 0x08, + INPUT(1), 0x01, // Constant + + REPORT_COUNT(1), 0x05, + REPORT_SIZE(1), 0x01, + USAGE_PAGE(1), 0x08, // LEDs + USAGE_MINIMUM(1), 0x01, + USAGE_MAXIMUM(1), 0x05, + OUTPUT(1), 0x02, // Data, Variable, Absolute + REPORT_COUNT(1), 0x01, + REPORT_SIZE(1), 0x03, + OUTPUT(1), 0x01, // Constant - USAGE_PAGE(1), 0x09, // Buttons - USAGE_MINIMUM(1), 0x01, // { buttons } - USAGE_MAXIMUM(1), 0x20, // { 1-32 } - LOGICAL_MINIMUM(1), 0x00, // 1-bit buttons - 0... - LOGICAL_MAXIMUM(1), 0x01, // ...to 1 - REPORT_SIZE(1), 0x01, // 1 bit per report - REPORT_COUNT(1), 0x20, // 32 reports - UNIT_EXPONENT(1), 0x00, // Unit_Exponent (0) - UNIT(1), 0x00, // Unit (None) - INPUT(1), 0x02, // Data, Variable, Absolute - - USAGE_PAGE(1), 0x01, // Generic desktop - USAGE(1), 0x30, // X axis - USAGE(1), 0x31, // Y axis - USAGE(1), 0x32, // Z axis - LOGICAL_MINIMUM(2), 0x00,0xF0, // each value ranges -4096 - LOGICAL_MAXIMUM(2), 0x00,0x10, // ...to +4096 - REPORT_SIZE(1), 0x10, // 16 bits per report - REPORT_COUNT(1), 0x03, // 3 reports (X, Y, Z) - INPUT(1), 0x02, // Data, Variable, Absolute - - // output report (host to device) - REPORT_SIZE(1), 0x08, // 8 bits per report - REPORT_COUNT(1), 0x08, // output report count - 8-byte LedWiz format - 0x09, 0x01, // usage - 0x91, 0x01, // Output (array) + REPORT_COUNT(1), 0x06, + REPORT_SIZE(1), 0x08, + LOGICAL_MINIMUM(1), 0x00, + LOGICAL_MAXIMUM(1), 0x65, + USAGE_PAGE(1), 0x07, // Key Codes + USAGE_MINIMUM(1), 0x00, + USAGE_MAXIMUM(1), 0x65, + INPUT(1), 0x00, // Data, Array + END_COLLECTION(0), - END_COLLECTION(0) + // Media Control + USAGE_PAGE(1), 0x0C, + USAGE(1), 0x01, + COLLECTION(1), 0x01, + REPORT_ID(1), REPORT_ID_MEDIA, + USAGE_PAGE(1), 0x0C, + LOGICAL_MINIMUM(1), 0x00, + LOGICAL_MAXIMUM(1), 0x01, + REPORT_SIZE(1), 0x01, + REPORT_COUNT(1), 0x07, + USAGE(1), 0xE9, // Volume Up + USAGE(1), 0xEA, // Volume Down + USAGE(1), 0xE2, // Mute + USAGE(1), 0xB5, // Next Track + USAGE(1), 0xB6, // Previous Track + USAGE(1), 0xB7, // Stop + USAGE(1), 0xCD, // Play / Pause + INPUT(1), 0x02, // Input (Data, Variable, Absolute) + REPORT_COUNT(1), 0x01, + INPUT(1), 0x01, + END_COLLECTION(0), +}; - }; -#else /* defined(ENABLE_JOYSTICK) */ +// +// USB HID Report Descriptor - LedWiz only, with no joystick or keyboard +// input reporting +// +static uint8_t reportDescriptorLW[] = +{ + USAGE_PAGE(1), 0x01, // Generic desktop + USAGE(1), 0x00, // Undefined - // Joystick reports are disabled. We still want to appear - // as a USB device for the LedWiz output emulation, but we - // don't want to appear as a joystick. + COLLECTION(1), 0x01, // Application - static uint8_t reportDescriptor[] = - { - USAGE_PAGE(1), 0x01, // Generic desktop - USAGE(1), 0x00, // Undefined + // input report (device to host) + USAGE_PAGE(1), 0x06, // generic device controls - for config status + USAGE(1), 0x00, // undefined device control + LOGICAL_MINIMUM(1), 0x00, // 8-bit values + LOGICAL_MAXIMUM(1), 0xFF, + REPORT_SIZE(1), 0x08, // 8 bits per report + REPORT_COUNT(1), reportLen, // standard report length (same as if we were in joystick mode) + INPUT(1), 0x02, // Data, Variable, Absolute + + // output report (host to device) + REPORT_SIZE(1), 0x08, // 8 bits per report + REPORT_COUNT(1), 0x08, // output report count (LEDWiz messages) + 0x09, 0x01, // usage + 0x91, 0x01, // Output (array) + + END_COLLECTION(0) +}; + - COLLECTION(1), 0x01, // Application - - // input report (device to host) - USAGE_PAGE(1), 0x06, // generic device controls - for config status - USAGE(1), 0x00, // undefined device control - LOGICAL_MINIMUM(1), 0x00, // 8-bit values - LOGICAL_MAXIMUM(1), 0xFF, - REPORT_SIZE(1), 0x08, // 8 bits per report - REPORT_COUNT(1), reportLen, // standard report length (same as if we were in joystick mode) - INPUT(1), 0x02, // Data, Variable, Absolute - - // output report (host to device) - REPORT_SIZE(1), 0x08, // 8 bits per report - REPORT_COUNT(1), 0x08, // output report count (LEDWiz messages) - 0x09, 0x01, // usage - 0x91, 0x01, // Output (array) - - END_COLLECTION(0) - }; - -#endif /* defined(ENABLE_JOYSTICK) */ - - reportLength = sizeof(reportDescriptor); - return reportDescriptor; -} +uint8_t * USBJoystick::reportDescN(int idx) +{ + if (enableJoystick) + { + // Joystick reports are enabled. Use the full joystick report + // format, or full keyboard report format, depending on which + // interface is being requested. + switch (idx) + { + case 0: + // joystick interface + reportLength = sizeof(reportDescriptorJS); + return reportDescriptorJS; + + case 1: + // keyboard interface + reportLength = sizeof(reportDescriptorKB); + return reportDescriptorKB; + + default: + // unknown interface + reportLength = 0; + return 0; + } + } + else + { + // Joystick reports are disabled. Use the LedWiz-only format. + reportLength = sizeof(reportDescriptorLW); + return reportDescriptorLW; + } +} uint8_t * USBJoystick::stringImanufacturerDesc() { static uint8_t stringImanufacturerDescriptor[] = { @@ -282,3 +396,206 @@ }; return stringIproductDescriptor; } + +#define DEFAULT_CONFIGURATION (1) + +uint8_t * USBJoystick::configurationDesc() +{ + int rptlen0 = reportDescLengthN(0); + int rptlen1 = reportDescLengthN(1); + if (useKB) + { + int cfglenKB = ((1 * CONFIGURATION_DESCRIPTOR_LENGTH) + + (2 * INTERFACE_DESCRIPTOR_LENGTH) + + (2 * HID_DESCRIPTOR_LENGTH) + + (4 * ENDPOINT_DESCRIPTOR_LENGTH)); + static uint8_t configurationDescriptorWithKB[] = + { + CONFIGURATION_DESCRIPTOR_LENGTH,// bLength + CONFIGURATION_DESCRIPTOR, // bDescriptorType + LSB(cfglenKB), // wTotalLength (LSB) + MSB(cfglenKB), // wTotalLength (MSB) + 0x02, // bNumInterfaces - TWO INTERFACES (JOYSTICK + KEYBOARD) + DEFAULT_CONFIGURATION, // bConfigurationValue + 0x00, // iConfiguration + C_RESERVED | C_SELF_POWERED, // bmAttributes + C_POWER(0), // bMaxPowerHello World from Mbed + + // INTERFACE 0 - JOYSTICK/LEDWIZ + INTERFACE_DESCRIPTOR_LENGTH, // bLength + INTERFACE_DESCRIPTOR, // bDescriptorType + 0x00, // bInterfaceNumber - first interface = 0 + 0x00, // bAlternateSetting + 0x02, // bNumEndpoints + HID_CLASS, // bInterfaceClass + HID_SUBCLASS_NONE, // bInterfaceSubClass + HID_PROTOCOL_NONE, // bInterfaceProtocol + 0x00, // iInterface + + HID_DESCRIPTOR_LENGTH, // bLength + HID_DESCRIPTOR, // bDescriptorType + LSB(HID_VERSION_1_11), // bcdHID (LSB) + MSB(HID_VERSION_1_11), // bcdHID (MSB) + 0x00, // bCountryCode + 0x01, // bNumDescriptors + REPORT_DESCRIPTOR, // bDescriptorType + LSB(rptlen0), // wDescriptorLength (LSB) + MSB(rptlen0), // wDescriptorLength (MSB) + + ENDPOINT_DESCRIPTOR_LENGTH, // bLength + ENDPOINT_DESCRIPTOR, // bDescriptorType + PHY_TO_DESC(EPINT_IN), // bEndpointAddress - EPINT == EP1 + E_INTERRUPT, // bmAttributes + LSB(MAX_PACKET_SIZE_EPINT), // wMaxPacketSize (LSB) + MSB(MAX_PACKET_SIZE_EPINT), // wMaxPacketSize (MSB) + 1, // bInterval (milliseconds) + + ENDPOINT_DESCRIPTOR_LENGTH, // bLength + ENDPOINT_DESCRIPTOR, // bDescriptorType + PHY_TO_DESC(EPINT_OUT), // bEndpointAddress - EPINT == EP1 + E_INTERRUPT, // bmAttributes + LSB(MAX_PACKET_SIZE_EPINT), // wMaxPacketSize (LSB) + MSB(MAX_PACKET_SIZE_EPINT), // wMaxPacketSize (MSB) + 1, // bInterval (milliseconds) + + // INTERFACE 1 - KEYBOARD + INTERFACE_DESCRIPTOR_LENGTH, // bLength + INTERFACE_DESCRIPTOR, // bDescriptorType + 0x01, // bInterfaceNumber - second interface = 1 + 0x00, // bAlternateSetting + 0x02, // bNumEndpoints + HID_CLASS, // bInterfaceClass + 1, // bInterfaceSubClass - KEYBOARD + 1, // bInterfaceProtocol - KEYBOARD + 0x00, // iInterface + + HID_DESCRIPTOR_LENGTH, // bLength + HID_DESCRIPTOR, // bDescriptorType + LSB(HID_VERSION_1_11), // bcdHID (LSB) + MSB(HID_VERSION_1_11), // bcdHID (MSB) + 0x00, // bCountryCode + 0x01, // bNumDescriptors + REPORT_DESCRIPTOR, // bDescriptorType + LSB(rptlen1), // wDescriptorLength (LSB) + MSB(rptlen1), // wDescriptorLength (MSB) + + ENDPOINT_DESCRIPTOR_LENGTH, // bLength + ENDPOINT_DESCRIPTOR, // bDescriptorType + PHY_TO_DESC(EP4IN), // bEndpointAddress + E_INTERRUPT, // bmAttributes + LSB(MAX_PACKET_SIZE_EPINT), // wMaxPacketSize (LSB) + MSB(MAX_PACKET_SIZE_EPINT), // wMaxPacketSize (MSB) + 1, // bInterval (milliseconds) + + ENDPOINT_DESCRIPTOR_LENGTH, // bLength + ENDPOINT_DESCRIPTOR, // bDescriptorType + PHY_TO_DESC(EP4OUT), // bEndpointAddress + E_INTERRUPT, // bmAttributes + LSB(MAX_PACKET_SIZE_EPINT), // wMaxPacketSize (LSB) + MSB(MAX_PACKET_SIZE_EPINT), // wMaxPacketSize (MSB) + 1, // bInterval (milliseconds) + }; + + // Keyboard + joystick interfaces + return configurationDescriptorWithKB; + } + else + { + // No keyboard - joystick interface only + int cfglenNoKB = ((1 * CONFIGURATION_DESCRIPTOR_LENGTH) + + (1 * INTERFACE_DESCRIPTOR_LENGTH) + + (1 * HID_DESCRIPTOR_LENGTH) + + (2 * ENDPOINT_DESCRIPTOR_LENGTH)); + static uint8_t configurationDescriptorNoKB[] = + { + CONFIGURATION_DESCRIPTOR_LENGTH,// bLength + CONFIGURATION_DESCRIPTOR, // bDescriptorType + LSB(cfglenNoKB), // wTotalLength (LSB) + MSB(cfglenNoKB), // wTotalLength (MSB) + 0x01, // bNumInterfaces + DEFAULT_CONFIGURATION, // bConfigurationValue + 0x00, // iConfiguration + C_RESERVED | C_SELF_POWERED, // bmAttributes + C_POWER(0), // bMaxPowerHello World from Mbed + + INTERFACE_DESCRIPTOR_LENGTH, // bLength + INTERFACE_DESCRIPTOR, // bDescriptorType + 0x00, // bInterfaceNumber + 0x00, // bAlternateSetting + 0x02, // bNumEndpoints + HID_CLASS, // bInterfaceClass + 1, // bInterfaceSubClass + 1, // bInterfaceProtocol (keyboard) + 0x00, // iInterface + + HID_DESCRIPTOR_LENGTH, // bLength + HID_DESCRIPTOR, // bDescriptorType + LSB(HID_VERSION_1_11), // bcdHID (LSB) + MSB(HID_VERSION_1_11), // bcdHID (MSB) + 0x00, // bCountryCode + 0x01, // bNumDescriptors + REPORT_DESCRIPTOR, // bDescriptorType + (uint8_t)(LSB(rptlen0)), // wDescriptorLength (LSB) + (uint8_t)(MSB(rptlen0)), // wDescriptorLength (MSB) + + ENDPOINT_DESCRIPTOR_LENGTH, // bLength + ENDPOINT_DESCRIPTOR, // bDescriptorType + PHY_TO_DESC(EPINT_IN), // bEndpointAddress + E_INTERRUPT, // bmAttributes + LSB(MAX_PACKET_SIZE_EPINT), // wMaxPacketSize (LSB) + MSB(MAX_PACKET_SIZE_EPINT), // wMaxPacketSize (MSB) + 1, // bInterval (milliseconds) + + ENDPOINT_DESCRIPTOR_LENGTH, // bLength + ENDPOINT_DESCRIPTOR, // bDescriptorType + PHY_TO_DESC(EPINT_OUT), // bEndpointAddress + E_INTERRUPT, // bmAttributes + LSB(MAX_PACKET_SIZE_EPINT), // wMaxPacketSize (LSB) + MSB(MAX_PACKET_SIZE_EPINT), // wMaxPacketSize (MSB) + 1, // bInterval (milliseconds) + }; + + return configurationDescriptorNoKB; + } +} + +// Set the configuration. We need to set up the endpoints for +// our active interfaces. +bool USBJoystick::USBCallback_setConfiguration(uint8_t configuration) +{ + // we only have one valid configuration + if (configuration != DEFAULT_CONFIGURATION) + return false; + + // Configure endpoint 1 - we use this in all cases, for either + // the combined joystick/ledwiz interface or just the ledwiz interface + addEndpoint(EPINT_IN, MAX_PACKET_SIZE_EPINT); + addEndpoint(EPINT_OUT, MAX_PACKET_SIZE_EPINT); + readStart(EPINT_OUT, MAX_HID_REPORT_SIZE); + + // if the keyboard is enabled, configure endpoint 4 for the kb interface + if (useKB) + { + addEndpoint(EP4IN, MAX_PACKET_SIZE_EPINT); + addEndpoint(EP4OUT, MAX_PACKET_SIZE_EPINT); + readStart(EP4OUT, MAX_PACKET_SIZE_EPINT); + } + + // success + return true; +} + +// Handle messages on endpoint 4 - this is the keyboard interface. +// The host uses this to send updates for the keyboard indicator LEDs +// (caps lock, num lock, etc). We don't do anything with these, but +// we at least need to read them to keep the pipe from clogging up. +bool USBJoystick::EP4_OUT_callback() +{ + // read this message + uint32_t bytesRead = 0; + uint8_t led[65]; + USBDevice::readEP(EP4OUT, led, &bytesRead, MAX_HID_REPORT_SIZE); + + // start the next read + return readStart(EP4OUT, MAX_HID_REPORT_SIZE); +}
--- a/USBJoystick/USBJoystick.h Thu Dec 03 07:34:57 2015 +0000 +++ b/USBJoystick/USBJoystick.h Sat Dec 19 06:37:19 2015 +0000 @@ -7,9 +7,11 @@ #define USBJOYSTICK_H #include "USBHID.h" - -#define REPORT_ID_JOYSTICK 4 - + +// keyboard interface report IDs +const uint8_t REPORT_ID_KB = 1; +const uint8_t REPORT_ID_MEDIA = 2; + /* Common usage */ enum JOY_BUTTON { JOY_B0 = 0x0001, @@ -90,15 +92,32 @@ * @param product_id Your product_id (default: 0x0002) * @param product_release Your product_release (default: 0x0001) */ - USBJoystick(uint16_t vendor_id = 0x1234, uint16_t product_id = 0x0100, uint16_t product_release = 0x0001, int waitForConnect = true): - USBHID(16, 64, vendor_id, product_id, product_release, false) - { - _init(); - connect(waitForConnect); - }; + USBJoystick(uint16_t vendor_id, uint16_t product_id, uint16_t product_release, + int waitForConnect, bool enableJoystick, bool useKB) + : USBHID(16, 64, vendor_id, product_id, product_release, false) + { + _init(); + this->useKB = useKB; + this->enableJoystick = enableJoystick; + connect(waitForConnect); + }; /** - * Write a state of the mouse + * Send a keyboard report. The argument gives the key state, in the standard + * 6KRO USB keyboard report format: byte 0 is the modifier key bit mask, byte 1 + * is reserved (must be 0), and bytes 2-6 are the currently pressed keys, as + * USB key codes. + */ + bool kbUpdate(uint8_t data[8]); + + /** + * Send a media key update. The argument gives the bit mask of media keys + * currently pressed. See the HID report descriptor for the order of bits. + */ + bool mediaUpdate(uint8_t data); + + /** + * Update the joystick status * * @param x x-axis position * @param y y-axis position @@ -131,10 +150,10 @@ * @param numOutputs the number of configured output channels * @param unitNo the device unit number */ - bool reportConfig(int numOutputs, int unitNo); + bool reportConfig(int numOutputs, int unitNo, int plungerZero, int plungerMax); /** - * Write a state of the mouse + * Send a joystick report to the host * * @returns true if there is no error, false otherwise */ @@ -163,20 +182,25 @@ * @returns true if there is no error, false otherwise */ bool buttons(uint32_t buttons); - - /* - * To define the report descriptor. Warning: this method has to store the length of the report descriptor in reportLength. - * - * @returns pointer to the report descriptor - */ - virtual uint8_t * reportDesc(); + + /* USB descriptor overrides */ + virtual uint8_t * configurationDesc(); + virtual uint8_t * reportDescN(int n); /* USB descriptor string overrides */ virtual uint8_t *stringImanufacturerDesc(); virtual uint8_t *stringIserialDesc(); virtual uint8_t *stringIproductDesc(); + + /* callback overrides */ + virtual bool USBCallback_setConfiguration(uint8_t configuration); + virtual bool USBCallback_setInterface(uint16_t interface, uint8_t alternate) + { return interface == 0 || interface == 1; } + virtual bool EP4_OUT_callback(); private: + bool enableJoystick; + bool useKB; int16_t _x; int16_t _y; int16_t _z;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/USBProtocol.h Sat Dec 19 06:37:19 2015 +0000 @@ -0,0 +1,519 @@ +// USB Message Protocol +// +// This file is purely for documentation, to describe our USB protocol. +// We use the standard HID setup with one endpoint in each direction. +// See USBJoystick.cpp/.h for our USB descriptor arrangement. +// + +// ------ OUTGOING MESSAGES (DEVICE TO HOST) ------ +// +// In most cases, our outgoing messages are HID joystick reports, using the +// format defined in USBJoystick.cpp. This allows us to be installed on +// Windows as a standard USB joystick, which all versions of Windows support +// using in-the-box drivers. This allows a completely transparent, driverless, +// plug-and-play installation experience on Windows. +// +// We subvert the joystick report format in certain cases to report other +// types of information, when specifically requested by the host. This allows +// our custom configuration UI on the Windows side to query additional +// information that we don't normally send via the joystick reports. We +// define a custom vendor-specific "status" field in the reports that we +// use to identify these special reports, as described below. +// +// Normal joystick reports always have 0 in the high bit of the first byte +// of the report. Special non-joystick reports always have 1 in the high bit +// of the first byte. (This byte is defined in the HID Report Descriptor +// as an opaque vendor-defined value, so the joystick interface on the +// Windows side simply ignores it.) +// +// Pixel dumps: requested by custom protocol message 65 3 (see below). +// This sends a series of reports to the host in the following format, for +// as many messages as are neessary to report all pixels: +// +// bytes 0:1 = 11-bit index, with high 5 bits set to 10000. For +// example, 0x04 0x80 indicates index 4. This is the +// starting pixel number in the report. The first report +// will be 0x00 0x80 to indicate pixel #0. +// bytes 2:3 = 16-bit unsigned int brightness level of pixel at index +// bytes 4:5 = brightness of pixel at index+1 +// etc for the rest of the packet +// +// Configuration query: requested by custom protocol message 65 4 (see +// below). This sends one report to the host using this format: +// +// bytes 0:1 = 0x8800. This has the bit pattern 10001 in the high +// 5 bits, which distinguishes it from regular joystick +// reports and from exposure status reports. +// bytes 2:3 = total number of outputs, little endian +// bytes 4:5 = plunger calibration zero point, little endian +// bytes 6:7 = plunger calibration maximum point, little endian +// remaining bytes = reserved for future use; set to 0 in current version +// +// +// WHY WE USE THIS HACKY APPROACH TO DIFFERENT REPORT TYPES +// +// The HID report system was specifically designed to provide a clean, +// structured way for devices to describe the data they send to the host. +// Our approach isn't clean or structured; it ignores the promises we +// make about the contents of our report via the HID Report Descriptor +// and stuffs our own different data format into the same structure. +// +// We use this hacky approach only because we can't use the official +// mechanism, due to the constraint that we want to emulate the LedWiz. +// The right way to send different report types is to declare different +// report types via extra HID Report Descriptors, then send each report +// using one of the types we declared. If it weren't for the LedWiz +// constraint, we'd simply define the pixel dump and config query reports +// as their own separate HID Report types, each consisting of opaque +// blocks of bytes. But we can't do this. The snag is that some versions +// of the LedWiz Windows host software parse the USB HID descriptors as part +// of identifying a device as a valid LedWiz unit, and will only recognize +// the device if it matches certain particulars about the descriptor +// structure of a real LedWiz. One of the features that's important to +// some versions of the software is the descriptor link structure, which +// is affected by the layout of HID Report Descriptor entries. In order +// to match the expected layout, we can only define a single kind of output +// report. Since we have to use Joystick reports for the sake of VP and +// other pinball software, and we're only allowed the one report type, we +// have to make that one report type the Joystick type. That's why we +// overload the joystick reports with other meanings. It's a hack, but +// at least it's a fairly reliable and isolated hack, iun that our special +// reports are only generated when clients specifically ask for them. +// Plus, even if a client who doesn't ask for a special report somehow +// gets one, the worst that happens is that they get a momentary spurious +// reading from the accelerometer and plunger. + + + +// ------- INCOMING MESSAGES (HOST TO DEVICE) ------- +// +// For LedWiz compatibility, our incoming message format conforms to the +// basic USB format used by real LedWiz units. This is simply 8 data +// bytes, all private vendor-specific values (meaning that the Windows HID +// driver treats them as opaque and doesn't attempt to parse them). +// +// Within this basic 8-byte format, we recognize the full protocol used +// by real LedWiz units, plus an extended protocol that we define privately. +// The LedWiz protocol leaves a large part of the potential protocol space +// undefined, so we take advantage of this undefined region for our +// extensions. This ensures that we can properly recognize all messages +// intended for a real LedWiz unit, as well as messages from custom host +// software that knows it's talking to a Pinscape unit. + +// --- REAL LED WIZ MESSAGES --- +// +// The real LedWiz protocol has two message types, identified by the first +// byte of the 8-byte USB packet: +// +// 64 -> SBA (64 xx xx xx xx ss uu uu) +// xx = on/off bit mask for 8 outputs +// ss = global flash speed setting (1-7) +// uu = unused +// +// If the first byte has value 64 (0x40), it's an SBA message. This type of +// message sets all 32 outputs individually ON or OFF according to the next +// 32 bits (4 bytes) of the message, and sets the flash speed to the value in +// the sixth byte. (The flash speed sets the global cycle rate for flashing +// outputs - outputs with their values set to the range 128-132 - to a +// relative speed, scaled linearly in frequency. 1 is the slowest at about +// 2 Hz, 7 is the fastest at about 14 Hz.) +// +// 0-49 or 128-132 -> PBA (bb bb bb bb bb bb bb bb) +// bb = brightness level/flash pattern for one output +// +// If the first byte is any valid brightness setting, it's a PBA message. +// Valid brightness settings are: +// +// 0-48 = fixed brightness level, linearly from 0% to 100% intensity +// 49 = fixed brightness level at 100% intensity (same as 48) +// 129 = flashing pattern, fade up / fade down (sawtooth wave) +// 130 = flashing pattern, on / off (square wave) +// 131 = flashing pattern, on for 50% duty cycle / fade down +// 132 = flashing pattern, fade up / on for 50% duty cycle +// +// A PBA message sets 8 outputs out of 32. Which 8 are to be set is +// implicit in the message sequence: the first PBA sets outputs 1-8, the +// second sets 9-16, and so on, rolling around after each fourth PBA. +// An SBA also resets the implicit "bank" for the next PBA to outputs 1-8. +// +// Note that there's no special first byte to indicate the PBA message +// type, as there is in an SBA. The first byte of a PBA is simply the +// first output setting. The way the LedWiz creators conceived this, the +// SBA distinguishable from a PBA because 64 isn't a valid output setting, +// hence a message that starts with a byte value of 64 isn't a valid PBA +// message. +// +// Our extended protocol uses the same principle, taking advantage of the +// other byte value ranges that are invalid in PBA messages. To be a valid +// PBA message, the first byte must be in the range 0-49 or 129-132. As +// already mentioned, byte value 64 indicates an SBA message. This leaves +// these ranges available for other uses: 50-63, 65-128, and 133-255. + + +// --- PRIVATE EXTENDED MESSAGES --- +// +// All of our extended protocol messages are identified by the first byte: +// +// 65 -> Miscellaneous control message. The second byte specifies the specific +// operation: +// +// 1 -> Set device unit number and plunger status, and save the changes immediately +// to flash. The device will automatically reboot after the changes are saved. +// The additional bytes of the message give the parameters: +// +// third byte = new unit number (0-15, corresponding to nominal unit numbers 1-16) +// fourth byte = plunger on/off (0=disabled, 1=enabled) +// +// 2 -> Begin plunger calibration mode. The device stays in this mode for about +// 15 seconds, and sets the zero point and maximum retraction points to the +// observed endpoints of sensor readings while the mode is running. After +// the time limit elapses, the device automatically stores the results in +// non-volatile flash memory and exits the mode. +// +// 3 -> Send pixel dump. The plunger sensor object sends a series of the special +// pixel dump reports, defined in USBJoystick.cpp; the device automatically +// resumes normal joystick messages after sending all pixels. If the +// plunger sensor isn't an image sensor type, no pixel messages are sent. +// +// 4 -> Query configuration. The device sends a special configuration report, +// defined in USBJoystick.cpp, then resumes sending normal joystick reports. +// +// 5 -> Turn all outputs off and restore LedWiz defaults. Sets output ports +// 1-32 to OFF and LedWiz brightness/mode setting 48, sets outputs 33 and +// higher to brightness level 0, and sets the LedWiz global flash speed to 2. +// +// 6 -> Save configuration to flash. This saves all variable updates sent via +// type 66 messages since the last reboot, then automatically reboots the +// device to put the changes into effect. +// +// 66 -> Set configuration variable. The second byte of the message is the config +// variable number, and the remaining bytes give the new value for the variable. +// The value format is specific to each variable; see the list below for details. +// This message only sets the value in RAM - it doesn't write the value to flash +// and doesn't put the change into effect immediately. To put updates into effect, +// the host must send a type 65 subtype 6 message (see above), which saves updates +// to flash and reboots the device. +// +// 200-228 -> Set extended output brightness. This sets outputs N to N+6 to the +// respective brightness values in the 2nd through 8th bytes of the message +// (output N is set to the 2nd byte value, N+1 is set to the 3rd byte value, +// etc). Each brightness level is a linear brightness level from 0-255, +// where 0 is 0% brightness and 255 is 100% brightness. N is calculated as +// (first byte - 200)*7 + 1: +// +// 200 = outputs 1-7 +// 201 = outputs 8-14 +// 202 = outputs 15-21 +// ... +// 228 = outputs 197-203 +// +// This message is the only way to address ports 33 and higher, since standard +// LedWiz messages are inherently limited to ports 1-32. +// +// Note that these extended output messages differ from regular LedWiz settings +// in two ways. First, the brightness is the ONLY attribute when an output is +// set using this mode - there's no separate ON/OFF setting per output as there +// is with the SBA/PBA messages. To turn an output OFF with this message, set +// the intensity to 0. Setting a non-zero intensity turns it on immediately +// without regard to the SBA status for the port. Second, the brightness is +// on a full 8-bit scale (0-255) rather than the LedWiz's approximately 5-bit +// scale, because there are no parts of the range reserved for flashing modes. +// +// Outputs 1-32 can be controlled by EITHER the regular LedWiz SBA/PBA messages +// or by the extended messages. The latest setting for a given port takes +// precedence. If an SBA/PBA message was the last thing sent to a port, the +// normal LedWiz combination of ON/OFF and brightness/flash mode status is used +// to determine the port's physical output setting. If an extended brightness +// message was the last thing sent to a port, the LedWiz ON/OFF status and +// flash modes are ignored, and the fixed brightness is set. Outputs 33 and +// higher inherently can't be addressed or affected by SBA/PBA messages. + + +// ------- CONFIGURATION VARIABLES ------- +// +// Message type 66 (see above) sets one configuration variable. The second byte +// of the message is the variable ID, and the rest of the bytes give the new +// value, in a variable-specific format. 16-bit values are little endian. +// +// 1 -> USB device ID. Bytes 3-4 give the 16-bit USB Vendor ID; bytes +// 5-6 give the 16-bit USB Product ID. For LedWiz emulation, use +// vendor 0xFAFA and product 0x00EF + unit# (where unit# is the +// nominal LedWiz unit number, from 1 to 16). If LedWiz emulation +// isn't desired or causes host conflicts, you can use our private +// ID assigned by http://pid.codes (a registry for open-source USB +// devices) of vendor 0x1209 and product 0xEAEA. (You can also use +// any other values that don't cause a conflict on your PC, but we +// recommend using one of these pre-assigned values if possible.) +// +// 2 -> Pinscape Controller unit number for DOF. Byte 3 is the new +// unit number, from 1 to 16. +// +// 3 -> Enable/disable joystick reports. Byte 2 is 1 to enable, 0 to +// disable. When disabled, the device registers as a generic HID +/ device, and only sends the private report types used by the +// Windows config tool. +// +// 4 -> Accelerometer orientation. Byte 3 is the new setting: +// +// 0 = ports at front (USB ports pointing towards front of cabinet) +// 1 = ports at left +// 2 = ports at right +// 3 = ports at rear +// +// 5 -> Plunger sensor type. Byte 3 is the type ID: +// +// 0 = none (disabled) +// 1 = TSL1410R linear image sensor, 1280x1 pixels, serial mode +// 2 = TSL1410R, parallel mode +// 3 = TSL1412R linear image sensor, 1536x1 pixels, serial mode +// 4 = TSL1412R, parallel mode +// 5 = Potentiometer with linear taper, or any other device that +// represents the position reading with a single analog voltage +// 6 = AEDR8300 optical quadrature sensor, 75lpi +// 7 = AS5304 magnetic quadrature sensor, 160 steps per 2mm +// +// 6 -> Plunger pin assignments. Bytes 3-6 give the pin assignments for +// pins 1, 2, 3, and 4. These use the Pin Number Mappings listed +// below. The meaning of each pin depends on the plunger type: +// +// TSL1410R/1412R, serial: SI (DigitalOut), CLK (DigitalOut), AO (AnalogIn), NC +// TSL1410R/1412R, parallel: SI (DigitalOut), CLK (DigitalOut), AO1 (AnalogIn), AO2 (AnalogIn) +// Potentiometer: AO (AnalogIn), NC, NC, NC +// AEDR8300: A (InterruptIn), B (InterruptIn), NC, NC +// AS5304: A (InterruptIn), B (InterruptIn), NC, NC +// +// 7 -> Plunger calibration button pin assignments. Byte 3 is the DigitalIn +// pin for the button switch; byte 4 is the DigitalOut pin for the indicator +// lamp. Either can be set to NC to disable the function. (Use the Pin +// Number Mappins listed below for both bytes.) +// +// 8 -> ZB Launch Ball setup. This configures the ZB Launch Ball feature. Byte +// 3 is the LedWiz port number (1-255) mapped to the "ZB Launch Ball" output +// in DOF. Set the port to 0 to disable the feature. Byte 4 is the button +// number (1-32) that we'll "press" when the feature is activated. Bytes 5-6 +// give the "push distance" for activating the button by pushing forward on +// the plunger knob, in .001 inch increments (e.g., 80 represents 0.08", which +// is the recommended setting). +// +// 9 -> TV ON relay setup. This requires external circuitry implemented on the +// Expansion Board (or an equivalent circuit as described in the Build Guide). +// Byte 3 is the GPIO DigitalIn pin for the "power status" input, using the +// Pin Number Mappings below. Byte 4 is the DigitalOut pin for the "latch" +// output. Byte 5 is the DigitalOut pin for the relay trigger. Bytes 6-7 +// give the delay time in 10ms increments as an unsigned 16-bit value (e.g., +// 550 represents 5.5 seconds). +// +// 10 -> TLC5940NT setup. This chip is an external PWM controller, with 32 outputs +// per chip and a serial data interface that allows the chips to be daisy- +// chained. We can use these chips to add an arbitrary number of PWM output +// ports for the LedWiz emulation. Set the number of chips to 0 to disable +// the feature. The bytes of the message are: +// byte 3 = number of chips attached (connected in daisy chain) +// byte 4 = SIN pin - Serial data (must connect to SPIO MOSI -> PTC6 or PTD2) +// byte 5 = SCLK pin - Serial clock (must connect to SPIO SCLK -> PTC5 or PTD1) +// byte 6 = XLAT pin - XLAT (latch) signal (any GPIO pin) +// byte 7 = BLANK pin - BLANK signal (any GPIO pin) +// byte 8 = GSCLK pin - Grayscale clock signal (must be a PWM-out capable pin) +// +// 11 -> 74HC595 setup. This chip is an external shift register, with 8 outputs per +// chip and a serial data interface that allows daisy-chaining. We use this +// chips to add extra digital outputs for the LedWiz emulation. In particular, +// the Chime Board (part of the Expansion Board suite) uses these to add timer- +// protected outputs for coil devices (knockers, chimes, bells, etc). Set the +// number of chips to 0 to disable the feature. The message bytes are: +// byte 3 = number of chips attached (connected in daisy chain) +// byte 4 = SIN pin - Serial data (any GPIO pin) +// byte 5 = SCLK pin - Serial clock (any GPIO pin) +// byte 6 = LATCH pin - LATCH signal (any GPIO pin) +// byte 7 = ENA pin - ENABLE signal (any GPIO pin) +// +// 12 -> Input button setup. This sets up one button; it can be repeated for each +// button to be configured. There are 32 button slots, numbered 1-32. Each +// key can be configured as a joystick button, a regular keyboard key, a +// keyboard modifier key (such as Shift, Ctrl, or Alt), or a media control +// key (such as volume up/down). +// +// The bytes of the message are: +// byte 3 = Button number (1-32) +// byte 4 = GPIO pin to read for button input +// byte 5 = key type reported to PC when button is pushed: +// 1 = joystick button -> byte 6 is the button number, 1-32 +// 2 = regular keyboard key -> byte 6 is the USB key code (see below) +// 3 = keyboard modifier key -> byte 6 is the USB modifier code (see below) +// 4 = media control key -> byte 6 is the USB key code (see below) +// byte 6 = key code, which depends on the key type in byte 5 +// +// 13 -> LedWiz output port setup. This sets up one output port; it can be repeated +// for each port to be configured. There are 203 possible slots for output ports, +// numbered 1 to 203. The number of ports visible to the host is determined by +// the first DISABLED port (type 0). For example, if ports 1-32 are set as GPIO +// outputs and port 33 is disabled, the host will see 32 ports, regardless of +// the settings for post 34 and higher. +// +// The bytes of the message are: +// byte 3 = LedWiz port number (1 to maximum number or ports) +// byte 4 = physical output type: +// 0 = Disabled. This output isn't used, and isn't visible to the +// LedWiz/DOF software on the host. The FIRST disabled port +// determines the number of ports visible to the host - ALL ports +// after the first disabled port are also implicitly disabled. +// 1 = GPIO PWM output: connected to GPIO pin specified in byte 5, +// operating in PWM mode. Note that only a subset of KL25Z GPIO +// ports are PWM-capable. +// 2 = GPIO Digital output: connected to GPIO pin specified in byte 5, +// operating in digital mode. Digital ports can only be set ON +// or OFF, with no brightness/intensity control. All pins can be +// used in this mode. +// 3 = TLC5940 port: connected to TLC5940 output port number specified +// in byte 5. Ports are numbered sequentially starting from port 0 +// for the first output (OUT0) on the first chip in the daisy chain. +// 4 = 74HC595 port: connected to 74HC595 output port specified in byte 5. +// As with the TLC5940 outputs, ports are numbered sequentially from 0 +// for the first output on the first chip in the daisy chain. +// 5 = Virtual output: this output port exists for the purposes of the +// LedWiz/DOF software on the host, but isn't physically connected +// to any output device. This can be used to create a virtual output +// for the DOF ZB Launch Ball signal, for example, or simply as a +// placeholder in the LedWiz port numbering. The physical output ID +// (byte 5) is ignored for this port type. +// byte 5 = physical output ID, interpreted according to the value in byte 4 +// byte 6 = flags: a combination of these bit values: +// 1 = active-high output (0V on output turns attached device ON) + + +// --- PIN NUMBER MAPPINGS --- +// +// In USB messages that specify GPIO pin assignments, pins are identified with +// our own private numbering scheme. Our numbering scheme only includes the +// ports connected to external header pins on the KL25Z board, so this is only +// a sparse subset of the full GPIO port set. These are numbered in order of +// pin name. The special value 0 = NC = Not Connected can be used where +// appropriate to indicate a disabled or unused pin. +// +// 0 = NC (not connected) +// 1 = PTA1 +// 2 = PTA2 +// 3 = PTA4 +// 4 = PTA5 +// 5 = PTA12 +// 6 = PTA13 +// 7 = PTA16 +// 8 = PTA17 +// 9 = PTB0 +// 10 = PTB1 +// 11 = PTB2 +// 12 = PTB3 +// 13 = PTB8 +// 14 = PTB9 +// 15 = PTB10 +// 16 = PTB11 +// 17 = PTC0 +// 18 = PTC1 +// 19 = PTC2 +// 20 = PTC3 +// 21 = PTC4 +// 22 = PTC5 +// 23 = PTC6 +// 24 = PTC7 +// 25 = PTC8 +// 26 = PTC9 +// 27 = PTC10 +// 28 = PTC11 +// 29 = PTC12 +// 30 = PTC13 +// 31 = PTC16 +// 32 = PTC17 +// 33 = PTD0 +// 34 = PTD1 +// 35 = PTD2 +// 36 = PTD3 +// 37 = PTD4 +// 38 = PTD5 +// 39 = PTD6 +// 40 = PTD7 +// 41 = PTE0 +// 42 = PTE1 +// 43 = PTE2 +// 44 = PTE3 +// 45 = PTE4 +// 46 = PTE5 +// 47 = PTE20 +// 48 = PTE21 +// 49 = PTE22 +// 50 = PTE23 +// 51 = PTE29 +// 52 = PTE30 +// 53 = PTE31 + + +// --- USB KEYBOARD SCAN CODES --- +// +// Use the standard USB HID keyboard codes for regular keys. See the +// HID Usage Tables in the official USB specifications for a full list. +// Here are the most common codes for quick references: +// +// A-Z -> 4-29 +// top row numbers -> 30-39 +// Return -> 40 +// Escape -> 41 +// Backspace -> 42 +// Tab -> 43 +// Spacebar -> 44 +// -_ -> 45 +// =+ -> 46 +// [{ -> 47 +// ]} -> 48 +// \| -> 49 +// ;: -> 51 +// '" -> 52 +// `~ -> 53 +// ,< -> 54 +// .> -> 55 +// /? -> 56 +// Caps Lock -> 57 +// F1-F12 -> 58-69 +// F13-F24 -> 104-115 +// Print Screen -> 70 +// Scroll Lock -> 71 +// Pause -> 72 +// Insert -> 73 +// Home -> 74 +// Page Up -> 75 +// Del -> 76 +// End -> 77 +// Page Down -> 78 +// Right Arrow -> 79 +// Left Arrow -> 80 +// Down Arrow -> 81 +// Up Arrow -> 82 +// Num Lock/Clear -> 83 +// Keypad / * - + -> 84 85 86 87 +// Keypad Enter -> 88 +// Keypad 1-9 -> 89-97 +// Keypad 0 -> 98 +// Keypad . -> 99 +// + + +// --- USB KEYBOARD MODIFIER KEY CODES --- +// +// Use these codes for modifier keys in the button mappings +// +// 0x01 = Left Control +// 0x02 = Left Shift +// 0x04 = Left Alt +// 0x08 = Left GUI ("Windows" key) +// 0x10 = Right Control +// 0x20 = Right Shift +// 0x40 = Right Alt +// 0x80 = Right GUI ("Windows" key) + + +// --- USB KEYBOARD MEDIA KEY CODES --- +// +// Use these for media control keys in the button mappings +// +// 0x01 = Volume Up +// 0x02 = Volume Down +// 0x04 = Mute on/off +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Updates.h Sat Dec 19 06:37:19 2015 +0000 @@ -0,0 +1,32 @@ +// UPDATES +// +// This is a record of new features and changes in recent versions. +// + +// January 2016 +// +// Dynamic configuration: all configuration options are now handled dynamically, +// through the Windows config tool. In earlier versions, most configuration options +// were set through compile-time constants, which made it necessary for everyone +// who wanted to customize anything to create a private branched version of the +// source repository, edit the source code, and compile their own binary. This +// was cumbersome, and required way too much technical knowledge to be worth the +// trouble to a lot of people. The goal of the new approach is that everyone can +// use the same standard binary build, and set options from the Windows tool. +// +// TSL1410R and 1412R parallel mode support: these sensors are physically built +// out of two separate pixel arrays, which can be read independently. Past +// versions only supported "serial" mode pixel transfer, where we read all of +// the first array's pixels before reading any of the second array's pixels. +// In parallel mode, we can read pixels from both arrays at the same time. The +// limiting factor in image read speed is the amount of time it takes for the +// ADC to transfer charge from a pixel and stabilize on a reading. The KL25Z +// has multiple ADC hardware channels, so we can read multiple analog values +// concurrently - it takes the same amount of time for one ADC reading to +// stabilize as two readings. So by reading from the two sensor sections +// concurrently, we can essentially double the transfer speed. Faster pixel +// transfer allows for more accurate motion tracking when the plunger is +// moving at high speed, allowing for more realistic plunger action on the +// virtual side. +// +//
--- a/ccdSensor.h Thu Dec 03 07:34:57 2015 +0000 +++ b/ccdSensor.h Sat Dec 19 06:37:19 2015 +0000 @@ -1,24 +1,40 @@ // CCD plunger sensor // -// This file implements our generic plunger sensor interface for the -// TAOS TSL1410R CCD array sensor. +// This class implements our generic plunger sensor interface for the +// TAOS TSL1410R and TSL1412R linear sensor arrays. Physically, these +// sensors are installed with their image window running parallel to +// the plunger rod, spanning the travel range of the plunger tip. +// A light source is positioned on the opposite side of the rod, so +// that the rod casts a shadow on the sensor. We sense the position +// by looking for the edge of the shadow. +// +// These sensors can take an image quickly, but it takes a significant +// amount of time to transfer the image data from the sensor to the +// microcontroller, since each pixel's analog voltage level must be +// sampled serially. It takes about 20us to sample a pixel accurately. +// The TSL1410R has 1280 pixels, and the 1412R has 1536. Sampling +// every pixel would thus take about 25ms or 30ms respectively. +// This is too slow for a responsive feel in the UI, and much too +// slow to track the plunger release motion in real time. To improve +// on the read speed, we only sample a subset of pixels for each +// reading - for higher speed at the expense of spatial resolution. +// The sensor's native resolution is much higher than we need, so +// this is a perfectly equitable trade. +#include "plunger.h" -// Number of pixels we read from the CCD on each frame. Use the -// sample size from config.h. -const int npix = CCD_NPIXELS_SAMPLED; - // PlungerSensor interface implementation for the CCD -class PlungerSensor +class PlungerSensorCCD: public PlungerSensor { public: - PlungerSensor() : ccd(CCD_SO_PIN) + PlungerSensorCCD(int nPix, PinName si, PinName clock, PinName ao1, PinName ao2) + : ccd(nPix, si, clock, ao1, ao2) { } // initialize - void init() + virtual void init() { // flush any random power-on values from the CCD's integration // capacitors, and start the first integration cycle @@ -26,9 +42,8 @@ } // Perform a low-res scan of the sensor. - int lowResScan() + virtual bool lowResScan(int &pos) { - // read the pixels at low resolution const int nlpix = 32; uint16_t pix[nlpix]; @@ -53,17 +68,17 @@ { // got it - normalize it to normal 'npix' resolution and // return the result - return n*npix/nlpix; + pos = n*npix/nlpix; + return true; } } - // didn't find a shadow - assume the whole array is in shadow (so - // the edge is at the zero pixel point) - return 0; + // didn't find a shadow - return failure + return false; } // Perform a high-res scan of the sensor. - bool highResScan(int &pos) + virtual bool highResScan(int &pos) { // read the array ccd.read(pix, npix); @@ -127,7 +142,7 @@ } // send an exposure report to the joystick interface - void sendExposureReport(USBJoystick &js) + virtual void sendExposureReport(USBJoystick &js) { // send reports for all pixels int idx = 0; @@ -147,10 +162,44 @@ ccd.read(pix, npix); } -private: +protected: // pixel buffer - uint16_t pix[npix]; + uint16_t *pix; // the low-level interface to the CCD hardware - TSL1410R<CCD_SI_PIN, CCD_CLOCK_PIN> ccd; + TSL1410R ccd; }; + + +// TSL1410R sensor +class PlungerSensorTSL1410R: public PlungerSensorCCD +{ +public: + PlungerSensorTSL1410R(PinName si, PinName clock, PinName ao1, PinName ao2) + : PlungerSensorCCD(1280, si, clock, ao1, ao2) + { + // This sensor is 1x1280 pixels at 400dpi. Sample every 8th + // pixel -> 160 pixels at 50dpi == 0.5mm spatial resolution. + npix = 160; + pix = pixbuf; + } + + uint16_t pixbuf[160]; +}; + +// TSL1412R +class PlungerSensorTSL1412R: public PlungerSensorCCD +{ +public: + PlungerSensorTSL1412R(PinName si, PinName clock, PinName ao1, PinName ao2) + : PlungerSensorCCD(1536, si, clock, ao1, ao2) + { + // This sensor is 1x1536 pixels at 400dpi. Sample every 8th + // pixel -> 192 pixels at 50dpi == 0.5mm spatial resolution. + npix = 192; + pix = pixbuf; + } + + uint16_t pixbuf[192]; +}; +
--- a/config.h Thu Dec 03 07:34:57 2015 +0000 +++ b/config.h Sat Dec 19 06:37:19 2015 +0000 @@ -1,916 +1,346 @@ // Pinscape Controller Configuration // -// To customize your private configuration, simply open this file in the -// mbed on-line IDE, make your changes, save the file, and click the Compile -// button at the top of the window. That will generate a customized .bin -// file that you can download onto your KL25Z board. +// New for 2016: dynamic configuration! To configure the controller, connect +// the KL25Z to your PC, install the .bin file, and run the Windows config tool. +// There's no need (as there was in the past) to edit this file or to compile a +// custom version of the binary (.bin) to customize setup options. +// +// In earlier versions, configuration was largely handled with compile-time +// constants. To customize the setup, you had to create a private forked copy +// of the source code, edit the constants defined in config.h, and compile a +// custom binary. That's no longer necessary! +// +// The new approach is to do everything (or as much as possible, anyway) +// via the Windows config tool. You shouldn't have to recompile a custom +// version just to make a configurable change. Of course, you're still free +// to create a custom version if you need to add or change features in ways +// that weren't anticipated in the original design. +// + #ifndef CONFIG_H #define CONFIG_H -// --------------------------------------------------------------------------- -// -// Expansion Board. If you're using the expansion board, un-comment the -// line below. This will select all of the correct defaults for the board. -// -// The expansion board settings are mostly automatic, so you shouldn't have -// to change much else. However, you should still look at and adjust the -// following as needed: -// - TV power on delay time -// - Plunger sensor settings, if you're using a plunger -// -//#define EXPANSION_BOARD - -// -------------------------------------------------------------------------- -// -// Enable/disable joystick functions. -// -// This controls whether or not we send joystick reports to the PC with the -// plunger and accelerometer readings. By default, this is enabled. If -// you want to use two or more physical KL25Z Pinscape controllers in your -// system (e.g., if you want to increase the number of output ports -// available by using two or more KL25Z's), you should disable the joystick -// features on the second (and third+) controller. It's not useful to have -// more than one board reporting the accelerometer readings to the host - -// doing so will just add USB overhead. This setting lets you turn off the -// reports for the secondary controllers, turning the secondary boards into -// output-only devices. -// -// Note that you can't use button inputs on a controller that has the -// joystick features disabled, because the buttons are handled via the -// joystick reports. Wire all of your buttons to the primary KL25Z that -// has the joystick features enabled. -// -// To disable the joystick features, just comment out the next line (add -// two slashes at the beginning of the line). -// -#define ENABLE_JOYSTICK - +// Plunger type codes +// NOTE! These values are part of the external USB interface. New +// values can be added, but the meaning of an existing assigned number +// should remain fixed to keep the PC-side config tool compatible across +// versions. +const int PlungerType_None = 0; // no plunger +const int PlungerType_TSL1410RS = 1; // TSL1410R linear image sensor (1280x1 pixels, 400dpi), serial mode +const int PlungerType_TSL1410RP = 2; // TSL1410R, parallel mode (reads the two sensor sections concurrently) +const int PlungerType_TSL1412RS = 3; // TSL1412R linear image sensor (1536x1 pixels, 400dpi), serial mode +const int PlungerType_TSL1412RP = 4; // TSL1412R, parallel mode +const int PlungerType_Pot = 5; // potentionmeter +const int PlungerType_OptQuad = 6; // AEDR8300 optical quadrature sensor +const int PlungerType_MagQuad = 7; // AS5304 magnetic quadrature sensor -// --------------------------------------------------------------------------- -// -// USB device vendor ID and product ID. These values identify the device -// to the host software on the PC. By default, we use the same settings as -// a real LedWiz so that host software will recognize us as an LedWiz. -// -// The standard settings *should* work without conflicts, even if you have -// a real LedWiz. My reference system is 64-bit Windows 7 with a real LedWiz -// on unit #1 and a Pinscape controller on unit #8 (the default), and the -// two coexist happily in my system. The LedWiz is designed specifically -// to allow multiple units in one system, using the unit number value -// (see below) to distinguish multiple units, so there should be no conflict -// between Pinscape and any real LedWiz devices you have. -// -// However, even though conflicts *shouldn't* happen, I've had one report -// from a user who experienced a Windows USB driver conflict that they could -// only resolve by changing the vendor ID. The real underlying cause is -// still a mystery, but whatever was going on, changing the vendor ID fixed -// it. If you run into a similar problem, you can try the same fix as a -// last resort. Before doing that, though, you should try changing the -// Pinscape unit number first - it's possible that your real LedWiz is using -// unit #8, which is our default setting. -// -// If you must change the vendor ID for any reason, you'll sacrifice LedWiz -// compatibility, which means that old programs like Future Pinball that use -// the LedWiz interface directly won't be able to access the LedWiz output -// controller features. However, all is not lost. All of the other functions -// (plunger, nudge, and key input) use the joystick interface, which will -// work regardless of the ID values. In addition, DOF R3 recognizes the -// "emergency fallback" ID below, so if you use that, *all* functions -// including the output controller will work in any DOF R3-enabled software, -// including Visual Pinball and PinballX. So the only loss will be that -// old LedWiz-only software won't be able to control the outputs. -// -// The "emergency fallback" ID below is officially registerd with -// http://pid.codes, a registry for open-source USB projects, which should -// all but guarantee that this alternative ID shouldn't conflict with -// any other devices in your system. +// Accelerometer orientation codes +// These values are part of the external USB interface +const int OrientationFront = 0; // USB ports pointed toward front of cabinet +const int OrientationLeft = 1; // ports pointed toward left side of cabinet +const int OrientationRight = 2; // ports pointed toward right side of cabinet +const int OrientationRear = 3; // ports pointed toward back of cabinet - -// STANDARD ID SETTINGS. These provide full, transparent LedWiz compatibility. -const uint16_t USB_VENDOR_ID = 0xFAFA; // LedWiz vendor ID = FAFA -const uint16_t USB_PRODUCT_ID = 0x00F0; // LedWiz start of product ID range = 00F0 - +// input button types +const int BtnTypeJoystick = 1; // joystick button +const int BtnTypeKey = 2; // regular keyboard key +const int BtnTypeModKey = 3; // keyboard modifier key (shift, ctrl, etc) +const int BtnTypeMedia = 4; // media control key (volume up/down, etc) -// EMERGENCY FALLBACK ID SETTINGS. These settings are not LedWiz-compatible, -// so older LedWiz-only software won't be able to access the output controller -// features. However, DOF R3 recognizes these IDs, so DOF-aware software (Visual -// Pinball, PinballX) will have full access to all features. -// -//const uint16_t USB_VENDOR_ID = 0x1209; // DOF R3-compatible vendor ID = 1209 -//const uint16_t USB_PRODUCT_ID = 0xEAEA; // DOF R3-compatible product ID = EAEA - +// maximum number of input button mappings +const int MAX_BUTTONS = 32; -// --------------------------------------------------------------------------- -// -// LedWiz unit number. -// -// Each LedWiz device has a unit number, from 1 to 16. This lets you install -// more than one LedWiz in your system: as long as each one has a different -// unit number, the software on the PC can tell them apart and route commands -// to the right device. -// -// A real LedWiz has its unit number set at the factory. If you don't tell -// them otherwise when placing your order, they will set it to unit #1. Most -// real LedWiz units therefore are set to unit #1. There's no provision on -// a real LedWiz for users to change the unit number after it leaves the -// factory. -// -// For our *emulated* LedWiz, we default to unit #8 if we're the primary -// Pinscape controller in the system, or unit #9 if we're set up as the -// secondary controller with the joystick functions turned off. -// -// The reason we start at unit #8 is that we want to avoid conflicting with -// any real LedWiz devices in your system. Most real LedWiz devices are -// set up as unit #1, and in the rare cases where people have two of them, -// the second one is usually unit #2. -// -// Note 1: the unit number here is the *user visible* unit number that -// you use on the PC side. It's the number you specify in your DOF -// configuration and so forth. Internally, the USB reports subtract -// one from this number - e.g., nominal unit #1 shows up as 0 in the USB -// reports. If you're trying to puzzle out why all of the USB reports -// are all off by one from the unit number you select here, that's why. -// -// Note 2: the DOF Configtool (google it) knows about the Pinscape -// controller. There it's referred to as simply "KL25Z" rather than -// Pinscape Controller, but that's what they're talking about. The DOF -// tool knows that it uses #8 as its default unit number, so it names the -// .ini file for this controller xxx8.ini. If you change the unit number -// here, remember to rename the DOF-generated .ini file to match, by -// changing the "8" at the end of the filename to the new number you set -// here. -const uint8_t DEFAULT_LEDWIZ_UNIT_NUMBER = -#ifdef ENABLE_JOYSTICK - 0x08; // joystick enabled - assume we're the primary KL25Z, so use unit #8 -#else - 0x09; // joystick disabled - assume we're a secondary, output-only KL25Z, so use #9 -#endif +// LedWiz output port type codes +// These values are part of the external USB interface +const int PortTypeDisabled = 0; // port is disabled - not visible to LedWiz/DOF host +const int PortTypeGPIOPWM = 1; // GPIO port, PWM enabled +const int PortTypeGPIODig = 2; // GPIO port, digital out +const int PortTypeTLC5940 = 3; // TLC5940 port +const int PortType74HC595 = 4; // 74HC595 port +const int PortTypeVirtual = 5; // Virtual port - visible to host software, but not connected to a physical output +// LedWiz output port flag bits +const uint8_t PortFlagActiveLow = 0x01; // physical output is active-low + +// maximum number of output ports +const int MAX_OUT_PORTS = 203; -// -------------------------------------------------------------------------- -// -// Accelerometer orientation. The accelerometer feature lets Visual Pinball -// (and other pinball software) sense nudges to the cabinet, and simulate -// the effect on the ball's trajectory during play. We report the direction -// of the accelerometer readings as well as the strength, so it's important -// for VP and the KL25Z to agree on the physical orientation of the -// accelerometer relative to the cabinet. The accelerometer on the KL25Z -// is always mounted the same way on the board, but we still have to know -// which way you mount the board in your cabinet. We assume as default -// orientation where the KL25Z is mounted flat on the bottom of your -// cabinet with the USB ports pointing forward, toward the coin door. If -// it's more convenient for you to mount the board in a different direction, -// you simply need to select the matching direction here. Comment out the -// ORIENTATION_PORTS_AT_FRONT line and un-comment the line that matches -// your board's orientation. - -#define ORIENTATION_PORTS_AT_FRONT // USB ports pointing toward front of cabinet -// #define ORIENTATION_PORTS_AT_LEFT // USB ports pointing toward left side of cab -// #define ORIENTATION_PORTS_AT_RIGHT // USB ports pointing toward right side of cab -// #define ORIENTATION_PORTS_AT_REAR // USB ports pointing toward back of cabinet - - - -// -------------------------------------------------------------------------- -// -// Plunger CCD sensor. -// -// If you're NOT using the CCD sensor, comment out the next line (by adding -// two slashes at the start of the line). - -#define ENABLE_CCD_SENSOR - -// Physical pixel count for your sensor. This software has been tested with -// TAOS TSL1410R (1280 pixels) and TSL1412R (1536 pixels) sensors. It might -// work with other similar sensors as well, but you'll probably have to make -// some changes to the software interface to the sensor if you're using any -// sensor outside of the TAOS TSL14xxR series. -// -// If you're not using a CCD sensor, you can ignore this. -const int CCD_NPIXELS = 1280; - -// Number of pixels from the CCD to sample on each high-res scan. We don't -// sample every pixel from the sensor on each scan, because (a) we don't -// have to, and (b) we don't want to. We don't have to sample all of the -// pixels because these sensors have much finer resolution than we need to -// get good results. On a typical pinball cabinet setup with a 1920x1080 -// HD TV display, the on-screen plunger travel distance is about 165 pixels, -// so that's all the pixels we need to sample for pixel-accurate animation. -// Even so, we still *could* sample at higher resolution, but we don't *want* -// to sample more pixels than we have to, because reading each pixel takes -// time. The limiting factor for read speed is the sampling time for the ADC -// (analog to digital converter); it needs about 20us per sample to get an -// accurate voltage reading. We want to animate the on-screen plunger in -// real time, with minimal lag, so it's important that we complete each scan -// as quickly as possible. The fewer pixels we sample, the faster we -// complete each scan. -// -// Happily, the time needed to read the approximately 165 pixels required -// for pixel-accurate positioning on the display is short enough that we can -// complete a scan within the cycle time for USB reports. Visual Pinball -// only polls for input at about 10ms intervals, so there's no benefit -// to going much faster than this. The sensor timing is such that we can -// read about 165 pixels in well under 10ms. So that's really the sweet -// spot for our scans. -// -// Note that we distribute the sampled pixels evenly across the full range -// of the sensor's pixels. That is, we read every nth pixel, and skip the -// ones in between. That means that the sample count here has to be an even -// divisor of the physical pixel count. Empirically, reading every 8th -// pixel gives us good results on both the TSL1410R and TSL1412R, so you -// shouldn't need to change this if you're using one of those sensors. If -// you're using a different sensor, you should be sure to adjust this so that -// it works out to an integer result with no remainder. -// -const int CCD_NPIXELS_SAMPLED = CCD_NPIXELS / 8; +struct Config +{ + // set all values to factory defaults + void setFactoryDefaults() + { + // By default, pretend to be LedWiz unit #8. This can be from 1 to 16. Real + // LedWiz units have their unit number set at the factory, and the vast majority + // are set up as unit #1, since that's the default for anyone who doesn't ask + // for a different setting. It seems rare for anyone to use more than one unit + // in a pin cab, but for the few who do, the others will probably be numbered + // sequentially as #2, #3, etc. It seems safe to assume that no one out there + // has a unit #8, so we'll use that as our default starting number. This can + // be changed from the config tool, but for the sake of convenience we want the + // default to be a value that most people won't have to change. + usbVendorID = 0xFAFA; // LedWiz vendor code + usbProductID = 0x00F7; // LedWiz product code for unit #8 + psUnitNo = 8; + + // enable joystick reports + joystickEnabled = true; + + // assume standard orientation, with USB ports toward front of cabinet + orientation = OrientationFront; -// The KL25Z pins that the CCD sensor is physically attached to: -// -// CCD_SI_PIN = the SI (sensor data input) pin -// CCD_CLOCK_PIN = the sensor clock pin -// CCD_SO_PIN = the SO (sensor data output) pin -// -// The SI an Clock pins are DigitalOut pins, so these can be set to just -// about any gpio pins that aren't used for something else. The SO pin must -// be an AnalogIn capable pin - only a few of the KL25Z gpio pins qualify, -// so check the pinout diagram to find suitable candidates if you need to -// change this. Note that some of the gpio pins shown in the mbed pinout -// diagrams are committed to other uses by the mbed software or by the KL25Z -// wiring itself, so if you do change these, be sure that the new pins you -// select are really available. - -const PinName CCD_SI_PIN = PTE20; -const PinName CCD_CLOCK_PIN = PTE21; -const PinName CCD_SO_PIN = PTB0; - -// -------------------------------------------------------------------------- -// -// Plunger potentiometer sensor. -// -// If you're using a potentiometer as the plunger sensor, un-comment the -// next line (by removing the two slashes at the start of the line), and -// also comment out the ENABLE_CCD_SENSOR line above. - -//#define ENABLE_POT_SENSOR - -// The KL25Z pin that your potentiometer is attached to. The potentiometer -// requires wiring three connectins: -// -// - Wire the fixed resistance end of the potentiometer nearest the KNOB -// end of the plunger to the 3.3V output from the KL25Z -// -// - Wire the other fixed resistance end to KL25Z Ground -// -// - Wire the potentiometer wiper (the variable output terminal) to the -// KL25Z pin identified below. -// -// Note that you can change the pin selection below, but if you do, the new -// pin must be AnalogIn capable. Only a few of the KL25Z pins qualify. Refer -// to the KL25Z pinout diagram to find another AnalogIn pin if you need to -// change this for any reason. Note that the default is to use the same analog -// input that the CCD sensor would use if it were enabled, which is why you -// have to be sure to disable the CCD support in the software if you're using -// a potentiometer as the sensor. - -const PinName POT_PIN = PTB0; - -// -------------------------------------------------------------------------- -// -// Plunger calibration button and indicator light. -// -// These specify the pin names of the plunger calibration button connections. -// If you're not using these, you can set these to NC. (You can even use the -// button but not the LED; set the LED to NC if you're only using the button.) -// -// If you're using the button, wire one terminal of a momentary switch or -// pushbutton to the input pin you select, and wire the other terminal to the -// KL25Z ground. Push and hold the button for a few seconds to enter plunger -// calibration mode. -// -// If you're using the LED, you'll need to build a little transistor power -// booster circuit to power the LED, as described in the build guide. The -// LED gives you visual confirmation that the you've triggered calibration -// mode and lets you know when the mode times out. Note that the LED on -// board the KL25Z also changes color to indicate the same information, so -// if the KL25Z is positioned so that you can see it while you're doing the -// calibration, you don't really need a separate button LED. But the -// separate LED is spiffy, especially if it's embedded in the pushbutton. -// -// Note that you can skip the pushbutton altogether and trigger calibration -// from the Windows control software. But again, the button is spiffier. - -// calibration button input -const PinName CAL_BUTTON_PIN = PTE29; - -// calibration button indicator LED -const PinName CAL_BUTTON_LED = PTE23; - - -// --------------------------------------------------------------------------- -// -// TV Power-On Timer. This section lets you set up a delayed relay timer -// for turning on your TV monitor(s) shortly after you turn on power to the -// system. This requires some external circuitry, which is built in to the -// expansion board, or which you can build yourself - refer to the Build -// Guide for the circuit plan. -// -// If you're using this feature, un-comment the next line, and make any -// changes to the port assignments below. The default port assignments are -// suitable for the expansion board. Note that the TV timer is enabled -// automatically if you're using the expansion board, since it's built in. -//#define ENABLE_TV_TIMER - -#if defined(ENABLE_TV_TIMER) || defined(EXPANSION_BOARD) -# define PSU2_STATUS_SENSE PTD2 // Digital In pin to read latch status -# define PSU2_STATUS_SET PTE0 // Digital Out pin to set latch -# define TV_RELAY_PIN PTD3 // Digital Out pin to control TV switch relay - -// Amount of time (in seconds) to wait after system power-up before -// pulsing the TV ON switch relay. Adjust as needed for your TV(s). -// Most monitors won't respond to any buttons for the first few seconds -// after they're plugged in, so we need to wait long enough to make sure -// the TVs are ready to receive input before pressing the button. -#define TV_DELAY_TIME 7.0 - -#endif - + // assume no plunger is attached + plunger.enabled = false; + plunger.sensorType = PlungerType_None; + + // assume that there's no calibration button + plunger.cal.btn = NC; + plunger.cal.led = NC; + + // clear the plunger calibration + plunger.cal.reset(4096); + + // disable the ZB Launch Ball by default + plunger.zbLaunchBall.port = 0; + plunger.zbLaunchBall.btn = 0; + + // assume no TV ON switch + TVON.statusPin = NC; + TVON.latchPin = NC; + TVON.relayPin = NC; + TVON.delayTime = 0; + + // assume no TLC5940 chips + tlc5940.nchips = 0; + + // assume no 74HC595 chips + hc595.nchips = 0; + + // initially configure with no LedWiz output ports + outPort[0].typ = PortTypeDisabled; + + // initially configure with no input buttons + for (int i = 0 ; i < MAX_BUTTONS ; ++i) + button[i].pin = 0; // 0 == index of NC in USB-to-PinName mapping + + button[0].pin = 6; // PTA13 + button[0].typ = BtnTypeKey; + button[0].val = 4; // A + button[1].pin = 38; // PTD5 + button[1].typ = BtnTypeJoystick; + button[1].val = 5; // B + button[2].pin = 37; // PTD4 + button[2].typ = BtnTypeModKey; + button[2].val = 0x02; // left shift + button[3].pin = 5; // PTA12 + button[3].typ = BtnTypeMedia; + button[3].val = 0x01; // volume up + button[4].pin = 3; // PTA4 + button[4].typ = BtnTypeMedia; + button[4].val = 0x02; // volume down + } + + // --- USB DEVICE CONFIGURATION --- + + // USB device identification - vendor ID and product ID. For LedLWiz + // emulation, use vendor ID 0xFAFA and product ID 0x00EF + unit#, where + // unit# is the nominal LedWiz unit number from 1 to 16. Alternatively, + // if LedWiz emulation isn't desired or causes any driver conflicts on + // the host, we have a private Pinscape assignment as vendor ID 0x1209 + // and product ID 0xEAEA (registered with http://pid.codes, a registry + // for open-source USB projects). + uint16_t usbVendorID; + uint16_t usbProductID; + + // Pinscape Controller unit number. This is the nominal unit number, + // from 1 to 16. We report this in the status query; DOF uses it to + // distinguish multiple Pinscape units. Note that this doesn't affect + // the LedWiz unit numbering, which is implied by the USB Product ID. + uint8_t psUnitNo; + + // Are joystick reports enabled? Joystick reports can be turned off, to + // use the device as purely an output controller. + char joystickEnabled; + + + // --- ACCELEROMETER --- + + // accelerometer orientation (ORIENTATION_xxx value) + char orientation; + + + // --- PLUNGER CONFIGURATION --- + struct + { + // plunger enabled/disabled + char enabled; -// -------------------------------------------------------------------------- -// -// Pseudo "Launch Ball" button. -// -// Zeb of zebsboards.com came up with a clever scheme for his plunger kit -// that lets the plunger simulate a Launch Ball button for tables where -// the original used a Launch button instead of a plunger (e.g., Medieval -// Madness, T2, or Star Trek: The Next Generation). The scheme uses an -// LedWiz output to tell us when such a table is loaded. On the DOF -// Configtool site, this is called "ZB Launch Ball". When this LedWiz -// output is ON, it tells us that the table will ignore the analog plunger -// because it doesn't have a plunger object, so the analog plunger should -// send a Launch Ball button press signal when the user releases the plunger. -// -// If you wish to use this feature, you need to do two things: -// -// First, adjust the two lines below to set the LedWiz output and joystick -// button you wish to use for this feature. The defaults below should be -// fine for most people, but if you're using the Pinscape controller for -// your physical button wiring, you should set the launch button to match -// where you physically wired your actual Launch Ball button. Likewise, -// change the LedWiz port if you're using the one below for some actual -// hardware output. This is a virtual port that won't control any hardware; -// it's just for signaling the plunger that we're in "button mode". Note -// that the numbering for the both the LedWiz port and joystick button -// start at 1 to match the DOF Configtool and VP dialog numbering. -// -// Second, in the DOF Configtool, make sure you have a Pinscape controller -// in your cabinet configuration, then go to your Port Assignments and set -// the port defined below to "ZB Launch Ball". -// -// Third, open the Visual Pinball editor, open the Preferences | Keys -// dialog, and find the Plunger item. Open the drop-down list under that -// item and select the button number defined below. -// -// To disable this feature, just set ZBLaunchBallPort to 0 here. - -const int ZBLaunchBallPort = 32; -const int LaunchBallButton = 24; + // plunger sensor type + char sensorType; + + // Plunger sensor pins. To accommodate a wide range of sensor types, + // we keep a generic list of 4 pin assignments. The use of each pin + // varies by sensor. The lists below are in order of the generic + // pins; NC means that the pin isn't used by the sensor. Each pin's + // GPIO usage is also listed. Certain usages limit which physical + // pins can be assigned (e.g., AnalogIn or PwmOut). + // + // TSL1410R/1412R, serial: SI (DigitalOut), CLK (DigitalOut), AO (AnalogIn), NC + // TSL1410R/1412R, parallel: SI (DigitalOut), CLK (DigitalOut), AO1 (AnalogIn), AO2 (AnalogIn) + // Potentiometer: AO (AnalogIn), NC, NC, NC + // AEDR8300: A (InterruptIn), B (InterruptIn), NC, NC + // AS5304: A (InterruptIn), B (InterruptIn), NC, NC + PinName sensorPin[4]; + + // Pseudo LAUNCH BALL button. + // + // This configures the "ZB Launch Ball" feature in DOF, based on Zeb's (of + // zebsboards.com) scheme for using a mechanical plunger as a Launch button. + // Set the port to 0 to disable the feature. + // + // The port number is an LedWiz port number that we monitor for activation. + // This port isn't connected to a physical device; rather, the host turns it + // on to indicate that the pseudo Launch button mode is in effect. + // + // The button number gives the button that we "press" when a launch occurs. + // This can be connected to the physical Launch button, or can simply be + // an otherwise unused button. + // + // The "push distance" is the distance, in inches, for registering a push + // on the plunger as a button push. If the player pushes the plunger forward + // of the rest position by this amount, we'll treat it as pushing the button, + // even if the player didn't pull back the plunger first. This lets the + // player treat the plunger knob as a button for games where it's meaningful + // to hold down the Launch button for specific intervals (e.g., "Championship + // Pub"). + struct + { + int port; + int btn; + float pushDistance; + + } zbLaunchBall; + + // --- PLUNGER CALIBRATION --- + struct + { + // has the plunger been calibrated? + int calibrated; + + // calibration button switch pin + PinName btn; + + // calibration button indicator light pin + PinName led; + + // Plunger calibration min, zero, and max. The zero point is the + // rest position (aka park position), where it's in equilibrium between + // the main spring and the barrel spring. It can travel a small distance + // forward of the rest position, because the barrel spring can be + // compressed by the user pushing on the plunger or by the momentum + // of a release motion. The minimum is the maximum forward point where + // the barrel spring can't be compressed any further. + int min; + int zero; + int max; + + // reset the plunger calibration + void reset(int npix) + { + calibrated = 0; // not calibrated + min = 0; // assume we can go all the way forward... + max = npix; // ...and all the way back + zero = npix/6; // the rest position is usually around 1/2" back = 1/6 of total travel + } -// Distance necessary to push the plunger to activate the simulated -// launch ball button, in inches. A standard pinball plunger can be -// pushed forward about 1/2". However, the barrel spring is very -// stiff, and anything more than about 1/8" requires quite a bit -// of force. Ideally the force required should be about the same as -// for any ordinary pushbutton. -// -// On my cabinet, empirically, a distance around 2mm (.08") seems -// to work pretty well. It's far enough that it doesn't trigger -// spuriously, but short enough that it responds to a reasonably -// light push. -// -// You might need to adjust this up or down to get the right feel. -// Alternatively, if you don't like the "push" gesture at all and -// would prefer to only make the plunger respond to a pull-and-release -// motion, simply set this to, say, 2.0 - it's impossible to push a -// plunger forward that far, so that will effectively turn off the -// push mode. -const float LaunchBallPushDistance = .08; + } cal; + } plunger; -// -------------------------------------------------------------------------- -// -// TLC5940 PWM controller chip setup - Enhanced LedWiz emulation -// -// By default, the Pinscape Controller software can provide limited LedWiz -// emulation through the KL25Z's on-board GPIO ports. This lets you hook -// up external devices, such as LED flashers or solenoids, to the KL25Z -// outputs (using external circuitry to boost power - KL25Z GPIO ports -// are limited to a meager 4mA per port). This capability is limited by -// the number of available GPIO ports on the KL25Z, and even smaller limit -// of 10 PWM-capable GPIO ports. -// -// As an alternative, the controller software lets you use external PWM -// controller chips to control essentially unlimited channels with full -// PWM control on all channels. This requires building external circuitry -// using TLC5940 chips. Each TLC5940 chip provides 16 full PWM channels, -// and you can daisy-chain multiple TLC5940 chips together to set up 32, -// 48, 64, or more channels. -// -// If you do add TLC5940 circuits to your controller hardware, use this -// section to configure the connection to the KL25Z. -// -// Note that when using the TLC5940, you can still also use some GPIO -// pins for outputs as normal. See ledWizPinMap[] for + + // --- TV ON SWITCH --- + // + // To use the TV ON switch feature, the special power sensing circuitry + // implemented on the Expansion Board must be attached (or an equivalent + // circuit, as described in the Build Guide). The circuitry lets us + // detect power state changes on the secondary power supply. + struct + { + // PSU2 power status sense (DigitalIn pin). This pin goes LOW when the + // secondary power supply is turned off, and remains LOW until the LATCH + // pin is raised high AND the secondary PSU is turned on. Once HIGH, + // it remains HIGH as long as the secondary PSU is on. + PinName statusPin; + + // PSU2 power status latch (DigitalOut pin) + PinName latchPin; + + // TV ON relay pin (DigitalOut pin). This pin controls the TV switch + // relay. Raising the pin HIGH turns the relay ON (energizes the coil). + PinName relayPin; + + // TV ON delay time, in seconds. This is the interval between sensing + // that the secondary power supply has turned on and pulsing the TV ON + // switch relay. + float delayTime; + + } TVON; + -// Number of TLC5940 chips you're using. For a full LedWiz-compatible -// setup, you need two of these chips, for 32 outputs. The software -// will handle up to 8. -// If you're using the expansion board, the main KL25Z interface board -// has 2 chips and the MOSFET board has 2 more, for a total of 4. If -// you add extra daisy-chained MOSFET boards, add 2 more per board. -#ifdef EXPANSION_BOARD -# define TLC5940_NCHIPS 4 -#else -# define TLC5940_NCHIPS 0 // change this if you're using TLC5940's without the expansion board -#endif + // --- TLC5940NT PWM Controller Chip Setup --- + struct + { + // number of TLC5940NT chips connected in daisy chain + int nchips; + + // pin connections + PinName sin; // Serial data - must connect to SPIO MOSI -> PTC6 or PTD2 + PinName sclk; // Serial clock - must connect to SPIO SCLK -> PTC5 or PTD1 + // (but don't use PTD1, since it's hard-wired to the on-board blue LED) + PinName xlat; // XLAT (latch) signal - connect to any GPIO pin + PinName blank; // BLANK signal - connect to any GPIO pin + PinName gsclk; // Grayscale clock - must connect to a PWM-out capable pin -// If you're using TLC5940s, change any of these as needed to match the -// GPIO pins that you connected to the TLC5940 control pins. Note that -// SIN and SCLK *must* be connected to the KL25Z SPI0 MOSI and SCLK -// outputs, respectively, which effectively limits them to the default -// selections, and that the GSCLK pin must be PWM-capable. These defaults -// all match the expansion board wiring. -#define TLC5940_SIN PTC6 // Serial data - Must connect to SPI0 MOSI -> PTC6 or PTD2 -#define TLC5940_SCLK PTC5 // Serial clock - Must connect to SPI0 SCLK -> PTC5 or PTD1, - // but don't use PTD1 because it's hard-wired to the on-board - // blue LED -#define TLC5940_XLAT PTC10 // XLAT (latch) signal - Any GPIO pin can be used -#define TLC5940_BLANK PTC7 // BLANK signal - Any GPIO pin can be used -#define TLC5940_GSCLK PTA1 // Grayscale clock - Must be a PWM-capable pin + } tlc5940; + + + // --- 74HC595 Shift Register Setup --- + struct + { + // number of 74HC595 chips attached in daisy chain + int nchips; + + // pin connections + PinName sin; // Serial data - use any GPIO pin + PinName sclk; // Serial clock - use any GPIO pin + PinName latch; // Latch - use any GPIO pin + PinName ena; // Enable signal - use any GPIO pin + + } hc595; -// -------------------------------------------------------------------------- -// -// 74HC595 digital output setup - "Chime Board" module -// -// The 74HC595 is an 8-output serial-to-parallel shift register IC. This lets -// us add extra digital outputs (on/off only, not PWM), 8 at a time, similar -// to the way the TLC5940 lets us add extra PWM outputs. The 74HC595 requires -// four control signals, so one chip gives us 8 outputs using only 4 GPIOs. -// The chips can be daisy-chained, so by adding multiple chips, we can add -// any number of new outputs, still using only 4 GPIO pins for the whole chain. -// -// The TLC5940 is more useful for general-purpose outputs because of its PWM -// capabilities, but digital-only outputs are better for some special cases. -// -// The Expansion Board "Chime" module uses these chips to add timer-protected -// outputs. The timer triggers are edge-sensitive, so we want simple on/off -// signals to control them; a PWM signal wouldn't work properly because it's -// constantly switching on and off even when nominally 100% on. -// - -#define HC595_NCHIPS 0 // Number of chips == number of Chime boards connected -#define HC595_SIN PTA5 // Serial data - use any GPIO pin -#define HC595_SCLK PTA4 // Serial clock - use any GPIO pin -#define HC595_LATCH PTA12 // Latch signal - use any GPIO pin -#define HC595_ENA PTD4 // Enable signal - use any GPIO pin - - -#endif // CONFIG_H - end of include-once section (code below this point can be multiply included) - - -#ifdef DECL_EXTERNS // this section defines global variables, only if this macro is set - -// -------------------------------------------------------------------------- -// + // --- Button Input Setup --- + struct + { + uint8_t pin; // physical input GPIO pin - a USB-to-PinName mapping index + uint8_t typ; // key type reported to PC - a BtnTypeXxx value + uint8_t val; // key value reported - meaning depends on 'typ' value + + } button[MAX_BUTTONS]; + -// Joystick button input pin assignments. -// -// You can wire up to 32 GPIO ports to buttons (equipped with -// momentary switches). Connect each switch between the desired -// GPIO port and ground (J9 pin 12 or 14). When the button is pressed, -// we'll tell the host PC that the corresponding joystick button is -// pressed. We debounce the keystrokes in software, so you can simply -// wire directly to pushbuttons with no additional external hardware. -// -// Note that we assign 24 buttons by default, even though the USB -// joystick interface can handle up to 32 buttons. VP itself only -// allows mapping of up to 24 buttons in the preferences dialog -// (although it can recognize 32 buttons internally). If you want -// more buttons, you can reassign pins that are assigned by default -// as LedWiz outputs. To reassign a pin, find the pin you wish to -// reassign in the LedWizPortMap array below, and change the pin name -// there to NC (for Not Connected). You can then change one of the -// "NC" entries below to the reallocated pin name. The limit is 32 -// buttons total. -// -// (If you're using TLC5940 chips to control outputs, many of the -// GPIO pins that are mapped to LedWiz outputs in the default -// mapping can be reassigned as keys, since the TLC5940 outputs -// take over for the GPIO pins. The exceptions are the pins that -// are reassigned to control the TLC5940 chips.) -// -// Note: PTD1 (pin J2-12) should NOT be assigned as a button input, -// as this pin is physically connected on the KL25Z to the on-board -// indicator LED's blue segment. -PinName buttonMap[] = { - PTC2, // J10 pin 10, joystick button 1 - PTB3, // J10 pin 8, joystick button 2 - PTB2, // J10 pin 6, joystick button 3 - PTB1, // J10 pin 4, joystick button 4 - - PTE30, // J10 pin 11, joystick button 5 -#ifdef EXPANSION_BOARD - PTC11, // J1 pin 15, joystick button 6 -#else - PTE22, // J10 pin 5, joystick button 6 -#endif - - PTE5, // J9 pin 15, joystick button 7 - PTE4, // J9 pin 13, joystick button 8 - PTE3, // J9 pin 11, joystick button 9 - PTE2, // J9 pin 9, joystick button 10 - PTB11, // J9 pin 7, joystick button 11 - PTB10, // J9 pin 5, joystick button 12 - PTB9, // J9 pin 3, joystick button 13 - PTB8, // J9 pin 1, joystick button 14 - - PTC12, // J2 pin 1, joystick button 15 - PTC13, // J2 pin 3, joystick button 16 - PTC16, // J2 pin 5, joystick button 17 - PTC17, // J2 pin 7, joystick button 18 - PTA16, // J2 pin 9, joystick button 19 - PTA17, // J2 pin 11, joystick button 20 - PTE31, // J2 pin 13, joystick button 21 - PTD6, // J2 pin 17, joystick button 22 - PTD7, // J2 pin 19, joystick button 23 - - PTE1, // J2 pin 20, joystick button 24 - - NC, // not used, joystick button 25 - NC, // not used, joystick button 26 - NC, // not used, joystick button 27 - NC, // not used, joystick button 28 - NC, // not used, joystick button 29 - NC, // not used, joystick button 30 - NC, // not used, joystick button 31 - NC // not used, joystick button 32 + // --- LedWiz Output Port Setup --- + struct + { + uint8_t typ; // port type: a PortTypeXxx value + uint8_t pin; // physical output pin: for a GPIO port, this is an index in the + // USB-to-PinName mapping list; for a TLC5940 or 74HC595 port, it's + // the output number, starting from 0 for OUT0 on the first chip in + // the daisy chain. For inactive and virtual ports, it's unused. + uint8_t flags; // flags: a combination of PortFlagXxx values + } outPort[MAX_OUT_PORTS]; }; -// -------------------------------------------------------------------------- -// -// LED-Wiz emulation output pin assignments -// -// This sets the mapping from logical LedWiz port numbers, as used -// in the software on the PC side, to physical hardware pins on the -// KL25Z and/or the TLC5940 controllers. -// -// The LedWiz protocol lets the PC software set a "brightness" level -// for each output. This is used to control the intensity of LEDs -// and other lights, and can also control motor speeds. To implement -// the intensity level in hardware, we use PWM, or pulse width -// modulation, which switches the output on and off very rapidly -// to give the effect of a reduced voltage. Unfortunately, the KL25Z -// hardware is limited to 10 channels of PWM control for its GPIO -// outputs, so it's impossible to implement the LedWiz's full set -// of 32 adjustable outputs using only GPIO ports. However, you can -// create 10 adjustable ports and fill out the rest with "digital" -// GPIO pins, which are simple on/off switches. The intensity level -// of a digital port can't be adjusted - it's either fully on or -// fully off - but this is fine for devices that don't have -// different intensity settings anyway, such as replay knockers -// and flipper solenoids. -// -// In the mapping list below, you can decide how to dole out the -// PWM-capable and digital-only GPIO pins. To make it easier to -// remember which is which, the default mapping below groups all -// of the PWM-capable ports together in the first 10 logical LedWiz -// port numbers. Unfortunately, these ports aren't *physically* -// together on the KL25Z pin headers, so this layout may be simple -// in terms of the LedWiz numbering, but it's a little jumbled -// in the physical layout.t -// -// "NC" in the pin name slot means "not connected". This means -// that there's no physical output for this LedWiz port number. -// The device will still accept commands that control the port, -// but these will just be silently ignored, since there's no pin -// to turn on or off for these ports. The reason we leave some -// ports unconnected is that we don't have enough physical GPIO -// pins to fill out the full LedWiz complement of 32 ports. Many -// pins are already taken for other purposes, such as button -// inputs or the plunger CCD interface. -// -// The mapping between physical output pins on the KL25Z and the -// assigned LED-Wiz port numbers is essentially arbitrary. You can -// customize this by changing the entries in the array below if you -// wish to rearrange the pins for any reason. Be aware that some -// of the physical outputs are already used for other purposes -// (e.g., some of the GPIO pins on header J10 are used for the -// CCD sensor - but you can of course reassign those as well by -// changing the corresponding declarations elsewhere in this file). -// The assignments we make here have two main objectives: first, -// to group the outputs on headers J1 and J2 (to facilitate neater -// wiring by keeping the output pins together physically), and -// second, to make the physical pin layout match the LED-Wiz port -// numbering order to the extent possible. There's one big wrench -// in the works, though, which is the limited number and discontiguous -// placement of the KL25Z PWM-capable output pins. This prevents -// us from doing the most obvious sequential ordering of the pins, -// so we end up with the outputs arranged into several blocks. -// Hopefully this isn't too confusing; for more detailed rationale, -// read on... -// -// With the LED-Wiz, the host software configuration usually -// assumes that each RGB LED is hooked up to three consecutive ports -// (for the red, green, and blue components, which need to be -// physically wired to separate outputs to allow each color to be -// controlled independently). To facilitate this, we arrange the -// PWM-enabled outputs so that they're grouped together in the -// port numbering scheme. Unfortunately, these outputs aren't -// together in a single group in the physical pin layout, so to -// group them logically in the LED-Wiz port numbering scheme, we -// have to break up the overall numbering scheme into several blocks. -// So our port numbering goes sequentially down each column of -// header pins, but there are several break points where we have -// to interrupt the obvious sequence to keep the PWM pins grouped -// logically. -// -// In the list below, "pin J1-2" refers to pin 2 on header J1 on -// the KL25Z, using the standard pin numbering in the KL25Z -// documentation - this is the physical pin that the port controls. -// "LW port 1" means LED-Wiz port 1 - this is the LED-Wiz port -// number that you use on the PC side (in the DirectOutput config -// file, for example) to address the port. PWM-capable ports are -// marked as such - we group the PWM-capable ports into the first -// 10 LED-Wiz port numbers. -// -// If you wish to reallocate a pin in the array below to some other -// use, such as a button input port, simply change the pin name in -// the entry to NC (for Not Connected). This will disable the given -// logical LedWiz port number and free up the physical pin. -// -// If you wish to reallocate a pin currently assigned to the button -// input array, simply change the entry for the pin in the buttonMap[] -// array above to NC (for "not connected"), and plug the pin name into -// a slot of your choice in the array below. -// -// Note: Don't assign PTD1 (pin J2-12) as an LedWiz output. That pin -// is hard-wired on the KL25Z to the on-board indicator LED's blue segment, -// which pretty precludes other uses of the pin. -// -// ACTIVE-LOW PORTS: By default, when a logical port is turned on in -// the software, we set the physical GPIO voltage to "high" (3.3V), and -// set it "low" (0V) when the logical port is off. This is the right -// scheme for the booster circuit described in the build guide. Some -// third-party booster circuits want the opposite voltage scheme, where -// logical "on" is represented by 0V on the port and logical "off" is -// represented by 3.3V. If you're using an "active low" booster like -// that, set the PORT_ACTIVE_LOW flag in the array below for each -// affected port. -// -// TLC5940 PORTS: To assign an LedWiz output port number to a particular -// output on a TLC5940, set the port type to TLC_PORT and set the 'pin' -// value to the index of the output port in the daisy chain. The first -// chip in the daisy chain has ports 1-16, the second has ports 17-32, -// and so on. -// -// 74HC595 PORTS: To assign an LedWiz output port to a 74HC595 port, -// set the port type to HC595_PORT and set 'pin' to the index of the port -// in the daisy chain. The first chip has ports 1-8, the second has -// 9-16, etc. -// - -// ledWizPortMap 'typ' values -enum LWPortType { - NO_PORT = -1, // Not connected - DIG_GPIO = 0, // DigitalOut I/O pin (not PWM capable) - PWM_GPIO = 1, // AnalogOut I/O pin (PWM capable) - TLC_PORT = 2, // TLC5940 output port - HC595_PORT = 3 // 74HC595 output port -}; - -// flags - combine with '|' -const int PORT_ACTIVE_LOW = 0x0001; // use LOW voltage (0V) when port is ON - -struct { - int pin; // Pin name/index - PinName for GPIO, pin index for TLC5940 or 74HC595 - LWPortType typ; // type of pin - int flags; // flags - a combination of PORT_xxx flag bits (see above) -} ledWizPortMap[] = { - -#if TLC5940_NCHIPS == 0 - - // *** BASIC MODE - GPIO OUTPUTS ONLY *** - // This is the basic mapping, using entirely GPIO pins, for when you're - // not using external TLC5940 chips. We provide 22 physical outputs, 10 - // of which are PWM capable. - // - // Important! Note that the "isPWM" setting isn't just something we get to - // choose. It's a feature of the KL25Z hardware. Some pins are PWM capable - // and some aren't, and there's nothing we can do about that in the software. - // Refer to the KL25Z manual or schematics for the possible connections. Note - // that there are other PWM-capable pins besides the 10 shown below, BUT they - // all share TPM channels with the pins below. For example, TPM 2.0 can be - // connected to PTA1, PTB2, PTB18, PTE22 - but only one at a time. So if you - // want to use PTB2 as a PWM out, it means you CAN'T use PTA1 as a PWM out. - // We commented each PWM pin with its hardware channel number to help you keep - // track of available channels if you do need to rearrange any of these pins. - - { PTA1, PWM_GPIO }, // pin J1-2, LW port 1 (PWM capable - TPM 2.0 = channel 9) - { PTA2, PWM_GPIO }, // pin J1-4, LW port 2 (PWM capable - TPM 2.1 = channel 10) - { PTD4, PWM_GPIO }, // pin J1-6, LW port 3 (PWM capable - TPM 0.4 = channel 5) - { PTA12, PWM_GPIO }, // pin J1-8, LW port 4 (PWM capable - TPM 1.0 = channel 7) - { PTA4, PWM_GPIO }, // pin J1-10, LW port 5 (PWM capable - TPM 0.1 = channel 2) - { PTA5, PWM_GPIO }, // pin J1-12, LW port 6 (PWM capable - TPM 0.2 = channel 3) - { PTA13, PWM_GPIO }, // pin J2-2, LW port 7 (PWM capable - TPM 1.1 = channel 13) - { PTD5, PWM_GPIO }, // pin J2-4, LW port 8 (PWM capable - TPM 0.5 = channel 6) - { PTD0, PWM_GPIO }, // pin J2-6, LW port 9 (PWM capable - TPM 0.0 = channel 1) - { PTD3, PWM_GPIO }, // pin J2-10, LW port 10 (PWM capable - TPM 0.3 = channel 4) - { PTD2, DIG_GPIO }, // pin J2-8, LW port 11 - { PTC8, DIG_GPIO }, // pin J1-14, LW port 12 - { PTC9, DIG_GPIO }, // pin J1-16, LW port 13 - { PTC7, DIG_GPIO }, // pin J1-1, LW port 14 - { PTC0, DIG_GPIO }, // pin J1-3, LW port 15 - { PTC3, DIG_GPIO }, // pin J1-5, LW port 16 - { PTC4, DIG_GPIO }, // pin J1-7, LW port 17 - { PTC5, DIG_GPIO }, // pin J1-9, LW port 18 - { PTC6, DIG_GPIO }, // pin J1-11, LW port 19 - { PTC10, DIG_GPIO }, // pin J1-13, LW port 20 - { PTC11, DIG_GPIO }, // pin J1-15, LW port 21 - { PTE0, DIG_GPIO }, // pin J2-18, LW port 22 - { NC, NO_PORT }, // Not connected, LW port 23 - { NC, NO_PORT }, // Not connected, LW port 24 - { NC, NO_PORT }, // Not connected, LW port 25 - { NC, NO_PORT }, // Not connected, LW port 26 - { NC, NO_PORT }, // Not connected, LW port 27 - { NC, NO_PORT }, // Not connected, LW port 28 - { NC, NO_PORT }, // Not connected, LW port 29 - { NC, NO_PORT }, // Not connected, LW port 30 - { NC, NO_PORT }, // Not connected, LW port 31 - { NC, NO_PORT } // Not connected, LW port 32 - -#elif defined(EXPANSION_BOARD) - - // *** EXPANSION BOARD MODE *** - // - // This mapping is for the expansion board, which uses four TLC5940 - // chips to provide 64 outputs. The expansion board also uses - // one GPIO pin to provide a digital (non-PWM) output dedicated to - // the knocker circuit. That's on a digital pin because it's used - // to trigger an external timer circuit that limits the amount of - // time that the knocker coil can be continuously energized, to protect - // it against software faults on the PC that leave the port stuck on. - // (The knocker coil is unique among standard virtual cabinet output - // devices in this respect - it's the only device in common use that - // can be damaged if left on for too long. Other devices won't be - // damaged, so they don't require such elaborate precautions.) - // - // The specific device assignments in the last column are just - // recommendations. You can assign any port to any device with - // compatible power needs. The "General Purpose" ports are good to - // at least 5A, so you can use these for virtually anything; put - // your heavy-duty devices, such as solenoids and motors, on these - // outputs. You can also put lighter loads like lamps and LEDs - // on these if you have ports left over after connecting all of - // your high-power devices. The "Flasher" and "Button light" ports - // are good to about 1.5A, so they work for medium loads like lamps, - // flashers, high-power LEDs, etc. The flipper and magnasave ports - // only provide 20mA; use these only for small LEDs. - // - // The TLC5940 outputs on the expansion board are hard-wired to - // specific output drivers - that's what determines the power - // limits described above. You can rearrange the ports in the - // list below to change the LedWiz port numbering to any order - // you prefer, but the association between a TLC5940 port number - // and the output circuit type can't be changed in the software. - // That's a function of how the TLC5940 port is physically wired - // on the board. Likewise, the PTC8 output is hard-wired to the - // knocker time limiter. - // TLC ports 1-20 and 44-47 = Darlington outputs, 1.5A max - // TLC ports 21-44 = MOSFET outputs (limit depends on MOSFET chosen) - // TLC ports 49-64 = direct outputs, limited to 20mA - - // The first 32 ports are LedWiz-compatible, so they're universally - // accessible, even to older non-DOF software. Attach the most common - // devices to these ports. - { 1, TLC_PORT }, // TLC port 1, LW output 1 - Flasher 1 R - { 2, TLC_PORT }, // TLC port 2, LW output 2 - Flasher 1 G - { 3, TLC_PORT }, // TLC port 3, LW output 3 - Flasher 1 B - { 4, TLC_PORT }, // TLC port 4, LW output 4 - Flasher 2 R - { 5, TLC_PORT }, // TLC port 5, LW output 5 - Flasher 2 G - { 6, TLC_PORT }, // TLC port 6, LW output 6 - Flasher 2 B - { 7, TLC_PORT }, // TLC port 7, LW output 7 - Flasher 3 R - { 8, TLC_PORT }, // TLC port 8, LW output 8 - Flasher 3 G - { 9, TLC_PORT }, // TLC port 9, LW output 9 - Flasher 3 B - { 10, TLC_PORT }, // TLC port 10, LW output 10 - Flasher 4 R - { 11, TLC_PORT }, // TLC port 11, LW output 11 - Flasher 4 G - { 12, TLC_PORT }, // TLC port 12, LW output 12 - Flasher 4 B - { 13, TLC_PORT }, // TLC port 13, LW output 13 - Flasher 5 R - { 14, TLC_PORT }, // TLC port 14, LW output 14 - Flasher 5 G - { 15, TLC_PORT }, // TLC port 15, LW output 15 - Flasher 5 B - { 16, TLC_PORT }, // TLC port 16, LW output 16 - Strobe/Button light - { 17, TLC_PORT }, // TLC port 17, LW output 17 - Button light 1 - { 18, TLC_PORT }, // TLC port 18, LW output 18 - Button light 2 - { 19, TLC_PORT }, // TLC port 19, LW output 19 - Button light 3 - { 20, TLC_PORT }, // TLC port 20, LW output 20 - Button light 4 - { PTC8, DIG_GPIO }, // PTC8, LW output 21 - Replay Knocker - { 21, TLC_PORT }, // TLC port 21, LW output 22 - Contactor 1/General purpose - { 22, TLC_PORT }, // TLC port 22, LW output 23 - Contactor 2/General purpose - { 23, TLC_PORT }, // TLC port 23, LW output 24 - Contactor 3/General purpose - { 24, TLC_PORT }, // TLC port 24, LW output 25 - Contactor 4/General purpose - { 25, TLC_PORT }, // TLC port 25, LW output 26 - Contactor 5/General purpose - { 26, TLC_PORT }, // TLC port 26, LW output 27 - Contactor 6/General purpose - { 27, TLC_PORT }, // TLC port 27, LW output 28 - Contactor 7/General purpose - { 28, TLC_PORT }, // TLC port 28, LW output 29 - Contactor 8/General purpose - { 29, TLC_PORT }, // TLC port 29, LW output 30 - Contactor 9/General purpose - { 30, TLC_PORT }, // TLC port 30, LW output 31 - Contactor 10/General purpose - { 31, TLC_PORT }, // TLC port 31, LW output 32 - Shaker Motor/General purpose - - // Ports 33+ are accessible only to DOF-based software. Older LedWiz-only - // software on the can't access these. Attach less common devices to these ports. - { 32, TLC_PORT }, // TLC port 32, LW output 33 - Gear Motor/General purpose - { 33, TLC_PORT }, // TLC port 33, LW output 34 - Fan/General purpose - { 34, TLC_PORT }, // TLC port 34, LW output 35 - Beacon/General purpose - { 35, TLC_PORT }, // TLC port 35, LW output 36 - Undercab RGB R/General purpose - { 36, TLC_PORT }, // TLC port 36, LW output 37 - Undercab RGB G/General purpose - { 37, TLC_PORT }, // TLC port 37, LW output 38 - Undercab RGB B/General purpose - { 38, TLC_PORT }, // TLC port 38, LW output 39 - Bell/General purpose - { 39, TLC_PORT }, // TLC port 39, LW output 40 - Chime 1/General purpose - { 40, TLC_PORT }, // TLC port 40, LW output 41 - Chime 2/General purpose - { 41, TLC_PORT }, // TLC port 41, LW output 42 - Chime 3/General purpose - { 42, TLC_PORT }, // TLC port 42, LW output 43 - General purpose - { 43, TLC_PORT }, // TLC port 43, LW output 44 - General purpose - { 44, TLC_PORT }, // TLC port 44, LW output 45 - Button light 5 - { 45, TLC_PORT }, // TLC port 45, LW output 46 - Button light 6 - { 46, TLC_PORT }, // TLC port 46, LW output 47 - Button light 7 - { 47, TLC_PORT }, // TLC port 47, LW output 48 - Button light 8 - { 49, TLC_PORT }, // TLC port 49, LW output 49 - Flipper button RGB left R - { 50, TLC_PORT }, // TLC port 50, LW output 50 - Flipper button RGB left G - { 51, TLC_PORT }, // TLC port 51, LW output 51 - Flipper button RGB left B - { 52, TLC_PORT }, // TLC port 52, LW output 52 - Flipper button RGB right R - { 53, TLC_PORT }, // TLC port 53, LW output 53 - Flipper button RGB right G - { 54, TLC_PORT }, // TLC port 54, LW output 54 - Flipper button RGB right B - { 55, TLC_PORT }, // TLC port 55, LW output 55 - MagnaSave button RGB left R - { 56, TLC_PORT }, // TLC port 56, LW output 56 - MagnaSave button RGB left G - { 57, TLC_PORT }, // TLC port 57, LW output 57 - MagnaSave button RGB left B - { 58, TLC_PORT }, // TLC port 58, LW output 58 - MagnaSave button RGB right R - { 59, TLC_PORT }, // TLC port 59, LW output 59 - MagnaSave button RGB right G - { 60, TLC_PORT }, // TLC port 60, LW output 60 - MagnaSave button RGB right B - { 61, TLC_PORT }, // TLC port 61, LW output 61 - Extra RGB LED R - { 62, TLC_PORT }, // TLC port 62, LW output 62 - Extra RGB LED G - { 63, TLC_PORT }, // TLC port 63, LW output 63 - Extra RGB LED B - { 64, TLC_PORT } // TLC port 64, LW output 64 - Extra single LED - -#else - - // *** TLC5940 + GPIO OUTPUTS, Without the expansion board *** - // - // This is the mapping for the ehnanced mode, with one or more TLC5940 - // chips connected. Each TLC5940 chip provides 16 PWM channels. We - // can supplement the TLC5940 outputs with GPIO pins to get even more - // physical outputs. - // - // Because we've already declared the number of TLC5940 chips earlier - // in this file, we don't actually have to map out all of the TLC5940 - // ports here. The software will automatically assign all of the - // TLC5940 ports that aren't explicitly mentioned here to the next - // available LedWiz port numbers after the end of this array, assigning - // them sequentially in TLC5940 port order. - // - // In contrast to the basic mode arrangement, we're putting all of the - // NON PWM ports first in this mapping. The logic is that all of the - // TLC5940 ports are PWM-capable, and they'll all at the end of the list - // here, so by putting the PWM GPIO pins last here, we'll keep all of the - // PWM ports grouped in the final mapping. - // - // Note that the TLC5940 control wiring takes away several GPIO pins - // that we used as output ports in the basic mode. Further, because the - // TLC5940 makes ports so plentiful, we're intentionally omitting several - // more of the pins from the basic set, to make them available for other - // uses. To keep things more neatly grouped, we're only assigning J1 pins - // in this set. This leaves the following ports from the basic mode output - // set available for other users: PTA13, PTD0, PTD2, PTD3, PTD5, PTE0. - - { PTC8, DIG_GPIO }, // pin J1-14, LW port 1 - { PTC9, DIG_GPIO }, // pin J1-16, LW port 2 - { PTC0, DIG_GPIO }, // pin J1-3, LW port 3 - { PTC3, DIG_GPIO }, // pin J1-5, LW port 4 - { PTC4, DIG_GPIO }, // pin J1-7, LW port 5 - { PTC11, DIG_GPIO }, // pin J1-15, LW port 6 - { PTA2, PWM_GPIO }, // pin J1-4, LW port 7 (PWM capable - TPM 2.1 = channel 10) - { PTD4, PWM_GPIO }, // pin J1-6, LW port 8 (PWM capable - TPM 0.4 = channel 5) - { PTA12, PWM_GPIO }, // pin J1-8, LW port 9 (PWM capable - TPM 1.0 = channel 7) - { PTA4, PWM_GPIO }, // pin J1-10, LW port 10 (PWM capable - TPM 0.1 = channel 2) - { PTA5, PWM_GPIO } // pin J1-12, LW port 11 (PWM capable - TPM 0.2 = channel 3) - - // TLC5940 ports start here! - // First chip port 0 -> LW port 12 - // First chip port 1 -> LW port 13 - // ... etc, filling out all chip ports sequentially ... - -#endif // TLC5940_NCHIPS -}; - - -#endif // DECL_EXTERNS +#endif
--- a/main.cpp Thu Dec 03 07:34:57 2015 +0000 +++ b/main.cpp Sat Dec 19 06:37:19 2015 +0000 @@ -1,4 +1,4 @@ -/* Copyright 2014 M J Roberts, MIT License +/* Copyright 2014, 2015 M J Roberts, MIT License * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software * and associated documentation files (the "Software"), to deal in the Software without @@ -17,64 +17,43 @@ */ // -// Pinscape Controller -// -// "Pinscape" is the name of my custom-built virtual pinball cabinet, so I call this -// software the Pinscape Controller. I wrote it to handle several tasks that I needed -// for my cabinet. It runs on a Freescale KL25Z microcontroller, which is a small and -// inexpensive device that attaches to the cabinet PC via a USB cable, and can attach -// via custom wiring to sensors, buttons, and other devices in the cabinet. +// The Pinscape Controller +// A comprehensive input/output controller for virtual pinball machines // -// I designed the software and hardware in this project especially for my own -// cabinet, but it uses standard interfaces in Windows and Visual Pinball, so it should -// work in any VP-based cabinet, as long as you're using the usual VP software suite. -// I've tried to document the hardware in enough detail for anyone else to duplicate -// the entire project, and the full software is open source. +// This project implements an I/O controller designed for use in custom-built virtual +// pinball cabinets. It can handle nearly all of the functions involved in connecting +// pinball simulation software on a Windows PC with devices in the cabinet, including +// input devices such as buttons and sensors, and output devices that generate visual +// or mechanical feedback during play, like lights, solenoids, and shaker motors. +// You can use one, some, or all of the functions, in any combination. You can select +// options and configure the controller using a setup tool that runs on Windows. // -// The Freescale board appears to the host PC as a standard USB joystick. This works -// with the built-in Windows joystick device drivers, so there's no need to install any -// new drivers or other software on the PC. Windows should recognize the Freescale -// as a joystick when you plug it into the USB port, and Windows shouldn't ask you to -// install any drivers. If you bring up the Windows control panel for USB Game -// Controllers, this device will appear as "Pinscape Controller". *Don't* do any -// calibration with the Windows control panel or third-part calibration tools. The -// software calibrates the accelerometer portion automatically, and has its own special -// calibration procedure for the plunger sensor, if you're using that (see below). +// The main functions are: // -// This software provides a whole bunch of separate features. You can use any of these -// features individually or all together. If you're not using a particular feature, you -// can simply omit the extra wiring and/or hardware for that feature. You can use -// the nudging feature by itself without any extra hardware attached, since the -// accelerometer is built in to the KL25Z board. -// -// - Nudge sensing via the KL25Z's on-board accelerometer. Nudging the cabinet +// - Nudge sensing, via the KL25Z's on-board accelerometer. Nudging the cabinet // causes small accelerations that the accelerometer can detect; these are sent to -// Visual Pinball via the joystick interface so that VP can simulate the effect -// of the real physical nudges on its simulated ball. VP has native handling for -// this type of input, so all you have to do is set some preferences in VP to tell -// it that an accelerometer is attached. +// Visual Pinball (or other pinball emulator software) on the PC via the joystick +// interface, using the X and Y axes. VP and most other PC pinball emulators have +// native handling for this type of nudge input, so all you have to do is set some +// preferences in VP to let it know that an accelerometer is attached. // -// - Plunger position sensing via an attached TAOS TSL 1410R CCD linear array sensor. -// To use this feature, you need to buy the TAOS device (it's not built in to the -// KL25Z, obviously), wire it to the KL25Z (5 wire connections between the two -// devices are required), and mount the TAOS sensor in your cabinet so that it's -// positioned properly to capture images of the physical plunger shooter rod. -// -// The physical mounting and wiring details are desribed in the project -// documentation. +// - Plunger position sensing, via a number of sensor options. To use this feature, +// you need to choose a sensor and set it up, connect the sensor electrically to +// the KL25Z, and configure the Pinscape software on the KL25Z to let it know how +// the sensor is hooked up. The Pinscape software monitors the sensor and sends +// readings to Visual Pinball via the joystick Z axis. VP and other PC software +// has native support for this type of input as well; as with the nudge setup, +// you just have to set some options in VP to activate the plunger. // -// If the CCD is attached, the software constantly captures images from the CCD -// and analyzes them to determine how far back the plunger is pulled. It reports -// this to Visual Pinball via the joystick interface. This allows VP to make the -// simulated on-screen plunger track the motion of the physical plunger in real -// time. As with the nudge data, VP has native handling for the plunger input, -// so you just need to set the VP preferences to tell it that an analog plunger -// device is attached. One caveat, though: although VP itself has built-in -// support for an analog plunger, not all existing tables take advantage of it. -// Many existing tables have their own custom plunger scripting that doesn't -// cooperate with the VP plunger input. All tables *can* be made to work with -// the plunger, and in most cases it only requires some simple script editing, -// but in some cases it requires some more extensive surgery. +// The Pinscape software supports optical sensors (the TAOS TSL1410R and TSL1412R +// linear sensor arrays) as well as slide potentiometers. The specific equipment +// that's supported, along with physical mounting and wiring details, can be found +// in the Build Guide. +// +// Note that while VP has its own built-in support for plunger devices like this +// one, many existing VP tables will ignore it, because they use custom scripting +// that's only designed for keyboard plunger input. The Build Guide has advice on +// adjusting tables to add plunger support when necessary. // // For best results, the plunger sensor should be calibrated. The calibration // is stored in non-volatile memory on board the KL25Z, so it's only necessary @@ -103,11 +82,7 @@ // for input - you just have to assign a VP function to each button using VP's // keyboard options dialog. To wire a button physically, connect one terminal of // the button switch to the KL25Z ground, and connect the other terminal to the -// the GPIO port you wish to assign to the button. See the buttonMap[] array -// below for the available GPIO ports and their assigned joystick button numbers. -// If you're not using a GPIO port, you can just leave it unconnected - the digital -// inputs have built-in pull-up resistors, so an unconnected port is the same as -// an open switch (an "off" state for the button). +// the GPIO port you wish to assign to the button. // // - LedWiz emulation. The KL25Z can appear to the PC as an LedWiz device, and will // accept and process LedWiz commands from the host. The software can turn digital @@ -159,6 +134,8 @@ // higher numbered ports for the less common devices that older software can't // use anyway, you'll get maximum functionality out of software new and old. // +// +// // STATUS LIGHTS: The on-board LED on the KL25Z flashes to indicate the current // device status. The flash patterns are: // @@ -169,10 +146,6 @@ // // short red flash = the host computer is in sleep/suspend mode // -// long red/green = the LedWiz unti number has been changed, so a reset -// is needed. You can simply unplug the device and plug it back in, -// or presss and hold the reset button on the device for a few seconds. -// // long yellow/green = everything's working, but the plunger hasn't // been calibrated; follow the calibration procedure described above. // This flash mode won't appear if the CCD has been disabled. Note @@ -181,79 +154,12 @@ // in config.h or use the Windows config tool to disable the CCD // software features. // -// alternating blue/green = everything's working -// -// Software configuration: you can some change option settings by sending special -// USB commands from the PC. I've provided a Windows program for this purpose; -// refer to the documentation for details. For reference, here's the format -// of the USB command for option changes: -// -// length of report = 8 bytes -// byte 0 = 65 (0x41) -// byte 1 = 1 (0x01) -// byte 2 = new LedWiz unit number, 0x01 to 0x0f -// byte 3 = feature enable bit mask: -// 0x01 = enable CCD (default = on) -// -// Plunger calibration mode: the host can activate plunger calibration mode -// by sending this packet. This has the same effect as pressing and holding -// the plunger calibration button for two seconds, to allow activating this -// mode without attaching a physical button. -// -// length = 8 bytes -// byte 0 = 65 (0x41) -// byte 1 = 2 (0x02) -// -// Exposure reports: the host can request a report of the full set of pixel -// values for the next frame by sending this special packet: -// -// length = 8 bytes -// byte 0 = 65 (0x41) -// byte 1 = 3 (0x03) -// -// We'll respond with a series of special reports giving the exposure status. -// Each report has the following structure: +// alternating blue/green = everything's working, and the plunger has +// been calibrated // -// bytes 0:1 = 11-bit index, with high 5 bits set to 10000. For -// example, 0x04 0x80 indicates index 4. This is the -// starting pixel number in the report. The first report -// will be 0x00 0x80 to indicate pixel #0. -// bytes 2:3 = 16-bit unsigned int brightness level of pixel at index -// bytes 4:5 = brightness of pixel at index+1 -// etc for the rest of the packet // -// This still has the form of a joystick packet at the USB level, but -// can be differentiated by the host via the status bits. It would have -// been cleaner to use a different Report ID at the USB level, but this -// would have necessitated a different container structure in the report -// descriptor, which would have broken LedWiz compatibility. Given that -// constraint, we have to re-use the joystick report type, making for -// this somewhat kludgey approach. -// -// Configuration query: the host can request a full report of our hardware -// configuration with this message. -// -// length = 8 bytes -// byte 0 = 65 (0x41) -// byte 1 = 4 (0x04) -// -// We'll response with one report containing the configuration status: -// -// bytes 0:1 = 0x8800. This has the bit pattern 10001 in the high -// 5 bits, which distinguishes it from regular joystick -// reports and from exposure status reports. -// bytes 2:3 = number of outputs -// remaining bytes = reserved for future use; set to 0 in current version -// -// Turn off all outputs: this message tells the device to turn off all -// outputs and restore power-up LedWiz defaults. This sets outputs #1-32 -// to profile 48 (full brightness) and switch state Off, sets all extended -// outputs (#33 and above) to brightness 0, and sets the LedWiz flash rate -// to 2. -// -// length = 8 bytes -// byte 0 = 65 (0x41) -// byte 1 = 5 (0x05) +// USB PROTOCOL: please refer to USBProtocol.h for details on the USB +// message protocol. #include "mbed.h" @@ -265,6 +171,11 @@ #include "crc32.h" #include "TLC5940.h" #include "74HC595.h" +#include "nvm.h" +#include "plunger.h" +#include "ccdSensor.h" +#include "potSensor.h" +#include "nullSensor.h" #define DECL_EXTERNS #include "config.h" @@ -287,18 +198,7 @@ // // USB product version number // -const uint16_t USB_VERSION_NO = 0x0007; - - -// -// Build the full USB product ID. If we're using the LedWiz compatible -// vendor ID, the full product ID is the combination of the LedWiz base -// product ID (0x00F0) and the 0-based unit number (0-15). If we're not -// trying to be LedWiz compatible, we just use the exact product ID -// specified in config.h. -#define MAKE_USB_PRODUCT_ID(vid, pidbase, unit) \ - ((vid) == 0xFAFA && (pidbase) == 0x00F0 ? (pidbase) | (unit) : (pidbase)) - +const uint16_t USB_VERSION_NO = 0x0008; // -------------------------------------------------------------------------- // @@ -306,52 +206,6 @@ // #define JOYMAX 4096 -// -------------------------------------------------------------------------- -// -// Set up mappings for the joystick X and Y reports based on the mounting -// orientation of the KL25Z in the cabinet. Visual Pinball and other -// pinball software effectively use video coordinates to define the axes: -// positive X is to the right of the table, negative Y to the left, positive -// Y toward the front of the table, negative Y toward the back. The KL25Z -// accelerometer is mounted on the board with positive Y toward the USB -// ports and positive X toward the right side of the board with the USB -// ports pointing up. It's a simple matter to remap the KL25Z coordinate -// system to match VP's coordinate system for mounting orientations at -// 90-degree increments... -// -#if defined(ORIENTATION_PORTS_AT_FRONT) -# define JOY_X(x, y) (y) -# define JOY_Y(x, y) (x) -#elif defined(ORIENTATION_PORTS_AT_LEFT) -# define JOY_X(x, y) (-(x)) -# define JOY_Y(x, y) (y) -#elif defined(ORIENTATION_PORTS_AT_RIGHT) -# define JOY_X(x, y) (x) -# define JOY_Y(x, y) (-(y)) -#elif defined(ORIENTATION_PORTS_AT_REAR) -# define JOY_X(x, y) (-(y)) -# define JOY_Y(x, y) (-(x)) -#else -# error Please define one of the ORIENTATION_PORTS_AT_xxx macros to establish the accelerometer orientation in your cabinet -#endif - - - -// -------------------------------------------------------------------------- -// -// Define a symbol to tell us whether any sort of plunger sensor code -// is enabled in this build. Note that this doesn't tell us that a -// plunger device is actually attached or *currently* enabled; it just -// tells us whether or not the code for plunger sensing is enabled in -// the software build. This lets us leave out some unnecessary code -// on installations where no physical plunger is attached. -// -const int PLUNGER_CODE_ENABLED = -#if defined(ENABLE_CCD_SENSOR) || defined(ENABLE_POT_SENSOR) - 1; -#else - 0; -#endif // --------------------------------------------------------------------------- // @@ -369,6 +223,47 @@ // --------------------------------------------------------------------------- // +// Wire protocol value translations. These translate byte values from +// the USB protocol to local native format. +// + +// unsigned 16-bit integer +inline uint16_t wireUI16(const uint8_t *b) +{ + return b[0] | ((uint16_t)b[1] << 8); +} + +inline int16_t wireI16(const uint8_t *b) +{ + return (int16_t)wireUI16(b); +} + +inline uint32_t wireUI32(const uint8_t *b) +{ + return b[0] | ((uint32_t)b[1] << 8) | ((uint32_t)b[2] << 16) | ((uint32_t)b[3] << 24); +} + +inline int32_t wireI32(const uint8_t *b) +{ + return (int32_t)wireUI32(b); +} + +inline PinName wirePinName(int c) +{ + static const PinName p[] = { + NC, PTA1, PTA2, PTA4, PTA5, PTA12, PTA13, PTA16, PTA17, PTB0, // 0-9 + PTB1, PTB2, PTB3, PTB8, PTB9, PTB10, PTB11, PTC0, PTC1, PTC2, // 10-19 + PTC3, PTC4, PTC5, PTC6, PTC7, PTC8, PTC9, PTC10, PTC11, PTC12, // 20-29 + PTC13, PTC16, PTC17, PTD0, PTD1, PTD2, PTD3, PTD4, PTD5, PTD6, // 30-39 + PTD7, PTE0, PTE1, PTE2, PTE3, PTE4, PTE5, PTE20, PTE21, PTE22, // 40-49 + PTE23, PTE29, PTE30, PTE31 // 50-53 + }; + return (c < countof(p) ? p[c] : NC); +} + + +// --------------------------------------------------------------------------- +// // LedWiz emulation, and enhanced TLC5940 output controller // // There are two modes for this feature. The default mode uses the on-board @@ -407,18 +302,15 @@ virtual void set(float val) = 0; }; -// LwOut class for unmapped ports. The LedWiz protocol is hardwired -// for 32 ports, but we might not want to assign all 32 software ports -// to physical output pins - the KL25Z has a limited number of GPIO -// ports, so we might not have enough available GPIOs to fill out the -// full LedWiz complement after assigning GPIOs for other functions. -// This class is used to populate the LedWiz mapping array for ports -// that aren't connected to physical outputs; it simply ignores value -// changes. -class LwUnusedOut: public LwOut +// LwOut class for virtual ports. This type of port is visible to +// the host software, but isn't connected to any physical output. +// This can be used for special software-only ports like the ZB +// Launch Ball output, or simply for placeholders in the LedWiz port +// numbering. +class LwVirtualOut: public LwOut { public: - LwUnusedOut() { } + LwVirtualOut() { } virtual void set(float val) { } }; @@ -436,13 +328,19 @@ }; -#if TLC5940_NCHIPS +// +// The TLC5940 interface object. We'll set this up with the port +// assignments set in config.h. // -// The TLC5940 interface object. Set this up with the port assignments -// set in config.h. -// -TLC5940 tlc5940(TLC5940_SCLK, TLC5940_SIN, TLC5940_GSCLK, TLC5940_BLANK, - TLC5940_XLAT, TLC5940_NCHIPS); +TLC5940 *tlc5940 = 0; +void init_tlc5940(Config &cfg) +{ + if (cfg.tlc5940.nchips != 0) + { + tlc5940 = new TLC5940(cfg.tlc5940.sclk, cfg.tlc5940.sin, cfg.tlc5940.gsclk, + cfg.tlc5940.blank, cfg.tlc5940.xlat, cfg.tlc5940.nchips); + } +} // LwOut class for TLC5940 outputs. These are fully PWM capable. // The 'idx' value in the constructor is the output index in the @@ -456,35 +354,27 @@ virtual void set(float val) { if (val != prv) - tlc5940.set(idx, (int)((prv = val) * 4095)); + tlc5940->set(idx, (int)((prv = val) * 4095)); } int idx; float prv; }; -#else -// No TLC5940 chips are attached, so we shouldn't encounter any ports -// in the map marked for TLC5940 outputs. If we do, treat them as unused. -class Lw5940Out: public LwUnusedOut -{ -public: - Lw5940Out(int idx) { } -}; -// dummy tlc5940 interface -class Dummy5940 -{ -public: - void start() { } -}; -Dummy5940 tlc5940; - -#endif // TLC5940_NCHIPS - -#if HC595_NCHIPS // 74HC595 interface object. Set this up with the port assignments in // config.h. -HC595 hc595(HC595_NCHIPS, HC595_SIN, HC595_SCLK, HC595_LATCH, HC595_ENA); +HC595 *hc595 = 0; + +// initialize the 74HC595 interface +void init_hc595(Config &cfg) +{ + if (cfg.hc595.nchips != 0) + { + hc595 = new HC595(cfg.hc595.nchips, cfg.hc595.sin, cfg.hc595.sclk, cfg.hc595.latch, cfg.hc595.ena); + hc595->init(); + hc595->update(); + } +} // LwOut class for 74HC595 outputs. These are simple digial outs. // The 'idx' value in the constructor is the output index in the @@ -498,31 +388,12 @@ virtual void set(float val) { if (val != prv) - hc595.set(idx, (prv = val) == 0.0 ? 0 : 1); + hc595->set(idx, (prv = val) == 0.0 ? 0 : 1); } int idx; float prv; }; -#else // HC595_NCHIPS -// No 74HC595 chips are attached, so we shouldn't encounter any ports -// in the map marked for these outputs. If we do, treat them as unused. -class Lw595Out: public LwUnusedOut -{ -public: - Lw595Out(int idx) { } -}; - -// dummy placeholder class -class DummyHC595 -{ -public: - void init() { } - void update() { } -}; -DummyHC595 hc595; - -#endif // HC595_NCHIPS // // Default LedWiz mode - using on-board GPIO ports. In this mode, we @@ -562,14 +433,23 @@ // Array of output physical pin assignments. This array is indexed // by LedWiz logical port number - lwPin[n] is the maping for LedWiz -// port n (0-based). If we're using GPIO ports to implement outputs, -// we initialize the array at start-up to map each logical port to the -// physical GPIO pin for the port specified in the ledWizPortMap[] -// array in config.h. If we're using TLC5940 chips for the outputs, -// we map each logical port to the corresponding TLC5940 output. +// port n (0-based). +// +// Each pin is handled by an interface object for the physical output +// type for the port, as set in the configuration. The interface +// objects handle the specifics of addressing the different hardware +// types (GPIO PWM ports, GPIO digital ports, TLC5940 ports, and +// 74HC595 ports). static int numOutputs; static LwOut **lwPin; +// Number of LedWiz emulation outputs. This is the number of ports +// accessible through the standard (non-extended) LedWiz protocol +// messages. The protocol has a fixed set of 32 outputs, but we +// might have fewer actual outputs. This is therefore set to the +// lower of 32 or the actual number of outputs. +static int numLwOutputs; + // Current absolute brightness level for an output. This is a float // value from 0.0 for fully off to 1.0 for fully on. This is the final // derived value for the port. For outputs set by LedWiz messages, @@ -579,90 +459,67 @@ static float *outLevel; // initialize the output pin array -void initLwOut() +void initLwOut(Config &cfg) { - // Figure out how many outputs we have. We always have at least - // 32 outputs, since that's the number fixed by the original LedWiz - // protocol. If we're using TLC5940 chips, each chip provides 16 - // outputs. Likewise, each 74HC595 provides 8 outputs. - - // start with 16 ports per TLC5940 and 8 per 74HC595 - numOutputs = TLC5940_NCHIPS*16 + HC595_NCHIPS*8; - - // add outputs explicitly assigned to GPIO pins or not connected + // Count the outputs. The first disabled output determines the + // total number of ports. + numOutputs = MAX_OUT_PORTS; int i; - for (i = 0 ; i < countof(ledWizPortMap) ; ++i) + for (i = 0 ; i < MAX_OUT_PORTS ; ++i) { - switch (ledWizPortMap[i].typ) + if (cfg.outPort[i].typ == PortTypeDisabled) { - case DIG_GPIO: - case PWM_GPIO: - case NO_PORT: - // count an explicitly GPIO port - ++numOutputs; - break; - - default: - // DON'T count TLC5940 or 74HC595 ports, as we've already - // counted all of these above + numOutputs = i; break; } } - // always set up at least 32 outputs, so that we don't have to - // check bounds on commands from the basic LedWiz protocol - if (numOutputs < 32) - numOutputs = 32; - + // the real LedWiz protocol can access at most 32 ports, or the + // actual number of outputs, whichever is lower + numLwOutputs = (numOutputs < 32 ? numOutputs : 32); + // allocate the pin array lwPin = new LwOut*[numOutputs]; - // allocate the current brightness array - outLevel = new float[numOutputs]; + // Allocate the current brightness array. + outLevel = new float[numOutputs < 32 ? 32 : numOutputs]; - // allocate a temporary array to keep track of which physical - // TLC5940 ports we've assigned so far - char *tlcasi = new char[TLC5940_NCHIPS*16+1]; - memset(tlcasi, 0, TLC5940_NCHIPS*16); + // create the pin interface object for each port + for (i = 0 ; i < numOutputs ; ++i) + { + // get this item's values + int typ = cfg.outPort[i].typ; + int pin = cfg.outPort[i].pin; + int flags = cfg.outPort[i].flags; + int activeLow = flags & PortFlagActiveLow; - // likewise for the 74HC595 ports - char *hcasi = new char[HC595_NCHIPS*8+1]; - memset(hcasi, 0, HC595_NCHIPS*8); - - // assign all pins from the explicit port map in config.h - for (i = 0 ; i < countof(ledWizPortMap) ; ++i) - { - int pin = ledWizPortMap[i].pin; - LWPortType typ = ledWizPortMap[i].typ; - int flags = ledWizPortMap[i].flags; - int activeLow = flags & PORT_ACTIVE_LOW; + // create the pin interface object according to the port type switch (typ) { - case DIG_GPIO: - lwPin[i] = new LwDigOut((PinName)pin); + case PortTypeGPIOPWM: + // PWM GPIO port + lwPin[i] = new LwPwmOut(wirePinName(pin)); break; - case PWM_GPIO: - // PWM GPIO port - lwPin[i] = new LwPwmOut((PinName)pin); + case PortTypeGPIODig: + // Digital GPIO port + lwPin[i] = new LwDigOut(wirePinName(pin)); break; - case TLC_PORT: - // TLC5940 port (note that the nominal pin in the map is 1-based, so we - // have to decrement it to get the real pin index) - lwPin[i] = new Lw5940Out(pin-1); - tlcasi[pin-1] = 1; + case PortTypeTLC5940: + // TLC5940 port + lwPin[i] = new Lw5940Out(pin); break; - case HC595_PORT: - // 74HC595 port (the pin in the map is 1-based, so decrement it to get the - // real pin index) - lwPin[i] = new Lw595Out(pin-1); - hcasi[pin-1] = 1; + case PortType74HC595: + // 74HC595 port + lwPin[i] = new Lw595Out(pin); break; - + + case PortTypeVirtual: default: - lwPin[i] = new LwUnusedOut(); + // virtual or unknown + lwPin[i] = new LwVirtualOut(); break; } @@ -673,41 +530,6 @@ // turn it off initially lwPin[i]->set(0); } - - // If we haven't assigned all of the LedWiz ports to physical pins, - // fill out the unassigned LedWiz ports with any unassigned TLC5940 - // pins, then with any unassigned 74HC595 ports. - int tlcnxt, hcnxt; - for (tlcnxt = 0 ; tlcnxt < TLC5940_NCHIPS*16 && tlcasi[tlcnxt] ; ++tlcnxt) ; - for (hcnxt = 0 ; hcnxt < HC595_NCHIPS*8 && hcasi[hcnxt] ; ++hcnxt) ; - for ( ; i < numOutputs ; ++i) - { - // If we have any more unassigned TLC5940 outputs, assign this LedWiz - // port to the next available TLC5940 output, or the next 74HC595 output - // if we're out of TLC5940 outputs. Leave it unassigned if there are - // no more unassigned ports of any type. - if (tlcnxt < TLC5940_NCHIPS*16) - { - // assign this available TLC5940 pin, and find the next unused one - lwPin[i] = new Lw5940Out(tlcnxt); - for (++tlcnxt ; tlcnxt < TLC5940_NCHIPS*16 && tlcasi[tlcnxt] ; ++tlcnxt) ; - } - else if (hcnxt < HC595_NCHIPS*8) - { - // assign this available 74HC595 pin, and find the next unused one - lwPin[i] = new Lw595Out(hcnxt); - for (++hcnxt ; hcnxt < HC595_NCHIPS*8 && hcasi[hcnxt] ; ++hcnxt) ; - } - else - { - // no more ports available - set up this port as unconnected - lwPin[i] = new LwUnusedOut(); - } - } - - // done with the temporary TLC5940 and 74HC595 port assignment lists - delete [] tlcasi; - delete [] hcasi; } // LedWiz output states. @@ -855,7 +677,7 @@ // if we have any flashing lights, update them int ena = false; - for (int i = 0 ; i < 32 ; ++i) + for (int i = 0 ; i < numLwOutputs ; ++i) { if (wizOn[i]) { @@ -884,7 +706,7 @@ { // update each output int pulse = false; - for (int i = 0 ; i < 32 ; ++i) + for (int i = 0 ; i < numLwOutputs ; ++i) { pulse |= (wizVal[i] >= 129 && wizVal[i] <= 132); lwPin[i]->set(wizState(i)); @@ -896,7 +718,8 @@ wizPulseTimer.attach(wizPulse, WIZ_PULSE_TIME_BASE); // flush changes to 74HC595 chips, if attached - hc595.update(); + if (hc595 != 0) + hc595->update(); } // --------------------------------------------------------------------------- @@ -904,12 +727,14 @@ // Button input // -// button input map array -DigitalIn *buttonDigIn[32]; - // button state struct ButtonState { + ButtonState() : di(NULL), pressed(0), t(0), js(0), keymod(0), keycode(0) { } + + // DigitalIn for the button + DigitalIn *di; + // current on/off state int pressed; @@ -917,49 +742,120 @@ // state transition occurs, we set this to a debounce // period. Future state transitions will be ignored // until the debounce time elapses. - int t; -} buttonState[32]; + float t; + + // joystick button mask for the button, if mapped as a joystick button + uint32_t js; + + // keyboard modifier bits and scan code for the button, if mapped as a keyboard key + uint8_t keymod; + uint8_t keycode; + + // media control key code + uint8_t mediakey; + + +} buttonState[MAX_BUTTONS]; // timer for button reports static Timer buttonTimer; // initialize the button inputs -void initButtons() +void initButtons(Config &cfg, bool &kbKeys) { + // presume we'll find no keyboard keys + kbKeys = false; + // create the digital inputs - for (int i = 0 ; i < countof(buttonDigIn) ; ++i) + ButtonState *bs = buttonState; + for (int i = 0 ; i < MAX_BUTTONS ; ++i, ++bs) { - if (i < countof(buttonMap) && buttonMap[i] != NC) - buttonDigIn[i] = new DigitalIn(buttonMap[i]); - else - buttonDigIn[i] = 0; + PinName pin = wirePinName(cfg.button[i].pin); + if (pin != NC) + { + // set up the GPIO input pin for this button + bs->di = new DigitalIn(pin); + + // note if it's a keyboard key of some kind (including media keys) + uint8_t val = cfg.button[i].val; + switch (cfg.button[i].typ) + { + case BtnTypeJoystick: + // joystick button - get the button bit mask + bs->js = 1 << val; + break; + + case BtnTypeKey: + // regular keyboard key - note the scan code + bs->keycode = val; + kbKeys = true; + break; + + case BtnTypeModKey: + // keyboard mod key - note the modifier mask + bs->keymod = val; + kbKeys = true; + break; + + case BtnTypeMedia: + // media key - note the code + bs->mediakey = val; + kbKeys = true; + break; + } + } } // start the button timer + buttonTimer.reset(); buttonTimer.start(); } +// Button data +uint32_t jsButtons = 0; + +// Keyboard state +struct +{ + bool changed; // flag: changed since last report sent + int nkeys; // number of active keys in the list + uint8_t data[8]; // key state, in USB report format: byte 0 is the modifier key mask, + // byte 1 is reserved, and bytes 2-7 are the currently pressed key codes +} kbState = { false, 0, { 0, 0, 0, 0, 0, 0, 0, 0 } }; + +// Media key state +struct +{ + bool changed; // flag: changed since last report sent + uint8_t data; // key state byte for USB reports +} mediaState = { false, 0 }; // read the button input state -uint32_t readButtons() +void readButtons(Config &cfg) { - // start with all buttons off - uint32_t buttons = 0; + // start with an empty list of USB key codes + uint8_t modkeys = 0; + uint8_t keys[7] = { 0, 0, 0, 0, 0, 0, 0 }; + int nkeys = 0; + // clear the joystick buttons + jsButtons = 0; + + // start with no media keys pressed + uint8_t mediakeys = 0; + // figure the time elapsed since the last scan - int dt = buttonTimer.read_ms(); + float dt = buttonTimer.read(); - // reset the timef for the next scan + // reset the time for the next scan buttonTimer.reset(); // scan the button list - uint32_t bit = 1; - DigitalIn **di = buttonDigIn; ButtonState *bs = buttonState; - for (int i = 0 ; i < countof(buttonDigIn) ; ++i, ++di, ++bs, bit <<= 1) + for (int i = 0 ; i < MAX_BUTTONS ; ++i, ++bs) { // read this button - if (*di != 0) + if (bs->di != 0) { // deduct the elapsed time since the last update // from the button's remaining sticky time @@ -976,7 +872,7 @@ if (bs->t == 0) { // get the new physical state - int pressed = !(*di)->read(); + int pressed = !bs->di->read(); // update the button's logical state if this is a change if (pressed != bs->pressed) @@ -986,18 +882,51 @@ // start a new sticky period for debouncing this // state change - bs->t = 25; + bs->t = 0.005; } } - - // if it's pressed, OR its bit into the state + + // if it's pressed, add it to the appropriate key state list if (bs->pressed) - buttons |= bit; + { + // OR in the joystick button bit, mod key bits, and media key bits + jsButtons |= bs->js; + modkeys |= bs->keymod; + mediakeys |= bs->mediakey; + + // if it has a keyboard key, add the scan code to the active list + if (bs->keycode != 0 && nkeys < 7) + keys[nkeys++] = bs->keycode; + } } } - // return the new button list - return buttons; + // Check for changes to the keyboard keys + if (kbState.data[0] != modkeys + || kbState.nkeys != nkeys + || memcmp(keys, &kbState.data[2], 6) != 0) + { + // we have changes - set the change flag and store the new key data + kbState.changed = true; + kbState.data[0] = modkeys; + if (nkeys <= 6) { + // 6 or fewer simultaneous keys - report the key codes + kbState.nkeys = nkeys; + memcpy(&kbState.data[2], keys, 6); + } + else { + // more than 6 simultaneous keys - report rollover (all '1' key codes) + kbState.nkeys = 6; + memset(&kbState.data[2], 1, 6); + } + } + + // Check for changes to media keys + if (mediaState.data != mediakeys) + { + mediaState.changed = true; + mediaState.data = mediakeys; + } } // --------------------------------------------------------------------------- @@ -1008,8 +937,9 @@ class MyUSBJoystick: public USBJoystick { public: - MyUSBJoystick(uint16_t vendor_id, uint16_t product_id, uint16_t product_release) - : USBJoystick(vendor_id, product_id, product_release, true) + MyUSBJoystick(uint16_t vendor_id, uint16_t product_id, uint16_t product_release, + bool waitForConnect, bool enableJoystick, bool useKB) + : USBJoystick(vendor_id, product_id, product_release, waitForConnect, enableJoystick, useKB) { suspended_ = false; } @@ -1347,116 +1277,6 @@ // --------------------------------------------------------------------------- // -// Include the appropriate plunger sensor definition. This will define a -// class called PlungerSensor, with a standard interface that we use in -// the main loop below. This is *kind of* like a virtual class interface, -// but it actually defines the methods statically, which is a little more -// efficient at run-time. There's no need for a true virtual interface -// because we don't need to be able to change sensor types on the fly. -// - -#if defined(ENABLE_CCD_SENSOR) -#include "ccdSensor.h" -#elif defined(ENABLE_POT_SENSOR) -#include "potSensor.h" -#else -#include "nullSensor.h" -#endif - - -// --------------------------------------------------------------------------- -// -// Non-volatile memory (NVM) -// - -// Structure defining our NVM storage layout. We store a small -// amount of persistent data in flash memory to retain calibration -// data when powered off. -struct NVM -{ - // checksum - we use this to determine if the flash record - // has been properly initialized - uint32_t checksum; - - // signature and version, to verify that we saved the config - // data to flash on a past run (as opposed to uninitialized - // data from a firmware update) - static const uint32_t SIGNATURE = 0x4D4A522A; - static const uint16_t VERSION = 0x0003; - - // Is the data structure valid? We test the signature and - // checksum to determine if we've been properly stored. - int valid() const - { - return (d.sig == SIGNATURE - && d.vsn == VERSION - && d.sz == sizeof(NVM) - && checksum == CRC32(&d, sizeof(d))); - } - - // save to non-volatile memory - void save(FreescaleIAP &iap, int addr) - { - // update the checksum and structure size - d.sig = SIGNATURE; - d.vsn = VERSION; - d.sz = sizeof(NVM); - checksum = CRC32(&d, sizeof(d)); - - // erase the sector - iap.erase_sector(addr); - - // save the data - iap.program_flash(addr, this, sizeof(*this)); - } - - // reset calibration data for calibration mode - void resetPlunger() - { - // set extremes for the calibration data - d.plungerMax = 0; - d.plungerZero = npix; - d.plungerMin = npix; - } - - // stored data (excluding the checksum) - struct - { - // Signature, structure version, and structure size - further verification - // that we have valid initialized data. The size is a simple proxy for a - // structure version, as the most common type of change to the structure as - // the software evolves will be the addition of new elements. We also - // provide an explicit version number that we can update manually if we - // make any changes that don't affect the structure size but would affect - // compatibility with a saved record (e.g., swapping two existing elements). - uint32_t sig; - uint16_t vsn; - int sz; - - // has the plunger been manually calibrated? - int plungerCal; - - // Plunger calibration min, zero, and max. The zero point is the - // rest position (aka park position), where it's in equilibrium between - // the main spring and the barrel spring. It can travel a small distance - // forward of the rest position, because the barrel spring can be - // compressed by the user pushing on the plunger or by the momentum - // of a release motion. The minimum is the maximum forward point where - // the barrel spring can't be compressed any further. - int plungerMin; - int plungerZero; - int plungerMax; - - // is the plunger sensor enabled? - int plungerEnabled; - - // LedWiz unit number - uint8_t ledWizUnitNo; - } d; -}; - -// --------------------------------------------------------------------------- -// // Simple binary (on/off) input debouncer. Requires an input to be stable // for a given interval before allowing an update. // @@ -1527,7 +1347,7 @@ void allOutputsOff() { // reset all LedWiz outputs to OFF/48 - for (int i = 0 ; i < 32 ; ++i) + for (int i = 0 ; i < numLwOutputs ; ++i) { outLevel[i] = 0; wizOn[i] = 0; @@ -1546,7 +1366,8 @@ wizSpeed = 2; // flush changes to hc595, if applicable - hc595.update(); + if (hc595 != 0) + hc595->update(); } // --------------------------------------------------------------------------- @@ -1615,7 +1436,6 @@ // so we don't want to push the button on a TV that's already on. // // -#ifdef ENABLE_TV_TIMER // Current PSU2 state: // 1 -> default: latch was on at last check, or we haven't checked yet @@ -1625,12 +1445,22 @@ // 5 -> TV relay on // int psu2_state = 1; -DigitalIn psu2_status_sense(PSU2_STATUS_SENSE); -DigitalOut psu2_status_set(PSU2_STATUS_SET); -DigitalOut tv_relay(TV_RELAY_PIN); -Timer tv_timer; + +// PSU2 power sensing circuit connections +DigitalIn *psu2_status_sense; +DigitalOut *psu2_status_set; + +// TV ON switch relay control +DigitalOut *tv_relay; + +// Timer interrupt +Ticker tv_ticker; +float tv_delay_time; void TVTimerInt() { + // time since last state change + static Timer tv_timer; + // Check our internal state switch (psu2_state) { @@ -1640,20 +1470,20 @@ // either case, if the latch is off, switch to state 2 and // try pulsing the latch. Next time we check, if the latch // stuck, it means that PSU2 is now on after being off. - if (!psu2_status_sense) + if (!psu2_status_sense->read()) { // switch to OFF state psu2_state = 2; // try setting the latch - psu2_status_set = 1; + psu2_status_set->write(1); } break; case 2: // PSU2 was off last time we checked, and we tried setting // the latch. Drop the SET signal and go to CHECK state. - psu2_status_set = 0; + psu2_status_set->write(0); psu2_state = 3; break; @@ -1662,7 +1492,7 @@ // if that stuck. If the latch is now on, PSU2 has transitioned // from OFF to ON, so start the TV countdown. If the latch is // off, our SET command didn't stick, so PSU2 is still off. - if (psu2_status_sense) + if (psu2_status_sense->read()) { // The latch stuck, so PSU2 has transitioned from OFF // to ON. Start the TV countdown timer. @@ -1675,7 +1505,7 @@ // The latch didn't stick, so PSU2 was still off at // our last check. Try pulsing it again in case PSU2 // was turned on since the last check. - psu2_status_set = 1; + psu2_status_set->write(1); psu2_state = 2; } break; @@ -1683,10 +1513,10 @@ case 4: // TV timer countdown in progress. If we've reached the // delay time, pulse the relay. - if (tv_timer.read() >= TV_DELAY_TIME) + if (tv_timer.read() >= tv_delay_time) { // turn on the relay for one timer interval - tv_relay = 1; + tv_relay->write(1); psu2_state = 5; } break; @@ -1694,27 +1524,631 @@ case 5: // TV timer relay on. We pulse this for one interval, so // it's now time to turn it off and return to the default state. - tv_relay = 0; + tv_relay->write(0); psu2_state = 1; break; } } -Ticker tv_ticker; -void startTVTimer() +// Start the TV ON checker. If the status sense circuit is enabled in +// the configuration, we'll set up the pin connections and start the +// interrupt handler that periodically checks the status. Does nothing +// if any of the pins are configured as NC. +void startTVTimer(Config &cfg) +{ + // only start the timer if the status sense circuit pins are configured + if (cfg.TVON.statusPin != NC && cfg.TVON.latchPin != NC && cfg.TVON.relayPin != NC) + { + psu2_status_sense = new DigitalIn(cfg.TVON.statusPin); + psu2_status_set = new DigitalOut(cfg.TVON.latchPin); + tv_relay = new DigitalOut(cfg.TVON.relayPin); + tv_delay_time = cfg.TVON.delayTime; + + // Set up our time routine to run every 1/4 second. + tv_ticker.attach(&TVTimerInt, 0.25); + } +} + +// --------------------------------------------------------------------------- +// +// In-memory configuration data structure. This is the live version in RAM +// that we use to determine how things are set up. +// +// When we save the configuration settings, we copy this structure to +// non-volatile flash memory. At startup, we check the flash location where +// we might have saved settings on a previous run, and it's valid, we copy +// the flash data to this structure. Firmware updates wipe the flash +// memory area, so you have to use the PC config tool to send the settings +// again each time the firmware is updated. +// +NVM nvm; + +// For convenience, a macro for the Config part of the NVM structure +#define cfg (nvm.d.c) + +// flash memory controller interface +FreescaleIAP iap; + +// figure the flash address as a pointer along with the number of sectors +// required to store the structure +NVM *configFlashAddr(int &addr, int &numSectors) +{ + // figure how many flash sectors we span, rounding up to whole sectors + numSectors = (sizeof(NVM) + SECTOR_SIZE - 1)/SECTOR_SIZE; + + // figure the address - this is the highest flash address where the + // structure will fit with the start aligned on a sector boundary + addr = iap.flash_size() - (numSectors * SECTOR_SIZE); + + // return the address as a pointer + return (NVM *)addr; +} + +// figure the flash address as a pointer +NVM *configFlashAddr() +{ + int addr, numSectors; + return configFlashAddr(addr, numSectors); +} + +// Load the config from flash +void loadConfigFromFlash() +{ + // We want to use the KL25Z's on-board flash to store our configuration + // data persistently, so that we can restore it across power cycles. + // Unfortunatly, the mbed platform doesn't explicitly support this. + // mbed treats the on-board flash as a raw storage device for linker + // output, and assumes that the linker output is the only thing + // stored there. There's no file system and no allowance for shared + // use for other purposes. Fortunately, the linker ues the space in + // the obvious way, storing the entire linked program in a contiguous + // block starting at the lowest flash address. This means that the + // rest of flash - from the end of the linked program to the highest + // flash address - is all unused free space. Writing our data there + // won't conflict with anything else. Since the linker doesn't give + // us any programmatic access to the total linker output size, it's + // safest to just store our config data at the very end of the flash + // region (i.e., the highest address). As long as it's smaller than + // the free space, it won't collide with the linker area. + + // Figure how many sectors we need for our structure + NVM *flash = configFlashAddr(); + + // if the flash is valid, load it; otherwise initialize to defaults + if (flash->valid()) + { + // flash is valid - load it into the RAM copy of the structure + memcpy(&nvm, flash, sizeof(NVM)); + } + else + { + // flash is invalid - load factory settings nito RAM structure + cfg.setFactoryDefaults(); + } +} + +void saveConfigToFlash() { - // Set up our time routine to run every 1/4 second. - tv_ticker.attach(&TVTimerInt, 0.25); + int addr, sectors; + configFlashAddr(addr, sectors); + nvm.save(iap, addr); +} + +// --------------------------------------------------------------------------- +// +// Plunger Sensor +// + +// the plunger sensor interface object +PlungerSensor *plungerSensor = 0; + +// Create the plunger sensor based on the current configuration. If +// there's already a sensor object, we'll delete it. +void createPlunger() +{ + // delete any existing sensor object + if (plungerSensor != 0) + delete plungerSensor; + + // create the new sensor object according to the type + switch (cfg.plunger.sensorType) + { + case PlungerType_TSL1410RS: + // pins are: SI, CLOCK, AO + plungerSensor = new PlungerSensorTSL1410R(cfg.plunger.sensorPin[0], cfg.plunger.sensorPin[1], cfg.plunger.sensorPin[2], NC); + break; + + case PlungerType_TSL1410RP: + // pins are: SI, CLOCK, AO1, AO2 + plungerSensor = new PlungerSensorTSL1410R(cfg.plunger.sensorPin[0], cfg.plunger.sensorPin[1], cfg.plunger.sensorPin[2], cfg.plunger.sensorPin[3]); + break; + + case PlungerType_TSL1412RS: + // pins are: SI, CLOCK, AO1, AO2 + plungerSensor = new PlungerSensorTSL1412R(cfg.plunger.sensorPin[0], cfg.plunger.sensorPin[1], cfg.plunger.sensorPin[2], NC); + break; + + case PlungerType_TSL1412RP: + // pins are: SI, CLOCK, AO1, AO2 + plungerSensor = new PlungerSensorTSL1412R(cfg.plunger.sensorPin[0], cfg.plunger.sensorPin[1], cfg.plunger.sensorPin[2], cfg.plunger.sensorPin[3]); + break; + + case PlungerType_Pot: + // pins are: AO + plungerSensor = new PlungerSensorPot(cfg.plunger.sensorPin[0]); + break; + + case PlungerType_None: + default: + plungerSensor = new PlungerSensorNull(); + break; + } } +// --------------------------------------------------------------------------- +// +// Reboot - resets the microcontroller +// +void reboot(USBJoystick &js) +{ + // disconnect from USB + js.disconnect(); + + // wait a few seconds to make sure the host notices the disconnect + wait(5); + + // reset the device + NVIC_SystemReset(); + while (true) { } +} + +// --------------------------------------------------------------------------- +// +// Translate joystick readings from raw values to reported values, based +// on the orientation of the controller card in the cabinet. +// +void accelRotate(int &x, int &y) +{ + int tmp; + switch (cfg.orientation) + { + case OrientationFront: + tmp = x; + x = y; + y = tmp; + break; + + case OrientationLeft: + x = -x; + break; + + case OrientationRight: + y = -y; + break; + + case OrientationRear: + tmp = -x; + x = -y; + y = tmp; + break; + } +} + +// --------------------------------------------------------------------------- +// +// Device status. We report this on each update so that the host config +// tool can detect our current settings. This is a bit mask consisting +// of these bits: +// 0x0001 -> plunger sensor enabled +// 0x8000 -> RESERVED - must always be zero +// +// Note that the high bit (0x8000) must always be 0, since we use that +// to distinguish special request reply packets. +uint16_t statusFlags; + +// flag: send a pixel dump after the next read +bool reportPix = false; + -#else // ENABLE_TV_TIMER +// --------------------------------------------------------------------------- +// +// Calibration button state: +// 0 = not pushed +// 1 = pushed, not yet debounced +// 2 = pushed, debounced, waiting for hold time +// 3 = pushed, hold time completed - in calibration mode +int calBtnState = 0; + +// calibration button debounce timer +Timer calBtnTimer; + +// calibration button light state +int calBtnLit = false; + + +// --------------------------------------------------------------------------- +// +// Handle a configuration variable update. 'data' is the USB message we +// received from the host. +// +void configVarMsg(uint8_t *data) +{ + switch (data[1]) + { + case 1: + // USB identification (Vendor ID, Product ID) + cfg.usbVendorID = wireUI16(data+2); + cfg.usbProductID = wireUI16(data+4); + break; + + case 2: + // Pinscape Controller unit number - note that data[2] contains + // the nominal unit number, 1-16 + if (data[2] >= 1 && data[2] <= 16) + cfg.psUnitNo = data[2]; + break; + + case 3: + // Enable/disable joystick + cfg.joystickEnabled = data[2]; + break; + + case 4: + // Accelerometer orientation + cfg.orientation = data[2]; + break; + + case 5: + // Plunger sensor type + cfg.plunger.sensorType = data[2]; + break; + + case 6: + // Set plunger pin assignments + cfg.plunger.sensorPin[0] = wirePinName(data[2]); + cfg.plunger.sensorPin[1] = wirePinName(data[3]); + cfg.plunger.sensorPin[2] = wirePinName(data[4]); + cfg.plunger.sensorPin[3] = wirePinName(data[5]); + break; + + case 7: + // Plunger calibration button and indicator light pin assignments + cfg.plunger.cal.btn = wirePinName(data[2]); + cfg.plunger.cal.led = wirePinName(data[3]); + break; + + case 8: + // ZB Launch Ball setup + cfg.plunger.zbLaunchBall.port = (int)(unsigned char)data[2]; + cfg.plunger.zbLaunchBall.btn = (int)(unsigned char)data[3]; + cfg.plunger.zbLaunchBall.pushDistance = (float)wireUI16(data+4) / 1000.0; + break; + + case 9: + // TV ON setup + cfg.TVON.statusPin = wirePinName(data[2]); + cfg.TVON.latchPin = wirePinName(data[3]); + cfg.TVON.relayPin = wirePinName(data[4]); + cfg.TVON.delayTime = (float)wireUI16(data+5) / 100.0; + break; + + case 10: + // TLC5940NT PWM controller chip setup + cfg.tlc5940.nchips = (int)(unsigned char)data[2]; + cfg.tlc5940.sin = wirePinName(data[3]); + cfg.tlc5940.sclk = wirePinName(data[4]); + cfg.tlc5940.xlat = wirePinName(data[5]); + cfg.tlc5940.blank = wirePinName(data[6]); + cfg.tlc5940.gsclk = wirePinName(data[7]); + break; + + case 11: + // 74HC595 shift register chip setup + cfg.hc595.nchips = (int)(unsigned char)data[2]; + cfg.hc595.sin = wirePinName(data[3]); + cfg.hc595.sclk = wirePinName(data[4]); + cfg.hc595.latch = wirePinName(data[5]); + cfg.hc595.ena = wirePinName(data[6]); + break; + + case 12: + // button setup + { + // get the button number + int idx = data[2]; + + // if it's in range, set the button data + if (idx > 0 && idx <= MAX_BUTTONS) + { + // adjust to an array index + --idx; + + // set the values + cfg.button[idx].pin = data[3]; + cfg.button[idx].typ = data[4]; + cfg.button[idx].val = data[5]; + } + } + break; + + case 13: + // LedWiz output port setup + { + // get the port number + int idx = data[2]; + + // if it's in range, set the port data + if (idx > 0 && idx <= MAX_OUT_PORTS) + { + // adjust to an array index + --idx; + + // set the values + cfg.outPort[idx].typ = data[3]; + cfg.outPort[idx].pin = data[4]; + cfg.outPort[idx].flags = data[5]; + } + } + break; + } +} + +// --------------------------------------------------------------------------- +// +// Handle an input report from the USB host. Input reports use our extended +// LedWiz protocol. // -// TV timer not used - just provide a dummy startup function -void startTVTimer() { } -// -#endif // ENABLE_TV_TIMER +void handleInputMsg(HID_REPORT &report, USBJoystick &js, int &z) +{ + // all Led-Wiz reports are exactly 8 bytes + if (report.length == 8) + { + // LedWiz commands come in two varieties: SBA and PBA. An + // SBA is marked by the first byte having value 64 (0x40). In + // the real LedWiz protocol, any other value in the first byte + // means it's a PBA message. However, *valid* PBA messages + // always have a first byte (and in fact all 8 bytes) in the + // range 0-49 or 129-132. Anything else is invalid. We take + // advantage of this to implement private protocol extensions. + // So our full protocol is as follows: + // + // first byte = + // 0-48 -> LWZ-PBA + // 64 -> LWZ SBA + // 65 -> private control message; second byte specifies subtype + // 129-132 -> LWZ-PBA + // 200-228 -> extended bank brightness set for outputs N to N+6, where + // N is (first byte - 200)*7 + // other -> reserved for future use + // + uint8_t *data = report.data; + if (data[0] == 64) + { + // LWZ-SBA - first four bytes are bit-packed on/off flags + // for the outputs; 5th byte is the pulse speed (1-7) + //printf("LWZ-SBA %02x %02x %02x %02x ; %02x\r\n", + // data[1], data[2], data[3], data[4], data[5]); + + // update all on/off states + for (int i = 0, bit = 1, ri = 1 ; i < numLwOutputs ; ++i, bit <<= 1) + { + // figure the on/off state bit for this output + if (bit == 0x100) { + bit = 1; + ++ri; + } + + // set the on/off state + wizOn[i] = ((data[ri] & bit) != 0); + + // If the wizVal setting is 255, it means that this + // output was last set to a brightness value with the + // extended protocol. Return it to LedWiz control by + // rescaling the brightness setting to the LedWiz range + // and updating wizVal with the result. If it's any + // other value, it was previously set by a PBA message, + // so simply retain the last setting - in the normal + // LedWiz protocol, the "profile" (brightness) and on/off + // states are independent, so an SBA just turns an output + // on or off but retains its last brightness level. + if (wizVal[i] == 255) + wizVal[i] = (uint8_t)round(outLevel[i]*48); + } + + // set the flash speed - enforce the value range 1-7 + wizSpeed = data[5]; + if (wizSpeed < 1) + wizSpeed = 1; + else if (wizSpeed > 7) + wizSpeed = 7; + + // update the physical outputs + updateWizOuts(); + if (hc595 != 0) + hc595->update(); + + // reset the PBA counter + pbaIdx = 0; + } + else if (data[0] == 65) + { + // Private control message. This isn't an LedWiz message - it's + // an extension for this device. 65 is an invalid PBA setting, + // and isn't used for any other LedWiz message, so we appropriate + // it for our own private use. The first byte specifies the + // message type. + if (data[1] == 1) + { + // 1 = Old Set Configuration: + // data[2] = LedWiz unit number (0x00 to 0x0f) + // data[3] = feature enable bit mask: + // 0x01 = enable plunger sensor + + // get the new LedWiz unit number - this is 0-15, whereas we + // we save the *nominal* unit number 1-16 in the config + uint8_t newUnitNo = (data[2] & 0x0f) + 1; + // we'll need a reset if the LedWiz unit number is changing + bool needReset = (newUnitNo != cfg.psUnitNo); + + // set the configuration parameters from the message + cfg.psUnitNo = newUnitNo; + cfg.plunger.enabled = data[3] & 0x01; + + // update the status flags + statusFlags = (statusFlags & ~0x01) | (data[3] & 0x01); + + // if the plunger is no longer enabled, use 0 for z reports + if (!cfg.plunger.enabled) + z = 0; + + // save the configuration + saveConfigToFlash(); + + // reboot if necessary + if (needReset) + reboot(js); + } + else if (data[1] == 2) + { + // 2 = Calibrate plunger + // (No parameters) + + // enter calibration mode + calBtnState = 3; + calBtnTimer.reset(); + cfg.plunger.cal.reset(plungerSensor->npix); + } + else if (data[1] == 3) + { + // 3 = pixel dump + // (No parameters) + reportPix = true; + + // show purple until we finish sending the report + ledR = 0; + ledB = 0; + ledG = 1; + } + else if (data[1] == 4) + { + // 4 = hardware configuration query + // (No parameters) + wait_ms(1); + js.reportConfig( + numOutputs, + cfg.psUnitNo - 1, // report 0-15 range for unit number (we store 1-16 internally) + cfg.plunger.cal.zero, cfg.plunger.cal.max); + } + else if (data[1] == 5) + { + // 5 = all outputs off, reset to LedWiz defaults + allOutputsOff(); + } + else if (data[1] == 6) + { + // 6 = Save configuration to flash. + saveConfigToFlash(); + + // Reboot the microcontroller. Nearly all config changes + // require a reset, and a reset only takes a few seconds, + // so we don't bother tracking whether or not a reboot is + // really needed. + reboot(js); + } + } + else if (data[0] == 66) + { + // Extended protocol - Set configuration variable. + // The second byte of the message is the ID of the variable + // to update, and the remaining bytes give the new value, + // in a variable-dependent format. + configVarMsg(data); + } + else if (data[0] >= 200 && data[0] <= 228) + { + // Extended protocol - Extended output port brightness update. + // data[0]-200 gives us the bank of 7 outputs we're setting: + // 200 is outputs 0-6, 201 is outputs 7-13, 202 is 14-20, etc. + // The remaining bytes are brightness levels, 0-255, for the + // seven outputs in the selected bank. The LedWiz flashing + // modes aren't accessible in this message type; we can only + // set a fixed brightness, but in exchange we get 8-bit + // resolution rather than the paltry 0-48 scale that the real + // LedWiz uses. There's no separate on/off status for outputs + // adjusted with this message type, either, as there would be + // for a PBA message - setting a non-zero value immediately + // turns the output, overriding the last SBA setting. + // + // For outputs 0-31, this overrides any previous PBA/SBA + // settings for the port. Any subsequent PBA/SBA message will + // in turn override the setting made here. It's simple - the + // most recent message of either type takes precedence. For + // outputs above the LedWiz range, PBA/SBA messages can't + // address those ports anyway. + int i0 = (data[0] - 200)*7; + int i1 = i0 + 7 < numOutputs ? i0 + 7 : numOutputs; + for (int i = i0 ; i < i1 ; ++i) + { + // set the brightness level for the output + float b = data[i-i0+1]/255.0; + outLevel[i] = b; + + // if it's in the basic LedWiz output set, set the LedWiz + // profile value to 255, which means "use outLevel" + if (i < 32) + wizVal[i] = 255; + + // set the output + lwPin[i]->set(b); + } + + // update 74HC595 outputs, if attached + if (hc595 != 0) + hc595->update(); + } + else + { + // Everything else is LWZ-PBA. This is a full "profile" + // dump from the host for one bank of 8 outputs. Each + // byte sets one output in the current bank. The current + // bank is implied; the bank starts at 0 and is reset to 0 + // by any LWZ-SBA message, and is incremented to the next + // bank by each LWZ-PBA message. Our variable pbaIdx keeps + // track of our notion of the current bank. There's no direct + // way for the host to select the bank; it just has to count + // on us staying in sync. In practice, the host will always + // send a full set of 4 PBA messages in a row to set all 32 + // outputs. + // + // Note that a PBA implicitly overrides our extended profile + // messages (message prefix 200-219), because this sets the + // wizVal[] entry for each output, and that takes precedence + // over the extended protocol settings. + // + //printf("LWZ-PBA[%d] %02x %02x %02x %02x %02x %02x %02x %02x\r\n", + // pbaIdx, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]); + + // Update all output profile settings + for (int i = 0 ; i < 8 ; ++i) + wizVal[pbaIdx + i] = data[i]; + + // Update the physical LED state if this is the last bank. + // Note that hosts always send a full set of four PBA + // messages, so there's no need to do a physical update + // until we've received the last bank's PBA message. + if (pbaIdx == 24) + { + updateWizOuts(); + if (hc595 != 0) + hc595->update(); + pbaIdx = 0; + } + else + pbaIdx += 8; + } + } +} // --------------------------------------------------------------------------- // @@ -1732,83 +2166,58 @@ ledG = 1; ledB = 1; + // clear the I2C bus for the accelerometer + clear_i2c(); + + // load the saved configuration + loadConfigFromFlash(); + // start the TV timer, if applicable - startTVTimer(); + startTVTimer(cfg); // we're not connected/awake yet bool connected = false; time_t connectChangeTime = time(0); - // initialize the LedWiz ports - initLwOut(); - - // initialize the button input ports - initButtons(); + // create the plunger sensor interface + createPlunger(); - // start the TLC5940 clock, if present - tlc5940.start(); + // set up the TLC5940 interface and start the TLC5940 clock, if applicable + init_tlc5940(cfg); // enable the 74HC595 chips, if present - hc595.init(); - hc595.update(); - - // we don't need a reset yet - bool needReset = false; + init_hc595(cfg); - // clear the I2C bus for the accelerometer - clear_i2c(); - - // set up a flash memory controller - FreescaleIAP iap; - - // use the last sector of flash for our non-volatile memory structure - int flash_addr = (iap.flash_size() - SECTOR_SIZE); - NVM *flash = (NVM *)flash_addr; - NVM cfg; + // initialize the LedWiz ports + initLwOut(cfg); - // if the flash is valid, load it; otherwise initialize to defaults - if (flash->valid()) { - memcpy(&cfg, flash, sizeof(cfg)); - printf("Flash restored: plunger cal=%d, min=%d, zero=%d, max=%d\r\n", - cfg.d.plungerCal, cfg.d.plungerMin, cfg.d.plungerZero, cfg.d.plungerMax); - } - else { - printf("Factory reset\r\n"); - cfg.d.plungerCal = 0; - cfg.d.plungerMin = 0; // assume we can go all the way forward... - cfg.d.plungerMax = npix; // ...and all the way back - cfg.d.plungerZero = npix/6; // the rest position is usually around 1/2" back - cfg.d.ledWizUnitNo = DEFAULT_LEDWIZ_UNIT_NUMBER - 1; // unit numbering starts from 0 internally - cfg.d.plungerEnabled = PLUNGER_CODE_ENABLED; - } - + // start the TLC5940 clock + if (tlc5940 != 0) + tlc5940->start(); + + // initialize the button input ports + bool kbKeys = false; + initButtons(cfg, kbKeys); + // Create the joystick USB client. Note that we use the LedWiz unit // number from the saved configuration. - MyUSBJoystick js( - USB_VENDOR_ID, - MAKE_USB_PRODUCT_ID(USB_VENDOR_ID, USB_PRODUCT_ID, cfg.d.ledWizUnitNo), - USB_VERSION_NO); + MyUSBJoystick js(cfg.usbVendorID, cfg.usbProductID, USB_VERSION_NO, true, cfg.joystickEnabled, kbKeys); // last report timer - we use this to throttle reports, since VP // doesn't want to hear from us more than about every 10ms Timer reportTimer; reportTimer.start(); + + // set the initial status flags + statusFlags = (cfg.plunger.enabled ? 0x01 : 0x00); // initialize the calibration buttons, if present - DigitalIn *calBtn = (CAL_BUTTON_PIN == NC ? 0 : new DigitalIn(CAL_BUTTON_PIN)); - DigitalOut *calBtnLed = (CAL_BUTTON_LED == NC ? 0 : new DigitalOut(CAL_BUTTON_LED)); + DigitalIn *calBtn = (cfg.plunger.cal.btn == NC ? 0 : new DigitalIn(cfg.plunger.cal.btn)); + DigitalOut *calBtnLed = (cfg.plunger.cal.led == NC ? 0 : new DigitalOut(cfg.plunger.cal.led)); - // plunger calibration button debounce timer - Timer calBtnTimer; + // initialize the calibration button calBtnTimer.start(); - int calBtnLit = false; - - // Calibration button state: - // 0 = not pushed - // 1 = pushed, not yet debounced - // 2 = pushed, debounced, waiting for hold time - // 3 = pushed, hold time completed - in calibration mode - int calBtnState = 0; + calBtnState = 0; // set up a timer for our heartbeat indicator Timer hbTimer; @@ -1823,18 +2232,10 @@ // create the accelerometer object Accel accel(MMA8451_SCL_PIN, MMA8451_SDA_PIN, MMA8451_I2C_ADDRESS, MMA8451_INT_PIN); -#ifdef ENABLE_JOYSTICK // last accelerometer report, in joystick units (we report the nudge // acceleration via the joystick x & y axes, per the VP convention) int x = 0, y = 0; - // flag: send a pixel dump after the next read - bool reportPix = false; -#endif - - // create our plunger sensor object - PlungerSensor plungerSensor; - // last plunger report position, in 'npix' normalized pixel units int pos = 0; @@ -1869,6 +2270,9 @@ // the park position from state 0) int lbState = 0; + // button bit for ZB launch ball button + const uint32_t lbButtonBit = (1 << (cfg.plunger.zbLaunchBall.btn - 1)); + // Time since last lbState transition. Some of the states are time- // sensitive. In the "uncocked" state, we'll return to state 0 if // we remain in this state for more than a few milliseconds, since @@ -1929,17 +2333,7 @@ int firing = 0; // start the first CCD integration cycle - plungerSensor.init(); - - // Device status. We report this on each update so that the host config - // tool can detect our current settings. This is a bit mask consisting - // of these bits: - // 0x0001 -> plunger sensor enabled - // 0x8000 -> RESERVED - must always be zero - // - // Note that the high bit (0x8000) must always be 0, since we use that - // to distinguish special request reply packets. - uint16_t statusFlags = (cfg.d.plungerEnabled ? 0x01 : 0x00); + plungerSensor->init(); // we're all set up - now just loop, processing sensor reports and // host requests @@ -1954,224 +2348,7 @@ HID_REPORT report; for (int rr = 0 ; rr < 4 && js.readNB(&report) ; ++rr, wait_ms(1)) { - // all Led-Wiz reports are 8 bytes exactly - if (report.length == 8) - { - // LedWiz commands come in two varieties: SBA and PBA. An - // SBA is marked by the first byte having value 64 (0x40). In - // the real LedWiz protocol, any other value in the first byte - // means it's a PBA message. However, *valid* PBA messages - // always have a first byte (and in fact all 8 bytes) in the - // range 0-49 or 129-132. Anything else is invalid. We take - // advantage of this to implement private protocol extensions. - // So our full protocol is as follows: - // - // first byte = - // 0-48 -> LWZ-PBA - // 64 -> LWZ SBA - // 65 -> private control message; second byte specifies subtype - // 129-132 -> LWZ-PBA - // 200-219 -> extended bank brightness set for outputs N to N+6, where - // N is (first byte - 200)*7 - // other -> reserved for future use - // - uint8_t *data = report.data; - if (data[0] == 64) - { - // LWZ-SBA - first four bytes are bit-packed on/off flags - // for the outputs; 5th byte is the pulse speed (1-7) - //printf("LWZ-SBA %02x %02x %02x %02x ; %02x\r\n", - // data[1], data[2], data[3], data[4], data[5]); - - // update all on/off states - for (int i = 0, bit = 1, ri = 1 ; i < 32 ; ++i, bit <<= 1) - { - // figure the on/off state bit for this output - if (bit == 0x100) { - bit = 1; - ++ri; - } - - // set the on/off state - wizOn[i] = ((data[ri] & bit) != 0); - - // If the wizVal setting is 255, it means that this - // output was last set to a brightness value with the - // extended protocol. Return it to LedWiz control by - // rescaling the brightness setting to the LedWiz range - // and updating wizVal with the result. If it's any - // other value, it was previously set by a PBA message, - // so simply retain the last setting - in the normal - // LedWiz protocol, the "profile" (brightness) and on/off - // states are independent, so an SBA just turns an output - // on or off but retains its last brightness level. - if (wizVal[i] == 255) - wizVal[i] = (uint8_t)round(outLevel[i]*48); - } - - // set the flash speed - enforce the value range 1-7 - wizSpeed = data[5]; - if (wizSpeed < 1) - wizSpeed = 1; - else if (wizSpeed > 7) - wizSpeed = 7; - - // update the physical outputs - updateWizOuts(); - hc595.update(); - - // reset the PBA counter - pbaIdx = 0; - } - else if (data[0] == 65) - { - // Private control message. This isn't an LedWiz message - it's - // an extension for this device. 65 is an invalid PBA setting, - // and isn't used for any other LedWiz message, so we appropriate - // it for our own private use. The first byte specifies the - // message type. - if (data[1] == 1) - { - // 1 = Set Configuration: - // data[2] = LedWiz unit number (0x00 to 0x0f) - // data[3] = feature enable bit mask: - // 0x01 = enable plunger sensor - - // we'll need a reset if the LedWiz unit number is changing - uint8_t newUnitNo = data[2] & 0x0f; - needReset |= (newUnitNo != cfg.d.ledWizUnitNo); - - // set the configuration parameters from the message - cfg.d.ledWizUnitNo = newUnitNo; - cfg.d.plungerEnabled = data[3] & 0x01; - - // update the status flags - statusFlags = (statusFlags & ~0x01) | (data[3] & 0x01); - - // if the ccd is no longer enabled, use 0 for z reports - if (!cfg.d.plungerEnabled) - z = 0; - - // save the configuration - cfg.save(iap, flash_addr); - } -#ifdef ENABLE_JOYSTICK - else if (data[1] == 2) - { - // 2 = Calibrate plunger - // (No parameters) - - // enter calibration mode - calBtnState = 3; - calBtnTimer.reset(); - cfg.resetPlunger(); - } - else if (data[1] == 3) - { - // 3 = pixel dump - // (No parameters) - reportPix = true; - - // show purple until we finish sending the report - ledR = 0; - ledB = 0; - ledG = 1; - } - else if (data[1] == 4) - { - // 4 = hardware configuration query - // (No parameters) - wait_ms(1); - js.reportConfig(numOutputs, cfg.d.ledWizUnitNo); - } - else if (data[1] == 5) - { - // 5 = all outputs off, reset to LedWiz defaults - allOutputsOff(); - } -#endif // ENABLE_JOYSTICK - } - else if (data[0] >= 200 && data[0] < 220) - { - // Extended protocol - banked brightness update. - // data[0]-200 gives us the bank of 7 outputs we're setting: - // 200 is outputs 0-6, 201 is outputs 7-13, 202 is 14-20, etc. - // The remaining bytes are brightness levels, 0-255, for the - // seven outputs in the selected bank. The LedWiz flashing - // modes aren't accessible in this message type; we can only - // set a fixed brightness, but in exchange we get 8-bit - // resolution rather than the paltry 0-48 scale that the real - // LedWiz uses. There's no separate on/off status for outputs - // adjusted with this message type, either, as there would be - // for a PBA message - setting a non-zero value immediately - // turns the output, overriding the last SBA setting. - // - // For outputs 0-31, this overrides any previous PBA/SBA - // settings for the port. Any subsequent PBA/SBA message will - // in turn override the setting made here. It's simple - the - // most recent message of either type takes precedence. For - // outputs above the LedWiz range, PBA/SBA messages can't - // address those ports anyway. - int i0 = (data[0] - 200)*7; - int i1 = i0 + 7 < numOutputs ? i0 + 7 : numOutputs; - for (int i = i0 ; i < i1 ; ++i) - { - // set the brightness level for the output - float b = data[i-i0+1]/255.0; - outLevel[i] = b; - - // if it's in the basic LedWiz output set, set the LedWiz - // profile value to 255, which means "use outLevel" - if (i < 32) - wizVal[i] = 255; - - // set the output - lwPin[i]->set(b); - } - - // update 74HC595 outputs, if attached - hc595.update(); - } - else - { - // Everything else is LWZ-PBA. This is a full "profile" - // dump from the host for one bank of 8 outputs. Each - // byte sets one output in the current bank. The current - // bank is implied; the bank starts at 0 and is reset to 0 - // by any LWZ-SBA message, and is incremented to the next - // bank by each LWZ-PBA message. Our variable pbaIdx keeps - // track of our notion of the current bank. There's no direct - // way for the host to select the bank; it just has to count - // on us staying in sync. In practice, the host will always - // send a full set of 4 PBA messages in a row to set all 32 - // outputs. - // - // Note that a PBA implicitly overrides our extended profile - // messages (message prefix 200-219), because this sets the - // wizVal[] entry for each output, and that takes precedence - // over the extended protocol settings. - // - //printf("LWZ-PBA[%d] %02x %02x %02x %02x %02x %02x %02x %02x\r\n", - // pbaIdx, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]); - - // Update all output profile settings - for (int i = 0 ; i < 8 ; ++i) - wizVal[pbaIdx + i] = data[i]; - - // Update the physical LED state if this is the last bank. - // Note that hosts always send a full set of four PBA - // messages, so there's no need to do a physical update - // until we've received the last bank's PBA message. - if (pbaIdx == 24) - { - updateWizOuts(); - hc595.update(); - pbaIdx = 0; - } - else - pbaIdx += 8; - } - } + handleInputMsg(report, js, z); } // check for plunger calibration @@ -2201,7 +2378,9 @@ // enter calibration mode calBtnState = 3; calBtnTimer.reset(); - cfg.resetPlunger(); + + // reset the plunger calibration limits + cfg.plunger.cal.reset(plungerSensor->npix); } break; @@ -2227,8 +2406,8 @@ calBtnState = 0; // save the updated configuration - cfg.d.plungerCal = 1; - cfg.save(iap, flash_addr); + cfg.plunger.cal.calibrated = 1; + saveConfigToFlash(); } else if (calBtnState != 3) { @@ -2282,25 +2461,26 @@ // and the last plunger reading had the plunger pulled back at least // a bit, watch for plunger release events until it's time for our next // USB report. - if (!firing && cfg.d.plungerEnabled && z >= JOYMAX/6) + if (!firing && cfg.plunger.enabled && z >= JOYMAX/6) { // monitor the plunger until it's time for our next report while (reportTimer.read_ms() < 15) { // do a fast low-res scan; if it's at or past the zero point, // start a firing event - if (plungerSensor.lowResScan() <= cfg.d.plungerZero) + int pos0; + if (plungerSensor->lowResScan(pos0) && pos0 <= cfg.plunger.cal.zero) firing = 1; } } // read the plunger sensor, if it's enabled - if (cfg.d.plungerEnabled) + if (cfg.plunger.enabled) { // start with the previous reading, in case we don't have a // clear result on this frame int znew = z; - if (plungerSensor.highResScan(pos)) + if (plungerSensor->highResScan(pos)) { // We got a new reading. If we're in calibration mode, use it // to figure the new calibration, otherwise adjust the new reading @@ -2309,15 +2489,15 @@ { // Calibration mode. If this reading is outside of the current // calibration bounds, expand the bounds. - if (pos < cfg.d.plungerMin) - cfg.d.plungerMin = pos; - if (pos < cfg.d.plungerZero) - cfg.d.plungerZero = pos; - if (pos > cfg.d.plungerMax) - cfg.d.plungerMax = pos; + if (pos < cfg.plunger.cal.min) + cfg.plunger.cal.min = pos; + if (pos < cfg.plunger.cal.zero) + cfg.plunger.cal.zero = pos; + if (pos > cfg.plunger.cal.max) + cfg.plunger.cal.max = pos; // normalize to the full physical range while calibrating - znew = int(round(float(pos)/npix * JOYMAX)); + znew = int(round(float(pos)/plungerSensor->npix * JOYMAX)); } else { @@ -2329,10 +2509,10 @@ // plunger has a small amount of travel in the "push" direction, // since the barrel spring can be compressed slightly. Negative // values represent travel in the push direction. - if (pos > cfg.d.plungerMax) - pos = cfg.d.plungerMax; - znew = int(round(float(pos - cfg.d.plungerZero) - / (cfg.d.plungerMax - cfg.d.plungerZero + 1) * JOYMAX)); + if (pos > cfg.plunger.cal.max) + pos = cfg.plunger.cal.max; + znew = int(round(float(pos - cfg.plunger.cal.zero) + / (cfg.plunger.cal.max - cfg.plunger.cal.zero + 1) * JOYMAX)); } } @@ -2348,54 +2528,59 @@ // The plunger has moved forward since the previous report. // Watch it for a few more ms to see if we can get a stable // new position. - int pos0 = plungerSensor.lowResScan(); - int pos1 = pos0; - Timer tw; - tw.start(); - while (tw.read_ms() < 6) + int pos0; + if (plungerSensor->lowResScan(pos0)) { - // read the new position - int pos2 = plungerSensor.lowResScan(); - - // If it's stable over consecutive readings, stop looping. - // (Count it as stable if the position is within about 1/8". - // pos1 and pos2 are reported in pixels, so they range from - // 0 to npix. The overall travel of a standard plunger is - // about 3.2", so we have (npix/3.2) pixels per inch, hence - // 1/8" is (npix/3.2)*(1/8) pixels.) - if (abs(pos2 - pos1) < int(npix/(3.2*8))) - break; - - // If we've crossed the rest position, and we've moved by - // a minimum distance from where we starting this loop, begin - // a firing event. (We require a minimum distance to prevent - // spurious firing from random analog noise in the readings - // when the plunger is actually just sitting still at the - // rest position. If it's at rest, it's normal to see small - // random fluctuations in the analog reading +/- 1% or so - // from the 0 point, especially with a sensor like a - // potentionemeter that reports the position as a single - // analog voltage.) Note that we compare the latest reading - // to the first reading of the loop - we don't require the - // threshold motion over consecutive readings, but any time - // over the stability wait loop. - if (pos1 < cfg.d.plungerZero - && abs(pos2 - pos0) > int(npix/(3.2*8))) + int pos1 = pos0; + Timer tw; + tw.start(); + while (tw.read_ms() < 6) { - firing = 1; - break; + // read the new position + int pos2; + if (plungerSensor->lowResScan(pos2)) + { + // If it's stable over consecutive readings, stop looping. + // (Count it as stable if the position is within about 1/8". + // pos1 and pos2 are reported in pixels, so they range from + // 0 to npix. The overall travel of a standard plunger is + // about 3.2", so we have (npix/3.2) pixels per inch, hence + // 1/8" is (npix/3.2)*(1/8) pixels.) + if (abs(pos2 - pos1) < int(plungerSensor->npix/(3.2*8))) + break; + + // If we've crossed the rest position, and we've moved by + // a minimum distance from where we starting this loop, begin + // a firing event. (We require a minimum distance to prevent + // spurious firing from random analog noise in the readings + // when the plunger is actually just sitting still at the + // rest position. If it's at rest, it's normal to see small + // random fluctuations in the analog reading +/- 1% or so + // from the 0 point, especially with a sensor like a + // potentionemeter that reports the position as a single + // analog voltage.) Note that we compare the latest reading + // to the first reading of the loop - we don't require the + // threshold motion over consecutive readings, but any time + // over the stability wait loop. + if (pos1 < cfg.plunger.cal.zero + && abs(pos2 - pos0) > int(plungerSensor->npix/(3.2*8))) + { + firing = 1; + break; + } + + // the new reading is now the prior reading + pos1 = pos2; + } } - - // the new reading is now the prior reading - pos1 = pos2; } } // Check for a simulated Launch Ball button press, if enabled - if (ZBLaunchBallPort != 0) + if (cfg.plunger.zbLaunchBall.port != 0) { const int cockThreshold = JOYMAX/3; - const int pushThreshold = int(-JOYMAX/3 * LaunchBallPushDistance); + const int pushThreshold = int(-JOYMAX/3 * cfg.plunger.zbLaunchBall.pushDistance); int newState = lbState; switch (lbState) { @@ -2466,14 +2651,13 @@ } // change states if desired - const uint32_t lbButtonBit = (1 << (LaunchBallButton - 1)); if (newState != lbState) { // If we're entering Launch state OR we're entering the // Press-and-Hold state, AND the ZB Launch Ball LedWiz signal // is turned on, simulate a Launch Ball button press. if (((newState == 3 && lbState != 4) || newState == 5) - && wizOn[ZBLaunchBallPort-1]) + && wizOn[cfg.plunger.zbLaunchBall.port-1]) { lbBtnTimer.reset(); lbBtnTimer.start(); @@ -2482,7 +2666,7 @@ // if we're switching to state 0, release the button if (newState == 0) - simButtons &= ~(1 << (LaunchBallButton - 1)); + simButtons &= ~(1 << (cfg.plunger.zbLaunchBall.btn - 1)); // switch to the new state lbState = newState; @@ -2517,7 +2701,7 @@ int turnOff = false; // turn it off if the ZB Launch Ball signal is off - if (!wizOn[ZBLaunchBallPort-1]) + if (!wizOn[cfg.plunger.zbLaunchBall.port-1]) turnOff = true; // also turn it off if we're in state 3 or 4 ("Launch"), @@ -2618,16 +2802,15 @@ } // update the buttons - uint32_t buttons = readButtons(); + readButtons(cfg); -#ifdef ENABLE_JOYSTICK // If it's been long enough since our last USB status report, // send the new report. We throttle the report rate because // it can overwhelm the PC side if we report too frequently. // VP only wants to sync with the real world in 10ms intervals, - // so reporting more frequently only creates i/o overhead - // without doing anything to improve the simulation. - if (reportTimer.read_ms() > 15) + // so reporting more frequently creates I/O overhead without + // doing anything to improve the simulation. + if (cfg.joystickEnabled && reportTimer.read_ms() > 15) { // read the accelerometer int xa, ya; @@ -2648,13 +2831,33 @@ // ZB Launch Ball turns off the plunger position because it // tells us that the table has a Launch Ball button instead of // a traditional plunger. - int zrep = (ZBLaunchBallPort != 0 && wizOn[ZBLaunchBallPort-1] ? 0 : z); + int zrep = (cfg.plunger.zbLaunchBall.port != 0 && wizOn[cfg.plunger.zbLaunchBall.port-1] ? 0 : z); + + // rotate X and Y according to the device orientation in the cabinet + accelRotate(x, y); + + // send the joystick report + js.update(x, y, zrep, jsButtons | simButtons, statusFlags); - // Send the status report. Note that we have to map the X and Y - // axes from the accelerometer to match the Windows joystick axes. - // The mapping is determined according to the mounting direction - // set in config.h via the ORIENTATION_xxx macros. - js.update(JOY_X(x,y), JOY_Y(x,y), zrep, buttons | simButtons, statusFlags); + // send the keyboard report(s), if applicable + bool waitBeforeMedia = false; + if (kbState.changed) + { + js.kbUpdate(kbState.data); + kbState.changed = false; + waitBeforeMedia = true; + } + if (mediaState.changed) + { + // just sent a key report - give the channel a moment to clear before + // sending another report on its heels, to avoid clogging the pipe + if (waitBeforeMedia) + wait_us(1); + + // send the media key report + js.mediaUpdate(mediaState.data); + mediaState.changed = false; + } // we've just started a new report interval, so reset the timer reportTimer.reset(); @@ -2664,23 +2867,19 @@ if (reportPix) { // send the report - plungerSensor.sendExposureReport(js); + plungerSensor->sendExposureReport(js); // we have satisfied this request reportPix = false; } -#else // ENABLE_JOYSTICK - // We're a secondary controller, with no joystick reporting. Send - // a generic status report to the host periodically for the sake of - // the Windows config tool. - if (reportTimer.read_ms() > 200) + // If joystick reports are turned off, send a generic status report + // periodically for the sake of the Windows config tool. + if (!cfg.joystickEnabled && reportTimer.read_ms() > 200) { js.updateStatus(0); } -#endif // ENABLE_JOYSTICK - #ifdef DEBUG_PRINTF if (x != 0 || y != 0) printf("%d,%d\r\n", x, y); @@ -2727,16 +2926,7 @@ } } } - else if (needReset) - { - // connected, need to reset due to changes in config parameters - - // flash red/green - hb = !hb; - ledR = (hb ? 0 : 1); - ledG = (hb ? 1 : 0); - ledB = 0; - } - else if (cfg.d.plungerEnabled && !cfg.d.plungerCal) + else if (cfg.plunger.enabled && !cfg.plunger.cal.calibrated) { // connected, plunger calibration needed - flash yellow/green hb = !hb;
--- a/nullSensor.h Thu Dec 03 07:34:57 2015 +0000 +++ b/nullSensor.h Sat Dec 19 06:37:19 2015 +0000 @@ -3,16 +3,19 @@ // This file defines a class that provides the plunger sensor interface // that the main program expects, but with no physical sensor underneath. -const int npix = JOYMAX; +#ifndef NULLSENSOR_H +#define NULLSENSOR_H -class PlungerSensor +#include "plunger.h" + +class PlungerSensorNull: public PlungerSensor { public: - PlungerSensor() { } + PlungerSensorNull() { } - void init() { } - int lowResScan() { return 0; } - bool highResScan(int &pos) { return false; } - void sendExposureReport(USBJoystick &) { } + virtual void init() { } + virtual bool lowResScan(int &pos) { return false; } + virtual bool highResScan(int &pos) { return false; } }; +#endif /* NULLSENSOR_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nvm.h Sat Dec 19 06:37:19 2015 +0000 @@ -0,0 +1,111 @@ +// NVM - Non-Volatile Memory +// +// This module handles the storage of our configuration settings +// and calibration data in flash memory, which allows us to +// retrieve these settings after each power cycle. + + +#ifndef NVM_H +#define NVM_H + +#include "config.h" +#include "FreescaleIAP.h" + + +// Non-volatile memory (NVM) structure +// +// This structure defines the layout of our saved configuration +// and calibration data in flash memory. +// +// Hack alert! +// Our use of flash for this purpose is ad hoc and not supported +// by the mbed platform. mbed doesn't impose a file system on the +// KL25Z flash; it simply treats the flash as a raw storage space +// and assumes that the linker output is the only thing using it. +// So if we want to use the flash, we basically have to do it on +// the sly, by using space that the linker happens to leave unused. +// Fortunately, it's fairly easy to do this, because the flash +// is mapped in the obvious way, as a single contiguous block in +// the CPU memory space, and the linker does the obvious thing, +// storing its entire output in a single contiguous block starting +// at the lowest flash address. This means that all flash memory +// from (lowest flash address + length of linker output) to +// (highest flash address) is unused and available for our sneaky +// system. Unfortunately, there's no reliable way for the program +// to determine the length of the linker output, so we can't know +// where our available region starts. But we do know how much flash +// there is overall, so we know where the flash ends. We can +// therefore align our storage region at the end of memory and hope +// that it's small enough not to encroach on the linker space. We +// can actually do a little better than hope: the mbed tools tell us +// at the UI level how much flash the linker is using, even though it +// doesn't expose that information to us programmatically, so we can +// manually check that we have enough room. As of this writing, the +// configuration structure is much much smaller than the available +// leftover flash space, so we should be safe indefinitely, barring +// a major expansion of the configuration structure or code size. +// +// The boot loader seems to erase the entire flash space every time +// we load new firmware, so our configuration structure is lost +// when we update. Furthermore, since we explicitly choose to put +// the config structure in space that isn't initialized by the linker, +// we can't specify the new contents stored on these erasure events. +// To deal with this, we use a signature and checksum to check the +// integrity of the stored data. The erasure leaves deterministic +// values in memory unused by the linker, so we'll always detect +// an uninitialized config structure after an update. +// +struct NVM +{ +public: + // checksum - we use this to determine if the flash record + // has been properly initialized + uint32_t checksum; + + // signature and version reference values + static const uint32_t SIGNATURE = 0x4D4A522A; + static const uint16_t VERSION = 0x0003; + + // Is the data structure valid? We test the signature and + // checksum to determine if we've been properly stored. + int valid() const + { + return (d.sig == SIGNATURE + && d.vsn == VERSION + && d.sz == sizeof(NVM) + && checksum == CRC32(&d, sizeof(d))); + } + + // save to non-volatile memory + void save(FreescaleIAP &iap, int addr) + { + // update the checksum and structure size + d.sig = SIGNATURE; + d.vsn = VERSION; + d.sz = sizeof(NVM); + checksum = CRC32(&d, sizeof(d)); + + // figure the number of sectors required + int sectors = (sizeof(NVM) + SECTOR_SIZE - 1) / SECTOR_SIZE; + for (int i = 0 ; i < sectors ; ++i) + iap.erase_sector(addr + i*SECTOR_SIZE); + + // save the data + iap.program_flash(addr, this, sizeof(*this)); + } + + // stored data (excluding the checksum) + struct + { + // Signature, structure version, and structure size, as further + // verification that we have valid data. + uint32_t sig; + uint16_t vsn; + int sz; + + // configuration and calibration data + Config c; + } d; +}; + +#endif /* NVM_M */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plunger.h Sat Dec 19 06:37:19 2015 +0000 @@ -0,0 +1,79 @@ +// Plunger Sensor Interface +// +// This module defines the abstract interface to the plunger sensors. +// We support several different physical sensor types, so we need a +// common interface for use in the main code. +// + +#ifndef PLUNGER_H +#define PLUNGER_H + +class PlungerSensor +{ +public: + + PlungerSensor() { } + virtual ~PlungerSensor() { } + + // Number of "pixels" in the sensor's range. We use the term "pixels" + // here for historical reasons, namely that the first sensor we implemented + // was an imaging sensor that physically sensed the plunger position as + // a pixel coordinate in the image. But it's no longer the right word, + // since we support sensor types that have nothing to do with imaging. + // Even so, the function this serves is still applicable. Abstractly, + // it represents the physical resolution of the sensor, by giving the + // total number of quanta that the sensor can resolve over the entire + // range of travel of the plunger. For devices that inherently quantize + // the position reading at the physical level, such as imaging sensors + // and quadrature sensors, this should be set to the total number of + // quanta (resolvable position steps) over the range of travel. For + // devices with physically analog outputs, such as potentiometers or + // LVDTs, the reading still has to be digitized for us to be able to + // work with it, but this happens invisibly in the ADC, so the "pixel" + // scale is essentially arbitrary. Analog sensor types should thus + // simply use the maximum joystick report range, since that's the + // final scale we have to convert to - using a different scale would + // have no benefit and would just introduce rounding errors. + // + // This value MUST be initialized in the constructor. + int npix; + + // Initialize the physical sensor device. This is called at startup + // to set up the device for first use. + virtual void init() = 0; + + // Take a high-resolution reading. Sets pos to the current position, + // on a scale from 0 to npix: 0 is the maximum forward plunger position, + // and npix is the maximum retracted position, in terms of the sensor's + // extremes. This is a raw reading in terms of the sensor range; the + // caller is responsible for applying calibration data and scaling the + // result to the the joystick report range. + // + // Returns true on success, false on failure. Return false if it wasn't + // possible to take a good reading for any reason. + virtual bool highResScan(int &pos) = 0; + + // Take a low-resolution reading. This reports the result on the same + // 0..npix scale as highResScan(). Returns true on success, false on + // failure. + // + // The difference between the high-res and low-res scans is the amount + // of time it takes to complete the reading. The high-res scan is allowed + // to take about 10ms; a low-res scan take less than 1ms. For many + // sensors, either of these time scales would yield identical resolution; + // if that's the case, simply take a reading the same way in both functions. + // The distinction is for the benefit of sensors that need significantly + // longer to read at higher resolutions, such as image sensors that have + // to sample pixels serially. + virtual bool lowResScan(int &pos) = 0; + + // Send an exposure report to the joystick interface. This is specifically + // for image sensors, and should be omitted by other sensor types. For + // image sensors, this takes one exposure and sends all pixels to the host + // through special joystick reports. This is used for PC-side testing tools + // to let the user check the sensor installation by directly viewing its + // pixel output. + virtual void sendExposureReport(class USBJoystick &js) { } +}; + +#endif /* PLUNGER_H */
--- a/potSensor.h Thu Dec 03 07:34:57 2015 +0000 +++ b/potSensor.h Sat Dec 19 06:37:19 2015 +0000 @@ -5,30 +5,29 @@ #include "FastAnalogIn.h" -// The potentiometer doesn't have pixels, but we still need an -// integer range for normalizing our digitized voltage level values. -// The number here is fairly arbitrary; the higher it is, the finer -// the digitized steps. A 40" 1080p HDTV has about 55 pixels per inch -// on its physical display, so if the on-screen plunger is displayed -// at roughly the true physical size, it's about 3" on screen or about -// 165 pixels. So the minimum quantization size here should be about -// the same. For the pot sensor, this is just a scaling factor, -// so higher values don't cost us anything (unlike the CCD, where the -// read time is proportional to the number of pixels we sample). -const int npix = 4096; - -class PlungerSensor +class PlungerSensorPot: public PlungerSensor { public: - PlungerSensor() : pot(POT_PIN) + PlungerSensorPot(PinName ao) : pot(ao) { } - void init() + virtual void init() { + // The potentiometer doesn't have pixels, but we still need an + // integer range for normalizing our digitized voltage level values. + // The number here is fairly arbitrary; the higher it is, the finer + // the digitized steps. A 40" 1080p HDTV has about 55 pixels per inch + // on its physical display, so if the on-screen plunger is displayed + // at roughly the true physical size, it's about 3" on screen or about + // 165 pixels. So the minimum quantization size here should be about + // the same. For the pot sensor, this is just a scaling factor, + // so higher values don't cost us anything (unlike the CCD, where the + // read time is proportional to the number of pixels we sample). + npix = 4096; } - bool highResScan(int &pos) + virtual bool highResScan(int &pos) { // Take a few readings and use the average, to reduce the effect // of analog voltage fluctuations. The voltage range on the ADC @@ -43,20 +42,17 @@ return true; } - int lowResScan() + virtual bool lowResScan(int &pos) { // Use an average of several readings. Note that even though this // is nominally a "low res" scan, we can still afford to take an // average. The point of the low res interface is speed, and since // we only have one analog value to read, we can afford to take // several samples here even in the low res case. - return int((pot.read() + pot.read() + pot.read())/3.0 * npix); + pos = int((pot.read() + pot.read() + pot.read())/3.0 * npix); + return true; } - void sendExposureReport(USBJoystick &) - { - } - private: AnalogIn pot; };