#include <Servo.h>
#include <Adafruit_NeoPixel.h>
#include <EEPROM.h>
#define NEWIR
#include <IRremote.hpp>
//#define OLDIR
//#include <IRremote.h>
#include <MemoryUsage.h>
//CHOOSE ONE IR REMOTE FOR TESTING DEPENDING ON IR remote available
//#define AMAZON
//#define BEGINNER
#define WOKWI
#define TIMEOUTRESTORE
#define DEBUGINPUT //enables print statements in input routines
#define DEBUGPROCESS //enables print statements in processing routines
#define DEBUGMEM //
#define DEBUGSERVOS //
#define DEBUGEEPROM
/* Comments for email posting:
Well. Thoughts about control of servos made me realize that with a bit of
work, one can control up to 12 servos with one Nano, complete with dual LEDs
and button inputs, with no external boards(i.e. no MCP23017, no PCA9685).
How? Well, the LEDs can all be Neopixels. The buttons are all single action
momentary, so they can be a cascade of resistors on a single analog input, as
shown in the attached schematic; IRL, I added a capacitor to the Analog pin,
for simple hardware debouncing purposes.
Note that, for wiring purposes, this means a panel connected to the Nano
needs a 4-pin connector, carrying VCC, data, and GND for the Neopixels,
and a single pin for the button feedback. If multiple subpanels are desired,
multiple connectors, each having an extra pin to return the pixel data out
signal are required. So, 5-pin connectors. With up to 32 pixels on the
VCC/GND, the paneld could draw ~2A peak, but we're only going to power one
colour at a time(R/G/B), and not full intensity, so < 0.5A is expected normally.
Board must also support connection of up to 12 servos, with external power
supply for them. See schematic for wiring details. Board to follow, once a
prototype is built.
3 simultaneous methods exist for control and editing of positions:
- Serial can be used (but needs D0/D1)
- IR remote can be used(Wokwi for testing, Amazon or other IRL)
- Button control can be used; resistor ladders as appropriate.
All 3 methods return characters to the software; characters are 0-9, A-F,
and +(increment) =(throw) -(decrement) P(rogram) S(ave); -1 is a no-command flag
RS485 can be used, as D0/D1/D2 are available for that
I2C is still an option, as we reserved A4/A5
I chose A2 for the IR remote and A3 for the pixel output. In Wokwi, the
pixels are all depicted and work. 12 servos work.
Serial and IR work in Wokwi; the resistor-button network does not, but
since the analog read module will return the same char values as serial
and IR, we can presume it will work, we just need to iron out the one routine.
All servos can be thrown using serial
(0..9, A..B); positions can be edited using +=- with S for Save and P for
entering/leaving Programming mode.
The IR remote is a bonus, and allows the same functionality on a 20-button
remote purchased from Amazon. For most similar remotes, simply replacing
the key values and changing button lettering will be necessary, after using
the IRremote.h demos to figure out key values. To relabel the buttons, I
will use Avery dots with suitable labels and a clear plastic overlay.
The readily available simple MAX485 circuit can be added by using headers.
A7 is used for the button array for commanding turnouts. A6 is available for
a similar array for route control, or remote requests; it remains to be seen
how many buttons could be reasonably supported by this method, but I2C button
support could be added instead.
I envisage this being used for local panels on my layout, allowing one Nano
to control up to 12 servos via up to 3 subpanels; I'll make a PCB capable of
supporting up to 4 sub-panels. Each subpanel could be connected with a 5 or 6
wire cable.
Note that the Nano footprint has been augmented by newer Arduino products. I
haven't explored whether they're fully compatible with this development. I
leave that to the user.
Enjoy!
*/
//*************************************************** CONSTANTS *************************************
const uint8_t rcv_pin = 17; //pin for IR reception
const uint8_t pxl_pin = 16; //pin for pixel output
const uint8_t mode_pin = 20; //buttons with resistors on this pin used for other functions, TBD
const uint8_t bttn_pin = 21; //buttons with resistors on this pin used to toggle all turnouts
const uint8_t bttn_map = 23; //value to use for Map, to generate values from 0..whatever for buttons. Needs appropriate resistor network for each pin.
const uint8_t THROWN = HIGH; //aka Reversed
const uint8_t HOME = LOW; //aka Normal or Closed
const uint8_t MAXSERVO = 160; //servo adjustment top and bottom; some cheap servos break if you run them 0-180
const uint8_t MINSERVO = 20;
const uint8_t ADJUSTDELTA = 5; //degrees to add or subtract when editing position; likely too coarse, but useful for testing, as it causes a visible change
const uint8_t MOVEDELTA = 5; //degrees to move per MOVEINTERVAL, if we're moving; we will add/subtract this many degrees each time we move the servo !!!NOT YET IMPLEMENTED
const uint32_t MOVEINTERVAL = 100; //ms between updates to the servo when moving
const uint32_t STARTINTERVAL = 100; //ms between servo moves in setup, to avoid power supply overloading
const uint32_t DEBOUNCEDURATION = 50; //ms for debounce when pressed
const uint32_t MAXTHROWNTIME = 10000; //ms maximum that a turnout can remain in THROWN state; 0 is special, means 'forever'
const uint16_t SIGNATURE = 0xAA55; //signature to be found/written in EEPROM; We must change whenever Turnout struct definition changes
//*************************************************** GLOBALS *************************************
//This software works with an array of turnout structs which define the characteristics of a turnout; the basic struct is as follows:
struct Turnout {
const uint8_t servo_Pin; //self-evident
uint8_t servo_Home; //angle 0-180
uint8_t servo_Thrown; //angle 0-180
uint8_t turnoutState; //when read from EEPROM, at startup or during editing, this could be the default state, after that, contains the present state
uint8_t servo_Pos; //position servo is presently at, if different from home or thrown; interim motion values
uint32_t timer; //two things require time elements; motion, and return-to-home. We can use the same timer, so we'll call it timer!
Servo servo; //the Servo element can't be initialized, hence compiler warns. Could make it a separate array, but this is convenient.
};
//this is the Turnout array declaration, complete with initialization of values
struct Turnout Turnouts[] = {
{3, 45, 135, 0, 0, 0}, //D3 Servo 1
{4, 45, 135, 0, 0, 0}, //D4 Servo 2
{5, 45, 135, 0, 0, 0}, //D5 Servo 3
{6, 45, 135, 0, 0, 0}, //D6 Servo 4
{7, 45, 135, 0, 0, 0}, //D7 Servo 5
{8, 45, 135, 0, 0, 0}, //D8 Servo 6
{9, 45, 135, 0, 0, 0}, //D9 Servo 7
{10, 45, 135, 0, 0, 0}, //D10 Servo 8
{11, 45, 135, 0, 0, 0}, //D11 Servo 9
{12, 45, 135, 0, 0, 0}, //D12 Servo 10
{14, 45, 135, 0, 0, 0}, //A0 Servo 11
{15, 45, 135, 0, 0, 0}, //A1 Servo 12
};
//D0 reserved for Serial/RS485 TX
//D1 reserved for Serial/RS485 RX
//D2 reserved for RS485 direction control pin
//D13 pin reserved for onboard LED; presently, just used to indicate programming enabled
//A2 pin reserved for pixel output
//A3 pin reserved for IR input
//A4 reserved for I2C
//A5 reserved for I2C
//A6 may be used for route requests, or alternate purposes(TBD); I expect I may want some interconnect requests with other servo controllers, or JMRI
//A7 is for direct turnout control, one button per turnout; up to 8 buttons could be added above that; 5(-=+SP) presently implemented.
//Finally, the software needs to know how big the array is. This is a foolproof way of determining that, rather than manually entering a constant
//Though constant, must be declared after Turnouts[] is created, as it's based on it's size
const uint8_t NUMCHAN = sizeof(Turnouts) / sizeof(Turnouts[0]); //defines how many servos we're driving
const uint8_t NUMPIXELS = NUMCHAN * 2; //and the number of pixels
//*************************************************** GLOBALS *************************************
bool editMode = false;
uint8_t currChan = 0;
Adafruit_NeoPixel pixels(NUMPIXELS, pxl_pin, NEO_GRB + NEO_KHZ800);
#ifdef OLDIR
IRrecv IrReceiver(rcv_pin);
#endif
void setup() { //*************** SETUP *******************************
Serial.begin(115200); //start Serial for Monitor
Serial.flush();
#ifdef NEWIR
IrReceiver.begin(rcv_pin); // Start the IR receiver
#endif
#ifdef OLDIR
IrReceiver.enableIRIn();
#endif
pixels.begin(); // setup the NeoPixel strip object
pinMode(LED_BUILTIN, OUTPUT); //use it for scan blinker, and maybe diagnostic blinks
if (readEEPROM() == -1) { //reads the EEPROM into Turnout array if signature is valid and array is correct size; returns -1 if failed
}
pixels.clear();
for (int chan = 0; chan < NUMCHAN; chan++) {
pixels.setPixelColor(chan * 2, pixels.Color(0, 150, 0)); //GREEN
pixels.setPixelColor(chan * 2 + 1, pixels.Color(150, 0, 0)); //RED
Turnouts[chan].servo.attach(Turnouts[chan].servo_Pin);//attach servo
Turnouts[chan].timer = 0; //clear
if (Turnouts[chan].turnoutState == HOME) {
Turnouts[chan].servo.write(Turnouts[chan].servo_Home);//and set initial position
Turnouts[chan].servo_Pos = Turnouts[chan].servo_Home; //set position variable
}
else {
Turnouts[chan].servo.write(Turnouts[chan].servo_Thrown);//and set initial position
Turnouts[chan].servo_Pos = Turnouts[chan].servo_Thrown; //set position variable
}
pixels.show();
delay(STARTINTERVAL); //pause, so that we don't enable and churn all servos at the same time
//now darken the LED for the position the turnout is NOT in
if (Turnouts[chan].turnoutState == HOME) pixels.setPixelColor(chan * 2 + 1, pixels.Color(0, 0, 0)); //DARK
else pixels.setPixelColor(chan * 2, pixels.Color(0, 0, 0)); //DARK
}
pixels.show(); //last pixel show, to catch the post-delay turn off of the unselected LED
#ifdef DEBUGMEM
MEMORY_PRINT_START
MEMORY_PRINT_HEAPSTART
MEMORY_PRINT_HEAPEND
MEMORY_PRINT_STACKSTART
MEMORY_PRINT_END
MEMORY_PRINT_HEAPSIZE
Serial.println();
FREERAM_PRINT;
Serial.println(F("Setup complete"));
#endif
} //end setup
void loop() { //*********** LOOP *************************************
// int bttn = checkIR(checkSerial(scanButtons())); //returns a character, or -1 Only 0123456789ABCDEF+-SP have significance
int bttn = checkIR(checkSerial(-1)); //does not check buttons, just IR and Serial
// int bttn = checkSerial(-1); //does not check buttons or IR, just Serial
if (bttn >= 0) { //deal with it; remember to debounce
bttn = toupper(bttn); //allows serial to include lower case letters
#ifdef DEBUGINPUT
Serial.print(F("Received command from input checks: "));
Serial.println(bttn, HEX);
#endif
processLogic(bttn);
}
updateOutputs(); //handle LEDs and Servos.
digitalWrite(LED_BUILTIN, editMode);
delay(50);
}//end loop
void processLogic(int bttn) { //*********** process Logic *********************
switch (bttn) {
case '0'...'9'://convert to number 0...9
bttn = bttn - '0';//fall through is deliberate
[[fallthrough]];
case 'A'...'B'://convert to numbers 10,11
if (bttn >= 'A') bttn = (bttn - 'A') + 10;
if (bttn < NUMCHAN) {
if (editMode == false) {
Turnouts[bttn].turnoutState = Turnouts[bttn].turnoutState == HOME ? THROWN : HOME; //if not editing, simply toggle Turnout position, as button already debounced.
//Turnouts[bttn].timer = millis(); // if timedreset is enabled, set timer for timed reset
#ifdef DEBUGPROCESS
Serial.print(F("set turnout "));
Serial.print(bttn + 1);
Serial.print(F(" to "));
Serial.println(Turnouts[bttn].turnoutState);
#endif
currChan = bttn; //track most recent channel for editing
}
else { //if we just pressed a number, and it's already our currChan, presume we should flip the turnout. If it's a different number, just change .
if (bttn == currChan) {
Turnouts[bttn].turnoutState = Turnouts[bttn].turnoutState == HOME ? THROWN : HOME;
#ifdef DEBUGPROCESS
Serial.println(F("flipped turnout being edited"));
#endif
}
else {
currChan = bttn; //track most recent channel for editing
#ifdef DEBUGPROCESS
Serial.println(F("moved to new turnout"));
#endif
}
}
}
break;
case '+': //if edit enabled, increment&limit present Turnout position; e.g. if home, and pos=home, then home = home + ADJUSTDELTA
if (editMode == true) { //consider whether we want this to become a "nudge harder to rail"
if (Turnouts[currChan].turnoutState == HOME && Turnouts[currChan].servo_Pos == Turnouts[currChan].servo_Home) {
Turnouts[currChan].servo_Home += ADJUSTDELTA;
if (Turnouts[currChan].servo_Home > MAXSERVO)Turnouts[currChan].servo_Home = MAXSERVO;
#ifdef DEBUGPROCESS
Serial.print(F("Increased Home Position to ")); Serial.println(Turnouts[currChan].servo_Home);
#endif
Turnouts[currChan].servo_Pos = Turnouts[currChan].servo_Home;
}
else {
Turnouts[currChan].servo_Thrown += ADJUSTDELTA;
if (Turnouts[currChan].servo_Thrown > MAXSERVO) Turnouts[currChan].servo_Thrown = MAXSERVO;
#ifdef DEBUGPROCESS
Serial.print(F("Increased Thrown Position to ")); Serial.println(Turnouts[currChan].servo_Thrown);
#endif
Turnouts[currChan].servo_Pos = Turnouts[currChan].servo_Thrown;
}
}
break;
case '-': //if edit enabled, decrement&limit present Turnout position; e.g. if home, and pos=home, then home = home + ADJUSTDELTA
if (editMode == true) { //consider whether we want this to become a "pull back from rail"
if (Turnouts[currChan].turnoutState == HOME && Turnouts[currChan].servo_Pos == Turnouts[currChan].servo_Home) {
Turnouts[currChan].servo_Home -= ADJUSTDELTA;
if (Turnouts[currChan].servo_Home < MINSERVO)Turnouts[currChan].servo_Home = MINSERVO;
#ifdef DEBUGPROCESS
Serial.println(F("Decreased Home Position to ")); Serial.println(Turnouts[currChan].servo_Home);
#endif
Turnouts[currChan].servo_Pos = Turnouts[currChan].servo_Home;
}
else {
Turnouts[currChan].servo_Thrown -= ADJUSTDELTA;
if (Turnouts[currChan].servo_Thrown < MINSERVO) Turnouts[currChan].servo_Thrown = MINSERVO;
#ifdef DEBUGPROCESS
Serial.print(F("Decreased Thrown Position to ")); Serial.println(Turnouts[currChan].servo_Thrown);
#endif
Turnouts[currChan].servo_Pos = Turnouts[currChan].servo_Thrown;
}
}
break;
case '=': //toggle turnout
Turnouts[currChan].turnoutState = Turnouts[currChan].turnoutState == HOME ? THROWN : HOME;
#ifdef DEBUGPROCESS
Serial.println(F("flipped current turnout"));
#endif
break;
case 'L':
#ifdef DEBUGPROCESS
Serial.println(F("ch pin pin pin state angle cmd"));
for (int ch = 0; ch < NUMCHAN; ch++) {
listTO(ch);
}
#endif
break;
case 'M': //emits a menu to Serial Monitor; not ifdef-ed because you only can use this with Serial anyway
#ifdef DEBUGPROCESS
Serial.println(F("---------------------------------- MENU -------------------------"));
Serial.println(F(" NEEDS UPDATING WITH ACTUAL COMMAND LETTERS "));
Serial.println(F("= - Alternate (Home->Thrown, or Thrown - > Home"));
Serial.println(F("+ - Increment - - Decrement"));
Serial.println(F("S - Store P - Enable/Disable limits programming"));
Serial.println(F("H - Help menu "));
Serial.println(F("C - Home T - Throw"));
Serial.println(F("L - Print complete turnout table for visual Inspection(for verifying changes)"));
Serial.println(F("any valid turnout number, e.g. 3 - go directly to that turnout"));
#endif
break;
case 'S': //saves all turnout info to EEPROM
if (editMode == true) {}
writeEEPROM();
#ifdef DEBUGPROCESS
Serial.println(F("Wrote EEPROM"));
#endif
break;
case 'P': //toggles between edit mode and not.
if (editMode == true) {
#ifdef DEBUGPROCESS
Serial.println(F("Turned edit mode OFF"));
#endif
editMode = false;
}
else {
#ifdef DEBUGPROCESS
Serial.print(F("Turned edit mode ON; "));
Serial.print(F("Editing turnout "));
Serial.println(currChan + 1);
#endif
editMode = true;
}
break;
default:
#ifdef DEBUGPROCESS
Serial.print(F("Character in processLogic: -"));
Serial.print(bttn, HEX);
Serial.println(F("- was unexpected"));
#endif
break;
}
#ifdef TIMEOUTRESTORE //can be added if desired. Simply checks each turnout for timeout.
//Feature is wanted by some, not all. Returns any non-home turnouts to home after a timeout, if editing is not happening.
for (int n = 0; n < NUMCHAN; n++) {
if ((editMode != true) && (millis() - Turnouts[n].timer >= MAXTHROWNTIME)) Turnouts[n].turnoutState = HOME;
}
#endif
} //end processLogic
void listTO(int ch) {
Serial.print(ch + 1);
Serial.print(F(" "));
Serial.print(F(" HOME: "));
if (Turnouts[ch].servo_Home < 100)Serial.print(" ");
Serial.print(Turnouts[ch].servo_Home);
Serial.print(F(" THROWN: "));
if (Turnouts[ch].servo_Thrown < 100)Serial.print(" ");
Serial.print(Turnouts[ch].servo_Thrown);
Serial.print(F(" Pos: "));
Serial.println(Turnouts[ch].servo_Pos);
}
void updateOutputs() { //*********** update Outputs ********************
//runs every loop, due to LED and motion changes between endpoints
pixels.clear(); //Set all pixel colors to 'off'
for (int chan = 0; chan < NUMCHAN; chan++) {
setLEDs(chan);
setServos(chan);
#ifdef DEBUGPROCESS
listTO(chan);
#endif
}
pixels.show(); // Send the updated pixel colors to the hardware.
}//end updateOutputs
void setServos(int chan) { //*********** set Servos ********************
if (Turnouts[chan].turnoutState == HOME && Turnouts[chan].servo_Pos == Turnouts[chan].servo_Home) {
Turnouts[chan].servo_Pos = Turnouts[chan].servo_Home;
Turnouts[chan].servo.write(Turnouts[chan].servo_Home); //write pos to servo
}
else if (Turnouts[chan].turnoutState == THROWN && Turnouts[chan].servo_Pos == Turnouts[chan].servo_Thrown) {
Turnouts[chan].servo_Pos = Turnouts[chan].servo_Thrown;
Turnouts[chan].servo.write(Turnouts[chan].servo_Thrown);
}
else {
/*
if pos is not at declared present state, must be moving there. If it's time to, add/subtract DELTA,
check constraints, and write to pos, then write pos to servo.
*/
if (millis() - Turnouts[chan].timer > MOVEINTERVAL) {
Turnouts[chan].timer = millis();
#ifdef DEBUGSERVOS
listTO(chan);
Serial.print(F("Turnout ")); Serial.print(chan + 1); Serial.print(F(" Not at HOME or THROWN positions; position is: ")); Serial.print(Turnouts[chan].servo_Pos); Serial.print(F(" Moving to: "));
if (Turnouts[chan].turnoutState == HOME) Serial.println(Turnouts[chan].servo_Home);
else Serial.println(Turnouts[chan].servo_Thrown);
#endif
int pos = Turnouts[chan].servo_Pos;
if (Turnouts[chan].turnoutState == HOME) {
if (abs(pos - Turnouts[chan].servo_Home) < MOVEDELTA) pos = Turnouts[chan].servo_Home;
else {
if (pos - Turnouts[chan].servo_Home < 0) pos += MOVEDELTA;
else pos -= MOVEDELTA;
}
}
if (Turnouts[chan].turnoutState == THROWN) {
if (abs(pos - Turnouts[chan].servo_Thrown) < MOVEDELTA) pos = Turnouts[chan].servo_Thrown;
else {
if (pos - Turnouts[chan].servo_Thrown < 0) pos += MOVEDELTA;
else pos -= MOVEDELTA;
}
}
// constrain pos and write to servo
Turnouts[chan].servo_Pos = pos < MINSERVO ? MINSERVO : pos > MAXSERVO ? MAXSERVO : pos;
Turnouts[chan].servo.write(Turnouts[chan].servo_Pos);
}//end of time conditional
}
} //end setServos
void setLEDs(int chan) { //*********** set LEDs ********************
//for homeLED to be lit green, must be at home position, and it's not being edited
//for homeLED to be lit blue, must be at home position, and we're in edit mode
//for homeLED to be dark, must not be at home position
//for thrownLED to be lit red, must be at thrown position, and it's not being edited
//for thrownLED to be lit blue, must be at thrown position, and we're in edit mode
//for thrownLED to be dark, must not be at thrown position
//pixels were cleared earlier, so only set them if needed
if (Turnouts[chan].servo_Pos == Turnouts[chan].servo_Home && Turnouts[chan].turnoutState == HOME ) {
if (editMode == true && currChan == chan) pixels.setPixelColor(chan * 2, pixels.Color(0, 0, 150)); //BLUE
else pixels.setPixelColor(chan * 2, pixels.Color(0, 150, 0)); //GREEN
}
if (Turnouts[chan].servo_Pos == Turnouts[chan].servo_Thrown && Turnouts[chan].turnoutState == THROWN ) {
if (editMode == true && chan == currChan ) pixels.setPixelColor((chan * 2) + 1, pixels.Color(0, 0, 150)); //BLUE
else pixels.setPixelColor((chan * 2) + 1, pixels.Color(150, 0, 0)); //RED
}
} //end setLEDs
int readEEPROM() { //*************************************** readEEPROM **************************************************
//read EEPROM signature. If current, read array size; otherwise, return error flag. If same as presently allocated, read the array; otherwise, return error flag
int retval = -1;
uint16_t sig;
EEPROM.get(0, sig); //read the signature (should be 0XAA55)
if (sig == SIGNATURE) {//if signature is correct,
uint8_t num;
EEPROM.get(sizeof(sig), num); //read array size;
if (num == NUMCHAN) {
EEPROM.get(sizeof(sig) + sizeof(num), Turnouts); //and read the array WHOOPS. Need to at least check that the array is the same size. Sigh.
#ifdef DEBUGEEPROM
Serial.println(F("EEPROM has been Read"));
#endif
retval = 0;
}
else {
#ifdef DEBUGEEPROM
Serial.println(F("Error - array size mismatch, EEPROM content not read; write new content to EEPROM first"));
#endif
}
}
else {
#ifdef DEBUGEEPROM
Serial.println(F("Error - signature mismatch, EEPROM content not read; write new content to EEPROM first"));
#endif
}
return (retval);
} //end readEEPROM
void writeEEPROM() { //*************************************** writeEEPROM **************************************************
EEPROM.put(0, SIGNATURE); //write the signature
//write the array size
EEPROM.put(2, NUMCHAN);
//write the array
EEPROM.put(3, Turnouts);
#ifdef DEBUGEEPROM
Serial.println(F("EEPROM Written"));
#endif
} //end writeEEPROM
//********************************* Local data for ScanButtons **********************************************
int prevButton = -1; //default previous button to "not pressed"
int prevReport = -1; //default previous reported button to "not pressed"
uint32_t buttonTimer = 0; //used as debounce timer for buttons
char results[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', '-', '=', '+', 'S', 'P'}; //defines the possible values to return
int scanButtons() { //*********** scan Buttons ***********************
//returns 0..9, A..B and +=-SP only after debounce, and -1 signifies no button pressed
//read the analog and use map to determine a value(0->?); convert that value to a character. See spreadsheet for resistor values.
int val = analogRead(A7);
val = map(val, 0, 1023, 0, bttn_map);
if (val >= (int) sizeof(results) / sizeof(results[0])) val = -1; //guard against higher values.
else val = results[val];
if (val != prevButton) {
prevButton = val;
buttonTimer = millis();
}
else { //okay, is same as previous button, check for debounce, and then check for previous report
if (millis() - buttonTimer < DEBOUNCEDURATION) val = -1; //not ready to report yet
else if (val == prevReport) val = -1; //if we reported this one already, don't do it again; if we wanted auto-repeat, this is where we watch for a longer period
}
return (val);
} //end scanButtons
int checkSerial(int retval) { //*********** check Serial **********************
//returns 0..9, A..B and -=+SPL (any other serial character as well, so beware).
//caller must ignore all other return values. -1 returned if no character
if (Serial.available()) {
retval = Serial.read();
}
return (retval);
} //end checkSerial
//Keypad codes and ASCII keys returned for:
// FUNCTION | KEY | WOKWI HEX | WOKWI DECIMAL
// Prog (none) Save | P S | 0xA2 0xE2 | 162 226
//(none) inc (none)| | 0x22 0x02 0xC2 | 034 002 194
// dec dec inc | - = + | 0xE0 0xA8 0x90 | 224 168 144
// 1 2 3 | 0 1 2 | 0x68 0x98 0xB0 | 104 152 176
// 4 5 6 | 3 4 5 | 0x30 0x18 0x7A | 048 024 122
// 7 8 9 | 6 7 8 | 0x10 0x38 0x5A | 016 056 090
// 10 11 12 | 9 A B | 0x42 0x4A 0x52 | 066 074 082
#ifdef WOKWI
const uint8_t IRCodes[][20] =
{ {0xA2, 0xE2, 0x22, 0x02, 0xC2, 0xE0, 0xA8, 0x90, 0x68, 0x98, 0xB0, 0x30, 0x18, 0x7A, 0x10, 0x38, 0x5A, 0x42, 0x4A, 0x52},
{ 'P', 'S', ' ', ' ', ' ', '-', '=', '+', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B'}
};
#endif
//Keypad codes and ASCII keys returned for:
// FUNCTION | KEY | AMAZON HEX
// Prog (none) Save | P S | 0x45 0x46 0x47
//(none)(none)(none)| | 0x44 0x40 0x43
// dec oper inc | - = + | 0x07 0x15 0x09
// 1 2 3 | 0 1 2 | 0x16 0x19 0x0d
// 4 5 6 | 3 4 5 | 0x0c 0x18 0x5e
// 7 8 9 | 6 7 8 | 0x08 0x1c 0x5a
// 10 11 12 | 9 A B | 0x42 0x52 0x4a
#ifdef AMAZON
const uint8_t IRCodes[][21] =
{ {0x45, 0X46, 0x47, 0x44, 0x40, 0x43, 0x07, 0x15, 0x09, 0x16, 0x19, 0x0D, 0x0C, 0x18, 0x5E, 0x08, 0x1C, 0x5A, 0x42, 0x52, 0x4A},
{ 'P', ' ', 'S', ' ', ' ', ' ', '-', '=', '+', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B'}
};
#endif
int sizeCodes = sizeof(IRCodes) / sizeof(IRCodes[0][0]) / 2;
int checkIR(int retval) { //*********** check IR Remote *******************
//returns 0..9, A..B and -=+SP
if (IrReceiver.decode()) {
retval = parseCodes(IrReceiver.decodedIRData.command);
IrReceiver.resume(); // Receive the next value
}
return (retval);
} //end checkIR
int parseCodes(int ch) { //looks up the IR code received, returns the character command
int retval = -1;
for (int n = 0; n < sizeCodes; n++)
if (ch == IRCodes[0][n]) {
retval = IRCodes[1][n];
break;
}
return retval;
}// end parseCodes