Compare commits

..

140 commits

Author SHA1 Message Date
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
Phillip Burgess
92be2a6863 clang-format fixes 2020-10-09 10:53:51 -07:00
Phillip Burgess
df57adc40b Minor version bump 2020-10-09 10:38:25 -07:00
Phillip Burgess
6dfe2ed3a0 animated_gif example: fix misplaced #endif, add comment about supported boards 2020-10-08 21:56:40 -07:00
Phillip Burgess
1257af6e90 Clean up comments, redundant declarations, loose ends 2020-10-08 20:29:24 -07:00
Phillip Burgess
d980bc15a0 Handle rotation in the GIF player 2020-10-08 15:55:17 -07:00
Phillip Burgess
9fb6a33975 Reorganize source (WIP) 2020-10-06 21:20:34 -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
Phillip Burgess
1ea4f5c61e Add a .test.only file for the animated_gif example 2020-10-06 10:17:49 -07:00
Phillip Burgess
ff3cbc63d0 Update library dependencies for AnimatedGIF example 2020-10-06 09:35:41 -07:00
Phillip Burgess
8f7ac498eb Sundry cleanup & simplification in animated_gif example 2020-10-06 09:28:41 -07:00
Phillip Burgess
9bf705cc9c Add BACK/NEXT button defines 2020-10-05 20:29:55 -07:00
Phillip Burgess
214511324a AnimagedGIF cycles among GIFs, does centering/clipping 2020-10-05 20:26:33 -07:00
Phillip Burgess
2c02ee9cb0 clang-formatted and more comments in animaged_gif example 2020-10-02 21:35:13 -07:00
Phillip Burgess
7114b9ede8 Add animated GIF example (uses AnimatedGIF lib) 2020-10-02 21:03:38 -07:00
Melissa LeBlanc-Williams
5f07ec6188 Bumping version for release 2020-09-30 10:54:11 -06:00
Melissa LeBlanc-Williams
60be539469
Merge pull request #21 from makermelissa/master
Added Pixeldust example for 64x64 matrix
2020-09-30 10:53:04 -06:00
Melissa LeBlanc-Williams
4395e82558 Added Pixeldust example for 64x64 matrix 2020-09-30 10:31:02 -06:00
Limor "Ladyada" Fried
2960fd2186
Merge pull request #20 from makermelissa/master
Added pixeldust demo
2020-09-24 19:36:35 -04:00
Melissa LeBlanc-Williams
52b8af0067 Clang formatted 2020-09-24 16:25:19 -06:00
Melissa LeBlanc-Williams
066de2063c Added PixelDust as dependency for example 2020-09-24 16:00:55 -06:00
Melissa LeBlanc-Williams
803799c368 Added LIS3DH as dependency for example 2020-09-24 15:57:40 -06:00
Melissa LeBlanc-Williams
43520c2fac Added pixeldust demo 2020-09-24 15:50:37 -06:00
Paint Your Dragon
ca711ce00f
Merge pull request #18 from jepler/fix-cpy-3184
_PM_stop: Don't HardFault when _PM_begin failed
2020-08-31 13:27:34 -07:00
2a1ba8fa47 _PM_stop: Don't HardFault when _PM_begin failed
When _PM_begin fails, it leaves the object in a partially-uninitialized
state.  To recover any allocated storage, we need to call _PM_free;
but if the allocation of core->screenData failed, then other internal
pointers such as core->oe.setReg are not set, and the actions in
_PM_stop will cause a HardFault.

This is a partial fix for adafruit/circuitpython#3184.
2020-08-28 11:03:43 -05:00
Jeff Epler
f257b3234f
Merge pull request #14 from hierophect/cpy-timer-allocator
STM32: switch to timer allocator
2020-08-13 10:13:07 -05:00
Lucian Copeland
761d6437e8 Fix NRF compile warning issue 2020-07-16 12:03:04 -04:00
hierophect
4c8bed2303
Merge branch 'master' into cpy-timer-allocator 2020-07-13 17:25:47 -04:00
Lucian Copeland
07015d5cb5 cpy-timer-allocator 2020-07-13 17:24:37 -04:00
Phillip Burgess
00e00ee8ac Teensy4: change one delayMicroseconds() to _PM_delayMicroseconds() 2020-07-13 10:16:01 -07:00
Paint Your Dragon
0651691425
Merge pull request #12 from adafruit/teensy4
Initial Teensy 4.0/4.1 support
2020-05-23 15:40:40 -07:00
Phillip Burgess
af5c045b6b Bump version # for initial Teensy 4 support 2020-05-23 14:18:11 -07:00
Phillip Burgess
017ba00ab4 Teensy 4 adjustments
See recent issue (roadmap) about dealing with timing better in the future
2020-05-22 19:38:00 -07:00
Phillip Burgess
8c53b2515c Teensy 4.1 WIP (works, but broke other boards) 2020-05-21 18:19:39 -07:00
Phillip Burgess
7daa236e41 Add Teensy4 pinout to doublebuffer example 2020-05-20 11:40:31 -07:00
Phillip Burgess
2b4489d809 clang-format let's try that again 2020-05-19 18:19:22 -07:00
Phillip Burgess
0bb0dea200 clang-format fix 2020-05-19 18:14:35 -07:00
Phillip Burgess
9b015cecd6 Add 2- and 4-way PEW unrolling 2020-05-19 17:52:31 -07:00
Phillip Burgess
5a8428975c Change nRF pinout for new FeatherWing 2020-05-18 16:28:02 -07:00
Phillip Burgess
f0ea03cecd Inexplicable delay for Teensy 4 2020-05-15 20:14:30 -07:00
Phillip Burgess
a3efbfbfdc Moar Teensy 4 WIP 2020-05-15 20:08:34 -07:00
Phillip Burgess
60414a4d0d Teensy 4 getting closer 2020-05-15 17:35:10 -07:00
Phillip Burgess
3e64c1e127 Teensy 4 WIP (timer runs, IO needs work) 2020-05-15 13:30:27 -07:00
Phillip Burgess
8c8d096f15 Teensy 4 timer WIP (compiles, doesn't work yet) 2020-05-15 13:24:24 -07:00
Phillip Burgess
b135ae26e5 Teensy bit of Teensy progress, WIP 2020-05-14 17:21:17 -07:00
Phillip Burgess
2ad6f03357 Some very early initial i.MX work 2020-05-14 15:23:37 -07:00
Limor "Ladyada" Fried
f27da0d358
Merge pull request #9 from adafruit/actionsci
Switch to Actions CI
2020-05-11 19:29:22 -04:00
Phillip Burgess
aa8411cc30 Second try at githubci.yml platforms 2020-05-11 16:20:04 -07:00
Phillip Burgess
e38c872cca Fix(?) build platforms for Actions 2020-05-11 15:38:12 -07:00
Phillip Burgess
e9db308764 Actions conversion 2020-05-11 15:21:34 -07:00
Paint Your Dragon
ac1b8e07b7
Merge pull request #7 from jepler/circuitpython-build-fix
fix circuitpython build error
2020-05-08 17:03:38 -07:00
Paint Your Dragon
63b4df7435
Merge pull request #8 from adafruit/esp32
ESP32 support, tested, works
2020-05-08 17:00:38 -07:00
Phillip Burgess
9f88b5a74c Update doublebuffer demo for ESP32 2020-05-08 16:37:22 -07:00
Phillip Burgess
7dbdf19c1d ESP32 fixes (looks OK on scope, untested on matrix) 2020-05-08 14:04:02 -07:00
Phillip Burgess
d405666d8a ESP32 pin notes 2020-05-08 09:37:55 -07:00
3a817d6057 fix circuitpython build error
In a51676db5e ("clang-format the lot"), headers were re-ordered.
This is not a change that makes no difference, and caused CircuitPython
to stop building:

../../lib/protomatter/arch.h: In function '_PM_convert_565_word':
../../lib/protomatter/arch.h:1114:3: error: implicit declaration of function 'memset' [-Werror=implicit-function-declaration]
 1114 |   memset(dest, 0, core->bufferSize);
      |   ^~~~~~
../../lib/protomatter/arch.h:1114:3: error: incompatible implicit declaration of built-in function 'memset' [-Werror]
../../lib/protomatter/arch.h:147:1: note: include '<string.h>' or provide a declaration of 'memset'

However, the real fix is to include in arch.h any header that MUST have its
declarations visible for the header to be self-supporting; do so.
2020-05-07 18:28:55 -05:00
Phillip Burgess
160dd0af4b ESP32 WIP, using higher-level timer funcs 2020-05-07 13:48:15 -07:00
Phillip Burgess
597c56e658 ESP32 WIP continues 2020-05-06 10:44:51 -07:00
Phillip Burgess
78d4c9bb15 ESP32 WIP 2020-05-05 21:23:18 -07:00
Phillip Burgess
4cbf99aa54 Fix clang-format detail 2020-04-30 15:35:13 -07:00
Phillip Burgess
a51676db5e clang-format the lot 2020-04-30 15:30:24 -07:00
Phillip Burgess
40cb6a9cac Use TC3 on SAMD's that lack a TC4 2020-04-30 15:18:18 -07:00
Phillip Burgess
87a95379b5 Create .travis.yml 2020-04-30 15:04:41 -07:00
Phillip Burgess
8968259c14 Doxygenate 2020-04-30 13:53:15 -07:00
Phillip Burgess
fc180e4689 Update README.md for Travis and library.properties for version # 2020-04-30 10:26:21 -07:00
Phillip Burgess
9f71088d2c Update core.c 2020-04-30 10:00:07 -07:00
Paint Your Dragon
973de07bfc
Merge pull request #6 from jepler/port-stm32f405
Port  to stm32f405
2020-04-30 09:49:18 -07:00
1b8f461f64 Port to STM32F405, other stm-series micros likely to follow
Testing performed: With the STM32F405 Feather, all expected pins waggle
on a logic probe.  I didn't hook up an actual matrix yet.
2020-04-30 08:55:53 -05:00
241bf2516c circuitpython: fix nRF, samd byte/word offset macros
Before, these would only work properly with pins from the first
GPIO bank.
2020-04-30 08:55:53 -05:00
41a92d7731 Allow GPIO registers to have a size _other than_ 32 bits, for stm32
stm32's GPIO set/clear registers are effectively 16 bits big,
unlike other platforms encountered so far.

Introduce macros to set/clear registers, and use them.

Later, stm32 will introduce its own definition of the _PM_PORT_TYPE macro
as uint16_t.

Testing performed: compile-tested on nRF.
2020-04-30 08:55:53 -05:00
Phillip Burgess
fac7eea645 Arg fix in .cpp for recent refactor 2020-04-19 10:54:34 -07:00
Paint Your Dragon
3bc6f23541
Merge pull request #2 from jepler/circuitpython
Port to Circuitpython
2020-04-13 10:32:35 -07:00
c411714cbd circuitpython: nrf port 2020-04-01 11:57:16 -05:00
c3a3e35731 Factor out _PM_convert_565, _PM_swapbuffer_maybe 2020-03-20 10:12:32 -05:00
7f448357d0 Merge remote-tracking branch 'origin/master' into circuitpython 2020-03-20 10:04:21 -05:00
969672dff1 Adapt to CircuitPython / samd51 2020-03-20 10:02:57 -05:00
Paint Your Dragon
5a1151db28
Merge pull request #4 from adafruit/nrf52
Initial nRF52 support, tested on 64x32 matrix
2020-03-17 17:38:42 -07:00
Phillip Burgess
e1659f270c nRF52840 tested and WORKING 2020-03-17 17:36:46 -07:00
Phillip Burgess
20c3b67bc3 nRF52 seems to work with some caveats
Plane-zero interval is hardcoded for now. Haven’t connected to matrix yet, just looking on scope.
2020-03-17 15:18:52 -07:00
Phillip Burgess
103ea89935 nRF52: timer oopsie + pin sudoku 2020-03-17 13:12:13 -07:00
Phillip Burgess
51f39d9594 More nRF fiddling (not yet working) 2020-03-17 12:33:12 -07:00
Paint Your Dragon
0958b759a5
Merge pull request #3 from adafruit/nrf52
SAMD21 fixes & cleanup, initial nRF52 attempt (not yet working)
2020-03-16 22:35:32 -07:00
Phillip Burgess
97c2122ba7 nRF timer almost starting to semi-work 2020-03-16 22:31:33 -07:00
Phillip Burgess
250675d2fc Pin changes for nRF52 2020-03-16 16:37:52 -07:00
Phillip Burgess
831a253898 nRF, initial GPIO stuff, very WIP 2020-03-16 15:49:58 -07:00
32 changed files with 4800 additions and 2009 deletions

46
.github/ISSUE_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,46 @@
Thank you for opening an issue on an Adafruit Arduino library repository. To
improve the speed of resolution please review the following guidelines and
common troubleshooting steps below before creating the issue:
- **Do not use GitHub issues for troubleshooting projects and issues.** Instead use
the forums at http://forums.adafruit.com to ask questions and troubleshoot why
something isn't working as expected. In many cases the problem is a common issue
that you will more quickly receive help from the forum community. GitHub issues
are meant for known defects in the code. If you don't know if there is a defect
in the code then start with troubleshooting on the forum first.
- **If following a tutorial or guide be sure you didn't miss a step.** Carefully
check all of the steps and commands to run have been followed. Consult the
forum if you're unsure or have questions about steps in a guide/tutorial.
- **For Arduino projects check these very common issues to ensure they don't apply**:
- For uploading sketches or communicating with the board make sure you're using
a **USB data cable** and **not** a **USB charge-only cable**. It is sometimes
very hard to tell the difference between a data and charge cable! Try using the
cable with other devices or swapping to another cable to confirm it is not
the problem.
- **Be sure you are supplying adequate power to the board.** Check the specs of
your board and plug in an external power supply. In many cases just
plugging a board into your computer is not enough to power it and other
peripherals.
- **Double check all soldering joints and connections.** Flakey connections
cause many mysterious problems. See the [guide to excellent soldering](https://learn.adafruit.com/adafruit-guide-excellent-soldering/tools) for examples of good solder joints.
- **Ensure you are using an official Arduino or Adafruit board.** We can't
guarantee a clone board will have the same functionality and work as expected
with this code and don't support them.
If you're sure this issue is a defect in the code and checked the steps above
please fill in the following fields to provide enough troubleshooting information.
You may delete the guideline and text above to just leave the following details:
- Arduino board: **INSERT ARDUINO BOARD NAME/TYPE HERE**
- Arduino IDE version (found in Arduino -> About Arduino menu): **INSERT ARDUINO
VERSION HERE**
- List the steps to reproduce the problem below (if possible attach a sketch or
copy the sketch code in too): **LIST REPRO STEPS BELOW**

26
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,26 @@
Thank you for creating a pull request to contribute to Adafruit's GitHub code!
Before you open the request please review the following guidelines and tips to
help it be more easily integrated:
- **Describe the scope of your change--i.e. what the change does and what parts
of the code were modified.** This will help us understand any risks of integrating
the code.
- **Describe any known limitations with your change.** For example if the change
doesn't apply to a supported platform of the library please mention it.
- **Please run any tests or examples that can exercise your modified code.** We
strive to not break users of the code and running tests/examples helps with this
process.
Thank you again for contributing! We will try to test and integrate the change
as soon as we can, but be aware we have many GitHub repositories to manage and
can't immediately respond to every request. There is no need to bump or check in
on a pull request (it will clutter the discussion of the request).
Also don't be worried if the request is closed or not integrated--sometimes the
priorities of Adafruit's GitHub code (education, ease of use) might not match the
priorities of the pull request. Don't fret, the open source community thrives on
forks and GitHub makes it easy to keep your changes in a forked repo.
After reviewing the guidelines above you can delete this text from the pull request.

37
.github/workflows/githubci.yml vendored Normal file
View file

@ -0,0 +1,37 @@
name: Arduino Library CI
on: [pull_request, push, repository_dispatch]
jobs:
build:
strategy:
fail-fast: false
matrix:
arduino-platform: ["metro_m0", "metro_m4", "nrf52840", "esp32"]
runs-on: ubuntu-latest
steps:
- uses: actions/setup-python@v1
with:
python-version: '3.8'
- uses: actions/checkout@v2
- uses: actions/checkout@v2
with:
repository: adafruit/ci-arduino
path: ci
- name: pre-install
run: bash ci/actions_install.sh
- name: test platforms
run: python3 ci/build_platform.py ${{ matrix.arduino-platform }}
- name: clang
run: python3 ci/run-clang-format.py -e "ci/*" -e "bin/*" -r .
- name: doxygen
env:
GH_REPO_TOKEN: ${{ secrets.GH_REPO_TOKEN }}
PRETTYNAME : "Adafruit Protomatter"
run: bash ci/doxy_gen_and_deploy.sh

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
# Our handy .gitignore for automation ease
Doxyfile*
doxygen_sqlite3.db
html

View file

@ -1,93 +0,0 @@
// Arduino-specific wrapper for the Protomatter C library (provides
// constructor and so forth, builds on Adafruit_GFX). There should
// not be any device-specific #ifdefs here. See notes in core.c and
// arch.h regarding portability.
#include "Adafruit_Protomatter.h" // Also includes core.h & Adafruit_GFX.h
extern Protomatter_core *_PM_protoPtr; // In core.c (via arch.h)
// Overall matrix refresh rate (frames/second) is a function of matrix width
// and chain length, number of address lines, number of bit planes, CPU speed
// and whether or not a GPIO toggle register is available. There is no "this
// will run at X-frames-per-second" constant figure. You typically just have
// to try it out and perhaps trade off some bit planes for refresh rate until
// the image looks good and stable. Anything over 100 Hz is usually passable,
// around 250 Hz is where things firm up. And while this could proceed higher
// in some situations, the tradeoff is that faster rates use progressively
// more CPU time (because it's timer interrupt based and not using DMA or
// special peripherals). So a throttle is set here, an approximate maximum
// frame rate which the software will attempt to avoid exceeding (but may
// refresh slower than this, and in many cases will...just need to set an
// upper limit to avoid excessive CPU load). An incredibly long comment block
// for a single constant, thank you for coming to my TED talk!
#define _PM_MAX_REFRESH_HZ 250
// Time (in milliseconds) to pause following any change in address lines
// (individually or collectively). Some matrices respond slowly there...
// must pause on change for matrix to catch up. Defined here (rather than
// arch.h) because it's not architecture-specific.
#define _PM_ROW_DELAY 8
Adafruit_Protomatter::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) :
GFXcanvas16(bitWidth, (2 << min(addrCount, 5)) * min(rgbCount, 5)) {
if(bitDepth > 6) bitDepth = 6; // GFXcanvas16 color limit (565)
// Arguments are passed through to the C _PM_init() function which does
// some input validation and minor allocation. Return value is ignored
// because we can't really do anything about it in a C++ constructor.
// 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);
}
Adafruit_Protomatter::~Adafruit_Protomatter(void) {
_PM_free(&core);
_PM_protoPtr = NULL;
}
ProtomatterStatus Adafruit_Protomatter::begin(void) {
_PM_protoPtr = &core;
_PM_begin(&core);
return PROTOMATTER_OK;
}
// Transfer data from GFXcanvas16 to the matrix framebuffer's weird
// internal format. The actual conversion functions referenced below
// are in core.c, reasoning is explained there.
void Adafruit_Protomatter::show(void) {
// Destination address is computed in convert function
// (based on active buffer value, if double-buffering),
// just need to pass in the canvas buffer address and
// width in pixels.
if(core.bytesPerElement == 1) {
_PM_convert_565_byte(&core, getBuffer(), WIDTH);
} else if(core.bytesPerElement == 2) {
_PM_convert_565_word(&core, getBuffer(), WIDTH);
} else {
_PM_convert_565_long(&core, getBuffer(), WIDTH);
}
if(core.doubleBuffer) {
core.swapBuffers = 1;
// To avoid overwriting data on the matrix, don't return
// until the timer ISR has performed the swap at the right time.
while(core.swapBuffers);
}
}
// 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
// the matrix (since this is difficult to estimate beforehand).
uint32_t Adafruit_Protomatter::getFrameCount(void) {
return _PM_getFrameCount(_PM_protoPtr);
}

View file

@ -1,28 +0,0 @@
// Arduino-specific header, accompanies Adafruit_Protomatter.cpp.
// There should not be any device-specific #ifdefs here.
#ifndef _ADAFRUIT_PROTOMATTER_H_
#define _ADAFRUIT_PROTOMATTER_H_
#include "core.h"
#include <Adafruit_GFX.h>
class Adafruit_Protomatter : public GFXcanvas16 {
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);
~Adafruit_Protomatter(void);
ProtomatterStatus begin(void);
void show(void);
uint32_t getFrameCount(void);
private:
Protomatter_core core; // Underlying C struct
void convert_byte(uint8_t *dest); // GFXcanvas16-to-matrix
void convert_word(uint16_t *dest); // conversion functions
void convert_long(uint32_t *dest); // for 8/16/32 bit bufs
};
#endif // _ADAFRUIT_PROTOMATTER_H_

