Compare commits
No commits in common. "master" and "gh-pages" have entirely different histories.
46
.github/ISSUE_TEMPLATE.md
vendored
|
|
@ -1,46 +0,0 @@
|
||||||
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
|
|
@ -1,26 +0,0 @@
|
||||||
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
|
|
@ -1,37 +0,0 @@
|
||||||
name: Arduino Library CI
|
|
||||||
|
|
||||||
on: [pull_request, push, repository_dispatch]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
arduino-platform: ["metro_m0", "metro_m4", "metroesp32s2", "feather_esp32s3", "feather_rp2040", "nrf52840", "esp32"]
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: '3.8'
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
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
|
|
@ -1,4 +0,0 @@
|
||||||
# Our handy .gitignore for automation ease
|
|
||||||
Doxyfile*
|
|
||||||
doxygen_sqlite3.db
|
|
||||||
html
|
|
||||||
1
.nojekyll
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
132
README.md
|
|
@ -1,132 +0,0 @@
|
||||||
# Adafruit_Protomatter [](https://github.com/adafruit/Adafruit_Protomatter/actions)
|
|
||||||
|
|
||||||
"I used protomatter in the Genesis matrix." - David Marcus, Star Trek III
|
|
||||||
|
|
||||||
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
|
|
||||||
peripherals beyond a timer interrupt, goal being portability).
|
|
||||||
|
|
||||||
# Matrix Concepts and Jargon
|
|
||||||
|
|
||||||
HUB75 RGB LED matrices are basically a set of six concurrent shift register
|
|
||||||
chains, each with one output bit per column, the six chains being red, green
|
|
||||||
and blue bits for two non-adjacent rows, plus a set of row drivers (each
|
|
||||||
driving the aforementioned two rows) selected by a combination of address
|
|
||||||
lines. The number of address lines determines the overall matrix height
|
|
||||||
(3 to 5 bits is common...as an example, 3 address lines = 2^3 = 8 distinct
|
|
||||||
address line combinations, each driving two rows = 16 pixels high). Address
|
|
||||||
0 enables rows 0 and height/2, address 1 enables rows 1 and height/2+1, etc.
|
|
||||||
Shift register chain length determines matrix width...32 and 64 pixels are
|
|
||||||
common...matrices can be chained to increase width, a 64-pixel wide matrix
|
|
||||||
is equivalent to two 32-pixel chained matrices, and so forth.
|
|
||||||
|
|
||||||
These matrices render only ONE BIT each for red, green and blue, they DO NOT
|
|
||||||
natively display full color and must be quickly refreshed by the driving
|
|
||||||
microcontroller, basically PWM-ing the intermediate shades (this in addition
|
|
||||||
to the row scanning that must be performed).
|
|
||||||
|
|
||||||
There are a few peculiar RGB LED matrices that have the same physical
|
|
||||||
connection but work a bit differently -- they might have only have three
|
|
||||||
shift register chains rather than six, or might use a shift register for
|
|
||||||
the row selection rather than a set of address lines. The code presented
|
|
||||||
here DOES NOT support these matrix variants. Aim is to provide support for
|
|
||||||
all HUB75 matrices in the Adafruit shop. Please don't submit pull requests
|
|
||||||
for these other matrices as we have no means to test them. If you require
|
|
||||||
this functionality, it's OK to create a fork of the code, which Git can
|
|
||||||
help keep up-to-date with any future changes here!
|
|
||||||
|
|
||||||
# Hardware Requirements and Jargon
|
|
||||||
|
|
||||||
The common ground for architectures to support this library:
|
|
||||||
|
|
||||||
* 32-bit device (e.g. ARM core, ESP32 and others)
|
|
||||||
* One or more 32-bit GPIO PORTs with atomic (single-cycle, not
|
|
||||||
read-modify-write) bitmask SET and CLEAR registers. A bitmask TOGGLE
|
|
||||||
register, if present, may improve performance but is NOT required.
|
|
||||||
* There may be performance or storage benefits if the architecture tolerates
|
|
||||||
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), but
|
|
||||||
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
|
|
||||||
|
|
||||||
This repository currently consists of:
|
|
||||||
|
|
||||||
* An Arduino C++ library (files Adafruit_Protomatter.cpp and
|
|
||||||
Adafruit_Protomatter.h, plus the "examples" directory). The Arduino code
|
|
||||||
is dependent on the Adafruit_GFX library.
|
|
||||||
|
|
||||||
* An underlying C library (files core.c, core.h and headers in the arch
|
|
||||||
directory) that might be adaptable to other runtime environments (e.g.
|
|
||||||
CircuitPython).
|
|
||||||
|
|
||||||
# Arduino Library
|
|
||||||
|
|
||||||
This supersedes the RGBmatrixPanel library on non-AVR devices, as the older
|
|
||||||
library has painted itself into a few corners. The newer library uses a
|
|
||||||
single constructor for all matrix setups, potentially handling parallel
|
|
||||||
chains (not yet fully implemented), various matrix sizes and chain lengths,
|
|
||||||
and variable bit depths from 1 to 6 (refresh rate is a function of all of
|
|
||||||
these). Note however that it is NOT A DROP-IN REPLACEMENT for RGBmatrixPanel.
|
|
||||||
The constructor is entirely different, and there are several changes in the
|
|
||||||
available functions. Also, all colors in the new library are specified as
|
|
||||||
5/6/5-bit RGB (as this is what the GFX library GFXcanvas16 type uses, being
|
|
||||||
aimed at low-cost color LCD displays), even if the matrix is configured for
|
|
||||||
a lower bit depth (colors will be decimated/quantized in this case).
|
|
||||||
|
|
||||||
It does have some new limitations, mostly significant RAM overhead (hence
|
|
||||||
no plans for AVR port) and (with a few exceptions) that all RGB data pins
|
|
||||||
and the clock pin MUST be on the same PORT register (e.g. all PORTA or PORTB
|
|
||||||
,can't intermix). RAM overhead is somewhat reduced (but still large) if
|
|
||||||
those pins are all in a single 8-bit byte within the PORT (they do not need
|
|
||||||
to be contiguous or sequential within this byte, if for instance it makes
|
|
||||||
PCB routing easier, but they should all aim for a single byte). Other pins
|
|
||||||
(matrix address lines, latch and output enable) can reside on any PORT or bit.
|
|
||||||
|
|
||||||
# C Library
|
|
||||||
|
|
||||||
The underlying C library is focused on *driving* the matrix and does not
|
|
||||||
provide any drawing operations of its own. That must be handled by
|
|
||||||
higher-level code, as in the Arduino wrapper which uses the Adafruit_GFX
|
|
||||||
drawing functions.
|
|
||||||
|
|
||||||
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
|
|
||||||
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
|
|
||||||
contiguous or sequential within that byte). Other pins (matrix address lines,
|
|
||||||
latch and output enable) can reside on any PORT or bit.
|
|
||||||
|
|
||||||
When adapting this code to new devices (e.g. iMX) or new runtime environments
|
|
||||||
(e.g. CircuitPython), goal is to put all the device- or platform-specific
|
|
||||||
code into a new header file in the arch directory (or completely separate
|
|
||||||
source files, as in the Arduino library .cpp and .h). core.c contains only
|
|
||||||
the device-neutral bitbang code and should not have any "#ifdef DEVICE"- or
|
|
||||||
"#ifdef ENVIRONMENT"-like lines (exception for the 565 color conversion
|
|
||||||
functions, since the internal representation is common to both Arduino and
|
|
||||||
CircuitPython). Macros for things like getting a PORT register address from
|
|
||||||
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).
|
|
||||||
|
|
||||||
Most macros and functions begin with the prefix **\_PM\_** in order to
|
|
||||||
avoid naming collisions with other code (exception being static functions,
|
|
||||||
which can't be seen outside their source file).
|
|
||||||
|
|
||||||
# Pull Requests
|
|
||||||
|
|
||||||
If you encounter artifacts (noise, sparkles, dropouts and other issues) and
|
|
||||||
it seems to resolve by adjusting the NOP counts, please do not submit this
|
|
||||||
as a PR claiming a fix. Quite often what improves stability for one matrix
|
|
||||||
type can make things worse for other types. Instead, open an issue and
|
|
||||||
describe the hardware (both microcontroller and RGB matrix) and what worked
|
|
||||||
for you. A general solution working across all matrix types typically
|
|
||||||
involves monitoring the signals on a logic analyzer and aiming for a 50%
|
|
||||||
duty cycle on the CLK signal, 20 MHz or less, and then testing across a
|
|
||||||
wide variety of different matrix types to confirm; trial and error on just
|
|
||||||
a single matrix type is problematic. Maintainers: this goes for you too.
|
|
||||||
Don't merge a "fix" unless you've checked it out on a 'scope and on tested
|
|
||||||
across a broad range of matrices.
|
|
||||||
|
|
@ -1,386 +0,0 @@
|
||||||
// Play GIFs from CIRCUITPY drive (USB-accessible filesystem) to LED matrix.
|
|
||||||
// ***DESIGNED FOR ADAFRUIT MATRIXPORTAL***, but may run on some other M4,
|
|
||||||
// M0, ESP32S3 and nRF52 boards (relies on TinyUSB stack). As written, runs
|
|
||||||
// on 64x32 pixel matrix, this can be changed by editing the WIDTH and HEIGHT
|
|
||||||
// definitions. See the "simple" example for a run-down on matrix config.
|
|
||||||
// Adapted from examples from Larry Bank's AnimatedGIF library and
|
|
||||||
// msc_external_flash example in Adafruit_TinyUSB_Arduino.
|
|
||||||
// Prerequisite libraries:
|
|
||||||
// - 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(ARDUINO_ARCH_ESP32)
|
|
||||||
static Adafruit_FlashTransport_ESP32 flashTransport;
|
|
||||||
#elif defined(EXTERNAL_FLASH_USE_QSPI)
|
|
||||||
Adafruit_FlashTransport_QSPI flashTransport;
|
|
||||||
#elif defined(EXTERNAL_FLASH_USE_SPI)
|
|
||||||
Adafruit_FlashTransport_SPI flashTransport(EXTERNAL_FLASH_USE_CS,
|
|
||||||
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(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3)
|
|
||||||
uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37};
|
|
||||||
uint8_t addrPins[] = {45, 36, 48, 35, 21}; // 16/32/64 pixels tall
|
|
||||||
uint8_t clockPin = 2;
|
|
||||||
uint8_t latchPin = 47;
|
|
||||||
uint8_t oePin = 14;
|
|
||||||
#define BACK_BUTTON 6
|
|
||||||
#define NEXT_BUTTON 7
|
|
||||||
#elif defined(_VARIANT_METRO_M4_)
|
|
||||||
uint8_t rgbPins[] = {2, 3, 4, 5, 6, 7};
|
|
||||||
uint8_t addrPins[] = {A0, A1, A2, A3}; // 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(const 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,212 +0,0 @@
|
||||||
/* ----------------------------------------------------------------------
|
|
||||||
Double-buffering (smooth animation) Protomatter library example.
|
|
||||||
PLEASE SEE THE "simple" EXAMPLE FOR AN INTRODUCTORY SKETCH.
|
|
||||||
Comments here pare down many of the basics and focus on the new concepts.
|
|
||||||
|
|
||||||
This example is written for a 64x32 matrix but can be adapted to others.
|
|
||||||
------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
#include <Adafruit_Protomatter.h>
|
|
||||||
#include <Fonts/FreeSansBold18pt7b.h> // Large friendly font
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------
|
|
||||||
The RGB matrix must be wired to VERY SPECIFIC pins, different for each
|
|
||||||
microcontroller board. This first section sets that up for a number of
|
|
||||||
supported boards.
|
|
||||||
------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
#if defined(_VARIANT_MATRIXPORTAL_M4_) // MatrixPortal M4
|
|
||||||
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
|
|
||||||
uint8_t addrPins[] = {17, 18, 19, 20, 21};
|
|
||||||
uint8_t clockPin = 14;
|
|
||||||
uint8_t latchPin = 15;
|
|
||||||
uint8_t oePin = 16;
|
|
||||||
#elif defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) // MatrixPortal ESP32-S3
|
|
||||||
uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37};
|
|
||||||
uint8_t addrPins[] = {45, 36, 48, 35, 21};
|
|
||||||
uint8_t clockPin = 2;
|
|
||||||
uint8_t latchPin = 47;
|
|
||||||
uint8_t oePin = 14;
|
|
||||||
#elif defined(_VARIANT_FEATHER_M4_) // Feather M4 + RGB Matrix FeatherWing
|
|
||||||
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
|
|
||||||
uint8_t addrPins[] = {A5, A4, A3, A2};
|
|
||||||
uint8_t clockPin = 13;
|
|
||||||
uint8_t latchPin = 0;
|
|
||||||
uint8_t oePin = 1;
|
|
||||||
#elif defined(__SAMD51__) // M4 Metro Variants (Express, AirLift)
|
|
||||||
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
|
|
||||||
uint8_t addrPins[] = {A5, A4, A3, A2};
|
|
||||||
uint8_t clockPin = 13;
|
|
||||||
uint8_t latchPin = 0;
|
|
||||||
uint8_t oePin = 1;
|
|
||||||
#elif defined(_SAMD21_) // Feather M0 variants
|
|
||||||
uint8_t rgbPins[] = {6, 7, 10, 11, 12, 13};
|
|
||||||
uint8_t addrPins[] = {0, 1, 2, 3};
|
|
||||||
uint8_t clockPin = SDA;
|
|
||||||
uint8_t latchPin = 4;
|
|
||||||
uint8_t oePin = 5;
|
|
||||||
#elif defined(NRF52_SERIES) // Special nRF52840 FeatherWing pinout
|
|
||||||
uint8_t rgbPins[] = {6, A5, A1, A0, A4, 11};
|
|
||||||
uint8_t addrPins[] = {10, 5, 13, 9};
|
|
||||||
uint8_t clockPin = 12;
|
|
||||||
uint8_t latchPin = PIN_SERIAL1_RX;
|
|
||||||
uint8_t oePin = PIN_SERIAL1_TX;
|
|
||||||
#elif USB_VID == 0x239A && USB_PID == 0x8113 // Feather ESP32-S3 No PSRAM
|
|
||||||
// M0/M4/RP2040 Matrix FeatherWing compatible:
|
|
||||||
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
|
|
||||||
uint8_t addrPins[] = {A5, A4, A3, A2};
|
|
||||||
uint8_t clockPin = 13; // Must be on same port as rgbPins
|
|
||||||
uint8_t latchPin = RX;
|
|
||||||
uint8_t oePin = TX;
|
|
||||||
#elif USB_VID == 0x239A && USB_PID == 0x80EB // Feather ESP32-S2
|
|
||||||
// M0/M4/RP2040 Matrix FeatherWing compatible:
|
|
||||||
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
|
|
||||||
uint8_t addrPins[] = {A5, A4, A3, A2};
|
|
||||||
uint8_t clockPin = 13; // Must be on same port as rgbPins
|
|
||||||
uint8_t latchPin = RX;
|
|
||||||
uint8_t oePin = TX;
|
|
||||||
#elif defined(ESP32)
|
|
||||||
// 'Safe' pins, not overlapping any peripherals:
|
|
||||||
// GPIO.out: 4, 12, 13, 14, 15, 21, 27, GPIO.out1: 32, 33
|
|
||||||
// Peripheral-overlapping pins, sorted from 'most expendible':
|
|
||||||
// 16, 17 (RX, TX)
|
|
||||||
// 25, 26 (A0, A1)
|
|
||||||
// 18, 5, 9 (MOSI, SCK, MISO)
|
|
||||||
// 22, 23 (SCL, SDA)
|
|
||||||
uint8_t rgbPins[] = {4, 12, 13, 14, 15, 21};
|
|
||||||
uint8_t addrPins[] = {16, 17, 25, 26};
|
|
||||||
uint8_t clockPin = 27; // Must be on same port as rgbPins
|
|
||||||
uint8_t latchPin = 32;
|
|
||||||
uint8_t oePin = 33;
|
|
||||||
#elif defined(ARDUINO_TEENSY40)
|
|
||||||
uint8_t rgbPins[] = {15, 16, 17, 20, 21, 22}; // A1-A3, A6-A8, skip SDA,SCL
|
|
||||||
uint8_t addrPins[] = {2, 3, 4, 5};
|
|
||||||
uint8_t clockPin = 23; // A9
|
|
||||||
uint8_t latchPin = 6;
|
|
||||||
uint8_t oePin = 9;
|
|
||||||
#elif defined(ARDUINO_TEENSY41)
|
|
||||||
uint8_t rgbPins[] = {26, 27, 38, 20, 21, 22}; // A12-14, A6-A8
|
|
||||||
uint8_t addrPins[] = {2, 3, 4, 5};
|
|
||||||
uint8_t clockPin = 23; // A9
|
|
||||||
uint8_t latchPin = 6;
|
|
||||||
uint8_t oePin = 9;
|
|
||||||
#elif defined(ARDUINO_ADAFRUIT_FEATHER_RP2040)
|
|
||||||
// RP2040 support requires the Earle Philhower board support package;
|
|
||||||
// will not compile with the Arduino Mbed OS board package.
|
|
||||||
// The following pinout works with the Adafruit Feather RP2040 and
|
|
||||||
// original RGB Matrix FeatherWing (M0/M4/RP2040, not nRF version).
|
|
||||||
// Pin numbers here are GP## numbers, which may be different than
|
|
||||||
// the pins printed on some boards' top silkscreen.
|
|
||||||
uint8_t rgbPins[] = {8, 7, 9, 11, 10, 12};
|
|
||||||
uint8_t addrPins[] = {25, 24, 29, 28};
|
|
||||||
uint8_t clockPin = 13;
|
|
||||||
uint8_t latchPin = 1;
|
|
||||||
uint8_t oePin = 0;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------
|
|
||||||
Matrix initialization is explained EXTENSIVELY in "simple" example sketch!
|
|
||||||
It's very similar here, but we're passing "true" for the last argument,
|
|
||||||
enabling double-buffering -- this permits smooth animation by having us
|
|
||||||
draw in a second "off screen" buffer while the other is being shown.
|
|
||||||
------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
Adafruit_Protomatter matrix(
|
|
||||||
64, // Matrix width in pixels
|
|
||||||
6, // Bit depth -- 6 here provides maximum color options
|
|
||||||
1, rgbPins, // # of matrix chains, array of 6 RGB pins for each
|
|
||||||
4, addrPins, // # of address pins (height is inferred), array of pins
|
|
||||||
clockPin, latchPin, oePin, // Other matrix control pins
|
|
||||||
true); // HERE IS THE MAGIC FOR DOUBLE-BUFFERING!
|
|
||||||
|
|
||||||
// Sundry globals used for animation ---------------------------------------
|
|
||||||
|
|
||||||
int16_t textX; // Current text position (X)
|
|
||||||
int16_t textY; // Current text position (Y)
|
|
||||||
int16_t textMin; // Text pos. (X) when scrolled off left edge
|
|
||||||
char str[64]; // Buffer to hold scrolling message text
|
|
||||||
int16_t ball[3][4] = {
|
|
||||||
{ 3, 0, 1, 1 }, // Initial X,Y pos+velocity of 3 bouncy balls
|
|
||||||
{ 17, 15, 1, -1 },
|
|
||||||
{ 27, 4, -1, 1 }
|
|
||||||
};
|
|
||||||
uint16_t ballcolor[3]; // Colors for bouncy balls (init in setup())
|
|
||||||
|
|
||||||
// SETUP - RUNS ONCE AT PROGRAM START --------------------------------------
|
|
||||||
|
|
||||||
void setup(void) {
|
|
||||||
Serial.begin(9600);
|
|
||||||
|
|
||||||
// Initialize matrix...
|
|
||||||
ProtomatterStatus status = matrix.begin();
|
|
||||||
Serial.print("Protomatter begin() status: ");
|
|
||||||
Serial.println((int)status);
|
|
||||||
if(status != PROTOMATTER_OK) {
|
|
||||||
// DO NOT CONTINUE if matrix setup encountered an error.
|
|
||||||
for(;;);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unlike the "simple" example, we don't do any drawing in setup().
|
|
||||||
// But we DO initialize some things we plan to animate...
|
|
||||||
|
|
||||||
// Set up the scrolling message...
|
|
||||||
sprintf(str, "Adafruit %dx%d RGB LED Matrix",
|
|
||||||
matrix.width(), matrix.height());
|
|
||||||
matrix.setFont(&FreeSansBold18pt7b); // Use nice bitmap font
|
|
||||||
matrix.setTextWrap(false); // Allow text off edge
|
|
||||||
matrix.setTextColor(0xFFFF); // White
|
|
||||||
int16_t x1, y1;
|
|
||||||
uint16_t w, h;
|
|
||||||
matrix.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); // How big is it?
|
|
||||||
textMin = -w; // All text is off left edge when it reaches this point
|
|
||||||
textX = matrix.width(); // Start off right edge
|
|
||||||
textY = matrix.height() / 2 - (y1 + h / 2); // Center text vertically
|
|
||||||
// Note: when making scrolling text like this, the setTextWrap(false)
|
|
||||||
// call is REQUIRED (to allow text to go off the edge of the matrix),
|
|
||||||
// AND it must be BEFORE the getTextBounds() call (or else that will
|
|
||||||
// return the bounds of "wrapped" text).
|
|
||||||
|
|
||||||
// Set up the colors of the bouncy balls.
|
|
||||||
ballcolor[0] = matrix.color565(0, 20, 0); // Dark green
|
|
||||||
ballcolor[1] = matrix.color565(0, 0, 20); // Dark blue
|
|
||||||
ballcolor[2] = matrix.color565(20, 0, 0); // Dark red
|
|
||||||
}
|
|
||||||
|
|
||||||
// LOOP - RUNS REPEATEDLY AFTER SETUP --------------------------------------
|
|
||||||
|
|
||||||
void loop(void) {
|
|
||||||
// Every frame, we clear the background and draw everything anew.
|
|
||||||
// This happens "in the background" with double buffering, that's
|
|
||||||
// why you don't see everything flicker. It requires double the RAM,
|
|
||||||
// so it's not practical for every situation.
|
|
||||||
|
|
||||||
matrix.fillScreen(0); // Fill background black
|
|
||||||
|
|
||||||
// Draw the big scrolly text
|
|
||||||
matrix.setCursor(textX, textY);
|
|
||||||
matrix.print(str);
|
|
||||||
|
|
||||||
// Update text position for next frame. If text goes off the
|
|
||||||
// left edge, reset its position to be off the right edge.
|
|
||||||
if((--textX) < textMin) textX = matrix.width();
|
|
||||||
|
|
||||||
// Draw the three bouncy balls on top of the text...
|
|
||||||
for(byte i=0; i<3; i++) {
|
|
||||||
// Draw 'ball'
|
|
||||||
matrix.fillCircle(ball[i][0], ball[i][1], 5, ballcolor[i]);
|
|
||||||
// Update ball's X,Y position for next frame
|
|
||||||
ball[i][0] += ball[i][2];
|
|
||||||
ball[i][1] += ball[i][3];
|
|
||||||
// Bounce off edges
|
|
||||||
if((ball[i][0] == 0) || (ball[i][0] == (matrix.width() - 1)))
|
|
||||||
ball[i][2] *= -1;
|
|
||||||
if((ball[i][1] == 0) || (ball[i][1] == (matrix.height() - 1)))
|
|
||||||
ball[i][3] *= -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AFTER DRAWING, A show() CALL IS REQUIRED TO UPDATE THE MATRIX!
|
|
||||||
|
|
||||||
matrix.show();
|
|
||||||
|
|
||||||
delay(20); // 20 milliseconds = ~50 frames/second
|
|
||||||
}
|
|
||||||
|
|
@ -1,149 +0,0 @@
|
||||||
/* ----------------------------------------------------------------------
|
|
||||||
"Pixel dust" Protomatter library example. As written, this is
|
|
||||||
SPECIFICALLY FOR THE ADAFRUIT MATRIXPORTAL with 64x32 pixel matrix.
|
|
||||||
Change "HEIGHT" below for 64x64 matrix. Could also be adapted to other
|
|
||||||
Protomatter-capable boards with an attached LIS3DH accelerometer.
|
|
||||||
|
|
||||||
PLEASE SEE THE "simple" EXAMPLE FOR AN INTRODUCTORY SKETCH,
|
|
||||||
or "doublebuffer" for animation basics.
|
|
||||||
------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
#include <Wire.h> // For I2C communication
|
|
||||||
#include <Adafruit_LIS3DH.h> // For accelerometer
|
|
||||||
#include <Adafruit_PixelDust.h> // For sand simulation
|
|
||||||
#include <Adafruit_Protomatter.h> // For RGB matrix
|
|
||||||
|
|
||||||
#define HEIGHT 32 // Matrix height (pixels) - SET TO 64 FOR 64x64 MATRIX!
|
|
||||||
#define WIDTH 64 // Matrix width (pixels)
|
|
||||||
#define MAX_FPS 45 // Maximum redraw rate, frames/second
|
|
||||||
|
|
||||||
#if defined(_VARIANT_MATRIXPORTAL_M4_) // MatrixPortal M4
|
|
||||||
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
|
|
||||||
uint8_t addrPins[] = {17, 18, 19, 20, 21};
|
|
||||||
uint8_t clockPin = 14;
|
|
||||||
uint8_t latchPin = 15;
|
|
||||||
uint8_t oePin = 16;
|
|
||||||
#else // MatrixPortal ESP32-S3
|
|
||||||
uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37};
|
|
||||||
uint8_t addrPins[] = {45, 36, 48, 35, 21};
|
|
||||||
uint8_t clockPin = 2;
|
|
||||||
uint8_t latchPin = 47;
|
|
||||||
uint8_t oePin = 14;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if HEIGHT == 16
|
|
||||||
#define NUM_ADDR_PINS 3
|
|
||||||
#elif HEIGHT == 32
|
|
||||||
#define NUM_ADDR_PINS 4
|
|
||||||
#elif HEIGHT == 64
|
|
||||||
#define NUM_ADDR_PINS 5
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Adafruit_Protomatter matrix(
|
|
||||||
WIDTH, 4, 1, rgbPins, NUM_ADDR_PINS, addrPins,
|
|
||||||
clockPin, latchPin, oePin, true);
|
|
||||||
|
|
||||||
Adafruit_LIS3DH accel = Adafruit_LIS3DH();
|
|
||||||
|
|
||||||
#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
|
|
||||||
}
|
|
||||||
|
|
@ -1,349 +0,0 @@
|
||||||
/* ----------------------------------------------------------------------
|
|
||||||
"Simple" Protomatter library example sketch (once you get past all
|
|
||||||
the various pin configurations at the top, and all the comments).
|
|
||||||
Shows basic use of Adafruit_Protomatter library with different devices.
|
|
||||||
|
|
||||||
This example is written for a 64x32 matrix but can be adapted to others.
|
|
||||||
|
|
||||||
Once the RGB matrix is initialized, most functions of the Adafruit_GFX
|
|
||||||
library are available for drawing -- code from other projects that use
|
|
||||||
LCDs or OLEDs can be easily adapted, or may be insightful for reference.
|
|
||||||
GFX library is documented here:
|
|
||||||
https://learn.adafruit.com/adafruit-gfx-graphics-library
|
|
||||||
------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
#include <Adafruit_Protomatter.h>
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------
|
|
||||||
The RGB matrix must be wired to VERY SPECIFIC pins, different for each
|
|
||||||
microcontroller board. This first section sets that up for a number of
|
|
||||||
supported boards. Notes have been moved to the bottom of the code.
|
|
||||||
------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
#if defined(_VARIANT_MATRIXPORTAL_M4_) // MatrixPortal M4
|
|
||||||
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
|
|
||||||
uint8_t addrPins[] = {17, 18, 19, 20, 21};
|
|
||||||
uint8_t clockPin = 14;
|
|
||||||
uint8_t latchPin = 15;
|
|
||||||
uint8_t oePin = 16;
|
|
||||||
#elif defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) // MatrixPortal ESP32-S3
|
|
||||||
uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37};
|
|
||||||
uint8_t addrPins[] = {45, 36, 48, 35, 21};
|
|
||||||
uint8_t clockPin = 2;
|
|
||||||
uint8_t latchPin = 47;
|
|
||||||
uint8_t oePin = 14;
|
|
||||||
#elif defined(_VARIANT_FEATHER_M4_) // Feather M4 + RGB Matrix FeatherWing
|
|
||||||
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
|
|
||||||
uint8_t addrPins[] = {A5, A4, A3, A2};
|
|
||||||
uint8_t clockPin = 13;
|
|
||||||
uint8_t latchPin = 0;
|
|
||||||
uint8_t oePin = 1;
|
|
||||||
#elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32C6) // Feather ESP32-C6
|
|
||||||
// not featherwing compatible, but can 'hand wire' if desired
|
|
||||||
uint8_t rgbPins[] = {6, A3, A1, A0, A2, 0};
|
|
||||||
uint8_t addrPins[] = {8, 5, 15, 7};
|
|
||||||
uint8_t clockPin = 14;
|
|
||||||
uint8_t latchPin = RX;
|
|
||||||
uint8_t oePin = TX;
|
|
||||||
#elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S2) // Feather ESP32-S2
|
|
||||||
// M0/M4/RP2040 Matrix FeatherWing compatible:
|
|
||||||
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
|
|
||||||
uint8_t addrPins[] = {A5, A4, A3, A2};
|
|
||||||
uint8_t clockPin = 13; // Must be on same port as rgbPins
|
|
||||||
uint8_t latchPin = RX;
|
|
||||||
uint8_t oePin = TX;
|
|
||||||
#elif defined(ARDUINO_METRO_ESP32S2) // Metro ESP32-S2
|
|
||||||
// Matrix Shield compatible:
|
|
||||||
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
|
|
||||||
uint8_t addrPins[] = {A0, A1, A2, A3};
|
|
||||||
uint8_t clockPin = 13; // Must be on same port as rgbPins
|
|
||||||
uint8_t latchPin = 15;
|
|
||||||
uint8_t oePin = 14;
|
|
||||||
#elif defined(__SAMD51__) // M4 Metro Variants (Express, AirLift)
|
|
||||||
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
|
|
||||||
uint8_t addrPins[] = {A5, A4, A3, A2};
|
|
||||||
uint8_t clockPin = 13;
|
|
||||||
uint8_t latchPin = 0;
|
|
||||||
uint8_t oePin = 1;
|
|
||||||
#elif defined(_SAMD21_) // Feather M0 variants
|
|
||||||
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
|
|
||||||
uint8_t addrPins[] = {A5, A4, A3, A2};
|
|
||||||
uint8_t clockPin = 13;
|
|
||||||
uint8_t latchPin = 0;
|
|
||||||
uint8_t oePin = 1;
|
|
||||||
#elif defined(NRF52_SERIES) // Special nRF52840 FeatherWing pinout
|
|
||||||
uint8_t rgbPins[] = {6, A5, A1, A0, A4, 11};
|
|
||||||
uint8_t addrPins[] = {10, 5, 13, 9};
|
|
||||||
uint8_t clockPin = 12;
|
|
||||||
uint8_t latchPin = PIN_SERIAL1_RX;
|
|
||||||
uint8_t oePin = PIN_SERIAL1_TX;
|
|
||||||
#elif USB_VID == 0x239A && USB_PID == 0x8113 // Feather ESP32-S3 No PSRAM
|
|
||||||
// M0/M4/RP2040 Matrix FeatherWing compatible:
|
|
||||||
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
|
|
||||||
uint8_t addrPins[] = {A5, A4, A3, A2};
|
|
||||||
uint8_t clockPin = 13; // Must be on same port as rgbPins
|
|
||||||
uint8_t latchPin = RX;
|
|
||||||
uint8_t oePin = TX;
|
|
||||||
#elif defined(ESP32)
|
|
||||||
// 'Safe' pins, not overlapping any peripherals:
|
|
||||||
// GPIO.out: 4, 12, 13, 14, 15, 21, 27, GPIO.out1: 32, 33
|
|
||||||
// Peripheral-overlapping pins, sorted from 'most expendible':
|
|
||||||
// 16, 17 (RX, TX)
|
|
||||||
// 25, 26 (A0, A1)
|
|
||||||
// 18, 5, 9 (MOSI, SCK, MISO)
|
|
||||||
// 22, 23 (SCL, SDA)
|
|
||||||
uint8_t rgbPins[] = {4, 12, 13, 14, 15, 21};
|
|
||||||
uint8_t addrPins[] = {16, 17, 25, 26};
|
|
||||||
uint8_t clockPin = 27; // Must be on same port as rgbPins
|
|
||||||
uint8_t latchPin = 32;
|
|
||||||
uint8_t oePin = 33;
|
|
||||||
#elif defined(ARDUINO_TEENSY40)
|
|
||||||
uint8_t rgbPins[] = {15, 16, 17, 20, 21, 22}; // A1-A3, A6-A8, skip SDA,SCL
|
|
||||||
uint8_t addrPins[] = {2, 3, 4, 5};
|
|
||||||
uint8_t clockPin = 23; // A9
|
|
||||||
uint8_t latchPin = 6;
|
|
||||||
uint8_t oePin = 9;
|
|
||||||
#elif defined(ARDUINO_TEENSY41)
|
|
||||||
uint8_t rgbPins[] = {26, 27, 38, 20, 21, 22}; // A12-14, A6-A8
|
|
||||||
uint8_t addrPins[] = {2, 3, 4, 5};
|
|
||||||
uint8_t clockPin = 23; // A9
|
|
||||||
uint8_t latchPin = 6;
|
|
||||||
uint8_t oePin = 9;
|
|
||||||
#elif defined(ARDUINO_ADAFRUIT_FEATHER_RP2040)
|
|
||||||
// RP2040 support requires the Earle Philhower board support package;
|
|
||||||
// will not compile with the Arduino Mbed OS board package.
|
|
||||||
// The following pinout works with the Adafruit Feather RP2040 and
|
|
||||||
// original RGB Matrix FeatherWing (M0/M4/RP2040, not nRF version).
|
|
||||||
// Pin numbers here are GP## numbers, which may be different than
|
|
||||||
// the pins printed on some boards' top silkscreen.
|
|
||||||
uint8_t rgbPins[] = {8, 7, 9, 11, 10, 12};
|
|
||||||
uint8_t addrPins[] = {25, 24, 29, 28};
|
|
||||||
uint8_t clockPin = 13;
|
|
||||||
uint8_t latchPin = 1;
|
|
||||||
uint8_t oePin = 0;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------
|
|
||||||
Okay, here's where the RGB LED matrix is actually declared...
|
|
||||||
|
|
||||||
First argument is the matrix width, in pixels. Usually 32 or
|
|
||||||
64, but might go larger if you're chaining multiple matrices.
|
|
||||||
|
|
||||||
Second argument is the "bit depth," which determines color
|
|
||||||
fidelity, applied to red, green and blue (e.g. "4" here means
|
|
||||||
4 bits red, 4 green, 4 blue = 2^4 x 2^4 x 2^4 = 4096 colors).
|
|
||||||
There is a trade-off between bit depth and RAM usage. Most
|
|
||||||
programs will tend to use either 1 (R,G,B on/off, 8 colors,
|
|
||||||
best for text, LED sand, etc.) or the maximum of 6 (best for
|
|
||||||
shaded images...though, because the GFX library was designed
|
|
||||||
for LCDs, only 5 of those bits are available for red and blue.
|
|
||||||
|
|
||||||
Third argument is the number of concurrent (parallel) matrix
|
|
||||||
outputs. THIS SHOULD ALWAYS BE "1" FOR NOW. Fourth is a uint8_t
|
|
||||||
array listing six pins: red, green and blue data out for the
|
|
||||||
top half of the display, and same for bottom half. There are
|
|
||||||
hard constraints as to which pins can be used -- they must all
|
|
||||||
be on the same PORT register, ideally all within the same byte
|
|
||||||
of that PORT.
|
|
||||||
|
|
||||||
Fifth argument is the number of "address" (aka row select) pins,
|
|
||||||
from which the matrix height is inferred. "4" here means four
|
|
||||||
address lines, matrix height is then (2 x 2^4) = 32 pixels.
|
|
||||||
16-pixel-tall matrices will have 3 pins here, 32-pixel will have
|
|
||||||
4, 64-pixel will have 5. Sixth argument is a uint8_t array
|
|
||||||
listing those pin numbers. No PORT constraints here.
|
|
||||||
|
|
||||||
Next three arguments are pin numbers for other RGB matrix
|
|
||||||
control lines: clock, latch and output enable (active low).
|
|
||||||
Clock pin MUST be on the same PORT register as RGB data pins
|
|
||||||
(and ideally in same byte). Other pins have no special rules.
|
|
||||||
|
|
||||||
Last argument is a boolean (true/false) to enable double-
|
|
||||||
buffering for smooth animation (requires 2X the RAM). See the
|
|
||||||
"doublebuffer" example for a demonstration.
|
|
||||||
------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
Adafruit_Protomatter matrix(
|
|
||||||
64, // Width of matrix (or matrix chain) in pixels
|
|
||||||
4, // Bit depth, 1-6
|
|
||||||
1, rgbPins, // # of matrix chains, array of 6 RGB pins for each
|
|
||||||
4, addrPins, // # of address pins (height is inferred), array of pins
|
|
||||||
clockPin, latchPin, oePin, // Other matrix control pins
|
|
||||||
false); // No double-buffering here (see "doublebuffer" example)
|
|
||||||
|
|
||||||
// SETUP - RUNS ONCE AT PROGRAM START --------------------------------------
|
|
||||||
|
|
||||||
void setup(void) {
|
|
||||||
Serial.begin(9600);
|
|
||||||
|
|
||||||
// Initialize matrix...
|
|
||||||
ProtomatterStatus status = matrix.begin();
|
|
||||||
Serial.print("Protomatter begin() status: ");
|
|
||||||
Serial.println((int)status);
|
|
||||||
if(status != PROTOMATTER_OK) {
|
|
||||||
// DO NOT CONTINUE if matrix setup encountered an error.
|
|
||||||
for(;;);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since this is a simple program with no animation, all the
|
|
||||||
// drawing can be done here in setup() rather than loop():
|
|
||||||
|
|
||||||
// Make four color bars (red, green, blue, white) with brightness ramp:
|
|
||||||
for(int x=0; x<matrix.width(); x++) {
|
|
||||||
uint8_t level = x * 256 / matrix.width(); // 0-255 brightness
|
|
||||||
matrix.drawPixel(x, matrix.height() - 4, matrix.color565(level, 0, 0));
|
|
||||||
matrix.drawPixel(x, matrix.height() - 3, matrix.color565(0, level, 0));
|
|
||||||
matrix.drawPixel(x, matrix.height() - 2, matrix.color565(0, 0, level));
|
|
||||||
matrix.drawPixel(x, matrix.height() - 1,
|
|
||||||
matrix.color565(level, level, level));
|
|
||||||
}
|
|
||||||
// You'll notice the ramp looks smoother as bit depth increases
|
|
||||||
// (second argument to the matrix constructor call above setup()).
|
|
||||||
|
|
||||||
// Simple shapes and text, showing GFX library calls:
|
|
||||||
matrix.drawCircle(12, 10, 9, matrix.color565(255, 0, 0));
|
|
||||||
matrix.drawRect(14, 6, 17, 17, matrix.color565(0, 255, 0));
|
|
||||||
matrix.drawTriangle(32, 9, 41, 27, 23, 27, matrix.color565(0, 0, 255));
|
|
||||||
matrix.println("ADAFRUIT"); // Default text color is white
|
|
||||||
if (matrix.height() > 32) {
|
|
||||||
matrix.setCursor(0, 32);
|
|
||||||
matrix.println("64 pixel"); // Default text color is white
|
|
||||||
matrix.println("matrix"); // Default text color is white
|
|
||||||
}
|
|
||||||
|
|
||||||
// AFTER DRAWING, A show() CALL IS REQUIRED TO UPDATE THE MATRIX!
|
|
||||||
|
|
||||||
matrix.show(); // Copy data to matrix buffers
|
|
||||||
}
|
|
||||||
|
|
||||||
// LOOP - RUNS REPEATEDLY AFTER SETUP --------------------------------------
|
|
||||||
|
|
||||||
void loop(void) {
|
|
||||||
// Since there's nothing more to be drawn, this loop() function just
|
|
||||||
// shows the approximate refresh rate of the matrix at current settings.
|
|
||||||
Serial.print("Refresh FPS = ~");
|
|
||||||
Serial.println(matrix.getFrameCount());
|
|
||||||
delay(1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// MORE NOTES --------------------------------------------------------------
|
|
||||||
|
|
||||||
/*
|
|
||||||
The "RGB and clock bits on same PORT register" constraint requires
|
|
||||||
considerable planning and knowledge of the underlying microcontroller
|
|
||||||
hardware. These are some earlier notes on various devices' PORT registers
|
|
||||||
and bits and their corresponding Arduino pin numbers. You probably won't
|
|
||||||
need this -- it's all codified in the #if defined() sections at the top
|
|
||||||
of this sketch now -- but keeping it around for reference if needed.
|
|
||||||
|
|
||||||
METRO M0 PORT-TO-PIN ASSIGNMENTS BY BYTE:
|
|
||||||
PA00 PA08 D4 PA16 D11 PB00 PB08 A1
|
|
||||||
PA01 PA09 D3 PA17 D13 PB01 PB09 A2
|
|
||||||
PA02 A0 PA10 D1 PA18 D10 PB02 A5 PB10 MOSI
|
|
||||||
PA03 PA11 D0 PA19 D12 PB03 PB11 SCK
|
|
||||||
PA04 A3 PA12 MISO PA20 D6 PB04 PB12
|
|
||||||
PA05 A4 PA13 PA21 D7 PB05 PB13
|
|
||||||
PA06 D8 PA14 D2 PA22 SDA PB06 PB14
|
|
||||||
PA07 D9 PA15 D5 PA23 SCL PB07 PB15
|
|
||||||
|
|
||||||
SAME, METRO M4:
|
|
||||||
PA00 PA08 PA16 D13 PB00 PB08 A4 PB16 D3
|
|
||||||
PA01 PA09 PA17 D12 PB01 PB09 A5 PB17 D2
|
|
||||||
PA02 A0 PA10 PA18 D10 PB02 SDA PB10 PB18
|
|
||||||
PA03 PA11 PA19 D11 PB03 SCL PB11 PB19
|
|
||||||
PA04 A3 PA12 MISO PA20 D9 PB04 PB12 D7 PB20
|
|
||||||
PA05 A1 PA13 SCK PA21 D8 PB05 PB13 D4 PB21
|
|
||||||
PA06 A2 PA14 MISO PA22 D1 PB06 PB14 D5 PB22
|
|
||||||
PA07 PA15 PA23 D0 PB07 PB15 D6 PB23
|
|
||||||
|
|
||||||
FEATHER M4:
|
|
||||||
PA00 PA08 PA16 D5 PB08 A2 PB16 D1/TX
|
|
||||||
PA01 PA09 PA17 SCK PB09 A3 PB17 D0/RX
|
|
||||||
PA02 A0 PA10 PA18 D6 PB10 PB18
|
|
||||||
PA03 PA11 PA19 D9 PB11 PB19
|
|
||||||
PA04 A4 PA12 SDA PA20 D10 PB12 PB20
|
|
||||||
PA05 A1 PA13 SCL PA21 D11 PB13 PB21
|
|
||||||
PA06 A5 PA14 D4 PA22 D12 PB14 PB22 MISO
|
|
||||||
PA07 PA15 PA23 D13 PB15 PB23 MOSI
|
|
||||||
|
|
||||||
FEATHER M0:
|
|
||||||
PA00 PA08 PA16 D11 PB00 PB08 A1
|
|
||||||
PA01 PA09 PA17 D13 PB01 PB09 A2
|
|
||||||
PA02 A0 PA10 TX/D1 PA18 D10 PB02 A5 PB10 MOSI
|
|
||||||
PA03 PA11 RX/D0 PA19 D12 PB03 PB11 SCK
|
|
||||||
PA04 A3 PA12 MISO PA20 D6 PB04 PB12
|
|
||||||
PA05 A4 PA13 PA21 D7 PB05 PB13
|
|
||||||
PA06 PA14 PA22 SDA PB06 PB14
|
|
||||||
PA07 D9 PA15 D5 PA23 SCL PB07 PB15
|
|
||||||
|
|
||||||
FEATHER nRF52840:
|
|
||||||
P0.00 P0.08 D12 P0.24 RXD P1.08 D5
|
|
||||||
P0.01 P0.09 P0.25 TXD P1.09 D13
|
|
||||||
P0.02 A4 P0.10 D2 (NFC) P0.26 D9 P1.10
|
|
||||||
P0.03 A5 P0.11 SCL P0.27 D10 P1.11
|
|
||||||
P0.04 A0 P0.12 SDA P0.28 A3 P1.12
|
|
||||||
P0.05 A1 P0.13 MOSI P0.29 P1.13
|
|
||||||
P0.06 D11 P0.14 SCK P0.30 A2 P1.14
|
|
||||||
P0.07 D6 P0.15 MISO P0.31 P1.15
|
|
||||||
|
|
||||||
FEATHER ESP32:
|
|
||||||
P0.00 P0.08 P0.16 16/RX P0.24 P1.00 32/A7
|
|
||||||
P0.01 P0.09 P0.17 17/TX P0.25 25/A1 P1.01 33/A9/SS
|
|
||||||
P0.02 P0.10 P0.18 18/MOSI P0.26 26/A0 P1.02 34/A2 (in)
|
|
||||||
P0.03 P0.11 P0.19 19/MISO P0.27 27/A10 P1.03
|
|
||||||
P0.04 4/A5 P0.12 12/A11 P0.20 P0.28 P1.04 36/A4 (in)
|
|
||||||
P0.05 5/SCK P0.13 13/A12 P0.21 21 P0.29 P1.05
|
|
||||||
P0.06 P0.14 14/A6 P0.22 22/SCL P0.30 P1.06
|
|
||||||
P0.07 P0.15 15/A8 P0.23 23/SDA P0.31 P1.07 39/A3 (in)
|
|
||||||
|
|
||||||
GRAND CENTRAL M4: (___ = byte boundaries)
|
|
||||||
PA00 PB00 D12 PC00 A3 PD00
|
|
||||||
PA01 PB01 D13 (LED) PC01 A4 PD01
|
|
||||||
PA02 A0 PB02 D9 PC02 A5 PD02
|
|
||||||
PA03 84 (AREF) PB03 A2 PC03 A6 PD03
|
|
||||||
PA04 A13 PB04 A7 PC04 D48 PD04
|
|
||||||
PA05 A1 PB05 A8 PC05 D49 PD05
|
|
||||||
PA06 A14 PB06 A9 PC06 D46 PD06
|
|
||||||
PA07 A15 ______ PB07 A10 ______ PC07 D47 _____ PD07 __________
|
|
||||||
PA08 PB08 A11 PC08 PD08 D51 (SCK)
|
|
||||||
PA09 PB09 A12 PC09 PD09 D52 (MOSI)
|
|
||||||
PA10 PB10 PC10 D45 PD10 D53
|
|
||||||
PA11 PB11 PC11 D44 PD11 D50 (MISO)
|
|
||||||
PA12 D26 PB12 D18 PC12 D41 PD12 D22
|
|
||||||
PA13 D27 PB13 D19 PC13 D40 PD13
|
|
||||||
PA14 D28 PB14 D39 PC14 D43 PD14
|
|
||||||
PA15 D23 ______ PB15 D38 ______ PC15 D42 _____ PD15 __________
|
|
||||||
PA16 D37 PB16 D14 PC16 D25 PD16
|
|
||||||
PA17 D36 PB17 D15 PC17 D24 PD17
|
|
||||||
PA18 D35 PB18 D8 PC18 D2 PD18
|
|
||||||
PA19 D34 PB19 D29 PC19 D3 PD19
|
|
||||||
PA20 D33 PB20 D20 (SDA) PC20 D4 PD20 D6
|
|
||||||
PA21 D32 PB21 D21 (SCL) PC21 D5 PD21 D7
|
|
||||||
PA22 D31 PB22 D10 PC22 D16 PD22
|
|
||||||
PA23 D30 ______ PB23 D11 ______ PC23 D17 _____ PD23 __________
|
|
||||||
PA24 PB24 D1
|
|
||||||
PA25 PB25 D0
|
|
||||||
PA26 PB26
|
|
||||||
PA27 PB27
|
|
||||||
PA28 PB28
|
|
||||||
PA29 PB29
|
|
||||||
PA30 PB30 96 (SWO)
|
|
||||||
PA31 __________ PB31 95 (SD CD) ______________________________
|
|
||||||
|
|
||||||
RGB MATRIX FEATHERWING NOTES:
|
|
||||||
R1 D6 A A5
|
|
||||||
G1 D5 B A4
|
|
||||||
B1 D9 C A3
|
|
||||||
R2 D11 D A2
|
|
||||||
G2 D10 LAT D0/RX
|
|
||||||
B2 D12 OE D1/TX
|
|
||||||
CLK D13
|
|
||||||
RGB+clock fit in one PORT byte on Feather M4!
|
|
||||||
RGB+clock are on same PORT but not within same byte on Feather M0 --
|
|
||||||
the code could run there, but would be super RAM-inefficient. Avoid.
|
|
||||||
Should be fine on other M0 devices like a Metro, if wiring manually
|
|
||||||
so one can pick a contiguous byte of PORT bits.
|
|
||||||
Original RGB Matrix FeatherWing will NOT work on Feather nRF52840
|
|
||||||
because RGB+clock are on different PORTs. This was resolved by making
|
|
||||||
a unique version of the FeatherWing that works with that board!
|
|
||||||
*/
|
|
||||||
|
|
@ -1,168 +0,0 @@
|
||||||
/* ----------------------------------------------------------------------
|
|
||||||
"Tiled" Protomatter library example sketch. Demonstrates use of multiple
|
|
||||||
RGB LED matrices as a single larger drawing surface. This example is
|
|
||||||
written for two 64x32 matrices (tiled into a 64x64 display) but can be
|
|
||||||
adapted to others. If using MatrixPortal, larger multi-panel tilings like
|
|
||||||
this should be powered from a separate 5V DC supply, not the USB port
|
|
||||||
(this example works OK because the graphics are very minimal).
|
|
||||||
|
|
||||||
PLEASE SEE THE "simple" EXAMPLE FOR AN INTRODUCTORY SKETCH.
|
|
||||||
------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
#include <Adafruit_Protomatter.h>
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------
|
|
||||||
The RGB matrix must be wired to VERY SPECIFIC pins, different for each
|
|
||||||
microcontroller board. This first section sets that up for a number of
|
|
||||||
supported boards.
|
|
||||||
------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
#if defined(_VARIANT_MATRIXPORTAL_M4_) // MatrixPortal M4
|
|
||||||
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
|
|
||||||
uint8_t addrPins[] = {17, 18, 19, 20, 21};
|
|
||||||
uint8_t clockPin = 14;
|
|
||||||
uint8_t latchPin = 15;
|
|
||||||
uint8_t oePin = 16;
|
|
||||||
#elif defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) // MatrixPortal ESP32-S3
|
|
||||||
uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37};
|
|
||||||
uint8_t addrPins[] = {45, 36, 48, 35, 21};
|
|
||||||
uint8_t clockPin = 2;
|
|
||||||
uint8_t latchPin = 47;
|
|
||||||
uint8_t oePin = 14;
|
|
||||||
#elif defined(_VARIANT_FEATHER_M4_) // Feather M4 + RGB Matrix FeatherWing
|
|
||||||
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
|
|
||||||
uint8_t addrPins[] = {A5, A4, A3, A2};
|
|
||||||
uint8_t clockPin = 13;
|
|
||||||
uint8_t latchPin = 0;
|
|
||||||
uint8_t oePin = 1;
|
|
||||||
#elif defined(__SAMD51__) // M4 Metro Variants (Express, AirLift)
|
|
||||||
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
|
|
||||||
uint8_t addrPins[] = {A5, A4, A3, A2};
|
|
||||||
uint8_t clockPin = 13;
|
|
||||||
uint8_t latchPin = 0;
|
|
||||||
uint8_t oePin = 1;
|
|
||||||
#elif defined(_SAMD21_) // Feather M0 variants
|
|
||||||
uint8_t rgbPins[] = {6, 7, 10, 11, 12, 13};
|
|
||||||
uint8_t addrPins[] = {0, 1, 2, 3};
|
|
||||||
uint8_t clockPin = SDA;
|
|
||||||
uint8_t latchPin = 4;
|
|
||||||
uint8_t oePin = 5;
|
|
||||||
#elif defined(NRF52_SERIES) // Special nRF52840 FeatherWing pinout
|
|
||||||
uint8_t rgbPins[] = {6, A5, A1, A0, A4, 11};
|
|
||||||
uint8_t addrPins[] = {10, 5, 13, 9};
|
|
||||||
uint8_t clockPin = 12;
|
|
||||||
uint8_t latchPin = PIN_SERIAL1_RX;
|
|
||||||
uint8_t oePin = PIN_SERIAL1_TX;
|
|
||||||
#elif USB_VID == 0x239A && USB_PID == 0x8113 // Feather ESP32-S3 No PSRAM
|
|
||||||
// M0/M4/RP2040 Matrix FeatherWing compatible:
|
|
||||||
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
|
|
||||||
uint8_t addrPins[] = {A5, A4, A3, A2};
|
|
||||||
uint8_t clockPin = 13; // Must be on same port as rgbPins
|
|
||||||
uint8_t latchPin = RX;
|
|
||||||
uint8_t oePin = TX;
|
|
||||||
#elif USB_VID == 0x239A && USB_PID == 0x80EB // Feather ESP32-S2
|
|
||||||
// M0/M4/RP2040 Matrix FeatherWing compatible:
|
|
||||||
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
|
|
||||||
uint8_t addrPins[] = {A5, A4, A3, A2};
|
|
||||||
uint8_t clockPin = 13; // Must be on same port as rgbPins
|
|
||||||
uint8_t latchPin = RX;
|
|
||||||
uint8_t oePin = TX;
|
|
||||||
#elif defined(ESP32)
|
|
||||||
// 'Safe' pins, not overlapping any peripherals:
|
|
||||||
// GPIO.out: 4, 12, 13, 14, 15, 21, 27, GPIO.out1: 32, 33
|
|
||||||
// Peripheral-overlapping pins, sorted from 'most expendible':
|
|
||||||
// 16, 17 (RX, TX)
|
|
||||||
// 25, 26 (A0, A1)
|
|
||||||
// 18, 5, 9 (MOSI, SCK, MISO)
|
|
||||||
// 22, 23 (SCL, SDA)
|
|
||||||
uint8_t rgbPins[] = {4, 12, 13, 14, 15, 21};
|
|
||||||
uint8_t addrPins[] = {16, 17, 25, 26};
|
|
||||||
uint8_t clockPin = 27; // Must be on same port as rgbPins
|
|
||||||
uint8_t latchPin = 32;
|
|
||||||
uint8_t oePin = 33;
|
|
||||||
#elif defined(ARDUINO_TEENSY40)
|
|
||||||
uint8_t rgbPins[] = {15, 16, 17, 20, 21, 22}; // A1-A3, A6-A8, skip SDA,SCL
|
|
||||||
uint8_t addrPins[] = {2, 3, 4, 5};
|
|
||||||
uint8_t clockPin = 23; // A9
|
|
||||||
uint8_t latchPin = 6;
|
|
||||||
uint8_t oePin = 9;
|
|
||||||
#elif defined(ARDUINO_TEENSY41)
|
|
||||||
uint8_t rgbPins[] = {26, 27, 38, 20, 21, 22}; // A12-14, A6-A8
|
|
||||||
uint8_t addrPins[] = {2, 3, 4, 5};
|
|
||||||
uint8_t clockPin = 23; // A9
|
|
||||||
uint8_t latchPin = 6;
|
|
||||||
uint8_t oePin = 9;
|
|
||||||
#elif defined(ARDUINO_ADAFRUIT_FEATHER_RP2040)
|
|
||||||
// RP2040 support requires the Earle Philhower board support package;
|
|
||||||
// will not compile with the Arduino Mbed OS board package.
|
|
||||||
// The following pinout works with the Adafruit Feather RP2040 and
|
|
||||||
// original RGB Matrix FeatherWing (M0/M4/RP2040, not nRF version).
|
|
||||||
// Pin numbers here are GP## numbers, which may be different than
|
|
||||||
// the pins printed on some boards' top silkscreen.
|
|
||||||
uint8_t rgbPins[] = {8, 7, 9, 11, 10, 12};
|
|
||||||
uint8_t addrPins[] = {25, 24, 29, 28};
|
|
||||||
uint8_t clockPin = 13;
|
|
||||||
uint8_t latchPin = 1;
|
|
||||||
uint8_t oePin = 0;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------
|
|
||||||
Matrix initialization is explained EXTENSIVELY in "simple" example sketch!
|
|
||||||
It's very similar here, but we're passing an extra argument to define the
|
|
||||||
matrix tiling along the vertical axis: -2 means there are two matrices
|
|
||||||
(or rows of matrices) arranged in a "serpentine" path (the second matrix
|
|
||||||
is rotated 180 degrees relative to the first, and positioned below).
|
|
||||||
A positive 2 would indicate a "progressive" path (both matrices are
|
|
||||||
oriented the same way), but usually requires longer cables.
|
|
||||||
------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
Adafruit_Protomatter matrix(
|
|
||||||
64, // Width of matrix (or matrices, if tiled horizontally)
|
|
||||||
6, // Bit depth, 1-6
|
|
||||||
1, rgbPins, // # of matrix chains, array of 6 RGB pins for each
|
|
||||||
4, addrPins, // # of address pins (height is inferred), array of pins
|
|
||||||
clockPin, latchPin, oePin, // Other matrix control pins
|
|
||||||
false, // No double-buffering here (see "doublebuffer" example)
|
|
||||||
-2); // Row tiling: two rows in "serpentine" path
|
|
||||||
|
|
||||||
// SETUP - RUNS ONCE AT PROGRAM START --------------------------------------
|
|
||||||
|
|
||||||
void setup(void) {
|
|
||||||
Serial.begin(9600);
|
|
||||||
|
|
||||||
// Initialize matrix...
|
|
||||||
ProtomatterStatus status = matrix.begin();
|
|
||||||
Serial.print("Protomatter begin() status: ");
|
|
||||||
Serial.println((int)status);
|
|
||||||
if(status != PROTOMATTER_OK) {
|
|
||||||
// DO NOT CONTINUE if matrix setup encountered an error.
|
|
||||||
for(;;);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since this program has no animation, all the drawing can be done
|
|
||||||
// here in setup() rather than loop(). It's just a few basic shapes
|
|
||||||
// that span across the matrices...nothing showy, the goal of this
|
|
||||||
// sketch is just to demonstrate tiling basics.
|
|
||||||
|
|
||||||
matrix.drawLine(0, 0, matrix.width() - 1, matrix.height() - 1,
|
|
||||||
matrix.color565(255, 0, 0)); // Red line
|
|
||||||
matrix.drawLine(matrix.width() - 1, 0, 0, matrix.height() - 1,
|
|
||||||
matrix.color565(0, 0, 255)); // Blue line
|
|
||||||
int radius = min(matrix.width(), matrix.height()) / 2;
|
|
||||||
matrix.drawCircle(matrix.width() / 2, matrix.height() / 2, radius,
|
|
||||||
matrix.color565(0, 255, 0)); // Green circle
|
|
||||||
|
|
||||||
// AFTER DRAWING, A show() CALL IS REQUIRED TO UPDATE THE MATRIX!
|
|
||||||
|
|
||||||
matrix.show(); // Copy data to matrix buffers
|
|
||||||
}
|
|
||||||
|
|
||||||
// LOOP - RUNS REPEATEDLY AFTER SETUP --------------------------------------
|
|
||||||
|
|
||||||
void loop(void) {
|
|
||||||
// Since there's nothing more to be drawn, this loop() function just
|
|
||||||
// prints the approximate refresh rate of the matrix at current settings.
|
|
||||||
Serial.print("Refresh FPS = ~");
|
|
||||||
Serial.println(matrix.getFrameCount());
|
|
||||||
delay(1000);
|
|
||||||
}
|
|
||||||
BIN
html/bc_s.png
Normal file
|
After Width: | Height: | Size: 676 B |
BIN
html/bdwn.png
Normal file
|
After Width: | Height: | Size: 147 B |
BIN
html/closed.png
Normal file
|
After Width: | Height: | Size: 132 B |
BIN
html/doc.png
Normal file
|
After Width: | Height: | Size: 746 B |
1596
html/doxygen.css
Normal file
BIN
html/doxygen.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
97
html/dynsections.js
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
function toggleVisibility(linkObj)
|
||||||
|
{
|
||||||
|
var base = $(linkObj).attr('id');
|
||||||
|
var summary = $('#'+base+'-summary');
|
||||||
|
var content = $('#'+base+'-content');
|
||||||
|
var trigger = $('#'+base+'-trigger');
|
||||||
|
var src=$(trigger).attr('src');
|
||||||
|
if (content.is(':visible')===true) {
|
||||||
|
content.hide();
|
||||||
|
summary.show();
|
||||||
|
$(linkObj).addClass('closed').removeClass('opened');
|
||||||
|
$(trigger).attr('src',src.substring(0,src.length-8)+'closed.png');
|
||||||
|
} else {
|
||||||
|
content.show();
|
||||||
|
summary.hide();
|
||||||
|
$(linkObj).removeClass('closed').addClass('opened');
|
||||||
|
$(trigger).attr('src',src.substring(0,src.length-10)+'open.png');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStripes()
|
||||||
|
{
|
||||||
|
$('table.directory tr').
|
||||||
|
removeClass('even').filter(':visible:even').addClass('even');
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleLevel(level)
|
||||||
|
{
|
||||||
|
$('table.directory tr').each(function() {
|
||||||
|
var l = this.id.split('_').length-1;
|
||||||
|
var i = $('#img'+this.id.substring(3));
|
||||||
|
var a = $('#arr'+this.id.substring(3));
|
||||||
|
if (l<level+1) {
|
||||||
|
i.removeClass('iconfopen iconfclosed').addClass('iconfopen');
|
||||||
|
a.html('▼');
|
||||||
|
$(this).show();
|
||||||
|
} else if (l==level+1) {
|
||||||
|
i.removeClass('iconfclosed iconfopen').addClass('iconfclosed');
|
||||||
|
a.html('►');
|
||||||
|
$(this).show();
|
||||||
|
} else {
|
||||||
|
$(this).hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
updateStripes();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleFolder(id)
|
||||||
|
{
|
||||||
|
// the clicked row
|
||||||
|
var currentRow = $('#row_'+id);
|
||||||
|
|
||||||
|
// all rows after the clicked row
|
||||||
|
var rows = currentRow.nextAll("tr");
|
||||||
|
|
||||||
|
var re = new RegExp('^row_'+id+'\\d+_$', "i"); //only one sub
|
||||||
|
|
||||||
|
// only match elements AFTER this one (can't hide elements before)
|
||||||
|
var childRows = rows.filter(function() { return this.id.match(re); });
|
||||||
|
|
||||||
|
// first row is visible we are HIDING
|
||||||
|
if (childRows.filter(':first').is(':visible')===true) {
|
||||||
|
// replace down arrow by right arrow for current row
|
||||||
|
var currentRowSpans = currentRow.find("span");
|
||||||
|
currentRowSpans.filter(".iconfopen").removeClass("iconfopen").addClass("iconfclosed");
|
||||||
|
currentRowSpans.filter(".arrow").html('►');
|
||||||
|
rows.filter("[id^=row_"+id+"]").hide(); // hide all children
|
||||||
|
} else { // we are SHOWING
|
||||||
|
// replace right arrow by down arrow for current row
|
||||||
|
var currentRowSpans = currentRow.find("span");
|
||||||
|
currentRowSpans.filter(".iconfclosed").removeClass("iconfclosed").addClass("iconfopen");
|
||||||
|
currentRowSpans.filter(".arrow").html('▼');
|
||||||
|
// replace down arrows by right arrows for child rows
|
||||||
|
var childRowsSpans = childRows.find("span");
|
||||||
|
childRowsSpans.filter(".iconfopen").removeClass("iconfopen").addClass("iconfclosed");
|
||||||
|
childRowsSpans.filter(".arrow").html('►');
|
||||||
|
childRows.show(); //show all children
|
||||||
|
}
|
||||||
|
updateStripes();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function toggleInherit(id)
|
||||||
|
{
|
||||||
|
var rows = $('tr.inherit.'+id);
|
||||||
|
var img = $('tr.inherit_header.'+id+' img');
|
||||||
|
var src = $(img).attr('src');
|
||||||
|
if (rows.filter(':first').is(':visible')===true) {
|
||||||
|
rows.css('display','none');
|
||||||
|
$(img).attr('src',src.substring(0,src.length-8)+'closed.png');
|
||||||
|
} else {
|
||||||
|
rows.css('display','table-row'); // using show() causes jump in firefox
|
||||||
|
$(img).attr('src',src.substring(0,src.length-10)+'open.png');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BIN
html/folderclosed.png
Normal file
|
After Width: | Height: | Size: 616 B |
BIN
html/folderopen.png
Normal file
|
After Width: | Height: | Size: 597 B |
73
html/index.html
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
||||||
|
<meta name="generator" content="Doxygen 1.8.13"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
|
<title>Adafruit Protomatter: Main Page</title>
|
||||||
|
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
||||||
|
<script type="text/javascript" src="jquery.js"></script>
|
||||||
|
<script type="text/javascript" src="dynsections.js"></script>
|
||||||
|
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
||||||
|
<script type="text/javascript" src="search/searchdata.js"></script>
|
||||||
|
<script type="text/javascript" src="search/search.js"></script>
|
||||||
|
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
||||||
|
<div id="titlearea">
|
||||||
|
<table cellspacing="0" cellpadding="0">
|
||||||
|
<tbody>
|
||||||
|
<tr style="height: 56px;">
|
||||||
|
<td id="projectalign" style="padding-left: 0.5em;">
|
||||||
|
<div id="projectname">Adafruit Protomatter
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- end header part -->
|
||||||
|
<!-- Generated by Doxygen 1.8.13 -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript" src="menudata.js"></script>
|
||||||
|
<script type="text/javascript" src="menu.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(function() {
|
||||||
|
initMenu('',true,false,'search.php','Search');
|
||||||
|
$(document).ready(function() { init_search(); });
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<div id="main-nav"></div>
|
||||||
|
</div><!-- top -->
|
||||||
|
<!-- window showing the filter options -->
|
||||||
|
<div id="MSearchSelectWindow"
|
||||||
|
onmouseover="return searchBox.OnSearchSelectShow()"
|
||||||
|
onmouseout="return searchBox.OnSearchSelectHide()"
|
||||||
|
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- iframe showing the search results (closed by default) -->
|
||||||
|
<div id="MSearchResultsWindow">
|
||||||
|
<iframe src="javascript:void(0)" frameborder="0"
|
||||||
|
name="MSearchResults" id="MSearchResults">
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="header">
|
||||||
|
<div class="headertitle">
|
||||||
|
<div class="title">Adafruit Protomatter Documentation</div> </div>
|
||||||
|
</div><!--header-->
|
||||||
|
<div class="contents">
|
||||||
|
</div><!-- contents -->
|
||||||
|
<!-- start footer part -->
|
||||||
|
<hr class="footer"/><address class="footer"><small>
|
||||||
|
Generated by  <a href="http://www.doxygen.org/index.html">
|
||||||
|
<img class="footer" src="doxygen.png" alt="doxygen"/>
|
||||||
|
</a> 1.8.13
|
||||||
|
</small></address>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
87
html/jquery.js
vendored
Normal file
26
html/menu.js
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
function initMenu(relPath,searchEnabled,serverSide,searchPage,search) {
|
||||||
|
function makeTree(data,relPath) {
|
||||||
|
var result='';
|
||||||
|
if ('children' in data) {
|
||||||
|
result+='<ul>';
|
||||||
|
for (var i in data.children) {
|
||||||
|
result+='<li><a href="'+relPath+data.children[i].url+'">'+
|
||||||
|
data.children[i].text+'</a>'+
|
||||||
|
makeTree(data.children[i],relPath)+'</li>';
|
||||||
|
}
|
||||||
|
result+='</ul>';
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#main-nav').append(makeTree(menudata,relPath));
|
||||||
|
$('#main-nav').children(':first').addClass('sm sm-dox').attr('id','main-menu');
|
||||||
|
if (searchEnabled) {
|
||||||
|
if (serverSide) {
|
||||||
|
$('#main-menu').append('<li style="float:right"><div id="MSearchBox" class="MSearchBoxInactive"><div class="left"><form id="FSearchBox" action="'+searchPage+'" method="get"><img id="MSearchSelect" src="'+relPath+'search/mag.png" alt=""/><input type="text" id="MSearchField" name="query" value="'+search+'" size="20" accesskey="S" onfocus="searchBox.OnSearchFieldFocus(true)" onblur="searchBox.OnSearchFieldFocus(false)"></form></div><div class="right"></div></div></li>');
|
||||||
|
} else {
|
||||||
|
$('#main-menu').append('<li style="float:right"><div id="MSearchBox" class="MSearchBoxInactive"><span class="left"><img id="MSearchSelect" src="'+relPath+'search/mag_sel.png" onmouseover="return searchBox.OnSearchSelectShow()" onmouseout="return searchBox.OnSearchSelectHide()" alt=""/><input type="text" id="MSearchField" value="'+search+'" accesskey="S" onfocus="searchBox.OnSearchFieldFocus(true)" onblur="searchBox.OnSearchFieldFocus(false)" onkeyup="searchBox.OnSearchFieldChange(event)"/></span><span class="right"><a id="MSearchClose" href="javascript:searchBox.CloseResultsWindow()"><img id="MSearchCloseImg" border="0" src="'+relPath+'search/close.png" alt=""/></a></span></div></li>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$('#main-menu').smartmenus();
|
||||||
|
}
|
||||||
2
html/menudata.js
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
var menudata={children:[
|
||||||
|
{text:"Main Page",url:"index.html"}]}
|
||||||
BIN
html/nav_f.png
Normal file
|
After Width: | Height: | Size: 153 B |
BIN
html/nav_g.png
Normal file
|
After Width: | Height: | Size: 95 B |
BIN
html/nav_h.png
Normal file
|
After Width: | Height: | Size: 98 B |
BIN
html/open.png
Normal file
|
After Width: | Height: | Size: 123 B |
BIN
html/search/close.png
Normal file
|
After Width: | Height: | Size: 273 B |
BIN
html/search/mag_sel.png
Normal file
|
After Width: | Height: | Size: 563 B |
12
html/search/nomatches.html
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html><head><title></title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="search.css"/>
|
||||||
|
<script type="text/javascript" src="search.js"></script>
|
||||||
|
</head>
|
||||||
|
<body class="SRPage">
|
||||||
|
<div id="SRIndex">
|
||||||
|
<div class="SRStatus" id="NoMatches">No Matches</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
271
html/search/search.css
Normal file
|
|
@ -0,0 +1,271 @@
|
||||||
|
/*---------------- Search Box */
|
||||||
|
|
||||||
|
#FSearchBox {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#MSearchBox {
|
||||||
|
white-space : nowrap;
|
||||||
|
float: none;
|
||||||
|
margin-top: 8px;
|
||||||
|
right: 0px;
|
||||||
|
width: 170px;
|
||||||
|
height: 24px;
|
||||||
|
z-index: 102;
|
||||||
|
}
|
||||||
|
|
||||||
|
#MSearchBox .left
|
||||||
|
{
|
||||||
|
display:block;
|
||||||
|
position:absolute;
|
||||||
|
left:10px;
|
||||||
|
width:20px;
|
||||||
|
height:19px;
|
||||||
|
background:url('search_l.png') no-repeat;
|
||||||
|
background-position:right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#MSearchSelect {
|
||||||
|
display:block;
|
||||||
|
position:absolute;
|
||||||
|
width:20px;
|
||||||
|
height:19px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left #MSearchSelect {
|
||||||
|
left:4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right #MSearchSelect {
|
||||||
|
right:5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#MSearchField {
|
||||||
|
display:block;
|
||||||
|
position:absolute;
|
||||||
|
height:19px;
|
||||||
|
background:url('search_m.png') repeat-x;
|
||||||
|
border:none;
|
||||||
|
width:115px;
|
||||||
|
margin-left:20px;
|
||||||
|
padding-left:4px;
|
||||||
|
color: #909090;
|
||||||
|
outline: none;
|
||||||
|
font: 9pt Arial, Verdana, sans-serif;
|
||||||
|
-webkit-border-radius: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#FSearchBox #MSearchField {
|
||||||
|
margin-left:15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#MSearchBox .right {
|
||||||
|
display:block;
|
||||||
|
position:absolute;
|
||||||
|
right:10px;
|
||||||
|
top:8px;
|
||||||
|
width:20px;
|
||||||
|
height:19px;
|
||||||
|
background:url('search_r.png') no-repeat;
|
||||||
|
background-position:left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#MSearchClose {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
background : none;
|
||||||
|
border: none;
|
||||||
|
margin: 0px 4px 0px 0px;
|
||||||
|
padding: 0px 0px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left #MSearchClose {
|
||||||
|
left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right #MSearchClose {
|
||||||
|
right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MSearchBoxActive #MSearchField {
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*---------------- Search filter selection */
|
||||||
|
|
||||||
|
#MSearchSelectWindow {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
left: 0; top: 0;
|
||||||
|
border: 1px solid #90A5CE;
|
||||||
|
background-color: #F9FAFC;
|
||||||
|
z-index: 10001;
|
||||||
|
padding-top: 4px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
-moz-border-radius: 4px;
|
||||||
|
-webkit-border-top-left-radius: 4px;
|
||||||
|
-webkit-border-top-right-radius: 4px;
|
||||||
|
-webkit-border-bottom-left-radius: 4px;
|
||||||
|
-webkit-border-bottom-right-radius: 4px;
|
||||||
|
-webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.SelectItem {
|
||||||
|
font: 8pt Arial, Verdana, sans-serif;
|
||||||
|
padding-left: 2px;
|
||||||
|
padding-right: 12px;
|
||||||
|
border: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.SelectionMark {
|
||||||
|
margin-right: 4px;
|
||||||
|
font-family: monospace;
|
||||||
|
outline-style: none;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.SelectItem {
|
||||||
|
display: block;
|
||||||
|
outline-style: none;
|
||||||
|
color: #000000;
|
||||||
|
text-decoration: none;
|
||||||
|
padding-left: 6px;
|
||||||
|
padding-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.SelectItem:focus,
|
||||||
|
a.SelectItem:active {
|
||||||
|
color: #000000;
|
||||||
|
outline-style: none;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.SelectItem:hover {
|
||||||
|
color: #FFFFFF;
|
||||||
|
background-color: #3D578C;
|
||||||
|
outline-style: none;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*---------------- Search results window */
|
||||||
|
|
||||||
|
iframe#MSearchResults {
|
||||||
|
width: 60ex;
|
||||||
|
height: 15em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#MSearchResultsWindow {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
left: 0; top: 0;
|
||||||
|
border: 1px solid #000;
|
||||||
|
background-color: #EEF1F7;
|
||||||
|
z-index:10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
#SRIndex {
|
||||||
|
clear:both;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SREntry {
|
||||||
|
font-size: 10pt;
|
||||||
|
padding-left: 1ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SRPage .SREntry {
|
||||||
|
font-size: 8pt;
|
||||||
|
padding: 1px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.SRPage {
|
||||||
|
margin: 5px 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SRChildren {
|
||||||
|
padding-left: 3ex; padding-bottom: .5em
|
||||||
|
}
|
||||||
|
|
||||||
|
.SRPage .SRChildren {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SRSymbol {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #425E97;
|
||||||
|
font-family: Arial, Verdana, sans-serif;
|
||||||
|
text-decoration: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.SRScope {
|
||||||
|
display: block;
|
||||||
|
color: #425E97;
|
||||||
|
font-family: Arial, Verdana, sans-serif;
|
||||||
|
text-decoration: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.SRSymbol:focus, a.SRSymbol:active,
|
||||||
|
a.SRScope:focus, a.SRScope:active {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.SRScope {
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SRPage .SRStatus {
|
||||||
|
padding: 2px 5px;
|
||||||
|
font-size: 8pt;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SRResult {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
DIV.searchresults {
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*---------------- External search page results */
|
||||||
|
|
||||||
|
.searchresult {
|
||||||
|
background-color: #F0F3F8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pages b {
|
||||||
|
color: white;
|
||||||
|
padding: 5px 5px 3px 5px;
|
||||||
|
background-image: url("../tab_a.png");
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
text-shadow: 0 1px 1px #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pages {
|
||||||
|
line-height: 17px;
|
||||||
|
margin-left: 4px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hl {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchresults {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchpages {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
791
html/search/search.js
Normal file
|
|
@ -0,0 +1,791 @@
|
||||||
|
function convertToId(search)
|
||||||
|
{
|
||||||
|
var result = '';
|
||||||
|
for (i=0;i<search.length;i++)
|
||||||
|
{
|
||||||
|
var c = search.charAt(i);
|
||||||
|
var cn = c.charCodeAt(0);
|
||||||
|
if (c.match(/[a-z0-9\u0080-\uFFFF]/))
|
||||||
|
{
|
||||||
|
result+=c;
|
||||||
|
}
|
||||||
|
else if (cn<16)
|
||||||
|
{
|
||||||
|
result+="_0"+cn.toString(16);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result+="_"+cn.toString(16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getXPos(item)
|
||||||
|
{
|
||||||
|
var x = 0;
|
||||||
|
if (item.offsetWidth)
|
||||||
|
{
|
||||||
|
while (item && item!=document.body)
|
||||||
|
{
|
||||||
|
x += item.offsetLeft;
|
||||||
|
item = item.offsetParent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getYPos(item)
|
||||||
|
{
|
||||||
|
var y = 0;
|
||||||
|
if (item.offsetWidth)
|
||||||
|
{
|
||||||
|
while (item && item!=document.body)
|
||||||
|
{
|
||||||
|
y += item.offsetTop;
|
||||||
|
item = item.offsetParent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A class handling everything associated with the search panel.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
name - The name of the global variable that will be
|
||||||
|
storing this instance. Is needed to be able to set timeouts.
|
||||||
|
resultPath - path to use for external files
|
||||||
|
*/
|
||||||
|
function SearchBox(name, resultsPath, inFrame, label)
|
||||||
|
{
|
||||||
|
if (!name || !resultsPath) { alert("Missing parameters to SearchBox."); }
|
||||||
|
|
||||||
|
// ---------- Instance variables
|
||||||
|
this.name = name;
|
||||||
|
this.resultsPath = resultsPath;
|
||||||
|
this.keyTimeout = 0;
|
||||||
|
this.keyTimeoutLength = 500;
|
||||||
|
this.closeSelectionTimeout = 300;
|
||||||
|
this.lastSearchValue = "";
|
||||||
|
this.lastResultsPage = "";
|
||||||
|
this.hideTimeout = 0;
|
||||||
|
this.searchIndex = 0;
|
||||||
|
this.searchActive = false;
|
||||||
|
this.insideFrame = inFrame;
|
||||||
|
this.searchLabel = label;
|
||||||
|
|
||||||
|
// ----------- DOM Elements
|
||||||
|
|
||||||
|
this.DOMSearchField = function()
|
||||||
|
{ return document.getElementById("MSearchField"); }
|
||||||
|
|
||||||
|
this.DOMSearchSelect = function()
|
||||||
|
{ return document.getElementById("MSearchSelect"); }
|
||||||
|
|
||||||
|
this.DOMSearchSelectWindow = function()
|
||||||
|
{ return document.getElementById("MSearchSelectWindow"); }
|
||||||
|
|
||||||
|
this.DOMPopupSearchResults = function()
|
||||||
|
{ return document.getElementById("MSearchResults"); }
|
||||||
|
|
||||||
|
this.DOMPopupSearchResultsWindow = function()
|
||||||
|
{ return document.getElementById("MSearchResultsWindow"); }
|
||||||
|
|
||||||
|
this.DOMSearchClose = function()
|
||||||
|
{ return document.getElementById("MSearchClose"); }
|
||||||
|
|
||||||
|
this.DOMSearchBox = function()
|
||||||
|
{ return document.getElementById("MSearchBox"); }
|
||||||
|
|
||||||
|
// ------------ Event Handlers
|
||||||
|
|
||||||
|
// Called when focus is added or removed from the search field.
|
||||||
|
this.OnSearchFieldFocus = function(isActive)
|
||||||
|
{
|
||||||
|
this.Activate(isActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.OnSearchSelectShow = function()
|
||||||
|
{
|
||||||
|
var searchSelectWindow = this.DOMSearchSelectWindow();
|
||||||
|
var searchField = this.DOMSearchSelect();
|
||||||
|
|
||||||
|
if (this.insideFrame)
|
||||||
|
{
|
||||||
|
var left = getXPos(searchField);
|
||||||
|
var top = getYPos(searchField);
|
||||||
|
left += searchField.offsetWidth + 6;
|
||||||
|
top += searchField.offsetHeight;
|
||||||
|
|
||||||
|
// show search selection popup
|
||||||
|
searchSelectWindow.style.display='block';
|
||||||
|
left -= searchSelectWindow.offsetWidth;
|
||||||
|
searchSelectWindow.style.left = left + 'px';
|
||||||
|
searchSelectWindow.style.top = top + 'px';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var left = getXPos(searchField);
|
||||||
|
var top = getYPos(searchField);
|
||||||
|
top += searchField.offsetHeight;
|
||||||
|
|
||||||
|
// show search selection popup
|
||||||
|
searchSelectWindow.style.display='block';
|
||||||
|
searchSelectWindow.style.left = left + 'px';
|
||||||
|
searchSelectWindow.style.top = top + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop selection hide timer
|
||||||
|
if (this.hideTimeout)
|
||||||
|
{
|
||||||
|
clearTimeout(this.hideTimeout);
|
||||||
|
this.hideTimeout=0;
|
||||||
|
}
|
||||||
|
return false; // to avoid "image drag" default event
|
||||||
|
}
|
||||||
|
|
||||||
|
this.OnSearchSelectHide = function()
|
||||||
|
{
|
||||||
|
this.hideTimeout = setTimeout(this.name +".CloseSelectionWindow()",
|
||||||
|
this.closeSelectionTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when the content of the search field is changed.
|
||||||
|
this.OnSearchFieldChange = function(evt)
|
||||||
|
{
|
||||||
|
if (this.keyTimeout) // kill running timer
|
||||||
|
{
|
||||||
|
clearTimeout(this.keyTimeout);
|
||||||
|
this.keyTimeout = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var e = (evt) ? evt : window.event; // for IE
|
||||||
|
if (e.keyCode==40 || e.keyCode==13)
|
||||||
|
{
|
||||||
|
if (e.shiftKey==1)
|
||||||
|
{
|
||||||
|
this.OnSearchSelectShow();
|
||||||
|
var win=this.DOMSearchSelectWindow();
|
||||||
|
for (i=0;i<win.childNodes.length;i++)
|
||||||
|
{
|
||||||
|
var child = win.childNodes[i]; // get span within a
|
||||||
|
if (child.className=='SelectItem')
|
||||||
|
{
|
||||||
|
child.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (window.frames.MSearchResults.searchResults)
|
||||||
|
{
|
||||||
|
var elem = window.frames.MSearchResults.searchResults.NavNext(0);
|
||||||
|
if (elem) elem.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (e.keyCode==27) // Escape out of the search field
|
||||||
|
{
|
||||||
|
this.DOMSearchField().blur();
|
||||||
|
this.DOMPopupSearchResultsWindow().style.display = 'none';
|
||||||
|
this.DOMSearchClose().style.display = 'none';
|
||||||
|
this.lastSearchValue = '';
|
||||||
|
this.Activate(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// strip whitespaces
|
||||||
|
var searchValue = this.DOMSearchField().value.replace(/ +/g, "");
|
||||||
|
|
||||||
|
if (searchValue != this.lastSearchValue) // search value has changed
|
||||||
|
{
|
||||||
|
if (searchValue != "") // non-empty search
|
||||||
|
{
|
||||||
|
// set timer for search update
|
||||||
|
this.keyTimeout = setTimeout(this.name + '.Search()',
|
||||||
|
this.keyTimeoutLength);
|
||||||
|
}
|
||||||
|
else // empty search field
|
||||||
|
{
|
||||||
|
this.DOMPopupSearchResultsWindow().style.display = 'none';
|
||||||
|
this.DOMSearchClose().style.display = 'none';
|
||||||
|
this.lastSearchValue = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.SelectItemCount = function(id)
|
||||||
|
{
|
||||||
|
var count=0;
|
||||||
|
var win=this.DOMSearchSelectWindow();
|
||||||
|
for (i=0;i<win.childNodes.length;i++)
|
||||||
|
{
|
||||||
|
var child = win.childNodes[i]; // get span within a
|
||||||
|
if (child.className=='SelectItem')
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.SelectItemSet = function(id)
|
||||||
|
{
|
||||||
|
var i,j=0;
|
||||||
|
var win=this.DOMSearchSelectWindow();
|
||||||
|
for (i=0;i<win.childNodes.length;i++)
|
||||||
|
{
|
||||||
|
var child = win.childNodes[i]; // get span within a
|
||||||
|
if (child.className=='SelectItem')
|
||||||
|
{
|
||||||
|
var node = child.firstChild;
|
||||||
|
if (j==id)
|
||||||
|
{
|
||||||
|
node.innerHTML='•';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
node.innerHTML=' ';
|
||||||
|
}
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when an search filter selection is made.
|
||||||
|
// set item with index id as the active item
|
||||||
|
this.OnSelectItem = function(id)
|
||||||
|
{
|
||||||
|
this.searchIndex = id;
|
||||||
|
this.SelectItemSet(id);
|
||||||
|
var searchValue = this.DOMSearchField().value.replace(/ +/g, "");
|
||||||
|
if (searchValue!="" && this.searchActive) // something was found -> do a search
|
||||||
|
{
|
||||||
|
this.Search();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.OnSearchSelectKey = function(evt)
|
||||||
|
{
|
||||||
|
var e = (evt) ? evt : window.event; // for IE
|
||||||
|
if (e.keyCode==40 && this.searchIndex<this.SelectItemCount()) // Down
|
||||||
|
{
|
||||||
|
this.searchIndex++;
|
||||||
|
this.OnSelectItem(this.searchIndex);
|
||||||
|
}
|
||||||
|
else if (e.keyCode==38 && this.searchIndex>0) // Up
|
||||||
|
{
|
||||||
|
this.searchIndex--;
|
||||||
|
this.OnSelectItem(this.searchIndex);
|
||||||
|
}
|
||||||
|
else if (e.keyCode==13 || e.keyCode==27)
|
||||||
|
{
|
||||||
|
this.OnSelectItem(this.searchIndex);
|
||||||
|
this.CloseSelectionWindow();
|
||||||
|
this.DOMSearchField().focus();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------- Actions
|
||||||
|
|
||||||
|
// Closes the results window.
|
||||||
|
this.CloseResultsWindow = function()
|
||||||
|
{
|
||||||
|
this.DOMPopupSearchResultsWindow().style.display = 'none';
|
||||||
|
this.DOMSearchClose().style.display = 'none';
|
||||||
|
this.Activate(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.CloseSelectionWindow = function()
|
||||||
|
{
|
||||||
|
this.DOMSearchSelectWindow().style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Performs a search.
|
||||||
|
this.Search = function()
|
||||||
|
{
|
||||||
|
this.keyTimeout = 0;
|
||||||
|
|
||||||
|
// strip leading whitespace
|
||||||
|
var searchValue = this.DOMSearchField().value.replace(/^ +/, "");
|
||||||
|
|
||||||
|
var code = searchValue.toLowerCase().charCodeAt(0);
|
||||||
|
var idxChar = searchValue.substr(0, 1).toLowerCase();
|
||||||
|
if ( 0xD800 <= code && code <= 0xDBFF && searchValue > 1) // surrogate pair
|
||||||
|
{
|
||||||
|
idxChar = searchValue.substr(0, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultsPage;
|
||||||
|
var resultsPageWithSearch;
|
||||||
|
var hasResultsPage;
|
||||||
|
|
||||||
|
var idx = indexSectionsWithContent[this.searchIndex].indexOf(idxChar);
|
||||||
|
if (idx!=-1)
|
||||||
|
{
|
||||||
|
var hexCode=idx.toString(16);
|
||||||
|
resultsPage = this.resultsPath + '/' + indexSectionNames[this.searchIndex] + '_' + hexCode + '.html';
|
||||||
|
resultsPageWithSearch = resultsPage+'?'+escape(searchValue);
|
||||||
|
hasResultsPage = true;
|
||||||
|
}
|
||||||
|
else // nothing available for this search term
|
||||||
|
{
|
||||||
|
resultsPage = this.resultsPath + '/nomatches.html';
|
||||||
|
resultsPageWithSearch = resultsPage;
|
||||||
|
hasResultsPage = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.frames.MSearchResults.location = resultsPageWithSearch;
|
||||||
|
var domPopupSearchResultsWindow = this.DOMPopupSearchResultsWindow();
|
||||||
|
|
||||||
|
if (domPopupSearchResultsWindow.style.display!='block')
|
||||||
|
{
|
||||||
|
var domSearchBox = this.DOMSearchBox();
|
||||||
|
this.DOMSearchClose().style.display = 'inline';
|
||||||
|
if (this.insideFrame)
|
||||||
|
{
|
||||||
|
var domPopupSearchResults = this.DOMPopupSearchResults();
|
||||||
|
domPopupSearchResultsWindow.style.position = 'relative';
|
||||||
|
domPopupSearchResultsWindow.style.display = 'block';
|
||||||
|
var width = document.body.clientWidth - 8; // the -8 is for IE :-(
|
||||||
|
domPopupSearchResultsWindow.style.width = width + 'px';
|
||||||
|
domPopupSearchResults.style.width = width + 'px';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var domPopupSearchResults = this.DOMPopupSearchResults();
|
||||||
|
var left = getXPos(domSearchBox) + 150; // domSearchBox.offsetWidth;
|
||||||
|
var top = getYPos(domSearchBox) + 20; // domSearchBox.offsetHeight + 1;
|
||||||
|
domPopupSearchResultsWindow.style.display = 'block';
|
||||||
|
left -= domPopupSearchResults.offsetWidth;
|
||||||
|
domPopupSearchResultsWindow.style.top = top + 'px';
|
||||||
|
domPopupSearchResultsWindow.style.left = left + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastSearchValue = searchValue;
|
||||||
|
this.lastResultsPage = resultsPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- Activation Functions
|
||||||
|
|
||||||
|
// Activates or deactivates the search panel, resetting things to
|
||||||
|
// their default values if necessary.
|
||||||
|
this.Activate = function(isActive)
|
||||||
|
{
|
||||||
|
if (isActive || // open it
|
||||||
|
this.DOMPopupSearchResultsWindow().style.display == 'block'
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.DOMSearchBox().className = 'MSearchBoxActive';
|
||||||
|
|
||||||
|
var searchField = this.DOMSearchField();
|
||||||
|
|
||||||
|
if (searchField.value == this.searchLabel) // clear "Search" term upon entry
|
||||||
|
{
|
||||||
|
searchField.value = '';
|
||||||
|
this.searchActive = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!isActive) // directly remove the panel
|
||||||
|
{
|
||||||
|
this.DOMSearchBox().className = 'MSearchBoxInactive';
|
||||||
|
this.DOMSearchField().value = this.searchLabel;
|
||||||
|
this.searchActive = false;
|
||||||
|
this.lastSearchValue = ''
|
||||||
|
this.lastResultsPage = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
// The class that handles everything on the search results page.
|
||||||
|
function SearchResults(name)
|
||||||
|
{
|
||||||
|
// The number of matches from the last run of <Search()>.
|
||||||
|
this.lastMatchCount = 0;
|
||||||
|
this.lastKey = 0;
|
||||||
|
this.repeatOn = false;
|
||||||
|
|
||||||
|
// Toggles the visibility of the passed element ID.
|
||||||
|
this.FindChildElement = function(id)
|
||||||
|
{
|
||||||
|
var parentElement = document.getElementById(id);
|
||||||
|
var element = parentElement.firstChild;
|
||||||
|
|
||||||
|
while (element && element!=parentElement)
|
||||||
|
{
|
||||||
|
if (element.nodeName == 'DIV' && element.className == 'SRChildren')
|
||||||
|
{
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.nodeName == 'DIV' && element.hasChildNodes())
|
||||||
|
{
|
||||||
|
element = element.firstChild;
|
||||||
|
}
|
||||||
|
else if (element.nextSibling)
|
||||||
|
{
|
||||||
|
element = element.nextSibling;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
element = element.parentNode;
|
||||||
|
}
|
||||||
|
while (element && element!=parentElement && !element.nextSibling);
|
||||||
|
|
||||||
|
if (element && element!=parentElement)
|
||||||
|
{
|
||||||
|
element = element.nextSibling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Toggle = function(id)
|
||||||
|
{
|
||||||
|
var element = this.FindChildElement(id);
|
||||||
|
if (element)
|
||||||
|
{
|
||||||
|
if (element.style.display == 'block')
|
||||||
|
{
|
||||||
|
element.style.display = 'none';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
element.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Searches for the passed string. If there is no parameter,
|
||||||
|
// it takes it from the URL query.
|
||||||
|
//
|
||||||
|
// Always returns true, since other documents may try to call it
|
||||||
|
// and that may or may not be possible.
|
||||||
|
this.Search = function(search)
|
||||||
|
{
|
||||||
|
if (!search) // get search word from URL
|
||||||
|
{
|
||||||
|
search = window.location.search;
|
||||||
|
search = search.substring(1); // Remove the leading '?'
|
||||||
|
search = unescape(search);
|
||||||
|
}
|
||||||
|
|
||||||
|
search = search.replace(/^ +/, ""); // strip leading spaces
|
||||||
|
search = search.replace(/ +$/, ""); // strip trailing spaces
|
||||||
|
search = search.toLowerCase();
|
||||||
|
search = convertToId(search);
|
||||||
|
|
||||||
|
var resultRows = document.getElementsByTagName("div");
|
||||||
|
var matches = 0;
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
while (i < resultRows.length)
|
||||||
|
{
|
||||||
|
var row = resultRows.item(i);
|
||||||
|
if (row.className == "SRResult")
|
||||||
|
{
|
||||||
|
var rowMatchName = row.id.toLowerCase();
|
||||||
|
rowMatchName = rowMatchName.replace(/^sr\d*_/, ''); // strip 'sr123_'
|
||||||
|
|
||||||
|
if (search.length<=rowMatchName.length &&
|
||||||
|
rowMatchName.substr(0, search.length)==search)
|
||||||
|
{
|
||||||
|
row.style.display = 'block';
|
||||||
|
matches++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
row.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
document.getElementById("Searching").style.display='none';
|
||||||
|
if (matches == 0) // no results
|
||||||
|
{
|
||||||
|
document.getElementById("NoMatches").style.display='block';
|
||||||
|
}
|
||||||
|
else // at least one result
|
||||||
|
{
|
||||||
|
document.getElementById("NoMatches").style.display='none';
|
||||||
|
}
|
||||||
|
this.lastMatchCount = matches;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the first item with index index or higher that is visible
|
||||||
|
this.NavNext = function(index)
|
||||||
|
{
|
||||||
|
var focusItem;
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
var focusName = 'Item'+index;
|
||||||
|
focusItem = document.getElementById(focusName);
|
||||||
|
if (focusItem && focusItem.parentNode.parentNode.style.display=='block')
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (!focusItem) // last element
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
focusItem=null;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return focusItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.NavPrev = function(index)
|
||||||
|
{
|
||||||
|
var focusItem;
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
var focusName = 'Item'+index;
|
||||||
|
focusItem = document.getElementById(focusName);
|
||||||
|
if (focusItem && focusItem.parentNode.parentNode.style.display=='block')
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (!focusItem) // last element
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
focusItem=null;
|
||||||
|
index--;
|
||||||
|
}
|
||||||
|
return focusItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ProcessKeys = function(e)
|
||||||
|
{
|
||||||
|
if (e.type == "keydown")
|
||||||
|
{
|
||||||
|
this.repeatOn = false;
|
||||||
|
this.lastKey = e.keyCode;
|
||||||
|
}
|
||||||
|
else if (e.type == "keypress")
|
||||||
|
{
|
||||||
|
if (!this.repeatOn)
|
||||||
|
{
|
||||||
|
if (this.lastKey) this.repeatOn = true;
|
||||||
|
return false; // ignore first keypress after keydown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (e.type == "keyup")
|
||||||
|
{
|
||||||
|
this.lastKey = 0;
|
||||||
|
this.repeatOn = false;
|
||||||
|
}
|
||||||
|
return this.lastKey!=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Nav = function(evt,itemIndex)
|
||||||
|
{
|
||||||
|
var e = (evt) ? evt : window.event; // for IE
|
||||||
|
if (e.keyCode==13) return true;
|
||||||
|
if (!this.ProcessKeys(e)) return false;
|
||||||
|
|
||||||
|
if (this.lastKey==38) // Up
|
||||||
|
{
|
||||||
|
var newIndex = itemIndex-1;
|
||||||
|
var focusItem = this.NavPrev(newIndex);
|
||||||
|
if (focusItem)
|
||||||
|
{
|
||||||
|
var child = this.FindChildElement(focusItem.parentNode.parentNode.id);
|
||||||
|
if (child && child.style.display == 'block') // children visible
|
||||||
|
{
|
||||||
|
var n=0;
|
||||||
|
var tmpElem;
|
||||||
|
while (1) // search for last child
|
||||||
|
{
|
||||||
|
tmpElem = document.getElementById('Item'+newIndex+'_c'+n);
|
||||||
|
if (tmpElem)
|
||||||
|
{
|
||||||
|
focusItem = tmpElem;
|
||||||
|
}
|
||||||
|
else // found it!
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (focusItem)
|
||||||
|
{
|
||||||
|
focusItem.focus();
|
||||||
|
}
|
||||||
|
else // return focus to search field
|
||||||
|
{
|
||||||
|
parent.document.getElementById("MSearchField").focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (this.lastKey==40) // Down
|
||||||
|
{
|
||||||
|
var newIndex = itemIndex+1;
|
||||||
|
var focusItem;
|
||||||
|
var item = document.getElementById('Item'+itemIndex);
|
||||||
|
var elem = this.FindChildElement(item.parentNode.parentNode.id);
|
||||||
|
if (elem && elem.style.display == 'block') // children visible
|
||||||
|
{
|
||||||
|
focusItem = document.getElementById('Item'+itemIndex+'_c0');
|
||||||
|
}
|
||||||
|
if (!focusItem) focusItem = this.NavNext(newIndex);
|
||||||
|
if (focusItem) focusItem.focus();
|
||||||
|
}
|
||||||
|
else if (this.lastKey==39) // Right
|
||||||
|
{
|
||||||
|
var item = document.getElementById('Item'+itemIndex);
|
||||||
|
var elem = this.FindChildElement(item.parentNode.parentNode.id);
|
||||||
|
if (elem) elem.style.display = 'block';
|
||||||
|
}
|
||||||
|
else if (this.lastKey==37) // Left
|
||||||
|
{
|
||||||
|
var item = document.getElementById('Item'+itemIndex);
|
||||||
|
var elem = this.FindChildElement(item.parentNode.parentNode.id);
|
||||||
|
if (elem) elem.style.display = 'none';
|
||||||
|
}
|
||||||
|
else if (this.lastKey==27) // Escape
|
||||||
|
{
|
||||||
|
parent.searchBox.CloseResultsWindow();
|
||||||
|
parent.document.getElementById("MSearchField").focus();
|
||||||
|
}
|
||||||
|
else if (this.lastKey==13) // Enter
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.NavChild = function(evt,itemIndex,childIndex)
|
||||||
|
{
|
||||||
|
var e = (evt) ? evt : window.event; // for IE
|
||||||
|
if (e.keyCode==13) return true;
|
||||||
|
if (!this.ProcessKeys(e)) return false;
|
||||||
|
|
||||||
|
if (this.lastKey==38) // Up
|
||||||
|
{
|
||||||
|
if (childIndex>0)
|
||||||
|
{
|
||||||
|
var newIndex = childIndex-1;
|
||||||
|
document.getElementById('Item'+itemIndex+'_c'+newIndex).focus();
|
||||||
|
}
|
||||||
|
else // already at first child, jump to parent
|
||||||
|
{
|
||||||
|
document.getElementById('Item'+itemIndex).focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (this.lastKey==40) // Down
|
||||||
|
{
|
||||||
|
var newIndex = childIndex+1;
|
||||||
|
var elem = document.getElementById('Item'+itemIndex+'_c'+newIndex);
|
||||||
|
if (!elem) // last child, jump to parent next parent
|
||||||
|
{
|
||||||
|
elem = this.NavNext(itemIndex+1);
|
||||||
|
}
|
||||||
|
if (elem)
|
||||||
|
{
|
||||||
|
elem.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (this.lastKey==27) // Escape
|
||||||
|
{
|
||||||
|
parent.searchBox.CloseResultsWindow();
|
||||||
|
parent.document.getElementById("MSearchField").focus();
|
||||||
|
}
|
||||||
|
else if (this.lastKey==13) // Enter
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setKeyActions(elem,action)
|
||||||
|
{
|
||||||
|
elem.setAttribute('onkeydown',action);
|
||||||
|
elem.setAttribute('onkeypress',action);
|
||||||
|
elem.setAttribute('onkeyup',action);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setClassAttr(elem,attr)
|
||||||
|
{
|
||||||
|
elem.setAttribute('class',attr);
|
||||||
|
elem.setAttribute('className',attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createResults()
|
||||||
|
{
|
||||||
|
var results = document.getElementById("SRResults");
|
||||||
|
for (var e=0; e<searchData.length; e++)
|
||||||
|
{
|
||||||
|
var id = searchData[e][0];
|
||||||
|
var srResult = document.createElement('div');
|
||||||
|
srResult.setAttribute('id','SR_'+id);
|
||||||
|
setClassAttr(srResult,'SRResult');
|
||||||
|
var srEntry = document.createElement('div');
|
||||||
|
setClassAttr(srEntry,'SREntry');
|
||||||
|
var srLink = document.createElement('a');
|
||||||
|
srLink.setAttribute('id','Item'+e);
|
||||||
|
setKeyActions(srLink,'return searchResults.Nav(event,'+e+')');
|
||||||
|
setClassAttr(srLink,'SRSymbol');
|
||||||
|
srLink.innerHTML = searchData[e][1][0];
|
||||||
|
srEntry.appendChild(srLink);
|
||||||
|
if (searchData[e][1].length==2) // single result
|
||||||
|
{
|
||||||
|
srLink.setAttribute('href',searchData[e][1][1][0]);
|
||||||
|
if (searchData[e][1][1][1])
|
||||||
|
{
|
||||||
|
srLink.setAttribute('target','_parent');
|
||||||
|
}
|
||||||
|
var srScope = document.createElement('span');
|
||||||
|
setClassAttr(srScope,'SRScope');
|
||||||
|
srScope.innerHTML = searchData[e][1][1][2];
|
||||||
|
srEntry.appendChild(srScope);
|
||||||
|
}
|
||||||
|
else // multiple results
|
||||||
|
{
|
||||||
|
srLink.setAttribute('href','javascript:searchResults.Toggle("SR_'+id+'")');
|
||||||
|
var srChildren = document.createElement('div');
|
||||||
|
setClassAttr(srChildren,'SRChildren');
|
||||||
|
for (var c=0; c<searchData[e][1].length-1; c++)
|
||||||
|
{
|
||||||
|
var srChild = document.createElement('a');
|
||||||
|
srChild.setAttribute('id','Item'+e+'_c'+c);
|
||||||
|
setKeyActions(srChild,'return searchResults.NavChild(event,'+e+','+c+')');
|
||||||
|
setClassAttr(srChild,'SRScope');
|
||||||
|
srChild.setAttribute('href',searchData[e][1][c+1][0]);
|
||||||
|
if (searchData[e][1][c+1][1])
|
||||||
|
{
|
||||||
|
srChild.setAttribute('target','_parent');
|
||||||
|
}
|
||||||
|
srChild.innerHTML = searchData[e][1][c+1][2];
|
||||||
|
srChildren.appendChild(srChild);
|
||||||
|
}
|
||||||
|
srEntry.appendChild(srChildren);
|
||||||
|
}
|
||||||
|
srResult.appendChild(srEntry);
|
||||||
|
results.appendChild(srResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function init_search()
|
||||||
|
{
|
||||||
|
var results = document.getElementById("MSearchSelectWindow");
|
||||||
|
for (var key in indexSectionLabels)
|
||||||
|
{
|
||||||
|
var link = document.createElement('a');
|
||||||
|
link.setAttribute('class','SelectItem');
|
||||||
|
link.setAttribute('onclick','searchBox.OnSelectItem('+key+')');
|
||||||
|
link.href='javascript:void(0)';
|
||||||
|
link.innerHTML='<span class="SelectionMark"> </span>'+indexSectionLabels[key];
|
||||||
|
results.appendChild(link);
|
||||||
|
}
|
||||||
|
searchBox.OnSelectItem(0);
|
||||||
|
}
|
||||||
|
|
||||||
BIN
html/search/search_l.png
Normal file
|
After Width: | Height: | Size: 604 B |
BIN
html/search/search_m.png
Normal file
|
After Width: | Height: | Size: 158 B |
BIN
html/search/search_r.png
Normal file
|
After Width: | Height: | Size: 612 B |
12
html/search/searchdata.js
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
var indexSectionsWithContent =
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
var indexSectionNames =
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
var indexSectionLabels =
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
BIN
html/splitbar.png
Normal file
|
After Width: | Height: | Size: 314 B |
BIN
html/sync_off.png
Normal file
|
After Width: | Height: | Size: 853 B |
BIN
html/sync_on.png
Normal file
|
After Width: | Height: | Size: 845 B |
BIN
html/tab_a.png
Normal file
|
After Width: | Height: | Size: 142 B |
BIN
html/tab_b.png
Normal file
|
After Width: | Height: | Size: 169 B |
BIN
html/tab_h.png
Normal file
|
After Width: | Height: | Size: 177 B |
BIN
html/tab_s.png
Normal file
|
After Width: | Height: | Size: 184 B |
1
html/tabs.css
Normal file
11
index.html
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html lang="en-US">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="refresh" content="1;url=html/index.html">
|
||||||
|
<title>Page Redirection</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
If you are not redirected automatically, follow the <a href="html/index.html">link to the documentation</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
name=Adafruit Protomatter
|
|
||||||
version=1.7.0
|
|
||||||
author=Adafruit
|
|
||||||
maintainer=Adafruit <info@adafruit.com>
|
|
||||||
sentence=A library for Adafruit RGB LED matrices.
|
|
||||||
paragraph=RGB LED matrix.
|
|
||||||
category=Display
|
|
||||||
url=https://github.com/adafruit/Adafruit_protomatter
|
|
||||||
architectures=samd,nrf52,stm32,esp32,rp2040
|
|
||||||
depends=Adafruit GFX Library, Adafruit LIS3DH, Adafruit PixelDust, AnimatedGIF, Adafruit SPIFlash, Adafruit TinyUSB Library
|
|
||||||
|
|
@ -1,141 +0,0 @@
|
||||||
/*!
|
|
||||||
* @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);
|
|
||||||
}
|
|
||||||
|
|
@ -1,162 +0,0 @@
|
||||||
// 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);
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief Adjust HUB clock signal duty cycle on architectures that support
|
|
||||||
this (currently SAMD51 only) (else ignored).
|
|
||||||
@param Duty setting, 0 minimum. Increasing values generate higher clock
|
|
||||||
duty cycles at the same frequency. Arbitrary granular units, max
|
|
||||||
varies by architecture and CPU speed, if supported at all.
|
|
||||||
e.g. SAMD51 @ 120 MHz supports 0 (~50% duty) through 2 (~75%).
|
|
||||||
*/
|
|
||||||
void setDuty(uint8_t d) { _PM_setDuty(d); };
|
|
||||||
|
|
||||||
private:
|
|
||||||
Protomatter_core core; // Underlying C struct
|
|
||||||
void convert_byte(uint8_t *dest); // GFXcanvas16-to-matrix
|
|
||||||
void convert_word(uint16_t *dest); // conversion functions
|
|
||||||
void convert_long(uint32_t *dest); // for 8/16/32 bit bufs
|
|
||||||
};
|
|
||||||
253
src/arch/arch.h
|
|
@ -1,253 +0,0 @@
|
||||||
/*!
|
|
||||||
* @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(Protomatter_core*): Initialize (but do not start) timer.
|
|
||||||
_PM_timerStart(Protomatter_core*,count): (Re)start timer for a given
|
|
||||||
timer-tick interval.
|
|
||||||
_PM_timerStop(Protomatter_core*): Stop timer, return current timer
|
|
||||||
counter value.
|
|
||||||
_PM_timerGetCount(Protomatter_core*): Get current timer counter value
|
|
||||||
(whether timer is running or stopped).
|
|
||||||
A timer interrupt service routine is also required, syntax for which varies
|
|
||||||
between architectures.
|
|
||||||
The void* argument passed to the timer functions is some indeterminate type
|
|
||||||
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.
|
|
||||||
_PM_bytesPerElement If defined, this allows an arch-specific source
|
|
||||||
file to override core's data size that's based
|
|
||||||
on pin selections. Reasonable values would be 1,
|
|
||||||
2 or 4. This came about during ESP32-S2
|
|
||||||
development; GPIO or I2S/LCD peripherals there
|
|
||||||
allows super flexible pin MUXing, so one byte
|
|
||||||
could be used even w/pins spread all over.
|
|
||||||
_PM_USE_TOGGLE_FORMAT If defined, this instructs the core code to
|
|
||||||
format pixel data for GPIO bit-toggling, even
|
|
||||||
if _PM_portToggleRegister is not defined.
|
|
||||||
_PM_CUSTOM_BLAST If defined, instructs core code to not compile
|
|
||||||
the blast_byte(), blast_word() or blast_long()
|
|
||||||
functions; these will be declared in the arch-
|
|
||||||
specific file instead. This might benefit
|
|
||||||
architectures, where DMA, PIO or other
|
|
||||||
specialized peripherals could be set up to
|
|
||||||
issue data independent of the CPU. This goes
|
|
||||||
against's Protomatter's normal design of using
|
|
||||||
the most baseline peripherals for a given
|
|
||||||
architecture, but time marches on, y'know?
|
|
||||||
*/
|
|
||||||
|
|
||||||
// ENVIRONMENT-SPECIFIC DECLARATIONS ---------------------------------------
|
|
||||||
|
|
||||||
#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)
|
|
||||||
|
|
||||||
#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 -------------------------------------------
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
#include "esp32-common.h"
|
|
||||||
#include "esp32.h" // Original ESP32
|
|
||||||
#include "esp32-s2.h"
|
|
||||||
#include "esp32-s3.h"
|
|
||||||
#include "esp32-c3.h"
|
|
||||||
#include "esp32-c6.h"
|
|
||||||
#include "nrf52.h"
|
|
||||||
#include "rp2040.h"
|
|
||||||
#include "samd-common.h"
|
|
||||||
#include "samd21.h"
|
|
||||||
#include "samd51.h"
|
|
||||||
#include "stm32.h"
|
|
||||||
#include "teensy4.h"
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
// DEFAULTS IF NOT DEFINED ABOVE -------------------------------------------
|
|
||||||
|
|
||||||
#if defined(_PM_portToggleRegister)
|
|
||||||
#define _PM_USE_TOGGLE_FORMAT
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !defined(_PM_portBitMask)
|
|
||||||
#define _PM_portBitMask(pin) digitalPinToBitMask(pin)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !defined(_PM_chunkSize)
|
|
||||||
#define _PM_chunkSize 8 ///< Unroll data-stuffing loop to this size
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#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
|
|
||||||
|
|
||||||
#if !defined(_PM_maxDuty)
|
|
||||||
#define _PM_maxDuty 0 ///< Max duty cycle setting (where supported)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !defined(_PM_defaultDuty)
|
|
||||||
#define _PM_defaultDuty 0 ///< Default duty cycle setting (where supported)
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
/*!
|
|
||||||
* @file esp32-c3.h
|
|
||||||
*
|
|
||||||
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
|
|
||||||
* This file contains ESP32-C3-SPECIFIC CODE.
|
|
||||||
*
|
|
||||||
* Adafruit invests time and resources providing this open source code,
|
|
||||||
* please support Adafruit and open-source hardware by purchasing
|
|
||||||
* products from Adafruit!
|
|
||||||
*
|
|
||||||
* Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for
|
|
||||||
* Adafruit Industries, with contributions from the open source community.
|
|
||||||
*
|
|
||||||
* BSD license, all text here must be included in any redistribution.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
// NOTE: there is some intentional repetition in the macros and functions
|
|
||||||
// for some ESP32 variants. Previously they were all one file, but complex
|
|
||||||
// preprocessor directives were turning into spaghetti. THEREFORE, if making
|
|
||||||
// a change or bugfix in one variant-specific header, check the others to
|
|
||||||
// see if the same should be applied!
|
|
||||||
|
|
||||||
#if defined(CONFIG_IDF_TARGET_ESP32C3)
|
|
||||||
|
|
||||||
#define _PM_portOutRegister(pin) (volatile uint32_t *)&GPIO.out
|
|
||||||
#define _PM_portSetRegister(pin) (volatile uint32_t *)&GPIO.out_w1ts
|
|
||||||
#define _PM_portClearRegister(pin) (volatile uint32_t *)&GPIO.out_w1tc
|
|
||||||
|
|
||||||
#define _PM_portBitMask(pin) (1U << ((pin) & 31))
|
|
||||||
|
|
||||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
|
||||||
#define _PM_byteOffset(pin) ((pin & 31) / 8)
|
|
||||||
#define _PM_wordOffset(pin) ((pin & 31) / 16)
|
|
||||||
#else
|
|
||||||
#define _PM_byteOffset(pin) (3 - ((pin & 31) / 8))
|
|
||||||
#define _PM_wordOffset(pin) (1 - ((pin & 31) / 16))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// No special peripheral setup on ESP32C3, just use common timer init...
|
|
||||||
#define _PM_timerInit(core) _PM_esp32commonTimerInit(core);
|
|
||||||
|
|
||||||
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
|
|
||||||
// Return current count value (timer enabled or not).
|
|
||||||
// Timer must be previously initialized.
|
|
||||||
// This function is the same on all ESP32 parts EXCEPT S3.
|
|
||||||
IRAM_ATTR inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
|
|
||||||
return (uint32_t)timerRead((hw_timer_t *)core->timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
|
|
||||||
|
|
||||||
#endif // END CIRCUITPYTHON ------------------------------------------------
|
|
||||||
|
|
||||||
#endif // END ESP32C3
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
/*!
|
|
||||||
* @file esp32-c3.h
|
|
||||||
*
|
|
||||||
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
|
|
||||||
* This file contains ESP32-C3-SPECIFIC CODE.
|
|
||||||
*
|
|
||||||
* Adafruit invests time and resources providing this open source code,
|
|
||||||
* please support Adafruit and open-source hardware by purchasing
|
|
||||||
* products from Adafruit!
|
|
||||||
*
|
|
||||||
* Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for
|
|
||||||
* Adafruit Industries, with contributions from the open source community.
|
|
||||||
*
|
|
||||||
* BSD license, all text here must be included in any redistribution.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
// NOTE: there is some intentional repetition in the macros and functions
|
|
||||||
// for some ESP32 variants. Previously they were all one file, but complex
|
|
||||||
// preprocessor directives were turning into spaghetti. THEREFORE, if making
|
|
||||||
// a change or bugfix in one variant-specific header, check the others to
|
|
||||||
// see if the same should be applied!
|
|
||||||
|
|
||||||
#if defined(CONFIG_IDF_TARGET_ESP32C6)
|
|
||||||
|
|
||||||
#define _PM_portOutRegister(pin) (volatile uint32_t *)&GPIO.out
|
|
||||||
#define _PM_portSetRegister(pin) (volatile uint32_t *)&GPIO.out_w1ts
|
|
||||||
#define _PM_portClearRegister(pin) (volatile uint32_t *)&GPIO.out_w1tc
|
|
||||||
|
|
||||||
#define _PM_portBitMask(pin) (1U << ((pin) & 31))
|
|
||||||
|
|
||||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
|
||||||
#define _PM_byteOffset(pin) ((pin & 31) / 8)
|
|
||||||
#define _PM_wordOffset(pin) ((pin & 31) / 16)
|
|
||||||
#else
|
|
||||||
#define _PM_byteOffset(pin) (3 - ((pin & 31) / 8))
|
|
||||||
#define _PM_wordOffset(pin) (1 - ((pin & 31) / 16))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// No special peripheral setup on ESP32C3, just use common timer init...
|
|
||||||
#define _PM_timerInit(core) _PM_esp32commonTimerInit(core);
|
|
||||||
|
|
||||||
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
|
|
||||||
// Return current count value (timer enabled or not).
|
|
||||||
// Timer must be previously initialized.
|
|
||||||
// This function is the same on all ESP32 parts EXCEPT S3.
|
|
||||||
IRAM_ATTR inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
|
|
||||||
return (uint32_t)timerRead((hw_timer_t *)core->timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
|
|
||||||
|
|
||||||
#endif // END CIRCUITPYTHON ------------------------------------------------
|
|
||||||
|
|
||||||
#endif // END ESP32C3
|
|
||||||
|
|
@ -1,247 +0,0 @@
|
||||||
/*!
|
|
||||||
* @file esp32-common.h
|
|
||||||
*
|
|
||||||
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
|
|
||||||
* This file contains ESP32-SPECIFIC CODE (common to all ESP variants).
|
|
||||||
*
|
|
||||||
* Adafruit invests time and resources providing this open source code,
|
|
||||||
* please support Adafruit and open-source hardware by purchasing
|
|
||||||
* products from Adafruit!
|
|
||||||
*
|
|
||||||
* Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for
|
|
||||||
* Adafruit Industries, with contributions from the open source community.
|
|
||||||
*
|
|
||||||
* BSD license, all text here must be included in any redistribution.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#if defined(ESP32) || \
|
|
||||||
defined(ESP_PLATFORM) // *All* ESP32 variants (OG, S2, S3, etc.)
|
|
||||||
|
|
||||||
#include <inttypes.h>
|
|
||||||
|
|
||||||
#include "esp_idf_version.h"
|
|
||||||
|
|
||||||
// NOTE: there is some intentional repetition in the macros and functions
|
|
||||||
// for some ESP32 variants. Previously they were all one file, but complex
|
|
||||||
// preprocessor directives were turning into spaghetti. THEREFORE, if making
|
|
||||||
// a change or bugfix in one variant-specific header, check the others to
|
|
||||||
// see if the same should be applied!
|
|
||||||
|
|
||||||
#include "soc/gpio_periph.h"
|
|
||||||
|
|
||||||
// As currently written, only one instance of the Protomatter_core struct
|
|
||||||
// is allowed, set up when calling begin()...so it's just a global here:
|
|
||||||
Protomatter_core *_PM_protoPtr;
|
|
||||||
|
|
||||||
#define _PM_timerFreq 40000000 // 40 MHz (1:2 prescale)
|
|
||||||
|
|
||||||
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
|
|
||||||
|
|
||||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
|
||||||
#define _PM_timerNum 0 // Timer #0 (can be 0-3)
|
|
||||||
static hw_timer_t *_PM_esp32timer = NULL;
|
|
||||||
#define _PM_TIMER_DEFAULT &_PM_esp32timer
|
|
||||||
#else
|
|
||||||
#define _PM_TIMER_DEFAULT ((void *)-1) // some non-NULL but non valid pointer
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// The following defines and functions are common to all ESP32 variants in
|
|
||||||
// the Arduino platform. Anything unique to one variant (or a subset of
|
|
||||||
// variants) is declared in the corresponding esp32-*.h header(s); please
|
|
||||||
// no #if defined(CONFIG_IDF_TARGET_*) action here...if you find yourself
|
|
||||||
// started down that path, it's okay, but move the code out of here and
|
|
||||||
// into the variant-specific headers.
|
|
||||||
|
|
||||||
extern void _PM_row_handler(Protomatter_core *core); // In core.c
|
|
||||||
|
|
||||||
// Timer interrupt handler. This, _PM_row_handler() and any functions
|
|
||||||
// called by _PM_row_handler() should all have the IRAM_ATTR attribute
|
|
||||||
// (RAM-resident functions). This isn't really the ISR itself, but a
|
|
||||||
// callback invoked by the real ISR (in arduino-esp32's esp32-hal-timer.c)
|
|
||||||
// which takes care of interrupt status bits & such.
|
|
||||||
IRAM_ATTR static void _PM_esp32timerCallback(void) {
|
|
||||||
_PM_row_handler(_PM_protoPtr); // In core.c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set timer period, initialize count value to zero, enable timer.
|
|
||||||
IRAM_ATTR inline void _PM_timerStart(Protomatter_core *core, uint32_t period) {
|
|
||||||
hw_timer_t *timer = (hw_timer_t *)core->timer;
|
|
||||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
|
||||||
timerAlarmWrite(timer, period, true);
|
|
||||||
timerAlarmEnable(timer);
|
|
||||||
timerStart(timer);
|
|
||||||
#else
|
|
||||||
timerWrite(timer, 0);
|
|
||||||
timerAlarm(timer, period ? period : 1, true, 0);
|
|
||||||
timerStart(timer);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable timer and return current count value.
|
|
||||||
// Timer must be previously initialized.
|
|
||||||
IRAM_ATTR uint32_t _PM_timerStop(Protomatter_core *core) {
|
|
||||||
timerStop((hw_timer_t *)core->timer);
|
|
||||||
return _PM_timerGetCount(core);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize, but do not start, timer. This function contains timer setup
|
|
||||||
// that's common to all ESP32 variants; code in variant-specific files might
|
|
||||||
// set up its own special peripherals, then call this.
|
|
||||||
void _PM_esp32commonTimerInit(Protomatter_core *core) {
|
|
||||||
hw_timer_t *timer_in = (hw_timer_t *)core->timer;
|
|
||||||
if (!timer_in || timer_in == _PM_TIMER_DEFAULT) {
|
|
||||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
|
||||||
core->timer = timerBegin(_PM_timerNum, 2, true); // 1:2 prescale, count up
|
|
||||||
#else
|
|
||||||
core->timer = timerBegin(_PM_timerFreq);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
|
||||||
timerAttachInterrupt(core->timer, &_PM_esp32timerCallback, true);
|
|
||||||
#else
|
|
||||||
timerAttachInterrupt(core->timer, _PM_esp32timerCallback);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
|
|
||||||
|
|
||||||
// The following defines and functions are common to all ESP32 variants in
|
|
||||||
// the CircuitPython platform. Anything unique to one variant (or a subset
|
|
||||||
// of variants) is declared in the corresponding esp32-*.h header(s);
|
|
||||||
// please no #if defined(CONFIG_IDF_TARGET_*) action here...if you find
|
|
||||||
// yourself started down that path, it's okay, but move the code out of
|
|
||||||
// here and into the variant-specific headers.
|
|
||||||
|
|
||||||
#include "driver/gpio.h"
|
|
||||||
#include "esp_idf_version.h"
|
|
||||||
#include "hal/timer_ll.h"
|
|
||||||
#if ESP_IDF_VERSION_MAJOR == 5
|
|
||||||
#include "driver/gptimer.h"
|
|
||||||
#include "esp_memory_utils.h"
|
|
||||||
#else
|
|
||||||
#include "driver/timer.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define _PM_TIMER_DEFAULT NULL
|
|
||||||
#define _PM_pinOutput(pin) gpio_set_direction((pin), GPIO_MODE_OUTPUT)
|
|
||||||
#define _PM_pinLow(pin) gpio_set_level((pin), false)
|
|
||||||
#define _PM_pinHigh(pin) gpio_set_level((pin), true)
|
|
||||||
|
|
||||||
// Timer interrupt handler. This, _PM_row_handler() and any functions
|
|
||||||
// called by _PM_row_handler() should all have the IRAM_ATTR attribute
|
|
||||||
// (RAM-resident functions). This isn't really the ISR itself, but a
|
|
||||||
// callback invoked by the real ISR (in arduino-esp32's esp32-hal-timer.c)
|
|
||||||
// which takes care of interrupt status bits & such.
|
|
||||||
#if ESP_IDF_VERSION_MAJOR == 5
|
|
||||||
// This is "private" for now. We link to it anyway because there isn't a more
|
|
||||||
// public method yet.
|
|
||||||
extern bool spi_flash_cache_enabled(void);
|
|
||||||
static IRAM_ATTR bool
|
|
||||||
_PM_esp32timerCallback(gptimer_handle_t timer,
|
|
||||||
const gptimer_alarm_event_data_t *event, void *unused) {
|
|
||||||
#else
|
|
||||||
static IRAM_ATTR bool _PM_esp32timerCallback(void *unused) {
|
|
||||||
#endif
|
|
||||||
#if ESP_IDF_VERSION_MAJOR == 5
|
|
||||||
// Some functions and data used by _PM_row_handler may exist in external flash
|
|
||||||
// or PSRAM so we can't run them when their access is disabled (through the
|
|
||||||
// flash cache.)
|
|
||||||
if (_PM_protoPtr && spi_flash_cache_enabled()) {
|
|
||||||
#else
|
|
||||||
if (_PM_protoPtr) {
|
|
||||||
#endif
|
|
||||||
_PM_row_handler(_PM_protoPtr); // In core.c
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Set timer period, initialize count value to zero, enable timer.
|
|
||||||
#if (ESP_IDF_VERSION_MAJOR == 5)
|
|
||||||
IRAM_ATTR void _PM_timerStart(Protomatter_core *core, uint32_t period) {
|
|
||||||
gptimer_handle_t timer = (gptimer_handle_t)core->timer;
|
|
||||||
|
|
||||||
gptimer_alarm_config_t alarm_config = {
|
|
||||||
.reload_count = 0, // counter will reload with 0 on alarm event
|
|
||||||
.alarm_count = period, // period in ms
|
|
||||||
.flags.auto_reload_on_alarm = true, // enable auto-reload
|
|
||||||
};
|
|
||||||
gptimer_set_alarm_action(timer, &alarm_config);
|
|
||||||
gptimer_start(timer);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
IRAM_ATTR void _PM_timerStart(Protomatter_core *core, uint32_t period) {
|
|
||||||
timer_index_t *timer = (timer_index_t *)core->timer;
|
|
||||||
timer_ll_set_counter_enable(timer->hw, timer->idx, false);
|
|
||||||
timer_ll_set_counter_value(timer->hw, timer->idx, 0);
|
|
||||||
timer_ll_set_alarm_value(timer->hw, timer->idx, period);
|
|
||||||
timer_ll_set_alarm_enable(timer->hw, timer->idx, true);
|
|
||||||
timer_ll_set_counter_enable(timer->hw, timer->idx, true);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Disable timer and return current count value.
|
|
||||||
// Timer must be previously initialized.
|
|
||||||
IRAM_ATTR uint32_t _PM_timerStop(Protomatter_core *core) {
|
|
||||||
#if (ESP_IDF_VERSION_MAJOR == 5)
|
|
||||||
gptimer_handle_t timer = (gptimer_handle_t)core->timer;
|
|
||||||
gptimer_stop(timer);
|
|
||||||
#else
|
|
||||||
timer_index_t *timer = (timer_index_t *)core->timer;
|
|
||||||
timer_ll_set_counter_enable(timer->hw, timer->idx, false);
|
|
||||||
#endif
|
|
||||||
return _PM_timerGetCount(core);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !defined(CONFIG_IDF_TARGET_ESP32S3)
|
|
||||||
IRAM_ATTR uint32_t _PM_timerGetCount(Protomatter_core *core) {
|
|
||||||
#if (ESP_IDF_VERSION_MAJOR == 5)
|
|
||||||
gptimer_handle_t timer = (gptimer_handle_t)core->timer;
|
|
||||||
uint64_t raw_count;
|
|
||||||
gptimer_get_raw_count(timer, &raw_count);
|
|
||||||
return (uint32_t)raw_count;
|
|
||||||
#else
|
|
||||||
timer_index_t *timer = (timer_index_t *)core->timer;
|
|
||||||
uint64_t result;
|
|
||||||
timer_ll_get_counter_value(timer->hw, timer->idx, &result);
|
|
||||||
return (uint32_t)result;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Initialize, but do not start, timer. This function contains timer setup
|
|
||||||
// that's common to all ESP32 variants; code in variant-specific files might
|
|
||||||
// set up its own special peripherals, then call this.
|
|
||||||
static void _PM_esp32commonTimerInit(Protomatter_core *core) {
|
|
||||||
|
|
||||||
#if (ESP_IDF_VERSION_MAJOR == 5)
|
|
||||||
gptimer_handle_t timer = (gptimer_handle_t)core->timer;
|
|
||||||
gptimer_event_callbacks_t cbs = {
|
|
||||||
.on_alarm = _PM_esp32timerCallback, // register user callback
|
|
||||||
};
|
|
||||||
gptimer_register_event_callbacks(timer, &cbs, NULL);
|
|
||||||
|
|
||||||
gptimer_enable(timer);
|
|
||||||
#else
|
|
||||||
timer_index_t *timer = (timer_index_t *)core->timer;
|
|
||||||
const timer_config_t config = {
|
|
||||||
.alarm_en = false,
|
|
||||||
.counter_en = false,
|
|
||||||
.intr_type = TIMER_INTR_LEVEL,
|
|
||||||
.counter_dir = TIMER_COUNT_UP,
|
|
||||||
.auto_reload = true,
|
|
||||||
.divider = 2 // 40MHz
|
|
||||||
};
|
|
||||||
|
|
||||||
timer_init(timer->group, timer->idx, &config);
|
|
||||||
timer_isr_callback_add(timer->group, timer->idx, _PM_esp32timerCallback, NULL,
|
|
||||||
0);
|
|
||||||
timer_enable_intr(timer->group, timer->idx);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // END CIRCUITPYTHON ------------------------------------------------
|
|
||||||
|
|
||||||
#endif // END ESP32 (all variants)
|
|
||||||
|
|
@ -1,213 +0,0 @@
|
||||||
/*!
|
|
||||||
* @file esp32-s2.h
|
|
||||||
*
|
|
||||||
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
|
|
||||||
* This file contains ESP32-S2-SPECIFIC CODE.
|
|
||||||
*
|
|
||||||
* Adafruit invests time and resources providing this open source code,
|
|
||||||
* please support Adafruit and open-source hardware by purchasing
|
|
||||||
* products from Adafruit!
|
|
||||||
*
|
|
||||||
* Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for
|
|
||||||
* Adafruit Industries, with contributions from the open source community.
|
|
||||||
*
|
|
||||||
* BSD license, all text here must be included in any redistribution.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
// NOTE: there is some intentional repetition in the macros and functions
|
|
||||||
// for some ESP32 variants. Previously they were all one file, but complex
|
|
||||||
// preprocessor directives were turning into spaghetti. THEREFORE, if making
|
|
||||||
// a change or bugfix in one variant-specific header, check the others to
|
|
||||||
// see if the same should be applied!
|
|
||||||
|
|
||||||
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
|
||||||
|
|
||||||
#define _PM_portOutRegister(pin) \
|
|
||||||
(volatile uint32_t *)((pin < 32) ? &GPIO.out : &GPIO.out1.val)
|
|
||||||
#define _PM_portSetRegister(pin) \
|
|
||||||
(volatile uint32_t *)((pin < 32) ? &GPIO.out_w1ts : &GPIO.out1_w1ts.val)
|
|
||||||
#define _PM_portClearRegister(pin) \
|
|
||||||
(volatile uint32_t *)((pin < 32) ? &GPIO.out_w1tc : &GPIO.out1_w1tc.val)
|
|
||||||
|
|
||||||
// On ESP32-S2, use the Dedicated GPIO peripheral, which allows faster bit-
|
|
||||||
// toggling than the conventional GPIO registers. Unfortunately NOT present
|
|
||||||
// on S3 or other ESP32 devices. Normal GPIO has a bottleneck where toggling
|
|
||||||
// a pin is limited to 8 MHz max. Dedicated GPIO can work around this and
|
|
||||||
// get over twice this rate, but requires some very weird hoops!
|
|
||||||
// Dedicated GPIO only supports 8-bit output, so parallel output isn't
|
|
||||||
// supported, but positives include that there's very flexible pin MUXing,
|
|
||||||
// so matrix data in RAM can ALWAYS be stored in byte format regardless how
|
|
||||||
// the RGB+clock bits are distributed among pins.
|
|
||||||
#define _PM_bytesPerElement 1
|
|
||||||
#define _PM_byteOffset(pin) 0
|
|
||||||
#define _PM_wordOffset(pin) 0
|
|
||||||
|
|
||||||
// Odd thing with Dedicated GPIO is that the bit-toggle operation is faster
|
|
||||||
// than bit set or clear (perhaps some underlying operation is atomic rather
|
|
||||||
// than read-modify-write). So, instruct core.c to format the matrix data in
|
|
||||||
// RAM as if we're using a port toggle register, even though
|
|
||||||
// _PM_portToggleRegister is NOT defined because ESP32 doesn't have that in
|
|
||||||
// conventional GPIO. Good times.
|
|
||||||
#define _PM_USE_TOGGLE_FORMAT
|
|
||||||
|
|
||||||
// This table is used to remap 7-bit (RGB+RGB+clock) data to the 2-bits-per
|
|
||||||
// GPIO format used by Dedicated GPIO. Bits corresponding to clock output
|
|
||||||
// are always set here, as we want that bit toggled low at the same time new
|
|
||||||
// RGB data is set up.
|
|
||||||
static uint16_t _bit_toggle[128] = {
|
|
||||||
0x3000, 0x3003, 0x300C, 0x300F, 0x3030, 0x3033, 0x303C, 0x303F, 0x30C0,
|
|
||||||
0x30C3, 0x30CC, 0x30CF, 0x30F0, 0x30F3, 0x30FC, 0x30FF, 0x3300, 0x3303,
|
|
||||||
0x330C, 0x330F, 0x3330, 0x3333, 0x333C, 0x333F, 0x33C0, 0x33C3, 0x33CC,
|
|
||||||
0x33CF, 0x33F0, 0x33F3, 0x33FC, 0x33FF, 0x3C00, 0x3C03, 0x3C0C, 0x3C0F,
|
|
||||||
0x3C30, 0x3C33, 0x3C3C, 0x3C3F, 0x3CC0, 0x3CC3, 0x3CCC, 0x3CCF, 0x3CF0,
|
|
||||||
0x3CF3, 0x3CFC, 0x3CFF, 0x3F00, 0x3F03, 0x3F0C, 0x3F0F, 0x3F30, 0x3F33,
|
|
||||||
0x3F3C, 0x3F3F, 0x3FC0, 0x3FC3, 0x3FCC, 0x3FCF, 0x3FF0, 0x3FF3, 0x3FFC,
|
|
||||||
0x3FFF, 0x3000, 0x3003, 0x300C, 0x300F, 0x3030, 0x3033, 0x303C, 0x303F,
|
|
||||||
0x30C0, 0x30C3, 0x30CC, 0x30CF, 0x30F0, 0x30F3, 0x30FC, 0x30FF, 0x3300,
|
|
||||||
0x3303, 0x330C, 0x330F, 0x3330, 0x3333, 0x333C, 0x333F, 0x33C0, 0x33C3,
|
|
||||||
0x33CC, 0x33CF, 0x33F0, 0x33F3, 0x33FC, 0x33FF, 0x3C00, 0x3C03, 0x3C0C,
|
|
||||||
0x3C0F, 0x3C30, 0x3C33, 0x3C3C, 0x3C3F, 0x3CC0, 0x3CC3, 0x3CCC, 0x3CCF,
|
|
||||||
0x3CF0, 0x3CF3, 0x3CFC, 0x3CFF, 0x3F00, 0x3F03, 0x3F0C, 0x3F0F, 0x3F30,
|
|
||||||
0x3F33, 0x3F3C, 0x3F3F, 0x3FC0, 0x3FC3, 0x3FCC, 0x3FCF, 0x3FF0, 0x3FF3,
|
|
||||||
0x3FFC, 0x3FFF,
|
|
||||||
};
|
|
||||||
|
|
||||||
#include <driver/dedic_gpio.h>
|
|
||||||
#include <soc/dedic_gpio_reg.h>
|
|
||||||
#include <soc/dedic_gpio_struct.h>
|
|
||||||
|
|
||||||
// Override the behavior of _PM_portBitMask macro so instead of returning
|
|
||||||
// a 32-bit mask for a pin within its corresponding GPIO register, it instead
|
|
||||||
// returns a 7-bit mask for the pin within the Direct GPIO register *IF* it's
|
|
||||||
// one of the RGB bits or the clock bit...this requires comparing against pin
|
|
||||||
// numbers in the core struct.
|
|
||||||
static uint32_t _PM_directBitMask(Protomatter_core *core, int pin) {
|
|
||||||
if (pin == core->clockPin)
|
|
||||||
return 1 << 6;
|
|
||||||
for (uint8_t i = 0; i < 6; i++) {
|
|
||||||
if (pin == core->rgbPins[i])
|
|
||||||
return 1 << i;
|
|
||||||
}
|
|
||||||
// Else return the bit that would normally be used for regular GPIO
|
|
||||||
return (1U << (pin & 31));
|
|
||||||
}
|
|
||||||
// Thankfully, at present, any core code which calls _PM_portBitMask()
|
|
||||||
// currently has a 'core' variable, so we can safely do this...
|
|
||||||
#define _PM_portBitMask(pin) _PM_directBitMask(core, pin)
|
|
||||||
|
|
||||||
// Dedicated GPIO requires a complete replacement of the "blast" functions
|
|
||||||
// in order to get sufficient speed.
|
|
||||||
#define _PM_CUSTOM_BLAST // Disable blast_*() functions in core.c
|
|
||||||
IRAM_ATTR static void blast_byte(Protomatter_core *core, uint8_t *data) {
|
|
||||||
volatile uint32_t *gpio = &DEDIC_GPIO.gpio_out_idv.val;
|
|
||||||
|
|
||||||
// GPIO has already been initialized with RGB data + clock bits
|
|
||||||
// all LOW, so we don't need to initialize that state here.
|
|
||||||
|
|
||||||
for (uint32_t bits = core->chainBits / 8; bits--;) {
|
|
||||||
*gpio = _bit_toggle[*data++]; // Toggle in new data + toggle clock low
|
|
||||||
*gpio = 0b11000000000000; // Toggle clock high
|
|
||||||
*gpio = _bit_toggle[*data++];
|
|
||||||
*gpio = 0b11000000000000;
|
|
||||||
*gpio = _bit_toggle[*data++];
|
|
||||||
*gpio = 0b11000000000000;
|
|
||||||
*gpio = _bit_toggle[*data++];
|
|
||||||
*gpio = 0b11000000000000;
|
|
||||||
*gpio = _bit_toggle[*data++];
|
|
||||||
*gpio = 0b11000000000000;
|
|
||||||
*gpio = _bit_toggle[*data++];
|
|
||||||
*gpio = 0b11000000000000;
|
|
||||||
*gpio = _bit_toggle[*data++];
|
|
||||||
*gpio = 0b11000000000000;
|
|
||||||
*gpio = _bit_toggle[*data++];
|
|
||||||
*gpio = 0b11000000000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Want the pins left with RGB data and clock LOW on function exit
|
|
||||||
// (so it's easier to see on 'scope, and to prime it for the next call).
|
|
||||||
// This is implicit in the no-toggle case (due to how the PEW macro
|
|
||||||
// works), but toggle case requires explicitly clearing those bits.
|
|
||||||
// rgbAndClockMask is an 8-bit value when toggling, hence offset here.
|
|
||||||
*gpio = 0b10101010101010; // Clear RGB + clock bits
|
|
||||||
}
|
|
||||||
|
|
||||||
// If using custom "blast" function(s), all three must be declared.
|
|
||||||
// Unused ones can be empty, that's fine, just need to exist.
|
|
||||||
IRAM_ATTR static void blast_word(Protomatter_core *core, uint16_t *data) {}
|
|
||||||
IRAM_ATTR static void blast_long(Protomatter_core *core, uint32_t *data) {}
|
|
||||||
|
|
||||||
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
|
|
||||||
|
|
||||||
void _PM_timerInit(Protomatter_core *core) {
|
|
||||||
|
|
||||||
// On S2, initialize the Dedicated GPIO peripheral using the RGB pin list
|
|
||||||
// list from the core struct, plus the clock pin (7 pins total). Unsure if
|
|
||||||
// these structs & arrays need to be persistent. Declaring static just in
|
|
||||||
// case...could experiment with removing one by one.
|
|
||||||
static int pins[7];
|
|
||||||
for (uint8_t i = 0; i < 6; i++)
|
|
||||||
pins[i] = core->rgbPins[i];
|
|
||||||
pins[6] = core->clockPin;
|
|
||||||
static dedic_gpio_bundle_config_t config_in = {
|
|
||||||
.gpio_array = pins, // Array of GPIO numbers
|
|
||||||
.array_size = 7, // RGB pins + clock pin
|
|
||||||
.flags = {
|
|
||||||
.in_en = 0, // Disable input
|
|
||||||
.out_en = 1, // Enable output
|
|
||||||
.out_invert = 0, // Non-inverted
|
|
||||||
}};
|
|
||||||
static dedic_gpio_bundle_handle_t bundle;
|
|
||||||
(void)dedic_gpio_new_bundle(&config_in, &bundle);
|
|
||||||
dedic_gpio_bundle_write(bundle, config_in.array_size, 1);
|
|
||||||
DEDIC_GPIO.gpio_out_cpu.val = 0; // Use GPIO registers, not CPU instructions
|
|
||||||
|
|
||||||
_PM_esp32commonTimerInit(core); // In esp32-common.h
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return current count value (timer enabled or not).
|
|
||||||
// Timer must be previously initialized.
|
|
||||||
// This function is the same on all ESP32 parts EXCEPT S3.
|
|
||||||
IRAM_ATTR inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
|
|
||||||
return (uint32_t)timerRead((hw_timer_t *)core->timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
|
|
||||||
|
|
||||||
void _PM_timerInit(Protomatter_core *core) {
|
|
||||||
|
|
||||||
// TO DO: adapt this function for any CircuitPython-specific changes.
|
|
||||||
// If none are required, this function can be deleted and the version
|
|
||||||
// above can be moved before the ARDUIO/CIRCUITPY checks. If minimal
|
|
||||||
// changes, consider a single _PM_timerInit() implementation with
|
|
||||||
// ARDUINO/CIRCUITPY checks inside.
|
|
||||||
|
|
||||||
// On S2, initialize the Dedicated GPIO peripheral using the RGB pin list
|
|
||||||
// list from the core struct, plus the clock pin (7 pins total). Unsure if
|
|
||||||
// these structs & arrays need to be persistent. Declaring static just in
|
|
||||||
// case...could experiment with removing one by one.
|
|
||||||
static int pins[7];
|
|
||||||
for (uint8_t i = 0; i < 6; i++)
|
|
||||||
pins[i] = core->rgbPins[i];
|
|
||||||
pins[6] = core->clockPin;
|
|
||||||
static dedic_gpio_bundle_config_t config_in = {
|
|
||||||
.gpio_array = pins, // Array of GPIO numbers
|
|
||||||
.array_size = 7, // RGB pins + clock pin
|
|
||||||
.flags = {
|
|
||||||
.in_en = 0, // Disable input
|
|
||||||
.out_en = 1, // Enable output
|
|
||||||
.out_invert = 0, // Non-inverted
|
|
||||||
}};
|
|
||||||
static dedic_gpio_bundle_handle_t bundle;
|
|
||||||
(void)dedic_gpio_new_bundle(&config_in, &bundle);
|
|
||||||
dedic_gpio_bundle_write(bundle, config_in.array_size, 1);
|
|
||||||
DEDIC_GPIO.gpio_out_cpu.val = 0; // Use GPIO registers, not CPU instructions
|
|
||||||
|
|
||||||
_PM_esp32commonTimerInit(core); // In esp32-common.h
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // END CIRCUITPYTHON ------------------------------------------------
|
|
||||||
|
|
||||||
#endif // END ESP32S2
|
|
||||||
|
|
@ -1,285 +0,0 @@
|
||||||
/*!
|
|
||||||
* @file esp32-s3.h
|
|
||||||
*
|
|
||||||
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
|
|
||||||
* This file contains ESP32-S3-SPECIFIC CODE.
|
|
||||||
*
|
|
||||||
* Adafruit invests time and resources providing this open source code,
|
|
||||||
* please support Adafruit and open-source hardware by purchasing
|
|
||||||
* products from Adafruit!
|
|
||||||
*
|
|
||||||
* Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for
|
|
||||||
* Adafruit Industries, with contributions from the open source community.
|
|
||||||
*
|
|
||||||
* BSD license, all text here must be included in any redistribution.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
// NOTE: there is some intentional repetition in the macros and functions
|
|
||||||
// for some ESP32 variants. Previously they were all one file, but complex
|
|
||||||
// preprocessor directives were turning into spaghetti. THEREFORE, if making
|
|
||||||
// a change or bugfix in one variant-specific header, check the others to
|
|
||||||
// see if the same should be applied!
|
|
||||||
|
|
||||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
|
||||||
|
|
||||||
#define GPIO_DRIVE_STRENGTH GPIO_DRIVE_CAP_3
|
|
||||||
#define LCD_CLK_PRESCALE 9 // 8, 9, 10 allowed. Bit clock = 160 MHz / this.
|
|
||||||
|
|
||||||
#if defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
|
|
||||||
#include "components/esp_rom/include/esp_rom_sys.h"
|
|
||||||
#include "components/heap/include/esp_heap_caps.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Use DMA-capable RAM (not PSRAM) for framebuffer:
|
|
||||||
#define _PM_allocate(x) heap_caps_malloc(x, MALLOC_CAP_DMA | MALLOC_CAP_8BIT)
|
|
||||||
#define _PM_free(x) heap_caps_free(x)
|
|
||||||
|
|
||||||
#define _PM_portOutRegister(pin) \
|
|
||||||
(volatile uint32_t *)((pin < 32) ? &GPIO.out : &GPIO.out1.val)
|
|
||||||
#define _PM_portSetRegister(pin) \
|
|
||||||
(volatile uint32_t *)((pin < 32) ? &GPIO.out_w1ts : &GPIO.out1_w1ts.val)
|
|
||||||
#define _PM_portClearRegister(pin) \
|
|
||||||
(volatile uint32_t *)((pin < 32) ? &GPIO.out_w1tc : &GPIO.out1_w1tc.val)
|
|
||||||
|
|
||||||
// On ESP32-S3, use the LCD_CAM peripheral for fast parallel output.
|
|
||||||
// Thanks to ESP's pin MUXing, matrix data in RAM can ALWAYS be stored in
|
|
||||||
// byte format regardless how RGB+clock bits are distributed among pins.
|
|
||||||
#define _PM_bytesPerElement 1
|
|
||||||
#define _PM_byteOffset(pin) 0
|
|
||||||
#define _PM_wordOffset(pin) 0
|
|
||||||
|
|
||||||
// On ESP32-S3, use the LCD_CAM peripheral for fast parallel output.
|
|
||||||
// Thanks to ESP's pin MUXing, matrix data in RAM can ALWAYS be stored in
|
|
||||||
// byte format regardless how RGB+clock bits are distributed among pins.
|
|
||||||
#define _PM_bytesPerElement 1
|
|
||||||
#define _PM_byteOffset(pin) 0
|
|
||||||
#define _PM_wordOffset(pin) 0
|
|
||||||
|
|
||||||
// On most architectures, _PM_timerGetCount() is used to measure bitbang
|
|
||||||
// speed for one scanline, which is then used for bitplane 0 time, and each
|
|
||||||
// subsequent plane doubles that. Since ESP32-S3 uses DMA and we don't have
|
|
||||||
// an end-of-transfer interrupt, we make an informed approximation.
|
|
||||||
// dmaSetupTime (measured in blast_byte()) measures the number of timer
|
|
||||||
// cycles to set up and trigger the DMA transfer...
|
|
||||||
static uint32_t dmaSetupTime = 100;
|
|
||||||
// ...then, the version of _PM_timerGetCount() here uses that as a starting
|
|
||||||
// point, plus the known constant DMA xfer speed (160/LCD_CLK_PRESCALE MHz)
|
|
||||||
// and timer frequency (40 MHz), to return an estimate of the one-scanline
|
|
||||||
// transfer time, from which everything is extrapolated:
|
|
||||||
IRAM_ATTR inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
|
|
||||||
// Time estimate seems to come in a little high, so the -10 here is an
|
|
||||||
// empirically-derived fudge factor that may yield ever-so-slightly better
|
|
||||||
// refresh in some edge cases. If visual glitches are encountered, might
|
|
||||||
// need to dial back this number a bit or remove it.
|
|
||||||
return dmaSetupTime + core->chainBits * 40 * LCD_CLK_PRESCALE / 160 - 10;
|
|
||||||
}
|
|
||||||
// Note that dmaSetupTime can vary from line to line, potentially influenced
|
|
||||||
// by interrupts, nondeterministic DMA channel clearing times, etc., which is
|
|
||||||
// why we don't just use a constant value. Each scanline might show for a
|
|
||||||
// slightly different length of time, but duty cycle scales with this so it's
|
|
||||||
// perceptually consistent; don't see bright or dark rows.
|
|
||||||
|
|
||||||
#define _PM_minMinPeriod \
|
|
||||||
(200 + (uint32_t)core->chainBits * 40 * LCD_CLK_PRESCALE / 160)
|
|
||||||
|
|
||||||
#if (ESP_IDF_VERSION_MAJOR == 5)
|
|
||||||
#include <esp_private/periph_ctrl.h>
|
|
||||||
#else
|
|
||||||
#include <driver/periph_ctrl.h>
|
|
||||||
#endif
|
|
||||||
#include <driver/gpio.h>
|
|
||||||
#include <esp_private/gdma.h>
|
|
||||||
#include <esp_rom_gpio.h>
|
|
||||||
#include <hal/dma_types.h>
|
|
||||||
#include <hal/gpio_hal.h>
|
|
||||||
#include <hal/lcd_ll.h>
|
|
||||||
#include <soc/lcd_cam_reg.h>
|
|
||||||
#include <soc/lcd_cam_struct.h>
|
|
||||||
|
|
||||||
// Override the behavior of _PM_portBitMask macro so instead of returning
|
|
||||||
// a 32-bit mask for a pin within its corresponding GPIO register, it instead
|
|
||||||
// returns a 7-bit mask for the pin within the LCD_CAM data order *IF* it's
|
|
||||||
// one of the RGB bits or the clock bit...this requires comparing against pin
|
|
||||||
// numbers in the core struct.
|
|
||||||
static uint32_t _PM_directBitMask(Protomatter_core *core, int pin) {
|
|
||||||
if (pin == core->clockPin)
|
|
||||||
return 1 << 6;
|
|
||||||
for (uint8_t i = 0; i < 6; i++) {
|
|
||||||
if (pin == core->rgbPins[i])
|
|
||||||
return 1 << i;
|
|
||||||
}
|
|
||||||
// Else return the bit that would normally be used for regular GPIO
|
|
||||||
return (1U << (pin & 31));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Thankfully, at present, any core code which calls _PM_portBitMask()
|
|
||||||
// currently has a 'core' variable, so we can safely do this...
|
|
||||||
#define _PM_portBitMask(pin) _PM_directBitMask(core, pin)
|
|
||||||
|
|
||||||
static dma_descriptor_t desc;
|
|
||||||
static gdma_channel_handle_t dma_chan;
|
|
||||||
|
|
||||||
// If using custom "blast" function(s), all three must be declared.
|
|
||||||
// Unused ones can be empty, that's fine, just need to exist.
|
|
||||||
IRAM_ATTR static void blast_word(Protomatter_core *core, uint16_t *data) {}
|
|
||||||
IRAM_ATTR static void blast_long(Protomatter_core *core, uint32_t *data) {}
|
|
||||||
|
|
||||||
static void pinmux(int8_t pin, uint8_t signal) {
|
|
||||||
esp_rom_gpio_connect_out_signal(pin, signal, false, false);
|
|
||||||
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[pin], PIN_FUNC_GPIO);
|
|
||||||
gpio_set_drive_capability((gpio_num_t)pin, GPIO_DRIVE_STRENGTH);
|
|
||||||
}
|
|
||||||
|
|
||||||
// LCD_CAM requires a complete replacement of the "blast" functions in order
|
|
||||||
// to use the DMA-based peripheral.
|
|
||||||
#define _PM_CUSTOM_BLAST // Disable blast_*() functions in core.c
|
|
||||||
IRAM_ATTR static void blast_byte(Protomatter_core *core, uint8_t *data) {
|
|
||||||
// Reset LCD DOUT parameters each time (required).
|
|
||||||
// IN PRINCIPLE, cyclelen should be chainBits-1 (resulting in chainBits
|
|
||||||
// cycles). But due to the required dummy phases at start of transfer,
|
|
||||||
// extend by 1; set to chainBits, issue chainBits+1 cycles.
|
|
||||||
LCD_CAM.lcd_user.lcd_dout_cyclelen = core->chainBits;
|
|
||||||
LCD_CAM.lcd_user.lcd_dout = 1;
|
|
||||||
LCD_CAM.lcd_user.lcd_update = 1;
|
|
||||||
|
|
||||||
// Reset LCD TX FIFO each time, else we see old data. When doing this,
|
|
||||||
// it's REQUIRED in the setup code to enable at least one dummy pulse,
|
|
||||||
// else the PCLK & data are randomly misaligned by 1-2 clocks!
|
|
||||||
LCD_CAM.lcd_misc.lcd_afifo_reset = 1;
|
|
||||||
|
|
||||||
// Partially re-init descriptor each time (required)
|
|
||||||
desc.dw0.size = desc.dw0.length = core->chainBits;
|
|
||||||
desc.buffer = data;
|
|
||||||
gdma_start(dma_chan, (intptr_t)&desc);
|
|
||||||
esp_rom_delay_us(1); // Necessary before starting xfer
|
|
||||||
|
|
||||||
LCD_CAM.lcd_user.lcd_start = 1; // Begin LCD DMA xfer
|
|
||||||
|
|
||||||
// Timer was cleared to 0 before calling blast_byte(), so this
|
|
||||||
// is the state of the timer immediately after DMA started:
|
|
||||||
#if defined(ARDUINO)
|
|
||||||
dmaSetupTime = (uint32_t)timerRead((hw_timer_t *)core->timer);
|
|
||||||
#elif defined(CIRCUITPY)
|
|
||||||
uint64_t value;
|
|
||||||
#if (ESP_IDF_VERSION_MAJOR == 5)
|
|
||||||
gptimer_handle_t timer = (gptimer_handle_t)core->timer;
|
|
||||||
gptimer_get_raw_count(timer, &value);
|
|
||||||
#else
|
|
||||||
timer_index_t *timer = (timer_index_t *)core->timer;
|
|
||||||
timer_get_counter_value(timer->group, timer->idx, &value);
|
|
||||||
#endif
|
|
||||||
dmaSetupTime = (uint32_t)value;
|
|
||||||
#endif
|
|
||||||
// See notes near top of this file for what's done with this info.
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _PM_timerInit(Protomatter_core *core) {
|
|
||||||
// On S3, initialize the LCD_CAM peripheral and DMA.
|
|
||||||
|
|
||||||
// LCD_CAM isn't enabled by default -- MUST begin with this:
|
|
||||||
periph_module_enable(PERIPH_LCD_CAM_MODULE);
|
|
||||||
periph_module_reset(PERIPH_LCD_CAM_MODULE);
|
|
||||||
|
|
||||||
// Reset LCD bus
|
|
||||||
LCD_CAM.lcd_user.lcd_reset = 1;
|
|
||||||
esp_rom_delay_us(100);
|
|
||||||
|
|
||||||
// Configure LCD clock
|
|
||||||
LCD_CAM.lcd_clock.clk_en = 1; // Enable clock
|
|
||||||
LCD_CAM.lcd_clock.lcd_clk_sel = 3; // PLL160M source
|
|
||||||
LCD_CAM.lcd_clock.lcd_clkm_div_a = 1; // 1/1 fractional divide,
|
|
||||||
LCD_CAM.lcd_clock.lcd_clkm_div_b = 1; // plus prescale below yields...
|
|
||||||
#if LCD_CLK_PRESCALE == 8
|
|
||||||
LCD_CAM.lcd_clock.lcd_clkm_div_num = 7; // 1:8 prescale (20 MHz CLK)
|
|
||||||
#elif LCD_CLK_PRESCALE == 9
|
|
||||||
LCD_CAM.lcd_clock.lcd_clkm_div_num = 8; // 1:9 prescale (17.8 MHz CLK)
|
|
||||||
#else
|
|
||||||
LCD_CAM.lcd_clock.lcd_clkm_div_num = 9; // 1:10 prescale (16 MHz CLK)
|
|
||||||
#endif
|
|
||||||
LCD_CAM.lcd_clock.lcd_ck_out_edge = 0; // PCLK low in first half of cycle
|
|
||||||
LCD_CAM.lcd_clock.lcd_ck_idle_edge = 0; // PCLK low idle
|
|
||||||
LCD_CAM.lcd_clock.lcd_clk_equ_sysclk = 1; // PCLK = CLK (ignore CLKCNT_N)
|
|
||||||
|
|
||||||
// Configure frame format. Some of these could probably be skipped and
|
|
||||||
// use defaults, but being verbose for posterity...
|
|
||||||
LCD_CAM.lcd_ctrl.lcd_rgb_mode_en = 0; // i8080 mode (not RGB)
|
|
||||||
LCD_CAM.lcd_rgb_yuv.lcd_conv_bypass = 0; // Disable RGB/YUV converter
|
|
||||||
LCD_CAM.lcd_misc.lcd_next_frame_en = 0; // Do NOT auto-frame
|
|
||||||
LCD_CAM.lcd_data_dout_mode.val = 0; // No data delays
|
|
||||||
LCD_CAM.lcd_user.lcd_always_out_en = 0; // Only when requested
|
|
||||||
LCD_CAM.lcd_user.lcd_8bits_order = 0; // Do not swap bytes
|
|
||||||
LCD_CAM.lcd_user.lcd_bit_order = 0; // Do not reverse bit order
|
|
||||||
LCD_CAM.lcd_user.lcd_2byte_en = 0; // 8-bit data mode
|
|
||||||
// MUST enable at least one dummy phase at start of output, else clock and
|
|
||||||
// data are randomly misaligned by 1-2 cycles following required TX FIFO
|
|
||||||
// reset in blast_byte(). One phase MOSTLY works but sparkles a tiny bit
|
|
||||||
// (as in still very occasionally misaligned by 1 cycle). Two seems ideal;
|
|
||||||
// no sparkle. Since HUB75 is just a shift register, the extra clock ticks
|
|
||||||
// are harmless and the zero-data shifts off end of the chain.
|
|
||||||
LCD_CAM.lcd_user.lcd_dummy = 1; // Enable dummy phase(s) @ LCD start
|
|
||||||
LCD_CAM.lcd_user.lcd_dummy_cyclelen = 1; // 2 dummy phases
|
|
||||||
LCD_CAM.lcd_user.lcd_cmd = 0; // No command at LCD start
|
|
||||||
LCD_CAM.lcd_user.lcd_cmd_2_cycle_en = 0;
|
|
||||||
LCD_CAM.lcd_user.lcd_update = 1;
|
|
||||||
|
|
||||||
// Configure signal pins. IN THEORY this could be expanded to support
|
|
||||||
// 2 parallel chains, but the rest of the LCD & DMA setup is not currently
|
|
||||||
// written for that, so it's limited to a single chain for now.
|
|
||||||
const uint8_t signal[] = {LCD_DATA_OUT0_IDX, LCD_DATA_OUT1_IDX,
|
|
||||||
LCD_DATA_OUT2_IDX, LCD_DATA_OUT3_IDX,
|
|
||||||
LCD_DATA_OUT4_IDX, LCD_DATA_OUT5_IDX};
|
|
||||||
for (int i = 0; i < 6; i++)
|
|
||||||
pinmux(core->rgbPins[i], signal[i]);
|
|
||||||
pinmux(core->clockPin, LCD_PCLK_IDX);
|
|
||||||
gpio_set_drive_capability(core->latch.pin, GPIO_DRIVE_STRENGTH);
|
|
||||||
gpio_set_drive_capability(core->oe.pin, GPIO_DRIVE_STRENGTH);
|
|
||||||
for (uint8_t i = 0; i < core->numAddressLines; i++) {
|
|
||||||
gpio_set_drive_capability(core->addr[i].pin, GPIO_DRIVE_STRENGTH);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable LCD_CAM interrupts, clear any pending interrupt
|
|
||||||
LCD_CAM.lc_dma_int_ena.val &= ~LCD_LL_EVENT_TRANS_DONE;
|
|
||||||
LCD_CAM.lc_dma_int_clr.val = 0x03;
|
|
||||||
|
|
||||||
// Set up DMA TX descriptor
|
|
||||||
desc.dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
|
|
||||||
desc.dw0.suc_eof = 1;
|
|
||||||
desc.dw0.size = desc.dw0.length = core->chainBits;
|
|
||||||
desc.buffer = core->screenData;
|
|
||||||
desc.next = NULL;
|
|
||||||
|
|
||||||
// Alloc DMA channel & connect it to LCD periph
|
|
||||||
#if defined(CIRCUITPY)
|
|
||||||
if (dma_chan == NULL) {
|
|
||||||
#endif
|
|
||||||
gdma_channel_alloc_config_t dma_chan_config = {
|
|
||||||
.sibling_chan = NULL,
|
|
||||||
.direction = GDMA_CHANNEL_DIRECTION_TX,
|
|
||||||
.flags = {.reserve_sibling = 0}};
|
|
||||||
gdma_new_channel(&dma_chan_config, &dma_chan);
|
|
||||||
gdma_connect(dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0));
|
|
||||||
gdma_strategy_config_t strategy_config = {.owner_check = false,
|
|
||||||
.auto_update_desc = false};
|
|
||||||
gdma_apply_strategy(dma_chan, &strategy_config);
|
|
||||||
gdma_transfer_ability_t ability = {
|
|
||||||
.sram_trans_align = 0,
|
|
||||||
.psram_trans_align = 0,
|
|
||||||
};
|
|
||||||
gdma_set_transfer_ability(dma_chan, &ability);
|
|
||||||
#if defined(CIRCUITPY)
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
gdma_start(dma_chan, (intptr_t)&desc);
|
|
||||||
|
|
||||||
// Enable TRANS_DONE interrupt. Note that we do NOT require nor install
|
|
||||||
// an interrupt service routine, but DO need to enable the TRANS_DONE
|
|
||||||
// flag to make the LCD DMA transfer work.
|
|
||||||
LCD_CAM.lc_dma_int_ena.val |= LCD_LL_EVENT_TRANS_DONE & 0x03;
|
|
||||||
|
|
||||||
_PM_esp32commonTimerInit(core); // In esp32-common.h
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // END ESP32S3
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
/*!
|
|
||||||
* @file esp32.h
|
|
||||||
*
|
|
||||||
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
|
|
||||||
* This file contains ORIGINAL-ESP32-SPECIFIC CODE.
|
|
||||||
*
|
|
||||||
* Adafruit invests time and resources providing this open source code,
|
|
||||||
* please support Adafruit and open-source hardware by purchasing
|
|
||||||
* products from Adafruit!
|
|
||||||
*
|
|
||||||
* Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for
|
|
||||||
* Adafruit Industries, with contributions from the open source community.
|
|
||||||
*
|
|
||||||
* BSD license, all text here must be included in any redistribution.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
// NOTE: there is some intentional repetition in the macros and functions
|
|
||||||
// for some ESP32 variants. Previously they were all one file, but complex
|
|
||||||
// preprocessor directives were turning into spaghetti. THEREFORE, if making
|
|
||||||
// a change or bugfix in one variant-specific header, check the others to
|
|
||||||
// see if the same should be applied!
|
|
||||||
|
|
||||||
#if defined(CONFIG_IDF_TARGET_ESP32) // ORIGINAL ESP32, NOT S2/S3/etc.
|
|
||||||
|
|
||||||
#define _PM_portOutRegister(pin) \
|
|
||||||
(volatile uint32_t *)((pin < 32) ? &GPIO.out : &GPIO.out1.val)
|
|
||||||
#define _PM_portSetRegister(pin) \
|
|
||||||
(volatile uint32_t *)((pin < 32) ? &GPIO.out_w1ts : &GPIO.out1_w1ts.val)
|
|
||||||
#define _PM_portClearRegister(pin) \
|
|
||||||
(volatile uint32_t *)((pin < 32) ? &GPIO.out_w1tc : &GPIO.out1_w1tc.val)
|
|
||||||
|
|
||||||
#define _PM_portBitMask(pin) (1U << ((pin) & 31))
|
|
||||||
|
|
||||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
|
||||||
#define _PM_byteOffset(pin) ((pin & 31) / 8)
|
|
||||||
#define _PM_wordOffset(pin) ((pin & 31) / 16)
|
|
||||||
#else
|
|
||||||
#define _PM_byteOffset(pin) (3 - ((pin & 31) / 8))
|
|
||||||
#define _PM_wordOffset(pin) (1 - ((pin & 31) / 16))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// No special peripheral setup on OG ESP32, just use common timer init...
|
|
||||||
#define _PM_timerInit(core) _PM_esp32commonTimerInit(core);
|
|
||||||
|
|
||||||
// Return current count value (timer enabled or not).
|
|
||||||
// Timer must be previously initialized.
|
|
||||||
// This function is the same on all ESP32 parts EXCEPT S3.
|
|
||||||
IRAM_ATTR inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
|
|
||||||
return (uint32_t)timerRead((hw_timer_t *)core->timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
|
|
||||||
|
|
||||||
// ESP32 requires a custom PEW declaration (issues one set of RGB color bits
|
|
||||||
// followed by clock pulse). Turns out the bit set/clear registers are not
|
|
||||||
// actually atomic. If two writes are made in quick succession, the second
|
|
||||||
// has no effect. One option is NOPs, other is to write a 0 (no effect) to
|
|
||||||
// the opposing register (set vs clear) to synchronize the next write.
|
|
||||||
// S2, S3 replace the whole "blast" functions and don't use PEW. C3 can use
|
|
||||||
// the default PEW.
|
|
||||||
#if !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32S2)
|
|
||||||
#define PEW \
|
|
||||||
*set = *data++; /* Set RGB data high */ \
|
|
||||||
*clear_full = 0; /* ESP32 MUST sync before 2nd 'set' */ \
|
|
||||||
*set_full = clock; /* Set clock high */ \
|
|
||||||
*clear_full = rgbclock; /* Clear RGB data + clock */ \
|
|
||||||
///< Bitbang one set of RGB data bits to matrix
|
|
||||||
#endif // end !ESP32S3/S2
|
|
||||||
|
|
||||||
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
|
|
||||||
|
|
||||||
#define _PM_STRICT_32BIT_IO (1)
|
|
||||||
|
|
||||||
// ESP32 requires a custom PEW declaration (issues one set of RGB color bits
|
|
||||||
// followed by clock pulse). Turns out the bit set/clear registers are not
|
|
||||||
// actually atomic. If two writes are made in quick succession, the second
|
|
||||||
// has no effect. One option is NOPs, other is to write a 0 (no effect) to
|
|
||||||
// the opposing register (set vs clear) to synchronize the next write.
|
|
||||||
#define PEW \
|
|
||||||
*set = (*data++) << shift; /* Set RGB data high */ \
|
|
||||||
*clear_full = 0; /* ESP32 MUST sync before 2nd 'set' */ \
|
|
||||||
*set = clock; /* Set clock high */ \
|
|
||||||
*clear_full = rgbclock; /* Clear RGB data + clock */ \
|
|
||||||
///< Bitbang one set of RGB data bits to matrix
|
|
||||||
|
|
||||||
#endif // END CIRCUITPYTHON ------------------------------------------------
|
|
||||||
|
|
||||||
#endif // END ESP32
|
|
||||||
212
src/arch/nrf52.h
|
|
@ -1,212 +0,0 @@
|
||||||
/*!
|
|
||||||
* @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(Protomatter_core *core) {
|
|
||||||
static const struct {
|
|
||||||
NRF_TIMER_Type *tc; // -> Timer peripheral base address
|
|
||||||
IRQn_Type IRQn; // Interrupt number
|
|
||||||
} timer[] = {
|
|
||||||
#if defined(NRF_TIMER0)
|
|
||||||
{NRF_TIMER0, TIMER0_IRQn},
|
|
||||||
#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 != (NRF_TIMER_Type *)core->timer)) {
|
|
||||||
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(Protomatter_core *core, uint32_t period) {
|
|
||||||
volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)core->timer;
|
|
||||||
tc->TASKS_STOP = 1; // Stop timer
|
|
||||||
tc->TASKS_CLEAR = 1; // Reset to 0
|
|
||||||
tc->CC[0] = period;
|
|
||||||
tc->TASKS_START = 1; // Start timer
|
|
||||||
}
|
|
||||||
|
|
||||||
inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
|
|
||||||
volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)core->timer;
|
|
||||||
tc->TASKS_CAPTURE[1] = 1; // Capture timer to CC[1] register
|
|
||||||
return tc->CC[1]; // (don't clobber value in CC[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t _PM_timerStop(Protomatter_core *core) {
|
|
||||||
volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)core->timer;
|
|
||||||
tc->TASKS_STOP = 1; // Stop timer
|
|
||||||
__attribute__((unused)) uint32_t count = _PM_timerGetCount(core);
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define _PM_clockHoldHigh asm("nop; nop");
|
|
||||||
|
|
||||||
#define _PM_minMinPeriod 100
|
|
||||||
|
|
||||||
#endif // END NRF52_SERIES
|
|
||||||
|
|
@ -1,265 +0,0 @@
|
||||||
/*!
|
|
||||||
* @file rp2040.h
|
|
||||||
*
|
|
||||||
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
|
|
||||||
* This file contains RP2040 (Raspberry Pi Pico, etc.) SPECIFIC CODE.
|
|
||||||
*
|
|
||||||
* Adafruit invests time and resources providing this open source code,
|
|
||||||
* please support Adafruit and open-source hardware by purchasing
|
|
||||||
* products from Adafruit!
|
|
||||||
*
|
|
||||||
* Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for
|
|
||||||
* Adafruit Industries, with contributions from the open source community.
|
|
||||||
*
|
|
||||||
* BSD license, all text here must be included in any redistribution.
|
|
||||||
*
|
|
||||||
* RP2040 NOTES: This initial implementation does NOT use PIO. That's normal
|
|
||||||
* for Protomatter, which was written for simple GPIO + timer interrupt for
|
|
||||||
* broadest portability. While not entirely optimal, it's not pessimal
|
|
||||||
* either...no worse than any other platform where we're not taking
|
|
||||||
* advantage of device-specific DMA or peripherals. Would require changes to
|
|
||||||
* the 'blast' functions or possibly the whole _PM_row_handler() (both
|
|
||||||
* currently in core.c). CPU load is just a few percent for a 64x32
|
|
||||||
* matrix @ 6-bit depth, so I'm not losing sleep over this.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#if defined(ARDUINO_ARCH_RP2040) || defined(PICO_BOARD) || \
|
|
||||||
defined(__RP2040__) || defined(__RP2350__)
|
|
||||||
|
|
||||||
#include "../../hardware_pwm/include/hardware/pwm.h"
|
|
||||||
#include "hardware/irq.h"
|
|
||||||
#include "hardware/timer.h"
|
|
||||||
#include "pico/stdlib.h" // For sio_hw, etc.
|
|
||||||
|
|
||||||
// RP2040 only allows full 32-bit aligned writes to GPIO.
|
|
||||||
#define _PM_STRICT_32BIT_IO ///< Change core.c behavior for long accesses only
|
|
||||||
|
|
||||||
// Enable this to use PWM for bitplane timing, else a timer alarm is used.
|
|
||||||
// PWM has finer resolution, but alarm is adequate -- this is more about
|
|
||||||
// which peripheral we'd rather use, as both are finite resources.
|
|
||||||
#ifndef _PM_CLOCK_PWM
|
|
||||||
#define _PM_CLOCK_PWM (1)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if _PM_CLOCK_PWM // Use PWM for timing
|
|
||||||
static void _PM_PWM_ISR(void);
|
|
||||||
#else // Use timer alarm for timing
|
|
||||||
static void _PM_timerISR(void);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
|
|
||||||
|
|
||||||
// THIS CURRENTLY ONLY WORKS WITH THE PHILHOWER RP2040 CORE.
|
|
||||||
// mbed Arduino RP2040 core won't compile due to missing stdio.h.
|
|
||||||
// Also, much of this currently assumes GPXX pin numbers with no remapping.
|
|
||||||
|
|
||||||
// 'pin' here is GPXX # (might change if pin remapping gets added in core)
|
|
||||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
|
||||||
#define _PM_byteOffset(pin) ((pin & 31) / 8)
|
|
||||||
#define _PM_wordOffset(pin) ((pin & 31) / 16)
|
|
||||||
#else
|
|
||||||
#define _PM_byteOffset(pin) (3 - ((pin & 31) / 8))
|
|
||||||
#define _PM_wordOffset(pin) (1 - ((pin & 31) / 16))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if _PM_CLOCK_PWM // Use PWM for timing
|
|
||||||
|
|
||||||
// Arduino implementation is tied to a specific PWM slice & frequency
|
|
||||||
#define _PM_PWM_SLICE 0
|
|
||||||
#define _PM_PWM_DIV ((F_CPU + 20000000) / 40000000) // 125 MHz->3->~41.6 MHz
|
|
||||||
#define _PM_timerFreq (F_CPU / _PM_PWM_DIV)
|
|
||||||
#define _PM_TIMER_DEFAULT NULL
|
|
||||||
|
|
||||||
#else // Use alarm for timing
|
|
||||||
|
|
||||||
// Arduino implementation is tied to a specific timer alarm & frequency
|
|
||||||
#define _PM_ALARM_NUM 1
|
|
||||||
#define _PM_IRQ_HANDLER TIMER_IRQ_1
|
|
||||||
#define _PM_timerFreq 1000000
|
|
||||||
#define _PM_TIMER_DEFAULT NULL
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Initialize, but do not start, timer.
|
|
||||||
void _PM_timerInit(Protomatter_core *core) {
|
|
||||||
#if _PM_CLOCK_PWM
|
|
||||||
// Enable PWM wrap interrupt
|
|
||||||
pwm_clear_irq(_PM_PWM_SLICE);
|
|
||||||
pwm_set_irq_enabled(_PM_PWM_SLICE, true);
|
|
||||||
irq_set_exclusive_handler(PWM_IRQ_WRAP, _PM_PWM_ISR);
|
|
||||||
irq_set_enabled(PWM_IRQ_WRAP, true);
|
|
||||||
|
|
||||||
// Config but do not start PWM
|
|
||||||
pwm_config config = pwm_get_default_config();
|
|
||||||
pwm_config_set_clkdiv_int(&config, _PM_PWM_DIV);
|
|
||||||
pwm_init(_PM_PWM_SLICE, &config, true);
|
|
||||||
#else
|
|
||||||
timer_hw->alarm[_PM_ALARM_NUM] = timer_hw->timerawl; // Clear any timer
|
|
||||||
hw_set_bits(&timer_hw->inte, 1u << _PM_ALARM_NUM);
|
|
||||||
irq_set_exclusive_handler(_PM_IRQ_HANDLER, _PM_timerISR); // Set IRQ handler
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
|
|
||||||
|
|
||||||
#if !defined(F_CPU) // Not sure if CircuitPython build defines this
|
|
||||||
#ifdef __RP2040__
|
|
||||||
#define F_CPU 125000000 // Standard RP2040 clock speed
|
|
||||||
#endif
|
|
||||||
#ifdef __RP2350__
|
|
||||||
#define F_CPU 150000000 // Standard RP2350 clock speed
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// 'pin' here is GPXX #
|
|
||||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
|
||||||
#define _PM_byteOffset(pin) ((pin & 31) / 8)
|
|
||||||
#define _PM_wordOffset(pin) ((pin & 31) / 16)
|
|
||||||
#else
|
|
||||||
#define _PM_byteOffset(pin) (3 - ((pin & 31) / 8))
|
|
||||||
#define _PM_wordOffset(pin) (1 - ((pin & 31) / 16))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if _PM_CLOCK_PWM
|
|
||||||
|
|
||||||
int _PM_pwm_slice;
|
|
||||||
#define _PM_PWM_SLICE (_PM_pwm_slice & 0xff)
|
|
||||||
#define _PM_PWM_DIV 3 // ~41.6 MHz, similar to SAMD
|
|
||||||
#define _PM_timerFreq (F_CPU / _PM_PWM_DIV)
|
|
||||||
#define _PM_TIMER_DEFAULT NULL
|
|
||||||
|
|
||||||
#else // Use alarm for timing
|
|
||||||
|
|
||||||
// Currently tied to a specific timer alarm & frequency
|
|
||||||
#define _PM_ALARM_NUM 1
|
|
||||||
#define _PM_IRQ_HANDLER TIMER_IRQ_1
|
|
||||||
#define _PM_timerFreq 1000000
|
|
||||||
#define _PM_TIMER_DEFAULT NULL
|
|
||||||
|
|
||||||
#endif // end PWM/alarm
|
|
||||||
|
|
||||||
// Initialize, but do not start, timer.
|
|
||||||
void _PM_timerInit(Protomatter_core *core) {
|
|
||||||
#if _PM_CLOCK_PWM
|
|
||||||
_PM_pwm_slice = (int)core->timer & 0xff;
|
|
||||||
// Enable PWM wrap interrupt
|
|
||||||
pwm_clear_irq(_PM_PWM_SLICE);
|
|
||||||
pwm_set_irq_enabled(_PM_PWM_SLICE, true);
|
|
||||||
irq_set_exclusive_handler(PWM_IRQ_WRAP, _PM_PWM_ISR);
|
|
||||||
irq_set_enabled(PWM_IRQ_WRAP, true);
|
|
||||||
|
|
||||||
// Config but do not start PWM
|
|
||||||
pwm_config config = pwm_get_default_config();
|
|
||||||
pwm_config_set_clkdiv_int(&config, _PM_PWM_DIV);
|
|
||||||
pwm_init(_PM_PWM_SLICE, &config, true);
|
|
||||||
#else
|
|
||||||
timer_hw->alarm[_PM_ALARM_NUM] = timer_hw->timerawl; // Clear any timer
|
|
||||||
hw_set_bits(&timer_hw->inte, 1u << _PM_ALARM_NUM);
|
|
||||||
irq_set_exclusive_handler(_PM_IRQ_HANDLER, _PM_timerISR); // Set IRQ handler
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// 'pin' here is GPXX #
|
|
||||||
#define _PM_portBitMask(pin) (1UL << pin)
|
|
||||||
// Same for these -- using GPXX #
|
|
||||||
#define _PM_pinOutput(pin) \
|
|
||||||
{ \
|
|
||||||
gpio_init(pin); \
|
|
||||||
gpio_set_dir(pin, GPIO_OUT); \
|
|
||||||
}
|
|
||||||
#define _PM_pinLow(pin) gpio_clr_mask(1UL << pin)
|
|
||||||
#define _PM_pinHigh(pin) gpio_set_mask(1UL << pin)
|
|
||||||
|
|
||||||
#ifndef _PM_delayMicroseconds
|
|
||||||
#define _PM_delayMicroseconds(n) sleep_us(n)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // end CIRCUITPY
|
|
||||||
|
|
||||||
#define _PM_portOutRegister(pin) ((void *)&sio_hw->gpio_out)
|
|
||||||
#define _PM_portSetRegister(pin) ((volatile uint32_t *)&sio_hw->gpio_set)
|
|
||||||
#define _PM_portClearRegister(pin) ((volatile uint32_t *)&sio_hw->gpio_clr)
|
|
||||||
#define _PM_portToggleRegister(pin) ((volatile uint32_t *)&sio_hw->gpio_togl)
|
|
||||||
|
|
||||||
#if !_PM_CLOCK_PWM
|
|
||||||
// Unlike timers on other devices, on RP2040 you don't reset a counter to
|
|
||||||
// zero at the start of a cycle. To emulate that behavior (for determining
|
|
||||||
// elapsed times), the timer start time must be saved somewhere...
|
|
||||||
static volatile uint32_t _PM_timerSave;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Because it's tied to a specific timer right now, there can be only
|
|
||||||
// one instance of the Protomatter_core struct. The Arduino library
|
|
||||||
// sets up this pointer when calling begin().
|
|
||||||
void *_PM_protoPtr = NULL;
|
|
||||||
|
|
||||||
#if _PM_CLOCK_PWM // Use PWM for timing
|
|
||||||
static void _PM_PWM_ISR(void) {
|
|
||||||
pwm_clear_irq(_PM_PWM_SLICE); // Reset PWM wrap interrupt
|
|
||||||
_PM_row_handler(_PM_protoPtr); // In core.c
|
|
||||||
}
|
|
||||||
#else // Use timer alarm for timing
|
|
||||||
static void _PM_timerISR(void) {
|
|
||||||
hw_clear_bits(&timer_hw->intr, 1u << _PM_ALARM_NUM); // Clear alarm flag
|
|
||||||
_PM_row_handler(_PM_protoPtr); // In core.c
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Set timer period and enable timer.
|
|
||||||
inline void _PM_timerStart(Protomatter_core *core, uint32_t period) {
|
|
||||||
#if _PM_CLOCK_PWM
|
|
||||||
pwm_set_counter(_PM_PWM_SLICE, 0);
|
|
||||||
pwm_set_wrap(_PM_PWM_SLICE, period);
|
|
||||||
pwm_set_enabled(_PM_PWM_SLICE, true);
|
|
||||||
#else
|
|
||||||
irq_set_enabled(_PM_IRQ_HANDLER, true); // Enable alarm IRQ
|
|
||||||
_PM_timerSave = timer_hw->timerawl; // Time at start
|
|
||||||
timer_hw->alarm[_PM_ALARM_NUM] = _PM_timerSave + period; // Time at end
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return current count value (timer enabled or not).
|
|
||||||
// Timer must be previously initialized.
|
|
||||||
inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
|
|
||||||
#if _PM_CLOCK_PWM
|
|
||||||
return pwm_get_counter(_PM_PWM_SLICE);
|
|
||||||
#else
|
|
||||||
return timer_hw->timerawl - _PM_timerSave;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable timer and return current count value.
|
|
||||||
// Timer must be previously initialized.
|
|
||||||
uint32_t _PM_timerStop(Protomatter_core *core) {
|
|
||||||
#if _PM_CLOCK_PWM
|
|
||||||
pwm_set_enabled(_PM_PWM_SLICE, false);
|
|
||||||
#else
|
|
||||||
irq_set_enabled(_PM_IRQ_HANDLER, false); // Disable alarm IRQ
|
|
||||||
#endif
|
|
||||||
return _PM_timerGetCount(core);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if (F_CPU >= 250000000)
|
|
||||||
#define _PM_clockHoldLow asm("nop; nop; nop;");
|
|
||||||
#define _PM_clockHoldHigh asm("nop; nop; nop;");
|
|
||||||
#elif (F_CPU >= 175000000)
|
|
||||||
#define _PM_clockHoldLow asm("nop; nop; nop;");
|
|
||||||
#define _PM_clockHoldHigh asm("nop;");
|
|
||||||
#elif (F_CPU >= 125000000)
|
|
||||||
#define _PM_clockHoldLow asm("nop; nop; nop;");
|
|
||||||
#define _PM_clockHoldHigh asm("nop;");
|
|
||||||
#elif (F_CPU >= 100000000)
|
|
||||||
#define _PM_clockHoldLow asm("nop;");
|
|
||||||
#endif // No NOPs needed at lower speeds
|
|
||||||
|
|
||||||
#define _PM_chunkSize 8
|
|
||||||
#if _PM_CLOCK_PWM
|
|
||||||
#define _PM_minMinPeriod 100
|
|
||||||
#else
|
|
||||||
#define _PM_minMinPeriod 8
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // END RP2040
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
/*!
|
|
||||||
* @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
|
|
||||||
|
|
@ -1,150 +0,0 @@
|
||||||
/*!
|
|
||||||
* @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(Protomatter_core *core) {
|
|
||||||
static const struct {
|
|
||||||
Tc *tc; // -> Timer/counter peripheral base address
|
|
||||||
IRQn_Type IRQn; // Interrupt number
|
|
||||||
uint8_t GCM_ID; // GCLK selection ID
|
|
||||||
} timer[] = {
|
|
||||||
#if defined(TC0)
|
|
||||||
{TC0, TC0_IRQn, GCM_TCC0_TCC1},
|
|
||||||
#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 *)core->timer; // 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(Protomatter_core *core, uint32_t period) {
|
|
||||||
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
|
|
||||||
tc->COUNT16.COUNT.reg = 0;
|
|
||||||
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
|
|
||||||
;
|
|
||||||
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(Protomatter_core *core) {
|
|
||||||
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
|
|
||||||
tc->COUNT16.READREQ.reg = TC_READREQ_RCONT | TC_READREQ_ADDR(0x10);
|
|
||||||
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
|
|
||||||
;
|
|
||||||
return tc->COUNT16.COUNT.reg;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable timer and return current count value.
|
|
||||||
// Timer must be previously initialized.
|
|
||||||
inline uint32_t _PM_timerStop(Protomatter_core *core) {
|
|
||||||
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
|
|
||||||
uint32_t count = _PM_timerGetCount(core);
|
|
||||||
tc->COUNT16.CTRLA.bit.ENABLE = 0;
|
|
||||||
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
|
|
||||||
;
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // END _SAMD21_ || SAMD21
|
|
||||||
|
|
@ -1,428 +0,0 @@
|
||||||
/*!
|
|
||||||
* @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)
|
|
||||||
|
|
||||||
// Enable high output driver strength on one pin. Arduino does this by
|
|
||||||
// default on pinMode(OUTPUT), but CircuitPython requires the motions...
|
|
||||||
static void _hi_drive(uint8_t pin) {
|
|
||||||
// For Arduino testing only:
|
|
||||||
// pin = g_APinDescription[pin].ulPort * 32 + g_APinDescription[pin].ulPin;
|
|
||||||
|
|
||||||
// Input, pull-up and peripheral MUX are disabled as we're only using
|
|
||||||
// vanilla PORT writes on Protomatter GPIO.
|
|
||||||
PORT->Group[pin / 32].WRCONFIG.reg =
|
|
||||||
(pin & 16)
|
|
||||||
? PORT_WRCONFIG_WRPINCFG | PORT_WRCONFIG_DRVSTR |
|
|
||||||
PORT_WRCONFIG_HWSEL | (1 << (pin & 15))
|
|
||||||
: PORT_WRCONFIG_WRPINCFG | PORT_WRCONFIG_DRVSTR | (1 << (pin & 15));
|
|
||||||
}
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
// Other port register lookups go here
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// CODE COMMON TO ALL ENVIRONMENTS -----------------------------------------
|
|
||||||
|
|
||||||
// Initialize, but do not start, timer
|
|
||||||
void _PM_timerInit(Protomatter_core *core) {
|
|
||||||
static const struct {
|
|
||||||
Tc *tc; // -> Timer/counter peripheral base address
|
|
||||||
IRQn_Type IRQn; // Interrupt number
|
|
||||||
uint8_t GCLK_ID; // Peripheral channel # for clock source
|
|
||||||
} timer[] = {
|
|
||||||
#if defined(TC0)
|
|
||||||
{TC0, TC0_IRQn, TC0_GCLK_ID},
|
|
||||||
#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 *)core->timer; // 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
|
|
||||||
|
|
||||||
#if defined(CIRCUITPY) // See notes earlier; Arduino doesn't need this.
|
|
||||||
// Enable high drive strength on all Protomatter pins. TBH this is kind
|
|
||||||
// of a jerky place to do this (it's not actually related to the timer
|
|
||||||
// peripheral) but Protomatter doesn't really have a spot for it.
|
|
||||||
uint8_t i;
|
|
||||||
for (i = 0; i < core->parallel * 6; i++)
|
|
||||||
_hi_drive(core->rgbPins[i]);
|
|
||||||
for (i = 0; i < core->numAddressLines; i++)
|
|
||||||
_hi_drive(core->addr[i].pin);
|
|
||||||
_hi_drive(core->clockPin);
|
|
||||||
_hi_drive(core->latch.pin);
|
|
||||||
_hi_drive(core->oe.pin);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set timer period, initialize count value to zero, enable timer.
|
|
||||||
// Timer must be initialized to 16-bit mode using the init function
|
|
||||||
// above, but must be inactive before calling this.
|
|
||||||
inline void _PM_timerStart(Protomatter_core *core, uint32_t period) {
|
|
||||||
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
|
|
||||||
tc->COUNT16.COUNT.reg = 0;
|
|
||||||
while (tc->COUNT16.SYNCBUSY.bit.COUNT)
|
|
||||||
;
|
|
||||||
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(Protomatter_core *core) {
|
|
||||||
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
|
|
||||||
tc->COUNT16.CTRLBSET.bit.CMD = 0x4; // Sync COUNT
|
|
||||||
while (tc->COUNT16.CTRLBSET.bit.CMD)
|
|
||||||
; // Wait for command
|
|
||||||
return tc->COUNT16.COUNT.reg;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable timer and return current count value.
|
|
||||||
// Timer must be previously initialized.
|
|
||||||
uint32_t _PM_timerStop(Protomatter_core *core) {
|
|
||||||
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
|
|
||||||
uint32_t count = _PM_timerGetCount(core);
|
|
||||||
tc->COUNT16.CTRLA.bit.ENABLE = 0;
|
|
||||||
while (tc->COUNT16.SYNCBUSY.bit.STATUS)
|
|
||||||
;
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SAMD51 takes a WEIRD TURN here, in an attempt to make the HUB75 clock
|
|
||||||
// waveform slightly adjustable. Old vs new matrices seem to have different
|
|
||||||
// preferences, and this tries to address that. If this works well then the
|
|
||||||
// approach might be applied to other architectures (which are all fixed
|
|
||||||
// duty cycle right now). THE CHALLENGE is that Protomatter works in a bit-
|
|
||||||
// bangingly way (this is on purpose and by design, avoiding peripherals
|
|
||||||
// that might work only on certain pins, for better compatibility with
|
|
||||||
// existing shields and wings from the AVR era), we're aiming for nearly a
|
|
||||||
// 20 MHz signal, and the SAMD51 cycle clock is ostensibly 120 MHz. With
|
|
||||||
// just a few cycles to toggle the data and clock lines, that doesn't even
|
|
||||||
// leave enough time for a counter loop.
|
|
||||||
|
|
||||||
#define _PM_CUSTOM_BLAST ///< Disable blast_*() functions in core.c
|
|
||||||
|
|
||||||
#define _PM_chunkSize 8 ///< Data-stuffing loop is unrolled to this size
|
|
||||||
|
|
||||||
extern uint8_t _PM_duty; // In core.c
|
|
||||||
|
|
||||||
// The approach is to use a small list of pointers, with a clock-toggling
|
|
||||||
// value written to each one in succession. Most of the pointers are aimed
|
|
||||||
// on a nonsense "bit bucket" variable, effectively becoming NOPs, and just
|
|
||||||
// one is set to the PORT toggle register, raising the clock. A couple of
|
|
||||||
// actual traditional NOPs are also present because concurrent PORT writes
|
|
||||||
// on SAMD51 incur a 1-cycle delay, so the NOPs keep the clock frequency
|
|
||||||
// constant (tradeoff is that the clock is now 7 rather than 6 cycles --
|
|
||||||
// ~17.1 MHz rather than 20 with F_CPU at 120 MHz). The NOPs could be
|
|
||||||
// removed and duty range increased by one, but the tradeoff then is
|
|
||||||
// inconsistent timing at different duty settings. That 1-cycle delay is
|
|
||||||
// also why this uses a list of pointers with a common value, rather than
|
|
||||||
// a common pointer (the PORT reg) with a list of values -- because those
|
|
||||||
// writes would all take 2 cycles, way too slow. A counter loop would also
|
|
||||||
// take 2 cycles/count, because of the branch.
|
|
||||||
|
|
||||||
#if F_CPU >= 200000000 // 200 MHz; 10 cycles/bit; 20 MHz, 6 duty settings
|
|
||||||
|
|
||||||
#define _PM_maxDuty 5 ///< Allow duty settings 0-5
|
|
||||||
#define _PM_defaultDuty 2 ///< ~60%
|
|
||||||
|
|
||||||
#define PEW \
|
|
||||||
asm("nop"); \
|
|
||||||
*toggle = *data++; \
|
|
||||||
asm("nop"); \
|
|
||||||
*ptr0 = clock; \
|
|
||||||
*ptr1 = clock; \
|
|
||||||
*ptr2 = clock; \
|
|
||||||
*ptr3 = clock; \
|
|
||||||
*ptr4 = clock; \
|
|
||||||
*ptr5 = clock;
|
|
||||||
|
|
||||||
#elif F_CPU >= 180000000 // 180 MHz; 9 cycles/bit; 20 MHz, 5 duty settings
|
|
||||||
|
|
||||||
#define _PM_maxDuty 4 ///< Allow duty settings 0-4
|
|
||||||
#define _PM_defaultDuty 1 ///< ~50%
|
|
||||||
|
|
||||||
#define PEW \
|
|
||||||
asm("nop"); \
|
|
||||||
*toggle = *data++; \
|
|
||||||
asm("nop"); \
|
|
||||||
*ptr0 = clock; \
|
|
||||||
*ptr1 = clock; \
|
|
||||||
*ptr2 = clock; \
|
|
||||||
*ptr3 = clock; \
|
|
||||||
*ptr4 = clock;
|
|
||||||
|
|
||||||
#elif F_CPU >= 150000000 // 150 MHz; 8 cycles/bit; 18.75 MHz, 4 duty settings
|
|
||||||
|
|
||||||
#define _PM_maxDuty 3 ///< Allow duty settings 0-3
|
|
||||||
#define _PM_defaultDuty 1 ///< ~55%
|
|
||||||
|
|
||||||
#define PEW \
|
|
||||||
asm("nop"); \
|
|
||||||
*toggle = *data++; \
|
|
||||||
asm("nop"); \
|
|
||||||
*ptr0 = clock; \
|
|
||||||
*ptr1 = clock; \
|
|
||||||
*ptr2 = clock; \
|
|
||||||
*ptr3 = clock;
|
|
||||||
|
|
||||||
#else // 120 MHz; 7 cycles/bit; 17.1 MHz, 3 duty settings
|
|
||||||
|
|
||||||
#define _PM_maxDuty 2 ///< Allow duty settings 0-2
|
|
||||||
#define _PM_defaultDuty 0 ///< ~50%
|
|
||||||
|
|
||||||
#define PEW \
|
|
||||||
asm("nop"); \
|
|
||||||
*toggle = *data++; \
|
|
||||||
asm("nop"); \
|
|
||||||
*ptr0 = clock; \
|
|
||||||
*ptr1 = clock; \
|
|
||||||
*ptr2 = clock;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static void blast_byte(Protomatter_core *core, uint8_t *data) {
|
|
||||||
// If here, it was established in begin() that the RGB data bits and
|
|
||||||
// clock are all within the same byte of a PORT register, else we'd be
|
|
||||||
// in the word- or long-blasting functions now. So we just need an
|
|
||||||
// 8-bit pointer to the PORT:
|
|
||||||
volatile uint8_t *toggle =
|
|
||||||
(volatile uint8_t *)core->toggleReg + core->portOffset;
|
|
||||||
uint8_t bucket, clock = core->clockMask;
|
|
||||||
// Pointer list must be distinct vars, not an array, else slow.
|
|
||||||
volatile uint8_t *ptr0 =
|
|
||||||
(_PM_duty == _PM_maxDuty) ? toggle : (volatile uint8_t *)&bucket;
|
|
||||||
volatile uint8_t *ptr1 =
|
|
||||||
(_PM_duty == (_PM_maxDuty - 1)) ? toggle : (volatile uint8_t *)&bucket;
|
|
||||||
volatile uint8_t *ptr2 =
|
|
||||||
(_PM_duty == (_PM_maxDuty - 2)) ? toggle : (volatile uint8_t *)&bucket;
|
|
||||||
#if _PM_maxDuty >= 3
|
|
||||||
volatile uint8_t *ptr3 =
|
|
||||||
(_PM_duty == (_PM_maxDuty - 3)) ? toggle : (volatile uint8_t *)&bucket;
|
|
||||||
#endif
|
|
||||||
#if _PM_maxDuty >= 4
|
|
||||||
volatile uint8_t *ptr4 =
|
|
||||||
(_PM_duty == (_PM_maxDuty - 4)) ? toggle : (volatile uint8_t *)&bucket;
|
|
||||||
#endif
|
|
||||||
#if _PM_maxDuty >= 5
|
|
||||||
volatile uint8_t *ptr5 =
|
|
||||||
(_PM_duty == (_PM_maxDuty - 5)) ? toggle : (volatile uint8_t *)&bucket;
|
|
||||||
#endif
|
|
||||||
uint16_t chunks = core->chainBits / 8;
|
|
||||||
|
|
||||||
// PORT has already been initialized with RGB data + clock bits
|
|
||||||
// all LOW, so we don't need to initialize that state here.
|
|
||||||
|
|
||||||
do {
|
|
||||||
PEW PEW PEW PEW PEW PEW PEW PEW
|
|
||||||
} while (--chunks);
|
|
||||||
|
|
||||||
// Want the PORT left with RGB data and clock LOW on function exit
|
|
||||||
// (so it's easier to see on 'scope, and to prime it for the next call).
|
|
||||||
// This is implicit in the no-toggle case (due to how the PEW macro
|
|
||||||
// works), but toggle case requires explicitly clearing those bits.
|
|
||||||
// rgbAndClockMask is an 8-bit value when toggling, hence offset here.
|
|
||||||
*((volatile uint8_t *)core->clearReg + core->portOffset) =
|
|
||||||
core->rgbAndClockMask;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a copypasta of blast_byte() with types changed to uint16_t.
|
|
||||||
static void blast_word(Protomatter_core *core, uint16_t *data) {
|
|
||||||
volatile uint16_t *toggle = (uint16_t *)core->toggleReg + core->portOffset;
|
|
||||||
uint16_t bucket, clock = core->clockMask;
|
|
||||||
volatile uint16_t *ptr0 =
|
|
||||||
(_PM_duty == _PM_maxDuty) ? toggle : (volatile uint16_t *)&bucket;
|
|
||||||
volatile uint16_t *ptr1 =
|
|
||||||
(_PM_duty == (_PM_maxDuty - 1)) ? toggle : (volatile uint16_t *)&bucket;
|
|
||||||
volatile uint16_t *ptr2 =
|
|
||||||
(_PM_duty == (_PM_maxDuty - 2)) ? toggle : (volatile uint16_t *)&bucket;
|
|
||||||
#if _PM_maxDuty >= 3
|
|
||||||
volatile uint16_t *ptr3 =
|
|
||||||
(_PM_duty == (_PM_maxDuty - 3)) ? toggle : (volatile uint16_t *)&bucket;
|
|
||||||
#endif
|
|
||||||
#if _PM_maxDuty >= 4
|
|
||||||
volatile uint16_t *ptr4 =
|
|
||||||
(_PM_duty == (_PM_maxDuty - 4)) ? toggle : (volatile uint16_t *)&bucket;
|
|
||||||
#endif
|
|
||||||
#if _PM_maxDuty >= 5
|
|
||||||
volatile uint16_t *ptr5 =
|
|
||||||
(_PM_duty == (_PM_maxDuty - 5)) ? toggle : (volatile uint16_t *)&bucket;
|
|
||||||
#endif
|
|
||||||
uint16_t chunks = core->chainBits / 8;
|
|
||||||
do {
|
|
||||||
PEW PEW PEW PEW PEW PEW PEW PEW
|
|
||||||
} while (--chunks);
|
|
||||||
*((volatile uint16_t *)core->clearReg + core->portOffset) =
|
|
||||||
core->rgbAndClockMask;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a copypasta of blast_byte() with types changed to uint32_t.
|
|
||||||
static void blast_long(Protomatter_core *core, uint32_t *data) {
|
|
||||||
volatile uint32_t *toggle = (uint32_t *)core->toggleReg;
|
|
||||||
uint32_t bucket, clock = core->clockMask;
|
|
||||||
volatile uint32_t *ptr0 =
|
|
||||||
(_PM_duty == _PM_maxDuty) ? toggle : (volatile uint32_t *)&bucket;
|
|
||||||
volatile uint32_t *ptr1 =
|
|
||||||
(_PM_duty == (_PM_maxDuty - 1)) ? toggle : (volatile uint32_t *)&bucket;
|
|
||||||
volatile uint32_t *ptr2 =
|
|
||||||
(_PM_duty == (_PM_maxDuty - 2)) ? toggle : (volatile uint32_t *)&bucket;
|
|
||||||
#if _PM_maxDuty >= 3
|
|
||||||
volatile uint32_t *ptr3 =
|
|
||||||
(_PM_duty == (_PM_maxDuty - 3)) ? toggle : (volatile uint32_t *)&bucket;
|
|
||||||
#endif
|
|
||||||
#if _PM_maxDuty >= 4
|
|
||||||
volatile uint32_t *ptr4 =
|
|
||||||
(_PM_duty == (_PM_maxDuty - 4)) ? toggle : (volatile uint32_t *)&bucket;
|
|
||||||
#endif
|
|
||||||
#if _PM_maxDuty >= 5
|
|
||||||
volatile uint32_t *ptr5 =
|
|
||||||
(_PM_duty == (_PM_maxDuty - 5)) ? toggle : (volatile uint32_t *)&bucket;
|
|
||||||
#endif
|
|
||||||
uint16_t chunks = core->chainBits / 8;
|
|
||||||
do {
|
|
||||||
PEW PEW PEW PEW PEW PEW PEW PEW
|
|
||||||
} while (--chunks);
|
|
||||||
*((volatile uint32_t *)core->clearReg + core->portOffset) =
|
|
||||||
core->rgbAndClockMask;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define _PM_minMinPeriod 160
|
|
||||||
|
|
||||||
#endif // END __SAMD51__ || SAM_D5X_E5X
|
|
||||||
146
src/arch/stm32.h
|
|
@ -1,146 +0,0 @@
|
||||||
/*!
|
|
||||||
* @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(Protomatter_core *core) {
|
|
||||||
TIM_TypeDef *tim_instance = (TIM_TypeDef *)core->timer;
|
|
||||||
stm_peripherals_timer_reserve(tim_instance);
|
|
||||||
// Set IRQs at max priority and start clock
|
|
||||||
stm_peripherals_timer_preinit(tim_instance, 0, _PM_IRQ_HANDLER);
|
|
||||||
|
|
||||||
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(Protomatter_core *core, uint32_t period) {
|
|
||||||
TIM_TypeDef *tim = core->timer;
|
|
||||||
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(Protomatter_core *core) {
|
|
||||||
TIM_TypeDef *tim = core->timer;
|
|
||||||
return tim->CNT;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t _PM_timerStop(Protomatter_core *core) {
|
|
||||||
TIM_TypeDef *tim = core->timer;
|
|
||||||
HAL_NVIC_DisableIRQ(stm_peripherals_timer_get_irqnum(tim));
|
|
||||||
tim->CR1 &= ~TIM_CR1_CEN;
|
|
||||||
tim->DIER &= ~TIM_DIER_UIE;
|
|
||||||
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
|
|
||||||
|
|
@ -1,172 +0,0 @@
|
||||||
/*!
|
|
||||||
* @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(Protomatter_core *core) {
|
|
||||||
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)core->timer;
|
|
||||||
CCM_CCGR1 |= CCM_CCGR1_PIT(CCM_CCGR_ON); // Enable clock signal to PIT
|
|
||||||
PIT_MCR = 1; // Enable PIT
|
|
||||||
timer->TCTRL = 0; // Disable timer and interrupt
|
|
||||||
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(Protomatter_core *core, uint32_t period) {
|
|
||||||
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)core->timer;
|
|
||||||
timer->TCTRL = 0; // Disable timer and interrupt
|
|
||||||
timer->LDVAL = period; // Set load value
|
|
||||||
// timer->CVAL = period; // And current value (just in case?)
|
|
||||||
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(Protomatter_core *core) {
|
|
||||||
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)core->timer;
|
|
||||||
return (timer->LDVAL - timer->CVAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable timer and return current count value.
|
|
||||||
// Timer must be previously initialized.
|
|
||||||
uint32_t _PM_timerStop(Protomatter_core *core) {
|
|
||||||
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)core->timer;
|
|
||||||
timer->TCTRL = 0; // Disable timer and interrupt
|
|
||||||
return _PM_timerGetCount(core);
|
|
||||||
}
|
|
||||||
|
|
||||||
#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)
|
|
||||||
1411
src/core.c
286
src/core.h
|
|
@ -1,286 +0,0 @@
|
||||||
/*!
|
|
||||||
* @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);
|
|
||||||
|
|
||||||
// *********************************************************************
|
|
||||||
// NOTE: AS OF 1.3.0, TIMER-RELATED FUNCTIONS REQUIRE A Protomatter_core
|
|
||||||
// STRUCT POINTER, RATHER THAN A void* TIMER-RELATED POINTER.
|
|
||||||
// *********************************************************************
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief Returns current value of frame counter and resets its value to
|
|
||||||
zero. Two calls to this, timed one second apart (or use math with
|
|
||||||
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 core Pointer to Protomatter core structure, from which timer
|
|
||||||
details can be derived.
|
|
||||||
@param period Timer 'top' / rollover value.
|
|
||||||
*/
|
|
||||||
extern void _PM_timerStart(Protomatter_core *core, uint32_t period);
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief Stop timer/counter peripheral.
|
|
||||||
@param core Pointer to Protomatter core structure, from which timer
|
|
||||||
details can be derived.
|
|
||||||
@return Counter value when timer was stopped.
|
|
||||||
*/
|
|
||||||
extern uint32_t _PM_timerStop(Protomatter_core *core);
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief Query a timer/counter peripheral's current count.
|
|
||||||
@param core Pointer to Protomatter core structure, from which timer
|
|
||||||
details can be derived.
|
|
||||||
@return Counter value.
|
|
||||||
*/
|
|
||||||
extern uint32_t _PM_timerGetCount(Protomatter_core *core);
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@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);
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief Adjust duty cycle of HUB75 clock signal. This is not supported on
|
|
||||||
all architectures.
|
|
||||||
@param d Duty setting, 0 minimum. Increasing values generate higher clock
|
|
||||||
duty cycles at the same frequency. Arbitrary granular units, max
|
|
||||||
varies by architecture and CPU speed, if supported at all.
|
|
||||||
e.g. SAMD51 @ 120 MHz supports 0 (~50% duty) through 2 (~75%).
|
|
||||||
*/
|
|
||||||
extern void _PM_setDuty(uint8_t d);
|
|
||||||
|
|
||||||
#if defined(ARDUINO) || defined(CIRCUITPY)
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@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
|
|
||||||