用户工具

站点工具


knowledge:electronic:2019032101

这是本文档旧的修订版!


Arduino声级计和频谱分析仪

最近我一直在使用 Arduino 电子平台做一些项目。其中一个项目涉及对某些电机进行基准测试,并要求我测量噪音水平。我将在以后的一篇文章中更详细地介绍该项目,但现在我想写一下使用 Arduino 测量声级和分析频率的过程和最佳实践。我将讨论声音、麦克风、采样、FFT 等。本文将针那些既不是信号处理专家也不是电子专家的初学者,并且这篇文章将是相当高水平的,并为全面阅读提供了链接。

声音理论

声音是一种在空间中移动的波,当它被存储(以数字或模拟形式)时,它由波形表示,波形是在空间中某个点的每个时间点测量的波的幅度。您可以将其视为通过麦克风不断测量声音,并且测量结果形成波形。因为我们每个时间单位只能测量有限次,所以这个测量过程称为采样,它会生成离散信号。此外,由于计算机和集成电路的精度和存储空间有限,因此在此过程中每个时间样本也是离散的。

Sampling of a waveform

Arduino 测量信号并将它们转换成为微处理器(MCU)能够处理的逻辑的能力是由MCU上的模数转换器(ADC)提供的。因此,对于 Arduino 实现,此过程相当于将测量设备(声音麦克风​​)连接到 MCU,并由 ADC 以恒定速率对设备上的值进行采样。Arduino ADC 感测电压电平(通常在 0-5V 范围内)并将其转换为 0 到 1024(10 位)范围内的值。

根据我们测量的内容,声级可能非常安静或非常响亮。一方面,麦克风本身通常无法为 Arduino 提供足够的电压来感知变化。ADC 需要 5V/1024=4.8mV 的变化才能将数字值增加 1,但典型的“驻极体麦克风”可能无法为安静的声音提供如此大的电压变化。因此,麦克风通常与放大器一起使用。另一方面,非常大的噪声和高增益放大器可以将信号带到最大值5V并会有“曝光过度”或“削波”的情况,并再次使我们陷入采样无用的情况。因此,将设备和放大级别(增益)与每个用例场景相匹配非常重要。

麦克风的选择

为 Arduino 选择麦克风时,您可以获得可用的“麦克风模块”之一,该模块将麦克风与放大器或小型 PCB 上的其他逻辑结合在一起。您还可以制作自己的模块,你自己的模块可能具有能够控制麦克风和扩音器的所有不同方面的额外优势。我选择使用现成的模块,因为它比自己制作更容易、更快。

如果您的目标是录制声音并获得固定的声级,即使在不可预测的情况下,您也可以基于例如 Adafruit 的 MAX9814 来获得一款具有自动增益的模块。这样的模块会将声音“标准化”到设定的水平。对于您想要录制语音以供播放或运行频率分析的时候,这确实是正确的解决方案。不过,这不是测量音量的正确选择。为了测量音量并能够比较不同的测量结果,您需要使用可预测增益的模块。这意味着增益是不固定的,增益可以由您配置并且不会自动更改。

我评估了3个这样的模块。值得注意的是,特定的设计可能会以不同的名称出现在市场上,因为不同的制造商会使用自己的型号制造自己的设计版本。查看电路板布局并记下主芯片,以便您可以识别设备。

基于MAX4466的模块

我从Far East买了这个模块,但看起来它是基于 Adafuit 设计的。该模块具有可调节增益,您可以使用微型单匝电势计进行控制。有一个 Vcc 引脚、一个接地引脚和一个模拟输出引脚。模拟引脚发出波形,其中“0”为 Vcc/2,幅度取决于增益和声音音量。MAX4466芯片是一款专门针对麦克风放大器而优化的运算放大器,这使得该模块在这个项目里展现了出色的性能,也是我该项目的最终选择

基于 LM393 的“HXJ-17”/“Keyes”模块

