Log in

View Full Version : Arduino and kop encoder


SeanPerez
24-04-2012, 21:44
Has any one used the encoder included in the KOP with the arduino? can someone give me the code for it so i can read the rpm?

Thanks

billbo911
24-04-2012, 22:43
Has any one used the encoder included in the KOP with the arduino? can someone give me the code for it so i can read the rpm?

Thanks

I can give you code, but wouldn't you learn more by doing the research and writing your own?

Let me push you in the right direction and see how far you can go. Don't be surprised if you learn to fly along the way.

The first thing you want to learn is how to use the "interrupt" input(s), more specifically, "External Interrupt". One channel of the encoder will go to that input. The other channel will be used to determine direction.
Your interrupt service routine will sample that input. High would be forward, otherwise it's reverse.

RPM after that is simply a little math.

OK, that's enough hints for now. Let us know what you find. I'll always help you out if you just ask.

taichichuan
25-04-2012, 16:15
We are using a Grayhill 63R64 encoder with an Arduino mega for the RPMs of our shooter. We opted for that encoder instead of the KOP because the PPR on the KOP encoder is so high that we were concerned at overrunning the Arduino. Further testing shows that the Arduino is easily capable of 30,000 PPS. So, that concern turned out to be bogus. But, using the KOP encoder would require another gearbox up high on our bot that would adversely effect the COG while trying to balance or going over the bump.

The Arduino mega can source the 5V for the Grayhill or the KOP encoder with no problem. We bring the A channel back from the encoder to Digital Pin 2 on the Arduino. The magic you're looking for can be found in the "attachInterrupt" command. Use it to increment a counter on the rising edge of the encoder pulse and then periodically read the pulse count and do your math for the RPM.

We also added the "SimpleTimer" library from the Arduino playground site so we could get updates every 100 ms. This library attaches a timer interrupt to a function so it's called at the interval you desire rather than simply polling the counter.

Our code sends it's output via an Ethernet shield direct to the cRio using UDP. We found some really interesting gotchas with the cRio robot code in that you must read the packets or face the possibility of a robot lockup. So, you'll need a blocking thread to read the socket constantly. Our robot code is written in C++, so creating a thread was pretty easy.

In addition, our Arduino is also sampling an absolute encoder on our ball shooting angle device. No problem doing both functions and sending out the results.

I'd say that the entire Arduino program for both sensors and the Ethernet output is maybe 50 lines of code total.

SeanPerez
25-04-2012, 18:31
how do i use the simpletimer and the attachinterrupt command?

Michael Hill
25-04-2012, 18:46
Too funny. I'm literally doing this as I speak. Here's my code.

// Controlling a servo position using a potentiometer (variable resistor)
// by Michal Rinott <http://people.interaction-ivrea.it/m.rinott>

#include <Servo.h>

Servo motor1; // create servo object to control a motor
Servo motor2; // ditto

int potpin = 0; // analog pin used to connect the potentiometer
int val; // variable to read the value from the analog pin
volatile byte rpmcount;
unsigned int rpm;
unsigned long timeold;


void setup()
{
motor1.attach(9); // attaches the motor 1 on pin 9 to the servo object
motor2.attach(10); // attaches the motor 2 on pin 10 to the servo object
attachInterrupt(0, rpm_fun, RISING); // attaches the encoder on (digital pin 2)
rpmcount = 0;
rpm = 0;
timeold = 0;
digitalWrite(2, HIGH);
Serial.begin(9600);
}

void loop()
{
val = analogRead(potpin); // reads the value of the potentiometer (value between 0 and 1023)
val = map(val, 0, 1023, 0, 179); // scale it to use it with the servo (value between 0 and 180)

if (rpmcount >= 0) {
rpm = 30*1000/(millis() - timeold)*rpmcount;
timeold = millis();
rpmcount = 0;
Serial.print("RPM: ");
Serial.print(rpm);
Serial.print(" Pot: ");
Serial.print(val);
Serial.print("\n");
}

motor1.write(val); // sets the motor speed according to the scaled value
motor2.write(val);
}

void rpm_fun()
{
rpmcount++;
}

