Published using Google Docs
An Introduction to MatrixPilot Interrupts
Updated automatically every 5 minutes

MatrixPilot’s Interrupt Service Routines

Note: All the examples in this document are for MatrixPilot 3.2.1 for a classic UDB2 or UDB3


Author: Peter Hollands, 9th December 2011

With thanks to Ben Levitt for review and additions

Version: 1.0

Introduction

A good way to understand how MatrixPilot works, is to learn about the interrupt routines, their purpose, frequency, and priority.

First off, note that the code for MatrixPilot is organized into 3 main sections.:-

The code is structured so that it is easy to re-use the main libraries for cars, boat, quads or any other uses that may come to mind.

The Main() routine

Developers often try to understand MatrixPilot by looking at the code in main.c (located in the MatrixPilot directory) to see how the software branches into its subroutines. However, there is very little to see in the main routine other than some logic to measure how much time in spent in the interrupts (CPU is busy), and how much time is spent in the main routine (cpu is idle, no interrupts need servicing). The main routine has two purposes:-

  1. Initialise all the code and switch on the interrupt routines
  2. Measure the percentage of CPU usage when the plane is flying.

The main program looks like this:-

int main (void)

{

            udb_init() ;

            dcm_init() ;

            init_servoPrepare() ;

            init_states() ;

            init_behavior() ;

            init_serial() ;

           

            udb_run() ;

            // This never returns.

           

            return 0 ;

}

udb_run() has one statement and looks like this:-

void udb_run(void)

{

            //  nothing else to do... entirely interrupt driven

            while (1)

            {

                    // pause cpu counting timer while not in an ISR

                    indicate_loading_main ;

            }

            // Never returns

}

“indicate_loading_main” is a macro in libUDB_internal.h . The macro is:-

#define indicate_loading_main   {                                  \

                                      T5CONbits.TON = 0 ;          \

                                }

Whenever the main routine is running it continually goes around an infinite while loop turning off the T5 timer  (by setting its control bit  to 0).

At the start of every interrupt routine is the “indicate_loading_inter” macro. This opposite macro

will turn on the T5 timer when the computer enters an interrupt routine.

#define indicate_loading_inter  {                                  \

                                      T5CONbits.TON = 1 ;                 \

                                }

So once the main() routine has initialised MatrixPilot’s variables, and turned on the interrupts, the sole purpose of the main routine is to help to measure cpu usage, by turning off the T5 timer when there are no interrupt routines being serviced.

You may wonder why the macro above is called “indicate_loading_main” rather than “stop_interrupt_timer”. The answer is historical. When Bill Premerlani originally wanted to look at CPU usage, he simply created two macros which turned on one of the LEDs when interrupt routines started and turned off the same LED when in main(). The brightness or dimness of the LED showed how busy the CPU was. The LED was the “indicator”. And the name “indicate_loading_main” has been left as it was. That code for turning the LEDs on and off is still in the source, but commented out, a few lines above ….

//#define indicate_loading_main             //LATEbits.LATE4 = 0

//#define indicate_loading_inter            //LATEbits.LATE4 = 1

So that is it. You’ve seen all there is to see in MatrixPilot’s main() routine (except the initialisation  routines. That is best done later:-) ).

All the real work, in computing the direction cosine matrix, and flying the plane is done using an Interrupt architecture.  We shall now look at the interrupt routines, their priority, frequency and their function.

An Overview of MatrixPilot Interrupt Routines

Interrupts are sorted here by priority, with the highest priority interrupts first.  Interrupts can interrupt each other, but only a higher priority interrupt can interrupt a lower priority interrupt.  If a lower priority interrupt is triggered while a higher priority one is running, the lower priority one will run after the higher priority one finishes.

Please click on underlined table entries to see the relevant code in the main trunk repository.

Interrupt Routine

Pri-or-

ity

Trigger / Frequency 

Description

_INT0Interrupt()

7

Triggers on a rising or falling edge of channel5 input PWM to UDB.
Frequency: 80Hz

PWM input on channel 5 (pin RE8).

The interrupt code checks that PWM pulse widths fall within normal parameters. If not, a failsafe counter is incremented. When failsafe_counter exceeds a threshold, other code in states.c  will trigger the fact that radio reception has been lost and move the plane to “Return To Landing” mode

_T4Interrupt()

7

One interrupt for every edge of every PWM channel for every active channel. So potentially 9 * 2 * 40 edges / second.
Max Frequency: 720Hz.

Create PWM pulses to drive external PWM OUT channels (control servos, and throttle).

_T1Interrupt()

6

Frequency: 40Hz
The frequency of this interrupt is often referred to as the “frame rate” by the MP / UDB development team. Many of the core routines of MatrixPilot will be updating the DCM at the frame rate, and controlling the flight of the plane at the frame rate.

This is the main heartbeat of the UDB. It initiates the sending of a PWM pulse to the servos by calling start_pwm_outputs() which in turn also starts another interrupting timer (_T4Interrupt )

The heartbeat calls the ½ second routines that actions state based transitions (manual, stabilized, autonomous, Return to Landing) by calling udb_background_callback_periodic()

It then starts all the DCM calculations ( running at different priority, 3) by triggering another interrupt.  (setting _THEARTBEATIF = 1 ; on the UDB3 this is the _PWMInterrupt ..  see below)

_T5Interrupt()

6

Triggers when 4096 (16*256) instructions have been executed in interrupt routines.

Frequency:
Typically 100Hz

(4 million instructions / second; 10% loading; means 400,000 instructions in interrupts / second. 4096 instructions / interrtupt gives

96 interrupts / second)

The interrupt does one small task which is to increment _cpu_timer, to allow MP3.2.1 to keep track of cpu usage.

_IC7Interrupt()

6

Rising or falling edge of channel1 input PWM to UDB.

Frequency: 80Hz

PWM input on channel 1

.The interrupt code checks that PWM pulse widths fall within normal parameters. If not, a failsafe counter is incremented. When failsafe_counter exceeds a threshold, other code in states.c  will rigger the fact that radio reception has been lost and move the plane to “Return To Landing” mode

_IC8Interrupt()

6

Same as above, for input channel2

PWM input on channel 2. Same functionality as channel 1 above.

_IC2Interrupt()

6

Same as above, for input channel3

PWM input on channel 3. Same functionality as channel 1 above.

_IC1Interrupt()

6

Same as above, for input channel4

PWM input on channel 4. Same functionality as channel 1 above.

_IC1Interrupt()

6

Rising and falling edge of every PPM pulse when UDB configured for PPM input. Frequency is potentially 10 * 40 * 2 edges / second. i.e. Frequency: 800 Hz.

Used for PPM input (as opposed to PWM input) on channel 4 input to UDB.

_ADCInterrupt()

5

Frequency:
Normally 8800 Hz

If additional analog inputs used, then code will reduce frequency.

Gyros, accelerometers and other analog inputs are continually sampled, integrated, and then averaged just before their expected use.

_MI2CInterrupt()

5

Triggered when new data, requested from magnetometer, has arrived.
Frequency: Low

Use data received from magnetometer.

_U2RXInterrupt()

4

Triggers when character received from GPS device. Frequency of interrupt related to baud rate while characters being received. For 19200 baud expect say burst of say 60 interrupts at a rate of 1920Hz per GPS line of data.

Frequency: 1920Hz for baud rate of 19200

When character is received from GPS unit, interrupt will call the GPS parser to convert serial character into an internal MP3.2.1. variable. The routine is called

udb_gps_callback_received_byte(rxchar) ; That routine is the entrance to  parser / state machine which is GPS dependent. For example, the EM406A state machine is initialised here. And the first character from the EM406A is then parsed by this state code.

_U2TXInterrupt()

4

Triggers when character sent to GPS device. Frequency related to baud rate.

Data is only sent to the GPS at initialisation to configure the data streams and their frequencies.

_U1RXInterrupt()

4

Triggers when character received from telemetry uplink. Frequency related to baud rate.

_U1TXInterrupt()

4

Triggers when character sent to telemetry downlink. Frequency related to baud rate.

_PWMInterrupt()

3

Triggered from the 40Hz Heartbeat interrupt called _InterruptT1(). The interrupt is called as a means of dropping the interrupt priority level of the code.

Frequency: 40Hz

This interrupt will:-

