Compare commits

...

188 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
Phillip Burgess
d802a5ca2a Fix pixeldust compilation for CI 2023-05-18 19:36:25 -07:00
Phillip Burgess
e263d6a890 Version bump for MatrixPortal ESP32-S3 examples 2023-05-18 19:13:18 -07:00
Phillip Burgess
5d5756737c MOST examples (except animated_gif) working on both MatrixPortals 2023-05-18 16:46:50 -07:00
Paint Your Dragon
5eefcf5316
Bump version for ESP32-S2 + matrix shield in example 2023-04-26 08:57:27 -07:00
Paint Your Dragon
6c8171cdca
Merge pull request #58 from adafruit/metroesp32s2
now working with esp32-s2 + matrix shield
2023-04-26 08:56:05 -07:00
ladyada
a22be49e73 now working with esp32-s2 + matrix shield 2023-04-24 14:52:46 -04:00
Phillip Burgess
ae2567aebe Timer adjustments centered around ESP32-S3 DMA 2022-11-21 16:23:05 -08:00
Phillip Burgess
8e74b7308b clang-format 2022-11-18 10:49:35 -08:00
Phillip Burgess
20feb8a10d ESP32-S3 DMA and pin drive strength fixes 2022-11-18 10:46:17 -08:00
Dan Halbert
cc93ff18c3
Merge pull request #54 from MicroDev1/master
stm32 type name fix
2022-11-09 14:24:39 -05:00
MicroDev
000ca43f79
stm32 type name fix 2022-11-09 10:27:36 +05:30
Paint Your Dragon
418c8e1728
Add support for panels with FM6126A chipset. 2022-09-21 17:52:58 -07:00
Paint Your Dragon
a0a194ef4f
Add support for panels with FM6126A chipset. 2022-09-21 17:48:42 -07:00
Phillip Burgess
a819183e61 Appease clang-format 2022-09-21 14:54:46 -07:00
Phillip Burgess
9c8872efd1 Simplify FM6126A init 2022-09-21 14:51:15 -07:00
Paint Your Dragon
c141ffbc46
Merge pull request #52 from colorama/pr-FM6126A
Add support for panels with FM6126A chipset. Copied from SmartMat…
2022-09-21 13:31:30 -07:00
Misha
0ba5e457d8 Code cleanup as requested. 2022-09-21 12:21:06 -07:00
Misha
257a05e12a Apparently delay() is no bueno 2022-09-21 00:51:52 -07:00
Misha
98fee126d8 Add support for panels with FM6126A chipset. Copied from SmartMatrix.
Have not tested with 'normal' panels - I don't have any.
2022-09-21 00:24:50 -07:00
Dan Halbert
db3c9f2720
Merge pull request #50 from MicroDev1/master
Updates to support esp-idf v5.0 on CircuitPython
2022-09-20 12:46:32 -04:00
microDev
94aeb8d31f
updates to support esp-idf v5.0 on circuitpython 2022-09-20 21:40:16 +05:30
Phillip Burgess
a902f81142 Fix _PM_timerGetCount() on nRF52 2022-06-24 22:00:23 -07:00
Paint Your Dragon
0baa8bd097
Update version number for ESP32 improvements 2022-06-15 19:21:32 -07:00
Paint Your Dragon
be25ca91b7
Merge pull request #46 from adafruit/pb-esp32s3
ESP32 family refactor; S2 and S3 performance improvements
2022-06-15 19:20:15 -07:00
Phillip Burgess
996913d1c3 Appease the clang-format monster. Rawr! 2022-06-15 19:06:24 -07:00
Phillip Burgess
b3d8339719 ESP32-S3 do-over 2022-06-11 16:07:40 -07:00
Phillip Burgess
b7c7c5d4f3 nRF fix 2022-06-02 08:56:38 -07:00
Phillip Burgess
73c4852fe1 Some clang-format and stuff 2022-06-01 21:25:32 -07:00
Phillip Burgess
7469be5084 Remove old & inaccurate comment cruft 2022-06-01 17:41:09 -07:00
Phillip Burgess
ee0f949da8 Moar comments, and changing some bytesPerElement code 2022-06-01 17:24:40 -07:00
Phillip Burgess
b28f9612d8 Oh, move core struct back into 'private' members 2022-06-01 17:05:10 -07:00
Phillip Burgess
25d75cdd01 Big refactor, still need to test all platforms 2022-06-01 17:03:12 -07:00
Phillip Burgess
c90e29b310 ESP32 refactor, not yet tested 2022-05-31 15:47:48 -07:00
Phillip Burgess
c036d5a7f2 Fiddle the LCD setup slightly 2022-05-28 07:08:32 -07:00
Phillip Burgess
de4af68ef0 ESP32-S3 starting to work, some caveats but hey 2022-05-27 17:25:42 -07:00
Phillip Burgess
d52180b747 Some progress on S3! 2022-05-27 15:10:45 -07:00
Phillip Burgess
63b1973ab7 Add ESP32-S2 comments for later
Although this approach will probably be abandoned in favor of I2S-LCD, setting the code aside as-is for now, with notes on how it works and why…may still be informative when adapting to the other peripheral.
2022-05-12 14:37:33 -07:00
Phillip Burgess
55194ee60f ESP32 WIP notes 2022-05-11 10:33:22 -07:00
Phillip Burgess
14fc498d35 ESP32 ah ha! Starting to work. 2022-05-10 23:24:39 -07:00
Phillip Burgess
5cf809733d ESP32 WIP some data format trouble 2022-05-10 22:40:08 -07:00
Phillip Burgess
4cb133d816 ESP32S2 WIP 2022-05-10 17:17:39 -07:00
Phillip Burgess
4c6a4abd10 ESP32-S2 WIP, almost working except OE & latch 2022-05-10 16:47:15 -07:00
Phillip Burgess
9c6bba76af WIP on ESP32-S2/S3 improvements 2022-05-06 19:29:32 -07:00
Phillip Burgess
0c5fef8139 Start adding capability for custom 'blast' funcs 2022-05-06 10:12:53 -07:00
Paint Your Dragon
4721641f03
Merge pull request #45 from adafruit/pb-esp32s3
Add ESP32-S2 support (about the same as S3)
2022-04-25 10:34:47 -07:00
Phillip Burgess
0ccd35551d Add ESP32-S2 support (about the same as S3) 2022-04-25 10:15:07 -07:00
Paint Your Dragon
d139dccc4d
Merge pull request #44 from adafruit/pb-esp32s3
Initial ESP32S3 Support
2022-04-22 17:34:50 -07:00
Phillip Burgess
8536ff119f Appease the clang-format monster 2022-04-22 17:01:19 -07:00
Phillip Burgess
1e3f82fe2c ESP32-S3 support 2022-04-22 16:52:35 -07:00
lady ada
b2574621aa update 64 tall example 2022-01-21 13:29:51 -05:00
ladyada
7697ddf496 Merge branch 'master' of github.com:adafruit/Adafruit_Protomatter 2022-01-04 13:32:37 -05:00
ladyada
6f15c31371 add feather esp32s2 2022-01-04 13:32:34 -05:00
Paint Your Dragon
c977db7641
Bump version # for RP2040 support 2021-12-30 21:34:20 -08:00
Paint Your Dragon
0c6ee7ebfe
Merge pull request #43 from adafruit/pb-rp2040
RP2040 Arduino support
2021-12-30 21:33:41 -08:00
Phillip Burgess
3c3fc02a5f Calibrate NOPs for different frame rates 2021-12-30 11:17:32 -08:00
Phillip Burgess
5efa76f440 Appease the clang-format monster. Rar. 2021-12-29 19:41:33 -08:00
Phillip Burgess
b0893cfbbf RP2040 WIP. Examples updated, still need to figure NOPs for different clocks 2021-12-29 19:28:21 -08:00
Phillip Burgess
11197228e8 clang-format 2021-12-29 11:21:56 -08:00
Phillip Burgess
2ef010d948 RP2040 fix-ups, compiles but untested 2021-12-29 11:14:35 -08:00
Jeff Epler
d0a07e14ad
Merge pull request #41 from microDev1/patch-1
Updates for espressif soc
2021-12-17 11:03:34 -07:00
microDev
c54b683614
updates for espressif soc
- support esp-idf v4.4
- add esp32c3 and esp32s3 support
2021-11-24 22:04:50 +05:30
Paint Your Dragon
7fe6406aff
Merge pull request #39 from jepler/esp32-cp
esp32.h: Add support for CircuitPython
2021-05-26 09:19:08 -07:00
594ed270d1 indent, again 2021-05-14 14:53:17 -05:00
f1a671d4b8 Fix arduino again, hopefully 2021-05-14 14:48:09 -05:00
f07cc8d6d6 esp32 needs strict 32-bit IO at least on circuitpython
.. which makes it also need a different PEW macro

.. CircuitPython is grumpy about unused variables, so set_full has
to be protected by a macro in blast_long.
2021-05-14 11:18:34 -05:00
53e040d202 update formatting 2021-05-14 09:38:28 -05:00
1250705c7b esp32.h: Add support for CircuitPython
this only works if you trick it into using blast_long.  Treat as a work
in progress only.
2021-05-13 11:17:04 -05:00
Paint Your Dragon
98017c5734
Bump version to 1.2.0 for SAM-E support 2021-04-12 16:37:32 -07:00
Limor "Ladyada" Fried
711f0ad9ee
Merge pull request #38 from jepler/sam-e5x
Add support for SAM E5x MCUs
2021-04-12 11:14:26 -04:00
62181db502 clang-format fixes 2021-04-12 10:06:42 -05:00
5f8469be05 Add support for SAM E5x MCUs
.. such as in the Feather M4 CAN.
2021-04-12 09:43:31 -05:00
Jeff Epler
c2c81ded11
Merge pull request #36 from jepler/rp2040-circuitpython-timer-fix
rp2040: circuitpython: Actually make dynamic PWM allocation work
2021-03-01 15:12:28 -06:00
ea0a1d3ce3 clang-format demands whitespace 2021-03-01 14:35:14 -06:00
c982a053cd rp2040: circuitpython: Actually make dynamic PWM allocation work
My initial testing was always done with PWM slice 0, so it didn't
matter that _PM_pwm_slice was never set to a nonzero value.  Limor tested
with the first pin as GPIO6, which led to use of slice 3 and after that
everything got sad really quickly.

Since Arduino doesn't use _PM_pwm_slice now, this means that
_PM_timerInit's implementation becomes unshared; because _PM_timerInit
nees to refer to _PM_PWM_ISR or _PM_timerISR, those newly require forward
(static) declarations.  That makes this change look bigger than it is.
The only "real" change is intended to be something like
```c
 #ifdef CIRCUITPYTHON
+void _PM_timerInit(void *tptr) {
+#if _PM_CLOCK_PWM
+  _PM_pwm_slice = (int)tptr & 0xff;
```
2021-03-01 08:59:07 -06:00
Phillip Burgess
632cfcede2 Dynamic refresh rate put back in, prior notes weren't entirely correct 2021-02-12 22:12:15 -08:00
Paint Your Dragon
1086f5e28f
Merge pull request #33 from jepler/rp2040-cp
Changes needed to let the code work on CircuitPython & RP2040
2021-02-12 12:47:09 -08:00
Jeff Epler
c3cccab105 Changes needed to let the code work on CircuitPython & RP2040
These changes are tested along with https://github.com/adafruit/circuitpython/pull/4186 and work on a 32x32 matrix.
2021-02-12 14:39:31 -06:00
Paint Your Dragon
d7e92f0b55
Merge pull request #34 from adafruit/pb-rp2040
RP2040 support (sorta)
2021-02-12 12:37:16 -08:00
Phillip Burgess
59e1d949cf Update notes on bitplane timing 2021-02-12 12:27:49 -08:00
Phillip Burgess
9d80b0d6a6 Add _PM_timerGetCount() to STM32 2021-02-11 11:44:48 -08:00
Phillip Burgess
ab56e7e88f Fix animated_gif example 2021-02-09 11:50:23 -08:00
Phillip Burgess
e3b1c14d0d Updated "Notes to Future Self" at end of core.c 2021-02-02 12:42:08 -08:00
Phillip Burgess
e80c101557 Further tweaks to the dynamic refresh rate 2021-02-01 15:52:55 -08:00
Phillip Burgess
6a9a307f3a Change rounding math in bit zero timing 2021-01-31 15:58:07 -08:00
Phillip Burgess
39de313d7e Fix dynamic throttling in core, add compile-time setting on RP2040 for PWM or alarm for timekeeping 2021-01-31 15:47:08 -08:00
Phillip Burgess
f2d0cde4e0 Restore timing "initial guesstimate" math. 2021-01-29 22:11:32 -08:00
Phillip Burgess
d3cda46436 RP2040 working (using PWM periph for interval timing) 2021-01-29 21:57:25 -08:00
Phillip Burgess
d116005b60 RP2040 starting to work 2021-01-29 17:54:48 -08:00
Phillip Burgess
c0c6413430 core.c: fix 32-bit-only no-toggle PEW 2021-01-29 13:23:51 -08:00
Phillip Burgess
846d95aac1 RP2040: fix _PM_pinOutput() 2021-01-29 13:04:38 -08:00
Phillip Burgess
19dc9058cd RP2040 WIP — not working yet (but not crashing either) 2021-01-29 12:51:35 -08:00
Phillip Burgess
97e0929a17 Bump version # for tiling support 2021-01-27 15:21:54 -08:00
Paint Your Dragon
e8018dd935
Merge pull request #32 from jepler/circuitpython-build-fixes
Circuitpython build fixes
2021-01-26 08:15:05 -08:00
5e925cea7a clang-format 2021-01-26 09:07:38 -06:00
9e71254732 core: Const-qualify pointers
This fixes diagnostics similar to `core.c:947:20: error: assignment discards 'const' qualifier from pointer target type [-Werror=discarded-qualifiers]`
2021-01-26 09:00:04 -06:00
092066ecc2 core: Include declaration for abs()
This fixes diagnostics such as `core.c:937:7: error: declaration of non-variable 'abs' in 'for' loop initial declaration`
2021-01-26 08:59:33 -06:00
Paint Your Dragon
75965eb640
Merge pull request #31 from adafruit/pb-tile
Matrix tiling support
2021-01-21 20:20:09 -08:00
Phillip Burgess
473c5dc3b4 Remove unused vars 2021-01-21 19:48:03 -08:00
Phillip Burgess
560ad3e2de Grand Central notes in simple example 2021-01-21 19:17:31 -08:00
Phillip Burgess
689f61c506 Word and long converters adapted for tiling but not extensively tested 2021-01-14 21:25:14 -08:00
Phillip Burgess
eacbdfdef4 Row tiling seems to work (for single chain, not for parallel chains) 2021-01-14 19:31:18 -08:00
Phillip Burgess
63240eb990 Tiling kinda starting to sorta work 2021-01-13 21:30:17 -08:00
Phillip Burgess
7dd3ad3fec Tiling WIP, not working 2021-01-13 14:47:12 -08:00
Phillip Burgess
020427f66a Initial tiling support WIP 2021-01-12 11:58:24 -08:00
Limor "Ladyada" Fried
b041e58718
Merge pull request #29 from aocole/fix-gifopenfile-signature
Fix signature of GIFOpenFile
2020-11-09 12:51:31 -05:00
Andrew Cole
c1d99cf422 Fix signature of GIFOpenFile 2020-11-06 13:51:51 -08:00
Phillip Burgess
902c16f491 animated_gif example bugfixes & changes 2020-11-02 10:36:43 -08:00
Phillip Burgess
9880ee62d9 Rename doublebuffer example to doublebuffer_scrolltext
Also, extra comments explaining some of the calls required for scrolling text like this.
2020-10-20 19:17:18 -07:00
Phillip Burgess
db208212f3 Move color565() from examples into Adafruit_Protomatter (not core) 2020-10-19 20:12:52 -07:00
Phillip Burgess
7fd87fa56b Merge branch 'master' of https://github.com/adafruit/Adafruit_Protomatter 2020-10-19 16:18:17 -07:00
Phillip Burgess
b9b663e87a Remove pixeldust_64x64 example, single demo can handle both with 1-line edit 2020-10-19 16:18:14 -07:00
Jeff Epler
864cc77c62
Merge pull request #25 from jepler/resume-pin-state
core: _PM_resume: put address pins in known state
2020-10-19 15:47:38 -05:00
b0c261b2e6 Merge remote-tracking branch 'origin/master' into resume-pin-state 2020-10-19 15:44:11 -05:00
Phillip Burgess
4193bfaea1 Overhaul examples - clean up, better comments, rename "protomatter" example to "simple", etc. 2020-10-19 13:28:33 -07:00
Limor "Ladyada" Fried
9651720252
Merge pull request #26 from jepler/fix-ci-python38
github actions: fix CI by requesting python verson 3.9
2020-10-19 14:05:26 -04:00
273aba770d github actions: fix CI by requesting python verson 3.9
this is the same fix used in Adafruit_Learning_System_Guides
2020-10-19 12:50:55 -05:00
b58c78c9d2 core: _PM_resume: put address pins in known state
Before this, using the "brightness" property in CircuitPython would cause the display to become jumbled.  Reproducer script for matrixportal:
```
import sys
import time
import random
from adafruit_matrixportal.matrix import Matrix

MATRIX = Matrix(bit_depth=1)
DISPLAY = MATRIX.display

sys.stdout.write("JUMBLED\nTEXT")
time.sleep(3)

while True:
    DISPLAY.brightness = 0
    DISPLAY.brightness = 1
    time.sleep(random.random())
```

After the fix there is only a slight flicker due to the time the display spends with zero brightness.

Thanks to @firepixie for the report!
2020-10-19 11:33:37 -05:00
Paint Your Dragon
de6b7704c5
Merge pull request #23 from adafruit/pb-gamma
Reorganize source, minor fixes
2020-10-09 11:01:04 -07:00
Paint Your Dragon
d4f5ade40b
Merge pull request #22 from adafruit/pb-gamma
Add animated_gif example using the AnimatedGIF library
(Version number is intentionally not bumped yet! More to do first.)
2020-10-06 10:26:54 -07:00
29 changed files with 2904 additions and 925 deletions

View file

@ -7,16 +7,16 @@ jobs:
strategy:
fail-fast: false
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
steps:
- uses: actions/setup-python@v1
- uses: actions/setup-python@v5
with:
python-version: '3.x'
- uses: actions/checkout@v2
- uses: actions/checkout@v2
python-version: '3.8'
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: adafruit/ci-arduino
path: ci

View file

