How to use PINB for rotary encoder.

I’m new to Arduino and fairly new to programming in general. I’ve been following the “Rotary Encoder Volume Control” but I need 3 rotary encoders for my project. I’ve been able to modify the code to add 2 of them using PIND (pins PD0-PD6) without a problem, but I can figure out how to use this code with any PINB (pins PB0-PB5).

Here’s how I’ve modified the code to the best of my understanding from reading through the forums and through Google searches. Any assistance would be greatly appreciated.

#include <ProTrinketHidCombo.h>

#define PIN_ENCODER_A      9
#define PIN_ENCODER_B      11
#define TRINKET_PINx       PINB
#define PIN_ENCODER_SWITCH 10

static uint8_t enc_prev_pos   = 0;
static uint8_t enc_flags      = 0;
static char    sw_was_pressed = 0;

void setup()
{
  // set pins as input with internal pull-up resistors enabled
  pinMode(PIN_ENCODER_A, INPUT_PULLUP);
  pinMode(PIN_ENCODER_B, INPUT_PULLUP);
  pinMode(PIN_ENCODER_SWITCH, INPUT_PULLUP);

  TrinketHidCombo.begin(); // start the USB device engine and enumerate

  // get an initial reading on the encoder pins
  if (digitalRead(PIN_ENCODER_A) == LOW) {
    enc_prev_pos |= (1 << 0);
  }
  if (digitalRead(PIN_ENCODER_B) == LOW) {
    enc_prev_pos |= (1 << 1);
  }
}

void loop()
{
  int8_t enc_action = 0; // 1 or -1 if moved, sign is direction

  // note: for better performance, the code will use
  // direct port access techniques
  // http://www.arduino.cc/en/Reference/PortManipulation
  uint8_t enc_cur_pos = 0;
  // read in the encoder state first
  if (bit_is_clear(TRINKET_PINx, PIN_ENCODER_A)) {
    enc_cur_pos |= (1 << 0);
  }
  if (bit_is_clear(TRINKET_PINx, PIN_ENCODER_B)) {
    enc_cur_pos |= (1 << 1);
  }

  // if any rotation at all
  if (enc_cur_pos != enc_prev_pos)
  {
    if (enc_prev_pos == 0x00)
    {
      // this is the first edge
      if (enc_cur_pos == 0x01) {
        enc_flags |= (1 << 0);
      }
      else if (enc_cur_pos == 0x02) {
        enc_flags |= (1 << 1);
      }
    }

    if (enc_cur_pos == 0x03)
    {
      // this is when the encoder is in the middle of a "step"
      enc_flags |= (1 << 4);
    }
    else if (enc_cur_pos == 0x00)
    {
      // this is the final edge
      if (enc_prev_pos == 0x02) {
        enc_flags |= (1 << 2);
      }
      else if (enc_prev_pos == 0x01) {
        enc_flags |= (1 << 3);
      }

      // check the first and last edge
      // or maybe one edge is missing, if missing then require the middle state
      // this will reject bounces and false movements
      if (bit_is_set(enc_flags, 0) && (bit_is_set(enc_flags, 2) || bit_is_set(enc_flags, 4))) {
        enc_action = 1;
      }
      else if (bit_is_set(enc_flags, 2) && (bit_is_set(enc_flags, 0) || bit_is_set(enc_flags, 4))) {
        enc_action = 1;
      }
      else if (bit_is_set(enc_flags, 1) && (bit_is_set(enc_flags, 3) || bit_is_set(enc_flags, 4))) {
        enc_action = -1;
      }
      else if (bit_is_set(enc_flags, 3) && (bit_is_set(enc_flags, 1) || bit_is_set(enc_flags, 4))) {
        enc_action = -1;
      }

      enc_flags = 0; // reset for next time
    }
  }

  enc_prev_pos = enc_cur_pos;

  if (enc_action > 0) {
    TrinketHidCombo.pressMultimediaKey(MMKEY_VOL_UP);  // Clockwise, send multimedia volume up
  }
  else if (enc_action < 0) {
    TrinketHidCombo.pressMultimediaKey(MMKEY_VOL_DOWN); // Counterclockwise, is multimedia volume down
  }

  // remember that the switch is active low 
  if (bit_is_clear(TRINKET_PINx, PIN_ENCODER_SWITCH)) 
  {
    if (sw_was_pressed == 0) // only on initial press, so the keystroke is not repeated while the button is held down
    {
      TrinketHidCombo.pressMultimediaKey(MMKEY_MUTE); // Encoder pushed down, toggle mute or not
      delay(5); // debounce delay
    }
    sw_was_pressed = 1;
  }
  else
  {
    if (sw_was_pressed != 0) {
      delay(5); // debounce delay
    }
    sw_was_pressed = 0;
  }

  TrinketHidCombo.poll(); // check if USB needs anything done, do every 10 ms or so
}

BTW I'm using the Trinket Pro. The same hardware from the tutorial.

You can't mix Arduino port pin definitions with direct access port pin definitions, as you are attempting to do.

When you access the variable PINB, it reads all pins of PORTB, numbered 0-7 (pin values are 1-128). To test whether PORTB, pin 0 (PB0) is set, you can use code similar to this:

  if((PINB & 1) == 1) {
     do_something();
   }

Note this can be simplified to:

  if(PINB & 1) {
     do_something();
   }

But that form is less easy for another person to understand.

Just to understand a bit better, should this code work with only one encoder if I change the definitions to for pins PB1, PB2 and PB3?:

#define PIN_ENCODER_A      1
#define PIN_ENCODER_B      3
#define TRINKET_PINx       PINB
#define PIN_ENCODER_SWITCH 2

No. Pin values are equal to 2(pin number) or 1 to 128 for pins 0 to 7.

If you use macros like loop_until_bit_is_set(port,bit) or bit_is_clear(port,bit) you use the port name (e.g. PINB) and bit number, not the value.

It takes quite a bit more effort to use direct port access than to use the Arduino pin numbers and digitalRead(), for example.

Thanks for your assistance. I'll see if I can wrap my head around it.