Go Down

Topic: 16-bit sample & store, then readback & play, finally store to SD (Read 27532 times) previous topic - next topic

CrossRoads

I'm thinking about how to make a 16-bit mono sound sampling system, with playback of the samples sound, and storage to SD card if playback is okay.
Wil then take the SD card & put in a PC for some post capture manipulation.

My planned architecture - AD7680 16 bit ADC for audio capture, Atmega1284 for control, a bank of 8 23LC1024 128Kx8 SRAMs for intermediate, and for playback a AD5662 16 bit DAC. And SD card for "longterm" storage.

I have this code done up for capture & playback. Any chance of it working fast enough to sample at 44,100? If not, what can be improved?
Datasheets & .ino file attached.
Code: [Select]

/* Crossroads, 26 July 2013
test for reading from 16 bit ADC
AD7680, needs 24 bit read, max SPI speed 2.5MHz
write read data to bank of 23LC1024 SRAM, eight 128K x 8 SRAM
((128 * 1024)bytes/bank * 8 banks)/88200 bytes/sec = 11.88S
Need two bytes/sample for 16-bit sampling
Needs to be realtime

playback, read SRAM, write to AD5662B (A?) DAC, write as 24 bit sample
Needs to be realtime

Storage - store on SD card once decide sample is good, move to PC for cleanup, trimming, etc.  Doesn't need to be realtime.
*/
// pins 0,1 used for Serial
#include <SPI.h>
// pins 11,12,13 used for SPO
#include (SDfat.h>
byte csSD = 10; // SD card chip select
byte csADC = 7; // ADC chip select
byte csDAC = 6 ; // DAC chip select
byte csSRAM[] = {22,23,24,25,26,27,28,29};  // SRAM chip selects
byte recordButton = 14;
byte endButton = 15;
byte playbackButton = 16;
byte storeSDButton = 17; // need autonamimg somehow
byte tbd18Button = 18;
byte tbd19Button = 19;
byte tbd20Button = 20;
byte activeLed = 21; // light up to show recording, playback, storage
byte free[] = {4,5,8,9,30,31,};


// ATMEL ATMEGA1284P on Bobuino & Project usage
//
//                    +---\/---+
//  free  (D 4) PB0 1 |        | 40 PA0 (D 21) AI 7  activeLED
//  free  (D 5) PB1 2 |        | 39 PA1 (D 20) AI 6  tbd20
//  csDAC (D 6) PB2 3 |INT2    | 38 PA2 (D 19) AI 5  tbd19
//  csADC (D 7) PB3 4 |PWM     | 37 PA3 (D 18) AI 4  tbd18
//  csSD (D 10) PB4 5 |PWM     | 36 PA4 (D 17) AI 3  storeSD
//  MOSI (D 11) PB5 6 |        | 35 PA5 (D 16) AI 2  playback
//  MISO (D 12) PB6 7 |PWM     | 34 PA6 (D 15) AI 1  end
//   SCK (D 13) PB7 8 |PWM     | 33 PA7 (D 14) AI 0  record
//              RST 9 |        | 32 AREF
//             VCC 10 |        | 31 GND
//             GND 11 |        | 30 AVCC
//           XTAL2 12 |        | 29 PC7 (D 29) SRAM7
//           XTAL1 13 |        | 28 PC6 (D 28) SRAM6
//   RX0 (D 0) PD0 14 |     TDI| 27 PC5 (D 27) SRAM5
//   TX0 (D 1) PD1 15 |     TDO| 26 PC4 (D 26) SRAM4
//   RX1 (D 2) PD2 16 |INT0 TMS| 25 PC3 (D 25) SRAM3
//   TX1 (D 3) PD3 17 |INT1 TCK| 24 PC2 (D 24) SRAM2
//  free(D 30) PD4 18 |PWM  SDA| 23 PC1 (D 23) SRAM1
//  free (D 8) PD5 19 |PWM  SCL| 22 PC0 (D 22) SRAM0
//  free (D 9) PD6 20 |PWM  PWM| 21 PD7 (D 31) free
//                    +--------+
//
byte x;

