Mirror with some correction
Dependencies: mbed FastIO FastPWM USBDevice
Diff: main.cpp
- Revision:
- 78:1e00b3fa11af
- Parent:
- 77:0b96f6867312
- Child:
- 79:682ae3171a08
--- a/main.cpp Fri Mar 17 22:02:08 2017 +0000 +++ b/main.cpp Sun Mar 19 05:30:53 2017 +0000 @@ -327,14 +327,10 @@ // once we set things up, we never delete anything. This means that we can // allocate memory in bare blocks without any bookkeeping overhead. // -// In addition, we can make a much larger overall pool of memory available -// in a custom allocator. The mbed library malloc() seems to have a pool -// of about 3K to work with, even though there's really about 9K of RAM -// left over after counting the static writable data and reserving space -// for a reasonable stack. I haven't looked at the mbed malloc to see why -// they're so stingy, but it appears from empirical testing that we can -// create a static array up to about 8K before things get crashy. - +// In addition, we can make a larger overall pool of memory available in +// a custom allocator. The RTL malloc() seems to have a pool of about 3K +// to work with, even though there really seems to be at least 8K left after +// reserving a reasonable amount of space for the stack. // halt with a diagnostic display if we run out of memory void HaltOutOfMem() @@ -350,68 +346,6 @@ } } -#if 0//$$$ -// Memory pool. We allocate two blocks at fixed addresses: one for -// the malloc heap, and one for the native stack. -// -// We allocate the stack block at the very top of memory. This is what -// the mbed startup code does anyway, so we don't actually ever move the -// stack pointer into this area ourselves. The point of this block is -// to reserve space with the linker, so that it won't put any other static -// data here in this region. -// -// The heap block goes just below the stack block. This is a contiguous -// block of bytes from which we allocate blocks for malloc() and 'operator -// new' requests. -// -// WARNING! When adding static data, be sure to check the build statistics -// to ensure that static data fits in the available RAM. The linker doesn't -// seem to make such a check on its own, so you might not see an error if -// added data pushes us past the 16K limit. - -// KL25Z address of top of RAM (one byte past end of RAM) -const uint32_t TOP_OF_RAM = 0x20003000UL; - -// malloc pool size -const size_t XMALLOC_POOL_SIZE = 8*1024; - -// stack size -const size_t XMALLOC_STACK_SIZE = 2*1024; - -// figure the fixed locations of the malloc pool and stack: the stack goes -// at the very top of RAM, and the malloc pool goes just below the stack -const uint32_t XMALLOC_STACK_BASE = TOP_OF_RAM - XMALLOC_STACK_SIZE; -const uint32_t XMALLOC_POOL_BASE = XMALLOC_STACK_BASE - XMALLOC_POOL_SIZE; - -// allocate the pools - use __attribute__((at)) to give them fixed addresses -static char xmalloc_stack[XMALLOC_STACK_SIZE] __attribute__((at(XMALLOC_STACK_BASE))); -static char xmalloc_pool[XMALLOC_POOL_SIZE] __attribute__((at(XMALLOC_POOL_BASE))); - -// malloc pool free pointer and space remaining -static char *xmalloc_nxt = xmalloc_pool; -static size_t xmalloc_rem = XMALLOC_POOL_SIZE; - -// allocate from our pool -void *xmalloc(size_t siz) -{ - // align to a 4-byte increment - siz = (siz + 3) & ~3; - - // if we're out of memory, halt with a diagnostic display - if (siz > xmalloc_rem) - HaltOutOfMem(); - - // get the next free location from the pool to return - char *ret = xmalloc_nxt; - - // advance the pool pointer and decrement the remaining size counter - xmalloc_nxt += siz; - xmalloc_rem -= siz; - - // return the allocated block - return ret; -}; -#elif 1//$$$ // For our custom malloc, we take advantage of the known layout of the // mbed library memory management. The mbed library puts all of the // static read/write data at the low end of RAM; this includes the @@ -467,33 +401,6 @@ return ret; } -#else //$$$ -extern char Image$$RW_IRAM1$$ZI$$Limit[]; // linker marker for top of ZI region -static char *xmalloc_nxt = Image$$RW_IRAM1$$ZI$$Limit; -const uint32_t xmallocMinStack = 2*1024; -char *const TopOfRAM = (char *)0x20003000UL; -uint16_t xmalloc_rem = uint16_t(TopOfRAM - Image$$RW_IRAM1$$ZI$$Limit - xmallocMinStack); -void *xmalloc(size_t siz) -{ - // align to a 4-byte increment - siz = (siz + 3) & ~3; - - // check to ensure we're leaving enough stack free - if (xmalloc_nxt + siz > TopOfRAM - xmallocMinStack) - HaltOutOfMem(); - - // get the next free location from the pool to return - char *ret = xmalloc_nxt; - - // advance past the allocated memory - xmalloc_nxt += siz; - xmalloc_rem -= siz; - - // return the allocated block - printf("malloc(%d) -> %lx\r\n", siz, ret); - return ret; -} -#endif//$$$ // Overload operator new to call our custom malloc. This ensures that // all 'new' allocations throughout the program (including library code) @@ -1947,7 +1854,23 @@ // IRConfigSlotToVirtualButton[n] = ir_tx virtual button number for // configuration slot n uint8_t IRConfigSlotToVirtualButton[MAX_IR_CODES]; -uint8_t IRAdHocSlot; + +// IR transmitter virtual button number for ad hoc IR command. We allocate +// one virtual button for sending ad hoc IR codes, such as through the USB +// protocol. +uint8_t IRAdHocBtn; + +// Staging area for ad hoc IR commands. It takes multiple messages +// to fill out an IR command, so we store the partial command here +// while waiting for the rest. +static struct +{ + uint8_t protocol; // protocol ID + uint64_t code; // code + uint8_t dittos : 1; // using dittos? + uint8_t ready : 1; // do we have a code ready to transmit? +} IRAdHocCmd; + // IR mode timer. In normal mode, this is the time since the last // command received; we use this to handle commands with timed effects, @@ -1980,7 +1903,7 @@ // it's a ditto or just a repeat of the full code. IRCommand learnedIRCode; -// IR comkmand received, as a config slot index, 1..MAX_IR_CODES. +// IR command received, as a config slot index, 1..MAX_IR_CODES. // When we receive a command that matches one of our programmed commands, // we note the slot here. We also reset the IR timer so that we know how // long it's been since the command came in. This lets us handle commands @@ -2001,6 +1924,7 @@ // distinct key press. uint8_t IRKeyGap = false; + // initialize void init_IR(Config &cfg, bool &kbKeys) { @@ -2045,7 +1969,7 @@ // allocate an additional virtual button for transmitting ad hoc // codes, such as for the "send code" USB API function - IRAdHocSlot = nVirtualButtons++; + IRAdHocBtn = nVirtualButtons++; // create the transmitter ir_tx = new IRTransmitter(pin, nVirtualButtons); @@ -2109,264 +2033,285 @@ } } -// Process IR input +// Process IR input and output void process_IR(Config &cfg, USBJoystick &js) { - // if there's no IR receiver attached, there's nothing to do - if (ir_rx == 0) - return; - - // Time out any received command - if (IRCommandIn != 0) + // check for transmitter tasks, if there's a transmitter + if (ir_tx != 0) { - // Time out inter-key gap mode after 30ms; time out all - // commands after 100ms. - uint32_t t = IRTimer.read_us(); - if (t > 100000) - IRCommandIn = 0; - else if (t > 30000) - IRKeyGap = false; + // If we're not currently sending, and an ad hoc IR command + // is ready to send, send it. + if (!ir_tx->isSending() && IRAdHocCmd.ready) + { + // program the command into the transmitter virtual button + // that we reserved for ad hoc commands + ir_tx->programButton(IRAdHocBtn, IRAdHocCmd.protocol, + IRAdHocCmd.dittos, IRAdHocCmd.code); + + // send the command - just pulse the button to send it once + ir_tx->pushButton(IRAdHocBtn, true); + ir_tx->pushButton(IRAdHocBtn, false); + + // we've sent the command, so clear the 'ready' flag + IRAdHocCmd.ready = false; + } } - - // Check if we're in learning mode - if (IRLearningMode != 0) + + // check for receiver tasks, if there's a receiver + if (ir_rx != 0) { - // Learning mode. Read raw inputs from the IR sensor and - // forward them to the PC via USB reports, up to the report - // limit. - const int nmax = USBJoystick::maxRawIR; - uint16_t raw[nmax]; - int n; - for (n = 0 ; n < nmax && ir_rx->processOne(raw[n]) ; ++n) ; - - // if we read any raw samples, report them - if (n != 0) - js.reportRawIR(n, raw); + // Time out any received command + if (IRCommandIn != 0) + { + // Time out inter-key gap mode after 30ms; time out all + // commands after 100ms. + uint32_t t = IRTimer.read_us(); + if (t > 100000) + IRCommandIn = 0; + else if (t > 30000) + IRKeyGap = false; + } + + // Check if we're in learning mode + if (IRLearningMode != 0) + { + // Learning mode. Read raw inputs from the IR sensor and + // forward them to the PC via USB reports, up to the report + // limit. + const int nmax = USBJoystick::maxRawIR; + uint16_t raw[nmax]; + int n; + for (n = 0 ; n < nmax && ir_rx->processOne(raw[n]) ; ++n) ; - // check for a command - IRCommand c; - if (ir_rx->readCommand(c)) - { - // check the current learning state - switch (IRLearningMode) - { - case 1: - // Initial state, waiting for the first decoded command. - // This is it. - learnedIRCode = c; + // if we read any raw samples, report them + if (n != 0) + js.reportRawIR(n, raw); - // Check if we need additional information. If the - // protocol supports dittos, we have to wait for a repeat - // to see if the remote actually uses the dittos, since - // some implementations of such protocols use the dittos - // while others just send repeated full codes. Otherwise, - // all we need is the initial code, so we're done. - IRLearningMode = (c.hasDittos ? 2 : 3); - break; + // check for a command + IRCommand c; + if (ir_rx->readCommand(c)) + { + // check the current learning state + switch (IRLearningMode) + { + case 1: + // Initial state, waiting for the first decoded command. + // This is it. + learnedIRCode = c; + + // Check if we need additional information. If the + // protocol supports dittos, we have to wait for a repeat + // to see if the remote actually uses the dittos, since + // some implementations of such protocols use the dittos + // while others just send repeated full codes. Otherwise, + // all we need is the initial code, so we're done. + IRLearningMode = (c.hasDittos ? 2 : 3); + break; + + case 2: + // Code received, awaiting auto-repeat information. If + // the protocol has dittos, check to see if we got a ditto: + // + // - If we received a ditto in the same protocol as the + // prior command, the remote uses dittos. + // + // - If we received a repeat of the prior command (not a + // ditto, but a repeat of the full code), the remote + // doesn't use dittos even though the protocol supports + // them. + // + // - Otherwise, it's not an auto-repeat at all, so we + // can't decide one way or the other on dittos: start + // over. + if (c.proId == learnedIRCode.proId + && c.hasDittos + && c.ditto) + { + // success - the remote uses dittos + IRLearningMode = 3; + } + else if (c.proId == learnedIRCode.proId + && c.hasDittos + && !c.ditto + && c.code == learnedIRCode.code) + { + // success - it's a repeat of the last code, so + // the remote doesn't use dittos even though the + // protocol supports them + learnedIRCode.hasDittos = false; + IRLearningMode = 3; + } + else + { + // It's not a ditto and not a full repeat of the + // last code, so it's either a new key, or some kind + // of multi-code key encoding that we don't recognize. + // We can't use this code, so start over. + IRLearningMode = 1; + } + break; + } - case 2: - // Code received, awaiting auto-repeat information. If - // the protocol has dittos, check to see if we got a ditto: - // - // - If we received a ditto in the same protocol as the - // prior command, the remote uses dittos. - // - // - If we received a repeat of the prior command (not a - // ditto, but a repeat of the full code), the remote - // doesn't use dittos even though the protocol supports - // them. - // - // - Otherwise, it's not an auto-repeat at all, so we - // can't decide one way or the other on dittos: start - // over. - if (c.proId == learnedIRCode.proId - && c.hasDittos - && c.ditto) + // If we ended in state 3, we've successfully decoded + // the transmission. Report the decoded data and terminate + // learning mode. + if (IRLearningMode == 3) { - // success - the remote uses dittos - IRLearningMode = 3; + // figure the flags: + // 0x02 -> dittos + uint8_t flags = 0; + if (learnedIRCode.hasDittos) + flags |= 0x02; + + // report the code + js.reportIRCode(learnedIRCode.proId, flags, learnedIRCode.code); + + // exit learning mode + IRLearningMode = 0; } - else if (c.proId == learnedIRCode.proId - && c.hasDittos - && !c.ditto - && c.code == learnedIRCode.code) - { - // success - it's a repeat of the last code, so - // the remote doesn't use dittos even though the - // protocol supports them - learnedIRCode.hasDittos = false; - IRLearningMode = 3; - } - else - { - // It's not a ditto and not a full repeat of the - // last code, so it's either a new key, or some kind - // of multi-code key encoding that we don't recognize. - // We can't use this code, so start over. - IRLearningMode = 1; - } - break; } - // If we ended in state 3, we've successfully decoded - // the transmission. Report the decoded data and terminate - // learning mode. - if (IRLearningMode == 3) + // time out of IR learning mode if it's been too long + if (IRLearningMode != 0 && IRTimer.read_us() > 10000000L) { - // figure the flags: - // 0x02 -> dittos - uint8_t flags = 0; - if (learnedIRCode.hasDittos) - flags |= 0x02; - - // report the code - js.reportIRCode(learnedIRCode.proId, flags, learnedIRCode.code); - - // exit learning mode + // report the termination by sending a raw IR report with + // zero data elements + js.reportRawIR(0, 0); + + + // cancel learning mode IRLearningMode = 0; } } - - // time out of IR learning mode if it's been too long - if (IRLearningMode != 0 && IRTimer.read_us() > 10000000L) - { - // report the termination by sending a raw IR report with - // zero data elements - js.reportRawIR(0, 0); - - - // cancel learning mode - IRLearningMode = 0; - } - } - else - { - // Not in learning mode. We don't care about the raw signals; - // just run them through the protocol decoders. - ir_rx->process(); - - // Check for decoded commands. Keep going until all commands - // have been read. - IRCommand c; - while (ir_rx->readCommand(c)) + else { - // We received a decoded command. Determine if it's a repeat, - // and if so, try to determine whether it's an auto-repeat (due - // to the remote key being held down) or a distinct new press - // on the same key as last time. The distinction is significant - // because it affects the auto-repeat behavior of the PC key - // input. An auto-repeat represents a key being held down on - // the remote, which we want to translate to a (virtual) key - // being held down on the PC keyboard; a distinct key press on - // the remote translates to a distinct key press on the PC. - // - // It can only be a repeat if there's a prior command that - // hasn't timed out yet, so start by checking for a previous - // command. - bool repeat = false, autoRepeat = false; - if (IRCommandIn != 0) + // Not in learning mode. We don't care about the raw signals; + // just run them through the protocol decoders. + ir_rx->process(); + + // Check for decoded commands. Keep going until all commands + // have been read. + IRCommand c; + while (ir_rx->readCommand(c)) { - // We have a command in progress. Check to see if the - // new command is a repeat of the previous command. Check - // first to see if it's a "ditto", which explicitly represents - // an auto-repeat of the last command. - IRCommandCfg &cmdcfg = cfg.IRCommand[IRCommandIn - 1]; - if (c.ditto) + // We received a decoded command. Determine if it's a repeat, + // and if so, try to determine whether it's an auto-repeat (due + // to the remote key being held down) or a distinct new press + // on the same key as last time. The distinction is significant + // because it affects the auto-repeat behavior of the PC key + // input. An auto-repeat represents a key being held down on + // the remote, which we want to translate to a (virtual) key + // being held down on the PC keyboard; a distinct key press on + // the remote translates to a distinct key press on the PC. + // + // It can only be a repeat if there's a prior command that + // hasn't timed out yet, so start by checking for a previous + // command. + bool repeat = false, autoRepeat = false; + if (IRCommandIn != 0) { - // We received a ditto. Dittos are always auto- - // repeats, so it's an auto-repeat as long as the - // ditto is in the same protocol as the last command. - // If the ditto is in a new protocol, the ditto can't - // be for the last command we saw, because a ditto - // never changes protocols from its antecedent. In - // such a case, we must have missed the antecedent - // command and thus don't know what's being repeated. - repeat = autoRepeat = (c.proId == cmdcfg.protocol); + // We have a command in progress. Check to see if the + // new command is a repeat of the previous command. Check + // first to see if it's a "ditto", which explicitly represents + // an auto-repeat of the last command. + IRCommandCfg &cmdcfg = cfg.IRCommand[IRCommandIn - 1]; + if (c.ditto) + { + // We received a ditto. Dittos are always auto- + // repeats, so it's an auto-repeat as long as the + // ditto is in the same protocol as the last command. + // If the ditto is in a new protocol, the ditto can't + // be for the last command we saw, because a ditto + // never changes protocols from its antecedent. In + // such a case, we must have missed the antecedent + // command and thus don't know what's being repeated. + repeat = autoRepeat = (c.proId == cmdcfg.protocol); + } + else + { + // It's not a ditto. The new command is a repeat if + // it matches the protocol and command code of the + // prior command. + repeat = (c.proId == cmdcfg.protocol + && uint32_t(c.code) == cmdcfg.code.lo + && uint32_t(c.code >> 32) == cmdcfg.code.hi); + + // If the command is a repeat, try to determine whether + // it's an auto-repeat or a new press on the same key. + // If the protocol uses dittos, it's definitely a new + // key press, because an auto-repeat would have used a + // ditto. For a protocol that doesn't use dittos, both + // an auto-repeat and a new key press just send the key + // code again, so we can't tell the difference based on + // that alone. But if the protocol has a toggle bit, we + // can tell by the toggle bit value: a new key press has + // the opposite toggle value as the last key press, while + // an auto-repeat has the same toggle. Note that if the + // protocol doesn't use toggle bits, the toggle value + // will always be the same, so we'll simply always treat + // any repeat as an auto-repeat. Many protocols simply + // provide no way to distinguish the two, so in such + // cases it's consistent with the native implementations + // to treat any repeat as an auto-repeat. + autoRepeat = + repeat + && !(cmdcfg.flags & IRFlagDittos) + && c.toggle == lastIRToggle; + } + } + + // Check to see if it's a repeat of any kind + if (repeat) + { + // It's a repeat. If it's not an auto-repeat, it's a + // new distinct key press, so we need to send the PC a + // momentary gap where we're not sending the same key, + // so that the PC also recognizes this as a distinct + // key press event. + if (!autoRepeat) + IRKeyGap = true; + + // restart the key-up timer + IRTimer.reset(); + } + else if (c.ditto) + { + // It's a ditto, but not a repeat of the last command. + // But a ditto doesn't contain any information of its own + // on the command being repeated, so given that it's not + // our last command, we can't infer what command the ditto + // is for and thus can't make sense of it. We have to + // simply ignore it and wait for the sender to start with + // a full command for a new key press. + IRCommandIn = 0; } else { - // It's not a ditto. The new command is a repeat if - // it matches the protocol and command code of the - // prior command. - repeat = (c.proId == cmdcfg.protocol - && uint32_t(c.code) == cmdcfg.code.lo - && uint32_t(c.code >> 32) == cmdcfg.code.hi); - - // If the command is a repeat, try to determine whether - // it's an auto-repeat or a new press on the same key. - // If the protocol uses dittos, it's definitely a new - // key press, because an auto-repeat would have used a - // ditto. For a protocol that doesn't use dittos, both - // an auto-repeat and a new key press just send the key - // code again, so we can't tell the difference based on - // that alone. But if the protocol has a toggle bit, we - // can tell by the toggle bit value: a new key press has - // the opposite toggle value as the last key press, while - // an auto-repeat has the same toggle. Note that if the - // protocol doesn't use toggle bits, the toggle value - // will always be the same, so we'll simply always treat - // any repeat as an auto-repeat. Many protocols simply - // provide no way to distinguish the two, so in such - // cases it's consistent with the native implementations - // to treat any repeat as an auto-repeat. - autoRepeat = - repeat - && !(cmdcfg.flags & IRFlagDittos) - && c.toggle == lastIRToggle; - } - } - - // Check to see if it's a repeat of any kind - if (repeat) - { - // It's a repeat. If it's not an auto-repeat, it's a - // new distinct key press, so we need to send the PC a - // momentary gap where we're not sending the same key, - // so that the PC also recognizes this as a distinct - // key press event. - if (!autoRepeat) - IRKeyGap = true; + // It's not a repeat, so the last command is no longer + // in effect (regardless of whether we find a match for + // the new command). + IRCommandIn = 0; - // restart the key-up timer - IRTimer.reset(); - } - else if (c.ditto) - { - // It's a ditto, but not a repeat of the last command. - // But a ditto doesn't contain any information of its own - // on the command being repeated, so given that it's not - // our last command, we can't infer what command the ditto - // is for and thus can't make sense of it. We have to - // simply ignore it and wait for the sender to start with - // a full command for a new key press. - IRCommandIn = 0; - } - else - { - // It's not a repeat, so the last command is no longer - // in effect (regardless of whether we find a match for - // the new command). - IRCommandIn = 0; - - // Check to see if we recognize the new command, by - // searching for a match in our learned code list. - for (int i = 0 ; i < MAX_IR_CODES ; ++i) - { - // if the protocol and command code from the code - // list both match the input, it's a match - IRCommandCfg &cmdcfg = cfg.IRCommand[i]; - if (cmdcfg.protocol == c.proId - && cmdcfg.code.lo == uint32_t(c.code) - && cmdcfg.code.hi == uint32_t(c.code >> 32)) + // Check to see if we recognize the new command, by + // searching for a match in our learned code list. + for (int i = 0 ; i < MAX_IR_CODES ; ++i) { - // Found it! Make this the last command, and - // remember the starting time. - IRCommandIn = i + 1; - lastIRToggle = c.toggle; - IRTimer.reset(); - - // no need to keep searching - break; + // if the protocol and command code from the code + // list both match the input, it's a match + IRCommandCfg &cmdcfg = cfg.IRCommand[i]; + if (cmdcfg.protocol == c.proId + && cmdcfg.code.lo == uint32_t(c.code) + && cmdcfg.code.hi == uint32_t(c.code >> 32)) + { + // Found it! Make this the last command, and + // remember the starting time. + IRCommandIn = i + 1; + lastIRToggle = c.toggle; + IRTimer.reset(); + + // no need to keep searching + break; + } } } } @@ -2490,12 +2435,12 @@ struct { int8_t index; // buttonState[] index of shift button; -1 if none - uint8_t state : 2; // current shift state: + uint8_t state; // current state, for "Key OR Shift" mode: // 0 = not shifted // 1 = shift button down, no key pressed yet // 2 = shift button down, key pressed - uint8_t pulse : 1; // sending pulsed keystroke on release - uint32_t pulseTime; // time of start of pulsed keystroke + // 3 = released, sending pulsed keystroke + uint32_t pulseTime; // time remaining in pulsed keystroke (state 3) } __attribute__((packed)) shiftButton; @@ -2616,7 +2561,7 @@ // We have to figure the buttonState[] index separately from // the config index, because the indices can differ if some // config slots are left unused. - if (cfg.shiftButton == i+1) + if (cfg.shiftButton.idx == i+1) shiftButton.index = bs - buttonState; // advance to the next button @@ -2804,38 +2749,67 @@ // check the shift button state if (shiftButton.index != -1) { + // get the shift button's physical state object ButtonState *sbs = &buttonState[shiftButton.index]; - switch (shiftButton.state) + + // figure what to do based on the shift button mode in the config + switch (cfg.shiftButton.mode) { case 0: - // Not shifted. Check if the button is now down: if so, - // switch to state 1 (shift button down, no key pressed yet). - if (sbs->physState) - shiftButton.state = 1; + default: + // "Shift OR Key" mode. The shift button doesn't send its key + // immediately when pressed. Instead, we wait to see what + // happens while it's down. Check the current cycle state. + switch (shiftButton.state) + { + case 0: + // Not shifted. Check if the button is now down: if so, + // switch to state 1 (shift button down, no key pressed yet). + if (sbs->physState) + shiftButton.state = 1; + break; + + case 1: + // Shift button down, no key pressed yet. If the button is + // now up, it counts as an ordinary button press instead of + // a shift button press, since the shift function was never + // used. Return to unshifted state and start a timed key + // pulse event. + if (!sbs->physState) + { + shiftButton.state = 3; + shiftButton.pulseTime = 50000+dt; // 50 ms left on the key pulse + } + break; + + case 2: + // Shift button down, other key was pressed. If the button is + // now up, simply clear the shift state without sending a key + // press for the shift button itself to the PC. The shift + // function was used, so its ordinary key press function is + // suppressed. + if (!sbs->physState) + shiftButton.state = 0; + break; + + case 3: + // Sending pulsed keystroke. Deduct the current time interval + // from the remaining pulse timer. End the pulse if the time + // has expired. + if (shiftButton.pulseTime > dt) + shiftButton.pulseTime -= dt; + else + shiftButton.state = 0; + break; + } break; case 1: - // Shift button down, no key pressed yet. If the button is - // now up, it counts as an ordinary button press instead of - // a shift button press, since the shift function was never - // used. Return to unshifted state and start a timed key - // pulse event. - if (!sbs->physState) - { - shiftButton.state = 0; - shiftButton.pulse = 1; - shiftButton.pulseTime = 50000+dt; // 50 ms left on the key pulse - } - break; - - case 2: - // Shift button down, other key was pressed. If the button is - // now up, simply clear the shift state without sending a key - // press for the shift button itself to the PC. The shift - // function was used, so its ordinary key press function is - // suppressed. - if (!sbs->physState) - shiftButton.state = 0; + // "Shift AND Key" mode. In this mode, the shift button acts + // like any other button and sends its mapped key immediately. + // The state cycle in this case simply matches the physical + // state: ON -> cycle state 1, OFF -> cycle state 0. + shiftButton.state = (sbs->physState ? 1 : 0); break; } } @@ -2853,22 +2827,24 @@ // - regular button if (shiftButton.index == i) { - // This is the shift button. Its logical state for key - // reporting purposes is controlled by the shift buttton - // pulse timer. If we're in a pulse, its logical state - // is pressed. - if (shiftButton.pulse) + // This is the shift button. The logical state handling + // depends on the mode. + switch (cfg.shiftButton.mode) { - // deduct the current interval from the pulse time, ending - // the pulse if the time has expired - if (shiftButton.pulseTime > dt) - shiftButton.pulseTime -= dt; - else - shiftButton.pulse = 0; + case 0: + default: + // "Shift OR Key" mode. The logical state is ON only + // during the timed pulse when the key is released, which + // is signified by shift button state 3. + bs->logState = (shiftButton.state == 3); + break; + + case 1: + // "Shif AND Key" mode. The shift button acts like any + // other button, so it's logically on when physically on. + bs->logState = bs->physState; + break; } - - // the button is logically pressed if we're in a pulse - bs->logState = shiftButton.pulse; } else if (bs->pulseState != 0) { @@ -2929,19 +2905,24 @@ } // Determine if we're going to use the shifted version of the - // button. We're using the shifted version if the shift button - // is down AND the button has ANY shifted meaning - a key assignment, - // a Night Mode toggle assignment, or an IR code. If the button - // doesn't have any meaning at all in shifted mode, the base version - // of the button applies whether or not the shift button is down. + // button. We're using the shifted version if... + // + // - the shift button is down, AND + // - this button isn't itself the shift button, AND + // - this button has some kind of shifted meaning // - // Note that the test for Night Mode is a bit tricky. The shifted - // version of the button is the Night Mode toggle if the button matches - // the Night Mode button index, AND its flags are set with "toggle - // mode ON" (bit 0x02 is on) and "switch mode OFF" (bit 0x01 is off). - // That means the button flags & 0x03 must equal 0x02. + // A "shifted meaning" means that we have any of the following + // assigned to the shifted version of the button: a key assignment, + // (in typ2,key2), an IR command (in IRCommand2), or Night mode. + // + // The test for Night Mode is a bit tricky. The shifted version of + // the button is the Night Mode toggle if the button matches the + // Night Mode button index, AND its flags are set with "toggle mode + // ON" (bit 0x02 is on) and "switch mode OFF" (bit 0x01 is off). + // So (button flags) & 0x03 must equal 0x02. bool useShift = (shiftButton.state != 0 + && shiftButton.index != i && (bc->typ2 != BtnTypeNone || bc->IRCommand2 != 0 || (cfg.nightMode.btn == i+1 && (cfg.nightMode.flags & 0x03) == 0x02))); @@ -2951,7 +2932,7 @@ // no one has used the shift function yet"), then we've "consumed" // the shift button press (so go to shift state 2: "shift button has // been used by some other button press that has a shifted meaning"). - if (useShift && shiftButton.state == 1) + if (useShift && shiftButton.state == 1 && bs->logState) shiftButton.state = 2; // carry out any edge effects from buttons changing states @@ -3438,7 +3419,8 @@ class Accel { public: - Accel(PinName sda, PinName scl, int i2cAddr, PinName irqPin, int range) + Accel(PinName sda, PinName scl, int i2cAddr, PinName irqPin, + int range, int autoCenterMode) : mma_(sda, scl, i2cAddr) { // remember the interrupt pin assignment @@ -3446,11 +3428,53 @@ // remember the range range_ = range; + + // set the auto-centering mode + setAutoCenterMode(autoCenterMode); + + // no manual centering request has been received + manualCenterRequest_ = false; // reset and initialize reset(); } + // Request manual centering. This applies the trailing average + // of recent measurements and applies it as the new center point + // as soon as we have enough data. + void manualCenterRequest() { manualCenterRequest_ = true; } + + // set the auto-centering mode + void setAutoCenterMode(int mode) + { + // remember the mode + autoCenterMode_ = mode; + + // Set the time between checks. We check 5 times over the course + // of the centering time, so the check interval is 1/5 of the total. + if (mode == 0) + { + // mode 0 is the old default of 5 seconds, so check every 1s + autoCenterCheckTime_ = 1000000; + } + else if (mode <= 60) + { + // mode 1-60 means reset after 'mode' seconds; the check + // interval is 1/5 of this + autoCenterCheckTime_ = mode*200000; + } + else + { + // Auto-centering is off, but still gather statistics to apply + // when we get a manual centering request. The check interval + // in this case is 1/5 of the total time for the trailing average + // we apply for the manual centering. We want this to be long + // enough to smooth out the data, but short enough that it only + // includes recent data. + autoCenterCheckTime_ = 500000; + } + } + void reset() { // clear the center point @@ -3512,8 +3536,11 @@ AccHist *p = accPrv_ + iAccPrv_; p->addAvg(ax, ay); - // check for auto-centering every so often - if (tCenter_.read_us() > 1000000) + // If we're in auto-centering mode, check for auto-centering + // at intervals of 1/5 of the overall time. If we're not in + // auto-centering mode, check anyway at one-second intervals + // so that we gather averages for manual centering requests. + if (tCenter_.read_us() > autoCenterCheckTime_) { // add the latest raw sample to the history list AccHist *prv = p; @@ -3523,22 +3550,33 @@ p = accPrv_ + iAccPrv_; p->set(ax, ay, prv); - // if we have a full complement, check for stability + // if we have a full complement, check for auto-centering if (nAccPrv_ >= maxAccPrv) { - // check if we've been stable for all recent samples + // Center if: + // + // - Auto-centering is on, and we've been stable over the + // whole sample period at our spot-check points + // + // - A manual centering request is pending + // static const int accTol = 164*164; // 1% of range, squared AccHist *p0 = accPrv_; - if (p0[0].dsq < accTol - && p0[1].dsq < accTol - && p0[2].dsq < accTol - && p0[3].dsq < accTol - && p0[4].dsq < accTol) + if (manualCenterRequest_ + || (autoCenterMode_ <= 60 + && p0[0].dsq < accTol + && p0[1].dsq < accTol + && p0[2].dsq < accTol + && p0[3].dsq < accTol + && p0[4].dsq < accTol)) { // Figure the new calibration point as the average of // the samples over the rest period cx_ = (p0[0].xAvg() + p0[1].xAvg() + p0[2].xAvg() + p0[3].xAvg() + p0[4].xAvg())/5; cy_ = (p0[0].yAvg() + p0[1].yAvg() + p0[2].yAvg() + p0[3].yAvg() + p0[4].yAvg())/5; + + // clear any pending manual centering request + manualCenterRequest_ = false; } } else @@ -3609,7 +3647,19 @@ // range (AccelRangeXxx value, from config.h) uint8_t range_; - + + // auto-center mode: + // 0 = default of 5-second auto-centering + // 1-60 = auto-center after this many seconds + // 255 = auto-centering off (manual centering only) + uint8_t autoCenterMode_; + + // flag: a manual centering request is pending + bool manualCenterRequest_; + + // time in us between auto-centering incremental checks + uint32_t autoCenterCheckTime_; + // atuo-centering timer Timer tCenter_; @@ -3625,8 +3675,8 @@ // cabinet's orientation (e.g., if it gets moved slightly by an // especially strong nudge) as well as any systematic drift in the // accelerometer measurement bias (e.g., from temperature changes). - int iAccPrv_, nAccPrv_; - static const int maxAccPrv = 5; + uint8_t iAccPrv_, nAccPrv_; + static const uint8_t maxAccPrv = 5; AccHist accPrv_[maxAccPrv]; // interurupt pin name @@ -5473,7 +5523,7 @@ void accelRotate(int &x, int &y) { int tmp; - switch (cfg.orientation) + switch (cfg.accel.orientation) { case OrientationFront: tmp = x; @@ -5559,7 +5609,7 @@ // Handle an input report from the USB host. Input reports use our extended // LedWiz protocol. // -void handleInputMsg(LedWizMsg &lwm, USBJoystick &js) +void handleInputMsg(LedWizMsg &lwm, USBJoystick &js, Accel &accel) { // LedWiz commands come in two varieties: SBA and PBA. An // SBA is marked by the first byte having value 64 (0x40). In @@ -5658,6 +5708,7 @@ cfg.plunger.cal.zero, cfg.plunger.cal.max, cfg.plunger.cal.tRelease, nvm.valid(), // a config is loaded if the config memory block is valid true, // we support sbx/pbx extensions + true, // we support the new accelerometer settings xmalloc_rem); // remaining memory size break; @@ -5740,6 +5791,29 @@ // 13 = Send button status report reportButtonStatus(js); break; + + case 14: + // 14 = manually center the accelerometer + accel.manualCenterRequest(); + break; + + case 15: + // 15 = set up ad hoc IR command, part 1. Mark the command + // as not ready, and save the partial data from the message. + IRAdHocCmd.ready = 0; + IRAdHocCmd.protocol = data[2]; + IRAdHocCmd.dittos = (data[3] & IRFlagDittos) != 0; + IRAdHocCmd.code = wireUI32(&data[4]); + break; + + case 16: + // 16 = send ad hoc IR command, part 2. Fill in the rest + // of the data from the message and mark the command as + // ready. The IR polling routine will send this as soon + // as the IR transmitter is free. + IRAdHocCmd.code |= (uint64_t(wireUI32(&data[2])) << 32); + IRAdHocCmd.ready = 1; + break; } } else if (data[0] == 66) @@ -6077,7 +6151,7 @@ // create the accelerometer object Accel accel(MMA8451_SCL_PIN, MMA8451_SDA_PIN, MMA8451_I2C_ADDRESS, - MMA8451_INT_PIN, cfg.accelRange); + MMA8451_INT_PIN, cfg.accel.range, cfg.accel.autoCenterTime); // last accelerometer report, in joystick units (we report the nudge // acceleration via the joystick x & y axes, per the VP convention) @@ -6121,7 +6195,7 @@ IF_DIAG(int msgCount = 0;) while (js.readLedWizMsg(lwm) && lwt.read_us() < 5000) { - handleInputMsg(lwm, js); + handleInputMsg(lwm, js, accel); IF_DIAG(++msgCount;) } @@ -6208,10 +6282,8 @@ // Otherwise, return to the base state without saving anything. // If the button is released before we make it to calibration // mode, it simply cancels the attempt. - diagLED(1,1,1); if (calBtnState == 3 && calBtnTimer.read_us() > 15000000) { - diagLED(0,0,0); // exit calibration mode calBtnState = 0; plungerReader.setCalMode(false); @@ -6222,7 +6294,6 @@ } else if (calBtnState != 3) { - diagLED(0,1,1); // didn't make it to calibration mode - cancel the operation calBtnState = 0; }