我从当地的一家电子商店购买了这个模块。不知道是谁设计的,但它有一个多圈电位器,没有放大器和一个LM393比较器芯片。有一个 Vcc 引脚、一个接地引脚、一个模拟输出引脚和一个数字输出引脚。由于该模块没有放大器,因此它仅适用于感测响亮的声音,例如拍手声和敲击声。LM393 的存在允许您配置阈值,以便当声级高于阈值时,板可以生成数字输出。我能想到的与在代码中实现阈值相比的唯一优点是:1) 比较器比 MCU 的 ADC 更灵敏, 2) 或者您一开始就没有 MCU,并且将此板直接连接到继电器或类似的 IC。一些卖家宣传该模块带有 LM393 放大器,

基于LM393的“声音检测”模块

我从同一家当地商店买了这个。这一款与HXJ-17类似,但更简单。它有一个一圈电位器,没有模拟输出。让它有助于了解是否有响亮的声音。

分析模拟输入

作为第一步,我建议您花一些时间分析模块的模拟输出,以查看基线和幅度。我使用以下 Arduino 函数来收集数据

#define MicSamples (1024*2)
#define MicPin A0
 
// 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();
    for (int i = 0; i < MicSamples; i++)
    {
        int k = analogRead(MicPin);
        signalMin = min(signalMin, k);
        signalMax = max(signalMax, k);
        signalAvg += k;
    }
    signalAvg /= MicSamples;
 
    // 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("");
}

然后,您可以以不同的音量发出一些声音,并查看平均值、最小值、最大值和跨度值的响应情况。查看结果,您可能会发现需要调整增益电位器,以便利用声级的最大跨度,同时不要过度使用,以免削波信号。

使用 3.3V 参考电压和自由运行实现精确采样

Arduino 的 AnalogRead 功能可以轻松获取模拟引脚的数字值。它是在考虑单个样本收集的情况下实施的。在对声音进行采样时,以恒定的速率采样并准确地采样每个样本非常重要。为了实现这两个属性,我们进行以下操作。

首先,我们将配置 ADC 以使用 3.3V 作为模拟参考电压。原因是 3.3V 通常比 5V 更稳定。5V 电压可能会上下波动,尤其是当 Arduino 从 USB 连接获取电源时。3.3V 来自 Arduino 板上的线性稳压器,可以连接到 Arduino 的 ARef 引脚。这会校准我们的 ADC,将 0 至 3.3V 范围的模拟输入映射到 0 至 1024 范围的数字值。为了在电子层面实现这一点,您需要为模块提供 3.3V 电压并将 Arduino ARef 引脚连接到 3.3V。确保您的模块能够在此电压下运行。 使用以下代码来配置此模式:

analogReference(EXTERNAL); // 3.3V to AREF

Second, we will configure the ADC to work in “free-running” mode and read the sample values directly from internal registers, bypassing analogRead. As mentioned, analogRead is designed to read one value at a time and will perform initialization of the ADC for each read, something that we better eliminate. This will allow us to get a sampling rate that is more predictable.

Setup “free-running” mode with this code: 其次,我们将配置 ADC 以“自由运行”模式工作,并直接从内部寄存器读取样本值,绕过analogRead。如前所述,analogRead 被设计为一次读取一个值,并且将为每次读取执行 ADC 的初始化,我们最好消除这种情况。这将使我们能够获得更可预测的采样率。

使用以下代码设置“自由运行”模式:

// register explanation: http://maxembedded.com/2011/06/the-adc-of-the-avr/
// 7 =&gt; 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

使用以下代码读取一批示例:

for (int i = 0; i < MicSamples; i++)
{
    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
    // work with k
}