Note that you either have to use a pullup resistor or use the digitalWrite(2, HIGH) command in the setup section (as I did). This uses the internal pull-up resistor on that pin. Also, note that I'm controlling 2 motors with a potentiometer. That's what's basically happening here.

Michael Hill
25-04-2012, 18:47
You have to treat victors as servos rather than directly feed PWM. Victors actually read PPM, so you have to treat them as servos (which read PPM) in Arduino.

SeanPerez
25-04-2012, 18:55
do i have to divide the rpm by 360 since my encoder has 360 positions in 1 rotation?

SeanPerez
25-04-2012, 19:07
i noticed that your code resets back to zero after 60,000 rpm. it is reading much faster that actually spinning. what do i do to make it the actual rpm

My encoder is the kop encoder and it has 360 positions in 1 rotation

Michael Hill
25-04-2012, 19:28
do i have to divide the rpm by 360 since my encoder has 360 positions in 1 rotation?

Actually, I just modified my code. I realized I had a mistake:

// Controlling a servo position using a potentiometer (variable resistor)
// by Michal Rinott <http://people.interaction-ivrea.it/m.rinott>

#include <Servo.h>

Servo motor1; // create servo object to control a motor
Servo motor2; // ditto

int potpin = 0; // analog pin used to connect the potentiometer
int val; // variable to read the value from the analog pin
float dt;
volatile byte rpmcount;
float rpm;
unsigned long timeold;


void setup()
{
motor1.attach(9); // attaches motor 1 on pin 9 to the servo object
motor2.attach(10); // ditto
attachInterrupt(0, rpm_fun, RISING);
rpmcount = 0;
rpm = 0;
timeold = 0;
digitalWrite(2, HIGH);
Serial.begin(9600);
}

void loop()
{
val = analogRead(potpin); // reads the value of the potentiometer (value between 0 and 1023)
val = map(val, 0, 1023, 0, 179); // scale it to use it with the motor (value between 0 and 180)


if (rpmcount >= 20) {
dt = (float)(millis() - timeold)/1000;
Serial.print(dt);
rpm = rpmcount/dt*60;
timeold = millis();
rpmcount = 0;
Serial.print(" RPM: ");
Serial.print(rpm);
Serial.print(" Pot: ");
Serial.print(val);
Serial.print("\n");
}

motor1.write(val); // sets the motor speed according to the scaled value
motor2.write(val);
}

void rpm_fun()
{
rpmcount++;
}

At any rate, when used as an interrupt, it only counts once per revolution.

SeanPerez
25-04-2012, 20:04
At any rate, when used as an interrupt, it only counts once per revolution.

it may only count once, but i was getting around 8000 rpm.


with your new code, the faster it spins, the lower the rpm.

Michael Hill
25-04-2012, 20:06
it may only count once, but i was getting around 8000 rpm.


with your new code, the faster it spins, the lower the rpm.

Are you looking at the first column?

If you are, disregard that. That's the "dt" value. Sorry, I didn't make it very legible. Look at the second set of numbers.

0.43 RPM: 2803.74 Pot: 82

means that 0.43 is the time between calculations, 2803.74 is the revolutions per minute, and 82 is the potentiometer value.

SeanPerez
25-04-2012, 20:18
ok now im reading the right number. but it seems the rpm is way to high then the actual rpm.

im spinning it by hand and its reading around 21,000 rpm. this is way to high. im spinning it at around 60 rpm +or- 10 rpm

Michael Hill
25-04-2012, 20:22
ok now im reading the right number. but it seems the rpm is way to high then the actual rpm.

im spinning it by hand and its reading around 21,000 rpm. this is way to high. im spinning it at around 60 rpm +or- 10 rpm

You didn't multiply it by 360 did you? If not, The arduino board MIGHT somehow me picking up every encoder tick. If that's the case, divide by 360. (Notice that 21000/360 = 58.3)

Michael Hill
25-04-2012, 20:23
It would make sense, by the way, if it is. My code is for a hall effect sensor being used as an encoder that reads a screw once per revolution.

