Renaming a recorded WAV file on SD card: Teensy 3.6 + Audio board

Status
Not open for further replies.

MaxLee

Member
Hi all, a project I've been working on which goes through a wake-sleep cycle and records audio is working great, no humming noises/clicks, I2C LCD is great, RTC works, etc., but I'm encountering difficulty when trying create WAV files with variable/unique filenames (rather than predefined string literals/constants), based on the RTC's timestamp.

The SD.h and corresponding functions seem to require "const char*", which is interferring with my ability to create a unique filename each time.

I experimented with going through and removing the "const"s in the SD.h and SD.cpp, but that just resulted in no file being created at all (when feeding in "char*"), with silent failures occurring (and a "0") object being returned by the SD.open() command. However, specifying the constant "BOB.WAV" in the file.open() still works, even with the const's having been removed (making think there's something else somewhere causing it to fail with a non-const data type).

Anybody have an solutions to get around this? My main desire is that each WAV file be uniquely named using the timestamp. I'm open to even initially writing a fixed filename like "temp.WAV", then renaming it to something with a timestamp, but I've encountered headwinds doing that (with different SD libraries eating each other) :(.

Thank you for any help :).

Code:
#include <SPI.h>
#include <SD.h>
#include <SD_t3.h>
#include <SerialFlash.h>

// for buttons
#include <Bounce.h>

#include <Audio.h>
#include <Wire.h>

// for RTC
#include <TimeLib.h>



//write wav
unsigned long ChunkSize = 0L;
unsigned long Subchunk1Size = 16;
unsigned int AudioFormat = 1;
unsigned int numChannels = 1;
unsigned long sampleRate = 44100;
unsigned int bitsPerSample = 16;
unsigned long byteRate = sampleRate * numChannels * (bitsPerSample / 8); // samplerate x channels x (bitspersample / 8)
unsigned int blockAlign = numChannels * bitsPerSample / 8;
unsigned long Subchunk2Size = 0L;
unsigned long recByteSaved = 0L;
unsigned long NumSamples = 0L;
byte byte1, byte2, byte3, byte4;

//const int myInput = AUDIO_INPUT_LINEIN;
const int myInput = AUDIO_INPUT_MIC;

AudioPlaySdWav           audioSD;
AudioInputI2S            audioInput;
AudioOutputI2S           audioOutput;
AudioRecordQueue         queue1;

//recod from mic
AudioConnection          patchCord1(audioInput, 0, queue1, 0);
AudioConnection          patchCord2(audioSD, 0, audioOutput, 0);
AudioConnection          patchCord3(audioSD, 0, audioOutput, 1);

AudioControlSGTL5000     audioShield;


// Bounce objects to easily and reliably read the buttons
// example: Bounce <alias> = Bounce(<pin number>, delay-in-ms)
Bounce buttonRecord = Bounce(32, 100);
Bounce buttonStop =   Bounce(31, 100);  // 100 = 100 ms debounce time
Bounce buttonPlay =   Bounce(30, 100);

int mode = 0;  // 0=stopped, 1=recording, 2=playing
File frec;
elapsedMillis  msecs;

// Use these with the Teensy Audio Shield
#define SDCARD_CS_PIN    10
#define SDCARD_MOSI_PIN  7
#define SDCARD_SCK_PIN   14

// Define LED to indicate when recording
#define LED 13

// declare variables used to hold the sleep-time length, and recording length
// TODO: use these addresses to store updated cycle parameters
unsigned int sleepaddr = 1;
unsigned int recordaddr = 0;


//////////////////// For setting up display //////////////////////////////
// Include NewLiquidCrystal Library for I2C
#include <LiquidCrystal_I2C.h>

// Define LCD pinout
const int  en = 2, rw = 1, rs = 0, d4 = 4, d5 = 5, d6 = 6, d7 = 7, bl = 3;

// Define I2C Address - change if reqiuired
const int i2c_addr = 0x3F;

LiquidCrystal_I2C lcd(i2c_addr, en, rw, rs, d4, d5, d6, d7, bl, POSITIVE);

bool screen_is_here = false;

/////////////////////////////////////////////////////////////////////////

//character array to store changing filename
char fname[16];


void setup() {


  //check if screen is plugged in
  Wire.begin();
  {
    Wire.beginTransmission(i2c_addr);
    if (Wire.endTransmission() == 0) {

      // Set the screen_is_here flag to 'true'
      screen_is_here = true;

      // Set display type as 16 char, 2 rows
      lcd.begin(16, 2);

      // Print on first row
      lcd.setCursor(0, 0);
      lcd.print("Recording Test");

      // Wait 1 second
      delay(1000);

      // Print on second row
      lcd.setCursor(0, 1);
      lcd.print("Press any button.");

      // Wait 8 seconds
      delay(8000);

      // Clear the display
      lcd.clear();
    }else{
      // execute automated stuff
     }
  }

  // set the Time library to use Teensy 3.0's RTC to keep time
  setSyncProvider(getTeensy3Time);

  // Setup name for new recording
  {

    String filename = month();
    filename += "-";
    filename += day();
    filename += "-";
    filename += hour();
    filename += "-";
    filename += minute();
    filename += ".WAV ";

    unsigned int stringlength = filename.length();

    filename.toCharArray(fname, stringlength);
  }


  // Configure the pushbutton pins
  pinMode(32, INPUT_PULLUP);
  pinMode(31, INPUT_PULLUP);
  pinMode(30, INPUT_PULLUP);
  pinMode(13, OUTPUT);

  Serial.begin(9600);
  AudioMemory(60);
  audioShield.enable();
  audioShield.inputSelect(myInput);
  audioShield.micGain(40);  //0-63
  audioShield.volume(0.5);  //0-1

  SPI.setMOSI(SDCARD_MOSI_PIN);
  SPI.setSCK(SDCARD_SCK_PIN);
}


/*
  TODO: In the main loop, there will be main functions:
  1. sleep
  2. record
  3. save

  TODO: In addition, there will be the functions for the menu, which listen to button input for changing
  the recording and sleep length.

*/


void loop() {
  asm volatile(" WFI"); //Tell cpu to wait for interrupt... saves battery.

  if (screen_is_here) {

    // First, read the buttons
    buttonRecord.update();
    buttonStop.update();
    buttonPlay.update();

    // Respond to button presses
    if (buttonRecord.fallingEdge()) {
      Serial.println("Record Button Press");

      digitalWrite(LED, HIGH);
      //LCD section
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Recording...");
      if (mode == 2) stopPlaying();
      if (mode == 0) {
        startRecording();
      }
    }
    if (  buttonStop.fallingEdge())  {//incomingByte == '2'  ||
      Serial.println("Stop Button Press");
      digitalWrite(LED, LOW);

      //LCD section
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Stopped...");
      if (mode == 1) stopRecording();
      if (mode == 2) stopPlaying();
    }
    if ( buttonPlay.fallingEdge())  {// incomingByte == '3'  ||
      Serial.println("Play Button Press");

      //LCD section
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Playing...");

      if (mode == 1) stopRecording();
      if (mode == 0) startPlaying();
    }
    if (mode == 1) {
      continueRecording();
    }
  }
  else
  { /* What to do when the screen isn't here */
    Serial.println("Screen detection failed");
  }
}

String startRecording() { // TODO: Modify to take a time parameter?

  //frec = SD.open("BOB.WAV", FILE_WRITE);
  frec = SD.open(fname, FILE_WRITE);
  Serial.print(frec);
  Serial.println();
  Serial.println(fname);
  // if the dated initial file on SD is successfully created, then start recording.
  if (frec) {
    queue1.begin();
    Serial.println("Supposedly started recording to SD..."); //Execution never enters this block :-(...
    mode = 1;
    recByteSaved = 0L;
  }
  return fname;
}

void continueRecording() {

  if (queue1.available() >= 2) {
    byte buffer[512];
    memcpy(buffer, queue1.readBuffer(), 256);
    queue1.freeBuffer();
    memcpy(buffer + 256, queue1.readBuffer(), 256);
    queue1.freeBuffer();
    // write all 512 bytes to the SD card
    frec.write(buffer, 512);
    recByteSaved += 512;
    elapsedMicros usec = 0;
    Serial.print("SD write, us=");
    Serial.println(usec);
  }
}

void stopRecording() {
  Serial.println("stopRecording");
  queue1.end();
  if (mode == 1) {
    while (queue1.available() > 0) {
      frec.write((byte*)queue1.readBuffer(), 256);
      queue1.freeBuffer();
      recByteSaved += 256;
    }
    writeOutHeader();
    frec.close();
  }
  Serial.println("Setting mode to 0");
  mode = 0;
  Serial.println(mode);
}


void startPlaying() { //Not certain that we need this later.
  Serial.println("startPlaying");
  Serial.println(fname);
  Serial.println();
  //audioSD.play("BOB.WAV");
  audioSD.play(fname);
  mode = 2;

}


void stopPlaying() { //Not certain that we need this later.
  Serial.println("stopPlaying");
  if (mode == 2) audioSD.stop();
  mode = 0;
}



// This function is what turns the RAW audio into a WAV, by adding a header to the file.
void writeOutHeader() { // update WAV header with final filesize/datasize

  //  NumSamples = (recByteSaved*8)/bitsPerSample/numChannels;
  //  Subchunk2Size = NumSamples*numChannels*bitsPerSample/8; // number of samples x number of channels x number of bytes per sample
  Subchunk2Size = recByteSaved;
  ChunkSize = Subchunk2Size + 36;
  frec.seek(0);
  frec.write("RIFF");
  byte1 = ChunkSize & 0xff;
  byte2 = (ChunkSize >> 8) & 0xff;
  byte3 = (ChunkSize >> 16) & 0xff;
  byte4 = (ChunkSize >> 24) & 0xff;
  frec.write(byte1);  frec.write(byte2);  frec.write(byte3);  frec.write(byte4);
  frec.write("WAVE");
  frec.write("fmt ");
  byte1 = Subchunk1Size & 0xff;
  byte2 = (Subchunk1Size >> 8) & 0xff;
  byte3 = (Subchunk1Size >> 16) & 0xff;
  byte4 = (Subchunk1Size >> 24) & 0xff;
  frec.write(byte1);  frec.write(byte2);  frec.write(byte3);  frec.write(byte4);
  byte1 = AudioFormat & 0xff;
  byte2 = (AudioFormat >> 8) & 0xff;
  frec.write(byte1);  frec.write(byte2);
  byte1 = numChannels & 0xff;
  byte2 = (numChannels >> 8) & 0xff;
  frec.write(byte1);  frec.write(byte2);
  byte1 = sampleRate & 0xff;
  byte2 = (sampleRate >> 8) & 0xff;
  byte3 = (sampleRate >> 16) & 0xff;
  byte4 = (sampleRate >> 24) & 0xff;
  frec.write(byte1);  frec.write(byte2);  frec.write(byte3);  frec.write(byte4);
  byte1 = byteRate & 0xff;
  byte2 = (byteRate >> 8) & 0xff;
  byte3 = (byteRate >> 16) & 0xff;
  byte4 = (byteRate >> 24) & 0xff;
  frec.write(byte1);  frec.write(byte2);  frec.write(byte3);  frec.write(byte4);
  byte1 = blockAlign & 0xff;
  byte2 = (blockAlign >> 8) & 0xff;
  frec.write(byte1);  frec.write(byte2);
  byte1 = bitsPerSample & 0xff;
  byte2 = (bitsPerSample >> 8) & 0xff;
  frec.write(byte1);  frec.write(byte2);
  frec.write("data");
  byte1 = Subchunk2Size & 0xff;
  byte2 = (Subchunk2Size >> 8) & 0xff;
  byte3 = (Subchunk2Size >> 16) & 0xff;
  byte4 = (Subchunk2Size >> 24) & 0xff;
  frec.write(byte1);  frec.write(byte2);  frec.write(byte3);  frec.write(byte4);
  frec.close();

  Serial.println("header written");
  Serial.print("Subchunk2: ");
  Serial.println(Subchunk2Size);

}






//////////// Below code the controls the overall system sleep  ////////////////
// https://forum.pjrc.com/threads/33243-RTC-alarm-to-wake-up-from-deep-sleep //
///////////////////////////////////////////////////////////////////////////////

/******************* Seting Alarm **************************/
#define RTC_IER_TAIE_MASK       0x4u
#define RTC_SR_TAF_MASK         0x4u

void rtcSetup(void)
{
  SIM_SCGC6 |= SIM_SCGC6_RTC;// enable RTC clock
  RTC_CR |= RTC_CR_OSCE;// enable RTC
}

void rtcSetAlarm(uint32_t nsec)
{
  RTC_TAR = RTC_TSR + nsec;
  RTC_IER |= RTC_IER_TAIE_MASK;
}

/********************LLWU**********************************/
#define WMUF5_MASK      0x20u

static void llwuISR(void)
{
#if defined(HAS_KINETIS_LLWU_32CH)
  LLWU_MF5 |= WMUF5_MASK;
#elif defined(HAS_KINETIS_LLWU_16CH)
  LLWU_F3 |= WMUF5_MASK; // clear source in LLWU Flag register
#endif
  RTC_IER = 0;// clear RTC interrupts
}

void llwuSetup(void)
{
  attachInterruptVector( IRQ_LLWU, llwuISR );
  NVIC_SET_PRIORITY( IRQ_LLWU, 2 * 16 );

  NVIC_CLEAR_PENDING( IRQ_LLWU );
  NVIC_ENABLE_IRQ( IRQ_LLWU );

  LLWU_PE1 = 0;
  LLWU_PE2 = 0;
  LLWU_PE3 = 0;
  LLWU_PE4 = 0;
  LLWU_ME  = LLWU_ME_WUME5; //rtc alarm
}

/********************* go to deep sleep *********************/
// These are defined in the core now
#define SMC_PMPROT_AVLLS_MASK   0x2u
#define SMC_PMCTRL_STOPM_MASK   0x7u
#define SCB_SCR_SLEEPDEEP_MASK  0x4u

void goSleep(void) {
  //volatile unsigned int dummyread;
  /* Make sure clock monitor is off so we don't get spurious reset */
  // currently not set by anything I know, so the clock monitor is not set from reset
  MCG_C6 &= ~MCG_C6_CME0;
  //
  /* Write to PMPROT to allow all possible power modes */
  SMC_PMPROT = SMC_PMPROT_AVLLS_MASK;// Not needed already taken care of in here.
  /* Set the STOPM field to 0b100 for VLLSx mode */
  SMC_PMCTRL &= ~SMC_PMCTRL_STOPM_MASK;
  SMC_PMCTRL |= SMC_PMCTRL_STOPM(0x4); // VLLSx

  SMC_VLLSCTRL =  SMC_VLLSCTRL_VLLSM(0x3); // VLLS3
  /*wait for write to complete to SMC before stopping core */
  (void) SMC_PMCTRL;

  SYST_CSR &= ~SYST_CSR_TICKINT;      // disable systick timer interrupt
  SCB_SCR |= SCB_SCR_SLEEPDEEP_MASK;  // Set SLEEPDEEP bit to enable deep sleep mode (STOP)
  asm volatile( "wfi" );  // WFI instruction will start entry into STOP mode
  // will never return, but generates system reset
}

