Skip to content

Commit 1a7f469

Browse files
authored
Merge pull request #308 from MoonModules/flickerFixer_backport
WLED-MM backport of upstream wled#4980 RMT High-priority Interrupt driver - solves flickering problems on esp32, esp32-S2 and esp32-S3 do not use the new RMTHI driver on old V3 builds (especially esp32dev_compat) upgrade all esp32 builds to NeoPixelBus 2.7.9 (we can't go higher due to API incompatibilities)
2 parents 4291572 + 5ce9ba6 commit 1a7f469

8 files changed

Lines changed: 1349 additions & 32 deletions

File tree

lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h

Lines changed: 489 additions & 0 deletions
Large diffs are not rendered by default.

lib/NeoESP32RmtHI/library.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "NeoESP32RmtHI",
3+
"build": { "libArchive": false },
4+
"platforms": ["espressif32"],
5+
"dependencies": [
6+
{
7+
"owner": "makuna",
8+
"name": "NeoPixelBus",
9+
"version": "^2.7.9"
10+
}
11+
]
12+
}
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
/* RMT ISR shim
2+
* Bridges from a high-level interrupt to the C++ code.
3+
*
4+
* This code is largely derived from Espressif's 'hli_vector.S' Bluetooth ISR.
5+
*
6+
*/
7+
8+
#if !defined(ESP32) && !defined(ESP8266) // WLEDMM buildenv sanity check (experimental)
9+
#error neither ESP32 nor ESP8266, don't know what to do
10+
#endif
11+
12+
#if defined(__XTENSA__) && defined(ESP32) && !defined(CONFIG_BTDM_CTRL_HLI)
13+
#if !defined(WLED_USE_SHARED_RMT) // WLEDMM don't compile this file on unsupported platforms
14+
15+
#include <freertos/xtensa_context.h>
16+
#include "sdkconfig.h"
17+
#include "soc/soc.h"
18+
19+
/* If the Bluetooth driver has hooked the high-priority interrupt, we piggyback on it and don't need this. */
20+
#ifndef CONFIG_BTDM_CTRL_HLI
21+
22+
/*
23+
Select interrupt based on system check level
24+
- Base ESP32: could be 4 or 5, depends on platform config
25+
- S2: 5
26+
- S3: 5
27+
*/
28+
29+
#if CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5
30+
/* Use level 4 */
31+
#define RFI_X 4
32+
#define xt_highintx xt_highint4
33+
#else /* !CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 */
34+
/* Use level 5 */
35+
#define RFI_X 5
36+
#define xt_highintx xt_highint5
37+
#endif /* CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 */
38+
39+
// Register map, based on interrupt level
40+
#define EPC_X (EPC + RFI_X)
41+
#define EXCSAVE_X (EXCSAVE + RFI_X)
42+
43+
// The sp mnemonic is used all over in ESP's assembly, though I'm not sure where it's expected to be defined?
44+
#define sp a1
45+
46+
/* Interrupt stack size, for C code. */
47+
#define RMT_INTR_STACK_SIZE 512
48+
49+
/* Save area for the CPU state:
50+
* - 64 words for the general purpose registers
51+
* - 7 words for some of the special registers:
52+
* - WINDOWBASE, WINDOWSTART — only WINDOWSTART is truly needed
53+
* - SAR, LBEG, LEND, LCOUNT — since the C code might use these
54+
* - EPC1 — since the C code might cause window overflow exceptions
55+
* This is not laid out as standard exception frame structure
56+
* for simplicity of the save/restore code.
57+
*/
58+
#define REG_FILE_SIZE (64 * 4)
59+
#define SPECREG_OFFSET REG_FILE_SIZE
60+
#define SPECREG_SIZE (7 * 4)
61+
#define REG_SAVE_AREA_SIZE (SPECREG_OFFSET + SPECREG_SIZE)
62+
63+
.data
64+
_rmt_intr_stack:
65+
.space RMT_INTR_STACK_SIZE
66+
_rmt_save_ctx:
67+
.space REG_SAVE_AREA_SIZE
68+
69+
.section .iram1,"ax"
70+
.global xt_highintx
71+
.type xt_highintx,@function
72+
.align 4
73+
74+
xt_highintx:
75+
76+
movi a0, _rmt_save_ctx
77+
/* save 4 lower registers */
78+
s32i a1, a0, 4
79+
s32i a2, a0, 8
80+
s32i a3, a0, 12
81+
rsr a2, EXCSAVE_X /* holds the value of a0 */
82+
s32i a2, a0, 0
83+
84+
/* Save special registers */
85+
addi a0, a0, SPECREG_OFFSET
86+
rsr a2, WINDOWBASE
87+
s32i a2, a0, 0
88+
rsr a2, WINDOWSTART
89+
s32i a2, a0, 4
90+
rsr a2, SAR
91+
s32i a2, a0, 8
92+
#if XCHAL_HAVE_LOOPS
93+
rsr a2, LBEG
94+
s32i a2, a0, 12
95+
rsr a2, LEND
96+
s32i a2, a0, 16
97+
rsr a2, LCOUNT
98+
s32i a2, a0, 20
99+
#endif
100+
rsr a2, EPC1
101+
s32i a2, a0, 24
102+
103+
/* disable exception mode, window overflow */
104+
movi a0, PS_INTLEVEL(RFI_X+1) | PS_EXCM
105+
wsr a0, PS
106+
rsync
107+
108+
/* Save the remaining physical registers.
109+
* 4 registers are already saved, which leaves 60 registers to save.
110+
* (FIXME: consider the case when the CPU is configured with physical 32 registers)
111+
* These 60 registers are saved in 5 iterations, 12 registers at a time.
112+
*/
113+
movi a1, 5
114+
movi a3, _rmt_save_ctx + 4 * 4
115+
116+
/* This is repeated 5 times, each time the window is shifted by 12 registers.
117+
* We come here with a1 = downcounter, a3 = save pointer, a2 and a0 unused.
118+
*/
119+
1:
120+
s32i a4, a3, 0
121+
s32i a5, a3, 4
122+
s32i a6, a3, 8
123+
s32i a7, a3, 12
124+
s32i a8, a3, 16
125+
s32i a9, a3, 20
126+
s32i a10, a3, 24
127+
s32i a11, a3, 28
128+
s32i a12, a3, 32
129+
s32i a13, a3, 36
130+
s32i a14, a3, 40
131+
s32i a15, a3, 44
132+
133+
/* We are about to rotate the window, so that a12-a15 will become the new a0-a3.
134+
* Copy a0-a3 to a12-15 to still have access to these values.
135+
* At the same time we can decrement the counter and adjust the save area pointer
136+
*/
137+
138+
/* a0 is constant (_rmt_save_ctx), no need to copy */
139+
addi a13, a1, -1 /* copy and decrement the downcounter */
140+
/* a2 is scratch so no need to copy */
141+
addi a15, a3, 48 /* copy and adjust the save area pointer */
142+
beqz a13, 2f /* have saved all registers ? */
143+
rotw 3 /* rotate the window and go back */
144+
j 1b
145+
146+
/* the loop is complete */
147+
2:
148+
rotw 4 /* this brings us back to the original window */
149+
/* a0 still points to _rmt_save_ctx */
150+
151+
/* Can clear WINDOWSTART now, all registers are saved */
152+
rsr a2, WINDOWBASE
153+
/* WINDOWSTART = (1 << WINDOWBASE) */
154+
movi a3, 1
155+
ssl a2
156+
sll a3, a3
157+
wsr a3, WINDOWSTART
158+
159+
_highint_stack_switch:
160+
movi a0, 0
161+
movi sp, _rmt_intr_stack + RMT_INTR_STACK_SIZE - 16
162+
s32e a0, sp, -12 /* For GDB: set null SP */
163+
s32e a0, sp, -16 /* For GDB: set null PC */
164+
movi a0, _highint_stack_switch /* For GDB: cosmetics, for the frame where stack switch happened */
165+
166+
/* Set up PS for C, disable all interrupts except NMI and debug, and clear EXCM. */
167+
movi a6, PS_INTLEVEL(RFI_X) | PS_UM | PS_WOE
168+
wsr a6, PS
169+
rsync
170+
171+
/* Call C handler */
172+
mov a6, sp
173+
call4 NeoEsp32RmtMethodIsr
174+
175+
l32e sp, sp, -12 /* switch back to the original stack */
176+
177+
/* Done with C handler; re-enable exception mode, disabling window overflow */
178+
movi a2, PS_INTLEVEL(RFI_X+1) | PS_EXCM /* TOCHECK */
179+
wsr a2, PS
180+
rsync
181+
182+
/* Restore the special registers.
183+
* WINDOWSTART will be restored near the end.
184+
*/
185+
movi a0, _rmt_save_ctx + SPECREG_OFFSET
186+
l32i a2, a0, 8
187+
wsr a2, SAR
188+
#if XCHAL_HAVE_LOOPS
189+
l32i a2, a0, 12
190+
wsr a2, LBEG
191+
l32i a2, a0, 16
192+
wsr a2, LEND
193+
l32i a2, a0, 20
194+
wsr a2, LCOUNT
195+
#endif
196+
l32i a2, a0, 24
197+
wsr a2, EPC1
198+
199+
/* Restoring the physical registers.
200+
* This is the reverse to the saving process above.
201+
*/
202+
203+
/* Rotate back to the final window, then start loading 12 registers at a time,
204+
* in 5 iterations.
205+
* Again, a1 is the downcounter and a3 is the save area pointer.
206+
* After each rotation, a1 and a3 are copied from a13 and a15.
207+
* To simplify the loop, we put the initial values into a13 and a15.
208+
*/
209+
rotw -4
210+
movi a15, _rmt_save_ctx + 64 * 4 /* point to the end of the save area */
211+
movi a13, 5
212+
213+
1:
214+
/* Copy a1 and a3 from their previous location,
215+
* at the same time decrementing and adjusting the save area pointer.
216+
*/
217+
addi a1, a13, -1
218+
addi a3, a15, -48
219+
220+
/* Load 12 registers */
221+
l32i a4, a3, 0
222+
l32i a5, a3, 4
223+
l32i a6, a3, 8
224+
l32i a7, a3, 12
225+
l32i a8, a3, 16
226+
l32i a9, a3, 20
227+
l32i a10, a3, 24
228+
l32i a11, a3, 28 /* ensure PS and EPC written */
229+
l32i a12, a3, 32
230+
l32i a13, a3, 36
231+
l32i a14, a3, 40
232+
l32i a15, a3, 44
233+
234+
/* Done with the loop? */
235+
beqz a1, 2f
236+
/* If no, rotate the window and repeat */
237+
rotw -3
238+
j 1b
239+
240+
2:
241+
/* Done with the loop. Only 4 registers (a0-a3 in the original window) remain
242+
* to be restored. Also need to restore WINDOWSTART, since all the general
243+
* registers are now in place.
244+
*/
245+
movi a0, _rmt_save_ctx
246+
247+
l32i a2, a0, SPECREG_OFFSET + 4
248+
wsr a2, WINDOWSTART
249+
250+
l32i a1, a0, 4
251+
l32i a2, a0, 8
252+
l32i a3, a0, 12
253+
rsr a0, EXCSAVE_X /* holds the value of a0 before the interrupt handler */
254+
255+
/* Return from the interrupt, restoring PS from EPS_X */
256+
rfi RFI_X
257+
258+
259+
/* The linker has no reason to link in this file; all symbols it exports are already defined
260+
(weakly!) in the default int handler. Define a symbol here so we can use it to have the
261+
linker inspect this anyway. */
262+
263+
.global ld_include_hli_vectors_rmt
264+
ld_include_hli_vectors_rmt:
265+
266+
267+
#endif // CONFIG_BTDM_CTRL_HLI
268+
#endif // WLED_USE_SHARED_RMT
269+
#endif // XTensa

0 commit comments

Comments
 (0)