16-bit sample & store, then readback & play, finally store to SD

“I will put together a a complete test sketch.”
Thanks Bill.
I’m playing with the timer overflow create an interrupt part right now, making sure I understand what’s happening.


// avr-libc library includes - added for timer interrupt
#include <avr/io.h>
#include <avr/interrupt.h>


// Added code for 1/44,100 Hz interrupt
    // initialize Timer1
    cli();             // disable global interrupts
    TCCR1A = 0x6B;        // set entire TCCR1 register to 363 (0x016B)
    TCCR1B = 0x01;
    // enable Timer1 overflow interrupt:
    TIMSK1 = (1 << TOIE1);
    // Set CS10 bit so timer runs at clock speed:
    TCCR1B |= (1 << CS10);
    // enable global interrupts:


// Added a simple ISR to check the timing
    digitalWrite(activeLed, !digitalRead(activeLed));

Compiles ok, time to download and break out the scope again …

Making progress - got an interrupt to fire ~44.1KHz rate:

// Added code for 1/44,100 Hz interrupt
    // initialize Timer1
    cli();             // disable global interrupts
    TCCR1A = 0x00;        // set entire TCCR1 register to 0
    //TCCR1B |= (1 << WGM12); // turn on CTC mode
    //TCCR1B |= (1 << CS10);  // timer runs at clock speed (no prescaler)
    TCCR1B = 0x09; // WGM12, CS10
    // set compare match register to desired timer count:
    OCR1A = 0x0169; // decimal 361, interrupt fires every ~22.676uS = (1/44,100)/(1/16,000,000)
    // enable Timer1 overflow interrupt:
    TIMSK1 =0x02;
    // enable global interrupts:

Needed correct ISR name as well to go with TIMSK1:

// Added a simple ISR to check the timing
    PINA = 0x01; // toggle IO pin

Trying to add more pieces.
Compiler (1.0.5 on WinVista, standard install with mighty1284 core added) is complaining:

sample_playback_store.ino: In function 'void __vector_13()':
sample_playback_store:150: error: 'STATIC_ALWAYS_INLINE' was not declared in this scope
sample_playback_store:150: error: expected `;' before 'void'
sample_playback_store:161: error: expected `;' before 'void'
sample_playback_store:170: error: expected `;' before 'uint16_t'

and I imagine
fastDigitalWrite, fastDigitalRead, fastDummy will be next. Some subtlety I am missing. Maybe need to declare some things volatile with their use in the ISR?

I’ve got the SDfat library in my IDE Preferences folder location.

top of the sketch, no SD card code added yet:

#include <SPI.h>
// pins 11,12,13 used for SPO
// avr-libc library includes - added for timer interrupt
#include <avr/io.h>
#include <avr/interrupt.h>
#include <SDfat.h>
#define nop asm volatile ("nop")
const byte csSD = 10; // SD card chip select
const byte csADC = 7; // ADC chip select
const byte csDAC = 6 ; // DAC chip select

// Added a simple ISR to check the timing
 PINA = 0x01; // toggle IO pin
// Change ISR to bit-bang "SPI" out to AD7680 for data capture
  /** clock for don't care bits */
  STATIC_ALWAYS_INLINE void fastDummy() {
    fastDigitalWrite(MCP_SAR_CLK_PIN, 1);
    fastDigitalWrite(MCP_SAR_CLK_PIN, 0);
  /** read next bit fast as possible
   * \param[in] v word to receive bit
   * \param[in] b bit number to be set.  v |= (1 << b) if next bit is high.
  STATIC_ALWAYS_INLINE void readBitFast16(uint16_t &v, uint8_t b) {
    fastDigitalWrite(MCP_SAR_CLK_PIN, 1);
    if (fastDigitalRead(MCP_SAR_DOUT_PIN)) v |= (1 << b);
    fastDigitalWrite(MCP_SAR_CLK_PIN, 0);
  /** Read AD7680 16-bit ADC in less than 8 microseconds
   *  cs is chip select pin
  STATIC_ALWAYS_INLINE uint16_t readADC(uint8_t cs) {
    fastDigitalWrite(cs, 0);
    uint16_t v = 0;
    readBitFast16(v,  15);
    readBitFast16(v,  14);
    readBitFast16(v,  13);
    readBitFast16(v,  12);
    readBitFast16(v,  11);
    readBitFast16(v,  10);
    readBitFast16(v,  9);
    readBitFast16(v,  8);
    readBitFast16(v,  7);
    readBitFast16(v,  6);
    readBitFast16(v,  5);
    readBitFast16(v,  4);
    readBitFast16(v,  3);
    readBitFast16(v,  2);
    readBitFast16(v,  1);
    readBitFast16(v,  0);
    fastDigitalWrite(cs, 1);
    return v;


I have a rough prototype for record to SD. It easily runs at 44.1 ksps but starts to fail around 50 ksps.

Since I don't have the AD7680, the files are trash, all zeros if the SDATA pin is low, all ones it SDATA is high, and random if SDATA is floating.

Guess I need to get some SOT-23 breakouts.

I will clean it up and post it soon.

Ok, getting my test bed set up. Got one of my original Bobuinos's with a non-working FTDI chip (could never it soldered on correctly, just going to download via ICSP instead). ICSP header is a little too close to the digital header, need to crimp up some male/female wires for an adapter....

And then order an ADC & DAC myself ...

The attached zip file has a first cut prototype. I realized it would be easy to replace the file writes with writes to SRAM.

I tested it on a Mega and looked at the CS and SCLK pins with scope. I set SDAT high, low, and floating to see what happened to the file data.

Try it on your board and let me know what happens.

See the readme.txt file.

CrossRoads.zip (7.19 KB)

Thanks. The pins in Crossroads.h (3,4,5) are fine. How do set the SD chip select to 30? (PortD pin 4 on my card) Compiles fine for the 1284.. Kinda weird doing it from within the library folder :) Downloaded via ICSP ok - time for the scope!

Okay, I changed this line to

#include <CrossRoads.h>
const uint8_t SD_CS_PIN = 30; // SS

and I get a file of small y’s with the … over them.
Screen prompts look good - I changed to Serial1 and added some blank spaces to make a little more readable, my serial program seems to ignore CRs or LFs or whatever is coming out.

I get a few clocks on D3 with the Start & Stop, D5 is not changing.

The ADC pins are not mapped right for your board. My fast digital I/O uses the mighty mapping for 1284.

Look at the following and change the pin numbers for the ADC using this map.

The mapping is in utility/DigitalPin.h

// Mighty Layout
static const pin_map_t pinMap[] = {
  {&DDRB, &PINB, &PORTB, 0},  // B0  0
  {&DDRB, &PINB, &PORTB, 1},  // B1  1
  {&DDRB, &PINB, &PORTB, 2},  // B2  2
  {&DDRB, &PINB, &PORTB, 3},  // B3  3
  {&DDRB, &PINB, &PORTB, 4},  // B4  4
  {&DDRB, &PINB, &PORTB, 5},  // B5  5
  {&DDRB, &PINB, &PORTB, 6},  // B6  6
  {&DDRB, &PINB, &PORTB, 7},  // B7  7
  {&DDRD, &PIND, &PORTD, 0},  // D0  8
  {&DDRD, &PIND, &PORTD, 1},  // D1  9
  {&DDRD, &PIND, &PORTD, 2},  // D2 10
  {&DDRD, &PIND, &PORTD, 3},  // D3 11
  {&DDRD, &PIND, &PORTD, 4},  // D4 12
  {&DDRD, &PIND, &PORTD, 5},  // D5 13
  {&DDRD, &PIND, &PORTD, 6},  // D6 14
  {&DDRD, &PIND, &PORTD, 7},  // D7 15
  {&DDRC, &PINC, &PORTC, 0},  // C0 16
  {&DDRC, &PINC, &PORTC, 1},  // C1 17
  {&DDRC, &PINC, &PORTC, 2},  // C2 18
  {&DDRC, &PINC, &PORTC, 3},  // C3 19
  {&DDRC, &PINC, &PORTC, 4},  // C4 20
  {&DDRC, &PINC, &PORTC, 5},  // C5 21
  {&DDRC, &PINC, &PORTC, 6},  // C6 22
  {&DDRC, &PINC, &PORTC, 7},  // C7 23
  {&DDRA, &PINA, &PORTA, 0},  // A0 24
  {&DDRA, &PINA, &PORTA, 1},  // A1 25
  {&DDRA, &PINA, &PORTA, 2},  // A2 26
  {&DDRA, &PINA, &PORTA, 3},  // A3 27
  {&DDRA, &PINA, &PORTA, 4},  // A4 28
  {&DDRA, &PINA, &PORTA, 5},  // A5 29
  {&DDRA, &PINA, &PORTA, 6},  // A6 30
  {&DDRA, &PINA, &PORTA, 7}   // A7 31

I am going to try to improve performance by using a preallocated contiguous file. This should reduce the chance of buffer overruns due to cluster allocation delays.

Okay, see ADC SCK toggling now. I don't know how fast, my scope is crap at that speed. I see periods of ADC CS, ~7.8uS, occurring about every 22.56uS, or 44.33 KHz. Pretty nice!

// in Crossroads.h, ADC AD7680 pin assignments (copied this as a note into the .ino file)
//const uint8_t ADC_CS_PIN = 7; // PB3 = D3 in Mighty1284 , Bobuino D7
//const uint8_t ADC_SDATA_PIN = 10; // PB4 = D4 -> D10
//const uint8_t ADC_SCLK_PIN = 11; // PB5 = D5 -> D11 = MOSI

Are you sure about using PB3-PB4-PB5? PB4 is SS, and PB5 is MOSI, I would thought using MOSI here would have interfered with SPI out to the SD card.

I set the pins for a Mega. I didn't realize they would map to B3, B4, B5 on your board.

I am having some success making it more reliable on Mega. I may be able to record 16-bit 44.1 ksps reliably on Mega.

Would it be hard to move to PORTA or PORTC, keep it away from the RX/TX pins & the SPI pins?

Any pins can be used for the ADC. The problem is that the 1284 map in DigitalPin.h is for the mighty.

If you edit CrossRoads.h and use the mighty pin number. For example:

const uint8_t ADC_CS_PIN = 24;

will make chip select A0, since this is the map for mighty digital pin 24.

It would be better to edit the pin map in DigitalPin.h to match your board. I can't change this in the DigitalPin library since there are a lot of mighty users.

I changed the ADC SCK pin to 2, PB2, = D6 in Bobuino,that works for me.

How's REF194, 4.5V output from 5V in, look for a reference voltage? http://www.analog.com/static/imported-files/data_sheets/REF19XSERIES.pdf

Man, can they make these parts any smaller!

Amazing, that is low drop out. Seems like a fine reference. I hope your analog skills are better than mine. 16-bit audio on an Arduino will have major noise problems. How do you plan to handle audio input/output?

I have really optimized the SD write. I now create a very large, 100 MB, contiguous file and use the most efficient SD raw multi-block write sequence. When the recording is done, I truncate the file.

It is so efficient that only two 512 byte buffer blocks are required. With great pain and effort I could make it run on an Uno.

I have been able to run at up to 60,000 samples per second on a Mega.

Here are stats. The min and max write times are for a 512 byte block. Really good since most of the cpu time is going to the ADC ISR.

Writing DATA18.BIN Type any character to stop. Min write time: 1768 usec Max write time: 1816 usec Max used buffer: 2 blocks File size: 3317248 bytes Record time: 37.64 Sec Rate: 44.07 ksps Done!

I tuned the rate a bit. It is now as close as possible to 44.1 ksps.

I will put a pin map in for your board, clean stuff up, and post the new version soon.

Thanks, it would have taken me ages to implement the store to SD.
What’s the best way /low latency to play back? I want to strike a drum pad and have sound play <1mS. Looking at AD5662 for a DAC to create the sound.

I have a circuit that I worked up before, 1990 :), to catch the peak of the strike, use that to trigger the playback and also set the volume level. The stored sound goes into the Vref of a multiplying DAC while the digital volume goes into the DAC input. That sounds funny writing it that way - maybe it’s the other way around. Need to find those schematics in a box in the cellar.
I’m thinking load the sound from SD card into SRAM and playback from there so that there is 0 latency, and I could allow one sound to die out while starting readout of the same sound a second time, vs just stopping the 1st one while the 2nd starts.

I have to find the schematics for a prototype I had worked up on a breadboard in 1990 (just before we bought our house - and not touched since!), see if the parts I used are still available. Figure to make up a card/channel with the trigger/volume sensor, uC, SD card, SRAM maybe, and volume/sound level mixing. What I was also considering is having the files on a small netbook and download into memory using USB connection. My PC programming is even worse than Arduino, so putting an SD card/channel with a bunch of sounds, say 16, and just having a push button and 4 indicator lights to cycle thru them would be a simpler way to go. If I get ambitious, could use one as a master with some sets to just pull up on an LCD screen, tell the slaves to all load sound 1, or mix & match. Having each stored locally would be faster and not need a dedicated PC with the system.

How many sound files need to be ready to start in under a ms?

It is far easier to play files from an SD than record. SD card have low read latency.

You could have a number of files open and have the first block buffered. As soon as a sound is triggered you start the DAC on the buffered block. That should take far less than 100 usec. You then start reading the rest of the selected file.

You could probably have at least 16 sounds ready on a 1284. 8 KB of RAM would be required to have the first block ready for each of 16 files.

I wrote a bit-bang driver for the AD5662 DAC. It takes about 12 microseconds to write a sample. I wrote a function to play a file and playing from an SD works with a some cpu to spare.

You must send 24 bits to the AD5662 with the first eight being zero for normal operation. Also bit-bang writing is a little slower than than bit-bang reading. You only need to clock 20 bits for the AD7680 ADC.

Using a serial port could be attractive for the AD5662 since it can operate with up to 30 MHz SPI. 8 MHz SPI over serial would cut the write time a lot.

I also prototyped the idea for buffering the first block of files to get an almost instant start for play. It will work fine, it is easy to read the next block from the SD in the 5.8 ms while the first block is playing. You could use 10 KB and have 20 quick-start sounds.