Adapting code to directly access digital ports

I'm trying to alter some quadrature encoder code I found online to directly access the digital ports as the resolution of the encoder I'm using is too high to use the digitalRead / digitalWrite functions.

I'm very new to this so I'm probably making a very obvious mistake.

This is the code I'm trying to adapt:

#define encoder_a 2 //keep this on and interrupt pin
#define encoder_b 3 //keep this on and interrupt pin
#define motor_step 4 //can be any pin
#define motor_direction 5 //can be any pin

#include <delay.h>
volatile long motor_position, encoder;

void setup () {
  //set up the various outputs
  pinMode(motor_step, OUTPUT);
  pinMode(motor_direction, OUTPUT);
  
  // then the encoder inputs
  pinMode(encoder_a, INPUT);
  pinMode(encoder_b, INPUT);
  // enable pullup as we are using an open collector encoder
  digitalWrite(encoder_a, HIGH); 
  digitalWrite(encoder_b, HIGH); 
  
  // encoder pin on interrupt 0 (pin 2)
  attachInterrupt(0, encoderPinChangeA, CHANGE);
  // encoder pin on interrupt 1 (pin 3)
  attachInterrupt(1, encoderPinChangeB, CHANGE);
  encoder = 0; //reseet the encoder position to 0
}

void loop() {
  //do stuff dependent on encoder position here
  //such as move a stepper motor to match encoder position
  //if you want to make it 1:1 ensure the encoder res matches the motor res by dividing/multiplying
  if (encoder > 0) {
    digitalWrite(motor_direction, HIGH);// move stepper in reverse
    digitalWrite(motor_step, HIGH);
    digitalWrite(motor_step, LOW);
    delayMicroseconds(200); //_delay_us(200); //modify to alter speed
    motor_position++;
    encoder = 0; //encoder--;
  }
  else if (encoder < 0) {
    digitalWrite (motor_direction, LOW); //move stepper forward
    digitalWrite (motor_step, HIGH);
    digitalWrite (motor_step, LOW);
    delayMicroseconds(200); //_delay_us(200); //modify to alter speed
    motor_position--;
    encoder = 0; //encoder++;
  }
}

void encoderPinChangeA() {
  if (digitalRead(encoder_a)==digitalRead(encoder_b)) {
    encoder--;
  }
  else{
      encoder++;
  }
}

void encoderPinChangeB() {
  if (digitalRead(encoder_a) != digitalRead(encoder_b)) {
    encoder--;
  }
  else {
    encoder++;
  }
}

And this is my new version:

volatile long motor_position, encoder;

void setup () {

  DDRD = B00110000; 
  PORTD = B00001100; 

  attachInterrupt(0, encoderPinChangeA, CHANGE);
  attachInterrupt(1, encoderPinChangeB, CHANGE);
  encoder = 0; 
}

void loop() {

  if (encoder > 0) {
    
    PORTD = B00110000;
    PORTD = B00100000;
    motor_position++;
    encoder = 0; 
  }
  else if (encoder < 0) {
    
    PORTD = B00010000;
    PORTD = B00000000;
    motor_position--;
    encoder = 0; 
  }
}

void encoderPinChangeA() {
  if (PIND0==PIND1) {
    encoder--;
  }
  else{
      encoder++;
  }
}

void encoderPinChangeB() {
  if (PIND0!=PIND1) {
    encoder--;
  }
  else {
    encoder++;
  }
}

I believe the problem is in the void loop.

Can anyone spot any glaring errors? :blush:

  DDRD = B00110000; 
  PORTD = B00001100;

Don't do this. It isn't causing you problems in this code I don't think, but it is a dangerous practice. This sets every bit in the direction register. What if one of those other pins was set to output for another piece of code, then you just undid that.

It would be better to use |= (OR equals). By OR'ing in the bits, you leave any other bits unchaged.

Like this:

  DDRD |= B00110000;

To clear a bit, use &=~ (AND equal NOT).

The other problem is here:

if (PIND0==PIND1)

This is equivalent to someone making this common mistake:

if (buttonPinNumber == HIGH)

Where they compare the pin number instead of reading the pin. To read a pin you have to use a bitmask and read the whole port. You AND in the bitmask to cut out just the bit you need.

Thank you for your reply Delta_G.

