I've had my arduino for a week, and have got it controlling home easy devices, listening for home easy (HE) commands, communicating via MQTT to a server but I've run into an annoying problem!
When the device receives home easy remote control messages, it sometime crashes, possibly because in the callback from the home easy receiver, I'm sending a MQTT message to the server, and some HE devices (like the motion detector) send 10-15 messages in quick succession, so I think it is stopping the MQTT message half way, leaving the thing in a weird state (i.e. it hangs, not running loop(), until another HE command is received, then it works as before).
I'm very new to interrupts, but think that I should try disabling the HE code when I've received a HE message, and enabling it at the end of it to guarantee only one thing will be processed at any one time. I've tried putting a simple lock in place, but it still crashes. Also tried cei()/sli() with no luck.
Can anyone tell me how to stop the HE receiver? I think the relevant bits of code are:
/**
* Initialise the system.
*
* Configure the interupt for the receiver.
*/
void HomeEasy::init()
{
pinMode(interuptPin, INPUT);
// disable PWM (default)
TCCR1A = 0x00;
// set prescaler to 1/8. TCNT1 increments every 0.5 micro seconds
// falling edge used as trigger
TCCR1B = 0x02;
// enable input capture interrupt for timer 1
TIMSK1 = _BV(ICIE1);
}
and
/**
* The interupt handler.
*
* This is where the message is received and decoded.
*/
ISR(TIMER1_CAPT_vect)
{
// reset counter
TCNT1 = 0;
// get value of input compare register, divide by two to get microseconds
pulseWidth = (ICR1 / 2);
Perhaps the problem lies in what your interrupt service routine is doing. Would you post that?
The ISR is meant to be very brief. Basically, it should just set some global variable, and return.
The actual handling of that interrupt is then done in the loop() function, based on the value of the global variable. The loop function is interruptable. Your ISR may or may not be.
The routine is from the home easy project on google code, but copied here:
ISR(TIMER1_CAPT_vect)
{
// reset counter
TCNT1 = 0;
// get value of input compare register, divide by two to get microseconds
pulseWidth = (ICR1 / 2);
if(bit_is_clear(TCCR1B, ICES1))
{ // falling edge was detected, HIGH pulse end
if(latchStage == 1 && pulseWidth > 230 && pulseWidth < 280)
{ // advanced protocol latch
latchStage = 2;
}
else if(latchStage == 3 && (pulseWidth < 150 || pulseWidth > 500))
{ // advanced protocol data out of timing range
latchStage = 0;
bitCount = 0;
sender = 0;
recipient = 0;
}
else if(latchStage == 1)
{ // simple protocol data
bitCount++;
if(pulseWidth > 320 && pulseWidth < 430)
{
bit = 0;
}
else if(pulseWidth > 1030 && pulseWidth < 1150 && bitCount % 2 == 0)
{
bit = 0x08;
}
else
{ // start over if the low pulse was out of range
latchStage = 0;
bitCount = 0;
sender = 0;
recipient = 0;
}
if(bitCount % 2 == 0)
{
if(bitCount < 9)
{
sender >>= 1;
sender |= bit;
}
else if(bitCount < 17)
{
recipient >>= 1;
recipient |= bit;
}
else
{
command >>= 1;
command |= bit;
}
}
if(bitCount == 25)
{ // message is complete
if(command == 14 || command == 6)
{
if(HomeEasy::simpleProtocolHandler != NULL)
{ HomeEasy::simpleProtocolHandler((int)sender, recipient, (command == 14));
}
}
latchStage = 0;
bitCount = 0;
sender = 0;
recipient = 0;
}
}
}
else
{ // raising edge was detected, LOW pulse end
if(latchStage == 0 && pulseWidth > 9480 && pulseWidth < 11500)
{ // pause between messages
latchStage = 1;
}
else if(latchStage == 2 && pulseWidth > 2550 && pulseWidth < 2750)
{ // advanced protocol latch
latchStage = 3;
}
else if(latchStage == 3)
{ // advanced protocol data
if(pulseWidth > 200 && pulseWidth < 365)
{
bit = 0;
}
else if(pulseWidth > 1000 && pulseWidth < 1360)
{
bit = 1;
}
else
{ // start over if the low pulse was out of range
latchStage = 0;
bitCount = 0;
sender = 0;
recipient = 0;
}
if(bitCount % 2 == 1)
{
if((prevBit ^ bit) == 0)
{ // must be either 01 or 10, cannot be 00 or 11
latchStage = 0;
bitCount = -1;
}
else if(bitCount < 53)
{ // first 26 data bits
sender <<= 1;
sender |= prevBit;
}
else if(bitCount == 53)
{ // 26th data bit
group = prevBit;
}
else if(bitCount == 55)
{ // 27th data bit
command = prevBit;
}
else
{ // last 4 data bits
recipient <<= 1;
recipient |= prevBit;
}
}
prevBit = bit;
bitCount++;
if(bitCount == 64)
{ // message is complete
if(HomeEasy::advancedProtocolHandler != NULL)
{ HomeEasy::advancedProtocolHandler(sender, recipient, (bool)command, group);
}
sender = 0;
recipient = 0;
latchStage = 0;
bitCount = 0;
}
}
}
// toggle bit value to trigger on the other edge
TCCR1B ^= _BV(ICES1);
}
There is a non-interrupt version on the playground, which takes its time to get through the loop (and therefore means incoming MQTT messages are delayed) - would you suggest I experiment with that version instead?
Thanks for your reply - much appreciated! I'm a Java developer, haven't touched C or embedded systems before this, so this is all very new!!
With the exception of the calls to HomeEasy::simpleProtocolHandler and/or HomeEasy::advancedProtocolHandler, nothing in that ISR appears to take long to execute. I have no idea what these functions do, or whether there are even any handlers registered that would need to be called.
I suppose I'd try the non-interrupt version. Perhaps the Arduino is simply not fast enough to handle the speed at which the interrupts arrive.
Ah, the advanced handler does nothing, but I've registered a simple handler in my code that gets called by the code above. In my simple handler, I make and send a MQTT message, which would take time.
I guess I could make some kind of queue of messages, and append to that queue in the simple handler, then in the loop() I could pull messages out of the queue and do the MQTT sending there. If only Java was supported, queues would be easy!!