External Interrupts on the ArdupilotMega

Coconut Pi uses a microcontroller platform as a bridge between the main controller board, our Raspberry Pi, and the sensors and actuators it has. So of course part of the work and problems it brings itself with would be in the interfacing with the various hardware given the slew of protocols we can utilise i.e. UART, I2C, SPI, PWM and Analog. Our ArdupilotMega (APM), based on the very popular open sourced platform Arduino, is able to interface using these protocols. In fact the sensors on our system makes use of all that I have listed in the following:

1. Two UARTs connected to the Pi, one as a CLI debugging interface and the one as the command channel.

2. Two UARTs connected to two Ultrasonic sensors

3. Magnetometer connected to I2C

4. Pressure sensor connected to I2C

5. Inertial Measurement Unit connected to I2C

6. Two Current and Voltage sensors connected via analog

7.  Flow sensor interfaced via PWM.

Interfacing with the Flow Sensor

Now the Flow sensor is actually the topic for this blog post. Why? Because it was a huge source of frustration. We used these 1/2 inch diameter Flow rate sensors from Seedstudio:flowsensor_LRGInterfacing with it was trivial, the sensor would output pulse width cycles which had a linear relationship with flow rate (L/min). Thus a flow rate of say 1 L/min would generate a 4 Hz cycle and 10 L/min would generate about 60Hz. You can get more information on how it works exactly and the linear graph that it generates here from the wiki.

There are a couple of ways to interface with the sensor with the simplest to understand being  by a constant polling method to check for the occurrence of a pulse. However such a method would waste precious cycles on the microcontroller and is not recommended. A better way would be to use Interrupts be it through External Interrupts or Pin Change Interrupts. These Interrupts are capable of detecting when there is a Low level, rising edge, falling edge, or either at the pin. Thus to detect a single pulse that occurs we can detect when there is a rising edge/falling edge and count the number of pulses that occur each time.

The problem

Of course in a nutshell, I was having trouble getting the External Interrupt to work on the APM. The APM is a highly customised board to suit Autopilot needs and I was lucky enough to find two pins on the board which corresponded to two Interrupts as defined by Arduino on their page . So here is the definition extracted from it:

Board int.0 int.1 int.2 int.3 int.4 int.5
Uno, Ethernet 2 3
Mega2560 2 3 21 20 19 18
Leonardo 3 2 0 1
Due (see below)

Alright so cool, based on that I realised I had GPIO 2 and GPIO 3 which was actually Output Channel 7 and Channel 6 on the APM and I could hook these up to INT0 and INT1. After reading thru the source code and running through quite a bit, I managed to figure out the registers I had to set and procedure for initialising of INT0 with rising edge trigger detection and integrate it into the APM source code which had it all done using avrlibc and not the Arduino libraries which would add overheads into our code definitely. This turned out to be the source of the problem and headache because after tons of debugging, reading and re-reading through the Atmega 2560 datasheet and intense googling I could not figure out how my Interrupt just did not work as expected. It was triggering off like crazy every second. Initially I had a tip off from a friend that it could have been a software instruction such as Timers having had triggered off the external interrupts but after disabling everything I could I still could not solve the problem. ( Even the bits COM3B1 and COm3B0 in TCCR3A to disable OCR3B from affecting the GPIO output).  Here is a sample of the Arduino and AVR code that I used to enable the external interrupt, INT0.

#include <avr/interrupt.h>

volatile int val = 0;

ISR(INT0_vect) {
    rpm();
}

void rpm(void)
{
	val++;
}

void setup()
{
//Initialise the Serial port for debugging
	Serial.begin(115200);
//Store the Status Register SREG in a temporary location
	uint8_t oldSREG = SREG;
	cli();
//Set the control registers to enable external interrupts with rising edge detection
    EICRB = (EICRB & ~((1 << ISC00) | (1 << ISC01))) | (3 << ISC00);
	EIMSK |= (1 << INT0);
	SREG = oldSREG;
	pinMode(2, INPUT); //initializes digital pin 2 as an input
    digitalWrite(2, HIGH);    // Enable pullup resistor
}

void loop()
{

  /* add main program code here */
	Serial.print(val);
	delay(500);
}

The Solution

OLYMPUS DIGITAL CAMERA
After further googling I chanced upon this blog post here which prompted me to double check my pins. I knew that GPIO 2 was connected to PE4 and after checking the datasheet again…I realised PE4 was also INT4 and not INT0! In implemented their attachInterrupt Function Arduino actually jumbled up the Interrupt notations which is extremely annoying. It was my source of frustration for two days. So I switched everything to Interrupt 4 but subsequently the program kept crashing every time I moved spun the flow sensor. I double checked and found out later then that I had not switched the interrupt vector handler and Interrupt 4 was just essentially jumping into null instructions which caused the system to repeatedly restart. Such is the fate of us embedded systems programmers but I just enjoy and get quite a bit of kick from doing all these :), and it worked perfectly fine right after anyway. Later on I realised also that the crazy triggering of the interrupt was caused by the SCL line for I2C. INT0 of the Atmega2560 is also tied to the SCL clock which could have been why Arduino changed the numberings.

Till next update! Btw a little teaser to what we have been working on!

OLYMPUS DIGITAL CAMERA

Advertisements