[SOLVED] Converting sketch from Arduino UNO to Attiny85

Hello to all,
I am a newbie to this platform however with some basics of programming in C and C++.
The issue I am facing to is the impossibility to convert a quite simple (I think it is) sketch to Attiny85.
The sketch itself is a working mechanical encoder routine using interrupts I found on GitHub - a fellow named Brian Low made a repackaged version of Ben Buxton’s rotary library [GitHub - brianlow/Rotary: Rotary encoder library for Arduino]
The sketch works like a charm on Arduino and ATmega328 processor.
And I am facing serious issues trying to make it work with Attiny85.

Let’s go into detals.
The original sketch is as follows

*
    Rotary Encoder - Interrupt Example
    
    The circuit:
    * encoder pin A to Arduino pin 2
    * encoder pin B to Arduino pin 3
    * encoder ground pin to ground (GND)
*/

#include <Rotary.h>

Rotary r = Rotary(2, 3);

void setup() {
  Serial.begin(9600);
  r.begin();
  PCICR |= (1 << PCIE2);
  PCMSK2 |= (1 << PCINT18) | (1 << PCINT19);
  sei();
}

void loop() {

}

ISR(PCINT2_vect) {
  unsigned char result = r.process();
  if (result == DIR_NONE) {
    // do nothing
  }
  else if (result == DIR_CW) {
    Serial.println("ClockWise"); 
    // Do something.....
  }
  else if (result == DIR_CCW) {
    Serial.println("CounterClockWise"); 
    // Do something.....
  }
}

Library “Rotary.h” is as follows:

/*
 * Rotary encoder library for Arduino.
 */

#ifndef Rotary_h
#define Rotary_h

#include "Arduino.h"

// Enable this to emit codes twice per step.
#define HALF_STEP

// Values returned by 'process'
// No complete step yet.
#define DIR_NONE 0x0
// Clockwise step.
#define DIR_CW 0x10
// Counter-clockwise step.
#define DIR_CCW 0x20

class Rotary
{
  public:
    Rotary(char, char);
    unsigned char process();
    void begin(bool pullup=true);
  private:
    unsigned char state;
    unsigned char pin1;
    unsigned char pin2;
};

#endif

And the third file- the handler - the decoder of Gray Code: “Rotary.cpp” is…

* Rotary encoder handler for arduino.
 *
 * Copyright 2011 Ben Buxton. Licenced under the GNU GPL Version 3.
 * Contact: bb@cactii.net
 *
 */

#include "Arduino.h"
#include "Rotary.h"

/*
 * The below state table has, for each state (row), the new state
 * to set based on the next encoder output. From left to right in,
 * the table, the encoder outputs are 00, 01, 10, 11, and the value
 * in that position is the new state to set.
 */

#define R_START 0x0

#ifdef HALF_STEP
// Use the half-step state table (emits a code at 00 and 11)
#define R_CCW_BEGIN 0x1
#define R_CW_BEGIN 0x2
#define R_START_M 0x3
#define R_CW_BEGIN_M 0x4
#define R_CCW_BEGIN_M 0x5
const unsigned char ttable[6][4] = {
  // R_START (00)
  {R_START_M, R_CW_BEGIN, R_CCW_BEGIN, R_START},
  // R_CCW_BEGIN
  {R_START_M | DIR_CCW, R_START, R_CCW_BEGIN, R_START},
  // R_CW_BEGIN
  {R_START_M | DIR_CW, R_CW_BEGIN, R_START, R_START},
  // R_START_M (11)
  {R_START_M, R_CCW_BEGIN_M, R_CW_BEGIN_M, R_START},
  // R_CW_BEGIN_M
  {R_START_M, R_START_M, R_CW_BEGIN_M, R_START | DIR_CW},
  // R_CCW_BEGIN_M
  {R_START_M, R_CCW_BEGIN_M, R_START_M, R_START | DIR_CCW},
};
#else
// Use the full-step state table (emits a code at 00 only)
#define R_CW_FINAL 0x1
#define R_CW_BEGIN 0x2
#define R_CW_NEXT 0x3
#define R_CCW_BEGIN 0x4
#define R_CCW_FINAL 0x5
#define R_CCW_NEXT 0x6

