Compare commits

...

69 commits

Author SHA1 Message Date
Dan Halbert
f83bac7e42
Merge pull request #86 from adafruit/idf5.5
Update for ESP-IDF 5.5 compatibility
2025-08-25 19:35:14 -04:00
Scott Shawcroft
9c45cfb0e4
Remove extra stuff from refactor 2025-08-25 12:07:12 -07:00
Dan Halbert
3614319ecd
Merge pull request #77 from chockenberry/master
Updated simple sketch to use correct values for pin mappings.
2025-08-20 13:54:44 -04:00
Scott Shawcroft
dbd84325c7
More formatting 2025-08-15 10:23:46 -07:00
Scott Shawcroft
c8ad602b45
clang format 2025-08-15 10:08:12 -07:00
Scott Shawcroft
1037d3f080
Tweak IOMUX setting 2025-08-15 09:59:10 -07:00
Scott Shawcroft
d55e3bf833
Mark version 1.7.0 2024-09-23 11:02:26 -07:00
Dan Halbert
0bd9873153
Merge pull request #82 from adafruit/STATIC-fix
STATIC -> static in stm32.h
2024-09-09 19:52:35 -04:00
Dan Halbert
2156ca0407 STATIC -> static 2024-09-09 19:44:17 -04:00
Scott Shawcroft
fb9903f97d
Merge pull request #80 from tannewt/rp2350
Enable RP2350 with same code as RP2040
2024-08-12 13:45:30 -07:00
Scott Shawcroft
282920f9ce
clang format 2024-08-12 13:26:21 -07:00
Scott Shawcroft
267d78d6cf
Enable RP2350 with same code as RP2040 2024-08-09 15:34:40 -07:00
Craig Hockenberry
386371d8fd Updated simple sketch to use correct values for pin mappings.
In reference to: https://forums.adafruit.com/viewtopic.php?p=1013458#p1013458
2024-04-28 13:15:14 -07:00
ladyada
ca4f8764be add c6 2024-04-24 11:31:51 -04:00
cffac25915
Merge pull request #75 from adafruit/updates-for-arduino-churn
Update for arduino esp32 3.0.0-rc1
2024-04-16 08:32:13 -05:00
7155be1a75 Update for arduino esp32 3.0.0-rc.1
.. tested on matrixportal s3 only. Other esp-family mcus may need
further changes.
2024-04-12 09:36:30 -05:00
e4506e5a57 bump actions versions to fix node deprecation warning 2024-04-12 09:32:29 -05:00
Scott Shawcroft
eadf2ee814
Merge pull request #73 from tannewt/cp_id5_5.1
Update CircuitPython implementation to ESP-IDF 5.1 APIs
2024-02-09 10:10:21 -08:00
Scott Shawcroft
d87532b044
Add comment and remove memory checks
I tried to check all relevant memory but still couldn't prevent it
from crashing. It wasn't clear how non-internal memory was being
referenced, so I couldn't fix it.

This is essentially what the old code was doing because of the
missed negation.
2024-02-08 16:00:59 -08:00
Scott Shawcroft
68943a8fcd
clang-format 2024-02-07 15:39:34 -08:00
Scott Shawcroft
5ad5f33a16
Switch to IDF 5.1 APIs
This also guards the ISR from running with inaccessible memory.
The timer will be called when the SPI flash/PSRAM cache is disabled.
When it is, we skip it because the data we need may not be available
and will crash if we access it.
2024-02-07 15:27:40 -08:00
ladyada
429d625ba7 try adding more nop's as per
https://forums.adafruit.com/viewtopic.php?t=204580&start=15
2023-10-06 20:41:39 -04:00
Paint Your Dragon
98a2da6da4
Merge pull request #68 from tannewt/fix_nons3_idf5_compile
Fix non-s3 idf5 compile
2023-10-05 09:51:15 -07:00
Scott Shawcroft
b0e9d045f8
clang-format 2023-10-03 15:38:18 -07:00
Scott Shawcroft
40d0971635
Fix non-S3 builds for IDF 5 and add C6 support 2023-10-03 10:55:27 -07:00
Scott Shawcroft
1faaec39b3
Merge pull request #67 from tannewt/fix_idf5_compile
Fix IDF 5 compilation error
2023-09-29 15:14:34 -07:00
Scott Shawcroft
67ac7d48d4
Fix IDF 5 compilation error 2023-09-29 14:26:55 -07:00
Phillip Burgess
a6dec45c38 SAMD51 CircuitPython: enable high drive strength (maybe, need to confirm pin indexing) 2023-09-10 16:31:06 -07:00
Phillip Burgess
4430d1ceb5 SAMD51: explicitly declare some vars volatile for cycle-counting reasons
This should have no effect on Arduino projects, where the library is already producing expected timing both before and after. This is really for CircuitPython, a last-ditch effort to avoid going to inline assembly or a complex DMA setup.
2023-09-08 10:07:39 -07:00
Paint Your Dragon
c9c1189e9d
Merge pull request #65 from adafruit/pb-s3-drive-strength
ESP32-S3: fix GPIO strength, dial down bit clock to ~17.8 MHz
2023-08-28 17:06:22 -07:00
Phillip Burgess
4a23a0bc7d ESP32-S3: fix GPIO strength, dial down bit clock to ~17.8 MHz 2023-08-28 16:47:12 -07:00
Paint Your Dragon
9a76ee2f81
Merge pull request #64 from adafruit/pb-m4-experiment
SAMD51: adjustable pixel clock duty cycle
2023-08-25 15:10:01 -07:00
Phillip Burgess
d15b59728d clang-format, bump version 2023-08-25 14:46:50 -07:00
Phillip Burgess
67cd55d033 clang-format 2023-08-25 14:41:39 -07:00
Phillip Burgess
df50a2761b SAMD51 loose ends; all widths and speeds implemented 2023-08-25 14:38:15 -07:00
Phillip Burgess
93ea4fa10d Ever-so-slightly adjustable duty on SAMD51 2023-08-24 17:07:04 -07:00
Paint Your Dragon
ed2e701871
Merge pull request #63 from adafruit/pb-m4-timing
M4 Timing Adjustments for Broader Matrix Compatibility
2023-08-04 10:05:57 -07:00
Phillip Burgess
eca749b742 Bump version # for M4 fixes, add PR note to README 2023-08-04 09:28:23 -07:00
Phillip Burgess
f1956d2506 Add notes on SAMD51 NOPs 2023-08-04 08:58:56 -07:00
Phillip Burgess
2c070c8640 Fix chunk counter type for long chains 2023-08-03 18:21:27 -07:00
Phillip Burgess
b4788466de M4: better CLK duty cycle 2023-08-03 16:22:48 -07:00
Phillip Burgess
7bd6440c14 Adjust NOPs on M4 for new matrices 2023-08-03 15:02:09 -07:00
Phillip Burgess
5f0b40ed59 Adjust _PM_timerGetCount() fudge constant on ESP32-S3 2023-07-26 11:25:55 -07:00
Paint Your Dragon
9766126f13
Merge pull request #62 from adafruit/pb-s3-example-fix
Fix doublebuffer_scrolltext example on Matrix Portal S3
2023-07-18 14:33:41 -07:00
Phillip Burgess
afce1d5149 Version bomp for S3 fix 2023-07-18 14:11:44 -07:00
Phillip Burgess
3be437876b Fix doublebuffer_scrolltext example on Matrix Portal S3 2023-07-18 14:00:28 -07:00
Phillip Burgess
bab07dfedb Examples: update MatrixPortal S3 pins for rev C schematic 2023-07-03 10:16:32 -07:00
Paint Your Dragon
d18ce6e085
Update library.properties for ESP32-S3 fixes 2023-06-30 14:22:03 -07:00
Paint Your Dragon
2216ca662f
Merge pull request #61 from adafruit/fixs3
test all platforms supported
2023-06-30 14:21:30 -07:00
1deae76007 run clang-format 2023-06-30 14:14:37 -05:00
94a18cc08a Fix building on esp32-s3
Tested on matrixportal s3 prototype
2023-06-30 14:08:34 -05:00
lady ada
9b88c0fead test all platforms supported 2023-06-30 01:10:20 -04:00
13b6c05d5f
Merge pull request #60 from jepler/cp-updates
Updates for CircuitPython compatibility
2023-06-13 08:35:55 -05:00
10666a9c50 run clang-format 2023-06-10 14:43:33 -05:00
ecab2fa75e fix time functions for all espressif platforms + circuitpython 2023-06-08 17:46:26 -05:00
8419c30127 Fix redefinition of _PM_timerCount in CircuitPython w/C3 2023-06-08 16:07:27 -05:00
a2711624df fix typo 2023-06-08 12:30:23 -05:00
ce18b6d465 esp32-s2: implement timerGetCount for circuitpython
only compile-tested
2023-06-07 14:33:07 -05:00
8dd189169c esp32-s3: keep gdma channel over invocations 2023-06-07 10:10:37 -05:00
fc1d948108 esp32-s3: ensure definitions for heap_caps_malloc and others are available 2023-06-05 11:03:14 -05:00
794e92179f esp32-s3: function in header must be marked static 2023-06-05 11:02:59 -05:00
7a98ebed61 esp32-s3: implement blast_byte for circuitpython 2023-06-05 11:02:38 -05:00
20ca3476e5 esp32-s3: fix signed vs unsigned diagnostic
core->chainBits is int16_t, so C promotion rules state that the type
of the arithmetic result is int. Cast it to uint32_t for comparison to
core->minPeriod, which is uint32_t.
2023-06-05 11:02:16 -05:00
e920fa9483 esp32: Declare functions in header as static 2023-06-05 11:01:21 -05:00
40a989b06d esp32: fix type of _PM_protoPtr 2023-06-05 11:01:07 -05:00
ac972f78cd esp32: CircuitPython uses ESP_PLATFORM as its main gating define for esp32 2023-06-05 11:00:44 -05:00
40ad09d4e9 esp32s2/s3: Fix "error: redefinition of '_PM_timerGetCount'"
In CircuitPython, it is an error to provide two implementations of
_PM_timerGetCount, so this one must be made conditional on !S2 && !S3
2023-06-05 10:41:52 -05:00
cade4a82f2 esp32-common: Make sure the ESP_IDF_VERSION_MAJOR macro is available
In CircuitPython, this header is not guaranteed to be indirectly included.
2023-06-05 10:38:58 -05:00
Paint Your Dragon
6967bffafe
Merge pull request #59 from adafruit/pb-matrixportal
Add MatrixPortal ESP32-S3 support in examples
2023-05-18 19:39:22 -07:00
24 changed files with 631 additions and 307 deletions

View file

@ -7,16 +7,16 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
arduino-platform: ["metro_m0", "metro_m4", "nrf52840", "esp32"] arduino-platform: ["metro_m0", "metro_m4", "metroesp32s2", "feather_esp32s3", "feather_rp2040", "nrf52840", "esp32"]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/setup-python@v1 - uses: actions/setup-python@v5
with: with:
python-version: '3.8' python-version: '3.8'
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- uses: actions/checkout@v2 - uses: actions/checkout@v4
with: with:
repository: adafruit/ci-arduino repository: adafruit/ci-arduino
path: ci path: ci

View file

@ -115,3 +115,18 @@ compile-time).
Most macros and functions begin with the prefix **\_PM\_** in order to Most macros and functions begin with the prefix **\_PM\_** in order to
avoid naming collisions with other code (exception being static functions, avoid naming collisions with other code (exception being static functions,
which can't be seen outside their source file). which can't be seen outside their source file).
# Pull Requests
If you encounter artifacts (noise, sparkles, dropouts and other issues) and
it seems to resolve by adjusting the NOP counts, please do not submit this
as a PR claiming a fix. Quite often what improves stability for one matrix
type can make things worse for other types. Instead, open an issue and
describe the hardware (both microcontroller and RGB matrix) and what worked
for you. A general solution working across all matrix types typically
involves monitoring the signals on a logic analyzer and aiming for a 50%
duty cycle on the CLK signal, 20 MHz or less, and then testing across a
wide variety of different matrix types to confirm; trial and error on just
a single matrix type is problematic. Maintainers: this goes for you too.
Don't merge a "fix" unless you've checked it out on a 'scope and on tested
across a broad range of matrices.

View file

@ -60,7 +60,7 @@ uint8_t oePin = 16;
#define NEXT_BUTTON 3 #define NEXT_BUTTON 3
#elif defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) #elif defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3)
uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37}; uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37};
uint8_t addrPins[] = {35, 36, 48, 45, 21}; // 16/32/64 pixels tall uint8_t addrPins[] = {45, 36, 48, 35, 21}; // 16/32/64 pixels tall
uint8_t clockPin = 2; uint8_t clockPin = 2;
uint8_t latchPin = 47; uint8_t latchPin = 47;
uint8_t oePin = 14; uint8_t oePin = 14;

View file

@ -23,7 +23,7 @@ supported boards.
uint8_t oePin = 16; uint8_t oePin = 16;
#elif defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) // MatrixPortal ESP32-S3 #elif defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) // MatrixPortal ESP32-S3
uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37}; uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37};
uint8_t addrPins[] = {35, 36, 48, 45, 21}; uint8_t addrPins[] = {45, 36, 48, 35, 21};
uint8_t clockPin = 2; uint8_t clockPin = 2;
uint8_t latchPin = 47; uint8_t latchPin = 47;
uint8_t oePin = 14; uint8_t oePin = 14;
@ -121,12 +121,11 @@ Adafruit_Protomatter matrix(
// Sundry globals used for animation --------------------------------------- // Sundry globals used for animation ---------------------------------------
int16_t textX = matrix.width(), // Current text position (X) int16_t textX; // Current text position (X)
textY, // Current text position (Y) int16_t textY; // Current text position (Y)
textMin, // Text pos. (X) when scrolled off left edge int16_t textMin; // Text pos. (X) when scrolled off left edge
hue = 0; char str[64]; // Buffer to hold scrolling message text
char str[50]; // Buffer to hold scrolling message text int16_t ball[3][4] = {
int8_t ball[3][4] = {
{ 3, 0, 1, 1 }, // Initial X,Y pos+velocity of 3 bouncy balls { 3, 0, 1, 1 }, // Initial X,Y pos+velocity of 3 bouncy balls
{ 17, 15, 1, -1 }, { 17, 15, 1, -1 },
{ 27, 4, -1, 1 } { 27, 4, -1, 1 }
@ -160,6 +159,7 @@ void setup(void) {
uint16_t w, h; uint16_t w, h;
matrix.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); // How big is it? matrix.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); // How big is it?
textMin = -w; // All text is off left edge when it reaches this point textMin = -w; // All text is off left edge when it reaches this point
textX = matrix.width(); // Start off right edge
textY = matrix.height() / 2 - (y1 + h / 2); // Center text vertically textY = matrix.height() / 2 - (y1 + h / 2); // Center text vertically
// Note: when making scrolling text like this, the setTextWrap(false) // Note: when making scrolling text like this, the setTextWrap(false)
// call is REQUIRED (to allow text to go off the edge of the matrix), // call is REQUIRED (to allow text to go off the edge of the matrix),
@ -169,7 +169,7 @@ void setup(void) {
// Set up the colors of the bouncy balls. // Set up the colors of the bouncy balls.
ballcolor[0] = matrix.color565(0, 20, 0); // Dark green ballcolor[0] = matrix.color565(0, 20, 0); // Dark green
ballcolor[1] = matrix.color565(0, 0, 20); // Dark blue ballcolor[1] = matrix.color565(0, 0, 20); // Dark blue
ballcolor[2] = matrix.color565(20, 0, 0); // ark red ballcolor[2] = matrix.color565(20, 0, 0); // Dark red
} }
// LOOP - RUNS REPEATEDLY AFTER SETUP -------------------------------------- // LOOP - RUNS REPEATEDLY AFTER SETUP --------------------------------------

View file

@ -25,7 +25,7 @@ uint8_t latchPin = 15;
uint8_t oePin = 16; uint8_t oePin = 16;
#else // MatrixPortal ESP32-S3 #else // MatrixPortal ESP32-S3
uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37}; uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37};
uint8_t addrPins[] = {35, 36, 48, 45, 21}; uint8_t addrPins[] = {45, 36, 48, 35, 21};
uint8_t clockPin = 2; uint8_t clockPin = 2;
uint8_t latchPin = 47; uint8_t latchPin = 47;
uint8_t oePin = 14; uint8_t oePin = 14;

View file

@ -28,7 +28,7 @@ supported boards. Notes have been moved to the bottom of the code.
uint8_t oePin = 16; uint8_t oePin = 16;
#elif defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) // MatrixPortal ESP32-S3 #elif defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) // MatrixPortal ESP32-S3
uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37}; uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37};
uint8_t addrPins[] = {35, 36, 48, 45, 21}; uint8_t addrPins[] = {45, 36, 48, 35, 21};
uint8_t clockPin = 2; uint8_t clockPin = 2;
uint8_t latchPin = 47; uint8_t latchPin = 47;
uint8_t oePin = 14; uint8_t oePin = 14;
@ -38,6 +38,13 @@ supported boards. Notes have been moved to the bottom of the code.
uint8_t clockPin = 13; uint8_t clockPin = 13;
uint8_t latchPin = 0; uint8_t latchPin = 0;
uint8_t oePin = 1; uint8_t oePin = 1;
#elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32C6) // Feather ESP32-C6
// not featherwing compatible, but can 'hand wire' if desired
uint8_t rgbPins[] = {6, A3, A1, A0, A2, 0};
uint8_t addrPins[] = {8, 5, 15, 7};
uint8_t clockPin = 14;
uint8_t latchPin = RX;
uint8_t oePin = TX;
#elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S2) // Feather ESP32-S2 #elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S2) // Feather ESP32-S2
// M0/M4/RP2040 Matrix FeatherWing compatible: // M0/M4/RP2040 Matrix FeatherWing compatible:
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12}; uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
@ -59,11 +66,11 @@ supported boards. Notes have been moved to the bottom of the code.
uint8_t latchPin = 0; uint8_t latchPin = 0;
uint8_t oePin = 1; uint8_t oePin = 1;
#elif defined(_SAMD21_) // Feather M0 variants #elif defined(_SAMD21_) // Feather M0 variants
uint8_t rgbPins[] = {6, 7, 10, 11, 12, 13}; uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {0, 1, 2, 3}; uint8_t addrPins[] = {A5, A4, A3, A2};
uint8_t clockPin = SDA; uint8_t clockPin = 13;
uint8_t latchPin = 4; uint8_t latchPin = 0;
uint8_t oePin = 5; uint8_t oePin = 1;
#elif defined(NRF52_SERIES) // Special nRF52840 FeatherWing pinout #elif defined(NRF52_SERIES) // Special nRF52840 FeatherWing pinout
uint8_t rgbPins[] = {6, A5, A1, A0, A4, 11}; uint8_t rgbPins[] = {6, A5, A1, A0, A4, 11};
uint8_t addrPins[] = {10, 5, 13, 9}; uint8_t addrPins[] = {10, 5, 13, 9};

View file

@ -25,7 +25,7 @@ supported boards.
uint8_t oePin = 16; uint8_t oePin = 16;
#elif defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) // MatrixPortal ESP32-S3 #elif defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) // MatrixPortal ESP32-S3
uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37}; uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37};
uint8_t addrPins[] = {35, 36, 48, 45, 21}; uint8_t addrPins[] = {45, 36, 48, 35, 21};
uint8_t clockPin = 2; uint8_t clockPin = 2;
uint8_t latchPin = 47; uint8_t latchPin = 47;
uint8_t oePin = 14; uint8_t oePin = 14;

View file

@ -1,5 +1,5 @@
name=Adafruit Protomatter name=Adafruit Protomatter
version=1.5.5 version=1.7.0
author=Adafruit author=Adafruit
maintainer=Adafruit <info@adafruit.com> maintainer=Adafruit <info@adafruit.com>
sentence=A library for Adafruit RGB LED matrices. sentence=A library for Adafruit RGB LED matrices.

View file

@ -144,6 +144,16 @@ public:
*/ */
uint16_t colorHSV(uint16_t hue, uint8_t sat = 255, uint8_t val = 255); uint16_t colorHSV(uint16_t hue, uint8_t sat = 255, uint8_t val = 255);
/*!
@brief Adjust HUB clock signal duty cycle on architectures that support
this (currently SAMD51 only) (else ignored).
@param Duty setting, 0 minimum. Increasing values generate higher clock
duty cycles at the same frequency. Arbitrary granular units, max
varies by architecture and CPU speed, if supported at all.
e.g. SAMD51 @ 120 MHz supports 0 (~50% duty) through 2 (~75%).
*/
void setDuty(uint8_t d) { _PM_setDuty(d); };
private: private:
Protomatter_core core; // Underlying C struct Protomatter_core core; // Underlying C struct
void convert_byte(uint8_t *dest); // GFXcanvas16-to-matrix void convert_byte(uint8_t *dest); // GFXcanvas16-to-matrix

View file

@ -192,6 +192,7 @@ _PM_CUSTOM_BLAST If defined, instructs core code to not compile
#include "esp32-s2.h" #include "esp32-s2.h"
#include "esp32-s3.h" #include "esp32-s3.h"
#include "esp32-c3.h" #include "esp32-c3.h"
#include "esp32-c6.h"
#include "nrf52.h" #include "nrf52.h"
#include "rp2040.h" #include "rp2040.h"
#include "samd-common.h" #include "samd-common.h"
@ -242,3 +243,11 @@ _PM_CUSTOM_BLAST If defined, instructs core code to not compile
#if !defined(_PM_PORT_TYPE) #if !defined(_PM_PORT_TYPE)
#define _PM_PORT_TYPE uint32_t ///< PORT register size/type #define _PM_PORT_TYPE uint32_t ///< PORT register size/type
#endif #endif
#if !defined(_PM_maxDuty)
#define _PM_maxDuty 0 ///< Max duty cycle setting (where supported)
#endif
#if !defined(_PM_defaultDuty)
#define _PM_defaultDuty 0 ///< Default duty cycle setting (where supported)
#endif

View file

@ -42,6 +42,7 @@
// No special peripheral setup on ESP32C3, just use common timer init... // No special peripheral setup on ESP32C3, just use common timer init...
#define _PM_timerInit(core) _PM_esp32commonTimerInit(core); #define _PM_timerInit(core) _PM_esp32commonTimerInit(core);
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
// Return current count value (timer enabled or not). // Return current count value (timer enabled or not).
// Timer must be previously initialized. // Timer must be previously initialized.
// This function is the same on all ESP32 parts EXCEPT S3. // This function is the same on all ESP32 parts EXCEPT S3.
@ -49,8 +50,6 @@ IRAM_ATTR inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
return (uint32_t)timerRead((hw_timer_t *)core->timer); return (uint32_t)timerRead((hw_timer_t *)core->timer);
} }
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON -------------------- #elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
#endif // END CIRCUITPYTHON ------------------------------------------------ #endif // END CIRCUITPYTHON ------------------------------------------------

57
src/arch/esp32-c6.h Normal file
View file

@ -0,0 +1,57 @@
/*!
* @file esp32-c3.h
*
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
* This file contains ESP32-C3-SPECIFIC CODE.
*
* Adafruit invests time and resources providing this open source code,
* please support Adafruit and open-source hardware by purchasing
* products from Adafruit!
*
* Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for
* Adafruit Industries, with contributions from the open source community.
*
* BSD license, all text here must be included in any redistribution.
*
*/
#pragma once
// NOTE: there is some intentional repetition in the macros and functions
// for some ESP32 variants. Previously they were all one file, but complex
// preprocessor directives were turning into spaghetti. THEREFORE, if making
// a change or bugfix in one variant-specific header, check the others to
// see if the same should be applied!
#if defined(CONFIG_IDF_TARGET_ESP32C6)
#define _PM_portOutRegister(pin) (volatile uint32_t *)&GPIO.out
#define _PM_portSetRegister(pin) (volatile uint32_t *)&GPIO.out_w1ts
#define _PM_portClearRegister(pin) (volatile uint32_t *)&GPIO.out_w1tc
#define _PM_portBitMask(pin) (1U << ((pin) & 31))
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define _PM_byteOffset(pin) ((pin & 31) / 8)
#define _PM_wordOffset(pin) ((pin & 31) / 16)
#else
#define _PM_byteOffset(pin) (3 - ((pin & 31) / 8))
#define _PM_wordOffset(pin) (1 - ((pin & 31) / 16))
#endif
// No special peripheral setup on ESP32C3, just use common timer init...
#define _PM_timerInit(core) _PM_esp32commonTimerInit(core);
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
// Return current count value (timer enabled or not).
// Timer must be previously initialized.
// This function is the same on all ESP32 parts EXCEPT S3.
IRAM_ATTR inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
return (uint32_t)timerRead((hw_timer_t *)core->timer);
}
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
#endif // END CIRCUITPYTHON ------------------------------------------------
#endif // END ESP32C3

View file

@ -17,7 +17,12 @@
#pragma once #pragma once
#if defined(ESP32) // *All* ESP32 variants (OG, S2, S3, etc.) #if defined(ESP32) || \
defined(ESP_PLATFORM) // *All* ESP32 variants (OG, S2, S3, etc.)
#include <inttypes.h>
#include "esp_idf_version.h"
// NOTE: there is some intentional repetition in the macros and functions // NOTE: there is some intentional repetition in the macros and functions
// for some ESP32 variants. Previously they were all one file, but complex // for some ESP32 variants. Previously they were all one file, but complex
@ -25,18 +30,23 @@
// a change or bugfix in one variant-specific header, check the others to // a change or bugfix in one variant-specific header, check the others to
// see if the same should be applied! // see if the same should be applied!
#include "driver/timer.h"
#include "soc/gpio_periph.h" #include "soc/gpio_periph.h"
// As currently written, only one instance of the Protomatter_core struct // As currently written, only one instance of the Protomatter_core struct
// is allowed, set up when calling begin()...so it's just a global here: // is allowed, set up when calling begin()...so it's just a global here:
void *_PM_protoPtr = NULL; Protomatter_core *_PM_protoPtr;
#define _PM_timerFreq 40000000 // 40 MHz (1:2 prescale) #define _PM_timerFreq 40000000 // 40 MHz (1:2 prescale)
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------ #if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
#define _PM_timerNum 0 // Timer #0 (can be 0-3) #define _PM_timerNum 0 // Timer #0 (can be 0-3)
static hw_timer_t *_PM_esp32timer = NULL;
#define _PM_TIMER_DEFAULT &_PM_esp32timer
#else
#define _PM_TIMER_DEFAULT ((void *)-1) // some non-NULL but non valid pointer
#endif
// The following defines and functions are common to all ESP32 variants in // The following defines and functions are common to all ESP32 variants in
// the Arduino platform. Anything unique to one variant (or a subset of // the Arduino platform. Anything unique to one variant (or a subset of
@ -45,16 +55,7 @@ void *_PM_protoPtr = NULL;
// started down that path, it's okay, but move the code out of here and // started down that path, it's okay, but move the code out of here and
// into the variant-specific headers. // into the variant-specific headers.
// This is the default aforementioned singular timer. IN THEORY, other extern void _PM_row_handler(Protomatter_core *core); // In core.c
// timers could be used, IF an Arduino sketch passes the address of its
// own hw_timer_t* to the Protomatter constructor and initializes that
// timer using ESP32's timerBegin(). All of the timer-related functions
// below pass around a handle rather than accessing _PM_esp32timer directly,
// in case that's ever actually used in the future.
static hw_timer_t *_PM_esp32timer = NULL;
#define _PM_TIMER_DEFAULT &_PM_esp32timer
extern IRAM_ATTR void _PM_row_handler(Protomatter_core *core); // In core.c
// Timer interrupt handler. This, _PM_row_handler() and any functions // Timer interrupt handler. This, _PM_row_handler() and any functions
// called by _PM_row_handler() should all have the IRAM_ATTR attribute // called by _PM_row_handler() should all have the IRAM_ATTR attribute
@ -68,9 +69,15 @@ IRAM_ATTR static void _PM_esp32timerCallback(void) {
// Set timer period, initialize count value to zero, enable timer. // Set timer period, initialize count value to zero, enable timer.
IRAM_ATTR inline void _PM_timerStart(Protomatter_core *core, uint32_t period) { IRAM_ATTR inline void _PM_timerStart(Protomatter_core *core, uint32_t period) {
hw_timer_t *timer = (hw_timer_t *)core->timer; hw_timer_t *timer = (hw_timer_t *)core->timer;
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
timerAlarmWrite(timer, period, true); timerAlarmWrite(timer, period, true);
timerAlarmEnable(timer); timerAlarmEnable(timer);
timerStart(timer); timerStart(timer);
#else
timerWrite(timer, 0);
timerAlarm(timer, period ? period : 1, true, 0);
timerStart(timer);
#endif
} }
// Disable timer and return current count value. // Disable timer and return current count value.
@ -84,11 +91,19 @@ IRAM_ATTR uint32_t _PM_timerStop(Protomatter_core *core) {
// that's common to all ESP32 variants; code in variant-specific files might // that's common to all ESP32 variants; code in variant-specific files might
// set up its own special peripherals, then call this. // set up its own special peripherals, then call this.
void _PM_esp32commonTimerInit(Protomatter_core *core) { void _PM_esp32commonTimerInit(Protomatter_core *core) {
hw_timer_t *timer = (hw_timer_t *)core->timer; // pointer-to-pointer hw_timer_t *timer_in = (hw_timer_t *)core->timer;
if (timer == _PM_TIMER_DEFAULT) { if (!timer_in || timer_in == _PM_TIMER_DEFAULT) {
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
core->timer = timerBegin(_PM_timerNum, 2, true); // 1:2 prescale, count up core->timer = timerBegin(_PM_timerNum, 2, true); // 1:2 prescale, count up
#else
core->timer = timerBegin(_PM_timerFreq);
#endif
} }
timerAttachInterrupt(timer, &_PM_esp32timerCallback, true); #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
timerAttachInterrupt(core->timer, &_PM_esp32timerCallback, true);
#else
timerAttachInterrupt(core->timer, _PM_esp32timerCallback);
#endif
} }
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON -------------------- #elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
@ -103,7 +118,12 @@ void _PM_esp32commonTimerInit(Protomatter_core *core) {
#include "driver/gpio.h" #include "driver/gpio.h"
#include "esp_idf_version.h" #include "esp_idf_version.h"
#include "hal/timer_ll.h" #include "hal/timer_ll.h"
#include "peripherals/timer.h" #if ESP_IDF_VERSION_MAJOR == 5
#include "driver/gptimer.h"
#include "esp_memory_utils.h"
#else
#include "driver/timer.h"
#endif
#define _PM_TIMER_DEFAULT NULL #define _PM_TIMER_DEFAULT NULL
#define _PM_pinOutput(pin) gpio_set_direction((pin), GPIO_MODE_OUTPUT) #define _PM_pinOutput(pin) gpio_set_direction((pin), GPIO_MODE_OUTPUT)
@ -115,8 +135,24 @@ void _PM_esp32commonTimerInit(Protomatter_core *core) {
// (RAM-resident functions). This isn't really the ISR itself, but a // (RAM-resident functions). This isn't really the ISR itself, but a
// callback invoked by the real ISR (in arduino-esp32's esp32-hal-timer.c) // callback invoked by the real ISR (in arduino-esp32's esp32-hal-timer.c)
// which takes care of interrupt status bits & such. // which takes care of interrupt status bits & such.
IRAM_ATTR bool _PM_esp32timerCallback(void *unused) { #if ESP_IDF_VERSION_MAJOR == 5
// This is "private" for now. We link to it anyway because there isn't a more
// public method yet.
extern bool spi_flash_cache_enabled(void);
static IRAM_ATTR bool
_PM_esp32timerCallback(gptimer_handle_t timer,
const gptimer_alarm_event_data_t *event, void *unused) {
#else
static IRAM_ATTR bool _PM_esp32timerCallback(void *unused) {
#endif
#if ESP_IDF_VERSION_MAJOR == 5
// Some functions and data used by _PM_row_handler may exist in external flash
// or PSRAM so we can't run them when their access is disabled (through the
// flash cache.)
if (_PM_protoPtr && spi_flash_cache_enabled()) {
#else
if (_PM_protoPtr) { if (_PM_protoPtr) {
#endif
_PM_row_handler(_PM_protoPtr); // In core.c _PM_row_handler(_PM_protoPtr); // In core.c
} }
return false; return false;
@ -125,13 +161,15 @@ IRAM_ATTR bool _PM_esp32timerCallback(void *unused) {
// Set timer period, initialize count value to zero, enable timer. // Set timer period, initialize count value to zero, enable timer.
#if (ESP_IDF_VERSION_MAJOR == 5) #if (ESP_IDF_VERSION_MAJOR == 5)
IRAM_ATTR void _PM_timerStart(Protomatter_core *core, uint32_t period) { IRAM_ATTR void _PM_timerStart(Protomatter_core *core, uint32_t period) {
timer_index_t *timer = (timer_index_t *)core->timer; gptimer_handle_t timer = (gptimer_handle_t)core->timer;
timer_ll_enable_counter(timer->hw, timer->idx, false);
timer_ll_set_reload_value(timer->hw, timer->idx, 0); gptimer_alarm_config_t alarm_config = {
timer_ll_trigger_soft_reload(timer->hw, timer->idx); .reload_count = 0, // counter will reload with 0 on alarm event
timer_ll_set_alarm_value(timer->hw, timer->idx, period); .alarm_count = period, // period in ms
timer_ll_enable_alarm(timer->hw, timer->idx, true); .flags.auto_reload_on_alarm = true, // enable auto-reload
timer_ll_enable_counter(timer->hw, timer->idx, true); };
gptimer_set_alarm_action(timer, &alarm_config);
gptimer_start(timer);
} }
#else #else
IRAM_ATTR void _PM_timerStart(Protomatter_core *core, uint32_t period) { IRAM_ATTR void _PM_timerStart(Protomatter_core *core, uint32_t period) {
@ -147,30 +185,46 @@ IRAM_ATTR void _PM_timerStart(Protomatter_core *core, uint32_t period) {
// Disable timer and return current count value. // Disable timer and return current count value.
// Timer must be previously initialized. // Timer must be previously initialized.
IRAM_ATTR uint32_t _PM_timerStop(Protomatter_core *core) { IRAM_ATTR uint32_t _PM_timerStop(Protomatter_core *core) {
timer_index_t *timer = (timer_index_t *)core->timer;
#if (ESP_IDF_VERSION_MAJOR == 5) #if (ESP_IDF_VERSION_MAJOR == 5)
timer_ll_enable_counter(timer->hw, timer->idx, false); gptimer_handle_t timer = (gptimer_handle_t)core->timer;
gptimer_stop(timer);
#else #else
timer_index_t *timer = (timer_index_t *)core->timer;
timer_ll_set_counter_enable(timer->hw, timer->idx, false); timer_ll_set_counter_enable(timer->hw, timer->idx, false);
#endif #endif
return _PM_timerGetCount(core); return _PM_timerGetCount(core);
} }
#if !defined(CONFIG_IDF_TARGET_ESP32S3)
IRAM_ATTR uint32_t _PM_timerGetCount(Protomatter_core *core) { IRAM_ATTR uint32_t _PM_timerGetCount(Protomatter_core *core) {
timer_index_t *timer = (timer_index_t *)core->timer; #if (ESP_IDF_VERSION_MAJOR == 5)
#ifdef CONFIG_IDF_TARGET_ESP32S3 gptimer_handle_t timer = (gptimer_handle_t)core->timer;
timer->hw->hw_timer[timer->idx].update.tn_update = 1; uint64_t raw_count;
return timer->hw->hw_timer[timer->idx].lo.tn_lo; gptimer_get_raw_count(timer, &raw_count);
return (uint32_t)raw_count;
#else #else
timer->hw->hw_timer[timer->idx].update.tx_update = 1; timer_index_t *timer = (timer_index_t *)core->timer;
return timer->hw->hw_timer[timer->idx].lo.tx_lo; uint64_t result;
timer_ll_get_counter_value(timer->hw, timer->idx, &result);
return (uint32_t)result;
#endif #endif
} }
#endif
// Initialize, but do not start, timer. This function contains timer setup // Initialize, but do not start, timer. This function contains timer setup
// that's common to all ESP32 variants; code in variant-specific files might // that's common to all ESP32 variants; code in variant-specific files might
// set up its own special peripherals, then call this. // set up its own special peripherals, then call this.
void _PM_esp32commonTimerInit(Protomatter_core *core) { static void _PM_esp32commonTimerInit(Protomatter_core *core) {
#if (ESP_IDF_VERSION_MAJOR == 5)
gptimer_handle_t timer = (gptimer_handle_t)core->timer;
gptimer_event_callbacks_t cbs = {
.on_alarm = _PM_esp32timerCallback, // register user callback
};
gptimer_register_event_callbacks(timer, &cbs, NULL);
gptimer_enable(timer);
#else
timer_index_t *timer = (timer_index_t *)core->timer; timer_index_t *timer = (timer_index_t *)core->timer;
const timer_config_t config = { const timer_config_t config = {
.alarm_en = false, .alarm_en = false,
@ -185,6 +239,7 @@ void _PM_esp32commonTimerInit(Protomatter_core *core) {
timer_isr_callback_add(timer->group, timer->idx, _PM_esp32timerCallback, NULL, timer_isr_callback_add(timer->group, timer->idx, _PM_esp32timerCallback, NULL,
0); 0);
timer_enable_intr(timer->group, timer->idx); timer_enable_intr(timer->group, timer->idx);
#endif
} }
#endif // END CIRCUITPYTHON ------------------------------------------------ #endif // END CIRCUITPYTHON ------------------------------------------------

View file

@ -139,13 +139,6 @@ IRAM_ATTR static void blast_byte(Protomatter_core *core, uint8_t *data) {
IRAM_ATTR static void blast_word(Protomatter_core *core, uint16_t *data) {} IRAM_ATTR static void blast_word(Protomatter_core *core, uint16_t *data) {}
IRAM_ATTR static void blast_long(Protomatter_core *core, uint32_t *data) {} IRAM_ATTR static void blast_long(Protomatter_core *core, uint32_t *data) {}
// Return current count value (timer enabled or not).
// Timer must be previously initialized.
// This function is the same on all ESP32 parts EXCEPT S3.
IRAM_ATTR inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
return (uint32_t)timerRead((hw_timer_t *)core->timer);
}
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------ #if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
void _PM_timerInit(Protomatter_core *core) { void _PM_timerInit(Protomatter_core *core) {
@ -174,6 +167,13 @@ void _PM_timerInit(Protomatter_core *core) {
_PM_esp32commonTimerInit(core); // In esp32-common.h _PM_esp32commonTimerInit(core); // In esp32-common.h
} }
// Return current count value (timer enabled or not).
// Timer must be previously initialized.
// This function is the same on all ESP32 parts EXCEPT S3.
IRAM_ATTR inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
return (uint32_t)timerRead((hw_timer_t *)core->timer);
}
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON -------------------- #elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
void _PM_timerInit(Protomatter_core *core) { void _PM_timerInit(Protomatter_core *core) {

View file

@ -25,6 +25,14 @@
#if defined(CONFIG_IDF_TARGET_ESP32S3) #if defined(CONFIG_IDF_TARGET_ESP32S3)
#define GPIO_DRIVE_STRENGTH GPIO_DRIVE_CAP_3
#define LCD_CLK_PRESCALE 9 // 8, 9, 10 allowed. Bit clock = 160 MHz / this.
#if defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
#include "components/esp_rom/include/esp_rom_sys.h"
#include "components/heap/include/esp_heap_caps.h"
#endif
// Use DMA-capable RAM (not PSRAM) for framebuffer: // Use DMA-capable RAM (not PSRAM) for framebuffer:
#define _PM_allocate(x) heap_caps_malloc(x, MALLOC_CAP_DMA | MALLOC_CAP_8BIT) #define _PM_allocate(x) heap_caps_malloc(x, MALLOC_CAP_DMA | MALLOC_CAP_8BIT)
#define _PM_free(x) heap_caps_free(x) #define _PM_free(x) heap_caps_free(x)
@ -57,16 +65,16 @@
// dmaSetupTime (measured in blast_byte()) measures the number of timer // dmaSetupTime (measured in blast_byte()) measures the number of timer
// cycles to set up and trigger the DMA transfer... // cycles to set up and trigger the DMA transfer...
static uint32_t dmaSetupTime = 100; static uint32_t dmaSetupTime = 100;
// ...then, the version of _PM_timerGetCount() here uses that figure as a // ...then, the version of _PM_timerGetCount() here uses that as a starting
// starting point, plus the known constant DMA transfer speed (20 MHz) and // point, plus the known constant DMA xfer speed (160/LCD_CLK_PRESCALE MHz)
// timer frequency (40 MHz), i.e. 2 cycles/column, to return a fair estimate // and timer frequency (40 MHz), to return an estimate of the one-scanline
// of the one-scanline transfer time, from which everything is extrapolated: // transfer time, from which everything is extrapolated:
IRAM_ATTR inline uint32_t _PM_timerGetCount(Protomatter_core *core) { IRAM_ATTR inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
// Time estimate seems to come in a little high, so the -25 here is an // Time estimate seems to come in a little high, so the -10 here is an
// empirically-derived fudge factor that may yield ever-so-slightly better // empirically-derived fudge factor that may yield ever-so-slightly better
// refresh in some edge cases. If visual glitches are encountered, might // refresh in some edge cases. If visual glitches are encountered, might
// need to dial back this number a bit. // need to dial back this number a bit or remove it.
return dmaSetupTime + core->chainBits * 2 - 25; return dmaSetupTime + core->chainBits * 40 * LCD_CLK_PRESCALE / 160 - 10;
} }
// Note that dmaSetupTime can vary from line to line, potentially influenced // Note that dmaSetupTime can vary from line to line, potentially influenced
// by interrupts, nondeterministic DMA channel clearing times, etc., which is // by interrupts, nondeterministic DMA channel clearing times, etc., which is
@ -74,13 +82,15 @@ IRAM_ATTR inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
// slightly different length of time, but duty cycle scales with this so it's // slightly different length of time, but duty cycle scales with this so it's
// perceptually consistent; don't see bright or dark rows. // perceptually consistent; don't see bright or dark rows.
#define _PM_minMinPeriod (200 + core->chainBits * 2) #define _PM_minMinPeriod \
(200 + (uint32_t)core->chainBits * 40 * LCD_CLK_PRESCALE / 160)
#if (ESP_IDF_VERSION_MAJOR == 5) #if (ESP_IDF_VERSION_MAJOR == 5)
#include <esp_private/periph_ctrl.h> #include <esp_private/periph_ctrl.h>
#else #else
#include <driver/periph_ctrl.h> #include <driver/periph_ctrl.h>
#endif #endif
#include <driver/gpio.h>
#include <esp_private/gdma.h> #include <esp_private/gdma.h>
#include <esp_rom_gpio.h> #include <esp_rom_gpio.h>
#include <hal/dma_types.h> #include <hal/dma_types.h>
@ -112,6 +122,17 @@ static uint32_t _PM_directBitMask(Protomatter_core *core, int pin) {
static dma_descriptor_t desc; static dma_descriptor_t desc;
static gdma_channel_handle_t dma_chan; static gdma_channel_handle_t dma_chan;
// If using custom "blast" function(s), all three must be declared.
// Unused ones can be empty, that's fine, just need to exist.
IRAM_ATTR static void blast_word(Protomatter_core *core, uint16_t *data) {}
IRAM_ATTR static void blast_long(Protomatter_core *core, uint32_t *data) {}
static void pinmux(int8_t pin, uint8_t signal) {
esp_rom_gpio_connect_out_signal(pin, signal, false, false);
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[pin], PIN_FUNC_GPIO);
gpio_set_drive_capability((gpio_num_t)pin, GPIO_DRIVE_STRENGTH);
}
// LCD_CAM requires a complete replacement of the "blast" functions in order // LCD_CAM requires a complete replacement of the "blast" functions in order
// to use the DMA-based peripheral. // to use the DMA-based peripheral.
#define _PM_CUSTOM_BLAST // Disable blast_*() functions in core.c #define _PM_CUSTOM_BLAST // Disable blast_*() functions in core.c
@ -139,24 +160,23 @@ IRAM_ATTR static void blast_byte(Protomatter_core *core, uint8_t *data) {
// Timer was cleared to 0 before calling blast_byte(), so this // Timer was cleared to 0 before calling blast_byte(), so this
// is the state of the timer immediately after DMA started: // is the state of the timer immediately after DMA started:
#if defined(ARDUINO)
dmaSetupTime = (uint32_t)timerRead((hw_timer_t *)core->timer); dmaSetupTime = (uint32_t)timerRead((hw_timer_t *)core->timer);
#elif defined(CIRCUITPY)
uint64_t value;
#if (ESP_IDF_VERSION_MAJOR == 5)
gptimer_handle_t timer = (gptimer_handle_t)core->timer;
gptimer_get_raw_count(timer, &value);
#else
timer_index_t *timer = (timer_index_t *)core->timer;
timer_get_counter_value(timer->group, timer->idx, &value);
#endif
dmaSetupTime = (uint32_t)value;
#endif
// See notes near top of this file for what's done with this info. // See notes near top of this file for what's done with this info.
} }
// If using custom "blast" function(s), all three must be declared. static void _PM_timerInit(Protomatter_core *core) {
// Unused ones can be empty, that's fine, just need to exist.
IRAM_ATTR static void blast_word(Protomatter_core *core, uint16_t *data) {}
IRAM_ATTR static void blast_long(Protomatter_core *core, uint32_t *data) {}
static void pinmux(int8_t pin, uint8_t signal) {
esp_rom_gpio_connect_out_signal(pin, signal, false, false);
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[pin], PIN_FUNC_GPIO);
gpio_set_drive_capability((gpio_num_t)pin, GPIO_DRIVE_CAP_MAX);
}
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
void _PM_timerInit(Protomatter_core *core) {
// On S3, initialize the LCD_CAM peripheral and DMA. // On S3, initialize the LCD_CAM peripheral and DMA.
// LCD_CAM isn't enabled by default -- MUST begin with this: // LCD_CAM isn't enabled by default -- MUST begin with this:
@ -171,8 +191,14 @@ void _PM_timerInit(Protomatter_core *core) {
LCD_CAM.lcd_clock.clk_en = 1; // Enable clock LCD_CAM.lcd_clock.clk_en = 1; // Enable clock
LCD_CAM.lcd_clock.lcd_clk_sel = 3; // PLL160M source LCD_CAM.lcd_clock.lcd_clk_sel = 3; // PLL160M source
LCD_CAM.lcd_clock.lcd_clkm_div_a = 1; // 1/1 fractional divide, LCD_CAM.lcd_clock.lcd_clkm_div_a = 1; // 1/1 fractional divide,
LCD_CAM.lcd_clock.lcd_clkm_div_b = 1; // plus '7' below yields... LCD_CAM.lcd_clock.lcd_clkm_div_b = 1; // plus prescale below yields...
#if LCD_CLK_PRESCALE == 8
LCD_CAM.lcd_clock.lcd_clkm_div_num = 7; // 1:8 prescale (20 MHz CLK) LCD_CAM.lcd_clock.lcd_clkm_div_num = 7; // 1:8 prescale (20 MHz CLK)
#elif LCD_CLK_PRESCALE == 9
LCD_CAM.lcd_clock.lcd_clkm_div_num = 8; // 1:9 prescale (17.8 MHz CLK)
#else
LCD_CAM.lcd_clock.lcd_clkm_div_num = 9; // 1:10 prescale (16 MHz CLK)
#endif
LCD_CAM.lcd_clock.lcd_ck_out_edge = 0; // PCLK low in first half of cycle LCD_CAM.lcd_clock.lcd_ck_out_edge = 0; // PCLK low in first half of cycle
LCD_CAM.lcd_clock.lcd_ck_idle_edge = 0; // PCLK low idle LCD_CAM.lcd_clock.lcd_ck_idle_edge = 0; // PCLK low idle
LCD_CAM.lcd_clock.lcd_clk_equ_sysclk = 1; // PCLK = CLK (ignore CLKCNT_N) LCD_CAM.lcd_clock.lcd_clk_equ_sysclk = 1; // PCLK = CLK (ignore CLKCNT_N)
@ -208,113 +234,10 @@ void _PM_timerInit(Protomatter_core *core) {
for (int i = 0; i < 6; i++) for (int i = 0; i < 6; i++)
pinmux(core->rgbPins[i], signal[i]); pinmux(core->rgbPins[i], signal[i]);
pinmux(core->clockPin, LCD_PCLK_IDX); pinmux(core->clockPin, LCD_PCLK_IDX);
gpio_set_drive_capability(core->latch.pin, GPIO_DRIVE_CAP_MAX); gpio_set_drive_capability(core->latch.pin, GPIO_DRIVE_STRENGTH);
gpio_set_drive_capability(core->oe.pin, GPIO_DRIVE_CAP_MAX); gpio_set_drive_capability(core->oe.pin, GPIO_DRIVE_STRENGTH);
for (uint8_t i = 0; i < core->numAddressLines; i++) { for (uint8_t i = 0; i < core->numAddressLines; i++) {
gpio_set_drive_capability(core->addr[i].pin, GPIO_DRIVE_CAP_MAX); gpio_set_drive_capability(core->addr[i].pin, GPIO_DRIVE_STRENGTH);
}
// Disable LCD_CAM interrupts, clear any pending interrupt
LCD_CAM.lc_dma_int_ena.val &= ~LCD_LL_EVENT_TRANS_DONE;
LCD_CAM.lc_dma_int_clr.val = 0x03;
// Set up DMA TX descriptor
desc.dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
desc.dw0.suc_eof = 1;
desc.dw0.size = desc.dw0.length = core->chainBits;
desc.buffer = core->screenData;
desc.next = NULL;
// Alloc DMA channel & connect it to LCD periph
gdma_channel_alloc_config_t dma_chan_config = {
.sibling_chan = NULL,
.direction = GDMA_CHANNEL_DIRECTION_TX,
.flags = {.reserve_sibling = 0}};
esp_err_t ret = gdma_new_channel(&dma_chan_config, &dma_chan);
gdma_connect(dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0));
gdma_strategy_config_t strategy_config = {.owner_check = false,
.auto_update_desc = false};
gdma_apply_strategy(dma_chan, &strategy_config);
gdma_transfer_ability_t ability = {
.sram_trans_align = 0,
.psram_trans_align = 0,
};
gdma_set_transfer_ability(dma_chan, &ability);
gdma_start(dma_chan, (intptr_t)&desc);
// Enable TRANS_DONE interrupt. Note that we do NOT require nor install
// an interrupt service routine, but DO need to enable the TRANS_DONE
// flag to make the LCD DMA transfer work.
LCD_CAM.lc_dma_int_ena.val |= LCD_LL_EVENT_TRANS_DONE & 0x03;
_PM_esp32commonTimerInit(core); // In esp32-common.h
}
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
void _PM_timerInit(Protomatter_core *core) {
// TO DO: adapt this function for any CircuitPython-specific changes.
// If none are required, this function can be deleted and the version
// above can be moved before the ARDUIO/CIRCUITPY checks. If minimal
// changes, consider a single _PM_timerInit() implementation with
// ARDUINO/CIRCUITPY checks inside. It's all good.
// On S3, initialize the LCD_CAM peripheral and DMA.
// LCD_CAM isn't enabled by default -- MUST begin with this:
periph_module_enable(PERIPH_LCD_CAM_MODULE);
periph_module_reset(PERIPH_LCD_CAM_MODULE);
// Reset LCD bus
LCD_CAM.lcd_user.lcd_reset = 1;
esp_rom_delay_us(100);
// Configure LCD clock
LCD_CAM.lcd_clock.clk_en = 1; // Enable clock
LCD_CAM.lcd_clock.lcd_clk_sel = 3; // PLL160M source
LCD_CAM.lcd_clock.lcd_clkm_div_a = 1; // 1/1 fractional divide,
LCD_CAM.lcd_clock.lcd_clkm_div_b = 1; // plus '7' below yields...
LCD_CAM.lcd_clock.lcd_clkm_div_num = 7; // 1:8 prescale (20 MHz CLK)
LCD_CAM.lcd_clock.lcd_ck_out_edge = 0; // PCLK low in first half of cycle
LCD_CAM.lcd_clock.lcd_ck_idle_edge = 0; // PCLK low idle
LCD_CAM.lcd_clock.lcd_clk_equ_sysclk = 1; // PCLK = CLK (ignore CLKCNT_N)
// Configure frame format. Some of these could probably be skipped and
// use defaults, but being verbose for posterity...
LCD_CAM.lcd_ctrl.lcd_rgb_mode_en = 0; // i8080 mode (not RGB)
LCD_CAM.lcd_rgb_yuv.lcd_conv_bypass = 0; // Disable RGB/YUV converter
LCD_CAM.lcd_misc.lcd_next_frame_en = 0; // Do NOT auto-frame
LCD_CAM.lcd_data_dout_mode.val = 0; // No data delays
LCD_CAM.lcd_user.lcd_always_out_en = 0; // Only when requested
LCD_CAM.lcd_user.lcd_8bits_order = 0; // Do not swap bytes
LCD_CAM.lcd_user.lcd_bit_order = 0; // Do not reverse bit order
LCD_CAM.lcd_user.lcd_2byte_en = 0; // 8-bit data mode
// MUST enable at least one dummy phase at start of output, else clock and
// data are randomly misaligned by 1-2 cycles following required TX FIFO
// reset in blast_byte(). One phase MOSTLY works but sparkles a tiny bit
// (as in still very occasionally misaligned by 1 cycle). Two seems ideal;
// no sparkle. Since HUB75 is just a shift register, the extra clock ticks
// are harmless and the zero-data shifts off end of the chain.
LCD_CAM.lcd_user.lcd_dummy = 1; // Enable dummy phase(s) @ LCD start
LCD_CAM.lcd_user.lcd_dummy_cyclelen = 1; // 2 dummy phases
LCD_CAM.lcd_user.lcd_cmd = 0; // No command at LCD start
LCD_CAM.lcd_user.lcd_cmd_2_cycle_en = 0;
LCD_CAM.lcd_user.lcd_update = 1;
// Configure signal pins. IN THEORY this could be expanded to support
// 2 parallel chains, but the rest of the LCD & DMA setup is not currently
// written for that, so it's limited to a single chain for now.
const uint8_t signal[] = {LCD_DATA_OUT0_IDX, LCD_DATA_OUT1_IDX,
LCD_DATA_OUT2_IDX, LCD_DATA_OUT3_IDX,
LCD_DATA_OUT4_IDX, LCD_DATA_OUT5_IDX};
for (int i = 0; i < 6; i++)
pinmux(core->rgbPins[i], signal[i]);
pinmux(core->clockPin, LCD_PCLK_IDX);
gpio_set_drive_capability(core->latch.pin, GPIO_DRIVE_CAP_MAX);
gpio_set_drive_capability(core->oe.pin, GPIO_DRIVE_CAP_MAX);
for (uint8_t i = 0; i < core->numAddressLines; i++) {
gpio_set_drive_capability(core->addr[i].pin, GPIO_DRIVE_CAP_MAX);
} }
// Disable LCD_CAM interrupts, clear any pending interrupt // Disable LCD_CAM interrupts, clear any pending interrupt
@ -329,6 +252,9 @@ void _PM_timerInit(Protomatter_core *core) {
desc.next = NULL; desc.next = NULL;
// Alloc DMA channel & connect it to LCD periph // Alloc DMA channel & connect it to LCD periph
#if defined(CIRCUITPY)
if (dma_chan == NULL) {
#endif
gdma_channel_alloc_config_t dma_chan_config = { gdma_channel_alloc_config_t dma_chan_config = {
.sibling_chan = NULL, .sibling_chan = NULL,
.direction = GDMA_CHANNEL_DIRECTION_TX, .direction = GDMA_CHANNEL_DIRECTION_TX,
@ -343,6 +269,9 @@ void _PM_timerInit(Protomatter_core *core) {
.psram_trans_align = 0, .psram_trans_align = 0,
}; };
gdma_set_transfer_ability(dma_chan, &ability); gdma_set_transfer_ability(dma_chan, &ability);
#if defined(CIRCUITPY)
}
#endif
gdma_start(dma_chan, (intptr_t)&desc); gdma_start(dma_chan, (intptr_t)&desc);
// Enable TRANS_DONE interrupt. Note that we do NOT require nor install // Enable TRANS_DONE interrupt. Note that we do NOT require nor install
@ -353,6 +282,4 @@ void _PM_timerInit(Protomatter_core *core) {
_PM_esp32commonTimerInit(core); // In esp32-common.h _PM_esp32commonTimerInit(core); // In esp32-common.h
} }
#endif // END CIRCUITPYTHON ------------------------------------------------
#endif // END ESP32S3 #endif // END ESP32S3

View file

@ -26,7 +26,8 @@
#pragma once #pragma once
#if defined(ARDUINO_ARCH_RP2040) || defined(PICO_BOARD) || defined(__RP2040__) #if defined(ARDUINO_ARCH_RP2040) || defined(PICO_BOARD) || \
defined(__RP2040__) || defined(__RP2350__)
#include "../../hardware_pwm/include/hardware/pwm.h" #include "../../hardware_pwm/include/hardware/pwm.h"
#include "hardware/irq.h" #include "hardware/irq.h"
@ -105,8 +106,13 @@ void _PM_timerInit(Protomatter_core *core) {
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON -------------------- #elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
#if !defined(F_CPU) // Not sure if CircuitPython build defines this #if !defined(F_CPU) // Not sure if CircuitPython build defines this
#ifdef __RP2040__
#define F_CPU 125000000 // Standard RP2040 clock speed #define F_CPU 125000000 // Standard RP2040 clock speed
#endif #endif
#ifdef __RP2350__
#define F_CPU 150000000 // Standard RP2350 clock speed
#endif
#endif
// 'pin' here is GPXX # // 'pin' here is GPXX #
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
@ -240,10 +246,10 @@ uint32_t _PM_timerStop(Protomatter_core *core) {
#define _PM_clockHoldLow asm("nop; nop; nop;"); #define _PM_clockHoldLow asm("nop; nop; nop;");
#define _PM_clockHoldHigh asm("nop; nop; nop;"); #define _PM_clockHoldHigh asm("nop; nop; nop;");
#elif (F_CPU >= 175000000) #elif (F_CPU >= 175000000)
#define _PM_clockHoldLow asm("nop; nop;"); #define _PM_clockHoldLow asm("nop; nop; nop;");
#define _PM_clockHoldHigh asm("nop;"); #define _PM_clockHoldHigh asm("nop;");
#elif (F_CPU >= 125000000) #elif (F_CPU >= 125000000)
#define _PM_clockHoldLow asm("nop;"); #define _PM_clockHoldLow asm("nop; nop; nop;");
#define _PM_clockHoldHigh asm("nop;"); #define _PM_clockHoldHigh asm("nop;");
#elif (F_CPU >= 100000000) #elif (F_CPU >= 100000000)
#define _PM_clockHoldLow asm("nop;"); #define _PM_clockHoldLow asm("nop;");

View file

@ -47,6 +47,21 @@
#define F_CPU (120000000) #define F_CPU (120000000)
// Enable high output driver strength on one pin. Arduino does this by
// default on pinMode(OUTPUT), but CircuitPython requires the motions...
static void _hi_drive(uint8_t pin) {
// For Arduino testing only:
// pin = g_APinDescription[pin].ulPort * 32 + g_APinDescription[pin].ulPin;
// Input, pull-up and peripheral MUX are disabled as we're only using
// vanilla PORT writes on Protomatter GPIO.
PORT->Group[pin / 32].WRCONFIG.reg =
(pin & 16)
? PORT_WRCONFIG_WRPINCFG | PORT_WRCONFIG_DRVSTR |
PORT_WRCONFIG_HWSEL | (1 << (pin & 15))
: PORT_WRCONFIG_WRPINCFG | PORT_WRCONFIG_DRVSTR | (1 << (pin & 15));
}
#else #else
// Other port register lookups go here // Other port register lookups go here
@ -157,6 +172,20 @@ void _PM_timerInit(Protomatter_core *core) {
NVIC_EnableIRQ(timer[timerNum].IRQn); NVIC_EnableIRQ(timer[timerNum].IRQn);
// Timer is configured but NOT enabled by default // Timer is configured but NOT enabled by default
#if defined(CIRCUITPY) // See notes earlier; Arduino doesn't need this.
// Enable high drive strength on all Protomatter pins. TBH this is kind
// of a jerky place to do this (it's not actually related to the timer
// peripheral) but Protomatter doesn't really have a spot for it.
uint8_t i;
for (i = 0; i < core->parallel * 6; i++)
_hi_drive(core->rgbPins[i]);
for (i = 0; i < core->numAddressLines; i++)
_hi_drive(core->addr[i].pin);
_hi_drive(core->clockPin);
_hi_drive(core->latch.pin);
_hi_drive(core->oe.pin);
#endif
} }
// Set timer period, initialize count value to zero, enable timer. // Set timer period, initialize count value to zero, enable timer.
@ -196,21 +225,204 @@ uint32_t _PM_timerStop(Protomatter_core *core) {
return count; return count;
} }
// See notes in core.c before the "blast" functions // SAMD51 takes a WEIRD TURN here, in an attempt to make the HUB75 clock
#if F_CPU >= 200000000 // waveform slightly adjustable. Old vs new matrices seem to have different
#define _PM_clockHoldHigh asm("nop; nop; nop; nop; nop"); // preferences, and this tries to address that. If this works well then the
#define _PM_clockHoldLow asm("nop; nop"); // approach might be applied to other architectures (which are all fixed
#elif F_CPU >= 180000000 // duty cycle right now). THE CHALLENGE is that Protomatter works in a bit-
#define _PM_clockHoldHigh asm("nop; nop; nop; nop"); // bangingly way (this is on purpose and by design, avoiding peripherals
#define _PM_clockHoldLow asm("nop"); // that might work only on certain pins, for better compatibility with
#elif F_CPU >= 150000000 // existing shields and wings from the AVR era), we're aiming for nearly a
#define _PM_clockHoldHigh asm("nop; nop; nop"); // 20 MHz signal, and the SAMD51 cycle clock is ostensibly 120 MHz. With
#define _PM_clockHoldLow asm("nop"); // just a few cycles to toggle the data and clock lines, that doesn't even
#else // leave enough time for a counter loop.
#define _PM_clockHoldHigh asm("nop; nop; nop");
#define _PM_clockHoldLow asm("nop"); #define _PM_CUSTOM_BLAST ///< Disable blast_*() functions in core.c
#define _PM_chunkSize 8 ///< Data-stuffing loop is unrolled to this size
extern uint8_t _PM_duty; // In core.c
// The approach is to use a small list of pointers, with a clock-toggling
// value written to each one in succession. Most of the pointers are aimed
// on a nonsense "bit bucket" variable, effectively becoming NOPs, and just
// one is set to the PORT toggle register, raising the clock. A couple of
// actual traditional NOPs are also present because concurrent PORT writes
// on SAMD51 incur a 1-cycle delay, so the NOPs keep the clock frequency
// constant (tradeoff is that the clock is now 7 rather than 6 cycles --
// ~17.1 MHz rather than 20 with F_CPU at 120 MHz). The NOPs could be
// removed and duty range increased by one, but the tradeoff then is
// inconsistent timing at different duty settings. That 1-cycle delay is
// also why this uses a list of pointers with a common value, rather than
// a common pointer (the PORT reg) with a list of values -- because those
// writes would all take 2 cycles, way too slow. A counter loop would also
// take 2 cycles/count, because of the branch.
#if F_CPU >= 200000000 // 200 MHz; 10 cycles/bit; 20 MHz, 6 duty settings
#define _PM_maxDuty 5 ///< Allow duty settings 0-5
#define _PM_defaultDuty 2 ///< ~60%
#define PEW \
asm("nop"); \
*toggle = *data++; \
asm("nop"); \
*ptr0 = clock; \
*ptr1 = clock; \
*ptr2 = clock; \
*ptr3 = clock; \
*ptr4 = clock; \
*ptr5 = clock;
#elif F_CPU >= 180000000 // 180 MHz; 9 cycles/bit; 20 MHz, 5 duty settings
#define _PM_maxDuty 4 ///< Allow duty settings 0-4
#define _PM_defaultDuty 1 ///< ~50%
#define PEW \
asm("nop"); \
*toggle = *data++; \
asm("nop"); \
*ptr0 = clock; \
*ptr1 = clock; \
*ptr2 = clock; \
*ptr3 = clock; \
*ptr4 = clock;
#elif F_CPU >= 150000000 // 150 MHz; 8 cycles/bit; 18.75 MHz, 4 duty settings
#define _PM_maxDuty 3 ///< Allow duty settings 0-3
#define _PM_defaultDuty 1 ///< ~55%
#define PEW \
asm("nop"); \
*toggle = *data++; \
asm("nop"); \
*ptr0 = clock; \
*ptr1 = clock; \
*ptr2 = clock; \
*ptr3 = clock;
#else // 120 MHz; 7 cycles/bit; 17.1 MHz, 3 duty settings
#define _PM_maxDuty 2 ///< Allow duty settings 0-2
#define _PM_defaultDuty 0 ///< ~50%
#define PEW \
asm("nop"); \
*toggle = *data++; \
asm("nop"); \
*ptr0 = clock; \
*ptr1 = clock; \
*ptr2 = clock;
#endif #endif
static void blast_byte(Protomatter_core *core, uint8_t *data) {
// If here, it was established in begin() that the RGB data bits and
// clock are all within the same byte of a PORT register, else we'd be
// in the word- or long-blasting functions now. So we just need an
// 8-bit pointer to the PORT:
volatile uint8_t *toggle =
(volatile uint8_t *)core->toggleReg + core->portOffset;
uint8_t bucket, clock = core->clockMask;
// Pointer list must be distinct vars, not an array, else slow.
volatile uint8_t *ptr0 =
(_PM_duty == _PM_maxDuty) ? toggle : (volatile uint8_t *)&bucket;
volatile uint8_t *ptr1 =
(_PM_duty == (_PM_maxDuty - 1)) ? toggle : (volatile uint8_t *)&bucket;
volatile uint8_t *ptr2 =
(_PM_duty == (_PM_maxDuty - 2)) ? toggle : (volatile uint8_t *)&bucket;
#if _PM_maxDuty >= 3
volatile uint8_t *ptr3 =
(_PM_duty == (_PM_maxDuty - 3)) ? toggle : (volatile uint8_t *)&bucket;
#endif
#if _PM_maxDuty >= 4
volatile uint8_t *ptr4 =
(_PM_duty == (_PM_maxDuty - 4)) ? toggle : (volatile uint8_t *)&bucket;
#endif
#if _PM_maxDuty >= 5
volatile uint8_t *ptr5 =
(_PM_duty == (_PM_maxDuty - 5)) ? toggle : (volatile uint8_t *)&bucket;
#endif
uint16_t chunks = core->chainBits / 8;
// PORT has already been initialized with RGB data + clock bits
// all LOW, so we don't need to initialize that state here.
do {
PEW PEW PEW PEW PEW PEW PEW PEW
} while (--chunks);
// Want the PORT left with RGB data and clock LOW on function exit
// (so it's easier to see on 'scope, and to prime it for the next call).
// This is implicit in the no-toggle case (due to how the PEW macro
// works), but toggle case requires explicitly clearing those bits.
// rgbAndClockMask is an 8-bit value when toggling, hence offset here.
*((volatile uint8_t *)core->clearReg + core->portOffset) =
core->rgbAndClockMask;
}
// This is a copypasta of blast_byte() with types changed to uint16_t.
static void blast_word(Protomatter_core *core, uint16_t *data) {
volatile uint16_t *toggle = (uint16_t *)core->toggleReg + core->portOffset;
uint16_t bucket, clock = core->clockMask;
volatile uint16_t *ptr0 =
(_PM_duty == _PM_maxDuty) ? toggle : (volatile uint16_t *)&bucket;
volatile uint16_t *ptr1 =
(_PM_duty == (_PM_maxDuty - 1)) ? toggle : (volatile uint16_t *)&bucket;
volatile uint16_t *ptr2 =
(_PM_duty == (_PM_maxDuty - 2)) ? toggle : (volatile uint16_t *)&bucket;
#if _PM_maxDuty >= 3
volatile uint16_t *ptr3 =
(_PM_duty == (_PM_maxDuty - 3)) ? toggle : (volatile uint16_t *)&bucket;
#endif
#if _PM_maxDuty >= 4
volatile uint16_t *ptr4 =
(_PM_duty == (_PM_maxDuty - 4)) ? toggle : (volatile uint16_t *)&bucket;
#endif
#if _PM_maxDuty >= 5
volatile uint16_t *ptr5 =
(_PM_duty == (_PM_maxDuty - 5)) ? toggle : (volatile uint16_t *)&bucket;
#endif
uint16_t chunks = core->chainBits / 8;
do {
PEW PEW PEW PEW PEW PEW PEW PEW
} while (--chunks);
*((volatile uint16_t *)core->clearReg + core->portOffset) =
core->rgbAndClockMask;
}
// This is a copypasta of blast_byte() with types changed to uint32_t.
static void blast_long(Protomatter_core *core, uint32_t *data) {
volatile uint32_t *toggle = (uint32_t *)core->toggleReg;
uint32_t bucket, clock = core->clockMask;
volatile uint32_t *ptr0 =
(_PM_duty == _PM_maxDuty) ? toggle : (volatile uint32_t *)&bucket;
volatile uint32_t *ptr1 =
(_PM_duty == (_PM_maxDuty - 1)) ? toggle : (volatile uint32_t *)&bucket;
volatile uint32_t *ptr2 =
(_PM_duty == (_PM_maxDuty - 2)) ? toggle : (volatile uint32_t *)&bucket;
#if _PM_maxDuty >= 3
volatile uint32_t *ptr3 =
(_PM_duty == (_PM_maxDuty - 3)) ? toggle : (volatile uint32_t *)&bucket;
#endif
#if _PM_maxDuty >= 4
volatile uint32_t *ptr4 =
(_PM_duty == (_PM_maxDuty - 4)) ? toggle : (volatile uint32_t *)&bucket;
#endif
#if _PM_maxDuty >= 5
volatile uint32_t *ptr5 =
(_PM_duty == (_PM_maxDuty - 5)) ? toggle : (volatile uint32_t *)&bucket;
#endif
uint16_t chunks = core->chainBits / 8;
do {
PEW PEW PEW PEW PEW PEW PEW PEW
} while (--chunks);
*((volatile uint32_t *)core->clearReg + core->portOffset) =
core->rgbAndClockMask;
}
#define _PM_minMinPeriod 160 #define _PM_minMinPeriod 160
#endif // END __SAMD51__ || SAM_D5X_E5X #endif // END __SAMD51__ || SAM_D5X_E5X

View file

@ -83,7 +83,7 @@ volatile uint16_t *_PM_portClearRegister(uint32_t pin) {
// TODO: this is no longer true, should it change? // TODO: this is no longer true, should it change?
void *_PM_protoPtr = NULL; void *_PM_protoPtr = NULL;
STATIC TIM_HandleTypeDef tim_handle; static TIM_HandleTypeDef tim_handle;
// Timer interrupt service routine // Timer interrupt service routine
void _PM_IRQ_HANDLER(void) { void _PM_IRQ_HANDLER(void) {

View file

@ -95,8 +95,9 @@ ProtomatterStatus _PM_init(Protomatter_core *core, uint16_t bitWidth,
uint8_t addrCount, uint8_t *addrList, uint8_t addrCount, uint8_t *addrList,
uint8_t clockPin, uint8_t latchPin, uint8_t oePin, uint8_t clockPin, uint8_t latchPin, uint8_t oePin,
bool doubleBuffer, int8_t tile, void *timer) { bool doubleBuffer, int8_t tile, void *timer) {
if (!core) if (!core) {
return PROTOMATTER_ERR_ARG; return PROTOMATTER_ERR_ARG;
}
// bitDepth is NOT constrained here, handle in calling function // bitDepth is NOT constrained here, handle in calling function
// (varies with implementation, e.g. GFX lib is max 6 bitplanes, // (varies with implementation, e.g. GFX lib is max 6 bitplanes,
@ -726,7 +727,7 @@ IRAM_ATTR static void blast_byte(Protomatter_core *core, uint8_t *data) {
_PM_PORT_TYPE rgbclock = core->rgbAndClockMask; // RGB + clock bit _PM_PORT_TYPE rgbclock = core->rgbAndClockMask; // RGB + clock bit
#endif #endif
_PM_PORT_TYPE clock = core->clockMask; // Clock bit _PM_PORT_TYPE clock = core->clockMask; // Clock bit
uint8_t chunks = (core->chainBits + (_PM_chunkSize - 1)) / _PM_chunkSize; uint16_t chunks = (core->chainBits + (_PM_chunkSize - 1)) / _PM_chunkSize;
// PORT has already been initialized with RGB data + clock bits // PORT has already been initialized with RGB data + clock bits
// all LOW, so we don't need to initialize that state here. // all LOW, so we don't need to initialize that state here.
@ -922,6 +923,10 @@ static void _PM_resetFM6126A(Protomatter_core *core) {
_PM_rgbState(core, 0); // Set all RGB low so port toggle can work _PM_rgbState(core, 0); // Set all RGB low so port toggle can work
} }
uint8_t _PM_duty = _PM_defaultDuty;
void _PM_setDuty(uint8_t d) { _PM_duty = (d > _PM_maxDuty) ? _PM_maxDuty : d; }
#if defined(ARDUINO) || defined(CIRCUITPY) #if defined(ARDUINO) || defined(CIRCUITPY)
// Arduino and CircuitPython happen to use the same internal canvas // Arduino and CircuitPython happen to use the same internal canvas
@ -1195,18 +1200,24 @@ void _PM_convert_565_word(Protomatter_core *core, uint16_t *source,
uint16_t upperRGB = upperSrc[srcIdx]; // Pixel in upper half uint16_t upperRGB = upperSrc[srcIdx]; // Pixel in upper half
uint16_t lowerRGB = lowerSrc[srcIdx]; // Pixel in lower half uint16_t lowerRGB = lowerSrc[srcIdx]; // Pixel in lower half
uint16_t result = 0; uint16_t result = 0;
if (upperRGB & redBit) if (upperRGB & redBit) {
result |= pinMask[0]; result |= pinMask[0];
if (upperRGB & greenBit) }
if (upperRGB & greenBit) {
result |= pinMask[1]; result |= pinMask[1];
if (upperRGB & blueBit) }
if (upperRGB & blueBit) {
result |= pinMask[2]; result |= pinMask[2];
if (lowerRGB & redBit) }
if (lowerRGB & redBit) {
result |= pinMask[3]; result |= pinMask[3];
if (lowerRGB & greenBit) }
if (lowerRGB & greenBit) {
result |= pinMask[4]; result |= pinMask[4];
if (lowerRGB & blueBit) }
if (lowerRGB & blueBit) {
result |= pinMask[5]; result |= pinMask[5];
}
// Main difference here vs byte converter is each chain // Main difference here vs byte converter is each chain
// ORs new bits into place (vs single-pass overwrite). // ORs new bits into place (vs single-pass overwrite).
// #if defined(_PM_portToggleRegister) // #if defined(_PM_portToggleRegister)
@ -1319,18 +1330,24 @@ void _PM_convert_565_long(Protomatter_core *core, uint16_t *source,
uint16_t upperRGB = upperSrc[srcIdx]; // Pixel in upper half uint16_t upperRGB = upperSrc[srcIdx]; // Pixel in upper half
uint16_t lowerRGB = lowerSrc[srcIdx]; // Pixel in lower half uint16_t lowerRGB = lowerSrc[srcIdx]; // Pixel in lower half
uint32_t result = 0; uint32_t result = 0;
if (upperRGB & redBit) if (upperRGB & redBit) {
result |= pinMask[0]; result |= pinMask[0];
if (upperRGB & greenBit) }
if (upperRGB & greenBit) {
result |= pinMask[1]; result |= pinMask[1];
if (upperRGB & blueBit) }
if (upperRGB & blueBit) {
result |= pinMask[2]; result |= pinMask[2];
if (lowerRGB & redBit) }
if (lowerRGB & redBit) {
result |= pinMask[3]; result |= pinMask[3];
if (lowerRGB & greenBit) }
if (lowerRGB & greenBit) {
result |= pinMask[4]; result |= pinMask[4];
if (lowerRGB & blueBit) }
if (lowerRGB & blueBit) {
result |= pinMask[5]; result |= pinMask[5];
}
// Main difference here vs byte converter is each chain // Main difference here vs byte converter is each chain
// ORs new bits into place (vs single-pass overwrite). // ORs new bits into place (vs single-pass overwrite).
// #if defined(_PM_portToggleRegister) // #if defined(_PM_portToggleRegister)

View file

@ -255,6 +255,16 @@ extern uint32_t _PM_timerGetCount(Protomatter_core *core);
*/ */
extern void _PM_swapbuffer_maybe(Protomatter_core *core); extern void _PM_swapbuffer_maybe(Protomatter_core *core);
/*!
@brief Adjust duty cycle of HUB75 clock signal. This is not supported on
all architectures.
@param d Duty setting, 0 minimum. Increasing values generate higher clock
duty cycles at the same frequency. Arbitrary granular units, max
varies by architecture and CPU speed, if supported at all.
e.g. SAMD51 @ 120 MHz supports 0 (~50% duty) through 2 (~75%).
*/
extern void _PM_setDuty(uint8_t d);
#if defined(ARDUINO) || defined(CIRCUITPY) #if defined(ARDUINO) || defined(CIRCUITPY)
/*! /*!