SeanPerez
25-04-2012, 20:27
sorry if i was unclear but i was looking for code for a encoder that reads 360 positions per revolution compared to your 1 . no worries, all i did was divide your rpm by 360.

Michael Hill
25-04-2012, 20:29
sorry if i was unclear but i was looking for code for a encoder that reads 360 positions per revolution compared to your 1 . no worries, all i did was divide your rpm by 360.

So you got it working then? Dividing by 360 would be the solution I think.

Michael Hill
25-04-2012, 20:40
Oh, and I thought I'd caution you. There's a maximum read rate on the arduinos. It takes about 100 microseconds to take a reading, so it can read about 10,000 times/second. Since you're reading all 360 values every rotation, you've got a maximum of 27.78 rev/sec, or 1666.67 RPM. Not really sure what you're using it for, but keep that in mind.

docdavies
25-04-2012, 21:32
Thank you folks. I found this thread very informative and useful.

We are also working to "off-load" more robot functions to an Arduino Mega.

Doc

SeanPerez
25-04-2012, 23:32
Oh, and I thought I'd caution you. There's a maximum read rate on the arduinos. It takes about 100 microseconds to take a reading, so it can read about 10,000 times/second. Since you're reading all 360 values every rotation, you've got a maximum of 27.78 rev/sec, or 1666.67 RPM. Not really sure what you're using it for, but keep that in mind.


are you sure its 1666? i went up to as high as 2000 multiple times in a row

SeanPerez
25-04-2012, 23:36
i think the fastest i could spin it was 2100rpm

taichichuan
26-04-2012, 00:11
Actually, the Arduino mega maxes out at about 30,000 PPS. It can easily read 3500-5000 RPM depending on your encoder. Rather than sample at 1x/second, you might try sampling at 100x/second and adjusting the RPM accordingly. Here's an example:

/***
File : turret.pde (opens in arduino IDE www.arduino.cc)
Description : Counts digital pulse on External Interrupt 0 (Digital pin 2) for 100 mseconds and prints the value via UDP
Date : 18th March 2012
Author : Mike Anderson FRC Team #116
Contact : manderson13@cox.net
***/

// include the library code:
#include <SPI.h> // needed for Arduino versions later than 0018
#include <Ethernet.h>
#include <EthernetUdp.h> // UDP library from: bjoern@cs.stanford.edu 12/30/2008
#include <SimpleTimer.h>

// Conversion of Analog value to degrees using U.S. Digital
#define ANALOG_TO_DEGREES 2.84

// Number of pulses per revolution on the shaft encoder
#define SHAFT_ENCODER_PPR 64.0

// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network:
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(10, 1, 16, 9);
static uint8_t crio_ip[4] = { 10,1,16,2 };
unsigned int remPort = 8888;
unsigned int distance = 0;

unsigned int localPort = 8888; // local port to listen on

// buffers for receiving and sending data
char packetBuffer[UDP_TX_PACKET_MAX_SIZE]; //buffer to hold incoming packet,

// An EthernetUDP instance to let us send and receive packets over UDP
EthernetUDP Udp;

int iTestPin=13; //Pin which generates test pulses, this is not needed when actual pulse is available for measurement
int iPulsePin = 2; //Pin serving as external interrupt (INT 0) to count the pulses
static int siPulseCounter=0; //Variable keeping track of pulse counts
static int reportInterval=1;

static int multiplierFactor = 60 / reportInterval;

int sensorPin = A0; // select the input pin for the potentiometer
int sensorValue = 0; // variable to store the value coming from the sensor
int tmpDegrees = 0;
int oldDegrees = 0;
float tempFloat = 0.0;

SimpleTimer rpmTimer;
SimpleTimer hoodAngleTimer;

void sendRPM() {
tempFloat = (siPulseCounter / SHAFT_ENCODER_PPR) * 600.0;
Serial.println(siPulseCounter, DEC);
sprintf(packetBuffer, "1 %d\n\0", (int)(tempFloat));
// send a reply, to the IP address and port that sent us the packet we received
// Serial.write(packetBuffer);
Udp.beginPacket(crio_ip, remPort);
Udp.write(packetBuffer);
Udp.endPacket();

siPulseCounter=0; //Reset counter values for next minute cycle

}

