SPI binary output for rotary encoder?

Hi guys, I don't really have much knowledge on arduino programming (yet) but use a code made by others quite frequently for my diy projects.

I have the following code to 8 bytes of data via SPI (it emulates arduino inputs as button press on Thrustmaster racing wheel) and run it on Arduino Nano:

/*
Ver. 1.01 - 2019-06-26

Changelog:
1.01 - 2019-06-25 - Wiring changed. Orange wire (SS) goes to pin D10 only. D2 now used for Button 5 (was at D11)
1.00 - 2019-06-17 - initial, based on tx_rw_ferrari_458_wheel_emu_16buttons.ino

This sketch emulates Thrustmaster TMX Pro stock wheel
allowing to connect arduino to TMX Pro wheelbase
and emulate button presses - up to 16 buttons can be connected to arduino (because stock TMX Pro wheel has 12 buttons + 4 for D_Pad)

Thrustmaster TMX Wheelbase cable pinout (2.54mm XH female connector)
1 - Green    - not used
2 - Blue     - GND (ground)
3 - White    - MISO (master in, slave out - to read the data from the wheel)
4 - Orange   - SS (slave select, or PL - parallel load, set it to 0 when you want to read the data)
5 - Red      - CLK (clock impulses)
6 - Black    - +VCC (+3.3 volts! if you have Arduino 5V version - use external +3.3V power or use the wheelbase power!!!)

Arduino UNO pins -> TMX wheelbase cable pins

Arduino GND                 -> TMX Blue wire (2)
Arduino pin 12 (MISO)        -> TMX White wire (3) (data from wheel to base)
Arduino pin 10 (SS)         -> TMX Orange wire pin (4) (INT0 moved to D10)
Arduino pin 13 (SCK)        -> TMX Red wire (5)
Arduino +5V                 -> TMX Black wire (6) (it gives 3.3V, but must be connected into +5V socket on arduino uno side)

Button mappings (we send 8 bytes to wheelbase, only 3 first bytes matter, only 16 bits are used for buttons)

Byte 1 (constant)
  1 - not used
  1 - n/u
  0 - n/u
  1 - n/u
  1 - n/u
  0 - n/u
  0 - n/u
  1 – n/u
  
Byte 2
  1 – Menu (button 8, bottom center)      ->   pin 7 (portD bit7)
  1 – Menu (button 12, bottom right)      ->   pin 6 (portD bit6)
  1 – Gear Up (RB paddle, button 2)       ->   pin 5 (portD bit5)
  1 – B (button 9)                        ->   pin 4 (portD bit4)
  1 – RB (button 11, bottom right)        ->   pin 3 (portD bit3)
  1 – A (button 5)                        ->   pin 2 (portD bit2)
  1 – Y (button 10)                       ->   pin 1 (portD bit1) - if you don't use serial debug (UART TX)
  1 – X (button 3)                        ->   pin 0 (portD bit0) - if you don't use serial debug (UART RX)

Byte 3
  1 – D-Pad Down                          ->   pin 9 (portB bit1)
  1 – View (button 7, bottom center)      ->   pin 8 (portB bit0)
  1 – LB (button 6, bottom left)          ->   pin A5 (portC bit5)
  1 – View (button 4, bottom left)        ->   pin A4 (portC bit4)
  1 – Gear Down (LB paddle, button 1)     ->   pin A3 (portC bit3)
  1 - D-Pad Left                          ->   pin A2 (portC bit2)
  1 - D-Pad Up                            ->   pin A1 (portC bit1)
  1 - D-Pad Right                         ->   pin A0 (portC bit0)
  
Each push button has 2 pins - you connect one to Arduino, another to the Ground :)
Pressed button gives you "0" (grounded signal), released = "1" (pulled-up to +VCC)

Feel free to modify and use for your needs.
This sketch and the documentation above provided "AS IS" under the BSD New license.
http://opensource.org/licenses/BSD-3-Clause

(c) Taras Ivaniukovich (blog@rr-m.org)
http://rr-m.org/blog/

Credits:
Jake L. for reading his TMX Pro wheel and testing
Virág I. for Pin Change Interrupt idea (move external interrupt from Pin 2 to Pin 10)
*/

byte wheelState [8]; // local push-buttons state saved here
volatile byte pos;

