Arduino Interrupts Question

Hello everyone,

Could someone please explain what the below snippets of code do? I know they have something to do with interrupts but aside from that I am still unsure.


// clear and set interup routines

#ifndef cbi

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))

#endif

#ifndef sbi

#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

#endif
  // Interupt setting
  GIMSK  = bit (PCIE1);   // Pin change interrupt enable
  PCMSK1 = bit (PCINT10); // Enable interupt on PCINT10 (D2)
  sei();                  // enables interrupts
ISR(PCINT1_vect){  // Magnet sensed
  if (!digitalRead(magPin)){
    rotationFlag = true;             // Increment volatile variables
    revolutions += 1;
  }
}

The full program, for context, is below.

/*

cd C:\Users\MakersBox\Downloads\arduino-1.6.4\hardware\tools\avr\bin
avrdude -C ..\etc\avrdude.conf -P COM8 -b 19200 -c avrisp -p attiny84 -v -e -U lfuse:w:0xE2:m -U hfuse:w:0xD7:m -U efuse:w:0xFf:m  -U flash:w:SMD_spinner_THT_R2b.cpp.hex


Attiny Core:
https://github.com/SpenceKonde/ATTinyCore
Board: Attiny 24/44/84
Pin Mapping: Counter-Clockwise
Chip: Attiny 84
Clock: 8 MHz (internal)

// ATMEL ATTINY84 / ARDUINO
//
//                           +-\/-+
//                     VCC  1|    |14  GND
//             (D  0)  PB0  2|    |13  AREF (D 10)
//             (D  1)  PB1  3|    |12  PA1  (D  9) 
//                     PB3  4|    |11  PA2  (D  8) 
//  PWM  INT0  (D  2)  PB2  5|    |10  PA3  (D  7) 
//  PWM        (D  3)  PA7  6|    |9   PA4  (D  6) 
//  PWM        (D  4)  PA6  7|    |8   PA5  (D  5)        PWM

*/

#include <EEPROMex.h>
#include <avr/pgmspace.h>
#include <avr/sleep.h>
#include "font.h"
#include "textAndShapes.h"

// clear and set interup routines
#ifndef cbi
  #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
  #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif


const int charHeight = 8;
const int charWidth = 5;
int rows= 8;		                 // Total LED's in a row
bool STATES[] = {LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW};
int LEDS[] = {10, 9, 8, 7, 6, 5, 4, 3};  // Port A
byte magPin = 2;                 // Hall effect sensor, pulled-up, goes low when magnet passes      
byte buttonPin = 0;        


char charArray[6];                       // holds characters to display
unsigned long lastTimeUs;                // time (us) magnet sensed
unsigned long revTimeUs;                 // time (us) of last full rotation
unsigned long dwellTimeUs;               // time (us) between LED changes (based on rotation speed)
volatile unsigned long revolutions = 0;  // track number of revolutions since last start
long totalRevolutions;                   // track total number of revolutions (stored in EEPROM)
bool spinning = true;                    // track reset of time & counts
unsigned long startTimeUs;               // time (us) current spinning started
  
int mode;                                // current operating mode, stored in EEPROM
int modes = 8;                           // number of modes available
  // 0 -> text "Hello World!"
  // 1 -> RPM
  // 2 -> time in seconds
  // 3 -> spin count
  // 4 -> spin count (total)
  // 5 -> "lilly pad" pattern
  // 6 -> shape 1 (heart)
  // 7 -> shape 2 (smile)
  
     

volatile boolean rotationFlag = false;  // modified by ISR

void setup(){

  Serial.begin(9600);
  
  // setup inputs
  pinMode(buttonPin, INPUT_PULLUP);
  //digitalWrite(buttonPin, HIGH);
  pinMode(magPin, INPUT_PULLUP);
  //digitalWrite(magPin, HIGH);
  
  // setup other LEDs
  for(int LED=0; LED<(sizeof(LEDS)/sizeof(int)); LED++){
    pinMode(LEDS[LED], OUTPUT);
    digitalWrite(LEDS[LED], STATES[LED]);
  }

  // Interupt setting
  GIMSK  = bit (PCIE1);   // Pin change interrupt enable
  PCMSK1 = bit (PCINT10); // Enable interupt on PCINT10 (D2)
  sei();                  // enables interrupts

  // get saved mode from eeprom
  mode = EEPROM.read(0);
  if (mode >= modes) {
    mode = 0; // may be very large first time
  }
  
  // get saved revolution total from eeprom
  totalRevolutions = EEPROMReadlong(1);
  if (totalRevolutions < 0 || totalRevolutions > 999999UL){ // will be -1 first time.
    totalRevolutions = 0;
    EEPROMWritelong(1, 0);
  }

  // show we are alive and what mode is active
  blipLEDs();

  lastTimeUs = micros();
}


void loop(){
  int sigFigs = 5;               // number of significant figures to display
  unsigned long curTimeUs;
  int seconds;
  unsigned int rpm;
  
  checkButton();
  if ((micros() - lastTimeUs) > 20000000UL){  // sleep after 20 seconds - time variables measured in microseconds
    digitalWrite(LEDS[0], HIGH);
    ledsOff();
    system_sleep();
    lastTimeUs = micros();
  }
  if (((micros() - lastTimeUs) > 2000000UL)){ // less than 1 rev / sec
    if (spinning){
      spinning = false;
      totalRevolutions = totalRevolutions + revolutions;
      EEPROMWritelong(1, totalRevolutions);
      revolutions = 0;
      clearArray();
      blipLEDs();
      digitalWrite(LEDS[mode], LOW);
      //system_sleep();
    }
    else {
      //digitalWrite(LEDS[mode], LOW); 
    }
  }

  if (mode == 5){  // lilly pad pattern, show regardles of magnet.
    for(int LED=0; LED<(sizeof(LEDS)/sizeof(int)); LED++){
       digitalWrite(LEDS[LED], HIGH); 
       delay(1); 
       digitalWrite(LEDS[LED], LOW); 
    } 
    for(int LED=(sizeof(LEDS)/sizeof(int))-1; LED >= 0; LED--){
       digitalWrite(LEDS[LED], HIGH); 
       delay(1); 
       digitalWrite(LEDS[LED], LOW); 
    }     
  }

  else if (rotationFlag){ // we are spinning!
    rotationFlag = false;
    if (!spinning){
      spinning = true;
      startTimeUs = micros(); 
    }
    curTimeUs = micros();
    revTimeUs = curTimeUs - lastTimeUs;
    dwellTimeUs = revTimeUs * 3UL / 360UL;   // 3 degrees
    seconds = (curTimeUs - startTimeUs) / 1000000UL;
    rpm = 60000 * 1000 / revTimeUs;
    lastTimeUs = curTimeUs;
    clearArray();    
    if (mode == 0){
      strcpy (charArray, text);
      //sprintf(charArray, "%lu", dwellTimeUs);
    }
    else if (mode == 1){
      sprintf(charArray, "%d", rpm);
      sigFigs = 3;
    }
    else if (mode == 2){
      sprintf(charArray, " %d", seconds);
    }   
    else if (mode == 3){   
      sprintf(charArray, " %lu", revolutions);
    }
    else if (mode == 4){   
      sprintf(charArray, "%lu", totalRevolutions + revolutions);
    }
    else if (mode == 6 || mode ==7){ // shapes 1 or 2
      dwellTimeUs = revTimeUs * 5UL / 360UL;   // 5 degrees
      for(int k=0; k< sizeof(shape_1);k++){
        if (rotationFlag){
          break;
        }
        int i = k + 26; // advancing because of magnet location
        if(i >= sizeof(shape_1)){
          i = i - sizeof(shape_1);
        }
        char b;
        if (mode == 6){
          b = pgm_read_byte_near(&(shape_1[i]));
        }
        else{
          b = pgm_read_byte_near(&(shape_2[i]));          
        }
        //for (int j=0; j<charHeight; j++) {
        //  digitalWrite(LEDS[j], bitRead(b, j));
        //}
        PORTA = b;
        delayMicroseconds(dwellTimeUs);
      }
    }    


    // Text in top half
    if (mode < 5) { 
      int digits = 0;    
      for(int k=0; k< sizeof(charArray); k++){
        char c = charArray[k];
        if (rotationFlag){
          break;
        }
        if(c){
          if(mode==0){
            printLetter(c);
          }
          else{
            if (digits < sigFigs){
              printLetter(c);
              //digits += 1;
            }
            else{
              printLetter('0');
            }
            digits += 1;
          }
        }
      }
      
      // Handle display in lower section
      clearArray();
      if(1 && (revTimeUs < 200000)){
        char * ptr = (char *) pgm_read_word (&string_table[mode]);
        //char buffer [6]; // must be large enough!
        strcpy_P (charArray, ptr);
  
        // wait for it . . .
        while((micros() < (lastTimeUs + revTimeUs / 2 - 5 * dwellTimeUs)) && !rotationFlag){};
        
        // show it
        for (int k=sizeof(charArray)-1; k>=0; k--){
          if (rotationFlag){
            break;
          }
          printLetterLower(charArray[k]);;
        }
      }
    }
  }  
}


ISR(PCINT1_vect){  // Magnet sensed
  if (!digitalRead(magPin)){
    rotationFlag = true;             // Increment volatile variables
    revolutions += 1;
  }
}


void ledsOff(){
  for(int LED=0; LED<(sizeof(LEDS)/sizeof(int)); LED++){
    digitalWrite(LEDS[LED], LOW); 
  }   
}


void dwellDelay(){ // avoid glitch on first rotation having erronious value 
  if (dwellTimeUs > 2000){
    dwellTimeUs = 2000;
  }
  if (dwellTimeUs < 100){
    dwellTimeUs = 100;
  }  
  delayMicroseconds(dwellTimeUs);
}


void printLetter(char ch){
// https://github.com/reger-men/Arduion-POV-clock/blob/master/clock.ino
  // make sure the character is within the alphabet bounds (defined by the font.h file)
  // if it's not, make it a blank character
  if (ch < 32 || ch > 126){
    ch = 32;
    }
  // subtract the space character (converts the ASCII number to the font index number)
  ch -= 32;
  // step through each byte of the character array
  for (int i=0; i<charWidth; i++) {
    char b = pgm_read_byte_near(&(font1[ch][i]));
    //for (int j=0; j<charHeight; j++) {
    //  digitalWrite(LEDS[j], bitRead(b, 7-j));
    //}
    PORTA = b;
    dwellDelay();
  }
  
  //clear the LEDs
  PORTA = 0;
  //for (int i = 0; i < rows; i++){
  //  digitalWrite(LEDS[i] , LOW);
  //}
  dwellDelay();
  dwellDelay();
}


void printLetterLower(char ch){
  // make sure the character is within the alphabet bounds (defined by the font.h file)
  // if it's not, make it a blank character
  if (ch < 32 || ch > 126){
    ch = 32;
    }
  // subtract the space character (converts the ASCII number to the font index number)
  ch -= 32;
  // step through each byte of the character array
  for (int i=charWidth-1; i>-1; i--) {
    char b = pgm_read_byte_near(&(font[ch][i]))<<1;
    PORTA = b;
    //for (int j=0; j<charHeight; j++) {
    //  digitalWrite(LEDS[j+1], bitRead(b, j));
    //}
    dwellDelay();
  }
  //clear the LEDs
  
  for (int i=0; i<rows; i++){
    digitalWrite(LEDS[i], LOW);
  }
  //PORTA = 0;
  // space between letters
  dwellDelay();
  dwellDelay();
}  
 


bool touched(){
  // returns true if touched, false if not.  Light LED until touch released
  bool touchVal = digitalRead(buttonPin);
  if (!touchVal){
    while(!digitalRead(buttonPin)){ // wait till touch release
      delay(10);
      digitalWrite(LEDS[mode], LOW);
    }
    //digitalWrite(LEDS[0], LOW);
    return (true);
  }
  else{
    return (false);
  }
}


void checkButton(){
  // check button for mode change and display current mode
  if (touched()){
    mode += 1;
    if (mode >= modes){
      mode = 0;
    }
    EEPROM.write(0, mode);
    blipLEDs();
  }
}


void blipLEDs(){
  // something to show we are alive
  for(int LED=0; LED<(sizeof(LEDS)/sizeof(int)); LED++){
    digitalWrite(LEDS[LED], HIGH); 
    delay(10); 
    digitalWrite(LEDS[LED], LOW); 
  } 
  for(int LED=sizeof(LEDS)/sizeof(int); LED>mode; LED--){
    digitalWrite(LEDS[LED], HIGH); 
    delay(10); 
    digitalWrite(LEDS[LED], LOW); 
  } 
  digitalWrite(LEDS[mode], HIGH);
}



