Hackster is hosting Hackster Holidays, Ep. 7: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Friday!Stream Hackster Holidays, Ep. 7 on Friday!
madmark2150
Published © GPL3+

Talking alarm clock with 8x8x4 matrix display

Wake to any of 100's of MP3 messages from SDRAM. Scrolling display w/remote shows clearly w/o glasses. Temp too!

IntermediateFull instructions provided971
Talking alarm clock with 8x8x4 matrix display

Things used in this project

Hardware components

Arduino Mega 2560
Arduino Mega 2560
×1
Shield, Screw, Mega 2560
This shield brings out EVERY pin on a Mega 2560 to a terminal block. The center area is open for breadboards and there are also duplicate pads for soldering to any Mega pin. Each terminal is clearly marked and follow the Mega pinning. The terminals can accept up to 18GA wire or two 22 ga (dupont) wires. Tinning is suggested. This arrives as a kit. Terminals blocks MUST be snapped together BEFORE insertion or soldering. Install reset sw (optional, very tiny surface mount) and double row connector first for ease of soldering.
×1
LED Dot Matrix Display, Red, 8x8x4
MISO/MOSI interface
×1
Grove - RTC
Seeed Studio Grove - RTC
Generic RTC. Uses 2032 coin call. Project can set time after cell change.
×1
DHT-11 Temp/Humidity Sensor, 3 pin, Velleman VMA311
Scheduled for upgrade to DHT-22
×1
IR receiver (generic)
Presumes standard remote - See function assignments drawing below.
×1
Display, LED, R/Y/G, Traffic Light
Red - Coffee Pot ON Yel - Alarm Set Grn - Heartbeat
×1
Rotary potentiometer (generic)
Rotary potentiometer (generic)
5K. 5/16" shaft. Used for data input
×1
Button, Push, NO, MOM, sealed, low profile, SS
Reset & AckBtn buttons. NO, MOM
×2
Relay, SPDT, 5V, Velleman VMA406
Coffee pot Power Control. - 10A @ 125VAC max., Safety limit to 800W max.
×1
Buzzer, Active, +5, Velleman VMA319
Hour Chime and button acknowledgement
×1
Player, MP3, serial, mono, w/spkr
Plays canned messages for hour and alarm.
×1
MicroSD card reader/writer
microSD card is used for data logging. Program captures temperature and humidity data every 15 minutes. Additionally program startups are logged. The program tracks the daily highs and lows of both temp and humidity at midnight. The files are named logMM-YY.txt where mm is the current month and YY is the last two digits of the year, New files are created automatically. data volume is low and the card is big, the installed 32gb card should NEVER run out of space.
×1
microSD card, 32gb
One for MP3 player, one for microSD data logger
×2
Power Supply, 5VDC @ 4A, 5.5x2.1 connector, wall
Main 5V Power Supply
×1
DC Power Jack Male 2.1mm x 5.5mm Panel Mtg
5VDC Power inlet
×1

Software apps and online services

Arduino IDE
Arduino IDE
NanoCAD - AutoCAD work-alike - FREE
Tinkercad
Autodesk Tinkercad

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Used to fabricate module mounts and cable shrouds.

Story

Read more

Custom parts and enclosures

3D .STL print file - 1" Piezo Speaker Mount

Mount tiny 1" piezo speaker with this bracket. Speaker snaps into holder and mounts with two #6 thru screw holes. Wire exit provided.

Speaker is available from Walmart:
https://www.walmart.com/ip/Pack-of-3-MCKP2644SP1F-4748-PIEZO-400HZ-86DB-Transducer-Function-Speaker-Power-Rating-RMS-Impedance-8ohm/986005462

Print 90% infill PLA or PLA+ for strength.

3D .STL print file - 10mm RGB Mount

Mounts a 10mm LED with two #6 thru holes. LED to be superglued in place.

3D .STL print file - 2" Speaker mount

MP3 and other cards have small, 2" round speakers with no mounting ears. This item bridges the speaker, holding the magnet in place and snaps on. The bridge ends have two #6 thru mounting pylons.

3D .STL print file - 8x8x4 LED matrix display frame

Frames 8x8x4 LED matrix display for panel mounting

3D .STL print file - Decorative cover for LED traffic light

It's CUTE. IT slips over the three LED's on the traffic light module (.045" on center). It has "eyebrows" for each LED and a Greek Revival frame.
Print at 90% infill for strength.
1-9/16" x 13/16" x 5/16"

3D .STL print file - LDR Module Mount

Used to mount LDR module available from Walmart: https://www.walmart.com/ip/5Pcs-Photosensitive-Resistance-LDR-Light-Intensity-Detection-Sensor-Module/318626091
Mounting hole for module to be tapped 4-40, mount with 4-40 x 1/4 SS screws and #4 red fiber washers to prevent shorts.
Mounts to rear of panel via two #6 thru mounting holes.
Print at 90% infill for strength.

3D .STL print file - microSD memory card slip-in mount

Mount for a microSD card interface. Card slips into mount. Mount attaches to panel with two #6 thru holes. Micro SD card can be removed while module is mounted.
Print at 90% infill for strength. 1-15/16" x 2-1/8" x 3/8"

3D .STL print file - Mount for Velleman VMA317 IR Receiver and VMA319 Buzzer

This is a mounting bracket for the Velleman VMA317 IR remote receiver and for the Velleman VMA319 active buzzer modules. These are three pin modules. The mount has an opening on the front for the IR signal to enter or the sound to exit. The shroud should be printed in a dark PLA to minimize outside light intrusion during operation of the IR receiver.
Two #6 thru mounting holes are provided.
Module is 1-1/2" x 1" x 9/16". The edges and corners are filleted/rounded for strength.
Print at 90% infill for strength.

3D .STL print file - MP3 microSD player module slip in mount

Mounting for OPEN-SMART MP3 player module. Module slips into base and microSD card can be accessed while mounted.
Print at 90% infill for strength.

3D .STL print file - RTC mount

Allows mounting of MH-Real Time Clock module. Module attaches with two 4-40 screws and is aligned with two posts. The mount has two #6 thru holes for mounting. The completed piece is 1-7/8" x 1-3/4" and elevates the module 1/4".
Print at 90% infill for strength.

3D .STL print file - Slip on knob for 5/16" shaft potentiometer

Slips on over end of standard 5/16" diameter mini poterntiometers commonly found in Arduino kits. Print 50% infill. Knob includes a fingernail indent for "no look" setting.

3D .STL print file - Standoff, #6, 1/4"

Several of the cards have #6 or #4 mounting holes. This is a 1/4" spacer that can be used to support a variety of modules using either #4, #6, or M3 hardware. I know it's a trivial design, but I used almost two dozen on the project.
Fast to print. Print at 90% infill for strength. 1/4" x 1/4".

3D .STL print file - Support bracket for two 1/8" Lexan panels, 4"

Stand off/support for two 1/8" (presumably Lexan, but aluminum would work too) panels. Spacer is 4" x 1/2" x 1/2" and has thru #6 mounting holes on each end as well as two in the long side. Ends are gusseted for strength. Designed to have screw thru matching hole in panel to be held by interior nut.
90% infill for strength.

3D .STL print file - Velleman VMA311 DHT-11 Humidity/Temp Module

Mounts the Velleman VMA311 DHT-11 module. Provides two #6 thru holes for mounting. 1-9/16" x 13/16" x 1/2".
Print at 90% infill for strength,

Schematics

Legend and Remote Control Functions

This is the Legend page that explains the symbology conventions used in the rest of the drawings here,. Also show is the remote and the current functions of each button. No scale.
All other pages are in alphabetical order. See block diagram in STORY for devices.

1" Piezo Speaker Assembly Drawing

Development of 1" piezo speaker assembly. Shows wiring, dimensions, parts list, and picture of final assembly

8x8x4 LED Matrix

8x8x4 LED Matrix assembly drawing for panel mounting.
Scrolling LED display is large and easily readable from across the room. Scrolling function allows long messages to be displayed while short static messages can also be shown. Intensity adjustable to 16 levels.

Buzzer Output Development

Buzzer module.

Coffee Pot Relay Circuit

Coffee Pot relay and NEMA 5-15 cord set (Plug & Outlet) Coffee pot relay comes on with alarm and, if ignored, will automagically go off one hour later. The power state can be manually overridden.
The relay is peak rated at 10A @ 125VAC, however for safety, its de-rated to an 800W limit.

DHT-11 Sensor Development

DHT-11 Assembly drawing
Used to capture temperature and humidity data. Plan is to upgrade to the higher precision DHT-22 in the future.

IR Receiver Development

38kHz IR receiver module development

LDR Module Assembly Drawing

LDR Mounting, wiring, dimensions, and picture.

Micro SD card

Micro SD card assembly and wiring drawing. The microSD card is used for data logging, The temp and humidity are logged every 15 minutes. Additionally the daily highs and lows and their times are logged at midnight. The data file turns over monthly and is named "LOGmm-yy.txt". This one card should hold all the data this system will ever collect.

MP3 Player

MP3 Player with speaker. Uses microSD card with canned phrases/sounds/music for announcing the hours and alarm.
Two 3D mounts are needed, one for the player and a second for the 2" speaker.

Potentiometer and Button Input

The potentiometer and the button work in conjunction to perform analog numerical input. The user is prompted to adjust the dial to the desired value and press the button to enter that value.
This is a fast and intuitive entry method and can be scaled from just two choices to 100 with smooth response.

RGB LED Assembly Drawing

10mm, RGB LED mount, wiring, dimensions, and picture.

RTC Development Drawing

Shows RTC connections and mounting.
Clock uses common CR2032 coin cell for time keeping over power down. Clock is sensitive to PS glitches. Turn off power before connecting.

Traffic Light Display

Traffic Light assembly & wiring drawing.
Three color LED's are used as follows:
RED - Accessory Power ON
YEL - Alarm Set
GRN - Heartbeat (hard to see thru red lexan)
Mounting includes two optional decorative covers. One vertical, one horizontal, both in classic "Federalist" style.

Wooden Case for Clock

Wooden case for clock. 3/4" x 5" x 40". Two of each piece required. Material & Finish to customer specification.
Insert Front Panel BEFORE gluing.
Assemble with wood glue.
Overall size 5" D x 8-3/4" W x 12-3/4" H

Code

Alarm Clock with temp/humidity and EPROM backup

Arduino
TOO BIG TO UPLOAD LATEST VERSION! SORRY - MSG ME HERE IF YOU NEED LATEST CODE
Remote control alarm clock with 8x8x4 LED matrix display. 12/24 hr, alarm set, Temperature F/C mode, displays humidity, chimes the hour, displays date, auto off coffee relay (on with alarm, auto off hr later) Plays MP3 files randomly for wake up alarm as well as scrolling message and beeps. Ack silences alarm.
//
// Cindy's alarm clock - By Mark M. Lambert on February 20th, 2022
// Copyright Mark M. Lambert - All Rights Reserved
//                             May be used or excepted with attribution
//
const float Version = 6.19;
//
// V6.19 - 18Mar22 - MML - Add radio code per original buttons
// V6.18 - 17Mar22 - MML - Try I2C FM module with antenna! :D
// V6.17 - 16Mar22 - MML - Swap ararm set and relay on LED's
// V6.16 - 11Mar22 - MML - Work on MP3 player
// V6.15 - 10Mar22 - MML - Fix bug in alarm on
// V6.14 - 08Mar22 - MML - Turn off log dump & clean up auto off logic
// V6.13 - 06Mar22 - MML - Invert Colon
// V6.12 - 04Mar22 - MML - Install SD card - read data on first compile! :D
// V6.11 - 03Mar22 - MML - Lint
// V6.10 - Omitted to prevent being mistaken for a large rev
// V6.09 - 01Mar22 - MML - Tweak display timing
// V6.08 - 28Feb22 - MML - Indicate power relay state
// V6.07 - 27Feb22 - MML - Resequence 420 & 840, simplify setting
// V6.06 - 26Feb22 - MML - Add clock set (after glitching the RTC)
// V6.05 - 25Feb22 - MML - Wire & rough out radio code - polish
// V6.04 - 24Feb22 - MML - Cleanup & tweak functionality
// V6.03 - 23Feb22 - MML - Add four digit 7 segment display & RTC
// V6.02 - 22Feb22 - MML - Rough out command framework. RTC module due today
// V6.01 - 21Feb22 - MML - LCD, DHT working, 8x8x4 matrix & buzzer up, time displaying
// V6.00 - 20Feb22 - MML - Subset/superset of Rick Box to be custom alarm clock
// V5.18 - 18Feb22 - MML - Fork from RTC project. See for 'tween versions
// V1.00 - 06Dec21 - MML - Baseline with LCD - Mega 2650 has built in pullups
//
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
//
// Device Master List
// ==================
// Mega 2560 Processor                            - Works
// Screw terminal shield                          - Works
// 8x8x4 dot matrix display module MOSI/MISO      - Works
// R/Y/G Traffic Light w/ traffic signal shroud   - Works
// IR remote command in & remote                  - Works
// RTC                                            - Works
// DHT11 Temp/Humidity                            - Works
// MP3 player - Serial Control - for rude wake up messages - Sorta works
//    Has own speaker - no relay needed
// Coffee Pot Relay                               - Works
// SD Card with MISO/MOSI                         - Works
// Reset Button                                   - Works
// Ack Button (also Select)                       - Works
// Analog pot for random input                    - Works
// I2C FM radio module                            - Works
//
// ==================================
// Start of Pin Constants
// ==================================
//
// Analogs first
//
int xx      = 0;            // Placeholder value
//
const int Vnoise  = A2;     // Random number seed is open AI
const int Vin     = A3;     // Power supply direct output monitor - NOT USED
//
const int I2C_A4  = A4;    // RESERVED
const int I2C_A5  = A5;    // RESERVED
//
// Float constant
//
const float aiSteps     = 1024.0;             // 10 bit ADC
const float VoltsPerBit = (5.0 / aiSteps);    // Compute bit weight based on +5 Vin max
const float stepsPerHr  = aiSteps / 24.0;     // Hours input
const float stepsPerMin = aiSteps / 60.0;     // Minutes input
const float stepsPerYr  = aiSteps / 100.0;    // Year input, 20xx presumed
const float stepsPerMon = aiSteps / 12.0;     // Months input
const float stepsPerDt  = aiSteps / 31.0;     // Date input
const float stepsPerDoW = aiSteps / 7.0;      // Day input, sat = 1, fri = 7
const float stepsPerYN  = aiSteps / 2.0;      // 0/1
//
const int Wiper   = A15;    // Wiper from 5k pot for data entry
//
// Analogs end
// -----------
// Now digitals 
//
// D0 and D1 are rx/tx for serial programming
//
// 2-10 sorta spare
//
// D11 & 12 reserved for MOSI/MISO
// D13 is LED_BUILTIN and is used (maybe) by MOSI/MISO devices
//
// Comm channel I/O's
//
const int S3_TX     = 14;     // ESP-01 WiFi - not wired
const int S3_RX     = 15;
//
const int S2_TX     = 16;     // Knob radio - OUT - See I2C
const int S2_RX     = 17;
//
const int S1_TX     = 18;     // MP3 player
const int S1_RX     = 19;
//
// I2C was brought out to buss bars for mass connections - wasn't needed
// Actually the one I2C device (FM radio) connected thru the SDA/SCL pins up by D13
//
const int I2C_SDA   = 20;     // These are attached to the matching buss bars for bussing to all I2C devices
const int I2C_SCL   = 21;     // Pullups are internal to Mega 2560
//
// 22-25 free
//
// RTC I/O pins - Working fine - Use SET_CLOCK project to preset time
//
const int kCePin    = 26;  // Chip Enable - grn
const int kIoPin    = 27;  // Input/Output - yel
const int kSclkPin  = 28;  // Serial Clock - orn
//
// 29-35 free
//
// Humidity/temp sensor - working great
//
const int DHTPIN    = 36;     // Digital pin connected to the DHT sensor
//
// IR remote control input - noisy - doesn't like LED lighting
//
const int RECV_PIN  = 37;     //IR Receiver input pin
//
// On the remote there are seven rows of three buttons each. 
// Each has a unique code. 
// We're going to define the button codes here instead
// of scattering them in a massive SWITCH.
//
// ----------------------
// _00 = _RC, 0 indexed
//
// All the little IR remotes appear to use the same set of codes
// Determined empirically:
//
const int Btn_00  = 23971;  // Top Row, left button - Row 0, Col 0
const int Btn_01  = 25245;  // Row 0, Col 1 - 
const int Btn_02  =  7651;   // Top Row, right button - Row 0, Col 2
//
const int Btn_10  =  8925;   // Row 1, col 0 - (red)
const int Btn_11  =   765;
const int Btn_12  = 15811;
//
const int Btn_20  =  8161;
const int Btn_21  = 22441;
const int Btn_22  = 28561;
//
const int Btn_30  = 26775;
const int Btn_31  = 26521;
const int Btn_32  = 20401;
//
const int Btn_40  = 12495;
const int Btn_41  =  6375;
const int Btn_42  = 31365;
//
const int Btn_50  =  4335;
const int Btn_51  = 14535;
const int Btn_52  = 23205;
//
const int Btn_60  = 17085;
const int Btn_61  = 19125;
const int Btn_62  = 21165;     // Bottom Row, right button
//
// Ordered a differnet brand of IR remote that has a DIFFERENT set of codes!
// Haven't sussed everything out yet but there is at least SOME overlap
// I've got half a dozen of these little remotes and this is the first
// one that isn't a clone. Stay tuned - more to come
//
// ----------------------
//
// 38, 39 - SPARE
//
// Relay pin
//
const int K2        = 40;    // Coffee Pot
//
// 41-42 SPARE
//
const int Buzzer    = 43;     //Annunciator
//
// 44 SPARE
//
const int AckBtn    = 45;   // Acknowledge button to silence alarn
//
// Traffic lights are near top end of DOs
//
const int TL_R      = 46;   // Alarm On
const int TL_Y      = 47;   // Follows AckBtn
const int TL_G      = 48;   // Heartbeat
//
// SD card is MOSI/MISO, needs its own CS line
//
const int sdSel     = 49;   // SD card Chip Select
//
// 50-53 reserved for MOSI/MISO
// SPI:
//  50/25/11 (MISO) <<== not needed for output only
//                      Needed for SD card
//  51/24/12 (MOSI)
//  52/23/13 (SCK)z 
//  53/22/10 (SS)   <<== Device Select
//
// 11, 12 & 13 Are MISO/MOSI - AKA SPI on UNO
// 22-25 reserved for MOSI/MISO SP1 devices
// this conflicts with other docs or it may be
// uno vs mega 2560
//
// Initializes the SD library and card. 
// This begins use of the SPI bus 
// pins 11, 12, and 13 on Arduino UNO boards; 
// pins 50, 51, and 52 on the Mega
// and the chip select pin, 
// which defaults to the hardware SS pin 
// pin 10 on Arduino UNO boards, 
// pin 53 on the Mega 
//
// Got dot matrix and 4 digit readouts in
// The matrix needs MOSI and I think the readouts
// are I2C, but I won't be sure until they arrive
// Modules arrived, waiting on cables
//
// Mega 2560 pinout is correct - worked on first try
//
const int MISOLED   = 50;   // Needed for 8X8 LED matrix comms
const int MOSILED   = 51;
const int SCKLED    = 52;   // builtin LED & SPI clock
const int CSLED     = 53;   // 8x8 x4 LED matrix CS line
//
// End of Digitals
//
// ==================================
// End of Pin Constants
// ==================================
//
// Device Constants
//
const unsigned interval = 1200;  // Don't overpoll
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
//
// EEPROM image - we need pointers, not variables
//
const int promTempFC        =  0;   // Flag - EEPROM BASE - ONLY ADD AT END
const int promAlarmSet      =  1;   // Flag
const int promMatrixBright  =  2;   // Int, 0-15
const int promClockMode12   =  3;   // Flag
const int promScrollRateMs  =  4;   // Int 50 to 100 typical
const int promAlarmHr       =  5;   // Int - 0-23
const int promAlarmMin      =  6;   // Int - 0-59
const int promFrequency     =  7;   // Int - 889-1079, two BYTES
//
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
//
// Includes - DO NOT REORDER
//
#include <EEPROM.h>       //EEPROM support for tables
#include <stdio.h>
#include <DHT.h>          //Digital Humidity/Temp sensor library
#include <DS1302.h>       //RTC library
#include <IRremote.h>     //IR Remote Library
//
// These three includes MUST be in this order or wierd "Error compiling for board Arduino Mega or Mega 2560." error
// Wire.h must load first
//
//#include <Wire.h>               //I2C Driver
//#include <LiquidCrystal_I2C.h>  //I2C LCD display driver for all LCD's
//
#include <LG_Matrix_Print.h>      // LED matrix - test works
//
// microSD card device
//
#include <SPI.h>      // SPI interface
#include <SD.h>       // SD Card
//
File LogFile;         // File object
// ----------------------------------------------------------------------------
//
#include <SoftwareSerial.h>
//
// Define the RX and TX pins to establish UART communication with the MP3 Player Module.
//
static int8_t loud[]  = {0x31, 0x1E, 0x01}; // 1E (max) volume, Play song 03
static int8_t wake[]  = {0x35, 0x02};       // Wake up device
static int8_t Reset[] = {0x35, 0x05};       // Reset device
//
// Define the Serial MP3 Player Module.
//
SoftwareSerial MP3(S1_RX, S1_TX);           // Serial1
//
// ----------------------------------------------------------------------------
//
// I2C FM radio - test says it works - must be below WIRE.H
//
#include <radio.h>        // Generic
#include <TEA5767.h>      // Specific - defines terms like RADIO_BAND_FM, etc./
//
// The band that will be tuned by this sketch is FM.
//
#define FIX_BAND RADIO_BAND_FM    // US
//
// Station 107.9 FM
//
// #define FIX_STATION 1079
#define FIX_STATION 1029
//
TEA5767 radio;    // Create an instance of Class for TEA5767 Chip
//
// ----------------------------------------------------------------------------
//
// Start building objects
//
// Create 8x8x4 Matrix LED object
// Number of 8x8 segments
//
const int segments = 4;
//
LG_Matrix_Print lmd(segments, CSLED);
//
// Create a DS1302 RTC object.
//
DS1302 rtc(kCePin, kIoPin, kSclkPin);
//
// Create humidity/temp object
//
//#define DHTTYPE DHT11   // DHT 11
//DHT dht(DHTPIN, DHTTYPE);     // Create Object
//
DHT dht(DHTPIN, DHT11);     // Create Object
//
// IR Remote control object
//
IRrecv irrecv(RECV_PIN);      // Build IR receiver object
decode_results results;     // Not sure about this, but it works
//
// -------------------------
// Objects built
// -------------------------
//
// Global state flags need to come from EEPROM
//
// We have to manually, byte by byte figure out what goes where
// in the EEPROM. We've got 4k to tinker with and are limited to 
// ~100k write operations on any given memory location. Rears
// are unlimited. So you want to check and only write changes
//
// 0000 - 0FFF - EEPROM range. Everything runs on bytes
//
int BaseAddr = 0;
int TopAddr = 4095;
//
// ******************************
// Stare of EEPROM address pointers
//
// define start of each object in EEPROM - ONLY ADD ITEMS AT END
//
// Declare stuff from EEPROM
//
int clockMode12       = false;
int AlarmSet          = false;
int tempFC            = false;
int MatrixBright      = false;
int scrollRateMs      = false;
int K2State           = false;
int alarmHr           = 11;   //Default to 11:00a
int alarmMin          = 00;
int onbyAlarm         = false;
//
// Time & date setting vars - might not need all
//
int clockDoW;   //Day of week
int clockHr;
int clockMin;
int clockDt;
int clockMon;
int clockYr;
//
int aborted = false;    //Return flag from sloChime and Chime
//
const int MatrixMax   = 15;     // Max intensity value
String MatrixText;   // Indeterminate string
//
// Temp/Humidity
//
float h, f, c;    // Humidity, TempF, TempC
//
int valx = 0;  // variable to store the value read
//
int MSH;          // Pull TWO bytes for numbers > 255
int LSH;
int Frequency;    // Radio station 889-1079
//
// ============================================
// SETUP
// ============================================
//
// Globalize
//
// End of EEPROM address pointers
// ******************************
int thisHr;
int thisMin;
int thisSec;
int thisDate;
int thisMon;
int thisYr;
int thisDay;
//
String logName = "logmm-yy.txt\0";  //Template
//
float tmpF;           // Working float
//
// =================================================
//
// SETUP BEGINS
//
void setup()
{
  //
  // serial is 0/1, s1 is 18/19 - MP3, s2 is 16/17 - radio
  //
  Serial.begin(9600);       // Warm up comm ports
  Serial1.begin(9600);      // MP3 player
  Serial2.begin(9600);      // SPARE
  //
  // Pull config
  //
  clockMode12       = EEPROM.read(promClockMode12 );  //Default to 12 hr time, false for 24, 
  AlarmSet          = EEPROM.read(promAlarmSet    );  // Alarm Set/not, save in EEPROM
  tempFC            = EEPROM.read(promTempFC      );  // Farenheit/not celcius
  MatrixBright      = EEPROM.read(promMatrixBright);  // Start near dim
  scrollRateMs      = EEPROM.read(promScrollRateMs);  // scroll rate, lower is faster
  alarmHr           = EEPROM.read(promAlarmHr     );  // Hour
  alarmMin          = EEPROM.read(promAlarmMin    );  // Minute
  LSH               = EEPROM.read(promFrequency   );  // 889 to 1079 for FM radio
  MSH               = EEPROM.read(promFrequency +1);  // 889 to 1079 for FM radio
  Frequency = word(MSH, LSH);
  //
  // Send this to SD card too
  //
  Serial.println(F("Stored config:"));
  Serial.println(F("=============="));
  Serial.println("AM/PM-24:         " + String( clockMode12));
  Serial.println("Alarm Set:        " + String(    AlarmSet));
  Serial.println("Temp FC:          " + String(      tempFC));
  Serial.println("Matrix Bright:    " + String(MatrixBright));
  Serial.println("Scroll Rate (ms): " + String(scrollRateMs));
  Serial.println("Alarm Hr:         " + String(     alarmHr));
  Serial.println("Alarm Min:        " + String(    alarmMin));
  Serial.println("FM Radio Preset:  " + String(   Frequency));
  //
  // Sanity check for uninitialized EEPROM
  //
  if (clockMode12 != false                        ) clockMode12  = true;
  if (AlarmSet    != false                        ) AlarmSet     = true;
  if (tempFC      != false                        ) tempFC       = true;
  if ((MatrixBright >   15) || (MatrixBright <  0)) MatrixBright =    2;
  //
  if ((scrollRateMs >  200) || (scrollRateMs < 25)) 
    {
    scrollRateMs =   50;
    LogProm(promScrollRateMs, scrollRateMs);
    }
  //
  // Default alarm
  //
  if ((alarmHr      >   23) || (alarmHr      < 0) ) alarmHr      =   11;
  if ((alarmMin     >   59) || (alarmMin     < 0) ) alarmMin     =   00;
  //
  if ((Frequency < 889) || (Frequency > 1079) ) 
    {
    Serial.println("Frequency defaulted");
    Frequency = 1029;  // Yes, force local preset
    }
  //
  tweakFreq(Frequency);
  //
  // Interesting hardware note. If unpowered, the serial port
  // BEGIN statement will bring up the digital portion of the knob FM card
  // just from the serial line - VERY STRANGE
  // But at least it tells me that I'm wired to the right port
  //
  // Fire up RTC
  //
  rtc.writeProtect(false);  //Gotta unlock before letting it run
  rtc.halt(false);          //Let clock run
  //
  Time t    = rtc.time();    // Device data type - read RTC
  thisHr    = t.hr;
  thisMin   = t.min;
  thisSec   = t.sec;
  thisDate  = t.date;
  thisMon   = t.mon;
  thisYr    = t.yr;
  thisDay   = t.day;
  //
  String  tmpStr =  "log";
  if (thisMon < 10) tmpStr = "log0";
  tmpStr += String(thisMon) + "-" + String(thisYr - 2000) + ".txt";
  logName = tmpStr + "\0";
  //
  // DOS FAT 8.3 file names only
  //
  Serial.println("Current Log file: " + tmpStr);
  //
  // INPUT is default but we need to make sure they don't float with INPUT_PULLUP
  // Declare all inputs as pullups
  //
  pinMode(AckBtn, INPUT_PULLUP);      // Alarm acknowledge
  //
  // Relay
  //
  pinMode(K2, OUTPUT);        // Coffee pot
  digitalWrite(K2, false);    // Coffee pot, default off
  //
  pinMode(Buzzer, OUTPUT);
  //
  // Traffic lights
  //
  pinMode(TL_G, OUTPUT);    // Heartbeat
  pinMode(TL_Y, OUTPUT);    // Silent
  pinMode(TL_R, OUTPUT);    // Alarm set
  //
  digitalWrite(TL_G, LOW);   //Turn off  
  digitalWrite(TL_Y, LOW);   //Turn off  
  digitalWrite(TL_R, LOW);   //Turn off  
  //
  // Pins all set, anything not listed is a hi-Z input
  //
  // ============
  //
  // 8x8x4 LED matrix is cool, readable display
  //
  lmd.setEnabled(true);
  lmd.setIntensity(MatrixBright);   // 0 = low, 15 = high - set via pot or LDR or command
  lmd.clear();
  lmd.stopTicker();               //Stop any old message
  //
  chime(1);
  waitTicker("Cindy's Alarm Clock-V" + String(Version));
  //
  // Turn on remote
  //
  irrecv.enableIRIn();
  //
  // Crank up humitity/temp sensor
  //
  dht.begin();      // Fire up temp/humidity sensor
  Read_DHT();       // Read temp/humidity to prime vars
  //
  // Ok fine, we want to test the SD card reader with this code
  //
  if (!SD.begin(sdSel))    // Crank up SD card
    {
    Serial.println(F("SD init failed"));
    }
  //
  // Special stuff for I2C radio module
  //
  // Initialize the Radio 
  //
  radio.init();
  //
  // Enable information to the Serial port
  //
  radio.debugEnable();
  //
  // Preset to 1079
  //
  radio.setBand(FIX_BAND);
  radio.setFrequency(Frequency);    
  //
  radio.setVolume(10);      // 0-15
  radio.setMono(false);     // No external indicator
  //
  // End of radio stuff
  //
  append(F("Restart"));
  //
  // We should see updated data
  //
  // readAfile(logName); // Try to read log file & display - takes forever for long file,
  // slows init
  //
//  chime(2);         // Let em know we're live
  //
  Serial.println("Wake");
  cmdMP3(wake, 2);
  delay(500);
  //
  Serial.println("Reset");
  cmdMP3(Reset, 2);
  delay(500);
  //
  Serial.println("Loud & play");
  cmdMP3(loud, 3);
  delay(500);
  //
  }
// ============================================
//
// SETUP ends
//
// ============================================
// ============================================
//
// LOOP BEGINS
//
// ============================================
//
// Globals
//
String colon = ":";   // Time marker default
//
int tmp;              // Working var
String tmpStr;        // Working strings
String IRval;         // Reading from remote
String LastIRCmd;
int lastSec;
//
// Declaring vars inside SWITCH blows up
//
// Fall into runtime
//
void loop() 
  {
  Time t = rtc.time();  
  // 
  // Let LED matrix running until eom
  // 
  if (!lmd.updateTicker()) lmd.stopTicker();  //Stop at end
  //
  // Fast execution items lead
  //
  // We need to watch the RTC and trigger when the seconds change
  //
  // hard poll IR while waiting
  //
  while (thisSec == lastSec)  // Wait for clock to tick
    {
    t = rtc.time();
    thisHr    = t.hr;
    thisMin   = t.min;
    thisSec   = t.sec;
    thisDate  = t.date;
    thisMon   = t.mon;
    thisYr    = t.yr;
    thisDay   = t.day;
    //
    // IR remote code is big switch.
    // There are 21 different buttons with unique responses
    //
    // IR Remote needs to be shielded from other light sources
    // Umm dunno how fast we should poll this. Its fast polling now. 
    // but we might need to slow it down
    //
    CheckIR();    // Does 21 different functions
    //
    // Update status lights in real time
    //
    digitalWrite(TL_R, digitalRead(K2));    // Reminder that power relay is on is RED
    digitalWrite(TL_Y, AlarmSet);           // Show if alarm is set amber
    //
    if (!lmd.updateTicker()) lmd.stopTicker();  //Stop at end
    //
    }
  //
  // New second passes
  //
  digitalWrite(TL_G, !digitalRead(TL_G));   //Toggle heartbeat on second
  //
  lastSec = thisSec;      // Note this second
  //
  // Alternate : and . as tic-tok on 8x8x4 matrix
  //
  colon == ":" ? colon = "." : colon = ":";
  //
  // Display time
  //
  formTime();
  //
  // Ok, from here on out we have our activities.
  // Each looks at the time and does their thing
  //
  // Check to see if the coffee pot is still on after an hour
  // if so, turn it off
  //
  int tempHr = alarmHr + 1;
  if (tempHr > 23) tempHr = tempHr - 24;  //Compensate for past midnight
  //
  if ( ( (tempHr   ==  thisHr)   && 
         (alarmMin == thisMin) ) && onbyAlarm)
    {
    digitalWrite(K2, LOW);    //Turn off
    onbyAlarm = false;
    K2State   = false;
    //
    Serial.print("K2 Auto off: " + String(tempHr) + " " + String(thisHr) );
    blip();                                 // Little noise to get you to look
    waitTicker(F("Aux relay auto off"));    // Let user know
    formTime();                             // Show time
    chime(3);                               // Different
    }
  //
  // Check for alarm time being set
  //
  if (  (alarmHr  ==  thisHr) && 
        (alarmMin == thisMin) && 
        (thisSec  ==       0) &&
         AlarmSet)
    {
    //
    // Kick on coffee pot relay - we should turn this
    // off automatically  after an hour
    //
    blip();                     // prealarm noise
    digitalWrite(K2, HIGH);     // Load comes on with alarm
    K2State   = true;           // Internal state flag - Y not read IO?
    onbyAlarm = true;           // Alarm turned us on
    //
    // Wakey Wakey!
    //
    waitTicker(F("Wakey! Wakey!"));
    formTime();  //Show time
    //
    sloChime(10);
    if (!aborted)
      {
      waitTicker(F("Get up & PEE! The world's on FIRE!"));
      formTime();  //Show time
      //
      sloChime(10);
      if (!aborted) 
        {
        waitTicker(F("Outta bed, sleepy head!"));
        formTime();  //Show time
        sloChime(10);
        }
      //
      }
    //
    }
    //
    // Show temp at 1/4 past minute
    //
    if ( (thisSec == 15) && !lmd.updateTicker() )
      {
      Read_DHT();       // Read temp/humidity
      tempFC ? lmd.ticker(String(f, 1) + "F", scrollRateMs) : 
               lmd.ticker(String(c, 1) + "C", scrollRateMs);
      //
      }
    //
    // Show Date at 30 - add month names
    //
    if ( (thisSec == 30) && !lmd.updateTicker() ) 
          lmd.ticker(String(thisMon) + 
          "/" + String(thisDate) + 
          "/" + String(thisYr).substring(2), scrollRateMs);
    //
    // Show RH at 3/4 of minute - use reading from :15
    //
    if ( (thisSec == 45) && !lmd.updateTicker() )
          lmd.ticker(String(h, 0) + "% RH", scrollRateMs);
  //
  // Min = 0 & sec = 0 = hour strike
  //
  if ( (thisMin == 0) && (thisSec == 0) )
    {
    blip();               //Little noise to make you look
    waitTicker(F("Cuckoo! Cuckoo!"));
    formTime();           // Show time
    //
    tmp = thisHr;         // Time from globals
    //
    if ((tmp > 12) && (clockMode12)) tmp = tmp - 12;   //Civilian time
    //
    // Start and end times need to be selectable
    if ((thisHr > 10) && (thisHr < 23)) // Only chime 11A-11P per swmbo
      {
      sloChime(tmp);    //Change to MP3 "Bong" when I get it installed
      }
    //
    }
  //
  // Later in execution overrides time display above
  //
  if ((thisHr == 16) && (thisMin == 20) && (thisSec == 0))
    {
    blip();     //Little noise to make you look
    waitTicker(F("It's 4:20! SMOKE BREAK!"));
    formTime();           // Show time
    sloChime(4);
    //
    waitTicker(F("It's 4:20! Time to roll a fattie!"));
    formTime();           // Show time
    sloChime(2);
    }
  //
  if ((thisHr == 20) && (thisMin == 40) && (thisSec == 0))
    {
    blip();     //Little noise to make you look
    waitTicker(F("It's 8:40! TIME TO SMOKE!"));
    formTime();           // Show time
    sloChime(8);
    //
    waitTicker(F("It's 8:40! Time to Relax!"));
    formTime();           // Show time
    sloChime(4);
    }
  //
  // Log data on 1/4 hour
  //
  if ( ((thisMin ==  0) || 
        (thisMin == 15) || 
        (thisMin == 30) || 
        (thisMin == 45)) && (thisSec == 0))
        append("");   // Append to log
  //
  }     // End of LOOP
// =============================================================================
// =============================================================================
// Functions
// =============================================================================
// =============================================================================
//
// Function to read and display Temp Humidity from sensor
//
void Read_DHT()
  {
  //
  // Reading temperature or humidity takes about 250 milliseconds!
  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  //
  h = dht.readHumidity();         // Get humidity
  c = dht.readTemperature(false); // Read temperature as Celsius (the default)
  f = dht.readTemperature(true);  // Read temperature as Fahrenheit (isFahrenheit = true)
  //
  }
//
// ==================================================
//
// Ring the bell very short
//
void blip()
{
digitalWrite(Buzzer, true);
delay(20);
digitalWrite(Buzzer, false);
}
// ==================================================
//
// Ring the bell rings times with .1 sec between beeps
//
void chime(int rings)
{
if (rings > 12) rings = rings - 12; //Fudge for midnight rollover
//
for (int i = 0; i < rings; i++)
  {
  if (!digitalRead(AckBtn)) break;
  //
  digitalWrite(Buzzer, true);
  delay(100);
  digitalWrite(Buzzer, false);
  if (!digitalRead(AckBtn)) break;
  //
  delay(100);
  }
//
}
// ==================================================
//
// Ring the bell rings times with 1 sec between beeps
//
void sloChime(int rings)
{
aborted = false;
//
if (rings > 12) rings = rings - 12; //Fudge for midnight rollover
//
for (int i = 0; i < rings; i++)
  {
  if (!digitalRead(AckBtn))
    {
    aborted = true;
    break;
    }
  //
  // Don't abort in middle of pulse, sound stays on forever
  //
  digitalWrite(Buzzer, true);
  delay(200);
  digitalWrite(Buzzer, false);
  if (!digitalRead(AckBtn))
    {
    aborted = true;
    break;
    }
  //
  delay(800);
  }
//
}
// ==================================================
//
void CheckIR()
{
//
// This is the master remote control switch. 
// See discussion on button structure near front
//
if (irrecv.decode(&results))
  {
    // Display what we got
    //
    valx     = abs(results.value);   // no negatives
    IRval    = String( abs(valx) );   // no negatives
    IRval.trim();   //Strange Syntax
    LastIRCmd.trim();
    //
    // Catch value
    //
    if ( (valx > 0) && (LastIRCmd != IRval))
    {
      if (IRval != "")
      {
      LastIRCmd = IRval;
      LastIRCmd.trim();
      }
    //
    }
    //
    // Live command when received
    //
    Serial.print(F("RXD:  = "));
    valx = abs(valx);
    Serial.println(valx);
    //
    // Ok, No silly negatives, Each button sends a code, both remotes send
    // the same code for the same physical button
    //
    // =============================
    // =============================
    // Main control switch on remote command
    // =============================
    // =============================
    //   
    if (valx >= 1)      // Triple filter out negs
        {
        //
        // At this point all 21 buttons have an
        // assigned (if not yet fully implemented) function
        //        
        // ======================
        // Radio    Alarm   AM/PM
        // On/off   Device  24 hr
        // ----------------------
        // Temp     Full    F/C
        // Humidity Date  
        // ----------------------
        // Dim      Bright  Set
        //                  Clock
        // ----------------------
        // Show     Toggle  Set
        // Alarm    Alarm   Alarm
        // ----------------------
        // Prev     Next    Vol
        // Sta      Sta     Up
        // ----------------------
        // Station  Station Vol
        // "A"      "B"     Dn
        // ----------------------
        // Set      Dim     Mute
        // Sta A/B
        // ======================
        //
        switch (valx)    //Needs integer for switch
          {
          //
          // Button codes in table up front - all tested & work
          //
          case Btn_00:
            Serial.println(F("btn 00"));
            chime(1);
            //
          break;
          // =================
          case Btn_01:
            Serial.println(F("btn 01"));
            chime(1);
            K2State = !K2State;   // Toggle state
            K2State ? MatrixText = "Power ON" : MatrixText = "Power Off";
            //
            onbyAlarm = false;        // Manual control, cancel auto flag
            //
            // Save for power blip
            //
            digitalWrite(K2, K2State);    // Tell hardware
            lmd.ticker(MatrixText, scrollRateMs);
            //           
          break;
          // =================
          case Btn_02:
            Serial.println(F("btn 02"));
            chime(1);
            //
            // Toggle 12/24 hour
            //
            clockMode12 = !clockMode12;
            clockMode12 ? MatrixText = "AM/PM" : MatrixText = "24 hour";
            //
            lmd.ticker(MatrixText, scrollRateMs);
            LogProm(promClockMode12, clockMode12);
            //
          break;
          // =================
          // 2nd Row
          //
          case Btn_10:
            Serial.println(F("btn 10"));
            chime(1);
            //
            // Display Temperature
            //
            // Check FC flag to show in C or F
            //
            tempFC ? MatrixText = String(f, 1) + "F/" : 
                     MatrixText = String(c, 1) + "C/";
            //
            lmd.ticker(MatrixText + String(h, 0) + "% RH", scrollRateMs);
            //
          break;
          // =================
          case Btn_11:
            Serial.println(F("btn 11"));
            chime(1);
            //
            // Display date - I know there's a smarter way,
            // but my C++ skills aren't that sharp about arrays
            //
            tmpStr = "";
            switch (thisDay)
            {
              case 1:
              tmpStr = "Sun";
              break;
              // ==========
              case 2:
              tmpStr = "Mon";
              break;
              // ==========
              case 3:
              tmpStr = "Tues";
              break;
              // ==========
              case 4:
              tmpStr = "Wednes";
              break;
              // ==========
              case 5:
              tmpStr = "Thurs";
              break;
              // ==========
              case 6:
              tmpStr = "Fri";
              break;
              // ==========
              case 7:
              tmpStr = "Satur";
              break;
              // ==========
            }
            //
            lmd.ticker(tmpStr + "day, " + 
                      String(thisMon) + "/" + 
                      String(thisDate) + "/" + 
                      String(thisYr), scrollRateMs);
            //
          break;
          // =================
          case Btn_12:
            Serial.println(F("btn 12"));
            chime(1);
            //
            // Toggle F/C
            //
            tempFC  = !tempFC;  //Flip flag
            tempFC ? lmd.ticker(String(f, 1) + "F", scrollRateMs) : 
                     lmd.ticker(String(c, 1) + "C", scrollRateMs);
            //
            LogProm(promTempFC, tempFC);
            //
          break;
          // =================
          // 3rd Row
          //
          case Btn_20:
            Serial.println(F("btn 20"));
            chime(1);
            //
            // Dim matrix
            //
            MatrixBright--;
            if (MatrixBright < 0) MatrixBright = 0;
            //
            lmd.setIntensity(MatrixBright);   // 0 = low, 15 = high
            LogProm(promMatrixBright, MatrixBright);
            //
          break;
          // =================
          case Btn_21:
            Serial.println(F("btn 21"));
            chime(1);
            //
            // Brighten matrix
            //
            ++MatrixBright;
            if (MatrixBright > MatrixMax) MatrixBright = MatrixMax;
            //
            lmd.setIntensity(MatrixBright);   // 0 = low, 15 = high
            LogProm(promMatrixBright, MatrixBright);
            //
          break;
          // =================
          case Btn_22:
            Serial.println(F("btn 22"));
            chime(1);
            //
            // Need to have a check to prevent going
            // thru this needlessly.
            // Allow bail out after time set
            // don't force date setting
            //
            waitTicker(F("Are You Sure? 00-No, 01-Yes:"));
            tmp = waitForIt(stepsPerYN, "Ok: ", 0, "");
            delay(250);
            chime(1);
            if (tmp == 1)
              {
              //
              // Ok, set Clock Hour
              //
              chime(1);
              waitTicker(F("Dial Hour:"));
              clockHr = waitForIt(stepsPerHr, "", 0, ":xx" );
              chime(1);
              //
              // Now get minute
              //
              waitTicker(F("Dial Min:"));
              // M is wider than normal, need to lose the :
              clockMin = waitForIt(stepsPerMin, String(clockHr) + ":", 0, "");
              chime(1);
              //
              // Do they wanna bail?
              waitTicker(F("Set Date? 00-No, 01-Yes:"));
              if (waitForIt(stepsPerYN, "Ok: ", 0, "") == 1)
                {
                chime(1);
                //
                // Now get month
                //
                delay(250);
                waitTicker(F("Dial Month:"));
                // M is wider than normal, need to lose the :
                clockMon = waitForIt(stepsPerMon, "", 1, "/xx");
                chime(1);
                delay(250);
                //
                // Now get date
                //
                waitTicker(F("Dial Date:"));
                clockDt = waitForIt(stepsPerDt, String(clockMon) + "/", 1, "");
                chime(1);
                //
                // Year = error trap
                //
                clockYr = 2021;
                while (clockYr < 2022)
                  {
                  waitTicker(F("Dial Year:"));
                  clockYr = 2000 + waitForIt(stepsPerYr, "20", 0, "");
                  }
                //
                chime(1);
                //
                // Now get day of week
                //
                waitTicker(F("Dial Day, Sun-01, Sat-07:"));
                clockDoW = waitForIt(stepsPerDoW, "Day:", 1, "");
                }
              else
                {
                //
                // Use current date
                //
                clockYr   = thisYr;
                clockMon  = thisMon;
                clockDt   = thisDate;
                clockDoW  = thisDay;
                }
              //
              // We need to wait for operator to click
              // to sync setting. Need to write data 
              // to RTC after go
              //
              chime(1);
              delay(250);
              waitTicker(F("Press to sync ..."));
              chime(1);   // Ack
              lmd.stopTicker();
              lmd.clear();
              lmd.printText(0, "NOW!");            
              lmd.display();
              //
              while (digitalRead(AckBtn))
                {
                 // Wait for press 
                }
              //
              blip();
              //
              // Write time to RTC
              //
              setRTC(clockYr, clockMon, clockDt, clockHr, clockMin, clockDoW);
              //
              chime(2);   // Ack
              //
              }
            //
            //
          break;
          // =================
          // 4th Row
          //
          case Btn_30:
            Serial.println(F("btn 30"));
            chime(1);
            //
            // Alarm show
            //
            MatrixText = "Alarm Time: " + String(alarmHr) + ":";
            if (alarmMin < 10) MatrixText += "0";
            MatrixText += String(alarmMin);
            //
            AlarmSet ? MatrixText += " ON" :  MatrixText += " OFF"; 
            //
            lmd.ticker(MatrixText, scrollRateMs);
            //
          break;
          // =================
          case Btn_31:
            Serial.println(F("btn 31"));
            chime(1);
            //
            // Alarm on/off
            //
            AlarmSet = !AlarmSet;
            if (AlarmSet)
              {
              MatrixText = "Alarm Set On: " + String(alarmHr) + ":";
              if (alarmMin < 10) MatrixText += "0";
              MatrixText += String(alarmMin);
              }
            else
              {
              MatrixText = "Alarm Off";
              chime(1);   // Twice is off - like car alarm
              }
            //
            onbyAlarm = false;
            lmd.ticker(MatrixText, scrollRateMs);
            LogProm(promAlarmSet,   AlarmSet);
            //
          break;
          // =================
          case Btn_32:
            Serial.println(F("btn 32"));
            chime(1);
            //
            // Ok, set Alarm Hour
            //
            waitTicker(F("Dial Hour:"));
            alarmHr = waitForIt(stepsPerHr, "", 0, ":xx");
            chime(1);
            //
            // Now get minute
            //
            waitTicker(F("Dial Min:"));
            alarmMin = waitForIt(stepsPerMin, String(alarmHr) + ":", 0, "");
            chime(1);
            delay(250);
            //
            tmpStr = "Alarm On: " + String(alarmHr) + ":";
            if (alarmMin < 10) tmpStr += "0";
            //
            tmpStr += String(alarmMin);
            waitTicker(tmpStr);
            chime(1);
            //
            // Save and turn alarm on automatically
            //
            AlarmSet  = true;
            onbyAlarm = false;          // Clear old state
            //
            // Retain state
            //
            LogProm(promAlarmHr,     alarmHr);
            LogProm(promAlarmMin,   alarmMin);
            LogProm(promAlarmSet,   AlarmSet);
            //
            chime(2);   // Ack
            //
          break;
          // =================
          // 5th Row
          // Originally this was for FM radio control
          // Since I had no luck getting it to run
          // OUT - these buttons can be used for the
          // MP3 or other control functions
          //
          case Btn_40:
            Serial.println(F("btn 40"));
            chime(1);
            //
            tmp = radio.getFrequency();
            --tmp;    // Dump once
            tweakFreq(tmp);
            //
          break;
          // =================
          case Btn_41:
            Serial.println(F("btn 41"));
            chime(1);
            //
            tmp = radio.getFrequency();
            ++tmp;    // Bump thrice
            ++tmp;
            ++tmp;
            tweakFreq(tmp);
            //            
          break;
          // =================
          case Btn_42:
            Serial.println(F("btn 42"));
            chime(1);
            //
            // Volume Up doesn't work on this
            // particular module. It's a preamp
            // so it's always at full blast or muted
            //
            // Old test function            
            // Test button for MP3 player
            //
            Serial.println("Play 3");
            playSong(3);
            //
          break;
          // =================
          // 6th Row
          //
          case Btn_50:
            Serial.println(F("btn 50"));
            chime(1);
            //
            // Saved Station "A"
            //
//            radioCmd = "AT+FRE=" + String(freqA);
            //
            break;
          // =================
          case Btn_51:
            Serial.println(F("btn 51"));
            chime(1);
            //
            // Saved Station "B"
            //
//            radioCmd = "AT+FREQ=" + String(freqB);
            //
          break;
          // =================
          case Btn_52:
            Serial.println(F("btn 52"));
            chime(1);
            //
            // Volume Down doesn't work on this
            // particular module. It's a preamp
            // so it's always at full blast or muted
            //
         break;
          // =================
          // 7th and last Row
          //
          case Btn_60:
            Serial.println(F("btn 60"));
            chime(1);
            //
            // This will allow setting of
            // the station presets selected
            // above at btn_50 and btn_51
            //
            tmp = waitForIt( 1024/200, "F:", 881, "");
            tweakFreq(tmp);
            //
          break;
          // =================
          case Btn_61:
            Serial.println(F("btn 61"));
            chime(1);
            //
            // Toggle matrix
            // Dim/restore display
            //
            (MatrixBright != 0) ? MatrixBright = 0 : 
                                  MatrixBright = EEPROM.read(promMatrixBright);
            //
            lmd.setIntensity(MatrixBright);   // 0 = low, 15 = high
            //
            // Since we didn't update EEPROM the preset
            // brightness will restore on next reset
            //
          break;
          // =================
          case Btn_62:
            Serial.println(F("btn 62"));
            chime(1);
            //
            // Toggle radio Mute & display frequency
            //
            radio.setMute(!radio.getMute() );
            //
            showFM();
            //
          break;
          // =================
          //
          // SWITCH VAL ENDS
          //
          }
        //
      //
      }   // If Val ends
    //
    // Resume IR control - not really sure what this does.
    //
    irrecv.resume();    
    }   // Decode ends
  //
}
// ==================================================
//
// Updates given EEPROM *BYTE* address with value, ints > 255 need TWO!
//
void LogProm(int Addr, int Valu)
{
EEPROM.update(Addr, Valu);
}
// ==================================================
//
// Keep marquee spinning - no DELAY's allowed
//
void dillyDally(int mills2wait)
{
//
int rightNow = millis();
//
while (millis() < rightNow + mills2wait)
  {
  if (!lmd.updateTicker()) 
    {
    lmd.stopTicker();  //Stop at end    
    lmd.clear();
    }
    //
  }
//
}
// ==================================================
//
// Spin hard letting ticker run - data from globals
//
void waitTicker(String Msg)
{
lmd.ticker(Msg, scrollRateMs);
//
  while (lmd.updateTicker())
  {
  if (!digitalRead(AckBtn)) 
    {
    aborted = true;   //Pass up food chain
    break;
    // =========
    }
  //
  }
//
}
// ==================================================
//
// Waits for input via pot, ended with ackbtn
//
// pot input 0-1023 is scaled by steps
// input is prompted with cue
// 0/1 origin is set with bias
//
// Scaled value returned
//
int waitForIt(float steps, String cue, int bias, String Suffix)
{
//
String tmpCue = cue;
int myTmp;  //Return var
//
lmd.stopTicker();
lmd.clear();
//
while (digitalRead(AckBtn))
  {
  tmpCue = "";   // Unpad for next loop
  myTmp  = (analogRead(Wiper) / steps);
  if (cue == "")
    {
    // 1st col - pad with blank
    if (myTmp < 10) tmpCue = " ";
    lmd.printText(0, tmpCue + String(myTmp + bias) + Suffix, true);  //0 index
    }
  else
    {
    // 2nd col - pad with zero
    if (myTmp < 10) tmpCue = "0";
    lmd.printText(0, cue + tmpCue + String(myTmp + bias) + Suffix, true);  //0 index
    }
  //
  lmd.display();
  delay(100);   //Can't dillydally static display
  lmd.clear();
  }
//
while (!digitalRead(AckBtn))   // Wait on release
  {
  }
//
chime(1);
return myTmp + bias;
//
}
// ==================================================
//
void setRTC(int yr, int mo, int dt, int hr, int mn, int dow)
{
rtc.writeProtect(false);
rtc.halt(true);  //Shouldn't this be TRUE?
//
// Make a new time object to set the date and time.
// Sunday, September 22, 2013 at 01:38:50.
//
// Full spec includes seconds and DoW
//
// Time t(yr, mo, dt, hr, mn, 00, Time::kTuesday);
//
// Load the object, will it puke w/o secs & DoW?
//
Time t(yr, mo, dt, hr, mn, 00, dow);
//
// Set the time and date on the chip.
//
rtc.time(t);
//  
rtc.halt(false);          //Let clock run
rtc.writeProtect(true);   //Only protect AFTER letting it run
//
}
// ==================================================
//
void formTime()
{
String outStr;
int wrk = thisHr;   // Time from globals
//
if ((wrk > 12) && (clockMode12)) wrk = wrk - 12;   //Civillian time
//
clockMode12 ? outStr = " " : outStr = "0";     // Pad leader
//
if (wrk >= 10) outStr = "";  // No pad needed
//
outStr += String(wrk) + colon;     // Tick tock symbol
//
if (thisMin < 10) outStr += "0";    // Pad leading zero on minutes
outStr += String(thisMin);
//
// Send time to 8X8x4 LED matrix display if not busy
//
if (clockMode12) ((thisHr < 12) ? outStr += "a" : outStr += "p");
//
if (!lmd.updateTicker())
  {
  lmd.stopTicker();     // Kill scrolling msg (pro forma)
  lmd.clear();          // Blank
  lmd.printText(0, outStr, true);       // 0 index static text
  lmd.display();        // Display
  }
//
}
// ==================================================
//
// Clean up 
//
void readAfile(String fileName)
{
//
// Crack open file and return file handle
//
int bytes = 0;    // Bytes read
//
// filename is being returned as a number?
File aFile = SD.open(String(fileName)); //Returns handle or false on fail
if (aFile)      // Success!
  {
  Serial.println("Reading: " + fileName);
  //
  // read from the file until there's nothing else in it:
  //
  while (aFile.available())   // not EOF
    {
    // Byte level copy?
    //
    Serial.write(aFile.read());   // Copy byte to comm port for visibility
    bytes++;        // Goose counter
    }
  //
  // close the file
  //
  aFile.close();
  //
  // Summary to serial port
  //
  Serial.println(String(bytes) + " bytes read");
  //
  }
//
}
// ==================================================
//
// Append to log file
//
void append(String lineOtext)
{
// Pass a line of text. Filename is given
//
File aFile;
Serial.println("Opening: " + logName);
aFile = SD.open(logName, FILE_WRITE); //Returns handle or false on fail
if (aFile)      // Success!
  {
  //
  String Hr = String(thisHr);
  if (thisHr < 10) Hr = "0" + Hr;
  String Mn = String(thisMin);
  if (thisMin < 10) Mn = "0" + Mn;
  aFile.println(" D&T: " + String(thisMon) + "/" + String(thisDate)
                         + " @ " + Hr + ":" + Mn );
  aFile.println("Temp: " + String(f, 1) + "F");
  aFile.println("RH %: " + String(h, 0));   // Data is integer
  //
  // Only append if not blank
  //
  if (lineOtext != "") aFile.println("[" + lineOtext + "]");
  //
  aFile.close();
  //
  Serial.println(F("Log updated"));
  }
else
  {
  Serial.println("Pbbbt! Failed file open of " && logName);
  }
//
}
// ==================================================
//
// Send random command to player
//
void cmdMP3(int8_t command[], int len)
{
//
// Rewrite to compute length and prefix/suffix automatically
//
Serial.print("7E ");
Serial1.write(0x7E);    // Prefix
//
Serial.print(String(len +1, HEX));
Serial.print(" "); // Pad with blank for readability
Serial1.write(len +1);    // Bytes to follow including suffix
//
for(int i = 0; i < len; i++)
  { 
  //
  Serial1.write(lowByte( command[i] ) );    // Send byte
  Serial.print(String( lowByte( command[i] ), HEX));  // mask LSH to prevent minus extend
  Serial.print(" "); // Pad with blank for readability
  }
//
Serial.println("EF");
Serial1.write(0xEF);    // Suffix
//  
}
// ==================================================
//
void playSong(int song)
{
//
// Rewrite to compute length and prefix/suffix automatically
//
Serial1.write(0x7E);    // Prefix
Serial1.write(0x04);    // Bytes to follow including suffix
Serial1.write(0x31);    // Set Volume command & play #
Serial1.write(0x1E);    // Volume to full
//
Serial1.write(0x01);    // Send song #
//
Serial1.write(0xEF);    // Suffix
//  
}
// ==================================================
//
// Display station from radio
//
void showFM()
{
tmpF = radio.getFrequency();
waitTicker("FM " + String(++tmpF / 10, 1) );  
}
// ==================================================
//
void tweakFreq(int Freq)
{
//
// Sanity check
//
if (Freq <  889) Freq =  889;
if (Freq > 1079) Freq = 1079;
//
Serial.println("Set FM: " + String(Freq));
radio.setFrequency(Freq);       // Tell radio
LogLong(promFrequency, Freq);   // Save in EPROM
//
showFM();           // Display freq
//
}
// ==================================================
//
void LogLong(int Addr, int Valu)
{
//
EEPROM.update(Addr    , lowByte( Valu));
EEPROM.update(Addr + 1, highByte(Valu));
}
// ==================================================
// ==================================================
// FIN
// ==================================================
// ==================================================

Credits

madmark2150
5 projects • 1 follower

Comments