unsigned long currentTime;
unsigned long duration;
unsigned long memFree = 524288;
byte recording;
byte playing;
byte storing;
byte buttons;
byte adc0;
byte adc1;
byte adc2;
byte SRAMbank = B11111110; // first bank
byte SRAMaddress0; // LSB
byte SRAMaddress1; // middle
byte SRAMaddress2; // MSB

/*********************************************/

void setup(){
  //Serial.begin (115200); // for debug testing
pinMode (csSD, OUTPUT);
pinMide (csADC, OUTPUT);
pinMode (csDAC, OUTPUT);
for (x=0; x<8; x=x+1){
pinMode (csSRAM[x], OUTPUT);
}
for (x=0; x<6; x=x+1){
pinMode (free[x], INPUT_PULLUP);
}
pinMode (recordButton, INPUT_PULLUP);
pinMode (endButton, INPUT_PULLUP);
pinMode (playbackButton, INPUT_PULLUP);
pinMode (storeSDButton, INPUT_PULLUP);
pinMode (activeLed, OUTPUT);

SPI.begin();
set SPI Speed to 2 MHz (divide by 4)

} // end setup

/*******************************************/

void loop(){
buttons = PINA;  // read buttons port & split out the bits
startStatus = buttons & B10000000;
endStatus = buttons & B01000000;
playStatus = buttons & B0010000;
storeStatus = buttons & B00010000;

// start recording, or keep recording
// 44,100 rate = sample every 362 clocks, 22.6uS
if ( (((startStatus == 0) && (endStatus != 0) && (playStatus != 0) && (storeStatus != 0) ) && (recording == 0)) ){
recording = 1; // turn on recording flag
SRAMaddress0 = 0; // set SRAM start address
SRAMaddress1 = 0; // set SRAM start address
SRAMaddress2 = 0; // set SRAM start address
SRAMbank = B11111110; // start at first chip
PORTA = PORTA | B00000001; // turn on status LED
}

while (recording == 1){  // stay here until memory is full, or end is pressed
currentTime = micros();
if ( currentTime - previousTime) <=22){
previousTime = previousTime + 22;   
// access ADC
PORTB = PORTB & B11110111; // clear csADC
adc2 = SPI.transfer(0); // MSB 0000 xxxx
adc1 = SPI.transfer(0); //     xxxx xxxx
adc0 = SPI.transfer(0); //     xxxx 0000 LSB
PORTB = PORTB | B00001000; // set csADC
// clean up into 2 bytes
upperADC = (adc2 <<4) + (adc1 >> 4);
lowerADC = (adc1 <<4) + (adc0 >> 4);
// write to SRAM
PORTC = SRAMbank; // clear SRAM chip select 0 to 7
SPI.transfer (SRAMaddress2);
SPI.transfer (SRAMaddress1);
SPI.transfer (SRAMaddress0);
SPI.transfer (upperADC);
SPI.transfer (lowerADC);
PORTC = 0xFF; // set SRAM chip select 0 to 7
// update for next address and/or bank
SRAMaddress0 = SRAMaddress0+1;
  if (SRAMaddress0 == 0){
    // lower address rolled over, increment middle address
    SRAMaddress1 = SRAMaddress1 +1; //
    if (SRAMaddress1 == 0){
        // middle address rolled over, increment upper address
        SRAMaddress2 = SRAMaddress2 + 1; //
        if (SRAMaddress2 == 2){ //gone thru all addresses,rollover to next bank
            SRAMaddress2 = 0; // clear upper bit, shift the 0 for next cs bit
            SRAMbank = SRAMbank <<1 + 0x01; // B11111110 -> B11111101, etc.
            if (SRAMbank == 0xFF){  // shifted a 0 across all outputs
                recording = 0; // Done!
                PORTA = PORTA & B11111110; // turn off status LED
            } // SRAM bank rollover
        } // upper address rollover
     } // middle address rollover
   } // lower address rollover
  // check endButton, stop if pressed
     if ((PINA & B01000000) == 0){
           recording = 0;
     }
  } // time check
} // while record/store loop


Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.

CrossRoads

2nd part of code
Code: [Select]

