ATTIny 85 - Rotary Encoder

So I've got an odd issue, or one that I don't have a clear answer for.

My code works exactly how I think it should on the bread board:
1)Turn On a set of NeoPixels
2) Adjust color using a rotary encoder.
- This is through the "check color state" function below which I think is where the issue is?
3) Change patterns through pressing the button of the encoder, while still allowing the color to change from the rotary encoder.

The general run of the loop is:

  1. Run Effect - Run one of 12 cases of a switch fuctions
  2. btn. tick - Check for Button Presses to advance case number
  3. Every 10 mSec, check for a state change on the rotarty encoder to adjust hue value
  4. Push the output information to the LEDs
  5. Repeat

When I got it all to a satisfactory level to test on the final product - a light up dog bone - everything worked except step 2, the color adjustment.

I changed the button input to both read one of the two rotary inputs, and then the other, and both times the pattern did change when the rotary encoder was turned, so the board is reading both pins as far as I can tell.

If anyone has any ideas/input I'd greatly appreciate it as this is my first larger project and I've hit a wall troubleshooting farther.

Code is below, as well as a schematic I drew of the bread board, and then the schematic my friend made for the final product.

This is also a video showing intended function vs what is happening of them side by side. The bread board one goes through red, while the other one doesn't change color.

//Change Max Brightness Here
int Brightness = 30;
//Total Case Total Here
int caseTotal = 13;

//Hue 0-255 at Turn on
//0-Red, 32-Orange, 64-Yellow, 96-Green, 128-Cyan, 160-Blue, 192-Purple, 224-Magenta,
int StartColor = 160;

#include <FastLED.h>
#include <OneButton.h>
//encoder Input
#define SPIN_CCW 0
#define SPIN_CW 1

// LED Outputs
#define NUM_LEDS  32
#define LED_PIN   4
int CHASE_LEDS = 26; // number of LEDs around edges of dog bone

//Button Input
#define BTN_PIN 2
int patternCounter = 0;
OneButton btn = OneButton(BTN_PIN, true, true);
int randomCounter = 0;

//Color Variables
int currentStateCLK;
int lastSpinState;
uint8_t hue = StartColor; //Hue
uint8_t sat = 0; //Saturation
uint8_t val = 0; // Value/Brightness to maximum brightness defined above
uint8_t r = 0;  // rainbow chase loop
CRGB leds[NUM_LEDS];
uint8_t hueRainbow = 0;

// fillPixel Variables
int fillPixelLoopCounter = 0;
int fillPixelCounter = 0;
int fillPixelBlackCoutner = NUM_LEDS;
int pixelVal = 255;

////DELETE THIS AT FINAL
//#define ledCW 8
//#define ledCCW 9
//// DELETE THIS AT FINAL ^

void setup() {
  FastLED.addLeds<WS2812B, LED_PIN, GRB>(leds, NUM_LEDS);
  FastLED.setBrightness(Brightness); //Sets Maximum Brightness - any in effect brightness @ 255 maxes to this
    // Set encoder pins as inputs
    pinMode (SPIN_CCW, INPUT);
    pinMode (SPIN_CW, INPUT);
 
  // Read the initial state of SPIN_CCW
  // Assign to lastSpinState variable
  lastSpinState = digitalRead(SPIN_CCW);

  btn.attachClick(nextPattern);


}

void loop() {

  RunEffect();

  btn.tick();
 
 EVERY_N_MILLISECONDS(10) {
    checkColorState();
    }

  FastLED.show();

}


