Arduino Port Manipulation - Reading Port Data

Hi guys,

I am trying to read in encoder data using an Arduino Mega 2560. I was using digitalRead() and some nested if loops to count the encoder pulses, but my data was sporadic and unreliable. My count per rotation would change with how fast my motors were rotating! Not good. I spoke to a friend very skilled in embedded systems and he told me there was no way a high-level function like digitalRead() could keep up with my encoder, especially considering I am reading in data from six encoders. He recommended I move all the encoder signals to two ports and then use a low-level Port Manipulation line of code to grab all the data.

I put the encoder signal wires on Port C, then wrote a very simple code. It turned a motor (motor six) on and spinning, used DDRC to turn Port C into all inputs and then used PINC to grab all that port data. That's it. But when we tried to port PINC to serial to make sure our data looked good, all we saw were zeros.

We double checked that the two encoder signal pins we had wired to PORT C were oscillating like they were supposed to, and they were - jumping up and down from 0 to 1 as the motor spun. We double checked that motor six's signal pins were on PORTC and they were. But we couldn't seem to grab the data using the PINC command.

I'm pretty sure we were making some ridiculously simple error that we just couldn't spot. Any idea what that error might be? The code is below. The majority of it is variable initialization; the tiny bit of port manipulation is at the very bottom. Thanks in advance for any help.

// setup ports for input; leave bits 6&7 in write... ?


//PWM pins 2,3,4,5,6,7
//Encoder pins 13,14,15,16,17,18
#include <Encoder.h>

///verified
int motor2speed = 13;
int motor1speed = 11;
int motor3speed = 12;
int motor5speed = 10;
int motor4speed = 9;
int motor6speed = 8;

int motor1dir = 38;
int motor2dir = 35;
int motor3dir = 36;
int motor4dir = 39;
int motor6dir = 40;
int motor5dir = 41;

int hefmotor6 = 2;
int hefmotor5 = 3;
int hefmotor4 = 4;
int hefmotor3 = 5;
int hefmotor2 = 6;
int hefmotor1 = 7;

int enc1 = 26;
int enc12 = 27;

int enc2 = 22;
int enc22 = 23;

int enc3 = 24;
int enc32 = 25;

int enc4 = 28;
int enc42 = 29; 

int enc5 = 32;
int enc52 = 33;

int enc6 = 30;
int enc62 = 31; 
int enc6pos = 0;
int enc6last = LOW;
int n6 = LOW;
int x6 = 0;
int y6 = LOW;

int enc4pos = 0;
int enc4last = LOW;
int n4 = LOW;
int x4 = 0;
int y4 = LOW;

int enc2pos = 0;
int enc2last = LOW;
int n2 = LOW;
int x2 = 0;
int y2 = LOW;

int enc1pos = 0;
int enc1last = LOW;
int n1 = LOW;

int enc3pos = 0;
int enc3last = LOW;
int n3 = LOW;

int enc5pos = 0;
int enc5last = LOW;
int n5 = LOW;


//*/

// define shit
int sensor = 0;

int lastp1 = PINC;
// int lastp2 = B00000000
int p1 = B00000000;
// int p2 = B00000000
int dp;

void setup(){

Serial.begin(9600);
  ///*
  //Motor PWM pins. Must be set to OUTPUT before analogwriting

  pinMode(motor6dir, OUTPUT);
  pinMode(motor4dir, OUTPUT);
  pinMode(motor5dir, OUTPUT);
  pinMode(motor1dir, OUTPUT);
  pinMode(motor3dir, OUTPUT);
  pinMode(motor2dir, OUTPUT);
  
   pinMode(motor1speed, OUTPUT);
   pinMode(motor2speed, OUTPUT);
   pinMode(motor3speed, OUTPUT);
   pinMode(motor4speed, OUTPUT);
   pinMode(motor5speed, OUTPUT);
   pinMode(motor6speed, OUTPUT);
  
  pinMode(49, OUTPUT);
  pinMode(48, OUTPUT);    
  pinMode(42, OUTPUT);
  pinMode(43, OUTPUT);
  
  pinMode(hefmotor1, INPUT); //HEF motor 6
  pinMode(hefmotor2, INPUT);
  pinMode(hefmotor3, INPUT);
  pinMode(hefmotor4, INPUT);
  pinMode(hefmotor5, INPUT);
  pinMode(hefmotor6, INPUT);
  
  pinMode(enc6, INPUT);
  pinMode(enc62, INPUT);
  pinMode(enc5, INPUT);
  pinMode(enc52, INPUT);
  pinMode(enc4, INPUT);
  pinMode(enc42, INPUT);
  pinMode(enc3, INPUT);
  pinMode(enc32, INPUT);
  pinMode(enc2, INPUT);
  pinMode(enc22, INPUT);
  pinMode(enc1, INPUT);
  pinMode(enc12, INPUT);

DDRC = B00000000;

}
void loop() {
  
  digitalWrite(motor6dir, HIGH); //sets motor six's direction
  analogWrite(motor6speed, 122); //sets motor six's speed

 byte damnport = PINC; 
  //dp = lastp1 ^ p1;

  //if (dp & 0x11000000 > 0){
    //sensor++;
  //}
    

  Serial.println(PINC, BIN);
  //lastp1 = p1;
 

}

