Encoder switch and increments

Pretty new to this.

I am building a timer for exposing photo resist to UV. Using an MCU and display seems to be the obvious solution. I have set about doing this initially using an Arduino Uno, rotary encoder and OLED display.

One issue remains that I cannot resolve.

The encoder works 100% when rotating it.

The pin to turn on/off an external FET is working 100%

The encoder switch, which I am using to start the exposure, almost works. The problem is that when the switch is pressed the ISR for the encoder is triggered for every second indent. So 50% of the indents work fine and 50% result in a reduction of the time (and state of the exposure desired) by 1s. My assumption is that pressing the momentary switch activates the rotary encoder for half the indents.

To work around this I have tried a number of alternatives but suspect that physically the encoder ISR is triggered before it can be disabled i.e. before the switch press ISR is triggered (which simply disables the encoder ISR).

Below is the code. Is there any way to resolve this?

/*
 * Code for an exposure timer. It provides for:
 * 1) A rotary encoder is used to set the time in steps of 1s
 * 2) The time is displayed on an oled screen
 * 3) A minimum of 0s and a maximum of 600s are permitted
 * 4) The lamp is switched via a relay, which is driven using an external mosfet
 * 5) Once the encoder switch is pressed, to start the exposure, an isr is used and prevents polling 
 * of the encoder until the exposure completes.
 *
 * Tutorial page: https://arduinogetstarted.com/tutorials/arduino-rotary-encoder - rotary encoder code used (and modified)
 */


//OLED libraries to include
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

//Define pins and directions for the rotary encoder. Note that only pins 2 and 3 are external interrupt pins
#define CLK_PIN 3
#define DT_PIN 4
#define SW_PIN 2 

#define DIRECTION_CW 0   // encoder clockwise direction
#define DIRECTION_CCW 1  // encoder counter-clockwise direction

// Define a pin to drive the external FET
#define FET_PIN 7

// Define the screen size
#define SCREEN_WIDTH 128  //OLED width in pixels
#define SCREEN_HEIGHT 32  //OLED display height in pixels

// Declare an OLED (SSD1306) object connected to I2C
Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

// Set encoder variables
volatile int counter = 60;               //Default exposure time is 60s
volatile int direction = DIRECTION_CW;
volatile unsigned long last_time;       // for debouncing, however, we are using hardware debounce so may disable
int prev_counter;
int count_down;                         //Used for timing the exposure

void setup() {
  Serial.begin(9600);

  // configure encoder and switch pins as inputs
  pinMode(CLK_PIN, INPUT);
  pinMode(DT_PIN, INPUT);
  pinMode(SW_PIN, INPUT_PULLUP);
  pinMode(FET_PIN, OUTPUT);
  digitalWrite(FET_PIN, LOW);         //Immediately turn the pin off. This means that an N channel FET will be used

  // Initialise the OLED display with address 0x3C 
  if (!oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)){
    Serial.println(F("SSD1306 allocation failed"));
    while(true);
  }

  delay(1000);              // wait for the oled to initialise
  oled.setTextColor(WHITE); // text colour
  oled.setTextSize(3);      // text size

  // use an interrupt for CLK pin
  // call ISR_encoderChange() when CLK pin changes from LOW to HIGH
  attachInterrupt(digitalPinToInterrupt(CLK_PIN), ISR_encoderChange, RISING);

  // use an interrupt to disable ISR_encoderChange. Hopefully a higher priority
  // whcih is why the switch is attached to pin 2 and the CLK to pin 3
  attachInterrupt(digitalPinToInterrupt(SW_PIN), ISR_disableISR, FALLING);

}

