Skip to content

Commit b03d6e5

Browse files
authored
Merge pull request #485 from henrygab/GPIOTE_Fix
GPIOTE fixes for attachInterrupt() / detachInterrupt()
2 parents 0d245da + 55c1b9f commit b03d6e5

File tree

1 file changed

+81
-37
lines changed

1 file changed

+81
-37
lines changed

cores/nRF5/WInterrupts.c

Lines changed: 81 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
#include "Arduino.h"
2323
#include "wiring_private.h"
24+
#include "nrf_gpiote.h"
2425

2526
#include <string.h>
2627

@@ -95,25 +96,59 @@ int attachInterrupt(uint32_t pin, voidFuncPtr callback, uint32_t mode)
9596
return 0;
9697
}
9798

98-
for (int ch = 0; ch < NUMBER_OF_GPIO_TE; ch++) {
99-
if (channelMap[ch] == -1 || (uint32_t)channelMap[ch] == pin) {
100-
channelMap[ch] = pin;
101-
callbacksInt[ch] = callback;
102-
callbackDeferred[ch] = deferred;
103-
104-
NRF_GPIOTE->CONFIG[ch] &= ~(GPIOTE_CONFIG_PORT_PIN_Msk | GPIOTE_CONFIG_POLARITY_Msk);
105-
NRF_GPIOTE->CONFIG[ch] |= ((pin << GPIOTE_CONFIG_PSEL_Pos) & GPIOTE_CONFIG_PORT_PIN_Msk) |
106-
((polarity << GPIOTE_CONFIG_POLARITY_Pos) & GPIOTE_CONFIG_POLARITY_Msk);
99+
// All information for the configuration is known, except the prior values
100+
// of the config register. Pre-compute the mask and new bits for later use.
101+
// CONFIG[n] = (CONFIG[n] & oldRegMask) | newRegBits;
102+
//
103+
// Three fields are configured here: PORT/PIN, POLARITY, MODE
104+
const uint32_t oldRegMask = ~(GPIOTE_CONFIG_PORT_PIN_Msk | GPIOTE_CONFIG_POLARITY_Msk | GPIOTE_CONFIG_MODE_Msk);
105+
const uint32_t newRegBits =
106+
((pin << GPIOTE_CONFIG_PSEL_Pos ) & GPIOTE_CONFIG_PORT_PIN_Msk) |
107+
((polarity << GPIOTE_CONFIG_POLARITY_Pos) & GPIOTE_CONFIG_POLARITY_Msk) |
108+
((GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos ) & GPIOTE_CONFIG_MODE_Msk ) ;
109+
110+
int ch = -1;
111+
int newChannel = 0;
112+
113+
// Find channel where pin is already assigned, if any
114+
for (int i = 0; i < NUMBER_OF_GPIO_TE; i++) {
115+
if ((uint32_t)channelMap[i] != pin) continue;
116+
ch = i;
117+
break;
118+
}
119+
// else, find one not already mapped and also not in use by others
120+
if (ch == -1) {
121+
for (int i = 0; i < NUMBER_OF_GPIO_TE; i++) {
122+
if (channelMap[i] != -1) continue;
123+
if (nrf_gpiote_te_is_enabled(NRF_GPIOTE, i)) continue;
124+
125+
ch = i;
126+
newChannel = 1;
127+
break;
128+
}
129+
}
130+
// if no channel found, exit
131+
if (ch == -1) {
132+
return 0; // no channel available
133+
}
107134

108-
NRF_GPIOTE->CONFIG[ch] |= GPIOTE_CONFIG_MODE_Event;
135+
channelMap[ch] = pin; // harmless for existing channel
136+
callbacksInt[ch] = callback; // caller might be updating this for existing channel
137+
callbackDeferred[ch] = deferred; // caller might be updating this for existing channel
109138

110-
NRF_GPIOTE->INTENSET = (1 << ch);
139+
uint32_t tmp = NRF_GPIOTE->CONFIG[ch];
140+
tmp &= oldRegMask;
141+
tmp |= newRegBits; // for existing channel, effectively updates only the polarity
142+
NRF_GPIOTE->CONFIG[ch] = tmp;
111143

112-
return (1 << ch);
113-
}
144+
// For a new channel, additionally ensure no old events existed, and enable the interrupt
145+
if (newChannel) {
146+
NRF_GPIOTE->EVENTS_IN[ch] = 0;
147+
NRF_GPIOTE->INTENSET = (1 << ch);
114148
}
115149

116-
return 0;
150+
// Finally, indicate to caller the allocated / updated channel
151+
return (1 << ch);
117152
}
118153