@ -64,9 +64,9 @@ This repository currently consists of:
# Arduino Library
This will likely supersede the RGBmatrixPanel library on non-AVR devices, as
the older library has painted itself into a few corners. The newer library
uses a single constructor for all matrix setups, potentially handling parallel
This supersedes the RGBmatrixPanel library on non-AVR devices, as the older
library has painted itself into a few corners. The newer library uses a
single constructor for all matrix setups, potentially handling parallel
chains (not yet fully implemented), various matrix sizes and chain lengths,
and variable bit depths from 1 to 6 (refresh rate is a function of all of
these). Note however that it is NOT A DROP-IN REPLACEMENT for RGBmatrixPanel.
@ -77,13 +77,13 @@ aimed at low-cost color LCD displays), even if the matrix is configured for
a lower bit depth (colors will be decimated/quantized in this case).
It does have some new limitations, mostly significant RAM overhead (hence
no plans for AVR port) and that all RGB data pins and the clock pin MUST be
on the same PORT register (e.g. all PORTA or PORTB, can't intermix). RAM
overhead is somewhat reduced (but still large) if those pins are all in a
single 8-bit byte within the PORT (they do not need to be contiguous or
sequential within this byte, if for instance it makes PCB routing easier,
but they should all aim for a single byte). Other pins (matrix address lines,
latch and output enable) can reside on any PORT or bit.
no plans for AVR port) and (with a few exceptions) that all RGB data pins
and the clock pin MUST be on the same PORT register (e.g. all PORTA or PORTB
,can't intermix). RAM overhead is somewhat reduced (but still large) if
those pins are all in a single 8-bit byte within the PORT (they do not need
to be contiguous or sequential within this byte, if for instance it makes
PCB routing easier, but they should all aim for a single byte). Other pins
(matrix address lines, latch and output enable) can reside on any PORT or bit.
# C Library
@ -115,3 +115,18 @@ compile-time).
Most macros and functions begin with the prefix **\_PM\_** in order to
avoid naming collisions with other code (exception being static functions,
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

@ -1,8 +1,8 @@
// Play GIFs from CIRCUITPY drive (USB-accessible filesystem) to LED matrix.
// Designed for Adafruit MatrixPortal M4, but may run on some other M4 & M0
// and nRF52 boards (relies on TinyUSB stack). As written, runs on 64x32 pixel
// matrix, this can be changed by editing the addrPins[] array (height) and/or
// matrix constructor call (width).
// ***DESIGNED FOR ADAFRUIT MATRIXPORTAL***, but may run on some other M4,
// M0, ESP32S3 and nRF52 boards (relies on TinyUSB stack). As written, runs
// on 64x32 pixel matrix, this can be changed by editing the WIDTH and HEIGHT
// definitions. See the "simple" example for a run-down on matrix config.
// Adapted from examples from Larry Bank's AnimatedGIF library and
// msc_external_flash example in Adafruit_TinyUSB_Arduino.
// Prerequisite libraries:
@ -25,11 +25,17 @@
char GIFpath[] = "/gifs"; // Absolute path to GIFs on CIRCUITPY drive
uint16_t GIFminimumTime = 10; // Min. repeat time (seconds) until next GIF
#define WIDTH 64 // Matrix width in pixels
#define HEIGHT 32 // Matrix height in pixels
// Maximim matrix height is 32px on most boards, 64 on MatrixPortal if the
// 'E' jumper is set.
// FLASH FILESYSTEM STUFF --------------------------------------------------
// External flash macros for QSPI or SPI are defined in board variant file.
#if defined(EXTERNAL_FLASH_USE_QSPI)
#if defined(ARDUINO_ARCH_ESP32)
static Adafruit_FlashTransport_ESP32 flashTransport;
#elif defined(EXTERNAL_FLASH_USE_QSPI)
Adafruit_FlashTransport_QSPI flashTransport;
#elif defined(EXTERNAL_FLASH_USE_SPI)
Adafruit_FlashTransport_SPI flashTransport(EXTERNAL_FLASH_USE_CS,
@ -46,28 +52,42 @@ Adafruit_USBD_MSC usb_msc; // USB mass storage object
#if defined(_VARIANT_MATRIXPORTAL_M4_)
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
uint8_t addrPins[] = {17, 18, 19, 20};
uint8_t addrPins[] = {17, 18, 19, 20, 21}; // 16/32/64 pixels tall
uint8_t clockPin = 14;
uint8_t latchPin = 15;
uint8_t oePin = 16;
#define BACK_BUTTON 2
#define NEXT_BUTTON 3
#elif defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3)
uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37};
uint8_t addrPins[] = {45, 36, 48, 35, 21}; // 16/32/64 pixels tall
uint8_t clockPin = 2;
uint8_t latchPin = 47;
uint8_t oePin = 14;
#define BACK_BUTTON 6
#define NEXT_BUTTON 7
#elif defined(_VARIANT_METRO_M4_)
uint8_t rgbPins[] = {2, 3, 4, 5, 6, 7};
uint8_t addrPins[] = {A0, A1, A2, A3};
uint8_t addrPins[] = {A0, A1, A2, A3}; // 16 or 32 pixels tall
uint8_t clockPin = A4;
uint8_t latchPin = 10;
uint8_t oePin = 9;
#elif defined(_VARIANT_FEATHER_M4_)
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2};
uint8_t addrPins[] = {A5, A4, A3, A2}; // 16 or 32 pixels tall
uint8_t clockPin = 13;
uint8_t latchPin = 0;
uint8_t oePin = 1;
#endif
#if HEIGHT == 16
#define NUM_ADDR_PINS 3
#elif HEIGHT == 32
#define NUM_ADDR_PINS 4
#elif HEIGHT == 64
#define NUM_ADDR_PINS 5
#endif
// Matrix width is first arg here, height is inferred from addrPins[]
Adafruit_Protomatter matrix(64, 6, 1, rgbPins, sizeof addrPins, addrPins,
Adafruit_Protomatter matrix(WIDTH, 6, 1, rgbPins, NUM_ADDR_PINS, addrPins,
clockPin, latchPin, oePin, true);
// ANIMATEDGIF LIBRARY STUFF -----------------------------------------------
@ -79,7 +99,7 @@ int16_t xPos = 0, yPos = 0; // Top-left pixel coord of GIF in matrix space
// FILE ACCESS FUNCTIONS REQUIRED BY ANIMATED GIF LIB ----------------------
// Pass in ABSOLUTE PATH of GIF file to open
void *GIFOpenFile(char *filename, int32_t *pSize) {
void *GIFOpenFile(const char *filename, int32_t *pSize) {
GIFfile = filesys.open(filename);
if (GIFfile) {
*pSize = GIFfile.size();
@ -332,7 +352,6 @@ void loop() {
GIFisOpen = false;
}
GIFindex += GIFincrement; // Fwd or back 1 file
GIFincrement = 0; // Reset increment flag
int num_files = numFiles(GIFpath, "GIF");
if(GIFindex >= num_files) GIFindex = 0; // 'Wrap around' file index
else if(GIFindex < 0) GIFindex = num_files - 1; // both directions
@ -351,15 +370,17 @@ void loop() {
yPos = (matrix.height() - GIF.getCanvasHeight()) / 2;
GIFisOpen = true;
GIFstartTime = millis();
GIFincrement = 0; // Reset increment flag
}
}
} else if(GIFisOpen) {
if (GIF.playFrame(true, NULL)) {
if (GIF.playFrame(true, NULL) >= 0) { // Auto resets to start if needed
matrix.show();
} else if ((millis() - GIFstartTime) < (GIFminimumTime * 1000)) {
GIF.reset(); // Minimum time hasn't elapsed yet, repeat this GIF
if ((millis() - GIFstartTime) >= (GIFminimumTime * 1000)) {
GIFincrement = 1; // Minimum time has elapsed, proceed to next GIF
}
} else {
GIFincrement = 1; // Minimum time has elapsed, proceed to next GIF
GIFincrement = 1; // Decode error, proceed to next GIF
}
}
}

View file

@ -1,192 +0,0 @@
#include "Adafruit_Protomatter.h"
/*
METRO M0 PORT-TO-PIN ASSIGNMENTS BY BYTE:
PA00 PA08 D4 PA16 D11 PB00 PB08 A1
PA01 PA09 D3 PA17 D13 PB01 PB09 A2
PA02 A0 PA10 D1 PA18 D10 PB02 A5 PB10 MOSI
PA03 PA11 D0 PA19 D12 PB03 PB11 SCK
PA04 A3 PA12 MISO PA20 D6 PB04 PB12
PA05 A4 PA13 PA21 D7 PB05 PB13
PA06 D8 PA14 D2 PA22 SDA PB06 PB14
PA07 D9 PA15 D5 PA23 SCL PB07 PB15
SAME, METRO M4:
PA00 PA08 PA16 D13 PB00 PB08 A4 PB16 D3
PA01 PA09 PA17 D12 PB01 PB09 A5 PB17 D2
PA02 A0 PA10 PA18 D10 PB02 SDA PB10 PB18
PA03 PA11 PA19 D11 PB03 SCL PB11 PB19
PA04 A3 PA12 MISO PA20 D9 PB04 PB12 D7 PB20
PA05 A1 PA13 SCK PA21 D8 PB05 PB13 D4 PB21
PA06 A2 PA14 MISO PA22 D1 PB06 PB14 D5 PB22
PA07 PA15 PA23 D0 PB07 PB15 D6 PB23
FEATHER M4:
PA00 PA08 PA16 D5 PB08 A2 PB16 D1/TX
PA01 PA09 PA17 SCK PB09 A3 PB17 D0/RX
PA02 A0 PA10 PA18 D6 PB10 PB18
PA03 PA11 PA19 D9 PB11 PB19
PA04 A4 PA12 SDA PA20 D10 PB12 PB20
PA05 A1 PA13 SCL PA21 D11 PB13 PB21
PA06 A5 PA14 D4 PA22 D12 PB14 PB22 MISO
PA07 PA15 PA23 D13 PB15 PB23 MOSI
FEATHER M0:
PA00 PA08 PA16 D11 PB00 PB08 A1
PA01 PA09 PA17 D13 PB01 PB09 A2
PA02 A0 PA10 TX/D1 PA18 D10 PB02 A5 PB10 MOSI
PA03 PA11 RX/D0 PA19 D12 PB03 PB11 SCK
PA04 A3 PA12 MISO PA20 D6 PB04 PB12
PA05 A4 PA13 PA21 D7 PB05 PB13
PA06 PA14 PA22 SDA PB06 PB14
PA07 D9 PA15 D5 PA23 SCL PB07 PB15
FEATHER nRF52840:
P0.00 P0.08 D12 P0.24 RXD P1.08 D5
P0.01 P0.09 P0.25 TXD P1.09 D13
P0.02 A4 P0.10 D2 (NFC) P0.26 D9 P1.10
P0.03 A5 P0.11 SCL P0.27 D10 P1.11
P0.04 A0 P0.12 SDA P0.28 A3 P1.12
P0.05 A1 P0.13 MOSI P0.29 P1.13
P0.06 D11 P0.14 SCK P0.30 A2 P1.14
P0.07 D6 P0.15 MISO P0.31 P1.15
FEATHER ESP32:
P0.00 P0.08 P0.16 16/RX P0.24 P1.00 32/A7
P0.01 P0.09 P0.17 17/TX P0.25 25/A1 P1.01 33/A9/SS
P0.02 P0.10 P0.18 18/MOSI P0.26 26/A0 P1.02 34/A2 (in)
P0.03 P0.11 P0.19 19/MISO P0.27 27/A10 P1.03
P0.04 4/A5 P0.12 12/A11 P0.20 P0.28 P1.04 36/A4 (in)
P0.05 5/SCK P0.13 13/A12 P0.21 21 P0.29 P1.05
P0.06 P0.14 14/A6 P0.22 22/SCL P0.30 P1.06
P0.07 P0.15 15/A8 P0.23 23/SDA P0.31 P1.07 39/A3 (in)
RGB Matrix FeatherWing:
R1 D6 A A5
G1 D5 B A4
B1 D9 C A3
R2 D11 D A2
G2 D10 LAT D0/RX
B2 D12 OE D1/TX
CLK D13
RGB+clock in one PORT byte on Feather M4!
RGB+clock are on same PORT but not within same byte on Feather M0 --
the code could run there (with some work to be done in the convert_*
functions), but would be super RAM-inefficient. Should be fine on other
M0 devices like a Metro, if wiring manually so one can pick a contiguous
byte of PORT bits.
RGB+clock are on different PORTs on nRF52840.
*/
#if defined(__SAMD51__)
// Use FeatherWing pinout
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2};
uint8_t clockPin = 13;
uint8_t latchPin = 0;
uint8_t oePin = 1;
#elif defined(_SAMD21_)
uint8_t rgbPins[] = {6, 7, 10, 11, 12, 13};
uint8_t addrPins[] = {0, 1, 2, 3};
uint8_t clockPin = SDA;
uint8_t latchPin = 4;
uint8_t oePin = 5;
#elif defined(NRF52_SERIES)
// Special nRF52840 FeatherWing pinout
uint8_t rgbPins[] = {6, A5, A1, A0, A4, 11};
uint8_t addrPins[] = {10, 5, 13, 9};
uint8_t clockPin = 12;
uint8_t latchPin = PIN_SERIAL1_RX;
uint8_t oePin = PIN_SERIAL1_TX;
#elif defined(ESP32)
// 'Safe' pins (not overlapping any peripherals):
// GPIO.out: 4, 12, 13, 14, 15, 21, 27, GPIO.out1: 32, 33
// Peripheral-overlapping pins, sorted from 'most expendible':
// 16, 17 (RX, TX), 25, 26 (A0, A1), 18, 5, 9 (MOSI, SCK, MISO), 22, 23 (SCL, SDA)
uint8_t rgbPins[] = {4, 12, 13, 14, 15, 21};
uint8_t addrPins[] = {16, 17, 25, 26};
uint8_t clockPin = 27; // Must be on same port as rgbPins
uint8_t latchPin = 32;
uint8_t oePin = 33;
#elif defined(ARDUINO_TEENSY40)
uint8_t rgbPins[] = {15, 16, 17, 20, 21, 22}; // A1-A3, A6-A8, skips SDA,SCL
uint8_t addrPins[] = {2, 3, 4, 5};
uint8_t clockPin = 23; // A9
uint8_t latchPin = 6;
uint8_t oePin = 9;
#elif defined(ARDUINO_TEENSY41)
uint8_t rgbPins[] = {26, 27, 38, 20, 21, 22}; // A12-14, A6-A8 (yes that's a 38, NOT 28!)
uint8_t addrPins[] = {2, 3, 4, 5};
uint8_t clockPin = 23; // A9
uint8_t latchPin = 6;
uint8_t oePin = 9;
#endif
// Last arg here enables double-buffering
Adafruit_Protomatter matrix(
64, 6, 1, rgbPins, 4, addrPins, clockPin, latchPin, oePin, true);
int16_t textMin,
textX = matrix.width(),
hue = 0;
char str[40];
int8_t ball[3][4] = {
{ 3, 0, 1, 1 }, // Initial X,Y pos & velocity for 3 bouncy balls
{ 17, 15, 1, -1 },
{ 27, 4, -1, 1 }
};
const uint16_t ballcolor[3] = {
0b0000000001000000, // Dark green
0b0000000000000001, // Dark blue
0b0000100000000000 // Dark red
};
void setup(void) {
Serial.begin(9600);
ProtomatterStatus status = matrix.begin();
Serial.print("Protomatter begin() status: ");
Serial.println((int)status);
sprintf(str, "Adafruit %dx%d RGB LED Matrix",
matrix.width(), matrix.height());
textMin = strlen(str) * -12;
matrix.setTextWrap(false);
matrix.setTextSize(2);
matrix.setTextColor(0xFFFF); // White
}
void loop(void) {
byte i;
// Clear background
matrix.fillScreen(0);
// Bounce three balls around
for(i=0; i<3; i++) {
// Draw 'ball'
matrix.fillCircle(ball[i][0], ball[i][1], 5, ballcolor[i]);
// Update X, Y position
ball[i][0] += ball[i][2];
ball[i][1] += ball[i][3];
// Bounce off edges
if((ball[i][0] == 0) || (ball[i][0] == (matrix.width() - 1)))
ball[i][2] *= -1;
if((ball[i][1] == 0) || (ball[i][1] == (matrix.height() - 1)))
ball[i][3] *= -1;
}
// Draw big scrolly text on top
matrix.setCursor(textX, 1);
matrix.print(str);
// Move text left (w/wrap), increase hue
if((--textX) < textMin) textX = matrix.width();
hue += 7;
if(hue >= 1536) hue -= 1536;
matrix.show();
delay(20);
}

View file

@ -0,0 +1,212 @@
/* ----------------------------------------------------------------------
Double-buffering (smooth animation) Protomatter library example.
PLEASE SEE THE "simple" EXAMPLE FOR AN INTRODUCTORY SKETCH.
Comments here pare down many of the basics and focus on the new concepts.
This example is written for a 64x32 matrix but can be adapted to others.
------------------------------------------------------------------------- */
#include <Adafruit_Protomatter.h>
#include <Fonts/FreeSansBold18pt7b.h> // Large friendly font
/* ----------------------------------------------------------------------
The RGB matrix must be wired to VERY SPECIFIC pins, different for each
microcontroller board. This first section sets that up for a number of
supported boards.
------------------------------------------------------------------------- */
#if defined(_VARIANT_MATRIXPORTAL_M4_) // MatrixPortal M4
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
uint8_t addrPins[] = {17, 18, 19, 20, 21};
uint8_t clockPin = 14;
uint8_t latchPin = 15;
uint8_t oePin = 16;
#elif defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) // MatrixPortal ESP32-S3
uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37};
uint8_t addrPins[] = {45, 36, 48, 35, 21};
uint8_t clockPin = 2;
uint8_t latchPin = 47;
uint8_t oePin = 14;
#elif defined(_VARIANT_FEATHER_M4_) // Feather M4 + RGB Matrix FeatherWing
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2};
uint8_t clockPin = 13;
uint8_t latchPin = 0;
uint8_t oePin = 1;
#elif defined(__SAMD51__) // M4 Metro Variants (Express, AirLift)
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2};
uint8_t clockPin = 13;
uint8_t latchPin = 0;
uint8_t oePin = 1;
#elif defined(_SAMD21_) // Feather M0 variants
uint8_t rgbPins[] = {6, 7, 10, 11, 12, 13};
uint8_t addrPins[] = {0, 1, 2, 3};
uint8_t clockPin = SDA;
uint8_t latchPin = 4;
uint8_t oePin = 5;
#elif defined(NRF52_SERIES) // Special nRF52840 FeatherWing pinout
uint8_t rgbPins[] = {6, A5, A1, A0, A4, 11};
uint8_t addrPins[] = {10, 5, 13, 9};
uint8_t clockPin = 12;
uint8_t latchPin = PIN_SERIAL1_RX;
uint8_t oePin = PIN_SERIAL1_TX;
#elif USB_VID == 0x239A && USB_PID == 0x8113 // Feather ESP32-S3 No PSRAM
// M0/M4/RP2040 Matrix FeatherWing compatible:
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2};
uint8_t clockPin = 13; // Must be on same port as rgbPins
uint8_t latchPin = RX;
uint8_t oePin = TX;
#elif USB_VID == 0x239A && USB_PID == 0x80EB // Feather ESP32-S2
// M0/M4/RP2040 Matrix FeatherWing compatible:
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2};
uint8_t clockPin = 13; // Must be on same port as rgbPins
uint8_t latchPin = RX;
uint8_t oePin = TX;
#elif defined(ESP32)
// 'Safe' pins, not overlapping any peripherals:
// GPIO.out: 4, 12, 13, 14, 15, 21, 27, GPIO.out1: 32, 33
// Peripheral-overlapping pins, sorted from 'most expendible':
// 16, 17 (RX, TX)
// 25, 26 (A0, A1)
// 18, 5, 9 (MOSI, SCK, MISO)
// 22, 23 (SCL, SDA)
uint8_t rgbPins[] = {4, 12, 13, 14, 15, 21};
uint8_t addrPins[] = {16, 17, 25, 26};
uint8_t clockPin = 27; // Must be on same port as rgbPins
uint8_t latchPin = 32;
uint8_t oePin = 33;
#elif defined(ARDUINO_TEENSY40)
uint8_t rgbPins[] = {15, 16, 17, 20, 21, 22}; // A1-A3, A6-A8, skip SDA,SCL
uint8_t addrPins[] = {2, 3, 4, 5};
uint8_t clockPin = 23; // A9
uint8_t latchPin = 6;
uint8_t oePin = 9;
#elif defined(ARDUINO_TEENSY41)
uint8_t rgbPins[] = {26, 27, 38, 20, 21, 22}; // A12-14, A6-A8
uint8_t addrPins[] = {2, 3, 4, 5};
uint8_t clockPin = 23; // A9
uint8_t latchPin = 6;
uint8_t oePin = 9;
#elif defined(ARDUINO_ADAFRUIT_FEATHER_RP2040)
// RP2040 support requires the Earle Philhower board support package;
// will not compile with the Arduino Mbed OS board package.
// The following pinout works with the Adafruit Feather RP2040 and
// original RGB Matrix FeatherWing (M0/M4/RP2040, not nRF version).
// Pin numbers here are GP## numbers, which may be different than
// the pins printed on some boards' top silkscreen.
uint8_t rgbPins[] = {8, 7, 9, 11, 10, 12};
uint8_t addrPins[] = {25, 24, 29, 28};
uint8_t clockPin = 13;
uint8_t latchPin = 1;
uint8_t oePin = 0;
#endif
/* ----------------------------------------------------------------------
Matrix initialization is explained EXTENSIVELY in "simple" example sketch!
It's very similar here, but we're passing "true" for the last argument,
enabling double-buffering -- this permits smooth animation by having us
draw in a second "off screen" buffer while the other is being shown.
------------------------------------------------------------------------- */
Adafruit_Protomatter matrix(
64, // Matrix width in pixels
6, // Bit depth -- 6 here provides maximum color options
1, rgbPins, // # of matrix chains, array of 6 RGB pins for each
4, addrPins, // # of address pins (height is inferred), array of pins
clockPin, latchPin, oePin, // Other matrix control pins
true); // HERE IS THE MAGIC FOR DOUBLE-BUFFERING!
// Sundry globals used for animation ---------------------------------------
int16_t textX; // Current text position (X)
int16_t textY; // Current text position (Y)
int16_t textMin; // Text pos. (X) when scrolled off left edge
char str[64]; // Buffer to hold scrolling message text
int16_t ball[3][4] = {
{ 3, 0, 1, 1 }, // Initial X,Y pos+velocity of 3 bouncy balls
{ 17, 15, 1, -1 },
{ 27, 4, -1, 1 }
};
uint16_t ballcolor[3]; // Colors for bouncy balls (init in setup())
// SETUP - RUNS ONCE AT PROGRAM START --------------------------------------
void setup(void) {
Serial.begin(9600);
// Initialize matrix...
ProtomatterStatus status = matrix.begin();
Serial.print("Protomatter begin() status: ");
Serial.println((int)status);
if(status != PROTOMATTER_OK) {
// DO NOT CONTINUE if matrix setup encountered an error.
for(;;);
}
// Unlike the "simple" example, we don't do any drawing in setup().
// But we DO initialize some things we plan to animate...
// Set up the scrolling message...
sprintf(str, "Adafruit %dx%d RGB LED Matrix",
matrix.width(), matrix.height());
matrix.setFont(&FreeSansBold18pt7b); // Use nice bitmap font
matrix.setTextWrap(false); // Allow text off edge
matrix.setTextColor(0xFFFF); // White
int16_t x1, y1;
uint16_t w, h;
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
textX = matrix.width(); // Start off right edge
textY = matrix.height() / 2 - (y1 + h / 2); // Center text vertically
// Note: when making scrolling text like this, the setTextWrap(false)
// call is REQUIRED (to allow text to go off the edge of the matrix),
// AND it must be BEFORE the getTextBounds() call (or else that will
// return the bounds of "wrapped" text).
// Set up the colors of the bouncy balls.
ballcolor[0] = matrix.color565(0, 20, 0); // Dark green
ballcolor[1] = matrix.color565(0, 0, 20); // Dark blue
ballcolor[2] = matrix.color565(20, 0, 0); // Dark red
}
// LOOP - RUNS REPEATEDLY AFTER SETUP --------------------------------------
void loop(void) {
// Every frame, we clear the background and draw everything anew.
// This happens "in the background" with double buffering, that's
// why you don't see everything flicker. It requires double the RAM,
// so it's not practical for every situation.
matrix.fillScreen(0); // Fill background black
// Draw the big scrolly text
matrix.setCursor(textX, textY);
matrix.print(str);
// Update text position for next frame. If text goes off the
// left edge, reset its position to be off the right edge.
if((--textX) < textMin) textX = matrix.width();
// Draw the three bouncy balls on top of the text...
for(byte i=0; i<3; i++) {
// Draw 'ball'
matrix.fillCircle(ball[i][0], ball[i][1], 5, ballcolor[i]);
// Update ball's X,Y position for next frame
ball[i][0] += ball[i][2];
ball[i][1] += ball[i][3];
// Bounce off edges
if((ball[i][0] == 0) || (ball[i][0] == (matrix.width() - 1)))
ball[i][2] *= -1;
if((ball[i][1] == 0) || (ball[i][1] == (matrix.height() - 1)))
ball[i][3] *= -1;
}
// AFTER DRAWING, A show() CALL IS REQUIRED TO UPDATE THE MATRIX!
matrix.show();
delay(20); // 20 milliseconds = ~50 frames/second
}

View file

