Help me make my interrupts-based code more efficient please!

Hi all,

I'm making a force-deflection device using an arduino UNO, a spring-return rotary encoder for deflection measurements, a load cell for force measurement, and a serial-enabled LCD to display results.

My digital rotary encoder currently is currently using interrupts, and was working quite nicely, HOWEVER, when I added the LCD code to it, it now struggles to "keep up". It seems like pulses are being skipped by the interrupts, and I have played around with the hardware enough to feel confident that it's a software issue.

Can y'all take a look at this code and see where things are interfering with one another? I want to make sure the encoder doesn't skip steps even when it's turned quickly.

//Include necessary libraries
#include "HX711.h" //must install bogde's HX711 library, available at https://github.com/bogde/HX711
#include "math.h" //included in arduino program download
#include "SoftwareSerial.h" //included in arduino program download

#define DOUT  5 //define data out pin
#define CLK  4 //define clock pin
#define txPin 6 //define transmitter pin for serial LCD
#define tare_Pin 9 //pin for manual taring

HX711 scale(DOUT, CLK); //initialize HX711 class called "scale" with pin specifications
SoftwareSerial LCD(0, txPin); //initialize SoftwareSerial class called "LCD" with pin specifications

//Variables
int pull_Position = 0; //the position of the pull cord
int A_SIG = 0; //square wave initial signals
int B_SIG = 0;
float mm_Reading; //variables for distance and scale reading
float scale_Reading;
float last_mm_Reading = 0;
float last_Scale_Reading = 0;
char load[7];
char distance[7];

//Constants
const byte num_Readings = 1; //average number of readings for weighing (leave as 1)
const byte dec_Pts = 1; //display this number of decimal points
const float scale_Calibration_Factor = 3440; //calibration factor, obtained with calibration script here: https://learn.sparkfun.com/tutorials/load-cell-amplifier-hx711-breakout-hookup-guide (saved as v5 calibration)
//******************************************** a higher calibration factor will result in lower readings at a given load and vice versa
const int distance_Calibration_Factor = 1293; //reading of pull_Position at calibration_Distance eg. 100mm
const int calibration_Distance = 100; //distance at which distance_Calibration_Factor pull position reading was taken (mm)
const int LCDdelay = 2; // conservative, 2ms works


//Run once at start of machine
void setup()
{
  attachInterrupt(digitalPinToInterrupt(2), A_RISE, RISING); //attach interrupts to pins 2 and 3 for digital distance encoder
  attachInterrupt(digitalPinToInterrupt(3), B_RISE, RISING);

  LCD.begin(9600); //initialize LCD serial communication

  pinMode(tare_Pin, INPUT); //initialize tare_Pin as an output
  pinMode(tare_Pin, INPUT_PULLUP); //turn on pull up resistor to tie pin signal to high when button isn't pressed
  pinMode(txPin, OUTPUT); //initialize LCD pin

  scale.set_scale(scale_Calibration_Factor); //calibrate scale
  scale.tare();  //reset the scale to 0
  pull_Position = 0; //reset the distance encoder to 0
  clearLCD(); //clear LCD

  backlightOn();
  lcdPosition(0, 3);
  LCD.print("WELCOME TO");
  lcdPosition(1, 2);
  LCD.print("TESTING MODE");
  delay(2000);
  clearLCD();

  /*(while(digitalRead(selection_Button_Pin) == HIGH)
  {
    //MENU SELECTION CODE GOES HERE -- NEED TO USE ROTARY ENCODER WITH INTERNAL BUTTON FOR SELECTION
  }*/

  //print initial dummy values
  lcdPosition(0, 0);
  LCD.print("LOAD:      0.0");
  lcdPosition(1, 0);
  LCD.print("DEFL:      0.0");
}

