Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
|
The ability to sense the amount of sound in an environment can come in handy. From the hobbyist standpoint, you can create light elements with LED strips that light up based on the amount of sound, similar to an equalizer, to add to the ambiance of the playing music. From a commercial perspective, you can create alerts once sound levels exceed a certain threshold at a workplace (or classroom). You can also create helpful visual indicators for sound that can help those that are hard of hearing, such as a lighting-based baby monitor that will light up when a baby cries. This article will show you how to use an electret microphone amplifier and the Raspberry Pi running Windows IoT Core to visualize the amount of sound in your environment.
Here is a video of the application in action:
Project Wiring Diagram
•Cobbler GND pin to Breadboard Ground Rail
•MCP3008 VDD to Breadboard Power Rail
•MCP3008 VREF to Breadboard Power Rail
•MCP3008 AGND to Breadboard Ground Rail
•MCP3008 DGND to Breadboard Ground Rail
•MCP3008 CLK pin to Cobbler SCLK pin
•MCP3008 DOUT pin to Cobbler MISO pin
•MCP3008 DIN pin to Cobbler MOSI pin
•MCP3008 CS/SHDN pin to Cobbler CE0 pin
•Electret Mic GND pin to Breadboard Ground Rail
•Electret Mic VDD pin to Breadboard Power Rail
•Electret Mic GAIN pin to Breadboard Power Rail (not pictured)
•Electret OUT pin to MCP3008 CH0 pin
MCP3008.cs Code Listing
C#using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Windows.Devices.Enumeration;
using Windows.Devices.Spi;
namespace PiSoundSensing
{
/// <summary>
/// Helper class that assists in reading from MCP3008 channels
/// using SPI0, Mode0 and Standard Configuration
/// assuming 3v3
/// </summary>
public class MCP3008
{
private SpiDevice _mcp3008 = null;
/// <summary>
/// Connects to the MCP3008 on SPI0
/// </summary>
/// <returns>True if successful, False otherwise</returns>
public async Task<bool> Connect()
{
var spiSettings = new SpiConnectionSettings(0);//for spi bus index 0
spiSettings.ClockFrequency = 3600000; //3.6 MHz
spiSettings.Mode = SpiMode.Mode0;
string spiQuery = SpiDevice.GetDeviceSelector("SPI0");
//using Windows.Devices.Enumeration;
var deviceInfo = await DeviceInformation.FindAllAsync(spiQuery);
if (deviceInfo != null && deviceInfo.Count > 0)
{
_mcp3008 = await SpiDevice.FromIdAsync(deviceInfo[0].Id, spiSettings);
return true;
}
else
{
return false;
}
}
/// <summary>
/// Obtains a single sample reading on the specified channel
/// </summary>
/// <param name="channel">Channel to read</param>
/// <returns>Analog Reading Value</returns>
public int Read(byte channel)
{
//From data sheet -- 1 byte selector for channel 0 on the ADC
//First Byte sends the Start bit for SPI
//Second Byte is the Configuration Bit
//1 - single ended
//0 - d2
//0 - d1
//0 - d0
// S321XXXX <-- single-ended channel selection configure bits
// Channel 0 = 10000000 = 0x80 OR (8+channel) << 4
int config = (8 + channel) << 4;
var transmitBuffer = new byte[3] { 1, (byte)config, 0x00 };
var receiveBuffer = new byte[3];
_mcp3008.TransferFullDuplex(transmitBuffer, receiveBuffer);
//first byte returned is 0 (00000000),
//second byte returned we are only interested in the last 2 bits 00000011 (mask of &3)
//then shift result 8 bits to make room for the data from the 3rd byte (makes 10 bits total)
//third byte, need all bits, simply add it to the above result
var result = ((receiveBuffer[1] & 3) << 8) + receiveBuffer[2];
return result;
}
/// <summary>
/// Samples analog reads for a specified duration, on a specified channel and returns a value mapped based on the desired scale
/// </summary>
/// <param name="milliseconds">Sample window duration in milliseconds</param>
/// <param name="adcChannel">MCP3008 channel to perform the reads</param>
/// <param name="scaleMax">Maximum value of the desired scale (minimum is by default 0)</param>
/// <returns></returns>
public async Task<int> Sample(int milliseconds, byte adcChannel, int scaleMax)
{
int retvalue = 0;
await Task.Run(() => {
var sw = Stopwatch.StartNew();
var startMs = sw.ElapsedMilliseconds;
int peakToPeak = 0;
int readMin = 1024;
int readMax = 0;
while ((sw.ElapsedMilliseconds - startMs) < milliseconds)
{
int sample = Read(adcChannel);
if (sample > readMax)
{
readMax = sample; // save just the max levels
}
else if (sample < readMin)
{
readMin = sample; // save just the min levels
}
}
sw.Stop();
peakToPeak = readMax - readMin; // max - min = peak-peak amplitude
//var volts = (peakToPeak * 3.3) / 1024; // convert to volts
//return result in the desired scale
retvalue = Map(peakToPeak, 0, 1024, 0, scaleMax);
});
return retvalue;
}
/// <summary>
/// Similar to the Arduino map function, this function adjusts the scale of the value returned
/// </summary>
/// <param name="value">value to map to a new scale</param>
/// <param name="currentScaleLow">low value of the scale from which value was read</param>
/// <param name="currentScaleHigh">high value of the scale form which value was read</param>
/// <param name="targetScaleLow">low value of the target scale</param>
/// <param name="targetScaleHigh">high value of the target scale</param>
/// <returns>The equivalent of value on the target scale</returns>
private int Map(int value, int currentScaleLow, int currentScaleHigh, int targetScaleLow, int targetScaleHigh)
{
decimal valueD = value;
decimal currentScaleLowD = currentScaleLow;
decimal currentScaleHighD = currentScaleHigh;
decimal targetScaleLowD = targetScaleLow;
decimal targetScaleHighD = targetScaleHigh;
decimal result = (valueD - currentScaleLowD) / (currentScaleHighD - currentScaleLowD) * (targetScaleHighD - targetScaleLowD) + targetScaleLowD;
int retvalue = Convert.ToInt32(Math.Ceiling(result));
return retvalue;
}
}
}
<StackPanel Orientation="Vertical" Margin="25,0">
<TextBlock x:Name="txtStatus" Margin="0,25,0,0">Configuring ...</TextBlock>
<StackPanel Orientation="Horizontal" Margin="0,25,0,0">
<Button x:Name="btnStartSampling" Height="75" Width="75" Background="LightGreen" Margin="0,0,50,0" IsEnabled="False" Click="btnStartSampling_Click">Start</Button>
<Button x:Name="btnEndSampling" Height="75" Width="75" Background="#FFFF505D" IsEnabled="False" Click="btnEndSampling_Click">End</Button>
</StackPanel>
<TextBlock Margin="0,25,0,0">Volume:</TextBlock>
<ProgressBar x:Name="pbVolume" Background="#7F9AF1EF" Height="33" Foreground="#FF1F0C9D" />
</StackPanel>
using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace PiSoundSensing
{
/// <summary>
/// Sound Sensing Main User Interface
/// </summary>
public sealed partial class MainPage : Page
{
//an instance of MCP3008 used to assist in communication with the chip
private MCP3008 _adc = new MCP3008();
//a timer that will be used to sample audio in the current environment
private DispatcherTimer _timer = new DispatcherTimer();
public MainPage()
{
this.InitializeComponent();
Setup();
}
/// <summary>
/// Initializes the connection to the MCP3008 chip
/// </summary>
private async void Setup()
{
//connect to the ADC
bool isConnected = await _adc.Connect();
if (!isConnected)
{
txtStatus.Text = "There was a problem connecting to the MCP3008, please check your wiring and try again.";
}
else
{
btnStartSampling.IsEnabled = true;
btnEndSampling.IsEnabled = true;
txtStatus.Text = "";
//retrieve sample from microphone every 100ms
_timer.Interval = TimeSpan.FromMilliseconds(100);
_timer.Tick += timer_Tick;
//set scale of the progress bar, on the scale of 0 to 100: a percentage
pbVolume.Minimum = 0;
pbVolume.Maximum = 100;
}
}
/// <summary>
/// When actively sampling audio, this method will be called every 100ms and sample audio through
/// the MCP3008 chip from the Electret Microphone Amplifier, retrieve it on a scale of 0-100
/// and display the value visually in the Progress Bar
/// </summary>
/// <param name="sender">ignore</param>
/// <param name="e">ignore</param>
private async void timer_Tick(object sender, object e)
{
//retrieve volume in the scale of 0 to 100
int volume = await _adc.Sample(50, 0, 100);
pbVolume.Value = volume;
}
/// <summary>
/// Begins the timer to sample audio every 100ms
/// </summary>
/// <param name="sender">Start Button</param>
/// <param name="e">ignore</param>
private void btnStartSampling_Click(object sender, RoutedEventArgs e)
{
_timer.Start();
}
/// <summary>
/// Stops the timer to suspend sampling audio
/// </summary>
/// <param name="sender">Stop Button</param>
/// <param name="e">ignore</param>
private void btnEndSampling_Click(object sender, RoutedEventArgs e)
{
_timer.Stop();
}
}
}
Comments