Step-by-Step Arduino

What's going on here. First off, look at the new setup and loop functions. It's what we made earlier with the array, and everything is right there with it. All of the Rotary Encoder stuff has been moved away from the main program except for the calls to attachVariable() and detachVariable().

Look at all of those function calls. There's the dot operator. (and a weird expression for the index of number[]. How do we know it's never greater than 2?)

knob.attachVariable(number[ total%3 ]);

knob is the name of our object. It is of class type QuickRotaryEncoder.

The class is defined right at the top after the includes and defines. It starts with the keyword class, then the name of the class, QuickRotaryEncoder, then a brace {.

Then we have all of our member variables. Some are public, some are private. Public variables and functions are available to everyone. These are the one's we want the user to use. The private variables and functions are to be used only within the class. You can't call them in the main program. This is all the stuff we don't want the user messing with because it will mess up the program.



After the main program you find all of the member functions. Notice that we have added the scope operator :: to all of the ones that belong to the class, but not to printMyNumbers() because is belongs to the example program.


void QuickRotaryEncoder::attachVariable(int& _var)
{
attached_val = &(_var);
attachFunction();
}


Nothing else has had to change about our functions. We just made sure they had the scope of the class we made so they will have access to the member variables.

In the class definition, we also made a couple of member functions static. Namely, doEncoderA() for the reasons discussed above, and setupPins(byte, byte) for safety reasons since it only deals with static member variables.

We have to make all of the member variables used by the static doEncoderA() static. So that's all the pin / port / bit_mask business and attached_val. There's only one copy of attached_val no matter how many QuickRotaryEncoder objects you try to make. They all have to share the same one. So in reality, we are going to be limited to a single encoder. (Although we can sort-of fix this later by adding the other interrupt to the class to expand it to two)

Remember I said the static members had to be initialized at some point out in program space? Well that's right in between the class definition and the set-up part. Note the syntax, but I'm not going to go into it because you shouldn't have to do that again for anything.






:fun4:
 
That's it. We've made a class. It compiles and runs on my board and produces the expected output. I like it. So let's make it where anyone can do it. We're going to make a library. We'll need two text files open, one called QuickRotaryEncoder.h and one called QuickRotaryEncoder.cpp.

The .h file is the header file. That's the one we include in a project if we want to use this library. It gets the class definition and any other variable declarations or function prototypes. It also gets all of the #includes. We need to include WProgram.h if we want to use this with Arduino.

We have to surround the whole header in an if statement. This tells the compiler, "If this isn't defined then define it as" then gives the definition. Having the class get defined twice would be bad. The syntax is:


#ifndef QuickRotaryEncoder_h
#define QuickRotaryEncoder_h


and at the end you need


#endif


That needs to go on every library no matter what. It might be included in another library you want to use and that would cause a double define problem.


The .cpp file gets all of the function definitions. In our particular case it also gets those pesky static variable initializations. It also needs an #include to the header file.






:rollface:
 
#include <QuickRotaryEncoder.h>


int number[3];

QuickRotaryEncoder knob(2,3);


void setup()
{
Serial.begin(9600);
knob.attachVariable(number[1]);
//knob.setupPins(2,3);
}

void loop()
{
printMyNumbers();

static int total = 0;

for (int i=0 ; i<3 ; i++)
{
total += number;
}

if ((total % 6 == 0) && ((number[0] + number[2]) % 4 == 0))
{
Serial.println();
Serial.println("YOU WIN YOU WIN YOU WIN");

knob.detachVariable();

printMyNumbers();

Serial.println("TURN IT BUT IT DOES NOT MOVE");
delay(1000);

printMyNumbers();

Serial.println("SEE!!!! NOW TURN IT A BUNCH AND WE WILL SEE IF YOU WIN AGAIN");

knob.attachVariable(number[1]);
delay(1000);

}

else
{
knob.attachVariable(number[ total%3 ]);
}
delay(500);
}




void printMyNumbers()
{
for (int i=0 ; i<3 ; i++)
{
Serial.print("number[");
Serial.print(i);
Serial.print("] = ");
Serial.print(number);
}
Serial.println();
}
 
Notice that all we had to do was create the object:

QuickRotaryEncoder knob(2,3);

and then use the dot operator to call the attachVariable and detachVariable functions. It's that easy from here on out.

I will zip up the whole package of code and post it here in a little while. So if you don't feel like reading through and following along, you can just grab the library and go.

Rotary encoders are better than pots for a lot of things that pots end up getting used for. That's because analogRead is easy. Well, now we've made rotary encoders just as easy.






HTH
 
I just realized that I cut out a paragraph. What about the new function in the class?

QuickRotaryEncoder::QuickRotaryEncoder(byte, byte)


That's the constructor. It get's called when we create a new object and usually just serves to set everything up. In our case it just calls setupPins. It takes two bytes as parameters. These are the pin numbers you want to use with the encoder.

RIGHT NOW WE HAVE TO USE PIN 2 FOR ONE SIDE

because we haven't accounted for using any interrupt other than 0.

It just won't work on any other two pins. I will get us out a better version in a few days. But I want to give you the library so far so you can see the header and program files.
 
Our (Almost) Finished Product

Our (Almost) Finished Product

Here's a zip file for what we have done so far. This library is useable. Extract it to your libraries folder in arduino and include it in your sketch. The example programs will run just like they are.



Later on, I'll polish this up a little and add functionality for a second encoder. When I get the full library ready, I will post it here and on the Dosing Computer thread.
 

Attachments

Here's some sample output where I got lucky! I was just advancing the knob at varying rates and letting the program run.


number[0] = 57number[1] = 67number[2] = 13
number[0] = 57number[1] = 67number[2] = 14
number[0] = 57number[1] = 67number[2] = 14
number[0] = 57number[1] = 67number[2] = 14

YOU WIN YOU WIN YOU WIN
number[0] = 57number[1] = 67number[2] = 15
TURN IT BUT IT DOES NOT MOVE
number[0] = 57number[1] = 67number[2] = 15
SEE!!!! NOW TURN IT A BUNCH AND WE WILL SEE IF YOU WIN AGAIN
number[0] = 57number[1] = 88number[2] = 15
number[0] = 57number[1] = 92number[2] = 15

YOU WIN YOU WIN YOU WIN
number[0] = 57number[1] = 92number[2] = 15
TURN IT BUT IT DOES NOT MOVE
number[0] = 57number[1] = 92number[2] = 15
SEE!!!! NOW TURN IT A BUNCH AND WE WILL SEE IF YOU WIN AGAIN
number[0] = 57number[1] = 105number[2] = 15
number[0] = 57number[1] = 117number[2] = 15
number[0] = 57number[1] = 119number[2] = 15

YOU WIN YOU WIN YOU WIN
number[0] = 57number[1] = 119number[2] = 15
TURN IT BUT IT DOES NOT MOVE
number[0] = 57number[1] = 119number[2] = 15
SEE!!!! NOW TURN IT A BUNCH AND WE WILL SEE IF YOU WIN AGAIN
number[0] = 57number[1] = 130number[2] = 15
number[0] = 57number[1] = 131number[2] = 15
number[0] = 58number[1] = 131number[2] = 15
number[0] = 58number[1] = 131number[2] = 15
number[0] = 58number[1] = 131number[2] = 15
number[0] = 58number[1] = 131number[2] = 15
number[0] = 58number[1] = 131number[2] = 15
number[0] = 58number[1] = 131number[2] = 15
number[0] = 58number[1] = 131number[2] = 15
number[0] = 58number[1] = 131number[2] = 15
number[0] = 58number[1] = 131number[2] = 15
 
Hi David,

nice tutorial, though this one is not really an easy one.
Just two quick questions:
I am using a rotary encoder with a button included - is there anything I need to consider when including your library or can I just define the button like:
QuickRotaryEncoder knob(2,3);
byte buttonA=2;
And then later on just normally read out the button with digital.read?

And secondly, how will I be able to use two Encoders in one sketch? E.g if I would like to use one for blue and one for white LED?

Thx
Thorsten
 
This was intended more as a lesson in several techniques of programming rather than an example of efficient use of a rotary encoder. Even on my own doser, I use a simple interrupt driven system and not this. Besides, when the new version 22 of Arduino just came out we found out that some of this trickery is no longer supported as such.

On your rotary encoder with a button, it should have three pins for the encoder and two pins for the button. Aside from the fact that they are part of one component physically, you can treat them as completely separate entities. The code would look no different than if you had an encoder and a button as completely separate components.

So yes, I think you're right on. Just declare it as an input pin and use digitalRead to read the button. Be sure to use a pull-up or pull-down as needed.



This little exercise would not support two encoders. Since it is a static class, you can only have one copy. The possible work around would be to make a second version of the library with a different name and include it twice. But that would be a waste of memory.

Actually the point of the exercise was to use a single encoder with more than one variable. The whole thing works better as inline code than it does as a library. I was never terribly happy about the way this turned out, but that's what I get for letting everyone watch start to finish. But it does work, and it could be used to adjust two variables for say brightness on white and blue lights off a single encoder. Maybe use a toggle switch to select which one you are adjusting and have the program attach the right variable to the encoder object based on which position the switch is in.






Reading an encoder with one of the hardware interrupts on Arduino is simple. If you want a simple one click = one increment kind of function, then you only need to have one pin connected to an interrupt, and you only need to fire the interrupt on one transition (ie. FALLING or RISING).

In the ISR, all you need to do is check the state of the other pin and then increment or decrement your variable. So if you have 2 encoders, one for white and one for blue, then you can have each on its own interrupt. Say white uses interrupt0 and blue uses interrupt1.

For example, if you attach the interrupt on FALLING, then the ISR would be

since we know pin1 of the encoder is low (because it only fires when FALLING) we just check to see if the other pin is HIGH or not)
Code:
void doEncoder() 
{
     (digitalRead (encoderPin2))? someVariable++:someVariable--;
}

depending on how you have your encoder mounted or what means plus and minus to you as far as direction, you may have to switch the addition and subtraction if it works backwards. But that's about it. It will also reverse if you switch the pins.


For yet another example of how to handle an encoder, look at my doser. There I used a single variable to hold the number of clicks of the encoder and a separate function to read that variable to another variable and reset it when I wanted to use the encoder. Then I used a function to turn on and off the interrupts for those sections of code where the encoder was used so it wouldn't fire off unintended clicks.


The basic structure looked like this. I've just given descriptions of the functions. Look at my doser project to see the function definitions. Or you can probably guess how they're written. Pretty easy stuff.

int clicks; // variable to hold the number of clicks

void EncoderOn(); // attaches the interrupt to doEncoder and resets clicks = 0
void EncoderOff(); // detaches interrupt from doEncoder and resets clicks = 0


void doEncoder(); // This is the ISR function to read the other pin and increment or decrement clicks. Looks just like the one above in this post.

int readEncoder(); // returns the value of clicks and resets clicks = 0

template class T
void useEncoder(T& var)
{

var += readEncoder();

}


This last function takes a variable by reference as a parameter and adds the value of clicks to it (and resets clicks in the readEncoder routine) Of course it would be easier just to put that one line in the code where you need it, but the bulk of the code had already been written using a speed sensor as an encoder and I needed something that would work without having to rewrite all that code. But you see how it works. Simple simple.



I guess the question is how do you want the encoders to behave? Do you want them constantly adjusting? Or do you want to use them to set a number on a screen and then have the program do something with that number? Do you want them to run one-click-one-increment which can take a while to adjust over a large range, but is really accurate and stable when just doing a few clicks? Or do you want to run them blazing fast where you get the range, but sometimes have to fiddle with it a little to hit one certain number? There are many ways to handle an encoder, and which one you choose is a function of how you want it to behave.
 
Hi David,
thx for you reply.
My plan is to use one encoder to manually fadein/out each LED stripe. Since I have 18 in total (all with a pwm driver) the idea would be:
Click on Encoder choses colour: Blue, White, Red, Green, UV, Moon
Encoder 1 changes the brightness so if I am on blue, all blue ones will be adjusted
Encoder 2 changes the stereo panning, like turning left, the left stripe gets lighter while the right one gets darker.
I think its a lot of programming and thinking to do, didnt start yet.

Anyways, I already had a encoder in use for my first LED which only adjusted each of the four stripes. THe thing is that it needs a lot of code to run through each time I use the encoder and so I thought of using your library since you said its much more efficient than just using the digital read.

Thats the code for my small 4 stripes LED:

Code:
/******Encoder*************/
int Aread, Bread, Cread, Dread=0;
byte modus=0;
byte A=3;
byte B=4;
byte C=19;
byte D=17;
byte buttonA=2; //können getaushct werden falls interrupt benötigt
byte buttonB=18; //können getaushct werden falls interrupt benötigt
boolean once=false;
byte encValue[4];
byte mod1[4];
byte mod2[4];
unsigned long interruptIntervall=60000;
unsigned long interruptMillis=0;
boolean tasterlesen1=true;
boolean last=true;

void setup()
{
/************ENCODER*************/   
  pinMode(buttonA, INPUT);
  pinMode(buttonB, INPUT);
  pinMode(A,INPUT);                        
  pinMode(B,INPUT);
  pinMode(C,INPUT);                        
  pinMode(D,INPUT);
  digitalWrite(buttonA, HIGH);  
  digitalWrite(buttonB, HIGH);
  digitalWrite(A,HIGH);                          
  digitalWrite(B,HIGH);                        
  digitalWrite(C,HIGH);                          
  digitalWrite(D,HIGH);   
  attachInterrupt(1,manuellBlau,CHANGE);        
  attachInterrupt(4,manuellWeiss,CHANGE);   
}

void loop()
{
 tasterlesen1=digitalRead(buttonA);
  if (tasterlesen1==false && last==true) {
      ++modus;
      if (modus>=4) {
        i[0]=mod2[0];
        i[1]=mod2[1];
        i[2]=mod2[2];
        i[3]=mod2[3];
        modus=0; 
      }
      last=false;
      once=false;
    }  
  if (tasterlesen1==true) {
    last=true;
    } 
///modus 0 is the RTC controlled dimming which I ignore here
//modus 1: encoder 1 changes both blue stripes encoder 2 changes both white stripes
//modus 2: encoder 1 changes blue 1 and encoder 2 changes blue 2
//modus 3: encoder 1 changes white 1 and encoder 2 changes white 2

if (modus==1) {
     if (once==false){
        encValue[0]=i[0];
        encValue[1]=i[1]; 
        encValue[2]=i[2];
        encValue[3]=i[3]; 
        interruptMillis=millis();
        once=true;
        lcd.clear();
      }
      mod1[0]=encValue[0];
      mod1[1]=encValue[1];      
      mod1[2]=encValue[2];
      mod1[3]=encValue[3];      
      analogWrite(stripePin[0], mod1[0]);  
      prozent[0]=(mod1[0]*100/255);
      analogWrite(stripePin[1], mod1[1]);  
      prozent[1]=(mod1[1]*100/255);
      analogWrite(stripePin[2], mod1[2]);  
      prozent[2]=(mod1[2]*100/255);
      analogWrite(stripePin[3], mod1[3]);  
      prozent[3]=(mod1[3]*100/255);      
      if (millis()-interruptMillis>interruptIntervall) {
      modus=0;
    }
    if (millis() - lcdmillis > 200) {  
    lcd3();
    lcdmillis=millis();
    }
  }
  if (modus==2) {
    if (once==false){
      encValue[0]=mod1[0];
      encValue[1]=mod1[1];
      interruptMillis=millis();
      once=true;
      }
    mod2[0]=encValue[0];
    mod2[1]=encValue[1];
    analogWrite(stripePin[0], mod2[0]); 
    prozent[0]=(mod2[0]*100/255);
    analogWrite(stripePin[1], mod2[1]);   
    prozent[1]=(mod2[1]*100/255);
    if (millis()-interruptMillis>interruptIntervall) {
      modus=0;
    } 
    if (millis() - lcdmillis > 200) {  
    lcd3();
    lcdmillis=millis();
    }
  }
  if (modus==3) {
    if (once==false){
      encValue[2]=mod1[2];
      encValue[3]=mod1[3];
      interruptMillis=millis();
      once=true;
      }
    mod2[2]=encValue[2];
    mod2[3]=encValue[3];
    analogWrite(stripePin[2], mod2[2]); 
    prozent[2]=(mod2[2]*100/255);
    analogWrite(stripePin[3], mod2[3]);
    prozent[3]=(mod2[3]*100/255);
    if (millis()-interruptMillis>interruptIntervall) {
      modus=0;
    }
    if (millis() - lcdmillis > 200) {  
    lcd3();
    lcdmillis=millis();
    }
  }
}

/********Encoder Steuerung blau [1] und [2]*****/
void manuellBlau() {
  Aread=digitalRead(A);
  Bread=digitalRead(B);
  /*--Linksdrehen--*/
  if (Aread==LOW && Bread==LOW ||Aread==HIGH&& Bread==HIGH){
    if (encValue[1]>10 && encValue[1]<245) {  
       encValue[1]=encValue[1]-5;
    }
    if (encValue[2]>10 && encValue[2]<245) {
       encValue[2]=encValue[2]-5;
    }
    if ((encValue[1]<=10||encValue[1]>=245)&& encValue[1]>=1) {
       encValue[1]=encValue[1]-1;
    }    
    if ((encValue[2]<=10||encValue[2]>=245)&& encValue[2]>=1) {
       encValue[2]=encValue[2]-1;
    }
  }  
  /*--Rechtsdrehen--*/
  if (Aread==HIGH && Bread==LOW||Aread==LOW && Bread==HIGH) {
    if (encValue[1]>10 && encValue[1]<245) {  
       encValue[1]=encValue[1]+5;
    }
    if (encValue[2]>10 && encValue[2]<245) {
       encValue[2]=encValue[2]+5;
    }
    if ((encValue[1]<=10||encValue[1]>=245)&& encValue[1]<=254) {
       encValue[1]=encValue[1]+1;
    }    
    if ((encValue[2]<=10||encValue[2]>=245)&& encValue[2]<=254) {
       encValue[2]=encValue[2]+1;
    } 
  }  
}   
/********Encoder Steuerung weiss [0] und [3]*****/
void manuellWeiss() {
  Cread=digitalRead(C);
  Dread=digitalRead(D);
  /*--Linksdrehen--*/
  if (Cread==LOW && Dread==LOW ||Cread==HIGH && Dread==HIGH){
    if (encValue[0]>10 && encValue[0]<245) {  
       encValue[0]=encValue[0]-5;
    }
    if (encValue[3]>10 && encValue[3]<245) {
       encValue[3]=encValue[3]-5;
    }
    if ((encValue[0]<=10||encValue[0]>=245)&& encValue[0]>=1) {
       encValue[0]=encValue[0]-1;
    }    
    if ((encValue[3]<=10||encValue[3]>=245)&& encValue[3]>=1) {
       encValue[3]=encValue[3]-1;
    } 
  }  
  /*--Rechtsdrehen--*/
  if (Cread==HIGH && Dread==LOW||Cread==LOW && Dread==HIGH) {
    if (encValue[0]>10 && encValue[0]<245) {  
       encValue[0]=encValue[0]+5;
    }
    if (encValue[3]>10 && encValue[3]<245) {
       encValue[3]=encValue[3]+5;
    }
    if ((encValue[0]<=10||encValue[0]>=245)&& encValue[0]<=254) {
       encValue[0]=encValue[0]+1;
    }    
    if ((encValue[3]<=10||encValue[3]>=245)&& encValue[3]<=254) {
       encValue[3]=encValue[3]+1;
    }
  }  
}

So as you can see, the functions already have a lot of code for only four stripes, so how about I will control 16 (or lets say 6 blue or 6 white) at a time?

Hope you can see the porblemI am facing.

Anyways, its a lot of coding Im looking at ;)
Thorsten
 
Any time you have to do the same thing more than once, the goal should be to write the code only once. If its a big long drawn out thing over and over, make it a function. If its a bunch of different things that each need their own copy of the same code, think classes.

For example, with 16 banks of lights, each is basically doing the same thing, just using different numbers right. So build the code for one string, make that into a class and then just have 16 instances of that class. Throw pointers to them into an array and you can even iterate through them in code.
 
currymuetze,

I think I can follow your code. The non-english variable names don't have any meaning to me, so it's a bit hard to follow. I think I see what it does.

The ISRs look cool, I like the way you have it slowed down on the ends and faster in the middle.

