Skip to content

Instantly share code, notes, and snippets.

@gregmankes
Last active November 5, 2016 04:51
Show Gist options
  • Save gregmankes/2e46dd03448ce5a0f56e187dce0e5dbb to your computer and use it in GitHub Desktop.
Save gregmankes/2e46dd03448ce5a0f56e187dce0e5dbb to your computer and use it in GitHub Desktop.
A simple PI controller for a motor and encoder combination
/* interrupt routine for Rotary Encoders
tested with Noble RE0124PVB 17.7FINB-24 http://www.nobleusa.com/pdf/xre.pdf - available at pollin.de
and a few others, seems pretty universal
The average rotary encoder has three pins, seen from front: A C B
Clockwise rotation A(on)->B(on)->A(off)->B(off)
CounterCW rotation B(on)->A(on)->B(off)->A(off)
and may be a push switch with another two pins, pulled low at pin 8 in this case
raf@synapps.de 20120107
*/
// usually the rotary encoders three pins have the ground pin in the middle
enum PinAssignments {
encoderPinA = 2, // rigth
encoderPinB = 3, // left
clearButton = 8 // another two pins
};
int kp = 10;
int ki = 10;
byte u =0;
double dt = .001;
double Ii = 0;
double Il = 1;
double ei = 1;
double dspeed = 30;
double p = 0;
const int AIA = 11; // (pwm) pin 9 connected to pin A-IA
const int AIB = 5; // (pwm) pin 5 connected to pin A-IB
byte speed = 0; // change this (0-255) to control the speed of the motors
volatile int encoderPos = 0; // a counter for the dial
unsigned int lastReportedPos = 1; // change management
static boolean rotating=false; // debounce management
// interrupt service routine vars
boolean A_set = false;
boolean B_set = false;
int flag, count;
double time, f, rps;
void setup() {
pinMode(encoderPinA, INPUT);
pinMode(encoderPinB, INPUT);
pinMode(clearButton, INPUT);
// turn on pullup resistors
digitalWrite(encoderPinA, HIGH);
digitalWrite(encoderPinB, HIGH);
digitalWrite(clearButton, HIGH);
// encoder pin on interrupt 0 (pin 2)
attachInterrupt(0, doEncoderA, CHANGE);
// encoder pin on interrupt 1 (pin 3)
attachInterrupt(1, doEncoderB, CHANGE);
pinMode(AIA, OUTPUT); // set pins to output
pinMode(AIB, OUTPUT);
// initialize Timer1
cli(); // disable global interrupts
TCCR1A = 0; // set entire TCCR1A register to 0
TCCR1B = 0; // same for TCCR1B
// set compare match register to desired timer count:
OCR1A = 249;
// turn on CTC mode:
TCCR1B |= (1 << WGM12);
// Set CS10 and CS12 bits for 64 prescaler:
TCCR1B |= (1 << CS10);
//TCCR1B |= (1 << CS11);
TCCR1B |= (1 << CS11);
// enable timer compare interrupt:
TIMSK1 |= (1 << OCIE1A);
// enable global interrupts:
sei();
Serial.begin(9600); // output
}
// main loop, work is done by interrupt service routines, this one only prints stuff
void loop() {
rotating = true; // reset the debouncer
if (lastReportedPos != encoderPos) {
// Serial.print("Index:");
// Serial.println(encoderPos, DEC);
lastReportedPos = encoderPos;
}
if (digitalRead(clearButton) == LOW ) {
encoderPos = 0;
}
ei = dspeed - f;
Integral();
Proportional();
u = (ki*Ii) + p;
forward();
Serial.println(f);
}
void Integral(){
Ii = Il + (dt*ei);
Il = Ii;
}
void Proportional(){
p = ei*kp;
}
void forward()
{
analogWrite(AIA, u);
analogWrite(AIB, 0);
}
// Interrupt on A changing state
void doEncoderA(){
// debounce
if ( rotating ) delay (1); // wait a little until the bouncing is done
// Test transition, did things really change?
if( digitalRead(encoderPinA) != A_set ) { // debounce once more
A_set = !A_set;
// adjust counter + if A leads B
if ( A_set && !B_set ){
encoderPos += 1;
flag = 1;
}
rotating = false; // no more debouncing until loop() hits again
}
}
// Interrupt on B changing state, same as A above
void doEncoderB(){
if ( rotating ) delay (1);
if( digitalRead(encoderPinB) != B_set ) {
B_set = !B_set;
// adjust counter - 1 if B leads A
if( B_set && !A_set ){
encoderPos -= 1;
flag = 1;
}
rotating = false;
}
}
ISR(TIMER1_COMPA_vect){
if(flag == 0){
count++;
}
else{
time = (double)count*.001;
f = 1/time;
rps = f/20;
count = 0;
flag = 0;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment