-
Notifications
You must be signed in to change notification settings - Fork 25
/
ArduinoSoundLevelMeter.ino
235 lines (210 loc) · 7.34 KB
/
ArduinoSoundLevelMeter.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
#include <LiquidCrystal.h>
// FHT, http://wiki.openmusiclabs.com/wiki/ArduinoFHT
#define LOG_OUT 1 // use the log output function
#define LIN_OUT8 1 // use the linear byte output function
#define FHT_N 256 // set to 256 point fht
#include <FHT.h> // include the library
// pins
#define MicPin A0 // used with analogRead mode only
// consts
#define AmpMax (1024 / 2)
#define MicSamples (1024*2) // Three of these time-weightings have been internationally standardised, 'S' (1 s) originally called Slow, 'F' (125 ms) originally called Fast and 'I' (35 ms) originally called Impulse.
// modes
#define Use3.3 // use 3.3 voltage. the 5v voltage from usb is not regulated. this is much more stable.
#define ADCReClock // switch to higher clock, not needed if we are ok with freq between 0 and 4Khz.
#define ADCFlow // read data from adc with free-run (not interupt). much better data, dc low. hardcoded for A0.
#define FreqLog // use log scale for FHT frequencies
#ifdef FreqLog
#define FreqOutData fht_log_out
#define FreqGainFactorBits 0
#else
#define FreqOutData fht_lin_out8
#define FreqGainFactorBits 3
#endif
#define FreqSerialBinary
#define VolumeGainFactorBits 0
// macros
// http://yaab-arduino.blogspot.co.il/2015/02/fast-sampling-from-analog-input.html
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
// LCD
LiquidCrystal lcd(9, 8, 7, 6, 5, 4);
char lcdLineBuf[16 + 1];
void setup()
{
//pinMode(MicPin, INPUT); // relevant for digital pins. not relevant for analog. however, don't put into digital OUTPUT mode if going to read analog values.
#ifdef ADCFlow
// set the adc to free running mode
// register explanation: http://maxembedded.com/2011/06/the-adc-of-the-avr/
// 5 => div 32. sample rate 38.4
// 7 => switch to divider=128, default 9.6khz sampling
ADCSRA = 0xe0+7; // "ADC Enable", "ADC Start Conversion", "ADC Auto Trigger Enable" and divider.
ADMUX = 0x0; // use adc0 (hardcoded, doesn't use MicPin). Use ARef pin for analog reference (same as analogReference(EXTERNAL)).
#ifndef Use3.3
ADMUX |= 0x40; // Use Vcc for analog reference.
#endif
DIDR0 = 0x01; // turn off the digital input for adc0
#else
#ifdef Use3.3
analogReference(EXTERNAL); // 3.3V to AREF
#endif
#endif
#ifdef ADCReClock // change ADC freq divider. default is div 128 9.6khz (bits 111)
// http://yaab-arduino.blogspot.co.il/2015/02/fast-sampling-from-analog-input.html
// 1 0 0 = mode 4 = divider 16 = 76.8khz
//sbi(ADCSRA, ADPS2);
//cbi(ADCSRA, ADPS1);
//cbi(ADCSRA, ADPS0);
// 1 0 1 = mode 5 = divider 32 = 38.4Khz
sbi(ADCSRA, ADPS2);
cbi(ADCSRA, ADPS1);
sbi(ADCSRA, ADPS0);
#endif
// serial
Serial.begin(9600);
while (!Serial); // Wait untilSerial is ready - Leonardo
Serial.println("Starting mic demo");
// lcd
lcd.begin(16, 2); // set up the LCD's number of columns and rows:
lcd.clear(); //Clears the LCD screen and positions the cursor in the upper-left corner.
lcd.setCursor(0, 0); // set the cursor to column 6, line 0
lcd.print("Sound: ");
}
void loop()
{
// what do we want to do?
//MeasureAnalog();
MeasureVolume();
//MeasureFHT();
}
// measure basic properties of the input signal
// determine if analog or digital, determine range and average.
void MeasureAnalog()
{
long signalAvg = 0, signalMax = 0, signalMin = 1024, t0 = millis();
//cli(); // UDRE interrupt slows this way down on arduino1.0
for (int i = 0; i < MicSamples; i++)
{
#ifdef ADCFlow
while (!(ADCSRA & /*0x10*/_BV(ADIF))); // wait for adc to be ready (ADIF)
sbi(ADCSRA, ADIF); // restart adc
byte m = ADCL; // fetch adc data
byte j = ADCH;
int k = ((int)j << 8) | m; // form into an int
#else
int k = analogRead(MicPin);
#endif
signalMin = min(signalMin, k);
signalMax = max(signalMax, k);
signalAvg += k;
}
signalAvg /= MicSamples;
//sei();
// print
Serial.print("Time: " + String(millis() - t0));
Serial.print(" Min: " + String(signalMin));
Serial.print(" Max: " + String(signalMax));
Serial.print(" Avg: " + String(signalAvg));
Serial.print(" Span: " + String(signalMax - signalMin));
Serial.print(", " + String(signalMax - signalAvg));
Serial.print(", " + String(signalAvg - signalMin));
Serial.println("");
//lcd.clear(); //Clears the LCD screen and positions the cursor in the upper-left corner.
//lcd.setCursor(6, 0); // set the cursor to column 0, line 0
//sprintf(lcdLineBuf, "%3d%% %3ddB", (int)soundVolRMS, (int)dB);
//lcd.print(lcdLineBuf);
}
// calculate volume level of the signal and print to serial and LCD
void MeasureVolume()
{
long soundVolAvg = 0, soundVolMax = 0, soundVolRMS = 0, t0 = millis();
//cli(); // UDRE interrupt slows this way down on arduino1.0
for (int i = 0; i < MicSamples; i++)
{
#ifdef ADCFlow
while (!(ADCSRA & /*0x10*/_BV(ADIF))); // wait for adc to be ready (ADIF)
sbi(ADCSRA, ADIF); // restart adc
byte m = ADCL; // fetch adc data
byte j = ADCH;
int k = ((int)j << 8) | m; // form into an int
#else
int k = analogRead(MicPin);
#endif
int amp = abs(k - AmpMax);
amp <<= VolumeGainFactorBits;
soundVolMax = max(soundVolMax, amp);
soundVolAvg += amp;
soundVolRMS += ((long)amp*amp);
}
soundVolAvg /= MicSamples;
soundVolRMS /= MicSamples;
float soundVolRMSflt = sqrt(soundVolRMS);
//sei();
float dB = 20.0*log10(soundVolRMSflt/AmpMax);
// convert from 0 to 100
soundVolAvg = 100 * soundVolAvg / AmpMax;
soundVolMax = 100 * soundVolMax / AmpMax;
soundVolRMSflt = 100 * soundVolRMSflt / AmpMax;
soundVolRMS = 10 * soundVolRMSflt / 7; // RMS to estimate peak (RMS is 0.7 of the peak in sin)
// print
Serial.print("Time: " + String(millis() - t0));
Serial.print(" Amp: Max: " + String(soundVolMax));
Serial.print("% Avg: " + String(soundVolAvg));
Serial.print("% RMS: " + String(soundVolRMS));
Serial.println("% dB: " + String(dB,3));
//lcd.clear(); //Clears the LCD screen and positions the cursor in the upper-left corner.
lcd.setCursor(6, 0); // set the cursor to column 6, line 0
sprintf(lcdLineBuf, "%3d%% %3ddB", (int)soundVolRMS, (int)dB);
lcd.print(lcdLineBuf);
}
// calculate frequencies in the signal and print to serial
void MeasureFHT()
{
long t0 = micros();
#ifdef ADCFlow
//cli(); // UDRE interrupt slows this way down on arduino1.0
#endif
for (int i = 0; i < FHT_N; i++) { // save 256 samples
#ifdef ADCFlow
while (!(ADCSRA & /*0x10*/_BV(ADIF))); // wait for adc to be ready (ADIF)
sbi(ADCSRA, ADIF); // restart adc
byte m = ADCL; // fetch adc data
byte j = ADCH;
int k = ((int)j << 8) | m; // form into an int
#else
int k = analogRead(MicPin);
#endif
k -= 0x0200; // form into a signed int
k <<= 6; // form into a 16b signed int
k <<= FreqGainFactorBits;
fht_input[i] = k; // put real data into bins
}
#ifdef ADCFlow
//sei();
#endif
long dt = micros() - t0;
fht_window(); // window the data for better frequency response
fht_reorder(); // reorder the data before doing the fht
fht_run(); // process the data in the fht
#ifdef FreqLog
fht_mag_log();
#else
fht_mag_lin8(); // take the output of the fht
#endif
#ifdef FreqSerialBinary
// print as binary
Serial.write(255); // send a start byte
Serial.write(FreqOutData, FHT_N / 2); // send out the data
#else
// print as text
for (int i = 0; i < FHT_N / 2; i++)
{
Serial.print(FreqOutData[i]);
Serial.print(',');
}
long sample_rate = FHT_N * 1000000l / dt;
Serial.print(dt);
Serial.print(',');
Serial.println(sample_rate);
#endif
}