void checkColorState() {
 // EVERY_N_MILLISECONDS(20) { // Read the current state of SPIN_CCW
    currentStateCLK = digitalRead(SPIN_CCW);

    // If the previous and the current state of the SPIN_CCW are different then a pulse has occured
    if (currentStateCLK != lastSpinState) {

      // If the SPIN_CW state is different than the SPIN_CCW state then
      // the encoder is rotating counterclockwise
      if (digitalRead(SPIN_CW) != currentStateCLK) {
        hue = hue - 2;
        //       digitalWrite(ledCW, LOW);
        //      digitalWrite(ledCCW, HIGH);

      } else {
        // Encoder is rotating clockwise
        hue = hue + 2;

        //       digitalWrite(ledCW, HIGH);
        //       digitalWrite(ledCCW, LOW);

      }
      //   delay (20);
   //   Serial.println(hue);
 //   }
  }
  // Update lastSpinState with the current state
  lastSpinState = currentStateCLK;

}


void nextPattern() {
  patternCounter = (patternCounter + 1);
  if (patternCounter > caseTotal) {
    patternCounter = 0;
  }
}

//case 0
   void solidFill(){ 
      for (int i = 0; i < NUM_LEDS; i++) {
        leds[i] = CHSV(hue, 255, 255);
      }
    }


//case 1
void  Horizontal32BPM() {
  //Side Wipe back and Forth 128BPM Quater Speed
  uint8_t sinBeat4 = beatsin8(32, 0, CHASE_LEDS - 1, 0, 0);
  uint8_t sinBeat5 = beatsin8(32, 0, CHASE_LEDS - 1, 0, 122);
  uint8_t sinBeat6 = beatsin8(64, 0, 4, 0, 50);
  leds[sinBeat4] = CHSV(hue, 255, 255);
  leds[sinBeat5] = CHSV(hue, 255, 255);
  leds[26 + sinBeat6] = CHSV(hue, 255, 255);
  EVERY_N_MILLISECONDS(40);
  fadeToBlackBy(leds, NUM_LEDS, 02);

}

//case 2
void  Horizontal64BPM() {
  //Side Wipe back and Forth 128BPM Quater Speed
  uint8_t sinBeat4 = beatsin8(64, 0, CHASE_LEDS - 1, 0, 0);
  uint8_t sinBeat5 = beatsin8(64, 0, CHASE_LEDS - 1, 0, 122);
  uint8_t sinBeat6 = beatsin8(128, 0, 4, 0, 50);
  leds[sinBeat4] = CHSV(hue, 255, 255);
  leds[sinBeat5] = CHSV(hue, 255, 255);
  leds[26 + sinBeat6] = CHSV(hue, 255, 255);
  EVERY_N_MILLISECONDS(40);
  fadeToBlackBy(leds, NUM_LEDS, 05);

}


//case 3
void  waterfall() {

//  Serial.println("Waterfall start");
  uint8_t valBeat = beatsin8(64, 0, 255, 0, 0);
  uint8_t valBeat2 = beatsin8(64, 0, 255, 0, 225);
  uint8_t valBeat3 = beatsin8(64, 0, 255, 0, 180);
  uint8_t valBeat4 = beatsin8(64, 0, 255, 0, 144);
  uint8_t valBeat5 = beatsin8(64, 0, 255, 0, 108);
  uint8_t valBeat6 = beatsin8(64, 0, 255, 0, 72);
  uint8_t valBeat7 = beatsin8(64, 0, 255, 0, 36);

  // First Row
  //     Serial.println("Waterfall - 1st row");
  for (int i = 2; i < 4; i++) { //i can only be a single integer, so previous line selects which pixels
    leds[i] = CHSV(hue, 255, valBeat);
  }
  for (int i = 9; i < 11; i++) { //Top right of bone since they're non-contiguous numbers
    leds[i] = CHSV(hue, 255, valBeat);
  }

  //Second Row
  //     Serial.println("Waterfall - 2nd row");
  for (int i = 4; i < 9; i++)
    leds[i] = CHSV(hue, 255, valBeat2);

  //Third Row
  //    Serial.println("Waterfall - 3rd row");
  for (int i = 0; i < 2; i++) {
    leds[i] = CHSV(hue, 255, valBeat3);
  }
  for (int i = 11; i < 13; i++) {
    leds[i] = CHSV(hue, 255, valBeat3);
  }

  //Fourth Row
  //  Serial.println("Waterfall - 4th row");
  for (int i = 26; i < 31; i++)
    leds[i] = CHSV(hue, 255, valBeat4);

  //Fifth Row
  //  Serial.println("Waterfall - 5th row");
  for (int i = 24; i < 26; i++) {
    leds[i] = CHSV(hue, 255, valBeat5);
  }
  for (int i = 13; i < 15; i++) {
    leds[i] = CHSV(hue, 255, valBeat5);
  }

  //Sixth Row
  //   Serial.println("Waterfall - 6th row");
  //    }
  for (int i = 17; i < 22; i++) {
    leds[i] = CHSV(hue, 255, valBeat6);
  }

  //Seventh Row
  //   Serial.println("Waterfall - 7th row");
  for (int i = 15; i < 17; i++) {
    leds[i] = CHSV(hue, 255, valBeat7);
  }
  for (int i = 22; i < 24; i++) { //Top right of bone since they're non-contiguous numbers
    leds[i] = CHSV(hue, 255, valBeat7);
  }
  btn.tick();

}


//caase 4
void  flashBone() {
  int flashVal = beat8(48, 0);
  for (int i = 0; i < NUM_LEDS; i++) {
    leds[i] = CHSV(hue, 255, flashVal);

  }

}


//case 5 - To be Written


//case 6
void  sawtoothChase() {
  //Perimeter Chase
  int saw1 = beat8(64, 0);
  int saw2 = beat8(64, 500);
  uint8_t sawi = map(saw1, 0, 255, 0, CHASE_LEDS - 1);
  uint8_t sawi2  = map(saw2, 0, 255, 0, CHASE_LEDS - 1);
  leds[sawi] = CHSV(hue, 255, 255);
  leds[sawi2] = CHSV(hue, 255, 255);
  EVERY_N_MILLISECONDS(40);
  fadeToBlackBy(leds, NUM_LEDS, 05);

}

//case 7 - Work in progress and not looking for help on this currently
void  fillPixels() { 
//  int pixelVal;
  int x;
  if (fillPixelLoopCounter = NUM_LEDS) { // If total number of loops through  is equal to total num of LEDs, then change the fill Pixel Counter
    x = 1;
    fillPixelBlackCoutner = 32;
    if (pixelVal = 255) {
      pixelVal = 0;
      hue = 0;
    }
    else {
      pixelVal = 255;
    }

  }

  int saw = beat8(32, 0); // What pixel should be on
  uint8_t sawi = map(saw, 0, 255, 0, NUM_LEDS - x); // used with x and



  leds[sawi] = CHSV(hue, 255, pixelVal);  // Turns on LEDs

  if (sawi = fillPixelBlackCoutner) { /// Moves the end of the t
    x++;
    fillPixelBlackCoutner--;
  }

  if (pixelVal = 255) { //if Pixel are turning on, then turn on
    fadeToBlackBy(leds, fillPixelBlackCoutner, 04);
  }

  fillPixelLoopCounter++; // Add 1 to loop coutner

}


//Case 8
void randomPixel() {


  EVERY_N_MILLISECONDS(30) {
    int randomi = random8(0, 30);
    leds [randomi] = CHSV(hue, 255, 255);
    randomCounter++;
    fadeToBlackBy(leds, NUM_LEDS, 10);
  }


}

//Case 9
void randomPixelFaster() {


  EVERY_N_MILLISECONDS(15) {
    int randomi = random8(0, 30);
    leds [randomi] = CHSV(hue, 255, 255);
    randomCounter++;
    fadeToBlackBy(leds, NUM_LEDS, 15);
  }


}

//case 10
void rainbowChase() {

  uint8_t rainbowHue = beat8(32, 0);   //(beat8( BPM, time offeset mSeconds)
  for (int i = 26; i < NUM_LEDS; i++) {
    leds[i] = CHSV(rainbowHue, 255, 255);
  }


  EVERY_N_MILLISECONDS(5) {
    r++;
    fill_rainbow(leds, CHASE_LEDS, r, 255 / CHASE_LEDS);
  }
}

//case 11
void rainbowFillChase() {

  EVERY_N_MILLISECONDS(14) {
    r++;
    fill_rainbow(leds, NUM_LEDS, r, 0);
  }
}

//case 12
void  rainbowWaterfall() {

  //  Serial.println("Waterfall start");
  uint8_t  hue2 = beatsin8(32, 0, 255, 0, 0);

  uint8_t valBeat = beatsin8(64, 0, 255, 0, 0);
  uint8_t valBeat2 = beatsin8(64, 0, 255, 0, 225);
  uint8_t valBeat3 = beatsin8(64, 0, 255, 0, 180);
  uint8_t valBeat4 = beatsin8(64, 0, 255, 0, 144);
  uint8_t valBeat5 = beatsin8(64, 0, 255, 0, 108);
  uint8_t valBeat6 = beatsin8(64, 0, 255, 0, 72);
  uint8_t valBeat7 = beatsin8(64, 0, 255, 0, 36);


  // First Row
  //     Serial.println("Waterfall - 1st row");
  for (int i = 2; i < 4; i++) { //i can only be a single integer, so previous line selects which pixels
    leds[i] = CHSV(hue2, 255, valBeat);
  }
  for (int i = 9; i < 11; i++) { //Top right of bone since they're non-contiguous numbers
    leds[i] = CHSV(hue2, 255, valBeat);
  }

  //Second Row
  //     Serial.println("Waterfall - 2nd row");
  for (int i = 4; i < 9; i++)
    leds[i] = CHSV(hue2, 255, valBeat2);

  //Third Row
  //    Serial.println("Waterfall - 3rd row");
  for (int i = 0; i < 2; i++) {
    leds[i] = CHSV(hue2, 255, valBeat3);
  }
  for (int i = 11; i < 13; i++) {
    leds[i] = CHSV(hue2, 255, valBeat3);
  }

  //Fourth Row
  //  Serial.println("Waterfall - 4th row");
  for (int i = 26; i < 31; i++)
    leds[i] = CHSV(hue2, 255, valBeat4);

  //Fifth Row
  //  Serial.println("Waterfall - 5th row");
  for (int i = 24; i < 26; i++) {
    leds[i] = CHSV(hue2, 255, valBeat5);
  }
  for (int i = 13; i < 15; i++) {
    leds[i] = CHSV(hue2, 255, valBeat5);
  }

  //Sixth Row
  //   Serial.println("Waterfall - 6th row");
  //    }
  for (int i = 17; i < 22; i++) {
    leds[i] = CHSV(hue2, 255, valBeat6);
  }

  //Seventh Row
  //   Serial.println("Waterfall - 7th row");
  for (int i = 15; i < 17; i++) {
    leds[i] = CHSV(hue2, 255, valBeat7);
  }
  for (int i = 22; i < 24; i++) { //Top right of bone since they're non-contiguous numbers
    leds[i] = CHSV(hue2, 255, valBeat7);
  }
  btn.tick();

}

void RunEffect() {
  // get current button state
  switch (patternCounter) {

    case 0:

    solidFill();
    
      break;

    case 1:
      Horizontal32BPM();
      break;


    case 2:

      Horizontal64BPM();
      break;

    case 3:
      waterfall();
      break;


    case 4:

      flashBone();
      break;

    case 5:
      // blank - to be written
      patternCounter++;
      break;

    case 6:
      sawtoothChase();
      break;

    case 7:

   fillPixels();

      break;

    case 8:

      randomPixel();

      break;

    case 9:

      randomPixelFaster();

      break;


    case 10:

      rainbowChase();

      break;

    case 11:

      rainbowFillChase();

      break;

    case 12:
      rainbowWaterfall();

      break;
  }
}

