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
This commit was merged in pull request #1101.
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
ADC Input Library
|
||||
=================
|
||||
|
||||
The ADC pins can be sampled and recorded by an application using the same
|
||||
interface as the I2S or PWM Audio libraries. This allows analog devices which
|
||||
need to be periodically sampled to be read by applications, easily, such as:
|
||||
|
||||
* Analog electret microphones
|
||||
|
||||
* Potentiometers
|
||||
|
||||
* Light dependent resistors (LDR), etc.
|
||||
|
||||
|
||||
Up to 4 analog samples can be recorded by the hardware (``A0`` ... ``A4``), and all
|
||||
recording is done at 16-bit levels (but be aware that the ADC in the Pico will only
|
||||
ever return values between 0...4095).
|
||||
|
||||
The interface for the ``ADCInput`` device is very similar to the ``I2S`` input
|
||||
device, and most code can be ported simply by instantiating a ``ADCInput``
|
||||
object in lieu of an ``I2S`` input object and choosing the pins to record.
|
||||
|
||||
Since this uses the ADC hardware, no ``analogRead`` or ``analogReadTemp`` calls are
|
||||
allowed while in use.
|
||||
|
||||
ADC Input API
|
||||
-------------
|
||||
|
||||
ADCInput(pin0 [, pin1, pin2, pin3])
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Creates an ADC input object which will record the pins specified in the code.
|
||||
Only pins ``A0`` ... ``A4`` can be used, and they must be specified in increasing
|
||||
order (i.e. ``ADCInput(A0, A1);`` is valid, but ``ADCInput(A1, A0)`` is not.
|
||||
|
||||
bool setBuffers(size_t buffers, size_t bufferWords)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Set the number of DMA buffers and their size in 32-bit words. Call before
|
||||
``ADCInput::begin()``.
|
||||
|
||||
When running at high sample rates, it is recommended to increase the
|
||||
``bufferWords`` to 32 or higher (i.e. ``adcinput.setBuffers(4, 32);`` ).
|
||||
|
||||
bool setPins(pin_size_t pin [, pin1, pin2, pin3])
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Adjusts the pin to record. Only legal before ``ADCInput::begin()``.
|
||||
|
||||
bool setFrequency(long sampleRate)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Sets the ADC sampling frequency, but does not start recording (however if the
|
||||
device was already running, it will wontinue to run at the new frequency). Note
|
||||
that every pin requested will be sampled at this frequency, one after the other.
|
||||
That is, if you have code like this:
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
ADCInput adc(A0, A1);
|
||||
adc.setFrequency(1000);
|
||||
|
||||
``A0`` will be sampled at 0ms, 1ms, 2ms, etc. and ``A1`` will be sampled at 0.5ms
|
||||
1.5ms, 2.5ms, etc. Each input is sampled at the proper frequency but offset in time
|
||||
since there is only one active ADC at a time.
|
||||
|
||||
bool begin()/begin(long sampleRate)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Start the ADC input up with the given sample rate, or with the value set
|
||||
using the prior ``setFrequency`` call.
|
||||
|
||||
void end()
|
||||
~~~~~~~~~~
|
||||
Stops the ADC Input device.
|
||||
|
||||
int read()
|
||||
~~~~~~~~~~
|
||||
Reads a single sample of recorded ADC data, as a 16-bit value. When multiple pins are
|
||||
recorded the first read will be pin 0, the second will be pin 1, etc. Applications need
|
||||
to keep track of which pin is being returned (normally by always reading out all pins
|
||||
at once). Will not return until data is available.
|
||||
|
||||
int available()
|
||||
~~~~~~~~~~~~~~~
|
||||
Returns the number of samples that can be read without potentially blocking.
|
||||
|
||||
void onReceive(void (\*fn)(void))
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Sets a callback to be called when a ADC input DMA buffer is fully filled.
|
||||
Will be in an interrupt context so the specified function must operate
|
||||
quickly and not use blocking calls like delay().
|
||||
@@ -31,6 +31,7 @@ For the latest version, always check https://github.com/earlephilhower/arduino-p
|
||||
EEPROM <eeprom>
|
||||
I2S Audio <i2s>
|
||||
PWM Audio <pwm>
|
||||
Microphone (and Analog Sensor) Input <adc>
|
||||
Serial USB and UARTs <serial>
|
||||
"Software Serial" PIO UART <piouart>
|
||||
Servo <servo>
|
||||
|
||||
+1
-2
@@ -36,8 +36,7 @@ in mono mode applies.
|
||||
|
||||
bool setBuffers(size_t buffers, size_t bufferWords)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Set the number of DMA buffers and their size in 32-bit words as well as
|
||||
the word to fill when no data is available to send to the PWM hardware.
|
||||
Set the number of DMA buffers and their size in 32-bit words.
|
||||
Call before ``PWMAudio::begin()``.
|
||||
|
||||
When running at high sample rates, it is recommended to increase the
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
Mono analog microphone example using electret mike on A0
|
||||
Run using the Arduino Serial Plotter to see waveform.
|
||||
Released to the Public Domain by Earle F. Philhower, III
|
||||
|
||||
Wire the mike's VCC to 3.3V on the Pico, connect the mike's
|
||||
GND to a convenient Pico GND, and then connect mike OUT to A0
|
||||
*/
|
||||
|
||||
#include <ADCInput.h>
|
||||
|
||||
ADCInput mike(A0);
|
||||
// For stereo/dual mikes, could use this line instead
|
||||
// ADCInput(A0, A1);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
mike.begin(8000);
|
||||
|
||||
while (1) {
|
||||
Serial.printf("%d\n", mike.read());
|
||||
// For stereo/dual mikes, use this line instead
|
||||
// Serial.printf("%d %d\n", mike.read(), mike.read());
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
/* Nothing here */
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#######################################
|
||||
# Syntax Coloring Map
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Datatypes (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
ADCInput KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
begin KEYWORD2
|
||||
end KEYWORD2
|
||||
|
||||
setPins KEYWORD2
|
||||
setFrequency KEYWORD2
|
||||
setBuffers KEYWORD2
|
||||
|
||||
onReceive KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
@@ -0,0 +1,10 @@
|
||||
name=ADCInput
|
||||
version=1.0
|
||||
author=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
sentence=Records ADC values (i.e. microphone, sensors) and presents an I2S-like callback/immediate read interface
|
||||
paragraph=Records ADC values (i.e. microphone, sensors) and presents an I2S-like callback/immediate read interface
|
||||
category=Communication
|
||||
url=http://github.com/earlephilhower/arduino-pico
|
||||
architectures=rp2040
|
||||
dot_a_linkage=true
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
ADCInput
|
||||
Records ADC values (i.e. microphone, sensors) and presents an I2S-like
|
||||
callback/immediate read interface
|
||||
|
||||
Copyright (c) 2023 Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "ADCInput.h"
|
||||
#include <hardware/adc.h>
|
||||
|
||||
ADCInput::ADCInput(pin_size_t p0, pin_size_t p1, pin_size_t p2, pin_size_t p3) {
|
||||
_running = false;
|
||||
setPins(p0, p1, p2, p3);
|
||||
_freq = 48000;
|
||||
_arb = nullptr;
|
||||
_cb = nullptr;
|
||||
_buffers = 8;
|
||||
_bufferWords = 0;
|
||||
}
|
||||
|
||||
ADCInput::~ADCInput() {
|
||||
end();
|
||||
}
|
||||
|
||||
bool ADCInput::setBuffers(size_t buffers, size_t bufferWords) {
|
||||
if (_running || (buffers < 3) || (bufferWords < 8)) {
|
||||
return false;
|
||||
}
|
||||
_buffers = buffers;
|
||||
_bufferWords = bufferWords;
|
||||
return true;
|
||||
}
|
||||
|
||||
int ADCInput::_mask(pin_size_t p) {
|
||||
switch (p) {
|
||||
case 26: return 1;
|
||||
case 27: return 2;
|
||||
case 28: return 4;
|
||||
case 29: return 8;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool ADCInput::setPins(pin_size_t pin0, pin_size_t pin1, pin_size_t pin2, pin_size_t pin3) {
|
||||
if (_running) {
|
||||
return false;
|
||||
}
|
||||
_pinMask = _mask(pin0) | _mask(pin1) | _mask(pin2) | _mask(pin3);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ADCInput::setFrequency(int newFreq) {
|
||||
_freq = newFreq * __builtin_popcount(_pinMask); // Want to sample all channels at given frequency
|
||||
adc_set_clkdiv(48000000.0f / _freq - 1.0f);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ADCInput::onReceive(void(*fn)(void)) {
|
||||
_cb = fn;
|
||||
if (_running) {
|
||||
_arb->setCallback(_cb);
|
||||
}
|
||||
}
|
||||
|
||||
bool ADCInput::begin() {
|
||||
_running = true;
|
||||
|
||||
_isHolding = 0;
|
||||
|
||||
if (!_bufferWords) {
|
||||
_bufferWords = 16;
|
||||
}
|
||||
|
||||
// Set up the GPIOs to go to ADC
|
||||
adc_init();
|
||||
int cnt = 0;
|
||||
for (int mask = 1, pin = 26; pin <= 29; mask <<= 1, pin++) {
|
||||
if (_pinMask & mask) {
|
||||
if (!cnt) {
|
||||
adc_select_input(pin - 26);
|
||||
}
|
||||
cnt++;
|
||||
adc_gpio_init(pin);
|
||||
}
|
||||
}
|
||||
adc_set_round_robin(_pinMask);
|
||||
adc_fifo_setup(true, true, 1, false, false);
|
||||
|
||||
setFrequency(_freq);
|
||||
|
||||
_arb = new AudioBufferManager(_buffers, _bufferWords, 0, INPUT, DMA_SIZE_16);
|
||||
_arb->begin(DREQ_ADC, (volatile void*)&adc_hw->fifo);
|
||||
_arb->setCallback(_cb);
|
||||
|
||||
adc_fifo_drain();
|
||||
|
||||
adc_run(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ADCInput::end() {
|
||||
if (_running) {
|
||||
_running = false;
|
||||
delete _arb;
|
||||
_arb = nullptr;
|
||||
}
|
||||
adc_run(false);
|
||||
adc_fifo_drain();
|
||||
}
|
||||
|
||||
int ADCInput::available() {
|
||||
if (!_running) {
|
||||
return 0;
|
||||
} else {
|
||||
return _arb->available();
|
||||
}
|
||||
}
|
||||
|
||||
int ADCInput::read() {
|
||||
if (!_running) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (_hasPeeked) {
|
||||
_hasPeeked = false;
|
||||
return _peekSaved;
|
||||
}
|
||||
|
||||
if (_isHolding <= 0) {
|
||||
_arb->read(&_holdWord, true);
|
||||
_isHolding = 32;
|
||||
}
|
||||
|
||||
int ret = _holdWord & 0x0fff;
|
||||
_holdWord >>= 16;
|
||||
_isHolding -= 16;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ADCInput::peek() {
|
||||
if (!_running) {
|
||||
return -1;
|
||||
}
|
||||
if (!_hasPeeked) {
|
||||
_peekSaved = read();
|
||||
_hasPeeked = true;
|
||||
}
|
||||
return _peekSaved;
|
||||
}
|
||||
|
||||
void ADCInput::flush() {
|
||||
if (_running) {
|
||||
_arb->flush();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
ADCInput
|
||||
Records ADC values (i.e. microphone, sensors) and presents an I2S-like
|
||||
callback/immediate read interface
|
||||
|
||||
Copyright (c) 2023 Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include "AudioBufferManager.h"
|
||||
|
||||
class ADCInput : public Stream {
|
||||
public:
|
||||
ADCInput(pin_size_t pin0, pin_size_t pin1 = 255, pin_size_t pin2 = 255, pin_size_t pin3 = 255);
|
||||
virtual ~ADCInput();
|
||||
|
||||
bool setBuffers(size_t buffers, size_t bufferWords);
|
||||
bool setFrequency(int newFreq);
|
||||
bool setPins(pin_size_t pin0, pin_size_t pin1 = 255, pin_size_t pin2 = 255, pin_size_t pin3 = 255);
|
||||
|
||||
bool begin(long sampleRate) {
|
||||
setFrequency(sampleRate);
|
||||
return begin();
|
||||
}
|
||||
|
||||
bool begin();
|
||||
void end();
|
||||
|
||||
// from Stream
|
||||
virtual int available() override;
|
||||
virtual int read() override;
|
||||
virtual int peek() override;
|
||||
virtual void flush() override;
|
||||
|
||||
// from Print, not supported
|
||||
virtual size_t write(const uint8_t *buffer, size_t size) override {
|
||||
(void) buffer;
|
||||
(void) size;
|
||||
return -1;
|
||||
}
|
||||
virtual size_t write(uint8_t x) override {
|
||||
(void) x;
|
||||
return -1;
|
||||
}
|
||||
virtual int availableForWrite() override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Note that these callback are called from **INTERRUPT CONTEXT** and hence
|
||||
// should be in RAM, not FLASH, and should be quick to execute.
|
||||
void onReceive(void(*)(void));
|
||||
|
||||
private:
|
||||
uint32_t _pinMask;
|
||||
|
||||
int _freq;
|
||||
|
||||
size_t _buffers;
|
||||
size_t _bufferWords;
|
||||
|
||||
bool _running;
|
||||
void (*_cb)();
|
||||
|
||||
bool _hasPeeked;
|
||||
uint32_t _peekSaved;
|
||||
uint32_t _holdWord = 0;
|
||||
int _isHolding = 0;
|
||||
|
||||
int _mask(pin_size_t pin);
|
||||
|
||||
AudioBufferManager *_arb;
|
||||
};
|
||||
@@ -59,6 +59,7 @@ AudioBufferManager::AudioBufferManager(size_t bufferCount, size_t bufferWords, i
|
||||
for (size_t i = 0; i < bufferCount; i++) {
|
||||
auto ab = new AudioBuffer;
|
||||
ab->buff = new uint32_t[_wordsPerBuffer];
|
||||
bzero(ab->buff, _wordsPerBuffer * 4);
|
||||
ab->next = nullptr;
|
||||
_addToList(&_empty, ab);
|
||||
}
|
||||
@@ -135,10 +136,10 @@ bool AudioBufferManager::begin(int dreq, volatile void *pioFIFOAddr) {
|
||||
channel_config_set_irq_quiet(&c, false); // Need IRQs
|
||||
|
||||
if (_isOutput) {
|
||||
dma_channel_configure(_channelDMA[i], &c, pioFIFOAddr, _silence->buff, _wordsPerBuffer, false);
|
||||
dma_channel_configure(_channelDMA[i], &c, pioFIFOAddr, _silence->buff, _wordsPerBuffer * (_dmaSize == DMA_SIZE_16 ? 2 : 1), false);
|
||||
} else {
|
||||
_active[i] = _takeFromList(&_empty);
|
||||
dma_channel_configure(_channelDMA[i], &c, _active[i]->buff, pioFIFOAddr, _wordsPerBuffer, false);
|
||||
dma_channel_configure(_channelDMA[i], &c, _active[i]->buff, pioFIFOAddr, _wordsPerBuffer * (_dmaSize == DMA_SIZE_16 ? 2 : 1), false);
|
||||
}
|
||||
dma_channel_set_irq0_enabled(_channelDMA[i], true);
|
||||
__channelMap[_channelDMA[i]] = this;
|
||||
@@ -264,7 +265,7 @@ void __not_in_flash_func(AudioBufferManager::_dmaIRQ)(int channel) {
|
||||
}
|
||||
dma_channel_set_write_addr(channel, _active[0]->buff, false);
|
||||
}
|
||||
dma_channel_set_trans_count(channel, _wordsPerBuffer, false);
|
||||
dma_channel_set_trans_count(channel, _wordsPerBuffer * (_dmaSize == DMA_SIZE_16 ? 2 : 1), false);
|
||||
dma_channel_acknowledge_irq0(channel);
|
||||
if (_callback) {
|
||||
_callback();
|
||||
|
||||
+14
-14
@@ -188,9 +188,9 @@ int I2S::read() {
|
||||
return _peekSaved;
|
||||
}
|
||||
|
||||
if (_wasHolding <= 0) {
|
||||
if (_isHolding <= 0) {
|
||||
read(&_holdWord, true);
|
||||
_wasHolding = 32;
|
||||
_isHolding = 32;
|
||||
}
|
||||
|
||||
int ret;
|
||||
@@ -198,18 +198,18 @@ int I2S::read() {
|
||||
case 8:
|
||||
ret = _holdWord >> 24;
|
||||
_holdWord <<= 8;
|
||||
_wasHolding -= 8;
|
||||
_isHolding -= 8;
|
||||
return ret;
|
||||
case 16:
|
||||
ret = _holdWord >> 16;
|
||||
_holdWord <<= 16;
|
||||
_wasHolding -= 32;
|
||||
_isHolding -= 32;
|
||||
return ret;
|
||||
case 24:
|
||||
case 32:
|
||||
default:
|
||||
ret = _holdWord;
|
||||
_wasHolding = 0;
|
||||
_isHolding = 0;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -238,26 +238,26 @@ size_t I2S::_writeNatural(int32_t s) {
|
||||
switch (_bps) {
|
||||
case 8:
|
||||
_holdWord |= s & 0xff;
|
||||
if (_wasHolding >= 24) {
|
||||
if (_isHolding >= 24) {
|
||||
auto ret = write(_holdWord, true);
|
||||
_holdWord = 0;
|
||||
_wasHolding = 0;
|
||||
_isHolding = 0;
|
||||
return ret;
|
||||
} else {
|
||||
_holdWord <<= 8;
|
||||
_wasHolding += 8;
|
||||
_isHolding += 8;
|
||||
return 1;
|
||||
}
|
||||
case 16:
|
||||
_holdWord |= s & 0xffff;
|
||||
if (_wasHolding) {
|
||||
if (_isHolding) {
|
||||
auto ret = write(_holdWord, true);
|
||||
_holdWord = 0;
|
||||
_wasHolding = 0;
|
||||
_isHolding = 0;
|
||||
return ret;
|
||||
} else {
|
||||
_holdWord <<= 16;
|
||||
_wasHolding = 16;
|
||||
_isHolding = 16;
|
||||
return 1;
|
||||
}
|
||||
case 24:
|
||||
@@ -314,13 +314,13 @@ bool I2S::read8(int8_t *l, int8_t *r) {
|
||||
if (!_running || _isOutput) {
|
||||
return false;
|
||||
}
|
||||
if (_wasHolding) {
|
||||
if (_isHolding) {
|
||||
*l = (_holdWord >> 8) & 0xff;
|
||||
*r = (_holdWord >> 0) & 0xff;
|
||||
_wasHolding = 0;
|
||||
_isHolding = 0;
|
||||
} else {
|
||||
read(&_holdWord, true);
|
||||
_wasHolding = 16;
|
||||
_isHolding = 16;
|
||||
*l = (_holdWord >> 24) & 0xff;
|
||||
*r = (_holdWord >> 16) & 0xff;
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ private:
|
||||
bool _writtenHalf;
|
||||
|
||||
int32_t _holdWord = 0;
|
||||
int _wasHolding = 0;
|
||||
int _isHolding = 0;
|
||||
|
||||
void (*_cb)();
|
||||
|
||||
|
||||
+15090
-15090
File diff suppressed because it is too large
Load Diff
@@ -71,7 +71,7 @@ bool PWMAudio::setFrequency(int newFreq) {
|
||||
|
||||
if (fPWM > clock_get_hz(clk_sys)) {
|
||||
// Need to downscale the range to hit the frequency target
|
||||
float pwmMax = (float) clock_get_hz(clk_sys) /(float) _freq;
|
||||
float pwmMax = (float) clock_get_hz(clk_sys) / (float) _freq;
|
||||
_pwmScale = pwmMax;
|
||||
fPWM = clock_get_hz(clk_sys);
|
||||
} else {
|
||||
@@ -166,7 +166,7 @@ size_t PWMAudio::write(int16_t val, bool sync) {
|
||||
return 0;
|
||||
}
|
||||
// Go from signed -32K...32K to unsigned 0...64K
|
||||
uint32_t sample = (uint32_t) (val + 0x8000);
|
||||
uint32_t sample = (uint32_t)(val + 0x8000);
|
||||
// Adjust to the real range
|
||||
sample *= _pwmScale;
|
||||
sample >>= 16;
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
for dir in ./cores/rp2040 ./libraries/EEPROM ./libraries/I2S ./libraries/SingleFileDrive \
|
||||
./libraries/LittleFS/src ./libraries/LittleFS/examples \
|
||||
./libraries/rp2040 ./libraries/SD ./libraries/ESP8266SdFat \
|
||||
./libraries/LittleFS/src ./libraries/LittleFS/examples ./libraries/PWMAudio \
|
||||
./libraries/rp2040 ./libraries/SD ./libraries/ESP8266SdFat ./libraries/ADCInput \
|
||||
./libraries/Servo ./libraries/SPI ./libraries/Wire ./libraries/PDM \
|
||||
./libraries/WiFi ./libraries/lwIP_Ethernet ./libraries/lwIP_CYW43 \
|
||||
./libraries/FreeRTOS/src ./libraries/LEAmDNS ./libraries/MD5Builder \
|
||||
|
||||
Reference in New Issue
Block a user