void EEPROMWritelong(int address, long value)  {
  //This function will write a 4 byte (32bit) long to the eeprom at
  //the specified address to address + 3.
  //https://playground.arduino.cc/Code/EEPROMReadWriteLong

  //Decomposition from a long to 4 bytes by using bitshift.
  //One = Most significant -> Four = Least significant byte
  // OxFF is the hexadecimal value equal to 00000000 00000000 00000000 11111111
  byte four = (value & 0xFF); // AND bitwise operator combines two binary values such that only two 1s equal a 1. This is taking only the last byte of the 32 bit long (4 bytes)
  byte three = ((value >> 8) & 0xFF);
  byte two = ((value >> 16) & 0xFF);
  byte one = ((value >> 24) & 0xFF);

  //Write the 4 bytes into the eeprom memory.
  EEPROM.write(address, four);
  EEPROM.write(address + 1, three);
  EEPROM.write(address + 2, two);
  EEPROM.write(address + 3, one);
}


long EEPROMReadlong(long address){
  //Read the 4 bytes from the eeprom memory.
  long four = EEPROM.read(address);
  long three = EEPROM.read(address + 1);
  long two = EEPROM.read(address + 2);
  long one = EEPROM.read(address + 3);

  //Return the recomposed long by using bitshift.
  return ((four << 0) & 0xFF) + ((three << 8) & 0xFFFF) + ((two << 16) & 0xFFFFFF) + ((one << 24) & 0xFFFFFFFF);
}


void clearArray(){
  for(int i=0; i<sizeof(charArray); i++){      // clear array
    charArray[i] = 0;
  }
}


// set system into the sleep state 
// system wakes up when wtchdog is timed out
void system_sleep() {
  cbi(ADCSRA,ADEN);                    // switch Analog to Digitalconverter OFF
  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep mode is set here
  sleep_enable();
  sleep_mode();                        // System sleeps here
  sleep_disable();                     // System continues execution here when watchdog timed out 
  sbi(ADCSRA,ADEN);                    // switch Analog to Digitalconverter ON
}

Thank you!

Franklin

Refencing file "sfr_defs.h" in the Arduino AVR core ..... the first section of the posted code #defines macro functions to set and clear individual bits in a variable treated as a volatile byte datatype. That volatile designation is necessary when dealing with memory mapped processor registers.

The later code uses the previously defined macro functions to set / clear bits in the ADCSRA processor register. You can find the definition of that register in the ATmega328P datasheet available on line.

What chip are you using? The Arduino UNO does not have a "GIMSK" register and the GIMSK register in the ATtiny85 doesn't have a "PCIE1" bit.

Good point. I only looked at the register accessed via the macro functions in question (ADCSRA). The UNO does have one of those.

I am using an ATtiny 84.

Then, per the datasheet, GIMSK is the General Interrupt Mask Register.

Okay I see. Thank you for the help! What is the 'vector' in the ISR () function above. Apologies if these are all silly questions. Still trying to wrap my head around interrupts.

Thanks again!

Basically, this:

ISR(PCINT1_vect) {

Is the syntax for defining an ISR to service the Change Interrupt Request 1 (PCINT1).

You'll need to spend some quality time curled up with the processor datasheet.

1 Like

Setting bit PCIE1 in the GIMSK enables the Pin Change Interrupts 8 through 11 (PB0-PB3).

Setting bit PCINT10 in the PCMSK1 enables Pin Change Interrupt 10 (PB2).

When pin PB2 (physical pin 5 on the 14-pin PDIP/SOIC or physical pin 14 on the 20-pin QFN/MLF) changes, the PCINT1 vector will be called.

1 Like

They are something you need to exhaustively study the details of, until you get it.

It's easy. Hardware detects an interrupt condition and set a flag. If that specific interrupt is enabled and global interrupts are enabled, the hardware disables global interrupts, clears the flag for the pending interrupt, calls the ISR like a function with no arguments and no return value, and then re-enables global interrupts so the next flag is set.

1 Like

In principle. :wink:

1 Like

Thank you all so much! Very helpful and I really appreciate it :slight_smile:

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.