Rework Drum to use ADC inputs

This commit is contained in:
Frederik Walk 2023-05-13 15:27:43 +02:00
parent 43aa53382c
commit 528172c2ca
7 changed files with 169 additions and 95 deletions

View File

@ -22,7 +22,7 @@ target_include_directories(${PROJECT_NAME}
PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include)
target_link_libraries(
${PROJECT_NAME} PUBLIC tinyusb_device tinyusb_board pico_stdlib
${PROJECT_NAME} PUBLIC tinyusb_device tinyusb_board pico_stdlib hardware_adc
pico_multicore pio_ws2812)
pico_enable_stdio_usb(${PROJECT_NAME} 1)

View File

@ -9,17 +9,14 @@ namespace Doncon::Config::Default {
const Peripherals::Drum::Config drum_config = {
// Pin config
{
4, // Don Left Weak
3, // Ka Left Weak
2, // Don Right Weak
1, // Ka Right Weak
6, // Don Left Strong
29, // Ka Left Strong
7, // Don Right Strong
0, // Ka Right Strong
0, // Don Left
1, // Ka Left
2, // Don Right
3, // Ka Right
},
10, // Debounce delay in milliseconds
400, // Trigger threshold
100, // Double hit threshold
17, // Debounce delay in milliseconds
};
const Peripherals::StatusLed::Config led_config = {

View File

@ -12,53 +12,46 @@ class Drum {
public:
struct Config {
struct {
uint8_t don_left_weak;
uint8_t ka_left_weak;
uint8_t don_right_weak;
uint8_t ka_right_weak;
uint8_t don_left_strong;
uint8_t ka_left_strong;
uint8_t don_right_strong;
uint8_t ka_right_strong;
uint8_t don_left;
uint8_t ka_left;
uint8_t don_right;
uint8_t ka_right;
} pins;
uint8_t debounce_delay_ms;
uint16_t trigger_threshold;
uint16_t double_hit_threshold;
uint16_t debounce_delay_ms;
};
private:
enum class Id {
DON_LEFT_WEAK,
KA_LEFT_WEAK,
DON_RIGHT_WEAK,
KA_RIGHT_WEAK,
DON_LEFT_STRONG,
KA_LEFT_STRONG,
DON_RIGHT_STRONG,
KA_RIGHT_STRONG,
DON_LEFT,
KA_LEFT,
DON_RIGHT,
KA_RIGHT,
};
class Pad {
private:
uint8_t gpio_pin;
uint32_t gpio_mask;
uint8_t pin;
uint32_t last_change;
bool active;
public:
Pad(uint8_t pin);
uint8_t getGpioPin() const { return gpio_pin; };
uint32_t getGpioMask() const { return gpio_mask; };
uint8_t getPin() const { return pin; };
bool getState() const { return active; };
void setState(bool state, uint8_t debounce_delay);
void setState(bool state, uint16_t debounce_delay);
};
Config m_config;
std::map<Id, Pad> m_pads;
private:
std::map<Id, uint16_t> sampleInputs(uint8_t count, uint16_t delay_us);
public:
Drum(const Config &config);

View File

@ -12,7 +12,12 @@ namespace Doncon::Utils {
struct InputState {
public:
struct Drum {
bool don_left, ka_left, don_right, ka_right;
struct Pad {
bool triggered;
uint16_t raw;
};
Pad don_left, ka_left, don_right, ka_right;
};
public:

View File

@ -1,13 +1,15 @@
#include "peripherals/Drum.h"
#include "hardware/gpio.h"
#include "hardware/adc.h"
#include "pico/time.h"
#include <algorithm>
namespace Doncon::Peripherals {
Drum::Pad::Pad(uint8_t pin) : gpio_pin(pin), gpio_mask(1 << pin), last_change(0), active(false) {}
Drum::Pad::Pad(uint8_t pin) : pin(pin), last_change(0), active(false) {}
void Drum::Pad::setState(bool state, uint8_t debounce_delay) {
void Drum::Pad::setState(bool state, uint16_t debounce_delay) {
if (active == state) {
return;
}
@ -21,43 +23,111 @@ void Drum::Pad::setState(bool state, uint8_t debounce_delay) {
}
Drum::Drum(const Config &config) : m_config(config) {
m_pads.emplace(Id::DON_LEFT_WEAK, config.pins.don_left_weak);
m_pads.emplace(Id::KA_LEFT_WEAK, config.pins.ka_left_weak);
m_pads.emplace(Id::DON_RIGHT_WEAK, config.pins.don_right_weak);
m_pads.emplace(Id::KA_RIGHT_WEAK, config.pins.ka_right_weak);
m_pads.emplace(Id::DON_LEFT_STRONG, config.pins.don_left_strong);
m_pads.emplace(Id::KA_LEFT_STRONG, config.pins.ka_left_strong);
m_pads.emplace(Id::DON_RIGHT_STRONG, config.pins.don_right_strong);
m_pads.emplace(Id::KA_RIGHT_STRONG, config.pins.ka_right_strong);
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);
for (const auto &button : m_pads) {
gpio_init(button.second.getGpioPin());
gpio_set_dir(button.second.getGpioPin(), GPIO_IN);
gpio_pull_up(button.second.getGpioPin());
adc_init();
for (const auto &pad : m_pads) {
adc_gpio_init(pad.second.getPin());
}
}
void Drum::updateInputState(Utils::InputState &input_state) {
uint32_t gpio_state = ~gpio_get_all();
const auto raw_values = sampleInputs(5, 100);
for (auto &button : m_pads) {
button.second.setState(gpio_state & button.second.getGpioMask(), m_config.debounce_delay_ms);
for (const auto &val : raw_values) {
switch (val.first) {
case Id::DON_LEFT:
input_state.drum.don_left.raw = val.second;
break;
case Id::KA_LEFT:
input_state.drum.ka_left.raw = val.second;
break;
case Id::DON_RIGHT:
input_state.drum.don_right.raw = val.second;
break;
case Id::KA_RIGHT:
input_state.drum.ka_right.raw = val.second;
break;
}
}
input_state.drum.don_left = m_pads.at(Id::DON_LEFT_WEAK).getState() || //
m_pads.at(Id::DON_LEFT_STRONG).getState() || //
m_pads.at(Id::DON_RIGHT_STRONG).getState();
auto hardest_hit = *std::max_element(raw_values.begin(), raw_values.end(),
[](const auto a, const auto b) { return a.second < b.second; });
input_state.drum.ka_left = m_pads.at(Id::KA_LEFT_WEAK).getState() || //
m_pads.at(Id::KA_LEFT_STRONG).getState() || //
m_pads.at(Id::KA_RIGHT_STRONG).getState();
if (hardest_hit.second > m_config.trigger_threshold) {
m_pads.at(hardest_hit.first).setState(true, m_config.debounce_delay_ms);
input_state.drum.don_right = m_pads.at(Id::DON_RIGHT_WEAK).getState() || //
m_pads.at(Id::DON_LEFT_STRONG).getState() || //
m_pads.at(Id::DON_RIGHT_STRONG).getState();
auto set_twin = [&](const Id twin) {
if ((raw_values.at(twin) > m_config.trigger_threshold) &&
std::abs(static_cast<int32_t>(hardest_hit.second) - raw_values.at(twin)) <
m_config.double_hit_threshold) {
input_state.drum.ka_right = m_pads.at(Id::KA_RIGHT_WEAK).getState() || //
m_pads.at(Id::KA_LEFT_STRONG).getState() || //
m_pads.at(Id::KA_RIGHT_STRONG).getState();
m_pads.at(twin).setState(true, m_config.debounce_delay_ms);
} else {
m_pads.at(twin).setState(false, m_config.debounce_delay_ms);
}
};
switch (hardest_hit.first) {
case Id::DON_LEFT:
set_twin(Id::DON_RIGHT);
m_pads.at(Id::KA_LEFT).setState(false, m_config.debounce_delay_ms);
m_pads.at(Id::KA_RIGHT).setState(false, m_config.debounce_delay_ms);
break;
case Id::KA_LEFT:
set_twin(Id::KA_RIGHT);
m_pads.at(Id::DON_LEFT).setState(false, m_config.debounce_delay_ms);
m_pads.at(Id::DON_RIGHT).setState(false, m_config.debounce_delay_ms);
break;
case Id::DON_RIGHT:
set_twin(Id::DON_LEFT);
m_pads.at(Id::KA_LEFT).setState(false, m_config.debounce_delay_ms);
m_pads.at(Id::KA_RIGHT).setState(false, m_config.debounce_delay_ms);
break;
case Id::KA_RIGHT:
set_twin(Id::KA_LEFT);
m_pads.at(Id::DON_LEFT).setState(false, m_config.debounce_delay_ms);
m_pads.at(Id::DON_RIGHT).setState(false, m_config.debounce_delay_ms);
break;
}
} else {
m_pads.at(Id::DON_LEFT).setState(false, m_config.debounce_delay_ms);
m_pads.at(Id::DON_RIGHT).setState(false, m_config.debounce_delay_ms);
m_pads.at(Id::KA_LEFT).setState(false, m_config.debounce_delay_ms);
m_pads.at(Id::KA_RIGHT).setState(false, m_config.debounce_delay_ms);
}
input_state.drum.don_left.triggered = m_pads.at(Id::DON_LEFT).getState();
input_state.drum.ka_left.triggered = m_pads.at(Id::KA_LEFT).getState();
input_state.drum.don_right.triggered = m_pads.at(Id::DON_RIGHT).getState();
input_state.drum.ka_right.triggered = m_pads.at(Id::KA_RIGHT).getState();
}
std::map<Drum::Id, uint16_t> Drum::sampleInputs(uint8_t count, uint16_t delay_us) {
std::map<Id, uint32_t> values;
delay_us = (delay_us - 8) > 0 ? (delay_us - 8) : 0; // Each sample takes 4 * 2µs
for (uint8_t sample_number = 0; sample_number < count; ++sample_number) {
for (const auto &pad : m_pads) {
adc_select_input(pad.second.getPin());
values[pad.first] += adc_read();
}
sleep_us(delay_us);
}
// Take average of all samples
std::map<Id, uint16_t> result;
for (auto &value : values) {
result[value.first] = value.second / count;
}
return result;
}
} // namespace Doncon::Peripherals

View File

@ -24,25 +24,25 @@ void StatusLed::update() {
uint8_t num_colors = 0;
if (m_input_state.drum.don_left) {
if (m_input_state.drum.don_left.triggered) {
mixed_red += m_config.don_left_color.r;
mixed_green += m_config.don_left_color.g;
mixed_blue += m_config.don_left_color.b;
num_colors++;
}
if (m_input_state.drum.ka_left) {
if (m_input_state.drum.ka_left.triggered) {
mixed_red += m_config.ka_left_color.r;
mixed_green += m_config.ka_left_color.g;
mixed_blue += m_config.ka_left_color.b;
num_colors++;
}
if (m_input_state.drum.don_right) {
if (m_input_state.drum.don_right.triggered) {
mixed_red += m_config.don_right_color.r;
mixed_green += m_config.don_right_color.g;
mixed_blue += m_config.don_right_color.b;
num_colors++;
}
if (m_input_state.drum.ka_right) {
if (m_input_state.drum.ka_right.triggered) {
mixed_red += m_config.ka_right_color.r;
mixed_green += m_config.ka_right_color.g;
mixed_blue += m_config.ka_right_color.b;

View File

@ -1,12 +1,13 @@
#include "utils/InputState.h"
#include <iomanip>
#include <sstream>
namespace Doncon::Utils {
InputState::InputState()
: drum({false, false, false, false}), m_xinput_report({0x00, sizeof(xinput_report_t), 0, 0, 0, 0, 0, 0, 0, 0, {}}) {
}
: drum({{false, 0}, {false, 0}, {false, 0}, {false, 0}}),
m_xinput_report({0x00, sizeof(xinput_report_t), 0, 0, 0, 0, 0, 0, 0, 0, {}}) {}
usb_report_t InputState::getReport(usb_mode_t mode) {
switch (mode) {
@ -27,24 +28,24 @@ usb_report_t InputState::getReport(usb_mode_t mode) {
}
usb_report_t InputState::getXinputReport() {
m_xinput_report.buttons1 = 0 //
| (false ? (1 << 0) : 0) // Dpad Up
| (drum.don_left ? (1 << 1) : 0) // Dpad Down
| (drum.ka_left ? (1 << 2) : 0) // Dpad Left
| (false ? (1 << 3) : 0) // Dpad Right
| (false ? (1 << 4) : 0) // Start
| (false ? (1 << 5) : 0) // Select
| (false ? (1 << 6) : 0) // L3
| (false ? (1 << 7) : 0); // R3
m_xinput_report.buttons1 = 0 //
| (false ? (1 << 0) : 0) // Dpad Up
| (drum.don_left.triggered ? (1 << 1) : 0) // Dpad Down
| (drum.ka_left.triggered ? (1 << 2) : 0) // Dpad Left
| (false ? (1 << 3) : 0) // Dpad Right
| (false ? (1 << 4) : 0) // Start
| (false ? (1 << 5) : 0) // Select
| (false ? (1 << 6) : 0) // L3
| (false ? (1 << 7) : 0); // R3
m_xinput_report.buttons2 = 0 //
| (false ? (1 << 0) : 0) // L1
| (false ? (1 << 1) : 0) // R1
| (false ? (1 << 2) : 0) // Guide
| (drum.don_right ? (1 << 4) : 0) // A
| (drum.ka_right ? (1 << 5) : 0) // B
| (false ? (1 << 6) : 0) // X
| (false ? (1 << 7) : 0); // Y
m_xinput_report.buttons2 = 0 //
| (false ? (1 << 0) : 0) // L1
| (false ? (1 << 1) : 0) // R1
| (false ? (1 << 2) : 0) // Guide
| (drum.don_right.triggered ? (1 << 4) : 0) // A
| (drum.ka_right.triggered ? (1 << 5) : 0) // B
| (false ? (1 << 6) : 0) // X
| (false ? (1 << 7) : 0); // Y
m_xinput_report.lt = 0;
m_xinput_report.rt = 0;
@ -60,11 +61,19 @@ usb_report_t InputState::getXinputReport() {
usb_report_t InputState::getDebugReport() {
std::stringstream out;
out << "Ka Left: " << drum.ka_left << " " //
<< "Don Left: " << drum.don_left << " " //
<< "Don Right: " << drum.don_right << " " //
<< "Ka Right: " << drum.ka_right << " " //
<< "\r";
auto bar = [](uint16_t val) { return std::string(val / 512, '#'); };
if (drum.don_left.triggered || drum.ka_left.triggered || drum.don_right.triggered || drum.ka_right.triggered) {
out << "(" << (drum.ka_left.triggered ? "*" : " ") << "( " //
<< std::setw(4) << drum.ka_left.raw << "[" << std::setw(8) << bar(drum.ka_left.raw) << "]" //
<< "(" << (drum.don_left.triggered ? "*" : " ") << "| " //
<< std::setw(4) << drum.don_left.raw << "[" << std::setw(8) << bar(drum.don_left.raw) << "]" //
<< "|" << (drum.don_right.triggered ? "*" : " ") << ") " //
<< std::setw(4) << drum.don_right.raw << "[" << std::setw(8) << bar(drum.don_right.raw) << "]" //
<< "(" << (drum.ka_right.triggered ? "*" : " ") << "( " << std::setw(4) << drum.ka_right.raw << "[" //
<< std::setw(8) << bar(drum.ka_right.raw) << "]" //
<< "\n";
}
m_debug_report = out.str();