Controller APIs
The Controller class provides a convenience for reading multiple GPIOs at once and mapping their state to common controller buttons. It can optionally be configured to support joystick select, as well as to convert analog joystick values into digital directional values (up/down/left/right). It can also be used for just a subset of the buttons, should you wish to do so, by providing the GPIO configuration for the unused buttons to be -1.
API Reference
Header File
Classes
-
class Controller : public espp::BaseComponent
Class for managing controller input.
The controller can be configured to either use a digital d-pad or an analog 2-axis joystick with select button.
Digital configuration can support ABXY, start, select, and 4 digital directional inputs.
Anaolg Joystick Configuration can support ABXY, start, select, two axis (analog) joystick, and joystick select button. It will also convert the joystick analog values into digital d-pad buttons.
Digital Controller Example
// make the controller - NOTE: this was designed for connecting the Sparkfun // Joystick Shield to the ESP32 S3 BOX espp::Controller controller(espp::Controller::DigitalConfig{ // buttons short to ground, so they are active low. this will enable the // GPIO_PULLUP and invert the logic .active_low = true, .gpio_a = 38, // D3 on the joystick shield .gpio_b = 39, // D5 on the joystick shield .gpio_start = 42, // D4 on the joystick shield .gpio_select = 21, // D6 on the joystick shield .log_level = espp::Logger::Verbosity::WARN}); // and finally, make the task to periodically poll the controller and print // the state auto task_fn = [&quit_test, &controller](std::mutex &m, std::condition_variable &cv) { controller.update(); bool is_a_pressed = controller.is_pressed(espp::Controller::Button::A); bool is_b_pressed = controller.is_pressed(espp::Controller::Button::B); bool is_select_pressed = controller.is_pressed(espp::Controller::Button::SELECT); bool is_start_pressed = controller.is_pressed(espp::Controller::Button::START); fmt::print("Controller buttons:\n" "\tA: {}\n" "\tB: {}\n" "\tSelect: {}\n" "\tStart: {}\n", is_a_pressed, is_b_pressed, is_select_pressed, is_start_pressed); quit_test = is_start_pressed && is_select_pressed; // 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); } // we don't want to stop, so return false return false; }; auto task = espp::Task({.callback = task_fn, .task_config = { .name = "Controller Task", .stack_size_bytes = 6 * 1024, }, .log_level = espp::Logger::Verbosity::WARN}); task.start();
Analog Controller Example
// make the adc we'll be reading from std::vector<espp::AdcConfig> channels{ {.unit = ADC_UNIT_2, .channel = ADC_CHANNEL_1, // (x) Analog 0 on the joystick shield .attenuation = ADC_ATTEN_DB_12}, {.unit = ADC_UNIT_2, .channel = ADC_CHANNEL_2, // (y) Analog 1 on the joystick shield .attenuation = ADC_ATTEN_DB_12}}; espp::OneshotAdc adc(espp::OneshotAdc::Config{ .unit = ADC_UNIT_2, .channels = channels, }); // make the function which will get the raw data from the ADC and convert to // uncalibrated [-1,1] auto read_joystick = [&adc, &channels](float *x, float *y) -> bool { 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 = (x_mv / 1700.0f - 1.0f); *y = (y_mv / 1700.0f - 1.0f); return true; } return false; }; // make the controller - NOTE: this was designed for connecting the Sparkfun // Joystick Shield to the ESP32 S3 BOX espp::Controller controller(espp::Controller::AnalogJoystickConfig{ // buttons short to ground, so they are active low. this will enable the // GPIO_PULLUP and invert the logic .active_low = true, .gpio_a = 38, // D3 on the joystick shield .gpio_b = 39, // D5 on the joystick shield .gpio_x = -1, // we're using this as start... .gpio_y = -1, // we're using this as select... .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, .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 auto task_fn = [&quit_test, &controller](std::mutex &m, std::condition_variable &cv) { controller.update(); bool is_a_pressed = controller.is_pressed(espp::Controller::Button::A); bool is_b_pressed = controller.is_pressed(espp::Controller::Button::B); bool is_select_pressed = controller.is_pressed(espp::Controller::Button::SELECT); bool is_start_pressed = controller.is_pressed(espp::Controller::Button::START); bool is_up_pressed = controller.is_pressed(espp::Controller::Button::UP); bool is_down_pressed = controller.is_pressed(espp::Controller::Button::DOWN); bool is_left_pressed = controller.is_pressed(espp::Controller::Button::LEFT); bool is_right_pressed = controller.is_pressed(espp::Controller::Button::RIGHT); fmt::print("Controller buttons:\n" "\tA: {}\n" "\tB: {}\n" "\tSelect: {}\n" "\tStart: {}\n" "\tUp: {}\n" "\tDown: {}\n" "\tLeft: {}\n" "\tRight: {}\n", is_a_pressed, is_b_pressed, is_select_pressed, is_start_pressed, is_up_pressed, is_down_pressed, is_left_pressed, is_right_pressed); quit_test = is_start_pressed && is_select_pressed; // 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); } // we don't want to stop, so return false return false; }; auto task = espp::Task({.callback = task_fn, .task_config = { .name = "Controller Task", .stack_size_bytes = 6 * 1024, }, .log_level = espp::Logger::Verbosity::WARN}); task.start();
I2C Analog Controller Example
// make the I2C that we'll use to communicate espp::I2c i2c({ .port = I2C_NUM_1, .sda_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SDA_GPIO, .scl_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SCL_GPIO, }); // make the actual ads class espp::Ads1x15 ads(espp::Ads1x15::Ads1015Config{ .write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), .read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)}); // make the task which will get the raw data from the I2C ADC and convert to // uncalibrated [-1,1] std::atomic<float> joystick_x{0}; std::atomic<float> joystick_y{0}; auto ads_read_task_fn = [&joystick_x, &joystick_y, &ads](std::mutex &m, std::condition_variable &cv) { // NOTE: sleeping in this way allows the sleep to exit early when the // task is being stopped / destroyed { using namespace std::chrono_literals; std::unique_lock<std::mutex> lk(m); cv.wait_for(lk, 20ms); } std::error_code ec; auto x_mv = ads.sample_mv(1, ec); if (ec) { fmt::print("error reading x: {}\n", ec.message()); return false; } auto y_mv = ads.sample_mv(0, ec); if (ec) { fmt::print("error reading y: {}\n", ec.message()); return false; } joystick_x.store(x_mv / 1700.0f - 1.0f); // y is inverted so negate it joystick_y.store(-(y_mv / 1700.0f - 1.0f)); // we don't want to stop, so return false return false; }; auto ads_task = espp::Task::make_unique({.callback = ads_read_task_fn, .task_config = { .name = "ADS Task", .stack_size_bytes{4 * 1024}, }, .log_level = espp::Logger::Verbosity::INFO}); ads_task->start(); // make the read joystick function used by the controller auto read_joystick = [&joystick_x, &joystick_y](float *x, float *y) -> bool { *x = joystick_x.load(); *y = joystick_y.load(); return true; }; // make the controller - NOTE: this was designed for connecting the Adafruit // JoyBonnet to the ESP32 S3 BOX espp::Controller controller(espp::Controller::AnalogJoystickConfig{ // buttons short to ground, so they are active low. this will enable the // GPIO_PULLUP and invert the logic .active_low = true, .gpio_a = 38, // pin 32 on the joybonnet .gpio_b = 39, // pin 31 on the joybonnet .gpio_x = -1, // pin 36 on the joybonnet .gpio_y = -1, // pin 33 on the joybonnet .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, .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 auto task_fn = [&quit_test, &controller](std::mutex &m, std::condition_variable &cv) { controller.update(); bool is_a_pressed = controller.is_pressed(espp::Controller::Button::A); bool is_b_pressed = controller.is_pressed(espp::Controller::Button::B); bool is_select_pressed = controller.is_pressed(espp::Controller::Button::SELECT); bool is_start_pressed = controller.is_pressed(espp::Controller::Button::START); bool is_up_pressed = controller.is_pressed(espp::Controller::Button::UP); bool is_down_pressed = controller.is_pressed(espp::Controller::Button::DOWN); bool is_left_pressed = controller.is_pressed(espp::Controller::Button::LEFT); bool is_right_pressed = controller.is_pressed(espp::Controller::Button::RIGHT); fmt::print("Controller buttons:\n" "\tA: {}\n" "\tB: {}\n" "\tSelect: {}\n" "\tStart: {}\n" "\tUp: {}\n" "\tDown: {}\n" "\tLeft: {}\n" "\tRight: {}\n", is_a_pressed, is_b_pressed, is_select_pressed, is_start_pressed, is_up_pressed, is_down_pressed, is_left_pressed, is_right_pressed); quit_test = is_start_pressed && is_select_pressed; // 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); } // we don't want to stop, return false return false; }; auto task = espp::Task({.callback = task_fn, .task_config = { .name = "Controller Task", .stack_size_bytes = 6 * 1024, }, .log_level = espp::Logger::Verbosity::WARN}); task.start();
Public Types
Public Functions
-
inline explicit Controller(const DigitalConfig &config)
Create a Digital controller.
-
inline explicit Controller(const AnalogJoystickConfig &config)
Create an analog joystick controller.
-
inline explicit Controller(const DualConfig &config)
Create a dual d-pad + analog joystick controller.
-
inline ~Controller()
Destroys the controller and deletes the associated dedicated GPIO bundle.
-
inline void update()
Read the current button values and update the internal input state accordingly.
-
inline const std::string &get_name() const
Get the name of the component
Note
This is the tag of the logger
- Returns
A const reference to the name of the component
-
inline void set_log_tag(const std::string_view &tag)
Set the tag for the logger
- Parameters
tag – The tag to use for the logger
-
inline espp::Logger::Verbosity get_log_level() const
Get the log level for the logger
See also
See also
- Returns
The verbosity level of the logger
-
inline void set_log_level(espp::Logger::Verbosity level)
Set the log level for the logger
See also
See also
- Parameters
level – The verbosity level to use for the logger
-
inline void set_log_verbosity(espp::Logger::Verbosity level)
Set the log verbosity for the logger
See also
See also
See also
Note
This is a convenience method that calls set_log_level
- Parameters
level – The verbosity level to use for the logger
-
inline espp::Logger::Verbosity get_log_verbosity() const
Get the log verbosity for the logger
See also
See also
See also
Note
This is a convenience method that calls get_log_level
- Returns
The verbosity level of the logger
-
inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
Set the rate limit for the logger
See also
Note
Only calls to the logger that have _rate_limit suffix will be rate limited
- Parameters
rate_limit – The rate limit to use for the logger
-
struct AnalogJoystickConfig
Configuration for the controller to be used with a joystick (which has a center-press / select button), no d-pad.
Note
In this configuration, the controller will create and manage a joystick component to read the analog axes of the joystick and convert them into digital up/down/left/right signals as a virtual d-pad.
Public Members
-
bool active_low = {true}
Whether the buttons are active-low (default) or not.
-
int gpio_a = {-1}
GPIO for the A button.
-
int gpio_b = {-1}
GPIO for the B button.
-
int gpio_x = {-1}
GPIO for the X button.
-
int gpio_y = {-1}
GPIO for the Y button.
-
int gpio_start = {-1}
GPIO for the START button.
-
int gpio_select = {-1}
GPIO for the SELECT button.
-
int gpio_joystick_select = {-1}
GPIO for the JOYSTICK SELECT button.
-
bool active_low = {true}
-
struct DigitalConfig
Configuration for the controller to use d-pad only, no joystick.
Public Members
-
bool active_low = {true}
Whether the buttons are active-low (default) or not.
-
int gpio_a = {-1}
GPIO for the A button.
-
int gpio_b = {-1}
GPIO for the B button.
-
int gpio_x = {-1}
GPIO for the X button.
-
int gpio_y = {-1}
GPIO for the Y button.
-
int gpio_start = {-1}
GPIO for the START button.
-
int gpio_select = {-1}
GPIO for the SELECT button.
-
int gpio_up = {-1}
GPIO for the UP button.
-
int gpio_down = {-1}
GPIO for the DOWN button.
-
int gpio_left = {-1}
GPIO for the LEFT button.
-
int gpio_right = {-1}
GPIO for the RIGHT button.
-
bool active_low = {true}
-
struct DualConfig
Configuration for the controller to be used with both a joystick (which has a center-press / select button), and a d-pad.
Note
In this configuration, the controller will NOT create / manage a joystick component. The configured d-pad provides the digital directions so the analog joystick values should be retrieved from a separately managed joystick component.
Public Members
-
bool active_low = {true}
Whether the buttons are active-low (default) or not.
-
int gpio_a = {-1}
GPIO for the A button.
-
int gpio_b = {-1}
GPIO for the B button.
-
int gpio_x = {-1}
GPIO for the X button.
-
int gpio_y = {-1}
GPIO for the Y button.
-
int gpio_start = {-1}
GPIO for the START button.
-
int gpio_select = {-1}
GPIO for the SELECT button.
-
int gpio_up = {-1}
GPIO for the UP button.
-
int gpio_down = {-1}
GPIO for the DOWN button.
-
int gpio_left = {-1}
GPIO for the LEFT button.
-
int gpio_right = {-1}
GPIO for the RIGHT button.
-
int gpio_joystick_select = {-1}
GPIO for the JOYSTICK SELECT button.
-
bool active_low = {true}
-
struct State
Packed bit structure containing the state (pressed=1) of each button.
Public Members
-
inline explicit Controller(const DigitalConfig &config)