void loop()
{
  //Get weight and distance readings
  scale_Reading = fabs(scale.get_units(num_Readings));
  mm_Reading = (float)pull_Position / distance_Calibration_Factor * calibration_Distance;

  //Check if tare button is pressed
  if (digitalRead(tare_Pin) == LOW)
  {
    scale.tare(); //reset measurements
    pull_Position = 0;
  }
 
  //LCD printing
  if (fabs(scale_Reading - last_Scale_Reading) >= 0.05)
  {
    lcdPosition(0, 7); //clear load reading
    LCD.print("        ");

    lcdPosition(0, 7); //print new load reading
    LCD.print(dtostrf(scale_Reading, 7, 1, load));
  }

  if (fabs(mm_Reading - last_mm_Reading) >= 0.05)
  {
    lcdPosition(1, 7); //clear distance reading
    LCD.print("        ");

    lcdPosition(1, 7); //print new distance reading
    LCD.print(dtostrf(mm_Reading, 7, 1, distance));
  }
  
  last_Scale_Reading = scale_Reading;
  last_mm_Reading = mm_Reading;
}



//LCD Functions
void lcdPosition(int row, int col)
{
  LCD.write(0xFE); //command flag for setting position
  LCD.write((col + row * 64 + 128)); //set position
  delay(LCDdelay);
}

void clearLCD()
{
  LCD.write(0xFE);   //command flag for setting position
  LCD.write(0x01);   //clear command
  delay(LCDdelay);
}

void backlightOn() //turns on the backlight
{
  LCD.write(0x7C);   //command flag for backlight level
  LCD.write(157);    //set light level.
  delay(LCDdelay);
}

void backlightOff() //turns off the backlight
{
  LCD.write(0x7C);   //command flag for backlight level
  LCD.write(128);     //light level for off.
  delay(LCDdelay);
}
void serCommand() {  //a general function to call the command flag for issuing all other commands
  LCD.write(0xFE);
}



//Interrupt functions for distance encoder
void A_RISE()
{
  detachInterrupt(digitalPinToInterrupt(2));
  A_SIG = 1;

  if (B_SIG == 1)
  {
    pull_Position++;//moving forward
  }
  else
  {
    pull_Position--;//moving reverse
  }

  attachInterrupt(digitalPinToInterrupt(2), A_FALL, FALLING);
}

void A_FALL()
{
  detachInterrupt(digitalPinToInterrupt(2));
  A_SIG = 0;

  if (B_SIG == 0)
  {
    pull_Position++;//moving forward
  }
  else
  {
    pull_Position--;//moving reverse
  }

  attachInterrupt(digitalPinToInterrupt(2), A_RISE, RISING);
}

void B_RISE()
{
  detachInterrupt(digitalPinToInterrupt(3));
  B_SIG = 1;

  if (A_SIG == 0)
  {
    pull_Position++;//moving forward
  }
  else
  {
    pull_Position--;//moving reverse
  }

  attachInterrupt(digitalPinToInterrupt(3), B_FALL, FALLING); \
}

void B_FALL()
{
  detachInterrupt(digitalPinToInterrupt(3));
  B_SIG = 0;

  if (A_SIG == 1)
  {
    pull_Position++;//moving forward
  }
  else
  {
    pull_Position--;//moving reverse
  }

  attachInterrupt(digitalPinToInterrupt(3), B_RISE, RISING);
}

I don't really know what I'm doing with this stuff, so any help will be much appreciated! Thanks,

Leo

Probably has to do with the detachInterrupts/attachInterrupts in your ISRs. That is a bad idea. You should use CHANGE interrupts, and sample the pin to determine whether you got a rising or falling interrupt. You are creating small "windows" between detach and attach where interrupts will not be recognized.

Regards,
Ray L.

SoftwareSerial disables interrupts for the entire duration of each received/sent character, about 1ms. Use AltSoftSerial if you can put the LCD on pins 8 & 9 (best), or NeoSWSerial for any other pins (better). They are both much more reliable and efficient.

Cheers,
/dev

Thanks /dev and Ray Livingston!

What I ended up doing is upgrading to a MEGA so I had enough pins, then using 4-bit LCD communication with a different LCD. Turns out using SoftwareSerial (or any kind of serial communication with the LCD) was what was causing the interrupt problems.

On an aside, for future reference, any variables modified in an ISR should be declared volatile:-

volatile int pull_Position = 0; //the position of the pull cord
volatile int A_SIG = 0;         //square wave initial signals
volatile int B_SIG = 0;