check whether the receiver is receiving valid PWM signals and then call:-

 calculate_analog_sensor_values() 

 udb_callback_read_sensors() 

 udb_servo_callback_prepare_outputs()

    which in turn will call:-

   

This interrupt and the code that it calls, is at the heart of controlling the plane.

_T3Interrupt()

2

This interrupt is triggered every time a new GPS message  has arrived from the external GPS unit and  has had its message “parsed” into the internal variables of MatrixPilot. The interrupt is triggered from within specific GPS parser driver. For example for the EM406A, it is triggered here by calling udb_background_trigger() .

Frequency: 1 to  4Hz

The resulting interrupt calls udb_background_callback_triggered(), which processes the new GPS information, calculates acceleration, kicks of the estimatewind() routine, and runs the yawdrift() code to correct for yaw gyro drift.

_T7Interrupt()

n/a

n/a

Not Applicable to UDB2 / 3. Used on UDB4.    

_T6Interrupt()

n/a

n/a

Not applicable to UDB2 / UDB 3. Used on UDB4.

The above table was constructed using the “Find All” facility in Microchip’s Interactive Development Environment called MPLAB (version 8.63). I used the menu called “Edit / Find in Files” and then filled in the prompt for “Find What” with “__interrupt__” (note there are two underscores at the beginning and end of “intterupt”). I then clicked on “Find”. The following output was then returned.

Note: For conciseness I have deleted the following first part of the file path from each line below:-

C:\Users\phollands\Desktop\MatrixPilot_3_2\libUDB\

All of the interrupt definitions are stored in the libUDB directory for the UDB2 and UDB3.

background.c:128: void __attribute__((__interrupt__,__no_auto_psv__)) _T1Interrupt(void)

background.c:177: void __attribute__((__interrupt__,__no_auto_psv__)) _T7Interrupt(void)

background.c:179: void __attribute__((__interrupt__,__no_auto_psv__)) _T3Interrupt(void)

background.c:200: void __attribute__((__interrupt__,__no_auto_psv__)) _T5Interrupt(void)

background.c:216: void __attribute__((__interrupt__,__no_auto_psv__)) _T6Interrupt(void)

background.c:218: void __attribute__((__interrupt__,__no_auto_psv__)) _PWMInterrupt(void)

analog2digital_udb.c:120: void __attribute__((__interrupt__,__no_auto_psv__)) _ADCInterrupt(void)

magneto_udb.c:190: void __attribute__((__interrupt__,__no_auto_psv__)) _MI2CInterrupt(void)

radioIn_udb.c:114: void __attribute__((__interrupt__,__no_auto_psv__)) _IC7Interrupt(void)

radioIn_udb.c:157: void __attribute__((__interrupt__,__no_auto_psv__)) _IC8Interrupt(void)

radioIn_udb.c:200: void __attribute__((__interrupt__,__no_auto_psv__)) _IC2Interrupt(void)

radioIn_udb.c:243: void __attribute__((__interrupt__,__no_auto_psv__)) _IC1Interrupt(void)

radioIn_udb.c:286: void __attribute__((__interrupt__,__no_auto_psv__)) _INT0Interrupt(void)

radioIn_udb.c:337: void __attribute__((__interrupt__,__no_auto_psv__)) _IC1Interrupt(void)

serialIO_udb.c:71: void __attribute__((__interrupt__,__no_auto_psv__)) _U2RXInterrupt(void)

serialIO_udb.c:90: void __attribute__((__interrupt__,__no_auto_psv__)) _U2TXInterrupt(void)

serialIO_udb.c:155: void __attribute__((__interrupt__,__no_auto_psv__)) _U1RXInterrupt(void)

serialIO_udb.c:174: void __attribute__((__interrupt__,__no_auto_psv__)) _U1TXInterrupt(void)

servoOut.c:194: void __attribute__((__interrupt__,__no_auto_psv__)) _T4Interrupt(void)

Search complete. 19 matches found.

When you are in  MPLAB IDE, each of the above lines can be “double clicked” so that MPLAB will then take you to the exact line where the interrupt function has been defined. For example line 128 of background.c to see the function that executes against the _T1Interrupt (Timer 1).

Using “Find All” is a useful way of exploring how each variable in MatrxiPilot is used, as it will find all occurrences of that variable across the entire project build (not just in one file).

*** DOCUMENT ENDS ***