diff --git a/src/Kaleidoscope-Hardware-Dygma-Raise.h b/src/Kaleidoscope-Hardware-Dygma-Raise.h new file mode 100644 index 00000000..f87b9f87 --- /dev/null +++ b/src/Kaleidoscope-Hardware-Dygma-Raise.h @@ -0,0 +1,21 @@ +/* -*- mode: c++ -*- + * kaleidoscope::device::dygma::Raise -- Kaleidoscope device plugin for Dygma Raise + * Copyright (C) 2017-2019 Keyboard.io, Inc + * Copyright (C) 2017-2019 Dygma Lab S.L. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +#include "kaleidoscope/device/dygma/Raise.h" diff --git a/src/kaleidoscope/device/dygma/Raise.cpp b/src/kaleidoscope/device/dygma/Raise.cpp new file mode 100644 index 00000000..45f134b7 --- /dev/null +++ b/src/kaleidoscope/device/dygma/Raise.cpp @@ -0,0 +1,515 @@ +/* -*- mode: c++ -*- + * kaleidoscope::device::dygma::Raise -- Kaleidoscope device plugin for Dygma Raise + * Copyright (C) 2017-2019 Keyboard.io, Inc + * Copyright (C) 2017-2019 Dygma Lab S.L. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifdef ARDUINO_SAMD_RAISE + +#include +#include +#include +#include +#include + +#include "kaleidoscope/util/crc16.h" +#include "kaleidoscope/driver/color/GammaCorrection.h" + +#define I2C_CLOCK_KHZ 200 +#define I2C_FLASH_CLOCK_KHZ 100 // flashing doesn't work reliably at higher clock speeds + +#define SIDE_POWER 1 // side power switch pa10 + +#define LAYOUT_ISO 0 +#define LAYOUT_ANSI 1 + +namespace kaleidoscope { +namespace device { +namespace dygma { + +/********* RaiseHands *********/ + +struct RaiseHands { + static raise::Hand leftHand; + static raise::Hand rightHand; + + static void setup(); + static void initializeSides(); + + static uint8_t layout; + + static void setSidePower(bool power); + static bool getSidePower() { + return side_power_; + } + + static void keyscanInterval(uint16_t interval); + static uint16_t keyscanInterval() { + return keyscan_interval_; + } + + private: + static uint16_t keyscan_interval_; + static bool side_power_; + static uint16_t settings_base_; + static constexpr uint8_t iso_only_led_ = 19; +}; + +raise::Hand RaiseHands::leftHand(0); +raise::Hand RaiseHands::rightHand(1); +uint8_t RaiseHands::layout; +bool RaiseHands::side_power_; +uint16_t RaiseHands::settings_base_; +uint16_t RaiseHands::keyscan_interval_ = 50; + +void RaiseHands::setSidePower(bool power) { + digitalWrite(SIDE_POWER, power ? HIGH : LOW); + side_power_ = power; +} + +void RaiseHands::setup() { + settings_base_ = ::EEPROMSettings.requestSlice(sizeof(keyscan_interval_)); + + // If keyscan is max, assume that EEPROM is uninitialized, and store the + // defaults. + uint16_t interval; + Kaleidoscope.storage().get(settings_base_, interval); + if (interval == 0xffff) { + Kaleidoscope.storage().put(settings_base_, keyscan_interval_); + Kaleidoscope.storage().commit(); + } + Kaleidoscope.storage().get(settings_base_, keyscan_interval_); +} + +void RaiseHands::keyscanInterval(uint16_t interval) { + leftHand.setKeyscanInterval(interval); + rightHand.setKeyscanInterval(interval); + keyscan_interval_ = interval; + Kaleidoscope.storage().put(settings_base_, keyscan_interval_); + Kaleidoscope.storage().commit(); +} + +void RaiseHands::initializeSides() { + // key scan interval from eeprom + leftHand.setKeyscanInterval(keyscan_interval_); + rightHand.setKeyscanInterval(keyscan_interval_); + + // get ANSI/ISO at every side replug + uint8_t l_layout = leftHand.readLayout(); + uint8_t r_layout = rightHand.readLayout(); + + // setup layout variable, this will affect led mapping - defaults to ISO if + // nothing reported + // FIXME + if (l_layout == 1 || r_layout == 1) + layout = 1; + else + layout = 0; + + /* + * if the neuron starts up with no sides connected, it will assume ISO. This + * turns on an extra LED (hardware LED 19 on left side). If an ANSI left is + * then plugged in, the keyboard will switch to ANSI, but LED 19 can't get + * wiped because the ANSI LED map doesn't include this LED. It will be driven + * from the SLED1735's memory with the same colour as before, which causes + * weird looking colours to come on on other seemingly unrelated keys. So: on + * a replug, set LED 19 to off to be safe. + */ + leftHand.led_data.leds[iso_only_led_] = {0, 0, 0}; + + // get activated LED plugin to refresh + ::LEDControl.refreshAll(); +} + +/********* LED Driver *********/ + +bool RaiseLEDDriver::isLEDChangedNeuron; +uint8_t RaiseLEDDriver::isLEDChangedLeft[LED_BANKS]; +uint8_t RaiseLEDDriver::isLEDChangedRight[LED_BANKS]; +cRGB RaiseLEDDriver::neuronLED; +constexpr uint8_t RaiseLEDDriver::led_map[][RaiseLEDDriverProps::led_count + 1]; + +constexpr uint8_t RaiseLEDDriverProps::key_led_map[]; + +void RaiseLEDDriver::syncLeds() { + // left and right sides + for (uint8_t i = 0; i < LED_BANKS; i ++) { + // only send the banks that have changed - try to improve jitter performance + if (isLEDChangedLeft[i]) { + RaiseHands::leftHand.sendLEDBank(i); + isLEDChangedLeft[i] = false; + } + if (isLEDChangedRight[i]) { + RaiseHands::rightHand.sendLEDBank(i); + isLEDChangedRight[i] = false; + } + } + + if (isLEDChangedNeuron) { + updateNeuronLED(); + isLEDChangedNeuron = false; + } +} + +void RaiseLEDDriver::updateNeuronLED() { + static constexpr struct { + uint8_t r, g, b; + } pins = { 3, 5, 4 }; + auto constexpr gamma8 = kaleidoscope::driver::color::gamma_correction; + + // invert as these are common anode, and make sure we reach 65535 to be able + // to turn fully off. + analogWrite(pins.r, ((256 - pgm_read_byte(&gamma8[neuronLED.r])) << 8) - 1); + analogWrite(pins.g, ((256 - pgm_read_byte(&gamma8[neuronLED.g])) << 8) - 1); + analogWrite(pins.b, ((256 - pgm_read_byte(&gamma8[neuronLED.b])) << 8) - 1); +} + +void RaiseLEDDriver::setCrgbAt(uint8_t i, cRGB crgb) { + // prevent reading off the end of the led_map array + if (i >= Props_::led_count) + return; + + // neuron LED + if (i == Props_::led_count - 1) { + isLEDChangedNeuron |= !(neuronLED.r == crgb.r && + neuronLED.g == crgb.g && + neuronLED.b == crgb.b); + neuronLED = crgb; + return; + } + + // get the SLED index + uint8_t sled_num = led_map[RaiseHands::layout][i]; + if (sled_num < LEDS_PER_HAND) { + cRGB oldColor = RaiseHands::leftHand.led_data.leds[sled_num]; + RaiseHands::leftHand.led_data.leds[sled_num] = crgb; + isLEDChangedLeft[uint8_t(sled_num / 8)] |= !(oldColor.r == crgb.r && + oldColor.g == crgb.g && + oldColor.b == crgb.b); + } else if (sled_num < 2 * LEDS_PER_HAND) { + cRGB oldColor = RaiseHands::rightHand.led_data.leds[sled_num - LEDS_PER_HAND]; + RaiseHands::rightHand.led_data.leds[sled_num - LEDS_PER_HAND] = crgb; + isLEDChangedRight[uint8_t((sled_num - LEDS_PER_HAND) / 8)] |= + !(oldColor.r == crgb.r && + oldColor.g == crgb.g && + oldColor.b == crgb.b); + } else { + // TODO(anyone): + // how do we want to handle debugging assertions about crazy user + // code that would overwrite other memory? + } +} + +cRGB RaiseLEDDriver::getCrgbAt(uint8_t i) { + if (i >= Props_::led_count) + return {0, 0, 0}; + + uint8_t sled_num = led_map[RaiseHands::layout][i]; + if (sled_num < LEDS_PER_HAND) { + return RaiseHands::leftHand.led_data.leds[sled_num]; + } else if (sled_num < 2 * LEDS_PER_HAND) { + return RaiseHands::rightHand.led_data.leds[sled_num - LEDS_PER_HAND]; + } else { + return {0, 0, 0}; + } +} + +void RaiseLEDDriver::setup() { + pinMode(SIDE_POWER, OUTPUT); + RaiseHands::setSidePower(false); + + // arduino zero analogWrite(255) isn't fully on as its actually working with a + // 16bit counter and the mapping is a bit shift. + // so change to 16 bit resolution to avoid the mapping and do the mapping + // ourselves in updateHubleLED() to ensure LEDs can be set fully off + analogWriteResolution(16); + updateNeuronLED(); + + delay(10); + RaiseHands::setSidePower(true); + delay(500); // wait for sides to power up and finish bootloader +} + +/********* Key scanner *********/ + +raise::keydata_t RaiseKeyScanner::leftHandState; +raise::keydata_t RaiseKeyScanner::rightHandState; +raise::keydata_t RaiseKeyScanner::previousLeftHandState; +raise::keydata_t RaiseKeyScanner::previousRightHandState; +raise::keydata_t RaiseKeyScanner::leftHandMask; +raise::keydata_t RaiseKeyScanner::rightHandMask; +bool RaiseKeyScanner::lastLeftOnline; +bool RaiseKeyScanner::lastRightOnline; + +void RaiseKeyScanner::readMatrix() { + previousLeftHandState = leftHandState; + previousRightHandState = rightHandState; + + if (RaiseHands::leftHand.readKeys()) { + leftHandState = RaiseHands::leftHand.getKeyData(); + // if ANSI, then swap r3c0 and r3c1 to match the PCB + if (RaiseHands::layout == LAYOUT_ANSI) + // only swap if bits are different + if ((leftHandState.rows[3] & (1 << 0)) ^ leftHandState.rows[3] & (1 << 1)) { + leftHandState.rows[3] ^= (1 << 0); // flip the bit + leftHandState.rows[3] ^= (1 << 1); // flip the bit + } + } + + if (RaiseHands::rightHand.readKeys()) { + rightHandState = RaiseHands::rightHand.getKeyData(); + // if ANSI, then swap r1c0 and r2c0 to match the PCB + if (RaiseHands::layout == LAYOUT_ANSI) + if ((rightHandState.rows[1] & (1 << 0)) ^ rightHandState.rows[2] & (1 << 0)) { + rightHandState.rows[1] ^= (1 << 0); + rightHandState.rows[2] ^= (1 << 0); + } + } + + // if a side has just been replugged, initialise it + if ((RaiseHands::leftHand.online && !lastLeftOnline) || + (RaiseHands::rightHand.online && !lastRightOnline)) + RaiseHands::initializeSides(); + + // if a side has just been unplugged, wipe its state + if (!RaiseHands::leftHand.online && lastLeftOnline) + leftHandState.all = 0; + + if (!RaiseHands::rightHand.online && lastRightOnline) + rightHandState.all = 0; + + // store previous state of whether the sides are plugged in + lastLeftOnline = RaiseHands::leftHand.online; + lastRightOnline = RaiseHands::rightHand.online; +} + +void RaiseKeyScanner::actOnMatrixScan() { + for (byte row = 0; row < Props_::matrix_rows; row++) { + for (byte col = 0; col < Props_::matrix_columns; col++) { + uint8_t keynum = (row * Props_::matrix_rows) + (col); + uint8_t keyState; + + // left + keyState = (bitRead(previousLeftHandState.all, keynum) << 0) | + (bitRead(leftHandState.all, keynum) << 1); + if (keyState) + ThisType::handleKeyswitchEvent(Key_NoKey, KeyAddr(row, col), keyState); + + // right + keyState = (bitRead(previousRightHandState.all, keynum) << 0) | + (bitRead(rightHandState.all, keynum) << 1); + if (keyState) + ThisType::handleKeyswitchEvent(Key_NoKey, KeyAddr(row, (Props_::matrix_columns - 1) - col), keyState); + } + } +} + +void RaiseKeyScanner::scanMatrix() { + readMatrix(); + actOnMatrixScan(); +} + +void RaiseKeyScanner::maskKey(KeyAddr key_addr) { + if (!key_addr.isValid()) + return; + + auto row = key_addr.row(); + auto col = key_addr.col(); + + if (col >= Props_::left_columns) { + rightHandMask.rows[row] |= 1 << (Props_::right_columns - (col - Props_::left_columns)); + } else { + leftHandMask.rows[row] |= 1 << (Props_::right_columns - col); + } +} + +void RaiseKeyScanner::unMaskKey(KeyAddr key_addr) { + if (!key_addr.isValid()) + return; + + auto row = key_addr.row(); + auto col = key_addr.col(); + + if (col >= Props_::left_columns) { + rightHandMask.rows[row] &= ~(1 << (Props_::right_columns - (col - Props_::left_columns))); + } else { + leftHandMask.rows[row] &= ~(1 << (Props_::right_columns - col)); + } +} + +bool RaiseKeyScanner::isKeyMasked(KeyAddr key_addr) { + if (!key_addr.isValid()) + return false; + + auto row = key_addr.row(); + auto col = key_addr.col(); + + if (col >= 8) { + return rightHandMask.rows[row] & (1 << (7 - (col - 8))); + } else { + return leftHandMask.rows[row] & (1 << (7 - col)); + } +} + +void RaiseKeyScanner::maskHeldKeys() { + memcpy(leftHandMask.rows, leftHandState.rows, sizeof(leftHandMask)); + memcpy(rightHandMask.rows, rightHandState.rows, sizeof(rightHandMask)); +} + +bool RaiseKeyScanner::isKeyswitchPressed(KeyAddr key_addr) { + auto row = key_addr.row(); + auto col = key_addr.col(); + + if (col >= Props_::left_columns) { + return (bitRead(rightHandState.rows[row], (Props_::matrix_columns - 1) - col) != 0); + } else { + return (bitRead(leftHandState.rows[row], col) != 0); + } +} + +bool RaiseKeyScanner::wasKeyswitchPressed(KeyAddr key_addr) { + auto row = key_addr.row(); + auto col = key_addr.col(); + + if (col >= Props_::left_columns) { + return (bitRead(previousRightHandState.rows[row], (Props_::matrix_columns - 1) - col) != 0); + } else { + return (bitRead(previousLeftHandState.rows[row], col) != 0); + } +} + +uint8_t RaiseKeyScanner::pressedKeyswitchCount() { + return __builtin_popcountl(leftHandState.all) + __builtin_popcountl(rightHandState.all); +} + +uint8_t RaiseKeyScanner::previousPressedKeyswitchCount() { + return __builtin_popcountl(previousLeftHandState.all) + __builtin_popcountl(previousRightHandState.all); +} + +void RaiseKeyScanner::setKeyscanInterval(uint8_t interval) { + RaiseHands::leftHand.setKeyscanInterval(interval); + RaiseHands::rightHand.setKeyscanInterval(interval); +} + +void RaiseKeyScanner::setup() { + static constexpr uint8_t keyscanner_pins[] = { + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42 + }; + for (int i = 0; i < sizeof(keyscanner_pins); i++) { + pinMode(keyscanner_pins[i], OUTPUT); + digitalWrite(keyscanner_pins[i], LOW); + } +} + +void RaiseKeyScanner::reset() { + leftHandState.all = 0; + rightHandState.all = 0; + kaleidoscope::hid::releaseAllKeys(); + kaleidoscope::hid::sendKeyboardReport(); +} + +/********* Hardware plugin *********/ +void Raise::setup() { + RaiseHands::setup(); + KeyScanner::setup(); + LEDDriver::setup(); + + // initialise Wire of scanner - have to do this here to avoid problem with + // static object intialisation ordering + Wire.begin(); + Wire.setClock(I2C_CLOCK_KHZ * 1000); + + RaiseHands::initializeSides(); +} + +void Raise::side::prepareForFlash() { + Wire.end(); + + setPower(LOW); + // also turn off i2c pins to stop attiny from getting enough current through i2c to stay on + pinMode(SCL, OUTPUT); + pinMode(SDA, OUTPUT); + digitalWrite(SCL, false); + digitalWrite(SDA, false); + + // wipe key states, to prevent accidental key repeats + RaiseKeyScanner::reset(); + + setPower(HIGH); + + Wire.begin(); + Wire.setClock(I2C_FLASH_CLOCK_KHZ * 1000); + // wait for side bootloader to be ready + delay(100); +} + +uint8_t Raise::side::getPower() { + return RaiseHands::getSidePower(); +} +void Raise::side::setPower(uint8_t power) { + RaiseHands::setSidePower(power); +} + +uint8_t Raise::side::leftVersion() { + return RaiseHands::leftHand.readVersion(); +} +uint8_t Raise::side::rightVersion() { + return RaiseHands::rightHand.readVersion(); +} + +uint8_t Raise::side::leftCRCErrors() { + return RaiseHands::leftHand.crc_errors(); +} +uint8_t Raise::side::rightCRCErrors() { + return RaiseHands::rightHand.crc_errors(); +} + +uint8_t Raise::side::leftSLEDVersion() { + return RaiseHands::leftHand.readSLEDVersion(); +} +uint8_t Raise::side::rightSLEDVersion() { + return RaiseHands::rightHand.readSLEDVersion(); +} + +uint8_t Raise::side::leftSLEDCurrent() { + return RaiseHands::leftHand.readSLEDCurrent(); +} +uint8_t Raise::side::rightSLEDCurrent() { + return RaiseHands::rightHand.readSLEDCurrent(); +} +void Raise::side::setSLEDCurrent(uint8_t current) { + RaiseHands::rightHand.setSLEDCurrent(current); + RaiseHands::leftHand.setSLEDCurrent(current); +} + +Raise::settings::Layout Raise::settings::layout() { + return RaiseHands::layout == LAYOUT_ANSI ? Layout::ANSI : Layout::ISO; +} +uint8_t Raise::settings::joint() { + return RaiseHands::rightHand.readJoint(); +} + +uint16_t Raise::settings::keyscanInterval() { + return RaiseHands::keyscanInterval(); +} +void Raise::settings::keyscanInterval(uint16_t interval) { + RaiseHands::keyscanInterval(interval); +} + +} +} +} + +#endif diff --git a/src/kaleidoscope/device/dygma/Raise.h b/src/kaleidoscope/device/dygma/Raise.h new file mode 100644 index 00000000..1edfbced --- /dev/null +++ b/src/kaleidoscope/device/dygma/Raise.h @@ -0,0 +1,272 @@ +/* -*- mode: c++ -*- + * kaleidoscope::device::dygma::Raise -- Kaleidoscope device plugin for Dygma Raise + * Copyright (C) 2017-2019 Keyboard.io, Inc + * Copyright (C) 2017-2019 Dygma Lab S.L. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +#ifdef ARDUINO_SAMD_RAISE +#include + +#include "Kaleidoscope-HIDAdaptor-KeyboardioHID.h" +#include "kaleidoscope/device/dygma/raise/Hand.h" + +#define CRGB(r,g,b) (cRGB){b, g, r} + +#include "kaleidoscope/driver/keyscanner/Base.h" +#include "kaleidoscope/driver/led/Base.h" +#include "kaleidoscope/driver/bootloader/samd/Bossac.h" +#include "kaleidoscope/driver/storage/Flash.h" +#include "kaleidoscope/device/Base.h" +#include "kaleidoscope/util/flasher/KeyboardioI2CBootloader.h" + +namespace kaleidoscope { +namespace device { +namespace dygma { + +// LHK = Left Hand Keys +#define LHK 33 + +using kaleidoscope::driver::led::no_led; + +struct RaiseLEDDriverProps : public kaleidoscope::driver::led::BaseProps { + static constexpr uint8_t led_count = 132; + static constexpr uint8_t key_led_map[] = { + // ISO & ANSI (ANSI has no LED at 20, but this key can never be pressed so we can have just one map). + 0, 1, 2, 3, 4, 5, 6, no_led, no_led, 6 + LHK, 5 + LHK, 4 + LHK, 3 + LHK, 2 + LHK, 1 + LHK, 0 + LHK, + 7, 8, 9, 10, 11, 12, no_led, no_led, 14 + LHK, 13 + LHK, 12 + LHK, 11 + LHK, 10 + LHK, 9 + LHK, 8 + LHK, 7 + LHK, + 13, 14, 15, 16, 17, 18, no_led, no_led, no_led, 21 + LHK, 20 + LHK, 19 + LHK, 18 + LHK, 17 + LHK, 16 + LHK, 15 + LHK, + 19, 20, 21, 22, 23, 24, 25, no_led, no_led, no_led, 27 + LHK, 26 + LHK, 25 + LHK, 24 + LHK, 23 + LHK, 22 + LHK, + 26, 27, 28, 29, 30, no_led, 31, 32, 35 + LHK, 34 + LHK, 33 + LHK, 32 + LHK, 31 + LHK, 30 + LHK, 29 + LHK, 28 + LHK + }; +}; +#undef LHK + +class RaiseLEDDriver : public kaleidoscope::driver::led::Base { + public: + static void setup(); + + static void syncLeds(); + static void setCrgbAt(uint8_t i, cRGB crgb); + static cRGB getCrgbAt(uint8_t i); + + static void updateNeuronLED(); + private: + static bool isLEDChangedNeuron; + static uint8_t isLEDChangedLeft[LED_BANKS]; + static uint8_t isLEDChangedRight[LED_BANKS]; + static cRGB neuronLED; + + static constexpr uint8_t lph = LEDS_PER_HAND; + // led_count + 1, to account for the Neuron's LED. The last one is the + // Neuron's LED, never send that to SLED. + static constexpr uint8_t led_map[][RaiseLEDDriverProps::led_count + 1] = { + // ISO + { + // left side - 33 keys includes LP + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 68, 69, + + // right side - 36 keys includes LP + 0 + LPH, 1 + LPH, 2 + LPH, 3 + LPH, 4 + LPH, 5 + LPH, 6 + LPH, 7 + LPH, 8 + LPH, 9 + LPH, 10 + LPH, 11 + LPH, 12 + LPH, 13 + LPH, 14 + LPH, 15 + LPH, 16 + LPH, 17 + LPH, 18 + LPH, 19 + LPH, + 20 + LPH, 21 + LPH, 22 + LPH, 23 + LPH, 24 + LPH, 25 + LPH, 26 + LPH, 27 + LPH, 28 + LPH, 29 + LPH, 30 + LPH, 31 + LPH, 32 + LPH, 33 + LPH, 68 + LPH, 69 + LPH, + + // left under glow - 30 + 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + + // right underglow - 32 + 34 + LPH, 35 + LPH, 36 + LPH, 37 + LPH, 38 + LPH, 39 + LPH, 40 + LPH, 41 + LPH, 42 + LPH, 43 + LPH, 44 + LPH, 45 + LPH, 46 + LPH, 47 + LPH, 48 + LPH, 49 + LPH, 50 + LPH, 51 + LPH, + 52 + LPH, 53 + LPH, 54 + LPH, 55 + LPH, 56 + LPH, 57 + LPH, 58 + LPH, 59 + LPH, 60 + LPH, 61 + LPH, 62 + LPH, 63 + LPH, 64 + LPH, 65 + LPH, 0xff + }, + // ANSI + { + // left side - 32 keys includes LP: key 19 is missing for ANSI layout + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 0xff, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 68, 69, + + // right side - 36 keys includes LP + 0 + LPH, 1 + LPH, 2 + LPH, 3 + LPH, 4 + LPH, 5 + LPH, 6 + LPH, 15 + LPH, 8 + LPH, 9 + LPH, 10 + LPH, 11 + LPH, 12 + LPH, 13 + LPH, 14 + LPH, 7 + LPH, 16 + LPH, 17 + LPH, 18 + LPH, 19 + LPH, + 20 + LPH, 21 + LPH, 22 + LPH, 23 + LPH, 24 + LPH, 25 + LPH, 26 + LPH, 27 + LPH, 28 + LPH, 29 + LPH, 30 + LPH, 31 + LPH, 32 + LPH, 33 + LPH, 68 + LPH, 69 + LPH, + + // left under glow - 30 + 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + + // right underglow - 32 + 34 + LPH, 35 + LPH, 36 + LPH, 37 + LPH, 38 + LPH, 39 + LPH, 40 + LPH, 41 + LPH, 42 + LPH, 43 + LPH, 44 + LPH, 45 + LPH, 46 + LPH, 47 + LPH, 48 + LPH, 49 + LPH, 50 + LPH, 51 + LPH, + 52 + LPH, 53 + LPH, 54 + LPH, 55 + LPH, 56 + LPH, 57 + LPH, 58 + LPH, 59 + LPH, 60 + LPH, 61 + LPH, 62 + LPH, 63 + LPH, 64 + LPH, 65 + LPH, 0xff + } + }; +}; + +struct RaiseKeyScannerProps : public kaleidoscope::driver::keyscanner::BaseProps { + KEYSCANNER_PROPS(5, 16); + static constexpr uint8_t left_columns = 8; + static constexpr uint8_t right_columns = matrix_columns - left_columns; +}; + +class RaiseKeyScanner : public kaleidoscope::driver::keyscanner::Base { + private: + typedef RaiseKeyScanner ThisType; + typedef RaiseKeyScannerProps Props_; + public: + static void setup(); + static void scanMatrix(); + static void readMatrix(); + static void actOnMatrixScan(); + + static void maskKey(KeyAddr key_addr); + static void unMaskKey(KeyAddr key_addr); + static bool isKeyMasked(KeyAddr key_addr); + static void maskHeldKeys(); + + static bool isKeyswitchPressed(KeyAddr key_addr); + static uint8_t pressedKeyswitchCount(); + + static bool wasKeyswitchPressed(KeyAddr key_addr); + static uint8_t previousPressedKeyswitchCount(); + + static void setKeyscanInterval(uint8_t interval); + + static void reset(); + + protected: + static raise::keydata_t leftHandState; + static raise::keydata_t rightHandState; + static raise::keydata_t previousLeftHandState; + static raise::keydata_t previousRightHandState; + + static raise::keydata_t leftHandMask; + static raise::keydata_t rightHandMask; + + static bool lastLeftOnline; + static bool lastRightOnline; +}; + +struct RaiseStorageProps : public kaleidoscope::driver::storage::FlashProps { + static constexpr uint16_t length = EEPROM_EMULATION_SIZE; +}; + +struct RaiseSideFlasherProps : public kaleidoscope::util::flasher::BaseProps {}; + +struct RaiseProps : kaleidoscope::device::BaseProps { + typedef RaiseLEDDriverProps LEDDriverProps; + typedef RaiseLEDDriver LEDDriver; + typedef RaiseKeyScannerProps KeyScannerProps; + typedef RaiseKeyScanner KeyScanner; + typedef RaiseStorageProps StorageProps; + typedef kaleidoscope::driver::storage::Flash Storage; + typedef kaleidoscope::driver::bootloader::samd::Bossac BootLoader; + + typedef RaiseSideFlasherProps SideFlasherProps; + typedef kaleidoscope::util::flasher::KeyboardioI2CBootloader SideFlasher; +}; + +class Raise: public kaleidoscope::device::Base { + private: + static RaiseProps::SideFlasher SideFlasher; + public: + static void setup(); + + auto serialPort() -> decltype(SerialUSB) & { + return SerialUSB; + } + + auto sideFlasher() -> decltype(SideFlasher) & { + return SideFlasher; + } + + struct side { + uint8_t getPower(); + void setPower(uint8_t power); + + uint8_t leftVersion(); + uint8_t rightVersion(); + + uint8_t leftCRCErrors(); + uint8_t rightCRCErrors(); + + uint8_t leftSLEDVersion(); + uint8_t rightSLEDVersion(); + + uint8_t leftSLEDCurrent(); + uint8_t rightSLEDCurrent(); + void setSLEDCurrent(uint8_t current); + + void prepareForFlash(); + + // Side bootloader addresses + static constexpr uint8_t left_boot_address = 0x50; + static constexpr uint8_t right_boot_address = 0x51; + } side; + + struct settings { + enum class Layout { + ISO, + ANSI + }; + Layout layout(); + uint8_t joint(); + + uint16_t keyscanInterval(); + void keyscanInterval(uint16_t interval); + } settings; +}; + + +} +} + +typedef kaleidoscope::device::dygma::Raise Device; + +} + +#define PER_KEY_DATA(dflt, \ + r0c0, r0c1, r0c2, r0c3, r0c4, r0c5, r0c6, r0c9, r0c10, r0c11, r0c12, r0c13, r0c14, r0c15, \ + r1c0, r1c1, r1c2, r1c3, r1c4, r1c5, r1c8, r1c9, r1c10, r1c11, r1c12, r1c13, r1c14, r1c15, \ + r2c0, r2c1, r2c2, r2c3, r2c4, r2c5, r2c9, r2c10, r2c11, r2c12, r2c13, r2c14, r2c15, \ + r3c0, r3c1, r3c2, r3c3, r3c4, r3c5, r3c6, r3c10, r3c11, r3c12, r3c13, r3c14, r3c15, \ + r4c0, r4c1, r4c2, r4c3, r4c4, r4c10, r4c11, r4c12, r4c13, r4c14, r4c15, \ + r4c6, r4c7, r4c8, r4c9 \ + ) \ + \ + r0c0, r0c1, r0c2, r0c3, r0c4, r0c5, r0c6, dflt, dflt, r0c9, r0c10, r0c11, r0c12, r0c13, r0c14, r0c15, \ + r1c0, r1c1, r1c2, r1c3, r1c4, r1c5, dflt, dflt, r1c8, r1c9, r1c10, r1c11, r1c12, r1c13, r1c14, r1c15, \ + r2c0, r2c1, r2c2, r2c3, r2c4, r2c5, dflt, dflt, dflt, r2c9, r2c10, r2c11, r2c12, r2c13, r2c14, r2c15, \ + r3c0, r3c1, r3c2, r3c3, r3c4, r3c5, r3c6, dflt, dflt, dflt, r3c10, r3c11, r3c12, r3c13, r3c14, r3c15, \ + r4c0, r4c1, r4c2, r4c3, r4c4, dflt, r4c6, r4c7, r4c8, r4c9, r4c10, r4c11, r4c12, r4c13, r4c14, r4c15 + + +#define PER_KEY_DATA_STACKED(dflt, \ + r0c0, r0c1, r0c2, r0c3, r0c4, r0c5, r0c6, \ + r1c0, r1c1, r1c2, r1c3, r1c4, r1c5, \ + r2c0, r2c1, r2c2, r2c3, r2c4, r2c5, \ + r3c0, r3c1, r3c2, r3c3, r3c4, r3c5, r3c6, \ + r4c0, r4c1, r4c2, r4c3, r4c4, \ + r4c6, r4c7, \ + \ + r0c9, r0c10, r0c11, r0c12, r0c13, r0c14, r0c15, \ + r1c8, r1c9, r1c10, r1c11, r1c12, r1c13, r1c14, r1c15, \ + r2c9, r2c10, r2c11, r2c12, r2c13, r2c14, r2c15, \ + r3c10, r3c11, r3c12, r3c13, r3c14, r3c15, \ + r4c10, r4c11, r4c12, r4c13, r4c14, r4c15, \ + r4c8, r4c9 \ + ) \ + \ + r0c0, r0c1, r0c2, r0c3, r0c4, r0c5, r0c6, dflt, dflt, r0c9, r0c10, r0c11, r0c12, r0c13, r0c14, r0c15, \ + r1c0, r1c1, r1c2, r1c3, r1c4, r1c5, dflt, dflt, r1c8, r1c9, r1c10, r1c11, r1c12, r1c13, r1c14, r1c15, \ + r2c0, r2c1, r2c2, r2c3, r2c4, r2c5, dflt, dflt, dflt, r2c9, r2c10, r2c11, r2c12, r2c13, r2c14, r2c15, \ + r3c0, r3c1, r3c2, r3c3, r3c4, r3c5, r3c6, dflt, dflt, dflt, r3c10, r3c11, r3c12, r3c13, r3c14, r3c15, \ + r4c0, r4c1, r4c2, r4c3, r4c4, dflt, r4c6, r4c7, r4c8, r4c9, r4c10, r4c11, r4c12, r4c13, r4c14, r4c15 + +#endif diff --git a/src/kaleidoscope/device/dygma/raise/Focus.cpp b/src/kaleidoscope/device/dygma/raise/Focus.cpp new file mode 100644 index 00000000..137ac9d3 --- /dev/null +++ b/src/kaleidoscope/device/dygma/raise/Focus.cpp @@ -0,0 +1,133 @@ +/* -*- mode: c++ -*- + * kaleidoscope::device::dygma::Raise -- Kaleidoscope device plugin for Dygma Raise + * Copyright (C) 2017-2019 Keyboard.io, Inc + * Copyright (C) 2017-2019 Dygma Lab S.L. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifdef ARDUINO_SAMD_RAISE + +#include +#include +#include "kaleidoscope/device/dygma/raise/Focus.h" + +namespace kaleidoscope { +namespace device { +namespace dygma { +namespace raise { + +#ifndef RAISE_FIRMWARE_VERSION +#define RAISE_FIRMWARE_VERSION "" +#endif + +EventHandlerResult Focus::onFocusEvent(const char *command) { + if (::Focus.handleHelp(command, PSTR("hardware.version\nhardware.side_power\nhardware.side_ver\nhardware.sled_ver\nhardware.sled_current\nhardware.layout\nhardware.joint\nhardware.keyscan\nhardware.crc_errors\nhardware.firmware"))) + return EventHandlerResult::OK; + + if (strncmp_P(command, PSTR("hardware."), 9) != 0) + return EventHandlerResult::OK; + + if (strcmp_P(command + 9, PSTR("version")) == 0) { + ::Focus.send("Dygma Raise"); + return EventHandlerResult::EVENT_CONSUMED; + } + + if (strcmp_P(command + 9, PSTR("firmware")) == 0) { + ::Focus.send(RAISE_FIRMWARE_VERSION); + return EventHandlerResult::EVENT_CONSUMED; + } + + if (strcmp_P(command + 9, PSTR("side_power")) == 0) + if (::Focus.isEOL()) { + ::Focus.send(Kaleidoscope.device().side.getPower()); + return EventHandlerResult::EVENT_CONSUMED; + } else { + uint8_t power; + ::Focus.read(power); + Kaleidoscope.device().side.setPower(power); + return EventHandlerResult::EVENT_CONSUMED; + } + + if (strcmp_P(command + 9, PSTR("side_ver")) == 0) { + ::Focus.send("left:"); + ::Focus.send(Kaleidoscope.device().side.leftVersion()); + ::Focus.send("\nright:"); + ::Focus.send(Kaleidoscope.device().side.rightVersion()); + return EventHandlerResult::EVENT_CONSUMED; + } + + if (strcmp_P(command + 9, PSTR("crc_errors")) == 0) { + ::Focus.send("left:"); + ::Focus.send(Kaleidoscope.device().side.leftCRCErrors()); + ::Focus.send("\nright:"); + ::Focus.send(Kaleidoscope.device().side.rightCRCErrors()); + return EventHandlerResult::EVENT_CONSUMED; + } + + if (strcmp_P(command + 9, PSTR("sled_ver")) == 0) { + ::Focus.send("left:"); + ::Focus.send(Kaleidoscope.device().side.leftSLEDVersion()); + ::Focus.send("\nright:"); + ::Focus.send(Kaleidoscope.device().side.rightSLEDVersion()); + return EventHandlerResult::EVENT_CONSUMED; + } + + if (strcmp_P(command + 9, PSTR("sled_current")) == 0) + if (::Focus.isEOL()) { + ::Focus.send("left:"); + ::Focus.send(Kaleidoscope.device().side.leftSLEDCurrent()); + ::Focus.send("\nright:"); + ::Focus.send(Kaleidoscope.device().side.rightSLEDCurrent()); + return EventHandlerResult::EVENT_CONSUMED; + } else { + uint8_t current; + ::Focus.read(current); + Kaleidoscope.device().side.setSLEDCurrent(current); + return EventHandlerResult::EVENT_CONSUMED; + } + + if (strcmp_P(command + 9, PSTR("layout")) == 0) { + static const auto ANSI = Kaleidoscope.device().settings.Layout::ANSI; + ::Focus.send(Kaleidoscope.device().settings.layout() == ANSI ? "ANSI" : "ISO"); + return EventHandlerResult::EVENT_CONSUMED; + } + + if (strcmp_P(command + 9, PSTR("joint")) == 0) { + ::Focus.send(Kaleidoscope.device().settings.joint()); + return EventHandlerResult::EVENT_CONSUMED; + } + + if (strcmp_P(command + 9, PSTR("keyscan")) == 0) { + if (::Focus.isEOL()) { + ::Focus.send(Kaleidoscope.device().settings.keyscanInterval()); + return EventHandlerResult::EVENT_CONSUMED; + } else { + uint8_t keyscan; + ::Focus.read(keyscan); + Kaleidoscope.device().settings.keyscanInterval(keyscan); + return EventHandlerResult::EVENT_CONSUMED; + } + } + + return EventHandlerResult::OK; +} + +} +} +} +} + +kaleidoscope::device::dygma::raise::Focus RaiseFocus; + +#endif diff --git a/src/kaleidoscope/device/dygma/raise/Focus.h b/src/kaleidoscope/device/dygma/raise/Focus.h new file mode 100644 index 00000000..9601147e --- /dev/null +++ b/src/kaleidoscope/device/dygma/raise/Focus.h @@ -0,0 +1,42 @@ +/* -*- mode: c++ -*- + * kaleidoscope::device::dygma::Raise -- Kaleidoscope device plugin for Dygma Raise + * Copyright (C) 2017-2019 Keyboard.io, Inc + * Copyright (C) 2017-2019 Dygma Lab S.L. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +#ifdef ARDUINO_SAMD_RAISE + +#include + +namespace kaleidoscope { +namespace device { +namespace dygma { +namespace raise { + +class Focus : public kaleidoscope::Plugin { + public: + EventHandlerResult onFocusEvent(const char *command); +}; + +} +} +} +} + +extern kaleidoscope::device::dygma::raise::Focus RaiseFocus; + +#endif diff --git a/src/kaleidoscope/device/dygma/raise/Hand.cpp b/src/kaleidoscope/device/dygma/raise/Hand.cpp new file mode 100644 index 00000000..33d9b387 --- /dev/null +++ b/src/kaleidoscope/device/dygma/raise/Hand.cpp @@ -0,0 +1,254 @@ +/* -*- mode: c++ -*- + * kaleidoscope::device::dygma::Raise -- Kaleidoscope device plugin for Dygma Raise + * Copyright (C) 2017-2019 Keyboard.io, Inc + * Copyright (C) 2017-2019 Dygma Lab S.L. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifdef ARDUINO_SAMD_RAISE + +#include +#include "Hand.h" + +#include "kaleidoscope/driver/color/GammaCorrection.h" + +namespace kaleidoscope { +namespace device { +namespace dygma { +namespace raise { + +#define TWI_CMD_NONE 0x00 +#define TWI_CMD_VERSION 0x01 +#define TWI_CMD_KEYSCAN_INTERVAL 0x02 +#define TWI_CMD_LED_SET_ALL_TO 0x03 +#define TWI_CMD_LED_SET_ONE_TO 0x04 +#define TWI_CMD_COLS_USE_PULLUPS 0x05 +#define TWI_CMD_LED_SPI_FREQUENCY 0x06 +#define TWI_CMD_LED_GLOBAL_BRIGHTNESS 0x07 + +#define TWI_CMD_SLED_STATUS 0x08 +#define TWI_CMD_LED_OPEN 0x09 +#define TWI_CMD_LED_SHORT 0x0A +#define TWI_CMD_JOINED 0x0B +#define TWI_CMD_LAYOUT 0x0C +#define TWI_CMD_SLED_CURRENT 0x0D +#define TWI_CMD_SLED_SELF_TEST 0x0E + +#define LED_SPI_FREQUENCY_4MHZ 0x07 +#define LED_SPI_FREQUENCY_2MHZ 0x06 +#define LED_SPI_FREQUENCY_1MHZ 0x05 +#define LED_SPI_FREQUENCY_512KHZ 0x04 +#define LED_SPI_FREQUENCY_256KHZ 0x03 +#define LED_SPI_FREQUENCY_128KHZ 0x02 +#define LED_SPI_FREQUENCY_64KHZ 0x01 +#define LED_SPI_OFF 0x00 + +// 512KHZ seems to be the sweet spot in early testing +// so make it the default +#define LED_SPI_FREQUENCY_DEFAULT LED_SPI_FREQUENCY_512KHZ + +#define TWI_CMD_LED_BASE 0x80 + +#define TWI_REPLY_NONE 0x00 +#define TWI_REPLY_KEYDATA 0x01 + +#define ELEMENTS(arr) (sizeof(arr) / sizeof((arr)[0])) + +// Returns the relative controller addresss. The expected range is 0-3 +uint8_t Hand::controllerAddress() { + return ad01_; +} + +// Sets the keyscan interval. We currently do three reads. +// before declaring a key event debounced. +// +// Takes an integer value representing a counter. +// +// 0 - 0.1-0.25ms +// 1 - 0.125ms +// 10 - 0.35ms +// 25 - 0.8ms +// 50 - 1.6ms +// 100 - 3.15ms +// +// You should think of this as the _minimum_ keyscan interval. +// LED updates can cause a bit of jitter. +// +// returns the Wire.endTransmission code (0 = success) +// https://www.arduino.cc/en/Reference/WireEndTransmission +byte Hand::setKeyscanInterval(byte delay) { + uint8_t data[] = {TWI_CMD_KEYSCAN_INTERVAL, delay}; + return twi_.writeTo(data, ELEMENTS(data)); +} + +// returns -1 on error, otherwise returns the scanner version integer +int Hand::readVersion() { + return readRegister(TWI_CMD_VERSION); +} + +// returns -1 on error, otherwise returns the sled version integer +int Hand::readSLEDVersion() { + return readRegister(TWI_CMD_SLED_STATUS); +} +// returns -1 on error, otherwise returns the sled current settings +int Hand::readSLEDCurrent() { + return readRegister(TWI_CMD_SLED_CURRENT); +} + +byte Hand::setSLEDCurrent(byte current) { + uint8_t data[] = {TWI_CMD_SLED_CURRENT, current}; + return twi_.writeTo(data, ELEMENTS(data)); +} + +// returns -1 on error, otherwise returns the scanner keyscan interval +int Hand::readKeyscanInterval() { + return readRegister(TWI_CMD_KEYSCAN_INTERVAL); +} + +// returns -1 on error, otherwise returns the layout (ANSI/ISO) setting +int Hand::readLayout() { + return readRegister(TWI_CMD_LAYOUT); +} + +// returns -1 on error, otherwise returns the LED SPI Frequncy +int Hand::readLEDSPIFrequency() { + return readRegister(TWI_CMD_LED_SPI_FREQUENCY); +} + +// Set the LED SPI Frequency. See wire-protocol-constants.h for +// values. +// +// returns the Wire.endTransmission code (0 = success) +// https://www.arduino.cc/en/Reference/WireEndTransmission +byte Hand::setLEDSPIFrequency(byte frequency) { + uint8_t data[] = {TWI_CMD_LED_SPI_FREQUENCY, frequency}; + return twi_.writeTo(data, ELEMENTS(data)); +} + +// returns -1 on error, otherwise returns the value of the hall sensor integer +int Hand::readJoint() { + byte return_value = 0; + + uint8_t data[] = {TWI_CMD_JOINED}; + uint8_t result = twi_.writeTo(data, ELEMENTS(data)); + if (result != 0) + return -1; + + // needs to be long enough for the slave to respond + delayMicroseconds(40); + + uint8_t rxBuffer[2]; + + // perform blocking read into buffer + uint8_t read = twi_.readFrom(rxBuffer, ELEMENTS(rxBuffer)); + if (read == 2) { + return rxBuffer[0] + (rxBuffer[1] << 8); + } else { + return -1; + } +} + +int Hand::readRegister(uint8_t cmd) { + byte return_value = 0; + + uint8_t data[] = {cmd}; + uint8_t result = twi_.writeTo(data, ELEMENTS(data)); + if (result != 0) + return -1; + + // needs to be long enough for the slave to respond + delayMicroseconds(40); + + uint8_t rxBuffer[1]; + + // perform blocking read into buffer + uint8_t read = twi_.readFrom(rxBuffer, ELEMENTS(rxBuffer)); + if (read > 0) { + return rxBuffer[0]; + } else { + return -1; + } +} + +// gives information on the key that was just pressed or released. +bool Hand::readKeys() { + uint8_t rxBuffer[6] = {0, 0, 0, 0, 0, 0}; + + // perform blocking read into buffer + uint8_t result = twi_.readFrom(rxBuffer, ELEMENTS(rxBuffer)); + // if result isn't 6? this can happens if slave nacks while trying to read + Hand::online = (result == 6) ? true : false; + + if (result != 6) + // could also try reset pressed keys here + return false; + + if (rxBuffer[0] == TWI_REPLY_KEYDATA) { + key_data_.rows[0] = rxBuffer[1]; + key_data_.rows[1] = rxBuffer[2]; + key_data_.rows[2] = rxBuffer[3]; + key_data_.rows[3] = rxBuffer[4]; + key_data_.rows[4] = rxBuffer[5]; + return true; + } else { + return false; + } +} + +keydata_t Hand::getKeyData() { + return key_data_; +} + +void Hand::sendLEDData() { + sendLEDBank(next_led_bank_++); + if (next_led_bank_ == LED_BANKS) { + next_led_bank_ = 0; + } +} + +auto constexpr gamma8 = kaleidoscope::driver::color::gamma_correction; + +void Hand::sendLEDBank(uint8_t bank) { + uint8_t data[LED_BYTES_PER_BANK + 1]; // + 1 for the update LED command itself + data[0] = TWI_CMD_LED_BASE + bank; + for (uint8_t i = 0 ; i < LED_BYTES_PER_BANK; i++) { + data[i + 1] = pgm_read_byte(&gamma8[led_data.bytes[bank][i]]); + } + uint8_t result = twi_.writeTo(data, ELEMENTS(data)); +} + +void Hand::setAllLEDsTo(cRGB color) { + uint8_t data[] = {TWI_CMD_LED_SET_ALL_TO, + pgm_read_byte(&gamma8[color.r]), + pgm_read_byte(&gamma8[color.g]), + pgm_read_byte(&gamma8[color.b]) + }; + twi_.writeTo(data, ELEMENTS(data)); +} + +void Hand::setOneLEDTo(byte led, cRGB color) { + uint8_t data[] = {TWI_CMD_LED_SET_ONE_TO, + led, + pgm_read_byte(&gamma8[color.r]), + pgm_read_byte(&gamma8[color.g]), + pgm_read_byte(&gamma8[color.b]) + }; + twi_.writeTo(data, ELEMENTS(data)); +} + +} +} +} +} +#endif diff --git a/src/kaleidoscope/device/dygma/raise/Hand.h b/src/kaleidoscope/device/dygma/raise/Hand.h new file mode 100644 index 00000000..d82f23c9 --- /dev/null +++ b/src/kaleidoscope/device/dygma/raise/Hand.h @@ -0,0 +1,105 @@ +/* -*- mode: c++ -*- + * kaleidoscope::device::dygma::Raise -- Kaleidoscope device plugin for Dygma Raise + * Copyright (C) 2017-2019 Keyboard.io, Inc + * Copyright (C) 2017-2019 Dygma Lab S.L. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +#ifdef ARDUINO_SAMD_RAISE + +#include +#include "TWI.h" + +struct cRGB { + uint8_t r; + uint8_t g; + uint8_t b; +}; + +namespace kaleidoscope { +namespace device { +namespace dygma { +namespace raise { + +#define LED_BANKS 9 + +#define LEDS_PER_HAND 72 +#define LPH LEDS_PER_HAND +#define LEDS_PER_BANK 8 +#define LED_BYTES_PER_BANK (sizeof(cRGB) * LEDS_PER_BANK) + +typedef union { + cRGB leds[LEDS_PER_HAND]; + byte bytes[LED_BANKS][LED_BYTES_PER_BANK]; +} LEDData_t; + +// return what bank the led is in +#define LED_TO_BANK(led) (led / LEDS_PER_BANK) + +typedef union { + uint8_t rows[5]; + uint64_t all; +} keydata_t; + +class Hand { + public: + Hand(byte ad01) : ad01_(ad01), twi_(i2c_addr_base_ | ad01) {} + + int readVersion(); + int readSLEDVersion(); + int readSLEDCurrent(); + byte setSLEDCurrent(byte current); + int readJoint(); + int readLayout(); + + byte setKeyscanInterval(byte delay); + int readKeyscanInterval(); + + byte setLEDSPIFrequency(byte frequency); + int readLEDSPIFrequency(); + + bool moreKeysWaiting(); + void sendLEDData(); + void sendLEDBank(uint8_t bank); + void setOneLEDTo(byte led, cRGB color); + void setAllLEDsTo(cRGB color); + keydata_t getKeyData(); + bool readKeys(); + uint8_t controllerAddress(); + uint8_t crc_errors() { + return twi_.crc_errors(); + } + + LEDData_t led_data; + bool online = false; + + private: + int ad01_; + TWI twi_; + keydata_t key_data_; + uint8_t next_led_bank_ = 0; + + static constexpr uint8_t i2c_addr_base_ = 0x58; + + int readRegister(uint8_t cmd); +}; + +} +} +} +} + +#endif diff --git a/src/kaleidoscope/device/dygma/raise/SideFlash.h b/src/kaleidoscope/device/dygma/raise/SideFlash.h new file mode 100644 index 00000000..f5b74b03 --- /dev/null +++ b/src/kaleidoscope/device/dygma/raise/SideFlash.h @@ -0,0 +1,84 @@ +/* -*- mode: c++ -*- + * kaleidoscope::device::dygma::Raise -- Kaleidoscope device plugin for Dygma Raise + * Copyright (C) 2017-2019 Keyboard.io, Inc + * Copyright (C) 2017-2019 Dygma Lab S.L. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +#ifdef ARDUINO_SAMD_RAISE + +#include + +namespace kaleidoscope { +namespace device { +namespace dygma { +namespace raise { + +template +class SideFlash : public kaleidoscope::Plugin { + private: + _Firmware firmware; + public: + EventHandlerResult onFocusEvent(const char *command) { + if (::Focus.handleHelp(command, PSTR("hardware.flash_left_side\nhardware.flash_right_side\nhardware.verify_left_side\nhardware.verify_right_side"))) + return EventHandlerResult::OK; + + if (strncmp_P(command, PSTR("hardware."), 9) != 0) + return EventHandlerResult::OK; + + auto sideFlasher = Kaleidoscope.device().sideFlasher(); + uint8_t left_boot_address = Kaleidoscope.device().side.left_boot_address; + uint8_t right_boot_address = Kaleidoscope.device().side.right_boot_address; + enum { + FLASH, + VERIFY + } sub_command; + uint8_t address = 0; + + if (strcmp_P(command + 9, PSTR("flash_left_side")) == 0) { + sub_command = FLASH; + address = left_boot_address; + } else if (strcmp_P(command + 9, PSTR("flash_right_side")) == 0) { + sub_command = FLASH; + address = right_boot_address; + } else if (strcmp_P(command + 9, PSTR("verify_left_side")) == 0) { + sub_command = VERIFY; + address = left_boot_address; + } else if (strcmp_P(command + 9, PSTR("verify_right_side")) == 0) { + sub_command = VERIFY; + address = right_boot_address; + } else { + return EventHandlerResult::OK; + } + + bool result; + Kaleidoscope.device().side.prepareForFlash(); + if (sub_command == FLASH) + result = sideFlasher.flash(address, firmware); + else + result = sideFlasher.verify(address, firmware); + ::Focus.send(result); + + return EventHandlerResult::EVENT_CONSUMED; + } +}; + +} +} +} +} + +#endif diff --git a/src/kaleidoscope/device/dygma/raise/TWI.cpp b/src/kaleidoscope/device/dygma/raise/TWI.cpp new file mode 100644 index 00000000..15fae04c --- /dev/null +++ b/src/kaleidoscope/device/dygma/raise/TWI.cpp @@ -0,0 +1,99 @@ +/* -*- mode: c++ -*- + * kaleidoscope::device::dygma::Raise -- Kaleidoscope device plugin for Dygma Raise + * Copyright (C) 2017-2019 Keyboard.io, Inc + * Copyright (C) 2017-2019 Dygma Lab S.L. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifdef ARDUINO_SAMD_RAISE + +#include + +#include "TWI.h" +#include "kaleidoscope/util/crc16.h" + +namespace kaleidoscope { +namespace device { +namespace dygma { +namespace raise { + +uint8_t TWI::writeTo(uint8_t *data, size_t length) { + Wire.beginTransmission(addr_); + + // calc cksum + uint16_t crc16 = 0xffff; + uint8_t *buffer = data; + for (uint8_t i = 0; i < length; i++) { + crc16 = _crc_ccitt_update(crc16, *buffer); + buffer++; + } + + // make cksum high byte and low byte + uint8_t crc_bytes[2]; + crc_bytes[0] = crc16 >> 8; + crc_bytes[1] = crc16; + + if (!Wire.write(data, length)) return 1; + if (!Wire.write(crc_bytes, 2)) return 1; + if (Wire.endTransmission(true) != 0) return 1; + return 0; +} + +uint8_t TWI::readFrom(uint8_t* data, size_t length) { + uint8_t counter = 0; + uint32_t timeout; + uint8_t *buffer = data; + + if (!Wire.requestFrom(addr_, length + 2, true)) { // + 2 for the cksum + // in case slave is not responding - return 0 (0 length of received data). + return 0; + } + while (counter < length) { + *data = Wire.read(); + data++; + counter++; + } + + uint16_t crc16 = 0xffff; + uint16_t rx_cksum = (Wire.read() << 8) + Wire.read(); + for (uint8_t i = 0; i < length; i++) { + crc16 = _crc_ccitt_update(crc16, *buffer); + buffer++; + } + + // check received CRC16 + if (crc16 != rx_cksum) { + crc_errors_++; + return 0; + } + + return length; +} + +void TWI::disable() { + Wire.end(); +} + +void TWI::init(uint16_t clock_khz) { + Wire.begin(); + Wire.setClock(clock_khz * 1000); +} + + +} +} +} +} + +#endif diff --git a/src/kaleidoscope/device/dygma/raise/TWI.h b/src/kaleidoscope/device/dygma/raise/TWI.h new file mode 100644 index 00000000..5d96abb2 --- /dev/null +++ b/src/kaleidoscope/device/dygma/raise/TWI.h @@ -0,0 +1,49 @@ +/* -*- mode: c++ -*- + * kaleidoscope::device::dygma::Raise -- Kaleidoscope device plugin for Dygma Raise + * Copyright (C) 2017-2019 Keyboard.io, Inc + * Copyright (C) 2017-2019 Dygma Lab S.L. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifdef ARDUINO_SAMD_RAISE + +#include + +namespace kaleidoscope { +namespace device { +namespace dygma { +namespace raise { + +class TWI { + public: + TWI(int addr) : addr_(addr), crc_errors_(0) {}; + + uint8_t writeTo(uint8_t *data, size_t length); + uint8_t readFrom(uint8_t* data, size_t length); + void disable(); + void init(uint16_t clock_khz); + uint8_t crc_errors() { + return crc_errors_; + } + + private: + int addr_; + uint8_t crc_errors_; +}; + +} +} +} +} +#endif diff --git a/src/kaleidoscope/driver/color/GammaCorrection.h b/src/kaleidoscope/driver/color/GammaCorrection.h new file mode 100644 index 00000000..f11137e3 --- /dev/null +++ b/src/kaleidoscope/driver/color/GammaCorrection.h @@ -0,0 +1,47 @@ +/* -*- mode: c++ -*- + * kaleidoscope::driver::led::Gamma -- Gamma correction table + * Copyright (C) 2017-2019 Keyboard.io, Inc + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +#include + +namespace kaleidoscope { +namespace driver { +namespace color { + +const uint8_t PROGMEM gamma_correction[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, + 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, + 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, + 10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, + 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25, + 25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, + 37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50, + 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68, + 69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89, + 90, 92, 93, 95, 96, 98, 99, 101, 102, 104, 105, 107, 109, 110, 112, 114, + 115, 117, 119, 120, 122, 124, 126, 127, 129, 131, 133, 135, 137, 138, 140, 142, + 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 167, 169, 171, 173, 175, + 177, 180, 182, 184, 186, 189, 191, 193, 196, 198, 200, 203, 205, 208, 210, 213, + 215, 218, 220, 223, 225, 228, 231, 233, 236, 239, 241, 244, 247, 249, 252, 255 +}; + +} +} +} diff --git a/src/kaleidoscope/util/crc16.h b/src/kaleidoscope/util/crc16.h new file mode 100644 index 00000000..92cdb77a --- /dev/null +++ b/src/kaleidoscope/util/crc16.h @@ -0,0 +1,94 @@ +// CRC compatibility, adapted from the C-only comments here: +// http://svn.savannah.nongnu.org/viewvc/trunk/avr-libc/include/util/crc16.h?revision=933&root=avr-libc&view=markup + +/* Copyright (c) 2002, 2003, 2004 Marek Michalkiewicz + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of the copyright holders nor the names of + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. */ + +#ifndef _UTIL_CRC16_H_ +#define _UTIL_CRC16_H_ + +#include + +static inline uint16_t _crc16_update(uint16_t crc, uint8_t data) __attribute__((always_inline, unused)); +static inline uint16_t _crc16_update(uint16_t crc, uint8_t data) { + unsigned int i; + + crc ^= data; + for (i = 0; i < 8; ++i) { + if (crc & 1) { + crc = (crc >> 1) ^ 0xA001; + } else { + crc = (crc >> 1); + } + } + return crc; +} + +static inline uint16_t _crc_xmodem_update(uint16_t crc, uint8_t data) __attribute__((always_inline, unused)); +static inline uint16_t _crc_xmodem_update(uint16_t crc, uint8_t data) { + unsigned int i; + + crc = crc ^ ((uint16_t)data << 8); + for (i = 0; i < 8; i++) { + if (crc & 0x8000) { + crc = (crc << 1) ^ 0x1021; + } else { + crc <<= 1; + } + } + return crc; +} + +static inline uint16_t _crc_ccitt_update(uint16_t crc, uint8_t data) __attribute__((always_inline, unused)); +static inline uint16_t _crc_ccitt_update(uint16_t crc, uint8_t data) { + data ^= (crc & 255); + data ^= data << 4; + + return ((((uint16_t)data << 8) | (crc >> 8)) ^ (uint8_t)(data >> 4) + ^ ((uint16_t)data << 3)); +} + +static inline uint8_t _crc_ibutton_update(uint8_t crc, uint8_t data) __attribute__((always_inline, unused)); +static inline uint8_t _crc_ibutton_update(uint8_t crc, uint8_t data) { + unsigned int i; + + crc = crc ^ data; + for (i = 0; i < 8; i++) { + if (crc & 0x01) { + crc = (crc >> 1) ^ 0x8C; + } else { + crc >>= 1; + } + } + return crc; +} + +#endif + diff --git a/src/kaleidoscope/util/flasher/Base.h b/src/kaleidoscope/util/flasher/Base.h new file mode 100644 index 00000000..753c0ef6 --- /dev/null +++ b/src/kaleidoscope/util/flasher/Base.h @@ -0,0 +1,60 @@ +/* -*- mode: c++ -*- + * kaleidoscope::util::flasher::Base -- Base flasher utility class + * Copyright (C) 2019 Keyboard.io, Inc + * Copyright (C) 2019 Dygma Lab S.L. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +namespace kaleidoscope { +namespace util { +namespace flasher { + +struct BaseProps { + static constexpr uint8_t page_size = 64; + static constexpr uint8_t frame_size = 16; + static constexpr uint8_t blank = 0xff; + static constexpr uint8_t delay = 1; + + static struct { + static constexpr uint8_t page_address = 0x01; + static constexpr uint8_t continue_page = 0x02; + static constexpr uint8_t execute = 0x03; + static constexpr uint8_t erase_program = 0x04; + static constexpr uint8_t get_version_and_crc = 0x06; + } command; +}; + +template +class Base { + public: + Base() {} + + template + static uint8_t flash(uint8_t address, T& firmware) { + return 0; + } + template + static uint8_t verify(uint8_t address, T& firmware) { + return 0; + } + static uint8_t command(uint8_t address, uint8_t command) { + return 0; + } +}; + +} +} +} diff --git a/src/kaleidoscope/util/flasher/KeyboardioI2CBootloader.h b/src/kaleidoscope/util/flasher/KeyboardioI2CBootloader.h new file mode 100644 index 00000000..88f789bc --- /dev/null +++ b/src/kaleidoscope/util/flasher/KeyboardioI2CBootloader.h @@ -0,0 +1,226 @@ +/* -*- mode: c++ -*- + * kaleidoscope::util::flasher::KeyboardioI2CBootloader -- Flasher for Keyboardio's I2C Bootloader + * Copyright (C) 2019 Keyboard.io, Inc + * Copyright (C) 2019 Dygma Lab S.L. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +// TODO(@algernon): We should support AVR here, too. +#ifdef __SAMD21G18A__ + +#include + +#include "kaleidoscope/util/flasher/Base.h" +#include "kaleidoscope/util/crc16.h" + +namespace kaleidoscope { +namespace util { +namespace flasher { + +template +class KeyboardioI2CBootloader: kaleidoscope::util::flasher::Base<_Props> { + public: + template + static bool flash(uint8_t address, T& firmware) { + if (!verify(address, firmware)) { + return false; + } + + if (!erase_program(address)) { + return false; + } + if (!write_firmware(address, firmware)) { + return false; + } + if (!verify_firmware(address, firmware)) { + return false; + } + + return command(address, _Props::command.execute) == 0 ? true : false; + } + + template + static bool verify(uint8_t address, T& firmware) { + CRCAndVersion crc_and_version = get_version(address, firmware); + return (crc_and_version.version != 0xff) && (crc_and_version.crc != 0xffff); + } + + static uint8_t command(uint8_t address, uint8_t command) { + Wire.beginTransmission(address); + Wire.write(command); + Wire.write(0x00); + uint8_t result = Wire.endTransmission(); + return result; + } + + private: + struct CRCAndVersion { + uint8_t version; + uint16_t crc; + }; + + static uint8_t read_crc16(uint8_t addr, + CRCAndVersion *crc_and_version, + uint16_t offset, uint16_t length) { + uint8_t result; + + Wire.beginTransmission(addr); + Wire.write(_Props::command.get_version_and_crc); + Wire.write(offset & 0xff); // addr (lo) + Wire.write(offset >> 8); // addr (hi) + Wire.write(length & 0xff); // len (lo) + Wire.write(length >> 8); // len (hi) + result = Wire.endTransmission(false); + if (result != 0) { + return result; + } + + // wait for cksum to be calculated - takes about 20ms + delay(100); + + Wire.requestFrom(addr, 3); + uint8_t v = Wire.read(); + crc_and_version->version = v; + if (Wire.available() == 0) { + return 0xFF; + } + uint8_t crc16_lo = Wire.read(); + if (Wire.available() == 0) { + return 0xFF; + } + uint8_t crc16_hi = Wire.read(); + while (Wire.available()) { + uint8_t c = Wire.read(); + } + crc_and_version->crc = (crc16_hi << 8) | crc16_lo; + return result; + } + + template + static CRCAndVersion get_version(uint8_t addr, T& firmware) { + static CRCAndVersion crc_and_version = {0xff, 0xff}; + + // This here to resolve some weird I2C startup bug. + // Usually in the RHS, get_version fails with the I2C master writing the + // address and the CRC request (0x06), the CRC parameters are never written + // doing a read first seems to let things settle in a way that allows the + // right to respond correctly + Wire.requestFrom(addr, (uint8_t) 3); + while (Wire.available()) { + // throw away the info, as cksum calculation request has yet to be issued. + Wire.read(); + } + + int result = read_crc16(addr, &crc_and_version, + firmware.offsets[0] + 4, + firmware.length - 4); + return crc_and_version; + } + + static bool erase_program(uint8_t addr) { + Wire.beginTransmission(addr); + Wire.write(_Props::command.erase_program); + uint8_t result = Wire.endTransmission(); + + // wait for erase + delay(1000); + + return result != 0; + } + + template + static bool write_firmware(uint8_t addr, T& firmware) { + uint8_t result; + uint8_t o = 0; + + for (uint16_t i = 0; i < firmware.length; i += _Props::page_size) { + Wire.beginTransmission(addr); + Wire.write(_Props::command.page_address); + Wire.write(firmware.offsets[o] & 0xff); + Wire.write(firmware.offsets[o] >> 8); + result = Wire.endTransmission(); + delay(_Props::delay); + + // got something other than ACK. Start over. + if (result != 0) { + return false; + } + + // transmit each frame separately + for (uint8_t frame = 0; frame < _Props::page_size / _Props::frame_size; frame++) { + Wire.beginTransmission(addr); + Wire.write(_Props::command.continue_page); + uint16_t crc16 = 0xffff; + for (uint8_t j = frame * _Props::frame_size; + j < (frame + 1) * _Props::frame_size; + j++) { + if (i + j < firmware.length) { + uint8_t b = pgm_read_byte(&firmware.data[i + j]); + Wire.write(b); + crc16 = _crc16_update(crc16, b); + } else { + Wire.write(_Props::blank); + crc16 = _crc16_update(crc16, _Props::blank); + } + } + // write the CRC16, little end first + Wire.write(crc16 & 0xff); + Wire.write(crc16 >> 8); + Wire.write(0x00); // dummy end uint8_t + result = Wire.endTransmission(); + // got something other than NACK. Start over. + if (result != 3) { + return false; + } + delay(_Props::delay); + } + o++; + } + return true; + } + + template + static bool verify_firmware(uint8_t addr, T& firmware) { + uint8_t result = 3; + CRCAndVersion crc_and_version; + + while (result != 0) { + // skip the first 4 uint8_ts, are they were probably overwritten by the + // reset vector preservation + result = read_crc16(addr, &crc_and_version, + firmware.offsets[0] + 4, firmware.length - 4); + if (result != 0) { + delay(100); + continue; + } + } + + // calculate our own CRC16 + uint16_t check_crc16 = 0xffff; + for (uint16_t i = 4; i < firmware.length; i++) { + check_crc16 = _crc16_update(check_crc16, pgm_read_byte(&(firmware.data[i]))); + } + + return crc_and_version.crc == check_crc16; + } + +}; + +} +} +} + +#endif