Sorry, how is this topic related to ATTiny85?

  1. Is your posted code for UNO or ATTiny?
  2. Where does one find the libraries OneButton and FastLED?
  3. Do you suspect that your encoder is 'hopping' intermediate steps? Your method for reading the encoder is unique and may be flawed. If it's a 2ø encoder, I normally read both pins and compares that (2 bit) read to the previous one, and, if it's a legal/logical transition, go up or down. If it's not a legal/logical transition, do nothing.
  1. Same code is on both the Uno and the ATTiny
  2. Both are libraries I found online and have had no issues with.
  3. I don't suspect anything. At this point I have just a feeling that something is wrong with how reading the pins and using the upward or downward progression.
    Do you have have an example of how to compare the read of both pins to the previous one? I think I understand this in theory but the actual code and syntax is not my strong suit yet.

Sorry, but I'm new here, and hope this code is posted properly. I also hope I've not missed something in copying it!

byte Enc1 = 0;  // encoder output: 0 = do nothing, 1 = go up, 2 = go down (or vice-versa)
const byte encDir[16] = { 0, 2, 1, 0,
                          1, 0, 0, 2,
                          2, 0, 0, 1,
                          0, 1, 2, 0 };
// the 0s are no activity or illegal transitions; 1 & 2 are up or down
byte encPhase = 0;
const byte EncP1 = 1;  // set these to the actual 2 pins you use for the encoder input
const byte EncP2 = 2;

void ReadEncoder() {
  encPhase = encPhase << 2;  // shift the last reading over 2 bits
  encPhase = encPhase & 15;  // get rid of all the really old junk; this leaves 2 bits of old data and 2 zeros: 0000 oo00

  if (digitalRead(EncP2) == HIGH) {
    encPhase += 1;  // add in the new bits
  }

  if (digitalRead(EncP1) == HIGH) {
    encPhase += 2;  // encPhase is now 0000 .. 1111 ( ie 0000oonn)
  }
  Enc1 = encDir[encPhase];
}
// you should call ReadEncoder() 2X in setup to pre-load encPhase with something that doesn't result in a spurious data change out of reset, if you don't want one.
// if Enc1 == 0, don't change your parameter
// if Enc1 != 0, change your parameter, and then set Enc1 to 0
// This is more reliably done with the encoder wires triggering interrupts
// if ReadEncoder() is called every 10 ms or so, it doesn't miss much
// if it does miss a transition, it still works

Here is basic demo code (tested and working) on how to read an encoder. Change pins to suit. No library necessary.

// change pins to suit yout setup
const byte aPin = 3;
const byte bPin = 4;
const byte swPin = 5;
const byte ledPin = 13;

long encoderCnt = 0;

void setup()
{
   Serial.begin(115200);
   Serial.println("encoder test");
   pinMode(aPin, INPUT_PULLUP);
   pinMode(bPin, INPUT_PULLUP);
   pinMode(swPin, INPUT_PULLUP);
   pinMode(ledPin, OUTPUT);
}

void loop()
{

   if (readEncoder()) // count changed?
   {
      Serial.print("Encoder count =  ");
      Serial.println(encoderCnt);
   }
   readSw();
}

void readSw()
{
   static bool lastSwState = HIGH;
   bool swState = digitalRead(swPin);
   if (swState != lastSwState)
   {
      if (swState == LOW)
      {
         digitalWrite(ledPin, !digitalRead(ledPin));
      }
   }
   lastSwState = swState;
}


boolean readEncoder()
{
   boolean newEncode = false;
   static boolean last_aState = 0;
   boolean aState = digitalRead(aPin);
   boolean bState = digitalRead(bPin);
   if (aState != last_aState)
   {
      if (aState != bState)
      {
         encoderCnt++;
         //Serial.println("clockwise");
      }
      else
      {
         encoderCnt--;
         //Serial.println("counter clockwise");
      }
      last_aState = aState;
      newEncode = true;
   }
   return newEncode;
}

This code works for a rotary encoder like the KY-040 with commons wired to ground, Vcc not connected and the clk, dt and sw pins wired to inputs set to pinMode INPUT_PULLUP. The encoder is hardware debounced with 0.1uF caps from each clk, dt and sw to ground.