Rotary Encoder with ATtiny85 and I2C LCD1602

Go To Last Post
28 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm strugling more that two days about my code, it seems that I can't figure out what is the issue.

 

I have the hardware properly configured and hooked up, but the code always output 4 time incrementation and vice versa.

 

So, instead of normal increasing like 0, 1, 2, 3, etc.. it shows me 0, 2, 4, 8, 12 and sometimes gives me some random number output.

 

I'm using Rotary Encoder KY-040 and MJKDZ (got same output), flashing ATtiny85 (8MHz internal clock) with Arduino IDE v1.8.2 as "Arduino as ISP".

 

#include "avr/interrupt.h"
#include "Wire.h"
#include "LiquidCrystal_I2C_Tiny.h"

LiquidCrystal_I2C lcd(0x26, 16, 2); //  LCD 1602

/*
  ATTiny 85 rotary encoder example

  pin  port   usage
  2    PB3    Channel B
  3    PB4    Channel A
  6    PB1    Button
*/

#define BUTTON 1
#define wheelA 3
#define wheelB 4

bool previousB = false;
volatile int portReading = 0;
volatile int value = 0;
volatile bool update = true;

void clearMenu(int lcdLine = -1) {
  if (lcdLine == -1) {
    lcd.clear();
  } else {
    lcd.setCursor(0, lcdLine);
    lcd.print(F("               "));
  }
}

void setup()
{
  pinMode(BUTTON, INPUT);
  pinMode(wheelA, INPUT);
  pinMode(wheelB, INPUT);

  lcd.begin();
  lcd.clear();
  lcd.backlight();
  delay(10);
  lcd.setCursor(0, 0);
  lcd.print(F("Rotary Test"));
  delay(500);

  // Setting Interrupt Registers
  GIMSK = 0b00100000;    // turns on pin change interrupts
  PCMSK = 0b00011000;    // turn on interrupt on pins PB3 and PB4
  sei();                 // enables interrupts (not necessary if it's not disabled)
}

void loop()
{
  if ((!digitalRead(BUTTON))) {
    value = 0;
    lcd.setCursor(14, 1);
    lcd.print(F("V"));
    update = true;
    while (!digitalRead(BUTTON))
      delay(10);
  }

  if (update) {
    update = false;

    clearMenu(1);
    lcd.setCursor(1, 1);
    lcd.print(value);
  }
}

ISR(PCINT0_vect)
{
  portReading = PINB & 24;

  if ( (portReading & (1 << wheelA)) == 0)
  {
    if (previousB)
      value++;
    else
      value--;
  } else {
    if (previousB)
      value--;
    else
      value++;
  }

  previousB = (portReading & (1 << wheelB)) == 0;

  //value = min(9, max(0, value));

  update = true;
}

 

Last Edited: Thu. Jul 19, 2018 - 11:57 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Excuse me, but do rotary encoder bounce? (might explain spurious increments)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Mechanical rotary encoders bounce, so your getting more then one interrupt per click!  The simple solution is to add external RC filter to your A/B lines to reduce the noise.

try using a 10k and 100nf, with the resistor in series and the cap from signal line to gnd at the pin.

 

Jim

 

Click Link: Get Free Stock: Retire early!

share.robinhood.com/jamesc3274

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think, it already has built-in, or?

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Can you not get an actual schematic (aka circuit diagram) for it?

 

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think it's same as this one (5x 10k resistor and 2x 100nF ceramic capacitor):

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Have you reviewed this website?  It was near the top of the list from a Google search on "KY-040 Arduino". http://henrysbench.capnfatz.com/...

 

When you are using Arduino and chose to refer to the internal registers of CPU in the .ino file, these references should be well documented so anyone who is reviewing your code won't have to go digging through the hundreds of pages of PDF ATmega chip datasheet file in order to understand exactly what you are doing when you directly diddle the peripheral control registers.  For example, what exactly happens here:??

   GIMSK = 0b00100000;

   PCMSK = 0b00011000;

 

Never use decimal numbers when doing binary bit masking, always binary or hex.  For example: portreading = PINB & 24;

Are you reading the results of two bits?  0x00011000  PINB4 and PINB3?  This interrupt code needs refocus and review.

 

The rotary encoder has one of its two reference pins set to an edge-based interrupt.  The other is set to a general-purpose input.  A third pin always goes to ground.  These pins are usually filtered by two resistors and a capacitor on each reference pin.  When the encoder is turned, an interrupt happens.  The IRQ routine sets a boolean flag for the main code that indicates that an encoder interrupt occured (I use isNewEncoder as a variable name. All my boolean vars start with is{condition}). 

 

  It then reads the other reference pin on the Input Port.  If the input port pin is high, then the IRQ sets a second boolean variable (isNewEncCW) indicating a small clockwise movement of the knob.  If the input port pin is low, then clear this second variable (isNewEncCW). Now exit the IRQ function.  IRQs should do VERY little; set a flag or two, test a input, toggle an output, and then leave.

 

  In the main loop, every 50 milliseconds or so, check if the global boolean isNewEncoder is true.  If it is, then you have a new encoder movement.  Now test for the direction of the movement by reading the boolean inNewEncCW that was updated by the latest IRQ.  Increment or decrement your main code rotary-encoder variable according to this boolean's value. Then clear the boolean: isNewEncoder (make it false) in order to enable the IRQ to catch the next encoder movement.  This will catch every encoder detent-click up to 20 per second.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Dear Simonetta,

 

Thank you for your feedback, but who is here talking about ATmega or bare/naked rotary encoder?

 

Edited my code above as you proposed.

 

Regards

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

for things that can bounce it's normally a bad idea to use port change interrupts. 

I would use a timer ISR and then make something like a key debounce routine.

 

Or with your code make sure it don't count just after a ISR (I think the cleanest way would be to still do the ISR but don't do anything if it's less than ?25ms since last ISR that was "real")

 

Add:

if the thing can bounce I think that you also need a previousA for the logic to be safe.

 

Last Edited: Fri. Jul 20, 2018 - 08:58 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

As we're talking Arduino, surely there must already be a library for this ... ?

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

There cannot be an arduino library, because, even with optical encoders, they must use interrupts (360 position, can lead to 3 ms pulses, when user is nervous  : it is too fast to be coped with arduino 1 ms "systicks") and that would become more complicated than the code in itself.

 

The issue with henrybench site is that they use  tests in loop(); if some other tasks are added, one might miss pulses...

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

awneil wrote:

As we're talking Arduino, surely there must already be a library for this ... ?

 

We are talking here about ATtiny85 and and not about standard ATmega328, the code with "attachInterrupt()" will work on ATmega328, but the same code with pin modification will not work on the ATtiny85.

 

Here is one simple example for that (working excelent on Arduino Uno, but if I change the pins and upload for ATtiny85, it's just messing the counts up):

const int PinA = 2; // Used for generating interrupts using CLK signal
const int PinB = 3; // Used for reading DT signal
const int PinSW = 4; // Used for the push button switch


int lCnt = 0; // Keep track of last rotary value
volatile int vPos = 0; // Updated by the ISR (Interrupt Service Routine)

void isr_event ()  {
  static unsigned long lIsrTmr = 0; // Last Interrupt time
  unsigned long IsrTmr = millis(); // Interrupt time

  // If interrupts come faster than 5ms, assume it's a bounce and ignore
  if (IsrTmr - lIsrTmr > 5) {
    if (digitalRead(PinB) == LOW)
    {
      vPos++ ; // Could be +5 or +10
    }
    else {
      vPos-- ; // Could be -5 or -10
    }

    // Restrict value from 0 to +10
    vPos = min(10, max(0, vPos));

    // Keep track of when we were here last (no more than every 5ms)
    lIsrTmr = IsrTmr;
  }
}

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

  // Rotary pulses are INPUTs
  pinMode(PinA, INPUT);
  pinMode(PinB, INPUT);
  pinMode(PinSW, INPUT);
  attachInterrupt(digitalPinToInterrupt(PinA), isr_event, LOW);
  
  Serial.println(F("Starting..."));}

void loop()
{

  // Is someone pressing the rotary switch?
  if ((!digitalRead(PinSW))) {
    vPos = 0;
    while (!digitalRead(PinSW))
      delay(10);
  }

  // If the current rotary switch position has changed then update everything
  if (vPos != lCnt) {
    // Write out to serial monitor the value and direction
    Serial.println(vPos);
    // Keep track of this new value
    lCnt = vPos ;
  }
}

 

 

That is the issue, and I tried more than 20+ examples for ATtiny85 and they just wont work at all.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

the code in #12 have a logic problem that get bigger if there are noise (I guess that the "dead time" help), if one channel stay the same and the other go up down up down .... it will run away, (it should flip between 2 numbers).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Does your loop () countain tasks which last long time?

Your delay() is very long -10 ms- you can  miss pulses, if button is quickly turned....

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

sparrow2 wrote:

the code in #12 have a logic problem that get bigger if there are noise (I guess that the "dead time" help), if one channel stay the same and the other go up down up down .... it will run away, (it should flip between 2 numbers).

 

Did you even try the code from my #12 post?

 

Even whit the low cost KY-040 encoder, has only 3x 10k pull-up reistor (without any HW debounce like my second encoder module posted above) and it's working rock solid without any number flipping (tried again as we speak).

 

