diff --git a/build/devies/esp32/targets/m5atom_s3r/host/provider.js b/build/devies/esp32/targets/m5atom_s3r/host/provider.js
new file mode 100644
index 000000000..a7a99c87c
--- /dev/null
+++ b/build/devies/esp32/targets/m5atom_s3r/host/provider.js
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2025 Moddable Tech, Inc.
+ *
+ * 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 Analog from "embedded:io/analog";
+import Digital from "embedded:io/digital";
+import DigitalBank from "embedded:io/digitalbank";
+import I2C from "embedded:io/i2c";
+import PulseCount from "embedded:io/pulsecount";
+import PWM from "embedded:io/pwm";
+import Serial from "embedded:io/serial";
+import SMBus from "embedded:io/smbus";
+import SPI from "embedded:io/spi";
+import Timer from "timer";
+import IMU from "embedded:sensor/Accelerometer-Gyroscope-Magnetometer/BMI270";
+
+const ACCELERATION_SCALER = 1 / 9.80665;
+
+class Backlight {
+ #io;
+
+ constructor(options) {
+ const io = this.#io = new SMBus({
+ ...device.I2C.internal,
+ hz: 400_000,
+ address:48,
+ });
+
+ io.writeUint8(0x00, 0b01000000)
+ Timer.delay(1)
+ io.writeUint8(0x08, 0b00000001)
+ io.writeUint8(0x70, 0b00000000)
+ }
+ close() {
+ this.#io?.close();
+ this.#io = undefined;
+ }
+ set brightness(value) {
+ if (value <= 0) value = 0;
+ else if (value >= 1) value = 255;
+ else value *= 255;
+ this.#io.writeUint8(0x0e, value)
+ }
+}
+
+const device = {
+ I2C: {
+ default: {
+ io: I2C,
+ data: 2,
+ clock: 1,
+ },
+ internal: {
+ io: I2C,
+ data: 45,
+ clock: 0,
+ },
+ },
+ SPI: {
+ default: {
+ io: SPI,
+ port: 3,
+ clock: 15,
+ out: 21,
+ },
+ },
+ Analog: {
+ default: {
+ io: Analog,
+ pin: 8,
+ },
+ },
+ io: {
+ Analog,
+ Digital,
+ DigitalBank,
+ I2C,
+ PulseCount,
+ PWM,
+ Serial,
+ SMBus,
+ SPI,
+ },
+ pin: {
+ button: 41,
+ displaySelect: 14,
+ },
+ peripheral: {
+ Backlight: class {
+ constructor() {
+ return new Backlight();
+ }
+ },
+ },
+ sensor: {
+ IMU: class extends IMU {
+ constructor(options) {
+ super({
+ ...options,
+ sensor: {
+ ...device.I2C.internal,
+ address: 0x68,
+ io: device.io.SMBus,
+ },
+ });
+ }
+ sample() {
+ const sample = super.sample();
+
+ if (sample.accelerometer) {
+ [sample.accelerometer.x, sample.accelerometer.y] = [-sample.accelerometer.y * ACCELERATION_SCALER, sample.accelerometer.x * ACCELERATION_SCALER];
+ sample.accelerometer.z *= -ACCELERATION_SCALER;
+ }
+ if (sample.gyroscope) {
+ [sample.gyroscope.x, sample.gyroscope.y] = [-sample.gyroscope.y, sample.gyroscope.x];
+ sample.gyroscope.z *= -1;
+ }
+
+ return sample;
+ }
+ },
+ },
+};
+
+export default device;
diff --git a/build/devies/esp32/targets/m5atom_s3r/manifest.json b/build/devies/esp32/targets/m5atom_s3r/manifest.json
new file mode 100644
index 000000000..51a1ac744
--- /dev/null
+++ b/build/devies/esp32/targets/m5atom_s3r/manifest.json
@@ -0,0 +1,94 @@
+{
+ "build":{
+ "ESP32_SUBCLASS": "esp32s3",
+ "USE_USB": "2",
+ "SDKCONFIGPATH": "./sdkconfig",
+ "PARTITIONS_FILE_FOR_TARGET": "./sdkconfig/partitions.csv",
+ "PROGRAMMING_MODE_MESSAGE": "INSTRUCTIONS: Press and hold the button until the LED lights.",
+ "BEFORE_DEBUGGING_MESSAGE": "Press and release the Reset button."
+ },
+ "include": [
+ "$(MODDABLE)/modules/io/manifest.json",
+ "$(MODDABLE)/modules/drivers/ili9341/manifest.json",
+ "$(MODDABLE)/modules/drivers/sensors/bmi270/manifest.json",
+ "$(MODULES)/drivers/button/manifest.json"
+ ],
+ "modules": {
+ "*": [
+ "../m5stack_fire/m5button"
+ ],
+ "setup/target": "./setup-target"
+ },
+ "preload": [
+ "setup/target",
+ "m5button"
+ ],
+ "config": {
+ "screen": "ili9341",
+ "touch": ""
+ },
+ "creation": {
+ "static": 0,
+ "chunk": {
+ "initial": 78848,
+ "incremental": 0
+ },
+ "heap": {
+ "initial": 4928,
+ "incremental": 0
+ },
+ "stack": 512
+ },
+ "defines": {
+ "i2c": {
+ "sda_pin": 45,
+ "scl_pin": 0
+ },
+ "spi": {
+ "mosi_pin":21,
+ "sck_pin": 15
+ },
+ "ili9341": {
+ "hz": 27000000,
+ "width": 128,
+ "height": 128,
+ "cs_pin": 14,
+ "rst_pin": 48,
+ "dc_pin": 42,
+ "column_offset": 0,
+ "row_offset": 32,
+ "spi_port": "SPI3_HOST",
+ "registers": [
+ "0xFE, 0,",
+ "kDelayMS, 10,",
+ "0xEF, 0,",
+ "kDelayMS, 10,",
+ "0x36, 1, 0x08,",
+ "0xB0, 1, 0xC0,",
+ "0xB2, 1, 0x2F,",
+ "0xB3, 1, 0x03,",
+ "0xB6, 1, 0x19,",
+ "0xB7, 1, 0x01,",
+ "0xAC, 1, 0xCB,",
+ "0xAB, 1, 0x0E,",
+ "0xB4, 1, 0x04,",
+ "0xA8, 1, 0x19,",
+ "0x3A, 1, 0x05,",
+ "0xB8, 1, 0x08,",
+ "0xE8, 1, 0x24,",
+ "0xE9, 1, 0x48,",
+ "0xEA, 1, 0x22,",
+ "0xC6, 1, 0x30,",
+ "0xC7, 1, 0x18,",
+ "0xF0, 14, 0x1F,0x28,0x04,0x3E,0x2A,0x2E,0x20,0x00,0x0C,0x06,0x00,0x1C,0x1F,0x0F,",
+ "0xF1, 14, 0x00,0x2D,0x2F,0x3C,0x6F,0x1C,0x0B,0x00,0x00,0x00,0x07,0x0D,0x11,0x0F,",
+ "0x20, 0,",
+ "0x11, 0,",
+ "kDelayMS, 120,",
+ "0x29, 0,",
+ "kDelayMS, 20,",
+ "kDelayMS, 0"
+ ]
+ }
+ }
+}
diff --git a/build/devies/esp32/targets/m5atom_s3r_cam/host/provider.js b/build/devies/esp32/targets/m5atom_s3r_cam/host/provider.js
new file mode 100644
index 000000000..60dbb1a12
--- /dev/null
+++ b/build/devies/esp32/targets/m5atom_s3r_cam/host/provider.js
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2021-2024 Moddable Tech, Inc.
+ *
+ * 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 Analog from "embedded:io/analog";
+import Digital from "embedded:io/digital";
+import DigitalBank from "embedded:io/digitalbank";
+import I2C from "embedded:io/i2c";
+import PulseCount from "embedded:io/pulsecount";
+import PWM from "embedded:io/pwm";
+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 IMU from "embedded:sensor/Accelerometer-Gyroscope-Magnetometer/BMI270";
+
+const device = {
+ I2C: {
+ default: {
+ io: I2C,
+ data: 45,
+ clock: 0,
+ },
+ internal: {
+ io: I2C,
+ data: 45,
+ clock: 0,
+ }
+ },
+ Serial: {
+ default: {
+ io: Serial,
+ port: 1,
+ receive: 44,
+ transmit: 43
+ }
+ },
+ SPI: {
+ default: {
+ io: SPI,
+ clock: 36,
+ in: 37,
+ out: 35,
+ port: 2
+ }
+ },
+ io: {Analog, Digital, DigitalBank, I2C, PulseCount, PulseWidth, PWM, Serial, SMBus, SPI},
+ pin: {
+ //@@ button
+ button: 0
+ },
+ sensor: {
+ IMU: class {
+ constructor(options) {
+ return new IMU({
+ ...options,
+ sensor: {
+ ...device.I2C.internal,
+ address: 0x68,
+ io: device.io.SMBus
+ }
+ });
+ }
+ }
+ }
+};
+
+export default device;
diff --git a/build/devies/esp32/targets/m5atom_s3r_cam/manifest.json b/build/devies/esp32/targets/m5atom_s3r_cam/manifest.json
new file mode 100644
index 000000000..f5b6e6ede
--- /dev/null
+++ b/build/devies/esp32/targets/m5atom_s3r_cam/manifest.json
@@ -0,0 +1,63 @@
+{
+ "build": {
+ "SDKCONFIGPATH": "./sdkconfig",
+ "PARTITIONS_FILE_FOR_TARGET": "./sdkconfig/partitions.csv",
+ "ESP32_SUBCLASS": "esp32s3",
+ "USE_USB": "2"
+ },
+ "include": [
+ "$(MODDABLE)/modules/io/manifest.json",
+ "$(MODDABLE)/modules/drivers/sensors/bmi270/manifest.json",
+ "$(MODULES)/drivers/button/manifest.json"
+ ],
+ "modules": {
+ "setup/target": "./setup-target"
+ },
+ "preload": [
+ "setup/target"
+ ],
+ "config": {
+ "Screen": "",
+ "button_pin": 41,
+ "power_pin": 18,
+ "ir_pin": 47
+ },
+ "creation": {
+ "static": 0,
+ "chunk": {
+ "initial": 78848,
+ "incremental": 0
+ },
+ "heap": {
+ "initial": 4928,
+ "incremental": 0
+ },
+ "stack": 512
+ },
+ "defines": {
+ "i2c": {
+ "sda_pin": 45,
+ "scl_pin": 0
+ },
+ "camera": {
+ "powerdown": -1,
+ "reset": -1,
+ "xclk": 21,
+ "pclk": 40,
+ "href": 14,
+ "vsync": 10,
+ "scl": 9,
+ "sda": 12,
+ "i2c_port": 1,
+ "d0": 3,
+ "d1": 42,
+ "d2": 46,
+ "d3": 48,
+ "d4": 4,
+ "d5": 17,
+ "d6": 11,
+ "d7": 13
+ }
+ }
+}
+
diff --git a/build/devies/esp32/targets/m5atom_s3r_m12/host/provider.js b/build/devies/esp32/targets/m5atom_s3r_m12/host/provider.js
new file mode 100644
index 000000000..60dbb1a12
--- /dev/null
+++ b/build/devies/esp32/targets/m5atom_s3r_m12/host/provider.js
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2021-2024 Moddable Tech, Inc.
+ *
+ * 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 Analog from "embedded:io/analog";
+import Digital from "embedded:io/digital";
+import DigitalBank from "embedded:io/digitalbank";
+import I2C from "embedded:io/i2c";
+import PulseCount from "embedded:io/pulsecount";
+import PWM from "embedded:io/pwm";
+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 IMU from "embedded:sensor/Accelerometer-Gyroscope-Magnetometer/BMI270";
+
+const device = {
+ I2C: {
+ default: {
+ io: I2C,
+ data: 45,
+ clock: 0,
+ },
+ internal: {
+ io: I2C,
+ data: 45,
+ clock: 0,
+ }
+ },
+ Serial: {
+ default: {
+ io: Serial,
+ port: 1,
+ receive: 44,
+ transmit: 43
+ }
+ },
+ SPI: {
+ default: {
+ io: SPI,
+ clock: 36,
+ in: 37,
+ out: 35,
+ port: 2
+ }
+ },
+ io: {Analog, Digital, DigitalBank, I2C, PulseCount, PulseWidth, PWM, Serial, SMBus, SPI},
+ pin: {
+ //@@ button
+ button: 0
+ },
+ sensor: {
+ IMU: class {
+ constructor(options) {
+ return new IMU({
+ ...options,
+ sensor: {
+ ...device.I2C.internal,
+ address: 0x68,
+ io: device.io.SMBus
+ }
+ });
+ }
+ }
+ }
+};
+
+export default device;
diff --git a/build/devies/esp32/targets/m5atom_s3r_m12/manifest.json b/build/devies/esp32/targets/m5atom_s3r_m12/manifest.json
new file mode 100644
index 000000000..19781b65f
--- /dev/null
+++ b/build/devies/esp32/targets/m5atom_s3r_m12/manifest.json
@@ -0,0 +1,64 @@
+{
+ "build": {
+ "SDKCONFIGPATH": "./sdkconfig",
+ "PARTITIONS_FILE_FOR_TARGET": "./sdkconfig/partitions.csv",
+ "ESP32_SUBCLASS": "esp32s3",
+ "USE_USB": "2"
+ },
+ "include": [
+ "$(MODDABLE)/modules/io/manifest.json",
+ "$(MODDABLE)/modules/drivers/sensors/bmi270/manifest.json",
+ "$(MODULES)/drivers/button/manifest.json"
+ ],
+ "modules": {
+ "setup/target": "./setup-target"
+ },
+ "preload": [
+ "setup/target"
+ ],
+ "config": {
+ "Screen": "",
+ "button_pin": 41,
+ "power_pin": 18,
+ "ir_pin": 47
+ },
+ "creation": {
+ "static": 0,
+ "chunk": {
+ "initial": 78848,
+ "incremental": 0
+ },
+ "heap": {
+ "initial": 4928,
+ "incremental": 0
+ },
+ "stack": 512
+ },
+ "defines": {
+ "i2c": {
+ "sda_pin": 45,
+ "scl_pin": 0
+ },
+ "camera": {
+ "powerdown": -1,
+ "reset": -1,
+ "xclk": 21,
+ "xclk_freq_hz": 20000000,
+ "pclk": 40,
+ "href": 14,
+ "vsync": 10,
+ "scl": 9,
+ "sda": 12,
+ "i2c_port": 1,
+ "d0": 3,
+ "d1": 42,
+ "d2": 46,
+ "d3": 48,
+ "d4": 4,
+ "d5": 17,
+ "d6": 11,
+ "d7": 13
+ }
+ }
+}
+
diff --git a/examples/drivers/atoms3-imu/main.js b/examples/drivers/atoms3-imu/main.js
index db59f5d0e..ee6ee1e0e 100644
--- a/examples/drivers/atoms3-imu/main.js
+++ b/examples/drivers/atoms3-imu/main.js
@@ -50,11 +50,10 @@ imu.configure({
})
Timer.repeat(() => {
const sample = imu.sample();
- if(flag) {
- onReading(sample.accelerometer, "a");
- } else {
- onReading(sample.gyroscope, "g");
- }
+ const values = flag ? sample.accelerometer : sample.gyroscope;
+
+ if (values)
+ onReading(values, flag ? "a" : "g");
}, 17)
let flag = true;
diff --git a/examples/drivers/sensors/bmi270/poll/main.js b/examples/drivers/sensors/bmi270/poll/main.js
new file mode 100644
index 000000000..d655a5951
--- /dev/null
+++ b/examples/drivers/sensors/bmi270/poll/main.js
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2026 Moddable Tech, Inc.
+ *
+ * This file is part of the Moddable SDK.
+ *
+ * This work is licensed under the
+ * Creative Commons Attribution 4.0 International License.
+ * To view a copy of this license, visit
+ * .
+ * or send a letter to Creative Commons, PO Box 1866,
+ * Mountain View, CA 94042, USA.
+ *
+ */
+
+import BMI270 from "embedded:sensor/Accelerometer-Gyroscope-Magnetometer/BMI270";
+import Timer from "timer";
+
+const i2c = device.I2C.internal ?? device.I2C.default;
+
+const sensor = new BMI270({
+ sensor: {
+ ...i2c,
+ io: device.io.SMBus
+ }
+});
+
+Timer.repeat(() => {
+ const sample = sensor.sample();
+
+ trace("Accel: ");
+ traceVector(sample.accelerometer);
+ trace(" - Gyro: ");
+ traceVector(sample.gyroscope);
+
+ if (sample.thermometer)
+ trace(` - Temp: ${sample.thermometer.temperature.toFixed(2)} C`);
+
+ trace("\n");
+}, 1000);
+
+function traceVector(values) {
+ if (!values) {
+ trace("[pending]");
+ return;
+ }
+
+ trace(`[${format(values.x)}, ${format(values.y)}, ${format(values.z)}]`);
+}
+
+function format(value) {
+ return (undefined === value) ? "n/a" : value.toFixed(3);
+}
diff --git a/examples/drivers/sensors/bmi270/poll/manifest.json b/examples/drivers/sensors/bmi270/poll/manifest.json
new file mode 100644
index 000000000..4fa271040
--- /dev/null
+++ b/examples/drivers/sensors/bmi270/poll/manifest.json
@@ -0,0 +1,10 @@
+{
+ "include": [
+ "$(MODDABLE)/examples/manifest_base.json",
+ "$(MODDABLE)/modules/io/manifest.json",
+ "$(MODDABLE)/modules/drivers/sensors/bmi270/manifest.json"
+ ],
+ "modules": {
+ "*": "./main"
+ }
+}
diff --git a/modules/drivers/co5300/co5300.js b/modules/drivers/co5300/co5300.js
index 03057c3c9..b5e66843f 100644
--- a/modules/drivers/co5300/co5300.js
+++ b/modules/drivers/co5300/co5300.js
@@ -38,6 +38,8 @@ export default class CO5300 @ "xs_co5300_destructor" {
get width() @ "xs_co5300_get_width";
get height() @ "xs_co5300_get_height";
get async() {return true;}
+ get rotation() @ "xs_co5300_get_rotation";
+ set rotation(value) @ "xs_co5300_set_rotation";
get c_dispatch() @ "xs_co5300_get_c_dispatch";
diff --git a/modules/drivers/co5300/modCo5300.c b/modules/drivers/co5300/modCo5300.c
index 04f2112ed..312438a5b 100644
--- a/modules/drivers/co5300/modCo5300.c
+++ b/modules/drivers/co5300/modCo5300.c
@@ -76,6 +76,9 @@
*/
#define CO5300_CMD(reg) ((0x02 << 24) | ((reg) << 8))
#define CO5300_COLOR_CMD ((0x32 << 24) | (0x2C << 8))
+#define CO5300_MADCTL_MY (0x80)
+#define CO5300_MADCTL_MX (0x40)
+#define CO5300_ROTATE_BUFFER_PIXELS ((((MODDEF_CO5300_WIDTH) > (MODDEF_CO5300_HEIGHT)) ? (MODDEF_CO5300_WIDTH) : (MODDEF_CO5300_HEIGHT)) * 2)
typedef struct {
PixelsOutDispatch dispatch;
@@ -85,6 +88,9 @@ typedef struct {
#endif
int updateWidth;
+ int updateHeight;
+ int updateX;
+ int updateY;
int updateLinesRemaining;
int yMin;
int yMax;
@@ -92,6 +98,8 @@ typedef struct {
uint8_t nothingSent;
uint8_t firstFrame;
uint8_t brightnessValue;
+ uint8_t rotation; // 0, 1, 2, 3 => 0, 90, 180, 270
+ uint8_t colorSlotReserved;
SemaphoreHandle_t colorsInFlight;
esp_lcd_panel_io_handle_t io_handle;
@@ -100,9 +108,16 @@ typedef struct {
int opZero;
uint8_t data[32];
+ uint16_t *rotateBuffer;
} co5300DisplayRecord, *co5300Display;
static void co5300Init(co5300Display sd);
+static void co5300SetMADCTL(co5300Display sd);
+static void co5300SetWindow(co5300Display sd, uint16_t x, uint16_t y, uint16_t w, uint16_t h);
+static void co5300WaitForColors(co5300Display sd);
+static void co5300SendColorSync(co5300Display sd, const void *pixels, int byteLength);
+static void co5300SendRotated(co5300Display sd, uint16_t *pixels, int byteLength);
+static void co5300SendHardwareRotated180(co5300Display sd, uint16_t *pixels, int byteLength);
#define co5300Command(sd, command, data, count) \
(esp_lcd_panel_io_tx_param(sd->io_handle, command, data, count))
@@ -143,6 +158,9 @@ void xs_co5300_destructor(void *data)
if (sd->colorsInFlight)
vSemaphoreDelete(sd->colorsInFlight);
+ if (sd->rotateBuffer)
+ c_free(sd->rotateBuffer);
+
c_free(data);
}
@@ -177,6 +195,13 @@ void xs_co5300(xsMachine *the)
xsmcSetHostData(xsThis, sd);
+ sd->rotateBuffer = c_malloc(CO5300_ROTATE_BUFFER_PIXELS * sizeof(uint16_t));
+ if (!sd->rotateBuffer) {
+ c_free(sd);
+ xsmcSetHostData(xsThis, NULL);
+ xsUnknownError("no memory");
+ }
+
spi_bus_config_t buscfg = {
.sclk_io_num = MODDEF_CO5300_SCK_PIN,
.data0_io_num = MODDEF_CO5300_DATA0_PIN,
@@ -189,6 +214,7 @@ void xs_co5300(xsMachine *the)
int err = spi_bus_initialize(MODDEF_CO5300_SPI_PORT, &buscfg, SPI_DMA_CH_AUTO);
if (err) {
+ c_free(sd->rotateBuffer);
c_free(sd);
xsmcSetHostData(xsThis, NULL);
xsUnknownError("spi_bus_initialize failed");
@@ -212,6 +238,7 @@ void xs_co5300(xsMachine *the)
err = esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)MODDEF_CO5300_SPI_PORT, &io_config, &sd->io_handle);
if (err) {
spi_bus_free(MODDEF_CO5300_SPI_PORT);
+ c_free(sd->rotateBuffer);
c_free(sd);
xsmcSetHostData(xsThis, NULL);
xsUnknownError("esp_lcd_new_panel_io_spi failed");
@@ -295,12 +322,32 @@ void xs_co5300_get_pixelFormat(xsMachine *the)
void xs_co5300_get_width(xsMachine *the)
{
- xsmcSetInteger(xsResult, MODDEF_CO5300_WIDTH);
+ co5300Display sd = xsmcGetHostData(xsThis);
+ xsmcSetInteger(xsResult, (sd->rotation & 1) ? MODDEF_CO5300_HEIGHT : MODDEF_CO5300_WIDTH);
}
void xs_co5300_get_height(xsMachine *the)
{
- xsmcSetInteger(xsResult, MODDEF_CO5300_HEIGHT);
+ co5300Display sd = xsmcGetHostData(xsThis);
+ xsmcSetInteger(xsResult, (sd->rotation & 1) ? MODDEF_CO5300_WIDTH : MODDEF_CO5300_HEIGHT);
+}
+
+void xs_co5300_get_rotation(xsMachine *the)
+{
+ co5300Display sd = xsmcGetHostData(xsThis);
+ xsmcSetInteger(xsResult, sd->rotation * 90);
+}
+
+void xs_co5300_set_rotation(xsMachine *the)
+{
+ co5300Display sd = xsmcGetHostData(xsThis);
+ int32_t rotation = xsmcToInteger(xsArg(0));
+
+ if ((0 != rotation) && (90 != rotation) && (180 != rotation) && (270 != rotation))
+ xsRangeError("invalid rotation");
+
+ sd->rotation = (uint8_t)(rotation / 90);
+ co5300SetMADCTL(sd);
}
void xs_co5300_get_c_dispatch(xsMachine *the)
@@ -365,8 +412,19 @@ void co5300Send(PocoPixel *pixels, int byteLength, void *refcon)
}
#endif
+ if (1 == (sd->rotation & 1)) {
+ co5300SendRotated(sd, (uint16_t *)pixels, byteLength);
+ return;
+ }
+
+ if (2 == sd->rotation) {
+ co5300SendHardwareRotated180(sd, (uint16_t *)pixels, byteLength);
+ return;
+ }
+
{
int one = 1;
+ sd->colorSlotReserved = 0;
xQueueSend(sd->ops, &one, portMAX_DELAY);
esp_lcd_panel_io_tx_color(sd->io_handle, CO5300_COLOR_CMD, pixels, byteLength);
@@ -401,6 +459,7 @@ static const uint8_t gInit[] ICACHE_RODATA_ATTR = {
0xFE, 1, 0x00, // Page select (page 0)
0xC4, 1, 0x80, // Set interface (QSPI)
0x3A, 1, 0x55, // Pixel format: 16-bit RGB565
+ 0x36, 1, 0x00, // Memory data access control
0x44, 2, 0x01, 0xD1, // Set tear scanline
0x35, 1, 0x00, // Tearing effect line on
0x53, 1, 0x20, // Write CTRL display value
@@ -440,6 +499,108 @@ void co5300Init(co5300Display sd)
sd->firstFrame = true;
sd->brightnessValue = 0;
+ sd->rotation = 0;
+}
+
+static void co5300SetMADCTL(co5300Display sd)
+{
+ uint8_t value = (2 == sd->rotation) ? (CO5300_MADCTL_MY | CO5300_MADCTL_MX) : 0;
+ co5300Command(sd, CO5300_CMD(0x36), &value, 1);
+}
+
+static void co5300SetWindow(co5300Display sd, uint16_t x, uint16_t y, uint16_t w, uint16_t h)
+{
+ uint16_t xMin = x + MODDEF_CO5300_COLUMN_OFFSET;
+ uint16_t yMin = y + MODDEF_CO5300_ROW_OFFSET;
+ uint16_t xMax = xMin + w - 1;
+ uint16_t yMax = yMin + h - 1;
+ uint8_t data[4];
+
+ data[0] = (xMin >> 8) & 0xff;
+ data[1] = xMin & 0xff;
+ data[2] = (xMax >> 8) & 0xff;
+ data[3] = xMax & 0xff;
+ co5300Command(sd, CO5300_CMD(0x2a), data, 4);
+
+ data[0] = (yMin >> 8) & 0xff;
+ data[1] = yMin & 0xff;
+ data[2] = (yMax >> 8) & 0xff;
+ data[3] = yMax & 0xff;
+ co5300Command(sd, CO5300_CMD(0x2b), data, 4);
+}
+
+static void co5300WaitForColors(co5300Display sd)
+{
+ xSemaphoreTake(sd->colorsInFlight, portMAX_DELAY);
+ xSemaphoreTake(sd->colorsInFlight, portMAX_DELAY);
+ xSemaphoreGive(sd->colorsInFlight);
+ xSemaphoreGive(sd->colorsInFlight);
+}
+
+static void co5300SendColorSync(co5300Display sd, const void *pixels, int byteLength)
+{
+ int one = 1;
+
+ if (sd->colorSlotReserved)
+ sd->colorSlotReserved = 0;
+ else
+ xSemaphoreTake(sd->colorsInFlight, portMAX_DELAY);
+
+ xQueueSend(sd->ops, &one, portMAX_DELAY);
+ esp_lcd_panel_io_tx_color(sd->io_handle, CO5300_COLOR_CMD, pixels, byteLength);
+ co5300WaitForColors(sd);
+}
+
+static void co5300SendRotated(co5300Display sd, uint16_t *pixels, int byteLength)
+{
+ int lines = (byteLength >> 1) / sd->updateWidth;
+ int row = sd->updateHeight - sd->updateLinesRemaining;
+ uint16_t *src = pixels;
+
+ while (lines > 0) {
+ int rows = (lines > 1) ? 2 : 1;
+ uint16_t *out = sd->rotateBuffer;
+ uint16_t x, y, w, h;
+
+ if (1 == sd->rotation) {
+ for (int col = 0; col < sd->updateWidth; col++) {
+ for (int r = rows - 1; r >= 0; r--)
+ *out++ = src[(r * sd->updateWidth) + col];
+ }
+ x = MODDEF_CO5300_WIDTH - (sd->updateY + row + rows);
+ y = sd->updateX;
+ }
+ else {
+ for (int col = sd->updateWidth - 1; col >= 0; col--) {
+ for (int r = 0; r < rows; r++)
+ *out++ = src[(r * sd->updateWidth) + col];
+ }
+ x = sd->updateY + row;
+ y = MODDEF_CO5300_HEIGHT - (sd->updateX + sd->updateWidth);
+ }
+ w = rows;
+ h = sd->updateWidth;
+
+ co5300SetWindow(sd, x, y, w, h);
+ co5300SendColorSync(sd, sd->rotateBuffer, (int)((out - sd->rotateBuffer) * sizeof(uint16_t)));
+
+ src += rows * sd->updateWidth;
+ row += rows;
+ lines -= rows;
+ sd->updateLinesRemaining -= rows;
+ }
+}
+
+static void co5300SendHardwareRotated180(co5300Display sd, uint16_t *pixels, int byteLength)
+{
+ int lines = (byteLength >> 1) / sd->updateWidth;
+ int row = sd->updateHeight - sd->updateLinesRemaining;
+ uint16_t x = MODDEF_CO5300_WIDTH - (sd->updateX + sd->updateWidth);
+ uint16_t y = MODDEF_CO5300_HEIGHT - (sd->updateY + row + lines);
+
+ co5300SetWindow(sd, x, y, sd->updateWidth, lines);
+ co5300SendColorSync(sd, pixels, byteLength);
+ sd->updateLinesRemaining -= lines;
}
void co5300AdaptInvalid(void *refcon, CommodettoRectangle r)
@@ -460,37 +621,30 @@ void co5300AdaptInvalid(void *refcon, CommodettoRectangle r)
void co5300Begin(void *refcon, CommodettoCoordinate x, CommodettoCoordinate y, CommodettoDimension w, CommodettoDimension h)
{
co5300Display sd = refcon;
- uint16_t xMin, xMax, yMin, yMax;
+ uint16_t yMin, yMax;
- if (sd->nothingSent)
+ if (sd->nothingSent && sd->colorSlotReserved) {
xSemaphoreGive(sd->colorsInFlight);
+ sd->colorSlotReserved = 0;
+ }
sd->nothingSent = 1;
- xMin = x + MODDEF_CO5300_COLUMN_OFFSET;
yMin = y + MODDEF_CO5300_ROW_OFFSET;
-
- xMax = xMin + w - 1;
yMax = yMin + h - 1;
+ sd->updateX = x;
+ sd->updateY = y;
sd->updateWidth = w;
+ sd->updateHeight = h;
sd->updateLinesRemaining = h;
sd->yMin = yMin;
sd->yMax = yMax;
- uint8_t data[4];
- data[0] = (xMin >> 8) & 0xff;
- data[1] = xMin & 0xff;
- data[2] = (xMax >> 8) & 0xff;
- data[3] = xMax & 0xff;
- co5300Command(sd, CO5300_CMD(0x2a), data, 4);
-
- data[0] = (yMin >> 8) & 0xff;
- data[1] = yMin & 0xff;
- data[2] = (yMax >> 8) & 0xff;
- data[3] = yMax & 0xff;
- co5300Command(sd, CO5300_CMD(0x2b), data, 4);
+ if (!sd->rotation)
+ co5300SetWindow(sd, x, y, w, h);
xSemaphoreTake(sd->colorsInFlight, portMAX_DELAY);
+ sd->colorSlotReserved = 1;
}
void co5300Continue(void *refcon)
@@ -512,7 +666,10 @@ void co5300End(void *refcon)
}
if (sd->nothingSent) {
- xSemaphoreGive(sd->colorsInFlight);
+ if (sd->colorSlotReserved) {
+ xSemaphoreGive(sd->colorsInFlight);
+ sd->colorSlotReserved = 0;
+ }
sd->nothingSent = 0;
}
}