diff --git a/src/lgfx/v1/panel/Panel_AMOLED.cpp b/src/lgfx/v1/panel/Panel_AMOLED.cpp new file mode 100644 index 00000000..4009751d --- /dev/null +++ b/src/lgfx/v1/panel/Panel_AMOLED.cpp @@ -0,0 +1,737 @@ +/*----------------------------------------------------------------------------/ + * Lovyan GFX - Graphics library for embedded devices. + * + * Original Source: + * https://github.com/lovyan03/LovyanGFX/ + * + * Licence: + * [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt) + * + * Author: + * [lovyan03](https://twitter.com/lovyan03) + * + * Contributors: + * [ciniml](https://github.com/ciniml) + * [mongonta0716](https://github.com/mongonta0716) + * [tobozo](https://github.com/tobozo) + * /----------------------------------------------------------------------------*/ + +#if defined (ESP_PLATFORM) + +#include "Panel_AMOLED.hpp" +#include "../Bus.hpp" +#include "../platforms/common.hpp" +#include "../misc/pixelcopy.hpp" +#include "../misc/colortype.hpp" +#include "driver/spi_master.h" +#include "esp_log.h" + +#if defined LGFX_USE_QSPI + + +namespace lgfx +{ + inline namespace v1 + { + + //---------------------------------------------------------------------------- + + + void Panel_AMOLED::update_madctl() + { + uint8_t madctl = 0; + switch (_rotation) { + case 0: break; + case 1: madctl = 0x60; break; + case 2: madctl = 0xc0; break; + case 3: madctl = 0xa0; break; + } + + startWrite(); + cs_control(false); + write_cmd(0x36); + _bus->writeCommand(madctl, 8); + _bus->wait(); + cs_control(true); + endWrite(); + } + + + void Panel_AMOLED::command_list(const uint8_t *addr) + { + startWrite(); + for (;;) + { // For each command... + uint8_t cmd = *addr++; + uint8_t num = *addr++; // Number of args to follow + if (cmd == 0xff && num == 0xff) break; + + cs_control(false); + + this->write_cmd(cmd); // Read, issue command + uint_fast8_t ms = num & CMD_INIT_DELAY; // If hibit set, delay follows args + num &= ~CMD_INIT_DELAY; // Mask out delay bit + if (num) { + do + { // For each argument... + _bus->writeCommand(*addr++, 8); + } while (--num); + } + + _bus->wait(); + cs_control(true); + + if (ms) { + ms = *addr++; // Read post-command delay time (ms) + delay( (ms==255 ? 500 : ms) ); + } + } + endWrite(); + } + + + void Panel_AMOLED::write_cmd(uint8_t cmd) + { + uint8_t cmd_buffer[4] = {0x02, 0x00, 0x00, 0x00}; + cmd_buffer[2] = cmd; + for (int i = 0; i < 4; i++) { + _bus->writeCommand(cmd_buffer[i], 8); + } + } + + + void Panel_AMOLED::start_qspi() + { + cs_control(false); + _bus->writeCommand(0x32, 8); // 4 wire pixel data transmission (0x32 or 0x12) + _bus->writeCommand(0x00, 8); + _bus->writeCommand(0x2c, 8); // WRITE_MEMORY_START + _bus->writeCommand(0x00, 8); + _bus->wait(); + } + + void Panel_AMOLED::end_qspi() + { + _bus->writeCommand(0x32, 8); // 4 wire pixel data transmission (0x32 or 0x12) + _bus->writeCommand(0x00, 8); + _bus->writeCommand(0x00, 8); // NOP + _bus->writeCommand(0x00, 8); + _bus->wait(); + cs_control(true); + } + + void Panel_AMOLED::write_bytes(const uint8_t* data, uint32_t len, bool use_dma) + { + start_qspi(); + _bus->writeBytes(data, len, true, use_dma); + _bus->wait(); + end_qspi(); + } + + + + + bool Panel_AMOLED::init(bool use_reset) + { + // ESP_LOGD("Panel_AMOLED","pannel init %d", use_reset); + if (!Panel_Device::init(use_reset)) { + return false; + } + + startWrite(true); + + for (uint8_t i = 0; auto cmds = getInitCommands(i); i++) + { + command_list(cmds); + } + + endWrite(); + + return true; + } + + + + void Panel_AMOLED::setBrightness(uint8_t brightness) + { + // ESP_LOGD("Panel_AMOLED","setBrightness %d", brightness); + startWrite(); + // Write Display Brightness MAX_VAL=0XFF + cs_control(false); + write_cmd(0x51); + _bus->writeCommand(brightness, 8); + _bus->wait(); + cs_control(true); + endWrite(); + } + + + void Panel_AMOLED::setRotation(uint_fast8_t r) + { + // ESP_LOGD("Panel_AMOLED","setRotation %d", r); + r &= 7; + _rotation = r; + // offset_rotationを加算 (0~3:回転方向、 4:上下反転フラグ); + _internal_rotation = ((r + _cfg.offset_rotation) & 3) | ((r & 4) ^ (_cfg.offset_rotation & 4)); + + auto ox = _cfg.offset_x; + auto oy = _cfg.offset_y; + auto pw = _cfg.panel_width; + auto ph = _cfg.panel_height; + auto mw = _cfg.memory_width; + auto mh = _cfg.memory_height; + if (_internal_rotation & 1) + { + std::swap(ox, oy); + std::swap(pw, ph); + std::swap(mw, mh); + } + _width = pw; + _height = ph; + + _colstart = ox; + _rowstart = oy; + + // _colstart = (_internal_rotation & 2) + // ? mw - (pw + ox) : ox; + // _rowstart = ((1 << _internal_rotation) & 0b10010110) // case 1:2:4:7 + // ? mh - (ph + oy) : oy; + + _xs = _xe = _ys = _ye = INT16_MAX; + + update_madctl(); + } + + + void Panel_AMOLED::setInvert(bool invert) + { + // ESP_LOGD("Panel_AMOLED","setInvert %d", invert); + cs_control(false); + + if (invert) + write_cmd(0x21); // Inversion On + else + write_cmd(0x20); // Inversion Off + + _bus->wait(); + + cs_control(true); + } + + + void Panel_AMOLED::setSleep(bool flg) + { + // ESP_LOGD("Panel_AMOLED","setSleep %d", flg); + cs_control(false); + + if (flg) { + write_cmd(0x10); // Sleep in + } + else { + write_cmd(0x11); // Sleep out + delay(150); + } + _bus->wait(); + + cs_control(true); + } + + + void Panel_AMOLED::setPowerSave(bool flg) + { + // ESP_LOGD("Panel_AMOLED","setPowerSave"); + } + + + void Panel_AMOLED::waitDisplay(void) + { + _bus->wait(); + // ESP_LOGD("Panel_AMOLED","waitDisplay"); + } + + + bool Panel_AMOLED::displayBusy(void) + { + // ESP_LOGD("Panel_AMOLED","displayBusy"); + return false; + } + + + color_depth_t Panel_AMOLED::setColorDepth(color_depth_t depth) + { + // ESP_LOGD("Panel_AMOLED","setColorDepth %d", depth); + // NOTE: this probably needs revisiting, supported formats are RGB888/RGB666/RGB565/RGB332/RGB111/Gray 256 + + // 0x55: 16bit/pixel + // 0x66: 18bit/pixel + // 0x77: 24bit/pixel + uint8_t cmd_send = 0; + if (depth == rgb565_2Byte) { + cmd_send = 0x55; + } + else if (depth == rgb666_3Byte) { + cmd_send = 0x66; + } + else if (depth == rgb888_3Byte) { + cmd_send = 0x77; + } + else { + return _write_depth; + } + _write_depth = depth; + + // Set interface Pixel Format + startWrite(); + + cs_control(false); + write_cmd(0x3a); + _bus->writeCommand(cmd_send, 8); + _bus->wait(); + cs_control(true); + + endWrite(); + + return _write_depth; + } + + + void Panel_AMOLED::beginTransaction(void) + { + // ESP_LOGD("Panel_AMOLED","beginTransaction"); + if (_in_transaction) return; + _in_transaction = true; + _bus->beginTransaction(); + } + + + void Panel_AMOLED::endTransaction(void) + { + // ESP_LOGD("Panel_AMOLED","endTransaction"); + // if (!_in_transaction) return; + // _in_transaction = false; + // _bus->endTransaction(); + + if (!_in_transaction) return; + _in_transaction = false; + + if (_has_align_data) + { + _has_align_data = false; + _bus->writeData(0, 8); + } + + _bus->endTransaction(); + } + + + void Panel_AMOLED::setPartialArea(uint_fast16_t ys, uint_fast16_t ye) + { + if( ys==0 && ys==_height-1 ) { + cs_control(false); + write_cmd(0x13); // Set Partial Display Mode to Normal (off) + cs_control(true); + return; + } + + cs_control(false); + write_cmd(0x12); // Set Partial Display Mode On + cs_control(true); + + cs_control(false); + write_cmd(0x30); // set Partial Area + _bus->writeCommand(ys >> 8, 8); + _bus->writeCommand(ys & 0xFF, 8); + _bus->writeCommand(ye >> 8, 8); + _bus->writeCommand(ye & 0xFF, 8); + _bus->wait(); + cs_control(true); + } + + + void Panel_AMOLED::setVerticalPartialArea(uint_fast16_t xs, uint_fast16_t xe) + { + if( xs==0 && xe==_width-1 ) { + cs_control(false); + write_cmd(0x13); // Set Partial Display Mode to Normal (off) + cs_control(true); + return; + } + + cs_control(false); + write_cmd(0x12); // Set Partial Display Mode On + cs_control(true); + + cs_control(false); + write_cmd(0x31);// set Vertical Partial Area + _bus->writeCommand(xs >> 8, 8); + _bus->writeCommand(xs & 0xFF, 8); + _bus->writeCommand(xe >> 8, 8); + _bus->writeCommand(xe & 0xFF, 8); + _bus->wait(); + cs_control(true); + } + + + void Panel_AMOLED::setWindow(uint_fast16_t xs, uint_fast16_t ys, uint_fast16_t xe, uint_fast16_t ye) + { + // ESP_LOGD("Panel_AMOLED","setWindow %d %d %d %d", xs, ys, xe, ye); + uint16_t w = (xe-xs)+1; + if(xs%2!=0 || w%2!=0) { // Panel_AMOLED restriction: x and w must be divisible by 2 + // ESP_LOGD("LGFX", "clip coords aren't aligned"); + return; + } + + if (xs > xe || xe > _width-1) { return; } + if (ys > ye || ye > _height-1) { return; } + + // apply offsets + xs += _colstart; + ys += _rowstart; + xe += _colstart; + ye += _rowstart; + + // Set limit + if ((xe - xs) >= _width) { xs = 0; xe = _width - 1; } + if ((ye - ys) >= _height) { ys = 0; ye = _height - 1; } + + { + // Set Column Start Address (CASET) + cs_control(false); + write_cmd(0x2A); + _bus->writeCommand(xs >> 8, 8); + _bus->writeCommand(xs & 0xFF, 8); + _bus->writeCommand(xe >> 8, 8); + _bus->writeCommand(xe & 0xFF, 8); + _bus->wait(); + cs_control(true); + } + + { + // Set Row Start Address (RASET) + cs_control(false); + write_cmd(0x2B); + _bus->writeCommand(ys >> 8, 8); + _bus->writeCommand(ys & 0xFF, 8); + _bus->writeCommand(ye >> 8, 8); + _bus->writeCommand(ye & 0xFF, 8); + _bus->wait(); + cs_control(true); + } + + // // Memory Write (disabled because setWindow can also be used for memory read) + // cs_control(false); + // write_cmd(0x2C); + // _bus->wait(); + // cs_control(true); + } + + + void Panel_AMOLED::writeBlock(uint32_t rawcolor, uint32_t len) + { + // ESP_LOGD("Panel_AMOLED","writeBlock 0x%lx %ld", rawcolor, len); + start_qspi(); + _bus->writeDataRepeat(rawcolor, _write_bits, len); + _bus->wait(); + end_qspi(); + } + + + void Panel_AMOLED::writePixels(pixelcopy_t* param, uint32_t len, bool use_dma) + { + start_qspi(); + + if (param->no_convert) { + _bus->writeBytes(reinterpret_cast(param->src_data), len * _write_bits >> 3, true, use_dma); + } + else { + _bus->writePixels(param, len); + } + if (_cfg.dlen_16bit && (_write_bits & 15) && (len & 1)) { + _has_align_data = !_has_align_data; + } + + _bus->wait(); + end_qspi(); + } + + + void Panel_AMOLED::drawPixelPreclipped(uint_fast16_t x, uint_fast16_t y, uint32_t rawcolor) + { + // ESP_LOGD("Panel_AMOLED","drawPixelPreclipped %d %d 0x%lX", x, y, rawcolor); + if(x%2!=0) { // Panel_AMOLED restriction: x and w must be divisible by 2 + // ESP_LOGD("LGFX", "clip coords aren't aligned"); + return; + } + setWindow(x,y,x,y); + if (_cfg.dlen_16bit) { _has_align_data = (_write_bits & 15); } + + start_qspi(); + + _bus->writeData(rawcolor, _write_bits); + + _bus->wait(); + end_qspi(); + } + + + void Panel_AMOLED::writeFillRectPreclipped(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, uint32_t rawcolor) + { + // ESP_LOGD("Panel_AMOLED","writeFillRectPreclipped %d %d %d %d 0x%lX", x, y, w, h, rawcolor); + if(x%2!=0 || w%2!=0) { // Panel_AMOLED restriction: x and w must be divisible by 2 + // ESP_LOGD("LGFX", "clip coords aren't aligned"); + return; + } + uint32_t len = w * h; + uint_fast16_t xe = w + x - 1; + uint_fast16_t ye = y + h - 1; + + setWindow(x,y,xe,ye); + // if (_cfg.dlen_16bit) { _has_align_data = (_write_bits & 15) && (len & 1); } + + start_qspi(); + _bus->writeDataRepeat(rawcolor, _write_bits, len); + _bus->wait(); + end_qspi(); + } + + + void Panel_AMOLED::writeImage(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t* param, bool use_dma) + { + // ESP_LOGD("Panel_AMOLED","writeImage %d %d %d %d %d", x, y, w, h, use_dma); + if(x%2!=0 || w%2!=0) { // Panel_AMOLED restriction: x and w must be divisible by 2 + // ESP_LOGD("LGFX", "clip coords aren't aligned x(%d) y(%d) w(%d) h(%d) use_dma(%d)", x, y, w, h, use_dma); + return; + } + // use_dma = false; + auto bytes = param->dst_bits >> 3; + auto src_x = param->src_x; + + if (param->transp == pixelcopy_t::NON_TRANSP) + { + if (param->no_convert) + { + auto wb = w * bytes; + uint32_t i = (src_x + param->src_y * param->src_bitwidth) * bytes; + auto src = &((const uint8_t*)param->src_data)[i]; + setWindow(x, y, x + w - 1, y + h - 1); + if (param->src_bitwidth == w || h == 1) + { + write_bytes(src, wb * h, use_dma); + } + else + { + auto add = param->src_bitwidth * bytes; + if (use_dma) + { + if (_cfg.dlen_16bit && ((wb * h) & 1)) + { + _has_align_data = !_has_align_data; + } + do + { + _bus->addDMAQueue(src, wb); + src += add; + } while (--h); + _bus->execDMAQueue(); + } + else + { + do + { + write_bytes(src, wb, false); + src += add; + } while (--h); + } + } + } + else + { + if (!_bus->busy()) + { + static constexpr uint32_t WRITEPIXELS_MAXLEN = 32767; + + setWindow(x, y, x + w - 1, y + h - 1); + // bool nogap = (param->src_bitwidth == w || h == 1); + bool nogap = (h == 1) || (param->src_y32_add == 0 && ((param->src_bitwidth << pixelcopy_t::FP_SCALE) == (w * param->src_x32_add))); + if (nogap && (w * h <= WRITEPIXELS_MAXLEN)) + { + writePixels(param, w * h, use_dma); + } + else + { + uint_fast16_t h_step = nogap ? WRITEPIXELS_MAXLEN / w : 1; + uint_fast16_t h_len = (h_step > 1) ? ((h - 1) % h_step) + 1 : 1; + writePixels(param, w * h_len, use_dma); + if (h -= h_len) + { + param->src_y += h_len; + do + { + param->src_x = src_x; + writePixels(param, w * h_step, use_dma); + param->src_y += h_step; + } while (h -= h_step); + } + } + } + else + { + size_t wb = w * bytes; + auto buf = _bus->getDMABuffer(wb); + param->fp_copy(buf, 0, w, param); + setWindow(x, y, x + w - 1, y + h - 1); + write_bytes(buf, wb, true); + _has_align_data = (_cfg.dlen_16bit && (_write_bits & 15) && (w & h & 1)); + while (--h) + { + param->src_x = src_x; + param->src_y++; + buf = _bus->getDMABuffer(wb); + param->fp_copy(buf, 0, w, param); + write_bytes(buf, wb, true); + } + } + } + } + else + { + h += y; + uint32_t wb = w * bytes; + do + { + uint32_t i = 0; + while (w != (i = param->fp_skip(i, w, param))) + { + auto buf = _bus->getDMABuffer(wb); + int32_t len = param->fp_copy(buf, 0, w - i, param); + setWindow(x + i, y, x + i + len - 1, y); + write_bytes(buf, len * bytes, true); + if (w == (i += len)) break; + } + param->src_x = src_x; + param->src_y++; + } while (++y != h); + } + } + + + + //---------------------------------------------------------------------------- + + + + bool Panel_AMOLED_Framebuffer::init(bool use_reset) + { + if( _frame_buffer ) + return true; + // setRotation(getRotation()); + ESP_LOGD("Panel_AMOLED","Panel_AMOLED_Framebuffer init [%d x %d] %d", _width, _height, use_reset); + if( !initFramebuffer(_width, _height) ) + return false; + _internal_rotation = 0; + return Panel_Device::init(false); + } + + + void Panel_AMOLED_Framebuffer::display(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h) + { + if( !_frame_buffer) + return; + + if( x == 0 && y == 0 && w == 0 && h == 0 ) + { + w = _width; + h = _height; + } + + if( w == 0 || h == 0 ) + return; + + uint16_t x0 = x & 0xfe; // Make even + uint16_t y0 = y & 0xfe; // Make even + uint16_t x1 = x + w - 1; + uint16_t y1 = y + h - 1; + + if( x1%2==0 ) x1++; // Make odd if not + if( y1%2==0 ) y1++; // Make odd if not + + uint16_t w1 = x1 - x0 + 1 ; // Corrected width + uint16_t h1 = y1 - y0 + 1; // Corrected height + + _panel->setWindow(x0, y0, x1, y1); + // ESP_LOGD("LGFX", "x(%d=>%d), y(%d=>%d), w(%d=>%d), h(%d=>%d)", x, x0, y, y0, w, w1, h, h1); + + uint8_t bpp = _write_bits >> 3; // bytes per pixel + size_t wb = w1 * bpp; // bytes per line + uint8_t* buf = _panel->getBus()->getDMABuffer(wb*2); + + _panel->start_qspi(); + for( int i=y0;igetBus()->writeBytes(buf, wb*2, false, true); + } + _panel->end_qspi(); + + return; + } + + + bool Panel_AMOLED_Framebuffer::initFramebuffer(uint_fast16_t w, uint_fast16_t h) + { + size_t lineArray_size = h * sizeof(void*); + // ESP_LOGE("DEBUG","height:%d", h); + uint8_t** lineArray = (uint8_t**)heap_alloc_dma(lineArray_size); + if (lineArray) + { + memset(lineArray, 0, lineArray_size); + uint8_t bits = (_write_depth & color_depth_t::bit_mask); + // ESP_LOGD("LGFX", "color has %d bits (%d bytes)", bits, bits/8); + w = (w + 3) & ~3; // round up to nearest multiple of 4 + // 暫定実装。画面全体のバッファを一括で確保する。 + // ToDo : 分割確保 + int framebuffersize = (w * (bits >> 3)) * h; + //log_d("framebuffersize: %d bytes (expected=%d)", framebuffersize, 600*452*2); + _frame_buffer = (uint8_t*)heap_alloc_psram(framebuffersize); + if (_frame_buffer) { + _lines_buffer = lineArray; + auto fb = _frame_buffer; + for (int i = 0; i < h; ++i) { + lineArray[i] = fb; + fb += w * bits >> 3; + } + // ESP_LOGD("LGFX", "Framebuffer allocated [%d x %d] (%d) bytes", w, h, framebuffersize); + return true; + } + heap_free(lineArray); + } + ESP_LOGE("LGFX", "Framebuffer allocation failed [%d x %d]", w, h); + return false; + } + + + void Panel_AMOLED_Framebuffer::deinitFramebuffer(void) + { + if (_frame_buffer) + { + heap_free(_frame_buffer); + _frame_buffer = nullptr; + } + + if (_lines_buffer) + { + heap_free(_lines_buffer); + _lines_buffer = nullptr; + } + } + + + //---------------------------------------------------------------------------- + } +} + +#endif +#endif diff --git a/src/lgfx/v1/panel/Panel_AMOLED.hpp b/src/lgfx/v1/panel/Panel_AMOLED.hpp new file mode 100644 index 00000000..31302385 --- /dev/null +++ b/src/lgfx/v1/panel/Panel_AMOLED.hpp @@ -0,0 +1,160 @@ +/*----------------------------------------------------------------------------/ + * Lovyan GFX - Graphics library for embedded devices. + * + * Original Source: + * https://github.com/lovyan03/LovyanGFX/ + * + * Licence: + * [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt) + * + * Author: + * [lovyan03](https://twitter.com/lovyan03) + * + * Contributors: + * [ciniml](https://github.com/ciniml) + * [mongonta0716](https://github.com/mongonta0716) + * [tobozo](https://github.com/tobozo) + * /----------------------------------------------------------------------------*/ +#pragma once + +#if defined (ESP_PLATFORM) + +#include "Panel_Device.hpp" +#include "Panel_FrameBufferBase.hpp" +#include "../platforms/common.hpp" +#include "../platforms/device.hpp" + +#if defined LGFX_USE_QSPI + +namespace lgfx +{ + inline namespace v1 + { + //---------------------------------------------------------------------------- + + struct Panel_AMOLED; + + struct Panel_AMOLED_Framebuffer : Panel_FrameBufferBase + { + public: + Panel_AMOLED_Framebuffer(Panel_AMOLED* panel) : _panel(panel) { assert(_panel); } + ~Panel_AMOLED_Framebuffer(void) { deinitFramebuffer(); } + bool init(bool use_reset) override; + bool initFramebuffer(uint_fast16_t w, uint_fast16_t h); + void deinitFramebuffer(void); + + // TODO: reinit frame buffer when one of those functions is called + //color_depth_t setColorDepth(color_depth_t depth) override; + //void setRotation(uint_fast8_t r) override; + //void setInvert(bool invert) override; + + void display(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h) override; + + protected: + uint8_t* _frame_buffer = nullptr; + Panel_AMOLED* _panel = nullptr; + }; + + + //---------------------------------------------------------------------------- + + + struct Panel_AMOLED : public Panel_Device + { + protected: + bool _in_transaction = false; + + Panel_AMOLED_Framebuffer* _panel_fb = nullptr; + + uint16_t _colstart = 0; + uint16_t _rowstart = 0; + + public: + Panel_AMOLED(void) { } + + ~Panel_AMOLED(void) + { + deinitPanelFb(); + } + + bool init(bool use_reset) override; + void beginTransaction(void) override; + void endTransaction(void) override; + + + bool initPanelFb() + { + if(_panel_fb) + return true; + _panel_fb = new Panel_AMOLED_Framebuffer(this); + _panel_fb->config(_cfg); + _panel_fb->setColorDepth(_write_depth); + _panel_fb->setRotation(getRotation()); + return _panel_fb->init(false); + } + + void deinitPanelFb() + { + if(_panel_fb) { + delete _panel_fb; + _panel_fb = nullptr; + } + } + + Panel_AMOLED_Framebuffer* getPanelFb() + { + return _panel_fb; + } + + void display(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h) override + { + if(_panel_fb) + _panel_fb->display(x, y, w, h); + } + + void command_list(const uint8_t *addr); + + void update_madctl(); + + void write_cmd(uint8_t cmd); + void start_qspi(); + void end_qspi(); + void write_bytes(const uint8_t* data, uint32_t len, bool use_dma); + + color_depth_t setColorDepth(color_depth_t depth) override; + void setRotation(uint_fast8_t r) override; + void setInvert(bool invert) override; + void setSleep(bool flg) override; + void setPowerSave(bool flg) override; + void setBrightness(uint8_t brightness) override; + + void waitDisplay(void) override; + bool displayBusy(void) override; + + void writePixels(pixelcopy_t* param, uint32_t len, bool use_dma) override; + void writeBlock(uint32_t rawcolor, uint32_t len) override; + + // not sure what these display commands are for, more testing needed + void setVerticalPartialArea(uint_fast16_t xs, uint_fast16_t xe); + void setPartialArea(uint_fast16_t ys, uint_fast16_t ye); + + void setWindow(uint_fast16_t xs, uint_fast16_t ys, uint_fast16_t xe, uint_fast16_t ye) override; + void drawPixelPreclipped(uint_fast16_t x, uint_fast16_t y, uint32_t rawcolor) override; + void writeFillRectPreclipped(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, uint32_t rawcolor) override; + void writeImage(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t* param, bool use_dma) override; + + // TODO: implement those + uint32_t readCommand(uint_fast16_t cmd, uint_fast8_t index, uint_fast8_t len) override { return 0;} + uint32_t readData(uint_fast8_t index, uint_fast8_t len) override { return 0;} + void readRect(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, void* dst, pixelcopy_t* param) override { }; + }; + + + //---------------------------------------------------------------------------- + } + +} + + +#endif +#endif diff --git a/src/lgfx/v1/panel/Panel_NV3041A.cpp b/src/lgfx/v1/panel/Panel_NV3041A.cpp new file mode 100644 index 00000000..56735db5 --- /dev/null +++ b/src/lgfx/v1/panel/Panel_NV3041A.cpp @@ -0,0 +1,534 @@ +/*----------------------------------------------------------------------------/ + * Lovyan GFX - Graphics library for embedded devices. + * + * Original Source: + * https://github.com/lovyan03/LovyanGFX/ + * + * Licence: + * [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt) + * + * Author: + * [lovyan03](https://twitter.com/lovyan03) + * + * Contributors: + * [ciniml](https://github.com/ciniml) + * [mongonta0716](https://github.com/mongonta0716) + * [tobozo](https://github.com/tobozo) + * /----------------------------------------------------------------------------*/ + +#if defined (ESP_PLATFORM) + +#include "Panel_NV3041A.hpp" +#include "../Bus.hpp" +#include "../platforms/common.hpp" +#include "../misc/pixelcopy.hpp" +#include "../misc/colortype.hpp" +#include "driver/spi_master.h" +#include "esp_log.h" + + +/** + * @brief Bug list (inherited from Panel_SH8601Z) + * + * > Write image (pushSprite) works fine, bugs down below are from writing directly + * + * 1> Write function is block even with DMA (manual CS wait data) + * 2> In spi 40MHz draw vertical line incomplete, but 10MHz OK (Likely because my dupont line connection) + * 3> After implement write/draw pixel funcs, "testFilledRects" stucks sometime, acts differently to the different sck freq + * 4> Haven't found the way to set rotation by reg + */ + + +namespace lgfx +{ + inline namespace v1 + { + //---------------------------------------------------------------------------- + + /* Panel init */ + bool Panel_NV3041A::init(bool use_reset) + { + // ESP_LOGD("NV3041A","pannel init %d", use_reset); + + if (!Panel_Device::init(use_reset)) { + return false; + } + + startWrite(); + cs_control(false); + write_cmd(CMD_SWRESET); + delay(150); + _bus->wait(); + cs_control(true); + endWrite(); + + startWrite(); + for(int i=0;iwrite_cmd(init_cmds[i]); + _bus->writeCommand(init_cmds[i+1], 8); + _bus->wait(); + cs_control(true); + delay(1); + } + endWrite(); + delay(120); + + startWrite(); + cs_control(false); + this->write_cmd(CMD_DISPON); + _bus->writeCommand(0x00, 8); + _bus->wait(); + cs_control(true); + endWrite(); + + return true; + } + + + void Panel_NV3041A::update_madctl(void) + { + uint8_t r = _internal_rotation; + uint8_t rgb_order = (_cfg.rgb_order ? CMD_MADCTL_RGB : CMD_MADCTL_BGR); + switch (r) + { + case 1: + r = CMD_MADCTL_MY | CMD_MADCTL_MV | rgb_order; + break; + case 2: + r = rgb_order; + break; + case 3: + r = CMD_MADCTL_MX | CMD_MADCTL_MV | rgb_order; + break; + default: // case 0: + r = CMD_MADCTL_MX | CMD_MADCTL_MY | rgb_order; + break; + } + + startWrite(); + cs_control(false); + this->write_cmd(CMD_MADCTL); + _bus->writeCommand(r, 8); + _bus->wait(); + cs_control(true); + endWrite(); + } + + + + void Panel_NV3041A::setInvert(bool invert) + { + // ESP_LOGD("NV3041A","setInvert %d", invert); + + cs_control(false); + + if (invert) { + /* Inversion On */ + write_cmd(CMD_INVON); + } + else { + /* Inversion Off */ + write_cmd(CMD_INVOFF); + } + _bus->wait(); + + cs_control(true); + } + + + void Panel_NV3041A::setSleep(bool flg) + { + // ESP_LOGD("NV3041A","setSleep %d", flg); + + cs_control(false); + + if (flg) { + /* Sleep in */ + write_cmd(CMD_SLPIN); + } + else { + /* Sleep out */ + write_cmd(CMD_SLPOUT); + delay(150); + } + _bus->wait(); + + cs_control(true); + } + + + void Panel_NV3041A::setPowerSave(bool flg) + { + // ESP_LOGD("NV3041A","setPowerSave"); + } + + + void Panel_NV3041A::waitDisplay(void) + { + // ESP_LOGD("NV3041A","waitDisplay"); + } + + + bool Panel_NV3041A::displayBusy(void) + { + // ESP_LOGD("NV3041A","displayBusy"); + return false; + } + + + color_depth_t Panel_NV3041A::setColorDepth(color_depth_t depth) + { + // ESP_LOGD("NV3041A","setColorDepth %d", depth); + + /* 0x00: 16bit/pixel */ + /* 0x01: 18bit/pixel */ + uint8_t cmd_send = 0; + if (depth == rgb565_2Byte) { + cmd_send = 0x00; + } + else if (depth == rgb666_3Byte) { + cmd_send = 0x01; + } + else { + return _write_depth; + } + _write_depth = depth; + + /* Set interface Pixel Format */ + startWrite(); + + cs_control(false); + write_cmd(CMD_COLMOD); + _bus->writeCommand(cmd_send, 8); + _bus->wait(); + cs_control(true); + + endWrite(); + + return _write_depth; + } + + + void Panel_NV3041A::write_cmd(uint8_t cmd) + { + uint8_t cmd_buffer[4] = {0x02, 0x00, 0x00, 0x00}; + cmd_buffer[2] = cmd; + // _bus->writeBytes(cmd_buffer, 4, 0, false); + for (int i = 0; i < 4; i++) { + _bus->writeCommand(cmd_buffer[i], 8); + } + } + + + void Panel_NV3041A::start_qspi() + { + /* Begin QSPI */ + cs_control(false); + _bus->writeCommand(0x32, 8); + _bus->writeCommand(0x00, 8); + _bus->writeCommand(0x2C, 8); + _bus->writeCommand(0x00, 8); + _bus->wait(); + } + + void Panel_NV3041A::end_qspi() + { + /* Stop QSPI */ + _bus->writeCommand(0x32, 8); + _bus->writeCommand(0x00, 8); + _bus->writeCommand(0x00, 8); + _bus->writeCommand(0x00, 8); + _bus->wait(); + cs_control(true); + } + + + void Panel_NV3041A::beginTransaction(void) + { + // ESP_LOGD("NV3041A","beginTransaction"); + if (_in_transaction) return; + _in_transaction = true; + _bus->beginTransaction(); + } + + + void Panel_NV3041A::endTransaction(void) + { + // ESP_LOGD("NV3041A","endTransaction"); + // if (!_in_transaction) return; + // _in_transaction = false; + // _bus->endTransaction(); + + if (!_in_transaction) return; + _in_transaction = false; + + if (_has_align_data) + { + _has_align_data = false; + _bus->writeData(0, 8); + } + + _bus->endTransaction(); + } + + + void Panel_NV3041A::write_bytes(const uint8_t* data, uint32_t len, bool use_dma) + { + start_qspi(); + _bus->writeBytes(data, len, true, use_dma); + _bus->wait(); + end_qspi(); + } + + + void Panel_NV3041A::setWindow(uint_fast16_t xs, uint_fast16_t ys, uint_fast16_t xe, uint_fast16_t ye) + { + // ESP_LOGD("NV3041A","setWindow %d %d %d %d", xs, ys, xe, ye); + + /* Set limit */ + if ((xe - xs) >= _width) { xs = 0; xe = _width - 1; } + if ((ye - ys) >= _height) { ys = 0; ye = _height - 1; } + + /* Set Column Start Address */ + cs_control(false); + write_cmd(CMD_CASET); + _bus->writeCommand(xs >> 8, 8); + _bus->writeCommand(xs & 0xFF, 8); + _bus->writeCommand(xe >> 8, 8); + _bus->writeCommand(xe & 0xFF, 8); + _bus->wait(); + cs_control(true); + + /* Set Row Start Address */ + cs_control(false); + write_cmd(CMD_RASET); + _bus->writeCommand(ys >> 8, 8); + _bus->writeCommand(ys & 0xFF, 8); + _bus->writeCommand(ye >> 8, 8); + _bus->writeCommand(ye & 0xFF, 8); + _bus->wait(); + cs_control(true); + + /* Memory Write */ + cs_control(false); + write_cmd(CMD_RAMWR); + _bus->wait(); + cs_control(true); + } + + + void Panel_NV3041A::writeBlock(uint32_t rawcolor, uint32_t len) + { + // ESP_LOGD("NV3041A","writeBlock 0x%lx %ld", rawcolor, len); + + /* Push color */ + start_qspi(); + _bus->writeDataRepeat(rawcolor, _write_bits, len); + _bus->wait(); + end_qspi(); + } + + + + + void Panel_NV3041A::writePixels(pixelcopy_t* param, uint32_t len, bool use_dma) + { + // ESP_LOGD("NV3041A","writePixels %ld %d", len, use_dma); + + start_qspi(); + + if (param->no_convert) { + _bus->writeBytes(reinterpret_cast(param->src_data), len * _write_bits >> 3, true, use_dma); + } + else { + _bus->writePixels(param, len); + } + if (_cfg.dlen_16bit && (_write_bits & 15) && (len & 1)) { + _has_align_data = !_has_align_data; + } + + _bus->wait(); + end_qspi(); + } + + + void Panel_NV3041A::drawPixelPreclipped(uint_fast16_t x, uint_fast16_t y, uint32_t rawcolor) + { + // ESP_LOGD("NV3041A","drawPixelPreclipped %d %d 0x%lX", x, y, rawcolor); + + setWindow(x,y,x,y); + if (_cfg.dlen_16bit) { _has_align_data = (_write_bits & 15); } + + start_qspi(); + + _bus->writeData(rawcolor, _write_bits); + + _bus->wait(); + end_qspi(); + } + + + void Panel_NV3041A::writeFillRectPreclipped(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, uint32_t rawcolor) + { + // ESP_LOGD("NV3041A","writeFillRectPreclipped %d %d %d %d 0x%lX", x, y, w, h, rawcolor); + + uint32_t len = w * h; + uint_fast16_t xe = w + x - 1; + uint_fast16_t ye = y + h - 1; + + setWindow(x,y,xe,ye); + // if (_cfg.dlen_16bit) { _has_align_data = (_write_bits & 15) && (len & 1); } + + start_qspi(); + _bus->writeDataRepeat(rawcolor, _write_bits, len); + _bus->wait(); + end_qspi(); + } + + + + void Panel_NV3041A::writeImage(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t* param, bool use_dma) + { + // ESP_LOGD("NV3041A","writeImage %d %d %d %d %d", x, y, w, h, use_dma); + // use_dma = false; + + auto bytes = param->dst_bits >> 3; + auto src_x = param->src_x; + + if (param->transp == pixelcopy_t::NON_TRANSP) + { + if (param->no_convert) + { + auto wb = w * bytes; + uint32_t i = (src_x + param->src_y * param->src_bitwidth) * bytes; + auto src = &((const uint8_t*)param->src_data)[i]; + setWindow(x, y, x + w - 1, y + h - 1); + if (param->src_bitwidth == w || h == 1) + { + write_bytes(src, wb * h, use_dma); + } + else + { + auto add = param->src_bitwidth * bytes; + if (use_dma) + { + if (_cfg.dlen_16bit && ((wb * h) & 1)) + { + _has_align_data = !_has_align_data; + } + do + { + _bus->addDMAQueue(src, wb); + src += add; + } while (--h); + _bus->execDMAQueue(); + } + else + { + do + { + write_bytes(src, wb, false); + src += add; + } while (--h); + } + } + } + else + { + if (!_bus->busy()) + { + static constexpr uint32_t WRITEPIXELS_MAXLEN = 32767; + + setWindow(x, y, x + w - 1, y + h - 1); + // bool nogap = (param->src_bitwidth == w || h == 1); + bool nogap = (h == 1) || (param->src_y32_add == 0 && ((param->src_bitwidth << pixelcopy_t::FP_SCALE) == (w * param->src_x32_add))); + if (nogap && (w * h <= WRITEPIXELS_MAXLEN)) + { + writePixels(param, w * h, use_dma); + } + else + { + uint_fast16_t h_step = nogap ? WRITEPIXELS_MAXLEN / w : 1; + uint_fast16_t h_len = (h_step > 1) ? ((h - 1) % h_step) + 1 : 1; + writePixels(param, w * h_len, use_dma); + if (h -= h_len) + { + param->src_y += h_len; + do + { + param->src_x = src_x; + writePixels(param, w * h_step, use_dma); + param->src_y += h_step; + } while (h -= h_step); + } + } + } + else + { + size_t wb = w * bytes; + auto buf = _bus->getDMABuffer(wb); + param->fp_copy(buf, 0, w, param); + setWindow(x, y, x + w - 1, y + h - 1); + write_bytes(buf, wb, true); + _has_align_data = (_cfg.dlen_16bit && (_write_bits & 15) && (w & h & 1)); + while (--h) + { + param->src_x = src_x; + param->src_y++; + buf = _bus->getDMABuffer(wb); + param->fp_copy(buf, 0, w, param); + write_bytes(buf, wb, true); + } + } + } + } + else + { + h += y; + uint32_t wb = w * bytes; + do + { + uint32_t i = 0; + while (w != (i = param->fp_skip(i, w, param))) + { + auto buf = _bus->getDMABuffer(wb); + int32_t len = param->fp_copy(buf, 0, w - i, param); + setWindow(x + i, y, x + i + len - 1, y); + write_bytes(buf, len * bytes, true); + if (w == (i += len)) break; + } + param->src_x = src_x; + param->src_y++; + } while (++y != h); + } + } + + + + + uint32_t Panel_NV3041A::readCommand(uint_fast16_t cmd, uint_fast8_t index, uint_fast8_t len) + { + // ESP_LOGD("NV3041A","readCommand"); + return 0; + } + + uint32_t Panel_NV3041A::readData(uint_fast8_t index, uint_fast8_t len) + { + // ESP_LOGD("NV3041A","readData"); + return 0; + } + + void Panel_NV3041A::readRect(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, void* dst, pixelcopy_t* param) + { + // ESP_LOGD("NV3041A","readRect"); + } + + + //---------------------------------------------------------------------------- + } +} + + +#endif diff --git a/src/lgfx/v1/panel/Panel_NV3041A.hpp b/src/lgfx/v1/panel/Panel_NV3041A.hpp new file mode 100644 index 00000000..9b20f194 --- /dev/null +++ b/src/lgfx/v1/panel/Panel_NV3041A.hpp @@ -0,0 +1,206 @@ +/*----------------------------------------------------------------------------/ + * Lovyan GFX - Graphics library for embedded devices. + * + * Original Source: + * https://github.com/lovyan03/LovyanGFX/ + * + * Licence: + * [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt) + * + * Author: + * [lovyan03](https://twitter.com/lovyan03) + * + * Contributors: + * [ciniml](https://github.com/ciniml) + * [mongonta0716](https://github.com/mongonta0716) + * [tobozo](https://github.com/tobozo) + * /----------------------------------------------------------------------------*/ +#pragma once + +#if defined (ESP_PLATFORM) + +#include "Panel_LCD.hpp" + + +namespace lgfx +{ + inline namespace v1 + { + //---------------------------------------------------------------------------- + + struct Panel_NV3041A : public Panel_LCD + { + protected: + + static constexpr uint8_t CMD_RST_DELAY = 120 ; ///< delay ms wait for reset finish + static constexpr uint8_t CMD_SLPIN_DELAY = 120 ; ///< delay ms wait for sleep in finish + static constexpr uint8_t CMD_SLPOUT_DELAY = 120 ; ///< delay ms wait for sleep out finish + static constexpr uint8_t CMD_NOP = 0x00; + static constexpr uint8_t CMD_SWRESET = 0x01; + static constexpr uint8_t CMD_SLPIN = 0x10; + static constexpr uint8_t CMD_SLPOUT = 0x11; + static constexpr uint8_t CMD_INVOFF = 0x20; + static constexpr uint8_t CMD_INVON = 0x21; + static constexpr uint8_t CMD_DISPOFF = 0x28; + static constexpr uint8_t CMD_DISPON = 0x29; + static constexpr uint8_t CMD_CASET = 0x2A; + static constexpr uint8_t CMD_RASET = 0x2B; + static constexpr uint8_t CMD_RAMWR = 0x2C; + static constexpr uint8_t CMD_MADCTL = 0x36; + static constexpr uint8_t CMD_COLMOD = 0x3A; + static constexpr uint8_t CMD_GATEON = 0x51; + static constexpr uint8_t CMD_MADCTL_MY = 0x80; + static constexpr uint8_t CMD_MADCTL_MX = 0x40; + static constexpr uint8_t CMD_MADCTL_MV = 0x20; + static constexpr uint8_t CMD_MADCTL_ML = 0x10; + static constexpr uint8_t CMD_MADCTL_RGB = 0x0 ; + static constexpr uint8_t CMD_MADCTL_BGR = 0x1 ; + + + static constexpr uint8_t init_cmds[91*2] = + { + // init sequence grabbed from Arduino_GFX (kudos to @moononournation) + // 91 pairs, delay after = 120 + 0xff, 0xa5, + 0x36, 0xc0, + 0x3A, 0x01, // 01---565,00---666 + 0x41, 0x03, // 01--8bit, 03-16bit + 0x44, 0x15, // VBP ????? 21 + 0x45, 0x15, // VFP ????? 21 + 0x7d, 0x03, // vdds_trim[2:0] + 0xc1, 0xbb, // avdd_clp_en avdd_clp[1:0] avcl_clp_en avcl_clp[1:0], 0xbb 88 a2 + 0xc2, 0x05, // vgl_clp_en vgl_clp[2:0] + 0xc3, 0x10, // vgl_clp_en vgl_clp[2:0] + 0xc6, 0x3e, // avdd_ratio_sel avcl_ratio_sel vgh_ratio_sel[1:0] vgl_ratio_sel[1:0] = 35 + 0xc7, 0x25, // mv_clk_sel[1:0] avdd_clk_sel[1:0] avcl_clk_sel[1:0] = 2e + 0xc8, 0x11, // VGL_CLK_sel + 0x7a, 0x5f, // user_vgsp .. 4f:0.8V 3f:1.04V 5f + 0x6f, 0x44, // user_gvdd .. 1C:5.61 5f 53 2a 3a + 0x78, 0x70, // user_gvcl .. 50:-3.22 75 58 66 + 0xc9, 0x00, + 0x67, 0x21, + 0x51, 0x0a, // gate_st_o[7:0] + 0x52, 0x76, // gate_ed_o[7:0] .. 76 + 0x53, 0x0a, // gate_st_e[7:0] .. 76 + 0x54, 0x76, // gate_ed_e[7:0] + 0x46, 0x0a, // fsm_hbp_o[5:0] + 0x47, 0x2a, // fsm_hfp_o[5:0] + 0x48, 0x0a, // fsm_hbp_e[5:0] + 0x49, 0x1a, // fsm_hfp_e[5:0] + 0x56, 0x43, // src_ld_wd[1:0] src_ld_st[5:0] + 0x57, 0x42, // pn_cs_en src_cs_st[5:0] + 0x58, 0x3c, // src_cs_p_wd[6:0] + 0x59, 0x64, // src_cs_n_wd[6:0] + 0x5a, 0x41, // src_pchg_st_o[6:0] .. 41 + 0x5b, 0x3c, // src_pchg_wd_o[6:0] + 0x5c, 0x02, // src_pchg_st_e[6:0] .. 02 + 0x5d, 0x3c, // src_pchg_wd_e[6:0] .. 3c + 0x5e, 0x1f, // src_pol_sw[7:0] + 0x60, 0x80, // src_op_st_o[7:0] + 0x61, 0x3f, // src_op_st_e[7:0] + 0x62, 0x21, // src_op_ed_o[9:8] src_op_ed_e[9:8] + 0x63, 0x07, // src_op_ed_o[7:0] + 0x64, 0xe0, // src_op_ed_e[7:0] + 0x65, 0x02, // chopper + 0xca, 0x20, // avdd_mux_st_o[7:0] + 0xcb, 0x52, // avdd_mux_ed_o[7:0] .. 52 + 0xcc, 0x10, // avdd_mux_st_e[7:0] + 0xcD, 0x42, // avdd_mux_ed_e[7:0] + 0xD0, 0x20, // avcl_mux_st_o[7:0] + 0xD1, 0x52, // avcl_mux_ed_o[7:0] + 0xD2, 0x10, // avcl_mux_st_e[7:0] + 0xD3, 0x42, // avcl_mux_ed_e[7:0] + 0xD4, 0x0a, // vgh_mux_st[7:0] + 0xD5, 0x32, // vgh_mux_ed[7:0] + ////gammma weihuan pianguangpian 0913 + 0x80, 0x00, // gam_vrp0 0 6bit + 0xA0, 0x00, // gam_VRN0 0- + 0x81, 0x07, // gam_vrp1 1 6bit + 0xA1, 0x06, // gam_VRN1 1- + 0x82, 0x02, // gam_vrp2 2 6bit + 0xA2, 0x01, // gam_VRN2 2- + 0x86, 0x11, // gam_prp0 7bit 8 7bit .. 33 + 0xA6, 0x10, // gam_PRN0 8- .. 2a + 0x87, 0x27, // gam_prp1 7bit 40 7bit .. 2d + 0xA7, 0x27, // gam_PRN1 40- .. 2d + 0x83, 0x37, // gam_vrp3 61 6bit + 0xA3, 0x37, // gam_VRN3 61- + 0x84, 0x35, // gam_vrp4 62 6bit + 0xA4, 0x35, // gam_VRN4 62- + 0x85, 0x3f, // gam_vrp5 63 6bit + 0xA5, 0x3f, // gam_VRN5 63- + 0x88, 0x0b, // gam_pkp0 4 5bit .. 0b + 0xA8, 0x0b, // gam_PKN0 4- .. 0b + 0x89, 0x14, // gam_pkp1 5 5bit .. 14 + 0xA9, 0x14, // gam_PKN1 5- .. 14 + 0x8a, 0x1a, // gam_pkp2 7 5bit .. 1a + 0xAa, 0x1a, // gam_PKN2 7- .. 1a + 0x8b, 0x0a, // gam_PKP3 10 5bit + 0xAb, 0x0a, // gam_PKN3 10- + 0x8c, 0x14, // gam_PKP4 16 5bit + 0xAc, 0x08, // gam_PKN4 16- + 0x8d, 0x17, // gam_PKP5 22 5bit + 0xAd, 0x07, // gam_PKN5 22- + 0x8e, 0x16, // gam_PKP6 28 5bit .. 16 change + 0xAe, 0x06, // gam_PKN6 28- .. 13change + 0x8f, 0x1B, // gam_PKP7 34 5bit + 0xAf, 0x07, // gam_PKN7 34- + 0x90, 0x04, // gam_PKP8 46 5bit + 0xB0, 0x04, // gam_PKN8 46- + 0x91, 0x0A, // gam_PKP9 52 5bit + 0xB1, 0x0A, // gam_PKN9 52- + 0x92, 0x16, // gam_PKP10 58 5bit + 0xB2, 0x15, // gam_PKN10 58- + 0xff, 0x00, + 0x11, 0x00, + + }; + + public: + Panel_NV3041A(void) + { + _cfg.memory_width = _cfg.panel_width = 480; + _cfg.memory_height = _cfg.panel_height = 272; + } + + bool init(bool use_reset) override; + void beginTransaction(void) override; + void endTransaction(void) override; + + color_depth_t setColorDepth(color_depth_t depth) override; + //void setRotation(uint_fast8_t r) override; + void setInvert(bool invert) override; + void setSleep(bool flg) override; + void setPowerSave(bool flg) override; + + void waitDisplay(void) override; + bool displayBusy(void) override; + + void writePixels(pixelcopy_t* param, uint32_t len, bool use_dma) override; + void writeBlock(uint32_t rawcolor, uint32_t len) override; + + void setWindow(uint_fast16_t xs, uint_fast16_t ys, uint_fast16_t xe, uint_fast16_t ye) override; + void drawPixelPreclipped(uint_fast16_t x, uint_fast16_t y, uint32_t rawcolor) override; + void writeFillRectPreclipped(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, uint32_t rawcolor) override; + void writeImage(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t* param, bool use_dma) override; + + uint32_t readCommand(uint_fast16_t cmd, uint_fast8_t index, uint_fast8_t len) override; + uint32_t readData(uint_fast8_t index, uint_fast8_t len) override; + void readRect(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, void* dst, pixelcopy_t* param) override; + + protected: + bool _in_transaction = false; + + void update_madctl(void) override; + void write_cmd(uint8_t cmd); + void start_qspi(); + void end_qspi(); + void write_bytes(const uint8_t* data, uint32_t len, bool use_dma); + }; + + //---------------------------------------------------------------------------- + } +} + + +#endif diff --git a/src/lgfx/v1/panel/Panel_RM67162.hpp b/src/lgfx/v1/panel/Panel_RM67162.hpp new file mode 100644 index 00000000..d077a88f --- /dev/null +++ b/src/lgfx/v1/panel/Panel_RM67162.hpp @@ -0,0 +1,89 @@ +/*----------------------------------------------------------------------------/ + * Lovyan GFX - Graphics library for embedded devices. + * + * Original Source: + * https://github.com/lovyan03/LovyanGFX/ + * + * Licence: + * [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt) + * + * Author: + * [lovyan03](https://twitter.com/lovyan03) + * + * Contributors: + * [ciniml](https://github.com/ciniml) + * [mongonta0716](https://github.com/mongonta0716) + * [tobozo](https://github.com/tobozo) + * /----------------------------------------------------------------------------*/ +#pragma once + +#if defined (ESP_PLATFORM) + +#include "Panel_AMOLED.hpp" +#include "Panel_FrameBufferBase.hpp" +#include "../platforms/common.hpp" +#include "../platforms/device.hpp" + +#if defined LGFX_USE_QSPI + +namespace lgfx +{ + inline namespace v1 + { + //---------------------------------------------------------------------------- + + // Panel used by LilyGO T-Display-S3-AMOLED + + struct Panel_RM67162 : public Panel_AMOLED + { + public: + + Panel_RM67162(void) + { + _cfg.memory_width = _cfg.panel_width = 240; + _cfg.memory_height = _cfg.panel_height = 536; + _write_depth = color_depth_t::rgb565_2Byte; + _read_depth = color_depth_t::rgb565_2Byte; + _cfg.dummy_read_pixel = 1; + } + + const uint8_t* getInitCommands(uint8_t listno) const override + { + static constexpr uint8_t list0[] = { + 0xFE, 1, 0x00, // {0xFE, {0x00}, 0x01}, //SET PAGE 00H + 0x11, 0+CMD_INIT_DELAY, 150, // Sleep Out + 0xfe, 1, 0x05, // {0xFE, {0x05}, 0x01}, //SET PAGE 05H + 0x05, 1, 0x05, // {0x05, {0x05}, 0x01}, //OVSS control set elvss -3.95v + 0xFE, 1, 0x01, // {0xFE, {0x01}, 0x01}, //SET PAGE 01H + 0x73, 1, 0x25, // {0x73, {0x25}, 0x01}, //set OVSS voltage level.= -4.0V + 0xFE, 1, 0x00, // {0xFE, {0x00}, 0x01}, //SET PAGE 00H + // 0x44, 2, 0x01, 0x66, // Set_Tear_Scanline + // 0x35, 1, 0x00, // TE ON + // 0x34, 1, 0x00, // TE OFF + // 0x36, 1, 0x00, // Scan Direction Control + 0x36, 1, 0x60, // + 0x3a, 1, 0x55, // Interface Pixel Format: 16bit/pixel + // 0x3a, 1, 0x66, // Interface Pixel Format: 18bit/pixel + // 0x3a, 1, 0x77, // Interface Pixel Format: 24bit/pixel + 0x51, 1, 0x00, // display brightness dark (max = 0xff) + // 0x51, 1, 0x01, // display brightness dark (max = 0xff) + 0x29, 0+CMD_INIT_DELAY, 150, // {0x29, {0x00}, 0x80}, // Display on + 0x51, 1, 0xAF, // display brightness on (max = 0xff) + 0xff, 0xff // end + }; + switch (listno) { + case 0: return list0; + default: return nullptr; + } + } + }; + + + //---------------------------------------------------------------------------- + } + +} + + +#endif +#endif diff --git a/src/lgfx/v1/panel/Panel_RM690B0.hpp b/src/lgfx/v1/panel/Panel_RM690B0.hpp new file mode 100644 index 00000000..ec4bac68 --- /dev/null +++ b/src/lgfx/v1/panel/Panel_RM690B0.hpp @@ -0,0 +1,86 @@ +/*----------------------------------------------------------------------------/ + * Lovyan GFX - Graphics library for embedded devices. + * + * Original Source: + * https://github.com/lovyan03/LovyanGFX/ + * + * Licence: + * [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt) + * + * Author: + * [lovyan03](https://twitter.com/lovyan03) + * + * Contributors: + * [ciniml](https://github.com/ciniml) + * [mongonta0716](https://github.com/mongonta0716) + * [tobozo](https://github.com/tobozo) + * /----------------------------------------------------------------------------*/ +#pragma once + +#if defined (ESP_PLATFORM) + +#include "Panel_AMOLED.hpp" +#include "Panel_FrameBufferBase.hpp" +#include "../platforms/common.hpp" +#include "../platforms/device.hpp" + +#if defined LGFX_USE_QSPI + +namespace lgfx +{ + inline namespace v1 + { + //---------------------------------------------------------------------------- + + // AMOLED Panel used by LilyGO T4-S3 + + struct Panel_RM690B0 : public Panel_AMOLED + { + public: + + Panel_RM690B0(void) + { + _cfg.memory_width = _cfg.panel_width = 452; + _cfg.memory_height = _cfg.panel_height = 600; + _write_depth = color_depth_t::rgb565_2Byte; + _read_depth = color_depth_t::rgb565_2Byte; + } + + const uint8_t* getInitCommands(uint8_t listno) const override + { + static constexpr uint8_t list0[] = { + 0x11, 0+CMD_INIT_DELAY, 150, // Sleep out + 0xfe, 1, 0x20, // SET PAGE + 0x26, 1, 0x0a, // MIPI OFF + 0x24, 1, 0x80, // SPI write RAM + 0x5a, 1, 0x51, //! 230918:SWIRE FOR BV6804 + 0x5b, 1, 0x2e, //! 230918:SWIRE FOR BV6804 + 0xfe, 1, 0x00, // SET PAGE + + 0x2a, 4, 0x00, 0x10, 0x01, 0xd1, // SET COLUMN START ADRESS SC = 0x0010 = 16 and EC = 0x01D1 = 465 (450 columns but an 16 offset) + 0x2b, 4, 0x00, 0x00, 0x02, 0x57, // SET ROW START ADRESS SP = 0 and EP = 0x256 = 599 (600 lines) + + 0xc2, 1, 0xA1, // Set DSI Mode; 0x00 = Internal Timmings, 0xA1 = 1010 0001, first bit = SPI interface write RAM enable + + 0x3a, 1+CMD_INIT_DELAY, 0x55, 20, // Interface Pixel Format, 0x55=16bit/pixel + 0x51, 1, 0x01, // display brightness dark (max = 0xff) + 0x29, 0+CMD_INIT_DELAY, 200, // display on + 0x51, 1, 0xd0, // display brightness (max = 0xff) + 0xff, 0xff // end + }; + switch (listno) { + case 0: return list0; + default: return nullptr; + } + } + }; + + + //---------------------------------------------------------------------------- + } + +} + + +#endif +#endif diff --git a/src/lgfx/v1/panel/Panel_SH8601Z.cpp b/src/lgfx/v1/panel/Panel_SH8601Z.cpp new file mode 100644 index 00000000..292871c9 --- /dev/null +++ b/src/lgfx/v1/panel/Panel_SH8601Z.cpp @@ -0,0 +1,584 @@ +/*----------------------------------------------------------------------------/ + * Lovyan GFX - Graphics library for embedded devices. + * + * Original Source: + * https://github.com/lovyan03/LovyanGFX/ + * + * Licence: + * [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt) + * + * Author: + * [lovyan03](https://twitter.com/lovyan03) + * + * Contributors: + * [ciniml](https://github.com/ciniml) + * [mongonta0716](https://github.com/mongonta0716) + * [tobozo](https://github.com/tobozo) + * /----------------------------------------------------------------------------*/ + +#if defined (ESP_PLATFORM) + +#include "Panel_SH8601Z.hpp" +#include "../Bus.hpp" +#include "../platforms/common.hpp" +#include "../misc/pixelcopy.hpp" +#include "../misc/colortype.hpp" +#include "driver/spi_master.h" +#include "esp_log.h" + + +/** + * @brief Bug list + * + * > Write image (pushSprite) works fine, bugs down below are from writing directly + * + * 1> Write function is block even with DMA (manual CS wait data) + * 2> In spi 40MHz draw vertical line incomplete, but 10MHz OK (Likely because my dupont line connection) + * 3> After implement write/draw pixel funcs, "testFilledRects" stucks sometime, acts differently to the different sck freq + * 4> Haven't find the way to set rotation by reg + */ + + +namespace lgfx +{ + inline namespace v1 + { + //---------------------------------------------------------------------------- + + + + /* Panel init */ + bool Panel_SH8601Z::init(bool use_reset) + { + // ESP_LOGD("SH8601Z","pannel init %d", use_reset); + + if (!Panel_Device::init(use_reset)) { + return false; + } + + + uint8_t cmds[] = + { + 0x11, 0+CMD_INIT_DELAY, 150, // Sleep out + 0x44, 2+CMD_INIT_DELAY, 0x01, 0x66, 1, + 0x35, 1+CMD_INIT_DELAY, 0x00, 1, // TE on + 0x3a, 1+CMD_INIT_DELAY, 0x55, 1, // Interface Pixel Format 16bit/pixel + 0x53, 1+CMD_INIT_DELAY, 0x20, 10, + 0x51, 1+CMD_INIT_DELAY, 0x00, 10, // Write Display Brightness MAX_VAL=0XFF + 0x29, 0+CMD_INIT_DELAY, 10, + 0x51, 1+CMD_INIT_DELAY, 0xff, 1, // Write Display Brightness MAX_VAL=0XFF + 0xff, 0xff + }; + + this->command_list(cmds); + + return true; + } + + + + void Panel_SH8601Z::setBrightness(uint8_t brightness) + { + // ESP_LOGD("SH8601Z","setBrightness %d", brightness); + + startWrite(); + + /* Write Display Brightness MAX_VAL=0XFF */ + cs_control(false); + write_cmd(0x51); + _bus->writeCommand(brightness, 8); + _bus->wait(); + cs_control(true); + + endWrite(); + } + + + void Panel_SH8601Z::setRotation(uint_fast8_t r) + { + // ESP_LOGD("SH8601Z","setRotation %d", r); + + r &= 7; + _rotation = r; + // offset_rotationを加算 (0~3:回転方向、 4:上下反転フラグ); + _internal_rotation = ((r + _cfg.offset_rotation) & 3) | ((r & 4) ^ (_cfg.offset_rotation & 4)); + + auto ox = _cfg.offset_x; + auto oy = _cfg.offset_y; + auto pw = _cfg.panel_width; + auto ph = _cfg.panel_height; + auto mw = _cfg.memory_width; + auto mh = _cfg.memory_height; + if (_internal_rotation & 1) + { + std::swap(ox, oy); + std::swap(pw, ph); + std::swap(mw, mh); + } + _width = pw; + _height = ph; + // _colstart = (_internal_rotation & 2) + // ? mw - (pw + ox) : ox; + + // _rowstart = ((1 << _internal_rotation) & 0b10010110) // case 1:2:4:7 + // ? mh - (ph + oy) : oy; + + _xs = _xe = _ys = _ye = INT16_MAX; + + // update_madctl(); + } + + + void Panel_SH8601Z::setInvert(bool invert) + { + // ESP_LOGD("SH8601Z","setInvert %d", invert); + + cs_control(false); + + if (invert) { + /* Inversion On */ + write_cmd(0x21); + } + else { + /* Inversion Off */ + write_cmd(0x20); + } + _bus->wait(); + + cs_control(true); + } + + + void Panel_SH8601Z::setSleep(bool flg) + { + // ESP_LOGD("SH8601Z","setSleep %d", flg); + + cs_control(false); + + if (flg) { + /* Sleep in */ + write_cmd(0x10); + } + else { + /* Sleep out */ + write_cmd(0x11); + delay(150); + } + _bus->wait(); + + cs_control(true); + } + + + void Panel_SH8601Z::setPowerSave(bool flg) + { + // ESP_LOGD("SH8601Z","setPowerSave"); + } + + + void Panel_SH8601Z::waitDisplay(void) + { + // ESP_LOGD("SH8601Z","waitDisplay"); + } + + + bool Panel_SH8601Z::displayBusy(void) + { + // ESP_LOGD("SH8601Z","displayBusy"); + return false; + } + + + color_depth_t Panel_SH8601Z::setColorDepth(color_depth_t depth) + { + // ESP_LOGD("SH8601Z","setColorDepth %d", depth); + + /* 0x55: 16bit/pixel */ + /* 0x66: 18bit/pixel */ + /* 0x77: 24bit/pixel */ + uint8_t cmd_send = 0; + if (depth == rgb565_2Byte) { + cmd_send = 0x55; + } + else if (depth == rgb666_3Byte) { + cmd_send = 0x66; + } + else if (depth == rgb888_3Byte) { + cmd_send = 0x77; + } + else { + return _write_depth; + } + _write_depth = depth; + + /* Set interface Pixel Format */ + startWrite(); + + cs_control(false); + write_cmd(0x3A); + _bus->writeCommand(cmd_send, 8); + _bus->wait(); + cs_control(true); + + endWrite(); + + return _write_depth; + } + + + void Panel_SH8601Z::command_list(const uint8_t *addr) + { + startWrite(); + for (;;) + { // For each command... + uint8_t cmd = *addr++; + uint8_t num = *addr++; // Number of args to follow + if (cmd == 0xFF && num == 0xFF) break; + + cs_control(false); + + this->write_cmd(cmd); // Read, issue command + uint_fast8_t ms = num & CMD_INIT_DELAY; // If hibit set, delay follows args + num &= ~CMD_INIT_DELAY; // Mask out delay bit + if (num) + { + do + { // For each argument... + _bus->writeCommand(*addr++, 8); + } while (--num); + } + + _bus->wait(); + cs_control(true); + + if (ms) + { + ms = *addr++; // Read post-command delay time (ms) + delay( (ms==255 ? 500 : ms) ); + } + } + endWrite(); + } + + + void Panel_SH8601Z::write_cmd(uint8_t cmd) + { + uint8_t cmd_buffer[4] = {0x02, 0x00, 0x00, 0x00}; + cmd_buffer[2] = cmd; + // _bus->writeBytes(cmd_buffer, 4, 0, false); + for (int i = 0; i < 4; i++) { + _bus->writeCommand(cmd_buffer[i], 8); + } + } + + + void Panel_SH8601Z::start_qspi() + { + /* Begin QSPI */ + cs_control(false); + _bus->writeCommand(0x32, 8); + _bus->writeCommand(0x00, 8); + _bus->writeCommand(0x2C, 8); + _bus->writeCommand(0x00, 8); + _bus->wait(); + } + + void Panel_SH8601Z::end_qspi() + { + /* Stop QSPI */ + _bus->writeCommand(0x32, 8); + _bus->writeCommand(0x00, 8); + _bus->writeCommand(0x00, 8); + _bus->writeCommand(0x00, 8); + _bus->wait(); + cs_control(true); + } + + + void Panel_SH8601Z::beginTransaction(void) + { + // ESP_LOGD("SH8601Z","beginTransaction"); + if (_in_transaction) return; + _in_transaction = true; + _bus->beginTransaction(); + } + + + void Panel_SH8601Z::endTransaction(void) + { + // ESP_LOGD("SH8601Z","endTransaction"); + // if (!_in_transaction) return; + // _in_transaction = false; + // _bus->endTransaction(); + + if (!_in_transaction) return; + _in_transaction = false; + + if (_has_align_data) + { + _has_align_data = false; + _bus->writeData(0, 8); + } + + _bus->endTransaction(); + } + + + void Panel_SH8601Z::write_bytes(const uint8_t* data, uint32_t len, bool use_dma) + { + start_qspi(); + _bus->writeBytes(data, len, true, use_dma); + _bus->wait(); + end_qspi(); + } + + + void Panel_SH8601Z::setWindow(uint_fast16_t xs, uint_fast16_t ys, uint_fast16_t xe, uint_fast16_t ye) + { + // ESP_LOGD("SH8601Z","setWindow %d %d %d %d", xs, ys, xe, ye); + + /* Set limit */ + if ((xe - xs) >= _width) { xs = 0; xe = _width - 1; } + if ((ye - ys) >= _height) { ys = 0; ye = _height - 1; } + + /* Set Column Start Address */ + cs_control(false); + write_cmd(0x2A); + _bus->writeCommand(xs >> 8, 8); + _bus->writeCommand(xs & 0xFF, 8); + _bus->writeCommand(xe >> 8, 8); + _bus->writeCommand(xe & 0xFF, 8); + _bus->wait(); + cs_control(true); + + /* Set Row Start Address */ + cs_control(false); + write_cmd(0x2B); + _bus->writeCommand(ys >> 8, 8); + _bus->writeCommand(ys & 0xFF, 8); + _bus->writeCommand(ye >> 8, 8); + _bus->writeCommand(ye & 0xFF, 8); + _bus->wait(); + cs_control(true); + + /* Memory Write */ + cs_control(false); + write_cmd(0x2C); + _bus->wait(); + cs_control(true); + } + + + void Panel_SH8601Z::writeBlock(uint32_t rawcolor, uint32_t len) + { + // ESP_LOGD("SH8601Z","writeBlock 0x%lx %ld", rawcolor, len); + + /* Push color */ + start_qspi(); + _bus->writeDataRepeat(rawcolor, _write_bits, len); + _bus->wait(); + end_qspi(); + } + + + + + void Panel_SH8601Z::writePixels(pixelcopy_t* param, uint32_t len, bool use_dma) + { + // ESP_LOGD("SH8601Z","writePixels %ld %d", len, use_dma); + + start_qspi(); + + if (param->no_convert) { + _bus->writeBytes(reinterpret_cast(param->src_data), len * _write_bits >> 3, true, use_dma); + } + else { + _bus->writePixels(param, len); + } + if (_cfg.dlen_16bit && (_write_bits & 15) && (len & 1)) { + _has_align_data = !_has_align_data; + } + + _bus->wait(); + end_qspi(); + } + + + void Panel_SH8601Z::drawPixelPreclipped(uint_fast16_t x, uint_fast16_t y, uint32_t rawcolor) + { + // ESP_LOGD("SH8601Z","drawPixelPreclipped %d %d 0x%lX", x, y, rawcolor); + + setWindow(x,y,x,y); + if (_cfg.dlen_16bit) { _has_align_data = (_write_bits & 15); } + + start_qspi(); + + _bus->writeData(rawcolor, _write_bits); + + _bus->wait(); + end_qspi(); + } + + + void Panel_SH8601Z::writeFillRectPreclipped(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, uint32_t rawcolor) + { + // ESP_LOGD("SH8601Z","writeFillRectPreclipped %d %d %d %d 0x%lX", x, y, w, h, rawcolor); + + uint32_t len = w * h; + uint_fast16_t xe = w + x - 1; + uint_fast16_t ye = y + h - 1; + + setWindow(x,y,xe,ye); + // if (_cfg.dlen_16bit) { _has_align_data = (_write_bits & 15) && (len & 1); } + + start_qspi(); + _bus->writeDataRepeat(rawcolor, _write_bits, len); + _bus->wait(); + end_qspi(); + } + + + + void Panel_SH8601Z::writeImage(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t* param, bool use_dma) + { + // ESP_LOGD("SH8601Z","writeImage %d %d %d %d %d", x, y, w, h, use_dma); + // use_dma = false; + + auto bytes = param->dst_bits >> 3; + auto src_x = param->src_x; + + if (param->transp == pixelcopy_t::NON_TRANSP) + { + if (param->no_convert) + { + auto wb = w * bytes; + uint32_t i = (src_x + param->src_y * param->src_bitwidth) * bytes; + auto src = &((const uint8_t*)param->src_data)[i]; + setWindow(x, y, x + w - 1, y + h - 1); + if (param->src_bitwidth == w || h == 1) + { + write_bytes(src, wb * h, use_dma); + } + else + { + auto add = param->src_bitwidth * bytes; + if (use_dma) + { + if (_cfg.dlen_16bit && ((wb * h) & 1)) + { + _has_align_data = !_has_align_data; + } + do + { + _bus->addDMAQueue(src, wb); + src += add; + } while (--h); + _bus->execDMAQueue(); + } + else + { + do + { + write_bytes(src, wb, false); + src += add; + } while (--h); + } + } + } + else + { + if (!_bus->busy()) + { + static constexpr uint32_t WRITEPIXELS_MAXLEN = 32767; + + setWindow(x, y, x + w - 1, y + h - 1); + // bool nogap = (param->src_bitwidth == w || h == 1); + bool nogap = (h == 1) || (param->src_y32_add == 0 && ((param->src_bitwidth << pixelcopy_t::FP_SCALE) == (w * param->src_x32_add))); + if (nogap && (w * h <= WRITEPIXELS_MAXLEN)) + { + writePixels(param, w * h, use_dma); + } + else + { + uint_fast16_t h_step = nogap ? WRITEPIXELS_MAXLEN / w : 1; + uint_fast16_t h_len = (h_step > 1) ? ((h - 1) % h_step) + 1 : 1; + writePixels(param, w * h_len, use_dma); + if (h -= h_len) + { + param->src_y += h_len; + do + { + param->src_x = src_x; + writePixels(param, w * h_step, use_dma); + param->src_y += h_step; + } while (h -= h_step); + } + } + } + else + { + size_t wb = w * bytes; + auto buf = _bus->getDMABuffer(wb); + param->fp_copy(buf, 0, w, param); + setWindow(x, y, x + w - 1, y + h - 1); + write_bytes(buf, wb, true); + _has_align_data = (_cfg.dlen_16bit && (_write_bits & 15) && (w & h & 1)); + while (--h) + { + param->src_x = src_x; + param->src_y++; + buf = _bus->getDMABuffer(wb); + param->fp_copy(buf, 0, w, param); + write_bytes(buf, wb, true); + } + } + } + } + else + { + h += y; + uint32_t wb = w * bytes; + do + { + uint32_t i = 0; + while (w != (i = param->fp_skip(i, w, param))) + { + auto buf = _bus->getDMABuffer(wb); + int32_t len = param->fp_copy(buf, 0, w - i, param); + setWindow(x + i, y, x + i + len - 1, y); + write_bytes(buf, len * bytes, true); + if (w == (i += len)) break; + } + param->src_x = src_x; + param->src_y++; + } while (++y != h); + } + } + + + + + uint32_t Panel_SH8601Z::readCommand(uint_fast16_t cmd, uint_fast8_t index, uint_fast8_t len) + { + // ESP_LOGD("SH8601Z","readCommand"); + return 0; + } + + uint32_t Panel_SH8601Z::readData(uint_fast8_t index, uint_fast8_t len) + { + // ESP_LOGD("SH8601Z","readData"); + return 0; + } + + void Panel_SH8601Z::readRect(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, void* dst, pixelcopy_t* param) + { + // ESP_LOGD("SH8601Z","readRect"); + } + + + //---------------------------------------------------------------------------- + } +} + + +#endif diff --git a/src/lgfx/v1/panel/Panel_SH8601Z.hpp b/src/lgfx/v1/panel/Panel_SH8601Z.hpp new file mode 100644 index 00000000..e9b0e3d2 --- /dev/null +++ b/src/lgfx/v1/panel/Panel_SH8601Z.hpp @@ -0,0 +1,79 @@ +/*----------------------------------------------------------------------------/ + * Lovyan GFX - Graphics library for embedded devices. + * + * Original Source: + * https://github.com/lovyan03/LovyanGFX/ + * + * Licence: + * [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt) + * + * Author: + * [lovyan03](https://twitter.com/lovyan03) + * + * Contributors: + * [ciniml](https://github.com/ciniml) + * [mongonta0716](https://github.com/mongonta0716) + * [tobozo](https://github.com/tobozo) + * /----------------------------------------------------------------------------*/ +#pragma once + +#if defined (ESP_PLATFORM) + +#include "Panel_Device.hpp" + +namespace lgfx +{ + inline namespace v1 + { + //---------------------------------------------------------------------------- + + struct Panel_SH8601Z : public Panel_Device + { + public: + Panel_SH8601Z(void) {} + + bool init(bool use_reset) override; + void beginTransaction(void) override; + void endTransaction(void) override; + + color_depth_t setColorDepth(color_depth_t depth) override; + void setRotation(uint_fast8_t r) override; + void setInvert(bool invert) override; + void setSleep(bool flg) override; + void setPowerSave(bool flg) override; + + void waitDisplay(void) override; + bool displayBusy(void) override; + + void writePixels(pixelcopy_t* param, uint32_t len, bool use_dma) override; + void writeBlock(uint32_t rawcolor, uint32_t len) override; + + void setWindow(uint_fast16_t xs, uint_fast16_t ys, uint_fast16_t xe, uint_fast16_t ye) override; + void drawPixelPreclipped(uint_fast16_t x, uint_fast16_t y, uint32_t rawcolor) override; + void writeFillRectPreclipped(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, uint32_t rawcolor) override; + void writeImage(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t* param, bool use_dma) override; + + uint32_t readCommand(uint_fast16_t cmd, uint_fast8_t index, uint_fast8_t len) override; + uint32_t readData(uint_fast8_t index, uint_fast8_t len) override; + void readRect(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, void* dst, pixelcopy_t* param) override; + + /* Override */ + void setBrightness(uint8_t brightness) override; + + + protected: + bool _in_transaction = false; + + void command_list(const uint8_t *addr); + + void write_cmd(uint8_t cmd); + void start_qspi(); + void end_qspi(); + void write_bytes(const uint8_t* data, uint32_t len, bool use_dma); + }; + + //---------------------------------------------------------------------------- + } +} + +#endif diff --git a/src/lgfx/v1/platforms/esp32/Bus_SPI.cpp b/src/lgfx/v1/platforms/esp32/Bus_SPI.cpp index c15da5d1..71346e52 100644 --- a/src/lgfx/v1/platforms/esp32/Bus_SPI.cpp +++ b/src/lgfx/v1/platforms/esp32/Bus_SPI.cpp @@ -56,7 +56,7 @@ Original Source: #include #else #include // dispatched by core -#endif +#endif #ifndef SPI_PIN_REG #define SPI_PIN_REG SPI_MISC_REG @@ -140,6 +140,13 @@ namespace lgfx auto spi_mode = cfg.spi_mode; _user_reg = (spi_mode == 1 || spi_mode == 2) ? SPI_CK_OUT_EDGE | SPI_USR_MOSI : SPI_USR_MOSI; //ESP_LOGI("LGFX","Bus_SPI::config spi_port:%d dc:%0d %02x", spi_port, _cfg.pin_dc, _mask_reg_dc); + +#if defined LGFX_USE_QSPI + if( _cfg.pin_io0 != -1 && _cfg.pin_io1 != -1 && _cfg.pin_io2 != -1 && _cfg.pin_io3 != -1 ) + { + _is_quad_spi = true; + } +#endif } bool Bus_SPI::init(void) @@ -154,7 +161,13 @@ namespace lgfx dma_ch = dma_ch ? SPI_DMA_CH_AUTO : SPI_DMA_DISABLED; #endif #endif - _inited = spi::init(_cfg.spi_host, _cfg.pin_sclk, _cfg.pin_miso, _cfg.pin_mosi, dma_ch).has_value(); + +#if defined LGFX_USE_QSPI + if( _is_quad_spi ) + _inited = spi::initQuad(_cfg.spi_host, _cfg.pin_sclk, _cfg.pin_io0, _cfg.pin_io1, _cfg.pin_io2, _cfg.pin_io3, dma_ch).has_value(); + else +#endif + _inited = spi::init(_cfg.spi_host, _cfg.pin_sclk, _cfg.pin_miso, _cfg.pin_mosi, dma_ch).has_value(); #if defined ( SOC_GDMA_SUPPORTED ) // 割当られたDMAチャネル番号を取得する @@ -200,9 +213,23 @@ namespace lgfx _inited = false; spi::release(_cfg.spi_host); gpio_reset(_cfg.pin_dc ); - gpio_reset(_cfg.pin_mosi); - gpio_reset(_cfg.pin_miso); - gpio_reset(_cfg.pin_sclk); + +#if defined LGFX_USE_QSPI + if(_is_quad_spi) + { + gpio_reset(_cfg.pin_io0); + gpio_reset(_cfg.pin_io1); + gpio_reset(_cfg.pin_io2); + gpio_reset(_cfg.pin_io3); + } + else +#endif + { + gpio_reset(_cfg.pin_mosi); + gpio_reset(_cfg.pin_miso); + gpio_reset(_cfg.pin_sclk); + } + } void Bus_SPI::beginTransaction(void) @@ -287,6 +314,13 @@ namespace lgfx auto spi_cmd_reg = _spi_cmd_reg; auto gpio_reg_dc = _gpio_reg_dc[0]; auto mask_reg_dc = _mask_reg_dc; + +#if defined LGFX_USE_QSPI + // reg for sending data in 1-bit mode + auto qspi_user_reg = _spi_user_reg; + uint32_t qspi_user = (*qspi_user_reg & (~SPI_FWRITE_QUAD)); +#endif + #if !defined ( CONFIG_IDF_TARGET ) || defined ( CONFIG_IDF_TARGET_ESP32 ) while (*spi_cmd_reg & SPI_USR) {} // wait SPI #else @@ -302,6 +336,11 @@ namespace lgfx while (*spi_cmd_reg & SPI_USR) {} // wait SPI } #endif + +#if defined LGFX_USE_QSPI + if( _is_quad_spi) + *qspi_user_reg = qspi_user; +#endif *spi_mosi_dlen_reg = bit_length; // set bitlength *spi_w0_reg = data; // set data *gpio_reg_dc = mask_reg_dc; // D/C @@ -318,6 +357,13 @@ namespace lgfx auto spi_cmd_reg = _spi_cmd_reg; auto gpio_reg_dc = _gpio_reg_dc[1]; auto mask_reg_dc = _mask_reg_dc; + +#if defined LGFX_USE_QSPI + // reg for sending data in 4-bit mode + auto qspi_user_reg = _spi_user_reg; + uint32_t qspi_user = (*qspi_user_reg | SPI_FWRITE_QUAD); +#endif + #if !defined ( CONFIG_IDF_TARGET ) || defined ( CONFIG_IDF_TARGET_ESP32 ) while (*spi_cmd_reg & SPI_USR) {} // wait SPI #else @@ -332,6 +378,10 @@ namespace lgfx { while (*spi_cmd_reg & SPI_USR) {} // wait SPI } +#endif +#if defined LGFX_USE_QSPI + if( _is_quad_spi) + *qspi_user_reg = qspi_user; #endif *spi_mosi_dlen_reg = bit_length; // set bitlength *spi_w0_reg = data; // set data @@ -346,6 +396,13 @@ namespace lgfx auto spi_cmd_reg = _spi_cmd_reg; auto gpio_reg_dc = _gpio_reg_dc[1]; auto mask_reg_dc = _mask_reg_dc; + +#if defined LGFX_USE_QSPI + // reg for sending data in 4-bit mode + auto qspi_user_reg = _spi_user_reg; + uint32_t qspi_user = (*qspi_user_reg | SPI_FWRITE_QUAD); +#endif + #if defined ( CONFIG_IDF_TARGET ) && !defined ( CONFIG_IDF_TARGET_ESP32 ) auto dma = _clear_dma_reg; if (dma) { _clear_dma_reg = nullptr; } @@ -356,6 +413,10 @@ namespace lgfx while (*spi_cmd_reg & SPI_USR); // wait SPI #if defined ( CONFIG_IDF_TARGET ) && !defined ( CONFIG_IDF_TARGET_ESP32 ) if (dma) { *dma = 0; } +#endif +#if defined LGFX_USE_QSPI + if( _is_quad_spi) + *qspi_user_reg = qspi_user; #endif *gpio_reg_dc = mask_reg_dc; // D/C high (data) *spi_mosi_dlen_reg = bit_length; // set bitlength @@ -387,6 +448,10 @@ namespace lgfx while (*spi_cmd_reg & SPI_USR) {} // wait SPI #if defined ( CONFIG_IDF_TARGET ) && !defined ( CONFIG_IDF_TARGET_ESP32 ) if (dma) { *dma = 0; } +#endif +#if defined LGFX_USE_QSPI + if( _is_quad_spi) + *qspi_user_reg = qspi_user; #endif *gpio_reg_dc = mask_reg_dc; // D/C high (data) *spi_mosi_dlen_reg = len; @@ -444,6 +509,17 @@ namespace lgfx void Bus_SPI::writePixels(pixelcopy_t* param, uint32_t length) { + +#if defined LGFX_USE_QSPI + if( _is_quad_spi) + { + // reg for sending data in 4-bit mode + auto qspi_user_reg = _spi_user_reg; + uint32_t qspi_user = (*qspi_user_reg | SPI_FWRITE_QUAD); + *qspi_user_reg = qspi_user; + } +#endif + const uint8_t bytes = param->dst_bits >> 3; if (_cfg.dma_channel) { @@ -548,6 +624,16 @@ namespace lgfx void Bus_SPI::writeBytes(const uint8_t* data, uint32_t length, bool dc, bool use_dma) { +#if defined LGFX_USE_QSPI + if( _is_quad_spi) + { + // reg for sending data in 4-bit mode + auto qspi_user_reg = _spi_user_reg; + uint32_t qspi_user = (*qspi_user_reg | SPI_FWRITE_QUAD); + *qspi_user_reg = qspi_user; + } +#endif + if (length <= 64) { auto spi_w0_reg = _spi_w0_reg; @@ -677,6 +763,10 @@ namespace lgfx uint32_t highpart = ((length - 1) & limit) >> 2; // 8 or 0 uint32_t user_reg = _user_reg; + + if( _is_quad_spi) + user_reg = user_reg | SPI_FWRITE_QUAD; + dc_control(dc); set_write_len(len << 3); @@ -753,6 +843,16 @@ namespace lgfx { if (0 == _dma_queue_size) return; +#if defined LGFX_USE_QSPI + if( _is_quad_spi) + { + // reg for sending data in 4-bit mode + auto qspi_user_reg = _spi_user_reg; + uint32_t qspi_user = (*qspi_user_reg | SPI_FWRITE_QUAD); + *qspi_user_reg = qspi_user; + } +#endif + int index = _dma_queue_size - 1; _dma_queue_size = 0; _dma_queue[index].eof = 1; diff --git a/src/lgfx/v1/platforms/esp32/Bus_SPI.hpp b/src/lgfx/v1/platforms/esp32/Bus_SPI.hpp index 2d258944..5fc10ef9 100644 --- a/src/lgfx/v1/platforms/esp32/Bus_SPI.hpp +++ b/src/lgfx/v1/platforms/esp32/Bus_SPI.hpp @@ -50,7 +50,7 @@ Original Source: #endif #ifndef LGFX_ESP32_SPI_DMA_CH -#define LGFX_ESP32_SPI_DMA_CH 0 + #define LGFX_ESP32_SPI_DMA_CH 0 #endif #include "../../Bus.hpp" @@ -81,6 +81,10 @@ namespace lgfx int16_t pin_miso = -1; int16_t pin_mosi = -1; int16_t pin_dc = -1; + int16_t pin_io0 = -1;// Quad spi + int16_t pin_io1 = -1;// Quad spi + int16_t pin_io2 = -1;// Quad spi + int16_t pin_io3 = -1;// Quad spi uint8_t spi_mode = 0; bool spi_3wire = true; bool use_lock = true; @@ -133,6 +137,8 @@ namespace lgfx private: + bool _is_quad_spi = false; + static __attribute__ ((always_inline)) inline volatile uint32_t* reg(uint32_t addr) { return (volatile uint32_t *)ETS_UNCACHED_ADDR(addr); } __attribute__ ((always_inline)) inline void exec_spi(void) { *_spi_cmd_reg = SPI_EXECUTE; } __attribute__ ((always_inline)) inline void wait_spi(void) { while (*_spi_cmd_reg & SPI_USR); } diff --git a/src/lgfx/v1/platforms/esp32/common.cpp b/src/lgfx/v1/platforms/esp32/common.cpp index 4ffc3553..92dbdf7c 100644 --- a/src/lgfx/v1/platforms/esp32/common.cpp +++ b/src/lgfx/v1/platforms/esp32/common.cpp @@ -602,6 +602,91 @@ namespace lgfx return {}; } + + + +#if defined LGFX_USE_QSPI + + cpp::result initQuad(int spi_host, int spi_sclk, int spi_io0, int spi_io1, int spi_io2, int spi_io3) + { + return initQuad(spi_host, spi_sclk, spi_io0, spi_io1, spi_io2, spi_io3, 0); // SPI_DMA_CH_AUTO; + } + + cpp::result initQuad(int spi_host, int spi_sclk, int spi_io0, int spi_io1, int spi_io2, int spi_io3, int dma_channel) + { + //ESP_LOGI("LGFX","spi::init host:%d, sclk:%d, miso:%d, mosi:%d, dma:%d", spi_host, spi_sclk, spi_miso, spi_mosi, dma_channel); + uint32_t spi_port = (spi_host + 1); + (void)spi_port; + + if (spi_sclk >= 0) { + gpio_lo(spi_sclk); // ここでLOWにしておくことで、pinMode変更によるHIGHパルスが出力されるのを防止する (CSなしパネル対策); + } + + // バスの設定にはESP-IDFのSPIドライバを使用する。; + if (_spi_dev_handle[spi_host] == nullptr) + { + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" + + spi_bus_config_t buscfg = { + .data0_io_num = spi_io0, + .data1_io_num = spi_io1, + .sclk_io_num = spi_sclk, + .data2_io_num = spi_io2, + .data3_io_num = spi_io3, + .max_transfer_sz = 1, + .flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_GPIO_PINS, + .intr_flags = 0 + }; + + if (ESP_OK != spi_bus_initialize(static_cast(spi_host), &buscfg, dma_channel)) + { + ESP_LOGW("LGFX", "Failed to spi_bus_initialize. "); + } + + spi_device_interface_config_t devcfg = { + .command_bits = 0, + .address_bits = 0, + .dummy_bits = 0, + .mode = 0, + .duty_cycle_pos = 0, + .cs_ena_pretrans = 0, + .cs_ena_posttrans = 0, + .clock_speed_hz = (int)getApbFrequency()>>1, + .input_delay_ns = 0, + .spics_io_num = -1, + .flags = SPI_DEVICE_3WIRE | SPI_DEVICE_HALFDUPLEX, + .queue_size = 1, + .pre_cb = nullptr, + .post_cb = nullptr + }; + + if (ESP_OK != spi_bus_add_device(static_cast(spi_host), &devcfg, &_spi_dev_handle[spi_host])) + { + ESP_LOGW("LGFX", "Failed to spi_bus_add_device. "); + } + } + +#pragma GCC diagnostic pop + + *reg(SPI_USER_REG(spi_port)) = SPI_USR_MOSI | SPI_USR_MISO | SPI_DOUTDIN; // need SD card access (full duplex setting) + *reg(SPI_CTRL_REG(spi_port)) = 0; + #if defined ( SPI_CTRL1_REG ) + *reg(SPI_CTRL1_REG(spi_port)) = 0; + #endif + #if defined ( SPI_CTRL2_REG ) + *reg(SPI_CTRL2_REG(spi_port)) = 0; + #endif + + return {}; + } + + +#endif + + + void release(int spi_host) { //ESP_LOGI("LGFX","spi::release"); diff --git a/src/lgfx/v1/platforms/esp32/common.hpp b/src/lgfx/v1/platforms/esp32/common.hpp index ada48783..e76bfed6 100644 --- a/src/lgfx/v1/platforms/esp32/common.hpp +++ b/src/lgfx/v1/platforms/esp32/common.hpp @@ -70,8 +70,17 @@ Original Source: #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) #define LGFX_IDF_V5 #endif + + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0) + #if ! defined(CONFIG_IDF_TARGET_ESP32P4) // QSPI support for ESP32P4 still needs to be fixed + #define LGFX_USE_QSPI + #endif + #endif + #endif + + namespace lgfx { inline namespace v1 @@ -293,6 +302,9 @@ namespace lgfx namespace spi { cpp::result init(int spi_host, int spi_sclk, int spi_miso, int spi_mosi, int dma_channel); +#if defined LGFX_USE_QSPI + cpp::result initQuad(int spi_host, int spi_sclk, int spi_io0, int spi_io1, int spi_io2, int spi_io3, int dma_channel); +#endif void beginTransaction(int spi_host); } diff --git a/src/lgfx/v1/touch/Touch_CST816S.cpp b/src/lgfx/v1/touch/Touch_CSTxxx.cpp similarity index 50% rename from src/lgfx/v1/touch/Touch_CST816S.cpp rename to src/lgfx/v1/touch/Touch_CSTxxx.cpp index 9698ea21..07df4ca2 100644 --- a/src/lgfx/v1/touch/Touch_CST816S.cpp +++ b/src/lgfx/v1/touch/Touch_CSTxxx.cpp @@ -19,7 +19,7 @@ Original Source: // CST816 info from here... // https://www.waveshare.com/w/upload/c/c2/CST816S_register_declaration.pdf -#include "Touch_CST816S.hpp" +#include "Touch_CSTxxx.hpp" #include "../platforms/common.hpp" @@ -174,5 +174,187 @@ namespace lgfx } //---------------------------------------------------------------------------- + + + bool Touch_CST226::init(void) + { + _inited = false; + + if (_cfg.pin_rst >= 0) { + lgfx::pinMode(_cfg.pin_rst, pin_mode_t::output); + lgfx::gpio_lo(_cfg.pin_rst); + lgfx::delay(10); + lgfx::gpio_hi(_cfg.pin_rst); + lgfx::delay(10); + } + + if (_cfg.pin_int >= 0) { + lgfx::pinMode(_cfg.pin_int, pin_mode_t::input_pullup); + } + + lgfx::i2c::init(_cfg.i2c_port, _cfg.pin_sda, _cfg.pin_scl).has_value(); + + return true; + } + + + bool Touch_CST226::_check_init(void) + { + if (_inited) return true; + + { // enter command mode + if( ! _write_reg16(0xd101)) // 0xd101 = ENUM_MODE_DEBUG_INFO + return false; + } + + lgfx::delay(10); + + { // verify chip integrity + uint32_t chip_ic_checkcode; + if( ! _read_reg16(0xd1fc, (uint8_t*)&chip_ic_checkcode, 4) ) // 0xd1fc = read chip_ic_checkcode + return false; + // printf("Touch Chip_ic_checkcode: 0x%08lx (expecting 0xcacaxxxx)\n", chip_ic_checkcode); // e.g. Chip_ic_checkcode: 0xcaca5fdc + if( (chip_ic_checkcode & 0xffff0000) != 0xcaca0000 ) + return false; + } + + lgfx::delay(10); + + { // read touch resolution + uint16_t resolution[2]; + if( ! _read_reg16(0xd1f8, (uint8_t*)&resolution, 4) ) // 0xd1f8 = read chip resolution + return false; + // printf("Touch Resolution: %d x %d\n", resolution[1], resolution[0]); // e.g. Resolution: 600 x 450 + } + + lgfx::delay(10); + + { // identify the chip id and project id (NOTE: this can be used in autodetect routine) + uint16_t c_p_info[2]; + if( ! _read_reg16(0xd204, (uint8_t*)&c_p_info, 4) ) // 0xd204 = read chip type and project id + return false; + if( c_p_info[1] != 0xa8 ) // 0xa8 is chip ID for CST226 and CST226SE + return false; + // printf("Touch Chip id: 0x%04x, project id: 0x%04x\n", c_p_info[1], c_p_info[0]); // e.g. Chip id: 0x00a8, project id: 0x465f + } + + lgfx::delay(10); + + { // check if firmware is installed + uint32_t fw_chksum[2]; + if( ! _read_reg16(0xd208, (uint8_t*)fw_chksum, 8) ) // 0xd208 = read fw version + checksum + return false; + if( fw_chksum[0]==0xA5A5A5A5 ) // the chip has no firmware !! + return false; + // printf("Touch FW Version: 0x%08lx, Checksum: 0x%08lx\n", fw_chksum[0], fw_chksum[1]); // e.g. FW version 0x01000001, Checksum: 0x140f47c8 + } + + lgfx::delay(10); + + { // exit command mode + if( ! _write_reg16(0xd109) ) // 0xd109 = ENUM_MODE_NORMAL, 0xd109 = ENUM_MODE_FACTORY + return false; + } + + lgfx::delay(5); + _inited = true; + return true; + } + + + void Touch_CST226::wakeup(void) + { + init(); + } + + + void Touch_CST226::sleep(void) + { + _write_reg16(0xd105); // 0xD105 = deep sleep + } + + + uint_fast8_t Touch_CST226::getTouchRaw(touch_point_t* tp, uint_fast8_t count) + { + if (!_inited && !_check_init()) return 0; + if (count > max_touch_points || count == 0) return 0; + auto requested_count = count; + + uint32_t msec = lgfx::millis(); + uint32_t diff_msec = msec - _last_update; + + if (diff_msec < 10 && _wait_cycle) + { + --_wait_cycle; + if (_cfg.pin_int < 0 || gpio_in(_cfg.pin_int)) { + return 0; + } + } + _last_update = msec; + + uint8_t readdata[28]; + uint8_t bytes_to_read = count==1 ? 8 : 28; + + if( ! _read_reg8(0, readdata, bytes_to_read) ) + { + return 0; + } + + if (readdata[6] != 0xab) return 0; + if (readdata[0] == 0xab) return 0; + if (readdata[5] == 0x80) return 0; + count = readdata[5] & 0x7f; + if (count > max_touch_points || count == 0) { + _write_reg16(0x00ab); // Publish the synchronization command to complete the coordinate read + return 0; + } + _wait_cycle = count ? 0 : 16; + + if (count) + { + if( requested_count > 1 ) // an array of touch_point_t was provided + { + int pIdx = 0; // points index with corrected offset + for (int i=0; i> 4) & 0xf)); + tp[i].y = (uint16_t)((readdata[pIdx+2] << 4) | (readdata[pIdx+3] & 0xf)); + pIdx += i==0 ? 7 : 5; // first point is followed by 2 extra bytes, next points are consecutive + } + } + else // a single touch_point_t was provided + { + tp[0].id = 0; + tp[0].size = readdata[4]; // pressure + tp[0].x = (uint16_t)((readdata[1] << 4) | ((readdata[3] >> 4) & 0xf)); + tp[0].y = (uint16_t)((readdata[2] << 4) | (readdata[3] & 0xf)); + } + } + return count; + } + + + bool Touch_CST226::_write_reg16(uint16_t reg) + { + reg = (reg<<8)+(reg>>8); // swap + return lgfx::i2c::transactionWrite(_cfg.i2c_port, _cfg.i2c_addr, (uint8_t*)®, 2, _cfg.freq).has_value(); + } + + + bool Touch_CST226::_read_reg16(uint16_t reg, uint8_t *data, size_t length) + { + reg = (reg<<8)+(reg>>8); // swap + return lgfx::i2c::transactionWriteRead(_cfg.i2c_port, _cfg.i2c_addr, (uint8_t*)®, 2, data, length, _cfg.freq).has_value(); + } + + + bool Touch_CST226::_read_reg8(uint8_t reg, uint8_t *data, size_t length) + { + return lgfx::i2c::transactionWriteRead(_cfg.i2c_port, _cfg.i2c_addr, ®, 1, data, length, _cfg.freq).has_value(); + } + +//---------------------------------------------------------------------------- + } } diff --git a/src/lgfx/v1/touch/Touch_CST816S.hpp b/src/lgfx/v1/touch/Touch_CSTxxx.hpp similarity index 51% rename from src/lgfx/v1/touch/Touch_CST816S.hpp rename to src/lgfx/v1/touch/Touch_CSTxxx.hpp index df54c5e9..15b78d6c 100644 --- a/src/lgfx/v1/touch/Touch_CST816S.hpp +++ b/src/lgfx/v1/touch/Touch_CSTxxx.hpp @@ -23,6 +23,13 @@ namespace lgfx { inline namespace v1 { + + // Hynitron CSTxxx family touch devices + + struct Touch_CST226; // CST226/CST226SE + struct Touch_CST816S; + + //---------------------------------------------------------------------------- struct Touch_CST816S : public ITouch @@ -41,10 +48,10 @@ namespace lgfx Touch_CST816S(void) { _cfg.i2c_addr = 0x15; - _cfg.x_min = 0; - _cfg.x_max = 320; - _cfg.y_min = 0; - _cfg.y_max = 320; + _cfg.x_min = 0; + _cfg.x_max = 320; + _cfg.y_min = 0; + _cfg.y_max = 320; } bool init(void) override; @@ -69,6 +76,64 @@ namespace lgfx size_t _read_data(uint8_t* data); }; + +//---------------------------------------------------------------------------- + + // Datasheet: https://github.com/lewisxhe/SensorLib/blob/master/datasheet/海栎创触摸芯片移植手册-v3.5-20220701(1).pdf + + struct Touch_CST226 : public ITouch + { + enum CST226_GESTURE { // D3h + NONE = 0x28, + SWIPE_LEFT = 0x20, + SWIPE_RIGHT = 0x21, + SWIPE_UP = 0x22, + SWIPE_DOWN = 0x23, + DOUBLE_CLICK = 0x24, + GESTURE_O = 0x30, + GESTURE_W = 0x31, + GESTURE_M = 0x32, + GESTURE_E = 0x33, + GESTURE_C = 0x34, + GESTURE_S = 0x46, + GESTURE_V = 0x54, + GESTURE_Z = 0x65, + }; + + Touch_CST226(void) + { + _cfg.i2c_addr = 0x5A; // 0x5a (CST226SE) or 0x1A (CST226) + _cfg.x_min = 0; + _cfg.x_max = 720; + _cfg.y_min = 0; + _cfg.y_max = 1280; + } + + bool init(void) override; + void wakeup(void) override; + void sleep(void) override; + uint_fast8_t getTouchRaw(touch_point_t* tp, uint_fast8_t count) override; + + private: + + enum + { + max_touch_points = 5, + max_keys = 3 + }; + + uint32_t _last_update = 0; + uint8_t _wait_cycle = 0; + + bool _check_init(void); + bool _read_reg8(uint8_t reg, uint8_t *data, size_t length); + bool _read_reg16(uint16_t reg, uint8_t *data, size_t length); + bool _write_reg16(uint16_t reg); + + }; + + + //---------------------------------------------------------------------------- } } diff --git a/src/lgfx/v1_init.hpp b/src/lgfx/v1_init.hpp index 051232c6..eb93fbf9 100644 --- a/src/lgfx/v1_init.hpp +++ b/src/lgfx/v1_init.hpp @@ -51,6 +51,12 @@ Original Source: #include "v1/panel/Panel_ST7796.hpp" #include "v1/panel/Panel_ST77961.hpp" +// AMOLED +#include "v1/panel/Panel_SH8601Z.hpp" +#include "v1/panel/Panel_NV3041A.hpp" +#include "v1/panel/Panel_RM690B0.hpp" +#include "v1/panel/Panel_RM67162.hpp" + // EPD #include "v1/panel/Panel_GDEW0154M09.hpp" #include "v1/panel/Panel_IT8951.hpp" @@ -60,7 +66,7 @@ Original Source: #include "v1/panel/Panel_M5UnitLCD.hpp" // TouchScreen -#include "v1/touch/Touch_CST816S.hpp" +#include "v1/touch/Touch_CSTxxx.hpp" #include "v1/touch/Touch_FT5x06.hpp" #include "v1/touch/Touch_GSLx680.hpp" #include "v1/touch/Touch_GT911.hpp" diff --git a/src/lgfx_user/LGFX_JC4827W543_4.3inch_ESP32S3.hpp b/src/lgfx_user/LGFX_JC4827W543_4.3inch_ESP32S3.hpp new file mode 100644 index 00000000..4df24d7e --- /dev/null +++ b/src/lgfx_user/LGFX_JC4827W543_4.3inch_ESP32S3.hpp @@ -0,0 +1,112 @@ +/** + * @file LGFX_JC4827W543_4.3inch_ESP32S3.hpp + * @author tobozo + * @brief + * @version 0.1 + * @date 2024-08-21 + * + * @copyleft Copyleft (c+) tobozo 2024 + * + */ +#pragma once +#include +#include + +class LGFX : public lgfx::LGFX_Device { + + lgfx::Touch_GT911 _touch_instance; + lgfx::Panel_NV3041A _panel_instance; + lgfx::Bus_SPI _bus_instance; + lgfx::Light_PWM _light_instance; + +public: + + LGFX(void) + { + { + auto cfg = _bus_instance.config(); + + cfg.spi_host = SPI3_HOST; + cfg.spi_mode = 1; + cfg.freq_write = 32000000UL; // NV3041A Maximum supported speed is 32MHz + cfg.freq_read = 16000000; + cfg.spi_3wire = true; + cfg.use_lock = true; + cfg.dma_channel = SPI_DMA_CH_AUTO; + + cfg.pin_sclk = GPIO_NUM_47; + cfg.pin_io0 = GPIO_NUM_21; + cfg.pin_io1 = GPIO_NUM_48; + cfg.pin_io2 = GPIO_NUM_40; + cfg.pin_io3 = GPIO_NUM_39; + + _bus_instance.config(cfg); + _panel_instance.setBus(&_bus_instance); + } + + + { + auto cfg = _panel_instance.config(); + + cfg.pin_cs = GPIO_NUM_45; // Chip select pin + cfg.pin_rst = GPIO_NUM_4 ; // Reset pin + cfg.pin_busy = -1; // Busy pin (not used) + + cfg.panel_width = 480; + cfg.panel_height = 272; + + cfg.memory_width = 480; + cfg.memory_height = 272; + + cfg.offset_x = 0; + cfg.offset_y = 0; + + cfg.offset_rotation = 0; + + cfg.dummy_read_pixel = 8; + cfg.dummy_read_bits = 1; + + cfg.readable = true; + cfg.invert = true; + cfg.rgb_order = true; + cfg.dlen_16bit = false; + cfg.bus_shared = true; + + _panel_instance.config(cfg); + } + + { // Configure settings for touch screen control. (delete if not necessary) + auto cfg = _touch_instance.config(); + + cfg.x_min = 0; + cfg.x_max = 480 - 1; + cfg.y_min = 0; + cfg.y_max = 272 - 1; + cfg.offset_rotation = 6; // 6 fits but double lines. why? + + // For SPI connection + cfg.bus_shared = false; // Set true when using a common bus with the screen + cfg.spi_host = SPI2_HOST; // Select SPI to use (HSPI_HOST or VSPI_HOST) + cfg.freq = 2500000; // SPI Clock frequency 1000000 -> 2500000 + cfg.pin_int = -1; // Pin number to which INT is connected (-1 = not connected) + cfg.pin_sda = GPIO_NUM_8 ; // SCLK + cfg.pin_scl = GPIO_NUM_4 ; // CS + + _touch_instance.config(cfg); + _panel_instance.setTouch(&_touch_instance); + } + + { + auto cfg = _light_instance.config(); + + cfg.pin_bl = GPIO_NUM_1 ; // Backlight pin + cfg.invert = false; + + _light_instance.config(cfg); + _panel_instance.setLight(&_light_instance); + } + + setPanel(&_panel_instance); + } + +}; diff --git a/src/lgfx_user/LGFX_Monica.hpp b/src/lgfx_user/LGFX_Monica.hpp new file mode 100644 index 00000000..65d8ca5b --- /dev/null +++ b/src/lgfx_user/LGFX_Monica.hpp @@ -0,0 +1,108 @@ +/** + * @file hal_disp.hpp + * @author Forairaaaaa + * @brief + * @version 0.1 + * @date 2023-05-20 + * + * @copyright Copyright (c) 2023 + * + */ +#pragma once +#include +#include + + +/// 独自の設定を行うクラスを、LGFX_Deviceから派生して作成します。 +class LGFX_Monica : public lgfx::LGFX_Device { + // 接続するパネルの型にあったインスタンスを用意します。 + lgfx::Panel_SH8601Z _panel_instance; + // パネルを接続するバスの種類にあったインスタンスを用意します。 + lgfx::Bus_SPI _bus_instance; + +public: + // コンストラクタを作成し、ここで各種設定を行います。 + // クラス名を変更した場合はコンストラクタも同じ名前を指定してください。 + LGFX_Monica(void) + { + { // バス制御の設定を行います。 + auto cfg = _bus_instance.config(); // バス設定用の構造体を取得します。 + + // SPIバスの設定 + cfg.spi_host = SPI3_HOST; // 使用するSPIを選択 ESP32-S2,C3 : SPI2_HOST or SPI3_HOST / ESP32 : VSPI_HOST or HSPI_HOST + // ※ ESP-IDFバージョンアップに伴い、VSPI_HOST , HSPI_HOSTの記述は非推奨になるため、エラーが出る場合は代わりにSPI2_HOST , SPI3_HOSTを使用してください。 + cfg.spi_mode = 1; // SPI通信モードを設定 (0 ~ 3) + // cfg.freq_write = 1*1000*1000; // 送信時のSPIクロック (最大80MHz, 80MHzを整数で割った値に丸められます) + // cfg.freq_write = 10*1000*1000; + cfg.freq_write = 40*1000*1000; + cfg.freq_read = 16000000; // 受信時のSPIクロック + cfg.spi_3wire = true; // 受信をMOSIピンで行う場合はtrueを設定 + cfg.use_lock = true; // トランザクションロックを使用する場合はtrueを設定 + cfg.dma_channel = SPI_DMA_CH_AUTO; // 使用するDMAチャンネルを設定 (0=DMA不使用 / 1=1ch / 2=ch / SPI_DMA_CH_AUTO=自動設 定) + + cfg.pin_sclk = 7; + cfg.pin_io0 = 9; + cfg.pin_io1 = 8; + cfg.pin_io2 = 5; + cfg.pin_io3 = 6; + // cfg.pin_dc = -1; + + _bus_instance.config(cfg); // 設定値をバスに反映します。 + _panel_instance.setBus(&_bus_instance); // バスをパネルにセットします。 + } + + { // 表示パネル制御の設定を行います。 + auto cfg = _panel_instance.config(); // 表示パネル設定用の構造体を取得します。 + + cfg.pin_cs = 13; // CSが接続されているピン番号 (-1 = disable) + cfg.pin_rst = 1; // RSTが接続されているピン番号 (-1 = disable) + cfg.pin_busy = -1; // BUSYが接続されているピン番号 (-1 = disable) + + // ※ 以下の設定値はパネル毎に一般的な初期値が設定されていますので、不明な項目はコメントアウトして試してみてください。 + + cfg.panel_width = 368; // 実際に表示可能な幅 + cfg.panel_height = 448; // 実際に表示可能な高さ + + // cfg.panel_width = 320; // 実際に表示可能な幅 + // cfg.panel_height = 240; // 実際に表示可能な高さ + + + cfg.offset_x = 0; // パネルのX方向オフセット量 + cfg.offset_y = 0; // パネルのY方向オフセット量 + cfg.offset_rotation = 0; // 回転方向の値のオフセット 0~7 (4~7は上下反転) + cfg.dummy_read_pixel = 8; // ピクセル読出し前のダミーリードのビット数 + cfg.dummy_read_bits = 1; // ピクセル以外のデータ読出し前のダミーリードのビット数 + cfg.readable = true; // データ読出しが可能な場合 trueに設定 + cfg.invert = true; // パネルの明暗が反転してしまう場合 trueに設定 + cfg.rgb_order = true; // パネルの赤と青が入れ替わってしまう場合 trueに設定 + cfg.dlen_16bit = false; // 16bitパラレルやSPIでデータ長を16bit単位で送信するパネルの場合 trueに設定 + cfg.bus_shared = true; // SDカードとバスを共有している場合 trueに設定(drawJpgFile等でバス制御を行います) + + // 以下はST7735やILI9163のようにピクセル数が可変のドライバで表示がずれる場合にのみ設定してください。 + cfg.memory_width = 480; // ドライバICがサポートしている最大の幅 + cfg.memory_height = 480; // ドライバICがサポートしている最大の高さ + + _panel_instance.config(cfg); + } + setPanel(&_panel_instance); // 使用するパネルをセットします。 + } + + + inline bool init(void) { + + /* PEN pin */ + gpio_reset_pin(GPIO_NUM_4); + gpio_set_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT); + gpio_set_pull_mode(GPIO_NUM_4, GPIO_PULLUP_PULLDOWN); + gpio_set_level(GPIO_NUM_4, 1); + // vTaskDelay(pdMS_TO_TICKS(10)); + + /* TE pin */ + gpio_reset_pin(GPIO_NUM_2); + gpio_set_direction(GPIO_NUM_2, GPIO_MODE_INPUT); + // vTaskDelay(pdMS_TO_TICKS(10)); + + /* Lgfx */ + return lgfx::LGFX_Device::init(); + } +}; diff --git a/src/lgfx_user/Lilygo_T4_S3.hpp b/src/lgfx_user/Lilygo_T4_S3.hpp new file mode 100644 index 00000000..213243f3 --- /dev/null +++ b/src/lgfx_user/Lilygo_T4_S3.hpp @@ -0,0 +1,156 @@ + +#include + +#if !defined LGFX_USE_QSPI + #error "This device does not support QSPI" +#endif + +// AMOLED Panel used by LilyGO T4-S3 + +class Lilygo_T4_S3 : public lgfx::LGFX_Device +{ + + lgfx::Bus_SPI _bus_instance; + lgfx::Panel_RM690B0 _panel_instance; + lgfx::Touch_CST226 _touch_instance; // I2C sda:6, scl:7, int:8, rst: 17 + + /* + * NOTE: RM690B0 has a limitation: for any given area to write [x, y, w, h], x and w + * must always be even or the command will be ignored. + * + * Because this limitation affects a lot of GFX functions, the Panel_RM690B0 driver + * comes with an optional framebuffer. + * + * With framebuffer disabled (default): + * Most GFX functions will misbehave when x-coords and area-width aren't even. + * This profile is appropriate to pick when LovyanGFX is acting as a display + * driver only (e.g. LVGL, camera streaming), or when the GFX functions are used + * within RM690B0's limitations. + * + * With framebuffer enabled: + * All GFX functions are available without limitation, but overall performance + * is lower since some writes will happen twice. + * Framebuffer can be enabled in two diffent ways: + * - tft.enableFrameBuffer(true) << screen draws are triggered automatically + * - tft.enableFrameBuffer(false) << screen draws must be explicitely triggered using + * tft.display() or tft.display(x, y, w, h). + * This profile is appropriate when maximum compatibility with GFX functions is + * more important than performance. + * + */ + + public: + + Lilygo_T4_S3(void) + { + { + auto cfg = _bus_instance.config(); + + cfg.freq_write = 40000000; + cfg.freq_read = 20000000; // irrelevant + + cfg.pin_sclk = GPIO_NUM_15; + cfg.pin_io0 = GPIO_NUM_14; + cfg.pin_io1 = GPIO_NUM_10; + cfg.pin_io2 = GPIO_NUM_16; + cfg.pin_io3 = GPIO_NUM_12; + + cfg.spi_host = SPI2_HOST; + cfg.spi_mode = SPI_MODE0; + cfg.dma_channel = SPI_DMA_CH_AUTO; + + _bus_instance.config(cfg); + _panel_instance.setBus(&_bus_instance); + } + + { + auto cfg = _panel_instance.config(); + cfg.pin_rst = GPIO_NUM_13; + cfg.pin_cs = GPIO_NUM_11; + cfg.panel_width = 452; + cfg.panel_height = 600; + + cfg.readable = true; + + cfg.offset_x = 16; + cfg.offset_y = 0; + + _panel_instance.config(cfg); + } + + + { + auto cfg = _touch_instance.config(); + cfg.i2c_addr = 0x5A; // 0x5a (CST226SE) or 0x1A (CST226) + cfg.pin_sda = GPIO_NUM_6; + cfg.pin_scl = GPIO_NUM_7; + cfg.pin_int = GPIO_NUM_8; + cfg.pin_rst = GPIO_NUM_17; + + cfg.x_min = 0; + cfg.y_min = 0; + cfg.x_max = 450; + cfg.y_max = 600; + + cfg.freq = 400000; + cfg.bus_shared = false; + cfg.offset_rotation = _offset_rotation = 0; + + _touch_instance.config(cfg); + _panel_instance.setTouch(&_touch_instance); + } + + setPanel(&_panel_instance); + } + + + bool enableFrameBuffer(bool auto_display=false) + { + if( _panel_instance.initPanelFb() ) { + auto fbPanel = _panel_instance.getPanelFb(); + if( fbPanel ) { + fbPanel->setBus(&_bus_instance); + auto touch_cfg = _touch_instance.config(); + _offset_rotation = touch_cfg.offset_rotation; // memoize for later restoration + touch_cfg.offset_rotation = _panel_instance.getRotation(); // apply display rotation + if(touch_cfg.offset_rotation & 1) + std::swap(touch_cfg.x_max, touch_cfg.y_max); + _touch_instance.config(touch_cfg); + fbPanel->setTouch(&_touch_instance); // attach touch to the framebuffer + fbPanel->setAutoDisplay(auto_display); + setPanel(fbPanel); + return true; + } + } + return false; + } + + + void disableFrameBuffer() + { + auto fbPanel = _panel_instance.getPanelFb(); + if(fbPanel) { + _panel_instance.deinitPanelFb(); + auto touch_cfg = _touch_instance.config(); + if(touch_cfg.offset_rotation & 1) + std::swap(touch_cfg.x_max, touch_cfg.y_max); + touch_cfg.offset_rotation = _offset_rotation; // restore memoized offset rotation + _touch_instance.config(touch_cfg); + _panel_instance.setTouch(&_touch_instance); // reattach touch to the display + setPanel(&_panel_instance); + } + } + + + bool init() + { + lgfx::pinMode(GPIO_NUM_9, lgfx::pin_mode_t::output); // power enable for LCD + lgfx::gpio_hi(GPIO_NUM_9); + bool ret = lgfx::LGFX_Device::init(); + return ret; + } + + protected: + uint8_t _offset_rotation = 0; // memoize offset rotation when toggling framebuffer + +}; diff --git a/src/lgfx_user/Lilygo_T_Display_S3_AMOLED.hpp b/src/lgfx_user/Lilygo_T_Display_S3_AMOLED.hpp new file mode 100644 index 00000000..0ef8b0b9 --- /dev/null +++ b/src/lgfx_user/Lilygo_T_Display_S3_AMOLED.hpp @@ -0,0 +1,115 @@ + +#include + +#if !defined LGFX_USE_QSPI + #error "This device does not support QSPI" +#endif + +// AMOLED Panel used by LilyGO T-Display-S3-AMOLED + +class Lilygo_T_Display_S3_AMOLED : public lgfx::LGFX_Device +{ + + lgfx::Bus_SPI _bus_instance; + lgfx::Panel_RM67162 _panel_instance; // 1.91 inch RM67162 IPS AMOLED: + + /* + * NOTE: RM67162 has a limitation: for any given area to write [x, y, w, h], x and w + * must always be even or the command will be ignored. + * + * Because this limitation affects a lot of GFX functions, the Panel_RM67162 driver + * comes with an optional framebuffer. + * + * With framebuffer disabled (default): + * Most GFX functions will misbehave when x-coords and area-width aren't even. + * This profile is appropriate to pick when LovyanGFX is acting as a display + * driver only (e.g. LVGL, camera streaming), or when the GFX functions are used + * within RM67162's limitations. + * + * With framebuffer enabled: + * All GFX functions are available without limitation, but overall performance + * is lower since some writes will happen twice. + * Framebuffer can be enabled in two diffent ways: + * - tft.enableFrameBuffer(true) << screen draws are triggered automatically + * - tft.enableFrameBuffer(false) << screen draws must be explicitely triggered using + * tft.display() or tft.display(x, y, w, h). + * This profile is appropriate when maximum compatibility with GFX functions is + * more important than performance. + * + */ + + public: + + Lilygo_T_Display_S3_AMOLED(void) + { + { + auto cfg = _bus_instance.config(); + + cfg.freq_write = 75000000; + cfg.freq_read = 20000000; // irrelevant + + cfg.pin_sclk = GPIO_NUM_47; + cfg.pin_io0 = GPIO_NUM_18; + cfg.pin_io1 = GPIO_NUM_7; + cfg.pin_io2 = GPIO_NUM_48; + cfg.pin_io3 = GPIO_NUM_5; + + cfg.spi_host = SPI2_HOST; + cfg.spi_mode = SPI_MODE0; + cfg.dma_channel = SPI_DMA_CH_AUTO; + + _bus_instance.config(cfg); + _panel_instance.setBus(&_bus_instance); + } + + { + auto cfg = _panel_instance.config(); + cfg.pin_rst = GPIO_NUM_17; + cfg.pin_cs = GPIO_NUM_6; + cfg.panel_width = 240; + cfg.panel_height = 536; + + cfg.readable = true; + + _panel_instance.config(cfg); + } + + setPanel(&_panel_instance); + } + + + bool enableFrameBuffer(bool auto_display=false) + { + if( _panel_instance.initPanelFb() ) { + auto fbPanel = _panel_instance.getPanelFb(); + if( fbPanel ) { + fbPanel->setBus(&_bus_instance); + fbPanel->setAutoDisplay(auto_display); + setPanel(fbPanel); + return true; + } + } + return false; + } + + + void disableFrameBuffer() + { + auto fbPanel = _panel_instance.getPanelFb(); + if(fbPanel) { + _panel_instance.deinitPanelFb(); + setPanel(&_panel_instance); + } + } + + + bool init() + { + lgfx::pinMode(GPIO_NUM_9, lgfx::pin_mode_t::output); // power enable for LCD + lgfx::gpio_hi(GPIO_NUM_9); + bool ret = lgfx::LGFX_Device::init(); + return ret; + } + + +};