Points for porting to M5Unified

? >This document provides a brief overview of how to migrate from the individual M5Stack libraries for the Arduino IDE and ESP-IDF to M5Unified when changing to M5Unified.
* This document is based on information from M5Unified v0.0.7.

M5Unified Overview

M5Unified is a library to handle hardware with different specifications of M5Stack products with a common API. Although it requires more configuration items, the same source code can be used for different devices, which is useful in the following cases.

  • Develop an application on M5Stack and operate it on M5Atom.
    M5Unified can share source with M5Atom, which does not have screen rendering functionality.
    * Sketch size and memory consumption will increase.
  • Develop an application that works with either M5Stack or M5StickC.
    Scale to different screen sizes by specifying graphics coordinates in relative coordinates (e.g., the center of the display) or by using sprites.
  • Speaker and buzzer can be used to play sound

Major differences from existing M5Stack libraries

  • Include files can be unified.
    In the past, the Include header files were different for each device, such as #include <M5Stack.h> for M5Stack Basic and #include <M5StickC.h> for M5StickC, but with M5Unified, #include <M5Unified.h>. The device type is automatically identified in the library.

  • The argument of M5.begin() is different.
    In the conventional M5.begin(), the content and number of arguments are different for each device library. In order to unify them, a structure is used as the argument. See M5.begin() for details.

  • M5Atom and other RGB LED controls are not supported.
    At the time of M5Unified v0.0.7, LED control of M5Atom is not supported. Please include and implement FastLED library separately.

  • Automatic mounting (SD.begin()) of TFCard (SDCard) is not done.
    In order to support ESP-IDF, SD.h cannot be used by default and SD.begin() is not executed internally, assuming that SDFat is also selected in the library of available TF cards.

  • Graphics library changed from TFT-eSPI to M5GFX
    The existing library had TFT_eSPI internally, but M5Unified depends on M5GFX and has been changed to use M5GFX. Note that the API is different.
    For more information about the M5GFX library, please refer to Getting Started with M5GFX for the M5GFX library.

  • Speaker function
    Core1 has noise in the sound, but it is an improvement over the previous library. Also, models with piezoelectric buzzers can play audio files, etc. as well as buzzers.
    * Please be careful not to play loud audio files or low frequencies continuously at high volume as it may damage the speaker.

  • Difference in Button name
    To make it common, the name is BtnA instead of Btn even if the model has one button.

Notes on using M5Unified

  • It cannot be used in conjunction with other M5Stack libraries.
    Because of the identical name M5, the following error will occur at compile time.
error: reference to 'jpeg_div_t' is ambiguous jpeg_div_t scale = JPEG_DIV_NONE);
error: conflicting declaration 'M5Stack M5' extern M5Stack M5;
  • When using M5Atom Matrix with the FastLED library, the brightness of the RGB LEDs should be less than 20 (setting range: 0~100). Using a brightness higher than that will damage the acrylic board and LEDs due to heat.


Usage of M5.config() and M5.begin()

Parameters of M5.begin() are set by structure, and should be executed at the beginning of setup() function.

? >If you are a beginner, please write the following at the beginning of the setup function even if you use the default settings.

auto cfg = M5.config(); // Assign a structure for setup.

// Set the items you want to configure. Omit the following two lines if you use the default settings.
cfg.serial_baudrate = 115200;
cfg.output_power = true;

M5.begin(cfg); // start the device with the value you set.

Parameters for M5.config()

See the following link for details.


conventional M5.begin()

Conventional M5.begin() has different contents and number of arguments for each device as follows.

// M5Stack
M5.begin(bool LCDEnable = true, bool SDEnable = true, bool SerialEnable = true, bool I2CEnable = false);

// M5Core2
M5.begin(bool LCDEnable = true, bool SDEnable = true, bool SerialEnable = true, bool I2CEnable = false, mbus_mode_t mode = KMBusModeOutput);

// M5StickC
M5.begin(bool LCDEnable = true, bool PowerEnable = true, bool SerialEnable = true);

// M5StickCPlus
M5.begin(bool LCDEnable = true, bool PowerEnable = true, bool SerialEnable = true);

// M5Atom
M5.begin(bool SerialEnable = true, bool I2CEnable = true, bool DisplayEnable = false);

About Buttons

The buttons available differ depending on the M5Stack product.

  • BtnA, BtnB, BtnC
    Customizable buttons
  • BtnPWR
    Power button is also customizable on some models.
  • BtnEXT
    For more information, please refer to the following links.

About Buttons

How to use the button


While button A is pressed, the message "BtnA isPressed." is displayed; when released, the message "BtnA isReleased" is displayed.

#include <M5Unified.h>

