Skip to content

Commit

Permalink
feat(math): Refactor espp::RangeMapper<> to have center deadband an…
Browse files Browse the repository at this point in the history
…d range deadband and remove invert-input. Similar update to `espp::Joystick`. Fixed bug in `espp::Joystick` which introduced non-linearity when configured as a `CIRCULAR` joystick. (#261)

* feat(math): Refactor range mapper to have center deadband and range deadband; remove invert-input

* feat(joystick): update joystick to have range deadzone that operates on the vector similar to the rangemapper range deadzone.

* feat(joystick): update circularization and add test
* Refactor joystick to allow the get function to be null and instead to use the .update(raw_x, raw_y) function, and use new protected recalculate(raw_x, raw_y) function in both the update overloads
* Fix bug in circular joystick scaling which inadvertently squared the magnitude as opposed to setting the new magnitude
* Update joystick example to loop through array of raw values and print out the circular and square joystick outputs for verification

* fix(joystick): update doc and add missing storage of range_deadzone

* fix doc

* doc: update

* final doc update
  • Loading branch information
finger563 authored Jun 20, 2024
1 parent be68510 commit 1cc408e
Show file tree
Hide file tree
Showing 6 changed files with 375 additions and 217 deletions.
34 changes: 22 additions & 12 deletions components/controller/example/main/controller_example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,10 @@ extern "C" void app_main(void) {
std::vector<espp::AdcConfig> channels{
{.unit = ADC_UNIT_2,
.channel = ADC_CHANNEL_1, // (x) Analog 0 on the joystick shield
.attenuation = ADC_ATTEN_DB_11},
.attenuation = ADC_ATTEN_DB_12},
{.unit = ADC_UNIT_2,
.channel = ADC_CHANNEL_2, // (y) Analog 1 on the joystick shield
.attenuation = ADC_ATTEN_DB_11}};
.attenuation = ADC_ATTEN_DB_12}};
espp::OneshotAdc adc(espp::OneshotAdc::Config{
.unit = ADC_UNIT_2,
.channels = channels,
Expand Down Expand Up @@ -111,11 +111,16 @@ extern "C" void app_main(void) {
.gpio_start = 42, // D4 on the joystick shield
.gpio_select = 21, // D6 on the joystick shield
.gpio_joystick_select = -1, // D2 on the joystick shield
.joystick_config =
{.x_calibration = {.center = 0.0f, .deadband = 0.2f, .minimum = -1.0f, .maximum = 1.0f},
.y_calibration = {.center = 0.0f, .deadband = 0.2f, .minimum = -1.0f, .maximum = 1.0f},
.get_values = read_joystick,
.log_level = espp::Logger::Verbosity::WARN},
.joystick_config = {.x_calibration = {.center = 0.0f,
.center_deadband = 0.2f,
.minimum = -1.0f,
.maximum = 1.0f},
.y_calibration = {.center = 0.0f,
.center_deadband = 0.2f,
.minimum = -1.0f,
.maximum = 1.0f},
.get_values = read_joystick,
.log_level = espp::Logger::Verbosity::WARN},
.log_level = espp::Logger::Verbosity::WARN});
// and finally, make the task to periodically poll the controller and print
// the state
Expand Down Expand Up @@ -236,11 +241,16 @@ extern "C" void app_main(void) {
.gpio_start = 42, // pin 37 on the joybonnet
.gpio_select = 21, // pin 38 on the joybonnet
.gpio_joystick_select = -1,
.joystick_config =
{.x_calibration = {.center = 0.0f, .deadband = 0.2f, .minimum = -1.0f, .maximum = 1.0f},
.y_calibration = {.center = 0.0f, .deadband = 0.2f, .minimum = -1.0f, .maximum = 1.0f},
.get_values = read_joystick,
.log_level = espp::Logger::Verbosity::WARN},
.joystick_config = {.x_calibration = {.center = 0.0f,
.center_deadband = 0.2f,
.minimum = -1.0f,
.maximum = 1.0f},
.y_calibration = {.center = 0.0f,
.center_deadband = 0.2f,
.minimum = -1.0f,
.maximum = 1.0f},
.get_values = read_joystick,
.log_level = espp::Logger::Verbosity::WARN},
.log_level = espp::Logger::Verbosity::WARN});
// and finally, make the task to periodically poll the controller and print
// the state
Expand Down
50 changes: 50 additions & 0 deletions components/joystick/example/main/Kconfig.projbuild
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
menu "Example Configuration"

choice EXAMPLE_HARDWARE
prompt "Hardware"
default EXAMPLE_HARDWARE_QTPYPICO
help
Select the hardware to run this example on.