/***************/
// start playback
if ( (startStatus != 0) && (endStatus != 0) && (playStatus == 0) && (storeStatus != 0) || playing == 0){
playing = 1;
SRAMaddress0 = 0; // set SRAM start address
SRAMaddress1 = 0; // set SRAM start address
SRAMaddress2 = 0; // set SRAM start address
SRAMbank = B11111110; // start at first chip
PORTA = PORTA | B00000001; // turn on status LED
}
while (playing == 1){ // stay here until playback is done, or end is pressed
currentTime = micros();
if ( currentTime - previousTime) <=22){   
previousTime = previousTime + 22;                       
// read SRAM
PORTC = SRAMbank; // clear SRAM chip select 0 to 7
SPI.transfer (SRAMaddress2);
SPI.transfer (SRAMaddress1);
SPI.transfer (SRAMaddress0);
upperDAC = SPI.transfer (0);
lowerDAC = SPI.transfer (0);
PORTC = 0xFF; // set SRAM chip select 0 to 7

// write data to DAC 000000xx xxxxxxxx xxxxxxxx
// first 2 bits are 0 for normal operations
PORTB = PORTB & B11111011; // clear csDAC
SPI.transfer(0);
SPI.transfer (upperDAC);
SPI.transfer (lowerDAC);
PORTB = 0xFF; // clear csDAC

// update for next address and/or bank
SRAMaddress0 = SRAMaddress0+1;
  if (SRAMaddress0 == 0){
    // lower address rolled over, increment middle address
    SRAMaddress1 = SRAMaddress1 +1;
    if (SRAMaddress1 == 0){
        // middle address rolled over, increment upper address
        SRAMaddress2 = SRAMaddress2 + 1;
        if (SRAMaddress2 == 2){ // gone thru all addresses,rollover to next bank
            SRAMaddress2 = 0; // clear upper bit, shift the 0 for next cs bit
            SRAMbank = SRAMbank <<1 + 0x01; // B11111110 -> B11111101, etc.
            if (SRAMbank == 0xFF){  // shifted a 0 across all outputs
                playing = 0; // Done!
                PORTA = PORTA & B11111110; // turn off status LED
            } // SRAM bank rollover
        } // upper address rollover
     } // middle address rollover
   } // lower address rollover
  // check endButton, stop if pressed
     if ((PINA & B01000000) == 0){
           playing = 0;
           // leave DAC at 2.5V output?
           // first 2 bits are 0 for normal operations
           PORTB = PORTB & B11111011; // clear csDAC
           SPI.transfer(0); //
           SPI.transfer (0x00); // upperDAC
           SPI.transfer (0xFF); // lowerDac
           PORTB = 0xFF; // clear csDAC
     }
  } // time check
} // while read/play loop


/***************/
// store to SD card - use sdfat16, haven't worked out how best to do this
if ( (startStatus != 0) && (endStatus == 0) && (playStatus != 0) && (storeStatus != 0) || storing == 1 ){
storing = 1;
PORTA = PORTA | B00000001; // turn on status LED
}
while (storing ==1){
// etc.
// auto create next file name, open file, store data from SRAM, close file
// 8 x 128K = 1M files
// store it all, clean up sample beginning, trim off ends in the PC process
}

} // end loop
Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.

JarkkoL

It would probbly be better to put recording and playback into a timer interrupt to avoid interrupts causing unwanted glitches to your sound.

CrossRoads

Interrupts occurring from what? There will be nothing else running while recording or playing back.
Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.


liuzengqiang

FYI, my experience with SD.h library was that it reads around 14KB/s but sdfat should be faster.Playback from SD card may be hard.

Arduino due has audio out. I wonder if its library may provide some insight for your project. My last project with sound was while back when they released some tech notes on sound blaster (TM). PCs have enough memory and DMA :)
Serial LCD keypad panel,phi_prompt user interface library,SDI-12 USB Adapter

Coding Badly

I have this code done up for capture & playback. Any chance of it working fast enough to sample at 44,100?


First, some sanity checks...

16000000 cycles per second / (44100 samples per second * 2 bytes per sample) = 181 cycles per sample.  Should be enough processor time.

// set SPI Speed to 2 MHz (divide by 4)
2000000 bits per second / 16 bits per value = 125000 values per second.  Should be enough bandwidth.


A quick and simple optimization...

[font=Courier New]const byte csSD = 10; // SD card chip select
const byte csADC = 7; // ADC chip select
const byte csDAC = 6 ; // DAC chip select
...
[/font]

CrossRoads

