Compare commits

...

79 commits

Author SHA1 Message Date
mrcodetastic
aed04adfcd Enhance VirtualMatrixPanel_T example 2025-03-19 22:20:23 +00:00
mrcodetastic
c9a8c50702 Create ScrollingTextLayer.ino 2025-03-17 20:55:35 +00:00
mrcodetastic
0be6f369ad Merge branch 'master' of https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA 2025-03-17 17:41:04 +00:00
mrcodetastic
27e61846a2 Update ESP32-HUB75-MatrixPanel-I2S-DMA.h 2025-03-17 17:41:02 +00:00
mrcodetastic
c5fd8d2d52
Update ESP32-HUB75-MatrixPanel-I2S-DMA.cpp 2025-03-17 13:59:39 +00:00
mrcodetastic
84d4388e4a
Merge pull request from Jason2866/master
Fix Arduino and IDF CI
2025-03-17 11:14:41 +00:00
Jason2866
2b4a90255f
Fix Arduino and IDF CI
* Platform pioarduino / Arduino core 3.1.3 IDF 5.3.2.250210
2025-03-17 11:55:10 +01:00
mrcodetastic
15fd8671d9 Fix overflow bug
LSB colour depth potentially has not been visible since the bug was introduced. Effective loss of 1 colour depth.
2025-03-17 00:31:46 +00:00
mrcodetastic
c6e708ae13 Update 3_DoubleBuffer.ino 2025-03-16 16:18:56 +00:00
mrcodetastic
f4357acd2d Fix double buffering
The existing way of delaying the timing the 'flip' to the back-buffer based on an ESP32 hardware level interrupt doesn't work properly for a variety of reasons, namely that the interrupt takes too long to process. As such, when flipDMABuffer() is called, it happens immediately. It is up to the user to ensure that they don't flip so quickly that they introduce flicker from excessive flipping!

Double-buffer example updated accordingly.
2025-03-16 15:21:06 +00:00
mrcodetastic
d121fa10ce 2025-03-16 01:58:30 +00:00
mrcodetastic
39d5960fc8 Merge branch 'master' of https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA 2025-03-16 00:12:38 +00:00
mrcodetastic
2a91f7e5d6 2025-03-16 00:12:35 +00:00
mrcodetastic
3d5dda371d
Update README.md 2025-02-25 23:29:02 +00:00
mrcodetastic
24bb80df29
Update README.md 2025-02-24 12:35:22 +00:00
mrcodetastic
1d25183895 2025-02-24 10:58:44 +00:00
mrcodetastic
fd4831cb0f Update ESP32-HUB75-MatrixPanel-I2S-DMA.cpp 2025-02-24 01:41:07 +00:00
mrcodetastic
33883656da 2025-02-24 01:38:50 +00:00
mrcodetastic
67c4decce0 Merge branch 'master' of https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA 2025-02-23 17:34:15 +00:00
mrcodetastic
9add343a41
Added new FOUR_SCAN_40_80PX_HFARCAN option for VirtualMatrixPanel_T
2025-02-23 17:34:12 +00:00
mrcodetastic
1c8629f5ae
Merge pull request from spiro-c/patch-idf
Fix for esp-idf with GFX
2025-02-23 09:24:27 +00:00
SpiroC
b586721422 Fix for esp-idf with GFX 2025-02-22 16:09:12 +11:00
mrcodetastic
9ae0e0c200
Merge pull request from spiro-c/patch-1
Fix PlatformIO GitHub CI does not work. 
2025-02-22 00:50:45 +00:00
SpiroC
a3298cc7a2 Update the build name 2025-02-21 16:03:29 +11:00
SpiroC
0dba05b64a Fix the build 2025-02-21 15:58:47 +11:00
mrcodetastic
af600a2fb6
Update esp-idf-with-gfx.yml 2025-02-19 11:19:36 +00:00
mrcodetastic
22a8eae880
Update esp-idf-with-gfx.yml 2025-02-19 11:13:30 +00:00
mrcodetastic
dc70588980 Update ESP32-VirtualMatrixPanel-I2S-DMA.h 2025-02-19 08:15:30 +00:00
mrcodetastic
ddebd4c317 Update ESP32-VirtualMatrixPanel-I2S-DMA.h 2025-02-19 08:14:10 +00:00
mrcodetastic
3382896c6e
Update README.md 2025-02-19 02:00:20 +00:00
mrcodetastic
7ba943e56d
Update README.md 2025-02-19 01:59:33 +00:00
mrcodetastic
a374445b88
Update esp-idf-without-gfx.yml 2025-02-19 01:58:43 +00:00
mrcodetastic
869078befb
Update esp-idf-with-gfx.yml 2025-02-19 01:58:25 +00:00
mrcodetastic
839443fdf6
Update and rename esp-idf-5.3.1_without-gfx.yml to esp-idf-without-gfx.yml 2025-02-19 01:54:52 +00:00
mrcodetastic
3e1d74f4e1
Update and rename esp-idf-5.3.1_with-gfx.yml to esp-idf-with-gfx.yml 2025-02-19 01:54:28 +00:00
mrcodetastic
5a5150881f
Update README.md 2025-02-19 01:48:10 +00:00
mrcodetastic
830283a6aa
Rename esp-idf-5.1.2_without-gfx.yml to esp-idf-5.3.1_without-gfx.yml 2025-02-19 01:47:52 +00:00
mrcodetastic
c6a298e1c4
Update esp-idf-5.3.1_with-gfx.yml 2025-02-19 01:45:17 +00:00
mrcodetastic
fc1e54e75f
Update esp-idf-5.1.2_without-gfx.yml 2025-02-19 01:44:59 +00:00
mrcodetastic
286dc1d2fb
Update and rename esp-idf-5.1.2_with-gfx.yml to esp-idf-5.3.1_with-gfx.yml 2025-02-19 01:44:38 +00:00
mrcodetastic
2ac3367e93
Rename pio_arduino_build.yml to pio_arduino_build.yml.disabled 2025-02-19 01:41:37 +00:00
mrcodetastic
35e441e8af
Merge pull request from mrcodetastic/virtdev
Migrate new VirtualDisplayPanel_T changes to master.
2025-02-19 01:39:09 +00:00
mrcodetastic
42f5da448f Update VirtualMatrixPanel.ino 2025-02-19 01:36:40 +00:00
mrcodetastic
5af3d83a10 VirtualMatrixPanel_T
Initial release
2025-02-19 01:34:59 +00:00
mrcodetastic
d18758c9e4 Filename Change 2025-02-18 08:21:59 +00:00
mrcodetastic
d77a90f809 Changes to Virtual Display
Templating based for performance and extendibility.
2025-02-18 01:36:58 +00:00
mrcodetastic
3a78a9dfb6
Update README.md 2025-02-10 23:57:59 +00:00
mrcodetastic
9da34a7225
Merge pull request from j-g00da/scan_rate_pixel_base
Settable pixel-base
2025-02-08 15:10:51 +00:00
mrcodetastic
e369f0ddb8
Merge pull request from j-g00da/40px_four_scan_panels_support
40px four-scan panels support
2025-02-08 15:10:44 +00:00
mrcodetastic
d02c44312f
Merge pull request from j-g00da/refactor_scan_rate_remapping
Refactor pixel-mapping code for four-scan screens
2025-02-08 15:10:33 +00:00
mrcodetastic
655c2bc838
Merge pull request from board707/pixel_mapper
Add Pixel_Mapping_Test example.
2025-02-08 15:10:26 +00:00
Jagoda Estera Ślązak
0e5c84da78 Use uint8_t for panel_pixel_base 2025-02-08 14:49:35 +01:00
Jagoda Estera Ślązak
6f7d1839c8 Merge branch 'use_uint8_t_for_panel_resolutions' of https://github.com/j-g00da/ESP32-HUB75-MatrixPanel-DMA into scan_rate_pixel_base
# Conflicts:
#	src/ESP32-VirtualMatrixPanel-I2S-DMA.h
2025-02-08 14:48:07 +01:00
Jagoda Estera Ślązak
7d2ed7936d Use 8-bit integers, where larger don't make sense 2025-02-08 14:42:47 +01:00
Jagoda Estera Ślązak
4695ed3a0f Use unsigned integers where negative values doesn't make sense 2025-02-08 14:37:12 +01:00
Jagoda Estera Ślązak
79ab7e76f8 Add ability to set pixel_base without overriding VirtualMatrixPanel 2025-02-08 12:15:53 +01:00
Jagoda Ślązak
4fa43b7fbc Add support for common 40px-high four-scan displays 2025-02-03 08:01:22 +01:00
Jagoda Ślązak
588f23456b Reformat pixel-mapping code for four-scan screens. 2025-02-02 11:55:31 +01:00
board707
cefa9d01b8 Add Pixel_Mapping_Test example. 2025-01-31 00:45:36 +03:00
mrcodetastic
ee796eb96f
Merge pull request from psy0rz/master
fix compile error on espidf v4.4.8 with target esp32s3
2025-01-28 00:17:06 +00:00
mrcodetastic
cac0137db7
Merge pull request from stef1949/patch-1
Title spelling mistake in README.md
2025-01-28 00:15:38 +00:00
Steph
8a0d40a41f
Title spelling mistake in README.md
Changed "Ohter" to "Other"
2025-01-24 20:30:13 +00:00
Edwin Eefting
4976416d18
fix compile error on espidf v4.4.8 with target esp32s3 2025-01-23 13:56:58 +01:00
mrcodetastic
447254d2b4
Merge pull request from jhbruhn/patch-1
Remove duplicate Arduino.h import from esp32c6 dma_parallel_io.cpp
2025-01-19 22:17:32 +00:00
Jan-Henrik Bruhn
c7cb4e30b1
Remove duplicate Arduino.h import from esp32c6 dma_parallel_io.cpp
The esp32c6 DMA Parallel IO implementation includes Arduino.h in the second line, and then later on, with the correct ifdef-gates. This removes that first line because it causes issues when building with esp-idf also for other targets.
2025-01-19 12:47:07 +01:00
mrcodetastic
99131abc83
Merge pull request from kroimon/guard_gdma_config_transfer
Guard new gdma_config_transfer to allow building with ESP-IDF < 5.4.0
2025-01-14 21:13:05 +00:00
kroimon
aa6fc263b2 Guard new gdma_config_transfer to allow building with ESP-IDF < 5.4.0 2025-01-14 13:57:59 +01:00
mrcodetastic
87518f921d
Update README.md
Add more chips that aren't supported.
2025-01-06 00:26:45 +00:00
mrcodetastic
babd8fd154
Update README.md 2025-01-05 23:30:55 +00:00
mrcodetastic
a03b8b6e47
Update README.md 2025-01-05 23:07:21 +00:00
mrcodetastic
2ef97a9368
Merge pull request from Lukaswnd/master
Update deprecated function in gdma_lcd_parallel16.cpp and dma_parallel_io.cpp
2025-01-03 14:05:27 +00:00
Lukaswnd
9f5e1adaad
Update deprecated function in gdma_lcd_parallel16.cpp and dma_parallel_io.cpp 2025-01-03 14:45:08 +01:00
mrcodetastic
2f7f5350e5
Update README.md
2024-12-08 00:15:38 +00:00
mrcodetastic
a5d6611b65
Merge pull request from softhack007/fix_dangling_pointer
esp32-S3 fix for crash in Bus_Parallel16::release()
2024-10-18 21:01:07 +01:00
Frank
24b98a87c3 esp32-S3 fix for crash in Bus_Parallel16::release()
has crashed during "delete display"
2024-10-11 20:38:49 +02:00
mrcodetastic
bba1a47f01
Merge pull request from DevxMike/master
fixed nullptr dereference
2024-09-19 22:24:16 +01:00
Michał Bazan
268fd5ea4e fixed nullptr dereference in example 3 2024-09-19 04:39:20 -07:00
Michał Bazan
4b3404a361 fixed nullptr dereference in simpletestshapes 2024-09-19 04:00:05 -07:00
mrcodetastic
7929b97850
Update README.md 2024-08-29 00:33:09 +01:00
28 changed files with 1874 additions and 812 deletions

View file

@ -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'

View file

@ -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'

View file

@ -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:

View file

@ -1,6 +1,6 @@
# HUB75 RGB LED matrix panel library utilizing ESP32 DMA
__[BUILD OPTIONS](/doc/BuildOptions.md) | [EXAMPLES](/examples/README.md)__ | [![PlatformIO CI](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/actions/workflows/pio_arduino_build.yml/badge.svg)](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/actions/workflows/pio_build.yml)
__[BUILD OPTIONS](/doc/BuildOptions.md) | [EXAMPLES](/examples/README.md)__ | [![PlatformIO CI](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/actions/workflows/esp-idf-with-gfx.yml/badge.svg)](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. ![Memory Calculator](doc/memcalc.jpg)
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. 😊
![It's better in real life](image.jpg)
## 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.

View file

@ -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

View file

@ -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++)
{

View file

@ -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.

View file

@ -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

View file

@ -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).

View file

@ -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

View 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

View 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!

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

View file

@ -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:
![image](https://user-images.githubusercontent.com/12006953/224537356-e3c8e87b-0bc0-4185-8f5d-d2d3b328d176.png)
3. In your Arduino sketch, use the 'VirtualMatrixPanel' class instance (virtualDisp) to draw to the display (i.e. drawPixel), instead of the underling MatrixPanel_I2S_DMA class instance (dma_display).
#### 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.
```

View file

@ -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 ScanType 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
*****************************************************************************/
}

View file

@ -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

View file

@ -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>

View 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 compiletime configuration for:
* - Panel chain type (PANEL_CHAIN_TYPE)
* - Scan type mapping (via a class, default is STANDARD_TWO_SCAN)
* - A compiletime 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 Compiletime panel chain configuration.
* @tparam ScanType Policy type implementing a static apply() function for mapping.
* Default is ScanTypeMapping<STANDARD_TWO_SCAN>.
* @tparam ScaleFactor Compiletime 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: compiletime panel chain configuration.
// - ScanTypeMapping: a policy type implementing a static "apply" function
// (default is ScanTypeMapping<STANDARD_TWO_SCAN>).
// - ScaleFactor: a compiletime 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 scantype 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 scantype 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 scantype mapping
inline void setDisplay(MatrixPanel_I2S_DMA &disp) {
display = &disp;
}
private:
MatrixPanel_I2S_DMA *display;
// Note: panel_chain_type is now fixed via the compiletime 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

View file

@ -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

View file

@ -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);
*/

View file

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

View file

@ -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

View file

@ -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

View file

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

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