Merge pull request #327 from mrfaptastic/3.0.0-beta

3.0.0 beta
This commit is contained in:
mrfaptastic 2022-10-06 23:52:22 +01:00 committed by GitHub
commit 7628be00c2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 1762 additions and 1481 deletions

View file

@ -1,4 +1,6 @@
The MIT License (MIT) MIT License
Copyright (c) 2018-2032 Faptastic
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -7,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in The above copyright notice and this permission notice shall be included in all
all copies or substantial portions of the Software. copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
THE SOFTWARE. SOFTWARE.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

View file

@ -1,35 +0,0 @@
#pragma once
/* Abstract the Espressif IDF ESP32 MCU variant compile-time defines
* into another list for the purposes of this library.
*
* i.e. I couldn't be bothered having to update the library when they
* release the ESP32S4,5,6,7, n+1 etc. if they are all fundamentally
* the same architecture.
*/
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
#define ESP32_SXXX 1
#define ESP32_I2S_DEVICE I2S_NUM_0
#define I2S_PARALLEL_CLOCK_HZ 160000000L
#define DMA_MAX (4096-4)
#elif CONFIG_IDF_TARGET_ESP32 || defined(ESP32)
// 2016 model that started it all, and this library. The best.
#define ESP32_ORIG 1
#define ESP32_I2S_DEVICE I2S_NUM_0
#define I2S_PARALLEL_CLOCK_HZ 80000000L
#define DMA_MAX (4096-4)
#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2
#error "ESPC-series RISC-V MCU's do not support parallel DMA and not supported by this library!"
#define ESP32_CXXX 1
#else
#error "ERROR: No ESP32 or ESP32 Espressif IDF detected at compile time."
#endif

View file

@ -100,9 +100,9 @@ void setup() {
PANEL_CHAIN // Chain length PANEL_CHAIN // Chain length
); );
mxconfig.gpio.e = 18; //mxconfig.gpio.e = 18;
mxconfig.clkphase = false; //mxconfig.clkphase = false;
mxconfig.driver = HUB75_I2S_CFG::FM6126A; //mxconfig.driver = HUB75_I2S_CFG::FM6126A;
// Display Setup // Display Setup
dma_display = new MatrixPanel_I2S_DMA(mxconfig); dma_display = new MatrixPanel_I2S_DMA(mxconfig);

View file

@ -1,102 +0,0 @@
// How to use this library with a FM6126 panel, thanks goes to:
// https://github.com/hzeller/rpi-rgb-led-matrix/issues/746
#include <Arduino.h>
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
#include <FastLED.h>
////////////////////////////////////////////////////////////////////
// FM6126 support is still experimental
// Output resolution and panel chain length configuration
#define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module.
#define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module.
#define PANEL_CHAIN 1 // Total number of panels chained one to another
// placeholder for the matrix object
MatrixPanel_I2S_DMA *dma_display = nullptr;
///////////////////////////////////////////////////////////////
// FastLED variables for pattern output
uint16_t time_counter = 0, cycles = 0, fps = 0;
unsigned long fps_timer;
CRGB currentColor;
CRGBPalette16 palettes[] = {HeatColors_p, LavaColors_p, RainbowColors_p, RainbowStripeColors_p, CloudColors_p};
CRGBPalette16 currentPalette = palettes[0];
CRGB ColorFromCurrentPalette(uint8_t index = 0, uint8_t brightness = 255, TBlendType blendType = LINEARBLEND) {
return ColorFromPalette(currentPalette, index, brightness, blendType);
}
void setup(){
/*
The configuration for MatrixPanel_I2S_DMA object is held in HUB75_I2S_CFG structure,
All options has it's predefined default values. So we can create a new structure and redefine only the options we need
Please refer to the '2_PatternPlasma.ino' example for detailed example of how to use the MatrixPanel_I2S_DMA configuration
if you need to change the pin mappings etc.
*/
HUB75_I2S_CFG mxconfig(
PANEL_RES_X, // module width
PANEL_RES_Y, // module height
PANEL_CHAIN // Chain length
);
mxconfig.driver = HUB75_I2S_CFG::FM6126A; // in case that we use panels based on FM6126A chip, we can set it here before creating MatrixPanel_I2S_DMA object
// OK, now we can create our matrix object
dma_display = new MatrixPanel_I2S_DMA(mxconfig);
// If you experience ghosting, you will need to reduce the brightness level, not all RGB Matrix
// Panels are the same - some seem to display ghosting artefacts at lower brightness levels.
// In the setup() function do something like:
// let's adjust default brightness to about 75%
dma_display->setBrightness8(192); // range is 0-255, 0 - 0%, 255 - 100%
// Allocate memory and start DMA display
if( not dma_display->begin() )
Serial.println("****** !KABOOM! Insufficient memory - allocation failed ***********");
fps_timer = millis();
}
void loop(){
for (int x = 0; x < dma_display->width(); x++) {
for (int y = 0; y < dma_display->height(); y++) {
int16_t v = 0;
uint8_t wibble = sin8(time_counter);
v += sin16(x * wibble * 3 + time_counter);
v += cos16(y * (128 - wibble) + time_counter);
v += sin16(y * x * cos8(-time_counter) / 8);
currentColor = ColorFromPalette(currentPalette, (v >> 8) + 127); //, brightness, currentBlendType);
dma_display->drawPixelRGB888(x, y, currentColor.r, currentColor.g, currentColor.b);
}
}
++time_counter;
++cycles;
++fps;
if (cycles >= 1024) {
time_counter = 0;
cycles = 0;
currentPalette = palettes[random(0,sizeof(palettes)/sizeof(palettes[0]))];
}
// print FPS rate every 5 seconds
// Note: this is NOT a matrix refresh rate, it's the number of data frames being drawn to the DMA buffer per second
if (fps_timer + 5000 < millis()){
Serial.printf_P(PSTR("Effect fps: %d\n"), fps/5);
fps_timer = millis();
fps = 0;
}
}

View file

@ -1,51 +0,0 @@
## The mystery of control registers for FM6126A chips
The only available Datasheet for this chips is in Chinese and does not shed a light on what those two control regs are.
An excellent insight could be found here https://github.com/hzeller/rpi-rgb-led-matrix/issues/746#issuecomment-453860510
So there are two regs in this chip - **REG1** and **REG2**,
one could be written with 12 clock pulses (and usually called reg12, dunno why :))
the other one could be written with 13 clock pulses (and usually called reg13, dunno why :))
I've done some measurements on power consumption while toggling bits of **REG1** and it looks that it could provide a fine grained brightness control over the entire matrix with no need for bitbanging over RGB or EO pins.
There are 6 bits (6 to 11) giving an increased brightness (compared to all-zeroes) and 4 bits (2-5) giving decreased brightness!!!
Still unclear if FM6112A brightness control is internally PWMed or current limited, might require some poking with oscilloscope.
So it seems that the most bright (and hungry for power) value is bool REG1[16] = {0,0,0,0,0, 1,1,1,1,1,1, 0,0,0,0,0}; and not {0,1,1,1,1, 1,1,1,1,1,1, 1,1,1,1,1} as it is usually used.
I'm not sure about bit 1 - it is either not used or I was unable to measure it's influence to brightness/power.
Giving at least 10 bits of hardware brightness control opens pretty nice options for offloading and simplifying matrix output. Should dig into this more deeper.
Here are some of the measurements I've took for 2 64x64 panels filled with white color - reg value and corresponding current drain in amps.
|REG1 |bit value|Current, amps |
|--|--|--|
|REG1| 0111111 00000| >5 amps|
|REG1| 0100010 00000| 3.890 amp|
|REG1| 0100000 00000| 3.885 amp|
|REG1| 0011110 00000| 3.640 amp|
|REG1| 0011100 00000| 3.620 amp|
|REG1| 0011000 00000| 3.240 amp|
|REG1| 0010010 00000| 2.520 amp|
|REG1| 0010001 00000| 2.518 amp|
|REG1| 0010001 10000| 2.493 amp|
|REG1| 0010000 00000| 2.490 amp|
|REG1| 0010000 11110| 2.214 amp|
|REG1| 0001100 00000| 2.120 amp|
|REG1| 0001000 00000| 1.750 amp|
|REG1| 0000100 00000| 1.375 amp|
|REG1| 0000010 00000| 1.000 amp|
|REG1| **0000000 00000**| 0.995 amp|
|REG1| 0000001 11111| 0.700 amp|
|REG1| 0000000 01111| 0.690 amp|
|REG1| 0000000 10000| 0.690 amp|
|REG1| 0000000 11110| 0.686 amp|
/Vortigont/

View file

@ -1,3 +0,0 @@
## FM6126 based LED Matrix Panel Reset ##
FM6216 panels require a special reset sequence before they can be used, check your panel chipset if you have issues. Refer to this example.

View file

@ -1,451 +0,0 @@
/*
Patch class for 32x16 RGB Matrix panels
reimplement all functions which use x,y coordinates
*/
#ifndef ESP_HUB75_32x16MatrixPanel
#define ESP_HUB75_32x16MatrixPanel
#include "ESP32-HUB75-MatrixPanel-I2S-DMA.h"
#include <Fonts/FreeSansBold12pt7b.h>
#include "glcdfont.c"
struct VirtualCoords {
int16_t x;
int16_t y;
};
#define VP_WIDTH 32
#define VP_HEIGHT 16
#define DEFAULT_FONT_W 5
#define DEFAULT_FONT_H 7
#define PIXEL_SPACE 1 // space between chars in a string
/* --- PRINTF_BYTE_TO_BINARY macro's --- */
#define PRINTF_BINARY_PATTERN_INT8 "%c%c%c%c%c%c%c%c"
#define PRINTF_BYTE_TO_BINARY_INT8(i) \
(((i) & 0x80ll) ? '1' : '0'), \
(((i) & 0x40ll) ? '1' : '0'), \
(((i) & 0x20ll) ? '1' : '0'), \
(((i) & 0x10ll) ? '1' : '0'), \
(((i) & 0x08ll) ? '1' : '0'), \
(((i) & 0x04ll) ? '1' : '0'), \
(((i) & 0x02ll) ? '1' : '0'), \
(((i) & 0x01ll) ? '1' : '0')
#define PRINTF_BINARY_PATTERN_INT16 \
PRINTF_BINARY_PATTERN_INT8 PRINTF_BINARY_PATTERN_INT8
#define PRINTF_BYTE_TO_BINARY_INT16(i) \
PRINTF_BYTE_TO_BINARY_INT8((i) >> 8), PRINTF_BYTE_TO_BINARY_INT8(i)
/* --- end macros --- */
class QuarterScanMatrixPanel : public Adafruit_GFX
{
public:
MatrixPanel_I2S_DMA *display;
QuarterScanMatrixPanel(MatrixPanel_I2S_DMA &disp) : Adafruit_GFX(64, 32)
{
this->display = &disp;
size_x = size_y = 1 ;
wrap = false;
cursor_x = cursor_y = 0;
dir = 1;
loop = true;
}
VirtualCoords getCoords(int16_t x, int16_t y);
int16_t getVirtualX(int16_t x) {
VirtualCoords coords = getCoords(x, 0);
return coords.x;
}
int16_t getVirtualY(int16_t y) {
VirtualCoords coords = getCoords(0,y);
return coords.y;
}
// int16_t getVirtualY(int16_t y) {return getCoords(0,y).y;}
/** extended function to draw lines/rects/... **/
virtual uint8_t width() {return VP_WIDTH;};
virtual uint8_t height() {return VP_HEIGHT;};
virtual void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color);
virtual void drawHLine(int16_t x0, int16_t y0, int16_t w, uint16_t color);
virtual void drawVLine(int16_t x0, int16_t y0, int16_t h, uint16_t color);
virtual void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color);
virtual void drawChar(int16_t x, int16_t y, unsigned char c, uint16_t color, uint16_t bg, uint8_t size);
virtual void drawChar(int16_t x, int16_t y, unsigned char c, uint16_t color, uint16_t bg, uint8_t size_x, uint8_t size_y);
virtual void scrollChar(int16_t x, int16_t y, unsigned char c, uint16_t color, uint16_t bg, uint16_t dir, uint16_t speed);
virtual void drawString(int16_t x, int16_t y, unsigned char* c, uint16_t color, uint16_t bg);
virtual size_t write(unsigned char c); // write a character on current cursor position
virtual size_t write(const char *str); // write a character array (string) on current cursor position
virtual void setTextWrap(bool w);
virtual void setCursor (int16_t x, int16_t y);
void setTextFGColor(uint16_t color) {textFGColor = color;};
void setTextBGColor(uint16_t color) {textBGColor = color;};
void setTextSize(uint8_t x, uint8_t y) {size_x = x; size_y = y;}; // magnification, default = 1
void setScrollDir(uint8_t d = 1) { dir = (d != 1) ? 0 : 1;}; // set scroll dir default = 1
void setScroolLoop (bool b = true) { loop = b;} ; // scroll text in a loop, default true
void scrollText(const char *str, uint16_t speed, uint16_t pixels);
/**------------------------------------------**/
// equivalent methods of the matrix library so it can be just swapped out.
virtual void drawPixel(int16_t x, int16_t y, uint16_t color);
virtual void fillScreen(uint16_t color); // overwrite adafruit implementation
void clearScreen() { fillScreen(0); }
//void drawPixelRGB565(int16_t x, int16_t y, uint16_t color);
void drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b);
//void drawPixelRGB24(int16_t x, int16_t y, RGB24 color);
void drawIcon (int *ico, int16_t x, int16_t y, int16_t module_cols, int16_t module_rows);
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 showDMABuffer() { display->showDMABuffer(); }
void drawDisplayTest();
protected:
int16_t cursor_x, cursor_y; // Cursor position
uint8_t size_x, size_y; // Font size Multiplier default = 1 => 5x7 Font (5width,7Height)
uint16_t textFGColor, textBGColor;
bool wrap ; // < If set, 'wrap' text at right edge of display
uint8_t dir ; // used for scrolling text direction
bool loop ; // used for scrolling text in a loop
private:
VirtualCoords coords;
}; // end Class header
/***************************************************************************************
@brief scroll text from right to left or vice versa on current cursor position
please note, this function is not interruptable.
@param *c pointer to \0 terminated string
@param pixels number of pixels to scroll, if 0, than scroll complete text
@param speed velocity of scrolling in ms
***************************************************************************************/
void QuarterScanMatrixPanel::scrollText(const char *str,uint16_t speed, uint16_t pixels = 0) {
// first we put all columns of every char inside str into a big array of lines
// than we move through this array and draw line per line and move this line
// one position to dir
const uint8_t xSize = 6;
uint16_t len = strlen(str);
uint8_t array[len * xSize]; // size of array number of chars * width of char
//uint16_t lenArray = sizeof(array)/sizeof(array[0]);
uint16_t aPtr = 0;
//
// generate array
char c = *str;
// Serial.printf("size *str (%d), size array: (%d) \n", len, lenArray);
while (c) {
// Serial.printf("** %c ** \n", c);
// read font line per line. A line is a column inside a char
for (int8_t i = 0; i < 5; i++) {
uint8_t line = pgm_read_byte(&font[c * 5 + i]);
array[aPtr++] = line;
// Serial.printf("%d - Line " PRINTF_BINARY_PATTERN_INT8 "\n", i, PRINTF_BYTE_TO_BINARY_INT8(line) );
}
str++;
c = *str;
array[aPtr++] = 0x00; // line with 0 (space between chars)
}
array[aPtr++] = 0x00; // line with 0 (space between chars)
/*
Serial.printf("---------------------------- \n");
for (aPtr=0; aPtr < (len*xSize); aPtr++) {
Serial.printf("%d - Line " PRINTF_BINARY_PATTERN_INT8 "\n", aPtr, PRINTF_BYTE_TO_BINARY_INT8(array[aPtr]) );
}
*/
int16_t x,y,lastX, p;
lastX = (dir) ? VP_WIDTH : 0;
x = cursor_x;
y = cursor_y;
Serial.printf("X: %d, Y: %d \n", x,y);
p=0;
pixels = (pixels) ? pixels : len * xSize;
while (p <= pixels) {
// remove last pixel positions
fillRect(x,y,5,7,textBGColor);
// set new pixel position
x = (dir) ? lastX - p : lastX + p - pixels;
// iterator through our array
for (uint8_t i=0; i < (len*xSize); i++) {
uint8_t line = array[i];
//Serial.printf("%d:%d : " PRINTF_BINARY_PATTERN_INT8 "\n", x, i, PRINTF_BYTE_TO_BINARY_INT8(line) );
// read line and shift from right to left
// start with bit 0 (top of char) to 7(bottom)
for (uint8_t j=0; j < 8; j++, line>>=1) {
if (line & 1) {
// got 1, if x + i outside panel ignore pixel
if (x + i >= 0 && x + i < VP_WIDTH) {
drawPixel(x + i, y + j, textFGColor);
}
}
else {
// got 0
if (x + i >= 0 && x + i < VP_WIDTH) {
drawPixel(x + i, y + j, textBGColor);
}
} // if
} // for j
} // for i
p++;
delay(speed);
} // while
}
inline size_t QuarterScanMatrixPanel::write(const char *str) {
uint8_t x, y;
x=cursor_x;
y=cursor_y;
char c = *str;
while (c) {
//Serial.printf("%c ", c);
write(c);
str++;
c = *str;
x = x + ((DEFAULT_FONT_W + PIXEL_SPACE) * size_x);
setCursor(x,y);
}
Serial.printf("\n");
return 1;
}
inline size_t QuarterScanMatrixPanel::write(unsigned char c) {
Serial.printf("\twrite(%d, %d, %c)\n", cursor_x, cursor_y, c);
drawChar(cursor_x, cursor_y, c, textFGColor, textBGColor, size_x, size_y);
return 1;
}
void QuarterScanMatrixPanel::setTextWrap(bool w) {this->display->setTextWrap(w);}
void QuarterScanMatrixPanel::setCursor(int16_t x, int16_t y) {
cursor_x = x;
cursor_y = y;
}
/* - new for 16x32 panels - */
inline void QuarterScanMatrixPanel::drawLine(int16_t x, int16_t y, int16_t x1, int16_t y1, uint16_t color)
{
int16_t a,b;
for (a=x; a <= x1; a++) {
for (b=y; b <= y1; b++) {
drawPixel(a,b,color);
}
}
}
inline void QuarterScanMatrixPanel::drawHLine(int16_t x, int16_t y, int16_t w, uint16_t color)
{
drawLine(x,y,x+w,y, color);
}
inline void QuarterScanMatrixPanel::drawVLine(int16_t x, int16_t y, int16_t h, uint16_t color)
{
drawLine(x,y,x,y+h, color);
}
inline void QuarterScanMatrixPanel::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color)
{
for (int16_t i = x; i < x + w; i++) {
drawVLine(i, y, h, color);
}
}
void QuarterScanMatrixPanel::drawChar(int16_t x, int16_t y, unsigned char c, uint16_t color, uint16_t bg, uint8_t size)
{
drawChar(x,y,c,color, bg, size, size);
}
inline void QuarterScanMatrixPanel::scrollChar(int16_t x, int16_t y, unsigned char c, uint16_t color, uint16_t bg, uint16_t dir, uint16_t speed){
if ((x >= VP_WIDTH) ||
(y >= VP_HEIGHT) ||
((x + 6 * size_x-1) < 0) ||
((y + 8 * size_y-1) <0))
return;
setTextWrap(true);
// text wrap is only for the right end of the panel, to scroll soft out of the left of panel
// algorithm should wrap the character from left to right
// loop s = scroll-loop, scrolls char 5 pixels into dir
uint8_t lastX = x;
for (int8_t s = 0; s < 6; s++) {
// loop i : width of a character
Serial.printf("X:%d ", x);
// clear current position
fillRect(x,y,5,7,0);
x = lastX - s;
for (int8_t i = 0; i < 5; i++) {
// first line is the first vertical part of a character and 8bits long
// last bit is everytime 0
// we read 5 lines with 8 bit (5x7 char + 8bit with zeros)
// Example : char A (90deg cw)
// 01111100
// 00010010
// 00010001
// 00010010
// 01111100
uint8_t line = pgm_read_byte(&font[c * 5 + i]);
// shift from right to left bit per bit
// loop j = height of a character
// loop through a column of current character
Serial.printf("i:%d ", i);
// ignore all pixels outside panel
if (x+i >= VP_WIDTH) continue;
for (int8_t j=0; j < 8; j++, line >>= 1) {
if (line & 1) {
Serial.printf
(" ON %d", x+i);
// we read 1
if (x >= 0) {
drawPixel(x+i, y+j, color);
}
else if (x+i >= 0) {
drawPixel(x+i, y+j, color);
}
}
else if (bg != color) {
// we read 0
Serial.printf(" OFF %d", x+i);
if (x >= 0) {
drawPixel(x+i, y+j, bg);
}
else if (x+i >= 0) {
drawPixel(x+i, y+j, bg);
}
}
}
}
Serial.printf("\n");
delay(speed);
}
}
inline void QuarterScanMatrixPanel::drawChar(int16_t x, int16_t y, unsigned char c, uint16_t color, uint16_t bg, uint8_t size_x, uint8_t size_y)
{
//Serial.printf("unmapped : drawChar(%d, %d, %c) \n",x, y, c);
// note: remapping to 16x32 coordinates is done inside drawPixel() or fillRect
if ((x >= VP_WIDTH) ||
(y >= VP_HEIGHT) ||
((x + 6 * size_x-1) < 0) ||
((y + 8 * size_y-1) <0))
return;
//Serial.printf("Font-Array : %d \n", sizeof(font));
for (int8_t i = 0; i < 5; i++) {
uint8_t line = pgm_read_byte(&font[c * 5 + i]);
//Serial.printf("%d - Line " PRINTF_BINARY_PATTERN_INT8 "\n", i, PRINTF_BYTE_TO_BINARY_INT8(line) );
for (int8_t j = 0; j < 8; j++, line >>= 1) {
if (line & 1) {
if (size_x == 1 && size_y == 1)
//Serial.printf("");
drawPixel(x + i, y + j, color);
else
// remark: it's important to call function with original coordinates for x/y
fillRect(x + i * size_x, y + j * size_y, size_x, size_y,
color);
} else if (bg != color) {
if (size_x == 1 && size_y == 1)
drawPixel(x + i, y + j, bg);
else
// remark: it's important to call function with original coordinates for x/y
fillRect(x + i * size_x, y + j * size_y, size_x, size_y, bg);
}
}
}
}
inline void QuarterScanMatrixPanel::drawString(int16_t x, int16_t y, unsigned char* c, uint16_t color, uint16_t bg) {
}
inline VirtualCoords QuarterScanMatrixPanel::getCoords(int16_t x, int16_t y)
{
const int y_remap[] = { 0,1,8,9,4,5,12,13,16,17,24,25,22,23,30,31 };
if (y > VP_HEIGHT)
y = VP_HEIGHT;
if (x > VP_WIDTH)
x = VP_WIDTH;
coords.x = x + VP_WIDTH;
coords.y = y_remap[y];
return coords;
}
/* -------------------------*/
inline void QuarterScanMatrixPanel::drawPixel(int16_t x, int16_t y, uint16_t color)
{
VirtualCoords coords = getCoords(x, y);
this->display->drawPixel(coords.x, coords.y, color);
}
inline void QuarterScanMatrixPanel::fillScreen(uint16_t color) // adafruit virtual void override
{
// No need to map this.
this->display->fillScreen(color);
}
/*
inline void QuarterScanMatrixPanel::drawPixelRGB565(int16_t x, int16_t y, uint16_t color)
{
VirtualCoords coords = getCoords(x, y);
this->display->drawPixelRGB565( coords.x, coords.y, color);
}
*/
inline void QuarterScanMatrixPanel::drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b)
{
VirtualCoords coords = getCoords(x, y);
this->display->drawPixelRGB888( coords.x, coords.y, r, g, b);
}
/*
inline void QuarterScanMatrixPanel::drawPixelRGB24(int16_t x, int16_t y, RGB24 color)
{
VirtualCoords coords = getCoords(x, y);
this->display->drawPixelRGB24(coords.x, coords.y, color);
}
*/
// need to recreate this one, as it wouldn't work to just map where it starts.
inline void QuarterScanMatrixPanel::drawIcon (int *ico, int16_t x, int16_t y, int16_t module_cols, int16_t module_rows) { }
#endif

View file

@ -1,226 +0,0 @@
/*************************************************************************
* Contributor: https://github.com/mrRobot62
*
* Description:
*
* The underlying implementation of the ESP32-HUB75-MatrixPanel-I2S-DMA only
* supports output to 1/16 or 1/32 scan panels (two scan parallel scan lines)
* this is fixed and cannot be changed.
*
* However, it is possible to connect 1/4 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 (in this example, it is the 'dmaOutput' class).
*
* This is done by way of the 'QuarterScanMatrixPanel.h' class that sends
* adjusted x,y co-ordinates to the underlying ESP32-HUB75-MatrixPanel-I2S-DMA
* library's drawPixel routine.
*
* Refer to the 'getCoords' function within 'QuarterScanMatrixPanel.h'
*
**************************************************************************/
// PLEASE NOTE THIS EXAMPLE NO LONGER WORKS AS OF AUGUST 2021
// IT NEEDS TO BE UPDATED TO THE NEW WAY OF USING THE LIBRARY
// uncomment to use custom pins, then provide below
#define USE_CUSTOM_PINS
/* Pin 1,3,5,7,9,11,13,15 */
#define R1_PIN 25
#define B1_PIN 27
#define R2_PIN 14
#define B2_PIN 13
#define A_PIN 23
#define C_PIN 5
#define CLK_PIN 16
#define OE_PIN 15
/* Pin 2,6,10,12,14 */
#define G1_PIN 26
#define G2_PIN 12
#define B_PIN 19
#define D_PIN 17
#define LAT_PIN 4
#define E_PIN -1 // required for 1/32 scan panels
#include "OneQuarterScanMatrixPanel.h" // Virtual Display to re-map co-ordinates such that they draw correctly on a 32x16 1/4 Scan panel
#include <Wire.h>
/*
* Below is an is the 'legacy' way of initialising the MatrixPanel_I2S_DMA class.
* i.e. MATRIX_WIDTH and MATRIX_HEIGHT are modified by compile-time directives.
* By default the library assumes a single 64x32 pixel panel is connected.
*
* Refer to the example '2_PatternPlasma' on the new / correct way to setup this library
* for different resolutions / panel chain lengths within the sketch 'setup()'.
*
*/
MatrixPanel_I2S_DMA dmaOutput;
// Create virtual 1/2 to 1/4 scan pixel co-ordinate mapping class.
QuarterScanMatrixPanel display(dmaOutput);
#include <FastLED.h>
int time_counter = 0;
int cycles = 0;
CRGBPalette16 currentPalette;
CRGB currentColor;
CRGB ColorFromCurrentPalette(uint8_t index = 0, uint8_t brightness = 255, TBlendType blendType = LINEARBLEND) {
return ColorFromPalette(currentPalette, index, brightness, blendType);
}
typedef struct Matrix {
uint8_t x;
uint8_t y;
} Matrix;
Matrix matrix;
void testSimpleChars(uint16_t timeout) {
/** drawChar() **/
Serial.println("draw chars with drawChar()");
display.fillScreen(display.color444(0,0,0));
uint16_t myFGColor = display.color565(180,0,0);
uint16_t myBGColor = display.color565(0,50,0);
display.fillScreen(display.color444(0,0,0));
display.drawChar(0,0,'X',myFGColor, myFGColor,1);
display.drawChar(16,1,'Y',myFGColor, myBGColor,1);
display.drawChar(3,9,'Z',myFGColor, myFGColor,1);
display.drawChar(16,9,'4',display.color565(0,220,0), myBGColor,1);
delay(timeout);
}
void testSimpleCharString(uint16_t timeout) {
uint8_t x,y,w,h;
w = 6; h=8;
x = 0; y=0;
display.fillScreen(display.color444(0,0,0));
display.setTextFGColor(display.color565(0,60,180));
display.setCursor(x,y); display.write('L');
display.setCursor(x+w,y); display.write('u');
display.setCursor(x+(2*w),y); display.write('n');
display.setCursor(x+(3*w),y); display.write('a');
display.setTextFGColor(display.color565(180,60,140));
display.setCursor(x+(4*w),y); display.write('X');
delay(timeout);
}
void testTextString(uint16_t timeout) {
display.fillScreen(display.color444(0,0,0));
display.setTextFGColor(display.color565(0,60,255));
display.setCursor(0,5);
display.write("HURRA");
delay(timeout);
}
void testWrapChar(const char c, uint16_t speed, uint16_t timeout) {
display.setTextWrap(true);
for (uint8_t i = 32; i > 0; i--) {
display.fillScreen(display.color444(0,0,0));
display.setCursor(i, 5);
display.write(c);
delay(speed);
}
delay(timeout);
}
void testScrollingChar(const char c, uint16_t speed, uint16_t timeout) {
Serial.println("Scrolling Char");
uint16_t myFGColor = display.color565(180,0,0);
uint16_t myBGColor = display.color565(60,120,0);
display.fillScreen(display.color444(0,0,0));
display.setTextWrap(true);
// from right to left with wrap
display.scrollChar(31,5,c, myFGColor, myFGColor, 1, speed);
// left out with wrap
delay(500);
display.scrollChar(0,5,c, myBGColor, myBGColor, 1, speed);
delay(timeout);
}
void testScrollingText(const char *str, uint16_t speed, uint16_t timeout) {
Serial.println("Scrolling Text as loop");
// pre config
uint16_t red = display.color565(255,0,100);
uint16_t blue100 = display.color565(0,0,100);
uint16_t black = display.color565(0,0,0);
uint16_t green = display.color565(0,255,0);
uint16_t green150 = display.color565(0,150,0);
display.fillScreen(display.color565(0,0,0));
display.setCursor(31,5);
display.setScrollDir(1);
/** black background **/
display.setTextFGColor(green150);
display.scrollText("** Welcome **", speed);
display.fillScreen(black);
delay(timeout / 2) ;
/** scrolling with colored background */
display.fillRect(0,4,VP_WIDTH,8,blue100);
// scrolling, using default pixels size = length of string (not used parameter pixels)
display.setTextFGColor(red);
display.setTextBGColor(blue100);
display.scrollText(str, speed);
delay(timeout / 2) ;
// same as above but now from left to right
display.setScrollDir(0);
display.setTextFGColor(blue100);
display.setTextBGColor(red);
display.fillRect(0,4,VP_WIDTH,8,red);
display.scrollText(str, speed, 0);
delay(timeout);
display.fillScreen(black);
display.setTextFGColor(red);
}
void setup() {
Serial.begin(115200);
delay(500);
Serial.println("*****************************************************");
Serial.println(" dmaOutput 32x16 !");
Serial.println("*****************************************************");
#ifdef USE_CUSTOM_PINS
dmaOutput.begin(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 ); // setup the LED matrix
#else
display.begin(true); // init buffers
#endif
// fill the screen with 'black'
display.fillScreen(display.color444(0, 0, 0));
// Set current FastLED palette
currentPalette = RainbowColors_p;
// display.fillScreen(display.color565(0, 0, 0));
}
void loop() {
display.fillScreen(display.color444(0, 0, 0));
//testSimpleChars(1500);
//testSimpleCharString (1500);
testTextString(2000);
// length = 16 bytes without \0
//testWrapChar('A', 250, 1500);
//testScrollingChar('X', 250, 2000);
testScrollingText("Scrolling 16x32", 100, 2000);
} // end loop

View file

