Ever-so-slightly adjustable duty on SAMD51

This commit is contained in:
Phillip Burgess 2023-08-24 17:07:04 -07:00
parent ed2e701871
commit 93ea4fa10d
4 changed files with 74 additions and 0 deletions

View file

@ -242,3 +242,7 @@ _PM_CUSTOM_BLAST If defined, instructs core code to not compile
#if !defined(_PM_PORT_TYPE) #if !defined(_PM_PORT_TYPE)
#define _PM_PORT_TYPE uint32_t ///< PORT register size/type #define _PM_PORT_TYPE uint32_t ///< PORT register size/type
#endif #endif
#if !defined(_PM_maxDuty)
#define _PM_maxDuty 0 ///< Max duty cycle setting (where supported)
#endif

View file

@ -196,6 +196,59 @@ uint32_t _PM_timerStop(Protomatter_core *core) {
return count; return count;
} }
#define _PM_CUSTOM_BLAST // Disable blast_*() functions in core.c
#define _PM_maxDuty 2
extern uint8_t _PM_duty;
// Concurrent PORT accesses incur a 1-cycle delay, so these NOPs keep the
// unrolled part of the loop at a constant 17.24 MHz (@ 120 MHz F_CPU).
// Could remove them and perhaps add a fourth duty configuration, but
// the tradeoff is a slower loop at min & max duty settings.
#define PEW \
asm("nop"); \
*toggle = *data++; \
asm("nop"); \
*ptr[0] = clock; \
*ptr[1] = clock; \
*ptr[2] = clock;
static void blast_byte(Protomatter_core *core, uint8_t *data) {
// If here, it was established in begin() that the RGB data bits and
// 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.
uint8_t *toggle = (uint8_t *)core->toggleReg + core->portOffset;
uint16_t chunks = core->chainBits / 8;
// PORT has already been initialized with RGB data + clock bits
// all LOW, so we don't need to initialize that state here.
uint8_t *ptr[_PM_maxDuty + 1], bucket, clock = core->clockMask;
for (uint8_t i=0; i<=_PM_maxDuty; i++) {
ptr[i] = (_PM_duty == (_PM_maxDuty - i)) ? toggle : &bucket;
}
do {
PEW PEW PEW PEW PEW PEW PEW PEW
} while(--chunks);
// Want the PORT left with RGB data and clock LOW on function exit
// (so it's easier to see on 'scope, and to prime it for the next call).
// 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;
}
static void blast_word(Protomatter_core *core, uint16_t *data) {
}
static void blast_long(Protomatter_core *core, uint32_t *data) {
}
#if 0
// See notes in core.c before the "blast" functions. // See notes in core.c before the "blast" functions.
// The NOP counts here were derived by monitoring on a fast logic analyzer, // The NOP counts here were derived by monitoring on a fast logic analyzer,
// aiming for 20 MHz clock at 50% duty cycle (for the unrolled parts of the // aiming for 20 MHz clock at 50% duty cycle (for the unrolled parts of the
@ -220,6 +273,7 @@ uint32_t _PM_timerStop(Protomatter_core *core) {
#define _PM_clockHoldHigh asm("nop"); #define _PM_clockHoldHigh asm("nop");
#define _PM_clockHoldLow asm("nop; nop"); #define _PM_clockHoldLow asm("nop; nop");
#endif #endif
#endif // 0
#define _PM_minMinPeriod 160 #define _PM_minMinPeriod 160

View file

@ -922,6 +922,12 @@ static void _PM_resetFM6126A(Protomatter_core *core) {
_PM_rgbState(core, 0); // Set all RGB low so port toggle can work _PM_rgbState(core, 0); // Set all RGB low so port toggle can work
} }
uint8_t _PM_duty = 0;
void _PM_setDuty(uint8_t d) {
_PM_duty = (d > _PM_maxDuty) ? _PM_maxDuty : d;
}
#if defined(ARDUINO) || defined(CIRCUITPY) #if defined(ARDUINO) || defined(CIRCUITPY)
// Arduino and CircuitPython happen to use the same internal canvas // Arduino and CircuitPython happen to use the same internal canvas

View file

@ -255,6 +255,16 @@ extern uint32_t _PM_timerGetCount(Protomatter_core *core);
*/ */
extern void _PM_swapbuffer_maybe(Protomatter_core *core); extern void _PM_swapbuffer_maybe(Protomatter_core *core);
/*!
@brief Adjust duty cycle of HUB75 clock signal. This is not supported on
all architectures.
@param d Duty setting, 0 minimum. Increasing values generate higher clock
duty cycles at the same frequency. Arbitrary granular units, max
varies by architecture and CPU speed, if supported at all.
e.g. SAMD51 @ 120 MHz supports 0 (~50% duty) through 2 (~75%).
*/
extern void _PM_setDuty(uint8_t d);
#if defined(ARDUINO) || defined(CIRCUITPY) #if defined(ARDUINO) || defined(CIRCUITPY)
/*! /*!