Record Audio to external Flash

18 Sep 2014

Hi all,

I am using the LPC1768. I am working on a project to record audio and saving it onto an external flash (AT45DB081). What I need is to record sound from a microphone that is connected to the AnalogIn pin of the LPC1768, sample at 40kHz and save 3 secs of data onto the external flash.

I already have codes for sampling data at 40kHz using ADC library (https://mbed.org/users/simonb/code/ADC_test/). I also found some codes to save data into flash.

Separately the codes work fine. However I dont quite know how I can put both codes together to do what I want. Here is my problem. The ADC resolution is 12 bits meaning I have to break it into 2 bytes to save into the external flash. With a sampling rate of 40kHz, I need to acquire every 25 microseconds. Do you think its even possible to do this conversion of 2 bytes and write to memory within 25 microseconds?

Someone did a test with a AT25DF041 with an arduino and he was able to write " "Hello, World!" in about 63 us. Still the timing seems really tight!

I wonder if anyone has done something similar and could provide some advise.

Cheers, Kian

18 Sep 2014

try to use the us_ticker to measure time intervals accurately to get an idea of the cost of writing to flash. You might want to buffer the data before writing; it amortizes the cost. Please report back with costs of accessing the flash. You might need DMA; Mbed is moving towards a DMA API.

18 Sep 2014

A few thoughts on things to try:

Firstly how good quality does the audio need to be? Can you get away with only storing the more significant byte and throwing the last 4 bits away? If so then that's your data rate halved. Similarly, do you really need 40kHz? For most things 16 or 20kHz would be just as good and again have a significant impact on the amount of data you would need to store. It all depends on what you are storing, music needs a reasonable rate, speech is fine at very low data rates.

It's fairly simple to get a high quality audio file on a PC and degrade it to lower sample rates and resolutions to see how it sounds. Make sure you are trying sounds similar to what you want to record, ideally recorded with the microphone you are planning to use.

Don't try to read the ADC and write to flash at 40kHz, that won't work well, put a buffer in between.

The flash chip you mentioned has a block size of 2112 bytes divided into pages of 264 bytes. That would imply that you'll get the best throughput if you write data in chunks that size. Buffer up 264 bytes (or 2112 bytes) in RAM and then write it in one transaction to the flash. Make sure that your flash addresses are aligned to the block boundaries (e.g. don't write 264 bytes starting at address 128, that would take up two different pages, write to address 0 or a multiples of 264)

Have two different RAM buffers so that while one is being written to the flash the second one is being filled by the ADC and keep switching them.

Something like the code below with the missing functions filled in should be a good start (note-I've not even tried compiling this let alone debugging it so there are probably some bugs in there. It certainly doesn't cope with buffer overflows very cleanly) If nothing else it should give you the general idea.

#define recordTimeSec 3

// 1 for 16 bit, 0 for 8
#define Sample16Bit 1

// rate in Hz
#define sampleRate 40000

#define bufferSize 264
#define samplePeriodUs (1000000 / sampleRate)


char buffer1[bufferSize ];
char buffer2[bufferSize ];
int adcWritePointer = 0;
char *adcBuffer;

volatile char *flashBuffer; // set in an interupt and checked in main so mark as volatile.

AnalogIn audioIn(AIn);

Ticker sampleClock;
Timer recordLength;


void onSampleClock()
{
#if Sample16Bit
    *((uint16_t*) (adcBuffer+adcWritePointer)) = audioIn.read_u16();
    adcWritePointer+= 2;
#else
    *(adcBuffer+adcWritePointer) = (char) (audioIn.read_u16() >> 8); 
    adcWritePointer++;
#endif

// buffer full
    if (adcWritePointer == bufferSize) {
        adcWritePointer  = 0;

        if (flashBuffer != NULL)
            printf("ERROR - flash write didn't finish in time");

// write this buffer to flash
        flashBuffer  = adcBuffer;

// store ADC in other buffer
        if (adcBuffer == buffer1)
            adcBuffer  = buffer2;
        else
            adcBuffer  = buffer1;

    }
}

main ()
{

    initFlash();

    while(true) {
        // set pointers
        adcWritePointer = 0;
        adcBuffer  = buffer1;
        flashBuffer = NULL;
        
        // reset timer
        recordLength.reset();
        
        //wait for the go signal
        waitForStartButton();
        
        // start the clocks
        recordLength.start();
        sampleClock.attach_us(onSampleClock,samplePeriodUs );

        // until we hit the time limit
        while (recordLength < recordTimeSec) {

            // if there is data waiting to write
            if (flashBuffer) {
                // write it
                writeToFlash(flashBuffer,bufferSize);
                // flag the buffer as written
                flashBuffer = NULL;
            }
        } // end of record time.
        sampleClock.detach();
        recordLength.stop();
    }
}
19 Sep 2014

