diff --git a/.github/workflows/esp-idf_with-gfx.yml b/.github/workflows/esp-idf_with-gfx.yml new file mode 100644 index 0000000..20a6af3 --- /dev/null +++ b/.github/workflows/esp-idf_with-gfx.yml @@ -0,0 +1,48 @@ +name: esp-idf with Adafruit GFX Library + +on: + push: + paths-ignore: + - '**.md' + - 'doc/**' + pull_request: + paths-ignore: + - '**.md' + - 'doc/**' + +jobs: + build: + name: esp-idf with Adafruit GFX + + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + with: + submodules: 'recursive' + - name: Checkout ESP32-HUB75-MatrixPanel-I2S-DMA component + uses: actions/checkout@v4 + with: + path: 'examples/esp-idf/with-gfx/components/ESP32-HUB75-MatrixPanel-I2S-DMA' + - name: Checkout Adafruit-GFX-Library repo + uses: actions/checkout@v4 + with: + repository: 'adafruit/Adafruit-GFX-Library' + path: 'examples/esp-idf/with-gfx/components/Adafruit-GFX-Library' + - name: Checkout Adafruit_BusIO repo + uses: actions/checkout@v4 + with: + repository: 'adafruit/Adafruit_BusIO' + path: 'examples/esp-idf/with-gfx/components/Adafruit_BusIO' + - name: Checkout arduino-esp32 repo + uses: actions/checkout@v4 + with: + repository: 'espressif/arduino-esp32' + path: 'examples/esp-idf/with-gfx/components/arduino' + - name: esp-idf build + uses: espressif/esp-idf-ci-action@v1 + with: + esp_idf_version: v4.4.4 + target: esp32 + path: 'examples/esp-idf/with-gfx' diff --git a/.github/workflows/esp-idf_without-gfx.yml b/.github/workflows/esp-idf_without-gfx.yml new file mode 100644 index 0000000..1038a65 --- /dev/null +++ b/.github/workflows/esp-idf_without-gfx.yml @@ -0,0 +1,33 @@ +name: esp-idf without Adafruit GFX Library + +on: + push: + paths-ignore: + - '**.md' + - 'doc/**' + pull_request: + paths-ignore: + - '**.md' + - 'doc/**' + +jobs: + build: + name: esp-idf without Adafruit GFX + + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + with: + submodules: 'recursive' + - name: Checkout ESP32-HUB75-MatrixPanel-I2S-DMA component + uses: actions/checkout@v4 + with: + path: 'examples/esp-idf/without-gfx/components/ESP32-HUB75-MatrixPanel-I2S-DMA' + - name: esp-idf build + uses: espressif/esp-idf-ci-action@v1 + with: + esp_idf_version: v4.4 + target: esp32 + path: 'examples/esp-idf/without-gfx' diff --git a/.github/workflows/pio_build.yml b/.github/workflows/pio_build.yml index 06729b5..3d2d91d 100644 --- a/.github/workflows/pio_build.yml +++ b/.github/workflows/pio_build.yml @@ -10,13 +10,11 @@ on: paths-ignore: - '**.md' - 'doc/**' - - '.github/**' pull_request: branches: [ master, dev ] paths-ignore: - '**.md' - 'doc/**' - - '.github/**' jobs: build: @@ -26,18 +24,18 @@ jobs: matrix: framework: ["Arduino", "IDF"] no_gfx: ["", -DNO_GFX] - no_fast_functions: ["", -DNO_FAST_FUNCTIONS] - no_cie1931: ["", -DNO_CIE1931] - virtual_panel: ["", -DVIRTUAL_PANE] +# no_fast_functions: ["", -DNO_FAST_FUNCTIONS] +# no_cie1931: ["", -DNO_CIE1931] +# virtual_panel: ["", -DVIRTUAL_PANE] example: - "examples/PIO_TestPatterns" - exclude: - - no_fast_functions: "" - virtual_panel: -DVIRTUAL_PANE +# exclude: +# - no_fast_functions: "" +# virtual_panel: -DVIRTUAL_PANE steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Cache pip and platformio uses: actions/cache@v3 with: @@ -50,7 +48,7 @@ jobs: with: python-version: '3.x' - name: Install Platformio - run: pip install --upgrade platformio + run: pip install --upgrade platformio==6.1.6 - name: Run PlatformIO CI (Arduino) if: ${{ matrix.framework == 'Arduino'}} env: diff --git a/CMakeLists.txt b/CMakeLists.txt index f315e09..aa6a54f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,17 +5,34 @@ cmake_minimum_required(VERSION 3.5) idf_build_get_property(target IDF_TARGET) -if(ARDUINO_ARCH_ESP32) - list(APPEND arduino_build arduino Adafruit-GFX-Library) +if(ARDUINO_ARCH_ESP32 OR CONFIG_ESP32_HUB75_USE_GFX) + list(APPEND build_dependencies arduino Adafruit-GFX-Library) else() - list(APPEND esp_idf_build esp_lcd driver) + list(APPEND build_dependencies esp_lcd driver) endif() -idf_component_register(SRCS "src/platforms/esp32/esp32_i2s_parallel_dma.cpp" "src/ESP32-HUB75-MatrixPanel-I2S-DMA.cpp" "src/ESP32-HUB75-MatrixPanel-leddrivers.cpp" - src/platforms/${target}/gdma_lcd_parallel16.cpp - INCLUDE_DIRS "./src" - REQUIRES ${arduino_build} ${esp_idf_build} + +if(${target} STREQUAL "esp32s3") + list(APPEND extra_srcs src/platforms/${target}/gdma_lcd_parallel16.cpp) +endif() + +idf_component_register(SRCS "src/platforms/esp32/esp32_i2s_parallel_dma.cpp" "src/ESP32-HUB75-MatrixPanel-I2S-DMA.cpp" "src/ESP32-HUB75-MatrixPanel-leddrivers.cpp" ${extra_srcs} + INCLUDE_DIRS "./src" ) +# Dependencies cannot be added to the REQUIRES argument of `idf_component_register` because (according to the build process +# listed at https://docs.espressif.com/projects/esp-idf/en/v4.2/esp32/api-guides/build-system.html#build-process) +# `idf_component_register` is processed during the "Enumeration" stage which happens before the sdkconfig file is loaded +# in the "Processing" stage. So if dependencies are going to be loaded based on certain CONFIG_* variables we must +# use `target_link_libraries` instead. This is the method used by Arduino's CMakeLists.txt file. +idf_build_get_property(components BUILD_COMPONENTS) +foreach(component_name IN LISTS build_dependencies) + if (NOT ${component_name} IN_LIST components) + message(FATAL_ERROR "Missing component: ${component_name}") + endif() + idf_component_get_property(lib_name ${component_name} COMPONENT_LIB) + target_link_libraries(${COMPONENT_LIB} PUBLIC ${lib_name}) +endforeach() + # In case you are running into issues with "missing" header files from 3rd party libraries # you can add them to the REQUIRES section above. If you use some of the build options below # you probably want to remove (NO_GFX) or replace Adafruit-GFX-Library (USE_GFX_ROOT) @@ -25,11 +42,14 @@ idf_component_register(SRCS "src/platforms/esp32/esp32_i2s_parallel_dma.cpp" "sr # target_compile_options(${COMPONENT_TARGET} PUBLIC -DNO_GFX) # esp-idf does not have any GFX library support yet, so we need to define NO_GFX -if(ARDUINO_ARCH_ESP32) +if(ARDUINO_ARCH_ESP32 OR CONFIG_ESP32_HUB75_USE_GFX) else() target_compile_options(${COMPONENT_TARGET} PUBLIC -DNO_GFX) if(${target} STREQUAL "esp32s3") - target_compile_options(${COMPONENT_TARGET} PUBLIC -DSPIRAM_FRAMEBUFFER) + # Don't enable PSRAM based framebuffer just because it's an S3. + # This is an advanced option and should only be used with an S3 with Octal-SPI RAM. + # target_compile_options(${COMPONENT_TARGET} PUBLIC -DSPIRAM_FRAMEBUFFER) + target_compile_options(${COMPONENT_TARGET} PUBLIC) endif() endif() diff --git a/Kconfig.projbuild b/Kconfig.projbuild new file mode 100644 index 0000000..1668a97 --- /dev/null +++ b/Kconfig.projbuild @@ -0,0 +1,9 @@ +menu "ESP32 HUB75 Configuration" + + config ESP32_HUB75_USE_GFX + bool "Use Adafruit GFX library." + default y + help + This option enables use of the Adafruit GFX library using the `Adafruit-GFX-Library` component. + +endmenu diff --git a/README.md b/README.md index 8cd3a49..cf27214 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ A typical 64x32px panel at 24bpp colour uses about 20kB of internal memory. Please use the ['Memory Calculator'](/doc/memcalc.md) to see what is *typically* achievable with the typical ESP32. ![Memory Calculator](doc/memcalc.jpg) -For the ESP32-S3 only, you can use SPIRAM/PSRAM to drive the HUB75 DMA buffer when using **Octal SPI-RAM** (i.e. ESP32 S3 N8R8 variant). However, due to bandwidth limitations, the maximum output frequency is limited to approx. 13Mhz, which will limit the real-world number of panels that can be chained without flicker. +For the ESP32-S3 only, you can use SPIRAM/PSRAM to drive the HUB75 DMA buffer when using an ESP32-S3 with **OCTAL SPI-RAM (PSTRAM)** (i.e. ESP32 S3 N8R8 variant). However, due to bandwidth limitations, the maximum output frequency is limited to approx. 13Mhz, which will limit the real-world number of panels that can be chained without flicker. Please do not use PSRAM as the DMA buffer if using QUAD SPI (Q-SPI), as it's too slow. To enable PSRAM support on the ESP32-S3, refer to [the build options](/doc/BuildOptions.md) to enable. @@ -82,9 +82,11 @@ Due to the high-speed optimized nature of this library, only specific panels are * ICND2012 * [RUC7258](http://www.ruichips.com/en/products.html?cateid=17496) * FM6126A AKA ICN2038S, [FM6124](https://datasheet4u.com/datasheet-pdf/FINEMADELECTRONICS/FM6124/pdf.php?id=1309677) (Refer to [PatternPlasma](/examples/2_PatternPlasma) example on how to use.) -* SM5266P +* SM5266P +* DP3246 with SM5368 row addressing registers -## Unsupported Panels +## Unsupported chips +* [SM1620B](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/issues/416) * RUL5358 / SHIFTREG_ABC_BIN_DE based panels are not supported. * ICN2053 / FM6353 based panels - Refer to [this library](https://github.com/LAutour/ESP32-HUB75-MatrixPanel-DMA-ICN2053), which is a fork of this library ( [discussion link](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/discussions/324)). * Any other panel not listed above. @@ -94,7 +96,7 @@ Please use an [alternative library](https://github.com/2dom/PxMatrix) if you bou # Getting Started ## 1. Library Installation -* Dependancy: You will need to install Adafruit_GFX from the "Library > Manage Libraries" menu. +* Dependency: You will need to install Adafruit_GFX from the "Library > Manage Libraries" menu. * Install this library from the Arduino Library manager. Library also tested to work fine with PlatformIO, install into your PlatformIO projects' lib/ folder as appropriate. Or just add it into [platformio.ini](/doc/BuildOptions.md) [lib_deps](https://docs.platformio.org/en/latest/projectconf/section_env_library.html#lib-deps) section. @@ -132,6 +134,8 @@ HUB75_I2S_CFG mxconfig( dma_display = new MatrixPanel_I2S_DMA(mxconfig); ``` +Make sure you also connect one of the HUB75 interfaces ground pins to a ground pin of the ESP32, otherwise you may get electrical artefacts on LED Matrix Panel. + Various people have created PCBs for which one can simply connect an ESP32 to a PCB, and then the PCB to the HUB75 connector, such as: * Brian Lough's [ESP32 I2S Matrix Shield](http://blough.ie/i2smat/) @@ -226,4 +230,6 @@ There are a number of great looking LED graphical display projects which leverag * [PaintYourDragon](https://github.com/PaintYourDragon) for the DMA logic for the ESP32-S3. * And lots of others, let me know if I've missed you. +If you want to donate money to the project, please refer to [this discussion](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/discussions/349) about it. If you want to donate/buy an LED panel for the library author to improve compatibility and/or testing - please feel free to post in the same [discussion](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/discussions/349). + ![It's better in real life](image.jpg) diff --git a/doc/Panel_Chaining_Types.ods b/doc/Panel_Chaining_Types.ods new file mode 100644 index 0000000..e41e576 Binary files /dev/null and b/doc/Panel_Chaining_Types.ods differ diff --git a/examples/ChainedPanels/VirtualMatrixPanel.odp b/doc/VirtualMatrixPanel (old).odp similarity index 100% rename from examples/ChainedPanels/VirtualMatrixPanel.odp rename to doc/VirtualMatrixPanel (old).odp diff --git a/doc/VirtualMatrixPanel.odp b/doc/VirtualMatrixPanel.odp new file mode 100644 index 0000000..748a211 Binary files /dev/null and b/doc/VirtualMatrixPanel.odp differ diff --git a/doc/VirtualMatrixPanel.pdf b/doc/VirtualMatrixPanel.pdf new file mode 100644 index 0000000..f6e7157 Binary files /dev/null and b/doc/VirtualMatrixPanel.pdf differ diff --git a/examples/BouncingSquares/BouncingSquares.ino b/examples/3_DoubleBuffer/3_DoubleBuffer.ino similarity index 74% rename from examples/BouncingSquares/BouncingSquares.ino rename to examples/3_DoubleBuffer/3_DoubleBuffer.ino index 6a01225..5a41d67 100644 --- a/examples/BouncingSquares/BouncingSquares.ino +++ b/examples/3_DoubleBuffer/3_DoubleBuffer.ino @@ -1,3 +1,9 @@ +// Example uses the following configuration: mxconfig.double_buff = true; +// to enable double buffering, which means display->flipDMABuffer(); is required. + +// Bounce squares around the screen, doing the re-drawing in the background back-buffer. +// Double buffering is not always required in reality. + #include MatrixPanel_I2S_DMA *display = nullptr; @@ -32,14 +38,14 @@ void setup() Serial.println("...Starting Display"); HUB75_I2S_CFG mxconfig; - //mxconfig.double_buff = true; // Turn of double buffer - mxconfig.clkphase = false; + mxconfig.double_buff = true; // <------------- Turn on double buffer + //mxconfig.clkphase = false; // OK, now we can create our matrix object display = new MatrixPanel_I2S_DMA(mxconfig); display->begin(); // setup display with pins as pre-defined in the library - // Create some Squares + // Create some random squares for (int i = 0; i < numSquares; i++) { Squares[i].square_size = random(2,10); @@ -47,8 +53,6 @@ void setup() Squares[i].ypos = random(0, display->height() - Squares[i].square_size); Squares[i].velocityx = static_cast (rand()) / static_cast (RAND_MAX); Squares[i].velocityy = static_cast (rand()) / static_cast (RAND_MAX); - //Squares[i].xdir = (random(2) == 1) ? true:false; - //Squares[i].ydir = (random(2) == 1) ? true:false; int random_num = random(6); Squares[i].colour = colours[random_num]; @@ -57,9 +61,11 @@ void setup() void loop() { - display->flipDMABuffer(); // not used if double buffering isn't enabled - delay(25); - display->clearScreen(); + + display->flipDMABuffer(); // Show the back buffer, set currently output buffer to the back (i.e. no longer being sent to LED panels) + display->clearScreen(); // Now clear the back-buffer + + delay(16); // <----------- Shouldn't see this clearscreen occur as it happens on the back buffer when double buffering is enabled. for (int i = 0; i < numSquares; i++) { diff --git a/examples/3_FM6126Panel/README.md b/examples/3_FM6126Panel/README.md deleted file mode 100644 index 65019e6..0000000 --- a/examples/3_FM6126Panel/README.md +++ /dev/null @@ -1,3 +0,0 @@ -## FM6126 based LED Matrix Panel Reset ## - -FM6216 panels require a special reset sequence before they can be used, check your panel chipset if you have issues. Refer to this example. diff --git a/examples/3_FM6126Panel/3_FM6126Panel.ino b/examples/4_OtherShiftDriverPanel/4_OtherShiftDriverPanel.ino similarity index 74% rename from examples/3_FM6126Panel/3_FM6126Panel.ino rename to examples/4_OtherShiftDriverPanel/4_OtherShiftDriverPanel.ino index 3b706e1..e62cecc 100644 --- a/examples/3_FM6126Panel/3_FM6126Panel.ino +++ b/examples/4_OtherShiftDriverPanel/4_OtherShiftDriverPanel.ino @@ -1,19 +1,30 @@ -// How to use this library with a FM6126 panel, thanks goes to: -// https://github.com/hzeller/rpi-rgb-led-matrix/issues/746 +/********************************************************************** + * The library by default supports simple 'shift register' based panels + * with A,B,C,D,E lines to select a specific row, but there are plenty + * of examples of new chips coming on the market that work different. + * + * Please search through the project's issues. For some of these chips + * (you will need to look at the back of your panel to identify), this + * library has workarounds. This can be configured through using one of: + + // mxconfig.driver = HUB75_I2S_CFG::FM6126A; + //mxconfig.driver = HUB75_I2S_CFG::ICN2038S; + //mxconfig.driver = HUB75_I2S_CFG::FM6124; + //mxconfig.driver = HUB75_I2S_CFG::MBI5124; + */ + #include #include #include //////////////////////////////////////////////////////////////////// -// FM6126 support is still experimental // Output resolution and panel chain length configuration #define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module. #define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module. #define PANEL_CHAIN 1 // Total number of panels chained one to another - // placeholder for the matrix object MatrixPanel_I2S_DMA *dma_display = nullptr; @@ -33,14 +44,6 @@ CRGB ColorFromCurrentPalette(uint8_t index = 0, uint8_t brightness = 255, TBlend } void setup(){ - - /* - The configuration for MatrixPanel_I2S_DMA object is held in HUB75_I2S_CFG structure, - All options has it's predefined default values. So we can create a new structure and redefine only the options we need - - Please refer to the '2_PatternPlasma.ino' example for detailed example of how to use the MatrixPanel_I2S_DMA configuration - if you need to change the pin mappings etc. - */ HUB75_I2S_CFG mxconfig( PANEL_RES_X, // module width @@ -48,7 +51,12 @@ void setup(){ PANEL_CHAIN // Chain length ); - mxconfig.driver = HUB75_I2S_CFG::FM6126A; // in case that we use panels based on FM6126A chip, we can set it here before creating MatrixPanel_I2S_DMA object + // in case that we use panels based on FM6126A chip, we can set it here before creating MatrixPanel_I2S_DMA object + mxconfig.driver = HUB75_I2S_CFG::FM6126A; + //mxconfig.driver = HUB75_I2S_CFG::ICN2038S; + //mxconfig.driver = HUB75_I2S_CFG::FM6124; + //mxconfig.driver = HUB75_I2S_CFG::MBI5124; + // OK, now we can create our matrix object dma_display = new MatrixPanel_I2S_DMA(mxconfig); @@ -99,4 +107,8 @@ void loop(){ fps_timer = millis(); fps = 0; } -} \ No newline at end of file +} + + +// FM6126 panel , thanks goes to: +// https://github.com/hzeller/rpi-rgb-led-matrix/issues/746 diff --git a/examples/3_FM6126Panel/FM6126A.md b/examples/4_OtherShiftDriverPanel/FM6126A.md similarity index 100% rename from examples/3_FM6126Panel/FM6126A.md rename to examples/4_OtherShiftDriverPanel/FM6126A.md diff --git a/examples/4_OtherShiftDriverPanel/README.md b/examples/4_OtherShiftDriverPanel/README.md new file mode 100644 index 0000000..40289cc --- /dev/null +++ b/examples/4_OtherShiftDriverPanel/README.md @@ -0,0 +1,13 @@ +## Ohter driver based LED Matrix Panels ## + +Limited support for other panels exists, but requires this to be passed as a configuration option when using the library. + +These panels require a special reset sequence before they can be used, check your panel chipset if you have issues. Refer to the example. + + +``` + mxconfig.driver = HUB75_I2S_CFG::FM6126A; + mxconfig.driver = HUB75_I2S_CFG::ICN2038S; + mxconfig.driver = HUB75_I2S_CFG::FM6124; + mxconfig.driver = HUB75_I2S_CFG::MBI5124; +``` diff --git a/examples/AnimatedGIFPanel/AnimatedGIFPanel.ino b/examples/AnimatedGIFPanel/AnimatedGIFPanel.ino deleted file mode 100644 index f6986f1..0000000 --- a/examples/AnimatedGIFPanel/AnimatedGIFPanel.ino +++ /dev/null @@ -1,7 +0,0 @@ -// Example sketch which shows how to display a 64x32 animated GIF image stored in FLASH memory -// on a 64x32 LED matrix -// -// Credits: https://github.com/bitbank2/AnimatedGIF/tree/master/examples/ESP32_LEDMatrix_I2S -// - -// Refer to: https://github.com/bitbank2/AnimatedGIF/blob/master/examples/ESP32_LEDMatrix_I2S/ESP32_LEDMatrix_I2S.ino diff --git a/examples/AnimatedGIFPanel_SD/AnimatedGIFPanel_SD.ino b/examples/AnimatedGIFPanel_SD/AnimatedGIFPanel_SD.ino new file mode 100644 index 0000000..9612244 --- /dev/null +++ b/examples/AnimatedGIFPanel_SD/AnimatedGIFPanel_SD.ino @@ -0,0 +1,268 @@ +/********************************************************************* + * AnimatedGif LED Matrix Panel example where the GIFs are + * stored on a SD card connected to the ESP32 using the + * standard GPIO pins used for SD card acces via. SPI. + * + * Put the gifs into a directory called 'gifs' (case sensitive) on + * a FAT32 formatted SDcard. + ********************************************************************/ +#include "FS.h" +#include "SD.h" +#include "SPI.h" +#include +#include + +/******************************************************************** + * Pin mapping below is for LOLIN D32 (ESP 32) + * + * Default pin mapping used by this library is NOT compatable with the use of the + * ESP32-Arduino 'SD' card library (there is overlap). As such, some of the pins + * used for the HUB75 panel need to be shifted. + * + * 'SD' card library requires GPIO 23, 18 and 19 + * https://github.com/espressif/arduino-esp32/tree/master/libraries/SD + * + */ + +/* + * Connect the SD card to the following pins: + * + * SD Card | ESP32 + * D2 - + * D3 SS + * CMD MOSI + * VSS GND + * VDD 3.3V + * CLK SCK + * VSS GND + * D0 MISO + * D1 - + */ + +/**** SD Card GPIO mappings ****/ +#define SS_PIN 5 +//#define MOSI_PIN 23 +//#define MISO_PIN 19 +//#define CLK_PIN 18 + + +/**** HUB75 GPIO mapping ****/ +// GPIO 34+ are on the ESP32 are input only!! +// https://randomnerdtutorials.com/esp32-pinout-reference-gpios/ + +#define A_PIN 33 // remap esp32 library default from 23 to 33 +#define B_PIN 32 // remap esp32 library default from 19 to 32 +#define C_PIN 22 // remap esp32 library defaultfrom 5 to 22 + +//#define R1_PIN 25 // library default for the esp32, unchanged +//#define G1_PIN 26 // library default for the esp32, unchanged +//#define B1_PIN 27 // library default for the esp32, unchanged +//#define R2_PIN 14 // library default for the esp32, unchanged +//#define G2_PIN 12 // library default for the esp32, unchanged +//#define B2_PIN 13 // library default for the esp32, unchanged +//#define D_PIN 17 // library default for the esp32, unchanged +//#define E_PIN -1 // IMPORTANT: Change to a valid pin if using a 64x64px panel. + +//#define LAT_PIN 4 // library default for the esp32, unchanged +//#define OE_PIN 15 // library default for the esp32, unchanged +//#define CLK_PIN 16 // library default for the esp32, unchanged + +/*************************************************************** + * HUB 75 LED DMA Matrix Panel Configuration + **************************************************************/ +#define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module. +#define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module. +#define PANEL_CHAIN 1 // Total number of panels chained one to another + +/**************************************************************/ + +AnimatedGIF gif; +MatrixPanel_I2S_DMA *dma_display = nullptr; + +static int totalFiles = 0; // GIF files count + +static File FSGifFile; // temp gif file holder +static File GifRootFolder; // directory listing + +std::vector GifFiles; // GIF files path + +const int maxGifDuration = 30000; // ms, max GIF duration + +#include "gif_functions.hpp" +#include "sdcard_functions.hpp" + + +/**************************************************************/ +void draw_test_patterns(); +int gifPlay( const char* gifPath ) +{ // 0=infinite + + if( ! gif.open( gifPath, GIFOpenFile, GIFCloseFile, GIFReadFile, GIFSeekFile, GIFDraw ) ) { + log_n("Could not open gif %s", gifPath ); + } + + Serial.print("Playing: "); Serial.println(gifPath); + + int frameDelay = 0; // store delay for the last frame + int then = 0; // store overall delay + + while (gif.playFrame(true, &frameDelay)) { + + then += frameDelay; + if( then > maxGifDuration ) { // avoid being trapped in infinite GIF's + //log_w("Broke the GIF loop, max duration exceeded"); + break; + } + } + + gif.close(); + + return then; +} + + +void setup() +{ + Serial.begin(115200); + + // **************************** Setup SD Card access via SPI **************************** + if(!SD.begin(SS_PIN)){ + // bool begin(uint8_t ssPin=SS, SPIClass &spi=SPI, uint32_t frequency=4000000, const char * mountpoint="/sd", uint8_t max_files=5, bool format_if_empty=false); + Serial.println("Card Mount Failed"); + return; + } + uint8_t cardType = SD.cardType(); + + if(cardType == CARD_NONE){ + Serial.println("No SD card attached"); + return; + } + + Serial.print("SD Card Type: "); + if(cardType == CARD_MMC){ + Serial.println("MMC"); + } else if(cardType == CARD_SD){ + Serial.println("SDSC"); + } else if(cardType == CARD_SDHC){ + Serial.println("SDHC"); + } else { + Serial.println("UNKNOWN"); + } + + uint64_t cardSize = SD.cardSize() / (1024 * 1024); + Serial.printf("SD Card Size: %lluMB\n", cardSize); + + //listDir(SD, "/", 1, false); + + Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024)); + Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024)); + + + + // **************************** Setup DMA Matrix **************************** + HUB75_I2S_CFG mxconfig( + PANEL_RES_X, // module width + PANEL_RES_Y, // module height + PANEL_CHAIN // Chain length + ); + + // Need to remap these HUB75 DMA pins because the SPI SDCard is using them. + // Otherwise the SD Card will not work. + mxconfig.gpio.a = A_PIN; + mxconfig.gpio.b = B_PIN; + mxconfig.gpio.c = C_PIN; + // mxconfig.gpio.d = D_PIN; + + //mxconfig.clkphase = false; + //mxconfig.driver = HUB75_I2S_CFG::FM6126A; + + // Display Setup + dma_display = new MatrixPanel_I2S_DMA(mxconfig); + + // Allocate memory and start DMA display + if( not dma_display->begin() ) + Serial.println("****** !KABOOM! HUB75 memory allocation failed ***********"); + + dma_display->setBrightness8(128); //0-255 + dma_display->clearScreen(); + + + // **************************** Setup Sketch **************************** + Serial.println("Starting AnimatedGIFs Sketch"); + + // SD CARD STOPS WORKING WITH DMA DISPLAY ENABLED>... + + File root = SD.open("/gifs"); + if(!root){ + Serial.println("Failed to open directory"); + return; + } + + File file = root.openNextFile(); + while(file){ + if(!file.isDirectory()) + { + Serial.print(" FILE: "); + Serial.print(file.name()); + Serial.print(" SIZE: "); + Serial.println(file.size()); + + std::string filename = "/gifs/" + std::string(file.name()); + Serial.println(filename.c_str()); + + GifFiles.push_back( filename ); + // Serial.println("Adding to gif list:" + String(filename)); + totalFiles++; + + } + file = root.openNextFile(); + } + + file.close(); + Serial.printf("Found %d GIFs to play.", totalFiles); + //totalFiles = getGifInventory("/gifs"); + + + + // This is important - Set the right endianness. + gif.begin(LITTLE_ENDIAN_PIXELS); + +} + +void loop(){ + + // Iterate over a vector using range based for loop + for(auto & elem : GifFiles) + { + gifPlay( elem.c_str() ); + gif.reset(); + delay(500); + } + +} + +void draw_test_patterns() +{ + // fix the screen with green + dma_display->fillRect(0, 0, dma_display->width(), dma_display->height(), dma_display->color444(0, 15, 0)); + delay(500); + + // draw a box in yellow + dma_display->drawRect(0, 0, dma_display->width(), dma_display->height(), dma_display->color444(15, 15, 0)); + delay(500); + + // draw an 'X' in red + dma_display->drawLine(0, 0, dma_display->width()-1, dma_display->height()-1, dma_display->color444(15, 0, 0)); + dma_display->drawLine(dma_display->width()-1, 0, 0, dma_display->height()-1, dma_display->color444(15, 0, 0)); + delay(500); + + // draw a blue circle + dma_display->drawCircle(10, 10, 10, dma_display->color444(0, 0, 15)); + delay(500); + + // fill a violet circle + dma_display->fillCircle(40, 21, 10, dma_display->color444(15, 0, 15)); + delay(500); + delay(1000); + +} diff --git a/examples/AnimatedGIFPanel_SD/Readme.md b/examples/AnimatedGIFPanel_SD/Readme.md new file mode 100644 index 0000000..6f9f06a --- /dev/null +++ b/examples/AnimatedGIFPanel_SD/Readme.md @@ -0,0 +1,15 @@ +# ESP32-HUB75-MatrixPanel-DMA SDCard example + +A very basic example using the 'Animated GIF' library by Larry Bank + the SD / File system library provided for Arduino by Espressif. + +Some default HUB75 pins need to be remapped to accomodate for the SD Card. + +![image](esp32_sdcard.jpg) + +## How to use it? + +1. Format a SD Card with FAT32 file system (default setting) +2. Create a directory called 'gifs' +3. Drop your gifs in there. The resolution of the GIFS must match that of the display. + + diff --git a/examples/AnimatedGIFPanel_SD/esp32_sdcard.jpg b/examples/AnimatedGIFPanel_SD/esp32_sdcard.jpg new file mode 100644 index 0000000..4a4441d Binary files /dev/null and b/examples/AnimatedGIFPanel_SD/esp32_sdcard.jpg differ diff --git a/examples/AnimatedGIFPanel_SD/gif_functions.hpp b/examples/AnimatedGIFPanel_SD/gif_functions.hpp new file mode 100644 index 0000000..7a16a63 --- /dev/null +++ b/examples/AnimatedGIFPanel_SD/gif_functions.hpp @@ -0,0 +1,132 @@ + +// Code copied from AnimatedGIF examples + +#ifndef M5STACK_SD + // for custom ESP32 builds + #define M5STACK_SD SD +#endif + + +static void * GIFOpenFile(const char *fname, int32_t *pSize) +{ + //log_d("GIFOpenFile( %s )\n", fname ); + FSGifFile = M5STACK_SD.open(fname); + if (FSGifFile) { + *pSize = FSGifFile.size(); + return (void *)&FSGifFile; + } + return NULL; +} + + +static void GIFCloseFile(void *pHandle) +{ + File *f = static_cast(pHandle); + if (f != NULL) + f->close(); +} + + +static int32_t GIFReadFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen) +{ + int32_t iBytesRead; + iBytesRead = iLen; + File *f = static_cast(pFile->fHandle); + // Note: If you read a file all the way to the last byte, seek() stops working + if ((pFile->iSize - pFile->iPos) < iLen) + iBytesRead = pFile->iSize - pFile->iPos - 1; // <-- ugly work-around + if (iBytesRead <= 0) + return 0; + iBytesRead = (int32_t)f->read(pBuf, iBytesRead); + pFile->iPos = f->position(); + return iBytesRead; +} + + +static int32_t GIFSeekFile(GIFFILE *pFile, int32_t iPosition) +{ + int i = micros(); + File *f = static_cast(pFile->fHandle); + f->seek(iPosition); + pFile->iPos = (int32_t)f->position(); + i = micros() - i; + //log_d("Seek time = %d us\n", i); + return pFile->iPos; +} + + +// Draw a line of image directly on the LCD +void GIFDraw(GIFDRAW *pDraw) +{ + uint8_t *s; + uint16_t *d, *usPalette, usTemp[320]; + int x, y, iWidth; + + iWidth = pDraw->iWidth; + if (iWidth > PANEL_RES_X) + iWidth = PANEL_RES_X; + usPalette = pDraw->pPalette; + y = pDraw->iY + pDraw->y; // current line + + s = pDraw->pPixels; + if (pDraw->ucDisposalMethod == 2) {// restore to background color + for (x=0; xucTransparent) + s[x] = pDraw->ucBackground; + } + pDraw->ucHasTransparency = 0; + } + // Apply the new pixels to the main image + if (pDraw->ucHasTransparency) { // if transparency used + uint8_t *pEnd, c, ucTransparent = pDraw->ucTransparent; + int x, iCount; + pEnd = s + iWidth; + x = 0; + iCount = 0; // count non-transparent pixels + while(x < iWidth) { + c = ucTransparent-1; + d = usTemp; + while (c != ucTransparent && s < pEnd) { + c = *s++; + if (c == ucTransparent) { // done, stop + s--; // back up to treat it like transparent + } else { // opaque + *d++ = usPalette[c]; + iCount++; + } + } // while looking for opaque pixels + if (iCount) { // any opaque pixels? + for(int xOffset = 0; xOffset < iCount; xOffset++ ){ + dma_display->drawPixel(x + xOffset, y, usTemp[xOffset]); // 565 Color Format + } + x += iCount; + iCount = 0; + } + // no, look for a run of transparent pixels + c = ucTransparent; + while (c == ucTransparent && s < pEnd) { + c = *s++; + if (c == ucTransparent) + iCount++; + else + s--; + } + if (iCount) { + x += iCount; // skip these + iCount = 0; + } + } + } else { + s = pDraw->pPixels; + // Translate the 8-bit pixels through the RGB565 palette (already byte reversed) + for (x=0; xdrawPixel(x, y, usPalette[*s++]); // color 565 + /* + usTemp[x] = usPalette[*s++]; + + for (x=0; xiWidth; x++) { + dma_display->drawPixel(x, y, usTemp[*s++]); // color 565 + } */ + + } +} /* GIFDraw() */ diff --git a/examples/AnimatedGIFPanel/data/gifs/cartoon.gif b/examples/AnimatedGIFPanel_SD/gifs/cartoon.gif similarity index 100% rename from examples/AnimatedGIFPanel/data/gifs/cartoon.gif rename to examples/AnimatedGIFPanel_SD/gifs/cartoon.gif diff --git a/examples/AnimatedGIFPanel/data/gifs/ezgif.com-pacmn.gif b/examples/AnimatedGIFPanel_SD/gifs/ezgif.com-pacmn.gif similarity index 100% rename from examples/AnimatedGIFPanel/data/gifs/ezgif.com-pacmn.gif rename to examples/AnimatedGIFPanel_SD/gifs/ezgif.com-pacmn.gif diff --git a/examples/AnimatedGIFPanel/data/gifs/loading.io-64x32px.gif b/examples/AnimatedGIFPanel_SD/gifs/loading.io-64x32px.gif similarity index 100% rename from examples/AnimatedGIFPanel/data/gifs/loading.io-64x32px.gif rename to examples/AnimatedGIFPanel_SD/gifs/loading.io-64x32px.gif diff --git a/examples/AnimatedGIFPanel/data/gifs/matrix-spin.gif b/examples/AnimatedGIFPanel_SD/gifs/matrix-spin.gif similarity index 100% rename from examples/AnimatedGIFPanel/data/gifs/matrix-spin.gif rename to examples/AnimatedGIFPanel_SD/gifs/matrix-spin.gif diff --git a/examples/AnimatedGIFPanel/data/gifs/parasite1.gif b/examples/AnimatedGIFPanel_SD/gifs/parasite1.gif similarity index 100% rename from examples/AnimatedGIFPanel/data/gifs/parasite1.gif rename to examples/AnimatedGIFPanel_SD/gifs/parasite1.gif diff --git a/examples/AnimatedGIFPanel/data/gifs/parasite2.gif b/examples/AnimatedGIFPanel_SD/gifs/parasite2.gif similarity index 100% rename from examples/AnimatedGIFPanel/data/gifs/parasite2.gif rename to examples/AnimatedGIFPanel_SD/gifs/parasite2.gif diff --git a/examples/AnimatedGIFPanel/data/gifs/shock-gs.gif b/examples/AnimatedGIFPanel_SD/gifs/shock-gs.gif similarity index 100% rename from examples/AnimatedGIFPanel/data/gifs/shock-gs.gif rename to examples/AnimatedGIFPanel_SD/gifs/shock-gs.gif diff --git a/examples/AnimatedGIFPanel_SD/sdcard_functions.hpp b/examples/AnimatedGIFPanel_SD/sdcard_functions.hpp new file mode 100644 index 0000000..51ff5b1 --- /dev/null +++ b/examples/AnimatedGIFPanel_SD/sdcard_functions.hpp @@ -0,0 +1,102 @@ +/************************ SD Card Code ************************/ +// As per: https://github.com/espressif/arduino-esp32/tree/master/libraries/SD/examples/SD_Test + + + +void listDir(fs::FS &fs, const char * dirname, uint8_t levels, bool add_to_gif_list = false){ + Serial.printf("Listing directory: %s\n", dirname); + + File root = fs.open(dirname); + if(!root){ + Serial.println("Failed to open directory"); + return; + } + if(!root.isDirectory()){ + Serial.println("Not a directory"); + return; + } + + File file = root.openNextFile(); + while(file){ + if(file.isDirectory()){ + Serial.print(" DIR : "); + Serial.println(file.name()); + if(levels){ + listDir(fs, file.path(), levels -1, false); + } + } else { + Serial.print(" FILE: "); + Serial.print(file.name()); + Serial.print(" SIZE: "); + Serial.println(file.size()); + + if (add_to_gif_list && levels == 0) + { + GifFiles.push_back( std::string(dirname) + file.name() ); + Serial.println("Adding to gif list:" + String(dirname) +"/" + file.name()); + totalFiles++; + } + } + file = root.openNextFile(); + } + + file.close(); +} + +void readFile(fs::FS &fs, const char * path){ + Serial.printf("Reading file: %s\n", path); + + File file = fs.open(path); + if(!file){ + Serial.println("Failed to open file for reading"); + return; + } + + Serial.print("Read from file: "); + while(file.available()){ + Serial.write(file.read()); + } + file.close(); +} + +void testFileIO(fs::FS &fs, const char * path){ + File file = fs.open(path); + static uint8_t buf[512]; + size_t len = 0; + uint32_t start = millis(); + uint32_t end = start; + if(file){ + len = file.size(); + size_t flen = len; + start = millis(); + while(len){ + size_t toRead = len; + if(toRead > 512){ + toRead = 512; + } + file.read(buf, toRead); + len -= toRead; + } + end = millis() - start; + Serial.printf("%u bytes read for %u ms\n", flen, end); + file.close(); + } else { + Serial.println("Failed to open file for reading"); + } + + + file = fs.open(path, FILE_WRITE); + if(!file){ + Serial.println("Failed to open file for writing"); + return; + } + + size_t i; + start = millis(); + for(i=0; i<2048; i++){ + file.write(buf, 512); + } + end = millis() - start; + Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end); + file.close(); +} \ No newline at end of file diff --git a/examples/AnimatedGIFPanel_SPIFFS/AnimatedGIFPanel_SPIFFS.ino b/examples/AnimatedGIFPanel_SPIFFS/AnimatedGIFPanel_SPIFFS.ino new file mode 100644 index 0000000..9d6d182 --- /dev/null +++ b/examples/AnimatedGIFPanel_SPIFFS/AnimatedGIFPanel_SPIFFS.ino @@ -0,0 +1,290 @@ +// Example sketch which shows how to display a 64x32 animated GIF image stored in FLASH memory +// on a 64x32 LED matrix +// +// Credits: https://github.com/bitbank2/AnimatedGIF/tree/master/examples/ESP32_LEDMatrix_I2S +// + +/* INSTRUCTIONS + * + * 1. First Run the 'ESP32 Sketch Data Upload Tool' in Arduino from the 'Tools' Menu. + * - If you don't know what this is or see it as an option, then read this: + * https://github.com/me-no-dev/arduino-esp32fs-plugin + * - This tool will upload the contents of the data/ directory in the sketch folder onto + * the ESP32 itself. + * + * 2. You can drop any animated GIF you want in there, but keep it to the resolution of the + * MATRIX you're displaying to. To resize a gif, use this online website: https://ezgif.com/ + * + * 3. Have fun. + */ + +#define FILESYSTEM SPIFFS +#include +#include +#include + +// ---------------------------- + +/* + * Below is an is the 'legacy' way of initialising the MatrixPanel_I2S_DMA class. + * i.e. MATRIX_WIDTH and MATRIX_HEIGHT are modified by compile-time directives. + * By default the library assumes a single 64x32 pixel panel is connected. + * + * Refer to the example '2_PatternPlasma' on the new / correct way to setup this library + * for different resolutions / panel chain lengths within the sketch 'setup()'. + * + */ + +#define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module. +#define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module. +#define PANEL_CHAIN 1 // Total number of panels chained one to another + +//MatrixPanel_I2S_DMA dma_display; +MatrixPanel_I2S_DMA *dma_display = nullptr; + +uint16_t myBLACK = dma_display->color565(0, 0, 0); +uint16_t myWHITE = dma_display->color565(255, 255, 255); +uint16_t myRED = dma_display->color565(255, 0, 0); +uint16_t myGREEN = dma_display->color565(0, 255, 0); +uint16_t myBLUE = dma_display->color565(0, 0, 255); + + +AnimatedGIF gif; +File f; +int x_offset, y_offset; + + + +// Draw a line of image directly on the LED Matrix +void GIFDraw(GIFDRAW *pDraw) +{ + uint8_t *s; + uint16_t *d, *usPalette, usTemp[320]; + int x, y, iWidth; + + iWidth = pDraw->iWidth; + if (iWidth > MATRIX_WIDTH) + iWidth = MATRIX_WIDTH; + + usPalette = pDraw->pPalette; + y = pDraw->iY + pDraw->y; // current line + + s = pDraw->pPixels; + if (pDraw->ucDisposalMethod == 2) // restore to background color + { + for (x=0; xucTransparent) + s[x] = pDraw->ucBackground; + } + pDraw->ucHasTransparency = 0; + } + // Apply the new pixels to the main image + if (pDraw->ucHasTransparency) // if transparency used + { + uint8_t *pEnd, c, ucTransparent = pDraw->ucTransparent; + int x, iCount; + pEnd = s + pDraw->iWidth; + x = 0; + iCount = 0; // count non-transparent pixels + while(x < pDraw->iWidth) + { + c = ucTransparent-1; + d = usTemp; + while (c != ucTransparent && s < pEnd) + { + c = *s++; + if (c == ucTransparent) // done, stop + { + s--; // back up to treat it like transparent + } + else // opaque + { + *d++ = usPalette[c]; + iCount++; + } + } // while looking for opaque pixels + if (iCount) // any opaque pixels? + { + for(int xOffset = 0; xOffset < iCount; xOffset++ ){ + dma_display->drawPixel(x + xOffset, y, usTemp[xOffset]); // 565 Color Format + } + x += iCount; + iCount = 0; + } + // no, look for a run of transparent pixels + c = ucTransparent; + while (c == ucTransparent && s < pEnd) + { + c = *s++; + if (c == ucTransparent) + iCount++; + else + s--; + } + if (iCount) + { + x += iCount; // skip these + iCount = 0; + } + } + } + else // does not have transparency + { + s = pDraw->pPixels; + // Translate the 8-bit pixels through the RGB565 palette (already byte reversed) + for (x=0; xiWidth; x++) + { + dma_display->drawPixel(x, y, usPalette[*s++]); // color 565 + } + } +} /* GIFDraw() */ + + +void * GIFOpenFile(const char *fname, int32_t *pSize) +{ + Serial.print("Playing gif: "); + Serial.println(fname); + f = FILESYSTEM.open(fname); + if (f) + { + *pSize = f.size(); + return (void *)&f; + } + return NULL; +} /* GIFOpenFile() */ + +void GIFCloseFile(void *pHandle) +{ + File *f = static_cast(pHandle); + if (f != NULL) + f->close(); +} /* GIFCloseFile() */ + +int32_t GIFReadFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen) +{ + int32_t iBytesRead; + iBytesRead = iLen; + File *f = static_cast(pFile->fHandle); + // Note: If you read a file all the way to the last byte, seek() stops working + if ((pFile->iSize - pFile->iPos) < iLen) + iBytesRead = pFile->iSize - pFile->iPos - 1; // <-- ugly work-around + if (iBytesRead <= 0) + return 0; + iBytesRead = (int32_t)f->read(pBuf, iBytesRead); + pFile->iPos = f->position(); + return iBytesRead; +} /* GIFReadFile() */ + +int32_t GIFSeekFile(GIFFILE *pFile, int32_t iPosition) +{ + int i = micros(); + File *f = static_cast(pFile->fHandle); + f->seek(iPosition); + pFile->iPos = (int32_t)f->position(); + i = micros() - i; +// Serial.printf("Seek time = %d us\n", i); + return pFile->iPos; +} /* GIFSeekFile() */ + +unsigned long start_tick = 0; + +void ShowGIF(char *name) +{ + start_tick = millis(); + + if (gif.open(name, GIFOpenFile, GIFCloseFile, GIFReadFile, GIFSeekFile, GIFDraw)) + { + x_offset = (MATRIX_WIDTH - gif.getCanvasWidth())/2; + if (x_offset < 0) x_offset = 0; + y_offset = (MATRIX_HEIGHT - gif.getCanvasHeight())/2; + if (y_offset < 0) y_offset = 0; + Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n", gif.getCanvasWidth(), gif.getCanvasHeight()); + Serial.flush(); + while (gif.playFrame(true, NULL)) + { + if ( (millis() - start_tick) > 8000) { // we'll get bored after about 8 seconds of the same looping gif + break; + } + } + gif.close(); + } + +} /* ShowGIF() */ + + + +/************************* Arduino Sketch Setup and Loop() *******************************/ +void setup() { + Serial.begin(115200); + delay(1000); + + HUB75_I2S_CFG mxconfig( + PANEL_RES_X, // module width + PANEL_RES_Y, // module height + PANEL_CHAIN // Chain length + ); + + // mxconfig.gpio.e = 18; + // mxconfig.clkphase = false; + //mxconfig.driver = HUB75_I2S_CFG::FM6126A; + + // Display Setup + dma_display = new MatrixPanel_I2S_DMA(mxconfig); + dma_display->begin(); + dma_display->setBrightness8(128); //0-255 + dma_display->clearScreen(); + dma_display->fillScreen(myWHITE); + + Serial.println("Starting AnimatedGIFs Sketch"); + + // Start filesystem + Serial.println(" * Loading SPIFFS"); + if(!SPIFFS.begin()){ + Serial.println("SPIFFS Mount Failed"); + } + + dma_display->begin(); + + /* all other pixel drawing functions can only be called after .begin() */ + dma_display->fillScreen(dma_display->color565(0, 0, 0)); + gif.begin(LITTLE_ENDIAN_PIXELS); + +} + +String gifDir = "/gifs"; // play all GIFs in this directory on the SD card +char filePath[256] = { 0 }; +File root, gifFile; + +void loop() +{ + while (1) // run forever + { + + root = FILESYSTEM.open(gifDir); + if (root) + { + gifFile = root.openNextFile(); + while (gifFile) + { + if (!gifFile.isDirectory()) // play it + { + + // C-strings... urghh... + memset(filePath, 0x0, sizeof(filePath)); + strcpy(filePath, gifFile.path()); + + // Show it. + ShowGIF(filePath); + + } + gifFile.close(); + gifFile = root.openNextFile(); + } + root.close(); + } // root + + delay(1000); // pause before restarting + + } // while +} \ No newline at end of file diff --git a/examples/AnimatedGIFPanel/README.md b/examples/AnimatedGIFPanel_SPIFFS/README.md similarity index 100% rename from examples/AnimatedGIFPanel/README.md rename to examples/AnimatedGIFPanel_SPIFFS/README.md diff --git a/examples/AnimatedGIFPanel_SPIFFS/data/gifs/cartoon.gif b/examples/AnimatedGIFPanel_SPIFFS/data/gifs/cartoon.gif new file mode 100644 index 0000000..32a0e25 Binary files /dev/null and b/examples/AnimatedGIFPanel_SPIFFS/data/gifs/cartoon.gif differ diff --git a/examples/AnimatedGIFPanel_SPIFFS/data/gifs/ezgif.com-pacmn.gif b/examples/AnimatedGIFPanel_SPIFFS/data/gifs/ezgif.com-pacmn.gif new file mode 100644 index 0000000..0a219a4 Binary files /dev/null and b/examples/AnimatedGIFPanel_SPIFFS/data/gifs/ezgif.com-pacmn.gif differ diff --git a/examples/AnimatedGIFPanel_SPIFFS/data/gifs/loading.io-64x32px.gif b/examples/AnimatedGIFPanel_SPIFFS/data/gifs/loading.io-64x32px.gif new file mode 100644 index 0000000..342f8ae Binary files /dev/null and b/examples/AnimatedGIFPanel_SPIFFS/data/gifs/loading.io-64x32px.gif differ diff --git a/examples/AnimatedGIFPanel_SPIFFS/data/gifs/matrix-spin.gif b/examples/AnimatedGIFPanel_SPIFFS/data/gifs/matrix-spin.gif new file mode 100644 index 0000000..7925d68 Binary files /dev/null and b/examples/AnimatedGIFPanel_SPIFFS/data/gifs/matrix-spin.gif differ diff --git a/examples/AnimatedGIFPanel_SPIFFS/data/gifs/parasite1.gif b/examples/AnimatedGIFPanel_SPIFFS/data/gifs/parasite1.gif new file mode 100644 index 0000000..8b8b67a Binary files /dev/null and b/examples/AnimatedGIFPanel_SPIFFS/data/gifs/parasite1.gif differ diff --git a/examples/AnimatedGIFPanel_SPIFFS/data/gifs/parasite2.gif b/examples/AnimatedGIFPanel_SPIFFS/data/gifs/parasite2.gif new file mode 100644 index 0000000..60d03c7 Binary files /dev/null and b/examples/AnimatedGIFPanel_SPIFFS/data/gifs/parasite2.gif differ diff --git a/examples/AnimatedGIFPanel_SPIFFS/data/gifs/shock-gs.gif b/examples/AnimatedGIFPanel_SPIFFS/data/gifs/shock-gs.gif new file mode 100644 index 0000000..1d023d9 Binary files /dev/null and b/examples/AnimatedGIFPanel_SPIFFS/data/gifs/shock-gs.gif differ diff --git a/examples/ChainedPanels/ChainedPanels.ino b/examples/ChainedPanels/ChainedPanels.ino index 33379c6..1343d9b 100644 --- a/examples/ChainedPanels/ChainedPanels.ino +++ b/examples/ChainedPanels/ChainedPanels.ino @@ -1,158 +1,67 @@ /****************************************************************************** - ----------- - Steps to use - ----------- + ------------------------------------------------------------------------- + Steps to create a virtual display made up of a chain of panels in a grid + ------------------------------------------------------------------------- - 1) In the sketch (i.e. this example): + Read the documentation! + https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/tree/master/examples/ChainedPanels + + tl/dr: - - Set values for NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, PANEL_CHAIN. - There are comments beside them explaining what they are in more detail. + - Set values for NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, PANEL_CHAIN_TYPE. + - Other than where the matrix is defined and matrix.begin in the setup, you should now be using the virtual display for everything (drawing pixels, writing text etc). You can do a find and replace of all calls if it's an existing sketch (just make sure you don't replace the definition and the matrix.begin) + - If the sketch makes use of MATRIX_HEIGHT or MATRIX_WIDTH, these will need to be replaced with the width and height of your virtual screen. Either make new defines and use that, or you can use virtualDisp.width() or .height() - Thanks to: - - * Brian Lough for the original example as raised in this issue: - https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/26 - - YouTube: https://www.youtube.com/brianlough - Tindie: https://www.tindie.com/stores/brianlough/ - Twitter: https://twitter.com/witnessmenow - - * Galaxy-Man for the kind donation of panels make/test that this is possible: - https://github.com/Galaxy-Man - *****************************************************************************/ +// 1) Include key virtual display library + #include - - /****************************************************************************** - * VIRTUAL DISPLAY / MATRIX PANEL CHAINING CONFIGURATION - * - * Note 1: If chaining from the top right to the left, and then S curving down - * then serpentine_chain = true and top_down_chain = true - * (these being the last two parameters of the virtualDisp(...) constructor. - * - * Note 2: If chaining starts from the bottom up, then top_down_chain = false. - * - * Note 3: By default, this library has serpentine_chain = true, that is, every - * second row has the panels 'upside down' (rotated 180), so the output - * pin of the row above is right above the input connector of the next - * row. - - Example 1 panel chaining: - +-----------------+-----------------+-------------------+ - | 64x32px PANEL 3 | 64x32px PANEL 2 | 64x32px PANEL 1 | - | ------------ <-------- | ------------xx | - | [OUT] | [IN] | [OUT] [IN] | [OUT] [ESP IN] | - +--------|--------+-----------------+-------------------+ - | 64x32px|PANEL 4 | 64x32px PANEL 5 | 64x32px PANEL 6 | - | \|/ ----------> | -----> | - | [IN] [OUT] | [IN] [OUT] | [IN] [OUT] | - +-----------------+-----------------+-------------------+ - - Example 1 configuration: - - #define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module. - #define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module. - - #define NUM_ROWS 2 // Number of rows of chained INDIVIDUAL PANELS - #define NUM_COLS 3 // Number of INDIVIDUAL PANELS per ROW - - virtualDisp(dma_display, NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, true, true); - - = 192x64 px virtual display, with the top left of panel 3 being pixel co-ord (0,0) - - ========================================================== - - Example 2 panel chaining: - - +-------------------+ - | 64x32px PANEL 1 | - | ----------------- | - | [OUT] [ESP IN] | - +-------------------+ - | 64x32px PANEL 2 | - | | - | [IN] [OUT] | - +-------------------+ - | 64x32px PANEL 3 | - | | - | [OUT] [IN] | - +-------------------+ - | 64x32px PANEL 4 | - | | - | [IN] [OUT] | - +-------------------+ - - Example 2 configuration: - - #define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module. - #define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module. - - #define NUM_ROWS 4 // Number of rows of chained INDIVIDUAL PANELS - #define NUM_COLS 1 // Number of INDIVIDUAL PANELS per ROW - - virtualDisp(dma_display, NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, true, true); - - virtualDisp(dma_display, NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, true, true); - - = 128x64 px virtual display, with the top left of panel 1 being pixel co-ord (0,0) - - ========================================================== - - Example 3 panel chaining (bottom up): - - +-----------------+-----------------+ - | 64x32px PANEL 4 | 64x32px PANEL 3 | - | <---------- | - | [OUT] [IN] | [OUT] [in] | - +-----------------+-----------------+ - | 64x32px PANEL 1 | 64x32px PANEL 2 | - | ----------> | - | [ESP IN] [OUT] | [IN] [OUT] | - +-----------------+-----------------+ - - Example 1 configuration: - +// 2) Set configuration #define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module. #define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module. #define NUM_ROWS 2 // Number of rows of chained INDIVIDUAL PANELS #define NUM_COLS 2 // Number of INDIVIDUAL PANELS per ROW + #define PANEL_CHAIN NUM_ROWS*NUM_COLS // total number of panels chained one to another - virtualDisp(dma_display, NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, true, false); + /* Configure the serpetine chaining approach. Options are: + CHAIN_TOP_LEFT_DOWN + CHAIN_TOP_RIGHT_DOWN + CHAIN_BOTTOM_LEFT_UP + CHAIN_BOTTOM_RIGHT_UP - = 128x64 px virtual display, with the top left of panel 4 being pixel co-ord (0,0) + The location (i.e. 'TOP_LEFT', 'BOTTOM_RIGHT') etc. refers to the starting point where + the ESP32 is located, and how the chain of panels will 'roll out' from there. -*/ + In this example we're using 'CHAIN_BOTTOM_LEFT_UP' which would look like this in the real world: + Chain of 4 x 64x32 panels with the ESP at the BOTTOM_LEFT: + +---------+---------+ + | 4 | 3 | + | | | + +---------+---------+ + | 1 | 2 | + | (ESP) | | + +---------+---------+ + */ + #define VIRTUAL_MATRIX_CHAIN_TYPE CHAIN_BOTTOM_LEFT_UP -#define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module. -#define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module. +// 3) Create the runtime objects to use -#define NUM_ROWS 2 // Number of rows of chained INDIVIDUAL PANELS -#define NUM_COLS 2 // Number of INDIVIDUAL PANELS per ROW -#define PANEL_CHAIN NUM_ROWS*NUM_COLS // total number of panels chained one to another + // placeholder for the matrix object + MatrixPanel_I2S_DMA *dma_display = nullptr; -// Change this to your needs, for details on VirtualPanel pls read the PDF! -#define SERPENT true -#define TOPDOWN false - -// library includes -#include - -// placeholder for the matrix object -MatrixPanel_I2S_DMA *dma_display = nullptr; - -// placeholder for the virtual display object -VirtualMatrixPanel *virtualDisp = nullptr; + // placeholder for the virtual display object + VirtualMatrixPanel *virtualDisp = nullptr; /****************************************************************************** @@ -203,37 +112,58 @@ void setup() { Serial.println("****** !KABOOM! I2S memory allocation failed ***********"); // create VirtualDisplay object based on our newly created dma_display object - virtualDisp = new VirtualMatrixPanel((*dma_display), NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, SERPENT, TOPDOWN); + virtualDisp = new VirtualMatrixPanel((*dma_display), NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, VIRTUAL_MATRIX_CHAIN_TYPE); // So far so good, so continue virtualDisp->fillScreen(virtualDisp->color444(0, 0, 0)); virtualDisp->drawDisplayTest(); // draw text numbering on each screen to check connectivity - delay(3000); + // delay(1000); - Serial.println("Chain of 64x32 panels for this example:"); - Serial.println("+--------+---------+"); - Serial.println("| 4 | 3 |"); - Serial.println("| | |"); - Serial.println("+--------+---------+"); - Serial.println("| 1 | 2 |"); - Serial.println("| (ESP) | |"); - Serial.println("+--------+---------+"); + Serial.println("Chain of 4x 64x32 panels for this example:"); + Serial.println("+---------+---------+"); + Serial.println("| 4 | 3 |"); + Serial.println("| | |"); + Serial.println("+---------+---------+"); + Serial.println("| 1 | 2 |"); + Serial.println("| (ESP32) | |"); + Serial.println("+---------+---------+"); + // draw blue text virtualDisp->setFont(&FreeSansBold12pt7b); virtualDisp->setTextColor(virtualDisp->color565(0, 0, 255)); - virtualDisp->setTextSize(2); - virtualDisp->setCursor(10, virtualDisp->height()-20); - + virtualDisp->setTextSize(3); + virtualDisp->setCursor(0, virtualDisp->height()- ((virtualDisp->height()-45)/2)); + virtualDisp->print("ABCD"); + // Red text inside red rect (2 pix in from edge) - virtualDisp->print("1234"); virtualDisp->drawRect(1,1, virtualDisp->width()-2, virtualDisp->height()-2, virtualDisp->color565(255,0,0)); // White line from top left to bottom right virtualDisp->drawLine(0,0, virtualDisp->width()-1, virtualDisp->height()-1, virtualDisp->color565(255,255,255)); + + virtualDisp->drawDisplayTest(); // re draw text numbering on each screen to check connectivity + } void loop() { } // end loop + + +/***************************************************************************** + + Thanks to: + + * Brian Lough for the original example as raised in this issue: + https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/26 + + YouTube: https://www.youtube.com/brianlough + Tindie: https://www.tindie.com/stores/brianlough/ + Twitter: https://twitter.com/witnessmenow + + * Galaxy-Man for the kind donation of panels make/test that this is possible: + https://github.com/Galaxy-Man + +*****************************************************************************/ \ No newline at end of file diff --git a/examples/ChainedPanels/README.md b/examples/ChainedPanels/README.md index ca55860..c5aca88 100644 --- a/examples/ChainedPanels/README.md +++ b/examples/ChainedPanels/README.md @@ -2,6 +2,8 @@ This is the PatternPlasma Demo adopted for use with multiple LED Matrix Panel displays arranged in a non standard order (i.e. a grid) to make a bigger display. +![334894846_975082690567510_1362796919784291270_n](https://user-images.githubusercontent.com/89576620/224304944-94fe3483-d3cc-4aba-be0a-40b33ff901dc.jpg) + ### What do we mean by 'non standard order'? ### When you link / chain multiple panels together, the ESP32-HUB75-MatrixPanel-I2S-DMA library treats as one wide horizontal panel. This would be a 'standard' (default) order. @@ -10,12 +12,12 @@ Non-standard order is essentially the creation of a non-horizontal-only display For example: You bought four (4) 64x32px panels, and wanted to use them to create a 128x64pixel display. You would use the VirtualMatrixPanel class. -[Refer to this document](VirtualMatrixPanel.pdf) for an explanation and refer to this example on how to use. +[Refer to this document](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/blob/master/doc/VirtualMatrixPanel.pdf) for an explanation and refer to this example on how to use. ### Steps to Use ### -1. [Refer to this document](VirtualMatrixPanel.pdf) for an explanation and refer to this example on how to use. +1. [Refer to this document](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/blob/master/doc/VirtualMatrixPanel.pdf) for an explanation and refer to this example on how to use. 2. In your Arduino sketch, configure these defines accordingly: ``` @@ -24,7 +26,15 @@ For example: You bought four (4) 64x32px panels, and wanted to use them to creat #define NUM_ROWS 2 // Number of rows of chained INDIVIDUAL PANELS #define NUM_COLS 2 // Number of INDIVIDUAL PANELS per ROW + +#define PANEL_CHAIN NUM_ROWS*NUM_COLS // total number of panels chained one to another + +#define VIRTUAL_MATRIX_CHAIN_TYPE + ``` +VIRTUAL_MATRIX_CHAIN_TYPE's: +![image](https://user-images.githubusercontent.com/12006953/224537356-e3c8e87b-0bc0-4185-8f5d-d2d3b328d176.png) + 3. In your Arduino sketch, use the 'VirtualMatrixPanel' class instance (virtualDisp) to draw to the display (i.e. drawPixel), instead of the underling MatrixPanel_I2S_DMA class instance (dma_display). diff --git a/examples/ChainedPanels/VirtualMatrixPanel.pdf b/examples/ChainedPanels/VirtualMatrixPanel.pdf deleted file mode 100644 index db63112..0000000 Binary files a/examples/ChainedPanels/VirtualMatrixPanel.pdf and /dev/null differ diff --git a/examples/ChainedPanelsAuroraDemo/ChainedPanelsAuroraDemo.ino b/examples/ChainedPanelsAuroraDemo/ChainedPanelsAuroraDemo.ino index db6d4c9..f1cd7de 100644 --- a/examples/ChainedPanelsAuroraDemo/ChainedPanelsAuroraDemo.ino +++ b/examples/ChainedPanelsAuroraDemo/ChainedPanelsAuroraDemo.ino @@ -117,7 +117,7 @@ void setup() matrix->setBrightness8(96); // range is 0-255, 0 - 0%, 255 - 100% // create VirtualDisplay object based on our newly created dma_display object - virtualDisp = new VirtualMatrixPanel((*matrix), NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, SERPENT, TOPDOWN); + virtualDisp = new VirtualMatrixPanel((*matrix), NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, CHAIN_TOP_LEFT_DOWN); Serial.println("**************** Starting Aurora Effects Demo ****************"); diff --git a/examples/Four_Scan_Panel/Four_Scan_Panel.ino b/examples/Four_Scan_Panel/Four_Scan_Panel.ino index 856d7e5..5086b62 100644 --- a/examples/Four_Scan_Panel/Four_Scan_Panel.ino +++ b/examples/Four_Scan_Panel/Four_Scan_Panel.ino @@ -91,7 +91,7 @@ delay(500); // create FourScanPanellay object based on our newly created dma_display object - FourScanPanel = new VirtualMatrixPanel((*dma_display), NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, SERPENT, TOPDOWN); + FourScanPanel = new VirtualMatrixPanel((*dma_display), NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y); // THE IMPORTANT BIT BELOW! FourScanPanel->setPhysicalPanelScanRate(FOUR_SCAN_32PX_HIGH); diff --git a/examples/4_HueValueSpectrumDemo/4_HueValueSpectrumDemo.ino b/examples/HueValueSpectrum/HueValueSpectrum.ino similarity index 83% rename from examples/4_HueValueSpectrumDemo/4_HueValueSpectrumDemo.ino rename to examples/HueValueSpectrum/HueValueSpectrum.ino index e64d419..897275e 100644 --- a/examples/4_HueValueSpectrumDemo/4_HueValueSpectrumDemo.ino +++ b/examples/HueValueSpectrum/HueValueSpectrum.ino @@ -7,6 +7,10 @@ MatrixPanel_I2S_DMA *dma_display = nullptr; void setup() { + + Serial.begin(112500); + + HUB75_I2S_CFG::i2s_pins _pins={ 25, //R1_PIN, 26, //G1_PIN, @@ -26,8 +30,8 @@ void setup() { HUB75_I2S_CFG mxconfig( PANEL_RES_X, // Module width PANEL_RES_Y, // Module height - PANEL_CHAIN, // chain length - _pins // pin mapping + PANEL_CHAIN //, // chain length + //_pins // pin mapping -- uncomment if providing own custom pin mapping as per above. ); //mxconfig.clkphase = false; //mxconfig.driver = HUB75_I2S_CFG::FM6126A; @@ -40,12 +44,13 @@ void setup() { void loop() { // Canvas loop - float t = (float)(millis()%4000)/4000.f; - float tt = (float)((millis()%16000)/16000.f; + float t = (float) ((millis()%4000)/4000.f); + float tt = (float) ((millis()%16000)/16000.f); - for(int x = 0; x < PANEL_RES_X*PANEL_CHAIN; x++){ + for(int x = 0; x < (PANEL_RES_X*PANEL_CHAIN); x++) + { // calculate the overal shade - float f = ((sin(tt-(float)x/PANEL_RES_Y/32.)*2.f*PI)+1)/2)*255; + float f = (((sin(tt-(float)x/PANEL_RES_Y/32.)*2.f*PI)+1)/2)*255; // calculate hue spectrum into rgb float r = max(min(cosf(2.f*PI*(t+((float)x/PANEL_RES_Y+0.f)/3.f))+0.5f,1.f),0.f); float g = max(min(cosf(2.f*PI*(t+((float)x/PANEL_RES_Y+1.f)/3.f))+0.5f,1.f),0.f); diff --git a/examples/PIO_TestPatterns/src/main.cpp b/examples/PIO_TestPatterns/src/main.cpp index 40d077e..4e0edb1 100644 --- a/examples/PIO_TestPatterns/src/main.cpp +++ b/examples/PIO_TestPatterns/src/main.cpp @@ -116,7 +116,7 @@ void setup(){ chain->begin(); chain->setBrightness8(255); // create VirtualDisplay object based on our newly created dma_display object - matrix = new VirtualMatrixPanel((*chain), NUM_ROWS, NUM_COLS, PANEL_WIDTH, PANEL_HEIGHT, SERPENT, TOPDOWN); + matrix = new VirtualMatrixPanel((*chain), NUM_ROWS, NUM_COLS, PANEL_WIDTH, PANEL_HEIGHT, CHAIN_TOP_LEFT_DOWN); #endif ledbuff = (CRGB *)malloc(NUM_LEDS * sizeof(CRGB)); // allocate buffer for some tests diff --git a/examples/esp-idf/.gitignore b/examples/esp-idf/.gitignore new file mode 100644 index 0000000..3abcf2c --- /dev/null +++ b/examples/esp-idf/.gitignore @@ -0,0 +1,12 @@ +# ESP-IDF default build directory +build + +# Temporary files +*.swp + +# lock files for examples and components +dependencies.lock + +sdkconfig* +# Unignore sdkconfig.defaults +!sdkconfig.defaults diff --git a/examples/esp-idf/with-gfx/CMakeLists.txt b/examples/esp-idf/with-gfx/CMakeLists.txt new file mode 100644 index 0000000..192eccb --- /dev/null +++ b/examples/esp-idf/with-gfx/CMakeLists.txt @@ -0,0 +1,10 @@ +# This is a boilerplate top-level project CMakeLists.txt file. +# This is the primary file which CMake uses to learn how to build the project. +# +# Most of the important stuff happens in the 'main' directory. +# +# See https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system.html#example-project for more details. +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(with-gfx) diff --git a/examples/esp-idf/with-gfx/README.md b/examples/esp-idf/with-gfx/README.md new file mode 100644 index 0000000..07565f9 --- /dev/null +++ b/examples/esp-idf/with-gfx/README.md @@ -0,0 +1,17 @@ +# ESP-IDF Example With Adafruit GFX Library + +This folder contains example code for using this library with `esp-idf` and the [Adafruit GFX library](https://github.com/adafruit/Adafruit-GFX-Library). + +First, follow the [Getting Started Guide for ESP-IDF](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html) to install ESP-IDF onto your computer. + +When you are ready to start your first project with this library, follow folow these steps: + + 1. Copy the files in this folder (and sub folders) into a new directory for your project. + 1. Clone the required repositories: + ``` + git clone https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git components/ESP32-HUB75-MatrixPanel-I2S-DMA + git clone https://github.com/adafruit/Adafruit-GFX-Library.git components/Adafruit-GFX-Library + git clone https://github.com/adafruit/Adafruit_BusIO.git components/Adafruit_BusIO + git clone https://github.com/espressif/arduino-esp32.git components/arduino + ``` + 1. Build your project: `idf.py build` diff --git a/examples/esp-idf/with-gfx/components/.gitignore b/examples/esp-idf/with-gfx/components/.gitignore new file mode 100644 index 0000000..5e7d273 --- /dev/null +++ b/examples/esp-idf/with-gfx/components/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore diff --git a/examples/esp-idf/with-gfx/main/CMakeLists.txt b/examples/esp-idf/with-gfx/main/CMakeLists.txt new file mode 100644 index 0000000..16901f0 --- /dev/null +++ b/examples/esp-idf/with-gfx/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRC_DIRS "." ${SRCDIRS} + INCLUDE_DIRS ${INCLUDEDIRS} + REQUIRES ESP32-HUB75-MatrixPanel-I2S-DMA + ) diff --git a/examples/esp-idf/with-gfx/main/main.cpp b/examples/esp-idf/with-gfx/main/main.cpp new file mode 100644 index 0000000..7672dc5 --- /dev/null +++ b/examples/esp-idf/with-gfx/main/main.cpp @@ -0,0 +1,14 @@ +#include "ESP32-HUB75-MatrixPanel-I2S-DMA.h" + +MatrixPanel_I2S_DMA *dma_display = nullptr; + +extern "C" void app_main() { + HUB75_I2S_CFG mxconfig(/* width = */ 64, /* height = */ 64, /* chain = */ 1); + + dma_display = new MatrixPanel_I2S_DMA(mxconfig); + dma_display->begin(); + dma_display->setBrightness8(80); + dma_display->clearScreen(); + // `println` is only available when the Adafruit GFX library is used. + dma_display->println("Test message"); +} diff --git a/examples/esp-idf/with-gfx/sdkconfig.defaults b/examples/esp-idf/with-gfx/sdkconfig.defaults new file mode 100644 index 0000000..879d223 --- /dev/null +++ b/examples/esp-idf/with-gfx/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_FREERTOS_HZ=1000 diff --git a/examples/esp-idf/without-gfx/CMakeLists.txt b/examples/esp-idf/without-gfx/CMakeLists.txt new file mode 100644 index 0000000..9fbb7f4 --- /dev/null +++ b/examples/esp-idf/without-gfx/CMakeLists.txt @@ -0,0 +1,10 @@ +# This is a boilerplate top-level project CMakeLists.txt file. +# This is the primary file which CMake uses to learn how to build the project. +# +# Most of the important stuff happens in the 'main' directory. +# +# See https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system.html#example-project for more details. +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(without-gfx) diff --git a/examples/esp-idf/without-gfx/README.md b/examples/esp-idf/without-gfx/README.md new file mode 100644 index 0000000..f428b23 --- /dev/null +++ b/examples/esp-idf/without-gfx/README.md @@ -0,0 +1,14 @@ +# ESP-IDF Example Without Adafruit GFX Library + +This folder contains example code for using this library with `esp-idf`. + +First, follow the [Getting Started Guide for ESP-IDF](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html) to install ESP-IDF onto your computer. + +When you are ready to start your first project with this library, follow folow these steps: + + 1. Copy the files in this folder (and sub folders) into a new directory for your project. + 1. Clone the required repositories: + ``` + git clone https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git components/ESP32-HUB75-MatrixPanel-I2S-DMA + ``` + 1. Build your project: `idf.py build` diff --git a/examples/esp-idf/without-gfx/components/.gitignore b/examples/esp-idf/without-gfx/components/.gitignore new file mode 100644 index 0000000..5e7d273 --- /dev/null +++ b/examples/esp-idf/without-gfx/components/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore diff --git a/examples/esp-idf/without-gfx/main/CMakeLists.txt b/examples/esp-idf/without-gfx/main/CMakeLists.txt new file mode 100644 index 0000000..16901f0 --- /dev/null +++ b/examples/esp-idf/without-gfx/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRC_DIRS "." ${SRCDIRS} + INCLUDE_DIRS ${INCLUDEDIRS} + REQUIRES ESP32-HUB75-MatrixPanel-I2S-DMA + ) diff --git a/examples/esp-idf/without-gfx/main/main.cpp b/examples/esp-idf/without-gfx/main/main.cpp new file mode 100644 index 0000000..c44b49c --- /dev/null +++ b/examples/esp-idf/without-gfx/main/main.cpp @@ -0,0 +1,12 @@ +#include "ESP32-HUB75-MatrixPanel-I2S-DMA.h" + +MatrixPanel_I2S_DMA *dma_display = nullptr; + +extern "C" void app_main() { + HUB75_I2S_CFG mxconfig(/* width = */ 64, /* height = */ 64, /* chain = */ 1); + + dma_display = new MatrixPanel_I2S_DMA(mxconfig); + dma_display->begin(); + dma_display->setBrightness8(80); + dma_display->clearScreen(); +} diff --git a/examples/esp-idf/without-gfx/sdkconfig.defaults b/examples/esp-idf/without-gfx/sdkconfig.defaults new file mode 100644 index 0000000..57aec4a --- /dev/null +++ b/examples/esp-idf/without-gfx/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_ESP32_HUB75_USE_GFX=n diff --git a/library.json b/library.json index a9232b3..2a665fd 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "ESP32 HUB75 LED MATRIX PANEL DMA Display", - "version": "3.0.5", + "version": "3.0.9", "description": "An Adafruit GFX compatible library for LED matrix modules which uses DMA for ultra-fast refresh rates and therefore very low CPU usage.", "keywords": "hub75, esp32, esp32s2, esp32s3, display, dma, rgb matrix", "repository": { diff --git a/library.properties b/library.properties index 0dc2256..da50937 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=ESP32 HUB75 LED MATRIX PANEL DMA Display -version= 3.0.5 +version= 3.0.9 author=Faptastic maintainer=Faptastic sentence=HUB75 LED Matrix Library for ESP32, ESP32-S2 and ESP32-S3 diff --git a/src/ESP32-HUB75-MatrixPanel-I2S-DMA.cpp b/src/ESP32-HUB75-MatrixPanel-I2S-DMA.cpp index cf1915c..85fb055 100644 --- a/src/ESP32-HUB75-MatrixPanel-I2S-DMA.cpp +++ b/src/ESP32-HUB75-MatrixPanel-I2S-DMA.cpp @@ -1,284 +1,309 @@ #include "ESP32-HUB75-MatrixPanel-I2S-DMA.h" #if defined(SPIRAM_DMA_BUFFER) - // Sprite_TM saves the day again... - // https://www.esp32.com/viewtopic.php?f=2&t=30584 - #include "rom/cache.h" -#endif +// Sprite_TM saves the day again... +// https://www.esp32.com/viewtopic.php?f=2&t=30584 +#include "rom/cache.h" +#endif -/* This replicates same function in rowBitStruct, but due to induced inlining it might be MUCH faster - * when used in tight loops while method from struct could be flushed out of instruction cache between +/* This replicates same function in rowBitStruct, but due to induced inlining it might be MUCH faster + * when used in tight loops while method from struct could be flushed out of instruction cache between * loop cycles do NOT forget about buff_id param if using this. */ -#define getRowDataPtr(row, _dpth, buff_id) &(dma_buff.rowBits[row]->data[_dpth * dma_buff.rowBits[row]->width + buff_id*(dma_buff.rowBits[row]->width * dma_buff.rowBits[row]->colour_depth)]) +// #define getRowDataPtr(row, _dpth, buff_id) &(dma_buff.rowBits[row]->data[_dpth * dma_buff.rowBits[row]->width + buff_id*(dma_buff.rowBits[row]->width * dma_buff.rowBits[row]->colour_depth)]) + +// BufferID is now ignored, seperate global pointer pointer! +#define getRowDataPtr(row, _dpth, buff_id) &(fb->rowBits[row]->data[_dpth * fb->rowBits[row]->width]) /* We need to update the correct uint16_t in the rowBitStruct array, that gets sent out in parallel * 16 bit parallel mode - Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering * Irrelevant for ESP32-S2 the way the FIFO ordering works is different - refer to page 679 of S2 technical reference manual */ -#if defined (ESP32_THE_ORIG) - #define ESP32_TX_FIFO_POSITION_ADJUST(x_coord) (((x_coord) & 1U) ? (x_coord-1):(x_coord+1)) -#else - #define ESP32_TX_FIFO_POSITION_ADJUST(x_coord) x_coord -#endif +#if defined(ESP32_THE_ORIG) +#define ESP32_TX_FIFO_POSITION_ADJUST(x_coord) (((x_coord)&1U) ? (x_coord - 1) : (x_coord + 1)) +#else +#define ESP32_TX_FIFO_POSITION_ADJUST(x_coord) x_coord +#endif -/* This library is designed to take an 8 bit / 1 byte value (0-255) for each R G B colour sub-pixel. +/* This library is designed to take an 8 bit / 1 byte value (0-255) for each R G B colour sub-pixel. * The PIXEL_COLOR_DEPTH_BITS should always be '8' as a result. * However, if the library is to be used with lower colour depth (i.e. 6 bit colour), then we need to ensure the 8-bit value passed to the colour masking * is adjusted accordingly to ensure the LSB's are shifted left to MSB, by the difference. Otherwise the colours will be all screwed up. */ -//#if PIXEL_COLOR_DEPTH_BITS > 12 -// #error "Color depth bits cannot be greater than 12." -//#elif PIXEL_COLOR_DEPTH_BITS < 2 -// #error "Color depth bits cannot be less than 2." -//#endif - - //#define MASK_OFFSET (16 - PIXEL_COLOR_DEPTH_BITS) - //#define PIXEL_COLOR_MASK_BIT(color_depth_index) (1 << (color_depth_index + MASK_OFFSET)) - #define PIXEL_COLOR_MASK_BIT(color_depth_index, mask_offset) (1 << (color_depth_index + mask_offset)) - //static constexpr uint8_t const MASK_OFFSET = 8-PIXEL_COLOUR_DEPTH_BITS; - -/* - #if PIXEL_COLOR_DEPTH_BITS < 8 - uint8_t mask = (1 << (colour_depth_idx+MASK_OFFSET)); // expect 24 bit colour (8 bits per RGB subpixel) - #else - uint8_t mask = (1 << (colour_depth_idx)); // expect 24 bit colour (8 bits per RGB subpixel) - #endif -*/ - +#define PIXEL_COLOR_MASK_BIT(color_depth_index, mask_offset) (1 << (color_depth_index + mask_offset)) bool MatrixPanel_I2S_DMA::allocateDMAmemory() { - ESP_LOGI("I2S-DMA", "Free heap: %d", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); - ESP_LOGI("I2S-DMA", "Free SPIRAM: %d", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); - + ESP_LOGI("I2S-DMA", "Free heap: %d", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); + ESP_LOGI("I2S-DMA", "Free SPIRAM: %d", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); // Alright, theoretically we should be OK, so let us do this, so // lets allocate a chunk of memory for each row (a row could span multiple panels if chaining is in place) - dma_buff.rowBits.reserve(ROWS_PER_FRAME); + ESP_LOGI("I2S-DMA", "allocating rowBitStructs with pixel_color_depth_bits of %d", m_cfg.getPixelColorDepthBits()); + // iterate through number of rows, allocate memory for each + size_t allocated_fb_memory = 0; - ESP_LOGI("I2S-DMA", "allocating rowBitStructs with pixel_color_depth_bits of %d", m_cfg.getPixelColorDepthBits()); - // iterate through number of rows, allocate memory for each - size_t allocated_fb_memory = 0; - for (int malloc_num =0; malloc_num < ROWS_PER_FRAME; ++malloc_num) + int fbs_required = (m_cfg.double_buff) ? 2 : 1; + for (int fb = 0; fb < (fbs_required); fb++) + { + frame_buffer[fb].rowBits.reserve(ROWS_PER_FRAME); + + for (int malloc_num = 0; malloc_num < ROWS_PER_FRAME; malloc_num++) { - auto ptr = std::make_shared(PIXELS_PER_ROW, m_cfg.getPixelColorDepthBits(), m_cfg.double_buff); + auto ptr = std::make_shared(PIXELS_PER_ROW, m_cfg.getPixelColorDepthBits(), m_cfg.double_buff); - if (ptr->data == nullptr) - { - ESP_LOGE("I2S-DMA", "CRITICAL ERROR: Not enough memory for requested colour depth! Please reduce pixel_color_depth_bits value.\r\n"); - ESP_LOGE("I2S-DMA", "Could not allocate rowBitStruct %d!.\r\n", malloc_num); + if (ptr->data == nullptr) + { + ESP_LOGE("I2S-DMA", "CRITICAL ERROR: Not enough memory for requested colour depth! Please reduce pixel_color_depth_bits value.\r\n"); + ESP_LOGE("I2S-DMA", "Could not allocate rowBitStruct %d!.\r\n", malloc_num); - return false; - // TODO: should we release all previous rowBitStructs here??? - } + return false; + // TODO: should we release all previous rowBitStructs here??? + } - allocated_fb_memory += ptr->size(); - dma_buff.rowBits.emplace_back(ptr); // save new rowBitStruct into rows vector - ++dma_buff.rows; + allocated_fb_memory += ptr->getColorDepthSize(); // byte required to display all colour depths for the rows shown at the same time + frame_buffer[fb].rowBits.emplace_back(ptr); // save new rowBitStruct pointer into rows vector + ++frame_buffer[fb].rows; } - ESP_LOGI("I2S-DMA", "Allocating %d bytes memory for DMA BCM framebuffer(s).", allocated_fb_memory); - - // calculate the lowest LSBMSB_TRANSITION_BIT value that will fit in memory that will meet or exceed the configured refresh rate -#if !defined(FORCE_COLOR_DEPTH) + } + ESP_LOGI("I2S-DMA", "Allocating %d bytes memory for DMA BCM framebuffer(s).", allocated_fb_memory); - ESP_LOGI("I2S-DMA", "Minimum visual refresh rate (scan rate from panel top to bottom) requested: %d Hz", m_cfg.min_refresh_rate); + // calculate the lowest LSBMSB_TRANSITION_BIT value that will fit in memory that will meet or exceed the configured refresh rate + +//#define FORCE_COLOR_DEPTH 1 + +#if !defined(FORCE_COLOR_DEPTH) - while(1) { - int psPerClock = 1000000000000UL/m_cfg.i2sspeed; - int nsPerLatch = ((PIXELS_PER_ROW + CLKS_DURING_LATCH) * psPerClock) / 1000; + ESP_LOGI("I2S-DMA", "Minimum visual refresh rate (scan rate from panel top to bottom) requested: %d Hz", m_cfg.min_refresh_rate); - // add time to shift out LSBs + LSB-MSB transition bit - this ignores fractions... - int nsPerRow = m_cfg.getPixelColorDepthBits() * nsPerLatch; + while (1) + { + int psPerClock = 1000000000000UL / m_cfg.i2sspeed; + int nsPerLatch = ((PIXELS_PER_ROW + CLKS_DURING_LATCH) * psPerClock) / 1000; - // add time to shift out MSBs - for(int i=lsbMsbTransitionBit + 1; i < m_cfg.getPixelColorDepthBits(); i++) - nsPerRow += (1<<(i - lsbMsbTransitionBit - 1)) * (m_cfg.getPixelColorDepthBits() - i) * nsPerLatch; + // add time to shift out LSBs + LSB-MSB transition bit - this ignores fractions... + int nsPerRow = m_cfg.getPixelColorDepthBits() * nsPerLatch; - int nsPerFrame = nsPerRow * ROWS_PER_FRAME; - int actualRefreshRate = 1000000000UL/(nsPerFrame); - calculated_refresh_rate = actualRefreshRate; + // add time to shift out MSBs + for (int i = lsbMsbTransitionBit + 1; i < m_cfg.getPixelColorDepthBits(); i++) + nsPerRow += (1 << (i - lsbMsbTransitionBit - 1)) * (m_cfg.getPixelColorDepthBits() - i) * nsPerLatch; - ESP_LOGW("I2S-DMA", "lsbMsbTransitionBit of %d gives %d Hz refresh rate.", lsbMsbTransitionBit, actualRefreshRate); + int nsPerFrame = nsPerRow * ROWS_PER_FRAME; + int actualRefreshRate = 1000000000UL / (nsPerFrame); + calculated_refresh_rate = actualRefreshRate; + + ESP_LOGW("I2S-DMA", "lsbMsbTransitionBit of %d gives %d Hz refresh rate.", lsbMsbTransitionBit, actualRefreshRate); + + if (actualRefreshRate > m_cfg.min_refresh_rate) + break; + + if (lsbMsbTransitionBit < m_cfg.getPixelColorDepthBits() - 1) + lsbMsbTransitionBit++; + else + break; + } + + if (lsbMsbTransitionBit > 0) + { + ESP_LOGW("I2S-DMA", "lsbMsbTransitionBit of %d used to achieve refresh rate of %d Hz. Percieved colour depth to the eye may be reduced.", lsbMsbTransitionBit, m_cfg.min_refresh_rate); + } - if (actualRefreshRate > m_cfg.min_refresh_rate) - break; - - if(lsbMsbTransitionBit < m_cfg.getPixelColorDepthBits() - 1) - lsbMsbTransitionBit++; - else - break; - } - - if ( lsbMsbTransitionBit > 0 ) - { - ESP_LOGW("I2S-DMA", "lsbMsbTransitionBit of %d used to achieve refresh rate of %d Hz. Percieved colour depth to the eye may be reduced.", lsbMsbTransitionBit, m_cfg.min_refresh_rate); - } - ESP_LOGI("I2S-DMA", "DMA has pixel_color_depth_bits of %d", m_cfg.getPixelColorDepthBits() - lsbMsbTransitionBit); #endif - /*** * Step 2a: lsbMsbTransition bit is now finalised - recalculate the DMA descriptor count required, which is used for * memory allocation of the DMA linked list memory structure. - */ - int numDMAdescriptorsPerRow = 1; - for(int i=lsbMsbTransitionBit + 1; i < m_cfg.getPixelColorDepthBits(); i++) { - numDMAdescriptorsPerRow += (1<<(i - lsbMsbTransitionBit - 1)); - } + */ + int numDMAdescriptorsPerRow = 1; + for (int i = lsbMsbTransitionBit + 1; i < m_cfg.getPixelColorDepthBits(); i++) + { + numDMAdescriptorsPerRow += (1 << (i - lsbMsbTransitionBit - 1)); + } - ESP_LOGI("I2S-DMA", "Recalculated number of DMA descriptors per row: %d", numDMAdescriptorsPerRow); + ESP_LOGI("I2S-DMA", "Recalculated number of DMA descriptors per row: %d", numDMAdescriptorsPerRow); - // Refer to 'DMA_LL_PAYLOAD_SPLIT' code in configureDMA() below to understand why this exists. - // numDMAdescriptorsPerRow is also used to calculate descount which is super important in i2s_parallel_config_t SoC DMA setup. - if ( dma_buff.rowBits[0]->size() > DMA_MAX ) - { + // Refer to 'DMA_LL_PAYLOAD_SPLIT' code in configureDMA() below to understand why this exists. + // numDMAdescriptorsPerRow is also used to calculate descount which is super important in i2s_parallel_config_t SoC DMA setup. + if (frame_buffer[0].rowBits[0]->getColorDepthSize() > DMA_MAX) + { - ESP_LOGW("I2S-DMA", "rowBits struct is too large to fit in one DMA transfer payload, splitting required. Adding %d DMA descriptors\n", m_cfg.getPixelColorDepthBits()-1); - - numDMAdescriptorsPerRow += m_cfg.getPixelColorDepthBits()-1; - // Note: If numDMAdescriptorsPerRow is even just one descriptor too large, DMA linked list will not correctly loop. - } + ESP_LOGW("I2S-DMA", "rowBits struct is too large to fit in one DMA transfer payload, splitting required. Adding %d DMA descriptors\n", m_cfg.getPixelColorDepthBits() - 1); + numDMAdescriptorsPerRow += m_cfg.getPixelColorDepthBits() - 1; + // Note: If numDMAdescriptorsPerRow is even just one descriptor too large, DMA linked list will not correctly loop. + } /*** * Step 3: Allocate memory for DMA linked list, linking up each framebuffer row in sequence for GPIO output. - */ + */ - // malloc the DMA linked list descriptors that i2s_parallel will need - desccount = numDMAdescriptorsPerRow * ROWS_PER_FRAME; + // malloc the DMA linked list descriptors that i2s_parallel will need + int desccount = numDMAdescriptorsPerRow * ROWS_PER_FRAME; - if (m_cfg.double_buff) - dma_bus.enable_double_dma_desc(); - - dma_bus.allocate_dma_desc_memory(desccount); + if (m_cfg.double_buff) + { + dma_bus.enable_double_dma_desc(); + } - // Just os we know - initialized = true; + dma_bus.allocate_dma_desc_memory(desccount); - return true; + // point FB we can write to, to 0 / dmadesc_a + fb = &frame_buffer[0]; + + // Just os we know + initialized = true; + + return true; } // end allocateDMAmemory() -void MatrixPanel_I2S_DMA::configureDMA(const HUB75_I2S_CFG& _cfg) +/* +// Version 2.0 March 2023 +int MatrixPanel_I2S_DMA::create_descriptor_links(void *data, size_t size, bool dmadesc_b, bool countonly) { + int len = size; + uint8_t *data2 = (uint8_t *)data; - // lldesc_t *previous_dmadesc_a = 0; - // lldesc_t *previous_dmadesc_b = 0; - int current_dmadescriptor_offset = 0; + int n = 0; + while (len) + { + int dmalen = len; + if (dmalen > DMA_MAX) + dmalen = DMA_MAX; - // HACK: If we need to split the payload in 1/2 so that it doesn't breach DMA_MAX, lets do it by the colour_depth. - int num_dma_payload_colour_depths = m_cfg.getPixelColorDepthBits(); - if ( dma_buff.rowBits[0]->size() > DMA_MAX ) { - num_dma_payload_colour_depths = 1; + if (!countonly) + dma_bus.create_dma_desc_link(data2, dmalen, dmadesc_b); + + len -= dmalen; + data2 += dmalen; + n++; } - // Fill DMA linked lists for both frames (as in, halves of the HUB75 panel) and if double buffering is enabled, link it up for both buffers. - for(int row = 0; row < ROWS_PER_FRAME; row++) + return n; +} +*/ +void MatrixPanel_I2S_DMA::configureDMA(const HUB75_I2S_CFG &_cfg) +{ + + // lldesc_t *previous_dmadesc_a = 0; + // lldesc_t *previous_dmadesc_b = 0; + int current_dmadescriptor_offset = 0; + + // HACK: If we need to split the payload in 1/2 so that it doesn't breach DMA_MAX, lets do it by the colour_depth. + int num_dma_payload_colour_depths = m_cfg.getPixelColorDepthBits(); + if (frame_buffer[0].rowBits[0]->getColorDepthSize() > DMA_MAX) + { + num_dma_payload_colour_depths = 1; + } + + + // Fill DMA linked lists for both frames (as in, halves of the HUB75 panel) in sequence (top to bottom) + for (int row = 0; row < ROWS_PER_FRAME; row++) + { + // first set of data is LSB through MSB, single pass (IF TOTAL SIZE < DMA_MAX) - all colour bits are displayed once, which takes care of everything below and including LSBMSB_TRANSITION_BIT + // NOTE: size must be less than DMA_MAX - worst case for library: 16-bpp with 256 pixels per row would exceed this, need to break into two + // link_dma_desc(&dmadesc_a[current_dmadescriptor_offset], previous_dmadesc_a, dma_buff.rowBits[row]->getDataPtr(), dma_buff.rowBits[row]->size(num_dma_payload_colour_depths)); + // previous_dmadesc_a = &dmadesc_a[current_dmadescriptor_offset]; + + dma_bus.create_dma_desc_link(frame_buffer[0].rowBits[row]->getDataPtr(0, 0), frame_buffer[0].rowBits[row]->getColorDepthSize(), false); + + if (m_cfg.double_buff) { - // first set of data is LSB through MSB, single pass (IF TOTAL SIZE < DMA_MAX) - all colour bits are displayed once, which takes care of everything below and including LSBMSB_TRANSITION_BIT - // NOTE: size must be less than DMA_MAX - worst case for library: 16-bpp with 256 pixels per row would exceed this, need to break into two - //link_dma_desc(&dmadesc_a[current_dmadescriptor_offset], previous_dmadesc_a, dma_buff.rowBits[row]->getDataPtr(), dma_buff.rowBits[row]->size(num_dma_payload_colour_depths)); - // previous_dmadesc_a = &dmadesc_a[current_dmadescriptor_offset]; + dma_bus.create_dma_desc_link(frame_buffer[1].rowBits[row]->getDataPtr(0, 1), frame_buffer[1].rowBits[row]->getColorDepthSize(), true); + } - dma_bus.create_dma_desc_link(dma_buff.rowBits[row]->getDataPtr(0, 0), dma_buff.rowBits[row]->size(num_dma_payload_colour_depths), false); + current_dmadescriptor_offset++; - if (m_cfg.double_buff) + // If the number of pixels per row is too great for the size of a DMA payload, so we need to split what we were going to send above. + if (frame_buffer[0].rowBits[0]->getColorDepthSize() > DMA_MAX) + { + + for (int cd = 1; cd < m_cfg.getPixelColorDepthBits(); cd++) + { + dma_bus.create_dma_desc_link(frame_buffer[0].rowBits[row]->getDataPtr(cd, 0), frame_buffer[0].rowBits[row]->getColorDepthSize(1), false); + + if (m_cfg.double_buff) { - dma_bus.create_dma_desc_link(dma_buff.rowBits[row]->getDataPtr(0, 1), dma_buff.rowBits[row]->size(num_dma_payload_colour_depths), true); + dma_bus.create_dma_desc_link(frame_buffer[1].rowBits[row]->getDataPtr(cd, 1), frame_buffer[1].rowBits[row]->getColorDepthSize(1), true); } current_dmadescriptor_offset++; - // If the number of pixels per row is too great for the size of a DMA payload, so we need to split what we were going to send above. - if ( dma_buff.rowBits[0]->size() > DMA_MAX ) + } // additional linked list items + } // row depth struct + + for (int i = lsbMsbTransitionBit + 1; i < m_cfg.getPixelColorDepthBits(); i++) + { + // binary time division setup: we need 2 of bit (LSBMSB_TRANSITION_BIT + 1) four of (LSBMSB_TRANSITION_BIT + 2), etc + // because we sweep through to MSB each time, it divides the number of times we have to sweep in half (saving linked list RAM) + // we need 2^(i - LSBMSB_TRANSITION_BIT - 1) == 1 << (i - LSBMSB_TRANSITION_BIT - 1) passes from i to MSB + + for (int k = 0; k < (1 << (i - lsbMsbTransitionBit - 1)); k++) + { + dma_bus.create_dma_desc_link(frame_buffer[0].rowBits[row]->getDataPtr(i, 0), frame_buffer[0].rowBits[row]->getColorDepthSize(1), false); + + if (m_cfg.double_buff) { - - for (int cd = 1; cd < m_cfg.getPixelColorDepthBits(); cd++) - { - dma_bus.create_dma_desc_link(dma_buff.rowBits[row]->getDataPtr(cd, 0), dma_buff.rowBits[row]->size(num_dma_payload_colour_depths), false); + dma_bus.create_dma_desc_link(frame_buffer[1].rowBits[row]->getDataPtr(i, 1), frame_buffer[1].rowBits[row]->getColorDepthSize(1), true); + } - if (m_cfg.double_buff) { - dma_bus.create_dma_desc_link(dma_buff.rowBits[row]->getDataPtr(cd, 1), dma_buff.rowBits[row]->size(num_dma_payload_colour_depths), true); - } + current_dmadescriptor_offset++; - current_dmadescriptor_offset++; + } // end colour depth ^ 2 linked list + } // end colour depth loop - } // additional linked list items - } // row depth struct + } // end frame rows + ESP_LOGI("I2S-DMA", "%d DMA descriptors linked to buffer data.", current_dmadescriptor_offset); - for(int i=lsbMsbTransitionBit + 1; i < m_cfg.getPixelColorDepthBits(); i++) - { - // binary time division setup: we need 2 of bit (LSBMSB_TRANSITION_BIT + 1) four of (LSBMSB_TRANSITION_BIT + 2), etc - // because we sweep through to MSB each time, it divides the number of times we have to sweep in half (saving linked list RAM) - // we need 2^(i - LSBMSB_TRANSITION_BIT - 1) == 1 << (i - LSBMSB_TRANSITION_BIT - 1) passes from i to MSB + // + // Setup DMA and Output to GPIO + // + auto bus_cfg = dma_bus.config(); // ใƒใ‚น่จญๅฎš็”จใฎๆง‹้€ ไฝ“ใ‚’ๅ–ๅพ—ใ—ใพใ™ใ€‚ - for(int k=0; k < (1<<(i - lsbMsbTransitionBit - 1)); k++) - { - dma_bus.create_dma_desc_link(dma_buff.rowBits[row]->getDataPtr(i, 0), dma_buff.rowBits[row]->size(m_cfg.getPixelColorDepthBits() - i), false); + bus_cfg.bus_freq = m_cfg.i2sspeed; + bus_cfg.pin_wr = m_cfg.gpio.clk; + bus_cfg.invert_pclk = m_cfg.clkphase; - if (m_cfg.double_buff) { - dma_bus.create_dma_desc_link(dma_buff.rowBits[row]->getDataPtr(i, 1), dma_buff.rowBits[row]->size(m_cfg.getPixelColorDepthBits() - i), true ); - } + bus_cfg.pin_d0 = m_cfg.gpio.r1; + bus_cfg.pin_d1 = m_cfg.gpio.g1; + bus_cfg.pin_d2 = m_cfg.gpio.b1; + bus_cfg.pin_d3 = m_cfg.gpio.r2; + bus_cfg.pin_d4 = m_cfg.gpio.g2; + bus_cfg.pin_d5 = m_cfg.gpio.b2; + bus_cfg.pin_d6 = m_cfg.gpio.lat; + bus_cfg.pin_d7 = m_cfg.gpio.oe; + bus_cfg.pin_d8 = m_cfg.gpio.a; + bus_cfg.pin_d9 = m_cfg.gpio.b; + bus_cfg.pin_d10 = m_cfg.gpio.c; + bus_cfg.pin_d11 = m_cfg.gpio.d; + bus_cfg.pin_d12 = m_cfg.gpio.e; + bus_cfg.pin_d13 = -1; + bus_cfg.pin_d14 = -1; + bus_cfg.pin_d15 = -1; - current_dmadescriptor_offset++; +#if defined(SPIRAM_DMA_BUFFER) + bus_cfg.psram_clk_override = true; +#endif - } // end colour depth ^ 2 linked list - } // end colour depth loop + dma_bus.config(bus_cfg); - } // end frame rows + dma_bus.init(); - ESP_LOGI("I2S-DMA", "%d DMA descriptors linked to buffer data.", current_dmadescriptor_offset); + dma_bus.dma_transfer_start(); -// -// Setup DMA and Output to GPIO -// - auto bus_cfg = dma_bus.config(); // ใƒใ‚น่จญๅฎš็”จใฎๆง‹้€ ไฝ“ใ‚’ๅ–ๅพ—ใ—ใพใ™ใ€‚ - - bus_cfg.bus_freq = m_cfg.i2sspeed; - bus_cfg.pin_wr = m_cfg.gpio.clk; // WR ใ‚’ๆŽฅ็ถšใ—ใฆใ„ใ‚‹ใƒ”ใƒณ็•ชๅท - - bus_cfg.pin_d0 = m_cfg.gpio.r1; - bus_cfg.pin_d1 = m_cfg.gpio.g1; - bus_cfg.pin_d2 = m_cfg.gpio.b1; - bus_cfg.pin_d3 = m_cfg.gpio.r2; - bus_cfg.pin_d4 = m_cfg.gpio.g2; - bus_cfg.pin_d5 = m_cfg.gpio.b2; - bus_cfg.pin_d6 = m_cfg.gpio.lat; - bus_cfg.pin_d7 = m_cfg.gpio.oe; - bus_cfg.pin_d8 = m_cfg.gpio.a; - bus_cfg.pin_d9 = m_cfg.gpio.b; - bus_cfg.pin_d10 = m_cfg.gpio.c; - bus_cfg.pin_d11 = m_cfg.gpio.d; - bus_cfg.pin_d12 = m_cfg.gpio.e; - bus_cfg.pin_d13 = -1; - bus_cfg.pin_d14 = -1; - bus_cfg.pin_d15 = -1; + flipDMABuffer(); // display back buffer 0, draw to 1, ignored if double buffering isn't enabled. - #if defined(SPIRAM_DMA_BUFFER) - bus_cfg.psram_clk_override = true; - #endif + // i2s_parallel_send_dma(ESP32_I2S_DEVICE, &dmadesc_a[0]); + ESP_LOGI("I2S-DMA", "DMA setup completed"); - dma_bus.config(bus_cfg); - - dma_bus.init(); - - dma_bus.dma_transfer_start(); - - flipDMABuffer(); // display back buffer 0, draw to 1, ignored if double buffering isn't enabled. - - //i2s_parallel_send_dma(ESP32_I2S_DEVICE, &dmadesc_a[0]); - ESP_LOGI("I2S-DMA", "DMA setup completed"); - } // end initMatrixDMABuff - /* There are 'bits' set in the frameStruct that we simply don't need to set every single time we change a pixel / DMA buffer co-ordinate. * For example, the bits that determine the address lines, we don't need to set these every time. Once they're in place, and assuming we * don't accidentally clear them, then we don't need to set them again. @@ -295,520 +320,415 @@ void MatrixPanel_I2S_DMA::configureDMA(const HUB75_I2S_CFG& _cfg) * Let's put it into IRAM to avoid situations when it could be flushed out of instruction cache * and had to be read from spi-flash over and over again. * Yes, it is always a tradeoff between memory/speed/size, but compared to DMA-buffer size is not a big deal - * + * * Note: Cannot pass a negative co-ord as it makes no sense in the DMA bit array lookup. */ void IRAM_ATTR MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint16_t x_coord, uint16_t y_coord, uint8_t red, uint8_t green, uint8_t blue) { - if ( !initialized ) return; + if (!initialized) + return; /* 1) Check that the co-ordinates are within range, or it'll break everything big time. - * Valid co-ordinates are from 0 to (MATRIX_XXXX-1) - */ - if ( x_coord >= PIXELS_PER_ROW || y_coord >= m_cfg.mx_height) { + * Valid co-ordinates are from 0 to (MATRIX_XXXX-1) + */ + if (x_coord >= PIXELS_PER_ROW || y_coord >= m_cfg.mx_height) + { return; } - /* LED Brightness Compensation. Because if we do a basic "red & mask" for example, - * we'll NEVER send the dimmest possible colour, due to binary skew. - * i.e. It's almost impossible for colour_depth_idx of 0 to be sent out to the MATRIX unless the 'value' of a colour is exactly '1' + /* LED Brightness Compensation. Because if we do a basic "red & mask" for example, + * we'll NEVER send the dimmest possible colour, due to binary skew. + * i.e. It's almost impossible for colour_depth_idx of 0 to be sent out to the MATRIX unless the 'value' of a colour is exactly '1' * https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/ - */ -uint16_t red16, green16, blue16; + */ + uint16_t red16, green16, blue16; #ifndef NO_CIE1931 - red16 = lumConvTab[red]; - green16 = lumConvTab[green]; - blue16 = lumConvTab[blue]; + red16 = lumConvTab[red]; + green16 = lumConvTab[green]; + blue16 = lumConvTab[blue]; #else - red16 = red << 8; - green16 = green << 8; - blue16 = blue << 8; + red16 = red << 8 | red; + green16 = green << 8 | green; + blue16 = blue << 8 | blue; #endif - /* When using the drawPixel, we are obviously only changing the value of one x,y position, - * however, the two-scan panels paint TWO lines at the same time - * and this reflects the parallel in-DMA-memory data structure of uint16_t's that are getting - * pumped out at high speed. - * - * So we need to ensure we persist the bits (8 of them) of the uint16_t for the row we aren't changing. - * - * The DMA buffer order has also been reversed (refer to the last code in this function) - * so we have to check for this and check the correct position of the MATRIX_DATA_STORAGE_TYPE - * data. - */ -/* -#if defined (ESP32_THE_ORIG) - // We need to update the correct uint16_t in the rowBitStruct array, that gets sent out in parallel - // 16 bit parallel mode - Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering - // Irrelevant for ESP32-S2 the way the FIFO ordering works is different - refer to page 679 of S2 technical reference manual - x_coord & 1U ? --x_coord : ++x_coord; -#endif -*/ - x_coord = ESP32_TX_FIFO_POSITION_ADJUST(x_coord); - - - uint16_t _colourbitclear = BITMASK_RGB1_CLEAR, _colourbitoffset = 0; + /* When using the drawPixel, we are obviously only changing the value of one x,y position, + * however, the two-scan panels paint TWO lines at the same time + * and this reflects the parallel in-DMA-memory data structure of uint16_t's that are getting + * pumped out at high speed. + * + * So we need to ensure we persist the bits (8 of them) of the uint16_t for the row we aren't changing. + * + * The DMA buffer order has also been reversed (refer to the last code in this function) + * so we have to check for this and check the correct position of the MATRIX_DATA_STORAGE_TYPE + * data. + */ + x_coord = ESP32_TX_FIFO_POSITION_ADJUST(x_coord); - if (y_coord >= ROWS_PER_FRAME){ // if we are drawing to the bottom part of the panel - _colourbitoffset = BITS_RGB2_OFFSET; - _colourbitclear = BITMASK_RGB2_CLEAR; - y_coord -= ROWS_PER_FRAME; - } + uint16_t _colourbitclear = BITMASK_RGB1_CLEAR, _colourbitoffset = 0; - // Iterating through colour depth bits, which we assume are 8 bits per RGB subpixel (24bpp) - uint8_t colour_depth_idx = m_cfg.getPixelColorDepthBits(); - do { - --colour_depth_idx; -/* -// uint8_t mask = (1 << (colour_depth_idx COLOR_DEPTH_LESS_THAN_8BIT_ADJUST)); // expect 24 bit colour (8 bits per RGB subpixel) - #if PIXEL_COLOUR_DEPTH_BITS < 8 - uint8_t mask = (1 << (colour_depth_idx+MASK_OFFSET)); // expect 24 bit colour (8 bits per RGB subpixel) - #else - uint8_t mask = (1 << (colour_depth_idx)); // expect 24 bit color (8 bits per RGB subpixel) - #endif -*/ - uint16_t mask = PIXEL_COLOR_MASK_BIT(colour_depth_idx, MASK_OFFSET); - uint16_t RGB_output_bits = 0; + if (y_coord >= ROWS_PER_FRAME) + { // if we are drawing to the bottom part of the panel + _colourbitoffset = BITS_RGB2_OFFSET; + _colourbitclear = BITMASK_RGB2_CLEAR; + y_coord -= ROWS_PER_FRAME; + } - /* Per the .h file, the order of the output RGB bits is: - * BIT_B2, BIT_G2, BIT_R2, BIT_B1, BIT_G1, BIT_R1 */ - RGB_output_bits |= (bool)(blue16 & mask); // --B - RGB_output_bits <<= 1; - RGB_output_bits |= (bool)(green16 & mask); // -BG - RGB_output_bits <<= 1; - RGB_output_bits |= (bool)(red16 & mask); // BGR - RGB_output_bits <<= _colourbitoffset; // shift colour bits to the required position + // Iterating through colour depth bits, which we assume are 8 bits per RGB subpixel (24bpp) + uint8_t colour_depth_idx = m_cfg.getPixelColorDepthBits(); + do + { + --colour_depth_idx; + uint16_t mask = PIXEL_COLOR_MASK_BIT(colour_depth_idx, MASK_OFFSET); + uint16_t RGB_output_bits = 0; - // Get the contents at this address, - // it would represent a vector pointing to the full row of pixels for the specified colour depth bit at Y coordinate - ESP32_I2S_DMA_STORAGE_TYPE *p = getRowDataPtr(y_coord, colour_depth_idx, back_buffer_id); + /* Per the .h file, the order of the output RGB bits is: + * BIT_B2, BIT_G2, BIT_R2, BIT_B1, BIT_G1, BIT_R1 */ + RGB_output_bits |= (bool)(blue16 & mask); // --B + RGB_output_bits <<= 1; + RGB_output_bits |= (bool)(green16 & mask); // -BG + RGB_output_bits <<= 1; + RGB_output_bits |= (bool)(red16 & mask); // BGR + RGB_output_bits <<= _colourbitoffset; // shift colour bits to the required position + // Get the contents at this address, + // it would represent a vector pointing to the full row of pixels for the specified colour depth bit at Y coordinate + ESP32_I2S_DMA_STORAGE_TYPE *p = getRowDataPtr(y_coord, colour_depth_idx, back_buffer_id); - // We need to update the correct uint16_t word in the rowBitStruct array pointing to a specific pixel at X - coordinate - p[x_coord] &= _colourbitclear; // reset RGB bits - p[x_coord] |= RGB_output_bits; // set new RGB bits + // We need to update the correct uint16_t word in the rowBitStruct array pointing to a specific pixel at X - coordinate + p[x_coord] &= _colourbitclear; // reset RGB bits + p[x_coord] |= RGB_output_bits; // set new RGB bits - #if defined(SPIRAM_DMA_BUFFER) - Cache_WriteBack_Addr((uint32_t)&p[x_coord], sizeof(ESP32_I2S_DMA_STORAGE_TYPE)) ; - #endif +#if defined(SPIRAM_DMA_BUFFER) + Cache_WriteBack_Addr((uint32_t)&p[x_coord], sizeof(ESP32_I2S_DMA_STORAGE_TYPE)); +#endif - } while(colour_depth_idx); // end of colour depth loop (8) + } while (colour_depth_idx); // end of colour depth loop (8) } // updateMatrixDMABuffer (specific co-ords change) - /* Update the entire buffer with a single specific colour - quicker */ void MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint8_t red, uint8_t green, uint8_t blue) { - if ( !initialized ) return; - - /* https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/ */ -uint16_t red16, green16, blue16; + if (!initialized) + return; + + /* https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/ */ + uint16_t red16, green16, blue16; #ifndef NO_CIE1931 - red16 = lumConvTab[red]; - green16 = lumConvTab[green]; - blue16 = lumConvTab[blue]; + red16 = lumConvTab[red]; + green16 = lumConvTab[green]; + blue16 = lumConvTab[blue]; #else - red16 = red << 8; - green16 = green << 8; - blue16 = blue << 8; + red16 = red << 8; + green16 = green << 8; + blue16 = blue << 8; #endif - for(uint8_t colour_depth_idx=0; colour_depth_idx < m_cfg.getPixelColorDepthBits(); colour_depth_idx++) // colour depth - 8 iterations + for (uint8_t colour_depth_idx = 0; colour_depth_idx < m_cfg.getPixelColorDepthBits(); colour_depth_idx++) // colour depth - 8 iterations { // let's precalculate RGB1 and RGB2 bits than flood it over the entire DMA buffer uint16_t RGB_output_bits = 0; -// uint8_t mask = (1 << colour_depth_idx COLOR_DEPTH_LESS_THAN_8BIT_ADJUST); // 24 bit colour - // #if PIXEL_COLOR_DEPTH_BITS < 8 - // uint8_t mask = (1 << (colour_depth_idx+MASK_OFFSET)); // expect 24 bit colour (8 bits per RGB subpixel) - // #else - // uint8_t mask = (1 << (colour_depth_idx)); // expect 24 bit colour (8 bits per RGB subpixel) - // #endif uint16_t mask = PIXEL_COLOR_MASK_BIT(colour_depth_idx, MASK_OFFSET); /* Per the .h file, the order of the output RGB bits is: * BIT_B2, BIT_G2, BIT_R2, BIT_B1, BIT_G1, BIT_R1 */ - RGB_output_bits |= (bool)(blue16 & mask); // --B + RGB_output_bits |= (bool)(blue16 & mask); // --B RGB_output_bits <<= 1; - RGB_output_bits |= (bool)(green16 & mask); // -BG + RGB_output_bits |= (bool)(green16 & mask); // -BG RGB_output_bits <<= 1; - RGB_output_bits |= (bool)(red16 & mask); // BGR - + RGB_output_bits |= (bool)(red16 & mask); // BGR + // Duplicate and shift across so we have have 6 populated bits of RGB1 and RGB2 pin values suitable for DMA buffer - RGB_output_bits |= RGB_output_bits << BITS_RGB2_OFFSET; //BGRBGR - - //Serial.printf("Fill with: 0x%#06x\n", RGB_output_bits); + RGB_output_bits |= RGB_output_bits << BITS_RGB2_OFFSET; // BGRBGR + + // Serial.printf("Fill with: 0x%#06x\n", RGB_output_bits); // iterate rows - int matrix_frame_parallel_row = dma_buff.rowBits.size(); - do { + int matrix_frame_parallel_row = fb->rowBits.size(); + do + { --matrix_frame_parallel_row; // The destination for the pixel row bitstream ESP32_I2S_DMA_STORAGE_TYPE *p = getRowDataPtr(matrix_frame_parallel_row, colour_depth_idx, back_buffer_id); // iterate pixels in a row - int x_coord=dma_buff.rowBits[matrix_frame_parallel_row]->width; - do { + int x_coord = fb->rowBits[matrix_frame_parallel_row]->width; + do + { --x_coord; - p[x_coord] &= BITMASK_RGB12_CLEAR; // reset colour bits - p[x_coord] |= RGB_output_bits; // set new colour bits + p[x_coord] &= BITMASK_RGB12_CLEAR; // reset colour bits + p[x_coord] |= RGB_output_bits; // set new colour bits +#if defined(SPIRAM_DMA_BUFFER) + Cache_WriteBack_Addr((uint32_t)&p[x_coord], sizeof(ESP32_I2S_DMA_STORAGE_TYPE)); +#endif - #if defined(SPIRAM_DMA_BUFFER) - Cache_WriteBack_Addr((uint32_t)&p[x_coord], sizeof(ESP32_I2S_DMA_STORAGE_TYPE)) ; - #endif + } while (x_coord); - } while(x_coord); - - } while(matrix_frame_parallel_row); // end row iteration - } // colour depth loop (8) + } while (matrix_frame_parallel_row); // end row iteration + } // colour depth loop (8) } // updateMatrixDMABuffer (full frame paint) /** * @brief - clears and reinitializes colour/control data in DMA buffs * When allocated, DMA buffs might be dirty, so we need to blank it and initialize ABCDE,LAT,OE control bits. * Those control bits are constants during the entire DMA sweep and never changed when updating just pixel colour data - * so we could set it once on DMA buffs initialization and forget. + * so we could set it once on DMA buffs initialization and forget. * This effectively clears buffers to blank BLACK and makes it ready to display output. * (Brightness control via OE bit manipulation is another case) - this must be done as well seperately! */ -void MatrixPanel_I2S_DMA::clearFrameBuffer(bool _buff_id){ +void MatrixPanel_I2S_DMA::clearFrameBuffer(bool _buff_id) +{ if (!initialized) return; + frameStruct *fb = &frame_buffer[_buff_id]; + // we start with iterating all rows in dma_buff structure - int row_idx = dma_buff.rowBits.size(); - do { + int row_idx = fb->rowBits.size(); + do + { --row_idx; - - ESP32_I2S_DMA_STORAGE_TYPE* row = dma_buff.rowBits[row_idx]->getDataPtr(0, _buff_id); // set pointer to the HEAD of a buffer holding data for the entire matrix row + + ESP32_I2S_DMA_STORAGE_TYPE *row = fb->rowBits[row_idx]->getDataPtr(0, -1); // set pointer to the HEAD of a buffer holding data for the entire matrix row ESP32_I2S_DMA_STORAGE_TYPE abcde = (ESP32_I2S_DMA_STORAGE_TYPE)row_idx; - abcde <<= BITS_ADDR_OFFSET; // shift row y-coord to match ABCDE bits in vector from 8 to 12 + abcde <<= BITS_ADDR_OFFSET; // shift row y-coord to match ABCDE bits in vector from 8 to 12 // get last pixel index in a row of all colourdepths - int x_pixel = dma_buff.rowBits[row_idx]->width * dma_buff.rowBits[row_idx]->colour_depth; - //Serial.printf(" from pixel %d, ", x_pixel); + int x_pixel = fb->rowBits[row_idx]->width * fb->rowBits[row_idx]->colour_depth; + // Serial.printf(" from pixel %d, ", x_pixel); // fill all x_pixels except colour_index[0] (LSB) ones, this also clears all colour data to 0's black - do { + do + { --x_pixel; - - if ( m_cfg.driver == HUB75_I2S_CFG::SM5266P) { - // modifications here for row shift register type SM5266P + + if (m_cfg.driver == HUB75_I2S_CFG::SM5266P) + { + // modifications here for row shift register type SM5266P // https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/164 - row[x_pixel] = abcde & (0x18 << BITS_ADDR_OFFSET); // mask out the bottom 3 bits which are the clk di bk inputs - } else { + row[x_pixel] = abcde & (0x18 << BITS_ADDR_OFFSET); // mask out the bottom 3 bits which are the clk di bk inputs + } + else if (m_cfg.driver == HUB75_I2S_CFG::DP3246_SM5368) + { + row[ESP32_TX_FIFO_POSITION_ADJUST(x_pixel)] = 0x0000; + } + else + { row[ESP32_TX_FIFO_POSITION_ADJUST(x_pixel)] = abcde; } - // ESP_LOGI("", "x pixel 1: %d", x_pixel); - } while(x_pixel!=dma_buff.rowBits[row_idx]->width && x_pixel); + + } while (x_pixel != fb->rowBits[row_idx]->width && x_pixel); // colour_index[0] (LSB) x_pixels must be "marked" with a previous's row address, 'cause it is used to display // previous row while we pump in LSB's for a new row - abcde = ((ESP32_I2S_DMA_STORAGE_TYPE)row_idx-1) << BITS_ADDR_OFFSET; - do { + abcde = ((ESP32_I2S_DMA_STORAGE_TYPE)row_idx - 1) << BITS_ADDR_OFFSET; + do + { --x_pixel; - - if ( m_cfg.driver == HUB75_I2S_CFG::SM5266P) { - // modifications here for row shift register type SM5266P + + if (m_cfg.driver == HUB75_I2S_CFG::SM5266P) + { + // modifications here for row shift register type SM5266P // https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/164 - row[x_pixel] = abcde & (0x18 << BITS_ADDR_OFFSET); // mask out the bottom 3 bits which are the clk di bk inputs - } else { + row[x_pixel] = abcde & (0x18 << BITS_ADDR_OFFSET); // mask out the bottom 3 bits which are the clk di bk inputs + } + else if (m_cfg.driver == HUB75_I2S_CFG::DP3246_SM5368) + { + row[ESP32_TX_FIFO_POSITION_ADJUST(x_pixel)] = 0x0000; + } + else + { row[ESP32_TX_FIFO_POSITION_ADJUST(x_pixel)] = abcde; - } - //row[x_pixel] = abcde; - // ESP_LOGI("", "x pixel 2: %d", x_pixel); - } while(x_pixel); - - - // modifications here for row shift register type SM5266P - // https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/164 - if ( m_cfg.driver == HUB75_I2S_CFG::SM5266P) { - uint16_t serialCount; - uint16_t latch; - x_pixel = dma_buff.rowBits[row_idx]->width - 16; // come back 8*2 pixels to allow for 8 writes - serialCount = 8; - do{ - serialCount--; - latch = row[x_pixel] | (((((ESP32_I2S_DMA_STORAGE_TYPE)row_idx) % 8) == serialCount) << 1) << BITS_ADDR_OFFSET; // data on 'B' - row[x_pixel++] = latch| (0x05<< BITS_ADDR_OFFSET); // clock high on 'A'and BK high for update - row[x_pixel++] = latch| (0x04<< BITS_ADDR_OFFSET); // clock low on 'A'and BK high for update - } while (serialCount); - } // end SM5266P - + } + + } while (x_pixel); + + // modifications here for row shift register type SM5266P + // https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/164 + if (m_cfg.driver == HUB75_I2S_CFG::SM5266P) + { + uint16_t serialCount; + uint16_t latch; + x_pixel = fb->rowBits[row_idx]->width - 16; // come back 8*2 pixels to allow for 8 writes + serialCount = 8; + do + { + serialCount--; + latch = row[x_pixel] | (((((ESP32_I2S_DMA_STORAGE_TYPE)row_idx) % 8) == serialCount) << 1) << BITS_ADDR_OFFSET; // data on 'B' + row[x_pixel++] = latch | (0x05 << BITS_ADDR_OFFSET); // clock high on 'A'and BK high for update + row[x_pixel++] = latch | (0x04 << BITS_ADDR_OFFSET); // clock low on 'A'and BK high for update + } while (serialCount); + } // end SM5266P + + // row selection for SM5368 shift regs with ABC-only addressing. A is row clk, B is BK and C is row data + if (m_cfg.driver == HUB75_I2S_CFG::DP3246_SM5368) + { + x_pixel = fb->rowBits[row_idx]->width - 1; // last pixel in first block) + uint16_t c = (row_idx == 0) ? BIT_C : 0x0000; // set row data (C) when row==0, then push through shift regs for all other rows + row[ESP32_TX_FIFO_POSITION_ADJUST(x_pixel - 1)] |= c; // set row data + row[ESP32_TX_FIFO_POSITION_ADJUST(x_pixel + 0)] |= c | BIT_A | BIT_B; // set row clk and bk, carry row data + } // end DP3246_SM5368 // let's set LAT/OE control bits for specific pixels in each colour_index subrows // Need to consider the original ESP32's (WROOM) DMA TX FIFO reordering of bytes... - uint8_t colouridx = dma_buff.rowBits[row_idx]->colour_depth; - do { + uint8_t colouridx = fb->rowBits[row_idx]->colour_depth; + do + { --colouridx; // switch pointer to a row for a specific colour index - row = dma_buff.rowBits[row_idx]->getDataPtr(colouridx, _buff_id); + row = fb->rowBits[row_idx]->getDataPtr(colouridx, -1); - /* - #if defined(ESP32_THE_ORIG) - // We need to update the correct uint16_t in the rowBitStruct array, that gets sent out in parallel - // 16 bit parallel mode - Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering - // Irrelevant for ESP32-S2 the way the FIFO ordering works is different - refer to page 679 of S2 technical reference manual - row[dma_buff.rowBits[row_idx]->width - 2] |= BIT_LAT; // -2 in the DMA array is actually -1 when it's reordered by TX FIFO - #else - // -1 works better on ESP32-S2 ? Because bytes get sent out in order... - row[dma_buff.rowBits[row_idx]->width - 1] |= BIT_LAT; // -1 pixel to compensate array index starting at 0 - #endif - */ - row[ESP32_TX_FIFO_POSITION_ADJUST(dma_buff.rowBits[row_idx]->width - 1)] |= BIT_LAT; // -1 pixel to compensate array index starting at 0 - - //ESP32_TX_FIFO_POSITION_ADJUST(dma_buff.rowBits[row_idx]->width - 1) - - // need to disable OE before/after latch to hide row transition - // Should be one clock or more before latch, otherwise can get ghosting - uint8_t _blank = m_cfg.latch_blanking; - do { - --_blank; - /* - #if defined(ESP32_THE_ORIG) - // Original ESP32 WROOM FIFO Ordering Sucks - uint8_t _blank_row_tx_fifo_tmp = 0 + _blank; - (_blank_row_tx_fifo_tmp & 1U) ? --_blank_row_tx_fifo_tmp : ++_blank_row_tx_fifo_tmp; - row[_blank_row_tx_fifo_tmp] |= BIT_OE; - - _blank_row_tx_fifo_tmp = dma_buff.rowBits[row_idx]->width - _blank - 1; // (LAT pulse is (width-2) -1 pixel to compensate array index starting at 0 - (_blank_row_tx_fifo_tmp & 1U) ? --_blank_row_tx_fifo_tmp : ++_blank_row_tx_fifo_tmp; - row[_blank_row_tx_fifo_tmp] |= BIT_OE; - #else - row[0 + _blank] |= BIT_OE; - row[dma_buff.rowBits[row_idx]->width - _blank - 1 ] |= BIT_OE; // (LAT pulse is (width-2) -1 pixel to compensate array index starting at 0 - #endif - */ - - row[ESP32_TX_FIFO_POSITION_ADJUST(0 + _blank)] |= BIT_OE; // disable output - row[ESP32_TX_FIFO_POSITION_ADJUST(dma_buff.rowBits[row_idx]->width - 1)] |= BIT_OE; // disable output - row[ESP32_TX_FIFO_POSITION_ADJUST(dma_buff.rowBits[row_idx]->width - _blank - 1)] |= BIT_OE; // (LAT pulse is (width-2) -1 pixel to compensate array index starting at 0 - - - } while (_blank); - - } while(colouridx); - - - - #if defined(SPIRAM_DMA_BUFFER) - Cache_WriteBack_Addr((uint32_t)row, sizeof(ESP32_I2S_DMA_STORAGE_TYPE) * ((dma_buff.rowBits[row_idx]->width * dma_buff.rowBits[row_idx]->colour_depth)-1)) ; - #endif - - - } while(row_idx); -} - -/** - * @brief - reset OE bits in DMA buffer in a way to control brightness - * @param brt - brightness level from 0 to row_width - * @param _buff_id - buffer id to control - */ -/* -// Depreciated -void MatrixPanel_I2S_DMA::brtCtrlOE(int brt, const bool _buff_id){ - - if (!initialized) - return; - - if (brt > PIXELS_PER_ROW - (MAX_LAT_BLANKING + 2)) // can't control values larger than (row_width - latch_blanking) to avoid ongoing issues being raised about brightness and ghosting. - brt = PIXELS_PER_ROW - (MAX_LAT_BLANKING + 2); // +2 for a bit of buffer... - - if (brt < 0) - brt = 0; - - // start with iterating all rows in dma_buff structure - int row_idx = dma_buff.rowBits.size(); - do { - --row_idx; - - // let's set OE control bits for specific pixels in each colour_index subrows - uint8_t colouridx = dma_buff.rowBits[row_idx]->colour_depth; - do { - --colouridx; - - // switch pointer to a row for a specific colour index - ESP32_I2S_DMA_STORAGE_TYPE* row = dma_buff.rowBits[row_idx]->getDataPtr(colouridx, _buff_id); - - int x_coord = dma_buff.rowBits[row_idx]->width; - do { - --x_coord; - - // clear OE bit for all other pixels - row[ESP32_TX_FIFO_POSITION_ADJUST(x_coord)] &= BITMASK_OE_CLEAR; - - // Brightness control via OE toggle - disable matrix output at specified x_coord - if((colouridx > lsbMsbTransitionBit || !colouridx) && ((x_coord) >= brt)){ - row[ESP32_TX_FIFO_POSITION_ADJUST(x_coord)] |= BIT_OE; // Disable output after this point. - continue; - } - // special case for the bits *after* LSB through (lsbMsbTransitionBit) - OE is output after data is shifted, so need to set OE to fractional brightness - if(colouridx && colouridx <= lsbMsbTransitionBit) { - // divide brightness in half for each bit below lsbMsbTransitionBit - int lsbBrightness = brt >> (lsbMsbTransitionBit - colouridx + 1); - if((x_coord) >= lsbBrightness) { - row[ESP32_TX_FIFO_POSITION_ADJUST(x_coord)] |= BIT_OE; // Disable output after this point. - continue; - } - } - - - } while(x_coord); - - // need to disable OE before/after latch to hide row transition - // Should be one clock or more before latch, otherwise can get ghosting - uint8_t _blank = m_cfg.latch_blanking; - do { - --_blank; - - - row[ESP32_TX_FIFO_POSITION_ADJUST(0 + _blank)] |= BIT_OE; + // DP3246 needs the latch high for 3 clock cycles, so start 2 cycles earlier + if (m_cfg.driver == HUB75_I2S_CFG::DP3246_SM5368) + { + row[ESP32_TX_FIFO_POSITION_ADJUST(fb->rowBits[row_idx]->width - 3)] |= BIT_LAT; // DP3246 needs 3 clock cycle latch + row[ESP32_TX_FIFO_POSITION_ADJUST(fb->rowBits[row_idx]->width - 2)] |= BIT_LAT; // DP3246 needs 3 clock cycle latch + } // DP3246_SM5368 + row[ESP32_TX_FIFO_POSITION_ADJUST(fb->rowBits[row_idx]->width - 1)] |= BIT_LAT; // -1 pixel to compensate array index starting at 0 + // ESP32_TX_FIFO_POSITION_ADJUST(dma_buff.rowBits[row_idx]->width - 1) + + // need to disable OE before/after latch to hide row transition + // Should be one clock or more before latch, otherwise can get ghosting + uint8_t _blank = m_cfg.latch_blanking; + do + { + --_blank; + + row[ESP32_TX_FIFO_POSITION_ADJUST(0 + _blank)] |= BIT_OE; // disable output + row[ESP32_TX_FIFO_POSITION_ADJUST(fb->rowBits[row_idx]->width - 1)] |= BIT_OE; // disable output + row[ESP32_TX_FIFO_POSITION_ADJUST(fb->rowBits[row_idx]->width - _blank - 1)] |= BIT_OE; // (LAT pulse is (width-2) -1 pixel to compensate array index starting at 0 - //row[0 + _blank] |= BIT_OE; - // no need, has been done already - //row[dma_buff.rowBits[row_idx]->width - _blank - 3 ] |= BIT_OE; // (LAT pulse is (width-2) -1 pixel to compensate array index starting at 0 } while (_blank); - } while(colouridx); + } while (colouridx); - // switch pointer to a row for a specific colour index - #if defined(SPIRAM_DMA_BUFFER) - ESP32_I2S_DMA_STORAGE_TYPE* row_hack = dma_buff.rowBits[row_idx]->getDataPtr(colouridx, _buff_id); - Cache_WriteBack_Addr((uint32_t)row_hack, sizeof(ESP32_I2S_DMA_STORAGE_TYPE) * ((dma_buff.rowBits[row_idx]->width * dma_buff.rowBits[row_idx]->colour_depth)-1)) ; - #endif +#if defined(SPIRAM_DMA_BUFFER) + Cache_WriteBack_Addr((uint32_t)row, fb->rowBits[row_idx]->getColorDepthSize()); +#endif - - } while(row_idx); + } while (row_idx); } - */ - /** * @brief - reset OE bits in DMA buffer in a way to control brightness * @param brt - brightness level from 0 to 255 - NOT MATRIX_WIDTH * @param _buff_id - buffer id to control */ -void MatrixPanel_I2S_DMA::brtCtrlOEv2(uint8_t brt, const int _buff_id) { - +void MatrixPanel_I2S_DMA::brtCtrlOEv2(uint8_t brt, const int _buff_id) +{ + if (!initialized) return; - uint8_t _blank = m_cfg.latch_blanking; // don't want to inadvertantly blast over this - uint8_t _depth = dma_buff.rowBits[0]->colour_depth; - uint16_t _width = dma_buff.rowBits[0]->width; + frameStruct *fb = &frame_buffer[_buff_id]; + + uint8_t _blank = m_cfg.latch_blanking; // don't want to inadvertantly blast over this + uint8_t _depth = fb->rowBits[0]->colour_depth; + uint16_t _width = fb->rowBits[0]->width; // start with iterating all rows in dma_buff structure - int row_idx = dma_buff.rowBits.size(); - do { + int row_idx = fb->rowBits.size(); + do + { --row_idx; // let's set OE control bits for specific pixels in each color_index subrows uint8_t colouridx = _depth; - do { + do + { --colouridx; - - char bitplane = ( 2 * _depth - colouridx ) % _depth; - char bitshift = (_depth - lsbMsbTransitionBit - 1) >> 1; - - char rightshift = std::max( bitplane - bitshift - 2, 0 ); + + char bitplane = (2 * _depth - colouridx) % _depth; + char bitshift = (_depth - lsbMsbTransitionBit - 1) >> 1; + + char rightshift = std::max(bitplane - bitshift - 2, 0); // calculate the OE disable period by brightness, and also blanking - int brightness_in_x_pixels = ( ( _width - _blank ) * brt ) >> ( 7 + rightshift ); - brightness_in_x_pixels = ( brightness_in_x_pixels >> 1 ) | ( brightness_in_x_pixels & 1 ); - + int brightness_in_x_pixels = ((_width - _blank) * brt) >> (7 + rightshift); + brightness_in_x_pixels = (brightness_in_x_pixels >> 1) | (brightness_in_x_pixels & 1); + // switch pointer to a row for a specific color index - ESP32_I2S_DMA_STORAGE_TYPE* row = dma_buff.rowBits[row_idx]->getDataPtr(colouridx, _buff_id); + ESP32_I2S_DMA_STORAGE_TYPE *row = fb->rowBits[row_idx]->getDataPtr(colouridx, _buff_id); // define range of Output Enable on the center of the row - int x_coord_max = ( _width + brightness_in_x_pixels + 1 ) >> 1; - int x_coord_min = ( _width - brightness_in_x_pixels + 0 ) >> 1; + int x_coord_max = (_width + brightness_in_x_pixels + 1) >> 1; + int x_coord_min = (_width - brightness_in_x_pixels + 0) >> 1; int x_coord = _width; - do { + do + { --x_coord; - + // (the check is already including "blanking" ) - if (x_coord >= x_coord_min && x_coord < x_coord_max) - { - row[ESP32_TX_FIFO_POSITION_ADJUST(x_coord)] &= BITMASK_OE_CLEAR; - } - else - { - row[ESP32_TX_FIFO_POSITION_ADJUST(x_coord)] |= BIT_OE; // Disable output after this point. - } - - - // Note: Old code below from 'brtCtrlOE' - - /* - // clear OE bit for all other pixels (that is, turn on output) - row[ESP32_TX_FIFO_POSITION_ADJUST(x_coord)] &= BITMASK_OE_CLEAR; - - - // Brightness control via OE toggle - disable matrix output at specified x_coord - if((colouridx > lsbMsbTransitionBit || !colouridx) && ((x_coord) >= brt)){ + if (x_coord >= x_coord_min && x_coord < x_coord_max) + { + row[ESP32_TX_FIFO_POSITION_ADJUST(x_coord)] &= BITMASK_OE_CLEAR; + } + else + { row[ESP32_TX_FIFO_POSITION_ADJUST(x_coord)] |= BIT_OE; // Disable output after this point. - continue; } - // special case for the bits *after* LSB through (lsbMsbTransitionBit) - OE is output after data is shifted, so need to set OE to fractional brightness - if(colouridx && colouridx <= lsbMsbTransitionBit) { - // divide brightness in half for each bit below lsbMsbTransitionBit - int lsbBrightness = brt >> (lsbMsbTransitionBit - colouridx + 1); - if((x_coord) >= lsbBrightness) { - row[ESP32_TX_FIFO_POSITION_ADJUST(x_coord)] |= BIT_OE; // Disable output after this point. - continue; - } - } - */ - - } while(x_coord); - - } while(colouridx); - // switch pointer to a row for a specific colour index - #if defined(SPIRAM_DMA_BUFFER) - ESP32_I2S_DMA_STORAGE_TYPE* row_hack = dma_buff.rowBits[row_idx]->getDataPtr(colouridx, _buff_id); - Cache_WriteBack_Addr((uint32_t)row_hack, sizeof(ESP32_I2S_DMA_STORAGE_TYPE) * ((dma_buff.rowBits[row_idx]->width * dma_buff.rowBits[row_idx]->colour_depth)-1)) ; - #endif - } while(row_idx); - + } while (x_coord); + + } while (colouridx); + +// switch pointer to a row for a specific colour index +#if defined(SPIRAM_DMA_BUFFER) + ESP32_I2S_DMA_STORAGE_TYPE *row_hack = fb->rowBits[row_idx]->getDataPtr(0, _buff_id); + //Cache_WriteBack_Addr((uint32_t)row_hack, sizeof(ESP32_I2S_DMA_STORAGE_TYPE) * ((fb->rowBits[row_idx]->width * fb->rowBits[row_idx]->colour_depth) - 1)); + Cache_WriteBack_Addr((uint32_t)row_hack, fb->rowBits[row_idx]->getColorDepthSize()); +#endif + } while (row_idx); } - /* * overload for compatibility */ - -bool MatrixPanel_I2S_DMA::begin(int r1, int g1, int b1, int r2, int g2, int b2, int a, int b, int c, int d, int e, int lat, int oe, int clk) { - if(initialized) return true; + +bool MatrixPanel_I2S_DMA::begin(int r1, int g1, int b1, int r2, int g2, int b2, int a, int b, int c, int d, int e, int lat, int oe, int clk) +{ + if (initialized) + return true; // RGB - m_cfg.gpio.r1 = r1; m_cfg.gpio.g1 = g1; m_cfg.gpio.b1 = b1; - m_cfg.gpio.r2 = r2; m_cfg.gpio.g2 = g2; m_cfg.gpio.b2 = b2; - + m_cfg.gpio.r1 = r1; + m_cfg.gpio.g1 = g1; + m_cfg.gpio.b1 = b1; + m_cfg.gpio.r2 = r2; + m_cfg.gpio.g2 = g2; + m_cfg.gpio.b2 = b2; + // Line Select - m_cfg.gpio.a = a; m_cfg.gpio.b = b; m_cfg.gpio.c = c; - m_cfg.gpio.d = d; m_cfg.gpio.e = e; - + m_cfg.gpio.a = a; + m_cfg.gpio.b = b; + m_cfg.gpio.c = c; + m_cfg.gpio.d = d; + m_cfg.gpio.e = e; + // Clock & Control - m_cfg.gpio.lat = lat; m_cfg.gpio.oe = oe; m_cfg.gpio.clk = clk; + m_cfg.gpio.lat = lat; + m_cfg.gpio.oe = oe; + m_cfg.gpio.clk = clk; return begin(); } -bool MatrixPanel_I2S_DMA::begin(const HUB75_I2S_CFG& cfg){ - if(initialized) return true; +bool MatrixPanel_I2S_DMA::begin(const HUB75_I2S_CFG &cfg) +{ + if (initialized) + return true; - if(!setCfg(cfg)) return false; + if (!setCfg(cfg)) + return false; return begin(); -} - - +} /** * @brief - Sets how many clock cycles to blank OE before/after LAT signal change @@ -817,7 +737,8 @@ bool MatrixPanel_I2S_DMA::begin(const HUB75_I2S_CFG& cfg){ * Max is MAX_LAT_BLANKING * @returns - new value for m_cfg.latch_blanking */ -uint8_t MatrixPanel_I2S_DMA::setLatBlanking(uint8_t pulses){ +uint8_t MatrixPanel_I2S_DMA::setLatBlanking(uint8_t pulses) +{ if (pulses > MAX_LAT_BLANKING) pulses = MAX_LAT_BLANKING; @@ -825,13 +746,12 @@ uint8_t MatrixPanel_I2S_DMA::setLatBlanking(uint8_t pulses){ pulses = DEFAULT_LAT_BLANKING; m_cfg.latch_blanking = pulses; - + // remove brightness var for now. - //setPanelBrightness(brightness); // set brightness to reset OE bits to the values matching new LAT blanking setting + // setPanelBrightness(brightness); // set brightness to reset OE bits to the values matching new LAT blanking setting return m_cfg.latch_blanking; } - #ifndef NO_FAST_FUNCTIONS /** * @brief - update DMA buff drawing horizontal line at specified coordinates @@ -840,94 +760,96 @@ uint8_t MatrixPanel_I2S_DMA::setLatBlanking(uint8_t pulses){ * @param l - line length * @param r,g,b, - RGB888 colour */ -void MatrixPanel_I2S_DMA::hlineDMA(int16_t x_coord, int16_t y_coord, int16_t l, uint8_t red, uint8_t green, uint8_t blue){ - if ( !initialized ) +void MatrixPanel_I2S_DMA::hlineDMA(int16_t x_coord, int16_t y_coord, int16_t l, uint8_t red, uint8_t green, uint8_t blue) +{ + if (!initialized) return; - if ( (x_coord + l) < 1 || y_coord < 0 || l < 1 || x_coord >= PIXELS_PER_ROW || y_coord >= m_cfg.mx_height) + if ((x_coord + l) < 1 || y_coord < 0 || l < 1 || x_coord >= PIXELS_PER_ROW || y_coord >= m_cfg.mx_height) return; - l = x_coord < 0 ? l+x_coord : l; + l = x_coord < 0 ? l + x_coord : l; x_coord = x_coord < 0 ? 0 : x_coord; + l = ((x_coord + l) >= PIXELS_PER_ROW) ? (PIXELS_PER_ROW - x_coord) : l; - l = ( (x_coord + l) >= PIXELS_PER_ROW ) ? (PIXELS_PER_ROW - x_coord):l; - - //if (x_coord+l > PIXELS_PER_ROW) -// l = PIXELS_PER_ROW - x_coord + 1; // reset width to end of row + // if (x_coord+l > PIXELS_PER_ROW) + // l = PIXELS_PER_ROW - x_coord + 1; // reset width to end of row /* LED Brightness Compensation */ -uint16_t red16, green16, blue16; + uint16_t red16, green16, blue16; #ifndef NO_CIE1931 - red16 = lumConvTab[red]; - green16 = lumConvTab[green]; - blue16 = lumConvTab[blue]; + red16 = lumConvTab[red]; + green16 = lumConvTab[green]; + blue16 = lumConvTab[blue]; #else - red16 = red << 8; - green16 = green << 8; - blue16 = blue << 8; + red16 = red << 8; + green16 = green << 8; + blue16 = blue << 8; #endif uint16_t _colourbitclear = BITMASK_RGB1_CLEAR, _colourbitoffset = 0; - if (y_coord >= ROWS_PER_FRAME){ // if we are drawing to the bottom part of the panel + if (y_coord >= ROWS_PER_FRAME) + { // if we are drawing to the bottom part of the panel _colourbitoffset = BITS_RGB2_OFFSET; - _colourbitclear = BITMASK_RGB2_CLEAR; + _colourbitclear = BITMASK_RGB2_CLEAR; y_coord -= ROWS_PER_FRAME; } // Iterating through colour depth bits (8 iterations) uint8_t colour_depth_idx = m_cfg.getPixelColorDepthBits(); - do { + do + { --colour_depth_idx; // let's precalculate RGB1 and RGB2 bits than flood it over the entire DMA buffer uint16_t RGB_output_bits = 0; -// uint8_t mask = (1 << colour_depth_idx COLOR_DEPTH_LESS_THAN_8BIT_ADJUST); + // uint8_t mask = (1 << colour_depth_idx COLOR_DEPTH_LESS_THAN_8BIT_ADJUST); // #if PIXEL_COLOR_DEPTH_BITS < 8 // uint8_t mask = (1 << (colour_depth_idx+MASK_OFFSET)); // expect 24 bit colour (8 bits per RGB subpixel) // #else // uint8_t mask = (1 << (colour_depth_idx)); // expect 24 bit colour (8 bits per RGB subpixel) - // #endif + // #endif uint16_t mask = PIXEL_COLOR_MASK_BIT(colour_depth_idx, MASK_OFFSET); /* Per the .h file, the order of the output RGB bits is: - * BIT_B2, BIT_G2, BIT_R2, BIT_B1, BIT_G1, BIT_R1 */ - RGB_output_bits |= (bool)(blue16 & mask); // --B + * BIT_B2, BIT_G2, BIT_R2, BIT_B1, BIT_G1, BIT_R1 */ + RGB_output_bits |= (bool)(blue16 & mask); // --B RGB_output_bits <<= 1; - RGB_output_bits |= (bool)(green16 & mask); // -BG + RGB_output_bits |= (bool)(green16 & mask); // -BG RGB_output_bits <<= 1; - RGB_output_bits |= (bool)(red16 & mask); // BGR - RGB_output_bits <<= _colourbitoffset; // shift color bits to the required position + RGB_output_bits |= (bool)(red16 & mask); // BGR + RGB_output_bits <<= _colourbitoffset; // shift color bits to the required position // Get the contents at this address, // it would represent a vector pointing to the full row of pixels for the specified colour depth bit at Y coordinate - ESP32_I2S_DMA_STORAGE_TYPE *p = dma_buff.rowBits[y_coord]->getDataPtr(colour_depth_idx, back_buffer_id); + ESP32_I2S_DMA_STORAGE_TYPE *p = fb->rowBits[y_coord]->getDataPtr(colour_depth_idx, back_buffer_id); // inlined version works slower here, dunno why :( // ESP32_I2S_DMA_STORAGE_TYPE *p = getRowDataPtr(y_coord, colour_depth_idx, back_buffer_id); int16_t _l = l; - do { // iterate pixels in a row - int16_t _x = x_coord + --_l; + do + { // iterate pixels in a row + int16_t _x = x_coord + --_l; - /* - #if defined(ESP32_THE_ORIG) - // Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering - uint16_t &v = p[_x & 1U ? --_x : ++_x]; - #else - // ESP 32 doesn't need byte flipping for TX FIFO. - uint16_t &v = p[_x]; - #endif - */ - uint16_t &v = p[ESP32_TX_FIFO_POSITION_ADJUST(_x)]; + /* + #if defined(ESP32_THE_ORIG) + // Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering + uint16_t &v = p[_x & 1U ? --_x : ++_x]; + #else + // ESP 32 doesn't need byte flipping for TX FIFO. + uint16_t &v = p[_x]; + #endif + */ + uint16_t &v = p[ESP32_TX_FIFO_POSITION_ADJUST(_x)]; - v &= _colourbitclear; // reset colour bits - v |= RGB_output_bits; // set new colour bits - } while(_l); // iterate pixels in a row - } while(colour_depth_idx); // end of colour depth loop (8) + v &= _colourbitclear; // reset colour bits + v |= RGB_output_bits; // set new colour bits + } while (_l); // iterate pixels in a row + } while (colour_depth_idx); // end of colour depth loop (8) } // hlineDMA() - /** * @brief - update DMA buff drawing vertical line at specified coordinates * @param x_coord - line start coordinate x @@ -935,87 +857,90 @@ uint16_t red16, green16, blue16; * @param l - line length * @param r,g,b, - RGB888 colour */ -void MatrixPanel_I2S_DMA::vlineDMA(int16_t x_coord, int16_t y_coord, int16_t l, uint8_t red, uint8_t green, uint8_t blue){ - if ( !initialized ) +void MatrixPanel_I2S_DMA::vlineDMA(int16_t x_coord, int16_t y_coord, int16_t l, uint8_t red, uint8_t green, uint8_t blue) +{ + if (!initialized) return; - if ( x_coord < 0 || (y_coord + l) < 1 || l < 1 || x_coord >= PIXELS_PER_ROW || y_coord >= m_cfg.mx_height) + if (x_coord < 0 || (y_coord + l) < 1 || l < 1 || x_coord >= PIXELS_PER_ROW || y_coord >= m_cfg.mx_height) return; - l = y_coord < 0 ? l+y_coord : l; + l = y_coord < 0 ? l + y_coord : l; y_coord = y_coord < 0 ? 0 : y_coord; // check for a length that goes beyond the height of the screen! Array out of bounds dma memory changes = screwed output #163 - l = ( (y_coord + l) >= m_cfg.mx_height ) ? (m_cfg.mx_height - y_coord):l; - //if (y_coord + l > m_cfg.mx_height) + l = ((y_coord + l) >= m_cfg.mx_height) ? (m_cfg.mx_height - y_coord) : l; + // if (y_coord + l > m_cfg.mx_height) /// l = m_cfg.mx_height - y_coord + 1; // reset width to end of col /* LED Brightness Compensation */ -uint16_t red16, green16, blue16; + uint16_t red16, green16, blue16; #ifndef NO_CIE1931 - red16 = lumConvTab[red]; - green16 = lumConvTab[green]; - blue16 = lumConvTab[blue]; + red16 = lumConvTab[red]; + green16 = lumConvTab[green]; + blue16 = lumConvTab[blue]; #else - red16 = red << 8; - green16 = green << 8; - blue16 = blue << 8; + red16 = red << 8; + green16 = green << 8; + blue16 = blue << 8; #endif -/* -#if defined(ESP32_THE_ORIG) - // Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering - x_coord & 1U ? --x_coord : ++x_coord; -#endif -*/ + /* + #if defined(ESP32_THE_ORIG) + // Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering + x_coord & 1U ? --x_coord : ++x_coord; + #endif + */ x_coord = ESP32_TX_FIFO_POSITION_ADJUST(x_coord); uint8_t colour_depth_idx = m_cfg.getPixelColorDepthBits(); - do { // Iterating through colour depth bits (8 iterations) + do + { // Iterating through colour depth bits (8 iterations) --colour_depth_idx; // let's precalculate RGB1 and RGB2 bits than flood it over the entire DMA buffer -// uint8_t mask = (1 << colour_depth_idx COLOR_DEPTH_LESS_THAN_8BIT_ADJUST); + // uint8_t mask = (1 << colour_depth_idx COLOR_DEPTH_LESS_THAN_8BIT_ADJUST); // #if PIXEL_COLOR_DEPTH_BITS < 8 // uint8_t mask = (1 << (colour_depth_idx+MASK_OFFSET)); // expect 24 bit colour (8 bits per RGB subpixel) // #else // uint8_t mask = (1 << (colour_depth_idx)); // expect 24 bit colour (8 bits per RGB subpixel) - // #endif + // #endif uint16_t mask = PIXEL_COLOR_MASK_BIT(colour_depth_idx, MASK_OFFSET); uint16_t RGB_output_bits = 0; /* Per the .h file, the order of the output RGB bits is: - * BIT_B2, BIT_G2, BIT_R2, BIT_B1, BIT_G1, BIT_R1 */ - RGB_output_bits |= (bool)(blue16 & mask); // --B + * BIT_B2, BIT_G2, BIT_R2, BIT_B1, BIT_G1, BIT_R1 */ + RGB_output_bits |= (bool)(blue16 & mask); // --B RGB_output_bits <<= 1; - RGB_output_bits |= (bool)(green16 & mask); // -BG + RGB_output_bits |= (bool)(green16 & mask); // -BG RGB_output_bits <<= 1; - RGB_output_bits |= (bool)(red16 & mask); // BGR + RGB_output_bits |= (bool)(red16 & mask); // BGR int16_t _l = 0, _y = y_coord; uint16_t _colourbitclear = BITMASK_RGB1_CLEAR; - do { // iterate pixels in a column + do + { // iterate pixels in a column - if (_y >= ROWS_PER_FRAME){ // if y-coord overlapped bottom-half panel + if (_y >= ROWS_PER_FRAME) + { // if y-coord overlapped bottom-half panel _y -= ROWS_PER_FRAME; - _colourbitclear = BITMASK_RGB2_CLEAR; + _colourbitclear = BITMASK_RGB2_CLEAR; RGB_output_bits <<= BITS_RGB2_OFFSET; } // Get the contents at this address, // it would represent a vector pointing to the full row of pixels for the specified colour depth bit at Y coordinate - //ESP32_I2S_DMA_STORAGE_TYPE *p = getRowDataPtr(_y, colour_depth_idx, back_buffer_id); - ESP32_I2S_DMA_STORAGE_TYPE *p = dma_buff.rowBits[_y]->getDataPtr(colour_depth_idx, back_buffer_id); + // ESP32_I2S_DMA_STORAGE_TYPE *p = getRowDataPtr(_y, colour_depth_idx, back_buffer_id); + ESP32_I2S_DMA_STORAGE_TYPE *p = fb->rowBits[_y]->getDataPtr(colour_depth_idx, back_buffer_id); - p[x_coord] &= _colourbitclear; // reset RGB bits - p[x_coord] |= RGB_output_bits; // set new RGB bits + p[x_coord] &= _colourbitclear; // reset RGB bits + p[x_coord] |= RGB_output_bits; // set new RGB bits ++_y; - } while(++_l!=l); // iterate pixels in a col - } while(colour_depth_idx); // end of colour depth loop (8) + } while (++_l != l); // iterate pixels in a col + } while (colour_depth_idx); // end of colour depth loop (8) } // vlineDMA() - /** * @brief - update DMA buff drawing a rectangular at specified coordinates * this works much faster than multiple consecutive per-pixel calls to updateMatrixDMABuffer() @@ -1025,23 +950,29 @@ uint16_t red16, green16, blue16; * @param uint8_t g - RGB888 colour * @param uint8_t b - RGB888 colour */ -void MatrixPanel_I2S_DMA::fillRectDMA(int16_t x, int16_t y, int16_t w, int16_t h, uint8_t r, uint8_t g, uint8_t b){ +void MatrixPanel_I2S_DMA::fillRectDMA(int16_t x, int16_t y, int16_t w, int16_t h, uint8_t r, uint8_t g, uint8_t b) +{ // h-lines are >2 times faster than v-lines // so will use it only for tall rects with h >2w - if (h>2*w){ + if (h > 2 * w) + { // draw using v-lines - do { + do + { --w; - vlineDMA(x+w, y, h, r,g,b); - } while(w); - } else { + vlineDMA(x + w, y, h, r, g, b); + } while (w); + } + else + { // draw using h-lines - do { + do + { --h; - hlineDMA(x, y+h, w, r,g,b); - } while(h); + hlineDMA(x, y + h, w, r, g, b); + } while (h); } } -#endif // NO_FAST_FUNCTIONS +#endif // NO_FAST_FUNCTIONS diff --git a/src/ESP32-HUB75-MatrixPanel-I2S-DMA.h b/src/ESP32-HUB75-MatrixPanel-I2S-DMA.h index 863a1b0..4a6e843 100644 --- a/src/ESP32-HUB75-MatrixPanel-I2S-DMA.h +++ b/src/ESP32-HUB75-MatrixPanel-I2S-DMA.h @@ -8,14 +8,14 @@ #include #include "esp_attr.h" -//#include +// #include #include "platforms/platform_detect.hpp" #ifdef USE_GFX_ROOT - #include - #include "GFX.h" // Adafruit GFX core class -> https://github.com/mrfaptastic/GFX_Root +#include +#include "GFX.h" // Adafruit GFX core class -> https://github.com/mrfaptastic/GFX_Root #elif !defined NO_GFX - #include "Adafruit_GFX.h" // Adafruit class with all the other stuff +#include "Adafruit_GFX.h" // Adafruit class with all the other stuff #endif /******************************************************************************************* @@ -29,9 +29,9 @@ // #define NO_CIE1931 -/* Physical / Chained HUB75(s) RGB pixel WIDTH and HEIGHT. +/* Physical / Chained HUB75(s) RGB pixel WIDTH and HEIGHT. * - * This library has been tested with a 64x32 and 64x64 RGB panels. + * This library has been tested with a 64x32 and 64x64 RGB panels. * If you want to chain two or more of these horizontally to make a 128x32 panel * you can do so with the cable and then set the CHAIN_LENGTH to '2'. * @@ -41,19 +41,18 @@ * */ #ifndef MATRIX_WIDTH - #define MATRIX_WIDTH 64 // Single panel of 64 pixel width +#define MATRIX_WIDTH 64 // Single panel of 64 pixel width #endif #ifndef MATRIX_HEIGHT - #define MATRIX_HEIGHT 32 // CHANGE THIS VALUE to 64 IF USING 64px HIGH panel(s) with E PIN +#define MATRIX_HEIGHT 32 // CHANGE THIS VALUE to 64 IF USING 64px HIGH panel(s) with E PIN #endif #ifndef CHAIN_LENGTH - #define CHAIN_LENGTH 1 // Number of modules chained together, i.e. 4 panels chained result in virtualmatrix 64x4=256 px long +#define CHAIN_LENGTH 1 // Number of modules chained together, i.e. 4 panels chained result in virtualmatrix 64x4=256 px long #endif - -// Interesting Fact: We end up using a uint16_t to send data in parallel to the HUB75... but +// Interesting Fact: We end up using a uint16_t to send data in parallel to the HUB75... but // given we only map to 14 physical output wires/bits, we waste 2 bits. /***************************************************************************************/ @@ -61,163 +60,168 @@ // keeping a check sine it was possibe to set it previously #ifdef MATRIX_ROWS_IN_PARALLEL - #pragma message "You are not supposed to set MATRIX_ROWS_IN_PARALLEL. Setting it back to default." - #undef MATRIX_ROWS_IN_PARALLEL +#pragma message "You are not supposed to set MATRIX_ROWS_IN_PARALLEL. Setting it back to default." +#undef MATRIX_ROWS_IN_PARALLEL #endif -#define MATRIX_ROWS_IN_PARALLEL 2 +#define MATRIX_ROWS_IN_PARALLEL 2 // 8bit per RGB color = 24 bit/per pixel, // can be extended to offer deeper colors, or // might be reduced to save DMA RAM #ifdef PIXEL_COLOUR_DEPTH_BITS - #define PIXEL_COLOR_DEPTH_BITS PIXEL_COLOUR_DEPTH_BITS +#define PIXEL_COLOR_DEPTH_BITS PIXEL_COLOUR_DEPTH_BITS #endif -//support backwarts compatibility +// support backwarts compatibility #ifdef PIXEL_COLOR_DEPTH_BITS - #define PIXEL_COLOR_DEPTH_BITS_DEFAULT PIXEL_COLOR_DEPTH_BITS -#else - #define PIXEL_COLOR_DEPTH_BITS_DEFAULT 8 +#define PIXEL_COLOR_DEPTH_BITS_DEFAULT PIXEL_COLOR_DEPTH_BITS +#else +#define PIXEL_COLOR_DEPTH_BITS_DEFAULT 8 #endif #define PIXEL_COLOR_DEPTH_BITS_MAX 12 /***************************************************************************************/ /* Definitions below should NOT be ever changed without rewriting library logic */ -#define ESP32_I2S_DMA_STORAGE_TYPE uint16_t // DMA output of one uint16_t at a time. -#define CLKS_DURING_LATCH 0 // Not (yet) used. +#define ESP32_I2S_DMA_STORAGE_TYPE uint16_t // DMA output of one uint16_t at a time. +#define CLKS_DURING_LATCH 0 // Not (yet) used. // Panel Upper half RGB (numbering according to order in DMA gpio_bus configuration) #define BITS_RGB1_OFFSET 0 // Start point of RGB_X1 bits -#define BIT_R1 (1<<0) -#define BIT_G1 (1<<1) -#define BIT_B1 (1<<2) +#define BIT_R1 (1 << 0) +#define BIT_G1 (1 << 1) +#define BIT_B1 (1 << 2) // Panel Lower half RGB #define BITS_RGB2_OFFSET 3 // Start point of RGB_X2 bits -#define BIT_R2 (1<<3) -#define BIT_G2 (1<<4) -#define BIT_B2 (1<<5) +#define BIT_R2 (1 << 3) +#define BIT_G2 (1 << 4) +#define BIT_B2 (1 << 5) // Panel Control Signals -#define BIT_LAT (1<<6) -#define BIT_OE (1<<7) +#define BIT_LAT (1 << 6) +#define BIT_OE (1 << 7) // Panel GPIO Pin Addresses (A, B, C, D etc..) -#define BITS_ADDR_OFFSET 8 // Start point of address bits -#define BIT_A (1<<8) -#define BIT_B (1<<9) -#define BIT_C (1<<10) -#define BIT_D (1<<11) -#define BIT_E (1<<12) +#define BITS_ADDR_OFFSET 8 // Start point of address bits +#define BIT_A (1 << 8) +#define BIT_B (1 << 9) +#define BIT_C (1 << 10) +#define BIT_D (1 << 11) +#define BIT_E (1 << 12) // BitMasks are pre-computed based on the above #define's for performance. -#define BITMASK_RGB1_CLEAR (0b1111111111111000) // inverted bitmask for R1G1B1 bit in pixel vector -#define BITMASK_RGB2_CLEAR (0b1111111111000111) // inverted bitmask for R2G2B2 bit in pixel vector -#define BITMASK_RGB12_CLEAR (0b1111111111000000) // inverted bitmask for R1G1B1R2G2B2 bit in pixel vector -#define BITMASK_CTRL_CLEAR (0b1110000000111111) // inverted bitmask for control bits ABCDE,LAT,OE in pixel vector -#define BITMASK_OE_CLEAR (0b1111111101111111) // inverted bitmask for control bit OE in pixel vector +#define BITMASK_RGB1_CLEAR (0b1111111111111000) // inverted bitmask for R1G1B1 bit in pixel vector +#define BITMASK_RGB2_CLEAR (0b1111111111000111) // inverted bitmask for R2G2B2 bit in pixel vector +#define BITMASK_RGB12_CLEAR (0b1111111111000000) // inverted bitmask for R1G1B1R2G2B2 bit in pixel vector +#define BITMASK_CTRL_CLEAR (0b1110000000111111) // inverted bitmask for control bits ABCDE,LAT,OE in pixel vector +#define BITMASK_OE_CLEAR (0b1111111101111111) // inverted bitmask for control bit OE in pixel vector // How many clock cycles to blank OE before/after LAT signal change, default is 2 clocks -#define DEFAULT_LAT_BLANKING 2 +#define DEFAULT_LAT_BLANKING 2 // Max clock cycles to blank OE before/after LAT signal change -#define MAX_LAT_BLANKING 4 +#define MAX_LAT_BLANKING 4 /***************************************************************************************/ /** @brief - Structure holds raw DMA data to drive TWO full rows of pixels spanning through all chained modules * Note: sizeof(data) must be multiple of 32 bits, as ESP32 DMA linked list buffer address pointer must be word-aligned */ -struct rowBitStruct { - const size_t width; - const uint8_t colour_depth; - const bool double_buff; - ESP32_I2S_DMA_STORAGE_TYPE *data; +struct rowBitStruct +{ + const size_t width; + const uint8_t colour_depth; + const bool double_buff; + ESP32_I2S_DMA_STORAGE_TYPE *data; - /** @brief - returns size of row of data vectorfor a SINGLE buff - * size (in bytes) of a vector holding full DMA data for a row of pixels with _dpth colour bits - * a SINGLE buffer only size is accounted, when using double buffers it actually takes twice as much space - * but returned size is for a half of double-buffer - * - * default - returns full data vector size for a SINGLE buff - * - */ - size_t size(uint8_t _dpth=0 ) { if (!_dpth) _dpth = colour_depth; return width * _dpth * sizeof(ESP32_I2S_DMA_STORAGE_TYPE); }; + /** @brief + * Returns size (in bytes) of row of data vectorfor a SINGLE buff for the number of colour depths requested + * + * default - Returns full data vector size for a SINGLE buff. + * You should only pass either PIXEL_COLOR_DEPTH_BITS or '1' to this + * + */ + size_t getColorDepthSize(uint8_t _dpth = 0) + { + if (!_dpth) + _dpth = colour_depth; + return width * _dpth * sizeof(ESP32_I2S_DMA_STORAGE_TYPE); + }; - /** @brief - returns pointer to the row's data vector beginning at pixel[0] for _dpth colour bit - * default - returns pointer to the data vector's head - * NOTE: this call might be very slow in loops. Due to poor instruction caching in esp32 it might be required a reread from flash - * every loop cycle, better use inlined #define instead in such cases - */ - inline ESP32_I2S_DMA_STORAGE_TYPE* getDataPtr(const uint8_t _dpth=0, const bool buff_id=0) { return &(data[_dpth*width + buff_id*(width*colour_depth)]); }; + /** @brief + * Returns pointer to the row's data vector beginning at pixel[0] for _dpth colour bit + * + * NOTE: this call might be very slow in loops. Due to poor instruction caching in esp32 it might be required a reread from flash + * every loop cycle, better use inlined #define instead in such cases + */ + // inline ESP32_I2S_DMA_STORAGE_TYPE* getDataPtr(const uint8_t _dpth=0, const bool buff_id=0) { return &(data[_dpth*width + buff_id*(width*colour_depth)]); }; - // constructor - allocates DMA-capable memory to hold the struct data - rowBitStruct(const size_t _width, const uint8_t _depth, const bool _dbuff) : width(_width), colour_depth(_depth), double_buff(_dbuff) { + // BUFFER ID VALUE IS NOW IGNORED!!!! + inline ESP32_I2S_DMA_STORAGE_TYPE *getDataPtr(const uint8_t _dpth = 0, const bool buff_id = 0) { return &(data[_dpth * width]); }; -//#if defined(SPIRAM_FRAMEBUFFER) && defined (CONFIG_IDF_TARGET_ESP32S3) - #if defined(SPIRAM_DMA_BUFFER) - // #pragma message "Enabling PSRAM / SPIRAM for frame buffer." - // ESP_LOGI("rowBitStruct", "Allocated DMA BitBuffer from PSRAM (SPIRAM)"); - //data = (ESP32_I2S_DMA_STORAGE_TYPE *)heap_caps_malloc( size()+size()*double_buff, MALLOC_CAP_SPIRAM); - data = (ESP32_I2S_DMA_STORAGE_TYPE *)heap_caps_aligned_alloc(64, size()+size()*double_buff, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); -/* - if (!psramFound()) - { - ESP_LOGE("rowBitStruct", "Requested to use PSRAM / SPIRAM for framebuffer, but it was not detected."); - } -*/ + // constructor - allocates DMA-capable memory to hold the struct data + rowBitStruct(const size_t _width, const uint8_t _depth, const bool _dbuff) : width(_width), colour_depth(_depth), double_buff(_dbuff) + { + + // #if defined(SPIRAM_FRAMEBUFFER) && defined (CONFIG_IDF_TARGET_ESP32S3) +#if defined(SPIRAM_DMA_BUFFER) + + // data = (ESP32_I2S_DMA_STORAGE_TYPE *)heap_caps_aligned_alloc(64, size()+size()*double_buff, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + // No longer have double buffer in the same struct - have a different struct + data = (ESP32_I2S_DMA_STORAGE_TYPE *)heap_caps_aligned_alloc(64, getColorDepthSize(), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); #else - data = (ESP32_I2S_DMA_STORAGE_TYPE *)heap_caps_malloc( size()+size()*double_buff, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA); - // ESP_LOGI("rowBitStruct", "Allocated DMA BitBuffer from regular (and limited) SRAM"); + // data = (ESP32_I2S_DMA_STORAGE_TYPE *)heap_caps_malloc( size()+size()*double_buff, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA); + + // No longer have double buffer in the same struct - have a different struct + data = (ESP32_I2S_DMA_STORAGE_TYPE *)heap_caps_malloc(getColorDepthSize(), MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA); + #endif - - } - ~rowBitStruct() { delete data;} + } + ~rowBitStruct() { delete data; } }; - /* frameStruct * Note: A 'frameStruct' contains ALL the data for a full-frame (i.e. BOTH 2x16-row frames are - * are contained in parallel within the one uint16_t that is sent in parallel to the HUB75). - * + * are contained in parallel within the one uint16_t that is sent in parallel to the HUB75). + * * This structure isn't actually allocated in one memory block anymore, as the library now allocates * memory per row (per rowBits) instead. */ -struct frameStruct { - uint8_t rows=0; // number of rows held in current frame, not used actually, just to keep the idea of struct - std::vector > rowBits; +struct frameStruct +{ + uint8_t rows = 0; // number of rows held in current frame, not used actually, just to keep the idea of struct + std::vector> rowBits; }; -/***************************************************************************************/ -//C/p'ed from https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/ -// Example calculator: https://gist.github.com/mathiasvr/19ce1d7b6caeab230934080ae1f1380e -// need to make sure this would end up in RAM for fastest access +/***************************************************************************************/ +// C/p'ed from https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/ +// Example calculator: https://gist.github.com/mathiasvr/19ce1d7b6caeab230934080ae1f1380e +// need to make sure this would end up in RAM for fastest access #ifndef NO_CIE1931 /* -static const uint8_t DRAM_ATTR lumConvTab[]={ +static const uint8_t DRAM_ATTR lumConvTab[]={ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 16, 16, 17, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 27, 27, 28, 28, 29, 30, 30, 31, 31, 32, 33, 33, 34, 35, 35, 36, 37, 38, 38, 39, 40, 41, 41, 42, 43, 44, 45, 45, 46, 47, 48, 49, 50, 51, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 80, 81, 82, 83, 84, 86, 87, 88, 90, 91, 92, 93, 95, 96, 98, 99, 100, 102, 103, 105, 106, 107, 109, 110, 112, 113, 115, 116, 118, 120, 121, 123, 124, 126, 128, 129, 131, 133, 134, 136, 138, 139, 141, 143, 145, 146, 148, 150, 152, 154, 156, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 192, 194, 196, 198, 200, 203, 205, 207, 209, 212, 214, 216, 218, 221, 223, 226, 228, 230, 233, 235, 238, 240, 243, 245, 248, 250, 253, 255, 255}; */ // This is 16-bit version of the table, // the constants taken from the example in the article above, each entries subtracted from 65535: -static const uint16_t DRAM_ATTR lumConvTab[]={ - 0, 27, 56, 84, 113, 141, 170, 198, 227, 255, 284, 312, 340, 369, 397, 426, - 454, 483, 511, 540, 568, 597, 626, 657, 688, 720, 754, 788, 824, 860, 898, 936, - 976, 1017, 1059, 1102, 1146, 1191, 1238, 1286, 1335, 1385, 1436, 1489, 1543, 1598, 1655, 1713, - 1772, 1833, 1895, 1958, 2023, 2089, 2156, 2225, 2296, 2368, 2441, 2516, 2592, 2670, 2750, 2831, - 2914, 2998, 3084, 3171, 3260, 3351, 3443, 3537, 3633, 3731, 3830, 3931, 4034, 4138, 4245, 4353, - 4463, 4574, 4688, 4803, 4921, 5040, 5161, 5284, 5409, 5536, 5665, 5796, 5929, 6064, 6201, 6340, - 6482, 6625, 6770, 6917, 7067, 7219, 7372, 7528, 7687, 7847, 8010, 8174, 8341, 8511, 8682, 8856, - 9032, 9211, 9392, 9575, 9761, 9949, 10139, 10332, 10527, 10725, 10925, 11127, 11332, 11540, 11750, 11963, - 12178, 12395, 12616, 12839, 13064, 13292, 13523, 13757, 13993, 14231, 14473, 14717, 14964, 15214, 15466, 15722, - 15980, 16240, 16504, 16771, 17040, 17312, 17587, 17865, 18146, 18430, 18717, 19006, 19299, 19595, 19894, 20195, - 20500, 20808, 21119, 21433, 21750, 22070, 22393, 22720, 23049, 23382, 23718, 24057, 24400, 24745, 25094, 25446, - 25802, 26160, 26522, 26888, 27256, 27628, 28004, 28382, 28765, 29150, 29539, 29932, 30328, 30727, 31130, 31536, - 31946, 32360, 32777, 33197, 33622, 34049, 34481, 34916, 35354, 35797, 36243, 36692, 37146, 37603, 38064, 38528, - 38996, 39469, 39945, 40424, 40908, 41395, 41886, 42382, 42881, 43383, 43890, 44401, 44916, 45434, 45957, 46484, - 47014, 47549, 48088, 48630, 49177, 49728, 50283, 50842, 51406, 51973, 52545, 53120, 53700, 54284, 54873, 55465, - 56062, 56663, 57269, 57878, 58492, 59111, 59733, 60360, 60992, 61627, 62268, 62912, 63561, 64215, 64873, 65535 -}; +static const uint16_t DRAM_ATTR lumConvTab[] = { + 0, 27, 56, 84, 113, 141, 170, 198, 227, 255, 284, 312, 340, 369, 397, 426, + 454, 483, 511, 540, 568, 597, 626, 657, 688, 720, 754, 788, 824, 860, 898, 936, + 976, 1017, 1059, 1102, 1146, 1191, 1238, 1286, 1335, 1385, 1436, 1489, 1543, 1598, 1655, 1713, + 1772, 1833, 1895, 1958, 2023, 2089, 2156, 2225, 2296, 2368, 2441, 2516, 2592, 2670, 2750, 2831, + 2914, 2998, 3084, 3171, 3260, 3351, 3443, 3537, 3633, 3731, 3830, 3931, 4034, 4138, 4245, 4353, + 4463, 4574, 4688, 4803, 4921, 5040, 5161, 5284, 5409, 5536, 5665, 5796, 5929, 6064, 6201, 6340, + 6482, 6625, 6770, 6917, 7067, 7219, 7372, 7528, 7687, 7847, 8010, 8174, 8341, 8511, 8682, 8856, + 9032, 9211, 9392, 9575, 9761, 9949, 10139, 10332, 10527, 10725, 10925, 11127, 11332, 11540, 11750, 11963, + 12178, 12395, 12616, 12839, 13064, 13292, 13523, 13757, 13993, 14231, 14473, 14717, 14964, 15214, 15466, 15722, + 15980, 16240, 16504, 16771, 17040, 17312, 17587, 17865, 18146, 18430, 18717, 19006, 19299, 19595, 19894, 20195, + 20500, 20808, 21119, 21433, 21750, 22070, 22393, 22720, 23049, 23382, 23718, 24057, 24400, 24745, 25094, 25446, + 25802, 26160, 26522, 26888, 27256, 27628, 28004, 28382, 28765, 29150, 29539, 29932, 30328, 30727, 31130, 31536, + 31946, 32360, 32777, 33197, 33622, 34049, 34481, 34916, 35354, 35797, 36243, 36692, 37146, 37603, 38064, 38528, + 38996, 39469, 39945, 40424, 40908, 41395, 41886, 42382, 42881, 43383, 43890, 44401, 44916, 45434, 45957, 46484, + 47014, 47549, 48088, 48630, 49177, 49728, 50283, 50842, 51406, 51973, 52545, 53120, 53700, 54284, 54873, 55465, + 56062, 56663, 57269, 57878, 58492, 59111, 59733, 60360, 60992, 61627, 62268, 62912, 63561, 64215, 64873, 65535}; #endif /** @brief - configuration values for HUB75_I2S driver @@ -225,50 +229,66 @@ static const uint16_t DRAM_ATTR lumConvTab[]={ * an initialization values when creating an instance of MatrixPanel_I2S_DMA object. * All params have it's default values. */ -struct HUB75_I2S_CFG { +struct HUB75_I2S_CFG +{ /** * Enumeration of hardware-specific chips * used to drive matrix modules */ - enum shift_driver {SHIFTREG=0, FM6124, FM6126A, ICN2038S, MBI5124, SM5266P}; + enum shift_driver + { + SHIFTREG = 0, + FM6124, + FM6126A, + ICN2038S, + MBI5124, + SM5266P, + DP3246_SM5368 + }; /** * I2S clock speed selector */ - enum clk_speed {HZ_8M=8000000, HZ_10M=10000000, HZ_15M=15000000, HZ_20M=20000000}; + enum clk_speed + { + HZ_8M = 8000000, + HZ_10M = 10000000, + HZ_15M = 15000000, + HZ_20M = 20000000 + }; - // Structure Variables + // + // Members must be in order of declaration or it breaks Arduino compiling due to strict checking. + // - /** - * GPIO pins mapping - */ - struct i2s_pins{ + // physical width of a single matrix panel module (in pixels, usually it is 64 ;) ) + uint16_t mx_width; + + // physical height of a single matrix panel module (in pixels, usually almost always it is either 32 or 64) + uint16_t mx_height; + + // number of chained panels regardless of the topology, default 1 - a single matrix module + uint16_t chain_length; + + // GPIO Mapping + struct i2s_pins + { int8_t r1, g1, b1, r2, g2, b2, a, b, c, d, e, lat, oe, clk; } gpio; // Matrix driver chip type - default is a plain shift register shift_driver driver; + + // use DMA double buffer (twice as much RAM required) + bool double_buff; + // I2S clock speed clk_speed i2sspeed; - // physical width of a single matrix panel module (in pixels, usually it is 64 ;) ) - uint16_t mx_width; - // physical height of a single matrix panel module (in pixels, usually almost always it is either 32 or 64) - uint16_t mx_height; - // number of chained panels regardless of the topology, default 1 - a single matrix module - uint16_t chain_length; - - // Minimum refresh / scan rate needs to be configured on start due to LSBMSB_TRANSITION_BIT calculation in allocateDMAmemory() - uint16_t min_refresh_rate; - // How many clock cycles to blank OE before/after LAT signal change, default is 1 clock uint8_t latch_blanking; - // use DMA double buffer (twice as much RAM required) - bool double_buff; - - /** * I2S clock phase * 0 - data lines are clocked with negative edge @@ -285,588 +305,571 @@ struct HUB75_I2S_CFG { */ bool clkphase; + // Minimum refresh / scan rate needs to be configured on start due to LSBMSB_TRANSITION_BIT calculation in allocateDMAmemory() + uint8_t min_refresh_rate; + // struct constructor - HUB75_I2S_CFG ( - uint16_t _w = MATRIX_WIDTH, - uint16_t _h = MATRIX_HEIGHT, - uint16_t _chain = CHAIN_LENGTH, - i2s_pins _pinmap = { - R1_PIN_DEFAULT, G1_PIN_DEFAULT, B1_PIN_DEFAULT, R2_PIN_DEFAULT, G2_PIN_DEFAULT, B2_PIN_DEFAULT, - A_PIN_DEFAULT, B_PIN_DEFAULT, C_PIN_DEFAULT, D_PIN_DEFAULT, E_PIN_DEFAULT, - LAT_PIN_DEFAULT, OE_PIN_DEFAULT, CLK_PIN_DEFAULT }, - shift_driver _drv = SHIFTREG, - bool _dbuff = false, - clk_speed _i2sspeed = HZ_15M, - uint8_t _latblk = DEFAULT_LAT_BLANKING, // Anything > 1 seems to cause artefacts on ICS panels - bool _clockphase = true, - uint16_t _min_refresh_rate = 60, - uint8_t _pixel_color_depth_bits = PIXEL_COLOR_DEPTH_BITS_DEFAULT - ) : mx_width(_w), - mx_height(_h), - chain_length(_chain), - gpio(_pinmap), - driver(_drv), - i2sspeed(_i2sspeed), - double_buff(_dbuff), - latch_blanking(_latblk), - clkphase(_clockphase), - min_refresh_rate(_min_refresh_rate) + HUB75_I2S_CFG( + uint16_t _w = MATRIX_WIDTH, + uint16_t _h = MATRIX_HEIGHT, + uint16_t _chain = CHAIN_LENGTH, + i2s_pins _pinmap = { + R1_PIN_DEFAULT, G1_PIN_DEFAULT, B1_PIN_DEFAULT, R2_PIN_DEFAULT, G2_PIN_DEFAULT, B2_PIN_DEFAULT, + A_PIN_DEFAULT, B_PIN_DEFAULT, C_PIN_DEFAULT, D_PIN_DEFAULT, E_PIN_DEFAULT, + LAT_PIN_DEFAULT, OE_PIN_DEFAULT, CLK_PIN_DEFAULT}, + shift_driver _drv = SHIFTREG, bool _dbuff = false, clk_speed _i2sspeed = HZ_15M, + uint8_t _latblk = DEFAULT_LAT_BLANKING, // Anything > 1 seems to cause artefacts on ICS panels + bool _clockphase = true, uint16_t _min_refresh_rate = 60, uint8_t _pixel_color_depth_bits = PIXEL_COLOR_DEPTH_BITS_DEFAULT) : mx_width(_w), mx_height(_h), chain_length(_chain), gpio(_pinmap), driver(_drv), double_buff(_dbuff), i2sspeed(_i2sspeed), latch_blanking(_latblk), clkphase(_clockphase), min_refresh_rate(_min_refresh_rate) { setPixelColorDepthBits(_pixel_color_depth_bits); } - //pixel_color_depth_bits must be between 12 and 2, and mask_offset needs to be calculated accordently - //so they have to be private with getter (and setter) - void setPixelColorDepthBits(uint8_t _pixel_color_depth_bits){ - if(_pixel_color_depth_bits > PIXEL_COLOR_DEPTH_BITS_MAX || _pixel_color_depth_bits < 2){ - - if(_pixel_color_depth_bits > PIXEL_COLOR_DEPTH_BITS_MAX){ + // pixel_color_depth_bits must be between 12 and 2, and mask_offset needs to be calculated accordently + // so they have to be private with getter (and setter) + void setPixelColorDepthBits(uint8_t _pixel_color_depth_bits) + { + if (_pixel_color_depth_bits > PIXEL_COLOR_DEPTH_BITS_MAX || _pixel_color_depth_bits < 2) + { + + if (_pixel_color_depth_bits > PIXEL_COLOR_DEPTH_BITS_MAX) + { pixel_color_depth_bits = PIXEL_COLOR_DEPTH_BITS_MAX; - }else{ + } + else + { pixel_color_depth_bits = 2; } ESP_LOGW("HUB75_I2S_CFG", "Invalid pixel_color_depth_bits (%d): 2 <= pixel_color_depth_bits <= %d, choosing nearest valid %d", _pixel_color_depth_bits, PIXEL_COLOR_DEPTH_BITS_MAX, pixel_color_depth_bits); - }else{ + } + else + { pixel_color_depth_bits = _pixel_color_depth_bits; } } - uint8_t getPixelColorDepthBits(){ + uint8_t getPixelColorDepthBits() + { return pixel_color_depth_bits; } - private: - //these were priviously handeld as defines (PIXEL_COLOR_DEPTH_BITS, MASK_OFFSET) - //to make it changable after compilation, it is now part of the config - uint8_t pixel_color_depth_bits; +private: + // these were priviously handeld as defines (PIXEL_COLOR_DEPTH_BITS, MASK_OFFSET) + // to make it changable after compilation, it is now part of the config + uint8_t pixel_color_depth_bits; }; // end of structure HUB75_I2S_CFG - - -/***************************************************************************************/ +/***************************************************************************************/ #ifdef USE_GFX_ROOT -class MatrixPanel_I2S_DMA : public GFX { +class MatrixPanel_I2S_DMA : public GFX +{ #elif !defined NO_GFX -class MatrixPanel_I2S_DMA : public Adafruit_GFX { +class MatrixPanel_I2S_DMA : public Adafruit_GFX +{ #else -class MatrixPanel_I2S_DMA { +class MatrixPanel_I2S_DMA +{ #endif // ------- PUBLIC ------- - public: - - /** - * MatrixPanel_I2S_DMA - * - * default predefined values are used for matrix configuration - * - */ - MatrixPanel_I2S_DMA() +public: + /** + * MatrixPanel_I2S_DMA + * + * default predefined values are used for matrix configuration + * + */ + MatrixPanel_I2S_DMA() #ifdef USE_GFX_ROOT : GFX(MATRIX_WIDTH, MATRIX_HEIGHT) #elif !defined NO_GFX : Adafruit_GFX(MATRIX_WIDTH, MATRIX_HEIGHT) #endif - {} + { + } - /** - * MatrixPanel_I2S_DMA - * - * @param {HUB75_I2S_CFG} opts : structure with matrix configuration - * - */ - MatrixPanel_I2S_DMA(const HUB75_I2S_CFG& opts) : -#ifdef USE_GFX_ROOT - GFX(opts.mx_width*opts.chain_length, opts.mx_height) + /** + * MatrixPanel_I2S_DMA + * + * @param {HUB75_I2S_CFG} opts : structure with matrix configuration + * + */ + MatrixPanel_I2S_DMA(const HUB75_I2S_CFG &opts) +#ifdef USE_GFX_ROOT + : GFX(opts.mx_width * opts.chain_length, opts.mx_height) #elif !defined NO_GFX - Adafruit_GFX(opts.mx_width*opts.chain_length, opts.mx_height) -#endif - { - setCfg(opts); - } + : Adafruit_GFX(opts.mx_width * opts.chain_length, opts.mx_height) +#endif + { + setCfg(opts); + } - /* Propagate the DMA pin configuration, allocate DMA buffs and start data output, initially blank */ - bool begin(){ - - if (initialized) return true; // we don't do this twice or more! - if(!config_set) return false; + /* Propagate the DMA pin configuration, allocate DMA buffs and start data output, initially blank */ + bool begin() + { - ESP_LOGI("begin()", "Using GPIO %d for R1_PIN", m_cfg.gpio.r1); - ESP_LOGI("begin()", "Using GPIO %d for G1_PIN", m_cfg.gpio.g1); - ESP_LOGI("begin()", "Using GPIO %d for B1_PIN", m_cfg.gpio.b1); - ESP_LOGI("begin()", "Using GPIO %d for R2_PIN", m_cfg.gpio.r2); - ESP_LOGI("begin()", "Using GPIO %d for G2_PIN", m_cfg.gpio.g2); - ESP_LOGI("begin()", "Using GPIO %d for B2_PIN", m_cfg.gpio.b2); - ESP_LOGI("begin()", "Using GPIO %d for A_PIN", m_cfg.gpio.a); - ESP_LOGI("begin()", "Using GPIO %d for B_PIN", m_cfg.gpio.b); - ESP_LOGI("begin()", "Using GPIO %d for C_PIN", m_cfg.gpio.c); - ESP_LOGI("begin()", "Using GPIO %d for D_PIN", m_cfg.gpio.d); - ESP_LOGI("begin()", "Using GPIO %d for E_PIN", m_cfg.gpio.e); - ESP_LOGI("begin()", "Using GPIO %d for LAT_PIN", m_cfg.gpio.lat); - ESP_LOGI("begin()", "Using GPIO %d for OE_PIN", m_cfg.gpio.oe); - ESP_LOGI("begin()", "Using GPIO %d for CLK_PIN", m_cfg.gpio.clk); - + if (initialized) + return true; // we don't do this twice or more! + if (!config_set) + return false; - // initialize some specific panel drivers - if (m_cfg.driver) - shiftDriver(m_cfg); - - #if defined(SPIRAM_DMA_BUFFER) - // Trick library into dropping colour depth slightly when using PSRAM. - // Actual output clockrate override occurs in configureDMA - m_cfg.i2sspeed = HUB75_I2S_CFG::HZ_8M; - #endif + ESP_LOGI("begin()", "Using GPIO %d for R1_PIN", m_cfg.gpio.r1); + ESP_LOGI("begin()", "Using GPIO %d for G1_PIN", m_cfg.gpio.g1); + ESP_LOGI("begin()", "Using GPIO %d for B1_PIN", m_cfg.gpio.b1); + ESP_LOGI("begin()", "Using GPIO %d for R2_PIN", m_cfg.gpio.r2); + ESP_LOGI("begin()", "Using GPIO %d for G2_PIN", m_cfg.gpio.g2); + ESP_LOGI("begin()", "Using GPIO %d for B2_PIN", m_cfg.gpio.b2); + ESP_LOGI("begin()", "Using GPIO %d for A_PIN", m_cfg.gpio.a); + ESP_LOGI("begin()", "Using GPIO %d for B_PIN", m_cfg.gpio.b); + ESP_LOGI("begin()", "Using GPIO %d for C_PIN", m_cfg.gpio.c); + ESP_LOGI("begin()", "Using GPIO %d for D_PIN", m_cfg.gpio.d); + ESP_LOGI("begin()", "Using GPIO %d for E_PIN", m_cfg.gpio.e); + ESP_LOGI("begin()", "Using GPIO %d for LAT_PIN", m_cfg.gpio.lat); + ESP_LOGI("begin()", "Using GPIO %d for OE_PIN", m_cfg.gpio.oe); + ESP_LOGI("begin()", "Using GPIO %d for CLK_PIN", m_cfg.gpio.clk); - /* As DMA buffers are dynamically allocated, we must allocated in begin() - * Ref: https://github.com/espressif/arduino-esp32/issues/831 - */ - if ( !allocateDMAmemory() ) { return false; } // couldn't even get the basic ram required. - + // initialize some specific panel drivers + if (m_cfg.driver) + shiftDriver(m_cfg); - // Flush the DMA buffers prior to configuring DMA - Avoid visual artefacts on boot. - resetbuffers(); // Must fill the DMA buffer with the initial output bit sequence or the panel will display garbage - - // Setup the ESP32 DMA Engine. Sprite_TM built this stuff. - configureDMA(m_cfg); //DMA and I2S configuration and setup - - //showDMABuffer(); // show backbuf_id of 0 - - if (!initialized) { - ESP_LOGE("being()", "MatrixPanel_I2S_DMA::begin() failed!"); - } - - return initialized; - - } - - // Obj destructor - ~MatrixPanel_I2S_DMA(){ - - dma_bus.release(); - - } - - - /* - * overload for compatibility - */ - bool begin(int r1, int g1 = G1_PIN_DEFAULT, int b1 = B1_PIN_DEFAULT, int r2 = R2_PIN_DEFAULT, int g2 = G2_PIN_DEFAULT, int b2 = B2_PIN_DEFAULT, int a = A_PIN_DEFAULT, int b = B_PIN_DEFAULT, int c = C_PIN_DEFAULT, int d = D_PIN_DEFAULT, int e = E_PIN_DEFAULT, int lat = LAT_PIN_DEFAULT, int oe = OE_PIN_DEFAULT, int clk = CLK_PIN_DEFAULT); - bool begin(const HUB75_I2S_CFG& cfg); - - // Adafruit's BASIC DRAW API (565 colour format) - virtual void drawPixel(int16_t x, int16_t y, uint16_t color); // overwrite adafruit implementation - virtual void fillScreen(uint16_t color); // overwrite adafruit implementation - - /** - * A wrapper to fill whatever selected DMA buffer / screen with black - */ - inline void clearScreen() { updateMatrixDMABuffer(0,0,0); }; - -#ifndef NO_FAST_FUNCTIONS - /** - * @brief - override Adafruit's FastVLine - * this works faster than multiple consecutive pixel by pixel drawPixel() call - */ - virtual void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color){ - uint8_t r, g, b; - color565to888(color, r, g, b); - startWrite(); - - int16_t w = 1; - transform( x, y, w, h); - if( h > w ) - vlineDMA( x, y, h, r, g, b); - else - hlineDMA( x, y, w, r, g, b); - - endWrite(); - } - // rgb888 overload - virtual inline void drawFastVLine(int16_t x, int16_t y, int16_t h, uint8_t r, uint8_t g, uint8_t b){ - int16_t w = 1; - transform( x, y, w, h); - if( h > w ) - vlineDMA( x, y, h, r, g, b); - else - hlineDMA( x, y, w, r, g, b); - }; - - /** - * @brief - override Adafruit's FastHLine - * this works faster than multiple consecutive pixel by pixel drawPixel() call - */ - virtual void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color){ - uint8_t r, g, b; - color565to888(color, r, g, b); - startWrite(); - - int16_t h = 1; - transform( x, y, w, h); - if( h > w ) - vlineDMA( x, y, h, r, g, b); - else - hlineDMA( x, y, w, r, g, b); - - endWrite(); - } - // rgb888 overload - virtual inline void drawFastHLine(int16_t x, int16_t y, int16_t w, uint8_t r, uint8_t g, uint8_t b){ - int16_t h = 1; - transform( x, y, w, h); - if( h > w ) - vlineDMA( x, y, h, r, g, b); - else - hlineDMA( x, y, w, r, g, b); - }; - - /** - * @brief - override Adafruit's fillRect - * this works much faster than multiple consecutive per-pixel drawPixel() calls - */ - virtual void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color){ - uint8_t r, g, b; - color565to888(color, r, g, b); - startWrite(); - transform( x, y, w, h); - fillRectDMA( x, y, w, h, r, g, b); - endWrite(); - } - // rgb888 overload - virtual inline void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint8_t r, uint8_t g, uint8_t b){ - startWrite(); - transform( x, y, w, h); - fillRectDMA( x, y, w, h, r, g, b); - endWrite(); - } +#if defined(SPIRAM_DMA_BUFFER) + // Trick library into dropping colour depth slightly when using PSRAM. + // Actual output clockrate override occurs in configureDMA + m_cfg.i2sspeed = HUB75_I2S_CFG::HZ_8M; #endif - void fillScreenRGB888(uint8_t r, uint8_t g, uint8_t b); - void drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b); + /* As DMA buffers are dynamically allocated, we must allocated in begin() + * Ref: https://github.com/espressif/arduino-esp32/issues/831 + */ + if (!allocateDMAmemory()) + { + return false; + } // couldn't even get the basic ram required. + + // Flush the DMA buffers prior to configuring DMA - Avoid visual artefacts on boot. + resetbuffers(); // Must fill the DMA buffer with the initial output bit sequence or the panel will display garbage + + // Setup the ESP32 DMA Engine. Sprite_TM built this stuff. + configureDMA(m_cfg); // DMA and I2S configuration and setup + + // showDMABuffer(); // show backbuf_id of 0 + + if (!initialized) + { + ESP_LOGE("being()", "MatrixPanel_I2S_DMA::begin() failed!"); + } + + return initialized; + } + + // Obj destructor + ~MatrixPanel_I2S_DMA() + { + + dma_bus.release(); + } + + /* + * overload for compatibility + */ + bool begin(int r1, int g1 = G1_PIN_DEFAULT, int b1 = B1_PIN_DEFAULT, int r2 = R2_PIN_DEFAULT, int g2 = G2_PIN_DEFAULT, int b2 = B2_PIN_DEFAULT, int a = A_PIN_DEFAULT, int b = B_PIN_DEFAULT, int c = C_PIN_DEFAULT, int d = D_PIN_DEFAULT, int e = E_PIN_DEFAULT, int lat = LAT_PIN_DEFAULT, int oe = OE_PIN_DEFAULT, int clk = CLK_PIN_DEFAULT); + bool begin(const HUB75_I2S_CFG &cfg); + + // Adafruit's BASIC DRAW API (565 colour format) + virtual void drawPixel(int16_t x, int16_t y, uint16_t color); // overwrite adafruit implementation + virtual void fillScreen(uint16_t color); // overwrite adafruit implementation + + /** + * A wrapper to fill whatever selected DMA buffer / screen with black + */ + inline void clearScreen() { updateMatrixDMABuffer(0, 0, 0); }; + +#ifndef NO_FAST_FUNCTIONS + /** + * @brief - override Adafruit's FastVLine + * this works faster than multiple consecutive pixel by pixel drawPixel() call + */ + virtual void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) + { + uint8_t r, g, b; + color565to888(color, r, g, b); + int16_t w = 1; + transform(x, y, w, h); + if (h > w) + vlineDMA(x, y, h, r, g, b); + else + hlineDMA(x, y, w, r, g, b); + + } + // rgb888 overload + virtual inline void drawFastVLine(int16_t x, int16_t y, int16_t h, uint8_t r, uint8_t g, uint8_t b) + { + int16_t w = 1; + transform(x, y, w, h); + if (h > w) + vlineDMA(x, y, h, r, g, b); + else + hlineDMA(x, y, w, r, g, b); + }; + + /** + * @brief - override Adafruit's FastHLine + * this works faster than multiple consecutive pixel by pixel drawPixel() call + */ + virtual void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) + { + uint8_t r, g, b; + color565to888(color, r, g, b); + + int16_t h = 1; + transform(x, y, w, h); + if (h > w) + vlineDMA(x, y, h, r, g, b); + else + hlineDMA(x, y, w, r, g, b); + + } + // rgb888 overload + virtual inline void drawFastHLine(int16_t x, int16_t y, int16_t w, uint8_t r, uint8_t g, uint8_t b) + { + int16_t h = 1; + transform(x, y, w, h); + if (h > w) + vlineDMA(x, y, h, r, g, b); + else + hlineDMA(x, y, w, r, g, b); + }; + + /** + * @brief - override Adafruit's fillRect + * this works much faster than multiple consecutive per-pixel drawPixel() calls + */ + virtual void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) + { + uint8_t r, g, b; + color565to888(color, r, g, b); + + transform(x, y, w, h); + fillRectDMA(x, y, w, h, r, g, b); + + } + // rgb888 overload + virtual inline void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint8_t r, uint8_t g, uint8_t b) + { + + transform(x, y, w, h); + fillRectDMA(x, y, w, h, r, g, b); + + } +#endif + + void fillScreenRGB888(uint8_t r, uint8_t g, uint8_t b); + void drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b); + #ifdef USE_GFX_ROOT - // 24bpp FASTLED CRGB colour struct support - void fillScreen(CRGB color); - void drawPixel(int16_t x, int16_t y, CRGB color); -#endif + // 24bpp FASTLED CRGB colour struct support + void fillScreen(CRGB color); + void drawPixel(int16_t x, int16_t y, CRGB color); +#endif - void drawIcon (int *ico, int16_t x, int16_t y, int16_t cols, int16_t rows); - - // Colour 444 is a 4 bit scale, so 0 to 15, colour 565 takes a 0-255 bit value, so scale up by 255/15 (i.e. 17)! - static uint16_t color444(uint8_t r, uint8_t g, uint8_t b) { return color565(r*17,g*17,b*17); } + void drawIcon(int *ico, int16_t x, int16_t y, int16_t cols, int16_t rows); - // Converts RGB888 to RGB565 - static uint16_t color565(uint8_t r, uint8_t g, uint8_t b); // This is what is used by Adafruit GFX! - - // Converts RGB333 to RGB565 - static uint16_t color333(uint8_t r, uint8_t g, uint8_t b); // This is what is used by Adafruit GFX! Not sure why they have a capital 'C' for this particular function. + // Colour 444 is a 4 bit scale, so 0 to 15, colour 565 takes a 0-255 bit value, so scale up by 255/15 (i.e. 17)! + static uint16_t color444(uint8_t r, uint8_t g, uint8_t b) { return color565(r * 17, g * 17, b * 17); } - /** - * @brief - convert RGB565 to RGB888 - * @param uint16_t colour - RGB565 input colour - * @param uint8_t &r, &g, &b - refs to variables where converted colors would be emplaced - */ - static void color565to888(const uint16_t color, uint8_t &r, uint8_t &g, uint8_t &b); + // Converts RGB888 to RGB565 + static uint16_t color565(uint8_t r, uint8_t g, uint8_t b); // This is what is used by Adafruit GFX! + // Converts RGB333 to RGB565 + static uint16_t color333(uint8_t r, uint8_t g, uint8_t b); // This is what is used by Adafruit GFX! Not sure why they have a capital 'C' for this particular function. - inline void flipDMABuffer() - { - if ( !m_cfg.double_buff) { return; } - - // while (active_gfx_writes) { } // wait a bit ? - // initialized = false; - dma_bus.flip_dma_output_buffer( back_buffer_id ); - // initialized = true; - - /* - i2s_parallel_set_previous_buffer_not_free(); - // Wait before we allow any writing to the buffer. Stop flicker. - while(i2s_parallel_is_previous_buffer_free() == false) { } - - i2s_parallel_flip_to_buffer(ESP32_I2S_DEVICE, back_buffer_id); - // Flip to other buffer as the backbuffer. - // i.e. Graphic changes happen to this buffer, but aren't displayed until flipDMABuffer() is called again. - back_buffer_id ^= 1; - - i2s_parallel_set_previous_buffer_not_free(); - // Wait before we allow any writing to the buffer. Stop flicker. - while(i2s_parallel_is_previous_buffer_free() == false) { } - */ + /** + * @brief - convert RGB565 to RGB888 + * @param uint16_t colour - RGB565 input colour + * @param uint8_t &r, &g, &b - refs to variables where converted colors would be emplaced + */ + static void color565to888(const uint16_t color, uint8_t &r, uint8_t &g, uint8_t &b); + inline void flipDMABuffer() + { + if (!m_cfg.double_buff) + { + return; } - /** - * @param uint8_t b - 8-bit brightness value - */ - void setBrightness(const uint8_t b) - { - if (!initialized) - { - ESP_LOGI("setBrightness()", "Tried to set output brightness before begin()"); - return; - } - - brightness = b; - brtCtrlOEv2(b, 0); - - if (m_cfg.double_buff) { - brtCtrlOEv2(b, 1); - } - - - } - - // Takes a value that is between 0 and MATRIX_WIDTH-1 - /* - void setPanelBrightness(int b) - { - if (!initialized) - { - ESP_LOGI("setPanelBrightness()", "Tried to set output brightness before begin()"); - return; - } - - // Change to set the brightness of the display, range of 1 to matrixWidth (i.e. 1 - 64) - // brightness = b * PIXELS_PER_ROW / 256; - - brtCtrlOE(b); - if (m_cfg.double_buff) - brtCtrlOE(b, 1); - } - */ + dma_bus.flip_dma_output_buffer(back_buffer_id); - /** - * @param uint8_t b - 8-bit brightness value - */ - void setPanelBrightness(const uint8_t b) + back_buffer_id ^= 1; + fb = &frame_buffer[back_buffer_id]; + + + + } + + /** + * @param uint8_t b - 8-bit brightness value + */ + void setBrightness(const uint8_t b) + { + if (!initialized) { - setBrightness(b); - } + ESP_LOGI("setBrightness()", "Tried to set output brightness before begin()"); + return; + } - /** - * this is just a wrapper to control brightness - * with an 8-bit value (0-255), very popular in FastLED-based sketches :) - * @param uint8_t b - 8-bit brightness value - */ - void setBrightness8(const uint8_t b) + brightness = b; + brtCtrlOEv2(b, 0); + + if (m_cfg.double_buff) { - setBrightness(b); - //setPanelBrightness(b * PIXELS_PER_ROW / 256); + brtCtrlOEv2(b, 1); } - + } - /** - * @brief - Sets how many clock cycles to blank OE before/after LAT signal change - * @param uint8_t pulses - clocks before/after OE - * default is DEFAULT_LAT_BLANKING - * Max is MAX_LAT_BLANKING - * @returns - new value for m_cfg.latch_blanking - */ - uint8_t setLatBlanking(uint8_t pulses); + /** + * @param uint8_t b - 8-bit brightness value + */ + void setPanelBrightness(const uint8_t b) + { + setBrightness(b); + } - /** - * Get a class configuration struct - * - */ - const HUB75_I2S_CFG& getCfg() const {return m_cfg;}; - - inline bool setCfg(const HUB75_I2S_CFG& cfg){ - if(initialized) return false; + /** + * this is just a wrapper to control brightness + * with an 8-bit value (0-255), very popular in FastLED-based sketches :) + * @param uint8_t b - 8-bit brightness value + */ + void setBrightness8(const uint8_t b) + { + setBrightness(b); + // setPanelBrightness(b * PIXELS_PER_ROW / 256); + } - m_cfg = cfg; - PIXELS_PER_ROW = m_cfg.mx_width * m_cfg.chain_length; - ROWS_PER_FRAME = m_cfg.mx_height / MATRIX_ROWS_IN_PARALLEL; - MASK_OFFSET = 16 - m_cfg.getPixelColorDepthBits(); + /** + * @brief - Sets how many clock cycles to blank OE before/after LAT signal change + * @param uint8_t pulses - clocks before/after OE + * default is DEFAULT_LAT_BLANKING + * Max is MAX_LAT_BLANKING + * @returns - new value for m_cfg.latch_blanking + */ + uint8_t setLatBlanking(uint8_t pulses); - config_set = true; - return true; - } - - /** - * Stop the ESP32 DMA Engine. Screen will forever be black until next ESP reboot. - */ - void stopDMAoutput() { - resetbuffers(); - //i2s_parallel_stop_dma(ESP32_I2S_DEVICE); - dma_bus.dma_transfer_stop(); - } + /** + * Get a class configuration struct + * + */ + const HUB75_I2S_CFG &getCfg() const { return m_cfg; }; - void startWrite() { - //ESP_LOGI("TAG", "startWrite() called"); - active_gfx_writes++; - } - + inline bool setCfg(const HUB75_I2S_CFG &cfg) + { + if (initialized) + return false; - void endWrite() { - active_gfx_writes--; - } + m_cfg = cfg; + PIXELS_PER_ROW = m_cfg.mx_width * m_cfg.chain_length; + ROWS_PER_FRAME = m_cfg.mx_height / MATRIX_ROWS_IN_PARALLEL; + MASK_OFFSET = 16 - m_cfg.getPixelColorDepthBits(); + + config_set = true; + return true; + } + + /** + * Stop the ESP32 DMA Engine. Screen will forever be black until next ESP reboot. + */ + void stopDMAoutput() + { + resetbuffers(); + // i2s_parallel_stop_dma(ESP32_I2S_DEVICE); + dma_bus.dma_transfer_stop(); + } // ------- PROTECTED ------- // those might be useful for child classes, like VirtualMatrixPanel - protected: +protected: + /** + * @brief - clears and reinitializes colour/control data in DMA buffs + * When allocated, DMA buffs might be dirty, so we need to blank it and initialize ABCDE,LAT,OE control bits. + * Those control bits are constants during the entire DMA sweep and never changed when updating just pixel colour data + * so we could set it once on DMA buffs initialization and forget. + * This effectively clears buffers to blank BLACK and makes it ready to display output. + * (Brightness control via OE bit manipulation is another case) + */ + void clearFrameBuffer(bool _buff_id); - /** - * @brief - clears and reinitializes colour/control data in DMA buffs - * When allocated, DMA buffs might be dirty, so we need to blank it and initialize ABCDE,LAT,OE control bits. - * Those control bits are constants during the entire DMA sweep and never changed when updating just pixel colour data - * so we could set it once on DMA buffs initialization and forget. - * This effectively clears buffers to blank BLACK and makes it ready to display output. - * (Brightness control via OE bit manipulation is another case) - */ - void clearFrameBuffer(bool _buff_id = 0); + /* Update a specific pixel in the DMA buffer to a colour */ + void updateMatrixDMABuffer(uint16_t x, uint16_t y, uint8_t red, uint8_t green, uint8_t blue); - /* Update a specific pixel in the DMA buffer to a colour */ - void updateMatrixDMABuffer(uint16_t x, uint16_t y, uint8_t red, uint8_t green, uint8_t blue); - - /* Update the entire DMA buffer (aka. The RGB Panel) a certain colour (wipe the screen basically) */ - void updateMatrixDMABuffer(uint8_t red, uint8_t green, uint8_t blue); + /* Update the entire DMA buffer (aka. The RGB Panel) a certain colour (wipe the screen basically) */ + void updateMatrixDMABuffer(uint8_t red, uint8_t green, uint8_t blue); - /** - * wipes DMA buffer(s) and reset all colour/service bits - */ - inline void resetbuffers(){ + /** + * wipes DMA buffer(s) and reset all colour/service bits + */ + inline void resetbuffers() + { + clearFrameBuffer(0); + brtCtrlOEv2(brightness, 0); + + if (m_cfg.double_buff) { - clearFrameBuffer(); - brtCtrlOEv2(brightness, 0); - - if (m_cfg.double_buff){ - clearFrameBuffer(1); - brtCtrlOEv2(brightness, 1); - } - - } + clearFrameBuffer(1); + brtCtrlOEv2(brightness, 1); + } + } #ifndef NO_FAST_FUNCTIONS - /** - * @brief - update DMA buff drawing horizontal line at specified coordinates - * @param x_ccord - line start coordinate x - * @param y_ccord - line start coordinate y - * @param l - line length - * @param r,g,b, - RGB888 colour - */ - void hlineDMA(int16_t x_coord, int16_t y_coord, int16_t l, uint8_t red, uint8_t green, uint8_t blue); + /** + * @brief - update DMA buff drawing horizontal line at specified coordinates + * @param x_ccord - line start coordinate x + * @param y_ccord - line start coordinate y + * @param l - line length + * @param r,g,b, - RGB888 colour + */ + void hlineDMA(int16_t x_coord, int16_t y_coord, int16_t l, uint8_t red, uint8_t green, uint8_t blue); - /** - * @brief - update DMA buff drawing horizontal line at specified coordinates - * @param x_ccord - line start coordinate x - * @param y_ccord - line start coordinate y - * @param l - line length - * @param r,g,b, - RGB888 colour - */ - void vlineDMA(int16_t x_coord, int16_t y_coord, int16_t l, uint8_t red, uint8_t green, uint8_t blue); + /** + * @brief - update DMA buff drawing horizontal line at specified coordinates + * @param x_ccord - line start coordinate x + * @param y_ccord - line start coordinate y + * @param l - line length + * @param r,g,b, - RGB888 colour + */ + void vlineDMA(int16_t x_coord, int16_t y_coord, int16_t l, uint8_t red, uint8_t green, uint8_t blue); - /** - * @brief - update DMA buff drawing a rectangular at specified coordinates - * uses Fast H/V line draw internally, works faster than multiple consecutive pixel by pixel calls to updateMatrixDMABuffer() - * @param int16_t x, int16_t y - coordinates of a top-left corner - * @param int16_t w, int16_t h - width and height of a rectangular, min is 1 px - * @param uint8_t r - RGB888 colour - * @param uint8_t g - RGB888 colour - * @param uint8_t b - RGB888 colour - */ - void fillRectDMA(int16_t x_coord, int16_t y_coord, int16_t w, int16_t h, uint8_t r, uint8_t g, uint8_t b); + /** + * @brief - update DMA buff drawing a rectangular at specified coordinates + * uses Fast H/V line draw internally, works faster than multiple consecutive pixel by pixel calls to updateMatrixDMABuffer() + * @param int16_t x, int16_t y - coordinates of a top-left corner + * @param int16_t w, int16_t h - width and height of a rectangular, min is 1 px + * @param uint8_t r - RGB888 colour + * @param uint8_t g - RGB888 colour + * @param uint8_t b - RGB888 colour + */ + void fillRectDMA(int16_t x_coord, int16_t y_coord, int16_t w, int16_t h, uint8_t r, uint8_t g, uint8_t b); #endif - // ------- PRIVATE ------- - private: + // ------- PRIVATE ------- +private: + /* Calculate the memory available for DMA use, do some other stuff, and allocate accordingly */ + bool allocateDMAmemory(); - /* Calculate the memory available for DMA use, do some other stuff, and allocate accordingly */ - bool allocateDMAmemory(); + /* Setup the DMA Link List chain and initiate the ESP32 DMA engine */ + void configureDMA(const HUB75_I2S_CFG &opts); - /* Setup the DMA Link List chain and initiate the ESP32 DMA engine */ - void configureDMA(const HUB75_I2S_CFG& opts); + /** + * pre-init procedures for specific drivers + * + */ + void shiftDriver(const HUB75_I2S_CFG &opts); - /** - * pre-init procedures for specific drivers - * - */ - void shiftDriver(const HUB75_I2S_CFG& opts); + /** + * @brief - FM6124-family chips initialization routine + */ + void fm6124init(const HUB75_I2S_CFG &_cfg); - /** - * @brief - FM6124-family chips initialization routine - */ - void fm6124init(const HUB75_I2S_CFG& _cfg); + /** + * @brief - DP3246-family chips initialization routine + */ + void dp3246init(const HUB75_I2S_CFG& _cfg); - /** - * @brief - reset OE bits in DMA buffer in a way to control brightness - * @param brt - brightness level from 0 to row_width - * @param _buff_id - buffer id to control - */ - //void brtCtrlOE(int brt, const bool _buff_id=0); - - /** - * @brief - reset OE bits in DMA buffer in a way to control brightness - * @param brt - brightness level from 0 to row_width - * @param _buff_id - buffer id to control - */ - void brtCtrlOEv2(uint8_t brt, const int _buff_id=0); + /** + * @brief - reset OE bits in DMA buffer in a way to control brightness + * @param brt - brightness level from 0 to row_width + * @param _buff_id - buffer id to control + */ + // void brtCtrlOE(int brt, const bool _buff_id=0); - /** - * @brief - transforms coordinates according to orientation - * @param x - x position origin - * @param y - y position origin - * @param w - rectangular width - * @param h - rectangular height - */ - void transform(int16_t &x, int16_t &y, int16_t &w, int16_t &h){ - #ifndef NO_GFX - int16_t t; - switch (rotation) { - case 1: t = _height - 1 - y - ( h - 1 ); y = x; x = t; t = h; h = w; w = t; return; - case 2: x = _width - 1 - x - ( w - 1 ); y = _height - 1 - y - ( h - 1 ); return; - case 3: t = y; y = _width - 1 - x - ( w - 1 ); x = t; t = h; h = w; w = t; return; - } - #endif - }; + /** + * @brief - reset OE bits in DMA buffer in a way to control brightness + * @param brt - brightness level from 0 to row_width + * @param _buff_id - buffer id to control + */ + void brtCtrlOEv2(uint8_t brt, const int _buff_id = 0); + /** + * @brief - transforms coordinates according to orientation + * @param x - x position origin + * @param y - y position origin + * @param w - rectangular width + * @param h - rectangular height + */ + void transform(int16_t &x, int16_t &y, int16_t &w, int16_t &h) + { +#ifndef NO_GFX + int16_t t; + switch (rotation) + { + case 1: + t = _height - 1 - y - (h - 1); + y = x; + x = t; + t = h; + h = w; + w = t; + return; + case 2: + x = _width - 1 - x - (w - 1); + y = _height - 1 - y - (h - 1); + return; + case 3: + t = y; + y = _width - 1 - x - (w - 1); + x = t; + t = h; + h = w; + w = t; + return; + } +#endif + }; - public: - /** - * Contains the resulting refresh rate (scan rate) that will be achieved - * based on the i2sspeed, colour depth and min_refresh_rate requested. - */ - int calculated_refresh_rate = 0; - protected: - Bus_Parallel16 dma_bus; - private: +public: + /** + * Contains the resulting refresh rate (scan rate) that will be achieved + * based on the i2sspeed, colour depth and min_refresh_rate requested. + */ + int calculated_refresh_rate = 0; - // Matrix i2s settings - HUB75_I2S_CFG m_cfg; +protected: + Bus_Parallel16 dma_bus; - /* Pixel data is organized from LSB to MSB sequentially by row, from row 0 to row matrixHeight/matrixRowsInParallel - * (two rows of pixels are refreshed in parallel) - * Memory is allocated (malloc'd) by the row, and not in one massive chunk, for flexibility. - * The whole DMA framebuffer is just a vector of pointers to structs with ESP32_I2S_DMA_STORAGE_TYPE arrays - * Since it's dimensions is unknown prior to class initialization, we just declare it here as empty struct and will do all allocations later. - * Refer to rowBitStruct to get the idea of it's internal structure - */ - frameStruct dma_buff; +private: - // ESP 32 DMA Linked List descriptor - int desccount = 0; - // lldesc_t * dmadesc_a = {0}; - // lldesc_t * dmadesc_b = {0}; + // Matrix i2s settings + HUB75_I2S_CFG m_cfg; - int active_gfx_writes = 0; // How many async routines are 'drawing' (writing) to the DMA bit buffer. Function called from Adafruit_GFX draw routines like drawCircle etc. - int back_buffer_id = 0; // If using double buffer, which one is NOT active (ie. being displayed) to write too? - int brightness = 128; // If you get ghosting... reduce brightness level. ((60/64)*255) seems to be the limit before ghosting on a 64 pixel wide physical panel for some panels. - int lsbMsbTransitionBit = 0; // For colour depth calculations + /* Pixel data is organized from LSB to MSB sequentially by row, from row 0 to row matrixHeight/matrixRowsInParallel + * (two rows of pixels are refreshed in parallel) + * Memory is allocated (malloc'd) by the row, and not in one massive chunk, for flexibility. + * The whole DMA framebuffer is just a vector of pointers to structs with ESP32_I2S_DMA_STORAGE_TYPE arrays + * Since it's dimensions is unknown prior to class initialization, we just declare it here as empty struct and will do all allocations later. + * Refer to rowBitStruct to get the idea of it's internal structure + */ + frameStruct frame_buffer[2]; + frameStruct *fb; // What framebuffer we are writing pixel changes to? (pointer to either frame_buffer[0] or frame_buffer[1] basically ) used within updateMatrixDMABuffer(...) - /* ESP32-HUB75-MatrixPanel-I2S-DMA functioning constants - * we should not those once object instance initialized it's DMA structs - * they weree const, but this lead to bugs, when the default constructor was called. - * So now they could be changed, but shouldn't. Maybe put a cpp lock around it, so it can't be changed after initialisation - */ - uint16_t PIXELS_PER_ROW = m_cfg.mx_width * m_cfg.chain_length; // number of pixels in a single row of all chained matrix modules (WIDTH of a combined matrix chain) - uint8_t ROWS_PER_FRAME = m_cfg.mx_height / MATRIX_ROWS_IN_PARALLEL; // RPF - rows per frame, either 16 or 32 depending on matrix module - uint8_t MASK_OFFSET = 16 - m_cfg.getPixelColorDepthBits(); - // Other private variables - bool initialized = false; - bool config_set = false; + volatile int back_buffer_id = 0; // If using double buffer, which one is NOT active (ie. being displayed) to write too? + int brightness = 128; // If you get ghosting... reduce brightness level. ((60/64)*255) seems to be the limit before ghosting on a 64 pixel wide physical panel for some panels. + int lsbMsbTransitionBit = 0; // For colour depth calculations + + /* ESP32-HUB75-MatrixPanel-I2S-DMA functioning constants + * we should not those once object instance initialized it's DMA structs + * they weree const, but this lead to bugs, when the default constructor was called. + * So now they could be changed, but shouldn't. Maybe put a cpp lock around it, so it can't be changed after initialisation + */ + uint16_t PIXELS_PER_ROW = m_cfg.mx_width * m_cfg.chain_length; // number of pixels in a single row of all chained matrix modules (WIDTH of a combined matrix chain) + uint8_t ROWS_PER_FRAME = m_cfg.mx_height / MATRIX_ROWS_IN_PARALLEL; // RPF - rows per frame, either 16 or 32 depending on matrix module + uint8_t MASK_OFFSET = 16 - m_cfg.getPixelColorDepthBits(); + + // Other private variables + bool initialized = false; + bool config_set = false; }; // end Class header -/***************************************************************************************/ +/***************************************************************************************/ // https://stackoverflow.com/questions/5057021/why-are-c-inline-functions-in-the-header /* 2. functions declared in the header must be marked inline because otherwise, every translation unit which includes the header will contain a definition of the function, and the linker will complain about multiple definitions (a violation of the One Definition Rule). The inline keyword suppresses this, allowing multiple translation units to contain (identical) definitions. */ @@ -875,99 +878,104 @@ class MatrixPanel_I2S_DMA { * @param uint16_t colour - RGB565 input colour * @param uint8_t &r, &g, &b - refs to variables where converted colours would be emplaced */ -inline void MatrixPanel_I2S_DMA::color565to888(const uint16_t color, uint8_t &r, uint8_t &g, uint8_t &b){ - r = ((((color >> 11) & 0x1F) * 527) + 23) >> 6; - g = ((((color >> 5) & 0x3F) * 259) + 33) >> 6; - b = (((color & 0x1F) * 527) + 23) >> 6; +inline void MatrixPanel_I2S_DMA::color565to888(const uint16_t color, uint8_t &r, uint8_t &g, uint8_t &b) +{ + r = (color >> 8) & 0xf8; + g = (color >> 3) & 0xfc; + b = (color << 3); + r |= r >> 5; + g |= g >> 6; + b |= b >> 5; } inline void MatrixPanel_I2S_DMA::drawPixel(int16_t x, int16_t y, uint16_t color) // adafruit virtual void override { - uint8_t r,g,b; - color565to888(color,r,g,b); - - int16_t w = 1, h = 1; - transform( x, y, w, h); - updateMatrixDMABuffer( x, y, r, g, b); -} + uint8_t r, g, b; + color565to888(color, r, g, b); -inline void MatrixPanel_I2S_DMA::fillScreen(uint16_t color) // adafruit virtual void override -{ - uint8_t r,g,b; - color565to888(color,r,g,b); - - updateMatrixDMABuffer(r, g, b); // RGB only (no pixel coordinate) version of 'updateMatrixDMABuffer' -} - -inline void MatrixPanel_I2S_DMA::drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g,uint8_t b) -{ int16_t w = 1, h = 1; - transform( x, y, w, h); - updateMatrixDMABuffer( x, y, r, g, b); + transform(x, y, w, h); + updateMatrixDMABuffer(x, y, r, g, b); } -inline void MatrixPanel_I2S_DMA::fillScreenRGB888(uint8_t r, uint8_t g,uint8_t b) +inline void MatrixPanel_I2S_DMA::fillScreen(uint16_t color) // adafruit virtual void override +{ + uint8_t r, g, b; + color565to888(color, r, g, b); + + updateMatrixDMABuffer(r, g, b); // RGB only (no pixel coordinate) version of 'updateMatrixDMABuffer' +} + +inline void MatrixPanel_I2S_DMA::drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b) +{ + int16_t w = 1, h = 1; + transform(x, y, w, h); + updateMatrixDMABuffer(x, y, r, g, b); +} + +inline void MatrixPanel_I2S_DMA::fillScreenRGB888(uint8_t r, uint8_t g, uint8_t b) { updateMatrixDMABuffer(r, g, b); // RGB only (no pixel coordinate) version of 'updateMatrixDMABuffer' -} +} #ifdef USE_GFX_ROOT // Support for CRGB values provided via FastLED -inline void MatrixPanel_I2S_DMA::drawPixel(int16_t x, int16_t y, CRGB color) +inline void MatrixPanel_I2S_DMA::drawPixel(int16_t x, int16_t y, CRGB color) { int16_t w = 1, h = 1; - transform( x, y, w, h); - updateMatrixDMABuffer( x, y, color.red, color.green, color.blue); + transform(x, y, w, h); + updateMatrixDMABuffer(x, y, color.red, color.green, color.blue); } -inline void MatrixPanel_I2S_DMA::fillScreen(CRGB color) +inline void MatrixPanel_I2S_DMA::fillScreen(CRGB color) { updateMatrixDMABuffer(color.red, color.green, color.blue); } #endif - // Pass 8-bit (each) R,G,B, get back 16-bit packed colour -//https://github.com/squix78/ILI9341Buffer/blob/master/ILI9341_SPI.cpp -inline uint16_t MatrixPanel_I2S_DMA::color565(uint8_t r, uint8_t g, uint8_t b) { +// https://github.com/squix78/ILI9341Buffer/blob/master/ILI9341_SPI.cpp +inline uint16_t MatrixPanel_I2S_DMA::color565(uint8_t r, uint8_t g, uint8_t b) +{ return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); } // Promote 3/3/3 RGB to Adafruit_GFX 5/6/5 RRRrrGGGgggBBBbb -inline uint16_t MatrixPanel_I2S_DMA::color333(uint8_t r, uint8_t g, uint8_t b) { - return ((r & 0x7) << 13) | ((r & 0x6) << 10) | ((g & 0x7) << 8) | ((g & 0x7) << 5) | ((b & 0x7) << 2) | ((b & 0x6) >> 1); +inline uint16_t MatrixPanel_I2S_DMA::color333(uint8_t r, uint8_t g, uint8_t b) +{ + return ((r & 0x7) << 13) | ((r & 0x6) << 10) | ((g & 0x7) << 8) | ((g & 0x7) << 5) | ((b & 0x7) << 2) | ((b & 0x6) >> 1); } -inline void MatrixPanel_I2S_DMA::drawIcon (int *ico, int16_t x, int16_t y, int16_t cols, int16_t rows) { -/* drawIcon draws a C style bitmap. -// Example 10x5px bitmap of a yellow sun -// - int half_sun [50] = { - 0x0000, 0x0000, 0x0000, 0xffe0, 0x0000, 0x0000, 0xffe0, 0x0000, 0x0000, 0x0000, - 0x0000, 0xffe0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffe0, 0x0000, - 0x0000, 0x0000, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0x0000, 0x0000, 0x0000, - 0xffe0, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0x0000, 0xffe0, - 0x0000, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0x0000, 0x0000, - }; - - MatrixPanel_I2S_DMA matrix; +inline void MatrixPanel_I2S_DMA::drawIcon(int *ico, int16_t x, int16_t y, int16_t cols, int16_t rows) +{ + /* drawIcon draws a C style bitmap. + // Example 10x5px bitmap of a yellow sun + // + int half_sun [50] = { + 0x0000, 0x0000, 0x0000, 0xffe0, 0x0000, 0x0000, 0xffe0, 0x0000, 0x0000, 0x0000, + 0x0000, 0xffe0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffe0, 0x0000, + 0x0000, 0x0000, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0x0000, 0x0000, 0x0000, + 0xffe0, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0x0000, 0xffe0, + 0x0000, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0x0000, 0x0000, + }; - matrix.drawIcon (half_sun, 0,0,10,5); -*/ + MatrixPanel_I2S_DMA matrix; + + matrix.drawIcon (half_sun, 0,0,10,5); + */ int i, j; - for (i = 0; i < rows; i++) { - for (j = 0; j < cols; j++) { - drawPixel (x + j, y + i, (uint16_t) ico[i * cols + j]); + for (i = 0; i < rows; i++) + { + for (j = 0; j < cols; j++) + { + drawPixel(x + j, y + i, (uint16_t)ico[i * cols + j]); } - } + } } - #endif - - // Credits: Louis Beaudoin // and Sprite_TM: https://www.esp32.com/viewtopic.php?f=17&t=3188 and https://www.esp32.com/viewtopic.php?f=13&t=3256 @@ -975,13 +983,13 @@ inline void MatrixPanel_I2S_DMA::drawIcon (int *ico, int16_t x, int16_t y, int16 This is example code to driver a p3(2121)64*32 -style RGB LED display. These types of displays do not have memory and need to be refreshed continuously. The display has 2 RGB inputs, 4 inputs to select the active line, a pixel clock input, a latch enable input and an output-enable - input. The display can be seen as 2 64x16 displays consisting of the upper half and the lower half of the display. Each half has a separate + input. The display can be seen as 2 64x16 displays consisting of the upper half and the lower half of the display. Each half has a separate RGB pixel input, the rest of the inputs are shared. Each display half can only show one line of RGB pixels at a time: to do this, the RGB data for the line is input by setting the RGB input pins to the desired value for the first pixel, giving the display a clock pulse, setting the RGB input pins to the desired value for the second pixel, - giving a clock pulse, etc. Do this 64 times to clock in an entire row. The pixels will not be displayed yet: until the latch input is made high, - the display will still send out the previously clocked in line. Pulsing the latch input high will replace the displayed data with the data just + giving a clock pulse, etc. Do this 64 times to clock in an entire row. The pixels will not be displayed yet: until the latch input is made high, + the display will still send out the previously clocked in line. Pulsing the latch input high will replace the displayed data with the data just clocked in. The 4 line select inputs select where the currently active line is displayed: when provided with a binary number (0-15), the latched pixel data @@ -1001,8 +1009,8 @@ inline void MatrixPanel_I2S_DMA::drawIcon (int *ico, int16_t x, int16_t y, int16 Binary code modulation is somewhat like PWM, but easier to implement in our case. First, we define the time we would refresh the display without binary code modulation as the 'frame time'. For, say, a four-bit binary code modulation, the frame time is divided into 15 ticks of equal length. - We also define 4 subframes (0 to 3), defining which LEDs are on and which LEDs are off during that subframe. (Subframes are the same as a - normal frame in non-binary-coded-modulation mode, but are showed faster.) From our (non-monochrome) input image, we take the (8-bit: bit 7 + We also define 4 subframes (0 to 3), defining which LEDs are on and which LEDs are off during that subframe. (Subframes are the same as a + normal frame in non-binary-coded-modulation mode, but are showed faster.) From our (non-monochrome) input image, we take the (8-bit: bit 7 to bit 0) RGB pixel values. If the pixel values have bit 7 set, we turn the corresponding LED on in subframe 3. If they have bit 6 set, we turn on the corresponding LED in subframe 2, if bit 5 is set subframe 1, if bit 4 is set in subframe 0. @@ -1017,5 +1025,5 @@ inline void MatrixPanel_I2S_DMA::drawIcon (int *ico, int16_t x, int16_t y, int16 We use a front buffer/back buffer technique here to make sure the display is refreshed in one go and drawing artefacts do not reach the display. In practice, for small displays this is not really necessarily. - + */ diff --git a/src/ESP32-HUB75-MatrixPanel-leddrivers.cpp b/src/ESP32-HUB75-MatrixPanel-leddrivers.cpp index 0bb20f7..7fa0182 100644 --- a/src/ESP32-HUB75-MatrixPanel-leddrivers.cpp +++ b/src/ESP32-HUB75-MatrixPanel-leddrivers.cpp @@ -27,6 +27,9 @@ void MatrixPanel_I2S_DMA::shiftDriver(const HUB75_I2S_CFG& _cfg){ case HUB75_I2S_CFG::FM6126A: fm6124init(_cfg); break; + case HUB75_I2S_CFG::DP3246_SM5368: + dp3246init(_cfg); + break; case HUB75_I2S_CFG::MBI5124: /* MBI5124 chips must be clocked with positive-edge, since it's LAT signal * resets on clock's rising edge while high @@ -49,6 +52,7 @@ void MatrixPanel_I2S_DMA::fm6124init(const HUB75_I2S_CFG& _cfg) { bool REG2[16] = {0,0,0,0,0, 0,0,0,0,1,0, 0,0,0,0,0}; // a single bit enables the matrix output for (uint8_t _pin:{_cfg.gpio.r1, _cfg.gpio.r2, _cfg.gpio.g1, _cfg.gpio.g2, _cfg.gpio.b1, _cfg.gpio.b2, _cfg.gpio.clk, _cfg.gpio.lat, _cfg.gpio.oe}){ + gpio_reset_pin((gpio_num_t)_pin); // some pins are not in gpio mode after reset => https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/gpio.html#gpio-summary gpio_set_direction((gpio_num_t) _pin, GPIO_MODE_OUTPUT); gpio_set_level((gpio_num_t) _pin, LOW); } @@ -97,4 +101,90 @@ void MatrixPanel_I2S_DMA::fm6124init(const HUB75_I2S_CFG& _cfg) { gpio_set_level((gpio_num_t) _cfg.gpio.lat, LOW); gpio_set_level((gpio_num_t) _cfg.gpio.oe, LOW); // Enable Display CLK_PULSE +} + +void MatrixPanel_I2S_DMA::dp3246init(const HUB75_I2S_CFG& _cfg) { + + ESP_LOGI("LEDdrivers", "MatrixPanel_I2S_DMA - initializing DP3246 driver..."); + + // DP3246 needs positive clock edge + m_cfg.clkphase = true; + + // 15:13 3 000 reserved + // 12:9 4 0000 OE widening (= OE_ADD * 6ns) + // 8 1 0 reserved + // 7:0 8 11111111 Iout = (Igain+1)/256 * 17.6 / Rext + bool REG1[16] = { 0,0,0, 0,0,0,0, 0, 1,1,1,1,1,1,1,1 }; // MSB first + + // 15:11 5 11111 Blanking potential selection, step 77mV, 00000: VDD-0.8V + // 10:8 3 111 Constant current source output inflection point selection + // 7 1 0 Disable dead pixel removel, 1: Enable + // 6 1 0 0->1: (OPEN_DET rising edge) start detection, 0: reset to ready-to-detect state + // 5 1 0 0: Enable black screen power saving, 1: Turn off the black screen to save energy + // 4 1 0 0: Do not enable the fading function, 1: Enable the fade function + // 3 1 0 Reserved + // 2:0 3 000 000: single edge pass, others: double edge transfer + bool REG2[16] = { 1,1,1,1,1, 1,1,1, 0, 0, 0, 0, 0, 0,0,0 }; // MSB first + + for (uint8_t _pin : {_cfg.gpio.r1, _cfg.gpio.r2, _cfg.gpio.g1, _cfg.gpio.g2, _cfg.gpio.b1, _cfg.gpio.b2, _cfg.gpio.clk, _cfg.gpio.lat, _cfg.gpio.oe}) { + gpio_reset_pin((gpio_num_t)_pin); // some pins are not in gpio mode after reset => https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/gpio.html#gpio-summary + gpio_set_direction((gpio_num_t)_pin, GPIO_MODE_OUTPUT); + gpio_set_level((gpio_num_t)_pin, LOW); + } + + gpio_set_level((gpio_num_t)_cfg.gpio.oe, HIGH); // disable Display + + // clear registers - this seems to help with reliability + for (int l = 0; l < PIXELS_PER_ROW; ++l) { + if (l == PIXELS_PER_ROW - 3) { // DP3246 wants the latch dropped for 3 clk cycles + gpio_set_level((gpio_num_t)_cfg.gpio.lat, HIGH); + } + CLK_PULSE + } + + gpio_set_level((gpio_num_t)_cfg.gpio.lat, LOW); + + // Send Data to control register REG1 + for (int l = 0; l < PIXELS_PER_ROW; l++) { + for (uint8_t _pin : {_cfg.gpio.r1, _cfg.gpio.r2, _cfg.gpio.g1, _cfg.gpio.g2, _cfg.gpio.b1, _cfg.gpio.b2}) + gpio_set_level((gpio_num_t)_pin, REG1[l % 16]); // we have 16 bits shifters and write the same value all over the matrix array + + if (l == PIXELS_PER_ROW - 11) { // pull the latch 11 clocks before the end of matrix so that REG1 starts counting to save the value + gpio_set_level((gpio_num_t)_cfg.gpio.lat, HIGH); + } + CLK_PULSE + } + + // drop the latch and save data to the REG1 all over the DP3246 chips + gpio_set_level((gpio_num_t)_cfg.gpio.lat, LOW); + + // Send Data to control register REG2 + for (int l = 0; l < PIXELS_PER_ROW; l++) { + for (uint8_t _pin : {_cfg.gpio.r1, _cfg.gpio.r2, _cfg.gpio.g1, _cfg.gpio.g2, _cfg.gpio.b1, _cfg.gpio.b2}) + gpio_set_level((gpio_num_t)_pin, REG2[l % 16]); // we have 16 bits shifters and we write the same value all over the matrix array + + if (l == PIXELS_PER_ROW - 12) { // pull the latch 12 clocks before the end of matrix so that REG2 starts counting to save the value + gpio_set_level((gpio_num_t)_cfg.gpio.lat, HIGH); + } + CLK_PULSE + } + + // drop the latch and save data to the REG2 all over the DP3246 chips + gpio_set_level((gpio_num_t)_cfg.gpio.lat, LOW); + CLK_PULSE + + // blank data regs to keep matrix clear after manipulations + for (uint8_t _pin : {_cfg.gpio.r1, _cfg.gpio.r2, _cfg.gpio.g1, _cfg.gpio.g2, _cfg.gpio.b1, _cfg.gpio.b2}) + gpio_set_level((gpio_num_t)_pin, LOW); + + for (int l = 0; l < PIXELS_PER_ROW; ++l) { + if (l == PIXELS_PER_ROW - 3) { // DP3246 wants the latch dropped for 3 clk cycles + gpio_set_level((gpio_num_t)_cfg.gpio.lat, HIGH); + } + CLK_PULSE + } + + gpio_set_level((gpio_num_t)_cfg.gpio.lat, LOW); + gpio_set_level((gpio_num_t)_cfg.gpio.oe, LOW); // enable Display + CLK_PULSE } \ No newline at end of file diff --git a/src/ESP32-VirtualMatrixPanel-I2S-DMA.h b/src/ESP32-VirtualMatrixPanel-I2S-DMA.h index cf5324a..ebac204 100644 --- a/src/ESP32-VirtualMatrixPanel-I2S-DMA.h +++ b/src/ESP32-VirtualMatrixPanel-I2S-DMA.h @@ -10,16 +10,16 @@ However, the function of this class has expanded now to also manage the output for - - 1) TWO scan panels = Two rows updated in parallel. - * 64px high panel = sometimes referred to as 1/32 scan - * 32px high panel = sometimes referred to as 1/16 scan - * 16px high panel = sometimes referred to as 1/8 scan - - 2) FOUR scan panels = Four rows updated in parallel - * 32px high panel = sometimes referred to as 1/8 scan - * 16px high panel = sometimes referred to as 1/4 scan - + + 1) TWO scan panels = Two rows updated in parallel. + * 64px high panel = sometimes referred to as 1/32 scan + * 32px high panel = sometimes referred to as 1/16 scan + * 16px high panel = sometimes referred to as 1/8 scan + + 2) FOUR scan panels = Four rows updated in parallel + * 32px high panel = sometimes referred to as 1/8 scan + * 16px high panel = sometimes referred to as 1/4 scan + YouTube: https://www.youtube.com/brianlough Tindie: https://www.tindie.com/stores/brianlough/ Twitter: https://twitter.com/witnessmenow @@ -30,23 +30,41 @@ #include #endif +// #include + struct VirtualCoords { - int16_t x; - int16_t y; - int16_t virt_row; // chain of panels row - int16_t virt_col; // chain of panels col + int16_t x; + int16_t y; + int16_t virt_row; // chain of panels row + int16_t virt_col; // chain of panels col - VirtualCoords() : x(0), y(0) - { - } + VirtualCoords() : x(0), y(0) + { + } }; enum PANEL_SCAN_RATE { - NORMAL_TWO_SCAN, NORMAL_ONE_SIXTEEN, // treated as the same - FOUR_SCAN_32PX_HIGH, - FOUR_SCAN_16PX_HIGH + NORMAL_TWO_SCAN, + NORMAL_ONE_SIXTEEN, // treated as the same + FOUR_SCAN_32PX_HIGH, + FOUR_SCAN_16PX_HIGH, + FOUR_SCAN_64PX_HIGH +}; + +// Chaining approach... From the perspective of the DISPLAY / LED side of the chain of panels. +enum PANEL_CHAIN_TYPE +{ + CHAIN_NONE, + CHAIN_TOP_LEFT_DOWN, + CHAIN_TOP_RIGHT_DOWN, + CHAIN_BOTTOM_LEFT_UP, + CHAIN_BOTTOM_RIGHT_UP, + CHAIN_TOP_LEFT_DOWN_ZZ, /// ZigZag chaining. Might need a big ass cable to do this, all panels right way up. + CHAIN_TOP_RIGHT_DOWN_ZZ, + CHAIN_BOTTOM_RIGHT_UP_ZZ, + CHAIN_BOTTOM_LEFT_UP_ZZ }; #ifdef USE_GFX_ROOT @@ -59,85 +77,92 @@ class VirtualMatrixPanel { public: - int16_t virtualResX; - int16_t virtualResY; - - int16_t vmodule_rows; - int16_t vmodule_cols; - - int16_t panelResX; - int16_t panelResY; - - int16_t dmaResX; // The width of the chain in pixels (as the DMA engine sees it) - - MatrixPanel_I2S_DMA *display; - - VirtualMatrixPanel(MatrixPanel_I2S_DMA &disp, int _vmodule_rows, int _vmodule_cols, int _panelResX, int _panelResY, bool serpentine_chain = true, bool top_down_chain = false) + VirtualMatrixPanel(MatrixPanel_I2S_DMA &disp, int _vmodule_rows, int _vmodule_cols, int _panelResX, int _panelResY, PANEL_CHAIN_TYPE _panel_chain_type = CHAIN_NONE) #ifdef USE_GFX_ROOT - : GFX(_vmodule_cols * _panelResX, _vmodule_rows * _panelResY) + : GFX(_vmodule_cols * _panelResX, _vmodule_rows * _panelResY) #elif !defined NO_GFX - : Adafruit_GFX(_vmodule_cols * _panelResX, _vmodule_rows * _panelResY) + : Adafruit_GFX(_vmodule_cols * _panelResX, _vmodule_rows * _panelResY) #endif - { - this->display = &disp; + { + this->display = &disp; - panelResX = _panelResX; - panelResY = _panelResY; + panel_chain_type = _panel_chain_type; - vmodule_rows = _vmodule_rows; - vmodule_cols = _vmodule_cols; + panelResX = _panelResX; + panelResY = _panelResY; - virtualResX = vmodule_cols * _panelResX; - virtualResY = vmodule_rows * _panelResY; + vmodule_rows = _vmodule_rows; + vmodule_cols = _vmodule_cols; - dmaResX = panelResX * vmodule_rows * vmodule_cols; + virtualResX = vmodule_cols * _panelResX; + virtualResY = vmodule_rows * _panelResY; - /* Virtual Display width() and height() will return a real-world value. For example: - * Virtual Display width: 128 - * Virtual Display height: 64 - * - * So, not values that at 0 to X-1 - */ + dmaResX = panelResX * vmodule_rows * vmodule_cols - 1; - _s_chain_party = serpentine_chain; // serpentine, or 'S' chain? - _chain_top_down = top_down_chain; + /* Virtual Display width() and height() will return a real-world value. For example: + * Virtual Display width: 128 + * Virtual Display height: 64 + * + * So, not values that at 0 to X-1 + */ - coords.x = coords.y = -1; // By default use an invalid co-ordinates that will be rejected by updateMatrixDMABuffer - } + coords.x = coords.y = -1; // By default use an invalid co-ordinates that will be rejected by updateMatrixDMABuffer + } - // equivalent methods of the matrix library so it can be just swapped out. - virtual void drawPixel(int16_t x, int16_t y, uint16_t color); - virtual void fillScreen(uint16_t color); // overwrite adafruit implementation - virtual void fillScreenRGB888(uint8_t r, uint8_t g, uint8_t b); - - void clearScreen() { display->clearScreen(); } - void drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b); + // equivalent methods of the matrix library so it can be just swapped out. + void drawPixel(int16_t x, int16_t y, uint16_t color); // overwrite adafruit implementation + void fillScreen(uint16_t color); // overwrite adafruit implementation + void setRotation(int rotate); // overwrite adafruit implementation + + void fillScreenRGB888(uint8_t r, uint8_t g, uint8_t b); + void clearScreen() { display->clearScreen(); } + void drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b); #ifdef USE_GFX_ROOT - // 24bpp FASTLED CRGB colour struct support - void fillScreen(CRGB color); - void drawPixel(int16_t x, int16_t y, CRGB color); + // 24bpp FASTLED CRGB colour struct support + void fillScreen(CRGB color); + void drawPixel(int16_t x, int16_t y, CRGB color); #endif - uint16_t color444(uint8_t r, uint8_t g, uint8_t b) { return display->color444(r, g, b); } - uint16_t color565(uint8_t r, uint8_t g, uint8_t b) { return display->color565(r, g, b); } - uint16_t color333(uint8_t r, uint8_t g, uint8_t b) { return display->color333(r, g, b); } + uint16_t color444(uint8_t r, uint8_t g, uint8_t b) + { + return display->color444(r, g, b); + } + uint16_t color565(uint8_t r, uint8_t g, uint8_t b) { return display->color565(r, g, b); } + uint16_t color333(uint8_t r, uint8_t g, uint8_t b) { return display->color333(r, g, b); } - void flipDMABuffer() { display->flipDMABuffer(); } - void drawDisplayTest(); - void setRotate(bool rotate); + void flipDMABuffer() { display->flipDMABuffer(); } + void drawDisplayTest(); - void setPhysicalPanelScanRate(PANEL_SCAN_RATE rate); + void setPhysicalPanelScanRate(PANEL_SCAN_RATE rate); + void setZoomFactor(int scale); -protected: - virtual VirtualCoords getCoords(int16_t &x, int16_t &y); - VirtualCoords coords; +private: + MatrixPanel_I2S_DMA *display; - bool _s_chain_party = true; // Are we chained? Ain't no party like a... - bool _chain_top_down = false; // is the ESP at the top or bottom of the matrix of devices? - bool _rotate = false; + PANEL_CHAIN_TYPE panel_chain_type; + PANEL_SCAN_RATE panel_scan_rate = NORMAL_TWO_SCAN; - PANEL_SCAN_RATE _panelScanRate = NORMAL_TWO_SCAN; + virtual VirtualCoords getCoords(int16_t x, int16_t y); + VirtualCoords coords; + + int16_t virtualResX; + int16_t virtualResY; + + int16_t _virtualResX; ///< Display width as modified by current rotation + int16_t _virtualResY; ///< Display height as modified by current rotation + + int16_t vmodule_rows; + int16_t vmodule_cols; + + int16_t panelResX; + int16_t panelResY; + + int16_t dmaResX; // The width of the chain in pixels (as the DMA engine sees it) + + int _rotate = 0; + + int _scale_factor = 0; }; // end Class header @@ -146,202 +171,368 @@ protected: * Updates the private class member variable 'coords', so no need to use the return value. * Not thread safe, but not a concern for ESP32 sketch anyway... I think. */ -inline VirtualCoords VirtualMatrixPanel::getCoords(int16_t &x, int16_t &y) +inline VirtualCoords VirtualMatrixPanel::getCoords(int16_t virt_x, int16_t virt_y) { - // Serial.println("Called Base."); - coords.x = coords.y = -1; // By defalt use an invalid co-ordinates that will be rejected by updateMatrixDMABuffer + +#if !defined NO_GFX + // I don't give any support if Adafruit GFX isn't being used. + + if (virt_x < 0 || virt_x >= _width || virt_y < 0 || virt_y >= _height) // _width and _height are defined in the adafruit constructor + { // Co-ordinates go from 0 to X-1 remember! otherwise they are out of range! + coords.x = coords.y = -1; // By defalt use an invalid co-ordinates that will be rejected by updateMatrixDMABuffer + return coords; + } +#else + + if (virt_x < 0 || virt_x >= _virtualResX || virt_y < 0 || virt_y >= _virtualResY) // _width and _height are defined in the adafruit constructor + { // Co-ordinates go from 0 to X-1 remember! otherwise they are out of range! + coords.x = coords.y = -1; // By defalt use an invalid co-ordinates that will be rejected by updateMatrixDMABuffer + return coords; + } + +#endif + + // Do we want to rotate? + switch (_rotate) { + case 0: //no rotation, do nothing + break; + + case (1): //90 degree rotation + { + int16_t temp_x = virt_x; + virt_x = virt_y; + virt_y = virtualResY - 1 - temp_x; + break; + } - // Do we want to rotate? - if (_rotate) - { - int16_t temp_x = x; - x = y; - y = virtualResY - 1 - temp_x; - } + case (2): //180 rotation + { + virt_x = virtualResX - 1 - virt_x; + virt_y = virtualResY - 1 - virt_y; + break; + } + + case (3): //270 rotation + { + int16_t temp_x = virt_x; + virt_x = virtualResX - 1 - virt_y; + virt_y = temp_x; + break; + } + } + + int row = (virt_y / panelResY); // 0 indexed + switch (panel_chain_type) + { + case (CHAIN_TOP_RIGHT_DOWN): + { + if ((row % 2) == 1) + { // upside down panel + + // Serial.printf("Condition 1, row %d ", row); + + // reversed for the row + coords.x = dmaResX - virt_x - (row * virtualResX); + + // y co-ord inverted within the panel + coords.y = panelResY - 1 - (virt_y % panelResY); + } + else + { + // Serial.printf("Condition 2, row %d ", row); + coords.x = ((vmodule_rows - (row + 1)) * virtualResX) + virt_x; + coords.y = (virt_y % panelResY); + } + } + break; + + case (CHAIN_TOP_RIGHT_DOWN_ZZ): + { + // Right side up. Starting from top right all the way down. + // Connected in a Zig Zag manner = some long ass cables being used potentially + + // Serial.printf("Condition 2, row %d ", row); + coords.x = ((vmodule_rows - (row + 1)) * virtualResX) + virt_x; + coords.y = (virt_y % panelResY); + } + break; + + case (CHAIN_TOP_LEFT_DOWN): // OK -> modulus opposite of CHAIN_TOP_RIGHT_DOWN + { + if ((row % 2) == 0) + { // reversed panel + + // Serial.printf("Condition 1, row %d ", row); + coords.x = dmaResX - virt_x - (row * virtualResX); + + // y co-ord inverted within the panel + coords.y = panelResY - 1 - (virt_y % panelResY); + } + else + { + // Serial.printf("Condition 2, row %d ", row); + coords.x = ((vmodule_rows - (row + 1)) * virtualResX) + virt_x; + coords.y = (virt_y % panelResY); + } + } + break; + + case (CHAIN_TOP_LEFT_DOWN_ZZ): + { + // Serial.printf("Condition 2, row %d ", row); + coords.x = ((vmodule_rows - (row + 1)) * virtualResX) + virt_x; + coords.y = (virt_y % panelResY); + } + break; + + case (CHAIN_BOTTOM_LEFT_UP): // + { + row = vmodule_rows - row - 1; + + if ((row % 2) == 1) + { + // Serial.printf("Condition 1, row %d ", row); + coords.x = ((vmodule_rows - (row + 1)) * virtualResX) + virt_x; + coords.y = (virt_y % panelResY); + } + else + { // inverted panel + + // Serial.printf("Condition 2, row %d ", row); + coords.x = dmaResX - (row * virtualResX) - virt_x; + coords.y = panelResY - 1 - (virt_y % panelResY); + } + } + break; + + case (CHAIN_BOTTOM_LEFT_UP_ZZ): // + { + row = vmodule_rows - row - 1; + // Serial.printf("Condition 1, row %d ", row); + coords.x = ((vmodule_rows - (row + 1)) * virtualResX) + virt_x; + coords.y = (virt_y % panelResY); + } + break; + + case (CHAIN_BOTTOM_RIGHT_UP): // OK -> modulus opposite of CHAIN_BOTTOM_LEFT_UP + { + row = vmodule_rows - row - 1; + + if ((row % 2) == 0) + { // right side up + + // Serial.printf("Condition 1, row %d ", row); + // refersed for the row + coords.x = ((vmodule_rows - (row + 1)) * virtualResX) + virt_x; + coords.y = (virt_y % panelResY); + } + else + { // inverted panel + + // Serial.printf("Condition 2, row %d ", row); + coords.x = dmaResX - (row * virtualResX) - virt_x; + coords.y = panelResY - 1 - (virt_y % panelResY); + } + } + break; + + case (CHAIN_BOTTOM_RIGHT_UP_ZZ): + { + // Right side up. Starting bottom right all the way up. + // Connected in a Zig Zag manner = some long ass cables being used potentially + + row = vmodule_rows - row - 1; + // Serial.printf("Condition 2, row %d ", row); + coords.x = ((vmodule_rows - (row + 1)) * virtualResX) + virt_x; + coords.y = (virt_y % panelResY); + } + break; + + // Q: 1 row!? Why? + // A: In cases people are only using virtual matrix panel for panels of non-standard scan rates. + default: + coords.x = virt_x; coords.y = virt_y; + break; + + } // end switch + + + /* START: Pixel remapping AGAIN to convert TWO parallel scanline output that the + * the underlying hardware library is designed for (because + * there's only 2 x RGB pins... and convert this to 1/4 or something + */ + + if ((panel_scan_rate == FOUR_SCAN_32PX_HIGH) || (panel_scan_rate == FOUR_SCAN_64PX_HIGH)) + { + + if (panel_scan_rate == FOUR_SCAN_64PX_HIGH) + { + // https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/issues/345#issuecomment-1510401192 + if ((virt_y & 8) != ((virt_y & 16) >> 1)) { virt_y = (virt_y & 0b11000) ^ 0b11000 + (virt_y & 0b11100111); } + } + + + /* Convert Real World 'VirtualMatrixPanel' co-ordinates (i.e. Real World pixel you're looking at + on the panel or chain of panels, per the chaining configuration) to a 1/8 panels + double 'stretched' and 'squished' coordinates which is what needs to be sent from the + DMA buffer. + + Note: Look at the FourScanPanel example code and you'll see that the DMA buffer is setup + as if the panel is 2 * W and 0.5 * H ! + */ + + if ((virt_y & 8) == 0) + { + coords.x += ((coords.x / panelResX) + 1) * panelResX; // 1st, 3rd 'block' of 8 rows of pixels, offset by panel width in DMA buffer + } + else + { + coords.x += (coords.x / panelResX) * panelResX; // 2nd, 4th 'block' of 8 rows of pixels, offset by panel width in DMA buffer + } + + // http://cpp.sh/4ak5u + // Real number of DMA y rows is half reality + // coords.y = (y / 16)*8 + (y & 0b00000111); + coords.y = (virt_y >> 4) * 8 + (virt_y & 0b00000111); + } + else if (panel_scan_rate == FOUR_SCAN_16PX_HIGH) + { + if ((virt_y & 8) == 0) + { + coords.x += (panelResX >> 2) * (((coords.x & 0xFFF0) >> 4) + 1); // 1st, 3rd 'block' of 8 rows of pixels, offset by panel width in DMA buffer + } + else + { + coords.x += (panelResX >> 2) * (((coords.x & 0xFFF0) >> 4)); // 2nd, 4th 'block' of 8 rows of pixels, offset by panel width in DMA buffer + } + + if (virt_y < 32) + coords.y = (virt_y >> 4) * 8 + (virt_y & 0b00000111); + else + { + coords.y = ((virt_y - 32) >> 4) * 8 + (virt_y & 0b00000111); + coords.x += 256; + } + } - if (x < 0 || x >= virtualResX || y < 0 || y >= virtualResY) - { // Co-ordinates go from 0 to X-1 remember! otherwise they are out of range! - // Serial.printf("VirtualMatrixPanel::getCoords(): Invalid virtual display coordinate. x,y: %d, %d\r\n", x, y); return coords; - } - - // Stupidity check - if ((vmodule_rows == 1) && (vmodule_cols == 1)) // single panel... - { - coords.x = x; - coords.y = y; - } - else - { - uint8_t row = (y / panelResY) + 1; // a non indexed 0 row number - if ((_s_chain_party && !_chain_top_down && (row % 2 == 0)) // serpentine vertically stacked chain starting from bottom row (i.e. ESP closest to ground), upwards - || - (_s_chain_party && _chain_top_down && (row % 2 != 0)) // serpentine vertically stacked chain starting from the sky downwards - ) - { - // First portion gets you to the correct offset for the row you need - // Second portion inverts the x on the row - coords.x = ((y / panelResY) * (virtualResX)) + (virtualResX - x) - 1; - - // inverts the y the row - coords.y = panelResY - 1 - (y % panelResY); - } - else - { - // Normal chain pixel co-ordinate - coords.x = x + ((y / panelResY) * (virtualResX)); - coords.y = y % panelResY; - } - } - - // Reverse co-ordinates if panel chain from ESP starts from the TOP RIGHT - if (_chain_top_down) - { - /* - const HUB75_I2S_CFG _cfg = this->display->getCfg(); - coords.x = (_cfg.mx_width * _cfg.chain_length - 1) - coords.x; - coords.y = (_cfg.mx_height-1) - coords.y; - */ - coords.x = (dmaResX - 1) - coords.x; - coords.y = (panelResY - 1) - coords.y; - } - - /* START: Pixel remapping AGAIN to convert TWO parallel scanline output that the - * the underlying hardware library is designed for (because - * there's only 2 x RGB pins... and convert this to 1/4 or something - */ - if (_panelScanRate == FOUR_SCAN_32PX_HIGH) - { - /* Convert Real World 'VirtualMatrixPanel' co-ordinates (i.e. Real World pixel you're looking at - on the panel or chain of panels, per the chaining configuration) to a 1/8 panels - double 'stretched' and 'squished' coordinates which is what needs to be sent from the - DMA buffer. - - Note: Look at the FourScanPanel example code and you'll see that the DMA buffer is setup - as if the panel is 2 * W and 0.5 * H ! - */ - - /* - Serial.print("VirtualMatrixPanel Mapping ("); Serial.print(x, DEC); Serial.print(","); Serial.print(y, DEC); Serial.print(") "); - // to - Serial.print("to ("); Serial.print(coords.x, DEC); Serial.print(","); Serial.print(coords.y, DEC); Serial.println(") "); - */ - if ((y & 8) == 0) - { - coords.x += ((coords.x / panelResX) + 1) * panelResX; // 1st, 3rd 'block' of 8 rows of pixels, offset by panel width in DMA buffer - } - else - { - coords.x += (coords.x / panelResX) * panelResX; // 2nd, 4th 'block' of 8 rows of pixels, offset by panel width in DMA buffer - } - - // http://cpp.sh/4ak5u - // Real number of DMA y rows is half reality - // coords.y = (y / 16)*8 + (y & 0b00000111); - coords.y = (y >> 4) * 8 + (y & 0b00000111); - - /* - Serial.print("OneEightScanPanel Mapping ("); Serial.print(x, DEC); Serial.print(","); Serial.print(y, DEC); Serial.print(") "); - // to - Serial.print("to ("); Serial.print(coords.x, DEC); Serial.print(","); Serial.print(coords.y, DEC); Serial.println(") "); - */ - } - else if (_panelScanRate == FOUR_SCAN_16PX_HIGH) - { - if ((y & 8) == 0) - { - coords.x += (panelResX >> 2) * (((coords.x & 0xFFF0) >> 4) + 1); // 1st, 3rd 'block' of 8 rows of pixels, offset by panel width in DMA buffer - } - else - { - coords.x += (panelResX >> 2) * (((coords.x & 0xFFF0) >> 4)); // 2nd, 4th 'block' of 8 rows of pixels, offset by panel width in DMA buffer - } - - if (y < 32) - coords.y = (y >> 4) * 8 + (y & 0b00000111); - else - { - coords.y = ((y - 32) >> 4) * 8 + (y & 0b00000111); - coords.x += 256; - } - } - - // Serial.print("Mapping to x: "); Serial.print(coords.x, DEC); Serial.print(", y: "); Serial.println(coords.y, DEC); - return coords; } inline void VirtualMatrixPanel::drawPixel(int16_t x, int16_t y, uint16_t color) { // adafruit virtual void override - getCoords(x, y); - this->display->drawPixel(coords.x, coords.y, color); + + if (_scale_factor > 1) // only from 2 and beyond + { + int16_t scaled_x_start_pos = x * _scale_factor; + int16_t scaled_y_start_pos = y * _scale_factor; + + for (int16_t x = 0; x < _scale_factor; x++) { + for (int16_t y = 0; y < _scale_factor; y++) { + VirtualCoords result = this->getCoords(scaled_x_start_pos+x, scaled_y_start_pos+y); + // Serial.printf("Requested virtual x,y coord (%d, %d), got phyical chain coord of (%d,%d)\n", x,y, coords.x, coords.y); + this->display->drawPixel(result.x, result.y, color); + } + } + } + else + { + this->getCoords(x, y); + // Serial.printf("Requested virtual x,y coord (%d, %d), got phyical chain coord of (%d,%d)\n", x,y, coords.x, coords.y); + this->display->drawPixel(coords.x, coords.y, color); + } } inline void VirtualMatrixPanel::fillScreen(uint16_t color) { // adafruit virtual void override - this->display->fillScreen(color); + this->display->fillScreen(color); } inline void VirtualMatrixPanel::fillScreenRGB888(uint8_t r, uint8_t g, uint8_t b) { - this->display->fillScreenRGB888(r, g, b); + this->display->fillScreenRGB888(r, g, b); } inline void VirtualMatrixPanel::drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b) { - getCoords(x, y); - this->display->drawPixelRGB888(coords.x, coords.y, r, g, b); + this->getCoords(x, y); + this->display->drawPixelRGB888(coords.x, coords.y, r, g, b); } #ifdef USE_GFX_ROOT // Support for CRGB values provided via FastLED inline void VirtualMatrixPanel::drawPixel(int16_t x, int16_t y, CRGB color) { - getCoords(x, y); - this->display->drawPixel(coords.x, coords.y, color); + this->getCoords(x, y); + this->display->drawPixel(coords.x, coords.y, color); } inline void VirtualMatrixPanel::fillScreen(CRGB color) { - this->display->fillScreen(color); + this->display->fillScreen(color); } #endif -inline void VirtualMatrixPanel::setRotate(bool rotate) +inline void VirtualMatrixPanel::setRotation(int rotate) { - _rotate = rotate; + if(rotate < 4 && rotate >= 0) + _rotate = rotate; -#ifndef NO_GFX - // We don't support rotation by degrees. - if (rotate) - { - setRotation(1); + // Change the _width and _height variables used by the underlying adafruit gfx library. + // Actual pixel rotation / mapping is done in the getCoords function. + rotation = (rotate & 3); + switch (rotation) { + case 0: // nothing + case 2: // 180 + _virtualResX = virtualResX; + _virtualResY = virtualResY; + +#if !defined NO_GFX + _width = virtualResX; // adafruit base class + _height = virtualResY; // adafruit base class +#endif + break; + case 1: + case 3: + _virtualResX = virtualResY; + _virtualResY = virtualResX; + +#if !defined NO_GFX + _width = virtualResY; // adafruit base class + _height = virtualResX; // adafruit base class +#endif + break; } - else - { - setRotation(0); - } -#endif + + } inline void VirtualMatrixPanel::setPhysicalPanelScanRate(PANEL_SCAN_RATE rate) { - _panelScanRate = rate; + panel_scan_rate = rate; +} + +inline void VirtualMatrixPanel::setZoomFactor(int scale) +{ + if(scale < 5 && scale > 0) + _scale_factor = scale; + } #ifndef NO_GFX inline void VirtualMatrixPanel::drawDisplayTest() { - this->display->setFont(&FreeSansBold12pt7b); - this->display->setTextColor(this->display->color565(255, 255, 0)); - this->display->setTextSize(1); + // Write to the underlying panels only via the dma_display instance. + this->display->setFont(&FreeSansBold12pt7b); + this->display->setTextColor(this->display->color565(255, 255, 0)); + this->display->setTextSize(1); - for (int panel = 0; panel < vmodule_cols * vmodule_rows; panel++) - { - int top_left_x = (panel == 0) ? 0 : (panel * panelResX); - this->display->drawRect(top_left_x, 0, panelResX, panelResY, this->display->color565(0, 255, 0)); - this->display->setCursor(panel * panelResX, panelResY - 3); - this->display->print((vmodule_cols * vmodule_rows) - panel); - } + for (int panel = 0; panel < vmodule_cols * vmodule_rows; panel++) + { + int top_left_x = (panel == 0) ? 0 : (panel * panelResX); + this->display->drawRect(top_left_x, 0, panelResX, panelResY, this->display->color565(0, 255, 0)); + this->display->setCursor((panel * panelResX) + 2, panelResY - 4); + this->display->print((vmodule_cols * vmodule_rows) - panel); + } } #endif diff --git a/src/platforms/esp32/esp32_i2s_parallel_dma.cpp b/src/platforms/esp32/esp32_i2s_parallel_dma.cpp index 4cfd01a..181503c 100644 --- a/src/platforms/esp32/esp32_i2s_parallel_dma.cpp +++ b/src/platforms/esp32/esp32_i2s_parallel_dma.cpp @@ -28,54 +28,35 @@ Modified heavily for the ESP32 HUB75 DMA library by: #include #include -#include // Need to make sure thi is uncommented to get ESP_LOG output on (Arduino) Serial output!!!! +#if defined (ARDUINO_ARCH_ESP32) +#include +#endif + #include #include // Get CPU freq function. #include -/* -callback shiftCompleteCallback; -void setShiftCompleteCallback(callback f) { - shiftCompleteCallback = f; -} + volatile bool previousBufferFree = true; -volatile int previousBufferOutputLoopCount = 0; -volatile bool previousBufferFree = true; - -static void IRAM_ATTR irq_hndlr(void* arg) { // if we use I2S1 (default) - - SET_PERI_REG_BITS(I2S_INT_CLR_REG(ESP32_I2S_DEVICE), I2S_OUT_EOF_INT_CLR_V, 1, I2S_OUT_EOF_INT_CLR_S); - - previousBufferFree = true; - - - -} // end irq_hndlr -*/ - - volatile int DRAM_ATTR active_dma_buffer_output_count = 0; - - void IRAM_ATTR irq_hndlr(void* arg) { + static void IRAM_ATTR i2s_isr(void* arg) { + // From original Sprite_TM Code + //REG_WRITE(I2S_INT_CLR_REG(1), (REG_READ(I2S_INT_RAW_REG(1)) & 0xffffffc0) | 0x3f); + // Clear flag so we can get retriggered SET_PERI_REG_BITS(I2S_INT_CLR_REG(ESP32_I2S_DEVICE), I2S_OUT_EOF_INT_CLR_V, 1, I2S_OUT_EOF_INT_CLR_S); + + // at this point, the previously active buffer is free, go ahead and write to it + previousBufferFree = true; + } - active_dma_buffer_output_count++; + bool DRAM_ATTR i2s_parallel_is_previous_buffer_free() { + return previousBufferFree; + } - /* - if ( active_dma_buffer_output_count++ ) - { - // Disable DMA chain EOF interrupt until next requested flipbuffer. - // Otherwise we're needlessly generating interrupts we don't care about. - //SET_PERI_REG_BITS(I2S_INT_ENA_REG(ESP32_I2S_DEVICE), I2S_OUT_EOF_INT_ENA_V, 0, I2S_OUT_EOF_INT_ENA_S); - active_dma_buffer_output_count = 0; - } - */ - - } // end irq_hndlr // Static i2s_dev_t* getDev() @@ -221,22 +202,6 @@ static void IRAM_ATTR irq_hndlr(void* arg) { // if we use I2S1 (default) ESP_LOGD("ESP32/S2", "Requested output clock frequency: %d Mhz", (freq/1000000)); // What is the current CPU frequency? -/* - rtc_cpu_freq_config_t conf; - rtc_clk_cpu_freq_get_config(&conf); - auto source_freq = conf.source_freq_mhz; - - - ESP_LOGD("ESP32/S2", "PLL (source) frequency: %d", source_freq); - ESP_LOGD("ESP32/S2", "CPU frequency: %d", conf.freq_mhz); -*/ - - /* - if(_div_num < 2 || _div_num > 16) { - - return false; - } - */ // Calculate clock divider for ESP32-S2 #if defined (CONFIG_IDF_TARGET_ESP32S2) @@ -303,7 +268,7 @@ static void IRAM_ATTR irq_hndlr(void* arg) { // if we use I2S1 (default) #endif - output_freq = output_freq + 0; // work around arudino 'unused var' issue if debug isn't enabled. + output_freq = output_freq + 0; // work around arudino 'unused var' issue if debug isn't enabled. ESP_LOGI("ESP32/S2", "Output frequency is %ld Mhz??", (output_freq/1000000/i2s_parallel_get_memory_width(ESP32_I2S_DEVICE, 16))); @@ -411,34 +376,23 @@ static void IRAM_ATTR irq_hndlr(void* arg) { // if we use I2S1 (default) dev->conf1.tx_stop_en = 0; dev->timing.val = 0; + + // If we have double buffering, then allocate an interrupt service routine function + // that can be used for I2S0/I2S1 created interrupts. + + // Setup I2S Interrupt + SET_PERI_REG_BITS(I2S_INT_ENA_REG(ESP32_I2S_DEVICE), I2S_OUT_EOF_INT_ENA_V, 1, I2S_OUT_EOF_INT_ENA_S); + + // Allocate a level 1 intterupt: lowest priority, as ISR isn't urgent and may take a long time to complete + esp_intr_alloc(irq_source, (int)(ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL1), i2s_isr, NULL, NULL); + - /* If we have double buffering, then allocate an interrupt service routine function - * that can be used for I2S0/I2S1 created interrupts. - */ - if (_double_dma_buffer) { - - // Get ISR setup - esp_err_t err = esp_intr_alloc(irq_source, - (int)(ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL1), - irq_hndlr, NULL, NULL); - - if(err) { - ESP_LOGE("ESP32/S2", "init() Failed to setup interrupt request handeler."); - return false; - } - - // Don't do this here. Don't enable just yet. - // dev->int_ena.out_eof = 1; - } - - #if defined (CONFIG_IDF_TARGET_ESP32S2) ESP_LOGD("ESP32-S2", "init() GPIO and clock configuration set for ESP32-S2"); #else ESP_LOGD("ESP32-ORIG", "init() GPIO and clock configuration set for ESP32"); #endif - return true; } @@ -490,7 +444,7 @@ static void IRAM_ATTR irq_hndlr(void* arg) { // if we use I2S1 (default) ESP_LOGD("ESP32/S2", "Allocating the second buffer (double buffer enabled)."); - _dmadesc_b= (HUB75_DMA_DESCRIPTOR_T*)heap_caps_malloc(sizeof(HUB75_DMA_DESCRIPTOR_T) * len, MALLOC_CAP_DMA); + _dmadesc_b = (HUB75_DMA_DESCRIPTOR_T*)heap_caps_malloc(sizeof(HUB75_DMA_DESCRIPTOR_T) * len, MALLOC_CAP_DMA); if (_dmadesc_b == nullptr) { @@ -611,20 +565,15 @@ static void IRAM_ATTR irq_hndlr(void* arg) { // if we use I2S1 (default) } // end - void Bus_Parallel16::flip_dma_output_buffer(int ¤t_back_buffer_id) // pass by reference so we can change in main matrixpanel class + void Bus_Parallel16::flip_dma_output_buffer(int buffer_id) // pass by reference so we can change in main matrixpanel class { // Setup interrupt handler which is focussed only on the (page 322 of Tech. Ref. Manual) // "I2S_OUT_EOF_INT: Triggered when rxlink has finished sending a packet" (when dma linked list with eof = 1 is hit) - //_dev->int_ena.out_eof = 1; - _dev->int_ena.out_eof = 1; // enable interrupt - - if ( current_back_buffer_id == 1) { + + if ( buffer_id == 1) { - _dmadesc_a[_dmadesc_last].qe.stqe_next = &_dmadesc_b[0]; // Start sending out _dmadesc_b (or buffer 1) - - active_dma_buffer_output_count = 0; - while (!active_dma_buffer_output_count) {} + _dmadesc_a[_dmadesc_last].qe.stqe_next = &_dmadesc_b[0]; // Start sending out _dmadesc_b (or buffer 1) //fix _dmadesc_ loop issue #407 //need to connect the up comming _dmadesc_ not the old one @@ -633,17 +582,16 @@ static void IRAM_ATTR irq_hndlr(void* arg) { // if we use I2S1 (default) } else { _dmadesc_b[_dmadesc_last].qe.stqe_next = &_dmadesc_a[0]; - - active_dma_buffer_output_count = 0; - while (!active_dma_buffer_output_count) {} - _dmadesc_a[_dmadesc_last].qe.stqe_next = &_dmadesc_a[0]; } - current_back_buffer_id ^= 1; - // Disable intterupt - _dev->int_ena.out_eof = 0; + previousBufferFree = false; + //while (i2s_parallel_is_previous_buffer_free() == false) {} + while (!previousBufferFree); + + + } // end flip diff --git a/src/platforms/esp32/esp32_i2s_parallel_dma.hpp b/src/platforms/esp32/esp32_i2s_parallel_dma.hpp index dd6c1f3..b0d3642 100644 --- a/src/platforms/esp32/esp32_i2s_parallel_dma.hpp +++ b/src/platforms/esp32/esp32_i2s_parallel_dma.hpp @@ -49,13 +49,16 @@ Contributors: #define DMA_MAX (4096-4) -#ifndef ESP32_I2S_DEVICE - #define ESP32_I2S_DEVICE I2S_NUM_0 -#endif - // The type used for this SoC #define HUB75_DMA_DESCRIPTOR_T lldesc_t + +#if defined (CONFIG_IDF_TARGET_ESP32S2) +#define ESP32_I2S_DEVICE I2S_NUM_0 +#else +#define ESP32_I2S_DEVICE I2S_NUM_1 +#endif + //---------------------------------------------------------------------------- void IRAM_ATTR irq_hndlr(void* arg); @@ -119,7 +122,7 @@ i2s_dev_t* getDev(); void dma_transfer_start(); void dma_transfer_stop(); - void flip_dma_output_buffer(int ¤t_back_buffer_id); + void flip_dma_output_buffer(int buffer_id); private: diff --git a/src/platforms/esp32s3/gdma_lcd_parallel16.cpp b/src/platforms/esp32s3/gdma_lcd_parallel16.cpp index 8f90462..d064210 100644 --- a/src/platforms/esp32s3/gdma_lcd_parallel16.cpp +++ b/src/platforms/esp32s3/gdma_lcd_parallel16.cpp @@ -1,4 +1,4 @@ -/* +/********************************************************************************************* Simple example of using the ESP32-S3's LCD peripheral for general-purpose (non-LCD) parallel data output with DMA. Connect 8 LEDs (or logic analyzer), cycles through a pattern among them at about 1 Hz. @@ -15,33 +15,30 @@ PLEASE SUPPORT THEM! - */ + ********************************************************************************************/ #if __has_include () // Stop compile errors: /src/platforms/esp32s3/gdma_lcd_parallel16.hpp:64:10: fatal error: hal/lcd_ll.h: No such file or directory -#ifdef ARDUINO_ARCH_ESP32 - #include -#endif + #ifdef ARDUINO_ARCH_ESP32 + #include + #endif #include "gdma_lcd_parallel16.hpp" #include "esp_attr.h" -//#if (CORE_DEBUG_LEVEL > ARDUHAL_LOG_LEVEL_NONE) || (ARDUHAL_LOG_LEVEL > ARDUHAL_LOG_LEVEL_NONE) -// static const char* TAG = "gdma_lcd_parallel16"; -//#endif - - //static int _dmadesc_a_idx = 0; - //static int _dmadesc_b_idx = 0; - - - dma_descriptor_t desc; // DMA descriptor for testing /* + dma_descriptor_t desc; // DMA descriptor for testing + uint8_t data[8][312]; // Transmit buffer (2496 bytes total) uint16_t* dmabuff2; */ + + DRAM_ATTR volatile bool previousBufferFree = true; + // End-of-DMA-transfer callback - IRAM_ATTR bool dma_callback(gdma_channel_handle_t dma_chan, + IRAM_ATTR bool gdma_on_trans_eof_callback(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data) { + // This DMA callback seems to trigger a moment before the last data has // issued (buffering between DMA & LCD peripheral?), so pause a moment // before stopping LCD data out. The ideal delay may depend on the LCD @@ -53,7 +50,10 @@ // the transfer has finished, and the same flag is set later to trigger // the next transfer. - LCD_CAM.lcd_user.lcd_start = 0; + //LCD_CAM.lcd_user.lcd_start = 0; + + previousBufferFree = true; + return true; } @@ -83,7 +83,7 @@ // Reset LCD bus LCD_CAM.lcd_user.lcd_reset = 1; - esp_rom_delay_us(100); + esp_rom_delay_us(1000); // uint32_t lcd_clkm_div_num = ((160000000 + 1) / _cfg.bus_freq); // ESP_LOGI("", "Clock divider is %d", lcd_clkm_div_num); @@ -99,45 +99,57 @@ // LCD_CAM_LCD_CLK_SEL Select LCD module source clock. 0: clock source is disabled. 1: XTAL_CLK. 2: PLL_D2_CLK. 3: PLL_F160M_CLK. (R/W) LCD_CAM.lcd_clock.lcd_clk_sel = 3; // Use 160Mhz Clock Source - + LCD_CAM.lcd_clock.lcd_ck_out_edge = 0; // PCLK low in 1st half cycle LCD_CAM.lcd_clock.lcd_ck_idle_edge = 0; // PCLK low idle - - LCD_CAM.lcd_clock.lcd_clkcnt_n = 1; // Should never be zero - - //LCD_CAM.lcd_clock.lcd_clk_equ_sysclk = 0; // PCLK = CLK / (CLKCNT_N+1) - LCD_CAM.lcd_clock.lcd_clk_equ_sysclk = 1; // PCLK = CLK / 1 (... so 160Mhz still) + + LCD_CAM.lcd_clock.lcd_clkcnt_n = 1; // Should never be zero + + //LCD_CAM.lcd_clock.lcd_clk_equ_sysclk = 0; // PCLK = CLK / (CLKCNT_N+1) + LCD_CAM.lcd_clock.lcd_clk_equ_sysclk = 1; // PCLK = CLK / 1 (... so 160Mhz still) - if (_cfg.psram_clk_override) // fastest speed I can get PSRAM to work before nothing shows + // https://esp32.com/viewtopic.php?f=5&t=24459&start=80#p94487 + /* Re: ESP32-S3 LCD and I2S FULL documentation + * by ESP_Sprite ยป Fri Mar 25, 2022 2:06 am + * + * Are you sure you are staying within the limits of the psram throughput? If GDMA can't fetch data fast + * enough it leads to corruption. Also keep in mind that worst case scenario, the gdma can only use half of + * the bandwidth of the psram peripheral (as it's round-robin shared with the CPUs). + */ + + // Fastest speed I can get with Octoal PSRAM to work before nothing shows. Based on manual testing. + // If using an ESP32-S3 with slower (half the bandwidth) Q-SPI (Quad), then the divisor will need to be '20' (8Mhz) which wil be flickery! + if (_cfg.psram_clk_override) { ESP_LOGI("S3", "DMA buffer is on PSRAM. Limiting clockspeed...."); - LCD_CAM.lcd_clock.lcd_clkm_div_num = 10; //16mhz is the fasted the Octal PSRAM can support it seems + //LCD_CAM.lcd_clock.lcd_clkm_div_num = 10; //16mhz is the fasted the Octal PSRAM can support it seems from faptastic's testing using an N8R8 variant (Octal SPI PSRAM). + + // https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/issues/441#issuecomment-1513631890 + LCD_CAM.lcd_clock.lcd_clkm_div_num = 12; // 13Mhz is the fastest when the DMA memory is needed to service other peripherals as well. } else { - - auto freq = (_cfg.bus_freq); - - auto _div_num = 8; // 20Mhz - if (freq < 20000000L) - { - _div_num = 12; // 13Mhz - } - else if (freq > 20000000L) - { - _div_num = 6; // 26Mhz --- likely to have noise without a good connection - } - + + auto freq = (_cfg.bus_freq); + + auto _div_num = 8; // 20Mhz + if (freq < 20000000L) { + _div_num = 12; // 13Mhz + } + else if (freq > 20000000L) { + _div_num = 6; // 26Mhz --- likely to have noise without a good connection + } + //LCD_CAM.lcd_clock.lcd_clkm_div_num = lcd_clkm_div_num; LCD_CAM.lcd_clock.lcd_clkm_div_num = _div_num; //3; } - ESP_LOGI("S3", "Clock divider is %d", LCD_CAM.lcd_clock.lcd_clkm_div_num); - ESP_LOGD("S3", "Resulting output clock frequency: %ld Mhz", (160000000L/LCD_CAM.lcd_clock.lcd_clkm_div_num)); + ESP_LOGI("S3", "Clock divider is %d", LCD_CAM.lcd_clock.lcd_clkm_div_num); + ESP_LOGD("S3", "Resulting output clock frequency: %ld Mhz", (160000000L/LCD_CAM.lcd_clock.lcd_clkm_div_num)); + - LCD_CAM.lcd_clock.lcd_clkm_div_a = 1; // 0/1 fractional divide LCD_CAM.lcd_clock.lcd_clkm_div_b = 0; @@ -150,13 +162,16 @@ LCD_CAM.lcd_ctrl.lcd_rgb_mode_en = 0; // i8080 mode (not RGB) LCD_CAM.lcd_rgb_yuv.lcd_conv_bypass = 0; // Disable RGB/YUV converter LCD_CAM.lcd_misc.lcd_next_frame_en = 0; // Do NOT auto-frame + + LCD_CAM.lcd_misc.lcd_bk_en = 1; // https://esp32.com/viewtopic.php?t=24459&start=60#p91835 + LCD_CAM.lcd_data_dout_mode.val = 0; // No data delays LCD_CAM.lcd_user.lcd_always_out_en = 1; // Enable 'always out' mode LCD_CAM.lcd_user.lcd_8bits_order = 0; // Do not swap bytes LCD_CAM.lcd_user.lcd_bit_order = 0; // Do not reverse bit order LCD_CAM.lcd_user.lcd_2byte_en = 1; // 8-bit data mode - LCD_CAM.lcd_user.lcd_dummy = 0; // Dummy phase(s) @ LCD start - LCD_CAM.lcd_user.lcd_dummy_cyclelen = 0; // 1 dummy phase + LCD_CAM.lcd_user.lcd_dummy = 1; // Dummy phase(s) @ LCD start + LCD_CAM.lcd_user.lcd_dummy_cyclelen = 1; // 1+1 dummy phase LCD_CAM.lcd_user.lcd_cmd = 0; // No command at LCD start // "Dummy phases" are initial LCD peripheral clock cycles before data // begins transmitting when requested. After much testing, determined @@ -212,10 +227,11 @@ // in a single DMA descriptor (max 4095 bytes). Large transfers would // require a linked list of descriptors, but here it's just one... +/* desc.dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; desc.dw0.suc_eof = 0; // Last descriptor desc.next = &desc; // No linked list - +*/ // Remaining descriptor elements are initialized before each DMA transfer. @@ -236,38 +252,18 @@ gdma_apply_strategy(dma_chan, &strategy_config); gdma_transfer_ability_t ability = { - .sram_trans_align = 4, + .sram_trans_align = 32, .psram_trans_align = 64, }; gdma_set_transfer_ability(dma_chan, &ability); // Enable DMA transfer callback - /* static gdma_tx_event_callbacks_t tx_cbs = { - .on_trans_eof = dma_callback + // .on_trans_eof is literally the only gdma tx event type available + .on_trans_eof = gdma_on_trans_eof_callback }; gdma_register_tx_event_callbacks(dma_chan, &tx_cbs, NULL); - */ - // As mentioned earlier, the slowest clock we can get to the LCD - // peripheral is 40 MHz / 250 / 64 = 2500 Hz. To make an even slower - // bit pattern that's perceptible, we just repeat each value many - // times over. The pattern here just counts through each of 8 bits - // (each LED lights in sequence)...so to get this to repeat at about - // 1 Hz, each LED is lit for 2500/8 or 312 cycles, hence the - // data[8][312] declaration at the start of this code (it's not - // precisely 1 Hz because reality is messy, but sufficient for demo). - // In actual use, say controlling an LED matrix or NeoPixels, such - // shenanigans aren't necessary, as these operate at multiple MHz - // with much smaller clock dividers and can use 1 byte per datum. - /* - for (int i = 0; i < (sizeof(data) / sizeof(data[0])); i++) { // 0 to 7 - for (int j = 0; j < sizeof(data[0]); j++) { // 0 to 311 - data[i][j] = 1 << i; - } - } - */ - // This uses a busy loop to wait for each DMA transfer to complete... // but the whole point of DMA is that one's code can do other work in @@ -277,36 +273,14 @@ // After much experimentation, each of these steps is required to get // a clean start on the next LCD transfer: gdma_reset(dma_chan); // Reset DMA to known state - LCD_CAM.lcd_user.lcd_dout = 1; // Enable data out - LCD_CAM.lcd_user.lcd_update = 1; // Update registers + esp_rom_delay_us(1000); + + LCD_CAM.lcd_user.lcd_dout = 1; // Enable data out + LCD_CAM.lcd_user.lcd_update = 1; // Update registers LCD_CAM.lcd_misc.lcd_afifo_reset = 1; // Reset LCD TX FIFO - // This program happens to send the same data over and over...but, - // if desired, one could fill the data buffer with a new bit pattern - // here, or point to a completely different buffer each time through. - // With two buffers, one can make best use of time by filling each - // with new data before the busy loop above, alternating between them. - - // Reset elements of DMA descriptor. Just one in this code, long - // transfers would loop through a linked list. - - /* - desc.dw0.size = desc.dw0.length = sizeof(data); - desc.buffer = dmabuff2; //data; - desc.next = &desc; -*/ - - -/* - //gdma_start(dma_chan, (intptr_t)&desc); // Start DMA w/updated descriptor(s) - gdma_start(dma_chan, (intptr_t)&_dmadesc_a[0]); // Start DMA w/updated descriptor(s) - esp_rom_delay_us(100); // Must 'bake' a moment before... - LCD_CAM.lcd_user.lcd_start = 1; // Trigger LCD DMA transfer -*/ - return true; // no return val = illegal instruction - } @@ -379,7 +353,8 @@ { _dmadesc_b[_dmadesc_b_idx].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; - _dmadesc_b[_dmadesc_b_idx].dw0.suc_eof = 0; + //_dmadesc_b[_dmadesc_b_idx].dw0.suc_eof = 0; + _dmadesc_b[_dmadesc_b_idx].dw0.suc_eof = (_dmadesc_b_idx == (_dmadesc_count-1)); _dmadesc_b[_dmadesc_b_idx].dw0.size = _dmadesc_b[_dmadesc_b_idx].dw0.length = size; //sizeof(data); _dmadesc_b[_dmadesc_b_idx].buffer = data; //data; @@ -396,7 +371,7 @@ } else { - + if ( _dmadesc_a_idx >= _dmadesc_count) { ESP_LOGE("S3", "Attempted to create more DMA descriptors than allocated. Expecting max %" PRIu32 " descriptors.", _dmadesc_count); @@ -404,7 +379,8 @@ } _dmadesc_a[_dmadesc_a_idx].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; - _dmadesc_a[_dmadesc_a_idx].dw0.suc_eof = 0; + //_dmadesc_a[_dmadesc_a_idx].dw0.suc_eof = 0; + _dmadesc_a[_dmadesc_a_idx].dw0.suc_eof = (_dmadesc_a_idx == (_dmadesc_count-1)); _dmadesc_a[_dmadesc_a_idx].dw0.size = _dmadesc_a[_dmadesc_a_idx].dw0.length = size; //sizeof(data); _dmadesc_a[_dmadesc_a_idx].buffer = data; //data; @@ -441,12 +417,12 @@ } // end - void Bus_Parallel16::flip_dma_output_buffer(int ¤t_back_buffer_id) + void Bus_Parallel16::flip_dma_output_buffer(int back_buffer_id) { // if ( _double_dma_buffer == false) return; - if ( current_back_buffer_id == 1) // change across to everything 'b'' + if ( back_buffer_id == 1) // change across to everything 'b'' { _dmadesc_a[_dmadesc_count-1].next = (dma_descriptor_t *) &_dmadesc_b[0]; _dmadesc_b[_dmadesc_count-1].next = (dma_descriptor_t *) &_dmadesc_b[0]; @@ -457,7 +433,14 @@ _dmadesc_a[_dmadesc_count-1].next = (dma_descriptor_t *) &_dmadesc_a[0]; } - current_back_buffer_id ^= 1; + //current_back_buffer_id ^= 1; + + previousBufferFree = false; + + //while (i2s_parallel_is_previous_buffer_free() == false) {} + while (!previousBufferFree); + + } // end flip diff --git a/src/platforms/esp32s3/gdma_lcd_parallel16.hpp b/src/platforms/esp32s3/gdma_lcd_parallel16.hpp index 3d47c22..958b65c 100644 --- a/src/platforms/esp32s3/gdma_lcd_parallel16.hpp +++ b/src/platforms/esp32s3/gdma_lcd_parallel16.hpp @@ -147,7 +147,7 @@ void dma_transfer_start(); void dma_transfer_stop(); - void flip_dma_output_buffer(int ¤t_back_buffer_id); + void flip_dma_output_buffer(int back_buffer_id); private: diff --git a/src/platforms/platform_detect.hpp b/src/platforms/platform_detect.hpp index 8a0982a..9b44356 100644 --- a/src/platforms/platform_detect.hpp +++ b/src/platforms/platform_detect.hpp @@ -55,7 +55,8 @@ Modified heavily for the ESP32 HUB75 DMA library by: // Assume an ESP32 (the original 2015 version) // Same include as ESP32S3 - #pragma message "Compiling for original ESP32 (released 2016)" + //#pragma message "Compiling for original ESP32 (released 2016)" + #define ESP32_THE_ORIG 1 //#include "esp32/esp32_i2s_parallel_dma.hpp" //#include "esp32/esp32_i2s_parallel_dma.h" diff --git a/testing/README.md b/testing/README.md new file mode 100644 index 0000000..fe6137f --- /dev/null +++ b/testing/README.md @@ -0,0 +1,5 @@ +Sample app to simulate the VirtualMatrixPanel class for testing / optimisation, without having to test with physical panels. + +``` +g++ -o myapp.exe virtual.cpp +``` \ No newline at end of file diff --git a/testing/baseline.hpp b/testing/baseline.hpp new file mode 100644 index 0000000..43d13e7 --- /dev/null +++ b/testing/baseline.hpp @@ -0,0 +1,189 @@ + +/** + * Calculate virtual->real co-ordinate mapping to underlying single chain of panels connected to ESP32. + * Updates the private class member variable 'coords', so no need to use the return value. + * Not thread safe, but not a concern for ESP32 sketch anyway... I think. + */ +// DO NOT CHANGE +inline VirtualCoords VirtualMatrixPanelTest::getCoords_WorkingBaslineMarch2023(int16_t virt_x, int16_t virt_y) +{ + coords.x = coords.y = -1; // By defalt use an invalid co-ordinates that will be rejected by updateMatrixDMABuffer + + if (virt_x < 0 || virt_x >= virtualResX || virt_y < 0 || virt_y >= virtualResY) + { // Co-ordinates go from 0 to X-1 remember! otherwise they are out of range! + return coords; + } + + // Do we want to rotate? + if (_rotate) + { + int16_t temp_x = virt_x; + virt_x = virt_y; + virt_y = virtualResY - 1 - temp_x; + } + + int row = (virt_y / panelResY); // 0 indexed + switch(panel_chain_type) + { + + case (CHAIN_TOP_RIGHT_DOWN): + { + if ( (row % 2) == 1 ) + { // upside down panel + + //Serial.printf("Condition 1, row %d ", row); + + // refersed for the row + coords.x = dmaResX - virt_x - (row*virtualResX); + + // y co-ord inverted within the panel + coords.y = panelResY - 1 - (virt_y % panelResY); + + + } + else + { + //Serial.printf("Condition 2, row %d ", row); + + coords.x = ((vmodule_rows - (row+1))*virtualResX)+virt_x; + coords.y = virt_y % panelResY; + + } + + } + break; + + + case (CHAIN_TOP_LEFT_DOWN): // OK -> modulus opposite of CHAIN_TOP_RIGHT_DOWN + { + if ( (row % 2) == 0 ) + { // refersed panel + + //Serial.printf("Condition 1, row %d ", row); + coords.x = dmaResX - virt_x - (row*virtualResX); + + // y co-ord inverted within the panel + coords.y = panelResY - 1 - (virt_y % panelResY); + + } + else + { + //Serial.printf("Condition 2, row %d ", row); + coords.x = ((vmodule_rows - (row+1))*virtualResX)+virt_x; + coords.y = virt_y % panelResY; + + } + + } + break; + + + + + case (CHAIN_BOTTOM_LEFT_UP): // + { + row = vmodule_rows - row - 1; + + if ( (row % 2) == 1 ) + { + // Serial.printf("Condition 1, row %d ", row); + coords.x = ((vmodule_rows - (row+1))*virtualResX)+virt_x; + coords.y = virt_y % panelResY; + + } + else + { // inverted panel + + // Serial.printf("Condition 2, row %d ", row); + coords.x = dmaResX - (row*virtualResX) - virt_x; + coords.y = panelResY - 1 - (virt_y % panelResY); + } + + } + break; + + case (CHAIN_BOTTOM_RIGHT_UP): // OK -> modulus opposite of CHAIN_BOTTOM_LEFT_UP + { + row = vmodule_rows - row - 1; + + if ( (row % 2) == 0 ) + { // right side up + + // Serial.printf("Condition 1, row %d ", row); + // refersed for the row + coords.x = ((vmodule_rows - (row+1))*virtualResX)+virt_x; + coords.y = virt_y % panelResY; + + } + else + { // inverted panel + + // Serial.printf("Condition 2, row %d ", row); + coords.x = dmaResX - (row*virtualResX) - virt_x; + coords.y = panelResY - 1 - (virt_y % panelResY); + } + + } + break; + + + default: + return coords; + break; + + } // end switch + + + + /* START: Pixel remapping AGAIN to convert TWO parallel scanline output that the + * the underlying hardware library is designed for (because + * there's only 2 x RGB pins... and convert this to 1/4 or something + */ + if (panel_scan_rate == FOUR_SCAN_32PX_HIGH) + { + /* Convert Real World 'VirtualMatrixPanel' co-ordinates (i.e. Real World pixel you're looking at + on the panel or chain of panels, per the chaining configuration) to a 1/8 panels + double 'stretched' and 'squished' coordinates which is what needs to be sent from the + DMA buffer. + + Note: Look at the FourScanPanel example code and you'll see that the DMA buffer is setup + as if the panel is 2 * W and 0.5 * H ! + */ + + if ((virt_y & 8) == 0) + { + coords.x += ((coords.x / panelResX) + 1) * panelResX; // 1st, 3rd 'block' of 8 rows of pixels, offset by panel width in DMA buffer + } + else + { + coords.x += (coords.x / panelResX) * panelResX; // 2nd, 4th 'block' of 8 rows of pixels, offset by panel width in DMA buffer + } + + // http://cpp.sh/4ak5u + // Real number of DMA y rows is half reality + // coords.y = (y / 16)*8 + (y & 0b00000111); + coords.y = (virt_y >> 4) * 8 + (virt_y & 0b00000111); + + } + else if (panel_scan_rate == FOUR_SCAN_16PX_HIGH) + { + if ((virt_y & 8) == 0) + { + coords.x += (panelResX >> 2) * (((coords.x & 0xFFF0) >> 4) + 1); // 1st, 3rd 'block' of 8 rows of pixels, offset by panel width in DMA buffer + } + else + { + coords.x += (panelResX >> 2) * (((coords.x & 0xFFF0) >> 4)); // 2nd, 4th 'block' of 8 rows of pixels, offset by panel width in DMA buffer + } + + if (virt_y < 32) + coords.y = (virt_y >> 4) * 8 + (virt_y & 0b00000111); + else + { + coords.y = ((virt_y - 32) >> 4) * 8 + (virt_y & 0b00000111); + coords.x += 256; + } + } + + return coords; +} diff --git a/testing/virtual.cpp b/testing/virtual.cpp new file mode 100644 index 0000000..168acb1 --- /dev/null +++ b/testing/virtual.cpp @@ -0,0 +1,459 @@ +#include +#include +#include + + +struct VirtualCoords +{ + int16_t x; + int16_t y; + int16_t virt_row; // chain of panels row + int16_t virt_col; // chain of panels col + + VirtualCoords() : x(0), y(0) + { + } +}; + +enum PANEL_SCAN_RATE +{ + NORMAL_TWO_SCAN, NORMAL_ONE_SIXTEEN, // treated as the same + FOUR_SCAN_32PX_HIGH, + FOUR_SCAN_16PX_HIGH +}; + +// Chaining approach... From the perspective of the DISPLAY / LED side of the chain of panels. +enum PANEL_CHAIN_TYPE +{ + CHAIN_TOP_LEFT_DOWN, + CHAIN_TOP_RIGHT_DOWN, + CHAIN_BOTTOM_LEFT_UP, + CHAIN_BOTTOM_RIGHT_UP, + CHAIN_TOP_RIGHT_DOWN_ZZ, /// ZigZag chaining. Might need a big ass cable to do this, all panels right way up. + CHAIN_BOTTOM_RIGHT_UP_ZZ +}; + + +class VirtualMatrixPanelTest +{ + +public: + VirtualMatrixPanelTest(int _vmodule_rows, int _vmodule_cols, int _panelResX, int _panelResY, PANEL_CHAIN_TYPE _panel_chain_type = CHAIN_TOP_RIGHT_DOWN) + { + + panelResX = _panelResX; + panelResY = _panelResY; + + vmodule_rows = _vmodule_rows; + vmodule_cols = _vmodule_cols; + + virtualResX = vmodule_cols * _panelResX; + virtualResY = vmodule_rows * _panelResY; + + dmaResX = panelResX * vmodule_rows * vmodule_cols; + + panel_chain_type = _panel_chain_type; + + /* Virtual Display width() and height() will return a real-world value. For example: + * Virtual Display width: 128 + * Virtual Display height: 64 + * + * So, not values that at 0 to X-1 + */ + + coords.x = coords.y = -1; // By default use an invalid co-ordinates that will be rejected by updateMatrixDMABuffer + + switch (panel_chain_type) { + case CHAIN_TOP_LEFT_DOWN: + chain_type_str = "CHAIN_TOP_LEFT_DOWN"; + break; + + case CHAIN_TOP_RIGHT_DOWN: + chain_type_str = "CHAIN_TOP_RIGHT_DOWN"; + break; + + case CHAIN_TOP_RIGHT_DOWN_ZZ: + chain_type_str = "CHAIN_TOP_RIGHT_DOWN_ZZ"; + break; + + case CHAIN_BOTTOM_RIGHT_UP: + chain_type_str = "CHAIN_BOTTOM_RIGHT_UP"; + break; + + case CHAIN_BOTTOM_LEFT_UP: + chain_type_str = "CHAIN_BOTTOM_LEFT_UP"; + break; + + default: + chain_type_str = "WTF!"; + break; + } + std::cout << "\n\n***************************************************************************\n"; + std::cout << "Chain type: " << chain_type_str << " "; + std::printf("Testing chain of panels of %d rows, %d columns, %d px by %d px resolution. \n\n", vmodule_rows, vmodule_cols, panelResX, panelResX, panelResY); + + + } + + // equivalent methods of the matrix library so it can be just swapped out. + void drawPixel(int16_t x, int16_t y, int16_t expected_x, int16_t expected_y); + + std::string chain_type_str = "UNKNOWN"; + + // Internal co-ord conversion function + VirtualCoords getCoords_Dev(int16_t x, int16_t y); + + VirtualCoords getCoords_WorkingBaslineMarch2023(int16_t x, int16_t y); + + VirtualCoords coords; + + +private: + + int16_t virtualResX; + int16_t virtualResY; + + int16_t vmodule_rows; + int16_t vmodule_cols; + + int16_t panelResX; + int16_t panelResY; + + int16_t dmaResX; // The width of the chain in pixels (as the DMA engine sees it) + + PANEL_CHAIN_TYPE panel_chain_type; + PANEL_SCAN_RATE panel_scan_rate = NORMAL_TWO_SCAN; + + bool _rotate = false; + +}; // end Class header + +#include "baseline.hpp" + +/** + * Development version for testing. + */ +inline VirtualCoords VirtualMatrixPanelTest::getCoords_Dev(int16_t virt_x, int16_t virt_y) +{ + coords.x = coords.y = -1; // By defalt use an invalid co-ordinates that will be rejected by updateMatrixDMABuffer + + if (virt_x < 0 || virt_x >= virtualResX || virt_y < 0 || virt_y >= virtualResY) + { // Co-ordinates go from 0 to X-1 remember! otherwise they are out of range! + return coords; + } + + // Do we want to rotate? + if (_rotate) + { + int16_t temp_x = virt_x; + virt_x = virt_y; + virt_y = virtualResY - 1 - temp_x; + } + + int row = (virt_y / panelResY); // 0 indexed + switch(panel_chain_type) + { + + case (CHAIN_TOP_RIGHT_DOWN): + { + if ( (row % 2) == 1 ) + { // upside down panel + + //Serial.printf("Condition 1, row %d ", row); + + // refersed for the row + coords.x = dmaResX - virt_x - (row*virtualResX); + + // y co-ord inverted within the panel + coords.y = panelResY - 1 - (virt_y % panelResY); + + + } + else + { + //Serial.printf("Condition 2, row %d ", row); + coords.x = ((vmodule_rows - (row+1))*virtualResX)+virt_x; + coords.y = virt_y % panelResY; + + } + + } + break; + + + case (CHAIN_TOP_LEFT_DOWN): // OK -> modulus opposite of CHAIN_TOP_RIGHT_DOWN + { + if ( (row % 2) == 0 ) + { // refersed panel + + //Serial.printf("Condition 1, row %d ", row); + coords.x = dmaResX - virt_x - (row*virtualResX); + + // y co-ord inverted within the panel + coords.y = panelResY - 1 - (virt_y % panelResY); + + } + else + { + //Serial.printf("Condition 2, row %d ", row); + coords.x = ((vmodule_rows - (row+1))*virtualResX)+virt_x; + coords.y = virt_y % panelResY; + + } + + } + break; + + + + + case (CHAIN_BOTTOM_LEFT_UP): // + { + row = vmodule_rows - row - 1; + + if ( (row % 2) == 1 ) + { + // Serial.printf("Condition 1, row %d ", row); + coords.x = ((vmodule_rows - (row+1))*virtualResX)+virt_x; + coords.y = virt_y % panelResY; + + } + else + { // inverted panel + + // Serial.printf("Condition 2, row %d ", row); + coords.x = dmaResX - (row*virtualResX) - virt_x; + coords.y = panelResY - 1 - (virt_y % panelResY); + } + + } + break; + + case (CHAIN_BOTTOM_RIGHT_UP): // OK -> modulus opposite of CHAIN_BOTTOM_LEFT_UP + { + row = vmodule_rows - row - 1; + + if ( (row % 2) == 0 ) + { // right side up + + // Serial.printf("Condition 1, row %d ", row); + // refersed for the row + coords.x = ((vmodule_rows - (row+1))*virtualResX)+virt_x; + coords.y = virt_y % panelResY; + + } + else + { // inverted panel + + // Serial.printf("Condition 2, row %d ", row); + coords.x = dmaResX - (row*virtualResX) - virt_x; + coords.y = panelResY - 1 - (virt_y % panelResY); + } + + } + break; + + case CHAIN_TOP_RIGHT_DOWN_ZZ: + { + // Right side up. Starting from top left all the way down. + // Connected in a Zig Zag manner = some long ass cables being used potentially + + //Serial.printf("Condition 2, row %d ", row); + coords.x = ((vmodule_rows - (row+1))*virtualResX)+virt_x; + coords.y = virt_y % panelResY; + + } + + case CHAIN_BOTTOM_RIGHT_UP_ZZ: + { + // Right side up. Starting from top left all the way down. + // Connected in a Zig Zag manner = some long ass cables being used potentially + + //Serial.printf("Condition 2, row %d ", row); + coords.x = (row*virtualResX)+virt_x; + coords.y = virt_y % panelResY; + + } + + + + + default: + return coords; + break; + + } // end switch + + + + /* START: Pixel remapping AGAIN to convert TWO parallel scanline output that the + * the underlying hardware library is designed for (because + * there's only 2 x RGB pins... and convert this to 1/4 or something + */ + if (panel_scan_rate == FOUR_SCAN_32PX_HIGH) + { + /* Convert Real World 'VirtualMatrixPanel' co-ordinates (i.e. Real World pixel you're looking at + on the panel or chain of panels, per the chaining configuration) to a 1/8 panels + double 'stretched' and 'squished' coordinates which is what needs to be sent from the + DMA buffer. + + Note: Look at the FourScanPanel example code and you'll see that the DMA buffer is setup + as if the panel is 2 * W and 0.5 * H ! + */ + + if ((virt_y & 8) == 0) + { + coords.x += ((coords.x / panelResX) + 1) * panelResX; // 1st, 3rd 'block' of 8 rows of pixels, offset by panel width in DMA buffer + } + else + { + coords.x += (coords.x / panelResX) * panelResX; // 2nd, 4th 'block' of 8 rows of pixels, offset by panel width in DMA buffer + } + + // http://cpp.sh/4ak5u + // Real number of DMA y rows is half reality + // coords.y = (y / 16)*8 + (y & 0b00000111); + coords.y = (virt_y >> 4) * 8 + (virt_y & 0b00000111); + + } + else if (panel_scan_rate == FOUR_SCAN_16PX_HIGH) + { + if ((virt_y & 8) == 0) + { + coords.x += (panelResX >> 2) * (((coords.x & 0xFFF0) >> 4) + 1); // 1st, 3rd 'block' of 8 rows of pixels, offset by panel width in DMA buffer + } + else + { + coords.x += (panelResX >> 2) * (((coords.x & 0xFFF0) >> 4)); // 2nd, 4th 'block' of 8 rows of pixels, offset by panel width in DMA buffer + } + + if (virt_y < 32) + coords.y = (virt_y >> 4) * 8 + (virt_y & 0b00000111); + else + { + coords.y = ((virt_y - 32) >> 4) * 8 + (virt_y & 0b00000111); + coords.x += 256; + } + } + + return coords; +} + +bool check(VirtualCoords expected, VirtualCoords result, int x = -1, int y = -1) +{ + + if ( result.x != expected.x || result.y != expected.y ) + { + std::printf("Requested (%d, %d) -> expecting physical (%d, %d) got (%d, %d).", x, y, expected.x, expected.y, result.x, result.y); + std::cout << "\t *** FAIL ***\n "; + std::cout << "\n"; + + return false; + } + else + { + return true; + } +} + + +main(int argc, char* argv[]) +{ + std::cout << "Starting Testing...\n"; + + std::list chain_t_test_list { CHAIN_TOP_LEFT_DOWN, CHAIN_TOP_RIGHT_DOWN, CHAIN_BOTTOM_LEFT_UP, CHAIN_BOTTOM_RIGHT_UP }; + + + // Draw pixel at virtual position 70x, 70y = + // x, y x, y + + // x == horizontal + // y = vert :-) + + // 192 x 192 pixel virtual display + int rows = 3; + int cols = 3; + int panel_width_x = 64; + int panel_height_y = 64; + + std::string panel_scan_type = "NORMAL_TWO_SCAN"; + + for (auto chain_t : chain_t_test_list) { + + + VirtualMatrixPanelTest test = VirtualMatrixPanelTest(rows,cols,panel_width_x,panel_height_y, chain_t); + int pass_counter = 0; + int fail_counter = 0; + for (int16_t x = 0; x < panel_width_x*cols; x++) + { + for (int16_t y = 0; y < panel_height_y*rows; y++) + { + VirtualCoords expected = test.getCoords_WorkingBaslineMarch2023(x,y); + VirtualCoords result = test.getCoords_Dev(x,y); + + bool chk_result = check(expected, result, x, y); + + if ( chk_result ) + { + fail_counter++; + } + else + { + pass_counter++; + } + } + } + + if ( fail_counter > 0) { + std::printf("ERROR: %d tests failed.\n", fail_counter); + } else{ + std::printf("SUCCESS: %d coord tests passed.\n", pass_counter); + } + + } // end chain type test list + + + std::cout << "Performing NON-SERPENTINE (ZIG ZAG) TEST"; + + rows = 3; + cols = 1; + panel_width_x = 64; + panel_height_y = 64; + + VirtualMatrixPanelTest test = VirtualMatrixPanelTest(rows,cols,panel_width_x,panel_height_y, CHAIN_TOP_RIGHT_DOWN_ZZ); + + // CHAIN_TOP_RIGHT_DOWN_ZZ test 1 + // (x,y) + VirtualCoords result = test.getCoords_Dev(0,0); + VirtualCoords expected; expected.x = 64*2; expected.y = 0; + std::printf("Expected physical (%d, %d) got (%d, %d).\n", expected.x, expected.y, result.x, result.y); + + // CHAIN_TOP_RIGHT_DOWN_ZZ test 2 + result = test.getCoords_Dev(10,64*3-1); + expected.x = 10; expected.y = 63; + std::printf("Expected physical (%d, %d) got (%d, %d).\n", expected.x, expected.y, result.x, result.y); + + + // CHAIN_TOP_RIGHT_DOWN_ZZ test 3 + result = test.getCoords_Dev(16,64*2-1); + expected.x = 80; expected.y = 63; + std::printf("Expected physical (%d, %d) got (%d, %d).\n", expected.x, expected.y, result.x, result.y); + + + + // CHAIN_BOTTOM_RIGHT_UP_ZZ test 4 + result = test.getCoords_Dev(0,0); + expected.x = 0; expected.y = 0; + std::printf("Expected physical (%d, %d) got (%d, %d).\n", expected.x, expected.y, result.x, result.y); + + // CHAIN_BOTTOM_RIGHT_UP_ZZ test 4 + result = test.getCoords_Dev(63,64); + expected.x = 64*2-1; expected.y = 0; + std::printf("Expected physical (%d, %d) got (%d, %d).\n", expected.x, expected.y, result.x, result.y); + + + + std::cout << "\n\n"; + + return 0; +} \ No newline at end of file