This commit is contained in:
mrcodetastic 2025-02-24 01:38:50 +00:00
parent 67c4decce0
commit 33883656da
2 changed files with 206 additions and 187 deletions

View file

@ -13,7 +13,7 @@
// #define getRowDataPtr(row, _dpth, buff_id) &(dma_buff.rowBits[row]->data[_dpth * dma_buff.rowBits[row]->width + buff_id*(dma_buff.rowBits[row]->width * dma_buff.rowBits[row]->colour_depth)])
// BufferID is now ignored, seperate global pointer pointer!
#define getRowDataPtr(row, _dpth, buff_id) &(fb->rowBits[row]->data[_dpth * fb->rowBits[row]->width])
#define getRowDataPtr(row, _dpth) &(fb->rowBits[row]->data[_dpth * fb->rowBits[row]->width])
/* We need to update the correct uint16_t in the rowBitStruct array, that gets sent out in parallel
* 16 bit parallel mode - Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering
@ -32,46 +32,53 @@
*/
#define PIXEL_COLOR_MASK_BIT(color_depth_index, mask_offset) (1 << (color_depth_index + mask_offset))
bool MatrixPanel_I2S_DMA::allocateDMAmemory()
bool MatrixPanel_I2S_DMA::setupDMA(const HUB75_I2S_CFG &_cfg)
{
/***
* Step 0: Allocate basic DMA framebuffer memory for the data we send out in parallel to the HUB75 panel.
* Colour depth is the only consideration.
*
*/
ESP_LOGI("I2S-DMA", "Free heap: %d", heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
ESP_LOGI("I2S-DMA", "Free SPIRAM: %d", heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
// Alright, theoretically we should be OK, so let us do this, so
// lets allocate a chunk of memory for each row (a row could span multiple panels if chaining is in place)
ESP_LOGI("I2S-DMA", "allocating rowBitStructs with pixel_color_depth_bits of %d", m_cfg.getPixelColorDepthBits());
// iterate through number of rows, allocate memory for each
size_t allocated_fb_memory = 0;
int fbs_required = (m_cfg.double_buff) ? 2 : 1;
for (int fb = 0; fb < (fbs_required); fb++)
{
frame_buffer[fb].rowBits.reserve(ROWS_PER_FRAME);
for (int malloc_num = 0; malloc_num < ROWS_PER_FRAME; malloc_num++)
{
auto ptr = std::make_shared<rowBitStruct>(PIXELS_PER_ROW, m_cfg.getPixelColorDepthBits(), m_cfg.double_buff);
auto ptr = std::make_shared<rowBitStruct>(PIXELS_PER_ROW, m_cfg.getPixelColorDepthBits());
if (ptr->data == nullptr)
{
ESP_LOGE("I2S-DMA", "CRITICAL ERROR: Not enough memory for requested colour depth! Please reduce pixel_color_depth_bits value.\r\n");
ESP_LOGE("I2S-DMA", "Could not allocate rowBitStruct %d!.\r\n", malloc_num);
if (ptr->data == nullptr) {
ESP_LOGE("I2S-DMA", "CRITICAL ERROR: Not enough memory for requested colour depth of %d bits! Please reduce pixel_color_depth_bits value.\r\n", m_cfg.getPixelColorDepthBits());
return false;
// TODO: should we release all previous rowBitStructs here???
}
allocated_fb_memory += ptr->getColorDepthSize(); // byte required to display all colour depths for the rows shown at the same time
allocated_fb_memory += ptr->getColorDepthSize(false); // byte required to display all colour depths for the two parallel rows
frame_buffer[fb].rowBits.emplace_back(ptr); // save new rowBitStruct pointer into rows vector
++frame_buffer[fb].rows;
}
}
ESP_LOGI("I2S-DMA", "Allocating %d bytes memory for DMA BCM framebuffer(s).", allocated_fb_memory);
// calculate the lowest LSBMSB_TRANSITION_BIT value that will fit in memory that will meet or exceed the configured refresh rate
//#define FORCE_COLOR_DEPTH 1
/***
* Step 1: Check what the minimum refresh rate is, and calculate the lsbMsbTransitionBit
* which is the bit at which we can reduce the colour depth to achieve the minimum refresh rate.
*
* This also determines the number of DMA descriptors required per row.
*/
//#define FORCE_COLOR_DEPTH 1
#if !defined(FORCE_COLOR_DEPTH)
@ -80,14 +87,16 @@ bool MatrixPanel_I2S_DMA::allocateDMAmemory()
while (1)
{
int psPerClock = 1000000000000UL / m_cfg.i2sspeed;
int nsPerLatch = ((PIXELS_PER_ROW + CLKS_DURING_LATCH) * psPerClock) / 1000;
int nsPerLatch = ((PIXELS_PER_ROW + CLKS_DURING_LATCH) * psPerClock) / 1000; // time per row
// add time to shift out LSBs + LSB-MSB transition bit - this ignores fractions...
int nsPerRow = m_cfg.getPixelColorDepthBits() * nsPerLatch;
// add time to shift out MSBs
for (int i = lsbMsbTransitionBit + 1; i < m_cfg.getPixelColorDepthBits(); i++)
nsPerRow += (1 << (i - lsbMsbTransitionBit - 1)) * (m_cfg.getPixelColorDepthBits() - i) * nsPerLatch;
// Now add the time for the remaining bit depths
for (int i = lsbMsbTransitionBit + 1; i < m_cfg.getPixelColorDepthBits(); i++) {
//nsPerRow += (1 << (i - lsbMsbTransitionBit - 1)) * (m_cfg.getPixelColorDepthBits() - i) * nsPerLatch;
nsPerRow += (1 << (i - lsbMsbTransitionBit - 1)) * nsPerLatch;
}
int nsPerFrame = nsPerRow * ROWS_PER_FRAME;
int actualRefreshRate = 1000000000UL / (nsPerFrame);
@ -114,154 +123,148 @@ bool MatrixPanel_I2S_DMA::allocateDMAmemory()
#endif
/***
* Step 2a: lsbMsbTransition bit is now finalised - recalculate the DMA descriptor count required, which is used for
* memory allocation of the DMA linked list memory structure.
* Step 2: Calculate the DMA descriptors required, which is used for memory allocation of the DMA linked list memory structure.
* This determines the number of passes required to shift out the colour bits in the framebuffer.
* We need to also take into consderation where a chain of panels (pixels) is so long, it requires more than one DMA payload,
* give this library's DMA output memory allocation approach is by the row.
*/
int numDMAdescriptorsPerRow = 1;
for (int i = lsbMsbTransitionBit + 1; i < m_cfg.getPixelColorDepthBits(); i++)
{
numDMAdescriptorsPerRow += (1 << (i - lsbMsbTransitionBit - 1));
int dma_descs_per_row_1cdepth = (frame_buffer[0].rowBits[0]->getColorDepthSize(true) + DMA_MAX - 1 ) / DMA_MAX;
size_t last_dma_desc_bytes_1cdepth = (frame_buffer[0].rowBits[0]->getColorDepthSize(true) % DMA_MAX);
int dma_descs_per_row_all_cdepths = (frame_buffer[0].rowBits[0]->getColorDepthSize(false) + DMA_MAX - 1 ) / DMA_MAX;
size_t last_dma_desc_bytes_all_cdepths = (frame_buffer[0].rowBits[0]->getColorDepthSize(false) % DMA_MAX);
// Logging the calculated values
ESP_LOGV("I2S-DMA", "dma_descs_per_row_1cdepth: %d", dma_descs_per_row_1cdepth);
ESP_LOGV("I2S-DMA", "last_dma_desc_bytes_1cdepth: %zu", last_dma_desc_bytes_1cdepth);
ESP_LOGV("I2S-DMA", "dma_descs_per_row_all_cdepths: %d", dma_descs_per_row_all_cdepths);
ESP_LOGV("I2S-DMA", "last_dma_desc_bytes_all_cdepths: %zu", last_dma_desc_bytes_all_cdepths);
// Calculate per-row number
int dma_descriptors_per_row = dma_descs_per_row_all_cdepths;
// Add descriptors for MSB bits after transition
for (int i = lsbMsbTransitionBit + 1; i < m_cfg.getPixelColorDepthBits(); i++) {
dma_descriptors_per_row += (1 << (i - lsbMsbTransitionBit - 1)) * dma_descs_per_row_1cdepth;
}
//dma_descriptors_per_row = 1;
ESP_LOGI("I2S-DMA", "Recalculated number of DMA descriptors per row: %d", numDMAdescriptorsPerRow);
// Refer to 'DMA_LL_PAYLOAD_SPLIT' code in configureDMA() below to understand why this exists.
// numDMAdescriptorsPerRow is also used to calculate descount which is super important in i2s_parallel_config_t SoC DMA setup.
if (frame_buffer[0].rowBits[0]->getColorDepthSize() > DMA_MAX)
{
ESP_LOGW("I2S-DMA", "rowBits struct is too large to fit in one DMA transfer payload, splitting required. Adding %d DMA descriptors\n", m_cfg.getPixelColorDepthBits() - 1);
numDMAdescriptorsPerRow += m_cfg.getPixelColorDepthBits() - 1;
// Note: If numDMAdescriptorsPerRow is even just one descriptor too large, DMA linked list will not correctly loop.
}
// Allocate DMA descriptors
int dma_descriptions_required = dma_descriptors_per_row * ROWS_PER_FRAME;
ESP_LOGV("I2S-DMA", "DMA descriptors per row: %d", dma_descriptors_per_row);
ESP_LOGV("I2S-DMA", "DMA descriptors required in total: %d", dma_descriptions_required);
/***
* Step 3: Allocate memory for DMA linked list, linking up each framebuffer row in sequence for GPIO output.
* Step 3: Allocate the DMA descriptor memory via. the relevant platform DMA implementation class.
*/
// malloc the DMA linked list descriptors that i2s_parallel will need
int desccount = numDMAdescriptorsPerRow * ROWS_PER_FRAME;
if (m_cfg.double_buff)
{
if (m_cfg.double_buff) {
dma_bus.enable_double_dma_desc();
}
dma_bus.allocate_dma_desc_memory(desccount);
// point FB we can write to, to 0 / dmadesc_a
fb = &frame_buffer[0];
// Just os we know
initialized = true;
return true;
} // end allocateDMAmemory()
/*
// Version 2.0 March 2023
int MatrixPanel_I2S_DMA::create_descriptor_links(void *data, size_t size, bool dmadesc_b, bool countonly)
{
int len = size;
uint8_t *data2 = (uint8_t *)data;
int n = 0;
while (len)
{
int dmalen = len;
if (dmalen > DMA_MAX)
dmalen = DMA_MAX;
if (!countonly)
dma_bus.create_dma_desc_link(data2, dmalen, dmadesc_b);
len -= dmalen;
data2 += dmalen;
n++;
}
return n;
}
*/
void MatrixPanel_I2S_DMA::configureDMA(const HUB75_I2S_CFG &_cfg)
{
// lldesc_t *previous_dmadesc_a = 0;
// lldesc_t *previous_dmadesc_b = 0;
int current_dmadescriptor_offset = 0;
// HACK: If we need to split the payload in 1/2 so that it doesn't breach DMA_MAX, lets do it by the colour_depth.
/*
int num_dma_payload_colour_depths = m_cfg.getPixelColorDepthBits();
if (frame_buffer[0].rowBits[0]->getColorDepthSize() > DMA_MAX)
if ( !dma_bus.allocate_dma_desc_memory(dma_descriptions_required) )
{
num_dma_payload_colour_depths = 1;
return false;
}
*/
// Fill DMA linked lists for both frames (as in, halves of the HUB75 panel) in sequence (top to bottom)
for (int row = 0; row < ROWS_PER_FRAME; row++)
{
// first set of data is LSB through MSB, single pass (IF TOTAL SIZE < DMA_MAX) - all colour bits are displayed once, which takes care of everything below and including LSBMSB_TRANSITION_BIT
// NOTE: size must be less than DMA_MAX - worst case for library: 16-bpp with 256 pixels per row would exceed this, need to break into two
// link_dma_desc(&dmadesc_a[current_dmadescriptor_offset], previous_dmadesc_a, dma_buff.rowBits[row]->getDataPtr(), dma_buff.rowBits[row]->size(num_dma_payload_colour_depths));
// previous_dmadesc_a = &dmadesc_a[current_dmadescriptor_offset];
/***
* Step 4: Link up the DMA descriptors per the colour depth and rows.
*/
int _dmadescriptor_count = 0; // for tracking
dma_bus.create_dma_desc_link(frame_buffer[0].rowBits[row]->getDataPtr(0, 0), frame_buffer[0].rowBits[row]->getColorDepthSize(), false);
if (m_cfg.double_buff)
fbs_required = 1; // (m_cfg.double_buff) ? 2 : 1;
for (int fb = 0; fb < (fbs_required); fb++)
{
for (int row = 0; row < ROWS_PER_FRAME; row++)
{
dma_bus.create_dma_desc_link(frame_buffer[1].rowBits[row]->getDataPtr(0, 1), frame_buffer[1].rowBits[row]->getColorDepthSize(), true);
}
//ESP_LOGV("I2S-DMA", ">>> Linking DMA descriptors for output row %d", row);
// Link and send all colour data, all passes of everything in one hit. 1 bit colour at least...
for (int dma_desc_all = 0; dma_desc_all < dma_descs_per_row_all_cdepths; dma_desc_all++)
{
size_t payload_bytes = (dma_desc_all == (dma_descs_per_row_all_cdepths-1)) ? last_dma_desc_bytes_all_cdepths:DMA_MAX;
// Log the current descriptor number and the payload size being used.
//ESP_LOGV("I2S-DMA", "Processing dma_desc_all: %d, payload_bytes: %zu", dma_desc_all, payload_bytes);
dma_bus.create_dma_desc_link(frame_buffer[fb].rowBits[row]->getDataPtr(0)+(dma_desc_all*(DMA_MAX/sizeof(ESP32_I2S_DMA_STORAGE_TYPE))), payload_bytes);
_dmadescriptor_count++;
// Log the updated descriptor count after each operation.
//ESP_LOGV("I2S-DMA", "Updated _dmadescriptor_count: %d", _dmadescriptor_count);
}
// Step 2: Handle additional descriptors for bits beyond the lsbMsbTransitionBit
for (int i = lsbMsbTransitionBit + 1; i < m_cfg.getPixelColorDepthBits(); i++)
{
// binary time division setup: we need 2 of bit (LSBMSB_TRANSITION_BIT + 1) four of (LSBMSB_TRANSITION_BIT + 2), etc
// because we sweep through to MSB each time, it divides the number of times we have to sweep in half (saving linked list RAM)
// we need 2^(i - LSBMSB_TRANSITION_BIT - 1) == 1 << (i - LSBMSB_TRANSITION_BIT - 1) passes from i to MSB
current_dmadescriptor_offset++;
for (int k = 0; k < (1 << (i - lsbMsbTransitionBit - 1)); k++)
{
// Link and send all colour data, all passes of everything in one hit.
for (int dma_desc_1cdepth = 0; dma_desc_1cdepth < dma_descs_per_row_1cdepth; dma_desc_1cdepth++)
{
size_t payload_bytes = (dma_desc_1cdepth == (dma_descs_per_row_1cdepth-1)) ? last_dma_desc_bytes_1cdepth:DMA_MAX;
// Log the current bit and the corresponding payload size.
// ESP_LOGV("I2S-DMA", "Processing color depth bit: %d, payload_bytes: %zu", i, payload_bytes);
dma_bus.create_dma_desc_link(frame_buffer[fb].rowBits[row]->getDataPtr(i)+(dma_desc_1cdepth*(DMA_MAX/sizeof(ESP32_I2S_DMA_STORAGE_TYPE))), payload_bytes);
_dmadescriptor_count++;
// Log the updated descriptor count after each operation.
// ESP_LOGV("I2S-DMA", "Updated _dmadescriptor_count: %d", _dmadescriptor_count);
}
} // end K
} // end all other colour depth bits
// If the number of pixels per row is too great for the size of a DMA payload, so we need to split what we were going to send above.
if (frame_buffer[0].rowBits[0]->getColorDepthSize() > DMA_MAX)
{
} // end all rows
} // end framebuffer loop
/*
#include <iostream>
#include <cmath> // For pow function (if needed, though bit shifts are better)
for (int cd = 1; cd < m_cfg.getPixelColorDepthBits(); cd++)
{
dma_bus.create_dma_desc_link(frame_buffer[0].rowBits[row]->getDataPtr(cd, 0), frame_buffer[0].rowBits[row]->getColorDepthSize(1), false);
int main() {
if (m_cfg.double_buff)
{
dma_bus.create_dma_desc_link(frame_buffer[1].rowBits[row]->getDataPtr(cd, 1), frame_buffer[1].rowBits[row]->getColorDepthSize(1), true);
}
int colorDepthBits = 8;
int lsbMsbTransitionBit = 0; // Assuming lsbMsbTransitionBit is 0
current_dmadescriptor_offset++;
// Step 2: Handle additional descriptors for bits beyond the lsbMsbTransitionBit
for (int i = lsbMsbTransitionBit + 1; i < colorDepthBits; i++) {
// Calculate the number of passes required
int passes = 1 << (i - lsbMsbTransitionBit - 1);
} // additional linked list items
} // row depth struct
std::cout << "Bit Level: " << i << ", Passes Required: " << passes << std::endl;
for (int i = lsbMsbTransitionBit + 1; i < m_cfg.getPixelColorDepthBits(); i++)
{
// binary time division setup: we need 2 of bit (LSBMSB_TRANSITION_BIT + 1) four of (LSBMSB_TRANSITION_BIT + 2), etc
// because we sweep through to MSB each time, it divides the number of times we have to sweep in half (saving linked list RAM)
// we need 2^(i - LSBMSB_TRANSITION_BIT - 1) == 1 << (i - LSBMSB_TRANSITION_BIT - 1) passes from i to MSB
// Simulate the inner loop for the number of passes
for (int k = 0; k < passes; k++) {
std::cout << " Pass " << k + 1 << " for bit " << i << std::endl;
}
}
for (int k = 0; k < (1 << (i - lsbMsbTransitionBit - 1)); k++)
{
dma_bus.create_dma_desc_link(frame_buffer[0].rowBits[row]->getDataPtr(i, 0), frame_buffer[0].rowBits[row]->getColorDepthSize(1), false);
return 0;
}
*/
if (m_cfg.double_buff)
{
dma_bus.create_dma_desc_link(frame_buffer[1].rowBits[row]->getDataPtr(i, 1), frame_buffer[1].rowBits[row]->getColorDepthSize(1), true);
}
ESP_LOGI("I2S-DMA", "%d DMA descriptors linked to buffer data vs %d required.", _dmadescriptor_count, dma_descriptions_required);
current_dmadescriptor_offset++;
/***
* Step 5: Set default framebuffer to fb[0]
*/
} // end colour depth ^ 2 linked list
} // end colour depth loop
} // end frame rows
ESP_LOGI("I2S-DMA", "%d DMA descriptors linked to buffer data.", current_dmadescriptor_offset);
fb = &frame_buffer[0];
//
// Setup DMA and Output to GPIO
@ -295,16 +298,14 @@ void MatrixPanel_I2S_DMA::configureDMA(const HUB75_I2S_CFG &_cfg)
dma_bus.config(bus_cfg);
dma_bus.init();
dma_bus.dma_transfer_start();
flipDMABuffer(); // display back buffer 0, draw to 1, ignored if double buffering isn't enabled.
// i2s_parallel_send_dma(ESP32_I2S_DEVICE, &dmadesc_a[0]);
ESP_LOGI("I2S-DMA", "DMA setup completed");
initialized = true;
} // end initMatrixDMABuff
return true;
} // end setupDMA
/* There are 'bits' set in the frameStruct that we simply don't need to set every single time we change a pixel / DMA buffer co-ordinate.
* For example, the bits that determine the address lines, we don't need to set these every time. Once they're in place, and assuming we
@ -396,7 +397,7 @@ void IRAM_ATTR MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint16_t x_coord, uint
// Get the contents at this address,
// it would represent a vector pointing to the full row of pixels for the specified colour depth bit at Y coordinate
ESP32_I2S_DMA_STORAGE_TYPE *p = getRowDataPtr(y_coord, colour_depth_idx, back_buffer_id);
ESP32_I2S_DMA_STORAGE_TYPE *p = getRowDataPtr(y_coord, colour_depth_idx);
// We need to update the correct uint16_t word in the rowBitStruct array pointing to a specific pixel at X - coordinate
p[x_coord] &= _colourbitclear; // reset RGB bits
@ -454,7 +455,7 @@ void MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint8_t red, uint8_t green, uint
--matrix_frame_parallel_row;
// The destination for the pixel row bitstream
ESP32_I2S_DMA_STORAGE_TYPE *p = getRowDataPtr(matrix_frame_parallel_row, colour_depth_idx, back_buffer_id);
ESP32_I2S_DMA_STORAGE_TYPE *p = getRowDataPtr(matrix_frame_parallel_row, colour_depth_idx);
// iterate pixels in a row
int x_coord = fb->rowBits[matrix_frame_parallel_row]->width;
@ -495,7 +496,7 @@ void MatrixPanel_I2S_DMA::clearFrameBuffer(bool _buff_id)
{
--row_idx;
ESP32_I2S_DMA_STORAGE_TYPE *row = fb->rowBits[row_idx]->getDataPtr(0, -1); // set pointer to the HEAD of a buffer holding data for the entire matrix row
ESP32_I2S_DMA_STORAGE_TYPE *row = fb->rowBits[row_idx]->getDataPtr(0); // set pointer to the HEAD of a buffer holding data for the entire matrix row
ESP32_I2S_DMA_STORAGE_TYPE abcde = (ESP32_I2S_DMA_STORAGE_TYPE)row_idx;
abcde <<= BITS_ADDR_OFFSET; // shift row y-coord to match ABCDE bits in vector from 8 to 12
@ -584,7 +585,7 @@ void MatrixPanel_I2S_DMA::clearFrameBuffer(bool _buff_id)
--colouridx;
// switch pointer to a row for a specific colour index
row = fb->rowBits[row_idx]->getDataPtr(colouridx, -1);
row = fb->rowBits[row_idx]->getDataPtr(colouridx);
// DP3246 needs the latch high for 3 clock cycles, so start 2 cycles earlier
if (m_cfg.driver == HUB75_I2S_CFG::DP3246_SM5368)
@ -657,7 +658,7 @@ void MatrixPanel_I2S_DMA::brtCtrlOEv2(uint8_t brt, const int _buff_id)
brightness_in_x_pixels = (brightness_in_x_pixels >> 1) | (brightness_in_x_pixels & 1);
// switch pointer to a row for a specific color index
ESP32_I2S_DMA_STORAGE_TYPE *row = fb->rowBits[row_idx]->getDataPtr(colouridx, _buff_id);
ESP32_I2S_DMA_STORAGE_TYPE *row = fb->rowBits[row_idx]->getDataPtr(colouridx);
// define range of Output Enable on the center of the row
int x_coord_max = (_width + brightness_in_x_pixels + 1) >> 1;
@ -826,7 +827,7 @@ void MatrixPanel_I2S_DMA::hlineDMA(int16_t x_coord, int16_t y_coord, int16_t l,
// Get the contents at this address,
// it would represent a vector pointing to the full row of pixels for the specified colour depth bit at Y coordinate
ESP32_I2S_DMA_STORAGE_TYPE *p = fb->rowBits[y_coord]->getDataPtr(colour_depth_idx, back_buffer_id);
ESP32_I2S_DMA_STORAGE_TYPE *p = fb->rowBits[y_coord]->getDataPtr(colour_depth_idx);
// inlined version works slower here, dunno why :(
// ESP32_I2S_DMA_STORAGE_TYPE *p = getRowDataPtr(y_coord, colour_depth_idx, back_buffer_id);
@ -934,7 +935,7 @@ void MatrixPanel_I2S_DMA::vlineDMA(int16_t x_coord, int16_t y_coord, int16_t l,
// Get the contents at this address,
// it would represent a vector pointing to the full row of pixels for the specified colour depth bit at Y coordinate
// ESP32_I2S_DMA_STORAGE_TYPE *p = getRowDataPtr(_y, colour_depth_idx, back_buffer_id);
ESP32_I2S_DMA_STORAGE_TYPE *p = fb->rowBits[_y]->getDataPtr(colour_depth_idx, back_buffer_id);
ESP32_I2S_DMA_STORAGE_TYPE *p = fb->rowBits[_y]->getDataPtr(colour_depth_idx);
p[x_coord] &= _colourbitclear; // reset RGB bits
p[x_coord] |= RGB_output_bits; // set new RGB bits

View file

@ -130,39 +130,56 @@
* Note: sizeof(data) must be multiple of 32 bits, as ESP32 DMA linked list buffer address pointer must be word-aligned
*/
struct rowBitStruct
/**
* @struct rowBitStruct
* @brief Structure to hold row data for HUB75 matrix panel DMA operations
*
* @var width
* Width of the row in pixels
*
* @var colour_depth
* Number of color depths (i.e. copies of each row for each colur bitmask)
*
* @var data
* Pointer to DMA storage type array holding pixel data for the row
*
* @note Memory allocation differs based on target platform and configuration:
* - For ESP32-S3 with SPIRAM: Allocates aligned memory in SPIRAM
* - For other configurations: Allocates DMA-capable internal memory
*/
{
const size_t width;
const uint8_t colour_depth;
const bool double_buff;
//const bool double_buff;
ESP32_I2S_DMA_STORAGE_TYPE *data;
/** @brief
* Returns size (in bytes) of row of data vectorfor a SINGLE buff for the number of colour depths requested
/** @brief Returns size (in bytes) of a colour depth row data array
*
* @param single_color_depth
* - if true, returns size for a single color depth layer
* - if false, returns total size for all color depth layers for a row.
*
* @returns size_t - Size in bytes required for DMA buffer allocation
*
* default - Returns full data vector size for a SINGLE buff.
* You should only pass either PIXEL_COLOR_DEPTH_BITS or '1' to this
*
*/
size_t getColorDepthSize(uint8_t _dpth = 0)
size_t getColorDepthSize(bool single_color_depth)
{
if (!_dpth)
_dpth = colour_depth;
return width * _dpth * sizeof(ESP32_I2S_DMA_STORAGE_TYPE);
int _cdepth = (single_color_depth) ? 1:colour_depth;
return width * _cdepth * sizeof(ESP32_I2S_DMA_STORAGE_TYPE);
};
/** @brief
* Returns pointer to the row's data vector beginning at pixel[0] for _dpth colour bit
*
* NOTE: this call might be very slow in loops. Due to poor instruction caching in esp32 it might be required a reread from flash
* every loop cycle, better use inlined #define instead in such cases
*/
// inline ESP32_I2S_DMA_STORAGE_TYPE* getDataPtr(const uint8_t _dpth=0, const bool buff_id=0) { return &(data[_dpth*width + buff_id*(width*colour_depth)]); };
// BUFFER ID VALUE IS NOW IGNORED!!!!
inline ESP32_I2S_DMA_STORAGE_TYPE *getDataPtr(const uint8_t _dpth = 0, const bool buff_id = 0) { return &(data[_dpth * width]); };
inline ESP32_I2S_DMA_STORAGE_TYPE *getDataPtr(const uint8_t _dpth = 0) { return &(data[_dpth * width]); };
// constructor - allocates DMA-capable memory to hold the struct data
rowBitStruct(const size_t _width, const uint8_t _depth, const bool _dbuff) : width(_width), colour_depth(_depth), double_buff(_dbuff)
//rowBitStruct(const size_t _width, const uint8_t _depth, const bool _dbuff) : width(_width), colour_depth(_depth), double_buff(_dbuff)
rowBitStruct(const size_t _width, const uint8_t _depth) : width(_width), colour_depth(_depth)
{
// #if defined(SPIRAM_FRAMEBUFFER) && defined (CONFIG_IDF_TARGET_ESP32S3)
@ -170,12 +187,12 @@ struct rowBitStruct
// data = (ESP32_I2S_DMA_STORAGE_TYPE *)heap_caps_aligned_alloc(64, size()+size()*double_buff, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
// No longer have double buffer in the same struct - have a different struct
data = (ESP32_I2S_DMA_STORAGE_TYPE *)heap_caps_aligned_alloc(64, getColorDepthSize(), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
data = (ESP32_I2S_DMA_STORAGE_TYPE *)heap_caps_aligned_alloc(64, getColorDepthSize(false), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
#else
// data = (ESP32_I2S_DMA_STORAGE_TYPE *)heap_caps_malloc( size()+size()*double_buff, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
// No longer have double buffer in the same struct - have a different struct
data = (ESP32_I2S_DMA_STORAGE_TYPE *)heap_caps_malloc(getColorDepthSize(), MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
data = (ESP32_I2S_DMA_STORAGE_TYPE *)heap_caps_malloc(getColorDepthSize(false), MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
#endif
}
@ -411,6 +428,7 @@ public:
if (initialized)
return true; // we don't do this twice or more!
if (!config_set)
return false;
@ -435,31 +453,33 @@ public:
#if defined(SPIRAM_DMA_BUFFER)
// Trick library into dropping colour depth slightly when using PSRAM.
// Actual output clockrate override occurs in configureDMA
m_cfg.i2sspeed = HUB75_I2S_CFG::HZ_8M;
#endif
/* As DMA buffers are dynamically allocated, we must allocated in begin()
* Ref: https://github.com/espressif/arduino-esp32/issues/831
*/
if (!allocateDMAmemory())
if (!setupDMA(m_cfg))
{
return false;
} // couldn't even get the basic ram required.
// Flush the DMA buffers prior to configuring DMA - Avoid visual artefacts on boot.
resetbuffers(); // Must fill the DMA buffer with the initial output bit sequence or the panel will display garbage
// Setup the ESP32 DMA Engine. Sprite_TM built this stuff.
configureDMA(m_cfg); // DMA and I2S configuration and setup
// showDMABuffer(); // show backbuf_id of 0
if (!initialized)
{
ESP_LOGE("being()", "MatrixPanel_I2S_DMA::begin() failed!");
}
// Flush the DMA buffers prior to configuring DMA - Avoid visual artefacts on boot.
resetbuffers(); // Must fill the DMA buffer with the initial output bit sequence or the panel will display garbage
flipDMABuffer(); // display back buffer 0, draw to 1, ignored if double buffering isn't enabled.
// Start output output
dma_bus.init();
dma_bus.dma_transfer_start();
return initialized;
}
@ -754,11 +774,9 @@ protected:
// ------- PRIVATE -------
private:
/* Calculate the memory available for DMA use, do some other stuff, and allocate accordingly */
bool allocateDMAmemory();
/* Setup the DMA Link List chain and initiate the ESP32 DMA engine */
void configureDMA(const HUB75_I2S_CFG &opts);
/* Setup the DMA Link List chain and configure the ESP32 DMA + I2S or LCD peripheral */
bool setupDMA(const HUB75_I2S_CFG &opts);
/**
* pre-init procedures for specific drivers