M5Stack Tab5

M5Stack-Tab5

The M5Stack Tab5 is a development board for the ESP32-P4 microprocessor. It features a nice touchscreen display, a speaker, microphones, camera, uSD card, usb-a, usb-c, 3.5mm headphone, lithium-ion battery, and a host of expansion headers.

The espp::M5StackTab5 component provides a singleton hardware abstraction for initializing the touch, display, and audio subsystems.

API Reference

Header File

Classes

class M5StackTab5 : public espp::BaseComponent

The M5StackTab5 class provides an interface to the M5Stack Tab5 development board.

The class provides access to the following features:

  • 5” 720p MIPI-DSI Display with GT911 multi-touch

  • Dual audio codecs (ES8388 + ES7210 AEC)

  • BMI270 6-axis IMU sensor

  • SC2356 2MP camera via MIPI-CSI (not yet implemented)

  • ESP32-C6 wireless module (Wi-Fi 6, Thread, ZigBee)

  • USB-A Host and USB-C OTG ports

  • RS-485 industrial interface (not yet implemented)

  • Grove and M5-Bus expansion headers (not yet implemented)

  • microSD card slot

  • NP-F550 removable battery with battery management via INA226

  • Real-time clock (RX8130CE)

  • Multiple buttons and interrupts

The class is a singleton and can be accessed using the get() method.

Example

  espp::M5StackTab5 &tab5 = espp::M5StackTab5::get();
  // tab5.set_log_level(espp::Logger::Verbosity::DEBUG);
  logger.info("Running on M5Stack Tab5");

  // first let's get the internal i2c bus and probe for all devices on the bus
  logger.info("Probing internal I2C bus...");
  auto &i2c = tab5.internal_i2c();
  std::vector<uint8_t> found_addresses;
  for (uint8_t address = 1; address < 128; address++) {
    if (i2c.probe_device(address)) {
      found_addresses.push_back(address);
    }
  }
  logger.info("Found devices at addresses: {::#02x}", found_addresses);

  // Initialize the IO expanders
  logger.info("Initializing IO expanders...");
  if (!tab5.initialize_io_expanders()) {
    logger.error("Failed to initialize IO expanders!");
    return;
  }

  logger.info("Initializing lcd...");
  // initialize the LCD
  if (!tab5.initialize_lcd()) {
    logger.error("Failed to initialize LCD!");
    return;
  }

  // initialize the display with a pixel buffer (Tab5 is 1280x720 with 2 bytes per pixel)
  logger.info("Initializing display...");
  auto pixel_buffer_size = tab5.display_width() * 10; // tab5.display_height();
  if (!tab5.initialize_display(pixel_buffer_size)) {
    logger.error("Failed to initialize display!");
    return;
  }

  auto touch_callback = [&](const auto &touch) {
    // NOTE: since we're directly using the touchpad data, and not using the
    // TouchpadInput + LVGL, we'll need to ensure the touchpad data is
    // converted into proper screen coordinates instead of simply using the
    // raw values.
    static auto previous_touchpad_data = tab5.touchpad_convert(touch);
    auto touchpad_data = tab5.touchpad_convert(touch);
    if (touchpad_data != previous_touchpad_data) {
      logger.info("Touch: {}", touchpad_data);
      previous_touchpad_data = touchpad_data;
      // if the button is pressed, clear the circles
      if (touchpad_data.btn_state) {
        std::lock_guard<std::recursive_mutex> lock(lvgl_mutex);
        clear_circles();
      }
      // if there is a touch point, draw a circle and play a click sound
      if (touchpad_data.num_touch_points > 0) {
        play_click(tab5);
        std::lock_guard<std::recursive_mutex> lock(lvgl_mutex);
        draw_circle(touchpad_data.x, touchpad_data.y, 10);
      }
    }
  };

  logger.info("Initializing touch...");
  if (!tab5.initialize_touch(touch_callback)) {
    logger.error("Failed to initialize touch!");
    return;
  }

  // make the filter we'll use for the IMU to compute the orientation
  static constexpr float angle_noise = 0.001f;
  static constexpr float rate_noise = 0.1f;
  static espp::KalmanFilter<2> kf;
  kf.set_process_noise(rate_noise);
  kf.set_measurement_noise(angle_noise);
  static constexpr float beta = 0.5f; // higher = more accelerometer, lower = more gyro
  static espp::MadgwickFilter f(beta);

  using Imu = espp::M5StackTab5::Imu;
  auto kalman_filter_fn = [](float dt, const Imu::Value &accel,
                             const Imu::Value &gyro) -> Imu::Value {
    // Apply Kalman filter
    float accelRoll = atan2(accel.y, accel.z);
    float accelPitch = atan2(-accel.x, sqrt(accel.y * accel.y + accel.z * accel.z));
    kf.predict({espp::deg_to_rad(gyro.x), espp::deg_to_rad(gyro.y)}, dt);
    kf.update({accelRoll, accelPitch});
    float roll, pitch;
    std::tie(roll, pitch) = kf.get_state();
    // return the computed orientation
    Imu::Value orientation{};
    orientation.roll = roll;
    orientation.pitch = pitch;
    orientation.yaw = 0.0f;
    return orientation;
  };

  auto madgwick_filter_fn = [](float dt, const Imu::Value &accel,
                               const Imu::Value &gyro) -> Imu::Value {
    // Apply Madgwick filter
    f.update(dt, accel.x, accel.y, accel.z, espp::deg_to_rad(gyro.x), espp::deg_to_rad(gyro.y),
             espp::deg_to_rad(gyro.z));
    float roll, pitch, yaw;
    f.get_euler(roll, pitch, yaw);
    // return the computed orientation
    Imu::Value orientation{};
    orientation.roll = espp::deg_to_rad(roll);
    orientation.pitch = espp::deg_to_rad(pitch);
    orientation.yaw = espp::deg_to_rad(yaw);
    return orientation;
  };

  logger.info("Initializing IMU...");
  // initialize the IMU
  if (!tab5.initialize_imu(kalman_filter_fn)) {
    logger.error("Failed to initialize IMU!");
    return;
  }

  // initialize the uSD card
  using SdCardConfig = espp::M5StackTab5::SdCardConfig;
  SdCardConfig sdcard_config{};
  if (!tab5.initialize_sdcard(sdcard_config)) {
    logger.warn("Failed to initialize uSD card, there may not be a uSD card inserted!");
  } else {
    uint32_t size_mb = 0;
    uint32_t free_mb = 0;
    if (tab5.get_sd_card_info(&size_mb, &free_mb)) {
      logger.info("uSD card size: {} MB, free space: {} MB", size_mb, free_mb);
    } else {
      logger.warn("Failed to get uSD card info");
    }
  }

  logger.info("Initializing RTC...");
  // initialize the RTC
  if (!tab5.initialize_rtc()) {
    logger.error("Failed to initialize RTC!");
    return;
  }

  auto current_time = std::tm{};
  if (!tab5.get_rtc_time(current_time)) {
    logger.error("Failed to get RTC time");
    return;
  }

  // only set the time if the year is before 2024
  if (current_time.tm_year < 124) {
    // set the RTC time to a known value (2024-01-15 14:30:45)
    // Set time using std::tm
    std::tm time = {};
    time.tm_year = 124; // 2024 - 1900
    time.tm_mon = 0;    // January (0-based)
    time.tm_mday = 15;  // 15th
    time.tm_hour = 14;  // 2 PM
    time.tm_min = 30;
    time.tm_sec = 45;
    time.tm_wday = 1; // Monday
    if (!tab5.set_rtc_time(time)) {
      logger.error("Failed to set RTC time");
      return;
    }
  } else {
    logger.info("RTC time is already set to a valid value {:%Y-%m-%d %H:%M:%S}", current_time);
  }

  logger.info("Initializing battery management...");
  // initialize battery monitoring
  if (!tab5.initialize_battery_monitoring()) {
    logger.error("Failed to initialize battery monitoring!");
    return;
  }

  // enable charging
  tab5.set_charging_enabled(true);

  logger.info("Initializing sound...");
  // initialize the sound
  if (!tab5.initialize_audio()) {
    logger.error("Failed to initialize sound!");
    return;
  }

  // Brightness control with button
  logger.info("Initializing button...");
  auto button_callback = [&](const auto &state) {
    logger.info("Button state: {}", state.active);
    if (state.active) {
      // Cycle through brightness levels: 25%, 50%, 75%, 100%
      static int brightness_level = 0;
      float brightness_values[] = {0.25f, 0.5f, 0.75f, 1.0f};
      brightness_level = (brightness_level + 1) % 4;
      float new_brightness = brightness_values[brightness_level];
      tab5.brightness(new_brightness);
      logger.info("Set brightness to {:.0f}%", new_brightness * 100);
    }
  };
  if (!tab5.initialize_button(button_callback)) {
    logger.warn("Failed to initialize button");
  }

  logger.info("Setting up LVGL UI...");
  // set the background color to black
  lv_obj_t *bg = lv_obj_create(lv_screen_active());
  lv_obj_set_size(bg, tab5.display_width(), tab5.display_height());
  lv_obj_set_style_bg_color(bg, lv_color_make(0, 0, 0), 0);

  // add text in the center of the screen
  lv_obj_t *label = lv_label_create(lv_screen_active());
  static std::string label_text = "\n\n\n\nTouch the screen!";
  lv_label_set_text(label, label_text.c_str());
  lv_obj_align(label, LV_ALIGN_TOP_LEFT, 0, 0);
  lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_LEFT, 0);

  // Create style for line 0 (blue line, used for kalman filter)
  static lv_style_t style_line0;
  lv_style_init(&style_line0);
  lv_style_set_line_width(&style_line0, 8);
  lv_style_set_line_color(&style_line0, lv_palette_main(LV_PALETTE_BLUE));
  lv_style_set_line_rounded(&style_line0, true);

  // make a line for showing the direction of "down"
  lv_obj_t *line0 = lv_line_create(lv_screen_active());
  static lv_point_precise_t line_points0[] = {{0, 0},
                                              {tab5.display_width(), tab5.display_height()}};
  lv_line_set_points(line0, line_points0, 2);
  lv_obj_add_style(line0, &style_line0, 0);

  // Create style for line 1 (red line, used for madgwick filter)
  static lv_style_t style_line1;
  lv_style_init(&style_line1);
  lv_style_set_line_width(&style_line1, 8);
  lv_style_set_line_color(&style_line1, lv_palette_main(LV_PALETTE_RED));
  lv_style_set_line_rounded(&style_line1, true);

  // make a line for showing the direction of "down"
  lv_obj_t *line1 = lv_line_create(lv_screen_active());
  static lv_point_precise_t line_points1[] = {{0, 0},
                                              {tab5.display_width(), tab5.display_height()}};
  lv_line_set_points(line1, line_points1, 2);
  lv_obj_add_style(line1, &style_line1, 0);

  static auto rotate_display = [&]() {
    std::lock_guard<std::recursive_mutex> lock(lvgl_mutex);
    clear_circles();
    static auto rotation = LV_DISPLAY_ROTATION_0;
    rotation = static_cast<lv_display_rotation_t>((static_cast<int>(rotation) + 1) % 4);
    lv_display_t *disp = lv_display_get_default();
    lv_disp_set_rotation(disp, rotation);
    // update the size of the screen
    lv_obj_set_size(bg, tab5.rotated_display_width(), tab5.rotated_display_height());
    // refresh the display
  };

  // add a button in the top left which (when pressed) will rotate the display
  // through 0, 90, 180, 270 degrees
  lv_obj_t *btn = lv_btn_create(lv_screen_active());
  lv_obj_set_size(btn, 50, 50);
  lv_obj_align(btn, LV_ALIGN_TOP_LEFT, 0, 0);
  lv_obj_t *label_btn = lv_label_create(btn);
  lv_label_set_text(label_btn, LV_SYMBOL_REFRESH);
  // center the text in the button
  lv_obj_align(label_btn, LV_ALIGN_CENTER, 0, 0);
  lv_obj_add_event_cb(
      btn, [](auto event) { rotate_display(); }, LV_EVENT_PRESSED, nullptr);

  // disable scrolling on the screen (so that it doesn't behave weirdly when
  // rotated and drawing with your finger)
  lv_obj_set_scrollbar_mode(lv_screen_active(), LV_SCROLLBAR_MODE_OFF);
  lv_obj_clear_flag(lv_screen_active(), LV_OBJ_FLAG_SCROLLABLE);

  // start a simple thread to do the lv_task_handler every 16ms
  logger.info("Starting LVGL task...");
  espp::Task lv_task({.callback = [](std::mutex &m, std::condition_variable &cv) -> bool {
                        {
                          std::lock_guard<std::recursive_mutex> lock(lvgl_mutex);
                          lv_task_handler();
                        }
                        std::unique_lock<std::mutex> lock(m);
                        cv.wait_for(lock, 16ms);
                        return false;
                      },
                      .task_config = {
                          .name = "lv_task",
                          .stack_size_bytes = 10 * 1024,
                      }});
  lv_task.start();

  // load the audio and play it once as a test
  logger.info("Loading audio...");
  auto num_bytes_loaded = load_audio();
  logger.info("Loaded {} bytes of audio", num_bytes_loaded);

  // unmute the audio and set the volume to 60%
  tab5.mute(false);
  tab5.volume(60.0f);

  // set the brightness to 75%
  tab5.brightness(75.0f);

  // make a task to read out various data such as IMU, battery monitoring, etc.
  // and print it to screen
  logger.info("Starting data display task...");
  espp::Task imu_task(
      {.callback = [&](std::mutex &m, std::condition_variable &cv) -> bool {
         // sleep first in case we don't get IMU data and need to exit early
         {
           std::unique_lock<std::mutex> lock(m);
           cv.wait_for(lock, 10ms);
         }
         static auto &tab5 = espp::M5StackTab5::get();
         static auto imu = tab5.imu();

         // Update the Date/Time from the RTC
         std::tm rtc_time;
         std::string rtc_text = "";
         if (tab5.get_rtc_time(rtc_time)) {
           rtc_text = fmt::format("\n{:%Y-%m-%d %H:%M:%S}\n", rtc_time);
         }

         // Update the battery status
         auto battery_status = tab5.read_battery_status();
         std::string battery_text =
             fmt::format("\nBattery: {:0.2f} V, {:0.1f} mA, {:0.1f} %, Charging: {}\n",
                         battery_status.voltage_v, battery_status.current_ma,
                         battery_status.charge_percent, battery_status.is_charging ? "Yes" : "No");

         auto now = esp_timer_get_time(); // time in microseconds
         static auto t0 = now;
         auto t1 = now;
         float dt = (t1 - t0) / 1'000'000.0f; // convert us to s
         t0 = t1;

         // Update the IMU data
         std::error_code ec;
         // update the imu data
         if (!imu->update(dt, ec)) {
           return false;
         }
         // get accel
         auto accel = imu->get_accelerometer();
         auto gyro = imu->get_gyroscope();
         auto temp = imu->get_temperature();
         auto orientation = imu->get_orientation();
         auto gravity_vector = imu->get_gravity_vector();
         // invert the axes
         gravity_vector.y = -gravity_vector.y;
         gravity_vector.x = -gravity_vector.x;

         // now update the gravity vector line to show the direction of "down"
         // taking into account the configured rotation of the display
         auto rotation = lv_display_get_rotation(lv_display_get_default());
         if (rotation == LV_DISPLAY_ROTATION_90) {
           std::swap(gravity_vector.x, gravity_vector.y);
           gravity_vector.x = -gravity_vector.x;
         } else if (rotation == LV_DISPLAY_ROTATION_180) {
           gravity_vector.x = -gravity_vector.x;
           gravity_vector.y = -gravity_vector.y;
         } else if (rotation == LV_DISPLAY_ROTATION_270) {
           std::swap(gravity_vector.x, gravity_vector.y);
           gravity_vector.y = -gravity_vector.y;
         }

         // separator for imu
         std::string imu_text = "\nIMU Data:\n";
         imu_text += fmt::format("Accel: {:02.2f} {:02.2f} {:02.2f}\n", accel.x, accel.y, accel.z);
         imu_text += fmt::format("Gyro: {:03.2f} {:03.2f} {:03.2f}\n", espp::deg_to_rad(gyro.x),
                                 espp::deg_to_rad(gyro.y), espp::deg_to_rad(gyro.z));
         imu_text += fmt::format("Angle: {:03.2f} {:03.2f}\n", espp::rad_to_deg(orientation.roll),
                                 espp::rad_to_deg(orientation.pitch));
         imu_text += fmt::format("Temp: {:02.1f} C\n", temp);

         // use the pitch to to draw a line on the screen indiating the
         // direction from the center of the screen to "down"
         int x0 = tab5.rotated_display_width() / 2;
         int y0 = tab5.rotated_display_height() / 2;

         int x1 = x0 + 50 * gravity_vector.x;
         int y1 = y0 + 50 * gravity_vector.y;

         static lv_point_precise_t line_points0[] = {{x0, y0}, {x1, y1}};
         line_points0[0].x = x0;
         line_points0[0].y = y0;
         line_points0[1].x = x1;
         line_points0[1].y = y1;

         // Now show the madgwick filter
         auto madgwick_orientation = madgwick_filter_fn(dt, accel, gyro);
         float roll = madgwick_orientation.roll;
         float pitch = madgwick_orientation.pitch;
         [[maybe_unused]] float yaw = madgwick_orientation.yaw;
         float vx = sin(pitch);
         float vy = -cos(pitch) * sin(roll);
         [[maybe_unused]] float vz = -cos(pitch) * cos(roll);

         // invert the axes
         vx = -vx;
         vy = -vy;

         // now update the line to show the direction of "down" based on the
         // configured rotation of the display
         if (rotation == LV_DISPLAY_ROTATION_90) {
           std::swap(vx, vy);
           vx = -vx;
         } else if (rotation == LV_DISPLAY_ROTATION_180) {
           vx = -vx;
           vy = -vy;
         } else if (rotation == LV_DISPLAY_ROTATION_270) {
           std::swap(vx, vy);
           vy = -vy;
         }

         x1 = x0 + 50 * vx;
         y1 = y0 + 50 * vy;

         static lv_point_precise_t line_points1[] = {{x0, y0}, {x1, y1}};
         line_points1[0].x = x0;
         line_points1[0].y = y0;
         line_points1[1].x = x1;
         line_points1[1].y = y1;

         std::string text = fmt::format("{}\n\n\n\n\n", label_text);
         text += battery_text;
         text += rtc_text;
         text += imu_text;

         std::lock_guard<std::recursive_mutex> lock(lvgl_mutex);
         lv_label_set_text(label, text.c_str());
         lv_line_set_points(line0, line_points0, 2);
         lv_line_set_points(line1, line_points1, 2);

         return false;
       },
       .task_config = {
           .name = "Data Display Task",
           .stack_size_bytes = 6 * 1024,
           .priority = 10,
           .core_id = 0,
       }});
  imu_task.start();

  // loop forever
  while (true) {
    std::this_thread::sleep_for(1s);
  }

Public Types

enum class ExpansionPort

Expansion port configuration.

Values:

enumerator GROVE

Grove connector.

enumerator M5_BUS

M5-Bus connector.

enumerator STAMP

STAMP expansion pads.

enumerator GPIO_EXT

GPIO extension header.

using button_callback_t = espp::Interrupt::event_callback_fn

Alias for the button callback function.

using IoExpander = espp::Pi4ioe5v

Alias for the I/O Expanders (IOX) used by the Tab5.

using Pixel = lv_color16_t

Alias for the pixel type used by the Tab5 display.

using DisplayDriver = espp::Ili9881

Alias for the display driver used by the Tab5.

using TouchDriver = espp::Gt911

Alias for the GT911 touch controller used by the Tab5.

using TouchpadData = espp::TouchpadData

Alias for the touchpad data used by the Tab5 touchpad.

using Rtc = espp::Rx8130ce<>

Alias for the RTC used by the Tab5.

using Imu = espp::Bmi270<espp::bmi270::Interface::I2C>

Alias the IMU used by the Tab5.

using BatteryMonitor = espp::Ina226

Alias the INA226 battery power monitor.

using touch_callback_t = std::function<void(const TouchpadData&)>

Alias for the touch callback when touch events are received.

Public Functions

inline I2c &internal_i2c()

Get a reference to the internal I2C bus

Note

The internal I2C bus is used for touchscreen, audio codecs, IMU, RTC, and power monitoring

Returns:

A reference to the internal I2C bus

inline espp::Interrupt &interrupts()

Get a reference to the interrupts

Returns:

A reference to the interrupts

bool initialize_lcd()

Initialize the LCD (low level display driver, MIPI-DSI + ST7703)

Returns:

true if the LCD was successfully initialized, false otherwise

bool initialize_display(size_t pixel_buffer_size = 1280 * 720 / 10)

Initialize the LVGL display

Parameters:

pixel_buffer_size – The size of the pixel buffer

Returns:

true if the display was successfully initialized, false otherwise

bool initialize_touch(const touch_callback_t &callback = nullptr)

Initialize the GT911 multi-touch controller

Parameters:

callback – The touchpad callback

Returns:

true if the touchpad was successfully initialized, false otherwise

inline size_t bytes_per_pixel() const

Get the number of bytes per pixel for the display

Returns:

The number of bytes per pixel

inline std::shared_ptr<TouchpadInput> touchpad_input() const

Get the touchpad input

Returns:

A shared pointer to the touchpad input

inline TouchpadData touchpad_data() const

Get the most recent touchpad data

Returns:

The touchpad data

void touchpad_read(uint8_t *num_touch_points, uint16_t *x, uint16_t *y, uint8_t *btn_state)

Get the touchpad data for LVGL integration

Parameters:
  • num_touch_points – The number of touch points

  • x – The x coordinate

  • y – The y coordinate

  • btn_state – The button state (0 = button released, 1 = button pressed)

TouchpadData touchpad_convert(const TouchpadData &data) const

Convert touchpad data from raw reading to display coordinates

Note

Uses the touch_invert_x and touch_invert_y settings to determine if the x and y coordinates should be inverted

Parameters:

data – The touchpad data to convert

Returns:

The converted touchpad data

void brightness(float brightness)

Set the display brightness

Parameters:

brightness – The brightness as a percentage (0-100)

float brightness() const

Get the display brightness

Returns:

The brightness as a percentage (0-100)

void set_backlight_enabled(bool enable)

Enable/disable the LCD backlight (routes through IO expander if mapped)

std::optional<bool> is_backlight_enabled() const

Query backlight enable state if readable

Returns:

true if enabled, false if disabled; std::nullopt if unknown

size_t rotated_display_width() const

Get the display width in pixels, according to the current orientation

Returns:

The display width in pixels, according to the current orientation

size_t rotated_display_height() const

Get the display height in pixels, according to the current orientation

Returns:

The display height in pixels, according to the current orientation

bool initialize_audio (uint32_t sample_rate=48000, const espp::Task::BaseConfig &task_config={ .name="tab5_audio",.stack_size_bytes=CONFIG_M5STACK_TAB5_AUDIO_TASK_STACK_SIZE,.priority=20,.core_id=1})

Initialize the dual audio system (ES8388 codec + ES7210 AEC)

Parameters:
  • sample_rate – The audio sample rate (default 48kHz)

  • task_config – The task configuration for the audio task

Returns:

true if the audio system was successfully initialized, false otherwise

void enable_audio(bool enable)

Enable or disable the audio system

Parameters:

enable – True to enable, false to disable

void volume(float volume)

Set the audio volume

Parameters:

volume – The volume as a percentage (0-100)

float volume() const

Get the audio volume

Returns:

The volume as a percentage (0-100)

void mute(bool mute)

Mute or unmute the audio

Parameters:

mute – True to mute, false to unmute

bool is_muted() const

Check if audio is muted

Returns:

True if muted, false otherwise

uint32_t audio_sample_rate() const

Get the audio sample rate

Returns:

The audio sample rate, in Hz

void audio_sample_rate(uint32_t sample_rate)

Set the audio sample rate

Parameters:

sample_rate – The audio sample rate, in Hz

