0

Have anyone ever successfully run any of the official playback and recording examples for VS1003 or VS1053 on Arduino / ESP8266? I mean, I was able to play sound with VS1003, but I just can't make the official example compile on Arduino IDE... It looks like it should be no problem (just provide a few methods and copy the main method code from the PDF file to the setup() in sketch) but I'm just getting a bunch of stupid compiler or linker errors, like "undefined reference to 'VSTestInitHardware()' " while the player.h header file (containing it) is included. I'm not a C / C++ expert so it's either something stupid I do or it's just not possible on Arduino. Can anyone provide a compilable example? I'm trying it with ESP8266 currently (and it is my desired target CPU), but an Arduino Uno version could help too (it throws different errors but doesn't compile neither). Thank you.

  • 1
    The Arduino IDE should only be used to build Arduino code. – Ignacio Vazquez-Abrams Jun 2 '16 at 21:36
  • @IgnacioVazquez-Abrams They say (in their examples) that their code is plain C and it can be compiled for ANY microcontroller - you just have to supply the device-specific methods. That is exactly what I'm trying to (and can't) do. – P.W. Jun 3 '16 at 9:47
  • You can't compile plain C in the Arduino IDE, only Arduino code. – Ignacio Vazquez-Abrams Jun 3 '16 at 15:36
  • @IgnacioVazquez-Abrams I would qualify that as "you can't compile it unmodified". And if all he's using is library routines, wrapping them in extern "C" {} should do the trick. – JayEye Jun 3 '16 at 21:08
  • @JayEye: Can you explain in detail what trick do you mean, please? I was trying making the header file's methods extern but it didn't help. I can live with modifying a little the examples - just don't want to rewrite them in different way, especially I want to test more than one chip version, so I would have to change them all separately. – P.W. Jun 3 '16 at 21:44
0

Ok, after long battle with C++ compiler I have a working example (at least for VS1003 and ESP8266) with a checklist how to recreate it (with minimal changes to the example files). I'm sure someone else will have this problem so I'm attaching it:

The things that you need to do in player1003.c:

  • rename to player1003.h (change file extension only)
  • remove all 5 includes from the top (stdio, string, stdlib, ctype, "player.h")
  • turn off REPORT_ON_SCREEN (change #if 1 to #if 0) if you don't want to have a lot of serial output
  • mass replace "FILE *" to "File " - mind the space (change type of file variables in all methods, mark "Case sensitive" to avoid errors in player1053!)
  • mass replace fopen( to fopen2(
  • mass replace printf( to Serial.printf(
  • mass replace fflush(stdout); to //fflush(stdout); (or Serial.flush();)
  • remove method: VSTestInitHardware(void)
  • remove SM_SDISHARE flag from WriteSci(SCI_MODE, SM_SDINEW|SM_SDISHARE|SM_TESTS|SM_RESET); line (in VSTestInitSoftware(void) method)
  • in VSTestHandleFile() at the bottom add fp.close(); after VS1003PlayFile(fp); and VS1003RecordFile(fp); lines (especially after the record!). We don't want unclosed files on SD card.

The main sketch (.ino):

#include <SPI.h>
#include <SD.h>
#include "player.h"

using namespace std;


// I use these values below for ESP-12 (ESP8266):
#define MP3_xCS 2
#define MP3_xDCS 4
#define MP3_DREQ 5
#define MP3_xRESET 16
#define SD_CS 15
#define SPI_MOSI 11 //SPI
#define SPI_MISO 12 //SPI
#define SPI_CLK  13 //SPI


#define  WAIT_DREQ()  while(!digitalRead(MP3_DREQ))


File song;


int VSTestInitHardware()
{
  pinMode(MP3_DREQ, INPUT);
  pinMode(MP3_xCS, OUTPUT);
  pinMode(MP3_xDCS, OUTPUT);
  pinMode(MP3_xRESET, OUTPUT);
  pinMode(SD_CS, OUTPUT);

  digitalWrite(MP3_xCS, HIGH);
  digitalWrite(MP3_xDCS, HIGH);

  digitalWrite(MP3_xRESET, LOW); // chip off!
  delay(100);
  digitalWrite(MP3_xRESET, HIGH); // chip on!

  Serial.begin(115200);

  if (!SD.begin(SD_CS))
  {
    Serial.println("Can't initialize SD!");
    return -1;
  }
  Serial.println("SD OK!");

  SPI.begin();
  SPI.setClockDivider(SPI_CLOCK_DIV32);  // Bus SPI at 1MHz (for Arduino: 16MHz / 16 = 1MHz)
  SPI.transfer(0xFF);  // Write something to the bus
  delay(10);
  return 0;
}


void setup()
{
  if (VSTestInitHardware() || VSTestInitSoftware())
  {
    Serial.printf("Failed initializing VS10xx, exiting\n");
    exit(EXIT_FAILURE);
  }
  /* ... */
  /* Playback example. You can call these functions many times in a row
  because they leave VS1003 in a stable state. */
  VSTestHandleFile("TEST.mp3", 0);  // remember to use 8.3 file names format on SD!
  /* Recording example */
  VSTestHandleFile("RECORDED.WAV", 1);  // original example for VS1003 has a bug here (OGG extension)

  Serial.println("Playing recorded file...");
  VSTestHandleFile("RECORDED.WAV", 0);

  Serial.println("THE END");
}

void loop()
{
}



void WriteSci(unsigned char reg, unsigned char hsb, unsigned char lsb)
{
  // for debugging:
  Serial.printf("Setting %s to: %d (%X, %X)\n", RegName(reg), (hsb<<8) + lsb, hsb, lsb);

  WAIT_DREQ();
  digitalWrite(MP3_xCS, LOW);

  SPI.transfer(0x02);
  SPI.transfer(reg);
  SPI.transfer(hsb);
  SPI.transfer(lsb);
  WAIT_DREQ();
  digitalWrite(MP3_xCS, HIGH); 
}


void WriteSci(u_int8 addr, u_int16 data)
{
  WriteSci(addr, data >> 8, data & 255);
}


uint16_t ReadSci (unsigned char reg)
{
  WAIT_DREQ();
  digitalWrite(MP3_xCS, LOW);

  SPI.transfer(0x03);
  SPI.transfer(reg);
  delayMicroseconds(10);

  char response1 = SPI.transfer(0xFF);
  WAIT_DREQ();
  char response2 = SPI.transfer(0xFF);
  WAIT_DREQ();

  digitalWrite(MP3_xCS, HIGH);

  unsigned int resultvalue = response1 << 8;
  resultvalue |= response2;

  // for debugging:
  if (reg != SCI_HDAT0 && reg != SCI_HDAT1)  // these registers are filtered out because they are intensively used during recording
    Serial.printf("%s is: %d (%X, %X)\n", RegName(reg), resultvalue, resultvalue >> 8, resultvalue & 255);

  return resultvalue;
}


int WriteSdi(const u_int8 *data, u_int8 bytes)
{
  WAIT_DREQ();
  digitalWrite(MP3_xDCS, LOW);

  for(int y = 0 ; y < bytes ; y++)
    SPI.transfer(data[y]);

  digitalWrite(MP3_xDCS, HIGH);
}


const char* RegName(uint8_t reg)
{
  switch (reg)
  {
    case 0x00: return "SCI_MODE";
    case 0x01: return "SCI_STATUS";
    case 0x02: return "SCI_BASS";
    case 0x03: return "SCI_CLOCKF";
    case 0x04: return "SCI_DECODE_TIME";
    case 0x05: return "SCI_AUDATA";
    case 0x06: return "SCI_WRAM";
    case 0x07: return "SCI_WRAMADDR";
    case 0x08: return "SCI_HDAT0";
    case 0x09: return "SCI_HDAT1";
    case 0x0A: return "SCI_AIADDR";
    case 0x0B: return "SCI_MIXERVOL";
    case 0x0C: return "SCI_AICTRL0";
    case 0x0D: return "SCI_AICTRL1";
    case 0x0E: return "SCI_AICTRL2";
    case 0x0F: return "SCI_AICTRL3";
  }
}


void SaveUIState(void)
{
}

void RestoreUIState(void)
{
}

int GetUICommand(void)
{
  while (Serial.available() > 0)
  {
    char c = Serial.read();
    if (c != 10 && c != 13)
      return c;
  }
  return -1;
}


void exit(int code)
{
  if (code == EXIT_SUCCESS)
    Serial.println("Exited with no error.");
  else
    Serial.printf("Terminated with error: %d\n", code);
  while (true) { yield(); }  // yield() is needed for ESP8266 only
}


File fopen2(const char* fileName, const char* mode)
{
  if (mode[0] == 'w')  // fast workaround!
  {
    Serial.printf("Opening for writing: %s\n", fileName);
    song = SD.open(fileName, O_WRONLY | O_TRUNC | O_CREAT);
  }
  else
  {
    Serial.printf("Opening for reading: %s\n", fileName);
    song = SD.open(fileName, O_RDONLY);
  }

  if (!(song)) 
  {
     Serial.printf("File not found: %s\n", fileName);
     return song;
  }
  Serial.printf("File opened: %s\n", fileName);
  return song;
}

size_t fread ( void * ptr, size_t size, size_t count, File stream )
{
  yield();  // this is needed for ESP8266 only (otherwise reboots)
  return stream.read(ptr, size * count);
}

int fseek ( File stream, long int offset, int origin )
{
  stream.seek(offset);
}

size_t fwrite ( const void * ptr, size_t size, size_t count, File stream )
{
  return stream.write((uint8_t*)ptr, size * count);
}

int fflush ( File stream )
{
  stream.flush();
  return 0;
}


#include "player1003.h"

(i had to manually clean this file out of my additional code, but I hope it will compile just fine).

I have a request:

If you run this example, please write me in comment whether the recording worked. I can't make my VS1003 module record sound. I have a suspicion that mic in my module is broken. That's why I was trying the official example (with the minimum changes as possible). It records only noise in my case, but plays mp3 perfectly. If you have a working example of recording, please share too.

And one more to the people with more rep (moderators): Please add a VS1003 (and VS1053 and VS1063) keywords and assign it to my questions about VS1003, because I'm unable to do it and it should be done. The chip deserves a keyword. (It's really good and not expensive.)

0

This is best given with an example.

foo.h contains:

#ifdef __cplusplus
extern "C" {
#endif
extern int foo(const char* s);
#ifdef __cplusplus
}
#endif

foo.c contains:

#include "foo.h"
#include <stdio.h>

int foo(const char* s) {
  printf("foo: %s\n", s);
  return 42;
}

So first, let's create a "library" from foo.c. Since we are compiling with a C, not a C++ compiler, the stuff in #ifdef __cplusplus is ignored.

$ gcc -c foo.c

This creates a file called foo.o, which we can then use to link with.

as24823.cpp contains:

#include "foo.h"
#include <iostream>

int main(int argc, char* argv[]) {
  int f = foo("called");
  std::cout << f << std::endl;
}

Now I can compile the C++ program (as24823.cpp) and use a C library (foo.c):

$ g++ -c ase24823.cpp
$ g++ ase24823.o foo.o
$ ./a.out
foo: called
42

Why is this necessary?, I hear you ask. Well, for reasons that are way beyond the scope of this posting, C++ mangles entry point names. Observe:

$ nm as24823.o
$ nm ase24823.o 
...
                 U foo    
0000000000000000 T main
...

(I've removed most of the output). What this is telling me is that the .o file has an entry point called "main", and an undefined external reference (something that will be provided by a library) called "foo". Now, if I remove the ifdefs from the foo.h file and recompile, this is what I get:

$ cat foo.h
extern int foo(const char* s);
$ g++ -c ase24823.cpp 
$ nm ase24823.o 
0000000000000000 T main
                 U _Z3fooPKc
....

What just happened? Because C++ no longer knows that it's supposed to call a C function, it has mangled the external so as to encode the types of its arguments (this is so that overloading can happen).

If you find this confusing, don't worry -- just edit the .h files that come with your C libraries and you'll be fine.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.