const unsigned char ttable[7][4] = {
  // R_START
  {R_START, R_CW_BEGIN, R_CCW_BEGIN, R_START},
  // R_CW_FINAL
  {R_CW_NEXT, R_START, R_CW_FINAL, R_START | DIR_CW},
  // R_CW_BEGIN
  {R_CW_NEXT, R_CW_BEGIN, R_START, R_START},
  // R_CW_NEXT
  {R_CW_NEXT, R_CW_BEGIN, R_CW_FINAL, R_START},
  // R_CCW_BEGIN
  {R_CCW_NEXT, R_START, R_CCW_BEGIN, R_START},
  // R_CCW_FINAL
  {R_CCW_NEXT, R_CCW_FINAL, R_START, R_START | DIR_CCW},
  // R_CCW_NEXT
  {R_CCW_NEXT, R_CCW_FINAL, R_CCW_BEGIN, R_START},
};
#endif

/* Constructor. Each arg is the pin number for each encoder contact. */

Rotary::Rotary(char _pin1, char _pin2) {
  // Assign variables.
  pin1 = _pin1;
  pin2 = _pin2;
  // Initialise state.
  state = R_START;
}

void Rotary::begin(bool pullup) {

  if (pullup){
    // Enable weak pullups
    pinMode(pin1,INPUT_PULLUP);
    pinMode(pin2,INPUT_PULLUP);
  }else{
    // Set pins to input.
    pinMode(pin1, INPUT);
    pinMode(pin2, INPUT);
  }
}

unsigned char Rotary::process() {
  // Grab state of input pins.
  unsigned char pinstate = (digitalRead(pin2) << 1) | digitalRead(pin1);
  // Determine new state from the pins and state table.
  state = ttable[state & 0xf][pinstate];
  // Return emit bits, ie the generated event.
  return state & 0x30;
}

The encoder (mechanical) is attached to pins PB3 and PB4 (and GND of course).
Output is set to pins PB0 and PB2

Conversion of the main sketch to Attiny85 is very easy:
PCICR => GIMSK
PCIE2 => PCIE
PCINT18 => PCINT3
PCINT19 => PCINT4
And
ISR(PCINT2_vect) => ISR(PCINT0_vect)
That’s all

With those substitutions the sketch can be compiled without errors, and can be loaded to Attiny85
I added also more libraries as
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

And of course I deleted all lines in relation to Serial commands

But it is not working.
Not at all

And it has to work on Attiny85 (the routine is so small that using a big ATmega processor is useless - and expensive).
My knowledge of AVR platform is too small to find out where is the issue.
Although I made quite large research over the web.

I guess the solution lays in library and/or handler files but I cannot find it.
Can anybody help me on this to make the sketch working? What has to be changed?

Konishoa

You don't need to use a library or interrupts to read an encoder, especially if it is manually operated, or if "reading the encoder" is all the ATtiny needs to do. There are a number of examples posted.

You might start here:

or much simpler, here:
https://codebender.cc/sketch:346306#Rotary%20Encoder%20no%20Interrupt.ino.

First off, are you using a PDIP ATTiny85 or a QFN ATTiny85?

Start by removing the includes and using a CORE for the ATTiny85, such as, GitHub - SpenceKonde/ATTinyCore: Arduino core for ATtiny 1634, 828, x313, x4, x41, x5, x61, x7 and x8 This will help with making the errors more visible directly related to the sketch.

jremington:
You don't need to use a library or interrupts to read an encoder, especially if it is manually operated, or if "reading the encoder" is all the ATtiny needs to do. There are a number of examples posted.

Sure. Before I wrote this post I have already tried few sketches not using interrupts. I was not satisfied of how they worked. Well - quite wery insatisfied I was. The routine I want to convert has to be the core of something more complicated - in the future. So why do not start on solid basis, don't you think? So, the routine I have included matches perfectly my needs and let's consider this routine only.

Perehama:
First off, are you using a PDIP ATTiny85 or a QFN ATTiny85?

Well - a DIP one. Is there a major difference with the code sent to a DIP or a QFN version?

