LCD + Shift Register + MP3 Shield (Sparkfun)

Hello,

I’ve got a few funny issues going on with my 20 x 4 LCD display as a result of coupling it with the Spark Fun MP3 shield. I’ve already tested the parts individually and have been working slowly to integrate them. I’m thinking it’s a SPI data collision. I’ve referred to Bill Porter’s site for “Using the SPI bus for something else as well” and I’m not quite sure about the integration to the code. As well, I’m sure it’s not the only problem.

He mentions:

 //disable interrupts to avoid collisions on the SPI bus between this code //and the MP3player library
  MP3player.pauseDataStream();

  //shift data
  tempIO = SPI.transfer(HIBYTE(output));
  tempIO<<8; 
  tempIO = SPI.transfer(LOBYTE(output));

  //latch output on shift registers
  digitalWrite(OUTLATCH,LOW);
  digitalWrite(OUTLATCH,HIGH);
  digitalWrite(OUTLATCH,LOW);

  // put code for other SPI Devices (aka LCD) here

  //enable interrupts
  MP3player.resumeDataStream();

But I’m missing key information, especially with regard to HIBYTE(output) re definitions and data types.

Can anyone shed some light on this for me?

I’m displaying long text files to the LCD. Four lines of the text file appear before the next four. The corresponding audio track is chosen first, then the corresponding text appears on the screen.

Current symptoms:

  • the first sound files plays. Sometimes the corresponding text file shows on the screen (with a few bugs). Lots of time it doesn’t.
  • the next sound file will play, but not text will appear on the screen, or it’s just a couple words and a few commas
  • Or, the sounds files will play, but the screen is blank.
  • All the time: not more than 3 sound files play and then nothing happens.
  • once in a while, the text on the screen is from aliens.

I have no idea what’s going on. Perhaps you can shed some light.

Other notes:

  • sound files labeled appropriately and in the correct format
  • SD card is 16 gb FAT 32
  • Arduino Uno, 74HC595, 20 x 4 LCD
  • Arduino is powered through a ac-dc converter
#include <SPI.h>
#include <SdFat.h>
#include <SdFatUtil.h>
#include <SFEMP3Shield.h>
#include <Bounce2.h>
#include <LiquidCrystal.h>
SdFat sd;
SFEMP3Shield MP3player;
const uint8_t volume = 0; //0 = max, 255 = lowest (0ff)
const uint16_t monoMode = 1;  // Mono setting 0=off, 3=max
const int chipSelect = 10;
LiquidCrystal lcd(5); // change this bc using mp3 shield
boolean nuevo = true;
int start, len;  // start and length
int screens; // how many screens we'll need
String whichLine;
String full_msg;
SdFile myFile;
int numFiles = 27;
byte tempIO;
byte output;

// define a serial output stream
ArduinoOutStream cout(Serial);

void setup() {
  Serial.begin(115200);
  SPI.setClockDivider(SPI_CLOCK_DIV32);
  lcd.begin(20, 4);
  lcd.clear();
  lcd.print("hello, world!!");
  delay(1000);
  lcd.clear();
  lcd.setCursor(0, 0);
  nuevo = true;
  // Print a message to the LCD.
  // not sure about these below
  if (!sd.begin(chipSelect, SPI_HALF_SPEED)) sd.initErrorHalt();
  if (!sd.chdir("/")) sd.errorHalt("sd.chdir");
  initSD();  // Initialize the SD card
  initMP3Player(); // Initialize the MP3 Shield
  MP3player.begin();
}

void loop() {

  delay(500);

  for (int i = 1; i < numFiles; i++) {
    full_msg;
    nuevo = true;
    //Serial.flush();
    lcd.clear();
    lcd.setCursor(0, 0);
    // play audio
    MP3player.playTrack(i);

    // pause mp3 player data stream here
    // shift the data
     // write to the latches

    // then select corresponding text file to show. concatenate the file name
    String t1 = "/text/";
    String t2 = ".txt";
    String title = t1 + i + t2;
    //myFile.open(title, 0_READ);
    char filename[8];
    title.toCharArray(filename, 14);
    Serial.println(filename);
    if (!myFile.open(filename, O_READ)) {
      sd.errorHalt("opening msg for read failed");
    } else {
      while (myFile.available()) {
        // read in a byte and store it before advancing the pointer
        char readByte = char(myFile.read());
        full_msg += readByte; // add the new bite to end of string
      }
    }
    Serial.println(full_msg);
    delay(1000);
    Serial.println("Space space space");

    // how long is our message & how many screens should we have?
    int numScreens = (full_msg.length() / 80) + 1;
    Serial.print("The number of screens we need is ");
    Serial.println(numScreens);
    if (nuevo) {
      start = 0;
      String whichLine = wordGlue(full_msg, start);
      lcd.print(whichLine);
      // figure remaining lines for this screen
      for (int i = 1; i < 4; i++) {
        lcd.setCursor(0, i);
        //Serial.println(start);
        whichLine = wordGlue(full_msg, start);
        Serial.println(whichLine);
        lcd.print(whichLine);
      }
      nuevo = false;
      delay(6000);
      lcd.clear();
    }
    for (int j = 0; j < numScreens; j++) {
      // how many screens do we need?
      for (int i = 0; i < 4; i++) {
        lcd.setCursor(0, i);
        String whichLine = wordGlue(full_msg, start);
        Serial.println(whichLine);
        lcd.print(whichLine);
      }
      delay(6000);
      lcd.clear();
    }
    myFile.close();

    //enable interrupts
    // MP3player.resumeDataStream();
    Serial.println("we should be selecting a new file to play");
  }// end for loop
  Serial.println("We are outside of for-loop. Start again");
}// end loop


