From 753365b71a46114cb4390521c33b94c19ac6f4ce Mon Sep 17 00:00:00 2001 From: skogaby Date: Thu, 12 Mar 2020 00:11:44 -0500 Subject: [PATCH] Add a single, unified serial processor that can handle both LED updates and config commands. Make lighting mode toggleable during runtime without needing a reflash. Overhaul the analog air sensor thresholding logic to be simpler and more robust. Make the sensitivity for the slider and analog air sensors configurable. Make the serial commands adhere to the format the config tool will use. --- Firmware/OpeNITHM/AirSensor.cpp | 75 +++++++++------------ Firmware/OpeNITHM/AirSensor.h | 11 +--- Firmware/OpeNITHM/AutoTouchboard.cpp | 8 ++- Firmware/OpeNITHM/AutoTouchboard.h | 3 +- Firmware/OpeNITHM/OpeNITHM.ino | 93 ++++++++++++--------------- Firmware/OpeNITHM/SerialLeds.cpp | 87 ++----------------------- Firmware/OpeNITHM/SerialLeds.h | 15 +---- Firmware/OpeNITHM/SerialProcessor.cpp | 61 ++++++++++++++++++ Firmware/OpeNITHM/SerialProcessor.h | 45 +++++++++++++ 9 files changed, 196 insertions(+), 202 deletions(-) create mode 100644 Firmware/OpeNITHM/SerialProcessor.cpp create mode 100644 Firmware/OpeNITHM/SerialProcessor.h diff --git a/Firmware/OpeNITHM/AirSensor.cpp b/Firmware/OpeNITHM/AirSensor.cpp index ba153d5..1c78a6a 100644 --- a/Firmware/OpeNITHM/AirSensor.cpp +++ b/Firmware/OpeNITHM/AirSensor.cpp @@ -117,10 +117,6 @@ int AirSensor::getValue(int sensor, bool light) 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) { - // Load config values - EEPROM.get(68, deadzone); - EEPROM.get(72, alpha); - #ifdef IR_SENSOR_ANALOG digitalMode = false; #else @@ -138,6 +134,8 @@ AirSensor::AirSensor(int requiredSamples, int skippedSamples) : thresholds{ 1000 calibrated[i] = getValue(i, true); } } + + analogSensitivity = 40; } // Check if all IR sensors are calibrated. If they are, set a flag to not need to re-check it @@ -159,45 +157,46 @@ bool AirSensor::isCalibrated() #endif } -bool AirSensor::getSensorState(int sensor) -{ +bool AirSensor::getSensorState(int sensor) { // Flash the LED and read the IR sensor int value = getValue(sensor, true); turnOffLight(); - if (digitalMode) - return value == LOW ? true : false; - else + if (digitalMode) { - - // If the sensor is calibrated, Store its current filtered value. - // We are using an exponential moving average to filter out environmental noise. Setting alpha to 1 disables it. - if (allCalibrated || calibrated[sensor]) { - sensorValues[sensor] = (float)value * alpha + sensorValues[sensor] * (1 - alpha); - return sensorValues[sensor] < thresholds[sensor]; - } - else + return value == LOW ? true : false; + } + else + { + if (allCalibrated || calibrated[sensor]) { - // If it is not calibrated, perform calibration: - // Skip the first few samples. This might not be required, but improved performance in my case. - // This might be due to wiring mistakes I made - I'm leaving the code in either way as it can't hurt. - if (skippedSamples[sensor] > samplesToSkip) + sensorValues[sensor] = value; + return sensorValues[sensor] < thresholds[sensor]; + } + else + { + // If it is not calibrated, perform calibration + // Skip the first few samples + if (skippedSamples[sensor] > samplesToSkip) { // Keep the minimum value seen by the sensor - if (value < thresholds[sensor]) thresholds[sensor] = value; + if (value < thresholds[sensor]) + thresholds[sensor] = value; + // If we have enough samples: - if (++calibrationSamples[sensor] > samplesToAcquire) + if (++calibrationSamples[sensor] > samplesToAcquire) { // Consider the sensor calibrated. Finalize calibration for this sensor. sensorValues[sensor] = value; calibrated[sensor] = true; - thresholds[sensor] -= deadzone; - }; - } - else + thresholds[sensor] *= (analogSensitivity / 100); + } + } + else { skippedSamples[sensor]++; } + return false; } } @@ -238,26 +237,9 @@ bool AirSensor::getSensorCalibrated(int i) return calibrated[i]; } -void AirSensor::setDeadzone(int deadzone) +void AirSensor::setAnalogSensitivity(uint8_t analogSensitivity) { - this->deadzone = deadzone; - EEPROM.put(68, deadzone); -} - -void AirSensor::setAlpha(float alpha) -{ - this->alpha = alpha; - EEPROM.put(72, alpha); -} - -int AirSensor::getDeadzone() -{ - return deadzone; -} - -float AirSensor::getAlpha() -{ - return alpha; + this->analogSensitivity = analogSensitivity; } void AirSensor::recalibrate() @@ -270,5 +252,6 @@ void AirSensor::recalibrate() sensorValues[i] = 0; calibrated[i] = false; } + allCalibrated = false; } diff --git a/Firmware/OpeNITHM/AirSensor.h b/Firmware/OpeNITHM/AirSensor.h index 2aa1266..8591dd3 100644 --- a/Firmware/OpeNITHM/AirSensor.h +++ b/Firmware/OpeNITHM/AirSensor.h @@ -18,6 +18,7 @@ class AirSensor void changeLight(int light); void turnOffLight(); + uint8_t analogSensitivity; int thresholds[6]; int calibrationSamples[6]; int skippedSamples[6]; @@ -28,9 +29,7 @@ class AirSensor bool calibrated[6]; bool allCalibrated; bool digitalMode; - - uint16_t deadzone; - float alpha; + public: AirSensor(int requiredSamples, int skippedSamples); bool isCalibrated(); @@ -40,11 +39,7 @@ class AirSensor float getHandPosition(); uint8_t getSensorReadings(); bool getSensorCalibrated(int i); - - void setDeadzone(int deadzone); - void setAlpha(float alpha); - int getDeadzone(); - float getAlpha(); + void setAnalogSensitivity(uint8_t analogSensitivity); void recalibrate(); }; diff --git a/Firmware/OpeNITHM/AutoTouchboard.cpp b/Firmware/OpeNITHM/AutoTouchboard.cpp index 920a55d..de4ae05 100644 --- a/Firmware/OpeNITHM/AutoTouchboard.cpp +++ b/Firmware/OpeNITHM/AutoTouchboard.cpp @@ -143,7 +143,7 @@ void AutoTouchboard::calibrateKeys(bool forceCalibrate = false) leds[i] = CRGB::Green; FastLED.show(); - uint16_t window = (key_values[i] - baselines[i]) * 0.76; + uint16_t window = (key_values[i] - baselines[i]) * (sensitivity / 100); single_thresholds[i] = baselines[i] + window; double_thresholds[i] = baselines[i] + (2 * window); } @@ -176,6 +176,11 @@ uint16_t AutoTouchboard::getRawValue(int key) return key_values[key]; } +void AutoTouchboard::setSensitivity(uint8_t sensitivity) +{ + this->sensitivity = sensitivity; +} + AutoTouchboard::AutoTouchboard() #ifndef TEENSY sensor(CapacitiveSensor(SEND, RECEIVE_1, RECEIVE_2)), @@ -184,6 +189,7 @@ AutoTouchboard::AutoTouchboard() pinMode(MUX_0, OUTPUT); pinMode(MUX_1, OUTPUT); pinMode(MUX_2, OUTPUT); + sensitivity = 76; calibrateKeys(); } diff --git a/Firmware/OpeNITHM/AutoTouchboard.h b/Firmware/OpeNITHM/AutoTouchboard.h index 1a66803..8db4429 100644 --- a/Firmware/OpeNITHM/AutoTouchboard.h +++ b/Firmware/OpeNITHM/AutoTouchboard.h @@ -24,7 +24,7 @@ class AutoTouchboard #ifndef TEENSY CapacitiveSensor sensor; #endif - + uint8_t sensitivity; uint16_t key_values[16]; uint16_t single_thresholds[16]; uint16_t double_thresholds[16]; @@ -37,6 +37,7 @@ class AutoTouchboard KeyState update(int key); uint16_t getRawValue(int key); void calibrateKeys(bool forceCalibrate = false); + void setSensitivity(uint8_t sensitivity); }; #endif diff --git a/Firmware/OpeNITHM/OpeNITHM.ino b/Firmware/OpeNITHM/OpeNITHM.ino index a136107..ba9156e 100644 --- a/Firmware/OpeNITHM/OpeNITHM.ino +++ b/Firmware/OpeNITHM/OpeNITHM.ino @@ -8,17 +8,21 @@ #ifdef LED_SERIAL #include "SerialLeds.h" -byte serialBuffer[200]; +byte serialBuffer[100]; SerialLeds serialLeds; #endif #include "AirSensor.h" #include "AutoTouchboard.h" +#include "SerialProcessor.h" #include +SerialProcessor serialProcessor; + KeyState key_states[16]; CRGB leds[16]; bool updateLeds = false; +bool useSerialLeds = true; CRGB led_on = CRGB::Purple; CRGB led_off = CRGB::Yellow; @@ -30,8 +34,6 @@ AutoTouchboard *touchboard; AirSensor *sensor; Output *output; -char command[32]; - void parseCommand() { char input1 = Serial.read(); @@ -63,13 +65,7 @@ void parseCommand() while (!Serial.available()); input2 = Serial.read(); switch (input2) - { - case 'd': // dead zone - sensor->setDeadzone(Serial.parseInt()); - break; - case 'a': // alpha - sensor->setAlpha(Serial.parseFloat()); - break; + { case 'c': // calibrate sensor->recalibrate(); } @@ -81,10 +77,6 @@ void parseCommand() activated = true; break; case 'g': // print values - Serial.print("id \t"); - Serial.println(sensor->getDeadzone()); - Serial.print("ia \t"); - Serial.println(sensor->getAlpha()); Serial.print("lor \t"); Serial.println(led_on.r); Serial.print("log \t"); @@ -184,7 +176,7 @@ void setup() { // Display the number of air sensors that were calibrated for (CRGB& led : leds) - led = CFGB::Black; + led = CRGB::Black; for (int i = 0; i < 6; i++) { @@ -199,7 +191,7 @@ void setup() { // Set LEDs blue for "ready" for (CRGB& led : leds) - led = CFGB::Blue; + led = CRGB::Blue; FastLED.show(); @@ -212,30 +204,21 @@ void setup() { } void loop() { - -#ifndef LED_SERIAL - // Config commands can only be sent when the LEDs aren't being updated via serial - if (Serial.available()) - { - parseCommand(); - } - - updateLeds = true; -#else - // Wait until have at least one message + // Check for serial messages if (Serial.available() >= 100) { Serial.readBytes(serialBuffer, 100); - updateLeds = serialLeds.processBulk(serialBuffer, 100); + serialProcessor.processBulk(serialBuffer); } - else - updateLeds = false; -#endif // If currently paused through a config command, do not execute main loop if (!activated) return; + // If we're not using serial LEDs, just update the lights every loop + if (!useSerialLeds) + updateLeds = true; + // Scan touch keyboard and update lights touchboard->scan(); int index = 0; @@ -247,36 +230,37 @@ void loop() { #else index = 15 - i; #endif - if (lightIntensity[index] > 0.05f) - lightIntensity[index] -= 0.05f; KeyState keyState = touchboard->update(i); - // If the key is currently being held, set its color to purple - if (keyState == SINGLE_PRESS || - keyState == DOUBLE_PRESS) + // handle changing key colors for non-serial LED updates + if (!useSerialLeds) { -#ifndef LED_SERIAL - lightIntensity[index] = 1.0f; - leds[index].setRGB(min(led_on.r / 2 + led_on.r / 2 * lightIntensity[index], 255), min(led_on.g / 2 + led_on.g / 2 * lightIntensity[index], 255), min(led_on.b / 2 + led_on.b / 2 * lightIntensity[index], 255)); -#endif + if (lightIntensity[index] > 0.05f) + lightIntensity[index] -= 0.05f; + + // If the key is currently being held, set its color to the on color + if (keyState == SINGLE_PRESS || keyState == DOUBLE_PRESS) + { + lightIntensity[index] = 1.0f; + leds[index].setRGB(min(led_on.r / 2 + led_on.r / 2 * lightIntensity[index], 255), min(led_on.g / 2 + led_on.g / 2 * lightIntensity[index], 255), min(led_on.b / 2 + led_on.b / 2 * lightIntensity[index], 255)); + } + else + { + // If not, make it the off color + leds[index].setRGB(led_off.r / 2, led_off.g / 2, led_off.b / 2); + } } - else + // handle changing key colors for serial LED updates + else { -#ifndef LED_SERIAL - // If not, make it yellow and send the "key released" event if it was previously pressed - leds[index].setRGB(led_off.r / 2, led_off.g / 2, led_off.b / 2); -#endif + if (updateLeds) + { + RGBLed temp = serialLeds.getKey(i); + leds[index].setRGB(temp.r, temp.g, temp.b); + } } -#ifdef LED_SERIAL - if (updateLeds) - { - RGBLed temp = serialLeds.getKey(i); - leds[index].setRGB(temp.r, temp.g, temp.b); - } -#endif - #if !defined(SERIAL_PLOT) && defined(USB) if (key_states[i] != keyState) output->sendKeyEvent(i, keyState); @@ -329,5 +313,8 @@ void loop() { // If the air sensor is calibrated, update lights. The lights will stay red as long as the air sensor is not calibrated. if (sensor->isCalibrated() && updateLeds) + { FastLED.show(); + updateLeds = false; + } } diff --git a/Firmware/OpeNITHM/SerialLeds.cpp b/Firmware/OpeNITHM/SerialLeds.cpp index c8de0f9..4ec0eee 100644 --- a/Firmware/OpeNITHM/SerialLeds.cpp +++ b/Firmware/OpeNITHM/SerialLeds.cpp @@ -3,90 +3,15 @@ SerialLeds::SerialLeds() { } -bool SerialLeds::process(byte in) +void SerialLeds::processBulk(uint8_t *buf) { - bool readyToProcess = false; - switch (currentState) + if (memcmp(buf + 2, lastBuffer + 2, 98) != 0) { - case headerSearch: - // Search for header - if (in == 0xAA) - { - dataBuffer[0] = in; - currentState = headerConfirm; - } - break; - case headerConfirm: - // Confirm header - if (in == 0xAA) - { - dataBuffer[1] = in; - currentState = dataRead; - currentReadNum = 2; - } - else - { - currentState = headerSearch; - } - break; - case dataRead: - { - dataBuffer[currentReadNum] = in; - currentReadNum++; - if (currentReadNum == 100) - { - if (memcmp(dataBuffer, lastBuffer, 100) != 0) - readyToProcess = true; - memcpy(lastBuffer, dataBuffer, 100); - currentState = headerSearch; - } - } - break; + memcpy(ledData.raw, buf + 2, 96); + updateLeds = true; } - - return readyToProcess; -} - -bool SerialLeds::processBulk(uint8_t *buf, size_t length) -{ - bool readyToProcess = false; - currentState = headerSearch; - - for (int i = 0; i < length; i++) - { - switch (currentState) - { - case headerSearch: - if (buf[i] == 0xAA) - { - currentState = headerConfirm; - dataBuffer[0] = buf[i]; - } - break; - case headerConfirm: - if (buf[i] == 0xAA) - { - currentState = dataRead; - dataBuffer[1] = buf[i]; - } - else - { - currentState = headerSearch; - } - break; - case dataRead: - memcpy(dataBuffer + 2, buf + i, 98); - if (memcmp(dataBuffer, lastBuffer, 100) != 0) - { - memcpy(ledData.raw, dataBuffer + 2, 96); - readyToProcess = true; - } - memcpy(lastBuffer, dataBuffer, 100); - currentState = headerSearch; - return readyToProcess; - } - } - return readyToProcess; + + memcpy(lastBuffer, buf, 100); } // Left to Right diff --git a/Firmware/OpeNITHM/SerialLeds.h b/Firmware/OpeNITHM/SerialLeds.h index c97442c..9d13288 100644 --- a/Firmware/OpeNITHM/SerialLeds.h +++ b/Firmware/OpeNITHM/SerialLeds.h @@ -7,6 +7,8 @@ #include "WProgram.h" #endif +extern bool updateLeds; + typedef struct { uint8_t b; uint8_t r; @@ -16,22 +18,11 @@ typedef struct { class SerialLeds { private: - enum state - { - headerSearch, - headerConfirm, - dataRead - }; - - uint8_t dataBuffer[100]; uint8_t lastBuffer[100]; - state currentState = headerSearch; - uint8_t currentReadNum = 0; public: SerialLeds(); - bool process(uint8_t in); - bool processBulk(uint8_t *buf, size_t length); + void processBulk(uint8_t *buf); RGBLed getKey(uint8_t key); // Left to Right union { diff --git a/Firmware/OpeNITHM/SerialProcessor.cpp b/Firmware/OpeNITHM/SerialProcessor.cpp new file mode 100644 index 0000000..d878162 --- /dev/null +++ b/Firmware/OpeNITHM/SerialProcessor.cpp @@ -0,0 +1,61 @@ +#include "SerialProcessor.h" + +SerialProcessor::SerialProcessor() +{ } + +void SerialProcessor::processConfigCommand(uint8_t* buf) +{ + // figure out which command we're running + switch (buf[2]) + { + case CMD_CHANGE_LIGHT_MODE: + if (buf[3] == LIGHT_MODE_SERIAL) + { + useSerialLeds = true; + + // Set LEDs blue for "ready" + for (CRGB& led : leds) + led = CRGB::Blue; + + FastLED.show(); + } + else if (buf[3] == LIGHT_MODE_REACTIVE) + { + useSerialLeds = false; + } + + break; + case CMD_CHANGE_ON_COLOR: + led_on.r = buf[3]; + led_on.g = buf[4]; + led_on.b = buf[5]; + break; + case CMD_CHANGE_OFF_COLOR: + led_off.r = buf[3]; + led_off.g = buf[4]; + led_off.b = buf[5]; + break; + case CMD_CALIBRATE_SLIDER: + touchboard->setSensitivity(buf[3]); + touchboard->calibrateKeys(true); + break; + case CMD_CALIBRATE_AIR_SENSORS: + sensor->setAnalogSensitivity(buf[3]); + sensor->recalibrate(); + break; + default: + break; + } +} + +void SerialProcessor::processBulk(uint8_t *buf) +{ + if (buf[0] == LED_FLAG && buf[1] == LED_FLAG) + { + serialLeds.processBulk(buf); + } + else if (buf[0] == CONFIG_FLAG && buf[1] == CONFIG_FLAG) + { + processConfigCommand(buf); + } +} diff --git a/Firmware/OpeNITHM/SerialProcessor.h b/Firmware/OpeNITHM/SerialProcessor.h new file mode 100644 index 0000000..3551575 --- /dev/null +++ b/Firmware/OpeNITHM/SerialProcessor.h @@ -0,0 +1,45 @@ +// SerialProcessor.h + +#ifndef _SERIALPROCESSOR_h +#define _SERIALPROCESSOR_h + +#include "AirSensor.h" +#include "AutoTouchboard.h" +#include "Config.h" +#include "SerialLeds.h" + +#include + +// magic bytes we look for in the packet headers to identify whether it's LED updates or config commands +#define LED_FLAG 0xAA +#define CONFIG_FLAG 0xBB + +// config command opcodes +#define CMD_CHANGE_LIGHT_MODE 0x11 +#define CMD_CHANGE_ON_COLOR 0x22 +#define CMD_CHANGE_OFF_COLOR 0x33 +#define CMD_CALIBRATE_SLIDER 0x44 +#define CMD_CALIBRATE_AIR_SENSORS 0x55 + +// light modes for the change light mode command +#define LIGHT_MODE_SERIAL 0x00 +#define LIGHT_MODE_REACTIVE 0x01 + +extern AirSensor *sensor; +extern AutoTouchboard *touchboard; +extern SerialLeds serialLeds; +extern bool useSerialLeds; +extern CRGB leds[16]; +extern CRGB led_on; +extern CRGB led_off; + +class SerialProcessor +{ + private: + void processConfigCommand(uint8_t* buf); + public: + SerialProcessor(); + void processBulk(uint8_t* buf); +}; + +#endif