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; } }