size_t audio_buffer_size() const

Get the audio buffer size

Returns:

The audio buffer size, in bytes

void play_audio(const uint8_t *data, uint32_t num_bytes)

Play audio data

Parameters:
  • data – The audio data to play

  • num_bytes – The number of bytes to play

void play_audio(std::span<const uint8_t> data)

Play audio data

Parameters:

data – The audio data to play

bool start_audio_recording(std::function<void(const uint8_t *data, size_t length)> callback)

Start recording audio

Parameters:

callback – Function to call with recorded audio data

Returns:

True if recording started successfully

void stop_audio_recording()

Stop recording audio.

bool initialize_imu(const Imu::filter_fn &orientation_filter = nullptr)

Initialize the BMI270 6-axis IMU

Parameters:

orientation_filter – Optional orientation filter function

Returns:

True if IMU was successfully initialized

inline std::shared_ptr<Imu> imu() const

Get the IMU instance

Returns:

Shared pointer to the IMU

bool initialize_battery_monitoring()

Initialize battery monitoring (INA226)

Returns:

True if battery monitoring was successfully initialized

BatteryStatus read_battery_status()

Get the latest battery status from the INA226

Returns:

Battery status structure

BatteryStatus get_battery_status() const

Get the most recent cached battery status

Note

This does not read from the INA226, use read_battery_status() to get the latest data

Returns:

Battery status structure

void enable_battery_charging(bool enable)

Enable or disable battery charging

Parameters:

enable – True to enable charging, false to disable

inline std::shared_ptr<BatteryMonitor> battery_monitor() const

Get the Battery Monitor Instance (INA226)

Returns:

Shared pointer to the battery monitor

bool initialize_rtc()

Initialize the RX8130CE real-time clock

Returns:

True if RTC was successfully initialized

bool set_rtc_time(uint64_t unix_timestamp)

Set the RTC time

Parameters:

unix_timestamp – Unix timestamp to set

Returns:

True if time was set successfully

bool set_rtc_time(const std::tm &time)

Set the RTC time

Parameters:

time – The time to set

Returns:

True if time was set successfully

bool get_rtc_time(std::tm &time)

Get the RTC time

Parameters:

time – The time structure to fill

Returns:

True if time was retrieved successfully

uint64_t get_unix_time()

Get the RTC time

Returns:

Unix timestamp, or 0 if RTC not initialized

bool set_rtc_wakeup(uint32_t seconds_from_now)

Enable RTC wake-up interrupt

Parameters:

seconds_from_now – Seconds from now to wake up

Returns:

True if wake-up was set successfully

inline std::shared_ptr<Rtc> rtc() const

Get the RTC instance

Returns:

Shared pointer to the RTC

bool initialize_button(const button_callback_t &callback = nullptr)

Initialize the button

Parameters:

callback – The callback function to call when pressed

Returns:

True if button was successfully initialized

bool button_state() const

Get the button state

Returns:

True if pressed, false otherwise

bool initialize_io_expanders()

Initialize the on-board IO expanders at addresses 0x43 and 0x44 Configures required directions and safe default output states.