I will say this. It has been my experience that digitalRead is too slow to work with an encoder. By the time you've read the pulse on the first pin, the pulse on the second pin is gone.

Read up on port manipulation. It sounds hard at first, but it's not really. Basically all of the digital inputs are grouped together and you can read the group as a single byte. Reading a single pin this way is way faster than a digitalRead to begin with, but you can also read both pins in one swipe.

Read this: http://www.arduino.cc/en/Reference/PortManipulation

That and a little bit-math and you're there.
 
Here are a few tricks they don't really tell you about on the Arduino site.

There are a few macros that you'll need to know. They are in a library called pins_arduino.h
Include that into your sketch. You don't need to download anything, it's in the main avr.


The first macro is digitalPinToPort(). You pass this the pin number and it gives you back a byte that has the address of the port. So you don't need to remember which pin is portB and which are portD.

The next one is digitalPinToBitMask(). Pass this the pin number and it gives you a byte that is all zeros except for the one bit that corresponds to the pin in the port. Example in a minute.

The last three are portOutputRegister(), portInputRegister(), and portModeRegister(). These return the registers from the port. If you read the link in the above thread, this is direct access without needing to know the names, DDRD, PIND, PORTD, DDRB, PINB, PORTB, etc. They return a byte pointer to the actual port. If we keep that in a byte pointer marked volatile, then it will read the port (like a digitalRead only faster) every time we use that variable. It's a variable that updates itself!

It usually looks like this:
Code:
#include "pins_arduino.h"  // don't forget this at the top of the page.

byte pin = 3;  //or whatever pin you're using

byte pin_port = digitalPinToPort(pin);
byte pin_bitmask = digitalPinToBitMask(pin);

volatile byte* pin_inReg = portInputRegister(port); 


pinMode(pin, INPUT);  // Still need this line.

//This is equivalent to if (digitalRead(pin)

if (*pin_inReg & pin_bitmask) // Whatever, the if evaluates to true/high or false/low.



Now let's see how the bitmask works. Let's say we're playing with pin 3 on Arduino UNO. That's the fourth bit in portD. So the bitmask is 8. In binary that's 00001000.

Now let's say we want to look at pin 3. we read the port (the variable *pin_inReg) and it is a byte. 8 bits. They correspond to the input states of pins 0-7. Let's say it's:

00110100

So we AND with the bitmask to get:

00110100 &
00001000 =

00000000

That's zero which is false which is LOW. However you want to read it, the pin is low.

If it was

01101100 &
00001000 =

00001000

That's more than zero, so it evaluates to true or HIGH. However you want to read it. It is best to cast it to a boolean variable to make sure you don't end up with comparison errors, but that's not hard.


boolean read = (*pin_inReg & pin_bitmask);

is equivalent to

boolean read = digitalRead(pin);

but it is many times faster.



If we also had:

Code:
volatile byte* pin_outReg = portOutputRegister(port);

Then we can use our bitmask to set the outputs.

First of all ~ means bitwise NOT. SO ~pin_bitmask = 11110111 in our case.


*pin_outReg |= bitmask;

means

xxxxxxxx |
00001000 =

xxxx1xxx

This is equivalent (but much much faster than) digitalWrite(pin, HIGH)


*pin_outReg &= ~bitmask;

means

xxxxxxxx &
11110111 =

xxxx0xxx

This is equivalent to digitalWrite(pin, LOW)


There's a lot more you can do with bitmath. For example XOR ^= bitmask toggles a pin no matter what its state was, makes it the opposite. I'll leave you to study how that works.

All of this can add up to much smaller code. For example, the ISR for a rotary encoder would look like:

(assuming we set up pinA and pinB like above^^^ )
Code:
boolean Aread = (*pinA_inReg & pinA_bitmask);
boolean Bread = (*pinB_inReg & pinB_bitmask);

(Aread == Bread)? var++:var--

If we had both pins on the same port, we could read the port once with
Code:
byte portRead = *pin_inReg; 

//and then use the two bitmasks against that byte:

boolean Aread = (portRead & pinA_bitmask);
boolean Bread = (portRead & pinB_bitmask);

That would be super fast and guaranteed to catch the state of the encoder when the interrupt fired.
 
Last edited:
Back
Top