Arduino C Expert to loan their eyes?

Thread Starter

djsfantasi

Joined Apr 11, 2010
9,163
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
2015-11-07 08.26.42.jpg .
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;
}
Moderators note : change code tags to C
 

Attachments

Top