And again, for some reason this same code will not work on ATtiny85, that is my issue.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Does this help?

 

https://github.com/damellis/attiny/

 

Also, Atmel Studio recognises ATTiny as a supported core - so then you could use the debugger ...

 

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

dbrion0606 wrote:

Does your loop () countain tasks which last long time?

 

No, only if then elses, no delays inside (except that one 10ms).

 

dbrion0606 wrote:

Your delay() is very long -10 ms- you can  miss pulses, if button is quickly turned....

 

Plese, see my post at #15

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

awneil wrote:

Does this help?

 

https://github.com/damellis/attiny/

 

Also, Atmel Studio recognises ATTiny as a supported core - so then you could use the debugger ...

 

I'm using this one https://github.com/SpenceKonde/ATTinyCore for years now without any issues (it's more straightforward), unfortunately I'm using Arduino IDE v1.8.2 and not Atmel Studio.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

No I just look at the code (we have been here more than 10 times).

 

I now I see that it's not quadrature only ISR on low pinA.

and that I guess is why that work.

If that is ok why not do the same in #1 ?  

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

in the code of #1 try in the ISR to add:

 

an portReading_old so if the reading is the same as last time do nothing! (it will happen if the pulse that fired the ISR is in noise)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

sparrow2 wrote:

If that is ok why not do the same in #1 ?  

 

 

Tried to port code from my post #12 with pinout changing for ATtiny85, but it's not working, that why I did the code from #1, and I cant figure out why is it not working on ATtiny85.

 

But I would prefer code from post #12

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

beic wrote:
it shows me 0, 2, 4, 8, 12 and sometimes gives me some random number output.

beic wrote:
 

PCMSK = 0b00011000;    // turn on interrupt on pins PB3 and PB4

 

You have enabled pin change interrupts on both pins. Each pin should change from high to low (or opposite) and back per click (detent) according to this post. This means that your current interrupt routine should get called 4 times when turning from one click to another. So it seems as if the expected output should be 0, 4, 8, ... if pulse sequence follows your assumed positive direction.  So, I don't see a problem with your observed values?

 

Edit: Trimmed quoted text and added code tag.

Last Edited: Fri. Jul 20, 2018 - 12:18 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ccrause wrote:

You have enabled pin change interrupts on both pins. Each pin should change from high to low (or opposite) and back per click (detent) according to this post. This means that your current interrupt routine should get called 4 times when turning from one click to another. So it seems as if the expected output should be 0, 4, 8, ... if pulse sequence follows your assumed positive direction.  So, I don't see a problem with your observed values?

 

Could you please edit my code to be suitable for me? Strugling like 3-4 days already. Thanks in advance!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

beic wrote:

ccrause wrote:

You have enabled pin change interrupts on both pins. Each pin should change from high to low (or opposite) and back per click (detent) according to this post. This means that your current interrupt routine should get called 4 times when turning from one click to another. So it seems as if the expected output should be 0, 4, 8, ... if pulse sequence follows your assumed positive direction.  So, I don't see a problem with your observed values?

 

Could you please edit my code to be suitable for me? Strugling like 3-4 days already. Thanks in advance!

 

If you are indeed reading 4 pulses per click, then one possible solution is simply divide by 4 (referring to code in #1):

 lcd.print(value/4);
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Enable the Tiny85 pin change interrupt for only one of the input pins.  The other one is just an input pin.  The PCINT interrupt will trigger for each edge.  Select one (falling) for encoder handling.  When the PCINT interrupt happens, test if the logic on the pin is high.  If yes, exit the interrupt without doing anything.  If the pin is low, then read the other input pin.   Update the two boolean flags (HasEncoderChanged?  and WhichDirection?)  for the main code, and leave the interrupt.  Read these two flags in the main code at a rate that is faster than your user will be turning the encoder knob (20 updates a second should work well). 

 

A reason why the code above works with an UNO but not a Tiny is that the UNO runs at 16MHz and the Tiny85 usually has an internal clock of 8MHz.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If needed the the tiny 85 can run 16MHz from the internal clk.

 

Add:

and don't use PCINTn but INT0 on one of the edges, then the same logic should work.

Last Edited: Sat. Jul 21, 2018 - 09:22 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

sparrow2 wrote:

If neededthe tiny 85 can run 16MHz from the internal clk.

 

Add:

and don't use PCINTn but INT0 on one of the edges, then the same logic should work.

 

I wish to run it on 8MHz internal CLK...

 

Can someone please fix my code, because I'm already lost in it! :(

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think this is the line you want to change:

PCMSK = 0b00011000;    // turn on interrupt on pins PB3 and PB4

Try changing to:

PCMSK = 0b00001000;    // turn on interrupt on pins PB3

 

- Jeff Wahaus -