From aef5b0cf0275e96b0a566dfc34c4b46a5e679ba8 Mon Sep 17 00:00:00 2001 From: STC Date: Sat, 6 Jun 2026 19:39:48 +0900 Subject: [PATCH 1/6] M5Stack Core2 use ECMA-419 touch driver --- .../targets/m5stack_core2/M5StackCoreTouch.js | 54 +++++++++++++++---- .../targets/m5stack_core2/host/provider.js | 21 +++++++- .../esp32/targets/m5stack_core2/manifest.json | 15 +----- 3 files changed, 64 insertions(+), 26 deletions(-) diff --git a/build/devices/esp32/targets/m5stack_core2/M5StackCoreTouch.js b/build/devices/esp32/targets/m5stack_core2/M5StackCoreTouch.js index 0228217f7d..dbdc132f9d 100644 --- a/build/devices/esp32/targets/m5stack_core2/M5StackCoreTouch.js +++ b/build/devices/esp32/targets/m5stack_core2/M5StackCoreTouch.js @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Moddable Tech, Inc. + * Copyright (c) 2022-2026 Moddable Tech, Inc. * * This file is part of the Moddable SDK Runtime. * @@ -17,28 +17,60 @@ * along with the Moddable SDK Runtime. If not, see . * */ - - import Touch from "ft6206" + +import Touch from "embedded:sensor/Touch/FT6x06"; class M5StackCoreTouch extends Touch { #captured; // undefined or pressed button instance + #capturedID; // touch id captured as virtual button + + sample() { + const points = super.sample(); + if (!points) + return points; - read(points) { - super.read(points); + let capturedPointIndex = -1; if (this.#captured) { - if (0 === points[0].state) { + for (let i = 0, length = points.length; i < length; i++) { + const point = points[i]; + + if (point.id === this.#capturedID) { + capturedPointIndex = i; + break; + } + } + + if (-1 === capturedPointIndex) { this.#captured.write(0); this.#captured = undefined; + this.#capturedID = undefined; } } - else if ((1 === points[0].state) && (points[0].y >= 240)) { - this.#captured = button[String.fromCharCode('a'.charCodeAt() + Math.idiv(points[0].x, 107))]; - this.#captured?.write(1); + else { + for (let i = 0, length = points.length; i < length; i++) { + const point = points[i]; + + if (point.y >= 240) { + const index = Math.idiv(point.x, 107); + this.#captured = globalThis.button[String.fromCharCode('a'.charCodeAt() + index)]; + + if (this.#captured) { + this.#capturedID = point.id; + this.#captured.write(1); + capturedPointIndex = i; + } + + break; + } + } + } + + if (-1 !== capturedPointIndex) { + points.splice(capturedPointIndex, 1); } - if (this.#captured) - points[0].state = 0; + return points; } } diff --git a/build/devices/esp32/targets/m5stack_core2/host/provider.js b/build/devices/esp32/targets/m5stack_core2/host/provider.js index cb0c7cae1c..b4d7640942 100644 --- a/build/devices/esp32/targets/m5stack_core2/host/provider.js +++ b/build/devices/esp32/targets/m5stack_core2/host/provider.js @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Moddable Tech, Inc. + * Copyright (c) 2021-2026 Moddable Tech, Inc. * * This file is part of the Moddable SDK Runtime. * @@ -30,6 +30,7 @@ import SPI from "embedded:io/spi"; import PulseWidth from "embedded:io/pulsewidth"; import BMI270 from "embedded:sensor/Accelerometer-Gyroscope-Magnetometer/BMI270"; import MPU6886 from "embedded:sensor/Accelerometer-Gyroscope/MPU6886"; +import Touch from "M5StackCoreTouch"; const ACCELERATION_SCALER = 1 / 9.80665; const IMU_ADDRESS = 0x68; @@ -94,6 +95,24 @@ const device = { displaySelect: 5 }, sensor: { + Touch: class { + constructor(options) { + const result = new Touch({ + ...options, + sensor: { + ...device.I2C.internal, + io: device.io.SMBus, + }, + interrupt: { + io: Digital, + mode: Digital.Input, + pin: 39 + } + }); + result.configure({threshold: 20}); + return result; + } + }, IMU: class { constructor(options) { const sensor = { diff --git a/build/devices/esp32/targets/m5stack_core2/manifest.json b/build/devices/esp32/targets/m5stack_core2/manifest.json index 2293466448..3cc76d2325 100644 --- a/build/devices/esp32/targets/m5stack_core2/manifest.json +++ b/build/devices/esp32/targets/m5stack_core2/manifest.json @@ -8,7 +8,7 @@ "include": [ "$(MODDABLE)/modules/io/manifest.json", "$(MODDABLE)/modules/drivers/ili9341/manifest.json", - "$(MODDABLE)/modules/drivers/ft6206/manifest.json", + "$(MODDABLE)/modules/drivers/sensors/ft6206/manifest.json", "$(MODDABLE)/modules/drivers/sensors/bmi270/manifest.json", "$(MODDABLE)/modules/drivers/sensors/mpu6886/manifest.json", "$(MODDABLE)/modules/drivers/axp192/manifest.json", @@ -16,7 +16,6 @@ ], "config": { "screen": "ili9341", - "touch": "M5StackCoreTouch", "startupSound": "bflatmajor.maud", "startupVibration": 600 }, @@ -55,18 +54,6 @@ "0xFF, 0" ] }, - "ft6206": { - "width": 320, - "height": 280, - "hz": 400000, - "sda": 21, - "scl": 22, - "threshold": 128, - "flipX": false, - "flipY": false, - "fitX": false, - "fitY": false - }, "audioOut": { "bitsPerSample": 16, "numChannels": 1, From 50e12420e5cc5b9839d86048a7617c8dc973e823 Mon Sep 17 00:00:00 2001 From: STC Date: Sat, 6 Jun 2026 21:13:52 +0900 Subject: [PATCH 2/6] M5Stack Core2 use ECMA-419 power peripheral and add power button --- .../esp32/targets/m5stack_core2/Core2Power.js | 158 +++++++++++++++++ .../targets/m5stack_core2/host/provider.js | 14 ++ .../esp32/targets/m5stack_core2/manifest.json | 14 +- .../targets/m5stack_core2/setup-target.js | 162 ++---------------- .../drivers/peripherals/axp2101/axp2101.js | 6 + 5 files changed, 200 insertions(+), 154 deletions(-) create mode 100644 build/devices/esp32/targets/m5stack_core2/Core2Power.js diff --git a/build/devices/esp32/targets/m5stack_core2/Core2Power.js b/build/devices/esp32/targets/m5stack_core2/Core2Power.js new file mode 100644 index 0000000000..17f27db2a0 --- /dev/null +++ b/build/devices/esp32/targets/m5stack_core2/Core2Power.js @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2026 Satoshi Tanaka + * + * This file is part of the Moddable SDK Runtime. + * + * The Moddable SDK Runtime is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Moddable SDK Runtime is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the Moddable SDK Runtime. If not, see . + * + */ + +import AXP192 from "embedded:peripheral/Power/axp192"; +import AXP2101 from "embedded:peripheral/Power/axp2101"; +import Timer from "timer"; + +class PowerAXP192 extends AXP192 { + constructor(options) { + super(options); + this.writeByte(0x30, (this.readByte(0x30) & 0x04) | 0x02); //AXP192 30H + this.writeByte(0x92, this.readByte(0x92) & 0xf8); //AXP192 GPIO1:OD OUTPUT + this.writeByte(0x93, this.readByte(0x93) & 0xf8); //AXP192 GPIO2:OD OUTPUT + this.writeByte(0x35, (this.readByte(0x35) & 0x1c) | 0xa3); //AXP192 RTC CHG + + // main power line + this._dcdc1.voltage = 3350; + this.chargeCurrent = AXP192.CHARGE_CURRENT.Ch_100mA; + + // LCD + this.lcd = this._dcdc3; + this.lcd.voltage = 2800; + + // internal LCD logic + this._ldo2.voltage = 3300; + this._ldo2.enable = true; + + // Vibration + this.vibration = this._ldo3; + this.vibration.voltage = 2000; + + // Speaker + this.speaker = this._gpio2; + + // AXP192 GPIO4 + this.writeByte(0x95, (this.readByte(0x95) & 0x72) | 0x84); + this.writeByte(0x36, 0x4c); + this.writeByte(0x82, 0xff); + // reset LCD + this._gpio4.enable = false + Timer.delay(20); + this._gpio4.enable = true + this.busPowerMode = 0; // bus power mode_output + Timer.delay(200); + } + set busPowerMode(mode) { + if (mode === 0) { + this.writeByte(0x91, (this.readByte(0x91) & 0x0f) | 0xf0); + this.writeByte(0x90, (this.readByte(0x90) & 0xf8) | 0x02); //set GPIO0 to LDO OUTPUT , pullup N_VBUSEN to disable supply from BUS_5V + this.writeByte(0x12, this.readByte(0x12) | 0x40); //set EXTEN to enable 5v boost + } else { + this.writeByte(0x12, this.readByte(0x12) & 0xbf); //set EXTEN to disable 5v boost + this.writeByte(0x90, (this.readByte(0x90) & 0xf8) | 0x01); //set GPIO0 to float , using enternal pulldown resistor to enable supply from BUS_5VS + } + } + + // value 0 - 100 % + set brightness(value) { + if (value <= 0) + value = 2500; + else if (value >= 100) + value = 3300; + else + value = (value / 100) * 800 + 2500; + this.lcd.voltage = value; + } + + get brightness() { + return (this.lcd.voltage - 2500) / 800 * 100; + } +} + +class PowerAXP2101 extends AXP2101 { + constructor(options) { + super(options); + this.writeByte(0x27, 0x00); // PowerKey Hold=1sec / PowerOff=4sec + this.writeByte(0x10, 0x30); // PMU common config (internal off-discharge enable) + this.writeByte(0x12, 0x00); // BATFET disable + this.writeByte(0x68, 0x01); // Battery detection enabled. + this.writeByte(0x69, 0x13); // CHGLED setting + this.writeByte(0x99, 0x00); // DLDO1 set 0.5v (vibration motor) + + // DCDC1&3 Enable + this.writeByte(0x80, this.readByte(0x00) | 0x04); + + // main power line + this._dcdc1.voltage = 3350; + + // LCD + this.lcd = this._bldo1; + this.lcd.voltage = 2800; + this.lcd.enable = true; + + // internal LCD logic + this._aldo4.voltage = 3300; + this._aldo4.enable = true; + + // Vibration + this.vibration = this._dldo1; + this.vibration.voltage = 2000; + + // Speaker + this.speaker = this._aldo3; + this.speaker.voltage = 3300; + + // LCD Reset + this._aldo2.voltage = 3300; + this._aldo2.enable = false; + Timer.delay(20); + this._aldo2.enable = true; + + // bus power mode_output + this._bldo2.voltage = 3300; + this._bldo2.enable = true; + Timer.delay(200); + } + // value 0 - 100 % + set brightness(value) { + if (value <= 0) value = 2500; + else if (value >= 100) value = 3300; + else value = (value / 100) * 800 + 2500; + this.lcd.voltage = value; + } + + get brightness() { + return ((this.lcd.voltage - 2500) / 800) * 100; + } +} + +export default class Core2Power { + constructor(options) { + const s = new device.io.SMBus({ + ...device.I2C.internal, + address: 0x34, + hz: 400_000 + }); + const powerICID = s.readByte(0x03); + s.close(); + return powerICID === 0x03 ? new PowerAXP192(options) : new PowerAXP2101(options) + } +} diff --git a/build/devices/esp32/targets/m5stack_core2/host/provider.js b/build/devices/esp32/targets/m5stack_core2/host/provider.js index b4d7640942..7d91e889ef 100644 --- a/build/devices/esp32/targets/m5stack_core2/host/provider.js +++ b/build/devices/esp32/targets/m5stack_core2/host/provider.js @@ -31,6 +31,7 @@ import PulseWidth from "embedded:io/pulsewidth"; import BMI270 from "embedded:sensor/Accelerometer-Gyroscope-Magnetometer/BMI270"; import MPU6886 from "embedded:sensor/Accelerometer-Gyroscope/MPU6886"; import Touch from "M5StackCoreTouch"; +import Core2Power from "Core2Power"; const ACCELERATION_SCALER = 1 / 9.80665; const IMU_ADDRESS = 0x68; @@ -136,6 +137,19 @@ const device = { throw new Error("IMU not found"); } } + }, + peripheral: { + Power: class { + constructor(options) { + return new Core2Power({ + ...options, + peripheral: { + ...device.I2C.internal, + io: device.io.SMBus + } + }); + } + } } }; diff --git a/build/devices/esp32/targets/m5stack_core2/manifest.json b/build/devices/esp32/targets/m5stack_core2/manifest.json index 3cc76d2325..a6238f9092 100644 --- a/build/devices/esp32/targets/m5stack_core2/manifest.json +++ b/build/devices/esp32/targets/m5stack_core2/manifest.json @@ -6,18 +6,20 @@ "PARTITIONS_FILE_FOR_TARGET": "./sdkconfig/partitions.csv" }, "include": [ + "$(MODDABLE)/modules/pins/smbus/manifest.json", "$(MODDABLE)/modules/io/manifest.json", "$(MODDABLE)/modules/drivers/ili9341/manifest.json", "$(MODDABLE)/modules/drivers/sensors/ft6206/manifest.json", "$(MODDABLE)/modules/drivers/sensors/bmi270/manifest.json", "$(MODDABLE)/modules/drivers/sensors/mpu6886/manifest.json", - "$(MODDABLE)/modules/drivers/axp192/manifest.json", - "$(MODDABLE)/modules/drivers/axp2101/manifest.json" + "$(MODDABLE)/modules/drivers/peripherals/axp192/manifest.json", + "$(MODDABLE)/modules/drivers/peripherals/axp2101/manifest.json" ], "config": { "screen": "ili9341", "startupSound": "bflatmajor.maud", - "startupVibration": 600 + "startupVibration": 600, + "enablePowerButton": false }, "defines": { "i2c": { @@ -84,7 +86,8 @@ "pins/smbus": "$(MODULES)/pins/smbus/smbus", "*": [ "$(MODULES)/drivers/mpu6886/*", - "./M5StackCoreTouch" + "./M5StackCoreTouch", + "./Core2Power" ] }, "preload": [ @@ -92,7 +95,8 @@ "setup/target", "mpu6886", "pins/smbus", - "M5StackCoreTouch" + "M5StackCoreTouch", + "Core2Power" ], "resources": { "*": "$(MODDABLE)/examples/assets/sounds/bflatmajor" diff --git a/build/devices/esp32/targets/m5stack_core2/setup-target.js b/build/devices/esp32/targets/m5stack_core2/setup-target.js index 13ec79dceb..498f45fe55 100644 --- a/build/devices/esp32/targets/m5stack_core2/setup-target.js +++ b/build/devices/esp32/targets/m5stack_core2/setup-target.js @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2023 Moddable Tech, Inc. + * Copyright (c) 2020-2026 Moddable Tech, Inc. * * This file is part of the Moddable SDK Runtime. * @@ -18,8 +18,6 @@ * */ -import AXP192 from "axp192"; -import AXP2101 from "axp2101"; import BMI270 from "embedded:sensor/Accelerometer-Gyroscope-Magnetometer/BMI270"; import EmbeddedSMBus from "embedded:io/smbus"; import MPU6886 from "mpu6886"; @@ -158,19 +156,19 @@ export default function (done) { c: new M5Core2Button, }; - // power - const s = new SMBus({ - ...INTERNAL_I2C, - address: 0x34, - }) - const powerICID = s.readByte(0x03) - s.close(); + globalThis.power = new device.peripheral.Power() - globalThis.power = - powerICID === 0x03 ? new PowerAXP192() : new PowerAXP2101() /* expects 0x4a */; + if (config.enablePowerButton) { + globalThis.button.power = new M5Core2Button(); + // AXP192/AXP2101 PEK reports latched press events, so expose them as a short 1 -> 0 pulse. + Timer.repeat(() => { + const state = globalThis.power.getPekState(); + globalThis.button.power.write(state ? 1 : 0); + }, 100); + } // speaker - power.speaker.enable = true; + globalThis.power.speaker.enable = true; // start-up sound if (config.startupSound) { @@ -191,10 +189,10 @@ export default function (done) { // vibration globalThis.vibration = { read: function () { - return power.vibration.enable; + return globalThis.power.vibration.enable; }, write: function (v) { - power.vibration.enable = v; + globalThis.power.vibration.enable = v; }, }; @@ -294,138 +292,4 @@ export default function (done) { done?.(); } -class PowerAXP192 extends AXP192 { - constructor() { - super(INTERNAL_I2C); - - // TODO: encapsulate direct register access by class method - this.writeByte(0x30, (this.readByte(0x30) & 0x04) | 0x02); //AXP192 30H - this.writeByte(0x92, this.readByte(0x92) & 0xf8); //AXP192 GPIO1:OD OUTPUT - this.writeByte(0x93, this.readByte(0x93) & 0xf8); //AXP192 GPIO2:OD OUTPUT - this.writeByte(0x35, (this.readByte(0x35) & 0x1c) | 0xa3); //AXP192 RTC CHG - - // main power line - this._dcdc1.voltage = 3350; - this.chargeCurrent = AXP192.CHARGE_CURRENT.Ch_100mA; - - // LCD - this.lcd = this._dcdc3; - this.lcd.voltage = 2800; - - // internal LCD logic - this._ldo2.voltage = 3300; - this._ldo2.enable = true; - - // Vibration - this.vibration = this._ldo3; - this.vibration.voltage = 2000; - - // Speaker - this.speaker = this._gpio2; - - // AXP192 GPIO4 - this.writeByte(0x95, (this.readByte(0x95) & 0x72) | 0x84); - this.writeByte(0x36, 0x4c); - this.writeByte(0x82, 0xff); - this.resetLcd(); - this.busPowerMode = 0; // bus power mode_output - Timer.delay(200); - } - - resetLcd() { - this._gpio4.enable = false - Timer.delay(20); - this._gpio4.enable = true - } - - set busPowerMode(mode) { - if (mode == 0) { - this.writeByte(0x91, (this.readByte(0x91) & 0x0f) | 0xf0); - this.writeByte(0x90, (this.readByte(0x90) & 0xf8) | 0x02); //set GPIO0 to LDO OUTPUT , pullup N_VBUSEN to disable supply from BUS_5V - this.writeByte(0x12, this.readByte(0x12) | 0x40); //set EXTEN to enable 5v boost - } else { - this.writeByte(0x12, this.readByte(0x12) & 0xbf); //set EXTEN to disable 5v boost - this.writeByte(0x90, (this.readByte(0x90) & 0xf8) | 0x01); //set GPIO0 to float , using enternal pulldown resistor to enable supply from BUS_5VS - } - } - - // value 0 - 100 % - set brightness(value) { - if (value <= 0) - value = 2500; - else if (value >= 100) - value = 3300; - else - value = (value / 100) * 800 + 2500; - this.lcd.voltage = value; - } - - get brightness() { - return (this.lcd.voltage - 2500) / 800 * 100; - } -} - -class PowerAXP2101 extends AXP2101 { - constructor() { - super(INTERNAL_I2C); - - this.writeByte(0x27, 0x00); // PowerKey Hold=1sec / PowerOff=4sec - this.writeByte(0x10, 0x30); // PMU common config (internal off-discharge enable) - this.writeByte(0x12, 0x00); // BATFET disable - this.writeByte(0x68, 0x01); // Battery detection enabled. - this.writeByte(0x69, 0x13); // CHGLED setting - this.writeByte(0x99, 0x00); // DLDO1 set 0.5v (vibration motor) - - // DCDC1&3 Enable - this.writeByte(0x80, this.readByte(0x00) | 0x04); - - // main power line - this._dcdc1.voltage = 3350; - - // LCD - this.lcd = this._bldo1; - this.lcd.voltage = 2800; - this.lcd.enable = true; - - // internal LCD logic - this._aldo4.voltage = 3300; - this._aldo4.enable = true; - - // Vibration - this.vibration = this._dldo1; - this.vibration.voltage = 2000; - - // Speaker - this.speaker = this._aldo3; - this.speaker.voltage = 3300; - - // LCD Reset - this._aldo2.voltage = 3300; - this.resetLcd(); - - // bus power mode_output - this._bldo2.voltage = 3300; - this._bldo2.enable = true; - Timer.delay(200); - } - - resetLcd() { - this._aldo2.enable = false; - Timer.delay(20); - this._aldo2.enable = true; - } - - // value 0 - 100 % - set brightness(value) { - if (value <= 0) value = 2500; - else if (value >= 100) value = 3300; - else value = (value / 100) * 800 + 2500; - this.lcd.voltage = value; - } - - get brightness() { - return ((this.lcd.voltage - 2500) / 800) * 100; - } -} - function nop() {} diff --git a/modules/drivers/peripherals/axp2101/axp2101.js b/modules/drivers/peripherals/axp2101/axp2101.js index 167f94c9f6..8f7b7fbb46 100644 --- a/modules/drivers/peripherals/axp2101/axp2101.js +++ b/modules/drivers/peripherals/axp2101/axp2101.js @@ -353,6 +353,12 @@ class AXP2101 { return ADC_LSB * this.#read12Bit(0x62) + OFFSET_DEG_C; } + getPekState() { + const state = this.readByte(0x49) & 0x0C; + if (state) this.writeByte(0x49, state); + return state; + } + #read24Bit(address) { const buff = this.readBlock(address, 3); return (buff[0] << 16) + (buff[1] << 8) + buff[2]; From 5286ec6975c52e5c528f308671a8b19281302282 Mon Sep 17 00:00:00 2001 From: STC Date: Sun, 7 Jun 2026 01:19:49 +0900 Subject: [PATCH 3/6] add RTC peripheral to M5StackCore2 --- .../esp32/targets/m5stack_core2/host/provider.js | 14 +++++++++++++- .../esp32/targets/m5stack_core2/manifest.json | 1 + 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/build/devices/esp32/targets/m5stack_core2/host/provider.js b/build/devices/esp32/targets/m5stack_core2/host/provider.js index 7d91e889ef..c883c88b21 100644 --- a/build/devices/esp32/targets/m5stack_core2/host/provider.js +++ b/build/devices/esp32/targets/m5stack_core2/host/provider.js @@ -30,6 +30,7 @@ import SPI from "embedded:io/spi"; import PulseWidth from "embedded:io/pulsewidth"; import BMI270 from "embedded:sensor/Accelerometer-Gyroscope-Magnetometer/BMI270"; import MPU6886 from "embedded:sensor/Accelerometer-Gyroscope/MPU6886"; +import RTC from "embedded:RTC/BM8563"; import Touch from "M5StackCoreTouch"; import Core2Power from "Core2Power"; @@ -149,7 +150,18 @@ const device = { } }); } - } + }, + RTC: class { + constructor(options) { + return new RTC({ + ...options, + clock: { + ...device.I2C.internal, + io: SMBus, + }, + }); + } + }, } }; diff --git a/build/devices/esp32/targets/m5stack_core2/manifest.json b/build/devices/esp32/targets/m5stack_core2/manifest.json index a6238f9092..db292c5871 100644 --- a/build/devices/esp32/targets/m5stack_core2/manifest.json +++ b/build/devices/esp32/targets/m5stack_core2/manifest.json @@ -12,6 +12,7 @@ "$(MODDABLE)/modules/drivers/sensors/ft6206/manifest.json", "$(MODDABLE)/modules/drivers/sensors/bmi270/manifest.json", "$(MODDABLE)/modules/drivers/sensors/mpu6886/manifest.json", + "$(MODDABLE)/modules/drivers/peripherals/bm8563/manifest.json", "$(MODDABLE)/modules/drivers/peripherals/axp192/manifest.json", "$(MODDABLE)/modules/drivers/peripherals/axp2101/manifest.json" ], From ed11f875b37c8389d3b47afad7a062db2fb7639d Mon Sep 17 00:00:00 2001 From: STC Date: Sun, 7 Jun 2026 02:08:07 +0900 Subject: [PATCH 4/6] refactor IMU of M5StackCore2 --- .../esp32/targets/m5stack_core2/Core2IMU.js | 71 +++++ .../targets/m5stack_core2/host/provider.js | 68 +---- .../esp32/targets/m5stack_core2/manifest.json | 17 +- .../targets/m5stack_core2/setup-target.js | 273 ++++-------------- 4 files changed, 137 insertions(+), 292 deletions(-) create mode 100644 build/devices/esp32/targets/m5stack_core2/Core2IMU.js diff --git a/build/devices/esp32/targets/m5stack_core2/Core2IMU.js b/build/devices/esp32/targets/m5stack_core2/Core2IMU.js new file mode 100644 index 0000000000..9b0438a1ae --- /dev/null +++ b/build/devices/esp32/targets/m5stack_core2/Core2IMU.js @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2026 Satoshi Tanaka + * + * This file is part of the Moddable SDK Runtime. + * + * The Moddable SDK Runtime is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Moddable SDK Runtime is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the Moddable SDK Runtime. If not, see . + * + */ + +import MPU6886 from "embedded:sensor/Accelerometer-Gyroscope/MPU6886"; +import BMI270 from "embedded:sensor/Accelerometer-Gyroscope-Magnetometer/BMI270"; + +class Core2PMU6886 extends MPU6886 { + sample() { + const sample = super.sample(); + sample.accelerometer.x *= -1; + sample.accelerometer.y *= -1; + sample.gyroscope.y *= -1; + + return sample; + } +} + +const ACCELERATION_SCALER = 1 / 9.80665; +class Core2BMI270 extends BMI270 { + sample() { + const sample = super.sample(); + + if (sample.accelerometer) { + sample.accelerometer.x *= ACCELERATION_SCALER; + sample.accelerometer.y *= ACCELERATION_SCALER; + sample.accelerometer.z *= ACCELERATION_SCALER; + } + + return sample; + } +} + +export class Core2IMU { + constructor(options) { + const s = new device.io.SMBus({ + ...device.I2C.internal, + address: 0x68, + hz: 400_000 + }); + + if (0x19 === s.readUint8(0x75)){ + s.close(); + return new Core2PMU6886(options); + } + + if (0x24 === s.readUint8(0x00)){ + s.close(); + return new Core2BMI270(options); + } + s.close(); + throw new Error("IMU not found"); + } +} +export default Core2IMU; \ No newline at end of file diff --git a/build/devices/esp32/targets/m5stack_core2/host/provider.js b/build/devices/esp32/targets/m5stack_core2/host/provider.js index c883c88b21..ee46afa38a 100644 --- a/build/devices/esp32/targets/m5stack_core2/host/provider.js +++ b/build/devices/esp32/targets/m5stack_core2/host/provider.js @@ -28,32 +28,10 @@ import Serial from "embedded:io/serial"; import SMBus from "embedded:io/smbus"; import SPI from "embedded:io/spi"; import PulseWidth from "embedded:io/pulsewidth"; -import BMI270 from "embedded:sensor/Accelerometer-Gyroscope-Magnetometer/BMI270"; -import MPU6886 from "embedded:sensor/Accelerometer-Gyroscope/MPU6886"; import RTC from "embedded:RTC/BM8563"; import Touch from "M5StackCoreTouch"; import Core2Power from "Core2Power"; - -const ACCELERATION_SCALER = 1 / 9.80665; -const IMU_ADDRESS = 0x68; -const BMI270_CHIP_ID_ADDR = 0x00; -const BMI270_CHIP_ID = 0x24; -const MPU6886_WHO_AM_I_ADDR = 0x75; -const MPU6886_WHO_AM_I = 0x19; - -class Core2BMI270 extends BMI270 { - sample() { - const sample = super.sample(); - - if (sample.accelerometer) { - sample.accelerometer.x *= ACCELERATION_SCALER; - sample.accelerometer.y *= ACCELERATION_SCALER; - sample.accelerometer.z *= ACCELERATION_SCALER; - } - - return sample; - } -} +import Core2IMU from "Core2IMU"; const device = { I2C: { @@ -117,25 +95,13 @@ const device = { }, IMU: class { constructor(options) { - const sensor = { - ...device.I2C.internal, - address: IMU_ADDRESS, - io: device.io.SMBus - }; - - if (MPU6886_WHO_AM_I === readIMURegister(MPU6886_WHO_AM_I_ADDR)) - return new MPU6886({ - ...options, - sensor - }); - - if (BMI270_CHIP_ID === readIMURegister(BMI270_CHIP_ID_ADDR)) - return new Core2BMI270({ - ...options, - sensor - }); - - throw new Error("IMU not found"); + return new Core2IMU({ + ...options, + sensor: { + ...device.I2C.internal, + io: device.io.SMBus + } + }); } } }, @@ -165,22 +131,4 @@ const device = { } }; -function readIMURegister(register) { - let io; - try { - io = new device.io.SMBus({ - data: device.I2C.internal.data, - clock: device.I2C.internal.clock, - hz: 400_000, - address: IMU_ADDRESS - }); - return io.readUint8(register); - } - catch (e) { - } - finally { - io?.close(); - } -} - export default device; diff --git a/build/devices/esp32/targets/m5stack_core2/manifest.json b/build/devices/esp32/targets/m5stack_core2/manifest.json index db292c5871..b4bd23dab7 100644 --- a/build/devices/esp32/targets/m5stack_core2/manifest.json +++ b/build/devices/esp32/targets/m5stack_core2/manifest.json @@ -6,7 +6,6 @@ "PARTITIONS_FILE_FOR_TARGET": "./sdkconfig/partitions.csv" }, "include": [ - "$(MODDABLE)/modules/pins/smbus/manifest.json", "$(MODDABLE)/modules/io/manifest.json", "$(MODDABLE)/modules/drivers/ili9341/manifest.json", "$(MODDABLE)/modules/drivers/sensors/ft6206/manifest.json", @@ -18,15 +17,9 @@ ], "config": { "screen": "ili9341", - "startupSound": "bflatmajor.maud", - "startupVibration": 600, "enablePowerButton": false }, "defines": { - "i2c": { - "sda_pin": 32, - "scl_pin": 33 - }, "spi": { "mosi_pin": 23, "miso_pin": 38, @@ -84,20 +77,18 @@ "modules": { "setup/target": "./setup-target", "pins/audioout": "$(MODULES)/pins/i2s/*", - "pins/smbus": "$(MODULES)/pins/smbus/smbus", "*": [ - "$(MODULES)/drivers/mpu6886/*", "./M5StackCoreTouch", - "./Core2Power" + "./Core2Power", + "./Core2IMU" ] }, "preload": [ "pins/audioout", "setup/target", - "mpu6886", - "pins/smbus", "M5StackCoreTouch", - "Core2Power" + "Core2Power", + "Core2IMU" ], "resources": { "*": "$(MODDABLE)/examples/assets/sounds/bflatmajor" diff --git a/build/devices/esp32/targets/m5stack_core2/setup-target.js b/build/devices/esp32/targets/m5stack_core2/setup-target.js index 498f45fe55..1f6ddd389b 100644 --- a/build/devices/esp32/targets/m5stack_core2/setup-target.js +++ b/build/devices/esp32/targets/m5stack_core2/setup-target.js @@ -17,110 +17,11 @@ * along with the Moddable SDK Runtime. If not, see . * */ - -import BMI270 from "embedded:sensor/Accelerometer-Gyroscope-Magnetometer/BMI270"; -import EmbeddedSMBus from "embedded:io/smbus"; -import MPU6886 from "mpu6886"; + import AudioOut from "pins/audioout"; import Resource from "Resource"; import Timer from "timer"; import config from "mc/config"; -import I2C from "pins/i2c"; -import SMBus from "pins/smbus"; - -const INTERNAL_I2C = Object.freeze({ - sda: 21, - scl: 22 -}); - -const INTERNAL_I2C_IO = Object.freeze({ - data: 21, - clock: 22 -}); -const ACCELERATION_SCALER = 1 / 9.80665; -const IMU_ADDRESS = 0x68; -const BMI270_CHIP_ID_ADDR = 0x00; -const BMI270_CHIP_ID = 0x24; -const MPU6886_WHO_AM_I_ADDR = 0x75; -const MPU6886_WHO_AM_I = 0x19; - -const state = { - handleRotation: nop, -}; - -class Core2BMI270 extends BMI270 { - #operation = "gyroscope"; - - constructor() { - super({ - sensor: { - ...INTERNAL_I2C_IO, - address: IMU_ADDRESS, - io: EmbeddedSMBus - } - }); - } - - configure(dictionary) { - if (dictionary?.operation) - this.#operation = dictionary.operation; - } - - sample() { - const sample = super.sample(); - let result; - - switch (this.#operation) { - case "accelerometer": - result = sample.accelerometer; - if (result) { - result.x *= ACCELERATION_SCALER; - result.y *= ACCELERATION_SCALER; - result.z *= ACCELERATION_SCALER; - } - return result; - case "gyroscope": - return sample.gyroscope; - case "temp": - return sample.thermometer?.temperature; - } - } -} - -function createAccelerometerGyro() { - if (MPU6886_WHO_AM_I === readIMURegister(MPU6886_WHO_AM_I_ADDR)) - return new MPU6886(INTERNAL_I2C); - - if (BMI270_CHIP_ID === readIMURegister(BMI270_CHIP_ID_ADDR)) - return new Core2BMI270; -} - -function getAccelerometerGyro() { - if (undefined === state.accelerometerGyro) - state.accelerometerGyro = createAccelerometerGyro(); - return state.accelerometerGyro; -} - -function readIMURegister(register) { - let io; - try { - io = new I2C({...INTERNAL_I2C, address: IMU_ADDRESS, throw: false}); - let result = io.write(register, false); - if (result instanceof Error) - return; - - result = io.read(1); - if (result instanceof Error) - return; - - return result?.[0]; - } - catch (e) { - } - finally { - io?.close(); - } -} globalThis.Host = { Backlight: class { @@ -131,31 +32,32 @@ globalThis.Host = { if (undefined !== globalThis.power) globalThis.power.brightness = value; } - close() {} + close() { } } } class M5Core2Button { // M5StackCoreTouch calls write when button changes - #value = 0; - read() { - return this.#value; - } - write(value) { - if (this.#value === value) - return; - this.#value = value; - this.onChanged?.(); - } + #value = 0; + read() { + return this.#value; + } + write(value) { + if (this.#value === value) + return; + this.#value = value; + this.onChanged?.(); + } } export default function (done) { - // buttons - globalThis.button = { - a: new M5Core2Button, - b: new M5Core2Button, - c: new M5Core2Button, - }; + // buttons + globalThis.button = { + a: new M5Core2Button, + b: new M5Core2Button, + c: new M5Core2Button, + }; + // power globalThis.power = new device.peripheral.Power() if (config.enablePowerButton) { @@ -167,129 +69,62 @@ export default function (done) { }, 100); } - // speaker - globalThis.power.speaker.enable = true; + // speaker + globalThis.power.speaker.enable = true; - // start-up sound - if (config.startupSound) { - const speaker = new AudioOut({streams: 1}); - speaker.callback = function () { - this.stop(); - this.close(); - Timer.set(this.done); - }; - speaker.done = done; - done = undefined; + // start-up sound + if (config.startupSound) { + const speaker = new AudioOut({ streams: 1 }); + speaker.callback = function () { + this.stop(); + this.close(); + Timer.set(this.done); + }; + speaker.done = done; + done = undefined; - speaker.enqueue(0, AudioOut.Samples, new Resource(config.startupSound)); - speaker.enqueue(0, AudioOut.Callback, 0); - speaker.start(); - } + speaker.enqueue(0, AudioOut.Samples, new Resource(config.startupSound)); + speaker.enqueue(0, AudioOut.Callback, 0); + speaker.start(); + } // vibration globalThis.vibration = { - read: function () { + read: () => { return globalThis.power.vibration.enable; }, - write: function (v) { + write: (v) => { globalThis.power.vibration.enable = v; }, }; if (config.startupVibration) { - vibration.write(true); + globalThis.vibration.write(true); Timer.set(() => { - vibration.write(false); + globalThis.vibration.write(false); }, config.startupVibration); } - // accelerometer and gyrometer - globalThis.accelerometer = { - onreading: nop, - }; - - globalThis.gyro = { - onreading: nop, - }; - - accelerometer.start = function (frequency) { - accelerometer.stop(); - state.accelerometerTimerID = Timer.repeat((id) => { - const accelerometerGyro = getAccelerometerGyro(); - if (undefined === accelerometerGyro) - return; - - accelerometerGyro.configure({ - operation: "accelerometer", - }); - const sample = accelerometerGyro.sample(); - if (sample) { - state.handleRotation(sample); - accelerometer.onreading(sample); - } - }, frequency); - }; - - gyro.start = function (frequency) { - gyro.stop(); - state.gyroTimerID = Timer.repeat((id) => { - const accelerometerGyro = getAccelerometerGyro(); - if (undefined === accelerometerGyro) - return; - - accelerometerGyro.configure({ - operation: "gyroscope", - }); - const sample = accelerometerGyro.sample(); - if (sample) { - let { x, y, z } = sample; - const temp = x; - x = y * -1; - y = temp * -1; - z *= -1; - gyro.onreading({ - x, - y, - z, - }); - } - }, frequency); - }; - - accelerometer.stop = function () { - if (undefined !== state.accelerometerTimerID) - Timer.clear(state.accelerometerTimerID); - delete state.accelerometerTimerID; - }; - - gyro.stop = function () { - if (undefined !== state.gyroTimerID) Timer.clear(state.gyroTimerID); - delete state.gyroTimerID; - }; - - // autorotate - if (config.autorotate && globalThis.Application && globalThis.accelerometer) { - state.handleRotation = function (reading) { - if (globalThis.application === undefined) return; - - if (Math.abs(reading.y) > Math.abs(reading.x)) { - if (reading.y < -0.7 && application.rotation != 180) { - application.rotation = 180; - } else if (reading.y > 0.7 && application.rotation != 0) { - application.rotation = 0; - } - } else { - if (reading.x < -0.7 && application.rotation != 270) { + if (config.autorotate && globalThis.Application) { + const imu = new device.sensor.IMU(); + Timer.repeat(id => { + const sample = imu.sample(); + const { x, y } = sample.accelerometer; + if (Math.abs(y) > Math.abs(x)) { + if (y < -0.7 && application.rotation !== 270) { application.rotation = 270; - } else if (reading.x > 0.7 && application.rotation != 90) { + } else if (y > 0.7 && application.rotation !== 90) { application.rotation = 90; } + } else { + if (x < -0.7 && application.rotation !== 180) { + application.rotation = 180; + } else if (x > 0.7 && application.rotation !== 0) { + application.rotation = 0; + } } - }; - accelerometer.start(300); + }, 300); } done?.(); } - -function nop() {} From 09779db402e3b56c2d77dea54f36688445a1898e Mon Sep 17 00:00:00 2001 From: STC Date: Sun, 7 Jun 2026 02:52:46 +0900 Subject: [PATCH 5/6] add M5StackCore2 to ESP32.md --- documentation/assets/devices/m5stack_core2.png | Bin 0 -> 56964 bytes documentation/devices/esp32.md | 3 ++- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 documentation/assets/devices/m5stack_core2.png diff --git a/documentation/assets/devices/m5stack_core2.png b/documentation/assets/devices/m5stack_core2.png new file mode 100644 index 0000000000000000000000000000000000000000..8a13fa5659a43dfbf0ed0eee758183bc6822b741 GIT binary patch literal 56964 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
Moddable Two | `esp32/moddable_two`
`simulator/moddable_two` | **2.4" IPS display**
240 x 320 QVGA
16-bit color
Capacitive touch

20 External pins |
  • [Moddable Two developer guide](./moddable-two.md)
  • [Moddable product page](https://www.moddable.com/hardware)
  • | |
    Moddable Display 2 | `esp32/moddable_display_2`
    `simulator/moddable_two` | **2.4" IPS display**
    240 x 320 QVGA
    16-bit color
    Capacitive touch

    20 External pins |
  • [Moddable Display developer guide](./moddable-display.md)
  • [Moddable product page](https://www.moddable.com/hardware)
  • | | ![ESP32](./../assets/devices/esp32.png)
    Node MCU ESP32 | `esp32/nodemcu` | | -| ![M5Stack](./../assets/devices/m5stack.png)
    M5Stack | `esp32/m5stack`
    `esp32/m5stack_core2` | **1.8" LCD display**
    320 x 240 QVGA
    16-bit color

    Audio playback
    Accelerometer
    NeoPixels |
  • [Product page](https://m5stack.com/collections/m5-core/products/basic-core-iot-development-kit)
  • | +| ![M5Stack](./../assets/devices/m5stack.png)
    M5Stack | `esp32/m5stack` | **1.8" LCD display**
    320 x 240 QVGA
    16-bit color

    Audio playback
    NeoPixels |
  • [Product page](https://m5stack.com/collections/m5-core/products/basic-core-iot-development-kit)
  • | | ![M5Stack Fire](./../assets/devices/m5stack-fire.png)
    M5Stack Fire | `esp32/m5stack_fire` | **1.8" LCD display**
    320 x 240 QVGA
    16-bit color

    Audio playback
    Accelerometer
    NeoPixels |
  • [Product page](https://m5stack.com/collections/m5-core/products/fire-iot-development-kit?variant=16804798169178)
  • | | ![M5Stick C](./../assets/devices/m5stick-c.png)
    M5Stick C | `esp32/m5stick_c`
    `simulator/m5stick_c` | **0.96" LCD display**
    80 x 160
    16-bit color

    IMU
    Microphone |
  • [Product page](https://m5stack.com/collections/m5-core/products/stick-c?variant=17203451265114)
  • | | ![M5Stick C PLUS](./../assets/devices/m5stick-cplus.png)
    M5Stick C PLUS | `esp32/m5stick_cplus` | **1.14" LCD display**
    135 x 240
    16-bit color

    IMU
    Microphone |
  • [Product page](https://docs.m5stack.com/en/core/m5stickc_plus)
  • | +| ![M5Stack Core2](./../assets/devices/m5stack_core2.png)
    M5Stack Core2 | `esp32/m5stack_core2` | **1.8" LCD display**
    320 x 240 QVGA
    16-bit color
    Touch screen

    Speaker
    Microphone
    IMU
    BM8563 RTC
    8MB PSRAM |
  • [Product page](https://shop.m5stack.com/products/m5stack-core2-esp32-iot-development-kit)
  • | | ![M5Atom](./../assets/devices/m5atom.png)
    M5Atom | `esp32/m5atom_echo`
    `esp32/m5atom_lite`
    `esp32/m5atom_matrix` | 5 x 5 RGB LED matrix panel

    MPU6886 Inertial Sensor
    6 External Pins |
  • [Product page](https://m5stack.com/collections/m5-atom/products/atom-matrix-esp32-development-kit)
  • | | ![M5AtomU](./../assets/devices/m5atomu.png)
    M5AtomU | `esp32/m5atom_u` | Neopixel, 1 button
    Microphone
    6 External Pins |
  • [Product page](https://docs.m5stack.com/en/core/atom_us)
  • | |
    M5 Paper | `esp32/m5paper`
    `simulator/m5paper` | **960 x 540 ePaper touch screen**
    Temperature sensor |
  • [Product page](https://shop.m5stack.com/products/m5paper-esp32-development-kit-960x540-4-7-eink-display-235-ppi?variant=37595977908396)
  • [Moddable SDK docs](./m5paper.md)
  • | From b4c5128af534524612f0652869d2d0e2f65de796 Mon Sep 17 00:00:00 2001 From: STC Date: Sun, 7 Jun 2026 03:25:05 +0900 Subject: [PATCH 6/6] add power button to M5Stack CoreS3 --- .../esp32/targets/m5stack_cores3/manifest.json | 3 ++- .../targets/m5stack_cores3/setup-target.js | 17 +++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/build/devices/esp32/targets/m5stack_cores3/manifest.json b/build/devices/esp32/targets/m5stack_cores3/manifest.json index dc3b05a87c..03ff8366ec 100644 --- a/build/devices/esp32/targets/m5stack_cores3/manifest.json +++ b/build/devices/esp32/targets/m5stack_cores3/manifest.json @@ -24,7 +24,8 @@ "frameSize": "QVGA" }, "virtualButton": false, - "startupSound": "bflatmajor.maud" + "startupSound": "bflatmajor.maud", + "enablePowerButton": false }, "modules": { "setup/target": "./setup-target", diff --git a/build/devices/esp32/targets/m5stack_cores3/setup-target.js b/build/devices/esp32/targets/m5stack_cores3/setup-target.js index 9d7adcc154..cf8af85d24 100644 --- a/build/devices/esp32/targets/m5stack_cores3/setup-target.js +++ b/build/devices/esp32/targets/m5stack_cores3/setup-target.js @@ -62,6 +62,8 @@ export default function (done) { b: new M5CoreS3Button(), c: new M5CoreS3Button(), }; + } else { + globalThis.button = {} } // power @@ -70,6 +72,15 @@ export default function (done) { globalThis.mic = new ES7210({sensor: { ...device.I2C.internal, io: device.io.SMBus }}); globalThis.mic.init(); + if (config.enablePowerButton) { + globalThis.button.power = new M5CoreS3Button(); + // AXP2101 PEK reports latched press events, so expose them as a short 1 -> 0 pulse. + Timer.repeat(() => { + const state = globalThis.power.getPekState(); + globalThis.button.power.write(state ? 1 : 0); + }, 100); + } + // start-up sound if (config.startupSound) { const buf = new Resource(config.startupSound); @@ -215,7 +226,7 @@ class Power { // extends AXP2101 { constructor(options) { const axp2101 = this.#axp2101 = new AXP2101({ address:0x34, ...options }); axp2101.writeByte(0x90, 0xbf); - axp2101.writeByte(0x92, 13); + axp2101.writeByte(0x92, 13); axp2101.writeByte(0x93, 28); axp2101.writeByte(0x94, 28); axp2101.writeByte(0x95, 28); @@ -241,7 +252,9 @@ class Power { // extends AXP2101 { Timer.delay(20); this.expander.writeByteMask(0x03, 0b00100000, 0xff); } - + getPekState() { + return this.#axp2101.getPekState(); + } } /**