This controller provides 76 On, Off, and display select commands.
The commands are grouped into 9 receivers, which are selected by the UP and Down buttons. The area is identified on the top line of the display.
Each area has multiple channels, which are selected by the Left and Right buttons. Each may designate an Off, an On, or a display select command. The channel is identified on the bottom line of the display.
The Select button initiates a transmission.
The controller is based on an Arduino Shield which has a LCD display as well as the buttons. It plugs onto a UNO.
It also has a Li Ion battery, a DC-DC converter, and a battery charger.
Power is switched by a rolling-ball tilt sensor.
Disregard the Tower designation since that project no longer exists. This controller is retired.
The shield encodes the push-buttons into an analog voltage using a resistor network.
The display uses 4-bit parallel. The 2 transistors correct for the inverted serial output of the Arduino and for its inability to form a pulse. The Chinese Arduino clone failed and was replaced by a genuine Italian one.
The Chinese charger failed and was replaced by an American one.
/*
LCD Controller.ino is actually a universal control.
It is in a wooden box with a 16x2 blue LCD display with 5 buttons plus reset below it.
It includes an Arduino UNO and is powered by a 3.7V L-iion 750 MAH battery which has a charger.
The box has two holes. The one giving access to a USB type B socket is for programming the UNO.
The other accesses a micro-USB for charging from a 5V, 1A supply.
The displays and transmit codes are specified in arrays which can be extended.
John Saunders 5/29/2021
*/
// include the library code:
#include <LiquidCrystal.h>
#include <avr/pgmspace.h>
/*
The circuit:
LCD RS pin to digital pin 8
LCD Enable pin to digital pin 9
LCD D4 pin to digital pin 4
LCD D5 pin to digital pin 5
LCD D6 pin to digital pin 6
LCD D7 pin to digital pin 7
LCD Backlight enable pin 10
*/
// initialize the library by associating any needed LCD interface pin
// with the arduino pin number it is connected to
const int rs = 8, en = 9, d4 = 4, d5 = 5, d6 = 6, d7 = 7;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
#define BLPin 10 //Controls the backlight
// Transmit pulse lengths (ms), for reliable 433 MHz transmissions
#define PREPULSE_LEN 20
#define SETUP_LEN 10
#define minVolts 3.5 // Time to charge the battery if less
#define Timeout 560000ul
#define flashON 600
// globals:
const int mids[] = {70, 222, 392, 600, 872, 2000}; //The buttons are on taps of a resistor divider
const char letters[] = {'R', 'U', 'D', 'L', 'S', 'N'};
const float Vcc = 5.119; // Measured
char keyCode; // Sent to control a device
byte selRcvr; // Index to an array of receivers
byte selChnl; // Index to an array of the actions of the selected receiver
typedef struct {
char line1[17]; // Second line of the display is action
char kCode; // keyCode value
} chnl_t;
typedef struct {
char line0[17]; // Top line of the display is receiver
const chnl_t *chnl; // Address of the array of actions
char keyType; // 'D' insert "O," before keyCode in Transmit, or 'C'
byte maxChnl; // max of actions. Used when changing the action
char dButNext; // The order of receivers is set by convenience
char uButPrev;
} rcvr_t;
// Arays of actions are PROGMEM or they would exceed available RAM
const chnl_t Tower[] PROGMEM = {
{ "Tower Lights off", 'm'}, {"Color LEDs off ", 'k'}, {" PWD LEDs off ", 'o'},
{" PWD LEDs on ", 'n'}, {"Color LEDs on", 'j'},{ "Tower Lights on", 'l'}
};
const chnl_t Gazebo[] PROGMEM = {
{"Owl Fountain off", '0'}, {"Owl Fountain on ", '1'}, {"Boy Fountain off", '2'},
{"Boy Fountain on ", '3'}, {"String light off", '6'}, {"String lights on", '7'},
{" Strip light off", '4'}, {"Strip lights on ", '5'} , {" Vegas Sign off ", 'V'},
{" Vegas Sign on ", 'L'} };
const chnl_t Garden[] PROGMEM = {
{" Girl pump on ", 'j'}, {" Girl pump off ", 'T'}, {" Clear AC on ", 'm'},
{" Clear AC off ", 'J'}, {" Clear Tip on ", 'X'} ,
{" Clear Tip off ", 'N'}, {" Clear Ring on ", 'd'}, {" Clear Ring off ", 'o'}
};
const chnl_t RLD[] PROGMEM = {
{" Display Date ", 'K'}, {" Display Time ", 'L'}, {"Ext Temperature ", 'M'},
{" Ext Humidity ", 'N'}, {" Solar Current ", 'O'}, {" Battery Volts ", 'P'}
};
const chnl_t LRW[] PROGMEM = {
{" Speaker off ", 'U'}, {"Ceiling Lamp off", 'G'}, {"Ceiling Light on", 'R'},
{" Speaker on ", 'S'}, {"Floor light off ", 'f'}, {" Floor light on ", 'b'},
{" Shelf light off", 'F'}, {" Shelf lights on", 'E'} , {" Pineapple off ", '8'},
{" Pineapple on ", 'K'},
};
const chnl_t Garage[] PROGMEM = {
{" Pole Light off ", 'k'}, {" Pole Light on ", 'i'}, {" Flower off ", 'c'},
{" Flower on ", 'a'}, {"Cube Red LED on ", 'A'}, {"Cube Blue LED on", 'e'},
{"Cube Gren LED on", 'D'}, {"Cube Plays BaBa ", 'B'}, {"Cube Plays Chime", 'Q'},
{"Cube PlaysSiren ", 'h'}, {"Cube All LED off", 'H'}
};
const chnl_t RGB[] PROGMEM = {
{" Temperatures ", 'C'}, {" Humidities ", 'D'}, {"Garage Temp&Fan ", 'A'},
{"Baro&Solar Curr ", 'E'}, {" Voltages ", 'B'}, {" Night Disp;ay ", 'F'}
};
const chnl_t LRE[] PROGMEM = {
{"Hanging Light on", 'I'}, {"Hanging Lamp off", 'C'}, {" Eggs off ", '9'},
{" Eggs on ", 'd'}, {"Circle LEDs off ", 'l'}, {" Circle LEDs on ", 'P'},
{" Missions off ", 'g'}, {" Missions on ", 'M'}, {" Flashing off ", 'W'},
{" Flashing on ", 'Y'},
}; //I = uc eye, l = lc ell
const chnl_t Atmosphere[] PROGMEM = {
{"Room Temperature", 'a'}, {" Room Humidity ", 'b'}, {"Ext Temperature ", 'c'},
{" Ext Humidity ", 'd'}, {" Baro Pressure ", 'f'}, {" Solar Current ", 'e'},{" Gas Quality ",'g'}
};
// Array of receivers. Some use multiple hardware devices
const rcvr_t rcvrs[] = {
{" Tower Display", Tower, 'D', 5, 5, 1}, {"Red LED Display", RLD, 'D', 5, 0, 8},
{"Atmosphere ", Atmosphere, 'D', 6, 6, 7}, {" rgb Display", RGB, 'D', 5, 7, 4},
{"Living Room East", LRE, 'C', 9, 3, 5}, {"Living Room West", LRW, 'C', 9, 4, 0},
{"Garage & Outside", Garage, 'C', 10, 8, 2}, {" Gazebo ", Gazebo, 'C', 9, 2, 3},
{" Garden ", Garden, 'C', 7, 1, 6}
};
// For the front porch of the command transmission:
#define PULSE_PORT 2
void setup() {
float battVolts;
char letter;
Serial.begin(2400);
pinMode(PULSE_PORT, OUTPUT); //For the 433 MHz pre-pulse
digitalWrite(PULSE_PORT, LOW);
// set up the LCD's number of columns and rows:
lcd.begin(16, 2);
pinMode(BLPin, OUTPUT);
digitalWrite(BLPin, HIGH); // Backlight control;
dispNotice();
delay(900);
battVolts = displayBatteryVolts();
delay(900);
selRcvr = 4;
selChnl = 0;
letter = getLetter();
if (letter == 'S') {
dispNotice();
}
if (battVolts < minVolts) {
dispWarning();
delay(1000);
}
dispRcvr(selRcvr);
keyCode = dispChnl();
}
void loop() {
byte newInx;
int blanking;
char letter = getLetter();
if (millis() > Timeout) {
letter = 'T';
blanking = millis() & 0xFFFF;
if (blanking > flashON) {
digitalWrite(BLPin, LOW);
}
else {
digitalWrite(BLPin, HIGH);
}
}
switch (letter) {
case 'S':
transmit();
break;
case 'L':
if (selChnl > 0) {
selChnl--;
}
else {
selChnl = rcvrs[selRcvr].maxChnl;
}
keyCode = dispChnl();
break;
case 'R':
if (selChnl < rcvrs[selRcvr].maxChnl) {
selChnl++;
}
else {
selChnl = 0;
}
keyCode = dispChnl();
break;
case 'D':
newInx = rcvrs[selRcvr].uButPrev;
dispRcvr(newInx);
selRcvr = newInx;
selChnl = 0;
keyCode = dispChnl();
break;
case 'U':
newInx = rcvrs[selRcvr].dButNext;
dispRcvr(newInx);
selRcvr = newInx;
selChnl = 0;
keyCode = dispChnl();
break;
case 'T':
dispTimeout();
break;
}
delay(100);
}
/*********************************************************************
Transmitter function
The serial port 'Serial' Tx cannot be made an output
Serial!.write produces a negative-going output in TX1.
There is no 'begin' option to invert this, so I had to add a hardware inverter.
After Serial1.begin() digitalWrite is ignored, so I had to add a hardware 'OR' gate.
The 433 MHz protocol cannot be done by Tx alone. A 2-transistor circuit is needed.
**********************************************************************
*/
void transmit(void) { //if dispCode == 'O' send it and a comma before keyCode
char charBuffer[] = {"14L1776 "};
byte index = 7;
if (rcvrs[selRcvr].keyType == 'D') {
charBuffer[index++] = 'O';
charBuffer[index++] = ',';
charBuffer[index++] = keyCode;
}
else {
charBuffer[index++] = keyCode;
}
charBuffer[index++] = 0x0d;
charBuffer[index++] = 0x0a;
digitalWrite(PULSE_PORT, HIGH);
delay(PREPULSE_LEN);
digitalWrite(PULSE_PORT, LOW);
delay(SETUP_LEN);
Serial.write(charBuffer, index);
}
void dispNotice(void) {
lcd.clear();
lcd.print("Made in 2020");
lcd.setCursor(0, 1);
lcd.print("by John Saunders");
}
void dispWarning(void) {
lcd.clear();
lcd.print("Turn me off now");
lcd.setCursor(0, 1);
lcd.print("& charge battery");
}
void dispTimeout(void) {
lcd.clear();
lcd.print("Timing out...");
lcd.setCursor(0, 1);
lcd.print("do something!");
}
float displayBatteryVolts(void) {
int AnalogVal;
float bv;
AnalogVal = analogRead(A1);
bv = AnalogVal * Vcc / 1024;
lcd.clear();
lcd.print(" Tower Control");
lcd.setCursor(0, 1);
lcd.print("Battery = ");
lcd.print(bv, 2);
lcd.print("V");
return bv;
}
void dispRcvr(byte inx) {
lcd.home();
lcd.print(" ");
lcd.home();
lcd.print(rcvrs[inx].line0);
}
char dispChnl(void) { // The lower line on the display
char buf[18];
for (int index = 0; index < 18; index ++ ) { // All of chnl_t
buf[index] = pgm_read_byte_near(&rcvrs[selRcvr].chnl[selChnl].line1[index]);
};
lcd.setCursor(0, 1);
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print(buf); // Stops at the null
return buf[17]; // Key Code
}
char getLetter(void) { // Dissects A0 to get the individual push-buttons
static bool buttReady = true; // All buttons must be released before next press.
int analogVal = analogRead(A0);
if (analogVal == 1023) {
buttReady = true;
return 'N'; // No button pressed
}
else {
if (buttReady == false) { // Waiting for the button to be released
return 'N';
}
else { // Find the button (R is 0)
byte index = 0;
while (mids[index++] < analogVal) ;
buttReady = false;
return letters[--index];
}
}
}