Teensy 3.0 and audio

Status
Not open for further replies.

emertonom

Active member
Hi all.

So, I'm trying to build an audio recording and playback device using Teensy 3.0. I'm pretty new at this stuff--I've been coding for years, but the hardware stuff still kind of feels like voodoo to me, so please bear with me if I make bizarre choices or silly mistakes. Arduino doesn't really seem fast enough for proper audio, but with Teensy's 32-bit processor, higher clock speed, and high-quality ADC, it seemed like audio might be within reach. This is for my sister, who does some fancy stuff with sampling and tracking for her music performance, but would like something she can use live at a venue. The input would be a microphone, probably either XLR or 1/4" jack; the output would be 1/4" jack line out to a PA system. Since it's just one mic input, monoaural is fine, but she'd like the audio to be fairly high quality.

Now, the Teensy ADC, being a successive approximation sampler, may not quite be suited to this, so I'm prepared to add an external ADC if necessary. I picked up one with a breakout board from sparkfun so I'll have it available if I need it.

Unfortunately, I haven't gotten as far as input yet...I'm having trouble already on output. I was hoping to achieve 44.1kHz 16-bit mono output. But samples for that take up quite a lot of space--the Teensy's onboard RAM holds less than a fifth of a second at that rate. So I loaded the sample (a copy of "Green and Blue" by Miles Davis, off "Kind of Blue", downmixed to mono) as a WAV file on a microSD card. Since the target design involves being able to record and play back from up to four different sample "slots," I named the file "1_44kHz.wav"--1 indicating the slot number, and 44kHz indicating the playback rate. I copied over a couple of reduced-sample-rate copies (22 & 11kHz, respectively) for additional testing.

