Disable auto back-buffer switch logic.

No point changing to the back-buffer automatically, should be for advanced/manual uses of the library.
This commit is contained in:
mrfaptastic 2019-01-09 23:51:27 +00:00
parent 47a79db50f
commit c66fced326
2 changed files with 171 additions and 131 deletions

View file

@ -327,8 +327,16 @@ void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(int16_t x_coord, int16_t
//Show our work! //Show our work!
i2s_parallel_flip_to_buffer(&I2S1, backbuf_id); if (immediateUpdate)
//swapBuffer(); refreshDMAOutput();
//i2s_parallel_flip_to_buffer(&I2S1, backbuf_id);
/*
// There's no reason you'd want to do this in the draw pixel routine.
if (autoBackBufferFlip)
swapBuffer();
*/
} // updateDMABuffer } // updateDMABuffer
@ -416,9 +424,23 @@ void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint8_t red, uint8_t gre
} // colour depth loop (8) } // colour depth loop (8)
} // end row iteration } // end row iteration
if (immediateUpdate)
refreshDMAOutput();
//i2s_parallel_flip_to_buffer(&I2S1, backbuf_id);
/*
// There's no reason you'd want to do this here either to be honest
if (autoBackBufferFlip)
swapBuffer();
*/
//Show our work! //Show our work!
i2s_parallel_flip_to_buffer(&I2S1, backbuf_id); //i2s_parallel_flip_to_buffer(&I2S1, backbuf_id);
swapBuffer();
//swapBuffer();
} // updateDMABuffer } // updateDMABuffer

View file

@ -13,59 +13,70 @@
/* /*
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 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 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 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. 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 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, 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, 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 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. 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 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 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. 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: 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 artifacts 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 doing so hides any artifacts 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. 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 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. 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 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. 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 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. 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 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 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, 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. 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 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 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.) 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.
In this code, we use the I2S peripheral in parallel mode to achieve this. Essentially, first we allocate memory for all subframes. This memory We use a frontbuffer/backbuffer technique here to make sure the display is refreshed in one go and drawing artifacts do not reach the display.
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. In practice, for small displays this is not really necessarily.
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 frontbuffer/backbuffer technique here to make sure the display is refreshed in one go and drawing artifacts do not reach the display.
In practice, for small displays this is not really necessarily.
Finally, the binary code modulated intensity of a LED does not correspond to the intensity as seen by human eyes. To correct for that, a
luminance correction is used. See val2pwm.c for more info.
Note: Because every subframe contains one bit of grayscale information, they are also referred to as 'bitplanes' by the code below.
*/ */
/***************************************************************************************/
/* HUB75 RGB pixel WIDTH and HEIGHT.
*
* This library has only been tested with a 64 pixel (wide) and 32 (high) RGB panel.
* Theoretically, if you want to chain two of these horizontally to make a 128x32 panel
* you can do so with the cable and then set the MATRIX_WIDTH to '128'.
*
* Also, if you use a 64x64 panel, then set the MATRIX_HEIGHT to '64', and it might work.
*
* All of this is memory permitting of course (dependant on your sketch etc.) ...
*
*/
#define MATRIX_HEIGHT 32
#define MATRIX_WIDTH 64
#define MATRIX_ROWS_IN_PARALLEL 2
/***************************************************************************************/ /***************************************************************************************/
/* ESP32 Pin Definition. You can change this, but best if you keep it as is... */ /* ESP32 Pin Definition. You can change this, but best if you keep it as is... */
@ -88,11 +99,7 @@
#define CLK_PIN_DEFAULT 16 #define CLK_PIN_DEFAULT 16
/***************************************************************************************/ /***************************************************************************************/
/* HUB75 RGB Panel definitions and DMA Config. It's best you don't change any of this. */ /* Don't change this stuff unless you know what you are doing */
#define MATRIX_HEIGHT 32
#define MATRIX_WIDTH 64
#define MATRIX_ROWS_IN_PARALLEL 2
// Panel Upper half RGB (numbering according to order in DMA gpio_bus configuration) // Panel Upper half RGB (numbering according to order in DMA gpio_bus configuration)
#define BIT_R1 (1<<0) #define BIT_R1 (1<<0)
@ -135,6 +142,7 @@
/***************************************************************************************/ /***************************************************************************************/
// note: sizeof(data) must be multiple of 32 bits, as ESP32 DMA linked list buffer address pointer must be word-aligned. // note: sizeof(data) must be multiple of 32 bits, as ESP32 DMA linked list buffer address pointer must be word-aligned.
struct rowBitStruct { struct rowBitStruct {
MATRIX_DATA_STORAGE_TYPE data[((MATRIX_WIDTH * MATRIX_HEIGHT) / MATRIX_HEIGHT) + CLKS_DURING_LATCH]; MATRIX_DATA_STORAGE_TYPE data[((MATRIX_WIDTH * MATRIX_HEIGHT) / MATRIX_HEIGHT) + CLKS_DURING_LATCH];
@ -162,67 +170,68 @@ typedef struct rgb_24 {
uint8_t blue; uint8_t blue;
} rgb_24; } rgb_24;
/***************************************************************************************/ /***************************************************************************************/
class RGB64x32MatrixPanel_I2S_DMA : public Adafruit_GFX { class RGB64x32MatrixPanel_I2S_DMA : public Adafruit_GFX {
// ------- PUBLIC ------- // ------- PUBLIC -------
public: public:
RGB64x32MatrixPanel_I2S_DMA(bool _doubleBuffer = false) // doublebuffer always enabled, option makes no difference RGB64x32MatrixPanel_I2S_DMA(bool _immediateUpdate = true, bool _autoBackBufferFlip = false) // Refer to commentary in the private: section
: Adafruit_GFX(MATRIX_WIDTH, MATRIX_HEIGHT), doubleBuffer(_doubleBuffer) { : Adafruit_GFX(MATRIX_WIDTH, MATRIX_HEIGHT), immediateUpdate(_immediateUpdate), autoBackBufferFlip(_autoBackBufferFlip) {
backbuf_id = 0; backbuf_id = 0;
brightness = 32; // default to max brightness, wear sunglasses when looking directly at panel. brightness = 32; // default to max brightness, wear sunglasses when looking directly at panel.
} }
// Painfully propagate the DMA pin configuration, or use compiler defaults // Painfully propagate the DMA pin configuration, or use compiler defaults
void begin(int dma_r1_pin = R1_PIN_DEFAULT , int dma_g1_pin = G1_PIN_DEFAULT, int dma_b1_pin = B1_PIN_DEFAULT , int dma_r2_pin = R2_PIN_DEFAULT , int dma_g2_pin = G2_PIN_DEFAULT , int dma_b2_pin = B2_PIN_DEFAULT , int dma_a_pin = A_PIN_DEFAULT , int dma_b_pin = B_PIN_DEFAULT , int dma_c_pin = C_PIN_DEFAULT , int dma_d_pin = D_PIN_DEFAULT , int dma_e_pin = E_PIN_DEFAULT , int dma_lat_pin = LAT_PIN_DEFAULT, int dma_oe_pin = OE_PIN_DEFAULT , int dma_clk_pin = CLK_PIN_DEFAULT) void begin(int dma_r1_pin = R1_PIN_DEFAULT , int dma_g1_pin = G1_PIN_DEFAULT, int dma_b1_pin = B1_PIN_DEFAULT , int dma_r2_pin = R2_PIN_DEFAULT , int dma_g2_pin = G2_PIN_DEFAULT , int dma_b2_pin = B2_PIN_DEFAULT , int dma_a_pin = A_PIN_DEFAULT , int dma_b_pin = B_PIN_DEFAULT , int dma_c_pin = C_PIN_DEFAULT , int dma_d_pin = D_PIN_DEFAULT , int dma_e_pin = E_PIN_DEFAULT , int dma_lat_pin = LAT_PIN_DEFAULT, int dma_oe_pin = OE_PIN_DEFAULT , int dma_clk_pin = CLK_PIN_DEFAULT)
{ {
/* As DMA buffers are dynamically allocated, we must allocated in begin() /* As DMA buffers are dynamically allocated, we must allocated in begin()
* Ref: https://github.com/espressif/arduino-esp32/issues/831 * Ref: https://github.com/espressif/arduino-esp32/issues/831
*/ */
allocateDMAbuffers(); allocateDMAbuffers();
// Change 'if' to '1' to enable, 0 to not include this Serial output in compiled program // Change 'if' to '1' to enable, 0 to not include this Serial output in compiled program
#if 1 #if 1
Serial.printf("Using pin %d for the R1_PIN\n", dma_r1_pin); Serial.printf("Using pin %d for the R1_PIN\n", dma_r1_pin);
Serial.printf("Using pin %d for the G1_PIN\n", dma_g1_pin); Serial.printf("Using pin %d for the G1_PIN\n", dma_g1_pin);
Serial.printf("Using pin %d for the B1_PIN\n", dma_b1_pin); Serial.printf("Using pin %d for the B1_PIN\n", dma_b1_pin);
Serial.printf("Using pin %d for the R2_PIN\n", dma_r2_pin); Serial.printf("Using pin %d for the R2_PIN\n", dma_r2_pin);
Serial.printf("Using pin %d for the G2_PIN\n", dma_g2_pin); Serial.printf("Using pin %d for the G2_PIN\n", dma_g2_pin);
Serial.printf("Using pin %d for the B2_PIN\n", dma_b2_pin); Serial.printf("Using pin %d for the B2_PIN\n", dma_b2_pin);
Serial.printf("Using pin %d for the A_PIN\n", dma_a_pin); Serial.printf("Using pin %d for the A_PIN\n", dma_a_pin);
Serial.printf("Using pin %d for the B_PIN\n", dma_b_pin); Serial.printf("Using pin %d for the B_PIN\n", dma_b_pin);
Serial.printf("Using pin %d for the C_PIN\n", dma_c_pin); Serial.printf("Using pin %d for the C_PIN\n", dma_c_pin);
Serial.printf("Using pin %d for the D_PIN\n", dma_d_pin); Serial.printf("Using pin %d for the D_PIN\n", dma_d_pin);
Serial.printf("Using pin %d for the E_PIN\n", dma_e_pin); Serial.printf("Using pin %d for the E_PIN\n", dma_e_pin);
Serial.printf("Using pin %d for the LAT_PIN\n", dma_lat_pin); Serial.printf("Using pin %d for the LAT_PIN\n", dma_lat_pin);
Serial.printf("Using pin %d for the OE_PIN\n", dma_oe_pin); Serial.printf("Using pin %d for the OE_PIN\n", dma_oe_pin);
Serial.printf("Using pin %d for the CLK_PIN\n", dma_clk_pin); Serial.printf("Using pin %d for the CLK_PIN\n", dma_clk_pin);
#endif #endif
configureDMA(dma_r1_pin, dma_g1_pin, dma_b1_pin, dma_r2_pin, dma_g2_pin, dma_b2_pin, dma_a_pin, dma_b_pin, dma_c_pin, dma_d_pin, dma_e_pin, dma_lat_pin, dma_oe_pin, dma_clk_pin ); //DMA and I2S configuration and setup configureDMA(dma_r1_pin, dma_g1_pin, dma_b1_pin, dma_r2_pin, dma_g2_pin, dma_b2_pin, dma_a_pin, dma_b_pin, dma_c_pin, dma_d_pin, dma_e_pin, dma_lat_pin, dma_oe_pin, dma_clk_pin ); //DMA and I2S configuration and setup
flushDMAbuffer(); flushDMAbuffer();
swapBuffer(); swapBuffer();
flushDMAbuffer(); flushDMAbuffer();
swapBuffer(); swapBuffer();
} }
// TODO: Disable/Enable auto buffer flipping (useful for lots of drawPixel usage)... // TODO: Disable/Enable auto buffer flipping (useful for lots of drawPixel usage)...
// Draw pixels // Draw pixels
virtual void drawPixel(int16_t x, int16_t y, uint16_t color); // overwrite adafruit implementation virtual void drawPixel(int16_t x, int16_t y, uint16_t color); // overwrite adafruit implementation
virtual void fillScreen(uint16_t color); // overwrite adafruit implementation virtual void fillScreen(uint16_t color); // overwrite adafruit implementation
void clearScreen() { fillScreen(0); } void clearScreen() { fillScreen(0); }
inline void drawPixelRGB565(int16_t x, int16_t y, uint16_t color); void drawPixelRGB565(int16_t x, int16_t y, uint16_t color);
inline void drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b); void drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b);
inline void drawPixelRGB24(int16_t x, int16_t y, rgb_24 color); void drawPixelRGB24(int16_t x, int16_t y, rgb_24 color);
// TODO: Draw a frame! Oooh. // TODO: Draw a frame! Oooh.
//void writeRGB24Frame2DMABuffer(rgb_24 *framedata, int16_t frame_width, int16_t frame_height); //void writeRGB24Frame2DMABuffer(rgb_24 *framedata, int16_t frame_width, int16_t frame_height);
@ -236,9 +245,14 @@ class RGB64x32MatrixPanel_I2S_DMA : public Adafruit_GFX {
backbuf_id ^=1; backbuf_id ^=1;
} }
void refreshDMAOutput()
{
i2s_parallel_flip_to_buffer(&I2S1, backbuf_id);
}
void setBrightness(int _brightness) void setBrightness(int _brightness)
{ {
// Change to set the brightness of the display, range of 1 to matrixWidth (i.e. 1 - 64) // Change to set the brightness of the display, range of 1 to matrixWidth (i.e. 1 - 64)
brightness = _brightness; brightness = _brightness;
} }
@ -247,50 +261,54 @@ class RGB64x32MatrixPanel_I2S_DMA : public Adafruit_GFX {
private: private:
void allocateDMAbuffers() void allocateDMAbuffers()
{ {
matrixUpdateFrames = (frameStruct *)heap_caps_malloc(sizeof(frameStruct) * ESP32_NUM_FRAME_BUFFERS, MALLOC_CAP_DMA); matrixUpdateFrames = (frameStruct *)heap_caps_malloc(sizeof(frameStruct) * ESP32_NUM_FRAME_BUFFERS, MALLOC_CAP_DMA);
Serial.printf("Allocating DMA Refresh Buffer...\r\nTotal DMA Memory available: %d bytes total. Largest free block: %d bytes\r\n", heap_caps_get_free_size(MALLOC_CAP_DMA), heap_caps_get_largest_free_block(MALLOC_CAP_DMA)); Serial.printf("Allocating DMA Refresh Buffer...\r\nTotal DMA Memory available: %d bytes total. Largest free block: %d bytes\r\n", heap_caps_get_free_size(MALLOC_CAP_DMA), heap_caps_get_largest_free_block(MALLOC_CAP_DMA));
} // end initMatrixDMABuffer() } // end initMatrixDMABuffer()
void flushDMAbuffer() void flushDMAbuffer()
{ {
Serial.printf("Flushing buffer %d", backbuf_id); Serial.printf("Flushing buffer %d", backbuf_id);
// Need to wipe the contents of the matrix buffers or weird things happen. // Need to wipe the contents of the matrix buffers or weird things happen.
for (int y=0;y<MATRIX_HEIGHT; y++) for (int y=0;y<MATRIX_HEIGHT; y++)
for (int x=0;x<MATRIX_WIDTH; x++) for (int x=0;x<MATRIX_WIDTH; x++)
{ {
//Serial.printf("\r\nFlushing x, y coord %d, %d", x, y); //Serial.printf("\r\nFlushing x, y coord %d, %d", x, y);
updateMatrixDMABuffer( x, y, 0, 0, 0); updateMatrixDMABuffer( x, y, 0, 0, 0);
} }
} }
void configureDMA(int r1_pin, int g1_pin, int b1_pin, int r2_pin, int g2_pin, int b2_pin, int a_pin, int b_pin, int c_pin, int d_pin, int e_pin, int lat_pin, int oe_pin, int clk_pin); // Get everything setup. Refer to the .c file void configureDMA(int r1_pin, int g1_pin, int b1_pin, int r2_pin, int g2_pin, int b2_pin, int a_pin, int b_pin, int c_pin, int d_pin, int e_pin, int lat_pin, int oe_pin, int clk_pin); // Get everything setup. Refer to the .c file
// Update a specific pixel in the DMA buffer a colour // Update a specific pixel in the DMA buffer to a colour
void updateMatrixDMABuffer(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t blue); void updateMatrixDMABuffer(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t blue);
// Update the entire DMA buffer a certain colour (wipe the screen basically) // Update the entire DMA buffer (aka. The RGB Panel) a certain colour (wipe the screen basically)
void updateMatrixDMABuffer(uint8_t red, uint8_t green, uint8_t blue); void updateMatrixDMABuffer(uint8_t red, uint8_t green, uint8_t blue);
// Internal variables // Internal variables
bool dma_configuration_success; bool immediateUpdate; // Purpose: as per the variable name says, the minute we change a pixel, tell the ESP32 to use this for the I2S DMA Output
bool doubleBuffer; bool autoBackBufferFlip; // (Note: currently not used) Purpose: do we use the other buffer automatically after an update, or change to the 2nd buffer manually? Only use this if you know what you're doing. Otherwise for example, calling drawPixel three times (i.e. you're updating 3 pixels), will update pixel 1 on buffer 0, pixel on buffer 1, and pixel 3 on buffer 0 again - so you'll get weird output.
// 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) // Setup
frameStruct *matrixUpdateFrames; bool dma_configuration_success;
int lsbMsbTransitionBit; // 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)
int refreshRate; frameStruct *matrixUpdateFrames;
int backbuf_id; // which buffer is the DMA backbuffer, as in, which one is not active so we can write to it int lsbMsbTransitionBit;
int refreshRate;
int backbuf_id; // which buffer is the DMA backbuffer, as in, which one is not active so we can write to it
int brightness; int brightness;
}; // end Class header }; // end Class header
/***************************************************************************************/ /***************************************************************************************/
// 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. */
inline void RGB64x32MatrixPanel_I2S_DMA::drawPixel(int16_t x, int16_t y, uint16_t color) // adafruit virtual void override inline void RGB64x32MatrixPanel_I2S_DMA::drawPixel(int16_t x, int16_t y, uint16_t color) // adafruit virtual void override
{ {
drawPixelRGB565( x, y, color); drawPixelRGB565( x, y, color);