Repository navigation
Expand file tree
/
Copy pathconfig.h
More file actions
388 lines (357 loc) · 16.9 KB
/
Copy pathconfig.h
File metadata and controls
388 lines (357 loc) · 16.9 KB
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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
// ═══════════════════════════════════════════════════════════
// PatternFlow - Hardware Configuration & Constants
// License: MIT
// ═══════════════════════════════════════════════════════════
#ifndef CONFIG_H
#define CONFIG_H
#include <Arduino.h>
// ── A variant's settings, before anything has a default ─────────────────
// Every tunable in this file and in net_config.h is `#ifndef`-guarded, which
// only helps somebody who can define the value FIRST. config.h is the first
// project header the sketch includes, so this is the earliest point that
// exists — and a variant that wants a different panel clock, a different
// transmit power or a different brightness cap drops an features/overrides.h
// and changes nothing else in the tree.
//
// The RFC named this file (§2.4) and it was never built; the first variant
// that wanted a setting rather than a feature is what surfaced it. Pair with
// features/features_local.h, which does the same job for the feature list.
#if defined(__has_include)
#if __has_include("features/overrides.h")
#include "features/overrides.h"
#endif
#endif
// --- Arduino macro detox ---
// Arduino.h defines PI / TWO_PI / HALF_PI / DEG_TO_RAD / RAD_TO_DEG / EULER as
// bare object-like macros, so a pattern that declares its own constant with
// one of those names ("static constexpr float PI = ...") gets preprocessor-
// mangled into a compile error — AI-generated patterns do this all the time.
// Replace the macros with typed constants: every existing use of PI keeps
// working, and a pattern namespace may now legally shadow them with its own
// definitions. (Function-like macros such as min/max/abs/constrain are left
// alone — patterns rely on them and they only expand before a parenthesis.)
//
// float, not double: Arduino's macros are double literals, which silently
// promotes float expressions like `hash * TWO_PI` to SOFTWARE-emulated double
// math on the S3 (its FPU is single-precision only). Patterns are float math
// throughout, so float constants keep the pixel loop on the hardware FPU;
// the precision difference is in the 8th significant digit — invisible.
#ifdef PI
#undef PI
#endif
#ifdef TWO_PI
#undef TWO_PI
#endif
#ifdef HALF_PI
#undef HALF_PI
#endif
#ifdef DEG_TO_RAD
#undef DEG_TO_RAD
#endif
#ifdef RAD_TO_DEG
#undef RAD_TO_DEG
#endif
#ifdef EULER
#undef EULER
#endif
constexpr float PI = 3.14159265358979323846f;
constexpr float TWO_PI = 6.28318530717958647693f;
constexpr float HALF_PI = 1.57079632679489661923f;
constexpr float DEG_TO_RAD = 0.01745329251994329577f;
constexpr float RAD_TO_DEG = 57.29577951308232087680f;
constexpr float EULER = 2.71828182845904523536f;
// --- Display Specifications ---
// Logical canvas (PANEL_RES_W/H) is what patterns, menus and modules see.
// Physical size + chain is what the HUB75 driver is told. A native 128×64
// module and two 64×64 modules daisy-chained are the same 128×64 canvas,
// but the driver must be configured differently — hence PANEL_GEOMETRY.
// PlatformIO: -DPANEL_GEOMETRY=PANEL_GEOM_64x64_CHAIN2 for firmware64x2.
// Arduino IDE: uncomment a PANEL_GEOMETRY line below, then build.
//
// A pattern composed for a grid that ISN'T the panel — a 64x128 portrait
// pattern on this 128x64 panel, say — is a separate matter, handled per
// pattern with PFCanvas::setFrame(). See README.md.
// Running a non-stock panel? Please report how it went, working or not:
// https://github.com/engmung/Patternflow/issues/224
//
// The ids and the sizes come from shared/constants.json, which the site
// reads too.
#include "src/core_shared.h"
#define PANEL_GEOM_128x64_SINGLE PF_SHARED_PANEL_GEOM_128x64_SINGLE // one 128×64 module (stock)
#define PANEL_GEOM_64x64_SINGLE PF_SHARED_PANEL_GEOM_64x64_SINGLE // one 64×64 module
#define PANEL_GEOM_64x64_CHAIN2 PF_SHARED_PANEL_GEOM_64x64_CHAIN2 // two 64×64 modules daisy-chained → 128×64
// #define PANEL_GEOMETRY PANEL_GEOM_64x64_CHAIN2
#ifndef PANEL_GEOMETRY
#define PANEL_GEOMETRY PF_SHARED_PANEL_GEOM_DEFAULT
#endif
#if PANEL_GEOMETRY == PANEL_GEOM_64x64_SINGLE
#define PANEL_PHYS_W PF_SHARED_PANEL_GEOM_64x64_SINGLE_PHYS_W
#define PANEL_PHYS_H PF_SHARED_PANEL_GEOM_64x64_SINGLE_PHYS_H
#define PANEL_CHAIN PF_SHARED_PANEL_GEOM_64x64_SINGLE_CHAIN
#define PANEL_RES_W PF_SHARED_PANEL_GEOM_64x64_SINGLE_RES_W
#define PANEL_RES_H PF_SHARED_PANEL_GEOM_64x64_SINGLE_RES_H
#elif PANEL_GEOMETRY == PANEL_GEOM_64x64_CHAIN2
#define PANEL_PHYS_W PF_SHARED_PANEL_GEOM_64x64_CHAIN2_PHYS_W
#define PANEL_PHYS_H PF_SHARED_PANEL_GEOM_64x64_CHAIN2_PHYS_H
#define PANEL_CHAIN PF_SHARED_PANEL_GEOM_64x64_CHAIN2_CHAIN
#define PANEL_RES_W PF_SHARED_PANEL_GEOM_64x64_CHAIN2_RES_W
#define PANEL_RES_H PF_SHARED_PANEL_GEOM_64x64_CHAIN2_RES_H
#else
#define PANEL_PHYS_W PF_SHARED_PANEL_GEOM_128x64_SINGLE_PHYS_W
#define PANEL_PHYS_H PF_SHARED_PANEL_GEOM_128x64_SINGLE_PHYS_H
#define PANEL_CHAIN PF_SHARED_PANEL_GEOM_128x64_SINGLE_CHAIN
#define PANEL_RES_W PF_SHARED_PANEL_GEOM_128x64_SINGLE_RES_W
#define PANEL_RES_H PF_SHARED_PANEL_GEOM_128x64_SINGLE_RES_H
#endif
#if PANEL_RES_W != (PANEL_PHYS_W * PANEL_CHAIN)
#error "PANEL_RES_W must equal PANEL_PHYS_W * PANEL_CHAIN"
#endif
#if PANEL_RES_H != PANEL_PHYS_H
#error "PANEL_RES_H must equal PANEL_PHYS_H"
#endif
// --- Panel Selection ---
// This firmware runs on classic HUB75 / HUB75E panels driven directly by the
// ESP32-S3 (no external sending/receiving card). Pick your panel's driver IC
// below, then build & upload. PANEL_PROFILE is the ONLY line you change.
//
// "HUB75E" on the listing guarantees a connector, NOT compatibility — the
// driver ICs on the back of the panel decide whether it lights up at all.
// Full buyer's guide, symptom table and a copy-paste question for the seller:
// docs/panel-compatibility.md
//
// Six names, but the library only has FOUR distinct behaviours — verified in
// ESP32-HUB75-MatrixPanel-leddrivers.cpp, shiftDriver():
//
// PANEL_STANDARD No init sequence at all. Plain 74HC595 shift-register
// panels, and the right starting point for any classic
// chip with no dedicated value below (ICN2037, SM162xx,
// unlabeled indoor panels). This is what the browser
// flasher ships, so a working stock build is by
// definition running this path.
// PANEL_HIGHREFRESH ┐ All three run the SAME fm6124init() register
// PANEL_FM6124 ├─ sequence — there is no per-chip branch inside it.
// PANEL_ICN2038S ┘ Pick whichever matches your silkscreen; switching
// between them changes nothing.
// PANEL_MBI5124 Only flips clkphase to true (MBI5124 latches on the
// clock's rising edge). No register writes.
// PANEL_DP3246 dp3246init() — its own register sequence, and also
// forces clkphase true.
//
// DON'T pick by part number alone — try PANEL_STANDARD first, always. The
// init sequence writes a brightness register and one output-enable bit; many
// panels ship with those already usable and light up with no init at all,
// others come up dark until they're written, and the part number does not
// tell you which. The Patternflow reference panel's driver is an FM6124 and
// it runs fine on PANEL_STANDARD. Only reach for another profile if the
// panel stays COMPLETELY dark.
//
// NOT SUPPORTED — no value here rescues these:
// S-PWM / GCLK "smart" panels — ICN2053, FM6353, FM6363/FM6363C, FM6373C,
// DP3264/DP3265, ICND2055, MBI5051/5052/5053, MBI6024. Usually sold on
// "1920/3840Hz high refresh" or as needing a Nova/Linsn/Colorlight/Huidu
// receiving card. Their drivers generate PWM on-chip from a separate
// grey-scale clock and use an addressing scheme ESP32-HUB75-MatrixPanel-DMA
// cannot produce, so the panel stays completely dark whatever you set.
// (Upstream: issue #642, closed wontfix.)
#define PANEL_STANDARD 0
#define PANEL_HIGHREFRESH 1
#define PANEL_FM6124 2
#define PANEL_ICN2038S 3
#define PANEL_MBI5124 4
#define PANEL_DP3246 5
#define PANEL_PROFILE PANEL_STANDARD // ← change ONLY if the panel stays dark
#if PANEL_PROFILE == PANEL_HIGHREFRESH
#define HUB75_DRIVER HUB75_I2S_CFG::FM6126A
#elif PANEL_PROFILE == PANEL_FM6124
#define HUB75_DRIVER HUB75_I2S_CFG::FM6124
#elif PANEL_PROFILE == PANEL_ICN2038S
#define HUB75_DRIVER HUB75_I2S_CFG::ICN2038S
#elif PANEL_PROFILE == PANEL_MBI5124
#define HUB75_DRIVER HUB75_I2S_CFG::MBI5124
#elif PANEL_PROFILE == PANEL_DP3246
#define HUB75_DRIVER HUB75_I2S_CFG::DP3246
#else
#define HUB75_DRIVER HUB75_I2S_CFG::SHIFTREG // plain shift-register panel
#endif
// --- HUB75 Pin Mapping (ESP32-S3) ---
#define R1_PIN 42
#define G1_PIN 41
#define B1_PIN 40
#define R2_PIN 38
#define G2_PIN 39
#define B2_PIN 13
#define PIN_A 46
#define PIN_B 11
#define PIN_C 48
#define PIN_D 12
#define PIN_E 21
#define LAT_PIN 47
#define OE_PIN 14
#define CLK_PIN 2
// --- Encoder Pin Mapping ---
// ENCn is the encoder at the front-panel Kn position (verified on the
// board) — no cross-routing; keep LOGICAL_TO_PHYSICAL_KNOB an identity.
// Encoder 1: Hue Control
#define ENC1_A 4
#define ENC1_B 8
#define ENC1_SW 9
// Encoder 2: Speed Control
#define ENC2_A 5
#define ENC2_B 10
#define ENC2_SW 15
// Encoder 3: Mode/Preset Control
#define ENC3_A 6
#define ENC3_B 16
#define ENC3_SW 17
// Encoder 4: Frequency Control
#define ENC4_A 7
#define ENC4_B 18
#define ENC4_SW 1
// --- Hardware Settings ---
// 0 for the official Bourns PEC11R-4220F-S0024 (used on the simplified
// revision board); set to 1 if rotation reads reversed (e.g. AliExpress
// clones, or encoders mounted on the back of the PCB).
#define INVERT_ENCODER 0
// Contact-bounce filter for the encoder A/B lines, in microseconds. These are
// mechanical contacts with no hardware help: the pull-ups are the ESP32's
// internal ones (weak), and the RC filter pads that v2 carried were never
// populated and are gone on v3. So an edge arriving sooner than this after the
// last accepted one is treated as bounce and dropped.
//
// Sizing: contact bounce settles in a few hundred microseconds. A real
// sub-step is ~12 ms apart at the EC11's rated 60 RPM ceiling, and still
// ~2.5 ms at a hard 300 RPM flick — so 500 µs leaves a wide margin either way.
// Raise it if a knob still double-counts; set it to 0 to disable the filter.
#define ENC_BOUNCE_US 500
#define DEFAULT_BRIGHTNESS 204 // 80% (0-255)
// --- Sleep mode ---
// Turns the panel off and idles the ESP32 without unplugging anything. The
// device stays on the network throughout — that is the whole point, because
// waking it over MQTT is impossible otherwise. See src/core_sleep.h.
#ifndef PF_SLEEP_ENABLED
#define PF_SLEEP_ENABLED 1
#endif
// Stop the HUB75 DMA engine while asleep, on top of blanking the framebuffer.
// Blanking alone turns the LEDs off, which is most of the draw, but the panel's
// driver ICs keep being clocked at 15 MHz for as long as the transfer runs —
// this is what stops that too.
//
// Needs the vendored driver's resumeDMAoutput() (src/hub75/VENDORED.md). Set
// this to 0 if the panel comes back black or garbled after waking on your
// hardware: sleep then blanks only, which is safe on any panel and still saves
// the LED current.
#ifndef PF_SLEEP_STOP_DMA
#define PF_SLEEP_STOP_DMA 1
#endif
// CPU clock while asleep. 80 MHz is the floor at which the Wi-Fi radio still
// works, so it is as low as a network-awake sleep can go; 0 disables the
// switch entirely.
#ifndef PF_SLEEP_CPU_MHZ
#define PF_SLEEP_CPU_MHZ 80
#endif
// CPU clock restored on wake — the ESP32-S3's normal full speed.
#ifndef PF_AWAKE_CPU_MHZ
#define PF_AWAKE_CPU_MHZ 240
#endif
// --- LED Panel Color Calibration ---
// Override these per panel; the defaults are a mild correction tuned for
// a typical HUB75 (red LED brighter than blue, slight green dominance in
// cyans). Steeper R gamma curbs red dominance; gentler B gamma keeps the
// blues from collapsing into black. WB gain trims R and G a touch so
// pure-white whites land closer to D65 instead of warm pink.
//
// These are PASSTHROUGH: the device adds nothing of its own to the colour a
// pattern asked for. That is the standard pipeline, not an absence of one —
// editor colours are sRGB, the panel is linear PWM, and the HUB75 driver
// already applies exactly that conversion (lumConvTab, a CIE1931 curve of
// effective exponent ~2.44). Anything set here stacks on top of it.
//
// It used to: gamma 2.5/2.4/2.2 with a 0.92 white-balance gain. Measured end
// to end, that put the panel here —
//
// input driver alone driver + the old values
// 64 4.4 % 0.3 %
// 128 18.6 % 2.2 %
// 192 48.7 % 14.6 %
// 255 100.0 % 81.1 %
//
// — an effective exponent of 5.5, with white capped at 81 %. Greys were not
// dim by accident; they were being crushed twice, and a fifth of the panel's
// brightness was being thrown away. Those values predate Pattern Lab being
// able to set colour precisely, and were written believing the driver did no
// curve of its own.
//
// The fine tuning on top of this baseline was done 2026-08-09 on the
// maintainer's v2.x panel, by eye against a monitor showing the same test
// frames (GET /api/display tunes all of these live; the panel tuner page in
// web/public/panel-tuner.html drives it). The WB and saturation defaults
// below are that panel's converged numbers. Panels vary — if yours reads
// differently, tune live and land your own numbers here. Gamma is the
// driver's job and almost never yours: putting a curve back here is how the
// stack above happened.
// OPEN: matching panel colour to a monitor properly.
// Per-channel gain is an approximation. The real difference is that the LED
// primaries sit at different wavelengths than sRGB's, so the same RGB triplet
// is a genuinely different colour on each. The textbook fix is a pair of 3x3
// matrices (sRGB -> XYZ -> panel RGB) derived from the panel primaries' xy
// chromaticity and its white point — which needs a colorimeter to measure, and
// a gamut decision, because these panels are often WIDER than sRGB and mapping
// into it costs saturation. Nobody has done that here. If you want to:
// russellcottrell.com/photo/matrixCalculator.htm derives the matrices from xy,
// and FastLED's ColorCorrection is the usual per-channel approximation.
// Worth doing once a production panel batch is fixed, not before — measure a
// panel you will not be replacing.
// To get the pre-2026-07 look back: gammas 2.5/2.4/2.2, WB 0.92/0.92/1.0,
// saturation 1.10. It is darker and dimmer, not more correct.
#ifndef LED_GAMMA_R
#define LED_GAMMA_R 1.0f
#endif
#ifndef LED_GAMMA_G
#define LED_GAMMA_G 1.0f
#endif
#ifndef LED_GAMMA_B
#define LED_GAMMA_B 1.0f
#endif
// Identity, on purpose (2026-09-17). The 0.930 / 1.000 / 0.975 that stood here
// from 2026-08-09 was converged by eye at full white against the upstream OE
// windows, whose top planes are far from binary - so it corrected that driver's
// error at one level, not the panel. Once the plane weights moved (see
// pfOEWindowPixels in the vendored driver) the same numbers read pink across the
// greys, and the panel with no correction at all read neutral. A white balance
// is only meaningful on top of a response whose channel ratio holds at every
// level; tune live via /api/display or /panel-tuner.html, judge on the GRAYS
// screen and not on white alone, then land the converged numbers here.
#ifndef LED_WB_R
#define LED_WB_R 1.000f
#endif
#ifndef LED_WB_G
#define LED_WB_G 1.000f
#endif
#ifndef LED_WB_B
#define LED_WB_B 1.000f
#endif
// Saturation boost is applied before gamma in present(). Gray stays gray
// (mathematically a no-op when r==g==b), saturated colors get pulled
// further away from gray. 1.0 disables.
//
// 1.62 was tuned by eye (2026-08-09) against the Calibration preset's
// side-by-side sRGB reference in the panel tuner, and held across the
// brightness range (checked at 14% and 100%). Counter to the wide-gamut
// theory that predicted a CUT: in practice the panel reads washed-out next
// to a monitor showing the same frame, so matching the design intent takes
// a strong boost. Strongly saturated colors clip a channel under it —
// acceptable; the match is judged on real patterns, not on test bars.
#ifndef LED_SAT_BOOST
#define LED_SAT_BOOST 1.62f
#endif
// --- Network features (Wi-Fi, OTA, OSC, audio-react) ---
// All connectivity config and per-device secrets live in net_config.h /
// patternflow_secrets.h, not here. config.h stays focused on hardware.
#include "net_config.h"
// --- Pattern Parameters Limits ---
#define MAX_HUE 360
#define MAX_SPEED 5.0f
#define SPEED_STEP 0.2f
#define MAX_FREQ 1000
#define FREQ_STEP 50
#endif