@ -1,34 +1,58 @@
#include <Wire.h> // For I2C communication
#include <Adafruit_LIS3DH.h>
#include <Adafruit_PixelDust.h> // For simulation
#include "Adafruit_Protomatter.h"
/* ----------------------------------------------------------------------
"Pixel dust" Protomatter library example. As written, this is
SPECIFICALLY FOR THE ADAFRUIT MATRIXPORTAL with 64x32 pixel matrix.
Change "HEIGHT" below for 64x64 matrix. Could also be adapted to other
Protomatter-capable boards with an attached LIS3DH accelerometer.
PLEASE SEE THE "simple" EXAMPLE FOR AN INTRODUCTORY SKETCH,
or "doublebuffer" for animation basics.
------------------------------------------------------------------------- */
#include <Wire.h> // For I2C communication
#include <Adafruit_LIS3DH.h> // For accelerometer
#include <Adafruit_PixelDust.h> // For sand simulation
#include <Adafruit_Protomatter.h> // For RGB matrix
#define HEIGHT 32 // Matrix height (pixels) - SET TO 64 FOR 64x64 MATRIX!
#define WIDTH 64 // Matrix width (pixels)
#define MAX_FPS 45 // Maximum redraw rate, frames/second
#if defined(_VARIANT_MATRIXPORTAL_M4_) // MatrixPortal M4
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
uint8_t addrPins[] = {17, 18, 19, 20};
uint8_t addrPins[] = {17, 18, 19, 20, 21};
uint8_t clockPin = 14;
uint8_t latchPin = 15;
uint8_t oePin = 16;
#else // MatrixPortal ESP32-S3
uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37};
uint8_t addrPins[] = {45, 36, 48, 35, 21};
uint8_t clockPin = 2;
uint8_t latchPin = 47;
uint8_t oePin = 14;
#endif
#if HEIGHT == 16
#define NUM_ADDR_PINS 3
#elif HEIGHT == 32
#define NUM_ADDR_PINS 4
#elif HEIGHT == 64
#define NUM_ADDR_PINS 5
#endif
Adafruit_Protomatter matrix(
WIDTH, 4, 1, rgbPins, NUM_ADDR_PINS, addrPins,
clockPin, latchPin, oePin, true);
Adafruit_LIS3DH accel = Adafruit_LIS3DH();
Adafruit_Protomatter matrix(
64, 4, 1, rgbPins, 4, addrPins, clockPin, latchPin, oePin, true);
#define WIDTH 64 // Display width in pixels
#define HEIGHT 32 // Display height in pixels
#define MAX_FPS 45 // Maximum redraw rate, frames/second
#define N_COLORS 8
#define N_COLORS 8
#define BOX_HEIGHT 8
#define N_GRAINS (BOX_HEIGHT*N_COLORS*8)
uint16_t colors[N_COLORS];
// Sand object, last 2 args are accelerometer scaling and grain elasticity
Adafruit_PixelDust sand(WIDTH, HEIGHT, N_GRAINS, 1, 128, false);
uint32_t prevTime = 0; // Used for frames-per-second throttle
uint32_t prevTime = 0; // Used for frames-per-second throttle
// SETUP - RUNS ONCE AT PROGRAM START --------------------------------------
@ -42,24 +66,23 @@ void err(int x) {
}
void setup(void) {
uint8_t i, j, bytes;
Serial.begin(115200);
//while (!Serial) delay(10);
ProtomatterStatus status = matrix.begin();
Serial.printf("Protomatter begin() status: %d\n", status);
if (!sand.begin()) {
if (!sand.begin()) {
Serial.println("Couldn't start sand");
err(1000); // Slow blink = malloc error
}
if (!accel.begin(0x19)) {
if (!accel.begin(0x19)) {
Serial.println("Couldn't find accelerometer");
err(250); // Fast bink = I2C error
}
accel.setRange(LIS3DH_RANGE_4_G); // 2, 4, 8 or 16 G!
//sand.randomize(); // Initialize random sand positions
// Set up initial sand coordinates, in 8x8 blocks
@ -76,19 +99,16 @@ void setup(void) {
}
Serial.printf("%d total pixels\n", n);
colors[0] = color565(64, 64, 64); // Dark Gray
colors[1] = color565(120, 79, 23); // Brown
colors[2] = color565(228, 3, 3); // Red
colors[3] = color565(255,140, 0); // Orange
colors[4] = color565(255,237, 0); // Yellow
colors[5] = color565( 0,128, 38); // Green
colors[6] = color565( 0, 77,255); // Blue
colors[7] = color565(117, 7,135); // Purple
colors[0] = matrix.color565(64, 64, 64); // Dark Gray
colors[1] = matrix.color565(120, 79, 23); // Brown
colors[2] = matrix.color565(228, 3, 3); // Red
colors[3] = matrix.color565(255,140, 0); // Orange
colors[4] = matrix.color565(255,237, 0); // Yellow
colors[5] = matrix.color565( 0,128, 38); // Green
colors[6] = matrix.color565( 0, 77,255); // Blue
colors[7] = matrix.color565(117, 7,135); // Purple
}
uint16_t color565(uint8_t red, uint8_t green, uint8_t blue) {
return ((red & 0xF8) << 8) | ((green & 0xFC) << 3) | (blue >> 3);
}
// MAIN LOOP - RUNS ONCE PER FRAME OF ANIMATION ----------------------------
void loop() {
@ -99,7 +119,7 @@ void loop() {
uint32_t t;
while(((t = micros()) - prevTime) < (1000000L / MAX_FPS));
prevTime = t;
// Read accelerometer...
sensors_event_t event;
accel.getEvent(&event);
@ -112,7 +132,7 @@ void loop() {
// Run one frame of the simulation
sand.iterate(xx, yy, zz);
//sand.iterate(-accel.y, accel.x, accel.z);
// Update pixel data in LED driver
@ -126,6 +146,4 @@ void loop() {
//Serial.printf("(%d, %d)\n", x, y);
}
matrix.show(); // Copy data to matrix buffers
}

View file

@ -1,131 +0,0 @@
#include <Wire.h> // For I2C communication
#include <Adafruit_LIS3DH.h>
#include <Adafruit_PixelDust.h> // For simulation
#include "Adafruit_Protomatter.h"
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
uint8_t addrPins[] = {17, 18, 19, 20, 21};
uint8_t clockPin = 14;
uint8_t latchPin = 15;
uint8_t oePin = 16;
Adafruit_LIS3DH accel = Adafruit_LIS3DH();
Adafruit_Protomatter matrix(
64, 4, 1, rgbPins, 5, addrPins, clockPin, latchPin, oePin, true);
#define WIDTH 64 // Display width in pixels
#define HEIGHT 64 // Display height in pixels
#define MAX_FPS 45 // Maximum redraw rate, frames/second
#define N_COLORS 8
#define BOX_HEIGHT 8
#define N_GRAINS (BOX_HEIGHT*N_COLORS*8)
uint16_t colors[N_COLORS];
// Sand object, last 2 args are accelerometer scaling and grain elasticity
Adafruit_PixelDust sand(WIDTH, HEIGHT, N_GRAINS, 1, 128, false);
uint32_t prevTime = 0; // Used for frames-per-second throttle
// SETUP - RUNS ONCE AT PROGRAM START --------------------------------------
void err(int x) {
uint8_t i;
pinMode(LED_BUILTIN, OUTPUT); // Using onboard LED
for(i=1;;i++) { // Loop forever...
digitalWrite(LED_BUILTIN, i & 1); // LED on/off blink to alert user
delay(x);
}
}
void setup(void) {
uint8_t i, j, bytes;
Serial.begin(115200);
//while (!Serial) delay(10);
ProtomatterStatus status = matrix.begin();
Serial.printf("Protomatter begin() status: %d\n", status);
if (!sand.begin()) {
Serial.println("Couldn't start sand");
err(1000); // Slow blink = malloc error
}
if (!accel.begin(0x19)) {
Serial.println("Couldn't find accelerometer");
err(250); // Fast bink = I2C error
}
accel.setRange(LIS3DH_RANGE_4_G); // 2, 4, 8 or 16 G!
//sand.randomize(); // Initialize random sand positions
// Set up initial sand coordinates, in 8x8 blocks
int n = 0;
for(int i=0; i<N_COLORS; i++) {
int xx = i * WIDTH / N_COLORS;
int yy = HEIGHT - BOX_HEIGHT;
for(int y=0; y<BOX_HEIGHT; y++) {
for(int x=0; x < WIDTH / N_COLORS; x++) {
//Serial.printf("#%d -> (%d, %d)\n", n, xx + x, yy + y);
sand.setPosition(n++, xx + x, yy + y);
}
}
}
Serial.printf("%d total pixels\n", n);
colors[0] = color565(64, 64, 64); // Dark Gray
colors[1] = color565(120, 79, 23); // Brown
colors[2] = color565(228, 3, 3); // Red
colors[3] = color565(255,140, 0); // Orange
colors[4] = color565(255,237, 0); // Yellow
colors[5] = color565( 0,128, 38); // Green
colors[6] = color565( 0, 77,255); // Blue
colors[7] = color565(117, 7,135); // Purple
}
uint16_t color565(uint8_t red, uint8_t green, uint8_t blue) {
return ((red & 0xF8) << 8) | ((green & 0xFC) << 3) | (blue >> 3);
}
// MAIN LOOP - RUNS ONCE PER FRAME OF ANIMATION ----------------------------
void loop() {
// Limit the animation frame rate to MAX_FPS. Because the subsequent sand
// calculations are non-deterministic (don't always take the same amount
// of time, depending on their current states), this helps ensure that
// things like gravity appear constant in the simulation.
uint32_t t;
while(((t = micros()) - prevTime) < (1000000L / MAX_FPS));
prevTime = t;
// Read accelerometer...
sensors_event_t event;
accel.getEvent(&event);
//Serial.printf("(%0.1f, %0.1f, %0.1f)\n", event.acceleration.x, event.acceleration.y, event.acceleration.z);
double xx, yy, zz;
xx = event.acceleration.x * 1000;
yy = event.acceleration.y * 1000;
zz = event.acceleration.z * 1000;
// Run one frame of the simulation
sand.iterate(xx, yy, zz);
//sand.iterate(-accel.y, accel.x, accel.z);
// Update pixel data in LED driver
dimension_t x, y;
matrix.fillScreen(0x0);
for(int i=0; i<N_GRAINS ; i++) {
sand.getPosition(i, &x, &y);
int n = i / ((WIDTH / N_COLORS) * BOX_HEIGHT); // Color index
uint16_t flakeColor = colors[n];
matrix.drawPixel(x, y, flakeColor);
//Serial.printf("(%d, %d)\n", x, y);
}
matrix.show(); // Copy data to matrix buffers
}

View file

@ -1,155 +0,0 @@
#include "Adafruit_Protomatter.h"
/*
METRO M0 PORT-TO-PIN ASSIGNMENTS BY BYTE:
PA00 PA08 D4 PA16 D11 PB00 PB08 A1
PA01 PA09 D3 PA17 D13 PB01 PB09 A2
PA02 A0 PA10 D1 PA18 D10 PB02 A5 PB10 MOSI
PA03 PA11 D0 PA19 D12 PB03 PB11 SCK
PA04 A3 PA12 MISO PA20 D6 PB04 PB12
PA05 A4 PA13 PA21 D7 PB05 PB13
PA06 D8 PA14 D2 PA22 SDA PB06 PB14
PA07 D9 PA15 D5 PA23 SCL PB07 PB15
SAME, METRO M4:
PA00 PA08 PA16 D13 PB00 PB08 A4 PB16 D3
PA01 PA09 PA17 D12 PB01 PB09 A5 PB17 D2
PA02 A0 PA10 PA18 D10 PB02 SDA PB10 PB18
PA03 PA11 PA19 D11 PB03 SCL PB11 PB19
PA04 A3 PA12 MISO PA20 D9 PB04 PB12 D7 PB20
PA05 A1 PA13 SCK PA21 D8 PB05 PB13 D4 PB21
PA06 A2 PA14 MISO PA22 D1 PB06 PB14 D5 PB22
PA07 PA15 PA23 D0 PB07 PB15 D6 PB23
FEATHER M4:
PA00 PA08 PA16 D5 PB08 A2 PB16 D1/TX
PA01 PA09 PA17 SCK PB09 A3 PB17 D0/RX
PA02 A0 PA10 PA18 D6 PB10 PB18
PA03 PA11 PA19 D9 PB11 PB19
PA04 A4 PA12 SDA PA20 D10 PB12 PB20
PA05 A1 PA13 SCL PA21 D11 PB13 PB21
PA06 A5 PA14 D4 PA22 D12 PB14 PB22 MISO
PA07 PA15 PA23 D13 PB15 PB23 MOSI
FEATHER M0:
PA00 PA08 PA16 D11 PB00 PB08 A1
PA01 PA09 PA17 D13 PB01 PB09 A2
PA02 A0 PA10 TX/D1 PA18 D10 PB02 A5 PB10 MOSI
PA03 PA11 RX/D0 PA19 D12 PB03 PB11 SCK
PA04 A3 PA12 MISO PA20 D6 PB04 PB12
PA05 A4 PA13 PA21 D7 PB05 PB13
PA06 PA14 PA22 SDA PB06 PB14
PA07 D9 PA15 D5 PA23 SCL PB07 PB15
FEATHER nRF52840:
P0.00 P0.08 D12 P0.24 RXD P1.08 D5
P0.01 P0.09 P0.25 TXD P1.09 D13
P0.02 A4 P0.10 D2 (NFC) P0.26 D9 P1.10
P0.03 A5 P0.11 SCL P0.27 D10 P1.11
P0.04 A0 P0.12 SDA P0.28 A3 P1.12
P0.05 A1 P0.13 MOSI P0.29 P1.13
P0.06 D11 P0.14 SCK P0.30 A2 P1.14
P0.07 D6 P0.15 MISO P0.31 P1.15
FEATHER ESP32:
P0.00 P0.08 P0.16 16/RX P0.24 P1.00 32/A7
P0.01 P0.09 P0.17 17/TX P0.25 25/A1 P1.01 33/A9/SS
P0.02 P0.10 P0.18 18/MOSI P0.26 26/A0 P1.02 34/A2 (in)
P0.03 P0.11 P0.19 19/MISO P0.27 27/A10 P1.03
P0.04 4/A5 P0.12 12/A11 P0.20 P0.28 P1.04 36/A4 (in)
P0.05 5/SCK P0.13 13/A12 P0.21 21 P0.29 P1.05
P0.06 P0.14 14/A6 P0.22 22/SCL P0.30 P1.06
P0.07 P0.15 15/A8 P0.23 23/SDA P0.31 P1.07 39/A3 (in)
RGB Matrix FeatherWing:
R1 D6 A A5
G1 D5 B A4
B1 D9 C A3
R2 D11 D A2
G2 D10 LAT D0/RX
B2 D12 OE D1/TX
CLK D13
RGB+clock in one PORT byte on Feather M4!
RGB+clock are on same PORT but not within same byte on Feather M0 --
the code could run there (with some work to be done in the convert_*
functions), but would be super RAM-inefficient. Should be fine on other
M0 devices like a Metro, if wiring manually so one can pick a contiguous
byte of PORT bits.
RGB+clock are on different PORTs on nRF52840.
*/
#if defined(__SAMD51__)
// Use FeatherWing pinout
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2};
uint8_t clockPin = 13;
uint8_t latchPin = 0;
uint8_t oePin = 1;
#elif defined(_SAMD21_)
uint8_t rgbPins[] = {6, 7, 10, 11, 12, 13};
uint8_t addrPins[] = {0, 1, 2, 3};
uint8_t clockPin = SDA;
uint8_t latchPin = 4;
uint8_t oePin = 5;
#elif defined(NRF52_SERIES)
// Special nRF52840 FeatherWing pinout
uint8_t rgbPins[] = {6, A5, A1, A0, A4, 11};
uint8_t addrPins[] = {10, 5, 13, 9};
uint8_t clockPin = 12;
uint8_t latchPin = PIN_SERIAL1_RX;
uint8_t oePin = PIN_SERIAL1_TX;
#elif defined(ESP32)
// 'Safe' pins (not overlapping any peripherals):
// GPIO.out: 4, 12, 13, 14, 15, 21, 27, GPIO.out1: 32, 33
// Peripheral-overlapping pins, sorted from 'most expendible':
// 16, 17 (RX, TX), 25, 26 (A0, A1), 18, 5, 9 (MOSI, SCK, MISO), 22, 23 (SCL, SDA)
uint8_t rgbPins[] = {4, 12, 13, 14, 15, 21};
uint8_t addrPins[] = {16, 17, 25, 26};
uint8_t clockPin = 27; // Must be on same port as rgbPins
uint8_t latchPin = 32;
uint8_t oePin = 33;
#elif defined(ARDUINO_TEENSY40)
uint8_t rgbPins[] = {15, 16, 17, 20, 21, 22}; // A1-A3, A6-A8, skips SDA,SCL
uint8_t addrPins[] = {2, 3, 4, 5};
uint8_t clockPin = 23; // A9
uint8_t latchPin = 6;
uint8_t oePin = 9;
#elif defined(ARDUINO_TEENSY41)
uint8_t rgbPins[] = {26, 27, 38, 20, 21, 22}; // A12-14, A6-A8 (yes that's a 38, NOT 28!)
uint8_t addrPins[] = {2, 3, 4, 5};
uint8_t clockPin = 23; // A9
uint8_t latchPin = 6;
uint8_t oePin = 9;
#endif
Adafruit_Protomatter matrix(
64, 4, 1, rgbPins, 4, addrPins, clockPin, latchPin, oePin, false);
void setup(void) {
Serial.begin(9600);
ProtomatterStatus status = matrix.begin();
Serial.print("Protomatter begin() status: ");
Serial.println((int)status);
for(int x=0; x<matrix.width(); x++) {
uint8_t rb = x * 32 / matrix.width();
uint8_t g = x * 64 / matrix.width();
matrix.drawPixel(x, matrix.height() - 4, rb << 11);
matrix.drawPixel(x, matrix.height() - 3, g << 5);
matrix.drawPixel(x, matrix.height() - 2, rb);
matrix.drawPixel(x, matrix.height() - 1, (rb << 11) | (g << 5) | rb);
}
matrix.drawCircle(12, 10, 9, 0b1111100000000000); // Red
matrix.drawCircle(22, 14, 9, 0b0000011111100000); // Green
matrix.drawCircle(32, 18, 9, 0b0000000000011111); // Blue
matrix.println("ADAFRUIT");
matrix.show(); // Copy data to matrix buffers
}
void loop(void) {
Serial.print("Refresh FPS = ~");
Serial.println(matrix.getFrameCount());
delay(1000);
}

349
examples/simple/simple.ino Normal file
View file

@ -0,0 +1,349 @@
/* ----------------------------------------------------------------------
"Simple" Protomatter library example sketch (once you get past all
the various pin configurations at the top, and all the comments).
Shows basic use of Adafruit_Protomatter library with different devices.
This example is written for a 64x32 matrix but can be adapted to others.
Once the RGB matrix is initialized, most functions of the Adafruit_GFX
library are available for drawing -- code from other projects that use
LCDs or OLEDs can be easily adapted, or may be insightful for reference.
GFX library is documented here:
https://learn.adafruit.com/adafruit-gfx-graphics-library
------------------------------------------------------------------------- */
#include <Adafruit_Protomatter.h>
/* ----------------------------------------------------------------------
The RGB matrix must be wired to VERY SPECIFIC pins, different for each
microcontroller board. This first section sets that up for a number of
supported boards. Notes have been moved to the bottom of the code.
------------------------------------------------------------------------- */
#if defined(_VARIANT_MATRIXPORTAL_M4_) // MatrixPortal M4
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
uint8_t addrPins[] = {17, 18, 19, 20, 21};
uint8_t clockPin = 14;
uint8_t latchPin = 15;
uint8_t oePin = 16;
#elif defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) // MatrixPortal ESP32-S3
uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37};
uint8_t addrPins[] = {45, 36, 48, 35, 21};
uint8_t clockPin = 2;
uint8_t latchPin = 47;
uint8_t oePin = 14;
#elif defined(_VARIANT_FEATHER_M4_) // Feather M4 + RGB Matrix FeatherWing
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2};
uint8_t clockPin = 13;
uint8_t latchPin = 0;
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
// M0/M4/RP2040 Matrix FeatherWing compatible:
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2};
uint8_t clockPin = 13; // Must be on same port as rgbPins
uint8_t latchPin = RX;
uint8_t oePin = TX;
#elif defined(ARDUINO_METRO_ESP32S2) // Metro ESP32-S2
// Matrix Shield compatible:
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
uint8_t addrPins[] = {A0, A1, A2, A3};
uint8_t clockPin = 13; // Must be on same port as rgbPins
uint8_t latchPin = 15;
uint8_t oePin = 14;
#elif defined(__SAMD51__) // M4 Metro Variants (Express, AirLift)
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2};
uint8_t clockPin = 13;
uint8_t latchPin = 0;
uint8_t oePin = 1;
#elif defined(_SAMD21_) // Feather M0 variants
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2};
uint8_t clockPin = 13;
uint8_t latchPin = 0;
uint8_t oePin = 1;
#elif defined(NRF52_SERIES) // Special nRF52840 FeatherWing pinout
uint8_t rgbPins[] = {6, A5, A1, A0, A4, 11};
uint8_t addrPins[] = {10, 5, 13, 9};
uint8_t clockPin = 12;
uint8_t latchPin = PIN_SERIAL1_RX;
uint8_t oePin = PIN_SERIAL1_TX;
#elif USB_VID == 0x239A && USB_PID == 0x8113 // Feather ESP32-S3 No PSRAM
// M0/M4/RP2040 Matrix FeatherWing compatible:
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2};
uint8_t clockPin = 13; // Must be on same port as rgbPins
uint8_t latchPin = RX;
uint8_t oePin = TX;
#elif defined(ESP32)
// 'Safe' pins, not overlapping any peripherals:
// GPIO.out: 4, 12, 13, 14, 15, 21, 27, GPIO.out1: 32, 33
// Peripheral-overlapping pins, sorted from 'most expendible':
// 16, 17 (RX, TX)
// 25, 26 (A0, A1)
// 18, 5, 9 (MOSI, SCK, MISO)
// 22, 23 (SCL, SDA)
uint8_t rgbPins[] = {4, 12, 13, 14, 15, 21};
uint8_t addrPins[] = {16, 17, 25, 26};
uint8_t clockPin = 27; // Must be on same port as rgbPins
uint8_t latchPin = 32;
uint8_t oePin = 33;
#elif defined(ARDUINO_TEENSY40)
uint8_t rgbPins[] = {15, 16, 17, 20, 21, 22}; // A1-A3, A6-A8, skip SDA,SCL
uint8_t addrPins[] = {2, 3, 4, 5};
uint8_t clockPin = 23; // A9
uint8_t latchPin = 6;
uint8_t oePin = 9;
#elif defined(ARDUINO_TEENSY41)
uint8_t rgbPins[] = {26, 27, 38, 20, 21, 22}; // A12-14, A6-A8
uint8_t addrPins[] = {2, 3, 4, 5};
uint8_t clockPin = 23; // A9
uint8_t latchPin = 6;
uint8_t oePin = 9;
#elif defined(ARDUINO_ADAFRUIT_FEATHER_RP2040)
// RP2040 support requires the Earle Philhower board support package;
// will not compile with the Arduino Mbed OS board package.
// The following pinout works with the Adafruit Feather RP2040 and
// original RGB Matrix FeatherWing (M0/M4/RP2040, not nRF version).
// Pin numbers here are GP## numbers, which may be different than
// the pins printed on some boards' top silkscreen.
uint8_t rgbPins[] = {8, 7, 9, 11, 10, 12};
uint8_t addrPins[] = {25, 24, 29, 28};
uint8_t clockPin = 13;
uint8_t latchPin = 1;
uint8_t oePin = 0;
#endif
/* ----------------------------------------------------------------------
Okay, here's where the RGB LED matrix is actually declared...
First argument is the matrix width, in pixels. Usually 32 or
64, but might go larger if you're chaining multiple matrices.
Second argument is the "bit depth," which determines color
fidelity, applied to red, green and blue (e.g. "4" here means
4 bits red, 4 green, 4 blue = 2^4 x 2^4 x 2^4 = 4096 colors).
There is a trade-off between bit depth and RAM usage. Most
programs will tend to use either 1 (R,G,B on/off, 8 colors,
best for text, LED sand, etc.) or the maximum of 6 (best for
shaded images...though, because the GFX library was designed
for LCDs, only 5 of those bits are available for red and blue.
Third argument is the number of concurrent (parallel) matrix
outputs. THIS SHOULD ALWAYS BE "1" FOR NOW. Fourth is a uint8_t
array listing six pins: red, green and blue data out for the
top half of the display, and same for bottom half. There are
hard constraints as to which pins can be used -- they must all
be on the same PORT register, ideally all within the same byte
of that PORT.
Fifth argument is the number of "address" (aka row select) pins,
from which the matrix height is inferred. "4" here means four
address lines, matrix height is then (2 x 2^4) = 32 pixels.
16-pixel-tall matrices will have 3 pins here, 32-pixel will have
4, 64-pixel will have 5. Sixth argument is a uint8_t array
listing those pin numbers. No PORT constraints here.
Next three arguments are pin numbers for other RGB matrix
control lines: clock, latch and output enable (active low).
Clock pin MUST be on the same PORT register as RGB data pins
(and ideally in same byte). Other pins have no special rules.
Last argument is a boolean (true/false) to enable double-
buffering for smooth animation (requires 2X the RAM). See the
"doublebuffer" example for a demonstration.
------------------------------------------------------------------------- */
Adafruit_Protomatter matrix(
64, // Width of matrix (or matrix chain) in pixels
4, // Bit depth, 1-6
1, rgbPins, // # of matrix chains, array of 6 RGB pins for each
4, addrPins, // # of address pins (height is inferred), array of pins
clockPin, latchPin, oePin, // Other matrix control pins
false); // No double-buffering here (see "doublebuffer" example)
// SETUP - RUNS ONCE AT PROGRAM START --------------------------------------
void setup(void) {
Serial.begin(9600);
// Initialize matrix...
ProtomatterStatus status = matrix.begin();
Serial.print("Protomatter begin() status: ");
Serial.println((int)status);
if(status != PROTOMATTER_OK) {
// DO NOT CONTINUE if matrix setup encountered an error.
for(;;);
}
// Since this is a simple program with no animation, all the
// drawing can be done here in setup() rather than loop():
// Make four color bars (red, green, blue, white) with brightness ramp:
for(int x=0; x<matrix.width(); x++) {
uint8_t level = x * 256 / matrix.width(); // 0-255 brightness
matrix.drawPixel(x, matrix.height() - 4, matrix.color565(level, 0, 0));
matrix.drawPixel(x, matrix.height() - 3, matrix.color565(0, level, 0));
matrix.drawPixel(x, matrix.height() - 2, matrix.color565(0, 0, level));
matrix.drawPixel(x, matrix.height() - 1,
matrix.color565(level, level, level));
}
// You'll notice the ramp looks smoother as bit depth increases
// (second argument to the matrix constructor call above setup()).
// Simple shapes and text, showing GFX library calls:
matrix.drawCircle(12, 10, 9, matrix.color565(255, 0, 0));
matrix.drawRect(14, 6, 17, 17, matrix.color565(0, 255, 0));
matrix.drawTriangle(32, 9, 41, 27, 23, 27, matrix.color565(0, 0, 255));
matrix.println("ADAFRUIT"); // Default text color is white
if (matrix.height() > 32) {
matrix.setCursor(0, 32);
matrix.println("64 pixel"); // Default text color is white
matrix.println("matrix"); // Default text color is white
}
// AFTER DRAWING, A show() CALL IS REQUIRED TO UPDATE THE MATRIX!
matrix.show(); // Copy data to matrix buffers
}
// LOOP - RUNS REPEATEDLY AFTER SETUP --------------------------------------
void loop(void) {
// Since there's nothing more to be drawn, this loop() function just
// shows the approximate refresh rate of the matrix at current settings.
Serial.print("Refresh FPS = ~");
Serial.println(matrix.getFrameCount());
delay(1000);
}
// MORE NOTES --------------------------------------------------------------
/*
The "RGB and clock bits on same PORT register" constraint requires
considerable planning and knowledge of the underlying microcontroller
hardware. These are some earlier notes on various devices' PORT registers
and bits and their corresponding Arduino pin numbers. You probably won't
need this -- it's all codified in the #if defined() sections at the top
of this sketch now -- but keeping it around for reference if needed.
METRO M0 PORT-TO-PIN ASSIGNMENTS BY BYTE:
PA00 PA08 D4 PA16 D11 PB00 PB08 A1
PA01 PA09 D3 PA17 D13 PB01 PB09 A2
PA02 A0 PA10 D1 PA18 D10 PB02 A5 PB10 MOSI
PA03 PA11 D0 PA19 D12 PB03 PB11 SCK
PA04 A3 PA12 MISO PA20 D6 PB04 PB12
PA05 A4 PA13 PA21 D7 PB05 PB13
PA06 D8 PA14 D2 PA22 SDA PB06 PB14
PA07 D9 PA15 D5 PA23 SCL PB07 PB15
SAME, METRO M4:
PA00 PA08 PA16 D13 PB00 PB08 A4 PB16 D3
PA01 PA09 PA17 D12 PB01 PB09 A5 PB17 D2
PA02 A0 PA10 PA18 D10 PB02 SDA PB10 PB18
PA03 PA11 PA19 D11 PB03 SCL PB11 PB19
PA04 A3 PA12 MISO PA20 D9 PB04 PB12 D7 PB20
PA05 A1 PA13 SCK PA21 D8 PB05 PB13 D4 PB21
PA06 A2 PA14 MISO PA22 D1 PB06 PB14 D5 PB22
PA07 PA15 PA23 D0 PB07 PB15 D6 PB23
FEATHER M4:
PA00 PA08 PA16 D5 PB08 A2 PB16 D1/TX
PA01 PA09 PA17 SCK PB09 A3 PB17 D0/RX
PA02 A0 PA10 PA18 D6 PB10 PB18
PA03 PA11 PA19 D9 PB11 PB19
PA04 A4 PA12 SDA PA20 D10 PB12 PB20
PA05 A1 PA13 SCL PA21 D11 PB13 PB21
PA06 A5 PA14 D4 PA22 D12 PB14 PB22 MISO
PA07 PA15 PA23 D13 PB15 PB23 MOSI
FEATHER M0:
PA00 PA08 PA16 D11 PB00 PB08 A1
PA01 PA09 PA17 D13 PB01 PB09 A2
PA02 A0 PA10 TX/D1 PA18 D10 PB02 A5 PB10 MOSI
PA03 PA11 RX/D0 PA19 D12 PB03 PB11 SCK
PA04 A3 PA12 MISO PA20 D6 PB04 PB12
PA05 A4 PA13 PA21 D7 PB05 PB13
PA06 PA14 PA22 SDA PB06 PB14
PA07 D9 PA15 D5 PA23 SCL PB07 PB15
FEATHER nRF52840:
P0.00 P0.08 D12 P0.24 RXD P1.08 D5
P0.01 P0.09 P0.25 TXD P1.09 D13
P0.02 A4 P0.10 D2 (NFC) P0.26 D9 P1.10
P0.03 A5 P0.11 SCL P0.27 D10 P1.11
P0.04 A0 P0.12 SDA P0.28 A3 P1.12
P0.05 A1 P0.13 MOSI P0.29 P1.13
P0.06 D11 P0.14 SCK P0.30 A2 P1.14
P0.07 D6 P0.15 MISO P0.31 P1.15
FEATHER ESP32:
P0.00 P0.08 P0.16 16/RX P0.24 P1.00 32/A7
P0.01 P0.09 P0.17 17/TX P0.25 25/A1 P1.01 33/A9/SS
P0.02 P0.10 P0.18 18/MOSI P0.26 26/A0 P1.02 34/A2 (in)
P0.03 P0.11 P0.19 19/MISO P0.27 27/A10 P1.03
P0.04 4/A5 P0.12 12/A11 P0.20 P0.28 P1.04 36/A4 (in)
P0.05 5/SCK P0.13 13/A12 P0.21 21 P0.29 P1.05
P0.06 P0.14 14/A6 P0.22 22/SCL P0.30 P1.06
P0.07 P0.15 15/A8 P0.23 23/SDA P0.31 P1.07 39/A3 (in)
GRAND CENTRAL M4: (___ = byte boundaries)
PA00 PB00 D12 PC00 A3 PD00
PA01 PB01 D13 (LED) PC01 A4 PD01
PA02 A0 PB02 D9 PC02 A5 PD02
PA03 84 (AREF) PB03 A2 PC03 A6 PD03
PA04 A13 PB04 A7 PC04 D48 PD04
PA05 A1 PB05 A8 PC05 D49 PD05
PA06 A14 PB06 A9 PC06 D46 PD06
PA07 A15 ______ PB07 A10 ______ PC07 D47 _____ PD07 __________
PA08 PB08 A11 PC08 PD08 D51 (SCK)
PA09 PB09 A12 PC09 PD09 D52 (MOSI)
PA10 PB10 PC10 D45 PD10 D53
PA11 PB11 PC11 D44 PD11 D50 (MISO)
PA12 D26 PB12 D18 PC12 D41 PD12 D22
PA13 D27 PB13 D19 PC13 D40 PD13
PA14 D28 PB14 D39 PC14 D43 PD14
PA15 D23 ______ PB15 D38 ______ PC15 D42 _____ PD15 __________
PA16 D37 PB16 D14 PC16 D25 PD16
PA17 D36 PB17 D15 PC17 D24 PD17
PA18 D35 PB18 D8 PC18 D2 PD18
PA19 D34 PB19 D29 PC19 D3 PD19
PA20 D33 PB20 D20 (SDA) PC20 D4 PD20 D6
PA21 D32 PB21 D21 (SCL) PC21 D5 PD21 D7
PA22 D31 PB22 D10 PC22 D16 PD22
PA23 D30 ______ PB23 D11 ______ PC23 D17 _____ PD23 __________
PA24 PB24 D1
PA25 PB25 D0
PA26 PB26
PA27 PB27
PA28 PB28
PA29 PB29
PA30 PB30 96 (SWO)
PA31 __________ PB31 95 (SD CD) ______________________________
RGB MATRIX FEATHERWING NOTES:
R1 D6 A A5
G1 D5 B A4
B1 D9 C A3
R2 D11 D A2
G2 D10 LAT D0/RX
B2 D12 OE D1/TX
CLK D13
RGB+clock fit in one PORT byte on Feather M4!
RGB+clock are on same PORT but not within same byte on Feather M0 --
the code could run there, but would be super RAM-inefficient. Avoid.
Should be fine on other M0 devices like a Metro, if wiring manually
so one can pick a contiguous byte of PORT bits.
Original RGB Matrix FeatherWing will NOT work on Feather nRF52840
because RGB+clock are on different PORTs. This was resolved by making
a unique version of the FeatherWing that works with that board!
*/