Your're only taking a single snapshot in time.
It sounds like you're trying to count the frequency of pulses. If so, clocking one of the counters to an external source (pulses from your motor) would make more sense.

ralphd:
Your're only taking a single snapshot in time.
It sounds like you're trying to count the frequency of pulses. If so, clocking one of the counters to an external source (pulses from your motor) would make more sense.

This is in the void loop(), so shouldn't it be constantly looping and outputting the latest PINC to Serial?

Yes, I am trying to count the pulses. Once I'm seeing the right data when I call PINC, I'll write more code to actually count those pulses. Right now I'm working my way up from the very bottom. Only problem is I've gotten stuck at that first step.

How often do you want to read the port value and how long does Serial.println() take?

...R

I put the encoder signal wires on Port C, then wrote a very simple code. It turned a motor (motor six) on and spinning, used DDRC to turn Port C into all inputs and then used PINC to grab all that port data. That's it

Mega PortC start on pin 30 and ends on pin 37.Then you have this

pinMode(enc6, INPUT);
pinMode(enc62, INPUT); //You had set enc62 to int enc62 = 31 which is ok
pinMode(enc5, INPUT);//You had set enc5 to int enc5 = 32 which is ok
pinMode(enc52, INPUT)//You had set enc52 to int enc52 = 33 which is ok
pinMode(enc4, INPUT)//You had set enc4 to int enc4 = 28 which is WRONG because PINC does not own pin 28
pinMode(enc42, INPUT);//Wrong pin to
pinMode(enc3, INPUT);//Wrong pin to
pinMode(enc32, INPUT);//Wrong pin to
pinMode(enc2, INPUT);//Wrong pin to
pinMode(enc22, INPUT);//Wrong pin to
pinMode(enc1, INPUT);//Wrong pin to
pinMode(enc12, INPUT);//Wrong pin to

DDRC = B00000000;//if you had use PinMode above why now you use a realPure port manipulation?
//This set the PORT C all pins in INPUT so you wast all the PinMode above.

In you case I would prefer using the DDRC only since it's more fast and efficient and it's only one line of code.

HugoPT:

I put the encoder signal wires on Port C, then wrote a very simple code. It turned a motor (motor six) on and spinning, used DDRC to turn Port C into all inputs and then used PINC to grab all that port data. That's it

Mega PortC start on pin 30 and ends on pin 37.Then you have this

pinMode(enc6, INPUT);
pinMode(enc62, INPUT); //You had set enc62 to int enc62 = 31 which is ok
pinMode(enc5, INPUT);//You had set enc5 to int enc5 = 32 which is ok
pinMode(enc52, INPUT)//You had set enc52 to int enc52 = 33 which is ok
pinMode(enc4, INPUT)//You had set enc4 to int enc4 = 28 which is WRONG because PINC does not own pin 28
pinMode(enc42, INPUT);//Wrong pin to
pinMode(enc3, INPUT);//Wrong pin to
pinMode(enc32, INPUT);//Wrong pin to
pinMode(enc2, INPUT);//Wrong pin to
pinMode(enc22, INPUT);//Wrong pin to
pinMode(enc1, INPUT);//Wrong pin to
pinMode(enc12, INPUT);//Wrong pin to

DDRC = B00000000;//if you had use PinMode above why now you use a realPure port manipulation?
//This set the PORT C all pins in INPUT so you wast all the PinMode above.

In you case I would prefer using the DDRC only since it's more fast and efficient and it's only one line of code.

You're right. I only put the encoder six signal wires on Port C for now. I didn't want to rewire the entire robot until I knew the port manipulation was working - which it isn't right now.

