Compare commits
13 commits
master
...
circuitpyt
| Author | SHA1 | Date | |
|---|---|---|---|
| c411714cbd | |||
| c3a3e35731 | |||
| 7f448357d0 | |||
| 969672dff1 | |||
|
|
5a1151db28 | ||
|
|
e1659f270c | ||
|
|
20c3b67bc3 | ||
|
|
103ea89935 | ||
|
|
51f39d9594 | ||
|
|
0958b759a5 | ||
|
|
97c2122ba7 | ||
|
|
250675d2fc | ||
|
|
831a253898 |
6 changed files with 458 additions and 142 deletions
|
|
@ -55,33 +55,15 @@ Adafruit_Protomatter::~Adafruit_Protomatter(void) {
|
|||
|
||||
ProtomatterStatus Adafruit_Protomatter::begin(void) {
|
||||
_PM_protoPtr = &core;
|
||||
_PM_begin(&core);
|
||||
return PROTOMATTER_OK;
|
||||
return _PM_begin(&core);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
||||
// 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);
|
||||
}
|
||||
_PM_convert_565(&core, getBuffer(), WIDTH);
|
||||
_PM_swapbuffer_maybe();
|
||||
}
|
||||
|
||||
// Returns current value of frame counter and resets its value to zero.
|
||||
|
|
|
|||
504
arch.h
504
arch.h
|
|
@ -125,10 +125,23 @@ _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
|
||||
|
||||
// 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)
|
||||
|
||||
|
||||
|
|
@ -140,13 +153,9 @@ _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)
|
||||
#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_byteOffset(pin) (3 - (g_APinDescription[pin].ulPin / 8))
|
||||
#define _PM_wordOffset(pin) (1 - (g_APinDescription[pin].ulPin / 16))
|
||||
#endif
|
||||
|
||||
|
|
@ -170,9 +179,44 @@ _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) / 8)
|
||||
#define _PM_wordOffset(pin) ((pin) / 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
|
||||
|
||||
// Non-arduino byte offset macros, timer and ISR work go here.
|
||||
// Other port byte offset macros, timer and ISR work go here.
|
||||
|
||||
#endif
|
||||
|
||||
|
|
@ -183,6 +227,7 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
|
|||
#endif // __SAMD51__ || _SAMD21_
|
||||
|
||||
|
||||
|
||||
// SAMD51-SPECIFIC CODE ----------------------------------------------------
|
||||
|
||||
#if defined(__SAMD51__)
|
||||
|
|
@ -202,9 +247,25 @@ _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
|
||||
|
||||
// Non-Arduino port register lookups go here
|
||||
// Other port register lookups go here
|
||||
|
||||
#endif
|
||||
|
||||
|
|
@ -215,32 +276,44 @@ _PM_minMinPeriod: Mininum value for the "minPeriod" class member,
|
|||
IRQn_Type IRQn; // Interrupt number
|
||||
uint8_t GCLK_ID; // Peripheral channel # for clock source
|
||||
} timer[] = {
|
||||
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(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
|
||||
#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])
|
||||
|
|
@ -374,94 +447,98 @@ _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[] = {
|
||||
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])
|
||||
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])
|
||||
|
||||
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_
|
||||
|
||||
|
|
@ -469,6 +546,196 @@ _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) / 8)
|
||||
#define _PM_wordOffset(pin) ((pin) / 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
|
||||
|
||||
|
||||
|
|
@ -496,9 +763,17 @@ _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)
|
||||
#if defined(ARDUINO) || defined(CIRCUITPY)
|
||||
|
||||
// 16-bit (565) color conversion functions go here (rather than in the
|
||||
// Arduino lib .cpp) because knowledge is required of chunksize and the
|
||||
|
|
@ -521,10 +796,11 @@ _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.
|
||||
void _PM_convert_565_byte(Protomatter_core *core, uint16_t *source,
|
||||
__attribute__((noinline))
|
||||
void _PM_convert_565_byte(Protomatter_core *core, const uint16_t *source,
|
||||
uint16_t width) {
|
||||
uint16_t *upperSrc = source; // Canvas top half
|
||||
uint16_t *lowerSrc = source + width * core->numRowPairs; // " bottom half
|
||||
const uint16_t *upperSrc = source; // Canvas top half
|
||||
const 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) {
|
||||
|
|
@ -831,6 +1107,30 @@ void _PM_convert_565_long(Protomatter_core *core, uint16_t *source,
|
|||
}
|
||||
}
|
||||
|
||||
#endif // ARDUINO
|
||||
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
|
||||
|
||||
#endif // _PROTOMATTER_ARCH_H_
|
||||
|
|
|
|||
24
core.c
24
core.c
|
|
@ -13,6 +13,8 @@
|
|||
// 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
|
||||
|
||||
|
|
@ -71,10 +73,14 @@ 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
|
||||
|
|
@ -95,15 +101,15 @@ ProtomatterStatus _PM_init(Protomatter_core *core,
|
|||
// the pin bitmasks.
|
||||
|
||||
rgbCount *= 6; // Convert parallel count to pin count
|
||||
if((core->rgbPins = (uint8_t *)malloc(rgbCount * sizeof(uint8_t)))) {
|
||||
if((core->addr = (_PM_pin *)malloc(addrCount * sizeof(_PM_pin)))) {
|
||||
if((core->rgbPins = (uint8_t *)_PM_ALLOCATOR(rgbCount * sizeof(uint8_t)))) {
|
||||
if((core->addr = (_PM_pin *)_PM_ALLOCATOR(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;
|
||||
}
|
||||
free(core->rgbPins);
|
||||
_PM_FREE(core->rgbPins);
|
||||
core->rgbPins = NULL;
|
||||
}
|
||||
return PROTOMATTER_ERR_MALLOC;
|
||||
|
|
@ -184,8 +190,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,
|
||||
// malloc() by definition always aligns to the longest type.
|
||||
if(!(core->screenData = (uint8_t *)malloc(screenBytes + rgbMaskBytes))) {
|
||||
// _PM_ALLOCATOR() by definition always aligns to the longest type.
|
||||
if(!(core->screenData = (uint8_t *)_PM_ALLOCATOR(screenBytes + rgbMaskBytes))) {
|
||||
return PROTOMATTER_ERR_MALLOC;
|
||||
}
|
||||
|
||||
|
|
@ -379,10 +385,10 @@ void _PM_free(Protomatter_core *core) {
|
|||
if((core)) {
|
||||
_PM_stop(core);
|
||||
// TO DO: Set all pins back to inputs here?
|
||||
if(core->screenData) free(core->screenData);
|
||||
if(core->addr) free(core->addr);
|
||||
if(core->screenData) _PM_FREE(core->screenData);
|
||||
if(core->addr) _PM_FREE(core->addr);
|
||||
if(core->rgbPins) {
|
||||
free(core->rgbPins);
|
||||
_PM_FREE(core->rgbPins);
|
||||
core->rgbPins = NULL;
|
||||
}
|
||||
}
|
||||
|
|
@ -560,7 +566,7 @@ static void blast_byte(Protomatter_core *core, uint8_t *data) {
|
|||
volatile uint8_t *set; // For RGB data set
|
||||
volatile uint32_t *set32; // For clock set
|
||||
volatile uint32_t *clear32; // For RGB data + clock clear
|
||||
set = (volatile uint8_t *)core->setReg + portOffset;
|
||||
set = (volatile uint8_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
|
||||
|
|
|
|||
10
core.h
10
core.h
|
|
@ -93,15 +93,9 @@ 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);
|
||||
|
||||
#if defined(ARDUINO)
|
||||
extern void _PM_convert_565_byte(Protomatter_core *core,
|
||||
extern void _PM_convert_565(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
|
||||
extern void _PM_swapbuffer_maybe(Protomatter_core *core);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
|
|
|
|||
|
|
@ -41,6 +41,16 @@ 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
|
||||
|
|
@ -55,6 +65,7 @@ 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__)
|
||||
|
|
@ -64,12 +75,18 @@ byte of PORT bits.
|
|||
uint8_t clockPin = 13;
|
||||
uint8_t latchPin = 0;
|
||||
uint8_t oePin = 1;
|
||||
#else // SAMD21
|
||||
#elif defined(_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
|
||||
|
|
|
|||
|
|
@ -41,6 +41,16 @@ 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
|
||||
|
|
@ -55,6 +65,7 @@ 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__)
|
||||
|
|
@ -64,12 +75,18 @@ byte of PORT bits.
|
|||
uint8_t clockPin = 13;
|
||||
uint8_t latchPin = 0;
|
||||
uint8_t oePin = 1;
|
||||
#else // SAMD21
|
||||
#elif defined(_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(
|
||||
|
|
|
|||
Loading…
Reference in a new issue