bool lcd_reset(bool assert_reset)

Control the LCD reset (active-low) routed via IO expander (0x43 P4)

Parameters:

assert_reset=true – drives reset low; false releases reset high.

Returns:

true on success

bool touch_reset(bool assert_reset)

Control the GT911 touch reset (active-low) via IO expander (0x43 P5)

Parameters:

assert_reset=true – drives reset low; false releases reset high.

Returns:

true on success

bool set_speaker_enabled(bool enable)

Enable/disable the speaker amplifier (NS4150B SPK_EN on 0x43 P1)

Parameters:

enable – True to enable speaker, false to disable

Returns:

true on success

bool set_charging_enabled(bool enable)

Enable/disable battery charging (IP2326 CHG_EN on 0x44 P7)

Parameters:

enable – True to enable charging, false to disable

Returns:

true on success

bool get_charging_status()

Read battery charging status (IP2326 CHG_STAT on 0x44 P6) Returns true if charging is indicated asserted.

bool set_io_expander_output(uint8_t address, uint8_t bit, bool level)

Generic helpers to control IO expander pins (0x43/0x44) These perform read-modify-write on the output latch.

Parameters:
  • address – 7-bit expander I2C address (e.g. 0x43 or 0x44)

  • bit – Bit index 0..7

  • level – Desired output level

Returns:

true on success

std::optional<bool> get_io_expander_output(uint8_t address, uint8_t bit)

Read a single output bit from the expander output register

Parameters:
  • address – 7-bit expander I2C address (e.g. 0x43 or 0x44)

  • bit – Bit index 0..7

Returns:

std::optional<bool> containing the output state, or std::nullopt on error

std::optional<bool> get_io_expander_input(uint8_t address, uint8_t bit)

Read a single input bit from the expander input register

Parameters:
  • address – 7-bit expander I2C address (e.g. 0x43 or 0x44)

  • bit – Bit index 0..7

Returns:

std::optional<bool> containing the input state, or std::nullopt on error

bool initialize_sdcard(const SdCardConfig &config)

Initialize microSD / uSD card

Parameters:

config – Configuration for the uSD card

Returns:

True if uSD card was successfully initialized

bool is_sd_card_available() const

Check if SD card is present and mounted

Returns:

True if SD card is available

inline sdmmc_card_t *sdcard() const

Get the uSD card

Note

The uSD card is only available if it was successfully initialized and the mount point is valid

Returns:

A pointer to the uSD card

bool get_sd_card_info(uint32_t *size_mb, uint32_t *free_mb) const

Get SD card info

Parameters:
  • size_mb – Pointer to store size in MB

  • free_mb – Pointer to store free space in MB

Returns:

True if info retrieved successfully

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

Returns:

The verbosity level of the logger

inline void set_log_level(espp::Logger::Verbosity level)

Set the log level for the logger

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

set_log_level

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

get_log_level

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

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

Public Static Functions

static inline M5StackTab5 &get()

Access the singleton instance of the M5StackTab5 class.

Returns:

Reference to the singleton instance of the M5StackTab5 class

static inline constexpr size_t display_width()

Get the display width in pixels

Returns:

The display width in pixels

static inline constexpr size_t display_height()

Get the display height in pixels

Returns:

The display height in pixels

Public Static Attributes

static constexpr char mount_point[] = "/sdcard"

Mount point for the uSD card on the TDeck.

struct BatteryStatus

Battery status structure.

Public Members

float voltage_v

Battery voltage in volts.

float current_ma

Battery current in milliamps.

float power_mw

Battery power in milliwatts.

float charge_percent

Estimated charge percentage (0-100)

bool is_charging

True if battery is charging.

bool is_present

True if battery is present.

struct SdCardConfig

Configuration for the uSD card.

Public Members

bool format_if_mount_failed = false

Format the uSD card if mount failed.

int max_files = 5

The maximum number of files to open at once.

size_t allocation_unit_size = 2 * 1024

The allocation unit size in bytes.