119154
/*
@@ -129,14 +164,14 @@ void detachInterrupt(uint32_t pin)
129164

130165
for (int ch = 0; ch < NUMBER_OF_GPIO_TE; ch++) {
131166
if ((uint32_t)channelMap[ch] == pin) {
167+
NRF_GPIOTE->INTENCLR = (1 << ch);
168+
NRF_GPIOTE->CONFIG[ch] = 0;
169+
NRF_GPIOTE->EVENTS_IN[ch] = 0; // clear any final events
170+
171+
// now cleanup the rest of the use of the channel
132172
channelMap[ch] = -1;
133173
callbacksInt[ch] = NULL;
134174
callbackDeferred[ch] = false;
135-
136-
NRF_GPIOTE->CONFIG[ch] &= ~GPIOTE_CONFIG_MODE_Event;
137-
138-
NRF_GPIOTE->INTENCLR = (1 << ch);
139-
140175
break;
141176
}
142177
}
@@ -148,30 +183,39 @@ void GPIOTE_IRQHandler()
148183
SEGGER_SYSVIEW_RecordEnterISR();
149184
#endif
150185

151-
uint32_t event = offsetof(NRF_GPIOTE_Type, EVENTS_IN[0]);
152-
186+
// Read this once (not 8x), as it's a volatile read
187+
// across the AHB, which adds up to 3 cycles.
188+
uint32_t const enabledInterruptMask = NRF_GPIOTE->INTENSET;
153189
for (int ch = 0; ch < NUMBER_OF_GPIO_TE; ch++) {
154-
if ((*(uint32_t *)((uint32_t)NRF_GPIOTE + event) == 0x1UL) && (NRF_GPIOTE->INTENSET & (1 << ch))) {
155-
if (channelMap[ch] != -1 && callbacksInt[ch]) {
156-
if ( callbackDeferred[ch] ) {
157-
// Adafruit defer callback to non-isr if configured so
158-
ada_callback(NULL, 0, callbacksInt[ch]);
159-
}else{
160-
callbacksInt[ch]();
161-
}
190+
// only process where the interrupt is enabled and the event register is set
191+
// check interrupt enabled mask first, as already read that IOM value, to
192+
// reduce delays from AHB (16MHz) reads.
193+
if ( 0 == (enabledInterruptMask & (1 << ch))) continue;
194+
if ( 0 == NRF_GPIOTE->EVENTS_IN[ch]) continue;
195+
196+
// If the event was set and interrupts are enabled,
197+
// call the callback function only if it exists,
198+
// but ALWAYS clear the event to prevent an interrupt storm.
199+
if (channelMap[ch] != -1 && callbacksInt[ch]) {
200+
if ( callbackDeferred[ch] ) {
201+
// Adafruit defer callback to non-isr if configured so
202+
ada_callback(NULL, 0, callbacksInt[ch]);
203+
} else {
204+
callbacksInt[ch]();
162205
}
163-
164-
*(uint32_t *)((uint32_t)NRF_GPIOTE + event) = 0;
165-
#if __CORTEX_M == 0x04
166-
volatile uint32_t dummy = *((volatile uint32_t *)((uint32_t)NRF_GPIOTE + event));
167-
(void)dummy;
168-
#endif
169206
}
170207

171-
event = (uint32_t)((uint32_t)event + 4);
208+
// clear the event
209+
NRF_GPIOTE->EVENTS_IN[ch] = 0;
172210
}
211+
#if __CORTEX_M == 0x04
212+
// See note at nRF52840_PS_v1.1.pdf section 6.1.8 ("interrupt clearing")
213+
// See also https://gcc.gnu.org/onlinedocs/gcc/Volatiles.html for why
214+
// using memory barrier instead of read of an unrelated volatile
215+
__DSB(); __NOP();__NOP();__NOP();__NOP();
216+
#endif
173217

174218
#if CFG_SYSVIEW
175-
SEGGER_SYSVIEW_RecordExitISR();
219+
SEGGER_SYSVIEW_RecordExitISR();
176220
#endif
177221
}

0 commit comments

Comments
 (0)