config EXAMPLE_HARDWARE_QTPYPICO
depends on IDF_TARGET_ESP32
bool "Qt Py PICO"

config EXAMPLE_HARDWARE_QTPYS3
depends on IDF_TARGET_ESP32S3
bool "Qt Py S3"

config EXAMPLE_HARDWARE_CUSTOM
bool "Custom"
endchoice

config EXAMPLE_ADC_UNIT
int "Example ADC Unit"
default 2 if EXAMPLE_HARDWARE_QTPYPICO
default 2 if EXAMPLE_HARDWARE_QTPYS3
range 1 2
help
The ADC unit number for the sensor. The ESP32 has two ADC units,
ADC_UNIT_1 and ADC_UNIT_2. Default is ADC UNIT 2.

config EXAMPLE_ADC_CHANNEL_X
int "Joystick X Axis ADC Channel"
range 0 50
default 9 if EXAMPLE_HARDWARE_QTPYPICO
default 7 if EXAMPLE_HARDWARE_QTPYS3
default 0 if EXAMPLE_HARDWARE_CUSTOM
help
ADC Channel for the X axis of the joystick. Default is ADC2 channel
9 (A0) for Qt Py PICO and ADC2 channel 7 (A0) for Qt Py ESP32S3.

config EXAMPLE_ADC_CHANNEL_Y
int "Joystick Y Axis ADC Channel"
range 0 50
default 8 if EXAMPLE_HARDWARE_QTPYPICO
default 6 if EXAMPLE_HARDWARE_QTPYS3
default 1 if EXAMPLE_HARDWARE_CUSTOM
help
ADC Channel for the Y axis of the joystick. Default is ADC2 channel
8 (A1) for Qt Py PICO and ADC2 channel 6 (A1) for Qt Py ESP32S3.

