Skip to content

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

Merged
merged 12 commits into from
Oct 14, 2021
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ App|Description
[hello_adc](adc/hello_adc)|Display the voltage from an ADC input.
[joystick_display](adc/joystick_display)|Display a Joystick X/Y input based on two ADC inputs.
[adc_console](adc/adc_console)|An interactive shell for playing with the ADC. Includes example of free-running capture mode.
[microphone_adc](adc/microphone_adc)|Read analog values from a microphone and plot the measured sound amplitude.

### Clocks

Expand Down
1 change: 1 addition & 0 deletions adc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ if (NOT PICO_NO_HARDWARE)
add_subdirectory(dma_capture)
add_subdirectory(hello_adc)
add_subdirectory(joystick_display)
add_subdirectory(microphone_adc)
endif ()
12 changes: 12 additions & 0 deletions adc/microphone_adc/CMakeLists.txt
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)
50 changes: 50 additions & 0 deletions adc/microphone_adc/README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
= 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 continuous 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, ie. 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 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 voltages from the Pico's default 3.3v to the 5 volts commonly seen on other microcontrollers. Ensure your board works with either the 3.3V or 5V supplied by the Pico.

WARNING: Do not connect a voltage greater than 3.3V to the Pico's ADC (and any of its GPIO) as this may result in permanent damage.

[[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
|===


48 changes: 48 additions & 0 deletions adc/microphone_adc/microphone_adc.c
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

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"));
Copy link
Contributor

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 be ADC_PIN + 26 as used with adc_gpio_init below?

Copy link
Author

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 while adc_gpio_init expects a proper pin number.


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);
Copy link
Contributor

Choose a reason for hiding this comment

The 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)

Copy link
Author

Choose a reason for hiding this comment

The 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;
}
Binary file added adc/microphone_adc/microphone_adc.fzz
Binary file not shown.
Binary file added adc/microphone_adc/microphone_adc_bb.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added adc/microphone_adc/microphone_adc_plotter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
77 changes: 77 additions & 0 deletions adc/microphone_adc/plotter.py
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
Copy link
Contributor

Choose a reason for hiding this comment

The 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()