View Full Version : AudioInputAnalog: reading from a DAC output

06-12-2016, 10:08 AM
First of all, thanks for the great Audio Library and the Teensyduino experience! I upgraded my project from an Arduino Nano in a day...

I'm working on a DIY Laser Projector (driving commercial laser galvos from ebay with the Teensy + DAC + some opamps for bipolar ILDA signals).
Drawing text and effects works fine already and I started to add sound via a WTV20 module that plays AD4
files from SD card.

The WTV20 has a DAC output which provides a 0v - 3.3v audio signal.
I am using that signal as AudioInputAnalog source and added a live spectrum analyzer drawn with the laser.

I have two questions regarding this:

1. The AudioInputAnalog sets the analog reference to INTERNAL, which matches the RCA biasing circuit's range. So I got my signal clamped to 1.2V... I patched the AudioInputAnalog to use DEFAULT and got my full signal. So my question is, would it be possible to make this configurable in AudioInputAnalog? Because I don't really want to require patching the Audio library. Or would you suggest scaling the signal down to the 1.2v range because the INTERNAL reference has better precision?

2. What would be the simplest thing to convert the 3.3V down to 1.2V? I was not sure if a voltage divider with two resistors would be sufficient, because it will draw some current (too much?) from the Teensy? I am more a software guy, so I am unsure about this.

3. I had some problems with the interrupts that fetch the audio data, because that takes so much time that is appears as a lighter laser dot because the laser will hold while the interrupt is working. My workaround for this is to only enable interrupts when the laser is not drawing and to wait some time between the laser drawings so that the audio data can be read and FFT can be done. Is there a more elegant way to do this?

I am working on a video to share with you, the spectrum analyzer looks nice already.

Best regards,
Florian Link

06-12-2016, 12:14 PM
A voltage divider formed by a 4k7 resistor and a 2k7 resistor will at the same time divide 3.3V down to 1.2V and present a sufficient low inner resistance to not to be troubled by the current sinking peaks of the SAR ADC.

Frank B
06-12-2016, 06:01 PM
You know that the Teensy is fast enough to play MP3 without additional hardware ?

06-12-2016, 06:17 PM
No, I didn't. But is it fast enough so that I can draw with the laser at the same time and play the mp3?
The laser drawing is very time sensitive, like I wrote I can see the audio framework grabbing audio blocks as artifacts in the laser drawings, which is why I stop the interrupts while drawing. I guess that would give me stuttering in the mp3 playback, right? And the WTV20 already supports a small speaker, so no amplifier is needed, which I guess I would need to add to the Teensy DAC output.

Frank B
06-12-2016, 06:31 PM
Yes, stopping the interrupts will stop the mp3-decoding.

06-12-2016, 06:49 PM
Thanks, the voltage devider works well! Is it better to use the INTERNAL 1.2 voltage reference instead of the DEFAULT 3.3v reference?

06-12-2016, 07:08 PM
FSo my question is, would it be possible to make this configurable in AudioInputAnalog? Because I don't really want to require patching the Audio library.

Sure. We already have this for AudioOutputAnalog, so it probably makes sense to add for AudioInputAnalog too. I personally won't be able to work on this for several weeks, while focusing on the K66 beta testing. But if anyone submits a good pull request, or eventually, it certainly can and should happen.

My workaround for this is to only enable interrupts when the laser is not drawing and to wait some time between the laser drawings so that the audio data can be read and FFT can be done. Is there a more elegant way to do this?

Perhaps use IntervalTimer to run your timing sensitive code. Maybe even increase the priority level, or go to even more extremes for low-jitter timing (https://forum.pjrc.com/threads/27690-IntervalTimer-is-not-precise?p=64142&viewfull=1#post64142). But this means your code will run as an interrupt, which imposes all the usual issues to share data properly with the main program. Which way is more elegant is probably a matter of opinion.

06-12-2016, 07:26 PM
Thanks Paul, but I am a bit afraid of doing a state machine for all my drawing code, which I would need to draw stepwise in the interrupt...

One simple thing I noticed is that the FFT is calculated even when the last calculation was not read by the users code.
This has the advantage that the user gets the most recent FFT when he asks, but it also means that various FFTs are calculated for nothing. So an option to use less CPU power for the FFT would be to skip the calculation if a calculated FFT has not yet been read.

I will also have a look if I can split the FFT to the 4 slots, as you mentioned.

Frank B
06-12-2016, 07:50 PM
What are your timing requirements ?
-Is it in the range of milliseconds or less ?

Perhaps the most easy path is to use 2 MCus.. Teensy LC + Teensy 3.2 ?

06-12-2016, 08:22 PM
My current code writes to the DAC (for the laser, dual channel for x/y) via SPI and uses delayMicroseconds in the range of 20-40 between positions on a line and 400 microseconds for turning the laser on/off to account for the delay of the DAC and galvos to reach the specified position. So it is more in the range of microseconds, not milliseconds.

If I would use two teensys, how would I do the communication? (I mean without running into the same timing problem, spending the time on reading the FFT result from the other Teensy.)

I think one solution would be to write all laser commands to memory from the main program and use a high priority interrupt to do each step at a given time slot. Then swap the buffer to the next frame etc.
Maybe I will give that a try next weekend...

Frank B
06-12-2016, 08:36 PM
Should be possible with a clock-line (+x data-lines).. (or SPI? The one for the laser as master)- the TeensyLC (for the laser) does the timing and reads when it has time to..

06-13-2016, 10:20 AM
I created a pull request that adds the analogRef to the constructor of AudioAnalogInput:


06-14-2016, 06:34 PM
I tried spreading the FFT code to the 4 time slots instead of doing it in one, but most time (approx. 70 percent) is
spent in the FFT arm_cfft_radix4_q15 arm function call (I measured 1.1 ms). I had a look at that function and it can't be easily split into multiple steps.

Then I found the solution to my problem: The FFT is calculated several times while I draw the spectrum with the laser,
so for my use case it is much better to calculate the FFT on demand. I did a patch to analyze_fft1024 to only copy the data blocks to the buffer in update() and to calculate the FFT in isAvailable(). By doing so, I only get the FFT data that I can really handle and I can choose when to spare the time to calculate it.

Would it be acceptable if I provide a pull request to make this configurable?

06-16-2016, 04:17 PM
Maybe try this?


06-16-2016, 05:36 PM
I fixed this for my project by calculating the FFT on demand. See this commit:


06-16-2016, 09:41 PM
Here's a short video showing the project:


(It looks better to the human eye than to the camera)

06-16-2016, 11:51 PM
Awesome project. I didn't know laser galvos are so cheap. The spectrum visualizer reminded me of this xkcd ;) https://xkcd.com/26/

06-17-2016, 07:40 AM
That's why I upgraded from speakers to real galvos, the slow galvos (like the ones I used) are really cheap compared to what you get.