Compare commits

...

27 commits

Author SHA1 Message Date
3a817d6057 fix circuitpython build error
In a51676db5e ("clang-format the lot"), headers were re-ordered.
This is not a change that makes no difference, and caused CircuitPython
to stop building:

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

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

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

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

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

4
.gitignore vendored Normal file
View file

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

41
.travis.yml Normal file
View 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)

View file

@ -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
// constructor and so forth, builds on Adafruit_GFX). There should
// 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
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
// 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
// upper limit to avoid excessive CPU load). An incredibly long comment block
// for a single constant, thank you for coming to my TED talk!
#define _PM_MAX_REFRESH_HZ 250
#define _PM_MAX_REFRESH_HZ 250 ///< Upper limit (ish) to matrix refresh rate
// Time (in milliseconds) to pause following any change in address lines
// (individually or collectively). Some matrices respond slowly there...
// must pause on change for matrix to catch up. Defined here (rather than
// arch.h) because it's not architecture-specific.
#define _PM_ROW_DELAY 8
#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 addrCount, uint8_t *addrList,
uint8_t clockPin, uint8_t latchPin, uint8_t oePin,
bool doubleBuffer, void *timer) :
GFXcanvas16(bitWidth, (2 << min(addrCount, 5)) * min(rgbCount, 5)) {
if(bitDepth > 6) bitDepth = 6; // GFXcanvas16 color limit (565)
uint8_t clockPin, uint8_t latchPin,
uint8_t oePin, bool doubleBuffer,
void *timer)
: GFXcanvas16(bitWidth, (2 << min(addrCount, 5)) * min(rgbCount, 5)) {
if (bitDepth > 6)
bitDepth = 6; // GFXcanvas16 color limit (565)
// Arguments are passed through to the C _PM_init() function which does
// some input validation and minor allocation. Return value is ignored
// because we can't really do anything about it in a C++ constructor.
// The class begin() function checks rgbPins for NULL to determine
// whether to proceed or indicate an error.
(void)_PM_init(&core, bitWidth, bitDepth, rgbCount, rgbList,
addrCount, addrList, clockPin, latchPin, oePin, doubleBuffer, timer);
(void)_PM_init(&core, bitWidth, bitDepth, rgbCount, rgbList, addrCount,
addrList, clockPin, latchPin, oePin, doubleBuffer, timer);
}
Adafruit_Protomatter::~Adafruit_Protomatter(void) {
@ -55,33 +90,15 @@ Adafruit_Protomatter::~Adafruit_Protomatter(void) {
ProtomatterStatus Adafruit_Protomatter::begin(void) {
_PM_protoPtr = &core;
_PM_begin(&core);
return PROTOMATTER_OK;
return _PM_begin(&core);
}
// Transfer data from GFXcanvas16 to the matrix framebuffer's weird
// internal format. The actual conversion functions referenced below
// are in core.c, reasoning is explained there.
void Adafruit_Protomatter::show(void) {
// Destination address is computed in convert function
// (based on active buffer value, if double-buffering),
// just need to pass in the canvas buffer address and
// width in pixels.
if(core.bytesPerElement == 1) {
_PM_convert_565_byte(&core, getBuffer(), WIDTH);
} else if(core.bytesPerElement == 2) {
_PM_convert_565_word(&core, getBuffer(), WIDTH);
} else {
_PM_convert_565_long(&core, getBuffer(), WIDTH);
}
if(core.doubleBuffer) {
core.swapBuffers = 1;
// To avoid overwriting data on the matrix, don't return
// until the timer ISR has performed the swap at the right time.
while(core.swapBuffers);
}
_PM_convert_565(&core, getBuffer(), WIDTH);
_PM_swapbuffer_maybe(&core);
}
// Returns current value of frame counter and resets its value to zero.

View file

@ -7,17 +7,94 @@
#include "core.h"
#include <Adafruit_GFX.h>
/*!
@brief Class representing the Arduino-facing side of the Protomatter
library. Subclass of Adafruit_GFX's GFXcanvas16 to allow all
the drawing operations.
*/
class Adafruit_Protomatter : public GFXcanvas16 {
public:
Adafruit_Protomatter(uint16_t bitWidth, uint8_t bitDepth,
uint8_t rgbCount, uint8_t *rgbList,
uint8_t addrCount, uint8_t *addrList,
/*!
@brief Adafruit_Protomatter constructor.
@param bitWidth Total width of RGB matrix chain, in pixels.
Usu. some multiple of 32, but maybe exceptions.
@param bitDepth Color "depth" in bitplanes, determines range of
shades of red, green and blue. e.g. passing 4
bits = 16 shades ea. R,G,B = 16x16x16 = 4096
colors. Max is 6, since the GFX library works
with "565" RGB colors (6 bits green, 5 red/blue).
@param rgbCount Number of "sets" of RGB data pins, each set
containing 6 pins (2 ea. R,G,B). Typically 1,
indicating a single matrix (or matrix chain).
In theory (but not yet extensively tested),
multiple sets of pins can be driven in parallel,
up to 5 on some devices (if the hardware design
provides all those bits on one PORT).
@param rgbList A uint8_t array of pins (Arduino pin numbering),
6X the prior rgbCount value, corresponding to
the 6 output color bits for a matrix (or chain).
Order is upper-half red, green, blue, lower-half
red, green blue (repeat for each add'l chain).
All the RGB pins (plus the clock pin below on
some architectures) MUST be on the same PORT
register. It's recommended (but not required)
that all RGB pins (and clock depending on arch)
be within the same byte of a PORT (but do not
need to be sequential or contiguous within that
byte) for more efficient RAM utilization. For
two concurrent chains, same principle but 16-bit
word instead of byte.
@param addrCount Number of row address lines required of matrix.
Total pixel height is then 2 x 2^addrCount, e.g.
32-pixel-tall matrices have 4 row address lines.
@param addrList A uint8_t array of pins (Arduino pin numbering),
one per row address line.
@param clockPin RGB clock pin (Arduino pin #).
@param latchPin RGB data latch pin (Arduino pin #).
@param oePin Output enable pin (Arduino pin #), active low.
@param doubleBuffer If true, two matrix buffers are allocated,
so changing display contents doesn't introduce
artifacts mid-conversion. Requires ~2X RAM.
@param timer Pointer to timer peripheral or timer-related
struct (architecture-dependent), or NULL to
use a default timer ID (also arch-dependent).
*/
Adafruit_Protomatter(uint16_t bitWidth, uint8_t bitDepth, uint8_t rgbCount,
uint8_t *rgbList, uint8_t addrCount, uint8_t *addrList,
uint8_t clockPin, uint8_t latchPin, uint8_t oePin,
bool doubleBuffer, void *timer = NULL);
~Adafruit_Protomatter(void);
/*!
@brief Start a Protomatter matrix display running -- initialize
pins, timer and interrupt into existence.
@return A ProtomatterStatus status, one of:
PROTOMATTER_OK if everything is good.
PROTOMATTER_ERR_PINS if data and/or clock pins are split
across different PORTs.
PROTOMATTER_ERR_MALLOC if insufficient RAM to allocate
display memory.
PROTOMATTER_ERR_ARG if a bad value was passed to the
constructor.
*/
ProtomatterStatus begin(void);
/*!
@brief Process data from GFXcanvas16 to the matrix framebuffer's
internal format for display.
*/
void show(void);
/*!
@brief 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);
private:
Protomatter_core core; // Underlying C struct
void convert_byte(uint8_t *dest); // GFXcanvas16-to-matrix

View file

@ -1,4 +1,4 @@
# Adafruit_Protomatter
# Adafruit_Protomatter [![Build Status](https://travis-ci.com/adafruit/Adafruit_Protomatter.svg?branch=master)](https://travis-ci.com/adafruit/Adafruit_Protomatter)
"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
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
HUB75 RGB LED matrices are basically a set of six concurrent shift register

653
arch.h
View file

@ -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.
// This should ONLY be included by core.c, nowhere else. Ever.
#if !defined(_PROTOMATTER_ARCH_H_)
#define _PROTOMATTER_ARCH_H_
#include <string.h>
/*
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_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
// sections below...unless other environments provide device-neutral
// functions as above, in which case those could go here (w/#elif).
#endif // end defined(ARDUINO)
// CODE COMMON TO BOTH SAMD51 AND SAMD21 -----------------------------------
#if defined(__SAMD51__) || defined(_SAMD21_)
@ -140,19 +174,20 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
// g_APinDescription[] table and pin indices are Arduino specific:
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define _PM_byteOffset(pin) (g_APinDescription[pin].ulPin / 8)
#else
#define _PM_byteOffset(pin) (3 - (g_APinDescription[pin].ulPin / 8))
#endif
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define _PM_wordOffset(pin) (g_APinDescription[pin].ulPin / 16)
#else
#define _PM_byteOffset(pin) (3 - (g_APinDescription[pin].ulPin / 8))
#define _PM_wordOffset(pin) (1 - (g_APinDescription[pin].ulPin / 16))
#endif
// Arduino implementation is tied to a specific timer/counter & freq:
#if defined(TC4)
#define _PM_TIMER_DEFAULT TC4
#define _PM_IRQ_HANDLER TC4_Handler
#else // No TC4 on some M4's
#define _PM_TIMER_DEFAULT TC3
#define _PM_IRQ_HANDLER TC3_Handler
#endif
#define _PM_timerFreq 48000000
// Partly because IRQs must be declared at compile-time, and partly
// because we know Arduino's already set up one of the GCLK sources
@ -170,9 +205,45 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
_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
// Non-arduino byte offset macros, timer and ISR work go here.
// Other port byte offset macros, timer and ISR work go here.
#endif
@ -182,7 +253,6 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
#endif // __SAMD51__ || _SAMD21_
// SAMD51-SPECIFIC CODE ----------------------------------------------------
#if defined(__SAMD51__)
@ -202,9 +272,21 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
#define _PM_portToggleRegister(pin) \
&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
// Non-Arduino port register lookups go here
// Other port register lookups go here
#endif
@ -215,32 +297,44 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
IRQn_Type IRQn; // Interrupt number
uint8_t GCLK_ID; // Peripheral channel # for clock source
} timer[] = {
TC0, TC0_IRQn, TC0_GCLK_ID,
TC1, TC1_IRQn, TC1_GCLK_ID,
TC2, TC2_IRQn, TC2_GCLK_ID,
TC3, TC3_IRQn, TC3_GCLK_ID,
TC4, TC4_IRQn, TC4_GCLK_ID,
TC5, TC5_IRQn, TC5_GCLK_ID,
#if defined(TC0)
{TC0, TC0_IRQn, TC0_GCLK_ID},
#endif
#if defined(TC1)
{TC1, TC1_IRQn, TC1_GCLK_ID},
#endif
#if defined(TC2)
{TC2, TC2_IRQn, TC2_GCLK_ID},
#endif
#if defined(TC3)
{TC3, TC3_IRQn, TC3_GCLK_ID},
#endif
#if defined(TC4)
{TC4, TC4_IRQn, TC4_GCLK_ID},
#endif
#if defined(TC5)
{TC5, TC5_IRQn, TC5_GCLK_ID},
#endif
#if defined(TC6)
TC6, TC6_IRQn, TC6_GCLK_ID,
{TC6, TC6_IRQn, TC6_GCLK_ID},
#endif
#if defined(TC7)
TC7, TC7_IRQn, TC7_GCLK_ID,
{TC7, TC7_IRQn, TC7_GCLK_ID},
#endif
#if defined(TC8)
TC8, TC8_IRQn, TC8_GCLK_ID,
{TC8, TC8_IRQn, TC8_GCLK_ID},
#endif
#if defined(TC9)
TC9, TC9_IRQn, TC9_GCLK_ID,
{TC9, TC9_IRQn, TC9_GCLK_ID},
#endif
#if defined(TC10)
TC10, TC10_IRQn, TC10_GCLK_ID,
{TC10, TC10_IRQn, TC10_GCLK_ID},
#endif
#if defined(TC11)
TC11, TC11_IRQn, TC11_GCLK_ID,
{TC11, TC11_IRQn, TC11_GCLK_ID},
#endif
#if defined(TC12)
TC12, TC12_IRQn, TC12_GCLK_ID,
{TC12, TC12_IRQn, TC12_GCLK_ID},
#endif
};
#define NUM_TIMERS (sizeof timer / sizeof timer[0])
@ -251,7 +345,8 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
while ((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tc)) {
timerNum++;
}
if(timerNum >= NUM_TIMERS) return;
if (timerNum >= NUM_TIMERS)
return;
// Feed timer/counter off GCLK1 (already set 48 MHz by Arduino core).
// Sure, SAMD51 can run timers up to F_CPU (e.g. 120 MHz or up to
@ -262,17 +357,20 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
// simply that the period doubles with each bitplane, and this can
// work fine at 48 MHz.
GCLK->PCHCTRL[timer[timerNum].GCLK_ID].bit.CHEN = 0; // Disable
while(GCLK->PCHCTRL[timer[timerNum].GCLK_ID].bit.CHEN); // Wait for it
while (GCLK->PCHCTRL[timer[timerNum].GCLK_ID].bit.CHEN)
; // Wait for it
GCLK_PCHCTRL_Type pchctrl; // Read-modify-store
pchctrl.reg = GCLK->PCHCTRL[timer[timerNum].GCLK_ID].reg;
pchctrl.bit.GEN = GCLK_PCHCTRL_GEN_GCLK1_Val;
pchctrl.bit.CHEN = 1;
GCLK->PCHCTRL[timer[timerNum].GCLK_ID].reg = pchctrl.reg;
while(!GCLK->PCHCTRL[timer[timerNum].GCLK_ID].bit.CHEN);
while (!GCLK->PCHCTRL[timer[timerNum].GCLK_ID].bit.CHEN)
;
// Disable timer before configuring it
tc->COUNT16.CTRLA.bit.ENABLE = 0;
while(tc->COUNT16.SYNCBUSY.bit.ENABLE);
while (tc->COUNT16.SYNCBUSY.bit.ENABLE)
;
// 16-bit counter mode, 1:1 prescale
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->COUNT16.CTRLBCLR.reg = TC_CTRLBCLR_DIR; // Count up
while(tc->COUNT16.SYNCBUSY.bit.CTRLB);
while (tc->COUNT16.SYNCBUSY.bit.CTRLB)
;
// Overflow interrupt
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) {
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
tc->COUNT16.COUNT.reg = 0;
while(tc->COUNT16.SYNCBUSY.bit.COUNT);
while (tc->COUNT16.SYNCBUSY.bit.COUNT)
;
tc->COUNT16.CC[0].reg = period;
while(tc->COUNT16.SYNCBUSY.bit.CC0);
while (tc->COUNT16.SYNCBUSY.bit.CC0)
;
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).
@ -313,7 +415,8 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
inline uint32_t _PM_timerGetCount(void *tptr) {
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
tc->COUNT16.CTRLBSET.bit.CMD = 0x4; // Sync COUNT
while(tc->COUNT16.CTRLBSET.bit.CMD); // Wait for command
while (tc->COUNT16.CTRLBSET.bit.CMD)
; // Wait for command
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
uint32_t count = _PM_timerGetCount(tptr);
tc->COUNT16.CTRLA.bit.ENABLE = 0;
while(tc->COUNT16.SYNCBUSY.bit.STATUS);
while (tc->COUNT16.SYNCBUSY.bit.STATUS)
;
return count;
}
@ -346,7 +450,6 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
#endif // end __SAMD51__
// SAMD21-SPECIFIC CODE ----------------------------------------------------
#if defined(_SAMD21_)
@ -372,7 +475,6 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
#endif
// Initialize, but do not start, timer
void _PM_timerInit(void *tptr) {
static const struct {
@ -380,16 +482,20 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
IRQn_Type IRQn; // Interrupt number
uint8_t GCM_ID; // GCLK selection ID
} timer[] = {
TC0, TC0_IRQn, GCM_TCC0_TCC1,
TC1, TC1_IRQn, GCM_TCC0_TCC1,
#if defined(TC0)
{TC0, TC0_IRQn, GCM_TCC0_TCC1},
#endif
#if defined(TC1)
{TC1, TC1_IRQn, GCM_TCC0_TCC1},
#endif
#if defined(TC2)
TC2, TC2_IRQn, GCM_TCC2_TC3,
{TC2, TC2_IRQn, GCM_TCC2_TC3},
#endif
#if defined(TC3)
TC3, TC3_IRQn, GCM_TCC2_TC3,
{TC3, TC3_IRQn, GCM_TCC2_TC3},
#endif
#if defined(TC4)
TC4, TC4_IRQn, GCM_TC4_TC5,
{TC4, TC4_IRQn, GCM_TC4_TC5},
#endif
};
#define NUM_TIMERS (sizeof timer / sizeof timer[0])
@ -400,25 +506,30 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
while ((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tc)) {
timerNum++;
}
if(timerNum >= NUM_TIMERS) return;
if (timerNum >= NUM_TIMERS)
return;
// Enable GCLK for timer/counter
GCLK->CLKCTRL.reg = (uint16_t)(GCLK_CLKCTRL_CLKEN |
GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(timer[timerNum].GCM_ID));
while(GCLK->STATUS.bit.SYNCBUSY == 1);
GCLK->CLKCTRL.reg = (uint16_t)(GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 |
GCLK_CLKCTRL_ID(timer[timerNum].GCM_ID));
while (GCLK->STATUS.bit.SYNCBUSY == 1)
;
// Counter must first be disabled to configure it
tc->COUNT16.CTRLA.bit.ENABLE = 0;
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
;
tc->COUNT16.CTRLA.reg = // Configure timer counter
TC_CTRLA_PRESCALER_DIV1 | // 1:1 Prescale
TC_CTRLA_WAVEGEN_MFRQ | // Match frequency generation mode (MFRQ)
TC_CTRLA_MODE_COUNT16; // 16-bit counter mode
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
;
tc->COUNT16.CTRLBCLR.reg = TCC_CTRLBCLR_DIR; // Count up
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
;
// Overflow interrupt
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) {
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
tc->COUNT16.COUNT.reg = 0;
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
;
tc->COUNT16.CC[0].reg = period;
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
;
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).
@ -449,7 +563,8 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
inline uint32_t _PM_timerGetCount(void *tptr) {
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
tc->COUNT16.READREQ.reg = TC_READREQ_RCONT | TC_READREQ_ADDR(0x10);
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
;
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
uint32_t count = _PM_timerGetCount(tptr);
tc->COUNT16.CTRLA.bit.ENABLE = 0;
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
;
return count;
}
#endif // _SAMD21_
// NRF52-SPECIFIC CODE -----------------------------------------------------
#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
// 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 -----------------------------------------------------
#if defined(ARDUINO_ARCH_ESP32)
#endif // ARDUINO_ARCH_ESP32
// DEFAULTS IF NOT DEFINED ABOVE -------------------------------------------
#if !defined(_PM_chunkSize)
#define _PM_chunkSize 8
#define _PM_chunkSize 8 ///< Unroll data-stuffing loop to this size
#endif
#if !defined(_PM_clockHoldHigh)
#define _PM_clockHoldHigh
#define _PM_clockHoldHigh ///< Extra cycles (if any) on clock HIGH signal
#endif
#if !defined(_PM_clockHoldLow)
#define _PM_clockHoldLow
#define _PM_clockHoldLow ///< Extra cycles (if any) on clock LOW signal
#endif
#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
// ARDUINO SPECIFIC CODE ---------------------------------------------------
#if defined(ARDUINO)
#if defined(ARDUINO) || defined(CIRCUITPY)
// 16-bit (565) color conversion functions go here (rather than in 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
// core's bitWidth (due to padding). height isn't needed, it can be
// inferred from core->numRowPairs.
void _PM_convert_565_byte(Protomatter_core *core, uint16_t *source,
__attribute__((noinline)) void _PM_convert_565_byte(Protomatter_core *core,
const uint16_t *source,
uint16_t width) {
uint16_t *upperSrc = source; // Canvas top half
uint16_t *lowerSrc = source + width * core->numRowPairs; // " bottom half
const uint16_t *upperSrc = source; // Canvas top half
const uint16_t *lowerSrc =
source + width * core->numRowPairs; // " bottom half
uint8_t *pinMask = (uint8_t *)core->rgbMask; // Pin bitmasks
uint8_t *dest = (uint8_t *)core->screenData;
if (core->doubleBuffer) {
@ -537,7 +967,8 @@ void _PM_convert_565_byte(Protomatter_core *core, uint16_t *source,
// 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
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 lowerRGB = lowerSrc[x]; // Pixel in lower half
uint8_t result = 0;
if(upperRGB & redBit) result |= pinMask[0];
if(upperRGB & greenBit) result |= pinMask[1];
if(upperRGB & blueBit) result |= pinMask[2];
if(lowerRGB & redBit) result |= pinMask[3];
if(lowerRGB & greenBit) result |= pinMask[4];
if(lowerRGB & blueBit) result |= pinMask[5];
if (upperRGB & redBit)
result |= pinMask[0];
if (upperRGB & greenBit)
result |= pinMask[1];
if (upperRGB & blueBit)
result |= pinMask[2];
if (lowerRGB & redBit)
result |= pinMask[3];
if (lowerRGB & greenBit)
result |= pinMask[4];
if (lowerRGB & blueBit)
result |= pinMask[5];
#if defined(_PM_portToggleRegister)
dest[x] = result ^ prior;
prior = result | core->clockMask; // Set clock bit on next out
@ -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 *dest = (uint16_t *)core->screenData;
if (core->doubleBuffer) {
dest += core->bufferSize / core->bytesPerElement *
(1 - core->activeBuffer);
dest += core->bufferSize / core->bytesPerElement * (1 - core->activeBuffer);
}
uint32_t bitplaneSize = _PM_chunkSize *
uint32_t bitplaneSize =
_PM_chunkSize *
((width + (_PM_chunkSize - 1)) / _PM_chunkSize); // 1 plane of row pair
uint8_t pad = bitplaneSize - width; // Start-of-plane pad
@ -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 lowerRGB = lowerSrc[x]; // Pixel in lower half
uint16_t result = 0;
if(upperRGB & redBit) result |= pinMask[0];
if(upperRGB & greenBit) result |= pinMask[1];
if(upperRGB & blueBit) result |= pinMask[2];
if(lowerRGB & redBit) result |= pinMask[3];
if(lowerRGB & greenBit) result |= pinMask[4];
if(lowerRGB & blueBit) result |= pinMask[5];
if (upperRGB & redBit)
result |= pinMask[0];
if (upperRGB & greenBit)
result |= pinMask[1];
if (upperRGB & blueBit)
result |= pinMask[2];
if (lowerRGB & redBit)
result |= pinMask[3];
if (lowerRGB & greenBit)
result |= pinMask[4];
if (lowerRGB & blueBit)
result |= pinMask[5];
// Main difference here vs byte converter is each chain
// ORs new bits into place (vs single-pass overwrite).
#if defined(_PM_portToggleRegister)
@ -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 *dest = (uint32_t *)core->screenData;
if (core->doubleBuffer) {
dest += core->bufferSize / core->bytesPerElement *
(1 - core->activeBuffer);
dest += core->bufferSize / core->bytesPerElement * (1 - core->activeBuffer);
}
uint32_t bitplaneSize = _PM_chunkSize *
uint32_t bitplaneSize =
_PM_chunkSize *
((width + (_PM_chunkSize - 1)) / _PM_chunkSize); // 1 plane of row pair
uint8_t pad = bitplaneSize - width; // Start-of-plane pad
@ -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 lowerRGB = lowerSrc[x]; // Pixel in lower half
uint32_t result = 0;
if(upperRGB & redBit) result |= pinMask[0];
if(upperRGB & greenBit) result |= pinMask[1];
if(upperRGB & blueBit) result |= pinMask[2];
if(lowerRGB & redBit) result |= pinMask[3];
if(lowerRGB & greenBit) result |= pinMask[4];
if(lowerRGB & blueBit) result |= pinMask[5];
if (upperRGB & redBit)
result |= pinMask[0];
if (upperRGB & greenBit)
result |= pinMask[1];
if (upperRGB & blueBit)
result |= pinMask[2];
if (lowerRGB & redBit)
result |= pinMask[3];
if (lowerRGB & greenBit)
result |= pinMask[4];
if (lowerRGB & blueBit)
result |= pinMask[5];
// Main difference here vs byte converter is each chain
// ORs new bits into place (vs single-pass overwrite).
#if defined(_PM_portToggleRegister)
@ -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_

218
core.c
View file

@ -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.
// See notes near top of arch.h regarding assumptions of hardware
// "common ground." If you find yourself doing an "#ifdef ARDUINO" or
@ -15,6 +31,8 @@
#include "core.h" // enums and structs
#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
// 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
// upper limit to avoid excessive CPU load). An incredibly long comment block
// for a single constant, thank you for coming to my TED talk!
#define _PM_MAX_REFRESH_HZ 250
#define _PM_MAX_REFRESH_HZ 250 ///< Max matrix refresh rate
// Time (in microseconds) to pause following any change in address lines
// (individually or collectively). Some matrices respond slowly there...
// must pause on change for matrix to catch up. Defined here (rather than
// arch.h) because it's not architecture-specific.
#define _PM_ROW_DELAY 8
#define _PM_ROW_DELAY 8 ///< Delay time between row address line changes (ms)
// 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
@ -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_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.
// Does NOT allocate core struct -- calling function must provide that.
// (In the Arduino C++ library, its part of the Protomatter class.)
ProtomatterStatus _PM_init(Protomatter_core *core,
uint16_t bitWidth, uint8_t bitDepth,
uint8_t rgbCount, uint8_t *rgbList,
ProtomatterStatus _PM_init(Protomatter_core *core, uint16_t bitWidth,
uint8_t bitDepth, uint8_t rgbCount, uint8_t *rgbList,
uint8_t addrCount, uint8_t *addrList,
uint8_t clockPin, uint8_t latchPin, uint8_t oePin,
bool doubleBuffer, void *timer) {
if(!core) return PROTOMATTER_ERR_ARG;
if (!core)
return PROTOMATTER_ERR_ARG;
if(rgbCount > 5) rgbCount = 5; // Max 5 in parallel (32-bit PORT)
if(addrCount > 5) addrCount = 5; // Max 5 address lines (A-E)
if (rgbCount > 5)
rgbCount = 5; // Max 5 in parallel (32-bit PORT)
if (addrCount > 5)
addrCount = 5; // Max 5 address lines (A-E)
// bitDepth is NOT constrained here, handle in calling function
// (varies with implementation, e.g. GFX lib is max 6 bitplanes,
// but might be more or less elsewhere)
#if defined(_PM_TIMER_DEFAULT)
// If NULL timer was passed in (the default case for the constructor),
// use default value from arch.h. For example, in the Arduino case it's
// tied to TC4 specifically.
if(timer == NULL) timer = _PM_TIMER_DEFAULT;
if (timer == NULL)
timer = _PM_TIMER_DEFAULT;
#else
if (timer == NULL)
return PROTOMATTER_ERR_ARG;
#endif
core->timer = timer;
core->width = bitWidth; // Total matrix chain length in bits
@ -95,15 +128,15 @@ ProtomatterStatus _PM_init(Protomatter_core *core,
// the pin bitmasks.
rgbCount *= 6; // Convert parallel count to pin count
if((core->rgbPins = (uint8_t *)malloc(rgbCount * sizeof(uint8_t)))) {
if((core->addr = (_PM_pin *)malloc(addrCount * sizeof(_PM_pin)))) {
if ((core->rgbPins = (uint8_t *)_PM_ALLOCATOR(rgbCount * sizeof(uint8_t)))) {
if ((core->addr = (_PM_pin *)_PM_ALLOCATOR(addrCount * sizeof(_PM_pin)))) {
memcpy(core->rgbPins, rgbList, rgbCount * sizeof(uint8_t));
for (uint8_t i = 0; i < addrCount; i++) {
core->addr[i].pin = addrList[i];
}
return PROTOMATTER_OK;
}
free(core->rgbPins);
_PM_FREE(core->rgbPins);
core->rgbPins = NULL;
}
return PROTOMATTER_ERR_MALLOC;
@ -111,7 +144,8 @@ ProtomatterStatus _PM_init(Protomatter_core *core,
// Allocate display buffers and populate additional elements.
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
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
// compact than if they're spread across a word or long.
uint8_t byteMask = 0;
if(bitMask & 0xFF000000) byteMask |= 0b1000;
if(bitMask & 0x00FF0000) byteMask |= 0b0100;
if(bitMask & 0x0000FF00) byteMask |= 0b0010;
if(bitMask & 0x000000FF) byteMask |= 0b0001;
if (bitMask & 0xFF000000)
byteMask |= 0b1000;
if (bitMask & 0x00FF0000)
byteMask |= 0b0100;
if (bitMask & 0x0000FF00)
byteMask |= 0b0010;
if (bitMask & 0x000000FF)
byteMask |= 0b0001;
switch (byteMask) {
case 0b0001: // If all PORT bits are in the same byte...
case 0b0010:
@ -175,17 +213,19 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
core->numRowPairs = 1 << core->numAddressLines;
uint8_t chunks = (core->width + (_PM_chunkSize - 1)) / _PM_chunkSize;
uint16_t columns = chunks * _PM_chunkSize; // Padded matrix width
uint32_t screenBytes = columns * core->numRowPairs * core->numPlanes *
core->bytesPerElement;
uint32_t screenBytes =
columns * core->numRowPairs * core->numPlanes * core->bytesPerElement;
core->bufferSize = screenBytes; // Bytes per matrix buffer (1 or 2)
if(core->doubleBuffer) screenBytes *= 2; // Total for matrix buffer(s)
if (core->doubleBuffer)
screenBytes *= 2; // Total for matrix buffer(s)
uint32_t rgbMaskBytes = core->parallel * 6 * core->bytesPerElement;
// Allocate matrix buffer(s). Don't worry about the return type...
// though we might be using words or longs for certain pin configs,
// malloc() by definition always aligns to the longest type.
if(!(core->screenData = (uint8_t *)malloc(screenBytes + rgbMaskBytes))) {
// _PM_ALLOCATOR() by definition always aligns to the longest type.
if (!(core->screenData =
(uint8_t *)_PM_ALLOCATOR(screenBytes + rgbMaskBytes))) {
return PROTOMATTER_ERR_MALLOC;
}
@ -203,10 +243,9 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
core->portOffset = _PM_byteOffset(core->rgbPins[0]);
#if defined(_PM_portToggleRegister)
// Clock and rgbAndClockMask are 8-bit values
core->clockMask = _PM_portBitMask(core->clockPin) >>
(core->portOffset * 8);
core->rgbAndClockMask = (bitMask >> (core->portOffset * 8)) |
core->clockMask;
core->clockMask = _PM_portBitMask(core->clockPin) >> (core->portOffset * 8);
core->rgbAndClockMask =
(bitMask >> (core->portOffset * 8)) | core->clockMask;
memset(core->screenData, core->clockMask, screenBytes);
#else
// Clock and rgbAndClockMask are 32-bit values
@ -221,10 +260,10 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
core->portOffset = _PM_wordOffset(core->rgbPins[0]);
#if defined(_PM_portToggleRegister)
// Clock and rgbAndClockMask are 16-bit values
core->clockMask = _PM_portBitMask(core->clockPin) >>
(core->portOffset * 16);
core->rgbAndClockMask = (bitMask >> (core->portOffset * 16)) |
core->clockMask;
core->clockMask =
_PM_portBitMask(core->clockPin) >> (core->portOffset * 16);
core->rgbAndClockMask =
(bitMask >> (core->portOffset * 16)) | core->clockMask;
uint32_t elements = screenBytes / 2;
for (uint32_t i = 0; i < elements; i++) {
((uint16_t *)core->screenData)[i] = core->clockMask;
@ -296,13 +335,11 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
core->addrPortToggle = _PM_portToggleRegister(core->addr[0].pin);
core->singleAddrPort = 1;
#endif
for(uint8_t line=0,bit=1; line<core->numAddressLines; line++, bit<<=1) {
core->addr[line].setReg =
_PM_portSetRegister(core->addr[line].pin);
core->addr[line].clearReg =
_PM_portClearRegister(core->addr[line].pin);
core->addr[line].bit =
_PM_portBitMask(core->addr[line].pin);
for (uint8_t line = 0, bit = 1; line < core->numAddressLines;
line++, bit <<= 1) {
core->addr[line].setReg = _PM_portSetRegister(core->addr[line].pin);
core->addr[line].clearReg = _PM_portClearRegister(core->addr[line].pin);
core->addr[line].bit = _PM_portBitMask(core->addr[line].pin);
_PM_pinOutput(core->addr[line].pin);
if (core->prevRow & bit) {
_PM_pinHigh(core->addr[line].pin);
@ -311,8 +348,7 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
}
#if defined(_PM_portToggleRegister)
// If address pin on different port than addr 0, no singleAddrPort.
if(_PM_portToggleRegister(core->addr[line].pin) !=
core->addrPortToggle) {
if (_PM_portToggleRegister(core->addr[line].pin) != core->addrPortToggle) {
core->singleAddrPort = 0;
}
#endif
@ -336,9 +372,10 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
// so it won't halt with lit LEDs.
void _PM_stop(Protomatter_core *core) {
if ((core)) {
while(core->swapBuffers); // Wait for any pending buffer swap
while (core->swapBuffers)
; // Wait for any pending buffer swap
_PM_timerStop(core->timer); // Halt timer
*core->oe.setReg = core->oe.bit; // Set OE HIGH (disable output)
_PM_setReg(core->oe); // Set OE HIGH (disable output)
// So, in PRINCIPLE, setting OE high would be sufficient...
// but in case that pin is shared with another function such
// as the onloard LED (which pulses during bootloading) let's
@ -355,8 +392,8 @@ void _PM_stop(Protomatter_core *core) {
_PM_clockHoldLow;
}
// Latch data
*core->latch.setReg = core->latch.bit;
*core->latch.clearReg = core->latch.bit;
_PM_setReg(core->latch);
_PM_clearReg(core->latch);
}
}
@ -379,26 +416,27 @@ void _PM_free(Protomatter_core *core) {
if ((core)) {
_PM_stop(core);
// TO DO: Set all pins back to inputs here?
if(core->screenData) free(core->screenData);
if(core->addr) free(core->addr);
if (core->screenData)
_PM_FREE(core->screenData);
if (core->addr)
_PM_FREE(core->addr);
if (core->rgbPins) {
free(core->rgbPins);
_PM_FREE(core->rgbPins);
core->rgbPins = NULL;
}
}
}
// ISR function (in arch.h) calls this function which it extern'd.
void _PM_row_handler(Protomatter_core *core) {
*core->oe.setReg = core->oe.bit; // Disable LED output
_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
uint32_t elapsed = _PM_timerStop(core->timer);
uint8_t prevPlane = core->plane; // Save that plane # for later timing
*core->latch.clearReg = core->latch.bit; // (split to add a few cycles)
_PM_clearReg(core->latch); // (split to add a few cycles)
// If plane 0 just finished being displayed (plane 1 was loaded on prior
// pass, or there's only one plane...I know, it's confusing), take note
@ -430,7 +468,7 @@ void _PM_row_handler(Protomatter_core *core) {
priorBits |= core->addr[line].bit;
}
}
*core->addrPortToggle = newBits ^ priorBits;
*(volatile _PM_PORT_TYPE *)core->addrPortToggle = newBits ^ priorBits;
_PM_delayMicroseconds(_PM_ROW_DELAY);
} else {
#endif
@ -440,9 +478,9 @@ void _PM_row_handler(Protomatter_core *core) {
line++, bit <<= 1) {
if ((core->row & bit) != (core->prevRow & bit)) {
if (core->row & bit) { // Set addr line high
*core->addr[line].setReg = core->addr[line].bit;
_PM_setReg(core->addr[line]);
} else { // Set addr line low
*core->addr[line].clearReg = core->addr[line].bit;
_PM_clearReg(core->addr[line]);
}
_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:
_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 *
((core->width + (_PM_chunkSize - 1)) / _PM_chunkSize);
uint32_t elementsPerLine =
_PM_chunkSize * ((core->width + (_PM_chunkSize - 1)) / _PM_chunkSize);
uint32_t srcOffset = elementsPerLine *
(core->numPlanes * core->row + core->plane) * core->bytesPerElement;
(core->numPlanes * core->row + core->plane) *
core->bytesPerElement;
if (core->doubleBuffer) {
srcOffset += core->bufferSize * core->activeBuffer;
}
@ -514,28 +553,29 @@ void _PM_row_handler(Protomatter_core *core) {
#define PEW \
*set = *data++; /* Set RGB data high */ \
_PM_clockHoldLow; \
*set32 = clock; /* Set clock high */ \
*set_full = clock; /* Set clock high */ \
_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
#if _PM_chunkSize == 1
#define PEW_UNROLL PEW
#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
#define PEW_UNROLL \
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW
#elif _PM_chunkSize == 32
#define PEW_UNROLL \
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW \
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW \
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW
#elif _PM_chunkSize == 64
#define PEW_UNROLL \
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW \
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW \
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW \
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW \
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW \
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW \
PEW PEW PEW PEW PEW PEW PEW PEW PEW PEW
#else
#error "Unimplemented _PM_chunkSize value"
#endif
@ -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
// in the word- or long-blasting functions now. So we just need an
// 8-bit pointer to the PORT.
volatile uint8_t *toggle = (volatile uint8_t *)core->toggleReg +
core->portOffset;
volatile uint8_t *toggle =
(volatile uint8_t *)core->toggleReg + core->portOffset;
#else
// No-toggle version is a little different. If here, RGB data is all
// in one byte of PORT register, clock can be any bit in 32-bit PORT.
volatile uint8_t *set; // For RGB data set
volatile uint32_t *set32; // For clock set
volatile uint32_t *clear32; // For RGB data + clock clear
set = (volatile uint8_t *)core->setReg + portOffset;
set32 = (volatile uint32_t *)core->setReg;
clear32 = (volatile uint32_t *)core->clearReg;
uint32_t rgbclock = core->rgbAndClockMask; // RGB + clock bit
volatile _PM_PORT_TYPE *set_full; // For clock set
volatile _PM_PORT_TYPE *clear_full; // For RGB data + clock clear
set = (volatile uint8_t *)core->setReg + core->portOffset;
set_full = (volatile _PM_PORT_TYPE *)core->setReg;
clear_full = (volatile _PM_PORT_TYPE *)core->clearReg;
_PM_PORT_TYPE rgbclock = core->rgbAndClockMask; // RGB + clock bit
#endif
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;
// 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) {
#if defined(_PM_portToggleRegister)
// See notes above -- except now 16-bit word in PORT.
volatile uint16_t *toggle = (volatile uint16_t *)core->toggleReg +
core->portOffset;
volatile uint16_t *toggle =
(volatile uint16_t *)core->toggleReg + core->portOffset;
#else
volatile uint16_t *set; // For RGB data set
volatile uint32_t *set32; // For clock set
volatile uint32_t *clear32; // For RGB data + clock clear
volatile _PM_PORT_TYPE *set_full; // For clock set
volatile _PM_PORT_TYPE *clear_full; // For RGB data + clock clear
set = (volatile uint16_t *)core->setReg + core->portOffset;
set32 = (volatile uint32_t *)core->setReg;
clear32 = (volatile uint32_t *)core->clearReg;
uint32_t rgbclock = core->rgbAndClockMask; // RGB + clock bit
set_full = (volatile _PM_PORT_TYPE *)core->setReg;
clear_full = (volatile _PM_PORT_TYPE *)core->clearReg;
_PM_PORT_TYPE rgbclock = core->rgbAndClockMask; // RGB + clock bit
#endif
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;
while (chunks--) {
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
// wanting a special case of the PEW macro due to divergence risk.
volatile uint32_t *set; // For RGB data set
volatile uint32_t *set32; // For clock set
volatile uint32_t *clear32; // For RGB data + clock clear
volatile _PM_PORT_TYPE *set_full; // For clock set
volatile _PM_PORT_TYPE *clear_full; // For RGB data + clock clear
set = (volatile uint32_t *)core->setReg;
set32 = (volatile uint32_t *)core->setReg;
clear32 = (volatile uint32_t *)core->clearReg;
uint32_t rgbclock = core->rgbAndClockMask; // RGB + clock bit
set_full = (volatile _PM_PORT_TYPE *)core->setReg;
clear_full = (volatile _PM_PORT_TYPE *)core->clearReg;
_PM_PORT_TYPE rgbclock = core->rgbAndClockMask; // RGB + clock bit
#endif
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;
while (chunks--) {
PEW_UNROLL // _PM_chunkSize RGB+clock writes

287
core.h
View file

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

View file

@ -41,6 +41,16 @@ PA05 A4 PA13 PA21 D7 PB05 PB13
PA06 PA14 PA22 SDA PB06 PB14
PA07 D9 PA15 D5 PA23 SCL PB07 PB15
FEATHER nRF52840:
P0.00 P0.08 D12 P0.24 RXD P1.08 D5
P0.01 P0.09 P0.25 TXD P1.09 D13
P0.02 A4 P0.10 D2 (NFC) P0.26 D9 P1.10
P0.03 A5 P0.11 SCL P0.27 D10 P1.11
P0.04 A0 P0.12 SDA P0.28 A3 P1.12
P0.05 A1 P0.13 MOSI P0.29 P1.13
P0.06 D11 P0.14 SCK P0.30 A2 P1.14
P0.07 D6 P0.15 MISO P0.31 P1.15
RGB Matrix FeatherWing:
R1 D6 A A5
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
M0 devices like a Metro, if wiring manually so one can pick a contiguous
byte of PORT bits.
RGB+clock are on different PORTs on nRF52840.
*/
#if defined(__SAMD51__)
@ -64,12 +75,18 @@ byte of PORT bits.
uint8_t clockPin = 13;
uint8_t latchPin = 0;
uint8_t oePin = 1;
#else // SAMD21
#elif defined(_SAMD21_)
uint8_t rgbPins[] = {6, 7, 10, 11, 12, 13};
uint8_t addrPins[] = {0, 1, 2, 3};
uint8_t clockPin = SDA;
uint8_t latchPin = 4;
uint8_t oePin = 5;
#elif defined(NRF52_SERIES)
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
// Last arg here enables double-buffering

View file

@ -41,6 +41,16 @@ PA05 A4 PA13 PA21 D7 PB05 PB13
PA06 PA14 PA22 SDA PB06 PB14
PA07 D9 PA15 D5 PA23 SCL PB07 PB15
FEATHER nRF52840:
P0.00 P0.08 D12 P0.24 RXD P1.08 D5
P0.01 P0.09 P0.25 TXD P1.09 D13
P0.02 A4 P0.10 D2 (NFC) P0.26 D9 P1.10
P0.03 A5 P0.11 SCL P0.27 D10 P1.11
P0.04 A0 P0.12 SDA P0.28 A3 P1.12
P0.05 A1 P0.13 MOSI P0.29 P1.13
P0.06 D11 P0.14 SCK P0.30 A2 P1.14
P0.07 D6 P0.15 MISO P0.31 P1.15
RGB Matrix FeatherWing:
R1 D6 A A5
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
M0 devices like a Metro, if wiring manually so one can pick a contiguous
byte of PORT bits.
RGB+clock are on different PORTs on nRF52840.
*/
#if defined(__SAMD51__)
@ -64,12 +75,18 @@ byte of PORT bits.
uint8_t clockPin = 13;
uint8_t latchPin = 0;
uint8_t oePin = 1;
#else // SAMD21
#elif defined(_SAMD21_)
uint8_t rgbPins[] = {6, 7, 10, 11, 12, 13};
uint8_t addrPins[] = {0, 1, 2, 3};
uint8_t clockPin = SDA;
uint8_t latchPin = 4;
uint8_t oePin = 5;
#elif defined(NRF52_SERIES)
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
Adafruit_Protomatter matrix(

View file

@ -1,10 +1,10 @@
name=Adafruit Protomatter
version=0.0.0
version=1.0.0
author=Adafruit
maintainer=Adafruit <info@adafruit.com>
sentence=This is a library for the Adafruit RGB LED matrix.
paragraph=RGB LED matrix.
category=Display
url=https://github.com/adafruit/Adafruit_protomatter
architectures=*
architectures=samd,nrf52,stm32
depends=Adafruit GFX Library