Speed up row addr selection, add doublebuffer example

This commit is contained in:
Phillip Burgess 2020-02-25 18:14:54 -08:00
parent dd414bbcc8
commit 7a2f65aaf6
4 changed files with 218 additions and 13 deletions

View file

@ -18,6 +18,12 @@ static Adafruit_Protomatter *protoPtr = NULL;
// for a single constant, thank you for coming to my TED talk!
#define _PM_MAX_REFRESH_HZ 250
// Time (in milliseconds) to pause following any change in address lines
// (individually or collectively). Some matrices respond slowly there...
// must pause on change for matrix to catch up. Defined here (rather than
// arch.h) because it's not architecture-specific.
#define _PM_ROW_DELAY 8
Adafruit_Protomatter::Adafruit_Protomatter(
uint16_t bitWidth, uint8_t bitDepth,
uint8_t rgbCount, uint8_t *rgbList, uint8_t addrCount,
@ -194,19 +200,38 @@ ProtomatterStatus Adafruit_Protomatter::begin(void) {
uint32_t minPeriodPerFrame = _PM_timerFreq / _PM_MAX_REFRESH_HZ;
uint32_t minPeriodPerLine = minPeriodPerFrame / rowPairs;
minPeriod = minPeriodPerLine / ((1 << numPlanes) - 1);
// Actual frame rate may be lower than this. That's OK, just
// don't want to exceed this, as it'll eat all the CPU cycles.
if(minPeriod < _PM_minMinPeriod) {
minPeriod = _PM_minMinPeriod;
}
// Actual frame rate may be lower than this...it's only an estimate
// and does not factor in things like address line selection delays
// or interrupt overhead. That's OK, just don't want to exceed this
// rate, as it'll eat all the CPU cycles.
// Once allocation is set up, configure all the
// pins as outputs and initialize their states.
plane = numPlanes - 1; // Initialize plane & row to their max values
row = numRowPairs - 1; // so they roll over to start on 1st interrupt.
prevRow = (numRowPairs > 1) ? (row - 1) : 1;
// Configure pins as outputs and initialize their states.
pinMode(clockPin, OUTPUT); digitalWrite(clockPin, LOW);
pinMode(latchPin, OUTPUT); digitalWrite(latchPin, LOW);
pinMode(oePin , OUTPUT); digitalWrite(oePin , HIGH); // Disable output
for(uint8_t i=0; i<parallel * 6; i++) {
pinMode(rgbPins[i], OUTPUT); digitalWrite(rgbPins[i], LOW);
}
for(uint8_t i=0; i<numAddressLines; i++) {
pinMode(addrPins[i], OUTPUT); digitalWrite(addrPins[i], LOW);
#if defined(_PM_portToggleRegister)
addrPortToggle = _PM_portToggleRegister(addrPins[0]);
singleAddrPort = 1;
#endif
for(uint8_t line=0,bit=1; line<numAddressLines; line++, bit<<=1) {
pinMode(addrPins[line], OUTPUT); digitalWrite(addrPins[line], LOW);
digitalWrite(addrPins[line], prevRow & bit);
#if defined(_PM_portToggleRegister)
// If address pin on different port than addr 0, no singleAddrPort.
if(_PM_portToggleRegister(addrPins[line]) != addrPortToggle) {
singleAddrPort = 0;
}
#endif
}
// Get pointers to bit set and clear registers (and toggle, if present)
@ -217,8 +242,6 @@ ProtomatterStatus Adafruit_Protomatter::begin(void) {
#endif
protoPtr = this; // Only one active Adafruit_Protomatter object!
plane = numPlanes - 1; // Initialize plane & row to their max values
row = numRowPairs - 1; // so they roll over to start on 1st interrupt.
_PM_timerInit(); // Configure timer
_PM_timerStart(1000); // Start timer
@ -402,11 +425,35 @@ void Adafruit_Protomatter::row_handler(void) {
}
if(prevPlane == 0) { // Plane 0 just finished loading
// Configure row address lines:
for(uint8_t line=0,bit=1; line<numAddressLines; line++, bit<<=1) {
digitalWrite(addrPins[line], row & bit);
delayMicroseconds(10);
#if defined(_PM_portToggleRegister)
// If all address lines are on a single PORT (and bit toggle is
// available), do address line change all at once. Even doing all
// this math takes MUCH less time than the delays required when
// doing line-by-line changes.
if(singleAddrPort) {
// Make bitmasks of prior and new row bits
uint32_t priorBits = 0, newBits = 0;
for(uint8_t line=0,bit=1; line<numAddressLines; line++, bit<<=1) {
if(row & bit) newBits |= _PM_portBitMask(addrPins[line]);
if(prevRow & bit) priorBits |= _PM_portBitMask(addrPins[line]);
}
*addrPortToggle = newBits ^ priorBits;
delayMicroseconds(_PM_ROW_DELAY);
} else {
#endif
// Configure row address lines individually, making changes
// (with delays) only where necessary.
for(uint8_t line=0,bit=1; line<numAddressLines; line++, bit<<=1) {
if((row & bit) != (prevRow & bit)) {
digitalWrite(addrPins[line], row & bit);
delayMicroseconds(_PM_ROW_DELAY);
}
}
#if defined(_PM_portToggleRegister)
}
#endif
prevRow = row;
// Optimization opportunity: if device has a toggle register, and if
// all address lines are on same PORT, can do in a single operation
// and not need delays for each address bit. Also consider even in

View file

@ -22,7 +22,7 @@ class Adafruit_Protomatter : public GFXcanvas16 {
// This function needs to be declared public for protoPtr (in .cpp)
// to access it, but should NOT be invoked by user code:
void row_handler(void);
// private:
private:
void blast_byte(uint8_t *data); // Data-writing functions
void blast_word(uint16_t *data); // for 8/16/32 bit output
void blast_long(uint32_t *data); // pin arrangements.
@ -50,6 +50,7 @@ class Adafruit_Protomatter : public GFXcanvas16 {
uint32_t bufferSize; // Bytes per matrix buffer (1 or 2)
uint32_t bitZeroPeriod; // Bitplane 0 timer period
uint32_t minPeriod; // Plane 0 timer period for ~400Hz
volatile uint32_t *addrPortToggle; // See singleAddrPort below
volatile uint32_t frameCount; // For estimating refresh rate
uint8_t bytesPerElement; // Using 8, 16 or 32 bits of PORT?
uint8_t clockPin; // Data clock pin (Arduino pin #)
@ -61,9 +62,11 @@ class Adafruit_Protomatter : public GFXcanvas16 {
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
};

16
arch.h
View file

@ -51,6 +51,16 @@ _PM_chunkSize: Matrix bitmap width (both in RAM and as issued
certain chunkSizes are actually implemented,
see .cpp code (avoiding GCC-specific tricks
that would handle arbitrary chunk sizes).
_PM_clockHoldHigh: Additional code (typically some number of NOPs)
needed to delay the clock fall after RGB data is
written to PORT. Only required on fast devices.
If left undefined, no delay happens.
_PM_clockHoldLow: Additional code (e.g. NOPs) needed to delay
clock rise after writing RGB data to PORT.
No delay if left undefined.
_PM_minMinPeriod: Mininum value for the "minPeriod" class member,
so bit-angle-modulation time always doubles with
each bitplane (else lower bits may be the same).
*/
@ -180,6 +190,8 @@ _PM_chunkSize: Matrix bitmap width (both in RAM and as issued
#define _PM_clockHoldHigh asm("nop; nop; nop");
#endif
#define _PM_minMinPeriod 160
#else
// SAMD21 (presumably) -------------------------------------------------
@ -301,4 +313,8 @@ _PM_chunkSize: Matrix bitmap width (both in RAM and as issued
#define _PM_clockHoldLow
#endif
#if !defined(_PM_minMinPeriod)
#define _PM_minMinPeriod 100
#endif
#endif // _PM_ARCH_H_

View file

@ -0,0 +1,139 @@
#include "Adafruit_Protomatter.h"
/*
METRO M0 PORT-TO-PIN ASSIGNMENTS BY BYTE:
PA00 PA08 D4 PA16 D11 PB00 PB08 A1
PA01 PA09 D3 PA17 D13 PB01 PB09 A2
PA02 A0 PA10 D1 PA18 D10 PB02 A5 PB10 MOSI
PA03 PA11 D0 PA19 D12 PB03 PB11 SCK
PA04 A3 PA12 MISO PA20 D6 PB04 PB12
PA05 A4 PA13 PA21 D7 PB05 PB13
PA06 D8 PA14 D2 PA22 SDA PB06 PB14
PA07 D9 PA15 D5 PA23 SCL PB07 PB15
SAME, METRO M4:
PA00 PA08 PA16 D13 PB00 PB08 A4 PB16 D3
PA01 PA09 PA17 D12 PB01 PB09 A5 PB17 D2
PA02 A0 PA10 PA18 D10 PB02 SDA PB10 PB18
PA03 PA11 PA19 D11 PB03 SCL PB11 PB19
PA04 A3 PA12 MISO PA20 D9 PB04 PB12 D7 PB20
PA05 A1 PA13 SCK PA21 D8 PB05 PB13 D4 PB21
PA06 A2 PA14 MISO PA22 D1 PB06 PB14 D5 PB22
PA07 PA15 PA23 D0 PB07 PB15 D6 PB23
FEATHER M4:
PA00 PA08 PA16 D5 PB08 A2 PB16 D1/TX
PA01 PA09 PA17 SCK PB09 A3 PB17 D0/RX
PA02 A0 PA10 PA18 D6 PB10 PB18
PA03 PA11 PA19 D9 PB11 PB19
PA04 A4 PA12 SDA PA20 D10 PB12 PB20
PA05 A1 PA13 SCL PA21 D11 PB13 PB21
PA06 A5 PA14 D4 PA22 D12 PB14 PB22 MISO
PA07 PA15 PA23 D13 PB15 PB23 MOSI
FEATHER M0:
PA00 PA08 PA16 D11 PB00 PB08 A1
PA01 PA09 PA17 D13 PB01 PB09 A2
PA02 A0 PA10 TX/D1 PA18 D10 PB02 A5 PB10 MOSI
PA03 PA11 RX/D0 PA19 D12 PB03 PB11 SCK
PA04 A3 PA12 MISO PA20 D6 PB04 PB12
PA05 A4 PA13 PA21 D7 PB05 PB13
PA06 PA14 PA22 SDA PB06 PB14
PA07 D9 PA15 D5 PA23 SCL PB07 PB15
RGB Matrix FeatherWing:
R1 D6 A A5
G1 D5 B A4
B1 D9 C A3
R2 D11 D A2
G2 D10 LAT D0/RX
B2 D12 OE D1/TX
CLK D13
RGB+clock in one PORT byte on Feather M4!
RGB+clock are on same PORT but not within same byte on Feather M0 --
the code could run there (with some work to be done in the convert_*
functions), but would be super RAM-inefficient. Should be fine on other
M0 devices like a Metro, if wiring manually so one can pick a contiguous
byte of PORT bits.
*/
#if defined(__SAMD51__)
// Use FeatherWing pinout
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2};
uint8_t clockPin = 13;
uint8_t latchPin = 0;
uint8_t oePin = 1;
#else // SAMD21
uint8_t rgbPins[] = {6, 7, 10, 11, 12, 13};
uint8_t addrPins[] = {0, 1, 2, 3};
uint8_t clockPin = SDA;
uint8_t latchPin = 4;
uint8_t oePin = 5;
#endif
// Last arg here enables double-buffering
Adafruit_Protomatter matrix(
64, 6, 1, rgbPins, 4, addrPins, clockPin, latchPin, oePin, true);
const char str[] = "Adafruit 16x32 RGB LED Matrix";
int16_t textX = matrix.width(),
textMin = sizeof(str) * -12,
hue = 0;
int8_t ball[3][4] = {
{ 3, 0, 1, 1 }, // Initial X,Y pos & velocity for 3 bouncy balls
{ 17, 15, 1, -1 },
{ 27, 4, -1, 1 }
};
const uint16_t ballcolor[3] = {
0b0000000001000000, // Dark green
0b0000000000000001, // Dark blue
0b0000100000000000 // Dark red
};
void setup(void) {
Serial.begin(9600);
ProtomatterStatus status = matrix.begin();
Serial.print("Protomatter begin() status: ");
Serial.println((int)status);
matrix.setTextWrap(false);
matrix.setTextSize(2);
matrix.setTextColor(0xFFFF); // White
}
void loop(void) {
byte i;
// Clear background
matrix.fillScreen(0);
// Bounce three balls around
for(i=0; i<3; i++) {
// Draw 'ball'
matrix.fillCircle(ball[i][0], ball[i][1], 5, ballcolor[i]);
// Update X, Y position
ball[i][0] += ball[i][2];
ball[i][1] += ball[i][3];
// Bounce off edges
if((ball[i][0] == 0) || (ball[i][0] == (matrix.width() - 1)))
ball[i][2] *= -1;
if((ball[i][1] == 0) || (ball[i][1] == (matrix.height() - 1)))
ball[i][3] *= -1;
}
// Draw big scrolly text on top
matrix.setCursor(textX, 1);
matrix.print(str);
// Move text left (w/wrap), increase hue
if((--textX) < textMin) textX = matrix.width();
hue += 7;
if(hue >= 1536) hue -= 1536;
matrix.show();
delay(20);
}