diff --git a/esp32-morse-decoder.ino b/esp32-morse-decoder.ino index 351a464..e7c3eaa 100644 --- a/esp32-morse-decoder.ino +++ b/esp32-morse-decoder.ino @@ -10,6 +10,8 @@ // 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" @@ -26,7 +28,7 @@ const byte ledPin = 2; 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 = 800; +const int intraCharacterPause = 500; // GLOBALS // A button object @@ -35,13 +37,16 @@ Button2 button; float pattern[patternLength]; // The time at which the last input was received unsigned long lastReleaseTime; -// Counter of how many elements have been received in the current pattern +// 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 @@ -54,6 +59,14 @@ void setup() { 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); @@ -64,6 +77,14 @@ void setup() { // 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 @@ -74,8 +95,12 @@ void onCharacterReceive() { } // If we're applying the classifier function #else - // Send the model's prediction to the serial monitor + // 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 diff --git a/model.h b/model.h index 1729d80..e21b00a 100644 --- a/model.h +++ b/model.h @@ -4823,7 +4823,7 @@ namespace Eloquent { case 25: return "z"; default: - return "Houston we have a problem"; + return " "; } } diff --git a/src/LiquidCrystal_PCF8574.cpp b/src/LiquidCrystal_PCF8574.cpp new file mode 100644 index 0000000..3fc154d --- /dev/null +++ b/src/LiquidCrystal_PCF8574.cpp @@ -0,0 +1,354 @@ +/// \file LiquidCrystal_PCF8574.cpp +/// \brief LiquidCrystal library with PCF8574 I2C adapter. +/// +/// \author Matthias Hertel, http://www.mathertel.de +/// \copyright Copyright (c) 2019 by Matthias Hertel. +/// +/// ChangeLog see: LiquidCrystal_PCF8574.h + +#include "LiquidCrystal_PCF8574.h" + +#include + +LiquidCrystal_PCF8574::LiquidCrystal_PCF8574(uint8_t i2cAddr) +{ + // default pin assignment + init(i2cAddr, 0, 1, 2, 4, 5, 6, 7, 3); +} // LiquidCrystal_PCF8574 + +// constructors, which allows to redefine bit assignments in case your adapter is wired differently +LiquidCrystal_PCF8574::LiquidCrystal_PCF8574(uint8_t i2cAddr, uint8_t rs, uint8_t enable, + uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7, uint8_t backlight) +{ + init(i2cAddr, rs, 255, enable, d4, d5, d6, d7, backlight); +} // LiquidCrystal_PCF8574 + +LiquidCrystal_PCF8574::LiquidCrystal_PCF8574(uint8_t i2cAddr, uint8_t rs, uint8_t rw, uint8_t enable, + uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7, uint8_t backlight) +{ + init(i2cAddr, rs, rw, enable, d4, d5, d6, d7, backlight); +} // LiquidCrystal_PCF8574 + +void LiquidCrystal_PCF8574::init(uint8_t i2cAddr, uint8_t rs, uint8_t rw, uint8_t enable, + uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7, uint8_t backlight) +{ + _i2cAddr = i2cAddr; + _backlight = 0; + + _entrymode = 0x02; // like Initializing by Internal Reset Circuit + _displaycontrol = 0x04; + + _rs_mask = 0x01 << rs; + if (rw != 255) + _rw_mask = 0x01 << rw; + else + _rw_mask = 0; + _enable_mask = 0x01 << enable; + _data_mask[0] = 0x01 << d4; + _data_mask[1] = 0x01 << d5; + _data_mask[2] = 0x01 << d6; + _data_mask[3] = 0x01 << d7; + + if (backlight != 255) + _backlight_mask = 0x01 << backlight; + else + _backlight_mask = 0; +} // init() + + +void LiquidCrystal_PCF8574::begin(uint8_t cols, uint8_t lines, TwoWire &wirePort) +{ + _i2cPort = &wirePort; //Grab which port the user wants us to use + + _cols = min(cols, (uint8_t)80); + _lines = min(lines, (uint8_t)4); + + uint8_t functionFlags = 0; + + _row_offsets[0] = 0x00; + _row_offsets[1] = 0x40; + _row_offsets[2] = 0x00 + cols; + _row_offsets[3] = 0x40 + cols; + + if (lines > 1) { + functionFlags |= 0x08; + } + + // initializing the display + _i2cPort->begin(); + _write2Wire(0x00, LOW, false); + delayMicroseconds(50000); + + // after reset the mode is this + _displaycontrol = 0x04; + _entrymode = 0x02; + + // sequence to reset. see "Initializing by Instruction" in datasheet + _sendNibble(0x03); + delayMicroseconds(4500); + _sendNibble(0x03); + delayMicroseconds(200); + _sendNibble(0x03); + delayMicroseconds(200); + _sendNibble(0x02); // finally, set to 4-bit interface + + // Instruction: Function set = 0x20 + _send(0x20 | functionFlags); + + display(); + clear(); + leftToRight(); +} // begin() + + +void LiquidCrystal_PCF8574::clear() +{ + // Instruction: Clear display = 0x01 + _send(0x01); + delayMicroseconds(1600); // this command takes 1.5ms! +} // clear() + + +void LiquidCrystal_PCF8574::home() +{ + // Instruction: Return home = 0x02 + _send(0x02); + delayMicroseconds(1600); // this command takes 1.5ms! +} // home() + + +/// Set the cursor to a new position. +void LiquidCrystal_PCF8574::setCursor(uint8_t col, uint8_t row) +{ + // check boundaries + if ((col < _cols) && (row < _lines)) { + // Instruction: Set DDRAM address = 0x80 + _send(0x80 | (_row_offsets[row] + col)); + } +} // setCursor() + + +// Turn the display on/off (quickly) +void LiquidCrystal_PCF8574::noDisplay() +{ + // Instruction: Display on/off control = 0x08 + _displaycontrol &= ~0x04; // display + _send(0x08 | _displaycontrol); +} // noDisplay() + + +void LiquidCrystal_PCF8574::display() +{ + // Instruction: Display on/off control = 0x08 + _displaycontrol |= 0x04; // display + _send(0x08 | _displaycontrol); +} // display() + + +// Turns the underline cursor on/off +void LiquidCrystal_PCF8574::cursor() +{ + // Instruction: Display on/off control = 0x08 + _displaycontrol |= 0x02; // cursor + _send(0x08 | _displaycontrol); +} // cursor() + + +void LiquidCrystal_PCF8574::noCursor() +{ + // Instruction: Display on/off control = 0x08 + _displaycontrol &= ~0x02; // cursor + _send(0x08 | _displaycontrol); +} // noCursor() + + +// Turn on and off the blinking cursor +void LiquidCrystal_PCF8574::blink() +{ + // Instruction: Display on/off control = 0x08 + _displaycontrol |= 0x01; // blink + _send(0x08 | _displaycontrol); +} // blink() + + +void LiquidCrystal_PCF8574::noBlink() +{ + // Instruction: Display on/off control = 0x08 + _displaycontrol &= ~0x01; // blink + _send(0x08 | _displaycontrol); +} // noBlink() + + +// These commands scroll the display without changing the RAM +void LiquidCrystal_PCF8574::scrollDisplayLeft(void) +{ + // Instruction: Cursor or display shift = 0x10 + // shift: 0x08, left: 0x00 + _send(0x10 | 0x08 | 0x00); +} // scrollDisplayLeft() + + +void LiquidCrystal_PCF8574::scrollDisplayRight(void) +{ + // Instruction: Cursor or display shift = 0x10 + // shift: 0x08, right: 0x04 + _send(0x10 | 0x08 | 0x04); +} // scrollDisplayRight() + + +// == controlling the entrymode + +// This is for text that flows Left to Right +void LiquidCrystal_PCF8574::leftToRight(void) +{ + // Instruction: Entry mode set, set increment/decrement =0x02 + _entrymode |= 0x02; + _send(0x04 | _entrymode); +} // leftToRight() + + +// This is for text that flows Right to Left +void LiquidCrystal_PCF8574::rightToLeft(void) +{ + // Instruction: Entry mode set, clear increment/decrement =0x02 + _entrymode &= ~0x02; + _send(0x04 | _entrymode); +} // rightToLeft() + + +// This will 'right justify' text from the cursor +void LiquidCrystal_PCF8574::autoscroll(void) +{ + // Instruction: Entry mode set, set shift S=0x01 + _entrymode |= 0x01; + _send(0x04 | _entrymode); +} // autoscroll() + + +// This will 'left justify' text from the cursor +void LiquidCrystal_PCF8574::noAutoscroll(void) +{ + // Instruction: Entry mode set, clear shift S=0x01 + _entrymode &= ~0x01; + _send(0x04 | _entrymode); +} // noAutoscroll() + + +/// Setting the brightness of the background display light. +/// The backlight can be switched on and off. +/// The current brightness is stored in the private _backlight variable to have it available for further data transfers. +void LiquidCrystal_PCF8574::setBacklight(uint8_t brightness) +{ + _backlight = brightness; + // send no data but set the background-pin right; + _write2Wire(0x00, true, false); +} // setBacklight() + + +// Allows us to fill the first 8 CGRAM locations +// with custom characters +void LiquidCrystal_PCF8574::createChar(uint8_t location, uint8_t charmap[]) +{ + location &= 0x7; // we only have 8 locations 0-7 + // Set CGRAM address + _send(0x40 | (location << 3)); + for (uint8_t i = 0; i < 8; i++) { + write(charmap[i]); + } +} // createChar() + + +#ifdef __AVR__ +// Allows us to fill the first 8 CGRAM locations +// with custom characters stored in PROGMEM +void LiquidCrystal_PCF8574::createChar_P(uint8_t location, const uint8_t *charmap) { + PGM_P p = reinterpret_cast(charmap); + location &= 0x7; // we only have 8 locations 0-7 + _send(0x40 | (location << 3)); + for (int i = 0; i < 8; i++) { + uint8_t c = pgm_read_byte(p++); + write(c); + } +} // createChar_P() +#endif + + +/* The write function is needed for derivation from the Print class. */ +inline size_t LiquidCrystal_PCF8574::write(uint8_t ch) +{ + _send(ch, true); + return 1; // assume success +} // write() + + +// write either command or data +void LiquidCrystal_PCF8574::_send(uint8_t value, bool isData) +{ + // An I2C transmission has a significant overhead of ~10+1 I2C clock + // cycles. We consequently only perform it only once per _send(). + + _i2cPort->beginTransmission(_i2cAddr); + // write high 4 bits + _writeNibble((value >> 4 & 0x0F), isData); + // write low 4 bits + _writeNibble((value & 0x0F), isData); + _i2cPort->endTransmission(); +} // _send() + + +// write a nibble / halfByte with handshake +void LiquidCrystal_PCF8574::_writeNibble(uint8_t halfByte, bool isData) +{ + // map the data to the given pin connections + uint8_t data = isData ? _rs_mask : 0; + // _rw_mask is not used here. + if (_backlight > 0) + data |= _backlight_mask; + + // allow for arbitrary pin configuration + if (halfByte & 0x01) data |= _data_mask[0]; + if (halfByte & 0x02) data |= _data_mask[1]; + if (halfByte & 0x04) data |= _data_mask[2]; + if (halfByte & 0x08) data |= _data_mask[3]; + + // Note that the specified speed of the PCF8574 chip is 100KHz. + // Transmitting a single byte takes 9 clock ticks at 100kHz -> 90us. + // The 37us delay is only necessary after sending the second nibble. + // But in that case we have to restart the transfer using additional + // >10 clock cycles. Hence, no additional delays are necessary even + // when the I2C bus is operated beyond the chip's spec in fast mode + // at 400 kHz. + + _i2cPort->write(data | _enable_mask); + // delayMicroseconds(1); // enable pulse must be >450ns + _i2cPort->write(data); + // delayMicroseconds(37); // commands need > 37us to settle +} // _writeNibble + + +// write a nibble / halfByte with handshake +void LiquidCrystal_PCF8574::_sendNibble(uint8_t halfByte, bool isData) +{ + _i2cPort->beginTransmission(_i2cAddr); + _writeNibble(halfByte, isData); + _i2cPort->endTransmission(); +} // _sendNibble + + +// private function to change the PCF8574 pins to the given value +void LiquidCrystal_PCF8574::_write2Wire(uint8_t data, bool isData, bool enable) +{ + if (isData) + data |= _rs_mask; + // _rw_mask is not used here. + if (enable) + data |= _enable_mask; + if (_backlight > 0) + data |= _backlight_mask; + + _i2cPort->beginTransmission(_i2cAddr); + _i2cPort->write(data); + _i2cPort->endTransmission(); +} // write2Wire + +// The End. diff --git a/src/LiquidCrystal_PCF8574.h b/src/LiquidCrystal_PCF8574.h new file mode 100644 index 0000000..d72ce5c --- /dev/null +++ b/src/LiquidCrystal_PCF8574.h @@ -0,0 +1,134 @@ +/// \file LiquidCrystal_PCF8574.h +/// \brief LiquidCrystal library with PCF8574 I2C adapter. +/// +/// \author Matthias Hertel, http://www.mathertel.de +/// +/// \copyright Copyright (c) 2019 by Matthias Hertel.\n +/// +/// The library work is licensed under a BSD style license.\n +/// See http://www.mathertel.de/License.aspx +/// +/// \details +/// This library can drive a Liquid Crystal Display (LCD) based on the Hitachi HD44780 chip that is connected +/// through a PCF8574 I2C adapter. It uses the original Wire library for communication. +/// The API if common to many LCD libraries and documented in https://www.arduino.cc/en/Reference/LiquidCrystal. +/// and partially functions from https://playground.arduino.cc/Code/LCDAPI/. + +/// +/// ChangeLog: +/// -------- +/// * 19.10.2013 created. +/// * 05.06.2019 rewrite from scratch. +/// * 26.05.2022 8-bit datatypes in interfaces and compatibility topics. +/// * 26.05.2022 createChar with PROGMEM character data for AVR processors. +/// * 26.05.2022 constructor with pin assignments. Thanks to @markisch. + +#ifndef LiquidCrystal_PCF8574_h +#define LiquidCrystal_PCF8574_h + +#include "Arduino.h" +#include +#include "Print.h" +#include +#include + + +class LiquidCrystal_PCF8574 : public Print +{ +public: + LiquidCrystal_PCF8574(uint8_t i2cAddr); + // note: + // + // When using multiple I2C ports one can initialize with + // LiquidCrystal_PCF8574 lcd(0x27); // create lcd + // TwoWire myWire = TwoWire(); // create new wire instance + // myWire.begin(sdaPin, sclPin); // define SDA and SCL pins + // myWire.setClock(100000); // I2C speed 100kHz + // myWire.setClockStretchLimit(200000); // I2C clock stretch to 200ms for slow devices + // lcd_port = &myWire; // Keep address to wire instance + // lcd.begin(clos, rows, *lcd_port); // Initialize lcd + // + // and in the main program one updates display with + // # if defined(ESP8266) + // ESP8266 is special case because there is only one wire structure available + // Although we can created multiple wire interfaces we still need to specify SDA and SCL + // before each transmission: + // lcd_port->begin(sdaPin, sclPin); + // lcd_port->setClock(100000); + // lcd_port->setClockStretchLimit(200000); // 200ms, for slow devices + // # endif + // lcd.setCursor(0, 0); + // lcd.print(lcdbuf); + + // constructors, which allow to redefine bit assignments in case your adapter is wired differently + LiquidCrystal_PCF8574(uint8_t i2cAddr, uint8_t rs, uint8_t enable, + uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7, uint8_t backlight=255); + LiquidCrystal_PCF8574(uint8_t i2cAddr, uint8_t rs, uint8_t rw, uint8_t enable, + uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7, uint8_t backlight=255); + + // Functions from reference: + + void begin(uint8_t cols, uint8_t rows, TwoWire &wirePort = Wire); + + void clear(); + void home(); + void setCursor(uint8_t col, uint8_t row); + void cursor(); + void noCursor(); + void blink(); + void noBlink(); + void display(); + void noDisplay(); + void scrollDisplayLeft(); + void scrollDisplayRight(); + void autoscroll(); + void noAutoscroll(); + void leftToRight(); + void rightToLeft(); + void createChar(uint8_t location, uint8_t charmap[]); +#ifdef __AVR__ + void createChar_P(uint8_t, const uint8_t *); + inline void createChar(uint8_t n, const uint8_t *data) { + createChar_P(n, data); + }; +#endif + + // plus functions from LCDAPI: + void setBacklight(uint8_t brightness); + inline void command(uint8_t value) { _send(value); } + + // support of Print class + virtual size_t write(uint8_t ch); + +private: + + TwoWire *_i2cPort; //The generic connection to user's chosen I2C hardware + + // instance variables + uint8_t _i2cAddr; ///< Wire Address of the LCD + uint8_t _backlight; ///< the backlight intensity + uint8_t _cols; ///< number of cols of the display + uint8_t _lines; ///< number of lines of the display + uint8_t _entrymode; ///