esp32-morse-decoder/esp32-morse-decoder.ino

151 wiersze
4.8 KiB
C++

/**
* ESP32 Morse decoder using trained machine learning model
*/
// DEFINES
// IS_TRAINING should be defined when gathering training data
// Otherwise, comment out to use the model to classify inputs
// #define IS_TRAINING
// INCLUDES
// See https://github.com/LennartHennigs/Button2
#include "src/Button2.h"
// See https://github.com/mathertel/LiquidCrystal_PCF8574
#include "src/LiquidCrystal_PCF8574.h"
// Uncomment when doing classification
#ifndef IS_TRAINING
#include "model.h"
#endif
// CONSTANTS
// This pin will be used to read dots and dashes - could be connected to any digital input that can be sustained - e.g. a mic, light sensor, or button
const byte inputPin = 16;
// This pin will be driven HIGH when input is being received
const byte buzzerPin = 5;
// This LED will light up when receiving character input
const byte ledPin = 2;
// Every letter can be represented by a maximum of four Morse dots/dashes (if including numbers, this needs to increase to five)
const byte patternLength = 4;
// The maximum allowed space between "dots" and "dashes" within the same character
// If the time elapsed since last input was received exceeds this value, we'll move onto the next character
const int intraCharacterPause = 500;
// GLOBALS
// A button object
Button2 button;
// The duration of inputs received in the current pattern
float pattern[patternLength];
// The time at which the last input was received
unsigned long lastReleaseTime;
// Counter of how many elements (dots/dashes) have been received in the current pattern
uint8_t counter;
// How many characters (a/b/c/...) have been received/decoded
uint8_t charsReceived;
// If we're not training...
#ifndef IS_TRAINING
// Grab a reference to the model's classifier function exported from SciKit-Learn
Eloquent::ML::Port::RandomForest classifier;
#endif
LiquidCrystal_PCF8574 lcd(0x27); //LCD address is 0x27 for PCF8574 and 0x3F for PCF8574A
void setup() {
// Initialise serial monitor connection
Serial.begin(115200);
Serial.println(__FILE__ __DATE__);
// Initialise LED and buzzer pins used for user feedback
pinMode(buzzerPin, OUTPUT);
digitalWrite(buzzerPin, LOW);
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);
// See http://playground.arduino.cc/Main/I2cScanner how to test for a I2C device.
Wire.begin();
lcd.begin(16, 2);
lcd.setBacklight(200);
lcd.print("Morse Decoder");
delay(1000);
lcd.clear();
// Configure the button input
// Anything less than 30ms we won't register as an input at all
button.setDebounceTime(30);
button.setPressedHandler(onButtonPress);
button.setReleasedHandler(onButtonRelease);
button.begin(inputPin);
}
// Called when a complete character pattern has been received
void onCharacterReceive() {
charsReceived++;
if(charsReceived % 16 == 0) {
lcd.setCursor(0, 1);
}
if(charsReceived % 32 == 0) {
lcd.clear();
lcd.setCursor(0, 0);
}
// If we're gathering training data
#ifdef IS_TRAINING
// Send the pattern input to the serial monitor
for(int i=0; i<patternLength; i++) {
Serial.print(pattern[i]);
Serial.print(i==patternLength-1 ? '\n' : ',');
}
// If we're applying the classifier function
#else
// Return the classifier's prediction
char* result = (char*)classifier.predictLabel(pattern);
// Send to the serial monitor
Serial.println(classifier.predictLabel(pattern));
// Send to the LCD display
lcd.print(result);
#endif
// Reset the counter and pattern array
counter = 0;
memset(pattern, 0, sizeof(pattern));
// Turn LED off to show we have finished reading input
digitalWrite(ledPin, LOW);
}
// When any sort of input is received
void onButtonPress(Button2& btn) {
// Turn the LED on when we're recording input
digitalWrite(ledPin, HIGH);
// Make the buzzer sound
digitalWrite(buzzerPin, HIGH);
}
// When any sort of input ends
void onButtonRelease(Button2& btn) {
// Record how long the most recent input lasted for
unsigned int duration = btn.wasPressedFor();
// Store it in the pattern array
pattern[counter] = duration;
// Store the current time
lastReleaseTime = millis();
// Increment the index in the pattern array
counter++;
// If we've recorded the maximum number of dots/dashes, we know the input
// must have ended
if(counter == patternLength) {
onCharacterReceive();
}
// Turn the buzzer off
digitalWrite(buzzerPin, LOW);
}
void loop() {
// Read button input
button.loop();
// If we've received at least one dot/dash, but user is not currently inputting anything, and the time elapsed
// since last input was released exceeds threshold
if(!button.isPressed() && counter > 0 && millis() - lastReleaseTime > intraCharacterPause) {
// Assume the character has ended
onCharacterReceive();
}
}