Interrupt on Electron pin C1 is not called after cellular code merged

I’ve been banging my head against this one for a day or so now. I have kludge that works, but I’d like to know what is wrong with the original code. Unfortunately, I can’t post a test case, for reasons that will become clear.

Background:

The system is started in
SYSTEM_THREAD(ENABLED);
SYSTEM_MODE(MANUAL);

Part 1:
I have a working program that attaches 8 interrupts to 8 pins, 7 external and the RI_UC pin from the modem.

A timer handler loop runs a state machine every 500mS, that responds to the pins to manage a diesel engine. It all works fine electrically and logically. The typical setup looks like this (all seven external pin interrupts are the same):

#define FSDET C0
#define STDET C1
... define others... and double checked no accidental re-use of the particle Cn labels...

struct InputStruct{
    bool state;
    bool raw;
    unsigned short hist;
};

// digital Input variables (set aynchronously by interrupts)
volatile InputStruct fuelSense;
volatile InputStruct startSense;
... repeat for other inputs...

void setup() {
    //Configure Sensing
    pinMode(FSDET,INPUT);
    pinMode(STDET,INPUT);
    ...repeat for other inputs ...

    // Set up interrupt driven inputs. no preferred priority, so leave default (13)
    attachInterrupt(FSDET, _fuelInterrupt, CHANGE);
    attachInterrupt(STDET, _starterInterrupt, CHANGE);
    .... repeat for other input handlers...
}

loop(){ 
... stuff...
}

// Fuel Solenoid Sense input capture
void _fuelInterrupt(){
    fuelSense.raw = (LOW == digitalRead(FSDET));
}

// Starter Solenoid Sense input capture
void _starterInterrupt(){
    startSense.raw = (LOW == digitalRead(STDET));
}
.. rinse and repeat...

The .raw boolean state of each input variable is counted by a timer handler based debounce function (10mS interval), where .hist stores the hysteresis count, and eventually sets the stable .state true or false.

This works perfectly well. Each interrupt handler is fired on CHANGE and updates its respective variable.

Part 2:
Separately, I have a working program that reads/sends SMS via the cellular.command calls. It uses the RI_UC pin to detect incoming SMS, via an interrupt. It works fine.

However, when I combine these two programs, so the SMS can control the engine, one of the interrupts, responding to CHANGE on pin STDET / C1 simply stops being fired.

After checking everything twice for stupid syntax errors, name/label collisions, re-ordering the attachInterrupt() calls, in case of some undocumented internal limit on interrupt handlers, etc, my work-around was to copy the
attachInterrupt(STDET, _starterInterrupt, CHANGE);
from setup() to being called again in loop(). The loop() is used for the SMS state machine, running independently of the engine state machine. The attachInterrupt() is called (again) once during an initialisation state.

Voila, the interrupt is again fired on CHANGE and updates the startSense variable.

My question is this - it seems the only explanation is that the interrupt handler function attached to STDET / C1 is being detached/re-assigned between setup() and loop(). What would do this? Is there some secondary function of the C1 pin that would detach the interrupt when Cellular.command is called? Only the one interrupt is affected - all the others remain working after the initial attachInterrupt() call.

I know this is incomplete, but I don’t want to post 1000 lines of code. Any clues welcome.

I hate the work-around but will live with it until I find the cause.

Cheers,
AT

I haven’t fully followed your exact setup, but in essence this sounds to me that your misbehaving interrupt may be tied to an EXTI line already occupied by another interrupt. Per EXTI line you can only have one interrupt.

To know which EXTI lines are shared, you need to look up the hardware GPIO lines for each of your used pins and look at the bit number on the respective port.
Simplified: The same bit number over all the ports will share the same EXTI line.

2 Likes

Can you share the code you have working for waking the Electron via incoming SMS using a 3rd part SIM?

I think SdruffR is right about your Interrupt problem.

Hi @Scruffr, thanks for your input. I will take a look - I’d been trying to avoid delving into the hardware level, apart from checking that the IO pins selected would/could be used electrically and logically as I required when I designed the controller hardware (now a PBA, so will be difficult to re-allocate if I’ve stuffed up!).
If it’s a hardware limitation, though, I’m still puzzled why re-attaching the exact same pin to same handler again, later in the code, restores expected behaviour?
Anyway, I will go and learn about EXTI setup, first, before I waste your time!
Cheers,
AT

