The part where he makes a small-ratio non-inverting amplifier is confusing to me, and he is using parts that take weeks to ship, and some are unavailable. Is there no way to get a single IC that does the same thing?
Is there a way to know which one will generate 5V logic level going out so it doesn't fry Arduino Uno?
I watched some op-amp videos on Khan Academy but they say op-amps are complex and have hundreds of transistors so they don't go into detail on how they work or what chips to get. This one has 3 resistors and a capacitor, so I am not sure where the complexity mention on Khan Academy comes from.
Thank you for your response! I attached the diagram in that article.
It's hooked up to measure a 555 circuit, in my case I need to measure an audio stream coming from 1/8" audio jack. The voltage on the jack is apparently so low that it needs to be amplified from some milliamps to 0-5V range for analog read on Arduino. I watched another hour of opamp/non-inverting-amp tutorials but still don't understand how to make that circuit and how it works.
The voltage on the jack is apparently so low that it needs to be amplified from some milliamps to 0-5V range for analog read on Arduino.
Where is the audio signal coming from?
A microphone signal is a few millivolts* and needs to be amplified. A line-level (like from the RCA jacks on a CD/DVD player or TV) or a headphone-level signal is about 1V and it's usually usable. You rarely need the whole 0-5V range.
The signal should be biased because the Arduino can be damaged by the negative half of the AC audio signal and/or the audio can be "damaged' (distorted). There is a simple bias circuit for line/headphone signals or the bias can be built-into the amplifier.
I watched some op-amp videos on Khan Academy but they say op-amps are complex and have hundreds of transistors
They are complex internally but you are not building an op-amp and you can build a simple inverting or non-inverting amplifier using an op-amp with just a few additional components.
...The need for bias and/or if you don't have a dual (positive and negative) power supply for you op-amp adds a little to the complexity. And if you're using an electret microphone they have to be supplied with power so that adds a bit more complexity.
BTW - Since audio is approximately symmetrical you can use an inverting or non-inverting amplifier and it won't change what you "see" on an oscilloscope (or what you hear). Technically a 'scope should "tell the truth" so it shouldn't invert, but you can re-invert in software.
In this case, it's the voltage we are concerned with, not the current (Amps/milliamps).
Thanks again for quick responses, I appreciate it!
The signal is coming from this TRS stereo audio jack (called both 1/8" and 3.5mm), with a computer audio source (headphone level):
After reading the outputs of that audio jack into analog pins, I would have to sum to mono before displaying the waveform.
I have no issue using map() to remap from 0-1V into 0-32 number range for displaying the waveform on my 128x32 OLED, but:
Will I need amplification to lower the noise floor? Perhaps 0-1V does not give me enough of a range for displaying a useful audio waveform and it will have artifacts from noise and interference that look like audio signal? I just need to make sure oscilloscope displays meaningful audio, and I'm OK at gaining at the output level on the computer to make it loud going in.
How do I bias the signal? This is in reference to: "The signal should be biased because the Arduino can be damaged by the negative half of the AC audio signal and/or the audio can be "damaged' (distorted). " I understand that I need to add resistor(s) and a capacitor to turn (-x to x) signal into (x to 2x), is that what the circuit in the original article does? If that circuit is not needed because I am dealing with headphone level audio, and doesn't bias, then what circuit should I use? What should the output voltage range be for reading on one of the Arduino analog pins?
Will I need amplification to lower the noise floor?
An amplifier will amplify any existing noise and it will add some noise of it's own. A good amplifier won't add significant noise.
You can amplify digitally (with multiplication or with the map function) and that won't add noise but you'll loose resolution compared to analog amplification.
How do I bias the signal?
The standard bias circuit is attached at the bottom of this post. Two equal-value resistors make a voltage divider which supplies 2.5V to the analog input. The capacitor "isolates" the bias voltage from the analog source.
Typically, you subtract-out the bias (which should be about 512 on the 10- bit ADC) in software but it depends on what you're doing.
All audio is AC so it has to be biased, or a protection circuit can be used to block the negative voltage. If all you want is amplitude or "loudness" you can block the negative half of the signal. On a 'scope you normally want to see the whole signal.
is that what the circuit in the original article does?
I don't know I didn't see and actual schematic and there is something about a 555 circuit, which isn't audio...
What should the output voltage range be for reading on one of the Arduino analog pins?
It depends on how much resolution you need. 1/2 of a volt will read about 100 on the ADC so that's enough for many applications. Audio signals vary a lot depending on the loudness of the recording (or the loudness of the sound) and if there is a volume control they vary even more. For that reason you don't want too much gain or you'll get clipping. If you want to get close to 5V (without "trying" to go over) you may need an amplifier with a gain control.
The signal is coming from this TRS stereo audio jack
Sadly that is just telling what plug is connecting where the signal is coming from. There can be many things in the other end, like a microphone, MP3 player or vinyl deck.
The Arduino only takes a sample every 0.1 mS or 10000 samples per second. You need about 20 samples for your display to look anything like a waveform. So that would give you a top display frequency of 10000 / 20 = 500 Hz.
You can change the prescaller value to give 56000 samples per second, which is a lot better but that is about as good as you can get. Then a lot of the quality of the display is up to the display software.
I wired everything up, and now I'm seeing that the readings with nothing plugged in are exactly the same as readings with laptop audio plugged in. It's even the same when nothing is plugged into A0 and A1 pins at all. So basically the circuit does nothing, just reads some kind of noise.
I attached the original circuit I copied, and my circuit (which is just original x2).
I will keep trying to debug it, appreciate if anyone has time to look.
Maybe it was designed for a slightly different situation than you have.
Why am I getting the same reading even if nothing is plugged into A0 and A1 on Arduino?
If you are pumping audio into it and there is no change from when you are not then the audio is not loud enough. So you need that op-amplifier to boost the signal to a higher level.
That is assuming you have 100K resistors and there is not a wiring error.
I don't know but I disagree with Mike... 1K won't hurt a headphone signal. (1K is low for a line-level signal but OK for a headphone output.)
I wired everything up, and now I'm seeing that the readings with nothing plugged in are exactly the same as readings with laptop audio plugged in. It's even the same when nothing is plugged into A0 and A1 pins at all. So basically the circuit does nothing, just reads some kind of noise.
With no signal you should be reading about 512 (assuming a standard Arduino with the 10-bit ADC) and I wouldn't expect more than 1 or 2 counts of noise, especially with the 1K resistor. (The lower impedance makes the input less sensitive to electrical noise pick-up when nothing is connected.)
With audio coming-in the numbers should "jump around randomly" above & below the ~512 bias. The louder the sound the farther it should deviate from the bias, but even with a constant tone you'll get get "random looking numbers" because you're sampling a constantly-changeing waveform that crosses-through zero twice per cycle (with "zero" reading about 512).
...If "random" doesn't make sense the [u]Audacity website[/u] has a nice little tutorial about how audio is sampled and you "connect the dots" to re-construct the analog waveform.
I wouldn't expect the Arduino to make a great oscilloscope because of the limited speed/sample rate. I didn't study the code but for the best results you'd have to take readings as fast as possible (at a known sample rate), and then pause to display the results.
And... Pure waveforms can "look nice" on an oscilloscope, but real audio pretty-much looks like garbage.
valn:
Why am I getting the same reading even if nothing is plugged into A0 and A1 on Arduino?
Just had a close look at that photograph of your circuit and it is not wired correctly.
The tracks on the middle two sections of solderless bread board run vertically, so you have five holes in a column all connected together. The next column across are not connected to this first on. It looks like you think it is the other way round, so your analogue inputs are not connected to anything.
By the way, never push two wires into the same hole.
I think it's almost done, just need to figure out how to detect and filter line noise when nothing is playing, and also detect waveform peaks, so that a single period of the waveform stays framed on the screen instead of scrolling at semi-random offsets.
I forgot that breadboard holes are connected horizontally, so I was plugging stuff into totally empty spots vertically
Min and Max handling are very important to getting something that looks like a waveform. I borrowed from your code DVDoug, to average min/max over time.
I also added DC bias and 2x over-sampling.
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1305.h>
const int OLED_CS = 10;
const int OLED_DC = 8;
const int OLED_RESET = 9;
const int AUDIO_LEFT = A0;
const int AUDIO_RIGHT = A1;
const int MIN = 0;
const int MAX = 1024;
const int BIAS = 512;
const int THRESHOLD = -48;
const int WIDTH = 128;
const int ALIAS = 2;
const int SAMPLES = WIDTH * ALIAS;
const int HEIGHT = 32;
const int HALF = HEIGHT / 2;
const int AVG_COUNT = 32;
const double AVG_MUL = 0.1;
Adafruit_SSD1305 display(WIDTH, HEIGHT, &SPI, OLED_DC, OLED_RESET, OLED_CS, 7000000UL);
short aud[SAMPLES] = {0};
int sample = 0;
int minCur = MAX;
short minAvg[AVG_COUNT] = {0};
int minSample = 0;
int maxCur = MIN;
short maxAvg[AVG_COUNT] = {0};
int maxSample = 0;
void setup() {
pinMode(AUDIO_LEFT, INPUT);
pinMode(AUDIO_RIGHT, INPUT);
initDisplay();
}
void loop() {
int value = analogRead(AUDIO_LEFT) - BIAS;
aud[sample++] = value;
averageRange(value, &minCur, minAvg, &minSample, true);
averageRange(value, &maxCur, maxAvg, &maxSample, false);
if (sample >= SAMPLES)
{
sample = 0;
tuneRange();
draw();
}
}
int averageRange(int value, int* current, short* samples, int* sample, bool minOrMax)
{
if ((minOrMax && value < *current) || (!minOrMax && value > *current))
*current = value;
samples[*sample] = *current;
*sample = *sample + 1;
if (*sample >= AVG_COUNT)
{
int sum = 0;
for (int n = 0; n < AVG_COUNT; ++n)
{
sum += samples[n];
}
*current = sum / AVG_COUNT;
*sample = 0;
}
}
void tuneRange()
{
int localMin = MAX;
int localMax = MIN;
for (int n = 0; n < SAMPLES; ++n)
{
if (aud[n] < localMin)
localMin = aud[n];
else if (aud[n] > localMax)
localMax = aud[n];
}
if (localMin > minCur)
{
minCur += ceil((localMin - minCur) * AVG_MUL);
}
if (localMax < maxCur)
{
maxCur -= ceil((maxCur - localMax) * AVG_MUL);
}
}
void draw()
{
display.clearDisplay();
int lastx = 0;
int lasty = mapSample(aud[0]);
int smoothy = lasty;
for (int x = 0; x < WIDTH; ++x)
{
int sum = 0;
for (int oversample = 0; oversample < ALIAS; ++oversample)
{
sum += aud[x];
}
int y = (mapSample(sum / ALIAS) + lasty + smoothy) / 3;
display.drawLine(lastx, lasty, x, y, WHITE);
smoothy = lasty;
lasty = y;
lastx = x;
}
display.display();
}
inline int mapSample(int value)
{
return max(map(value, minCur, maxCur, HEIGHT - 1, 0), 0);
}
void initDisplay()
{
Serial.begin(9600);
if (!display.begin(0x3C) ) {
Serial.println("Unable to initialize OLED");
while (1) yield();
}
display.display();
delay(1000);
display.clearDisplay();
}
detect waveform peaks, so that a single period of the waveform stays framed on the screen instead of scrolling at semi-random offsets.
On a real oscilloscope this is called triggering and can occur at any voltage you set not just the peaks. The simplest way to do that is to wait until the input signal is below some threshold say the DC bias at a reading of 512, then wait until it is above the threshold, say above 530, and then gather the samples in the memory for later display.
If you start filtering stuff like line noise that will also filter the signal you are trying to see.
Can you please read this:- How to use this forum
Because your post is breaking the rules about posting code.
I just got an oscilloscope to see what going on. With no sound playing it says Vmax is 2.43 volts, which assuming Arduino pins go 0 -> 5V translates to numeric value 502, or -10 after bias subtraction (bias is 512).
The osc has a trigger that you can drag on the screen. I dragged it around, but either that does nothing to calm the chaos of the waveform (if too low) or freezes it (if too high). Values around high mids make it look like a stair stepped slope, which doesn't look like anything audio related. For a simple repeating sine wave, it looks like saw teeth. So if a real oscilloscope can't trigger properly from audio, it doesn't seem like I could use the same method.
I'm guessing I need to look into pitch detection, there's probably a sample for that somewhere.
No. You need to incorporate a bit of hysteresis into the triggering.
An audio waveform is constantly changing amplitude, period and waveform. It will never sit still on a screen for very long. If you want to look at the waveform then implement a single shot mode.