You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
483 lines
15 KiB
483 lines
15 KiB
5 years ago
|
/* -*- mode: c++ -*-
|
||
|
* kaleidoscope::device::dygma::Raise -- Kaleidoscope device plugin for Dygma Raise
|
||
5 years ago
|
* Copyright (C) 2017-2020 Keyboard.io, Inc
|
||
|
* Copyright (C) 2017-2020 Dygma Lab S.L.
|
||
5 years ago
|
*
|
||
|
* 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
|
||
|
|
||
5 years ago
|
#include "kaleidoscope/Runtime.h"
|
||
5 years ago
|
#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"
|
||
5 years ago
|
#include "kaleidoscope/driver/keyscanner/Base_Impl.h"
|
||
5 years ago
|
|
||
5 years ago
|
#define I2C_CLOCK_KHZ 100
|
||
5 years ago
|
#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 {
|
||
4 years ago
|
static raise::RaiseSide leftHand;
|
||
|
static raise::RaiseSide rightHand;
|
||
5 years ago
|
|
||
|
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;
|
||
|
};
|
||
|
|
||
4 years ago
|
raise::RaiseSide RaiseHands::leftHand(0);
|
||
|
raise::RaiseSide RaiseHands::rightHand(1);
|
||
5 years ago
|
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;
|
||
5 years ago
|
Runtime.storage().get(settings_base_, interval);
|
||
5 years ago
|
if (interval == 0xffff) {
|
||
5 years ago
|
Runtime.storage().put(settings_base_, keyscan_interval_);
|
||
|
Runtime.storage().commit();
|
||
5 years ago
|
}
|
||
5 years ago
|
Runtime.storage().get(settings_base_, keyscan_interval_);
|
||
5 years ago
|
}
|
||
|
|
||
|
void RaiseHands::keyscanInterval(uint16_t interval) {
|
||
|
leftHand.setKeyscanInterval(interval);
|
||
|
rightHand.setKeyscanInterval(interval);
|
||
|
keyscan_interval_ = interval;
|
||
5 years ago
|
Runtime.storage().put(settings_base_, keyscan_interval_);
|
||
|
Runtime.storage().commit();
|
||
5 years ago
|
}
|
||
|
|
||
|
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[];
|
||
|
|
||
5 years ago
|
void RaiseLEDDriver::setBrightness(uint8_t brightness) {
|
||
|
RaiseHands::leftHand.setBrightness(brightness);
|
||
|
RaiseHands::rightHand.setBrightness(brightness);
|
||
|
for (uint8_t i = 0; i < LED_BANKS; i++) {
|
||
|
isLEDChangedLeft[i] = true;
|
||
|
isLEDChangedRight[i] = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
uint8_t RaiseLEDDriver::getBrightness() {
|
||
|
return RaiseHands::leftHand.getBrightness();
|
||
|
}
|
||
|
|
||
5 years ago
|
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 &&
|
||
4 years ago
|
oldColor.g == crgb.g &&
|
||
|
oldColor.b == crgb.b);
|
||
5 years ago
|
} 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;
|
||
|
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
|
||
4 years ago
|
if (RaiseHands::layout == LAYOUT_ANSI) {
|
||
5 years ago
|
// 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
|
||
|
}
|
||
4 years ago
|
}
|
||
5 years ago
|
}
|
||
|
|
||
|
if (RaiseHands::rightHand.readKeys()) {
|
||
|
rightHandState = RaiseHands::rightHand.getKeyData();
|
||
|
// if ANSI, then swap r1c0 and r2c0 to match the PCB
|
||
4 years ago
|
if (RaiseHands::layout == LAYOUT_ANSI) {
|
||
5 years ago
|
if ((rightHandState.rows[1] & (1 << 0)) ^ rightHandState.rows[2] & (1 << 0)) {
|
||
|
rightHandState.rows[1] ^= (1 << 0);
|
||
|
rightHandState.rows[2] ^= (1 << 0);
|
||
|
}
|
||
4 years ago
|
}
|
||
5 years ago
|
}
|
||
|
|
||
|
// 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++) {
|
||
5 years ago
|
for (byte col = 0; col < Props_::left_columns; col++) {
|
||
|
uint8_t keynum = (row * Props_::left_columns) + col;
|
||
5 years ago
|
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();
|
||
|
}
|
||
|
|
||
|
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() {
|
||
5 years ago
|
return __builtin_popcountll(leftHandState.all) + __builtin_popcountll(rightHandState.all);
|
||
5 years ago
|
}
|
||
|
|
||
|
uint8_t RaiseKeyScanner::previousPressedKeyswitchCount() {
|
||
5 years ago
|
return __builtin_popcountll(previousLeftHandState.all) + __builtin_popcountll(previousRightHandState.all);
|
||
5 years ago
|
}
|
||
|
|
||
|
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;
|
||
5 years ago
|
Runtime.hid().keyboard().releaseAllKeys();
|
||
|
Runtime.hid().keyboard().sendReport();
|
||
5 years ago
|
}
|
||
|
|
||
|
/********* 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
|