Some Arduino progress!
This commit is contained in:
parent
1cb7eee459
commit
36ff73e2e6
4 changed files with 172 additions and 151 deletions
|
|
@ -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
4
arch.h
|
|
@ -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
177
core.c
|
|
@ -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
10
core.h
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue