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 39:b3815a1c3802, committed 2016-01-11
- Comitter:
- mjr
- Date:
- Mon Jan 11 21:08:36 2016 +0000
- Parent:
- 38:091e511ce8a0
- Child:
- 40:cc0d9814522b
- Commit message:
- USB fixes; accelerometer auto un-sticking with watchdog timer.
Changed in this revision
--- a/TLC5940/TLC5940.h Tue Jan 05 05:23:07 2016 +0000 +++ b/TLC5940/TLC5940.h Mon Jan 11 21:08:36 2016 +0000 @@ -173,10 +173,6 @@ // power-on, for example.) blank = 1; - // allocate the grayscale buffer, and set all outputs to fully off - gs = new unsigned short[nchips*16]; - memset(gs, 0, nchips*16*sizeof(gs[0])); - // Configure SPI format and speed. Note that KL25Z ONLY supports 8-bit // mode. The TLC5940 nominally requires 12-bit data blocks for the // grayscale levels, but SPI is ultimately just a bit-level serial format, @@ -202,8 +198,8 @@ xlat = 1; xlat = 0; - // Allocate a DMA buffer. The transfer on each cycle is 192 bits per - // chip = 24 bytes per chip. + // Allocate our DMA buffers. The transfer on each cycle is 192 bits per + // chip = 24 bytes per chip. dmabuf = new char[nchips*24]; memset(dmabuf, 0, nchips*24); @@ -257,20 +253,55 @@ ~TLC5940() { - delete [] gs; delete [] dmabuf; } - /** - * Set the next chunk of grayscale data to be sent - * @param data - Array of 16 bit shorts containing 16 12 bit grayscale data chunks per TLC5940 - * @note These must be in intervals of at least (1/GSCLK_SPEED) * 4096 to be sent + /* + * Set an output */ void set(int idx, unsigned short data) { - // store the data, and flag the pending update for the interrupt handler to carry out - gs[idx] = data; - newGSData = true; + // validate the index + if (idx >= 0 && idx < nchips*16) + { + // Figure the DMA buffer location of the data. The DMA buffer has the + // packed bit format that we send across the wire, with 12 bits per output, + // arranged from last output to first output (N = number of outputs = nchips*16): + // + // byte 0 = high 8 bits of output N-1 + // 1 = low 4 bits of output N-1 | high 4 bits of output N-2 + // 2 = low 8 bits of N-2 + // 3 = high 8 bits of N-3 + // 4 = low 4 bits of N-3 | high 4 bits of N-2 + // 5 = low 8bits of N-4 + // ... + // 24*nchips-3 = high 8 bits of output 1 + // 24*nchips-2 = low 4 bits of output 1 | high 4 bits of output 0 + // 24*nchips-1 = low 8 bits of output 0 + // + // So this update will affect two bytes. If the output number if even, we're + // in the high 4 + low 8 pair; if odd, we're in the high 8 + low 4 pair. + int di = nchips*24 - 3 - (3*(idx/2)); + if (idx & 1) + { + //printf("out %d = %d -> updating dma[%d] odd (xx x. ..)\r\n", idx, data, di); + // ODD = high 8 | low 4 + dmabuf[di] = uint8_t((data >> 4) & 0xff); + dmabuf[di+1] &= 0x0F; + dmabuf[di+1] |= uint8_t((data << 4) & 0xf0); + } + else + { + // EVEN = high 4 | low 8 + //printf("out %d = %d -> updating dma[%d] even (.. .x xx)\r\n", idx, data, di); + dmabuf[di+1] &= 0xF0; + dmabuf[di+1] |= uint8_t((data >> 8) & 0x0f); + dmabuf[di+2] = uint8_t(data & 0xff); + } + + // note the update + newGSData = true; + } } private: @@ -330,9 +361,7 @@ // did updates on some cycles and not others. By doing an // update on every cycle, we make the brightness reduction // uniform across time, which makes it less perceptible. - update(); sdma.start(nchips*24); - #else // DATA_UPDATE_INSIDE_BLANKING @@ -340,12 +369,14 @@ endBlank(); // if we have pending grayscale data, update the DMA data - if (newGSData) - update(); - - // send out the DMA contents - sdma.start(nchips*24); - + // if (newGSData) + { + // send out the DMA contents + sdma.start(nchips*24); + + // we don't have to send again until the next gs data cahnge + newGSData = false; + } #endif // DATA_UPDATE_INSIDE_BLANKING } @@ -377,47 +408,6 @@ resetTimer.attach(this, &TLC5940::reset, (1.0/GSCLK_SPEED)*4096.0); } - void update() - { - // Send new grayscale data to the TLC5940 chips. - // - // To do this, we set up our DMA buffer with the bytes formatted exactly - // as they will go across the wire, then kick off the transfer request with - // the DMA controller. We can then return from the interrupt and continue - // with other tasks while the DMA hardware handles the transfer for us. - // When the transfer is completed, the DMA controller will fire an - // interrupt, which will call our interrupt handler, which will finish - // the blanking cycle. - // - // The serial format orders the outputs from last to first (output #15 on - // the last chip in the daisy-chain to output #0 on the first chip). For - // each output, we send 12 bits containing the grayscale level (0 = fully - // off, 0xFFF = fully on). Bit order is most significant bit first. - // - // The KL25Z SPI can only send in 8-bit increments, so we need to divvy up - // the 12-bit outputs into 8-bit bytes. Each pair of 12-bit outputs adds up - // to 24 bits, which divides evenly into 3 bytes, so send each pairs of - // outputs as three bytes: - // - // [ element i+1 bits ] [ element i bits ] - // 11 10 9 8 7 6 5 4 3 2 1 0 11 10 9 8 7 6 5 4 3 2 1 0 - // [ first byte ] [ second byte ] [ third byte ] - for (int i = (16 * nchips) - 2, dst = 0 ; i >= 0 ; i -= 2) - { - // first byte - element i+1 bits 4-11 - dmabuf[dst++] = (((gs[i+1] & 0xFF0) >> 4) & 0xff); - - // second byte - element i+1 bits 0-3, then element i bits 8-11 - dmabuf[dst++] = ((((gs[i+1] & 0x00F) << 4) | ((gs[i] & 0xF00) >> 8)) & 0xFF); - - // third byte - element i bits 0-7 - dmabuf[dst++] = (gs[i] & 0x0FF); - } - - // we've now cleared the new GS data - newGSData = false; - } - // Interrupt handler for DMA completion. The DMA controller calls this // when it finishes with the transfer request we set up above. When the // transfer is done, we simply end the blanking cycle and start a new
--- a/USBDevice.lib Tue Jan 05 05:23:07 2016 +0000 +++ b/USBDevice.lib Mon Jan 11 21:08:36 2016 +0000 @@ -1,1 +1,1 @@ -http://mbed.org/users/mjr/code/USBDevice/#20bb47609697 +http://mbed.org/users/mjr/code/USBDevice/#c5ac4ccf6597
--- a/USBJoystick/USBJoystick.cpp Tue Jan 05 05:23:07 2016 +0000 +++ b/USBJoystick/USBJoystick.cpp Mon Jan 11 21:08:36 2016 +0000 @@ -421,7 +421,7 @@ C_RESERVED | C_SELF_POWERED, // bmAttributes C_POWER(0), // bMaxPowerHello World from Mbed - // INTERFACE 0 - JOYSTICK/LEDWIZ + // ***** INTERFACE 0 - JOYSTICK/LEDWIZ ****** INTERFACE_DESCRIPTOR_LENGTH, // bLength INTERFACE_DESCRIPTOR, // bDescriptorType 0x00, // bInterfaceNumber - first interface = 0 @@ -458,7 +458,7 @@ MSB(MAX_PACKET_SIZE_EPINT), // wMaxPacketSize (MSB) 1, // bInterval (milliseconds) - // INTERFACE 1 - KEYBOARD + // ****** INTERFACE 1 - KEYBOARD ****** INTERFACE_DESCRIPTOR_LENGTH, // bLength INTERFACE_DESCRIPTOR, // bDescriptorType 0x01, // bInterfaceNumber - second interface = 1 @@ -604,12 +604,16 @@ uint32_t bytesRead = 0; USBDevice::readEP(EP1OUT, buf.buf, &bytesRead, MAX_HID_REPORT_SIZE); +// printf("joy.read len=%d [%2x %2x %2x %2x %2x %2x %2x %2x], msg=[%2x %2x %2x %2x %2x %2x %2x %2x]\r\n", bytesRead, +// buf.buf[0], buf.buf[1], buf.buf[2], buf.buf[3], buf.buf[4], buf.buf[5], buf.buf[6], buf.buf[7], +// buf.msg.data[0], buf.msg.data[1], buf.msg.data[2], buf.msg.data[3], buf.msg.data[4], buf.msg.data[5], buf.msg.data[6], buf.msg.data[7]); + // if it's the right length, queue it to our circular buffer if (bytesRead == 8) lwbuf.write(buf.msg); // start the next read - return readStart(EP1OUT, 9); + return readStart(EP1OUT, MAX_HID_REPORT_SIZE); } // Handle incoming messages on the keyboard interface = endpoint 4. @@ -626,3 +630,4 @@ // start the next read return readStart(EP4OUT, MAX_HID_REPORT_SIZE); } +
--- a/USBJoystick/USBJoystick.h Tue Jan 05 05:23:07 2016 +0000 +++ b/USBJoystick/USBJoystick.h Mon Jan 11 21:08:36 2016 +0000 @@ -8,12 +8,20 @@ #include "USBHID.h" +// Bufferd incoming LedWiz message structure struct LedWizMsg { uint8_t data[8]; }; -// circular buffer for incoming reports +// Circular buffer for incoming reports. We write reports in the IRQ +// handler, and we read reports in the main loop in normal application +// (non-IRQ) context. +// +// The design is organically safe for IRQ threading; there are no critical +// sections. The IRQ context has exclusive access to the write pointer, +// and the application context has exclusive access to the read pointer, +// so there are no test-and-set or read-and-modify race conditions. template<class T, int cnt> class CircBuf { public: @@ -23,11 +31,13 @@ } // Read an item from the buffer. Returns true if an item was available, - // false if the buffer was empty. + // false if the buffer was empty. (Called in the main loop, in application + // context.) bool read(T &result) { if (iRead != iWrite) { + //{uint8_t *d = buf[iRead].data; printf("circ read [%02x %02x %02x %02x %02x %02x %02x %02x]\r\n", d[0],d[1],d[2],d[3],d[4],d[5],d[6],d[7]);} memcpy(&result, &buf[iRead], sizeof(T)); iRead = advance(iRead); return true; @@ -36,12 +46,14 @@ return false; } + // Write an item to the buffer. (Called in the IRQ handler, in interrupt + // context.) bool write(const T &item) { int nxt = advance(iWrite); if (nxt != iRead) { - memcpy(&buf[nxt], &item, sizeof(T)); + memcpy(&buf[iWrite], &item, sizeof(T)); iWrite = nxt; return true; } @@ -52,7 +64,8 @@ private: int advance(int i) { - return i + 1 >= cnt ? 0 : i + 1; + ++i; + return i < cnt ? i : 0; } int iRead; @@ -60,6 +73,10 @@ T buf[cnt]; }; +// interface IDs +const uint8_t IFC_ID_JS = 0; // joystick + LedWiz interface +const uint8_t IFC_ID_KB = 1; // keyboard interface + // keyboard interface report IDs const uint8_t REPORT_ID_KB = 1; const uint8_t REPORT_ID_MEDIA = 2; @@ -151,7 +168,6 @@ _init(); this->useKB = useKB; this->enableJoystick = enableJoystick; - reqTimer.start(); connect(waitForConnect); }; @@ -161,6 +177,11 @@ return lwbuf.read(msg); } + /* get the idle time settings, in milliseconds */ + uint32_t getKbIdle() const { return kbIdleTime * 4UL; } + uint32_t getMediaIdle() const { return mediaIdleTime * 4UL; } + + /** * 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 @@ -251,6 +272,35 @@ virtual uint8_t *stringIserialDesc(); virtual uint8_t *stringIproductDesc(); + /* set/get idle time */ + virtual void setIdleTime(int ifc, int rptid, int t) + { + // Remember the new value if operating on the keyboard. Remember + // separate keyboard and media control idle times, in case the + // host wants separate report rates. + if (ifc == IFC_ID_KB) + { + if (rptid == REPORT_ID_KB) + kbIdleTime = t; + else if (rptid == REPORT_ID_MEDIA) + mediaIdleTime = t; + } + } + virtual uint8_t getIdleTime(int ifc, int rptid) + { + // Return the kb idle time if the kb interface is the one requested. + if (ifc == IFC_ID_KB) + { + if (rptid == REPORT_ID_KB) + return kbIdleTime; + if (rptid == REPORT_ID_MEDIA) + return mediaIdleTime; + } + + // we don't use idle times for other interfaces or report types + return 0; + } + /* callback overrides */ virtual bool USBCallback_setConfiguration(uint8_t configuration); virtual bool USBCallback_setInterface(uint16_t interface, uint8_t alternate) @@ -260,9 +310,14 @@ virtual bool EP4_OUT_callback(); private: - Timer reqTimer; + + // Incoming LedWiz message buffer. Each LedWiz message is exactly 8 bytes. + CircBuf<LedWizMsg, 64> lwbuf; + bool enableJoystick; bool useKB; + uint8_t kbIdleTime; + uint8_t mediaIdleTime; int16_t _x; int16_t _y; int16_t _z; @@ -270,9 +325,6 @@ uint16_t _buttonsHi; uint16_t _status; - // Incoming LedWiz message buffer. Each LedWiz message is exactly 8 bytes. - CircBuf<LedWizMsg, 64> lwbuf; - void _init(); };
--- a/USBProtocol.h Tue Jan 05 05:23:07 2016 +0000 +++ b/USBProtocol.h Mon Jan 11 21:08:36 2016 +0000 @@ -7,12 +7,34 @@ // ------ OUTGOING MESSAGES (DEVICE TO HOST) ------ // +// 1. Joystick reports // 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. +// plug-and-play installation experience on Windows. Our joystick report +// looks like this (see USBJoystick.cpp for the formal HID report descriptor): // +// ss status bits: 0x01 -> plunger enabled +// 00 always zero for joystick reports +// bb joystick buttons, low byte (buttons 1-16, 1 bit per button) +// bb joystick buttons, high byte (buttons 17-32) +// xx low byte of X position = nudge/accelerometer X axis +// xx high byte of X position +// yy low byte of Y position = nudge/accelerometer Y axis +// yy high byte of Y position +// zz low byte of Z position = plunger position +// zz high byte of Z position +// +// The X, Y, and Z values are 16-bit signed integers. The accelerometer +// values are on an abstract scale, where 0 represents no acceleration, +// negative maximum represents -1g on that axis, and positive maximum +// represents +1g on that axis. For the plunger position, 0 is the park +// position (the rest position of the plunger) and positive values represent +// retracted (pulled back) positions. A negative value means that the plunger +// is pushed forward of the park position. +// +// 2. Special reports // 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 @@ -20,15 +42,24 @@ // 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 +// Normal joystick reports always have 0 in the high bit of the 2nd 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: +// 2A. Plunger sensor pixel dump +// Software on the PC can request a full read of the pixels from the plunger +// image sensor (if an imaging sensor type is being used) by sending custom +// protocol message 65 3 (see below). Normally, the pixels from the image +// sensor are read and processed on the controller device without being sent +// to the PC; the PC only receives the plunger position reading obtained from +// analyzing the image data. For debugging and setup purposes, software on +// the host can use this special report to obtain the full image pixel array. +// The image sensors we use have too many pixels to fit into one report, so +// we have to send a series of reports to transmit the full image. We send +// as many reports as necessary to transmit the full image. Each report +// looks like this: // // bytes 0:1 = 11-bit index, with high 5 bits set to 10000. For // example, 0x04 0x80 indicates index 4. This is the @@ -38,8 +69,9 @@ // 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: +// 2B. Configuration query. +// This is requested by sending custom protocol message 65 4 (see below). +// In reponse, the device 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 @@ -157,6 +189,9 @@ // 65 -> Miscellaneous control message. The second byte specifies the specific // operation: // +// 0 -> No Op - does nothing. (This can be used to send a test message on the +// USB endpoint.) +// // 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:
--- a/Updates.h Tue Jan 05 05:23:07 2016 +0000 +++ b/Updates.h Mon Jan 11 21:08:36 2016 +0000 @@ -29,4 +29,49 @@ // moving at high speed, allowing for more realistic plunger action on the // virtual side. // -// +// Keyboard mappings for buttons: button inputs can now be mapped to keyboard +// keys. Joystick buttons are of course also still supported. Some software on +// the PC side is easier to configure for keyboard input than for joystick +// input, so many users might prefer to map some or all buttons to keys. If +// you map any buttons to keyboard input, the controller device will have +// two entries in the Windows Device Manager list, one as a joystick and +// the other as a keyboard. This is automatic; the keyboard interface will +// appear automatically if you have any keyboard keys mapped, otherwise only +// the joystick interface will appear. +// +// "Pulse" buttons: you can now designate individual button inputs as pulse +// mode buttons. When a button is configured in pulse mode, the software +// translates each ON/OFF or OFF/ON transition in the physical switch to a +// short virtual key press. This is especially designed to make it easier +// to wire a coin door switch, but could be used for other purposes as well. +// For the coin door, the VPinMAME software uses the End key to *toggle* the +// open/closed state of the door in the simulation, but it's much easier +// to wire a physical on/off switch to the door instead. Pulse mode bridges +// this gap by translating the on/off switch state to key presses. When +// you open the door, the switch will go from OFF to ON, so the controller +// will send one short key press, causing VPinMAME to toggle the simulated +// door to OPEN. When you close the door, the switch will go from ON to +// OFF, which will make the controller send another short key press, which +// in turn will make VPinMAME toggle the simulated door state to CLOSED. +// There are other ways to solve this problem (VP cab builders have come +// up with various physical devices and electronic timer circuits to deal +// with it), but the software approach implemented here is a lot simpler +// to set up and is very reliable. +// +// Night mode: you can now put the device in "night mode" by configuring a +// physical button input to activate the mode, or by sending a command from +// the PC config tool software. When night mode is activated, outputs that +// you designate as "noisemaker" devices are disabled. You can designate +// any outputs as noisy or not. This feature is designed to let you use your +// virtual pinball machine during quiet hours (e.g., late at night) without +// disturbing housemates or neighbors with noise from flippers, knockers, +// shaker motors, and so on. You can designate outputs individually as +// noisy, so you can still enjoy the rest of your feedback features during +// night play (e.g., flashers and other lighting effects). +// +// USB fixes: the low-level USB device code had some serious bugs that only +// very occasionally manifested in past versions, but became much more +// frequently triggered due to other changes in this release (particularly +// the USB keyboard input feature). These should now be fixed, so the USB +// connection should now be very reliable. +//
--- a/config.h Tue Jan 05 05:23:07 2016 +0000 +++ b/config.h Mon Jan 11 21:08:36 2016 +0000 @@ -216,6 +216,7 @@ button[20].typ = BtnTypeMedia; button[20].val = 0x01; // vol up + #endif #if 1 // $$$
--- a/main.cpp Tue Jan 05 05:23:07 2016 +0000 +++ b/main.cpp Mon Jan 11 21:08:36 2016 +0000 @@ -1063,6 +1063,11 @@ bs->mediakey = val; kbKeys = true; break; + + case BtnTypeSpecial: + // special key + bs->special = val; + break; } } } @@ -1438,6 +1443,26 @@ // reset the timer tCenter_.reset(); + + // If we haven't seen an interrupt in a while, do an explicit read to + // "unstick" the device. The device can become stuck - which is to say, + // it will stop delivering data-ready interrupts - if we fail to service + // one data-ready interrupt before the next one occurs. Reading a sample + // will clear up this overrun condition and allow normal interrupt + // generation to continue. + // + // Note that this stuck condition *shouldn't* ever occur - if it does, + // it means that we're spending a long period with interrupts disabled + // (either in a critical section or in another interrupt handler), which + // will likely cause other worse problems beyond the sticky accelerometer. + // Even so, it's easy to detect and correct, so we'll do so for the sake + // of making the system more fault-tolerant. + if (tInt_.read() > 1.0f) + { + printf("unwedging the accelerometer\r\n"); + float x, y, z; + mma_.getAccXYZ(x, y, z); + } } // report our integrated velocity reading in x,y @@ -1479,7 +1504,7 @@ mma_.getAccXYZ(x, y, z); // calculate the time since the last interrupt - float dt = tInt_.read_us()/1.0e6; + float dt = tInt_.read(); tInt_.reset(); // integrate the time slice from the previous reading to this reading @@ -2250,7 +2275,7 @@ // Handle an input report from the USB host. Input reports use our extended // LedWiz protocol. // -void handleInputMsg(uint8_t data[8], USBJoystick &js, int &z) +void handleInputMsg(LedWizMsg &lwm, USBJoystick &js, int &z) { // LedWiz commands come in two varieties: SBA and PBA. An // SBA is marked by the first byte having value 64 (0x40). In @@ -2270,6 +2295,7 @@ // N is (first byte - 200)*7 // other -> reserved for future use // + uint8_t *data = lwm.data; if (data[0] == 64) { // LWZ-SBA - first four bytes are bit-packed on/off flags @@ -2325,40 +2351,47 @@ // 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) + switch (data[1]) { + case 0: + // No Op + break; + + case 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; + { + + // 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); + } + break; - // 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) - { + case 2: // 2 = Calibrate plunger // (No parameters) @@ -2366,33 +2399,32 @@ calBtnState = 3; calBtnTimer.reset(); cfg.plunger.cal.reset(plungerSensor->npix); - } - else if (data[1] == 3) - { + break; + + case 3: // 3 = pixel dump // (No parameters) reportPix = true; // show purple until we finish sending the report diagLED(1, 0, 1); - } - else if (data[1] == 4) - { + break; + + case 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) - { + break; + + case 5: // 5 = all outputs off, reset to LedWiz defaults allOutputsOff(); - } - else if (data[1] == 6) - { + break; + + case 6: // 6 = Save configuration to flash. saveConfigToFlash(); @@ -2401,6 +2433,7 @@ // so we don't bother tracking whether or not a reboot is // really needed. reboot(js); + break; } } else if (data[0] == 66) @@ -2518,9 +2551,10 @@ // int main(void) { - printf("\r\nPinscape Controller starting\r\n"); // $$$ debug + printf("\r\nPinscape Controller starting\r\n"); + // memory config debugging: {int *a = new int; printf("Stack=%lx, heap=%lx, free=%ld\r\n", (long)&a, (long)a, (long)&a - (long)a);} - // clear the I2C bus for the accelerometer + // clear the I2C bus (for the accelerometer) clear_i2c(); // load the saved configuration @@ -2715,12 +2749,12 @@ // host requests for (;;) { - // Process incoming reports - LedWizMsg lwmsg; - for (int rr = 0 ; rr < 64 && js.readLedWizMsg(lwmsg) ; ++rr) - handleInputMsg(lwmsg.data, js, z); + // Process incoming reports on the joystick interface. This channel + // is used for LedWiz commands are our extended protocol commands. + LedWizMsg lwm; + while (js.readLedWizMsg(lwm)) + handleInputMsg(lwm, js, z); - // check for plunger calibration if (calBtn != 0 && !calBtn->read()) { @@ -3300,20 +3334,24 @@ } else if (jsOKTimer.read() > 5) { - // too long without a USB report - show red/yellow + // USB freeze - show red/yellow. + // Our outgoing joystick messages aren't going through, even though we + // think we're still connected. This indicates that one or more of our + // USB endpoints have stopped working, which can happen as a result of + // bugs in the USB HAL or latency responding to a USB IRQ. Show a + // distinctive diagnostic flash to signal the error. I haven't found a + // way to recover from this class of error other than rebooting the MCU, + // so the goal is to fix the HAL so that this error never happens. This + // flash pattern is thus for debugging purposes only; hopefully it won't + // ever occur in a real installation. static bool dumped; if (!dumped) { + // If we haven't already, dump the USB HAL status to the debug console, + // in case it helps identify the reason for the endpoint failure. extern void USBDeviceStatusDump(void); USBDeviceStatusDump(); dumped = true; } - extern bool USB_DMAERR; - if (USB_DMAERR) { - printf("USB DMAERR DETECTED!\r\n"); - // js.disconnect(); - // js.connect(); - // USB_DMAERR = false; - } jsOKTimer.stop(); hb = !hb; diagLED(1, hb, 0);