/*  From previously used example for how to activate the sleep/alarm
  void setup() {
   //
   // put your setup code here, to run once:
   flashLed(100);
   //
   rtcSetup();
   llwuSetup();

   rtcSetAlarm(5); // This value is seconds
   goSleep();
  }
*/

//This is used by setup loop to specify where to get time from, pointing to RTC
time_t getTeensy3Time()
{
  return Teensy3Clock.get();
}

SD card is in the audio board, rather than the Teensy 3.6's SD slot.
 
Last edited:
Hi all, a project I've been working on which goes through a wake-sleep cycle and records audio is working great, no humming noises/clicks, I2C LCD is great, RTC works, etc., but I'm encountering difficulty when trying create WAV files with variable/unique filenames (rather than predefined string literals/constants), based on the RTC's timestamp.

Did you try to print the filename to Serial before open/creating the file on disk?
Did you also check that the filename length is <8+3 (<8 characters before and 3 characters after the dot?

I would exclude that String class is an issue , by simply using sprint to generate the filename.
 
Did you try to print the filename to Serial before open/creating the file on disk?
Did you also check that the filename length is <8+3 (<8 characters before and 3 characters after the dot?

I would exclude that String class is an issue , by simply using sprint to generate the filename.

Thank you for replying! I appreciate it.

Generating the string then converting to a char array doesn't seem to be an issue, as I've gotten it to print fine to serial and to an LCD display.

Hmm... I didn't consider that there might be a length limit. It must be <8 then?

I've been aiming for the format:
(m)m-dd-(h)h-(m)m.WAV <--- (Where a single-digit month/hour/minute would be a single digit)
 
I got rid of the "-" character, which brought the timestamp down to 7 characters in length. Pressing the record, stop, then play button results in the following serial output:

Code:
Record Button Press
0
6191220.WAV
Stop Button Press
Play Button Press
startPlaying
6191220.WAV

The lone "0" output is what is being returned by the "file.open()" command, and the execution flow doesn't go into the queue creation block.
 
I would use mmddHHMM.wav, if you can force String to do so fine. I would use
Code:
sprintf(filename,"%02d%02d%02d%02d.wav",month,day,hour,minute);
Also you must start the queue with
Code:
queue1.begin();
at the end of setup()
 
I would use mmddHHMM.wav, if you can force String to do so fine. I would use
Code:
sprintf(filename,"%02d%02d%02d%02d.wav",month,day,hour,minute);

I'm taking your wise advice here.

You might also be right about the placement of that queue1.begin() statement, though it doesn't seem to have made a difference prior, nor did it seem to negatively affect recording. What's the rationale for having it be in the setup() block instead of its current location?

At the moment, I'm still focusing trying to get audio to record via variably changing names.
 
You might also be right about the placement of that queue1.begin() statement, though it doesn't seem to have made a difference prior, nor did it seem to negatively affect recording. What's the rationale for having it be in the setup() block instead of its current location?
I typically have it at the end of setup, but you are write it could be also after opening the file, it only set a variable (enabled) to 1.

I cannot find that you initialized SD after you set mosi and clk. I expect SD.begin(CS); or similar (caveat I'm not using SD but other filesystems)
Not clear to me, why it works with BOB.WAV
 
Bless you.

Yup, during one of my iterations, I chopped that bit out of my code.

Everything is now working perfectly, including timestamped WAV files.

I appreciate your help. Thank you! :D
 
I have a few more details to touch up on, but I'll happily share a generalized version of my code here when finished. Feel free to incorporate anything you see into your other audio recorder project that you have going on.

And again, thank you forum!
 
Last edited:
And here's the final code, very much a product of the good work of many folks here on this forum. For simplistic setups where a low-power, scheduled audio recording is needed, you'd be hard-pressed to beat this.

Only outside requirement (outside of Arduino and Teensyduino libs) is "Newliquidcrystal" (I used v1.3.5).

Take it, use it, enjoy.
https://github.com/gaolaowai/TeensyOutdoorRecorder
 
Status
Not open for further replies.
Back
Top