parent
8118e696cf
commit
c6e4d17ffb
|
@ -0,0 +1,2 @@
|
|||
filter=-build/include_subdir,-whitespace/line_length
|
||||
root=./
|
|
@ -0,0 +1,158 @@
|
|||
/**
|
||||
* \file cimdit.ino
|
||||
* \brief main functions
|
||||
* \author GrumpyDeveloper (Sascha Nitsch)
|
||||
* \copyright 2022 Sascha Nitsch
|
||||
* Licensed under MIT license
|
||||
*
|
||||
*/
|
||||
|
||||
/// use a custom keyboard layout
|
||||
#define HID_CUSTOM_LAYOUT
|
||||
/// use german keyboard layout
|
||||
#define LAYOUT_GERMAN
|
||||
#define HAVE_DISPLAY
|
||||
#ifdef HAVE_DISPLAY
|
||||
/// no splash screen on OLED
|
||||
#define SSD1306_NO_SPLASH
|
||||
/// OLED display width, in pixels
|
||||
#define SCREEN_WIDTH 128
|
||||
/// OLED display height, in pixels
|
||||
#define SCREEN_HEIGHT 32
|
||||
/// OLED screen address
|
||||
#define SCREEN_ADDRESS 0x3C
|
||||
#endif
|
||||
|
||||
// library includes
|
||||
#include <HID-Project.h>
|
||||
#ifdef HAVE_DISPLAY
|
||||
#include <Adafruit_SSD1306.h>
|
||||
#endif
|
||||
|
||||
// own includes
|
||||
#include "cimdithal.h"
|
||||
|
||||
/// update usb devices every x ms if needed
|
||||
#define UPDATE_INTERVAL 100
|
||||
|
||||
/// our hardware abstraction layer
|
||||
CimditHAL hal;
|
||||
|
||||
/// flag if the rotary interrupt has been triggered
|
||||
volatile bool outstandingRotInterrupt = false;
|
||||
|
||||
/// interrupt service routine for rotatry change interrupt
|
||||
void rotInt() {
|
||||
outstandingRotInterrupt = true;
|
||||
}
|
||||
|
||||
/// next update time in ms
|
||||
uint32_t nextUpdate = 0;
|
||||
#ifdef HAVE_DISPLAY
|
||||
/// our display
|
||||
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
|
||||
#endif
|
||||
/// display
|
||||
///
|
||||
/// initial setup
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
hal.begin();
|
||||
attachInterrupt(digitalPinToInterrupt(7), rotInt, RISING);
|
||||
// initialize USB related things
|
||||
Keyboard.begin();
|
||||
Mouse.begin();
|
||||
Gamepad.begin();
|
||||
#ifdef HAVE_DISPLAY
|
||||
// init display
|
||||
display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
|
||||
display.clearDisplay();
|
||||
display.display();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// main loop
|
||||
void loop() {
|
||||
hal.readFromHardware(outstandingRotInterrupt);
|
||||
if (outstandingRotInterrupt) {
|
||||
outstandingRotInterrupt = false;
|
||||
return;
|
||||
}
|
||||
if (hal.m_millis < nextUpdate && hal.m_millis > ROLLOVER_INTERVAL) return;
|
||||
nextUpdate = hal.m_millis + UPDATE_INTERVAL;
|
||||
uint8_t rotaryChanged = hal.rotaryChanged();
|
||||
if (rotaryChanged) {
|
||||
for (uint8_t i = 0; i < 7; ++i) {
|
||||
if (rotaryChanged & 1) {
|
||||
// temporary debug output
|
||||
Serial.print(F("rot "));
|
||||
Serial.print(i);
|
||||
Serial.print(F(" => "));
|
||||
Serial.println(hal.getEncoder(i));
|
||||
// end of temporary debug output
|
||||
}
|
||||
rotaryChanged >>= 1;
|
||||
}
|
||||
}
|
||||
uint16_t analogChanged = hal.analogChanged();
|
||||
if (analogChanged) {
|
||||
for (uint8_t i = 0; i < 4; ++i) {
|
||||
if (analogChanged & 1) {
|
||||
uint16_t analog = hal.getAnalog(i);
|
||||
// temporary debug output
|
||||
int16_t analogMapped16 = map(analog, 0, 1024, -32767, 32768);
|
||||
int8_t analogMapped8 = map(analog, 0, 1024, -127, 127);
|
||||
switch(i) {
|
||||
case 0:
|
||||
Gamepad.xAxis(analogMapped16);
|
||||
break;
|
||||
case 1:
|
||||
Gamepad.yAxis(analogMapped16);
|
||||
break;
|
||||
case 2:
|
||||
Gamepad.zAxis(analogMapped8);
|
||||
break;
|
||||
case 3:
|
||||
Gamepad.rxAxis(analogMapped16);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
Serial.print(F("analog "));
|
||||
Serial.print(i);
|
||||
Serial.print(F(": "));
|
||||
Serial.println(analogMapped16);
|
||||
// end of temporary debug output
|
||||
}
|
||||
analogChanged >>= 1;
|
||||
}
|
||||
Gamepad.write();
|
||||
}
|
||||
|
||||
uint64_t buttonsChanged = hal.buttonsChanged();
|
||||
if (buttonsChanged) {
|
||||
for (uint8_t i = 0; i < 64; ++i) {
|
||||
if (buttonsChanged & 0xFF) { // quickcheck for 8 bits at a time
|
||||
if (buttonsChanged & 1) {
|
||||
// temporary debug output
|
||||
Serial.print(F("button "));
|
||||
Serial.print(i);
|
||||
Serial.print(F(" "));
|
||||
bool pressed = hal.getButton(i);
|
||||
Serial.println(pressed);
|
||||
if (i<32) {
|
||||
if (pressed)
|
||||
Gamepad.press(i+1);
|
||||
else
|
||||
Gamepad.release(i+1);
|
||||
}
|
||||
// end of temporary debug output
|
||||
}
|
||||
buttonsChanged >>= 1;
|
||||
} else {
|
||||
buttonsChanged >>= 8;
|
||||
i += 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
/**
|
||||
* \file cimdithal.cpp
|
||||
* \brief implementation of the CimditHAL class
|
||||
* \author GrumpyDeveloper (Sascha Nitsch)
|
||||
* \copyright 2022 Sascha Nitsch
|
||||
* Licensed under MIT license
|
||||
*
|
||||
*/
|
||||
/// pin for analog mux bit 0
|
||||
#define ANALOG_MUX0 6
|
||||
/// pin for analog mux bit 1
|
||||
#define ANALOG_MUX1 8
|
||||
/// pin for analog mux bit 2
|
||||
#define ANALOG_MUX2 9
|
||||
/// pin for analog mux bit 3
|
||||
#define ANALOG_MUX3 10
|
||||
/// analog input pin
|
||||
#define ANALOG_IN A1
|
||||
/// rotary encoder interrupt pin
|
||||
#define ROT_INT 7
|
||||
/// scan analog every 12 milliseconds
|
||||
#define ANALOG_INTERVAL 12
|
||||
/// scan keys every 24 milliseconds
|
||||
#define KEYSCAN_INTERVAL 24
|
||||
|
||||
// library includes
|
||||
#include <Adafruit_MCP23X17.h>
|
||||
|
||||
// own includes
|
||||
#include "cimdithal.h"
|
||||
|
||||
uint32_t CimditHAL::m_millis = 0;
|
||||
|
||||
CimditHAL::CimditHAL() {
|
||||
m_nextKeyScan = 0;
|
||||
m_nextAnalogRead = 0;
|
||||
m_keyRow = 0;
|
||||
m_analogNum = 0;
|
||||
m_analogChanged = 0;
|
||||
for (uint8_t i = 0; i < 8; ++i) {
|
||||
m_currentButtons[i] = 0;
|
||||
m_buttonsChanged[i] = 0;
|
||||
}
|
||||
m_rotChanged = 0;
|
||||
}
|
||||
|
||||
void CimditHAL::begin() {
|
||||
pinMode(ROT_INT, INPUT);
|
||||
pinMode(ANALOG_MUX0, OUTPUT);
|
||||
pinMode(ANALOG_MUX1, OUTPUT);
|
||||
pinMode(ANALOG_MUX2, OUTPUT);
|
||||
pinMode(ANALOG_MUX3, OUTPUT);
|
||||
m_keyMatrix.begin_I2C(0x20);
|
||||
Serial.println(m_rotEncoders.begin_I2C(0x21));
|
||||
// combine the inputs to a single interrupt pin
|
||||
m_rotEncoders.setupInterrupts(true, false, HIGH);
|
||||
for (uint8_t i = 0; i < 8; ++i) {
|
||||
// set key matrix row to output
|
||||
m_keyMatrix.pinMode(i, OUTPUT);
|
||||
// set encoders to input and setup interrupt to fire on change
|
||||
m_rotEncoders.pinMode(i, INPUT_PULLUP);
|
||||
m_rotEncoders.setupInterruptPin(i, CHANGE);
|
||||
m_currentButtons[i] = 0xff;
|
||||
m_rotaryEncoders[i].begin();
|
||||
}
|
||||
for (uint8_t i = 8; i < 15; ++i) {
|
||||
// set key matrix rows to input
|
||||
m_keyMatrix.pinMode(i, INPUT_PULLUP);
|
||||
// set encoders to input and setup interrupt to fire on change
|
||||
m_rotEncoders.pinMode(i, INPUT_PULLUP);
|
||||
m_rotEncoders.setupInterruptPin(i, CHANGE);
|
||||
}
|
||||
m_keyMatrix.writeGPIO(0xff, 0);
|
||||
|
||||
// read in encoder values
|
||||
uint16_t rot = m_rotEncoders.readGPIOAB();
|
||||
for (uint8_t i = 0; i < 8; ++i) {
|
||||
m_rotaryEncoders[i].update((rot & 1) != 0, (rot & 2) != 0);
|
||||
rot >>= 2;
|
||||
}
|
||||
|
||||
// read in key matrix values
|
||||
for (uint8_t i = 0 ; i < 8; ++i) {
|
||||
m_keyMatrix.writeGPIO(0xff ^ (1 << i), 0);
|
||||
delay(10);
|
||||
m_currentButtons[i] = m_keyMatrix.readGPIO(1);
|
||||
}
|
||||
|
||||
// read in analog values
|
||||
for (uint8_t i = 0 ; i < 16; ++i) {
|
||||
digitalWrite(ANALOG_MUX3, i & 8);
|
||||
digitalWrite(ANALOG_MUX2, i & 4);
|
||||
digitalWrite(ANALOG_MUX1, i & 2);
|
||||
digitalWrite(ANALOG_MUX0, i & 1);
|
||||
delay(10); // give the muxer a chance
|
||||
m_currentAnalogValues[i] = analogRead(ANALOG_IN);
|
||||
}
|
||||
m_millis = millis();
|
||||
}
|
||||
|
||||
void CimditHAL::readFromHardware(bool interrupt) {
|
||||
bool rotChanged = interrupt; // if true, we return after doing the rotary steps
|
||||
while (interrupt || digitalRead(ROT_INT)) {
|
||||
rotChanged = true;
|
||||
interrupt = false;
|
||||
uint16_t rot = m_rotEncoders.readGPIOAB();
|
||||
for (uint8_t i = 0; i < 8; ++i) {
|
||||
m_rotChanged |= m_rotaryEncoders[i].update((rot & 1) != 0, (rot & 2) != 0) << i;
|
||||
rot = rot >> 2;
|
||||
}
|
||||
}
|
||||
// quick return if rotary encoder changed
|
||||
if (rotChanged) return;
|
||||
|
||||
m_millis = millis();
|
||||
// uint32_t rollover every 92 days
|
||||
if (m_millis < ROLLOVER_INTERVAL) {
|
||||
m_nextAnalogRead = 0;
|
||||
m_nextKeyScan = 0;
|
||||
}
|
||||
if (m_millis > m_nextAnalogRead) { // only scan analog every ANALOG_INTERVAL ms
|
||||
m_nextAnalogRead = m_millis + ANALOG_INTERVAL;
|
||||
// save old state
|
||||
int16_t lastAnalogValue = m_currentAnalogValues[m_analogNum];
|
||||
// scan analog value
|
||||
m_currentAnalogValues[m_analogNum] = analogRead(ANALOG_IN);
|
||||
// set changed bits
|
||||
if (lastAnalogValue != m_currentAnalogValues[m_analogNum]) {
|
||||
m_analogChanged |= 1 << m_analogNum;
|
||||
}
|
||||
// set mux value for next round, wrap to 0 at 16
|
||||
m_analogNum = (m_analogNum + 1) & 15;
|
||||
// set output
|
||||
digitalWrite(ANALOG_MUX3, m_analogNum & 8);
|
||||
digitalWrite(ANALOG_MUX2, m_analogNum & 4);
|
||||
digitalWrite(ANALOG_MUX1, m_analogNum & 2);
|
||||
digitalWrite(ANALOG_MUX0, m_analogNum & 1);
|
||||
}
|
||||
|
||||
// scan key matrix
|
||||
if (m_millis > m_nextKeyScan) { // only scan analog every KEYSCAN_INTERVAL ms
|
||||
m_nextKeyScan = m_millis + KEYSCAN_INTERVAL;
|
||||
// save old state
|
||||
uint8_t lastButtons = m_currentButtons[m_keyRow];
|
||||
// read column bits
|
||||
m_currentButtons[m_keyRow] = m_keyMatrix.readGPIO(1);
|
||||
// set changed bits
|
||||
m_buttonsChanged[m_keyRow] |= lastButtons ^ m_currentButtons[m_keyRow];
|
||||
// set row bit wrap 8 to 0
|
||||
m_keyRow = (m_keyRow + 1) & 7;
|
||||
m_keyMatrix.writeGPIO(0xff ^ (1 << m_keyRow), 0);
|
||||
}
|
||||
}
|
||||
uint8_t CimditHAL::rotaryChanged() {
|
||||
uint8_t ret = m_rotChanged;
|
||||
m_rotChanged = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint16_t CimditHAL::analogChanged() {
|
||||
uint16_t ret = m_analogChanged;
|
||||
m_analogChanged = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint64_t CimditHAL::buttonsChanged() {
|
||||
uint64_t ret = 0;
|
||||
for (int8_t i = 7; i >= 0; --i) {
|
||||
ret = (ret << 8) + m_buttonsChanged[i];
|
||||
m_buttonsChanged[i] = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int8_t CimditHAL::getEncoder(uint8_t num) {
|
||||
return m_rotaryEncoders[num].getDelta();
|
||||
}
|
||||
|
||||
uint16_t CimditHAL::getAnalog(uint8_t num) const {
|
||||
return m_currentAnalogValues[num];
|
||||
}
|
||||
|
||||
bool CimditHAL::getButton(uint8_t num) const {
|
||||
return !((m_currentButtons[num >> 3] >> (num & 7)) & 1);
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/**
|
||||
* \file cimdithal.h
|
||||
* \brief declaration of the CimditHAL class
|
||||
* \author GrumpyDeveloper (Sascha Nitsch)
|
||||
* \copyright 2022 Sascha Nitsch
|
||||
* Licensed under MIT license
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CIMDITHAL_H_
|
||||
#define CIMDITHAL_H_
|
||||
|
||||
/// rollover time (when 32 bit millis rolls over)
|
||||
#define ROLLOVER_INTERVAL 200
|
||||
|
||||
// external libraries
|
||||
#include <Adafruit_MCP23X17.h>
|
||||
|
||||
// own libraries
|
||||
#include "cimditrotary.h"
|
||||
|
||||
/**
|
||||
* \class CimditHAL
|
||||
* \brief basic hardware abstraction layer
|
||||
*/
|
||||
class CimditHAL {
|
||||
public:
|
||||
/// constructor
|
||||
CimditHAL();
|
||||
|
||||
/// initialize
|
||||
void begin();
|
||||
|
||||
/// \brief read current state from hardware
|
||||
/// \param interrupt true if called from an interrupt
|
||||
void readFromHardware(bool interrupt);
|
||||
|
||||
/// \brief has a rotary input changed
|
||||
uint8_t rotaryChanged();
|
||||
|
||||
/// \brief has an analog value changed
|
||||
uint16_t analogChanged();
|
||||
|
||||
/// \brief has a button changed
|
||||
uint64_t buttonsChanged();
|
||||
|
||||
/// \brief get single rotary value
|
||||
/// \param num number of encoder
|
||||
/// \retval delta value for the given encoder
|
||||
int8_t getEncoder(uint8_t num);
|
||||
|
||||
/// \brief get analog value
|
||||
/// \param num number of analog values
|
||||
/// \retval analog values
|
||||
uint16_t getAnalog(uint8_t num) const;
|
||||
|
||||
/// \brief get state of a single button
|
||||
/// \param num button number
|
||||
/// \retval buttons status
|
||||
bool getButton(uint8_t num) const;
|
||||
|
||||
/// global millis value
|
||||
static uint32_t m_millis;
|
||||
|
||||
private:
|
||||
CimditRotary m_rotaryEncoders[8];
|
||||
|
||||
/// rotary encoder change flags
|
||||
uint8_t m_rotChanged;
|
||||
|
||||
/// current button values
|
||||
uint8_t m_currentButtons[8];
|
||||
|
||||
/// changed values since last hardware read
|
||||
uint8_t m_buttonsChanged[8];
|
||||
|
||||
/// current analag values
|
||||
int16_t m_currentAnalogValues[16];
|
||||
|
||||
/// changed analog values flags
|
||||
uint16_t m_analogChanged;
|
||||
|
||||
/// our key matrix
|
||||
Adafruit_MCP23X17 m_keyMatrix;
|
||||
|
||||
/// our rotary encoders
|
||||
Adafruit_MCP23X17 m_rotEncoders;
|
||||
|
||||
/// next time we do an analog read
|
||||
uint32_t m_nextAnalogRead;
|
||||
|
||||
/// next time we do an key row scan
|
||||
uint32_t m_nextKeyScan;
|
||||
|
||||
/// current analog number
|
||||
uint8_t m_analogNum;
|
||||
|
||||
/// current key matrix row
|
||||
uint8_t m_keyRow;
|
||||
};
|
||||
|
||||
#endif // CIMDITHAL_H_
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* \file cimditrotary.cpp
|
||||
* \brief implementiton of the CimditRotary class
|
||||
* \author GrumpyDeveloper (Sascha Nitsch)
|
||||
* \copyright 2022 Sascha Nitsch
|
||||
* Licensed under MIT license
|
||||
*
|
||||
*/
|
||||
|
||||
// own includes
|
||||
#include "cimditrotary.h"
|
||||
|
||||
CimditRotary::CimditRotary() {
|
||||
m_a = false;
|
||||
m_delta = 0;
|
||||
}
|
||||
|
||||
void CimditRotary::begin() {
|
||||
}
|
||||
|
||||
bool CimditRotary::update(bool a, bool b) {
|
||||
if (a == m_a) return false; // no change
|
||||
if (a && !m_a) { // rising bit a
|
||||
if (b) {
|
||||
++m_delta;
|
||||
} else {
|
||||
--m_delta;
|
||||
}
|
||||
}
|
||||
m_a = a;
|
||||
return a;
|
||||
}
|
||||
|
||||
int8_t CimditRotary::getDelta() {
|
||||
int8_t ret = m_delta;
|
||||
m_delta = 0;
|
||||
return ret;
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* \file cimditrotary.h
|
||||
* \brief declaration of the CimditRotary class
|
||||
* \author GrumpyDeveloper (Sascha Nitsch)
|
||||
* \copyright 2022 Sascha Nitsch
|
||||
* Licensed under MIT license
|
||||
*
|
||||
*/
|
||||
#ifndef CIMDITROTARY_H_
|
||||
#define CIMDITROTARY_H_
|
||||
|
||||
// system includes
|
||||
#include <inttypes.h>
|
||||
|
||||
/**
|
||||
* \class CimditRotary
|
||||
* \brief rotary encocer class
|
||||
*/
|
||||
class CimditRotary {
|
||||
public:
|
||||
/// constructor
|
||||
CimditRotary();
|
||||
|
||||
/// initialize
|
||||
void begin();
|
||||
|
||||
/// update internal state with new inputs
|
||||
/// \param a first pin
|
||||
/// \param b second pin
|
||||
/// \return true on position change
|
||||
bool update(bool a, bool b);
|
||||
|
||||
/// get delta value since last read
|
||||
/// \returns number of ticks in positive - negative direction
|
||||
/// \note resets internal counters
|
||||
int8_t getDelta();
|
||||
|
||||
private:
|
||||
/// delta position since last read
|
||||
int8_t m_delta;
|
||||
|
||||
/// old a value
|
||||
bool m_a;
|
||||
};
|
||||
|
||||
#endif // CIMDITROTARY_H_
|
Loading…
Reference in New Issue