Here the code (diagnostic program for the smart hand controller in project Onstep -
Code: Select all
/// A diagnostic exerciser and dumb handbox emulator for the OnStep Project ESP32-based Smart Hand Controller (SHC)
//
// Author: Dave Schwartz
//
// Revision history:
// 1.0 Initial version
// 1.1 Switched from interrupt-driven to input scanning - more flexible for multiple lines
// 1.2 Added use of OLED display using U8G2 library in addition to serial port output
// 1.3 Added pass-through line monitoring and user LED flash when activity detected
// 1.4 Added control of ST4 output signals to create "dumb handbox emulation" mode
// 1.5 Change RA+ output from D12 to D23
//
// Released under the GPL3 including later versions
#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>
// Type of Display
// Uncomment one of the following. The SSD1306 is the 0.96" OLED and SH1106 is the 1.3" OLED
//U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); // 0.96"
U8G2_SH1106_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); // 1.3"
// Do not change anything below this line
#define SPLASH_LINE_1 "ESP32 SHC"
#define SPLASH_LINE_2 "Diagnostic 1.5"
#define SPLASH_MILLIS 5000
#define USER_LED_PIN 2
#define USER_LED_MILLIS 100
#define UP 0
#define DOWN 1
#define DEBOUNCING 2
#define DEBOUNCE_MILLIS 20
struct inputLine {
uint8_t pin; // ESP32 GPIO for input
const char* lineName; // label for display
uint32_t transMillis; // millisecond counter when state transition first detected
int state; // UP, DOWN or DEBOUNCING
unsigned int xPos; // X position of label on OLED
unsigned int yPos; // Y position of label on OLED
uint8_t outPin; // ST4 output signal GPIO if state is to be passed through to OnStep controller (0 == no passthrough)
};
inputLine inputLines[] = {
{25, "N", 0, UP, 35, 11, 14},
{36, "E", 0, UP, 5, 26, 23},
{33, "SHIFT", 0, UP, 20, 26, 0},
{34, "W", 0, UP, 60, 26, 26},
{32, "S", 0, UP, 35, 41, 27},
{39, "F1", 0, UP, 0, 56, 0},
{35, "F2", 0, UP, 60, 56, 0},
{19, "RA1+", 0, UP, 100, 11, 23},
{18, "DE1+", 0, UP, 100, 26, 14},
{17, "DE1-", 0, UP, 100, 41, 27},
{5, "RA1-", 0, UP, 100, 56, 26}
};
// inputLine inputLines[] = {
// {25, "N", 0, UP, 35, 11, 0},
// {36, "E", 0, UP, 5, 26, 0},
// {33, "SHIFT", 0, UP, 20, 26, 0},
// {34, "W", 0, UP, 60, 26, 0},
// {32, "S", 0, UP, 35, 41, 0},
// {39, "F1", 0, UP, 0, 56, 0},
// {35, "F2", 0, UP, 60, 56, 0},
// {19, "RA1+", 0, UP, 100, 11, 0},
// {18, "DE1+", 0, UP, 100, 26, 0},
// {17, "DE1-", 0, UP, 100, 41, 0},
// {5, "RA1-", 0, UP, 100, 56, 0}
// };
int nLines = sizeof( inputLines ) / sizeof( inputLine);
boolean ledOn;
uint32_t ledOffAt;
void turnLedOn() {
digitalWrite( USER_LED_PIN, HIGH );
ledOn = true;
ledOffAt = millis() + USER_LED_MILLIS;
}
void turnLedOff() {
digitalWrite( USER_LED_PIN, LOW );
ledOn = false;
}
void setup() {
// initialize the user LED and turn in on (the main loop will turn it off again)
pinMode( USER_LED_PIN, OUTPUT );
turnLedOn();
// Put all input lines into INPUT mode. No need to enable internal pullups because external pullup resistors are plenty strong.
for( int i=0; i<nLines; ++i ) {
pinMode( inputLines[i].pin, INPUT );
if( inputLines[i].outPin ) { // if input has an output pass-through, initialize it HIGH
pinMode( inputLines[i].outPin, OUTPUT );
digitalWrite( inputLines[i].outPin, HIGH );
}
}
Serial.begin( 115200 );
Serial.print( "Monitoring " );
Serial.print( nLines );
Serial.println( " lines:" );
u8g2.begin();
// Write the splash screen
u8g2.firstPage();
do {
u8g2.setFont(u8g2_font_ncenB10_tr);
u8g2.drawStr(0,24,SPLASH_LINE_1);
u8g2.drawStr(0,36,SPLASH_LINE_2);
} while ( u8g2.nextPage() );
// Go to a smaller font for the input status display
u8g2.setFont(u8g2_font_7x13B_mf);
}
void loop() {
// If LED is on, see if its time to turn it off...
if( ledOn && ( millis() > ledOffAt ) ) {
turnLedOff();
}
// Keep splash screen up for the a bit
if( millis() < SPLASH_MILLIS ) {
return;
}
// Scan the input lines...
for( int i=0; i<nLines; ++i ) {
// If a line is in debounce mode...
if( inputLines[i].state == DEBOUNCING ) {
// Pay attention to it only after the debounce timer for the line has expired...
if( millis() > inputLines[i].transMillis + DEBOUNCE_MILLIS ) {
// Debounce complete...
Serial.print( "Debounce on " );
Serial.print( inputLines[i].lineName );
Serial.print( " complete, state is now " );
// Set the line state, OLED label and pass-through output according to the logic level
if( digitalRead( inputLines[i].pin ) == LOW ) {
inputLines[i].state = DOWN;
Serial.println( "DOWN" );
if( inputLines[i].outPin ) {
digitalWrite( inputLines[i].outPin, LOW );
}
turnLedOn();
}
else {
inputLines[i].state = UP;
Serial.println( "UP" );
if( inputLines[i].outPin ) {
digitalWrite( inputLines[i].outPin, HIGH );
}
}
}
}
else {
// Not debouncing... check if a line has changed state...
if( ( inputLines[i].state == UP && digitalRead( inputLines[i].pin ) == LOW ) ||
( inputLines[i].state == DOWN && digitalRead( inputLines[i].pin ) == HIGH ) ) {
// State change detected... enter debouncing state and set debounce start timer for the line
Serial.print( "State change detected on " );
Serial.print( inputLines[i].lineName );
Serial.println( ", entering debounce period." );
inputLines[i].state = DEBOUNCING;
inputLines[i].transMillis = millis();
}
}
}
// Refresh the screen with the input line statuses
u8g2.firstPage();
do {
for( int i=0; i<nLines; ++i ) {
// When a line is low/button down, write label in inverse video
if( inputLines[i].state == DOWN ) {
u8g2.setDrawColor( 0 );
}
else {
u8g2.setDrawColor( 1 );
}
//* position the cursor and write the label
u8g2.drawStr( inputLines[i].xPos, inputLines[i].yPos, inputLines[i].lineName );
}
} while ( u8g2.nextPage() );
}