HAL first iteration

Debug and glue code to show the data / map to joystick
master
Sascha Nitsch 2022-02-18 18:15:30 +01:00
parent 8118e696cf
commit c6e4d17ffb
7 changed files with 3194 additions and 0 deletions

2
CPPLINT.cfg Normal file
View File

@ -0,0 +1,2 @@
filter=-build/include_subdir,-whitespace/line_length
root=./

2663
Doxyfile Normal file

File diff suppressed because it is too large Load Diff

158
cimdit.ino Normal file
View File

@ -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;
}
}
}
}

185
cimdithal.cpp Normal file
View File

@ -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);
}

102
cimdithal.h Normal file
View File

@ -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_

38
cimditrotary.cpp Normal file
View File

@ -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;
}

46
cimditrotary.h Normal file
View File

@ -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_