void setup (void) {
  DDRB  |= B00000011; // digital pins 8,9 used as inputs with a pull-up to +VCC
  PORTB |= B00000011;
  
  DDRC  |= B00111111; // pins 14-19 (A0 - A5) also used as digital inputs
  PORTC |= B00111111; // pulled-up to +VCC via internal 100k resistors
  
  DDRD  |= B11111111; // digital pins 0-7 used as inputs
  PORTD |= B11111111; // pulled-up to +VCC via internal 100k resistors
  
  wheelState[0] = B11011001; // TMX Pro wheel first data byte - constant
  wheelState[1] = B11111111; // second data byte - buttons
  wheelState[2] = B11111111; // third data byte - buttons
  wheelState[3] = B11111111; // this and below - not used, but wheelbase reads all 8 bytes...
  wheelState[4] = B11111111;
  wheelState[5] = B11111111;
  wheelState[6] = B11111111;
  wheelState[7] = B11111111;
  
  /* Wheelbase acts as SPI Master device, Arduino as SPI Slave.
   *  SPCR SPI control Register
   *  SPIE     // Enables the SPI interrupt when 1
   *  SPE      // Enables the SPI when 1
   *  DORD     // MSB first
   *  MSTR     // Clear MSTR (MSTR = Master)
   *  CPOL     // SPI mode 1 (CPOL|CPHA 0/1) - Clock Polarity - see Atmega8a datasheet 40001974A-page 179
   *  CPHA     // Clock Phase
   *  SPR1     // SPI Clock Rate Select - Master only
   *  SPR0     // SPI Clock Rate Select - Master only
   *   1    1   0    0    0    1??  x    x
   *  SPIE SPE DORD MSTR CPOL CPHA SPR1 SPR0   */
  SPCR = B11000000;      // SPI Slave setup
  pinMode(MISO, OUTPUT);


  /* We use pin 10 (SS) to identify when SPI transfer starts (wheelbase sets it to LOW)
   * and when it ends (wheelbase sets it to HIGH and we reset pos counter + prepare SPDR for next transfer)
   * Here we enable external interrupt on pin 10 - via Pin Change Interrupt.
   *
   * https://www.teachmemicro.com/arduino-interrupt-tutorial/
   * Pin Change Interrupt services
   * PCICR = x x x x x PCIE2 PCIE1 PCIE0 - enable PCINT group. PCIE0 = PORTB pins group (PCINT0-PCINT7 = PB0-PB7 = pins 8-13 + XTAL), PCIE1 = PORTC group, PCIE2 = PORTD group.
   * 
   * PCMSK0 = PCINT7 PCINT6 PCINT5 PCINT4 PCINT3 PCINT2 PCINT1 PCINT0 - those are PORTB PB0-PB7 (digital pins 8 - 13, XTAL1, XTAL2)
   * PCMSK1 = x PCINT14 PCINT13 PCINT12 PCINT11 PCINT10 PCINT9 PCINT8 - those are PORTC PC0-PC6 (A0-A5, Reset)
   * PCMSK2 = PCINT23 PCINT22 PCINT21 PCINT20 PCINT19 PCINT18 PCINT17 PCINT16 - those are PORTD PD0-PD7 (digital pins 0 - 7) */
  PCMSK0 = B00000100;    // Only react to PCINT2 = PB2 = D10 (SS pin) (when signal on this pin changes 0->1 OR 1->0 - interrupt happens)
  PCIFR = B00000000;     // clear all interrupt flags
  PCICR = B00000001;     // Enable PCIE0 = PORTB group (PCINT0 - PCINT7 = digital pins 8-13)

  sei();                 // enable global interrupts
}

// External interrupt handler for whole PCIE0 group. Prepare for next SPI communication
ISR (PCINT0_vect) {
  if (PINB2) {
    SPDR = wheelState[0]; // load first byte into SPI data register
    pos = 1;
  }
}

// SPI interrupt routine - this transfers data to wheelbase byte-by-byte
ISR (SPI_STC_vect) {
  SPDR = wheelState[pos++]; // load the next byte to SPI output register and return.
}

void loop() {
  // scan button presses and save that to wheelState array. Data transfer to wheelbase is interrupt-driven - see above.
  // wheelState[0] - always constant for TMX Pro wheel
  wheelState[1] = PIND; // read 8 buttons from PORTD
  wheelState[2] = ((PINB & B00000011) << 6) | (PINC & B00111111); // take bits 0,1 from PORTB + bits 0-5 from PORTC
}

