Compare commits

..

No commits in common. "master" and "legacy-1.2.4" have entirely different histories.

208 changed files with 10231 additions and 10227 deletions

View file

@ -1,11 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "github-actions" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"

View file

@ -1,48 +0,0 @@
name: esp-idf 5.1.2 with Adafruit GFX Library
on:
push:
paths-ignore:
- '**.md'
- 'doc/**'
pull_request:
paths-ignore:
- '**.md'
- 'doc/**'
jobs:
build:
name: esp-idf with Adafruit GFX
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Checkout ESP32-HUB75-MatrixPanel-I2S-DMA component
uses: actions/checkout@v4
with:
path: 'examples/esp-idf/with-gfx/components/ESP32-HUB75-MatrixPanel-I2S-DMA'
- name: Checkout Adafruit-GFX-Library repo
uses: actions/checkout@v4
with:
repository: 'adafruit/Adafruit-GFX-Library'
path: 'examples/esp-idf/with-gfx/components/Adafruit-GFX-Library'
- name: Checkout Adafruit_BusIO repo
uses: actions/checkout@v4
with:
repository: 'adafruit/Adafruit_BusIO'
path: 'examples/esp-idf/with-gfx/components/Adafruit_BusIO'
- name: Checkout arduino-esp32 repo
uses: actions/checkout@v4
with:
repository: 'espressif/arduino-esp32'
path: 'examples/esp-idf/with-gfx/components/arduino'
- name: esp-idf build
uses: espressif/esp-idf-ci-action@v1
with:
esp_idf_version: v5.1.2
target: esp32
path: 'examples/esp-idf/with-gfx'

View file

@ -1,33 +0,0 @@
name: esp-idf 5.1.2 without Adafruit GFX Library
on:
push:
paths-ignore:
- '**.md'
- 'doc/**'
pull_request:
paths-ignore:
- '**.md'
- 'doc/**'
jobs:
build:
name: esp-idf 5.1.2 without Adafruit GFX
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Checkout ESP32-HUB75-MatrixPanel-I2S-DMA component
uses: actions/checkout@v4
with:
path: 'examples/esp-idf/without-gfx/components/ESP32-HUB75-MatrixPanel-I2S-DMA'
- name: esp-idf build
uses: espressif/esp-idf-ci-action@v1
with:
esp_idf_version: v5.1.2
target: esp32
path: 'examples/esp-idf/without-gfx'

View file

@ -1,64 +0,0 @@
# Build examples with Platformio
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
# https://docs.platformio.org/en/latest/integration/ci/github-actions.html
name: PlatformIO 6.1.11 Arduino CI
on:
push:
branches: [ master, dev ]
paths-ignore:
- '**.md'
- 'doc/**'
pull_request:
branches: [ master, dev ]
paths-ignore:
- '**.md'
- 'doc/**'
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
framework: ["Arduino", "IDF"]
no_gfx: ["", -DNO_GFX]
# no_fast_functions: ["", -DNO_FAST_FUNCTIONS]
# no_cie1931: ["", -DNO_CIE1931]
# virtual_panel: ["", -DVIRTUAL_PANE]
example:
- "examples/PIO_TestPatterns"
# exclude:
# - no_fast_functions: ""
# virtual_panel: -DVIRTUAL_PANE
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Cache pip and platformio
uses: actions/cache@v4
with:
path: |
~/.cache/pip
~/.platformio/.cache
key: ${{ runner.os }}-pio
- name: Set up Python 3.x
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install Platformio
run: pip install --upgrade platformio==6.1.11
- name: Run PlatformIO CI (Arduino)
if: ${{ matrix.framework == 'Arduino'}}
env:
PLATFORMIO_BUILD_FLAGS: ${{ matrix.no_gfx }} ${{ matrix.no_fast_functions }} ${{ matrix.no_cie1931 }} ${{ matrix.virtual_panel }}
PLATFORMIO_CI_SRC: ${{ matrix.example }}
run: pio ci -e esp32 -c ${{ matrix.example }}/platformio.ini
- name: Run PlatformIO CI (ESP-IDF)
if: ${{ matrix.framework == 'IDF'}}
env:
PLATFORMIO_BUILD_FLAGS: -DIDF_BUILD ${{ matrix.no_gfx }} ${{ matrix.no_fast_functions }} ${{ matrix.no_cie1931 }} ${{ matrix.virtual_panel }}
# pio ci doesn't use our sdkconfig, so we have to use pio run
#run: pio run -d ${{ matrix.example }} -e esp32idf -c ${{ matrix.example }}/platformio.ini
run: pio run -d ${{ matrix.example }}

View file

@ -1,67 +0,0 @@
# HUB75 RGB LED matrix library utilizing ESP32 DMA Engine
# https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA
# MIT License
cmake_minimum_required(VERSION 3.5)
idf_build_get_property(target IDF_TARGET)
if(ARDUINO_ARCH_ESP32 OR CONFIG_ESP32_HUB75_USE_GFX)
list(APPEND build_dependencies arduino Adafruit-GFX-Library)
else()
list(APPEND build_dependencies esp_lcd driver)
endif()
if(${target} STREQUAL "esp32s3")
list(APPEND extra_srcs src/platforms/${target}/gdma_lcd_parallel16.cpp)
# Required by gdma_lcd_parallel16.cpp
if (NOT esp_lcd IN_LIST build_dependencies)
list(APPEND build_dependencies esp_lcd)
endif()
endif()
idf_component_register(SRCS "src/platforms/esp32/esp32_i2s_parallel_dma.cpp" "src/ESP32-HUB75-MatrixPanel-I2S-DMA.cpp" "src/ESP32-HUB75-MatrixPanel-leddrivers.cpp" ${extra_srcs}
INCLUDE_DIRS "./src"
)
# Dependencies cannot be added to the REQUIRES argument of `idf_component_register` because (according to the build process
# listed at https://docs.espressif.com/projects/esp-idf/en/v4.2/esp32/api-guides/build-system.html#build-process)
# `idf_component_register` is processed during the "Enumeration" stage which happens before the sdkconfig file is loaded
# in the "Processing" stage. So if dependencies are going to be loaded based on certain CONFIG_* variables we must
# use `target_link_libraries` instead. This is the method used by Arduino's CMakeLists.txt file.
idf_build_get_property(components BUILD_COMPONENTS)
foreach(component_name IN LISTS build_dependencies)
if (NOT ${component_name} IN_LIST components)
message(FATAL_ERROR "Missing component: ${component_name}")
endif()
idf_component_get_property(lib_name ${component_name} COMPONENT_LIB)
target_link_libraries(${COMPONENT_LIB} PUBLIC ${lib_name})
endforeach()
# In case you are running into issues with "missing" header files from 3rd party libraries
# you can add them to the REQUIRES section above. If you use some of the build options below
# you probably want to remove (NO_GFX) or replace Adafruit-GFX-Library (USE_GFX_ROOT)
# Example to build with USE_GFX_ROOT or NO_GFX / just uncomment the appropriate line
# target_compile_options(${COMPONENT_TARGET} PUBLIC -DUSE_GFX_ROOT)
# target_compile_options(${COMPONENT_TARGET} PUBLIC -DNO_GFX)
# esp-idf does not have any GFX library support yet, so we need to define NO_GFX
if(ARDUINO_ARCH_ESP32 OR CONFIG_ESP32_HUB75_USE_GFX)
else()
target_compile_options(${COMPONENT_TARGET} PUBLIC -DNO_GFX)
if(${target} STREQUAL "esp32s3")
# Don't enable PSRAM based framebuffer just because it's an S3.
# This is an advanced option and should only be used with an S3 with Octal-SPI RAM.
# target_compile_options(${COMPONENT_TARGET} PUBLIC -DSPIRAM_FRAMEBUFFER)
target_compile_options(${COMPONENT_TARGET} PUBLIC)
endif()
endif()
# You can also use multiple options like this
# target_compile_options(${COMPONENT_TARGET} PUBLIC -DNO_GFX -DNO_FAST_FUNCTIONS)
# All options can be found here:
# https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/blob/master/doc/BuildOptions.md
project(ESP32-HUB75-MatrixPanel-I2S-DMA)

View file

@ -0,0 +1,722 @@
#include "ESP32-HUB75-MatrixPanel-I2S-DMA.h"
// Credits: Louis Beaudoin <https://github.com/pixelmatix/SmartMatrix/tree/teensylc>
// and Sprite_TM: https://www.esp32.com/viewtopic.php?f=17&t=3188 and https://www.esp32.com/viewtopic.php?f=13&t=3256
/*
This is example code to driver a p3(2121)64*32 -style RGB LED display. These types of displays do not have memory and need to be refreshed
continuously. The display has 2 RGB inputs, 4 inputs to select the active line, a pixel clock input, a latch enable input and an output-enable
input. The display can be seen as 2 64x16 displays consisting of the upper half and the lower half of the display. Each half has a separate
RGB pixel input, the rest of the inputs are shared.
Each display half can only show one line of RGB pixels at a time: to do this, the RGB data for the line is input by setting the RGB input pins
to the desired value for the first pixel, giving the display a clock pulse, setting the RGB input pins to the desired value for the second pixel,
giving a clock pulse, etc. Do this 64 times to clock in an entire row. The pixels will not be displayed yet: until the latch input is made high,
the display will still send out the previously clocked in line. Pulsing the latch input high will replace the displayed data with the data just
clocked in.
The 4 line select inputs select where the currently active line is displayed: when provided with a binary number (0-15), the latched pixel data
will immediately appear on this line. Note: While clocking in data for a line, the *previous* line is still displayed, and these lines should
be set to the value to reflect the position the *previous* line is supposed to be on.
Finally, the screen has an OE input, which is used to disable the LEDs when latching new data and changing the state of the line select inputs:
doing so hides any artefacts that appear at this time. The OE line is also used to dim the display by only turning it on for a limited time every
line.
All in all, an image can be displayed by 'scanning' the display, say, 100 times per second. The slowness of the human eye hides the fact that
only one line is showed at a time, and the display looks like every pixel is driven at the same time.
Now, the RGB inputs for these types of displays are digital, meaning each red, green and blue subpixel can only be on or off. This leads to a
color palette of 8 pixels, not enough to display nice pictures. To get around this, we use binary code modulation.
Binary code modulation is somewhat like PWM, but easier to implement in our case. First, we define the time we would refresh the display without
binary code modulation as the 'frame time'. For, say, a four-bit binary code modulation, the frame time is divided into 15 ticks of equal length.
We also define 4 subframes (0 to 3), defining which LEDs are on and which LEDs are off during that subframe. (Subframes are the same as a
normal frame in non-binary-coded-modulation mode, but are showed faster.) From our (non-monochrome) input image, we take the (8-bit: bit 7
to bit 0) RGB pixel values. If the pixel values have bit 7 set, we turn the corresponding LED on in subframe 3. If they have bit 6 set,
we turn on the corresponding LED in subframe 2, if bit 5 is set subframe 1, if bit 4 is set in subframe 0.
Now, in order to (on average within a frame) turn a LED on for the time specified in the pixel value in the input data, we need to weigh the
subframes. We have 15 pixels: if we show subframe 3 for 8 of them, subframe 2 for 4 of them, subframe 1 for 2 of them and subframe 1 for 1 of
them, this 'automatically' happens. (We also distribute the subframes evenly over the ticks, which reduces flicker.)
In this code, we use the I2S peripheral in parallel mode to achieve this. Essentially, first we allocate memory for all subframes. This memory
contains a sequence of all the signals (2xRGB, line select, latch enable, output enable) that need to be sent to the display for that subframe.
Then we ask the I2S-parallel driver to set up a DMA chain so the subframes are sent out in a sequence that satisfies the requirement that
subframe x has to be sent out for (2^x) ticks. Finally, we fill the subframes with image data.
We use a front buffer/back buffer technique here to make sure the display is refreshed in one go and drawing artifacts do not reach the display.
In practice, for small displays this is not really necessarily.
*/
// For development testing only
//#define IGNORE_REFRESH_RATE 1
uint8_t val2PWM(int val) {
if (val<0) val=0;
if (val>255) val=255;
return lumConvTab[val];
}
bool MatrixPanel_I2S_DMA::allocateDMAmemory()
{
/***
* Step 1: Look at the overall DMA capable memory for the DMA FRAMEBUFFER data only (not the DMA linked list descriptors yet)
* and do some pre-checks.
*/
int _num_frame_buffers = (double_buffering_enabled) ? 2:1;
size_t _frame_buffer_memory_required = sizeof(frameStruct) * _num_frame_buffers;
size_t _dma_linked_list_memory_required = 0;
size_t _total_dma_capable_memory_reserved = 0;
// 1. Calculate the amount of DMA capable memory that's actually available
#if SERIAL_DEBUG
Serial.printf("Panel Height: %d pixels.\r\n", MATRIX_HEIGHT);
Serial.printf("Panel Width: %d pixels.\r\n", MATRIX_WIDTH);
if (double_buffering_enabled) {
Serial.println("DOUBLE FRAME BUFFERS / DOUBLE BUFFERING IS ENABLED. DOUBLE THE RAM REQUIRED!");
}
Serial.println("DMA memory blocks available before any malloc's: ");
heap_caps_print_heap_info(MALLOC_CAP_DMA);
Serial.printf("We're going to need %d bytes of SRAM just for the frame buffer(s).\r\n", _frame_buffer_memory_required);
Serial.printf("The total amount of DMA capable SRAM memory is %d bytes.\r\n", heap_caps_get_free_size(MALLOC_CAP_DMA));
Serial.printf("Largest DMA capable SRAM memory block is %d bytes.\r\n", heap_caps_get_largest_free_block(MALLOC_CAP_DMA));
#endif
// Can we potentially fit the framebuffer into the DMA capable memory that's available?
if ( heap_caps_get_free_size(MALLOC_CAP_DMA) < _frame_buffer_memory_required ) {
#if SERIAL_DEBUG
Serial.printf("######### Insufficient memory for requested resolution. Reduce MATRIX_COLOR_DEPTH and try again.\r\n\tAdditional %d bytes of memory required.\r\n\r\n", (_frame_buffer_memory_required-heap_caps_get_free_size(MALLOC_CAP_DMA)) );
#endif
return false;
}
// Alright, theoretically we should be OK, so let us do this, so
// lets allocate a chunk of memory for each row (a row could span multiple panels if chaining is in place)
for (int malloc_num =0; malloc_num < ROWS_PER_FRAME; malloc_num++)
{
matrix_row_framebuffer_malloc[malloc_num] = (rowColorDepthStruct *)heap_caps_malloc( (sizeof(rowColorDepthStruct) * _num_frame_buffers) , MALLOC_CAP_DMA);
// If the ESP crashes here, then we must have a horribly fragmented memory space, or trying to allocate a ludicrous resolution.
#if SERIAL_DEBUG
Serial.printf("Malloc'ing %d bytes of memory @ address %ud for frame row %d.\r\n", (sizeof(rowColorDepthStruct) * _num_frame_buffers), (unsigned int)matrix_row_framebuffer_malloc[malloc_num], malloc_num);
#endif
if ( matrix_row_framebuffer_malloc[malloc_num] == NULL ) {
Serial.printf("ERROR: Couldn't malloc matrix_row_framebuffer %d! Critical fail.\r\n", malloc_num);
return false;
}
}
_total_dma_capable_memory_reserved += _frame_buffer_memory_required;
/***
* Step 2: Calculate the amount of memory required for the DMA engine's linked list descriptors.
* Credit to SmartMatrix for this stuff.
*/
// Calculate what colour depth is actually possible based on memory available vs. required dma linked-list descriptors.
// aka. Calculate the lowest LSBMSB_TRANSITION_BIT value that will fit in memory
int numDMAdescriptorsPerRow = 0;
lsbMsbTransitionBit = 0;
while(1) {
numDMAdescriptorsPerRow = 1;
for(int i=lsbMsbTransitionBit + 1; i<PIXEL_COLOR_DEPTH_BITS; i++) {
numDMAdescriptorsPerRow += (1<<(i - lsbMsbTransitionBit - 1));
}
int ramrequired = numDMAdescriptorsPerRow * ROWS_PER_FRAME * _num_frame_buffers * sizeof(lldesc_t);
int largestblockfree = heap_caps_get_largest_free_block(MALLOC_CAP_DMA);
#if SERIAL_DEBUG
Serial.printf("lsbMsbTransitionBit of %d with %d DMA descriptors per frame row, requires %d bytes RAM, %d available, leaving %d free: \r\n", lsbMsbTransitionBit, numDMAdescriptorsPerRow, ramrequired, largestblockfree, largestblockfree - ramrequired);
#endif
if(ramrequired < largestblockfree)
break;
if(lsbMsbTransitionBit < PIXEL_COLOR_DEPTH_BITS - 1)
lsbMsbTransitionBit++;
else
break;
}
Serial.printf("Raised lsbMsbTransitionBit to %d/%d to fit in remaining RAM\r\n", lsbMsbTransitionBit, PIXEL_COLOR_DEPTH_BITS - 1);
#ifndef IGNORE_REFRESH_RATE
// calculate the lowest LSBMSB_TRANSITION_BIT value that will fit in memory that will meet or exceed the configured refresh rate
while(1) {
int psPerClock = 1000000000000UL/ESP32_I2S_CLOCK_SPEED;
int nsPerLatch = ((PIXELS_PER_ROW + CLKS_DURING_LATCH) * psPerClock) / 1000;
// add time to shift out LSBs + LSB-MSB transition bit - this ignores fractions...
int nsPerRow = PIXEL_COLOR_DEPTH_BITS * nsPerLatch;
// add time to shift out MSBs
for(int i=lsbMsbTransitionBit + 1; i<PIXEL_COLOR_DEPTH_BITS; i++)
nsPerRow += (1<<(i - lsbMsbTransitionBit - 1)) * (PIXEL_COLOR_DEPTH_BITS - i) * nsPerLatch;
int nsPerFrame = nsPerRow * ROWS_PER_FRAME;
int actualRefreshRate = 1000000000UL/(nsPerFrame);
calculated_refresh_rate = actualRefreshRate;
#if SERIAL_DEBUG
Serial.printf("lsbMsbTransitionBit of %d gives %d Hz refresh: \r\n", lsbMsbTransitionBit, actualRefreshRate);
#endif
if (actualRefreshRate > min_refresh_rate) // HACK Hard Coded: 100
break;
if(lsbMsbTransitionBit < PIXEL_COLOR_DEPTH_BITS - 1)
lsbMsbTransitionBit++;
else
break;
}
Serial.printf("Raised lsbMsbTransitionBit to %d/%d to meet minimum refresh rate\r\n", lsbMsbTransitionBit, PIXEL_COLOR_DEPTH_BITS - 1);
#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.
*/
numDMAdescriptorsPerRow = 1;
for(int i=lsbMsbTransitionBit + 1; i<PIXEL_COLOR_DEPTH_BITS; i++) {
numDMAdescriptorsPerRow += (1<<(i - lsbMsbTransitionBit - 1));
}
#if SERIAL_DEBUG
Serial.printf("Recalculated number of DMA descriptors per row: %d\n", numDMAdescriptorsPerRow);
#endif
// Refer to 'DMA_LL_PAYLOAD_SPLIT' code in configureDMA() below to understand why this exists.
// numDMAdescriptorsPerRow is also used to calcaulte descount which is super important in i2s_parallel_config_t SoC DMA setup.
if ( sizeof(rowColorDepthStruct) > DMA_MAX ) {
#if SERIAL_DEBUG
Serial.printf("rowColorDepthStruct struct is too large, split DMA payload required. Adding %d DMA descriptors\n", PIXEL_COLOR_DEPTH_BITS-1);
#endif
numDMAdescriptorsPerRow += PIXEL_COLOR_DEPTH_BITS-1;
// Not if numDMAdescriptorsPerRow is even just one descriptor too large, DMA linked list will not correctly loop.
}
/***
* Step 3: Allocate memory for DMA linked list, linking up each framebuffer row in sequence for GPIO output.
*/
_dma_linked_list_memory_required = numDMAdescriptorsPerRow * ROWS_PER_FRAME * _num_frame_buffers * sizeof(lldesc_t);
#if SERIAL_DEBUG
Serial.printf("Descriptors for lsbMsbTransitionBit of %d/%d with %d frame rows require %d bytes of DMA RAM with %d numDMAdescriptorsPerRow.\r\n", lsbMsbTransitionBit, PIXEL_COLOR_DEPTH_BITS - 1, ROWS_PER_FRAME, _dma_linked_list_memory_required, numDMAdescriptorsPerRow);
#endif
_total_dma_capable_memory_reserved += _dma_linked_list_memory_required;
// Do a final check to see if we have enough space for the additional DMA linked list descriptors that will be required to link it all up!
if(_dma_linked_list_memory_required > heap_caps_get_largest_free_block(MALLOC_CAP_DMA)) {
Serial.printf("ERROR: Not enough SRAM left over for DMA linked-list descriptor memory reservation! Oh so close!\r\n");
return false;
} // linked list descriptors memory check
// malloc the DMA linked list descriptors that i2s_parallel will need
desccount = numDMAdescriptorsPerRow * ROWS_PER_FRAME;
//lldesc_t * dmadesc_a = (lldesc_t *)heap_caps_malloc(desccount * sizeof(lldesc_t), MALLOC_CAP_DMA);
dmadesc_a = (lldesc_t *)heap_caps_malloc(desccount * sizeof(lldesc_t), MALLOC_CAP_DMA);
assert("Can't allocate descriptor framebuffer a");
if(!dmadesc_a) {
Serial.printf("ERROR: Could not malloc descriptor framebuffer a.");
return false;
}
if (double_buffering_enabled) // reserve space for second framebuffer linked list
{
//lldesc_t * dmadesc_b = (lldesc_t *)heap_caps_malloc(desccount * sizeof(lldesc_t), MALLOC_CAP_DMA);
dmadesc_b = (lldesc_t *)heap_caps_malloc(desccount * sizeof(lldesc_t), MALLOC_CAP_DMA);
assert("Could not malloc descriptor framebuffer b.");
if(!dmadesc_b) {
Serial.printf("ERROR: Could not malloc descriptor framebuffer b.");
return false;
}
}
Serial.printf("*** ESP32-HUB75-MatrixPanel-I2S-DMA: Memory Allocations Complete *** \r\n");
Serial.printf("Total memory that was reserved: %d kB.\r\n", _total_dma_capable_memory_reserved/1024);
Serial.printf("... of which was used for the DMA Linked List(s): %d kB.\r\n", _dma_linked_list_memory_required/1024);
Serial.printf("Heap Memory Available: %d bytes total. Largest free block: %d bytes.\r\n", heap_caps_get_free_size(0), heap_caps_get_largest_free_block(0));
Serial.printf("General RAM Available: %d bytes total. Largest free block: %d bytes.\r\n", heap_caps_get_free_size(MALLOC_CAP_DEFAULT), heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT));
#if SERIAL_DEBUG
Serial.println("DMA capable memory map available after malloc's: ");
heap_caps_print_heap_info(MALLOC_CAP_DMA);
delay(1000);
#endif
// Just os we know
everything_OK = true;
return true;
} // end allocateDMAmemory()
void MatrixPanel_I2S_DMA::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)
{
#if SERIAL_DEBUG
Serial.println("configureDMA(): Starting configuration of DMA engine.\r\n");
#endif
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 color_depth.
int num_dma_payload_color_depths = PIXEL_COLOR_DEPTH_BITS;
if ( sizeof(rowColorDepthStruct) > DMA_MAX ) {
num_dma_payload_color_depths = 1;
}
// Fill DMA linked lists for both frames (as in, halves of the HUB75 panel) and if double buffering is enabled, link it up for both buffers.
for(int row = 0; row < ROWS_PER_FRAME; row++) {
// Split framebuffer malloc hack 'improvement'
rowColorDepthStruct *fb_malloc_ptr = matrix_row_framebuffer_malloc[row];
#if SERIAL_DEBUG
Serial.printf("Row %d DMA payload of %d bytes. DMA_MAX is %d.\r\n", row, sizeof(rowBitStruct) * PIXEL_COLOR_DEPTH_BITS, DMA_MAX);
#endif
// first set of data is LSB through MSB, single pass (IF TOTAL SIZE < DMA_MAX) - all color bits are displayed once, which takes care of everything below and inlcluding 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, &(fb_malloc_ptr[0].rowbits[0].data), sizeof(rowBitStruct) * num_dma_payload_color_depths);
previous_dmadesc_a = &dmadesc_a[current_dmadescriptor_offset];
if (double_buffering_enabled) {
link_dma_desc(&dmadesc_b[current_dmadescriptor_offset], previous_dmadesc_b, &(fb_malloc_ptr[1].rowbits[0].data), sizeof(rowBitStruct) * num_dma_payload_color_depths);
previous_dmadesc_b = &dmadesc_b[current_dmadescriptor_offset]; }
current_dmadescriptor_offset++;
// If the number of pixels per row is to great for the size of a DMA payload, so we need to split what we were going to send above.
if ( sizeof(rowColorDepthStruct) > DMA_MAX )
{
#if SERIAL_DEBUG
Serial.printf("Spliting DMA payload for %d color depths into %d byte payloads.\r\n", PIXEL_COLOR_DEPTH_BITS-1, sizeof(rowBitStruct) );
#endif
for (int cd = 1; cd < PIXEL_COLOR_DEPTH_BITS; cd++)
{
// first set of data is LSB through MSB, single pass - all color bits are displayed once, which takes care of everything below and inlcluding LSBMSB_TRANSITION_BIT
// TODO: size must be less than DMA_MAX - worst case for library: 16-bpp with 256 pixels per row would exceed this, need to break into two
link_dma_desc(&dmadesc_a[current_dmadescriptor_offset], previous_dmadesc_a, &(fb_malloc_ptr[0].rowbits[cd].data), sizeof(rowBitStruct) );
previous_dmadesc_a = &dmadesc_a[current_dmadescriptor_offset];
if (double_buffering_enabled) {
link_dma_desc(&dmadesc_b[current_dmadescriptor_offset], previous_dmadesc_b, &(fb_malloc_ptr[1].rowbits[cd].data), sizeof(rowBitStruct) );
previous_dmadesc_b = &dmadesc_b[current_dmadescriptor_offset]; }
current_dmadescriptor_offset++;
} // additional linked list items
} // row depth struct
for(int i=lsbMsbTransitionBit + 1; i<PIXEL_COLOR_DEPTH_BITS; 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
//Serial.printf("buffer %d: repeat %d times, size: %d, from %d - %d\r\n", current_dmadescriptor_offset, 1<<(i - lsbMsbTransitionBit - 1), (PIXEL_COLOR_DEPTH_BITS - i), i, PIXEL_COLOR_DEPTH_BITS-1);
#if SERIAL_DEBUG
Serial.printf("configureDMA(): DMA Loops for PIXEL_COLOR_DEPTH_BITS %d is: %d.\r\n", i, (1<<(i - lsbMsbTransitionBit - 1)));
#endif
for(int k=0; k < (1<<(i - lsbMsbTransitionBit - 1)); k++)
{
link_dma_desc(&dmadesc_a[current_dmadescriptor_offset], previous_dmadesc_a, &(fb_malloc_ptr[0].rowbits[i].data), sizeof(rowBitStruct) * (PIXEL_COLOR_DEPTH_BITS - i));
previous_dmadesc_a = &dmadesc_a[current_dmadescriptor_offset];
if (double_buffering_enabled) {
link_dma_desc(&dmadesc_b[current_dmadescriptor_offset], previous_dmadesc_b, &(fb_malloc_ptr[1].rowbits[i].data), sizeof(rowBitStruct) * (PIXEL_COLOR_DEPTH_BITS - i));
previous_dmadesc_b = &dmadesc_b[current_dmadescriptor_offset]; }
current_dmadescriptor_offset++;
} // end color depth ^ 2 linked list
} // end color depth loop
} // end frame rows
#if SERIAL_DEBUG
Serial.printf("configureDMA(): Configured LL structure. %d DMA Linked List descriptors populated.\r\n", current_dmadescriptor_offset);
if ( desccount != current_dmadescriptor_offset)
{
Serial.printf("configureDMA(): ERROR! Expected descriptor count of %d != actual DMA descriptors of %d!\r\n", desccount, current_dmadescriptor_offset);
}
#endif
dmadesc_a[desccount-1].eof = 1;
dmadesc_a[desccount-1].qe.stqe_next=(lldesc_t*)&dmadesc_a[0];
//End markers for DMA LL
if (double_buffering_enabled) {
dmadesc_b[desccount-1].eof = 1;
dmadesc_b[desccount-1].qe.stqe_next=(lldesc_t*)&dmadesc_b[0];
} else {
dmadesc_b = dmadesc_a; // link to same 'a' buffer
}
//Serial.printf("Performing I2S setup.\n");
i2s_parallel_config_t cfg={
.gpio_bus={r1_pin, g1_pin, b1_pin, r2_pin, g2_pin, b2_pin, lat_pin, oe_pin, a_pin, b_pin, c_pin, d_pin, e_pin, -1, -1, -1},
.gpio_clk=clk_pin,
.clkspeed_hz=ESP32_I2S_CLOCK_SPEED, //ESP32_I2S_CLOCK_SPEED, // formula used is 80000000L/(cfg->clkspeed_hz + 1), must result in >=2. Acceptable values 26.67MHz, 20MHz, 16MHz, 13.34MHz...
.bits=ESP32_I2S_DMA_MODE, //ESP32_I2S_DMA_MODE,
.bufa=0,
.bufb=0,
desccount,
desccount,
dmadesc_a,
dmadesc_b
};
//Setup I2S
i2s_parallel_setup_without_malloc(&I2S1, &cfg);
#if SERIAL_DEBUG
Serial.println("configureDMA(): DMA configuration completed on I2S1.\r\n");
#endif
#if SERIAL_DEBUG
Serial.println("DMA Memory Map after DMA LL allocations: ");
heap_caps_print_heap_info(MALLOC_CAP_DMA);
delay(1000);
#endif
} // end initMatrixDMABuff
/* 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
* don't accidently clear them, then we don't need to set them again.
* So to save processing, we strip this logic out to the absolute bare minimum, which is toggling only the R,G,B pixels (bits) per co-ord.
*
* Critical dependency: That 'updateMatrixDMABuffer(uint8_t red, uint8_t green, uint8_t blue)' has been run at least once over the
* entire frameBuffer to ensure all the non R,G,B bitmasks are in place (i.e. like OE, Address Lines etc.)
*
* Note: If you change the brightness with setBrightness() you MUST then clearScreen() and repaint / flush the entire framebuffer.
*/
/* Update a specific co-ordinate in the DMA buffer */
/* Original version were we re-create the bitstream from scratch for each x,y co-ordinate / pixel changed. Slightly slower. */
void MatrixPanel_I2S_DMA::updateMatrixDMABuffer(int16_t x_coord, int16_t y_coord, uint8_t red, uint8_t green, uint8_t blue)
{
if ( !everything_OK ) {
#if SERIAL_DEBUG
Serial.println("Cannot updateMatrixDMABuffer as setup failed!");
#endif
return;
}
/* 1) Check that the co-ordinates are within range, or it'll break everything big time.
* Valid co-ordinates are from 0 to (MATRIX_XXXX-1)
*/
if ( x_coord < 0 || y_coord < 0 || x_coord >= MATRIX_WIDTH || y_coord >= MATRIX_HEIGHT) {
return;
}
/* LED Brightness Compensation. Because if we do a basic "red & mask" for example,
* we'll NEVER send the dimmest possible colour, due to binary skew.
* i.e. It's almost impossible for color_depth_idx of 0 to be sent out to the MATRIX unless the 'value' of a color is exactly '1'
* https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/
*/
red = lumConvTab[red];
green = lumConvTab[green];
blue = lumConvTab[blue];
/* When using the drawPixel, we are obviously only changing the value of one x,y position,
* however, the two-scan panels paint TWO lines at the same time
* and this reflects the parallel in-DMA-memory data structure of uint16_t's that are getting
* pumped out at high speed.
*
* So we need to ensure we persist the bits (8 of them) of the uint16_t for the row we aren't changing.
*
* The DMA buffer order has also been reversed (refer to the last code in this function)
* so we have to check for this and check the correct position of the MATRIX_DATA_STORAGE_TYPE
* data.
*/
bool painting_top_frame = true;
if ( y_coord >= ROWS_PER_FRAME) // co-ords start at zero, y_coord = 15 => 16 (rows per frame)
{
y_coord -= ROWS_PER_FRAME; // Subtract the ROWS_PER_FRAME from the pixel co-ord to get the panel ROW (not really the 'y_coord' anymore)
painting_top_frame = false;
}
// Find the memory address for the malloc for this framebuffer row.
rowColorDepthStruct *fb_row_malloc_ptr = (rowColorDepthStruct *) matrix_row_framebuffer_malloc[y_coord];
// 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
uint16_t rowBitStruct_x_coord_uint16_t_position = (x_coord % 2) ? (x_coord-1):(x_coord+1);
for(uint8_t color_depth_idx=0; color_depth_idx<PIXEL_COLOR_DEPTH_BITS; color_depth_idx++) // color depth - 8 iterations
{
uint8_t mask = (1 << color_depth_idx); // 24 bit color
// Get the contents at this address, cast as a rowColorDepthStruct
rowBitStruct *p = &fb_row_malloc_ptr[back_buffer_id].rowbits[color_depth_idx]; //matrixUpdateFrames location to write to uint16_t's
// We need to update the correct uint16_t in the rowBitStruct array, that gets sent out in parallel
uint16_t &v = p->data[rowBitStruct_x_coord_uint16_t_position]; // persist what we already have
if (painting_top_frame)
{ // Need to copy what the RGB status is for the bottom pixels
v &= BITMASK_RGB1_CLEAR; // reset R1G1B1 bits
// Set the color of the pixel of interest
if (green & mask) { v|=BIT_G1; }
if (blue & mask) { v|=BIT_B1; }
if (red & mask) { v|=BIT_R1; }
} else { // Do it the other way around
v &= BITMASK_RGB2_CLEAR; // reset R2G2B2 bits
// Color to set
if (red & mask) { v|=BIT_R2; }
if (green & mask) { v|=BIT_G2; }
if (blue & mask) { v|=BIT_B2; }
} // paint
if (fastmode)
continue;
// update address/control bits
v &= BITMASK_CTRL_CLEAR; // reset ABCDE,EO,LAT address bits
uint16_t _y = color_depth_idx ? y_coord : y_coord -1;
v|=_y << BITS_ADDR_OFFSET; // shift row coord to match ABCDE bits from bit positions 8 to 12 and set bitvector
// drive latch while shifting out last bit of RGB data
if((x_coord) == PIXELS_PER_ROW-1) v|=BIT_LAT;
// need to disable OE after latch to hide row transition
// OR one clock before latch, otherwise can get ghosting
if((x_coord) == 0 || (x_coord)==PIXELS_PER_ROW-2){ v|=BIT_OE; continue;}
if((color_depth_idx > lsbMsbTransitionBit || !color_depth_idx) && ((x_coord) >= brightness))
{v|=BIT_OE; continue;}// For Brightness
// special case for the bits *after* LSB through (lsbMsbTransitionBit) - OE is output after data is shifted, so need to set OE to fractional brightness
if(color_depth_idx && color_depth_idx <= lsbMsbTransitionBit) {
// divide brightness in half for each bit below lsbMsbTransitionBit
int lsbBrightness = brightness >> (lsbMsbTransitionBit - color_depth_idx + 1);
if((x_coord) >= lsbBrightness) v|=BIT_OE; // For Brightness
}
/*
// Development / testing code only.
Serial.printf("r value of %d, color depth: %d, mask: %d\r\n", red, color_depth_idx, mask);
if (red & mask) { Serial.println("Success - Binary"); v|=BIT_R1; }
Serial.printf("val2pwm r value: %d\r\n", val2PWM(red));
if (val2PWM(red) & mask) { Serial.println("Success - PWM"); v|=BIT_R2; }
*/
} // color depth loop (8)
} // updateMatrixDMABuffer (specific co-ords change)
/* Update the entire buffer with a single specific colour - quicker */
void MatrixPanel_I2S_DMA::updateMatrixDMABuffer(uint8_t red, uint8_t green, uint8_t blue)
{
if ( !everything_OK ) return;
/* https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/ */
/*
red = val2PWM(red);
green = val2PWM(green);
blue = val2PWM(blue);
*/
red = lumConvTab[red];
green = lumConvTab[green];
blue = lumConvTab[blue];
for(uint8_t color_depth_idx=0; color_depth_idx<PIXEL_COLOR_DEPTH_BITS; color_depth_idx++) // color depth - 8 iterations
{
// let's precalculate RGB1 and RGB2 bits than flood it over the entire DMA buffer
uint16_t RGB_output_bits = 0;
uint8_t mask = (1 << color_depth_idx); // 24 bit color
/* Per the .h file, the order of the output RGB bits is:
* BIT_B2, BIT_G2, BIT_R2, BIT_B1, BIT_G1, BIT_R1 */
RGB_output_bits |= (bool)(blue & mask); // --B
RGB_output_bits <<= 1;
RGB_output_bits |= (bool)(green & mask); // -BG
RGB_output_bits <<= 1;
RGB_output_bits |= (bool)(red & mask); // BGR
// Duplicate and shift across so we have have 6 populated bits of RGB1 and RGB2 pin values suitable for DMA buffer
RGB_output_bits |= RGB_output_bits << BITS_RGB2_OFFSET; //BGRBGR
//Serial.printf("Fill with: 0x%#06x\n", RGB_output_bits);
// iterate rows
for (uint16_t matrix_frame_parallel_row = 0; matrix_frame_parallel_row < ROWS_PER_FRAME; matrix_frame_parallel_row++) // half height - 16 iterations
{
rowColorDepthStruct *fb_row_malloc_ptr = (rowColorDepthStruct *) matrix_row_framebuffer_malloc[matrix_frame_parallel_row];
//Serial.printf("Accessing fb address: %d\r\n", fb_row_malloc_ptr);
// The destination for the pixel bitstream
rowBitStruct *p = &fb_row_malloc_ptr[back_buffer_id].rowbits[color_depth_idx]; //matrixUpdateFrames location to write to uint16_t's
// iterate pixels in a row
if (fastmode){
for(uint16_t x_coord=0; x_coord < MATRIX_WIDTH; x_coord++) {
uint16_t &v = p->data[(x_coord % 2) ? (x_coord-1):(x_coord+1)]; // take reference to bit vector
v &= BITMASK_RGB12_CLEAR; // reset color bits
v |= RGB_output_bits; // set new color bits
}
} else {
// Set ABCDE address bits vector
uint16_t _y = color_depth_idx ? matrix_frame_parallel_row : matrix_frame_parallel_row -1;
_y <<= BITS_ADDR_OFFSET; // shift row y-coord to match ABCDE bits in vector from 8 to 12
for(uint16_t x_coord=0; x_coord < MATRIX_WIDTH; x_coord++) {
// 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
uint16_t rowBitStruct_x_coord_uint16_t_position = (x_coord % 2) ? (x_coord-1):(x_coord+1);
uint16_t &v = p->data[rowBitStruct_x_coord_uint16_t_position]; // persist what we already have
v = RGB_output_bits; // set colot bits and reset all others
v|=_y; // set ABCDE address bits for current row
// drive latch while shifting out last bit of RGB data
if((x_coord) == PIXELS_PER_ROW-1) v|=BIT_LAT;
// need to disable OE after latch to hide row transition
// OR one clock before latch, otherwise can get ghosting
if(!x_coord || (x_coord)==PIXELS_PER_ROW-2){
v|=BIT_OE; continue;
}
// BRT OE
if((color_depth_idx > lsbMsbTransitionBit || !color_depth_idx) && ((x_coord) >= brightness)){
v|=BIT_OE; continue; // For Brightness control
}
// special case for the bits *after* LSB through (lsbMsbTransitionBit) - OE is output after data is shifted, so need to set OE to fractional brightness
if(color_depth_idx && color_depth_idx <= lsbMsbTransitionBit) {
// divide brightness in half for each bit below lsbMsbTransitionBit
int lsbBrightness = brightness >> (lsbMsbTransitionBit - color_depth_idx + 1);
if((x_coord) >= lsbBrightness) v|=BIT_OE; // For Brightness
}
} // end of x-iterator
} // end x_coord iteration
} // end row iteration
} // colour depth loop (8)
} // updateMatrixDMABuffer (full frame paint)
/**
* pre-init procedures for specific drivers
*
*/
void MatrixPanel_I2S_DMA::shiftDriver(const shift_driver _drv, const int dma_r1_pin, const int dma_g1_pin, const int dma_b1_pin, const int dma_r2_pin, const int dma_g2_pin, const int dma_b2_pin, const int dma_a_pin, const int dma_b_pin, const int dma_c_pin, const int dma_d_pin, const int dma_e_pin, const int dma_lat_pin, const int dma_oe_pin, const int dma_clk_pin){
switch (_drv){
case ICN2038S:
case FM6124:
case FM6126A:
{
#if SERIAL_DEBUG
Serial.println( F("MatrixPanel_I2S_DMA - initializing FM6124 driver..."));
#endif
bool REG1[16] = {0,0,0,0,0, 1,1,1,1,1,1, 0,0,0,0,0}; // this sets global matrix brightness power
bool REG2[16] = {0,0,0,0,0, 0,0,0,0,1,0, 0,0,0,0,0}; // a single bit enables the matrix output
for (uint8_t _pin:{dma_r1_pin, dma_r2_pin, dma_g1_pin, dma_g2_pin, dma_b1_pin, dma_b2_pin, dma_clk_pin, dma_lat_pin, dma_oe_pin})
pinMode(_pin, OUTPUT);
digitalWrite(dma_oe_pin, HIGH); // Disable Display
digitalWrite(dma_lat_pin, LOW);
digitalWrite(dma_clk_pin, LOW);
// Send Data to control register REG1
// this sets the matrix brightness actually
for (int l = 0; l < MATRIX_WIDTH; l++){
for (uint8_t _pin:{dma_r1_pin, dma_r2_pin, dma_g1_pin, dma_g2_pin, dma_b1_pin, dma_b2_pin})
digitalWrite(_pin, REG1[l%16]); // we have 16 bits shifters and write the same value all over the matrix array
if (l > MATRIX_WIDTH - 12){ // pull the latch 11 clocks before the end of matrix so that REG1 starts counting to save the value
digitalWrite(dma_lat_pin, HIGH);
}
digitalWrite(dma_clk_pin, HIGH); // 1-clock pulse
digitalWrite(dma_clk_pin, LOW);
}
// drop the latch and save data to the REG1 all over the FM6124 chips
digitalWrite(dma_lat_pin, LOW);
digitalWrite(dma_clk_pin, LOW);
// Send Data to control register REG2 (enable LED output)
for (int l = 0; l < MATRIX_WIDTH; l++){
for (uint8_t _pin:{dma_r1_pin, dma_r2_pin, dma_g1_pin, dma_g2_pin, dma_b1_pin, dma_b2_pin})
digitalWrite(_pin, REG2[l%16]); // we have 16 bits shifters and we write the same value all over the matrix array
if (l > MATRIX_WIDTH - 13){ // pull the latch 12 clocks before the end of matrix so that reg2 stars counting to save the value
digitalWrite(dma_lat_pin, HIGH);
}
digitalWrite(dma_clk_pin, HIGH); // 1-clock pulse
digitalWrite(dma_clk_pin, LOW);
}
// drop the latch and save data to the REG1 all over the FM6126 chips
digitalWrite(dma_lat_pin, LOW);
digitalWrite(dma_clk_pin, LOW);
}
break;
case SHIFT:
default:
break;
}
}
/**
* clear screen to black and reset service bits
*/
void MatrixPanel_I2S_DMA::clearScreen(){
if (fastmode) {
fastmode = false; // we always clear screen in 'slow' mode to update all bits in DMA buffer
updateMatrixDMABuffer(0, 0, 0);
fastmode = true; // restore fastmode
} else {
updateMatrixDMABuffer(0, 0, 0);
}
}

