aboutsummaryrefslogtreecommitdiff
path: root/circuitpython/lib/protomatter/src/arch/rp2040.h
blob: b098251bab9d7ed6c2448043ac44a398ef8d1b77 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
/*!
 * @file rp2040.h
 *
 * Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
 * This file contains RP2040 (Raspberry Pi Pico, etc.) SPECIFIC CODE.
 *
 * Adafruit invests time and resources providing this open source code,
 * please support Adafruit and open-source hardware by purchasing
 * products from Adafruit!
 *
 * Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for
 * Adafruit Industries, with contributions from the open source community.
 *
 * BSD license, all text here must be included in any redistribution.
 *
 * RP2040 NOTES: This initial implementation does NOT use PIO. That's normal
 * for Protomatter, which was written for simple GPIO + timer interrupt for
 * broadest portability. While not entirely optimal, it's not pessimal
 * either...no worse than any other platform where we're not taking
 * advantage of device-specific DMA or peripherals. Would require changes to
 * the 'blast' functions or possibly the whole _PM_row_handler() (both
 * currently in core.c). CPU load is just a few percent for a 64x32
 * matrix @ 6-bit depth, so I'm not losing sleep over this.
 *
 */

#pragma once

// TO DO: PUT A *PROPER* RP2040 CHECK HERE
#if defined(PICO_BOARD) || defined(__RP2040__)

#include "../../hardware_pwm/include/hardware/pwm.h"
#include "hardware/irq.h"
#include "hardware/timer.h"
#include "pico/stdlib.h" // For sio_hw, etc.

// RP2040 only allows full 32-bit aligned writes to GPIO.
#define _PM_STRICT_32BIT_IO ///< Change core.c behavior for long accesses only

// TEMPORARY: FORCING ARDUINO COMPILATION FOR INITIAL C TESTING
#if !defined(CIRCUITPY)
#define ARDUINO
#endif

// Enable this to use PWM for bitplane timing, else a timer alarm is used.
// PWM has finer resolution, but alarm is adequate -- this is more about
// which peripheral we'd rather use, as both are finite resources.
#ifndef _PM_CLOCK_PWM
#define _PM_CLOCK_PWM (1)
#endif

#if _PM_CLOCK_PWM // Use PWM for timing
static void _PM_PWM_ISR(void);
#else // Use timer alarm for timing
static void _PM_timerISR(void);
#endif

#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------

// 'pin' here is GPXX # -- that might change in Arduino implementation
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define _PM_byteOffset(pin) ((pin & 31) / 8)
#define _PM_wordOffset(pin) ((pin & 31) / 16)
#else
#define _PM_byteOffset(pin) (3 - ((pin & 31) / 8))
#define _PM_wordOffset(pin) (1 - ((pin & 31) / 16))
#endif

#if _PM_CLOCK_PWM

// Arduino implementation is tied to a specific PWM slice & frequency
#define _PM_PWM_SLICE 0
#define _PM_PWM_DIV 3 // ~41.6 MHz, similar to SAMD
#define _PM_timerFreq (125000000 / _PM_PWM_DIV)
#define _PM_TIMER_DEFAULT NULL

#else // Use alarm for timing

// Arduino implementation is tied to a specific timer alarm & frequency
#define _PM_ALARM_NUM 1
#define _PM_IRQ_HANDLER TIMER_IRQ_1
#define _PM_timerFreq 1000000
#define _PM_TIMER_DEFAULT NULL

// Initialize, but do not start, timer.
void _PM_timerInit(void *tptr) {
#if _PM_CLOCK_PWM
  // Enable PWM wrap interrupt
  pwm_clear_irq(_PM_PWM_SLICE);
  pwm_set_irq_enabled(_PM_PWM_SLICE, true);
  irq_set_exclusive_handler(PWM_IRQ_WRAP, _PM_PWM_ISR);
  irq_set_enabled(PWM_IRQ_WRAP, true);

  // Config but do not start PWM
  pwm_config config = pwm_get_default_config();
  pwm_config_set_clkdiv_int(&config, _PM_PWM_DIV);
  pwm_init(_PM_PWM_SLICE, &config, true);
#else
  timer_hw->alarm[_PM_ALARM_NUM] = timer_hw->timerawl; // Clear any timer
  hw_set_bits(&timer_hw->inte, 1u << _PM_ALARM_NUM);
  irq_set_exclusive_handler(_PM_IRQ_HANDLER, _PM_timerISR); // Set IRQ handler
#endif
}

#endif

#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------

// 'pin' here is GPXX #
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define _PM_byteOffset(pin) ((pin & 31) / 8)
#define _PM_wordOffset(pin) ((pin & 31) / 16)
#else
#define _PM_byteOffset(pin) (3 - ((pin & 31) / 8))
#define _PM_wordOffset(pin) (1 - ((pin & 31) / 16))
#endif

#if _PM_CLOCK_PWM

int _PM_pwm_slice;
#define _PM_PWM_SLICE (_PM_pwm_slice & 0xff)
#define _PM_PWM_DIV 3 // ~41.6 MHz, similar to SAMD
#define _PM_timerFreq (125000000 / _PM_PWM_DIV)
#define _PM_TIMER_DEFAULT NULL

