Encoder development

Hey, y’all. So I recently added an encoder to my arduboyFX because it was something I hadn’t yet tried with arduboys, and it will be very useful if I can get it working properly as I’m also working on a diy pip-boy version of the arduboy, and being able to use a rotary encoder connected to pins 2 & 3 to select screens will be a very nice touch, imo.

But alas, there’s one little issue I’m having: if arduboy.begin(); is used, or any variation of it, the encoder will not read properly. Instead of incrementing or decrementing, the result is switching between two numbers next to each other, usually either 0 & 1 or -1 & -2.

This works, and will increment or decrement from 0:

#include <Arduboy2.h>
Arduboy2 arduboy;

#include <Encoder.h>

Encoder myEnc(3, 2);

long oldPos = -999;
void setup() {
  //arduboy.begin();
  Serial.begin(9600);
  Serial.println("Encoder test:\n");
}

void loop() {
  long newPos = myEnc.read();
  if (newPos != oldPos) {
    oldPos = newPos;
    Serial.println(newPos);  
  }
}

But as soon as arduboy.begin(); is uncommented, it breaks functionality, and there’s no way to tell whether the encoder was moved to the left or to the right, just that it was moved.

Why is this? Any ideas to get around this issue? Or should I just use the encoder being pulsed as a function, and forget about differentiating left and right within arduboy sketches?

Any thoughts & ideas are greatly appreciated! :smiley:

The pins will not have their configuration with begin, you will need to set them as input with pullups.

2 Likes

Oh! Thanks :smiley: it’s always the most obvious thing lol

I believe the ArduboyFX uses Arduino Pin 2 for the flash chip select signal, so you probably will have a conflict trying to use it for the encoder.

3 Likes

The only conflict I’ve noticed is that it momentarily shows the “USB boot” symbol on the screen sometimes during a reset. Though I haven’t tested anything that pulls data from the FX within a game/sketch yet, so that may be where the issues start cropping up.

But in good news, the encoder is otherwise working perfectly now, with this being my new test code:

#include <Arduboy2.h>

#include <Encoder.h>

// Change these two numbers to the pins connected to your encoder.
//   Best Performance: both pins have interrupt capability
//   Good Performance: only the first pin has interrupt capability
//   Low Performance:  neither pin has interrupt capability
Encoder myEnc(3, 2);
//   avoid using pins with LEDs attached

Arduboy2 arduboy;

long oldPosition = -999;
void setup() {
  // put your setup code here, to run once:
  arduboy.begin();
  pinMode (3,INPUT_PULLUP);
  pinMode (2,INPUT_PULLUP);
  arduboy.clear();
  arduboy.setFrameRate(15);
  
}

void loop() {
  // put your main code here, to run repeatedly:
  if (!arduboy.nextFrame()) return;
  arduboy.clear();
  
  long newPosition = myEnc.read();
  if (newPosition != oldPosition) {
    oldPosition = newPosition;
   // Serial.println(newPosition);
  }
  arduboy.setCursor(0,0);
  arduboy.print("Encoder val: \n");
  arduboy.print(newPosition);
  arduboy.display();
}

Using pin 2 / SDA is a bad idea.

  • your rotary encoder will connect this pin to GND on each turn step. the FX menu configures this pin as output and sets it to high when when the FX flash chip is not accessed. Basically the pin is shorted to GND frequently.
  • when the rotary encoder connects this pin to GND any data written to the OLED display will also be written to the FX flash chip causing unexpected behaviour.
  • when the rotary encoder connects this pin to GND. It is not possible to correctly address the flash chip . Thats why the USB boot icon shows.
  • the Encoder library uses interrupts. any FX flash chip access in a sketch will also trigger a interrupt causing ghost movements of your rotary switch.

You can use pin 0/1 (RX/TX) as alternative.

Had a look at the Encoder lib. Reason why your earlier code failed is that the pins where configured by the line

Encoder myEnc(3, 2);

But then arduboy.begin(); changes the SDA pin back to output (Arduboy2 Homemade package)

3 Likes

Good to know! I’ll go ahead and switch that over to pin 0 or 1.

I’m also trying to implement the encoder as a way to choose different items in a menu (with similar behavior to justPressed()), but the encoder increments a few times between each position, making it a little difficult to get it to only move one by one item at a time throughout the menu, instead it jumps 3-4 spaces each time. The best I could figure out was to make it so that it had to be turning clockwise or counterclockwise with every 3 frames. Together, this is approximately the result I’m looking for, but very dirty and not a real solution.

Here’s what I’ve got:

void handleEncoder(){
  long newPosition = Enc.read();
  if (newPosition != oldPosition) {
    if (newPosition > oldPosition){
      COUNTER_CLOCKWISE = true;
      CLOCKWISE = false;
    } else{
      COUNTER_CLOCKWISE = false;
      CLOCKWISE = true;
    }
    oldPosition = newPosition;
   // Serial.println(newPosition);
  } else {
    COUNTER_CLOCKWISE = false;
    CLOCKWISE = false;
  }
  
}

and

 if (((pipboy.everyXFrames(3) && CLOCKWISE) || pipboy.justPressed(RIGHT_BUTTON)) && mainMenu < 3) {
    ++mainMenu;
  }
  if (((pipboy.everyXFrames(3) && COUNTER_CLOCKWISE) || pipboy.justPressed(LEFT_BUTTON)) && mainMenu > 0) {
    --mainMenu;
  }

Oh yeah, I used 2 and 3 for the Crank Mod because back then we were using a different pin for the chip select.

Reading from the encoder library was a bit tricky for me to get right too, cant remember exactly the tricks though. Make sure you are attaching the interrupt to those pins I think maybe?

You could just use encoder.read() / 4, so the value would only change every 4 steps.

Or if you know how many menu options you have, you could use range mapping to map the potentiometer’s output range to your options.

(Before anyone says ‘a rotary encoder is not a potentiometer’, I am aware they’re different things, but they’re both angular inputs so I’m doing some handwaving and pretending they have the same abstract role.)

Arduino comes with a map function which will probably suit your needs, but be careful about fractions.

If you have a lot of options and the fractions issue becomes a problem then you may be better off using a float version.

float map(float value, float lowerInput, float higherInput, float lowerOutput, float higherOutput)
{
	return (lowerOutput + ((value - lowerInput) * ((higherOutput - lowerOutput) / (higherInput - lowerInput))));
}

Anyway, you’d use it like:

// Assuming 1023 is the maximum value you'll get out of the potentiometer
option = map(encoder.read(), 0, 1024, 0, optionCount);

The effect is that the potentiometer’s turn arc essentially gets divided up like a pie chart, with each slice of the arc representing a different menu option.

However, this assumes your potentiometer has a ‘start’ and ‘end’ point and doesn’t just spin constantly like a mouse wheel.

If you’ve got a ‘mouse wheel’ style input then you pretty much have to just use the delta (the change in position). Though you can still shrink and maginify that change with division, multiplication or mapping.


A few other tips…

If you do end up sticking with the clockwise/anticlockwise approach, you might be better off using a scoped enumeration instead of a pair of bools.

E.g.

enum class TurnDirection : uint8_t
{
	None,
	Clockwise,
	Anticlockwise,
};

Alternative you could use the difference between the old value and the new value to determine the direction, because the difference will be negative, positive or zero depending on the direction the potentiometer has moved.

Which way round it is would depend on how the potentiometer is set up. Assuming the values increase as the potentiometer is turned clockwise, doing (newPosition - oldPosition) would give a positive result for clockwise turns and a negative result for anticlockwise turns.

Then you can just do:

// Anticlockwise
if (difference < 0)
{
}
// Clockwise
else if (diffence > 0)
{
}
// No change
else
{
}

For what it’s worth, you may as well just do oldPosition = newPosition; regardless of whether newPosition != oldPosition, it’ll make your code a bit easier to read and the 2-4 cycles saved by trying to dodge the assignment aren’t really worth it.

Lastly, make sure to avoid using ‘all caps’ (a.k.a. macro case) for anything other than macros, as is C++ convention.


For the record, I think Encoder is a really terrible library name because encoding is an abstract concept and there are many different types of encodings. E.g. text encodings. Without more details, one would be forgiven for assuming Encoder was for encoding text or did base 64 encoding or something similar.

1 Like

Fantastic! I was able to do what I’m aiming for by doing encoder.read()/4 and following your advice on using the difference to determine anticlockwise, clockwise, or no change, then moving oldPosition = newPosition to just before display(). Here’s the example sketch I built before trying to use it with the pip-boy menu:

#include <Arduboy2.h>

#include <Encoder.h>

// Change these two numbers to the pins connected to your encoder.
//   Best Performance: both pins have interrupt capability
//   Good Performance: only the first pin has interrupt capability
//   Low Performance:  neither pin has interrupt capability
Encoder myEnc(3, 0);
//   avoid using pins with LEDs attached

Arduboy2 arduboy;
int counter = 0;
int oldPosition = -999;
void setup() {
  // put your setup code here, to run once:
  arduboy.begin();
  pinMode (3,INPUT_PULLUP);
  pinMode (0,INPUT_PULLUP);
  arduboy.clear();
  arduboy.setFrameRate(15);
  
}
void loop() {
  // put your main code here, to run repeatedly:
  if (!arduboy.nextFrame()) return;
  arduboy.clear();
  int newPosition = myEnc.read()/4;
  
  int difference = (newPosition - oldPosition);
   if (difference < 0) {
    //clockwise
    counter++;
   } else if (difference >0){
    //anticlockwise
    counter--;
   } else {
    //no change
   }
  arduboy.setCursor(0,0);
  arduboy.print("Encoder val: \n");
  arduboy.print(counter);
  oldPosition = newPosition;
  arduboy.display();
}

This is definitely the best behavior I’ve seen so far. It doesn’t do anything unexpected and it offers some wiggle room for the developer in regards to how they handle the rotations.

1 Like

If behaviour is still is unreliable. Your rotary encoder will probably need some extra parts to debounce it’s
Outputs.

A simple debounce circuit can be made by putting a 10k resistor in series between the rotary encoders output and the Arduboy/Arduino pin and then connect a 100nF capacitor between ground and the Arduino pin.

Repeat this for the other encoder pin as well.

2 Likes

Good to know! Luckily the above code’s behavior is reliable, so I shouldn’t need a filter like that with this one, but if I find that a different rotary encoder is giving me trouble, I’ll keep that in mind :smiley: