0

I have been given to understand the ATTiny88 is very similar to the Arduino Uno / ATMega328 MCU. There does not seem to be a tag for the ATTiny88, so that is why I chose the tags I did. I do not have a high enough reputation to create a tag, yet. Thanks to the generous help of timemage, Edgar Bonet, and others, my MCU board is now responding, albeit very erratically, to pulses on pins 12 and 13, designated Phase_B and Phase_A, respectively, in the code below. I have not yet entirely ruled out a hardware issue (I will do so), but a software issue is still likely. I don't quite know what. The MCU is registering pulses, but it very often misses large numbers of turns in either direction. What's more, it frequently reports the wrong direction, strongly favoring a CW rotation, but sometimes reporting a CCW rotation when the knob is turned CW. It is definitely not an overflow issue, as the anomalous effects are seen in abundance when in the mid range of the variables.

#include <Arduino.h>
#include <TM1637Display.h>
#include "PinChangeInterrupt.h"

volatile uint8_t counter = 0;
volatile bool Update = false;
volatile int currentStatePhase_B;
volatile int lastStatePhase_B;
volatile int pulses = 0;

// Shutdown Output
#define SW 11

// Rotary Encoder Inputs
#define Phase_A 13
#define Phase_B 12
// TM1637 I/O ports
#define DIO 3
#define CLK 4
TM1637Display display(CLK, DIO);

void setup() {

  // Set encoder pins as inputs
  pinMode(Phase_A,INPUT);
  pinMode(Phase_B,INPUT);
  pinMode(SW, OUTPUT);

    // Read the initial state of Phase_B
    lastStatePhase_B = digitalRead(Phase_B);
    
    // Call updateEncoder() when any high/low changed seen
    // on interrupt 0 (pin 2), or interrupt 1 (pin 3)
    attachPCINT(digitalPinToPCINT(Phase_A), updateEncoder, RISING);
    attachPCINT(digitalPinToPCINT(Phase_B), updateEncoder, RISING);

  display.setBrightness(0x0f);
  display.clear();
  display.showNumberDec(0, false);
}

void loop() {
    if (Update){
        // Show decimal numbers with/without leading zeros
    display.showNumberDec(pulses, false);
    delay(1000);
    display.showNumberDec(counter, false);
        Update = false;
    }
}

void updateEncoder(){
    // Read the current state of Phase_B
    currentStatePhase_B = digitalRead(Phase_B);

    // If last and current state of Phase_B are different, then pulse occurred
    // React to only 1 state change to avoid double count
    if (currentStatePhase_B != lastStatePhase_B  && currentStatePhase_B == 1){
        // If the Phase_A state is different than the Phase_B state then
        // the encoder is rotating CCW so decrement
        if (digitalRead(Phase_A) != currentStatePhase_B) {
            counter --;
        } else {
            // Encoder is rotating CW so increment
            counter ++;
        }
    }
    if (counter > 1023){
      counter = 1023;
    }
    if (counter < 0){
      counter = 0;
    }  
    pulses ++;
    // Remember last Phase_B state
    lastStatePhase_B = currentStatePhase_B;
    Update = true;
}

12
  • try this ... use interrupt on only phase_A ... when interrupt fires, increment or decrement counter depending on the state of phase_B ... also set Update flag
    – jsotola
    Feb 26, 2023 at 22:36
  • For answers to most of these questions, see arduino.stackexchange.com/questions/92354/… Feb 26, 2023 at 23:30
  • @jsotola It's an EC11 on a board I created myself. I don't know what sort of encoder you might be imagining, but this is as basic as it gets. What do you suggest for debouncing? Note (I have tried a number of things, including polling rather than employing interrupts. Feb 26, 2023 at 23:32
  • I've seen an encoder that clicks repeatedly on one contact when turned in one direction, and clicks repeatedly on the second contact when turned in the other direction ... I think that EC11E0B is like that
    – jsotola
    Feb 26, 2023 at 23:49
  • OK, this is definitely not that. Actually, it would be great if it were. Feb 27, 2023 at 0:15

1 Answer 1

0

OK, I have an answer. The MCU is still missing a lot of pulses, but I am fairly certain this is a hardware issue, or at least mostly so. I found some code by Ralph S. Bacon and Marko Pinteric. The YouTube video can be found here

As should always be the case, the ISR is brief. Here it is a single line that merely sets a Boolean variable true. In the loop section, the code repeatedly checks to see if the interrupt has occurred. The checkRotaryEncoder function then verifies the pin values to make sure the encoder is in a valid state (not bouncing). If so, then the counter value is updated with the result.

#include <Arduino.h>
#include <TM1637Display.h>
#include "PinChangeInterrupt.h"

uint8_t lrmem = 3;
int lrsum = 0;
int num = 0;
unsigned int counter = 0;

// Shutdown Output
#define SW 11
// Rotary Encoder Inputs
#define Phase_A 12
#define Phase_B 13
// TM1637 Display I/O
#define DIO 3
#define CLK 4
TM1637Display display(CLK, DIO);

// A turn counter for the rotary encoder (negative = anti-clockwise)
int rotationCounter = 200;
// Flag from interrupt routine (moved=true)
volatile bool rotaryEncoder = false;

// Rotary encoder has moved (interrupt tells us) but what happened?
// See https://www.pinteric.com/rotary.html
int8_t checkRotaryEncoder()
{
    // Reset the flag that brought us here (from ISR)
    rotaryEncoder = false;

    static uint8_t lrmem = 3;
    static int lrsum = 0;
    static int8_t TRANS[] = {0, -1, 1, 14, 1, 0, 14, -1, -1, 14, 0, 1, 14, 1, -1, 0};

    // Read BOTH pin states to deterimine validity of rotation (ie not just switch bounce)
    int8_t l = digitalRead(Phase_A);
    int8_t r = digitalRead(Phase_B);

    // Move previous value 2 bits to the left and add in our new values
    lrmem = ((lrmem & 0x03) << 2) + 2 * l + r;
    // Convert the bit pattern to a movement indicator (14 = impossible, ie switch bounce)
    lrsum += TRANS[lrmem];
    /* encoder not in the neutral (detent) state */
    if (lrsum % 4 != 0)
    {
        return 0;
    }
    /* encoder in the neutral state - clockwise rotation*/
    if (lrsum == 4)
    {
        lrsum = 0;
        return 1;
    }
    /* encoder in the neutral state - anti-clockwise rotation*/
    if (lrsum == -4)
    {
        lrsum = 0;
        return -1;
    }
    // An impossible rotation has been detected - ignore the movement
    lrsum = 0;
    return 0;
}

void setup() {
  // Set encoder pins as inputs
  pinMode(Phase_A,INPUT);
  pinMode(Phase_B,INPUT);
  // Set Power Switch as output
  pinMode(SW, OUTPUT);
  // Initialize display
  display.setBrightness(0x0f);
  display.clear();
  display.showNumberDec(0, false);
  // Set up interrupt pins and vector
  attachPCINT(digitalPinToPCINT(Phase_A), rotary, CHANGE);
  attachPCINT(digitalPinToPCINT(Phase_B), rotary, CHANGE);
}

void loop()
{
  // Has rotary encoder moved?
  if (rotaryEncoder) {
    // Get the movement (if valid)
    int8_t rotationValue = checkRotaryEncoder();
    // If valid movement, do something
    if (rotationValue != 0) {
      counter += rotationValue;
      if (counter > 1023){
        counter = 1023;
      }
      else if (counter < 0){
        counter = 0;
      }
      display.showNumberDec(counter, false);
    }
  }
}

// Interrupt routine just sets a flag when rotation is detected
void rotary()
{
    rotaryEncoder = true;
}

Any comments are welcom. In addition, Ralph Bacon used the IRAM_ATTR directive to create the rotary() ISR. My understanding of this directive is it is not necessary in most cases. On the other hand, presumably the code will execute faster if it is pushed into flash RAM. I could not make this work. The compiler complains, "expected initializer before 'rotary'" Is this by chance because the ATTiny88 does not have flash RAM? If not, then what?

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.