Some Arduino progress!

This commit is contained in:
Phillip Burgess 2020-03-04 14:07:56 -08:00
parent 1cb7eee459
commit 36ff73e2e6
4 changed files with 172 additions and 151 deletions

View file

@ -65,17 +65,14 @@ ProtomatterStatus Adafruit_Protomatter::begin(void) {
// used by the matrix-driving loop.
void Adafruit_Protomatter::show(void) {
uint8_t *dest = (uint8_t *)core.screenData;
if(core.doubleBuffer) {
dest += core.bufferSize * (1 - core.activeBuffer);
}
// Dest address is computed in convert function
// (based on active buffer value).
if(core.bytesPerElement == 1) {
convert_byte((uint8_t *)dest);
_PM_convert_565_byte(&core, getBuffer(), WIDTH);
} else if(core.bytesPerElement == 2) {
convert_word((uint16_t *)dest);
_PM_convert_565_word(&core, getBuffer(), WIDTH);
} else {
convert_long((uint32_t *)dest);
_PM_convert_565_long(&core, getBuffer(), WIDTH);
}
if(core.doubleBuffer) {
@ -86,125 +83,6 @@ void Adafruit_Protomatter::show(void) {
}
}
// Process data from GFXcanvas16 into screenData buffer
// There are THREE COPIES of the following function -- one each for byte,
// word and long. If changes are made in any one of them, the others MUST
// be updated to match! Note that they are not simple duplicates of each
// other. The byte case, for example, doesn't need to handle parallel
// matrix chains (matrix data can only be byte-sized if one chain).
void Adafruit_Protomatter::convert_byte(uint8_t *dest) {
uint16_t *upperSrc = getBuffer(); // Canvas top half
uint16_t *lowerSrc = upperSrc + WIDTH * core.numRowPairs; // " bottom half
uint8_t *pinMask = (uint8_t *)core.rgbMask; // Pin bitmasks
// No need to clear matrix buffer, loops below do a full overwrite
// (except for any scanline pad, which was already initialized in the
// begin() function and won't be touched here).
// Determine matrix bytes per bitplane & row (row pair really):
uint8_t chunkSize = _PM_getChunkSize();
uint32_t bitplaneSize = chunkSize *
((WIDTH + (chunkSize - 1)) / chunkSize); // 1 plane of row pair
uint8_t pad = bitplaneSize - WIDTH; // Start-of-plane pad
// Skip initial scanline padding if present (HUB75 matrices shift data
// in from right-to-left, so if we need scanline padding it occurs at
// the start of a line, rather than the usual end). Destination pointer
// passed in already handles double-buffer math, so we don't need to
// handle that here, just the pad...
dest += pad;
uint32_t initialRedBit, initialGreenBit, initialBlueBit;
if(core.numPlanes == 6) {
// If numPlanes is 6, red and blue are expanded from 5 to 6 bits.
// This involves duplicating the MSB of the 5-bit value to the LSB
// of its corresponding 6-bit value...or in this case, bitmasks for
// red and blue are initially assigned to canvas MSBs, while green
// starts at LSB (because it's already 6-bit). Inner loop below then
// wraps red & blue after the first bitplane.
initialRedBit = 0b1000000000000000; // MSB red
initialGreenBit = 0b0000000000100000; // LSB green
initialBlueBit = 0b0000000000010000; // MSB blue
} else {
// If numPlanes is 1 to 5, no expansion is needed, and one or all
// three color components might be decimated by some number of bits.
// The initial bitmasks are set to the components' numPlanesth bit
// (e.g. for 5 planes, start at red & blue bit #0, green bit #1,
// for 4 planes, everything starts at the next bit up, etc.).
uint8_t shiftLeft = 5 - core.numPlanes;
initialRedBit = 0b0000100000000000 << shiftLeft;
initialGreenBit = 0b0000000001000000 << shiftLeft;
initialBlueBit = 0b0000000000000001 << shiftLeft;
}
// This works sequentially-ish through the destination buffer,
// reading from the canvas source pixels in repeated passes,
// beginning from the least bit.
for(uint8_t row=0; row<core.numRowPairs; row++) {
uint32_t redBit = initialRedBit;
uint32_t greenBit = initialGreenBit;
uint32_t blueBit = initialBlueBit;
for(uint8_t plane=0; plane<core.numPlanes; plane++) {
#if defined(_PM_portToggleRegister)
uint8_t prior = clockMask; // Set clock bit on 1st out
#endif
for(uint16_t x=0; x<WIDTH; x++) {
uint16_t upperRGB = upperSrc[x]; // Pixel in upper half
uint16_t lowerRGB = lowerSrc[x]; // Pixel in lower half
uint8_t result = 0;
if(upperRGB & redBit) result |= pinMask[0];
if(upperRGB & greenBit) result |= pinMask[1];
if(upperRGB & blueBit) result |= pinMask[2];
if(lowerRGB & redBit) result |= pinMask[3];
if(lowerRGB & greenBit) result |= pinMask[4];
if(lowerRGB & blueBit) result |= pinMask[5];
#if defined(_PM_portToggleRegister)
dest[x] = result ^ prior;
prior = result | clockMask; // Set clock bit on next out
#else
dest[x] = result;
#endif
} // end x
greenBit <<= 1;
if(plane || (core.numPlanes < 6)) {
// In most cases red & blue bit scoot 1 left...
redBit <<= 1;
blueBit <<= 1;
} else {
// Exception being after bit 0 with 6-plane display,
// in which case they're reset to red & blue LSBs
// (so 5-bit colors are expanded to 6 bits).
redBit = 0b0000100000000000;
blueBit = 0b0000000000000001;
}
#if defined(_PM_portToggleRegister)
// If using bit-toggle register, erase the toggle bit on the
// first element of each bitplane & row pair. The matrix-driving
// interrupt functions correspondingly set the clock low before
// finishing. This is all done for legibility on oscilloscope --
// so idle clock appears LOW -- but really the matrix samples on
// a rising edge and we could leave it high, but at this stage
// in development just want the scope "readable."
dest[-pad] &= ~clockMask; // Negative index is legal & intentional
#endif
dest += bitplaneSize; // Advance one scanline in dest buffer
} // end plane
upperSrc += WIDTH; // Advance one scanline in source buffer
lowerSrc += WIDTH;
} // end row
}
void Adafruit_Protomatter::convert_word(uint16_t *dest) {
// TO DO
}
void Adafruit_Protomatter::convert_long(uint32_t *dest) {
// TO DO
}
// 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

4
arch.h
View file

@ -154,6 +154,9 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
#define _PM_TIMER_DEFAULT TC4
#define _PM_IRQ_HANDLER TC4_Handler
#define _PM_timerFreq 48000000
// Partly because IRQs must be declared at compile-time, and partly
// because we know Arduino's already set up one of the GCLK sources
// for 48 MHz.
// Because it's tied to a specific timer right now, there can be only
// one instance of the Protomatter_core struct. The Arduino library
@ -336,6 +339,7 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
#define _PM_clockHoldLow asm("nop");
#else
#define _PM_clockHoldHigh asm("nop; nop; nop");
#define _PM_clockHoldLow asm("nop");
#endif
#define _PM_minMinPeriod 160

177
core.c
View file

@ -324,9 +324,9 @@ 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
_PM_timerStop(core->timer); // Halt timer
_PM_pinHigh(core->oe.pin); // Set OE HIGH (disable output)
while(core->swapBuffers); // Wait for any pending buffer swap
_PM_timerStop(core->timer); // Halt timer
*core->oe.setReg = core->oe.bit; // Set OE HIGH (disable output)
// So, in PRINCIPLE, setting OE high would be sufficient...
// but in case that pin is shared with another function such
// as the onloard LED (which pulses during bootloading) let's
@ -343,8 +343,8 @@ void _PM_stop(Protomatter_core *core) {
_PM_clockHoldLow;
}
// Latch data
_PM_pinHigh(core->latch.pin);
_PM_pinLow(core->latch.pin);
*core->latch.setReg = core->latch.bit;
*core->latch.clearReg = core->latch.bit;
}
}
@ -380,7 +380,7 @@ void _PM_free(Protomatter_core *core) {
// ISR function (in arch.h) calls this function which it extern'd.
void _PM_row_handler(Protomatter_core *core) {
_PM_pinHigh(core->oe.pin); // Disable LED output
*core->oe.setReg = core->oe.bit; // Disable LED output
// Stop timer, save count value at stop
uint32_t elapsed = _PM_timerStop(core->timer);
@ -411,12 +411,11 @@ void _PM_row_handler(Protomatter_core *core) {
// Make bitmasks of prior and new row bits
uint32_t priorBits = 0, newBits = 0;
for(uint8_t line=0,bit=1; line<core->numAddressLines; line++, bit<<=1) {
// Don't have to look up the bitmasks here, they're stored in addr struct
if(core->row & bit) {
newBits |= _PM_portBitMask(core->addr[line].pin);
newBits |= core->addr[line].bit;
}
if(core->prevRow & bit) {
priorBits |= _PM_portBitMask(core->addr[line].pin);
priorBits |= core->addr[line].bit;
}
}
*core->addrPortToggle = newBits ^ priorBits;
@ -462,14 +461,12 @@ 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);
// Some of these pinLows and stuff...you have the port and mask,
// can use those.
_PM_pinLow(core->oe.pin);
*core->oe.clearReg = core->oe.bit; // Enable LED output
uint32_t elementsPerLine = _PM_chunkSize *
((core->width + (_PM_chunkSize - 1)) / _PM_chunkSize);
uint32_t srcOffset = elementsPerLine * (core->numPlanes * core->row + core->plane) *
core->bytesPerElement;
uint32_t srcOffset = elementsPerLine *
(core->numPlanes * core->row + core->plane) * core->bytesPerElement;
if(core->doubleBuffer) {
srcOffset += core->bufferSize * core->activeBuffer;
}
@ -571,7 +568,8 @@ static void blast_byte(Protomatter_core *core, uint8_t *data) {
// This is implicit in the no-toggle case (due to how the PEW macro
// works), but toggle case requires explicitly clearing those bits.
// rgbAndClockMask is an 8-bit value when toggling, hence offset here.
*((volatile uint8_t *)core->clearReg + core->portOffset) = core->rgbAndClockMask;
*((volatile uint8_t *)core->clearReg + core->portOffset) =
core->rgbAndClockMask;
#endif
}
@ -638,12 +636,145 @@ uint32_t _PM_getFrameCount(Protomatter_core *core) {
return count;
}
// Some mid-level code requires knowledge of the _PM_chunkSize setting,
// but it's declared in arch.h, which is unique to core.c. For example,
// in the Arduino class, when converting a GFXcanvas16 image to
// Protomatter's format. It can be used for estimating storage and
// dealing with loops there, but not for unrolling loops in the same
// way as core.c does.
inline uint8_t _PM_getChunkSize(void) {
return _PM_chunkSize;
#if defined(ARDUINO)
// 16-bit (565) color conversion functions go here (rather than in the
// Arduino lib .cpp) because knowledge is required of chunksize and the
// toggle register (or lack thereof), which are only known to this file,
// not the .cpp or anywhere else
// However...this file knows nothing of the GFXcanvas16 type (from
// Adafruit_GFX...another C++ lib), so the .cpp just passes down some
// pointers and info about the canvas buffer.
// It's probably not ideal but this is my life now, oh well.
// Different runtime environments (which might not use the 565 canvas
// format) will need their own conversion functions.
// There are THREE COPIES of the following function -- one each for byte,
// word and long. If changes are made in any one of them, the others MUST
// be updated to match! Note that they are not simple duplicates of each
// other. The byte case, for example, doesn't need to handle parallel
// matrix chains (matrix data can only be byte-sized if one chain).
// width argument comes from GFX canvas width, which may be less than
// core's bitWidth (due to padding). height isn't needed, it can be
// inferred from core->numRowPairs.
void _PM_convert_565_byte(Protomatter_core *core, uint16_t *source,
uint16_t width) {
uint16_t *upperSrc = source; // Canvas top half
uint16_t *lowerSrc = source + width * core->numRowPairs; // " bottom half
uint8_t *pinMask = (uint8_t *)core->rgbMask; // Pin bitmasks
uint8_t *dest = (uint8_t *)core->screenData;
if(core->doubleBuffer) {
dest += core->bufferSize * (1 - core->activeBuffer);
}
// No need to clear matrix buffer, loops below do a full overwrite
// (except for any scanline pad, which was already initialized in the
// begin() function and won't be touched here).
// Determine matrix bytes per bitplane & row (row pair really):
uint32_t bitplaneSize = _PM_chunkSize *
((width + (_PM_chunkSize - 1)) / _PM_chunkSize); // 1 plane of row pair
uint8_t pad = bitplaneSize - width; // Start-of-plane pad
// Skip initial scanline padding if present (HUB75 matrices shift data
// in from right-to-left, so if we need scanline padding it occurs at
// the start of a line, rather than the usual end). Destination pointer
// passed in already handles double-buffer math, so we don't need to
// handle that here, just the pad...
dest += pad;
uint32_t initialRedBit, initialGreenBit, initialBlueBit;
if(core->numPlanes == 6) {
// If numPlanes is 6, red and blue are expanded from 5 to 6 bits.
// This involves duplicating the MSB of the 5-bit value to the LSB
// of its corresponding 6-bit value...or in this case, bitmasks for
// red and blue are initially assigned to canvas MSBs, while green
// starts at LSB (because it's already 6-bit). Inner loop below then
// wraps red & blue after the first bitplane.
initialRedBit = 0b1000000000000000; // MSB red
initialGreenBit = 0b0000000000100000; // LSB green
initialBlueBit = 0b0000000000010000; // MSB blue
} else {
// If numPlanes is 1 to 5, no expansion is needed, and one or all
// three color components might be decimated by some number of bits.
// The initial bitmasks are set to the components' numPlanesth bit
// (e.g. for 5 planes, start at red & blue bit #0, green bit #1,
// for 4 planes, everything starts at the next bit up, etc.).
uint8_t shiftLeft = 5 - core->numPlanes;
initialRedBit = 0b0000100000000000 << shiftLeft;
initialGreenBit = 0b0000000001000000 << shiftLeft;
initialBlueBit = 0b0000000000000001 << shiftLeft;
}
// This works sequentially-ish through the destination buffer,
// reading from the canvas source pixels in repeated passes,
// beginning from the least bit.
for(uint8_t row=0; row<core->numRowPairs; row++) {
uint32_t redBit = initialRedBit;
uint32_t greenBit = initialGreenBit;
uint32_t blueBit = initialBlueBit;
for(uint8_t plane=0; plane<core->numPlanes; plane++) {
#if defined(_PM_portToggleRegister)
uint8_t prior = core->clockMask; // Set clock bit on 1st out
#endif
for(uint16_t x=0; x<width; x++) {
uint16_t upperRGB = upperSrc[x]; // Pixel in upper half
uint16_t lowerRGB = lowerSrc[x]; // Pixel in lower half
uint8_t result = 0;
if(upperRGB & redBit) result |= pinMask[0];
if(upperRGB & greenBit) result |= pinMask[1];
if(upperRGB & blueBit) result |= pinMask[2];
if(lowerRGB & redBit) result |= pinMask[3];
if(lowerRGB & greenBit) result |= pinMask[4];
if(lowerRGB & blueBit) result |= pinMask[5];
#if defined(_PM_portToggleRegister)
dest[x] = result ^ prior;
prior = result | core->clockMask; // Set clock bit on next out
#else
dest[x] = result;
#endif
} // end x
greenBit <<= 1;
if(plane || (core->numPlanes < 6)) {
// In most cases red & blue bit scoot 1 left...
redBit <<= 1;
blueBit <<= 1;
} else {
// Exception being after bit 0 with 6-plane display,
// in which case they're reset to red & blue LSBs
// (so 5-bit colors are expanded to 6 bits).
redBit = 0b0000100000000000;
blueBit = 0b0000000000000001;
}
#if defined(_PM_portToggleRegister)
// If using bit-toggle register, erase the toggle bit on the
// first element of each bitplane & row pair. The matrix-driving
// interrupt functions correspondingly set the clock low before
// finishing. This is all done for legibility on oscilloscope --
// so idle clock appears LOW -- but really the matrix samples on
// a rising edge and we could leave it high, but at this stage
// in development just want the scope "readable."
dest[-pad] &= ~core->clockMask; // Negative index is legal & intentional
#endif
dest += bitplaneSize; // Advance one scanline in dest buffer
} // end plane
upperSrc += width; // Advance one scanline in source buffer
lowerSrc += width;
} // end row
}
void _PM_convert_565_word(Protomatter_core *core, uint16_t *source,
uint16_t width) {
// TO DO
}
void _PM_convert_565_long(Protomatter_core *core, uint16_t *source,
uint16_t width) {
// TO DO
}
#endif // ARDUINO

10
core.h
View file

@ -90,11 +90,19 @@ extern void _PM_resume(Protomatter_core *core);
extern void _PM_free(Protomatter_core *core);
extern void _PM_row_handler(Protomatter_core *core);
extern uint32_t _PM_getFrameCount(Protomatter_core *core);
extern uint8_t _PM_getChunkSize(void);
extern void _PM_timerStart(void *tptr, uint32_t period);
extern uint32_t _PM_timerStop(void *tptr);
extern uint32_t _PM_timerGetCount(void *tptr);
#if defined(ARDUINO)
extern void _PM_convert_565_byte(Protomatter_core *core,
uint16_t *source, uint16_t width);
extern void _PM_convert_565_word(Protomatter_core *core,
uint16_t *source, uint16_t width);
extern void _PM_convert_565_long(Protomatter_core *core,
uint16_t *source, uint16_t width);
#endif // ARDUINO
#ifdef __cplusplus
} // extern "C"
#endif