aI was hoping that someone familiar with coding for the Arduino would be willing to take a read of my code to see if I am doing anything egregious. I consider myself an accopmplished coder, but two things bring me to ask for this favor. I) I am relatively new to C, II) The Arduino's memory structure is still a bit of a mystery (in a function, is there any advantages to including the static qualifier on constants?) and III) this is a project that I've been working on for someone else (who happens to be my boss) and I want to make the code as bullet-proof as possible.
So if you can spare a wee bit o'time, Happy Holidays and take a look. If not, I understand and Happy Holidays to you, too.
A little background. This is for a motivational device that will sit on our desktops. It will look like a rock for that is what represents. a "Rock" or solid business goal. The goal and it's due date will scroll across a two line LCD screen. Occasionally, the rock will say an encouraging message. If you are working on the goal/rock, you can press a button and the rock will acknowledge your effort. However, if the due date approaches, the rock will warn you that you are running out of time. And if you are really close, you will get a stern message. But if you are done, you hold the button down and confirm that you are finished and you will get a congratulatory message and a musical tribute (The Stars and Stripes Forever).
The Arduino has a real time clock, SD card drive, MP3 player with amplifier, LCD Display and an interrupt driven button for input
.
Moderators note : change code tags to C
So if you can spare a wee bit o'time, Happy Holidays and take a look. If not, I understand and Happy Holidays to you, too.
A little background. This is for a motivational device that will sit on our desktops. It will look like a rock for that is what represents. a "Rock" or solid business goal. The goal and it's due date will scroll across a two line LCD screen. Occasionally, the rock will say an encouraging message. If you are working on the goal/rock, you can press a button and the rock will acknowledge your effort. However, if the due date approaches, the rock will warn you that you are running out of time. And if you are really close, you will get a stern message. But if you are done, you hold the button down and confirm that you are finished and you will get a congratulatory message and a musical tribute (The Stars and Stripes Forever).
The Arduino has a real time clock, SD card drive, MP3 player with amplifier, LCD Display and an interrupt driven button for input

C:
/* Wave Rock v4
11/17/2015 08:17
Function List
-------------
void setup ()
void loop()
void backlit(byte myPin)
void checkButton()
boolean confirm()
long Date2Epoch(String myDate)
void flash()
boolean readln_Rocks(String& myDate, long& myDateL, String& myMessage)
void playMessage(char myType, char mySelection)
void scrollMessage(String myMessage)
*/
//*****************************************************************
//*****************************************************************
// include necessary libraries
#include <SPI.h> // for talking to Music Maker shield
#include <Adafruit_VS1053.h> // MusicMaker library
#include <SD.h> // SD card library
#include <Wire.h> // I2C library
//#include <Adafruit_MCP23008.h>
#include <LiquidCrystal.h> // LCD Display (Backpack) library
#include "RTClib.h" // DS1307 Real Time Clock library
#include <Time.h> // UTC Epoch time converter
// define the pins used for the MusicMaker shield
#define CLK 13 // SPI Clock, shared with SD card
#define MISO 12 // Input data, from VS1053/SD card
#define MOSI 11 // Output data, to VS1053/SD card
#define SHIELD_CS 7 // VS1053 chip select pin (output)
#define SHIELD_DCS 6 // VS1053 Data/command select pin (output)
#define CARDCS 4 // Card chip select pin
#define DREQ 3 // VS1053 Data request, ideally an Interrupt pin
#define SDCS 10 // SD chip select
// Number of rocks to display
const byte MAX_ROCKS = 3;
// Constants which define the various states the rock may be in
// #define WARN_DAYS 1209600L // 14 days before due date when in warn condition. WARN_DAYS will always be greater than ALARM_DAYS
// #define ALARM_DAYS 864000L // 10 days before due date when in alarm condition. ALARM_DAYS will always be less than WARN_DAYS
#define WARN_DAYS 30L // Test seconds before the end of the display time
#define ALARM_DAYS 10L // Test seconds before the end of the display time
/* Note that variables whose name ends in "Date" or "Days" are in Unixtime format;
Their units are in seconds. Those variables which end in "Time" are milliseconds
and are used with the millis() function. Hence, their units are in ms.
*/
// LCD-related values
#define COLUMNS_LCD 16
#define ROWS_LCD 2
#define MESSAGE_LEN 64
const byte RED_PIN = 28;
const byte GREEN_PIN = 26;
const byte BLUE_PIN = 24;
// or 44,45,46 for PWM control
// or 24,26,28 for digital control
// Button input related values
static const byte BUTTON_PIN = 2;
static const int STATE_NORMAL = 0; // no button activity
static const int STATE_SHORT = 1; // short button press
static const int STATE_LONG = 2; // long button press
volatile int resultButton = 0; // global value set by checkButton()
// Message parameters
static const byte MAX_MSGS = 3; // number of messages per state.
static const byte ENC_PROB = 65; // probability of an encouraging message
static const byte INTERCHAR_DELAY = 125; // pause between characters on scrolling display
static const long DISPLAY_CYCLE_SECS = 120L;
// definition of "rock" variables
String rockDuedate[MAX_ROCKS]; // Due date in mm/dd/yy character format
long rockDuedateL[MAX_ROCKS]; // Due date in UNIX time format
String rockMessage[MAX_ROCKS]; // The actual message (up to characters)
TimeElements rockDuedate_t; // used to gerate unixtime stamp from date string
long todayDate = 0;
// File I/O objects
File RockMessages;
//File RockLogging;
// Initialize the Music Maker shield
Adafruit_VS1053_FilePlayer audioplayer =
Adafruit_VS1053_FilePlayer(SHIELD_CS, SHIELD_DCS, DREQ, CARDCS);
// LCD, connected via i2c, default address #0
LiquidCrystal lcd(0);
// Initialize the Real Time Clock
RTC_DS1307 rtc;
//*****************************************************************
//*****************************************************************
void setup () {
Serial.begin(57600);
byte indexRock = 0;
randomSeed(analogRead(A0)); // initialize pRNG
// set up the LCD's number of rows and columns:
Serial.println(F("Initializing LCD & backlight"));
pinMode(RED_PIN, OUTPUT); // initializing backlight pins
pinMode(GREEN_PIN, OUTPUT);
pinMode(BLUE_PIN, OUTPUT);
lcd.begin(16, 2);
Serial.println(F("Backlight initialized"));
// Print a message to the LCD.
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" Rock Keeper!");
Serial.println(F("Rock Master!"));
// initialize input button pins
Serial.println(F("Initializing Button pin"));
pinMode(BUTTON_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), checkButton, CHANGE);
Serial.println(F("Button pin initialized"));
Serial.println(F("Initializing RTC"));
rtc.begin();
Serial.println(F("RTC initialized"));
Serial.println(F("inititialize audio player/MusicMaker"));
audioplayer.useInterrupt(VS1053_FILEPLAYER_PIN_INT); // DREQ int
if (! audioplayer.useInterrupt(VS1053_FILEPLAYER_PIN_INT)) {
Serial.println(F("DREQ pin is not an interrupt pin"));
}
if (! audioplayer.begin()) { // initialise the music player
Serial.println(F("Couldn't find VS1053, do you have the right pins defined?"));
while (true);
}
Serial.println(F("VS1053 found"));
// initializa SD card to read Rocks
Serial.print(F("Initializing SD card..."));
pinMode(SDCS, OUTPUT);
if (!SD.begin(CARDCS)) {
Serial.println(F("initialization failed!"));
while (true);
}
Serial.println(F("initialization done."));
if (SD.exists("Rock.csv")) {
Serial.println(F("Rock.csv exists."));
} else {
Serial.println(F("Rock.csv doesn't exist."));
while (true);
}
Serial.println(F("Initializing -Rocks-")); // read in rocks from file
Serial.println(F("Opening Rock.csv..."));
RockMessages = SD.open("Rock.csv"); // open file
Serial.println(F("read in rocks from file"));
for (indexRock = 0; indexRock < MAX_ROCKS; indexRock++) { // initialize all "rocks"/ read in from SD card
readln_Rocks(rockDuedate[indexRock], rockDuedateL[indexRock], rockMessage[indexRock]);
// Serial.print(rockDuedate[indexRock]); Serial.print(F(" "));
// Serial.print(rockDuedateL[indexRock]); Serial.print(F(" "));
// Serial.println(rockMessage[indexRock]);
}
RockMessages.close();
DateTime now = rtc.now(); // Test
rockDuedateL[0] = now.unixtime() + 999L; // Test
rockDuedateL[1] = now.unixtime() + 999L; // Test
rockDuedateL[2] = now.unixtime() + 999L; // Test
Serial.println(F("-Rocks- initialized"));
playMessage('5', '0');
Serial.println(F("Setup complete"));
}
//*****************************************************************
//*****************************************************************
void loop() {
// ----------------------------
char setSelection = '\0';
boolean ack = false, enc = false, wrn = false, alm = false, cmp = false;
long endTime = 0;
long endWait = 0;
long promptTime = 0;
long differenceDate = 0;
byte indexRock = 0;
byte Bckg = 0;
static boolean displayTime = true;
static boolean allDone = false;
while (true) {
if (allDone) {
lcd.setCursor(0, 0); lcd.print("Need more rocks!");
lcd.setCursor(0, 1); lcd.print("You did them all");
backlit(GREEN_PIN); delay(1000);
backlit(RED_PIN); delay(1000);
backlit(BLUE_PIN); delay(1000);
} else {
// For each rock, perform a cycle of displaying and interacting with the user.
// ---------------------------------------------------------------------------
displayTime = true;
for (indexRock = 0; indexRock < MAX_ROCKS; indexRock++) { // loop through all rocks
// for (indexRock = 0; indexRock < 1; indexRock++) { // loop through all rocks
Serial.print(F("Here at top of loop, indexRock equals "));
Serial.println(indexRock);
ack = false; enc = false; wrn = false; alm = false; cmp = false;
displayTime = true;
blanklcd(); flash();
// Only display if it has been displayed for less than 2 hours
// and it has not been completed.
while (displayTime && ( rockDuedateL[indexRock] > 0 )) { // if the rock hasn't been completed, display
Serial.println(F(" @rock display loop"));
endTime = (millis() + (DISPLAY_CYCLE_SECS * 1000L));
promptTime = (millis() + (DISPLAY_CYCLE_SECS * 500L));
DateTime now = rtc.now(); todayDate = now.unixtime(); if (rockDuedateL[indexRock] > 0 ) rockDuedateL[indexRock] = todayDate + DISPLAY_CYCLE_SECS - 1; // Test
// Loop for the display time (DISPLAY_CYCLE_SECS))
// ***********************************
while (displayTime) {
Serial.println(F(" @main display loop ")); // Test
displayTime = (millis() < endTime );
DateTime now = rtc.now();
todayDate = now.unixtime();
differenceDate = rockDuedateL[indexRock] - todayDate; // time left until the due date is reached.
// Prepare for display
// *******************
lcd.setCursor(0, 1); //Display the due date on the second line
lcd.print(" ");
lcd.setCursor(2, 1);
if (rockDuedateL[indexRock] != 0) {
lcd.print(rockDuedate[indexRock]);
} else {
lcd.print("Completed!");
}
// Determine appropriate backlighting
// ----------------------------------
if (differenceDate < ALARM_DAYS ) { // figure out background
Bckg = RED_PIN; // if within ALARM_DAYS of the due date
} else if (differenceDate < WARN_DAYS ) {
Bckg = BLUE_PIN; // if within the WARN_DAYS of the due date
} else {
Bckg = GREEN_PIN;
} // otherwise the background is GREEN.
backlit(Bckg);
// Display due date (or completed)
if (rockDuedateL[indexRock] != 0) {
scrollMessage(rockMessage[indexRock]);
} else {
lcd.setCursor(0, 0); lcd.print(" Completed! ");
}
endWait = millis() + INTERCHAR_DELAY;
Serial.println(F(" @status check code block"));
while (millis() < endWait) {
// Check state and play appropriate messages
//------------------------------------------
// if the user presses a button, play a encouraging message
if (((resultButton & STATE_SHORT) == STATE_SHORT ) && !ack) {
setSelection = char(random(0, MAX_MSGS + 1) + 0x31);
ack = true;
playMessage('0', setSelection); // Acknowledgement message
resultButton = STATE_NORMAL;
}
// sometimes at the 1 hour mark (possibly never), play a motivating message
// if not previously played this cycle
if ((millis() >= promptTime) && !enc) {
enc = true;
if (random(0, 101) < ENC_PROB) {
setSelection = char(random(0, MAX_MSGS + 1) + 0x31);
playMessage('1', setSelection); // Encouragement message
}
else { //Test
Serial.println(F("No encouragement at prompt time")); //Test
} //Test
}
// if the user is in a warn state, play a warning message
// if not previously played this cycle
if ((differenceDate < WARN_DAYS) && !wrn) {
wrn = true;
setSelection = char(random(0, MAX_MSGS + 1) + 0x31);
playMessage('2', setSelection); // Warning message
backlit(BLUE_PIN);
}
// if a user is in an alarm state, play a serious message if not
// previously played this cycle
if ((differenceDate < ALARM_DAYS) && !alm) {
alm = true;
setSelection = char(random(0, MAX_MSGS + 1) + 0x31);
playMessage('3', setSelection); // Alarm message
backlit(RED_PIN);
}
// if a user completes the rock, confirm and play a congratulatory
// message if not previously played this cycle
if (((resultButton & STATE_LONG) == STATE_LONG) && !cmp) {
resultButton = STATE_NORMAL;
if (confirm()) {
cmp = true;
setSelection = char(random(0, MAX_MSGS + 1) + 0x31);
playMessage('4', setSelection); // Completion message
delay(1000);
playMessage('5', '9'); // Stars and Stripes; confirmation message
resultButton = STATE_NORMAL;
rockDuedateL[indexRock] = 0;
displayTime = false;
}
}
}
Serial.println(F(" End of status check code block")); // Test
}
Serial.println(F(" End of main display loop")); // Test
}
Serial.println(F(" End of rock display loop")); //Test
}
allDone = ((rockDuedateL[0] == 0 ) &&
(rockDuedateL[1] == 0 ) &&
(rockDuedateL[2] == 0 ));
Serial.println(F("End of for loop")); //Test
}
}
}
//*****************************************************************
void backlit(byte myPin) {
digitalWrite(RED_PIN, HIGH);
digitalWrite(BLUE_PIN, HIGH);
digitalWrite(GREEN_PIN, HIGH);
digitalWrite(myPin, LOW);
delay(1000);
return;
}
//*****************************************************************
void blanklcd() {
lcd.setCursor(0, 0); lcd.print(" ");
lcd.setCursor(0, 1); lcd.print(" ");
}
//*****************************************************************
void checkButton() {
/*
* This function implements software debouncing for a two-state button.
* It responds to a short press and a long press and identifies between
* the two states. Your sketch can continue processing while the button
* function is driven by pin changes.
*/
const long LONG_DELTA = 1000; // hold seconds for a long press
const long DEBOUNCE_DELTA = 30; // debounce time
static int lastButtonStatus = HIGH; // HIGH indicates the button is NOT pressed
int buttonStatus; // button atate Pressed/LOW; Open/HIGH
static long longTime = 0, shortTime = 0; // future times to determine is button has been poressed a short or long time
boolean Released = true, Transition = false; // various button states
boolean timeoutShort = false, timeoutLong = false; // flags for the state of the presses
buttonStatus = digitalRead(BUTTON_PIN); // read the button state on the pin "BUTTON_PIN"
timeoutShort = (millis() > shortTime); // calculate the current time states for the button presses
timeoutLong = (millis() > longTime);
if (buttonStatus != lastButtonStatus) { // reset the timeouts if the button state changed
longTime = millis() + LONG_DELTA;
shortTime = millis() + DEBOUNCE_DELTA;
}
Transition = (buttonStatus != lastButtonStatus);
Released = (Transition && (buttonStatus == HIGH)); // for input pullup circuit
lastButtonStatus = buttonStatus; // save the button status
if ( ! Transition) {
resultButton = STATE_NORMAL | resultButton;
return;
} // if there has not been a transition, return the normal state
if (timeoutLong && Released) { // long timeout has occurred and the button was just released
resultButton = STATE_LONG | resultButton;
} else if (timeoutShort && Released) { // short timeout has occurred (and not long timeout) and button ws just released
resultButton = STATE_SHORT | resultButton;
} else { // else there is no change in status, return the normal state
resultButton = STATE_NORMAL | resultButton;
}
}
//*****************************************************************
boolean confirm() {
static const long WAIT = 10000;
long waitTime = 0;
waitTime = millis() + WAIT ;
Serial.println(F("in confirm"));
//play confirm message
// -------------------
playMessage('5', '1'); // Confirm question message
// Wait for a response from the user
// ---------------------------------
while ((waitTime > millis()) && (resultButton == STATE_NORMAL));
if (resultButton >= STATE_LONG) {
// Rock completion confirmed
// -------------------------
resultButton = STATE_NORMAL;
return true;
}
else {
// Completion not confirmed
// ------------------------
resultButton = STATE_NORMAL;
return false;
}
}
//*****************************************************************
long Date2Epoch(String myDate) {
int fromPtr = 0, toPtr = 0;
static TimeElements rockDuedate_t;
static String myMonth;
static String myDay;
static String myYear;
myMonth.reserve(3);
myDay.reserve(3);
myYear.reserve(5);
// Parse month from string
toPtr = myDate.indexOf('/');
myMonth = myDate.substring(0, toPtr);
// Parse day from string
fromPtr = toPtr + 1;
toPtr = myDate.indexOf('/', fromPtr);
myDay = myDate.substring(fromPtr, toPtr);
// Parse year from string
fromPtr = toPtr + 1;
myYear = myDate.substring(fromPtr);
rockDuedate_t.Second = 59;
rockDuedate_t.Minute = 59;
rockDuedate_t.Hour = 23;
rockDuedate_t.Month = myMonth.toInt();
rockDuedate_t.Day = myDay.toInt();
rockDuedate_t.Year = myYear.toInt() - 1970;
return makeTime(rockDuedate_t);
}
//*****************************************************************
void flash() {
const byte delayTime = 250;
const byte flashCount = 6;
int index = 0;
// flash the backlight
// -------------------
for (index = 0; index < flashCount; index++) {
digitalWrite( RED_PIN, LOW);
digitalWrite( BLUE_PIN, LOW);
digitalWrite(GREEN_PIN, LOW);
delay(delayTime);
digitalWrite( RED_PIN, HIGH);
digitalWrite( BLUE_PIN, HIGH);
digitalWrite(GREEN_PIN, HIGH);
delay(delayTime);
}
}
//*****************************************************************
void playMessage(char myType, char mySelection) {
static char fileMessage[10] = {"Msg__.mp3"};
// construct the filename of the message to play
// ---------------------------------------------
fileMessage[3] = myType;
fileMessage[4] = mySelection;
fileMessage[9] = '\0';
// Stop playing any previous message
// ---------------------------------
if (audioplayer.playingMusic) {
audioplayer.stopPlaying();
delay(50);
}
// Reset the audio player
// ----------------------
audioplayer.reset();
delay(25);
// Play the message specififed by myType and mySelection
// -----------------------------------------------------
Serial.print(F("Playing ")); Serial.println(fileMessage);
audioplayer.setVolume(1, 100); // Set volume for left, right channels. lower numbers == louder volume!
audioplayer.playFullFile(fileMessage);
}
//*****************************************************************
boolean readln_Rocks(String & myDate, long & myDateL, String & myMessage)
// Function to read rocks from SD card
{
static String inputstring = "";
inputstring.reserve(128);
byte inputchar = '\0';
boolean Done = false;
Done = false;
myDate = "";
myDateL = 0;
myMessage = "";
if (RockMessages.available() == 0) return 0;
Serial.println(F("Reading Rock record"));
while (! Done) {
inputchar = RockMessages.read();
if (inputchar == '|') {
// pipe found, have chars for date
myDate = inputstring;
myDateL = Date2Epoch(myDate);
inputstring = "";
Serial.println("");
Serial.print(myDate + " ");
Serial.println(myDateL);
} else if (inputchar == 13) {
// EOL found; last character found
Serial.println("Found end of record");
Done = true; myMessage = inputstring;
inputstring = "";
inputchar = RockMessages.read(); // clear out LF character
Done = true;
} else if ((inputchar < 32) or (inputchar > 125)) {
//invalid character
} else {
// append character to string
inputstring = String(inputstring + String(char(inputchar)));
}
}
Serial.println("Read rock record");
return RockMessages.available();
}
//****************************************************************
void scrollMessage(String myMessage) {
static int indexMessage = 0;
int endex = 0;
int lastex = 0;
endex = myMessage.length() - COLUMNS_LCD;
lastex = indexMessage + 15;
lcd.setCursor(0, 0); lcd.print(" ");
lcd.setCursor(0, 0); lcd.print(myMessage.substring(indexMessage, lastex));
// delay(INTERCHAR_DELAY);
/* the delay is now performed in main loop, by repeating
* the following block of code for a delay defined by INTERCHAR_DELAY */
indexMessage++; if (indexMessage > endex) indexMessage = 0;
}
Attachments
-
20.9 KB Views: 2