I connected a cheap rotary encoder to GND, D3 and D7. The problem is it's giving me a value in both directions when turning either CW or CCW. I thought it's a faulty encoder but tested few units and they all behave the same way.
It might be that the reason is poor quality of the encoder, but I used exactly same units on Arduino Leonardo with this code (it's a button box you connect to PC), and had no such issues (ran out of char for message length, will put the code in first reply).

The button box code (second one) is perfectly clear for me, unfortunately the emulator (first code) is just a black magic and I have no idea what makes my rotary encoder malfunction in one case and work perfectly fine in another.
I would be extremely grateful for your help!

Here's the code for button box where the rotary encoder works fine:

//BUTTON BOX 
//USE w ProMicro
//Tested in WIN10 + Assetto Corsa
//AMSTUDIO
//20.8.17

#include <Keypad.h>
#include <Joystick.h>

#define ENABLE_PULLUPS
#define NUMROTARIES 3
#define NUMBUTTONS 16
#define NUMROWS 4
#define NUMCOLS 4


byte buttons[NUMROWS][NUMCOLS] = {
  {0,1,2,3},
  {4,5,6,7},
  {8,9,10,11},
  {12,13,14,15},
};

struct rotariesdef {
  byte pin1;
  byte pin2;
  int ccwchar;
  int cwchar;
  volatile unsigned char state;
};

rotariesdef rotaries[NUMROTARIES] {
  //{0,1,24,25,0},
  {2,3,26,27,0},
  {4,5,28,29,0},
  {6,7,30,31,0},
};

#define DIR_CCW 0x10
#define DIR_CW 0x20
#define R_START 0x0

#ifdef HALF_STEP
#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
#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

byte rowPins[NUMROWS] = {21,20,19,18}; 
byte colPins[NUMCOLS] = {15,14,16,10}; 

Keypad buttbx = Keypad( makeKeymap(buttons), rowPins, colPins, NUMROWS, NUMCOLS); 

Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, 
  JOYSTICK_TYPE_JOYSTICK, 32, 0,
  false, false, false, false, false, false,
  false, false, false, false, false);

void setup() {
  Joystick.begin();
  rotary_init();
  Serial.begin(9600);
  Serial1.begin(38400);}

void loop() { 

  CheckAllEncoders();

  CheckAllButtons();

  //Serial1 is the physical Serial Connections on TX and RX pins
  if (Serial1.available()) Serial.write(Serial1.read());

  // Serial is from my understanding the virtual connection with computer via USB 
  if (Serial.available()) Serial1.write(Serial.read());
}

void CheckAllButtons(void) {
      if (buttbx.getKeys())
    {
       for (int i=0; i<LIST_MAX; i++)   
        {
           if ( buttbx.key[i].stateChanged )   
            {
            switch (buttbx.key[i].kstate) {  
                    case PRESSED:
                    case HOLD:
                              Joystick.setButton(buttbx.key[i].kchar, 1);
                              break;
                    case RELEASED:
                    case IDLE:
                              Joystick.setButton(buttbx.key[i].kchar, 0);
                              break;
            }
           }   
         }
     }
}


void rotary_init() {
  for (int i=0;i<NUMROTARIES;i++) {
    pinMode(rotaries[i].pin1, INPUT);
    pinMode(rotaries[i].pin2, INPUT);
    #ifdef ENABLE_PULLUPS
      digitalWrite(rotaries[i].pin1, HIGH);
      digitalWrite(rotaries[i].pin2, HIGH);
    #endif
  }
}


unsigned char rotary_process(int _i) {
   unsigned char pinstate = (digitalRead(rotaries[_i].pin2) << 1) | digitalRead(rotaries[_i].pin1);
  rotaries[_i].state = ttable[rotaries[_i].state & 0xf][pinstate];
  return (rotaries[_i].state & 0x30);
}

void CheckAllEncoders(void) {
  for (int i=0;i<NUMROTARIES;i++) {
    unsigned char result = rotary_process(i);
    if (result == DIR_CCW) {
      Joystick.setButton(rotaries[i].ccwchar, 1); delay(50); Joystick.setButton(rotaries[i].ccwchar, 0);
    };
    if (result == DIR_CW) {
      Joystick.setButton(rotaries[i].cwchar, 1); delay(50); Joystick.setButton(rotaries[i].cwchar, 0);
    };
  }
}