168
examples/tiled/tiled.ino Normal file
View file

@ -0,0 +1,168 @@
/* ----------------------------------------------------------------------
"Tiled" Protomatter library example sketch. Demonstrates use of multiple
RGB LED matrices as a single larger drawing surface. This example is
written for two 64x32 matrices (tiled into a 64x64 display) but can be
adapted to others. If using MatrixPortal, larger multi-panel tilings like
this should be powered from a separate 5V DC supply, not the USB port
(this example works OK because the graphics are very minimal).
PLEASE SEE THE "simple" EXAMPLE FOR AN INTRODUCTORY SKETCH.
------------------------------------------------------------------------- */
#include <Adafruit_Protomatter.h>
/* ----------------------------------------------------------------------
The RGB matrix must be wired to VERY SPECIFIC pins, different for each
microcontroller board. This first section sets that up for a number of
supported boards.
------------------------------------------------------------------------- */
#if defined(_VARIANT_MATRIXPORTAL_M4_) // MatrixPortal M4
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
uint8_t addrPins[] = {17, 18, 19, 20, 21};
uint8_t clockPin = 14;
uint8_t latchPin = 15;
uint8_t oePin = 16;
#elif defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) // MatrixPortal ESP32-S3
uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37};
uint8_t addrPins[] = {45, 36, 48, 35, 21};
uint8_t clockPin = 2;
uint8_t latchPin = 47;
uint8_t oePin = 14;
#elif defined(_VARIANT_FEATHER_M4_) // Feather M4 + RGB Matrix FeatherWing
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2};
uint8_t clockPin = 13;
uint8_t latchPin = 0;
uint8_t oePin = 1;
#elif defined(__SAMD51__) // M4 Metro Variants (Express, AirLift)
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2};
uint8_t clockPin = 13;
uint8_t latchPin = 0;
uint8_t oePin = 1;
#elif defined(_SAMD21_) // Feather M0 variants
uint8_t rgbPins[] = {6, 7, 10, 11, 12, 13};
uint8_t addrPins[] = {0, 1, 2, 3};
uint8_t clockPin = SDA;
uint8_t latchPin = 4;
uint8_t oePin = 5;
#elif defined(NRF52_SERIES) // Special nRF52840 FeatherWing pinout
uint8_t rgbPins[] = {6, A5, A1, A0, A4, 11};
uint8_t addrPins[] = {10, 5, 13, 9};
uint8_t clockPin = 12;
uint8_t latchPin = PIN_SERIAL1_RX;
uint8_t oePin = PIN_SERIAL1_TX;
#elif USB_VID == 0x239A && USB_PID == 0x8113 // Feather ESP32-S3 No PSRAM
// M0/M4/RP2040 Matrix FeatherWing compatible:
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2};
uint8_t clockPin = 13; // Must be on same port as rgbPins
uint8_t latchPin = RX;
uint8_t oePin = TX;
#elif USB_VID == 0x239A && USB_PID == 0x80EB // Feather ESP32-S2
// M0/M4/RP2040 Matrix FeatherWing compatible:
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2};
uint8_t clockPin = 13; // Must be on same port as rgbPins
uint8_t latchPin = RX;
uint8_t oePin = TX;
#elif defined(ESP32)
// 'Safe' pins, not overlapping any peripherals:
// GPIO.out: 4, 12, 13, 14, 15, 21, 27, GPIO.out1: 32, 33
// Peripheral-overlapping pins, sorted from 'most expendible':
// 16, 17 (RX, TX)
// 25, 26 (A0, A1)
// 18, 5, 9 (MOSI, SCK, MISO)
// 22, 23 (SCL, SDA)
uint8_t rgbPins[] = {4, 12, 13, 14, 15, 21};
uint8_t addrPins[] = {16, 17, 25, 26};
uint8_t clockPin = 27; // Must be on same port as rgbPins
uint8_t latchPin = 32;
uint8_t oePin = 33;
#elif defined(ARDUINO_TEENSY40)
uint8_t rgbPins[] = {15, 16, 17, 20, 21, 22}; // A1-A3, A6-A8, skip SDA,SCL
uint8_t addrPins[] = {2, 3, 4, 5};
uint8_t clockPin = 23; // A9
uint8_t latchPin = 6;
uint8_t oePin = 9;
#elif defined(ARDUINO_TEENSY41)
uint8_t rgbPins[] = {26, 27, 38, 20, 21, 22}; // A12-14, A6-A8
uint8_t addrPins[] = {2, 3, 4, 5};
uint8_t clockPin = 23; // A9
uint8_t latchPin = 6;
uint8_t oePin = 9;
#elif defined(ARDUINO_ADAFRUIT_FEATHER_RP2040)
// RP2040 support requires the Earle Philhower board support package;
// will not compile with the Arduino Mbed OS board package.
// The following pinout works with the Adafruit Feather RP2040 and
// original RGB Matrix FeatherWing (M0/M4/RP2040, not nRF version).
// Pin numbers here are GP## numbers, which may be different than
// the pins printed on some boards' top silkscreen.
uint8_t rgbPins[] = {8, 7, 9, 11, 10, 12};
uint8_t addrPins[] = {25, 24, 29, 28};
uint8_t clockPin = 13;
uint8_t latchPin = 1;
uint8_t oePin = 0;
#endif
/* ----------------------------------------------------------------------
Matrix initialization is explained EXTENSIVELY in "simple" example sketch!
It's very similar here, but we're passing an extra argument to define the
matrix tiling along the vertical axis: -2 means there are two matrices
(or rows of matrices) arranged in a "serpentine" path (the second matrix
is rotated 180 degrees relative to the first, and positioned below).
A positive 2 would indicate a "progressive" path (both matrices are
oriented the same way), but usually requires longer cables.
------------------------------------------------------------------------- */
Adafruit_Protomatter matrix(
64, // Width of matrix (or matrices, if tiled horizontally)
6, // Bit depth, 1-6
1, rgbPins, // # of matrix chains, array of 6 RGB pins for each
4, addrPins, // # of address pins (height is inferred), array of pins
clockPin, latchPin, oePin, // Other matrix control pins
false, // No double-buffering here (see "doublebuffer" example)
-2); // Row tiling: two rows in "serpentine" path
// SETUP - RUNS ONCE AT PROGRAM START --------------------------------------
void setup(void) {
Serial.begin(9600);
// Initialize matrix...
ProtomatterStatus status = matrix.begin();
Serial.print("Protomatter begin() status: ");
Serial.println((int)status);
if(status != PROTOMATTER_OK) {
// DO NOT CONTINUE if matrix setup encountered an error.
for(;;);
}
// Since this program has no animation, all the drawing can be done
// here in setup() rather than loop(). It's just a few basic shapes
// that span across the matrices...nothing showy, the goal of this
// sketch is just to demonstrate tiling basics.
matrix.drawLine(0, 0, matrix.width() - 1, matrix.height() - 1,
matrix.color565(255, 0, 0)); // Red line
matrix.drawLine(matrix.width() - 1, 0, 0, matrix.height() - 1,
matrix.color565(0, 0, 255)); // Blue line
int radius = min(matrix.width(), matrix.height()) / 2;
matrix.drawCircle(matrix.width() / 2, matrix.height() / 2, radius,
matrix.color565(0, 255, 0)); // Green circle
// AFTER DRAWING, A show() CALL IS REQUIRED TO UPDATE THE MATRIX!
matrix.show(); // Copy data to matrix buffers
}
// LOOP - RUNS REPEATEDLY AFTER SETUP --------------------------------------
void loop(void) {
// Since there's nothing more to be drawn, this loop() function just
// prints the approximate refresh rate of the matrix at current settings.
Serial.print("Refresh FPS = ~");
Serial.println(matrix.getFrameCount());
delay(1000);
}

View file

@ -1,10 +1,10 @@
name=Adafruit Protomatter
version=1.0.5
version=1.7.0
author=Adafruit
maintainer=Adafruit <info@adafruit.com>
sentence=A library for Adafruit RGB LED matrices.
paragraph=RGB LED matrix.
category=Display
url=https://github.com/adafruit/Adafruit_protomatter
architectures=samd,nrf52,stm32,esp32
architectures=samd,nrf52,stm32,esp32,rp2040
depends=Adafruit GFX Library, Adafruit LIS3DH, Adafruit PixelDust, AnimatedGIF, Adafruit SPIFlash, Adafruit TinyUSB Library

View file

@ -47,9 +47,10 @@ Adafruit_Protomatter::Adafruit_Protomatter(uint16_t bitWidth, uint8_t bitDepth,
uint8_t addrCount, uint8_t *addrList,
uint8_t clockPin, uint8_t latchPin,
uint8_t oePin, bool doubleBuffer,
void *timer)
: GFXcanvas16(bitWidth,
(2 << min((int)addrCount, 5)) * min((int)rgbCount, 5)) {
int8_t tile, void *timer)
: GFXcanvas16(bitWidth, (2 << min((int)addrCount, 5)) *
min((int)rgbCount, 5) *
(tile ? abs(tile) : 1)) {
if (bitDepth > 6)
bitDepth = 6; // GFXcanvas16 color limit (565)
@ -59,7 +60,8 @@ Adafruit_Protomatter::Adafruit_Protomatter(uint16_t bitWidth, uint8_t bitDepth,
// The class begin() function checks rgbPins for NULL to determine
// whether to proceed or indicate an error.
(void)_PM_init(&core, bitWidth, bitDepth, rgbCount, rgbList, addrCount,
addrList, clockPin, latchPin, oePin, doubleBuffer, timer);
addrList, clockPin, latchPin, oePin, doubleBuffer, tile,
timer);
}
Adafruit_Protomatter::~Adafruit_Protomatter(void) {
@ -87,3 +89,53 @@ void Adafruit_Protomatter::show(void) {
uint32_t Adafruit_Protomatter::getFrameCount(void) {
return _PM_getFrameCount(_PM_protoPtr);
}
// This is based on the HSV function in Adafruit_NeoPixel.cpp, but with
// 16-bit RGB565 output for GFX lib rather than 24-bit. See that code for
// an explanation of the math, this is stripped of comments for brevity.
uint16_t Adafruit_Protomatter::colorHSV(uint16_t hue, uint8_t sat,
uint8_t val) {
uint8_t r, g, b;
hue = (hue * 1530L + 32768) / 65536;
if (hue < 510) { // Red to Green-1
b = 0;
if (hue < 255) { // Red to Yellow-1
r = 255;
g = hue; // g = 0 to 254
} else { // Yellow to Green-1
r = 510 - hue; // r = 255 to 1
g = 255;
}
} else if (hue < 1020) { // Green to Blue-1
r = 0;
if (hue < 765) { // Green to Cyan-1
g = 255;
b = hue - 510; // b = 0 to 254
} else { // Cyan to Blue-1
g = 1020 - hue; // g = 255 to 1
b = 255;
}
} else if (hue < 1530) { // Blue to Red-1
g = 0;
if (hue < 1275) { // Blue to Magenta-1
r = hue - 1020; // r = 0 to 254
b = 255;
} else { // Magenta to Red-1
r = 255;
b = 1530 - hue; // b = 255 to 1
}
} else { // Last 0.5 Red (quicker than % operator)
r = 255;
g = b = 0;
}
// Apply saturation and value to R,G,B, pack into 16-bit 'RGB565' result:
uint32_t v1 = 1 + val; // 1 to 256; allows >>8 instead of /255
uint16_t s1 = 1 + sat; // 1 to 256; same reason
uint8_t s2 = 255 - sat; // 255 to 0
return (((((r * s1) >> 8) + s2) * v1) & 0xF800) |
((((((g * s1) >> 8) + s2) * v1) & 0xFC00) >> 5) |
(((((b * s1) >> 8) + s2) * v1) >> 11);
}

View file

@ -54,6 +54,14 @@ public:
@param doubleBuffer If true, two matrix buffers are allocated,
so changing display contents doesn't introduce
artifacts mid-conversion. Requires ~2X RAM.
@param tile If multiple matrices are chained and stacked
vertically (rather than or in addition to
horizontally), the number of vertical tiles is
specified here. Positive values indicate a
"progressive" arrangement (always left-to-right),
negative for a "serpentine" arrangement (alternating
180 degree orientation). Horizontal tiles are implied
in the 'bitWidth' argument.
@param timer Pointer to timer peripheral or timer-related
struct (architecture-dependent), or NULL to
use a default timer ID (also arch-dependent).
@ -61,7 +69,7 @@ public:
Adafruit_Protomatter(uint16_t bitWidth, uint8_t bitDepth, uint8_t rgbCount,
uint8_t *rgbList, uint8_t addrCount, uint8_t *addrList,
uint8_t clockPin, uint8_t latchPin, uint8_t oePin,
bool doubleBuffer, void *timer = NULL);
bool doubleBuffer, int8_t tile = 1, void *timer = NULL);
~Adafruit_Protomatter(void);
/*!
@ -84,6 +92,16 @@ public:
*/
void show(void);
/*!
@brief Disable (but do not deallocate) a Protomatter matrix.
*/
void stop(void) { _PM_stop(&core); }
/*!
@brief Resume a previously-stopped matrix.
*/
void resume(void) { _PM_resume(&core); }
/*!
@brief Returns current value of frame counter and resets its value
to zero. Two calls to this, timed one second apart (or use
@ -94,6 +112,48 @@ public:
*/
uint32_t getFrameCount(void);
/*!
@brief Converts 24-bit color (8 bits red, green, blue) used in a lot
a lot of existing graphics code down to the "565" color format
used by Adafruit_GFX. Might get further quantized by matrix if
using less than 6-bit depth.
@param red Red brightness, 0 (min) to 255 (max).
@param green Green brightness, 0 (min) to 255 (max).
@param blue Blue brightness, 0 (min) to 255 (max).
@return Packed 16-bit (uint16_t) color value suitable for GFX drawing
functions.
*/
uint16_t color565(uint8_t red, uint8_t green, uint8_t blue) {
return ((red & 0xF8) << 8) | ((green & 0xFC) << 3) | (blue >> 3);
}
/*!
@brief Convert hue, saturation and value into a packed 16-bit RGB color
that can be passed to GFX drawing functions.
@param hue An unsigned 16-bit value, 0 to 65535, representing one full
loop of the color wheel, which allows 16-bit hues to "roll
over" while still doing the expected thing (and allowing
more precision than the wheel() function that was common to
older graphics examples).
@param sat Saturation, 8-bit value, 0 (min or pure grayscale) to 255
(max or pure hue). Default of 255 if unspecified.
@param val Value (brightness), 8-bit value, 0 (min / black / off) to
255 (max or full brightness). Default of 255 if unspecified.
@return Packed 16-bit '565' RGB color. Result is linearly but not
perceptually correct (no gamma correction).
*/
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:
Protomatter_core core; // Underlying C struct
void convert_byte(uint8_t *dest); // GFXcanvas16-to-matrix

View file

@ -82,11 +82,13 @@ Timer-related macros/functions:
_PM_timerFreq: A numerical constant - the source clock rate
(in Hz) that's fed to the timer peripheral.
_PM_timerInit(void*): Initialize (but do not start) timer.
_PM_timerStart(void*,count): (Re)start timer for a given timer-tick interval.
_PM_timerStop(void*): Stop timer, return current timer counter value.
_PM_timerGetCount(void*): Get current timer counter value (whether timer
is running or stopped).
_PM_timerInit(Protomatter_core*): Initialize (but do not start) timer.
_PM_timerStart(Protomatter_core*,count): (Re)start timer for a given
timer-tick interval.
_PM_timerStop(Protomatter_core*): Stop timer, return current timer
counter value.
_PM_timerGetCount(Protomatter_core*): Get current timer counter value
(whether timer is running or stopped).
A timer interrupt service routine is also required, syntax for which varies
between architectures.
The void* argument passed to the timer functions is some indeterminate type
@ -135,6 +137,26 @@ _PM_allocate: Memory allocation function, should return a
If not defined, malloc() is used.
_PM_free: Corresponding deallocator for _PM_allocate().
If not defined, free() is used.
_PM_bytesPerElement If defined, this allows an arch-specific source
file to override core's data size that's based
on pin selections. Reasonable values would be 1,
2 or 4. This came about during ESP32-S2
development; GPIO or I2S/LCD peripherals there
allows super flexible pin MUXing, so one byte
could be used even w/pins spread all over.
_PM_USE_TOGGLE_FORMAT If defined, this instructs the core code to
format pixel data for GPIO bit-toggling, even
if _PM_portToggleRegister is not defined.
_PM_CUSTOM_BLAST If defined, instructs core code to not compile
the blast_byte(), blast_word() or blast_long()
functions; these will be declared in the arch-
specific file instead. This might benefit
architectures, where DMA, PIO or other
specialized peripherals could be set up to
issue data independent of the CPU. This goes
against's Protomatter's normal design of using
the most baseline peripherals for a given
architecture, but time marches on, y'know?
*/
// ENVIRONMENT-SPECIFIC DECLARATIONS ---------------------------------------
@ -148,7 +170,6 @@ _PM_free: Corresponding deallocator for _PM_allocate().
#define _PM_pinInput(pin) pinMode(pin, INPUT)
#define _PM_pinHigh(pin) digitalWrite(pin, HIGH)
#define _PM_pinLow(pin) digitalWrite(pin, LOW)
#define _PM_portBitMask(pin) digitalPinToBitMask(pin)
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
@ -165,16 +186,32 @@ _PM_free: Corresponding deallocator for _PM_allocate().
// ARCHITECTURE-SPECIFIC HEADERS -------------------------------------------
#include "esp32.h"
// clang-format off
#include "esp32-common.h"
#include "esp32.h" // Original ESP32
#include "esp32-s2.h"
#include "esp32-s3.h"
#include "esp32-c3.h"
#include "esp32-c6.h"
#include "nrf52.h"
#include "rp2040.h"
#include "samd-common.h"
#include "samd21.h"
#include "samd51.h"
#include "stm32.h"
#include "teensy4.h"
// clang-format on
// DEFAULTS IF NOT DEFINED ABOVE -------------------------------------------
#if defined(_PM_portToggleRegister)
#define _PM_USE_TOGGLE_FORMAT
#endif
#if !defined(_PM_portBitMask)
#define _PM_portBitMask(pin) digitalPinToBitMask(pin)
#endif
#if !defined(_PM_chunkSize)
#define _PM_chunkSize 8 ///< Unroll data-stuffing loop to this size
#endif
@ -206,3 +243,11 @@ _PM_free: Corresponding deallocator for _PM_allocate().
#if !defined(_PM_PORT_TYPE)
#define _PM_PORT_TYPE uint32_t ///< PORT register size/type
#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

57
src/arch/esp32-c3.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_ESP32C3)
#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

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

247
src/arch/esp32-common.h Normal file
View file

