2018-10-23 02:00:47 +02:00
# ifndef _ESP32_RGB_64_32_MATRIX_PANEL_I2S_DMA
# define _ESP32_RGB_64_32_MATRIX_PANEL_I2S_DMA
2021-09-19 23:46:25 +02:00
/***************************************************************************************/
/* Core ESP32 hardware / idf includes! */
# include <vector>
# include <memory>
2022-10-23 13:41:45 +02:00
# include <esp_err.h>
# include <esp_log.h>
2022-12-17 12:54:54 +01:00
# include "esp_attr.h"
2022-10-23 13:41:45 +02:00
2022-10-23 13:30:26 +02:00
//#include <Arduino.h>
2022-09-30 04:17:19 +02:00
# include "platforms/platform_detect.hpp"
2021-09-19 23:46:25 +02:00
# ifdef USE_GFX_ROOT
# include <FastLED.h>
# include "GFX.h" // Adafruit GFX core class -> https://github.com/mrfaptastic/GFX_Root
# elif !defined NO_GFX
# include "Adafruit_GFX.h" // Adafruit class with all the other stuff
# endif
2018-10-23 02:00:47 +02:00
2021-09-06 17:29:38 +02:00
/*******************************************************************************************
* COMPILE - TIME OPTIONS - MUST BE PROVIDED as part of PlatformIO project build_flags . *
* Changing the values just here won ' t work - as defines needs to persist beyond the scope *
* of just this file . *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Do NOT build additional methods optimized for fast drawing,
* i . e . Adafruits drawFastHLine , drawFastVLine , etc . . . */
2022-09-30 04:17:19 +02:00
// #define NO_FAST_FUNCTIONS
2021-02-10 16:49:19 +01:00
2022-09-30 04:17:19 +02:00
// #define NO_CIE1931
2021-03-28 18:34:20 +02:00
2020-07-30 09:37:39 +02:00
/* Physical / Chained HUB75(s) RGB pixel WIDTH and HEIGHT.
2019-01-10 00:51:27 +01:00
*
2021-02-10 16:49:19 +01:00
* This library has been tested with a 64 x32 and 64 x64 RGB panels .
* If you want to chain two or more of these horizontally to make a 128 x32 panel
* you can do so with the cable and then set the CHAIN_LENGTH to ' 2 ' .
2019-01-10 00:51:27 +01:00
*
2020-07-30 09:37:39 +02:00
* Also , if you use a 64 x64 panel , then set the MATRIX_HEIGHT to ' 64 ' and an E_PIN ; it will work !
2019-01-10 00:51:27 +01:00
*
* All of this is memory permitting of course ( dependant on your sketch etc . ) . . .
*
*/
2020-11-28 01:19:20 +01:00
# ifndef MATRIX_WIDTH
2020-12-16 18:52:11 +01:00
# define MATRIX_WIDTH 64 // Single panel of 64 pixel width
2020-11-28 01:19:20 +01:00
# endif
2020-03-28 10:47:27 +01:00
2020-11-28 01:19:20 +01:00
# ifndef MATRIX_HEIGHT
2020-12-16 18:52:11 +01:00
# define MATRIX_HEIGHT 32 // CHANGE THIS VALUE to 64 IF USING 64px HIGH panel(s) with E PIN
2020-11-28 01:19:20 +01:00
# endif
2020-07-29 09:47:54 +02:00
2021-02-10 16:49:19 +01:00
# ifndef CHAIN_LENGTH
# define CHAIN_LENGTH 1 // Number of modules chained together, i.e. 4 panels chained result in virtualmatrix 64x4=256 px long
2020-11-28 01:19:20 +01:00
# endif
2018-10-23 02:00:47 +02:00
2020-07-29 09:47:54 +02:00
// 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.
2020-07-30 09:37:39 +02:00
2018-10-23 02:00:47 +02:00
/***************************************************************************************/
2021-02-10 16:49:19 +01:00
/* Do not change definitions below unless you pretty sure you know what you are doing! */
2020-07-30 09:37:39 +02:00
2021-02-10 16:49:19 +01:00
// RGB Panel Constants / Calculated Values
# ifndef MATRIX_ROWS_IN_PARALLEL
# define MATRIX_ROWS_IN_PARALLEL 2
# endif
2020-07-30 09:37:39 +02:00
2021-02-10 16:49:19 +01:00
// 8bit per RGB color = 24 bit/per pixel,
2023-02-08 15:33:14 +01:00
// can be extended to offer deeper colors, or
2021-02-20 16:26:20 +01:00
// might be reduced to save DMA RAM
2023-01-28 22:54:09 +01:00
# ifdef PIXEL_COLOUR_DEPTH_BITS
# define PIXEL_COLOR_DEPTH_BITS PIXEL_COLOUR_DEPTH_BITS
2021-02-10 16:49:19 +01:00
# endif
2020-07-30 09:37:39 +02:00
2023-01-28 22:54:09 +01:00
# ifndef PIXEL_COLOR_DEPTH_BITS
# define PIXEL_COLOR_DEPTH_BITS 8
# endif
2020-07-30 09:37:39 +02:00
/***************************************************************************************/
2021-02-14 15:12:45 +01:00
/* Definitions below should NOT be ever changed without rewriting library logic */
# define ESP32_I2S_DMA_STORAGE_TYPE uint16_t // DMA output of one uint16_t at a time.
2021-12-21 23:19:59 +01:00
# define CLKS_DURING_LATCH 0 // Not (yet) used.
2018-10-23 02:00:47 +02:00
// Panel Upper half RGB (numbering according to order in DMA gpio_bus configuration)
2020-12-08 09:10:35 +01:00
# define BITS_RGB1_OFFSET 0 // Start point of RGB_X1 bits
2018-10-23 02:00:47 +02:00
# define BIT_R1 (1<<0)
# define BIT_G1 (1<<1)
# define BIT_B1 (1<<2)
// Panel Lower half RGB
2020-12-08 09:10:35 +01:00
# define BITS_RGB2_OFFSET 3 // Start point of RGB_X2 bits
2018-10-23 02:00:47 +02:00
# define BIT_R2 (1<<3)
# define BIT_G2 (1<<4)
# define BIT_B2 (1<<5)
// Panel Control Signals
# define BIT_LAT (1<<6)
# define BIT_OE (1<<7)
// Panel GPIO Pin Addresses (A, B, C, D etc..)
2020-12-08 09:10:35 +01:00
# define BITS_ADDR_OFFSET 8 // Start point of address bits
2018-10-23 02:00:47 +02:00
# define BIT_A (1<<8)
# define BIT_B (1<<9)
# define BIT_C (1<<10)
# define BIT_D (1<<11)
# define BIT_E (1<<12)
2021-02-10 16:49:19 +01:00
// BitMasks are pre-computed based on the above #define's for performance.
# define BITMASK_RGB1_CLEAR (0b1111111111111000) // inverted bitmask for R1G1B1 bit in pixel vector
# define BITMASK_RGB2_CLEAR (0b1111111111000111) // inverted bitmask for R2G2B2 bit in pixel vector
# define BITMASK_RGB12_CLEAR (0b1111111111000000) // inverted bitmask for R1G1B1R2G2B2 bit in pixel vector
# define BITMASK_CTRL_CLEAR (0b1110000000111111) // inverted bitmask for control bits ABCDE,LAT,OE in pixel vector
# define BITMASK_OE_CLEAR (0b1111111101111111) // inverted bitmask for control bit OE in pixel vector
2022-10-26 17:49:49 +02:00
// How many clock cycles to blank OE before/after LAT signal change, default is 2 clocks
# define DEFAULT_LAT_BLANKING 2
2022-10-05 22:47:16 +02:00
2021-02-10 16:49:19 +01:00
// Max clock cycles to blank OE before/after LAT signal change
# define MAX_LAT_BLANKING 4
2020-12-07 10:25:29 +01:00
2021-02-10 16:49:19 +01:00
/***************************************************************************************/
2019-01-10 00:51:27 +01:00
2021-10-16 16:00:43 +02:00
/** @brief - Structure holds raw DMA data to drive TWO full rows of pixels spanning through all chained modules
2021-02-10 16:49:19 +01:00
* Note : sizeof ( data ) must be multiple of 32 bits , as ESP32 DMA linked list buffer address pointer must be word - aligned
2020-07-29 09:47:54 +02:00
*/
2018-10-23 02:00:47 +02:00
struct rowBitStruct {
2021-02-10 16:49:19 +01:00
const size_t width ;
2022-10-05 22:47:16 +02:00
const uint8_t colour_depth ;
2021-02-10 16:49:19 +01:00
const bool double_buff ;
ESP32_I2S_DMA_STORAGE_TYPE * data ;
/** @brief - returns size of row of data vectorfor a SINGLE buff
2023-01-28 22:54:09 +01:00
* size ( in bytes ) of a vector holding full DMA data for a row of pixels with _dpth colour bits
2021-02-10 16:49:19 +01:00
* a SINGLE buffer only size is accounted , when using double buffers it actually takes twice as much space
* but returned size is for a half of double - buffer
*
* default - returns full data vector size for a SINGLE buff
*
*/
2022-10-05 22:47:16 +02:00
size_t size ( uint8_t _dpth = 0 ) { if ( ! _dpth ) _dpth = colour_depth ; return width * _dpth * sizeof ( ESP32_I2S_DMA_STORAGE_TYPE ) ; } ;
2018-10-23 02:00:47 +02:00
2023-01-28 22:54:09 +01:00
/** @brief - returns pointer to the row's data vector beginning at pixel[0] for _dpth colour bit
2021-02-10 16:49:19 +01:00
* default - returns pointer to the data vector ' s head
* NOTE : this call might be very slow in loops . Due to poor instruction caching in esp32 it might be required a reread from flash
* every loop cycle , better use inlined # define instead in such cases
*/
2022-10-05 22:47:16 +02:00
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 ) ] ) ; } ;
2021-02-10 16:49:19 +01:00
// constructor - allocates DMA-capable memory to hold the struct data
2022-10-05 22:47:16 +02:00
rowBitStruct ( const size_t _width , const uint8_t _depth , const bool _dbuff ) : width ( _width ) , colour_depth ( _depth ) , double_buff ( _dbuff ) {
2022-09-30 04:17:19 +02:00
2022-11-17 01:45:40 +01:00
//#if defined(SPIRAM_FRAMEBUFFER) && defined (CONFIG_IDF_TARGET_ESP32S3)
# if defined(SPIRAM_DMA_BUFFER)
// #pragma message "Enabling PSRAM / SPIRAM for frame buffer."
// ESP_LOGI("rowBitStruct", "Allocated DMA BitBuffer from PSRAM (SPIRAM)");
//data = (ESP32_I2S_DMA_STORAGE_TYPE *)heap_caps_malloc( size()+size()*double_buff, MALLOC_CAP_SPIRAM);
data = ( ESP32_I2S_DMA_STORAGE_TYPE * ) heap_caps_aligned_alloc ( 64 , size ( ) + size ( ) * double_buff , MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT ) ;
2022-10-23 17:32:29 +02:00
/*
if ( ! psramFound ( ) )
{
ESP_LOGE ( " rowBitStruct " , " Requested to use PSRAM / SPIRAM for framebuffer, but it was not detected. " ) ;
}
*/
2022-09-30 04:17:19 +02:00
# else
2022-10-23 17:32:29 +02:00
data = ( ESP32_I2S_DMA_STORAGE_TYPE * ) heap_caps_malloc ( size ( ) + size ( ) * double_buff , MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ) ;
2022-11-17 01:45:40 +01:00
// ESP_LOGI("rowBitStruct", "Allocated DMA BitBuffer from regular (and limited) SRAM");
2022-09-30 04:17:19 +02:00
# endif
2021-02-10 16:49:19 +01:00
}
~ rowBitStruct ( ) { delete data ; }
2018-10-23 02:00:47 +02:00
} ;
2021-02-10 16:49:19 +01:00
2020-07-29 09:47:54 +02:00
/* frameStruct
2020-08-13 00:40:44 +02:00
* Note : A ' frameStruct ' contains ALL the data for a full - frame ( i . e . BOTH 2 x16 - row frames are
* are contained in parallel within the one uint16_t that is sent in parallel to the HUB75 ) .
*
* This structure isn ' t actually allocated in one memory block anymore , as the library now allocates
2023-01-28 22:54:09 +01:00
* memory per row ( per rowBits ) instead .
2020-07-29 09:47:54 +02:00
*/
2018-10-23 02:00:47 +02:00
struct frameStruct {
2021-02-10 16:49:19 +01:00
uint8_t rows = 0 ; // number of rows held in current frame, not used actually, just to keep the idea of struct
std : : vector < std : : shared_ptr < rowBitStruct > > rowBits ;
2018-10-23 02:00:47 +02:00
} ;
2020-08-11 21:44:47 +02:00
/***************************************************************************************/
//C/p'ed from https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/
// Example calculator: https://gist.github.com/mathiasvr/19ce1d7b6caeab230934080ae1f1380e
2021-02-10 16:49:19 +01:00
// need to make sure this would end up in RAM for fastest access
2021-02-20 16:26:20 +01:00
# ifndef NO_CIE1931
2023-02-08 15:33:14 +01:00
/*
2021-02-10 16:49:19 +01:00
static const uint8_t DRAM_ATTR lumConvTab [ ] = {
2020-08-11 21:44:47 +02:00
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 4 , 4 , 4 , 4 , 4 , 5 , 5 , 5 , 5 , 5 , 6 , 6 , 6 , 6 , 6 , 7 , 7 , 7 , 7 , 8 , 8 , 8 , 8 , 9 , 9 , 9 , 10 , 10 , 10 , 11 , 11 , 11 , 12 , 12 , 12 , 13 , 13 , 13 , 14 , 14 , 14 , 15 , 15 , 16 , 16 , 17 , 17 , 17 , 18 , 18 , 19 , 19 , 20 , 20 , 21 , 21 , 22 , 22 , 23 , 23 , 24 , 24 , 25 , 25 , 26 , 27 , 27 , 28 , 28 , 29 , 30 , 30 , 31 , 31 , 32 , 33 , 33 , 34 , 35 , 35 , 36 , 37 , 38 , 38 , 39 , 40 , 41 , 41 , 42 , 43 , 44 , 45 , 45 , 46 , 47 , 48 , 49 , 50 , 51 , 51 , 52 , 53 , 54 , 55 , 56 , 57 , 58 , 59 , 60 , 61 , 62 , 63 , 64 , 65 , 66 , 67 , 68 , 69 , 70 , 71 , 73 , 74 , 75 , 76 , 77 , 78 , 80 , 81 , 82 , 83 , 84 , 86 , 87 , 88 , 90 , 91 , 92 , 93 , 95 , 96 , 98 , 99 , 100 , 102 , 103 , 105 , 106 , 107 , 109 , 110 , 112 , 113 , 115 , 116 , 118 , 120 , 121 , 123 , 124 , 126 , 128 , 129 , 131 , 133 , 134 , 136 , 138 , 139 , 141 , 143 , 145 , 146 , 148 , 150 , 152 , 154 , 156 , 157 , 159 , 161 , 163 , 165 , 167 , 169 , 171 , 173 , 175 , 177 , 179 , 181 , 183 , 185 , 187 , 189 , 192 , 194 , 196 , 198 , 200 , 203 , 205 , 207 , 209 , 212 , 214 , 216 , 218 , 221 , 223 , 226 , 228 , 230 , 233 , 235 , 238 , 240 , 243 , 245 , 248 , 250 , 253 , 255 , 255 } ;
2023-02-08 15:33:14 +01:00
*/
// This is 16-bit version of the table,
// the constants taken from the example in the article above, each entries subtracted from 65535:
static const uint16_t DRAM_ATTR lumConvTab [ ] = {
0 , 27 , 56 , 84 , 113 , 141 , 170 , 198 , 227 , 255 , 284 , 312 , 340 , 369 , 397 , 426 ,
454 , 483 , 511 , 540 , 568 , 597 , 626 , 657 , 688 , 720 , 754 , 788 , 824 , 860 , 898 , 936 ,
976 , 1017 , 1059 , 1102 , 1146 , 1191 , 1238 , 1286 , 1335 , 1385 , 1436 , 1489 , 1543 , 1598 , 1655 , 1713 ,
1772 , 1833 , 1895 , 1958 , 2023 , 2089 , 2156 , 2225 , 2296 , 2368 , 2441 , 2516 , 2592 , 2670 , 2750 , 2831 ,
2914 , 2998 , 3084 , 3171 , 3260 , 3351 , 3443 , 3537 , 3633 , 3731 , 3830 , 3931 , 4034 , 4138 , 4245 , 4353 ,
4463 , 4574 , 4688 , 4803 , 4921 , 5040 , 5161 , 5284 , 5409 , 5536 , 5665 , 5796 , 5929 , 6064 , 6201 , 6340 ,
6482 , 6625 , 6770 , 6917 , 7067 , 7219 , 7372 , 7528 , 7687 , 7847 , 8010 , 8174 , 8341 , 8511 , 8682 , 8856 ,
9032 , 9211 , 9392 , 9575 , 9761 , 9949 , 10139 , 10332 , 10527 , 10725 , 10925 , 11127 , 11332 , 11540 , 11750 , 11963 ,
12178 , 12395 , 12616 , 12839 , 13064 , 13292 , 13523 , 13757 , 13993 , 14231 , 14473 , 14717 , 14964 , 15214 , 15466 , 15722 ,
15980 , 16240 , 16504 , 16771 , 17040 , 17312 , 17587 , 17865 , 18146 , 18430 , 18717 , 19006 , 19299 , 19595 , 19894 , 20195 ,
20500 , 20808 , 21119 , 21433 , 21750 , 22070 , 22393 , 22720 , 23049 , 23382 , 23718 , 24057 , 24400 , 24745 , 25094 , 25446 ,
25802 , 26160 , 26522 , 26888 , 27256 , 27628 , 28004 , 28382 , 28765 , 29150 , 29539 , 29932 , 30328 , 30727 , 31130 , 31536 ,
31946 , 32360 , 32777 , 33197 , 33622 , 34049 , 34481 , 34916 , 35354 , 35797 , 36243 , 36692 , 37146 , 37603 , 38064 , 38528 ,
38996 , 39469 , 39945 , 40424 , 40908 , 41395 , 41886 , 42382 , 42881 , 43383 , 43890 , 44401 , 44916 , 45434 , 45957 , 46484 ,
47014 , 47549 , 48088 , 48630 , 49177 , 49728 , 50283 , 50842 , 51406 , 51973 , 52545 , 53120 , 53700 , 54284 , 54873 , 55465 ,
56062 , 56663 , 57269 , 57878 , 58492 , 59111 , 59733 , 60360 , 60992 , 61627 , 62268 , 62912 , 63561 , 64215 , 64873 , 65535
} ;
2021-02-20 16:26:20 +01:00
# endif
2021-02-10 16:49:19 +01:00
/** @brief - configuration values for HUB75_I2S driver
* This structure holds configuration vars that are used as
* an initialization values when creating an instance of MatrixPanel_I2S_DMA object .
* All params have it ' s default values .
*/
struct HUB75_I2S_CFG {
/**
* Enumeration of hardware - specific chips
* used to drive matrix modules
*/
2021-08-16 12:01:26 +02:00
enum shift_driver { SHIFTREG = 0 , FM6124 , FM6126A , ICN2038S , MBI5124 , SM5266P } ;
2021-02-10 16:49:19 +01:00
2021-02-16 21:15:29 +01:00
/**
* I2S clock speed selector
*/
2023-01-26 00:46:34 +01:00
enum clk_speed { HZ_8M = 8000000 , HZ_10M = 10000000 , HZ_15M = 15000000 , HZ_20M = 20000000 } ;
2021-02-16 21:15:29 +01:00
2021-02-10 16:49:19 +01:00
// Structure Variables
// physical width of a single matrix panel module (in pixels, usually it is 64 ;) )
uint16_t mx_width ;
2021-10-16 16:00:43 +02:00
// physical height of a single matrix panel module (in pixels, usually almost always it is either 32 or 64)
2021-02-10 16:49:19 +01:00
uint16_t mx_height ;
// number of chained panels regardless of the topology, default 1 - a single matrix module
uint16_t chain_length ;
2021-02-22 13:13:31 +01:00
2021-02-10 16:49:19 +01:00
/**
* GPIO pins mapping
*/
struct i2s_pins {
int8_t r1 , g1 , b1 , r2 , g2 , b2 , a , b , c , d , e , lat , oe , clk ;
} gpio ;
// Matrix driver chip type - default is a plain shift register
shift_driver driver ;
2021-02-16 21:15:29 +01:00
// I2S clock speed
clk_speed i2sspeed ;
2021-02-10 16:49:19 +01:00
// use DMA double buffer (twice as much RAM required)
bool double_buff ;
// How many clock cycles to blank OE before/after LAT signal change, default is 1 clock
uint8_t latch_blanking ;
2021-02-22 13:13:31 +01:00
2021-02-19 10:08:15 +01:00
/**
* I2S clock phase
2021-06-10 21:49:28 +02:00
* 0 - data lines are clocked with negative edge
2021-02-19 10:08:15 +01:00
* Clk / ¯ \ _ / ¯ \ _ /
* LAT __ / ¯ ¯ ¯ \ __
* EO ¯ ¯ ¯ ¯ ¯ ¯ \ ___
*
2021-06-10 21:49:28 +02:00
* 1 - data lines are clocked with positive edge ( default now as of 10 June 2021 )
* https : //github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/130
2021-02-19 10:08:15 +01:00
* Clk \ _ / ¯ \ _ / ¯ \
* LAT __ / ¯ ¯ ¯ \ __
* EO ¯ ¯ ¯ ¯ ¯ ¯ \ __
*
*/
bool clkphase ;
2021-02-10 16:49:19 +01:00
2021-02-28 20:11:27 +01:00
// Minimum refresh / scan rate needs to be configured on start due to LSBMSB_TRANSITION_BIT calculation in allocateDMAmemory()
2023-03-09 12:25:43 +01:00
uint16_t min_refresh_rate ;
2021-02-28 20:11:27 +01:00
2021-02-10 16:49:19 +01:00
// struct constructor
HUB75_I2S_CFG (
uint16_t _w = MATRIX_WIDTH ,
uint16_t _h = MATRIX_HEIGHT ,
uint16_t _chain = CHAIN_LENGTH ,
i2s_pins _pinmap = {
R1_PIN_DEFAULT , G1_PIN_DEFAULT , B1_PIN_DEFAULT , R2_PIN_DEFAULT , G2_PIN_DEFAULT , B2_PIN_DEFAULT ,
A_PIN_DEFAULT , B_PIN_DEFAULT , C_PIN_DEFAULT , D_PIN_DEFAULT , E_PIN_DEFAULT ,
LAT_PIN_DEFAULT , OE_PIN_DEFAULT , CLK_PIN_DEFAULT } ,
2021-02-19 15:25:29 +01:00
shift_driver _drv = SHIFTREG ,
2021-02-10 16:49:19 +01:00
bool _dbuff = false ,
2023-01-22 14:22:36 +01:00
clk_speed _i2sspeed = HZ_15M ,
2022-10-26 17:49:49 +02:00
uint8_t _latblk = DEFAULT_LAT_BLANKING , // Anything > 1 seems to cause artefacts on ICS panels
2021-06-10 21:49:28 +02:00
bool _clockphase = true ,
2023-03-09 12:25:43 +01:00
uint16_t _min_refresh_rate = 60
2021-02-10 16:49:19 +01:00
) : mx_width ( _w ) ,
mx_height ( _h ) ,
chain_length ( _chain ) ,
gpio ( _pinmap ) ,
2023-01-28 22:54:09 +01:00
driver ( _drv ) ,
i2sspeed ( _i2sspeed ) ,
2021-02-10 16:49:19 +01:00
double_buff ( _dbuff ) ,
2021-02-19 10:08:15 +01:00
latch_blanking ( _latblk ) ,
2021-02-22 13:13:31 +01:00
clkphase ( _clockphase ) ,
min_refresh_rate ( _min_refresh_rate ) { }
2021-02-10 16:49:19 +01:00
} ; // end of structure HUB75_I2S_CFG
2018-10-23 02:00:47 +02:00
/***************************************************************************************/
2020-07-30 09:37:39 +02:00
# ifdef USE_GFX_ROOT
2020-11-28 10:45:30 +01:00
class MatrixPanel_I2S_DMA : public GFX {
2021-02-10 16:49:19 +01:00
# elif !defined NO_GFX
class MatrixPanel_I2S_DMA : public Adafruit_GFX {
2020-07-30 09:37:39 +02:00
# else
2021-02-10 16:49:19 +01:00
class MatrixPanel_I2S_DMA {
2020-07-30 09:37:39 +02:00
# endif
2018-10-23 02:00:47 +02:00
// ------- PUBLIC -------
public :
2021-02-10 16:49:19 +01:00
/**
* MatrixPanel_I2S_DMA
*
2021-10-16 16:00:43 +02:00
* default predefined values are used for matrix configuration
2021-02-10 16:49:19 +01:00
*
*/
MatrixPanel_I2S_DMA ( )
# ifdef USE_GFX_ROOT
: GFX ( MATRIX_WIDTH , MATRIX_HEIGHT )
# elif !defined NO_GFX
: Adafruit_GFX ( MATRIX_WIDTH , MATRIX_HEIGHT )
# endif
{ }
2020-07-29 09:47:54 +02:00
/**
2020-11-28 10:45:30 +01:00
* MatrixPanel_I2S_DMA
2020-07-29 09:47:54 +02:00
*
2021-02-10 16:49:19 +01:00
* @ param { HUB75_I2S_CFG } opts : structure with matrix configuration
2020-07-29 09:47:54 +02:00
*
*/
2021-02-10 16:49:19 +01:00
MatrixPanel_I2S_DMA ( const HUB75_I2S_CFG & opts ) :
2021-02-22 13:13:31 +01:00
# ifdef USE_GFX_ROOT
2021-02-10 16:49:19 +01:00
GFX ( opts . mx_width * opts . chain_length , opts . mx_height ) ,
# elif !defined NO_GFX
Adafruit_GFX ( opts . mx_width * opts . chain_length , opts . mx_height ) ,
2021-02-22 13:13:31 +01:00
# endif
2021-02-10 16:49:19 +01:00
m_cfg ( opts ) { }
2020-07-29 09:47:54 +02:00
2021-10-16 16:00:43 +02:00
/* Propagate the DMA pin configuration, allocate DMA buffs and start data output, initially blank */
2021-02-10 16:49:19 +01:00
bool begin ( ) {
2021-09-19 23:46:25 +02:00
if ( initialized ) return true ; // we don't do this twice or more!
2020-07-29 09:47:54 +02:00
2022-10-05 22:47:16 +02:00
ESP_LOGI ( " begin() " , " Using GPIO %d for R1_PIN " , m_cfg . gpio . r1 ) ;
ESP_LOGI ( " begin() " , " Using GPIO %d for G1_PIN " , m_cfg . gpio . g1 ) ;
ESP_LOGI ( " begin() " , " Using GPIO %d for B1_PIN " , m_cfg . gpio . b1 ) ;
ESP_LOGI ( " begin() " , " Using GPIO %d for R2_PIN " , m_cfg . gpio . r2 ) ;
ESP_LOGI ( " begin() " , " Using GPIO %d for G2_PIN " , m_cfg . gpio . g2 ) ;
ESP_LOGI ( " begin() " , " Using GPIO %d for B2_PIN " , m_cfg . gpio . b2 ) ;
ESP_LOGI ( " begin() " , " Using GPIO %d for A_PIN " , m_cfg . gpio . a ) ;
ESP_LOGI ( " begin() " , " Using GPIO %d for B_PIN " , m_cfg . gpio . b ) ;
ESP_LOGI ( " begin() " , " Using GPIO %d for C_PIN " , m_cfg . gpio . c ) ;
ESP_LOGI ( " begin() " , " Using GPIO %d for D_PIN " , m_cfg . gpio . d ) ;
ESP_LOGI ( " begin() " , " Using GPIO %d for E_PIN " , m_cfg . gpio . e ) ;
ESP_LOGI ( " begin() " , " Using GPIO %d for LAT_PIN " , m_cfg . gpio . lat ) ;
ESP_LOGI ( " begin() " , " Using GPIO %d for OE_PIN " , m_cfg . gpio . oe ) ;
ESP_LOGI ( " begin() " , " Using GPIO %d for CLK_PIN " , m_cfg . gpio . clk ) ;
2020-07-29 09:47:54 +02:00
2021-02-22 13:13:31 +01:00
// initialize some specific panel drivers
2021-02-10 16:49:19 +01:00
if ( m_cfg . driver )
shiftDriver ( m_cfg ) ;
2021-02-22 13:13:31 +01:00
2023-01-26 00:46:34 +01:00
# if defined(SPIRAM_DMA_BUFFER)
// Trick library into dropping colour depth slightly when using PSRAM.
// Actual output clockrate override occurs in configureDMA
m_cfg . i2sspeed = HUB75_I2S_CFG : : HZ_8M ;
# endif
2020-11-28 01:19:20 +01:00
2019-01-10 00:51:27 +01:00
/* As DMA buffers are dynamically allocated, we must allocated in begin()
* Ref : https : //github.com/espressif/arduino-esp32/issues/831
*/
2020-07-29 09:47:54 +02:00
if ( ! allocateDMAmemory ( ) ) { return false ; } // couldn't even get the basic ram required.
2019-01-10 00:51:27 +01:00
2019-01-12 18:20:49 +01:00
2019-07-29 14:45:06 +02:00
// Flush the DMA buffers prior to configuring DMA - Avoid visual artefacts on boot.
2021-05-22 00:57:54 +02:00
resetbuffers ( ) ; // Must fill the DMA buffer with the initial output bit sequence or the panel will display garbage
2021-02-10 16:49:19 +01:00
2019-01-12 18:20:49 +01:00
// Setup the ESP32 DMA Engine. Sprite_TM built this stuff.
2021-02-10 16:49:19 +01:00
configureDMA ( m_cfg ) ; //DMA and I2S configuration and setup
2019-01-12 18:20:49 +01:00
2021-08-19 20:34:40 +02:00
//showDMABuffer(); // show backbuf_id of 0
2020-07-29 09:47:54 +02:00
2022-10-05 22:47:16 +02:00
if ( ! initialized ) {
ESP_LOGE ( " being() " , " MatrixPanel_I2S_DMA::begin() failed! " ) ;
}
2020-07-29 11:44:38 +02:00
2021-02-10 16:49:19 +01:00
return initialized ;
2020-07-29 09:47:54 +02:00
2018-10-23 02:00:47 +02:00
}
2021-02-10 16:49:19 +01:00
2021-08-16 23:39:19 +02:00
// Obj destructor
~ MatrixPanel_I2S_DMA ( ) {
2021-08-18 14:01:05 +02:00
2022-09-30 04:17:19 +02:00
dma_bus . release ( ) ;
2021-08-16 23:39:19 +02:00
}
2021-02-10 16:49:19 +01:00
/*
* overload for compatibility
*/
bool begin ( int r1 , int g1 = G1_PIN_DEFAULT , int b1 = B1_PIN_DEFAULT , int r2 = R2_PIN_DEFAULT , int g2 = G2_PIN_DEFAULT , int b2 = B2_PIN_DEFAULT , int a = A_PIN_DEFAULT , int b = B_PIN_DEFAULT , int c = C_PIN_DEFAULT , int d = D_PIN_DEFAULT , int e = E_PIN_DEFAULT , int lat = LAT_PIN_DEFAULT , int oe = OE_PIN_DEFAULT , int clk = CLK_PIN_DEFAULT ) ;
2019-07-29 13:57:53 +02:00
2021-03-28 16:35:11 +02:00
// Adafruit's BASIC DRAW API (565 colour format)
2019-01-10 00:51:27 +01:00
virtual void drawPixel ( int16_t x , int16_t y , uint16_t color ) ; // overwrite adafruit implementation
virtual void fillScreen ( uint16_t color ) ; // overwrite adafruit implementation
2021-02-10 16:49:19 +01:00
/**
2021-08-18 17:33:59 +02:00
* A wrapper to fill whatever selected DMA buffer / screen with black
2021-02-10 16:49:19 +01:00
*/
2023-01-08 01:16:29 +01:00
inline void clearScreen ( ) { updateMatrixDMABuffer ( 0 , 0 , 0 ) ; } ;
2021-02-10 16:49:19 +01:00
# ifndef NO_FAST_FUNCTIONS
/**
* @ brief - override Adafruit ' s FastVLine
* this works faster than multiple consecutive pixel by pixel drawPixel ( ) call
*/
virtual void drawFastVLine ( int16_t x , int16_t y , int16_t h , uint16_t color ) {
uint8_t r , g , b ;
color565to888 ( color , r , g , b ) ;
2022-11-07 01:56:44 +01:00
startWrite ( ) ;
2023-02-11 06:46:34 +01:00
int16_t w = 1 ;
transform ( x , y , w , h ) ;
if ( h > w )
vlineDMA ( x , y , h , r , g , b ) ;
else
hlineDMA ( x , y , w , r , g , b ) ;
2022-11-07 01:56:44 +01:00
endWrite ( ) ;
2021-02-10 16:49:19 +01:00
}
// rgb888 overload
2023-02-10 17:43:07 +01:00
virtual inline void drawFastVLine ( int16_t x , int16_t y , int16_t h , uint8_t r , uint8_t g , uint8_t b ) {
2023-02-11 06:46:34 +01:00
int16_t w = 1 ;
transform ( x , y , w , h ) ;
if ( h > w )
vlineDMA ( x , y , h , r , g , b ) ;
else
hlineDMA ( x , y , w , r , g , b ) ;
2023-02-10 17:43:07 +01:00
} ;
2021-02-10 16:49:19 +01:00
/**
* @ brief - override Adafruit ' s FastHLine
* this works faster than multiple consecutive pixel by pixel drawPixel ( ) call
*/
virtual void drawFastHLine ( int16_t x , int16_t y , int16_t w , uint16_t color ) {
uint8_t r , g , b ;
color565to888 ( color , r , g , b ) ;
2023-02-11 06:46:34 +01:00
startWrite ( ) ;
int16_t h = 1 ;
transform ( x , y , w , h ) ;
if ( h > w )
vlineDMA ( x , y , h , r , g , b ) ;
else
hlineDMA ( x , y , w , r , g , b ) ;
2022-11-07 01:56:44 +01:00
endWrite ( ) ;
2021-02-10 16:49:19 +01:00
}
// rgb888 overload
2023-02-11 06:46:34 +01:00
virtual inline void drawFastHLine ( int16_t x , int16_t y , int16_t w , uint8_t r , uint8_t g , uint8_t b ) {
int16_t h = 1 ;
transform ( x , y , w , h ) ;
if ( h > w )
vlineDMA ( x , y , h , r , g , b ) ;
else
hlineDMA ( x , y , w , r , g , b ) ;
2023-02-10 17:43:07 +01:00
} ;
2021-02-10 16:49:19 +01:00
/**
* @ brief - override Adafruit ' s fillRect
2021-10-16 16:00:43 +02:00
* this works much faster than multiple consecutive per - pixel drawPixel ( ) calls
2021-02-10 16:49:19 +01:00
*/
virtual void fillRect ( int16_t x , int16_t y , int16_t w , int16_t h , uint16_t color ) {
uint8_t r , g , b ;
color565to888 ( color , r , g , b ) ;
2023-02-11 06:46:34 +01:00
startWrite ( ) ;
transform ( x , y , w , h ) ;
fillRectDMA ( x , y , w , h , r , g , b ) ;
2022-11-07 01:56:44 +01:00
endWrite ( ) ;
2021-02-10 16:49:19 +01:00
}
// rgb888 overload
2022-11-07 01:56:44 +01:00
virtual inline void fillRect ( int16_t x , int16_t y , int16_t w , int16_t h , uint8_t r , uint8_t g , uint8_t b ) {
2023-02-11 06:46:34 +01:00
startWrite ( ) ;
transform ( x , y , w , h ) ;
fillRectDMA ( x , y , w , h , r , g , b ) ;
2022-11-07 01:56:44 +01:00
endWrite ( ) ;
}
2021-02-10 16:49:19 +01:00
# endif
2021-02-22 13:13:31 +01:00
void fillScreenRGB888 ( uint8_t r , uint8_t g , uint8_t b ) ;
2019-01-10 00:51:27 +01:00
void drawPixelRGB888 ( int16_t x , int16_t y , uint8_t r , uint8_t g , uint8_t b ) ;
2021-09-19 23:46:25 +02:00
2021-03-31 19:29:03 +02:00
# ifdef USE_GFX_ROOT
2021-09-19 23:46:25 +02:00
// 24bpp FASTLED CRGB colour struct support
void fillScreen ( CRGB color ) ;
2021-03-31 19:29:03 +02:00
void drawPixel ( int16_t x , int16_t y , CRGB color ) ;
2021-03-28 16:35:11 +02:00
# endif
2019-08-14 11:37:03 +02:00
void drawIcon ( int * ico , int16_t x , int16_t y , int16_t cols , int16_t rows ) ;
2019-07-29 14:45:06 +02:00
2023-01-28 22:54:09 +01:00
// Colour 444 is a 4 bit scale, so 0 to 15, colour 565 takes a 0-255 bit value, so scale up by 255/15 (i.e. 17)!
2021-02-10 16:49:19 +01:00
static uint16_t color444 ( uint8_t r , uint8_t g , uint8_t b ) { return color565 ( r * 17 , g * 17 , b * 17 ) ; }
2018-10-23 02:00:47 +02:00
// Converts RGB888 to RGB565
2021-02-10 16:49:19 +01:00
static uint16_t color565 ( uint8_t r , uint8_t g , uint8_t b ) ; // This is what is used by Adafruit GFX!
2020-07-30 00:11:03 +02:00
2019-07-31 00:50:14 +02:00
// Converts RGB333 to RGB565
2021-02-10 16:49:19 +01:00
static uint16_t color333 ( uint8_t r , uint8_t g , uint8_t b ) ; // This is what is used by Adafruit GFX! Not sure why they have a capital 'C' for this particular function.
/**
* @ brief - convert RGB565 to RGB888
2023-01-28 22:54:09 +01:00
* @ param uint16_t colour - RGB565 input colour
2021-02-10 16:49:19 +01:00
* @ param uint8_t & r , & g , & b - refs to variables where converted colors would be emplaced
*/
static void color565to888 ( const uint16_t color , uint8_t & r , uint8_t & g , uint8_t & b ) ;
2018-10-23 02:00:47 +02:00
2022-11-07 01:56:44 +01:00
inline void flipDMABuffer ( )
2020-07-30 00:11:03 +02:00
{
2022-11-07 01:56:44 +01:00
if ( ! m_cfg . double_buff ) { return ; }
2022-10-05 22:47:16 +02:00
2022-11-07 01:56:44 +01:00
// while (active_gfx_writes) { } // wait a bit ?
// initialized = false;
dma_bus . flip_dma_output_buffer ( back_buffer_id ) ;
// initialized = true;
2022-09-30 04:17:19 +02:00
/*
2021-12-21 23:19:59 +01:00
i2s_parallel_set_previous_buffer_not_free ( ) ;
2021-08-19 20:22:29 +02:00
// Wait before we allow any writing to the buffer. Stop flicker.
2021-08-19 20:34:40 +02:00
while ( i2s_parallel_is_previous_buffer_free ( ) = = false ) { }
2021-09-19 23:46:25 +02:00
2021-12-21 23:19:59 +01:00
i2s_parallel_flip_to_buffer ( ESP32_I2S_DEVICE , back_buffer_id ) ;
2021-08-19 21:47:36 +02:00
// Flip to other buffer as the backbuffer.
2021-09-19 23:46:25 +02:00
// i.e. Graphic changes happen to this buffer, but aren't displayed until flipDMABuffer() is called again.
2021-12-21 23:19:59 +01:00
back_buffer_id ^ = 1 ;
i2s_parallel_set_previous_buffer_not_free ( ) ;
// Wait before we allow any writing to the buffer. Stop flicker.
while ( i2s_parallel_is_previous_buffer_free ( ) = = false ) { }
2022-09-30 04:17:19 +02:00
*/
2021-08-19 20:22:29 +02:00
}
2023-01-08 00:59:12 +01:00
/**
* @ param uint8_t b - 8 - bit brightness value
*/
void setBrightness ( const uint8_t b )
{
if ( ! initialized )
{
ESP_LOGI ( " setBrightness() " , " Tried to set output brightness before begin() " ) ;
return ;
}
brightness = b ;
brtCtrlOEv2 ( b , 0 ) ;
if ( m_cfg . double_buff ) {
brtCtrlOEv2 ( b , 1 ) ;
}
}
2021-08-19 20:34:40 +02:00
2023-01-08 00:59:12 +01:00
// Takes a value that is between 0 and MATRIX_WIDTH-1
/*
void setPanelBrightness ( int b )
2019-07-29 14:45:06 +02:00
{
2021-02-10 16:49:19 +01:00
if ( ! initialized )
2023-01-08 00:59:12 +01:00
{
ESP_LOGI ( " setPanelBrightness() " , " Tried to set output brightness before begin() " ) ;
2021-02-10 16:49:19 +01:00
return ;
2023-01-08 00:59:12 +01:00
}
// Change to set the brightness of the display, range of 1 to matrixWidth (i.e. 1 - 64)
// brightness = b * PIXELS_PER_ROW / 256;
2021-02-10 16:49:19 +01:00
brtCtrlOE ( b ) ;
if ( m_cfg . double_buff )
brtCtrlOE ( b , 1 ) ;
2019-07-29 14:45:06 +02:00
}
2023-01-08 00:59:12 +01:00
*/
2023-01-08 01:16:29 +01:00
/**
* @ param uint8_t b - 8 - bit brightness value
*/
void setPanelBrightness ( const uint8_t b )
2023-01-08 00:59:12 +01:00
{
setBrightness ( b ) ;
}
2020-12-07 10:25:29 +01:00
/**
* this is just a wrapper to control brightness
* with an 8 - bit value ( 0 - 255 ) , very popular in FastLED - based sketches : )
* @ param uint8_t b - 8 - bit brightness value
*/
void setBrightness8 ( const uint8_t b )
{
2023-01-08 00:59:12 +01:00
setBrightness ( b ) ;
//setPanelBrightness(b * PIXELS_PER_ROW / 256);
2020-12-07 10:25:29 +01:00
}
2021-02-22 13:13:31 +01:00
/**
* Contains the resulting refresh rate ( scan rate ) that will be achieved
* based on the i2sspeed , colour depth and min_refresh_rate requested .
*/
int calculated_refresh_rate = 0 ;
2021-02-10 16:49:19 +01:00
2020-12-07 10:25:29 +01:00
/**
2021-02-10 16:49:19 +01:00
* @ brief - Sets how many clock cycles to blank OE before / after LAT signal change
* @ param uint8_t pulses - clocks before / after OE
* default is DEFAULT_LAT_BLANKING
* Max is MAX_LAT_BLANKING
* @ returns - new value for m_cfg . latch_blanking
2020-12-07 10:25:29 +01:00
*/
2021-02-10 16:49:19 +01:00
uint8_t setLatBlanking ( uint8_t pulses ) ;
2020-12-07 10:25:29 +01:00
/**
2021-02-10 16:49:19 +01:00
* Get a class configuration struct
*
2020-12-07 10:25:29 +01:00
*/
2021-02-10 16:49:19 +01:00
const HUB75_I2S_CFG & getCfg ( ) const { return m_cfg ; } ;
2021-02-22 13:13:31 +01:00
/**
* Stop the ESP32 DMA Engine . Screen will forever be black until next ESP reboot .
*/
void stopDMAoutput ( ) {
2021-05-22 00:57:54 +02:00
resetbuffers ( ) ;
2022-09-30 04:17:19 +02:00
//i2s_parallel_stop_dma(ESP32_I2S_DEVICE);
dma_bus . dma_transfer_stop ( ) ;
2021-02-22 13:13:31 +01:00
}
2022-11-07 01:56:44 +01:00
void startWrite ( ) {
//ESP_LOGI("TAG", "startWrite() called");
active_gfx_writes + + ;
}
2021-02-22 13:13:31 +01:00
2020-12-07 10:25:29 +01:00
2022-11-07 01:56:44 +01:00
void endWrite ( ) {
active_gfx_writes - - ;
}
2020-12-07 10:25:29 +01:00
2021-02-10 16:49:19 +01:00
// ------- PROTECTED -------
// those might be useful for child classes, like VirtualMatrixPanel
protected :
2022-09-30 04:17:19 +02:00
Bus_Parallel16 dma_bus ;
2021-02-10 16:49:19 +01:00
/**
2023-01-28 22:54:09 +01:00
* @ brief - clears and reinitializes colour / control data in DMA buffs
2021-10-16 16:00:43 +02:00
* When allocated , DMA buffs might be dirty , so we need to blank it and initialize ABCDE , LAT , OE control bits .
2023-01-28 22:54:09 +01:00
* Those control bits are constants during the entire DMA sweep and never changed when updating just pixel colour data
2021-02-10 16:49:19 +01:00
* so we could set it once on DMA buffs initialization and forget .
* This effectively clears buffers to blank BLACK and makes it ready to display output .
* ( Brightness control via OE bit manipulation is another case )
*/
void clearFrameBuffer ( bool _buff_id = 0 ) ;
/* Update a specific pixel in the DMA buffer to a colour */
2022-10-26 17:49:49 +02:00
void updateMatrixDMABuffer ( uint16_t x , uint16_t y , uint8_t red , uint8_t green , uint8_t blue ) ;
2021-02-10 16:49:19 +01:00
/* Update the entire DMA buffer (aka. The RGB Panel) a certain colour (wipe the screen basically) */
2021-08-18 17:33:59 +02:00
void updateMatrixDMABuffer ( uint8_t red , uint8_t green , uint8_t blue ) ;
2021-02-10 16:49:19 +01:00
2021-05-22 00:57:54 +02:00
/**
2023-01-28 22:54:09 +01:00
* wipes DMA buffer ( s ) and reset all colour / service bits
2021-05-22 00:57:54 +02:00
*/
inline void resetbuffers ( ) {
2023-01-08 01:16:29 +01:00
2021-05-22 00:57:54 +02:00
clearFrameBuffer ( ) ;
2023-01-08 00:59:12 +01:00
brtCtrlOEv2 ( brightness , 0 ) ;
if ( m_cfg . double_buff ) {
2021-05-22 00:57:54 +02:00
clearFrameBuffer ( 1 ) ;
2023-01-08 00:59:12 +01:00
brtCtrlOEv2 ( brightness , 1 ) ;
2021-05-22 00:57:54 +02:00
}
2023-01-08 00:59:12 +01:00
2021-05-22 00:57:54 +02:00
}
2021-02-10 16:49:19 +01:00
# ifndef NO_FAST_FUNCTIONS
/**
* @ brief - update DMA buff drawing horizontal line at specified coordinates
* @ param x_ccord - line start coordinate x
* @ param y_ccord - line start coordinate y
* @ param l - line length
2023-01-28 22:54:09 +01:00
* @ param r , g , b , - RGB888 colour
2021-02-10 16:49:19 +01:00
*/
void hlineDMA ( int16_t x_coord , int16_t y_coord , int16_t l , uint8_t red , uint8_t green , uint8_t blue ) ;
/**
* @ brief - update DMA buff drawing horizontal line at specified coordinates
* @ param x_ccord - line start coordinate x
* @ param y_ccord - line start coordinate y
* @ param l - line length
2023-01-28 22:54:09 +01:00
* @ param r , g , b , - RGB888 colour
2021-02-10 16:49:19 +01:00
*/
void vlineDMA ( int16_t x_coord , int16_t y_coord , int16_t l , uint8_t red , uint8_t green , uint8_t blue ) ;
/**
* @ brief - update DMA buff drawing a rectangular at specified coordinates
2021-10-16 16:00:43 +02:00
* uses Fast H / V line draw internally , works faster than multiple consecutive pixel by pixel calls to updateMatrixDMABuffer ( )
2021-02-10 16:49:19 +01:00
* @ param int16_t x , int16_t y - coordinates of a top - left corner
* @ param int16_t w , int16_t h - width and height of a rectangular , min is 1 px
2023-01-28 22:54:09 +01:00
* @ param uint8_t r - RGB888 colour
* @ param uint8_t g - RGB888 colour
* @ param uint8_t b - RGB888 colour
2021-02-10 16:49:19 +01:00
*/
void fillRectDMA ( int16_t x_coord , int16_t y_coord , int16_t w , int16_t h , uint8_t r , uint8_t g , uint8_t b ) ;
# endif
2018-10-23 02:00:47 +02:00
// ------- PRIVATE -------
private :
2021-02-10 16:49:19 +01:00
// Matrix i2s settings
HUB75_I2S_CFG m_cfg ;
/* ESP32-HUB75-MatrixPanel-I2S-DMA functioning constants
* we can ' t change those once object instance initialized it ' s DMA structs
2020-08-13 00:40:44 +02:00
*/
2021-02-22 13:13:31 +01:00
const uint8_t ROWS_PER_FRAME = m_cfg . mx_height / MATRIX_ROWS_IN_PARALLEL ; // RPF - rows per frame, either 16 or 32 depending on matrix module
const uint16_t PIXELS_PER_ROW = m_cfg . mx_width * m_cfg . chain_length ; // number of pixels in a single row of all chained matrix modules (WIDTH of a combined matrix chain)
2021-02-10 16:49:19 +01:00
// Other private variables
bool initialized = false ;
2022-11-07 01:56:44 +01:00
int active_gfx_writes = 0 ; // How many async routines are 'drawing' (writing) to the DMA bit buffer. Function called from Adafruit_GFX draw routines like drawCircle etc.
2021-02-10 16:49:19 +01:00
int back_buffer_id = 0 ; // If using double buffer, which one is NOT active (ie. being displayed) to write too?
2023-01-08 00:59:12 +01:00
int brightness = 128 ; // If you get ghosting... reduce brightness level. ((60/64)*255) seems to be the limit before ghosting on a 64 pixel wide physical panel for some panels.
2021-02-22 13:13:31 +01:00
int lsbMsbTransitionBit = 0 ; // For colour depth calculations
2021-02-10 16:49:19 +01:00
// *** DMA FRAMEBUFFER structures
2020-07-29 09:47:54 +02:00
// ESP 32 DMA Linked List descriptor
2021-02-22 13:13:31 +01:00
int desccount = 0 ;
2022-09-30 04:17:19 +02:00
// lldesc_t * dmadesc_a = {0};
// lldesc_t * dmadesc_b = {0};
2020-07-29 09:47:54 +02:00
2021-02-10 16:49:19 +01:00
/* Pixel data is organized from LSB to MSB sequentially by row, from row 0 to row matrixHeight/matrixRowsInParallel
* ( two rows of pixels are refreshed in parallel )
* Memory is allocated ( malloc ' d ) by the row , and not in one massive chunk , for flexibility .
* The whole DMA framebuffer is just a vector of pointers to structs with ESP32_I2S_DMA_STORAGE_TYPE arrays
2021-10-16 16:00:43 +02:00
* Since it ' s dimensions is unknown prior to class initialization , we just declare it here as empty struct and will do all allocations later .
2021-02-10 16:49:19 +01:00
* Refer to rowBitStruct to get the idea of it ' s internal structure
2020-12-07 10:25:29 +01:00
*/
2021-02-10 16:49:19 +01:00
frameStruct dma_buff ;
2020-12-07 10:25:29 +01:00
2020-07-29 09:47:54 +02:00
/* Calculate the memory available for DMA use, do some other stuff, and allocate accordingly */
bool allocateDMAmemory ( ) ;
/* Setup the DMA Link List chain and initiate the ESP32 DMA engine */
2021-02-10 16:49:19 +01:00
void configureDMA ( const HUB75_I2S_CFG & opts ) ;
2018-10-23 02:00:47 +02:00
2020-11-28 01:19:20 +01:00
/**
* pre - init procedures for specific drivers
*
*/
2021-02-10 16:49:19 +01:00
void shiftDriver ( const HUB75_I2S_CFG & opts ) ;
2021-02-19 15:25:29 +01:00
/**
* @ brief - FM6124 - family chips initialization routine
*/
void fm6124init ( const HUB75_I2S_CFG & _cfg ) ;
2021-02-10 16:49:19 +01:00
/**
* @ brief - reset OE bits in DMA buffer in a way to control brightness
* @ param brt - brightness level from 0 to row_width
* @ param _buff_id - buffer id to control
*/
2023-01-21 12:37:28 +01:00
//void brtCtrlOE(int brt, const bool _buff_id=0);
2023-01-08 00:59:12 +01:00
/**
* @ brief - reset OE bits in DMA buffer in a way to control brightness
* @ param brt - brightness level from 0 to row_width
* @ param _buff_id - buffer id to control
*/
void brtCtrlOEv2 ( uint8_t brt , const int _buff_id = 0 ) ;
2021-02-10 16:49:19 +01:00
2023-02-11 06:46:34 +01:00
/**
* @ brief - transforms coordinates according to orientation
* @ param x - x position origin
* @ param y - y position origin
* @ param w - rectangular width
* @ param h - rectangular height
*/
void transform ( int16_t & x , int16_t & y , int16_t & w , int16_t & h ) {
# ifndef NO_GFX
int16_t t ;
switch ( rotation ) {
case 1 : t = _height - 1 - y - ( h - 1 ) ; y = x ; x = t ; t = h ; h = w ; w = t ; return ;
case 2 : x = _width - 1 - x - ( w - 1 ) ; y = _height - 1 - y - ( h - 1 ) ; return ;
case 3 : t = y ; y = _width - 1 - x - ( w - 1 ) ; x = t ; t = h ; h = w ; w = t ; return ;
}
# endif
} ;
2018-10-23 02:00:47 +02:00
} ; // end Class header
/***************************************************************************************/
2019-01-10 00:51:27 +01:00
// https://stackoverflow.com/questions/5057021/why-are-c-inline-functions-in-the-header
/* 2. functions declared in the header must be marked inline because otherwise, every translation unit which includes the header will contain a definition of the function, and the linker will complain about multiple definitions (a violation of the One Definition Rule). The inline keyword suppresses this, allowing multiple translation units to contain (identical) definitions. */
2021-03-28 16:35:11 +02:00
/**
* @ brief - convert RGB565 to RGB888
2023-01-28 22:54:09 +01:00
* @ param uint16_t colour - RGB565 input colour
2021-03-28 16:35:11 +02:00
* @ param uint8_t & r , & g , & b - refs to variables where converted colours would be emplaced
*/
inline void MatrixPanel_I2S_DMA : : color565to888 ( const uint16_t color , uint8_t & r , uint8_t & g , uint8_t & b ) {
r = ( ( ( ( color > > 11 ) & 0x1F ) * 527 ) + 23 ) > > 6 ;
g = ( ( ( ( color > > 5 ) & 0x3F ) * 259 ) + 33 ) > > 6 ;
b = ( ( ( color & 0x1F ) * 527 ) + 23 ) > > 6 ;
}
2020-11-28 10:45:30 +01:00
inline void MatrixPanel_I2S_DMA : : drawPixel ( int16_t x , int16_t y , uint16_t color ) // adafruit virtual void override
2018-10-23 02:00:47 +02:00
{
2021-03-28 16:35:11 +02:00
uint8_t r , g , b ;
color565to888 ( color , r , g , b ) ;
2023-02-11 06:46:34 +01:00
int16_t w = 1 , h = 1 ;
transform ( x , y , w , h ) ;
updateMatrixDMABuffer ( x , y , r , g , b ) ;
2018-10-23 02:00:47 +02:00
}
2020-11-28 10:45:30 +01:00
inline void MatrixPanel_I2S_DMA : : fillScreen ( uint16_t color ) // adafruit virtual void override
2019-01-03 00:09:32 +01:00
{
2021-03-28 16:35:11 +02:00
uint8_t r , g , b ;
color565to888 ( color , r , g , b ) ;
2019-01-03 00:09:32 +01:00
2021-03-28 16:35:11 +02:00
updateMatrixDMABuffer ( r , g , b ) ; // RGB only (no pixel coordinate) version of 'updateMatrixDMABuffer'
2020-08-11 21:44:47 +02:00
}
2021-03-28 16:35:11 +02:00
inline void MatrixPanel_I2S_DMA : : drawPixelRGB888 ( int16_t x , int16_t y , uint8_t r , uint8_t g , uint8_t b )
2018-10-23 02:00:47 +02:00
{
2023-02-11 06:46:34 +01:00
int16_t w = 1 , h = 1 ;
transform ( x , y , w , h ) ;
updateMatrixDMABuffer ( x , y , r , g , b ) ;
2018-10-23 02:00:47 +02:00
}
2021-03-28 16:35:11 +02:00
inline void MatrixPanel_I2S_DMA : : fillScreenRGB888 ( uint8_t r , uint8_t g , uint8_t b )
2018-10-23 02:00:47 +02:00
{
2021-03-28 16:35:11 +02:00
updateMatrixDMABuffer ( r , g , b ) ; // RGB only (no pixel coordinate) version of 'updateMatrixDMABuffer'
}
2018-10-23 02:00:47 +02:00
2021-03-31 19:29:03 +02:00
# ifdef USE_GFX_ROOT
2021-03-28 16:35:11 +02:00
// Support for CRGB values provided via FastLED
inline void MatrixPanel_I2S_DMA : : drawPixel ( int16_t x , int16_t y , CRGB color )
2018-10-26 00:49:41 +02:00
{
2023-02-11 06:46:34 +01:00
int16_t w = 1 , h = 1 ;
transform ( x , y , w , h ) ;
2018-10-26 00:49:41 +02:00
updateMatrixDMABuffer ( x , y , color . red , color . green , color . blue ) ;
}
2021-03-28 16:35:11 +02:00
inline void MatrixPanel_I2S_DMA : : fillScreen ( CRGB color )
{
updateMatrixDMABuffer ( color . red , color . green , color . blue ) ;
}
# endif
2023-01-28 22:54:09 +01:00
// Pass 8-bit (each) R,G,B, get back 16-bit packed colour
2018-10-23 02:00:47 +02:00
//https://github.com/squix78/ILI9341Buffer/blob/master/ILI9341_SPI.cpp
2020-11-28 10:45:30 +01:00
inline uint16_t MatrixPanel_I2S_DMA : : color565 ( uint8_t r , uint8_t g , uint8_t b ) {
2018-10-23 02:00:47 +02:00
return ( ( r & 0xF8 ) < < 8 ) | ( ( g & 0xFC ) < < 3 ) | ( b > > 3 ) ;
}
2019-07-31 00:50:14 +02:00
// Promote 3/3/3 RGB to Adafruit_GFX 5/6/5 RRRrrGGGgggBBBbb
2020-11-28 10:45:30 +01:00
inline uint16_t MatrixPanel_I2S_DMA : : color333 ( uint8_t r , uint8_t g , uint8_t b ) {
2021-03-28 16:35:11 +02:00
return ( ( r & 0x7 ) < < 13 ) | ( ( r & 0x6 ) < < 10 ) | ( ( g & 0x7 ) < < 8 ) | ( ( g & 0x7 ) < < 5 ) | ( ( b & 0x7 ) < < 2 ) | ( ( b & 0x6 ) > > 1 ) ;
2019-07-31 00:50:14 +02:00
}
2018-10-23 02:00:47 +02:00
2020-11-28 10:45:30 +01:00
inline void MatrixPanel_I2S_DMA : : drawIcon ( int * ico , int16_t x , int16_t y , int16_t cols , int16_t rows ) {
2020-07-29 09:47:54 +02:00
/* drawIcon draws a C style bitmap.
// Example 10x5px bitmap of a yellow sun
//
int half_sun [ 50 ] = {
0x0000 , 0x0000 , 0x0000 , 0xffe0 , 0x0000 , 0x0000 , 0xffe0 , 0x0000 , 0x0000 , 0x0000 ,
0x0000 , 0xffe0 , 0x0000 , 0x0000 , 0x0000 , 0x0000 , 0x0000 , 0x0000 , 0xffe0 , 0x0000 ,
0x0000 , 0x0000 , 0x0000 , 0xffe0 , 0xffe0 , 0xffe0 , 0xffe0 , 0x0000 , 0x0000 , 0x0000 ,
0xffe0 , 0x0000 , 0xffe0 , 0xffe0 , 0xffe0 , 0xffe0 , 0xffe0 , 0xffe0 , 0x0000 , 0xffe0 ,
0x0000 , 0x0000 , 0xffe0 , 0xffe0 , 0xffe0 , 0xffe0 , 0xffe0 , 0xffe0 , 0x0000 , 0x0000 ,
} ;
2020-11-28 10:45:30 +01:00
MatrixPanel_I2S_DMA matrix ;
2020-07-29 09:47:54 +02:00
matrix . drawIcon ( half_sun , 0 , 0 , 10 , 5 ) ;
*/
int i , j ;
for ( i = 0 ; i < rows ; i + + ) {
for ( j = 0 ; j < cols ; j + + ) {
2021-03-28 16:35:11 +02:00
drawPixel ( x + j , y + i , ( uint16_t ) ico [ i * cols + j ] ) ;
2020-07-29 09:47:54 +02:00
}
}
}
2020-11-28 10:45:30 +01:00
2020-12-16 18:52:11 +01:00
# endif
2022-09-30 04:17:19 +02:00
// 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 64 x16 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
2023-01-28 22:54:09 +01:00
colour palette of 8 pixels , not enough to display nice pictures . To get around this , we use binary code modulation .
2022-09-30 04:17:19 +02:00
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 ( 2 xRGB , 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 .
*/