Perehama:
Start by removing the includes and using a CORE for the ATTiny85, such as, GitHub - SpenceKonde/ATTinyCore: Arduino core for ATtiny 1634, 828, x313, x4, x41, x5, x61, x7 and x8 This will help with making the errors more visible directly related to the sketch.

If I remove the Rotary.h library I am affraid the sketch will not work.... I wrote I am a newbie. I do not expect a complete solution (howewer as far as I am concerned I do not have problems sharing my knowledge with others), but at least an indication what is wrong and by what manner it can be fixed. I read the document you referred to. More then - I read also the Attiny85 specs (235 pages - hard to read) and I did not found anything to guide me.
Perhaps the solution is so obvious I don't see it.
Konishoa

You tried some code that worked fine for other people, but weren’t happy with it. Then you tried some different code, and can’t get that to work at all.

Good learning experience!

jremington:
You tried some code that worked fine for other people, but weren't happy with it. Then you tried some different code, and can't get that to work at all.

Good learning experience!

Yes. In oposition to bad reading experience. I wrote the code is working perfectly on ATmega328. For other people too. No bouncing, no overclicking, no loosing clicks. What I need is help to make it work on ATtiny85 and not writing poems on which holidays Xmas or Eastern are more cool. Can you help?

konishoa:
Well - a DIP one. Is there a major difference with the code sent to a DIP or a QFN version?

I thought there was a difference, but looking again, I see most of the QFN pins are not used.

konishoa:
If I remove the Rotary.h library I am afraid the sketch will not work....

The sketch does not work now. I am suggesting that you strip the code to a core you can be sure will work, which is not the same core for the UNO. That way you don't have to debug the core. Then, set aside other people's code for now... structure what you want in pseudo-code, your own words, that you will replace with working code as you learn. Rotary.h might work on the ATtiny85. Looking into the code, it works with Arduino pin numbers, so that might be one of the problems. Changing from the UNO core to an ATTiny Core, would give you the Arduino feel for the ATtiny85.

I haven't done an in-depth survey of your code, but one of the main differences that come to mind is that using pin-change interrupts on an ATtiny, results in any of the pins that are enabled calling the (same) ISR, and you have to determine within that which of the enabled pins have caused the call.

Are you making these things by the thousands? If you are making fewer than 1000 then an Arduino Mini for Nano for about $4 each might be a reasonable compromise. It will save you the trouble of producing a custom circuit board and paying for circuit board assembly.

johnwasser:
Are you making these things by the thousands? If you are making fewer than 1000 then an Arduino Mini for Nano for about $4 each might be a reasonable compromise. It will save you the trouble of producing a custom circuit board and paying for circuit board assembly.

The mini is retired and the Nano is $20 USD. I'd recommend a barebones Atmega328P because unless you are buying the ATmega85 in bulk, or need the space, $4 USD includes the price of the capacitors, resistor, external crystal and PCB.

Perehama:
Looking into the code, it works with Arduino pin numbers, so that might be one of the problems. Changing from the UNO core to an ATTiny Core, would give you the Arduino feel for the ATtiny85.

I don't think so. The "pin numbers" in the code are parameters. And even if, the pins # are the same for this project:2 and 3. And what else, beside all changes I already did, has to me made?

Deva_Rishi:
I haven't done an in-depth survey of your code, but one of the main differences that come to mind is that using pin-change interrupts on an ATtiny, results in any of the pins that are enabled calling the (same) ISR, and you have to determine within that which of the enabled pins have caused the call.

Deva_Rishi, You may not have seen the last part of my post #1, but I mentionned I adapted the interrupts and names to an ATTiny85 specifications according to the ATTiny85 Data Sheet from Atmel. Or maybe I didn't catch the trick....

johnwasser:
Are you making these things by the thousands?

johnwasser - No, this will be an unique device installed in an unique car (my Toyota 3rd gen 4Runner :slight_smile: ). So this is not a commercial project. Just for me, made by me (and by lot of helpers I would like they will actively assist in this pain).

Perehama:
The mini is retired and the Nano is $20 USD. I'd recommend a barebones Atmega328P because unless you are buying the ATmega85 in bulk, or need the space, $4 USD includes the price of the capacitors, resistor, external crystal and PCB.