@ -0,0 +1,247 @@
/*!
* @file esp32-common.h
*
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
* This file contains ESP32-SPECIFIC CODE (common to all ESP variants).
*
* 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
#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
// 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!
#include "soc/gpio_periph.h"
// 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:
Protomatter_core *_PM_protoPtr;
#define _PM_timerFreq 40000000 // 40 MHz (1:2 prescale)
#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)
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 Arduino platform. Anything unique to one variant (or a subset of
// variants) is declared in the corresponding esp32-*.h header(s); please
// no #if defined(CONFIG_IDF_TARGET_*) action here...if you find yourself
// started down that path, it's okay, but move the code out of here and
// into the variant-specific headers.
extern void _PM_row_handler(Protomatter_core *core); // In core.c
// Timer interrupt handler. This, _PM_row_handler() and any functions
// called by _PM_row_handler() should all have the IRAM_ATTR attribute
// (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)
// which takes care of interrupt status bits & such.
IRAM_ATTR static void _PM_esp32timerCallback(void) {
_PM_row_handler(_PM_protoPtr); // In core.c
}
// Set timer period, initialize count value to zero, enable timer.
IRAM_ATTR inline void _PM_timerStart(Protomatter_core *core, uint32_t period) {
hw_timer_t *timer = (hw_timer_t *)core->timer;
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
timerAlarmWrite(timer, period, true);
timerAlarmEnable(timer);
timerStart(timer);
#else
timerWrite(timer, 0);
timerAlarm(timer, period ? period : 1, true, 0);
timerStart(timer);
#endif
}
// Disable timer and return current count value.
// Timer must be previously initialized.
IRAM_ATTR uint32_t _PM_timerStop(Protomatter_core *core) {
timerStop((hw_timer_t *)core->timer);
return _PM_timerGetCount(core);
}
// Initialize, but do not start, timer. This function contains timer setup
// that's common to all ESP32 variants; code in variant-specific files might
// set up its own special peripherals, then call this.
void _PM_esp32commonTimerInit(Protomatter_core *core) {
hw_timer_t *timer_in = (hw_timer_t *)core->timer;
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
#else
core->timer = timerBegin(_PM_timerFreq);
#endif
}
#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 --------------------
// The following defines and functions are common to all ESP32 variants in
// the CircuitPython platform. Anything unique to one variant (or a subset
// of variants) is declared in the corresponding esp32-*.h header(s);
// please no #if defined(CONFIG_IDF_TARGET_*) action here...if you find
// yourself started down that path, it's okay, but move the code out of
// here and into the variant-specific headers.
#include "driver/gpio.h"
#include "esp_idf_version.h"
#include "hal/timer_ll.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_pinOutput(pin) gpio_set_direction((pin), GPIO_MODE_OUTPUT)
#define _PM_pinLow(pin) gpio_set_level((pin), false)
#define _PM_pinHigh(pin) gpio_set_level((pin), true)
// Timer interrupt handler. This, _PM_row_handler() and any functions
// called by _PM_row_handler() should all have the IRAM_ATTR attribute
// (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)
// which takes care of interrupt status bits & such.
#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) {
#endif
_PM_row_handler(_PM_protoPtr); // In core.c
}
return false;
};
// Set timer period, initialize count value to zero, enable timer.
#if (ESP_IDF_VERSION_MAJOR == 5)
IRAM_ATTR void _PM_timerStart(Protomatter_core *core, uint32_t period) {
gptimer_handle_t timer = (gptimer_handle_t)core->timer;
gptimer_alarm_config_t alarm_config = {
.reload_count = 0, // counter will reload with 0 on alarm event
.alarm_count = period, // period in ms
.flags.auto_reload_on_alarm = true, // enable auto-reload
};
gptimer_set_alarm_action(timer, &alarm_config);
gptimer_start(timer);
}
#else
IRAM_ATTR void _PM_timerStart(Protomatter_core *core, uint32_t period) {
timer_index_t *timer = (timer_index_t *)core->timer;
timer_ll_set_counter_enable(timer->hw, timer->idx, false);
timer_ll_set_counter_value(timer->hw, timer->idx, 0);
timer_ll_set_alarm_value(timer->hw, timer->idx, period);
timer_ll_set_alarm_enable(timer->hw, timer->idx, true);
timer_ll_set_counter_enable(timer->hw, timer->idx, true);
}
#endif
// Disable timer and return current count value.
// Timer must be previously initialized.
IRAM_ATTR uint32_t _PM_timerStop(Protomatter_core *core) {
#if (ESP_IDF_VERSION_MAJOR == 5)
gptimer_handle_t timer = (gptimer_handle_t)core->timer;
gptimer_stop(timer);
#else
timer_index_t *timer = (timer_index_t *)core->timer;
timer_ll_set_counter_enable(timer->hw, timer->idx, false);
#endif
return _PM_timerGetCount(core);
}
#if !defined(CONFIG_IDF_TARGET_ESP32S3)
IRAM_ATTR uint32_t _PM_timerGetCount(Protomatter_core *core) {
#if (ESP_IDF_VERSION_MAJOR == 5)
gptimer_handle_t timer = (gptimer_handle_t)core->timer;
uint64_t raw_count;
gptimer_get_raw_count(timer, &raw_count);
return (uint32_t)raw_count;
#else
timer_index_t *timer = (timer_index_t *)core->timer;
uint64_t result;
timer_ll_get_counter_value(timer->hw, timer->idx, &result);
return (uint32_t)result;
#endif
}
#endif
// Initialize, but do not start, timer. This function contains timer setup
// that's common to all ESP32 variants; code in variant-specific files might
// set up its own special peripherals, then call this.
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;
const timer_config_t config = {
.alarm_en = false,
.counter_en = false,
.intr_type = TIMER_INTR_LEVEL,
.counter_dir = TIMER_COUNT_UP,
.auto_reload = true,
.divider = 2 // 40MHz
};
timer_init(timer->group, timer->idx, &config);
timer_isr_callback_add(timer->group, timer->idx, _PM_esp32timerCallback, NULL,
0);
timer_enable_intr(timer->group, timer->idx);
#endif
}
#endif // END CIRCUITPYTHON ------------------------------------------------
#endif // END ESP32 (all variants)

213
src/arch/esp32-s2.h Normal file
View file

@ -0,0 +1,213 @@
/*!
* @file esp32-s2.h
*
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
* This file contains ESP32-S2-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_ESP32S2)
#define _PM_portOutRegister(pin) \
(volatile uint32_t *)((pin < 32) ? &GPIO.out : &GPIO.out1.val)
#define _PM_portSetRegister(pin) \
(volatile uint32_t *)((pin < 32) ? &GPIO.out_w1ts : &GPIO.out1_w1ts.val)
#define _PM_portClearRegister(pin) \
(volatile uint32_t *)((pin < 32) ? &GPIO.out_w1tc : &GPIO.out1_w1tc.val)
// On ESP32-S2, use the Dedicated GPIO peripheral, which allows faster bit-
// toggling than the conventional GPIO registers. Unfortunately NOT present
// on S3 or other ESP32 devices. Normal GPIO has a bottleneck where toggling
// a pin is limited to 8 MHz max. Dedicated GPIO can work around this and
// get over twice this rate, but requires some very weird hoops!
// Dedicated GPIO only supports 8-bit output, so parallel output isn't
// supported, but positives include that there's very flexible pin MUXing,
// so matrix data in RAM can ALWAYS be stored in byte format regardless how
// the RGB+clock bits are distributed among pins.
#define _PM_bytesPerElement 1
#define _PM_byteOffset(pin) 0
#define _PM_wordOffset(pin) 0
// Odd thing with Dedicated GPIO is that the bit-toggle operation is faster
// than bit set or clear (perhaps some underlying operation is atomic rather
// than read-modify-write). So, instruct core.c to format the matrix data in
// RAM as if we're using a port toggle register, even though
// _PM_portToggleRegister is NOT defined because ESP32 doesn't have that in
// conventional GPIO. Good times.
#define _PM_USE_TOGGLE_FORMAT
// This table is used to remap 7-bit (RGB+RGB+clock) data to the 2-bits-per
// GPIO format used by Dedicated GPIO. Bits corresponding to clock output
// are always set here, as we want that bit toggled low at the same time new
// RGB data is set up.
static uint16_t _bit_toggle[128] = {
0x3000, 0x3003, 0x300C, 0x300F, 0x3030, 0x3033, 0x303C, 0x303F, 0x30C0,
0x30C3, 0x30CC, 0x30CF, 0x30F0, 0x30F3, 0x30FC, 0x30FF, 0x3300, 0x3303,
0x330C, 0x330F, 0x3330, 0x3333, 0x333C, 0x333F, 0x33C0, 0x33C3, 0x33CC,
0x33CF, 0x33F0, 0x33F3, 0x33FC, 0x33FF, 0x3C00, 0x3C03, 0x3C0C, 0x3C0F,
0x3C30, 0x3C33, 0x3C3C, 0x3C3F, 0x3CC0, 0x3CC3, 0x3CCC, 0x3CCF, 0x3CF0,
0x3CF3, 0x3CFC, 0x3CFF, 0x3F00, 0x3F03, 0x3F0C, 0x3F0F, 0x3F30, 0x3F33,
0x3F3C, 0x3F3F, 0x3FC0, 0x3FC3, 0x3FCC, 0x3FCF, 0x3FF0, 0x3FF3, 0x3FFC,
0x3FFF, 0x3000, 0x3003, 0x300C, 0x300F, 0x3030, 0x3033, 0x303C, 0x303F,
0x30C0, 0x30C3, 0x30CC, 0x30CF, 0x30F0, 0x30F3, 0x30FC, 0x30FF, 0x3300,
0x3303, 0x330C, 0x330F, 0x3330, 0x3333, 0x333C, 0x333F, 0x33C0, 0x33C3,
0x33CC, 0x33CF, 0x33F0, 0x33F3, 0x33FC, 0x33FF, 0x3C00, 0x3C03, 0x3C0C,
0x3C0F, 0x3C30, 0x3C33, 0x3C3C, 0x3C3F, 0x3CC0, 0x3CC3, 0x3CCC, 0x3CCF,
0x3CF0, 0x3CF3, 0x3CFC, 0x3CFF, 0x3F00, 0x3F03, 0x3F0C, 0x3F0F, 0x3F30,
0x3F33, 0x3F3C, 0x3F3F, 0x3FC0, 0x3FC3, 0x3FCC, 0x3FCF, 0x3FF0, 0x3FF3,
0x3FFC, 0x3FFF,
};
#include <driver/dedic_gpio.h>
#include <soc/dedic_gpio_reg.h>
#include <soc/dedic_gpio_struct.h>
// Override the behavior of _PM_portBitMask macro so instead of returning
// a 32-bit mask for a pin within its corresponding GPIO register, it instead
// returns a 7-bit mask for the pin within the Direct GPIO register *IF* it's
// one of the RGB bits or the clock bit...this requires comparing against pin
// numbers in the core struct.
static uint32_t _PM_directBitMask(Protomatter_core *core, int pin) {
if (pin == core->clockPin)
return 1 << 6;
for (uint8_t i = 0; i < 6; i++) {
if (pin == core->rgbPins[i])
return 1 << i;
}
// Else return the bit that would normally be used for regular GPIO
return (1U << (pin & 31));
}
// Thankfully, at present, any core code which calls _PM_portBitMask()
// currently has a 'core' variable, so we can safely do this...
#define _PM_portBitMask(pin) _PM_directBitMask(core, pin)
// Dedicated GPIO requires a complete replacement of the "blast" functions
// in order to get sufficient speed.
#define _PM_CUSTOM_BLAST // Disable blast_*() functions in core.c
IRAM_ATTR static void blast_byte(Protomatter_core *core, uint8_t *data) {
volatile uint32_t *gpio = &DEDIC_GPIO.gpio_out_idv.val;
// GPIO has already been initialized with RGB data + clock bits
// all LOW, so we don't need to initialize that state here.
for (uint32_t bits = core->chainBits / 8; bits--;) {
*gpio = _bit_toggle[*data++]; // Toggle in new data + toggle clock low
*gpio = 0b11000000000000; // Toggle clock high
*gpio = _bit_toggle[*data++];
*gpio = 0b11000000000000;
*gpio = _bit_toggle[*data++];
*gpio = 0b11000000000000;
*gpio = _bit_toggle[*data++];
*gpio = 0b11000000000000;
*gpio = _bit_toggle[*data++];
*gpio = 0b11000000000000;
*gpio = _bit_toggle[*data++];
*gpio = 0b11000000000000;
*gpio = _bit_toggle[*data++];
*gpio = 0b11000000000000;
*gpio = _bit_toggle[*data++];
*gpio = 0b11000000000000;
}
// Want the pins 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.
*gpio = 0b10101010101010; // Clear RGB + clock bits
}
// 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) {}
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
void _PM_timerInit(Protomatter_core *core) {
// On S2, initialize the Dedicated GPIO peripheral using the RGB pin list
// list from the core struct, plus the clock pin (7 pins total). Unsure if
// these structs & arrays need to be persistent. Declaring static just in
// case...could experiment with removing one by one.
static int pins[7];
for (uint8_t i = 0; i < 6; i++)
pins[i] = core->rgbPins[i];
pins[6] = core->clockPin;
static dedic_gpio_bundle_config_t config_in = {
.gpio_array = pins, // Array of GPIO numbers
.array_size = 7, // RGB pins + clock pin
.flags = {
.in_en = 0, // Disable input
.out_en = 1, // Enable output
.out_invert = 0, // Non-inverted
}};
static dedic_gpio_bundle_handle_t bundle;
(void)dedic_gpio_new_bundle(&config_in, &bundle);
dedic_gpio_bundle_write(bundle, config_in.array_size, 1);
DEDIC_GPIO.gpio_out_cpu.val = 0; // Use GPIO registers, not CPU instructions
_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 --------------------
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.
// On S2, initialize the Dedicated GPIO peripheral using the RGB pin list
// list from the core struct, plus the clock pin (7 pins total). Unsure if
// these structs & arrays need to be persistent. Declaring static just in
// case...could experiment with removing one by one.
static int pins[7];
for (uint8_t i = 0; i < 6; i++)
pins[i] = core->rgbPins[i];
pins[6] = core->clockPin;
static dedic_gpio_bundle_config_t config_in = {
.gpio_array = pins, // Array of GPIO numbers
.array_size = 7, // RGB pins + clock pin
.flags = {
.in_en = 0, // Disable input
.out_en = 1, // Enable output
.out_invert = 0, // Non-inverted
}};
static dedic_gpio_bundle_handle_t bundle;
(void)dedic_gpio_new_bundle(&config_in, &bundle);
dedic_gpio_bundle_write(bundle, config_in.array_size, 1);
DEDIC_GPIO.gpio_out_cpu.val = 0; // Use GPIO registers, not CPU instructions
_PM_esp32commonTimerInit(core); // In esp32-common.h
}
#endif // END CIRCUITPYTHON ------------------------------------------------
#endif // END ESP32S2

285
src/arch/esp32-s3.h Normal file
View file

@ -0,0 +1,285 @@
/*!
* @file esp32-s3.h
*
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
* This file contains ESP32-S3-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_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:
#define _PM_allocate(x) heap_caps_malloc(x, MALLOC_CAP_DMA | MALLOC_CAP_8BIT)
#define _PM_free(x) heap_caps_free(x)
#define _PM_portOutRegister(pin) \
(volatile uint32_t *)((pin < 32) ? &GPIO.out : &GPIO.out1.val)
#define _PM_portSetRegister(pin) \
(volatile uint32_t *)((pin < 32) ? &GPIO.out_w1ts : &GPIO.out1_w1ts.val)
#define _PM_portClearRegister(pin) \
(volatile uint32_t *)((pin < 32) ? &GPIO.out_w1tc : &GPIO.out1_w1tc.val)
// On ESP32-S3, use the LCD_CAM peripheral for fast parallel output.
// Thanks to ESP's pin MUXing, matrix data in RAM can ALWAYS be stored in
// byte format regardless how RGB+clock bits are distributed among pins.
#define _PM_bytesPerElement 1
#define _PM_byteOffset(pin) 0
#define _PM_wordOffset(pin) 0
// On ESP32-S3, use the LCD_CAM peripheral for fast parallel output.
// Thanks to ESP's pin MUXing, matrix data in RAM can ALWAYS be stored in
// byte format regardless how RGB+clock bits are distributed among pins.
#define _PM_bytesPerElement 1
#define _PM_byteOffset(pin) 0
#define _PM_wordOffset(pin) 0
// On most architectures, _PM_timerGetCount() is used to measure bitbang
// speed for one scanline, which is then used for bitplane 0 time, and each
// subsequent plane doubles that. Since ESP32-S3 uses DMA and we don't have
// an end-of-transfer interrupt, we make an informed approximation.
// dmaSetupTime (measured in blast_byte()) measures the number of timer
// cycles to set up and trigger the DMA transfer...
static uint32_t dmaSetupTime = 100;
// ...then, the version of _PM_timerGetCount() here uses that as a starting
// point, plus the known constant DMA xfer speed (160/LCD_CLK_PRESCALE MHz)
// and timer frequency (40 MHz), to return an estimate of the one-scanline
// transfer time, from which everything is extrapolated:
IRAM_ATTR inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
// 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
// refresh in some edge cases. If visual glitches are encountered, might
// need to dial back this number a bit or remove it.
return dmaSetupTime + core->chainBits * 40 * LCD_CLK_PRESCALE / 160 - 10;
}
// Note that dmaSetupTime can vary from line to line, potentially influenced
// by interrupts, nondeterministic DMA channel clearing times, etc., which is
// why we don't just use a constant value. Each scanline might show for a
// slightly different length of time, but duty cycle scales with this so it's
// perceptually consistent; don't see bright or dark rows.
#define _PM_minMinPeriod \
(200 + (uint32_t)core->chainBits * 40 * LCD_CLK_PRESCALE / 160)
#if (ESP_IDF_VERSION_MAJOR == 5)
#include <esp_private/periph_ctrl.h>
#else
#include <driver/periph_ctrl.h>
#endif
#include <driver/gpio.h>
#include <esp_private/gdma.h>
#include <esp_rom_gpio.h>
#include <hal/dma_types.h>
#include <hal/gpio_hal.h>
#include <hal/lcd_ll.h>
#include <soc/lcd_cam_reg.h>
#include <soc/lcd_cam_struct.h>
// Override the behavior of _PM_portBitMask macro so instead of returning
// a 32-bit mask for a pin within its corresponding GPIO register, it instead
// returns a 7-bit mask for the pin within the LCD_CAM data order *IF* it's
// one of the RGB bits or the clock bit...this requires comparing against pin
// numbers in the core struct.
static uint32_t _PM_directBitMask(Protomatter_core *core, int pin) {
if (pin == core->clockPin)
return 1 << 6;
for (uint8_t i = 0; i < 6; i++) {
if (pin == core->rgbPins[i])
return 1 << i;
}
// Else return the bit that would normally be used for regular GPIO
return (1U << (pin & 31));
}
// Thankfully, at present, any core code which calls _PM_portBitMask()
// currently has a 'core' variable, so we can safely do this...
#define _PM_portBitMask(pin) _PM_directBitMask(core, pin)
static dma_descriptor_t desc;
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
// to use the DMA-based peripheral.
#define _PM_CUSTOM_BLAST // Disable blast_*() functions in core.c
IRAM_ATTR static void blast_byte(Protomatter_core *core, uint8_t *data) {
// Reset LCD DOUT parameters each time (required).
// IN PRINCIPLE, cyclelen should be chainBits-1 (resulting in chainBits
// cycles). But due to the required dummy phases at start of transfer,
// extend by 1; set to chainBits, issue chainBits+1 cycles.
LCD_CAM.lcd_user.lcd_dout_cyclelen = core->chainBits;
LCD_CAM.lcd_user.lcd_dout = 1;
LCD_CAM.lcd_user.lcd_update = 1;
// Reset LCD TX FIFO each time, else we see old data. When doing this,
// it's REQUIRED in the setup code to enable at least one dummy pulse,
// else the PCLK & data are randomly misaligned by 1-2 clocks!
LCD_CAM.lcd_misc.lcd_afifo_reset = 1;
// Partially re-init descriptor each time (required)
desc.dw0.size = desc.dw0.length = core->chainBits;
desc.buffer = data;
gdma_start(dma_chan, (intptr_t)&desc);
esp_rom_delay_us(1); // Necessary before starting xfer
LCD_CAM.lcd_user.lcd_start = 1; // Begin LCD DMA xfer
// Timer was cleared to 0 before calling blast_byte(), so this
// is the state of the timer immediately after DMA started:
#if defined(ARDUINO)
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.
}
static void _PM_timerInit(Protomatter_core *core) {
// 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 prescale below yields...
#if LCD_CLK_PRESCALE == 8
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_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_STRENGTH);
gpio_set_drive_capability(core->oe.pin, GPIO_DRIVE_STRENGTH);
for (uint8_t i = 0; i < core->numAddressLines; i++) {
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
#if defined(CIRCUITPY)
if (dma_chan == NULL) {
#endif
gdma_channel_alloc_config_t dma_chan_config = {
.sibling_chan = NULL,
.direction = GDMA_CHANNEL_DIRECTION_TX,
.flags = {.reserve_sibling = 0}};
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);
#if defined(CIRCUITPY)
}
#endif
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
}
#endif // END ESP32S3

View file

@ -2,7 +2,7 @@
* @file esp32.h
*
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
* This file contains ESP32-SPECIFIC CODE.
* This file contains ORIGINAL-ESP32-SPECIFIC CODE.
*
* Adafruit invests time and resources providing this open source code,
* please support Adafruit and open-source hardware by purchasing
@ -17,21 +17,23 @@
#pragma once
#if defined(ESP32)
// 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(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
#include "driver/timer.h"
#if defined(CONFIG_IDF_TARGET_ESP32) // ORIGINAL ESP32, NOT S2/S3/etc.
#define _PM_portOutRegister(pin) \
(volatile uint32_t *)((pin < 32) ? &GPIO.out : &GPIO.out1.val)
#define _PM_portSetRegister(pin) \
(volatile uint32_t *)((pin < 32) ? &GPIO.out_w1ts : &GPIO.out1_w1ts.val)
#define _PM_portClearRegister(pin) \
(volatile uint32_t *)((pin < 32) ? &GPIO.out_w1tc : &GPIO.out1_w1tc.val)
#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)
@ -40,83 +42,49 @@
#define _PM_wordOffset(pin) (1 - ((pin & 31) / 16))
#endif
// No special peripheral setup on OG ESP32, just use common timer init...
#define _PM_timerInit(core) _PM_esp32commonTimerInit(core);
// 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 ------------------------------
// ESP32 requires a custom PEW declaration (issues one set of RGB color bits
// followed by clock pulse). Turns out the bit set/clear registers are not
// actually atomic. If two writes are made in quick succession, the second
// has no effect. One option is NOPs, other is to write a 0 (no effect) to
// the opposing register (set vs clear) to synchronize the next write.
// S2, S3 replace the whole "blast" functions and don't use PEW. C3 can use
// the default PEW.
#if !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32S2)
#define PEW \
*set = *data++; /* Set RGB data high */ \
*clear_full = 0; /* ESP32 MUST sync before 2nd 'set' */ \
*set_full = clock; /* Set clock high */ \
*clear_full = rgbclock; /* Clear RGB data + clock */ \
///< Bitbang one set of RGB data bits to matrix
// As written, because it's tied to a specific timer right now, the
// Arduino lib only permits one instance of the Protomatter_core struct,
// which it sets up when calling begin().
void *_PM_protoPtr = NULL;
#define _PM_timerFreq 40000000 // 40 MHz (1:2 prescale)
#define _PM_timerNum 0 // Timer #0 (can be 0-3)
// This is the default aforementioned singular timer. IN THEORY, other
// 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);
// Timer interrupt handler. This, _PM_row_handler() and any functions
// called by _PM_row_handler() should all have the IRAM_ATTR attribute
// (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)
// which takes care of interrupt status bits & such.
IRAM_ATTR static void _PM_esp32timerCallback(void) {
_PM_row_handler(_PM_protoPtr); // In core.c
}
// Initialize, but do not start, timer.
void _PM_timerInit(void *tptr) {
hw_timer_t **timer = (hw_timer_t **)tptr; // pointer-to-pointer
if (timer == _PM_TIMER_DEFAULT) {
*timer = timerBegin(_PM_timerNum, 2, true); // 1:2 prescale, count up
}
timerAttachInterrupt(*timer, &_PM_esp32timerCallback, true);
}
// Set timer period, initialize count value to zero, enable timer.
IRAM_ATTR inline void _PM_timerStart(void *tptr, uint32_t period) {
hw_timer_t *timer = *(hw_timer_t **)tptr;
timerAlarmWrite(timer, period, true);
timerAlarmEnable(timer);
timerStart(timer);
}
// Return current count value (timer enabled or not).
// Timer must be previously initialized.
IRAM_ATTR inline uint32_t _PM_timerGetCount(void *tptr) {
hw_timer_t *timer = *(hw_timer_t **)tptr;
return (uint32_t)timerRead(timer);
}
// Disable timer and return current count value.
// Timer must be previously initialized.
IRAM_ATTR uint32_t _PM_timerStop(void *tptr) {
hw_timer_t *timer = *(hw_timer_t **)tptr;
timerStop(timer);
return _PM_timerGetCount(tptr);
}
#endif // end !ESP32S3/S2
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
// ESP32 CircuitPython magic goes here. If any of the above Arduino-specific
// defines, structs or functions are useful as-is, don't copy them, just
// move them above the ARDUINO check so fixes/changes carry over, thx.
#define _PM_STRICT_32BIT_IO (1)
// ESP32 requires a custom PEW declaration (issues one set of RGB color bits
// followed by clock pulse). Turns out the bit set/clear registers are not
// actually atomic. If two writes are made in quick succession, the second
// has no effect. One option is NOPs, other is to write a 0 (no effect) to
// the opposing register (set vs clear) to synchronize the next write.
#define PEW \
*set = (*data++) << shift; /* Set RGB data high */ \
*clear_full = 0; /* ESP32 MUST sync before 2nd 'set' */ \
*set = clock; /* Set clock high */ \
*clear_full = rgbclock; /* Clear RGB data + clock */ \
///< Bitbang one set of RGB data bits to matrix
#endif // END CIRCUITPYTHON ------------------------------------------------

View file

