work in progress
Dependencies: FastAnalogIn FastIO USBDevice mbed FastPWM SimpleDMA
Fork of Pinscape_Controller by
Diff: main.cpp
- Revision:
- 35:d832bcab089e
- Parent:
- 32:6e9902f06f48
- Child:
- 36:6b981a2afab7
--- a/main.cpp Sat Sep 26 02:15:59 2015 +0000 +++ b/main.cpp Wed Oct 21 21:53:07 2015 +0000 @@ -143,10 +143,24 @@ // The software can control a set of daisy-chained TLC5940 chips, which provide // 16 PWM outputs per chip. Two of these chips give you the full complement // of 32 output ports of an actual LedWiz, and four give you 64 ports, which -// should be plenty for nearly any virtual pinball project. +// should be plenty for nearly any virtual pinball project. A private, extended +// version of the LedWiz protocol lets the host control the extra outputs, up to +// 128 outputs per KL25Z (8 TLC5940s). To take advantage of the extra outputs +// on the PC side, you need software that knows about the protocol extensions, +// which means you need the latest version of DirectOutput Framework (DOF). VP +// uses DOF for its output, so VP will be able to use the added ports without any +// extra work on your part. Older software (e.g., Future Pinball) that doesn't +// use DOF will still be able to use the LedWiz-compatible protocol, so it'll be +// able to control your first 32 ports (numbered 1-32 in the LedWiz scheme), but +// older software won't be able to address higher-numbered ports. That shouldn't +// be a problem because older software wouldn't know what to do with the extra +// devices anyway - FP, for example, is limited to a pre-defined set of outputs. +// As long as you put the most common devices on the first 32 outputs, and use +// 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. // -// -// The on-board LED on the KL25Z flashes to indicate the current device status: +// STATUS LIGHTS: The on-board LED on the KL25Z flashes to indicate the current +// device status. The flash patterns are: // // two short red flashes = the device is powered but hasn't successfully // connected to the host via USB (either it's not physically connected @@ -169,14 +183,14 @@ // // alternating blue/green = everything's working // -// Software configuration: you can change option settings by sending special +// 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 1 = 1 (0x01) // byte 2 = new LedWiz unit number, 0x01 to 0x0f // byte 3 = feature enable bit mask: // 0x01 = enable CCD (default = on) @@ -188,14 +202,14 @@ // // length = 8 bytes // byte 0 = 65 (0x41) -// byte 1 = 2 (0x02) +// 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) +// byte 1 = 3 (0x03) // // We'll respond with a series of special reports giving the exposure status. // Each report has the following structure: @@ -215,7 +229,33 @@ // 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) + + #include "mbed.h" #include "math.h" #include "USBJoystick.h" @@ -225,7 +265,6 @@ #include "crc32.h" #include "TLC5940.h" -// our local configuration file #define DECL_EXTERNS #include "config.h" @@ -243,35 +282,27 @@ inline float round(float x) { return x > 0 ? floor(x + 0.5) : ceil(x - 0.5); } -// --------------------------------------------------------------------------- -// USB device vendor ID, product ID, and version. +// -------------------------------------------------------------------------- +// +// USB product version number // -// We use the vendor ID for the LedWiz, so that the PC-side software can -// identify us as capable of performing LedWiz commands. The LedWiz uses -// a product ID value from 0xF0 to 0xFF; the last four bits identify the -// unit number (e.g., product ID 0xF7 means unit #7). This allows multiple -// LedWiz units to be installed in a single PC; the software on the PC side -// uses the unit number to route commands to the devices attached to each -// unit. On the real LedWiz, the unit number must be set in the firmware -// at the factory; it's not configurable by the end user. Most LedWiz's -// ship with the unit number set to 0, but the vendor will set different -// unit numbers if requested at the time of purchase. So if you have a -// single LedWiz already installed in your cabinet, and you didn't ask for -// a non-default unit number, your existing LedWiz will be unit 0. -// -// Note that the USB_PRODUCT_ID value set here omits the unit number. We -// take the unit number from the saved configuration. We provide a -// configuration command that can be sent via the USB connection to change -// the unit number, so that users can select the unit number without having -// to install a different version of the software. We'll combine the base -// product ID here with the unit number to get the actual product ID that -// we send to the USB controller. -const uint16_t USB_VENDOR_ID = 0xFAFA; -const uint16_t USB_PRODUCT_ID = 0x00F0; -const uint16_t USB_VERSION_NO = 0x0006; +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)) + + +// -------------------------------------------------------------------------- +// // Joystick axis report range - we report from -JOYMAX to +JOYMAX +// #define JOYMAX 4096 // -------------------------------------------------------------------------- @@ -357,16 +388,6 @@ // for 32 outputs). Every port in this mode has full PWM support. // -// Figure the number of outputs. If we're in the default LedWiz mode, -// we have a fixed set of 32 outputs. If we're in TLC5940 enhanced mode, -// we have 16 outputs per chip. To simplify the LedWiz compatibility code, -// always use a minimum of 32 outputs even if we have fewer than two of the -// TLC5940 chips. -#if !defined(ENABLE_TLC5940) || (TLC_NCHIPS) < 2 -# define NUM_OUTPUTS 32 -#else -# define NUM_OUTPUTS ((TLC5940_NCHIPS)*16) -#endif // Current starting output index for "PBA" messages from the PC (using // the LedWiz USB protocol). Each PBA message implicitly uses the @@ -385,10 +406,27 @@ 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 +{ +public: + LwUnusedOut() { } + virtual void set(float val) { } +}; -#ifdef ENABLE_TLC5940 -// The TLC5940 interface object. +#if TLC5940_NCHIPS +// +// 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); @@ -410,7 +448,31 @@ float prv; }; -#else // ENABLE_TLC5940 +// Inverted voltage version of TLC5940 class (Active Low - logical "on" +// is represented by 0V on output) +class Lw5940OutInv: public Lw5940Out +{ +public: + Lw5940OutInv(int idx) : Lw5940Out(idx) { } + virtual void set(float val) { Lw5940Out::set(1.0 - val); } +}; + +#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) { } +}; + +class Lw5940OutInv: public Lw5940Out +{ +public: + Lw5940OutInv(int idx) : Lw5940Out(idx) { } +}; + +#endif // TLC5940_NCHIPS // // Default LedWiz mode - using on-board GPIO ports. In this mode, we @@ -434,6 +496,16 @@ float prv; }; +// Inverted voltage PWM-capable GPIO port. This is the Active Low +// version of the port - logical "on" is represnted by 0V on the +// GPIO pin. +class LwPwmOutInv: public LwPwmOut +{ +public: + LwPwmOutInv(PinName pin) : LwPwmOut(pin) { } + virtual void set(float val) { LwPwmOut::set(1.0 - val); } +}; + // LwOut class for a Digital-Only (Non-PWM) GPIO port class LwDigOut: public LwOut { @@ -448,21 +520,12 @@ float prv; }; -#endif // ENABLE_TLC5940 - -// 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 +// Inverted voltage digital out +class LwDigOutInv: public LwDigOut { public: - LwUnusedOut() { } - virtual void set(float val) { } + LwDigOutInv(PinName pin) : LwDigOut(pin) { } + virtual void set(float val) { LwDigOut::set(1.0 - val); } }; // Array of output physical pin assignments. This array is indexed @@ -472,48 +535,127 @@ // 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. -static LwOut *lwPin[NUM_OUTPUTS]; +static int numOutputs; +static LwOut **lwPin; + +// 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, +// this is derived from the LedWiz state, and is updated on each pulse +// timer interrupt for lights in flashing states. For outputs set by +// extended protocol messages, this is simply the brightness last set. +static float *outLevel; // initialize the output pin array void initLwOut() { - for (int i = 0 ; i < countof(lwPin) ; ++i) + // 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, we use our own custom + // extended protocol that allows for many more ports. In this case, + // we have 16 outputs per TLC5940, plus any assigned to GPIO pins. + + // start with 16 ports per TLC5940 + numOutputs = TLC5940_NCHIPS * 16; + + // add outputs assigned to GPIO pins in the LedWiz-to-pin mapping + int i; + for (i = 0 ; i < countof(ledWizPortMap) ; ++i) { -#ifdef ENABLE_TLC5940 - // Set up a TLC5940 output. If the output is within range of - // the connected number of chips (16 outputs per chip), assign it - // to the current index, otherwise leave it unattached. - if (i < (TLC5940_NCHIPS)*16) - lwPin[i] = new Lw5940Out(i); - else - lwPin[i] = new LwUnusedOut(); + if (ledWizPortMap[i].pin != NC) + ++numOutputs; + } + + // 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; + + // allocate the pin array + lwPin = new LwOut*[numOutputs]; + + // allocate the current brightness array + outLevel = new float[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); -#else // ENABLE_TLC5940 - // Set up the GPIO pin. If the pin is not connected ("NC" in the - // pin map), set up a dummy "unused" output for it. If it's a - // real pin, set up a PWM-capable or Digital-Only output handler - // object, according to the pin type in the map. - PinName p = (i < countof(ledWizPortMap) ? ledWizPortMap[i].pin : NC); - if (p == NC) - lwPin[i] = new LwUnusedOut(); - else if (ledWizPortMap[i].isPWM) - lwPin[i] = new LwPwmOut(p); - else - lwPin[i] = new LwDigOut(p); + // assign all pins from the port map in config.h + for (i = 0 ; i < countof(ledWizPortMap) ; ++i) + { + // Figure out which type of pin to assign to this port: + // + // - If it has a valid GPIO pin (other than "NC"), create a PWM + // or Digital output pin according to the port type. + // + // - If the pin has a TLC5940 port number, set up a TLC5940 port. + // + // - Otherwise, the pin is unconnected, so set up an unused out. + // + PinName p = ledWizPortMap[i].pin; + int flags = ledWizPortMap[i].flags; + int tlcPortNum = ledWizPortMap[i].tlcPortNum; + int isPwm = flags & PORT_IS_PWM; + int activeLow = flags & PORT_ACTIVE_LOW; + if (p != NC) + { + // This output is a GPIO - set it up as PWM or Digital, and + // active high or low, as marked + if (isPwm) + lwPin[i] = activeLow ? new LwPwmOutInv(p) : new LwPwmOut(p); + else + lwPin[i] = activeLow ? new LwDigOutInv(p) : new LwDigOut(p); + } + else if (tlcPortNum != 0) + { + // It's a TLC5940 port. Note that the port numbering in the map + // starts at 1, but internally we number the ports starting at 0, + // so subtract one to get the correct numbering. + lwPin[i] = activeLow ? new Lw5940OutInv(tlcPortNum-1) : new Lw5940Out(tlcPortNum-1); -#endif // ENABLE_TLC5940 - + // mark this port as used, so that we don't reassign it when we + // fill out the remaining unassigned ports + tlcasi[tlcPortNum-1] = 1; + } + else + { + // it's not a GPIO or TLC5940 port -> it's not connected + lwPin[i] = new LwUnusedOut(); + } + lwPin[i]->set(0); } + + // find the next unassigned tlc port + int tlcnxt; + for (tlcnxt = 0 ; tlcnxt < TLC5940_NCHIPS*16 && tlcasi[tlcnxt] ; ++tlcnxt) ; + + // assign any remaining pins + for ( ; i < numOutputs ; ++i) + { + // If we have any more unassigned TLC5940 outputs, assign this LedWiz + // port to the next available TLC5940 output. Otherwise make it + // unconnected. + if (tlcnxt < TLC5940_NCHIPS*16) + { + // we have a TLC5940 output available - assign it + lwPin[i] = new Lw5940Out(tlcnxt); + + // find the next unassigned TLC5940 output, for the next port + for (++tlcnxt ; tlcnxt < TLC5940_NCHIPS*16 && tlcasi[tlcnxt] ; ++tlcnxt) ; + } + else + { + // no more ports available - set up this port as unconnected + lwPin[i] = new LwUnusedOut(); + } + } + + // done with the temporary TLC5940 port assignment list + delete [] tlcasi; } -// 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, -// this is derived from te LedWiz state, and is updated on each pulse -// timer interrupt for lights in flashing states. For outputs set by -// extended protocol messages, this is simply the brightness last set. -static float outLevel[NUM_OUTPUTS]; - // LedWiz output states. // // The LedWiz protocol has two separate control axes for each output. @@ -1252,6 +1394,263 @@ } d; }; +// --------------------------------------------------------------------------- +// +// Simple binary (on/off) input debouncer. Requires an input to be stable +// for a given interval before allowing an update. +// +class Debouncer +{ +public: + Debouncer(bool initVal, float tmin) + { + t.start(); + this->stable = this->prv = initVal; + this->tmin = tmin; + } + + // Get the current stable value + bool val() const { return stable; } + + // Apply a new sample. This tells us the new raw reading from the + // input device. + void sampleIn(bool val) + { + // If the new raw reading is different from the previous + // raw reading, we've detected an edge - start the clock + // on the sample reader. + if (val != prv) + { + // we have an edge - reset the sample clock + t.reset(); + + // this is now the previous raw sample for nxt time + prv = val; + } + else if (val != stable) + { + // The new raw sample is the same as the last raw sample, + // and different from the stable value. This means that + // the sample value has been the same for the time currently + // indicated by our timer. If enough time has elapsed to + // consider the value stable, apply the new value. + if (t.read() > tmin) + stable = val; + } + } + +private: + // current stable value + bool stable; + + // last raw sample value + bool prv; + + // elapsed time since last raw input change + Timer t; + + // Minimum time interval for stability, in seconds. Input readings + // must be stable for this long before the stable value is updated. + float tmin; +}; + + +// --------------------------------------------------------------------------- +// +// Turn off all outputs and restore everything to the default LedWiz +// state. This sets outputs #1-32 to LedWiz profile value 48 (full +// brightness) and switch state Off, sets all extended outputs (#33 +// and above) to zero brightness, and sets the LedWiz flash rate to 2. +// This effectively restores the power-on conditions. +// +void allOutputsOff() +{ + // reset all LedWiz outputs to OFF/48 + for (int i = 0 ; i < 32 ; ++i) + { + outLevel[i] = 0; + wizOn[i] = 0; + wizVal[i] = 48; + lwPin[i]->set(0); + } + + // reset all extended outputs (ports >32) to full off (brightness 0) + for (int i = 32 ; i < numOutputs ; ++i) + { + outLevel[i] = 0; + lwPin[i]->set(0); + } + + // restore default LedWiz flash rate + wizSpeed = 2; +} + +// --------------------------------------------------------------------------- +// +// TV ON timer. If this feature is enabled, we toggle a TV power switch +// relay (connected to a GPIO pin) to turn on the cab's TV monitors shortly +// after the system is powered. This is useful for TVs that don't remember +// their power state and don't turn back on automatically after being +// unplugged and plugged in again. This feature requires external +// circuitry, which is built in to the expansion board and can also be +// built separately - see the Build Guide for the circuit plan. +// +// Theory of operation: to use this feature, the cabinet must have a +// secondary PC-style power supply (PSU2) for the feedback devices, and +// this secondary supply must be plugged in to the same power strip or +// switched outlet that controls power to the TVs. This lets us use PSU2 +// as a proxy for the TV power state - when PSU2 is on, the TV outlet is +// powered, and when PSU2 is off, the TV outlet is off. We use a little +// latch circuit powered by PSU2 to monitor the status. The latch has a +// current state, ON or OFF, that we can read via a GPIO input pin, and +// we can set the state to ON by pulsing a separate GPIO output pin. As +// long as PSU2 is powered off, the latch stays in the OFF state, even if +// we try to set it by pulsing the SET pin. When PSU2 is turned on after +// being off, the latch starts receiving power but stays in the OFF state, +// since this is the initial condition when the power first comes on. So +// if our latch state pin is reading OFF, we know that PSU2 is either off +// now or *was* off some time since we last checked. We use a timer to +// check the state periodically. Each time we see the state is OFF, we +// try pulsing the SET pin. If the state still reads as OFF, we know +// that PSU2 is currently off; if the state changes to ON, though, we +// know that PSU2 has gone from OFF to ON some time between now and the +// previous check. When we see this condition, we start a countdown +// timer, and pulse the TV switch relay when the countdown ends. +// +// This scheme might seem a little convoluted, but it neatly handles +// all of the different cases that can occur: +// +// - Most cabinets systems are set up with "soft" PC power switches, +// so that the PC goes into "Soft Off" mode (ACPI state S5, in Windows +// parlance) when the user turns off the cabinet. In this state, the +// motherboard supplies power to USB devices, so the KL25Z continues +// running without interruption. The latch system lets us monitor +// the power state even when we're never rebooted, since the latch +// will turn off when PSU2 is off regardless of what the KL25Z is doing. +// +// - Some cabinet builders might prefer to use "hard" power switches, +// cutting all power to the cabinet, including the PC motherboard (and +// thus the KL25Z) every time the machine is turned off. This also +// applies to the "soft" switch case above when the cabinet is unplugged, +// a power outage occurs, etc. In these cases, the KL25Z will do a cold +// boot when the PC is turned on. We don't know whether the KL25Z +// will power up before or after PSU2, so it's not good enough to +// observe the *current* state of PSU2 when we first check - if PSU2 +// were to come on first, checking the current state alone would fool +// us into thinking that no action is required, because we would never +// have known that PSU2 was ever off. The latch handles this case by +// letting us see that PSU2 *was* off before we checked. +// +// - If the KL25Z is rebooted while the main system is running, or the +// KL25Z is unplugged and plugged back in, we will correctly leave the +// TVs as they are. The latch state is independent of the KL25Z's +// power or software state, so it's won't affect the latch state when +// the KL25Z is unplugged or rebooted; when we boot, we'll see that +// the latch is already on and that we don't have to turn on the TVs. +// This is important because TV ON buttons are usually on/off toggles, +// 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 +// 2 -> latch was off at last check, SET pulsed high +// 3 -> SET pulsed low, ready to check status +// 4 -> TV timer countdown in progress +// 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; +void TVTimerInt() +{ + // Check our internal state + switch (psu2_state) + { + case 1: + // Default state. This means that the latch was on last + // time we checked or that this is the first check. In + // 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) + { + // switch to OFF state + psu2_state = 2; + + // try setting the latch + psu2_status_set = 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_state = 3; + break; + + case 3: + // CHECK state: we pulsed SET, and we're now ready to see + // 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) + { + // The latch stuck, so PSU2 has transitioned from OFF + // to ON. Start the TV countdown timer. + tv_timer.reset(); + tv_timer.start(); + psu2_state = 4; + } + else + { + // 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_state = 2; + } + break; + + case 4: + // TV timer countdown in progress. If we've reached the + // delay time, pulse the relay. + if (tv_timer.read() >= TV_DELAY_TIME) + { + // turn on the relay for one timer interval + tv_relay = 1; + psu2_state = 5; + } + break; + + 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; + psu2_state = 1; + break; + } +} + +Ticker tv_ticker; +void startTVTimer() +{ + // Set up our time routine to run every 1/4 second. + tv_ticker.attach(&TVTimerInt, 0.25); +} + + +#else // ENABLE_TV_TIMER +// +// TV timer not used - just provide a dummy startup function +void startTVTimer() { } +// +#endif // ENABLE_TV_TIMER + // --------------------------------------------------------------------------- // @@ -1269,12 +1668,31 @@ ledG = 1; ledB = 1; + // start the TV timer, if applicable + startTVTimer(); + + // we're not connected/awake yet + bool connected = false; + time_t connectChangeTime = time(0); + +#if TLC5940_NCHIPS + // start the TLC5940 clock + for (int i = 0 ; i < numOutputs ; ++i) lwPin[i]->set(1.0); + tlc5940.start(); + + // enable power to the TLC5940 opto/LED outputs +# ifdef TLC5940_PWRENA + DigitalOut tlcPwrEna(TLC5940_PWRENA); + tlcPwrEna = 1; +# endif +#endif + // initialize the LedWiz ports initLwOut(); // initialize the button input ports initButtons(); - + // we don't need a reset yet bool needReset = false; @@ -1314,7 +1732,7 @@ // number from the saved configuration. MyUSBJoystick js( USB_VENDOR_ID, - USB_PRODUCT_ID | cfg.d.ledWizUnitNo, + MAKE_USB_PRODUCT_ID(USB_VENDOR_ID, USB_PRODUCT_ID, cfg.d.ledWizUnitNo), USB_VERSION_NO); // last report timer - we use this to throttle reports, since VP @@ -1360,11 +1778,6 @@ bool reportPix = false; #endif -#ifdef ENABLE_TLC5940 - // start the TLC5940 clock - tlc5940.start(); -#endif - // create our plunger sensor object PlungerSensor plungerSensor; @@ -1467,7 +1880,11 @@ // 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: - // 0x01 -> plunger sensor enabled + // 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); // we're all set up - now just loop, processing sensor reports and @@ -1486,6 +1903,24 @@ // 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) { @@ -1497,11 +1932,27 @@ // 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 @@ -1571,21 +2022,88 @@ 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); + } + } else { - // LWZ-PBA - full state dump; each byte is one output - // in the current bank. pbaIdx keeps track of the bank; - // this is incremented implicitly by each PBA message. + // 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 + // 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 + // 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(); @@ -2112,10 +2630,28 @@ printf("%d,%d\r\n", x, y); #endif + // check for connection status changes + int newConnected = js.isConnected() && !js.isSuspended(); + if (newConnected != connected) + { + // give it a few seconds to stabilize + time_t tc = time(0); + if (tc - connectChangeTime > 3) + { + // note the new status + connected = newConnected; + connectChangeTime = tc; + + // if we're no longer connected, turn off all outputs + if (!connected) + allOutputsOff(); + } + } + // provide a visual status indication on the on-board LED if (calBtnState < 2 && hbTimer.read_ms() > 1000) { - if (js.isSuspended() || !js.isConnected()) + if (!newConnected) { // suspended - turn off the LED ledR = 1;