Perehama - Yes - Right now I am using the ATmega328 μP, and as I wrote before - everything works. But I desperately need to reduce the dimensions of the PCB and this is the reason to convert the sketch to ATTiny85. Here where I live ATmega48 (frankly, there is no need to use a 328) costs around 2 bucks, but the target is space (and DIY, so definitely no QFN μP).

Anyway....no solution yet... still on the road.... :frowning:

Konishoa

unsigned char result = r.process();

This line doesn’t work because

unsigned char Rotary::process() {
  // Grab state of input pins.
  unsigned char pinstate = (digitalRead(pin2) << 1) | digitalRead(pin1);
  // Determine new state from the pins and state table.
  state = ttable[state & 0xf][pinstate];
  // Return emit bits, ie the generated event.
  return state & 0x30;
}

this doesn’t work because

unsigned char pinstate = (digitalRead(pin2) << 1) | digitalRead(pin1);

this line takes the value returned by digitalRead() and shifts it one bit, for one pin OR’d with the value returned by digital read() for the other pin, which are pin numbers, which are defined in the avr core for the target board you are using, which is not defined unless you use a core for the ATTiny85.

Perehama:
this doesn’t work because

unsigned char pinstate = (digitalRead(pin2) << 1) | digitalRead(pin1);

this line takes the value returned by digitalRead() and shifts it one bit, for one pin OR’d with the value returned by digital read() for the other pin, which are pin numbers, which are defined in the avr core for the target board you are using, which is not defined unless you use a core for the ATTiny85.

Thanks to show me the possible issue. I was also thinking of that, but with my (very) little knowledge of howto Arduino I did not know how to fix it

However… (in the same file)

Rotary::Rotary(char _pin1, char _pin2) {
  // Assign variables.
  pin1 = _pin1;
  pin2 = _pin2;
  // Initialise state.
  state = R_START;
}

And Rotary function in the main sketch …

#include <Rotary.h>
Rotary r = Rotary(2, 3);

We have 2 parameters (pins) 2 and 3, so I thought those arguments are taken in consideration by the code in Rotary.cpp

And you wrote:
…which are defined in the avr core for the target board you are using, which is not defined unless you use a core for the ATTiny85

When I am uploading the sketch to ATtiny actually I am using the board “ATtiny25/45/85 (no bootloader)” from ATTinyCore package (before I used the old damelis/attiny package). And the uploading goes via Arduino (as ISP). And I think I reloaded the bootloader when I changed the board to ATTinyCore (but not 100% sure)

I think we are closer now…

Konishoa

I found the issue!
(at least one good news at the end of this f.....g year 2020)
thanks Perehama!
I'll write my comments later on as now I am a litle bit in a hurry.

Konishoa

Right then, as I wrote before I made the sketch to work.

The issue was basic, but as a lot of newbies I made the same error - I confused physical ATTTiny85 pins with Port Pins.
In that particular case the confusion was easy as in ATmega328 sketch the Port Pins were 2 and 3 (what corresponds to physical pins 4 and 5). And in my conversion to ATTiny85 sketch I left the definition as is, as I was connecting the encoder to… ATTiny physical pins 2 and 3.
I saw this error when I decided to shrink the code into a unique sketch with no libraries.
And make the appriopriate change.
So instead

Rotary r = Rotary(2, 3);

in the sketch one has to write

Rotary r = Rotary(3, 4);

Elementaire, mon cher Watson…, but not so evident for a newbie.

Anyway - everything is working on ATTiny85 as it worked on ATmega328.
Goal achieved 12 hours before midnight (European time, as I live in Europe).

I played again with the sketch just to shrink as more as possible and came down from initial 1130 bytes to 874 bytes. Good base to use an ATTiny13, don’t you think :wink:

Here it is, but one has in mind the sketch is for a dedicated purpose, using half-step, not full-step, table.
In the future I will try to base the sketch on timer interrupts, to be more reliable for other, more advanced procedures I have in mind.

Enjoy and Happy New Year…

The sketch