View file

@ -1,4 +1,4 @@
# Adafruit_Protomatter # Adafruit_Protomatter [![Build Status](https://github.com/adafruit/Adafruit_Protomatter/workflows/Arduino%20Library%20CI/badge.svg)](https://github.com/adafruit/Adafruit_Protomatter/actions)
"I used protomatter in the Genesis matrix." - David Marcus, Star Trek III "I used protomatter in the Genesis matrix." - David Marcus, Star Trek III
@ -6,8 +6,6 @@ Code for driving HUB75-style RGB LED matrices, targeted at 32-bit MCUs
using brute-force GPIO (that is, not relying on DMA or other specialized using brute-force GPIO (that is, not relying on DMA or other specialized
peripherals beyond a timer interrupt, goal being portability). peripherals beyond a timer interrupt, goal being portability).
Name might change as it's nondescriptive and tedious to type in code.
# Matrix Concepts and Jargon # Matrix Concepts and Jargon
HUB75 RGB LED matrices are basically a set of six concurrent shift register HUB75 RGB LED matrices are basically a set of six concurrent shift register
@ -41,14 +39,16 @@ help keep up-to-date with any future changes here!
The common ground for architectures to support this library: The common ground for architectures to support this library:
* 32-bit device (e.g. ARM core, but potentially ESP32 and others in future) * 32-bit device (e.g. ARM core, ESP32 and others)
* One or more 32-bit GPIO PORTs with atomic (single-cycle, not * One or more 32-bit GPIO PORTs with atomic (single-cycle, not
read-modify-write) bitmask SET and CLEAR registers. A bitmask TOGGLE read-modify-write) bitmask SET and CLEAR registers. A bitmask TOGGLE
register, if present, may improve performance but is NOT required. register, if present, may improve performance but is NOT required.
* Tolerate 8-bit or word-aligned 16-bit accesses within the 32-bit PORT * There may be performance or storage benefits if the architecture tolerates
registers (e.g. writing just one of four bytes, rather than the whole 8-bit or word-aligned 16-bit accesses within the 32-bit PORT registers
32 bits). The library does not use any unaligned accesses (i.e. the (e.g. writing just one of four bytes, rather than the whole 32 bits), but
"middle word" of a 32-bit register), even if a device tolerates such. this is NOT a hardware requirement. Also, the library does not use any
unaligned accesses (i.e. "middle word" of a 32-bit register), even if a
device tolerates such.
# Software Components # Software Components
@ -58,14 +58,15 @@ This repository currently consists of:
Adafruit_Protomatter.h, plus the "examples" directory). The Arduino code Adafruit_Protomatter.h, plus the "examples" directory). The Arduino code
is dependent on the Adafruit_GFX library. is dependent on the Adafruit_GFX library.
* An underlying C library (files core.c, core.h and arch.h) that might be * An underlying C library (files core.c, core.h and headers in the arch
adaptable to other runtime environments (e.g. CircuitPython). directory) that might be adaptable to other runtime environments (e.g.
CircuitPython).
# Arduino Library # Arduino Library
This *might* supersede the RGBmatrixPanel library on non-AVR devices, as the This will likely supersede the RGBmatrixPanel library on non-AVR devices, as
older library has painted itself into a few corners. The newer library uses the older library has painted itself into a few corners. The newer library
a single constructor for all matrix setups, potentially handling parallel uses a single constructor for all matrix setups, potentially handling parallel
chains (not yet fully implemented), various matrix sizes and chain lengths, 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 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. these). Note however that it is NOT A DROP-IN REPLACEMENT for RGBmatrixPanel.
@ -93,20 +94,22 @@ drawing functions.
The C code has the same limitations as the Arduino library: all RGB data The C code has the same limitations as the Arduino library: all RGB data
pins and the clock pin MUST be on the same PORT register, and it's most pins and the clock pin MUST be on the same PORT register, and it's most
memory efficient (though still a bit gluttonous) if those pins are all memory efficient (though still slightly gluttonous) if those pins are all
within the same 8-bit byte within the PORT (they do not need to be within the same 8-bit byte within the PORT (they do not need to be
contiguous or sequential within that byte). Other pins (matrix address lines, contiguous or sequential within that byte). Other pins (matrix address lines,
latch and output enable) can reside on any PORT or bit. latch and output enable) can reside on any PORT or bit.
When adapting this code to new devices (e.g. nRF52, ESP32) or new runtime When adapting this code to new devices (e.g. iMX) or new runtime environments
environments (e.g. CircuitPython), goal is to put all the device- or (e.g. CircuitPython), goal is to put all the device- or platform-specific
platform-specific code into the arch.h file (or completely separate source code into a new header file in the arch directory (or completely separate
files, as in the Arduino library .cpp and .h). core.c contains only the source files, as in the Arduino library .cpp and .h). core.c contains only
device-neutral bitbang code and should not have any "#ifdef DEVICE"- or the device-neutral bitbang code and should not have any "#ifdef DEVICE"- or
"#ifdef ENVIRONMENT"-like lines. Macros for things like getting a PORT "#ifdef ENVIRONMENT"-like lines (exception for the 565 color conversion
register address from a pin, or setting up a timer peripheral, all occur functions, since the internal representation is common to both Arduino and
in arch.h, which is ONLY #included by core.c (to prevent problems like CircuitPython). Macros for things like getting a PORT register address from
multiple instances of ISR functions, which must be singularly declared at a pin, or setting up a timer peripheral, all occur in the arch header files,
which are ONLY #included by core.c (to prevent problems like multiple
instances of ISR functions, which must be singularly declared at
compile-time). compile-time).
Most macros and functions begin with the prefix **\_PM\_** in order to Most macros and functions begin with the prefix **\_PM\_** in order to

836
arch.h
View file

@ -1,836 +0,0 @@
// Establishes some very low-level things specific to each supported device.
// This should ONLY be included by core.c, nowhere else. Ever.
#if !defined(_PROTOMATTER_ARCH_H_)
#define _PROTOMATTER_ARCH_H_
/*
Common ground for architectures to support this library:
- 32-bit device (e.g. ARM core, but potentially ESP32 and others in future)
- One or more 32-bit GPIO PORTs with atomic bitmask SET and CLEAR registers.
A TOGGLE register, if present, may improve performance but is NOT required.
- Tolerate 8-bit or word-aligned 16-bit accesses within the 32-bit PORT
registers (e.g. writing just one of four bytes, rather than the whole
32 bits). The library does not use any unaligned accesses (i.e. the
"middle word" of a 32-bit register), even if a device tolerates such.
"Pin" as used in this code is always a uint8_t value, but the semantics
of what it means may vary between Arduino and non-Arduino situations.
In Arduino, it's the pin index one would pass to functions such as
digitalWrite(), and doesn't necessarily correspond to physical hardware
pins or any other arrangement. Some may have names like 'A0' that really
just map to higher indices.
In non-Arduino settings (CircuitPython, other languages, etc.), how a
pin index relates to hardware is entirely implementation dependent, and
how to get from one to the other is what must be implemented in this file.
Quite often an environment will follow the Arduino pin designations
(since the numbers are on a board's silkscreen) and will have an internal
table mapping those indices to registers and bitmasks...but probably not
an identically-named and -structured table to the Arduino code, hence the
reason for many "else" situations in this code.
Each architecture defines the following macros and/or functions (the _PM_
prefix on each is to reduce likelihood of naming collisions...especially
on ESP32, which has some similarly-named timer functions...though note
that this library is NOT CURRENTLY PORTED to ESP32):
GPIO-related macros/functions:
_PM_portOutRegister(pin): Get address of PORT out register. Code calling
this can cast it to whatever type's needed.
_PM_portSetRegister(pin): Get address of PORT set-bits register.
_PM_portClearRegister(pin): Get address of PORT clear-bits register.
_PM_portToggleRegister(pin): Get address of PORT toggle-bits register.
Not all devices support this, in which case
it must be left undefined.
_PM_portBitMask(pin): Get bit mask within PORT register corresponding
to a pin number. When compiling for Arduino,
this just maps to digitalPinToBitMask(), other
environments will need an equivalent.
_PM_byteOffset(pin): Get index of byte (0 to 3) within 32-bit PORT
corresponding to a pin number.
_PM_wordOffset(pin): Get index of word (0 or 1) within 32-bit PORT
corresponding to a pin number.
_PM_pinOutput(pin): Set a pin to output mode. In Arduino this maps
to pinMode(pin, OUTPUT). Other environments
will need an equivalent.
_PM_pinInput(pin): Set a pin to input mode, no pullup. In Arduino
this maps to pinMode(pin, INPUT).
_PM_pinHigh(pin): Set an output pin to a high or 1 state. In
Arduino this maps to digitalWrite(pin, HIGH).
_PM_pinLow(pin): Set an output pin to a low or 0 state. In
Arduino this maps to digitalWrite(pin, LOW).
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).
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
used to uniquely identify a timer peripheral within a given environment. For
example, in the Arduino wrapper for this library, compiling for SAMD chips,
it's just a pointer directly to a timer/counter peripheral base address. If
an implementation needs more data associated alongside a peripheral, this
could instead be a pointer to a struct, or an integer index.
Other macros/functions:
_PM_chunkSize: Matrix bitmap width (both in RAM and as issued
to the device) is rounded up (if necessary) to
a multiple of this value as a way of explicitly
unrolling the innermost data-stuffing loops.
So far all HUB75 displays I've encountered are
a multiple of 32 pixels wide, but in case
something new comes along, or if a larger
unroll actually decreases performance due to
cache size, this can be set to whatever works
best (any additional data is simply shifted
out the other end of the matrix). Default if
unspecified is 8 (e.g. four loop passes on a
32-pixel matrix, eight if 64-pixel). Only
certain chunkSizes are actually implemented,
see .cpp code (avoiding GCC-specific tricks
that would handle arbitrary chunk sizes).
_PM_delayMicroseconds(us): Function or macro to delay some number of
microseconds. For Arduino, this just maps to
delayMicroseconds(). Other environments will
need to provide their own or map to an
an equivalent function.
_PM_clockHoldHigh: Additional code (typically some number of NOPs)
needed to delay the clock fall after RGB data is
written to PORT. Only required on fast devices.
If left undefined, no delay happens.
_PM_clockHoldLow: Additional code (e.g. NOPs) needed to delay
clock rise after writing RGB data to PORT.
No delay if left undefined.
_PM_minMinPeriod: Mininum value for the "minPeriod" class member,
so bit-angle-modulation time always doubles with
each bitplane (else lower bits may be the same).
*/
#if defined(ARDUINO) // If compiling in Arduino IDE...
#include <Arduino.h> // pull in all that stuff.
#define _PM_delayMicroseconds(us) delayMicroseconds(us)
#define _PM_pinOutput(pin) pinMode(pin, OUTPUT)
#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)
// No #else here. In non-Arduino case, declare things in the arch-specific
// sections below...unless other environments provide device-neutral
// functions as above, in which case those could go here (w/#elif).
#endif // end defined(ARDUINO)
// CODE COMMON TO BOTH SAMD51 AND SAMD21 -----------------------------------
#if defined(__SAMD51__) || defined(_SAMD21_)
#if defined(ARDUINO)
// g_APinDescription[] table and pin indices are Arduino specific:
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define _PM_byteOffset(pin) (g_APinDescription[pin].ulPin / 8)
#else
#define _PM_byteOffset(pin) (3 - (g_APinDescription[pin].ulPin / 8))
#endif
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define _PM_wordOffset(pin) (g_APinDescription[pin].ulPin / 16)
#else
#define _PM_wordOffset(pin) (1 - (g_APinDescription[pin].ulPin / 16))
#endif
// Arduino implementation is tied to a specific timer/counter & freq:
#define _PM_TIMER_DEFAULT TC4
#define _PM_IRQ_HANDLER TC4_Handler
#define _PM_timerFreq 48000000
// Partly because IRQs must be declared at compile-time, and partly
// because we know Arduino's already set up one of the GCLK sources
// for 48 MHz.
// 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;
// Timer interrupt service routine
void _PM_IRQ_HANDLER(void) {
// Clear overflow flag:
_PM_TIMER_DEFAULT->COUNT16.INTFLAG.reg = TC_INTFLAG_OVF;
_PM_row_handler(_PM_protoPtr); // In core.c
}
#else
// Non-arduino byte offset macros, timer and ISR work go here.
#endif
// Code below diverges for SAMD51 vs SAMD21, but is still very similar...
// If making a change or bug fix in one, check to see if an equivalent
// change should be made in the other!
#endif // __SAMD51__ || _SAMD21_
// SAMD51-SPECIFIC CODE ----------------------------------------------------
#if defined(__SAMD51__)
#if defined(ARDUINO)
// g_APinDescription[] table and pin indices are Arduino specific:
#define _PM_portOutRegister(pin) \
&PORT->Group[g_APinDescription[pin].ulPort].OUT.reg
#define _PM_portSetRegister(pin) \
&PORT->Group[g_APinDescription[pin].ulPort].OUTSET.reg
#define _PM_portClearRegister(pin) \
&PORT->Group[g_APinDescription[pin].ulPort].OUTCLR.reg
#define _PM_portToggleRegister(pin) \
&PORT->Group[g_APinDescription[pin].ulPort].OUTTGL.reg
#else
// Non-Arduino port register lookups go here
#endif
// Initialize, but do not start, timer
void _PM_timerInit(void *tptr) {
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[] = {
TC0, TC0_IRQn, TC0_GCLK_ID,
TC1, TC1_IRQn, TC1_GCLK_ID,
TC2, TC2_IRQn, TC2_GCLK_ID,
TC3, TC3_IRQn, TC3_GCLK_ID,
TC4, TC4_IRQn, TC4_GCLK_ID,
TC5, TC5_IRQn, TC5_GCLK_ID,
#if defined(TC6)
TC6, TC6_IRQn, TC6_GCLK_ID,
#endif
#if defined(TC7)
TC7, TC7_IRQn, TC7_GCLK_ID,
#endif
#if defined(TC8)
TC8, TC8_IRQn, TC8_GCLK_ID,
#endif
#if defined(TC9)
TC9, TC9_IRQn, TC9_GCLK_ID,
#endif
#if defined(TC10)
TC10, TC10_IRQn, TC10_GCLK_ID,
#endif
#if defined(TC11)
TC11, TC11_IRQn, TC11_GCLK_ID,
#endif
#if defined(TC12)
TC12, TC12_IRQn, TC12_GCLK_ID,
#endif
};
#define NUM_TIMERS (sizeof timer / sizeof timer[0])
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
uint8_t timerNum = 0;
while((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tc)) {
timerNum++;
}
if(timerNum >= NUM_TIMERS) return;
// Feed timer/counter off GCLK1 (already set 48 MHz by Arduino core).
// Sure, SAMD51 can run timers up to F_CPU (e.g. 120 MHz or up to
// 200 MHz with overclocking), but on higher bitplanes (which have
// progressively longer timer periods) I could see this possibly
// exceeding a 16-bit timer, and would have to switch prescalers.
// We don't actually need atomic precision on the timer -- point is
// simply that the period doubles with each bitplane, and this can
// work fine at 48 MHz.
GCLK->PCHCTRL[timer[timerNum].GCLK_ID].bit.CHEN = 0; // Disable
while(GCLK->PCHCTRL[timer[timerNum].GCLK_ID].bit.CHEN); // Wait for it
GCLK_PCHCTRL_Type pchctrl; // Read-modify-store
pchctrl.reg = GCLK->PCHCTRL[timer[timerNum].GCLK_ID].reg;
pchctrl.bit.GEN = GCLK_PCHCTRL_GEN_GCLK1_Val;
pchctrl.bit.CHEN = 1;
GCLK->PCHCTRL[timer[timerNum].GCLK_ID].reg = pchctrl.reg;
while(!GCLK->PCHCTRL[timer[timerNum].GCLK_ID].bit.CHEN);
// Disable timer before configuring it
tc->COUNT16.CTRLA.bit.ENABLE = 0;
while(tc->COUNT16.SYNCBUSY.bit.ENABLE);
// 16-bit counter mode, 1:1 prescale
tc->COUNT16.CTRLA.bit.MODE = TC_CTRLA_MODE_COUNT16;
tc->COUNT16.CTRLA.bit.PRESCALER = TC_CTRLA_PRESCALER_DIV1_Val;
tc->COUNT16.WAVE.bit.WAVEGEN =
TC_WAVE_WAVEGEN_MFRQ_Val; // Match frequency generation mode (MFRQ)
tc->COUNT16.CTRLBCLR.reg = TC_CTRLBCLR_DIR; // Count up
while(tc->COUNT16.SYNCBUSY.bit.CTRLB);
// Overflow interrupt
tc->COUNT16.INTENSET.reg = TC_INTENSET_OVF;
NVIC_DisableIRQ(timer[timerNum].IRQn);
NVIC_ClearPendingIRQ(timer[timerNum].IRQn);
NVIC_SetPriority(timer[timerNum].IRQn, 0); // Top priority
NVIC_EnableIRQ(timer[timerNum].IRQn);
// Timer is configured but NOT enabled by default
}
// 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
tc->COUNT16.COUNT.reg = 0;
while(tc->COUNT16.SYNCBUSY.bit.COUNT);
tc->COUNT16.CC[0].reg = period;
while(tc->COUNT16.SYNCBUSY.bit.CC0);
tc->COUNT16.CTRLA.bit.ENABLE = 1;
while(tc->COUNT16.SYNCBUSY.bit.STATUS);
}
// 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
tc->COUNT16.CTRLBSET.bit.CMD = 0x4; // Sync COUNT
while(tc->COUNT16.CTRLBSET.bit.CMD); // Wait for command
return tc->COUNT16.COUNT.reg;
}
// 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);
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");
#endif
#define _PM_minMinPeriod 160
#endif // end __SAMD51__
// SAMD21-SPECIFIC CODE ----------------------------------------------------
#if defined(_SAMD21_)
#if defined(ARDUINO)
// g_APinDescription[] table and pin indices are Arduino specific:
#define _PM_portOutRegister(pin) \
&PORT_IOBUS->Group[g_APinDescription[pin].ulPort].OUT.reg
#define _PM_portSetRegister(pin) \
&PORT_IOBUS->Group[g_APinDescription[pin].ulPort].OUTSET.reg
#define _PM_portClearRegister(pin) \
&PORT_IOBUS->Group[g_APinDescription[pin].ulPort].OUTCLR.reg
#define _PM_portToggleRegister(pin) \
&PORT_IOBUS->Group[g_APinDescription[pin].ulPort].OUTTGL.reg
#else
// Non-Arduino port register lookups go here
#endif
// Initialize, but do not start, timer
void _PM_timerInit(void *tptr) {
static const struct {
Tc *tc; // -> Timer/counter peripheral base address
IRQn_Type IRQn; // Interrupt number
uint8_t GCM_ID; // GCLK selection ID
} timer[] = {
TC0, TC0_IRQn, GCM_TCC0_TCC1,
TC1, TC1_IRQn, GCM_TCC0_TCC1,
#if defined(TC2)
TC2, TC2_IRQn, GCM_TCC2_TC3,
#endif
#if defined(TC3)
TC3, TC3_IRQn, GCM_TCC2_TC3,
#endif
#if defined(TC4)
TC4, TC4_IRQn, GCM_TC4_TC5,
#endif
};
#define NUM_TIMERS (sizeof timer / sizeof timer[0])
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
uint8_t timerNum = 0;
while((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tc)) {
timerNum++;
}
if(timerNum >= NUM_TIMERS) return;
// Enable GCLK for timer/counter
GCLK->CLKCTRL.reg = (uint16_t)(GCLK_CLKCTRL_CLKEN |
GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(timer[timerNum].GCM_ID));
while(GCLK->STATUS.bit.SYNCBUSY == 1);
// Counter must first be disabled to configure it
tc->COUNT16.CTRLA.bit.ENABLE = 0;
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
tc->COUNT16.CTRLA.reg = // Configure timer counter
TC_CTRLA_PRESCALER_DIV1 | // 1:1 Prescale
TC_CTRLA_WAVEGEN_MFRQ | // Match frequency generation mode (MFRQ)
TC_CTRLA_MODE_COUNT16; // 16-bit counter mode
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
tc->COUNT16.CTRLBCLR.reg = TCC_CTRLBCLR_DIR; // Count up
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
// Overflow interrupt
tc->COUNT16.INTENSET.reg = TC_INTENSET_OVF;
NVIC_DisableIRQ(timer[timerNum].IRQn);
NVIC_ClearPendingIRQ(timer[timerNum].IRQn);
NVIC_SetPriority(timer[timerNum].IRQn, 0); // Top priority
NVIC_EnableIRQ(timer[timerNum].IRQn);
// Timer is configured but NOT enabled by default
}
// 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
tc->COUNT16.COUNT.reg = 0;
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
tc->COUNT16.CC[0].reg = period;
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
tc->COUNT16.CTRLA.bit.ENABLE = 1;
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
}
// 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
tc->COUNT16.READREQ.reg = TC_READREQ_RCONT | TC_READREQ_ADDR(0x10);
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
return tc->COUNT16.COUNT.reg;
}
// 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);
tc->COUNT16.CTRLA.bit.ENABLE = 0;
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
return count;
}
#endif // _SAMD21_
// NRF52-SPECIFIC CODE -----------------------------------------------------
#if defined(NRF52_SERIES)
#endif // NRF52_SERIES
// ESP32-SPECIFIC CODE -----------------------------------------------------
#if defined(ARDUINO_ARCH_ESP32)
#endif // ARDUINO_ARCH_ESP32
// DEFAULTS IF NOT DEFINED ABOVE -------------------------------------------
#if !defined(_PM_chunkSize)
#define _PM_chunkSize 8
#endif
#if !defined(_PM_clockHoldHigh)
#define _PM_clockHoldHigh
#endif
#if !defined(_PM_clockHoldLow)
#define _PM_clockHoldLow
#endif
#if !defined(_PM_minMinPeriod)
#define _PM_minMinPeriod 100
#endif
// ARDUINO SPECIFIC CODE ---------------------------------------------------
#if defined(ARDUINO)
// 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.
// Different runtime environments (which might not use the 565 canvas
// format) will need their own conversion functions.
// There are THREE COPIES of the following function -- one each for byte,
// word and long. If changes are made in any one of them, the others MUST
// be updated to match! Note that they are not simple duplicates of each
// other. The byte case, for example, doesn't need to handle parallel
// matrix chains (matrix data can only be byte-sized if one chain).
// 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.
void _PM_convert_565_byte(Protomatter_core *core, uint16_t *source,
uint16_t width) {
uint16_t *upperSrc = source; // Canvas top half
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);
}
// No need to clear matrix buffer, loops below do a full overwrite
// (except for any scanline pad, which was already initialized in the
// begin() function and won't be touched here).
// Determine matrix bytes per bitplane & row (row pair really):
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
// Skip initial scanline padding if present (HUB75 matrices shift data
// in from right-to-left, so if we need scanline padding it occurs at
// the start of a line, rather than the usual end). Destination pointer
// passed in already handles double-buffer math, so we don't need to
// handle that here, just the pad...
dest += pad;
uint32_t initialRedBit, initialGreenBit, initialBlueBit;
if(core->numPlanes == 6) {
// If numPlanes is 6, red and blue are expanded from 5 to 6 bits.
// This involves duplicating the MSB of the 5-bit value to the LSB
// of its corresponding 6-bit value...or in this case, bitmasks for
// red and blue are initially assigned to canvas MSBs, while green
// starts at LSB (because it's already 6-bit). Inner loop below then
// wraps red & blue after the first bitplane.
initialRedBit = 0b1000000000000000; // MSB red
initialGreenBit = 0b0000000000100000; // LSB green
initialBlueBit = 0b0000000000010000; // MSB blue
} else {
// If numPlanes is 1 to 5, no expansion is needed, and one or all
// three color components might be decimated by some number of bits.
// The initial bitmasks are set to the components' numPlanesth bit
// (e.g. for 5 planes, start at red & blue bit #0, green bit #1,
// for 4 planes, everything starts at the next bit up, etc.).
uint8_t shiftLeft = 5 - core->numPlanes;
initialRedBit = 0b0000100000000000 << shiftLeft;
initialGreenBit = 0b0000000001000000 << shiftLeft;
initialBlueBit = 0b0000000000000001 << shiftLeft;
}
// This works sequentially-ish through the destination buffer,
// reading from the canvas source pixels in repeated passes,
// beginning from the least bit.
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)
uint8_t prior = core->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 | core->clockMask; // Set clock bit on next out
#else
dest[x] = result;
#endif
} // end x
greenBit <<= 1;
if(plane || (core->numPlanes < 6)) {
// In most cases red & blue bit scoot 1 left...
redBit <<= 1;
blueBit <<= 1;
} else {
// Exception being after bit 0 with 6-plane display,
// in which case they're reset to red & blue LSBs
// (so 5-bit colors are expanded to 6 bits).
redBit = 0b0000100000000000;
blueBit = 0b0000000000000001;
}
#if defined(_PM_portToggleRegister)
// 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
// finishing. This is all done for legibility on oscilloscope --
// so idle clock appears LOW -- but really the matrix samples on
// a rising edge and we could leave it high, but at this stage
// in development just want the scope "readable."
dest[-pad] &= ~core->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 row
}
// Corresponding function for word output -- either 12 RGB bits (2 parallel
// 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.
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 *dest = (uint16_t *)core->screenData;
if(core->doubleBuffer) {
dest += core->bufferSize / core->bytesPerElement *
(1 - core->activeBuffer);
}
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
uint32_t initialRedBit, initialGreenBit, initialBlueBit;
if(core->numPlanes == 6) {
initialRedBit = 0b1000000000000000; // MSB red
initialGreenBit = 0b0000000000100000; // LSB green
initialBlueBit = 0b0000000000010000; // MSB blue
} else {
uint8_t shiftLeft = 5 - core->numPlanes;
initialRedBit = 0b0000100000000000 << shiftLeft;
initialGreenBit = 0b0000000001000000 << shiftLeft;
initialBlueBit = 0b0000000000000001 << shiftLeft;
}
// Unlike the 565 byte converter, the word converter DOES clear out the
// matrix buffer (because each chain is OR'd into place). If a toggle
// 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)
// 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++) {
for(uint8_t plane=0; plane<core->numPlanes; plane++) {
dest[offset++] = 0; // First element of each plane
for(uint16_t x=1; x<bitplaneSize; x++) { // All subsequent items
dest[offset++] = core->clockMask;
}
}
}
#else
memset(dest, 0, core->bufferSize);
#endif
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)
// 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];
// 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;
#else
dest[x] |= result; // Bitwise OR
#endif
} // end x
greenBit <<= 1;
if(plane || (core->numPlanes < 6)) {
redBit <<= 1;
blueBit <<= 1;
} else {
redBit = 0b0000100000000000;
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;
}
}
// 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.
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 *dest = (uint32_t *)core->screenData;
if(core->doubleBuffer) {
dest += core->bufferSize / core->bytesPerElement *
(1 - core->activeBuffer);
}
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
uint32_t initialRedBit, initialGreenBit, initialBlueBit;
if(core->numPlanes == 6) {
initialRedBit = 0b1000000000000000; // MSB red
initialGreenBit = 0b0000000000100000; // LSB green
initialBlueBit = 0b0000000000010000; // MSB blue
} else {
uint8_t shiftLeft = 5 - core->numPlanes;
initialRedBit = 0b0000100000000000 << shiftLeft;
initialGreenBit = 0b0000000001000000 << shiftLeft;
initialBlueBit = 0b0000000000000001 << shiftLeft;
}
#if defined(_PM_portToggleRegister)
// 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++) {
for(uint8_t plane=0; plane<core->numPlanes; plane++) {
dest[offset++] = 0; // First element of each plane
for(uint16_t x=1; x<bitplaneSize; x++) { // All subsequent items
dest[offset++] = core->clockMask;
}
}
}
#else
memset(dest, 0, core->bufferSize);
#endif
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)
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];
// 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;
#else
dest[x] |= result; // Bitwise OR
#endif
} // end x
greenBit <<= 1;
if(plane || (core->numPlanes < 6)) {
redBit <<= 1;
blueBit <<= 1;
} else {
redBit = 0b0000100000000000;
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;
}
}
#endif // ARDUINO
#endif // _PROTOMATTER_ARCH_H_

668
core.c
View file

@ -1,668 +0,0 @@
// Device- and environment-neutral core matrix-driving functionality.
// See notes near top of arch.h regarding assumptions of hardware
// "common ground." If you find yourself doing an "#ifdef ARDUINO" or
// "#ifdef _SAMD21_" in this file, STOP. Idea is that the code in this
// file is neutral and portable (within aforementioned assumptions).
// Nonportable elements should appear in arch.h. If arch.h functionality
// is lacking, extend it there, do not go making device- or environment-
// specific cases within this file.
// Function names are intentionally a little obtuse, idea is that one writes
// a more sensible wrapper around this for specific environments (e.g. the
// Arduino stuff in Adafruit_Protomatter.cpp). The "_PM_" prefix on most
// things hopefully makes function and variable name collisions much less
// likely with one's own code.
#include "core.h" // enums and structs
#include "arch.h" // Do NOT include this in any other source files
// Overall matrix refresh rate (frames/second) is a function of matrix width
// and chain length, number of address lines, number of bit planes, CPU speed
// and whether or not a GPIO toggle register is available. There is no "this
// will run at X-frames-per-second" constant figure. You typically just have
// to try it out and perhaps trade off some bit planes for refresh rate until
// the image looks good and stable. Anything over 100 Hz is usually passable,
// around 250 Hz is where things firm up. And while this could proceed higher
// in some situations, the tradeoff is that faster rates use progressively
// more CPU time (because it's timer interrupt based and not using DMA or
// special peripherals). So a throttle is set here, an approximate maximum
// frame rate which the software will attempt to avoid exceeding (but may
// refresh slower than this, and in many cases will...just need to set an
// upper limit to avoid excessive CPU load). An incredibly long comment block
// for a single constant, thank you for coming to my TED talk!
#define _PM_MAX_REFRESH_HZ 250
// Time (in microseconds) to pause following any change in address lines
// (individually or collectively). Some matrices respond slowly there...
// must pause on change for matrix to catch up. Defined here (rather than
// arch.h) because it's not architecture-specific.
#define _PM_ROW_DELAY 8
// These are the lowest-level functions for issing data to matrices.
// There are three versions because it depends on how the six RGB data bits
// (and clock bit) are arranged within a 32-bit PORT register. If all six
// (seven) fit within one byte or word of the PORT, the library's memory
// use (and corresponding data-issuing function) change. This will also have
// an impact on parallel chains in the future, where the number of concurrent
// RGB data bits isn't always six, but some multiple thereof (i.e. up to five
// parallel outputs -- 30 RGB bits + clock -- on a 32-bit PORT, though that's
// largely hypothetical as the chance of finding a PORT with that many bits
// exposed and NOT interfering with other peripherals on a board is highly
// improbable. But I could see four happening, maybe on a Grand Central or
// other kitchen-sink board.
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);
// Validate and populate vital elements of core structure.
// Does NOT allocate core struct -- calling function must provide that.
// (In the Arduino C++ library, its part of the Protomatter class.)
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) return PROTOMATTER_ERR_ARG;
if(rgbCount > 5) rgbCount = 5; // Max 5 in parallel (32-bit PORT)
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 NULL timer was passed in (the default case for the constructor),
// use default value from arch.h. For example, in the Arduino case it's
// tied to TC4 specifically.
if(timer == NULL) timer = _PM_TIMER_DEFAULT;
core->timer = timer;
core->width = bitWidth; // Total matrix chain length in bits
core->numPlanes = bitDepth;
core->parallel = rgbCount;
core->numAddressLines = addrCount;
core->clockPin = clockPin;
core->latch.pin = latchPin;
core->oe.pin = oePin;
core->doubleBuffer = doubleBuffer;
core->addr = NULL;
core->screenData = NULL;
// Make a copy of the rgbList and addrList tables in case they're
// passed from local vars on the stack or some other non-persistent
// source. screenData is NOT allocated here because data size (byte,
// word, long) is not known until the begin function evaluates all
// the pin bitmasks.
rgbCount *= 6; // Convert parallel count to pin count
if((core->rgbPins = (uint8_t *)malloc(rgbCount * sizeof(uint8_t)))) {
if((core->addr = (_PM_pin *)malloc(addrCount * sizeof(_PM_pin)))) {
memcpy(core->rgbPins, rgbList, rgbCount * sizeof(uint8_t));
for(uint8_t i=0; i<addrCount; i++) {
core->addr[i].pin = addrList[i];
}
return PROTOMATTER_OK;
}
free(core->rgbPins);
core->rgbPins = NULL;
}
return PROTOMATTER_ERR_MALLOC;
}
// Allocate display buffers and populate additional elements.
ProtomatterStatus _PM_begin(Protomatter_core *core) {
if(!core) return PROTOMATTER_ERR_ARG;
if(!core->rgbPins) { // NULL if copy failed to allocate
return PROTOMATTER_ERR_MALLOC;
}
// 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
// referenced.
uint8_t *port = (uint8_t *)_PM_portOutRegister(core->clockPin);
#if defined(_PM_portToggleRegister)
// If a bit-toggle register is present, the clock pin is included
// in determining which bytes of the PORT register are used (and thus
// the data storage efficiency).
uint32_t bitMask = _PM_portBitMask(core->clockPin);
#else
// If no bit-toggle register, clock pin can be on any bit, doesn't
// affect storage efficiency.
uint32_t bitMask = 0;
#endif
for(uint8_t i=0; i<core->parallel * 6; i++) {
uint8_t *p2 = (uint8_t *)_PM_portOutRegister(core->rgbPins[i]);
if(p2 != port) {
return PROTOMATTER_ERR_PINS;
}
bitMask |= _PM_portBitMask(core->rgbPins[i]);
}
// RGB + clock are on same port, we can proceed...
// Determine data type for internal representation. If all the data
// bitmasks (and possibly clock bitmask, depending whether toggle-bits
// register is present) are in the same byte, this can be stored more
// compact than if they're spread across a word or long.
uint8_t byteMask = 0;
if(bitMask & 0xFF000000) byteMask |= 0b1000;
if(bitMask & 0x00FF0000) byteMask |= 0b0100;
if(bitMask & 0x0000FF00) byteMask |= 0b0010;
if(bitMask & 0x000000FF) byteMask |= 0b0001;
switch(byteMask) {
case 0b0001: // If all PORT bits are in the same byte...
case 0b0010:
case 0b0100:
case 0b1000:
core->bytesPerElement = 1; // Use 8-bit PORT accesses.
break;
case 0b0011: // If all PORT bits in upper/lower word...
case 0b1100:
core->bytesPerElement = 2; // Use 16-bit PORT accesses.
// Although some devices might tolerate unaligned 16-bit accesses
// ('middle' word of 32-bit PORT), that is NOT handled here.
// It's a portability liability.
break;
default: // Any other situation...
core->bytesPerElement = 4; // Use 32-bit PORT accesses.
break;
}
// Planning for screen data allocation...
core->numRowPairs = 1 << core->numAddressLines;
uint8_t chunks = (core->width + (_PM_chunkSize - 1)) / _PM_chunkSize;
uint16_t columns = chunks * _PM_chunkSize; // Padded matrix width
uint32_t screenBytes = columns * core->numRowPairs * core->numPlanes *
core->bytesPerElement;
core->bufferSize = screenBytes; // Bytes per matrix buffer (1 or 2)
if(core->doubleBuffer) screenBytes *= 2; // Total for matrix buffer(s)
uint32_t rgbMaskBytes = core->parallel * 6 * core->bytesPerElement;
// Allocate matrix buffer(s). Don't worry about the return type...
// though we might be using words or longs for certain pin configs,
// malloc() by definition always aligns to the longest type.
if(!(core->screenData = (uint8_t *)malloc(screenBytes + rgbMaskBytes))) {
return PROTOMATTER_ERR_MALLOC;
}
// rgbMask data follows the matrix buffer(s)
core->rgbMask = core->screenData + screenBytes;
#if !defined(_PM_portToggleRegister)
// 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);
#endif
// Figure out clockMask and rgbAndClockMask, clear matrix buffers
if(core->bytesPerElement == 1) {
core->portOffset = _PM_byteOffset(core->rgbPins[0]);
#if defined(_PM_portToggleRegister)
// Clock and rgbAndClockMask are 8-bit values
core->clockMask = _PM_portBitMask(core->clockPin) >>
(core->portOffset * 8);
core->rgbAndClockMask = (bitMask >> (core->portOffset * 8)) |
core->clockMask;
memset(core->screenData, core->clockMask, screenBytes);
#else
// Clock and rgbAndClockMask are 32-bit values
core->clockMask = _PM_portBitMask(core->clockPin);
core->rgbAndClockMask = bitMask | core->clockMask;
#endif
for(uint8_t i=0; i<core->parallel * 6; i++) {
((uint8_t *)core->rgbMask)[i] = // Pin bitmasks are 8-bit
_PM_portBitMask(core->rgbPins[i]) >> (core->portOffset * 8);
}
} else if(core->bytesPerElement == 2) {
core->portOffset = _PM_wordOffset(core->rgbPins[0]);
#if defined(_PM_portToggleRegister)
// Clock and rgbAndClockMask are 16-bit values
core->clockMask = _PM_portBitMask(core->clockPin) >>
(core->portOffset * 16);
core->rgbAndClockMask = (bitMask >> (core->portOffset * 16)) |
core->clockMask;
uint32_t elements = screenBytes / 2;
for(uint32_t i=0; i<elements; i++) {
((uint16_t *)core->screenData)[i] = core->clockMask;
}
#else
// Clock and rgbAndClockMask are 32-bit values
core->clockMask = _PM_portBitMask(core->clockPin);
core->rgbAndClockMask = bitMask | core->clockMask;
#endif
for(uint8_t i=0; i<core->parallel * 6; i++) {
((uint16_t *)core->rgbMask)[i] = // Pin bitmasks are 16-bit
_PM_portBitMask(core->rgbPins[i]) >> (core->portOffset * 16);
}
} else {
core->portOffset = 0;
core->clockMask = _PM_portBitMask(core->clockPin);
core->rgbAndClockMask = bitMask | core->clockMask;
#if defined(_PM_portToggleRegister)
uint32_t elements = screenBytes / 4;
for(uint32_t i=0; i<elements; i++) {
((uint32_t *)core->screenData)[i] = core->clockMask;
}
#endif
for(uint8_t i=0; i<core->parallel * 6; i++) {
((uint32_t *)core->rgbMask)[i] = // Pin bitmasks are 32-bit
_PM_portBitMask(core->rgbPins[i]);
}
}
// Estimate minimum bitplane #0 period for _PM_MAX_REFRESH_HZ rate.
uint32_t minPeriodPerFrame = _PM_timerFreq / _PM_MAX_REFRESH_HZ;
uint32_t minPeriodPerLine = minPeriodPerFrame / core->numRowPairs;
core->minPeriod = minPeriodPerLine / ((1 << core->numPlanes) - 1);
if(core->minPeriod < _PM_minMinPeriod) {
core->minPeriod = _PM_minMinPeriod;
}
// 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;
// Configure pins as outputs and initialize their states.
core->latch.setReg = _PM_portSetRegister(core->latch.pin);
core->latch.clearReg = _PM_portClearRegister(core->latch.pin);
core->latch.bit = _PM_portBitMask(core->latch.pin);
core->oe.setReg = _PM_portSetRegister(core->oe.pin);
core->oe.clearReg = _PM_portClearRegister(core->oe.pin);
core->oe.bit = _PM_portBitMask(core->oe.pin);
_PM_pinOutput(core->clockPin);
_PM_pinLow(core->clockPin); // Init clock LOW
_PM_pinOutput(core->latch.pin);
_PM_pinLow(core->latch.pin); // Init latch LOW
_PM_pinOutput(core->oe.pin);
_PM_pinHigh(core->oe.pin); // Init OE HIGH (disable output)
for(uint8_t i=0; i<core->parallel * 6; i++) {
_PM_pinOutput(core->rgbPins[i]);
_PM_pinLow(core->rgbPins[i]);
}
#if defined(_PM_portToggleRegister)
core->addrPortToggle = _PM_portToggleRegister(core->addr[0].pin);
core->singleAddrPort = 1;
#endif
for(uint8_t line=0,bit=1; line<core->numAddressLines; line++, bit<<=1) {
core->addr[line].setReg =
_PM_portSetRegister(core->addr[line].pin);
core->addr[line].clearReg =
_PM_portClearRegister(core->addr[line].pin);
core->addr[line].bit =
_PM_portBitMask(core->addr[line].pin);
_PM_pinOutput(core->addr[line].pin);
if(core->prevRow & bit) {
_PM_pinHigh(core->addr[line].pin);
} else {
_PM_pinLow(core->addr[line].pin);
}
#if defined(_PM_portToggleRegister)
// If address pin on different port than addr 0, no singleAddrPort.
if(_PM_portToggleRegister(core->addr[line].pin) !=
core->addrPortToggle) {
core->singleAddrPort = 0;
}
#endif
}
// Get pointers to bit set and clear registers (and toggle, if present)
core->setReg = (uint8_t *)_PM_portSetRegister(core->clockPin);
core->clearReg = (uint8_t *)_PM_portClearRegister(core->clockPin);
#if defined(_PM_portToggleRegister)
core->toggleReg = (uint8_t *)_PM_portToggleRegister(core->clockPin);
#endif
// Reset plane/row counters, config and start timer
_PM_resume(core);
return PROTOMATTER_OK;
}
// Disable (but do not deallocate) a Protomatter matrix. Disables matrix by
// setting OE pin HIGH and writing all-zero data to matrix shift registers,
// so it won't halt with lit LEDs.
void _PM_stop(Protomatter_core *core) {
if((core)) {
while(core->swapBuffers); // Wait for any pending buffer swap
_PM_timerStop(core->timer); // Halt timer
*core->oe.setReg = core->oe.bit; // 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
// also clear out the matrix shift registers for good measure.
// Set all RGB pins LOW...
for(uint8_t i=0; i<core->parallel * 6; i++) {
_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++) {
_PM_pinHigh(core->clockPin);
_PM_clockHoldHigh;
_PM_pinLow(core->clockPin);
_PM_clockHoldLow;
}
// Latch data
*core->latch.setReg = core->latch.bit;
*core->latch.clearReg = core->latch.bit;
}
}
void _PM_resume(Protomatter_core *core) {
if((core)) {
// Init plane & row to max values so they roll over on 1st interrupt
core->plane = core->numPlanes - 1;
core->row = core->numRowPairs - 1;
core->prevRow = (core->numRowPairs > 1) ? (core->row - 1) : 1;
core->swapBuffers = 0;
core->frameCount = 0;
_PM_timerInit(core->timer); // Configure timer
_PM_timerStart(core->timer, 1000); // Start timer
}
}
// Free memory associated with core structure. Does NOT dealloc struct.
void _PM_free(Protomatter_core *core) {
if((core)) {
_PM_stop(core);
// TO DO: Set all pins back to inputs here?
if(core->screenData) free(core->screenData);
if(core->addr) free(core->addr);
if(core->rgbPins) {
free(core->rgbPins);
core->rgbPins = NULL;
}
}
}
// ISR function (in arch.h) calls this function which it extern'd.
void _PM_row_handler(Protomatter_core *core) {
*core->oe.setReg = core->oe.bit; // Disable LED output
*core->latch.setReg = core->latch.bit; // Latch data from PRIOR pass
// Stop timer, save count value at stop
uint32_t elapsed = _PM_timerStop(core->timer);
uint8_t prevPlane = core->plane; // Save that plane # for later timing
*core->latch.clearReg = core->latch.bit; // (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
// available), do address line change all at once. Even doing all
// this math takes MUCH less time than the delays required when
// doing line-by-line changes.
if(core->singleAddrPort) {
// Make bitmasks of prior and new row bits
uint32_t priorBits = 0, newBits = 0;
for(uint8_t line=0,bit=1; line<core->numAddressLines;
line++, bit<<=1) {
if(core->row & bit) {
newBits |= core->addr[line].bit;
}
if(core->prevRow & bit) {
priorBits |= core->addr[line].bit;
}
}
*core->addrPortToggle = newBits ^ priorBits;
_PM_delayMicroseconds(_PM_ROW_DELAY);
} else {
#endif
// Configure row address lines individually, making changes
// (with delays) only where necessary.
for(uint8_t line=0,bit=1; line<core->numAddressLines;
line++, bit<<=1) {
if((core->row & bit) != (core->prevRow & bit)) {
if(core->row & bit) { // Set addr line high
*core->addr[line].setReg = core->addr[line].bit;
} else { // Set addr line low
*core->addr[line].clearReg = core->addr[line].bit;
}
_PM_delayMicroseconds(_PM_ROW_DELAY);
}
}
#if defined(_PM_portToggleRegister)
}
#endif
core->prevRow = core->row;
}
// Advance bitplane index and/or row as necessary
if(++core->plane >= core->numPlanes) { // Next data bitplane, or
core->plane = 0; // roll over bitplane to start
if(++core->row >= core->numRowPairs) { // Next row, or
core->row = 0; // roll over row to start
// Switch matrix buffers if due (only if double-buffered)
if(core->swapBuffers) {
core->activeBuffer = 1 - core->activeBuffer;
core->swapBuffers = 0; // Swapped!
}
core->frameCount++;
}
}
// '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);
*core->oe.clearReg = core->oe.bit; // Enable LED output
uint32_t elementsPerLine = _PM_chunkSize *
((core->width + (_PM_chunkSize - 1)) / _PM_chunkSize);
uint32_t srcOffset = elementsPerLine *
(core->numPlanes * core->row + core->plane) * core->bytesPerElement;
if(core->doubleBuffer) {
srcOffset += core->bufferSize * core->activeBuffer;
}
if(core->bytesPerElement == 1) {
blast_byte(core, (uint8_t *)(core->screenData + srcOffset));
} else if(core->bytesPerElement == 2) {
blast_word(core, (uint16_t *)(core->screenData + srcOffset));
} else {
blast_long(core, (uint32_t *)(core->screenData + srcOffset));
}
// 'plane' data is now loaded, will be shown on NEXT pass
}
// Innermost data-stuffing loop functions
// The presence of a bit-toggle register can make the data-stuffing loop a
// fair bit faster (2 PORT accesses per column vs 3). But ironically, some
// devices (e.g. SAMD51) can outpace the matrix max CLK speed, so we slow
// them down with a few NOPs. These are defined in arch.h as needed.
// _PM_clockHoldLow is whatever code necessary to delay the clock rise
// after data is placed on the PORT. _PM_clockHoldHigh is code for delay
// before setting the clock back low. If undefined, nothing goes there.
#if defined(_PM_portToggleRegister)
#define PEW \
*toggle = *data++; /* Toggle in new data + toggle clock low */ \
_PM_clockHoldLow; \
*toggle = clock; /* Toggle clock high */ \
_PM_clockHoldHigh;
#else
#define PEW \
*set = *data++; /* Set RGB data high */ \
_PM_clockHoldLow; \
*set32 = clock; /* Set clock high */ \
_PM_clockHoldHigh; \
*clear32 = rgbclock; /* Clear RGB data + clock */
#endif
#if _PM_chunkSize == 1
#define PEW_UNROLL PEW
#elif _PM_chunkSize == 8
#define PEW_UNROLL PEW PEW PEW PEW PEW PEW PEW PEW
#elif _PM_chunkSize == 16
#define PEW_UNROLL \
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW
#elif _PM_chunkSize == 32
#define PEW_UNROLL \
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW \
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW
#elif _PM_chunkSize == 64
#define PEW_UNROLL \
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW \
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW \
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW \
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW
#else
#error "Unimplemented _PM_chunkSize value"
#endif
// There are THREE COPIES of the following function -- one each for byte,
// word and long. If changes are made in any one of them, the others MUST
// be updated to match! (Decided against using macro tricks for the
// function, too often ends in disaster...but must be vigilant in the
// three-function maintenance then.)
static void blast_byte(Protomatter_core *core, uint8_t *data) {
#if defined(_PM_portToggleRegister)
// 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;
#else
// No-toggle version is a little different. If here, RGB data is all
// in one byte of PORT register, clock can be any bit in 32-bit PORT.
volatile uint8_t *set; // For RGB data set
volatile uint32_t *set32; // For clock set
volatile uint32_t *clear32; // For RGB data + clock clear
set = (volatile uint8_t *)core->setReg + portOffset;
set32 = (volatile uint32_t *)core->setReg;
clear32 = (volatile uint32_t *)core->clearReg;
uint32_t rgbclock = core->rgbAndClockMask; // RGB + clock bit
#endif
uint32_t clock = core->clockMask; // Clock bit
uint8_t chunks = (core->width + (_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.
while(chunks--) {
PEW_UNROLL // _PM_chunkSize RGB+clock writes
}
#if defined(_PM_portToggleRegister)
// 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;
#endif
}
static void blast_word(Protomatter_core *core, uint16_t *data) {
#if defined(_PM_portToggleRegister)
// See notes above -- except now 16-bit word in PORT.
volatile uint16_t *toggle = (volatile uint16_t *)core->toggleReg +
core->portOffset;
#else
volatile uint16_t *set; // For RGB data set
volatile uint32_t *set32; // For clock set
volatile uint32_t *clear32; // For RGB data + clock clear
set = (volatile uint16_t *)core->setReg + core->portOffset;
set32 = (volatile uint32_t *)core->setReg;
clear32 = (volatile uint32_t *)core->clearReg;
uint32_t rgbclock = core->rgbAndClockMask; // RGB + clock bit
#endif
uint32_t clock = core->clockMask; // Clock bit
uint8_t chunks = (core->width + (_PM_chunkSize - 1)) / _PM_chunkSize;
while(chunks--) {
PEW_UNROLL // _PM_chunkSize RGB+clock writes
}
#if defined(_PM_portToggleRegister)
// rgbAndClockMask is a 16-bit value when toggling, hence offset here.
*((volatile uint16_t *)core->clearReg + core->portOffset) =
core->rgbAndClockMask;
#endif
}
static void blast_long(Protomatter_core *core, uint32_t *data) {
#if defined(_PM_portToggleRegister)
// See notes above -- except now full 32-bit PORT.
volatile uint32_t *toggle = (volatile uint32_t *)core->toggleReg;
#else
// 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 uint32_t *set32; // For clock set
volatile uint32_t *clear32; // For RGB data + clock clear
set = (volatile uint32_t *)core->setReg;
set32 = (volatile uint32_t *)core->setReg;
clear32 = (volatile uint32_t *)core->clearReg;
uint32_t rgbclock = core->rgbAndClockMask; // RGB + clock bit
#endif
uint32_t clock = core->clockMask; // Clock bit
uint8_t chunks = (core->width + (_PM_chunkSize - 1)) / _PM_chunkSize;
while(chunks--) {
PEW_UNROLL // _PM_chunkSize RGB+clock writes
}
#if defined(_PM_portToggleRegister)
*(volatile uint32_t *)core->clearReg = core->rgbAndClockMask;
#endif
}
// 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
// the matrix (since this is difficult to estimate beforehand).
uint32_t _PM_getFrameCount(Protomatter_core *core) {
uint32_t count = 0;
if((core)) {
count = core->frameCount;
core->frameCount = 0;
}
return count;
}
// 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.

110
core.h
View file

@ -1,110 +0,0 @@
#ifndef _PROTOMATTER_CORE_H_
#define _PROTOMATTER_CORE_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
// Status type returned by some functions.
typedef enum {
PROTOMATTER_OK, // Everything is hunky-dory!
PROTOMATTER_ERR_PINS, // Clock and/or data pins on different PORTs
PROTOMATTER_ERR_MALLOC, // Couldn't allocate memory for display
PROTOMATTER_ERR_ARG, // Bad input to function
} ProtomatterStatus;
// Struct for matrix control lines NOT related to RGB data or clock, i.e.
// latch, OE and address lines. RGB data and clock ("RGBC") are handled
// differently as they have specific requirements (and might use a toggle
// register if present). The data conversion functions need bitmasks for
// RGB data but do NOT need the set or clear registers, so those items
// are also declared as separate things in the core structure that follows.
typedef struct {
volatile uint32_t *setReg; // GPIO bit set register
volatile uint32_t *clearReg; // GPIO bit clear register
uint32_t bit; // GPIO bitmask
uint8_t pin; // Some identifier, e.g. Arduino pin #
} _PM_pin;
// Struct with info about an RGB matrix chain and lots of state and buffer
// details for the library. Toggle-related items in this structure MUST be
// declared even if the device lacks GPIO bit-toggle registers (i.e. don't
// do an ifdef check around these). All hardware-specific details (including
// the presence or lack of toggle registers) are isolated to a single
// file -- arch.h -- which should ONLY be included by core.c, and ifdef'ing
// them would result in differing representations of this structure which
// must be shared between the library and calling code. (An alternative is
// to put any toggle-specific stuff at the end of the struct with an ifdef
// check, but that's just dirty pool and asking for trouble.)
typedef struct {
void *timer; // Arch-specific timer/counter info
void *setReg; // RGBC bit set register (cast to use)
void *clearReg; // RGBC bit clear register "
void *toggleReg; // RGBC bit toggle register "
uint8_t *rgbPins; // Array of RGB data pins (mult of 6)
void *rgbMask; // PORT bit mask for each RGB pin
uint32_t clockMask; // PORT bit mask for RGB clock
uint32_t rgbAndClockMask; // PORT bit mask for RGB data + clock
volatile uint32_t *addrPortToggle; // See singleAddrPort below
void *screenData; // Per-bitplane RGB data for matrix
_PM_pin latch; // RGB data latch
_PM_pin oe; // !OE (LOW out enable)
_PM_pin *addr; // Array of address pins
uint32_t bufferSize; // Bytes per matrix buffer
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
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
uint8_t numAddressLines; // Number of address line pins
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
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
volatile uint8_t plane; // Current bitplane (changes in ISR)
volatile uint8_t row; // Current scanline (changes in ISR)
volatile uint8_t prevRow; // Scanline from prior ISR
volatile bool swapBuffers; // If 1, awaiting double-buf switch
} Protomatter_core;
// Protomatter core function prototypes. Environment-specific code (like the
// Adafruit_Protomatter class for Arduino) calls on these underlying things,
// and has to provide a few extras of its own (interrupt handlers and such).
// User code shouldn't need to invoke any of them directly.
extern 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);
extern ProtomatterStatus _PM_begin(Protomatter_core *core);
extern void _PM_stop(Protomatter_core *core);
extern void _PM_resume(Protomatter_core *core);
extern void _PM_free(Protomatter_core *core);
extern void _PM_row_handler(Protomatter_core *core);
extern uint32_t _PM_getFrameCount(Protomatter_core *core);
extern void _PM_timerStart(void *tptr, uint32_t period);
extern uint32_t _PM_timerStop(void *tptr);
extern uint32_t _PM_timerGetCount(void *tptr);
#if defined(ARDUINO)
extern void _PM_convert_565_byte(Protomatter_core *core,
uint16_t *source, uint16_t width);
extern void _PM_convert_565_word(Protomatter_core *core,
uint16_t *source, uint16_t width);
extern void _PM_convert_565_long(Protomatter_core *core,
uint16_t *source, uint16_t width);
#endif // ARDUINO
#ifdef __cplusplus
} // extern "C"
#endif
#endif // _PROTOMATTER_CORE_H_

