Add support for external ADC

This commit is contained in:
Frederik Walk 2023-08-09 22:29:27 +02:00
parent d82f751701
commit 96c34813dc
7 changed files with 118 additions and 29 deletions

View File

@ -27,6 +27,8 @@ target_link_libraries(
tinyusb_board
pico_stdlib
hardware_adc
hardware_i2c
hardware_spi
pico_multicore
pio_ws2812
pico_ssd1306

View File

@ -51,7 +51,7 @@ Few things which you probably want to change more regularly can be changed using
Those settings are persisted to flash memory if you choose 'Save' when exiting the Menu and will survive power cycles.
Defaults and everything else are compiled statically into the firmware. You can find everything in `include/GlobalConfiguration.h`. This covers default controller emulation mode, i2c pins, addresses and speed, default trigger thresholds, scale and debounce delay, button mapping, LED colors and brightness.
Defaults and everything else are compiled statically into the firmware. You can find everything in `include/GlobalConfiguration.h`. This covers default controller emulation mode, i2c pins, external ADC configuration, addresses and speed, default trigger thresholds, scale and debounce delay, button mapping, LED colors and brightness.
### Debounce Delay / Hold Time

View File

@ -7,6 +7,7 @@
#include "peripherals/StatusLed.h"
#include "hardware/i2c.h"
#include "hardware/spi.h"
namespace Doncon::Config {
@ -45,8 +46,16 @@ const Peripherals::Drum::Config drum_config = {
},
230, // Trigger threshold scale level
50, // ADC sample count
18, // Debounce delay in milliseconds
true, // Use external ADC
// SPI config for external ADC, unused if above is false
{
3, // MOSI Pin
4, // MISO Pin
2, // SCLK Pin
1, // SCSn Pin
spi0, // Block
2000000, // Speed
},
};
const Peripherals::Buttons::Config button_config = {

View File

@ -3,7 +3,12 @@
#include "utils/InputState.h"
#include "hardware/spi.h"
#include <mcp3204/Mcp3204.h>
#include <map>
#include <memory>
#include <stdint.h>
namespace Doncon::Peripherals {
@ -18,18 +23,30 @@ class Drum {
uint16_t ka_right;
};
struct {
struct AdcInputs {
uint8_t don_left;
uint8_t ka_left;
uint8_t don_right;
uint8_t ka_right;
} pins;
};
AdcInputs adc_inputs;
Thresholds trigger_thresholds;
uint8_t trigger_threshold_scale_level;
uint8_t sample_count;
uint16_t debounce_delay_ms;
bool use_external_adc;
struct Spi {
uint8_t mosi_pin;
uint8_t miso_pin;
uint8_t sclk_pin;
uint8_t scsn_pin;
spi_inst_t *block;
uint speed_hz;
} external_adc_spi_config;
};
private:
@ -42,19 +59,40 @@ class Drum {
class Pad {
private:
uint8_t pin;
uint8_t channel;
uint32_t last_change;
bool active;
public:
Pad(uint8_t pin);
Pad(uint8_t channel);
uint8_t getPin() const { return pin; };
uint8_t getChannel() const { return channel; };
bool getState() const { return active; };
void setState(bool state, uint16_t debounce_delay);
};
class AdcInterface {
public:
virtual uint16_t read(uint8_t channel) = 0;
};
class InternalAdc : public AdcInterface {
public:
InternalAdc(const Config::AdcInputs &adc_inputs);
virtual uint16_t read(uint8_t channel) final;
};
class ExternalAdc : public AdcInterface {
private:
Mcp3204 m_mcp3204;
public:
ExternalAdc(const Config::Spi &spi_config);
virtual uint16_t read(uint8_t channel) final;
};
Config m_config;
std::unique_ptr<AdcInterface> m_adc;
std::map<Id, Pad> m_pads;
private:
@ -65,7 +103,7 @@ class Drum {
void updateInputState(Utils::InputState &input_state);
void setThresholds(const Config::Thresholds& thresholds);
void setThresholds(const Config::Thresholds &thresholds);
void setThresholdScaleLevel(const uint8_t threshold_scale_level);
};

View File

@ -6,9 +6,10 @@
class Mcp3204 {
private:
spi_inst *m_spi;
uint8_t m_cs_pin;
public:
Mcp3204(spi_inst *spi);
Mcp3204(spi_inst *spi, uint8_t cs_pin);
uint16_t read(uint8_t channel);
};

View File

@ -1,12 +1,8 @@
#include "Mcp3204.h"
Mcp3204::Mcp3204(spi_inst *spi) : m_spi(spi) {
// Put SPI in 1,1 mode. In this mode date will be clocked out on
// a falling edge and latched from the ADC on a rising edge.
// Also the CS will be held low in-between bytes as required
// by the mcp3204.
spi_set_format(m_spi, 8, SPI_CPOL_1, SPI_CPHA_1, SPI_MSB_FIRST);
}
#include "hardware/gpio.h"
Mcp3204::Mcp3204(spi_inst *spi, uint8_t cs_pin) : m_spi(spi), m_cs_pin(cs_pin) {}
uint16_t Mcp3204::read(uint8_t channel) {
// Byte 1: '00000' to align the ADC's output,
@ -18,7 +14,9 @@ uint16_t Mcp3204::read(uint8_t channel) {
uint8_t data_out[3] = {0x06, static_cast<uint8_t>(channel << 6), 0x00};
uint8_t data_in[3] = {};
gpio_put(m_cs_pin, false);
spi_write_read_blocking(m_spi, data_out, data_in, 3);
gpio_put(m_cs_pin, true);
// The 12 result bits are at the end of the ADC's output.
return (static_cast<uint16_t>(data_in[1] & 0x0F) << 8) | data_in[2];

View File

@ -7,7 +7,49 @@
namespace Doncon::Peripherals {
Drum::Pad::Pad(uint8_t pin) : pin(pin), last_change(0), active(false) {}
Drum::InternalAdc::InternalAdc(const Drum::Config::AdcInputs &adc_inputs) {
adc_gpio_init(adc_inputs.don_left + 26);
adc_gpio_init(adc_inputs.don_right + 26);
adc_gpio_init(adc_inputs.ka_left + 26);
adc_gpio_init(adc_inputs.ka_right + 26);
adc_init();
}
uint16_t Drum::InternalAdc::read(uint8_t channel) {
adc_select_input(channel);
return adc_read();
}
Drum::ExternalAdc::ExternalAdc(const Drum::Config::Spi &spi_config) : m_mcp3204(spi_config.block, spi_config.scsn_pin) {
// Enable level shifter
gpio_init(0);
gpio_set_dir(0, GPIO_OUT);
gpio_put(0, true);
gpio_set_function(spi_config.miso_pin, GPIO_FUNC_SPI);
gpio_set_function(spi_config.mosi_pin, GPIO_FUNC_SPI);
gpio_set_function(spi_config.sclk_pin, GPIO_FUNC_SPI);
// gpio_set_function(spi_config.scsn_pin, GPIO_FUNC_SPI);
spi_init(spi_config.block, spi_config.speed_hz);
// Theoretically the ADC should work in SPI 1,1 mode.
// In this mode date will be clocked out on
// a falling edge and latched from the ADC on a rising edge.
// Also the CS will be held low in-between bytes as required
// by the mcp3204.
// However this mode causes glitches during continuous reading,
// so we need to use default mode and set CS manually.
// spi_set_format(m_spi, 8, SPI_CPOL_1, SPI_CPHA_1, SPI_MSB_FIRST);
gpio_init(spi_config.scsn_pin);
gpio_set_dir(spi_config.scsn_pin, GPIO_OUT);
gpio_put(spi_config.scsn_pin, true);
}
uint16_t Drum::ExternalAdc::read(uint8_t channel) { return m_mcp3204.read(channel); }
Drum::Pad::Pad(uint8_t channel) : channel(channel), last_change(0), active(false) {}
void Drum::Pad::setState(bool state, uint16_t debounce_delay) {
if (active == state) {
@ -23,16 +65,16 @@ void Drum::Pad::setState(bool state, uint16_t debounce_delay) {
}
Drum::Drum(const Config &config) : m_config(config) {
m_pads.emplace(Id::DON_LEFT, config.pins.don_left);
m_pads.emplace(Id::KA_LEFT, config.pins.ka_left);
m_pads.emplace(Id::DON_RIGHT, config.pins.don_right);
m_pads.emplace(Id::KA_RIGHT, config.pins.ka_right);
adc_init();
for (const auto &pad : m_pads) {
adc_gpio_init(pad.second.getPin());
if (m_config.use_external_adc) {
m_adc = std::make_unique<ExternalAdc>(config.external_adc_spi_config);
} else {
m_adc = std::make_unique<InternalAdc>(config.adc_inputs);
}
m_pads.emplace(Id::DON_LEFT, config.adc_inputs.don_left);
m_pads.emplace(Id::KA_LEFT, config.adc_inputs.ka_left);
m_pads.emplace(Id::DON_RIGHT, config.adc_inputs.don_right);
m_pads.emplace(Id::KA_RIGHT, config.adc_inputs.ka_right);
}
std::map<Drum::Id, uint16_t> Drum::sampleInputs() {
@ -40,8 +82,7 @@ std::map<Drum::Id, uint16_t> Drum::sampleInputs() {
for (uint8_t sample_number = 0; sample_number < m_config.sample_count; ++sample_number) {
for (const auto &pad : m_pads) {
adc_select_input(pad.second.getPin());
values[pad.first] += adc_read();
values[pad.first] += m_adc->read(pad.second.getChannel());
}
}