New device: Dygma Raise

This implements a new device plugin, to drive the Dygma Raise. A few helpers are
also introduces, which are used by the Raise only for now, but are generic
enough so that eventually, other boards may use them too.

Signed-off-by: Gergely Nagy <algernon@keyboard.io>
pull/722/head
Gergely Nagy 5 years ago
parent 7ab65b94d5
commit 2c5f4a78a2
No known key found for this signature in database
GPG Key ID: AC1E90BAC433F68F

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "kaleidoscope/device/dygma/Raise.h"

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifdef ARDUINO_SAMD_RAISE
#include <Kaleidoscope.h>
#include <Kaleidoscope-EEPROM-Settings.h>
#include <Kaleidoscope-LEDControl.h>
#include <KeyboardioHID.h>
#include <Wire.h>
#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

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#ifdef ARDUINO_SAMD_RAISE
#include <Arduino.h>
#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<RaiseLEDDriverProps> {
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<RaiseKeyScannerProps> {
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<StorageProps> Storage;
typedef kaleidoscope::driver::bootloader::samd::Bossac BootLoader;
typedef RaiseSideFlasherProps SideFlasherProps;
typedef kaleidoscope::util::flasher::KeyboardioI2CBootloader<SideFlasherProps> SideFlasher;
};
class Raise: public kaleidoscope::device::Base<RaiseProps> {
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

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifdef ARDUINO_SAMD_RAISE
#include <Kaleidoscope.h>
#include <Kaleidoscope-FocusSerial.h>
#include "kaleidoscope/device/dygma/raise/Focus.h"
namespace kaleidoscope {
namespace device {
namespace dygma {
namespace raise {
#ifndef RAISE_FIRMWARE_VERSION
#define RAISE_FIRMWARE_VERSION "<unknown>"
#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

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#ifdef ARDUINO_SAMD_RAISE
#include <Kaleidoscope.h>
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

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifdef ARDUINO_SAMD_RAISE
#include <Arduino.h>
#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

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#ifdef ARDUINO_SAMD_RAISE
#include <Arduino.h>
#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

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#ifdef ARDUINO_SAMD_RAISE
#include <Kaleidoscope.h>
namespace kaleidoscope {
namespace device {
namespace dygma {
namespace raise {
template <typename _Firmware>
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

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifdef ARDUINO_SAMD_RAISE
#include <Wire.h>
#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

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifdef ARDUINO_SAMD_RAISE
#include <Arduino.h>
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

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <Arduino.h>
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
};
}
}
}

@ -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 <stdint.h>
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

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <typename _Props>
class Base {
public:
Base() {}
template <typename T>
static uint8_t flash(uint8_t address, T& firmware) {
return 0;
}
template <typename T>
static uint8_t verify(uint8_t address, T& firmware) {
return 0;
}
static uint8_t command(uint8_t address, uint8_t command) {
return 0;
}
};
}
}
}

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
// TODO(@algernon): We should support AVR here, too.
#ifdef __SAMD21G18A__
#include <Wire.h>
#include "kaleidoscope/util/flasher/Base.h"
#include "kaleidoscope/util/crc16.h"
namespace kaleidoscope {
namespace util {
namespace flasher {
template <typename _Props>
class KeyboardioI2CBootloader: kaleidoscope::util::flasher::Base<_Props> {
public:
template <typename T>
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 <typename T>
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 <typename T>
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 <typename T>
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 <typename T>
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
Loading…
Cancel
Save