Can an Arduino sample audio in microseconds for 1-4 kHz? - arduino

I've just hooked up a electret microphone to an Arduino, and I'd like to sample between the ranges of 1 kHz and 4 kHz.
I understand this is limited to the machine code and the ADC, so I'm trying to keep the sketch simple.
Is it possible to sample between these frequencies with the sketch below?
const int analogPin = 0;
int ledPin = 13;
void setup() {
pinMode(ledPin, OUTPUT);
}
void loop() {
int mn = 1024;
int mx = 0;
for (int i = 0; i < 5; ++i) {
int val = analogRead(analogPin);
mn = min(mn, val);
mx = max(mx, val);
}
if (mx-mn >= 50) {
digitalWrite(ledPin, HIGH);
}
else {
digitalWrite(ledPin, LOW);
}
}

Arduino is a prototyping platform consisting of a number of hardware boards plus a software abstraction layer. For a question like this, it is useful to consider the capabilities of the underlying hardware, as these provide the ultimate limits. I'll assume you are using Arduino Uno/Nano, the story is different for Due.
According to the datasheet, each ADC reading (beyond the first one) takes 13 ADC clock cycles. ADC clock (different from the MCU) clock is derived by dividing the system clock by some factor, at least 2. So at 16Mhz board this amounts to 0.6 million samples per second. So far so good. However, that's not the end of the story, you still need to read the data. If you use interrupts, even if you do something very simple, experience suggests that you will lose about 100 clock to interrupt processing. Now you are down to 126K samples/second. But this is a theoretical maximum.
The datasheet states that for maximum accuracy for the ADC requires 50kHz - 200kHz ADC clock. In the Arduino code (in wiring.c), a division factor of 128 is chosen:
sbi(ADCSRA, ADPS2);
sbi(ADCSRA, ADPS1);
sbi(ADCSRA, ADPS0);
This means that each conversion takes 128*13 = 1764 clocks, which yields a theoretical maximum of 10K samples per second. It is a little bit worse than that given that the readAnalog() function does something beyond just starting the ADC conversion and waiting for it to finish, but it shouldn't be too much worse. This doesn't involve your code of course: any processing you do on the results of readAnalog() will make it more difficult to capture more samples. But yes, to capture at 4Khz you will need to make sure that you code spends less than 1.5k clock cycles/sample, which should be doable. Note that if you are doing five readings like you are doing in the code you posted, the maximum capture rate will be 2kHz if your code does very little.
As far as how to capture the data, you need to make deal with the fact that microphones without amplification will not give you 0-5V readings that you might expect if you are using analogRead(). In fact, microphone output voltages swing from positive to negative, however, the negative voltages will not be picked up by the ADC, and show up as just zeros, unless you give your microphone a voltage offset.
I am not exactly sure what your code that compares minimum amplitude to maximum amplitude is supposed to be doing. Are you wanting to digitize the audio? In this case you need to save all the amplitude readings collected from analogRead(), and then you can run FFTs on them on another computer: Arduino is most likely not going to be fast enough to do frequency analysis on the data.

I have heard, or rather remember reading, that the ADC could handle up to 10k-samples per second, so it should be OK up to 5 kHz. However, I have not tried this nor do I have a link to back it up at the moment.
Just try and see.
Now I know some of the Arduino library functions are slow, notably the DigitalRead/Write that has overhead of hundreds of cycles. Most of this is the sanity checking that allows people to just perform DigitalRead/Write without thinking much about setting everything up.
However, to squeeze out the maximum performance you could look into writing your own AnalogRead that is optimized for your use case.
At least some links on the subject:
Pin I/O performance (JeeLabs)
C++ Template method for faster access (JeeLabs)

I happen to have just tried this using an Arduino Uno and similar code to yours and I've been able to sample an average of 8000 times a second. That is the Nyquist frequency for 4 kHz so you're good but not much margin for error.

Related

Why is the data coming out weird in arduino code?

