diff --git a/AirSensor.cpp b/AirSensor.cpp new file mode 100644 index 0000000..0eb742f --- /dev/null +++ b/AirSensor.cpp @@ -0,0 +1,194 @@ +// +// +// + +#include "AirSensor.h" + + +void AirSensor::changeLight(int light) +{ + switch (light) + { + case 0: + pinMode(IR_A, OUTPUT); + pinMode(IR_B, OUTPUT); + pinMode(IR_C, INPUT); + + digitalWrite(IR_A, LOW); + digitalWrite(IR_B, HIGH); + digitalWrite(IR_C, LOW); + break; + case 1: + pinMode(IR_A, OUTPUT); + pinMode(IR_B, OUTPUT); + pinMode(IR_C, INPUT); + + digitalWrite(IR_A, HIGH); + digitalWrite(IR_B, LOW); + digitalWrite(IR_C, LOW); + break; + case 2: + pinMode(IR_A, INPUT); + pinMode(IR_B, OUTPUT); + pinMode(IR_C, OUTPUT); + + digitalWrite(IR_A, LOW); + digitalWrite(IR_B, LOW); + digitalWrite(IR_C, HIGH); + break; + case 3: + pinMode(IR_A, INPUT); + pinMode(IR_B, OUTPUT); + pinMode(IR_C, OUTPUT); + + digitalWrite(IR_A, LOW); + digitalWrite(IR_B, HIGH); + digitalWrite(IR_C, LOW); + break; + case 4: + pinMode(IR_A, OUTPUT); + pinMode(IR_B, INPUT); + pinMode(IR_C, OUTPUT); + + digitalWrite(IR_A, LOW); + digitalWrite(IR_B, LOW); + digitalWrite(IR_C, HIGH); + break; + case 5: + pinMode(IR_A, OUTPUT); + pinMode(IR_B, INPUT); + pinMode(IR_C, OUTPUT); + + digitalWrite(IR_A, HIGH); + digitalWrite(IR_B, LOW); + digitalWrite(IR_C, LOW); + break; + default: + turnOffLight(); + break; + } +} + +void AirSensor::turnOffLight() +{ + pinMode(IR_A, INPUT); + pinMode(IR_B, INPUT); + pinMode(IR_C, INPUT); +} + +int AirSensor::getValue(int sensor, bool light) +{ + digitalWrite(MUX_A, bitRead(sensor, 0)); + digitalWrite(MUX_B, bitRead(sensor, 1)); + digitalWrite(MUX_C, bitRead(sensor, 2)); + + if(light) + { + changeLight(sensor); + } + else + { + turnOffLight(); + } + return analogRead(SENSOR_IN); +} + + +AirSensor::AirSensor(int requiredSamples, int skippedSamples) : thresholds{10000, 10000, 10000, 10000, 10000, 10000}, calibrationSamples{0, 0, 0, 0, 0, 0}, skippedSamples{0, 0, 0, 0, 0, 0}, samplesToAcquire(requiredSamples), samplesToSkip(skippedSamples), calibrated{0, 0, 0, 0, 0, 0}, allCalibrated(false) +{ + EEPROM.get(12, deadzone); + EEPROM.get(16, alpha); +} + +bool AirSensor::isCalibrated() +{ + if(!allCalibrated) + { + for(int i = 0; i < 6; i++) + { + if(!calibrated[i]) + return false; + } + allCalibrated = true; + } + return allCalibrated; +} + +bool AirSensor::getSensorState(int sensor) +{ + int value = getValue(sensor, true); + turnOffLight(); + + if(allCalibrated || calibrated[sensor]){ + sensorValues[sensor] = (float) value * EMA_AIRSENSOR_ALPHA + sensorValues[sensor] * (1 - EMA_AIRSENSOR_ALPHA); + return sensorValues[sensor] < thresholds[sensor]; + } + else + { + if(skippedSamples[sensor] > samplesToSkip) + { + if(value < thresholds[sensor]) thresholds[sensor] = value; + if(++calibrationSamples[sensor] > samplesToAcquire) + { + sensorValues[sensor] = value; + calibrated[sensor] = true; + thresholds[sensor] -= AIR_SENSOR_THRESHOLD_SUBTRACT; + }; + } + else + { + skippedSamples[sensor]++; + } + return false; + } +} + +float AirSensor::getHandPosition() +{ + float total = 0; + float sensorsTriggered = 0; + for(int i = 0; i < 6; i++) + { + if(getSensorState(i)) + { + sensorsTriggered++; + total += i + 1; + } + } + return sensorsTriggered == 0 ? 0 : (total / (sensorsTriggered)) / 6; +} + +void AirSensor::setDeadzone(int deadzone) +{ + this->deadzone = deadzone; + EEPROM.put(12, deadzone); +} + +void AirSensor::setAlpha(float alpha) +{ + this->alpha = alpha; + EEPROM.put(16, alpha); +} + +int AirSensor::getDeadzone() +{ + return deadzone; +} + +float AirSensor::getAlpha() +{ + return alpha; +} + +void AirSensor::recalibrate() +{ + for(int i = 0; i < 6; i++) + { + thresholds[i] = 0; + calibrationSamples[i] = 0; + skippedSamples[i] = 0; + sensorValues[i] = 0; + calibrated[i] = false; + } + allCalibrated = false; +} diff --git a/AirSensor.h b/AirSensor.h new file mode 100644 index 0000000..4692043 --- /dev/null +++ b/AirSensor.h @@ -0,0 +1,52 @@ +// AirSensor.h + +#ifndef _AIRSENSOR_h +#define _AIRSENSOR_h + +#if defined(ARDUINO) && ARDUINO >= 100 + #include "arduino.h" +#else + #include "WProgram.h" +#endif + +#include "PinConfig.h" +#include + +#define AIR_SENSOR_THRESHOLD_SUBTRACT 5 +#define EMA_AIRSENSOR_ALPHA 0.4f + +class AirSensor +{ +private: + void changeLight(int light); + void turnOffLight(); + + int thresholds[6]; + int calibrationSamples[6]; + int skippedSamples[6]; + float sensorValues[6]; + int samplesToAcquire; + int samplesToSkip; + + bool calibrated[6]; + bool allCalibrated; + + uint16_t deadzone; + float alpha; +public: + AirSensor(int requiredSamples, int skippedSamples); + bool isCalibrated(); + bool getSensorState(int sensor); + int getValue(int sensor, bool light); + float getHandPosition(); + + void setDeadzone(int deadzone); + void setAlpha(float alpha); + int getDeadzone(); + float getAlpha(); + void recalibrate(); +}; + + +#endif + diff --git a/CapacitiveSensor.cpp b/CapacitiveSensor.cpp new file mode 100644 index 0000000..dddd227 --- /dev/null +++ b/CapacitiveSensor.cpp @@ -0,0 +1,158 @@ +/* + CapacitiveSense.h - Capacitive Sensing Library for 'duino / Wiring + https://github.com/PaulStoffregen/CapacitiveSensor + http://www.pjrc.com/teensy/td_libs_CapacitiveSensor.html + http://playground.arduino.cc/Main/CapacitiveSensor + Copyright (c) 2009 Paul Bagder + Updates for other hardare by Paul Stoffregen, 2010-2016 + vim: set ts=4: + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#if ARDUINO >= 100 +#include "Arduino.h" +#else +#include "WProgram.h" +#include "pins_arduino.h" +#include "WConstants.h" +#endif + +#include "CapacitiveSensor.h" + +// Constructor ///////////////////////////////////////////////////////////////// +// Function that handles the creation and setup of instances + +CapacitiveSensor::CapacitiveSensor(uint8_t sendPin, uint8_t receivePin1, uint8_t receivePin2) +{ + // initialize this instance's variables + error = 1; + loopTimingFactor = 310; // determined empirically - a hack + + CS_Timeout_Millis = (200 * (float)loopTimingFactor * (float)F_CPU) / 16000000; + + // Serial.print("timwOut = "); + // Serial.println(CS_Timeout_Millis); + + // get pin mapping and port for send Pin - from PinMode function in core + +#ifdef NUM_DIGITAL_PINS + if (sendPin >= NUM_DIGITAL_PINS) error = -1; + if (receivePin1 >= NUM_DIGITAL_PINS) error = -1; + if (receivePin2 >= NUM_DIGITAL_PINS) error = -1; +#endif + pinMode(sendPin, OUTPUT); // sendpin to OUTPUT + pinMode(receivePin1, INPUT); // receivePin to INPUT + pinMode(receivePin2, INPUT); // receivePin to INPUT + + sBit = PIN_TO_BITMASK(sendPin); // get send pin's ports and bitmask + sReg = PIN_TO_BASEREG(sendPin); // get pointer to output register + + r1Bit = PIN_TO_BITMASK(receivePin1); + r1Reg = PIN_TO_BASEREG(receivePin1); + + r2Bit = PIN_TO_BITMASK(receivePin2); + r2Reg = PIN_TO_BASEREG(receivePin2); + + retVal = new unsigned int[2]; +} + +// Public Methods ////////////////////////////////////////////////////////////// +// Functions available in Wiring sketches, this library, and other libraries + +unsigned int* CapacitiveSensor::sense(uint8_t samples) +{ + total1 = 0; + total2 = 0; + + noInterrupts(); + for (uint8_t i = 0; i < samples; i++) { + if (!SenseOneCycle()) return nullptr; // variable over timeout + } + interrupts(); + + retVal[0] = total1; + retVal[1] = total2; + + return retVal; + +} + +// Private Methods ///////////////////////////////////////////////////////////// +// Functions only available to other functions in this library + +int CapacitiveSensor::SenseOneCycle(void) +{ + + DIRECT_WRITE_LOW(sReg, sBit); // sendPin Register low + DIRECT_MODE_INPUT(r1Reg, r1Bit); // r1eceivePin to input (pullups ar1e off) + DIRECT_MODE_OUTPUT(r1Reg, r1Bit); // r1eceivePin to OUTPUT + DIRECT_WRITE_LOW(r1Reg, r1Bit); // pin is now LOW AND OUTPUT + delayMicroseconds(10); + DIRECT_MODE_INPUT(r1Reg, r1Bit); // r1eceivePin to input (pullups ar1e off) + + DIRECT_MODE_INPUT(r2Reg, r2Bit); // r2eceivePin to input (pullups ar2e off) + DIRECT_MODE_OUTPUT(r2Reg, r2Bit); // r2eceivePin to OUTPUT + DIRECT_WRITE_LOW(r2Reg, r2Bit); // pin is now LOW AND OUTPUT + delayMicroseconds(10); + DIRECT_MODE_INPUT(r2Reg, r2Bit); // r2eceivePin to input (pullups ar2e off) + DIRECT_WRITE_HIGH(sReg, sBit); // sendPin High + + while ((total1 < CS_Timeout_Millis)) { // while total is positive value + pin1State = DIRECT_READ(r1Reg, r1Bit); + pin2State = DIRECT_READ(r2Reg, r2Bit); + + total1 += !pin1State; + total2 += !pin2State; + + if(pin1State && pin2State) break; + } + //Serial.print("SenseOneCycle(1): "); + //Serial.println(total); + + if (total1 > CS_Timeout_Millis) { + return -2; // total variable over timeout + } + // set receive pin HIGH briefly to charge up fully - because the while loop above will exit when pin is ~ 2.5V + DIRECT_WRITE_HIGH(r1Reg, r1Bit); + DIRECT_MODE_OUTPUT(r1Reg, r1Bit); // receivePin to OUTPUT - pin is now HIGH AND OUTPUT + DIRECT_MODE_OUTPUT(r2Reg, r2Bit); // receivePin to OUTPUT - pin is now HIGH AND OUTPUT + DIRECT_WRITE_HIGH(r1Reg, r1Bit); + DIRECT_WRITE_HIGH(r2Reg, r2Bit); + DIRECT_MODE_INPUT(r1Reg, r1Bit); // receivePin to INPUT (pullup is off) + DIRECT_MODE_INPUT(r2Reg, r2Bit); // receivePin to INPUT (pullup is off) + DIRECT_WRITE_LOW(sReg, sBit); // sendPin LOW + + while ( (total1 < CS_Timeout_Millis) ) { // while receive pin is HIGH AND total is less than timeout + pin1State = DIRECT_READ(r1Reg, r1Bit); + pin2State = DIRECT_READ(r2Reg, r2Bit); + + total1 += pin1State; + total2 += pin2State; + + if(!(pin1State || pin2State)) break; + } + //Serial.print("SenseOneCycle(2): "); + //Serial.println(total); + if (total1 >= CS_Timeout_Millis) { + return -2; // total variable over timeout + } else { + return 1; + } +} diff --git a/CapacitiveSensor.h b/CapacitiveSensor.h new file mode 100644 index 0000000..8112244 --- /dev/null +++ b/CapacitiveSensor.h @@ -0,0 +1,232 @@ +/* + CapacitiveSense.h v.04 - Capacitive Sensing Library for 'duino / Wiring + https://github.com/PaulStoffregen/CapacitiveSensor + http://www.pjrc.com/teensy/td_libs_CapacitiveSensor.html + http://playground.arduino.cc/Main/CapacitiveSensor + Copyright (c) 2008 Paul Bagder All rights reserved. + Version 05 by Paul Stoffregen - Support non-AVR board: Teensy 3.x, Arduino Due + Version 04 by Paul Stoffregen - Arduino 1.0 compatibility, issue 146 fix + vim: set ts=4: +*/ + +// ensure this library description is only included once +#ifndef CapacitiveSensor_h +#define CapacitiveSensor_h + +#if ARDUINO >= 100 +#include "Arduino.h" +#else +#include "WProgram.h" +#endif + +// Direct I/O through registers and bitmask (from OneWire library) + +#if defined(__AVR__) +#define PIN_TO_BASEREG(pin) (portInputRegister(digitalPinToPort(pin))) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint8_t +#define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) &= ~(mask), (*((base)+2)) &= ~(mask)) +#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+1)) |= (mask)) +#define DIRECT_WRITE_LOW(base, mask) ((*((base)+2)) &= ~(mask)) +#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+2)) |= (mask)) + +#elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK66FX1M0__) || defined(__MK64FX512__) +#define PIN_TO_BASEREG(pin) (portOutputRegister(pin)) +#define PIN_TO_BITMASK(pin) (1) +#define IO_REG_TYPE uint8_t +#define IO_REG_ASM +#define DIRECT_READ(base, mask) (*((base)+512)) +#define DIRECT_MODE_INPUT(base, mask) (*((base)+640) = 0) +#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+640) = 1) +#define DIRECT_WRITE_LOW(base, mask) (*((base)+256) = 1) +#define DIRECT_WRITE_HIGH(base, mask) (*((base)+128) = 1) + +#elif defined(__MKL26Z64__) +#define PIN_TO_BASEREG(pin) (portOutputRegister(pin)) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint8_t +#define IO_REG_ASM +#define DIRECT_READ(base, mask) ((*((base)+16) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) (*((base)+20) &= ~(mask)) +#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+20) |= (mask)) +#define DIRECT_WRITE_LOW(base, mask) (*((base)+8) = (mask)) +#define DIRECT_WRITE_HIGH(base, mask) (*((base)+4) = (mask)) + +#elif defined(__SAM3X8E__) +#define PIN_TO_BASEREG(pin) (&(digitalPinToPort(pin)->PIO_PER)) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint32_t +#define IO_REG_ASM +#define DIRECT_READ(base, mask) (((*((base)+15)) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) ((*((base)+5)) = (mask)) +#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+4)) = (mask)) +#define DIRECT_WRITE_LOW(base, mask) ((*((base)+13)) = (mask)) +#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+12)) = (mask)) + +#elif defined(__PIC32MX__) +#define PIN_TO_BASEREG(pin) (portModeRegister(digitalPinToPort(pin))) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint32_t +#define IO_REG_ASM +#define DIRECT_READ(base, mask) (((*(base+4)) & (mask)) ? 1 : 0) //PORTX + 0x10 +#define DIRECT_MODE_INPUT(base, mask) ((*(base+2)) = (mask)) //TRISXSET + 0x08 +#define DIRECT_MODE_OUTPUT(base, mask) ((*(base+1)) = (mask)) //TRISXCLR + 0x04 +#define DIRECT_WRITE_LOW(base, mask) ((*(base+8+1)) = (mask)) //LATXCLR + 0x24 +#define DIRECT_WRITE_HIGH(base, mask) ((*(base+8+2)) = (mask)) //LATXSET + 0x28 + +#elif defined(ARDUINO_ARCH_ESP8266) +#define PIN_TO_BASEREG(pin) (portOutputRegister(digitalPinToPort(pin))) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint32_t +#define IO_REG_ASM +#define DIRECT_READ(base, mask) (((*(base+6)) & (mask)) ? 1 : 0) //GPIO_IN_ADDRESS +#define DIRECT_MODE_INPUT(base, mask) ((*(base+5)) = (mask)) //GPIO_ENABLE_W1TC_ADDRESS +#define DIRECT_MODE_OUTPUT(base, mask) ((*(base+4)) = (mask)) //GPIO_ENABLE_W1TS_ADDRESS +#define DIRECT_WRITE_LOW(base, mask) ((*(base+2)) = (mask)) //GPIO_OUT_W1TC_ADDRESS +#define DIRECT_WRITE_HIGH(base, mask) ((*(base+1)) = (mask)) //GPIO_OUT_W1TS_ADDRESS + +#elif defined(__SAMD21G18A__) +// runs extremely slow/unreliable on Arduino Zero - help wanted.... +#define PIN_TO_BASEREG(pin) portModeRegister(digitalPinToPort(pin)) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint32_t +#define IO_REG_ASM +#define DIRECT_READ(base, mask) (((*((base)+8)) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) = (mask)) +#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+2)) = (mask)) +#define DIRECT_WRITE_LOW(base, mask) ((*((base)+5)) = (mask)) +#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+6)) = (mask)) + +#elif defined(RBL_NRF51822) +#define PIN_TO_BASEREG(pin) (0) +#define PIN_TO_BITMASK(pin) (pin) +#define IO_REG_TYPE uint32_t +#define IO_REG_ASM +#define DIRECT_READ(base, pin) nrf_gpio_pin_read(pin) +#define DIRECT_WRITE_LOW(base, pin) nrf_gpio_pin_clear(pin) +#define DIRECT_WRITE_HIGH(base, pin) nrf_gpio_pin_set(pin) +#define DIRECT_MODE_INPUT(base, pin) nrf_gpio_cfg_input(pin, NRF_GPIO_PIN_NOPULL) +#define DIRECT_MODE_OUTPUT(base, pin) nrf_gpio_cfg_output(pin) + +#elif defined(__arc__) + +#include "scss_registers.h" +#include "portable.h" +#include "avr/pgmspace.h" + +#define GPIO_ID(pin) (g_APinDescription[pin].ulGPIOId) +#define GPIO_TYPE(pin) (g_APinDescription[pin].ulGPIOType) +#define GPIO_BASE(pin) (g_APinDescription[pin].ulGPIOBase) +#define DIR_OFFSET_SS 0x01 +#define DIR_OFFSET_SOC 0x04 +#define EXT_PORT_OFFSET_SS 0x0A +#define EXT_PORT_OFFSET_SOC 0x50 + +/* GPIO registers base address */ +#define PIN_TO_BASEREG(pin) ((volatile uint32_t *)g_APinDescription[pin].ulGPIOBase) +#define PIN_TO_BITMASK(pin) pin +#define IO_REG_TYPE uint32_t +#define IO_REG_ASM + +static inline __attribute__((always_inline)) +IO_REG_TYPE directRead(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) +{ + IO_REG_TYPE ret; + if (SS_GPIO == GPIO_TYPE(pin)) { + ret = READ_ARC_REG(((IO_REG_TYPE)base + EXT_PORT_OFFSET_SS)); + } else { + ret = MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, EXT_PORT_OFFSET_SOC); + } + return ((ret >> GPIO_ID(pin)) & 0x01); +} + +static inline __attribute__((always_inline)) +void directModeInput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) +{ + if (SS_GPIO == GPIO_TYPE(pin)) { + WRITE_ARC_REG(READ_ARC_REG((((IO_REG_TYPE)base) + DIR_OFFSET_SS)) & ~(0x01 << GPIO_ID(pin)), + ((IO_REG_TYPE)(base) + DIR_OFFSET_SS)); + } else { + MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) &= ~(0x01 << GPIO_ID(pin)); + } +} + +static inline __attribute__((always_inline)) +void directModeOutput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) +{ + if (SS_GPIO == GPIO_TYPE(pin)) { + WRITE_ARC_REG(READ_ARC_REG(((IO_REG_TYPE)(base) + DIR_OFFSET_SS)) | (0x01 << GPIO_ID(pin)), + ((IO_REG_TYPE)(base) + DIR_OFFSET_SS)); + } else { + MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) |= (0x01 << GPIO_ID(pin)); + } +} + +static inline __attribute__((always_inline)) +void directWriteLow(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) +{ + if (SS_GPIO == GPIO_TYPE(pin)) { + WRITE_ARC_REG(READ_ARC_REG(base) & ~(0x01 << GPIO_ID(pin)), base); + } else { + MMIO_REG_VAL(base) &= ~(0x01 << GPIO_ID(pin)); + } +} + +static inline __attribute__((always_inline)) +void directWriteHigh(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) +{ + if (SS_GPIO == GPIO_TYPE(pin)) { + WRITE_ARC_REG(READ_ARC_REG(base) | (0x01 << GPIO_ID(pin)), base); + } else { + MMIO_REG_VAL(base) |= (0x01 << GPIO_ID(pin)); + } +} + +#define DIRECT_READ(base, pin) directRead(base, pin) +#define DIRECT_MODE_INPUT(base, pin) directModeInput(base, pin) +#define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(base, pin) +#define DIRECT_WRITE_LOW(base, pin) directWriteLow(base, pin) +#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(base, pin) + +#endif + +// some 3.3V chips with 5V tolerant pins need this workaround +// +#if defined(__MK20DX256__) +#define FIVE_VOLT_TOLERANCE_WORKAROUND +#endif + + +// library interface description +class CapacitiveSensor +{ + // user-accessible "public" interface + public: + // methods + CapacitiveSensor(uint8_t sendPin, uint8_t receivePin1, uint8_t receivePin2); + unsigned int* sense(uint8_t samples); + // library-accessible "private" interface + private: + // variables + int error; + unsigned long leastTotal; + unsigned int loopTimingFactor; + unsigned long CS_Timeout_Millis; + unsigned int total1; + unsigned int total2; + unsigned int *retVal; + bool pin1State; + bool pin2State; + + IO_REG_TYPE sBit; // send pin's ports and bitmask + volatile IO_REG_TYPE *sReg; + IO_REG_TYPE r1Bit; // receive 1 pin's ports and bitmask + volatile IO_REG_TYPE *r1Reg; + IO_REG_TYPE r2Bit; // receive 2 pin's ports and bitmask + volatile IO_REG_TYPE *r2Reg; + // methods + int SenseOneCycle(void); +}; + +#endif diff --git a/OpeNITHM.ino b/OpeNITHM.ino new file mode 100644 index 0000000..a98f726 --- /dev/null +++ b/OpeNITHM.ino @@ -0,0 +1,161 @@ +#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega32u4__) +#include "USBOutput.h" +#define USB +#endif + +#ifdef USB +#include "USBOutput.h" +#else +#include "SerialOutput.h" +#endif +#include "AirSensor.h" +#include "Touchboard.h" +#include +#include + +CRGB leds[16]; + +float lightIntensity[16]; +bool keyStates[16]; +bool activated = true; +float sensorPosition = -1; + +Touchboard *touchboard; +AirSensor *sensor; +Output *output; + +char *command; + +void onKeyPress(int key, bool wasHeld) +{ + lightIntensity[key] = 1.0f; + output->sendKeyEvent(key, true, wasHeld); + keyStates[key] = true; +} + +void parseCommand() +{ + char input1 = Serial.read(); + char input2 = Serial.read(); + switch (input1) + { + case 't': + switch (input2) + { + case 't': + touchboard->setThreshold(Serial.parseInt()); + break; + case 'd': + touchboard->setDeadzone(Serial.parseInt()); + break; + case 'a': + touchboard->setAlpha(Serial.parseFloat()); + break; + case 'c': + touchboard->calibrateKeys(); + } + break; + case 'i': + switch (input2) + { + case 'd': + sensor->setDeadzone(Serial.parseInt()); + break; + case 'a': + sensor->setAlpha(Serial.parseFloat()); + break; + case 'c': + sensor->recalibrate(); + } + break; + case 'p': + activated = false; + break; + case 'r': + activated = true; + break; + } + + Serial.print("tt \t"); + Serial.println(touchboard->getThreshold()); + Serial.print("td \t"); + Serial.println(touchboard->getDeadzone()); + Serial.print("ta \t"); + Serial.println(touchboard->getAlpha()); + Serial.print("id \t"); + Serial.println(sensor->getDeadzone()); + Serial.print("ia \t"); + Serial.println(sensor->getAlpha()); + Serial.print(";"); +} + +void setup() { + Serial.begin(112500); + command = malloc(32); + + FastLED.addLeds(leds, 16); + + for (CRGB& led : leds) + { + led = 0x0000FF; + FastLED.show(); + } + + touchboard = new Touchboard(onKeyPress); + + for (CRGB& led : leds) + { + led = 0xFF0000; + FastLED.show(); + } + + sensor = new AirSensor(500, 50); + #ifdef USB + output = new USBOutput(); + #else + output = new SerialOutput(); + #endif +} + +void loop() { + if (!activated) return; + + // Scan touch keyboard and update lights + touchboard->scan(); + for (int i = 0; i < 16; i++) + { + if (lightIntensity[i] > 0.05f) + lightIntensity[i] -= 0.05f; + if (touchboard->update(i)) + { + leds[i].setRGB(128 + 127 * lightIntensity[i], 0, 128 + 127 * lightIntensity[i]); + } + else + { + leds[i].setRGB(128, 128, 0); + if (keyStates[i]) + { + output->sendKeyEvent(i, false, false); + keyStates[i] = false; + } + } + } + + const float newPosition = sensor->getHandPosition(); + if (newPosition != sensorPosition) + { + output->sendSensorEvent(newPosition); + sensorPosition = newPosition; + } + + + // If the air sensor is calibrated, begin updating lights. The air sensor will automatically calibrate as it is being polled. + if (sensor->isCalibrated()) + FastLED.show(); + + if (Serial.available()) + { + parseCommand(); + } +} + diff --git a/Output.h b/Output.h new file mode 100644 index 0000000..76cd6a5 --- /dev/null +++ b/Output.h @@ -0,0 +1,20 @@ +// Output.h + +#ifndef _OUTPUT_h +#define _OUTPUT_h + +#if defined(ARDUINO) && ARDUINO >= 100 + #include "arduino.h" +#else + #include "WProgram.h" +#endif + +class Output +{ + public: + virtual void sendKeyEvent(int key, bool pressed, bool doublePressed); + virtual void sendSensorEvent(float position); +}; + +#endif + diff --git a/PinConfig.h b/PinConfig.h new file mode 100644 index 0000000..004580b --- /dev/null +++ b/PinConfig.h @@ -0,0 +1,23 @@ +#pragma once + +// Multiplexer pin settings +#define MUX_A 9 +#define MUX_B 8 +#define MUX_C 7 + +// Sensor pin settings +#define IR_A 5 +#define IR_B 4 +#define IR_C 3 + +#define SENSOR_IN A0 + +// Capsense pin settings +#define KEYBOARDPIN_1 10 +#define KEYBOARDPIN_2 12 +#define KEYBOARDPIN_COM 11 + +// Lighting pin settings +#define LED_TYPE WS2812B +#define LED_ORDER GRB +#define RGBPIN 2 diff --git a/SerialOutput.cpp b/SerialOutput.cpp new file mode 100644 index 0000000..111d398 --- /dev/null +++ b/SerialOutput.cpp @@ -0,0 +1,25 @@ +// +// +// + +#include "SerialOutput.h" + +void SerialOutput::sendKeyEvent(int key, bool pressed, bool doublePressed) +{ + builtPacket.data = 0; + builtPacket.keyEvent.isKeyboard = true; + builtPacket.keyEvent.isPressed = pressed; + builtPacket.keyEvent.isDoublePressed = doublePressed; + builtPacket.keyEvent.key = key; + + Serial.write(builtPacket.data); +} + +void SerialOutput::sendSensorEvent(float position) +{ + builtPacket.data = 0; + builtPacket.sensorEvent.isKeyboard = false; + builtPacket.sensorEvent.position = position * 127; + + Serial.write(builtPacket.data); +} diff --git a/SerialOutput.h b/SerialOutput.h new file mode 100644 index 0000000..cdac7b7 --- /dev/null +++ b/SerialOutput.h @@ -0,0 +1,42 @@ +// SerialOutput.h + +#ifndef _SERIALOUTPUT_h +#define _SERIALOUTPUT_h + +#if defined(ARDUINO) && ARDUINO >= 100 + #include "arduino.h" +#else + #include "WProgram.h" +#endif +#include "Output.h" + +//All events can fit within one byte to save time sending over serial +union Packet +{ + uint8_t data; + struct + { + bool isKeyboard : 1; + bool isPressed : 1; + bool isDoublePressed : 1; + unsigned int key : 5; + } keyEvent; + struct + { + bool isKeyboard : 1; + int position : 7; + } sensorEvent; +}; + + +class SerialOutput : public Output +{ +private: + Packet builtPacket = Packet(); +public: + void sendKeyEvent(int key, bool pressed, bool doublePressed) override; + void sendSensorEvent(float position) override; +}; + +#endif + diff --git a/Touchboard.cpp b/Touchboard.cpp new file mode 100644 index 0000000..2d49217 --- /dev/null +++ b/Touchboard.cpp @@ -0,0 +1,117 @@ +#include "Touchboard.h" + +void Touchboard::scan() +{ + for (int i = 0; i < 8; i++) + { + digitalWrite(MUX_A, bitRead(i, 0)); + digitalWrite(MUX_B, bitRead(i, 1)); + digitalWrite(MUX_C, bitRead(i, 2)); + + unsigned int* values = sensor.sense(3); + keys[i] = values[0]; + keys[i + 8] = values[1]; + } +} + +void Touchboard::calibrateKeys() +{ + for (int i = 0; i < 16; i++) + { + em_averages[i] = 0; + neutral_values[i] = 0; + key_states[i] = false; + keys[i] = false; + } + + for (int i = 0; i < 100; i++) { + scan(); + for (int j = 0; j < 16; j++) { + if (keys[j] > neutral_values[j]) neutral_values[j] = keys[j]; + } + } + for (int i = 0; i < 16; i++) { + em_averages[i] = neutral_values[i]; + } +} + +bool Touchboard::update(int key) +{ + int read_value = keys[key]; + float new_average = alpha * read_value + (1 - alpha) * em_averages[key]; + if (read_value - neutral_values[key] < deadzone) + { + // If we are in the deadzone, ignore any touch events, set the key to be untouched and update the moving average. + if (key_states[key]) + em_averages[key] = neutral_values[key]; + else + em_averages[key] = new_average; + key_states[key] = false; + } + else + //If we are outside of the deadzone: + { + if (read_value > em_averages[key] + threshold) + { + // If we just detected a touch, make the key touched, discard the previous moving average and trigger the callback. + em_averages[key] = read_value; + onKeyPress(key, key_states[key]); + key_states[key] = true; + } + else + { + // Otherwise, just update the average. + em_averages[key] = new_average; + } + } + return key_states[key]; +} + +void Touchboard::setThreshold(uint16_t threshold) +{ + this->threshold = threshold; + EEPROM.put(0, threshold); +} + +void Touchboard::setDeadzone(uint16_t deadzone) +{ + this->deadzone = deadzone; + EEPROM.put(4, deadzone); +} + +void Touchboard::setAlpha(float alpha) +{ + this->alpha = alpha; + EEPROM.put(8, alpha); +} + +int Touchboard::getThreshold() +{ + return threshold; +} + +int Touchboard::getDeadzone() +{ + return deadzone; +} + +float Touchboard::getAlpha() +{ + return alpha; +} + +Touchboard::Touchboard(void(*keyPressCallback)(int, bool)) : + sensor(CapacitiveSensor(KEYBOARDPIN_COM, KEYBOARDPIN_1, KEYBOARDPIN_2)), + onKeyPress(keyPressCallback) +{ + EEPROM.get(0, threshold); + EEPROM.get(4, deadzone); + EEPROM.get(8, alpha); + + pinMode(MUX_A, OUTPUT); + pinMode(MUX_B, OUTPUT); + pinMode(MUX_C, OUTPUT); + + calibrateKeys(); +} + diff --git a/Touchboard.h b/Touchboard.h new file mode 100644 index 0000000..f2a5fff --- /dev/null +++ b/Touchboard.h @@ -0,0 +1,49 @@ +// Touchboard.h + +#ifndef _TOUCHBOARD_h +#define _TOUCHBOARD_h + +#if defined(ARDUINO) && ARDUINO >= 100 + #include "arduino.h" +#else + #include "WProgram.h" +#endif + +#include "PinConfig.h" +#include "CapacitiveSensor.h" +#include +#define EMA_TOUCHDETECT_ALPHA 0.1f + +class Touchboard +{ +private: + CapacitiveSensor sensor; + + + uint16_t threshold; + uint16_t deadzone; + float alpha; + + float em_averages[16]; + unsigned int keys[16]; + int neutral_values[16]; + void (*onKeyPress)(int, bool); + + +public: + boolean key_states[16]; + + Touchboard(void(*keyPressCallback)(int, bool)); + void scan(); + bool update(int key); + void setThreshold(uint16_t threshold); + void setDeadzone(uint16_t deadzone); + void setAlpha(float alpha); + int getThreshold(); + int getDeadzone(); + float getAlpha(); + void calibrateKeys(); +}; + +#endif + diff --git a/USBOutput.cpp b/USBOutput.cpp new file mode 100644 index 0000000..3e45730 --- /dev/null +++ b/USBOutput.cpp @@ -0,0 +1,13 @@ +// +// +// + +#include "USBOutput.h" + +void USBOutput::sendKeyEvent(int key, bool pressed, bool doublePressed) +{ +} + +void USBOutput::sendSensorEvent(float position) +{ +} diff --git a/USBOutput.h b/USBOutput.h new file mode 100644 index 0000000..0bf76ed --- /dev/null +++ b/USBOutput.h @@ -0,0 +1,21 @@ +// USBOutput.h + +#ifndef _USBOUTPUT_h +#define _USBOUTPUT_h + +#if defined(ARDUINO) && ARDUINO >= 100 + #include "arduino.h" +#else + #include "WProgram.h" +#endif +#include "Output.h" + +class USBOutput : public Output +{ +public: + void sendKeyEvent(int key, bool pressed, bool doublePressed) override; + void sendSensorEvent(float position) override; +}; + +#endif +