Reading pins directly (atomic read)

Hi all,

Currently I'm trying to read out a linear displacement encoder with an Arduino. However, this reading is done with interrupts, so it has to be done as quickly as possible. Furthermore, I think it is a good thing to read both inputs (it's a quadrature encoder) at the same time, so I'll have to use the AVR registers directly. However, I've got a question about reading those registers.

When doing an analog read, the measurement takes quite some time, so you have to start the measurement and read it after it is complete. It this true with digital inputs as well? I read the AVR datasheet, and all it says is that there is a delay of 1 clock cycle. However, it doesn't say whether or not you have to tell in advance that you want to read out the digital pin.

TL;DR So, basically my question is: are the digital input registers (PINX) updated automatically every clock cycle, or do I have to trigger an update manually?

PINX are updated automatically as you put it. There is no conversion circuitry to start

If you really are using a quadrature encoder then you can not read them at the same time and expect it to work.

Mark

nilton61:
PINX are updated automatically as you put it. There is no conversion circuitry to start

OK, that was what I wanted to hear. Thanks!

holmes4:
If you really are using a quadrature encoder then you can not read them at the same time and expect it to work.

Why not? Am I missing something?

The quadrature encoder has two outputs, I was thinking of hooking them up on two different external interrupt pins that share the same bus (so I can read them out at once). I'll write a ISRs for both (duplicate), which will check which way the change has been by reading the inputs and checking whether this change is forward or backward. By reading both pins, I can even keep up if both pins have changed once while processing the ISR (which can happen).

Of course you can read the pins for an encoder that way.
In this program i’m reading two encoders by polling, it has been testet for pulse frequencies up to 40khz

#include <TimerOne.h>

const int outPwm = 9;//pin for speed control
const int outDir = 8;//pin for direction control
const int signalPin = 12;//pin for signaling and triggering

//constants for PID calculation
const unsigned long samplePeriod = 400;//time between pid calculations in us
const long kp = 12;//proportional gain
const long ki = 3;//integral gain
const long kd = 2700;//derivative gain
const byte shift = 2;//for shifting/scaling output value
const long outMax = (1024<<shift)-1;//keep max output <=1023
const byte iShift = 11;//right shift for i term
const long iMax = (1024<<(iShift+shift))-1;//keep i term contribution <=1023 and avoid windup
const int stepFrame = 1500;//Number of values for step response


//variables for PID calculation
volatile long iTerm;//summing up integral error
volatile long lastPos;//for calculation of derivative
volatile int output;//output to speedcontrol 
enum {FWD, REW} dir;

//data for position calculation
const char encTable[16] ={0, 1, -1, -0, -1, 0, -0, 1, 1, -0, 0, -1, -0, -1, 1, 0};//gives -1, 0 or 1 depending on encoder movement
volatile byte encState;//remembering the encoder output and acting as index for encTable[]
volatile byte stepState;//for edge detection on step input = D10
volatile byte inp;//for reading encoder pins
volatile byte setInp;//input signals from second encoder
volatile byte setState;//remembering setpoint encoder state
volatile long setPos;//position setpoint, updated by second encoder
volatile long actPos;//actual position from encoder
volatile long error;
volatile long dInput;
volatile byte sched;//for scheduling calculations

unsigned long t;//timekeeper

void setup(){
  pinMode(outPwm, OUTPUT);
  pinMode(outDir, OUTPUT);
  pinMode(signalPin, OUTPUT);
  Timer1.initialize(samplePeriod);
  Timer1.attachInterrupt(doPID);
  Timer1.pwm(outPwm, 0);
}//setup()

void loop(){
  while(1){
    PINB |= (1<<4);//toggle signal pin
    //aquire input values
    inp = PIND;//read inputs 0..7
    encState = ((encState<<2)|((inp>>2)&3))&15;//use encoder bits and last state to form index
    actPos += encTable[encState];//update actual position on encoder movement
    setState = ((setState<<2)|((inp>>4)&3))&15;//use encoder bits and last state to form index
    setPos += encTable[setState];
  }//while(1)
}//loop()

void doPID(){
  /*Compute all the working error variables*/
  error = setPos-actPos;
  iTerm += (ki*error);
  if(iTerm>iMax) iTerm = iMax;
  else if(iTerm<-iMax) iTerm = -iMax;
  dInput = (actPos-lastPos);
   
  /*Compute PID Output*/
  output = kp*error+(iTerm>>iShift)-kd*dInput;
  if(output < 0){
    dir = REW;
    output = -output;
  }else dir = FWD;
  //if(output<0)
  if(output > outMax) output = outMax;  
  Timer1.setPwmDuty(outPwm, output);
  digitalWrite(outDir, dir);
  lastPos = actPos;
}//doPID()

A good explanation of nilton61's ISR can be found at:

Actually, the isr is doing the pid because i needed consistent timing so the encoders are polled. But thank you for the link, i worked it out myself but its nice to refer to something when helping others.

nilton61:
Actually, the isr is doing the pid because i needed consistent timing so the encoders are polled. But thank you for the link, i worked it out myself but its nice to refer to something when helping others.

That’s not likely to work well… On a 16MHz Arduino, a PID update takes several mSec - MORE Than enough time to lose encoder counts, unless you’re either moving very slowly, or using low-res encoders.

Regards,
Ray L.

@Ray: I see a lot of Arduino circuits that use the KY-040 encoder, probably because it's cheap. It has 20 pulses (i.e., every 18 degrees) on a full rotation. If rotating by hand, do you think timing will be an issue?

econjack:
@Ray: I see a lot of Arduino circuits that use the KY-040 encoder, probably because it's cheap. It has 20 pulses (i.e., every 18 degrees) on a full rotation. If rotating by hand, do you think timing will be an issue?

At that resolution, rotating by hand, you'll be fine. I though we were talking about an encoder on a motor or other rotating machine. But, if that is a mechanical encoder, you'll need to de-bounce the outputs. Perhaps it's optical?

Regards,
Ray L.

The OP may be rotating by a method other than by hand. However, I have a circuit that rotates by hand and so far I don't see any problems, but was wondering if the potential was there. Thanks for the help.

Well, actually, it is a linear encoder. Resolution 10µm, speed probably somewhere around 5cm/s max. I might need some debouncing indeed, but I'll see that soon enough.

I had bouncing problems, but put a couple of 0.1uF caps across the data and clock lines (right on the encoder) to ground. I'm no EE guy, but it seems to work fine.

RayLivingston:
That's not likely to work well.... On a 16MHz Arduino, a PID update takes several mSec - MORE Than enough time to lose encoder counts, unless you're either moving very slowly, or using low-res encoders.

Regards,
Ray L.

The PID is interrupt driven with sample period 400us using integer calculations. One encoder is manually moved right now. It shall be an input later. The other is a dc motor controlled by the pid output. Everything works excellent up to pulse frequencies of about 40kHz

nilton61:
The PID is interrupt driven with sample period 400us using integer calculations. One encoder is manually moved right now. It shall be an input later. The other is a dc motor controlled by the pid output. Everything works excellent up to pulse frequencies of about 40kHz

Ah, if you're using an integer PID it will be much faster. The standard Arduino PID is floating point.

Where did you find the integer one? Does it use longs?

Regards,
Ray L.

I wrote it myself based on this article which is basically a explanation of the PID library. Yes, it uses longs, have a look at the code in answer#4