I also recognize the code is a bit redundant with using both PinMode and DDRC, but this doesn't explain why I don't see anything changing when I output PORTC to serial, does it? I should see two bits in PORTC (corresponding to encoder 6 and 6-2) oscillating between 0 and 1. Instead, I just see zeros.

Shouldn't you be using INPUT_PULLUP pinMode for encoder inputs? They are normally
open-collector.

If you reduce your code to the bare minimum

void setup()
{
  Serial.begin(115200);
  DDRC = B00000000;
}

void loop() 
{  
  Serial.println(PINC, BIN);
}

Then turn the encoder manually. What do you get in the Serial monitor ?
Have the pins used for the encoder got any pull up or down resistors ?

For what it's worth, when I run the code above on a Uno and take any of the analogue pins HIGH I see the result in the Serial monitor along with noise on the other 4 analogue pins which are floating.

MarkT:
Shouldn't you be using INPUT_PULLUP pinMode for encoder inputs? They are normally
open-collector.

No, I don't. Haven't read about needing to in the encoder literature or example codes, but I can try this and see if it makes a difference.

UKHeliBob:
If you reduce your code to the bare minimum

void setup()

{
 Serial.begin(115200);
 DDRC = B00000000;
}

void loop()
{  
 Serial.println(PINC, BIN);
}


Then turn the encoder manually. What do you get in the Serial monitor ?
Have the pins used for the encoder got any pull up or down resistors ?

For what it's worth, when I run the code above on a Uno and take any of the analogue pins HIGH I see the result in the Serial monitor along with noise on the other 4 analogue pins which are floating.

