Compare commits
79 commits
8bc420da0e
...
aed04adfcd
Author | SHA1 | Date | |
---|---|---|---|
|
aed04adfcd | ||
|
c9a8c50702 | ||
|
0be6f369ad | ||
|
27e61846a2 | ||
|
c5fd8d2d52 | ||
|
84d4388e4a | ||
|
2b4a90255f | ||
|
15fd8671d9 | ||
|
c6e708ae13 | ||
|
f4357acd2d | ||
|
d121fa10ce | ||
|
39d5960fc8 | ||
|
2a91f7e5d6 | ||
|
3d5dda371d | ||
|
24bb80df29 | ||
|
1d25183895 | ||
|
fd4831cb0f | ||
|
33883656da | ||
|
67c4decce0 | ||
|
9add343a41 | ||
|
1c8629f5ae | ||
|
b586721422 | ||
|
9ae0e0c200 | ||
|
a3298cc7a2 | ||
|
0dba05b64a | ||
|
af600a2fb6 | ||
|
22a8eae880 | ||
|
dc70588980 | ||
|
ddebd4c317 | ||
|
3382896c6e | ||
|
7ba943e56d | ||
|
a374445b88 | ||
|
869078befb | ||
|
839443fdf6 | ||
|
3e1d74f4e1 | ||
|
5a5150881f | ||
|
830283a6aa | ||
|
c6a298e1c4 | ||
|
fc1e54e75f | ||
|
286dc1d2fb | ||
|
2ac3367e93 | ||
|
35e441e8af | ||
|
42f5da448f | ||
|
5af3d83a10 | ||
|
d18758c9e4 | ||
|
d77a90f809 | ||
|
3a78a9dfb6 | ||
|
9da34a7225 | ||
|
e369f0ddb8 | ||
|
d02c44312f | ||
|
655c2bc838 | ||
|
0e5c84da78 | ||
|
6f7d1839c8 | ||
|
7d2ed7936d | ||
|
4695ed3a0f | ||
|
79ab7e76f8 | ||
|
4fa43b7fbc | ||
|
588f23456b | ||
|
cefa9d01b8 | ||
|
ee796eb96f | ||
|
cac0137db7 | ||
|
8a0d40a41f | ||
|
4976416d18 | ||
|
447254d2b4 | ||
|
c7cb4e30b1 | ||
|
99131abc83 | ||
|
aa6fc263b2 | ||
|
87518f921d | ||
|
babd8fd154 | ||
|
a03b8b6e47 | ||
|
2ef97a9368 | ||
|
9f5e1adaad | ||
|
2f7f5350e5 | ||
|
a5d6611b65 | ||
|
24b98a87c3 | ||
|
bba1a47f01 | ||
|
268fd5ea4e | ||
|
4b3404a361 | ||
|
7929b97850 |
28 changed files with 1874 additions and 812 deletions
.github/workflows
README.mddoc
examples
1_SimpleTestShapes
3_DoubleBuffer
4_OtherShiftDriverPanel
Four_Scan_Panel
PIO_TestPatterns
Pixel_Mapping_Test
ScrollingTextLayer
VirtualMatrixPanel
src
ESP32-HUB75-MatrixPanel-I2S-DMA.cppESP32-HUB75-MatrixPanel-I2S-DMA.hESP32-HUB75-VirtualMatrixPanel_T.hppESP32-VirtualMatrixPanel-I2S-DMA.h
platforms
testing
|
@ -1,4 +1,4 @@
|
|||
name: esp-idf 5.1.2 with Adafruit GFX Library
|
||||
name: esp-idf with Adafruit GFX Library
|
||||
|
||||
on:
|
||||
push:
|
||||
|
@ -12,7 +12,7 @@ on:
|
|||
|
||||
jobs:
|
||||
build:
|
||||
name: esp-idf with Adafruit GFX
|
||||
name: esp-idf v5.3.2 with Adafruit GFX
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
@ -39,10 +39,13 @@ jobs:
|
|||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'espressif/arduino-esp32'
|
||||
ref: 3.1.3
|
||||
path: 'examples/esp-idf/with-gfx/components/arduino'
|
||||
- name: Edit Adafruit_BusIO CMakeLists.txt
|
||||
run: sed -i 's/arduino-esp32)/arduino)/g' examples/esp-idf/with-gfx/components/Adafruit_BusIO/CMakeLists.txt
|
||||
- name: esp-idf build
|
||||
uses: espressif/esp-idf-ci-action@v1
|
||||
with:
|
||||
esp_idf_version: v5.1.2
|
||||
esp_idf_version: v5.3.2
|
||||
target: esp32
|
||||
path: 'examples/esp-idf/with-gfx'
|
|
@ -1,4 +1,4 @@
|
|||
name: esp-idf 5.1.2 without Adafruit GFX Library
|
||||
name: esp-idf without Adafruit GFX Library
|
||||
|
||||
on:
|
||||
push:
|
||||
|
@ -12,7 +12,7 @@ on:
|
|||
|
||||
jobs:
|
||||
build:
|
||||
name: esp-idf 5.1.2 without Adafruit GFX
|
||||
name: esp-idf 5.1.4 without Adafruit GFX
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
@ -28,6 +28,6 @@ jobs:
|
|||
- name: esp-idf build
|
||||
uses: espressif/esp-idf-ci-action@v1
|
||||
with:
|
||||
esp_idf_version: v5.1.2
|
||||
esp_idf_version: v5.1.4
|
||||
target: esp32
|
||||
path: 'examples/esp-idf/without-gfx'
|
4
.github/workflows/pio_arduino_build.yml
vendored
4
.github/workflows/pio_arduino_build.yml
vendored
|
@ -2,7 +2,7 @@
|
|||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
|
||||
# https://docs.platformio.org/en/latest/integration/ci/github-actions.html
|
||||
|
||||
name: PlatformIO 6.1.11 Arduino CI
|
||||
name: PlatformIO 6.1.17 Arduino CI
|
||||
|
||||
on:
|
||||
push:
|
||||
|
@ -48,7 +48,7 @@ jobs:
|
|||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install Platformio
|
||||
run: pip install --upgrade platformio==6.1.11
|
||||
run: pip install --upgrade platformio==6.1.17
|
||||
- name: Run PlatformIO CI (Arduino)
|
||||
if: ${{ matrix.framework == 'Arduino'}}
|
||||
env:
|
||||
|
|
34
README.md
34
README.md
|
@ -1,6 +1,6 @@
|
|||
# HUB75 RGB LED matrix panel library utilizing ESP32 DMA
|
||||
|
||||
__[BUILD OPTIONS](/doc/BuildOptions.md) | [EXAMPLES](/examples/README.md)__ | [](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/actions/workflows/pio_build.yml)
|
||||
__[BUILD OPTIONS](/doc/BuildOptions.md) | [EXAMPLES](/examples/README.md)__ | [](https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA/actions)
|
||||
|
||||
**Table of Content**
|
||||
|
||||
|
@ -11,6 +11,7 @@ __[BUILD OPTIONS](/doc/BuildOptions.md) | [EXAMPLES](/examples/README.md)__ | [!
|
|||
* [Supported Panels](#supported-panels)
|
||||
* [Panel driver chips known to be working well](#driver-chips-known-to-be-working-well)
|
||||
* [Unsupported Panels](#unsupported-panels)
|
||||
* [Other hardware notes](#Other-hardware-notes)
|
||||
- [Getting Started](#getting-started)
|
||||
* [1. Library Installation](#1-library-installation)
|
||||
* [2. Wiring the ESP32 to an LED Matrix Panel](#2-wiring-the-esp32-to-an-led-matrix-panel)
|
||||
|
@ -25,11 +26,12 @@ __[BUILD OPTIONS](/doc/BuildOptions.md) | [EXAMPLES](/examples/README.md)__ | [!
|
|||
* [Inspiration](#inspiration)
|
||||
* [Cool uses of this library](#cool-uses-of-this-library)
|
||||
- [Thank you!](#thank-you)
|
||||
- [Music](#music)
|
||||
|
||||
# Introduction
|
||||
* This is an ESP32 Arduino/IDF library for HUB75 / HUB75E connection based RGB LED panels.
|
||||
* This library 'out of the box' (mostly) supports HUB75 panels where simple TWO rows/lines are updated in parallel... referred to as 'two scan' panels within this documentation.
|
||||
* 'Four scan' panels are also supported - but please refer to the Four Scan Panel example sketch.
|
||||
* 1/4 (aka. 'Four Scan') outdoor panels are also supported - but please refer to the VirtualMatrixPanel example.
|
||||
* The library uses the DMA functionality provided by the ESP32's 'LCD Mode' for fast data output.
|
||||
|
||||
## Features
|
||||
|
@ -52,7 +54,7 @@ RISC-V ESP32's (like the C3) are not supported as they do not have the hardware
|
|||
|
||||
Please use the ['Memory Calculator'](/doc/memcalc.md) to see what is *typically* achievable with the typical ESP32. This is only a guide. 
|
||||
|
||||
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.
|
||||
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 (PSRAM)** (i.e. ESP32 S3 N8R8 variant). However, due to bandwidth limitations, the maximum output frequency is limited to approx. 13Mhz, which will limit the real-world number of panels that can be chained without flicker. Please do not use PSRAM as the DMA buffer if using QUAD SPI (Q-SPI), as it's too slow.
|
||||
|
||||
To enable PSRAM support on the ESP32-S3, refer to [the build options](/doc/BuildOptions.md) to enable.
|
||||
|
||||
|
@ -82,7 +84,7 @@ Ones interested in internals of such matrices could find [this article](https://
|
|||
* DP3246 with SM5368 row addressing registers
|
||||
|
||||
## Specific chips found NOT TO work
|
||||
* ANY panel that uses S-PWM or PWM based chips (such as the RUL6024, MBI6024).
|
||||
* ANY panel that has S-PWM or PWM based chips (such as the RUL6024, MBI6024, HX6158SP, MBI5051, MBI5052, MBI5053, ICND2055CP etc.). There are LOTS of panels now which are 'self PWM generating'. Essentially these panel aren't just a dumb array of LEDs and a series of shift registers, but have a framebuffer that pixel colour data is sent to, and they generate the relevant PWM output for each LED, independantly. A more advanced LED panel technology, but not what this library supports.
|
||||
* [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)).
|
||||
|
@ -90,6 +92,11 @@ Ones interested in internals of such matrices could find [this article](https://
|
|||
|
||||
Please use an [alternative library](https://github.com/2dom/PxMatrix) if you bought one of these.
|
||||
|
||||
## Other hardware notes
|
||||
There appers to be an issue with some ESP32-S3 based products when using this library. The thinking is the high-frequency DMA output generated by this library affects the S3's sensitive WiFi radio if the PCB isn't designed to minimise EMF.
|
||||
* 'Adafruit MatrixPortal S3' in particular does not work well with this library. Do not buy if you want to use with this library and WiFi. [Discussion](https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA/discussions/258#discussioncomment-12274566)
|
||||
|
||||
|
||||
# Getting Started
|
||||
## 1. Library Installation
|
||||
|
||||
|
@ -138,6 +145,7 @@ Various people have created PCBs for which one can simply connect an ESP32 to a
|
|||
* Brian Lough's [ESP32 I2S Matrix Shield](https://github.com/rorosaurus/esp32-hub75-driver)
|
||||
* Charles Hallard's [WeMos Matrix Shield](https://github.com/hallard/WeMos-Matrix-Shield-DMA)
|
||||
* Bogdan Sass's [Morph Clock Shield](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/discussions/110#discussioncomment-861152)
|
||||
* [Matouch 1.28" ToolSet_RGB LED Matrix](https://www.makerfabs.com/matouch-1-28-toolset-rgb-led-matrix.html)
|
||||
|
||||
Please contact or order these products from the respective authors.
|
||||
|
||||
|
@ -238,6 +246,11 @@ An example:
|
|||
dma_display->setLatBlanking(2);
|
||||
```
|
||||
|
||||
## Clock Phase
|
||||
If you are facing issues with pixels being 'off' by 1 px to the co-ordinate requested, or experiencing ghosting, then it could be due to the 'clock phase' setting. For some panels data is clocked 'in' with negative clock edge, others with the positive.
|
||||
|
||||
By default this library is configured to 'clock data' in with a positive clock edge. To change this, configure with `mxconfig.clkphase = false;`. Refer to the [example](https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA/blob/a5d6611b65c365a252e6787e0afc267cf63c1996/examples/1_SimpleTestShapes/1_SimpleTestShapes.ino#L98) for the relevant line that is commented out.
|
||||
|
||||
## Power, Power and Power!
|
||||
Having a good power supply is CRITICAL, and it is highly recommended, for chains of LED Panels to have a 1000-2000uf capacitor soldered to the back of each LED Panel across the [GND and VCC pins](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/39#issuecomment-720780463), otherwise you WILL run into issues with 'flashy' graphics whereby a large amount of LEDs are turned on and off in succession (due to current/power draw peaks and troughs).
|
||||
|
||||
|
@ -262,6 +275,7 @@ There are a number of great looking LED graphical display projects which leverag
|
|||
* [Big Visualisation](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/discussions/155)
|
||||
* [Clockwise](https://jnthas.github.io/clockwise/)
|
||||
* [ZeDMD](https://github.com/PPUC/ZeDMD)
|
||||
* [MatrixCOS](https://github.com/mklossde/MatrixCOS)
|
||||
|
||||
# Thank you!
|
||||
* [Brian Lough](https://www.tindie.com/stores/brianlough/) ([youtube link](https://www.youtube.com/c/brianlough)) for providing code contributions, hardware and suggestions
|
||||
|
@ -274,4 +288,16 @@ There are a number of great looking LED graphical display projects which leverag
|
|||
|
||||
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).
|
||||
|
||||
|
||||
## Music
|
||||
|
||||
Of course, now Generative AI is everywhere. What happens when you ask AI to generate a song about an 'ESP32 HUB75 DMA LED Matrix Panel library'?
|
||||
|
||||
[You get this.](https://suno.com/song/183aa807-9fb6-410c-b0e9-0ea945232950)... Enjoy. 😊
|
||||
|
||||

|
||||
|
||||
|
||||
## Support
|
||||
|
||||
This library has been developed in my own time as a personal project. If you find it useful and wish to support the ongoing development, feel free to [support me](https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA). I'll end up using any funds to potentially buy more panels to test against.
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -13,13 +13,7 @@
|
|||
//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);
|
||||
|
||||
|
||||
uint16_t myBLACK, myWHITE, myRED, myGREEN, myBLUE;
|
||||
|
||||
// Input a value 0 to 255 to get a color value.
|
||||
// The colours are a transition r - g - b - back to r.
|
||||
|
@ -109,6 +103,14 @@ void setup() {
|
|||
dma_display->begin();
|
||||
dma_display->setBrightness8(90); //0-255
|
||||
dma_display->clearScreen();
|
||||
|
||||
myBLACK = dma_display->color565(0, 0, 0);
|
||||
myWHITE = dma_display->color565(255, 255, 255);
|
||||
myRED = dma_display->color565(255, 0, 0);
|
||||
myGREEN = dma_display->color565(0, 255, 0);
|
||||
myBLUE = dma_display->color565(0, 0, 255);
|
||||
|
||||
|
||||
dma_display->fillScreen(myWHITE);
|
||||
|
||||
// fix the screen with green
|
||||
|
|
|
@ -1,20 +1,36 @@
|
|||
// Example uses the following configuration: mxconfig.double_buff = true;
|
||||
// to enable double buffering, which means display->flipDMABuffer(); is required.
|
||||
/**
|
||||
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 usually required. It is only useful when you have a long (in duration)
|
||||
drawing routine that you want to 'flip to' once complete without the drawing being visible to
|
||||
the naked eye when looking at the HUB75 panel.
|
||||
|
||||
Please note that double buffering isn't a silver bullet, and may still result in flickering
|
||||
if you end up 'flipping' the buffer quicker than the physical HUB75 refresh output rate.
|
||||
|
||||
Refer to the runtime debug output to see, i.e:
|
||||
|
||||
[ 2103][I][ESP32-HUB75-MatrixPanel-I2S-DMA.cpp:85] setupDMA(): [I2S-DMA] Minimum visual refresh rate (scan rate from panel top to bottom) requested: 60 Hz
|
||||
[ 2116][W][ESP32-HUB75-MatrixPanel-I2S-DMA.cpp:105] setupDMA(): [I2S-DMA] lsbMsbTransitionBit of 0 gives 57 Hz refresh rate.
|
||||
[ 2128][W][ESP32-HUB75-MatrixPanel-I2S-DMA.cpp:105] setupDMA(): [I2S-DMA] lsbMsbTransitionBit of 1 gives 110 Hz refresh rate.
|
||||
[ 2139][W][ESP32-HUB75-MatrixPanel-I2S-DMA.cpp:118] setupDMA(): [I2S-DMA] lsbMsbTransitionBit of 1 used to achieve refresh rate of 60 Hz.
|
||||
|
||||
**/
|
||||
|
||||
// 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>
|
||||
#include <array>
|
||||
|
||||
MatrixPanel_I2S_DMA *display = nullptr;
|
||||
|
||||
uint16_t myDARK = display->color565(64, 64, 64);
|
||||
uint16_t myWHITE = display->color565(192, 192, 192);
|
||||
uint16_t myRED = display->color565(255, 0, 0);
|
||||
uint16_t myGREEN = display->color565(0, 255, 0);
|
||||
uint16_t myBLUE = display->color565(0, 0, 255);
|
||||
constexpr std::size_t color_num = 5;
|
||||
using colour_arr_t = std::array<uint16_t, color_num>;
|
||||
|
||||
uint16_t colours[5] = { myDARK, myWHITE, myRED, myGREEN, myBLUE };
|
||||
uint16_t myDARK, myWHITE, myRED, myGREEN, myBLUE;
|
||||
colour_arr_t colours;
|
||||
|
||||
struct Square
|
||||
{
|
||||
|
@ -39,12 +55,20 @@ void setup()
|
|||
Serial.println("...Starting Display");
|
||||
HUB75_I2S_CFG mxconfig;
|
||||
mxconfig.double_buff = true; // <------------- Turn on double buffer
|
||||
//mxconfig.clkphase = false;
|
||||
//mxconfig.clkphase = false; // <------------- Turn off double buffer and it'll look flickery
|
||||
|
||||
// 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
|
||||
|
||||
myDARK = display->color565(64, 64, 64);
|
||||
myWHITE = display->color565(192, 192, 192);
|
||||
myRED = display->color565(255, 0, 0);
|
||||
myGREEN = display->color565(0, 255, 0);
|
||||
myBLUE = display->color565(0, 0, 255);
|
||||
|
||||
colours = {{ myDARK, myWHITE, myRED, myGREEN, myBLUE }};
|
||||
|
||||
// Create some random squares
|
||||
for (int i = 0; i < numSquares; i++)
|
||||
{
|
||||
|
@ -61,11 +85,19 @@ void setup()
|
|||
|
||||
void loop()
|
||||
{
|
||||
|
||||
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.
|
||||
// Flip all future drawPixel calls to write to the back buffer which is NOT being displayed.
|
||||
display->flipDMABuffer();
|
||||
|
||||
// SUPER IMPORTANT: Wait at least long enough to ensure that a "frame" has been displayed on the LED Matrix Panel before the next flip!
|
||||
delay(1000/display->calculated_refresh_rate);
|
||||
|
||||
// Now clear the back-buffer we are drawing to.
|
||||
display->clearScreen();
|
||||
|
||||
// This is here to demonstrate flicker if double buffering is disabled. Emulates a long draw routine that would typically occur after a 'clearscreen'.
|
||||
delay(25);
|
||||
|
||||
|
||||
for (int i = 0; i < numSquares; i++)
|
||||
{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
## Ohter driver based LED Matrix Panels ##
|
||||
## Other 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.
|
||||
|
||||
|
|
|
@ -1,153 +0,0 @@
|
|||
/*************************************************************************
|
||||
* Description:
|
||||
*
|
||||
* The underlying implementation of the ESP32-HUB75-MatrixPanel-I2S-DMA only
|
||||
* supports output to HALF scan panels - which means outputting
|
||||
* two lines at the same time, 16 or 32 rows apart if a 32px or 64px high panel
|
||||
* respectively.
|
||||
* This cannot be changed at the DMA layer as it would require a messy and complex
|
||||
* rebuild of the library's internals.
|
||||
*
|
||||
* However, it is possible to connect QUARTER (i.e. FOUR lines updated in parallel)
|
||||
* scan panels to this same library and
|
||||
* 'trick' the output to work correctly on these panels by way of adjusting the
|
||||
* pixel co-ordinates that are 'sent' to the ESP32-HUB75-MatrixPanel-I2S-DMA
|
||||
* library.
|
||||
*
|
||||
**************************************************************************/
|
||||
#include "ESP32-HUB75-MatrixPanel-I2S-DMA.h"
|
||||
|
||||
/* Use the Virtual Display class to re-map co-ordinates such that they draw
|
||||
* correctly on a 32x16 1/8 Scan panel (or chain of such panels).
|
||||
*/
|
||||
#include "ESP32-VirtualMatrixPanel-I2S-DMA.h"
|
||||
|
||||
|
||||
// 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 NUM_ROWS 1 // Number of rows of chained INDIVIDUAL PANELS
|
||||
#define NUM_COLS 2 // Number of INDIVIDUAL PANELS per ROW
|
||||
|
||||
// ^^^ NOTE: DEFAULT EXAMPLE SETUP IS FOR A CHAIN OF TWO x 1/8 SCAN PANELS
|
||||
|
||||
// Change this to your needs, for details on VirtualPanel pls read the PDF!
|
||||
#define SERPENT true
|
||||
#define TOPDOWN false
|
||||
|
||||
// placeholder for the matrix object
|
||||
MatrixPanel_I2S_DMA *dma_display = nullptr;
|
||||
|
||||
// placeholder for the virtual display object
|
||||
VirtualMatrixPanel *FourScanPanel = nullptr;
|
||||
|
||||
/******************************************************************************
|
||||
* Setup!
|
||||
******************************************************************************/
|
||||
void setup()
|
||||
{
|
||||
delay(250);
|
||||
|
||||
Serial.begin(115200);
|
||||
Serial.println(""); Serial.println(""); Serial.println("");
|
||||
Serial.println("*****************************************************");
|
||||
Serial.println("* 1/8 Scan Panel Demonstration *");
|
||||
Serial.println("*****************************************************");
|
||||
|
||||
/*
|
||||
// 62x32 1/8 Scan Panels don't have a D and E pin!
|
||||
|
||||
HUB75_I2S_CFG::i2s_pins _pins = {
|
||||
R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN,
|
||||
A_PIN, B_PIN, C_PIN, D_PIN, E_PIN,
|
||||
LAT_PIN, OE_PIN, CLK_PIN
|
||||
};
|
||||
*/
|
||||
HUB75_I2S_CFG mxconfig(
|
||||
PANEL_RES_X*2, // DO NOT CHANGE THIS
|
||||
PANEL_RES_Y/2, // DO NOT CHANGE THIS
|
||||
NUM_ROWS*NUM_COLS // DO NOT CHANGE THIS
|
||||
//,_pins // Uncomment to enable custom pins
|
||||
);
|
||||
|
||||
mxconfig.clkphase = false; // Change this if you see pixels showing up shifted wrongly by one column the left or right.
|
||||
|
||||
//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
|
||||
|
||||
// OK, now we can create our matrix object
|
||||
dma_display = new MatrixPanel_I2S_DMA(mxconfig);
|
||||
|
||||
// let's adjust default brightness to about 75%
|
||||
dma_display->setBrightness8(96); // range is 0-255, 0 - 0%, 255 - 100%
|
||||
|
||||
// Allocate memory and start DMA display
|
||||
if( not dma_display->begin() )
|
||||
Serial.println("****** !KABOOM! I2S memory allocation failed ***********");
|
||||
|
||||
|
||||
dma_display->clearScreen();
|
||||
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);
|
||||
|
||||
// THE IMPORTANT BIT BELOW!
|
||||
FourScanPanel->setPhysicalPanelScanRate(FOUR_SCAN_32PX_HIGH);
|
||||
}
|
||||
|
||||
|
||||
void loop() {
|
||||
|
||||
// What the panel sees from the DMA engine!
|
||||
for (int i=PANEL_RES_X*2+10; i< PANEL_RES_X*(NUM_ROWS*NUM_COLS)*2; i++)
|
||||
{
|
||||
dma_display->drawLine(i, 0, i, 7, dma_display->color565(255, 0, 0)); // red
|
||||
delay(10);
|
||||
}
|
||||
|
||||
dma_display->clearScreen();
|
||||
delay(1000);
|
||||
/*
|
||||
// Try again using the pixel / dma memory remapper
|
||||
for (int i=PANEL_RES_X+5; i< (PANEL_RES_X*2)-1; i++)
|
||||
{
|
||||
FourScanPanel->drawLine(i, 0, i, 7, dma_display->color565(0, 0, 255)); // blue
|
||||
delay(10);
|
||||
}
|
||||
*/
|
||||
|
||||
// Try again using the pixel / dma memory remapper
|
||||
int offset = PANEL_RES_X*((NUM_ROWS*NUM_COLS)-1);
|
||||
for (int i=0; i< PANEL_RES_X; i++)
|
||||
{
|
||||
FourScanPanel->drawLine(i+offset, 0, i+offset, 7, dma_display->color565(0, 0, 255)); // blue
|
||||
FourScanPanel->drawLine(i+offset, 8, i+offset, 15, dma_display->color565(0, 128,0)); // g
|
||||
FourScanPanel->drawLine(i+offset, 16, i+offset, 23, dma_display->color565(128, 0,0)); // red
|
||||
FourScanPanel->drawLine(i+offset, 24, i+offset, 31, dma_display->color565(0, 128, 128)); // blue
|
||||
delay(10);
|
||||
}
|
||||
|
||||
delay(1000);
|
||||
|
||||
|
||||
// Print on each chained panel 1/8 module!
|
||||
// This only really works for a single horizontal chain
|
||||
for (int i = 0; i < NUM_ROWS*NUM_COLS; i++)
|
||||
{
|
||||
FourScanPanel->setTextColor(FourScanPanel->color565(255, 255, 255));
|
||||
FourScanPanel->setCursor(i*PANEL_RES_X+7, FourScanPanel->height()/3);
|
||||
|
||||
// Red text inside red rect (2 pix in from edge)
|
||||
FourScanPanel->print("Panel " + String(i+1));
|
||||
FourScanPanel->drawRect(1,1, FourScanPanel->width()-2, FourScanPanel->height()-2, FourScanPanel->color565(255,0,0));
|
||||
|
||||
// White line from top left to bottom right
|
||||
FourScanPanel->drawLine(0,0, FourScanPanel->width()-1, FourScanPanel->height()-1, FourScanPanel->color565(255,255,255));
|
||||
}
|
||||
|
||||
delay(2000);
|
||||
dma_display->clearScreen();
|
||||
|
||||
} // end loop
|
|
@ -1,7 +0,0 @@
|
|||
# Using this library with 32x16 1/8 Scan Panels
|
||||
|
||||
## Problem
|
||||
ESP32-HUB75-MatrixPanel-I2S-DMA library will not display output correctly with 'Four Scan' or 1/8 scan panels such [as this](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/154) by default.
|
||||
|
||||
## Solution
|
||||
It is possible to connect 1/8 scan panels to this library and 'trick' the output to work correctly on these panels by way of adjusting the pixel co-ordinates that are 'sent' to the underlying ESP32-HUB75-MatrixPanel-I2S-DMA library (in this example, it is the 'dmaOutput' class).
|
|
@ -4,7 +4,7 @@ description = HUB75 ESP32 I2S DMA test patterns example
|
|||
;src_dir = src
|
||||
|
||||
[env]
|
||||
platform = espressif32
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.13/platform-espressif32.zip
|
||||
board = wemos_d1_mini32
|
||||
lib_deps =
|
||||
fastled/FastLED
|
||||
|
|
143
examples/Pixel_Mapping_Test/Pixel_Mapping_Test.ino
Normal file
143
examples/Pixel_Mapping_Test/Pixel_Mapping_Test.ino
Normal file
|
@ -0,0 +1,143 @@
|
|||
/*************************************************************************
|
||||
Description:
|
||||
|
||||
The underlying implementation of the ESP32-HUB75-MatrixPanel-I2S-DMA only
|
||||
supports output to HALF scan panels - which means outputting
|
||||
two lines at the same time, 16 or 32 rows apart if a 32px or 64px high panel
|
||||
respectively.
|
||||
This cannot be changed at the DMA layer as it would require a messy and complex
|
||||
rebuild of the library's internals.
|
||||
|
||||
However, it is possible to connect QUARTER (i.e. FOUR lines updated in parallel)
|
||||
scan panels to this same library and
|
||||
'trick' the output to work correctly on these panels by way of adjusting the
|
||||
pixel co-ordinates that are 'sent' to the ESP32-HUB75-MatrixPanel-I2S-DMA
|
||||
library.
|
||||
|
||||
**************************************************************************/
|
||||
|
||||
/* Use the Virtual Display class to re-map co-ordinates such that they draw
|
||||
correctly on a 32x16 1/4 or 64x32 1/8 Scan panel (or chain of such panels).
|
||||
*/
|
||||
#include "ESP32-VirtualMatrixPanel-I2S-DMA.h"
|
||||
|
||||
// Define custom class derived from VirtualMatrixPanel
|
||||
class CustomPxBasePanel : public VirtualMatrixPanel
|
||||
{
|
||||
public:
|
||||
using VirtualMatrixPanel::VirtualMatrixPanel; // inherit VirtualMatrixPanel's constructor(s)
|
||||
|
||||
protected:
|
||||
|
||||
VirtualCoords getCoords(int16_t x, int16_t y); // custom getCoords() method for specific pixel mapping
|
||||
|
||||
};
|
||||
|
||||
// custom getCoords() method for specific pixel mapping
|
||||
inline VirtualCoords CustomPxBasePanel ::getCoords(int16_t x, int16_t y) {
|
||||
|
||||
coords = VirtualMatrixPanel::getCoords(x, y); // call base class method to update coords for chaining approach
|
||||
|
||||
if ( coords.x == -1 || coords.y == -1 ) { // Co-ordinates go from 0 to X-1 remember! width() and height() are out of range!
|
||||
return coords;
|
||||
}
|
||||
|
||||
uint8_t pxbase = panelResX; // pixel base
|
||||
// mapper for panels with 32 pixs height (64x32 or 32x32)
|
||||
if (panelResY == 32)
|
||||
{
|
||||
if ((coords.y & 8) == 0)
|
||||
{
|
||||
coords.x += ((coords.x / pxbase) + 1) * pxbase; // 1st, 3rd 'block' of 8 rows of pixels
|
||||
}
|
||||
else
|
||||
{
|
||||
coords.x += (coords.x / pxbase) * pxbase; // 2nd, 4th 'block' of 8 rows of pixels
|
||||
}
|
||||
coords.y = (coords.y >> 4) * 8 + (coords.y & 0b00000111);
|
||||
}
|
||||
|
||||
// mapper for panels with 16 pixs height (32x16 1/4)
|
||||
else if (panelResY == 16)
|
||||
{
|
||||
if ((coords.y & 4) == 0)
|
||||
{
|
||||
// 1. Normal line, from left to right
|
||||
coords.x += ((coords.x / pxbase) + 1) * pxbase; // 1st, 3rd 'block' of 4 rows of pixels
|
||||
//2. in case the line filled from right to left, use this (and comment 1st)
|
||||
//coords.x = ((coords.x / pxbase) + 1) * 2 * pxbase - (coords.x % pxbase) - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
coords.x += (coords.x / pxbase) * pxbase; // 2nd, 4th 'block' of 4 rows of pixels
|
||||
}
|
||||
coords.y = (coords.y >> 3) * 4 + (coords.y & 0b00000011);
|
||||
}
|
||||
return coords;
|
||||
}
|
||||
|
||||
// Panel configuration
|
||||
#define PANEL_RES_X 32 // Number of pixels wide of each INDIVIDUAL panel module.
|
||||
#define PANEL_RES_Y 16 // Number of pixels tall of each INDIVIDUAL panel module.
|
||||
|
||||
// Use a single panel for tests
|
||||
#define NUM_ROWS 1 // Number of rows of chained INDIVIDUAL PANELS
|
||||
#define NUM_COLS 1 // Number of INDIVIDUAL PANELS per ROW
|
||||
|
||||
// Chain settings, do not cnahge
|
||||
#define SERPENT true
|
||||
#define TOPDOWN false
|
||||
#define VIRTUAL_MATRIX_CHAIN_TYPE CHAIN_BOTTOM_RIGHT_UP
|
||||
|
||||
// placeholder for the matrix object
|
||||
MatrixPanel_I2S_DMA *dma_display = nullptr;
|
||||
|
||||
// placeholder for the virtual display object
|
||||
CustomPxBasePanel *FourScanPanel = nullptr;
|
||||
|
||||
/******************************************************************************
|
||||
Setup!
|
||||
******************************************************************************/
|
||||
void setup()
|
||||
{
|
||||
HUB75_I2S_CFG mxconfig(
|
||||
PANEL_RES_X * 2, // DO NOT CHANGE THIS
|
||||
PANEL_RES_Y / 2, // DO NOT CHANGE THIS
|
||||
NUM_ROWS * NUM_COLS // DO NOT CHANGE THIS
|
||||
//,_pins // Uncomment to enable custom pins
|
||||
);
|
||||
|
||||
mxconfig.clkphase = false; // Change this if you see pixels showing up shifted wrongly by one column the left or right.
|
||||
|
||||
// OK, now we can create our matrix object
|
||||
dma_display = new MatrixPanel_I2S_DMA(mxconfig);
|
||||
|
||||
// let's adjust default brightness to about 75%
|
||||
dma_display->setBrightness8(40); // range is 0-255, 0 - 0%, 255 - 100%
|
||||
|
||||
// Allocate memory and start DMA display
|
||||
if ( not dma_display->begin() )
|
||||
Serial.println("****** !KABOOM! I2S memory allocation failed ***********");
|
||||
|
||||
|
||||
dma_display->clearScreen();
|
||||
delay(500);
|
||||
|
||||
// create FourScanPanellay object based on our newly created dma_display object
|
||||
FourScanPanel = new CustomPxBasePanel ((*dma_display), NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, VIRTUAL_MATRIX_CHAIN_TYPE);
|
||||
|
||||
}
|
||||
|
||||
|
||||
void loop() {
|
||||
for (int i = 0; i < FourScanPanel->height(); i++)
|
||||
{
|
||||
for (int j = 0; j < FourScanPanel->width(); j++)
|
||||
{
|
||||
FourScanPanel->drawPixel(j, i, FourScanPanel->color565(255, 0, 0));
|
||||
delay(30);
|
||||
}
|
||||
}
|
||||
delay(2000);
|
||||
dma_display->clearScreen();
|
||||
} // end loop
|
17
examples/Pixel_Mapping_Test/README.md
Normal file
17
examples/Pixel_Mapping_Test/README.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
## Four Scan Panel pixel mapping test and tune
|
||||
This example is to help you set up pixel coordinate mapping on most common cases of Four_Scan_Panels such as 64x32 1/8 or 32x16 1/4. Please follow the steps below in sequence.
|
||||
|
||||
### 1. Run this code on a single panel
|
||||
If the panel lines are filled sequentially, from left to right and from top to bottom, then you don't need to change the setting.
|
||||
|
||||
### 2. If your panel filling in parts
|
||||
Most often, the panel rows are filled in small segments, 4, 8 or 16 pixels (there are other values). This number is the pixel base of the panel. Substitute it into line 45 of the example and run the code again.
|
||||
|
||||
### 3. Wrong order of rows
|
||||
At this point, your panel should already be filled with whole rows. If the top row is not the first to be filled, swap the expressions, marked in the comments as "1st, 3rd 'block' of rows" with "2nd, 4th 'block' of rows" one.
|
||||
|
||||
### 4. Wrong filling direction
|
||||
If any block of rows is filled from right to left, change the formula according to the example shown in the lines 65-68 of the code.
|
||||
|
||||
### Conclusion
|
||||
If your panel works correctly now - congratulations. But if not - it's okay. There are many types of different panels and it is impossible to foresee all the nuances. Create an issue and you will be helped!
|
267
examples/ScrollingTextLayer/ScrollingTextLayer.ino
Normal file
267
examples/ScrollingTextLayer/ScrollingTextLayer.ino
Normal file
|
@ -0,0 +1,267 @@
|
|||
// Modified from: https://github.com/wilson3682/My-HUB75-ESP32-Practice-Files/tree/main
|
||||
// by MrCodetastic
|
||||
|
||||
/***************************************************************************************
|
||||
* This sketch does a couple of things, it uses GFX_Lite's GFX_Layer to independently
|
||||
* draw the background and the text onto separate layers. (memory used for each)
|
||||
* Then these are stacked on top of each other with some opacity, and drawn directly
|
||||
* to the dma_display via a callback (layer_draw_callback).
|
||||
*
|
||||
* These layers are offscreen pixel buffers that use memory of their own
|
||||
* (approx 3 bytes for each pixel).
|
||||
*
|
||||
* By using this approach, we don't really need to use DMA double buffering though.
|
||||
*
|
||||
***************************************************************************************/
|
||||
|
||||
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
|
||||
|
||||
// You need to have: https://github.com/mrcodetastic/GFX_Lite
|
||||
#include <GFX_Layer.hpp>
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// Configure for your panel(s) as appropriate!
|
||||
#define PANEL_WIDTH 64
|
||||
#define PANEL_HEIGHT 32 // Panel height of 64 will required PIN_E to be defined.
|
||||
#define CHAIN_LENGTH 1 // Number of chained panels, if just a single panel, obviously set to 1
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
#define RL1 18
|
||||
#define GL1 17
|
||||
#define BL1 16
|
||||
#define RL2 15
|
||||
#define GL2 7
|
||||
#define BL2 6
|
||||
#define CH_A 4
|
||||
#define CH_B 10
|
||||
#define CH_C 14
|
||||
#define CH_D 21
|
||||
#define CH_E 5 // assign to any available pin if using two panels or 64x64 panels with 1/32 scan
|
||||
#define CLK 47
|
||||
#define LAT 48
|
||||
#define OE 38
|
||||
*/
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
MatrixPanel_I2S_DMA *dma_display = nullptr;
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
//====================== Variables For scrolling Text=====================================================
|
||||
unsigned long isAnimationDue;
|
||||
int delayBetweeenAnimations = 18; // Smaller == faster
|
||||
int textXPosition = PANEL_WIDTH * CHAIN_LENGTH; // Will start off screen
|
||||
int textYPosition = PANEL_HEIGHT / 2 - 7; // center of screen - 8 (half of the text height)
|
||||
//====================== Variables For scrolling Text=====================================================
|
||||
|
||||
|
||||
// Pointers to this variable will be passed into getTextBounds,
|
||||
// they will be updated from inside the method
|
||||
int16_t xOne, yOne;
|
||||
uint16_t w, h;
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
// The layers don't draw to hardware directly, they use a callback function.
|
||||
// You could be smart here and additionally draw to the VirtualMatrixPanel_T class...
|
||||
void layer_draw_callback(int16_t x, int16_t y, uint8_t r_data, uint8_t g_data, uint8_t b_data) {
|
||||
|
||||
dma_display->drawPixelRGB888(x,y,r_data,g_data,b_data);
|
||||
|
||||
}
|
||||
|
||||
// Global GFX_Layer object
|
||||
GFX_Layer gfx_layer_bg(PANEL_WIDTH*CHAIN_LENGTH, PANEL_HEIGHT, layer_draw_callback); // background
|
||||
GFX_Layer gfx_layer_fg(PANEL_WIDTH*CHAIN_LENGTH, PANEL_HEIGHT, layer_draw_callback); // foreground
|
||||
|
||||
GFX_LayerCompositor gfx_compositor(layer_draw_callback);
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
void setup() {
|
||||
|
||||
Serial.begin(115200);
|
||||
|
||||
//Custom pin mapping for all pins
|
||||
HUB75_I2S_CFG::i2s_pins _pins={RL1, GL1, BL1, RL2, GL2, BL2, CH_A, CH_B, CH_C, CH_D, CH_E, LAT, OE, CLK};
|
||||
HUB75_I2S_CFG mxconfig(
|
||||
PANEL_WIDTH, // width
|
||||
PANEL_HEIGHT, // height
|
||||
CHAIN_LENGTH // chain length
|
||||
// ,_pins // pin mapping
|
||||
// ,HUB75_I2S_CFG::FM6126A // driver chip
|
||||
);
|
||||
|
||||
mxconfig.i2sspeed = HUB75_I2S_CFG::HZ_20M;
|
||||
|
||||
// If you are using a 64x64 matrix you need to pass a value for the E pin
|
||||
// The trinity connects GPIO 18 to E.
|
||||
// This can be commented out for any smaller displays (but should work fine with it)
|
||||
//mxconfig.gpio.e = -1;
|
||||
|
||||
// May or may not be needed depending on your matrix
|
||||
// Example of what needing it looks like:
|
||||
// https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/134#issuecomment-866367216
|
||||
//mxconfig.clkphase = false;
|
||||
|
||||
// Some matrix panels use different ICs for driving them and some of them have strange quirks.
|
||||
// If the display is not working right, try this.
|
||||
//mxconfig.driver = HUB75_I2S_CFG::FM6126A;
|
||||
|
||||
dma_display = new MatrixPanel_I2S_DMA(mxconfig);
|
||||
dma_display->begin();
|
||||
dma_display->setBrightness8(120); //0-255
|
||||
dma_display->clearScreen();
|
||||
|
||||
// Clear the layers
|
||||
gfx_layer_fg.clear();
|
||||
gfx_layer_fg.setTextWrap(false);
|
||||
|
||||
gfx_layer_bg.clear();
|
||||
|
||||
}
|
||||
|
||||
void printTextRainbowCentered(int colorWheelOffset, const char *text, int yPos) {
|
||||
gfx_layer_fg.setTextSize(1); // size 1 == 8 pixels high
|
||||
|
||||
// Calculate the width of the text in pixels
|
||||
int textWidth = strlen(text) * 6; // Assuming 6 pixels per character for size 1
|
||||
|
||||
// Center the text horizontally
|
||||
int xPos = (gfx_layer_fg.width() - textWidth) / 2;
|
||||
|
||||
gfx_layer_fg.setCursor(xPos, yPos); // Set cursor position for centered text
|
||||
|
||||
// Draw text with a rotating color
|
||||
for (uint8_t w = 0; w < strlen(text); w++) {
|
||||
gfx_layer_fg.setTextColor(colorWheel((w * 32) + colorWheelOffset));
|
||||
gfx_layer_fg.print(text[w]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Code taken from: https://github.com/witnessmenow/ESP32-Trinity/blob/master/examples/BuildingBlocks/Text/ScrollingText/ScrollingText.ino
|
||||
//
|
||||
void scrollText(int colorWheelOffset, const char *text) {
|
||||
|
||||
const char *str = text;
|
||||
byte offSet = 25;
|
||||
unsigned long now = millis();
|
||||
if (now > isAnimationDue)
|
||||
{
|
||||
|
||||
gfx_layer_fg.setTextSize(2); // size 2 == 16 pixels high
|
||||
|
||||
isAnimationDue = now + delayBetweeenAnimations;
|
||||
textXPosition -= 1;
|
||||
|
||||
// Checking is the very right of the text off screen to the left
|
||||
gfx_layer_fg.getTextBounds(str, textXPosition, textYPosition, &xOne, &yOne, &w, &h);
|
||||
if (textXPosition + w <= 0) {
|
||||
textXPosition = gfx_layer_fg.width() + offSet;
|
||||
}
|
||||
|
||||
gfx_layer_fg.setCursor(textXPosition, textYPosition);
|
||||
|
||||
// Clear the area of text to be drawn to
|
||||
gfx_layer_fg.drawRect(1, textYPosition, gfx_layer_fg.width() - 1, 14, gfx_layer_fg.color565(0, 0, 0));
|
||||
gfx_layer_fg.fillRect(1, textYPosition, gfx_layer_fg.width() - 1, 14, gfx_layer_fg.color565(0, 0, 0));
|
||||
|
||||
uint8_t w = 0;
|
||||
for (w = 0; w < strlen(str); w++) {
|
||||
//gfx_layer_fg.setTextColor(colorWheel((w * 32) + colorWheelOffset));
|
||||
gfx_layer_fg.setTextColor(gfx_layer_fg.color565(255, 255, 255));
|
||||
gfx_layer_fg.print(str[w]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void drawTextCentered(int colorWheelOffset, const char *text, int yPos) {
|
||||
|
||||
// draw text with a rotating colour
|
||||
gfx_layer_fg.setTextSize(1); // size 1 == 8 pixels high
|
||||
// Calculate the width of the text in pixels
|
||||
int textWidth = strlen(text) * 6; // Assuming 6 pixels per character for size 1
|
||||
|
||||
// Center the text horizontally
|
||||
int xPos = (gfx_layer_fg.width() - textWidth) / 2;
|
||||
|
||||
|
||||
gfx_layer_fg.setCursor(xPos, yPos); // start at top left, with 8 pixel of spacing
|
||||
uint8_t w = 0;
|
||||
//const char *str = "ESP32 DMA";
|
||||
const char *str = text;
|
||||
for (w = 0; w < strlen(str); w++) {
|
||||
gfx_layer_fg.setTextColor(colorWheel((w * 32) + colorWheelOffset));
|
||||
gfx_layer_fg.print(str[w]);
|
||||
}
|
||||
}
|
||||
|
||||
// Input a value 0 to 255 to get a color value.
|
||||
// The colours are a transition r - g - b - back to r.
|
||||
// From: https://gist.github.com/davidegironi/3144efdc6d67e5df55438cc3cba613c8
|
||||
uint16_t colorWheel(uint8_t pos) {
|
||||
if (pos < 85) {
|
||||
return dma_display->color565(pos * 3, 255 - pos * 3, 0);
|
||||
} else if (pos < 170) {
|
||||
pos -= 85;
|
||||
return dma_display->color565(255 - pos * 3, 0, pos * 3);
|
||||
} else {
|
||||
pos -= 170;
|
||||
return dma_display->color565(0, pos * 3, 255 - pos * 3);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long last_increment = 0;
|
||||
unsigned long increment_amt = 0;
|
||||
void updateBackground()
|
||||
{
|
||||
|
||||
CRGBPalette16 currentPalette = CloudColors_p;
|
||||
|
||||
if ( (millis() - last_increment) > 20) {
|
||||
increment_amt++;
|
||||
last_increment = millis();
|
||||
}
|
||||
|
||||
for (int x = 0; x < gfx_layer_bg.width(); x++) {
|
||||
for (int y = 0; y < gfx_layer_bg.height(); y++) {
|
||||
|
||||
int val = sin8(x + increment_amt);
|
||||
val += sin8(y + increment_amt);
|
||||
|
||||
CRGB currentColor = ColorFromPalette(currentPalette, val); //, brightness, currentBlendType);
|
||||
|
||||
// Set a pixel in the back layer
|
||||
gfx_layer_bg.setPixel(x, y, currentColor.r, currentColor.g, currentColor.b);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
gfx_layer_bg.dim(192); // darken it a little
|
||||
}
|
||||
|
||||
uint8_t wheelval = 0;
|
||||
void loop() {
|
||||
|
||||
updateBackground();
|
||||
|
||||
scrollText(wheelval, "HAVE A WONDERFUL DAY!"); //Prints Scrolling text with a rainbow color
|
||||
printTextRainbowCentered(wheelval, "ENJOY IT", 24); //Prints text X-Centered to chosen Y position with rainbow color
|
||||
|
||||
//gfx_layer_fg.display();
|
||||
//gfx_layer_bg.display();
|
||||
|
||||
gfx_compositor.Blend(gfx_layer_bg, gfx_layer_fg); // blend and immediately display
|
||||
|
||||
wheelval += 1;
|
||||
|
||||
}
|
|
@ -1,4 +1,9 @@
|
|||
## Chained Panels example - Chaining individual LED matrix panels to make a larger panel ##
|
||||
# The 'VirtualMatrixPanel_T' class
|
||||
The `VirtualMatrixPanel_T` is used to perform pixel re-mapping in order to support the following use-cases that can be used together:
|
||||
1. To create a larger display based on a chain of individual physical panels connected electrically in a Serpentine or Zig-Zag manner.
|
||||
2. To provide support for physical panels with non-standard (i.e. Not a 1/2 scan panel) pixel mapping approaches. This is often seen with 1/4 scan outdoor panels.
|
||||
|
||||
## 1. Chaining individual LED matrix panels to make a larger virtual display ##
|
||||
|
||||
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.
|
||||
|
||||
|
@ -19,33 +24,34 @@ For example: You bought four (4) 64x32px panels, and wanted to use them to creat
|
|||
|
||||
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:
|
||||
2. Read the `VirtualMatrixPanel.ino` code
|
||||
|
||||
## 2. Using this library with 1/4 Scan Panels (Four Scan)
|
||||
|
||||
This library does not natively support 'Four Scan' 64x32 1/8 or 32x16 1/4 scan panels such [as this](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/154) by default.
|
||||
|
||||
### Solution
|
||||
Read the `VirtualMatrixPanel.ino` code.
|
||||
|
||||
The VirtualMatrixPanel_T class provides a way to additionally remap pixel for each individual panel by way of the `ScanTypeMapping` class.
|
||||
|
||||
You can create your own custom per-panel pixel mapping class as well should you wish.
|
||||
|
||||
```cpp
|
||||
// --- Example 3: Single non-standard 1/4 Scan (Four-Scan 1/8) ---
|
||||
|
||||
// Use an existing library user-contributed Scan Type pixel mapping
|
||||
using MyScanTypeMapping = ScanTypeMapping<FOUR_SCAN_32PX_HIGH>;
|
||||
|
||||
// Create a pointer to the specific instantiation of the VirtualMatrixPanel_T class
|
||||
VirtualMatrixPanel_T<CHAIN_NONE, MyScanTypeMapping>* virtualDisp = nullptr;
|
||||
```
|
||||
#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
|
||||
The library has these user-contributed additions, but given the variety of panels on the market, your success with any of these may vary.
|
||||
|
||||
#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:
|
||||

|
||||
|
||||
|
||||
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).
|
||||
|
||||
|
||||
#### Thanks to ####
|
||||
* Brian Lough for the Virtual to Real pixel co-ordinate code.
|
||||
|
||||
YouTube: https://www.youtube.com/brianlough
|
||||
|
||||
Tindie: https://www.tindie.com/stores/brianlough/
|
||||
|
||||
Twitter: https://twitter.com/witnessmenow
|
||||
|
||||
* Galaxy-Man for the donation of hardware for testing.
|
||||
```cpp
|
||||
FOUR_SCAN_32PX_HIGH, ///< Four-scan mode, 32-pixel high panels.
|
||||
FOUR_SCAN_16PX_HIGH, ///< Four-scan mode, 16-pixel high panels.
|
||||
FOUR_SCAN_64PX_HIGH, ///< Four-scan mode, 64-pixel high panels.
|
||||
FOUR_SCAN_40PX_HIGH ///< Four-scan mode, 40-pixel high panels.
|
||||
```
|
|
@ -1,169 +1,299 @@
|
|||
/******************************************************************************
|
||||
-------------------------------------------------------------------------
|
||||
Steps to create a virtual display made up of a chain of panels in a grid
|
||||
-------------------------------------------------------------------------
|
||||
/**
|
||||
* @file VirtualMatrixPanel.ino
|
||||
* @brief Example of using the VirtualMatrixPanel_T template class.
|
||||
*
|
||||
* The VirtualMatrixPanel_T class can be used for two purposes:
|
||||
*
|
||||
* 1) Create a much larger display out of a number of physical LED panels
|
||||
* chained in a Serpentine or Zig-Zag manner;
|
||||
*
|
||||
* 2) Provide a way to deal with weird individual physical panels that do not have a
|
||||
* simple linear X, Y pixel mapping. For example, 1/4 scan panels, or outdoor panels.
|
||||
*
|
||||
* 1) and 2) can be combined and utilsied together.
|
||||
*
|
||||
* There are FOUR examples contained within this library. What example gets built depends
|
||||
* on the value of the "#define EXAMPLE_NUMBER X" value. Where X = Example number.
|
||||
*
|
||||
* Example 1: STANDARD 1/2 Scan (i.e. 1/16, 1/32) LED matrix panels, 64x32 pixels each,
|
||||
* in a grid of 2x2 panels, chained in a Serpentine manner.
|
||||
*
|
||||
* Example 2: Non-Standard 1/4 Scan (i.e. Four-Scan 1/8) outdoor LED matrix panels, 64x32 pixels each,
|
||||
* in a grid of 2x2 panels, chained in a Serpentine manner.
|
||||
*
|
||||
* Example 3: A single non-standard 1/4 Scan (i.e. Four-Scan 1/8) outdoor LED matrix panel, 64x32 pixels.
|
||||
*
|
||||
* Example 4: Having your own panel pixel mapping logic of use only to a specific panel that isn't supported.
|
||||
* In this case we re-use this to map an individual pixel in a weird way.
|
||||
*/
|
||||
|
||||
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.
|
||||
|
||||
- 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()
|
||||
|
||||
*****************************************************************************/
|
||||
// 1) Include key virtual display library
|
||||
#include <ESP32-VirtualMatrixPanel-I2S-DMA.h>
|
||||
|
||||
// 2) Set configuration
|
||||
#define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module.
|
||||
#define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module.
|
||||
|
||||
#define NUM_ROWS 2 // Number of rows of chained INDIVIDUAL PANELS
|
||||
#define NUM_COLS 2 // Number of INDIVIDUAL PANELS per ROW
|
||||
#define PANEL_CHAIN NUM_ROWS*NUM_COLS // total number of panels chained one to another
|
||||
|
||||
/* Configure the serpetine chaining approach. Options are:
|
||||
CHAIN_TOP_LEFT_DOWN
|
||||
CHAIN_TOP_RIGHT_DOWN
|
||||
CHAIN_BOTTOM_LEFT_UP
|
||||
CHAIN_BOTTOM_RIGHT_UP
|
||||
|
||||
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;
|
||||
|
||||
// placeholder for the virtual display object
|
||||
VirtualMatrixPanel *virtualDisp = nullptr;
|
||||
#include <Arduino.h>
|
||||
#include <ESP32-HUB75-VirtualMatrixPanel_T.hpp>
|
||||
|
||||
// Select example to compile!
|
||||
#define EXAMPLE_NUMBER 1
|
||||
//#define EXAMPLE_NUMBER 2
|
||||
//#define EXAMPLE_NUMBER 3
|
||||
//#define EXAMPLE_NUMBER 4 // Custom Special Effects example!
|
||||
|
||||
/**
|
||||
* Configuration of the LED matrix panels number and individual pixel resolution.
|
||||
**/
|
||||
#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 VDISP_NUM_ROWS 2 // Number of rows of individual LED panels
|
||||
#define VDISP_NUM_COLS 2 // Number of individual LED panels per row
|
||||
|
||||
#define PANEL_CHAIN_LEN (VDISP_NUM_ROWS*VDISP_NUM_COLS) // Don't change
|
||||
|
||||
|
||||
/**
|
||||
* Configuration of the approach used to chain all the individual panels together.
|
||||
* Refer to the documentation or check the enum 'PANEL_CHAIN_TYPE' in VirtualMatrixPanel_T.hpp for options.
|
||||
**/
|
||||
#define PANEL_CHAIN_TYPE CHAIN_TOP_RIGHT_DOWN
|
||||
|
||||
/**
|
||||
* Optional config for the per-panel pixel mapping, for non-standard panels.
|
||||
* i.e. 1/4 scan panels, or outdoor panels. They're a pain in the a-- and all
|
||||
* have their own weird pixel mapping that is not linear.
|
||||
*
|
||||
* This is used for Examples 2 and 3.
|
||||
*
|
||||
**/
|
||||
#define PANEL_SCAN_TYPE FOUR_SCAN_32PX_HIGH
|
||||
|
||||
/**
|
||||
* Mandatory declaration of the dma_display. DO NOT CHANGE
|
||||
**/
|
||||
MatrixPanel_I2S_DMA *dma_display = nullptr;
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* Setup!
|
||||
******************************************************************************/
|
||||
void setup() {
|
||||
// ------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
/**
|
||||
* Template instantiation for the VirtualMatrixPanel_T class, depending on use-case.
|
||||
**/
|
||||
#if EXAMPLE_NUMBER == 1
|
||||
|
||||
// --- Example 1: STANDARD 1/2 Scan ---
|
||||
|
||||
delay(2000);
|
||||
Serial.begin(115200);
|
||||
Serial.println(""); Serial.println(""); Serial.println("");
|
||||
Serial.println("*****************************************************");
|
||||
Serial.println(" HELLO !");
|
||||
Serial.println("*****************************************************");
|
||||
// Declare a pointer to the specific instantiation:
|
||||
VirtualMatrixPanel_T<PANEL_CHAIN_TYPE>* virtualDisp = nullptr;
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#if EXAMPLE_NUMBER == 2
|
||||
|
||||
// --- Example 2: Non-Standard 1/4 Scan (Four-Scan 1/8) ---
|
||||
|
||||
// Use an existing library user-contributed Scan Type pixel mapping
|
||||
using MyScanTypeMapping = ScanTypeMapping<PANEL_SCAN_TYPE>;
|
||||
|
||||
// Create a pointer to the specific instantiation of the VirtualMatrixPanel_T class
|
||||
VirtualMatrixPanel_T<PANEL_CHAIN_TYPE, MyScanTypeMapping>* virtualDisp = nullptr;
|
||||
|
||||
#endif
|
||||
|
||||
#if EXAMPLE_NUMBER == 3
|
||||
|
||||
/******************************************************************************
|
||||
* Create physical DMA panel class AND virtual (chained) display class.
|
||||
******************************************************************************/
|
||||
// --- Example 3: Single non-standard 1/4 Scan (Four-Scan 1/8) ---
|
||||
|
||||
// Use an existing library user-contributed Scan Type pixel mapping
|
||||
using MyScanTypeMapping = ScanTypeMapping<PANEL_SCAN_TYPE>;
|
||||
|
||||
// Create a pointer to the specific instantiation of the VirtualMatrixPanel_T class
|
||||
VirtualMatrixPanel_T<CHAIN_NONE, MyScanTypeMapping>* virtualDisp = nullptr;
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
// Bonus non-existnat example. Create your own per-panel custom pixel mapping!
|
||||
#if EXAMPLE_NUMBER == 4
|
||||
|
||||
// --- Custom Scan–Type Pixel Mapping ---
|
||||
// This is not what you would use this for, but in any case it
|
||||
// makes a flipped mirror image
|
||||
struct CustomMirrorScanTypeMapping {
|
||||
|
||||
/*
|
||||
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
|
||||
static VirtualCoords apply(VirtualCoords coords, int vy, int pb) {
|
||||
|
||||
Please refer to the '2_PatternPlasma.ino' example for detailed example of how to use the MatrixPanel_I2S_DMA configuration
|
||||
*/
|
||||
// coords are the input coords for adjusting
|
||||
|
||||
HUB75_I2S_CFG mxconfig(
|
||||
PANEL_RES_X, // module width
|
||||
PANEL_RES_Y, // module height
|
||||
PANEL_CHAIN // chain length
|
||||
);
|
||||
int width = PANEL_RES_X;
|
||||
int height = PANEL_RES_Y;
|
||||
|
||||
//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
|
||||
// Flip / Mirror x
|
||||
coords.x = PANEL_RES_X - coords.x - 1;
|
||||
// coords.y = PANEL_RES_Y - coords.y - 1;
|
||||
|
||||
return coords;
|
||||
|
||||
// Sanity checks
|
||||
if (NUM_ROWS <= 1) {
|
||||
Serial.println(F("There is no reason to use the VirtualDisplay class for a single horizontal chain and row!"));
|
||||
}
|
||||
}
|
||||
|
||||
// OK, now we can create our matrix object
|
||||
dma_display = new MatrixPanel_I2S_DMA(mxconfig);
|
||||
};
|
||||
|
||||
// let's adjust default brightness to about 75%
|
||||
dma_display->setBrightness8(192); // range is 0-255, 0 - 0%, 255 - 100%
|
||||
// Create a pointer to the specific instantiation of the VirtualMatrixPanel_T class
|
||||
VirtualMatrixPanel_T<CHAIN_NONE, CustomMirrorScanTypeMapping>* virtualDisp = nullptr;
|
||||
|
||||
#endif
|
||||
|
||||
// ------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// Allocate memory and start DMA display
|
||||
if( not dma_display->begin() )
|
||||
Serial.println("****** !KABOOM! I2S memory allocation failed ***********");
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
delay(2000);
|
||||
|
||||
// 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, 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
|
||||
#define RL1 18
|
||||
#define GL1 17
|
||||
#define BL1 16
|
||||
#define RL2 15
|
||||
#define GL2 7
|
||||
#define BL2 6
|
||||
#define CH_A 4
|
||||
#define CH_B 10
|
||||
#define CH_C 14
|
||||
#define CH_D 21
|
||||
#define CH_E 5 // assign to any available pin if using two panels or 64x64 panels with 1/32 scan
|
||||
#define CLK 47
|
||||
#define LAT 48
|
||||
#define OE 38
|
||||
|
||||
// delay(1000);
|
||||
// HUB75_I2S_CFG::i2s_pins _pins={RL1, GL1, BL1, RL2, GL2, BL2, CH_A, CH_B, CH_C, CH_D, CH_E, LAT, OE, CLK};
|
||||
|
||||
Serial.println("Chain of 4x 64x32 panels for this example:");
|
||||
Serial.println("+---------+---------+");
|
||||
Serial.println("| 4 | 3 |");
|
||||
Serial.println("| | |");
|
||||
Serial.println("+---------+---------+");
|
||||
Serial.println("| 1 | 2 |");
|
||||
Serial.println("| (ESP32) | |");
|
||||
Serial.println("+---------+---------+");
|
||||
*/
|
||||
|
||||
// draw blue text
|
||||
virtualDisp->setFont(&FreeSansBold12pt7b);
|
||||
virtualDisp->setTextColor(virtualDisp->color565(0, 0, 255));
|
||||
virtualDisp->setTextSize(3);
|
||||
virtualDisp->setCursor(0, virtualDisp->height()- ((virtualDisp->height()-45)/2));
|
||||
virtualDisp->print("ABCD");
|
||||
#if EXAMPLE_NUMBER == 1
|
||||
// A grid of normal (i.e. supported out of the box) 1/16, 1/32 (two scan) panels
|
||||
|
||||
// Red text inside red rect (2 pix in from edge)
|
||||
virtualDisp->drawRect(1,1, virtualDisp->width()-2, virtualDisp->height()-2, virtualDisp->color565(255,0,0));
|
||||
// Standard panel type natively supported by this library (Example 1, 4)
|
||||
HUB75_I2S_CFG mxconfig(
|
||||
PANEL_RES_X,
|
||||
PANEL_RES_Y,
|
||||
PANEL_CHAIN_LEN
|
||||
//, _pins
|
||||
);
|
||||
|
||||
// White line from top left to bottom right
|
||||
virtualDisp->drawLine(0,0, virtualDisp->width()-1, virtualDisp->height()-1, virtualDisp->color565(255,255,255));
|
||||
#endif
|
||||
|
||||
#if EXAMPLE_NUMBER == 2
|
||||
// A grid of 1/4 scan panels. This panel type is not supported 'out of the box' and require specific
|
||||
// ScanTypeMapping (pixel mapping) within the panel itself.
|
||||
|
||||
virtualDisp->drawDisplayTest(); // re draw text numbering on each screen to check connectivity
|
||||
/**
|
||||
* HACK ALERT!
|
||||
* For 1/4 scan panels (namely outdoor panels), electrically the pixels are connected in a chain that is
|
||||
* twice the physical panel's pixel width, and half the pixel height. As such, we need to configure
|
||||
* the underlying DMA library to match the same. Then we use the VirtualMatrixPanel_T class to map the
|
||||
* physical pixels to the virtual pixels.
|
||||
*/
|
||||
HUB75_I2S_CFG mxconfig(
|
||||
PANEL_RES_X*2, // DO NOT CHANGE THIS
|
||||
PANEL_RES_Y/2, // DO NOT CHANGE THIS
|
||||
PANEL_CHAIN_LEN
|
||||
//, _pins
|
||||
);
|
||||
|
||||
#endif
|
||||
|
||||
#if EXAMPLE_NUMBER == 3
|
||||
// A single 1/4 scan panel. This panel type is not supported 'out of the box' and require specific
|
||||
// ScanTypeMapping (pixel mapping) within the panel itself.
|
||||
|
||||
/**
|
||||
* HACK ALERT!
|
||||
* For 1/4 scan panels (namely outdoor panels), electrically the pixels are connected in a chain that is
|
||||
* twice the physical panel's pixel width, and half the pixel height. As such, we need to configure
|
||||
* the underlying DMA library to match the same. Then we use the VirtualMatrixPanel_T class to map the
|
||||
* physical pixels to the virtual pixels.
|
||||
*/
|
||||
HUB75_I2S_CFG mxconfig(
|
||||
PANEL_RES_X*2, // DO NOT CHANGE THIS
|
||||
PANEL_RES_Y/2, // DO NOT CHANGE THIS
|
||||
1 // A Single panel
|
||||
//, _pins
|
||||
);
|
||||
#endif
|
||||
|
||||
#if EXAMPLE_NUMBER == 4
|
||||
// A single normal scan panel, but we're using a custom CustomScanTypeMapping in the 'wrong'
|
||||
// way to demonstrate how it can be used to create custom physical LED Matrix panel mapping.
|
||||
|
||||
HUB75_I2S_CFG::i2s_pins _pins={RL1, GL1, BL1, RL2, GL2, BL2, CH_A, CH_B, CH_C, CH_D, CH_E, LAT, OE, CLK};
|
||||
// Standard panel type natively supported by this library (Example 1, 4)
|
||||
HUB75_I2S_CFG mxconfig(
|
||||
PANEL_RES_X,
|
||||
PANEL_RES_Y,
|
||||
1
|
||||
// , _pins
|
||||
);
|
||||
|
||||
#endif
|
||||
|
||||
mxconfig.i2sspeed = HUB75_I2S_CFG::HZ_10M;
|
||||
mxconfig.clkphase = false;
|
||||
//mxconfig.driver = HUB75_I2S_CFG::FM6126A;
|
||||
|
||||
|
||||
}
|
||||
|
||||
void loop() {
|
||||
/**
|
||||
* Setup physical DMA LED display output.
|
||||
*/
|
||||
dma_display = new MatrixPanel_I2S_DMA(mxconfig);
|
||||
dma_display->begin();
|
||||
dma_display->setBrightness8(128); //0-255
|
||||
dma_display->clearScreen();
|
||||
|
||||
/**
|
||||
* Setup the VirtualMatrixPanel_T class to map the virtual pixels to the physical pixels.
|
||||
*/
|
||||
|
||||
#if EXAMPLE_NUMBER == 1
|
||||
virtualDisp = new VirtualMatrixPanel_T<PANEL_CHAIN_TYPE>(VDISP_NUM_ROWS, VDISP_NUM_COLS, PANEL_RES_X, PANEL_RES_Y);
|
||||
#elif EXAMPLE_NUMBER == 2
|
||||
virtualDisp = new VirtualMatrixPanel_T<PANEL_CHAIN_TYPE, MyScanTypeMapping>(VDISP_NUM_ROWS, VDISP_NUM_COLS, PANEL_RES_X, PANEL_RES_Y);
|
||||
#elif EXAMPLE_NUMBER == 3
|
||||
virtualDisp = new VirtualMatrixPanel_T<CHAIN_NONE, MyScanTypeMapping>(1, 1, PANEL_RES_X, PANEL_RES_Y); // Single 1/4 scan panel
|
||||
#elif EXAMPLE_NUMBER == 4
|
||||
virtualDisp = new VirtualMatrixPanel_T<CHAIN_NONE, CustomMirrorScanTypeMapping>(1, 1, PANEL_RES_X, PANEL_RES_Y); // Single 1/4 scan panel
|
||||
#endif
|
||||
|
||||
// Pass a reference to the DMA display to the VirtualMatrixPanel_T class
|
||||
virtualDisp->setDisplay(*dma_display);
|
||||
|
||||
for (int y = 0; y < virtualDisp->height(); y++) {
|
||||
for (int x = 0; x < virtualDisp->width(); x++) {
|
||||
|
||||
uint16_t color = virtualDisp->color565(96, 0, 0); // red
|
||||
|
||||
if (x == 0) color = virtualDisp->color565(0, 255, 0); // g
|
||||
if (x == (virtualDisp->width()-1)) color = virtualDisp->color565(0, 0, 255); // b
|
||||
|
||||
virtualDisp->drawPixel(x, y, color);
|
||||
delay(1);
|
||||
}
|
||||
}
|
||||
|
||||
} // end loop
|
||||
virtualDisp->drawLine(virtualDisp->width() - 1, virtualDisp->height() - 1, 0, 0, virtualDisp->color565(255, 255, 255));
|
||||
|
||||
virtualDisp->print("Virtual Matrix Panel");
|
||||
|
||||
delay(3000);
|
||||
virtualDisp->clearScreen();
|
||||
virtualDisp->drawDisplayTest(); // re draw text numbering on each screen to check connectivity
|
||||
|
||||
/*****************************************************************************
|
||||
}
|
||||
|
||||
Thanks to:
|
||||
// ------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
void loop() {
|
||||
|
||||
// Do nothing here.
|
||||
|
||||
* 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
|
||||
|
||||
*****************************************************************************/
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
// #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])
|
||||
#define getRowDataPtr(row, _dpth) &(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
|
||||
|
@ -32,46 +32,53 @@
|
|||
*/
|
||||
#define PIXEL_COLOR_MASK_BIT(color_depth_index, mask_offset) (1 << (color_depth_index + mask_offset))
|
||||
|
||||
bool MatrixPanel_I2S_DMA::allocateDMAmemory()
|
||||
bool MatrixPanel_I2S_DMA::setupDMA(const HUB75_I2S_CFG &_cfg)
|
||||
{
|
||||
|
||||
|
||||
/***
|
||||
* Step 0: Allocate basic DMA framebuffer memory for the data we send out in parallel to the HUB75 panel.
|
||||
* Colour depth is the only consideration.
|
||||
*
|
||||
*/
|
||||
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)
|
||||
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;
|
||||
|
||||
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);
|
||||
auto ptr = std::make_shared<rowBitStruct>(PIXELS_PER_ROW, m_cfg.getPixelColorDepthBits());
|
||||
|
||||
if (ptr->data == nullptr)
|
||||
{
|
||||
ESP_LOGE("I2S-DMA", "CRITICAL ERROR: Not enough memory for requested colour depth! Please reduce pixel_color_depth_bits value.\r\n");
|
||||
ESP_LOGE("I2S-DMA", "Could not allocate rowBitStruct %d!.\r\n", malloc_num);
|
||||
if (ptr->data == nullptr) {
|
||||
|
||||
ESP_LOGE("I2S-DMA", "CRITICAL ERROR: Not enough memory for requested colour depth of %d bits! Please reduce pixel_color_depth_bits value.\r\n", m_cfg.getPixelColorDepthBits());
|
||||
|
||||
return false;
|
||||
// TODO: should we release all previous rowBitStructs here???
|
||||
}
|
||||
|
||||
allocated_fb_memory += ptr->getColorDepthSize(); // byte required to display all colour depths for the rows shown at the same time
|
||||
allocated_fb_memory += ptr->getColorDepthSize(false); // byte required to display all colour depths for the two parallel rows
|
||||
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
|
||||
|
||||
/***
|
||||
* Step 1: Check what the minimum refresh rate is, and calculate the lsbMsbTransitionBit
|
||||
* which is the bit at which we can reduce the colour depth to achieve the minimum refresh rate.
|
||||
*
|
||||
* This also determines the number of DMA descriptors required per row.
|
||||
*/
|
||||
|
||||
//#define FORCE_COLOR_DEPTH 1
|
||||
|
||||
#if !defined(FORCE_COLOR_DEPTH)
|
||||
|
||||
|
@ -80,14 +87,16 @@ bool MatrixPanel_I2S_DMA::allocateDMAmemory()
|
|||
while (1)
|
||||
{
|
||||
int psPerClock = 1000000000000UL / m_cfg.i2sspeed;
|
||||
int nsPerLatch = ((PIXELS_PER_ROW + CLKS_DURING_LATCH) * psPerClock) / 1000;
|
||||
int nsPerLatch = ((PIXELS_PER_ROW + CLKS_DURING_LATCH) * psPerClock) / 1000; // time per row
|
||||
|
||||
// add time to shift out LSBs + LSB-MSB transition bit - this ignores fractions...
|
||||
int nsPerRow = m_cfg.getPixelColorDepthBits() * nsPerLatch;
|
||||
|
||||
// add time to shift out MSBs
|
||||
for (int i = lsbMsbTransitionBit + 1; i < m_cfg.getPixelColorDepthBits(); i++)
|
||||
nsPerRow += (1 << (i - lsbMsbTransitionBit - 1)) * (m_cfg.getPixelColorDepthBits() - i) * nsPerLatch;
|
||||
// Now add the time for the remaining bit depths
|
||||
for (int i = lsbMsbTransitionBit + 1; i < m_cfg.getPixelColorDepthBits(); i++) {
|
||||
//nsPerRow += (1 << (i - lsbMsbTransitionBit - 1)) * (m_cfg.getPixelColorDepthBits() - i) * nsPerLatch;
|
||||
nsPerRow += (1 << (i - lsbMsbTransitionBit - 1)) * nsPerLatch;
|
||||
}
|
||||
|
||||
int nsPerFrame = nsPerRow * ROWS_PER_FRAME;
|
||||
int actualRefreshRate = 1000000000UL / (nsPerFrame);
|
||||
|
@ -109,159 +118,157 @@ bool MatrixPanel_I2S_DMA::allocateDMAmemory()
|
|||
ESP_LOGW("I2S-DMA", "lsbMsbTransitionBit of %d used to achieve refresh rate of %d Hz. Percieved colour depth to the eye may be reduced.", lsbMsbTransitionBit, m_cfg.min_refresh_rate);
|
||||
}
|
||||
|
||||
ESP_LOGI("I2S-DMA", "DMA has pixel_color_depth_bits of %d", m_cfg.getPixelColorDepthBits() - lsbMsbTransitionBit);
|
||||
ESP_LOGI("I2S-DMA", "DMA frame buffer color depths: %d", m_cfg.getPixelColorDepthBits());
|
||||
|
||||
#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.
|
||||
* Step 2: Calculate the DMA descriptors required, which is used for memory allocation of the DMA linked list memory structure.
|
||||
* This determines the number of passes required to shift out the colour bits in the framebuffer.
|
||||
* We need to also take into consderation where a chain of panels (pixels) is so long, it requires more than one DMA payload,
|
||||
* give this library's DMA output memory allocation approach is by the row.
|
||||
*/
|
||||
int numDMAdescriptorsPerRow = 1;
|
||||
for (int i = lsbMsbTransitionBit + 1; i < m_cfg.getPixelColorDepthBits(); i++)
|
||||
{
|
||||
numDMAdescriptorsPerRow += (1 << (i - lsbMsbTransitionBit - 1));
|
||||
|
||||
int dma_descs_per_row_1cdepth = (frame_buffer[0].rowBits[0]->getColorDepthSize(true) + DMA_MAX - 1 ) / DMA_MAX;
|
||||
size_t last_dma_desc_bytes_1cdepth = (frame_buffer[0].rowBits[0]->getColorDepthSize(true) % DMA_MAX);
|
||||
|
||||
int dma_descs_per_row_all_cdepths = (frame_buffer[0].rowBits[0]->getColorDepthSize(false) + DMA_MAX - 1 ) / DMA_MAX;
|
||||
size_t last_dma_desc_bytes_all_cdepths = (frame_buffer[0].rowBits[0]->getColorDepthSize(false) % DMA_MAX);
|
||||
|
||||
// Logging the calculated values
|
||||
ESP_LOGV("I2S-DMA", "dma_descs_per_row_1cdepth: %d", dma_descs_per_row_1cdepth);
|
||||
ESP_LOGV("I2S-DMA", "last_dma_desc_bytes_1cdepth: %zu", last_dma_desc_bytes_1cdepth);
|
||||
ESP_LOGV("I2S-DMA", "dma_descs_per_row_all_cdepths: %d", dma_descs_per_row_all_cdepths);
|
||||
ESP_LOGV("I2S-DMA", "last_dma_desc_bytes_all_cdepths: %zu", last_dma_desc_bytes_all_cdepths);
|
||||
|
||||
|
||||
// Calculate per-row number
|
||||
int dma_descriptors_per_row = dma_descs_per_row_all_cdepths;
|
||||
|
||||
// Add descriptors for MSB bits after transition
|
||||
for (int i = lsbMsbTransitionBit + 1; i < m_cfg.getPixelColorDepthBits(); i++) {
|
||||
dma_descriptors_per_row += (1 << (i - lsbMsbTransitionBit - 1)) * dma_descs_per_row_1cdepth;
|
||||
}
|
||||
|
||||
//dma_descriptors_per_row = 1;
|
||||
|
||||
ESP_LOGI("I2S-DMA", "Recalculated number of DMA descriptors per row: %d", numDMAdescriptorsPerRow);
|
||||
|
||||
// Refer to 'DMA_LL_PAYLOAD_SPLIT' code in configureDMA() below to understand why this exists.
|
||||
// numDMAdescriptorsPerRow is also used to calculate descount which is super important in i2s_parallel_config_t SoC DMA setup.
|
||||
if (frame_buffer[0].rowBits[0]->getColorDepthSize() > DMA_MAX)
|
||||
{
|
||||
|
||||
ESP_LOGW("I2S-DMA", "rowBits struct is too large to fit in one DMA transfer payload, splitting required. Adding %d DMA descriptors\n", m_cfg.getPixelColorDepthBits() - 1);
|
||||
|
||||
numDMAdescriptorsPerRow += m_cfg.getPixelColorDepthBits() - 1;
|
||||
// Note: If numDMAdescriptorsPerRow is even just one descriptor too large, DMA linked list will not correctly loop.
|
||||
}
|
||||
// Allocate DMA descriptors
|
||||
int dma_descriptions_required = dma_descriptors_per_row * ROWS_PER_FRAME;
|
||||
|
||||
ESP_LOGV("I2S-DMA", "DMA descriptors per row: %d", dma_descriptors_per_row);
|
||||
ESP_LOGV("I2S-DMA", "DMA descriptors required per buffer: %d", dma_descriptions_required);
|
||||
|
||||
/***
|
||||
* Step 3: Allocate memory for DMA linked list, linking up each framebuffer row in sequence for GPIO output.
|
||||
* Step 3: Allocate the DMA descriptor memory via. the relevant platform DMA implementation class.
|
||||
*/
|
||||
|
||||
// malloc the DMA linked list descriptors that i2s_parallel will need
|
||||
int desccount = numDMAdescriptorsPerRow * ROWS_PER_FRAME;
|
||||
|
||||
if (m_cfg.double_buff)
|
||||
{
|
||||
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;
|
||||
|
||||
return true;
|
||||
|
||||
} // end 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)
|
||||
{
|
||||
|
||||
// lldesc_t *previous_dmadesc_a = 0;
|
||||
// lldesc_t *previous_dmadesc_b = 0;
|
||||
int current_dmadescriptor_offset = 0;
|
||||
|
||||
// HACK: If we need to split the payload in 1/2 so that it doesn't breach DMA_MAX, lets do it by the colour_depth.
|
||||
/*
|
||||
int num_dma_payload_colour_depths = m_cfg.getPixelColorDepthBits();
|
||||
if (frame_buffer[0].rowBits[0]->getColorDepthSize() > DMA_MAX)
|
||||
if ( !dma_bus.allocate_dma_desc_memory(dma_descriptions_required) )
|
||||
{
|
||||
num_dma_payload_colour_depths = 1;
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
// Fill DMA linked lists for both frames (as in, halves of the HUB75 panel) in sequence (top to bottom)
|
||||
for (int row = 0; row < ROWS_PER_FRAME; row++)
|
||||
{
|
||||
// first set of data is LSB through MSB, single pass (IF TOTAL SIZE < DMA_MAX) - all colour bits are displayed once, which takes care of everything below and including LSBMSB_TRANSITION_BIT
|
||||
// NOTE: size must be less than DMA_MAX - worst case for library: 16-bpp with 256 pixels per row would exceed this, need to break into two
|
||||
// link_dma_desc(&dmadesc_a[current_dmadescriptor_offset], previous_dmadesc_a, dma_buff.rowBits[row]->getDataPtr(), dma_buff.rowBits[row]->size(num_dma_payload_colour_depths));
|
||||
// previous_dmadesc_a = &dmadesc_a[current_dmadescriptor_offset];
|
||||
/***
|
||||
* Step 4: Link up the DMA descriptors per the colour depth and rows.
|
||||
*/
|
||||
|
||||
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)
|
||||
//fbs_required = 1; // (m_cfg.double_buff) ? 2 : 1;
|
||||
for (int fb = 0; fb < (fbs_required); fb++)
|
||||
{
|
||||
|
||||
int _dmadescriptor_count = 0; // for tracking
|
||||
|
||||
for (int row = 0; row < ROWS_PER_FRAME; row++)
|
||||
{
|
||||
dma_bus.create_dma_desc_link(frame_buffer[1].rowBits[row]->getDataPtr(0, 1), frame_buffer[1].rowBits[row]->getColorDepthSize(), true);
|
||||
}
|
||||
//ESP_LOGV("I2S-DMA", ">>> Linking DMA descriptors for output row %d", row);
|
||||
|
||||
// Link and send all colour data, all passes of everything in one hit. 1 bit colour at least...
|
||||
for (int dma_desc_all = 0; dma_desc_all < dma_descs_per_row_all_cdepths; dma_desc_all++)
|
||||
{
|
||||
size_t payload_bytes = (dma_desc_all == (dma_descs_per_row_all_cdepths-1)) ? last_dma_desc_bytes_all_cdepths:DMA_MAX;
|
||||
|
||||
// Log the current descriptor number and the payload size being used.
|
||||
//ESP_LOGV("I2S-DMA", "Processing dma_desc_all: %d, payload_bytes: %zu, memory location: %p", dma_desc_all, payload_bytes, (frame_buffer[fb].rowBits[row]->getDataPtr(0)+(dma_desc_all*(DMA_MAX/sizeof(ESP32_I2S_DMA_STORAGE_TYPE)))));
|
||||
|
||||
dma_bus.create_dma_desc_link(frame_buffer[fb].rowBits[row]->getDataPtr(0)+(dma_desc_all*(DMA_MAX/sizeof(ESP32_I2S_DMA_STORAGE_TYPE))), payload_bytes, (fb==1));
|
||||
_dmadescriptor_count++;
|
||||
|
||||
// Log the updated descriptor count after each operation.
|
||||
//ESP_LOGV("I2S-DMA", "Updated _dmadescriptor_count: %d", _dmadescriptor_count);
|
||||
}
|
||||
|
||||
// Step 2: Handle additional descriptors for bits beyond the lsbMsbTransitionBit
|
||||
for (int i = lsbMsbTransitionBit + 1; i < m_cfg.getPixelColorDepthBits(); i++)
|
||||
{
|
||||
// binary time division setup: we need 2 of bit (LSBMSB_TRANSITION_BIT + 1) four of (LSBMSB_TRANSITION_BIT + 2), etc
|
||||
// because we sweep through to MSB each time, it divides the number of times we have to sweep in half (saving linked list RAM)
|
||||
// we need 2^(i - LSBMSB_TRANSITION_BIT - 1) == 1 << (i - LSBMSB_TRANSITION_BIT - 1) passes from i to MSB
|
||||
|
||||
current_dmadescriptor_offset++;
|
||||
for (int k = 0; k < (1 << (i - lsbMsbTransitionBit - 1)); k++)
|
||||
{
|
||||
// Link and send all colour data, all passes of everything in one hit.
|
||||
for (int dma_desc_1cdepth = 0; dma_desc_1cdepth < dma_descs_per_row_1cdepth; dma_desc_1cdepth++)
|
||||
{
|
||||
size_t payload_bytes = (dma_desc_1cdepth == (dma_descs_per_row_1cdepth-1)) ? last_dma_desc_bytes_1cdepth:DMA_MAX;
|
||||
|
||||
// Log the current bit and the corresponding payload size.
|
||||
//ESP_LOGV("I2S-DMA", "Processing dma_desc_1cdepth: %d, payload_bytes: %zu, memory location: %p", dma_desc_1cdepth, payload_bytes, (frame_buffer[fb].rowBits[row]->getDataPtr(i)+(dma_desc_1cdepth*(DMA_MAX/sizeof(ESP32_I2S_DMA_STORAGE_TYPE)))));
|
||||
|
||||
dma_bus.create_dma_desc_link(frame_buffer[fb].rowBits[row]->getDataPtr(i)+(dma_desc_1cdepth*(DMA_MAX/sizeof(ESP32_I2S_DMA_STORAGE_TYPE))), payload_bytes, (fb==1));
|
||||
_dmadescriptor_count++;
|
||||
|
||||
// Log the updated descriptor count after each operation.
|
||||
// ESP_LOGV("I2S-DMA", "Updated _dmadescriptor_count: %d", _dmadescriptor_count);
|
||||
}
|
||||
} // end K
|
||||
|
||||
} // end all other colour depth bits
|
||||
|
||||
|
||||
// If the number of pixels per row is too great for the size of a DMA payload, so we need to split what we were going to send above.
|
||||
if (frame_buffer[0].rowBits[0]->getColorDepthSize() > DMA_MAX)
|
||||
{
|
||||
} // end all rows
|
||||
|
||||
ESP_LOGI("I2S-DMA", "Created %d DMA descriptors for buffer %d.", _dmadescriptor_count, fb);
|
||||
|
||||
} // end framebuffer loop
|
||||
|
||||
/*
|
||||
#include <iostream>
|
||||
#include <cmath> // For pow function (if needed, though bit shifts are better)
|
||||
|
||||
for (int cd = 1; cd < m_cfg.getPixelColorDepthBits(); cd++)
|
||||
{
|
||||
dma_bus.create_dma_desc_link(frame_buffer[0].rowBits[row]->getDataPtr(cd, 0), frame_buffer[0].rowBits[row]->getColorDepthSize(1), false);
|
||||
int main() {
|
||||
|
||||
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);
|
||||
}
|
||||
int colorDepthBits = 8;
|
||||
int lsbMsbTransitionBit = 0; // Assuming lsbMsbTransitionBit is 0
|
||||
|
||||
current_dmadescriptor_offset++;
|
||||
// Step 2: Handle additional descriptors for bits beyond the lsbMsbTransitionBit
|
||||
for (int i = lsbMsbTransitionBit + 1; i < colorDepthBits; i++) {
|
||||
// Calculate the number of passes required
|
||||
int passes = 1 << (i - lsbMsbTransitionBit - 1);
|
||||
|
||||
} // additional linked list items
|
||||
} // row depth struct
|
||||
std::cout << "Bit Level: " << i << ", Passes Required: " << passes << std::endl;
|
||||
|
||||
for (int i = lsbMsbTransitionBit + 1; i < m_cfg.getPixelColorDepthBits(); i++)
|
||||
{
|
||||
// binary time division setup: we need 2 of bit (LSBMSB_TRANSITION_BIT + 1) four of (LSBMSB_TRANSITION_BIT + 2), etc
|
||||
// because we sweep through to MSB each time, it divides the number of times we have to sweep in half (saving linked list RAM)
|
||||
// we need 2^(i - LSBMSB_TRANSITION_BIT - 1) == 1 << (i - LSBMSB_TRANSITION_BIT - 1) passes from i to MSB
|
||||
// Simulate the inner loop for the number of passes
|
||||
for (int k = 0; k < passes; k++) {
|
||||
std::cout << " Pass " << k + 1 << " for bit " << i << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
for (int k = 0; k < (1 << (i - lsbMsbTransitionBit - 1)); k++)
|
||||
{
|
||||
dma_bus.create_dma_desc_link(frame_buffer[0].rowBits[row]->getDataPtr(i, 0), frame_buffer[0].rowBits[row]->getColorDepthSize(1), false);
|
||||
return 0;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
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++;
|
||||
/***
|
||||
* Step 5: Set default framebuffer to fb[0]
|
||||
*/
|
||||
|
||||
} // end colour depth ^ 2 linked list
|
||||
} // end colour depth loop
|
||||
|
||||
} // end frame rows
|
||||
|
||||
ESP_LOGI("I2S-DMA", "%d DMA descriptors linked to buffer data.", current_dmadescriptor_offset);
|
||||
fb = &frame_buffer[0];
|
||||
|
||||
|
||||
//
|
||||
// Setup DMA and Output to GPIO
|
||||
|
@ -295,16 +302,14 @@ void MatrixPanel_I2S_DMA::configureDMA(const HUB75_I2S_CFG &_cfg)
|
|||
|
||||
dma_bus.config(bus_cfg);
|
||||
|
||||
dma_bus.init();
|
||||
|
||||
dma_bus.dma_transfer_start();
|
||||
|
||||
flipDMABuffer(); // display back buffer 0, draw to 1, ignored if double buffering isn't enabled.
|
||||
|
||||
// i2s_parallel_send_dma(ESP32_I2S_DEVICE, &dmadesc_a[0]);
|
||||
ESP_LOGI("I2S-DMA", "DMA setup completed");
|
||||
|
||||
initialized = true;
|
||||
|
||||
} // end initMatrixDMABuff
|
||||
return true;
|
||||
|
||||
} // end setupDMA
|
||||
|
||||
/* 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
|
||||
|
@ -344,14 +349,14 @@ void IRAM_ATTR MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint16_t x_coord, uint
|
|||
* https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/
|
||||
*/
|
||||
uint16_t red16, green16, blue16;
|
||||
#ifndef NO_CIE1931
|
||||
#ifdef NO_CIE1931
|
||||
red16 = red;
|
||||
green16 = green;
|
||||
blue16 = blue;
|
||||
#else
|
||||
red16 = lumConvTab[red];
|
||||
green16 = lumConvTab[green];
|
||||
blue16 = lumConvTab[blue];
|
||||
#else
|
||||
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,
|
||||
|
@ -382,7 +387,11 @@ void IRAM_ATTR MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint16_t x_coord, uint
|
|||
{
|
||||
--colour_depth_idx;
|
||||
|
||||
#ifdef NO_CIE1931
|
||||
uint16_t mask = colour_depth_idx;
|
||||
#else
|
||||
uint16_t mask = PIXEL_COLOR_MASK_BIT(colour_depth_idx, MASK_OFFSET);
|
||||
#endif
|
||||
uint16_t RGB_output_bits = 0;
|
||||
|
||||
/* Per the .h file, the order of the output RGB bits is:
|
||||
|
@ -396,7 +405,7 @@ void IRAM_ATTR MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint16_t x_coord, uint
|
|||
|
||||
// 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);
|
||||
ESP32_I2S_DMA_STORAGE_TYPE *p = getRowDataPtr(y_coord, colour_depth_idx);
|
||||
|
||||
// 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
|
||||
|
@ -409,6 +418,7 @@ void IRAM_ATTR MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint16_t x_coord, uint
|
|||
} 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)
|
||||
{
|
||||
|
@ -417,14 +427,14 @@ void MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint8_t red, uint8_t green, uint
|
|||
|
||||
/* https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/ */
|
||||
uint16_t red16, green16, blue16;
|
||||
#ifndef NO_CIE1931
|
||||
red16 = lumConvTab[red];
|
||||
green16 = lumConvTab[green];
|
||||
blue16 = lumConvTab[blue];
|
||||
#else
|
||||
red16 = red << 8;
|
||||
green16 = green << 8;
|
||||
blue16 = blue << 8;
|
||||
#ifdef NO_CIE1931
|
||||
red16 = red;
|
||||
green16 = green;
|
||||
blue16 = blue;
|
||||
#else
|
||||
red16 = lumConvTab[red];
|
||||
green16 = lumConvTab[green];
|
||||
blue16 = lumConvTab[blue];
|
||||
#endif
|
||||
|
||||
for (uint8_t colour_depth_idx = 0; colour_depth_idx < m_cfg.getPixelColorDepthBits(); colour_depth_idx++) // colour depth - 8 iterations
|
||||
|
@ -432,7 +442,11 @@ void MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint8_t red, uint8_t green, uint
|
|||
// let's precalculate RGB1 and RGB2 bits than flood it over the entire DMA buffer
|
||||
uint16_t RGB_output_bits = 0;
|
||||
|
||||
#ifdef NO_CIE1931
|
||||
uint16_t mask = colour_depth_idx;
|
||||
#else
|
||||
uint16_t mask = PIXEL_COLOR_MASK_BIT(colour_depth_idx, MASK_OFFSET);
|
||||
#endif
|
||||
|
||||
/* Per the .h file, the order of the output RGB bits is:
|
||||
* BIT_B2, BIT_G2, BIT_R2, BIT_B1, BIT_G1, BIT_R1 */
|
||||
|
@ -454,7 +468,7 @@ void MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint8_t red, uint8_t green, uint
|
|||
--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);
|
||||
ESP32_I2S_DMA_STORAGE_TYPE *p = getRowDataPtr(matrix_frame_parallel_row, colour_depth_idx);
|
||||
|
||||
// iterate pixels in a row
|
||||
int x_coord = fb->rowBits[matrix_frame_parallel_row]->width;
|
||||
|
@ -495,41 +509,43 @@ void MatrixPanel_I2S_DMA::clearFrameBuffer(bool _buff_id)
|
|||
{
|
||||
--row_idx;
|
||||
|
||||
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 *row = fb->rowBits[row_idx]->getDataPtr(0); // 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 = 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
|
||||
{
|
||||
--x_pixel;
|
||||
abcde <<= BITS_ADDR_OFFSET; // shift row y-coord to match ABCDE bits in vector from 8 to 12
|
||||
do
|
||||
{
|
||||
--x_pixel;
|
||||
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 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;
|
||||
}
|
||||
|
||||
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 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;
|
||||
}
|
||||
} while (x_pixel != fb->rowBits[row_idx]->width); // spare the first "width's" worth of pixels as they are the LSB pixels/colordepth
|
||||
|
||||
} while (x_pixel != fb->rowBits[row_idx]->width && x_pixel);
|
||||
// The colour_index[0] (LSB) x_pixels must be "marked" with a previous's row address, because it is used to display
|
||||
// previous row while we pump in MSBs's for the next row.
|
||||
if (row_idx == 0) {
|
||||
abcde = ROWS_PER_FRAME-1; // wrap around
|
||||
} else {
|
||||
abcde = row_idx-1;
|
||||
}
|
||||
|
||||
// colour_index[0] (LSB) x_pixels must be "marked" with a previous's row address, 'cause it is used to display
|
||||
// previous row while we pump in LSB's for a new row
|
||||
abcde = ((ESP32_I2S_DMA_STORAGE_TYPE)row_idx - 1) << BITS_ADDR_OFFSET;
|
||||
do
|
||||
abcde <<= BITS_ADDR_OFFSET; // shift row y-coord to match ABCDE bits in vector from 8 to 12
|
||||
do
|
||||
{
|
||||
--x_pixel;
|
||||
|
||||
|
@ -584,7 +600,7 @@ void MatrixPanel_I2S_DMA::clearFrameBuffer(bool _buff_id)
|
|||
--colouridx;
|
||||
|
||||
// switch pointer to a row for a specific colour index
|
||||
row = fb->rowBits[row_idx]->getDataPtr(colouridx, -1);
|
||||
row = fb->rowBits[row_idx]->getDataPtr(colouridx);
|
||||
|
||||
// DP3246 needs the latch high for 3 clock cycles, so start 2 cycles earlier
|
||||
if (m_cfg.driver == HUB75_I2S_CFG::DP3246_SM5368)
|
||||
|
@ -624,7 +640,60 @@ void MatrixPanel_I2S_DMA::clearFrameBuffer(bool _buff_id)
|
|||
* @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::setBrightnessOE(uint8_t brightness, const int buffer_id) {
|
||||
|
||||
if (!initialized)
|
||||
return;
|
||||
|
||||
frameStruct *frameBuffer = &frame_buffer[buffer_id];
|
||||
|
||||
uint8_t blanking = m_cfg.latch_blanking; // Prevent overwriting during latch blanking
|
||||
uint8_t colorDepth = frameBuffer->rowBits[0]->colour_depth;
|
||||
uint16_t width = frameBuffer->rowBits[0]->width;
|
||||
|
||||
// Iterate through each row in the DMA buffer
|
||||
for (int rowIdx = frameBuffer->rowBits.size() - 1; rowIdx >= 0; --rowIdx) {
|
||||
|
||||
// Process each bitplane (color depth level)
|
||||
for (int depthIdx = colorDepth - 1; depthIdx >= 0; --depthIdx) {
|
||||
int bitPlane = (2 * colorDepth - depthIdx) % colorDepth;
|
||||
int bitShift = (colorDepth - lsbMsbTransitionBit - 1) >> 1;
|
||||
int rightShift = std::max(bitPlane - bitShift - 2, 0);
|
||||
|
||||
// Calculate the brightness range for OE control
|
||||
int activePixelRange = ((width - blanking) * brightness) >> (7 + rightShift);
|
||||
activePixelRange = (activePixelRange >> 1) | (activePixelRange & 1);
|
||||
|
||||
int xCoordMin = (width - activePixelRange) >> 1;
|
||||
int xCoordMax = (width + activePixelRange + 1) >> 1;
|
||||
|
||||
// Get pointer to the specific row and bitplane
|
||||
ESP32_I2S_DMA_STORAGE_TYPE *rowData = frameBuffer->rowBits[rowIdx]->getDataPtr(depthIdx);
|
||||
|
||||
// Set OE bits based on brightness thresholds
|
||||
for (int x = width - 1; x >= 0; --x) {
|
||||
if (x >= xCoordMin && x < xCoordMax) {
|
||||
rowData[ESP32_TX_FIFO_POSITION_ADJUST(x)] &= BITMASK_OE_CLEAR; // Enable output
|
||||
} else {
|
||||
rowData[ESP32_TX_FIFO_POSITION_ADJUST(x)] |= BIT_OE; // Disable output
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// switch pointer to a row for a specific colour index
|
||||
#if defined(SPIRAM_DMA_BUFFER)
|
||||
ESP32_I2S_DMA_STORAGE_TYPE *row_hack = fb->rowBits[rowIdx]->getDataPtr(0);
|
||||
//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[rowIdx]->getColorDepthSize());
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
void MatrixPanel_I2S_DMA::setBrightnessOE(uint8_t brt, const int _buff_id)
|
||||
{
|
||||
|
||||
if (!initialized)
|
||||
|
@ -657,7 +726,7 @@ 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 = fb->rowBits[row_idx]->getDataPtr(colouridx, _buff_id);
|
||||
ESP32_I2S_DMA_STORAGE_TYPE *row = fb->rowBits[row_idx]->getDataPtr(colouridx);
|
||||
|
||||
// define range of Output Enable on the center of the row
|
||||
int x_coord_max = (_width + brightness_in_x_pixels + 1) >> 1;
|
||||
|
@ -690,6 +759,7 @@ void MatrixPanel_I2S_DMA::brtCtrlOEv2(uint8_t brt, const int _buff_id)
|
|||
} while (row_idx);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* overload for compatibility
|
||||
*/
|
||||
|
@ -785,9 +855,9 @@ void MatrixPanel_I2S_DMA::hlineDMA(int16_t x_coord, int16_t y_coord, int16_t l,
|
|||
green16 = lumConvTab[green];
|
||||
blue16 = lumConvTab[blue];
|
||||
#else
|
||||
red16 = red << 8;
|
||||
green16 = green << 8;
|
||||
blue16 = blue << 8;
|
||||
red16 = red << MASK_OFFSET;
|
||||
green16 = green << MASK_OFFSET;
|
||||
blue16 = blue << MASK_OFFSET;
|
||||
#endif
|
||||
|
||||
uint16_t _colourbitclear = BITMASK_RGB1_CLEAR, _colourbitoffset = 0;
|
||||
|
@ -826,7 +896,7 @@ void MatrixPanel_I2S_DMA::hlineDMA(int16_t x_coord, int16_t y_coord, int16_t l,
|
|||
|
||||
// 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 = fb->rowBits[y_coord]->getDataPtr(colour_depth_idx, back_buffer_id);
|
||||
ESP32_I2S_DMA_STORAGE_TYPE *p = fb->rowBits[y_coord]->getDataPtr(colour_depth_idx);
|
||||
// inlined version works slower here, dunno why :(
|
||||
// ESP32_I2S_DMA_STORAGE_TYPE *p = getRowDataPtr(y_coord, colour_depth_idx, back_buffer_id);
|
||||
|
||||
|
@ -882,9 +952,9 @@ void MatrixPanel_I2S_DMA::vlineDMA(int16_t x_coord, int16_t y_coord, int16_t l,
|
|||
green16 = lumConvTab[green];
|
||||
blue16 = lumConvTab[blue];
|
||||
#else
|
||||
red16 = red << 8;
|
||||
green16 = green << 8;
|
||||
blue16 = blue << 8;
|
||||
red16 = red << MASK_OFFSET;
|
||||
green16 = green << MASK_OFFSET;
|
||||
blue16 = blue << MASK_OFFSET;
|
||||
#endif
|
||||
|
||||
/*
|
||||
|
@ -934,7 +1004,7 @@ void MatrixPanel_I2S_DMA::vlineDMA(int16_t x_coord, int16_t y_coord, int16_t l,
|
|||
// 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 = fb->rowBits[_y]->getDataPtr(colour_depth_idx, back_buffer_id);
|
||||
ESP32_I2S_DMA_STORAGE_TYPE *p = fb->rowBits[_y]->getDataPtr(colour_depth_idx);
|
||||
|
||||
p[x_coord] &= _colourbitclear; // reset RGB bits
|
||||
p[x_coord] |= RGB_output_bits; // set new RGB bits
|
||||
|
|
|
@ -130,39 +130,56 @@
|
|||
* 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
|
||||
* @brief Structure to hold row data for HUB75 matrix panel DMA operations
|
||||
*
|
||||
* @var width
|
||||
* Width of the row in pixels
|
||||
*
|
||||
* @var colour_depth
|
||||
* Number of color depths (i.e. copies of each row for each colur bitmask)
|
||||
*
|
||||
* @var data
|
||||
* Pointer to DMA storage type array holding pixel data for the row
|
||||
*
|
||||
* @note Memory allocation differs based on target platform and configuration:
|
||||
* - For ESP32-S3 with SPIRAM: Allocates aligned memory in SPIRAM
|
||||
* - For other configurations: Allocates DMA-capable internal memory
|
||||
*/
|
||||
{
|
||||
const size_t width;
|
||||
const uint8_t colour_depth;
|
||||
const bool double_buff;
|
||||
//const bool double_buff;
|
||||
ESP32_I2S_DMA_STORAGE_TYPE *data;
|
||||
|
||||
/** @brief
|
||||
* Returns size (in bytes) of row of data vectorfor a SINGLE buff for the number of colour depths requested
|
||||
/** @brief Returns size (in bytes) of a colour depth row data array
|
||||
*
|
||||
* @param single_color_depth
|
||||
* - if true, returns size for a single color depth layer
|
||||
* - if false, returns total size for all color depth layers for a row.
|
||||
*
|
||||
* @returns size_t - Size in bytes required for DMA buffer allocation
|
||||
*
|
||||
* default - Returns full data vector size for a SINGLE buff.
|
||||
* You should only pass either PIXEL_COLOR_DEPTH_BITS or '1' to this
|
||||
*
|
||||
*/
|
||||
size_t getColorDepthSize(uint8_t _dpth = 0)
|
||||
size_t getColorDepthSize(bool single_color_depth)
|
||||
{
|
||||
if (!_dpth)
|
||||
_dpth = colour_depth;
|
||||
return width * _dpth * sizeof(ESP32_I2S_DMA_STORAGE_TYPE);
|
||||
int _cdepth = (single_color_depth) ? 1:colour_depth;
|
||||
return width * _cdepth * sizeof(ESP32_I2S_DMA_STORAGE_TYPE);
|
||||
};
|
||||
|
||||
|
||||
/** @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)]); };
|
||||
|
||||
// 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]); };
|
||||
inline ESP32_I2S_DMA_STORAGE_TYPE *getDataPtr(const uint8_t _dpth = 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)
|
||||
rowBitStruct(const size_t _width, const uint8_t _depth) : width(_width), colour_depth(_depth)
|
||||
{
|
||||
|
||||
// #if defined(SPIRAM_FRAMEBUFFER) && defined (CONFIG_IDF_TARGET_ESP32S3)
|
||||
|
@ -170,12 +187,12 @@ struct rowBitStruct
|
|||
|
||||
// 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);
|
||||
data = (ESP32_I2S_DMA_STORAGE_TYPE *)heap_caps_aligned_alloc(64, getColorDepthSize(false), 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);
|
||||
data = (ESP32_I2S_DMA_STORAGE_TYPE *)heap_caps_malloc(getColorDepthSize(false), MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
@ -196,33 +213,27 @@ struct frameStruct
|
|||
};
|
||||
|
||||
/***************************************************************************************/
|
||||
// C/p'ed from https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/
|
||||
// Example calculator: https://gist.github.com/mathiasvr/19ce1d7b6caeab230934080ae1f1380e
|
||||
// need to make sure this would end up in RAM for fastest access
|
||||
#ifndef NO_CIE1931
|
||||
/*
|
||||
static const uint8_t DRAM_ATTR lumConvTab[]={
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 16, 16, 17, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 27, 27, 28, 28, 29, 30, 30, 31, 31, 32, 33, 33, 34, 35, 35, 36, 37, 38, 38, 39, 40, 41, 41, 42, 43, 44, 45, 45, 46, 47, 48, 49, 50, 51, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 80, 81, 82, 83, 84, 86, 87, 88, 90, 91, 92, 93, 95, 96, 98, 99, 100, 102, 103, 105, 106, 107, 109, 110, 112, 113, 115, 116, 118, 120, 121, 123, 124, 126, 128, 129, 131, 133, 134, 136, 138, 139, 141, 143, 145, 146, 148, 150, 152, 154, 156, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 192, 194, 196, 198, 200, 203, 205, 207, 209, 212, 214, 216, 218, 221, 223, 226, 228, 230, 233, 235, 238, 240, 243, 245, 248, 250, 253, 255, 255};
|
||||
*/
|
||||
// This is 16-bit version of the table,
|
||||
// the constants taken from the example in the article above, each entries subtracted from 65535:
|
||||
static const uint16_t DRAM_ATTR lumConvTab[] = {
|
||||
0, 27, 56, 84, 113, 141, 170, 198, 227, 255, 284, 312, 340, 369, 397, 426,
|
||||
454, 483, 511, 540, 568, 597, 626, 657, 688, 720, 754, 788, 824, 860, 898, 936,
|
||||
976, 1017, 1059, 1102, 1146, 1191, 1238, 1286, 1335, 1385, 1436, 1489, 1543, 1598, 1655, 1713,
|
||||
1772, 1833, 1895, 1958, 2023, 2089, 2156, 2225, 2296, 2368, 2441, 2516, 2592, 2670, 2750, 2831,
|
||||
2914, 2998, 3084, 3171, 3260, 3351, 3443, 3537, 3633, 3731, 3830, 3931, 4034, 4138, 4245, 4353,
|
||||
4463, 4574, 4688, 4803, 4921, 5040, 5161, 5284, 5409, 5536, 5665, 5796, 5929, 6064, 6201, 6340,
|
||||
6482, 6625, 6770, 6917, 7067, 7219, 7372, 7528, 7687, 7847, 8010, 8174, 8341, 8511, 8682, 8856,
|
||||
9032, 9211, 9392, 9575, 9761, 9949, 10139, 10332, 10527, 10725, 10925, 11127, 11332, 11540, 11750, 11963,
|
||||
12178, 12395, 12616, 12839, 13064, 13292, 13523, 13757, 13993, 14231, 14473, 14717, 14964, 15214, 15466, 15722,
|
||||
15980, 16240, 16504, 16771, 17040, 17312, 17587, 17865, 18146, 18430, 18717, 19006, 19299, 19595, 19894, 20195,
|
||||
20500, 20808, 21119, 21433, 21750, 22070, 22393, 22720, 23049, 23382, 23718, 24057, 24400, 24745, 25094, 25446,
|
||||
25802, 26160, 26522, 26888, 27256, 27628, 28004, 28382, 28765, 29150, 29539, 29932, 30328, 30727, 31130, 31536,
|
||||
31946, 32360, 32777, 33197, 33622, 34049, 34481, 34916, 35354, 35797, 36243, 36692, 37146, 37603, 38064, 38528,
|
||||
38996, 39469, 39945, 40424, 40908, 41395, 41886, 42382, 42881, 43383, 43890, 44401, 44916, 45434, 45957, 46484,
|
||||
47014, 47549, 48088, 48630, 49177, 49728, 50283, 50842, 51406, 51973, 52545, 53120, 53700, 54284, 54873, 55465,
|
||||
56062, 56663, 57269, 57878, 58492, 59111, 59733, 60360, 60992, 61627, 62268, 62912, 63561, 64215, 64873, 65535};
|
||||
// C/p'ed from https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/
|
||||
// 16 bit resolution of https://gist.github.com/mathiasvr/19ce1d7b6caeab230934080ae1f1380e
|
||||
static const uint16_t lumConvTab[] = {
|
||||
0, 28, 57, 85, 114, 142, 171, 199, 228, 256, 285, 313, 342, 370, 399, 427,
|
||||
456, 484, 513, 541, 570, 598, 627, 658, 689, 721, 755, 789, 825, 861, 899, 937,
|
||||
977, 1018, 1060, 1103, 1147, 1192, 1239, 1287, 1336, 1386, 1437, 1490, 1544, 1599, 1656, 1714,
|
||||
1773, 1834, 1896, 1959, 2024, 2090, 2157, 2226, 2297, 2369, 2442, 2517, 2593, 2671, 2751, 2832,
|
||||
2914, 2999, 3085, 3172, 3261, 3352, 3444, 3538, 3634, 3732, 3831, 3932, 4035, 4139, 4245, 4354,
|
||||
4464, 4575, 4689, 4804, 4922, 5041, 5162, 5285, 5410, 5537, 5666, 5797, 5930, 6065, 6202, 6341,
|
||||
6482, 6626, 6771, 6918, 7068, 7220, 7373, 7529, 7687, 7848, 8010, 8175, 8342, 8512, 8683, 8857,
|
||||
9033, 9212, 9393, 9576, 9762, 9949, 10140, 10333, 10528, 10725, 10926, 11128, 11333, 11541, 11751, 11963,
|
||||
12179, 12396, 12617, 12840, 13065, 13293, 13524, 13757, 13993, 14232, 14474, 14718, 14965, 15215, 15467, 15722,
|
||||
15980, 16241, 16505, 16771, 17041, 17313, 17588, 17866, 18147, 18431, 18717, 19007, 19300, 19596, 19894, 20196,
|
||||
20501, 20809, 21119, 21433, 21750, 22071, 22394, 22720, 23050, 23383, 23719, 24058, 24400, 24746, 25095, 25447,
|
||||
25802, 26161, 26523, 26888, 27257, 27629, 28004, 28383, 28765, 29151, 29540, 29932, 30328, 30728, 31131, 31537,
|
||||
31947, 32360, 32777, 33198, 33622, 34050, 34481, 34916, 35355, 35797, 36243, 36693, 37146, 37603, 38064, 38529,
|
||||
38997, 39469, 39945, 40425, 40908, 41396, 41887, 42382, 42881, 43384, 43891, 44401, 44916, 45435, 45957, 46484,
|
||||
47015, 47549, 48088, 48631, 49178, 49728, 50283, 50843, 51406, 51973, 52545, 53120, 53700, 54284, 54873, 55465,
|
||||
56062, 56663, 57269, 57878, 58492, 59111, 59733, 60360, 60992, 61627, 62268, 62912, 63561, 64215, 64873, 65535,
|
||||
};
|
||||
#endif
|
||||
|
||||
/** @brief - configuration values for HUB75_I2S driver
|
||||
|
@ -308,6 +319,7 @@ struct HUB75_I2S_CFG
|
|||
bool clkphase;
|
||||
|
||||
// Minimum refresh / scan rate needs to be configured on start due to LSBMSB_TRANSITION_BIT calculation in allocateDMAmemory()
|
||||
// Set this to '1' to get all colour depths displayed with correct BCM time weighting.
|
||||
uint8_t min_refresh_rate;
|
||||
|
||||
// struct constructor
|
||||
|
@ -417,6 +429,7 @@ public:
|
|||
|
||||
if (initialized)
|
||||
return true; // we don't do this twice or more!
|
||||
|
||||
if (!config_set)
|
||||
return false;
|
||||
|
||||
|
@ -441,31 +454,43 @@ public:
|
|||
|
||||
#if defined(SPIRAM_DMA_BUFFER)
|
||||
// Trick library into dropping colour depth slightly when using PSRAM.
|
||||
// Actual output clockrate override occurs in configureDMA
|
||||
m_cfg.i2sspeed = HUB75_I2S_CFG::HZ_8M;
|
||||
#endif
|
||||
|
||||
ESP_LOGI("begin()", "HUB75 effective display resolution of width: %dpx height: %dpx.", m_cfg.mx_width * m_cfg.chain_length, m_cfg.mx_height);
|
||||
|
||||
if (m_cfg.mx_height % 2 != 0) {
|
||||
ESP_LOGE("begin()", "Error: m_cfg.mx_height must be an even number!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* As DMA buffers are dynamically allocated, we must allocated in begin()
|
||||
* Ref: https://github.com/espressif/arduino-esp32/issues/831
|
||||
*/
|
||||
if (!allocateDMAmemory())
|
||||
if (!setupDMA(m_cfg))
|
||||
{
|
||||
return false;
|
||||
} // couldn't even get the basic ram required.
|
||||
|
||||
// Flush the DMA buffers prior to configuring DMA - Avoid visual artefacts on boot.
|
||||
resetbuffers(); // Must fill the DMA buffer with the initial output bit sequence or the panel will display garbage
|
||||
|
||||
// Setup the ESP32 DMA Engine. Sprite_TM built this stuff.
|
||||
configureDMA(m_cfg); // DMA and I2S configuration and setup
|
||||
|
||||
// showDMABuffer(); // show backbuf_id of 0
|
||||
|
||||
|
||||
if (!initialized)
|
||||
{
|
||||
ESP_LOGE("being()", "MatrixPanel_I2S_DMA::begin() failed!");
|
||||
}
|
||||
|
||||
// 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
|
||||
ESP_LOGV("being()", "Completed resetbuffers()");
|
||||
|
||||
flipDMABuffer(); // display back buffer 0, draw to 1, ignored if double buffering isn't enabled.
|
||||
ESP_LOGV("being()", "Completed flipDMABuffer()");
|
||||
|
||||
// Start output output
|
||||
dma_bus.init();
|
||||
ESP_LOGV("being()", "Completed dma_bus.init()");
|
||||
|
||||
dma_bus.dma_transfer_start();
|
||||
ESP_LOGV("being()", "Completed dma_bus.dma_transfer_start()");
|
||||
|
||||
return initialized;
|
||||
}
|
||||
|
||||
|
@ -588,13 +613,8 @@ public:
|
|||
|
||||
// Colour 444 is a 4 bit scale, so 0 to 15, colour 565 takes a 0-255 bit value, so scale up by 255/15 (i.e. 17)!
|
||||
static uint16_t color444(uint8_t r, uint8_t g, uint8_t b) { return color565(r * 17, g * 17, b * 17); }
|
||||
|
||||
// Converts RGB888 to RGB565
|
||||
static uint16_t color565(uint8_t r, uint8_t g, uint8_t b); // This is what is used by Adafruit GFX!
|
||||
|
||||
// Converts RGB333 to RGB565
|
||||
static uint16_t color333(uint8_t r, uint8_t g, uint8_t b); // This is what is used by Adafruit GFX! Not sure why they have a capital 'C' for this particular function.
|
||||
|
||||
/**
|
||||
* @brief - convert RGB565 to RGB888
|
||||
* @param uint16_t colour - RGB565 input colour
|
||||
|
@ -631,11 +651,11 @@ public:
|
|||
}
|
||||
|
||||
brightness = b;
|
||||
brtCtrlOEv2(b, 0);
|
||||
setBrightnessOE(b, 0);
|
||||
|
||||
if (m_cfg.double_buff)
|
||||
{
|
||||
brtCtrlOEv2(b, 1);
|
||||
setBrightnessOE(b, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -722,12 +742,12 @@ protected:
|
|||
inline void resetbuffers()
|
||||
{
|
||||
clearFrameBuffer(0);
|
||||
brtCtrlOEv2(brightness, 0);
|
||||
setBrightnessOE(brightness, 0);
|
||||
|
||||
if (m_cfg.double_buff) {
|
||||
|
||||
clearFrameBuffer(1);
|
||||
brtCtrlOEv2(brightness, 1);
|
||||
setBrightnessOE(brightness, 1);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -765,11 +785,9 @@ protected:
|
|||
|
||||
// ------- PRIVATE -------
|
||||
private:
|
||||
/* Calculate the memory available for DMA use, do some other stuff, and allocate accordingly */
|
||||
bool allocateDMAmemory();
|
||||
|
||||
/* Setup the DMA Link List chain and initiate the ESP32 DMA engine */
|
||||
void configureDMA(const HUB75_I2S_CFG &opts);
|
||||
/* Setup the DMA Link List chain and configure the ESP32 DMA + I2S or LCD peripheral */
|
||||
bool setupDMA(const HUB75_I2S_CFG &opts);
|
||||
|
||||
/**
|
||||
* pre-init procedures for specific drivers
|
||||
|
@ -799,7 +817,7 @@ private:
|
|||
* @param brt - brightness level from 0 to row_width
|
||||
* @param _buff_id - buffer id to control
|
||||
*/
|
||||
void brtCtrlOEv2(uint8_t brt, const int _buff_id = 0);
|
||||
void setBrightnessOE(uint8_t brt, const int _buff_id = 0);
|
||||
|
||||
/**
|
||||
* @brief - transforms coordinates according to orientation
|
||||
|
@ -953,40 +971,6 @@ inline uint16_t MatrixPanel_I2S_DMA::color565(uint8_t r, uint8_t g, uint8_t b)
|
|||
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
|
||||
}
|
||||
|
||||
// Promote 3/3/3 RGB to Adafruit_GFX 5/6/5 RRRrrGGGgggBBBbb
|
||||
inline uint16_t MatrixPanel_I2S_DMA::color333(uint8_t r, uint8_t g, uint8_t b)
|
||||
{
|
||||
return ((r & 0x7) << 13) | ((r & 0x6) << 10) | ((g & 0x7) << 8) | ((g & 0x7) << 5) | ((b & 0x7) << 2) | ((b & 0x6) >> 1);
|
||||
}
|
||||
|
||||
inline void MatrixPanel_I2S_DMA::drawIcon(int *ico, int16_t x, int16_t y, int16_t cols, int16_t rows)
|
||||
{
|
||||
/* drawIcon draws a C style bitmap.
|
||||
// Example 10x5px bitmap of a yellow sun
|
||||
//
|
||||
int half_sun [50] = {
|
||||
0x0000, 0x0000, 0x0000, 0xffe0, 0x0000, 0x0000, 0xffe0, 0x0000, 0x0000, 0x0000,
|
||||
0x0000, 0xffe0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffe0, 0x0000,
|
||||
0x0000, 0x0000, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0x0000, 0x0000, 0x0000,
|
||||
0xffe0, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0x0000, 0xffe0,
|
||||
0x0000, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0x0000, 0x0000,
|
||||
};
|
||||
|
||||
MatrixPanel_I2S_DMA matrix;
|
||||
|
||||
matrix.drawIcon (half_sun, 0,0,10,5);
|
||||
*/
|
||||
|
||||
int i, j;
|
||||
for (i = 0; i < rows; i++)
|
||||
{
|
||||
for (j = 0; j < cols; j++)
|
||||
{
|
||||
drawPixel(x + j, y + i, (uint16_t)ico[i * cols + j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Credits: Louis Beaudoin <https://github.com/pixelmatix/SmartMatrix/tree/teensylc>
|
||||
|
|
510
src/ESP32-HUB75-VirtualMatrixPanel_T.hpp
Normal file
510
src/ESP32-HUB75-VirtualMatrixPanel_T.hpp
Normal file
|
@ -0,0 +1,510 @@
|
|||
/**
|
||||
* @file VirtualMatrixPanel_T.hpp
|
||||
* @brief TEMPLATED Virtual Matrix Panel class for HUB75 displays. Hence the '_T'.
|
||||
*
|
||||
* This header defines the VirtualMatrixPanel_T template class which maps virtual pixel
|
||||
* coordinates to physical LED coordinates. It supports compile‐time configuration for:
|
||||
* - Panel chain type (PANEL_CHAIN_TYPE)
|
||||
* - Scan type mapping (via a class, default is STANDARD_TWO_SCAN)
|
||||
* - A compile‐time scale factor (each virtual pixel is drawn as a block)
|
||||
*
|
||||
* Runtime rotation is supported via setRotation(). Depending on the build options,
|
||||
* the class conditionally inherits from Adafruit_GFX, GFX_Lite, or stands alone.
|
||||
*
|
||||
* This class is used to accomplish two objectives:
|
||||
* 1) Create a much larger display out of a number of physical LED panels
|
||||
* chained in a various pattern.
|
||||
*
|
||||
* 2) Provide a way to deal with weird individual physical panels that
|
||||
* do not have a simple linear X, Y pixel mapping.
|
||||
* i.e. Their DMA pixel mapping differs to the real world.
|
||||
* i.e. Weird four-scan outdoor panels etc.
|
||||
*
|
||||
* @tparam ChainType Compile‐time panel chain configuration.
|
||||
* @tparam ScanType Policy type implementing a static apply() function for mapping.
|
||||
* Default is ScanTypeMapping<STANDARD_TWO_SCAN>.
|
||||
* @tparam ScaleFactor Compile‐time zoom factor (each virtual pixel becomes a
|
||||
* ScaleFactor x ScaleFactor block).
|
||||
*
|
||||
* @note The enum PANEL_SCAN_TYPE replaces the former PANEL_SCAN_RATE.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#ifndef VIRTUAL_MATRIX_PANEL_TEMPLATE_H
|
||||
#define VIRTUAL_MATRIX_PANEL_TEMPLATE_H
|
||||
|
||||
//#include <cstdint>
|
||||
#include "ESP32-HUB75-MatrixPanel-I2S-DMA.h"
|
||||
|
||||
#ifdef USE_GFX_LITE
|
||||
#include "GFX_Lite.h"
|
||||
#elif !defined(NO_GFX)
|
||||
#include "Adafruit_GFX.h"
|
||||
#endif
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Data structures and enums
|
||||
|
||||
/**
|
||||
* @brief Structure holding virtual/physical coordinate mapping.
|
||||
*/
|
||||
struct VirtualCoords {
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
int16_t virt_row; // chain of panels row (optional)
|
||||
int16_t virt_col; // chain of panels col (optional)
|
||||
VirtualCoords() : x(0), y(0), virt_row(0), virt_col(0) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Panel scan types.
|
||||
*
|
||||
* Defines the different scanning modes.
|
||||
*/
|
||||
enum PANEL_SCAN_TYPE {
|
||||
STANDARD_TWO_SCAN,
|
||||
FOUR_SCAN_16PX_HIGH, ///< Four-scan mode, 16-pixel high panels.
|
||||
FOUR_SCAN_32PX_HIGH, ///< Four-scan mode, 32-pixel high panels.
|
||||
FOUR_SCAN_40PX_HIGH, ///< Four-scan mode, 40-pixel high panels.
|
||||
FOUR_SCAN_40_80PX_HFARCAN, ///< Four-scan mode, 40-pixel high, 80px wide panel. Weird mapping: https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA/issues/759
|
||||
FOUR_SCAN_64PX_HIGH, ///< Four-scan mode, 64-pixel high panels.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Panel chain types.
|
||||
*
|
||||
* Defines the physical chain configuration for multiple panels.
|
||||
*/
|
||||
enum PANEL_CHAIN_TYPE {
|
||||
CHAIN_NONE, ///< No chaining.
|
||||
CHAIN_TOP_LEFT_DOWN, ///< Chain starting top-left, going down.
|
||||
CHAIN_TOP_RIGHT_DOWN, ///< Chain starting top-right, going down.
|
||||
CHAIN_BOTTOM_LEFT_UP, ///< Chain starting bottom-left, going up.
|
||||
CHAIN_BOTTOM_RIGHT_UP, ///< Chain starting bottom-right, going up.
|
||||
CHAIN_TOP_LEFT_DOWN_ZZ, ///< Zigzag chain starting top-left.
|
||||
CHAIN_TOP_RIGHT_DOWN_ZZ, ///< Zigzag chain starting top-right.
|
||||
CHAIN_BOTTOM_RIGHT_UP_ZZ, ///< Zigzag chain starting bottom-right.
|
||||
CHAIN_BOTTOM_LEFT_UP_ZZ ///< Zigzag chain starting bottom-left.
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Default Scan Rate Policy
|
||||
/**
|
||||
* @brief Default policy for scan type mapping.
|
||||
*
|
||||
* This templated policy implements the static function apply() to remap
|
||||
* coordinates according to the panel scan type. It uses the panel's pixel base
|
||||
* to calculate offsets.
|
||||
*
|
||||
* @tparam Type The compile-time scan type (of type PANEL_SCAN_TYPE).
|
||||
*/
|
||||
template <PANEL_SCAN_TYPE ScanType>
|
||||
struct ScanTypeMapping {
|
||||
static constexpr VirtualCoords apply(VirtualCoords coords, int virt_y, int panel_pixel_base)
|
||||
{
|
||||
//log_v("ScanTypeMapping: coords.x: %d, coords.y: %d, virt_y: %d, pixel_base: %d", coords.x, coords.y, virt_y, panel_pixel_base);
|
||||
|
||||
// FOUR_SCAN_16PX_HIGH
|
||||
if constexpr (ScanType == FOUR_SCAN_16PX_HIGH)
|
||||
{
|
||||
if ((coords.y & 4) == 0) {
|
||||
coords.x += (((coords.x / panel_pixel_base) + 1) * panel_pixel_base);
|
||||
} else {
|
||||
coords.x += ((coords.x / panel_pixel_base) * panel_pixel_base);
|
||||
}
|
||||
|
||||
coords.y = (coords.y >> 3) * 4 + (coords.y & 0b00000011);
|
||||
}
|
||||
// FOUR_SCAN_40PX_HIGH
|
||||
else if constexpr (ScanType == FOUR_SCAN_40PX_HIGH)
|
||||
{
|
||||
|
||||
if (((coords.y) / 10) % 2 == 0) {
|
||||
coords.x += (((coords.x / panel_pixel_base) + 1) * panel_pixel_base);
|
||||
} else {
|
||||
coords.x += ((coords.x / panel_pixel_base) * panel_pixel_base);
|
||||
}
|
||||
coords.y = (coords.y / 20) * 10 + (coords.y % 10);
|
||||
}
|
||||
else if constexpr (ScanType == FOUR_SCAN_40_80PX_HFARCAN)
|
||||
{
|
||||
// Weird mapping: https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA/issues/759
|
||||
panel_pixel_base = 16;
|
||||
|
||||
// Mapping logic
|
||||
int panel_local_x = coords.x % 80; // Compensate for chain of panels
|
||||
|
||||
if ((((coords.y) / 10) % 2) ^ ((panel_local_x / panel_pixel_base) % 2)) {
|
||||
coords.x += ((coords.x / panel_pixel_base) * panel_pixel_base);
|
||||
} else {
|
||||
coords.x += (((coords.x / panel_pixel_base) + 1) * panel_pixel_base);
|
||||
}
|
||||
|
||||
coords.y = (coords.y % 10) + 10 * ((coords.y / 20) % 2);
|
||||
|
||||
}
|
||||
// FOUR_SCAN_64PX_HIGH || FOUR_SCAN_32PX_HIGH
|
||||
else if constexpr (ScanType == FOUR_SCAN_64PX_HIGH || ScanType == FOUR_SCAN_32PX_HIGH)
|
||||
{
|
||||
int adjusted_y = virt_y;
|
||||
if constexpr (ScanType == FOUR_SCAN_64PX_HIGH)
|
||||
{
|
||||
// As in the original code (with extra remapping for 64px high panels)
|
||||
if ((virt_y & 8) != ((virt_y & 16) >> 1))
|
||||
adjusted_y = (((virt_y & 0b11000) ^ 0b11000) + (virt_y & 0b11100111));
|
||||
}
|
||||
|
||||
if ((coords.y & 8) == 0) {
|
||||
coords.x += (((coords.x / panel_pixel_base) + 1) * panel_pixel_base);
|
||||
} else {
|
||||
coords.x += ((coords.x / panel_pixel_base) * panel_pixel_base);
|
||||
}
|
||||
|
||||
coords.y = (adjusted_y >> 4) * 8 + (adjusted_y & 0b00000111);
|
||||
}
|
||||
|
||||
// For STANDARD_TWO_SCAN / NORMAL_ONE_SIXTEEN no remapping is done.
|
||||
return coords;
|
||||
}
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// VirtualMatrixPanel_T Declaration
|
||||
//
|
||||
// Template parameters:
|
||||
// - ChainScanType: compile–time panel chain configuration.
|
||||
// - ScanTypeMapping: a policy type implementing a static "apply" function
|
||||
// (default is ScanTypeMapping<STANDARD_TWO_SCAN>).
|
||||
// - ScaleFactor: a compile–time zoom factor (must be >= 1).
|
||||
#ifdef USE_GFX_LITE
|
||||
template <PANEL_CHAIN_TYPE ChainScanType,
|
||||
class ScanTypeMapping = ScanTypeMapping<STANDARD_TWO_SCAN>,
|
||||
int ScaleFactor = 1>
|
||||
class VirtualMatrixPanel_T : public GFX {
|
||||
public:
|
||||
#elif !defined(NO_GFX)
|
||||
template <PANEL_CHAIN_TYPE ChainScanType,
|
||||
class ScanTypeMapping = ScanTypeMapping<STANDARD_TWO_SCAN>,
|
||||
int ScaleFactor = 1>
|
||||
class VirtualMatrixPanel_T : public Adafruit_GFX {
|
||||
public:
|
||||
#else
|
||||
template <PANEL_CHAIN_TYPE ChainScanType,
|
||||
class ScanTypeMapping = ScanTypeMapping<STANDARD_TWO_SCAN>,
|
||||
int ScaleFactor = 1>
|
||||
class VirtualMatrixPanel_T {
|
||||
public:
|
||||
#endif
|
||||
|
||||
// Constructor: pass the underlying MatrixPanel_I2S_DMA display,
|
||||
// virtual module dimensions, and physical panel resolution.
|
||||
// (Chain type is chosen at compile time.)
|
||||
VirtualMatrixPanel_T(uint8_t _vmodule_rows,
|
||||
uint8_t _vmodule_cols,
|
||||
uint8_t _panel_res_x,
|
||||
uint8_t _panel_res_y)
|
||||
#ifdef USE_GFX_LITE
|
||||
: GFX(_vmodule_cols * _panel_res_x, _vmodule_rows * _panel_res_y),
|
||||
#elif !defined(NO_GFX)
|
||||
: Adafruit_GFX(_vmodule_cols * _panel_res_x, _vmodule_rows * _panel_res_y),
|
||||
#endif
|
||||
panel_res_x(_panel_res_x),
|
||||
panel_res_y(_panel_res_y),
|
||||
panel_pixel_base(_panel_res_x), // default pixel base is panel_res_x
|
||||
vmodule_rows(_vmodule_rows),
|
||||
vmodule_cols(_vmodule_cols),
|
||||
virtual_res_x(_vmodule_cols * _panel_res_x),
|
||||
virtual_res_y(_vmodule_rows * _panel_res_y),
|
||||
dma_res_x(_panel_res_x * _vmodule_rows * _vmodule_cols - 1),
|
||||
_virtual_res_x(virtual_res_x),
|
||||
_virtual_res_y(virtual_res_y),
|
||||
_rotate(0)
|
||||
{
|
||||
// Initialize with an invalid coordinate.
|
||||
coords.x = coords.y = -1;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Drawing methods
|
||||
inline void drawPixel(int16_t x, int16_t y, uint16_t color) {
|
||||
if constexpr (ScaleFactor > 1)
|
||||
{
|
||||
for (int dx = 0; dx < ScaleFactor; dx++) {
|
||||
for (int dy = 0; dy < ScaleFactor; dy++) {
|
||||
//irtualCoords v = getCoords(x * ScaleFactor + dx, y * ScaleFactor + dy);
|
||||
// display->drawPixel(v.x, v.y, color);
|
||||
calcPhysicalToElectricalCoords(x * ScaleFactor + dx, y * ScaleFactor + dy);
|
||||
display->drawPixel(coords.x, coords.y, color);
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//VirtualCoords v = getCoords(x, y);
|
||||
//display->drawPixel(v.x, v.y, color);
|
||||
|
||||
calcPhysicalToElectricalCoords(x , y);
|
||||
display->drawPixel(coords.x, coords.y, color);
|
||||
}
|
||||
|
||||
log_v("x: %d, y: %d -> coords.x: %d, coords.y: %d", x, y, coords.x, coords.y);
|
||||
}
|
||||
|
||||
inline void fillScreen(uint16_t color) {
|
||||
display->fillScreen(color);
|
||||
}
|
||||
|
||||
inline void fillScreenRGB888(uint8_t r, uint8_t g, uint8_t b) {
|
||||
display->fillScreenRGB888(r, g, b);
|
||||
}
|
||||
|
||||
inline void drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b) {
|
||||
//VirtualCoords v = getCoords(x, y);
|
||||
//display->drawPixelRGB888(v.x, v.y, r, g, b);
|
||||
|
||||
calcPhysicalToElectricalCoords(x , y);
|
||||
display->drawPixelRGB888(coords.x, coords.y, r, g, b);
|
||||
}
|
||||
|
||||
#ifdef USE_GFX_LITE
|
||||
inline void drawPixel(int16_t x, int16_t y, CRGB color) {
|
||||
//VirtualCoords v = getCoords(x, y);
|
||||
//display->drawPixel(v.x, v.y, color);
|
||||
|
||||
calcPhysicalToElectricalCoords(x , y);
|
||||
display->drawPixel(coords.x, coords.y, color);
|
||||
|
||||
}
|
||||
|
||||
inline void fillScreen(CRGB color) {
|
||||
display->fillScreen(color);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef NO_GFX
|
||||
inline void drawDisplayTest() {
|
||||
|
||||
// Call ourself as we need to re-map pixels if we're using our own ScanTypeMapping
|
||||
// Note: Will mean this display test will be impacted by chaining approach etc.
|
||||
// this->setFont(&FreeSansBold12pt7b);
|
||||
this->setTextColor(display->color565(255, 255, 0));
|
||||
// this->setTextSize(1);
|
||||
for (int col = 0; col < vmodule_cols; col++) {
|
||||
for (int row = 0; row < vmodule_rows; row++) {
|
||||
|
||||
int start_x = col * panel_res_x;
|
||||
int start_y = row * panel_res_y;
|
||||
|
||||
int panel_id = col + (row * vmodule_cols) + 1;
|
||||
//int top_left_x = panel * panel_res_x;
|
||||
this->drawRect(start_x, start_y, panel_res_x, panel_res_y, this->color565(0, 255, 0));
|
||||
this->setCursor(start_x + panel_res_x/2 - 2, start_y + panel_res_y/2 - 4);
|
||||
this->print(panel_id);
|
||||
|
||||
//log_d("drawDisplayTest() Panel: %d, start_x: %d, start_y: %d", panel_id, start_x, start_y);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inline void drawDisplayTestDMA()
|
||||
{
|
||||
// Write to the underlying panels only via the dma_display instance.
|
||||
// This only works on standard panels with a linear mapping (i.e. two-scan).
|
||||
this->display->setTextColor(this->display->color565(255, 255, 0));
|
||||
this->display->setTextSize(1);
|
||||
|
||||
for (int panel = 0; panel < vmodule_cols * vmodule_rows; panel++)
|
||||
{
|
||||
int top_left_x = panel * panel_res_x;
|
||||
this->display->drawRect(top_left_x, 0, panel_res_x, panel_res_y, this->display->color565(0, 255, 0));
|
||||
this->display->setCursor((panel * panel_res_x) + 6, panel_res_y - 12);
|
||||
this->display->print((vmodule_cols * vmodule_rows) - panel);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
inline void clearScreen() { display->clearScreen(); }
|
||||
|
||||
inline uint16_t color444(uint8_t r, uint8_t g, uint8_t b) { return display->color444(r, g, b); }
|
||||
inline uint16_t color565(uint8_t r, uint8_t g, uint8_t b) { return display->color565(r, g, b); }
|
||||
|
||||
inline void flipDMABuffer() { display->flipDMABuffer(); }
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Rotation (runtime)
|
||||
inline void setRotation(uint8_t rotate) {
|
||||
if (rotate < 4)
|
||||
_rotate = rotate;
|
||||
#ifdef NO_GFX
|
||||
// When NO_GFX is defined, update _virtual_res_x/_virtual_res_y as needed.
|
||||
#else
|
||||
uint8_t rotation = (rotate & 3);
|
||||
switch (rotation) {
|
||||
case 0:
|
||||
case 2:
|
||||
_virtual_res_x = virtual_res_x;
|
||||
_virtual_res_y = virtual_res_y;
|
||||
_width = virtual_res_x;
|
||||
_height = virtual_res_y;
|
||||
break;
|
||||
case 1:
|
||||
case 3:
|
||||
_virtual_res_x = virtual_res_y;
|
||||
_virtual_res_y = virtual_res_x;
|
||||
_width = virtual_res_y;
|
||||
_height = virtual_res_x;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Panel scan–type configuration (runtime adjustment of pixel base)
|
||||
inline void setPixelBase(uint8_t pixel_base) {
|
||||
panel_pixel_base = pixel_base;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// calcPhysicalToElectricalCoords() maps a virtual (x,y) coordinate to a physical coordinate.
|
||||
// VirtualCoords getCoords(int16_t virt_x, int16_t virt_y) {
|
||||
void calcPhysicalToElectricalCoords(int16_t virt_x, int16_t virt_y) {
|
||||
|
||||
#ifdef NO_GFX
|
||||
if (virt_x < 0 || virt_x >= _virtual_res_x || virt_y < 0 || virt_y >= _virtual_res_y) {
|
||||
#else
|
||||
if (virt_x < 0 || virt_x >= _width || virt_y < 0 || virt_y >= _height) {
|
||||
#endif
|
||||
coords.x = coords.y = -1;
|
||||
return;
|
||||
//return coords;
|
||||
}
|
||||
|
||||
//log_d("calcCoords pre-chain: virt_x: %d, virt_y: %d", virt_x, virt_y);
|
||||
|
||||
// --- Runtime rotation ---
|
||||
switch (_rotate) {
|
||||
case 1: {
|
||||
int16_t temp = virt_x;
|
||||
virt_x = virt_y;
|
||||
virt_y = virtual_res_y - 1 - temp;
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
virt_x = virtual_res_x - 1 - virt_x;
|
||||
virt_y = virtual_res_y - 1 - virt_y;
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
int16_t temp = virt_x;
|
||||
virt_x = virtual_res_x - 1 - virt_y;
|
||||
virt_y = temp;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// --- Chain mapping ---
|
||||
int row = virt_y / panel_res_y; // 0-indexed row in the virtual module
|
||||
if constexpr (ChainScanType == CHAIN_TOP_RIGHT_DOWN) {
|
||||
if ((row & 1) == 1) {
|
||||
coords.x = dma_res_x - virt_x - (row * virtual_res_x);
|
||||
coords.y = panel_res_y - 1 - (virt_y % panel_res_y);
|
||||
} else {
|
||||
coords.x = ((vmodule_rows - (row + 1)) * virtual_res_x) + virt_x;
|
||||
coords.y = (virt_y % panel_res_y);
|
||||
}
|
||||
}
|
||||
else if constexpr (ChainScanType == CHAIN_TOP_RIGHT_DOWN_ZZ) {
|
||||
coords.x = ((vmodule_rows - (row + 1)) * virtual_res_x) + virt_x;
|
||||
coords.y = (virt_y % panel_res_y);
|
||||
}
|
||||
else if constexpr (ChainScanType == CHAIN_TOP_LEFT_DOWN) {
|
||||
if ((row & 1) == 0) {
|
||||
coords.x = dma_res_x - virt_x - (row * virtual_res_x);
|
||||
coords.y = panel_res_y - 1 - (virt_y % panel_res_y);
|
||||
} else {
|
||||
coords.x = ((vmodule_rows - (row + 1)) * virtual_res_x) + virt_x;
|
||||
coords.y = (virt_y % panel_res_y);
|
||||
}
|
||||
}
|
||||
else if constexpr (ChainScanType == CHAIN_TOP_LEFT_DOWN_ZZ) {
|
||||
coords.x = ((vmodule_rows - (row + 1)) * virtual_res_x) + virt_x;
|
||||
coords.y = (virt_y % panel_res_y);
|
||||
}
|
||||
else if constexpr (ChainScanType == CHAIN_BOTTOM_LEFT_UP) {
|
||||
row = vmodule_rows - row - 1;
|
||||
if ((row & 1) == 1) {
|
||||
coords.x = ((vmodule_rows - (row + 1)) * virtual_res_x) + virt_x;
|
||||
coords.y = (virt_y % panel_res_y);
|
||||
} else {
|
||||
coords.x = dma_res_x - (row * virtual_res_x) - virt_x;
|
||||
coords.y = panel_res_y - 1 - (virt_y % panel_res_y);
|
||||
}
|
||||
}
|
||||
else if constexpr (ChainScanType == CHAIN_BOTTOM_LEFT_UP_ZZ) {
|
||||
row = vmodule_rows - row - 1;
|
||||
coords.x = ((vmodule_rows - (row + 1)) * virtual_res_x) + virt_x;
|
||||
coords.y = (virt_y % panel_res_y);
|
||||
}
|
||||
else if constexpr (ChainScanType == CHAIN_BOTTOM_RIGHT_UP) {
|
||||
row = vmodule_rows - row - 1;
|
||||
if ((row & 1) == 0) {
|
||||
coords.x = ((vmodule_rows - (row + 1)) * virtual_res_x) + virt_x;
|
||||
coords.y = (virt_y % panel_res_y);
|
||||
} else {
|
||||
coords.x = dma_res_x - (row * virtual_res_x) - virt_x;
|
||||
coords.y = panel_res_y - 1 - (virt_y % panel_res_y);
|
||||
}
|
||||
}
|
||||
else if constexpr (ChainScanType == CHAIN_BOTTOM_RIGHT_UP_ZZ) {
|
||||
row = vmodule_rows - row - 1;
|
||||
coords.x = ((vmodule_rows - (row + 1)) * virtual_res_x) + virt_x;
|
||||
coords.y = (virt_y % panel_res_y);
|
||||
}
|
||||
else { // CHAIN_NONE (default)
|
||||
coords.x = virt_x;
|
||||
coords.y = virt_y;
|
||||
}
|
||||
|
||||
//log_d("calcCoords post-chain: virt_x: %d, virt_y: %d", virt_x, virt_y);
|
||||
|
||||
// --- Apply physical LED panel scan–type mapping / fix ---
|
||||
coords = ScanTypeMapping::apply(coords, virt_y, panel_pixel_base);
|
||||
|
||||
}
|
||||
|
||||
#ifdef NO_GFX
|
||||
inline uint16_t width() const { return _virtual_res_x; }
|
||||
inline uint16_t height() const { return _virtual_res_y; }
|
||||
#endif
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Data members (public for compatibility)
|
||||
VirtualCoords coords;
|
||||
|
||||
uint8_t panel_res_x; // physical panel resolution X
|
||||
uint8_t panel_res_y; // physical panel resolution Y
|
||||
uint8_t panel_pixel_base; // used for scan–type mapping
|
||||
|
||||
inline void setDisplay(MatrixPanel_I2S_DMA &disp) {
|
||||
display = &disp;
|
||||
}
|
||||
|
||||
private:
|
||||
MatrixPanel_I2S_DMA *display;
|
||||
// Note: panel_chain_type is now fixed via the compile–time template parameter 'ChainScanType'.
|
||||
uint16_t virtual_res_x; // virtual display width (combination of panels)
|
||||
uint16_t virtual_res_y; // virtual display height (combination of panels)
|
||||
uint16_t _virtual_res_x; // width adjusted by current rotation
|
||||
uint16_t _virtual_res_y; // height adjusted by current rotation
|
||||
uint8_t vmodule_rows; // virtual module rows
|
||||
uint8_t vmodule_cols; // virtual module columns
|
||||
uint16_t dma_res_x; // width as seen by the DMA engine
|
||||
|
||||
int _rotate; // runtime rotation (0 to 3)
|
||||
};
|
||||
|
||||
#endif // VIRTUAL_MATRIX_PANEL_TEMPLATE_H
|
|
@ -35,8 +35,7 @@
|
|||
#include <Fonts/FreeSansBold12pt7b.h>
|
||||
#endif
|
||||
|
||||
|
||||
// #include <iostream>
|
||||
#warning "VirtualMatrixPanel is depreciated. Please include 'ESP32-HUB75-VirtualMatrixPanel_T.hpp' and use VirtualMatrixPanel_T instead. Refer to the documentation and VirtualMatrixPanel.ino example."
|
||||
|
||||
struct VirtualCoords
|
||||
{
|
||||
|
@ -56,7 +55,8 @@ enum PANEL_SCAN_RATE
|
|||
NORMAL_ONE_SIXTEEN, // treated as the same
|
||||
FOUR_SCAN_32PX_HIGH,
|
||||
FOUR_SCAN_16PX_HIGH,
|
||||
FOUR_SCAN_64PX_HIGH
|
||||
FOUR_SCAN_64PX_HIGH,
|
||||
FOUR_SCAN_40PX_HIGH
|
||||
};
|
||||
|
||||
// Chaining approach... From the perspective of the DISPLAY / LED side of the chain of panels.
|
||||
|
@ -73,6 +73,9 @@ enum PANEL_CHAIN_TYPE
|
|||
CHAIN_BOTTOM_LEFT_UP_ZZ
|
||||
};
|
||||
|
||||
|
||||
|
||||
//[[deprecated("VirtualMatrixPanel is depreciated. Please include 'ESP32-VirtualMatrixPanel_T.hpp' and use VirtualMatrixPanel_T instead. Refer to the documentation and VirtualMatrixPanel.ino example.")]]
|
||||
#ifdef USE_GFX_LITE
|
||||
class VirtualMatrixPanel : public GFX
|
||||
#elif !defined NO_GFX
|
||||
|
@ -96,6 +99,7 @@ public:
|
|||
|
||||
panelResX = _panelResX;
|
||||
panelResY = _panelResY;
|
||||
panel_pixel_base = _panelResX;
|
||||
|
||||
vmodule_rows = _vmodule_rows;
|
||||
vmodule_cols = _vmodule_cols;
|
||||
|
@ -134,27 +138,25 @@ public:
|
|||
#endif
|
||||
|
||||
#ifdef NO_GFX
|
||||
inline int16_t width() const { return _virtualResX; }
|
||||
inline int16_t height() const { return _virtualResY; }
|
||||
inline uint16_t width() const { return _virtualResX; }
|
||||
inline uint16_t height() const { return _virtualResY; }
|
||||
#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 setPhysicalPanelScanRate(PANEL_SCAN_RATE rate);
|
||||
void setPhysicalPanelScanRate(PANEL_SCAN_RATE rate, uint8_t pixel_base);
|
||||
void setZoomFactor(int scale);
|
||||
|
||||
virtual VirtualCoords getCoords(int16_t x, int16_t y);
|
||||
VirtualCoords coords;
|
||||
int16_t panelResX;
|
||||
int16_t panelResY;
|
||||
uint8_t panelResX;
|
||||
uint8_t panelResY;
|
||||
uint8_t panel_pixel_base;
|
||||
|
||||
private:
|
||||
MatrixPanel_I2S_DMA *display;
|
||||
|
@ -162,17 +164,17 @@ private:
|
|||
PANEL_CHAIN_TYPE panel_chain_type;
|
||||
PANEL_SCAN_RATE panel_scan_rate = NORMAL_TWO_SCAN;
|
||||
|
||||
int16_t virtualResX; ///< Display width as combination of panels
|
||||
int16_t virtualResY; ///< Display height as combination of panels
|
||||
uint16_t virtualResX; ///< Display width as combination of panels
|
||||
uint16_t virtualResY; ///< Display height as combination of panels
|
||||
|
||||
|
||||
int16_t _virtualResX; ///< Display width as modified by current rotation
|
||||
int16_t _virtualResY; ///< Display height as modified by current rotation
|
||||
uint16_t _virtualResX; ///< Display width as modified by current rotation
|
||||
uint16_t _virtualResY; ///< Display height as modified by current rotation
|
||||
|
||||
int16_t vmodule_rows;
|
||||
int16_t vmodule_cols;
|
||||
uint8_t vmodule_rows;
|
||||
uint8_t vmodule_cols;
|
||||
|
||||
int16_t dmaResX; // The width of the chain in pixels (as the DMA engine sees it)
|
||||
uint16_t dmaResX; // The width of the chain in pixels (as the DMA engine sees it)
|
||||
|
||||
int _rotate = 0;
|
||||
|
||||
|
@ -374,52 +376,54 @@ inline VirtualCoords VirtualMatrixPanel::getCoords(int16_t virt_x, int16_t virt_
|
|||
/* START: Pixel remapping AGAIN to convert TWO parallel scanline output that the
|
||||
* the underlying hardware library is designed for (because
|
||||
* there's only 2 x RGB pins... and convert this to 1/4 or something
|
||||
*/
|
||||
|
||||
if ((panel_scan_rate == FOUR_SCAN_32PX_HIGH) || (panel_scan_rate == FOUR_SCAN_64PX_HIGH))
|
||||
{
|
||||
*/
|
||||
|
||||
if (panel_scan_rate == FOUR_SCAN_64PX_HIGH)
|
||||
{
|
||||
// https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/issues/345#issuecomment-1510401192
|
||||
if ((virt_y & 8) != ((virt_y & 16) >> 1)) { virt_y = (virt_y & 0b11000) ^ 0b11000 + (virt_y & 0b11100111); }
|
||||
}
|
||||
switch (panel_scan_rate) {
|
||||
case 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);
|
||||
// no break, rest of code is the same for 64 and 32px high screens
|
||||
case 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 !
|
||||
*/
|
||||
|
||||
/* 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.
|
||||
if ((coords.y & 8) == 0)
|
||||
// 1st, 3rd 'block' of 8 rows of pixels, offset by panel width in DMA buffer
|
||||
coords.x += ((coords.x / panel_pixel_base) + 1) * panel_pixel_base;
|
||||
else
|
||||
// 2nd, 4th 'block' of 8 rows of pixels, offset by panel width in DMA buffer
|
||||
coords.x += (coords.x / panel_pixel_base) * panel_pixel_base;
|
||||
|
||||
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 ((coords.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 ((coords.y & 4) == 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
|
||||
}
|
||||
coords.y = (coords.y >> 3) * 4 + (coords.y & 0b00000011);
|
||||
// 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);
|
||||
break;
|
||||
case FOUR_SCAN_16PX_HIGH:
|
||||
if ((coords.y & 4) == 0)
|
||||
// 1st, 3rd 'block' of 8 rows of pixels, offset by panel width in DMA buffer
|
||||
coords.x += ((coords.x / panel_pixel_base) + 1) * panel_pixel_base;
|
||||
else
|
||||
// 2nd, 4th 'block' of 8 rows of pixels, offset by panel width in DMA buffer
|
||||
coords.x += (coords.x / panel_pixel_base) * panel_pixel_base;
|
||||
coords.y = (coords.y >> 3) * 4 + (coords.y & 0b00000011);
|
||||
break;
|
||||
case FOUR_SCAN_40PX_HIGH:
|
||||
if ((coords.y / 10) % 2 == 0)
|
||||
coords.x += ((coords.x / panel_pixel_base) + 1) * panel_pixel_base;
|
||||
else
|
||||
coords.x += (coords.x / panel_pixel_base) * panel_pixel_base;
|
||||
coords.y = (coords.y / 20) * 10 + (coords.y % 10);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return coords;
|
||||
|
@ -521,6 +525,12 @@ inline void VirtualMatrixPanel::setPhysicalPanelScanRate(PANEL_SCAN_RATE rate)
|
|||
panel_scan_rate = rate;
|
||||
}
|
||||
|
||||
inline void VirtualMatrixPanel::setPhysicalPanelScanRate(PANEL_SCAN_RATE rate, uint8_t pixel_base)
|
||||
{
|
||||
panel_scan_rate = rate;
|
||||
panel_pixel_base = pixel_base;
|
||||
}
|
||||
|
||||
inline void VirtualMatrixPanel::setZoomFactor(int scale)
|
||||
{
|
||||
if(scale < 5 && scale > 0)
|
||||
|
@ -546,19 +556,5 @@ inline void VirtualMatrixPanel::drawDisplayTest()
|
|||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
// need to recreate this one, as it wouldn't work to just map where it starts.
|
||||
inline void VirtualMatrixPanel::drawIcon (int *ico, int16_t x, int16_t y, int16_t icon_cols, int16_t icon_rows) {
|
||||
int i, j;
|
||||
for (i = 0; i < icon_rows; i++) {
|
||||
for (j = 0; j < icon_cols; j++) {
|
||||
// This is a call to this libraries version of drawPixel
|
||||
// which will map each pixel, which is what we want.
|
||||
//drawPixelRGB565 (x + j, y + i, ico[i * module_cols + j]);
|
||||
drawPixel (x + j, y + i, ico[i * icon_cols + j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
#endif
|
||||
|
|
|
@ -50,6 +50,7 @@ Modified heavily for the ESP32 HUB75 DMA library by:
|
|||
// Get CPU freq function.
|
||||
#include <soc/rtc.h>
|
||||
|
||||
/*
|
||||
|
||||
volatile bool previousBufferFree = true;
|
||||
|
||||
|
@ -68,7 +69,7 @@ Modified heavily for the ESP32 HUB75 DMA library by:
|
|||
bool DRAM_ATTR i2s_parallel_is_previous_buffer_free() {
|
||||
return previousBufferFree;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
// Static
|
||||
i2s_dev_t* getDev()
|
||||
|
@ -402,11 +403,14 @@ Modified heavily for the ESP32 HUB75 DMA library by:
|
|||
// If we have double buffering, then allocate an interrupt service routine function
|
||||
// that can be used for I2S0/I2S1 created interrupts.
|
||||
|
||||
/*
|
||||
// Setup I2S Interrupt
|
||||
SET_PERI_REG_BITS(I2S_INT_ENA_REG(ESP32_I2S_DEVICE), I2S_OUT_EOF_INT_ENA_V, 1, I2S_OUT_EOF_INT_ENA_S);
|
||||
|
||||
// Allocate a level 1 intterupt: lowest priority, as ISR isn't urgent and may take a long time to complete
|
||||
esp_intr_alloc(irq_source, (int)(ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL1), i2s_isr, NULL, NULL);
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#if defined (CONFIG_IDF_TARGET_ESP32S2)
|
||||
|
@ -481,6 +485,7 @@ Modified heavily for the ESP32 HUB75 DMA library by:
|
|||
|
||||
ESP_LOGD("ESP32/S2", "Allocating %d bytes of memory for DMA descriptors.", (int)sizeof(HUB75_DMA_DESCRIPTOR_T) * len);
|
||||
|
||||
/*
|
||||
// New - Temporary blank descriptor for transitions between DMA buffer
|
||||
_dmadesc_blank = (HUB75_DMA_DESCRIPTOR_T*)heap_caps_malloc(sizeof(HUB75_DMA_DESCRIPTOR_T) * 1, MALLOC_CAP_DMA);
|
||||
_dmadesc_blank->size = 1024*2;
|
||||
|
@ -491,6 +496,7 @@ Modified heavily for the ESP32 HUB75 DMA library by:
|
|||
_dmadesc_blank->owner = 1;
|
||||
_dmadesc_blank->qe.stqe_next = (lldesc_t*) _dmadesc_blank;
|
||||
_dmadesc_blank->offset = 0;
|
||||
*/
|
||||
|
||||
return true;
|
||||
|
||||
|
@ -595,23 +601,24 @@ Modified heavily for the ESP32 HUB75 DMA library by:
|
|||
|
||||
if ( buffer_id == 1) {
|
||||
|
||||
_dmadesc_a[_dmadesc_last].qe.stqe_next = &_dmadesc_b[0]; // Start sending out _dmadesc_b (or buffer 1)
|
||||
//fix _dmadesc_ loop issue #407
|
||||
//need to connect the up comming _dmadesc_ not the old one
|
||||
_dmadesc_b[_dmadesc_last].qe.stqe_next = &_dmadesc_b[0];
|
||||
|
||||
//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];
|
||||
_dmadesc_a[_dmadesc_last].qe.stqe_next = &_dmadesc_b[0]; // Start sending out _dmadesc_b (or buffer 1)
|
||||
|
||||
} else {
|
||||
|
||||
_dmadesc_b[_dmadesc_last].qe.stqe_next = &_dmadesc_a[0];
|
||||
|
||||
_dmadesc_a[_dmadesc_last].qe.stqe_next = &_dmadesc_a[0];
|
||||
|
||||
_dmadesc_b[_dmadesc_last].qe.stqe_next = &_dmadesc_a[0]; // Start sending out _dmadesc_a (or buffer 0)
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
previousBufferFree = false;
|
||||
//while (i2s_parallel_is_previous_buffer_free() == false) {}
|
||||
while (!previousBufferFree);
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -151,8 +151,10 @@ i2s_dev_t* getDev();
|
|||
HUB75_DMA_DESCRIPTOR_T* _dmadesc_a = nullptr;
|
||||
HUB75_DMA_DESCRIPTOR_T* _dmadesc_b = nullptr;
|
||||
|
||||
/*
|
||||
HUB75_DMA_DESCRIPTOR_T* _dmadesc_blank = nullptr;
|
||||
uint16_t _blank_data[1024] = {0};
|
||||
*/
|
||||
|
||||
volatile i2s_dev_t* _dev;
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "dma_parallel_io.hpp"
|
||||
#include <Arduino.h>
|
||||
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C6
|
||||
|
||||
|
@ -140,11 +139,24 @@ bool Bus_Parallel16::init(void)
|
|||
.auto_update_desc = false};
|
||||
gdma_apply_strategy(dma_chan, &strategy_config);
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0)
|
||||
gdma_transfer_config_t transfer_config = {
|
||||
#ifdef SPIRAM_DMA_BUFFER
|
||||
.max_data_burst_size = 64,
|
||||
.access_ext_mem = true
|
||||
#else
|
||||
.max_data_burst_size = 32,
|
||||
.access_ext_mem = false
|
||||
#endif
|
||||
};
|
||||
gdma_config_transfer(dma_chan, &transfer_config);
|
||||
#else
|
||||
gdma_transfer_ability_t ability = {
|
||||
.sram_trans_align = 32,
|
||||
.psram_trans_align = 64,
|
||||
};
|
||||
gdma_set_transfer_ability(dma_chan, &ability);
|
||||
#endif
|
||||
|
||||
// Enable DMA transfer callback
|
||||
static gdma_tx_event_callbacks_t tx_cbs = {
|
||||
|
@ -356,4 +368,4 @@ void Bus_Parallel16::flip_dma_output_buffer(int back_buffer_id)
|
|||
|
||||
} // end flip
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
@ -26,14 +26,9 @@
|
|||
|
||||
#include "gdma_lcd_parallel16.hpp"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_idf_version.h"
|
||||
|
||||
/*
|
||||
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
|
||||
|
@ -57,6 +52,7 @@
|
|||
|
||||
return true;
|
||||
}
|
||||
*/
|
||||
|
||||
lcd_cam_dev_t* getDev()
|
||||
{
|
||||
|
@ -86,8 +82,6 @@
|
|||
LCD_CAM.lcd_user.lcd_reset = 1;
|
||||
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);
|
||||
|
||||
// Configure LCD clock. Since this program generates human-perceptible
|
||||
// output and not data for LED matrices or NeoPixels, use almost the
|
||||
|
@ -203,42 +197,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
const struct {
|
||||
int8_t pin;
|
||||
uint8_t signal;
|
||||
} mux[] = {
|
||||
{ 43, LCD_DATA_OUT0_IDX }, // These are 8 consecutive pins down one
|
||||
{ 42, LCD_DATA_OUT1_IDX }, // side of the ESP32-S3 Feather. The ESP32
|
||||
{ 2, LCD_DATA_OUT2_IDX }, // has super flexible pin MUX capabilities,
|
||||
{ 9, LCD_DATA_OUT3_IDX }, // so any signal can go to any pin!
|
||||
{ 10, LCD_DATA_OUT4_IDX },
|
||||
{ 11, LCD_DATA_OUT5_IDX },
|
||||
{ 12, LCD_DATA_OUT6_IDX },
|
||||
{ 13, LCD_DATA_OUT7_IDX },
|
||||
};
|
||||
for (int i = 0; i < 8; i++) {
|
||||
esp_rom_gpio_connect_out_signal(mux[i].pin, LCD_DATA_OUT0_IDX + i, false, false);
|
||||
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[mux[i].pin], PIN_FUNC_GPIO);
|
||||
gpio_set_drive_capability((gpio_num_t)mux[i].pin, (gpio_drive_cap_t)3);
|
||||
}
|
||||
*/
|
||||
// Clock
|
||||
esp_rom_gpio_connect_out_signal(_cfg.pin_wr, LCD_PCLK_IDX, _cfg.invert_pclk, false);
|
||||
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[_cfg.pin_wr], PIN_FUNC_GPIO);
|
||||
gpio_set_drive_capability((gpio_num_t)_cfg.pin_wr, (gpio_drive_cap_t)3);
|
||||
|
||||
// This program has a known fixed-size data buffer (2496 bytes) that fits
|
||||
// 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.
|
||||
|
||||
// Allocate DMA channel and connect it to the LCD peripheral
|
||||
static gdma_channel_alloc_config_t dma_chan_config = {
|
||||
|
@ -256,19 +219,33 @@
|
|||
};
|
||||
gdma_apply_strategy(dma_chan, &strategy_config);
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
|
||||
gdma_transfer_config_t transfer_config = {
|
||||
#ifdef SPIRAM_DMA_BUFFER
|
||||
.max_data_burst_size = 64,
|
||||
.access_ext_mem = true
|
||||
#else
|
||||
.max_data_burst_size = 32,
|
||||
.access_ext_mem = false
|
||||
#endif
|
||||
};
|
||||
gdma_config_transfer(dma_chan, &transfer_config);
|
||||
#else
|
||||
gdma_transfer_ability_t ability = {
|
||||
.sram_trans_align = 32,
|
||||
.psram_trans_align = 64,
|
||||
};
|
||||
gdma_set_transfer_ability(dma_chan, &ability);
|
||||
gdma_set_transfer_ability(dma_chan, &ability);
|
||||
#endif
|
||||
|
||||
/*
|
||||
// Enable DMA transfer callback
|
||||
static gdma_tx_event_callbacks_t tx_cbs = {
|
||||
// .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);
|
||||
|
||||
*/
|
||||
|
||||
// This uses a busy loop to wait for each DMA transfer to complete...
|
||||
// but the whole point of DMA is that one's code can do other work in
|
||||
|
@ -294,6 +271,7 @@
|
|||
if (_i80_bus)
|
||||
{
|
||||
esp_lcd_del_i80_bus(_i80_bus);
|
||||
_i80_bus = nullptr;
|
||||
}
|
||||
if (_dmadesc_a)
|
||||
{
|
||||
|
@ -391,6 +369,7 @@
|
|||
|
||||
if (_dmadesc_a_idx == _dmadesc_count-1) {
|
||||
_dmadesc_a[_dmadesc_a_idx].next = (dma_descriptor_t *) &_dmadesc_a[0];
|
||||
ESP_LOGV("S3", "Creating last _dmadesc_a descriptor which loops back to _dmadesc_a[0]!");
|
||||
}
|
||||
else {
|
||||
_dmadesc_a[_dmadesc_a_idx].next = (dma_descriptor_t *) &_dmadesc_a[_dmadesc_a_idx+1];
|
||||
|
@ -424,29 +403,21 @@
|
|||
|
||||
void Bus_Parallel16::flip_dma_output_buffer(int back_buffer_id)
|
||||
{
|
||||
|
||||
// if ( _double_dma_buffer == false) return;
|
||||
|
||||
|
||||
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];
|
||||
_dmadesc_b[_dmadesc_count-1].next = (dma_descriptor_t *) &_dmadesc_b[0]; // setup loop
|
||||
_dmadesc_a[_dmadesc_count-1].next = (dma_descriptor_t *) &_dmadesc_b[0]; // flip across
|
||||
}
|
||||
else
|
||||
{
|
||||
_dmadesc_b[_dmadesc_count-1].next = (dma_descriptor_t *) &_dmadesc_a[0];
|
||||
_dmadesc_a[_dmadesc_count-1].next = (dma_descriptor_t *) &_dmadesc_a[0];
|
||||
_dmadesc_a[_dmadesc_count-1].next = (dma_descriptor_t *) &_dmadesc_a[0]; // setup loop
|
||||
_dmadesc_b[_dmadesc_count-1].next = (dma_descriptor_t *) &_dmadesc_a[0]; // flip across
|
||||
}
|
||||
|
||||
//current_back_buffer_id ^= 1;
|
||||
|
||||
/*
|
||||
previousBufferFree = false;
|
||||
|
||||
//while (i2s_parallel_is_previous_buffer_free() == false) {}
|
||||
while (!previousBufferFree);
|
||||
|
||||
|
||||
|
||||
*/
|
||||
|
||||
} // end flip
|
||||
|
||||
|
|
|
@ -167,9 +167,8 @@
|
|||
HUB75_DMA_DESCRIPTOR_T* _dmadesc_b = nullptr;
|
||||
|
||||
bool _double_dma_buffer = false;
|
||||
//bool _dmadesc_a_active = true;
|
||||
|
||||
esp_lcd_i80_bus_handle_t _i80_bus;
|
||||
esp_lcd_i80_bus_handle_t _i80_bus = nullptr;
|
||||
|
||||
|
||||
};
|
||||
|
|
45
testing/four_scan_40_80px_hfarcan.cpp
Normal file
45
testing/four_scan_40_80px_hfarcan.cpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
#include <iostream>
|
||||
#include <iomanip> // For output formatting
|
||||
|
||||
|
||||
// FOUR_SCAN_40_80PX_HFARCAN test
|
||||
struct Coords {
|
||||
int x;
|
||||
int y;
|
||||
};
|
||||
|
||||
int main() {
|
||||
int panel_pixel_base = 16; // Updated pixel base
|
||||
Coords coords;
|
||||
|
||||
// Header for easy Excel copying (Pipe-delimited format)
|
||||
std::cout << "Input X|Input Y|Output X|Output Y" << std::endl;
|
||||
|
||||
// Loop for testing multiple inputs
|
||||
for (int y = 0; y < 40; y += 3) { // Updated y increment to 3
|
||||
for (int x = 0; x < 80; x += 10) {
|
||||
coords.x = x;
|
||||
coords.y = y;
|
||||
|
||||
// Store original coordinates for display
|
||||
int input_x = coords.x;
|
||||
int input_y = coords.y;
|
||||
|
||||
// Mapping logic
|
||||
int panel_local_x = coords.x % 80; // Compensate for chain of panels
|
||||
|
||||
if ((((coords.y) / 10) % 2) ^ ((panel_local_x / panel_pixel_base) % 2)) {
|
||||
coords.x += ((coords.x / panel_pixel_base) * panel_pixel_base);
|
||||
} else {
|
||||
coords.x += (((coords.x / panel_pixel_base) + 1) * panel_pixel_base);
|
||||
}
|
||||
|
||||
coords.y = (coords.y % 10) + 10 * ((coords.y / 20) % 2);
|
||||
|
||||
// Output results in pipe-delimited format for easy Excel import
|
||||
std::cout << input_x << "|" << input_y << "|" << coords.x << "|" << coords.y << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Add table
Reference in a new issue