void sendHoodAngle() {
sensorValue = analogRead(sensorPin);
tmpDegrees = (int) (sensorValue / ANALOG_TO_DEGREES);

sprintf(packetBuffer, "2 %d\n\0", tmpDegrees);
// send a reply, to the IP address and port that sent us the packet we received
// Serial.write(packetBuffer);
Udp.beginPacket(crio_ip, remPort);
Udp.write(packetBuffer);
Udp.endPacket();
oldDegrees = tmpDegrees;

}

void setup(void)
{

// start the Ethernet and UDP:
Ethernet.begin(mac,ip);
Udp.begin(localPort);

pinMode(iPulsePin, INPUT); // Set Pulsepin to accept inputs

digitalWrite(iPulsePin, HIGH);
pinMode(iTestPin, OUTPUT); // Test signal generator pin as output. Can be ignored if the actual digital signal is available
attachInterrupt(0, count, RISING); // Caputres external interrupt 0 at iPulsepin at rising edge and calls funtion count defined below
Serial.begin(115200); // Begin Serial communication at baudrate 115200 bps
Serial.println(" Initialising, Please wait...");
rpmTimer.setInterval(100, sendRPM);
hoodAngleTimer.setInterval(250, sendHoodAngle);

}

void loop(void)
{
rpmTimer.run();
hoodAngleTimer.run();

/*** Following 4 lines just generate pulses for testing purposes.
All the 4 lines Can be ignored if the actual digital signal is available ***/
//
// digitalWrite(iTestPin, HIGH); // sets the iTestPin ON
// delay(1); // waits for a second
// digitalWrite(iTestPin, LOW); // sets the iTestPin off
// delay(1); // waits for a second

}

void count() // Function called by AttachInterrupt at interrupt at int 0
{
siPulseCounter++; //increment the pulse count
}


/* End of turret.ino */

purduephotog
29-04-2012, 09:36
We are using a Grayhill 63R64 encoder with an Arduino mega for the RPMs of our shooter. We opted for that encoder instead of the KOP because the PPR on the KOP encoder is so high that we were concerned at overrunning the Arduino. Further testing shows that the Arduino is easily capable of 30,000 PPS. So, that concern turned out to be bogus. But, using the KOP encoder would require another gearbox up high on our bot that would adversely effect the COG while trying to balance or going over the bump.

The Arduino mega can source the 5V for the Grayhill or the KOP encoder with no problem. We bring the A channel back from the encoder to Digital Pin 2 on the Arduino. The magic you're looking for can be found in the "attachInterrupt" command. Use it to increment a counter on the rising edge of the encoder pulse and then periodically read the pulse count and do your math for the RPM.

We also added the "SimpleTimer" library from the Arduino playground site so we could get updates every 100 ms. This library attaches a timer interrupt to a function so it's called at the interval you desire rather than simply polling the counter.

Our code sends it's output via an Ethernet shield direct to the cRio using UDP. We found some really interesting gotchas with the cRio robot code in that you must read the packets or face the possibility of a robot lockup. So, you'll need a blocking thread to read the socket constantly. Our robot code is written in C++, so creating a thread was pretty easy.

In addition, our Arduino is also sampling an absolute encoder on our ball shooting angle device. No problem doing both functions and sending out the results.

I'd say that the entire Arduino program for both sensors and the Ethernet output is maybe 50 lines of code total.

You are blocking what here- or are you saying there is a sync here on this. Using java there is the observer observable classes that I found handled serial come quite nicely.

taichichuan
29-04-2012, 15:02
You are blocking what here- or are you saying there is a sync here on this. Using java there is the observer observable classes that I found handled serial come quite nicely.

We're not using Java, but rather C++. Nonetheless, the blocking is in a thread that simply reads from the UDP socket (recvfrom() call) using a blocking socket (one not marked as FIONBIO via ioctl()). No synchronization is needed because the thread gives up the CPU when there is no data to be read and the main robot code continues as normal.

HTH,

Mike