// =============================
// Scope, Description, Overview
// =============================
/*
* The objective of this program is to make the Arduino Nano generate quadrature 90 degrees offset pulse trains on two channels in order to mimic the output of an
* ATEX/intrinsically safe, optical/NAMUR Hohner "SERIE E"/Series E (SPE-15) encoder that we use for depth measurement in EX-zone. (Internal company P/N: 104725).
* The user will be able to specify the direction in which the simulated distance will go: "Up" increases elapsed distance,
* "Down" decreases elapsed distance (negative if already at zero), and "Both" will have the distance travel up at first, then back down to zero again.
* Most important params: distance, speed & direction. Highest speed is 100m/min and 1000m distance. Desirable distances to test are 10, 100, 300, 500 and 1000m.
* Code will be run on an Raspberry Pi Pico (RP2040) with a 20x4 character LCD display (i2c), and a push-button rotary encoder for user input and control.
* https://www.encoder-hohner.com/product-detail/series-e/
*/
/* PREPROCESSOR */
/* LIBRARIES */
#include <Arduino.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <BlockNot.h> /* https://github.com/EasyG0ing1/BlockNot */
#include <OneButton.h> /* https://github.com/mathertel/OneButton */
/* PINS & OBJECT SETTINGS */
// Timing
BlockNot globalTimer(20); // 20ms non-blocking timer
// LCD
#define LCD_I2C_ADDR 0x27 // LCD i2c address
#define LCD_COLUMNS 20 // Number of LCD columns
#define LCD_ROWS 4 // Number of LCD rows
LiquidCrystal_I2C lcd(LCD_I2C_ADDR, LCD_COLUMNS, LCD_ROWS);
// GPIO
#define OUTPUT_CH_A 7 // YELLOW LED
#define OUTPUT_CH_B 6 // BLUE LED
#define ENC_CLK 0 // Rotary encoder pulse input channel A pin
#define ENC_DT 1 // Rotary encoder pulse input channel B pin
#define ENC_SW 2 // Rotary encoder pushbutton pin
// Serial
#define SERIAL_BAUD_RATE 115200 // Serial monitor speed
#define NUMBER_OF_COMMANDS 4 // Total number of commands/size of serial command buffer
#define COMMAND_MAX_LENGTH 6 // Max number of serial command characters
// =================
// Global variables
// =================
// LCD & LCD menu variables
int8_t lcdRows = 4;
int8_t lcdColumns = 20;
int8_t menuCounter = -2; // Determines the cursor row position in the LCD menu
int8_t currentMenu = 0; // The current menu shown: 0 = main, 1 = secondary
int8_t menuCounterMin = 0; // Menu's uppermost cursor position (LCD row)
int8_t menuCounterMax = (LCD_ROWS - 1); // Menu's lowermost cursor position (LCD row)
const String cursor = ">"; // Menu cursor
bool runFinished = false; // Flag to indicate if the run is completed
// Serial monitor commands variables
char cmdBuffer[COMMAND_MAX_LENGTH]; // Command buffer
// Other variables
int16_t distElapsed = 0; //
// =======
// Arrays
// =======
// Array structures
struct SerialCommand // Define serial commands structure
{
const char *cmd; // Command
const char *desc; // Command description
};
struct MenuItem // Define menu item array structure
{
String label; // Menu item name
String value; // Menu item value
};
// Declare array
SerialCommand serialCommands[NUMBER_OF_COMMANDS] = { // Serial commands array, easily expandable
{"help", " - Shows all available commands"},
{"quit", " - Returns to main menu"},
{"poll", " - Displays multiple variables' current values"}};
MenuItem mainMenuItems[] = { // Main menu array
{"Pulse pr m: ", ""},
{"Distance : ", ""},
{"Speed : ", ""},
{"Continue... ", ""}};
MenuItem secondMenuItems[] = { // Secondary menu array
{"Direction : ", ""},
{"Back ", ""},
{"Start ", ""}};
MenuItem runMenuItems[] = { // Run menu items
{"Target : ", ""},
{"Speed : ", ""},
{"Elapsed : ", ""},
{"Direction: ", ""}};
// Arrays containing available menu items
const uint16_t pulsesPerMeter[] = {1024, 512}; // Pulses per meter options, encoder type dependant, indexed by "ppmSel"
int8_t ppmSel = 1; // "pulsesPerMeter" index selection factor
const uint16_t distances[] = {10, 100, 300, 500, 1000}; // Distance options, meters, indexed by "distSel"
int8_t distSel = 0; // "distances" index selection factor
const uint8_t speeds[] = {10, 30, 50, 100}; // Speed options, meters per minute, indexed by "speedSel"
int8_t speedSel = 0; // "speeds" index selection factor
const String dir[] = {"Both", "Up", "Down"}; // Direction options, "Both" runs up first then down again, indexed by "dirSel"
int8_t dirSel = 1; // "dir" (direction) index selection factor: 0 = "BOTH", 1 = "UP", 2 = "DOWN"
// Encoder + Button
int prevCLK;
int encoderCount = 0;
OneButton btn; // Button object
// Function prototypes
void debug();
bool checkEncoder();
void onClick();
void longPress();
int minMaxReset(int input, int min, int max);
void lcdClearLn(uint8_t clearRow, uint8_t clearColumn, uint8_t lcdMaxNumOfColumns);
void splashScreen(uint8_t pinA, uint8_t pinB, unsigned int blinkCount, unsigned int blinkSpeed);
void showMenu(const uint8_t menuType);
void moveCursor(uint8_t menuPos);
void setup() {
// Config pins
pinMode(ENC_CLK, INPUT); // Rotary encoder input channel A
pinMode(ENC_DT, INPUT); // Rotary encoder input channel B
pinMode(OUTPUT_CH_A, OUTPUT); // Encoder pulse simulation output channel A
pinMode(OUTPUT_CH_B, OUTPUT); // Encoder pulse simulation output channel B
btn.setup(ENC_SW, INPUT_PULLUP, true);
btn.attachClick(onClick);
btn.attachLongPressStart(longPress); // Fires as soon as the button is held down for 1 second
Serial.begin(SERIAL_BAUD_RATE);
Serial.println("Serial comms OK!");
lcd.init();
lcd.backlight();
splashScreen(OUTPUT_CH_A, OUTPUT_CH_B, 10, 70);
delay(1000);
// Initialize previous states
prevCLK = digitalRead(ENC_CLK);
// Set up serial monitor for debugging and testing
Serial.begin(SERIAL_BAUD_RATE);
Serial.println(F("DEEPBox CLI Initiated!"));
Serial.println(F("Enter a valid command to get started"));
Serial.println(F("Or enter command 'help' to see a list of all available commands..."));
lcd.clear();
showMenu(0);
} // setup
void loop() {
if (checkEncoder()) { // If the encoder changed position, do something
moveCursor(menuCounter);
Serial.println("menuCounter: " + (String)menuCounter);
}
if (globalTimer.TRIGGERED) {
debug();
}
delay(1); // delay for simulation/Wokwi, not need in real world!
} // loop