Compare commits
27 commits
master
...
circuitpyt
| Author | SHA1 | Date | |
|---|---|---|---|
| 3a817d6057 | |||
|
|
4cbf99aa54 | ||
|
|
a51676db5e | ||
|
|
40cb6a9cac | ||
|
|
87a95379b5 | ||
|
|
8968259c14 | ||
|
|
fc180e4689 | ||
|
|
9f71088d2c | ||
|
|
973de07bfc | ||
| 1b8f461f64 | |||
| 241bf2516c | |||
| 41a92d7731 | |||
|
|
fac7eea645 | ||
|
|
3bc6f23541 | ||
| c411714cbd | |||
| c3a3e35731 | |||
| 7f448357d0 | |||
| 969672dff1 | |||
|
|
5a1151db28 | ||
|
|
e1659f270c | ||
|
|
20c3b67bc3 | ||
|
|
103ea89935 | ||
|
|
51f39d9594 | ||
|
|
0958b759a5 | ||
|
|
97c2122ba7 | ||
|
|
250675d2fc | ||
|
|
831a253898 |
11 changed files with 1986 additions and 1145 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Our handy .gitignore for automation ease
|
||||||
|
Doxyfile*
|
||||||
|
doxygen_sqlite3.db
|
||||||
|
html
|
||||||
41
.travis.yml
Normal file
41
.travis.yml
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
language: c
|
||||||
|
sudo: false
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- ~/arduino_ide
|
||||||
|
- ~/.arduino15/packages/
|
||||||
|
git:
|
||||||
|
depth: false
|
||||||
|
quiet: true
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
sources:
|
||||||
|
- llvm-toolchain-trusty-5.0
|
||||||
|
- key_url: 'http://apt.llvm.org/llvm-snapshot.gpg.key'
|
||||||
|
packages:
|
||||||
|
- python3-pip
|
||||||
|
- python3-wheel
|
||||||
|
- clang-format-5.0
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
# - ARDUINO_IDE_VERSION="1.8.10"
|
||||||
|
- PRETTYNAME="Adafruit Protomatter"
|
||||||
|
# Optional, will default to "$TRAVIS_BUILD_DIR/Doxyfile"
|
||||||
|
# - DOXYFILE: $TRAVIS_BUILD_DIR/Doxyfile
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- source <(curl -SLs https://raw.githubusercontent.com/adafruit/travis-ci-arduino/master/install.sh)
|
||||||
|
- curl -SLs https://raw.githubusercontent.com/adafruit/travis-ci-arduino/master/run-clang-format.py > run-clang-format.py
|
||||||
|
|
||||||
|
install:
|
||||||
|
- arduino --install-library "Adafruit GFX Library"
|
||||||
|
|
||||||
|
script:
|
||||||
|
- python run-clang-format.py -r .
|
||||||
|
- build_m4_platforms
|
||||||
|
- build_nrf5x_platforms
|
||||||
|
|
||||||
|
# Generate and deploy documentation
|
||||||
|
after_success:
|
||||||
|
- source <(curl -SLs https://raw.githubusercontent.com/adafruit/travis-ci-arduino/master/library_check.sh)
|
||||||
|
- source <(curl -SLs https://raw.githubusercontent.com/adafruit/travis-ci-arduino/master/doxy_gen_and_deploy.sh)
|
||||||
|
|
@ -1,3 +1,38 @@
|
||||||
|
/*!
|
||||||
|
* @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
|
// Arduino-specific wrapper for the Protomatter C library (provides
|
||||||
// constructor and so forth, builds on Adafruit_GFX). There should
|
// constructor and so forth, builds on Adafruit_GFX). There should
|
||||||
// not be any device-specific #ifdefs here. See notes in core.c and
|
// not be any device-specific #ifdefs here. See notes in core.c and
|
||||||
|
|
@ -5,7 +40,7 @@
|
||||||
|
|
||||||
#include "Adafruit_Protomatter.h" // Also includes core.h & Adafruit_GFX.h
|
#include "Adafruit_Protomatter.h" // Also includes core.h & Adafruit_GFX.h
|
||||||
|
|
||||||
extern Protomatter_core *_PM_protoPtr; // In core.c (via arch.h)
|
extern Protomatter_core *_PM_protoPtr; ///< In core.c (via arch.h)
|
||||||
|
|
||||||
// Overall matrix refresh rate (frames/second) is a function of matrix width
|
// Overall matrix refresh rate (frames/second) is a function of matrix width
|
||||||
// and chain length, number of address lines, number of bit planes, CPU speed
|
// and chain length, number of address lines, number of bit planes, CPU speed
|
||||||
|
|
@ -21,31 +56,31 @@ extern Protomatter_core *_PM_protoPtr; // In core.c (via arch.h)
|
||||||
// refresh slower than this, and in many cases will...just need to set an
|
// refresh slower than this, and in many cases will...just need to set an
|
||||||
// upper limit to avoid excessive CPU load). An incredibly long comment block
|
// upper limit to avoid excessive CPU load). An incredibly long comment block
|
||||||
// for a single constant, thank you for coming to my TED talk!
|
// for a single constant, thank you for coming to my TED talk!
|
||||||
#define _PM_MAX_REFRESH_HZ 250
|
#define _PM_MAX_REFRESH_HZ 250 ///< Upper limit (ish) to matrix refresh rate
|
||||||
|
|
||||||
// Time (in milliseconds) to pause following any change in address lines
|
// Time (in milliseconds) to pause following any change in address lines
|
||||||
// (individually or collectively). Some matrices respond slowly there...
|
// (individually or collectively). Some matrices respond slowly there...
|
||||||
// must pause on change for matrix to catch up. Defined here (rather than
|
// must pause on change for matrix to catch up. Defined here (rather than
|
||||||
// arch.h) because it's not architecture-specific.
|
// arch.h) because it's not architecture-specific.
|
||||||
#define _PM_ROW_DELAY 8
|
#define _PM_ROW_DELAY 8 ///< Delay time between row address line changes (ms)
|
||||||
|
|
||||||
|
Adafruit_Protomatter::Adafruit_Protomatter(uint16_t bitWidth, uint8_t bitDepth,
|
||||||
Adafruit_Protomatter::Adafruit_Protomatter(
|
|
||||||
uint16_t bitWidth, uint8_t bitDepth,
|
|
||||||
uint8_t rgbCount, uint8_t *rgbList,
|
uint8_t rgbCount, uint8_t *rgbList,
|
||||||
uint8_t addrCount, uint8_t *addrList,
|
uint8_t addrCount, uint8_t *addrList,
|
||||||
uint8_t clockPin, uint8_t latchPin, uint8_t oePin,
|
uint8_t clockPin, uint8_t latchPin,
|
||||||
bool doubleBuffer, void *timer) :
|
uint8_t oePin, bool doubleBuffer,
|
||||||
GFXcanvas16(bitWidth, (2 << min(addrCount, 5)) * min(rgbCount, 5)) {
|
void *timer)
|
||||||
if(bitDepth > 6) bitDepth = 6; // GFXcanvas16 color limit (565)
|
: GFXcanvas16(bitWidth, (2 << min(addrCount, 5)) * min(rgbCount, 5)) {
|
||||||
|
if (bitDepth > 6)
|
||||||
|
bitDepth = 6; // GFXcanvas16 color limit (565)
|
||||||
|
|
||||||
// Arguments are passed through to the C _PM_init() function which does
|
// Arguments are passed through to the C _PM_init() function which does
|
||||||
// some input validation and minor allocation. Return value is ignored
|
// some input validation and minor allocation. Return value is ignored
|
||||||
// because we can't really do anything about it in a C++ constructor.
|
// because we can't really do anything about it in a C++ constructor.
|
||||||
// The class begin() function checks rgbPins for NULL to determine
|
// The class begin() function checks rgbPins for NULL to determine
|
||||||
// whether to proceed or indicate an error.
|
// whether to proceed or indicate an error.
|
||||||
(void)_PM_init(&core, bitWidth, bitDepth, rgbCount, rgbList,
|
(void)_PM_init(&core, bitWidth, bitDepth, rgbCount, rgbList, addrCount,
|
||||||
addrCount, addrList, clockPin, latchPin, oePin, doubleBuffer, timer);
|
addrList, clockPin, latchPin, oePin, doubleBuffer, timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
Adafruit_Protomatter::~Adafruit_Protomatter(void) {
|
Adafruit_Protomatter::~Adafruit_Protomatter(void) {
|
||||||
|
|
@ -55,33 +90,15 @@ Adafruit_Protomatter::~Adafruit_Protomatter(void) {
|
||||||
|
|
||||||
ProtomatterStatus Adafruit_Protomatter::begin(void) {
|
ProtomatterStatus Adafruit_Protomatter::begin(void) {
|
||||||
_PM_protoPtr = &core;
|
_PM_protoPtr = &core;
|
||||||
_PM_begin(&core);
|
return _PM_begin(&core);
|
||||||
return PROTOMATTER_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transfer data from GFXcanvas16 to the matrix framebuffer's weird
|
// Transfer data from GFXcanvas16 to the matrix framebuffer's weird
|
||||||
// internal format. The actual conversion functions referenced below
|
// internal format. The actual conversion functions referenced below
|
||||||
// are in core.c, reasoning is explained there.
|
// are in core.c, reasoning is explained there.
|
||||||
void Adafruit_Protomatter::show(void) {
|
void Adafruit_Protomatter::show(void) {
|
||||||
|
_PM_convert_565(&core, getBuffer(), WIDTH);
|
||||||
// Destination address is computed in convert function
|
_PM_swapbuffer_maybe(&core);
|
||||||
// (based on active buffer value, if double-buffering),
|
|
||||||
// just need to pass in the canvas buffer address and
|
|
||||||
// width in pixels.
|
|
||||||
if(core.bytesPerElement == 1) {
|
|
||||||
_PM_convert_565_byte(&core, getBuffer(), WIDTH);
|
|
||||||
} else if(core.bytesPerElement == 2) {
|
|
||||||
_PM_convert_565_word(&core, getBuffer(), WIDTH);
|
|
||||||
} else {
|
|
||||||
_PM_convert_565_long(&core, getBuffer(), WIDTH);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(core.doubleBuffer) {
|
|
||||||
core.swapBuffers = 1;
|
|
||||||
// To avoid overwriting data on the matrix, don't return
|
|
||||||
// until the timer ISR has performed the swap at the right time.
|
|
||||||
while(core.swapBuffers);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns current value of frame counter and resets its value to zero.
|
// Returns current value of frame counter and resets its value to zero.
|
||||||
|
|
|
||||||
|
|
@ -7,17 +7,94 @@
|
||||||
#include "core.h"
|
#include "core.h"
|
||||||
#include <Adafruit_GFX.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 {
|
class Adafruit_Protomatter : public GFXcanvas16 {
|
||||||
public:
|
public:
|
||||||
Adafruit_Protomatter(uint16_t bitWidth, uint8_t bitDepth,
|
/*!
|
||||||
uint8_t rgbCount, uint8_t *rgbList,
|
@brief Adafruit_Protomatter constructor.
|
||||||
uint8_t addrCount, uint8_t *addrList,
|
@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 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,
|
uint8_t clockPin, uint8_t latchPin, uint8_t oePin,
|
||||||
bool doubleBuffer, void *timer = NULL);
|
bool doubleBuffer, void *timer = NULL);
|
||||||
~Adafruit_Protomatter(void);
|
~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);
|
ProtomatterStatus begin(void);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Process data from GFXcanvas16 to the matrix framebuffer's
|
||||||
|
internal format for display.
|
||||||
|
*/
|
||||||
void show(void);
|
void show(void);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@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);
|
uint32_t getFrameCount(void);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Protomatter_core core; // Underlying C struct
|
Protomatter_core core; // Underlying C struct
|
||||||
void convert_byte(uint8_t *dest); // GFXcanvas16-to-matrix
|
void convert_byte(uint8_t *dest); // GFXcanvas16-to-matrix
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Adafruit_Protomatter
|
# Adafruit_Protomatter [](https://travis-ci.com/adafruit/Adafruit_Protomatter)
|
||||||
|
|
||||||
"I used protomatter in the Genesis matrix." - David Marcus, Star Trek III
|
"I used protomatter in the Genesis matrix." - David Marcus, Star Trek III
|
||||||
|
|
||||||
|
|
@ -6,8 +6,6 @@ Code for driving HUB75-style RGB LED matrices, targeted at 32-bit MCUs
|
||||||
using brute-force GPIO (that is, not relying on DMA or other specialized
|
using brute-force GPIO (that is, not relying on DMA or other specialized
|
||||||
peripherals beyond a timer interrupt, goal being portability).
|
peripherals beyond a timer interrupt, goal being portability).
|
||||||
|
|
||||||
Name might change as it's nondescriptive and tedious to type in code.
|
|
||||||
|
|
||||||
# Matrix Concepts and Jargon
|
# Matrix Concepts and Jargon
|
||||||
|
|
||||||
HUB75 RGB LED matrices are basically a set of six concurrent shift register
|
HUB75 RGB LED matrices are basically a set of six concurrent shift register
|
||||||
|
|
|
||||||
653
arch.h
653
arch.h
|
|
@ -1,9 +1,27 @@
|
||||||
|
/*!
|
||||||
|
* @file arch.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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
// Establishes some very low-level things specific to each supported device.
|
// Establishes some very low-level things specific to each supported device.
|
||||||
// This should ONLY be included by core.c, nowhere else. Ever.
|
// This should ONLY be included by core.c, nowhere else. Ever.
|
||||||
|
|
||||||
#if !defined(_PROTOMATTER_ARCH_H_)
|
#if !defined(_PROTOMATTER_ARCH_H_)
|
||||||
#define _PROTOMATTER_ARCH_H_
|
#define _PROTOMATTER_ARCH_H_
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Common ground for architectures to support this library:
|
Common ground for architectures to support this library:
|
||||||
|
|
||||||
|
|
@ -125,13 +143,29 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
|
||||||
#define _PM_pinLow(pin) digitalWrite(pin, LOW)
|
#define _PM_pinLow(pin) digitalWrite(pin, LOW)
|
||||||
#define _PM_portBitMask(pin) digitalPinToBitMask(pin)
|
#define _PM_portBitMask(pin) digitalPinToBitMask(pin)
|
||||||
|
|
||||||
|
#elif defined(CIRCUITPY)
|
||||||
|
#include "py/mphal.h"
|
||||||
|
#include "shared-bindings/microcontroller/Pin.h"
|
||||||
|
|
||||||
|
#define _PM_delayMicroseconds(us) mp_hal_delay_us(us)
|
||||||
|
|
||||||
|
#ifdef SAMD51
|
||||||
|
#define __SAMD51__
|
||||||
|
#define F_CPU (120000000)
|
||||||
|
#endif
|
||||||
|
#ifdef SAMD21
|
||||||
|
#define _SAMD21_
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef STM32F405xx
|
||||||
|
#define STM32F4_SERIES (1)
|
||||||
|
#endif
|
||||||
|
|
||||||
// No #else here. In non-Arduino case, declare things in the arch-specific
|
// No #else here. In non-Arduino case, declare things in the arch-specific
|
||||||
// sections below...unless other environments provide device-neutral
|
// sections below...unless other environments provide device-neutral
|
||||||
// functions as above, in which case those could go here (w/#elif).
|
// functions as above, in which case those could go here (w/#elif).
|
||||||
|
|
||||||
#endif // end defined(ARDUINO)
|
#endif // end defined(ARDUINO)
|
||||||
|
|
||||||
|
|
||||||
// CODE COMMON TO BOTH SAMD51 AND SAMD21 -----------------------------------
|
// CODE COMMON TO BOTH SAMD51 AND SAMD21 -----------------------------------
|
||||||
|
|
||||||
#if defined(__SAMD51__) || defined(_SAMD21_)
|
#if defined(__SAMD51__) || defined(_SAMD21_)
|
||||||
|
|
@ -140,19 +174,20 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
|
||||||
// g_APinDescription[] table and pin indices are Arduino specific:
|
// g_APinDescription[] table and pin indices are Arduino specific:
|
||||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
#define _PM_byteOffset(pin) (g_APinDescription[pin].ulPin / 8)
|
#define _PM_byteOffset(pin) (g_APinDescription[pin].ulPin / 8)
|
||||||
#else
|
|
||||||
#define _PM_byteOffset(pin) (3 - (g_APinDescription[pin].ulPin / 8))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
|
||||||
#define _PM_wordOffset(pin) (g_APinDescription[pin].ulPin / 16)
|
#define _PM_wordOffset(pin) (g_APinDescription[pin].ulPin / 16)
|
||||||
#else
|
#else
|
||||||
|
#define _PM_byteOffset(pin) (3 - (g_APinDescription[pin].ulPin / 8))
|
||||||
#define _PM_wordOffset(pin) (1 - (g_APinDescription[pin].ulPin / 16))
|
#define _PM_wordOffset(pin) (1 - (g_APinDescription[pin].ulPin / 16))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Arduino implementation is tied to a specific timer/counter & freq:
|
// Arduino implementation is tied to a specific timer/counter & freq:
|
||||||
|
#if defined(TC4)
|
||||||
#define _PM_TIMER_DEFAULT TC4
|
#define _PM_TIMER_DEFAULT TC4
|
||||||
#define _PM_IRQ_HANDLER TC4_Handler
|
#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
|
#define _PM_timerFreq 48000000
|
||||||
// Partly because IRQs must be declared at compile-time, and partly
|
// Partly because IRQs must be declared at compile-time, and partly
|
||||||
// because we know Arduino's already set up one of the GCLK sources
|
// because we know Arduino's already set up one of the GCLK sources
|
||||||
|
|
@ -170,9 +205,45 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
|
||||||
_PM_row_handler(_PM_protoPtr); // In core.c
|
_PM_row_handler(_PM_protoPtr); // In core.c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#elif defined(CIRCUITPY)
|
||||||
|
|
||||||
|
#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) % 32))
|
||||||
|
|
||||||
|
#define _PM_byteOffset(pin) ((pin % 32) / 8)
|
||||||
|
#define _PM_wordOffset(pin) ((pin % 32) / 16)
|
||||||
|
|
||||||
|
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
|
||||||
|
#error SRSLY
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// CircuitPython implementation is tied to a specific freq (but the counter
|
||||||
|
// is dynamically allocated):
|
||||||
|
#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) {
|
||||||
|
((Tc *)(((Protomatter_core *)_PM_protoPtr)->timer))->COUNT16.INTFLAG.reg =
|
||||||
|
TC_INTFLAG_OVF;
|
||||||
|
_PM_row_handler(_PM_protoPtr); // In core.c
|
||||||
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
// Non-arduino byte offset macros, timer and ISR work go here.
|
// Other port byte offset macros, timer and ISR work go here.
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -182,7 +253,6 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
|
||||||
|
|
||||||
#endif // __SAMD51__ || _SAMD21_
|
#endif // __SAMD51__ || _SAMD21_
|
||||||
|
|
||||||
|
|
||||||
// SAMD51-SPECIFIC CODE ----------------------------------------------------
|
// SAMD51-SPECIFIC CODE ----------------------------------------------------
|
||||||
|
|
||||||
#if defined(__SAMD51__)
|
#if defined(__SAMD51__)
|
||||||
|
|
@ -202,9 +272,21 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
|
||||||
#define _PM_portToggleRegister(pin) \
|
#define _PM_portToggleRegister(pin) \
|
||||||
&PORT->Group[g_APinDescription[pin].ulPort].OUTTGL.reg
|
&PORT->Group[g_APinDescription[pin].ulPort].OUTTGL.reg
|
||||||
|
|
||||||
|
#elif defined(CIRCUITPY)
|
||||||
|
|
||||||
|
#include "hal_gpio.h"
|
||||||
|
|
||||||
|
#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)
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
// Non-Arduino port register lookups go here
|
// Other port register lookups go here
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -215,32 +297,44 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
|
||||||
IRQn_Type IRQn; // Interrupt number
|
IRQn_Type IRQn; // Interrupt number
|
||||||
uint8_t GCLK_ID; // Peripheral channel # for clock source
|
uint8_t GCLK_ID; // Peripheral channel # for clock source
|
||||||
} timer[] = {
|
} timer[] = {
|
||||||
TC0, TC0_IRQn, TC0_GCLK_ID,
|
#if defined(TC0)
|
||||||
TC1, TC1_IRQn, TC1_GCLK_ID,
|
{TC0, TC0_IRQn, TC0_GCLK_ID},
|
||||||
TC2, TC2_IRQn, TC2_GCLK_ID,
|
#endif
|
||||||
TC3, TC3_IRQn, TC3_GCLK_ID,
|
#if defined(TC1)
|
||||||
TC4, TC4_IRQn, TC4_GCLK_ID,
|
{TC1, TC1_IRQn, TC1_GCLK_ID},
|
||||||
TC5, TC5_IRQn, TC5_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)
|
#if defined(TC6)
|
||||||
TC6, TC6_IRQn, TC6_GCLK_ID,
|
{TC6, TC6_IRQn, TC6_GCLK_ID},
|
||||||
#endif
|
#endif
|
||||||
#if defined(TC7)
|
#if defined(TC7)
|
||||||
TC7, TC7_IRQn, TC7_GCLK_ID,
|
{TC7, TC7_IRQn, TC7_GCLK_ID},
|
||||||
#endif
|
#endif
|
||||||
#if defined(TC8)
|
#if defined(TC8)
|
||||||
TC8, TC8_IRQn, TC8_GCLK_ID,
|
{TC8, TC8_IRQn, TC8_GCLK_ID},
|
||||||
#endif
|
#endif
|
||||||
#if defined(TC9)
|
#if defined(TC9)
|
||||||
TC9, TC9_IRQn, TC9_GCLK_ID,
|
{TC9, TC9_IRQn, TC9_GCLK_ID},
|
||||||
#endif
|
#endif
|
||||||
#if defined(TC10)
|
#if defined(TC10)
|
||||||
TC10, TC10_IRQn, TC10_GCLK_ID,
|
{TC10, TC10_IRQn, TC10_GCLK_ID},
|
||||||
#endif
|
#endif
|
||||||
#if defined(TC11)
|
#if defined(TC11)
|
||||||
TC11, TC11_IRQn, TC11_GCLK_ID,
|
{TC11, TC11_IRQn, TC11_GCLK_ID},
|
||||||
#endif
|
#endif
|
||||||
#if defined(TC12)
|
#if defined(TC12)
|
||||||
TC12, TC12_IRQn, TC12_GCLK_ID,
|
{TC12, TC12_IRQn, TC12_GCLK_ID},
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
#define NUM_TIMERS (sizeof timer / sizeof timer[0])
|
#define NUM_TIMERS (sizeof timer / sizeof timer[0])
|
||||||
|
|
@ -251,7 +345,8 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
|
||||||
while ((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tc)) {
|
while ((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tc)) {
|
||||||
timerNum++;
|
timerNum++;
|
||||||
}
|
}
|
||||||
if(timerNum >= NUM_TIMERS) return;
|
if (timerNum >= NUM_TIMERS)
|
||||||
|
return;
|
||||||
|
|
||||||
// Feed timer/counter off GCLK1 (already set 48 MHz by Arduino core).
|
// 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
|
// Sure, SAMD51 can run timers up to F_CPU (e.g. 120 MHz or up to
|
||||||
|
|
@ -262,17 +357,20 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
|
||||||
// simply that the period doubles with each bitplane, and this can
|
// simply that the period doubles with each bitplane, and this can
|
||||||
// work fine at 48 MHz.
|
// work fine at 48 MHz.
|
||||||
GCLK->PCHCTRL[timer[timerNum].GCLK_ID].bit.CHEN = 0; // Disable
|
GCLK->PCHCTRL[timer[timerNum].GCLK_ID].bit.CHEN = 0; // Disable
|
||||||
while(GCLK->PCHCTRL[timer[timerNum].GCLK_ID].bit.CHEN); // Wait for it
|
while (GCLK->PCHCTRL[timer[timerNum].GCLK_ID].bit.CHEN)
|
||||||
|
; // Wait for it
|
||||||
GCLK_PCHCTRL_Type pchctrl; // Read-modify-store
|
GCLK_PCHCTRL_Type pchctrl; // Read-modify-store
|
||||||
pchctrl.reg = GCLK->PCHCTRL[timer[timerNum].GCLK_ID].reg;
|
pchctrl.reg = GCLK->PCHCTRL[timer[timerNum].GCLK_ID].reg;
|
||||||
pchctrl.bit.GEN = GCLK_PCHCTRL_GEN_GCLK1_Val;
|
pchctrl.bit.GEN = GCLK_PCHCTRL_GEN_GCLK1_Val;
|
||||||
pchctrl.bit.CHEN = 1;
|
pchctrl.bit.CHEN = 1;
|
||||||
GCLK->PCHCTRL[timer[timerNum].GCLK_ID].reg = pchctrl.reg;
|
GCLK->PCHCTRL[timer[timerNum].GCLK_ID].reg = pchctrl.reg;
|
||||||
while(!GCLK->PCHCTRL[timer[timerNum].GCLK_ID].bit.CHEN);
|
while (!GCLK->PCHCTRL[timer[timerNum].GCLK_ID].bit.CHEN)
|
||||||
|
;
|
||||||
|
|
||||||
// Disable timer before configuring it
|
// Disable timer before configuring it
|
||||||
tc->COUNT16.CTRLA.bit.ENABLE = 0;
|
tc->COUNT16.CTRLA.bit.ENABLE = 0;
|
||||||
while(tc->COUNT16.SYNCBUSY.bit.ENABLE);
|
while (tc->COUNT16.SYNCBUSY.bit.ENABLE)
|
||||||
|
;
|
||||||
|
|
||||||
// 16-bit counter mode, 1:1 prescale
|
// 16-bit counter mode, 1:1 prescale
|
||||||
tc->COUNT16.CTRLA.bit.MODE = TC_CTRLA_MODE_COUNT16;
|
tc->COUNT16.CTRLA.bit.MODE = TC_CTRLA_MODE_COUNT16;
|
||||||
|
|
@ -282,7 +380,8 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
|
||||||
TC_WAVE_WAVEGEN_MFRQ_Val; // Match frequency generation mode (MFRQ)
|
TC_WAVE_WAVEGEN_MFRQ_Val; // Match frequency generation mode (MFRQ)
|
||||||
|
|
||||||
tc->COUNT16.CTRLBCLR.reg = TC_CTRLBCLR_DIR; // Count up
|
tc->COUNT16.CTRLBCLR.reg = TC_CTRLBCLR_DIR; // Count up
|
||||||
while(tc->COUNT16.SYNCBUSY.bit.CTRLB);
|
while (tc->COUNT16.SYNCBUSY.bit.CTRLB)
|
||||||
|
;
|
||||||
|
|
||||||
// Overflow interrupt
|
// Overflow interrupt
|
||||||
tc->COUNT16.INTENSET.reg = TC_INTENSET_OVF;
|
tc->COUNT16.INTENSET.reg = TC_INTENSET_OVF;
|
||||||
|
|
@ -301,11 +400,14 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
|
||||||
inline void _PM_timerStart(void *tptr, uint32_t period) {
|
inline void _PM_timerStart(void *tptr, uint32_t period) {
|
||||||
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
|
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
|
||||||
tc->COUNT16.COUNT.reg = 0;
|
tc->COUNT16.COUNT.reg = 0;
|
||||||
while(tc->COUNT16.SYNCBUSY.bit.COUNT);
|
while (tc->COUNT16.SYNCBUSY.bit.COUNT)
|
||||||
|
;
|
||||||
tc->COUNT16.CC[0].reg = period;
|
tc->COUNT16.CC[0].reg = period;
|
||||||
while(tc->COUNT16.SYNCBUSY.bit.CC0);
|
while (tc->COUNT16.SYNCBUSY.bit.CC0)
|
||||||
|
;
|
||||||
tc->COUNT16.CTRLA.bit.ENABLE = 1;
|
tc->COUNT16.CTRLA.bit.ENABLE = 1;
|
||||||
while(tc->COUNT16.SYNCBUSY.bit.STATUS);
|
while (tc->COUNT16.SYNCBUSY.bit.STATUS)
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return current count value (timer enabled or not).
|
// Return current count value (timer enabled or not).
|
||||||
|
|
@ -313,7 +415,8 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
|
||||||
inline uint32_t _PM_timerGetCount(void *tptr) {
|
inline uint32_t _PM_timerGetCount(void *tptr) {
|
||||||
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
|
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
|
||||||
tc->COUNT16.CTRLBSET.bit.CMD = 0x4; // Sync COUNT
|
tc->COUNT16.CTRLBSET.bit.CMD = 0x4; // Sync COUNT
|
||||||
while(tc->COUNT16.CTRLBSET.bit.CMD); // Wait for command
|
while (tc->COUNT16.CTRLBSET.bit.CMD)
|
||||||
|
; // Wait for command
|
||||||
return tc->COUNT16.COUNT.reg;
|
return tc->COUNT16.COUNT.reg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -323,7 +426,8 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
|
||||||
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
|
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
|
||||||
uint32_t count = _PM_timerGetCount(tptr);
|
uint32_t count = _PM_timerGetCount(tptr);
|
||||||
tc->COUNT16.CTRLA.bit.ENABLE = 0;
|
tc->COUNT16.CTRLA.bit.ENABLE = 0;
|
||||||
while(tc->COUNT16.SYNCBUSY.bit.STATUS);
|
while (tc->COUNT16.SYNCBUSY.bit.STATUS)
|
||||||
|
;
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -346,7 +450,6 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
|
||||||
|
|
||||||
#endif // end __SAMD51__
|
#endif // end __SAMD51__
|
||||||
|
|
||||||
|
|
||||||
// SAMD21-SPECIFIC CODE ----------------------------------------------------
|
// SAMD21-SPECIFIC CODE ----------------------------------------------------
|
||||||
|
|
||||||
#if defined(_SAMD21_)
|
#if defined(_SAMD21_)
|
||||||
|
|
@ -372,7 +475,6 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
// Initialize, but do not start, timer
|
// Initialize, but do not start, timer
|
||||||
void _PM_timerInit(void *tptr) {
|
void _PM_timerInit(void *tptr) {
|
||||||
static const struct {
|
static const struct {
|
||||||
|
|
@ -380,16 +482,20 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
|
||||||
IRQn_Type IRQn; // Interrupt number
|
IRQn_Type IRQn; // Interrupt number
|
||||||
uint8_t GCM_ID; // GCLK selection ID
|
uint8_t GCM_ID; // GCLK selection ID
|
||||||
} timer[] = {
|
} timer[] = {
|
||||||
TC0, TC0_IRQn, GCM_TCC0_TCC1,
|
#if defined(TC0)
|
||||||
TC1, TC1_IRQn, GCM_TCC0_TCC1,
|
{TC0, TC0_IRQn, GCM_TCC0_TCC1},
|
||||||
|
#endif
|
||||||
|
#if defined(TC1)
|
||||||
|
{TC1, TC1_IRQn, GCM_TCC0_TCC1},
|
||||||
|
#endif
|
||||||
#if defined(TC2)
|
#if defined(TC2)
|
||||||
TC2, TC2_IRQn, GCM_TCC2_TC3,
|
{TC2, TC2_IRQn, GCM_TCC2_TC3},
|
||||||
#endif
|
#endif
|
||||||
#if defined(TC3)
|
#if defined(TC3)
|
||||||
TC3, TC3_IRQn, GCM_TCC2_TC3,
|
{TC3, TC3_IRQn, GCM_TCC2_TC3},
|
||||||
#endif
|
#endif
|
||||||
#if defined(TC4)
|
#if defined(TC4)
|
||||||
TC4, TC4_IRQn, GCM_TC4_TC5,
|
{TC4, TC4_IRQn, GCM_TC4_TC5},
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
#define NUM_TIMERS (sizeof timer / sizeof timer[0])
|
#define NUM_TIMERS (sizeof timer / sizeof timer[0])
|
||||||
|
|
@ -400,25 +506,30 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
|
||||||
while ((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tc)) {
|
while ((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tc)) {
|
||||||
timerNum++;
|
timerNum++;
|
||||||
}
|
}
|
||||||
if(timerNum >= NUM_TIMERS) return;
|
if (timerNum >= NUM_TIMERS)
|
||||||
|
return;
|
||||||
|
|
||||||
// Enable GCLK for timer/counter
|
// Enable GCLK for timer/counter
|
||||||
GCLK->CLKCTRL.reg = (uint16_t)(GCLK_CLKCTRL_CLKEN |
|
GCLK->CLKCTRL.reg = (uint16_t)(GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 |
|
||||||
GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(timer[timerNum].GCM_ID));
|
GCLK_CLKCTRL_ID(timer[timerNum].GCM_ID));
|
||||||
while(GCLK->STATUS.bit.SYNCBUSY == 1);
|
while (GCLK->STATUS.bit.SYNCBUSY == 1)
|
||||||
|
;
|
||||||
|
|
||||||
// Counter must first be disabled to configure it
|
// Counter must first be disabled to configure it
|
||||||
tc->COUNT16.CTRLA.bit.ENABLE = 0;
|
tc->COUNT16.CTRLA.bit.ENABLE = 0;
|
||||||
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
|
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
|
||||||
|
;
|
||||||
|
|
||||||
tc->COUNT16.CTRLA.reg = // Configure timer counter
|
tc->COUNT16.CTRLA.reg = // Configure timer counter
|
||||||
TC_CTRLA_PRESCALER_DIV1 | // 1:1 Prescale
|
TC_CTRLA_PRESCALER_DIV1 | // 1:1 Prescale
|
||||||
TC_CTRLA_WAVEGEN_MFRQ | // Match frequency generation mode (MFRQ)
|
TC_CTRLA_WAVEGEN_MFRQ | // Match frequency generation mode (MFRQ)
|
||||||
TC_CTRLA_MODE_COUNT16; // 16-bit counter mode
|
TC_CTRLA_MODE_COUNT16; // 16-bit counter mode
|
||||||
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
|
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
|
||||||
|
;
|
||||||
|
|
||||||
tc->COUNT16.CTRLBCLR.reg = TCC_CTRLBCLR_DIR; // Count up
|
tc->COUNT16.CTRLBCLR.reg = TCC_CTRLBCLR_DIR; // Count up
|
||||||
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
|
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
|
||||||
|
;
|
||||||
|
|
||||||
// Overflow interrupt
|
// Overflow interrupt
|
||||||
tc->COUNT16.INTENSET.reg = TC_INTENSET_OVF;
|
tc->COUNT16.INTENSET.reg = TC_INTENSET_OVF;
|
||||||
|
|
@ -437,11 +548,14 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
|
||||||
inline void _PM_timerStart(void *tptr, uint32_t period) {
|
inline void _PM_timerStart(void *tptr, uint32_t period) {
|
||||||
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
|
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
|
||||||
tc->COUNT16.COUNT.reg = 0;
|
tc->COUNT16.COUNT.reg = 0;
|
||||||
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
|
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
|
||||||
|
;
|
||||||
tc->COUNT16.CC[0].reg = period;
|
tc->COUNT16.CC[0].reg = period;
|
||||||
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
|
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
|
||||||
|
;
|
||||||
tc->COUNT16.CTRLA.bit.ENABLE = 1;
|
tc->COUNT16.CTRLA.bit.ENABLE = 1;
|
||||||
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
|
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return current count value (timer enabled or not).
|
// Return current count value (timer enabled or not).
|
||||||
|
|
@ -449,7 +563,8 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
|
||||||
inline uint32_t _PM_timerGetCount(void *tptr) {
|
inline uint32_t _PM_timerGetCount(void *tptr) {
|
||||||
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
|
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
|
||||||
tc->COUNT16.READREQ.reg = TC_READREQ_RCONT | TC_READREQ_ADDR(0x10);
|
tc->COUNT16.READREQ.reg = TC_READREQ_RCONT | TC_READREQ_ADDR(0x10);
|
||||||
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
|
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
|
||||||
|
;
|
||||||
return tc->COUNT16.COUNT.reg;
|
return tc->COUNT16.COUNT.reg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -459,46 +574,359 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
|
||||||
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
|
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
|
||||||
uint32_t count = _PM_timerGetCount(tptr);
|
uint32_t count = _PM_timerGetCount(tptr);
|
||||||
tc->COUNT16.CTRLA.bit.ENABLE = 0;
|
tc->COUNT16.CTRLA.bit.ENABLE = 0;
|
||||||
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
|
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
|
||||||
|
;
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // _SAMD21_
|
#endif // _SAMD21_
|
||||||
|
|
||||||
|
|
||||||
// NRF52-SPECIFIC CODE -----------------------------------------------------
|
// NRF52-SPECIFIC CODE -----------------------------------------------------
|
||||||
|
|
||||||
#if defined(NRF52_SERIES)
|
#if defined(NRF52_SERIES)
|
||||||
|
|
||||||
|
#if defined(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)
|
||||||
|
|
||||||
|
#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) % 32))
|
||||||
|
|
||||||
|
#define _PM_byteOffset(pin) ((pin % 32) / 8)
|
||||||
|
#define _PM_wordOffset(pin) ((pin % 32) / 16)
|
||||||
|
|
||||||
|
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
|
||||||
|
#error SRSLY
|
||||||
|
#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
|
||||||
|
|
||||||
|
// Non-arduino byte offset macros, timer and ISR work go here.
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void _PM_timerInit(void *tptr) {
|
||||||
|
static const struct {
|
||||||
|
NRF_TIMER_Type *tc; // -> Timer peripheral base address
|
||||||
|
IRQn_Type IRQn; // Interrupt number
|
||||||
|
} timer[] = {
|
||||||
|
#if defined(NRF_TIMER0)
|
||||||
|
{NRF_TIMER0, TIMER0_IRQn},
|
||||||
|
#endif
|
||||||
|
#if defined(NRF_TIMER1)
|
||||||
|
{NRF_TIMER1, TIMER1_IRQn},
|
||||||
|
#endif
|
||||||
|
#if defined(NRF_TIMER2)
|
||||||
|
{NRF_TIMER2, TIMER2_IRQn},
|
||||||
|
#endif
|
||||||
|
#if defined(NRF_TIMER3)
|
||||||
|
{NRF_TIMER3, TIMER3_IRQn},
|
||||||
|
#endif
|
||||||
|
#if defined(NRF_TIMER4)
|
||||||
|
{NRF_TIMER4, TIMER4_IRQn},
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
#define NUM_TIMERS (sizeof timer / sizeof timer[0])
|
||||||
|
|
||||||
|
// Determine IRQn from timer address
|
||||||
|
uint8_t timerNum = 0;
|
||||||
|
while ((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tptr)) {
|
||||||
|
timerNum++;
|
||||||
|
}
|
||||||
|
if (timerNum >= NUM_TIMERS)
|
||||||
|
return;
|
||||||
|
|
||||||
|
NRF_TIMER_Type *tc = timer[timerNum].tc;
|
||||||
|
|
||||||
|
tc->TASKS_STOP = 1; // Stop timer
|
||||||
|
tc->MODE = TIMER_MODE_MODE_Timer; // Timer (not counter) mode
|
||||||
|
tc->TASKS_CLEAR = 1;
|
||||||
|
tc->BITMODE = TIMER_BITMODE_BITMODE_16Bit
|
||||||
|
<< TIMER_BITMODE_BITMODE_Pos; // 16-bit timer res
|
||||||
|
tc->PRESCALER = 0; // 1:1 prescale (16 MHz)
|
||||||
|
tc->INTENSET = TIMER_INTENSET_COMPARE0_Enabled
|
||||||
|
<< TIMER_INTENSET_COMPARE0_Pos; // Event 0 interrupt
|
||||||
|
// NVIC_DisableIRQ(timer[timerNum].IRQn);
|
||||||
|
// NVIC_ClearPendingIRQ(timer[timerNum].IRQn);
|
||||||
|
// NVIC_SetPriority(timer[timerNum].IRQn, 0); // Top priority
|
||||||
|
NVIC_EnableIRQ(timer[timerNum].IRQn);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void _PM_timerStart(void *tptr, uint32_t period) {
|
||||||
|
volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)tptr;
|
||||||
|
tc->TASKS_STOP = 1; // Stop timer
|
||||||
|
tc->TASKS_CLEAR = 1; // Reset to 0
|
||||||
|
tc->CC[0] = period;
|
||||||
|
tc->TASKS_START = 1; // Start timer
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint32_t _PM_timerGetCount(void *tptr) {
|
||||||
|
volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)tptr;
|
||||||
|
tc->TASKS_CAPTURE[0] = 1; // Capture timer to CC[n] register
|
||||||
|
return tc->CC[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t _PM_timerStop(void *tptr) {
|
||||||
|
volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)tptr;
|
||||||
|
tc->TASKS_STOP = 1; // Stop timer
|
||||||
|
__attribute__((unused)) uint32_t count = _PM_timerGetCount(tptr);
|
||||||
|
// NOTE TO FUTURE SELF: I don't know why the GetCount code isn't
|
||||||
|
// working. It does the expected thing in a small test program but
|
||||||
|
// not here. I need to get on with testing on an actual matrix, so
|
||||||
|
// this is just a nonsense fudge value for now:
|
||||||
|
return 100;
|
||||||
|
// return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define _PM_clockHoldHigh asm("nop; nop");
|
||||||
|
|
||||||
|
#define _PM_minMinPeriod 100
|
||||||
|
|
||||||
#endif // NRF52_SERIES
|
#endif // NRF52_SERIES
|
||||||
|
|
||||||
|
// STM32F4xx SPECIFIC CODE -------------------------------------------------
|
||||||
|
#if defined(STM32F4_SERIES)
|
||||||
|
#if defined(ARDUINO)
|
||||||
|
// Arduino port register lookups go here
|
||||||
|
#elif defined(CIRCUITPY)
|
||||||
|
|
||||||
|
#undef _PM_portBitMask
|
||||||
|
#define _PM_portBitMask(pin) (1u << ((pin) % 16))
|
||||||
|
#define _PM_byteOffset(pin) ((pin % 16) / 8)
|
||||||
|
#define _PM_wordOffset(pin) ((pin % 16) / 16)
|
||||||
|
|
||||||
|
#define _PM_pinOutput(pin_) \
|
||||||
|
do { \
|
||||||
|
int8_t pin = (pin_); \
|
||||||
|
GPIO_InitTypeDef GPIO_InitStruct = {0}; \
|
||||||
|
GPIO_InitStruct.Pin = 1 << (pin % 16); \
|
||||||
|
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 % 16); \
|
||||||
|
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 % 16), GPIO_PIN_SET)
|
||||||
|
#define _PM_pinLow(pin) \
|
||||||
|
HAL_GPIO_WritePin(pin_port(pin / 16), 1 << (pin % 16), 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use hard-coded TIM6 (TIM7 is used by PulseOut, other TIM by PWMOut)
|
||||||
|
#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().
|
||||||
|
void *_PM_protoPtr = NULL;
|
||||||
|
|
||||||
|
STATIC TIM_HandleTypeDef t6_handle;
|
||||||
|
|
||||||
|
#define _PM_IRQ_HANDLER TIM6_DAC_IRQHandler
|
||||||
|
|
||||||
|
// Timer interrupt service routine
|
||||||
|
void _PM_IRQ_HANDLER(void) {
|
||||||
|
// Clear overflow flag:
|
||||||
|
//_PM_TIMER_DEFAULT->COUNT16.INTFLAG.reg = TC_INTFLAG_OVF;
|
||||||
|
_PM_row_handler(_PM_protoPtr); // In core.c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize, but do not start, timer
|
||||||
|
void _PM_timerInit(void *tptr) {
|
||||||
|
__HAL_RCC_TIM6_CLK_ENABLE();
|
||||||
|
|
||||||
|
t6_handle.Instance = TIM6;
|
||||||
|
t6_handle.Init.Period = 1000; // immediately replaced.
|
||||||
|
t6_handle.Init.Prescaler = 0;
|
||||||
|
t6_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
|
||||||
|
t6_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
|
||||||
|
t6_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
|
||||||
|
|
||||||
|
HAL_TIM_Base_Init(&t6_handle);
|
||||||
|
|
||||||
|
HAL_NVIC_DisableIRQ(TIM6_DAC_IRQn);
|
||||||
|
NVIC_ClearPendingIRQ(TIM6_DAC_IRQn);
|
||||||
|
NVIC_SetPriority(TIM6_DAC_IRQn, 0); // Top priority
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void _PM_timerStart(void *tptr, uint32_t period) {
|
||||||
|
TIM_TypeDef *tim = tptr;
|
||||||
|
tim->SR = 0;
|
||||||
|
tim->ARR = period;
|
||||||
|
tim->CR1 |= TIM_CR1_CEN;
|
||||||
|
tim->DIER |= TIM_DIER_UIE;
|
||||||
|
HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t _PM_timerStop(void *tptr) {
|
||||||
|
HAL_NVIC_DisableIRQ(TIM6_DAC_IRQn);
|
||||||
|
TIM_TypeDef *tim = tptr;
|
||||||
|
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
|
||||||
|
#endif // STM32F4_SERIES
|
||||||
|
|
||||||
// ESP32-SPECIFIC CODE -----------------------------------------------------
|
// ESP32-SPECIFIC CODE -----------------------------------------------------
|
||||||
|
|
||||||
#if defined(ARDUINO_ARCH_ESP32)
|
#if defined(ARDUINO_ARCH_ESP32)
|
||||||
#endif // ARDUINO_ARCH_ESP32
|
#endif // ARDUINO_ARCH_ESP32
|
||||||
|
|
||||||
|
|
||||||
// DEFAULTS IF NOT DEFINED ABOVE -------------------------------------------
|
// DEFAULTS IF NOT DEFINED ABOVE -------------------------------------------
|
||||||
|
|
||||||
#if !defined(_PM_chunkSize)
|
#if !defined(_PM_chunkSize)
|
||||||
#define _PM_chunkSize 8
|
#define _PM_chunkSize 8 ///< Unroll data-stuffing loop to this size
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if !defined(_PM_clockHoldHigh)
|
#if !defined(_PM_clockHoldHigh)
|
||||||
#define _PM_clockHoldHigh
|
#define _PM_clockHoldHigh ///< Extra cycles (if any) on clock HIGH signal
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if !defined(_PM_clockHoldLow)
|
#if !defined(_PM_clockHoldLow)
|
||||||
#define _PM_clockHoldLow
|
#define _PM_clockHoldLow ///< Extra cycles (if any) on clock LOW signal
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if !defined(_PM_minMinPeriod)
|
#if !defined(_PM_minMinPeriod)
|
||||||
#define _PM_minMinPeriod 100
|
#define _PM_minMinPeriod 100 ///< Minimum timer interval for least bit
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef _PM_ALLOCATOR
|
||||||
|
#define _PM_ALLOCATOR(x) (malloc((x))) ///< Memory alloc call
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef _PM_FREE
|
||||||
|
#define _PM_FREE(x) (free((x))) ///< Memory free call
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// ARDUINO SPECIFIC CODE ---------------------------------------------------
|
// ARDUINO SPECIFIC CODE ---------------------------------------------------
|
||||||
|
|
||||||
#if defined(ARDUINO)
|
#if defined(ARDUINO) || defined(CIRCUITPY)
|
||||||
|
|
||||||
// 16-bit (565) color conversion functions go here (rather than in the
|
// 16-bit (565) color conversion functions go here (rather than in the
|
||||||
// Arduino lib .cpp) because knowledge is required of chunksize and the
|
// Arduino lib .cpp) because knowledge is required of chunksize and the
|
||||||
|
|
@ -521,10 +949,12 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
|
||||||
// width argument comes from GFX canvas width, which may be less than
|
// width argument comes from GFX canvas width, which may be less than
|
||||||
// core's bitWidth (due to padding). height isn't needed, it can be
|
// core's bitWidth (due to padding). height isn't needed, it can be
|
||||||
// inferred from core->numRowPairs.
|
// inferred from core->numRowPairs.
|
||||||
void _PM_convert_565_byte(Protomatter_core *core, uint16_t *source,
|
__attribute__((noinline)) void _PM_convert_565_byte(Protomatter_core *core,
|
||||||
|
const uint16_t *source,
|
||||||
uint16_t width) {
|
uint16_t width) {
|
||||||
uint16_t *upperSrc = source; // Canvas top half
|
const uint16_t *upperSrc = source; // Canvas top half
|
||||||
uint16_t *lowerSrc = source + width * core->numRowPairs; // " bottom half
|
const uint16_t *lowerSrc =
|
||||||
|
source + width * core->numRowPairs; // " bottom half
|
||||||
uint8_t *pinMask = (uint8_t *)core->rgbMask; // Pin bitmasks
|
uint8_t *pinMask = (uint8_t *)core->rgbMask; // Pin bitmasks
|
||||||
uint8_t *dest = (uint8_t *)core->screenData;
|
uint8_t *dest = (uint8_t *)core->screenData;
|
||||||
if (core->doubleBuffer) {
|
if (core->doubleBuffer) {
|
||||||
|
|
@ -537,7 +967,8 @@ void _PM_convert_565_byte(Protomatter_core *core, uint16_t *source,
|
||||||
|
|
||||||
// Determine matrix bytes per bitplane & row (row pair really):
|
// Determine matrix bytes per bitplane & row (row pair really):
|
||||||
|
|
||||||
uint32_t bitplaneSize = _PM_chunkSize *
|
uint32_t bitplaneSize =
|
||||||
|
_PM_chunkSize *
|
||||||
((width + (_PM_chunkSize - 1)) / _PM_chunkSize); // 1 plane of row pair
|
((width + (_PM_chunkSize - 1)) / _PM_chunkSize); // 1 plane of row pair
|
||||||
uint8_t pad = bitplaneSize - width; // Start-of-plane pad
|
uint8_t pad = bitplaneSize - width; // Start-of-plane pad
|
||||||
|
|
||||||
|
|
@ -586,12 +1017,18 @@ void _PM_convert_565_byte(Protomatter_core *core, uint16_t *source,
|
||||||
uint16_t upperRGB = upperSrc[x]; // Pixel in upper half
|
uint16_t upperRGB = upperSrc[x]; // Pixel in upper half
|
||||||
uint16_t lowerRGB = lowerSrc[x]; // Pixel in lower half
|
uint16_t lowerRGB = lowerSrc[x]; // Pixel in lower half
|
||||||
uint8_t result = 0;
|
uint8_t result = 0;
|
||||||
if(upperRGB & redBit) result |= pinMask[0];
|
if (upperRGB & redBit)
|
||||||
if(upperRGB & greenBit) result |= pinMask[1];
|
result |= pinMask[0];
|
||||||
if(upperRGB & blueBit) result |= pinMask[2];
|
if (upperRGB & greenBit)
|
||||||
if(lowerRGB & redBit) result |= pinMask[3];
|
result |= pinMask[1];
|
||||||
if(lowerRGB & greenBit) result |= pinMask[4];
|
if (upperRGB & blueBit)
|
||||||
if(lowerRGB & blueBit) result |= pinMask[5];
|
result |= pinMask[2];
|
||||||
|
if (lowerRGB & redBit)
|
||||||
|
result |= pinMask[3];
|
||||||
|
if (lowerRGB & greenBit)
|
||||||
|
result |= pinMask[4];
|
||||||
|
if (lowerRGB & blueBit)
|
||||||
|
result |= pinMask[5];
|
||||||
#if defined(_PM_portToggleRegister)
|
#if defined(_PM_portToggleRegister)
|
||||||
dest[x] = result ^ prior;
|
dest[x] = result ^ prior;
|
||||||
prior = result | core->clockMask; // Set clock bit on next out
|
prior = result | core->clockMask; // Set clock bit on next out
|
||||||
|
|
@ -639,11 +1076,11 @@ void _PM_convert_565_word(Protomatter_core *core, uint16_t *source,
|
||||||
uint16_t *pinMask = (uint16_t *)core->rgbMask; // Pin bitmasks
|
uint16_t *pinMask = (uint16_t *)core->rgbMask; // Pin bitmasks
|
||||||
uint16_t *dest = (uint16_t *)core->screenData;
|
uint16_t *dest = (uint16_t *)core->screenData;
|
||||||
if (core->doubleBuffer) {
|
if (core->doubleBuffer) {
|
||||||
dest += core->bufferSize / core->bytesPerElement *
|
dest += core->bufferSize / core->bytesPerElement * (1 - core->activeBuffer);
|
||||||
(1 - core->activeBuffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t bitplaneSize = _PM_chunkSize *
|
uint32_t bitplaneSize =
|
||||||
|
_PM_chunkSize *
|
||||||
((width + (_PM_chunkSize - 1)) / _PM_chunkSize); // 1 plane of row pair
|
((width + (_PM_chunkSize - 1)) / _PM_chunkSize); // 1 plane of row pair
|
||||||
uint8_t pad = bitplaneSize - width; // Start-of-plane pad
|
uint8_t pad = bitplaneSize - width; // Start-of-plane pad
|
||||||
|
|
||||||
|
|
@ -701,12 +1138,18 @@ void _PM_convert_565_word(Protomatter_core *core, uint16_t *source,
|
||||||
uint16_t upperRGB = upperSrc[x]; // Pixel in upper half
|
uint16_t upperRGB = upperSrc[x]; // Pixel in upper half
|
||||||
uint16_t lowerRGB = lowerSrc[x]; // Pixel in lower half
|
uint16_t lowerRGB = lowerSrc[x]; // Pixel in lower half
|
||||||
uint16_t result = 0;
|
uint16_t result = 0;
|
||||||
if(upperRGB & redBit) result |= pinMask[0];
|
if (upperRGB & redBit)
|
||||||
if(upperRGB & greenBit) result |= pinMask[1];
|
result |= pinMask[0];
|
||||||
if(upperRGB & blueBit) result |= pinMask[2];
|
if (upperRGB & greenBit)
|
||||||
if(lowerRGB & redBit) result |= pinMask[3];
|
result |= pinMask[1];
|
||||||
if(lowerRGB & greenBit) result |= pinMask[4];
|
if (upperRGB & blueBit)
|
||||||
if(lowerRGB & blueBit) result |= pinMask[5];
|
result |= pinMask[2];
|
||||||
|
if (lowerRGB & redBit)
|
||||||
|
result |= pinMask[3];
|
||||||
|
if (lowerRGB & greenBit)
|
||||||
|
result |= pinMask[4];
|
||||||
|
if (lowerRGB & blueBit)
|
||||||
|
result |= pinMask[5];
|
||||||
// Main difference here vs byte converter is each chain
|
// Main difference here vs byte converter is each chain
|
||||||
// ORs new bits into place (vs single-pass overwrite).
|
// ORs new bits into place (vs single-pass overwrite).
|
||||||
#if defined(_PM_portToggleRegister)
|
#if defined(_PM_portToggleRegister)
|
||||||
|
|
@ -745,11 +1188,11 @@ void _PM_convert_565_long(Protomatter_core *core, uint16_t *source,
|
||||||
uint32_t *pinMask = (uint32_t *)core->rgbMask; // Pin bitmasks
|
uint32_t *pinMask = (uint32_t *)core->rgbMask; // Pin bitmasks
|
||||||
uint32_t *dest = (uint32_t *)core->screenData;
|
uint32_t *dest = (uint32_t *)core->screenData;
|
||||||
if (core->doubleBuffer) {
|
if (core->doubleBuffer) {
|
||||||
dest += core->bufferSize / core->bytesPerElement *
|
dest += core->bufferSize / core->bytesPerElement * (1 - core->activeBuffer);
|
||||||
(1 - core->activeBuffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t bitplaneSize = _PM_chunkSize *
|
uint32_t bitplaneSize =
|
||||||
|
_PM_chunkSize *
|
||||||
((width + (_PM_chunkSize - 1)) / _PM_chunkSize); // 1 plane of row pair
|
((width + (_PM_chunkSize - 1)) / _PM_chunkSize); // 1 plane of row pair
|
||||||
uint8_t pad = bitplaneSize - width; // Start-of-plane pad
|
uint8_t pad = bitplaneSize - width; // Start-of-plane pad
|
||||||
|
|
||||||
|
|
@ -797,12 +1240,18 @@ void _PM_convert_565_long(Protomatter_core *core, uint16_t *source,
|
||||||
uint16_t upperRGB = upperSrc[x]; // Pixel in upper half
|
uint16_t upperRGB = upperSrc[x]; // Pixel in upper half
|
||||||
uint16_t lowerRGB = lowerSrc[x]; // Pixel in lower half
|
uint16_t lowerRGB = lowerSrc[x]; // Pixel in lower half
|
||||||
uint32_t result = 0;
|
uint32_t result = 0;
|
||||||
if(upperRGB & redBit) result |= pinMask[0];
|
if (upperRGB & redBit)
|
||||||
if(upperRGB & greenBit) result |= pinMask[1];
|
result |= pinMask[0];
|
||||||
if(upperRGB & blueBit) result |= pinMask[2];
|
if (upperRGB & greenBit)
|
||||||
if(lowerRGB & redBit) result |= pinMask[3];
|
result |= pinMask[1];
|
||||||
if(lowerRGB & greenBit) result |= pinMask[4];
|
if (upperRGB & blueBit)
|
||||||
if(lowerRGB & blueBit) result |= pinMask[5];
|
result |= pinMask[2];
|
||||||
|
if (lowerRGB & redBit)
|
||||||
|
result |= pinMask[3];
|
||||||
|
if (lowerRGB & greenBit)
|
||||||
|
result |= pinMask[4];
|
||||||
|
if (lowerRGB & blueBit)
|
||||||
|
result |= pinMask[5];
|
||||||
// Main difference here vs byte converter is each chain
|
// Main difference here vs byte converter is each chain
|
||||||
// ORs new bits into place (vs single-pass overwrite).
|
// ORs new bits into place (vs single-pass overwrite).
|
||||||
#if defined(_PM_portToggleRegister)
|
#if defined(_PM_portToggleRegister)
|
||||||
|
|
@ -831,6 +1280,34 @@ void _PM_convert_565_long(Protomatter_core *core, uint16_t *source,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // ARDUINO
|
void _PM_convert_565(Protomatter_core *core, uint16_t *source, uint16_t width) {
|
||||||
|
// Destination address is computed in convert function
|
||||||
|
// (based on active buffer value, if double-buffering),
|
||||||
|
// just need to pass in the canvas buffer address and
|
||||||
|
// width in pixels.
|
||||||
|
if (core->bytesPerElement == 1) {
|
||||||
|
_PM_convert_565_byte(core, source, width);
|
||||||
|
} else if (core->bytesPerElement == 2) {
|
||||||
|
_PM_convert_565_word(core, source, width);
|
||||||
|
} else {
|
||||||
|
_PM_convert_565_long(core, source, width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _PM_swapbuffer_maybe(Protomatter_core *core) {
|
||||||
|
if (core->doubleBuffer) {
|
||||||
|
core->swapBuffers = 1;
|
||||||
|
// To avoid overwriting data on the matrix, don't return
|
||||||
|
// until the timer ISR has performed the swap at the right time.
|
||||||
|
while (core->swapBuffers)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // ARDUINO || CIRCUITPYTHON
|
||||||
|
|
||||||
|
#ifndef _PM_PORT_TYPE
|
||||||
|
#define _PM_PORT_TYPE uint32_t ///< PORT register size/type
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif // _PROTOMATTER_ARCH_H_
|
#endif // _PROTOMATTER_ARCH_H_
|
||||||
|
|
|
||||||
218
core.c
218
core.c
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*!
|
||||||
|
* @file core.c
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
// Device- and environment-neutral core matrix-driving functionality.
|
// Device- and environment-neutral core matrix-driving functionality.
|
||||||
// See notes near top of arch.h regarding assumptions of hardware
|
// See notes near top of arch.h regarding assumptions of hardware
|
||||||
// "common ground." If you find yourself doing an "#ifdef ARDUINO" or
|
// "common ground." If you find yourself doing an "#ifdef ARDUINO" or
|
||||||
|
|
@ -15,6 +31,8 @@
|
||||||
|
|
||||||
#include "core.h" // enums and structs
|
#include "core.h" // enums and structs
|
||||||
#include "arch.h" // Do NOT include this in any other source files
|
#include "arch.h" // Do NOT include this in any other source files
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
// Overall matrix refresh rate (frames/second) is a function of matrix width
|
// Overall matrix refresh rate (frames/second) is a function of matrix width
|
||||||
// and chain length, number of address lines, number of bit planes, CPU speed
|
// and chain length, number of address lines, number of bit planes, CPU speed
|
||||||
|
|
@ -30,13 +48,13 @@
|
||||||
// refresh slower than this, and in many cases will...just need to set an
|
// refresh slower than this, and in many cases will...just need to set an
|
||||||
// upper limit to avoid excessive CPU load). An incredibly long comment block
|
// upper limit to avoid excessive CPU load). An incredibly long comment block
|
||||||
// for a single constant, thank you for coming to my TED talk!
|
// for a single constant, thank you for coming to my TED talk!
|
||||||
#define _PM_MAX_REFRESH_HZ 250
|
#define _PM_MAX_REFRESH_HZ 250 ///< Max matrix refresh rate
|
||||||
|
|
||||||
// Time (in microseconds) to pause following any change in address lines
|
// Time (in microseconds) to pause following any change in address lines
|
||||||
// (individually or collectively). Some matrices respond slowly there...
|
// (individually or collectively). Some matrices respond slowly there...
|
||||||
// must pause on change for matrix to catch up. Defined here (rather than
|
// must pause on change for matrix to catch up. Defined here (rather than
|
||||||
// arch.h) because it's not architecture-specific.
|
// arch.h) because it's not architecture-specific.
|
||||||
#define _PM_ROW_DELAY 8
|
#define _PM_ROW_DELAY 8 ///< Delay time between row address line changes (ms)
|
||||||
|
|
||||||
// These are the lowest-level functions for issing data to matrices.
|
// These are the lowest-level functions for issing data to matrices.
|
||||||
// There are three versions because it depends on how the six RGB data bits
|
// There are three versions because it depends on how the six RGB data bits
|
||||||
|
|
@ -54,27 +72,42 @@ static void blast_byte(Protomatter_core *core, uint8_t *data);
|
||||||
static void blast_word(Protomatter_core *core, uint16_t *data);
|
static void blast_word(Protomatter_core *core, uint16_t *data);
|
||||||
static void blast_long(Protomatter_core *core, uint32_t *data);
|
static void blast_long(Protomatter_core *core, uint32_t *data);
|
||||||
|
|
||||||
|
#define _PM_clearReg(x) \
|
||||||
|
(*(volatile _PM_PORT_TYPE *)((x).clearReg) = \
|
||||||
|
((x).bit)) ///< Clear non-RGB-data-or-clock control line (_PM_pin type)
|
||||||
|
#define _PM_setReg(x) \
|
||||||
|
(*(volatile _PM_PORT_TYPE *)((x).setReg) = \
|
||||||
|
((x).bit)) ///< Set non-RGB-data-or-clock control line (_PM_pin type)
|
||||||
|
|
||||||
// Validate and populate vital elements of core structure.
|
// Validate and populate vital elements of core structure.
|
||||||
// Does NOT allocate core struct -- calling function must provide that.
|
// Does NOT allocate core struct -- calling function must provide that.
|
||||||
// (In the Arduino C++ library, it’s part of the Protomatter class.)
|
// (In the Arduino C++ library, it’s part of the Protomatter class.)
|
||||||
ProtomatterStatus _PM_init(Protomatter_core *core,
|
ProtomatterStatus _PM_init(Protomatter_core *core, uint16_t bitWidth,
|
||||||
uint16_t bitWidth, uint8_t bitDepth,
|
uint8_t bitDepth, uint8_t rgbCount, uint8_t *rgbList,
|
||||||
uint8_t rgbCount, uint8_t *rgbList,
|
|
||||||
uint8_t addrCount, uint8_t *addrList,
|
uint8_t addrCount, uint8_t *addrList,
|
||||||
uint8_t clockPin, uint8_t latchPin, uint8_t oePin,
|
uint8_t clockPin, uint8_t latchPin, uint8_t oePin,
|
||||||
bool doubleBuffer, void *timer) {
|
bool doubleBuffer, void *timer) {
|
||||||
if(!core) return PROTOMATTER_ERR_ARG;
|
if (!core)
|
||||||
|
return PROTOMATTER_ERR_ARG;
|
||||||
|
|
||||||
if(rgbCount > 5) rgbCount = 5; // Max 5 in parallel (32-bit PORT)
|
if (rgbCount > 5)
|
||||||
if(addrCount > 5) addrCount = 5; // Max 5 address lines (A-E)
|
rgbCount = 5; // Max 5 in parallel (32-bit PORT)
|
||||||
|
if (addrCount > 5)
|
||||||
|
addrCount = 5; // Max 5 address lines (A-E)
|
||||||
// bitDepth is NOT constrained here, handle in calling function
|
// bitDepth is NOT constrained here, handle in calling function
|
||||||
// (varies with implementation, e.g. GFX lib is max 6 bitplanes,
|
// (varies with implementation, e.g. GFX lib is max 6 bitplanes,
|
||||||
// but might be more or less elsewhere)
|
// but might be more or less elsewhere)
|
||||||
|
|
||||||
|
#if defined(_PM_TIMER_DEFAULT)
|
||||||
// If NULL timer was passed in (the default case for the constructor),
|
// If NULL timer was passed in (the default case for the constructor),
|
||||||
// use default value from arch.h. For example, in the Arduino case it's
|
// use default value from arch.h. For example, in the Arduino case it's
|
||||||
// tied to TC4 specifically.
|
// tied to TC4 specifically.
|
||||||
if(timer == NULL) timer = _PM_TIMER_DEFAULT;
|
if (timer == NULL)
|
||||||
|
timer = _PM_TIMER_DEFAULT;
|
||||||
|
#else
|
||||||
|
if (timer == NULL)
|
||||||
|
return PROTOMATTER_ERR_ARG;
|
||||||
|
#endif
|
||||||
|
|
||||||
core->timer = timer;
|
core->timer = timer;
|
||||||
core->width = bitWidth; // Total matrix chain length in bits
|
core->width = bitWidth; // Total matrix chain length in bits
|
||||||
|
|
@ -95,15 +128,15 @@ ProtomatterStatus _PM_init(Protomatter_core *core,
|
||||||
// the pin bitmasks.
|
// the pin bitmasks.
|
||||||
|
|
||||||
rgbCount *= 6; // Convert parallel count to pin count
|
rgbCount *= 6; // Convert parallel count to pin count
|
||||||
if((core->rgbPins = (uint8_t *)malloc(rgbCount * sizeof(uint8_t)))) {
|
if ((core->rgbPins = (uint8_t *)_PM_ALLOCATOR(rgbCount * sizeof(uint8_t)))) {
|
||||||
if((core->addr = (_PM_pin *)malloc(addrCount * sizeof(_PM_pin)))) {
|
if ((core->addr = (_PM_pin *)_PM_ALLOCATOR(addrCount * sizeof(_PM_pin)))) {
|
||||||
memcpy(core->rgbPins, rgbList, rgbCount * sizeof(uint8_t));
|
memcpy(core->rgbPins, rgbList, rgbCount * sizeof(uint8_t));
|
||||||
for (uint8_t i = 0; i < addrCount; i++) {
|
for (uint8_t i = 0; i < addrCount; i++) {
|
||||||
core->addr[i].pin = addrList[i];
|
core->addr[i].pin = addrList[i];
|
||||||
}
|
}
|
||||||
return PROTOMATTER_OK;
|
return PROTOMATTER_OK;
|
||||||
}
|
}
|
||||||
free(core->rgbPins);
|
_PM_FREE(core->rgbPins);
|
||||||
core->rgbPins = NULL;
|
core->rgbPins = NULL;
|
||||||
}
|
}
|
||||||
return PROTOMATTER_ERR_MALLOC;
|
return PROTOMATTER_ERR_MALLOC;
|
||||||
|
|
@ -111,7 +144,8 @@ ProtomatterStatus _PM_init(Protomatter_core *core,
|
||||||
|
|
||||||
// Allocate display buffers and populate additional elements.
|
// Allocate display buffers and populate additional elements.
|
||||||
ProtomatterStatus _PM_begin(Protomatter_core *core) {
|
ProtomatterStatus _PM_begin(Protomatter_core *core) {
|
||||||
if(!core) return PROTOMATTER_ERR_ARG;
|
if (!core)
|
||||||
|
return PROTOMATTER_ERR_ARG;
|
||||||
|
|
||||||
if (!core->rgbPins) { // NULL if copy failed to allocate
|
if (!core->rgbPins) { // NULL if copy failed to allocate
|
||||||
return PROTOMATTER_ERR_MALLOC;
|
return PROTOMATTER_ERR_MALLOC;
|
||||||
|
|
@ -148,10 +182,14 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
|
||||||
// register is present) are in the same byte, this can be stored more
|
// register is present) are in the same byte, this can be stored more
|
||||||
// compact than if they're spread across a word or long.
|
// compact than if they're spread across a word or long.
|
||||||
uint8_t byteMask = 0;
|
uint8_t byteMask = 0;
|
||||||
if(bitMask & 0xFF000000) byteMask |= 0b1000;
|
if (bitMask & 0xFF000000)
|
||||||
if(bitMask & 0x00FF0000) byteMask |= 0b0100;
|
byteMask |= 0b1000;
|
||||||
if(bitMask & 0x0000FF00) byteMask |= 0b0010;
|
if (bitMask & 0x00FF0000)
|
||||||
if(bitMask & 0x000000FF) byteMask |= 0b0001;
|
byteMask |= 0b0100;
|
||||||
|
if (bitMask & 0x0000FF00)
|
||||||
|
byteMask |= 0b0010;
|
||||||
|
if (bitMask & 0x000000FF)
|
||||||
|
byteMask |= 0b0001;
|
||||||
switch (byteMask) {
|
switch (byteMask) {
|
||||||
case 0b0001: // If all PORT bits are in the same byte...
|
case 0b0001: // If all PORT bits are in the same byte...
|
||||||
case 0b0010:
|
case 0b0010:
|
||||||
|
|
@ -175,17 +213,19 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
|
||||||
core->numRowPairs = 1 << core->numAddressLines;
|
core->numRowPairs = 1 << core->numAddressLines;
|
||||||
uint8_t chunks = (core->width + (_PM_chunkSize - 1)) / _PM_chunkSize;
|
uint8_t chunks = (core->width + (_PM_chunkSize - 1)) / _PM_chunkSize;
|
||||||
uint16_t columns = chunks * _PM_chunkSize; // Padded matrix width
|
uint16_t columns = chunks * _PM_chunkSize; // Padded matrix width
|
||||||
uint32_t screenBytes = columns * core->numRowPairs * core->numPlanes *
|
uint32_t screenBytes =
|
||||||
core->bytesPerElement;
|
columns * core->numRowPairs * core->numPlanes * core->bytesPerElement;
|
||||||
|
|
||||||
core->bufferSize = screenBytes; // Bytes per matrix buffer (1 or 2)
|
core->bufferSize = screenBytes; // Bytes per matrix buffer (1 or 2)
|
||||||
if(core->doubleBuffer) screenBytes *= 2; // Total for matrix buffer(s)
|
if (core->doubleBuffer)
|
||||||
|
screenBytes *= 2; // Total for matrix buffer(s)
|
||||||
uint32_t rgbMaskBytes = core->parallel * 6 * core->bytesPerElement;
|
uint32_t rgbMaskBytes = core->parallel * 6 * core->bytesPerElement;
|
||||||
|
|
||||||
// Allocate matrix buffer(s). Don't worry about the return type...
|
// Allocate matrix buffer(s). Don't worry about the return type...
|
||||||
// though we might be using words or longs for certain pin configs,
|
// though we might be using words or longs for certain pin configs,
|
||||||
// malloc() by definition always aligns to the longest type.
|
// _PM_ALLOCATOR() by definition always aligns to the longest type.
|
||||||
if(!(core->screenData = (uint8_t *)malloc(screenBytes + rgbMaskBytes))) {
|
if (!(core->screenData =
|
||||||
|
(uint8_t *)_PM_ALLOCATOR(screenBytes + rgbMaskBytes))) {
|
||||||
return PROTOMATTER_ERR_MALLOC;
|
return PROTOMATTER_ERR_MALLOC;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -203,10 +243,9 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
|
||||||
core->portOffset = _PM_byteOffset(core->rgbPins[0]);
|
core->portOffset = _PM_byteOffset(core->rgbPins[0]);
|
||||||
#if defined(_PM_portToggleRegister)
|
#if defined(_PM_portToggleRegister)
|
||||||
// Clock and rgbAndClockMask are 8-bit values
|
// Clock and rgbAndClockMask are 8-bit values
|
||||||
core->clockMask = _PM_portBitMask(core->clockPin) >>
|
core->clockMask = _PM_portBitMask(core->clockPin) >> (core->portOffset * 8);
|
||||||
(core->portOffset * 8);
|
core->rgbAndClockMask =
|
||||||
core->rgbAndClockMask = (bitMask >> (core->portOffset * 8)) |
|
(bitMask >> (core->portOffset * 8)) | core->clockMask;
|
||||||
core->clockMask;
|
|
||||||
memset(core->screenData, core->clockMask, screenBytes);
|
memset(core->screenData, core->clockMask, screenBytes);
|
||||||
#else
|
#else
|
||||||
// Clock and rgbAndClockMask are 32-bit values
|
// Clock and rgbAndClockMask are 32-bit values
|
||||||
|
|
@ -221,10 +260,10 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
|
||||||
core->portOffset = _PM_wordOffset(core->rgbPins[0]);
|
core->portOffset = _PM_wordOffset(core->rgbPins[0]);
|
||||||
#if defined(_PM_portToggleRegister)
|
#if defined(_PM_portToggleRegister)
|
||||||
// Clock and rgbAndClockMask are 16-bit values
|
// Clock and rgbAndClockMask are 16-bit values
|
||||||
core->clockMask = _PM_portBitMask(core->clockPin) >>
|
core->clockMask =
|
||||||
(core->portOffset * 16);
|
_PM_portBitMask(core->clockPin) >> (core->portOffset * 16);
|
||||||
core->rgbAndClockMask = (bitMask >> (core->portOffset * 16)) |
|
core->rgbAndClockMask =
|
||||||
core->clockMask;
|
(bitMask >> (core->portOffset * 16)) | core->clockMask;
|
||||||
uint32_t elements = screenBytes / 2;
|
uint32_t elements = screenBytes / 2;
|
||||||
for (uint32_t i = 0; i < elements; i++) {
|
for (uint32_t i = 0; i < elements; i++) {
|
||||||
((uint16_t *)core->screenData)[i] = core->clockMask;
|
((uint16_t *)core->screenData)[i] = core->clockMask;
|
||||||
|
|
@ -296,13 +335,11 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
|
||||||
core->addrPortToggle = _PM_portToggleRegister(core->addr[0].pin);
|
core->addrPortToggle = _PM_portToggleRegister(core->addr[0].pin);
|
||||||
core->singleAddrPort = 1;
|
core->singleAddrPort = 1;
|
||||||
#endif
|
#endif
|
||||||
for(uint8_t line=0,bit=1; line<core->numAddressLines; line++, bit<<=1) {
|
for (uint8_t line = 0, bit = 1; line < core->numAddressLines;
|
||||||
core->addr[line].setReg =
|
line++, bit <<= 1) {
|
||||||
_PM_portSetRegister(core->addr[line].pin);
|
core->addr[line].setReg = _PM_portSetRegister(core->addr[line].pin);
|
||||||
core->addr[line].clearReg =
|
core->addr[line].clearReg = _PM_portClearRegister(core->addr[line].pin);
|
||||||
_PM_portClearRegister(core->addr[line].pin);
|
core->addr[line].bit = _PM_portBitMask(core->addr[line].pin);
|
||||||
core->addr[line].bit =
|
|
||||||
_PM_portBitMask(core->addr[line].pin);
|
|
||||||
_PM_pinOutput(core->addr[line].pin);
|
_PM_pinOutput(core->addr[line].pin);
|
||||||
if (core->prevRow & bit) {
|
if (core->prevRow & bit) {
|
||||||
_PM_pinHigh(core->addr[line].pin);
|
_PM_pinHigh(core->addr[line].pin);
|
||||||
|
|
@ -311,8 +348,7 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
|
||||||
}
|
}
|
||||||
#if defined(_PM_portToggleRegister)
|
#if defined(_PM_portToggleRegister)
|
||||||
// If address pin on different port than addr 0, no singleAddrPort.
|
// If address pin on different port than addr 0, no singleAddrPort.
|
||||||
if(_PM_portToggleRegister(core->addr[line].pin) !=
|
if (_PM_portToggleRegister(core->addr[line].pin) != core->addrPortToggle) {
|
||||||
core->addrPortToggle) {
|
|
||||||
core->singleAddrPort = 0;
|
core->singleAddrPort = 0;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -336,9 +372,10 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
|
||||||
// so it won't halt with lit LEDs.
|
// so it won't halt with lit LEDs.
|
||||||
void _PM_stop(Protomatter_core *core) {
|
void _PM_stop(Protomatter_core *core) {
|
||||||
if ((core)) {
|
if ((core)) {
|
||||||
while(core->swapBuffers); // Wait for any pending buffer swap
|
while (core->swapBuffers)
|
||||||
|
; // Wait for any pending buffer swap
|
||||||
_PM_timerStop(core->timer); // Halt timer
|
_PM_timerStop(core->timer); // Halt timer
|
||||||
*core->oe.setReg = core->oe.bit; // Set OE HIGH (disable output)
|
_PM_setReg(core->oe); // Set OE HIGH (disable output)
|
||||||
// So, in PRINCIPLE, setting OE high would be sufficient...
|
// So, in PRINCIPLE, setting OE high would be sufficient...
|
||||||
// but in case that pin is shared with another function such
|
// but in case that pin is shared with another function such
|
||||||
// as the onloard LED (which pulses during bootloading) let's
|
// as the onloard LED (which pulses during bootloading) let's
|
||||||
|
|
@ -355,8 +392,8 @@ void _PM_stop(Protomatter_core *core) {
|
||||||
_PM_clockHoldLow;
|
_PM_clockHoldLow;
|
||||||
}
|
}
|
||||||
// Latch data
|
// Latch data
|
||||||
*core->latch.setReg = core->latch.bit;
|
_PM_setReg(core->latch);
|
||||||
*core->latch.clearReg = core->latch.bit;
|
_PM_clearReg(core->latch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -379,26 +416,27 @@ void _PM_free(Protomatter_core *core) {
|
||||||
if ((core)) {
|
if ((core)) {
|
||||||
_PM_stop(core);
|
_PM_stop(core);
|
||||||
// TO DO: Set all pins back to inputs here?
|
// TO DO: Set all pins back to inputs here?
|
||||||
if(core->screenData) free(core->screenData);
|
if (core->screenData)
|
||||||
if(core->addr) free(core->addr);
|
_PM_FREE(core->screenData);
|
||||||
|
if (core->addr)
|
||||||
|
_PM_FREE(core->addr);
|
||||||
if (core->rgbPins) {
|
if (core->rgbPins) {
|
||||||
free(core->rgbPins);
|
_PM_FREE(core->rgbPins);
|
||||||
core->rgbPins = NULL;
|
core->rgbPins = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ISR function (in arch.h) calls this function which it extern'd.
|
// ISR function (in arch.h) calls this function which it extern'd.
|
||||||
void _PM_row_handler(Protomatter_core *core) {
|
void _PM_row_handler(Protomatter_core *core) {
|
||||||
|
|
||||||
*core->oe.setReg = core->oe.bit; // Disable LED output
|
_PM_setReg(core->oe); // Disable LED output
|
||||||
|
|
||||||
*core->latch.setReg = core->latch.bit; // Latch data from PRIOR pass
|
_PM_setReg(core->latch);
|
||||||
// Stop timer, save count value at stop
|
// Stop timer, save count value at stop
|
||||||
uint32_t elapsed = _PM_timerStop(core->timer);
|
uint32_t elapsed = _PM_timerStop(core->timer);
|
||||||
uint8_t prevPlane = core->plane; // Save that plane # for later timing
|
uint8_t prevPlane = core->plane; // Save that plane # for later timing
|
||||||
*core->latch.clearReg = core->latch.bit; // (split to add a few cycles)
|
_PM_clearReg(core->latch); // (split to add a few cycles)
|
||||||
|
|
||||||
// If plane 0 just finished being displayed (plane 1 was loaded on prior
|
// If plane 0 just finished being displayed (plane 1 was loaded on prior
|
||||||
// pass, or there's only one plane...I know, it's confusing), take note
|
// pass, or there's only one plane...I know, it's confusing), take note
|
||||||
|
|
@ -430,7 +468,7 @@ void _PM_row_handler(Protomatter_core *core) {
|
||||||
priorBits |= core->addr[line].bit;
|
priorBits |= core->addr[line].bit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*core->addrPortToggle = newBits ^ priorBits;
|
*(volatile _PM_PORT_TYPE *)core->addrPortToggle = newBits ^ priorBits;
|
||||||
_PM_delayMicroseconds(_PM_ROW_DELAY);
|
_PM_delayMicroseconds(_PM_ROW_DELAY);
|
||||||
} else {
|
} else {
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -440,9 +478,9 @@ void _PM_row_handler(Protomatter_core *core) {
|
||||||
line++, bit <<= 1) {
|
line++, bit <<= 1) {
|
||||||
if ((core->row & bit) != (core->prevRow & bit)) {
|
if ((core->row & bit) != (core->prevRow & bit)) {
|
||||||
if (core->row & bit) { // Set addr line high
|
if (core->row & bit) { // Set addr line high
|
||||||
*core->addr[line].setReg = core->addr[line].bit;
|
_PM_setReg(core->addr[line]);
|
||||||
} else { // Set addr line low
|
} else { // Set addr line low
|
||||||
*core->addr[line].clearReg = core->addr[line].bit;
|
_PM_clearReg(core->addr[line]);
|
||||||
}
|
}
|
||||||
_PM_delayMicroseconds(_PM_ROW_DELAY);
|
_PM_delayMicroseconds(_PM_ROW_DELAY);
|
||||||
}
|
}
|
||||||
|
|
@ -473,12 +511,13 @@ void _PM_row_handler(Protomatter_core *core) {
|
||||||
|
|
||||||
// Set timer and enable LED output for data loaded on PRIOR pass:
|
// Set timer and enable LED output for data loaded on PRIOR pass:
|
||||||
_PM_timerStart(core->timer, core->bitZeroPeriod << prevPlane);
|
_PM_timerStart(core->timer, core->bitZeroPeriod << prevPlane);
|
||||||
*core->oe.clearReg = core->oe.bit; // Enable LED output
|
_PM_clearReg(core->oe); // Enable LED output
|
||||||
|
|
||||||
uint32_t elementsPerLine = _PM_chunkSize *
|
uint32_t elementsPerLine =
|
||||||
((core->width + (_PM_chunkSize - 1)) / _PM_chunkSize);
|
_PM_chunkSize * ((core->width + (_PM_chunkSize - 1)) / _PM_chunkSize);
|
||||||
uint32_t srcOffset = elementsPerLine *
|
uint32_t srcOffset = elementsPerLine *
|
||||||
(core->numPlanes * core->row + core->plane) * core->bytesPerElement;
|
(core->numPlanes * core->row + core->plane) *
|
||||||
|
core->bytesPerElement;
|
||||||
if (core->doubleBuffer) {
|
if (core->doubleBuffer) {
|
||||||
srcOffset += core->bufferSize * core->activeBuffer;
|
srcOffset += core->bufferSize * core->activeBuffer;
|
||||||
}
|
}
|
||||||
|
|
@ -514,28 +553,29 @@ void _PM_row_handler(Protomatter_core *core) {
|
||||||
#define PEW \
|
#define PEW \
|
||||||
*set = *data++; /* Set RGB data high */ \
|
*set = *data++; /* Set RGB data high */ \
|
||||||
_PM_clockHoldLow; \
|
_PM_clockHoldLow; \
|
||||||
*set32 = clock; /* Set clock high */ \
|
*set_full = clock; /* Set clock high */ \
|
||||||
_PM_clockHoldHigh; \
|
_PM_clockHoldHigh; \
|
||||||
*clear32 = rgbclock; /* Clear RGB data + clock */
|
*clear_full = rgbclock; \
|
||||||
|
/* Clear RGB data + clock */ ///< Bitbang one set of RGB data bits to matrix
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if _PM_chunkSize == 1
|
#if _PM_chunkSize == 1
|
||||||
#define PEW_UNROLL PEW
|
#define PEW_UNROLL PEW
|
||||||
#elif _PM_chunkSize == 8
|
#elif _PM_chunkSize == 8
|
||||||
#define PEW_UNROLL PEW PEW PEW PEW PEW PEW PEW PEW
|
#define PEW_UNROLL PEW PEW PEW PEW PEW PEW PEW PEW ///< 8-way PEW unroll
|
||||||
#elif _PM_chunkSize == 16
|
#elif _PM_chunkSize == 16
|
||||||
#define PEW_UNROLL \
|
#define PEW_UNROLL \
|
||||||
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW
|
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW
|
||||||
#elif _PM_chunkSize == 32
|
#elif _PM_chunkSize == 32
|
||||||
#define PEW_UNROLL \
|
#define PEW_UNROLL \
|
||||||
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW \
|
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW \
|
||||||
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW
|
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW
|
||||||
#elif _PM_chunkSize == 64
|
#elif _PM_chunkSize == 64
|
||||||
#define PEW_UNROLL \
|
#define PEW_UNROLL \
|
||||||
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW \
|
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW \
|
||||||
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW \
|
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW \
|
||||||
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW \
|
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW \
|
||||||
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW
|
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW
|
||||||
#else
|
#else
|
||||||
#error "Unimplemented _PM_chunkSize value"
|
#error "Unimplemented _PM_chunkSize value"
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -552,20 +592,20 @@ static void blast_byte(Protomatter_core *core, uint8_t *data) {
|
||||||
// clock are all within the same byte of a PORT register, else we'd be
|
// 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
|
// in the word- or long-blasting functions now. So we just need an
|
||||||
// 8-bit pointer to the PORT.
|
// 8-bit pointer to the PORT.
|
||||||
volatile uint8_t *toggle = (volatile uint8_t *)core->toggleReg +
|
volatile uint8_t *toggle =
|
||||||
core->portOffset;
|
(volatile uint8_t *)core->toggleReg + core->portOffset;
|
||||||
#else
|
#else
|
||||||
// No-toggle version is a little different. If here, RGB data is all
|
// No-toggle version is a little different. If here, RGB data is all
|
||||||
// in one byte of PORT register, clock can be any bit in 32-bit PORT.
|
// in one byte of PORT register, clock can be any bit in 32-bit PORT.
|
||||||
volatile uint8_t *set; // For RGB data set
|
volatile uint8_t *set; // For RGB data set
|
||||||
volatile uint32_t *set32; // For clock set
|
volatile _PM_PORT_TYPE *set_full; // For clock set
|
||||||
volatile uint32_t *clear32; // For RGB data + clock clear
|
volatile _PM_PORT_TYPE *clear_full; // For RGB data + clock clear
|
||||||
set = (volatile uint8_t *)core->setReg + portOffset;
|
set = (volatile uint8_t *)core->setReg + core->portOffset;
|
||||||
set32 = (volatile uint32_t *)core->setReg;
|
set_full = (volatile _PM_PORT_TYPE *)core->setReg;
|
||||||
clear32 = (volatile uint32_t *)core->clearReg;
|
clear_full = (volatile _PM_PORT_TYPE *)core->clearReg;
|
||||||
uint32_t rgbclock = core->rgbAndClockMask; // RGB + clock bit
|
_PM_PORT_TYPE rgbclock = core->rgbAndClockMask; // RGB + clock bit
|
||||||
#endif
|
#endif
|
||||||
uint32_t clock = core->clockMask; // Clock bit
|
_PM_PORT_TYPE clock = core->clockMask; // Clock bit
|
||||||
uint8_t chunks = (core->width + (_PM_chunkSize - 1)) / _PM_chunkSize;
|
uint8_t chunks = (core->width + (_PM_chunkSize - 1)) / _PM_chunkSize;
|
||||||
|
|
||||||
// PORT has already been initialized with RGB data + clock bits
|
// PORT has already been initialized with RGB data + clock bits
|
||||||
|
|
@ -589,18 +629,18 @@ static void blast_byte(Protomatter_core *core, uint8_t *data) {
|
||||||
static void blast_word(Protomatter_core *core, uint16_t *data) {
|
static void blast_word(Protomatter_core *core, uint16_t *data) {
|
||||||
#if defined(_PM_portToggleRegister)
|
#if defined(_PM_portToggleRegister)
|
||||||
// See notes above -- except now 16-bit word in PORT.
|
// See notes above -- except now 16-bit word in PORT.
|
||||||
volatile uint16_t *toggle = (volatile uint16_t *)core->toggleReg +
|
volatile uint16_t *toggle =
|
||||||
core->portOffset;
|
(volatile uint16_t *)core->toggleReg + core->portOffset;
|
||||||
#else
|
#else
|
||||||
volatile uint16_t *set; // For RGB data set
|
volatile uint16_t *set; // For RGB data set
|
||||||
volatile uint32_t *set32; // For clock set
|
volatile _PM_PORT_TYPE *set_full; // For clock set
|
||||||
volatile uint32_t *clear32; // For RGB data + clock clear
|
volatile _PM_PORT_TYPE *clear_full; // For RGB data + clock clear
|
||||||
set = (volatile uint16_t *)core->setReg + core->portOffset;
|
set = (volatile uint16_t *)core->setReg + core->portOffset;
|
||||||
set32 = (volatile uint32_t *)core->setReg;
|
set_full = (volatile _PM_PORT_TYPE *)core->setReg;
|
||||||
clear32 = (volatile uint32_t *)core->clearReg;
|
clear_full = (volatile _PM_PORT_TYPE *)core->clearReg;
|
||||||
uint32_t rgbclock = core->rgbAndClockMask; // RGB + clock bit
|
_PM_PORT_TYPE rgbclock = core->rgbAndClockMask; // RGB + clock bit
|
||||||
#endif
|
#endif
|
||||||
uint32_t clock = core->clockMask; // Clock bit
|
_PM_PORT_TYPE clock = core->clockMask; // Clock bit
|
||||||
uint8_t chunks = (core->width + (_PM_chunkSize - 1)) / _PM_chunkSize;
|
uint8_t chunks = (core->width + (_PM_chunkSize - 1)) / _PM_chunkSize;
|
||||||
while (chunks--) {
|
while (chunks--) {
|
||||||
PEW_UNROLL // _PM_chunkSize RGB+clock writes
|
PEW_UNROLL // _PM_chunkSize RGB+clock writes
|
||||||
|
|
@ -621,14 +661,14 @@ static void blast_long(Protomatter_core *core, uint32_t *data) {
|
||||||
// The optimizer will most likely simplify this; leaving as-is, not
|
// The optimizer will most likely simplify this; leaving as-is, not
|
||||||
// wanting a special case of the PEW macro due to divergence risk.
|
// wanting a special case of the PEW macro due to divergence risk.
|
||||||
volatile uint32_t *set; // For RGB data set
|
volatile uint32_t *set; // For RGB data set
|
||||||
volatile uint32_t *set32; // For clock set
|
volatile _PM_PORT_TYPE *set_full; // For clock set
|
||||||
volatile uint32_t *clear32; // For RGB data + clock clear
|
volatile _PM_PORT_TYPE *clear_full; // For RGB data + clock clear
|
||||||
set = (volatile uint32_t *)core->setReg;
|
set = (volatile uint32_t *)core->setReg;
|
||||||
set32 = (volatile uint32_t *)core->setReg;
|
set_full = (volatile _PM_PORT_TYPE *)core->setReg;
|
||||||
clear32 = (volatile uint32_t *)core->clearReg;
|
clear_full = (volatile _PM_PORT_TYPE *)core->clearReg;
|
||||||
uint32_t rgbclock = core->rgbAndClockMask; // RGB + clock bit
|
_PM_PORT_TYPE rgbclock = core->rgbAndClockMask; // RGB + clock bit
|
||||||
#endif
|
#endif
|
||||||
uint32_t clock = core->clockMask; // Clock bit
|
_PM_PORT_TYPE clock = core->clockMask; // Clock bit
|
||||||
uint8_t chunks = (core->width + (_PM_chunkSize - 1)) / _PM_chunkSize;
|
uint8_t chunks = (core->width + (_PM_chunkSize - 1)) / _PM_chunkSize;
|
||||||
while (chunks--) {
|
while (chunks--) {
|
||||||
PEW_UNROLL // _PM_chunkSize RGB+clock writes
|
PEW_UNROLL // _PM_chunkSize RGB+clock writes
|
||||||
|
|
|
||||||
287
core.h
287
core.h
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*!
|
||||||
|
* @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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
#ifndef _PROTOMATTER_CORE_H_
|
#ifndef _PROTOMATTER_CORE_H_
|
||||||
#define _PROTOMATTER_CORE_H_
|
#define _PROTOMATTER_CORE_H_
|
||||||
|
|
||||||
|
|
@ -5,10 +21,10 @@
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
// Status type returned by some functions.
|
/** Status type returned by some functions. */
|
||||||
typedef enum {
|
typedef enum {
|
||||||
PROTOMATTER_OK, // Everything is hunky-dory!
|
PROTOMATTER_OK, // Everything is hunky-dory!
|
||||||
PROTOMATTER_ERR_PINS, // Clock and/or data pins on different PORTs
|
PROTOMATTER_ERR_PINS, // Clock and/or data pins on different PORTs
|
||||||
|
|
@ -16,92 +32,229 @@ typedef enum {
|
||||||
PROTOMATTER_ERR_ARG, // Bad input to function
|
PROTOMATTER_ERR_ARG, // Bad input to function
|
||||||
} ProtomatterStatus;
|
} ProtomatterStatus;
|
||||||
|
|
||||||
// Struct for matrix control lines NOT related to RGB data or clock, i.e.
|
/** 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
|
latch, OE and address lines. RGB data and clock ("RGBC") are handled
|
||||||
// differently as they have specific requirements (and might use a toggle
|
differently as they have specific requirements (and might use a toggle
|
||||||
// register if present). The data conversion functions need bitmasks for
|
register if present). The data conversion functions need bitmasks for
|
||||||
// RGB data but do NOT need the set or clear registers, so those items
|
RGB data but do NOT need the set or clear registers, so those items are
|
||||||
// are also declared as separate things in the core structure that follows.
|
also declared as separate things in the core structure that follows. */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
volatile uint32_t *setReg; // GPIO bit set register
|
volatile void *setReg; ///< GPIO bit set register
|
||||||
volatile uint32_t *clearReg; // GPIO bit clear register
|
volatile void *clearReg; ///< GPIO bit clear register
|
||||||
uint32_t bit; // GPIO bitmask
|
uint32_t bit; ///< GPIO bitmask
|
||||||
uint8_t pin; // Some identifier, e.g. Arduino pin #
|
uint8_t pin; ///< Some unique ID, e.g. Arduino pin #
|
||||||
} _PM_pin;
|
} _PM_pin;
|
||||||
|
|
||||||
// Struct with info about an RGB matrix chain and lots of state and buffer
|
/** 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
|
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
|
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
|
do an ifdef check around these). All hardware-specific details (including
|
||||||
// the presence or lack of toggle registers) are isolated to a single
|
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
|
file -- arch.h -- which should ONLY be included by core.c, and ifdef'ing
|
||||||
// them would result in differing representations of this structure which
|
them would result in differing representations of this structure which
|
||||||
// must be shared between the library and calling code. (An alternative is
|
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
|
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.)
|
check, but that's just dirty pool and asking for trouble.) */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
void *timer; // Arch-specific timer/counter info
|
void *timer; ///< Arch-specific timer/counter info
|
||||||
void *setReg; // RGBC bit set register (cast to use)
|
void *setReg; ///< RGBC bit set register (cast to use)
|
||||||
void *clearReg; // RGBC bit clear register "
|
void *clearReg; ///< RGBC bit clear register "
|
||||||
void *toggleReg; // RGBC bit toggle register "
|
void *toggleReg; ///< RGBC bit toggle register "
|
||||||
uint8_t *rgbPins; // Array of RGB data pins (mult of 6)
|
uint8_t *rgbPins; ///< Array of RGB data pins (mult of 6)
|
||||||
void *rgbMask; // PORT bit mask for each RGB pin
|
void *rgbMask; ///< PORT bit mask for each RGB pin
|
||||||
uint32_t clockMask; // PORT bit mask for RGB clock
|
uint32_t clockMask; ///< PORT bit mask for RGB clock
|
||||||
uint32_t rgbAndClockMask; // PORT bit mask for RGB data + clock
|
uint32_t rgbAndClockMask; ///< PORT bit mask for RGB data + clock
|
||||||
volatile uint32_t *addrPortToggle; // See singleAddrPort below
|
volatile void *addrPortToggle; ///< See singleAddrPort below
|
||||||
void *screenData; // Per-bitplane RGB data for matrix
|
void *screenData; ///< Per-bitplane RGB data for matrix
|
||||||
_PM_pin latch; // RGB data latch
|
_PM_pin latch; ///< RGB data latch
|
||||||
_PM_pin oe; // !OE (LOW out enable)
|
_PM_pin oe; ///< !OE (LOW out enable)
|
||||||
_PM_pin *addr; // Array of address pins
|
_PM_pin *addr; ///< Array of address pins
|
||||||
uint32_t bufferSize; // Bytes per matrix buffer
|
uint32_t bufferSize; ///< Bytes per matrix buffer
|
||||||
uint32_t bitZeroPeriod; // Bitplane 0 timer period
|
uint32_t bitZeroPeriod; ///< Bitplane 0 timer period
|
||||||
uint32_t minPeriod; // Plane 0 timer period for ~250Hz
|
uint32_t minPeriod; ///< Plane 0 timer period for ~250Hz
|
||||||
volatile uint32_t frameCount; // For estimating refresh rate
|
volatile uint32_t frameCount; ///< For estimating refresh rate
|
||||||
uint16_t width; // Matrix chain width in bits
|
uint16_t width; ///< Matrix chain width in bits
|
||||||
uint8_t bytesPerElement; // Using 8, 16 or 32 bits of PORT?
|
uint8_t bytesPerElement; ///< Using 8, 16 or 32 bits of PORT?
|
||||||
uint8_t clockPin; // RGB clock pin identifier
|
uint8_t clockPin; ///< RGB clock pin identifier
|
||||||
uint8_t parallel; // Number of concurrent matrix outs
|
uint8_t parallel; ///< Number of concurrent matrix outs
|
||||||
uint8_t numAddressLines; // Number of address line pins
|
uint8_t numAddressLines; ///< Number of address line pins
|
||||||
uint8_t portOffset; // Active 8- or 16-bit pos. in PORT
|
uint8_t portOffset; ///< Active 8- or 16-bit pos. in PORT
|
||||||
uint8_t numPlanes; // Display bitplanes (1 to 6)
|
uint8_t numPlanes; ///< Display bitplanes (1 to 6)
|
||||||
uint8_t numRowPairs; // Addressable row pairs
|
uint8_t numRowPairs; ///< Addressable row pairs
|
||||||
bool doubleBuffer; // 2X buffers for clean switchover
|
bool doubleBuffer; ///< 2X buffers for clean switchover
|
||||||
bool singleAddrPort; // If 1, all addr lines on same PORT
|
bool singleAddrPort; ///< If 1, all addr lines on same PORT
|
||||||
volatile uint8_t activeBuffer; // Index of currently-displayed buf
|
volatile uint8_t activeBuffer; ///< Index of currently-displayed buf
|
||||||
volatile uint8_t plane; // Current bitplane (changes in ISR)
|
volatile uint8_t plane; ///< Current bitplane (changes in ISR)
|
||||||
volatile uint8_t row; // Current scanline (changes in ISR)
|
volatile uint8_t row; ///< Current scanline (changes in ISR)
|
||||||
volatile uint8_t prevRow; // Scanline from prior ISR
|
volatile uint8_t prevRow; ///< Scanline from prior ISR
|
||||||
volatile bool swapBuffers; // If 1, awaiting double-buf switch
|
volatile bool swapBuffers; ///< If 1, awaiting double-buf switch
|
||||||
} Protomatter_core;
|
} Protomatter_core;
|
||||||
|
|
||||||
// Protomatter core function prototypes. Environment-specific code (like the
|
// Protomatter core function prototypes. Environment-specific code (like the
|
||||||
// Adafruit_Protomatter class for Arduino) calls on these underlying things,
|
// Adafruit_Protomatter class for Arduino) calls on these underlying things,
|
||||||
// and has to provide a few extras of its own (interrupt handlers and such).
|
// 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.
|
// User code shouldn't need to invoke any of them directly.
|
||||||
extern ProtomatterStatus _PM_init(Protomatter_core *core,
|
|
||||||
uint16_t bitWidth, uint8_t bitDepth,
|
/*!
|
||||||
uint8_t rgbCount, uint8_t *rgbList,
|
@brief Initialize values in Protomatter_core structure.
|
||||||
uint8_t addrCount, uint8_t *addrList,
|
@param core Pointer to Protomatter_core structure.
|
||||||
uint8_t clockPin, uint8_t latchPin, uint8_t oePin,
|
@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 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, void *timer);
|
bool doubleBuffer, 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);
|
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);
|
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);
|
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_free(Protomatter_core *core);
|
extern void _PM_free(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);
|
extern void _PM_row_handler(Protomatter_core *core);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Returns current value of frame counter and resets its value to
|
||||||
|
zero. Two calls to this, timed one second apart (or use math with
|
||||||
|
other intervals), can be used to get a rough frames-per-second
|
||||||
|
value for the matrix (since this is difficult to estimate
|
||||||
|
beforehand).
|
||||||
|
@param core Pointer to Protomatter_core structure.
|
||||||
|
@return Frame count since previous call to function, as a uint32_t.
|
||||||
|
*/
|
||||||
extern uint32_t _PM_getFrameCount(Protomatter_core *core);
|
extern uint32_t _PM_getFrameCount(Protomatter_core *core);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Start (or restart) a timer/counter peripheral.
|
||||||
|
@param tptr Pointer to timer/counter peripheral OR a struct
|
||||||
|
encapsulating information about a timer/counter
|
||||||
|
periph (architecture-dependent).
|
||||||
|
@param period Timer 'top' / rollover value.
|
||||||
|
*/
|
||||||
extern void _PM_timerStart(void *tptr, uint32_t period);
|
extern void _PM_timerStart(void *tptr, uint32_t period);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Stop timer/counter peripheral.
|
||||||
|
@param tptr Pointer to timer/counter peripheral OR a struct
|
||||||
|
encapsulating information about a timer/counter
|
||||||
|
periph (architecture-dependent).
|
||||||
|
@return Counter value when timer was stopped.
|
||||||
|
*/
|
||||||
extern uint32_t _PM_timerStop(void *tptr);
|
extern uint32_t _PM_timerStop(void *tptr);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Query a timer/counter peripheral's current count.
|
||||||
|
@param tptr Pointer to timer/counter peripheral OR a struct
|
||||||
|
encapsulating information about a timer/counter
|
||||||
|
periph (architecture-dependent).
|
||||||
|
@return Counter value.
|
||||||
|
*/
|
||||||
extern uint32_t _PM_timerGetCount(void *tptr);
|
extern uint32_t _PM_timerGetCount(void *tptr);
|
||||||
|
|
||||||
#if defined(ARDUINO)
|
/*!
|
||||||
extern void _PM_convert_565_byte(Protomatter_core *core,
|
@brief Converts image data from GFX16 canvas to the matrices weird
|
||||||
uint16_t *source, uint16_t width);
|
internal format.
|
||||||
extern void _PM_convert_565_word(Protomatter_core *core,
|
@param core Pointer to Protomatter_core structure.
|
||||||
uint16_t *source, uint16_t width);
|
@param source Pointer to source image data (see Adafruit_GFX 16-bit
|
||||||
extern void _PM_convert_565_long(Protomatter_core *core,
|
canvas type for format).
|
||||||
uint16_t *source, uint16_t width);
|
@param width Width of canvas in pixels, as this may be different than
|
||||||
#endif // ARDUINO
|
the matrix pixel width due to row padding.
|
||||||
|
*/
|
||||||
|
extern void _PM_convert_565(Protomatter_core *core, uint16_t *source,
|
||||||
|
uint16_t width);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@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);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,16 @@ PA05 A4 PA13 PA21 D7 PB05 PB13
|
||||||
PA06 PA14 PA22 SDA PB06 PB14
|
PA06 PA14 PA22 SDA PB06 PB14
|
||||||
PA07 D9 PA15 D5 PA23 SCL PB07 PB15
|
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
|
||||||
|
|
||||||
RGB Matrix FeatherWing:
|
RGB Matrix FeatherWing:
|
||||||
R1 D6 A A5
|
R1 D6 A A5
|
||||||
G1 D5 B A4
|
G1 D5 B A4
|
||||||
|
|
@ -55,6 +65,7 @@ the code could run there (with some work to be done in the convert_*
|
||||||
functions), but would be super RAM-inefficient. Should be fine on other
|
functions), but would be super RAM-inefficient. Should be fine on other
|
||||||
M0 devices like a Metro, if wiring manually so one can pick a contiguous
|
M0 devices like a Metro, if wiring manually so one can pick a contiguous
|
||||||
byte of PORT bits.
|
byte of PORT bits.
|
||||||
|
RGB+clock are on different PORTs on nRF52840.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#if defined(__SAMD51__)
|
#if defined(__SAMD51__)
|
||||||
|
|
@ -64,12 +75,18 @@ byte of PORT bits.
|
||||||
uint8_t clockPin = 13;
|
uint8_t clockPin = 13;
|
||||||
uint8_t latchPin = 0;
|
uint8_t latchPin = 0;
|
||||||
uint8_t oePin = 1;
|
uint8_t oePin = 1;
|
||||||
#else // SAMD21
|
#elif defined(_SAMD21_)
|
||||||
uint8_t rgbPins[] = {6, 7, 10, 11, 12, 13};
|
uint8_t rgbPins[] = {6, 7, 10, 11, 12, 13};
|
||||||
uint8_t addrPins[] = {0, 1, 2, 3};
|
uint8_t addrPins[] = {0, 1, 2, 3};
|
||||||
uint8_t clockPin = SDA;
|
uint8_t clockPin = SDA;
|
||||||
uint8_t latchPin = 4;
|
uint8_t latchPin = 4;
|
||||||
uint8_t oePin = 5;
|
uint8_t oePin = 5;
|
||||||
|
#elif defined(NRF52_SERIES)
|
||||||
|
uint8_t rgbPins[] = {6, 11, A0, A1, A4, A5};
|
||||||
|
uint8_t addrPins[] = {5, 9, 10, 13};
|
||||||
|
uint8_t clockPin = 12;
|
||||||
|
uint8_t latchPin = A2;
|
||||||
|
uint8_t oePin = A3;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Last arg here enables double-buffering
|
// Last arg here enables double-buffering
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,16 @@ PA05 A4 PA13 PA21 D7 PB05 PB13
|
||||||
PA06 PA14 PA22 SDA PB06 PB14
|
PA06 PA14 PA22 SDA PB06 PB14
|
||||||
PA07 D9 PA15 D5 PA23 SCL PB07 PB15
|
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
|
||||||
|
|
||||||
RGB Matrix FeatherWing:
|
RGB Matrix FeatherWing:
|
||||||
R1 D6 A A5
|
R1 D6 A A5
|
||||||
G1 D5 B A4
|
G1 D5 B A4
|
||||||
|
|
@ -55,6 +65,7 @@ the code could run there (with some work to be done in the convert_*
|
||||||
functions), but would be super RAM-inefficient. Should be fine on other
|
functions), but would be super RAM-inefficient. Should be fine on other
|
||||||
M0 devices like a Metro, if wiring manually so one can pick a contiguous
|
M0 devices like a Metro, if wiring manually so one can pick a contiguous
|
||||||
byte of PORT bits.
|
byte of PORT bits.
|
||||||
|
RGB+clock are on different PORTs on nRF52840.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#if defined(__SAMD51__)
|
#if defined(__SAMD51__)
|
||||||
|
|
@ -64,12 +75,18 @@ byte of PORT bits.
|
||||||
uint8_t clockPin = 13;
|
uint8_t clockPin = 13;
|
||||||
uint8_t latchPin = 0;
|
uint8_t latchPin = 0;
|
||||||
uint8_t oePin = 1;
|
uint8_t oePin = 1;
|
||||||
#else // SAMD21
|
#elif defined(_SAMD21_)
|
||||||
uint8_t rgbPins[] = {6, 7, 10, 11, 12, 13};
|
uint8_t rgbPins[] = {6, 7, 10, 11, 12, 13};
|
||||||
uint8_t addrPins[] = {0, 1, 2, 3};
|
uint8_t addrPins[] = {0, 1, 2, 3};
|
||||||
uint8_t clockPin = SDA;
|
uint8_t clockPin = SDA;
|
||||||
uint8_t latchPin = 4;
|
uint8_t latchPin = 4;
|
||||||
uint8_t oePin = 5;
|
uint8_t oePin = 5;
|
||||||
|
#elif defined(NRF52_SERIES)
|
||||||
|
uint8_t rgbPins[] = {6, 11, A0, A1, A4, A5};
|
||||||
|
uint8_t addrPins[] = {5, 9, 10, 13};
|
||||||
|
uint8_t clockPin = 12;
|
||||||
|
uint8_t latchPin = A2;
|
||||||
|
uint8_t oePin = A3;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Adafruit_Protomatter matrix(
|
Adafruit_Protomatter matrix(
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
name=Adafruit Protomatter
|
name=Adafruit Protomatter
|
||||||
version=0.0.0
|
version=1.0.0
|
||||||
author=Adafruit
|
author=Adafruit
|
||||||
maintainer=Adafruit <info@adafruit.com>
|
maintainer=Adafruit <info@adafruit.com>
|
||||||
sentence=This is a library for the Adafruit RGB LED matrix.
|
sentence=This is a library for the Adafruit RGB LED matrix.
|
||||||
paragraph=RGB LED matrix.
|
paragraph=RGB LED matrix.
|
||||||
category=Display
|
category=Display
|
||||||
url=https://github.com/adafruit/Adafruit_protomatter
|
url=https://github.com/adafruit/Adafruit_protomatter
|
||||||
architectures=*
|
architectures=samd,nrf52,stm32
|
||||||
depends=Adafruit GFX Library
|
depends=Adafruit GFX Library
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue