48
.github/workflows/esp-idf_with-gfx.yml
vendored
Normal file
|
@ -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'
|
33
.github/workflows/esp-idf_without-gfx.yml
vendored
Normal file
|
@ -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'
|
18
.github/workflows/pio_build.yml
vendored
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
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"
|
||||
REQUIRES ${arduino_build} ${esp_idf_build}
|
||||
)
|
||||
|
||||
# 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()
|
||||
|
||||
|
|
9
Kconfig.projbuild
Normal file
|
@ -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
|
12
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.
|
||||
|
||||
|
@ -83,8 +83,10 @@ Due to the high-speed optimized nature of this library, only specific panels are
|
|||
* [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
|
||||
* 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)
|
||||
|
|
BIN
doc/Panel_Chaining_Types.ods
Normal file
BIN
doc/VirtualMatrixPanel.odp
Normal file
BIN
doc/VirtualMatrixPanel.pdf
Normal file
|
@ -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 <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
|
||||
|
||||
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 <float> (rand()) / static_cast <float> (RAND_MAX);
|
||||
Squares[i].velocityy = static_cast <float> (rand()) / static_cast <float> (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++)
|
||||
{
|
|
@ -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.
|
|
@ -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 <Arduino.h>
|
||||
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
|
||||
#include <FastLED.h>
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// 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;
|
||||
|
||||
|
@ -34,21 +45,18 @@ 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
|
||||
PANEL_RES_Y, // module height
|
||||
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);
|
||||
|
@ -100,3 +108,7 @@ void loop(){
|
|||
fps = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// FM6126 panel , thanks goes to:
|
||||
// https://github.com/hzeller/rpi-rgb-led-matrix/issues/746
|
13
examples/4_OtherShiftDriverPanel/README.md
Normal file
|
@ -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;
|
||||
```
|
|
@ -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
|
268
examples/AnimatedGIFPanel_SD/AnimatedGIFPanel_SD.ino
Normal file
|
@ -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 <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
|
||||
#include <AnimatedGIF.h>
|
||||
|
||||
/********************************************************************
|
||||
* 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<std::string> 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);
|
||||
|
||||
}
|
15
examples/AnimatedGIFPanel_SD/Readme.md
Normal file
|
@ -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.
|
||||
|
||||
|
BIN
examples/AnimatedGIFPanel_SD/esp32_sdcard.jpg
Normal file
After Width: | Height: | Size: 150 KiB |
132
examples/AnimatedGIFPanel_SD/gif_functions.hpp
Normal file
|
@ -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<File *>(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<File *>(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<File *>(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; x<iWidth; x++) {
|
||||
if (s[x] == pDraw->ucTransparent)
|
||||
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; x<iWidth; x++)
|
||||
dma_display->drawPixel(x, y, usPalette[*s++]); // color 565
|
||||
/*
|
||||
usTemp[x] = usPalette[*s++];
|
||||
|
||||
for (x=0; x<pDraw->iWidth; x++) {
|
||||
dma_display->drawPixel(x, y, usTemp[*s++]); // color 565
|
||||
} */
|
||||
|
||||
}
|
||||
} /* GIFDraw() */
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
102
examples/AnimatedGIFPanel_SD/sdcard_functions.hpp
Normal file
|
@ -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();
|
||||
}
|
290
examples/AnimatedGIFPanel_SPIFFS/AnimatedGIFPanel_SPIFFS.ino
Normal file
|
@ -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 <SPIFFS.h>
|
||||
#include <AnimatedGIF.h>
|
||||
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
|
||||
|
||||
// ----------------------------
|
||||
|
||||
/*
|
||||
* 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; x<iWidth; x++)
|
||||
{
|
||||
if (s[x] == pDraw->ucTransparent)
|
||||
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; x<pDraw->iWidth; 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<File *>(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<File *>(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<File *>(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
|
||||
}
|
BIN
examples/AnimatedGIFPanel_SPIFFS/data/gifs/cartoon.gif
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
examples/AnimatedGIFPanel_SPIFFS/data/gifs/ezgif.com-pacmn.gif
Normal file
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 39 KiB |
BIN
examples/AnimatedGIFPanel_SPIFFS/data/gifs/matrix-spin.gif
Normal file
After Width: | Height: | Size: 171 KiB |
BIN
examples/AnimatedGIFPanel_SPIFFS/data/gifs/parasite1.gif
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
examples/AnimatedGIFPanel_SPIFFS/data/gifs/parasite2.gif
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
examples/AnimatedGIFPanel_SPIFFS/data/gifs/shock-gs.gif
Normal file
After Width: | Height: | Size: 34 KiB |
|
@ -1,139 +1,29 @@
|
|||
/******************************************************************************
|
||||
-----------
|
||||
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_TYPE.
|
||||
|
||||
- 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.
|
||||
- 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 <ESP32-VirtualMatrixPanel-I2S-DMA.h>
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* 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:
|
||||
|
||||
#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
|
||||
|
||||
virtualDisp(dma_display, NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, true, false);
|
||||
|
||||
= 128x64 px virtual display, with the top left of panel 4 being pixel co-ord (0,0)
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
// 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.
|
||||
|
||||
|
@ -141,12 +31,31 @@
|
|||
#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
|
||||
|
||||
// Change this to your needs, for details on VirtualPanel pls read the PDF!
|
||||
#define SERPENT true
|
||||
#define TOPDOWN false
|
||||
/* Configure the serpetine chaining approach. Options are:
|
||||
CHAIN_TOP_LEFT_DOWN
|
||||
CHAIN_TOP_RIGHT_DOWN
|
||||
CHAIN_BOTTOM_LEFT_UP
|
||||
CHAIN_BOTTOM_RIGHT_UP
|
||||
|
||||
// library includes
|
||||
#include <ESP32-VirtualMatrixPanel-I2S-DMA.h>
|
||||
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
|
||||
|
||||
// 3) Create the runtime objects to use
|
||||
|
||||
// placeholder for the matrix object
|
||||
MatrixPanel_I2S_DMA *dma_display = 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("Chain of 4x 64x32 panels for this example:");
|
||||
Serial.println("+---------+---------+");
|
||||
Serial.println("| 4 | 3 |");
|
||||
Serial.println("| | |");
|
||||
Serial.println("+--------+---------+");
|
||||
Serial.println("+---------+---------+");
|
||||
Serial.println("| 1 | 2 |");
|
||||
Serial.println("| (ESP) | |");
|
||||
Serial.println("+--------+---------+");
|
||||
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
|
||||
|
||||
*****************************************************************************/
|
|
@ -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 <INSERT CHAINING TYPE HERE - Refer to documentation or example>
|
||||
|
||||
```
|
||||
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).
|
||||
|
||||
|
|
|
@ -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 ****************");
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
|
@ -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
|
||||
|
|
12
examples/esp-idf/.gitignore
vendored
Normal file
|
@ -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
|
10
examples/esp-idf/with-gfx/CMakeLists.txt
Normal file
|
@ -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)
|
17
examples/esp-idf/with-gfx/README.md
Normal file
|
@ -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`
|
4
examples/esp-idf/with-gfx/components/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
# Ignore everything in this directory
|
||||
*
|
||||
# Except this file
|
||||
!.gitignore
|
5
examples/esp-idf/with-gfx/main/CMakeLists.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
idf_component_register(
|
||||
SRC_DIRS "." ${SRCDIRS}
|
||||
INCLUDE_DIRS ${INCLUDEDIRS}
|
||||
REQUIRES ESP32-HUB75-MatrixPanel-I2S-DMA
|
||||
)
|
14
examples/esp-idf/with-gfx/main/main.cpp
Normal file
|
@ -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");
|
||||
}
|
1
examples/esp-idf/with-gfx/sdkconfig.defaults
Normal file
|
@ -0,0 +1 @@
|
|||
CONFIG_FREERTOS_HZ=1000
|
10
examples/esp-idf/without-gfx/CMakeLists.txt
Normal file
|
@ -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)
|
14
examples/esp-idf/without-gfx/README.md
Normal file
|
@ -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`
|
4
examples/esp-idf/without-gfx/components/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
# Ignore everything in this directory
|
||||
*
|
||||
# Except this file
|
||||
!.gitignore
|
5
examples/esp-idf/without-gfx/main/CMakeLists.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
idf_component_register(
|
||||
SRC_DIRS "." ${SRCDIRS}
|
||||
INCLUDE_DIRS ${INCLUDEDIRS}
|
||||
REQUIRES ESP32-HUB75-MatrixPanel-I2S-DMA
|
||||
)
|
12
examples/esp-idf/without-gfx/main/main.cpp
Normal file
|
@ -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();
|
||||
}
|
1
examples/esp-idf/without-gfx/sdkconfig.defaults
Normal file
|
@ -0,0 +1 @@
|
|||
CONFIG_ESP32_HUB75_USE_GFX=n
|
|
@ -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": {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -10,7 +10,10 @@
|
|||
* 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
|
||||
|
@ -27,25 +30,7 @@
|
|||
* 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
|
||||
*/
|
||||
|
||||
|
||||
bool MatrixPanel_I2S_DMA::allocateDMAmemory()
|
||||
{
|
||||
|
@ -53,15 +38,18 @@ 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));
|
||||
|
||||
|
||||
// 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;
|
||||
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<rowBitStruct>(PIXELS_PER_ROW, m_cfg.getPixelColorDepthBits(), m_cfg.double_buff);
|
||||
|
||||
|
@ -74,18 +62,23 @@ bool MatrixPanel_I2S_DMA::allocateDMAmemory()
|
|||
// 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
|
||||
|
||||
//#define FORCE_COLOR_DEPTH 1
|
||||
|
||||
#if !defined(FORCE_COLOR_DEPTH)
|
||||
|
||||
ESP_LOGI("I2S-DMA", "Minimum visual refresh rate (scan rate from panel top to bottom) requested: %d Hz", m_cfg.min_refresh_rate);
|
||||
|
||||
while(1) {
|
||||
while (1)
|
||||
{
|
||||
int psPerClock = 1000000000000UL / m_cfg.i2sspeed;
|
||||
int nsPerLatch = ((PIXELS_PER_ROW + CLKS_DURING_LATCH) * psPerClock) / 1000;
|
||||
|
||||
|
@ -120,13 +113,13 @@ bool MatrixPanel_I2S_DMA::allocateDMAmemory()
|
|||
|
||||
#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++) {
|
||||
for (int i = lsbMsbTransitionBit + 1; i < m_cfg.getPixelColorDepthBits(); i++)
|
||||
{
|
||||
numDMAdescriptorsPerRow += (1 << (i - lsbMsbTransitionBit - 1));
|
||||
}
|
||||
|
||||
|
@ -134,7 +127,7 @@ bool MatrixPanel_I2S_DMA::allocateDMAmemory()
|
|||
|
||||
// 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 )
|
||||
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);
|
||||
|
@ -143,19 +136,23 @@ bool MatrixPanel_I2S_DMA::allocateDMAmemory()
|
|||
// 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;
|
||||
int desccount = numDMAdescriptorsPerRow * ROWS_PER_FRAME;
|
||||
|
||||
if (m_cfg.double_buff)
|
||||
{
|
||||
dma_bus.enable_double_dma_desc();
|
||||
}
|
||||
|
||||
dma_bus.allocate_dma_desc_memory(desccount);
|
||||
|
||||
// point FB we can write to, to 0 / dmadesc_a
|
||||
fb = &frame_buffer[0];
|
||||
|
||||
// Just os we know
|
||||
initialized = true;
|
||||
|
||||
|
@ -165,6 +162,31 @@ bool MatrixPanel_I2S_DMA::allocateDMAmemory()
|
|||
|
||||
|
||||
|
||||
/*
|
||||
// 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;
|
||||
|
||||
int n = 0;
|
||||
while (len)
|
||||
{
|
||||
int dmalen = len;
|
||||
if (dmalen > DMA_MAX)
|
||||
dmalen = DMA_MAX;
|
||||
|
||||
if (!countonly)
|
||||
dma_bus.create_dma_desc_link(data2, dmalen, dmadesc_b);
|
||||
|
||||
len -= dmalen;
|
||||
data2 += dmalen;
|
||||
n++;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
*/
|
||||
void MatrixPanel_I2S_DMA::configureDMA(const HUB75_I2S_CFG &_cfg)
|
||||
{
|
||||
|
||||
|
@ -174,11 +196,13 @@ void MatrixPanel_I2S_DMA::configureDMA(const HUB75_I2S_CFG& _cfg)
|
|||
|
||||
// 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 ) {
|
||||
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) and if double buffering is enabled, link it up for both buffers.
|
||||
|
||||
// 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
|
||||
|
@ -186,25 +210,26 @@ void MatrixPanel_I2S_DMA::configureDMA(const HUB75_I2S_CFG& _cfg)
|
|||
// 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(dma_buff.rowBits[row]->getDataPtr(0, 0), dma_buff.rowBits[row]->size(num_dma_payload_colour_depths), false);
|
||||
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)
|
||||
{
|
||||
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(0, 1), frame_buffer[1].rowBits[row]->getColorDepthSize(), 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 )
|
||||
if (frame_buffer[0].rowBits[0]->getColorDepthSize() > DMA_MAX)
|
||||
{
|
||||
|
||||
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[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(cd, 1), dma_buff.rowBits[row]->size(num_dma_payload_colour_depths), true);
|
||||
if (m_cfg.double_buff)
|
||||
{
|
||||
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++;
|
||||
|
@ -212,7 +237,6 @@ void MatrixPanel_I2S_DMA::configureDMA(const HUB75_I2S_CFG& _cfg)
|
|||
} // 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
|
||||
|
@ -221,10 +245,11 @@ void MatrixPanel_I2S_DMA::configureDMA(const HUB75_I2S_CFG& _cfg)
|
|||
|
||||
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);
|
||||
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) {
|
||||
dma_bus.create_dma_desc_link(dma_buff.rowBits[row]->getDataPtr(i, 1), dma_buff.rowBits[row]->size(m_cfg.getPixelColorDepthBits() - i), true );
|
||||
if (m_cfg.double_buff)
|
||||
{
|
||||
dma_bus.create_dma_desc_link(frame_buffer[1].rowBits[row]->getDataPtr(i, 1), frame_buffer[1].rowBits[row]->getColorDepthSize(1), true);
|
||||
}
|
||||
|
||||
current_dmadescriptor_offset++;
|
||||
|
@ -242,7 +267,8 @@ void MatrixPanel_I2S_DMA::configureDMA(const HUB75_I2S_CFG& _cfg)
|
|||
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_wr = m_cfg.gpio.clk;
|
||||
bus_cfg.invert_pclk = m_cfg.clkphase;
|
||||
|
||||
bus_cfg.pin_d0 = m_cfg.gpio.r1;
|
||||
bus_cfg.pin_d1 = m_cfg.gpio.g1;
|
||||
|
@ -278,7 +304,6 @@ void MatrixPanel_I2S_DMA::configureDMA(const HUB75_I2S_CFG& _cfg)
|
|||
|
||||
} // 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.
|
||||
|
@ -300,12 +325,14 @@ void MatrixPanel_I2S_DMA::configureDMA(const HUB75_I2S_CFG& _cfg)
|
|||
*/
|
||||
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) {
|
||||
if (x_coord >= PIXELS_PER_ROW || y_coord >= m_cfg.mx_height)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -320,9 +347,9 @@ uint16_t red16, green16, blue16;
|
|||
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,
|
||||
|
@ -336,20 +363,12 @@ uint16_t red16, green16, blue16;
|
|||
* 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;
|
||||
|
||||
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;
|
||||
y_coord -= ROWS_PER_FRAME;
|
||||
|
@ -357,16 +376,10 @@ uint16_t red16, green16, blue16;
|
|||
|
||||
// Iterating through colour depth bits, which we assume are 8 bits per RGB subpixel (24bpp)
|
||||
uint8_t colour_depth_idx = m_cfg.getPixelColorDepthBits();
|
||||
do {
|
||||
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;
|
||||
|
||||
|
@ -379,12 +392,10 @@ uint16_t red16, green16, blue16;
|
|||
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
|
||||
|
@ -396,11 +407,11 @@ uint16_t red16, green16, blue16;
|
|||
} 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;
|
||||
if (!initialized)
|
||||
return;
|
||||
|
||||
/* https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/ */
|
||||
uint16_t red16, green16, blue16;
|
||||
|
@ -418,12 +429,6 @@ uint16_t red16, green16, blue16;
|
|||
{
|
||||
// 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);
|
||||
|
||||
|
@ -441,21 +446,22 @@ uint16_t red16, green16, blue16;
|
|||
// 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
|
||||
|
||||
|
||||
#if defined(SPIRAM_DMA_BUFFER)
|
||||
Cache_WriteBack_Addr((uint32_t)&p[x_coord], sizeof(ESP32_I2S_DMA_STORAGE_TYPE));
|
||||
#endif
|
||||
|
@ -474,64 +480,84 @@ uint16_t red16, green16, blue16;
|
|||
* 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
|
||||
|
||||
// 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;
|
||||
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) {
|
||||
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 {
|
||||
}
|
||||
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 {
|
||||
do
|
||||
{
|
||||
--x_pixel;
|
||||
|
||||
if ( m_cfg.driver == HUB75_I2S_CFG::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 {
|
||||
}
|
||||
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);
|
||||
|
||||
} 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) {
|
||||
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
|
||||
x_pixel = fb->rowBits[row_idx]->width - 16; // come back 8*2 pixels to allow for 8 writes
|
||||
serialCount = 8;
|
||||
do{
|
||||
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
|
||||
|
@ -539,179 +565,85 @@ void MatrixPanel_I2S_DMA::clearFrameBuffer(bool _buff_id){
|
|||
} 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
|
||||
// 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 {
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
} 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)) ;
|
||||
Cache_WriteBack_Addr((uint32_t)row, fb->rowBits[row_idx]->getColorDepthSize());
|
||||
#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;
|
||||
|
||||
|
||||
|
||||
//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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
frameStruct *fb = &frame_buffer[_buff_id];
|
||||
|
||||
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;
|
||||
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;
|
||||
|
@ -723,13 +655,14 @@ void MatrixPanel_I2S_DMA::brtCtrlOEv2(uint8_t brt, const int _buff_id) {
|
|||
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 = _width;
|
||||
do {
|
||||
do
|
||||
{
|
||||
--x_coord;
|
||||
|
||||
// (the check is already including "blanking" )
|
||||
|
@ -742,74 +675,61 @@ void MatrixPanel_I2S_DMA::brtCtrlOEv2(uint8_t brt, const int _buff_id) {
|
|||
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)){
|
||||
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)) ;
|
||||
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
|
||||
* @param uint8_t pulses - clocks before/after OE
|
||||
|
@ -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;
|
||||
|
||||
|
@ -831,7 +752,6 @@ uint8_t MatrixPanel_I2S_DMA::setLatBlanking(uint8_t pulses){
|
|||
return m_cfg.latch_blanking;
|
||||
}
|
||||
|
||||
|
||||
#ifndef NO_FAST_FUNCTIONS
|
||||
/**
|
||||
* @brief - update DMA buff drawing horizontal line at specified coordinates
|
||||
|
@ -840,7 +760,8 @@ 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){
|
||||
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;
|
||||
|
||||
|
@ -850,7 +771,6 @@ void MatrixPanel_I2S_DMA::hlineDMA(int16_t x_coord, int16_t y_coord, int16_t 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;
|
||||
|
||||
// if (x_coord+l > PIXELS_PER_ROW)
|
||||
|
@ -870,7 +790,8 @@ uint16_t red16, green16, blue16;
|
|||
|
||||
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;
|
||||
y_coord -= ROWS_PER_FRAME;
|
||||
|
@ -878,7 +799,8 @@ uint16_t red16, green16, blue16;
|
|||
|
||||
// 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
|
||||
|
@ -902,12 +824,13 @@ uint16_t red16, green16, blue16;
|
|||
|
||||
// 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
|
||||
do
|
||||
{ // iterate pixels in a row
|
||||
int16_t _x = x_coord + --_l;
|
||||
|
||||
/*
|
||||
|
@ -927,7 +850,6 @@ uint16_t red16, green16, blue16;
|
|||
} 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,7 +857,8 @@ 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){
|
||||
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;
|
||||
|
||||
|
@ -971,7 +894,8 @@ uint16_t red16, green16, blue16;
|
|||
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
|
||||
|
@ -995,9 +919,11 @@ uint16_t red16, green16, blue16;
|
|||
|
||||
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;
|
||||
RGB_output_bits <<= BITS_RGB2_OFFSET;
|
||||
|
@ -1006,7 +932,7 @@ uint16_t red16, green16, blue16;
|
|||
// 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 = 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
|
||||
|
@ -1015,7 +941,6 @@ uint16_t red16, green16, blue16;
|
|||
} 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,19 +950,25 @@ 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 {
|
||||
}
|
||||
else
|
||||
{
|
||||
// draw using h-lines
|
||||
do {
|
||||
do
|
||||
{
|
||||
--h;
|
||||
hlineDMA(x, y + h, w, r, g, b);
|
||||
} while (h);
|
||||
|
|
|
@ -52,7 +52,6 @@
|
|||
#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
|
||||
// given we only map to 14 physical output wires/bits, we waste 2 bits.
|
||||
|
||||
|
@ -129,54 +128,59 @@
|
|||
/** @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 {
|
||||
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
|
||||
/** @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
|
||||
* 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 size(uint8_t _dpth=0 ) { if (!_dpth) _dpth = colour_depth; return width * _dpth * sizeof(ESP32_I2S_DMA_STORAGE_TYPE); };
|
||||
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
|
||||
/** @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)]); };
|
||||
// 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)]); };
|
||||
|
||||
// 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]); };
|
||||
|
||||
// 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) {
|
||||
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)
|
||||
// #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.");
|
||||
}
|
||||
*/
|
||||
#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");
|
||||
#endif
|
||||
|
||||
// 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);
|
||||
|
||||
// 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; }
|
||||
};
|
||||
|
||||
|
||||
/* 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).
|
||||
|
@ -184,7 +188,8 @@ struct rowBitStruct {
|
|||
* This structure isn't actually allocated in one memory block anymore, as the library now allocates
|
||||
* memory per row (per rowBits) instead.
|
||||
*/
|
||||
struct frameStruct {
|
||||
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<std::shared_ptr<rowBitStruct>> rowBits;
|
||||
};
|
||||
|
@ -216,8 +221,7 @@ static const uint16_t DRAM_ATTR lumConvTab[]={
|
|||
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
|
||||
};
|
||||
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,49 +229,65 @@ 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;
|
||||
// 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 speed
|
||||
clk_speed i2sspeed;
|
||||
|
||||
// How many clock cycles to blank OE before/after LAT signal change, default is 1 clock
|
||||
uint8_t latch_blanking;
|
||||
|
||||
/**
|
||||
* I2S clock phase
|
||||
|
@ -285,6 +305,9 @@ 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,
|
||||
|
@ -294,44 +317,38 @@ struct HUB75_I2S_CFG {
|
|||
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,
|
||||
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)
|
||||
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){
|
||||
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){
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -341,20 +358,20 @@ struct HUB75_I2S_CFG {
|
|||
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
|
||||
*
|
||||
|
@ -367,7 +384,8 @@ class MatrixPanel_I2S_DMA {
|
|||
#elif !defined NO_GFX
|
||||
: Adafruit_GFX(MATRIX_WIDTH, MATRIX_HEIGHT)
|
||||
#endif
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* MatrixPanel_I2S_DMA
|
||||
|
@ -375,21 +393,24 @@ class MatrixPanel_I2S_DMA {
|
|||
* @param {HUB75_I2S_CFG} opts : structure with matrix configuration
|
||||
*
|
||||
*/
|
||||
MatrixPanel_I2S_DMA(const HUB75_I2S_CFG& opts) :
|
||||
MatrixPanel_I2S_DMA(const HUB75_I2S_CFG &opts)
|
||||
#ifdef USE_GFX_ROOT
|
||||
GFX(opts.mx_width*opts.chain_length, opts.mx_height)
|
||||
: 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)
|
||||
: 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(){
|
||||
bool begin()
|
||||
{
|
||||
|
||||
if (initialized) return true; // we don't do this twice or more!
|
||||
if(!config_set) return false;
|
||||
if (initialized)
|
||||
return true; // we don't do this twice or more!
|
||||
if (!config_set)
|
||||
return false;
|
||||
|
||||
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);
|
||||
|
@ -406,7 +427,6 @@ class MatrixPanel_I2S_DMA {
|
|||
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);
|
||||
|
||||
|
||||
// initialize some specific panel drivers
|
||||
if (m_cfg.driver)
|
||||
shiftDriver(m_cfg);
|
||||
|
@ -420,8 +440,10 @@ class MatrixPanel_I2S_DMA {
|
|||
/* 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.
|
||||
|
||||
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
|
||||
|
@ -431,22 +453,21 @@ class MatrixPanel_I2S_DMA {
|
|||
|
||||
// showDMABuffer(); // show backbuf_id of 0
|
||||
|
||||
if (!initialized) {
|
||||
if (!initialized)
|
||||
{
|
||||
ESP_LOGE("being()", "MatrixPanel_I2S_DMA::begin() failed!");
|
||||
}
|
||||
|
||||
return initialized;
|
||||
|
||||
}
|
||||
|
||||
// Obj destructor
|
||||
~MatrixPanel_I2S_DMA(){
|
||||
~MatrixPanel_I2S_DMA()
|
||||
{
|
||||
|
||||
dma_bus.release();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* overload for compatibility
|
||||
*/
|
||||
|
@ -467,10 +488,10 @@ class MatrixPanel_I2S_DMA {
|
|||
* @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){
|
||||
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);
|
||||
|
@ -479,10 +500,10 @@ class MatrixPanel_I2S_DMA {
|
|||
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){
|
||||
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)
|
||||
|
@ -495,10 +516,10 @@ class MatrixPanel_I2S_DMA {
|
|||
* @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){
|
||||
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);
|
||||
|
@ -507,10 +528,10 @@ class MatrixPanel_I2S_DMA {
|
|||
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){
|
||||
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)
|
||||
|
@ -523,20 +544,22 @@ class MatrixPanel_I2S_DMA {
|
|||
* @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){
|
||||
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();
|
||||
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);
|
||||
endWrite();
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -567,30 +590,19 @@ class MatrixPanel_I2S_DMA {
|
|||
*/
|
||||
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; }
|
||||
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;
|
||||
fb = &frame_buffer[back_buffer_id];
|
||||
|
||||
|
||||
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) { }
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
|
@ -608,32 +620,12 @@ class MatrixPanel_I2S_DMA {
|
|||
brightness = b;
|
||||
brtCtrlOEv2(b, 0);
|
||||
|
||||
if (m_cfg.double_buff) {
|
||||
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);
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param uint8_t b - 8-bit brightness value
|
||||
*/
|
||||
|
@ -653,7 +645,6 @@ class MatrixPanel_I2S_DMA {
|
|||
// setPanelBrightness(b * PIXELS_PER_ROW / 256);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief - Sets how many clock cycles to blank OE before/after LAT signal change
|
||||
* @param uint8_t pulses - clocks before/after OE
|
||||
|
@ -669,8 +660,10 @@ class MatrixPanel_I2S_DMA {
|
|||
*/
|
||||
const HUB75_I2S_CFG &getCfg() const { return m_cfg; };
|
||||
|
||||
inline bool setCfg(const HUB75_I2S_CFG& cfg){
|
||||
if(initialized) return false;
|
||||
inline bool setCfg(const HUB75_I2S_CFG &cfg)
|
||||
{
|
||||
if (initialized)
|
||||
return false;
|
||||
|
||||
m_cfg = cfg;
|
||||
PIXELS_PER_ROW = m_cfg.mx_width * m_cfg.chain_length;
|
||||
|
@ -684,26 +677,16 @@ class MatrixPanel_I2S_DMA {
|
|||
/**
|
||||
* Stop the ESP32 DMA Engine. Screen will forever be black until next ESP reboot.
|
||||
*/
|
||||
void stopDMAoutput() {
|
||||
void stopDMAoutput()
|
||||
{
|
||||
resetbuffers();
|
||||
// i2s_parallel_stop_dma(ESP32_I2S_DEVICE);
|
||||
dma_bus.dma_transfer_stop();
|
||||
}
|
||||
|
||||
void startWrite() {
|
||||
//ESP_LOGI("TAG", "startWrite() called");
|
||||
active_gfx_writes++;
|
||||
}
|
||||
|
||||
|
||||
void endWrite() {
|
||||
active_gfx_writes--;
|
||||
}
|
||||
|
||||
// ------- PROTECTED -------
|
||||
// those might be useful for child classes, like VirtualMatrixPanel
|
||||
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.
|
||||
|
@ -712,7 +695,7 @@ class MatrixPanel_I2S_DMA {
|
|||
* 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);
|
||||
void clearFrameBuffer(bool _buff_id);
|
||||
|
||||
/* 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);
|
||||
|
@ -723,18 +706,18 @@ class MatrixPanel_I2S_DMA {
|
|||
/**
|
||||
* wipes DMA buffer(s) and reset all colour/service bits
|
||||
*/
|
||||
inline void resetbuffers(){
|
||||
|
||||
clearFrameBuffer();
|
||||
inline void resetbuffers()
|
||||
{
|
||||
clearFrameBuffer(0);
|
||||
brtCtrlOEv2(brightness, 0);
|
||||
|
||||
if (m_cfg.double_buff) {
|
||||
|
||||
clearFrameBuffer(1);
|
||||
brtCtrlOEv2(brightness, 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#ifndef NO_FAST_FUNCTIONS
|
||||
/**
|
||||
|
@ -769,7 +752,6 @@ class MatrixPanel_I2S_DMA {
|
|||
|
||||
// ------- PRIVATE -------
|
||||
private:
|
||||
|
||||
/* Calculate the memory available for DMA use, do some other stuff, and allocate accordingly */
|
||||
bool allocateDMAmemory();
|
||||
|
||||
|
@ -787,6 +769,11 @@ class MatrixPanel_I2S_DMA {
|
|||
*/
|
||||
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
|
||||
|
@ -808,26 +795,46 @@ class MatrixPanel_I2S_DMA {
|
|||
* @param w - rectangular width
|
||||
* @param h - rectangular height
|
||||
*/
|
||||
void transform(int16_t &x, int16_t &y, int16_t &w, int16_t &h){
|
||||
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;
|
||||
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:
|
||||
|
||||
// Matrix i2s settings
|
||||
|
@ -840,15 +847,10 @@ class MatrixPanel_I2S_DMA {
|
|||
* 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;
|
||||
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(...)
|
||||
|
||||
// ESP 32 DMA Linked List descriptor
|
||||
int desccount = 0;
|
||||
// lldesc_t * dmadesc_a = {0};
|
||||
// lldesc_t * dmadesc_b = {0};
|
||||
|
||||
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?
|
||||
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
|
||||
|
||||
|
@ -860,6 +862,7 @@ class MatrixPanel_I2S_DMA {
|
|||
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;
|
||||
|
@ -875,10 +878,14 @@ 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
|
||||
|
@ -926,19 +933,21 @@ inline void MatrixPanel_I2S_DMA::fillScreen(CRGB color)
|
|||
}
|
||||
#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) {
|
||||
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) {
|
||||
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) {
|
||||
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
|
||||
//
|
||||
|
@ -956,18 +965,17 @@ inline void MatrixPanel_I2S_DMA::drawIcon (int *ico, int16_t x, int16_t y, int16
|
|||
*/
|
||||
|
||||
int i, j;
|
||||
for (i = 0; i < rows; i++) {
|
||||
for (j = 0; j < 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 <https://github.com/pixelmatix/SmartMatrix/tree/teensylc>
|
||||
// and Sprite_TM: https://www.esp32.com/viewtopic.php?f=17&t=3188 and https://www.esp32.com/viewtopic.php?f=13&t=3256
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
@ -98,3 +102,89 @@ void MatrixPanel_I2S_DMA::fm6124init(const HUB75_I2S_CFG& _cfg) {
|
|||
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
|
||||
}
|
|
@ -30,6 +30,8 @@
|
|||
#include <Fonts/FreeSansBold12pt7b.h>
|
||||
#endif
|
||||
|
||||
// #include <iostream>
|
||||
|
||||
struct VirtualCoords
|
||||
{
|
||||
int16_t x;
|
||||
|
@ -44,9 +46,25 @@ struct VirtualCoords
|
|||
|
||||
enum PANEL_SCAN_RATE
|
||||
{
|
||||
NORMAL_TWO_SCAN, NORMAL_ONE_SIXTEEN, // treated as the same
|
||||
NORMAL_TWO_SCAN,
|
||||
NORMAL_ONE_SIXTEEN, // treated as the same
|
||||
FOUR_SCAN_32PX_HIGH,
|
||||
FOUR_SCAN_16PX_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,20 +77,7 @@ 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)
|
||||
#elif !defined NO_GFX
|
||||
|
@ -81,6 +86,8 @@ public:
|
|||
{
|
||||
this->display = &disp;
|
||||
|
||||
panel_chain_type = _panel_chain_type;
|
||||
|
||||
panelResX = _panelResX;
|
||||
panelResY = _panelResY;
|
||||
|
||||
|
@ -90,7 +97,7 @@ public:
|
|||
virtualResX = vmodule_cols * _panelResX;
|
||||
virtualResY = vmodule_rows * _panelResY;
|
||||
|
||||
dmaResX = panelResX * vmodule_rows * vmodule_cols;
|
||||
dmaResX = panelResX * vmodule_rows * vmodule_cols - 1;
|
||||
|
||||
/* Virtual Display width() and height() will return a real-world value. For example:
|
||||
* Virtual Display width: 128
|
||||
|
@ -99,17 +106,15 @@ public:
|
|||
* So, not values that at 0 to X-1
|
||||
*/
|
||||
|
||||
_s_chain_party = serpentine_chain; // serpentine, or 'S' chain?
|
||||
_chain_top_down = top_down_chain;
|
||||
|
||||
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 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);
|
||||
|
||||
|
@ -119,25 +124,45 @@ public:
|
|||
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 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 setPhysicalPanelScanRate(PANEL_SCAN_RATE rate);
|
||||
void setZoomFactor(int scale);
|
||||
|
||||
protected:
|
||||
virtual VirtualCoords getCoords(int16_t &x, int16_t &y);
|
||||
private:
|
||||
MatrixPanel_I2S_DMA *display;
|
||||
|
||||
PANEL_CHAIN_TYPE panel_chain_type;
|
||||
PANEL_SCAN_RATE panel_scan_rate = NORMAL_TWO_SCAN;
|
||||
|
||||
virtual VirtualCoords getCoords(int16_t x, int16_t y);
|
||||
VirtualCoords coords;
|
||||
|
||||
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;
|
||||
int16_t virtualResX;
|
||||
int16_t virtualResY;
|
||||
|
||||
PANEL_SCAN_RATE _panelScanRate = NORMAL_TWO_SCAN;
|
||||
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,72 +171,207 @@ 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
|
||||
|
||||
// Do we want to rotate?
|
||||
if (_rotate)
|
||||
{
|
||||
int16_t temp_x = x;
|
||||
x = y;
|
||||
y = virtualResY - 1 - temp_x;
|
||||
}
|
||||
#if !defined NO_GFX
|
||||
// I don't give any support if Adafruit GFX isn't being used.
|
||||
|
||||
if (x < 0 || x >= virtualResX || y < 0 || y >= virtualResY)
|
||||
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!
|
||||
// Serial.printf("VirtualMatrixPanel::getCoords(): Invalid virtual display coordinate. x,y: %d, %d\r\n", x, y);
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
#endif
|
||||
|
||||
// inverts the y the row
|
||||
coords.y = panelResY - 1 - (y % panelResY);
|
||||
}
|
||||
else
|
||||
// Do we want to rotate?
|
||||
switch (_rotate) {
|
||||
case 0: //no rotation, do nothing
|
||||
break;
|
||||
|
||||
case (1): //90 degree rotation
|
||||
{
|
||||
// Normal chain pixel co-ordinate
|
||||
coords.x = x + ((y / panelResY) * (virtualResX));
|
||||
coords.y = y % panelResY;
|
||||
int16_t temp_x = virt_x;
|
||||
virt_x = virt_y;
|
||||
virt_y = virtualResY - 1 - temp_x;
|
||||
break;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse co-ordinates if panel chain from ESP starts from the TOP RIGHT
|
||||
if (_chain_top_down)
|
||||
int row = (virt_y / panelResY); // 0 indexed
|
||||
switch (panel_chain_type)
|
||||
{
|
||||
/*
|
||||
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;
|
||||
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 (_panelScanRate == FOUR_SCAN_32PX_HIGH)
|
||||
|
||||
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
|
||||
|
@ -221,12 +381,7 @@ inline VirtualCoords VirtualMatrixPanel::getCoords(int16_t &x, int16_t &y)
|
|||
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)
|
||||
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
|
||||
}
|
||||
|
@ -238,17 +393,11 @@ inline VirtualCoords VirtualMatrixPanel::getCoords(int16_t &x, int16_t &y)
|
|||
// 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(") ");
|
||||
*/
|
||||
coords.y = (virt_y >> 4) * 8 + (virt_y & 0b00000111);
|
||||
}
|
||||
else if (_panelScanRate == FOUR_SCAN_16PX_HIGH)
|
||||
else if (panel_scan_rate == FOUR_SCAN_16PX_HIGH)
|
||||
{
|
||||
if ((y & 8) == 0)
|
||||
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
|
||||
}
|
||||
|
@ -257,24 +406,41 @@ inline VirtualCoords VirtualMatrixPanel::getCoords(int16_t &x, int16_t &y)
|
|||
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);
|
||||
if (virt_y < 32)
|
||||
coords.y = (virt_y >> 4) * 8 + (virt_y & 0b00000111);
|
||||
else
|
||||
{
|
||||
coords.y = ((y - 32) >> 4) * 8 + (y & 0b00000111);
|
||||
coords.y = ((virt_y - 32) >> 4) * 8 + (virt_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);
|
||||
|
||||
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
|
||||
|
@ -288,7 +454,7 @@ inline void VirtualMatrixPanel::fillScreenRGB888(uint8_t r, uint8_t g, uint8_t b
|
|||
|
||||
inline void VirtualMatrixPanel::drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b)
|
||||
{
|
||||
getCoords(x, y);
|
||||
this->getCoords(x, y);
|
||||
this->display->drawPixelRGB888(coords.x, coords.y, r, g, b);
|
||||
}
|
||||
|
||||
|
@ -296,7 +462,7 @@ inline void VirtualMatrixPanel::drawPixelRGB888(int16_t x, int16_t y, uint8_t r,
|
|||
// Support for CRGB values provided via FastLED
|
||||
inline void VirtualMatrixPanel::drawPixel(int16_t x, int16_t y, CRGB color)
|
||||
{
|
||||
getCoords(x, y);
|
||||
this->getCoords(x, y);
|
||||
this->display->drawPixel(coords.x, coords.y, color);
|
||||
}
|
||||
|
||||
|
@ -306,31 +472,56 @@ inline void VirtualMatrixPanel::fillScreen(CRGB color)
|
|||
}
|
||||
#endif
|
||||
|
||||
inline void VirtualMatrixPanel::setRotate(bool rotate)
|
||||
inline void VirtualMatrixPanel::setRotation(int rotate)
|
||||
{
|
||||
if(rotate < 4 && rotate >= 0)
|
||||
_rotate = rotate;
|
||||
|
||||
#ifndef NO_GFX
|
||||
// We don't support rotation by degrees.
|
||||
if (rotate)
|
||||
{
|
||||
setRotation(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
setRotation(0);
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
// 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);
|
||||
|
@ -339,7 +530,7 @@ inline void VirtualMatrixPanel::drawDisplayTest()
|
|||
{
|
||||
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->setCursor((panel * panelResX) + 2, panelResY - 4);
|
||||
this->display->print((vmodule_cols * vmodule_rows) - panel);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,54 +28,35 @@ Modified heavily for the ESP32 HUB75 DMA library by:
|
|||
#include <driver/periph_ctrl.h>
|
||||
#include <soc/gpio_sig_map.h>
|
||||
|
||||
#include <Arduino.h> // Need to make sure thi is uncommented to get ESP_LOG output on (Arduino) Serial output!!!!
|
||||
#if defined (ARDUINO_ARCH_ESP32)
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
|
||||
#include <esp_err.h>
|
||||
#include <esp_log.h>
|
||||
|
||||
// Get CPU freq function.
|
||||
#include <soc/rtc.h>
|
||||
|
||||
/*
|
||||
|
||||
callback shiftCompleteCallback;
|
||||
void setShiftCompleteCallback(callback f) {
|
||||
shiftCompleteCallback = f;
|
||||
}
|
||||
|
||||
volatile int previousBufferOutputLoopCount = 0;
|
||||
volatile bool previousBufferFree = true;
|
||||
|
||||
static void IRAM_ATTR irq_hndlr(void* arg) { // if we use I2S1 (default)
|
||||
static void IRAM_ATTR i2s_isr(void* arg) {
|
||||
|
||||
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) {
|
||||
// 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);
|
||||
|
||||
active_dma_buffer_output_count++;
|
||||
|
||||
/*
|
||||
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;
|
||||
// at this point, the previously active buffer is free, go ahead and write to it
|
||||
previousBufferFree = true;
|
||||
}
|
||||
|
||||
bool DRAM_ATTR i2s_parallel_is_previous_buffer_free() {
|
||||
return previousBufferFree;
|
||||
}
|
||||
*/
|
||||
|
||||
} // 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)
|
||||
|
@ -412,24 +377,14 @@ static void IRAM_ATTR irq_hndlr(void* arg) { // if we use I2S1 (default)
|
|||
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.
|
||||
*/
|
||||
if (_double_dma_buffer) {
|
||||
// If we have double buffering, then allocate an interrupt service routine function
|
||||
// that can be used for I2S0/I2S1 created interrupts.
|
||||
|
||||
// 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);
|
||||
// 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);
|
||||
|
||||
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;
|
||||
}
|
||||
// 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 defined (CONFIG_IDF_TARGET_ESP32S2)
|
||||
|
@ -438,7 +393,6 @@ static void IRAM_ATTR irq_hndlr(void* arg) { // if we use I2S1 (default)
|
|||
ESP_LOGD("ESP32-ORIG", "init() GPIO and clock configuration set for ESP32");
|
||||
#endif
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -611,21 +565,16 @@ 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) {}
|
||||
|
||||
//fix _dmadesc_ loop issue #407
|
||||
//need to connect the up comming _dmadesc_ not the old one
|
||||
_dmadesc_b[_dmadesc_last].qe.stqe_next = &_dmadesc_b[0];
|
||||
|
@ -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
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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,7 +15,7 @@
|
|||
|
||||
PLEASE SUPPORT THEM!
|
||||
|
||||
*/
|
||||
********************************************************************************************/
|
||||
#if __has_include (<hal/lcd_ll.h>)
|
||||
// Stop compile errors: /src/platforms/esp32s3/gdma_lcd_parallel16.hpp:64:10: fatal error: hal/lcd_ll.h: No such file or directory
|
||||
|
||||
|
@ -26,22 +26,19 @@
|
|||
#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);
|
||||
|
@ -109,10 +109,24 @@
|
|||
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
|
||||
{
|
||||
|
@ -120,12 +134,10 @@
|
|||
auto freq = (_cfg.bus_freq);
|
||||
|
||||
auto _div_num = 8; // 20Mhz
|
||||
if (freq < 20000000L)
|
||||
{
|
||||
if (freq < 20000000L) {
|
||||
_div_num = 12; // 13Mhz
|
||||
}
|
||||
else if (freq > 20000000L)
|
||||
{
|
||||
else if (freq > 20000000L) {
|
||||
_div_num = 6; // 26Mhz --- likely to have noise without a good connection
|
||||
}
|
||||
|
||||
|
@ -133,8 +145,8 @@
|
|||
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_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));
|
||||
|
||||
|
||||
|
@ -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,37 +252,17 @@
|
|||
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...
|
||||
|
@ -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
|
||||
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;
|
||||
|
||||
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
5
testing/README.md
Normal file
|
@ -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
|
||||
```
|
189
testing/baseline.hpp
Normal file
|
@ -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;
|
||||
}
|
459
testing/virtual.cpp
Normal file
|
@ -0,0 +1,459 @@
|
|||
#include <iostream>
|
||||
#include <string>
|
||||
#include <list>
|
||||
|
||||
|
||||
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 <PANEL_CHAIN_TYPE> 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;
|
||||
}
|