I want to get data from the sensor(I used velostat). And this is my code.
Arduino Code
#define numRows 1
#define numCols 1
int rows_out = 13;
int cols_in = 12;
int Values = 0;
void setup() {
// set all rows and columns to INPUT (high impedance):
pinMode(rows_out, INPUT_PULLUP);
pinMode(cols_in, INPUT);
Serial.begin(9600);
}
void loop() {
pinMode(cols_in, OUTPUT); // set as OUTPUT
digitalWrite(cols_in, LOW); // set LOW
Values = analogRead(rows_out); // read INPUT
pinMode(cols_in, INPUT); // set back to INPUT!
// Print the incoming values of the grid:
Serial.println(Values);
delay(1000);
}
I made a sensor with velostat. It has two cable.
I connected one of them with an analog pin of arduino board. And the other one with GND and other analog pin. like this.
velostat cable 1 - analog pin 12
velostat cable 2 - analog pin 13 - GND
But when I upload this program, weird data is coming out on serial monitor.
Data displayed on serial monitor
4095
4095
4075
3283
3082
3056
2973
2941
2865
2685
2308
1859
1365
If anyone knows the reason, please help me. I guess it is because connection of cable has problem or I connected wrong pin, but i'm not sure.
The data could be fine, you most possibly skipped the calibration step.
A velostat measures pressure, but depending on the materials used to make the sensor, its PressureIn-VoltageOut characteristic may differ greatly. Furthermore, differences exist even in sensors with the same build, thus the need for calibration is vital in applications that require precision.
I would suggest you assume a linear characteristic for simplicity's sake, and do the following 3-step calibration:
Average out 10 values for when there's nothing pressing on the sensor. That'd be your zero reference. We'll reference it as X1.
Press with the average expected pressure on the velostat and average 10 measurements again. That'd be your 'ON' reference, and we'll reference it as X2. This ON value needs an arbitrary numeric equivalent. I chose 15.
In math, it'd look like:
aX1 + b = 0
aX2 + b = 15
Once you solve for a and b, your true values would be represented by: a*[measuerement]+b.
P.S.: Sorry for the horrendous math formatting, still not familiar how to integrate math nicely in markdown.
Later edit: Higher precision can be achieved by averaging across more measurements AND picking a larger arbitrary value for the second equation (to have more granularity).

Arduino: find the peak and troughs in sensor data

I'm trying to poll an accelerometer every x ms on 3 axiz, and trying to figure out how to determine the peaks and troughs of the readings I get.
Ideally, I wouldn't want to collect a whole bunch of data before I can start counting the peaks - maybe every 10 minutes at most if data collection is first required. The peaks should also only be counted if the absolute value of the peak is within an acceptable "distance" of the average set of peaks - to prevent a very small peak from being counted...
I would appreciate any pointers wrt doing this?
You can start with calculation of standard deviation and count peaks which deviates more than a specified level.
Wiki article
It depends on what your signal looks like. I solved a similar problem with a sinusoidal signal using an used a IIR filter on the signal to smooth out the noise and prevent false peaks.
You might try something like this:
int signalPin = 3; // accel connected to pin 3
float signal;
float gain = 0.1;
void setup()
{
pinMode(signalPin, INPUT);
signal = analogRead(signalPin); // get initial reading
}
void loop()
{
// allow a new reading of the accel to slightly change the signal (depending on the value of gain)
signal += (analogRead(signalPin)*gain - signal); // IIR filter
}
You will probably also want to use the map function to adjust the value obtained from analogRead, as well as use int or long instead of float if you're worried about efficiency.
Now you can use a strategy like the following:
Read a new value from the accel (checking if a new value is higher than the previous and if so, save it as the new max. When they stop increasing, that is your max value. You can require N number of samples within a certain threshold to verify you are at the flat top of the peak. Hopefully, this gets you started.

How to deal with multiple timings in arduino and millis?

I have a series of relays I'm controlling with an arduino connected to solenoid valves which in turn control the flow of water around a system of pipes. The relays are to be switched at regular intervals along the lines of this:
Relay 1 and 3 high
Wait 13s
Relay 2 and 4 high
Wait 17s
Relay 1 and 3 low
Wait 13s
Relay 2 and 4 low
Wait 300s
Repeat
I started with a simple series of delays commands like this:
#include <DmxMaster.h>
void setup() {
DmxMaster.maxChannel(8);
}
void loop() {
delay(300000);
DmxMaster.write(1,HIGH);
DmxMaster.write(7,HIGH);
delay(13000);
DmxMaster.write(2,HIGH);
DmxMaster.write(8,HIGH);
delay(17000);
DmxMaster.write(1,LOW);
DmxMaster.write(7,LOW);
delay(13000);
DmxMaster.write(2,LOW);
DmxMaster.write(8,LOW);
}
Most of the time this works, but I will see instances where it'll skip one or more delay lines and jump to the next step. I started to look at the millis function for the longest delay (300s) and came up with the following, but I'm wondering how to implement this for the shorter delays and if this would be of any improvement:
#include <DmxMaster.h>
unsigned long currentTime;
unsigned long loopTime;
void setup()
{
DmxMaster.maxChannel(8);
currentTime = millis();
loopTime = currentTime;
}
void loop()
{
currentTime=millis();
if(currentTime >= (loopTime + 300000)){
DmxMaster.write(1,HIGH);
DmxMaster.write(7,HIGH);
delay(13000);
DmxMaster.write(2,HIGH);
DmxMaster.write(8,HIGH);
delay(17000);
DmxMaster.write(1,LOW);
DmxMaster.write(7,LOW);
delay(13000);
DmxMaster.write(2,LOW);
DmxMaster.write(8,LOW);
loopTime = currentTime;
}
}
Thanks in advance,
Cameron
In my own project, I have the same kind of constraints: I need to check sensors, get the data, analyze them and choose what to do at different intervals.
The best solution I've found is ChibiOS/RT. It's a Real Time Operating System (RTOS) developed by Giovanni Di Sirio from STMicroelectronics.
It has been ported on Arduino by Bill Greiman and is available on Github : ChibiOS-Arduino.
It is very easy to use, very well documented and has a low memory and size footprint.
I'm using it in a robotic project, Moti, you can take a look if you want but Bill has a lot of great examples.
It might look like overkill at first, but once you get used to it, you'll wonder why you did not use it earlier and every Arduino project you'll be working on will have a new dimension.
Hope this helps :)