So you're suggesting disabling millis() and micros() and somehow making a 22.6uS timer interrupt that will perhaps set a flag, and when the code sees the flag set then do an ADC sample and store to SRAM, or alternately an SRAM read out and write out to the DAC?
I don't know what's involved in making that timer interrupt.

My plan was to use Sdfat.h:
#include (SDfat.h>
I am not interested in playback from SD, only playback from SRAM to confirm the sampled sound. When satisfied with the sample, then move it from SRAM to SD card for further manipulation on a PC.
Final goal is electronic drums, standalone from any PC.
Or possibly an inexpensive netbook for loading cleaned up sounds into SRAM or FRAM, one bank/drum. And then just playback from SRAM/FRAM into DAC.

const - does that help with speed?
Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.

Coding Badly

const - does that help with speed?


Yes.  Each should const eliminate one to a half-dozen machine instructions.

JarkkoL

Just referring to:
Quote

Interrupts occurring from what? There will be nothing else running while recording or playing back.

I.e. micros() you are using is implemented with a timer interrupt. I don't know if that results in any audiable artifacts (depends on your uC and micros() interrupt handler) but with timer interrupt you can quarantee the frequency of your output without clicks and snaps in the sound. Your interrupt handler should just push those audio samplers to the DAC, i.e. essentially the code you have inside the if-statement that checks the 22uS delta time should go there.

CrossRoads

So instead of this
Code: [Select]

currentTime = micros();
if ( currentTime - previousTime) <=22){   
previousTime = previousTime + 22;

do something that waits for a timer interrupt instead?
Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.

CrossRoads

Heading out to play, will see happens.

I came across Nick Gammon's page on timers
http://www.gammon.com.au/forum/?id=11504
not sure I can get 44,100 no matter what I do.
Maybe set it for 50,000, see how long the code takes, and some no-ops to finish slowing it down before enabling the interrupt again?

Will start with a baseline, see what it looks like ...
Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.

JarkkoL

Quote

do something that waits for a timer interrupt instead?

No, I mean write your own interrupt handler and put the code there. Like I did in this simple audio sample player

CrossRoads

#13
Aug 03, 2013, 09:25 am Last Edit: Aug 03, 2013, 09:57 am by CrossRoads Reason: 1
Okay, I've got this thing free-running, going flat out with no timing measurements or interrupts in the code, just whatever milis() and micros() are doing in the background.  Results from micros() captured just before sampling starts and right after it ends, and the same for playback:
Code: [Select]

Start recording
done from record memory
14696056 (uS)
Start playback
done from playback memory
13706116 (uS)

Total memory accessed: 8 * 128 *1024 = 1048576 bytes/2 = 524,288 words / 14.696 seconds = 35,675 16-bit samples/second while recording.
How do I squeeze a few more seconds out of this? I want to be down to 11.888 seconds, so 2.808 seconds faster.

Reading out, it's a little quicker because the 16 bits of data can go right from memory into the DAC, the 16 bits don't need to be pulled from the middle of the 24 bits out of the ADC.
524,288/13.706 = 38,252 samples/second. So I want 1.818 seconds improvement there.

The numbers above assume ADC, DAC, and memory running 8 MHz and not the 2MHz I started with, that was a big improvement:
Code: [Select]

Start recording
done from record memory
39953364 (uS)
Start playback
done from playback memory
38950760 (uS)


If I could find ADC & DAC that only needed 2 byte transfers to read out & write in, that would help:
Code: [Select]

Start recording
done from record memory
12123192 (uS)
Start playback
done from playback memory
12064720 (uS)

524,288/12.123sec = 43,246 samples/sec. Close to CD quality.

What else can I do to get faster? I don't know if 16 bit, 2-byte transfers ADC/DAC even exist, the choices were getting limited at 16 bit already, hence the AD7680 & AD5662B (datasheets posted earlier).

Here's the capture loop, full code is attached below:
Code: [Select]

 while (recording == 1){  // stay here until memory is full, or end is pressed
   //currentTime = micros();
   //if ( ( currentTime - previousTime) >=duration){
   //  previousTime = previousTime + duration;    
     // access ADC
     PORTB = PORTB & B11110111; // clear csADC
     adc2 = SPI.transfer(0); // MSB 0000 xxxx
     adc1 = SPI.transfer(0); //     xxxx xxxx
     //adc0 = SPI.transfer(0); //     xxxx 0000 LSB
     PORTB = PORTB | B00001000; // set csADC - csADC is toggling
     // clean up into 2 bytes
     //upperADC = (adc2 <<4) + (adc1 >> 4);
     //lowerADC = (adc1 <<4) + (adc0 >> 4);

     // write to SRAM
     //Serial.print(" sram sel ");
     //Serial.println (SRAMbank, HEX);
     PORTC = SRAMbank; // clear SRAM chip select 0 to 7
     SPI.transfer (SRAMaddress2);
     SPI.transfer (SRAMaddress1);
     SPI.transfer (SRAMaddress0);
     SPI.transfer (upperADC);
     SPI.transfer (lowerADC);
     PORTC = 0xFF; // set SRAM chip select 0 to 7
     // update for next address and/or bank
     SRAMaddress0 = SRAMaddress0+1;
     if (SRAMaddress0 == 0){
       // lower address rolled over, increment middle address
       //Serial.println ("low address roll");
       SRAMaddress1 = SRAMaddress1 +1; //
       if (SRAMaddress1 == 0){
         // middle address rolled over, increment upper address
         //Serial.println (" middle address roll");
         SRAMaddress2 = SRAMaddress2 + 1; //
         if (SRAMaddress2 == 2){ //gone thru all addresses,rollover to next bank
           SRAMaddress2 = 0; // clear upper bit, shift the 0 for next cs bit
           //Serial.print ("Sb ");
           SRAMbank = (SRAMbank <<1) + 0x01; // B11111110 -> B11111101, etc.
           //Serial.println (SRAMbank,BIN);
           if (SRAMbank == 0xFF){  // shifted a 0 across all outputs
           endTime = micros();
             recording = 0; // Done!
             Serial.println ("done from record memory");
             Serial.println (endTime - startTime);
             
             PORTA = PORTA & B11111110; // turn off status LED
           } // SRAM bank rollover
         } // upper address rollover
       } // middle address rollover
     } // lower address rollover
     // check endButton, stop if pressed - this read works
     //buttons = PINA;
     //if ((PINA & B01000000) == 0){
     //  recording = 0;
     //  Serial.println ("done from record end");
     //  PORTA = PORTA & B11111110; // turn off status LED
     //}
   //} // time check
 } // while record/store loop
Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.

fat16lib

I don't understand your read/write of SRAM.  I have used 23LCV1024 SRAM, the battery backup NVRAM version, and it requires four bytes to send an instruction and address.  It appears that the 23LC1024 requires the same sequence.

See Figure 2-6 of the 23A1024/23LC1024 data sheet.  You must send a 0X02 instruction byte before the three byte address for a write.

Reading and writing two bytes at a time to the SRAM is very slow since it requires a transfer of six bytes total.

I have used the SRAM to read and save an SPI ADC at high rate.  I read the ADC in an ISR for timer 1 using bit-bang and store the data in 512 byte buffers.  In loop() I write buffers to SRAM in 512 byte chunks.  I wrote an optimized driver to write the SRAM, not the Arduino SPI library.  The driver can write a 512 byte block to SRAM in about 800 microseconds or about 1.56 microseconds per byte.

You can't run the AD7680 at 8 MHz since the SPI clock is used for the SAR ADC and the max clock rate is 2.5 MHz.  See Table 4 of the AD7680 data sheet.  The SPI clock must be between 250kHz and 2.5 MHz.

My approach for SPI ADC/DACs is to write a bit-bang driver for the ADC/DAC and connect them to their own pins.  I use the AVR SPI controller for the mass storage device.

I used this method in a library for the Adafruit Wave Shield http://code.google.com/p/wavehc/downloads/list. It uses a 12-bit DAC so it only plays 12 of 16 bits but it can play 16-bit 44.1 ksps Wave files from an SD.

It looks like a bit-bang driver would only require 20 clocks to read the AD7680 ADC, see .  I tend to get about a 2 MHz clock rate in optimized bit-bang SPI drivers so that would be 10 microseconds per sample.  It should be possible but would require tricky programming.  See Figure 21. AD7680 Serial Interface Timing Diagram--20 SCLK Transfer in the data sheet.


Go Up