View file

@ -0,0 +1,501 @@
#ifndef _ESP32_RGB_64_32_MATRIX_PANEL_I2S_DMA
#define _ESP32_RGB_64_32_MATRIX_PANEL_I2S_DMA
/***************************************************************************************/
/* COMPILE-TIME OPTIONS - CONFIGURE AS DESIRED */
/***************************************************************************************/
/* Enable serial debugging of the library, to see how memory is allocated etc. */
//#define SERIAL_DEBUG 1
/* Use GFX_Root (https://github.com/mrfaptastic/GFX_Root) instead of
* Adafruit_GFX library. No real benefit unless you don't want Bus_IO & Wire.h library dependencies.
*/
//#define USE_GFX_ROOT 1
/* Physical / Chained HUB75(s) 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 an E_PIN; it will work!
*
* All of this is memory permitting of course (dependant on your sketch etc.) ...
*
*/
#ifndef MATRIX_WIDTH
#define MATRIX_WIDTH 64 // Single panel of 64 pixel width
// #define MATRIX_WIDTH 64*4 // Example of chaining four (4) panels. Need to multiply the width.
#endif
#ifndef MATRIX_HEIGHT
#define MATRIX_HEIGHT 32 // CHANGE THIS VALUE to 64 IF USING 64px HIGH panel(s) with E PIN
#endif
/* Best to keep these values as is. */
#ifndef PIXEL_COLOR_DEPTH_BITS
#define PIXEL_COLOR_DEPTH_BITS 8 // 8bit per RGB color = 24 bit/per pixel, reduce to save RAM
#endif
#ifndef MATRIX_ROWS_IN_PARALLEL
#define MATRIX_ROWS_IN_PARALLEL 2 // Don't change this unless you know what you're doing
#endif
/* ESP32 Default Pin definition. You can change this, but best if you keep it as is and provide custom pin mappings
* as part of the begin(...) function.
*/
#define R1_PIN_DEFAULT 25
#define G1_PIN_DEFAULT 26
#define B1_PIN_DEFAULT 27
#define R2_PIN_DEFAULT 14
#define G2_PIN_DEFAULT 12
#define B2_PIN_DEFAULT 13
#define A_PIN_DEFAULT 23
#define B_PIN_DEFAULT 19
#define C_PIN_DEFAULT 5
#define D_PIN_DEFAULT 17
#define E_PIN_DEFAULT -1 // IMPORTANT: Change to a valid pin if using a 64x64px panel.
#define LAT_PIN_DEFAULT 4
#define OE_PIN_DEFAULT 15
#define CLK_PIN_DEFAULT 16
// 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.
/***************************************************************************************/
/* Do not change. */
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "esp_heap_caps.h"
#include "esp32_i2s_parallel.h"
#ifdef USE_GFX_ROOT
#include "GFX.h" // Adafruit GFX core class -> https://github.com/mrfaptastic/GFX_Root
#else
#include "Adafruit_GFX.h" // Adafruit class with all the other stuff
#endif
/***************************************************************************************/
/* Do not change. */
// Panel Upper half RGB (numbering according to order in DMA gpio_bus configuration)
#define BITS_RGB1_OFFSET 0 // Start point of RGB_X1 bits
#define BIT_R1 (1<<0)
#define BIT_G1 (1<<1)
#define BIT_B1 (1<<2)
// Panel Lower half RGB
#define BITS_RGB2_OFFSET 3 // Start point of RGB_X2 bits
#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..)
#define BITS_ADDR_OFFSET 8 // Start point of address bits
#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)
#define BITMASK_RGB1_CLEAR (0xfff8) // inverted bitmask for R1G1B1 bit in pixel vector
#define BITMASK_RGB2_CLEAR (0xffc7) // inverted bitmask for R2G2B2 bit in pixel vector
#define BITMASK_RGB12_CLEAR (0xffc0) // inverted bitmask for R1G1B1R2G2B2 bit in pixel vector
#define BITMASK_CTRL_CLEAR (0xe03f) // inverted bitmask for control bits ABCDE,LAT,OE in pixel vector
// RGB Panel Constants / Calculated Values
#define COLOR_CHANNELS_PER_PIXEL 3
#define PIXELS_PER_ROW MATRIX_WIDTH // number of all pixels in a row of chained modules
//#define PIXEL_COLOR_DEPTH_BITS (MATRIX_COLOR_DEPTH/COLOR_CHANNELS_PER_PIXEL) // = 8
#define ROWS_PER_FRAME (MATRIX_HEIGHT/MATRIX_ROWS_IN_PARALLEL) // = 16
/***************************************************************************************/
/* Keep this as is. Do not change. */
#define ESP32_I2S_DMA_MODE I2S_PARALLEL_BITS_16 // Pump 16 bits out in parallel
#define ESP32_I2S_DMA_STORAGE_TYPE uint16_t // one uint16_t at a time.
#ifndef ESP32_I2S_CLOCK_SPEED
#define ESP32_I2S_CLOCK_SPEED (10000000UL) // @ 10Mhz
//#define ESP32_I2S_CLOCK_SPEED (20000000UL) // @ 20Mhz
#endif
#define CLKS_DURING_LATCH 0 // Not used.
/***************************************************************************************/
/* rowBitStruct
* Note: sizeof(data) must be multiple of 32 bits, as ESP32 DMA linked list buffer address pointer
* must be word-aligned.
*/
struct rowBitStruct {
ESP32_I2S_DMA_STORAGE_TYPE data[PIXELS_PER_ROW + CLKS_DURING_LATCH];
// This evaluates to just data[64] really.. an array of 64 uint16_t's
};
/* rowColorDepthStruct
* Duplicates of row bit structure, but for each color 'depth'ness.
*/
struct rowColorDepthStruct {
rowBitStruct rowbits[PIXEL_COLOR_DEPTH_BITS];
};
/* frameStruct
* Note: A 'frameStruct' contains ALL the data for a full-frame (i.e. BOTH 2x16-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
* memory per row (per rowColorDepthStruct) instead.
*/
struct frameStruct {
rowColorDepthStruct rowdata[ROWS_PER_FRAME];
};
typedef struct RGB24 {
RGB24() : RGB24(0,0,0) {}
RGB24(uint8_t r, uint8_t g, uint8_t b) {
red = r; green = g; blue = b;
}
RGB24& operator=(const RGB24& col);
uint8_t red;
uint8_t green;
uint8_t blue;
} RGB24;
/**
* Enumeration of hardware-specific chips
* used to drive matrix modules
*/
enum shift_driver {SHIFT=0, FM6124, FM6126A, ICN2038S};
/***************************************************************************************/
// Used by val2PWM
//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
const uint8_t lumConvTab[]={
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 16, 16, 17, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 27, 27, 28, 28, 29, 30, 30, 31, 31, 32, 33, 33, 34, 35, 35, 36, 37, 38, 38, 39, 40, 41, 41, 42, 43, 44, 45, 45, 46, 47, 48, 49, 50, 51, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 80, 81, 82, 83, 84, 86, 87, 88, 90, 91, 92, 93, 95, 96, 98, 99, 100, 102, 103, 105, 106, 107, 109, 110, 112, 113, 115, 116, 118, 120, 121, 123, 124, 126, 128, 129, 131, 133, 134, 136, 138, 139, 141, 143, 145, 146, 148, 150, 152, 154, 156, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 192, 194, 196, 198, 200, 203, 205, 207, 209, 212, 214, 216, 218, 221, 223, 226, 228, 230, 233, 235, 238, 240, 243, 245, 248, 250, 253, 255, 255};
/***************************************************************************************/
#ifdef USE_GFX_ROOT
class MatrixPanel_I2S_DMA : public GFX {
#else
class MatrixPanel_I2S_DMA : public Adafruit_GFX {
#endif
// ------- PUBLIC -------
public:
/**
* MatrixPanel_I2S_DMA
*
* @param {bool} _double_buffer : Double buffer is disabled by default. Enable only if you know what you're doing. Manual switching required with flipDMABuffer() and showDMABuffer()
*
*/
MatrixPanel_I2S_DMA(bool _double_buffer = false)
#ifdef USE_GFX_ROOT
: GFX(MATRIX_WIDTH, MATRIX_HEIGHT), double_buffering_enabled(_double_buffer) {
#else
: Adafruit_GFX(MATRIX_WIDTH, MATRIX_HEIGHT), double_buffering_enabled(_double_buffer) {
#endif
}
/* Propagate the DMA pin configuration, or use compiler defaults */
bool 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, const shift_driver _drv=SHIFT)
{
// Change 'if' to '1' to enable, 0 to not include this Serial output in compiled program
#if SERIAL_DEBUG
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 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 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 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 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 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 OE_PIN\n", dma_oe_pin);
Serial.printf("Using pin %d for the CLK_PIN\n", dma_clk_pin);
#endif
// initialize some sppecific panel drivers
if (_drv)
shiftDriver(_drv, 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);
/* As DMA buffers are dynamically allocated, we must allocated in begin()
* Ref: https://github.com/espressif/arduino-esp32/issues/831
*/
if ( !allocateDMAmemory() ) { return false; } // couldn't even get the basic ram required.
// Flush the DMA buffers prior to configuring DMA - Avoid visual artefacts on boot.
clearScreen(); // Must fill the DMA buffer with the initial output bit sequence or the panel will display garbage
if (double_buffering_enabled){
flipDMABuffer(); // flip to backbuffer 1
clearScreen(); // Must fill the DMA buffer with the initial output bit sequence or the panel will display garbage
flipDMABuffer(); // backbuffer 0
}
// Setup the ESP32 DMA Engine. Sprite_TM built this stuff.
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
showDMABuffer(); // show backbuf_id of 0
#if SERIAL_DEBUG
if (!everything_OK)
Serial.println("MatrixPanel_I2S_DMA::begin() failed.");
#endif
return everything_OK;
}
// TODO: Disable/Enable auto buffer flipping (useful for lots of drawPixel usage)...
// Draw pixels
virtual void drawPixel(int16_t x, int16_t y, uint16_t color); // overwrite adafruit implementation
virtual void fillScreen(uint16_t color); // overwrite adafruit implementation
void clearScreen();
void fillScreenRGB888(uint8_t r, uint8_t g, uint8_t b);
void drawPixelRGB565(int16_t x, int16_t y, uint16_t color);
void drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b);
void drawPixelRGB24(int16_t x, int16_t y, RGB24 color);
void drawIcon (int *ico, int16_t x, int16_t y, int16_t cols, int16_t rows);
// Color 444 is a 4 bit scale, so 0 to 15, color 565 takes a 0-255 bit value, so scale up by 255/15 (i.e. 17)!
uint16_t color444(uint8_t r, uint8_t g, uint8_t b) { return color565(r*17,g*17,b*17); }
// Converts RGB888 to RGB565
uint16_t color565(uint8_t r, uint8_t g, uint8_t b); // This is what is used by Adafruit GFX!
// Converts RGB333 to RGB565
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.
inline void flipDMABuffer()
{
if ( !double_buffering_enabled) return;
// Flip to other buffer as the backbuffer. i.e. Graphic changes happen to this buffer (but aren't displayed until showDMABuffer())
back_buffer_id ^= 1;
#if SERIAL_DEBUG
Serial.printf("Set back buffer to: %d\n", back_buffer_id);
#endif
// Wait before we allow any writing to the buffer. Stop flicker.
while(!i2s_parallel_is_previous_buffer_free()) {}
}
inline void showDMABuffer()
{
if (!double_buffering_enabled) return;
#if SERIAL_DEBUG
Serial.printf("Showtime for buffer: %d\n", back_buffer_id);
#endif
i2s_parallel_flip_to_buffer(&I2S1, back_buffer_id);
// Wait before we allow any writing to the buffer. Stop flicker.
while(!i2s_parallel_is_previous_buffer_free()) {}
}
inline void setPanelBrightness(int b)
{
// Change to set the brightness of the display, range of 1 to matrixWidth (i.e. 1 - 64)
brightness = b;
if (fastmode) // in 'fast' mode we should always reset DMA buffer to update OE bits that controls brightness
clearScreen(); // and YES, it WILL flicker. you've been warned :)
}
/**
* 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)
{
setPanelBrightness(b * MATRIX_WIDTH / 256);
}
inline void setMinRefreshRate(int rr)
{
min_refresh_rate = rr;
}
/**
* Controls fast-update mode, when only RGB bits are upated in DMA buffer
* @param mode bool - set/clear fastmode. Will take effect on newly updated pixels only
* @return fastmode status
*/
bool setFastMode(const bool mode){
fastmode = mode;
return fastmode;
};
/**
* Controls fast-update mode, when only RGB bits are upated in DMA buffer
* @param void - returns current fastmode status
*/
bool setFastMode(){return fastmode;};
int calculated_refresh_rate = 0;
// ------- PRIVATE -------
private:
/* 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.
*/
rowColorDepthStruct *matrix_row_framebuffer_malloc[ROWS_PER_FRAME];
// ESP 32 DMA Linked List descriptor
int desccount = 0;
lldesc_t * dmadesc_a = {0};
lldesc_t * dmadesc_b = {0};
// ESP32-HUB75-MatrixPanel-I2S-DMA functioning
bool everything_OK = false;
bool double_buffering_enabled = false;// Do we use double buffer mode? Your project code will have to manually flip between both.
int back_buffer_id = 0; // If using double buffer, which one is NOT active (ie. being displayed) to write too?
int brightness = 32; // If you get ghosting... reduce brightness level. 60 seems to be the limit before ghosting on a 64 pixel wide physical panel for some panels.
int min_refresh_rate = 99; // Probably best to leave as is unless you want to experiment. Framerate has an impact on brightness and also power draw - voltage ripple.
int lsbMsbTransitionBit = 0; // For possible color depth calculations
/**
* this var controls how DMA buffers are updated
* if set to false (default) - full recalculation performed, including address line bits, OE, LAT
* if set to true, only RGB1, RGB2 bits are updated.
* Could be toggled any time, see Notes regarding #define GO_FOR_SPEED 1 above
*/
#ifdef GO_FOR_SPEED
bool fastmode = true;
#else
bool fastmode = false;
#endif
/* 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(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 to a colour */
void updateMatrixDMABuffer(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t blue);
/* 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);
/**
* pre-init procedures for specific drivers
*
*/
void shiftDriver(const shift_driver _drv, const int dma_r1_pin, const int dma_g1_pin, const int dma_b1_pin, const int dma_r2_pin, const int dma_g2_pin, const int dma_b2_pin, const int dma_a_pin, const int dma_b_pin, const int dma_c_pin, const int dma_d_pin, const int dma_e_pin, const int dma_lat_pin, const int dma_oe_pin, const int dma_clk_pin);
}; // 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 MatrixPanel_I2S_DMA::drawPixel(int16_t x, int16_t y, uint16_t color) // adafruit virtual void override
{
drawPixelRGB565( x, y, color);
}
inline void MatrixPanel_I2S_DMA::fillScreen(uint16_t color) // adafruit virtual void override
{
uint8_t r = ((((color >> 11) & 0x1F) * 527) + 23) >> 6;
uint8_t g = ((((color >> 5) & 0x3F) * 259) + 33) >> 6;
uint8_t b = (((color & 0x1F) * 527) + 23) >> 6;
updateMatrixDMABuffer(r, g, b); // the RGB only (no pixel coordinate) version of 'updateMatrixDMABuffer'
}
inline void MatrixPanel_I2S_DMA::fillScreenRGB888(uint8_t r, uint8_t g,uint8_t b) // adafruit virtual void override
{
updateMatrixDMABuffer(r, g, b);
}
// For adafruit
inline void MatrixPanel_I2S_DMA::drawPixelRGB565(int16_t x, int16_t y, uint16_t color)
{
uint8_t r = ((((color >> 11) & 0x1F) * 527) + 23) >> 6;
uint8_t g = ((((color >> 5) & 0x3F) * 259) + 33) >> 6;
uint8_t b = (((color & 0x1F) * 527) + 23) >> 6;
updateMatrixDMABuffer( x, y, r, g, b);
}
inline void MatrixPanel_I2S_DMA::drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g,uint8_t b)
{
updateMatrixDMABuffer( x, y, r, g, b);
}
inline void MatrixPanel_I2S_DMA::drawPixelRGB24(int16_t x, int16_t y, RGB24 color)
{
updateMatrixDMABuffer( x, y, color.red, color.green, color.blue);
}
// Pass 8-bit (each) R,G,B, get back 16-bit packed color
//https://github.com/squix78/ILI9341Buffer/blob/master/ILI9341_SPI.cpp
inline uint16_t MatrixPanel_I2S_DMA::color565(uint8_t r, uint8_t g, uint8_t b) {
/*
Serial.printf("Got r value of %d\n", r);
Serial.printf("Got g value of %d\n", g);
Serial.printf("Got b value of %d\n", b);
*/
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}
// Promote 3/3/3 RGB to Adafruit_GFX 5/6/5 RRRrrGGGgggBBBbb
inline uint16_t MatrixPanel_I2S_DMA::color333(uint8_t r, uint8_t g, uint8_t b) {
return ((r & 0x7) << 13) | ((r & 0x6) << 10) | ((g & 0x7) << 8) | ((g & 0x7) << 5) | ((b & 0x7) << 2) | ((b & 0x6) >> 1);
}
inline void MatrixPanel_I2S_DMA::drawIcon (int *ico, int16_t x, int16_t y, int16_t cols, int16_t rows) {
/* drawIcon draws a C style bitmap.
// Example 10x5px bitmap of a yellow sun
//
int half_sun [50] = {
0x0000, 0x0000, 0x0000, 0xffe0, 0x0000, 0x0000, 0xffe0, 0x0000, 0x0000, 0x0000,
0x0000, 0xffe0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffe0, 0x0000,
0x0000, 0x0000, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0x0000, 0x0000, 0x0000,
0xffe0, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0x0000, 0xffe0,
0x0000, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0x0000, 0x0000,
};
MatrixPanel_I2S_DMA matrix;
matrix.drawIcon (half_sun, 0,0,10,5);
*/
int i, j;
for (i = 0; i < rows; i++) {
for (j = 0; j < cols; j++) {
drawPixelRGB565 (x + j, y + i, ico[i * cols + j]);
}
}
}
#endif

View file

@ -0,0 +1,4 @@
/* For backwards compatibility only */
#pragma message "Please update your include to use ESP32-HUB75-MatrixPanel-I2S-DMA.h instead!"
#include "ESP32-HUB75-MatrixPanel-I2S-DMA.h"
typedef MatrixPanel_I2S_DMA RGB64x32MatrixPanel_I2S_DMA; // for backwards compatibility to old class name

View file

@ -0,0 +1,199 @@
#ifndef _ESP32_VIRTUAL_MATRIX_PANEL_I2S_DMA
#define _ESP32_VIRTUAL_MATRIX_PANEL_I2S_DMA
/*******************************************************************
Class contributed by Brian Lough, and expanded by Faptastic.
YouTube: https://www.youtube.com/brianlough
Tindie: https://www.tindie.com/stores/brianlough/
Twitter: https://twitter.com/witnessmenow
*******************************************************************/
#include "ESP32-HUB75-MatrixPanel-I2S-DMA.h"
#include <Fonts/FreeSansBold12pt7b.h>
struct VirtualCoords {
int16_t x;
int16_t y;
};
#ifdef USE_GFX_ROOT
class VirtualMatrixPanel : public GFX
#else
class VirtualMatrixPanel : public Adafruit_GFX
#endif
{
public:
int16_t virtualResX;
int16_t virtualResY;
int16_t module_rows;
int16_t module_cols;
int16_t panelResX;
int16_t panelResY;
MatrixPanel_I2S_DMA *display;
#ifdef USE_GFX_ROOT
VirtualMatrixPanel(MatrixPanel_I2S_DMA &disp, int vmodule_rows, int vmodule_cols, int panelX, int panelY, bool serpentine_chain = true, bool top_down_chain = false)
: GFX(vmodule_cols*panelX, vmodule_rows*panelY)
#else
VirtualMatrixPanel(MatrixPanel_I2S_DMA &disp, int vmodule_rows, int vmodule_cols, int panelX, int panelY, bool serpentine_chain = true, bool top_down_chain = false )
: Adafruit_GFX(vmodule_cols*panelX, vmodule_rows*panelY)
#endif
{
this->display = &disp;
panelResX = panelX;
panelResY = panelY;
module_rows = vmodule_rows;
module_cols = vmodule_cols;
virtualResY = vmodule_rows*panelY;
virtualResX = vmodule_cols*panelX;
_s_chain_party = serpentine_chain; // serpentine, or 'S' chain?
_chain_top_down= top_down_chain;
}
VirtualCoords getCoords(int16_t x, int16_t y);
// equivalent methods of the matrix library so it can be just swapped out.
virtual void drawPixel(int16_t x, int16_t y, uint16_t color);
virtual void fillScreen(uint16_t color); // overwrite adafruit implementation
void clearScreen() {
fillScreen(0);
}
void drawPixelRGB565(int16_t x, int16_t y, uint16_t color);
void drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b);
void drawPixelRGB24(int16_t x, int16_t y, RGB24 color);
void drawIcon (int *ico, int16_t x, int16_t y, int16_t module_cols, int16_t module_rows);
uint16_t color444(uint8_t r, uint8_t g, uint8_t b) {
return display->color444(r, g, b);
}
uint16_t color565(uint8_t r, uint8_t g, uint8_t b) {
return display->color565(r, g, b);
}
uint16_t color333(uint8_t r, uint8_t g, uint8_t b) {
return display->color333(r, g, b);
}
void flipDMABuffer() { display->flipDMABuffer(); }
void showDMABuffer() { display->showDMABuffer(); }
void drawDisplayTest();
private:
VirtualCoords coords;
bool _s_chain_party = true; // Are we chained? Ain't no party like a...
bool _chain_top_down = false; // is the ESP at the top or bottom of the matrix of devices?
}; // end Class header
inline VirtualCoords VirtualMatrixPanel::getCoords(int16_t x, int16_t y) {
coords.x = coords.y = 0;
if (x < 0 || x > module_cols*panelResX || y < 0 || y > module_rows*panelResY ) {
//Serial.printf("VirtualMatrixPanel::getCoords(): Invalid virtual display coordinate. x,y: %d, %d\r\n", x, y);
return coords;
}
uint8_t row = (y / panelResY) + 1; //a non indexed 0 row number
if( ( _s_chain_party && !_chain_top_down && (row % 2 == 0) ) // serpentine vertically stacked chain starting from bottom row (i.e. ESP closest to ground), upwards
||
( _s_chain_party && _chain_top_down && (row % 2 != 0) ) // serpentine vertically stacked chain starting from the sky downwards
)
{
// First portion gets you to the correct offset for the row you need
// Second portion inverts the x on the row
coords.x = (y / panelResY) * (module_cols * panelResX) + (virtualResX - 1 - x);
// inverts the y the row
coords.y = panelResY - 1 - (y % panelResY);
}
else
{
coords.x = x + (y / panelResY) * (module_cols * panelResX) ;
coords.y = y % panelResY;
}
// Reverse co-ordinates if panel chain from ESP starts from the TOP RIGHT
if (_chain_top_down)
{
coords.x = (this->display->width()-1) - coords.x;
coords.y = (this->display->height()-1) - coords.y;
}
//Serial.print("Mapping to x: "); Serial.print(coords.x, DEC); Serial.print(", y: "); Serial.println(coords.y, DEC);
return coords;
}
inline void VirtualMatrixPanel::drawPixel(int16_t x, int16_t y, uint16_t color)
{
VirtualCoords coords = getCoords(x, y);
this->display->drawPixel(coords.x, coords.y, color);
}
inline void VirtualMatrixPanel::fillScreen(uint16_t color) // adafruit virtual void override
{
// No need to map this.
this->display->fillScreen(color);
}
inline void VirtualMatrixPanel::drawPixelRGB565(int16_t x, int16_t y, uint16_t color)
{
VirtualCoords coords = getCoords(x, y);
this->display->drawPixelRGB565( coords.x, coords.y, color);
}
inline void VirtualMatrixPanel::drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b)
{
VirtualCoords coords = getCoords(x, y);
this->display->drawPixelRGB888( coords.x, coords.y, r, g, b);
}
inline void VirtualMatrixPanel::drawPixelRGB24(int16_t x, int16_t y, RGB24 color)
{
VirtualCoords coords = getCoords(x, y);
this->display->drawPixelRGB24(coords.x, coords.y, color);
}
inline void VirtualMatrixPanel::drawDisplayTest()
{
this->display->setFont(&FreeSansBold12pt7b);
this->display->setTextColor(this->display->color565(255, 255, 0));
this->display->setTextSize(1);
for ( int panel = 0; panel < module_cols*module_rows; panel++ ) {
int top_left_x = (panel == 0) ? 0:(panel*panelResX);
this->display->drawRect( top_left_x, 0, panelResX, panelResY, this->display->color565( 0, 255, 0));
this->display->setCursor(panel*panelResX, panelResY-3);
this->display->print((module_cols*module_rows)-panel);
}
}
// need to recreate this one, as it wouldnt work to just map where it starts.
inline void VirtualMatrixPanel::drawIcon (int *ico, int16_t x, int16_t y, int16_t module_cols, int16_t module_rows) {
int i, j;
for (i = 0; i < module_rows; i++) {
for (j = 0; j < module_cols; j++) {
// This is a call to this libraries version of drawPixel
// which will map each pixel, which is what we want.
drawPixelRGB565 (x + j, y + i, ico[i * module_cols + j]);
}
}
}
#endif

View file

@ -8,20 +8,20 @@ An excellent insight could be found here https://github.com/hzeller/rpi-rgb-led-
So there are two regs in this chip - **REG1** and **REG2**, So there are two regs in this chip - **REG1** and **REG2**,
one could be written with 12 clock pulses (and usually called reg12, dunno why :)) one could be written with 12 clock pusles (and usually called reg12, dunno why :))
the other one could be written with 13 clock pulses (and usually called reg13, dunno why :)) the other one could be written with 13 clock pulses (and usually called reg13, dunno why :))
I've done some measurements on power consumption while toggling bits of **REG1** and it looks that it could provide a fine grained brightness control over the entire matrix with no need for bitbanging over RGB or EO pins. I've done some measurmens on power consumption while toggling bits of **REG1** and it looks that it could provide a fine grained brighness control over the entire matrix with no need for bitbanging over RGB or EO pins.
There are 6 bits (6 to 11) giving an increased brightness (compared to all-zeroes) and 4 bits (2-5) giving decreased brightness!!! There are 6 bits (6 to 11) giving an increased brighness (compared to all-zeroes) and 4 bits (2-5) giving decreased brighness!!!
Still unclear if FM6112A brightness control is internally PWMed or current limited, might require some poking with oscilloscope. Still unclear if FM6112A brightness control is internally PWMed or current limited, might require some poking with oscilloscope.
So it seems that the most bright (and hungry for power) value is bool REG1[16] = {0,0,0,0,0, 1,1,1,1,1,1, 0,0,0,0,0}; and not {0,1,1,1,1, 1,1,1,1,1,1, 1,1,1,1,1} as it is usually used. So it seems that the most bright (and hungry for power) value is bool REG1[16] = {0,0,0,0,0, 1,1,1,1,1,1, 0,0,0,0,0}; and not {0,1,1,1,1, 1,1,1,1,1,1, 1,1,1,1,1} as it is usually used.
I'm not sure about bit 1 - it is either not used or I was unable to measure it's influence to brightness/power. I'm not sure about bit 1 - it is either not used or I was unable to measure it's influence to brightness/power.
Giving at least 10 bits of hardware brightness control opens pretty nice options for offloading and simplifying matrix output. Should dig into this more deeper. Giving at least 10 bits of hardware brightness control opens pretty nice options for offloading and simplifiyng matrix output. Should dig into this more deeper.
Here are some of the measurements I've took for 2 64x64 panels filled with white color - reg value and corresponding current drain in amps. Here are some of the measurments I've took for 2 64x64 panels filled with white color - reg value and corresponding current drain in amps.
|REG1 |bit value|Current, amps | |REG1 |bit value|Current, amps |

View file

@ -1,9 +0,0 @@
menu "ESP32 HUB75 Configuration"
config ESP32_HUB75_USE_GFX
bool "Use Adafruit GFX library."
default y
help
This option enables use of the Adafruit GFX library using the `Adafruit-GFX-Library` component.
endmenu

View file

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

296
README.md
View file

