Skip to content

Commit da26016

Browse files
DMA-based ADC input (microphone, analog sensor) (#1101)
Mimics the I2S/PWMAudio/Stream interface for ease of use. * Fix non-32b DMA size transfer calculation in ABM * Rename wasHolding to isHolding in the I2S/PWM It is the **current** number of bits left, not the past number. * Add commented microphone example * Add docs
1 parent 6bef238 commit da26016

File tree

14 files changed

+15526
-15114
lines changed

14 files changed

+15526
-15114
lines changed

docs/adc.rst

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
ADC Input Library
2+
=================
3+
4+
The ADC pins can be sampled and recorded by an application using the same
5+
interface as the I2S or PWM Audio libraries. This allows analog devices which
6+
need to be periodically sampled to be read by applications, easily, such as:
7+
8+
* Analog electret microphones
9+
10+
* Potentiometers
11+
12+
* Light dependent resistors (LDR), etc.
13+
14+
15+
Up to 4 analog samples can be recorded by the hardware (``A0`` ... ``A4``), and all
16+
recording is done at 16-bit levels (but be aware that the ADC in the Pico will only
17+
ever return values between 0...4095).
18+
19+
The interface for the ``ADCInput`` device is very similar to the ``I2S`` input
20+
device, and most code can be ported simply by instantiating a ``ADCInput``
21+
object in lieu of an ``I2S`` input object and choosing the pins to record.
22+
23+
Since this uses the ADC hardware, no ``analogRead`` or ``analogReadTemp`` calls are
24+
allowed while in use.
25+
26+
ADC Input API
27+
-------------
28+
29+
ADCInput(pin0 [, pin1, pin2, pin3])
30+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
31+
Creates an ADC input object which will record the pins specified in the code.
32+
Only pins ``A0`` ... ``A4`` can be used, and they must be specified in increasing
33+
order (i.e. ``ADCInput(A0, A1);`` is valid, but ``ADCInput(A1, A0)`` is not.
34+
35+
bool setBuffers(size_t buffers, size_t bufferWords)
36+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
37+
Set the number of DMA buffers and their size in 32-bit words. Call before
38+
``ADCInput::begin()``.
39+
40+
When running at high sample rates, it is recommended to increase the
41+
``bufferWords`` to 32 or higher (i.e. ``adcinput.setBuffers(4, 32);`` ).
42+
43+
bool setPins(pin_size_t pin [, pin1, pin2, pin3])
44+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
45+
Adjusts the pin to record. Only legal before ``ADCInput::begin()``.
46+
47+
bool setFrequency(long sampleRate)
48+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
49+
Sets the ADC sampling frequency, but does not start recording (however if the
50+
device was already running, it will wontinue to run at the new frequency). Note
51+
that every pin requested will be sampled at this frequency, one after the other.
52+
That is, if you have code like this:
53+
54+
.. code:: cpp
55+
56+
ADCInput adc(A0, A1);
57+
adc.setFrequency(1000);
58+
59+
``A0`` will be sampled at 0ms, 1ms, 2ms, etc. and ``A1`` will be sampled at 0.5ms
60+
1.5ms, 2.5ms, etc. Each input is sampled at the proper frequency but offset in time
61+
since there is only one active ADC at a time.
62+
63+
bool begin()/begin(long sampleRate)
64+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
65+
Start the ADC input up with the given sample rate, or with the value set
66+
using the prior ``setFrequency`` call.
67+
68+
void end()
69+
~~~~~~~~~~
70+
Stops the ADC Input device.
71+
72+
int read()
73+
~~~~~~~~~~
74+
Reads a single sample of recorded ADC data, as a 16-bit value. When multiple pins are
75+
recorded the first read will be pin 0, the second will be pin 1, etc. Applications need
76+
to keep track of which pin is being returned (normally by always reading out all pins
77+
at once). Will not return until data is available.
78+
79+
int available()
80+
~~~~~~~~~~~~~~~
81+
Returns the number of samples that can be read without potentially blocking.
82+
83+
void onReceive(void (\*fn)(void))
84+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
85+
Sets a callback to be called when a ADC input DMA buffer is fully filled.
86+
Will be in an interrupt context so the specified function must operate
87+
quickly and not use blocking calls like delay().

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ For the latest version, always check https://github.com/earlephilhower/arduino-p
3131
EEPROM <eeprom>
3232
I2S Audio <i2s>
3333
PWM Audio <pwm>
34+
Microphone (and Analog Sensor) Input <adc>
3435
Serial USB and UARTs <serial>
3536
"Software Serial" PIO UART <piouart>
3637
Servo <servo>

docs/pwm.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@ in mono mode applies.
3636

3737
bool setBuffers(size_t buffers, size_t bufferWords)
3838
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
39-
Set the number of DMA buffers and their size in 32-bit words as well as
40-
the word to fill when no data is available to send to the PWM hardware.
39+
Set the number of DMA buffers and their size in 32-bit words.
4140
Call before ``PWMAudio::begin()``.
4241

4342
When running at high sample rates, it is recommended to increase the
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
Mono analog microphone example using electret mike on A0
3+
Run using the Arduino Serial Plotter to see waveform.
4+
Released to the Public Domain by Earle F. Philhower, III
5+
6+
Wire the mike's VCC to 3.3V on the Pico, connect the mike's
7+
GND to a convenient Pico GND, and then connect mike OUT to A0
8+
*/
9+
10+
#include <ADCInput.h>
11+
12+
ADCInput mike(A0);
13+
// For stereo/dual mikes, could use this line instead
14+
// ADCInput(A0, A1);
15+
16+
void setup() {
17+
Serial.begin(115200);
18+
19+
mike.begin(8000);
20+
21+
while (1) {
22+
Serial.printf("%d\n", mike.read());
23+
// For stereo/dual mikes, use this line instead
24+
// Serial.printf("%d %d\n", mike.read(), mike.read());
25+
}
26+
}
27+
28+
void loop() {
29+
/* Nothing here */
30+
}

libraries/ADCInput/keywords.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#######################################
2+
# Syntax Coloring Map
3+
#######################################
4+
5+
#######################################
6+
# Datatypes (KEYWORD1)
7+
#######################################
8+
9+
ADCInput KEYWORD1
10+
11+
#######################################
12+
# Methods and Functions (KEYWORD2)
13+
#######################################
14+
begin KEYWORD2
15+
end KEYWORD2
16+
17+
setPins KEYWORD2
18+
setFrequency KEYWORD2
19+
setBuffers KEYWORD2
20+
21+
onReceive KEYWORD2
22+
23+
#######################################
24+
# Constants (LITERAL1)
25+
#######################################

libraries/ADCInput/library.properties

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
name=ADCInput
2+
version=1.0
3+
author=Earle F. Philhower, III <[email protected]>
4+
maintainer=Earle F. Philhower, III <[email protected]>
5+
sentence=Records ADC values (i.e. microphone, sensors) and presents an I2S-like callback/immediate read interface
6+
paragraph=Records ADC values (i.e. microphone, sensors) and presents an I2S-like callback/immediate read interface
7+
category=Communication
8+
url=http://github.com/earlephilhower/arduino-pico
9+
architectures=rp2040
10+
dot_a_linkage=true

libraries/ADCInput/src/ADCInput.cpp

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
/*
2+
ADCInput
3+
Records ADC values (i.e. microphone, sensors) and presents an I2S-like
4+
callback/immediate read interface
5+
6+
Copyright (c) 2023 Earle F. Philhower, III <[email protected]>
7+
8+
This library is free software; you can redistribute it and/or
9+
modify it under the terms of the GNU Lesser General Public
10+
License as published by the Free Software Foundation; either
11+
version 2.1 of the License, or (at your option) any later version.
12+
13+
This library is distributed in the hope that it will be useful,
14+
but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16+
Lesser General Public License for more details.
17+
18+
You should have received a copy of the GNU Lesser General Public
19+
License along with this library; if not, write to the Free Software
20+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21+
*/
22+
23+
#include <Arduino.h>
24+
#include "ADCInput.h"
25+
#include <hardware/adc.h>
26+
27+
ADCInput::ADCInput(pin_size_t p0, pin_size_t p1, pin_size_t p2, pin_size_t p3) {
28+
_running = false;
29+
setPins(p0, p1, p2, p3);
30+
_freq = 48000;
31+
_arb = nullptr;
32+
_cb = nullptr;
33+
_buffers = 8;
34+
_bufferWords = 0;
35+
}
36+
37+
ADCInput::~ADCInput() {
38+
end();
39+
}
40+
41+
bool ADCInput::setBuffers(size_t buffers, size_t bufferWords) {
42+
if (_running || (buffers < 3) || (bufferWords < 8)) {
43+
return false;
44+
}
45+
_buffers = buffers;
46+
_bufferWords = bufferWords;
47+
return true;
48+
}
49+
50+
int ADCInput::_mask(pin_size_t p) {
51+
switch (p) {
52+
case 26: return 1;
53+
case 27: return 2;
54+
case 28: return 4;
55+
case 29: return 8;
56+
default: return 0;
57+
}
58+
}
59+
60+
bool ADCInput::setPins(pin_size_t pin0, pin_size_t pin1, pin_size_t pin2, pin_size_t pin3) {
61+
if (_running) {
62+
return false;
63+
}
64+
_pinMask = _mask(pin0) | _mask(pin1) | _mask(pin2) | _mask(pin3);
65+
return true;
66+
}
67+
68+
bool ADCInput::setFrequency(int newFreq) {
69+
_freq = newFreq * __builtin_popcount(_pinMask); // Want to sample all channels at given frequency
70+
adc_set_clkdiv(48000000.0f / _freq - 1.0f);
71+
return true;
72+
}
73+
74+
void ADCInput::onReceive(void(*fn)(void)) {
75+
_cb = fn;
76+
if (_running) {
77+
_arb->setCallback(_cb);
78+
}
79+
}
80+
81+
bool ADCInput::begin() {
82+
_running = true;
83+
84+
_isHolding = 0;
85+
86+
if (!_bufferWords) {
87+
_bufferWords = 16;
88+
}
89+
90+
// Set up the GPIOs to go to ADC
91+
adc_init();
92+
int cnt = 0;
93+
for (int mask = 1, pin = 26; pin <= 29; mask <<= 1, pin++) {
94+
if (_pinMask & mask) {
95+
if (!cnt) {
96+
adc_select_input(pin - 26);
97+
}
98+
cnt++;
99+
adc_gpio_init(pin);
100+
}
101+
}
102+
adc_set_round_robin(_pinMask);
103+
adc_fifo_setup(true, true, 1, false, false);
104+
105+
setFrequency(_freq);
106+
107+
_arb = new AudioBufferManager(_buffers, _bufferWords, 0, INPUT, DMA_SIZE_16);
108+
_arb->begin(DREQ_ADC, (volatile void*)&adc_hw->fifo);
109+
_arb->setCallback(_cb);
110+
111+
adc_fifo_drain();
112+
113+
adc_run(true);
114+
115+
return true;
116+
}
117+
118+
void ADCInput::end() {
119+
if (_running) {
120+
_running = false;
121+
delete _arb;
122+
_arb = nullptr;
123+
}
124+
adc_run(false);
125+
adc_fifo_drain();
126+
}
127+
128+
int ADCInput::available() {
129+
if (!_running) {
130+
return 0;
131+
} else {
132+
return _arb->available();
133+
}
134+
}
135+
136+
int ADCInput::read() {
137+
if (!_running) {
138+
return -1;
139+
}
140+
141+
if (_hasPeeked) {
142+
_hasPeeked = false;
143+
return _peekSaved;
144+
}
145+
146+
if (_isHolding <= 0) {
147+
_arb->read(&_holdWord, true);
148+
_isHolding = 32;
149+
}
150+
151+
int ret = _holdWord & 0x0fff;
152+
_holdWord >>= 16;
153+
_isHolding -= 16;
154+
return ret;
155+
}
156+
157+
int ADCInput::peek() {
158+
if (!_running) {
159+
return -1;
160+
}
161+
if (!_hasPeeked) {
162+
_peekSaved = read();
163+
_hasPeeked = true;
164+
}
165+
return _peekSaved;
166+
}
167+
168+
void ADCInput::flush() {
169+
if (_running) {
170+
_arb->flush();
171+
}
172+
}

0 commit comments

Comments
 (0)