void setup() {
    auto cfg = M5.config();
    cfg.clear_display = true;
    M5.Lcd.println("Press BtnA");

void loop() {
    // update the button state.

    if (M5.BtnA.isPressed()) {
        M5.Display.setCursor(0, 16);
        M5.Display.println("BtnA isPressed ");
    } else {
        M5.Display.setCursor(0, 16);
        M5.Display.println("BtnA isReleased");

For multiple loops

M5.update() must be executed to update the button state. Note that if there are more loops, M5.update() is needed for those loops as well.

#include <M5Unified.h>

uint32_t loop_count = 0;

void setup() {
    auto cfg = M5.config();
    cfg.clear_display = true;
    M5.Lcd.println("Press BtnA");

void loop() {

    M5.update(); // update the button state.

    if (M5.BtnA.wasPressed()) {
        M5.Display.setCursor(0, 0);
        M5.Display.println("BtnA wasPressed");
        M5.Display.println("Press BtnA Stop Count");

        while (true) {
            M5.update(); // update the button state within the subloop.

            M5.Display.setCursor(0, 32);
            M5.Display.printf("count: %10d\n", loop_count);
            if (M5.BtnA.wasPressed()) {
                M5.Display.println("Count Stop");
                loop_count = 0;

About button states

The state of a button can be obtained with the following functions. Beginners should try using wasPressed() or wasClicked() first.

  • was
    Detects if the state occurred before M5.update() was executed.

    • wasPressed
      Whether it was pressed.
    • wasReleased
      whether it was released
  • is
    Detects if the state happened when M5.update() was executed.

    • isPressed
      whether it is pressed
    • isReleased
      Whether it is released
  • Click
    Detects if a click has been made.

    • wasClicked
      Returns true each time a click is made.
    • wasSingleClicked
      Returns true when a single click is detected.
    • wasDoubleClicked
      Returns true when two clicks are detected.
    • wasDecideClickCount
      Returns true when the last click is detected in the case of consecutive clicks.(There is a version of wasDeciedClickCount in typo.)
  • longPressed
    Detects a long press or release of a button with a specified time.

    • pressedFor(uint32_t msec)
      Returns true when the button has been pressed for the specified time or longer.
    • releasedFor(uint32_t msec)
      Returns true if the button is released for the specified time or longer.

Number of clicks

The number of times a button is clicked consecutively is added. The number of times can be obtained with M5.Btn.getClickCount().

About screen drawing

M5Unified depends on M5GFX library and should be installed together if not already installed. (ArduinoIDE displays a dialog message when installing M5Unified; PlatformIO installs it automatically.)

M5.Display (or M5.Lcd)

For devices with a screen, the M5GFX API can be used with M5.Display. No definition is required.

HelloWorld with M5Unified

To do HelloWorld on M5Unified, write the following code, which is the same source for both LCD and e-Ink Display devices. The same source code can be used for both LCD and e-Ink Display models, and can be used for M5Atom models as well, although the screen does not move.

#include <M5Unified.h>

void setup() {

  auto cfg = M5.config();


void loop() {

Major changes when converting to M5Unified.

There are some differences in the API as well, but I have omitted the details.

Change of header files to include

Change from product-specific library headers to M5Unified.h, and add SD.h when accessing SD.

- #include <M5Stack.h>
+ #include <SD.h>
+ #include <M5Unified.h>

Change M5.begin()

Change M5.begin() by executing M5.config() as follows

- M5.begin(true, true, true, true);
+ auto cfg = M5.config();
+ cfg.internal_imu = true;
+ M5.begin(cfg);

IMU processing

Change IMU processing to be accessed by M5.Imu.

Add SD.begin()

Add SD.begin()

+    while (false == SD.begin(GPIO_NUM_4, SPI, 25000000))
+    {
+      delay(500);
+    }

Internal I2C release

If you want to use Port.A of M5Stack Basic/Gray/Fire etc. other than I2C, you need to run M5.In_I2C.release().

auto cfg = M5.config();

[Reference] M5Unified of FactoryTest.ino

If you change FactoryTest.ino, an Example of M5Stack, to M5Unified, it will be as follows.

  • Change the header file
  • Change the IMU process
  • Addition of SD.begin
  • Add definition of SPEAKER_PIN
  • Changed the argument of M5.Btn.pressedFor
diff --git a/src/main.cpp b/src/main.cpp
index b735e30..dae9b99 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,10 +1,10 @@
 #include <Arduino.h>
-#include <M5Stack.h>
+#include <SD.h>
+#include <M5Unified.h>
 #include <stdlib.h>
 //#include "FastLED.h"
 #include "WiFi.h"
-#include "utility/MPU9250.h"
 extern const unsigned char gImage_logoM5[];
 extern const unsigned char m5stack_startup_music[];
@@ -13,7 +13,8 @@ extern const unsigned char m5stack_startup_music[];
 #define min(a, b) (((a) < (b)) ? (a) : (b))
-MPU9250 IMU;
+#define SPEAKER_PIN 25
 // #define LEDS_PIN 15
 // #define LEDS_NUM 10
@@ -127,15 +128,15 @@ void writeFile(fs::FS &fs, const char *path, const char *message) {
 void buttons_test() {
-    if (M5.BtnA.wasReleased() || M5.BtnA.pressedFor(1000, 200)) {
+    if (M5.BtnA.wasReleased() || M5.BtnA.pressedFor(1000)) {
-    if (M5.BtnB.wasReleased() || M5.BtnB.pressedFor(1000, 200)) {
+    if (M5.BtnB.wasReleased() || M5.BtnB.pressedFor(1000)) {
-    if (M5.BtnC.wasReleased() || M5.BtnC.pressedFor(1000, 200)) {
+    if (M5.BtnC.wasReleased() || M5.BtnC.pressedFor(1000)) {
@@ -475,8 +476,11 @@ void setup() {
     //     GPIO_test();
     // }
+    auto cfg = M5.config();
+    cfg.internal_imu = true;
     // initialize the M5Stack object
-    M5.begin();
+    M5.begin(cfg);
       Power chip connected to gpio21, gpio22, I2C device
@@ -484,6 +488,10 @@ void setup() {
       If used battery, please call this function in your project
+    while (false == SD.begin(GPIO_NUM_4, SPI, 25000000))
+    {
+      delay(500);
+    }
     // dac test
     // if (gpio_test_flg)
@@ -492,7 +500,6 @@ void setup() {
     // }
     // ledBar();
-    Wire.begin();
     // Lcd display
@@ -609,54 +616,33 @@ void setup() {
-    byte c = IMU.readByte(MPU9250_ADDRESS, WHO_AM_I_MPU9250);
-    Serial.print("MPU9250 ");
-    Serial.print("I AM ");
-    Serial.print(c, HEX);
-    Serial.print(" I should be ");
-    Serial.println(0x71, HEX);
-    Serial.println("");
-    M5.Lcd.setCursor(20, 0);
-    M5.Lcd.print("MPU9250");
-    M5.Lcd.setCursor(0, 10);
-    M5.Lcd.print("I AM");
-    M5.Lcd.setCursor(0, 20);
-    M5.Lcd.print(c, HEX);
-    M5.Lcd.setCursor(0, 30);
-    M5.Lcd.print("I Should Be");
-    M5.Lcd.setCursor(0, 40);
-    M5.Lcd.println(0x71, HEX);
-    M5.Lcd.println();
-    delay(100);
-    IMU.initMPU9250();
-    // Initialize device for active mode read of acclerometer, gyroscope, and
-    // temperature
-    Serial.println("MPU9250 initialized for active data mode....");
-    // Read the WHO_AM_I register of the magnetometer, this is a good test of
-    // communication
-    byte d = IMU.readByte(AK8963_ADDRESS, WHO_AM_I_AK8963);
-    Serial.print("AK8963 ");
-    Serial.print("I AM ");
-    Serial.print(d, HEX);
-    Serial.print(" I should be ");
-    Serial.println(0x48, HEX);
-    // M5.Lcd.fillScreen(BLACK);
-    M5.Lcd.setCursor(20, 100);
-    M5.Lcd.print("AK8963");
-    M5.Lcd.setCursor(0, 110);
-    M5.Lcd.print("I AM");
-    M5.Lcd.setCursor(0, 120);
-    M5.Lcd.print(d, HEX);
-    M5.Lcd.setCursor(0, 130);
-    M5.Lcd.print("I Should Be");
-    M5.Lcd.setCursor(0, 140);
-    M5.Lcd.print(0x48, HEX);
+    const char* name;
+    // run-time branch : imu model check
+    switch (M5.Imu.getType())
+    {
+      case m5::imu_t::imu_mpu6050:
+        name = "MPU6050";
+        break;
+      case m5::imu_t::imu_mpu6886:
+        name = "MPU6886";
+        break;
+      case m5::imu_t::imu_mpu9250:
+        name = "MPU9250";
+        break;
+      case m5::imu_t::imu_sh200q:
+        name = "SH200Q";
+        break;
+      default:
+        name = "none";
+        break;
+    }
+    M5.Display.print("IMU:");
+    M5.Display.println(name);
+    M5.Display.endWrite();
+    ESP_LOGI("setup", "imu:%s", name);
     M5.Lcd.setCursor(0, 0);
     M5.Lcd.println("wifi test:");
On This Page