Skip to content

encoder interrupts

Peter Harrison edited this page Jul 20, 2019 · 5 revisions

Interrupts

Among the many sources of interrupts on the ATMega328p, two are particularly interesting when you ant to interface encoders. These are INT0 and INT1. Each has a dedicated pin on the processor and each has a separate interrupt service vector. This means that the interrupt service can respond quickly to the specific pin without having to waste time working out which pin caused the change.

Each of these pins can be programmed to generate an interrupt when there is a logical change on the pin. The encoders attached to the motors generate a series of pulses that change state 12 times for every rotation of the motor. By using the INT0 and INT1 interrupts, the processor can keep a count of hose changes and so measure the speed of rotation and the distance travelled by the robot.

Setting up the processor

The ATMega328p can generate interrupt as a result of changes of state on two of its pins:

PD2 for INT0 - Arduino Digital Pin 2 PD3 for INT1 - Arduino Digital Pin 3

The INT0 and INT1 interrupts can be triggered by a change in state on the corresponding pin. This action is set up by writing bits to the External Interrupt Control Register A (EICRA).

###The EICRA

The External Interrupt 0 is activated by the external pin INT0 if the corresponding interrupt mask is set and interrupts are enabled. The level and edges on the external INT0 pin that activate the interrupt are defined as

ISC01 ISC00 Description
0 0 Low Level of INT0 generates interrupt
0 1 Logical change of INT0 generates interrupt
1 0 Falling Edge of INT0 generates interrupt
1 1 Rising Edge of INT0 generates interrupt

The External Interrupt 1 is activated by the external pin INT1 if the corresponding interrupt mask is set and interrupts are enabled. The level and edges on thexternal INT1 pin that activate the interrupt are defined as

ISC11 ISC10 Description
0 0 Low Level of INT1 generates interrupt
0 1 Logical change of INT1 generates interrupt
1 0 Falling Edge of INT1 generates interrupt
1 1 Rising Edge of INT1 generates interrupt

To enable these interrupts, bits must be set in the external interrupt mask register EIMSK

EIMSK:INT0 (bit 0) enables the INT0 external interrupt EIMSK:INT1 (bit 1) enables the INT1 external interrupt

The configuration code

It may have seemed complicated to perform all this configuration but in reality it needs only a few of lines of code:

EICRA |= (0 << ISC01) | (1 << ISC00);   // logical change on INT0
EIMSK |= (1 << INT0);                   // Enable the INT0 interrupt
EICRA |= (0 << ISC11) | (1 << ISC10);   // logical change on INT1
EIMSK |= (1 << INT1);                   // Enable the INT1 interrupt

Interrupt Service Routines

Once the state of one of the pins changes, the processor will generate an interrupt and the flow of execution will jump to a predetermined location where it expects to find some code that can respond to the interrupt. This is written as a special type of function called an Interrupt Service Routine (ISR). Note that, if the ISR is not present, the processor behaviour is not defined and the program will fail.

To declare an ISR in the arduino environment, you need to use one of the predefined names. For the INT0 and INT1 interrupts, you will need these two ISRs:

ISR(INT0_vect) {
}

ISR(INT1_vect) {
}

If there is no code in the ISR, the processor will clear the interrupt state and immediately return to normal execution.

Servicing the Encoders

Each of the robot's encoders behaves the same way. Two pins are used on the processor. One has the combined pulse signal from the XOR gate and is connected to one of the interrupt pins, INT0 or INT1. The other signal is connected to another pin so that the ISR can examine it and work out whether the motor has turned and in what direction. Both ISRs have essentially the same code but operate on different pins. here are the complete ISRs:

int encoderLeftCount;
int encoderRightCount;

ISR(INT0_vect) {
  static bool oldA = 0;
  static bool oldB = 0;;
  bool newB = digitalReadFast(encoderLeftPinB);
  bool newA = digitalReadFast(encoderLeftPinAxB) ^ newB;
  encoderLeftCount += (oldA ^ newB) - (newA ^ oldB);
  oldA = newA;
  oldB = newB;
}

ISR(INT1_vect) {
  static bool oldA = 0;
  static bool oldB = 0;;
  bool newB = digitalReadFast(encoderRightPinB);
  bool newA = digitalReadFast(encoderRightPinAxB) ^ newB;
  encoderRight += (oldA ^ newB) - (newA ^ oldB);
  oldA = newA;
  oldB = newB;
}

The code is a little hard to understand but it is written to execute as fast as possible. On a 16MHz Arduino, each function will take 3 microseconds to execute so it should be possible to respond to quite high pulse rates without getting the processor bogged down.