Thanks for the reply guys. I will try out these suggestions and give my feedback later.

19 Sep 2014

Some things which can help with this: AnalogIn especially on the LPC1768 is fairly slow. A faster alternative is: https://mbed.org/users/Sissors/code/FastAnalogIn/, although directly addressing hardware is even faster (might look in the near future for some optimizations in that lib).

Writing the flash IC should be fast enough for your purposes if you just increase your spi frequency to the max your chip can handle. But if it needs to be faster you could use BurstSPI lib (mainly useful if you are running at very high spi frequencies), or one of the DMA libs on the LPC1768 (MODDMA and SimpleDMA that I know of). These can let you transfer the RAM buffer to the flash IC in the background.

19 Sep 2014

Hi all,

Sorry I am really very much a beginner still and I am getting codes from various examples and trying to piece them together.

I am sharing my codes below. But I am getting an error which says "No instance of overloaded function "writeEEPROM" matches the argument list.

The function accepts 3 inputs, an integer address, a char * of data to write and an integer size. In main, I am passing flashbuffer into the function. Isn't flashbuffer declared as a char* too? Why am I getting this error?

include the mbed library with this snippet

#include "mbed.h"

#define recordTimeSec 3
 
// 1 for 16 bit, 0 for 8
#define Sample16Bit 0
 
// rate in Hz
#define sampleRate 20000
 
#define bufferSize 264
#define samplePeriodUs (1000000 / sampleRate)
 
char buffer1[bufferSize ];
char buffer2[bufferSize ];
int adcWritePointer = 0;
char *adcBuffer;
 
volatile char *flashBuffer; // set in an interupt and checked in main so mark as volatile.
 
AnalogIn audioIn(p20);
 
Ticker sampleClock;
Timer recordLength;

void onSampleClock()
{
#if Sample16Bit
    *((uint16_t*) (adcBuffer+adcWritePointer)) = audioIn.read_u16();
    adcWritePointer+= 2;
#else
    *(adcBuffer+adcWritePointer) = (char) (audioIn.read_u16() >> 8); 
    adcWritePointer++;
#endif
 
// buffer full
    if (adcWritePointer == bufferSize) {
        adcWritePointer  = 0;
 
        if (flashBuffer != NULL)
            printf("ERROR - flash write didn't finish in time");
 
// write this buffer to flash
        flashBuffer  = adcBuffer;
 
// store ADC in other buffer
        if (adcBuffer == buffer1)
            adcBuffer  = buffer2;
        else
            adcBuffer  = buffer1;
 
    }
}

// AT25DF041A EEPROM commands

// reading
#define ReadArray             0x0B
#define ReadArrayLowFrequency 0x03

// programming
#define BlockErase4Kb       0x20
#define BlockErase32Kb      0x52
#define BlockErase64Kb      0xD8
#define ChipErase           0x60
#define ByteProgram         0x02
#define SequentialProgram   0xAD

// protection
#define WriteEnable           0x06
#define WriteDisable          0x04
#define ProtectSector         0x36
#define UnProtectSector       0x39
#define ReadSectorProtection  0x3C

// status
#define ReadStatus 0x05
#define WriteStatus 0x01

// miscellaneous
#define ReadManufacturer     0x9F
#define DeepPowerDown        0xB9
#define ResumeFromPowerDown  0xAB

SPI spi(p11, p12, p13); // mosi, miso, sclk

DigitalOut cs(p19);

Serial pc(USBTX, USBRX); // tx, rx

// wait until chip not busy
void notBusy ()
{
  cs = 0;
  spi.write (ReadStatus);       
  // wait until busy bit cleared
  while (spi.write(0) & 1) 
     {} 
  cs = 1;  
}  // end notBusy

// enable writing
void writeEnable ()
{
 notBusy ();
 
 cs = 0;
 spi.write(WriteEnable);       
 cs = 1;  
}  // end of writeEnable

// read device status
int readStatus (void)
{
 cs = 0;
 spi.write(ReadStatus);       
 int status = spi.write(status);       
 cs = 1;  
   
 return status;
}  // end of readStatus

// write status register
void writeStatus (int status)
{
   writeEnable ();
   notBusy ();  // wait until ready
   
   cs = 0;
   spi.write(WriteStatus);       
   spi.write(status);       
   cs = 1;  
}  // end of writeStatus