String wordGlue(String text, int &vals) {
  int indice;
  // if we aren't at the very beginning, move start over one (not last stop)
  if (vals != 0) {
    vals++;
  }
  //Serial.print("vals is ");
  //Serial.println(vals);
  // add the # of char to cross the screen for 20 char
  int finish = vals + 19;
  // starting at the back, cycle thru looking for the space
  for (int i = finish; i > vals; i--) {
    // if we are at the end of the msg, just print the rest.
    if ( i == full_msg.length()) {
      // add a space
      //full_msg.concat(" ");
      break;
    }

    // are we at a space??
    if (text[i] == ' ') {
      // if so, save that spot
      indice = i;
      break;
    }
  }
  String line = full_msg.substring(vals, indice);
  //Serial.println(line);
  // record the finish so we know where to start again
  vals = indice;
  return line;
}

void initSD()
{
  //Initialize the SdCard.
  if (!sd.begin(SD_SEL, SPI_HALF_SPEED))
    sd.initErrorHalt();
  if (!sd.chdir("/"))
    sd.errorHalt("sd.chdir");
}

// initMP3Player() sets up all of the initialization for the
// MP3 Player Shield. It runs the begin() function, checks
// for errors, applies a patch if found, and sets the volume/
// stero mode.
void initMP3Player()
{
  uint8_t result = MP3player.begin(); // init the mp3 player shield
  if (result != 0) // check result, see readme for error codes.
  {
    // Error checking can go here!
  }
  MP3player.setVolume(volume, volume);
  MP3player.setMonoMode(monoMode);
  Serial.print("Volume level is  "); Serial.println(volume);
  Serial.println("volume raised");
}
    full_msg;

Give me a clue. What is this supposed to do?

    // then select corresponding text file to show. concatenate the file name
    String t1 = "/text/";
    String t2 = ".txt";
    String title = t1 + i + t2;

You have 27 files on the SD card with names like /text/1.txt?

        full_msg += readByte; // add the new bite to end of string

I think you've bitten off more than you can chew.

You need to tell us more than "But I'm missing key information, especially with regard to HIBYTE(output) re definitions and data types.". This implies that the code won't even compile.

Personally, I suspect that you are using about 300% of the Arduino's memory, but I could be wrong.

full_msg

It is a String. Once a file is open from the SD card, the char of that text file are added to the end of a the String full_msg. Then that String gets processed and broken into smaller Strings based on where the spaces were within the text and their length. This allows for them to then be printed on the LCD without being cut mid-word.

// then select corresponding text file to show. concatenate the file name
    String t1 = "/text/";
    String t2 = ".txt";
    String title = t1 + i + t2;

The text files are in a different directory than the sound files. Hence, the above concatenates the file name to with i, which is incrementing through the files, to add the file path so that the file can be opened appropriately. The text files are named: 1.txt, 2.txt, 3.txt within the directory /text.

It's quite possibly true, that I could optimize this code in many ways. I'm open to suggestions.

I'm trying to get the mp3 files to play while displaying the text on the screen. Since I can get both parts working independently of each other, I'm lead to believe it's data collisions with SPI. Bill Porter has a bit of information about integrating multiple SPI clients with the MP3 player. Yet, it's not clear to me what his code is doing, that I've posted above. If anyone could shed some light on that, I might have a better sense of how to move forward.

Thanks.

It is a String. Once a file is open from the SD card, the char of that text file are added to the end of a the String full_msg. Then that String gets processed and broken into smaller Strings based on where the spaces were within the text and their length. This allows for them to then be printed on the LCD without being cut mid-word.

But, a String does not contain compile-time data, so full_msg; is as useful code as 47;. In other words, as useful as tits on a bull. Get rid of it.

I'm lead to believe it's data collisions with SPI.

It is FAR more likely that you are running out of memory or stepping on memory you don't own.

    char filename[8];
    title.toCharArray(filename, 14);

You are telling the toCharArray() method that filename can hold 14 characters. It most certainly can NOT.

How much data is in a given txt file?

The largest text file is just over 800 bytes.

On compilation,

Sketch uses 21,474 bytes (66%) of program storage space. Maximum is 32,256 bytes. Global variables use 1,332 bytes (65%) of dynamic memory, leaving 716 bytes for local variables. Maximum is 2,048 bytes.

Right. I'm guessing I should go with string's + chars, and not Strings.

leaving 716 bytes for local variables.

You're going to find it a real challenge to store 800+ bytes in the memory you have available.

I don't see why you need to, though. You are displaying data from a file. You know how many bytes you can display. Open the file, seek to the position you last extracted data from, read that number of bytes, and close the file. Update the position, so that next time you seek to the first byte past the ones just read.

It is not necessary to have the whole file in SRAM. And certainly not in a String.

Okay. Just trying to make sure I understand you correctly. So you propose something more like this:

  1. to open the file,
  2. extract the data between two locations
  3. close file
  4. load that data to first screen
  5. clear screen
  6. repeat until entire message has shown

Kbeezy: Okay. Just trying to make sure I understand you correctly. So you propose something more like this:

  1. to open the file,
  2. extract the data between two locations
  3. close file
  4. load that data to first screen
  5. clear screen
  6. repeat until entire message has shown

Yes. That way, the only memory you use is the memory needed to hold the file position and the data that fits on the LCD. Even that isn't really required, though it makes it a lot simpler.

Got it. I'll re-structure and report back.

thanks for the suggestions in the meantime!