Third, you can also adjust the speed of the ADC. By default the ADC is running at 1:128 of the MCU speed (mode #7). Each sample takes the ADC about 13 clock cycles to get processed. So by default we get 16Mhz/128/13=9846Hz sampling. If we want to sample at double the rate we can change the divider to be 64 instead.

Here is an example of how to set divider to 32 (mode #5) which equals a sampling rate of 16Mhz/32/13~=38Khz:

// macros
// http://yaab-arduino.blogspot.co.il/2015/02/fast-sampling-from-analog-input.html
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &amp;= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
 
// 1 0 1 = mode 5 = divider 32 = 38.4Khz
sbi(ADCSRA, ADPS2);
cbi(ADCSRA, ADPS1);
sbi(ADCSRA, ADPS0);

You can see all three of these code snippets implemented together in the source code of the project at github.

With this logic in place we can get decent waveform data for the Arduino to process.

Sound level measurement

Theory

The sound level is defined as the amplitude of the waveform and can be measured per set of samples which represent a part of a signal.

Peak Envelopes

For an ideal sine signal, the amplitude would be the max sample, but in practice it is easy for some samples to be outliers and to affect the max value significantly. For this reason it is more practical to use a metric that takes all the sample values into account. You can use an average, but it is more common to use Root Mean Square (RMS) which will give more “weight” to higher values.

The relationship between amplitude and RMS for sine waves is known and is amplitude= sqrt(2)*RMS. If we assume that a sound waveform is similar to a sine waveform we can use this relationship to estimate a stable amplitude based on an RMS value that we calculate.

Waveform with an RMS envelope

The values we are dealing with are relative and not absolute. After all we are using some value of gain to tune the volume levels to our digital 10bit range. It is very common to work with relative values when processing sound waveforms. The volume is then measured as a ratio from some other “reference point” value. A common unit used to express the ratio is decibel (dB). Resulting a formula:

dB=10*log10(v/v0)

Where dB is the level is dB units, v is the sample value and v0 is the reference value.

Since sound pressure is a field quantity a ratio of squares is used and the value “2” in the log becomes “20” [due to log(a^b)=b*log(a)] :

dB=20*log10(v/v0)

I was trying to achieve relative measurements, I chose v0 as the max possible amplitude (1024/2 for a 10bit ADC). This yields dB measurements which are specific to a certain gain and my device, but as long as the gain remains fixed, I can take several measurements and make valid comparisons between them. If you are looking to measure absolute sound levels you would need to calculate your level relative to a standard agreed sound pressure baseline value of 20 micropascals, which is the typical threshold of perception of an average human. Practically, this is typically done by attaching a special calibration device to the microphone. The device generates sound at a fixed dB level and you can adjust your calculations such that your dB measurement matches the dB value of the calibration device.

When using a reference value that is higher than your samples (max amplitude), your dB values would be negative and smaller as you approach the max.

When using a reference value that is lower than your samples (threshold of perception), your dB values would be positive and larger as you approach the max.

To make this even more complex, several factors affect measurement in practice. First, the human ear is not equally sensitive to all frequencies. It is typical to apply different weights to different frequency ranges. One such a unit of measurement is called dBA, but there are others with slightly different weights. Second, your microphone might not have equal sensitivity to all frequencies. Third, your speakers might not have equal ability to reproduce all frequencies at the same exact level. These complexities require very accurate and expensive equipment together with special calibration procedures to be able to measure sound levels correctly per standards. You need to understand that your ability to measure sound level with the setup described here is pretty rudimentary and suitable for rough relative measurements only.

Implementation

Let’s recap that our values are 0 to 1024 which stand for [-max,max] with 1024/2=512 being “0”. We will retrieve and process sample for some time, where the standard defines 1 second as “Slow” and 125ms as “Fast”. For each sample, we will measure the distance from the “0” to the sample value, which is the amplitude of that sample. Then we can do simple calculations for max, average and RMS. The values on our scale can be “normalized” to percentage of max amplitude or using dB or both. Here is a relevant code sample:

// 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 ADCFlow // read data from adc with free-run (not interupt). much better data, dc low. hardcoded for A0.
 
// calculate volume level of the signal and print to serial and LCD
void MeasureVolume()
{
    long soundVolAvg = 0, soundVolMax = 0, soundVolRMS = 0, t0 = millis();
    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);
    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));
}

