Compare commits

...

13 commits

Author SHA1 Message Date
c411714cbd circuitpython: nrf port 2020-04-01 11:57:16 -05:00
c3a3e35731 Factor out _PM_convert_565, _PM_swapbuffer_maybe 2020-03-20 10:12:32 -05:00
7f448357d0 Merge remote-tracking branch 'origin/master' into circuitpython 2020-03-20 10:04:21 -05:00
969672dff1 Adapt to CircuitPython / samd51 2020-03-20 10:02:57 -05:00
Paint Your Dragon
5a1151db28
Merge pull request #4 from adafruit/nrf52
Initial nRF52 support, tested on 64x32 matrix
2020-03-17 17:38:42 -07:00
Phillip Burgess
e1659f270c nRF52840 tested and WORKING 2020-03-17 17:36:46 -07:00
Phillip Burgess
20c3b67bc3 nRF52 seems to work with some caveats
Plane-zero interval is hardcoded for now. Haven’t connected to matrix yet, just looking on scope.
2020-03-17 15:18:52 -07:00
Phillip Burgess
103ea89935 nRF52: timer oopsie + pin sudoku 2020-03-17 13:12:13 -07:00
Phillip Burgess
51f39d9594 More nRF fiddling (not yet working) 2020-03-17 12:33:12 -07:00
Paint Your Dragon
0958b759a5
Merge pull request #3 from adafruit/nrf52
SAMD21 fixes & cleanup, initial nRF52 attempt (not yet working)
2020-03-16 22:35:32 -07:00
Phillip Burgess
97c2122ba7 nRF timer almost starting to semi-work 2020-03-16 22:31:33 -07:00
Phillip Burgess
250675d2fc Pin changes for nRF52 2020-03-16 16:37:52 -07:00
Phillip Burgess
831a253898 nRF, initial GPIO stuff, very WIP 2020-03-16 15:49:58 -07:00
6 changed files with 458 additions and 142 deletions

View file

@ -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
View file

@ -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
View file

@ -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
View file

@ -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"

View file

@ -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

View file

@ -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(