Brand new to C++ so total noob question here. I've created a char array of tracks I want to play from my sdcard. Then I want to loop through them and play them using SFEMP3Shield playMP3 function. But that function requires a char* pointer array. I can't seem to figure out how to convert my normal array into that pointer array.
Here's my code, 'tracks' is a standard char array:
EDIT: See comment further down for my full sketch (which isn't included fully here)
void loop()
{
//tracks is a char array
for(auto track : tracks){
if(track != NULL){
uint8_t result = MP3player.playMP3(track); //this needs to be a pointer array
delay(10000);
}
}
}
I tried converting tracks to a pointer array before passing it into the foreach loop like this:
But when I do that the loop gives me an error like this (so not sure if I can pass a pointer into a for each loop?). I don't know what 'begin' is, since it's not part of this loop.
/Arduinostuff/arduino_sample/arduino_sample.ino: In function 'void loop()':
/Arduinostuff/arduino_sample/arduino_sample.ino:38:20: error: 'begin' was not declared in this scope
for(auto track : trackList){
^~~~~~~~~
/Arduinostuff/arduino_sample/arduino_sample.ino:38:20: note: suggested alternative: 'rewind'
for(auto track : trackList){
^~~~~~~~~
rewind
/Arduinostuff/arduino_sample/arduino_sample.ino:38:20: error: 'end' was not declared in this scope
exit status 1
Compilation error: 'begin' was not declared in this scope
For the sake of clarity, here's my entire sketch as of now. I didn't want to include it all initially to avoid confusion on what I thought was the problem..but maybe this is better to include it all:
#include <SPI.h> // SPI library
#include <SdFat.h> // SDFat Library
#include <SFEMP3Shield.h> // Mp3 Shield Library
//create objects
SdFat sd;
File dir;
File file;
SFEMP3Shield MP3player; // Create Mp3 library object
//define some vars
const uint8_t volume = 30; // MP3 Player volume 0=max, 255=lowest (off)
const uint16_t monoMode = 1; // Mono setting 0=off, 3=max
int delayTime;
int dlyMin = 1000;
int dlyMax = 20000;
//how many tracks should we load
char tracks[50];
void createTrackArray(char* = "/");
void setup()
{
Serial.begin(9600);
initSD(); // Initialize the SD card
initMP3Player(); // Initialize the MP3 Shield
//load our files
createTrackArray();
}
// Loop
void loop()
{
char* trackList = tracks;
for(auto track : trackList){
if(track != NULL){
delayTime = random(dlyMin, dlyMax);
uint8_t result = MP3player.playMP3(track);
delay(delayTime);
}
}
}
// initSD() initializes the SD card and checks for an error.
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);
}
void createTrackArray(char* directory = "/"){
// Open directory
if (!dir.open(directory)) {
}
char name[40];
int i = 0;
while (file.openNext(&dir, O_RDONLY)) {
if (!file.isDir() && !file.isHidden()) {
file.getName(name, 40);
byte len = strlen (name);
if (len > 4 && strcmp (&name [len - 4], ".mp3") == 0){;
tracks[i] = name;
i++;
}
}
//always end char array with \0 (?)
tracks[i] = '\0';
file.close();
}
//debug, see some of our track array that was created
// Serial.print(tracks[0]);
// Serial.println();
// Serial.print(tracks[1]);
// Serial.println();
// Serial.print(tracks[2]);
// Serial.println();
}
Thanks, I appreciate the help. I'm not even sure what to Google to understand this, but I'll try to learn more about what you say here and see if I can figure it out.
So to create a 2d array of this, I initialize the array like this:
//up to 40 tracks, each no more than 14 characters in length
char tracks[40][14];
But then, when trying to assign track names to the array, do I assign them like: tracks[i][14] or like tracks[i] or like tracks[40][i] or...something else?
That's the part I can't seem to find an answer to....
Then, after that, I still need to convert it to a pointer...so is that like:
C-string! OK, that gets me somewhere, thank you! I have a library book and I just got to the section on these, so I'm hoping it helps push me in the right direction. Thank you!
an array is just a set of contiguous bytes reserved somewhere in memory. The number of bytes reserved is the array size times the size necessary to represent one element of the array.
a char takes only one byte.
So if you define an array of 16 chars with char anArray[16]; then you are asking the compiler to set aside 16 x 1 = 16 bytes
those 16 bytes will be somewhere in SRAM, in the memory. Let's say the compiler decides to set that at address starting at byte N° 438, then it could look like this
if you want to find what char is stored somewhere in the array, you just use the array notation with the square brackets. The 'H' of "Hello World" is at index 0 so you would find it with anArray[0]. The type of this is char as you are referring to a char type in that memory cell.
now if you want to find a pointer to some specific part of that array you use the address of operator which in compiler language is &. The pointer is really just the address where you byte is allocated in memory. So for the 'H' character, if you want to know its address (which would happen to also be the start address of the array) you just write &(anArray[0]). ➜ address of the char at index 0. Here it would be the SRAM memory cell at position 438. The type of this is char * as you are referring to a pointer to a char type in that memory cell.
Now this was done so often in C that the language designers decided that the array's variable name was also usable as the address of the start or the array
so from a programming point of view you can use either &(anArray[0]) or just anArray it's the same thing
A 2D array works in the same way.
say you want to store 3 c-string of max 15 characters + the null termination. You would define a variable named anArray2D this way:
char anArray2D[3][16];
this secures 3 x 16 x 1 = 48 bytes somewhere in memory, say starting at address 358
and from a pointer or address perspective, it works the same way &(anArray[0][0]) would be the pointer (the address in memory) of the first cell in memory (address 358) and &(anArray[1][0]) would be the address in memory for the start of the second c-string (address 374).
where the first index represents the line number and the second index the column number
And again, because this was done so often, it was decided to simplify this and the name of the array would be again the address of the first byte allocated in memory and if you want to find the address of the start of the individual "line" then you can use just the first part of the notation, for example anArray2D[1] is really the same thing as the address of anArray2D[1][0]
An alternative approach is to read the filenames into a block of memory and construct an array of pointers to the start of each string. The disadvantage is the extra space needed for the pointers but the space saved on storing the names is likely to be a lot larger. It means that a name longer that 50 characters will be OK provided that there are enough names shorter than 50 characters to provide the space.
// define the constants at the start so that if changes need to be made
// there is only one place that changes are needed.
const size_t max_tracks = 50;
const size_t average_name_size = 50;
// let the compiler do the arithmetic, it is better at it!
const size_t size = max_tracks * average_name_size;
// Block of memory for the names
char names[size];
char* limit = &names[size]; // pointer to first byte after allocated block
// Array of pointers to char*, ie they will point to the first char of each name.
char* tracks[max_tracks];
int n_tracks = 0;;
tracks[0] = names; // where the first string will be put
// Read in the names and call insert_name() for each one
bool insert_name(char* name) {
char* destination = names[track_index];
size_t length = strlen(name) + 1; // extra byte for the terminating nul
// check that enough space remains
// limit points to first byte after the reserved memory so use >=
if (&destination[length] >= limit) return false;
if (n_tracks >= max_tracks) return false;
strcpy(destination, name); // safe because we have checked that there is space
track_index++;
tracks[track_index] = destination + length; // equivalent to &destination[length]
return true;
}
Then to iterate over the names you just do
for (size_t i = 0; i < n_tracks; i++) {
printf("%s\n", tracks[i]);
}
NB the above code is completely untested!
If very short of memory then the tracks array can be dispensed with but the code will run slower, perhaps a lot slower. If you want to find the nth track name you search through the names array until you find the nth nul byte. The next byte is the start of the nth name.
Some operating systems provide a way to read the filenames into a block of memory with a single function call. You then have to search through the memory looking for the separators in order to find the individual names.
Common pitfalls with this sort of stuff are the numerous opportunities to make out by one errors. Examples include:
Forgetting to allow for the terminating nul. strlen("abc") is 3 but it needs 4 bytes to store it.
Arrays index from 0 so char array[2] reserves space for two values array[0] and array[1], but array[2] is beyond the end of the array and could contain anything. Writing to array[2] may corrupt data belonging to something else and result in a bug that could be very difficult to find because it probably won't manifest until long after the faulty code was executed and it can be difficult to reproduce. There are tools that can help but the easiest way to fix memory corruption bugs is to not create them!
Getting your head around arrays, pointers and pointer arithmetic is one of the most difficult parts of learning C. It is also one of the most essential and pointers are extremely powerful. Unfortunately they also provide lots of opportunity for mistakes. C++ is better in that respect because it provides libraries that hide a lot of it.
You're telling me I come from PHP and it seems needlessly difficult to do anything in comparison. But, I know there are advantages that hassle brings, too.
This is a great post and I'm going to pour over it slowly and see if I can understand how it all works. Thanks for taking the time!