work in progress
Dependencies: FastAnalogIn FastIO USBDevice mbed FastPWM SimpleDMA
Fork of Pinscape_Controller by
main.cpp@2:c174f9ee414a, 2014-07-22 (annotated)
- Committer:
- mjr
- Date:
- Tue Jul 22 04:33:47 2014 +0000
- Revision:
- 2:c174f9ee414a
- Parent:
- 1:d913e0afb2ac
- Child:
- 3:3514575d4f86
Before change to ISR for accelerometer
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
mjr | 0:5acbbe3f4cf4 | 1 | #include "mbed.h" |
mjr | 0:5acbbe3f4cf4 | 2 | #include "USBJoystick.h" |
mjr | 0:5acbbe3f4cf4 | 3 | #include "MMA8451Q.h" |
mjr | 1:d913e0afb2ac | 4 | #include "tsl1410r.h" |
mjr | 1:d913e0afb2ac | 5 | #include "FreescaleIAP.h" |
mjr | 2:c174f9ee414a | 6 | #include "crc32.h" |
mjr | 2:c174f9ee414a | 7 | |
mjr | 2:c174f9ee414a | 8 | // customization of the joystick class to expose connect/suspend status |
mjr | 2:c174f9ee414a | 9 | class MyUSBJoystick: public USBJoystick |
mjr | 2:c174f9ee414a | 10 | { |
mjr | 2:c174f9ee414a | 11 | public: |
mjr | 2:c174f9ee414a | 12 | MyUSBJoystick(uint16_t vendor_id, uint16_t product_id, uint16_t product_release) |
mjr | 2:c174f9ee414a | 13 | : USBJoystick(vendor_id, product_id, product_release) |
mjr | 2:c174f9ee414a | 14 | { |
mjr | 2:c174f9ee414a | 15 | connected_ = false; |
mjr | 2:c174f9ee414a | 16 | suspended_ = false; |
mjr | 2:c174f9ee414a | 17 | } |
mjr | 2:c174f9ee414a | 18 | |
mjr | 2:c174f9ee414a | 19 | int isConnected() const { return connected_; } |
mjr | 2:c174f9ee414a | 20 | int isSuspended() const { return suspended_; } |
mjr | 2:c174f9ee414a | 21 | |
mjr | 2:c174f9ee414a | 22 | protected: |
mjr | 2:c174f9ee414a | 23 | virtual void connectStateChanged(unsigned int connected) |
mjr | 2:c174f9ee414a | 24 | { connected_ = connected; } |
mjr | 2:c174f9ee414a | 25 | virtual void suspendStateChanged(unsigned int suspended) |
mjr | 2:c174f9ee414a | 26 | { suspended_ = suspended; } |
mjr | 2:c174f9ee414a | 27 | |
mjr | 2:c174f9ee414a | 28 | int connected_; |
mjr | 2:c174f9ee414a | 29 | int suspended_; |
mjr | 2:c174f9ee414a | 30 | }; |
mjr | 0:5acbbe3f4cf4 | 31 | |
mjr | 1:d913e0afb2ac | 32 | // on-board RGB LED elements - we use these for diagnostics |
mjr | 0:5acbbe3f4cf4 | 33 | PwmOut led1(LED1), led2(LED2), led3(LED3); |
mjr | 0:5acbbe3f4cf4 | 34 | |
mjr | 1:d913e0afb2ac | 35 | // calibration button - switch input and LED output |
mjr | 1:d913e0afb2ac | 36 | DigitalIn calBtn(PTE29); |
mjr | 1:d913e0afb2ac | 37 | DigitalOut calBtnLed(PTE23); |
mjr | 0:5acbbe3f4cf4 | 38 | |
mjr | 0:5acbbe3f4cf4 | 39 | static int pbaIdx = 0; |
mjr | 0:5acbbe3f4cf4 | 40 | |
mjr | 0:5acbbe3f4cf4 | 41 | // on/off state for each LedWiz output |
mjr | 1:d913e0afb2ac | 42 | static uint8_t wizOn[32]; |
mjr | 0:5acbbe3f4cf4 | 43 | |
mjr | 0:5acbbe3f4cf4 | 44 | // profile (brightness/blink) state for each LedWiz output |
mjr | 1:d913e0afb2ac | 45 | static uint8_t wizVal[32] = { |
mjr | 0:5acbbe3f4cf4 | 46 | 0, 0, 0, 0, 0, 0, 0, 0, |
mjr | 0:5acbbe3f4cf4 | 47 | 0, 0, 0, 0, 0, 0, 0, 0, |
mjr | 0:5acbbe3f4cf4 | 48 | 0, 0, 0, 0, 0, 0, 0, 0, |
mjr | 0:5acbbe3f4cf4 | 49 | 0, 0, 0, 0, 0, 0, 0, 0 |
mjr | 0:5acbbe3f4cf4 | 50 | }; |
mjr | 0:5acbbe3f4cf4 | 51 | |
mjr | 1:d913e0afb2ac | 52 | static float wizState(int idx) |
mjr | 0:5acbbe3f4cf4 | 53 | { |
mjr | 1:d913e0afb2ac | 54 | if (wizOn[idx]) { |
mjr | 0:5acbbe3f4cf4 | 55 | // on - map profile brightness state to PWM level |
mjr | 1:d913e0afb2ac | 56 | uint8_t val = wizVal[idx]; |
mjr | 0:5acbbe3f4cf4 | 57 | if (val >= 1 && val <= 48) |
mjr | 0:5acbbe3f4cf4 | 58 | return 1.0 - val/48.0; |
mjr | 0:5acbbe3f4cf4 | 59 | else if (val >= 129 && val <= 132) |
mjr | 0:5acbbe3f4cf4 | 60 | return 0.0; |
mjr | 0:5acbbe3f4cf4 | 61 | else |
mjr | 0:5acbbe3f4cf4 | 62 | return 1.0; |
mjr | 0:5acbbe3f4cf4 | 63 | } |
mjr | 0:5acbbe3f4cf4 | 64 | else { |
mjr | 0:5acbbe3f4cf4 | 65 | // off |
mjr | 0:5acbbe3f4cf4 | 66 | return 1.0; |
mjr | 0:5acbbe3f4cf4 | 67 | } |
mjr | 0:5acbbe3f4cf4 | 68 | } |
mjr | 0:5acbbe3f4cf4 | 69 | |
mjr | 1:d913e0afb2ac | 70 | static void updateWizOuts() |
mjr | 1:d913e0afb2ac | 71 | { |
mjr | 1:d913e0afb2ac | 72 | led1 = wizState(0); |
mjr | 1:d913e0afb2ac | 73 | led2 = wizState(1); |
mjr | 1:d913e0afb2ac | 74 | led3 = wizState(2); |
mjr | 1:d913e0afb2ac | 75 | } |
mjr | 1:d913e0afb2ac | 76 | |
mjr | 1:d913e0afb2ac | 77 | struct AccPrv |
mjr | 0:5acbbe3f4cf4 | 78 | { |
mjr | 1:d913e0afb2ac | 79 | AccPrv() : x(0), y(0) { } |
mjr | 1:d913e0afb2ac | 80 | float x; |
mjr | 1:d913e0afb2ac | 81 | float y; |
mjr | 1:d913e0afb2ac | 82 | |
mjr | 1:d913e0afb2ac | 83 | double dist(AccPrv &b) |
mjr | 1:d913e0afb2ac | 84 | { |
mjr | 1:d913e0afb2ac | 85 | float dx = x - b.x, dy = y - b.y; |
mjr | 1:d913e0afb2ac | 86 | return sqrt(dx*dx + dy*dy); |
mjr | 1:d913e0afb2ac | 87 | } |
mjr | 1:d913e0afb2ac | 88 | }; |
mjr | 0:5acbbe3f4cf4 | 89 | |
mjr | 2:c174f9ee414a | 90 | // Non-volatile memory structure. We store persistent a small |
mjr | 2:c174f9ee414a | 91 | // amount of persistent data in flash memory to retain calibration |
mjr | 2:c174f9ee414a | 92 | // data between sessions. |
mjr | 2:c174f9ee414a | 93 | struct NVM |
mjr | 2:c174f9ee414a | 94 | { |
mjr | 2:c174f9ee414a | 95 | // checksum - we use this to determine if the flash record |
mjr | 2:c174f9ee414a | 96 | // has been initialized |
mjr | 2:c174f9ee414a | 97 | uint32_t checksum; |
mjr | 2:c174f9ee414a | 98 | |
mjr | 2:c174f9ee414a | 99 | // signature value |
mjr | 2:c174f9ee414a | 100 | static const uint32_t SIGNATURE = 0x4D4A522A; |
mjr | 2:c174f9ee414a | 101 | static const uint16_t VERSION = 0x0002; |
mjr | 2:c174f9ee414a | 102 | |
mjr | 2:c174f9ee414a | 103 | // stored data (excluding the checksum) |
mjr | 2:c174f9ee414a | 104 | struct |
mjr | 2:c174f9ee414a | 105 | { |
mjr | 2:c174f9ee414a | 106 | // signature and version - further verification that we have valid |
mjr | 2:c174f9ee414a | 107 | // initialized data |
mjr | 2:c174f9ee414a | 108 | uint32_t sig; |
mjr | 2:c174f9ee414a | 109 | uint16_t vsn; |
mjr | 2:c174f9ee414a | 110 | |
mjr | 2:c174f9ee414a | 111 | // direction - 0 means unknown, 1 means bright end is pixel 0, 2 means reversed |
mjr | 2:c174f9ee414a | 112 | uint8_t dir; |
mjr | 2:c174f9ee414a | 113 | |
mjr | 2:c174f9ee414a | 114 | // plunger calibration min and max |
mjr | 2:c174f9ee414a | 115 | int plungerMin; |
mjr | 2:c174f9ee414a | 116 | int plungerMax; |
mjr | 2:c174f9ee414a | 117 | } d; |
mjr | 2:c174f9ee414a | 118 | }; |
mjr | 2:c174f9ee414a | 119 | |
mjr | 0:5acbbe3f4cf4 | 120 | int main(void) |
mjr | 0:5acbbe3f4cf4 | 121 | { |
mjr | 1:d913e0afb2ac | 122 | // turn off our on-board indicator LED |
mjr | 0:5acbbe3f4cf4 | 123 | led1 = 1; |
mjr | 0:5acbbe3f4cf4 | 124 | led2 = 1; |
mjr | 0:5acbbe3f4cf4 | 125 | led3 = 1; |
mjr | 1:d913e0afb2ac | 126 | |
mjr | 2:c174f9ee414a | 127 | // set up a flash memory controller |
mjr | 2:c174f9ee414a | 128 | FreescaleIAP iap; |
mjr | 2:c174f9ee414a | 129 | |
mjr | 2:c174f9ee414a | 130 | // use the last sector of flash for our non-volatile memory structure |
mjr | 2:c174f9ee414a | 131 | int flash_addr = (iap.flash_size() - SECTOR_SIZE); |
mjr | 2:c174f9ee414a | 132 | NVM *flash = (NVM *)flash_addr; |
mjr | 2:c174f9ee414a | 133 | NVM cfg; |
mjr | 2:c174f9ee414a | 134 | |
mjr | 2:c174f9ee414a | 135 | // check for valid flash |
mjr | 2:c174f9ee414a | 136 | bool flash_valid = (flash->d.sig == flash->SIGNATURE |
mjr | 2:c174f9ee414a | 137 | && flash->d.vsn == flash->VERSION |
mjr | 2:c174f9ee414a | 138 | && flash->checksum == CRC32(&flash->d, sizeof(flash->d))); |
mjr | 2:c174f9ee414a | 139 | |
mjr | 2:c174f9ee414a | 140 | // Number of pixels we read from the sensor on each frame. This can be |
mjr | 2:c174f9ee414a | 141 | // less than the physical pixel count if desired; we'll read every nth |
mjr | 2:c174f9ee414a | 142 | // piexl if so. E.g., with a 1280-pixel physical sensor, if npix is 320, |
mjr | 2:c174f9ee414a | 143 | // we'll read every 4th pixel. VP doesn't seem to have very high |
mjr | 2:c174f9ee414a | 144 | // resolution internally for the plunger, so it's probably not necessary |
mjr | 2:c174f9ee414a | 145 | // to use the full resolution of the sensor - about 160 pixels seems |
mjr | 2:c174f9ee414a | 146 | // perfectly adequate. We can read the sensor faster (and thus provide |
mjr | 2:c174f9ee414a | 147 | // a higher refresh rate) if we read fewer pixels in each frame. |
mjr | 2:c174f9ee414a | 148 | const int npix = 160; |
mjr | 2:c174f9ee414a | 149 | |
mjr | 2:c174f9ee414a | 150 | // if the flash is valid, load it; otherwise initialize to defaults |
mjr | 2:c174f9ee414a | 151 | if (flash_valid) { |
mjr | 2:c174f9ee414a | 152 | memcpy(&cfg, flash, sizeof(cfg)); |
mjr | 2:c174f9ee414a | 153 | printf("Flash restored: plunger min=%d, max=%d\r\n", |
mjr | 2:c174f9ee414a | 154 | cfg.d.plungerMin, cfg.d.plungerMax); |
mjr | 2:c174f9ee414a | 155 | } |
mjr | 2:c174f9ee414a | 156 | else { |
mjr | 2:c174f9ee414a | 157 | printf("Factory reset\r\n"); |
mjr | 2:c174f9ee414a | 158 | cfg.d.sig = cfg.SIGNATURE; |
mjr | 2:c174f9ee414a | 159 | cfg.d.vsn = cfg.VERSION; |
mjr | 2:c174f9ee414a | 160 | cfg.d.plungerMin = 0; |
mjr | 2:c174f9ee414a | 161 | cfg.d.plungerMax = npix; |
mjr | 2:c174f9ee414a | 162 | } |
mjr | 1:d913e0afb2ac | 163 | |
mjr | 1:d913e0afb2ac | 164 | // plunger calibration button debounce timer |
mjr | 1:d913e0afb2ac | 165 | Timer calBtnTimer; |
mjr | 1:d913e0afb2ac | 166 | calBtnTimer.start(); |
mjr | 1:d913e0afb2ac | 167 | int calBtnDownTime = 0; |
mjr | 1:d913e0afb2ac | 168 | int calBtnLit = false; |
mjr | 1:d913e0afb2ac | 169 | |
mjr | 1:d913e0afb2ac | 170 | // Calibration button state: |
mjr | 1:d913e0afb2ac | 171 | // 0 = not pushed |
mjr | 1:d913e0afb2ac | 172 | // 1 = pushed, not yet debounced |
mjr | 1:d913e0afb2ac | 173 | // 2 = pushed, debounced, waiting for hold time |
mjr | 1:d913e0afb2ac | 174 | // 3 = pushed, hold time completed - in calibration mode |
mjr | 1:d913e0afb2ac | 175 | int calBtnState = 0; |
mjr | 1:d913e0afb2ac | 176 | |
mjr | 1:d913e0afb2ac | 177 | // set up a timer for our heartbeat indicator |
mjr | 1:d913e0afb2ac | 178 | Timer hbTimer; |
mjr | 1:d913e0afb2ac | 179 | hbTimer.start(); |
mjr | 1:d913e0afb2ac | 180 | int t0Hb = hbTimer.read_ms(); |
mjr | 1:d913e0afb2ac | 181 | int hb = 0; |
mjr | 1:d913e0afb2ac | 182 | |
mjr | 1:d913e0afb2ac | 183 | // set a timer for accelerometer auto-centering |
mjr | 1:d913e0afb2ac | 184 | Timer acTimer; |
mjr | 1:d913e0afb2ac | 185 | acTimer.start(); |
mjr | 1:d913e0afb2ac | 186 | int t0ac = acTimer.read_ms(); |
mjr | 1:d913e0afb2ac | 187 | |
mjr | 1:d913e0afb2ac | 188 | // Create the joystick USB client. Light the on-board indicator LED |
mjr | 1:d913e0afb2ac | 189 | // red while connecting, and change to green after we connect. |
mjr | 2:c174f9ee414a | 190 | led1 = 0; |
mjr | 2:c174f9ee414a | 191 | MyUSBJoystick js(0xFAFA, 0x00F7, 0x0001); |
mjr | 0:5acbbe3f4cf4 | 192 | led1 = 1; |
mjr | 2:c174f9ee414a | 193 | led2 = 0; |
mjr | 2:c174f9ee414a | 194 | |
mjr | 0:5acbbe3f4cf4 | 195 | // create the accelerometer object |
mjr | 0:5acbbe3f4cf4 | 196 | const int MMA8451_I2C_ADDRESS = (0x1d<<1); |
mjr | 0:5acbbe3f4cf4 | 197 | MMA8451Q accel(PTE25, PTE24, MMA8451_I2C_ADDRESS); |
mjr | 0:5acbbe3f4cf4 | 198 | |
mjr | 0:5acbbe3f4cf4 | 199 | // create the CCD array object |
mjr | 1:d913e0afb2ac | 200 | TSL1410R ccd(PTE20, PTE21, PTB0); |
mjr | 2:c174f9ee414a | 201 | |
mjr | 1:d913e0afb2ac | 202 | // recent accelerometer readings, for auto centering |
mjr | 1:d913e0afb2ac | 203 | int iAccPrv = 0, nAccPrv = 0; |
mjr | 1:d913e0afb2ac | 204 | const int maxAccPrv = 5; |
mjr | 1:d913e0afb2ac | 205 | AccPrv accPrv[maxAccPrv]; |
mjr | 0:5acbbe3f4cf4 | 206 | |
mjr | 1:d913e0afb2ac | 207 | // last accelerometer report, in mouse coordinates |
mjr | 1:d913e0afb2ac | 208 | int x = 127, y = 127, z = 0; |
mjr | 2:c174f9ee414a | 209 | |
mjr | 1:d913e0afb2ac | 210 | // raw accelerator centerpoint, on the unit interval (-1.0 .. +1.0) |
mjr | 1:d913e0afb2ac | 211 | float xCenter = 0.0, yCenter = 0.0; |
mjr | 2:c174f9ee414a | 212 | |
mjr | 2:c174f9ee414a | 213 | // start the first CCD integration cycle |
mjr | 2:c174f9ee414a | 214 | ccd.clear(); |
mjr | 1:d913e0afb2ac | 215 | |
mjr | 1:d913e0afb2ac | 216 | // we're all set up - now just loop, processing sensor reports and |
mjr | 1:d913e0afb2ac | 217 | // host requests |
mjr | 0:5acbbe3f4cf4 | 218 | for (;;) |
mjr | 0:5acbbe3f4cf4 | 219 | { |
mjr | 0:5acbbe3f4cf4 | 220 | // Look for an incoming report. Continue processing input as |
mjr | 0:5acbbe3f4cf4 | 221 | // long as there's anything pending - this ensures that we |
mjr | 0:5acbbe3f4cf4 | 222 | // handle input in as timely a fashion as possible by deferring |
mjr | 0:5acbbe3f4cf4 | 223 | // output tasks as long as there's input to process. |
mjr | 0:5acbbe3f4cf4 | 224 | HID_REPORT report; |
mjr | 0:5acbbe3f4cf4 | 225 | while (js.readNB(&report) && report.length == 8) |
mjr | 0:5acbbe3f4cf4 | 226 | { |
mjr | 0:5acbbe3f4cf4 | 227 | uint8_t *data = report.data; |
mjr | 1:d913e0afb2ac | 228 | if (data[0] == 64) |
mjr | 1:d913e0afb2ac | 229 | { |
mjr | 0:5acbbe3f4cf4 | 230 | // LWZ-SBA - first four bytes are bit-packed on/off flags |
mjr | 0:5acbbe3f4cf4 | 231 | // for the outputs; 5th byte is the pulse speed (0-7) |
mjr | 0:5acbbe3f4cf4 | 232 | //printf("LWZ-SBA %02x %02x %02x %02x ; %02x\r\n", |
mjr | 0:5acbbe3f4cf4 | 233 | // data[1], data[2], data[3], data[4], data[5]); |
mjr | 0:5acbbe3f4cf4 | 234 | |
mjr | 0:5acbbe3f4cf4 | 235 | // update all on/off states |
mjr | 0:5acbbe3f4cf4 | 236 | for (int i = 0, bit = 1, ri = 1 ; i < 32 ; ++i, bit <<= 1) |
mjr | 0:5acbbe3f4cf4 | 237 | { |
mjr | 0:5acbbe3f4cf4 | 238 | if (bit == 0x100) { |
mjr | 0:5acbbe3f4cf4 | 239 | bit = 1; |
mjr | 0:5acbbe3f4cf4 | 240 | ++ri; |
mjr | 0:5acbbe3f4cf4 | 241 | } |
mjr | 1:d913e0afb2ac | 242 | wizOn[i] = ((data[ri] & bit) != 0); |
mjr | 0:5acbbe3f4cf4 | 243 | } |
mjr | 0:5acbbe3f4cf4 | 244 | |
mjr | 1:d913e0afb2ac | 245 | // update the physical outputs |
mjr | 1:d913e0afb2ac | 246 | updateWizOuts(); |
mjr | 0:5acbbe3f4cf4 | 247 | |
mjr | 0:5acbbe3f4cf4 | 248 | // reset the PBA counter |
mjr | 0:5acbbe3f4cf4 | 249 | pbaIdx = 0; |
mjr | 0:5acbbe3f4cf4 | 250 | } |
mjr | 1:d913e0afb2ac | 251 | else |
mjr | 1:d913e0afb2ac | 252 | { |
mjr | 0:5acbbe3f4cf4 | 253 | // LWZ-PBA - full state dump; each byte is one output |
mjr | 0:5acbbe3f4cf4 | 254 | // in the current bank. pbaIdx keeps track of the bank; |
mjr | 0:5acbbe3f4cf4 | 255 | // this is incremented implicitly by each PBA message. |
mjr | 0:5acbbe3f4cf4 | 256 | //printf("LWZ-PBA[%d] %02x %02x %02x %02x %02x %02x %02x %02x\r\n", |
mjr | 0:5acbbe3f4cf4 | 257 | // pbaIdx, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]); |
mjr | 0:5acbbe3f4cf4 | 258 | |
mjr | 0:5acbbe3f4cf4 | 259 | // update all output profile settings |
mjr | 0:5acbbe3f4cf4 | 260 | for (int i = 0 ; i < 8 ; ++i) |
mjr | 1:d913e0afb2ac | 261 | wizVal[pbaIdx + i] = data[i]; |
mjr | 0:5acbbe3f4cf4 | 262 | |
mjr | 0:5acbbe3f4cf4 | 263 | // update the physical LED state if this is the last bank |
mjr | 0:5acbbe3f4cf4 | 264 | if (pbaIdx == 24) |
mjr | 1:d913e0afb2ac | 265 | updateWizOuts(); |
mjr | 0:5acbbe3f4cf4 | 266 | |
mjr | 0:5acbbe3f4cf4 | 267 | // advance to the next bank |
mjr | 0:5acbbe3f4cf4 | 268 | pbaIdx = (pbaIdx + 8) & 31; |
mjr | 0:5acbbe3f4cf4 | 269 | } |
mjr | 0:5acbbe3f4cf4 | 270 | } |
mjr | 1:d913e0afb2ac | 271 | |
mjr | 1:d913e0afb2ac | 272 | // check for plunger calibration |
mjr | 1:d913e0afb2ac | 273 | if (!calBtn) |
mjr | 0:5acbbe3f4cf4 | 274 | { |
mjr | 1:d913e0afb2ac | 275 | // check the state |
mjr | 1:d913e0afb2ac | 276 | switch (calBtnState) |
mjr | 0:5acbbe3f4cf4 | 277 | { |
mjr | 1:d913e0afb2ac | 278 | case 0: |
mjr | 1:d913e0afb2ac | 279 | // button not yet pushed - start debouncing |
mjr | 1:d913e0afb2ac | 280 | calBtnTimer.reset(); |
mjr | 1:d913e0afb2ac | 281 | calBtnDownTime = calBtnTimer.read_ms(); |
mjr | 1:d913e0afb2ac | 282 | calBtnState = 1; |
mjr | 1:d913e0afb2ac | 283 | break; |
mjr | 1:d913e0afb2ac | 284 | |
mjr | 1:d913e0afb2ac | 285 | case 1: |
mjr | 1:d913e0afb2ac | 286 | // pushed, not yet debounced - if the debounce time has |
mjr | 1:d913e0afb2ac | 287 | // passed, start the hold period |
mjr | 1:d913e0afb2ac | 288 | if (calBtnTimer.read_ms() - calBtnDownTime > 50) |
mjr | 1:d913e0afb2ac | 289 | calBtnState = 2; |
mjr | 1:d913e0afb2ac | 290 | break; |
mjr | 1:d913e0afb2ac | 291 | |
mjr | 1:d913e0afb2ac | 292 | case 2: |
mjr | 1:d913e0afb2ac | 293 | // in the hold period - if the button has been held down |
mjr | 1:d913e0afb2ac | 294 | // for the entire hold period, move to calibration mode |
mjr | 1:d913e0afb2ac | 295 | if (calBtnTimer.read_ms() - calBtnDownTime > 2050) |
mjr | 1:d913e0afb2ac | 296 | { |
mjr | 1:d913e0afb2ac | 297 | // enter calibration mode |
mjr | 1:d913e0afb2ac | 298 | calBtnState = 3; |
mjr | 1:d913e0afb2ac | 299 | |
mjr | 1:d913e0afb2ac | 300 | // reset the calibration limits |
mjr | 2:c174f9ee414a | 301 | cfg.d.plungerMax = 0; |
mjr | 2:c174f9ee414a | 302 | cfg.d.plungerMin = npix; |
mjr | 1:d913e0afb2ac | 303 | } |
mjr | 1:d913e0afb2ac | 304 | break; |
mjr | 2:c174f9ee414a | 305 | |
mjr | 2:c174f9ee414a | 306 | case 3: |
mjr | 2:c174f9ee414a | 307 | // Already in calibration mode - pushing the button in this |
mjr | 2:c174f9ee414a | 308 | // state doesn't change the current state, but we won't leave |
mjr | 2:c174f9ee414a | 309 | // this state as long as it's held down. We can simply do |
mjr | 2:c174f9ee414a | 310 | // nothing here. |
mjr | 2:c174f9ee414a | 311 | break; |
mjr | 0:5acbbe3f4cf4 | 312 | } |
mjr | 0:5acbbe3f4cf4 | 313 | } |
mjr | 1:d913e0afb2ac | 314 | else |
mjr | 1:d913e0afb2ac | 315 | { |
mjr | 2:c174f9ee414a | 316 | // Button released. If we're in calibration mode, and |
mjr | 2:c174f9ee414a | 317 | // the calibration time has elapsed, end the calibration |
mjr | 2:c174f9ee414a | 318 | // and save the results to flash. |
mjr | 2:c174f9ee414a | 319 | // |
mjr | 2:c174f9ee414a | 320 | // Otherwise, return to the base state without saving anything. |
mjr | 2:c174f9ee414a | 321 | // If the button is released before we make it to calibration |
mjr | 2:c174f9ee414a | 322 | // mode, it simply cancels the attempt. |
mjr | 2:c174f9ee414a | 323 | if (calBtnState == 3 |
mjr | 2:c174f9ee414a | 324 | && calBtnTimer.read_ms() - calBtnDownTime > 17500) |
mjr | 2:c174f9ee414a | 325 | { |
mjr | 2:c174f9ee414a | 326 | // exit calibration mode |
mjr | 1:d913e0afb2ac | 327 | calBtnState = 0; |
mjr | 2:c174f9ee414a | 328 | |
mjr | 2:c174f9ee414a | 329 | // Save the current configuration state to flash, so that it |
mjr | 2:c174f9ee414a | 330 | // will be preserved through power off. Update the checksum |
mjr | 2:c174f9ee414a | 331 | // first so that we recognize the flash record as valid. |
mjr | 2:c174f9ee414a | 332 | cfg.checksum = CRC32(&cfg.d, sizeof(cfg.d)); |
mjr | 2:c174f9ee414a | 333 | iap.erase_sector(flash_addr); |
mjr | 2:c174f9ee414a | 334 | iap.program_flash(flash_addr, &cfg, sizeof(cfg)); |
mjr | 2:c174f9ee414a | 335 | |
mjr | 2:c174f9ee414a | 336 | // the flash state is now valid |
mjr | 2:c174f9ee414a | 337 | flash_valid = true; |
mjr | 2:c174f9ee414a | 338 | } |
mjr | 2:c174f9ee414a | 339 | else if (calBtnState != 3) |
mjr | 2:c174f9ee414a | 340 | { |
mjr | 2:c174f9ee414a | 341 | // didn't make it to calibration mode - cancel the operation |
mjr | 1:d913e0afb2ac | 342 | calBtnState = 0; |
mjr | 2:c174f9ee414a | 343 | } |
mjr | 1:d913e0afb2ac | 344 | } |
mjr | 1:d913e0afb2ac | 345 | |
mjr | 1:d913e0afb2ac | 346 | // light/flash the calibration button light, if applicable |
mjr | 1:d913e0afb2ac | 347 | int newCalBtnLit = calBtnLit; |
mjr | 1:d913e0afb2ac | 348 | switch (calBtnState) |
mjr | 0:5acbbe3f4cf4 | 349 | { |
mjr | 1:d913e0afb2ac | 350 | case 2: |
mjr | 1:d913e0afb2ac | 351 | // in the hold period - flash the light |
mjr | 1:d913e0afb2ac | 352 | newCalBtnLit = (((calBtnTimer.read_ms() - calBtnDownTime)/250) & 1); |
mjr | 1:d913e0afb2ac | 353 | break; |
mjr | 1:d913e0afb2ac | 354 | |
mjr | 1:d913e0afb2ac | 355 | case 3: |
mjr | 1:d913e0afb2ac | 356 | // calibration mode - show steady on |
mjr | 1:d913e0afb2ac | 357 | newCalBtnLit = true; |
mjr | 1:d913e0afb2ac | 358 | break; |
mjr | 1:d913e0afb2ac | 359 | |
mjr | 1:d913e0afb2ac | 360 | default: |
mjr | 1:d913e0afb2ac | 361 | // not calibrating/holding - show steady off |
mjr | 1:d913e0afb2ac | 362 | newCalBtnLit = false; |
mjr | 1:d913e0afb2ac | 363 | break; |
mjr | 1:d913e0afb2ac | 364 | } |
mjr | 1:d913e0afb2ac | 365 | if (calBtnLit != newCalBtnLit) |
mjr | 1:d913e0afb2ac | 366 | { |
mjr | 1:d913e0afb2ac | 367 | calBtnLit = newCalBtnLit; |
mjr | 2:c174f9ee414a | 368 | if (calBtnLit) { |
mjr | 2:c174f9ee414a | 369 | calBtnLed = 1; |
mjr | 2:c174f9ee414a | 370 | led1 = 0; |
mjr | 2:c174f9ee414a | 371 | led2 = 0; |
mjr | 2:c174f9ee414a | 372 | led3 = 1; |
mjr | 2:c174f9ee414a | 373 | } |
mjr | 2:c174f9ee414a | 374 | else { |
mjr | 2:c174f9ee414a | 375 | calBtnLed = 0; |
mjr | 2:c174f9ee414a | 376 | led1 = 1; |
mjr | 2:c174f9ee414a | 377 | led2 = 1; |
mjr | 2:c174f9ee414a | 378 | led3 = 0; |
mjr | 2:c174f9ee414a | 379 | } |
mjr | 1:d913e0afb2ac | 380 | } |
mjr | 1:d913e0afb2ac | 381 | |
mjr | 1:d913e0afb2ac | 382 | // read the plunger sensor |
mjr | 1:d913e0afb2ac | 383 | int znew = z; |
mjr | 2:c174f9ee414a | 384 | uint16_t pix[npix]; |
mjr | 2:c174f9ee414a | 385 | ccd.read(pix, npix); |
mjr | 2:c174f9ee414a | 386 | |
mjr | 2:c174f9ee414a | 387 | // get the average brightness at each end of the sensor |
mjr | 2:c174f9ee414a | 388 | long avg1 = (long(pix[0]) + long(pix[1]) + long(pix[2]) + long(pix[3]) + long(pix[4]))/5; |
mjr | 2:c174f9ee414a | 389 | long avg2 = (long(pix[npix-1]) + long(pix[npix-2]) + long(pix[npix-3]) + long(pix[npix-4]) + long(pix[npix-5]))/5; |
mjr | 2:c174f9ee414a | 390 | |
mjr | 2:c174f9ee414a | 391 | // figure the midpoint in the brightness; multiply by 3 so that we can |
mjr | 2:c174f9ee414a | 392 | // compare sums of three pixels at a time to smooth out noise |
mjr | 2:c174f9ee414a | 393 | long midpt = (avg1 + avg2)/2 * 3; |
mjr | 2:c174f9ee414a | 394 | |
mjr | 2:c174f9ee414a | 395 | // Work from the bright end to the dark end. VP interprets the |
mjr | 2:c174f9ee414a | 396 | // Z axis value as the amount the plunger is pulled: the minimum |
mjr | 2:c174f9ee414a | 397 | // is the rest position, the maximum is fully pulled. So we |
mjr | 2:c174f9ee414a | 398 | // essentially want to report how much of the sensor is lit, |
mjr | 2:c174f9ee414a | 399 | // since this increases as the plunger is pulled back. |
mjr | 2:c174f9ee414a | 400 | int si = 1, di = 1; |
mjr | 2:c174f9ee414a | 401 | if (avg1 < avg2) |
mjr | 2:c174f9ee414a | 402 | si = npix - 2, di = -1; |
mjr | 2:c174f9ee414a | 403 | |
mjr | 2:c174f9ee414a | 404 | // scan for the midpoint |
mjr | 2:c174f9ee414a | 405 | uint16_t *pixp = pix + si; |
mjr | 2:c174f9ee414a | 406 | for (int n = 1 ; n < npix - 1 ; ++n, pixp += di) |
mjr | 1:d913e0afb2ac | 407 | { |
mjr | 2:c174f9ee414a | 408 | // if we've crossed the midpoint, report this position |
mjr | 2:c174f9ee414a | 409 | if (long(pixp[-1]) + long(pixp[0]) + long(pixp[1]) < midpt) |
mjr | 1:d913e0afb2ac | 410 | { |
mjr | 2:c174f9ee414a | 411 | // note the new position |
mjr | 2:c174f9ee414a | 412 | int pos = n; |
mjr | 2:c174f9ee414a | 413 | |
mjr | 2:c174f9ee414a | 414 | // if the bright end and dark end don't differ by enough, skip this |
mjr | 2:c174f9ee414a | 415 | // reading entirely - we must have an overexposed or underexposed frame |
mjr | 2:c174f9ee414a | 416 | if (labs(avg1 - avg2) < 0x3333) |
mjr | 2:c174f9ee414a | 417 | break; |
mjr | 2:c174f9ee414a | 418 | |
mjr | 2:c174f9ee414a | 419 | // Calibrate, or apply calibration, depending on the mode. |
mjr | 2:c174f9ee414a | 420 | // In either case, normalize to a 0-127 range. VP appears to |
mjr | 2:c174f9ee414a | 421 | // ignore negative Z axis values. |
mjr | 2:c174f9ee414a | 422 | if (calBtnState == 3) |
mjr | 1:d913e0afb2ac | 423 | { |
mjr | 2:c174f9ee414a | 424 | // calibrating - note if we're expanding the calibration envelope |
mjr | 2:c174f9ee414a | 425 | if (pos < cfg.d.plungerMin) |
mjr | 2:c174f9ee414a | 426 | cfg.d.plungerMin = pos; |
mjr | 2:c174f9ee414a | 427 | if (pos > cfg.d.plungerMax) |
mjr | 2:c174f9ee414a | 428 | cfg.d.plungerMax = pos; |
mjr | 2:c174f9ee414a | 429 | |
mjr | 2:c174f9ee414a | 430 | // normalize to the full physical range while calibrating |
mjr | 2:c174f9ee414a | 431 | znew = int(float(pos)/npix * 127); |
mjr | 1:d913e0afb2ac | 432 | } |
mjr | 2:c174f9ee414a | 433 | else |
mjr | 2:c174f9ee414a | 434 | { |
mjr | 2:c174f9ee414a | 435 | // running normally - normalize to the calibration range |
mjr | 2:c174f9ee414a | 436 | if (pos < cfg.d.plungerMin) |
mjr | 2:c174f9ee414a | 437 | pos = cfg.d.plungerMin; |
mjr | 2:c174f9ee414a | 438 | if (pos > cfg.d.plungerMax) |
mjr | 2:c174f9ee414a | 439 | pos = cfg.d.plungerMax; |
mjr | 2:c174f9ee414a | 440 | znew = int(float(pos - cfg.d.plungerMin) |
mjr | 2:c174f9ee414a | 441 | / (cfg.d.plungerMax - cfg.d.plungerMin + 1) * 127); |
mjr | 2:c174f9ee414a | 442 | } |
mjr | 2:c174f9ee414a | 443 | |
mjr | 2:c174f9ee414a | 444 | // done |
mjr | 2:c174f9ee414a | 445 | break; |
mjr | 1:d913e0afb2ac | 446 | } |
mjr | 2:c174f9ee414a | 447 | } |
mjr | 1:d913e0afb2ac | 448 | |
mjr | 1:d913e0afb2ac | 449 | // read the accelerometer |
mjr | 1:d913e0afb2ac | 450 | float xa, ya; |
mjr | 1:d913e0afb2ac | 451 | accel.getAccXY(xa, ya); |
mjr | 1:d913e0afb2ac | 452 | |
mjr | 1:d913e0afb2ac | 453 | // check for auto-centering every so often |
mjr | 1:d913e0afb2ac | 454 | if (acTimer.read_ms() - t0ac > 1000) |
mjr | 1:d913e0afb2ac | 455 | { |
mjr | 1:d913e0afb2ac | 456 | // add the sample to the history list |
mjr | 1:d913e0afb2ac | 457 | accPrv[iAccPrv].x = xa; |
mjr | 1:d913e0afb2ac | 458 | accPrv[iAccPrv].y = ya; |
mjr | 1:d913e0afb2ac | 459 | |
mjr | 1:d913e0afb2ac | 460 | // store the slot |
mjr | 1:d913e0afb2ac | 461 | iAccPrv += 1; |
mjr | 1:d913e0afb2ac | 462 | iAccPrv %= maxAccPrv; |
mjr | 1:d913e0afb2ac | 463 | nAccPrv += 1; |
mjr | 1:d913e0afb2ac | 464 | |
mjr | 1:d913e0afb2ac | 465 | // If we have a full complement, check for stability. The |
mjr | 1:d913e0afb2ac | 466 | // raw accelerometer input is in the rnage -4096 to 4096, but |
mjr | 1:d913e0afb2ac | 467 | // the class cover normalizes to a unit interval (-1.0 .. +1.0). |
mjr | 1:d913e0afb2ac | 468 | const float accTol = .005; |
mjr | 1:d913e0afb2ac | 469 | if (nAccPrv >= maxAccPrv |
mjr | 1:d913e0afb2ac | 470 | && accPrv[0].dist(accPrv[1]) < accTol |
mjr | 1:d913e0afb2ac | 471 | && accPrv[0].dist(accPrv[2]) < accTol |
mjr | 1:d913e0afb2ac | 472 | && accPrv[0].dist(accPrv[3]) < accTol |
mjr | 1:d913e0afb2ac | 473 | && accPrv[0].dist(accPrv[4]) < accTol) |
mjr | 1:d913e0afb2ac | 474 | { |
mjr | 1:d913e0afb2ac | 475 | // figure the new center |
mjr | 1:d913e0afb2ac | 476 | xCenter = (accPrv[0].x + accPrv[1].x + accPrv[2].x + accPrv[3].x + accPrv[4].x)/5.0; |
mjr | 1:d913e0afb2ac | 477 | yCenter = (accPrv[0].y + accPrv[1].y + accPrv[2].y + accPrv[3].y + accPrv[4].y)/5.0; |
mjr | 1:d913e0afb2ac | 478 | } |
mjr | 1:d913e0afb2ac | 479 | |
mjr | 1:d913e0afb2ac | 480 | // reset the auto-center timer |
mjr | 1:d913e0afb2ac | 481 | acTimer.reset(); |
mjr | 1:d913e0afb2ac | 482 | t0ac = acTimer.read_ms(); |
mjr | 1:d913e0afb2ac | 483 | } |
mjr | 1:d913e0afb2ac | 484 | |
mjr | 1:d913e0afb2ac | 485 | // adjust for our auto centering |
mjr | 1:d913e0afb2ac | 486 | xa -= xCenter; |
mjr | 1:d913e0afb2ac | 487 | ya -= yCenter; |
mjr | 1:d913e0afb2ac | 488 | |
mjr | 1:d913e0afb2ac | 489 | // confine to the unit interval |
mjr | 1:d913e0afb2ac | 490 | if (xa < -1.0) xa = -1.0; |
mjr | 1:d913e0afb2ac | 491 | if (xa > 1.0) xa = 1.0; |
mjr | 1:d913e0afb2ac | 492 | if (ya < -1.0) ya = -1.0; |
mjr | 1:d913e0afb2ac | 493 | if (ya > 1.0) ya = 1.0; |
mjr | 0:5acbbe3f4cf4 | 494 | |
mjr | 1:d913e0afb2ac | 495 | // figure the new mouse report data |
mjr | 1:d913e0afb2ac | 496 | int xnew = (int)(127 * xa); |
mjr | 1:d913e0afb2ac | 497 | int ynew = (int)(127 * ya); |
mjr | 2:c174f9ee414a | 498 | |
mjr | 2:c174f9ee414a | 499 | // store the updated joystick coordinates |
mjr | 2:c174f9ee414a | 500 | x = xnew; |
mjr | 2:c174f9ee414a | 501 | y = ynew; |
mjr | 2:c174f9ee414a | 502 | z = znew; |
mjr | 1:d913e0afb2ac | 503 | |
mjr | 2:c174f9ee414a | 504 | // if we're in USB suspend or disconnect mode, spin |
mjr | 2:c174f9ee414a | 505 | if (js.isSuspended() || !js.isConnected()) |
mjr | 0:5acbbe3f4cf4 | 506 | { |
mjr | 2:c174f9ee414a | 507 | // go dark (turn off the indicator LEDs) |
mjr | 2:c174f9ee414a | 508 | led2 = 1; |
mjr | 2:c174f9ee414a | 509 | led3 = 1; |
mjr | 2:c174f9ee414a | 510 | led1 = 1; |
mjr | 2:c174f9ee414a | 511 | |
mjr | 2:c174f9ee414a | 512 | // wait until we're connected and come out of suspend mode |
mjr | 2:c174f9ee414a | 513 | while (js.isSuspended() || !js.isConnected()) |
mjr | 2:c174f9ee414a | 514 | { |
mjr | 2:c174f9ee414a | 515 | // spin for a bit |
mjr | 2:c174f9ee414a | 516 | wait(1); |
mjr | 2:c174f9ee414a | 517 | |
mjr | 2:c174f9ee414a | 518 | // if we're not suspended, flash red; otherwise stay dark |
mjr | 2:c174f9ee414a | 519 | if (!js.isSuspended()) |
mjr | 2:c174f9ee414a | 520 | led1 = !led1; |
mjr | 2:c174f9ee414a | 521 | } |
mjr | 2:c174f9ee414a | 522 | } |
mjr | 1:d913e0afb2ac | 523 | |
mjr | 2:c174f9ee414a | 524 | // Send the status report. Note one of the axes needs to be |
mjr | 2:c174f9ee414a | 525 | // reversed, because the native accelerometer reports seem to |
mjr | 2:c174f9ee414a | 526 | // assume that the card is component side down; we have to |
mjr | 2:c174f9ee414a | 527 | // reverse one or the other axis to account for the reversed |
mjr | 2:c174f9ee414a | 528 | // coordinate system. It doesn't really matter which one, |
mjr | 2:c174f9ee414a | 529 | // but reversing Y seems to give intuitive results when viewed |
mjr | 2:c174f9ee414a | 530 | // in the Windows joystick control panel. Note that the |
mjr | 2:c174f9ee414a | 531 | // coordinate system we report is ultimately arbitrary, since |
mjr | 2:c174f9ee414a | 532 | // Visual Pinball has preference settings that let us set up |
mjr | 2:c174f9ee414a | 533 | // axis reversals and a global rotation for the joystick. |
mjr | 2:c174f9ee414a | 534 | js.update(x, -y, z, 0); |
mjr | 1:d913e0afb2ac | 535 | |
mjr | 2:c174f9ee414a | 536 | // show a heartbeat flash in blue every so often if not in |
mjr | 2:c174f9ee414a | 537 | // calibration mode |
mjr | 2:c174f9ee414a | 538 | if (calBtnState < 2 && hbTimer.read_ms() - t0Hb > 1000) |
mjr | 1:d913e0afb2ac | 539 | { |
mjr | 2:c174f9ee414a | 540 | if (js.isSuspended()) |
mjr | 2:c174f9ee414a | 541 | { |
mjr | 2:c174f9ee414a | 542 | // suspended - turn off the LEDs entirely |
mjr | 2:c174f9ee414a | 543 | led1 = 1; |
mjr | 2:c174f9ee414a | 544 | led2 = 1; |
mjr | 2:c174f9ee414a | 545 | led3 = 1; |
mjr | 2:c174f9ee414a | 546 | } |
mjr | 2:c174f9ee414a | 547 | else if (!js.isConnected()) |
mjr | 2:c174f9ee414a | 548 | { |
mjr | 2:c174f9ee414a | 549 | // not connected - flash red |
mjr | 2:c174f9ee414a | 550 | hb = !hb; |
mjr | 2:c174f9ee414a | 551 | led1 = (hb ? 0 : 1); |
mjr | 2:c174f9ee414a | 552 | led2 = 1; |
mjr | 2:c174f9ee414a | 553 | led3 = 1; |
mjr | 2:c174f9ee414a | 554 | } |
mjr | 2:c174f9ee414a | 555 | else if (flash_valid) |
mjr | 2:c174f9ee414a | 556 | { |
mjr | 2:c174f9ee414a | 557 | // connected, NVM valid - flash blue/green |
mjr | 2:c174f9ee414a | 558 | hb = !hb; |
mjr | 2:c174f9ee414a | 559 | led1 = 1; |
mjr | 2:c174f9ee414a | 560 | led2 = (hb ? 0 : 1); |
mjr | 2:c174f9ee414a | 561 | led3 = (hb ? 1 : 0); |
mjr | 2:c174f9ee414a | 562 | } |
mjr | 2:c174f9ee414a | 563 | else |
mjr | 2:c174f9ee414a | 564 | { |
mjr | 2:c174f9ee414a | 565 | // connected, factory reset - flash yellow/green |
mjr | 2:c174f9ee414a | 566 | hb = !hb; |
mjr | 2:c174f9ee414a | 567 | led1 = (hb ? 0 : 1); |
mjr | 2:c174f9ee414a | 568 | led2 = 0; |
mjr | 2:c174f9ee414a | 569 | led3 = 0; |
mjr | 2:c174f9ee414a | 570 | } |
mjr | 1:d913e0afb2ac | 571 | |
mjr | 1:d913e0afb2ac | 572 | // reset the heartbeat timer |
mjr | 1:d913e0afb2ac | 573 | hbTimer.reset(); |
mjr | 1:d913e0afb2ac | 574 | t0Hb = hbTimer.read_ms(); |
mjr | 1:d913e0afb2ac | 575 | } |
mjr | 1:d913e0afb2ac | 576 | } |
mjr | 0:5acbbe3f4cf4 | 577 | } |