-
Notifications
You must be signed in to change notification settings - Fork 908
Add example for analog ICS-40180 microphone #135
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
fa88fb2
f29e22a
af2257e
c35c9aa
1975e44
a20d433
8b55f4b
af936d1
afb2e52
12560fd
9dffd1b
b7a8957
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
add_executable(microphone_adc | ||
microphone_adc.c | ||
) | ||
|
||
# pull in common dependencies and adc hardware support | ||
target_link_libraries(microphone_adc pico_stdlib hardware_adc) | ||
|
||
# create map/bin/hex file etc. | ||
pico_add_extra_outputs(microphone_adc) | ||
|
||
# add url via pico_set_program_url | ||
example_auto_set_url(microphone_adc) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
= Attaching a microphone using the ADC | ||
|
||
This example code shows how to interface the Raspberry Pi Pico with a standard analog microphone via the onboard analog to digital converter (ADC). In this example, we use an ICS-40180 breakout board by SparkFun but any analog microphone should be compatible with this tutorial. SparkFun have https://learn.sparkfun.com/tutorials/mems-microphone-hookup-guide[written a guide] for this board that goes into more detail about the board and how it works. | ||
|
||
[TIP] | ||
====== | ||
An analog to digital converter (ADC) is responsible for reading continually varying input signals that may range from 0 to a specified reference voltage (in the Pico's case this reference voltage is set by the supply voltage and can be measured on pin 35, ADC_VREF) and converting them into binary, i.e. a number that can be digitally stored. | ||
====== | ||
|
||
The Pico has a 12-bit ADC (ENOB of 8.7-bit, see https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf[RP2040 datasheet section 4.9.3 for more details]), meaning that a read operation will return a number ranging from 0 to 4095 (2^12 - 1) for a total of 4096 possible values. Therefore, the resolution of the ADC is 3.3/4096, so roughly steps of 0.8 millivolts. The SparkFun breakout uses an OPA344 operational amplifier to boost the signal coming from the microphone to voltage levels that can be easily read by the ADC. An important side effect is that a bias of 0.5*Vcc is added to the signal, even when the microphone is not picking up any sound. | ||
|
||
The ADC provides us with a raw voltage value but when dealing with sound, we're more interested in the amplitude of the audio signal. This is defined as one half the peak-to-peak amplitude. Included with this example is a very simple Python script that will plot the voltage values it receives via the serial port. By tweaking the sampling rates, and various other parameters, the data from the microphone can be analysed in various ways, such as in a Fast Fourier Transform to see what frequencies make up the signal. | ||
|
||
[[microphone_adc_plotter_image]] | ||
[pdfwidth=75%] | ||
.Example output from included Python script | ||
image::microphone_adc_plotter.png[] | ||
|
||
== Wiring information | ||
|
||
Wiring up the device requires 3 jumpers, to connect VCC (3.3v), GND, and AOUT. The example here uses ADC0, which is GP26. Power is supplied from the 3.3V pin. | ||
|
||
WARNING: Most boards will take a range of VCC voltages from the Pico's default 3.3V to the 5 volts commonly seen on other microcontrollers. Ensure your board doesn't output an analogue signal greater than 3.3V as this may result in permanent damage to the Pico's ADC. | ||
|
||
[[ics-40180-adc_wiring]] | ||
[pdfwidth=75%] | ||
.Wiring Diagram for ICS-40180 microphone breakout board. | ||
image::microphone_adc_bb.png[] | ||
|
||
== List of Files | ||
|
||
CMakeLists.txt:: CMake file to incorporate the example in to the examples build tree. | ||
microphone_adc.c:: The example code. | ||
|
||
== Bill of Materials | ||
|
||
.A list of materials required for the example | ||
[[ics-40180-adc-bom-table]] | ||
[cols=3] | ||
|=== | ||
| *Item* | *Quantity* | Details | ||
| Breadboard | 1 | generic part | ||
| Raspberry Pi Pico | 1 | http://raspberrypi.org/ | ||
| ICS-40180 microphone breakout board or similar | 1 | https://www.sparkfun.com/products/18011[From SparkFun] | ||
| M/M Jumper wires | 3 | generic part | ||
|=== | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/** | ||
* Copyright (c) 2021 Raspberry Pi (Trading) Ltd. | ||
* | ||
* SPDX-License-Identifier: BSD-3-Clause | ||
*/ | ||
|
||
#include <stdio.h> | ||
#include "pico/stdlib.h" | ||
#include "hardware/gpio.h" | ||
#include "hardware/adc.h" | ||
#include "hardware/uart.h" | ||
#include "pico/binary_info.h" | ||
|
||
/* Example code to extract analog values from a microphone using the ADC | ||
with accompanying Python file to plot these values | ||
|
||
Connections on Raspberry Pi Pico board, other boards may vary. | ||
|
||
GPIO 26/ADC0 (pin 31)-> AOUT or AUD on microphone board | ||
3.3v (pin 36) -> VCC on microphone board | ||
GND (pin 38) -> GND on microphone board | ||
*/ | ||
|
||
#define ADC_PIN 0 | ||
#define ADC_VREF 3.3 | ||
#define ADC_RANGE (1 << 12) | ||
#define ADC_CONVERT ADC_VREF / (ADC_RANGE - 1) | ||
|
||
int main() { | ||
stdio_init_all(); | ||
printf("Beep boop, listening...\n"); | ||
|
||
bi_decl(bi_program_description("Analog microphone example for Raspberry Pi Pico")); // for picotool | ||
bi_decl(bi_1pin_with_name(ADC_PIN, "ADC input pin")); | ||
|
||
adc_init(); | ||
adc_gpio_init(ADC_PIN + 26); | ||
adc_select_input(ADC_PIN); | ||
|
||
uint adc_raw; | ||
while (1) { | ||
adc_raw = adc_read(); // raw voltage from ADC | ||
printf("%.2f\n", adc_raw * ADC_CONVERT); | ||
sleep_ms(10); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess the 10ms sleep here implies there's a maximum frequency that this code will be able to detect? (only mentioning this because you talked about FFT analysis earlier) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. Though I'm not 100% sure where the botteneck is/will be, I don't think it's with the ADC but rather on the Python side with polling values from the UART, so I just set 10ms as a sensible value. |
||
} | ||
|
||
return 0; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
#!/usr/bin/env python3 | ||
|
||
# Grabs raw data from the Pico's UART and plots it as received | ||
|
||
# Install dependencies: | ||
# python3 -m pip install pyserial matplotlib | ||
|
||
# Usage: python3 plotter <port> | ||
# eg. python3 plotter /dev/ttyACM0 | ||
|
||
# see matplotlib animation API for more: https://matplotlib.org/stable/api/animation_api.html | ||
|
||
import serial | ||
import sys | ||
import matplotlib.pyplot as plt | ||
import matplotlib.animation as animation | ||
from matplotlib.lines import Line2D | ||
|
||
# disable toolbar | ||
plt.rcParams['toolbar'] = 'None' | ||
|
||
class Plotter: | ||
def __init__(self, ax): | ||
self.ax = ax | ||
self.maxt = 250 | ||
self.tdata = [0] | ||
self.ydata = [3.3/2] | ||
self.line = Line2D(self.tdata, self.ydata) | ||
|
||
self.ax.add_line(self.line) | ||
self.ax.set_ylim(0, 3.3) | ||
self.ax.set_xlim(0, self.maxt) | ||
|
||
def update(self, y): | ||
lastt = self.tdata[-1] | ||
if lastt - self.tdata[0] >= self.maxt: # drop old frames | ||
self.tdata = self.tdata[1:] | ||
self.ydata = self.ydata[1:] | ||
self.ax.set_xlim(self.tdata[0], self.tdata[0] + self.maxt) | ||
|
||
t = lastt + 1 | ||
self.tdata.append(t) | ||
self.ydata.append(y) | ||
self.line.set_data(self.tdata, self.ydata) | ||
return self.line, | ||
|
||
|
||
def serial_getter(): | ||
# grab fresh ADC values | ||
# note sometimes UART drops chars so we try a max of 5 times | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahhh, this is probably related to raspberrypi/pico-sdk#504 ! |
||
# to get proper data | ||
while True: | ||
for i in range(5): | ||
line = ser.readline() | ||
try: | ||
line = float(line) | ||
except ValueError: | ||
continue | ||
break | ||
yield line | ||
|
||
if len(sys.argv) < 2: | ||
raise Exception("Ruh roh..no port specified!") | ||
|
||
ser = serial.Serial(sys.argv[1], 115200, timeout=1) | ||
|
||
fig, ax = plt.subplots() | ||
plotter = Plotter(ax) | ||
|
||
ani = animation.FuncAnimation(fig, plotter.update, serial_getter, interval=1, | ||
blit=True, cache_frame_data=False) | ||
|
||
ax.set_xlabel("Samples") | ||
ax.set_ylabel("Voltage (V)") | ||
fig.canvas.manager.set_window_title('Microphone ADC example') | ||
fig.tight_layout() | ||
plt.show() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kilograham Is
ADC_PIN
here correct, or should it beADC_PIN + 26
as used withadc_gpio_init
below?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amusingly, I faced the same dilemma.
adc_select_input
expects 0-3 whileadc_gpio_init
expects a proper pin number.