// send a command to the EEPROM followed by a 3-byte address
void sendCommandAndAddress (int command, int addr)
{
  spi.write(command);       
  spi.write((addr >> 16) & 0xFF);       
  spi.write((addr >> 8) & 0xFF);       
  spi.write(addr & 0xFF);       
}  // end of sendCommandAndAddress

// write len (max 256) bytes to device

// Note that if writing multiple bytes the address plus
//  length must not cross a 256-byte boundary or it will "wrap"
void writeEEPROM (int addr, char * data, int len) 
{
  // now write to it
  writeEnable();
  
  notBusy ();  // wait until ready
  cs = 0;
  sendCommandAndAddress (ByteProgram, addr);
  for ( ; len ; --len)
  { 
    spi.write(*data++);   
  }
  cs = 1;  
  notBusy (); 
} // end of writeEEPROM

// write one byte to device
void writeEEPROM (int addr, char data) 
{
  writeEEPROM (addr, &data, 1);
} // end of writeEEPROM

// read len bytes from device
void readEEPROM (int addr, char * data, int len) 
{
  notBusy ();  // wait until ready
  cs = 0;
  sendCommandAndAddress (ReadArray, addr);

  spi.write(0);  // clock in "don't care" byte
 
  for ( ; len ; --len)
  {
   int result = spi.write(0);
   printf("read: %02x\n",result);
   
   *data++ = result;
   //*data++ = spi.write(0);
   }       
  cs = 1;  
  
}  // end of readEEPROM

// erase a 4Kb block of bytes which contains addr
void eraseEEPROM (int addr)
{
  writeEnable ();

  notBusy ();  // wait until ready
  cs = 0;
  sendCommandAndAddress (BlockErase4Kb, addr);
  cs = 1;  
  notBusy ();  // wait until done
  
}  // end of eraseEEPROM


// show device info and status
void info ()
{
  
  notBusy (); // wait until ready
  
  cs = 0;
  spi.write(ReadManufacturer);       
  
  printf("Manufacturer: %02x\r\n",spi.write(0));
  //printf(spi.write(0));
  printf("Device ID Part 1: %02x\r\n",spi.write(0));
  //printf(SPI.transfer (0));
  printf("Device ID Part 2: %02x\r\n",spi.write(0));
  //Serial.println (SPI.transfer (0), HEX);
  printf("Extended Information Length: %02x\r\n",spi.write(0));
  //Serial.println (SPI.transfer (0),HEX);

  cs = 1;
  readStatus();


} // end of info


int main() {
    cs = 1;
    pc.baud(115200);
    spi.frequency(10000000);    // tired with 16000000, did not work
    wait_ms(500);
    
    writeStatus(0);
    info();
    
    eraseEEPROM(0x1000);

    printf("Start\n");

    // set pointers
    adcWritePointer = 0;
    adcBuffer  = buffer1;
    flashBuffer = NULL;
        
    // reset timer
    recordLength.reset();
    
    
    // start the clocks
    recordLength.start();
    sampleClock.attach_us(onSampleClock,samplePeriodUs );
 
    // until we hit the time limit
    while (recordLength < recordTimeSec) {
 
        // if there is data waiting to write
        if (flashBuffer) {
            // write it
            writeEEPROM (0, flashBuffer, bufferSize); 
            //writeToFlash(flashBuffer,bufferSize);
            // flag the buffer as written
            flashBuffer = NULL;
        }
    } // end of record time.
    sampleClock.detach();
    recordLength.stop(); 
}
19 Sep 2014

Just looking at this specific error you report: I don't think it automatically converts between char * and volatile char *. A volatile char is specifically stores in the RAM memory, while a regular char isn't. I don't really know why it doesn't automatically converts it, but you can specify it manually using:

 writeEEPROM (0, (char*) flashBuffer, bufferSize); 
19 Sep 2014

Also the comments in the EEPROM code say:

write len (max 256) bytes to device Note that if writing multiple bytes the address plus length must not cross a 256-byte boundary or it will "wrap"

Which would indicate that while each page is 264 physically bytes 8 are used for some form of housekeeping giving a far more sensible usable block size of 256 bytes. So you should change the buffersize #define at the start to be 256

21 Sep 2014

The datasheet for the AT45DB081E says the page size is user configurable to either 256 bytes per page or 264 (default) bytes per page. I presume it correct to assume the page size is 264 and I can use a buffersize of 264.