/*
    Rotary Encoder - Timer Interrupt Routine
    
    The circuit:
    * encoder pin A to Attiny85 pin 2 (PB3)
    * encoder pin B to Attiny85 pin 3 (PB4)
    * encoder ground pin to pin 4 (GND)
    * outputs to Attiny85 pins 5 and 7 (PB2 and PB0)
*/

#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

#define PINA 3   //Set PB3 (physical: 2) as input
#define PINB 4   //Set PB4 (physical: 3) as input
#define REL1 0   //Set PB0 (physical: 5) as output
#define REL2 2   //Set PB2 (physical: 7) as output

#define DEL  300 //Set 300ms delay on outputs PB0 and PB2

// Values returned by 'process'
#define DIR_NONE 0x0   // No complete step yet.
#define DIR_CW 0x10    // Clockwise step.
#define DIR_CCW 0x20   // Counter-clockwise step.
#define R_START       0x0 
#define R_CCW_BEGIN   0x1
#define R_CW_BEGIN    0x2
#define R_START_M     0x3
#define R_CW_BEGIN_M  0x4
#define R_CCW_BEGIN_M 0x5
const unsigned char ttable[6][4] = {
  // R_START (00)
  {R_START_M,            R_CW_BEGIN,     R_CCW_BEGIN,  R_START},
  // R_CCW_BEGIN
  {R_START_M | DIR_CCW,  R_START,        R_CCW_BEGIN,  R_START},
  // R_CW_BEGIN
  {R_START_M | DIR_CW,   R_CW_BEGIN,     R_START,      R_START},
  // R_START_M (11)
  {R_START_M,            R_CCW_BEGIN_M,  R_CW_BEGIN_M, R_START},
  // R_CW_BEGIN_M
  {R_START_M,            R_START_M,      R_CW_BEGIN_M, R_START | DIR_CW},
  // R_CCW_BEGIN_M
  {R_START_M,            R_CCW_BEGIN_M,  R_START_M,    R_START | DIR_CCW},
}; 
unsigned char state = R_START;     // Initialise state

void setup()
{
  DDRB  = 0x05;  // enable PB0 and PB2 as outputs
  PORTB = 0x18;  // enable pullup on encoder inputs
  GIMSK = 0x20;  // turns on pin change interrupts
  PCMSK = 0x18;  // turn on interrupts on pins PB3 & PB4
/* //or, for better visibility
  DDRB  |= (1<< DDB0) | (1 << DDB2);       // enable PB0 and PB2 as outputs   (0x05)
  PORTB |= (1 << PB3) | (1 << PB4);        // enable pullup on encoder inputs (0x18)
  GIMSK |= (1 << PCIE);                    // turns on pin change interrupts
  PCMSK |= (1 << PCINT3) | (1 << PCINT4);  // turn on interrupts on pins PB3 & PB4
*/
  sei();                                   // enables interrupts
}

void loop()
{
 
}

ISR(PCINT0_vect) {
  unsigned char pinstate = (digitalRead(PINB) << 1) | digitalRead(PINA);  // Grab state of input pins.
  state = ttable[state & 0xf][pinstate];                                  // Determine new state from the pins and state table.
  unsigned char result = state & 0x30;                                    // Return emit bits, ie the generated event.

  if (result == DIR_NONE) {
    // do nothing
  }
  else if (result == DIR_CW) {
    // do something
    digitalWrite(REL1, HIGH); //output to #1 - CW
    delay(DEL);
    digitalWrite(REL1, LOW);
  }
  else if (result == DIR_CCW) {
    // do something    
    digitalWrite(REL2, HIGH); //output to #2 - CCW
    delay(DEL);
    digitalWrite(REL2, LOW);
  }
}

Konishoa

I confused physical ATTTiny85 pins with Port Pins.

Honestly that is a mistake that can only happen while working on the first ATtiny project and not looking at the pinout diagram. It really should have been 'blink' or something like that.

I happened as it was in fact my first ATTiny project and there is no shame to say that. I made the mistake despite I was looking to the pinout and reading datasheet, so never say never.
It's good to share corrective and preventive actions, isnt'it?
So I wrote my comment to serve other unexperienced people. It was not intended to be commented by hi-level experts that honestly never make mistakes (or carefully hide them, or something like that).

I think we can close the topic as the solution was found and brought to life.

Konishoa