@ -1,78 +0,0 @@
# Using this library with 32x16 1/4 Scan Panels
## Problem
ESP32-HUB75-MatrixPanel-I2S-DMA library will not display output correctly with 1/4 scan panels such [as this](https://de.aliexpress.com/item/33017477904.html?spm=a2g0o.detail.1000023.16.1fedd556Yw52Zi&algo_pvid=4329f1c0-04d2-43d9-bdfd-7d4ee95e6b40&algo_expid=4329f1c0-04d2-43d9-bdfd-7d4ee95e6b40-52&btsid=9a8bf2b5-334b-45ea-a849-063d7461362e&ws_ab_test=searchweb0_0,searchweb201602_10,searchweb201603_60%5BAliExpress%2016x32%5D).
## Solution
It is possible to connect 1/4 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 ESP32-HUB75-MatrixPanel-I2S-DMA library (in this example, it is the 'dmaOutput' class).
Creation of a 'QuarterScanMatrixPanel.h' class which sends an adjusted x,y co-ordinates to the underlying ESP32-HUB75-MatrixPanel-I2S-DMA library's drawPixel routine, to trick the output to look pixel perfect. Refer to the 'getCoords' function within 'QuarterScanMatrixPanel.h'
## Limitations
* Only one font (glcd - standard font) is implemented. This font can't be resized.
## New functions (and adapted function) in this QuarterScanMatrixPanel class
### drawLine
`void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color)`
Parameters should be self explained. x0/y0 upper left corner, x1/y1 lower right corner
### drawHLine
`void drawHLine(int16_t x0, int16_t y0, int16_t w, uint16_t color)`
Draw a fast horizontal line with length `w`. Starting at `x0/y0`
### drawVLine
`void drawVLine(int16_t x0, int16_t y0, int16_t h, uint16_t color)`
Draw a fast vertical line with length `h` Starting at `x0/y0`
### fillRect
`void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color)`
Draw a rectangle starting at `x/y` with width `w` and height `h`in `color`
### drawChar (5x7) Standard font
`drawChar(int16_t x, int16_t y, unsigned char c, uint16_t color, uint16_t bg, uint8_t size)`
Draw a char at position x/y in `color` with a background color `bg`
`size` is ignored.
### writeChar (5x7)
`size_t write(unsigned char c)`
Write a char at current cursor position with current foreground and background color.
### writeString (5x7)
`size_t write(const char *c)`
Write a string at current cursor position with current foreground and background color.
You have to use `setCursor(x,y)` and `setTextFGColor() / setTextBGColor()`
### drawString (5x7)
`void drawString(int16_t x, int16_t y, unsigned char* c, uint16_t color, uint16_t bg)`
Draw String at position x/y wit foreground `color` and background `bg`
Example: `display.drawString(0,5,"**Welcome**",display.color565(0,60,255));`
### void setScrollDir(uint8_t d = 1)
Set scrolling direction 0=left to right, 1= right to left (default)
### scrollText
`void scrollText(const char *str, uint16_t speed, uint16_t pixels)`
Scroll text `str` into `setScrollDir`. Speed indicates how fast in ms per pixel, pixels are the number pixes which should be scrolled, if not set or 0, than pixels is calculates by size of `*str`
### drawPixel(int16_t x, int16_t y, uint16_t color)
Draw a pixel at x/y in `color`. This function is the atomic function for all above drawing functions
### clearScreen() (all pixels off (black))
Same as `fillScreen(0)`
## Example videos:
https://user-images.githubusercontent.com/949032/104838449-4aae5600-58bb-11eb-802f-a358b49a9315.mp4
https://user-images.githubusercontent.com/949032/104366906-5647f880-551a-11eb-9792-a6f8276629e6.mp4

View file

@ -1,16 +1,16 @@
{ {
"name": "ESP32 HUB75 LED MATRIX PANEL DMA Display", "name": "ESP32 HUB75 LED MATRIX PANEL DMA Display",
"keywords": "hub75, esp32, display, dma, rgb matrix", "keywords": "hub75, esp32, esp32s2, esp32s3, display, dma, rgb matrix",
"description": "An experimental Adafruit GFX compatible library for 64x32 or 64x64 LED matrix modules using the ESP32 DMA Engine for ultra-fast refresh rates, no-interrupts and therefore very low CPU usage.", "description": "An Adafruit GFX compatible library for LED matrix modules which uses DMA for ultra-fast refresh rates and therefore very low CPU usage.",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA.git" "url": "https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git"
}, },
"authors": { "authors": {
"name": "Faptastic", "name": "Faptastic",
"url": "https://github.com/mrfaptastic/" "url": "https://github.com/mrfaptastic/"
}, },
"version": "2.0.7", "version": "3.0.0",
"frameworks": "arduino", "frameworks": "arduino",
"platforms": "esp32", "platforms": "esp32",
"examples": [ "examples": [

View file

@ -1,9 +1,9 @@
name=ESP32 HUB75 LED MATRIX PANEL DMA Display name=ESP32 HUB75 LED MATRIX PANEL DMA Display
version=2.0.7 version= 3.0.0
author=Faptastic author=Faptastic
maintainer=Faptastic maintainer=Faptastic
sentence=Experimental DMA based LED Matrix HUB75 Library sentence=HUB75 LED Matrix Library for ESP32, ESP32-S2 and ESP32-S3
paragraph=An experimental Adafruit GFX compatible library for 64x32 or 64x64 LED matrix modules using the ESP32 DMA Engine for ultra-fast refresh rates, no-interrupts and therefore very low CPU usage. paragraph=An Adafruit GFX compatible library for LED matrix modules which uses DMA for ultra-fast refresh rates and therefore very low CPU usage.
category=Display category=Display
url=https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA url=https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA
architectures=esp32 architectures=esp32

View file

@ -1,246 +1,88 @@
#include <Arduino.h>
#include "ESP32-HUB75-MatrixPanel-I2S-DMA.h" #include "ESP32-HUB75-MatrixPanel-I2S-DMA.h"
static const char* TAG = "MatrixPanel";
#if defined(ESP32_SXXX)
#pragma message "Compiling for ESP32-Sx MCUs"
#elif defined(ESP32_CXXX)
#pragma message "Compiling for ESP32-Cx MCUs"
#elif CONFIG_IDF_TARGET_ESP32 || defined(ESP32)
#pragma message "Compiling for original (released 2016) 520kB SRAM ESP32."
#else
#error "Compiling for something unknown!"
#endif
// Credits: Louis Beaudoin <https://github.com/pixelmatix/SmartMatrix/tree/teensylc>
// and Sprite_TM: https://www.esp32.com/viewtopic.php?f=17&t=3188 and https://www.esp32.com/viewtopic.php?f=13&t=3256
/*
This is example code to driver a p3(2121)64*32 -style RGB LED display. These types of displays do not have memory and need to be refreshed
continuously. The display has 2 RGB inputs, 4 inputs to select the active line, a pixel clock input, a latch enable input and an output-enable
input. The display can be seen as 2 64x16 displays consisting of the upper half and the lower half of the display. Each half has a separate
RGB pixel input, the rest of the inputs are shared.
Each display half can only show one line of RGB pixels at a time: to do this, the RGB data for the line is input by setting the RGB input pins
to the desired value for the first pixel, giving the display a clock pulse, setting the RGB input pins to the desired value for the second pixel,
giving a clock pulse, etc. Do this 64 times to clock in an entire row. The pixels will not be displayed yet: until the latch input is made high,
the display will still send out the previously clocked in line. Pulsing the latch input high will replace the displayed data with the data just
clocked in.
The 4 line select inputs select where the currently active line is displayed: when provided with a binary number (0-15), the latched pixel data
will immediately appear on this line. Note: While clocking in data for a line, the *previous* line is still displayed, and these lines should
be set to the value to reflect the position the *previous* line is supposed to be on.
Finally, the screen has an OE input, which is used to disable the LEDs when latching new data and changing the state of the line select inputs:
doing so hides any artefacts that appear at this time. The OE line is also used to dim the display by only turning it on for a limited time every
line.
All in all, an image can be displayed by 'scanning' the display, say, 100 times per second. The slowness of the human eye hides the fact that
only one line is showed at a time, and the display looks like every pixel is driven at the same time.
Now, the RGB inputs for these types of displays are digital, meaning each red, green and blue subpixel can only be on or off. This leads to a
color palette of 8 pixels, not enough to display nice pictures. To get around this, we use binary code modulation.
Binary code modulation is somewhat like PWM, but easier to implement in our case. First, we define the time we would refresh the display without
binary code modulation as the 'frame time'. For, say, a four-bit binary code modulation, the frame time is divided into 15 ticks of equal length.
We also define 4 subframes (0 to 3), defining which LEDs are on and which LEDs are off during that subframe. (Subframes are the same as a
normal frame in non-binary-coded-modulation mode, but are showed faster.) From our (non-monochrome) input image, we take the (8-bit: bit 7
to bit 0) RGB pixel values. If the pixel values have bit 7 set, we turn the corresponding LED on in subframe 3. If they have bit 6 set,
we turn on the corresponding LED in subframe 2, if bit 5 is set subframe 1, if bit 4 is set in subframe 0.
Now, in order to (on average within a frame) turn a LED on for the time specified in the pixel value in the input data, we need to weigh the
subframes. We have 15 pixels: if we show subframe 3 for 8 of them, subframe 2 for 4 of them, subframe 1 for 2 of them and subframe 1 for 1 of
them, this 'automatically' happens. (We also distribute the subframes evenly over the ticks, which reduces flicker.)
In this code, we use the I2S peripheral in parallel mode to achieve this. Essentially, first we allocate memory for all subframes. This memory
contains a sequence of all the signals (2xRGB, line select, latch enable, output enable) that need to be sent to the display for that subframe.
Then we ask the I2S-parallel driver to set up a DMA chain so the subframes are sent out in a sequence that satisfies the requirement that
subframe x has to be sent out for (2^x) ticks. Finally, we fill the subframes with image data.
We use a front buffer/back buffer technique here to make sure the display is refreshed in one go and drawing artefacts do not reach the display.
In practice, for small displays this is not really necessarily.
*/
// macro's to calculate sizes of a single buffer (double buffer takes twice as this)
#define rowBitStructBuffSize sizeof(ESP32_I2S_DMA_STORAGE_TYPE) * (PIXELS_PER_ROW + CLKS_DURING_LATCH) * PIXEL_COLOR_DEPTH_BITS
#define frameStructBuffSize ROWS_PER_FRAME * rowBitStructBuffSize
/* this replicates same function in rowBitStruct, but due to induced inlining it might be MUCH faster when used in tight loops /* this replicates same function in rowBitStruct, but due to induced inlining it might be MUCH faster when used in tight loops
* while method from struct could be flushed out of instruction cache between loop cycles * while method from struct could be flushed out of instruction cache between loop cycles
* do NOT forget about buff_id param if using this * do NOT forget about buff_id param if using this
*
* faptastic note oct22: struct call is not inlined... commenting out this additional compile declaration
*/ */
#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]->color_depth)]) //#define getRowDataPtr(row, _dpth, buff_id) &(dma_buff.rowBits[row]->data[_dpth * dma_buff.rowBits[row]->width + buff_id*(dma_buff.rowBits[row]->width * dma_buff.rowBits[row]->color_depth)])
bool MatrixPanel_I2S_DMA::allocateDMAmemory() bool MatrixPanel_I2S_DMA::allocateDMAmemory()
{ {
/***
* Step 1: Look at the overall DMA capable memory for the DMA FRAMEBUFFER data only (not the DMA linked list descriptors yet)
* and do some pre-checks.
*/
int _num_frame_buffers = (m_cfg.double_buff) ? 2:1;
size_t _frame_buffer_memory_required = frameStructBuffSize * _num_frame_buffers;
size_t _dma_linked_list_memory_required = 0;
size_t _total_dma_capable_memory_reserved = 0;
// 1. Calculate the amount of DMA capable memory that's actually available // Alright, theoretically we should be OK, so let us do this, so
#if SERIAL_DEBUG // lets allocate a chunk of memory for each row (a row could span multiple panels if chaining is in place)
Serial.printf_P(PSTR("Panel Width: %d pixels.\r\n"), PIXELS_PER_ROW);
Serial.printf_P(PSTR("Panel Height: %d pixels.\r\n"), m_cfg.mx_height);
if (m_cfg.double_buff) {
Serial.println(F("DOUBLE FRAME BUFFERS / DOUBLE BUFFERING IS ENABLED. DOUBLE THE RAM REQUIRED!"));
}
Serial.println(F("DMA memory blocks available before any malloc's: "));
heap_caps_print_heap_info(MALLOC_CAP_DMA);
Serial.println(F("******************************************************************"));
Serial.printf_P(PSTR("We're going to need %d bytes of SRAM just for the frame buffer(s).\r\n"), _frame_buffer_memory_required);
Serial.printf_P(PSTR("The total amount of DMA capable SRAM memory is %d bytes.\r\n"), heap_caps_get_free_size(MALLOC_CAP_DMA));
Serial.printf_P(PSTR("Largest DMA capable SRAM memory block is %d bytes.\r\n"), heap_caps_get_largest_free_block(MALLOC_CAP_DMA));
Serial.println(F("******************************************************************"));
#endif
// Can we potentially fit the framebuffer into the DMA capable memory that's available?
if ( heap_caps_get_free_size(MALLOC_CAP_DMA) < _frame_buffer_memory_required ) {
#if SERIAL_DEBUG
Serial.printf_P(PSTR("######### Insufficient memory for requested resolution. Reduce MATRIX_COLOR_DEPTH and try again.\r\n\tAdditional %d bytes of memory required.\r\n\r\n"), (_frame_buffer_memory_required-heap_caps_get_free_size(MALLOC_CAP_DMA)) );
#endif
return false;
}
// Alright, theoretically we should be OK, so let us do this, so
// lets allocate a chunk of memory for each row (a row could span multiple panels if chaining is in place)
dma_buff.rowBits.reserve(ROWS_PER_FRAME); dma_buff.rowBits.reserve(ROWS_PER_FRAME);
// iterate through number of rows // iterate through number of rows
for (int malloc_num =0; malloc_num < ROWS_PER_FRAME; ++malloc_num) for (int malloc_num =0; malloc_num < ROWS_PER_FRAME; ++malloc_num)
{ {
auto ptr = std::make_shared<rowBitStruct>(PIXELS_PER_ROW, PIXEL_COLOR_DEPTH_BITS, m_cfg.double_buff); auto ptr = std::make_shared<rowBitStruct>(PIXELS_PER_ROW, PIXEL_COLOUR_DEPTH_BITS, m_cfg.double_buff);
if (ptr->data == nullptr){ if (ptr->data == nullptr)
#if SERIAL_DEBUG {
Serial.printf_P(PSTR("ERROR: Couldn't malloc rowBitStruct %d! Critical fail.\r\n"), malloc_num); ESP_LOGE(TAG, "ERROR: Couldn't malloc rowBitStruct %d! Critical fail.\r\n", malloc_num);
#endif return false;
return false; // TODO: should we release all previous rowBitStructs here???
// TODO: should we release all previous rowBitStructs here???
}
dma_buff.rowBits.emplace_back(ptr); // save new rowBitStruct into rows vector
++dma_buff.rows;
#if SERIAL_DEBUG
Serial.printf_P(PSTR("Malloc'ing %d bytes of memory @ address %ud for frame row %d.\r\n"), ptr->size()*_num_frame_buffers, (unsigned int)ptr->getDataPtr(), malloc_num);
#endif
}
_total_dma_capable_memory_reserved += _frame_buffer_memory_required;
/***
* Step 2: Calculate the amount of memory required for the DMA engine's linked list descriptors.
* Credit to SmartMatrix for this stuff.
*/
// Calculate what colour depth is actually possible based on memory available vs. required DMA linked-list descriptors.
// aka. Calculate the lowest LSBMSB_TRANSITION_BIT value that will fit in memory
int numDMAdescriptorsPerRow = 0;
lsbMsbTransitionBit = 0;
while(1) {
numDMAdescriptorsPerRow = 1;
for(int i=lsbMsbTransitionBit + 1; i<PIXEL_COLOR_DEPTH_BITS; i++) {
numDMAdescriptorsPerRow += (1<<(i - lsbMsbTransitionBit - 1));
} }
size_t ramrequired = numDMAdescriptorsPerRow * ROWS_PER_FRAME * _num_frame_buffers * sizeof(lldesc_t); dma_buff.rowBits.emplace_back(ptr); // save new rowBitStruct into rows vector
size_t largestblockfree = heap_caps_get_largest_free_block(MALLOC_CAP_DMA); ++dma_buff.rows;
#if SERIAL_DEBUG
Serial.printf_P(PSTR("lsbMsbTransitionBit of %d with %d DMA descriptors per frame row, requires %d bytes RAM, %d available, leaving %d free: \r\n"), lsbMsbTransitionBit, numDMAdescriptorsPerRow, ramrequired, largestblockfree, largestblockfree - ramrequired);
#endif
if(ramrequired < largestblockfree)
break;
if(lsbMsbTransitionBit < PIXEL_COLOR_DEPTH_BITS - 1)
lsbMsbTransitionBit++;
else
break;
} }
#if SERIAL_DEBUG
Serial.printf_P(PSTR("Raised lsbMsbTransitionBit to %d/%d to fit in remaining RAM\r\n"), lsbMsbTransitionBit, PIXEL_COLOR_DEPTH_BITS - 1);
#endif
#ifndef IGNORE_REFRESH_RATE
// calculate the lowest LSBMSB_TRANSITION_BIT value that will fit in memory that will meet or exceed the configured refresh rate // calculate the lowest LSBMSB_TRANSITION_BIT value that will fit in memory that will meet or exceed the configured refresh rate
#if !defined(FORCE_COLOUR_DEPTH)
while(1) { while(1) {
int psPerClock = 1000000000000UL/m_cfg.i2sspeed; 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;
// add time to shift out LSBs + LSB-MSB transition bit - this ignores fractions... // add time to shift out LSBs + LSB-MSB transition bit - this ignores fractions...
int nsPerRow = PIXEL_COLOR_DEPTH_BITS * nsPerLatch; int nsPerRow = PIXEL_COLOUR_DEPTH_BITS * nsPerLatch;
// add time to shift out MSBs // add time to shift out MSBs
for(int i=lsbMsbTransitionBit + 1; i<PIXEL_COLOR_DEPTH_BITS; i++) for(int i=lsbMsbTransitionBit + 1; i<PIXEL_COLOUR_DEPTH_BITS; i++)
nsPerRow += (1<<(i - lsbMsbTransitionBit - 1)) * (PIXEL_COLOR_DEPTH_BITS - i) * nsPerLatch; nsPerRow += (1<<(i - lsbMsbTransitionBit - 1)) * (PIXEL_COLOUR_DEPTH_BITS - i) * nsPerLatch;
int nsPerFrame = nsPerRow * ROWS_PER_FRAME; int nsPerFrame = nsPerRow * ROWS_PER_FRAME;
int actualRefreshRate = 1000000000UL/(nsPerFrame); int actualRefreshRate = 1000000000UL/(nsPerFrame);
calculated_refresh_rate = actualRefreshRate; calculated_refresh_rate = actualRefreshRate;
#if SERIAL_DEBUG ESP_LOGW(TAG, "lsbMsbTransitionBit of %d gives %d Hz refresh rate.", lsbMsbTransitionBit, actualRefreshRate);
Serial.printf_P(PSTR("lsbMsbTransitionBit of %d gives %d Hz refresh: \r\n"), lsbMsbTransitionBit, actualRefreshRate);
#endif
if (actualRefreshRate > m_cfg.min_refresh_rate) if (actualRefreshRate > m_cfg.min_refresh_rate)
break; break;
if(lsbMsbTransitionBit < PIXEL_COLOR_DEPTH_BITS - 1) if(lsbMsbTransitionBit < PIXEL_COLOUR_DEPTH_BITS - 1)
lsbMsbTransitionBit++; lsbMsbTransitionBit++;
else else
break; break;
} }
#endif
#if SERIAL_DEBUG
Serial.printf_P(PSTR("Raised lsbMsbTransitionBit to %d/%d to meet minimum refresh rate\r\n"), lsbMsbTransitionBit, PIXEL_COLOR_DEPTH_BITS - 1);
#endif
#endif
/*** /***
* Step 2a: lsbMsbTransition bit is now finalised - recalculate the DMA descriptor count required, which is used for * 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. * memory allocation of the DMA linked list memory structure.
*/ */
numDMAdescriptorsPerRow = 1; int numDMAdescriptorsPerRow = 1;
for(int i=lsbMsbTransitionBit + 1; i<PIXEL_COLOR_DEPTH_BITS; i++) { for(int i=lsbMsbTransitionBit + 1; i<PIXEL_COLOUR_DEPTH_BITS; i++) {
numDMAdescriptorsPerRow += (1<<(i - lsbMsbTransitionBit - 1)); numDMAdescriptorsPerRow += (1<<(i - lsbMsbTransitionBit - 1));
} }
#if SERIAL_DEBUG
Serial.printf_P(PSTR("Recalculated number of DMA descriptors per row: %d\n"), numDMAdescriptorsPerRow); ESP_LOGI(TAG, "Recalculated number of DMA descriptors per row: %d", numDMAdescriptorsPerRow);
#endif
// Refer to 'DMA_LL_PAYLOAD_SPLIT' code in configureDMA() below to understand why this exists. // 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. // numDMAdescriptorsPerRow is also used to calculate descount which is super important in i2s_parallel_config_t SoC DMA setup.
if ( rowBitStructBuffSize > DMA_MAX ) { if ( dma_buff.rowBits[0]->size() > DMA_MAX )
{
#if SERIAL_DEBUG ESP_LOGW(TAG, "rowColorDepthStruct struct is too large, split DMA payload required. Adding %d DMA descriptors\n", PIXEL_COLOUR_DEPTH_BITS-1);
Serial.printf_P(PSTR("rowColorDepthStruct struct is too large, split DMA payload required. Adding %d DMA descriptors\n"), PIXEL_COLOR_DEPTH_BITS-1);
#endif
numDMAdescriptorsPerRow += PIXEL_COLOR_DEPTH_BITS-1; numDMAdescriptorsPerRow += PIXEL_COLOUR_DEPTH_BITS-1;
// Note: If numDMAdescriptorsPerRow is even just one descriptor too large, DMA linked list will not correctly loop. // Note: If numDMAdescriptorsPerRow is even just one descriptor too large, DMA linked list will not correctly loop.
} }
@ -249,55 +91,13 @@ bool MatrixPanel_I2S_DMA::allocateDMAmemory()
* Step 3: Allocate memory for DMA linked list, linking up each framebuffer row in sequence for GPIO output. * Step 3: Allocate memory for DMA linked list, linking up each framebuffer row in sequence for GPIO output.
*/ */
_dma_linked_list_memory_required = numDMAdescriptorsPerRow * ROWS_PER_FRAME * _num_frame_buffers * sizeof(lldesc_t);
#if SERIAL_DEBUG
Serial.printf_P(PSTR("Descriptors for lsbMsbTransitionBit of %d/%d with %d frame rows require %d bytes of DMA RAM with %d numDMAdescriptorsPerRow.\r\n"), lsbMsbTransitionBit, PIXEL_COLOR_DEPTH_BITS - 1, ROWS_PER_FRAME, _dma_linked_list_memory_required, numDMAdescriptorsPerRow);
#endif
_total_dma_capable_memory_reserved += _dma_linked_list_memory_required;
// Do a final check to see if we have enough space for the additional DMA linked list descriptors that will be required to link it all up!
if(_dma_linked_list_memory_required > heap_caps_get_largest_free_block(MALLOC_CAP_DMA)) {
#if SERIAL_DEBUG
Serial.println(F("ERROR: Not enough SRAM left over for DMA linked-list descriptor memory reservation! Oh so close!\r\n"));
#endif
return false;
} // linked list descriptors memory check
// malloc the DMA linked list descriptors that i2s_parallel will need // malloc the DMA linked list descriptors that i2s_parallel will need
desccount = numDMAdescriptorsPerRow * ROWS_PER_FRAME; desccount = numDMAdescriptorsPerRow * ROWS_PER_FRAME;
//lldesc_t * dmadesc_a = (lldesc_t *)heap_caps_malloc(desccount * sizeof(lldesc_t), MALLOC_CAP_DMA); if (m_cfg.double_buff)
dmadesc_a = (lldesc_t *)heap_caps_malloc(desccount * sizeof(lldesc_t), MALLOC_CAP_DMA); dma_bus.enable_double_dma_desc();
assert("Can't allocate descriptor framebuffer a");
if(!dmadesc_a) { dma_bus.allocate_dma_desc_memory(desccount);
#if SERIAL_DEBUG
Serial.println(F("ERROR: Could not malloc descriptor framebuffer a."));
#endif
return false;
}
if (m_cfg.double_buff) // reserve space for second framebuffer linked list
{
//lldesc_t * dmadesc_b = (lldesc_t *)heap_caps_malloc(desccount * sizeof(lldesc_t), MALLOC_CAP_DMA);
dmadesc_b = (lldesc_t *)heap_caps_malloc(desccount * sizeof(lldesc_t), MALLOC_CAP_DMA);
assert("Could not malloc descriptor framebuffer b.");
if(!dmadesc_b) {
#if SERIAL_DEBUG
Serial.println(F("ERROR: Could not malloc descriptor framebuffer b."));
#endif
return false;
}
}
#if SERIAL_DEBUG
Serial.println(F("*** ESP32-HUB75-MatrixPanel-I2S-DMA: Memory Allocations Complete ***"));
Serial.printf_P(PSTR("Total memory that was reserved: %d kB.\r\n"), _total_dma_capable_memory_reserved/1024);
Serial.printf_P(PSTR("... of which was used for the DMA Linked List(s): %d kB.\r\n"), _dma_linked_list_memory_required/1024);
Serial.printf_P(PSTR("Heap Memory Available: %d bytes total. Largest free block: %d bytes.\r\n"), heap_caps_get_free_size(0), heap_caps_get_largest_free_block(0));
Serial.printf_P(PSTR("General RAM Available: %d bytes total. Largest free block: %d bytes.\r\n"), heap_caps_get_free_size(MALLOC_CAP_DEFAULT), heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT));
#endif
// Just os we know // Just os we know
initialized = true; initialized = true;
@ -310,57 +110,45 @@ bool MatrixPanel_I2S_DMA::allocateDMAmemory()
void MatrixPanel_I2S_DMA::configureDMA(const HUB75_I2S_CFG& _cfg) void MatrixPanel_I2S_DMA::configureDMA(const HUB75_I2S_CFG& _cfg)
{ {
#if SERIAL_DEBUG
Serial.println(F("configureDMA(): Starting configuration of DMA engine.\r\n"));
#endif
// lldesc_t *previous_dmadesc_a = 0;
lldesc_t *previous_dmadesc_a = 0; // lldesc_t *previous_dmadesc_b = 0;
lldesc_t *previous_dmadesc_b = 0;
int current_dmadescriptor_offset = 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 color_depth. // 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_color_depths = PIXEL_COLOR_DEPTH_BITS; int num_dma_payload_colour_depths = PIXEL_COLOUR_DEPTH_BITS;
if ( rowBitStructBuffSize > DMA_MAX ) { if ( dma_buff.rowBits[0]->size() > DMA_MAX ) {
num_dma_payload_color_depths = 1; num_dma_payload_colour_depths = 1;
} }
// Fill DMA linked lists for both frames (as in, halves of the HUB75 panel) and if double buffering is enabled, link it up for both buffers. // Fill DMA linked lists for both frames (as in, halves of the HUB75 panel) and if double buffering is enabled, link it up for both buffers.
for(int row = 0; row < ROWS_PER_FRAME; row++) { for(int row = 0; row < ROWS_PER_FRAME; row++)
{
#if SERIAL_DEBUG // 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
Serial.printf_P(PSTR( "Row %d DMA payload of %d bytes. DMA_MAX is %d.\n"), row, dma_buff.rowBits[row]->size(), DMA_MAX);
#endif
// first set of data is LSB through MSB, single pass (IF TOTAL SIZE < DMA_MAX) - all color 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 // 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_color_depths)); //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]; // previous_dmadesc_a = &dmadesc_a[current_dmadescriptor_offset];
if (m_cfg.double_buff) { dma_bus.create_dma_desc_link(dma_buff.rowBits[row]->getDataPtr(0, 0), dma_buff.rowBits[row]->size(num_dma_payload_colour_depths), false);
link_dma_desc(&dmadesc_b[current_dmadescriptor_offset], previous_dmadesc_b, dma_buff.rowBits[row]->getDataPtr(0, 1), dma_buff.rowBits[row]->size(num_dma_payload_color_depths));
previous_dmadesc_b = &dmadesc_b[current_dmadescriptor_offset]; } if (m_cfg.double_buff)
{
dma_bus.create_dma_desc_link(dma_buff.rowBits[row]->getDataPtr(0, 1), dma_buff.rowBits[row]->size(num_dma_payload_colour_depths), true);
}
current_dmadescriptor_offset++; current_dmadescriptor_offset++;
// If the number of pixels per row is too great for the size of a DMA payload, so we need to split what we were going to send above. // If 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 ( rowBitStructBuffSize > DMA_MAX ) if ( dma_buff.rowBits[0]->size() > DMA_MAX )
{ {
#if SERIAL_DEBUG
Serial.printf_P(PSTR("Splitting DMA payload for %d color depths into %d byte payloads.\r\n"), PIXEL_COLOR_DEPTH_BITS-1, rowBitStructBuffSize/PIXEL_COLOR_DEPTH_BITS );
#endif
for (int cd = 1; cd < PIXEL_COLOR_DEPTH_BITS; cd++) for (int cd = 1; cd < PIXEL_COLOUR_DEPTH_BITS; cd++)
{ {
// first set of data is LSB through MSB, single pass - all color bits are displayed once, which takes care of everything below and including LSBMSB_TRANSITION_BIT dma_bus.create_dma_desc_link(dma_buff.rowBits[row]->getDataPtr(cd, 0), dma_buff.rowBits[row]->size(num_dma_payload_colour_depths), false);
// TODO: 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(cd, 0), dma_buff.rowBits[row]->size(num_dma_payload_color_depths) );
previous_dmadesc_a = &dmadesc_a[current_dmadescriptor_offset];
if (m_cfg.double_buff) { if (m_cfg.double_buff) {
link_dma_desc(&dmadesc_b[current_dmadescriptor_offset], previous_dmadesc_b, dma_buff.rowBits[row]->getDataPtr(cd, 1), dma_buff.rowBits[row]->size(num_dma_payload_color_depths)); dma_bus.create_dma_desc_link(dma_buff.rowBits[row]->getDataPtr(cd, 1), dma_buff.rowBits[row]->size(num_dma_payload_colour_depths), true);
previous_dmadesc_b = &dmadesc_b[current_dmadescriptor_offset]; } }
current_dmadescriptor_offset++; current_dmadescriptor_offset++;
@ -368,77 +156,64 @@ void MatrixPanel_I2S_DMA::configureDMA(const HUB75_I2S_CFG& _cfg)
} // row depth struct } // row depth struct
for(int i=lsbMsbTransitionBit + 1; i<PIXEL_COLOR_DEPTH_BITS; i++) for(int i=lsbMsbTransitionBit + 1; i<PIXEL_COLOUR_DEPTH_BITS; i++)
{ {
// binary time division setup: we need 2 of bit (LSBMSB_TRANSITION_BIT + 1) four of (LSBMSB_TRANSITION_BIT + 2), etc // 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) // 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 // we need 2^(i - LSBMSB_TRANSITION_BIT - 1) == 1 << (i - LSBMSB_TRANSITION_BIT - 1) passes from i to MSB
#if SERIAL_DEBUG
Serial.printf_P(PSTR("configureDMA(): DMA Loops for PIXEL_COLOR_DEPTH_BITS %d is: %d.\r\n"), i, (1<<(i - lsbMsbTransitionBit - 1)));
#endif
for(int k=0; k < (1<<(i - lsbMsbTransitionBit - 1)); k++) for(int k=0; k < (1<<(i - lsbMsbTransitionBit - 1)); k++)
{ {
link_dma_desc(&dmadesc_a[current_dmadescriptor_offset], previous_dmadesc_a, dma_buff.rowBits[row]->getDataPtr(i, 0), dma_buff.rowBits[row]->size(PIXEL_COLOR_DEPTH_BITS - i) ); dma_bus.create_dma_desc_link(dma_buff.rowBits[row]->getDataPtr(i, 0), dma_buff.rowBits[row]->size(PIXEL_COLOUR_DEPTH_BITS - i), false);
previous_dmadesc_a = &dmadesc_a[current_dmadescriptor_offset];
if (m_cfg.double_buff) { if (m_cfg.double_buff) {
link_dma_desc(&dmadesc_b[current_dmadescriptor_offset], previous_dmadesc_b, dma_buff.rowBits[row]->getDataPtr(i, 1), dma_buff.rowBits[row]->size(PIXEL_COLOR_DEPTH_BITS - i) ); dma_bus.create_dma_desc_link(dma_buff.rowBits[row]->getDataPtr(i, 1), dma_buff.rowBits[row]->size(PIXEL_COLOUR_DEPTH_BITS - i), true );
previous_dmadesc_b = &dmadesc_b[current_dmadescriptor_offset];
} }
current_dmadescriptor_offset++; current_dmadescriptor_offset++;
} // end color depth ^ 2 linked list } // end colour depth ^ 2 linked list
} // end color depth loop } // end colour depth loop
} // end frame rows } // end frame rows
#if SERIAL_DEBUG ESP_LOGI(TAG, "%d DMA descriptors linked to buffer data.");
Serial.printf_P(PSTR("configureDMA(): Configured LL structure. %d DMA Linked List descriptors populated.\r\n"), current_dmadescriptor_offset);
//
// Setup DMA and Output to GPIO
//
auto bus_cfg = dma_bus.config(); // バス設定用の構造体を取得します。
//bus_cfg.i2s_port = I2S_NUM_0; // 使用するI2Sポートを選択 (I2S_NUM_0 or I2S_NUM_1) (ESP32のI2S LCDモードを使用します)
bus_cfg.bus_freq = _cfg.i2sspeed;
bus_cfg.pin_wr = m_cfg.gpio.clk; // WR を接続しているピン番号
if ( desccount != current_dmadescriptor_offset) bus_cfg.pin_d0 = m_cfg.gpio.r1;
{ bus_cfg.pin_d1 = m_cfg.gpio.g1;
Serial.printf_P(PSTR("configureDMA(): ERROR! Expected descriptor count of %d != actual DMA descriptors of %d!\r\n"), desccount, current_dmadescriptor_offset); bus_cfg.pin_d2 = m_cfg.gpio.b1;
} bus_cfg.pin_d3 = m_cfg.gpio.r2;
#endif bus_cfg.pin_d4 = m_cfg.gpio.g2;
bus_cfg.pin_d5 = m_cfg.gpio.b2;
bus_cfg.pin_d6 = m_cfg.gpio.lat;
bus_cfg.pin_d7 = m_cfg.gpio.oe;
bus_cfg.pin_d8 = m_cfg.gpio.a;
bus_cfg.pin_d9 = m_cfg.gpio.b;
bus_cfg.pin_d10 = m_cfg.gpio.c;
bus_cfg.pin_d11 = m_cfg.gpio.d;
bus_cfg.pin_d12 = m_cfg.gpio.e;
bus_cfg.pin_d13 = -1;
bus_cfg.pin_d14 = -1;
bus_cfg.pin_d15 = -1;
//End markers for DMA LL dma_bus.config(bus_cfg);
dmadesc_a[desccount-1].eof = 1;
dmadesc_a[desccount-1].qe.stqe_next=(lldesc_t*)&dmadesc_a[0];
if (m_cfg.double_buff) { dma_bus.init();
dmadesc_b[desccount-1].eof = 1;
dmadesc_b[desccount-1].qe.stqe_next=(lldesc_t*)&dmadesc_b[0];
} else {
dmadesc_b = dmadesc_a; // link to same 'a' buffer
}
#if SERIAL_DEBUG dma_bus.dma_transfer_start();
Serial.println(F("Performing I2S setup:"));
#endif
i2s_parallel_config_t dma_cfg = {
.gpio_bus={_cfg.gpio.r1, _cfg.gpio.g1, _cfg.gpio.b1, _cfg.gpio.r2, _cfg.gpio.g2, _cfg.gpio.b2, _cfg.gpio.lat, _cfg.gpio.oe, _cfg.gpio.a, _cfg.gpio.b, _cfg.gpio.c, _cfg.gpio.d, _cfg.gpio.e, -1, -1, -1},
.gpio_clk=_cfg.gpio.clk,
.sample_rate=_cfg.i2sspeed,
.sample_width=ESP32_I2S_DMA_MODE,
.desccount_a=desccount,
.lldesc_a=dmadesc_a,
.desccount_b=desccount,
.lldesc_b=dmadesc_b,
.clkphase=_cfg.clkphase,
.int_ena_out_eof=_cfg.double_buff
};
// Setup I2S
i2s_parallel_driver_install(ESP32_I2S_DEVICE, &dma_cfg);
i2s_parallel_send_dma(ESP32_I2S_DEVICE, &dmadesc_a[0]);
#if SERIAL_DEBUG //i2s_parallel_send_dma(ESP32_I2S_DEVICE, &dmadesc_a[0]);
Serial.println(F("configureDMA(): DMA setup completed on ESP32_I2S_DEVICE.")); ESP_LOGI(TAG, "DMA setup completed");
#endif
} // end initMatrixDMABuff } // end initMatrixDMABuff
@ -462,12 +237,7 @@ void MatrixPanel_I2S_DMA::configureDMA(const HUB75_I2S_CFG& _cfg)
*/ */
void IRAM_ATTR MatrixPanel_I2S_DMA::updateMatrixDMABuffer(int16_t x_coord, int16_t y_coord, uint8_t red, uint8_t green, uint8_t blue) void IRAM_ATTR MatrixPanel_I2S_DMA::updateMatrixDMABuffer(int16_t x_coord, int16_t y_coord, uint8_t red, uint8_t green, uint8_t blue)
{ {
if ( !initialized ) { if ( !initialized ) return;
#if SERIAL_DEBUG
Serial.println(F("Cannot updateMatrixDMABuffer as setup failed!"));
#endif
return;
}
/* 1) Check that the co-ordinates are within range, or it'll break everything big time. /* 1) Check that the co-ordinates are within range, or it'll break everything big time.
* Valid co-ordinates are from 0 to (MATRIX_XXXX-1) * Valid co-ordinates are from 0 to (MATRIX_XXXX-1)
@ -478,7 +248,7 @@ void IRAM_ATTR MatrixPanel_I2S_DMA::updateMatrixDMABuffer(int16_t x_coord, int16
/* LED Brightness Compensation. Because if we do a basic "red & mask" for example, /* LED Brightness Compensation. Because if we do a basic "red & mask" for example,
* we'll NEVER send the dimmest possible colour, due to binary skew. * we'll NEVER send the dimmest possible colour, due to binary skew.
* i.e. It's almost impossible for color_depth_idx of 0 to be sent out to the MATRIX unless the 'value' of a color is exactly '1' * i.e. It's almost impossible for colour_depth_idx of 0 to be sent out to the MATRIX unless the 'value' of a color is exactly '1'
* https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/ * https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/
*/ */
#ifndef NO_CIE1931 #ifndef NO_CIE1931
@ -499,7 +269,7 @@ void IRAM_ATTR MatrixPanel_I2S_DMA::updateMatrixDMABuffer(int16_t x_coord, int16
* data. * data.
*/ */
#ifndef ESP32_SXXX #if defined (ESP32_THE_ORIG)
// We need to update the correct uint16_t in the rowBitStruct array, that gets sent out in parallel // 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 // 16 bit parallel mode - Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering
// Irrelevant for ESP32-S2 the way the FIFO ordering works is different - refer to page 679 of S2 technical reference manual // Irrelevant for ESP32-S2 the way the FIFO ordering works is different - refer to page 679 of S2 technical reference manual
@ -507,23 +277,23 @@ void IRAM_ATTR MatrixPanel_I2S_DMA::updateMatrixDMABuffer(int16_t x_coord, int16
#endif #endif
uint16_t _colorbitclear = BITMASK_RGB1_CLEAR, _colorbitoffset = 0; uint16_t _colourbitclear = BITMASK_RGB1_CLEAR, _colourbitoffset = 0;
if (y_coord >= ROWS_PER_FRAME){ // if we are drawing to the bottom part of the panel if (y_coord >= ROWS_PER_FRAME){ // if we are drawing to the bottom part of the panel
_colorbitoffset = BITS_RGB2_OFFSET; _colourbitoffset = BITS_RGB2_OFFSET;
_colorbitclear = BITMASK_RGB2_CLEAR; _colourbitclear = BITMASK_RGB2_CLEAR;
y_coord -= ROWS_PER_FRAME; y_coord -= ROWS_PER_FRAME;
} }
// Iterating through colour depth bits, which we assume are 8 bits per RGB subpixel (24bpp) // Iterating through colour depth bits, which we assume are 8 bits per RGB subpixel (24bpp)
uint8_t color_depth_idx = PIXEL_COLOR_DEPTH_BITS; uint8_t colour_depth_idx = PIXEL_COLOUR_DEPTH_BITS;
do { do {
--color_depth_idx; --colour_depth_idx;
// uint8_t mask = (1 << (color_depth_idx COLOR_DEPTH_LESS_THAN_8BIT_ADJUST)); // expect 24 bit color (8 bits per RGB subpixel) // uint8_t mask = (1 << (colour_depth_idx COLOR_DEPTH_LESS_THAN_8BIT_ADJUST)); // expect 24 bit colour (8 bits per RGB subpixel)
#if PIXEL_COLOR_DEPTH_BITS < 8 #if PIXEL_COLOUR_DEPTH_BITS < 8
uint8_t mask = (1 << (color_depth_idx+MASK_OFFSET)); // expect 24 bit color (8 bits per RGB subpixel) uint8_t mask = (1 << (colour_depth_idx+MASK_OFFSET)); // expect 24 bit colour (8 bits per RGB subpixel)
#else #else
uint8_t mask = (1 << (color_depth_idx)); // expect 24 bit color (8 bits per RGB subpixel) uint8_t mask = (1 << (colour_depth_idx)); // expect 24 bit color (8 bits per RGB subpixel)
#endif #endif
uint16_t RGB_output_bits = 0; uint16_t RGB_output_bits = 0;
@ -534,19 +304,20 @@ void IRAM_ATTR MatrixPanel_I2S_DMA::updateMatrixDMABuffer(int16_t x_coord, int16
RGB_output_bits |= (bool)(green & mask); // -BG RGB_output_bits |= (bool)(green & mask); // -BG
RGB_output_bits <<= 1; RGB_output_bits <<= 1;
RGB_output_bits |= (bool)(red & mask); // BGR RGB_output_bits |= (bool)(red & mask); // BGR
RGB_output_bits <<= _colorbitoffset; // shift color bits to the required position RGB_output_bits <<= _colourbitoffset; // shift colour bits to the required position
// Get the contents at this address, // Get the contents at this address,
// it would represent a vector pointing to the full row of pixels for the specified color depth bit at Y coordinate // it would represent a vector pointing to the full row of pixels for the specified color depth bit at Y coordinate
ESP32_I2S_DMA_STORAGE_TYPE *p = getRowDataPtr(y_coord, color_depth_idx, back_buffer_id); //ESP32_I2S_DMA_STORAGE_TYPE *p = getRowDataPtr(y_coord, colour_depth_idx, back_buffer_id);
ESP32_I2S_DMA_STORAGE_TYPE *p = dma_buff.rowBits[y_coord]->getDataPtr(colour_depth_idx, back_buffer_id);
// We need to update the correct uint16_t word in the rowBitStruct array pointing to a specific pixel at X - coordinate // We need to update the correct uint16_t word in the rowBitStruct array pointing to a specific pixel at X - coordinate
p[x_coord] &= _colorbitclear; // reset RGB bits p[x_coord] &= _colourbitclear; // reset RGB bits
p[x_coord] |= RGB_output_bits; // set new RGB bits p[x_coord] |= RGB_output_bits; // set new RGB bits
} while(color_depth_idx); // end of color depth loop (8) } while(colour_depth_idx); // end of colour depth loop (8)
} // updateMatrixDMABuffer (specific co-ords change) } // updateMatrixDMABuffer (specific co-ords change)
@ -562,15 +333,15 @@ void MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint8_t red, uint8_t green, uint
blue = lumConvTab[blue]; blue = lumConvTab[blue];
#endif #endif
for(uint8_t color_depth_idx=0; color_depth_idx<PIXEL_COLOR_DEPTH_BITS; color_depth_idx++) // color depth - 8 iterations for(uint8_t colour_depth_idx=0; colour_depth_idx<PIXEL_COLOUR_DEPTH_BITS; colour_depth_idx++) // color depth - 8 iterations
{ {
// let's precalculate RGB1 and RGB2 bits than flood it over the entire DMA buffer // let's precalculate RGB1 and RGB2 bits than flood it over the entire DMA buffer
uint16_t RGB_output_bits = 0; uint16_t RGB_output_bits = 0;
// uint8_t mask = (1 << color_depth_idx COLOR_DEPTH_LESS_THAN_8BIT_ADJUST); // 24 bit color // uint8_t mask = (1 << colour_depth_idx COLOR_DEPTH_LESS_THAN_8BIT_ADJUST); // 24 bit colour
#if PIXEL_COLOR_DEPTH_BITS < 8 #if PIXEL_COLOUR_DEPTH_BITS < 8
uint8_t mask = (1 << (color_depth_idx+MASK_OFFSET)); // expect 24 bit color (8 bits per RGB subpixel) uint8_t mask = (1 << (colour_depth_idx+MASK_OFFSET)); // expect 24 bit color (8 bits per RGB subpixel)
#else #else
uint8_t mask = (1 << (color_depth_idx)); // expect 24 bit color (8 bits per RGB subpixel) uint8_t mask = (1 << (colour_depth_idx)); // expect 24 bit colour (8 bits per RGB subpixel)
#endif #endif
/* Per the .h file, the order of the output RGB bits is: /* Per the .h file, the order of the output RGB bits is:
@ -592,14 +363,15 @@ void MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint8_t red, uint8_t green, uint
--matrix_frame_parallel_row; --matrix_frame_parallel_row;
// The destination for the pixel row bitstream // The destination for the pixel row bitstream
ESP32_I2S_DMA_STORAGE_TYPE *p = getRowDataPtr(matrix_frame_parallel_row, color_depth_idx, back_buffer_id); //ESP32_I2S_DMA_STORAGE_TYPE *p = getRowDataPtr(matrix_frame_parallel_row, colour_depth_idx, back_buffer_id);
ESP32_I2S_DMA_STORAGE_TYPE *p = dma_buff.rowBits[matrix_frame_parallel_row]->getDataPtr(colour_depth_idx, back_buffer_id);
// iterate pixels in a row // iterate pixels in a row
int x_coord=dma_buff.rowBits[matrix_frame_parallel_row]->width; int x_coord=dma_buff.rowBits[matrix_frame_parallel_row]->width;
do { do {
--x_coord; --x_coord;
p[x_coord] &= BITMASK_RGB12_CLEAR; // reset color bits p[x_coord] &= BITMASK_RGB12_CLEAR; // reset colour bits
p[x_coord] |= RGB_output_bits; // set new color bits p[x_coord] |= RGB_output_bits; // set new colour bits
} while(x_coord); } while(x_coord);
} while(matrix_frame_parallel_row); // end row iteration } while(matrix_frame_parallel_row); // end row iteration
@ -607,9 +379,9 @@ void MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint8_t red, uint8_t green, uint
} // updateMatrixDMABuffer (full frame paint) } // updateMatrixDMABuffer (full frame paint)
/** /**
* @brief - clears and reinitializes color/control data in DMA buffs * @brief - clears and reinitializes colour/control data in DMA buffs
* When allocated, DMA buffs might be dirty, so we need to blank it and initialize ABCDE,LAT,OE control bits. * When allocated, DMA buffs might be dirty, so we need to blank it and initialize ABCDE,LAT,OE control bits.
* Those control bits are constants during the entire DMA sweep and never changed when updating just pixel color data * Those control bits are constants during the entire DMA sweep and never changed when updating just pixel colour data
* so we could set it once on DMA buffs initialization and forget. * so we could set it once on DMA buffs initialization and forget.
* This effectively clears buffers to blank BLACK and makes it ready to display output. * This effectively clears buffers to blank BLACK and makes it ready to display output.
* (Brightness control via OE bit manipulation is another case) * (Brightness control via OE bit manipulation is another case)
@ -628,11 +400,11 @@ void MatrixPanel_I2S_DMA::clearFrameBuffer(bool _buff_id){
ESP32_I2S_DMA_STORAGE_TYPE abcde = (ESP32_I2S_DMA_STORAGE_TYPE)row_idx; ESP32_I2S_DMA_STORAGE_TYPE abcde = (ESP32_I2S_DMA_STORAGE_TYPE)row_idx;
abcde <<= BITS_ADDR_OFFSET; // shift row y-coord to match ABCDE bits in vector from 8 to 12 abcde <<= BITS_ADDR_OFFSET; // shift row y-coord to match ABCDE bits in vector from 8 to 12
// get last pixel index in a row of all colordepths // get last pixel index in a row of all colourdepths
int x_pixel = dma_buff.rowBits[row_idx]->width * dma_buff.rowBits[row_idx]->color_depth; int x_pixel = dma_buff.rowBits[row_idx]->width * dma_buff.rowBits[row_idx]->colour_depth;
//Serial.printf(" from pixel %d, ", x_pixel); //Serial.printf(" from pixel %d, ", x_pixel);
// fill all x_pixels except color_index[0] (LSB) ones, this also clears all color data to 0's black // fill all x_pixels except colour_index[0] (LSB) ones, this also clears all colour data to 0's black
do { do {
--x_pixel; --x_pixel;
@ -646,7 +418,7 @@ void MatrixPanel_I2S_DMA::clearFrameBuffer(bool _buff_id){
} while(x_pixel!=dma_buff.rowBits[row_idx]->width); } while(x_pixel!=dma_buff.rowBits[row_idx]->width);
// color_index[0] (LSB) x_pixels must be "marked" with a previous's row address, 'cause it is used to display // 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 // previous row while we pump in LSB's for a new row
abcde = ((ESP32_I2S_DMA_STORAGE_TYPE)row_idx-1) << BITS_ADDR_OFFSET; abcde = ((ESP32_I2S_DMA_STORAGE_TYPE)row_idx-1) << BITS_ADDR_OFFSET;
do { do {
@ -681,21 +453,21 @@ void MatrixPanel_I2S_DMA::clearFrameBuffer(bool _buff_id){
// let's set LAT/OE control bits for specific pixels in each color_index subrows // let's set LAT/OE control bits for specific pixels in each color_index subrows
// Need to consider the original ESP32's (WROOM) DMA TX FIFO reordering of bytes... // Need to consider the original ESP32's (WROOM) DMA TX FIFO reordering of bytes...
uint8_t coloridx = dma_buff.rowBits[row_idx]->color_depth; uint8_t colouridx = dma_buff.rowBits[row_idx]->colour_depth;
do { do {
--coloridx; --colouridx;
// switch pointer to a row for a specific color index // switch pointer to a row for a specific color index
row = dma_buff.rowBits[row_idx]->getDataPtr(coloridx, _buff_id); row = dma_buff.rowBits[row_idx]->getDataPtr(colouridx, _buff_id);
#ifdef ESP32_SXXX #if defined(ESP32_THE_ORIG)
// -1 works better on ESP32-S2 ? Because bytes get sent out in order...
row[dma_buff.rowBits[row_idx]->width - 1] |= BIT_LAT; // -1 pixel to compensate array index starting at 0
#else
// We need to update the correct uint16_t in the rowBitStruct array, that gets sent out in parallel // 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 // 16 bit parallel mode - Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering
// Irrelevant for ESP32-S2 the way the FIFO ordering works is different - refer to page 679 of S2 technical reference manual // Irrelevant for ESP32-S2 the way the FIFO ordering works is different - refer to page 679 of S2 technical reference manual
row[dma_buff.rowBits[row_idx]->width - 2] |= BIT_LAT; // -2 in the DMA array is actually -1 when it's reordered by TX FIFO row[dma_buff.rowBits[row_idx]->width - 2] |= BIT_LAT; // -2 in the DMA array is actually -1 when it's reordered by TX FIFO
#else
// -1 works better on ESP32-S2 ? Because bytes get sent out in order...
row[dma_buff.rowBits[row_idx]->width - 1] |= BIT_LAT; // -1 pixel to compensate array index starting at 0
#endif #endif
// need to disable OE before/after latch to hide row transition // need to disable OE before/after latch to hide row transition
@ -704,11 +476,7 @@ void MatrixPanel_I2S_DMA::clearFrameBuffer(bool _buff_id){
do { do {
--_blank; --_blank;
#ifdef ESP32_SXXX #if defined(ESP32_THE_ORIG)
row[0 + _blank] |= BIT_OE;
row[dma_buff.rowBits[row_idx]->width - _blank - 1 ] |= BIT_OE; // (LAT pulse is (width-2) -1 pixel to compensate array index starting at 0
#else
// Original ESP32 WROOM FIFO Ordering Sucks // Original ESP32 WROOM FIFO Ordering Sucks
uint8_t _blank_row_tx_fifo_tmp = 0 + _blank; uint8_t _blank_row_tx_fifo_tmp = 0 + _blank;
(_blank_row_tx_fifo_tmp & 1U) ? --_blank_row_tx_fifo_tmp : ++_blank_row_tx_fifo_tmp; (_blank_row_tx_fifo_tmp & 1U) ? --_blank_row_tx_fifo_tmp : ++_blank_row_tx_fifo_tmp;
@ -717,12 +485,14 @@ void MatrixPanel_I2S_DMA::clearFrameBuffer(bool _buff_id){
_blank_row_tx_fifo_tmp = dma_buff.rowBits[row_idx]->width - _blank - 1; // (LAT pulse is (width-2) -1 pixel to compensate array index starting at 0 _blank_row_tx_fifo_tmp = dma_buff.rowBits[row_idx]->width - _blank - 1; // (LAT pulse is (width-2) -1 pixel to compensate array index starting at 0
(_blank_row_tx_fifo_tmp & 1U) ? --_blank_row_tx_fifo_tmp : ++_blank_row_tx_fifo_tmp; (_blank_row_tx_fifo_tmp & 1U) ? --_blank_row_tx_fifo_tmp : ++_blank_row_tx_fifo_tmp;
row[_blank_row_tx_fifo_tmp] |= BIT_OE; row[_blank_row_tx_fifo_tmp] |= BIT_OE;
#else
row[0 + _blank] |= BIT_OE;
row[dma_buff.rowBits[row_idx]->width - _blank - 1 ] |= BIT_OE; // (LAT pulse is (width-2) -1 pixel to compensate array index starting at 0
#endif #endif
} while (_blank); } while (_blank);
} while(coloridx); } while(colouridx);
} while(row_idx); } while(row_idx);
} }
@ -748,12 +518,12 @@ void MatrixPanel_I2S_DMA::brtCtrlOE(int brt, const bool _buff_id){
--row_idx; --row_idx;
// let's set OE control bits for specific pixels in each color_index subrows // let's set OE control bits for specific pixels in each color_index subrows
uint8_t coloridx = dma_buff.rowBits[row_idx]->color_depth; uint8_t colouridx = dma_buff.rowBits[row_idx]->colour_depth;
do { do {
--coloridx; --colouridx;
// switch pointer to a row for a specific color index // switch pointer to a row for a specific color index
ESP32_I2S_DMA_STORAGE_TYPE* row = dma_buff.rowBits[row_idx]->getDataPtr(coloridx, _buff_id); ESP32_I2S_DMA_STORAGE_TYPE* row = dma_buff.rowBits[row_idx]->getDataPtr(colouridx, _buff_id);
int x_coord = dma_buff.rowBits[row_idx]->width; int x_coord = dma_buff.rowBits[row_idx]->width;
do { do {
@ -763,14 +533,14 @@ void MatrixPanel_I2S_DMA::brtCtrlOE(int brt, const bool _buff_id){
row[x_coord] &= BITMASK_OE_CLEAR; row[x_coord] &= BITMASK_OE_CLEAR;
// Brightness control via OE toggle - disable matrix output at specified x_coord // Brightness control via OE toggle - disable matrix output at specified x_coord
if((coloridx > lsbMsbTransitionBit || !coloridx) && ((x_coord) >= brt)){ if((colouridx > lsbMsbTransitionBit || !colouridx) && ((x_coord) >= brt)){
row[x_coord] |= BIT_OE; // Disable output after this point. row[x_coord] |= BIT_OE; // Disable output after this point.
continue; continue;
} }
// special case for the bits *after* LSB through (lsbMsbTransitionBit) - OE is output after data is shifted, so need to set OE to fractional brightness // special case for the bits *after* LSB through (lsbMsbTransitionBit) - OE is output after data is shifted, so need to set OE to fractional brightness
if(coloridx && coloridx <= lsbMsbTransitionBit) { if(colouridx && colouridx <= lsbMsbTransitionBit) {
// divide brightness in half for each bit below lsbMsbTransitionBit // divide brightness in half for each bit below lsbMsbTransitionBit
int lsbBrightness = brt >> (lsbMsbTransitionBit - coloridx + 1); int lsbBrightness = brt >> (lsbMsbTransitionBit - colouridx + 1);
if((x_coord) >= lsbBrightness) { if((x_coord) >= lsbBrightness) {
row[x_coord] |= BIT_OE; // Disable output after this point. row[x_coord] |= BIT_OE; // Disable output after this point.
continue; continue;
@ -786,13 +556,13 @@ void MatrixPanel_I2S_DMA::brtCtrlOE(int brt, const bool _buff_id){
do { do {
--_blank; --_blank;
#ifdef ESP32_SXXX #if defined(ESP32_THE_ORIG)
row[0 + _blank] |= BIT_OE;
#else
// Original ESP32 WROOM FIFO Ordering Sucks // Original ESP32 WROOM FIFO Ordering Sucks
uint8_t _blank_row_tx_fifo_tmp = 0 + _blank; uint8_t _blank_row_tx_fifo_tmp = 0 + _blank;
(_blank_row_tx_fifo_tmp & 1U) ? --_blank_row_tx_fifo_tmp : ++_blank_row_tx_fifo_tmp; (_blank_row_tx_fifo_tmp & 1U) ? --_blank_row_tx_fifo_tmp : ++_blank_row_tx_fifo_tmp;
row[_blank_row_tx_fifo_tmp] |= BIT_OE; row[_blank_row_tx_fifo_tmp] |= BIT_OE;
#else
row[0 + _blank] |= BIT_OE;
#endif #endif
//row[0 + _blank] |= BIT_OE; //row[0 + _blank] |= BIT_OE;
@ -800,7 +570,7 @@ void MatrixPanel_I2S_DMA::brtCtrlOE(int brt, const bool _buff_id){
//row[dma_buff.rowBits[row_idx]->width - _blank - 3 ] |= BIT_OE; // (LAT pulse is (width-2) -1 pixel to compensate array index starting at 0 //row[dma_buff.rowBits[row_idx]->width - _blank - 3 ] |= BIT_OE; // (LAT pulse is (width-2) -1 pixel to compensate array index starting at 0
} while (_blank); } while (_blank);
} while(coloridx); } while(colouridx);
} while(row_idx); } while(row_idx);
} }
@ -874,26 +644,26 @@ void MatrixPanel_I2S_DMA::hlineDMA(int16_t x_coord, int16_t y_coord, int16_t l,
blue = lumConvTab[blue]; blue = lumConvTab[blue];
#endif #endif
uint16_t _colorbitclear = BITMASK_RGB1_CLEAR, _colorbitoffset = 0; uint16_t _colourbitclear = BITMASK_RGB1_CLEAR, _colourbitoffset = 0;
if (y_coord >= ROWS_PER_FRAME){ // if we are drawing to the bottom part of the panel if (y_coord >= ROWS_PER_FRAME){ // if we are drawing to the bottom part of the panel
_colorbitoffset = BITS_RGB2_OFFSET; _colourbitoffset = BITS_RGB2_OFFSET;
_colorbitclear = BITMASK_RGB2_CLEAR; _colourbitclear = BITMASK_RGB2_CLEAR;
y_coord -= ROWS_PER_FRAME; y_coord -= ROWS_PER_FRAME;
} }
// Iterating through color depth bits (8 iterations) // Iterating through color depth bits (8 iterations)
uint8_t color_depth_idx = PIXEL_COLOR_DEPTH_BITS; uint8_t colour_depth_idx = PIXEL_COLOUR_DEPTH_BITS;
do { do {
--color_depth_idx; --colour_depth_idx;
// let's precalculate RGB1 and RGB2 bits than flood it over the entire DMA buffer // let's precalculate RGB1 and RGB2 bits than flood it over the entire DMA buffer
uint16_t RGB_output_bits = 0; uint16_t RGB_output_bits = 0;
// uint8_t mask = (1 << color_depth_idx COLOR_DEPTH_LESS_THAN_8BIT_ADJUST); // uint8_t mask = (1 << colour_depth_idx COLOR_DEPTH_LESS_THAN_8BIT_ADJUST);
#if PIXEL_COLOR_DEPTH_BITS < 8 #if PIXEL_COLOUR_DEPTH_BITS < 8
uint8_t mask = (1 << (color_depth_idx+MASK_OFFSET)); // expect 24 bit color (8 bits per RGB subpixel) uint8_t mask = (1 << (colour_depth_idx+MASK_OFFSET)); // expect 24 bit color (8 bits per RGB subpixel)
#else #else
uint8_t mask = (1 << (color_depth_idx)); // expect 24 bit color (8 bits per RGB subpixel) uint8_t mask = (1 << (colour_depth_idx)); // expect 24 bit color (8 bits per RGB subpixel)
#endif #endif
/* Per the .h file, the order of the output RGB bits is: /* Per the .h file, the order of the output RGB bits is:
@ -903,30 +673,30 @@ void MatrixPanel_I2S_DMA::hlineDMA(int16_t x_coord, int16_t y_coord, int16_t l,
RGB_output_bits |= (bool)(green & mask); // -BG RGB_output_bits |= (bool)(green & mask); // -BG
RGB_output_bits <<= 1; RGB_output_bits <<= 1;
RGB_output_bits |= (bool)(red & mask); // BGR RGB_output_bits |= (bool)(red & mask); // BGR
RGB_output_bits <<= _colorbitoffset; // shift color bits to the required position RGB_output_bits <<= _colourbitoffset; // shift color bits to the required position
// Get the contents at this address, // Get the contents at this address,
// it would represent a vector pointing to the full row of pixels for the specified color depth bit at Y coordinate // it would represent a vector pointing to the full row of pixels for the specified color depth bit at Y coordinate
ESP32_I2S_DMA_STORAGE_TYPE *p = dma_buff.rowBits[y_coord]->getDataPtr(color_depth_idx, back_buffer_id); ESP32_I2S_DMA_STORAGE_TYPE *p = dma_buff.rowBits[y_coord]->getDataPtr(colour_depth_idx, back_buffer_id);
// inlined version works slower here, dunno why :( // inlined version works slower here, dunno why :(
// ESP32_I2S_DMA_STORAGE_TYPE *p = getRowDataPtr(y_coord, color_depth_idx, back_buffer_id); // ESP32_I2S_DMA_STORAGE_TYPE *p = getRowDataPtr(y_coord, colour_depth_idx, back_buffer_id);
int16_t _l = l; int16_t _l = l;
do { // iterate pixels in a row do { // iterate pixels in a row
int16_t _x = x_coord + --_l; int16_t _x = x_coord + --_l;
#ifdef ESP32_SXXX #if defined(ESP32_THE_ORIG)
// ESP 32 doesn't need byte flipping for TX FIFO.
uint16_t &v = p[_x];
#else
// Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering // Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering
uint16_t &v = p[_x & 1U ? --_x : ++_x]; uint16_t &v = p[_x & 1U ? --_x : ++_x];
#else
// ESP 32 doesn't need byte flipping for TX FIFO.
uint16_t &v = p[_x];
#endif #endif
v &= _colorbitclear; // reset color bits v &= _colourbitclear; // reset color bits
v |= RGB_output_bits; // set new color bits v |= RGB_output_bits; // set new color bits
} while(_l); // iterate pixels in a row } while(_l); // iterate pixels in a row
} while(color_depth_idx); // end of color depth loop (8) } while(colour_depth_idx); // end of color depth loop (8)
} // hlineDMA() } // hlineDMA()
@ -956,21 +726,21 @@ void MatrixPanel_I2S_DMA::vlineDMA(int16_t x_coord, int16_t y_coord, int16_t l,
blue = lumConvTab[blue]; blue = lumConvTab[blue];
#endif #endif
#ifndef ESP32_SXXX #if defined(ESP32_THE_ORIG)
// Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering // Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering
x_coord & 1U ? --x_coord : ++x_coord; x_coord & 1U ? --x_coord : ++x_coord;
#endif #endif
uint8_t color_depth_idx = PIXEL_COLOR_DEPTH_BITS; uint8_t colour_depth_idx = PIXEL_COLOUR_DEPTH_BITS;
do { // Iterating through color depth bits (8 iterations) do { // Iterating through color depth bits (8 iterations)
--color_depth_idx; --colour_depth_idx;
// let's precalculate RGB1 and RGB2 bits than flood it over the entire DMA buffer // let's precalculate RGB1 and RGB2 bits than flood it over the entire DMA buffer
// uint8_t mask = (1 << color_depth_idx COLOR_DEPTH_LESS_THAN_8BIT_ADJUST); // uint8_t mask = (1 << colour_depth_idx COLOR_DEPTH_LESS_THAN_8BIT_ADJUST);
#if PIXEL_COLOR_DEPTH_BITS < 8 #if PIXEL_COLOUR_DEPTH_BITS < 8
uint8_t mask = (1 << (color_depth_idx+MASK_OFFSET)); // expect 24 bit color (8 bits per RGB subpixel) uint8_t mask = (1 << (colour_depth_idx+MASK_OFFSET)); // expect 24 bit color (8 bits per RGB subpixel)
#else #else
uint8_t mask = (1 << (color_depth_idx)); // expect 24 bit color (8 bits per RGB subpixel) uint8_t mask = (1 << (colour_depth_idx)); // expect 24 bit color (8 bits per RGB subpixel)
#endif #endif
uint16_t RGB_output_bits = 0; uint16_t RGB_output_bits = 0;
@ -983,24 +753,25 @@ void MatrixPanel_I2S_DMA::vlineDMA(int16_t x_coord, int16_t y_coord, int16_t l,
RGB_output_bits |= (bool)(red & mask); // BGR RGB_output_bits |= (bool)(red & mask); // BGR
int16_t _l = 0, _y = y_coord; int16_t _l = 0, _y = y_coord;
uint16_t _colorbitclear = BITMASK_RGB1_CLEAR; uint16_t _colourbitclear = BITMASK_RGB1_CLEAR;
do { // iterate pixels in a column do { // iterate pixels in a column
if (_y >= ROWS_PER_FRAME){ // if y-coord overlapped bottom-half panel if (_y >= ROWS_PER_FRAME){ // if y-coord overlapped bottom-half panel
_y -= ROWS_PER_FRAME; _y -= ROWS_PER_FRAME;
_colorbitclear = BITMASK_RGB2_CLEAR; _colourbitclear = BITMASK_RGB2_CLEAR;
RGB_output_bits <<= BITS_RGB2_OFFSET; RGB_output_bits <<= BITS_RGB2_OFFSET;
} }
// Get the contents at this address, // Get the contents at this address,
// it would represent a vector pointing to the full row of pixels for the specified color depth bit at Y coordinate // it would represent a vector pointing to the full row of pixels for the specified color depth bit at Y coordinate
ESP32_I2S_DMA_STORAGE_TYPE *p = getRowDataPtr(_y, color_depth_idx, back_buffer_id); //ESP32_I2S_DMA_STORAGE_TYPE *p = getRowDataPtr(_y, colour_depth_idx, back_buffer_id);
ESP32_I2S_DMA_STORAGE_TYPE *p = dma_buff.rowBits[_y]->getDataPtr(colour_depth_idx, back_buffer_id);
p[x_coord] &= _colorbitclear; // reset RGB bits p[x_coord] &= _colourbitclear; // reset RGB bits
p[x_coord] |= RGB_output_bits; // set new RGB bits p[x_coord] |= RGB_output_bits; // set new RGB bits
++_y; ++_y;
} while(++_l!=l); // iterate pixels in a col } while(++_l!=l); // iterate pixels in a col
} while(color_depth_idx); // end of color depth loop (8) } while(colour_depth_idx); // end of color depth loop (8)
} // vlineDMA() } // vlineDMA()

View file

@ -4,6 +4,8 @@
/* Core ESP32 hardware / idf includes! */ /* Core ESP32 hardware / idf includes! */
#include <vector> #include <vector>
#include <memory> #include <memory>
#include <Arduino.h>
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
@ -11,7 +13,8 @@
#include "freertos/queue.h" #include "freertos/queue.h"
#include "esp_heap_caps.h" #include "esp_heap_caps.h"
#include "esp32_i2s_parallel_dma.h" #include "platforms/platform_detect.hpp"
#ifdef USE_GFX_ROOT #ifdef USE_GFX_ROOT
#include <FastLED.h> #include <FastLED.h>
@ -25,19 +28,11 @@
* Changing the values just here won't work - as defines needs to persist beyond the scope * * Changing the values just here won't work - as defines needs to persist beyond the scope *
* of just this file. * * of just this file. *
*******************************************************************************************/ *******************************************************************************************/
/* Enable serial debugging of the library, to see how memory is allocated etc. */
//#define SERIAL_DEBUG 1
/* Do NOT build additional methods optimized for fast drawing, /* Do NOT build additional methods optimized for fast drawing,
* i.e. Adafruits drawFastHLine, drawFastVLine, etc... */ * i.e. Adafruits drawFastHLine, drawFastVLine, etc... */
//#define NO_FAST_FUNCTIONS // #define NO_FAST_FUNCTIONS
/* Use GFX_Root (https://github.com/mrfaptastic/GFX_Root) instead of Adafruit_GFX library. // #define NO_CIE1931
* > Removes Bus_IO & Wire.h library dependencies.
* > Provides 24bpp (CRGB) colour support for Adafruit_GFX functions like drawCircle etc.
* > Requires FastLED.h
*/
//#define USE_GFX_ROOT 1
/* Physical / Chained HUB75(s) RGB pixel WIDTH and HEIGHT. /* Physical / Chained HUB75(s) RGB pixel WIDTH and HEIGHT.
* *
@ -62,48 +57,6 @@
#define CHAIN_LENGTH 1 // Number of modules chained together, i.e. 4 panels chained result in virtualmatrix 64x4=256 px long #define CHAIN_LENGTH 1 // Number of modules chained together, i.e. 4 panels chained result in virtualmatrix 64x4=256 px long
#endif #endif
/* ESP32 Default Pin definition. You can change this, but best if you keep it as is and provide custom pin mappings
* as part of the begin(...) function.
*/
// Default pin mapping for ESP32-S2 and ESP32-S3
#ifdef ESP32_SXXX
#define R1_PIN_DEFAULT 45
#define G1_PIN_DEFAULT 42
#define B1_PIN_DEFAULT 41
#define R2_PIN_DEFAULT 40
#define G2_PIN_DEFAULT 39
#define B2_PIN_DEFAULT 38
#define A_PIN_DEFAULT 37
#define B_PIN_DEFAULT 36
#define C_PIN_DEFAULT 35
#define D_PIN_DEFAULT 34
#define E_PIN_DEFAULT -1 // required for 1/32 scan panels, like 64x64. Any available pin would do, i.e. IO32
#define LAT_PIN_DEFAULT 26
#define OE_PIN_DEFAULT 21
#define CLK_PIN_DEFAULT 33
// Else use default pin mapping for ESP32 Original WROOM module.
#else
#define R1_PIN_DEFAULT 25
#define G1_PIN_DEFAULT 26
#define B1_PIN_DEFAULT 27
#define R2_PIN_DEFAULT 14
#define G2_PIN_DEFAULT 12
#define B2_PIN_DEFAULT 13
#define A_PIN_DEFAULT 23
#define B_PIN_DEFAULT 19
#define C_PIN_DEFAULT 5
#define D_PIN_DEFAULT 17
#define E_PIN_DEFAULT -1 // IMPORTANT: Change to a valid pin if using a 64x64px panel.
#define LAT_PIN_DEFAULT 4
#define OE_PIN_DEFAULT 15
#define CLK_PIN_DEFAULT 16
#endif
// Interesting Fact: We end up using a uint16_t to send data in parallel to the HUB75... but // Interesting Fact: We end up using a uint16_t to send data in parallel to the HUB75... but
// given we only map to 14 physical output wires/bits, we waste 2 bits. // given we only map to 14 physical output wires/bits, we waste 2 bits.
@ -118,18 +71,14 @@
// 8bit per RGB color = 24 bit/per pixel, // 8bit per RGB color = 24 bit/per pixel,
// might be reduced to save DMA RAM // might be reduced to save DMA RAM
#ifndef PIXEL_COLOR_DEPTH_BITS #ifndef PIXEL_COLOUR_DEPTH_BITS
#define PIXEL_COLOR_DEPTH_BITS 8 #define PIXEL_COLOUR_DEPTH_BITS 8
#endif #endif
#define COLOR_CHANNELS_PER_PIXEL 3 #define COLOUR_CHANNELS_PER_PIXEL 3
// #define NO_CIE1931
/***************************************************************************************/ /***************************************************************************************/
/* Definitions below should NOT be ever changed without rewriting library logic */ /* Definitions below should NOT be ever changed without rewriting library logic */
#define ESP32_I2S_DMA_MODE I2S_PARALLEL_WIDTH_16 // From esp32_i2s_parallel_v2.h = 16 bits in parallel
#define ESP32_I2S_DMA_STORAGE_TYPE uint16_t // DMA output of one uint16_t at a time. #define ESP32_I2S_DMA_STORAGE_TYPE uint16_t // DMA output of one uint16_t at a time.
#define CLKS_DURING_LATCH 0 // Not (yet) used. #define CLKS_DURING_LATCH 0 // Not (yet) used.
@ -166,24 +115,25 @@
// How many clock cycles to blank OE before/after LAT signal change, default is 1 clock // How many clock cycles to blank OE before/after LAT signal change, default is 1 clock
#define DEFAULT_LAT_BLANKING 1 #define DEFAULT_LAT_BLANKING 1
// Max clock cycles to blank OE before/after LAT signal change // Max clock cycles to blank OE before/after LAT signal change
#define MAX_LAT_BLANKING 4 #define MAX_LAT_BLANKING 4
/***************************************************************************************/ /***************************************************************************************/
// Check compile-time only options // Check compile-time only options
#if PIXEL_COLOR_DEPTH_BITS > 8 #if PIXEL_COLOUR_DEPTH_BITS > 8
#error "Pixel color depth bits cannot be greater than 8." #error "Pixel color depth bits cannot be greater than 8."
#elif PIXEL_COLOR_DEPTH_BITS < 2 #elif PIXEL_COLOUR_DEPTH_BITS < 2
#error "Pixel color depth bits cannot be less than 2." #error "Pixel color depth bits cannot be less than 2."
#endif #endif
/* This library is designed to take an 8 bit / 1 byte value (0-255) for each R G B colour sub-pixel. /* This library is designed to take an 8 bit / 1 byte value (0-255) for each R G B colour sub-pixel.
* The PIXEL_COLOR_DEPTH_BITS should always be '8' as a result. * The PIXEL_COLOUR_DEPTH_BITS should always be '8' as a result.
* However, if the library is to be used with lower colour depth (i.e. 6 bit colour), then we need to ensure the 8-bit value passed to the colour masking * However, if the library is to be used with lower colour depth (i.e. 6 bit colour), then we need to ensure the 8-bit value passed to the colour masking
* is adjusted accordingly to ensure the LSB's are shifted left to MSB, by the difference. Otherwise the colours will be all screwed up. * is adjusted accordingly to ensure the LSB's are shifted left to MSB, by the difference. Otherwise the colours will be all screwed up.
*/ */
#if PIXEL_COLOR_DEPTH_BITS != 8 #if PIXEL_COLOUR_DEPTH_BITS != 8
static constexpr uint8_t const MASK_OFFSET = 8-PIXEL_COLOR_DEPTH_BITS; static constexpr uint8_t const MASK_OFFSET = 8-PIXEL_COLOUR_DEPTH_BITS;
#endif #endif
/***************************************************************************************/ /***************************************************************************************/
@ -193,7 +143,7 @@ static constexpr uint8_t const MASK_OFFSET = 8-PIXEL_COLOR_DEPTH_BITS;
*/ */
struct rowBitStruct { struct rowBitStruct {
const size_t width; const size_t width;
const uint8_t color_depth; const uint8_t colour_depth;
const bool double_buff; const bool double_buff;
ESP32_I2S_DMA_STORAGE_TYPE *data; ESP32_I2S_DMA_STORAGE_TYPE *data;
@ -205,18 +155,25 @@ struct rowBitStruct {
* default - returns full data vector size for a SINGLE buff * default - returns full data vector size for a SINGLE buff
* *
*/ */
size_t size(uint8_t _dpth=0 ) { if (!_dpth) _dpth = color_depth; return width * _dpth * sizeof(ESP32_I2S_DMA_STORAGE_TYPE); }; size_t size(uint8_t _dpth=0 ) { if (!_dpth) _dpth = colour_depth; return width * _dpth * sizeof(ESP32_I2S_DMA_STORAGE_TYPE); };
/** @brief - returns pointer to the row's data vector beginning at pixel[0] for _dpth color bit /** @brief - returns pointer to the row's data vector beginning at pixel[0] for _dpth color bit
* default - returns pointer to the data vector's head * default - returns pointer to the data vector's head
* NOTE: this call might be very slow in loops. Due to poor instruction caching in esp32 it might be required a reread from flash * 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 * every loop cycle, better use inlined #define instead in such cases
*/ */
ESP32_I2S_DMA_STORAGE_TYPE* getDataPtr(const uint8_t _dpth=0, const bool buff_id=0) { return &(data[_dpth*width + buff_id*(width*color_depth)]); }; inline ESP32_I2S_DMA_STORAGE_TYPE* getDataPtr(const uint8_t _dpth=0, const bool buff_id=0) { return &(data[_dpth*width + buff_id*(width*colour_depth)]); };
// constructor - allocates DMA-capable memory to hold the struct data // constructor - allocates DMA-capable memory to hold the struct data
rowBitStruct(const size_t _width, const uint8_t _depth, const bool _dbuff) : width(_width), color_depth(_depth), double_buff(_dbuff) { rowBitStruct(const size_t _width, const uint8_t _depth, const bool _dbuff) : width(_width), colour_depth(_depth), double_buff(_dbuff) {
#if defined(SPIRAM_FRAMEBUFFER)
#pragma message "Enabling PSRAM / SPIRAM for frame buffer."
data = (ESP32_I2S_DMA_STORAGE_TYPE *)heap_caps_malloc( size()+size()*double_buff, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
#else
data = (ESP32_I2S_DMA_STORAGE_TYPE *)heap_caps_malloc( size()+size()*double_buff, MALLOC_CAP_DMA); data = (ESP32_I2S_DMA_STORAGE_TYPE *)heap_caps_malloc( size()+size()*double_buff, MALLOC_CAP_DMA);
#endif
} }
~rowBitStruct() { delete data;} ~rowBitStruct() { delete data;}
}; };
@ -378,23 +335,22 @@ class MatrixPanel_I2S_DMA {
if (initialized) return true; // we don't do this twice or more! if (initialized) return true; // we don't do this twice or more!
// Change 'if' to '1' to enable, 0 to not include this Serial output in compiled program
#if SERIAL_DEBUG ESP_LOGI("begin()", "Using GPIO %d for R1_PIN", m_cfg.gpio.r1);
Serial.printf_P(PSTR("Using pin %d for the R1_PIN\n"), m_cfg.gpio.r1); ESP_LOGI("begin()", "Using GPIO %d for G1_PIN", m_cfg.gpio.g1);
Serial.printf_P(PSTR("Using pin %d for the G1_PIN\n"), m_cfg.gpio.g1); ESP_LOGI("begin()", "Using GPIO %d for B1_PIN", m_cfg.gpio.b1);
Serial.printf_P(PSTR("Using pin %d for the B1_PIN\n"), m_cfg.gpio.b1); ESP_LOGI("begin()", "Using GPIO %d for R2_PIN", m_cfg.gpio.r2);
Serial.printf_P(PSTR("Using pin %d for the R2_PIN\n"), m_cfg.gpio.r2); ESP_LOGI("begin()", "Using GPIO %d for G2_PIN", m_cfg.gpio.g2);
Serial.printf_P(PSTR("Using pin %d for the G2_PIN\n"), m_cfg.gpio.g2); ESP_LOGI("begin()", "Using GPIO %d for B2_PIN", m_cfg.gpio.b2);
Serial.printf_P(PSTR("Using pin %d for the B2_PIN\n"), m_cfg.gpio.b2); ESP_LOGI("begin()", "Using GPIO %d for A_PIN", m_cfg.gpio.a);
Serial.printf_P(PSTR("Using pin %d for the A_PIN\n"), m_cfg.gpio.a); ESP_LOGI("begin()", "Using GPIO %d for B_PIN", m_cfg.gpio.b);
Serial.printf_P(PSTR("Using pin %d for the B_PIN\n"), m_cfg.gpio.b); ESP_LOGI("begin()", "Using GPIO %d for C_PIN", m_cfg.gpio.c);
Serial.printf_P(PSTR("Using pin %d for the C_PIN\n"), m_cfg.gpio.c); ESP_LOGI("begin()", "Using GPIO %d for D_PIN", m_cfg.gpio.d);
Serial.printf_P(PSTR("Using pin %d for the D_PIN\n"), m_cfg.gpio.d); ESP_LOGI("begin()", "Using GPIO %d for E_PIN", m_cfg.gpio.e);
Serial.printf_P(PSTR("Using pin %d for the E_PIN\n"), m_cfg.gpio.e); ESP_LOGI("begin()", "Using GPIO %d for LAT_PIN", m_cfg.gpio.lat);
Serial.printf_P(PSTR("Using pin %d for the LAT_PIN\n"), m_cfg.gpio.lat); ESP_LOGI("begin()", "Using GPIO %d for OE_PIN", m_cfg.gpio.oe);
Serial.printf_P(PSTR("Using pin %d for the OE_PIN\n"), m_cfg.gpio.oe); ESP_LOGI("begin()", "Using GPIO %d for CLK_PIN", m_cfg.gpio.clk);
Serial.printf_P(PSTR("Using pin %d for the CLK_PIN\n"), m_cfg.gpio.clk);
#endif
// initialize some specific panel drivers // initialize some specific panel drivers
if (m_cfg.driver) if (m_cfg.driver)
@ -415,10 +371,9 @@ class MatrixPanel_I2S_DMA {
//showDMABuffer(); // show backbuf_id of 0 //showDMABuffer(); // show backbuf_id of 0
#if SERIAL_DEBUG if (!initialized) {
if (!initialized) ESP_LOGE("being()", "MatrixPanel_I2S_DMA::begin() failed!");
Serial.println(F("MatrixPanel_I2S_DMA::begin() failed.")); }
#endif
return initialized; return initialized;
@ -426,13 +381,9 @@ class MatrixPanel_I2S_DMA {
// Obj destructor // Obj destructor
~MatrixPanel_I2S_DMA(){ ~MatrixPanel_I2S_DMA(){
stopDMAoutput();
delete dmadesc_a;
if (m_cfg.double_buff)
delete dmadesc_b;
dma_bus.release();
} }
@ -520,11 +471,19 @@ class MatrixPanel_I2S_DMA {
inline void IRAM_ATTR flipDMABuffer() inline void IRAM_ATTR flipDMABuffer()
{ {
if ( !m_cfg.double_buff) return; if ( !m_cfg.double_buff) return;
#if SERIAL_DEBUG
Serial.printf_P(PSTR("Set back buffer to: %d\n"), back_buffer_id);
#endif
//ESP_LOGI("flipDMABuffer()", "Set back buffer to: %d", back_buffer_id);
if (back_buffer_id == 0)
{
dma_bus.set_dma_output_buffer( false );
}
else
{
dma_bus.set_dma_output_buffer( true );
}
/*
i2s_parallel_set_previous_buffer_not_free(); i2s_parallel_set_previous_buffer_not_free();
// Wait before we allow any writing to the buffer. Stop flicker. // Wait before we allow any writing to the buffer. Stop flicker.
while(i2s_parallel_is_previous_buffer_free() == false) { } while(i2s_parallel_is_previous_buffer_free() == false) { }
@ -537,11 +496,10 @@ class MatrixPanel_I2S_DMA {
i2s_parallel_set_previous_buffer_not_free(); i2s_parallel_set_previous_buffer_not_free();
// Wait before we allow any writing to the buffer. Stop flicker. // Wait before we allow any writing to the buffer. Stop flicker.
while(i2s_parallel_is_previous_buffer_free() == false) { } while(i2s_parallel_is_previous_buffer_free() == false) { }
*/
back_buffer_id ^= 1;
} }
inline void setPanelBrightness(int b) inline void setPanelBrightness(int b)
@ -593,7 +551,8 @@ class MatrixPanel_I2S_DMA {
*/ */
void stopDMAoutput() { void stopDMAoutput() {
resetbuffers(); resetbuffers();
i2s_parallel_stop_dma(ESP32_I2S_DEVICE); //i2s_parallel_stop_dma(ESP32_I2S_DEVICE);
dma_bus.dma_transfer_stop();
} }
@ -602,6 +561,8 @@ class MatrixPanel_I2S_DMA {
// those might be useful for child classes, like VirtualMatrixPanel // those might be useful for child classes, like VirtualMatrixPanel
protected: protected:
Bus_Parallel16 dma_bus;
/** /**
* @brief - clears and reinitializes color/control data in DMA buffs * @brief - clears and reinitializes color/control data in DMA buffs
* When allocated, DMA buffs might be dirty, so we need to blank it and initialize ABCDE,LAT,OE control bits. * When allocated, DMA buffs might be dirty, so we need to blank it and initialize ABCDE,LAT,OE control bits.
@ -685,8 +646,8 @@ class MatrixPanel_I2S_DMA {
// ESP 32 DMA Linked List descriptor // ESP 32 DMA Linked List descriptor
int desccount = 0; int desccount = 0;
lldesc_t * dmadesc_a = {0}; // lldesc_t * dmadesc_a = {0};
lldesc_t * dmadesc_b = {0}; // lldesc_t * dmadesc_b = {0};
/* Pixel data is organized from LSB to MSB sequentially by row, from row 0 to row matrixHeight/matrixRowsInParallel /* Pixel data is organized from LSB to MSB sequentially by row, from row 0 to row matrixHeight/matrixRowsInParallel
* (two rows of pixels are refreshed in parallel) * (two rows of pixels are refreshed in parallel)
@ -818,3 +779,57 @@ inline void MatrixPanel_I2S_DMA::drawIcon (int *ico, int16_t x, int16_t y, int16
#endif #endif
// Credits: Louis Beaudoin <https://github.com/pixelmatix/SmartMatrix/tree/teensylc>
// and Sprite_TM: https://www.esp32.com/viewtopic.php?f=17&t=3188 and https://www.esp32.com/viewtopic.php?f=13&t=3256
/*
This is example code to driver a p3(2121)64*32 -style RGB LED display. These types of displays do not have memory and need to be refreshed
continuously. The display has 2 RGB inputs, 4 inputs to select the active line, a pixel clock input, a latch enable input and an output-enable
input. The display can be seen as 2 64x16 displays consisting of the upper half and the lower half of the display. Each half has a separate
RGB pixel input, the rest of the inputs are shared.
Each display half can only show one line of RGB pixels at a time: to do this, the RGB data for the line is input by setting the RGB input pins
to the desired value for the first pixel, giving the display a clock pulse, setting the RGB input pins to the desired value for the second pixel,
giving a clock pulse, etc. Do this 64 times to clock in an entire row. The pixels will not be displayed yet: until the latch input is made high,
the display will still send out the previously clocked in line. Pulsing the latch input high will replace the displayed data with the data just
clocked in.
The 4 line select inputs select where the currently active line is displayed: when provided with a binary number (0-15), the latched pixel data
will immediately appear on this line. Note: While clocking in data for a line, the *previous* line is still displayed, and these lines should
be set to the value to reflect the position the *previous* line is supposed to be on.
Finally, the screen has an OE input, which is used to disable the LEDs when latching new data and changing the state of the line select inputs:
doing so hides any artefacts that appear at this time. The OE line is also used to dim the display by only turning it on for a limited time every
line.
All in all, an image can be displayed by 'scanning' the display, say, 100 times per second. The slowness of the human eye hides the fact that
only one line is showed at a time, and the display looks like every pixel is driven at the same time.
Now, the RGB inputs for these types of displays are digital, meaning each red, green and blue subpixel can only be on or off. This leads to a
color palette of 8 pixels, not enough to display nice pictures. To get around this, we use binary code modulation.
Binary code modulation is somewhat like PWM, but easier to implement in our case. First, we define the time we would refresh the display without
binary code modulation as the 'frame time'. For, say, a four-bit binary code modulation, the frame time is divided into 15 ticks of equal length.
We also define 4 subframes (0 to 3), defining which LEDs are on and which LEDs are off during that subframe. (Subframes are the same as a
normal frame in non-binary-coded-modulation mode, but are showed faster.) From our (non-monochrome) input image, we take the (8-bit: bit 7
to bit 0) RGB pixel values. If the pixel values have bit 7 set, we turn the corresponding LED on in subframe 3. If they have bit 6 set,
we turn on the corresponding LED in subframe 2, if bit 5 is set subframe 1, if bit 4 is set in subframe 0.
Now, in order to (on average within a frame) turn a LED on for the time specified in the pixel value in the input data, we need to weigh the
subframes. We have 15 pixels: if we show subframe 3 for 8 of them, subframe 2 for 4 of them, subframe 1 for 2 of them and subframe 1 for 1 of
them, this 'automatically' happens. (We also distribute the subframes evenly over the ticks, which reduces flicker.)
In this code, we use the I2S peripheral in parallel mode to achieve this. Essentially, first we allocate memory for all subframes. This memory
contains a sequence of all the signals (2xRGB, line select, latch enable, output enable) that need to be sent to the display for that subframe.
Then we ask the I2S-parallel driver to set up a DMA chain so the subframes are sent out in a sequence that satisfies the requirement that
subframe x has to be sent out for (2^x) ticks. Finally, we fill the subframes with image data.
We use a front buffer/back buffer technique here to make sure the display is refreshed in one go and drawing artefacts do not reach the display.
In practice, for small displays this is not really necessarily.
*/

View file

@ -35,10 +35,10 @@ void MatrixPanel_I2S_DMA::shiftDriver(const HUB75_I2S_CFG& _cfg){
} }
void MatrixPanel_I2S_DMA::fm6124init(const HUB75_I2S_CFG& _cfg){ void MatrixPanel_I2S_DMA::fm6124init(const HUB75_I2S_CFG& _cfg) {
#if SERIAL_DEBUG
Serial.println( F("MatrixPanel_I2S_DMA - initializing FM6124 driver...")); ESP_LOGI("LEDdrivers", "MatrixPanel_I2S_DMA - initializing FM6124 driver...");
#endif
bool REG1[16] = {0,0,0,0,0, 1,1,1,1,1,1, 0,0,0,0,0}; // this sets global matrix brightness power bool REG1[16] = {0,0,0,0,0, 1,1,1,1,1,1, 0,0,0,0,0}; // this sets global matrix brightness power
bool REG2[16] = {0,0,0,0,0, 0,0,0,0,1,0, 0,0,0,0,0}; // a single bit enables the matrix output bool REG2[16] = {0,0,0,0,0, 0,0,0,0,1,0, 0,0,0,0,0}; // a single bit enables the matrix output

View file

@ -0,0 +1,18 @@
#pragma once
#define R1_PIN_DEFAULT 25
#define G1_PIN_DEFAULT 26
#define B1_PIN_DEFAULT 27
#define R2_PIN_DEFAULT 14
#define G2_PIN_DEFAULT 12
#define B2_PIN_DEFAULT 13
#define A_PIN_DEFAULT 23
#define B_PIN_DEFAULT 19
#define C_PIN_DEFAULT 5
#define D_PIN_DEFAULT 17
#define E_PIN_DEFAULT -1 // IMPORTANT: Change to a valid pin if using a 64x64px panel.
#define LAT_PIN_DEFAULT 4
#define OE_PIN_DEFAULT 15
#define CLK_PIN_DEFAULT 16

View file

@ -0,0 +1,577 @@
/*----------------------------------------------------------------------------/
Lovyan GFX - Graphics library for embedded devices.
Original Source:
https://github.com/lovyan03/LovyanGFX/
Licence:
[FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt)
Author:
[lovyan03](https://twitter.com/lovyan03)
Contributors:
[ciniml](https://github.com/ciniml)
[mongonta0716](https://github.com/mongonta0716)
[tobozo](https://github.com/tobozo)
Modified heavily for the ESP32 HUB75 DMA library by:
[mrfaptastic](https://github.com/mrfaptastic)
/----------------------------------------------------------------------------*/
static const char* TAG = "esp32_i2s_parallel_dma";
#include <sdkconfig.h>
#if defined (CONFIG_IDF_TARGET_ESP32)
#include "esp32_i2s_parallel_dma.hpp"
#include <driver/gpio.h>
#include <driver/periph_ctrl.h>
#include <soc/gpio_sig_map.h>
#include <esp_err.h>
#include <esp_log.h>
/*
callback shiftCompleteCallback;
void setShiftCompleteCallback(callback f) {
shiftCompleteCallback = f;
}
volatile int previousBufferOutputLoopCount = 0;
volatile bool previousBufferFree = true;
static void IRAM_ATTR irq_hndlr(void* arg) { // if we use I2S1 (default)
SET_PERI_REG_BITS(I2S_INT_CLR_REG(ESP32_I2S_DEVICE), I2S_OUT_EOF_INT_CLR_V, 1, I2S_OUT_EOF_INT_CLR_S);
previousBufferFree = true;
} // end irq_hndlr
*/
static i2s_dev_t* getDev(int port)
{
#if defined (CONFIG_IDF_TARGET_ESP32S2)
return &I2S0;
#else
return (port == 0) ? &I2S0 : &I2S1;
#endif
}
void Bus_Parallel16::config(const config_t& cfg)
{
ESP_LOGI(TAG, "Performing config for ESP32 or ESP32-S2");
_cfg = cfg;
auto port = cfg.port;
_dev = getDev(port);
}
//#if defined (CONFIG_IDF_TARGET_ESP32S2)
static void _gpio_pin_init(int pin)
{
if (pin >= 0)
{
gpio_pad_select_gpio(pin);
//gpio_hi(pin);
gpio_set_direction((gpio_num_t)pin, GPIO_MODE_OUTPUT);
gpio_set_drive_capability((gpio_num_t)pin, (gpio_drive_cap_t)3); // esp32s3 as well?
}
}
bool Bus_Parallel16::init(void) // The big one that gets everything setup.
{
ESP_LOGI(TAG, "Performing DMA bus init() for ESP32 or ESP32-S2");
if(_cfg.port < I2S_NUM_0 || _cfg.port >= I2S_NUM_MAX) {
//return ESP_ERR_INVALID_ARG;
return false;
}
if(_cfg.parallel_width < 8 || _cfg.parallel_width >= 24) {
return false;
}
//auto freq = (_cfg.freq_write, 50000000u); // ?
auto freq = (_cfg.bus_freq);
uint32_t _clkdiv_write = 0;
size_t _div_num = 10;
// Calculate clock divider for ESP32-S2
#if defined (CONFIG_IDF_TARGET_ESP32S2)
static constexpr uint32_t pll_160M_clock_d2 = 160 * 1000 * 1000 >> 1;
// I2S_CLKM_DIV_NUM 2=40MHz / 3=27MHz / 4=20MHz / 5=16MHz / 8=10MHz / 10=8MHz
_div_num = std::min(255u, 1 + ((pll_160M_clock_d2) / (1 + _cfg.freq_write)));
_clkdiv_write = I2S_CLK_160M_PLL << I2S_CLK_SEL_S
| I2S_CLK_EN
| 1 << I2S_CLKM_DIV_A_S
| 0 << I2S_CLKM_DIV_B_S
| _div_num << I2S_CLKM_DIV_NUM_S
;
#else
// clock = 80MHz(PLL_D2_CLK)
static constexpr uint32_t pll_d2_clock = 80 * 1000 * 1000;
// I2S_CLKM_DIV_NUM 4=20MHz / 5=16MHz / 8=10MHz / 10=8MHz
_div_num = std::min(255u, std::max(3u, 1 + (pll_d2_clock / (1 + freq))));
_clkdiv_write = I2S_CLK_EN
| 1 << I2S_CLKM_DIV_A_S
| 0 << I2S_CLKM_DIV_B_S
| _div_num << I2S_CLKM_DIV_NUM_S
;
#endif
if(_div_num < 2 || _div_num > 16) {
return false;
}
//ESP_LOGI(TAG, "i2s pll clk_div_main is: %d", _div_num);
auto dev = _dev;
volatile int iomux_signal_base;
volatile int iomux_clock;
int irq_source;
// Initialize I2S0 peripheral
if (_cfg.port == 0)
{
periph_module_reset(PERIPH_I2S0_MODULE);
periph_module_enable(PERIPH_I2S0_MODULE);
iomux_clock = I2S0O_WS_OUT_IDX;
irq_source = ETS_I2S0_INTR_SOURCE;
switch(_cfg.parallel_width) {
case 8:
case 16:
iomux_signal_base = I2S0O_DATA_OUT8_IDX;
break;
case 24:
iomux_signal_base = I2S0O_DATA_OUT0_IDX;
break;
default:
return ESP_ERR_INVALID_ARG;
}
}
#if !defined (CONFIG_IDF_TARGET_ESP32S2)
// Can't compile if I2S1 if it doesn't exist with that hardware's IDF....
else {
periph_module_reset(PERIPH_I2S1_MODULE);
periph_module_enable(PERIPH_I2S1_MODULE);
iomux_clock = I2S1O_WS_OUT_IDX;
irq_source = ETS_I2S1_INTR_SOURCE;
switch(_cfg.parallel_width) {
case 16:
iomux_signal_base = I2S1O_DATA_OUT8_IDX;
break;
case 8:
case 24:
iomux_signal_base = I2S1O_DATA_OUT0_IDX;
break;
default:
return ESP_ERR_INVALID_ARG;
}
}
#endif
// Setup GPIOs
int bus_width = _cfg.parallel_width;
// Clock output GPIO setup
_gpio_pin_init(_cfg.pin_rd); // not used
_gpio_pin_init(_cfg.pin_wr); // clock
_gpio_pin_init(_cfg.pin_rs); // not used
// Data output GPIO setup
int8_t* pins = _cfg.pin_data;
for(int i = 0; i < bus_width; i++)
_gpio_pin_init(pins[i]);
// Route clock signal to clock pin
gpio_matrix_out(_cfg.pin_wr, iomux_clock, _cfg.invert_pclk, 0); // inverst clock if required
for (size_t i = 0; i < bus_width; i++) {
if (pins[i] >= 0) {
gpio_matrix_out(pins[i], iomux_signal_base + i, false, false);
}
}
// Setup i2s clock
dev->sample_rate_conf.val = 0;
// Third stage config, width of data to be written to IO (I think this should always be the actual data width?)
dev->sample_rate_conf.rx_bits_mod = bus_width;
dev->sample_rate_conf.tx_bits_mod = bus_width;
dev->sample_rate_conf.rx_bck_div_num = 2;
dev->sample_rate_conf.tx_bck_div_num = 2;
// Clock configuration
// dev->clkm_conf.val=0; // Clear the clkm_conf struct
/*
#if defined (CONFIG_IDF_TARGET_ESP32S2)
dev->clkm_conf.clk_sel = 2; // esp32-s2 only
dev->clkm_conf.clk_en = 1;
#endif
#if !defined (CONFIG_IDF_TARGET_ESP32S2)
dev->clkm_conf.clka_en=0; // Use the 80mhz system clock (PLL_D2_CLK) when '0'
#endif
dev->clkm_conf.clkm_div_b=0; // Clock numerator
dev->clkm_conf.clkm_div_a=1; // Clock denominator
*/
// Note: clkm_div_num must only be set here AFTER clkm_div_b, clkm_div_a, etc. Or weird things happen!
// On original ESP32, max I2S DMA parallel speed is 20Mhz.
//dev->clkm_conf.clkm_div_num = 32;
dev->clkm_conf.val = _clkdiv_write;
// I2S conf2 reg
dev->conf2.val = 0;
dev->conf2.lcd_en = 1;
dev->conf2.lcd_tx_wrx2_en=0;
dev->conf2.lcd_tx_sdx2_en=0;
// I2S conf reg
dev->conf.val = 0;
#if defined (CONFIG_IDF_TARGET_ESP32S2)
dev->conf.tx_dma_equal=1; // esp32-s2 only
dev->conf.pre_req_en=1; // esp32-s2 only - enable I2S to prepare data earlier? wtf?
#endif
// Now start setting up DMA FIFO
dev->fifo_conf.val = 0;
dev->fifo_conf.rx_data_num = 32; // Thresholds.
dev->fifo_conf.tx_data_num = 32;
dev->fifo_conf.dscr_en = 1;
#if !defined (CONFIG_IDF_TARGET_ESP32S2)
// Enable "One datum will be written twice in LCD mode" - for some reason,
// if we don't do this in 8-bit mode, data is updated on half-clocks not clocks
if(_cfg.parallel_width == 8)
dev->conf2.lcd_tx_wrx2_en=1;
// Not really described for non-pcm modes, although datasheet states it should be set correctly even for LCD mode
// First stage config. Configures how data is loaded into fifo
if(_cfg.parallel_width == 24) {
// Mode 0, single 32-bit channel, linear 32 bit load to fifo
dev->fifo_conf.tx_fifo_mod = 3;
} else {
// Mode 1, single 16-bit channel, load 16 bit sample(*) into fifo and pad to 32 bit with zeros
// *Actually a 32 bit read where two samples are read at once. Length of fifo must thus still be word-aligned
dev->fifo_conf.tx_fifo_mod = 1;
}
// Dictated by ESP32 datasheet
dev->fifo_conf.rx_fifo_mod_force_en = 1;
dev->fifo_conf.tx_fifo_mod_force_en = 1;
// Second stage config
dev->conf_chan.val = 0;
// 16-bit single channel data
dev->conf_chan.tx_chan_mod = 1;
dev->conf_chan.rx_chan_mod = 1;
#endif
// Reset FIFO
dev->conf.rx_fifo_reset = 1;
#if defined (CONFIG_IDF_TARGET_ESP32S2)
while(dev->conf.rx_fifo_reset_st); // esp32-s2 only
#endif
dev->conf.rx_fifo_reset = 0;
dev->conf.tx_fifo_reset = 1;
#if defined (CONFIG_IDF_TARGET_ESP32S2)
while(dev->conf.tx_fifo_reset_st); // esp32-s2 only
#endif
dev->conf.tx_fifo_reset = 0;
// Reset DMA
dev->lc_conf.in_rst = 1;
dev->lc_conf.in_rst = 0;
dev->lc_conf.out_rst = 1;
dev->lc_conf.out_rst = 0;
dev->lc_conf.ahbm_rst = 1;
dev->lc_conf.ahbm_rst = 0;
dev->in_link.val = 0;
dev->out_link.val = 0;
// Device reset
dev->conf.rx_reset=1;
dev->conf.tx_reset=1;
dev->conf.rx_reset=0;
dev->conf.tx_reset=0;
dev->conf1.val = 0;
dev->conf1.tx_stop_en = 0;
/*
// Allocate I2S status structure for buffer swapping stuff
i2s_state = (i2s_parallel_state_t*) malloc(sizeof(i2s_parallel_state_t));
assert(i2s_state != NULL);
i2s_parallel_state_t *state = i2s_state;
state->desccount_a = conf->desccount_a;
state->desccount_b = conf->desccount_b;
state->dmadesc_a = conf->lldesc_a;
state->dmadesc_b = conf->lldesc_b;
state->i2s_interrupt_port_arg = port; // need to keep this somewhere in static memory for the ISR
*/
dev->timing.val = 0;
/*
// We using the double buffering switch logic?
if (conf->int_ena_out_eof)
{
// Get ISR setup
esp_err_t err = esp_intr_alloc(irq_source,
(int)(ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL1),
irq_hndlr,
&state->i2s_interrupt_port_arg, NULL);
if(err) {
return err;
}
// Setup interrupt handler which is focussed only on the (page 322 of Tech. Ref. Manual)
// "I2S_OUT_EOF_INT: Triggered when rxlink has finished sending a packet"
// ... whatever the hell that is supposed to mean... One massive linked list? So all pixels in the chain?
dev->int_ena.out_eof = 1;
}
*/
#if defined (CONFIG_IDF_TARGET_ESP32S2)
ESP_LOGD(TAG, "init() GPIO and clock configuration set for ESP32-S2");
#else
ESP_LOGD(TAG, "init() GPIO and clock configuration set for ESP32");
#endif
return true;
}
void Bus_Parallel16::release(void)
{
if (_dmadesc_a)
{
heap_caps_free(_dmadesc_a);
_dmadesc_a = nullptr;
_dmadesc_count = 0;
}
if (_dmadesc_b)
{
heap_caps_free(_dmadesc_b);
_dmadesc_b = nullptr;
_dmadesc_count = 0;
}
}
void Bus_Parallel16::enable_double_dma_desc(void)
{
_double_dma_buffer = true;
}
// Need this to work for double buffers etc.
bool Bus_Parallel16::allocate_dma_desc_memory(size_t len)
{
if (_dmadesc_a) heap_caps_free(_dmadesc_a); // free all dma descrptios previously
_dmadesc_count = len;
ESP_LOGI(TAG, "Allocating memory for %d DMA descriptors.", len);
_dmadesc_a= (HUB75_DMA_DESCRIPTOR_T*)heap_caps_malloc(sizeof(HUB75_DMA_DESCRIPTOR_T) * len, MALLOC_CAP_DMA);
if (_dmadesc_a == nullptr)
{
ESP_LOGE(TAG, "ERROR: Couldn't malloc _dmadesc_a. Not enough memory.");
return false;
}
if (_double_dma_buffer)
{
if (_dmadesc_b) heap_caps_free(_dmadesc_b); // free all dma descrptios previously
ESP_LOGD(TAG, "Allocating the second buffer (double buffer enabled).");
_dmadesc_b= (HUB75_DMA_DESCRIPTOR_T*)heap_caps_malloc(sizeof(HUB75_DMA_DESCRIPTOR_T) * len, MALLOC_CAP_DMA);
if (_dmadesc_b == nullptr)
{
ESP_LOGE(TAG, "ERROR: Couldn't malloc _dmadesc_b. Not enough memory.");
_double_dma_buffer = false;
return false;
}
}
_dmadesc_a_idx = 0;
_dmadesc_b_idx = 0;
ESP_LOGD(TAG, "Allocating %d bytes of memory for DMA descriptors.", sizeof(HUB75_DMA_DESCRIPTOR_T) * len);
return true;
}
void Bus_Parallel16::create_dma_desc_link(void *data, size_t size, bool dmadesc_b)
{
static constexpr size_t MAX_DMA_LEN = (4096-4);
if (dmadesc_b)
ESP_LOGI(TAG, " * Double buffer descriptor.");
if (size > MAX_DMA_LEN)
{
size = MAX_DMA_LEN;
ESP_LOGW(TAG, "Creating DMA descriptor which links to payload with size greater than MAX_DMA_LEN!");
}
if ( !dmadesc_b )
{
if ( (_dmadesc_a_idx+1) > _dmadesc_count) {
ESP_LOGE(TAG, "Attempted to create more DMA descriptors than allocated memory for. Expecting a maximum of %d DMA descriptors", _dmadesc_count);
return;
}
}
volatile lldesc_t *dmadesc;
volatile lldesc_t *next;
bool eof = false;
/*
dmadesc_a[desccount-1].eof = 1;
dmadesc_a[desccount-1].qe.stqe_next=(lldesc_t*)&dmadesc_a[0];
*/
// ESP_LOGI(TAG, "Creating descriptor %d\n", _dmadesc_a_idx);
if ( (dmadesc_b == true) ) // for primary buffer
{
dmadesc = &_dmadesc_b[_dmadesc_b_idx];
next = (_dmadesc_b_idx < (_dmadesc_count-1) ) ? &_dmadesc_b[_dmadesc_b_idx+1]:_dmadesc_b;
eof = (_dmadesc_b_idx == (_dmadesc_count-1));
}
else
{
dmadesc = &_dmadesc_a[_dmadesc_a_idx];
// https://stackoverflow.com/questions/47170740/c-negative-array-index
next = (_dmadesc_a_idx < (_dmadesc_count-1) ) ? _dmadesc_a + _dmadesc_a_idx+1:_dmadesc_a;
eof = (_dmadesc_a_idx == (_dmadesc_count-1));
}
if ( _dmadesc_a_idx == (_dmadesc_count-1) ) {
ESP_LOGW(TAG, "Creating final DMA descriptor and linking back to 0.");
}
dmadesc->size = size;
dmadesc->length = size;
dmadesc->buf = (uint8_t*) data;
dmadesc->eof = 0;
dmadesc->sosf = 0;
dmadesc->owner = 1;
dmadesc->qe.stqe_next = (lldesc_t*) next;
dmadesc->offset = 0;
if ( (dmadesc_b == true) ) { // for primary buffer
_dmadesc_b_idx++;
} else {
_dmadesc_a_idx++;
}
} // end create_dma_desc_link
void Bus_Parallel16::dma_transfer_start()
{
auto dev = _dev;
// Configure DMA burst mode
dev->lc_conf.val = I2S_OUT_DATA_BURST_EN | I2S_OUTDSCR_BURST_EN;
// Set address of DMA descriptor
dev->out_link.addr = (uint32_t) _dmadesc_a;
// Start DMA operation
dev->out_link.stop = 0;
dev->out_link.start = 1;
dev->conf.tx_start = 1;
} // end
void Bus_Parallel16::dma_transfer_stop()
{
auto dev = _dev;
// Stop all ongoing DMA operations
dev->out_link.stop = 1;
dev->out_link.start = 0;
dev->conf.tx_start = 0;
} // end
void Bus_Parallel16::set_dma_output_buffer(bool dmadesc_b)
{
if ( _double_dma_buffer == false) return;
if ( dmadesc_b == true) // change across to everything 'b''
{
_dmadesc_a[_dmadesc_count-1].qe.stqe_next = &_dmadesc_b[0];
_dmadesc_b[_dmadesc_count-1].qe.stqe_next = &_dmadesc_b[0];
}
else
{
_dmadesc_a[_dmadesc_count-1].qe.stqe_next = &_dmadesc_a[0];
_dmadesc_b[_dmadesc_count-1].qe.stqe_next = &_dmadesc_a[0];
}
//_dmadesc_a_active ^= _dmadesc_a_active;
} // end flip
#endif

View file

@ -0,0 +1,138 @@
/*----------------------------------------------------------------------------/
Lovyan GFX - Graphics library for embedded devices.
Original Source:
https://github.com/lovyan03/LovyanGFX/
Licence:
[FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt)
Author:
[lovyan03](https://twitter.com/lovyan03)
Contributors:
[ciniml](https://github.com/ciniml)
[mongonta0716](https://github.com/mongonta0716)
[tobozo](https://github.com/tobozo)
Modified heavily for the ESP32 HUB75 DMA library by:
[mrfaptastic](https://github.com/mrfaptastic)
------------------------------------------------------------------------------
Putins Russia and its genocide in Ukraine is a disgrace to humanity.
https://www.reddit.com/r/ukraine/comments/xfuc6v/more_than_460_graves_have_already_been_found_in/
Xi Jinping and his communist Chinas silence on the war in Ukraine says everything about
how China condones such genocide, especially if it's against 'the west' (aka. decency).
Whilst the good people at Espressif probably have nothing to do with this, the unfortunate
reality is libraries like this increase the popularity of Chinese silicon chips, which
indirectly funds (through CCP state taxes) the growth and empowerment of such a despot government.
Global democracy, decency and security is put at risk with every Chinese silicon chip that is bought.
/----------------------------------------------------------------------------*/
#pragma once
#include <string.h> // memcpy
#include <algorithm>
#include <stdbool.h>
#include <sys/types.h>
#include <freertos/FreeRTOS.h>
#include <driver/i2s.h>
#include <rom/lldesc.h>
#include <rom/gpio.h>
#define DMA_MAX (4096-4)
// The type used for this SoC
#define HUB75_DMA_DESCRIPTOR_T lldesc_t
//----------------------------------------------------------------------------
class Bus_Parallel16
{
public:
Bus_Parallel16()
{
}
struct config_t
{
int port = 0;
// max 20MHz (when in 16 bit / 2 byte mode)
uint32_t bus_freq = 10000000;
int8_t pin_wr = -1; //
int8_t pin_rd = -1;
int8_t pin_rs = -1; // D/C
bool invert_pclk = false;
int8_t parallel_width = 16; // do not change
union
{
int8_t pin_data[16];
struct
{
int8_t pin_d0;
int8_t pin_d1;
int8_t pin_d2;
int8_t pin_d3;
int8_t pin_d4;
int8_t pin_d5;
int8_t pin_d6;
int8_t pin_d7;
int8_t pin_d8;
int8_t pin_d9;
int8_t pin_d10;
int8_t pin_d11;
int8_t pin_d12;
int8_t pin_d13;
int8_t pin_d14;
int8_t pin_d15;
};
};
};
const config_t& config(void) const { return _cfg; }
void config(const config_t& config);
bool init(void) ;
void release(void) ;
void enable_double_dma_desc();
bool allocate_dma_desc_memory(size_t len);
void create_dma_desc_link(void *memory, size_t size, bool dmadesc_b = false);
void dma_transfer_start();
void dma_transfer_stop();
void set_dma_output_buffer(bool dmadesc_b = false);
private:
void _init_pins() { };
config_t _cfg;
bool _double_dma_buffer = false;
//bool _dmadesc_a_active = true;
uint32_t _dmadesc_count = 0; // number of dma decriptors
uint32_t _dmadesc_a_idx = 0;
uint32_t _dmadesc_b_idx = 0;
HUB75_DMA_DESCRIPTOR_T* _dmadesc_a = nullptr;
HUB75_DMA_DESCRIPTOR_T* _dmadesc_b = nullptr;
volatile i2s_dev_t* _dev;
};

View file

@ -0,0 +1,16 @@
#pragma once
#define R1_PIN_DEFAULT 45
#define G1_PIN_DEFAULT 42
#define B1_PIN_DEFAULT 41
#define R2_PIN_DEFAULT 40
#define G2_PIN_DEFAULT 39
#define B2_PIN_DEFAULT 38
#define A_PIN_DEFAULT 37
#define B_PIN_DEFAULT 36
#define C_PIN_DEFAULT 35
#define D_PIN_DEFAULT 34
#define E_PIN_DEFAULT -1 // required for 1/32 scan panels, like 64x64. Any available pin would do, i.e. IO32
#define LAT_PIN_DEFAULT 26
#define OE_PIN_DEFAULT 21
#define CLK_PIN_DEFAULT 33

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 KiB

View file

@ -0,0 +1,16 @@
https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-guides/external-ram.html
Restrictions
External RAM use has the following restrictions:
When flash cache is disabled (for example, if the flash is being written to), the external RAM also becomes inaccessible; any reads from or writes to it will lead to an illegal cache access exception. This is also the reason why ESP-IDF does not by default allocate any task stacks in external RAM (see below).
External RAM cannot be used as a place to store DMA transaction descriptors or as a buffer for a DMA transfer to read from or write into. Therefore when External RAM is enabled, any buffer that will be used in combination with DMA must be allocated using heap_caps_malloc(size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL) and can be freed using a standard free() call.
*Note, although ESP32-S3 has hardware support for DMA to/from external RAM, this is not yet supported in ESP-IDF.*
External RAM uses the same cache region as the external flash. This means that frequently accessed variables in external RAM can be read and modified almost as quickly as in internal ram. However, when accessing large chunks of data (>32 KB), the cache can be insufficient, and speeds will fall back to the access speed of the external RAM. Moreover, accessing large chunks of data can “push out” cached flash, possibly making the execution of code slower afterwards.
In general, external RAM will not be used as task stack memory. xTaskCreate() and similar functions will always allocate internal memory for stack and task TCBs.

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

View file

@ -0,0 +1,18 @@
#pragma once
// Avoid and QSPI pins
#define R1_PIN_DEFAULT 4
#define G1_PIN_DEFAULT 5
#define B1_PIN_DEFAULT 6
#define R2_PIN_DEFAULT 7
#define G2_PIN_DEFAULT 15
#define B2_PIN_DEFAULT 16
#define A_PIN_DEFAULT 18
#define B_PIN_DEFAULT 8
#define C_PIN_DEFAULT 3
#define D_PIN_DEFAULT 42
#define E_PIN_DEFAULT -1 // required for 1/32 scan panels, like 64x64. Any available pin would do, i.e. IO32
#define LAT_PIN_DEFAULT 40
#define OE_PIN_DEFAULT 2
#define CLK_PIN_DEFAULT 41

View file

@ -0,0 +1,425 @@
/*
Simple example of using the ESP32-S3's LCD peripheral for general-purpose
(non-LCD) parallel data output with DMA. Connect 8 LEDs (or logic analyzer),
cycles through a pattern among them at about 1 Hz.
This code is ONLY for the ESP32-S3, NOT the S2, C3 or original ESP32.
None of this is authoritative canon, just a lot of trial error w/datasheet
and register poking. Probably more robust ways of doing this still TBD.
FULL CREDIT goes to AdaFruit and https://github.com/PaintYourDragon
https://blog.adafruit.com/2022/06/21/esp32uesday-more-s3-lcd-peripheral-hacking-with-code/
https://github.com/adafruit/Adafruit_Protomatter/blob/master/src/arch/esp32-s3.h
PLEASE SUPPORT THEM!
*/
#if __has_include (<hal/lcd_ll.h>)
// Stop compile errors: /src/platforms/esp32s3/gdma_lcd_parallel16.hpp:64:10: fatal error: hal/lcd_ll.h: No such file or directory
#include <Arduino.h>
#include "gdma_lcd_parallel16.hpp"
static const char* TAG = "gdma_lcd_parallel16";
static int _dmadesc_a_idx = 0;
static int _dmadesc_b_idx = 0;
dma_descriptor_t desc; // DMA descriptor for testing
/*
uint8_t data[8][312]; // Transmit buffer (2496 bytes total)
uint16_t* dmabuff2;
*/
// End-of-DMA-transfer callback
static IRAM_ATTR bool dma_callback(gdma_channel_handle_t dma_chan,
gdma_event_data_t *event_data, void *user_data) {
// This DMA callback seems to trigger a moment before the last data has
// issued (buffering between DMA & LCD peripheral?), so pause a moment
// before stopping LCD data out. The ideal delay may depend on the LCD
// clock rate...this one was determined empirically by monitoring on a
// logic analyzer. YMMV.
esp_rom_delay_us(100);
// The LCD peripheral stops transmitting at the end of the DMA xfer, but
// clear the lcd_start flag anyway -- we poll it in loop() to decide when
// the transfer has finished, and the same flag is set later to trigger
// the next transfer.
LCD_CAM.lcd_user.lcd_start = 0;
return true;
}
static lcd_cam_dev_t* getDev(int port)
{
return &LCD_CAM;
}
// ------------------------------------------------------------------------------
void Bus_Parallel16::config(const config_t& cfg)
{
_cfg = cfg;
auto port = cfg.port;
_dev = getDev(port);
}
//https://github.com/adafruit/Adafruit_Protomatter/blob/master/src/arch/esp32-s3.h
bool Bus_Parallel16::init(void)
{
///dmabuff2 = (uint16_t*)heap_caps_malloc(sizeof(uint16_t) * 64*32, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
// LCD_CAM peripheral isn't enabled by default -- MUST begin with this:
periph_module_enable(PERIPH_LCD_CAM_MODULE);
periph_module_reset(PERIPH_LCD_CAM_MODULE);
// Reset LCD bus
LCD_CAM.lcd_user.lcd_reset = 1;
esp_rom_delay_us(100);
uint32_t lcd_clkm_div_num = ((160000000 + 1) / _cfg.bus_freq) / 2;
ESP_LOGI(TAG, "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
// slowest LCD clock rate possible. The S3-mini module used on Feather
// ESP32-S3 has a 40 MHz crystal. A 2-stage clock division of 1:16000
// is applied (250*64), yielding 2,500 Hz. Still much too fast for
// human eyes, so later we set up the data to repeat each output byte
// many times over.
LCD_CAM.lcd_clock.clk_en = 1; // Enable peripheral clock
LCD_CAM.lcd_clock.lcd_clk_sel = 2; // 160mhz source
LCD_CAM.lcd_clock.lcd_ck_out_edge = 0; // PCLK low in 1st half cycle
LCD_CAM.lcd_clock.lcd_ck_idle_edge = 0; // PCLK low idle
LCD_CAM.lcd_clock.lcd_clk_equ_sysclk = 0; // PCLK = CLK / (CLKCNT_N+1)
LCD_CAM.lcd_clock.lcd_clkm_div_num = lcd_clkm_div_num; // 1st stage 1:250 divide
LCD_CAM.lcd_clock.lcd_clkm_div_a = 1; // 0/1 fractional divide
LCD_CAM.lcd_clock.lcd_clkm_div_b = 0;
// See section 26.3.3.1 of the ESP32­S3 Technical Reference Manual
// for information on other clock sources and dividers.
// Configure LCD frame format. This is where we fiddle the peripheral
// to provide generic 8-bit output rather than actually driving an LCD.
// There's also a 16-bit mode but that's not shown here.
LCD_CAM.lcd_ctrl.lcd_rgb_mode_en = 0; // i8080 mode (not RGB)
LCD_CAM.lcd_rgb_yuv.lcd_conv_bypass = 0; // Disable RGB/YUV converter
LCD_CAM.lcd_misc.lcd_next_frame_en = 0; // Do NOT auto-frame
LCD_CAM.lcd_data_dout_mode.val = 0; // No data delays
LCD_CAM.lcd_user.lcd_always_out_en = 1; // Enable 'always out' mode
LCD_CAM.lcd_user.lcd_8bits_order = 0; // Do not swap bytes
LCD_CAM.lcd_user.lcd_bit_order = 0; // Do not reverse bit order
LCD_CAM.lcd_user.lcd_2byte_en = 1; // 8-bit data mode
LCD_CAM.lcd_user.lcd_dummy = 0; // Dummy phase(s) @ LCD start
LCD_CAM.lcd_user.lcd_dummy_cyclelen = 0; // 1 dummy phase
LCD_CAM.lcd_user.lcd_cmd = 0; // No command at LCD start
// "Dummy phases" are initial LCD peripheral clock cycles before data
// begins transmitting when requested. After much testing, determined
// that at least one dummy phase MUST be enabled for DMA to trigger
// reliably. A problem with dummy phase(s) is if we're also using the
// LCD_PCLK_IDX signal (not used in this code, but Adafruit_Protomatter
// does)...the clock signal will start a couple of pulses before data,
// which may or may not be problematic in some situations. You can
// disable the dummy phase but need to keep the LCD TX FIFO primed
// in that case, which gets complex.
// always_out_en is set above to allow aribtrary-length transfers,
// else lcd_dout_cyclelen is used...but is limited to 8K. Long (>4K)
// transfers need DMA linked lists, not used here but mentioned later.
// Route 8 LCD data signals to GPIO pins
int8_t* pins = _cfg.pin_data;
for(int i = 0; i < 16; i++)
{
if (pins[i] >= 0) { // -1 value will CRASH the ESP32!
esp_rom_gpio_connect_out_signal(pins[i], LCD_DATA_OUT0_IDX + i, false, false);
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[pins[i]], PIN_FUNC_GPIO);
gpio_set_drive_capability((gpio_num_t)pins[i], (gpio_drive_cap_t)3);
}
}
/*
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 = {
.sibling_chan = NULL,
.direction = GDMA_CHANNEL_DIRECTION_TX,
.flags = {
.reserve_sibling = 0
}
};
gdma_new_channel(&dma_chan_config, &dma_chan);
gdma_connect(dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0));
static gdma_strategy_config_t strategy_config = {
.owner_check = false,
.auto_update_desc = false
};
gdma_apply_strategy(dma_chan, &strategy_config);
gdma_transfer_ability_t ability = {
.sram_trans_align = 0,
.psram_trans_align = 0,
};
gdma_set_transfer_ability(dma_chan, &ability);
// Enable DMA transfer callback
/*
static gdma_tx_event_callbacks_t tx_cbs = {
.on_trans_eof = dma_callback
};
gdma_register_tx_event_callbacks(dma_chan, &tx_cbs, NULL);
*/
// As mentioned earlier, the slowest clock we can get to the LCD
// peripheral is 40 MHz / 250 / 64 = 2500 Hz. To make an even slower
// bit pattern that's perceptible, we just repeat each value many
// times over. The pattern here just counts through each of 8 bits
// (each LED lights in sequence)...so to get this to repeat at about
// 1 Hz, each LED is lit for 2500/8 or 312 cycles, hence the
// data[8][312] declaration at the start of this code (it's not
// precisely 1 Hz because reality is messy, but sufficient for demo).
// In actual use, say controlling an LED matrix or NeoPixels, such
// shenanigans aren't necessary, as these operate at multiple MHz
// with much smaller clock dividers and can use 1 byte per datum.
/*
for (int i = 0; i < (sizeof(data) / sizeof(data[0])); i++) { // 0 to 7
for (int j = 0; j < sizeof(data[0]); j++) { // 0 to 311
data[i][j] = 1 << i;
}
}
*/
// This uses a busy loop to wait for each DMA transfer to complete...
// but the whole point of DMA is that one's code can do other work in
// the interim. The CPU is totally free while the transfer runs!
while (LCD_CAM.lcd_user.lcd_start); // Wait for DMA completion callback
// After much experimentation, each of these steps is required to get
// a clean start on the next LCD transfer:
gdma_reset(dma_chan); // Reset DMA to known state
LCD_CAM.lcd_user.lcd_dout = 1; // Enable data out
LCD_CAM.lcd_user.lcd_update = 1; // Update registers
LCD_CAM.lcd_misc.lcd_afifo_reset = 1; // Reset LCD TX FIFO
// This program happens to send the same data over and over...but,
// if desired, one could fill the data buffer with a new bit pattern
// here, or point to a completely different buffer each time through.
// With two buffers, one can make best use of time by filling each
// with new data before the busy loop above, alternating between them.
// Reset elements of DMA descriptor. Just one in this code, long
// transfers would loop through a linked list.
/*
desc.dw0.size = desc.dw0.length = sizeof(data);
desc.buffer = dmabuff2; //data;
desc.next = &desc;
*/
/*
//gdma_start(dma_chan, (intptr_t)&desc); // Start DMA w/updated descriptor(s)
gdma_start(dma_chan, (intptr_t)&_dmadesc_a[0]); // Start DMA w/updated descriptor(s)
esp_rom_delay_us(100); // Must 'bake' a moment before...
LCD_CAM.lcd_user.lcd_start = 1; // Trigger LCD DMA transfer
*/
return true; // no return val = illegal instruction
}
void Bus_Parallel16::release(void)
{
if (_i80_bus)
{
esp_lcd_del_i80_bus(_i80_bus);
}
if (_dmadesc_a)
{
heap_caps_free(_dmadesc_a);
_dmadesc_a = nullptr;
_dmadesc_count = 0;
}
}
void Bus_Parallel16::enable_double_dma_desc(void)
{
ESP_LOGI(TAG, "Enabled support for secondary DMA buffer.");
_double_dma_buffer = true;
}
// Need this to work for double buffers etc.
bool Bus_Parallel16::allocate_dma_desc_memory(size_t len)
{
if (_dmadesc_a) heap_caps_free(_dmadesc_a); // free all dma descrptios previously
_dmadesc_count = len;
ESP_LOGD(TAG, "Allocating %d bytes memory for DMA descriptors.", sizeof(HUB75_DMA_DESCRIPTOR_T) * len);
_dmadesc_a= (HUB75_DMA_DESCRIPTOR_T*)heap_caps_malloc(sizeof(HUB75_DMA_DESCRIPTOR_T) * len, MALLOC_CAP_DMA);
if (_dmadesc_a == nullptr)
{
ESP_LOGE(TAG, "ERROR: Couldn't malloc _dmadesc_a. Not enough memory.");
return false;
}
if (_double_dma_buffer)
{
_dmadesc_b= (HUB75_DMA_DESCRIPTOR_T*)heap_caps_malloc(sizeof(HUB75_DMA_DESCRIPTOR_T) * len, MALLOC_CAP_DMA);
if (_dmadesc_b == nullptr)
{
ESP_LOGE(TAG, "ERROR: Couldn't malloc _dmadesc_b. Not enough memory.");
_double_dma_buffer = false;
}
}
/// override static
_dmadesc_a_idx = 0;
_dmadesc_b_idx = 0;
return true;
}
void Bus_Parallel16::create_dma_desc_link(void *data, size_t size, bool dmadesc_b)
{
static constexpr size_t MAX_DMA_LEN = (4096-4);
if (size > MAX_DMA_LEN) {
size = MAX_DMA_LEN;
ESP_LOGW(TAG, "Creating DMA descriptor which links to payload with size greater than MAX_DMA_LEN!");
}
if ( dmadesc_b == true)
{
// ESP_LOGI(TAG, "Creating dma desc B %d", _dmadesc_b_idx);
_dmadesc_b[_dmadesc_b_idx].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
_dmadesc_b[_dmadesc_b_idx].dw0.suc_eof = 0;
_dmadesc_b[_dmadesc_b_idx].dw0.size = _dmadesc_b[_dmadesc_b_idx].dw0.length = size; //sizeof(data);
_dmadesc_b[_dmadesc_b_idx].buffer = data; //data;
if (_dmadesc_b_idx == _dmadesc_count-1) {
_dmadesc_b[_dmadesc_b_idx].next = (dma_descriptor_t *) &_dmadesc_b[0];
}
else {
_dmadesc_b[_dmadesc_b_idx].next = (dma_descriptor_t *) &_dmadesc_b[_dmadesc_b_idx+1];
}
_dmadesc_b_idx++;
}
else
{
// ESP_LOGI(TAG, "Creating dma desc A %d", _dmadesc_a_idx);
if ( _dmadesc_a_idx >= _dmadesc_count)
{
ESP_LOGE(TAG, "Attempted to create more DMA descriptors than allocated. Expecting max %d descriptors.", _dmadesc_count);
return;
}
_dmadesc_a[_dmadesc_a_idx].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
_dmadesc_a[_dmadesc_a_idx].dw0.suc_eof = 0;
_dmadesc_a[_dmadesc_a_idx].dw0.size = _dmadesc_a[_dmadesc_a_idx].dw0.length = size; //sizeof(data);
_dmadesc_a[_dmadesc_a_idx].buffer = data; //data;
if (_dmadesc_a_idx == _dmadesc_count-1) {
_dmadesc_a[_dmadesc_a_idx].next = (dma_descriptor_t *) &_dmadesc_a[0];
}
else {
_dmadesc_a[_dmadesc_a_idx].next = (dma_descriptor_t *) &_dmadesc_a[_dmadesc_a_idx+1];
}
_dmadesc_a_idx++;
}
} // end create_dma_desc_link
void Bus_Parallel16::dma_transfer_start()
{
gdma_start(dma_chan, (intptr_t)&_dmadesc_a[0]); // Start DMA w/updated descriptor(s)
esp_rom_delay_us(100); // Must 'bake' a moment before...
LCD_CAM.lcd_user.lcd_start = 1; // Trigger LCD DMA transfer
} // end
void Bus_Parallel16::dma_transfer_stop()
{
LCD_CAM.lcd_user.lcd_reset = 1; // Trigger LCD DMA transfer
LCD_CAM.lcd_user.lcd_update = 1; // Trigger LCD DMA transfer
gdma_stop(dma_chan);
} // end
void Bus_Parallel16::set_dma_output_buffer(bool dmadesc_b)
{
if ( _double_dma_buffer == false) return;
if ( dmadesc_b == true) // 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];
}
else
{
_dmadesc_a[_dmadesc_count-1].next = (dma_descriptor_t *) &_dmadesc_a[0];
_dmadesc_b[_dmadesc_count-1].next = (dma_descriptor_t *) &_dmadesc_a[0];
}
} // end flip
#endif

View file

@ -0,0 +1,174 @@
/*
Simple example of using the ESP32-S3's LCD peripheral for general-purpose
(non-LCD) parallel data output with DMA. Connect 8 LEDs (or logic analyzer),
cycles through a pattern among them at about 1 Hz.
This code is ONLY for the ESP32-S3, NOT the S2, C3 or original ESP32.
None of this is authoritative canon, just a lot of trial error w/datasheet
and register poking. Probably more robust ways of doing this still TBD.
FULL CREDIT goes to AdaFruit
https://blog.adafruit.com/2022/06/21/esp32uesday-more-s3-lcd-peripheral-hacking-with-code/
PLEASE SUPPORT THEM!
Putins Russia and its genocide in Ukraine is a disgrace to humanity.
https://www.reddit.com/r/ukraine/comments/xfuc6v/more_than_460_graves_have_already_been_found_in/
/----------------------------------------------------------------------------
*/
#pragma once
#if __has_include (<hal/lcd_ll.h>)
#include <sdkconfig.h>
#include <esp_lcd_panel_io.h>
//#include <freertos/portmacro.h>
#include <esp_intr_alloc.h>
#include <esp_err.h>
#include <esp_log.h>
#include <driver/gpio.h>
#include <soc/gpio_sig_map.h>
#include <hal/gpio_ll.h>
#include <hal/lcd_hal.h>
#include <string.h>
#include <math.h>
#include <stdint.h>
#if (ESP_IDF_VERSION_MAJOR == 5)
#include <esp_private/periph_ctrl.h>
#else
#include <driver/periph_ctrl.h>
#endif
#include <esp_private/gdma.h>
#include <esp_rom_gpio.h>
#include <hal/dma_types.h>
#include <hal/gpio_hal.h>
#include <hal/lcd_ll.h>
#include <soc/lcd_cam_reg.h>
#include <soc/lcd_cam_struct.h>
#include <esp_heap_caps.h>
#include <esp_heap_caps_init.h>
#if __has_include (<esp_private/periph_ctrl.h>)
#include <esp_private/periph_ctrl.h>
#else
#include <driver/periph_ctrl.h>
#endif
#if __has_include(<esp_arduino_version.h>)
#include <esp_arduino_version.h>
#endif
#define DMA_MAX (4096-4)
// The type used for this SoC
#define HUB75_DMA_DESCRIPTOR_T dma_descriptor_t
//----------------------------------------------------------------------------
class Bus_Parallel16
{
public:
Bus_Parallel16()
{
}
struct config_t
{
// LCD_CAM peripheral number. No need to change (only 0 for ESP32-S3.)
int port = 0;
// max 40MHz (when in 16 bit / 2 byte mode)
uint32_t bus_freq = 20000000;
int8_t pin_wr = -1;
int8_t pin_rd = -1;
int8_t pin_rs = -1; // D/C
bool invert_pclk = false;
union
{
int8_t pin_data[16];
struct
{
int8_t pin_d0;
int8_t pin_d1;
int8_t pin_d2;
int8_t pin_d3;
int8_t pin_d4;
int8_t pin_d5;
int8_t pin_d6;
int8_t pin_d7;
int8_t pin_d8;
int8_t pin_d9;
int8_t pin_d10;
int8_t pin_d11;
int8_t pin_d12;
int8_t pin_d13;
int8_t pin_d14;
int8_t pin_d15;
};
};
};
const config_t& config(void) const { return _cfg; }
void config(const config_t& config);
bool init(void) ;
void release(void) ;
void enable_double_dma_desc();
bool allocate_dma_desc_memory(size_t len);
void create_dma_desc_link(void *memory, size_t size, bool dmadesc_b = false);
void dma_transfer_start();
void dma_transfer_stop();
void set_dma_output_buffer(bool dmadesc_b = false);
private:
config_t _cfg;
volatile lcd_cam_dev_t* _dev;
gdma_channel_handle_t dma_chan;
uint32_t _dmadesc_count = 0; // number of dma decriptors
// uint32_t _dmadesc_a_idx = 0;
//uint32_t _dmadesc_b_idx = 0;
HUB75_DMA_DESCRIPTOR_T* _dmadesc_a = nullptr;
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;
};
#endif

View file

@ -0,0 +1,57 @@
/*----------------------------------------------------------------------------/
Original Source:
https://github.com/lovyan03/LovyanGFX/
Licence:
[FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt)
Author:
[lovyan03](https://twitter.com/lovyan03)
Contributors:
[ciniml](https://github.com/ciniml)
[mongonta0716](https://github.com/mongonta0716)
[tobozo](https://github.com/tobozo)
Modified heavily for the ESP32 HUB75 DMA library by:
[mrfaptastic](https://github.com/mrfaptastic)
/----------------------------------------------------------------------------*/
#pragma once
#if defined (ESP_PLATFORM)
#include <sdkconfig.h>
#if defined (CONFIG_IDF_TARGET_ESP32C3)
#error "ERROR: ESP32C3 not supported."
#elif defined (CONFIG_IDF_TARGET_ESP32S2)
#pragma message "Compiling for ESP32-S2"
#include "esp32/esp32_i2s_parallel_dma.hpp"
#include "esp32s2/esp32s2-default-pins.hpp"
#elif defined (CONFIG_IDF_TARGET_ESP32S3)
#pragma message "Compiling for ESP32-S3"
#include "esp32s3/gdma_lcd_parallel16.hpp"
#include "esp32s3/esp32s3-default-pins.hpp"
#elif defined (CONFIG_IDF_TARGET_ESP32)
// Assume an ESP32 (the original 2015 version)
// Same include as ESP32S3
#pragma message "Compiling for original ESP32 (2015 release)"
#define ESP32_THE_ORIG 1
//#include "esp32/esp32_i2s_parallel_dma.hpp"
//#include "esp32/esp32_i2s_parallel_dma.h"
#include "esp32/esp32_i2s_parallel_dma.hpp"
#include "esp32/esp32-default-pins.hpp"
#endif
#endif