So now with proper module and calibration you can measure sound level of different events or devices and compare them one to the other.

Frequency analysis with FHT

What if you want to “break” the sound into individual frequencies and measure or visualize each individual frequency? Can this be done with Arduino? The answer is that it can be done relatively easily thanks to some existing libraries. To turn signals from a time domain to a frequency domain you would generally use a Fourier transform. Such transforms are used for signals of different types, sound, images, radio transmissions, etc. Each signal type has its own properties and the transform that best suits a sound signal is the Discrete Hartley Transform (DHT). DHT will work with discrete, real values which form our waveform. To implement DHT we will use Fast Hartley Transform (FHT) and specifically the ArduinoFHT library.

The Arduino FHT library works with vectors of 16 to 256 samples. This size is denoted as N. In this project I will be using N=256 to achieve maximum resolution, but you may use smaller values if you are short on memory or processing power.

First, the algorithm takes N real numbers and results in N/2 complex numbers. Then we can pass the data to another function to calculate the magnitude of the complex numbers to get N/2 bins. In the end we get N/2 bins, each covering a frequency range of sampling_rate/N Hz. The highest value of the last bin will be sampling_rate/2 . The reasons for this relate to signal processing theory, specifically aliasing and Nyquist law. In practice, if you want to avoid any strange effects, such as higher frequencies “folding” over lower frequencies, you will need to make sure to use a sampling rate that is twice the highest frequency you expect to have in the sound signal. Otherwise you are not sampling fast enough. You should also not over sample, as it will result in low ADC accuracy and wasting of FHT bins on ranges that don’t appear in the signal. I found the value of 20Khz to be a good upper frequency based on the range of my microphone and on the range of typical human hearing. As a result the, sampling at 38.4Khz (divider=32) seemed optimal.

So for N=256 and sampling_rate=38.4Khz we get 128 150hz bins with the first been holding the magnitude value of 0-150hz and the last bin holding the magnitude value of 19050-19200hz. We can now focus on specific bins that interest us, send the values of all the bins over serial connection, store the values, display them in some way, etc.

One of the fun ways to use the data, especially when troubleshooting and developing is to visualize with an analyser. Load the following FHT example code to the Arduino or adapt it to your needs. It gets the samples, runs FHT on the data and sends it in binary form over serial. Your Arduino should be connected to a computer running Processing development environment. In Processing, load the “FHT 128 channel analyser” project. I had to make a change to the project to make it compatible with Processing 3.0 . To do so, move the call to “size” function from within the “setup” function to a new function called “settings”.

Analyzer

Another way to analyze the data is for the Arduino to send it over serial in textual form, let it run for some time, then copy it from the serial monitor and paste it in a spreadsheet. For example using a code that is similar to this:

void MeasureFHT()
{
    long t0 = micros();
    for (int i = 0; i < FHT_N; i++) { // save 256 samples
        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
        k -= 0x0200; // form into a signed int
        k <<= 6; // form into a 16b signed int
        fht_input[i] = k; // put real data into bins
    }
    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
    fht_mag_log();
 
    // 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);
}

Then you can format the data in a spreadsheet, such as Excel, as a “3-D Surface” mesh graph. For example, see a graph of a Frequency Sweep from 1hz to 5000hz as captured and analyzed by the Arduino and FHT:

Mesh of FHT frequency sweep

Summary

My code for this project can be found at github for you to experiment with.

The Arduino can be used for relative sound level measurement and for frequency analysis/visualization. One just needs a microphone to match the use case, an Arduino, some coding and optionally the FHT library. Have fun and let me know in the comments if you make something nice using such a setup.

https://blog.yavilevich.com/2016/08/arduino-sound-level-meter-and-spectrum-analyzer/

knowledge/electronic/2019032101.1688634162.txt · 最后更改: 2023/07/06 09:02 由 ob