The pins used for the encoder do not have any pull up or down resistors. I take the signal wires from the encoder and attach them directly the analog PORTC pins. I can't work on the robot until this weekend, but when I do I'll run this code and report back. Still, despite all the extra stuff floating around in my code (I'll definitely be cleaning it up soon), there doesn't seem to be any different between our codes. Except I notice you're running your serial at 115200 instead of the 9600 I used, why is that?

You're right, the code is yours with all the 'fluff' removed to make it easier to test. As to the serial speed, I don't see why it shouldn't run as fast as reasonably possible so I use 115200 as my default for the Serial monitor.

UKHeliBob:
You're right, the code is yours with all the 'fluff' removed to make it easier to test. As to the serial speed, I don't see why it shouldn't run as fast as reasonably possible so I use 115200 as my default for the Serial monitor.

Below is a cleaner version of the code I will test come Sunday. With no real changes I'm not too optimistic. My first thought would be that something was wrong with the encoder signal pins, but we checked them by outputting digitalRead() to serial and sure enough, they were oscillating between 0 and 1 as the motor spun. With this in mind, do you think a pullup/pulldown resistor (or lack thereof) could be the problem?

int motor2speed = 13;
int motor1speed = 11;
int motor3speed = 12;
int motor5speed = 10;
int motor4speed = 9;
int motor6speed = 8;
int motor1dir = 38;
int motor2dir = 35;
int motor3dir = 36;
int motor4dir = 39;
int motor6dir = 40;
int motor5dir = 41;

int hefmotor6 = 2;
int hefmotor5 = 3;
int hefmotor4 = 4;
int hefmotor3 = 5;
int hefmotor2 = 6;
int hefmotor1 = 7;

int enc1 = 26;
int enc12 = 27;
int enc1pos = 0;
int enc1last = LOW;
int n1 = LOW;

int enc2 = 22;
int enc22 = 23;
int enc2pos = 0;
int enc2last = LOW;
int n2 = LOW;
int x2 = 0;
int y2 = LOW;

int enc3 = 24;
int enc32 = 25;
int enc3pos = 0;
int enc3last = LOW;
int n3 = LOW;

int enc4 = 28;
int enc42 = 29; 
int enc4pos = 0;
int enc4last = LOW;
int n4 = LOW;
int x4 = 0;
int y4 = LOW;

int enc5 = 32;
int enc52 = 33;
int enc5pos = 0;
int enc5last = LOW;
int n5 = LOW;

int enc6 = 30;
int enc62 = 31; 
int enc6pos = 0;
int enc6last = LOW;
int n6 = LOW;
int x6 = 0;
int y6 = LOW;

int sensor = 0;
int lastp1 = PINC;
int p1 = B00000000;
int dp;
// int lastp2 = B00000000
// int p2 = B00000000

void setup(){
  
  Serial.begin(115200);
  //Motor PWM pins. Must be set to OUTPUT before analogwriting
  pinMode(motor6dir, OUTPUT);
  pinMode(motor4dir, OUTPUT);
  pinMode(motor5dir, OUTPUT);
  pinMode(motor1dir, OUTPUT);
  pinMode(motor3dir, OUTPUT);
  pinMode(motor2dir, OUTPUT);
  pinMode(motor1speed, OUTPUT);
  pinMode(motor2speed, OUTPUT);
  pinMode(motor3speed, OUTPUT);
  pinMode(motor4speed, OUTPUT);
  pinMode(motor5speed, OUTPUT);
  pinMode(motor6speed, OUTPUT);
  
  pinMode(49, OUTPUT);
  pinMode(48, OUTPUT);    
  pinMode(42, OUTPUT);
  pinMode(43, OUTPUT);
  
 //Hall Effect Pins
  pinMode(hefmotor1, INPUT); 
  pinMode(hefmotor2, INPUT);
  pinMode(hefmotor3, INPUT);
  pinMode(hefmotor4, INPUT);
  pinMode(hefmotor5, INPUT);
  pinMode(hefmotor6, INPUT);
  
  DDRC = B00000000;
  
  //Replaced with DDRC:
  /*pinMode(enc6, INPUT);
  pinMode(enc62, INPUT);
  pinMode(enc5, INPUT);
  pinMode(enc52, INPUT);
  pinMode(enc4, INPUT);
  pinMode(enc42, INPUT);
  pinMode(enc3, INPUT);
  pinMode(enc32, INPUT);
  pinMode(enc2, INPUT);
  pinMode(enc22, INPUT);
  pinMode(enc1, INPUT);
  pinMode(enc12, INPUT);*/

}
void loop() {
  
  digitalWrite(motor6dir, HIGH); //sets motor six's direction
  analogWrite(motor6speed, 122); //sets motor six's speed
 
  /*dp = lastp1 ^ p1;
    if (dp & 0x11000000 > 0){
    sensor++;}*/
    
  Serial.println(PINC, BIN);
  lastp1 = p1;
 
}

You typically use interrupts to count encoder pulses:

For high speed pulses you can use a hardware timer, that's what they are there for:

I have a page about reading encoders:

As others have pointed out, trying to do a serial print for every pulse will seriously slow it down. Don't do that.

Reading an encoder is a good application for using an interrupt. Trying to keep a loop fast enough to read an encoder with more than just a few counts per revolution is an exercise in futility in Arduino C. You have to account for the software overhead of the language and there is just too much going on to be able to read I/O fast enough for an encoder.

The exact routine you use to read the encoder depends on the resolution you need. There are 4 states - A on- B on, A On- B off, A off-B off, A off-B-on. With 1 interrupt (Change on Signal A) you can use these states to tell forward or reverse rotation of the encodet and a resolution of 1/2 the encoder lines.

kf2qd:
Reading an encoder is a good application for using an interrupt. Trying to keep a loop fast enough to read an encoder with more than just a few counts per revolution is an exercise in futility in Arduino C. You have to account for the software overhead of the language and there is just too much going on to be able to read I/O fast enough for an encoder.

The exact routine you use to read the encoder depends on the resolution you need. There are 4 states - A on- B on, A On- B off, A off-B off, A off-B-on. With 1 interrupt (Change on Signal A) you can use these states to tell forward or reverse rotation of the encodet and a resolution of 1/2 the encoder lines.

I ddi find this out when I started troubleshooting; however, I have six quadrature encoder gearmotors being controlled by one Arduino Mega, and the Mega doesn't have enough interrupts for 12 encoder signals wires. So I've been forced to look into workarounds.

Are you certain that even low-level port manipulation won't allow me to acquire the data fast enough? Perhaps if I read only one signal pin per motor instead of two? This would lower my resolution to 32 counts/revolution instead of the max of 64, but the robot would likely still work. The motors will be rotating at 75 RPM, or 1.25 RPS. Assuming I only use one encoder signal pin that's 32 pulses/revolution, which is 1.25*32 = 40 pulses/second. void loop() will have to have a period of 1/40 or less to catch all the pulses.

For background - I'm building a hexapod walking robot. In order to accurately control the alternating tripod gait, I need to count the encoder pulses coming off each motor.

I wish the forum didn't force a youtube hyperlink into an embedded video: http://www.youtube.com/watch?v=uHYGQ-lMXYA#t=57

Are you certain that even low-level port manipulation won't allow me to acquire the data fast enough?

I don't know enough about your data rates to be certain of anything, however interrupts are a better bet than checking multiple things in a loop. I would lose the serial print, and see how you go. Print once a second or something.