@ -1,110 +1,47 @@
# HUB75 RGB LED matrix panel library utilizing ESP32 DMA # HUB75 LED matrix library for the ESP32, utilising DMA
__[BUILD OPTIONS](/doc/BuildOptions.md) | [EXAMPLES](/examples/README.md)__ | [![PlatformIO CI](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/actions/workflows/pio_arduino_build.yml/badge.svg)](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/actions/workflows/pio_build.yml) ## First time user? Please take the time to read the below
**Table of Content** This ESP32 Arduino library for HUB75 / HUB75E connector type 64x32 RGB LED 1/16 Scan OR 64x64 RGB LED 1/32 Scan LED Matrix Panel, utilises the DMA functionality provided by the ESP32's I2S 'LCD Mode' which basically means that pixel data is sent straight from memory, via the DMA controller, to the relevant LED Matrix GPIO pins with little CPU overhead.
- [Introduction](#introduction) As a result, this library can theoretically provide ~16-24 bit colour, at various brightness levels without noticeable flicker.
* [Features](#features)
* [ESP32 variants supported](#esp32-variants-supported)
* [Required memory](#required-memory)
* [Supported Panels](#supported-panels)
* [Panel driver chips known to be working well](#driver-chips-known-to-be-working-well)
* [Unsupported Panels](#unsupported-panels)
- [Getting Started](#getting-started)
* [1. Library Installation](#1-library-installation)
* [2. Wiring the ESP32 to an LED Matrix Panel](#2-wiring-the-esp32-to-an-led-matrix-panel)
* [3. Run a Test Sketch](#3-run-a-test-sketch)
- [Further Information](#further-information)
* [Can I chain panels?](#can-i-chain-panels)
* [Can I use with a larger panel (i.e. 64x64px square panel)?](#can-i-use-with-a-larger-panel-ie-64x64px-square-panel)
* [Adjusting Panel Brightness](#adjusting-panel-brightness)
* [Build-time options](#build-time-options)
* [Latch blanking](#latch-blanking)
* [Power, Power and Power!](#power-power-and-power)
* [Inspiration](#inspiration)
* [Cool uses of this library](#cool-uses-of-this-library)
- [Thank you!](#thank-you)
# Introduction ## Panels Supported
* This is an ESP32 Arduino/IDF library for HUB75 / HUB75E connection based RGB LED panels. * 64x32 pixel 1/16 Scan LED Matrix 'Indoor' Panel, such as this [typical RGB panel available for purchase](https://www.aliexpress.com/item/256-128mm-64-32-pixels-1-16-Scan-Indoor-3in1-SMD2121-RGB-full-color-P4-led/32810362851.html).
* This library 'out of the box' (mostly) supports HUB75 panels where simple TWO rows/lines are updated in parallel... referred to as 'two scan' panels within this documentation. * 64x64 pixel 1/32 Scan LED Matrix 'Indoor' Panel (experimental).
* 'Four scan' panels are also supported - but please refer to the Four Scan Panel example sketch. * [FM6126](FM6126A.md) / ICN2038S panels based on [this example](/examples/FM6126Panel) will also work with the correct initialisation.
* The library uses the DMA functionality provided by the ESP32's 'LCD Mode' for fast data output.
## Features ## Panels Not Supported
- **Low CPU overhead** - Pixel data is sent directly with the use of hardware-backed DMA, no CPU involvement * 1/4, 1/8 Scan LED Matrix Panels are not supported, please use an alternative library if you bought one of these.
- **Fast** - Updating pixel data involves only bit-wise logic over DMA buffer memory, no pins manipulation or blocking IO * Panels with a resolution of less than 64x32pixels and/or scan rate != 1/32 or 1/16
- **Full screen BCM** - Library utilizes [binary-code modulation](http://www.batsocks.co.uk/readme/art_bcm_5.htm) to render pixel color depth / brightness over the entire matrix to give reasonable colour depth
- **Variable color depth** - Up to TrueColor 24 bits output is possible depending on matrix size/refresh rate required
- **CIE 1931** luminance [correction](https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/) (aka natural LED dimming) implemented
- **Adafruit GFX API** - Library can be built with AdafruitGFX, simplified GFX or without a GFX API at all
## ESP32 variants supported # Installation
* Original ESP32 - That being the ESP-WROOM-32 module with ESP32D0WDQ6 chip from ~2017.
* ESP32-S2; and
* ESP32-S3
RISC-V ESP32's (like the C3) are not supported as they do not have the hardware 'LCD mode' support.
## Required memory
"*What's the price for those features?*" - It's [memory](/doc/memcalc.md), you pay it all by precious MCU's internal memory (SRAM) for the DMA buffer.
Please use the ['Memory Calculator'](/doc/memcalc.md) to see what is *typically* achievable with the typical ESP32. This is only a guide. ![Memory Calculator](doc/memcalc.jpg)
For the ESP32-S3 only, you can use SPIRAM/PSRAM to drive the HUB75 DMA buffer when using an ESP32-S3 with **OCTAL SPI-RAM (PSTRAM)** (i.e. ESP32 S3 N8R8 variant). However, due to bandwidth limitations, the maximum output frequency is limited to approx. 13Mhz, which will limit the real-world number of panels that can be chained without flicker. Please do not use PSRAM as the DMA buffer if using QUAD SPI (Q-SPI), as it's too slow.
To enable PSRAM support on the ESP32-S3, refer to [the build options](/doc/BuildOptions.md) to enable.
For all other ESP32 variants (like the most popular original ESP32), [only *internal* SRAM can be used](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/55), so you will be limited to the ~200KB or so of 'free' SRAM (because of the memory used for your sketch amongst other things) regardless of how many megabytes of SPIRAM/PSRAM you may have connected.
## Supported panel can types
It is impossible to provide a comprehensive list of what panels are supported (or not supported) as new variations of the chips used to 'drive' these panels are created almost weekly (usually from China). You should contact the seller to confirm the chips used in a panel before purchasing to use with this library.
* 'Two scan' panels where **two** rows/lines are updated in parallel.
* 64x32 (width x height) 'Indoor' panels, which are often referred to as 1/16 'scan panel' as every 16th row is updated in parallel (hence why I refer to it as 'two scan')
* 64x64 pixel 1/32 Scan LED Matrix 'Indoor' Panel
* 'Four scan' panels where **four** rows/lines are updated in parallel.
* 32x16 pixel 1/4 Scan LED Matrix 'Indoor' Panel using an ingenious workaround as demonstrated in the Four_Scan_Panel example.
* 126x64 [SM5266P](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/164)
Ones interested in internals of such matrices could find [this article](https://www.sparkfun.com/news/2650) useful.
![Panel Scan Types](doc/ScanRateGraphic.jpg)
## Specific chips found to work
* ICND2012
* [RUC7258](http://www.ruichips.com/en/products.html?cateid=17496)
* FM6126A AKA ICN2038S, [FM6124](https://datasheet4u.com/datasheet-pdf/FINEMADELECTRONICS/FM6124/pdf.php?id=1309677) (Refer to [PatternPlasma](/examples/2_PatternPlasma) example on how to use.)
* SM5266P
* DP3246 with SM5368 row addressing registers
## Specific chips found NOT TO work
* ANY panel that uses S-PWM or PWM based chips (such as the RUL6024, MBI6024).
* [SM1620B](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/issues/416)
* RUL5358 / SHIFTREG_ABC_BIN_DE based panels are not supported.
* ICN2053 / FM6353 based panels - Refer to [this library](https://github.com/LAutour/ESP32-HUB75-MatrixPanel-DMA-ICN2053), which is a fork of this library ( [discussion link](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/discussions/324)).
* Any other panel not listed above.
Please use an [alternative library](https://github.com/2dom/PxMatrix) if you bought one of these.
# Getting Started
## 1. Library Installation
* Dependency: You will need to install Adafruit_GFX from the "Library > Manage Libraries" menu. * Dependency: You will need to install Adafruit_GFX from the "Library > Manage Libraries" menu.
* Install this library from the Arduino Library manager. * Download and unzip this repository into your Arduino/libraries folder (or better still, use the Arduino 'add library from .zip' option.
* Library also tested to work fine with PlatformIO, install into your PlatformIO projects' lib/ folder as appropriate.
Library also tested to work fine with PlatformIO, install into your PlatformIO projects' lib/ folder as appropriate. Or just add it into [platformio.ini](/doc/BuildOptions.md) [lib_deps](https://docs.platformio.org/en/latest/projectconf/section_env_library.html#lib-deps) section. # Wiring ESP32 with the LED Matrix Panel
## 2. Wiring the ESP32 to an LED Matrix Panel By default the pin mapping is as follows (defaults defined in ESP32-HUB75-MatrixPanel-I2S-DMA.h).
Refer to the '*default-pins.hpp' file within the [applicable platforms folder](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/tree/master/src/platforms).
``` ```
If you want to change the GPIO mapping at runtime, simply provide the wanted pin mapping as part of the class initialization structure. For example, in your sketch have something like the following: HUB 75 PANEL ESP 32 PIN
+-----------+
| R1 G1 | R1 -> IO25 G1 -> IO26
| B1 GND | B1 -> IO27
| R2 G2 | R2 -> IO14 G2 -> IO12
| B2 E | B2 -> IO13 E -> N/A (required for 1/32 scan panels, like 64x64. Any available pin would do, i.e. IO32 )
| A B | A -> IO23 B -> IO19
| C D | C -> IO 5 D -> IO17
| CLK LAT | CLK -> IO16 LAT -> IO 4
| OE GND | OE -> IO15 GND -> ESP32 GND
+-----------+
```
However, if you want to change this, simply provide the wanted pin mapping as part of the display.begin() call. For example, in your sketch have something like the following:
```
// Change these to whatever suits // Change these to whatever suits
#define R1_PIN 25 #define R1_PIN 25
#define G1_PIN 26 #define G1_PIN 26
@ -112,166 +49,109 @@ If you want to change the GPIO mapping at runtime, simply provide the wanted pin
#define R2_PIN 14 #define R2_PIN 14
#define G2_PIN 12 #define G2_PIN 12
#define B2_PIN 13 #define B2_PIN 13
#define A_PIN 23 #define A_PIN 23
#define B_PIN 19 #define B_PIN 22
#define C_PIN 5 #define C_PIN 5
#define D_PIN 17 #define D_PIN 17
#define E_PIN -1 // required for 1/32 scan panels, like 64x64px. Any available pin would do, i.e. IO32 #define E_PIN -1 // required for 1/32 scan panels, like 64x64. Any available pin would do, i.e. IO32
#define LAT_PIN 4 #define LAT_PIN 4
#define OE_PIN 15 #define OE_PIN 15
#define CLK_PIN 16 #define CLK_PIN 16
HUB75_I2S_CFG::i2s_pins _pins={R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, LAT_PIN, OE_PIN, CLK_PIN};
HUB75_I2S_CFG mxconfig( display.begin(R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, LAT_PIN, OE_PIN, CLK_PIN ); // setup the LED matrix
64, // Module width
32, // Module height
2, // chain length
_pins, // pin mapping
);
dma_display = new MatrixPanel_I2S_DMA(mxconfig);
``` ```
Make sure you also connect one of the HUB75 interfaces ground pins to a ground pin of the ESP32, otherwise you may get electrical artefacts on LED Matrix Panel. The panel must be powered by 5V AC adapter with enough current capacity. (Current varies due to how many LED are turned on at the same time. To drive all the LEDs, you need 5V4A adapter.)
Various people have created PCBs for which one can simply connect an ESP32 to a PCB, and then the PCB to the HUB75 connector, such as: # How to Use
* Brian Lough's [ESP32 I2S Matrix Shield](https://github.com/rorosaurus/esp32-hub75-driver) Below is a bare minimum sketch to draw a single white dot in the top left. You must call .begin() before you call ANY pixel-drawing (fonts, lines, colours etc.) function of the MatrixPanel_I2S_DMA class.
* Charles Hallard's [WeMos Matrix Shield](https://github.com/hallard/WeMos-Matrix-Shield-DMA)
* Bogdan Sass's [Morph Clock Shield](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/discussions/110#discussioncomment-861152)
Please contact or order these products from the respective authors.
### How can I configure it to work with an off-the-shelf board/shield with HUB75 connector, e.g. Adafruit MatrixPortal?
You need to find the correct pin mapping for your board. For Adafruit boards/shields, you can look in one of the examples provided with the Protomatter library, for example [here](https://github.com/adafruit/Adafruit_Protomatter/blob/master/examples/doublebuffer_scrolltext/doublebuffer_scrolltext.ino). Find your board variant, copy the pin values into the `#define`s described above, and pass the pin mapping into your `mxconfig`.
For example, for MatrixPortal S3, the Protomatter example file contains the following:
No .begin() before other functions = Crash
``` ```
uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37}; #include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
uint8_t addrPins[] = {45, 36, 48, 35, 21}; MatrixPanel_I2S_DMA matrix;
uint8_t clockPin = 2;
uint8_t latchPin = 47;
uint8_t oePin = 14;
```
which for use with this library, converts to: void setup()
{
// MUST DO THIS FIRST!
matrix.begin(); // Use default pins supplied within ESP32-HUB75-MatrixPanel-I2S-DMA.h
// matrix.begin(R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, LAT_PIN, OE_PIN, CLK_PIN ); // or custom pins
``` // Draw a single white pixel
#define R1_PIN 42 matrix.drawPixel(0,0, matrix.color565(255,255,255)); // can do this after .begin() only
#define G1_PIN 41
#define B1_PIN 40
#define R2_PIN 38
#define G2_PIN 39
#define B2_PIN 37
#define A_PIN 45
#define B_PIN 36
#define C_PIN 48
#define D_PIN 35
#define E_PIN 21
#define LAT_PIN 47
#define OE_PIN 14
#define CLK_PIN 2
HUB75_I2S_CFG::i2s_pins _pins={R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, LAT_PIN, OE_PIN, CLK_PIN}; }
void loop()
{ }
// Module configuration
HUB75_I2S_CFG mxconfig(
PANEL_RES_X, // module width
PANEL_RES_Y, // module height
PANEL_CHAIN, // Chain length
_pins // Pin mapping
);
``` ```
### Can I use with a larger panel (i.e. 64x64px square panel)? ## Can I use with a larger panel (i.e. 64x64px square panel)?
If you want to use with a 64x64 pixel panel (typically a HUB75*E* panel) you MUST configure a valid *E_PIN* to your ESP32 and connect it to the E pin of the HUB75 panel! Hence the 'E' in 'HUB75E' If you want to use with a 64x64 pixel panel (typically a HUB75*E* panel) you MUST configure a valid *E_PIN* to your ESP32 and connect it to the E pin of the HUB75 panel! Hence the 'E' in 'HUB75E'
## 3. Run a Test Sketch
Below is a bare minimum sketch to draw a single white dot in the top left. You must call begin() before you call ANY pixel-drawing (fonts, lines, colours etc.) function of the MatrixPanel_I2S_DMA class.
Once this is working, refer to the [PIO Test Patterns](/examples/PIO_TestPatterns) example. This sketch draws simple colors/lines/gradients over the entire matrix and it could help to troubleshoot various issues with ghosting, flickering, etc...
Note: Requires the use of [PlatformIO](https://platformio.org/), which you should probably use if you aren't already.
# Further information
## Can I chain panels? ## Can I chain panels?
Yes!
[Horizontal](https://user-images.githubusercontent.com/12006953/122657476-cd358d00-d15b-11eb-9c6c-99b61378c56a.mp4) Yes.
For example: If you want to chain two of these horizontally to make a 128x32 panel you can do so by connecting the panels in series using the HUB75 ribbon cable. Than you must provide proper configuration structure to the class constructor letting it know that you use "one long virtual matrix chain". Refer to [Pattern Plasma](/examples/2_PatternPlasma/) example for all the details about configuration setup. For example: If you want to chain two of these horizontally to make a 128x32 panel you can do so by setting the MATRIX_WIDTH to '128' and connecting the panels in series using the HUB75 ribbon cable.
Finally, if you wanted to chain 4 x (64x32px) panels to make 128x64px display (essentially a 2x2 grid of 64x32 LED Matrix modules), a little more magic will be required. Refer to the [VirtualMatrixPanel](examples/VirtualMatrixPanel/) example and the [AuroraDemo](examples/AuroraDemo/) example of its practical use. Similarly, if you wanted to chain 4 panels to make a 256x32 px horizontal panel, you can easily by setting the MATRIX_WIDTH to '256' and connecting the panels in series using the HUB75 ribbon cable.
Resolutions beyond 128x64 are more likely to result in crashes due to [memory](/doc/i2s_memcalc.md) constraints etc. You are on your own after this point - PLEASE do not raise issues about this, the library can't magically defeat the SRAM memory constraints of the ESP32. You MUST either change the MATRIX_WIDTH or MATRIX_HEIGHT values within the 'ESP32-HUB75-MatrixPanel-I2S-DMA.h' file OR pass a [compile time option](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/48#issuecomment-749402379) if using PlatformIO for your development (you should use this).
Finally, if you wanted to chain 4 x (64x32px) panels to make 128x64px display (essentially a 2x2 grid of 64x32 LED Matrix modules), a little more magic will be required. Refer to the [Chained Panels](examples/ChainedPanels/) example.
Resolutions beyond 128x128 are likely to result in crashes due to memory constraints etc. You're on your own at this point.
![ezgif com-video-to-gif](https://user-images.githubusercontent.com/12006953/89837358-b64c0480-db60-11ea-870d-4b6482068a3b.gif) ![ezgif com-video-to-gif](https://user-images.githubusercontent.com/12006953/89837358-b64c0480-db60-11ea-870d-4b6482068a3b.gif)
## Adjusting Panel Brightness
By default you should not need to change / set the brightness value (which is 128 or 50%) as it should be sufficient for most purposes. Brightness can be changed by calling `setPanelBrightness(xx)` or `setBrightness8(xx)`. ## Panel Brightness
The value to pass must be a number between 0 (for a black screen) and 255 (max brightness). By default you should not need to change / set the brightness setting as the default value (16) is sufficent for most purposes. Brightness can be changed by calling `setPanelBrightness(XX)` and then `clearScreen()`.
The value to pass 'setPanelBrightness' must be a value less than MATRIX_WIDTH. For example for a single 64x32 LED Matrix Module, a value less than 64. However, if you set the brightness too high, you may experience ghosting.
Also you may use method `setPanelBrightness8(x)`, where x is a uint8_t value between 0-255. Library will recalculate required brightness level depending on matrix width (mostly useful with FastLED-based sketches).
Example: Example:
``` ```
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);
dma_display->begin(); // setup the LED matrix matrix.begin(R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, LAT_PIN, OE_PIN, CLK_PIN ); // setup the LED matrix
dma_display->setBrightness8(192); //0-255 matrix.setPanelBrightness(16); // Set the brightness. 32 or lower ideal for a single 64x32 LED Matrix Panel.
dma_display->clearScreen(); matrix.clearScreen(); // You must clear the screen after changing brightness level for it to take effect.
// or another way
matrix.setPanelBrightness(192); // Set the brightness to about 3/4 (192/256) of maximum.
matrix.clearScreen(); // You must clear the screen after changing brightness level for it to take effect.
} }
```
![Brightness Samples](https://user-images.githubusercontent.com/55933003/211192894-f90311f5-b6fe-4665-bf26-2f363bb36047.png)
## Build-time options
Although Arduino IDE does not [seem](https://github.com/arduino/Arduino/issues/421) to offer any way of specifying compile-time options for external libs there are other IDE's (like [PlatformIO](https://platformio.org/)/[Eclipse](https://www.eclipse.org/ide/)) that could use that. Check [Build Options](doc/BuildOptions.md) document for reference.
## Latch blanking
If you are facing issues with image ghosting when pixels has clones with horizontal offset, than you try to change Latch blanking value. Latch blanking controls for how many clock pulses matrix output is disabled via EO signal before/after toggling LAT signal. It hides row bits transitioning and different panels may require longer times for proper operation. Default value is 1 clock before/after LAT row transition. This could be controlled with `MatrixPanel_I2S_DMA::setLatBlanking(uint8_t v)`. v could be between 1 to 4, default is 1, larger values won't give any benefit other than reducing brightness.
An example:
```
dma_display->setLatBlanking(2);
``` ```
Summary: setPanelBrightness(xx) value can be any number from 0 (display off) to MATRIX_WIDTH-1. So if you are chaining multiple 64x32 panels, then this value may actually be > 64 (or you will have a dim display). Changing the brightness will have a huge impact on power usage.
![It's better in real life](image.jpg)
## Power, Power and Power! ## Power, Power and Power!
Having a good power supply is CRITICAL, and it is highly recommended, for chains of LED Panels to have a 1000-2000uf capacitor soldered to the back of each LED Panel across the [GND and VCC pins](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/39#issuecomment-720780463), otherwise you WILL run into issues with 'flashy' graphics whereby a large amount of LEDs are turned on and off in succession (due to current/power draw peaks and troughs).
- Refer to this guide written for the [rpi-rgb-led-matrix library](https://github.com/hzeller/rpi-rgb-led-matrix/blob/master/wiring.md#a-word-about-power) for an explanation. Having a good power supply is CRITICAL, and it is highly recommended, for chains of LED Panels to have a 2000uf capacitor soldered to the back of each LED Panel across the [GND and VCC pins](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/39#issuecomment-720780463), otherwise you WILL run into issues with 'flashy' graphics whereby a large amount of LEDs are turned on and off in succession (due to current/power draw peaks and troughs).
- Refer to this [example](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/39#issuecomment-722691127) issue of what can go wrong with a poor power supply.
Refer to this guide written for the [rpi-rgb-led-matrix library](https://github.com/hzeller/rpi-rgb-led-matrix/blob/master/wiring.md#a-word-about-power) for an explanation.
- Refer to this [example](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/39#issuecomment-722691127) issue of what can go wrong with a poor powersupply.
- Refer to [this comment](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/35#issuecomment-726419862) in regards to certain panels not playing nice with voltages, and a 3.3volt signal that the ESP32 GPIO can only provide. - Refer to [this comment](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/35#issuecomment-726419862) in regards to certain panels not playing nice with voltages, and a 3.3volt signal that the ESP32 GPIO can only provide.
## Inspiration ## Inspiration
This project was inspired by:
* 'SmartMatrix': https://github.com/pixelmatix/SmartMatrix/tree/teensylc * 'SmartMatrix' project code: https://github.com/pixelmatix/SmartMatrix/tree/teensylc
* Sprite_TM's demo implementation here: https://www.esp32.com/viewtopic.php?f=17&t=3188 * Sprite_TM's demo implementation here: https://www.esp32.com/viewtopic.php?f=17&t=3188
## Cool uses of this library
There are a number of great looking LED graphical display projects which leverage this library, these include:
* [128x64 Morph Clock](https://github.com/bogd/esp32-morphing-clock)
* [FFT Audio Visualisation](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/discussions/149)
* [Clock, GIF Animator and Audio Visualiser](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/discussions/153)
* [Aurora Audio Visualiser](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/discussions/188)
* [Big Visualisation](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/discussions/155)
* [Clockwise](https://jnthas.github.io/clockwise/)
* [ZeDMD](https://github.com/PPUC/ZeDMD)
# Thank you!
* [Brian Lough](https://www.tindie.com/stores/brianlough/) ([youtube link](https://www.youtube.com/c/brianlough)) for providing code contributions, hardware and suggestions
* [Vortigont](https://github.com/vortigont) for his game changing code contributions and performance optimisations
* [Galaxy Man](https://github.com/Galaxy-Man) for donation of 1/16 scan panels to support the implemenation of led matrix panel chaining (virtual display) support
* [Pipimaxi](https://github.com/Pipimaxi) for the donation of a ESP32-S2 and [Radu](https://github.com/juniorradu) for the donation of an ESP32-S3 to enable support for ESP32 S2/S3's to be tested and implemented.
* [Mark Donners](https://github.com/donnersm) ('The Electronic Engineer' on [youtube](https://www.youtube.com/watch?v=bQ7c9Vlhyp0&t=118s)) for the donation of a 1/8 scan panel to build and test working support of these led matrix panels!
* [PaintYourDragon](https://github.com/PaintYourDragon) for the DMA logic for the ESP32-S3.
* And lots of others, let me know if I've missed you.
If you want to donate money to the project, please refer to [this discussion](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/discussions/349) about it. If you want to donate/buy an LED panel for the library author to improve compatibility and/or testing - please feel free to post in the same [discussion](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA/discussions/349).
![It's better in real life](image.jpg)

BIN
WiringExample.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

View file

@ -1 +0,0 @@
COMPONENT_ADD_INCLUDEDIRS = .

View file

@ -1,38 +0,0 @@
### Build Options and flags
This library supports build-time defines to modify its features or enable greater debugging information. Please use the debugging capabilities before raising any issues.
For example build flags could be set using PlatformIO's .ini file like this
```
[env]
framework = arduino
platform = espressif32
lib_deps =
ESP32 HUB75 LED MATRIX PANEL DMA Display
build_flags =
-DCORE_DEBUG_LEVEL=3
-DNO_GFX=1
(etc.....)
```
Or if using Arduino: 'Tools' menu > 'Core Debug Level' > Select 'Debug'
... and use the Serial output to see the debug information.
## Build flags
| Flag | Description | Note |
| :------------ |---------------|-----|
| **CORE_DEBUG_LEVEL** |Adjust the espressif ESP32 IDF debug level, for which this library leverages to output information on what is going on when allocating memory etc. This will provide detailed information about memory allocations, DMA descriptors setup and color depth [BCM](http://www.batsocks.co.uk/readme/art_bcm_5.htm) |Set value to at least 3 [(Info)](https://iotespresso.com/core-debug-level-in-esp32/)
| **USE_GFX_ROOT** | Use [lightweight](https://github.com/mrfaptastic/Adafruit_GFX_Lite) version of AdafuitGFX, without Adafruit BusIO extensions | You **must** install [Adafruit_GFX_Lite](https://github.com/mrfaptastic/Adafruit_GFX_Lite) library instead of original AdafruitGFX|
| **NO_GFX** | Build without AdafuitGFX API, only native methods supported based on manipulating DMA buffer. I.e. no methods of drawing circles/shapes, typing text or using fonts!!! This might save some resources for applications using it's own internal graphics buffer or working solely with per-pixel manipulation. | Use this if you rely on FastLED, Neomatrix or any other API. For example [Aurora](/examples/AuroraDemo/) effects can work fine w/o AdafruitGFX. |
| **NO_FAST_FUNCTIONS** | Do not build auxiliary speed-optimized functions. Those are used to speed-up operations like drawing straight lines or rectangles. Otherwise lines/shapes are drawn using drawPixel() method. The trade-off for speed is RAM/code-size, take it or leave it ;) | If you are not using AdafruitGFX than you probably do not need this either|
|**NO_CIE1931**|Do not use LED brightness [compensation](https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/) described in [CIE 1931](https://en.wikipedia.org/wiki/CIE_1931_color_space). Normally library would adjust every pixel's RGB888 so that luminance (or brightness control) for the corresponding LED's would appear 'linear' to the human's eye. I.e. a white dot with rgb(128,128,128) would seem to be at 50% brightness between rgb(0,0,0) and rgb(255,255,255). Normally you would like to keep this enabled by default. Not only it makes brightness control "linear", it also makes colours more vivid, otherwise it looks brighter but 'bleached'.|You might want to turn it off in some special cases like: <ul><li>Using some other overlay lib for intermediate calculations that makes it's own compensation, like FastLED's [dimming functions](http://fastled.io/docs/3.1/group___dimming.html).<li>running at low colour depth's - it **might** (or might not) look better in shadows, darker gradients w/o compensation, try it<li>you run for as bright output as possible, no matter what (make sure you have proper powering)<li>you run for speed/save resources at all costs</ul> |
| **FORCE_COLOR_DEPTH** |In some cases the library may reduce colour fidelity to increase the refresh rate (i.e. reduce visible flicker). This is most likely to occur with a large chain of panels. However, if you want to force pure 24bpp colour, at the expense of likely noticeable flicker, then set this defined. |Not required in 99% of cases.
| **SPIRAM_FRAMEBUFFER** |Use SPIRAM/PSRAM for the HUB75 DMA buffer and not internal SRAM. ONLY SUPPORTED ON ESP32-S3 VARIANTS WITH OCTAL (not quad!) SPIRAM/PSRAM, as ony OCTAL PSRAM an provide the required data rate / bandwidth to drive the panels adequately.|ONLY SUPPORTED ON ESP32-S3 VARIANTS WITH OCTAL (not quad) SPIRAM/PSRAM
## Build-time variables
| Flag | Description | Note |
| :------------ |---------------|-----|
| **PIXEL_COLOR_DEPTH_BITS=8** | Colour depth per pixel in range 2-8. More bit's - more natural colour. But on the other hand every additional bit:<ul><li>eats ~2.5 bits of DMA memory per pixel<li>reduces matrix refresh rate in power of two due to nature of [BCM](http://www.batsocks.co.uk/readme/art_bcm_5.htm)</ul> | For large chains of panels (i.e. 6 x 64x64 panels) you WILL need to reduce the colour depth, or likely run out of memory. Default is 8 bits per colour per pixel, i.e. True colour 24 bit RGB. <br><br>For higher resolutions, from 64x64 and above it is not possible to provide full 24 bits colour without significant flickering OR reducing dynamic range in shadows. In that case using 5-6 bits at high res make very small difference to the humans eye actually. Refer to the [I2S memcalc](i2s_memcalc.md) for more details.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

View file

@ -1,40 +0,0 @@
### Memory Calculator
I've made this [spreadsheet](memcalc.xlsm) to estimate all of the main parameters for ESP32-HUB75-MatrixPanel-DMA lib driving any combination of matrices/chains so that I do not need to reflash it hundreds of times just to check for the debug info about memory.
Be sure to enable embedded macro's to allow refresh rate calculations.
![](i2scalc.png)
Just fill-in all of the INPUT fields and get the OUTPUTs.
So there are two main resources used to drive LED matrix
- Memory
- Bus clock speed (resulting in available bandwidth to pump pixel color data)
And there are lot's of hogs for those:
- matrix resolution (number of pixels)
- number of modules in chain
- pixel color depth
- [BCM](http://www.batsocks.co.uk/readme/art_bcm_5.htm) LSB to MSB transition
- double buffering
Equalising ones with the others results in **Refresh rate**,
or (rough approximation)
<img src="https://render.githubusercontent.com/render/math?math=RefreshRate=\frac{%20I%20^2S%20_%20{clock}%20}{resolution%20\times%20chain%20\times%20(ColorDepth-LSB2MSB)}">
[//]: # (github markdown does not like LaTex formulas)
[//]: # ($$RefreshRate=\frac{resolution \times chain \times (ColorDepth-LSB2MSB)}{ I ^2S _ {clock} }$$)
So, how to find optimum balance for all of these? Obviously you can't change *resolution* and *chain length*, it is physical characteristics and there is not much you can do about it except cutting off your chain or pushing it to the memory limits.
There are 3 parameters you can choose from (actually two:)
- **Color Depth** - predefined at [build-time]((/doc/BuildOptions.md)) option
- I2S clock speed - run-time tunable with a very limited options
- **LSB-to-MSB** transition - it can't be controlled in any way, library uses it internally trying to balance all of the above
Using provided table it is possible to estimate all of the parameters before running the library. Besides calculating memory requirements it could help to find **optimum color depth** for your matrix configuration. For higher resolutions default 8 bits could be too much to sustain minimal refresh rate and avoid annoying flickering. So the library would increase MSB transition to keep the balance, thus reducing dynamic range in shadows and dark colors. As a result it is nearly almost the same as just reducing overall color depth. **But** reducing global color depth would also save lot's of precious RAM!
Now it's all up to you to decide :)
/Vortigont/

Binary file not shown.

285
esp32_i2s_parallel.c Normal file
View file

@ -0,0 +1,285 @@
// Copyright 2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#if defined(ESP32)
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "soc/i2s_struct.h"
#include "soc/i2s_reg.h"
#include "driver/periph_ctrl.h"
#include "soc/io_mux_reg.h"
#include "rom/lldesc.h"
//#include "esp_heap_caps.h"
#include "esp32_i2s_parallel.h"
typedef struct {
volatile lldesc_t *dmadesc_a, *dmadesc_b;
int desccount_a, desccount_b;
} i2s_parallel_state_t;
static i2s_parallel_state_t *i2s_state[2]={NULL, NULL};
callback shiftCompleteCallback;
void setShiftCompleteCallback(callback f) {
shiftCompleteCallback = f;
}
volatile bool previousBufferFree = true;
static int i2snum(i2s_dev_t *dev) {
return (dev==&I2S0)?0:1;
}
// Todo: handle IS20? (this is hard coded for I2S1 only)
static void IRAM_ATTR i2s_isr(void* arg) {
REG_WRITE(I2S_INT_CLR_REG(1), (REG_READ(I2S_INT_RAW_REG(1)) & 0xffffffc0) | 0x3f);
// at this point, the previously active buffer is free, go ahead and write to it
previousBufferFree = true;
if(shiftCompleteCallback)
shiftCompleteCallback();
}
/*
//Calculate the amount of dma descs needed for a buffer desc
static int calc_needed_dma_descs_for(i2s_parallel_buffer_desc_t *desc) {
int ret=0;
for (int i=0; desc[i].memory!=NULL; i++) {
ret+=(desc[i].size+DMA_MAX-1)/DMA_MAX;
}
return ret;
}
static void fill_dma_desc(volatile lldesc_t *dmadesc, i2s_parallel_buffer_desc_t *bufdesc) {
int n=0;
for (int i=0; bufdesc[i].memory!=NULL; i++) {
int len=bufdesc[i].size;
uint8_t *data=(uint8_t*)bufdesc[i].memory;
while(len) {
int dmalen=len;
if (dmalen>DMA_MAX) dmalen=DMA_MAX;
dmadesc[n].size=dmalen;
dmadesc[n].length=dmalen;
dmadesc[n].buf=data;
dmadesc[n].eof=0;
dmadesc[n].sosf=0;
dmadesc[n].owner=1;
dmadesc[n].qe.stqe_next=(lldesc_t*)&dmadesc[n+1];
dmadesc[n].offset=0;
len-=dmalen;
data+=dmalen;
n++;
}
}
// set EOF bit in last dma descriptor
dmadesc[n-1].eof=1;
// link end of list back to beginning so current frame will be refreshed continously
dmadesc[n-1].qe.stqe_next=(lldesc_t*)&dmadesc[0];
printf("fill_dma_desc: filled %d descriptors\n", n);
}
*/
// size must be less than DMA_MAX - need to handle breaking long transfer into two descriptors before call
// DMA_MAX by the way is the maximum data packet size you can hold in one chunk
void link_dma_desc(volatile lldesc_t *dmadesc, volatile lldesc_t *prevdmadesc, void *memory, size_t size)
{
if(size > DMA_MAX) size = DMA_MAX;
dmadesc->size = size;
dmadesc->length = size;
dmadesc->buf = memory;
dmadesc->eof = 0;
dmadesc->sosf = 0;
dmadesc->owner = 1;
dmadesc->qe.stqe_next = 0; // will need to set this elsewhere
dmadesc->offset = 0;
// link previous to current
if(prevdmadesc)
prevdmadesc->qe.stqe_next = (lldesc_t*)dmadesc;
}
static void gpio_setup_out(int gpio, int sig) {
if (gpio==-1) return;
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpio], PIN_FUNC_GPIO);
gpio_set_direction(gpio, GPIO_MODE_DEF_OUTPUT);
gpio_matrix_out(gpio, sig, false, false);
}
static void dma_reset(i2s_dev_t *dev) {
dev->lc_conf.in_rst=1; dev->lc_conf.in_rst=0;
dev->lc_conf.out_rst=1; dev->lc_conf.out_rst=0;
}
static void fifo_reset(i2s_dev_t *dev) {
dev->conf.rx_fifo_reset=1; dev->conf.rx_fifo_reset=0;
dev->conf.tx_fifo_reset=1; dev->conf.tx_fifo_reset=0;
}
void i2s_parallel_setup_without_malloc(i2s_dev_t *dev, const i2s_parallel_config_t *cfg) {
//Figure out which signal numbers to use for routing
//printf("Setting up parallel I2S bus at I2S%d\n", i2snum(dev));
int sig_data_base, sig_clk;
if (dev==&I2S0) {
sig_data_base=I2S0O_DATA_OUT0_IDX;
sig_clk=I2S0O_WS_OUT_IDX;
} else {
//printf("Setting up i2s parallel mode in %d bit mode!\n", cfg->bits);
if (cfg->bits==I2S_PARALLEL_BITS_32) {
sig_data_base=I2S1O_DATA_OUT0_IDX;
} else if (cfg->bits==I2S_PARALLEL_BITS_16) {
//Because of... reasons... the 16-bit values for i2s1 appear on d8...d23
sig_data_base=I2S1O_DATA_OUT8_IDX;
} else { // I2S_PARALLEL_BITS_8
//printf("Setting up i2s parallel mode in %d bit mode -> https://www.esp32.com/viewtopic.php?f=17&t=3188 | https://www.esp32.com/viewtopic.php?f=13&t=3256", 8);
sig_data_base=I2S1O_DATA_OUT0_IDX;
}
sig_clk=I2S1O_WS_OUT_IDX;
}
//Route the signals
for (int x=0; x<cfg->bits; x++) {
gpio_setup_out(cfg->gpio_bus[x], sig_data_base+x);
}
//ToDo: Clk/WS may need inversion?
gpio_setup_out(cfg->gpio_clk, sig_clk);
//Power on dev
if (dev==&I2S0) {
periph_module_enable(PERIPH_I2S0_MODULE);
} else {
periph_module_enable(PERIPH_I2S1_MODULE);
}
//Initialize I2S dev
dev->conf.rx_reset=1; dev->conf.rx_reset=0;
dev->conf.tx_reset=1; dev->conf.tx_reset=0;
dma_reset(dev);
fifo_reset(dev);
//Enable LCD mode
dev->conf2.val=0;
dev->conf2.lcd_en=1;
// Enable "One datum will be written twice in LCD mode" - for some reason, if we don't do this in 8-bit mode, data is updated on half-clocks not clocks
if(cfg->bits == I2S_PARALLEL_BITS_8)
dev->conf2.lcd_tx_wrx2_en=1;
dev->sample_rate_conf.val=0;
dev->sample_rate_conf.rx_bits_mod=cfg->bits;
dev->sample_rate_conf.tx_bits_mod=cfg->bits;
dev->sample_rate_conf.rx_bck_div_num=4; //ToDo: Unsure about what this does...
// because conf2.lcd_tx_wrx2_en is set for 8-bit mode, the clock speed is doubled, drop it in half here
if(cfg->bits == I2S_PARALLEL_BITS_8)
dev->sample_rate_conf.tx_bck_div_num=2;
else
dev->sample_rate_conf.tx_bck_div_num=1; // datasheet says this must be 2 or greater (but 1 seems to work)
dev->clkm_conf.val=0;
dev->clkm_conf.clka_en=0;
dev->clkm_conf.clkm_div_a=63;
dev->clkm_conf.clkm_div_b=63;
//We ignore the possibility for fractional division here, clkspeed_hz must round up for a fractional clock speed, must result in >= 2
dev->clkm_conf.clkm_div_num=80000000L/(cfg->clkspeed_hz + 1);
dev->fifo_conf.val=0;
dev->fifo_conf.rx_fifo_mod_force_en=1;
dev->fifo_conf.tx_fifo_mod_force_en=1;
//dev->fifo_conf.tx_fifo_mod=1;
dev->fifo_conf.tx_fifo_mod=1;
dev->fifo_conf.rx_data_num=32; //Thresholds.
dev->fifo_conf.tx_data_num=32;
dev->fifo_conf.dscr_en=1;
dev->conf1.val=0;
dev->conf1.tx_stop_en=0;
dev->conf1.tx_pcm_bypass=1;
dev->conf_chan.val=0;
dev->conf_chan.tx_chan_mod=1;
dev->conf_chan.rx_chan_mod=1;
//Invert ws to be active-low... ToDo: make this configurable
//dev->conf.tx_right_first=1;
dev->conf.tx_right_first=0;
//dev->conf.rx_right_first=1;
dev->conf.rx_right_first=0;
dev->timing.val=0;
//Allocate DMA descriptors
i2s_state[i2snum(dev)]=malloc(sizeof(i2s_parallel_state_t));
assert(i2s_state[i2snum(dev)] != NULL);
i2s_parallel_state_t *st=i2s_state[i2snum(dev)];
st->desccount_a = cfg->desccount_a;
st->desccount_b = cfg->desccount_b;
st->dmadesc_a = cfg->lldesc_a;
st->dmadesc_b = cfg->lldesc_b;
//Reset FIFO/DMA -> needed? Doesn't dma_reset/fifo_reset do this?
dev->lc_conf.in_rst=1; dev->lc_conf.out_rst=1; dev->lc_conf.ahbm_rst=1; dev->lc_conf.ahbm_fifo_rst=1;
dev->lc_conf.in_rst=0; dev->lc_conf.out_rst=0; dev->lc_conf.ahbm_rst=0; dev->lc_conf.ahbm_fifo_rst=0;
dev->conf.tx_reset=1; dev->conf.tx_fifo_reset=1; dev->conf.rx_fifo_reset=1;
dev->conf.tx_reset=0; dev->conf.tx_fifo_reset=0; dev->conf.rx_fifo_reset=0;
// setup I2S Interrupt
SET_PERI_REG_BITS(I2S_INT_ENA_REG(1), I2S_OUT_EOF_INT_ENA_V, 1, I2S_OUT_EOF_INT_ENA_S);
// allocate a level 1 intterupt: lowest priority, as ISR isn't urgent and may take a long time to complete
esp_intr_alloc(ETS_I2S1_INTR_SOURCE, (int)(ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL1), i2s_isr, NULL, NULL);
//Start dma on front buffer (buffer a)
dev->lc_conf.val=I2S_OUT_DATA_BURST_EN | I2S_OUTDSCR_BURST_EN | I2S_OUT_DATA_BURST_EN;
dev->out_link.addr=((uint32_t)(&st->dmadesc_a[0]));
dev->out_link.start=1;
dev->conf.tx_start=1;
}
//Flip to a buffer: 0 for bufa, 1 for bufb
void i2s_parallel_flip_to_buffer(i2s_dev_t *dev, int bufid) {
int no=i2snum(dev);
if (i2s_state[no]==NULL) return;
lldesc_t *active_dma_chain;
if (bufid==0) {
active_dma_chain=(lldesc_t*)&i2s_state[no]->dmadesc_a[0];
} else {
active_dma_chain=(lldesc_t*)&i2s_state[no]->dmadesc_b[0];
}
// setup linked list to refresh from new buffer (continuously) when the end of the current list has been reached
i2s_state[no]->dmadesc_a[i2s_state[no]->desccount_a-1].qe.stqe_next=active_dma_chain;
i2s_state[no]->dmadesc_b[i2s_state[no]->desccount_b-1].qe.stqe_next=active_dma_chain;
// we're still refreshing the previously buffer, so it shouldn't be written to yet
previousBufferFree = false;
}
bool i2s_parallel_is_previous_buffer_free() {
return previousBufferFree;
}
#endif

59
esp32_i2s_parallel.h Normal file
View file

@ -0,0 +1,59 @@
#ifndef I2S_PARALLEL_H
#define I2S_PARALLEL_H
#if defined(ESP32)
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#include "soc/i2s_struct.h"
#include "rom/lldesc.h"
#define DMA_MAX (4096-4)
//#define DMA_MAX (512)
typedef enum {
I2S_PARALLEL_BITS_8=8,
I2S_PARALLEL_BITS_16=16,
I2S_PARALLEL_BITS_32=32,
} i2s_parallel_cfg_bits_t;
typedef struct {
void *memory;
size_t size;
} i2s_parallel_buffer_desc_t;
typedef struct {
int gpio_bus[24];
int gpio_clk;
int clkspeed_hz;
i2s_parallel_cfg_bits_t bits;
i2s_parallel_buffer_desc_t *bufa;
i2s_parallel_buffer_desc_t *bufb; // only used with double buffering
int desccount_a;
int desccount_b; // only used with double buffering
lldesc_t * lldesc_a;
lldesc_t * lldesc_b; // only used with double buffering
} i2s_parallel_config_t;
void i2s_parallel_setup_without_malloc(i2s_dev_t *dev, const i2s_parallel_config_t *cfg);
void link_dma_desc(volatile lldesc_t *dmadesc, volatile lldesc_t *prevdmadesc, void *memory, size_t size);
void i2s_parallel_flip_to_buffer(i2s_dev_t *dev, int bufid);
bool i2s_parallel_is_previous_buffer_free();
typedef void (*callback)(void);
void setShiftCompleteCallback(callback f);
#ifdef __cplusplus
}
#endif
#endif
#endif

View file

@ -1,166 +0,0 @@
// Example sketch which shows how to display some patterns
// on a 64x32 LED matrix
//
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
#define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module.
#define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module.
#define PANEL_CHAIN 1 // Total number of panels chained one to another
//MatrixPanel_I2S_DMA dma_display;
MatrixPanel_I2S_DMA *dma_display = nullptr;
uint16_t myBLACK = dma_display->color565(0, 0, 0);
uint16_t myWHITE = dma_display->color565(255, 255, 255);
uint16_t myRED = dma_display->color565(255, 0, 0);
uint16_t myGREEN = dma_display->color565(0, 255, 0);
uint16_t myBLUE = dma_display->color565(0, 0, 255);
// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
// From: https://gist.github.com/davidegironi/3144efdc6d67e5df55438cc3cba613c8
uint16_t colorWheel(uint8_t pos) {
if(pos < 85) {
return dma_display->color565(pos * 3, 255 - pos * 3, 0);
} else if(pos < 170) {
pos -= 85;
return dma_display->color565(255 - pos * 3, 0, pos * 3);
} else {
pos -= 170;
return dma_display->color565(0, pos * 3, 255 - pos * 3);
}
}
void drawText(int colorWheelOffset)
{
// draw text with a rotating colour
dma_display->setTextSize(1); // size 1 == 8 pixels high
dma_display->setTextWrap(false); // Don't wrap at end of line - will do ourselves
dma_display->setCursor(5, 0); // start at top left, with 8 pixel of spacing
uint8_t w = 0;
const char *str = "ESP32 DMA";
for (w=0; w<strlen(str); w++) {
dma_display->setTextColor(colorWheel((w*32)+colorWheelOffset));
dma_display->print(str[w]);
}
dma_display->println();
dma_display->print(" ");
for (w=9; w<18; w++) {
dma_display->setTextColor(colorWheel((w*32)+colorWheelOffset));
dma_display->print("*");
}
dma_display->println();
dma_display->setTextColor(dma_display->color444(15,15,15));
dma_display->println("LED MATRIX!");
// print each letter with a fixed rainbow color
dma_display->setTextColor(dma_display->color444(0,8,15));
dma_display->print('3');
dma_display->setTextColor(dma_display->color444(15,4,0));
dma_display->print('2');
dma_display->setTextColor(dma_display->color444(15,15,0));
dma_display->print('x');
dma_display->setTextColor(dma_display->color444(8,15,0));
dma_display->print('6');
dma_display->setTextColor(dma_display->color444(8,0,15));
dma_display->print('4');
// Jump a half character
dma_display->setCursor(34, 24);
dma_display->setTextColor(dma_display->color444(0,15,15));
dma_display->print("*");
dma_display->setTextColor(dma_display->color444(15,0,0));
dma_display->print('R');
dma_display->setTextColor(dma_display->color444(0,15,0));
dma_display->print('G');
dma_display->setTextColor(dma_display->color444(0,0,15));
dma_display->print("B");
dma_display->setTextColor(dma_display->color444(15,0,8));
dma_display->println("*");
}
void setup() {
// Module configuration
HUB75_I2S_CFG mxconfig(
PANEL_RES_X, // module width
PANEL_RES_Y, // module height
PANEL_CHAIN // Chain length
);
//mxconfig.gpio.e = 18;
//mxconfig.clkphase = false;
//mxconfig.driver = HUB75_I2S_CFG::FM6126A;
// Display Setup
dma_display = new MatrixPanel_I2S_DMA(mxconfig);
dma_display->begin();
dma_display->setBrightness8(90); //0-255
dma_display->clearScreen();
dma_display->fillScreen(myWHITE);
// fix the screen with green
dma_display->fillRect(0, 0, dma_display->width(), dma_display->height(), dma_display->color444(0, 15, 0));
delay(500);
// draw a box in yellow
dma_display->drawRect(0, 0, dma_display->width(), dma_display->height(), dma_display->color444(15, 15, 0));
delay(500);
// draw an 'X' in red
dma_display->drawLine(0, 0, dma_display->width()-1, dma_display->height()-1, dma_display->color444(15, 0, 0));
dma_display->drawLine(dma_display->width()-1, 0, 0, dma_display->height()-1, dma_display->color444(15, 0, 0));
delay(500);
// draw a blue circle
dma_display->drawCircle(10, 10, 10, dma_display->color444(0, 0, 15));
delay(500);
// fill a violet circle
dma_display->fillCircle(40, 21, 10, dma_display->color444(15, 0, 15));
delay(500);
// fill the screen with 'black'
dma_display->fillScreen(dma_display->color444(0, 0, 0));
//drawText(0);
}
uint8_t wheelval = 0;
void loop() {
// animate by going through the colour wheel for the first two lines
drawText(wheelval);
wheelval +=1;
delay(20);
/*
drawText(0);
delay(2000);
dma_display->clearScreen();
dma_display->fillScreen(myBLACK);
delay(2000);
dma_display->fillScreen(myBLUE);
delay(2000);
dma_display->fillScreen(myRED);
delay(2000);
dma_display->fillScreen(myGREEN);
delay(2000);
dma_display->fillScreen(myWHITE);
dma_display->clearScreen();
*/
}

View file

@ -1,207 +0,0 @@
/*
* Portions of this code are adapted from Aurora: https://github.com/pixelmatix/aurora
* Copyright (c) 2014 Jason Coon
*
* Portions of this code are adapted from LedEffects Plasma by Robert Atkins: https://bitbucket.org/ratkins/ledeffects/src/26ed3c51912af6fac5f1304629c7b4ab7ac8ca4b/Plasma.cpp?at=default
* Copyright (c) 2013 Robert Atkins
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// HUB75E pinout
// R1 | G1
// B1 | GND
// R2 | G2
// B2 | E
// A | B
// C | D
// CLK| LAT
// OE | GND
/* Default library pin configuration for the reference
you can redefine only ones you need later on object creation
#define R1 25
#define G1 26
#define BL1 27
#define R2 14
#define G2 12
#define BL2 13
#define CH_A 23
#define CH_B 19
#define CH_C 5
#define CH_D 17
#define CH_E -1 // assign to any available pin if using two panels or 64x64 panels with 1/32 scan
#define CLK 16
#define LAT 4
#define OE 15
*/
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
#include <FastLED.h>
// Configure for your panel(s) as appropriate!
#define PANEL_WIDTH 64
#define PANEL_HEIGHT 64 // Panel height of 64 will required PIN_E to be defined.
#define PANELS_NUMBER 2 // Number of chained panels, if just a single panel, obviously set to 1
#define PIN_E 32
#define PANE_WIDTH PANEL_WIDTH * PANELS_NUMBER
#define PANE_HEIGHT PANEL_HEIGHT
// placeholder for the matrix object
MatrixPanel_I2S_DMA *dma_display = nullptr;
uint16_t time_counter = 0, cycles = 0, fps = 0;
unsigned long fps_timer;
CRGB currentColor;
CRGBPalette16 palettes[] = {HeatColors_p, LavaColors_p, RainbowColors_p, RainbowStripeColors_p, CloudColors_p};
CRGBPalette16 currentPalette = palettes[0];
CRGB ColorFromCurrentPalette(uint8_t index = 0, uint8_t brightness = 255, TBlendType blendType = LINEARBLEND) {
return ColorFromPalette(currentPalette, index, brightness, blendType);
}
void setup() {
Serial.begin(115200);
Serial.println(F("*****************************************************"));
Serial.println(F("* ESP32-HUB75-MatrixPanel-I2S-DMA DEMO *"));
Serial.println(F("*****************************************************"));
/*
The configuration for MatrixPanel_I2S_DMA object is held in HUB75_I2S_CFG structure,
pls refer to the lib header file for full details.
All options has it's predefined default values. So we can create a new structure and redefine only the options we need
// those are the defaults
mxconfig.mx_width = 64; // physical width of a single matrix panel module (in pixels, usually it is always 64 ;) )
mxconfig.mx_height = 32; // physical height of a single matrix panel module (in pixels, usually almost always it is either 32 or 64)
mxconfig.chain_length = 1; // number of chained panels regardless of the topology, default 1 - a single matrix module
mxconfig.gpio.r1 = R1; // pin mappings
mxconfig.gpio.g1 = G1;
mxconfig.gpio.b1 = B1; // etc
mxconfig.driver = HUB75_I2S_CFG::SHIFT; // shift reg driver, default is plain shift register
mxconfig.double_buff = false; // use double buffer (twice amount of RAM required)
mxconfig.i2sspeed = HUB75_I2S_CFG::HZ_10M;// I2S clock speed, better leave as-is unless you want to experiment
*/
/*
For example we have two 64x64 panels chained, so we need to customize our setup like this
*/
HUB75_I2S_CFG mxconfig;
mxconfig.mx_height = PANEL_HEIGHT; // we have 64 pix heigh panels
mxconfig.chain_length = PANELS_NUMBER; // we have 2 panels chained
mxconfig.gpio.e = PIN_E; // we MUST assign pin e to some free pin on a board to drive 64 pix height panels with 1/32 scan
//mxconfig.driver = HUB75_I2S_CFG::FM6126A; // in case that we use panels based on FM6126A chip, we can change that
/*
//Another way of creating config structure
//Custom pin mapping for all pins
HUB75_I2S_CFG::i2s_pins _pins={R1, G1, BL1, R2, G2, BL2, CH_A, CH_B, CH_C, CH_D, CH_E, LAT, OE, CLK};
HUB75_I2S_CFG mxconfig(
64, // width
64, // height
4, // chain length
_pins, // pin mapping
HUB75_I2S_CFG::FM6126A // driver chip
);
*/
// OK, now we can create our matrix object
dma_display = new MatrixPanel_I2S_DMA(mxconfig);
// let's adjust default brightness to about 75%
dma_display->setBrightness8(255); // range is 0-255, 0 - 0%, 255 - 100%
// Allocate memory and start DMA display
if( not dma_display->begin() )
Serial.println("****** !KABOOM! I2S memory allocation failed ***********");
// well, hope we are OK, let's draw some colors first :)
Serial.println("Fill screen: RED");
dma_display->fillScreenRGB888(255, 0, 0);
delay(1000);
Serial.println("Fill screen: GREEN");
dma_display->fillScreenRGB888(0, 255, 0);
delay(1000);
Serial.println("Fill screen: BLUE");
dma_display->fillScreenRGB888(0, 0, 255);
delay(1000);
Serial.println("Fill screen: Neutral White");
dma_display->fillScreenRGB888(64, 64, 64);
delay(1000);
Serial.println("Fill screen: black");
dma_display->fillScreenRGB888(0, 0, 0);
delay(1000);
// Set current FastLED palette
currentPalette = RainbowColors_p;
Serial.println("Starting plasma effect...");
fps_timer = millis();
}
void loop() {
for (int x = 0; x < PANE_WIDTH; x++) {
for (int y = 0; y < PANE_HEIGHT; y++) {
int16_t v = 128;
uint8_t wibble = sin8(time_counter);
v += sin16(x * wibble * 3 + time_counter);
v += cos16(y * (128 - wibble) + time_counter);
v += sin16(y * x * cos8(-time_counter) / 8);
currentColor = ColorFromPalette(currentPalette, (v >> 8)); //, brightness, currentBlendType);
dma_display->drawPixelRGB888(x, y, currentColor.r, currentColor.g, currentColor.b);
}
}
++time_counter;
++cycles;
++fps;
if (cycles >= 1024) {
time_counter = 0;
cycles = 0;
currentPalette = palettes[random(0,sizeof(palettes)/sizeof(palettes[0]))];
}
// print FPS rate every 5 seconds
// Note: this is NOT a matrix refresh rate, it's the number of data frames being drawn to the DMA buffer per second
if (fps_timer + 5000 < millis()){
Serial.printf_P(PSTR("Effect fps: %d\n"), fps/5);
fps_timer = millis();
fps = 0;
}
} // end loop

View file

@ -1,90 +0,0 @@
// Example uses the following configuration: mxconfig.double_buff = true;
// to enable double buffering, which means display->flipDMABuffer(); is required.
// Bounce squares around the screen, doing the re-drawing in the background back-buffer.
// Double buffering is not always required in reality.
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
MatrixPanel_I2S_DMA *display = nullptr;
uint16_t myDARK = display->color565(64, 64, 64);
uint16_t myWHITE = display->color565(192, 192, 192);
uint16_t myRED = display->color565(255, 0, 0);
uint16_t myGREEN = display->color565(0, 255, 0);
uint16_t myBLUE = display->color565(0, 0, 255);
uint16_t colours[5] = { myDARK, myWHITE, myRED, myGREEN, myBLUE };
struct Square
{
float xpos, ypos;
float velocityx;
float velocityy;
boolean xdir, ydir;
uint16_t square_size;
uint16_t colour;
};
const int numSquares = 25;
Square Squares[numSquares];
void setup()
{
// put your setup code here, to run once:
delay(1000);
Serial.begin(115200);
delay(200);
Serial.println("...Starting Display");
HUB75_I2S_CFG mxconfig;
mxconfig.double_buff = true; // <------------- Turn on double buffer
//mxconfig.clkphase = false;
// OK, now we can create our matrix object
display = new MatrixPanel_I2S_DMA(mxconfig);
display->begin(); // setup display with pins as pre-defined in the library
// Create some random squares
for (int i = 0; i < numSquares; i++)
{
Squares[i].square_size = random(2,10);
Squares[i].xpos = random(0, display->width() - Squares[i].square_size);
Squares[i].ypos = random(0, display->height() - Squares[i].square_size);
Squares[i].velocityx = static_cast <float> (rand()) / static_cast <float> (RAND_MAX);
Squares[i].velocityy = static_cast <float> (rand()) / static_cast <float> (RAND_MAX);
int random_num = random(6);
Squares[i].colour = colours[random_num];
}
}
void loop()
{
display->flipDMABuffer(); // Show the back buffer, set currently output buffer to the back (i.e. no longer being sent to LED panels)
display->clearScreen(); // Now clear the back-buffer
delay(16); // <----------- Shouldn't see this clearscreen occur as it happens on the back buffer when double buffering is enabled.
for (int i = 0; i < numSquares; i++)
{
// Draw rect and then calculate
display->fillRect(Squares[i].xpos, Squares[i].ypos, Squares[i].square_size, Squares[i].square_size, Squares[i].colour);
if (Squares[i].square_size + Squares[i].xpos >= display->width()) {
Squares[i].velocityx *= -1;
} else if (Squares[i].xpos <= 0) {
Squares[i].velocityx = abs (Squares[i].velocityx);
}
if (Squares[i].square_size + Squares[i].ypos >= display->height()) {
Squares[i].velocityy *= -1;
} else if (Squares[i].ypos <= 0) {
Squares[i].velocityy = abs (Squares[i].velocityy);
}
Squares[i].xpos += Squares[i].velocityx;
Squares[i].ypos += Squares[i].velocityy;
}
}

View file

@ -1,114 +0,0 @@
/**********************************************************************
* The library by default supports simple 'shift register' based panels
* with A,B,C,D,E lines to select a specific row, but there are plenty
* of examples of new chips coming on the market that work different.
*
* Please search through the project's issues. For some of these chips
* (you will need to look at the back of your panel to identify), this
* library has workarounds. This can be configured through using one of:
// mxconfig.driver = HUB75_I2S_CFG::FM6126A;
//mxconfig.driver = HUB75_I2S_CFG::ICN2038S;
//mxconfig.driver = HUB75_I2S_CFG::FM6124;
//mxconfig.driver = HUB75_I2S_CFG::MBI5124;
*/
#include <Arduino.h>
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
#include <FastLED.h>
////////////////////////////////////////////////////////////////////
// Output resolution and panel chain length configuration
#define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module.
#define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module.
#define PANEL_CHAIN 1 // Total number of panels chained one to another
// placeholder for the matrix object
MatrixPanel_I2S_DMA *dma_display = nullptr;
///////////////////////////////////////////////////////////////
// FastLED variables for pattern output
uint16_t time_counter = 0, cycles = 0, fps = 0;
unsigned long fps_timer;
CRGB currentColor;
CRGBPalette16 palettes[] = {HeatColors_p, LavaColors_p, RainbowColors_p, RainbowStripeColors_p, CloudColors_p};
CRGBPalette16 currentPalette = palettes[0];
CRGB ColorFromCurrentPalette(uint8_t index = 0, uint8_t brightness = 255, TBlendType blendType = LINEARBLEND) {
return ColorFromPalette(currentPalette, index, brightness, blendType);
}
void setup(){
HUB75_I2S_CFG mxconfig(
PANEL_RES_X, // module width
PANEL_RES_Y, // module height
PANEL_CHAIN // Chain length
);
// in case that we use panels based on FM6126A chip, we can set it here before creating MatrixPanel_I2S_DMA object
mxconfig.driver = HUB75_I2S_CFG::FM6126A;
//mxconfig.driver = HUB75_I2S_CFG::ICN2038S;
//mxconfig.driver = HUB75_I2S_CFG::FM6124;
//mxconfig.driver = HUB75_I2S_CFG::MBI5124;
// OK, now we can create our matrix object
dma_display = new MatrixPanel_I2S_DMA(mxconfig);
// If you experience ghosting, you will need to reduce the brightness level, not all RGB Matrix
// Panels are the same - some seem to display ghosting artefacts at lower brightness levels.
// In the setup() function do something like:
// let's adjust default brightness to about 75%
dma_display->setBrightness8(192); // range is 0-255, 0 - 0%, 255 - 100%
// Allocate memory and start DMA display
if( not dma_display->begin() )
Serial.println("****** !KABOOM! Insufficient memory - allocation failed ***********");
fps_timer = millis();
}
void loop(){
for (int x = 0; x < dma_display->width(); x++) {
for (int y = 0; y < dma_display->height(); y++) {
int16_t v = 0;
uint8_t wibble = sin8(time_counter);
v += sin16(x * wibble * 3 + time_counter);
v += cos16(y * (128 - wibble) + time_counter);
v += sin16(y * x * cos8(-time_counter) / 8);
currentColor = ColorFromPalette(currentPalette, (v >> 8) + 127); //, brightness, currentBlendType);
dma_display->drawPixelRGB888(x, y, currentColor.r, currentColor.g, currentColor.b);
}
}
++time_counter;
++cycles;
++fps;
if (cycles >= 1024) {
time_counter = 0;
cycles = 0;
currentPalette = palettes[random(0,sizeof(palettes)/sizeof(palettes[0]))];
}
// print FPS rate every 5 seconds
// Note: this is NOT a matrix refresh rate, it's the number of data frames being drawn to the DMA buffer per second
if (fps_timer + 5000 < millis()){
Serial.printf_P(PSTR("Effect fps: %d\n"), fps/5);
fps_timer = millis();
fps = 0;
}
}
// FM6126 panel , thanks goes to:
// https://github.com/hzeller/rpi-rgb-led-matrix/issues/746

View file

@ -1,13 +0,0 @@
## Ohter driver based LED Matrix Panels ##
Limited support for other panels exists, but requires this to be passed as a configuration option when using the library.
These panels require a special reset sequence before they can be used, check your panel chipset if you have issues. Refer to the example.
```
mxconfig.driver = HUB75_I2S_CFG::FM6126A;
mxconfig.driver = HUB75_I2S_CFG::ICN2038S;
mxconfig.driver = HUB75_I2S_CFG::FM6124;
mxconfig.driver = HUB75_I2S_CFG::MBI5124;
```

View file

@ -18,27 +18,14 @@
* 3. Have fun. * 3. Have fun.
*/ */
#include "FS.h" #define FILESYSTEM SPIFFS
#include <LittleFS.h> #include <SPIFFS.h>
#include <AnimatedGIF.h> #include <AnimatedGIF.h>
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h> #include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
#define FILESYSTEM LittleFS // ----------------------------
#define FORMAT_LITTLEFS_IF_FAILED true
#define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module.
#define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module.
#define PANEL_CHAIN 1 // Total number of panels chained one to another horizontally only.
//MatrixPanel_I2S_DMA dma_display;
MatrixPanel_I2S_DMA *dma_display = nullptr;
uint16_t myBLACK = dma_display->color565(0, 0, 0);
uint16_t myWHITE = dma_display->color565(255, 255, 255);
uint16_t myRED = dma_display->color565(255, 0, 0);
uint16_t myGREEN = dma_display->color565(0, 255, 0);
uint16_t myBLUE = dma_display->color565(0, 0, 255);
MatrixPanel_I2S_DMA dma_display;
AnimatedGIF gif; AnimatedGIF gif;
File f; File f;
int x_offset, y_offset; int x_offset, y_offset;
@ -53,8 +40,8 @@ void GIFDraw(GIFDRAW *pDraw)
int x, y, iWidth; int x, y, iWidth;
iWidth = pDraw->iWidth; iWidth = pDraw->iWidth;
if (iWidth > dma_display->width()) if (iWidth > MATRIX_WIDTH)
iWidth = dma_display->width(); iWidth = MATRIX_WIDTH;
usPalette = pDraw->pPalette; usPalette = pDraw->pPalette;
y = pDraw->iY + pDraw->y; // current line y = pDraw->iY + pDraw->y; // current line
@ -97,7 +84,7 @@ void GIFDraw(GIFDRAW *pDraw)
if (iCount) // any opaque pixels? if (iCount) // any opaque pixels?
{ {
for(int xOffset = 0; xOffset < iCount; xOffset++ ){ for(int xOffset = 0; xOffset < iCount; xOffset++ ){
dma_display->drawPixel(x + xOffset, y, usTemp[xOffset]); // 565 Color Format dma_display.drawPixelRGB565(x + xOffset, y, usTemp[xOffset]);
} }
x += iCount; x += iCount;
iCount = 0; iCount = 0;
@ -125,7 +112,7 @@ void GIFDraw(GIFDRAW *pDraw)
// Translate the 8-bit pixels through the RGB565 palette (already byte reversed) // Translate the 8-bit pixels through the RGB565 palette (already byte reversed)
for (x=0; x<pDraw->iWidth; x++) for (x=0; x<pDraw->iWidth; x++)
{ {
dma_display->drawPixel(x, y, usPalette[*s++]); // color 565 dma_display.drawPixelRGB565(x, y, usPalette[*s++]);
} }
} }
} /* GIFDraw() */ } /* GIFDraw() */
@ -133,8 +120,6 @@ void GIFDraw(GIFDRAW *pDraw)
void * GIFOpenFile(const char *fname, int32_t *pSize) void * GIFOpenFile(const char *fname, int32_t *pSize)
{ {
Serial.print("Playing gif: ");
Serial.println(fname);
f = FILESYSTEM.open(fname); f = FILESYSTEM.open(fname);
if (f) if (f)
{ {
@ -185,9 +170,9 @@ void ShowGIF(char *name)
if (gif.open(name, GIFOpenFile, GIFCloseFile, GIFReadFile, GIFSeekFile, GIFDraw)) if (gif.open(name, GIFOpenFile, GIFCloseFile, GIFReadFile, GIFSeekFile, GIFDraw))
{ {
x_offset = (dma_display->width() - gif.getCanvasWidth())/2; x_offset = (MATRIX_WIDTH - gif.getCanvasWidth())/2;
if (x_offset < 0) x_offset = 0; if (x_offset < 0) x_offset = 0;
y_offset = (dma_display->height() - gif.getCanvasHeight())/2; y_offset = (MATRIX_HEIGHT - gif.getCanvasHeight())/2;
if (y_offset < 0) y_offset = 0; if (y_offset < 0) y_offset = 0;
Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n", gif.getCanvasWidth(), gif.getCanvasHeight()); Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n", gif.getCanvasWidth(), gif.getCanvasHeight());
Serial.flush(); Serial.flush();
@ -206,71 +191,56 @@ void ShowGIF(char *name)
/************************* Arduino Sketch Setup and Loop() *******************************/ /************************* Arduino Sketch Setup and Loop() *******************************/
void setup() { void setup() {
Serial.begin(115200);
if(!LittleFS.begin(FORMAT_LITTLEFS_IF_FAILED)){ Serial.begin(115200);
Serial.println("LittleFS Mount Failed"); Serial.println("Starting AnimatedGIFs Sketch");
return;
// Start filesystem
Serial.println(" * Loading SPIFFS");
if(!SPIFFS.begin()){
Serial.println("SPIFFS Mount Failed");
} }
HUB75_I2S_CFG mxconfig( /* setPanelBrightness should be called before .begin() */
PANEL_RES_X, // module width /* setMinRefreshRate must be called before .begin() */
PANEL_RES_Y, // module height dma_display.setPanelBrightness(32);
PANEL_CHAIN // Chain of panels - Horizontal width only. dma_display.setMinRefreshRate(200);
);
// mxconfig.gpio.e = 18; dma_display.begin();
// mxconfig.clkphase = false;
// mxconfig.driver = HUB75_I2S_CFG::FM6126A;
// Display Setup /* all other pixel drawing functions can only be called after .begin() */
dma_display = new MatrixPanel_I2S_DMA(mxconfig); dma_display.fillScreen(dma_display.color565(0, 0, 0));
dma_display->begin();
dma_display->setBrightness8(128); //0-255
dma_display->clearScreen();
dma_display->fillScreen(myWHITE);
// Start going through GIFS
gif.begin(LITTLE_ENDIAN_PIXELS); gif.begin(LITTLE_ENDIAN_PIXELS);
} }
String gifDir = "/gifs"; // play all GIFs in this directory on the SD card void loop() {
char filePath[256] = { 0 }; char *szDir = "/gifs"; // play all GIFs in this directory on the SD card
File root, gifFile; char fname[256];
File root, temp;
void loop()
{
while (1) // run forever while (1) // run forever
{ {
root = FILESYSTEM.open(szDir);
root = FILESYSTEM.open(gifDir);
if (root) if (root)
{ {
gifFile = root.openNextFile(); temp = root.openNextFile();
while (gifFile) while (temp)
{ {
if (!gifFile.isDirectory()) // play it if (!temp.isDirectory()) // play it
{ {
strcpy(fname, temp.name());
// C-strings... urghh... Serial.printf("Playing %s\n", temp.name());
memset(filePath, 0x0, sizeof(filePath)); Serial.flush();
strcpy(filePath, gifFile.path()); ShowGIF((char *)temp.name());
// Show it.
ShowGIF(filePath);
} }
gifFile.close(); temp.close();
gifFile = root.openNextFile(); temp = root.openNextFile();
} }
root.close(); root.close();
} // root } // root
delay(4000); // pause before restarting
delay(1000); // pause before restarting
} // while } // while
} }
// Other LittleFS filesystem function examples available at:
// https://randomnerdtutorials.com/esp32-write-data-littlefs-arduino/

View file

@ -0,0 +1,13 @@
## Animated GIF Decoding Example
### Prerequisites
1. The excellent 'AnimatedGIF' library by Larry Bank needs to be installed: https://github.com/bitbank2/AnimatedGIF
This is available via the Arduino Library manager, or can be placed in the 'libs' directory with PlatformIO.
2. The files in the 'data' folder are written to the ESP32's SPIFFS file system.
## Credits
https://github.com/bitbank2/AnimatedGIF

View file

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View file

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View file

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 171 KiB

View file

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View file

@ -1,13 +0,0 @@
## Animated GIF Decoding Example
### Prerequisites
1. The excellent 'AnimatedGIF' library by Larry Bank needs to be installed: https://github.com/bitbank2/AnimatedGIF
2. The files in the 'data' folder are written to the ESP32's SPIFFS file system. In order to be able to do this using the Arduino 2.0 IDE, you need to install the ESP32 LittleFS plugin: https://github.com/lorol/arduino-esp32littlefs-plugin
Follow the instructions in the link to install the plugin: https://randomnerdtutorials.com/arduino-ide-2-install-esp32-littlefs/
## Credits
https://github.com/bitbank2/AnimatedGIF

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

View file

@ -1,268 +0,0 @@
/*********************************************************************
* AnimatedGif LED Matrix Panel example where the GIFs are
* stored on a SD card connected to the ESP32 using the
* standard GPIO pins used for SD card acces via. SPI.
*
* Put the gifs into a directory called 'gifs' (case sensitive) on
* a FAT32 formatted SDcard.
********************************************************************/
#include "FS.h"
#include "SD.h"
#include "SPI.h"
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
#include <AnimatedGIF.h>
/********************************************************************
* Pin mapping below is for LOLIN D32 (ESP 32)
*
* Default pin mapping used by this library is NOT compatable with the use of the
* ESP32-Arduino 'SD' card library (there is overlap). As such, some of the pins
* used for the HUB75 panel need to be shifted.
*
* 'SD' card library requires GPIO 23, 18 and 19
* https://github.com/espressif/arduino-esp32/tree/master/libraries/SD
*
*/
/*
* Connect the SD card to the following pins:
*
* SD Card | ESP32
* D2 -
* D3 SS
* CMD MOSI
* VSS GND
* VDD 3.3V
* CLK SCK
* VSS GND
* D0 MISO
* D1 -
*/
/**** SD Card GPIO mappings ****/
#define SS_PIN 5
//#define MOSI_PIN 23
//#define MISO_PIN 19
//#define CLK_PIN 18
/**** HUB75 GPIO mapping ****/
// GPIO 34+ are on the ESP32 are input only!!
// https://randomnerdtutorials.com/esp32-pinout-reference-gpios/
#define A_PIN 33 // remap esp32 library default from 23 to 33
#define B_PIN 32 // remap esp32 library default from 19 to 32
#define C_PIN 22 // remap esp32 library defaultfrom 5 to 22
//#define R1_PIN 25 // library default for the esp32, unchanged
//#define G1_PIN 26 // library default for the esp32, unchanged
//#define B1_PIN 27 // library default for the esp32, unchanged
//#define R2_PIN 14 // library default for the esp32, unchanged
//#define G2_PIN 12 // library default for the esp32, unchanged
//#define B2_PIN 13 // library default for the esp32, unchanged
//#define D_PIN 17 // library default for the esp32, unchanged
//#define E_PIN -1 // IMPORTANT: Change to a valid pin if using a 64x64px panel.
//#define LAT_PIN 4 // library default for the esp32, unchanged
//#define OE_PIN 15 // library default for the esp32, unchanged
//#define CLK_PIN 16 // library default for the esp32, unchanged
/***************************************************************
* HUB 75 LED DMA Matrix Panel Configuration
**************************************************************/
#define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module.
#define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module.
#define PANEL_CHAIN 1 // Total number of panels chained one to another
/**************************************************************/
AnimatedGIF gif;
MatrixPanel_I2S_DMA *dma_display = nullptr;
static int totalFiles = 0; // GIF files count
static File FSGifFile; // temp gif file holder
static File GifRootFolder; // directory listing
std::vector<std::string> GifFiles; // GIF files path
const int maxGifDuration = 30000; // ms, max GIF duration
#include "gif_functions.hpp"
#include "sdcard_functions.hpp"
/**************************************************************/
void draw_test_patterns();
int gifPlay( const char* gifPath )
{ // 0=infinite
if( ! gif.open( gifPath, GIFOpenFile, GIFCloseFile, GIFReadFile, GIFSeekFile, GIFDraw ) ) {
log_n("Could not open gif %s", gifPath );
}
Serial.print("Playing: "); Serial.println(gifPath);
int frameDelay = 0; // store delay for the last frame
int then = 0; // store overall delay
while (gif.playFrame(true, &frameDelay)) {
then += frameDelay;
if( then > maxGifDuration ) { // avoid being trapped in infinite GIF's
//log_w("Broke the GIF loop, max duration exceeded");
break;
}
}
gif.close();
return then;
}
void setup()
{
Serial.begin(115200);
// **************************** Setup SD Card access via SPI ****************************
if(!SD.begin(SS_PIN)){
// bool begin(uint8_t ssPin=SS, SPIClass &spi=SPI, uint32_t frequency=4000000, const char * mountpoint="/sd", uint8_t max_files=5, bool format_if_empty=false);
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD.cardType();
if(cardType == CARD_NONE){
Serial.println("No SD card attached");
return;
}
Serial.print("SD Card Type: ");
if(cardType == CARD_MMC){
Serial.println("MMC");
} else if(cardType == CARD_SD){
Serial.println("SDSC");
} else if(cardType == CARD_SDHC){
Serial.println("SDHC");
} else {
Serial.println("UNKNOWN");
}
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
Serial.printf("SD Card Size: %lluMB\n", cardSize);
//listDir(SD, "/", 1, false);
Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));
// **************************** Setup DMA Matrix ****************************
HUB75_I2S_CFG mxconfig(
PANEL_RES_X, // module width
PANEL_RES_Y, // module height
PANEL_CHAIN // Chain length
);
// Need to remap these HUB75 DMA pins because the SPI SDCard is using them.
// Otherwise the SD Card will not work.
mxconfig.gpio.a = A_PIN;
mxconfig.gpio.b = B_PIN;
mxconfig.gpio.c = C_PIN;
// mxconfig.gpio.d = D_PIN;
//mxconfig.clkphase = false;
//mxconfig.driver = HUB75_I2S_CFG::FM6126A;
// Display Setup
dma_display = new MatrixPanel_I2S_DMA(mxconfig);
// Allocate memory and start DMA display
if( not dma_display->begin() )
Serial.println("****** !KABOOM! HUB75 memory allocation failed ***********");
dma_display->setBrightness8(128); //0-255
dma_display->clearScreen();
// **************************** Setup Sketch ****************************
Serial.println("Starting AnimatedGIFs Sketch");
// SD CARD STOPS WORKING WITH DMA DISPLAY ENABLED>...
File root = SD.open("/gifs");
if(!root){
Serial.println("Failed to open directory");
return;
}
File file = root.openNextFile();
while(file){
if(!file.isDirectory())
{
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print(" SIZE: ");
Serial.println(file.size());
std::string filename = "/gifs/" + std::string(file.name());
Serial.println(filename.c_str());
GifFiles.push_back( filename );
// Serial.println("Adding to gif list:" + String(filename));
totalFiles++;
}
file = root.openNextFile();
}
file.close();
Serial.printf("Found %d GIFs to play.", totalFiles);
//totalFiles = getGifInventory("/gifs");
// This is important - Set the right endianness.
gif.begin(LITTLE_ENDIAN_PIXELS);
}
void loop(){
// Iterate over a vector using range based for loop
for(auto & elem : GifFiles)
{
gifPlay( elem.c_str() );
gif.reset();
delay(500);
}
}
void draw_test_patterns()
{
// fix the screen with green
dma_display->fillRect(0, 0, dma_display->width(), dma_display->height(), dma_display->color444(0, 15, 0));
delay(500);
// draw a box in yellow
dma_display->drawRect(0, 0, dma_display->width(), dma_display->height(), dma_display->color444(15, 15, 0));
delay(500);
// draw an 'X' in red
dma_display->drawLine(0, 0, dma_display->width()-1, dma_display->height()-1, dma_display->color444(15, 0, 0));
dma_display->drawLine(dma_display->width()-1, 0, 0, dma_display->height()-1, dma_display->color444(15, 0, 0));
delay(500);
// draw a blue circle
dma_display->drawCircle(10, 10, 10, dma_display->color444(0, 0, 15));
delay(500);
// fill a violet circle
dma_display->fillCircle(40, 21, 10, dma_display->color444(15, 0, 15));
delay(500);
delay(1000);
}

View file

@ -1,15 +0,0 @@
# ESP32-HUB75-MatrixPanel-DMA SDCard example
A very basic example using the 'Animated GIF' library by Larry Bank + the SD / File system library provided for Arduino by Espressif.
Some default HUB75 pins need to be remapped to accomodate for the SD Card.
![image](esp32_sdcard.jpg)
## How to use it?
1. Format a SD Card with FAT32 file system (default setting)
2. Create a directory called 'gifs'
3. Drop your gifs in there. The resolution of the GIFS must match that of the display.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

View file

@ -1,132 +0,0 @@
// Code copied from AnimatedGIF examples
#ifndef M5STACK_SD
// for custom ESP32 builds
#define M5STACK_SD SD
#endif
static void * GIFOpenFile(const char *fname, int32_t *pSize)
{
//log_d("GIFOpenFile( %s )\n", fname );
FSGifFile = M5STACK_SD.open(fname);
if (FSGifFile) {
*pSize = FSGifFile.size();
return (void *)&FSGifFile;
}
return NULL;
}
static void GIFCloseFile(void *pHandle)
{
File *f = static_cast<File *>(pHandle);
if (f != NULL)
f->close();
}
static int32_t GIFReadFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen)
{
int32_t iBytesRead;
iBytesRead = iLen;
File *f = static_cast<File *>(pFile->fHandle);
// Note: If you read a file all the way to the last byte, seek() stops working
if ((pFile->iSize - pFile->iPos) < iLen)
iBytesRead = pFile->iSize - pFile->iPos - 1; // <-- ugly work-around
if (iBytesRead <= 0)
return 0;
iBytesRead = (int32_t)f->read(pBuf, iBytesRead);
pFile->iPos = f->position();
return iBytesRead;
}
static int32_t GIFSeekFile(GIFFILE *pFile, int32_t iPosition)
{
int i = micros();
File *f = static_cast<File *>(pFile->fHandle);
f->seek(iPosition);
pFile->iPos = (int32_t)f->position();
i = micros() - i;
//log_d("Seek time = %d us\n", i);
return pFile->iPos;
}
// Draw a line of image directly on the LCD
void GIFDraw(GIFDRAW *pDraw)
{
uint8_t *s;
uint16_t *d, *usPalette, usTemp[320];
int x, y, iWidth;
iWidth = pDraw->iWidth;
if (iWidth > PANEL_RES_X)
iWidth = PANEL_RES_X;
usPalette = pDraw->pPalette;
y = pDraw->iY + pDraw->y; // current line
s = pDraw->pPixels;
if (pDraw->ucDisposalMethod == 2) {// restore to background color
for (x=0; x<iWidth; x++) {
if (s[x] == pDraw->ucTransparent)
s[x] = pDraw->ucBackground;
}
pDraw->ucHasTransparency = 0;
}
// Apply the new pixels to the main image
if (pDraw->ucHasTransparency) { // if transparency used
uint8_t *pEnd, c, ucTransparent = pDraw->ucTransparent;
int x, iCount;
pEnd = s + iWidth;
x = 0;
iCount = 0; // count non-transparent pixels
while(x < iWidth) {
c = ucTransparent-1;
d = usTemp;
while (c != ucTransparent && s < pEnd) {
c = *s++;
if (c == ucTransparent) { // done, stop
s--; // back up to treat it like transparent
} else { // opaque
*d++ = usPalette[c];
iCount++;
}
} // while looking for opaque pixels
if (iCount) { // any opaque pixels?
for(int xOffset = 0; xOffset < iCount; xOffset++ ){
dma_display->drawPixel(x + xOffset, y, usTemp[xOffset]); // 565 Color Format
}
x += iCount;
iCount = 0;
}
// no, look for a run of transparent pixels
c = ucTransparent;
while (c == ucTransparent && s < pEnd) {
c = *s++;
if (c == ucTransparent)
iCount++;
else
s--;
}
if (iCount) {
x += iCount; // skip these
iCount = 0;
}
}
} else {
s = pDraw->pPixels;
// Translate the 8-bit pixels through the RGB565 palette (already byte reversed)
for (x=0; x<iWidth; x++)
dma_display->drawPixel(x, y, usPalette[*s++]); // color 565
/*
usTemp[x] = usPalette[*s++];
for (x=0; x<pDraw->iWidth; x++) {
dma_display->drawPixel(x, y, usTemp[*s++]); // color 565
} */
}
} /* GIFDraw() */

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

View file

@ -1,102 +0,0 @@
/************************ SD Card Code ************************/
// As per: https://github.com/espressif/arduino-esp32/tree/master/libraries/SD/examples/SD_Test
void listDir(fs::FS &fs, const char * dirname, uint8_t levels, bool add_to_gif_list = false){
Serial.printf("Listing directory: %s\n", dirname);
File root = fs.open(dirname);
if(!root){
Serial.println("Failed to open directory");
return;
}
if(!root.isDirectory()){
Serial.println("Not a directory");
return;
}
File file = root.openNextFile();
while(file){
if(file.isDirectory()){
Serial.print(" DIR : ");
Serial.println(file.name());
if(levels){
listDir(fs, file.path(), levels -1, false);
}
} else {
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print(" SIZE: ");
Serial.println(file.size());
if (add_to_gif_list && levels == 0)
{
GifFiles.push_back( std::string(dirname) + file.name() );
Serial.println("Adding to gif list:" + String(dirname) +"/" + file.name());
totalFiles++;
}
}
file = root.openNextFile();
}
file.close();
}
void readFile(fs::FS &fs, const char * path){
Serial.printf("Reading file: %s\n", path);
File file = fs.open(path);
if(!file){
Serial.println("Failed to open file for reading");
return;
}
Serial.print("Read from file: ");
while(file.available()){
Serial.write(file.read());
}
file.close();
}
void testFileIO(fs::FS &fs, const char * path){
File file = fs.open(path);
static uint8_t buf[512];
size_t len = 0;
uint32_t start = millis();
uint32_t end = start;
if(file){
len = file.size();
size_t flen = len;
start = millis();
while(len){
size_t toRead = len;
if(toRead > 512){
toRead = 512;
}
file.read(buf, toRead);
len -= toRead;
}
end = millis() - start;
Serial.printf("%u bytes read for %u ms\n", flen, end);
file.close();
} else {
Serial.println("Failed to open file for reading");
}
file = fs.open(path, FILE_WRITE);
if(!file){
Serial.println("Failed to open file for writing");
return;
}
size_t i;
start = millis();
for(i=0; i<2048; i++){
file.write(buf, 512);
}
end = millis() - start;
Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end);
file.close();
}

View file

@ -24,7 +24,7 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
#include "Vector2.hpp" #include "Vector.h"
class Attractor { class Attractor {
public: public:
@ -33,7 +33,7 @@ public:
PVector location; // Location PVector location; // Location
Attractor() { Attractor() {
location = PVector(effects.getCenterX(), effects.getCenterY()); location = PVector(MATRIX_CENTRE_X, MATRIX_CENTRE_Y);
mass = 10; mass = 10;
G = .5; G = .5;
} }
@ -43,7 +43,7 @@ public:
float d = force.mag(); // Distance between objects float d = force.mag(); // Distance between objects
d = constrain(d, 5.0, 32.0); // Limiting the distance to eliminate "extreme" results for very close or very far objects d = constrain(d, 5.0, 32.0); // Limiting the distance to eliminate "extreme" results for very close or very far objects
force.normalize(); // Normalize vector (distance doesn't matter here, we just want this vector for direction) force.normalize(); // Normalize vector (distance doesn't matter here, we just want this vector for direction)
float strength = (G * mass * m.mass) / (d * d); // Calculate gravitational force magnitude float strength = (G * mass * m.mass) / (d * d); // Calculate gravitional force magnitude
force *= strength; // Get force vector --> magnitude * direction force *= strength; // Get force vector --> magnitude * direction
return force; return force;
} }

View file

@ -1,200 +1,120 @@
/* /* ------------------------- CUSTOM GPIO PIN MAPPING ------------------------- */
_ _ _ ____ ___ ____ _ #define R1_PIN 18
/ \ | | | | _ \ / _ \| _ \ / \ #define G1_PIN 25
/ _ \ | | | | |_) | | | | |_) | / _ \ #define B1_PIN 5
/ ___ \| |_| | _ <| |_| | _ < / ___ \ #define R2_PIN 17
/_/ \_\\___/|_| \_\\___/|_| \_\/_/ \_\ #define G2_PIN 26
#define B2_PIN 16
#define A_PIN 14
#define B_PIN 27
#define C_PIN 12
#define D_PIN 4
#define E_PIN -1
#define LAT_PIN 13
#define OE_PIN 15
#define CLK_PIN 2
____ _____ __ __ ___ /* -------------------------- Display Config Initialisation -------------------- */
| _ \| ____| \/ |/ _ \
| | | | _| | |\/| | | | |
| |_| | |___| | | | |_| |
|____/|_____|_| |_|\___/
Description: // MATRIX_WIDTH and MATRIX_HEIGHT *must* be changed in ESP32-HUB75-MatrixPanel-I2S-DMA.h
* This demonstrates a combination of the following libraries two: // If you are using Platform IO (you should), pass MATRIX_WIDTH and MATRIX_HEIGHT as a compile time option.
- "ESP32-HUB75-MatrixPanel-DMA" to send pixel data to the physical panels in combination with its // Refer to: https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/48#issuecomment-749402379
in-built "VirtualMatrix" class which used to create a virtual display of chained panels, so the
graphical effects of the Aurora demonstration can be shown on a 'bigger' grid of physical panels
acting as one big display.
- "GFX_Lite" to provide a simple graphics library for drawing on the virtual display. /* -------------------------- Class Initialisation -------------------------- */
GFX_Lite is a fork of AdaFruitGFX and FastLED library combined together, with a focus on simplicity and ease of use. #include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
MatrixPanel_I2S_DMA matrix;
Instructions: #include <FastLED.h>
* Use the serial input to advance through the patterns, or to toggle auto advance. Sending 'n' will advance to the next
pattern, 'p' will go to the previous pattern. Sending 'a' will toggle auto advance on and off.
*/ #include "Effects.h"
Effects effects;
#define USE_GFX_LITE 1 #include "Drawable.h"
#include <ESP32-VirtualMatrixPanel-I2S-DMA.h> #include "Playlist.h"
//#include "Geometry.h"
/***************************************************************************************************************************/ #include "Patterns.h"
// Step 1) Provide the size of each individual physical panel LED Matrix panel that is chained (or not) together
#define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module.
#define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module.
// Step 2) Provide details of the physical panel chaining that is in place.
#define NUM_ROWS 2 // Number of rows of chained INDIVIDUAL PANELS
#define NUM_COLS 1 // Number of INDIVIDUAL PANELS per ROW
#define PANEL_CHAIN NUM_ROWS*NUM_COLS // total number of panels chained one to another
// Step 3) How are the panels chained together?
#define PANEL_CHAIN_TYPE CHAIN_TOP_RIGHT_DOWN
// Refer to: https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA/tree/master/examples/VirtualMatrixPanel
// and: https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA/blob/master/doc/VirtualMatrixPanel.pdf
// Virtual Panel dimensions - our combined panel would be a square 4x4 modules with a combined resolution of 128x128 pixels
#define VPANEL_W PANEL_RES_X*NUM_COLS // Kosso: All Pattern files have had the MATRIX_WIDTH and MATRIX_HEIGHT replaced by these.
#define VPANEL_H PANEL_RES_Y*NUM_ROWS //
/***************************************************************************************************************************/
// The palettes are set to change every 60 seconds.
int lastPattern = 0;
// placeholder for the matrix object
MatrixPanel_I2S_DMA *matrix = nullptr;
// placeholder for the virtual display object
VirtualMatrixPanel *virtualDisp = nullptr;
#include "EffectsLayer.hpp" // FastLED CRGB Pixel Buffer for which the patterns are drawn
EffectsLayer effects(VPANEL_W, VPANEL_H);
#include "Drawable.hpp"
#include "Geometry.hpp"
#include "Patterns.hpp"
Patterns patterns; Patterns patterns;
/* -------------------------- Some variables -------------------------- */ /* -------------------------- Some variables -------------------------- */
unsigned long ms_current = 0; unsigned long fps = 0, fps_timer; // fps (this is NOT a matix refresh rate!)
unsigned long ms_previous = 0; unsigned int default_fps = 30, pattern_fps = 30; // default fps limit (this is not a matix refresh conuter!)
unsigned long ms_previous_palette = 0; unsigned long ms_animation_max_duration = 20000; // 20 seconds
unsigned long ms_animation_max_duration = 30000; // 10 seconds unsigned long last_frame=0, ms_previous=0;
unsigned long next_frame = 0;
void listPatterns();
void setup() void setup()
{ {
// Setup serial interface // Setup serial interface
Serial.begin(115200); Serial.begin(115200);
delay(250); delay(250);
matrix.begin(R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, LAT_PIN, OE_PIN, CLK_PIN ); // setup the LED matrix
/**
* this demos runs pretty fine in fast-mode which gives much better fps on large matrixes (>128x64)
* see comments in the lib header on what does that means
*/
//dma_display.setFastMode(true);
// SETS THE BRIGHTNESS HERE. MAX value is MATRIX_WIDTH, 2/3 OR LOWER IDEAL, default is about 50%
// Configure your matrix setup here // dma_display.setPanelBrightness(30);
HUB75_I2S_CFG mxconfig(PANEL_RES_X, PANEL_RES_Y, PANEL_CHAIN); /* another way to change brightness is to use
* dma_display.setPanelBrightness8(uint8_t brt); // were brt is within range 0-255
// custom pin mapping (if required) * it will recalculate to consider matrix width automatically
//HUB75_I2S_CFG::i2s_pins _pins={R1, G1, BL1, R2, G2, BL2, CH_A, CH_B, CH_C, CH_D, CH_E, LAT, OE, CLK}; */
//mxconfig.gpio = _pins; //dma_display.setPanelBrightness8(180);
// in case that we use panels based on FM6126A chip, we can change that
//mxconfig.driver = HUB75_I2S_CFG::FM6126A;
// FM6126A panels could be cloked at 20MHz with no visual artefacts
// mxconfig.i2sspeed = HUB75_I2S_CFG::HZ_20M;
// OK, now we can create our matrix object
matrix = new MatrixPanel_I2S_DMA(mxconfig);
// Allocate memory and start DMA display
if( not matrix->begin() )
Serial.println("****** !KABOOM! I2S memory allocation failed ***********");
// let's adjust default brightness to about 75%
matrix->setBrightness8(192); // range is 0-255, 0 - 0%, 255 - 100%
// create VirtualDisplay object based on our newly created dma_display object
virtualDisp = new VirtualMatrixPanel((*matrix), NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, PANEL_CHAIN_TYPE);
Serial.println("**************** Starting Aurora Effects Demo ****************"); Serial.println("**************** Starting Aurora Effects Demo ****************");
Serial.print("MATRIX_WIDTH: "); Serial.println(PANEL_RES_X*PANEL_CHAIN); // setup the effects generator
Serial.print("MATRIX_HEIGHT: "); Serial.println(PANEL_RES_Y); effects.Setup();
#ifdef VPANEL_W
Serial.println("VIRTUAL PANEL WIDTH " + String(VPANEL_W));
Serial.println("VIRTUAL PANEL HEIGHT " + String(VPANEL_H));
#endif
delay(500);
Serial.println("Effects being loaded: ");
listPatterns(); listPatterns();
patterns.setPattern(0);
patterns.start();
ms_previous = millis(); patterns.moveRandom(1); // start from a random pattern
Serial.print("Starting with pattern: "); Serial.print("Starting with pattern: ");
Serial.println(patterns.getCurrentPatternName()); Serial.println(patterns.getCurrentPatternName());
patterns.start();
}
bool autoAdvance = true;
char incomingByte = 0;
void handleSerialRead()
{
if (Serial.available() > 0) {
// read the incoming byte:
incomingByte = Serial.read();
if (incomingByte == 'n') {
Serial.println("Going to next pattern");
patterns.move(1);
}
if (incomingByte == 'p') {
Serial.println("Going to previous pattern");
patterns.move(-1);
}
if (incomingByte == 'a') {
autoAdvance = !autoAdvance;
if (autoAdvance)
Serial.println("Auto pattern advance is ON");
else
Serial.println("Auto pattern advance is OFF");
}
ms_previous = millis(); ms_previous = millis();
} fps_timer = millis();
} // end handleSerialRead }
void loop() void loop()
{ {
// menu.run(mainMenuItems, mainMenuItemCount);
handleSerialRead(); if ( (millis() - ms_previous) > ms_animation_max_duration )
ms_current = millis();
if (ms_current - ms_previous_palette > 10000) // change colour palette evert 10 seconds
{ {
effects.RandomPalette(); patterns.stop();
ms_previous_palette = ms_current; patterns.moveRandom(1);
//patterns.move(1);
patterns.start();
Serial.print("Changing pattern to: ");
Serial.println(patterns.getCurrentPatternName());
ms_previous = millis();
// Select a random palette as well
//effects.RandomPalette();
} }
if ( ((ms_current - ms_previous) > ms_animation_max_duration) && autoAdvance) if ( 1000 / pattern_fps + last_frame < millis()){
{ last_frame = millis();
pattern_fps = patterns.drawFrame();
if (!pattern_fps)
pattern_fps = default_fps;
patterns.move(1); ++fps;
ms_previous = ms_current;
} }
if ( next_frame < ms_current) if (fps_timer + 1000 < millis()){
next_frame = patterns.drawFrame() + ms_current; Serial.printf_P(PSTR("Effect fps: %ld\n"), fps);
fps_timer = millis();
fps = 0;
}
} }

326
examples/AuroraDemo/Boid.h Normal file
View file

@ -0,0 +1,326 @@
/*
* Aurora: https://github.com/pixelmatix/aurora
* Copyright (c) 2014 Jason Coon
*
* Portions of this code are adapted from "Flocking" in "The Nature of Code" by Daniel Shiffman: http://natureofcode.com/
* Copyright (c) 2014 Daniel Shiffman
* http://www.shiffman.net
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Flocking
// Daniel Shiffman <http://www.shiffman.net>
// The Nature of Code, Spring 2009
// Boid class
// Methods for Separation, Cohesion, Alignment added
class Boid {
public:
PVector location;
PVector velocity;
PVector acceleration;
float maxforce; // Maximum steering force
float maxspeed; // Maximum speed
float desiredseparation = 4;
float neighbordist = 8;
byte colorIndex = 0;
float mass;
boolean enabled = true;
Boid() {}
Boid(float x, float y) {
acceleration = PVector(0, 0);
velocity = PVector(randomf(), randomf());
location = PVector(x, y);
maxspeed = 1.5;
maxforce = 0.05;
}
static float randomf() {
return mapfloat(random(0, 255), 0, 255, -.5, .5);
}
static float mapfloat(float x, float in_min, float in_max, float out_min, float out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
void run(Boid boids [], uint8_t boidCount) {
flock(boids, boidCount);
update();
// wrapAroundBorders();
// render();
}
// Method to update location
void update() {
// Update velocity
velocity += acceleration;
// Limit speed
velocity.limit(maxspeed);
location += velocity;
// Reset acceleration to 0 each cycle
acceleration *= 0;
}
void applyForce(PVector force) {
// We could add mass here if we want A = F / M
acceleration += force;
}
void repelForce(PVector obstacle, float radius) {
//Force that drives boid away from obstacle.
PVector futPos = location + velocity; //Calculate future position for more effective behavior.
PVector dist = obstacle - futPos;
float d = dist.mag();
if (d <= radius) {
PVector repelVec = location - obstacle;
repelVec.normalize();
if (d != 0) { //Don't divide by zero.
// float scale = 1.0 / d; //The closer to the obstacle, the stronger the force.
repelVec.normalize();
repelVec *= (maxforce * 7);
if (repelVec.mag() < 0) { //Don't let the boids turn around to avoid the obstacle.
repelVec.y = 0;
}
}
applyForce(repelVec);
}
}
// We accumulate a new acceleration each time based on three rules
void flock(Boid boids [], uint8_t boidCount) {
PVector sep = separate(boids, boidCount); // Separation
PVector ali = align(boids, boidCount); // Alignment
PVector coh = cohesion(boids, boidCount); // Cohesion
// Arbitrarily weight these forces
sep *= 1.5;
ali *= 1.0;
coh *= 1.0;
// Add the force vectors to acceleration
applyForce(sep);
applyForce(ali);
applyForce(coh);
}
// Separation
// Method checks for nearby boids and steers away
PVector separate(Boid boids [], uint8_t boidCount) {
PVector steer = PVector(0, 0);
int count = 0;
// For every boid in the system, check if it's too close
for (int i = 0; i < boidCount; i++) {
Boid other = boids[i];
if (!other.enabled)
continue;
float d = location.dist(other.location);
// If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself)
if ((d > 0) && (d < desiredseparation)) {
// Calculate vector pointing away from neighbor
PVector diff = location - other.location;
diff.normalize();
diff /= d; // Weight by distance
steer += diff;
count++; // Keep track of how many
}
}
// Average -- divide by how many
if (count > 0) {
steer /= (float) count;
}
// As long as the vector is greater than 0
if (steer.mag() > 0) {
// Implement Reynolds: Steering = Desired - Velocity
steer.normalize();
steer *= maxspeed;
steer -= velocity;
steer.limit(maxforce);
}
return steer;
}
// Alignment
// For every nearby boid in the system, calculate the average velocity
PVector align(Boid boids [], uint8_t boidCount) {
PVector sum = PVector(0, 0);
int count = 0;
for (int i = 0; i < boidCount; i++) {
Boid other = boids[i];
if (!other.enabled)
continue;
float d = location.dist(other.location);
if ((d > 0) && (d < neighbordist)) {
sum += other.velocity;
count++;
}
}
if (count > 0) {
sum /= (float) count;
sum.normalize();
sum *= maxspeed;
PVector steer = sum - velocity;
steer.limit(maxforce);
return steer;
}
else {
return PVector(0, 0);
}
}
// Cohesion
// For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location
PVector cohesion(Boid boids [], uint8_t boidCount) {
PVector sum = PVector(0, 0); // Start with empty vector to accumulate all locations
int count = 0;
for (int i = 0; i < boidCount; i++) {
Boid other = boids[i];
if (!other.enabled)
continue;
float d = location.dist(other.location);
if ((d > 0) && (d < neighbordist)) {
sum += other.location; // Add location
count++;
}
}
if (count > 0) {
sum /= count;
return seek(sum); // Steer towards the location
}
else {
return PVector(0, 0);
}
}
// A method that calculates and applies a steering force towards a target
// STEER = DESIRED MINUS VELOCITY
PVector seek(PVector target) {
PVector desired = target - location; // A vector pointing from the location to the target
// Normalize desired and scale to maximum speed
desired.normalize();
desired *= maxspeed;
// Steering = Desired minus Velocity
PVector steer = desired - velocity;
steer.limit(maxforce); // Limit to maximum steering force
return steer;
}
// A method that calculates a steering force towards a target
// STEER = DESIRED MINUS VELOCITY
void arrive(PVector target) {
PVector desired = target - location; // A vector pointing from the location to the target
float d = desired.mag();
// Normalize desired and scale with arbitrary damping within 100 pixels
desired.normalize();
if (d < 4) {
float m = map(d, 0, 100, 0, maxspeed);
desired *= m;
}
else {
desired *= maxspeed;
}
// Steering = Desired minus Velocity
PVector steer = desired - velocity;
steer.limit(maxforce); // Limit to maximum steering force
applyForce(steer);
//Serial.println(d);
}
void wrapAroundBorders() {
if (location.x < 0) location.x = MATRIX_WIDTH - 1;
if (location.y < 0) location.y = MATRIX_HEIGHT - 1;
if (location.x >= MATRIX_WIDTH) location.x = 0;
if (location.y >= MATRIX_HEIGHT) location.y = 0;
}
void avoidBorders() {
PVector desired = velocity;
if (location.x < 8) desired = PVector(maxspeed, velocity.y);
if (location.x >= MATRIX_WIDTH - 8) desired = PVector(-maxspeed, velocity.y);
if (location.y < 8) desired = PVector(velocity.x, maxspeed);
if (location.y >= MATRIX_HEIGHT - 8) desired = PVector(velocity.x, -maxspeed);
if (desired != velocity) {
PVector steer = desired - velocity;
steer.limit(maxforce);
applyForce(steer);
}
if (location.x < 0) location.x = 0;
if (location.y < 0) location.y = 0;
if (location.x >= MATRIX_WIDTH) location.x = MATRIX_WIDTH - 1;
if (location.y >= MATRIX_HEIGHT) location.y = MATRIX_HEIGHT - 1;
}
bool bounceOffBorders(float bounce) {
bool bounced = false;
if (location.x >= MATRIX_WIDTH) {
location.x = MATRIX_WIDTH - 1;
velocity.x *= -bounce;
bounced = true;
}
else if (location.x < 0) {
location.x = 0;
velocity.x *= -bounce;
bounced = true;
}
if (location.y >= MATRIX_HEIGHT) {
location.y = MATRIX_HEIGHT - 1;
velocity.y *= -bounce;
bounced = true;
}
else if (location.y < 0) {
location.y = 0;
velocity.y *= -bounce;
bounced = true;
}
return bounced;
}
void render() {
//// Draw a triangle rotated in the direction of velocity
//float theta = velocity.heading2D() + radians(90);
//fill(175);
//stroke(0);
//pushMatrix();
//translate(location.x,location.y);
//rotate(theta);
//beginShape(TRIANGLES);
//vertex(0, -r*2);
//vertex(-r, r*2);
//vertex(r, r*2);
//endShape();
//popMatrix();
//matrix.drawBackgroundPixelRGB888(location.x, location.y, CRGB::Blue);
}
};
static const uint8_t AVAILABLE_BOID_COUNT = 40;
Boid boids[AVAILABLE_BOID_COUNT];

View file

@ -38,7 +38,7 @@ public:
// a single frame should be drawn as fast as possible, without any delay or blocking // a single frame should be drawn as fast as possible, without any delay or blocking
// return how many millisecond delay is requested before the next call to drawFrame() // return how many millisecond delay is requested before the next call to drawFrame()
virtual unsigned int drawFrame() { virtual unsigned int drawFrame() {
matrix->fillScreen(0); matrix.fillScreen(0);
//backgroundLayer.fillScreen({ 0, 0, 0 }); //backgroundLayer.fillScreen({ 0, 0, 0 });
return 0; return 0;
}; };

View file

@ -0,0 +1,855 @@
/*
* Aurora: https://github.com/pixelmatix/aurora
* Copyright (c) 2014 Jason Coon
*
* Portions of this code are adapted from "Funky Clouds" by Stefan Petrick: https://gist.github.com/anonymous/876f908333cd95315c35
* Portions of this code are adapted from "NoiseSmearing" by Stefan Petrick: https://gist.github.com/StefanPetrick/9ee2f677dbff64e3ba7a
* Copyright (c) 2014 Stefan Petrick
* http://www.stefan-petrick.de/wordpress_beta
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef Effects_H
#define Effects_H
/* ---------------------------- GLOBAL CONSTANTS ----------------------------- */
const int MATRIX_CENTER_X = MATRIX_WIDTH / 2;
const int MATRIX_CENTER_Y = MATRIX_HEIGHT / 2;
// US vs GB, huh? :)
//const byte MATRIX_CENTRE_X = MATRIX_CENTER_X - 1;
//const byte MATRIX_CENTRE_Y = MATRIX_CENTER_Y - 1;
#define MATRIX_CENTRE_X MATRIX_CENTER_X
#define MATRIX_CENTRE_Y MATRIX_CENTER_Y
const uint16_t NUM_LEDS = (MATRIX_WIDTH * MATRIX_HEIGHT) + 1; // one led spare to capture out of bounds
// forward declaration
uint16_t XY16( uint16_t x, uint16_t y);
/* Convert x,y co-ordinate to flat array index.
* x and y positions start from 0, so must not be >= 'real' panel width or height
* (i.e. 64 pixels or 32 pixels.). Max value: MATRIX_WIDTH-1 etc.
* Ugh... uint8_t - really??? this weak method can't cope with 256+ pixel matrixes :(
*/
uint16_t XY( uint8_t x, uint8_t y)
{
return XY16(x, y);
}
/**
* The one for 256+ matrixes
* otherwise this:
* for (uint8_t i = 0; i < MATRIX_WIDTH; i++) {}
* turns into an infinite loop
*/
uint16_t XY16( uint16_t x, uint16_t y)
{
if( x >= MATRIX_WIDTH) return 0;
if( y >= MATRIX_HEIGHT) return 0;
return (y * MATRIX_WIDTH) + x + 1; // everything offset by one to capute out of bounds stuff - never displayed by ShowFrame()
}
uint8_t beatcos8(accum88 beats_per_minute, uint8_t lowest = 0, uint8_t highest = 255, uint32_t timebase = 0, uint8_t phase_offset = 0)
{
uint8_t beat = beat8(beats_per_minute, timebase);
uint8_t beatcos = cos8(beat + phase_offset);
uint8_t rangewidth = highest - lowest;
uint8_t scaledbeat = scale8(beatcos, rangewidth);
uint8_t result = lowest + scaledbeat;
return result;
}
uint8_t mapsin8(uint8_t theta, uint8_t lowest = 0, uint8_t highest = 255) {
uint8_t beatsin = sin8(theta);
uint8_t rangewidth = highest - lowest;
uint8_t scaledbeat = scale8(beatsin, rangewidth);
uint8_t result = lowest + scaledbeat;
return result;
}
uint8_t mapcos8(uint8_t theta, uint8_t lowest = 0, uint8_t highest = 255) {
uint8_t beatcos = cos8(theta);
uint8_t rangewidth = highest - lowest;
uint8_t scaledbeat = scale8(beatcos, rangewidth);
uint8_t result = lowest + scaledbeat;
return result;
}
// Array of temperature readings at each simulation cell
//byte heat[NUM_LEDS]; // none of the currently enabled effects uses this
uint32_t noise_x;
uint32_t noise_y;
uint32_t noise_z;
uint32_t noise_scale_x;
uint32_t noise_scale_y;
//uint8_t noise[MATRIX_WIDTH][MATRIX_HEIGHT];
uint8_t **noise = nullptr; // we will allocate mem later
uint8_t noisesmoothing;
class Effects {
public:
CRGB *leds;
//CRGB leds[NUM_LEDS];
//CRGB leds2[NUM_LEDS]; // Faptastic: getting rid of this and any dependant effects or algos. to save memory 24*64*32 bytes of ram (50k).
Effects(){
// we do dynamic allocation for leds buffer, otherwise esp32 toolchain can't link static arrays of such a big size for 256+ matrixes
leds = (CRGB *)malloc(NUM_LEDS * sizeof(CRGB));
// allocate mem for noise effect
// (there should be some guards for malloc errors eventually)
noise = (uint8_t **)malloc(MATRIX_WIDTH * sizeof(uint8_t *));
for (int i = 0; i < MATRIX_WIDTH; ++i) {
noise[i] = (uint8_t *)malloc(MATRIX_HEIGHT * sizeof(uint8_t));
}
ClearFrame();
matrix.clearScreen();
}
~Effects(){
free(leds);
for (int i = 0; i < MATRIX_WIDTH; ++i) {
free(noise[i]);
}
free(noise);
}
/* The only 'framebuffer' we have is what is contained in the leds and leds2 variables.
* We don't store what the color a particular pixel might be, other than when it's turned
* into raw electrical signal output gobbly-gook (i.e. the DMA matrix buffer), but this * is not reversible.
*
* As such, any time these effects want to write a pixel color, we first have to update
* the leds or leds2 array, and THEN write it to the RGB panel. This enables us to 'look up' the array to see what a pixel color was previously, each drawFrame().
*/
void drawBackgroundFastLEDPixelCRGB(int16_t x, int16_t y, CRGB color)
{
leds[XY(x, y)] = color;
//matrix.drawPixelRGB888(x, y, color.r, color.g, color.b);
}
// write one pixel with the specified color from the current palette to coordinates
void Pixel(int x, int y, uint8_t colorIndex) {
leds[XY(x, y)] = ColorFromCurrentPalette(colorIndex);
//matrix.drawPixelRGB888(x, y, temp.r, temp.g, temp.b); // now draw it?
}
void PrepareFrame() {
// leds = (CRGB*) backgroundLayer.backBuffer();
}
void ShowFrame() {
//#if (FASTLED_VERSION >= 3001000)
// nblendPaletteTowardPalette(currentPalette, targetPalette, 24);
//#else
currentPalette = targetPalette;
//#endif
// backgroundLayer.swapBuffers();
// leds = (CRGB*) backgroundLayer.backBuffer();
// LEDS.countFPS();
for (int y=0; y<MATRIX_HEIGHT; ++y){
for (int x=0; x<MATRIX_WIDTH; ++x){
//Serial.printf("Flushing x, y coord %d, %d\n", x, y);
uint16_t _pixel = XY16(x,y);
matrix.drawPixelRGB888( x, y, leds[_pixel].r, leds[_pixel].g, leds[_pixel].b);
} // end loop to copy fast led to the dma matrix
}
}
// scale the brightness of the screenbuffer down
void DimAll(byte value)
{
for (int i = 0; i < NUM_LEDS; i++)
{
leds[i].nscale8(value);
}
}
void ClearFrame()
{
memset(leds, 0x00, NUM_LEDS * sizeof(CRGB)); // flush
}
/*
void CircleStream(uint8_t value) {
DimAll(value); ShowFrame();
for (uint8_t offset = 0; offset < MATRIX_CENTER_X; offset++) {
boolean hasprev = false;
uint16_t prevxy = 0;
for (uint8_t theta = 0; theta < 255; theta++) {
uint8_t x = mapcos8(theta, offset, (MATRIX_WIDTH - 1) - offset);
uint8_t y = mapsin8(theta, offset, (MATRIX_HEIGHT - 1) - offset);
uint16_t xy = XY(x, y);
if (hasprev) {
leds[prevxy] += leds[xy];
}
prevxy = xy;
hasprev = true;
}
}
for (uint8_t x = 0; x < MATRIX_WIDTH; x++) {
for (uint8_t y = 0; y < MATRIX_HEIGHT; y++) {
uint16_t xy = XY(x, y);
leds[xy] = leds2[xy];
leds[xy].nscale8(value);
leds2[xy].nscale8(value);
}
}
}
*/
// palettes
static const int paletteCount = 10;
int paletteIndex = -1;
TBlendType currentBlendType = LINEARBLEND;
CRGBPalette16 currentPalette;
CRGBPalette16 targetPalette;
char* currentPaletteName;
static const int HeatColorsPaletteIndex = 6;
static const int RandomPaletteIndex = 9;
void Setup() {
currentPalette = RainbowColors_p;
loadPalette(0);
NoiseVariablesSetup();
}
void CyclePalette(int offset = 1) {
loadPalette(paletteIndex + offset);
}
void RandomPalette() {
loadPalette(RandomPaletteIndex);
}
void loadPalette(int index) {
paletteIndex = index;
if (paletteIndex >= paletteCount)
paletteIndex = 0;
else if (paletteIndex < 0)
paletteIndex = paletteCount - 1;
switch (paletteIndex) {
case 0:
targetPalette = RainbowColors_p;
currentPaletteName = (char *)"Rainbow";
break;
//case 1:
// targetPalette = RainbowStripeColors_p;
// currentPaletteName = (char *)"RainbowStripe";
// break;
case 1:
targetPalette = OceanColors_p;
currentPaletteName = (char *)"Ocean";
break;
case 2:
targetPalette = CloudColors_p;
currentPaletteName = (char *)"Cloud";
break;
case 3:
targetPalette = ForestColors_p;
currentPaletteName = (char *)"Forest";
break;
case 4:
targetPalette = PartyColors_p;
currentPaletteName = (char *)"Party";
break;
case 5:
setupGrayscalePalette();
currentPaletteName = (char *)"Grey";
break;
case HeatColorsPaletteIndex:
targetPalette = HeatColors_p;
currentPaletteName = (char *)"Heat";
break;
case 7:
targetPalette = LavaColors_p;
currentPaletteName = (char *)"Lava";
break;
case 8:
setupIcePalette();
currentPaletteName = (char *)"Ice";
break;
case RandomPaletteIndex:
loadPalette(random(0, paletteCount - 1));
paletteIndex = RandomPaletteIndex;
currentPaletteName = (char *)"Random";
break;
}
}
void setPalette(String paletteName) {
if (paletteName == "Rainbow")
loadPalette(0);
//else if (paletteName == "RainbowStripe")
// loadPalette(1);
else if (paletteName == "Ocean")
loadPalette(1);
else if (paletteName == "Cloud")
loadPalette(2);
else if (paletteName == "Forest")
loadPalette(3);
else if (paletteName == "Party")
loadPalette(4);
else if (paletteName == "Grayscale")
loadPalette(5);
else if (paletteName == "Heat")
loadPalette(6);
else if (paletteName == "Lava")
loadPalette(7);
else if (paletteName == "Ice")
loadPalette(8);
else if (paletteName == "Random")
RandomPalette();
}
void listPalettes() {
Serial.println(F("{"));
Serial.print(F(" \"count\": "));
Serial.print(paletteCount);
Serial.println(",");
Serial.println(F(" \"results\": ["));
String paletteNames [] = {
"Rainbow",
// "RainbowStripe",
"Ocean",
"Cloud",
"Forest",
"Party",
"Grayscale",
"Heat",
"Lava",
"Ice",
"Random"
};
for (int i = 0; i < paletteCount; i++) {
Serial.print(F(" \""));
Serial.print(paletteNames[i]);
if (i == paletteCount - 1)
Serial.println(F("\""));
else
Serial.println(F("\","));
}
Serial.println(" ]");
Serial.println("}");
}
void setupGrayscalePalette() {
targetPalette = CRGBPalette16(CRGB::Black, CRGB::White);
}
void setupIcePalette() {
targetPalette = CRGBPalette16(CRGB::Black, CRGB::Blue, CRGB::Aqua, CRGB::White);
}
// Oscillators and Emitters
// the oscillators: linear ramps 0-255
byte osci[6];
// sin8(osci) swinging between 0 to MATRIX_WIDTH - 1
byte p[6];
// set the speeds (and by that ratios) of the oscillators here
void MoveOscillators() {
osci[0] = osci[0] + 5;
osci[1] = osci[1] + 2;
osci[2] = osci[2] + 3;
osci[3] = osci[3] + 4;
osci[4] = osci[4] + 1;
if (osci[4] % 2 == 0)
osci[5] = osci[5] + 1; // .5
for (int i = 0; i < 4; i++) {
p[i] = map8(sin8(osci[i]), 0, MATRIX_WIDTH - 1); //why? to keep the result in the range of 0-MATRIX_WIDTH (matrix size)
}
}
// All the caleidoscope functions work directly within the screenbuffer (leds array).
// Draw whatever you like in the area x(0-15) and y (0-15) and then copy it arround.
// rotates the first 16x16 quadrant 3 times onto a 32x32 (+90 degrees rotation for each one)
void Caleidoscope1() {
for (int x = 0; x < MATRIX_CENTER_X; x++) {
for (int y = 0; y < MATRIX_CENTER_Y; y++) {
leds[XY16(MATRIX_WIDTH - 1 - x, y)] = leds[XY16(x, y)];
leds[XY16(MATRIX_WIDTH - 1 - x, MATRIX_HEIGHT - 1 - y)] = leds[XY16(x, y)];
leds[XY16(x, MATRIX_HEIGHT - 1 - y)] = leds[XY16(x, y)];
}
}
}
// mirror the first 16x16 quadrant 3 times onto a 32x32
void Caleidoscope2() {
for (int x = 0; x < MATRIX_CENTER_X; x++) {
for (int y = 0; y < MATRIX_CENTER_Y; y++) {
leds[XY16(MATRIX_WIDTH - 1 - x, y)] = leds[XY16(y, x)];
leds[XY16(x, MATRIX_HEIGHT - 1 - y)] = leds[XY16(y, x)];
leds[XY16(MATRIX_WIDTH - 1 - x, MATRIX_HEIGHT - 1 - y)] = leds[XY16(x, y)];
}
}
}
// copy one diagonal triangle into the other one within a 16x16
void Caleidoscope3() {
for (int x = 0; x <= MATRIX_CENTRE_X && x < MATRIX_HEIGHT; x++) {
for (int y = 0; y <= x && y<MATRIX_HEIGHT; y++) {
leds[XY16(x, y)] = leds[XY16(y, x)];
}
}
}
// copy one diagonal triangle into the other one within a 16x16 (90 degrees rotated compared to Caleidoscope3)
void Caleidoscope4() {
for (int x = 0; x <= MATRIX_CENTRE_X; x++) {
for (int y = 0; y <= MATRIX_CENTRE_Y - x; y++) {
leds[XY16(MATRIX_CENTRE_Y - y, MATRIX_CENTRE_X - x)] = leds[XY16(x, y)];
}
}
}
// copy one diagonal triangle into the other one within a 8x8
void Caleidoscope5() {
for (int x = 0; x < MATRIX_WIDTH / 4; x++) {
for (int y = 0; y <= x && y<=MATRIX_HEIGHT; y++) {
leds[XY16(x, y)] = leds[XY16(y, x)];
}
}
for (int x = MATRIX_WIDTH / 4; x < MATRIX_WIDTH / 2; x++) {
for (int y = MATRIX_HEIGHT / 4; y >= 0; y--) {
leds[XY16(x, y)] = leds[XY16(y, x)];
}
}
}
void Caleidoscope6() {
for (int x = 1; x < MATRIX_CENTER_X; x++) {
leds[XY16(7 - x, 7)] = leds[XY16(x, 0)];
} //a
for (int x = 2; x < MATRIX_CENTER_X; x++) {
leds[XY16(7 - x, 6)] = leds[XY16(x, 1)];
} //b
for (int x = 3; x < MATRIX_CENTER_X; x++) {
leds[XY16(7 - x, 5)] = leds[XY16(x, 2)];
} //c
for (int x = 4; x < MATRIX_CENTER_X; x++) {
leds[XY16(7 - x, 4)] = leds[XY16(x, 3)];
} //d
for (int x = 5; x < MATRIX_CENTER_X; x++) {
leds[XY16(7 - x, 3)] = leds[XY16(x, 4)];
} //e
for (int x = 6; x < MATRIX_CENTER_X; x++) {
leds[XY16(7 - x, 2)] = leds[XY16(x, 5)];
} //f
for (int x = 7; x < MATRIX_CENTER_X; x++) {
leds[XY16(7 - x, 1)] = leds[XY16(x, 6)];
} //g
}
// create a square twister to the left or counter-clockwise
// x and y for center, r for radius
void SpiralStream(int x, int y, int r, byte dimm) {
for (int d = r; d >= 0; d--) { // from the outside to the inside
for (int i = x - d; i <= x + d; i++) {
leds[XY16(i, y - d)] += leds[XY16(i + 1, y - d)]; // lowest row to the right
leds[XY16(i, y - d)].nscale8(dimm);
}
for (int i = y - d; i <= y + d; i++) {
leds[XY16(x + d, i)] += leds[XY16(x + d, i + 1)]; // right colum up
leds[XY16(x + d, i)].nscale8(dimm);
}
for (int i = x + d; i >= x - d; i--) {
leds[XY16(i, y + d)] += leds[XY16(i - 1, y + d)]; // upper row to the left
leds[XY16(i, y + d)].nscale8(dimm);
}
for (int i = y + d; i >= y - d; i--) {
leds[XY16(x - d, i)] += leds[XY16(x - d, i - 1)]; // left colum down
leds[XY16(x - d, i)].nscale8(dimm);
}
}
}
// expand everything within a circle
void Expand(int centerX, int centerY, int radius, byte dimm) {
if (radius == 0)
return;
int currentRadius = radius;
while (currentRadius > 0) {
int a = radius, b = 0;
int radiusError = 1 - a;
int nextRadius = currentRadius - 1;
int nextA = nextRadius - 1, nextB = 0;
int nextRadiusError = 1 - nextA;
while (a >= b)
{
// move them out one pixel on the radius
leds[XY16(a + centerX, b + centerY)] = leds[XY16(nextA + centerX, nextB + centerY)];
leds[XY16(b + centerX, a + centerY)] = leds[XY16(nextB + centerX, nextA + centerY)];
leds[XY16(-a + centerX, b + centerY)] = leds[XY16(-nextA + centerX, nextB + centerY)];
leds[XY16(-b + centerX, a + centerY)] = leds[XY16(-nextB + centerX, nextA + centerY)];
leds[XY16(-a + centerX, -b + centerY)] = leds[XY16(-nextA + centerX, -nextB + centerY)];
leds[XY16(-b + centerX, -a + centerY)] = leds[XY16(-nextB + centerX, -nextA + centerY)];
leds[XY16(a + centerX, -b + centerY)] = leds[XY16(nextA + centerX, -nextB + centerY)];
leds[XY16(b + centerX, -a + centerY)] = leds[XY16(nextB + centerX, -nextA + centerY)];
// dim them
leds[XY16(a + centerX, b + centerY)].nscale8(dimm);
leds[XY16(b + centerX, a + centerY)].nscale8(dimm);
leds[XY16(-a + centerX, b + centerY)].nscale8(dimm);
leds[XY16(-b + centerX, a + centerY)].nscale8(dimm);
leds[XY16(-a + centerX, -b + centerY)].nscale8(dimm);
leds[XY16(-b + centerX, -a + centerY)].nscale8(dimm);
leds[XY16(a + centerX, -b + centerY)].nscale8(dimm);
leds[XY16(b + centerX, -a + centerY)].nscale8(dimm);
b++;
if (radiusError < 0)
radiusError += 2 * b + 1;
else
{
a--;
radiusError += 2 * (b - a + 1);
}
nextB++;
if (nextRadiusError < 0)
nextRadiusError += 2 * nextB + 1;
else
{
nextA--;
nextRadiusError += 2 * (nextB - nextA + 1);
}
}
currentRadius--;
}
}
// give it a linear tail to the right
void StreamRight(byte scale, int fromX = 0, int toX = MATRIX_WIDTH, int fromY = 0, int toY = MATRIX_HEIGHT)
{
for (int x = fromX + 1; x < toX; x++) {
for (int y = fromY; y < toY; y++) {
leds[XY16(x, y)] += leds[XY16(x - 1, y)];
leds[XY16(x, y)].nscale8(scale);
}
}
for (int y = fromY; y < toY; y++)
leds[XY16(0, y)].nscale8(scale);
}
// give it a linear tail to the left
void StreamLeft(byte scale, int fromX = MATRIX_WIDTH, int toX = 0, int fromY = 0, int toY = MATRIX_HEIGHT)
{
for (int x = toX; x < fromX; x++) {
for (int y = fromY; y < toY; y++) {
leds[XY16(x, y)] += leds[XY16(x + 1, y)];
leds[XY16(x, y)].nscale8(scale);
}
}
for (int y = fromY; y < toY; y++)
leds[XY16(0, y)].nscale8(scale);
}
// give it a linear tail downwards
void StreamDown(byte scale)
{
for (int x = 0; x < MATRIX_WIDTH; x++) {
for (int y = 1; y < MATRIX_HEIGHT; y++) {
leds[XY16(x, y)] += leds[XY16(x, y - 1)];
leds[XY16(x, y)].nscale8(scale);
}
}
for (int x = 0; x < MATRIX_WIDTH; x++)
leds[XY16(x, 0)].nscale8(scale);
}
// give it a linear tail upwards
void StreamUp(byte scale)
{
for (int x = 0; x < MATRIX_WIDTH; x++) {
for (int y = MATRIX_HEIGHT - 2; y >= 0; y--) {
leds[XY16(x, y)] += leds[XY16(x, y + 1)];
leds[XY16(x, y)].nscale8(scale);
}
}
for (int x = 0; x < MATRIX_WIDTH; x++)
leds[XY16(x, MATRIX_HEIGHT - 1)].nscale8(scale);
}
// give it a linear tail up and to the left
void StreamUpAndLeft(byte scale)
{
for (int x = 0; x < MATRIX_WIDTH - 1; x++) {
for (int y = MATRIX_HEIGHT - 2; y >= 0; y--) {
leds[XY16(x, y)] += leds[XY16(x + 1, y + 1)];
leds[XY16(x, y)].nscale8(scale);
}
}
for (int x = 0; x < MATRIX_WIDTH; x++)
leds[XY16(x, MATRIX_HEIGHT - 1)].nscale8(scale);
for (int y = 0; y < MATRIX_HEIGHT; y++)
leds[XY16(MATRIX_WIDTH - 1, y)].nscale8(scale);
}
// give it a linear tail up and to the right
void StreamUpAndRight(byte scale)
{
for (int x = 0; x < MATRIX_WIDTH - 1; x++) {
for (int y = MATRIX_HEIGHT - 2; y >= 0; y--) {
leds[XY16(x + 1, y)] += leds[XY16(x, y + 1)];
leds[XY16(x, y)].nscale8(scale);
}
}
// fade the bottom row
for (int x = 0; x < MATRIX_WIDTH; x++)
leds[XY16(x, MATRIX_HEIGHT - 1)].nscale8(scale);
// fade the right column
for (int y = 0; y < MATRIX_HEIGHT; y++)
leds[XY16(MATRIX_WIDTH - 1, y)].nscale8(scale);
}
// just move everything one line down
void MoveDown() {
for (int y = MATRIX_HEIGHT - 1; y > 0; y--) {
for (int x = 0; x < MATRIX_WIDTH; x++) {
leds[XY16(x, y)] = leds[XY16(x, y - 1)];
}
}
}
// just move everything one line down
void VerticalMoveFrom(int start, int end) {
for (int y = end; y > start; y--) {
for (int x = 0; x < MATRIX_WIDTH; x++) {
leds[XY16(x, y)] = leds[XY16(x, y - 1)];
}
}
}
// copy the rectangle defined with 2 points x0, y0, x1, y1
// to the rectangle beginning at x2, x3
void Copy(byte x0, byte y0, byte x1, byte y1, byte x2, byte y2) {
for (int y = y0; y < y1 + 1; y++) {
for (int x = x0; x < x1 + 1; x++) {
leds[XY16(x + x2 - x0, y + y2 - y0)] = leds[XY16(x, y)];
}
}
}
// rotate + copy triangle (MATRIX_CENTER_X*MATRIX_CENTER_X)
void RotateTriangle() {
for (int x = 1; x < MATRIX_CENTER_X; x++) {
for (int y = 0; y < x; y++) {
leds[XY16(x, 7 - y)] = leds[XY16(7 - x, y)];
}
}
}
// mirror + copy triangle (MATRIX_CENTER_X*MATRIX_CENTER_X)
void MirrorTriangle() {
for (int x = 1; x < MATRIX_CENTER_X; x++) {
for (int y = 0; y < x; y++) {
leds[XY16(7 - y, x)] = leds[XY16(7 - x, y)];
}
}
}
// draw static rainbow triangle pattern (MATRIX_CENTER_XxWIDTH / 2)
// (just for debugging)
void RainbowTriangle() {
for (int i = 0; i < MATRIX_CENTER_X; i++) {
for (int j = 0; j <= i; j++) {
Pixel(7 - i, j, i * j * 4);
}
}
}
void BresenhamLine(int x0, int y0, int x1, int y1, byte colorIndex)
{
BresenhamLine(x0, y0, x1, y1, ColorFromCurrentPalette(colorIndex));
}
void BresenhamLine(int x0, int y0, int x1, int y1, CRGB color)
{
int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
int err = dx + dy, e2;
for (;;) {
leds[XY16(x0, y0)] += color;
if (x0 == x1 && y0 == y1) break;
e2 = 2 * err;
if (e2 > dy) {
err += dy;
x0 += sx;
}
if (e2 < dx) {
err += dx;
y0 += sy;
}
}
}
// write one pixel with the specified color from the current palette to coordinates
/*
void Pixel(int x, int y, uint8_t colorIndex) {
leds[XY(x, y)] = ColorFromCurrentPalette(colorIndex);
matrix.drawBackgroundPixelRGB888(x,y, leds[XY(x, y)]); // now draw it?
}
*/
CRGB ColorFromCurrentPalette(uint8_t index = 0, uint8_t brightness = 255, TBlendType blendType = LINEARBLEND) {
return ColorFromPalette(currentPalette, index, brightness, currentBlendType);
}
CRGB HsvToRgb(uint8_t h, uint8_t s, uint8_t v) {
CHSV hsv = CHSV(h, s, v);
CRGB rgb;
hsv2rgb_spectrum(hsv, rgb);
return rgb;
}
void NoiseVariablesSetup() {
noisesmoothing = 200;
noise_x = random16();
noise_y = random16();
noise_z = random16();
noise_scale_x = 6000;
noise_scale_y = 6000;
}
void FillNoise() {
for (uint16_t i = 0; i < MATRIX_WIDTH; i++) {
uint32_t ioffset = noise_scale_x * (i - MATRIX_CENTRE_Y);
for (uint16_t j = 0; j < MATRIX_HEIGHT; j++) {
uint32_t joffset = noise_scale_y * (j - MATRIX_CENTRE_Y);
byte data = inoise16(noise_x + ioffset, noise_y + joffset, noise_z) >> 8;
uint8_t olddata = noise[i][j];
uint8_t newdata = scale8(olddata, noisesmoothing) + scale8(data, 256 - noisesmoothing);
data = newdata;
noise[i][j] = data;
}
}
}
// non leds2 memory version.
void MoveX(byte delta)
{
CRGB tmp = 0;
for (int y = 0; y < MATRIX_HEIGHT; y++)
{
// Shift Left: https://codedost.com/c/arraypointers-in-c/c-program-shift-elements-array-left-direction/
// Computationally heavier but doesn't need an entire leds2 array
tmp = leds[XY16(0, y)];
for (int m = 0; m < delta; m++)
{
// Do this delta time for each row... computationally expensive potentially.
for(int x = 0; x < MATRIX_WIDTH; x++)
{
leds[XY16(x, y)] = leds [XY16(x+1, y)];
}
leds[XY16(MATRIX_WIDTH-1, y)] = tmp;
}
/*
// Shift
for (int x = 0; x < MATRIX_WIDTH - delta; x++) {
leds2[XY(x, y)] = leds[XY(x + delta, y)];
}
// Wrap around
for (int x = MATRIX_WIDTH - delta; x < MATRIX_WIDTH; x++) {
leds2[XY(x, y)] = leds[XY(x + delta - MATRIX_WIDTH, y)];
}
*/
} // end row loop
/*
// write back to leds
for (uint8_t y = 0; y < MATRIX_HEIGHT; y++) {
for (uint8_t x = 0; x < MATRIX_WIDTH; x++) {
leds[XY(x, y)] = leds2[XY(x, y)];
}
}
*/
}
void MoveY(byte delta)
{
CRGB tmp = 0;
for (int x = 0; x < MATRIX_WIDTH; x++)
{
tmp = leds[XY16(x, 0)];
for (int m = 0; m < delta; m++) // moves
{
// Do this delta time for each row... computationally expensive potentially.
for(int y = 0; y < MATRIX_HEIGHT; y++)
{
leds[XY16(x, y)] = leds [XY16(x, y+1)];
}
leds[XY16(x, MATRIX_HEIGHT-1)] = tmp;
}
} // end column loop
} /// MoveY
};
#endif

View file

@ -24,7 +24,7 @@
class PatternAttract : public Drawable { class PatternAttract : public Drawable {
private: private:
const int count = AVAILABLE_BOID_COUNT-1; const int count = 8;
Attractor attractor; Attractor attractor;
public: public:
@ -38,7 +38,7 @@ public:
direction = -1; direction = -1;
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
Boid boid = Boid(VPANEL_W/2, VPANEL_H - i); Boid boid = Boid(15, 31 - i);
boid.mass = 1; // random(0.1, 2); boid.mass = 1; // random(0.1, 2);
boid.velocity.x = ((float) random(40, 50)) / 100.0; boid.velocity.x = ((float) random(40, 50)) / 100.0;
boid.velocity.x *= direction; boid.velocity.x *= direction;
@ -61,7 +61,7 @@ public:
boid.applyForce(force); boid.applyForce(force);
boid.update(); boid.update();
effects.setPixel(boid.location.x, boid.location.y, effects.ColorFromCurrentPalette(boid.colorIndex)); effects.drawBackgroundFastLEDPixelCRGB(boid.location.x, boid.location.y, effects.ColorFromCurrentPalette(boid.colorIndex));
boids[i] = boid; boids[i] = boid;
} }

View file

@ -0,0 +1,73 @@
/*
* Aurora: https://github.com/pixelmatix/aurora
* Copyright (c) 2014 Jason Coon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef PatternBounce_H
class PatternBounce : public Drawable {
private:
static const int count = 32;
PVector gravity = PVector(0, 0.0125);
public:
PatternBounce() {
name = (char *)"Bounce";
}
void start() {
unsigned int colorWidth = 256 / count;
for (int i = 0; i < count; i++) {
Boid boid = Boid(i, 0);
boid.velocity.x = 0;
boid.velocity.y = i * -0.01;
boid.colorIndex = colorWidth * i;
boid.maxforce = 10;
boid.maxspeed = 10;
boids[i] = boid;
}
}
unsigned int drawFrame() {
// dim all pixels on the display
effects.DimAll(170); effects.ShowFrame();
for (int i = 0; i < count; i++) {
Boid boid = boids[i];
boid.applyForce(gravity);
boid.update();
effects.drawBackgroundFastLEDPixelCRGB(boid.location.x, boid.location.y, effects.ColorFromCurrentPalette(boid.colorIndex));
if (boid.location.y >= MATRIX_HEIGHT - 1) {
boid.location.y = MATRIX_HEIGHT - 1;
boid.velocity.y *= -1.0;
}
boids[i] = boid;
}
return 15;
}
};
#endif

View file

@ -0,0 +1,219 @@
/*
* Aurora: https://github.com/pixelmatix/aurora
* Copyright (c) 2014 Jason Coon
*
* Portions of this code are adapted from Noel Bundy's work: https://github.com/TwystNeko/Object3d
* Copyright (c) 2014 Noel Bundy
*
* Portions of this code are adapted from the Petty library: https://code.google.com/p/peggy/
* Copyright (c) 2008 Windell H Oskay. All right reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef PatternCube_H
#define PatternCube_H
class PatternCube : public Drawable {
private:
float focal = 30; // Focal of the camera
int cubeWidth = 28; // Cube size
float Angx = 20.0, AngxSpeed = 0.05; // rotation (angle+speed) around X-axis
float Angy = 10.0, AngySpeed = 0.05; // rotation (angle+speed) around Y-axis
float Ox = 15.5, Oy = 15.5; // position (x,y) of the frame center
int zCamera = 110; // distance from cube to the eye of the camera
// Local vertices
Vertex local[8];
// Camera aligned vertices
Vertex aligned[8];
// On-screen projected vertices
Point screen[8];
// Faces
squareFace face[6];
// Edges
EdgePoint edge[12];
int nbEdges;
// ModelView matrix
float m00, m01, m02, m10, m11, m12, m20, m21, m22;
// constructs the cube
void make(int w)
{
nbEdges = 0;
local[0].set(-w, w, w);
local[1].set(w, w, w);
local[2].set(w, -w, w);
local[3].set(-w, -w, w);
local[4].set(-w, w, -w);
local[5].set(w, w, -w);
local[6].set(w, -w, -w);
local[7].set(-w, -w, -w);
face[0].set(1, 0, 3, 2);
face[1].set(0, 4, 7, 3);
face[2].set(4, 0, 1, 5);
face[3].set(4, 5, 6, 7);
face[4].set(1, 2, 6, 5);
face[5].set(2, 3, 7, 6);
int f, i;
for (f = 0; f < 6; f++)
{
for (i = 0; i < face[f].length; i++)
{
face[f].ed[i] = this->findEdge(face[f].sommets[i], face[f].sommets[i ? i - 1 : face[f].length - 1]);
}
}
}
// finds edges from faces
int findEdge(int a, int b)
{
int i;
for (i = 0; i < nbEdges; i++)
if ((edge[i].x == a && edge[i].y == b) || (edge[i].x == b && edge[i].y == a))
return i;
edge[nbEdges++].set(a, b);
return i;
}
// rotates according to angle x&y
void rotate(float angx, float angy)
{
int i;
float cx = cos(angx);
float sx = sin(angx);
float cy = cos(angy);
float sy = sin(angy);
m00 = cy;
m01 = 0;
m02 = -sy;
m10 = sx * sy;
m11 = cx;
m12 = sx * cy;
m20 = cx * sy;
m21 = -sx;
m22 = cx * cy;
for (i = 0; i < 8; i++)
{
aligned[i].x = m00 * local[i].x + m01 * local[i].y + m02 * local[i].z;
aligned[i].y = m10 * local[i].x + m11 * local[i].y + m12 * local[i].z;
aligned[i].z = m20 * local[i].x + m21 * local[i].y + m22 * local[i].z + zCamera;
screen[i].x = floor((Ox + focal * aligned[i].x / aligned[i].z));
screen[i].y = floor((Oy - focal * aligned[i].y / aligned[i].z));
}
for (i = 0; i < 12; i++)
edge[i].visible = false;
Point *pa, *pb, *pc;
for (i = 0; i < 6; i++)
{
pa = screen + face[i].sommets[0];
pb = screen + face[i].sommets[1];
pc = screen + face[i].sommets[2];
boolean back = ((pb->x - pa->x) * (pc->y - pa->y) - (pb->y - pa->y) * (pc->x - pa->x)) < 0;
if (!back)
{
int j;
for (j = 0; j < 4; j++)
{
edge[face[i].ed[j]].visible = true;
}
}
}
}
byte hue = 0;
int step = 0;
public:
PatternCube() {
name = (char *)"Cube";
make(cubeWidth);
}
unsigned int drawFrame() {
uint8_t blurAmount = beatsin8(2, 10, 255);
#if FASTLED_VERSION >= 3001000
blur2d(effects.leds, MATRIX_WIDTH, MATRIX_HEIGHT, blurAmount);
#else
effects.DimAll(blurAmount); effects.ShowFrame();
#endif
zCamera = beatsin8(2, 100, 140);
AngxSpeed = beatsin8(3, 1, 10) / 100.0f;
AngySpeed = beatcos8(5, 1, 10) / 100.0f;
// Update values
Angx += AngxSpeed;
Angy += AngySpeed;
if (Angx >= TWO_PI)
Angx -= TWO_PI;
if (Angy >= TWO_PI)
Angy -= TWO_PI;
rotate(Angx, Angy);
// Draw cube
int i;
CRGB color = effects.ColorFromCurrentPalette(hue, 128);
// Backface
EdgePoint *e;
for (i = 0; i < 12; i++)
{
e = edge + i;
if (!e->visible) {
matrix.drawLine(screen[e->x].x, screen[e->x].y, screen[e->y].x, screen[e->y].y, color);
}
}
color = effects.ColorFromCurrentPalette(hue, 255);
// Frontface
for (i = 0; i < 12; i++)
{
e = edge + i;
if (e->visible)
{
matrix.drawLine(screen[e->x].x, screen[e->x].y, screen[e->y].x, screen[e->y].y, color);
}
}
step++;
if (step == 8) {
step = 0;
hue++;
}
effects.ShowFrame();
return 20;
}
};
#endif

View file

@ -0,0 +1,116 @@
/*
* Aurora: https://github.com/pixelmatix/aurora
* Copyright (c) 2014 Jason Coon
*
* Portions of this code are adapted from "Funky Noise" by Stefan Petrick: https://github.com/StefanPetrick/FunkyNoise
* Copyright (c) 2014 Stefan Petrick
* http://www.stefan-petrick.de/wordpress_beta
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef PatternElectricMandala_H
class PatternElectricMandala : public Drawable {
private:
// The coordinates for 16-bit noise spaces.
#define NUM_LAYERS 1
// used for the random based animations
int16_t dx;
int16_t dy;
int16_t dz;
int16_t dsx;
int16_t dsy;
public:
PatternElectricMandala() {
name = (char *)"ElectricMandala";
}
void start() {
// set to reasonable values to avoid a black out
noisesmoothing = 200;
// just any free input pin
//random16_add_entropy(analogRead(18));
// fill coordinates with random values
// set zoom levels
noise_x = random16();
noise_y = random16();
noise_z = random16();
noise_scale_x = 6000;
noise_scale_y = 6000;
// for the random movement
dx = random8();
dy = random8();
dz = random8();
dsx = random8();
dsy = random8();
}
unsigned int drawFrame() {
#if FASTLED_VERSION >= 3001000
// a new parameter set every 15 seconds
EVERY_N_SECONDS(15) {
//SetupRandomPalette3();
dy = random16(500) - 250; // random16(2000) - 1000 is pretty fast but works fine, too
dx = random16(500) - 250;
dz = random16(500) - 250;
noise_scale_x = random16(10000) + 2000;
noise_scale_y = random16(10000) + 2000;
}
#endif
noise_y += dy;
noise_x += dx;
noise_z += dz;
effects.FillNoise();
ShowNoiseLayer(0, 1, 0);
effects.Caleidoscope3();
effects.Caleidoscope1();
effects.ShowFrame();
return 30;
}
// show just one layer
void ShowNoiseLayer(byte layer, byte colorrepeat, byte colorshift) {
for (uint16_t i = 0; i < MATRIX_WIDTH; i++) {
for (uint16_t j = 0; j < MATRIX_HEIGHT; j++) {
uint8_t color = noise[i][j];
uint8_t bri = color;
// assign a color depending on the actual palette
CRGB pixel = ColorFromPalette(effects.currentPalette, colorrepeat * (color + colorshift), bri);
effects.leds[XY16(i, j)] = pixel;
}
}
}
};
#endif

View file

@ -0,0 +1,118 @@
/*
* Aurora: https://github.com/pixelmatix/aurora
* Copyright (c) 2014 Jason Coon
*
* Portions of this code are adapted from FastLED Fire2012 example by Mark Kriegsman: https://github.com/FastLED/FastLED/tree/master/examples/Fire2012WithPalette
* Copyright (c) 2013 FastLED
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef PatternFire_H
#define PatternFire_H
#ifndef Effects_H
#include "Effects.h"
#endif
class PatternFire : public Drawable {
private:
public:
PatternFire() {
name = (char *)"Fire";
}
// There are two main parameters you can play with to control the look and
// feel of your fire: COOLING (used in step 1 above), and SPARKING (used
// in step 3 above).
//
// cooling: How much does the air cool as it rises?
// Less cooling = taller flames. More cooling = shorter flames.
// Default 55, suggested range 20-100
int cooling = 100;
// sparking: What chance (out of 255) is there that a new spark will be lit?
// Higher chance = more roaring fire. Lower chance = more flickery fire.
// Default 120, suggested range 50-200.
unsigned int sparking = 100;
unsigned int drawFrame() {
// Add entropy to random number generator; we use a lot of it.
random16_add_entropy( random16());
effects.DimAll(235);
for (int x = 0; x < MATRIX_WIDTH; x++) {
// Step 1. Cool down every cell a little
for (int y = 0; y < MATRIX_HEIGHT; y++) {
int xy = XY(x, y);
heat[xy] = qsub8(heat[xy], random8(0, ((cooling * 10) / MATRIX_HEIGHT) + 2));
}
// Step 2. Heat from each cell drifts 'up' and diffuses a little
for (int y = 0; y < MATRIX_HEIGHT; y++) {
heat[XY(x, y)] = (heat[XY(x, y + 1)] + heat[XY(x, y + 2)] + heat[XY(x, y + 2)]) / 3;
}
// Step 2. Randomly ignite new 'sparks' of heat
if (random8() < sparking) {
// int x = (p[0] + p[1] + p[2]) / 3;
int xy = XY(x, MATRIX_HEIGHT - 1);
heat[xy] = qadd8(heat[xy], random8(160, 255));
}
// Step 4. Map from heat cells to LED colors
for (int y = 0; y < MATRIX_HEIGHT; y++) {
int xy = XY(x, y);
byte colorIndex = heat[xy];
// Recommend that you use values 0-240 rather than
// the usual 0-255, as the last 15 colors will be
// 'wrapping around' from the hot end to the cold end,
// which looks wrong.
colorIndex = scale8(colorIndex, 200);
// override color 0 to ensure a black background?
if (colorIndex != 0)
// effects.leds[xy] = CRGB::Black;
// else
effects.leds[xy] = effects.ColorFromCurrentPalette(colorIndex);
}
}
// Noise
noise_x += 1000;
noise_y += 1000;
noise_z += 1000;
noise_scale_x = 4000;
noise_scale_y = 4000;
effects.FillNoise();
effects.MoveX(2);
effects.MoveFractionalNoiseX(2);
effects.ShowFrame();
return 15;
}
};
#endif

View file

@ -1,235 +0,0 @@
#ifndef FireWork_H
#define FireWork_H
/****************************************************************
* Fireworks Class
****************************************************************/
// Ripped from: https://github.com/lmirel/MorphingClockRemix
const int FIREWORKS = 4; // Number of fireworks
const int FIREWORK_PARTICLES = 32; // Number of particles per firework
const float GRAVITY = 0.03f;
const float baselineSpeed = -1.2f;
const float maxSpeed = -2.0f;
class Firework
{
public:
float x[FIREWORK_PARTICLES];
float y[FIREWORK_PARTICLES];
char lx[FIREWORK_PARTICLES], ly[FIREWORK_PARTICLES];
float xSpeed[FIREWORK_PARTICLES];
float ySpeed[FIREWORK_PARTICLES];
char red;
char blue;
char green;
char alpha;
int framesUntilLaunch;
char particleSize;
bool hasExploded;
Firework(); // Constructor declaration
void initialise();
void move();
void explode();
};
// Constructor implementation
Firework::Firework()
{
initialise();
for (int loop = 0; loop < FIREWORK_PARTICLES; loop++)
{
lx[loop] = 0;
ly[loop] = VPANEL_H + 1; // Push the particle location down off the bottom of the screen
}
}
void Firework::initialise()
{
// Pick an initial x location and random x/y speeds
float xLoc = (rand() % VPANEL_W);
float xSpeedVal = baselineSpeed + (rand() % (int)maxSpeed);
float ySpeedVal = baselineSpeed + (rand() % (int)maxSpeed);
// Set initial x/y location and speeds
for (int loop = 0; loop < FIREWORK_PARTICLES; loop++)
{
x[loop] = xLoc;
y[loop] = VPANEL_H + 1; // Push the particle location down off the bottom of the screen
xSpeed[loop] = xSpeedVal;
ySpeed[loop] = ySpeedVal;
//don't reset these otherwise particles won't be removed
//lx[loop] = 0;
//ly[loop] = LAYER_HEIGHT + 1; // Push the particle location down off the bottom of the screen
}
// Assign a random colour and full alpha (i.e. particle is completely opaque)
red = (rand() % 255);/// (float)RAND_MAX);
green = (rand() % 255); /// (float)RAND_MAX);
blue = (rand() % 255); /// (float)RAND_MAX);
alpha = 50;//max particle frames
// Firework will launch after a random amount of frames between 0 and 400
framesUntilLaunch = ((int)rand() % (VPANEL_H));
// Size of the particle (as thrown to glPointSize) - range is 1.0f to 4.0f
particleSize = 1.0f + ((float)rand() / (float)RAND_MAX) * 3.0f;
// Flag to keep trackof whether the firework has exploded or not
hasExploded = false;
//cout << "Initialised a firework." << endl;
}
void Firework::move()
{
for (int loop = 0; loop < FIREWORK_PARTICLES; loop++)
{
// Once the firework is ready to launch start moving the particles
if (framesUntilLaunch <= 0)
{
//draw black on last known position
//tl->drawPixel (x[loop], y[loop], cc_blk);
lx[loop] = x[loop];
ly[loop] = y[loop];
//
x[loop] += xSpeed[loop];
y[loop] += ySpeed[loop];
ySpeed[loop] += GRAVITY;
}
}
framesUntilLaunch--;
// Once a fireworks speed turns positive (i.e. at top of arc) - blow it up!
if (ySpeed[0] > 0.0f)
{
for (int loop2 = 0; loop2 < FIREWORK_PARTICLES; loop2++)
{
// Set a random x and y speed beteen -4 and + 4
xSpeed[loop2] = -2 + (rand() / (float)RAND_MAX) * 4;
ySpeed[loop2] = -2 + (rand() / (float)RAND_MAX) * 4;
}
//cout << "Boom!" << endl;
hasExploded = true;
}
}
void Firework::explode()
{
for (int loop = 0; loop < FIREWORK_PARTICLES; loop++)
{
// Dampen the horizontal speed by 1% per frame
xSpeed[loop] *= 0.99;
//draw black on last known position (NO LONGER USED tl->dim used instead)
//tl->drawPixel (x[loop], y[loop], cc_blk);
lx[loop] = x[loop];
ly[loop] = y[loop];
//
// Move the particle
x[loop] += xSpeed[loop];
y[loop] += ySpeed[loop];
// Apply gravity to the particle's speed
ySpeed[loop] += GRAVITY;
}
// Fade out the particles (alpha is stored per firework, not per particle)
if (alpha > 0)
{
alpha -= 1;
}
else // Once the alpha hits zero reset the firework
{
initialise();
}
}
/*********************** */
class PatternFirework : public Drawable {
public:
PatternFirework() {
name = (char *)"PatternFirework";
}
void start();
unsigned int drawFrame();
void stop();
private:
// Create our array of fireworks
Firework fw[FIREWORKS];
};
void PatternFirework::start() { }
void PatternFirework::stop() { }
unsigned int PatternFirework::drawFrame()
{
effects.DimAll(250);
CRGB cc_frw;
//display.fillScreen (0);
// Draw fireworks
//cout << "Firework count is: " << Firework::fireworkCount << endl;
for (int loop = 0; loop < FIREWORKS; loop++)
{
for (int particleLoop = 0; particleLoop < FIREWORK_PARTICLES; particleLoop++)
{
// Set colour to yellow on way up, then whatever colour firework should be when exploded
if (fw[loop].hasExploded == false)
{
//glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
cc_frw = CRGB (255, 255, 0);
}
else
{
//glColor4f(fw[loop].red, fw[loop].green, fw[loop].blue, fw[loop].alpha);
//glVertex2f(fw[loop].x[particleLoop], fw[loop].y[particleLoop]);
cc_frw = CRGB (fw[loop].red, fw[loop].green, fw[loop].blue);
}
// Draw the point
//glVertex2f(fw[loop].x[particleLoop], fw[loop].y[particleLoop]);
effects.setPixel (fw[loop].x[particleLoop], fw[loop].y[particleLoop], cc_frw);
// effects.setPixel (fw[loop].lx[particleLoop], fw[loop].ly[particleLoop], 0);
}
// Move the firework appropriately depending on its explosion state
if (fw[loop].hasExploded == false)
{
fw[loop].move();
}
else
{
fw[loop].explode();
}
//
//delay (10);
} // end loop
effects.ShowFrame();
return 20;
} // end drawframe
#endif

View file

@ -36,17 +36,12 @@
#define PatternFlock_H #define PatternFlock_H
class PatternFlock : public Drawable { class PatternFlock : public Drawable {
private:
unsigned long last_update_hue_ms = 0;
unsigned long last_update_predator_ms = 0;
public: public:
PatternFlock() { PatternFlock() {
name = (char *)"Flock"; name = (char *)"Flock";
} }
static const int boidCount = VPANEL_W-1; static const int boidCount = 10;
Boid predator; Boid predator;
PVector wind; PVector wind;
@ -95,8 +90,8 @@ class PatternFlock : public Drawable {
PVector location = boid->location; PVector location = boid->location;
// PVector velocity = boid->velocity; // PVector velocity = boid->velocity;
// backgroundLayer.drawLine(location.x, location.y, location.x - velocity.x, location.y - velocity.y, color); // backgroundLayer.drawLine(location.x, location.y, location.x - velocity.x, location.y - velocity.y, color);
// effects.leds[XY16(location.x, location.y)] += color; // effects.leds[XY(location.x, location.y)] += color;
effects.setPixel(location.x, location.y, color); effects.drawBackgroundFastLEDPixelCRGB(location.x, location.y, color);
if (applyWind) { if (applyWind) {
boid->applyForce(wind); boid->applyForce(wind);
@ -111,17 +106,15 @@ class PatternFlock : public Drawable {
PVector location = predator.location; PVector location = predator.location;
// PVector velocity = predator.velocity; // PVector velocity = predator.velocity;
// backgroundLayer.drawLine(location.x, location.y, location.x - velocity.x, location.y - velocity.y, color); // backgroundLayer.drawLine(location.x, location.y, location.x - velocity.x, location.y - velocity.y, color);
// effects.leds[XY16(location.x, location.y)] += color; // effects.leds[XY(location.x, location.y)] += color;
effects.setPixel(location.x, location.y, color); effects.drawBackgroundFastLEDPixelCRGB(location.x, location.y, color);
} }
if (millis() - last_update_hue_ms > 200) { EVERY_N_MILLIS(200) {
last_update_hue_ms = millis();
hue++; hue++;
} }
if (millis() - last_update_predator_ms > 30000) { EVERY_N_SECONDS(30) {
last_update_predator_ms = millis();
predatorPresent = !predatorPresent; predatorPresent = !predatorPresent;
} }

View file

@ -0,0 +1,92 @@
/*
* Aurora: https://github.com/pixelmatix/aurora
* Copyright (c) 2014 Jason Coon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef PatternFlowField_H
class PatternFlowField : public Drawable {
public:
PatternFlowField() {
name = (char *)"FlowField";
}
uint16_t x;
uint16_t y;
uint16_t z;
uint16_t speed = 1;
uint16_t scale = 26;
static const int count = 40;
byte hue = 0;
void start() {
x = random16();
y = random16();
z = random16();
for (int i = 0; i < count; i++) {
boids[i] = Boid(random(MATRIX_WIDTH), 0);
}
}
unsigned int drawFrame() {
effects.DimAll(240);
// CRGB color = effects.ColorFromCurrentPalette(hue);
for (int i = 0; i < count; i++) {
Boid * boid = &boids[i];
int ioffset = scale * boid->location.x;
int joffset = scale * boid->location.y;
byte angle = inoise8(x + ioffset, y + joffset, z);
boid->velocity.x = (float) sin8(angle) * 0.0078125 - 1.0;
boid->velocity.y = -((float)cos8(angle) * 0.0078125 - 1.0);
boid->update();
effects.drawBackgroundFastLEDPixelCRGB(boid->location.x, boid->location.y, effects.ColorFromCurrentPalette(angle + hue)); // color
if (boid->location.x < 0 || boid->location.x >= MATRIX_WIDTH ||
boid->location.y < 0 || boid->location.y >= MATRIX_HEIGHT) {
boid->location.x = random(MATRIX_WIDTH);
boid->location.y = 0;
}
}
EVERY_N_MILLIS(200) {
hue++;
}
x += speed;
y += speed;
z += speed;
effects.ShowFrame();
return 50;
}
};
#endif

View file

@ -1,55 +0,0 @@
// Codetastic 2024
// ChatGPT was used to create this.
// It sucks.
#ifndef PatternTheMatrix_H
#define PatternTheMatrix_H
// Function to generate a random greenish color for the digital rain
CRGB generateRainColor() {
return CHSV(96 + random(64), 255, 255); // Greenish colors
}
class PatternTheMatrix : public Drawable {
public:
PatternTheMatrix() {
name = (char *)"The Matrix";
}
// Function to draw the digital rain effect
void drawDigitalRain() {
// Shift all the LEDs down by one row
for (int x = 0; x < VPANEL_W ; x++) {
for (int y = VPANEL_H - 1; y > 0; y--) {
effects.leds[XY(x, y)] = effects.leds[XY(x, y - 1)];
}
// Add a new drop at the top of the column randomly
if (random(10) > 7) { // Adjust the probability to control density of rain
effects.leds[XY(x, 0)] = generateRainColor();
} else {
effects.leds[XY(x, 0)] = CRGB::Black;
}
}
}
void start()
{
};
unsigned int drawFrame() {
effects.DimAll(250);
drawDigitalRain();
effects.ShowFrame();
return 0;
}
};
#endif

View file

@ -0,0 +1,51 @@
/*
*
* Aurora: https://github.com/pixelmatix/aurora
* Copyright (c) 2014 Jason Coon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef PatternIncrementalDrift_H
#define PatternIncrementalDrift_H
class PatternIncrementalDrift : public Drawable {
public:
PatternIncrementalDrift() {
name = (char *)"Incremental Drift";
}
unsigned int drawFrame() {
uint8_t dim = beatsin8(2, 230, 250);
effects.DimAll(dim); effects.ShowFrame();
for (int i = 2; i <= MATRIX_WIDTH / 2; i++)
{
CRGB color = effects.ColorFromCurrentPalette((i - 2) * (240 / (MATRIX_WIDTH / 2)));
uint8_t x = beatcos8((17 - i) * 2, MATRIX_CENTER_X - i, MATRIX_CENTER_X + i);
uint8_t y = beatsin8((17 - i) * 2, MATRIX_CENTER_Y - i, MATRIX_CENTER_Y + i);
effects.drawBackgroundFastLEDPixelCRGB(x, y, color);
}
return 0;
}
};
#endif

View file

@ -0,0 +1,63 @@
/*
*
* Aurora: https://github.com/pixelmatix/aurora
* Copyright (c) 2014 Jason Coon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef PatternIncrementalDrift2_H
#define PatternIncrementalDrift2_H
class PatternIncrementalDrift2 : public Drawable {
public:
PatternIncrementalDrift2() {
name = (char *)"Incremental Drift Rose";
}
unsigned int drawFrame() {
uint8_t dim = beatsin8(2, 170, 250);
effects.DimAll(dim); effects.ShowFrame();
for (uint8_t i = 0; i < 32; i++)
{
CRGB color;
uint8_t x = 0;
uint8_t y = 0;
if (i < 16) {
x = beatcos8((i + 1) * 2, i, MATRIX_WIDTH - i);
y = beatsin8((i + 1) * 2, i, MATRIX_HEIGHT - i);
color = effects.ColorFromCurrentPalette(i * 14);
}
else
{
x = beatsin8((32 - i) * 2, MATRIX_WIDTH - i, i + 1);
y = beatcos8((32 - i) * 2, MATRIX_HEIGHT - i, i + 1);
color = effects.ColorFromCurrentPalette((31 - i) * 14);
}
effects.drawBackgroundFastLEDPixelCRGB(x, y, color);
}
return 0;
}
};
#endif

View file

@ -28,35 +28,30 @@ public:
name = (char *)"Infinity"; name = (char *)"Infinity";
} }
void start() {
effects.ClearFrame();
}
unsigned int drawFrame() { unsigned int drawFrame() {
// dim all pixels on the display slightly // dim all pixels on the display slightly
// to 250/255 (98%) of their current brightness // to 250/255 (98%) of their current brightness
//blur2d(effects.leds, VPANEL_W > 255 ? 255 : VPANEL_W, VPANEL_H > 255 ? 255 : VPANEL_H, 250); blur2d(effects.leds, MATRIX_WIDTH > 255 ? 255 : MATRIX_WIDTH, MATRIX_HEIGHT > 255 ? 255 : MATRIX_HEIGHT, 250);
// effects.DimAll(250); effects.ShowFrame(); // effects.DimAll(250); effects.ShowFrame();
// the EffectsLayer class has some sample oscillators // the Effects class has some sample oscillators
// that move from 0 to 255 at different speeds // that move from 0 to 255 at different speeds
effects.MoveOscillators(); effects.MoveOscillators();
// the horizontal position of the head of the infinity sign // the horizontal position of the head of the infinity sign
// oscillates from 0 to the maximum horizontal and back // oscillates from 0 to the maximum horizontal and back
int x = (VPANEL_W - 4) - effects.p[1]; int x = (MATRIX_WIDTH - 1) - effects.p[1];
// the vertical position of the head oscillates // the vertical position of the head oscillates
// from 8 to 23 and back (hard-coded for a 32x32 matrix) // from 8 to 23 and back (hard-coded for a 32x32 matrix)
int y = map8(sin8(effects.osci[3]), 8, VPANEL_H - 8); int y = map8(sin8(effects.osci[3]), 8, 23);
// the hue oscillates from 0 to 255, overflowing back to 0 // the hue oscillates from 0 to 255, overflowing back to 0
byte hue = sin8(effects.osci[5]); byte hue = sin8(effects.osci[5]);
// draw a pixel at x,y using a color from the current palette // draw a pixel at x,y using a color from the current palette
effects.drawTriangle(x,y,x+1,y+1,x+2,y+2,effects.ColorFromCurrentPalette(hue)); effects.Pixel(x, y, hue);
////effects.setPixelFromPaletteIndex(x, y, hue);
effects.ShowFrame(); effects.ShowFrame();
return 30; return 30;

View file

@ -0,0 +1,154 @@
/*
* Aurora: https://github.com/pixelmatix/aurora
* Copyright (c) 2014 Jason Coon
*
* Inspired by 'Space Invader Generator': https://the8bitpimp.wordpress.com/2013/05/07/space-invader-generator
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef PatternInvaders_H
#define PatternInvaders_H
class PatternInvadersSmall : public Drawable {
private:
uint8_t x = 1;
uint8_t y = 1;
public:
PatternInvadersSmall() {
name = (char *)"Invaders Small";
}
void start() {
matrix.fillScreen(0);
}
unsigned int drawFrame() {
CRGB color1 = effects.ColorFromCurrentPalette(random(0, 255));
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 5; j++) {
CRGB color = CRGB::Black;
if (random(0, 2) == 1) color = color1;
effects.drawBackgroundFastLEDPixelCRGB(x + i, y + j, color);
if (i < 2)
effects.drawBackgroundFastLEDPixelCRGB(x + (4 - i), y + j, color);
}
}
x += 6;
if (x > 25) {
x = 1;
y += 6;
}
if (y > 25) y = x = 1;
effects.ShowFrame();
return 125;
}
};
class PatternInvadersMedium : public Drawable {
private:
uint8_t x = 0;
uint8_t y = 0;
public:
PatternInvadersMedium() {
name = (char *)"Invaders Medium";
}
void start() {
matrix.fillScreen(0);
}
unsigned int drawFrame() {
CRGB color1 = effects.ColorFromCurrentPalette(random(0, 255));
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 5; j++) {
CRGB color = CRGB::Black;
if (random(0, 2) == 1) color = color1;
matrix.fillRect(x + (i * 2), y + (j * 2), x + (i * 2 + 1), y + (j * 2 + 1), color);
if (i < 2)
matrix.fillRect(x + (8 - i * 2), y + (j * 2), x + (9 - i * 2), y + (j * 2 + 1), color);
}
}
x += 11;
if (x > 22) {
x = 0;
y += 11;
}
if (y > 22) y = x = 0;
effects.ShowFrame();
return 500;
}
};
class PatternInvadersLarge : public Drawable {
private:
public:
PatternInvadersLarge() {
name = (char *)"Invaders Large";
}
void start() {
matrix.fillScreen(0);
}
unsigned int drawFrame() {
matrix.fillScreen(0);
CRGB color1 = effects.ColorFromCurrentPalette(random(0, 255));
for (int x = 0; x < 3; x++) {
for (int y = 0; y < 5; y++) {
CRGB color = CRGB::Black;
if (random(0, 2) == 1) {
color = color1;
}
matrix.fillRect(1 + x * 6, 1 + y * 6, 5 + x * 6, 5 + y * 6, color);
if (x < 2)
matrix.fillRect(1 + (4 - x) * 6, 1 + y * 6, 5 + (4 - x) * 6, 5 + y * 6, color);
}
}
effects.ShowFrame();
return 2000;
}
};
#endif

View file

@ -1,129 +0,0 @@
#ifndef JuliaSet_H
#define JuliaSet_H
// Codetastic 2024
#define USE_FLOATHACK // To boost float performance, comment if this doesn't work.
// inspired by
// https://en.wikipedia.org/wiki/Fast_inverse_square_root
#ifdef USE_FLOATHACK
// cast float as int32_t
int32_t intfloat(float n){ return *(int32_t *)&n; }
// cast int32_t as float
float floatint(int32_t n){ return *(float *)&n; }
// fast approx sqrt(x)
float floatsqrt(float n){ return floatint(0x1fbb4000+(intfloat(n)>>1)); }
// fast approx 1/x
float floatinv(float n){ return floatint(0x7f000000-intfloat(n)); }
// fast approx log2(x)
float floatlog2(float n){ return (float)((intfloat(n)<<1)-0x7f000000)*5.9604645e-08f; }
#else
float floatinv(float n){ return 1.f/n;}
float floatsqrt(float n){ return std::sqrt(n); }
float floatlog2(float n){ return std::log2f(n); }
#endif
////////////////////////////////////////
// Escape time mandelbrot set function,
// with arbitrary start point zx, zy
// and arbitrary seed point ax, ay
//
// For julia set
// zx = pos_x, zy = pos_y;
// ax = seed_x, ay = seed_y;
//
// For mandelbrot set
// zx = 0, zy = 0;
// ax = pos_x, ay = pos_y;
//
const float bailOut = 4; // Escape radius
const int32_t itmult = 1<<10; // Color speed
//
// https://en.wikipedia.org/wiki/Mandelbrot_set
int32_t iteratefloat(float ax, float ay, float zx, float zy, uint16_t mxIT) {
float zzl = 0;
for (int it = 0; it<mxIT; it++) {
float zzx = zx * zx;
float zzy = zy * zy;
// is the point is escaped?
if(zzx+zzy>=bailOut){
if(it>0){
// calculate smooth coloring
float zza = floatlog2(zzl);
float zzb = floatlog2(zzx+zzy);
float zzc = floatlog2(bailOut);
float zzd = (zzc-zza)*floatinv(zzb-zza);
return it*itmult+zzd*itmult;
}
};
// z -> z*z + c
zy = 2.f*zx*zy+ay;
zx = zzx-zzy+ax;
zzl = zzx+zzy;
}
return 0;
}
class PatternJuliaSet : public Drawable {
private:
float sint[256]; // precalculated sin table, for performance reasons
public:
PatternJuliaSet() {
name = (char *)"Julia Set";
}
void start() {
for(int i=0;i<256;i++){
sint[i] = sinf(i/256.f*2.f*PI);
}
}
// Palette color taken from:
// https://editor.p5js.org/Kouzerumatsukite/sketches/DwTiq9D01
// color palette originally made by piano_miles, written in p5js
// hsv2rgb(IT, cos(4096*it)/2+0.5, 1-sin(2048*it)/2-0.5)
void drawPixelPalette(int x, int y, uint32_t m){
float r = 0.f, g = 0.f, b = 0.f;
if(m){
char n = m>> 4 ;
float l =abs(sint[m>> 2&255] )*255.f ;
float s = (sint[m &255]+ 1.f)*0.5f ;
r = (max(min(sint[n &255]+0.5f,1.f),0.f)*s+(1-s))*l;
g = (max(min(sint[n+ 85&255]+0.5f,1.f),0.f)*s+(1-s))*l;
b = (max(min(sint[n+170&255]+0.5f,1.f),0.f)*s+(1-s))*l;
}
effects.setPixel(x,y,CRGB(r,g,b));
}
unsigned int drawFrame() {
uint32_t lastMicros = micros();
double t = (double)lastMicros/8000000;
double k = sin(t*3.212/2)*sin(t*3.212/2)/16+1;
float cosk = (k-cos(t))/2;
float xoff = (cos(t)*cosk+k/2-0.25);
float yoff = (sin(t)*cosk );
for(uint8_t y=0;y<VPANEL_H;y++){
for(uint8_t x=0;x<VPANEL_W;x++){
uint32_t itcount = iteratefloat(xoff,yoff,((x-64)+1)/64.f,(y)/64.f,64);
uint32_t itcolor = itcount?floatsqrt(itcount)*4+t*1024:0;
drawPixelPalette(x,y,itcolor);
}
}
blur2d(effects.leds, VPANEL_W, VPANEL_H, 64);
effects.ShowFrame();
return 0;
}
};
#endif

View file

@ -0,0 +1,129 @@
/*
* Aurora: https://github.com/pixelmatix/aurora
* Copyright (c) 2014 Jason Coon
*
* Portions of this code are adapted from Andrew: http://pastebin.com/f22bfe94d
* which, in turn, was "Adapted from the Life example on the Processing.org site"
*
* Made much more colorful by J.B. Langston: https://github.com/jblang/aurora/commit/6db5a884e3df5d686445c4f6b669f1668841929b
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef PatternLife_H
#define PatternLife_H
class Cell {
public:
byte alive : 1;
byte prev : 1;
byte hue: 6;
byte brightness;
};
class PatternLife : public Drawable {
private:
Cell world[MATRIX_WIDTH][MATRIX_HEIGHT];
unsigned int density = 50;
int generation = 0;
void randomFillWorld() {
for (int i = 0; i < MATRIX_WIDTH; i++) {
for (int j = 0; j < MATRIX_HEIGHT; j++) {
if (random(100) < density) {
world[i][j].alive = 1;
world[i][j].brightness = 255;
}
else {
world[i][j].alive = 0;
world[i][j].brightness = 0;
}
world[i][j].prev = world[i][j].alive;
world[i][j].hue = 0;
}
}
}
int neighbours(int x, int y) {
return (world[(x + 1) % MATRIX_WIDTH][y].prev) +
(world[x][(y + 1) % MATRIX_HEIGHT].prev) +
(world[(x + MATRIX_WIDTH - 1) % MATRIX_WIDTH][y].prev) +
(world[x][(y + MATRIX_HEIGHT - 1) % MATRIX_HEIGHT].prev) +
(world[(x + 1) % MATRIX_WIDTH][(y + 1) % MATRIX_HEIGHT].prev) +
(world[(x + MATRIX_WIDTH - 1) % MATRIX_WIDTH][(y + 1) % MATRIX_HEIGHT].prev) +
(world[(x + MATRIX_WIDTH - 1) % MATRIX_WIDTH][(y + MATRIX_HEIGHT - 1) % MATRIX_HEIGHT].prev) +
(world[(x + 1) % MATRIX_WIDTH][(y + MATRIX_HEIGHT - 1) % MATRIX_HEIGHT].prev);
}
public:
PatternLife() {
name = (char *)"Life";
}
unsigned int drawFrame() {
if (generation == 0) {
effects.ClearFrame();
randomFillWorld();
}
// Display current generation
for (int i = 0; i < MATRIX_WIDTH; i++) {
for (int j = 0; j < MATRIX_HEIGHT; j++) {
effects.leds[XY(i, j)] = effects.ColorFromCurrentPalette(world[i][j].hue * 4, world[i][j].brightness);
}
}
// Birth and death cycle
for (int x = 0; x < MATRIX_WIDTH; x++) {
for (int y = 0; y < MATRIX_HEIGHT; y++) {
// Default is for cell to stay the same
if (world[x][y].brightness > 0 && world[x][y].prev == 0)
world[x][y].brightness *= 0.9;
int count = neighbours(x, y);
if (count == 3 && world[x][y].prev == 0) {
// A new cell is born
world[x][y].alive = 1;
world[x][y].hue += 2;
world[x][y].brightness = 255;
} else if ((count < 2 || count > 3) && world[x][y].prev == 1) {
// Cell dies
world[x][y].alive = 0;
}
}
}
// Copy next generation into place
for (int x = 0; x < MATRIX_WIDTH; x++) {
for (int y = 0; y < MATRIX_HEIGHT; y++) {
world[x][y].prev = world[x][y].alive;
}
}
generation++;
if (generation >= 256)
generation = 0;
effects.ShowFrame();
return 60;
}
};
#endif

View file

@ -0,0 +1,264 @@
/*
* Aurora: https://github.com/pixelmatix/aurora
* Copyright (c) 2014 Jason Coon
*
* Many thanks to Jamis Buck for the documentation of the Growing Tree maze generation algorithm: http://weblog.jamisbuck.org/2011/1/27/maze-generation-growing-tree-algorithm
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef PatternMaze_H
#define PatternMaze_H
class PatternMaze : public Drawable {
private:
enum Directions {
None = 0,
Up = 1,
Down = 2,
Left = 4,
Right = 8,
};
struct Point{
int x;
int y;
static Point New(int x, int y) {
Point point;
point.x = x;
point.y = y;
return point;
}
Point Move(Directions direction) {
switch (direction)
{
case Up:
return New(x, y - 1);
case Down:
return New(x, y + 1);
case Left:
return New(x - 1, y);
case Right:
default:
return New(x + 1, y);
}
}
static Directions Opposite(Directions direction) {
switch (direction) {
case Up:
return Down;
case Down:
return Up;
case Left:
return Right;
case Right:
default:
return Left;
}
}
};
// int width = 16;
// int height = 16;
static const int width = MATRIX_WIDTH / 2;
static const int height = MATRIX_HEIGHT / 2;
Directions grid[width][height];
Point point;
Point cells[256];
int cellCount = 0;
int algorithm = 0;
int algorithmCount = 1;
byte hue = 0;
byte hueOffset = 0;
Directions directions[4] = { Up, Down, Left, Right };
void removeCell(int index) {// shift cells after index down one
for (int i = index; i < cellCount - 1; i++) {
cells[i] = cells[i + 1];
}
cellCount--;
}
void shuffleDirections() {
for (int a = 0; a < 4; a++)
{
int r = random(a, 4);
Directions temp = directions[a];
directions[a] = directions[r];
directions[r] = temp;
}
}
Point createPoint(int x, int y) {
Point point;
point.x = x;
point.y = y;
return point;
}
CRGB chooseColor(int index) {
byte h = index + hueOffset;
switch (algorithm) {
case 0:
default:
return effects.ColorFromCurrentPalette(h);
case 1:
return effects.ColorFromCurrentPalette(hue++);
}
}
int chooseIndex(int max) {
switch (algorithm) {
case 0:
default:
// choose newest (recursive backtracker)
return max - 1;
case 1:
// choose random(Prim's)
return random(max);
// case 2:
// // choose oldest (not good, so disabling)
// return 0;
}
}
void generateMaze() {
while (cellCount > 1) {
drawNextCell();
}
}
void drawNextCell() {
int index = chooseIndex(cellCount);
if (index < 0)
return;
point = cells[index];
Point imagePoint = createPoint(point.x * 2, point.y * 2);
//effects.drawBackgroundFastLEDPixelCRGB(imagePoint.x, imagePoint.y, CRGB(CRGB::Gray));
shuffleDirections();
CRGB color = chooseColor(index);
for (int i = 0; i < 4; i++) {
Directions direction = directions[i];
Point newPoint = point.Move(direction);
if (newPoint.x >= 0 && newPoint.y >= 0 && newPoint.x < width && newPoint.y < height && grid[newPoint.y][newPoint.x] == None) {
grid[point.y][point.x] = (Directions) ((int) grid[point.y][point.x] | (int) direction);
grid[newPoint.y][newPoint.x] = (Directions) ((int) grid[newPoint.y][newPoint.x] | (int) point.Opposite(direction));
Point newImagePoint = imagePoint.Move(direction);
effects.drawBackgroundFastLEDPixelCRGB(newImagePoint.x, newImagePoint.y, color);
cellCount++;
cells[cellCount - 1] = newPoint;
index = -1;
break;
}
}
if (index > -1) {
Point finishedPoint = cells[index];
imagePoint = createPoint(finishedPoint.x * 2, finishedPoint.y * 2);
effects.drawBackgroundFastLEDPixelCRGB(imagePoint.x, imagePoint.y, color);
removeCell(index);
}
}
public:
PatternMaze() {
name = (char *)"Maze";
}
unsigned int drawFrame() {
if (cellCount < 1) {
effects.ClearFrame();
// reset the maze grid
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
grid[y][x] = None;
}
}
int x = random(width);
int y = random(height);
cells[0] = createPoint(x, y);
cellCount = 1;
hue = 0;
hueOffset = random(0, 256);
}
drawNextCell();
if (cellCount < 1) {
algorithm++;
if (algorithm >= algorithmCount)
algorithm = 0;
return 0;
}
effects.ShowFrame();
return 0;
}
void start() {
effects.ClearFrame();
cellCount = 0;
hue = 0;
}
};
#endif

View file

@ -0,0 +1,73 @@
/*
* Aurora: https://github.com/pixelmatix/aurora
* Copyright (c) 2014 Jason Coon
*
* Munch pattern created by J.B. Langston: https://github.com/jblang/aurora/blob/master/PatternMunch.h
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef PatternMunch_H
#define PatternMunch_H
class PatternMunch : public Drawable {
private:
byte count = 0;
byte dir = 1;
byte flip = 0;
byte generation = 0;
public:
PatternMunch() {
name = (char *)"Munch";
}
unsigned int drawFrame() {
for (uint16_t x = 0; x < MATRIX_WIDTH; x++) {
for (uint16_t y = 0; y < MATRIX_HEIGHT; y++) {
effects.leds[XY16(x, y)] = (x ^ y ^ flip) < count ? effects.ColorFromCurrentPalette(((x ^ y) << 2) + generation) : CRGB::Black;
// The below is more pleasant
// effects.leds[XY(x, y)] = effects.ColorFromCurrentPalette(((x ^ y) << 2) + generation) ;
}
}
count += dir;
if (count <= 0 || count >= MATRIX_WIDTH) {
dir = -dir;
}
if (count <= 0) {
if (flip == 0)
flip = MATRIX_WIDTH-1;
else
flip = 0;
}
generation++;
// show it ffs!
effects.ShowFrame();
return 60;
}
};
#endif

View file

@ -0,0 +1,338 @@
/*
* Aurora: https://github.com/pixelmatix/aurora
* Copyright (c) 2014 Jason Coon
*
* Portions of this code are adapted from "Noise Smearing" by Stefan Petrick: https://gist.githubusercontent.com/embedded-creations/5cd47d83cb0e04f4574d/raw/ebf6a82b4755d55cfba3bf6598f7b19047f89daf/NoiseSmearing.ino
* Copyright (c) 2014 Stefan Petrick
* http://www.stefan-petrick.de/wordpress_beta
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef PatternNoiseSmearing_H
#define PatternNoiseSmearing_H
byte patternNoiseSmearingHue = 0;
class PatternMultipleStream : public Drawable {
public:
PatternMultipleStream() {
name = (char *)"MultipleStream";
}
// this pattern draws two points to the screen based on sin/cos if a counter
// (comment out NoiseSmearWithRadius to see pattern of pixels)
// these pixels are smeared by a large radius, giving a lot of movement
// the image is dimmed before each drawing to not saturate the screen with color
// the smear has an offset so the pixels usually have a trail leading toward the upper left
unsigned int drawFrame() {
static unsigned long counter = 0;
#if 0
// this counter lets put delays between each frame and still get the same animation
counter++;
#else
// this counter updates in real time and can't be slowed down for debugging
counter = millis() / 10;
#endif
byte x1 = 4 + sin8(counter * 2) / 10;
byte x2 = 8 + sin8(counter * 2) / 16;
byte y2 = 8 + cos8((counter * 2) / 3) / 16;
effects.leds[XY(x1, x2)] = effects.ColorFromCurrentPalette(patternNoiseSmearingHue);
effects.leds[XY(x2, y2)] = effects.ColorFromCurrentPalette(patternNoiseSmearingHue + 128);
// Noise
noise_x += 1000;
noise_y += 1000;
noise_scale_x = 4000;
noise_scale_y = 4000;
effects.FillNoise();
effects.MoveX(8);
effects.MoveFractionalNoiseX();
effects.MoveY(8);
effects.MoveFractionalNoiseY();
patternNoiseSmearingHue++;
return 0;
}
};
class PatternMultipleStream2 : public Drawable {
public:
PatternMultipleStream2() {
name = (char *)"MultipleStream2";
}
unsigned int drawFrame() {
effects.DimAll(230); effects.ShowFrame();
byte xx = 4 + sin8(millis() / 9) / 10;
byte yy = 4 + cos8(millis() / 10) / 10;
effects.leds[XY(xx, yy)] += effects.ColorFromCurrentPalette(patternNoiseSmearingHue);
xx = 8 + sin8(millis() / 10) / 16;
yy = 8 + cos8(millis() / 7) / 16;
effects.leds[XY(xx, yy)] += effects.ColorFromCurrentPalette(patternNoiseSmearingHue + 80);
effects.leds[XY(15, 15)] += effects.ColorFromCurrentPalette(patternNoiseSmearingHue + 160);
noise_x += 1000;
noise_y += 1000;
noise_z += 1000;
noise_scale_x = 4000;
noise_scale_y = 4000;
effects.FillNoise();
effects.MoveX(3);
effects.MoveFractionalNoiseY(4);
effects.MoveY(3);
effects.MoveFractionalNoiseX(4);
patternNoiseSmearingHue++;
return 0;
}
};
class PatternMultipleStream3 : public Drawable {
public:
PatternMultipleStream3() {
name = (char *)"MultipleStream3";
}
unsigned int drawFrame() {
//CLS();
effects.DimAll(235); effects.ShowFrame();
for (uint8_t i = 3; i < 32; i = i + 4) {
effects.leds[XY(i, 15)] += effects.ColorFromCurrentPalette(i * 8);
}
// Noise
noise_x += 1000;
noise_y += 1000;
noise_z += 1000;
noise_scale_x = 4000;
noise_scale_y = 4000;
effects.FillNoise();
effects.MoveX(3);
effects.MoveFractionalNoiseY(4);
effects.MoveY(3);
effects.MoveFractionalNoiseX(4);
effects.ShowFrame();
return 1;
}
};
class PatternMultipleStream4 : public Drawable {
public:
PatternMultipleStream4() {
name = (char *)"MultipleStream4";
}
unsigned int drawFrame() {
//CLS();
effects.DimAll(235); effects.ShowFrame();
effects.leds[XY(15, 15)] += effects.ColorFromCurrentPalette(patternNoiseSmearingHue);
// Noise
noise_x += 1000;
noise_y += 1000;
noise_scale_x = 4000;
noise_scale_y = 4000;
effects.FillNoise();
effects.MoveX(8);
effects.MoveFractionalNoiseX();
effects.MoveY(8);
effects.MoveFractionalNoiseY();
patternNoiseSmearingHue++;
return 0;
}
};
class PatternMultipleStream5 : public Drawable {
public:
PatternMultipleStream5() {
name = (char *)"MultipleStream5";
}
unsigned int drawFrame() {
//CLS();
effects.DimAll(235); effects.ShowFrame();
for (uint8_t i = 3; i < 32; i = i + 4) {
effects.leds[XY(i, 31)] += effects.ColorFromCurrentPalette(i * 8);
}
// Noise
noise_x += 1000;
noise_y += 1000;
noise_z += 1000;
noise_scale_x = 4000;
noise_scale_y = 4000;
effects.FillNoise();
effects.MoveX(3);
effects.MoveFractionalNoiseY(4);
effects.MoveY(4);
effects.MoveFractionalNoiseX(4);
return 0;
}
};
class PatternMultipleStream8 : public Drawable {
public:
PatternMultipleStream8() {
name = (char *)"MultipleStream8";
}
unsigned int drawFrame() {
effects.DimAll(230); effects.ShowFrame();
// draw grid of rainbow dots on top of the dimmed image
for (uint8_t y = 1; y < 32; y = y + 6) {
for (uint8_t x = 1; x < 32; x = x + 6) {
effects.leds[XY(x, y)] += effects.ColorFromCurrentPalette((x * y) / 4);
}
}
// Noise
noise_x += 1000;
noise_y += 1000;
noise_z += 1000;
noise_scale_x = 4000;
noise_scale_y = 4000;
effects.FillNoise();
effects.MoveX(3);
effects.MoveFractionalNoiseX(4);
effects.MoveY(3);
effects.MoveFractionalNoiseY(4);
return 0;
}
};
class PatternPaletteSmear : public Drawable {
public:
PatternPaletteSmear() {
name = (char *)"PaletteSmear";
}
unsigned int drawFrame() {
effects.DimAll(170); effects.ShowFrame();
// draw a rainbow color palette
for (uint8_t y = 0; y < MATRIX_HEIGHT; y++) {
for (uint8_t x = 0; x < MATRIX_WIDTH; x++) {
effects.leds[XY(x, y)] += effects.ColorFromCurrentPalette(x * 8, y * 8 + 7);
}
}
// Noise
noise_x += 1000;
noise_y += 1000;
noise_scale_x = 4000;
noise_scale_y = 4000;
effects.FillNoise();
effects.MoveX(3);
//effects.MoveFractionalNoiseY(4);
effects.MoveY(3);
effects.MoveFractionalNoiseX(4);
effects.ShowFrame();
return 0;
}
};
class PatternRainbowFlag : public Drawable {
public:
PatternRainbowFlag() {
name = (char *)"RainbowFlag";
}
unsigned int drawFrame() {
effects.DimAll(10); effects.ShowFrame();
CRGB rainbow[7] = {
CRGB::Red,
CRGB::Orange,
CRGB::Yellow,
CRGB::Green,
CRGB::Blue,
CRGB::Violet
};
uint8_t y = 2;
for (uint8_t c = 0; c < 6; c++) {
for (uint8_t j = 0; j < 5; j++) {
for (uint8_t x = 0; x < MATRIX_WIDTH; x++) {
effects.leds[XY(x, y)] += rainbow[c];
}
y++;
if (y >= MATRIX_HEIGHT)
break;
}
}
// Noise
noise_x += 1000;
noise_y += 1000;
noise_scale_x = 4000;
noise_scale_y = 4000;
effects.FillNoise();
effects.MoveX(3);
effects.MoveFractionalNoiseY(4);
effects.MoveY(3);
effects.MoveFractionalNoiseX(4);
return 0;
}
};
#endif

View file

@ -0,0 +1,66 @@
/*
*
* Inspired by and based on a loading animation for Prismata by Lunarch Studios:
* http://www.reddit.com/r/gifs/comments/2on8si/connecting_to_server_so_mesmerizing/cmow0sz
*
* Lunarch Studios Inc. hereby publishes the Actionscript 3 source code pasted in this
* comment under the Creative Commons CC0 1.0 Universal Public Domain Dedication.
* Lunarch Studios Inc. waives all rights to the work worldwide under copyright law,
* including all related and neighboring rights, to the extent allowed by law.
* You can copy, modify, distribute and perform the work, even for commercial purposes,
* all without asking permission.
*
* Aurora: https://github.com/pixelmatix/aurora
* Copyright (c) 2014 Jason Coon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef PatternPendulumWave_H
#define PatternPendulumWave_H
#define WAVE_BPM 25
#define AMP_BPM 2
#define SKEW_BPM 4
#define WAVE_TIMEMINSKEW MATRIX_WIDTH/8
#define WAVE_TIMEMAXSKEW MATRIX_WIDTH/2
class PatternPendulumWave : public Drawable {
public:
PatternPendulumWave() {
name = (char *)"Pendulum Wave";
}
unsigned int drawFrame() {
effects.ClearFrame();
for (int x = 0; x < MATRIX_WIDTH; ++x)
{
uint16_t amp = beatsin16(AMP_BPM, MATRIX_HEIGHT/8, MATRIX_HEIGHT-1);
uint16_t offset = (MATRIX_HEIGHT - beatsin16(AMP_BPM, 0, MATRIX_HEIGHT))/2;
uint8_t y = beatsin16(WAVE_BPM, 0, amp, x*beatsin16(SKEW_BPM, WAVE_TIMEMINSKEW, WAVE_TIMEMAXSKEW)) + offset;
effects.drawBackgroundFastLEDPixelCRGB(x, y, effects.ColorFromCurrentPalette(x * 7));
}
effects.ShowFrame();
return 20;
}
};
#endif

View file

@ -0,0 +1,66 @@
/*
* Aurora: https://github.com/pixelmatix/aurora
* Copyright (c) 2014 Jason Coon
*
* Portions of this code are adapted from LedEffects Plasma by Robert Atkins: https://bitbucket.org/ratkins/ledeffects/src/26ed3c51912af6fac5f1304629c7b4ab7ac8ca4b/Plasma.cpp?at=default
* Copyright (c) 2013 Robert Atkins
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef PatternPlasma_H
#define PatternPlasma_H
class PatternPlasma : public Drawable {
private:
int time = 0;
int cycles = 0;
public:
PatternPlasma() {
name = (char *)"Plasma";
}
unsigned int drawFrame() {
for (int x = 0; x < MATRIX_WIDTH; x++) {
for (int y = 0; y < MATRIX_HEIGHT; y++) {
int16_t v = 0;
uint8_t wibble = sin8(time);
v += sin16(x * wibble * 2 + time);
v += cos16(y * (128 - wibble) * 2 + time);
v += sin16(y * x * cos8(-time) / 2);
effects.Pixel(x, y, (v >> 8) + 127);
}
}
time += 1;
cycles++;
if (cycles >= 2048) {
time = 0;
cycles = 0;
}
effects.ShowFrame();
return 30;
}
};
#endif

View file

@ -0,0 +1,82 @@
/*
* Aurora: https://github.com/pixelmatix/aurora
* Copyright (c) 2014 Jason Coon
*
* Based at least in part on someone else's work that I can no longer find.
* Please let me know if you recognize any of this code!
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef PatternPulse_H
#define PatternPulse_H
class PatternPulse : public Drawable {
private:
int hue;
int centerX = 0;
int centerY = 0;
int step = -1;
int maxSteps = 16;
float fadeRate = 0.8;
int diff;
public:
PatternPulse() {
name = (char *)"Pulse";
}
unsigned int drawFrame() {
effects.DimAll(235);
if (step == -1) {
centerX = random(32);
centerY = random(32);
hue = random(256); // 170;
step = 0;
}
if (step == 0) {
matrix.drawCircle(centerX, centerY, step, effects.ColorFromCurrentPalette(hue));
step++;
}
else {
if (step < maxSteps) {
// initial pulse
matrix.drawCircle(centerX, centerY, step, effects.ColorFromCurrentPalette(hue, pow(fadeRate, step - 2) * 255));
// secondary pulse
if (step > 3) {
matrix.drawCircle(centerX, centerY, step - 3, effects.ColorFromCurrentPalette(hue, pow(fadeRate, step - 2) * 255));
}
step++;
}
else {
step = -1;
}
}
effects.standardNoiseSmearing();
effects.ShowFrame();
return 30;
}
};
#endif

View file

@ -26,7 +26,6 @@ class PatternRadar : public Drawable {
private: private:
byte theta = 0; byte theta = 0;
byte hueoffset = 0; byte hueoffset = 0;
unsigned long last_update_hue_ms = 0;
public: public:
PatternRadar() { PatternRadar() {
@ -36,16 +35,15 @@ class PatternRadar : public Drawable {
unsigned int drawFrame() { unsigned int drawFrame() {
effects.DimAll(254); effects.ShowFrame(); effects.DimAll(254); effects.ShowFrame();
for (int offset = 0; offset < effects.getCenterX(); offset++) { for (int offset = 0; offset < MATRIX_CENTER_X; offset++) {
byte hue = 255 - (offset * 16 + hueoffset); byte hue = 255 - (offset * 16 + hueoffset);
CRGB color = effects.ColorFromCurrentPalette(hue); CRGB color = effects.ColorFromCurrentPalette(hue);
uint8_t x = effects.mapcos8(theta, offset, (VPANEL_W - 1) - offset); uint8_t x = mapcos8(theta, offset, (MATRIX_WIDTH - 1) - offset);
uint8_t y = effects.mapsin8(theta, offset, (VPANEL_H - 1) - offset); uint8_t y = mapsin8(theta, offset, (MATRIX_HEIGHT - 1) - offset);
uint16_t xy = XY16(x, y); uint16_t xy = XY(x, y);
effects.leds[xy] = color; effects.leds[xy] = color;
if (millis() - last_update_hue_ms > 25) { EVERY_N_MILLIS(25) {
last_update_hue_ms = millis();
theta += 2; theta += 2;
hueoffset += 1; hueoffset += 1;
} }

View file

@ -1,93 +0,0 @@
#ifndef PatternRain_H
#define PatternRain_H
// Codetastic 2024
struct rainDrop {
uint8_t x;
uint8_t y;
CRGB colour;
};
#define MAX_RAINDROPS 128
class PatternRain : public Drawable {
public:
PatternRain()
{
name = (char *)"PatternRain";
}
void start() {
buffer = (uint16_t *) malloc(((VPANEL_W*VPANEL_H)+1)*sizeof(uint16_t)); // always alloc an extra amount for XY
}
void stop() {
free(buffer);
}
unsigned int drawFrame()
{
rain(32, 255, 224, 240, CRGB::Green);
effects.ShowFrame();
return 45; // 1000/45 frames per secton
}
private:
struct rainDrop rainDrops[MAX_RAINDROPS];
int rainDropPos = 0;
uint16_t* buffer = NULL; // buffer of number
void rain(byte backgroundDepth, byte maxBrightness, byte spawnFreq, byte tailLength, CRGB rainColor)
{
CRGBPalette16 rain_p( CRGB::Black, rainColor );
// Dim routine
for (int16_t i = 0; i < VPANEL_W; i++) {
for (int16_t j = 0; j < VPANEL_H; j++) {
uint16_t xy = XY16(i, j);
effects.leds[xy].nscale8(tailLength);
}
}
// Genrate a new raindrop if the randomness says we should
if (random(255) < spawnFreq) {
// Find a spare raindrop slot
for (int d = 0; d < MAX_RAINDROPS; d++) {
// This raindrop is done with, it has... dropped
if (rainDrops[d].y >= VPANEL_H ) // not currently in use
{
rainDrops[d].colour = ColorFromPalette(rain_p, random(backgroundDepth, maxBrightness));
rainDrops[d].x = random(VPANEL_W-1);
rainDrops[d].y = 0;
break; // exit until next time.
}
}
} // end random spawn
// Iterate through all the rainDrops, draw the drop pixel on the layer
for (int d = 0; d < MAX_RAINDROPS; d++) {
effects.setPixel(rainDrops[d].x, rainDrops[d].y++, rainDrops[d].colour);
}
}
};
#endif

View file

@ -0,0 +1,79 @@
/*
* Aurora: https://github.com/pixelmatix/aurora
* Copyright (c) 2014 Jason Coon
*
* Portions of this code are adapted from FastLED Fire2012 example by Mark Kriegsman: https://github.com/FastLED/FastLED/blob/master/examples/Noise/Noise.ino
* Copyright (c) 2013 FastLED
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef PatternSimplexNoise_H
#define PatternSimplexNoise_H
class PatternSimplexNoise : public Drawable {
public:
PatternSimplexNoise() {
name = (char *)"Noise";
}
void start() {
// Initialize our coordinates to some random values
noise_x = random16();
noise_y = random16();
noise_z = random16();
}
unsigned int drawFrame() {
#if FASTLED_VERSION >= 3001000
// a new parameter set every 15 seconds
EVERY_N_SECONDS(15) {
noise_x = random16();
noise_y = random16();
noise_z = random16();
}
#endif
uint32_t speed = 100;
effects.FillNoise();
ShowNoiseLayer(0, 1, 0);
// noise_x += speed;
noise_y += speed;
noise_z += speed;
effects.ShowFrame();
return 30;
}
// show just one layer
void ShowNoiseLayer(byte layer, byte colorrepeat, byte colorshift) {
for (uint16_t i = 0; i < MATRIX_WIDTH; i++) {
for (uint16_t j = 0; j < MATRIX_HEIGHT; j++) {
uint8_t pixel = noise[i][j];
// assign a color depending on the actual palette
effects.leds[XY16(i, j)] = effects.ColorFromCurrentPalette(colorrepeat * (pixel + colorshift), pixel);
}
}
}
};
#endif

View file

@ -0,0 +1,145 @@
/*
* Aurora: https://github.com/pixelmatix/aurora
* Copyright (c) 2014 Jason Coon
*
* Portions of this code are adapted from LedEffects Snake by Robert Atkins: https://bitbucket.org/ratkins/ledeffects/src/26ed3c51912af6fac5f1304629c7b4ab7ac8ca4b/Snake.cpp?at=default
* Copyright (c) 2013 Robert Atkins
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef PatternSnake_H
#define PatternSnake_H
class PatternSnake : public Drawable {
private:
static const byte SNAKE_LENGTH = 16;
CRGB colors[SNAKE_LENGTH];
uint8_t initialHue;
enum Direction {
UP, DOWN, LEFT, RIGHT
};
struct Pixel {
uint8_t x;
uint8_t y;
};
struct Snake {
Pixel pixels[SNAKE_LENGTH];
Direction direction;
void newDirection() {
switch (direction) {
case UP:
case DOWN:
direction = random(0, 2) == 1 ? RIGHT : LEFT;
break;
case LEFT:
case RIGHT:
direction = random(0, 2) == 1 ? DOWN : UP;
default:
break;
}
}
void shuffleDown() {
for (byte i = SNAKE_LENGTH - 1; i > 0; i--) {
pixels[i] = pixels[i - 1];
}
}
void reset() {
direction = UP;
for (int i = 0; i < SNAKE_LENGTH; i++) {
pixels[i].x = 0;
pixels[i].y = 0;
}
}
void move() {
switch (direction) {
case UP:
pixels[0].y = (pixels[0].y + 1) % MATRIX_HEIGHT;
break;
case LEFT:
pixels[0].x = (pixels[0].x + 1) % MATRIX_WIDTH;
break;
case DOWN:
pixels[0].y = pixels[0].y == 0 ? MATRIX_HEIGHT - 1 : pixels[0].y - 1;
break;
case RIGHT:
pixels[0].x = pixels[0].x == 0 ? MATRIX_WIDTH - 1 : pixels[0].x - 1;
break;
}
}
void draw(CRGB colors[SNAKE_LENGTH]) {
for (byte i = 0; i < SNAKE_LENGTH; i++) {
effects.leds[XY(pixels[i].x, pixels[i].y)] = colors[i] %= (255 - i * (255 / SNAKE_LENGTH));
}
}
};
static const int snakeCount = 6;
Snake snakes[snakeCount];
public:
PatternSnake() {
name = (char *)"Snake";
for (int i = 0; i < snakeCount; i++) {
Snake* snake = &snakes[i];
snake->reset();
}
}
void start()
{
effects.ClearFrame();
}
unsigned int drawFrame() {
fill_palette(colors, SNAKE_LENGTH, initialHue++, 5, effects.currentPalette, 255, LINEARBLEND);
for (int i = 0; i < snakeCount; i++) {
Snake* snake = &snakes[i];
snake->shuffleDown();
if (random(10) > 7) {
snake->newDirection();
}
snake->move();
snake->draw(colors);
}
effects.ShowFrame();
return 30;
}
};
#endif

View file

@ -0,0 +1,113 @@
/*
* Aurora: https://github.com/pixelmatix/aurora
* Copyright (c) 2014 Jason Coon
*
* Portions of this code are adapted from FastLED Fire2012 example by Mark Kriegsman: https://github.com/FastLED/FastLED/tree/master/examples/Fire2012WithPalette
* Copyright (c) 2013 FastLED
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef PatternSpark_H
#define PatternSpark_H
class PatternSpark : public Drawable {
private:
public:
PatternSpark() {
name = (char *)"Spark";
}
// There are two main parameters you can play with to control the look and
// feel of your fire: COOLING (used in step 1 above), and SPARKING (used
// in step 3 above).
//
// COOLING: How much does the air cool as it rises?
// Less cooling = taller flames. More cooling = shorter flames.
// Default 55, suggested range 20-100
uint8_t cooling = 100;
// SPARKING: What chance (out of 255) is there that a new spark will be lit?
// Higher chance = more roaring fire. Lower chance = more flickery fire.
// Default 120, suggested range 50-200.
uint8_t sparking = 50;
unsigned int drawFrame() {
// Add entropy to random number generator; we use a lot of it.
random16_add_entropy( random16());
effects.DimAll(235); effects.ShowFrame();
for (uint8_t x = 0; x < MATRIX_WIDTH; x++) {
// Step 1. Cool down every cell a little
for (int y = 0; y < MATRIX_HEIGHT; y++) {
int xy = XY(x, y);
heat[xy] = qsub8(heat[xy], random8(0, ((cooling * 10) / MATRIX_HEIGHT) + 2));
}
// Step 2. Heat from each cell drifts 'up' and diffuses a little
for (int y = 0; y < MATRIX_HEIGHT; y++) {
heat[XY(x, y)] = (heat[XY(x, y + 1)] + heat[XY(x, y + 2)] + heat[XY(x, y + 2)]) / 3;
}
// Step 2. Randomly ignite new 'sparks' of heat
if (random8() < sparking) {
uint8_t xt = random8(MATRIX_CENTRE_X - 2, MATRIX_CENTER_X + 3);
int xy = XY(xt, MATRIX_HEIGHT - 1);
heat[xy] = qadd8(heat[xy], random8(160, 255));
}
// Step 4. Map from heat cells to LED colors
for (int y = 0; y < MATRIX_HEIGHT; y++) {
int xy = XY(x, y);
byte colorIndex = heat[xy];
// Recommend that you use values 0-240 rather than
// the usual 0-255, as the last 15 colors will be
// 'wrapping around' from the hot end to the cold end,
// which looks wrong.
colorIndex = scale8(colorIndex, 240);
// override color 0 to ensure a black background?
if (colorIndex != 0)
// effects.leds[xy] = CRGB::Black;
// else
effects.leds[xy] = effects.ColorFromCurrentPalette(colorIndex);
}
}
// Noise
noise_x += 1000;
noise_y += 1000;
noise_z += 1000;
noise_scale_x = 4000;
noise_scale_y = 4000;
effects.FillNoise();
effects.MoveX(3);
effects.MoveFractionalNoiseX(4);
effects.ShowFrame();
return 15;
}
};
#endif

View file

@ -48,7 +48,7 @@ public:
} }
unsigned int drawFrame() { unsigned int drawFrame() {
effects.DimAll(190); effects.ShowFrame();
CRGB color = effects.ColorFromCurrentPalette(speed * 8); CRGB color = effects.ColorFromCurrentPalette(speed * 8);
@ -59,26 +59,23 @@ public:
// target position // target position
float targetDegrees = degrees + speed; float targetDegrees = degrees + speed;
float targetRadians = radians(targetDegrees); float targetRadians = radians(targetDegrees);
int targetX = (int) (effects.getCenterX() + radius * cos(targetRadians)); int targetX = (int) (MATRIX_CENTER_X + radius * cos(targetRadians));
int targetY = (int) (effects.getCenterY() - radius * sin(targetRadians)); int targetY = (int) (MATRIX_CENTER_Y - radius * sin(targetRadians));
float tempDegrees = degrees; float tempDegrees = degrees;
for (int i =0; i < 16; i++) do{
{
float radians = radians(tempDegrees); float radians = radians(tempDegrees);
x = (int) (effects.getCenterX() + radius * cos(radians)); x = (int) (MATRIX_CENTER_X + radius * cos(radians));
y = (int) (effects.getCenterY() - radius * sin(radians)); y = (int) (MATRIX_CENTER_Y - radius * sin(radians));
effects.setPixel(x, y, color); effects.drawBackgroundFastLEDPixelCRGB(x, y, color);
effects.setPixel(y, x, color); effects.drawBackgroundFastLEDPixelCRGB(y, x, color);
tempDegrees += 1; tempDegrees += 1;
if (tempDegrees >= 360) if (tempDegrees >= 360)
tempDegrees = 0; tempDegrees = 0;
} while (x != targetX || y != targetY);
}
degrees += speed; degrees += speed;
@ -96,8 +93,6 @@ public:
} }
} }
effects.ShowFrame();
return 0; return 0;
} }
}; };

View file

@ -0,0 +1,138 @@
/*
* Portions of this code are adapted from "Funky Clouds" by Stefan Petrick:
* https://gist.github.com/anonymous/876f908333cd95315c35
*
* Copyright (c) 2014 Stefan Petrick
* http://www.stefan-petrick.de/wordpress_beta
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef PatternSpiral_H
#define PatternSpiral_H
class PatternSpiral : public Drawable {
private:
// Timer stuff (Oszillators)
struct timer {
unsigned long takt;
unsigned long lastMillis;
unsigned long count;
int delta;
byte up;
byte down;
};
timer multiTimer[5];
int timers = sizeof(multiTimer) / sizeof(multiTimer[0]);
// counts all variables with different speeds linear up and down
void UpdateTimers()
{
unsigned long now = millis();
for (int i = 0; i < timers; i++)
{
while (now - multiTimer[i].lastMillis >= multiTimer[i].takt)
{
multiTimer[i].lastMillis += multiTimer[i].takt;
multiTimer[i].count = multiTimer[i].count + multiTimer[i].delta;
if ((multiTimer[i].count == multiTimer[i].up) || (multiTimer[i].count == multiTimer[i].down))
{
multiTimer[i].delta = -multiTimer[i].delta;
}
}
}
}
public:
PatternSpiral() {
name = (char *)"Spiral";
}
void start() {
// set all counting directions positive for the beginning
for (int i = 0; i < timers; i++) multiTimer[i].delta = 1;
// set range (up/down), speed (takt=ms between steps) and starting point of all oszillators
unsigned long now = millis();
multiTimer[0].lastMillis = now;
multiTimer[0].takt = 42; //x1
multiTimer[0].up = MATRIX_WIDTH - 1;
multiTimer[0].down = 0;
multiTimer[0].count = 0;
multiTimer[1].lastMillis = now;
multiTimer[1].takt = 55; //y1
multiTimer[1].up = MATRIX_HEIGHT - 1;
multiTimer[1].down = 0;
multiTimer[1].count = 0;
multiTimer[2].lastMillis = now;
multiTimer[2].takt = 3; //color
multiTimer[2].up = 255;
multiTimer[2].down = 0;
multiTimer[2].count = 0;
multiTimer[3].lastMillis = now;
multiTimer[3].takt = 71; //x2
multiTimer[3].up = MATRIX_WIDTH - 1;
multiTimer[3].down = 0;
multiTimer[3].count = 0;
multiTimer[4].lastMillis = now;
multiTimer[4].takt = 89; //y2
multiTimer[4].up = MATRIX_HEIGHT - 1;
multiTimer[4].down = 0;
multiTimer[4].count = 0;
}
unsigned int drawFrame() {
// manage the Oszillators
UpdateTimers();
// draw just a line defined by 5 oszillators
effects.BresenhamLine(
multiTimer[3].count, // x1
multiTimer[4].count, // y1
multiTimer[0].count, // x2
multiTimer[1].count, // y2
multiTimer[2].count); // color
// manipulate the screen buffer
// with fixed parameters (could be oszillators too)
// Params: center x, y, radius, scale color down
// --> NOTE: Affects always a SQUARE with an odd length
// effects.SpiralStream(15, 15, 10, 128);
effects.SpiralStream(31, 15, 64, 128); // for 64 pixel wide matrix!
// effects.SpiralStream(47, 15, 10, 128); // for 64 pixel wide matrix!
// why not several times?!
// effects.SpiralStream(16, 6, 6, 128);
// effects.SpiralStream(10, 24, 10, 128);
// increase the contrast
effects.DimAll(250); effects.ShowFrame();
return 0;
}
};
#endif

View file

@ -0,0 +1,112 @@
/*
* Aurora: https://github.com/pixelmatix/aurora
* Copyright (c) 2014 Jason Coon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef PatternSpiro_H
class PatternSpiro : public Drawable {
private:
byte theta1 = 0;
byte theta2 = 0;
byte hueoffset = 0;
uint8_t radiusx = MATRIX_WIDTH / 4;
uint8_t radiusy = MATRIX_HEIGHT / 4;
uint8_t minx = MATRIX_CENTER_X - radiusx;
uint8_t maxx = MATRIX_CENTER_X + radiusx + 1;
uint8_t miny = MATRIX_CENTER_Y - radiusy;
uint8_t maxy = MATRIX_CENTER_Y + radiusy + 1;
uint8_t spirocount = 1;
uint8_t spirooffset = 256 / spirocount;
boolean spiroincrement = true;
boolean handledChange = false;
public:
PatternSpiro() {
name = (char *)"Spiro";
}
void start(){
effects.ClearFrame();
};
unsigned int drawFrame() {
blur2d(effects.leds, MATRIX_WIDTH > 255 ? 255 : MATRIX_WIDTH, MATRIX_HEIGHT > 255 ? 255 : MATRIX_HEIGHT, 192);
boolean change = false;
for (int i = 0; i < spirocount; i++) {
uint8_t x = mapsin8(theta1 + i * spirooffset, minx, maxx);
uint8_t y = mapcos8(theta1 + i * spirooffset, miny, maxy);
uint8_t x2 = mapsin8(theta2 + i * spirooffset, x - radiusx, x + radiusx);
uint8_t y2 = mapcos8(theta2 + i * spirooffset, y - radiusy, y + radiusy);
CRGB color = effects.ColorFromCurrentPalette(hueoffset + i * spirooffset, 128);
effects.leds[XY(x2, y2)] += color;
if((x2 == MATRIX_CENTER_X && y2 == MATRIX_CENTER_Y) ||
(x2 == MATRIX_CENTRE_X && y2 == MATRIX_CENTRE_Y)) change = true;
}
theta2 += 1;
EVERY_N_MILLIS(25) {
theta1 += 1;
}
EVERY_N_MILLIS(100) {
if (change && !handledChange) {
handledChange = true;
if (spirocount >= MATRIX_WIDTH || spirocount == 1) spiroincrement = !spiroincrement;
if (spiroincrement) {
if(spirocount >= 4)
spirocount *= 2;
else
spirocount += 1;
}
else {
if(spirocount > 4)
spirocount /= 2;
else
spirocount -= 1;
}
spirooffset = 256 / spirocount;
}
if(!change) handledChange = false;
}
EVERY_N_MILLIS(33) {
hueoffset += 1;
}
effects.ShowFrame();
return 0;
}
};
#endif

View file

@ -1,130 +0,0 @@
#ifndef PatternStarfield_H
#define PatternStarfield_H
struct Star
{
Star() {
x = y = z = 0;
colour = CRGB::White;
}
float x;
float y;
float z;
CRGB colour;
};
// Based on https://github.com/sinoia/oled-starfield/blob/master/src/starfield.cpp
class PatternStarfield : public Drawable {
private:
const int starCount = 100; // number of stars in the star field
const int maxDepth = 32; // maximum distance away for a star
// the star field - starCount stars represented as x, y and z co-ordinates
// https://www.cplusplus.com/doc/tutorial/dynamic/
Star * stars;
//CRGBPalette16 currentPalette;
unsigned int drawFrame() { // aka drawStars
// Dim routine
for (int16_t i = 0; i < VPANEL_W; i++) {
for (int16_t j = 0; j < VPANEL_H; j++) {
uint16_t xy = XY16(i, j);
effects.leds[xy].nscale8(250);
}
}
int origin_x = VPANEL_W / 2;
int origin_y = VPANEL_H / 2;
// Iterate through the stars reducing the z co-ordinate in order to move the
// star closer.
for (int i = 0; i < starCount; ++i) {
stars[i].z -= 0.1;
// if the star has moved past the screen (z < 0) reposition it far away
// with random x and y positions.
if (stars[i].z <= 0)
{
stars[i].x = getRandom(-25, 25);
stars[i].y = getRandom(-25, 25);
stars[i].z = maxDepth;
}
// Convert the 3D coordinates to 2D using perspective projection.
float k = VPANEL_W / stars[i].z;
int x = static_cast<int>(stars[i].x * k + origin_x);
int y = static_cast<int>(stars[i].y * k + origin_y);
// Draw the star (if it is visible in the screen).
// Distant stars are smaller than closer stars.
if ((0 <= x and x < VPANEL_H)
and (0 <= y and y < VPANEL_H)) {
CRGB tmp = stars[i].colour;
//CRGB tmp = CRGB::White;
byte scale = 255 -(stars[i].z*7);
tmp.nscale8(scale);
effects.setPixel(x,y, CRGB(tmp.r,tmp.g,tmp.b));
}
else
{
stars[i].z = -1; // set to -1 so it gets re-popualted
}
}
effects.ShowFrame();
return 5;
}
int getRandom(int lower, int upper) {
/* Generate and return a random number between lower and upper bound */
return lower + static_cast<int>(rand() % (upper - lower + 1));
}
/*
CRGB ColorFromCurrentPalette(uint8_t index = 0, uint8_t brightness = 255, TBlendType blendType = LINEARBLEND) {
return ColorFromPalette(currentPalette, index, brightness, blendType);
}
*/
public:
PatternStarfield()
{
name = (char *)"PatternStarfield";
}
void start() {
//currentPalette = RainbowColors_p;
//currentPalette = CloudColors_p;
// Allocate memory
stars = new Star[starCount];
// Initialise the star field with random stars
for (int i = 0; i < starCount; i++) {
stars[i].x = getRandom(-25, 25);
stars[i].y = getRandom(-25, 25);
stars[i].z = getRandom(0, maxDepth);
//stars[i].colour = ColorFromCurrentPalette(random(0, 128));
stars[i].colour = effects.ColorFromCurrentPalette(random(0, 128));
}
} // end start
void stop() {
delete[] stars;
}
};
#endif

View file

@ -0,0 +1,79 @@
/*
* Aurora: https://github.com/pixelmatix/aurora
* Copyright (c) 2014 Jason Coon
*
* Portions of this code are adapted from SmartMatrixSwirl by Mark Kriegsman: https://gist.github.com/kriegsman/5adca44e14ad025e6d3b
* https://www.youtube.com/watch?v=bsGBT-50cts
* Copyright (c) 2014 Mark Kriegsman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef PatternSwirl_H
class PatternSwirl : public Drawable {
private:
const uint8_t borderWidth = 2;
public:
PatternSwirl() {
name = (char *)"Swirl";
}
void start() {
effects.ClearFrame();
}
unsigned int drawFrame() {
// Apply some blurring to whatever's already on the matrix
// Note that we never actually clear the matrix, we just constantly
// blur it repeatedly. Since the blurring is 'lossy', there's
// an automatic trend toward black -- by design.
uint8_t blurAmount = beatsin8(2, 10, 255);
#if FASTLED_VERSION >= 3001000
blur2d(effects.leds, MATRIX_WIDTH > 255 ? 255 : MATRIX_WIDTH, MATRIX_HEIGHT > 255 ? 255 : MATRIX_HEIGHT, blurAmount);
#else
effects.DimAll(blurAmount);
#endif
// Use two out-of-sync sine waves
uint8_t i = beatsin8(256/MATRIX_HEIGHT, borderWidth, MATRIX_WIDTH - borderWidth);
uint8_t j = beatsin8(2048/MATRIX_WIDTH, borderWidth, MATRIX_HEIGHT - borderWidth);
// Also calculate some reflections
uint8_t ni = (MATRIX_WIDTH - 1) - i;
uint8_t nj = (MATRIX_HEIGHT - 1) - j;
// The color of each point shifts over time, each at a different speed.
uint16_t ms = millis();
effects.leds[XY(i, j)] += effects.ColorFromCurrentPalette(ms / 11);
//effects.leds[XY(j, i)] += effects.ColorFromCurrentPalette(ms / 13); // this doesn't work for non-square matrixes
effects.leds[XY(ni, nj)] += effects.ColorFromCurrentPalette(ms / 17);
//effects.leds[XY(nj, ni)] += effects.ColorFromCurrentPalette(ms / 29); // this doesn't work for non-square matrixes
effects.leds[XY(i, nj)] += effects.ColorFromCurrentPalette(ms / 37);
effects.leds[XY(ni, j)] += effects.ColorFromCurrentPalette(ms / 41);
effects.ShowFrame();
return 0;
}
};
#endif

View file

@ -12,7 +12,7 @@ class PatternTest : public Drawable {
unsigned int drawFrame() { unsigned int drawFrame() {
matrix->fillScreen(matrix->color565(128, 0, 0)); matrix.fillScreen(matrix.color565(128, 0, 0));
return 1000; return 1000;
} }
}; };

View file

@ -0,0 +1,120 @@
/*
* Aurora: https://github.com/pixelmatix/aurora
* Copyright (c) 2014 Jason Coon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef PatternWave_H
#define PatternWave_H
class PatternWave : public Drawable {
private:
byte thetaUpdate = 0;
byte thetaUpdateFrequency = 0;
byte theta = 0;
byte hueUpdate = 0;
byte hueUpdateFrequency = 0;
byte hue = 0;
byte rotation = 0;
uint8_t scale = 256 / MATRIX_WIDTH;
uint8_t maxX = MATRIX_WIDTH - 1;
uint8_t maxY = MATRIX_HEIGHT - 1;
uint8_t waveCount = 1;
public:
PatternWave() {
name = (char *)"Wave";
}
void start() {
rotation = random(0, 4);
waveCount = random(1, 3);
}
unsigned int drawFrame() {
int n = 0;
switch (rotation) {
case 0:
for (int x = 0; x < MATRIX_WIDTH; x++) {
n = quadwave8(x * 2 + theta) / scale;
effects.drawBackgroundFastLEDPixelCRGB(x, n, effects.ColorFromCurrentPalette(x + hue));
if (waveCount == 2)
effects.drawBackgroundFastLEDPixelCRGB(x, maxY - n, effects.ColorFromCurrentPalette(x + hue));
}
break;
case 1:
for (int y = 0; y < MATRIX_HEIGHT; y++) {
n = quadwave8(y * 2 + theta) / scale;
effects.drawBackgroundFastLEDPixelCRGB(n, y, effects.ColorFromCurrentPalette(y + hue));
if (waveCount == 2)
effects.drawBackgroundFastLEDPixelCRGB(maxX - n, y, effects.ColorFromCurrentPalette(y + hue));
}
break;
case 2:
for (int x = 0; x < MATRIX_WIDTH; x++) {
n = quadwave8(x * 2 - theta) / scale;
effects.drawBackgroundFastLEDPixelCRGB(x, n, effects.ColorFromCurrentPalette(x + hue));
if (waveCount == 2)
effects.drawBackgroundFastLEDPixelCRGB(x, maxY - n, effects.ColorFromCurrentPalette(x + hue));
}
break;
case 3:
for (int y = 0; y < MATRIX_HEIGHT; y++) {
n = quadwave8(y * 2 - theta) / scale;
effects.drawBackgroundFastLEDPixelCRGB(n, y, effects.ColorFromCurrentPalette(y + hue));
if (waveCount == 2)
effects.drawBackgroundFastLEDPixelCRGB(maxX - n, y, effects.ColorFromCurrentPalette(y + hue));
}
break;
}
effects.DimAll(254);
effects.ShowFrame();
if (thetaUpdate >= thetaUpdateFrequency) {
thetaUpdate = 0;
theta++;
}
else {
thetaUpdate++;
}
if (hueUpdate >= hueUpdateFrequency) {
hueUpdate = 0;
hue++;
}
else {
hueUpdate++;
}
return 0;
}
};
#endif

View file

@ -0,0 +1,299 @@
/*
* Aurora: https://github.com/pixelmatix/aurora
* Copyright (c) 2014 Jason Coon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef Patterns_H
#define Patterns_H
#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0]))
#include "Vector.h"
#include "Boid.h"
#include "Attractor.h"
/*
* Note from mrfaptastic:
*
* Commented out patterns are due to the fact they either didn't work properly with a non-square display,
* or from my personal opinion, are crap.
*/
#include "PatternTest.h"
//#include "PatternNoiseSmearing.h" // Doesn't seem to work, omitting.
#include "PatternSpiro.h"
#include "PatternRadar.h"
#include "PatternSwirl.h"
#include "PatternPendulumWave.h"
#include "PatternFlowField.h"
#include "PatternIncrementalDrift.h"
//#include "PatternIncrementalDrift2.h" // Doesn't seem to work, omitting.
#include "PatternMunch.h"
#include "PatternElectricMandala.h"
//#include "PatternSpin.h" // Doesn't seem to work, omitting.
#include "PatternSimplexNoise.h"
#include "PatternWave.h"
#include "PatternAttract.h"
//#include "PatternBounce.h" // Doesn't seem to work, omitting.
#include "PatternFlock.h"
#include "PatternInfinity.h"
#include "PatternPlasma.h"
#include "PatternSnake.h"
#include "PatternInvaders.h"
//#include "PatternCube.h" // Doesn't seem to work, omitting.
//#include "PatternFire.h" // Doesn't seem to work, omitting.
#include "PatternLife.h"
#include "PatternMaze.h"
//#include "PatternPulse.h" // Doesn't seem to work, omitting.
//#include "PatternSpark.h" // Doesn't seem to work, omitting.
#include "PatternSpiral.h"
class Patterns : public Playlist {
private:
PatternTest patternTest;
// PatternRainbowFlag rainbowFlag; // doesn't work
// PatternPaletteSmear paletteSmear;
// PatternMultipleStream multipleStream; // doesn't work
// PatternMultipleStream2 multipleStream2; // doesn't work
// PatternMultipleStream3 multipleStream3; // doesn't work
// PatternMultipleStream4 multipleStream4; // doesn't work
// PatternMultipleStream5 multipleStream5; // doesn't work
// PatternMultipleStream8 multipleStream8; // doesn't work
PatternSpiro spiro;
// PatternRadar radar;
PatternSwirl swirl;
PatternPendulumWave pendulumWave;
PatternFlowField flowField;
PatternIncrementalDrift incrementalDrift;
// PatternIncrementalDrift2 incrementalDrift2;
PatternMunch munch;
PatternElectricMandala electricMandala;
// PatternSpin spin;
PatternSimplexNoise simplexNoise;
PatternWave wave;
PatternAttract attract;
// PatternBounce bounce;
PatternFlock flock;
PatternInfinity infinity;
PatternPlasma plasma;
PatternInvadersSmall invadersSmall;
// PatternInvadersMedium invadersMedium;
// PatternInvadersLarge invadersLarge;
PatternSnake snake;
// PatternCube cube;
// PatternFire fire;
PatternLife life;
PatternMaze maze;
// PatternPulse pulse;
// PatternSpark spark;
PatternSpiral spiral;
int currentIndex = 0;
Drawable* currentItem;
int getCurrentIndex() {
return currentIndex;
}
//const static int PATTERN_COUNT = 37;
const static int PATTERN_COUNT = 17;
Drawable* shuffledItems[PATTERN_COUNT];
Drawable* items[PATTERN_COUNT] = {
// &patternTest, // ok
&spiro, // cool
// &paletteSmear, // fail
// &multipleStream, // fail
// &multipleStream8,// fail
// &multipleStream5,// fail
// &multipleStream3,// fail
// &radar, // fail
// &multipleStream4, // fail
// &multipleStream2, // fail
&life, // ok
&flowField,
&pendulumWave, //11 ok
&incrementalDrift, //12 ok
// &incrementalDrift2, // 13 fail
&munch, // 14 ok
&electricMandala, // 15 ok
// &spin, // 16 ok but repeditivev
&simplexNoise, // 17 - cool!
// &wave, // 18 ok (can't work with 256+ matrix due to uint8_t vars)
// &rainbowFlag, //20 // fail
&attract, // 21 ok
&swirl, // 22
// &bounce, // boncing line crap
&flock, // works
&infinity, // works
&plasma, // works
&invadersSmall, // works ish
// &invadersMedium, // fail
// &invadersLarge, // fail
&snake, // ok
// &cube, // works ish
// &fire, // ok ish
&maze, // ok
// &pulse,// fail
// &spark, // same as fire
&spiral, // ok
};
public:
Patterns() {
// add the items to the shuffledItems array
for (int a = 0; a < PATTERN_COUNT; a++) {
shuffledItems[a] = items[a];
}
shuffleItems();
this->currentItem = items[0];
this->currentItem->start();
}
char* Drawable::name = (char *)"Patterns";
void stop() {
if (currentItem)
currentItem->stop();
}
void start() {
if (currentItem)
currentItem->start();
}
void move(int step) {
currentIndex += step;
if (currentIndex >= PATTERN_COUNT) currentIndex = 0;
else if (currentIndex < 0) currentIndex = PATTERN_COUNT - 1;
if (effects.paletteIndex == effects.RandomPaletteIndex)
effects.RandomPalette();
moveTo(currentIndex);
//if (!isTimeAvailable && currentItem == &analogClock)
// move(step);
}
void moveRandom(int step) {
currentIndex += step;
if (currentIndex >= PATTERN_COUNT) currentIndex = 0;
else if (currentIndex < 0) currentIndex = PATTERN_COUNT - 1;
if (effects.paletteIndex == effects.RandomPaletteIndex)
effects.RandomPalette();
if (currentItem)
currentItem->stop();
currentItem = shuffledItems[currentIndex];
if (currentItem)
currentItem->start();
// if (!isTimeAvailable && currentItem == &analogClock)
// moveRandom(step);
}
void shuffleItems() {
for (int a = 0; a < PATTERN_COUNT; a++)
{
int r = random(a, PATTERN_COUNT);
Drawable* temp = shuffledItems[a];
shuffledItems[a] = shuffledItems[r];
shuffledItems[r] = temp;
}
}
unsigned int drawFrame() {
return currentItem->drawFrame();
}
void listPatterns() {
Serial.println(F("{"));
Serial.print(F(" \"count\": "));
Serial.print(PATTERN_COUNT);
Serial.println(",");
Serial.println(F(" \"results\": ["));
for (int i = 0; i < PATTERN_COUNT; i++) {
Serial.print(F(" \""));
Serial.print(i, DEC);
Serial.print(F(": "));
Serial.print(items[i]->name);
if (i == PATTERN_COUNT - 1)
Serial.println(F("\""));
else
Serial.println(F("\","));
}
Serial.println(" ]");
Serial.println("}");
}
char * getCurrentPatternName()
{
return currentItem->name;
}
void moveTo(int index) {
if (currentItem)
currentItem->stop();
currentIndex = index;
currentItem = items[currentIndex];
if (currentItem)
currentItem->start();
}
bool setPattern(String name) {
for (int i = 0; i < PATTERN_COUNT; i++) {
if (name.compareTo(items[i]->name) == 0) {
moveTo(i);
return true;
}
}
return false;
}
bool setPattern(int index) {
if (index >= PATTERN_COUNT || index < 0)
return false;
moveTo(index);
return true;
}
};
#endif

View file

@ -1,222 +0,0 @@
/*
* Aurora: https://github.com/pixelmatix/aurora
* Original Copyright (c) 2014 Jason Coon
*
* Modified by Codetastic 2024
*
*/
#ifndef Patterns_H
#define Patterns_H
#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0]))
#include "Vector2.hpp"
#include "Boid.hpp"
#include "Attractor.hpp"
/*
* Note from mrfaptastic:
*
* Commented out patterns are due to the fact they either didn't work properly with a non-square display,
* or from my personal opinion, are crap.
*/
#include "PatternStarfield.hpp" // new 2024
#include "PatternAttract.hpp"
#include "PatternBounce.hpp" // Doesn't seem to work, omitting.
#include "PatternCube.hpp" // Doesn't seem to work, omitting.
#include "PatternElectricMandala.hpp"
#include "PatternFireKoz.hpp" // Doesn't seem to work, omitting.
#include "PatternFlock.hpp"
#include "PatternFlowField.hpp"
#include "PatternIncrementalDrift.hpp"
#include "PatternIncrementalDrift2.hpp" // Doesn't seem to work, omitting.
#include "PatternInfinity.hpp"
#include "PatternMaze.hpp" // ??
#include "PatternMunch.hpp"
//#include "PatternNoiseSmearing.hpp"
#include "PatternPendulumWave.hpp"
#include "PatternPlasma.hpp"
#include "PatternRadar.hpp"
#include "PatternSimplexNoise.hpp"
#include "PatternSnake.hpp"
#include "PatternSpiral.hpp"
#include "PatternSpiro.hpp"
#include "PatternWave.hpp"
#include "PatternRain.hpp"
#include "PatternJuliaSetFractal.hpp"
#include "PatternRain.hpp"
#include "PatternFireworks.hpp"
class Patterns {
private:
PatternStarfield starfield;
PatternAttract attract;
PatternBounce bounce;
PatternCube cube;
PatternElectricMandala electricMandala;
PatternFireKoz fire;
PatternFlock flock;
PatternFlowField flowField;
PatternIncrementalDrift incrementalDrift;
PatternIncrementalDrift2 incrementalDrift2;
PatternInfinity infinity;
PatternMaze maze;
PatternMunch munch;
PatternPendulumWave pendwave;
PatternPlasma plasma;
PatternRadar radar;
PatternSimplexNoise simpnoise;
PatternSnake snake;
PatternSpiral spiral;
PatternSpiro spiro;
PatternWave wave;
PatternJuliaSet juliaSet;
PatternRain rain;
PatternFirework fireworks;
std::vector<Drawable*> availablePatterns = {
&juliaSet,
&starfield,
&attract,
&bounce,
&cube,
&electricMandala,
&fire,
&flock,
&spiro,
&radar,
&flowField,
&incrementalDrift,
&incrementalDrift2,
&infinity,
&maze,
&munch,
&pendwave,
&plasma,
&radar,
&simpnoise,
&spiro,
&wave,
&rain,
&fireworks
};
int currentIndex = 0;
Drawable* currentItem;
int getCurrentIndex() {
return currentIndex;
}
public:
Patterns() {
this->currentItem = availablePatterns[0];
this->currentItem->start();
}
void stop() {
if (currentItem)
currentItem->stop();
}
void start() {
if (currentItem)
currentItem->start();
}
void moveTo(int index)
{
index = ((index >= availablePatterns.size()) || (index < 0)) ? 0 : index;
if (currentItem)
currentItem->stop();
currentIndex = index;
currentItem = availablePatterns[currentIndex];
Serial.print("Changing pattern to: ");
Serial.println(getCurrentPatternName());
if (currentItem)
currentItem->start();
} // index
void move(int step) {
currentIndex += step;
if (currentIndex >= availablePatterns.size()) currentIndex = 0;
else if (currentIndex < 0) currentIndex = 0;
moveTo(currentIndex);
}
void moveRandom(int step) {
int rand_index = random(0, availablePatterns.size()-1);
moveTo(rand_index);
}
unsigned int drawFrame() {
return currentItem->drawFrame();
}
void listPatterns() {
Serial.println(F("{"));
Serial.print(F(" \"count\": "));
Serial.print(availablePatterns.size());
Serial.println(",");
Serial.println(F(" \"results\": ["));
for (size_t i = 0; i < availablePatterns.size(); i++) {
Serial.print(F(" \""));
Serial.print(i, DEC);
Serial.print(F(": "));
Serial.print(availablePatterns[i]->name);
if (i == availablePatterns.size() - 1)
Serial.println(F("\""));
else
Serial.println(F("\","));
}
Serial.println(" ]");
Serial.println("}");
}
char * getCurrentPatternName()
{
return currentItem->name;
}
/*
bool setPattern(String name) {
for (size_t i = 0; i < availablePatterns.size(); i++) {
if (name.compareTo(availablePatterns[i]->name) == 0) {
moveTo(i);
return true;
}
}
return false;
}
*/
bool setPattern(int index) {
if (index >= availablePatterns.size() || index < 0)
return false;
moveTo(index);
return true;
}
};
#endif

View file

@ -0,0 +1,39 @@
/*
* Aurora: https://github.com/pixelmatix/aurora
* Copyright (c) 2014 Jason Coon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef Playlist_H
#define Playlist_H
class Playlist : public Drawable {
public:
virtual bool isPlaylist() {
return true;
}
boolean isCurrentItemFinished = true;
virtual void move(int step) = 0;
virtual void moveRandom(int step) = 0;
virtual int getCurrentIndex();
};
#endif

View file

@ -1,9 +1,6 @@
## Aurora Demo A port of Aurora visualisations
* This demonstrates a combination of the following libraries: ======
- "ESP32-HUB75-MatrixPanel-DMA" to send pixel data to the physical panels in combination with its in-built "VirtualMatrix" class to create a virtual display of chained panels, so the graphical effects of the Aurora demonstrations can be shown over a 'bigger' grid of physical panels acting as one big display.
- "GFX_Lite" to provide a simple graphics library for drawing on the virtual display.
- Note: GFX_Lite is a fork of AdaFruitGFX and FastLED library combined together, with a focus on simplicity and ease of use.
## Instructions Not all of the visualisations have been ported. About 17 of 37 work, or have been included as I think they look best.
* Use the serial input to advance through the patterns, or to toggle auto advance.
* Sending 'n' will advance to the next pattern, 'p' will go to the previous pattern. Sending 'a' will toggle auto advance on and off. Original source: https://github.com/pixelmatix/aurora

View file

@ -27,51 +27,10 @@
#define CLK_PIN 16 #define CLK_PIN 16
/*--------------------- MATRIX LILBRARY CONFIG -------------------------*/ // Display
#define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module. MatrixPanel_I2S_DMA display; // RGB Panel
#define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module.
#define PANEL_CHAIN 1 // Total number of panels chained one to another
MatrixPanel_I2S_DMA *dma_display = nullptr;
// Module configuration
HUB75_I2S_CFG mxconfig(
PANEL_RES_X, // module width
PANEL_RES_Y, // module height
PANEL_CHAIN // Chain length
);
/*
//Another way of creating config structure
//Custom pin mapping for all pins
HUB75_I2S_CFG::i2s_pins _pins={R1, G1, BL1, R2, G2, BL2, CH_A, CH_B, CH_C, CH_D, CH_E, LAT, OE, CLK};
HUB75_I2S_CFG mxconfig(
64, // width
64, // height
4, // chain length
_pins, // pin mapping
HUB75_I2S_CFG::FM6126A // driver chip
);
*/
//mxconfig.gpio.e = -1; // Assign a pin if you have a 64x64 panel
//mxconfig.clkphase = false; // Change this if you have issues with ghosting.
//mxconfig.driver = HUB75_I2S_CFG::FM6126A; // Change this according to your pane.
/*
* Wifi Logo, generated with the following steps:
*
* Python and Paint.Net needs to be installed.
*
* 1. SAVE BITMAP AS 1BIT COLOUR in paint.net
* 2. Run: bmp2hex.py -i -x <BITMAP FILE>
* 3. Copy paste output into sketch.
*
*/
// Wifi Logo, generated using LCD Image Converter: http://www.riuson.com/lcd-image-converter
const char wifi_image1bit[] PROGMEM = { const char wifi_image1bit[] PROGMEM = {
0x00,0x00,0x00,0xf8,0x1f,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0x01,0x00, 0x00,0x00,0x00,0xf8,0x1f,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,0x01,0x00,
0x00,0x00,0x00,0xf0,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0x1f, 0x00,0x00,0x00,0xf0,0xff,0xff,0x07,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0x1f,
@ -92,7 +51,10 @@ const char wifi_image1bit[] PROGMEM = {
0x00,0x00,0x00,0x80,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x80,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00 }; 0x00 };
/* Draw a bitmap, default bitmap pixels to white if no color provided.
* Use LCD Image Converter to generate the char array: http://www.riuson.com/lcd-image-converter
* In Options>Conversion, use: Monochrome Preset
*/
void drawXbm565(int x, int y, int width, int height, const char *xbm, uint16_t color = 0xffff) void drawXbm565(int x, int y, int width, int height, const char *xbm, uint16_t color = 0xffff)
{ {
if (width % 8 != 0) { if (width % 8 != 0) {
@ -104,7 +66,7 @@ void drawXbm565(int x, int y, int width, int height, const char *xbm, uint16_t c
int targetX = (i * 8 + j) % width + x; int targetX = (i * 8 + j) % width + x;
int targetY = (8 * i / (width)) + y; int targetY = (8 * i / (width)) + y;
if (bitRead(charColumn, j)) { if (bitRead(charColumn, j)) {
dma_display->drawPixel(targetX, targetY, color); display.drawPixel(targetX, targetY, color);
} }
} }
} }
@ -171,22 +133,19 @@ void setup() {
/************** DISPLAY **************/ /************** DISPLAY **************/
Sprintln("...Starting Display"); Sprintln("...Starting Display");
dma_display = new MatrixPanel_I2S_DMA(mxconfig); display.begin(R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, LAT_PIN, OE_PIN, CLK_PIN );
dma_display->begin();
dma_display->setBrightness8(90); //0-255
dma_display->clearScreen();
dma_display->fillScreen(dma_display->color444(0, 1, 0)); display.fillScreen(display.color444(0, 1, 0));
// Fade a Red Wifi Logo In // Fade a Red Wifi Logo In
for (int r=0; r < 255; r++ ) for (int r=0; r < 255; r++ )
{ {
drawXbm565(0,0,64,32, wifi_image1bit, dma_display->color565(r,0,0)); drawXbm565(0,0,64,32, wifi_image1bit, display.color565(r,0,0));
delay(10); delay(10);
} }
delay(2000); delay(2000);
dma_display->clearScreen(); display.clearScreen();
} }
@ -199,6 +158,6 @@ void loop() {
current_icon = (current_icon +1 ) % num_icons; current_icon = (current_icon +1 ) % num_icons;
delay(2000); delay(2000);
dma_display->clearScreen(); display.clearScreen();
} }

View file

@ -1,13 +0,0 @@
# Xbm Bitmap example
## Requirements
* To generate the required Xbm data to be copied into the Sketch. Have python and [paint.net](https://www.getpaint.net/) installed.
* Bitmap should match the resolution of your display configuration.
## Instructions
1. SAVE BITMAP AS 1BIT COLOUR in paint.net
1. Run: bmp2hex.py -i -x <BITMAP> (e.g. "bmp2hex.py -i -x WiFi1bit.bmp")
1. Copy paste output into sketch.
![bmp2hex usage screenshot](screenshot.jpg)

Some files were not shown because too many files have changed in this diff Show more