View file

@ -0,0 +1,376 @@
// 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 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:
// - Adafruit_Protomatter
// - Adafruit_SPIFlash
// - Adafruit_TinyUSB
// - SdFat (Adafruit fork)
// - AnimatedGIF
// Set ENABLE_EXTENDED_TRANSFER_CLASS and FAT12_SUPPORT in SdFatConfig.h.
// Select Tools->USB Stack->TinyUSB before compiling.
#include <Adafruit_Protomatter.h>
#include <Adafruit_SPIFlash.h>
#include <Adafruit_TinyUSB.h>
#include <AnimatedGIF.h>
#include <SPI.h>
#include <SdFat.h>
// CONFIGURABLE SETTINGS ---------------------------------------------------
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)
Adafruit_FlashTransport_QSPI flashTransport;
#elif defined(EXTERNAL_FLASH_USE_SPI)
Adafruit_FlashTransport_SPI flashTransport(EXTERNAL_FLASH_USE_CS,
EXTERNAL_FLASH_USE_SPI);
#else
#error No QSPI/SPI flash are defined in your board variant.h!
#endif
Adafruit_SPIFlash flash(&flashTransport);
FatFileSystem filesys; // Filesystem object from SdFat
Adafruit_USBD_MSC usb_msc; // USB mass storage object
// RGB MATRIX (PROTOMATTER) LIBRARY STUFF ----------------------------------
#if defined(_VARIANT_MATRIXPORTAL_M4_)
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
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(_VARIANT_METRO_M4_)
uint8_t rgbPins[] = {2, 3, 4, 5, 6, 7};
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}; // 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
Adafruit_Protomatter matrix(WIDTH, 6, 1, rgbPins, NUM_ADDR_PINS, addrPins,
clockPin, latchPin, oePin, true);
// ANIMATEDGIF LIBRARY STUFF -----------------------------------------------
AnimatedGIF GIF;
File GIFfile;
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) {
GIFfile = filesys.open(filename);
if (GIFfile) {
*pSize = GIFfile.size();
return (void *)&GIFfile;
}
return NULL;
}
void GIFCloseFile(void *pHandle) {
File *f = static_cast<File *>(pHandle);
if (f) f->close();
}
int32_t GIFReadFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen) {
int32_t iBytesRead = iLen;
File *f = static_cast<File *>(pFile->fHandle);
// If a file is read all the way to last byte, seek() stops working
if ((pFile->iSize - pFile->iPos) < iLen)
iBytesRead = pFile->iSize - pFile->iPos - 1; // ugly work-around
if (iBytesRead <= 0) return 0;
iBytesRead = (int32_t)f->read(pBuf, iBytesRead);
pFile->iPos = f->position();
return iBytesRead;
}
int32_t GIFSeekFile(GIFFILE *pFile, int32_t iPosition) {
File *f = static_cast<File *>(pFile->fHandle);
f->seek(iPosition);
pFile->iPos = (int32_t)f->position();
return pFile->iPos;
}
// Draw one line of image to matrix back buffer
void GIFDraw(GIFDRAW *pDraw) {
uint8_t *s;
uint16_t *d, *usPalette, usTemp[320];
int x, y;
y = pDraw->iY + pDraw->y; // current line in image
// Vertical clip
int16_t screenY = yPos + y; // current row on matrix
if ((screenY < 0) || (screenY >= matrix.height())) return;
usPalette = pDraw->pPalette;
s = pDraw->pPixels;
// Apply the new pixels to the main image
if (pDraw->ucHasTransparency) { // if transparency used
uint8_t *pEnd, c, ucTransparent = pDraw->ucTransparent;
int x, iCount;
pEnd = s + pDraw->iWidth;
x = 0;
iCount = 0; // count non-transparent pixels
while (x < pDraw->iWidth) {
c = ucTransparent - 1;
d = usTemp;
while (c != ucTransparent && s < pEnd) {
c = *s++;
if (c == ucTransparent) { // done, stop
s--; // back up to treat it like transparent
} else { // opaque
*d++ = usPalette[c];
iCount++;
}
} // while looking for opaque pixels
if (iCount) { // any opaque pixels?
span(usTemp, xPos + pDraw->iX + x, screenY, iCount);
x += iCount;
iCount = 0;
}
// no, look for a run of transparent pixels
c = ucTransparent;
while (c == ucTransparent && s < pEnd) {
c = *s++;
if (c == ucTransparent)
iCount++;
else
s--;
}
if (iCount) {
x += iCount; // skip these
iCount = 0;
}
}
} else {
s = pDraw->pPixels;
// Translate 8-bit pixels through RGB565 palette (already byte reversed)
for (x = 0; x < pDraw->iWidth; x++)
usTemp[x] = usPalette[*s++];
span(usTemp, xPos + pDraw->iX, screenY, pDraw->iWidth);
}
}
// Copy a horizontal span of pixels from a source buffer to an X,Y position
// in matrix back buffer, applying horizontal clipping. Vertical clipping is
// handled in GIFDraw() above -- y can safely be assumed valid here.
void span(uint16_t *src, int16_t x, int16_t y, int16_t width) {
if (x >= matrix.width()) return; // Span entirely off right of matrix
int16_t x2 = x + width - 1; // Rightmost pixel
if (x2 < 0) return; // Span entirely off left of matrix
if (x < 0) { // Span partially off left of matrix
width += x; // Decrease span width
src -= x; // Increment source pointer to new start
x = 0; // Leftmost pixel is first column
}
if (x2 >= matrix.width()) { // Span partially off right of matrix
width -= (x2 - matrix.width() + 1);
}
if(matrix.getRotation() == 0) {
memcpy(matrix.getBuffer() + y * matrix.width() + x, src, width * 2);
} else {
while(x <= x2) {
matrix.drawPixel(x++, y, *src++);
}
}
}
// FUNCTIONS REQUIRED FOR USB MASS STORAGE ---------------------------------
static bool msc_changed = true; // Is set true on filesystem changes
// Callback on READ10 command.
int32_t msc_read_cb(uint32_t lba, void *buffer, uint32_t bufsize) {
return flash.readBlocks(lba, (uint8_t *)buffer, bufsize / 512) ? bufsize : -1;
}
// Callback on WRITE10 command.
int32_t msc_write_cb(uint32_t lba, uint8_t *buffer, uint32_t bufsize) {
digitalWrite(LED_BUILTIN, HIGH);
return flash.writeBlocks(lba, buffer, bufsize / 512) ? bufsize : -1;
}
// Callback on WRITE10 completion.
void msc_flush_cb(void) {
flash.syncBlocks(); // Sync with flash
filesys.cacheClear(); // Clear filesystem cache to force refresh
digitalWrite(LED_BUILTIN, LOW);
msc_changed = true;
}
// Get number of files in a specified path that match extension ('filter').
// Pass in absolute path (e.g. "/" or "/gifs") and extension WITHOUT period
// (e.g. "gif", NOT ".gif").
int16_t numFiles(const char *path, const char *filter) {
File dir = filesys.open(path);
if (!dir) return -1;
char filename[256];
for(int16_t num_files = 0;;) {
File entry = dir.openNextFile();
if (!entry) return num_files; // No more files
entry.getName(filename, sizeof(filename) - 1);
entry.close();
if (!entry.isDirectory() && // Skip directories
strncmp(filename, "._", 2)) { // and Mac junk files
char *extension = strrchr(filename, '.');
if (extension && !strcasecmp(&extension[1], filter)) num_files++;
}
}
return -1;
}
// Return name of file (matching extension) by index (0 to numFiles()-1)
char *filenameByIndex(const char *path, const char *filter, int16_t index) {
static char filename[256]; // Must be static, we return a pointer to this!
File entry, dir = filesys.open(path);
if (!dir) return NULL;
while(entry = dir.openNextFile()) {
entry.getName(filename, sizeof(filename) - 1);
entry.close();
if(!entry.isDirectory() && // Skip directories
strncmp(filename, "._", 2)) { // and Mac junk files
char *extension = strrchr(filename, '.');
if (extension && !strcasecmp(&extension[1], filter)) {
if(!index--) {
return filename;
}
}
}
}
return NULL;
}
// SETUP FUNCTION - RUNS ONCE AT STARTUP -----------------------------------
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
#if defined(BACK_BUTTON)
pinMode(BACK_BUTTON, INPUT_PULLUP);
#endif
#if defined(NEXT_BUTTON)
pinMode(NEXT_BUTTON, INPUT_PULLUP);
#endif
// USB mass storage / filesystem setup (do BEFORE Serial init)
flash.begin();
// Set disk vendor id, product id and revision
usb_msc.setID("Adafruit", "External Flash", "1.0");
// Set disk size, block size is 512 regardless of spi flash page size
usb_msc.setCapacity(flash.pageSize() * flash.numPages() / 512, 512);
usb_msc.setReadWriteCallback(msc_read_cb, msc_write_cb, msc_flush_cb);
usb_msc.setUnitReady(true); // MSC is ready for read/write
usb_msc.begin();
filesys.begin(&flash); // Start filesystem on the flash
Serial.begin(115200);
//while (!Serial);
// Protomatter (RGB matrix) setup
ProtomatterStatus status = matrix.begin();
Serial.print("Protomatter begin() status: ");
Serial.println((int)status);
matrix.fillScreen(0);
matrix.show();
// GIF setup
GIF.begin(LITTLE_ENDIAN_PIXELS);
}
// LOOP FUNCTION - RUNS REPEATEDLY UNTIL RESET / POWER OFF -----------------
int16_t GIFindex = -1; // Current file index in GIFpath
int8_t GIFincrement = 1; // +1 = next GIF, -1 = prev, 0 = same
uint32_t GIFstartTime = 0; // When current GIF started playing
bool GIFisOpen = false; // True if GIF is currently open
void loop() {
if (msc_changed) { // If filesystem has changed...
msc_changed = false; // Clear flag
GIFincrement = 1; // Set index to next file when we resume here
return; // Prioritize USB, handled in calling func
}
#if defined(BACK_BUTTON)
if(!digitalRead(BACK_BUTTON)) {
GIFincrement = -1; // Back
while(!digitalRead(BACK_BUTTON)); // Wait for release
}
#endif
#if defined(NEXT_BUTTON)
if(!digitalRead(NEXT_BUTTON)) {
GIFincrement = 1; // Forward
while(!digitalRead(NEXT_BUTTON)); // Wait for release
}
#endif
if (GIFincrement) { // Change file?
if (GIFisOpen) { // If currently playing,
GIF.close(); // stop it
GIFisOpen = false;
}
GIFindex += GIFincrement; // Fwd or back 1 file
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
char *filename = filenameByIndex(GIFpath, "GIF", GIFindex);
if (filename) {
char fullname[sizeof GIFpath + 256];
sprintf(fullname, "%s/%s", GIFpath, filename); // Absolute path to GIF
Serial.printf("Opening file '%s'\n", fullname);
if (GIF.open(fullname, GIFOpenFile, GIFCloseFile,
GIFReadFile, GIFSeekFile, GIFDraw)) {
matrix.fillScreen(0);
Serial.printf("GIF dimensions: %d x %d\n",
GIF.getCanvasWidth(), GIF.getCanvasHeight());
xPos = (matrix.width() - GIF.getCanvasWidth()) / 2; // Center on matrix
yPos = (matrix.height() - GIF.getCanvasHeight()) / 2;
GIFisOpen = true;
GIFstartTime = millis();
GIFincrement = 0; // Reset increment flag
}
}
} else if(GIFisOpen) {
if (GIF.playFrame(true, NULL) >= 0) { // Auto resets to start if needed
matrix.show();
if ((millis() - GIFstartTime) >= (GIFminimumTime * 1000)) {
GIFincrement = 1; // Minimum time has elapsed, proceed to next GIF
}
} else {
GIFincrement = 1; // Decode error, proceed to next GIF
}
}
}

View file

@ -1,142 +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
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.
*/
#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;
#else // 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;
#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,180 @@
/* ----------------------------------------------------------------------
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};
uint8_t clockPin = 14;
uint8_t latchPin = 15;
uint8_t oePin = 16;
#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 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;
#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 MAGIG FOR DOUBLE-BUFFERING!
// Sundry globals used for animation ---------------------------------------
int16_t textX = matrix.width(), // Current text position (X)
textY, // Current text position (Y)
textMin, // Text pos. (X) when scrolled off left edge
hue = 0;
char str[50]; // Buffer to hold scrolling message text
int8_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
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); // ark 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

@ -0,0 +1,140 @@
/* ----------------------------------------------------------------------
"Pixel dust" Protomatter library example. As written, this is
SPECIFICALLY FOR THE ADAFRUIT MATRIXPORTAL M4 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 HEIGHT == 64 // 64-pixel tall matrices have 5 address lines:
uint8_t addrPins[] = {17, 18, 19, 20, 21};
#else // 32-pixel tall matrices have 4 address lines:
uint8_t addrPins[] = {17, 18, 19, 20};
#endif
// Remaining pins are the same for all matrix sizes. These values
// are for MatrixPortal M4. See "simple" example for other boards.
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
uint8_t clockPin = 14;
uint8_t latchPin = 15;
uint8_t oePin = 16;
Adafruit_Protomatter matrix(
WIDTH, 4, 1, rgbPins, sizeof(addrPins), addrPins,
clockPin, latchPin, oePin, true);
Adafruit_LIS3DH accel = Adafruit_LIS3DH();
#define N_COLORS 8
#define BOX_HEIGHT 8
#define N_GRAINS (BOX_HEIGHT*N_COLORS*8)
uint16_t colors[N_COLORS];
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) {
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] = 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
}
// 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,105 +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
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.
*/
#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;
#else // 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;
#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);
}

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

@ -0,0 +1,298 @@
/* ----------------------------------------------------------------------
"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};
uint8_t clockPin = 14;
uint8_t latchPin = 15;
uint8_t oePin = 16;
#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 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;
#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
// 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!
*/

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

@ -0,0 +1,136 @@
/* ----------------------------------------------------------------------
"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};
uint8_t clockPin = 14;
uint8_t latchPin = 15;
uint8_t oePin = 16;
#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 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;
#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 name=Adafruit Protomatter
version=0.0.0 version=1.2.0
author=Adafruit author=Adafruit
maintainer=Adafruit <info@adafruit.com> maintainer=Adafruit <info@adafruit.com>
sentence=This is a library for the Adafruit RGB LED matrix. sentence=A library for Adafruit RGB LED matrices.
paragraph=RGB LED matrix. paragraph=RGB LED matrix.
category=Display category=Display
url=https://github.com/adafruit/Adafruit_protomatter url=https://github.com/adafruit/Adafruit_protomatter
architectures=* architectures=samd,nrf52,stm32,esp32
depends=Adafruit GFX Library depends=Adafruit GFX Library, Adafruit LIS3DH, Adafruit PixelDust, AnimatedGIF, Adafruit SPIFlash, Adafruit TinyUSB Library

View file

@ -0,0 +1,141 @@
/*!
* @file Adafruit_Protomatter.cpp
*
* @mainpage Adafruit Protomatter RGB LED matrix library.
*
* @section intro_sec Introduction
*
* This is documentation for Adafruit's protomatter library for HUB75-style
* RGB LED matrices. It is designed to work with various matrices sold by
* Adafruit ("HUB75" is a vague term and other similar matrices are not
* guaranteed to work). This file is the Arduino-specific calls; the
* underlying C code is more platform-neutral.
*
* Adafruit invests time and resources providing this open source code,
* please support Adafruit and open-source hardware by purchasing products
* from Adafruit!
*
* @section dependencies Dependencies
*
* This library depends on
* <a href="https://github.com/adafruit/Adafruit-GFX-Library">Adafruit_GFX</a>
* being present on your system. Please make sure you have installed the
* latest version before using this library.
*
* @section author Author
*
* Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for
* Adafruit Industries, with contributions from the open source community.
*
* @section license License
*
* BSD license, all text here must be included in any redistribution.
*
*/
// Arduino-specific wrapper for the Protomatter C library (provides
// constructor and so forth, builds on Adafruit_GFX). There should
// not be any device-specific #ifdefs here. See notes in core.c and
// arch/arch.h regarding portability.
#include "Adafruit_Protomatter.h" // Also includes core.h & Adafruit_GFX.h
extern Protomatter_core *_PM_protoPtr; ///< In core.c (via arch.h)
Adafruit_Protomatter::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,
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)
// Arguments are passed through to the C _PM_init() function which does
// some input validation and minor allocation. Return value is ignored
// because we can't really do anything about it in a C++ constructor.
// 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, tile,
timer);
}
Adafruit_Protomatter::~Adafruit_Protomatter(void) {
_PM_deallocate(&core);
_PM_protoPtr = NULL;
}
ProtomatterStatus Adafruit_Protomatter::begin(void) {
_PM_protoPtr = &core;
return _PM_begin(&core);
}
// Transfer data from GFXcanvas16 to the matrix framebuffer's weird
// internal format. The actual conversion functions referenced below
// are in core.c, reasoning is explained there.
void Adafruit_Protomatter::show(void) {
_PM_convert_565(&core, getBuffer(), WIDTH);
_PM_swapbuffer_maybe(&core);
}
// 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
// the matrix (since this is difficult to estimate beforehand).
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);
}

152
src/Adafruit_Protomatter.h Normal file
View file

@ -0,0 +1,152 @@
// Arduino-specific header, accompanies Adafruit_Protomatter.cpp.
// There should not be any device-specific #ifdefs here.
#pragma once
#include "core.h"
#include <Adafruit_GFX.h>
/*!
@brief Class representing the Arduino-facing side of the Protomatter
library. Subclass of Adafruit_GFX's GFXcanvas16 to allow all
the drawing operations.
*/
class Adafruit_Protomatter : public GFXcanvas16 {
public:
/*!
@brief Adafruit_Protomatter constructor.
@param bitWidth Total width of RGB matrix chain, in pixels.
Usu. some multiple of 32, but maybe exceptions.
@param bitDepth Color "depth" in bitplanes, determines range of
shades of red, green and blue. e.g. passing 4
bits = 16 shades ea. R,G,B = 16x16x16 = 4096
colors. Max is 6, since the GFX library works
with "565" RGB colors (6 bits green, 5 red/blue).
@param rgbCount Number of "sets" of RGB data pins, each set
containing 6 pins (2 ea. R,G,B). Typically 1,
indicating a single matrix (or matrix chain).
In theory (but not yet extensively tested),
multiple sets of pins can be driven in parallel,
up to 5 on some devices (if the hardware design
provides all those bits on one PORT).
@param rgbList A uint8_t array of pins (Arduino pin numbering),
6X the prior rgbCount value, corresponding to
the 6 output color bits for a matrix (or chain).
Order is upper-half red, green, blue, lower-half
red, green blue (repeat for each add'l chain).
All the RGB pins (plus the clock pin below on
some architectures) MUST be on the same PORT
register. It's recommended (but not required)
that all RGB pins (and clock depending on arch)
be within the same byte of a PORT (but do not
need to be sequential or contiguous within that
byte) for more efficient RAM utilization. For
two concurrent chains, same principle but 16-bit
word instead of byte.
@param addrCount Number of row address lines required of matrix.
Total pixel height is then 2 x 2^addrCount, e.g.
32-pixel-tall matrices have 4 row address lines.
@param addrList A uint8_t array of pins (Arduino pin numbering),
one per row address line.
@param clockPin RGB clock pin (Arduino pin #).
@param latchPin RGB data latch pin (Arduino pin #).
@param oePin Output enable pin (Arduino pin #), active low.
@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).
*/
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, int8_t tile = 1, void *timer = NULL);
~Adafruit_Protomatter(void);
/*!
@brief Start a Protomatter matrix display running -- initialize
pins, timer and interrupt into existence.
@return A ProtomatterStatus status, one of:
PROTOMATTER_OK if everything is good.
PROTOMATTER_ERR_PINS if data and/or clock pins are split
across different PORTs.
PROTOMATTER_ERR_MALLOC if insufficient RAM to allocate
display memory.
PROTOMATTER_ERR_ARG if a bad value was passed to the
constructor.
*/
ProtomatterStatus begin(void);
/*!
@brief Process data from GFXcanvas16 to the matrix framebuffer's
internal format for display.
*/
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
math with other intervals), can be used to get a rough
frames-per-second value for the matrix (since this is
difficult to estimate beforehand).
@return Frame count since previous call to function, as a uint32_t.
*/
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);
private:
Protomatter_core core; // Underlying C struct
void convert_byte(uint8_t *dest); // GFXcanvas16-to-matrix
void convert_word(uint16_t *dest); // conversion functions
void convert_long(uint32_t *dest); // for 8/16/32 bit bufs
};

209
src/arch/arch.h Normal file
View file

@ -0,0 +1,209 @@
/*!
* @file arch.h
*
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
* This file establishes some very low-level things and includes headers
* specific to each supported device. This should ONLY be included by
* core.c, nowhere else. Ever.
*
* 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
#include <string.h>
/*
Common ground for architectures to support this library:
- 32-bit device (e.g. ARM core, ESP32, potentially others in the future)
- One or more 32-bit GPIO PORTs with atomic bitmask SET and CLEAR registers.
A TOGGLE register, if present, may improve performance but is NOT required.
- Tolerate 8-bit or word-aligned 16-bit accesses within the 32-bit PORT
registers (e.g. writing just one of four bytes, rather than the whole
32 bits). The library does not use any unaligned accesses (i.e. the
"middle word" of a 32-bit register), even if a device tolerates such.
"Pin" as used in this code is always a uint8_t value, but the semantics
of what it means may vary between Arduino and non-Arduino situations.
In Arduino, it's the pin index one would pass to functions such as
digitalWrite(), and doesn't necessarily correspond to physical hardware
pins or any other arrangement. Some may have names like 'A0' that really
just map to higher indices.
In non-Arduino settings (CircuitPython, other languages, etc.), how a
pin index relates to hardware is entirely implementation dependent, and
how to get from one to the other is what must be implemented in this file.
Quite often an environment will follow the Arduino pin designations
(since the numbers are on a board's silkscreen) and will have an internal
table mapping those indices to registers and bitmasks...but probably not
an identically-named and -structured table to the Arduino code, hence the
reason for many "else" situations in this code.
Each architecture defines the following macros and/or functions (the _PM_
prefix on each is to reduce likelihood of naming collisions...especially
on ESP32, which has some similarly-named timer functions:
GPIO-related macros/functions:
_PM_portOutRegister(pin): Get address of PORT out register. Code calling
this can cast it to whatever type's needed.
_PM_portSetRegister(pin): Get address of PORT set-bits register.
_PM_portClearRegister(pin): Get address of PORT clear-bits register.
_PM_portToggleRegister(pin): Get address of PORT toggle-bits register.
Not all devices support this, in which case
it must be left undefined.
_PM_portBitMask(pin): Get bit mask within PORT register corresponding
to a pin number. When compiling for Arduino,
this just maps to digitalPinToBitMask(), other
environments will need an equivalent.
_PM_byteOffset(pin): Get index of byte (0 to 3) within 32-bit PORT
corresponding to a pin number.
_PM_wordOffset(pin): Get index of word (0 or 1) within 32-bit PORT
corresponding to a pin number.
_PM_pinOutput(pin): Set a pin to output mode. In Arduino this maps
to pinMode(pin, OUTPUT). Other environments
will need an equivalent.
_PM_pinInput(pin): Set a pin to input mode, no pullup. In Arduino
this maps to pinMode(pin, INPUT).
_PM_pinHigh(pin): Set an output pin to a high or 1 state. In
Arduino this maps to digitalWrite(pin, HIGH).
_PM_pinLow(pin): Set an output pin to a low or 0 state. In
Arduino this maps to digitalWrite(pin, LOW).
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).
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
used to uniquely identify a timer peripheral within a given environment. For
example, in the Arduino wrapper for this library, compiling for SAMD chips,
it's just a pointer directly to a timer/counter peripheral base address. If
an implementation needs more data associated alongside a peripheral, this
could instead be a pointer to a struct, or an integer index.
Other macros/functions:
_PM_chunkSize: Matrix bitmap width (both in RAM and as issued
to the device) is rounded up (if necessary) to
a multiple of this value as a way of explicitly
unrolling the innermost data-stuffing loops.
So far all HUB75 displays I've encountered are
a multiple of 32 pixels wide, but in case
something new comes along, or if a larger
unroll actually decreases performance due to
cache size, this can be set to whatever works
best (any additional data is simply shifted
out the other end of the matrix). Default if
unspecified is 8 (e.g. four loop passes on a
32-pixel matrix, eight if 64-pixel). Only
certain chunkSizes are actually implemented,
see .cpp code (avoiding GCC-specific tricks
that would handle arbitrary chunk sizes).
_PM_delayMicroseconds(us): Function or macro to delay some number of
microseconds. For Arduino, this just maps to
delayMicroseconds(). Other environments will
need to provide their own or map to an
an equivalent function.
_PM_clockHoldHigh: Additional code (typically some number of NOPs)
needed to delay the clock fall after RGB data is
written to PORT. Only required on fast devices.
If left undefined, no delay happens.
_PM_clockHoldLow: Additional code (e.g. NOPs) needed to delay
clock rise after writing RGB data to PORT.
No delay if left undefined.
_PM_minMinPeriod: Mininum value for the "minPeriod" class member,
so bit-angle-modulation time always doubles with
each bitplane (else lower bits may be the same).
_PM_allocate: Memory allocation function, should return a
pointer to a buffer of requested size, aligned
to the architecture's largest native type.
If not defined, malloc() is used.
_PM_free: Corresponding deallocator for _PM_allocate().
If not defined, free() is used.
*/
// ENVIRONMENT-SPECIFIC DECLARATIONS ---------------------------------------
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
#include <Arduino.h> // Pull in all that stuff.
#define _PM_delayMicroseconds(us) delayMicroseconds(us)
#define _PM_pinOutput(pin) pinMode(pin, OUTPUT)
#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 --------------------
#include "py/mphal.h"
#include "shared-bindings/microcontroller/Pin.h"
#define _PM_delayMicroseconds(us) mp_hal_delay_us(us)
// No #else here. In non-Arduino case, declare things in the arch-specific
// files below...unless other environments provide device-neutral functions
// as above, in which case those could go here (w/#elif).
#endif // END CIRCUITPYTHON ------------------------------------------------
// ARCHITECTURE-SPECIFIC HEADERS -------------------------------------------
#include "esp32.h"
#include "nrf52.h"
#include "rp2040.h"
#include "samd-common.h"
#include "samd21.h"
#include "samd51.h"
#include "stm32.h"
#include "teensy4.h"
// DEFAULTS IF NOT DEFINED ABOVE -------------------------------------------
#if !defined(_PM_chunkSize)
#define _PM_chunkSize 8 ///< Unroll data-stuffing loop to this size
#endif
#if !defined(_PM_clockHoldHigh)
#define _PM_clockHoldHigh ///< Extra cycles (if any) on clock HIGH signal
#endif
#if !defined(_PM_clockHoldLow)
#define _PM_clockHoldLow ///< Extra cycles (if any) on clock LOW signal
#endif
#if !defined(_PM_minMinPeriod)
#define _PM_minMinPeriod 100 ///< Minimum timer interval for least bit
#endif
#if !defined(_PM_allocate)
#define _PM_allocate(x) (malloc((x))) ///< Memory alloc call
#endif
#if !defined(_PM_free)
#define _PM_free(x) (free((x))) ///< Corresponding memory free call
#endif
#if !defined(IRAM_ATTR)
#define IRAM_ATTR ///< Neutralize ESP32-specific attribute in core.c
#endif
#if !defined(_PM_PORT_TYPE)
#define _PM_PORT_TYPE uint32_t ///< PORT register size/type
#endif

206
src/arch/esp32.h Normal file
View file

@ -0,0 +1,206 @@
/*!
* @file esp32.h
*
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
* This file contains ESP32-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
#if defined(ESP32)
#include "driver/timer.h"
#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)
#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
// 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)
#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.
#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
#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);
}
#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.
// 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
#include "driver/gpio.h"
#include "hal/timer_ll.h"
#include "peripherals/timer.h"
#define _PM_STRICT_32BIT_IO (1)
#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)
#define _PM_portBitMask(pin) (1U << ((pin)&31))
// 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 bool _PM_esp32timerCallback(void *unused) {
if (_PM_protoPtr) {
_PM_row_handler(_PM_protoPtr); // In core.c
}
return false;
};
// Initialize, but do not start, timer.
void _PM_timerInit(void *tptr) {
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_index_t *timer = (timer_index_t *)tptr;
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);
}
// Set timer period, initialize count value to zero, enable timer.
IRAM_ATTR void _PM_timerStart(void *tptr, uint32_t period) {
timer_index_t *timer = (timer_index_t *)tptr;
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);
}
IRAM_ATTR uint32_t _PM_timerGetCount(void *tptr) {
timer_index_t *timer = (timer_index_t *)tptr;
timer->hw->hw_timer[timer->idx].update.update = 1;
return timer->hw->hw_timer[timer->idx].cnt_low;
}
// Disable timer and return current count value.
// Timer must be previously initialized.
IRAM_ATTR uint32_t _PM_timerStop(void *tptr) {
timer_index_t *timer = (timer_index_t *)tptr;
timer_ll_set_counter_enable(timer->hw, timer->idx, false);
return _PM_timerGetCount(tptr);
}
#endif // END CIRCUITPYTHON ------------------------------------------------
#endif // END ESP32

216
src/arch/nrf52.h Normal file
View file

@ -0,0 +1,216 @@
/*!
* @file nrf52.h
*
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
* This file contains NRF52-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
#if defined(NRF52_SERIES)
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
// digitalPinToPort, g_ADigitalPinMap[] are Arduino specific:
void *_PM_portOutRegister(uint32_t pin) {
NRF_GPIO_Type *port = digitalPinToPort(pin);
return &port->OUT;
}
void *_PM_portSetRegister(uint32_t pin) {
NRF_GPIO_Type *port = digitalPinToPort(pin);
return &port->OUTSET;
}
void *_PM_portClearRegister(uint32_t pin) {
NRF_GPIO_Type *port = digitalPinToPort(pin);
return &port->OUTCLR;
}
// Leave _PM_portToggleRegister(pin) undefined on nRF!
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define _PM_byteOffset(pin) ((g_ADigitalPinMap[pin] & 0x1F) / 8)
#define _PM_wordOffset(pin) ((g_ADigitalPinMap[pin] & 0x1F) / 16)
#else
#define _PM_byteOffset(pin) (3 - ((g_ADigitalPinMap[pin] & 0x1F) / 8))
#define _PM_wordOffset(pin) (1 - ((g_ADigitalPinMap[pin] & 0x1F) / 16))
#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;
// Arduino implementation is tied to a specific timer/counter,
// Partly because IRQs must be declared at compile-time.
#define _PM_IRQ_HANDLER TIMER4_IRQHandler
#define _PM_timerFreq 16000000
#define _PM_TIMER_DEFAULT NRF_TIMER4
#ifdef __cplusplus
extern "C" {
#endif
// Timer interrupt service routine
void _PM_IRQ_HANDLER(void) {
if (_PM_TIMER_DEFAULT->EVENTS_COMPARE[0]) {
_PM_TIMER_DEFAULT->EVENTS_COMPARE[0] = 0;
}
_PM_row_handler(_PM_protoPtr); // In core.c
}
#ifdef __cplusplus
}
#endif
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
#include "nrf_gpio.h"
volatile uint32_t *_PM_portOutRegister(uint32_t pin) {
NRF_GPIO_Type *port = nrf_gpio_pin_port_decode(&pin);
return &port->OUT;
}
volatile uint32_t *_PM_portSetRegister(uint32_t pin) {
NRF_GPIO_Type *port = nrf_gpio_pin_port_decode(&pin);
return &port->OUTSET;
}
volatile uint32_t *_PM_portClearRegister(uint32_t pin) {
NRF_GPIO_Type *port = nrf_gpio_pin_port_decode(&pin);
return &port->OUTCLR;
}
#define _PM_pinOutput(pin) \
nrf_gpio_cfg(pin, NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT, \
NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE)
#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))
#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
// CircuitPython implementation is tied to a specific freq (but the counter
// is dynamically allocated):
#define _PM_timerFreq 16000000
// 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;
// Timer interrupt service routine
void _PM_IRQ_HANDLER(void) {
NRF_TIMER_Type *timer = (((Protomatter_core *)_PM_protoPtr)->timer);
if (timer->EVENTS_COMPARE[0]) {
timer->EVENTS_COMPARE[0] = 0;
}
_PM_row_handler(_PM_protoPtr); // In core.c
}
#else // END CIRCUITPYTHON -------------------------------------------------
// Byte offset macros, timer and ISR work for other environments go here.
#endif
// CODE COMMON TO ALL ENVIRONMENTS -----------------------------------------
void _PM_timerInit(void *tptr) {
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},
#endif
#if defined(NRF_TIMER1)
{NRF_TIMER1, TIMER1_IRQn},
#endif
#if defined(NRF_TIMER2)
{NRF_TIMER2, TIMER2_IRQn},
#endif
#if defined(NRF_TIMER3)
{NRF_TIMER3, TIMER3_IRQn},
#endif
#if defined(NRF_TIMER4)
{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)) {
timerNum++;
}
if (timerNum >= NUM_TIMERS)
return;
NRF_TIMER_Type *tc = timer[timerNum].tc;
tc->TASKS_STOP = 1; // Stop timer
tc->MODE = TIMER_MODE_MODE_Timer; // Timer (not counter) mode
tc->TASKS_CLEAR = 1;
tc->BITMODE = TIMER_BITMODE_BITMODE_16Bit
<< TIMER_BITMODE_BITMODE_Pos; // 16-bit timer res
tc->PRESCALER = 0; // 1:1 prescale (16 MHz)
tc->INTENSET = TIMER_INTENSET_COMPARE0_Enabled
<< TIMER_INTENSET_COMPARE0_Pos; // Event 0 interrupt
// NVIC_DisableIRQ(timer[timerNum].IRQn);
// NVIC_ClearPendingIRQ(timer[timerNum].IRQn);
// NVIC_SetPriority(timer[timerNum].IRQn, 0); // Top priority
NVIC_EnableIRQ(timer[timerNum].IRQn);
}
inline void _PM_timerStart(void *tptr, uint32_t period) {
volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)tptr;
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];
}
uint32_t _PM_timerStop(void *tptr) {
volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)tptr;
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;
}
#define _PM_clockHoldHigh asm("nop; nop");
#define _PM_minMinPeriod 100
#endif // END NRF52_SERIES

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

@ -0,0 +1,245 @@
/*!
* @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
// TO DO: PUT A *PROPER* RP2040 CHECK HERE
#if defined(PICO_BOARD) || defined(__RP2040__)
#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
// TEMPORARY: FORCING ARDUINO COMPILATION FOR INITIAL C TESTING
#if !defined(CIRCUITPY)
#define ARDUINO
#endif
// 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 ------------------------------
// 'pin' here is GPXX # -- that might change in Arduino implementation
#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
// Arduino implementation is tied to a specific PWM slice & frequency
#define _PM_PWM_SLICE 0
#define _PM_PWM_DIV 3 // ~41.6 MHz, similar to SAMD
#define _PM_timerFreq (125000000 / _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
// Initialize, but do not start, timer.
void _PM_timerInit(void *tptr) {
#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
}
#endif
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
// '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 (125000000 / _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
// Initialize, but do not start, timer.
void _PM_timerInit(void *tptr) {
#if _PM_CLOCK_PWM
_PM_pwm_slice = (int)tptr & 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
}
#endif
#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;
#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)
// 'pin' here is GPXX # -- that might change in Arduino implementation
#define _PM_portBitMask(pin) (1UL << pin)
// Same for these -- using GPXX #, but Arduino might assign different order
#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
#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(void *tptr, 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(void *tptr) {
#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(void *tptr) {
#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(tptr);
}
#define _PM_chunkSize 8
#define _PM_clockHoldLow asm("nop; nop;");
#if _PM_CLOCK_PWM
#define _PM_minMinPeriod 100
#else
#define _PM_minMinPeriod 8
#endif
#endif // END PICO_BOARD

98
src/arch/samd-common.h Normal file
View file

@ -0,0 +1,98 @@
/*!
* @file samd-common.h
*
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
* This file contains SAMD-SPECIFIC CODE (SAMD51 & SAMD21).
*
* 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(__SAMD51__) || defined(SAM_D5X_E5X) || defined(_SAMD21_) || \
defined(SAMD21)
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
// g_APinDescription[] table and pin indices are Arduino specific:
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define _PM_byteOffset(pin) (g_APinDescription[pin].ulPin / 8)
#define _PM_wordOffset(pin) (g_APinDescription[pin].ulPin / 16)
#else
#define _PM_byteOffset(pin) (3 - (g_APinDescription[pin].ulPin / 8))
#define _PM_wordOffset(pin) (1 - (g_APinDescription[pin].ulPin / 16))
#endif
// Arduino implementation is tied to a specific timer/counter & freq:
#if defined(TC4)
#define _PM_TIMER_DEFAULT TC4
#define _PM_IRQ_HANDLER TC4_Handler
#else // No TC4 on some M4's
#define _PM_TIMER_DEFAULT TC3
#define _PM_IRQ_HANDLER TC3_Handler
#endif
#define _PM_timerFreq 48000000
// Partly because IRQs must be declared at compile-time, and partly
// because we know Arduino's already set up one of the GCLK sources
// for 48 MHz.
// 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;
// Timer interrupt service routine
void _PM_IRQ_HANDLER(void) {
// Clear overflow flag:
_PM_TIMER_DEFAULT->COUNT16.INTFLAG.reg = TC_INTFLAG_OVF;
_PM_row_handler(_PM_protoPtr); // In core.c
}
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
#include "hal_gpio.h"
#define _PM_pinOutput(pin) gpio_set_pin_direction(pin, GPIO_DIRECTION_OUT)
#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))
#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
// CircuitPython implementation is tied to a specific freq (but the counter
// is dynamically allocated):
#define _PM_timerFreq 48000000
// As currently implemented, there can be only one instance of the
// Protomatter_core struct. This pointer is set up when starting the matrix.
void *_PM_protoPtr = NULL;
// Timer interrupt service routine
void _PM_IRQ_HANDLER(void) {
((Tc *)(((Protomatter_core *)_PM_protoPtr)->timer))->COUNT16.INTFLAG.reg =
TC_INTFLAG_OVF;
_PM_row_handler(_PM_protoPtr); // In core.c
}
#else // END CIRCUITPYTHON -------------------------------------------------
// Byte offset macros, timer and ISR work for other environments go here.
#endif
#endif // END SAMD5x/SAME5x/SAMD21

150
src/arch/samd21.h Normal file
View file

@ -0,0 +1,150 @@
/*!
* @file samd21.h
*
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
* This file contains SAMD21-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
#if defined(_SAMD21_) || defined(SAMD21) // Arduino, Circuitpy SAMD21 defs
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
// g_APinDescription[] table and pin indices are Arduino specific:
#define _PM_portOutRegister(pin) \
&PORT_IOBUS->Group[g_APinDescription[pin].ulPort].OUT.reg
#define _PM_portSetRegister(pin) \
&PORT_IOBUS->Group[g_APinDescription[pin].ulPort].OUTSET.reg
#define _PM_portClearRegister(pin) \
&PORT_IOBUS->Group[g_APinDescription[pin].ulPort].OUTCLR.reg
#define _PM_portToggleRegister(pin) \
&PORT_IOBUS->Group[g_APinDescription[pin].ulPort].OUTTGL.reg
#else // END ARDUINO -------------------------------------------------------
// Non-Arduino port register lookups go here, if not already declared
// in samd-common.h.
#endif
// CODE COMMON TO ALL ENVIRONMENTS -----------------------------------------
// Initialize, but do not start, timer
void _PM_timerInit(void *tptr) {
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},
#endif
#if defined(TC1)
{TC1, TC1_IRQn, GCM_TCC0_TCC1},
#endif
#if defined(TC2)
{TC2, TC2_IRQn, GCM_TCC2_TC3},
#endif
#if defined(TC3)
{TC3, TC3_IRQn, GCM_TCC2_TC3},
#endif
#if defined(TC4)
{TC4, TC4_IRQn, GCM_TC4_TC5},
#endif
};
#define NUM_TIMERS (sizeof timer / sizeof timer[0])
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
uint8_t timerNum = 0;
while ((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tc)) {
timerNum++;
}
if (timerNum >= NUM_TIMERS)
return;
// Enable GCLK for timer/counter
GCLK->CLKCTRL.reg = (uint16_t)(GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 |
GCLK_CLKCTRL_ID(timer[timerNum].GCM_ID));
while (GCLK->STATUS.bit.SYNCBUSY == 1)
;
// Counter must first be disabled to configure it
tc->COUNT16.CTRLA.bit.ENABLE = 0;
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
;
tc->COUNT16.CTRLA.reg = // Configure timer counter
TC_CTRLA_PRESCALER_DIV1 | // 1:1 Prescale
TC_CTRLA_WAVEGEN_MFRQ | // Match frequency generation mode (MFRQ)
TC_CTRLA_MODE_COUNT16; // 16-bit counter mode
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
;
tc->COUNT16.CTRLBCLR.reg = TCC_CTRLBCLR_DIR; // Count up
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
;
// Overflow interrupt
tc->COUNT16.INTENSET.reg = TC_INTENSET_OVF;
NVIC_DisableIRQ(timer[timerNum].IRQn);
NVIC_ClearPendingIRQ(timer[timerNum].IRQn);
NVIC_SetPriority(timer[timerNum].IRQn, 0); // Top priority
NVIC_EnableIRQ(timer[timerNum].IRQn);
// Timer is configured but NOT enabled by default
}
// 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
tc->COUNT16.COUNT.reg = 0;
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
;
tc->COUNT16.CC[0].reg = period;
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
;
tc->COUNT16.CTRLA.bit.ENABLE = 1;
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
;
}
// 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
tc->COUNT16.READREQ.reg = TC_READREQ_RCONT | TC_READREQ_ADDR(0x10);
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
;
return tc->COUNT16.COUNT.reg;
}
// 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);
tc->COUNT16.CTRLA.bit.ENABLE = 0;
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
;
return count;
}
#endif // END _SAMD21_ || SAMD21

216
src/arch/samd51.h Normal file
View file

@ -0,0 +1,216 @@
/*!
* @file samd51.h
*
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
* This file contains SAMD51-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
#if defined(__SAMD51__) || \
defined(SAM_D5X_E5X) // Arduino, Circuitpy SAMD5x / E5x defs
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
// g_APinDescription[] table and pin indices are Arduino specific:
#define _PM_portOutRegister(pin) \
&PORT->Group[g_APinDescription[pin].ulPort].OUT.reg
#define _PM_portSetRegister(pin) \
&PORT->Group[g_APinDescription[pin].ulPort].OUTSET.reg
#define _PM_portClearRegister(pin) \
&PORT->Group[g_APinDescription[pin].ulPort].OUTCLR.reg
#define _PM_portToggleRegister(pin) \
&PORT->Group[g_APinDescription[pin].ulPort].OUTTGL.reg
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
#define _PM_portOutRegister(pin) (&PORT->Group[(pin / 32)].OUT.reg)
#define _PM_portSetRegister(pin) (&PORT->Group[(pin / 32)].OUTSET.reg)
#define _PM_portClearRegister(pin) (&PORT->Group[(pin / 32)].OUTCLR.reg)
#define _PM_portToggleRegister(pin) (&PORT->Group[(pin / 32)].OUTTGL.reg)
#define F_CPU (120000000)
#else
// Other port register lookups go here
#endif
// CODE COMMON TO ALL ENVIRONMENTS -----------------------------------------
// Initialize, but do not start, timer
void _PM_timerInit(void *tptr) {
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},
#endif
#if defined(TC1)
{TC1, TC1_IRQn, TC1_GCLK_ID},
#endif
#if defined(TC2)
{TC2, TC2_IRQn, TC2_GCLK_ID},
#endif
#if defined(TC3)
{TC3, TC3_IRQn, TC3_GCLK_ID},
#endif
#if defined(TC4)
{TC4, TC4_IRQn, TC4_GCLK_ID},
#endif
#if defined(TC5)
{TC5, TC5_IRQn, TC5_GCLK_ID},
#endif
#if defined(TC6)
{TC6, TC6_IRQn, TC6_GCLK_ID},
#endif
#if defined(TC7)
{TC7, TC7_IRQn, TC7_GCLK_ID},
#endif
#if defined(TC8)
{TC8, TC8_IRQn, TC8_GCLK_ID},
#endif
#if defined(TC9)
{TC9, TC9_IRQn, TC9_GCLK_ID},
#endif
#if defined(TC10)
{TC10, TC10_IRQn, TC10_GCLK_ID},
#endif
#if defined(TC11)
{TC11, TC11_IRQn, TC11_GCLK_ID},
#endif
#if defined(TC12)
{TC12, TC12_IRQn, TC12_GCLK_ID},
#endif
};
#define NUM_TIMERS (sizeof timer / sizeof timer[0])
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
uint8_t timerNum = 0;
while ((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tc)) {
timerNum++;
}
if (timerNum >= NUM_TIMERS)
return;
// Feed timer/counter off GCLK1 (already set 48 MHz by Arduino core).
// Sure, SAMD51 can run timers up to F_CPU (e.g. 120 MHz or up to
// 200 MHz with overclocking), but on higher bitplanes (which have
// progressively longer timer periods) I could see this possibly
// exceeding a 16-bit timer, and would have to switch prescalers.
// We don't actually need atomic precision on the timer -- point is
// simply that the period doubles with each bitplane, and this can
// work fine at 48 MHz.
GCLK->PCHCTRL[timer[timerNum].GCLK_ID].bit.CHEN = 0; // Disable
while (GCLK->PCHCTRL[timer[timerNum].GCLK_ID].bit.CHEN)
; // Wait for it
GCLK_PCHCTRL_Type pchctrl; // Read-modify-store
pchctrl.reg = GCLK->PCHCTRL[timer[timerNum].GCLK_ID].reg;
pchctrl.bit.GEN = GCLK_PCHCTRL_GEN_GCLK1_Val;
pchctrl.bit.CHEN = 1;
GCLK->PCHCTRL[timer[timerNum].GCLK_ID].reg = pchctrl.reg;
while (!GCLK->PCHCTRL[timer[timerNum].GCLK_ID].bit.CHEN)
;
// Disable timer before configuring it
tc->COUNT16.CTRLA.bit.ENABLE = 0;
while (tc->COUNT16.SYNCBUSY.bit.ENABLE)
;
// 16-bit counter mode, 1:1 prescale
tc->COUNT16.CTRLA.bit.MODE = TC_CTRLA_MODE_COUNT16;
tc->COUNT16.CTRLA.bit.PRESCALER = TC_CTRLA_PRESCALER_DIV1_Val;
tc->COUNT16.WAVE.bit.WAVEGEN =
TC_WAVE_WAVEGEN_MFRQ_Val; // Match frequency generation mode (MFRQ)
tc->COUNT16.CTRLBCLR.reg = TC_CTRLBCLR_DIR; // Count up
while (tc->COUNT16.SYNCBUSY.bit.CTRLB)
;
// Overflow interrupt
tc->COUNT16.INTENSET.reg = TC_INTENSET_OVF;
NVIC_DisableIRQ(timer[timerNum].IRQn);
NVIC_ClearPendingIRQ(timer[timerNum].IRQn);
NVIC_SetPriority(timer[timerNum].IRQn, 0); // Top priority
NVIC_EnableIRQ(timer[timerNum].IRQn);
// Timer is configured but NOT enabled by default
}
// 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
tc->COUNT16.COUNT.reg = 0;
while (tc->COUNT16.SYNCBUSY.bit.COUNT)
;
tc->COUNT16.CC[0].reg = period;
while (tc->COUNT16.SYNCBUSY.bit.CC0)
;
tc->COUNT16.CTRLA.bit.ENABLE = 1;
while (tc->COUNT16.SYNCBUSY.bit.STATUS)
;
}
// 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
tc->COUNT16.CTRLBSET.bit.CMD = 0x4; // Sync COUNT
while (tc->COUNT16.CTRLBSET.bit.CMD)
; // Wait for command
return tc->COUNT16.COUNT.reg;
}
// 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);
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");
#endif
#define _PM_minMinPeriod 160
#endif // END __SAMD51__ || SAM_D5X_E5X

146
src/arch/stm32.h Normal file
View file

@ -0,0 +1,146 @@
/*!
* @file stm32.h
*
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
* This file contains STM32-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
#if defined(STM32F4_SERIES) || defined(STM32F405xx) // Arduino, CircuitPy
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
// Arduino port register lookups go here, else ones in arch.h are used.
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
#include "timers.h"
#undef _PM_portBitMask
#define _PM_portBitMask(pin) (1u << ((pin)&15))
#define _PM_byteOffset(pin) ((pin & 15) / 8)
#define _PM_wordOffset(pin) ((pin & 15) / 16)
#define _PM_pinOutput(pin_) \
do { \
int8_t pin = (pin_); \
GPIO_InitTypeDef GPIO_InitStruct = {0}; \
GPIO_InitStruct.Pin = 1 << (pin & 15); \
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; \
GPIO_InitStruct.Pull = GPIO_NOPULL; \
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; \
HAL_GPIO_Init(pin_port(pin / 16), &GPIO_InitStruct); \
} while (0)
#define _PM_pinInput(pin_) \
do { \
int8_t pin = (pin_); \
GPIO_InitTypeDef GPIO_InitStruct = {0}; \
GPIO_InitStruct.Pin = 1 << (pin & 15); \
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; \
GPIO_InitStruct.Pull = GPIO_NOPULL; \
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; \
HAL_GPIO_Init(pin_port(pin / 16), &GPIO_InitStruct); \
} while (0)
#define _PM_pinHigh(pin) \
HAL_GPIO_WritePin(pin_port(pin / 16), 1 << (pin & 15), GPIO_PIN_SET)
#define _PM_pinLow(pin) \
HAL_GPIO_WritePin(pin_port(pin / 16), 1 << (pin & 15), GPIO_PIN_RESET)
#define _PM_PORT_TYPE uint16_t
volatile uint16_t *_PM_portOutRegister(uint32_t pin) {
return (uint16_t *)&pin_port(pin / 16)->ODR;
}
volatile uint16_t *_PM_portSetRegister(uint32_t pin) {
return (uint16_t *)&pin_port(pin / 16)->BSRR;
}
// To make things interesting, STM32F4xx places the set and clear
// GPIO bits within a single register. The "clear" bits are upper, so
// offset by 1 in uint16_ts
volatile uint16_t *_PM_portClearRegister(uint32_t pin) {
return 1 + (uint16_t *)&pin_port(pin / 16)->BSRR;
}
// TODO: was this somehow specific to TIM6?
#define _PM_timerFreq 42000000
// 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().
// TODO: this is no longer true, should it change?
void *_PM_protoPtr = NULL;
STATIC TIM_HandleTypeDef tim_handle;
// Timer interrupt service routine
void _PM_IRQ_HANDLER(void) {
// Clear overflow flag:
//_PM_TIMER_DEFAULT->COUNT16.INTFLAG.reg = TC_INTFLAG_OVF;
_PM_row_handler(_PM_protoPtr); // In core.c
}
// Initialize, but do not start, timer
void _PM_timerInit(void *tptr) {
TIM_TypeDef *tim_instance = (TIM_TypeDef *)tptr;
stm_peripherals_timer_reserve(tim_instance);
// Set IRQs at max priority and start clock
stm_peripherals_timer_preinit(tim_instance, 0, _PM_IRQ_HANDLER);
tim_handle.Instance = tim_instance;
tim_handle.Init.Period = 1000; // immediately replaced.
tim_handle.Init.Prescaler = 0;
tim_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
tim_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
tim_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
HAL_TIM_Base_Init(&tim_handle);
size_t tim_irq = stm_peripherals_timer_get_irqnum(tim_instance);
HAL_NVIC_DisableIRQ(tim_irq);
NVIC_ClearPendingIRQ(tim_irq);
NVIC_SetPriority(tim_irq, 0); // Top priority
}
inline void _PM_timerStart(void *tptr, uint32_t period) {
TIM_TypeDef *tim = tptr;
tim->SR = 0;
tim->ARR = period;
tim->CR1 |= TIM_CR1_CEN;
tim->DIER |= TIM_DIER_UIE;
HAL_NVIC_EnableIRQ(stm_peripherals_timer_get_irqnum(tim));
}
inline uint32_t _PM_timerGetCount(void *tptr) {
TIM_TypeDef *tim = tptr;
return tim->CNT;
}
uint32_t _PM_timerStop(void *tptr) {
TIM_TypeDef *tim = tptr;
HAL_NVIC_DisableIRQ(stm_peripherals_timer_get_irqnum(tim));
tim->CR1 &= ~TIM_CR1_CEN;
tim->DIER &= ~TIM_DIER_UIE;
return tim->CNT;
}
// settings from M4 for >= 150MHz, we use this part at 168MHz
#define _PM_clockHoldHigh asm("nop; nop; nop");
#define _PM_clockHoldLow asm("nop");
#define _PM_minMinPeriod 140
#endif // END CIRCUITPYTHON ------------------------------------------------
#endif // END STM32F4_SERIES || STM32F405xx

172
src/arch/teensy4.h Normal file
View file

@ -0,0 +1,172 @@
/*!
* @file teensy4.h
*
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
* This file contains i.MX 1062 (Teensy 4.x) 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
#if defined(__IMXRT1062__)
// i.MX only allows full 32-bit aligned writes to GPIO.
#define _PM_STRICT_32BIT_IO ///< Change core.c behavior for long accesses only
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
static const struct {
volatile uint32_t *base; ///< GPIO base address for pin
uint8_t bit; ///< GPIO bit number for pin (0-31)
} _PM_teensyPins[] = {
{&CORE_PIN0_PORTREG, CORE_PIN0_BIT},
{&CORE_PIN1_PORTREG, CORE_PIN1_BIT},
{&CORE_PIN2_PORTREG, CORE_PIN2_BIT},
{&CORE_PIN3_PORTREG, CORE_PIN3_BIT},
{&CORE_PIN4_PORTREG, CORE_PIN4_BIT},
{&CORE_PIN5_PORTREG, CORE_PIN5_BIT},
{&CORE_PIN6_PORTREG, CORE_PIN6_BIT},
{&CORE_PIN7_PORTREG, CORE_PIN7_BIT},
{&CORE_PIN8_PORTREG, CORE_PIN8_BIT},
{&CORE_PIN9_PORTREG, CORE_PIN9_BIT},
{&CORE_PIN10_PORTREG, CORE_PIN10_BIT},
{&CORE_PIN11_PORTREG, CORE_PIN11_BIT},
{&CORE_PIN12_PORTREG, CORE_PIN12_BIT},
{&CORE_PIN13_PORTREG, CORE_PIN13_BIT},
{&CORE_PIN14_PORTREG, CORE_PIN14_BIT},
{&CORE_PIN15_PORTREG, CORE_PIN15_BIT},
{&CORE_PIN16_PORTREG, CORE_PIN16_BIT},
{&CORE_PIN17_PORTREG, CORE_PIN17_BIT},
{&CORE_PIN18_PORTREG, CORE_PIN18_BIT},
{&CORE_PIN19_PORTREG, CORE_PIN19_BIT},
{&CORE_PIN20_PORTREG, CORE_PIN20_BIT},
{&CORE_PIN21_PORTREG, CORE_PIN21_BIT},
{&CORE_PIN22_PORTREG, CORE_PIN22_BIT},
{&CORE_PIN23_PORTREG, CORE_PIN23_BIT},
{&CORE_PIN24_PORTREG, CORE_PIN24_BIT},
{&CORE_PIN25_PORTREG, CORE_PIN25_BIT},
{&CORE_PIN26_PORTREG, CORE_PIN26_BIT},
{&CORE_PIN27_PORTREG, CORE_PIN27_BIT},
{&CORE_PIN28_PORTREG, CORE_PIN28_BIT},
{&CORE_PIN29_PORTREG, CORE_PIN29_BIT},
{&CORE_PIN30_PORTREG, CORE_PIN30_BIT},
{&CORE_PIN31_PORTREG, CORE_PIN31_BIT},
{&CORE_PIN32_PORTREG, CORE_PIN32_BIT},
{&CORE_PIN33_PORTREG, CORE_PIN33_BIT},
{&CORE_PIN34_PORTREG, CORE_PIN34_BIT},
{&CORE_PIN35_PORTREG, CORE_PIN35_BIT},
{&CORE_PIN36_PORTREG, CORE_PIN36_BIT},
{&CORE_PIN37_PORTREG, CORE_PIN37_BIT},
{&CORE_PIN38_PORTREG, CORE_PIN38_BIT},
{&CORE_PIN39_PORTREG, CORE_PIN39_BIT},
};
#define _PM_SET_OFFSET 33 ///< 0x84 byte offset = 33 longs
#define _PM_CLEAR_OFFSET 34 ///< 0x88 byte offset = 34 longs
#define _PM_TOGGLE_OFFSET 35 ///< 0x8C byte offset = 35 longs
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define _PM_byteOffset(pin) (_PM_teensyPins[pin].bit / 8)
#define _PM_wordOffset(pin) (_PM_teensyPins[pin].bit / 16)
#else
#define _PM_byteOffset(pin) (3 - (_PM_teensyPins[pin].bit / 8))
#define _PM_wordOffset(pin) (1 - (_PM_teensyPins[pin].bit / 16))
#endif
#define _PM_portOutRegister(pin) (void *)_PM_teensyPins[pin].base
#define _PM_portSetRegister(pin) \
((volatile uint32_t *)_PM_teensyPins[pin].base + _PM_SET_OFFSET)
#define _PM_portClearRegister(pin) \
((volatile uint32_t *)_PM_teensyPins[pin].base + _PM_CLEAR_OFFSET)
#define _PM_portToggleRegister(pin) \
((volatile uint32_t *)_PM_teensyPins[pin].base + _PM_TOGGLE_OFFSET)
// 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;
// Code as written works with the Periodic Interrupt Timer directly,
// rather than using the Teensy IntervalTimer library, reason being we
// need to be able to poll the current timer value in _PM_timerGetCount(),
// but that's not available from IntervalTimer, and the timer base address
// it keeps is a private member (possible alternative is to do dirty pool
// and access the pointer directly, knowing it's the first element in the
// IntervalTimer object, but this is fraught with peril).
#define _PM_timerFreq 24000000 // 24 MHz
#define _PM_timerNum 0 // PIT timer #0 (can be 0-3)
#define _PM_TIMER_DEFAULT (IMXRT_PIT_CHANNELS + _PM_timerNum) // PIT channel *
// Interrupt service routine for Periodic Interrupt Timer
static void _PM_timerISR(void) {
IMXRT_PIT_CHANNEL_t *timer = _PM_TIMER_DEFAULT;
_PM_row_handler(_PM_protoPtr); // In core.c
timer->TFLG = 1; // Clear timer interrupt
}
// Initialize, but do not start, timer.
void _PM_timerInit(void *tptr) {
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)tptr;
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
timer->LDVAL = 100000; // Timer initial load value
// Interrupt is attached but not enabled yet
attachInterruptVector(IRQ_PIT, &_PM_timerISR);
NVIC_ENABLE_IRQ(IRQ_PIT);
}
// 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;
timer->TCTRL = 0; // Disable timer and interrupt
timer->LDVAL = period; // Set load value
// timer->CVAL = period; // And current value (just in case?)
timer->TFLG = 1; // Clear timer interrupt
timer->TCTRL = 3; // Enable timer and interrupt
}
// 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;
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;
timer->TCTRL = 0; // Disable timer and interrupt
return _PM_timerGetCount(tptr);
}
#define _PM_clockHoldHigh \
asm("nop; nop; nop; nop; nop; nop; nop;"); \
asm("nop; nop; nop; nop; nop; nop; nop;");
#define _PM_clockHoldLow \
asm("nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;"); \
asm("nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;");
#define _PM_chunkSize 1 ///< DON'T unroll loop, Teensy 4 is SO FAST
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
// Teensy 4 CircuitPython magic goes here.
#endif // END CIRCUITPYTHON ------------------------------------------------
#endif // END __IMXRT1062__ (Teensy 4)

1302
src/core.c Normal file

File diff suppressed because it is too large Load diff

274
src/core.h Normal file
View file

@ -0,0 +1,274 @@
/*!
* @file core.h
*
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
*
* 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
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdint.h>
/** Status type returned by some functions. */
typedef enum {
PROTOMATTER_OK, // Everything is hunky-dory!
PROTOMATTER_ERR_PINS, // Clock and/or data pins on different PORTs
PROTOMATTER_ERR_MALLOC, // Couldn't allocate memory for display
PROTOMATTER_ERR_ARG, // Bad input to function
} ProtomatterStatus;
/** Struct for matrix control lines NOT related to RGB data or clock, i.e.
latch, OE and address lines. RGB data and clock ("RGBC") are handled
differently as they have specific requirements (and might use a toggle
register if present). The data conversion functions need bitmasks for
RGB data but do NOT need the set or clear registers, so those items are
also declared as separate things in the core structure that follows. */
typedef struct {
volatile void *setReg; ///< GPIO bit set register
volatile void *clearReg; ///< GPIO bit clear register
uint32_t bit; ///< GPIO bitmask
uint8_t pin; ///< Some unique ID, e.g. Arduino pin #
} _PM_pin;
/** Struct with info about an RGB matrix chain and lots of state and buffer
details for the library. Toggle-related items in this structure MUST be
declared even if the device lacks GPIO bit-toggle registers (i.e. don't
do an ifdef check around these). All hardware-specific details (including
the presence or lack of toggle registers) are isolated to a single
file -- arch.h -- which should ONLY be included by core.c, and ifdef'ing
them would result in differing representations of this structure which
must be shared between the library and calling code. (An alternative is
to put any toggle-specific stuff at the end of the struct with an ifdef
check, but that's just dirty pool and asking for trouble.) */
typedef struct {
void *timer; ///< Arch-specific timer/counter info
void *setReg; ///< RGBC bit set register (cast to use)
void *clearReg; ///< RGBC bit clear register "
void *toggleReg; ///< RGBC bit toggle register "
uint8_t *rgbPins; ///< Array of RGB data pins (mult of 6)
void *rgbMask; ///< PORT bit mask for each RGB pin
uint32_t clockMask; ///< PORT bit mask for RGB clock
uint32_t rgbAndClockMask; ///< PORT bit mask for RGB data + clock
volatile void *addrPortToggle; ///< See singleAddrPort below
void *screenData; ///< Per-bitplane RGB data for matrix
_PM_pin latch; ///< RGB data latch
_PM_pin oe; ///< !OE (LOW out enable)
_PM_pin *addr; ///< Array of address pins
uint32_t bufferSize; ///< Bytes per matrix buffer
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 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
uint8_t numAddressLines; ///< Number of address line pins
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
volatile uint8_t plane; ///< Current bitplane (changes in ISR)
volatile uint8_t row; ///< Current scanline (changes in ISR)
volatile uint8_t prevRow; ///< Scanline from prior ISR
volatile bool swapBuffers; ///< If 1, awaiting double-buf switch
} Protomatter_core;
// Protomatter core function prototypes. Environment-specific code (like the
// Adafruit_Protomatter class for Arduino) calls on these underlying things,
// and has to provide a few extras of its own (interrupt handlers and such).
// User code shouldn't need to invoke any of them directly.
/*!
@brief Initialize values in Protomatter_core structure.
@param core Pointer to Protomatter_core structure.
@param bitWidth Total width of RGB matrix chain, in pixels.
Usu. some multiple of 32, but maybe exceptions.
@param bitDepth Color "depth" in bitplanes, determines range of
shades of red, green and blue. e.g. passing 4
bits = 16 shades ea. R,G,B = 16x16x16 = 4096
colors.
@param rgbCount Number of "sets" of RGB data pins, each set
containing 6 pins (2 ea. R,G,B). Typically 1,
indicating a single matrix (or matrix chain).
In theory (but not yet extensively tested),
multiple sets of pins can be driven in parallel,
up to 5 on some devices (if the hardware design
provides all those bits on one PORT).
@param rgbList A uint8_t array of pins (values are platform-
dependent), 6X the prior rgbCount value,
corresponding to the 6 output color bits for a
matrix (or chain). Order is upper-half red, green,
blue, lower-half red, green blue (repeat for each
add'l chain). All the RGB pins (plus the clock pin
below on some architectures) MUST be on the same
PORT register. It's recommended (but not required)
that all RGB pins (and clock depending on arch) be
within the same byte of a PORT (but do not need to
be sequential or contiguous within that byte) for
more efficient RAM utilization. For two concurrent
chains, same principle but 16-bit word.
@param addrCount Number of row address lines required of matrix.
Total pixel height is then 2 x 2^addrCount, e.g.
32-pixel-tall matrices have 4 row address lines.
@param addrList A uint8_t array of pins (platform-dependent pin
numbering), one per row address line.
@param clockPin RGB clock pin (platform-dependent pin #).
@param latchPin RGB data latch pin (platform-dependent pin #).
@param oePin Output enable pin (platform-dependent pin #),
active low.
@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).
@return A ProtomatterStatus status, one of:
PROTOMATTER_OK if everything is good.
PROTOMATTER_ERR_PINS if data and/or clock pins are split across
different PORTs.
PROTOMATTER_ERR_MALLOC if insufficient RAM to allocate display
memory.
PROTOMATTER_ERR_ARG if a bad value (core or timer pointer) was
passed in.
*/
extern 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, int8_t tile, void *timer);
/*!
@brief Allocate display buffers and populate additional elements of a
Protomatter matrix.
@param core Pointer to Protomatter_core structure.
@return A ProtomatterStatus status, one of:
PROTOMATTER_OK if everything is good.
PROTOMATTER_ERR_PINS if data and/or clock pins are split across
different PORTs.
PROTOMATTER_ERR_MALLOC if insufficient RAM to allocate display
memory.
PROTOMATTER_ERR_ARG if a bad value.
*/
extern ProtomatterStatus _PM_begin(Protomatter_core *core);
/*!
@brief Disable (but do not deallocate) a Protomatter matrix. Disables
matrix by setting OE pin HIGH and writing all-zero data to
matrix shift registers, so it won't halt with lit LEDs.
@param core Pointer to Protomatter_core structure.
*/
extern void _PM_stop(Protomatter_core *core);
/*!
@brief Start or restart a matrix. Initialize counters, configure and
start timer.
@param core Pointer to Protomatter_core structure.
*/
extern void _PM_resume(Protomatter_core *core);
/*!
@brief Deallocate memory associated with Protomatter_core structure
(e.g. screen data, pin lists for data and rows). Does not
deallocate the structure itself.
@param core Pointer to Protomatter_core structure.
*/
extern void _PM_deallocate(Protomatter_core *core);
/*!
@brief Matrix "row handler" that's called by the timer interrupt.
Handles row address lines and issuing data to matrix.
@param core Pointer to Protomatter_core structure.
*/
extern void _PM_row_handler(Protomatter_core *core);
/*!
@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
other intervals), can be used to get a rough frames-per-second
value for the matrix (since this is difficult to estimate
beforehand).
@param core Pointer to Protomatter_core structure.
@return Frame count since previous call to function, as a uint32_t.
*/
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 period Timer 'top' / rollover value.
*/
extern void _PM_timerStart(void *tptr, 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).
@return Counter value when timer was stopped.
*/
extern uint32_t _PM_timerStop(void *tptr);
/*!
@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).
@return Counter value.
*/
extern uint32_t _PM_timerGetCount(void *tptr);
/*!
@brief Pauses until the next vertical blank to avoid 'tearing' animation
(if display is double-buffered). If single-buffered, has no effect.
@param core Pointer to Protomatter_core structure.
*/
extern void _PM_swapbuffer_maybe(Protomatter_core *core);
#if defined(ARDUINO) || defined(CIRCUITPY)
/*!
@brief Converts image data from GFX16 canvas to the matrices weird
internal format.
@param core Pointer to Protomatter_core structure.
@param source Pointer to source image data (see Adafruit_GFX 16-bit
canvas type for format).
@param width Width of canvas in pixels, as this may be different than
the matrix pixel width due to row padding.
*/
extern void _PM_convert_565(Protomatter_core *core, uint16_t *source,
uint16_t width);
#endif // END ARDUINO || CIRCUITPY
#ifdef __cplusplus
} // extern "C"
#endif