I’ve been trying to find a reference to EXTI assignments for Electron pins, but the documentation (https://docs.particle.io/reference/firmware/electron/#interrupts) only mentions Core and Photon - seems to be a cut and paste of those devices text, despite being the Electron topic/doco. Sigh. Any suggestions, please, @Scruffr?

Hi @RWB, it’s a bit long to bung into a community post, but was based on code from https://github.com/robynjayqueerie/electron-atcommand-library

However, here is the essence, based on my experimentation with what works and doesn’t.

#include "uCommand.h"
// Supporting Notes, from logged behaviour:
//
// Modem startup sequence issues commands:
//[ Modem::powerOn ] = = = = = = = = = = = = = =
//"AT+CMEE=2"
//  Verbose error reporting
//"AT+CMER=1,0,0,2,1"
//  Discard URC in data mode, discard keyboard evt, dscard display evt, Use +CIEV URC for all events, Send buffered URCs
//[ Modem::init ] = = = = = = = = = = = = = = =
//"AT+UPSV=1"
//  Power Saving: UART goes idle when possible
//"AT+CMGF=1"
//  Use SMS Text mode (not PDU mode)
//"AT+CNMI=2,1"
//  Buffer URCs when busy, +CMTI will indicate message location
// nb:
//  +URING is default (0), so only incoming SMS or call will assert RI line
//
// NOTES:
// The modem library internally requests and snaffles responses for +CSQ (signal quality)
// and URC +CNMI (new message indication). Using these commands/URCs is not recommended.
//
// NOTE! The cellular library can call a software handler, if one has been set up, if a
// +CNMI URC (new message indication) is received from the modem. 
// This DOES NOT WORK unless the modem is continuously polled by the system for URCs. This
// happens in AUTOMATIC mode or when particle cloud is connected in other modes. It cannot
// be relied on in MANUAL or cloud disconnected states. Hence, this code uses the RI line 
// and requires +URING to be configured to raise RI when an SMS or incoming call arrives.

// automatic mode requires this set in STARTUP()
//STARTUP(cellular_credentials_set("live.vodafone.com", "", "", NULL));

SYSTEM_MODE(MANUAL);
SYSTEM_THREAD(ENABLED);

SerialLogHandler logHandler(LOG_LEVEL_ALL);

uCommand modem;

void _riInterrupt();

bool ringIndicated=false;
unsigned long stateTime;

void setup() {
	Serial.begin();
	
	pinMode(D7, OUTPUT);

	// Ring Interrupt - rising edge is interesting
	pinMode(RI_UC, INPUT);
	attachInterrupt(RI_UC, _riInterrupt, RISING);

	// as we're not auto-connecting to the particle cloud, we can set this here 
	cellular_credentials_set("live.vodafone.com", "", "", NULL);

	Log.info("turning on modem...");
	Cellular.on();
	delay(4000);
	Cellular.connect();
	delay(4000);
    stateTime=millis();
}

void loop() {
	digitalWrite(D7,(ringIndicated?HIGH:LOW));
	if (ringIndicated)
	{
		ringIndicated=false;
		Log.info("checking incoming SMS...");
		smsRecvCheck();
        }
        else if (millis() - stateTime > 300000UL){
        	//keep alive and clear out backlog of URCs in manual mode
		CellularSignal sig = Cellular.RSSI();
		Log.info("Poll; Network: RSSI: %d, Quality: %d",sig.rssi,sig.qual);
            stateTime=millis();
        }
	delay(1000);
}

// if CM set, then RI goes HIGH if SMS or Call incoming, for one second
void _riInterrupt(){
    ringIndicated=digitalRead(RI_UC);
}

void smsRecvCheck()
{
	Log.trace("smsRecvCheck:");
	// read next available messages
    	if(modem.checkMessages(10000) == RESP_OK){
		// process messages
	}
}

HTH,
AT

Hi,
Yep, I think you’re likely to be right, and it’s something to do with common infrastructure, probably the shared EXTI signal, as the RI_UC interrupt is now ignored. The one thing I didn’t reverse when I was shuffling the order of interrupt attachment, to see if the problem followed the interrupt IO pin or the attach operation, was to move it after the RI_UC handler was attached. Effectively, this is what I did with my so-called work-around and the RI_UC signal edge is now ignored. Sigh. Not a work-around after all.
As the hardware is built, I will have to revert to polling the sensor inputs. Not quite so elegant but, as I have to debounce the inputs anyway, and I have a 20mS timer running to do that - it may as well just poll the GPIO’s as well.
Cheers for clue, gentlemen.
AT

1 Like

Thanks for sharing that! It will be there when I need it in the future.

Ok, I think I understand, now, what @Scruffr meant by 'The same bit number … will share the same EXTI line’
Electron Pin C1 is [PC12] on the STM32, and RI_UC is [PB12]. I guess both being #12 means they share an EXTI line? I wish I’d known this when designing the board, as I could easily have chosen another GPIO pin! Sigh.
It would be useful if Particle updated/added this kind of detail to the Electron reference docs. I will post this feedback to Particle if I can work out how/where to that.
Cheers,
AT
Edt: Found this later; Linking for reference: Interrupts: #, priority, architecture

2 Likes