endmenu
163 changes: 105 additions & 58 deletions components/joystick/example/main/joystick_example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,66 +10,113 @@ using namespace std::chrono_literals;
extern "C" void app_main(void) {
{
fmt::print("Running joystick example\n");
//! [adc joystick example]
std::vector<espp::AdcConfig> channels{{.unit = ADC_UNIT_2,
.channel = ADC_CHANNEL_9, // Qt Py ESP32 PICO A0
.attenuation = ADC_ATTEN_DB_11},
{.unit = ADC_UNIT_2,
.channel = ADC_CHANNEL_8, // Qt Py ESP32 PICO A1
.attenuation = ADC_ATTEN_DB_11}};
espp::OneshotAdc adc({
.unit = ADC_UNIT_2,
.channels = channels,
});
auto read_joystick = [&adc, &channels](float *x, float *y) -> bool {
// this will be in mv
auto maybe_x_mv = adc.read_mv(channels[0]);
auto maybe_y_mv = adc.read_mv(channels[1]);
if (maybe_x_mv.has_value() && maybe_y_mv.has_value()) {
auto x_mv = maybe_x_mv.value();
auto y_mv = maybe_y_mv.value();
*x = (float)(x_mv);
*y = (float)(y_mv);
return true;
{
//! [circular joystick example]
float min = 0;
float max = 255;
float center = 127;
float deadband_percent = 0.1;
float deadband = deadband_percent * (max - min);

// circular joystick
espp::Joystick js1({
.x_calibration = {.center = center, .minimum = min, .maximum = max},
.y_calibration = {.center = center, .minimum = min, .maximum = max},
.type = espp::Joystick::Type::CIRCULAR,
.center_deadzone_radius = deadband_percent,
.range_deadzone = deadband_percent,
});
// square joystick (for comparison)
espp::Joystick js2({
.x_calibration = {.center = center,
.center_deadband = deadband,
.minimum = min,
.maximum = max,
.range_deadband = deadband},
.y_calibration = {.center = center,
.center_deadband = deadband,
.minimum = min,
.maximum = max,
.range_deadband = deadband},
});
// now make a loop where we update the raw valuse and print out the joystick values
fmt::print("raw x, raw y, js1 x, js1 y, js2 x, js2 y\n");
for (float x = min - 10.0f; x <= max + 10.0f; x += 10.0f) {
for (float y = min - 10.0f; y <= max + 10.0f; y += 10.0f) {
js1.update(x, y);
js2.update(x, y);
fmt::print("{}, {}, {}, {}, {}, {}\n", x, y, js1.x(), js1.y(), js2.x(), js2.y());
}
}
return false;
};
espp::Joystick js1({
// convert [0, 3300]mV to approximately [-1.0f, 1.0f]
.x_calibration =
{.center = 1700.0f, .deadband = 100.0f, .minimum = 0.0f, .maximum = 3300.0f},
.y_calibration =
{.center = 1700.0f, .deadband = 100.0f, .minimum = 0.0f, .maximum = 3300.0f},
.get_values = read_joystick,
});
espp::Joystick js2({
// convert [0, 3300]mV to approximately [-1.0f, 1.0f]
.x_calibration = {.center = 1700.0f, .deadband = 0.0f, .minimum = 0.0f, .maximum = 3300.0f},
.y_calibration = {.center = 1700.0f, .deadband = 0.0f, .minimum = 0.0f, .maximum = 3300.0f},
.type = espp::Joystick::Type::CIRCULAR,
.center_deadzone_radius = 0.1f,
.get_values = read_joystick,
});
auto task_fn = [&js1, &js2](std::mutex &m, std::condition_variable &cv) {
js1.update();
js2.update();
fmt::print("{}, {}\n", js1, js2);
// NOTE: sleeping in this way allows the sleep to exit early when the
// task is being stopped / destroyed
{
std::unique_lock<std::mutex> lk(m);
cv.wait_for(lk, 500ms);
//! [circular joystick example]
}
{
//! [adc joystick example]
static constexpr adc_unit_t ADC_UNIT = CONFIG_EXAMPLE_ADC_UNIT == 1 ? ADC_UNIT_1 : ADC_UNIT_2;
static constexpr adc_channel_t ADC_CHANNEL_X = (adc_channel_t)CONFIG_EXAMPLE_ADC_CHANNEL_X;
static constexpr adc_channel_t ADC_CHANNEL_Y = (adc_channel_t)CONFIG_EXAMPLE_ADC_CHANNEL_Y;

std::vector<espp::AdcConfig> channels{
{.unit = ADC_UNIT, .channel = ADC_CHANNEL_X, .attenuation = ADC_ATTEN_DB_12},
{.unit = ADC_UNIT, .channel = ADC_CHANNEL_Y, .attenuation = ADC_ATTEN_DB_12}};
espp::OneshotAdc adc({
.unit = ADC_UNIT_2,
.channels = channels,
});
auto read_joystick = [&adc, &channels](float *x, float *y) -> bool {
// this will be in mv
auto maybe_x_mv = adc.read_mv(channels[0]);
auto maybe_y_mv = adc.read_mv(channels[1]);
if (maybe_x_mv.has_value() && maybe_y_mv.has_value()) {
auto x_mv = maybe_x_mv.value();
auto y_mv = maybe_y_mv.value();
*x = (float)(x_mv);
*y = (float)(y_mv);
return true;
}
return false;
};
espp::Joystick js1({
// convert [0, 3300]mV to approximately [-1.0f, 1.0f]
.x_calibration =
{.center = 1700.0f, .center_deadband = 100.0f, .minimum = 0.0f, .maximum = 3300.0f},
.y_calibration =
{.center = 1700.0f, .center_deadband = 100.0f, .minimum = 0.0f, .maximum = 3300.0f},
.get_values = read_joystick,
});
espp::Joystick js2({
// convert [0, 3300]mV to approximately [-1.0f, 1.0f]
.x_calibration =
{.center = 1700.0f, .center_deadband = 0.0f, .minimum = 0.0f, .maximum = 3300.0f},
.y_calibration =
{.center = 1700.0f, .center_deadband = 0.0f, .minimum = 0.0f, .maximum = 3300.0f},
.type = espp::Joystick::Type::CIRCULAR,
.center_deadzone_radius = 0.1f,
.get_values = read_joystick,
});
auto task_fn = [&js1, &js2](std::mutex &m, std::condition_variable &cv) {
js1.update();
js2.update();
fmt::print("{}, {}\n", js1, js2);
// NOTE: sleeping in this way allows the sleep to exit early when the
// task is being stopped / destroyed
{
std::unique_lock<std::mutex> lk(m);
cv.wait_for(lk, 500ms);
}
// don't want to stop the task
return false;
};
auto task = espp::Task(
{.name = "Joystick", .callback = task_fn, .log_level = espp::Logger::Verbosity::INFO});
fmt::print("js1 x, js1 y, js2 x, js2 y\n");
task.start();
//! [adc joystick example]

// loop forever to let the task run and user to play with the joystick
while (true) {
std::this_thread::sleep_for(1s);
}
// don't want to stop the task
return false;
};
auto task = espp::Task(
{.name = "Joystick", .callback = task_fn, .log_level = espp::Logger::Verbosity::INFO});
fmt::print("js1 x, js1 y, js2 x, js2 y\n");
task.start();
//! [adc joystick example]
while (true) {
std::this_thread::sleep_for(1s);
}
}

Expand Down
Loading

0 comments on commit 1cc408e

Please sign in to comment.