#else // Use alarm for timing

// Currently tied to a specific timer alarm & frequency
#define _PM_ALARM_NUM 1
#define _PM_IRQ_HANDLER TIMER_IRQ_1
#define _PM_timerFreq 1000000
#define _PM_TIMER_DEFAULT NULL

#endif

// Initialize, but do not start, timer.
void _PM_timerInit(void *tptr) {
#if _PM_CLOCK_PWM
  _PM_pwm_slice = (int)tptr & 0xff;
  // Enable PWM wrap interrupt
  pwm_clear_irq(_PM_PWM_SLICE);
  pwm_set_irq_enabled(_PM_PWM_SLICE, true);
  irq_set_exclusive_handler(PWM_IRQ_WRAP, _PM_PWM_ISR);
  irq_set_enabled(PWM_IRQ_WRAP, true);

  // Config but do not start PWM
  pwm_config config = pwm_get_default_config();
  pwm_config_set_clkdiv_int(&config, _PM_PWM_DIV);
  pwm_init(_PM_PWM_SLICE, &config, true);
#else
  timer_hw->alarm[_PM_ALARM_NUM] = timer_hw->timerawl; // Clear any timer
  hw_set_bits(&timer_hw->inte, 1u << _PM_ALARM_NUM);
  irq_set_exclusive_handler(_PM_IRQ_HANDLER, _PM_timerISR); // Set IRQ handler
#endif
}

#endif

#if !_PM_CLOCK_PWM
// Unlike timers on other devices, on RP2040 you don't reset a counter to
// zero at the start of a cycle. To emulate that behavior (for determining
// elapsed times), the timer start time must be saved somewhere...
static volatile uint32_t _PM_timerSave;

#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;

#define _PM_portOutRegister(pin) ((void *)&sio_hw->gpio_out)
#define _PM_portSetRegister(pin) ((volatile uint32_t *)&sio_hw->gpio_set)
#define _PM_portClearRegister(pin) ((volatile uint32_t *)&sio_hw->gpio_clr)
#define _PM_portToggleRegister(pin) ((volatile uint32_t *)&sio_hw->gpio_togl)
// 'pin' here is GPXX # -- that might change in Arduino implementation
#define _PM_portBitMask(pin) (1UL << pin)
// Same for these -- using GPXX #, but Arduino might assign different order
#define _PM_pinOutput(pin)                                                     \
  {                                                                            \
    gpio_init(pin);                                                            \
    gpio_set_dir(pin, GPIO_OUT);                                               \
  }
#define _PM_pinLow(pin) gpio_clr_mask(1UL << pin)
#define _PM_pinHigh(pin) gpio_set_mask(1UL << pin)

#ifndef _PM_delayMicroseconds
#define _PM_delayMicroseconds(n) sleep_us(n)
#endif

#if _PM_CLOCK_PWM // Use PWM for timing
static void _PM_PWM_ISR(void) {
  pwm_clear_irq(_PM_PWM_SLICE);  // Reset PWM wrap interrupt
  _PM_row_handler(_PM_protoPtr); // In core.c
}
#else // Use timer alarm for timing
static void _PM_timerISR(void) {
  hw_clear_bits(&timer_hw->intr, 1u << _PM_ALARM_NUM); // Clear alarm flag
  _PM_row_handler(_PM_protoPtr);                       // In core.c
}
#endif

// Set timer period and enable timer.
inline void _PM_timerStart(void *tptr, uint32_t period) {
#if _PM_CLOCK_PWM
  pwm_set_counter(_PM_PWM_SLICE, 0);
  pwm_set_wrap(_PM_PWM_SLICE, period);
  pwm_set_enabled(_PM_PWM_SLICE, true);
#else
  irq_set_enabled(_PM_IRQ_HANDLER, true);                  // Enable alarm IRQ
  _PM_timerSave = timer_hw->timerawl;                      // Time at start
  timer_hw->alarm[_PM_ALARM_NUM] = _PM_timerSave + period; // Time at end
#endif
}

// Return current count value (timer enabled or not).
// Timer must be previously initialized.
inline uint32_t _PM_timerGetCount(void *tptr) {
#if _PM_CLOCK_PWM
  return pwm_get_counter(_PM_PWM_SLICE);
#else
  return timer_hw->timerawl - _PM_timerSave;
#endif
}

// Disable timer and return current count value.
// Timer must be previously initialized.
uint32_t _PM_timerStop(void *tptr) {
#if _PM_CLOCK_PWM
  pwm_set_enabled(_PM_PWM_SLICE, false);
#else
  irq_set_enabled(_PM_IRQ_HANDLER, false); // Disable alarm IRQ
#endif
  return _PM_timerGetCount(tptr);
}

#define _PM_chunkSize 8
#define _PM_clockHoldLow asm("nop; nop;");
#if _PM_CLOCK_PWM
#define _PM_minMinPeriod 100
#else
#define _PM_minMinPeriod 8
#endif

#endif // END PICO_BOARD