Compare commits

..

No commits in common. "port-stm32f405" and "master" have entirely different histories.

6 changed files with 178 additions and 618 deletions

View file

@ -55,15 +55,33 @@ Adafruit_Protomatter::~Adafruit_Protomatter(void) {
ProtomatterStatus Adafruit_Protomatter::begin(void) {
_PM_protoPtr = &core;
return _PM_begin(&core);
_PM_begin(&core);
return PROTOMATTER_OK;
}
// 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) {
_PM_convert_565(&core, getBuffer(), WIDTH);
_PM_swapbuffer_maybe(&core);
// 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);
}
}
// Returns current value of frame counter and resets its value to zero.

625
arch.h
View file

@ -125,27 +125,10 @@ _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 "shared-bindings/microcontroller/Pin.h"
#include "py/mphal.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)
@ -157,9 +140,13 @@ _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)
#define _PM_wordOffset(pin) (g_APinDescription[pin].ulPin / 16)
#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_wordOffset(pin) (1 - (g_APinDescription[pin].ulPin / 16))
#endif
@ -183,44 +170,9 @@ _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
// Other port byte offset macros, timer and ISR work go here.
// Non-arduino byte offset macros, timer and ISR work go here.
#endif
@ -231,7 +183,6 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
#endif // __SAMD51__ || _SAMD21_
// SAMD51-SPECIFIC CODE ----------------------------------------------------
#if defined(__SAMD51__)
@ -251,25 +202,9 @@ _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
// Other port register lookups go here
// Non-Arduino port register lookups go here
#endif
@ -280,44 +215,32 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
IRQn_Type IRQn; // Interrupt number
uint8_t GCLK_ID; // Peripheral channel # for clock source
} timer[] = {
#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
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(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])
@ -451,98 +374,94 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
// Initialize, but do not start, timer
void _PM_timerInit(void *tptr) {
static const struct {
Tc *tc; // -> Timer/counter peripheral base address
IRQn_Type IRQn; // Interrupt number
uint8_t GCM_ID; // GCLK selection ID
} timer[] = {
#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 },
#endif
#if defined(TC3)
{ TC3, TC3_IRQn, GCM_TCC2_TC3 },
#endif
#if defined(TC4)
{ TC4, TC4_IRQn, GCM_TC4_TC5 },
#endif
};
#define NUM_TIMERS (sizeof timer / sizeof timer[0])
void _PM_timerInit(void *tptr) {
static const struct {
Tc *tc; // -> Timer/counter peripheral base address
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(TC2)
TC2, TC2_IRQn, GCM_TCC2_TC3,
#endif
#if defined(TC3)
TC3, TC3_IRQn, GCM_TCC2_TC3,
#endif
#if defined(TC4)
TC4, TC4_IRQn, GCM_TC4_TC5,
#endif
};
#define NUM_TIMERS (sizeof timer / sizeof timer[0])
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
uint8_t timerNum = 0;
while((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tc)) {
timerNum++;
}
if(timerNum >= NUM_TIMERS) return;
uint8_t timerNum = 0;
while((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tc)) {
timerNum++;
}
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);
// 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);
// Counter must first be disabled to configure it
tc->COUNT16.CTRLA.bit.ENABLE = 0;
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
// Counter must first be disabled to configure it
tc->COUNT16.CTRLA.bit.ENABLE = 0;
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);
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);
tc->COUNT16.CTRLBCLR.reg = TCC_CTRLBCLR_DIR; // Count up
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
tc->COUNT16.CTRLBCLR.reg = TCC_CTRLBCLR_DIR; // Count up
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
// Overflow interrupt
tc->COUNT16.INTENSET.reg = TC_INTENSET_OVF;
// Overflow interrupt
tc->COUNT16.INTENSET.reg = TC_INTENSET_OVF;
NVIC_DisableIRQ(timer[timerNum].IRQn);
NVIC_ClearPendingIRQ(timer[timerNum].IRQn);
NVIC_SetPriority(timer[timerNum].IRQn, 0); // Top priority
NVIC_EnableIRQ(timer[timerNum].IRQn);
NVIC_DisableIRQ(timer[timerNum].IRQn);
NVIC_ClearPendingIRQ(timer[timerNum].IRQn);
NVIC_SetPriority(timer[timerNum].IRQn, 0); // Top priority
NVIC_EnableIRQ(timer[timerNum].IRQn);
// Timer is configured but NOT enabled by default
}
// Timer is configured but NOT enabled by default
}
// Set timer period, initialize count value to zero, enable timer.
// Timer must be initialized to 16-bit mode using the init function
// above, but must be inactive before calling this.
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);
tc->COUNT16.CC[0].reg = period;
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
tc->COUNT16.CTRLA.bit.ENABLE = 1;
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
}
// Set timer period, initialize count value to zero, enable timer.
// Timer must be initialized to 16-bit mode using the init function
// above, but must be inactive before calling this.
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);
tc->COUNT16.CC[0].reg = period;
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
tc->COUNT16.CTRLA.bit.ENABLE = 1;
while(tc->COUNT16.STATUS.bit.SYNCBUSY);
}
// Return current count value (timer enabled or not).
// Timer must be previously initialized.
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);
return tc->COUNT16.COUNT.reg;
}
// Return current count value (timer enabled or not).
// Timer must be previously initialized.
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);
return tc->COUNT16.COUNT.reg;
}
// Disable timer and return current count value.
// Timer must be previously initialized.
inline uint32_t _PM_timerStop(void *tptr) {
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);
return count;
}
// Disable timer and return current count value.
// Timer must be previously initialized.
inline uint32_t _PM_timerStop(void *tptr) {
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);
return count;
}
#endif // _SAMD21_
@ -550,312 +469,9 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
// 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)
@ -880,17 +496,9 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
#define _PM_minMinPeriod 100
#endif
#ifndef _PM_ALLOCATOR
#define _PM_ALLOCATOR(x) (malloc((x)))
#endif
#ifndef _PM_FREE
#define _PM_FREE(x) (free((x)))
#endif
// ARDUINO SPECIFIC CODE ---------------------------------------------------
#if defined(ARDUINO) || defined(CIRCUITPY)
#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
@ -913,11 +521,10 @@ _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.
__attribute__((noinline))
void _PM_convert_565_byte(Protomatter_core *core, const uint16_t *source,
void _PM_convert_565_byte(Protomatter_core *core, uint16_t *source,
uint16_t width) {
const uint16_t *upperSrc = source; // Canvas top half
const uint16_t *lowerSrc = source + width * core->numRowPairs; // " bottom half
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) {
@ -1224,34 +831,6 @@ void _PM_convert_565_long(Protomatter_core *core, uint16_t *source,
}
}
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
#endif
#endif // ARDUINO
#endif // _PROTOMATTER_ARCH_H_

93
core.c
View file

@ -13,8 +13,6 @@
// things hopefully makes function and variable name collisions much less
// likely with one's own code.
#include <stddef.h>
#include <string.h>
#include "core.h" // enums and structs
#include "arch.h" // Do NOT include this in any other source files
@ -56,9 +54,6 @@ 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))
#define _PM_setReg(x) (*(volatile _PM_PORT_TYPE*)((x).setReg) = ((x).bit))
// 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.)
@ -76,14 +71,10 @@ ProtomatterStatus _PM_init(Protomatter_core *core,
// (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;
#else
if(timer == NULL) return PROTOMATTER_ERR_ARG;
#endif
core->timer = timer;
core->width = bitWidth; // Total matrix chain length in bits
@ -104,15 +95,15 @@ ProtomatterStatus _PM_init(Protomatter_core *core,
// the pin bitmasks.
rgbCount *= 6; // Convert parallel count to pin count
if((core->rgbPins = (uint8_t *)_PM_ALLOCATOR(rgbCount * sizeof(uint8_t)))) {
if((core->addr = (_PM_pin *)_PM_ALLOCATOR(addrCount * sizeof(_PM_pin)))) {
if((core->rgbPins = (uint8_t *)malloc(rgbCount * sizeof(uint8_t)))) {
if((core->addr = (_PM_pin *)malloc(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;
}
_PM_FREE(core->rgbPins);
free(core->rgbPins);
core->rgbPins = NULL;
}
return PROTOMATTER_ERR_MALLOC;
@ -193,8 +184,8 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
// Allocate matrix buffer(s). Don't worry about the return type...
// though we might be using words or longs for certain pin configs,
// _PM_ALLOCATOR() by definition always aligns to the longest type.
if(!(core->screenData = (uint8_t *)_PM_ALLOCATOR(screenBytes + rgbMaskBytes))) {
// malloc() by definition always aligns to the longest type.
if(!(core->screenData = (uint8_t *)malloc(screenBytes + rgbMaskBytes))) {
return PROTOMATTER_ERR_MALLOC;
}
@ -347,7 +338,7 @@ void _PM_stop(Protomatter_core *core) {
if((core)) {
while(core->swapBuffers); // Wait for any pending buffer swap
_PM_timerStop(core->timer); // Halt timer
_PM_setReg(core->oe); // Set OE HIGH (disable output)
*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
@ -364,8 +355,8 @@ void _PM_stop(Protomatter_core *core) {
_PM_clockHoldLow;
}
// Latch data
_PM_setReg(core->latch);
_PM_clearReg(core->latch);
*core->latch.setReg = core->latch.bit;
*core->latch.clearReg = core->latch.bit;
}
}
@ -388,10 +379,10 @@ void _PM_free(Protomatter_core *core) {
if((core)) {
_PM_stop(core);
// TO DO: Set all pins back to inputs here?
if(core->screenData) _PM_FREE(core->screenData);
if(core->addr) _PM_FREE(core->addr);
if(core->screenData) free(core->screenData);
if(core->addr) free(core->addr);
if(core->rgbPins) {
_PM_FREE(core->rgbPins);
free(core->rgbPins);
core->rgbPins = NULL;
}
}
@ -401,13 +392,13 @@ 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_setReg(core->oe); // Disable LED output
*core->oe.setReg = core->oe.bit; // Disable LED output
_PM_setReg(core->latch);
*core->latch.setReg = core->latch.bit; // Latch data from PRIOR pass
// 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
_PM_clearReg(core->latch); // (split to add a few cycles)
*core->latch.clearReg = core->latch.bit; // (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
@ -449,9 +440,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
_PM_setReg(core->addr[line]);
*core->addr[line].setReg = core->addr[line].bit;
} else { // Set addr line low
_PM_clearReg(core->addr[line]);
*core->addr[line].clearReg = core->addr[line].bit;
}
_PM_delayMicroseconds(_PM_ROW_DELAY);
}
@ -482,7 +473,7 @@ 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);
_PM_clearReg(core->oe); // Enable LED output
*core->oe.clearReg = core->oe.bit; // Enable LED output
uint32_t elementsPerLine = _PM_chunkSize *
((core->width + (_PM_chunkSize - 1)) / _PM_chunkSize);
@ -523,9 +514,9 @@ void _PM_row_handler(Protomatter_core *core) {
#define PEW \
*set = *data++; /* Set RGB data high */ \
_PM_clockHoldLow; \
*set_full = clock; /* Set clock high */ \
*set32 = clock; /* Set clock high */ \
_PM_clockHoldHigh; \
*clear_full = rgbclock; /* Clear RGB data + clock */
*clear32 = rgbclock; /* Clear RGB data + clock */
#endif
#if _PM_chunkSize == 1
@ -567,14 +558,14 @@ static void blast_byte(Protomatter_core *core, uint8_t *data) {
// 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 _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
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
#endif
_PM_PORT_TYPE clock = core->clockMask; // Clock bit
uint32_t 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
@ -601,15 +592,15 @@ static void blast_word(Protomatter_core *core, uint16_t *data) {
volatile uint16_t *toggle = (volatile uint16_t *)core->toggleReg +
core->portOffset;
#else
volatile uint16_t *set; // For RGB data set
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;
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
volatile uint16_t *set; // For RGB data set
volatile uint32_t *set32; // For clock set
volatile uint32_t *clear32; // 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
#endif
_PM_PORT_TYPE clock = core->clockMask; // Clock bit
uint32_t clock = core->clockMask; // Clock bit
uint8_t chunks = (core->width + (_PM_chunkSize - 1)) / _PM_chunkSize;
while(chunks--) {
PEW_UNROLL // _PM_chunkSize RGB+clock writes
@ -629,15 +620,15 @@ static void blast_long(Protomatter_core *core, uint32_t *data) {
// Note in this case two copies exist of the PORT set register.
// 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 _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;
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
volatile uint32_t *set; // For RGB data set
volatile uint32_t *set32; // For clock set
volatile uint32_t *clear32; // 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
#endif
_PM_PORT_TYPE clock = core->clockMask; // Clock bit
uint32_t clock = core->clockMask; // Clock bit
uint8_t chunks = (core->width + (_PM_chunkSize - 1)) / _PM_chunkSize;
while(chunks--) {
PEW_UNROLL // _PM_chunkSize RGB+clock writes

16
core.h
View file

@ -23,8 +23,8 @@ typedef enum {
// 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 void *setReg; // GPIO bit set register
volatile void *clearReg; // GPIO bit clear register
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 #
} _PM_pin;
@ -48,7 +48,7 @@ typedef struct {
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
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)
@ -93,9 +93,15 @@ extern uint32_t _PM_getFrameCount(Protomatter_core *core);
extern void _PM_timerStart(void *tptr, uint32_t period);
extern uint32_t _PM_timerStop(void *tptr);
extern uint32_t _PM_timerGetCount(void *tptr);
extern void _PM_convert_565(Protomatter_core *core,
#if defined(ARDUINO)
extern void _PM_convert_565_byte(Protomatter_core *core,
uint16_t *source, uint16_t width);
extern void _PM_swapbuffer_maybe(Protomatter_core *core);
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"

View file

@ -41,16 +41,6 @@ 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
@ -65,7 +55,6 @@ 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__)
@ -75,18 +64,12 @@ RGB+clock are on different PORTs on nRF52840.
uint8_t clockPin = 13;
uint8_t latchPin = 0;
uint8_t oePin = 1;
#elif defined(_SAMD21_)
#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;
#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,16 +41,6 @@ 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
@ -65,7 +55,6 @@ 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__)
@ -75,18 +64,12 @@ RGB+clock are on different PORTs on nRF52840.
uint8_t clockPin = 13;
uint8_t latchPin = 0;
uint8_t oePin = 1;
#elif defined(_SAMD21_)
#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;
#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(