Simultanously Reading Two Analog Inputs with Arduino

We are simulating an oven. The potentiometer sets the desired temp and the sensor reads the current temperature of a little copper plate that is "the oven."
Both the temp sensor are connected to their own analog input pin on my arduino uno. Individually, I have gotten values for both the potentiometer and the temp sensor that make sense (I am monitoring the values on the serial window). However, when I adjust the potentiometer it significantly alters the sensor reading.
For example:
The potentiometer is at its 0 position, and the sensor is in the room temperature air. The serial shows TempSensor = 22 C, TSet = 0 C. This is normal.
Then when I turn the pot up: TempSensor= 40 C, TSet=55 C. -But the temperature sensor is still in the room temp air! So the pot value, TSet, goes up like it should, but also affects the sensor reading even though the temperature hasn't really changed.
Any advice would be greatly appreciated. Thanks!
void setup() {
Serial.begin(9600);
}
void loop() {
int sensorValue = analogRead(A3);
float tsens = map(sensorValue, 0, 1023, 0, 500);
int sensorValue2 = analogRead(A1);
float tset = map(sensorValue2, 0, 1023, 0, 70);
Serial.println(tsens);
Serial.println(tset);
}
I've recently bumped into a similar problem and my searches suggest that inserting a delay between the reads can help. On this question, I found this answer and this answer particularly helpful.
The idea is that you need to let some time pass after making a reading, then make another reading after the ADC has stabilized. Here's a function I have been using:
int safeAnalogRead(int pin)
{
int x = analogRead(pin); // make an initial reading to set up the ADC
delay(10); // let the ADC stabilize
x = analogRead(pin); // toss the first reading and take one we will keep
delay(10); // delay again to be friendly to future readings
return x;
}
I'm still having trouble getting an accurate reading of several potentiometers connected to the analog pins configured as a voltage dividers between vcc and ground, but at least now the values are stable.
BTW, it could be argued that since you have a delay after the first reading, it isn't necessary to have the second delay. This might matter if you call safeAnalogRead() twice in quick succession on two different pins.
You most likely have a ungrounded or miswired grounding on your temperature sensor. The pin configurations on the analog pins in Arduinos are layed out very close to each other such that floating voltages will move up or down when nearby pins have an applied voltage. If your ground connection (or power, though if it's correct at the beginning it's probably ground) for the sensor is disconnected or fixed to a high impedance line the analog voltages will move all over the place as other normally minuscule voltage sources will dominate the signal pathing. It probably fluctuates heavily if you put your finger near the A3 pin as well.
Is it noise or a bad value? I did a little test routine that looks at a pin and checks it against previous max and min values. I printed it to the serial monitor whenever a new boundary value showed up. If the wrong value is stable, check the circuit. If its noisey around a valid value, a digital low pass filter works pretty well. Take 34 readings of the pot. Find the highest and lowest values and discard those. Then take the average of the remaining 32 readings. I saw a 90% improvement in my setup (40 count error down to 3). 36 readings with 2 high and 2 low discarded would probably improve things further. If you have the time, you can do a double pass filter. Do this same process 34 times, then throw away the high and low and average it again. All together this is 34 x 34 readings, so noise should go away, but you are taking a long time to get a sample and a pot change will take awhile to get detected. To help with the time, I read the pot every pass through the main loop and save each value in a circular buffer. When I need to read a pot, I look at the historical 33 readings along with a 34th new one.
I have run into this issue before when reading multiple analog sensors in rapid succession. One possible cause(and the one I experienced) is the fact that the arduino only has 1 ADC and it charges a capacitor to take that reading. That capacitor can remain charged between readings thus skewing them.
Introducing a delay could potentially help with this as another user has pointed out, however the cleanest solution I was able to come up with was to "Reset" and discharge the ADC capacitor by taking an analog read of a third pin that is connected directly to ground.
int sensorValue;
int sensorValue2;
float tsens;
float tset;
int resetADC;
void setup()
{
Serial.begin(9600);
pinMode(A0,input);
pinMode(A1,input);
pinMode(A3,input);
}
void loop()
{
resetADC = analogRead(A0);
sensorValue = analogRead(A3);
tsens = map(sensorValue, 0, 1023, 0, 500);
resetADC = analogRead(A0);
sensorValue2 = analogRead(A1);
tset = map(sensorValue2, 0, 1023, 0, 70);
Serial.println(tsens);
Serial.println(tset);
}

I need help reading analog voltages in an arduino mega2560

I'm new to microcontrollers and I need help reading voltages and printing them in the serial monitor. I have done this using a potentiometer, but I was wondering if I could do the same with just voltages. Below is the code that I used when I read the potentiometer values:
I have tried reading voltages but when I input a voltage smaller than between 3-5V in pin A0 I get 0s in the serial monitor
int potPin = 0; // select the input pin
int val = 0; // variable to store the value coming from the function generator
void setup()
{
Serial.begin(9600); // opens serial port, sets data rate to 9600 bps
}
void loop() {
val = analogRead(potPin); // read the value from the function generator
Serial.println(val);
delay(1000); //Sampling rate for the signal
}
as Udo Klein said, this looks like a hardware issue. try setting up your circuit like this: http://imgur.com/PYbeqLq
note that the grounds are shared between the function generator and the arduino - this is important for ensuring that voltages are measured correctly. remember that voltage is actually a difference in electric potential, so you need a common reference point to measure from.
To answer your question, yes you can read voltages from another piece of hardware.
As gearbot tells you it is important to keep a common ground between the arduino and the other piece of hardware.
Also be carefull, the arduino can only measure voltages between 0 and 5 Volts. Don't try to measure higher voltages without adjusting the circuitry as you might destroy your arduino board.
If you're unsure measure the voltages before attaching to the arduino with a multimeter.
The Uno has 6 analog inputs, labeled A0 through A5, each of which provide 10 bits of resolution (i.e. 1024 different values). By default they measure from ground to 5 volts, though is it possible to change the upper end of their range using the AREF pin and the analogReference() function.
edit:
I didn't see the added comment at the bottom of your post. This indicates indeed a hardware problem (probably in your circuitry)
To be sure the arduino does the right thing, measure the voltage with your multimeter between pin A0 and GND while your hardware is attached and working. You should be able to read the correct voltage there.
I suspect it will give the same values as the Arduino.
Then trace back your circuitry with your multimeter (keeping GND attached) to find where something is going wrong.
Try scaling your 0 to 5 or 1-5v input
using map function.
int setValue = map(ana1Value, 0, 1023, 0, 100);
could be a help for you.
the analogue input voltage is the scaled 0-100 then you can work with that

Resources