2025-02-26 15:07:14 +09:00
|
|
|
/*
|
|
|
|
|
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: MIT
|
|
|
|
|
*/
|
|
|
|
|
/*
|
2026-03-24 14:55:00 +09:00
|
|
|
Example using M5UnitUnified for UnitToF/UnitToF4M/HatToF
|
2025-02-26 15:07:14 +09:00
|
|
|
*/
|
|
|
|
|
#include <M5Unified.h>
|
|
|
|
|
#include <M5UnitUnified.h>
|
|
|
|
|
#include <M5UnitUnifiedTOF.h>
|
2026-02-19 16:22:21 +09:00
|
|
|
#include <M5HAL.hpp>
|
2025-02-26 15:07:14 +09:00
|
|
|
#include <M5Utility.h>
|
2026-02-20 20:31:39 +09:00
|
|
|
#include <Wire.h>
|
2025-02-26 15:07:14 +09:00
|
|
|
|
2025-05-30 18:41:58 +09:00
|
|
|
// *************************************************************
|
|
|
|
|
// Choose one define symbol to match the unit you are using
|
|
|
|
|
// *************************************************************
|
2025-05-30 18:57:11 +09:00
|
|
|
#if !defined(USING_UNIT_TOF) && !defined(USING_UNIT_TOF4M) && !defined(USING_HAT_TOF) && !defined(USING_UNIT_TOF90)
|
2025-05-30 18:41:58 +09:00
|
|
|
// For UnitToF
|
|
|
|
|
// #define USING_UNIT_TOF
|
|
|
|
|
// For UnitToF4M
|
|
|
|
|
// #define USING_UNIT_TOF4M
|
|
|
|
|
// For HatToF
|
|
|
|
|
// #define USING_HAT_TOF
|
|
|
|
|
// For UnitToF90
|
|
|
|
|
// #define USING_UNIT_TOF90
|
|
|
|
|
#endif
|
|
|
|
|
|
2025-02-26 15:07:14 +09:00
|
|
|
namespace {
|
|
|
|
|
auto& lcd = M5.Display;
|
|
|
|
|
m5::unit::UnitUnified Units;
|
|
|
|
|
#if defined(USING_UNIT_TOF)
|
|
|
|
|
m5::unit::UnitToF unit;
|
|
|
|
|
#elif defined(USING_UNIT_TOF4M)
|
|
|
|
|
m5::unit::UnitToF4M unit;
|
|
|
|
|
#elif defined(USING_HAT_TOF)
|
|
|
|
|
m5::unit::HatToF unit;
|
2025-05-30 18:41:58 +09:00
|
|
|
#elif defined(USING_UNIT_TOF90)
|
|
|
|
|
m5::unit::UnitToF90 unit;
|
2025-02-26 15:07:14 +09:00
|
|
|
#else
|
|
|
|
|
#error Choose unit please!
|
|
|
|
|
#endif
|
2026-02-19 16:22:21 +09:00
|
|
|
|
2026-03-09 18:18:38 +09:00
|
|
|
#if defined(USING_UNIT_TOF)
|
|
|
|
|
constexpr uint32_t display_color{TFT_ORANGE};
|
|
|
|
|
#elif defined(USING_UNIT_TOF4M)
|
|
|
|
|
constexpr uint32_t display_color{0x00CC99U}; // Mint
|
|
|
|
|
#elif defined(USING_HAT_TOF)
|
|
|
|
|
constexpr uint32_t display_color{0xCC66FFU}; // Lavender
|
|
|
|
|
#elif defined(USING_UNIT_TOF90)
|
|
|
|
|
constexpr uint32_t display_color{0x66CCFFU}; // Sky blue
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
LGFX_Sprite sprite;
|
|
|
|
|
bool has_lcd{};
|
|
|
|
|
int16_t last_range{-1};
|
|
|
|
|
|
2026-02-19 16:22:21 +09:00
|
|
|
#if defined(USING_HAT_TOF)
|
|
|
|
|
struct HatPins {
|
|
|
|
|
int sda, scl;
|
|
|
|
|
};
|
|
|
|
|
HatPins get_hat_pins(const m5::board_t board)
|
|
|
|
|
{
|
|
|
|
|
switch (board) {
|
|
|
|
|
case m5::board_t::board_M5StickC:
|
|
|
|
|
case m5::board_t::board_M5StickCPlus:
|
|
|
|
|
case m5::board_t::board_M5StickCPlus2:
|
|
|
|
|
return {0, 26};
|
|
|
|
|
case m5::board_t::board_M5StickS3:
|
|
|
|
|
return {8, 0};
|
|
|
|
|
case m5::board_t::board_M5StackCoreInk:
|
|
|
|
|
return {25, 26};
|
|
|
|
|
case m5::board_t::board_ArduinoNessoN1:
|
|
|
|
|
return {6, 7};
|
|
|
|
|
default:
|
|
|
|
|
return {-1, -1};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2025-02-26 15:07:14 +09:00
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
void setup()
|
|
|
|
|
{
|
2026-02-19 16:22:21 +09:00
|
|
|
auto m5cfg = M5.config();
|
|
|
|
|
#if defined(USING_HAT_TOF)
|
|
|
|
|
m5cfg.pmic_button = false; // Disable BtnPWR
|
|
|
|
|
m5cfg.internal_imu = false; // Disable internal IMU
|
|
|
|
|
m5cfg.internal_rtc = false; // Disable internal RTC
|
|
|
|
|
#endif
|
|
|
|
|
M5.begin(m5cfg);
|
|
|
|
|
M5.setTouchButtonHeightByRatio(100);
|
|
|
|
|
|
|
|
|
|
// The screen shall be in landscape mode
|
|
|
|
|
if (lcd.height() > lcd.width()) {
|
|
|
|
|
lcd.setRotation(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto board = M5.getBoard();
|
2025-02-26 15:07:14 +09:00
|
|
|
|
|
|
|
|
#if defined(USING_HAT_TOF)
|
2026-02-19 16:22:21 +09:00
|
|
|
const auto pins = get_hat_pins(board);
|
|
|
|
|
M5_LOGI("getHatPin: SDA:%d SCL:%d", pins.sda, pins.scl);
|
|
|
|
|
if (pins.sda < 0 || pins.scl < 0) {
|
|
|
|
|
M5_LOGE("Unsupported board for HatToF");
|
|
|
|
|
lcd.fillScreen(TFT_RED);
|
2025-02-26 15:07:14 +09:00
|
|
|
while (true) {
|
|
|
|
|
m5::utility::delay(10000);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-20 20:31:39 +09:00
|
|
|
auto& wire = (board == m5::board_t::board_ArduinoNessoN1) ? Wire1 : Wire;
|
|
|
|
|
wire.end();
|
|
|
|
|
wire.begin(pins.sda, pins.scl, 400 * 1000U);
|
|
|
|
|
if (!Units.add(unit, wire) || !Units.begin()) {
|
2026-02-19 16:22:21 +09:00
|
|
|
M5_LOGE("Failed to begin");
|
|
|
|
|
lcd.fillScreen(TFT_RED);
|
|
|
|
|
while (true) {
|
|
|
|
|
m5::utility::delay(10000);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#else
|
2026-03-09 18:18:38 +09:00
|
|
|
// NessoN1: Arduino Wire (I2C_NUM_0) cannot be used for GROVE port.
|
|
|
|
|
// Wire is used by M5Unified In_I2C for internal devices (IOExpander etc.).
|
|
|
|
|
// Wire1 exists but is reserved for HatPort — cannot be used for GROVE.
|
|
|
|
|
// Reconfiguring Wire to GROVE pins breaks In_I2C, causing ESP_ERR_INVALID_STATE in M5.update().
|
|
|
|
|
// Solution: Use SoftwareI2C via M5HAL (bit-banging) for the GROVE port.
|
|
|
|
|
// NanoC6: Wire.begin() on GROVE pins conflicts with m5::I2C_Class registered by Ex_I2C.setPort()
|
|
|
|
|
// on the same I2C_NUM_0, causing sporadic NACK errors.
|
|
|
|
|
// Solution: Use M5.Ex_I2C (m5::I2C_Class) directly instead of Arduino Wire.
|
|
|
|
|
bool unit_ready{};
|
2026-02-19 16:22:21 +09:00
|
|
|
if (board == m5::board_t::board_ArduinoNessoN1) {
|
2026-03-09 18:18:38 +09:00
|
|
|
// NessoN1: GROVE is on port_b (GPIO 5/4), not port_a (which maps to Wire pins 8/10)
|
|
|
|
|
auto pin_num_sda = M5.getPin(m5::pin_name_t::port_b_out);
|
|
|
|
|
auto pin_num_scl = M5.getPin(m5::pin_name_t::port_b_in);
|
|
|
|
|
M5_LOGI("getPin(M5HAL): SDA:%u SCL:%u", pin_num_sda, pin_num_scl);
|
2026-02-19 16:22:21 +09:00
|
|
|
m5::hal::bus::I2CBusConfig i2c_cfg;
|
|
|
|
|
i2c_cfg.pin_sda = m5::hal::gpio::getPin(pin_num_sda);
|
|
|
|
|
i2c_cfg.pin_scl = m5::hal::gpio::getPin(pin_num_scl);
|
|
|
|
|
auto i2c_bus = m5::hal::bus::i2c::getBus(i2c_cfg);
|
2026-02-20 20:31:39 +09:00
|
|
|
M5_LOGI("Bus:%d", i2c_bus.has_value());
|
2026-03-09 18:18:38 +09:00
|
|
|
unit_ready = Units.add(unit, i2c_bus ? i2c_bus.value() : nullptr) && Units.begin();
|
|
|
|
|
} else if (board == m5::board_t::board_M5NanoC6) {
|
|
|
|
|
// NanoC6: Use M5.Ex_I2C (m5::I2C_Class, not Arduino Wire)
|
|
|
|
|
M5_LOGI("Using M5.Ex_I2C");
|
|
|
|
|
unit_ready = Units.add(unit, M5.Ex_I2C) && Units.begin();
|
2026-02-19 16:22:21 +09:00
|
|
|
} else {
|
2026-03-09 18:18:38 +09:00
|
|
|
auto pin_num_sda = M5.getPin(m5::pin_name_t::port_a_sda);
|
|
|
|
|
auto pin_num_scl = M5.getPin(m5::pin_name_t::port_a_scl);
|
2026-02-19 16:22:21 +09:00
|
|
|
M5_LOGI("getPin: SDA:%u SCL:%u", pin_num_sda, pin_num_scl);
|
|
|
|
|
Wire.end();
|
|
|
|
|
Wire.begin(pin_num_sda, pin_num_scl, 400 * 1000U);
|
2026-03-09 18:18:38 +09:00
|
|
|
unit_ready = Units.add(unit, Wire) && Units.begin();
|
|
|
|
|
}
|
|
|
|
|
if (!unit_ready) {
|
|
|
|
|
M5_LOGE("Failed to begin");
|
|
|
|
|
lcd.fillScreen(TFT_RED);
|
|
|
|
|
while (true) {
|
|
|
|
|
m5::utility::delay(10000);
|
2026-02-19 16:22:21 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2025-02-26 15:07:14 +09:00
|
|
|
|
|
|
|
|
M5_LOGI("M5UnitUnified has been begun");
|
|
|
|
|
M5_LOGI("%s", Units.debugInfo().c_str());
|
|
|
|
|
|
2026-03-09 18:18:38 +09:00
|
|
|
// Display setup (skip for no-LCD or EPD devices)
|
|
|
|
|
has_lcd = lcd.width() > 0 && lcd.height() > 0 && !lcd.isEPD();
|
|
|
|
|
if (has_lcd) {
|
|
|
|
|
sprite.setPsram(false);
|
|
|
|
|
sprite.setColorDepth(1);
|
|
|
|
|
has_lcd = sprite.createSprite(lcd.width(), lcd.height());
|
|
|
|
|
}
|
|
|
|
|
if (has_lcd) {
|
|
|
|
|
sprite.setPaletteColor(0, TFT_BLACK);
|
|
|
|
|
sprite.setPaletteColor(1, display_color);
|
|
|
|
|
sprite.setFont(&fonts::Orbitron_Light_32);
|
|
|
|
|
sprite.setTextDatum(middle_center);
|
|
|
|
|
float scale = lcd.width() / (32 * 4.0f);
|
|
|
|
|
sprite.setTextSize(scale, scale);
|
|
|
|
|
sprite.setTextColor(1, 0);
|
|
|
|
|
sprite.fillSprite(0);
|
|
|
|
|
sprite.pushSprite(&lcd, 0, 0);
|
|
|
|
|
}
|
2025-02-26 15:07:14 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Behavior when BtnA is clicked changes depending on the value
|
|
|
|
|
constexpr int32_t BTN_A_FUNCTION{0};
|
|
|
|
|
|
|
|
|
|
#if defined(USING_UNIT_TOF4M)
|
|
|
|
|
using namespace m5::unit::vl53l1x;
|
|
|
|
|
uint32_t idx{};
|
|
|
|
|
constexpr Timing tb_table[] = {
|
|
|
|
|
// Timing::Budget15ms // only Short
|
|
|
|
|
Timing::Budget20ms, Timing::Budget33ms, Timing::Budget50ms,
|
|
|
|
|
Timing::Budget100ms, Timing::Budget200ms, Timing::Budget500ms,
|
|
|
|
|
};
|
|
|
|
|
constexpr Window w_table[] = {Window::Below, Window::Beyond, Window::Out, Window::In};
|
|
|
|
|
|
|
|
|
|
void button_function()
|
|
|
|
|
{
|
|
|
|
|
switch (BTN_A_FUNCTION) {
|
|
|
|
|
case 0: { // Singleshot
|
|
|
|
|
static uint32_t sscnt{};
|
|
|
|
|
unit.stopPeriodicMeasurement();
|
|
|
|
|
m5::unit::vl53l1x::Data d{};
|
|
|
|
|
if (unit.measureSingleshot(d)) {
|
2026-02-19 16:22:21 +09:00
|
|
|
M5.Log.printf("Singleshot[%d]: >Range:%d\nStatus:%u\n", sscnt, d.range(), d.range_status());
|
2025-02-26 15:07:14 +09:00
|
|
|
} else {
|
|
|
|
|
M5_LOGE("Failed to measure");
|
|
|
|
|
}
|
|
|
|
|
// Return to periodic measurement after 8 measurements
|
|
|
|
|
if (++sscnt >= 8) {
|
|
|
|
|
sscnt = 0;
|
|
|
|
|
unit.startPeriodicMeasurement(unit.config().distance);
|
|
|
|
|
}
|
|
|
|
|
} break;
|
|
|
|
|
case 1: { // Change window mode
|
|
|
|
|
M5.Log.printf("Change Window:%u\n", w_table[idx]);
|
|
|
|
|
unit.writeDistanceThreshold(w_table[idx], 200 /*20cm*/, 400 /*40cm*/);
|
|
|
|
|
if (++idx >= m5::stl::size(w_table)) {
|
|
|
|
|
idx = 0;
|
|
|
|
|
}
|
|
|
|
|
} break;
|
|
|
|
|
case 2: { // Change interval
|
|
|
|
|
M5.Log.printf("Change interval %u", tb_table[idx]);
|
|
|
|
|
unit.stopPeriodicMeasurement();
|
|
|
|
|
unit.startPeriodicMeasurement(Distance::Short, tb_table[idx]);
|
|
|
|
|
if (++idx >= m5::stl::size(tb_table)) {
|
|
|
|
|
idx = 0;
|
|
|
|
|
}
|
|
|
|
|
} break;
|
|
|
|
|
case 3: { // Change ROI
|
|
|
|
|
unit.stopPeriodicMeasurement();
|
|
|
|
|
if (++idx & 1) {
|
|
|
|
|
unit.writeROI(5, 5);
|
|
|
|
|
unit.writeROICenter(18);
|
|
|
|
|
M5.Log.printf("Change ROI 5:5\n");
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
unit.writeROI(16, 16); // default
|
|
|
|
|
M5.Log.printf("Change ROI 16:16\n");
|
|
|
|
|
}
|
|
|
|
|
unit.startPeriodicMeasurement(unit.config().distance);
|
|
|
|
|
|
|
|
|
|
} break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
|
|
void button_function()
|
|
|
|
|
{
|
|
|
|
|
switch (BTN_A_FUNCTION) {
|
|
|
|
|
case 0: { // Singleshot
|
|
|
|
|
static uint32_t sscnt{};
|
|
|
|
|
unit.stopPeriodicMeasurement();
|
|
|
|
|
m5::unit::vl53l0x::Data d{};
|
|
|
|
|
if (unit.measureSingleshot(d)) {
|
2026-02-19 16:22:21 +09:00
|
|
|
M5.Log.printf("Singleshot[%d]: >Range:%d\nStatus:%u\n", sscnt, d.range(), d.range_status());
|
2025-02-26 15:07:14 +09:00
|
|
|
} else {
|
|
|
|
|
M5_LOGE("Failed to measure");
|
|
|
|
|
}
|
|
|
|
|
// Return to periodic measurement after 8 measurements
|
|
|
|
|
if (++sscnt >= 8) {
|
|
|
|
|
sscnt = 0;
|
|
|
|
|
unit.startPeriodicMeasurement(unit.config().mode);
|
|
|
|
|
}
|
|
|
|
|
} break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
void loop()
|
|
|
|
|
{
|
|
|
|
|
M5.update();
|
|
|
|
|
Units.update();
|
|
|
|
|
|
2026-03-09 18:18:38 +09:00
|
|
|
if (unit.updated()) {
|
|
|
|
|
auto range = unit.range();
|
|
|
|
|
if (range >= 0) {
|
|
|
|
|
// Can be checked e.g. by serial plotters
|
|
|
|
|
M5.Log.printf(">Range:%d\n", range);
|
|
|
|
|
|
|
|
|
|
if (has_lcd && range != last_range) {
|
|
|
|
|
last_range = range;
|
|
|
|
|
sprite.fillSprite(0);
|
|
|
|
|
sprite.drawString(m5::utility::formatString("%d", range).c_str(), sprite.width() / 2,
|
|
|
|
|
sprite.height() / 2);
|
|
|
|
|
sprite.pushSprite(&lcd, 0, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-02-26 15:07:14 +09:00
|
|
|
}
|
|
|
|
|
|
2026-02-19 16:22:21 +09:00
|
|
|
if (M5.BtnA.wasClicked()) {
|
2025-02-26 15:07:14 +09:00
|
|
|
button_function();
|
|
|
|
|
} else if (M5.BtnA.wasHold()) {
|
|
|
|
|
M5.Log.printf("Reset!\n");
|
|
|
|
|
unit.softReset();
|
|
|
|
|
}
|
|
|
|
|
}
|