Could you provide an example of how to do the last bit? It's not immediately clear to me how to do it.

  if (PIND & 0b00010000)
  {
    ...

I like to use the digitalPinToXXXX macros when I'm doing port manipulation. That way if I mae a pin change later I don't have to go find each time I used it int he code.

Here is some code that handles a rotary encoder on one of my projects.

At Global Scope:

const byte intPin = 2;
const byte bPin = 4;

byte intMask;
byte bMask;

volatile byte *intReg;
volatile byte *bReg;

Then in an init method those get values.

void initEncoder(){
  pinMode(intPin, INPUT_PULLUP);
  pinMode(bPin, INPUT_PULLUP);
  bMask = digitalPinToBitMask(bPin);
  bReg = portInputRegister(digitalPinToPort(bPin));  
}

I use pinMode here because this isn't the part where it needs to be fast.

Here is my interrupt handler. If you want to catch all 4 transitions, then you'd need to look at the other pin as well. But this gets me one increment or decrement per "click" of my knob.

void ISR_encoder_handler(){
  //  My encoder has a full cycle of 4 transitions per click. 
  //  I only want one increment or decrement per click
  //  So we are only looking at one transition
  //  We're only using the falling edge on the interrupt pin.
  //  So we know intPin is LOW.
  //  If bPin is HIGH then they're different so decrement

  if(*bReg & bMask){   
    encoderCounter--;
  } 
  else {
    encoderCounter++;
  }
}

EDIT: Here's where I attach the interrupt. Just for completenes.

void encoderOn(){
  attachInterrupt(0, ISR_encoder_handler, FALLING);
  encoderCounter = 0;
  encoderIsOn = true;
}

MarkT:

  if (PIND & 0b00010000)

{
    ...

Am I correct in saying that this reads pin 5?

I want to know when Pins 0 and 1 are either the same or different so can I still use == and !=? like this (below)?

if ((PIND & 0b00000001)==(PIND & 0b00000010))
  {
    encoder--;
  }
  else{
      encoder++;
  }
}

void encoderPinChangeB() {
  if ((PIND & 0b00000001)!=(PIND & 0b00000010))
  {
    encoder--;
  }
  else {
    encoder++;
  }
}

Delta_G what you have posted there is beyond my current understanding so I don't know how to implement it.

What I posted is exactly what you have here except I have a variable to hold the "PIND" value called bReg and I have a varable to hold the "0b00000001" part called bMask.

And I used the appropriate macros to get those values to the variables so if I ever need to change the pin I'm using I only have to change one line of code.

if ((PIND & 0b00000001)==(PIND & 0b00000010))

{
   encoder--;
 }
 else{
     encoder++;
 }
}

No, this doesn't work like you want. This will seem to work if they are both zero but it will fail when one or both are high. The reason why it works in the previous code, with only one pin being tested, is that false is represented as a zero and true is any non-zero value. It doesn't care if the answer is 1 or 2 or 20000 that is all taken as true by the implicit conversion to boolean.

There's a lot of ways to do this:

//double-negative !! always converts zero to false and non-zero to true
if (!!(PIND & 0b00000001)==!!(PIND & 0b00000010)) 

//since we don't actually care if it's on or off, just different, a single negative works too
if (!(PIND & 0b00000001)==!(PIND & 0b00000010))  

//this is probably the best way of writing out our real intention while still using the binary magic numbers
if ((PIND & 0b00000001)&&!(PIND & 0b00000010) || !(PIND & 0b00000001)&&(PIND & 0b00000010))
/*Now let's do it without using magic numbers...*/
#define encoder_a 2 //keep this on and interrupt pin
#define encoder_b 3 //keep this on and interrupt pin
  bMaskA = digitalPinToBitMask(encoder_a );
  bRegA = portInputRegister(digitalPinToPort(encoder_a)); 
  bMaskB = digitalPinToBitMask(encoder_b);
  bRegB = portInputRegister(digitalPinToPort(encoder_b)); 

if ((bRegA & bMaskA)&&!(bRegB & bMaskB ) || !(bRegA & bMaskA)&&(bRegB & bMaskB))
/*You know, digitalRead() is not forbidden inside an interrupt routine. 
This is readable, maintainable and portable:*/

if(digitalRead(encoder_a) == digitalRead(encoder_b))

MorganS - Thank you! I've tried to use the double negative for now as it seems the simplest of the bunch!

here is the code as it stands:

volatile long motor_position, encoder;

void setup () {

  DDRD = B00110000; 
  PORTD = B00001100; 

  attachInterrupt(0, encoderPinChangeA, CHANGE);
  attachInterrupt(1, encoderPinChangeB, CHANGE);
  encoder = 0; 
}

void loop() {

  if (encoder > 0) {
    
    PORTD = B00110000;
    PORTD = B00100000;
    motor_position++;
    encoder = 0; 
  }
  else if (encoder < 0) {
    
    PORTD = B00010000;
    PORTD = B00000000;
    motor_position--;
    encoder = 0; 
  }
}

void encoderPinChangeA() {
  if (!!(PIND & 0b00000001)==!!(PIND & 0b00000010)) 
//  (PIND0!=PIND1)
  {
    encoder--;
  }
  else{
      encoder++;
  }
}

void encoderPinChangeB() {
  if (!!(PIND & 0b00000001)!=!!(PIND & 0b00000010)) 
//  (PIND0!=PIND1) 
  {
    encoder--;
  }
  else {
    encoder++;
  }
}

So I've replaced (PIND0!=PIND1) with (!!(PIND & 0b00000001)==!!(PIND & 0b00000010))

and (PIND0!=PIND1) with (!!(PIND & 0b00000001)!=!!(PIND & 0b00000010))

is that correct?

It has complied OK but for some reason the stepper motor is turning very slowly without the encoder turning at all?

Delta_G - I've managed to cut and paste your code into my sketch and it has compiled ok. I'm slightly confused which pins its using though?

Unfortunately I'm on a very steep learning curve here.

I've tried to use the double negative for now as it seems the simplest of the bunch!

Huh!

Using the bitRead() function will gain most of the speed benefit of the direct port manipulation over digitalRead(). In fact, in my benchmark testing, direct port manipulation was only faster when reading both pins at once with a mask like b00000011 and then using the result to build full quadrature past and present states.

if(bitRead(PIND,0)==bitRead(PIND,1)
  {
    encoder--;
  }
  else
 {
    encoder++;
  }

Edit: Fast and simple would be

if(PIND & B00000011 == B00000000 || B00000011)
{
encoder --;
}
else
{
encoder++;
}