void loop() {

  if(prev_counter != counter){

  //Print top the serial terminal
    Serial.print("DIRECTION: ");
    if (direction == DIRECTION_CW)
      Serial.print("Clockwise");
    else
      Serial.print("Counter-clockwise");

    Serial.print(" | COUNTER: ");
    Serial.println(counter);

    prev_counter = counter;

    //Display counter on the OLED
    oled.clearDisplay();    // clear the display
    oled.setCursor(20, 10); // display position
    oled.print(counter);    // text to print
    oled.print("s");
    oled.display();         // show on OLED

  }
  
  //Check if the button is pressed

  if(digitalRead(SW_PIN) == 0){
    //reset_count = counter;
    count_down = prev_counter;
    prev_counter = 0;   //change the prev_counter to force the if loop above to run on completion of the exposure
    digitalWrite(FET_PIN, HIGH);       //pull the gate high for an N channel FET to turn on
    while(count_down >> 0){
      Serial.print("Time remaining ");
      Serial.println(count_down);

      //Display counter on the OLED
      oled.clearDisplay();      // clear the display
      oled.setCursor(20, 10);   // display position
      oled.print(count_down);   // text to print
      oled.print("s");
      oled.display();           // show on OLED
      
      count_down--;
      delay(978);               //Use a delay of slightly less than 1s due to execution time
    }
  digitalWrite(FET_PIN, LOW);        //turn off an N channel FET
  attachInterrupt(digitalPinToInterrupt(CLK_PIN), ISR_encoderChange, RISING);
  //counter = reset_count;
  }
  //interrupts();
}

void ISR_encoderChange(){
  /* Disable software bounce as we are using hardware debounce 
  if((millis() - last_time) < 100)    //debounce time is set to 50ms
    return; */
  
  if(digitalRead(DT_PIN) == HIGH){
    //encoder is rotating in a counter clockwise direction
    if (counter > 0){
      counter--;
      direction = DIRECTION_CCW;
    }
  } 
  else {
    //the encoder is rotating in a clockwise direction
    if (counter < 600){
      counter++;
      direction = DIRECTION_CW;
    }
  }
  //last_time = millis();
}
 void ISR_disableISR(){
  detachInterrupt(digitalPinToInterrupt(CLK_PIN)); // disable the encoder ISR
 }

You've got a couple of problems with your ISR and the one will fix the other.

Is more than one byte. So when you use it:

It needs to be protected by a critical section. Not only is there the problem that it might change values between these lines, it might change values during one of those lines and you get the high byte from one value and the low byte from another.

This also means that you aren't in control of when the encoder count gets added to the run time. And that's causing your other problem.

You need to introduce a separate variable for the count that isn't messed with in the ISR. In loop, read the counter from the ISR and save a copy and work with that copy. Add it to the run time or whatever you want to do with it.

void loop() {
  noInterrupts();
  int copy = counter;
  counter = 0;
  interrupts();

  if(prev_counter != copy){

  //Print top the serial terminal
    Serial.print("DIRECTION: ");
    if (direction == DIRECTION_CW)
      Serial.print("Clockwise");
    else
      Serial.print("Counter-clockwise");

    Serial.print(" | COUNTER: ");
    Serial.println(copy);

    prev_counter = copy;

Do the same with direction and make a copy at the top of loop and use that to do your work.

Now that you have only one place in the code where you are applying this counter to your actual run time, you can control whether or not it is going to update anything. Instead of reading the switch in an interrupt, read it in loop at that same place. If the switch is pressed, then you can just ignore the count from the encoder interrupt. If not, then go ahead and apply it to the run time.

This looks wrong...

Working 100%. One more question :grinning:

I am using an ISR when the button is pressed to disable the encoder ISR (not to time the exposure). This means that during the exposure the counter value does not change, even if the encoder is rotated. This appears to work well. Is there a reason not to do it this way?

As they say if it works don't fix it.

In general it's easier to deal with a volatile variable from an ISR in one place. When the exposure is happening it shouldn't matter if the encoder ticks or if its internal counter changes. That should be decoupled from the actual exposure time. The encoder should just be telling you that it moved and then your code decides what to do or not do about that.

It's a separation of responsibility thing. It makes code easier to write and maintain. But like I said, save that for later because right now it's doing what you want.

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