@ -99,7 +99,7 @@ volatile uint32_t *_PM_portClearRegister(uint32_t pin) {
#define _PM_pinInput(pin) nrf_gpio_cfg_input(pin)
#define _PM_pinHigh(pin) nrf_gpio_pin_set(pin)
#define _PM_pinLow(pin) nrf_gpio_pin_clear(pin)
#define _PM_portBitMask(pin) (1u << ((pin)&31))
#define _PM_portBitMask(pin) (1u << ((pin) & 31))
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define _PM_byteOffset(pin) ((pin & 31) / 8)
@ -136,32 +136,33 @@ void _PM_IRQ_HANDLER(void) {
// CODE COMMON TO ALL ENVIRONMENTS -----------------------------------------
void _PM_timerInit(void *tptr) {
void _PM_timerInit(Protomatter_core *core) {
static const struct {
NRF_TIMER_Type *tc; // -> Timer peripheral base address
IRQn_Type IRQn; // Interrupt number
} timer[] = {
#if defined(NRF_TIMER0)
{NRF_TIMER0, TIMER0_IRQn},
{NRF_TIMER0, TIMER0_IRQn},
#endif
#if defined(NRF_TIMER1)
{NRF_TIMER1, TIMER1_IRQn},
{NRF_TIMER1, TIMER1_IRQn},
#endif
#if defined(NRF_TIMER2)
{NRF_TIMER2, TIMER2_IRQn},
{NRF_TIMER2, TIMER2_IRQn},
#endif
#if defined(NRF_TIMER3)
{NRF_TIMER3, TIMER3_IRQn},
{NRF_TIMER3, TIMER3_IRQn},
#endif
#if defined(NRF_TIMER4)
{NRF_TIMER4, TIMER4_IRQn},
{NRF_TIMER4, TIMER4_IRQn},
#endif
};
#define NUM_TIMERS (sizeof timer / sizeof timer[0])
// Determine IRQn from timer address
uint8_t timerNum = 0;
while ((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tptr)) {
while ((timerNum < NUM_TIMERS) &&
(timer[timerNum].tc != (NRF_TIMER_Type *)core->timer)) {
timerNum++;
}
if (timerNum >= NUM_TIMERS)
@ -183,30 +184,25 @@ void _PM_timerInit(void *tptr) {
NVIC_EnableIRQ(timer[timerNum].IRQn);
}
inline void _PM_timerStart(void *tptr, uint32_t period) {
volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)tptr;
inline void _PM_timerStart(Protomatter_core *core, uint32_t period) {
volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)core->timer;
tc->TASKS_STOP = 1; // Stop timer
tc->TASKS_CLEAR = 1; // Reset to 0
tc->CC[0] = period;
tc->TASKS_START = 1; // Start timer
}
inline uint32_t _PM_timerGetCount(void *tptr) {
volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)tptr;
tc->TASKS_CAPTURE[0] = 1; // Capture timer to CC[n] register
return tc->CC[0];
inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)core->timer;
tc->TASKS_CAPTURE[1] = 1; // Capture timer to CC[1] register
return tc->CC[1]; // (don't clobber value in CC[0])
}
uint32_t _PM_timerStop(void *tptr) {
volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)tptr;
uint32_t _PM_timerStop(Protomatter_core *core) {
volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)core->timer;
tc->TASKS_STOP = 1; // Stop timer
__attribute__((unused)) uint32_t count = _PM_timerGetCount(tptr);
// NOTE TO FUTURE SELF: I don't know why the GetCount code isn't
// working. It does the expected thing in a small test program but
// not here. I need to get on with testing on an actual matrix, so
// this is just a nonsense fudge value for now:
return 100;
// return count;
__attribute__((unused)) uint32_t count = _PM_timerGetCount(core);
return count;
}
#define _PM_clockHoldHigh asm("nop; nop");

265
src/arch/rp2040.h Normal file
View file

@ -0,0 +1,265 @@
/*!
* @file rp2040.h
*
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
* This file contains RP2040 (Raspberry Pi Pico, etc.) 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.
*
* RP2040 NOTES: This initial implementation does NOT use PIO. That's normal
* for Protomatter, which was written for simple GPIO + timer interrupt for
* broadest portability. While not entirely optimal, it's not pessimal
* either...no worse than any other platform where we're not taking
* advantage of device-specific DMA or peripherals. Would require changes to
* the 'blast' functions or possibly the whole _PM_row_handler() (both
* currently in core.c). CPU load is just a few percent for a 64x32
* matrix @ 6-bit depth, so I'm not losing sleep over this.
*
*/
#pragma once
#if defined(ARDUINO_ARCH_RP2040) || defined(PICO_BOARD) || \
defined(__RP2040__) || defined(__RP2350__)
#include "../../hardware_pwm/include/hardware/pwm.h"
#include "hardware/irq.h"
#include "hardware/timer.h"
#include "pico/stdlib.h" // For sio_hw, etc.
// RP2040 only allows full 32-bit aligned writes to GPIO.
#define _PM_STRICT_32BIT_IO ///< Change core.c behavior for long accesses only
// Enable this to use PWM for bitplane timing, else a timer alarm is used.
// PWM has finer resolution, but alarm is adequate -- this is more about
// which peripheral we'd rather use, as both are finite resources.
#ifndef _PM_CLOCK_PWM
#define _PM_CLOCK_PWM (1)
#endif
#if _PM_CLOCK_PWM // Use PWM for timing
static void _PM_PWM_ISR(void);
#else // Use timer alarm for timing
static void _PM_timerISR(void);
#endif
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
// THIS CURRENTLY ONLY WORKS WITH THE PHILHOWER RP2040 CORE.
// mbed Arduino RP2040 core won't compile due to missing stdio.h.
// Also, much of this currently assumes GPXX pin numbers with no remapping.
// 'pin' here is GPXX # (might change if pin remapping gets added in core)
#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
#if _PM_CLOCK_PWM // Use PWM for timing
// Arduino implementation is tied to a specific PWM slice & frequency
#define _PM_PWM_SLICE 0
#define _PM_PWM_DIV ((F_CPU + 20000000) / 40000000) // 125 MHz->3->~41.6 MHz
#define _PM_timerFreq (F_CPU / _PM_PWM_DIV)
#define _PM_TIMER_DEFAULT NULL
#else // Use alarm for timing
// Arduino implementation is tied to a specific timer alarm & frequency
#define _PM_ALARM_NUM 1
#define _PM_IRQ_HANDLER TIMER_IRQ_1
#define _PM_timerFreq 1000000
#define _PM_TIMER_DEFAULT NULL
#endif
// Initialize, but do not start, timer.
void _PM_timerInit(Protomatter_core *core) {
#if _PM_CLOCK_PWM
// Enable PWM wrap interrupt
pwm_clear_irq(_PM_PWM_SLICE);
pwm_set_irq_enabled(_PM_PWM_SLICE, true);
irq_set_exclusive_handler(PWM_IRQ_WRAP, _PM_PWM_ISR);
irq_set_enabled(PWM_IRQ_WRAP, true);
// Config but do not start PWM
pwm_config config = pwm_get_default_config();
pwm_config_set_clkdiv_int(&config, _PM_PWM_DIV);
pwm_init(_PM_PWM_SLICE, &config, true);
#else
timer_hw->alarm[_PM_ALARM_NUM] = timer_hw->timerawl; // Clear any timer
hw_set_bits(&timer_hw->inte, 1u << _PM_ALARM_NUM);
irq_set_exclusive_handler(_PM_IRQ_HANDLER, _PM_timerISR); // Set IRQ handler
#endif
}
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
#if !defined(F_CPU) // Not sure if CircuitPython build defines this
#ifdef __RP2040__
#define F_CPU 125000000 // Standard RP2040 clock speed
#endif
#ifdef __RP2350__
#define F_CPU 150000000 // Standard RP2350 clock speed
#endif
#endif
// 'pin' here is GPXX #
#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
#if _PM_CLOCK_PWM
int _PM_pwm_slice;
#define _PM_PWM_SLICE (_PM_pwm_slice & 0xff)
#define _PM_PWM_DIV 3 // ~41.6 MHz, similar to SAMD
#define _PM_timerFreq (F_CPU / _PM_PWM_DIV)
#define _PM_TIMER_DEFAULT NULL
#else // Use alarm for timing
// Currently tied to a specific timer alarm & frequency
#define _PM_ALARM_NUM 1
#define _PM_IRQ_HANDLER TIMER_IRQ_1
#define _PM_timerFreq 1000000
#define _PM_TIMER_DEFAULT NULL
#endif // end PWM/alarm
// Initialize, but do not start, timer.
void _PM_timerInit(Protomatter_core *core) {
#if _PM_CLOCK_PWM
_PM_pwm_slice = (int)core->timer & 0xff;
// Enable PWM wrap interrupt
pwm_clear_irq(_PM_PWM_SLICE);
pwm_set_irq_enabled(_PM_PWM_SLICE, true);
irq_set_exclusive_handler(PWM_IRQ_WRAP, _PM_PWM_ISR);
irq_set_enabled(PWM_IRQ_WRAP, true);
// Config but do not start PWM
pwm_config config = pwm_get_default_config();
pwm_config_set_clkdiv_int(&config, _PM_PWM_DIV);
pwm_init(_PM_PWM_SLICE, &config, true);
#else
timer_hw->alarm[_PM_ALARM_NUM] = timer_hw->timerawl; // Clear any timer
hw_set_bits(&timer_hw->inte, 1u << _PM_ALARM_NUM);
irq_set_exclusive_handler(_PM_IRQ_HANDLER, _PM_timerISR); // Set IRQ handler
#endif
}
// 'pin' here is GPXX #
#define _PM_portBitMask(pin) (1UL << pin)
// Same for these -- using GPXX #
#define _PM_pinOutput(pin) \
{ \
gpio_init(pin); \
gpio_set_dir(pin, GPIO_OUT); \
}
#define _PM_pinLow(pin) gpio_clr_mask(1UL << pin)
#define _PM_pinHigh(pin) gpio_set_mask(1UL << pin)
#ifndef _PM_delayMicroseconds
#define _PM_delayMicroseconds(n) sleep_us(n)
#endif
#endif // end CIRCUITPY
#define _PM_portOutRegister(pin) ((void *)&sio_hw->gpio_out)
#define _PM_portSetRegister(pin) ((volatile uint32_t *)&sio_hw->gpio_set)
#define _PM_portClearRegister(pin) ((volatile uint32_t *)&sio_hw->gpio_clr)
#define _PM_portToggleRegister(pin) ((volatile uint32_t *)&sio_hw->gpio_togl)
#if !_PM_CLOCK_PWM
// Unlike timers on other devices, on RP2040 you don't reset a counter to
// zero at the start of a cycle. To emulate that behavior (for determining
// elapsed times), the timer start time must be saved somewhere...
static volatile uint32_t _PM_timerSave;
#endif
// Because it's tied to a specific timer right now, there can be only
// one instance of the Protomatter_core struct. The Arduino library
// sets up this pointer when calling begin().
void *_PM_protoPtr = NULL;
#if _PM_CLOCK_PWM // Use PWM for timing
static void _PM_PWM_ISR(void) {
pwm_clear_irq(_PM_PWM_SLICE); // Reset PWM wrap interrupt
_PM_row_handler(_PM_protoPtr); // In core.c
}
#else // Use timer alarm for timing
static void _PM_timerISR(void) {
hw_clear_bits(&timer_hw->intr, 1u << _PM_ALARM_NUM); // Clear alarm flag
_PM_row_handler(_PM_protoPtr); // In core.c
}
#endif
// Set timer period and enable timer.
inline void _PM_timerStart(Protomatter_core *core, uint32_t period) {
#if _PM_CLOCK_PWM
pwm_set_counter(_PM_PWM_SLICE, 0);
pwm_set_wrap(_PM_PWM_SLICE, period);
pwm_set_enabled(_PM_PWM_SLICE, true);
#else
irq_set_enabled(_PM_IRQ_HANDLER, true); // Enable alarm IRQ
_PM_timerSave = timer_hw->timerawl; // Time at start
timer_hw->alarm[_PM_ALARM_NUM] = _PM_timerSave + period; // Time at end
#endif
}
// Return current count value (timer enabled or not).
// Timer must be previously initialized.
inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
#if _PM_CLOCK_PWM
return pwm_get_counter(_PM_PWM_SLICE);
#else
return timer_hw->timerawl - _PM_timerSave;
#endif
}
// Disable timer and return current count value.
// Timer must be previously initialized.
uint32_t _PM_timerStop(Protomatter_core *core) {
#if _PM_CLOCK_PWM
pwm_set_enabled(_PM_PWM_SLICE, false);
#else
irq_set_enabled(_PM_IRQ_HANDLER, false); // Disable alarm IRQ
#endif
return _PM_timerGetCount(core);
}
#if (F_CPU >= 250000000)
#define _PM_clockHoldLow asm("nop; nop; nop;");
#define _PM_clockHoldHigh asm("nop; nop; nop;");
#elif (F_CPU >= 175000000)
#define _PM_clockHoldLow asm("nop; nop; nop;");
#define _PM_clockHoldHigh asm("nop;");
#elif (F_CPU >= 125000000)
#define _PM_clockHoldLow asm("nop; nop; nop;");
#define _PM_clockHoldHigh asm("nop;");
#elif (F_CPU >= 100000000)
#define _PM_clockHoldLow asm("nop;");
#endif // No NOPs needed at lower speeds
#define _PM_chunkSize 8
#if _PM_CLOCK_PWM
#define _PM_minMinPeriod 100
#else
#define _PM_minMinPeriod 8
#endif
#endif // END RP2040

View file

@ -17,7 +17,7 @@
#pragma once
#if defined(__SAMD51__) || defined(SAMD51) || defined(_SAMD21_) || \
#if defined(__SAMD51__) || defined(SAM_D5X_E5X) || defined(_SAMD21_) || \
defined(SAMD21)
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
@ -64,7 +64,7 @@ void _PM_IRQ_HANDLER(void) {
#define _PM_pinInput(pin) gpio_set_pin_direction(pin, GPIO_DIRECTION_IN)
#define _PM_pinHigh(pin) gpio_set_pin_level(pin, 1)
#define _PM_pinLow(pin) gpio_set_pin_level(pin, 0)
#define _PM_portBitMask(pin) (1u << ((pin)&31))
#define _PM_portBitMask(pin) (1u << ((pin) & 31))
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define _PM_byteOffset(pin) ((pin & 31) / 8)
@ -95,4 +95,4 @@ void _PM_IRQ_HANDLER(void) {
#endif
#endif // END SAMD51/SAMD21
#endif // END SAMD5x/SAME5x/SAMD21

View file

@ -44,31 +44,31 @@
// CODE COMMON TO ALL ENVIRONMENTS -----------------------------------------
// Initialize, but do not start, timer
void _PM_timerInit(void *tptr) {
void _PM_timerInit(Protomatter_core *core) {
static const struct {
Tc *tc; // -> Timer/counter peripheral base address
IRQn_Type IRQn; // Interrupt number
uint8_t GCM_ID; // GCLK selection ID
} timer[] = {
#if defined(TC0)
{TC0, TC0_IRQn, GCM_TCC0_TCC1},
{TC0, TC0_IRQn, GCM_TCC0_TCC1},
#endif
#if defined(TC1)
{TC1, TC1_IRQn, GCM_TCC0_TCC1},
{TC1, TC1_IRQn, GCM_TCC0_TCC1},
#endif
#if defined(TC2)
{TC2, TC2_IRQn, GCM_TCC2_TC3},
{TC2, TC2_IRQn, GCM_TCC2_TC3},
#endif
#if defined(TC3)
{TC3, TC3_IRQn, GCM_TCC2_TC3},
{TC3, TC3_IRQn, GCM_TCC2_TC3},
#endif
#if defined(TC4)
{TC4, TC4_IRQn, GCM_TC4_TC5},
{TC4, TC4_IRQn, GCM_TC4_TC5},
#endif
};
#define NUM_TIMERS (sizeof timer / sizeof timer[0])
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
uint8_t timerNum = 0;
while ((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tc)) {
@ -113,8 +113,8 @@ void _PM_timerInit(void *tptr) {
// Set timer period, initialize count value to zero, enable timer.
// Timer must be initialized to 16-bit mode using the init function
// above, but must be inactive before calling this.
inline void _PM_timerStart(void *tptr, uint32_t period) {
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
inline void _PM_timerStart(Protomatter_core *core, uint32_t period) {
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
tc->COUNT16.COUNT.reg = 0;
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
;
@ -128,8 +128,8 @@ inline void _PM_timerStart(void *tptr, uint32_t period) {
// Return current count value (timer enabled or not).
// Timer must be previously initialized.
inline uint32_t _PM_timerGetCount(void *tptr) {
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
tc->COUNT16.READREQ.reg = TC_READREQ_RCONT | TC_READREQ_ADDR(0x10);
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
;
@ -138,9 +138,9 @@ inline uint32_t _PM_timerGetCount(void *tptr) {
// Disable timer and return current count value.
// Timer must be previously initialized.
inline uint32_t _PM_timerStop(void *tptr) {
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
uint32_t count = _PM_timerGetCount(tptr);
inline uint32_t _PM_timerStop(Protomatter_core *core) {
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
uint32_t count = _PM_timerGetCount(core);
tc->COUNT16.CTRLA.bit.ENABLE = 0;
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
;

View file

@ -17,7 +17,8 @@
#pragma once
#if defined(__SAMD51__) || defined(SAMD51) // Arduino, Circuitpy SAMD51 defs
#if defined(__SAMD51__) || \
defined(SAM_D5X_E5X) // Arduino, Circuitpy SAMD5x / E5x defs
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
@ -46,6 +47,21 @@
#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
// Other port register lookups go here
@ -55,55 +71,55 @@
// CODE COMMON TO ALL ENVIRONMENTS -----------------------------------------
// Initialize, but do not start, timer
void _PM_timerInit(void *tptr) {
void _PM_timerInit(Protomatter_core *core) {
static const struct {
Tc *tc; // -> Timer/counter peripheral base address
IRQn_Type IRQn; // Interrupt number
uint8_t GCLK_ID; // Peripheral channel # for clock source
} timer[] = {
#if defined(TC0)
{TC0, TC0_IRQn, TC0_GCLK_ID},
{TC0, TC0_IRQn, TC0_GCLK_ID},
#endif
#if defined(TC1)
{TC1, TC1_IRQn, TC1_GCLK_ID},
{TC1, TC1_IRQn, TC1_GCLK_ID},
#endif
#if defined(TC2)
{TC2, TC2_IRQn, TC2_GCLK_ID},
{TC2, TC2_IRQn, TC2_GCLK_ID},
#endif
#if defined(TC3)
{TC3, TC3_IRQn, TC3_GCLK_ID},
{TC3, TC3_IRQn, TC3_GCLK_ID},
#endif
#if defined(TC4)
{TC4, TC4_IRQn, TC4_GCLK_ID},
{TC4, TC4_IRQn, TC4_GCLK_ID},
#endif
#if defined(TC5)
{TC5, TC5_IRQn, TC5_GCLK_ID},
{TC5, TC5_IRQn, TC5_GCLK_ID},
#endif
#if defined(TC6)
{TC6, TC6_IRQn, TC6_GCLK_ID},
{TC6, TC6_IRQn, TC6_GCLK_ID},
#endif
#if defined(TC7)
{TC7, TC7_IRQn, TC7_GCLK_ID},
{TC7, TC7_IRQn, TC7_GCLK_ID},
#endif
#if defined(TC8)
{TC8, TC8_IRQn, TC8_GCLK_ID},
{TC8, TC8_IRQn, TC8_GCLK_ID},
#endif
#if defined(TC9)
{TC9, TC9_IRQn, TC9_GCLK_ID},
{TC9, TC9_IRQn, TC9_GCLK_ID},
#endif
#if defined(TC10)
{TC10, TC10_IRQn, TC10_GCLK_ID},
{TC10, TC10_IRQn, TC10_GCLK_ID},
#endif
#if defined(TC11)
{TC11, TC11_IRQn, TC11_GCLK_ID},
{TC11, TC11_IRQn, TC11_GCLK_ID},
#endif
#if defined(TC12)
{TC12, TC12_IRQn, TC12_GCLK_ID},
{TC12, TC12_IRQn, TC12_GCLK_ID},
#endif
};
#define NUM_TIMERS (sizeof timer / sizeof timer[0])
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
uint8_t timerNum = 0;
while ((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tc)) {
@ -156,13 +172,27 @@ void _PM_timerInit(void *tptr) {
NVIC_EnableIRQ(timer[timerNum].IRQn);
// 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.
// Timer must be initialized to 16-bit mode using the init function
// above, but must be inactive before calling this.
inline void _PM_timerStart(void *tptr, uint32_t period) {
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
inline void _PM_timerStart(Protomatter_core *core, uint32_t period) {
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
tc->COUNT16.COUNT.reg = 0;
while (tc->COUNT16.SYNCBUSY.bit.COUNT)
;
@ -176,8 +206,8 @@ inline void _PM_timerStart(void *tptr, uint32_t period) {
// Return current count value (timer enabled or not).
// Timer must be previously initialized.
inline uint32_t _PM_timerGetCount(void *tptr) {
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
tc->COUNT16.CTRLBSET.bit.CMD = 0x4; // Sync COUNT
while (tc->COUNT16.CTRLBSET.bit.CMD)
; // Wait for command
@ -186,30 +216,213 @@ inline uint32_t _PM_timerGetCount(void *tptr) {
// Disable timer and return current count value.
// Timer must be previously initialized.
uint32_t _PM_timerStop(void *tptr) {
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
uint32_t count = _PM_timerGetCount(tptr);
uint32_t _PM_timerStop(Protomatter_core *core) {
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
uint32_t count = _PM_timerGetCount(core);
tc->COUNT16.CTRLA.bit.ENABLE = 0;
while (tc->COUNT16.SYNCBUSY.bit.STATUS)
;
return count;
}
// See notes in core.c before the "blast" functions
#if F_CPU >= 200000000
#define _PM_clockHoldHigh asm("nop; nop; nop; nop; nop");
#define _PM_clockHoldLow asm("nop; nop");
#elif F_CPU >= 180000000
#define _PM_clockHoldHigh asm("nop; nop; nop; nop");
#define _PM_clockHoldLow asm("nop");
#elif F_CPU >= 150000000
#define _PM_clockHoldHigh asm("nop; nop; nop");
#define _PM_clockHoldLow asm("nop");
#else
#define _PM_clockHoldHigh asm("nop; nop; nop");
#define _PM_clockHoldLow asm("nop");
// SAMD51 takes a WEIRD TURN here, in an attempt to make the HUB75 clock
// waveform slightly adjustable. Old vs new matrices seem to have different
// preferences, and this tries to address that. If this works well then the
// approach might be applied to other architectures (which are all fixed
// duty cycle right now). THE CHALLENGE is that Protomatter works in a bit-
// bangingly way (this is on purpose and by design, avoiding peripherals
// that might work only on certain pins, for better compatibility with
// existing shields and wings from the AVR era), we're aiming for nearly a
// 20 MHz signal, and the SAMD51 cycle clock is ostensibly 120 MHz. With
// just a few cycles to toggle the data and clock lines, that doesn't even
// leave enough time for a counter loop.
#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
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
#endif // END __SAMD51__ || SAMD51
#endif // END __SAMD51__ || SAM_D5X_E5X

View file

@ -28,7 +28,7 @@
#include "timers.h"
#undef _PM_portBitMask
#define _PM_portBitMask(pin) (1u << ((pin)&15))
#define _PM_portBitMask(pin) (1u << ((pin) & 15))
#define _PM_byteOffset(pin) ((pin & 15) / 8)
#define _PM_wordOffset(pin) ((pin & 15) / 16)
@ -83,7 +83,7 @@ volatile uint16_t *_PM_portClearRegister(uint32_t pin) {
// TODO: this is no longer true, should it change?
void *_PM_protoPtr = NULL;
STATIC TIM_HandleTypeDef tim_handle;
static TIM_HandleTypeDef tim_handle;
// Timer interrupt service routine
void _PM_IRQ_HANDLER(void) {
@ -93,8 +93,8 @@ void _PM_IRQ_HANDLER(void) {
}
// Initialize, but do not start, timer
void _PM_timerInit(void *tptr) {
TIM_TypeDef *tim_instance = (TIM_TypeDef *)tptr;
void _PM_timerInit(Protomatter_core *core) {
TIM_TypeDef *tim_instance = (TIM_TypeDef *)core->timer;
stm_peripherals_timer_reserve(tim_instance);
// Set IRQs at max priority and start clock
stm_peripherals_timer_preinit(tim_instance, 0, _PM_IRQ_HANDLER);
@ -114,8 +114,8 @@ void _PM_timerInit(void *tptr) {
NVIC_SetPriority(tim_irq, 0); // Top priority
}
inline void _PM_timerStart(void *tptr, uint32_t period) {
TIM_TypeDef *tim = tptr;
inline void _PM_timerStart(Protomatter_core *core, uint32_t period) {
TIM_TypeDef *tim = core->timer;
tim->SR = 0;
tim->ARR = period;
tim->CR1 |= TIM_CR1_CEN;
@ -123,8 +123,13 @@ inline void _PM_timerStart(void *tptr, uint32_t period) {
HAL_NVIC_EnableIRQ(stm_peripherals_timer_get_irqnum(tim));
}
uint32_t _PM_timerStop(void *tptr) {
TIM_TypeDef *tim = tptr;
inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
TIM_TypeDef *tim = core->timer;
return tim->CNT;
}
uint32_t _PM_timerStop(Protomatter_core *core) {
TIM_TypeDef *tim = core->timer;
HAL_NVIC_DisableIRQ(stm_peripherals_timer_get_irqnum(tim));
tim->CR1 &= ~TIM_CR1_CEN;
tim->DIER &= ~TIM_DIER_UIE;

View file

@ -118,8 +118,8 @@ static void _PM_timerISR(void) {
}
// Initialize, but do not start, timer.
void _PM_timerInit(void *tptr) {
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)tptr;
void _PM_timerInit(Protomatter_core *core) {
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)core->timer;
CCM_CCGR1 |= CCM_CCGR1_PIT(CCM_CCGR_ON); // Enable clock signal to PIT
PIT_MCR = 1; // Enable PIT
timer->TCTRL = 0; // Disable timer and interrupt
@ -130,8 +130,8 @@ void _PM_timerInit(void *tptr) {
}
// Set timer period, initialize count value to zero, enable timer.
inline void _PM_timerStart(void *tptr, uint32_t period) {
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)tptr;
inline void _PM_timerStart(Protomatter_core *core, uint32_t period) {
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)core->timer;
timer->TCTRL = 0; // Disable timer and interrupt
timer->LDVAL = period; // Set load value
// timer->CVAL = period; // And current value (just in case?)
@ -141,17 +141,17 @@ inline void _PM_timerStart(void *tptr, uint32_t period) {
// Return current count value (timer enabled or not).
// Timer must be previously initialized.
inline uint32_t _PM_timerGetCount(void *tptr) {
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)tptr;
inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)core->timer;
return (timer->LDVAL - timer->CVAL);
}
// Disable timer and return current count value.
// Timer must be previously initialized.
uint32_t _PM_timerStop(void *tptr) {
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)tptr;
uint32_t _PM_timerStop(Protomatter_core *core) {
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)core->timer;
timer->TCTRL = 0; // Disable timer and interrupt
return _PM_timerGetCount(tptr);
return _PM_timerGetCount(core);
}
#define _PM_clockHoldHigh \

View file

@ -32,6 +32,7 @@
#include "core.h" // enums and structs
#include "arch/arch.h" // Do NOT include this in any other source files
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
// Overall matrix refresh rate (frames/second) is a function of matrix width
@ -72,12 +73,19 @@ static void blast_byte(Protomatter_core *core, uint8_t *data);
static void blast_word(Protomatter_core *core, uint16_t *data);
static void blast_long(Protomatter_core *core, uint32_t *data);
// Needed only for panels with FM6126A chipset
static void _PM_resetFM6126A(Protomatter_core *core);
#if !defined(_PM_clearReg)
#define _PM_clearReg(x) \
(*(volatile _PM_PORT_TYPE *)((x).clearReg) = \
((x).bit)) ///< Clear non-RGB-data-or-clock control line (_PM_pin type)
#endif
#if !defined(_PM_setReg)
#define _PM_setReg(x) \
(*(volatile _PM_PORT_TYPE *)((x).setReg) = \
((x).bit)) ///< Set non-RGB-data-or-clock control line (_PM_pin type)
#endif
// Validate and populate vital elements of core structure.
// Does NOT allocate core struct -- calling function must provide that.
@ -86,17 +94,33 @@ ProtomatterStatus _PM_init(Protomatter_core *core, uint16_t bitWidth,
uint8_t bitDepth, uint8_t rgbCount, uint8_t *rgbList,
uint8_t addrCount, uint8_t *addrList,
uint8_t clockPin, uint8_t latchPin, uint8_t oePin,
bool doubleBuffer, void *timer) {
if (!core)
bool doubleBuffer, int8_t tile, void *timer) {
if (!core) {
return PROTOMATTER_ERR_ARG;
}
// bitDepth is NOT constrained here, handle in calling function
// (varies with implementation, e.g. GFX lib is max 6 bitplanes,
// but might be more or less elsewhere)
#if defined(_PM_bytesPerElement)
#if _PM_bytesPerElement == 1
if (rgbCount > 1)
rgbCount = 1; // Parallel output not supported if only 8-bit PORT
#elif _PM_bytesPerElement == 2
if (rgbCount > 2)
rgbCount = 2; // Max 2 in parallel (13 bits in 16-bit PORT)
#else
if (rgbCount > 5)
rgbCount = 5; // Max 5 in parallel (32-bit PORT)
rgbCount = 5; // Max 5 in parallel (31 bits in 32-bit PORT)
#endif
#else
if (rgbCount > 5)
rgbCount = 5; // Max 5 in parallel (31 bits in 32-bit PORT)
#endif // end _PM_bytesPerElement
if (addrCount > 5)
addrCount = 5; // Max 5 address lines (A-E)
// bitDepth is NOT constrained here, handle in calling function
// (varies with implementation, e.g. GFX lib is max 6 bitplanes,
// but might be more or less elsewhere)
if (!tile)
tile = 1; // Can't have zero vertical tiling. Single matrix is 1.
#if defined(_PM_TIMER_DEFAULT)
// If NULL timer was passed in (the default case for the constructor),
@ -110,7 +134,9 @@ ProtomatterStatus _PM_init(Protomatter_core *core, uint16_t bitWidth,
#endif
core->timer = timer;
core->width = bitWidth; // Total matrix chain length in bits
core->width = bitWidth; // Matrix chain width in bits (NOT including V tile)
core->tile = tile; // Matrix chain vertical tiling
core->chainBits = bitWidth * abs(tile); // Total matrix chain bits
core->numPlanes = bitDepth;
core->parallel = rgbCount;
core->numAddressLines = addrCount;
@ -151,6 +177,14 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
return PROTOMATTER_ERR_MALLOC;
}
#if defined(_PM_bytesPerElement)
// Some chips (e.g. ESP32S2 & S3) have potent pin MUX capabilities and
// arch-specific code might use special peripherals. The usual rules about
// RGB+clock on one PORT, and the size of the internal data representation,
// can be overridden because they're much simplified.
core->bytesPerElement = _PM_bytesPerElement;
uint32_t bitMask = 0;
#else
// Verify that rgbPins and clockPin are all on the same PORT. If not,
// return an error. Pin list is not freed; please call dealloc function.
// Also get bitmask of which bits within 32-bit PORT register are
@ -208,10 +242,11 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
core->bytesPerElement = 4; // Use 32-bit PORT accesses.
break;
}
#endif // end RGB+clock PORT check & bytesPerElement calc
// Planning for screen data allocation...
core->numRowPairs = 1 << core->numAddressLines;
uint8_t chunks = (core->width + (_PM_chunkSize - 1)) / _PM_chunkSize;
uint8_t chunks = (core->chainBits + (_PM_chunkSize - 1)) / _PM_chunkSize;
uint16_t columns = chunks * _PM_chunkSize; // Padded matrix width
uint32_t screenBytes =
columns * core->numRowPairs * core->numPlanes * core->bytesPerElement;
@ -232,7 +267,7 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
// rgbMask data follows the matrix buffer(s)
core->rgbMask = core->screenData + screenBytes;
#if !defined(_PM_portToggleRegister)
#if !defined(_PM_USE_TOGGLE_FORMAT)
// Clear entire screenData buffer so there's no cruft in any pad bytes
// (if using toggle register, each is set to clockMask below instead).
memset(core->screenData, 0, screenBytes);
@ -241,7 +276,7 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
// Figure out clockMask and rgbAndClockMask, clear matrix buffers
if (core->bytesPerElement == 1) {
core->portOffset = _PM_byteOffset(core->rgbPins[0]);
#if defined(_PM_portToggleRegister) && !defined(_PM_STRICT_32BIT_IO)
#if defined(_PM_USE_TOGGLE_FORMAT) && !defined(_PM_STRICT_32BIT_IO)
// Clock and rgbAndClockMask are 8-bit values
core->clockMask = _PM_portBitMask(core->clockPin) >> (core->portOffset * 8);
core->rgbAndClockMask =
@ -258,7 +293,7 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
}
} else if (core->bytesPerElement == 2) {
core->portOffset = _PM_wordOffset(core->rgbPins[0]);
#if defined(_PM_portToggleRegister) && !defined(_PM_STRICT_32BIT_IO)
#if defined(_PM_USE_TOGGLE_FORMAT) && !defined(_PM_STRICT_32BIT_IO)
// Clock and rgbAndClockMask are 16-bit values
core->clockMask =
_PM_portBitMask(core->clockPin) >> (core->portOffset * 16);
@ -272,7 +307,7 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
// Clock and rgbAndClockMask are 32-bit values
core->clockMask = _PM_portBitMask(core->clockPin);
core->rgbAndClockMask = bitMask | core->clockMask;
#if defined(_PM_portToggleRegister)
#if defined(_PM_USE_TOGGLE_FORMAT)
// TO DO: this ifdef and the one above can probably be wrapped up
// in a more cohesive case. Think something similar will be needed
// for the byte case. Will need Teensy 4.1 to test.
@ -291,7 +326,7 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
core->portOffset = 0;
core->clockMask = _PM_portBitMask(core->clockPin);
core->rgbAndClockMask = bitMask | core->clockMask;
#if defined(_PM_portToggleRegister)
#if defined(_PM_USE_TOGGLE_FORMAT)
uint32_t elements = screenBytes / 4;
for (uint32_t i = 0; i < elements; i++) {
((uint32_t *)core->screenData)[i] = core->clockMask;
@ -310,14 +345,11 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
if (core->minPeriod < _PM_minMinPeriod) {
core->minPeriod = _PM_minMinPeriod;
}
core->bitZeroPeriod = core->minPeriod;
// Actual frame rate may be lower than this...it's only an estimate
// and does not factor in things like address line selection delays
// or interrupt overhead. That's OK, just don't want to exceed this
// rate, as it'll eat all the CPU cycles.
// Make a wild guess for the initial bit-zero interval. It's okay
// that this is off, code adapts to actual timer results pretty quick.
core->bitZeroPeriod = core->width * 5; // Initial guesstimate
core->activeBuffer = 0;
@ -341,6 +373,9 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
_PM_pinOutput(core->rgbPins[i]);
_PM_pinLow(core->rgbPins[i]);
}
_PM_resetFM6126A(core);
#if defined(_PM_portToggleRegister)
core->addrPortToggle = _PM_portToggleRegister(core->addr[0].pin);
core->singleAddrPort = 1;
@ -389,9 +424,9 @@ void _PM_stop(Protomatter_core *core) {
return;
}
while (core->swapBuffers)
; // Wait for any pending buffer swap
_PM_timerStop(core->timer); // Halt timer
_PM_setReg(core->oe); // Set OE HIGH (disable output)
; // Wait for any pending buffer swap
_PM_timerStop(core); // Halt timer
_PM_setReg(core->oe); // Set OE HIGH (disable output)
// So, in PRINCIPLE, setting OE high would be sufficient...
// but in case that pin is shared with another function such
// as the onloard LED (which pulses during bootloading) let's
@ -401,7 +436,7 @@ void _PM_stop(Protomatter_core *core) {
_PM_pinLow(core->rgbPins[i]);
}
// Clock out bits (just need to toggle clock with RGBs held low)
for (uint32_t i = 0; i < core->width; i++) {
for (uint32_t i = 0; i < core->chainBits; i++) {
_PM_pinHigh(core->clockPin);
_PM_clockHoldHigh;
_PM_pinLow(core->clockPin);
@ -422,8 +457,18 @@ void _PM_resume(Protomatter_core *core) {
core->swapBuffers = 0;
core->frameCount = 0;
_PM_timerInit(core->timer); // Configure timer
_PM_timerStart(core->timer, 1000); // Start timer
for (uint8_t line = 0, bit = 1; line < core->numAddressLines;
line++, bit <<= 1) {
_PM_pinOutput(core->addr[line].pin);
if (core->prevRow & bit) {
_PM_pinHigh(core->addr[line].pin);
} else {
_PM_pinLow(core->addr[line].pin);
}
}
_PM_timerInit(core); // Configure timer & any other periphs
_PM_timerStart(core, 1000); // Start timer
}
}
@ -468,23 +513,10 @@ IRAM_ATTR void _PM_row_handler(Protomatter_core *core) {
_PM_clearReg(core->latch);
_PM_setReg(core->latch);
// Stop timer, save count value at stop
uint32_t elapsed = _PM_timerStop(core->timer);
(void)_PM_timerStop(core);
uint8_t prevPlane = core->plane; // Save that plane # for later timing
_PM_clearReg(core->latch); // (split to add a few cycles)
// If plane 0 just finished being displayed (plane 1 was loaded on prior
// pass, or there's only one plane...I know, it's confusing), take note
// of the elapsed timer value, for subsequent bitplane timing (each
// plane period is double the previous). Value is filtered slightly to
// avoid jitter.
if ((prevPlane == 1) || (core->numPlanes == 1)) {
core->bitZeroPeriod = ((core->bitZeroPeriod * 7) + elapsed) / 8;
if (core->bitZeroPeriod < core->minPeriod) {
core->bitZeroPeriod = core->minPeriod;
}
}
if (prevPlane == 0) { // Plane 0 just finished loading
#if defined(_PM_portToggleRegister)
// If all address lines are on a single PORT (and bit toggle is
@ -540,17 +572,17 @@ IRAM_ATTR void _PM_row_handler(Protomatter_core *core) {
}
}
// 'plane' now is index of data to issue, NOT data to display.
// core->plane now is index of data to issue, NOT data to display.
// 'prevPlane' is the previously-loaded data, which gets displayed
// now while the next plane data is loaded.
// Set timer and enable LED output for data loaded on PRIOR pass:
_PM_timerStart(core->timer, core->bitZeroPeriod << prevPlane);
_PM_timerStart(core, core->bitZeroPeriod << prevPlane);
_PM_delayMicroseconds(1); // Appease Teensy4
_PM_clearReg(core->oe); // Enable LED output
uint32_t elementsPerLine =
_PM_chunkSize * ((core->width + (_PM_chunkSize - 1)) / _PM_chunkSize);
_PM_chunkSize * ((core->chainBits + (_PM_chunkSize - 1)) / _PM_chunkSize);
uint32_t srcOffset = elementsPerLine *
(core->numPlanes * core->row + core->plane) *
core->bytesPerElement;
@ -566,9 +598,30 @@ IRAM_ATTR void _PM_row_handler(Protomatter_core *core) {
blast_long(core, (uint32_t *)(core->screenData + srcOffset));
}
// 'plane' data is now loaded, will be shown on NEXT pass
// core->plane data is now loaded, will be shown on NEXT pass
// On the last (longest) bitplane (note that 'plane' has already wrapped
// around earlier, so a value of 0 here indicates longest plane), take
// note of the elapsed timer value at this point...that's the number of
// cycles required to issue (not necessarily display) data for one plane,
// and the bare minimum display duration allowed for plane 0.
if ((core->numPlanes > 1) && (core->plane == 0)) {
// Determine number of timer cycles taken to issue the data.
// It can vary slightly if heavy interrupts happen, things like that.
// Timer is still running and counting up at this point.
uint32_t elapsed = _PM_timerGetCount(core);
// Nudge the plane-zero time up or down (filtering to avoid jitter)
core->bitZeroPeriod = ((core->bitZeroPeriod * 7) + elapsed + 4) / 8;
// But don't allow it to drop below the minimum period calculated during
// begin(), that's a hard limit and would just waste cycles.
if (core->bitZeroPeriod < core->minPeriod) {
core->bitZeroPeriod = core->minPeriod;
}
}
}
#if !defined _PM_CUSTOM_BLAST
// Innermost data-stuffing loop functions
// The presence of a bit-toggle register can make the data-stuffing loop a
@ -613,7 +666,7 @@ IRAM_ATTR void _PM_row_handler(Protomatter_core *core) {
_PM_clockHoldLow; \
*set = clock; /* Set clock high */ \
_PM_clockHoldHigh; \
*clear = rgbclock; /* Clear RGB data + clock */ \
*clear_full = rgbclock; /* Clear RGB data + clock */ \
///< Bitbang one set of RGB data bits to matrix
#endif
@ -674,7 +727,7 @@ IRAM_ATTR static void blast_byte(Protomatter_core *core, uint8_t *data) {
_PM_PORT_TYPE rgbclock = core->rgbAndClockMask; // RGB + clock bit
#endif
_PM_PORT_TYPE clock = core->clockMask; // Clock bit
uint8_t chunks = (core->width + (_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
// all LOW, so we don't need to initialize that state here.
@ -699,12 +752,12 @@ IRAM_ATTR static void blast_byte(Protomatter_core *core, uint8_t *data) {
volatile _PM_PORT_TYPE *toggle = (volatile _PM_PORT_TYPE *)core->toggleReg;
#else
volatile _PM_PORT_TYPE *set = (volatile _PM_PORT_TYPE *)core->setReg;
volatile _PM_PORT_TYPE *clear = (volatile _PM_PORT_TYPE *)core->clearReg;
volatile _PM_PORT_TYPE *clear_full = (volatile _PM_PORT_TYPE *)core->clearReg;
_PM_PORT_TYPE rgbclock = core->rgbAndClockMask; // RGB + clock bit
#endif
_PM_PORT_TYPE clock = core->clockMask; // Clock bit
uint8_t shift = core->portOffset * 8;
uint8_t chunks = (core->width + (_PM_chunkSize - 1)) / _PM_chunkSize;
uint8_t chunks = (core->chainBits + (_PM_chunkSize - 1)) / _PM_chunkSize;
// PORT has already been initialized with RGB data + clock bits
// all LOW, so we don't need to initialize that state here.
@ -728,16 +781,16 @@ IRAM_ATTR static void blast_word(Protomatter_core *core, uint16_t *data) {
volatile uint16_t *toggle =
(volatile uint16_t *)core->toggleReg + core->portOffset;
#else
volatile uint16_t *set; // For RGB data set
volatile _PM_PORT_TYPE *set_full; // For clock set
volatile _PM_PORT_TYPE *clear_full; // For RGB data + clock clear
volatile uint16_t *set; // For RGB data set
volatile _PM_PORT_TYPE *set_full; // For clock set
volatile _PM_PORT_TYPE *clear_full; // For RGB data + clock clear
set = (volatile uint16_t *)core->setReg + core->portOffset;
set_full = (volatile _PM_PORT_TYPE *)core->setReg;
clear_full = (volatile _PM_PORT_TYPE *)core->clearReg;
_PM_PORT_TYPE rgbclock = core->rgbAndClockMask; // RGB + clock bit
#endif
_PM_PORT_TYPE clock = core->clockMask; // Clock bit
uint8_t chunks = (core->width + (_PM_chunkSize - 1)) / _PM_chunkSize;
uint8_t chunks = (core->chainBits + (_PM_chunkSize - 1)) / _PM_chunkSize;
while (chunks--) {
PEW_UNROLL // _PM_chunkSize RGB+clock writes
}
@ -753,12 +806,12 @@ IRAM_ATTR static void blast_word(Protomatter_core *core, uint16_t *data) {
volatile _PM_PORT_TYPE *toggle = (volatile _PM_PORT_TYPE *)core->toggleReg;
#else
volatile _PM_PORT_TYPE *set = (volatile _PM_PORT_TYPE *)core->setReg;
volatile _PM_PORT_TYPE *clear = (volatile _PM_PORT_TYPE *)core->clearReg;
volatile _PM_PORT_TYPE *clear_full = (volatile _PM_PORT_TYPE *)core->clearReg;
_PM_PORT_TYPE rgbclock = core->rgbAndClockMask; // RGB + clock bit
#endif
_PM_PORT_TYPE clock = core->clockMask; // Clock bit
uint8_t shift = core->portOffset * 16;
uint8_t chunks = (core->width + (_PM_chunkSize - 1)) / _PM_chunkSize;
uint8_t chunks = (core->chainBits + (_PM_chunkSize - 1)) / _PM_chunkSize;
while (chunks--) {
PEW_UNROLL // _PM_chunkSize RGB+clock writes
}
@ -777,11 +830,13 @@ IRAM_ATTR static void blast_long(Protomatter_core *core, uint32_t *data) {
// Note in this case two copies exist of the PORT set register.
// The optimizer will most likely simplify this; leaving as-is, not
// wanting a special case of the PEW macro due to divergence risk.
volatile uint32_t *set; // For RGB data set
volatile _PM_PORT_TYPE *set_full; // For clock set
volatile uint32_t *set; // For RGB data set
#if !defined(_PM_STRICT_32BIT_IO)
volatile _PM_PORT_TYPE *set_full; // For clock set
set_full = (volatile _PM_PORT_TYPE *)core->setReg;
#endif
volatile _PM_PORT_TYPE *clear_full; // For RGB data + clock clear
set = (volatile uint32_t *)core->setReg;
set_full = (volatile _PM_PORT_TYPE *)core->setReg;
clear_full = (volatile _PM_PORT_TYPE *)core->clearReg;
_PM_PORT_TYPE rgbclock = core->rgbAndClockMask; // RGB + clock bit
#endif
@ -789,7 +844,7 @@ IRAM_ATTR static void blast_long(Protomatter_core *core, uint32_t *data) {
#if defined(_PM_STRICT_32BIT_IO)
uint8_t shift = 0;
#endif
uint8_t chunks = (core->width + (_PM_chunkSize - 1)) / _PM_chunkSize;
uint8_t chunks = (core->chainBits + (_PM_chunkSize - 1)) / _PM_chunkSize;
while (chunks--) {
PEW_UNROLL // _PM_chunkSize RGB+clock writes
}
@ -798,6 +853,8 @@ IRAM_ATTR static void blast_long(Protomatter_core *core, uint32_t *data) {
#endif
}
#endif // end !_PM_CUSTOM_BLAST
// Returns current value of frame counter and resets its value to zero.
// Two calls to this, timed one second apart (or use math with other
// intervals), can be used to get a rough frames-per-second value for
@ -821,6 +878,55 @@ void _PM_swapbuffer_maybe(Protomatter_core *core) {
}
}
// Set all RGB data bits high or low. Its done this way (rather than via
// setReg/clearReg members, which would be a single call) because the latter
// aren't configured on some architectures (e.g. ESP32-S3) where special
// peripherals are used. Nothing in FM6126A init needs to be super optimal
// as it's only called once briefly on startup...clocking takes more time!
static void _PM_rgbState(Protomatter_core *core, bool state) {
for (uint8_t p = 0; p < core->parallel * 6; p++) {
if (state)
_PM_pinHigh(core->rgbPins[p]);
else
_PM_pinLow(core->rgbPins[p]);
}
}
// Configure one register of FM6126A. Latch assumed LOW on entry.
static void _PM_FM6126A_reg(Protomatter_core *core, uint8_t reg,
uint16_t dataMask) {
for (uint16_t i = 0; i < core->chainBits; i++) {
_PM_rgbState(core, dataMask & (0x8000 >> (i & 15)));
if (i > (core->chainBits - reg))
_PM_pinHigh(core->latch.pin);
_PM_pinHigh(core->clockPin);
_PM_delayMicroseconds(1);
_PM_pinLow(core->clockPin);
_PM_delayMicroseconds(1);
}
_PM_pinLow(core->latch.pin);
}
// Adapted from SmartMatrix: FM6126A chipset reset sequence,
// harmless and ignored by other chips. Thanks to Bob Davis:
// bobdavis321.blogspot.com/2019/02/p3-64x32-hub75e-led-matrix-panels-with.html
static void _PM_resetFM6126A(Protomatter_core *core) {
// On arrival here, clock and latch are low, OE is high, no need to config,
// but they must be in the same states when finished.
_PM_FM6126A_reg(core, 12, 0b0111111111111111);
_PM_FM6126A_reg(core, 13, 0b0000000001000000);
_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)
// Arduino and CircuitPython happen to use the same internal canvas
@ -829,11 +935,10 @@ void _PM_swapbuffer_maybe(Protomatter_core *core) {
// 16-bit (565) color conversion functions go here (rather than in the
// Arduino lib .cpp) because knowledge is required of chunksize and the
// toggle register (or lack thereof), which are only known to this file,
// not the .cpp or anywhere else
// However...this file knows nothing of the GFXcanvas16 type (from
// Adafruit_GFX...another C++ lib), so the .cpp just passes down some
// pointers and minimal info about the canvas buffer.
// It's probably not ideal but this is my life now, oh well.
// not the .cpp or anywhere else. However...this file knows nothing of
// the GFXcanvas16 type (from Adafruit_GFX...another C++ lib), so the
// .cpp just passes down some pointers and minimal info about the canvas
// buffer. It's probably not ideal but this is my life now, oh well.
// Different runtime environments (which might not use the 565 canvas
// format) will need their own conversion functions.
@ -846,20 +951,18 @@ void _PM_swapbuffer_maybe(Protomatter_core *core) {
// width argument comes from GFX canvas width, which may be less than
// core's bitWidth (due to padding). height isn't needed, it can be
// inferred from core->numRowPairs.
// inferred from core->numRowPairs and core->tile.
__attribute__((noinline)) void _PM_convert_565_byte(Protomatter_core *core,
const uint16_t *source,
uint16_t width) {
const uint16_t *upperSrc = source; // Canvas top half
const uint16_t *lowerSrc =
source + width * core->numRowPairs; // " bottom half
uint8_t *pinMask = (uint8_t *)core->rgbMask; // Pin bitmasks
uint8_t *dest = (uint8_t *)core->screenData;
if (core->doubleBuffer) {
dest += core->bufferSize * (1 - core->activeBuffer);
}
#if defined(_PM_portToggleRegister)
// #if defined(_PM_portToggleRegister)
#if defined(_PM_USE_TOGGLE_FORMAT)
#if !defined(_PM_STRICT_32BIT_IO)
// core->clockMask mask is already an 8-bit value
uint8_t clockMask = core->clockMask;
@ -875,10 +978,10 @@ __attribute__((noinline)) void _PM_convert_565_byte(Protomatter_core *core,
// Determine matrix bytes per bitplane & row (row pair really):
// Size of 1 plane of row pair (across full chain / tile set)
uint32_t bitplaneSize =
_PM_chunkSize *
((width + (_PM_chunkSize - 1)) / _PM_chunkSize); // 1 plane of row pair
uint8_t pad = bitplaneSize - width; // Start-of-plane pad
_PM_chunkSize * ((core->chainBits + (_PM_chunkSize - 1)) / _PM_chunkSize);
uint8_t pad = bitplaneSize - core->chainBits; // Plane-start pad
// Skip initial scanline padding if present (HUB75 matrices shift data
// in from right-to-left, so if we need scanline padding it occurs at
@ -918,32 +1021,62 @@ __attribute__((noinline)) void _PM_convert_565_byte(Protomatter_core *core,
uint32_t greenBit = initialGreenBit;
uint32_t blueBit = initialBlueBit;
for (uint8_t plane = 0; plane < core->numPlanes; plane++) {
#if defined(_PM_portToggleRegister)
// #if defined(_PM_portToggleRegister)
#if defined(_PM_USE_TOGGLE_FORMAT)
uint8_t prior = clockMask; // Set clock bit on 1st out
#endif
for (uint16_t x = 0; x < width; x++) {
uint16_t upperRGB = upperSrc[x]; // Pixel in upper half
uint16_t lowerRGB = lowerSrc[x]; // Pixel in lower half
uint8_t result = 0;
if (upperRGB & redBit)
result |= pinMask[0];
if (upperRGB & greenBit)
result |= pinMask[1];
if (upperRGB & blueBit)
result |= pinMask[2];
if (lowerRGB & redBit)
result |= pinMask[3];
if (lowerRGB & greenBit)
result |= pinMask[4];
if (lowerRGB & blueBit)
result |= pinMask[5];
#if defined(_PM_portToggleRegister)
dest[x] = result ^ prior;
prior = result | clockMask; // Set clock bit on next out
uint8_t *d2 = dest; // Incremented per-pixel across all tiles
// Work from bottom tile to top, because data is issued in that order
for (int8_t tile = abs(core->tile) - 1; tile >= 0; tile--) {
const uint16_t *upperSrc, *lowerSrc; // Canvas scanline pointers
int16_t srcIdx;
int8_t srcInc;
// Source pointer to tile's upper-left pixel
const uint16_t *srcTileUL =
source + tile * width * core->numRowPairs * 2;
if ((tile & 1) && (core->tile < 0)) {
// Special handling for serpentine tiles
lowerSrc = srcTileUL + width * (core->numRowPairs - 1 - row);
upperSrc = lowerSrc + width * core->numRowPairs;
srcIdx = width - 1; // Work right to left
srcInc = -1;
} else {
// Progressive tile
upperSrc = srcTileUL + width * row; // Top row
lowerSrc = upperSrc + width * core->numRowPairs; // Bottom row
srcIdx = 0; // Left to right
srcInc = 1;
}
for (uint16_t x = 0; x < width; x++, srcIdx += srcInc) {
uint16_t upperRGB = upperSrc[srcIdx]; // Pixel in upper half
uint16_t lowerRGB = lowerSrc[srcIdx]; // Pixel in lower half
uint8_t result = 0;
if (upperRGB & redBit)
result |= pinMask[0];
if (upperRGB & greenBit)
result |= pinMask[1];
if (upperRGB & blueBit)
result |= pinMask[2];
if (lowerRGB & redBit)
result |= pinMask[3];
if (lowerRGB & greenBit)
result |= pinMask[4];
if (lowerRGB & blueBit)
result |= pinMask[5];
// THIS is where toggle format (without toggle reg.) messes up
// #if defined(_PM_portToggleRegister)
#if defined(_PM_USE_TOGGLE_FORMAT)
*d2++ = result ^ prior;
prior = result | clockMask; // Set clock bit on next out
#else
dest[x] = result;
*d2++ = result;
#endif
} // end x
} // end x
} // end tile
greenBit <<= 1;
if (plane || (core->numPlanes < 6)) {
// In most cases red & blue bit scoot 1 left...
@ -956,7 +1089,8 @@ __attribute__((noinline)) void _PM_convert_565_byte(Protomatter_core *core,
redBit = 0b0000100000000000;
blueBit = 0b0000000000000001;
}
#if defined(_PM_portToggleRegister)
// #if defined(_PM_portToggleRegister)
#if defined(_PM_USE_TOGGLE_FORMAT)
// If using bit-toggle register, erase the toggle bit on the
// first element of each bitplane & row pair. The matrix-driving
// interrupt functions correspondingly set the clock low before
@ -967,9 +1101,7 @@ __attribute__((noinline)) void _PM_convert_565_byte(Protomatter_core *core,
dest[-pad] &= ~clockMask; // Negative index is legal & intentional
#endif
dest += bitplaneSize; // Advance one scanline in dest buffer
} // end plane
upperSrc += width; // Advance one scanline in source buffer
lowerSrc += width;
} // end plane
} // end row
}
@ -977,20 +1109,19 @@ __attribute__((noinline)) void _PM_convert_565_byte(Protomatter_core *core,
// matrix chains), or 1 chain with RGB bits not in the same byte (but in the
// same 16-bit word). Some of the comments have been stripped out since it's
// largely the same operation, but changes are noted.
// WORD OUTPUT IS UNTESTED AND ROW TILING MAY ESPECIALLY PRESENT ISSUES.
void _PM_convert_565_word(Protomatter_core *core, uint16_t *source,
uint16_t width) {
uint16_t *upperSrc = source; // Matrix top half
uint16_t *lowerSrc = source + width * core->numRowPairs; // " bottom half
uint16_t *pinMask = (uint16_t *)core->rgbMask; // Pin bitmasks
uint16_t *pinMask = (uint16_t *)core->rgbMask; // Pin bitmasks
uint16_t *dest = (uint16_t *)core->screenData;
if (core->doubleBuffer) {
dest += core->bufferSize / core->bytesPerElement * (1 - core->activeBuffer);
}
// Size of 1 plane of row pair (across full chain / tile set)
uint32_t bitplaneSize =
_PM_chunkSize *
((width + (_PM_chunkSize - 1)) / _PM_chunkSize); // 1 plane of row pair
uint8_t pad = bitplaneSize - width; // Start-of-plane pad
_PM_chunkSize * ((core->chainBits + (_PM_chunkSize - 1)) / _PM_chunkSize);
uint8_t pad = bitplaneSize - core->chainBits; // Plane-start pad
uint32_t initialRedBit, initialGreenBit, initialBlueBit;
if (core->numPlanes == 6) {
@ -1009,7 +1140,8 @@ void _PM_convert_565_word(Protomatter_core *core, uint16_t *source,
// register exists, "clear" really means the clock mask is set in all
// but the first element on a scanline (per bitplane). If no toggle
// register, can just zero everything out.
#if defined(_PM_portToggleRegister)
// #if defined(_PM_portToggleRegister)
#if defined(_PM_USE_TOGGLE_FORMAT)
// No per-chain loop is required; one clock bit handles all chains
uint32_t offset = 0; // Current position in the 'dest' buffer
uint16_t mask = core->clockMask >> (core->portOffset * 16);
@ -1027,47 +1159,76 @@ void _PM_convert_565_word(Protomatter_core *core, uint16_t *source,
dest += pad; // Pad value is in 'elements,' not bytes, so this is OK
// After a set of rows+bitplanes are processed, upperSrc and lowerSrc
// have advanced halfway down one matrix. This offset is used after
// each chain to advance them to the start/middle of the next matrix.
uint32_t halfMatrixOffset = width * core->numPlanes * core->numRowPairs;
for (uint8_t chain = 0; chain < core->parallel; chain++) {
for (uint8_t row = 0; row < core->numRowPairs; row++) {
uint32_t redBit = initialRedBit;
uint32_t greenBit = initialGreenBit;
uint32_t blueBit = initialBlueBit;
for (uint8_t plane = 0; plane < core->numPlanes; plane++) {
#if defined(_PM_portToggleRegister)
// #if defined(_PM_portToggleRegister)
#if defined(_PM_USE_TOGGLE_FORMAT)
// Since we're ORing in bits over an existing clock bit,
// prior is 0 rather than clockMask as in the byte case.
uint16_t prior = 0;
#endif
for (uint16_t x = 0; x < width; x++) {
uint16_t upperRGB = upperSrc[x]; // Pixel in upper half
uint16_t lowerRGB = lowerSrc[x]; // Pixel in lower half
uint16_t result = 0;
if (upperRGB & redBit)
result |= pinMask[0];
if (upperRGB & greenBit)
result |= pinMask[1];
if (upperRGB & blueBit)
result |= pinMask[2];
if (lowerRGB & redBit)
result |= pinMask[3];
if (lowerRGB & greenBit)
result |= pinMask[4];
if (lowerRGB & blueBit)
result |= pinMask[5];
uint16_t *d2 = dest; // Incremented per-pixel across all tiles
// Work from bottom tile to top, because data is issued in that order
for (int8_t tile = abs(core->tile) - 1; tile >= 0; tile--) {
uint16_t *upperSrc, *lowerSrc; // Canvas scanline pointers
int16_t srcIdx;
int8_t srcInc;
// Source pointer to tile's upper-left pixel
uint16_t *srcTileUL = source + (chain * abs(core->tile) + tile) *
width * core->numRowPairs * 2;
if ((tile & 1) && (core->tile < 0)) {
// Special handling for serpentine tiles
lowerSrc = srcTileUL + width * (core->numRowPairs - 1 - row);
upperSrc = lowerSrc + width * core->numRowPairs;
srcIdx = width - 1; // Work right to left
srcInc = -1;
} else {
// Progressive tile
upperSrc = srcTileUL + width * row; // Top row
lowerSrc = upperSrc + width * core->numRowPairs; // Bottom row
srcIdx = 0; // Left to right
srcInc = 1;
}
for (uint16_t x = 0; x < width; x++, srcIdx += srcInc) {
uint16_t upperRGB = upperSrc[srcIdx]; // Pixel in upper half
uint16_t lowerRGB = lowerSrc[srcIdx]; // Pixel in lower half
uint16_t result = 0;
if (upperRGB & redBit) {
result |= pinMask[0];
}
if (upperRGB & greenBit) {
result |= pinMask[1];
}
if (upperRGB & blueBit) {
result |= pinMask[2];
}
if (lowerRGB & redBit) {
result |= pinMask[3];
}
if (lowerRGB & greenBit) {
result |= pinMask[4];
}
if (lowerRGB & blueBit) {
result |= pinMask[5];
}
// Main difference here vs byte converter is each chain
// ORs new bits into place (vs single-pass overwrite).
#if defined(_PM_portToggleRegister)
dest[x] |= result ^ prior; // Bitwise OR
prior = result;
// #if defined(_PM_portToggleRegister)
#if defined(_PM_USE_TOGGLE_FORMAT)
*d2++ |= result ^ prior; // Bitwise OR
prior = result;
#else
dest[x] |= result; // Bitwise OR
*d2++ |= result; // Bitwise OR
#endif
} // end x
} // end x
} // end tile
greenBit <<= 1;
if (plane || (core->numPlanes < 6)) {
redBit <<= 1;
@ -1077,33 +1238,28 @@ void _PM_convert_565_word(Protomatter_core *core, uint16_t *source,
blueBit = 0b0000000000000001;
}
dest += bitplaneSize; // Advance one scanline in dest buffer
} // end plane
upperSrc += width; // Advance one scanline in source buffer
lowerSrc += width;
} // end row
pinMask += 6; // Next chain's RGB pin masks
upperSrc += halfMatrixOffset; // Advance to next matrix start pos
lowerSrc += halfMatrixOffset;
} // end plane
} // end row
pinMask += 6; // Next chain's RGB pin masks
}
}
// Corresponding function for long output -- either several parallel chains
// (up to 5), or 1 chain with RGB bits scattered widely about the PORT.
// Same deal, comments are pared back, see above functions for explanations.
// LONG OUTPUT IS UNTESTED AND ROW TILING MAY ESPECIALLY PRESENT ISSUES.
void _PM_convert_565_long(Protomatter_core *core, uint16_t *source,
uint16_t width) {
uint16_t *upperSrc = source; // Matrix top half
uint16_t *lowerSrc = source + width * core->numRowPairs; // " bottom half
uint32_t *pinMask = (uint32_t *)core->rgbMask; // Pin bitmasks
uint32_t *pinMask = (uint32_t *)core->rgbMask; // Pin bitmasks
uint32_t *dest = (uint32_t *)core->screenData;
if (core->doubleBuffer) {
dest += core->bufferSize / core->bytesPerElement * (1 - core->activeBuffer);
}
// Size of 1 plane of row pair (across full chain / tile set)
uint32_t bitplaneSize =
_PM_chunkSize *
((width + (_PM_chunkSize - 1)) / _PM_chunkSize); // 1 plane of row pair
uint8_t pad = bitplaneSize - width; // Start-of-plane pad
_PM_chunkSize * ((core->chainBits + (_PM_chunkSize - 1)) / _PM_chunkSize);
uint8_t pad = bitplaneSize - core->chainBits; // Plane-start pad
uint32_t initialRedBit, initialGreenBit, initialBlueBit;
if (core->numPlanes == 6) {
@ -1117,7 +1273,8 @@ void _PM_convert_565_long(Protomatter_core *core, uint16_t *source,
initialBlueBit = 0b0000000000000001 << shiftLeft;
}
#if defined(_PM_portToggleRegister)
// #if defined(_PM_portToggleRegister)
#if defined(_PM_USE_TOGGLE_FORMAT)
// No per-chain loop is required; one clock bit handles all chains
uint32_t offset = 0; // Current position in the 'dest' buffer
for (uint8_t row = 0; row < core->numRowPairs; row++) {
@ -1134,42 +1291,74 @@ void _PM_convert_565_long(Protomatter_core *core, uint16_t *source,
dest += pad; // Pad value is in 'elements,' not bytes, so this is OK
uint32_t halfMatrixOffset = width * core->numPlanes * core->numRowPairs;
for (uint8_t chain = 0; chain < core->parallel; chain++) {
for (uint8_t row = 0; row < core->numRowPairs; row++) {
uint32_t redBit = initialRedBit;
uint32_t greenBit = initialGreenBit;
uint32_t blueBit = initialBlueBit;
for (uint8_t plane = 0; plane < core->numPlanes; plane++) {
#if defined(_PM_portToggleRegister)
// #if defined(_PM_portToggleRegister)
#if defined(_PM_USE_TOGGLE_FORMAT)
uint32_t prior = 0;
#endif
for (uint16_t x = 0; x < width; x++) {
uint16_t upperRGB = upperSrc[x]; // Pixel in upper half
uint16_t lowerRGB = lowerSrc[x]; // Pixel in lower half
uint32_t result = 0;
if (upperRGB & redBit)
result |= pinMask[0];
if (upperRGB & greenBit)
result |= pinMask[1];
if (upperRGB & blueBit)
result |= pinMask[2];
if (lowerRGB & redBit)
result |= pinMask[3];
if (lowerRGB & greenBit)
result |= pinMask[4];
if (lowerRGB & blueBit)
result |= pinMask[5];
uint32_t *d2 = dest; // Incremented per-pixel across all tiles
// Work from bottom tile to top, because data is issued in that order
for (int8_t tile = abs(core->tile) - 1; tile >= 0; tile--) {
uint16_t *upperSrc, *lowerSrc; // Canvas scanline pointers
int16_t srcIdx;
int8_t srcInc;
// Source pointer to tile's upper-left pixel
uint16_t *srcTileUL = source + (chain * abs(core->tile) + tile) *
width * core->numRowPairs * 2;
if ((tile & 1) && (core->tile < 0)) {
// Special handling for serpentine tiles
lowerSrc = srcTileUL + width * (core->numRowPairs - 1 - row);
upperSrc = lowerSrc + width * core->numRowPairs;
srcIdx = width - 1; // Work right to left
srcInc = -1;
} else {
// Progressive tile
upperSrc = srcTileUL + width * row; // Top row
lowerSrc = upperSrc + width * core->numRowPairs; // Bottom row
srcIdx = 0; // Left to right
srcInc = 1;
}
for (uint16_t x = 0; x < width; x++, srcIdx += srcInc) {
uint16_t upperRGB = upperSrc[srcIdx]; // Pixel in upper half
uint16_t lowerRGB = lowerSrc[srcIdx]; // Pixel in lower half
uint32_t result = 0;
if (upperRGB & redBit) {
result |= pinMask[0];
}
if (upperRGB & greenBit) {
result |= pinMask[1];
}
if (upperRGB & blueBit) {
result |= pinMask[2];
}
if (lowerRGB & redBit) {
result |= pinMask[3];
}
if (lowerRGB & greenBit) {
result |= pinMask[4];
}
if (lowerRGB & blueBit) {
result |= pinMask[5];
}
// Main difference here vs byte converter is each chain
// ORs new bits into place (vs single-pass overwrite).
#if defined(_PM_portToggleRegister)
dest[x] |= result ^ prior; // Bitwise OR
prior = result;
// #if defined(_PM_portToggleRegister)
#if defined(_PM_USE_TOGGLE_FORMAT)
*d2++ |= result ^ prior; // Bitwise OR
prior = result;
#else
dest[x] |= result; // Bitwise OR
*d2++ |= result; // Bitwise OR
#endif
} // end x
} // end x
} // end tile
greenBit <<= 1;
if (plane || (core->numPlanes < 6)) {
redBit <<= 1;
@ -1179,13 +1368,9 @@ void _PM_convert_565_long(Protomatter_core *core, uint16_t *source,
blueBit = 0b0000000000000001;
}
dest += bitplaneSize; // Advance one scanline in dest buffer
} // end plane
upperSrc += width; // Advance one scanline in source buffer
lowerSrc += width;
} // end row
pinMask += 6; // Next chain's RGB pin masks
upperSrc += halfMatrixOffset; // Advance to next matrix start pos
lowerSrc += halfMatrixOffset;
} // end plane
} // end row
pinMask += 6; // Next chain's RGB pin masks
}
}
@ -1205,18 +1390,22 @@ void _PM_convert_565(Protomatter_core *core, uint16_t *source, uint16_t width) {
#endif // END ARDUINO || CIRCUITPY
// Note to future self: I've gone back and forth between implementing all
// this either as it currently is (with byte, word and long cases for various
// steps), or using a uint32_t[64] table for expanding RGB bit combos to PORT
// bit combos. The latter would certainly simplify the code a ton, and the
// additional table lookup step wouldn't significantly impact performance,
// especially going forward with faster processors (the SAMD51 code already
// requires a few NOPs in the innermost loop to avoid outpacing the matrix).
// BUT, the reason this is NOT currently done is that it only allows for a
// single matrix chain (doing parallel chains would require either an
// impractically large lookup table, or adding together multiple tables'
// worth of bitmasks, which would slow things down in the vital inner loop).
// Although parallel matrix chains aren't yet 100% implemented in this code
// right now, I wanted to leave that possibility for the future, as a way to
// handle larger matrix combos, because long chains will slow down the
// refresh rate.
/* NOTES TO FUTURE SELF ----------------------------------------------------
ON BYTES, WORDS and LONGS:
I've gone back and forth between implementing all this either as it
currently is (with byte, word and long cases for various steps), or using
a uint32_t[64] table for expanding RGB bit combos to PORT bit combos.
The latter would certainly simplify the code a ton, and the additional
table lookup step wouldn't significantly impact performance, especially
going forward with faster processors (several devices already require a
few NOPs in the innermost loop to avoid outpacing the matrix).
BUT, the reason this is NOT currently done is that it only allows for a
single matrix chain (doing parallel chains would require either an
impractically large lookup table, or adding together multiple tables'
worth of bitmasks, which would slow things down in the vital inner loop).
Although parallel matrix chains aren't yet 100% implemented in this code
right now, I wanted to leave that possibility for the future, as a way to
handle larger matrix combos, because long chains will slow down the
refresh rate.
*/

View file

@ -72,7 +72,8 @@ typedef struct {
uint32_t bitZeroPeriod; ///< Bitplane 0 timer period
uint32_t minPeriod; ///< Plane 0 timer period for ~250Hz
volatile uint32_t frameCount; ///< For estimating refresh rate
uint16_t width; ///< Matrix chain width in bits
uint16_t width; ///< Matrix chain width only in bits
uint16_t chainBits; ///< Matrix chain width*tiling in bits
uint8_t bytesPerElement; ///< Using 8, 16 or 32 bits of PORT?
uint8_t clockPin; ///< RGB clock pin identifier
uint8_t parallel; ///< Number of concurrent matrix outs
@ -80,6 +81,7 @@ typedef struct {
uint8_t portOffset; ///< Active 8- or 16-bit pos. in PORT
uint8_t numPlanes; ///< Display bitplanes (1 to 6)
uint8_t numRowPairs; ///< Addressable row pairs
int8_t tile; ///< Vertical tiling repetitions
bool doubleBuffer; ///< 2X buffers for clean switchover
bool singleAddrPort; ///< If 1, all addr lines on same PORT
volatile uint8_t activeBuffer; ///< Index of currently-displayed buf
@ -135,6 +137,14 @@ typedef struct {
@param doubleBuffer If true, two matrix buffers are allocated,
so changing display contents doesn't introduce
artifacts mid-conversion. Requires ~2X RAM.
@param tile If multiple matrices are chained and stacked
vertically (rather than or in addition to
horizontally), the number of vertical tiles is
specified here. Positive values indicate a
"progressive" arrangement (always left-to-right),
negative for a "serpentine" arrangement (alternating
180 degree orientation). Horizontal tiles are implied
in the 'bitWidth' argument.
@param timer Pointer to timer peripheral or timer-related
struct (architecture-dependent), or NULL to
use a default timer ID (also arch-dependent).
@ -152,7 +162,7 @@ extern ProtomatterStatus _PM_init(Protomatter_core *core, uint16_t bitWidth,
uint8_t *rgbList, uint8_t addrCount,
uint8_t *addrList, uint8_t clockPin,
uint8_t latchPin, uint8_t oePin,
bool doubleBuffer, void *timer);
bool doubleBuffer, int8_t tile, void *timer);
/*!
@brief Allocate display buffers and populate additional elements of a
@ -198,6 +208,11 @@ extern void _PM_deallocate(Protomatter_core *core);
*/
extern void _PM_row_handler(Protomatter_core *core);
// *********************************************************************
// NOTE: AS OF 1.3.0, TIMER-RELATED FUNCTIONS REQUIRE A Protomatter_core
// STRUCT POINTER, RATHER THAN A void* TIMER-RELATED POINTER.
// *********************************************************************
/*!
@brief Returns current value of frame counter and resets its value to
zero. Two calls to this, timed one second apart (or use math with
@ -211,30 +226,27 @@ extern uint32_t _PM_getFrameCount(Protomatter_core *core);
/*!
@brief Start (or restart) a timer/counter peripheral.
@param tptr Pointer to timer/counter peripheral OR a struct
encapsulating information about a timer/counter
periph (architecture-dependent).
@param core Pointer to Protomatter core structure, from which timer
details can be derived.
@param period Timer 'top' / rollover value.
*/
extern void _PM_timerStart(void *tptr, uint32_t period);
extern void _PM_timerStart(Protomatter_core *core, uint32_t period);
/*!
@brief Stop timer/counter peripheral.
@param tptr Pointer to timer/counter peripheral OR a struct
encapsulating information about a timer/counter
periph (architecture-dependent).
@param core Pointer to Protomatter core structure, from which timer
details can be derived.
@return Counter value when timer was stopped.
*/
extern uint32_t _PM_timerStop(void *tptr);
extern uint32_t _PM_timerStop(Protomatter_core *core);
/*!
@brief Query a timer/counter peripheral's current count.
@param tptr Pointer to timer/counter peripheral OR a struct
encapsulating information about a timer/counter
periph (architecture-dependent).
@param core Pointer to Protomatter core structure, from which timer
details can be derived.
@return Counter value.
*/
extern uint32_t _PM_timerGetCount(void *tptr);
extern uint32_t _PM_timerGetCount(Protomatter_core *core);
/*!
@brief Pauses until the next vertical blank to avoid 'tearing' animation
@ -243,6 +255,16 @@ extern uint32_t _PM_timerGetCount(void *tptr);
*/
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)
/*!