Because I still need to do so much experimentation to get things working these days, I used the PCM56P audio chip from TI as my DAC; it's a 16-bit Burr-Brown DAC, capable of running at tremendous sample rates, with a convenient serial interface and an even more convenient 16-pin DIP package, so I can use it on a breadboard. The timing turned out to be fairly forgiving, and it's able to keep up as fast as I can bitbang the interface with digitalWriteFast(). (I was a little worried about this, because it says the max clock is 10MHz, but apparently I'm wasting enough clock cycles, even at a 96MHz clock, that the DAC has no trouble with it, and the 'volatile asm("nop\n nop\n")' sort of stuff I had in macros was unnecessary.) It required +5 and -5 voltage lines for input, which was a bit of a pain, but I had an ancient ATX 1.0 computer power supply sitting on a shelf, and ran some lines from that, which worked fine, though it's a bit bulky. I'll work out a more svelte power supply once I've gotten the kinks out of the workings.

For the sketch, I used the programmable interrupt timer (which required a bit of fiddling--not sure if they've sorted that in a newer beta of the code yet; I'm still using the system I described in an earlier post, "Teensy 3.0 and interrupts") to generate interrupts at 44.1kHz, and played back from a sample buffer. When the buffer runs out, I load a new set of samples from the microSD.

The trouble with this is that the microSD takes a while to read when it's a new access. The byte transfer rate is no problem, but the read latency is. I haven't figured out exactly how long the read takes; it's less than a millisecond (I used "millis()" to check for slow reads and output a message to Serial), but it's audible even at 11kHz, which means it's more than about 180 microseconds. Interestingly, the effect is quite subtle until the trumpet kicks in; it's loud and high-frequency, so that makes some sense, but it's striking how much more noticeable it is than with just the bass and piano.

I tried adding a "deglitcher," or sample-and-hold amplifier, but that didn't do much...which makes sense, since the problem is that it *stops* varying, not that it's varying when it shouldn't. A deglitcher is really only called for with this DAC when you're using it for stereo output--then you can use two deglitchers and an inverter to multiplex the output. But it seemed worth a try, anyway.

So I'd love advice and input on the project. I'm not exactly stalled--there are things I can think of to try with the project. The first is looking more at the documentation for the sdfat library; it's possible there are better ways to do sequential reads, or perhaps I can do something to align the reads with the block boundaries. I'm not overly hopeful on that front, though, since I'm currently using the standard read(buffer, num_bytes) format, and the buffer is quite large by Teensy standards, so the seek costs should be somewhat amortized there. I've also tried using a much smaller buffer (512B), to match the block size on the microsd, with much more frequent reads, but that just increases the *number* of latency waits, which is no good. It just makes the whole sample sound slowed-down and wobbly. Another possibility is setting up some SRAM chips to handle the buffering; that adds a fair amount of complexity, and the chips aren't necessarily huge anyway, but it'd definitely speed up memory access, and if the glitches were rare enough, they'd probably be fine. But this seems like the sort of thing other people have probably done before, and done better--is there a completely different approach that would work much better? Perhaps it'd be better to have an external SRAM chip, but also an external counter and latch and timer, so that the SRAM could feed directly in to the DAC, and the Teensy could just sneak in between those reads and burst-transfer data from the microSD to the SRAM. Or something along those lines. Anyone have thoughts on this, or on good approaches to audio in general?

I know there have been a lot of audio shields for Arduino in the past, but it seems like they're all either strictly mp3-playback-from-microsd-with-minimal-arduino-interaction, which doesn't really seem suited (not least because I don't want to try to implement mp3 audio compression on the teensy to store recorded stuff) or else 12-bit recording-and-playback shields. Or, in one case, a 24-bit recording-and-playback shield that handled the input and output, but not the microSD stuff, which is where I'm having trouble in the first place...nobody seems to be doing precisely this. But I'm sure that's partly because the Arduinos were slow and 8-bit; with the newer Arduinos and the Teensy, it seems like this ought to be possible.

I can provide the code if people are interested in trying my half-broken version as-is.

Thanks in advance!
-Nick
 
Looking at the WaveHC library from the AdaFruit Audio Shield, it looks like they got things working pretty well by using double-buffering (one to fill while the other plays, with tiny buffers--only 512 bytes) and enabling interrupts during the reads to allow playback to continue. And supposedly they could handle 44kHz 16-bit mono on an Uno, albeit playing back at only 12-bit precision. How would I go about re-enabling interrupts during a function which was itself called by an interrupt? In their library they just use "sei()", but that does nothing here. I also tried "__enable_irq()" and "NVIC_ENABLE_IRQ()", but they didn't appear to do anything either. I'm sort of fumbling in the dark here--is there a manual somewhere I should be referring to for this information?

Sorry about the frequent, low-information posts...I tend to work on these things in bursts.

Thanks,
-Nick
 
How would I go about re-enabling interrupts during a function which was itself called by an interrupt?

The interrupt controller on the teensy 3 is a "nested" interrupt controller which basically means that you can assign configurable priority to interrupts. Only interrupts with a higher priority than the current one can interrupt you. (Somewhat confusingly, in ARM higher priorities are numerically closer to 0.)

This is documented in "ARMv7-M Architecture Reference Manual" page B1-6 "Priority Levels and Execution Pre-emption"

The actual priority registers are documented in table B3-29 on page B3-31: "NVIC_IPR0" etc...

The book "The Definitive Guide to the ARM Cortex-M3, Second Edition" was pretty useful to me in figuring some of this stuff out.

Hope this helps.

-c
 
Thanks, that was exactly what I needed! Here's my line for NVIC_SET_PRIORITY, since that's not currently implemented in mk20dx128.h:

#define NVIC_SET_PRIORITY(n, p) (*((volatile uint8_t *)0xE000E400 + n) = p << 4)

I'm not completely certain that's right, for three reasons. First, I'm not wholly sure that should be unt8_t rather than int8_t--the manual at one point (B1-18) describes Reset, the non-maskable interrupt, and hardware fault as having priorities -3, -2, and -1, respectively, but I think that's to imply they are necessarily higher priority than user-definable interrupt priorities, not to imply those are valid values.

And second, oddly, the manual claims that IPR123 starts at 0xE000E7EC. That appears to be an error--I think it actually starts at 0xE000E5EC (0xE000E400 plus 0x1EC = 492 = 123*4) even though all the other addresses around that part of the manual refer also to the E7 aspect. (E.g., the "reserved" area is given as starting at 0xE000E7F0, and there's a table labelled "Table B3-36 Interrupt Priority Registers – (0xE000E400-E7F8)".) It seems weird that it would be quite so consistent if it were an error, but I couldn't for the life of me come up with a way to make a table of 123 words of 4 bytes each take up an extra 512 bytes. The division just doesn't work. If you've got any insight there, I'd much appreciate it.

But the code did work for setting the priorities for the programmable interrupt timer, which is what I was using. (In fact I could have used basically any interrupt for the second one, since I just used NVIC_SET_PENDING to trigger it, but I was already working with the PIT, so I threw the second one on there too. After I've finished my incremental tweaks and got everything fully working, I'll start over and write a proper version of this code using everything I've learned.) So for the time being I'm going to assume that line works.

It's also worth noting that I'm throwing out 4 bits of the 8-bit priority setting here--only the settings 0-15 will have any effect. That's because the Teensy appears to implement only 16 priority levels. If the line were implemented as:
#define NVIC_SET_PRIORITY(n, p) (*((volatile uint8_t *)0xE000E400 + n) = p)
then levels 0-15 would all be treated as equivalent, and no overriding would take place--the low-order 4 bits would be discarded. That's fine too, but to me it seems slightly more confusing to have to use increments of 16 in my priority numbering than to have the priority numbers restricted to a small range but contiguous.


But this is a big step forward for my project! It means I've got a fully functioning sketch that streams a WAV file off a microSD card and plays it back through the PCM56P DAC at 44.1kHz, 16-bit mono. That's a big portion of the way towards a functioning sampler! I'm quite excited. The Teensy 3.0 is proving quite capable of this kind of audio handling!

Thanks for the tips on interrupt priority!
-Nick
 
Thanks for reporting on your progress. I was unaware of that particular DAC, and the control method is also a bit different than the usual interface (I2S) used for audio DAC. The availabliity in DIP rather than the nowadays common SMD formats is also a plus. (data sheet). TI claim that it is obsolete and replaced by DAC8580, although that part is rather different and does not seem especially audio-oriented.

Would you be willing to post your code, for others to use as an example of interfacing to it?
 
Last edited:
Sure, yeah. I know the chip is deprecated, but since this is a one-off project, the DIP format seemed extremely appealing to me. I'm not very good at soldering, and the surface-mount components are really tricky for me--the audio DACs tend to be ridiculously tiny. I know I2S is standard and there are libraries for it, but this chip's interface is pretty simple.

Fair warning--this code is extremely rough, because it's an experiment in progress. In particular, I just swizzled it around to make use of the changes in Beta 8's interrupt code, which let me write interrupts without changing mk20dx128.h, provided I put the interrupt service routine in a .cpp file rather than a .ino file. So the division between the .cpp file and the .h file is not based on anything particularly rational, and the comments are a bit messed up. Also, expect NVIC_SET_PRIORITY to be implemented in a subsequent version of mk20dx128.h, which will make the definition in this project unnecessary.

First, wiring:
Teensy 3 is being powered off USB. Pins 10, 11, 12, and 13 are connected to the MicroSD slot, as are Ground and 3.3V; these are the standard pins for the sdfat library, which I'm using. I've specified 10 as Chip Select. Teensy Pin 2 goes to the DAC's pin 6, which is the latch enable input; Pin 3 goes to DAC's pin 5, clock; and Teensy 4 goes to DAC 7, which is the data line. As for the rest of the DAC's pins: 1 is connected to the power supply's -5V, 2 to ground (common between Teensy and the power supply), 3 to PS +5V, 4 is unconnected, (5, 6, and 7 are connected to Teensy as described above), 8 is connected to PS -5V, 9 is the output but is also shorted to 10, 11 is shorted to 13, 12 is ground, 14 and 15 are unconnected, and 16 is PS +5V. It's a little weird that I'm supplying the logic side of the DAC off the PS instead of Teensy, but it wanted +5 and -5 as well as common and wouldn't operate without them, so I just hooked them up. The output from the DAC (pin 9) goes to a potentiometer, which I'm using as a crude volume control / voltage divider, and from there to a headphone jack.

The sketch:
Code:
// These two includes are needed in the sketch so that the compiler will add the 
// libraries to the include path when compiling the .cpp file.
#include <SdFat.h>
#include <SdFile.h>

#include "playback.h"

//------------------------------------------------------------------------------
void setup() {
  pinMode(LATCH_ENABLE_PIN, OUTPUT);
  pinMode(CLOCK_PIN, OUTPUT);
  pinMode(DATA_PIN, OUTPUT);

  Serial.begin(115200);
  while (!Serial);

  Serial.write("Starting\n");
  Serial.flush();

  buffer_setup();

  timer_setup();

  Serial.write("Timers started, initialization done\n");
  Serial.flush();
}
//------------------------------------------------------------------------------

void loop() {
}

I know, it's short. As I said, I didn't bother much with the logic of dividing things up here.

The "playback.h" file:
Code:
#ifndef PLAYBACK_H
#define PLAYBACK_H
/*
 * Read a mono, 16-bit WAV file from the SD card and play it back over an external DAC.
 * Edits inspired by the AdaFruit audio shield library.
 */

#include "Arduino.h" 

#include <SdFat.h>
#include <SdFile.h>

// A future version of the Teensy IDE will provide a better implementation of this macro.
#define NVIC_SET_PRIORITY(n, p)  (*((volatile uint8_t *)0xE000E400 + n) = p << 4)

// SD chip select pin
const uint8_t chipSelect = 10;

// Pins for pcm56 chip serial com
const int LATCH_ENABLE_PIN = 2;
const int CLOCK_PIN = 3;
const int DATA_PIN = 4;

// Constant for the highest bit in a 16-bit integer
#define HIGH_BIT 32768

// Constants for bitvalues within the TCTRL1 register
#define TIE 2
#define TEN 1

// initialize the SD card at SPI_HALF_SPEED to avoid bus errors with
// breadboards.  use SPI_FULL_SPEED for better performance.
#define SPI_SPEED SPI_FULL_SPEED

#define MAX_SAMPLES 2000

#define HEADER_LENGTH 44
#define LOOPS_REPORT 60000

#define FILENAME "1_44k.wav"
#define SAMPLES_PER_SECOND 44100
//#define FILENAME "1_11k.wav"
//#define SAMPLES_PER_SECOND 11025
//#define FILENAME "1_22k.wav"
//#define SAMPLES_PER_SECOND 22050

#define BUFFER_READY 1
#define BUFFER_FILLING 2
#define BUFFER_END_OF_FILE 3

// Maximum number of times to retry reading a block from the card before giving up
// Note that this is guaranteed to occur at the end of the recording
#define MAX_READ_RETRY 5

// Set the period of the timer.  Unit is (1 / 50MHz) = 20ns
// So 50000000 periods per second -> 50000000 / SAMPLES_PER_SECOND periods per sample.
#define SET_LDVAL (50000000 / SAMPLES_PER_SECOND)

// Priorities for the two PIT timers
// Lower value takes precedence
// Must be in the range 0-15
#define PIT_CH1_PRIORITY 1
#define PIT_CH2_PRIORITY 2

typedef struct {
  char RIFF[4];  // Literally "RIFF"
  int32_t chunk_size;
  char WAVE[4];  // Literally "WAVE"
  char fmt_[4];  // Literally "fmt "
  int32_t subchunk1_size;
  int16_t audio_format;
  int16_t num_channels;
  int32_t sample_rate;
  int32_t byte_rate;
  int16_t block_align;
  int16_t bits_per_sample;
  char data[4];  // Literally "data"
  int32_t subchunk2_size;
} wave_header;


void timer_setup();
void buffer_setup();

#endif // ndef PLAYBACK_H

Er, the forum is objecting my post is too long, so I'll post the .cpp in one more message, coming almost immediately.
-Nick
 
As promised, finally, "playback.cpp":

Code:
#include "playback.h"

volatile int current_sample;
int16_t buffer1[MAX_SAMPLES];
int16_t buffer2[MAX_SAMPLES];
volatile boolean using_buffer1;
volatile int buffer1_status;  // status of buffer1
volatile int buffer2_status;  // status of buffer2
wave_header header;
volatile long sample_num;

// file system object
SdFat sd;
SdFile *file;

void stop_interrupts() {
  PIT_TCTRL1 = 0;
}

void fill_buffer() {
//  int start_time = millis();

  // Want to read a full buffer's worth of data.
  int8_t *current_read_point;
  if (using_buffer1) {
    current_read_point = (int8_t *)buffer2;
  } else {
    current_read_point = (int8_t *)buffer1;
  }
  
  int bytes_to_read = MAX_SAMPLES * 2;
  int bytes_read = 0;
  int loops = 0;
  while (bytes_to_read > 0) {
    if (loops > MAX_READ_RETRY) {
      Serial.write("Repeated short reads, aborting\n");
      Serial.flush();
      stop_interrupts();
      cli();
      return;
    } else if (loops > 0) {
      Serial.write("Short read, retrying\n");
      Serial.flush();
    }
    bytes_read = file->read(current_read_point, bytes_to_read);
    bytes_to_read -= bytes_read;
    current_read_point += bytes_read;
    loops++;
  }
  if (using_buffer1) {
    buffer2_status = BUFFER_READY;
  } else {
    buffer1_status = BUFFER_READY;
  }

/*  int end_time = millis();
  if (start_time != end_time) {
    Serial.write("Buffer fill took ");
    Serial.print(end_time - start_time);
    Serial.write(" milliseconds.\n");
    Serial.flush();
  }
  */
}

extern "C" void pit2_isr(void) {
  // Clear the timer interrupt flag
  // This prevents this interrupt from being called endlessly.
  // (Not technically needed in this sketch, because we're ONLY
  // using NVIC_SET_PENDING to call this interrupt--if we were using
  // the timer interrupt as an actual timer, we'd need this line.)
  PIT_TFLG2 = 1;
  
  // Run "fill_buffer".  Since this interrupt has lower priority than
  // pit1_isr, playback will continue while the buffer fills.
  fill_buffer();
}

extern "C" void pit1_isr(void) {
  // Clear the timer interrupt flag
  // This prevents this interrupt from being called endlessly.
  PIT_TFLG1 = 1;

  uint16_t full_sample;

  if (using_buffer1) {
    if (buffer1_status == BUFFER_READY) {
      full_sample = (uint16_t) buffer1[current_sample];
    } else {
      Serial.write("Buffer underrun\n");
      Serial.flush();
      return;
    }
  } else {
    if (buffer2_status == BUFFER_READY) {
      full_sample = (uint16_t) buffer2[current_sample];
    } else {
      Serial.write("Buffer underrun\n");
      Serial.flush();
      return;
    }
  }

  // Write first data bit before setting LATCH_ENABLE pin
  if (full_sample & HIGH_BIT) digitalWriteFast(DATA_PIN, HIGH);
  else digitalWriteFast(DATA_PIN, LOW);
  digitalWriteFast(CLOCK_PIN, HIGH);
  digitalWriteFast(LATCH_ENABLE_PIN, HIGH);
  digitalWriteFast(CLOCK_PIN, LOW);
  full_sample = full_sample << 1;

  // Write remaining bits
  for (int i = 1; i < 16; ++i) {
    if (full_sample & HIGH_BIT) digitalWriteFast(DATA_PIN, HIGH);
    else digitalWriteFast(DATA_PIN, LOW);
    digitalWriteFast(CLOCK_PIN, HIGH);
    full_sample = full_sample << 1;
    digitalWriteFast(CLOCK_PIN, LOW);
  }

  // Set LATCH_ENABLE low
  digitalWriteFast(LATCH_ENABLE_PIN, LOW);

  current_sample++;
  if (current_sample == MAX_SAMPLES) {
    current_sample = 0;
    using_buffer1 = !using_buffer1;
    if (using_buffer1) {
      buffer2_status = BUFFER_FILLING;
    } else {
      buffer1_status = BUFFER_FILLING;
    }
    
    // Schedule PIT_CH2 timer to go off soon
    // This timer's interrupt will load from the SD--with a lower priority than 
    // the playback IRQ.
    NVIC_SET_PENDING(IRQ_PIT_CH2);
  }
}

void timer_setup() {
  SIM_SCGC6 |= SIM_SCGC6_PIT; // Activates the clock for PIT

  // Turn on PIT
  PIT_MCR = 0x00;

  // Set the period of the timer.  Unit is (1 / 50MHz) = 20ns
  // So 44.1 kHz frequency -> 22.68us / 20ns = 1134 periods
  PIT_LDVAL1 = SET_LDVAL;
  // Enable interrupts on timer1
  PIT_TCTRL1 = TIE;
  // Set PIT_CH1 interrupt priority
  NVIC_SET_PRIORITY(IRQ_PIT_CH1, PIT_CH1_PRIORITY);
  // Start the timer
  PIT_TCTRL1 |= TEN;
  
  // Enable interrupts on timer2
  PIT_TCTRL2 = TIE;
  // And set the priority
  NVIC_SET_PRIORITY(IRQ_PIT_CH2, PIT_CH2_PRIORITY);
  // But don't start the timer!
    
  NVIC_ENABLE_IRQ(IRQ_PIT_CH1); // Enable IRQs for interrupts on timer1
  NVIC_ENABLE_IRQ(IRQ_PIT_CH2); // Enable IRQs for interrupts on timer2
}

void buffer_setup() {
  if (!sd.begin(chipSelect, SPI_SPEED)) sd.initErrorHalt();
  Serial.write("Sd Initialized\n");

  // open file in current working directory
  file = new SdFile(FILENAME, O_READ);

  if (!file->isOpen()) sd.errorHalt("open failed");

  Serial.write("File opened\n");

  Serial.write("Preparing to read header--header length:");
  Serial.println(sizeof(header));

  if (file->read(&header, HEADER_LENGTH) != HEADER_LENGTH) {
    Serial.write("Error reading header--hanging\n");
    // Hang forever
    file->close();
    return;
  }

  Serial.write("Read header\n");

  buffer1_status = BUFFER_FILLING;
  buffer2_status = BUFFER_FILLING;
  current_sample = 0;
  using_buffer1 = true;
  fill_buffer();
  using_buffer1 = false;
  fill_buffer();
  using_buffer1 = true;
  sample_num = 0;

  Serial.write("Read first sample block\n");
}

It's not pretty, and it does all sorts of stupid things like ignore the EOF marker, and use the PIT channel 2 interrupt vector where it should be using the software interrupt vector (IRQ_SOFTWARE and software_isr). The WaveHC Arduino library is a lot better-written example of the same sort of program. But this does demonstrate Teensy 3 interrupts and interrupt priority, as well as interfacing with the PCM56P DAC, so hopefully there's some value there.

Hope this helps!
-Nick
 
Sure, yeah. I know the chip is deprecated, but since this is a one-off project, the DIP format seemed extremely appealing to me. I'm not very good at soldering, and the surface-mount components are really tricky for me--the audio DACs tend to be ridiculously tiny. I know I2S is standard and there are libraries for it, but this chip's interface is pretty simple.
Absolutely agree. The only SMD components I have used, I bought already soldered onto breakout boards for that reason.

Many thanks for posting your code.

It's a little weird that I'm supplying the logic side of the DAC off the PS instead of Teensy, but it wanted +5 and -5 as well as common and wouldn't operate without them, so I just hooked them up. The output from the DAC (pin 9) goes to a potentiometer, which I'm using as a crude volume control / voltage divider, and from there to a headphone jack.

Does that jack go to drive headphones, or is it used as an unbalanced line out? I would suggest an op-amp output buffer (which should be easy since you have +5V and -5V rails already in your project) after the volume control and before the output. That isolates the DAC from any effects of longer cables, lower input impedance loads, etc. It will probably make it sound better, too.
 
Of course--I'm more than happy to share my code. It's nice to hear from someone else who's interested in the same sorts of applications.

Output isolation is definitely a good suggestion. I'm just using crummy headphones for proof-of-concept testing right now, so I'm not worried about it yet, but I'm definitely planning to put something in there in the final design. It's strictly a work in progress. :) Thanks for the feedback, though.

Actually, at the moment I'm working more on the input side of things, and if you've got any advice on that, it'd be quite helpful, as I've never used microphone input before. My preamp is an INA217 chip. I've got it connected to the +5 and -5 rails, and I've got an audio pot as the gain-set resistor. My mic is just a cheap dynamic mic from radioshack; it's plugged in to another 3.5mm jack, and only has two connections. I've wired those connections to the +Vin and -Vin on the preamp. I tried to test it by tying the output offset to ground and sending the output to my headphones, but I got no sound, and my multimeter couldn't pick up any AC voltage at all, not even in the millivolts. It's supposed to be able to generate a gain up to 10000x, so it seems like I should be hearing, well, a hiss, if nothing else. It's always possible I've wired something wrong, or maybe the mic is broken, but this is just my best guess at the correct setup, so it seems possible to me you might recognize a foolish mistake just from that description. Any suggestions would be most welcome!

I've also set up an op-amp to provide a 2.5V output offset for when I hook it to the ADC, which isn't expecting negative voltages, but I haven't even started on setting up the ADC yet, so at present the op-amp isn't connected to anything. But I have taken offsets into account. :)

Oh yeah, a caveat about the program I posted--it doesn't actually look at the file header at all. It just skips past it to the samples and starts playing them, and assumes they're all in one "block," and that the bit depth and sample rate match the parameters in playback.h. The AdaFruit library actually reads the header and handles that stuff properly; it wouldn't be hard to adapt it with this interrupt code.

Thanks for the feedback. Hope you get some use out of the code. Let me know if you have any thoughts on the preamp!
Thanks,
-Nick
 
Found this blog post: http://hifiduino.wordpress.com/2012/09/12/teensy-3-0-with-i2s-interface/
which links to an Application Note: http://cache.freescale.com/files/32bit/doc/app_note/AN4369.pdf

Both of those are talking about using DMA and I2S, but they point to another way of doing an audio playback system. I don't think sdfat for Teensy 3.0 supports DMA yet, but I think it's under development, so down the line this may be possible. The post also mentions an audio shield that Paul is developing. So it's likely that in a few months there'll be a way of doing exactly what I'm trying to do here, but with much less work and a much higher quality of input and output. Oops! Maybe I'm tackling this prematurely. Ah well, it's a hobby. I'm going to keep at it for now.
 
"Teensy 3...high-quality ADC..." yes, in some applications, but I don't think it was designed for audio. An external ADC can give better performance.

"...ancient ATX 1.0 computer power supply..." You probably know this, but be sure to use a good quality low-noise power supply if you want a clean audio signal. An old computer supply is probably the opposite of that.

I don't know about the SDcard issues except that all the SD cards I've used with Arduino have had occasional long latencies. I presume "real" SD card audio devices use the faster 4-bit wide SDIO interface, not the basic 1-bit wide SPI bus like Teensy does.
 
Also: I'm an idiot--the INA 217 data sheet shows the wiring for the mic, both with phantom power (non-electret condenser mics) and without (dynamic mics). Not sure why I forgot about that, because I knew about it previously--I even ordered some bits particularly for the circuit. Ah well. Anyone wanting to know how to wire up a decent quality microphone should refer to that data sheet! http://www.ti.com/lit/ds/sbos247b/sbos247b.pdf , and it's Figure 4 that shows the full wiring. The wiring's identical for a 1/4" or 3.5mm (or indeed 2.5mm) jack; the ground goes to the "sleeve" connector (see http://en.wikipedia.org/wiki/Phone_connector_(audio)#Tip-ring-sleeve_terminology ). The preamp and the dynamic mic are both working perfectly now! Sorry for the dumb question.
 
@JBeale: Duly noted. The crummy power supply is just for proof-of-concept testing and experimentation--it offered the dual virtues of "having +/-5v and +/-12V" and "being in the same room." :) I'll use much better parts for the actual device. And the ADC--well, the Teensy's ADC is inarguably higher-quality than the Arduino ADC, but you're quite right that an external ADC would be better here. I've got a PCM1803 on a breakout board I got from Sparkfun which is going in to the design. The Teensy's ADC actually probably wouldn't be terrible in terms of resolution, but the sample rate would be too low.

And I fixed the SD card thing with the interrupt priority settings, taking a cue from the AdaFruit library, which was managing to do 44.1kHz on an Arduino Uno, so in fact the read latencies probably aren't going to be *too* big a deal. I may run into problems once I start reading and writing multiple files at once, in which case I'll just throw on a 23lcv1024 megabit sram chip for the buffers; it should have hardly any latency, and it can hold enough samples in enough buffers that I shouldn't underrun even with pretty substantial latencies.

I think it's going to work out. I continue to inch towards having all the components talking to one another.

Thanks for all the help, guys. I'm sure I'll have more questions...
-Nick
 
INA217 is probably a good choice. I don't have direct experience with it but I am aware of other mic preamp designs that use it or similar instrumentation-style chips, like THAT 1510/1512. This includes some well-regareded commerial designs - as an example, the Rane MS1S (spec sheet | schematic).

There was a long thread on the Yahoo group 'micbuilders' about someone who built an INA217-based mic pre, which exhibited unstable oscillation. It seems that their design was particularly sensitive to radio frequency interference. (Thread starts here).
I also came acros a report of a successful INA217-based DIY project, mostly based on the spec sheet sample circuit. The discussion centred around turning the original unbalanced output into a balanced design.

If your current circuit has high gain and produces no sound, that could be because a small input DC offset is being amplified way up and slamming the output to one of the power rails. A multimeter would show if that is the case.

For your mic - a dynamic is a good choice to start with for a portable/live use scenario as they are typically fairly robust, can tolerate high input levels, and don't need phantom power. (in the studio, small and large diaphragn condenser mics are more usual, which need clean 48V phantom power, or tube/valve mics which have their own high voltge supplies, or ribbon mics which are fragile and need very low noise, very high gain mic preamplifiers). Check though, especially if its a real low budget mic that it is in face a dynamic and not an electret mic (as typically used for PC soundcards, and needing 5V power).

What mic will your sister be using live? It seems like a good idea to find out and design around that. Also, if she has studio recordig equipment already, checking your curent mic for correct functioning in that setup would seem prudent.
 
Thanks for the tips! In fact both the mic and preamp are now working fine--I just hadn't connected things correctly. Once I corrected it to match the design in the preamp's datasheet, it worked fine. They also include specs for adding optional (switched) phantom power, which would make the system a bit more versatile. I suspect she'll be using a dynamic mic, for precisely the robustness you mention, but she's a guitarist and vocalist and also sometimes does recordings in a sort of home-studio setup, so it's possible she may want to use a condenser at some point too; it'd be nice to offer both. I may offer both 1/4" and XLR jacks in the final version for that reason. But I'm not worrying about it yet, because I don't have an adequate power supply to do phantom power at this point, not to mention the only condensers I have on hand are in fact terrible little computer mics that came free with other stuff.

Right now I'm focusing on the ADC chip. It turns out the breakout board I got has an oscillator pre-loaded, and it's a 24.576MHz clock, which means I'll have to switch to either 32kHz or 48kHz for the recording speed--you need a different clock for 44.1kHz. But of more concern is that it's an I2S chip, and the libraries for that aren't done yet, and won't be for a few months. Paul Stoffregen is an amazing guy, but he's busy working on the basic Arduino compatibility libraries. So it's starting to occur to me that it may be a little bit irresponsible to work on this now; when a proper hardware guy has had a chance to design well-written libraries for I2S, it'll be much easier to make this kind of project, and indeed, since I2S supports DMA on the K20 chip, it'll be possible to do all the stuff I've done so far while putting practically no load on the core chip, leaving it free for other tasks, particularly if sdfat also comes to support DMA. By contrast, if I write this stuff now, cobbling it together out of code fragments from application notes and datasheets, it'll be kind of a mess, use lots of CPU time, and do many things in ways that are inefficient or simply wrong--and that may be a disservice to the community, since having an example out there of an interesting application done in an improper way could lead some people to conclude that the hardware is less capable than it actually is.

That said...I think I'm still going to give it a try, because I'm curious and I'm really learning a lot about electronics and embedded-systems programming. So I'm sticking with it. We'll see whether I'm up to the task of actually implementing even rudimentary I2S, though. I'm not even sure which pins it's on yet. It may be a little silly to do this now--a little like climbing a cliff face while someone ten feet over is building an elevator--but as I say, it's educational.

Thanks for the help!
-Nick
 
I suspect she'll be using a dynamic mic, for precisely the robustness you mention, but she's a guitarist and vocalist and also sometimes does recordings in a sort of home-studio setup, so it's possible she may want to use a condenser at some point too; it'd be nice to offer both.

Yes. My wife plays ukelele and sings, and we use condenser mikes to record her. These are chosen to give a reasonable sound at a relatively modest cost (compared to multi-thousand studio mikes).

I may offer both 1/4" and XLR jacks in the final version for that reason. But I'm not worrying about it yet, because I don't have an adequate power supply to do phantom power at this point, not to mention the only condensers I have on hand are in fact terrible little computer mics that came free with other stuff.

My understanding is that a common circuit is a 15-0-15 mains transformer, with the centre tap unconnected to give 30V, feeding a voltage doubler to give 60V, and then regulated to give low-ripple 48V at 10mA.

Right now I'm focusing on the ADC chip. It turns out the breakout board I got has an oscillator pre-loaded, and it's a 24.576MHz clock, which means I'll have to switch to either 32kHz or 48kHz for the recording speed--you need a different clock for 44.1kHz.

Recording at 24bit/48kHz should be fine, it is what is used for video and also the old ADAT standard for audio. Plus that maks the anti-aliasing low pass filter easier as you have more room between the top of the audible spectrum at 20kHz and the Nyquist limit.

But of more concern is that it's an I2S chip, and the libraries for that aren't done yet, and won't be for a few months.
...
That said...I think I'm still going to give it a try, because I'm curious and I'm really learning a lot about electronics and embedded-systems programming. So I'm sticking with it.
Nothing wrong with some educational exploring.
Plus you can experiment with the mic pre and output buffer to PA for live sound (without the digital recording part).
 
What is this mem smt slot and how can I use it?

not quite sure what you are referring to? also it's a very old thread (?).

i can see mention of a/the microSD 'slot', i2s DACs which are SMD, and some SRAM ICs, which are SMD, too.
so i'm guessing the latter? as it happens, the specific one mentioned - 23LCV1024 - should work with the audio adapter; that is, in place of the SPI flash (pins 3,7,8 are tied to VDD, i'm not sure though about #3, which is NC). 23LC1024 might be the better choice, if you're using the adapter. i'm not aware of anyone actually using it though, or having done anything in the way of adding RAM.
 
Status
Not open for further replies.
Back
Top