work in progress

Dependencies:   FastAnalogIn FastIO USBDevice mbed FastPWM SimpleDMA

Fork of Pinscape_Controller by Mike R

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?

UserRevisionLine numberNew 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 }