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