aboutsummaryrefslogtreecommitdiff
path: root/circuitpython/lib/protomatter/src/arch/arch.h
blob: 2e28d2fe67c35b72fea2bf858b254ee64f597a1e (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
/*!
 * @file arch.h
 *
 * Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
 * This file establishes some very low-level things and includes headers
 * specific to each supported device. This should ONLY be included by
 * core.c, nowhere else. Ever.
 *
 * 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.
 *
 */

#pragma once

#include <string.h>

/*
Common ground for architectures to support this library:

- 32-bit device (e.g. ARM core, ESP32, potentially others in the future)
- One or more 32-bit GPIO PORTs with atomic bitmask SET and CLEAR registers.
  A TOGGLE register, if present, may improve performance but is NOT required.
- Tolerate 8-bit or word-aligned 16-bit accesses within the 32-bit PORT
  registers (e.g. writing just one of four bytes, rather than the whole
  32 bits). The library does not use any unaligned accesses (i.e. the
  "middle word" of a 32-bit register), even if a device tolerates such.

"Pin" as used in this code is always a uint8_t value, but the semantics
of what it means may vary between Arduino and non-Arduino situations.
In Arduino, it's the pin index one would pass to functions such as
digitalWrite(), and doesn't necessarily correspond to physical hardware
pins or any other arrangement. Some may have names like 'A0' that really
just map to higher indices.
In non-Arduino settings (CircuitPython, other languages, etc.), how a
pin index relates to hardware is entirely implementation dependent, and
how to get from one to the other is what must be implemented in this file.
Quite often an environment will follow the Arduino pin designations
(since the numbers are on a board's silkscreen) and will have an internal
table mapping those indices to registers and bitmasks...but probably not
an identically-named and -structured table to the Arduino code, hence the
reason for many "else" situations in this code.

Each architecture defines the following macros and/or functions (the _PM_
prefix on each is to reduce likelihood of naming collisions...especially
on ESP32, which has some similarly-named timer functions:

GPIO-related macros/functions:

_PM_portOutRegister(pin):    Get address of PORT out register. Code calling
                             this can cast it to whatever type's needed.
_PM_portSetRegister(pin):    Get address of PORT set-bits register.
_PM_portClearRegister(pin):  Get address of PORT clear-bits register.
_PM_portToggleRegister(pin): Get address of PORT toggle-bits register.
                             Not all devices support this, in which case
                             it must be left undefined.
_PM_portBitMask(pin):        Get bit mask within PORT register corresponding
                             to a pin number. When compiling for Arduino,
                             this just maps to digitalPinToBitMask(), other
                             environments will need an equivalent.
_PM_byteOffset(pin):         Get index of byte (0 to 3) within 32-bit PORT
                             corresponding to a pin number.
_PM_wordOffset(pin):         Get index of word (0 or 1) within 32-bit PORT
                             corresponding to a pin number.
_PM_pinOutput(pin):          Set a pin to output mode. In Arduino this maps
                             to pinMode(pin, OUTPUT). Other environments
                             will need an equivalent.
_PM_pinInput(pin):           Set a pin to input mode, no pullup. In Arduino
                             this maps to pinMode(pin, INPUT).
_PM_pinHigh(pin):            Set an output pin to a high or 1 state. In
                             Arduino this maps to digitalWrite(pin, HIGH).
_PM_pinLow(pin):             Set an output pin to a low or 0 state. In
                             Arduino this maps to digitalWrite(pin, LOW).

Timer-related macros/functions:

_PM_timerFreq:               A numerical constant - the source clock rate
                             (in Hz) that's fed to the timer peripheral.
_PM_timerInit(void*):        Initialize (but do not start) timer.
_PM_timerStart(void*,count): (Re)start timer for a given timer-tick interval.
_PM_timerStop(void*):        Stop timer, return current timer counter value.
_PM_timerGetCount(void*):    Get current timer counter value (whether timer
                             is running or stopped).
A timer interrupt service routine is also required, syntax for which varies
between architectures.
The void* argument passed to the timer functions is some indeterminate type
used to uniquely identify a timer peripheral within a given environment. For
example, in the Arduino wrapper for this library, compiling for SAMD chips,
it's just a pointer directly to a timer/counter peripheral base address. If
an implementation needs more data associated alongside a peripheral, this
could instead be a pointer to a struct, or an integer index.

Other macros/functions:

_PM_chunkSize:               Matrix bitmap width (both in RAM and as issued
                             to the device) is rounded up (if necessary) to
                             a multiple of this value as a way of explicitly
                             unrolling the innermost data-stuffing loops.
                             So far all HUB75 displays I've encountered are
                             a multiple of 32 pixels wide, but in case
                             something new comes along, or if a larger
                             unroll actually decreases performance due to
                             cache size, this can be set to whatever works
                             best (any additional data is simply shifted
                             out the other end of the matrix). Default if
                             unspecified is 8 (e.g. four loop passes on a
                             32-pixel matrix, eight if 64-pixel). Only
                             certain chunkSizes are actually implemented,
                             see .cpp code (avoiding GCC-specific tricks
                             that would handle arbitrary chunk sizes).
_PM_delayMicroseconds(us):   Function or macro to delay some number of
                             microseconds. For Arduino, this just maps to
                             delayMicroseconds(). Other environments will
                             need to provide their own or map to an
                             an equivalent function.
_PM_clockHoldHigh:           Additional code (typically some number of NOPs)
                             needed to delay the clock fall after RGB data is
                             written to PORT. Only required on fast devices.
                             If left undefined, no delay happens.
_PM_clockHoldLow:            Additional code (e.g. NOPs) needed to delay
                             clock rise after writing RGB data to PORT.
                             No delay if left undefined.
_PM_minMinPeriod:            Mininum value for the "minPeriod" class member,
                             so bit-angle-modulation time always doubles with
                             each bitplane (else lower bits may be the same).
_PM_allocate:                Memory allocation function, should return a
                             pointer to a buffer of requested size, aligned
                             to the architecture's largest native type.
                             If not defined, malloc() is used.
_PM_free:                    Corresponding deallocator for _PM_allocate().
                             If not defined, free() is used.
*/

// ENVIRONMENT-SPECIFIC DECLARATIONS ---------------------------------------

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

#include <Arduino.h> // Pull in all that stuff.

#define _PM_delayMicroseconds(us) delayMicroseconds(us)
#define _PM_pinOutput(pin) pinMode(pin, OUTPUT)
#define _PM_pinInput(pin) pinMode(pin, INPUT)
#define _PM_pinHigh(pin) digitalWrite(pin, HIGH)
#define _PM_pinLow(pin) digitalWrite(pin, LOW)
#define _PM_portBitMask(pin) digitalPinToBitMask(pin)

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

#include "py/mphal.h"
#include "shared-bindings/microcontroller/Pin.h"

#define _PM_delayMicroseconds(us) mp_hal_delay_us(us)

// No #else here. In non-Arduino case, declare things in the arch-specific
// files below...unless other environments provide device-neutral functions
// as above, in which case those could go here (w/#elif).

#endif // END CIRCUITPYTHON ------------------------------------------------

// ARCHITECTURE-SPECIFIC HEADERS -------------------------------------------

#include "esp32.h"
#include "nrf52.h"
#include "rp2040.h"
#include "samd-common.h"
#include "samd21.h"
#include "samd51.h"
#include "stm32.h"
#include "teensy4.h"

// DEFAULTS IF NOT DEFINED ABOVE -------------------------------------------

#if !defined(_PM_chunkSize)
#define _PM_chunkSize 8 ///< Unroll data-stuffing loop to this size
#endif

#if !defined(_PM_clockHoldHigh)
#define _PM_clockHoldHigh ///< Extra cycles (if any) on clock HIGH signal
#endif

#if !defined(_PM_clockHoldLow)
#define _PM_clockHoldLow ///< Extra cycles (if any) on clock LOW signal
#endif

#if !defined(_PM_minMinPeriod)
#define _PM_minMinPeriod 100 ///< Minimum timer interval for least bit
#endif

#if !defined(_PM_allocate)
#define _PM_allocate(x) (malloc((x))) ///< Memory alloc call
#endif

#if !defined(_PM_free)
#define _PM_free(x) (free((x))) ///< Corresponding memory free call
#endif

#if !defined(IRAM_ATTR)
#define IRAM_ATTR ///< Neutralize ESP32-specific attribute in core.c
#endif

#if !defined(_PM_PORT_TYPE)
#define _PM_PORT_TYPE uint32_t ///< PORT register size/type
#endif