Step-by-Step Arduino

disc1

-RT * ln(k)
This was started as a post onto my DIY Dosing Computer thread. But it grew out of control until I whought it merited it's own thread.

I know this is a reef forum and not an Arduino forum, but there are a lot of people around here interested in the Arduino and the Arduino forums can have a steep learning curve. I intended this as a walk-through on the research and development of a simple Arduino library and as a jumping off point for getting into some more advanced stuff.

The write-up assumes a lot of knowledge on the readers part, but I've linked out all the technical words to good tutorials. Even if you don't want to make a rotary encoder, there is some meat just in the links here. I've broken it into pieces since it's long.

So here we go
 
I'm using a horribly inefficient hardware solution to using a rotary encoder with my Dosing Computer. THat solution is great for measuring the speed of a moving part on a robot with minimal interference in the code running the robot, but it's doing nothing for us here. I set about a solution that only involved connecting the rotary encoder. I had planned to add it to the Doser post, but it gets into so many juicy Arduino topics, that I thought it merited it's own post.

The end goal of this effort is a simple solution to using a rotary encoder with any arduino program to scroll menus or input values, wrapped into a simple library so that the user need only attach the variable to the encoder when necessary and detach when no longer needed.

For those who want to know more about how a rotary encoder works, see this from WikiPedia or see this from the Arduino Playground.I am using this encoder. It's part number 858-EN16-V22AF15 from mouser.com .

I'm also using the Arduino-UNO in this project.

This thing puts out grey-code, which basicly means that you have two pins that are pulsed, and depending on the direction you are turning it, one pulse leads the other. Here's a picture from the Arduino Playground that explains it.

RotaryEncoderWaveform.gif


I was having trouble with it so I put it on the scope, and this one is a little less desireable than some of the other gery-code encoders I've played with. The pulses seem longer on one pin than the other. I think that is related to the mechanical detent positions (the spots where it clicks). If you look at the data sheet you can see what I mean. The detent positions aren't symmetrical to the wave, so when you are clicking slowly, you end up with one long pulse and one short one.

The consequence of this has turned out to be that you can only use the rising edge of one signal and the falling edge of the other reliably. If you look at it on the oscilloscope while you click through it slowly, there is a long pulse on one pin, and a short pulse on the other pin, either right at the beginning of the long pulse or right at the end depending on the direction.

For scrolling through menu's and setting values in a prgram like this, I don't care. I only want one increment per click anyway, so I really only need to use one of the four transitions.

How are we going to accomplish this efficiently? We're going to use an interrupt.

Here's a quick lesson on how that works. Suppose you have a friend that is coming over to visit sometime today, but you aren't real sure exactly when. You could go every so often to the door and check to see if your friend is there, but that would really impede you getting anything else done. The alternative is to have a doorbell. Then you can go about your normal activity right up until the moment your friend arrives. As soon as you hear the bell, you INTERRUPT what you were doing and go take care of the door.

The same with the program, I could poll the pins that the encoder are connected to every so often. The problem is, some of these pulses are very very short, so if the program is busy, it may miss a lot of clicks. By using an interrupt, the program can keep up with what it has to do and only deal with the interrupt when it needs to.

More on this topic later when we write it.


Lets write a test program to use on the Arduino for playing with an encoder. We need a variable to increment and decrement with the encoder. We'll need the serial monitor so we can watch the number in the variable go up and down. And we'll need to set up for the rotary encoder. Let's use this for right now, I'll explain some of the pieces as we go.
 
Last edited:
The Test Program

=============================================================================================================================




#define EncoderPinA 2
#define EncoderPinB 3

#include "pins_arduino.h"

byte portA;
byte portB;
byte bit_maskA;
byte bit_maskB;
volatile byte *registerA;
volatile byte *registerB;

volatile static int numberA;

volatile int* attached_val;


void setup()
{
Serial.begin(9600);
pinMode(EncoderPinA, INPUT);
pinMode(EncoderPinB, INPUT);
digitalWrite(EncoderPinA, HIGH);
digitalWrite(EncoderPinB, HIGH);

portA=digitalPinToPort(EncoderPinA);
portB=digitalPinToPort(EncoderPinB);

bit_maskA = digitalPinToBitMask(EncoderPinA);
bit_maskB = digitalPinToBitMask(EncoderPinB);
registerA = portInputRegister(portA);
registerB = portInputRegister(portB);

attached_val = &numberA;
attachInterrupt(0, doEncoderA, FALLING); // for some reason the new mouser encoders only work on A falling and b rising The other ones don't read fast enough

and always count the same way
}

void loop()
{

Serial.print("numberA = ");
Serial.println(numberA);


delay(500);


}


void doEncoderA()
{

((((*registerA) & bit_maskA) && ((*registerB) & bit_maskB)) || ((!((*registerA) & bit_maskA)) && (!((*registerB) & bit_maskB))))? (*attached_val)++ :

(*attached_val)--;
}


===========================================================================================================================
 
SO what's going on here? First we define the pins and a file called pins_arduino.h. If you have the normal arduino software you have it already, if not it's not hard to find. We are using pins 2 and 3 because we are going to use the built in interrupts 0 and 1. If you're not on a MEGA then these are the only two you get.

The connection to the Arduino looks like this. For some reason I forgot to label the resitors. They are 10K Ohm.

picture.php


The capacitors are there to smooth out the signal. Without them, our program would real a bunch of clicks for each one as the signal bounces. This is an example of hardware debouncing in it's simplest form. Adding a resistor, 2K2 or so, between the encoder pins and the pint where the cap connects would make a better filter, but I haven't found it necessary for this.


Then we name a bunch of variables. Don't worry about all the port and pin stuff, we'll get to that later. We also initialize a variable to hold our number called numberA

We also define a pointer variable called attached_val. This variable "points to" another variable, and in the setup program, we will tell it to point to the variable numberA. By using a pointer variable to do this, we will be able to use this same interrupt later with more than one variable by simply changin which variable the pointer points to.

We give these variables the volatile and static keywords to make sure it stays constantly updated and persists through function calls.

Set-up configures our pins. THe middle encoder pin is connected to ground, so it's active-low. Therefore we are going to turn on the pull-up resistors on the input pins with the digitalWrite commands. Again, don't worry about the pin / port / bit_mask stuff for now.

The last line attaches the interrupt. We are going to tell the program to look to a function called doEncoderA. We will look at that function, called the interrupt service routine (ISR) in a minute. We are using interrupt 0, which is pin 2, and we are going to trigger on a FALLING signal edge.

Here's more on the attachInterrupt command.

The loop function just prints the number and delays for half a second. It has nothing to do with the encoder, that's all in the ISR.
 
Last edited:
The Interrupt Sercive Routine

Now for the meaty part. The doEncoderA function. This function gets called any time the input from the rotary encoder on Ardino pin #2 see's a falling edge. Everything else in the program gets put on pause while this happens and other interrupt driven services stop working. Notably millis() stops updating and delay() function does not work while the ISR is running. So for that reason, we want to make the ISR as short as possible. We don't want a bunch of spurrious code in there. It's important to keep it simple and short.

Now what do we need to do in the ISR? We are getting here because there was a falling edge on the A side of the encoder. So we need to check the other side and see if it is leading or lagging the A side. We really only need to test to see if it is LOW. IF it's already low, then B is ahead and if it is HIGH, then it is lagging behind. It would be just the inverse if we were using a RISING edge. Since we want this to be usable with all types of different encoders, and one day we may be working with a RISING transition, we will instead test to see if the two pins have the same value or not. We will assign increment and decrement arbitrarily for now, since we can just reverse it if it's backward of what we want.





So it is tempting to use something like this:

if (digitalRead(EncoderPinA)==digitalRead(EncoderPinB)) (*attached_val)++;
else (*attached_val)--;





But there's a huge problem with this approach. digitalRead is painfully slow, and the pulses are lightning quick. So instead we need to use port registers. This allows us to access the input pins directly. This is all that port / pin / bit_mask stuff. It's a big topic that can't go into depth here, but what we are going to do is look at the input register directly. This is one byte of ROM, and each bit represents one pin, 0 for LOW and 1 for HIGH. The bit_mask is a byte with a 1 in the right place so that we can do bit math to get a simple boolean value to tell us whether the pin is high or low. THe advantage is that we can now do it in a matter of a couple of clock cycles. Much faster than a digitalRead. I will do a thread on my QuickPin library one day and will explain better.

So now the ISR looks like this long complicated thing.





((((*registerA) & bit_maskA) && ((*registerB) & bit_maskB)) || ((!((*registerA) & bit_maskA)) && (!((*registerB) & bit_maskB))))? (*attached_val)++ :

(*attached_val)--;





The ? notation is a quick way to do an if/else. Basicly it takes whatever is in the parentheses before it and if it's true it runs what comes before the colon and if it's false it runs what's after the colon. See this link for more info on C++ operators. The ? is about halfway down.

Now for what's before the ?. You can see the || or operator in the middle. On the left hand side of that is says if both pins HIGH,and on the right side is both pins LOW. So is both pins are the same, then the whole thing is true.

How does each side work? (*registerA) dereferences the pointer to the input register for pin 2. Then it does an and operation with the bitmask, if the pin is high there will be a 1 left in the result, which will evaluate as TRUE. If the pin is low, the result will be zero and evaluate to FALSE.
 
Last edited:
So in the end what's happening so far? If we hook this up and run the program, we get something that is stable and increments or decrements the value by one for each click of the rotary encoder. Our program does nothing more than print out the number every half second, but the interrupt allows the number to update any time. Try it out. If you click really slow, the number changes as you click. If you can click it faster than the screen is updating, it will change by more than one number. You will also find with this type of encoder, that if you click it one click really fast, it sometimes moves by four or more at a time. I'm not real sure why that is.


So that's it for now. We have a working encoder. But I'm not satisfied. I will be back soon to show you how to use that pointer variable to allow you to use the encoder on more than one variable. Then we will wrap the whole thing up into a class and put it in a library so we never have to work at programming a rotary encoder again.
 
Last edited:
Pointers

Before we move forward, lets take a look at these pointer things. I have been asked several times about these, and I think if you want to use Arduino, it will help to understand them.

Check out this tutorial before you go any further if you don't know about pointers. It will explain the * and the & operators. http://www.cplusplus.com/doc/tutorial/pointers/

We declared a pointer variable called attached_val. When we say:

*attached_val = 5

using the * we are saying, "The variable pointed to by attached_val" = 5.

When we put the & in front of a regular variable, like :

attached_val = &numberA

we are saying, "Let the pointer attached_val point to the address of numberA.

========================================================

CAUTION: It's usually not wise to use the two together in one line unless you know what you are doing. If attached_val points to number A and you say this:

*attached_val = &numberB;

that will set the value of numberA to the address (some random number as far as we are concerned) of numberB.

=========================================================


So now we can rewrite our program to have three numbers A, B, and C and some line to allow us to switch which one will be attached to the encoder interrupt.
 
#define EncoderPinA 2
#define EncoderPinB 3

#include "pins_arduino.h"

byte portA;
byte portB;
byte bit_maskA;
byte bit_maskB;
volatile byte *registerA;
volatile byte *registerB;

volatile static int numberA;
volatile static int numberB;
volatile static int numberC;

volatile int* attached_val;



void setup()
{
Serial.begin(9600);
pinMode(EncoderPinA, INPUT);
pinMode(EncoderPinB, INPUT);
digitalWrite(EncoderPinA, HIGH);
digitalWrite(EncoderPinB, HIGH);

portA=digitalPinToPort(EncoderPinA);
portB=digitalPinToPort(EncoderPinB);

bit_maskA = digitalPinToBitMask(EncoderPinA);
bit_maskB = digitalPinToBitMask(EncoderPinB);
registerA = portInputRegister(portA);
registerB = portInputRegister(portB);

attached_val = &numberA;
attachInterrupt(0, doEncoderA, FALLING); // for some reason the new mouser encoders only work on A falling and b rising The other ones don't read fast enough and always count the same way
}

void loop()
{

Serial.print("numberA = ");
Serial.print(numberA);
Serial.print(" numberB = ");
Serial.print(numberB);
Serial.print(" numberC = ");
Serial.println(numberC);

int total = numberA + numberB + numberC;

if (total % 3 == 0) attached_val = &numberA;
else if (total % 3 == 1) attached_val = &numberB;
else if (total % 3 == 2) attached_val = &numberC;


delay(500);


}


void doEncoderA()
{
((((*registerA) & bit_maskA) && ((*registerB) & bit_maskB)) || ((!((*registerA) & bit_maskA)) && (!((*registerB) & bit_maskB))))? (*attached_val)++ : (*attached_val)--;
}
 
So we've added two more variables and set in this piece of code:

int total = numberA + numberB + numberC;

if (total % 3 == 0) attached_val = &numberA;
else if (total % 3 == 1) attached_val = &numberB;
else if (total % 3 == 2) attached_val = &numberC;



This uses the total of the three numbers to decide which one of the variables to point attached_val to. It does this based on the remainder left over when you divide by 3.

If you run this piece of code and turn your encoder very slowly, the numbers will increase across the screen. A then B then C then A etc. If you turn faster something different happens.

While the loop function is in the delay(500) the encoder interrupt can fire as many times as you can click the encoder in half a second. Since attached_val keeps pointing at the same variable through the whole delay, it counts how many times the encoder fires. On the next pass through, attached_val gets assigned to point at a different variable and for 500ms you are adjusting that one. So on each cycle of the loop, only one number will change.

This is what we will wrap up into a class. But since I said step by step, we are going to learn a little along the way.
 
The first step in writing a code library is to condense all the repetitive parts into functions. Let's say I put the following all over my code.

attachInterrupt(0, doEncoderA, FALLING);

and then we figure out later that we need to be using interrupt 1 and RISING for some reason. We would have to go through the whole program and change each instance of it. Imagine if we had to troubleshoot!!! We could write a Macro, but I hate Macros. So we're going to write a quick little function. That way, if we ever want to change it, we only have to make the change in one place. Let's call this function attachFunction() since later we are going to overload it so that we can use it to also change the ISR on the fly by passing a pointer to a function (yes pointers can do that too!!!). Let's say:

void attachFunction()
{
attachInterrupt(0, doEncoderA, FALLING);
}

That looks redundant now, but we can expand on it later. It also allows us to let the user of our library attach the interrupt without ever knowing anything about what it is doing (or even how an interrupt works).


We also need a function to make attached_val point to whatever variable we want. In the interest of making this easy, we're going to take the volatile keyword off of numberA, B, and C. There's more involved in passing volatile variables, and we're only accessing them during the updates. If we need to make them volatile again, we can overload these functions.

So let's write:

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

This functionlooks different. What's that & in the declaration?

That's call-by-reference. It's like passing a pointer to a function, and then having the function put the * in front of it for you. If we wrote the function without the two &'s, we would have to put an & in the function call. Since we don't want the user to have to know a bunch about C++, we'll do the reference on this end.

Currently the function call looks like:

attachVariable(numberA);

If we had used:

void attachVariable(int* _var)
{
attached_val = (_var);
}


then the function call would have been like this:

attachVariable(&numberA);


The moral of the story is that we have to get the address at some point. And the whole purpose of writing a program is to only have to do things once. So I write the reference into the function and in the future I just pass it a variable and never think again about how it works.
 
Last edited:
Now our code looks like this:

=========================================================


#define EncoderPinA 2
#define EncoderPinB 3

#include "pins_arduino.h"

byte portA;
byte portB;
byte bit_maskA;
byte bit_maskB;
volatile byte *registerA;
volatile byte *registerB;

static int numberA;
static int numberB;
static int numberC;

volatile int* attached_val;


/* =========== FUNCTION PROTOTYPES ===========*/

void attachVariable(int&);
void attachFunction();




void setup()
{
Serial.begin(9600);
pinMode(EncoderPinA, INPUT);
pinMode(EncoderPinB, INPUT);
digitalWrite(EncoderPinA, HIGH);
digitalWrite(EncoderPinB, HIGH);

portA=digitalPinToPort(EncoderPinA);
portB=digitalPinToPort(EncoderPinB);

bit_maskA = digitalPinToBitMask(EncoderPinA);
bit_maskB = digitalPinToBitMask(EncoderPinB);
registerA = portInputRegister(portA);
registerB = portInputRegister(portB);

attachVariable(numberA);
attachFunction();
}

void loop()
{

Serial.print("numberA = ");
Serial.print(numberA);
Serial.print(" numberB = ");
Serial.print(numberB);
Serial.print(" numberC = ");
Serial.println(numberC);

int total = numberA + numberB + numberC;

if (total % 3 == 0) attachVariable(numberA);
else if (total % 3 == 1) attachVariable(numberB);
else if (total % 3 == 2) attachVariable(numberC);


delay(500);


}


void doEncoderA()
{
((((*registerA) & bit_maskA) && ((*registerB) & bit_maskB)) || ((!((*registerA) & bit_maskA)) && (!((*registerB) & bit_maskB))))? (*attached_val)++ : (*attached_val)--;
}


void attachFunction()
{
attachInterrupt(0, doEncoderA, FALLING); // for some reason the new mouser encoders only work on A falling and b rising The other ones don't read fast enough and always count the same way
}

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




=========================================================


Note the function prototypes for the new functions at the top.
 
Here's an example of the output at this point.

Turning slowly:

numberA = 1 numberB = 0 numberC = 0
numberA = 1 numberB = 0 numberC = 0
numberA = 1 numberB = 1 numberC = 0
numberA = 1 numberB = 1 numberC = 0
numberA = 1 numberB = 1 numberC = 1
numberA = 1 numberB = 1 numberC = 1
numberA = 1 numberB = 1 numberC = 1
numberA = 2 numberB = 1 numberC = 1
numberA = 2 numberB = 1 numberC = 1
numberA = 2 numberB = 2 numberC = 1
numberA = 2 numberB = 2 numberC = 1
numberA = 2 numberB = 2 numberC = 2
numberA = 2 numberB = 2 numberC = 2
numberA = 2 numberB = 2 numberC = 2
numberA = 3 numberB = 2 numberC = 2



Turning Fast:

numberA = 4 numberB = 12 numberC = 12
numberA = 4 numberB = 20 numberC = 12
numberA = 16 numberB = 20 numberC = 12
numberA = 25 numberB = 20 numberC = 12
numberA = 30 numberB = 20 numberC = 12
numberA = 48 numberB = 20 numberC = 12
numberA = 51 numberB = 20 numberC = 12
numberA = 51 numberB = 20 numberC = 26
numberA = 51 numberB = 28 numberC = 26
numberA = 51 numberB = 28 numberC = 26
numberA = 51 numberB = 28 numberC = 26
numberA = 51 numberB = 28 numberC = 26
numberA = 51 numberB = 28 numberC = 26
 
Let's do one last thing before we wrap it into a class. Let's give the user a way to turn the function off. We don't really have a way to put that in our test program, so you'll have to trust me for now that it will work.

The two functions are these:


void detachFunction()
{
detachInterrupt(0);
}

void detachVariable()
{
detachFunction();
attached_val = NULL;
}



Notice that detachVariable calls detachFunction before it clears the pointer. If it cleared the pointer first, what would happen if the ISR ran before it got turned off? An error probably! So we'll turn the ISR off first THEN clear the pointer.

Setting a pointer to NULL make's the pointer useless. It's a good way to keep a pointer from messing with a variable after you're done with it, but it will throw an error if we try to do anything with it.

We could test attached_val in the ISR itself with:

if (attached_val == NULL) ...

but that would take time and we want to keep the ISR short. So we'll make a change to attachVariable so that it calls attachFunction after it sets up the pointer. We're going to do it after for the same reason we turned it off before we cleared the pointer. Pointers are powerful, but we have to be careful with them.

We will only give the end user access to attachVariable and detachVariable. The Function equivalents we'll make private in the class so the end user can't get in trouble with the pointers.

The end user won't even need to know that pointers are involved or how they work!!!!

So now attachVariable looks like this:



void attachFunction()
{
attachInterrupt(0, doEncoderA, FALLING);
}


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



We're also going to get ready to set up a constructor for our new class by moving all of that pin / port/ bit_mask stuff into a function of it's own.



void setupPins(byte _pin1, byte _pin2)
{
pinMode(_pin1, INPUT);
pinMode(_pin2, INPUT);
digitalWrite(_pin1, HIGH);
digitalWrite(_pin2, HIGH);

portA=digitalPinToPort(_pin1);
portB=digitalPinToPort(_pin2);

bit_maskA = digitalPinToBitMask(_pin1);
bit_maskB = digitalPinToBitMask(_pin2);
registerA = portInputRegister(portA);
registerB = portInputRegister(portB);

}



Next we'll look at the code as it stands. You can already start to see how much space we're going to save when we wrap this up. Soon any reefer with an arduino LED controller build will be able to use a rotary encoder instead of a pot for a more stable, more precise, and more fun dimmer control.
 
#define EncoderPinA 2
#define EncoderPinB 3

#include "pins_arduino.h"

byte portA;
byte portB;
byte bit_maskA;
byte bit_maskB;
volatile byte *registerA;
volatile byte *registerB;

static int numberA;
static int numberB;
static int numberC;

volatile int* attached_val;

/* =============== FUNCTION PROTOTYPES =============*/


void attachFunction();
void attachVariable(int&);
void detachFunction();
void detachVariable();
void setupPins(byte, byte);


void setup()
{
Serial.begin(9600);

setupPins(EncoderPinA, EncoderPinB);
attachVariable(numberA);
}



void loop()
{

Serial.print("numberA = ");
Serial.print(numberA);
Serial.print(" numberB = ");
Serial.print(numberB);
Serial.print(" numberC = ");
Serial.println(numberC);

int total = numberA + numberB + numberC;

if (total % 3 == 0) attachVariable(numberA);
else if (total % 3 == 1) attachVariable(numberB);
else if (total % 3 == 2) attachVariable(numberC);


delay(500);


}


void doEncoderA()
{
((((*registerA) & bit_maskA) && ((*registerB) & bit_maskB)) || ((!((*registerA) & bit_maskA)) && (!((*registerB) & bit_maskB))))? (*attached_val)++ : (*attached_val)--;
}


void attachFunction()
{
attachInterrupt(0, doEncoderA, FALLING); // for some reason the new mouser encoders only work on A falling and b rising The other ones don't read fast enough and always count the same way
}

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



void detachFunction()
{
detachInterrupt(0);
}

void detachVariable()
{
detachFunction();
attached_val = NULL;
}


void setupPins(byte _pin1, byte _pin2)
{
pinMode(_pin1, INPUT);
pinMode(_pin2, INPUT);
digitalWrite(_pin1, HIGH);
digitalWrite(_pin2, HIGH);

portA=digitalPinToPort(_pin1);
portB=digitalPinToPort(_pin2);

bit_maskA = digitalPinToBitMask(_pin1);
bit_maskB = digitalPinToBitMask(_pin2);
registerA = portInputRegister(portA);
registerB = portInputRegister(portB);

}
 
Arrays

Arrays

Let's take a quick break from the encoder to talk about arrays. This is the only big topic that wasn't going to come up in the build of a rotary encoder, so we're going to use it to clean up our test program to give us a chance to learn.

Here is a quick tutorial on arrays to help you get the basics. And here's another one.

An array is a group of variables with the same type and the same name. Each individual variable in the array differs by an index number. So where we used numberA, numberB, and numberC we could have used an array and saved some time. The array would look like number[0], number[1], and number[2]. We declare this array like this:

int number[3];

That creates an array of 3 int variables with the names we wanted. Notice that the names don't start at 1. Arrays always start at 0. So we have number 0, 1, and 2, NOT 1, 2, and 3.

We use the names from the array like any other variable. We can say:

number[1] = 4;

or even

number[some variable] = 4


The number inside the brackets is called the index and we can use any expression we want for the index, so long as that expression returns an integer.

Arrays have one more special property. Remember pointers? Well, array's are pointers!!!!!

In our case, number is actually a pointer to the first member of the array. If we use number without the brackets and index, we can treat it just like any other pointer. The brackets dereference the pointer, that is, the brackets serve the same purpose as the * . Then the index is used to count off how far down the list to go.

So this whole part that printed out the list of three numbers:


Serial.print("numberA = ");
Serial.print(numberA);
Serial.print(" numberB = ");
Serial.print(numberB);
Serial.print(" numberC = ");
Serial.println(numberC);




becomes with an array:


for (int i=0 ; i<3 ; i++)
{
Serial.print("number[");
Serial.print(i);
Serial.print("] = ");
Serial.print(number);
}


If you aren't familiar with the for loop, look here!

What we've ended up with here is almost as many lines of code as we started with. But we're just playing around with 3 values. What if we had 10 or 12 strings of LEDs and wanted to store a dimming value for each one, with even longer pieces of code for each one. The savings add up quick.

What I'm going to do here, is fix us up a way to show off our detach functions in our example code. We're going to build a little lottery. At each pass of the loop function, we're going to test the total of the three numbers. If it meet's some criteria, then we'll run a little piece of code to show off detachVariable().

I don't want to have to type the code to print the numbers over and over, so I'm putting that into a function. It's called printMyNumbers().

Up before the setup function, where we said:


static int numberA;
static int numberB;
static int numberC;


This becomes:

int number[3];

with the array. We also have to change the attachVariable(numberA); to use number[0] instead.



Here's the new loop and the new function.





==================================
 
Last edited:
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");

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");

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

}

else
{
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();
}






:spin1:
 
What we have created thus far is a useful program to use a rotary encoder for an input knob to an arduino. The problem is that you still need to know how the code works to be able to use it. I want something that the non-coders can use. And not for completely altruistic reasons. I want it to be easy for me in the future as well.

I like my libraries wrapped in classes. I just like object oriented thinking. It allows me to give short descriptive names for everything in my code and it keeps me from naming over some variable from a library.

If you don't know about C++ classes and structs here's one tutorial , and another .

Basically a class creates a new type of object, like an int, or a long, or a string. A string is actually a special kind of class that takes an array of char variables. The class has it's own variable called member variables, and it's own functions called member functions. You declare an object of class type just like an object of any other type. But to access it's members, you use the . "dot" operator.

For example if we had a class called MyClass and it had a member variable called member_var, and we want to create an object of type MyClass called the_object and set it's member_var equal to 4, we would write:


MyClass the_object;

the_object.member_var = 4;


The member functions get direct access to the member variables. Calling a member function works just like with the variables.


the_object.someFunction(parameter1, parameter2);


There's a LOT more that goes into classes, but this is enough for us to go on and use this Rotary Encoder.

Usually, to wrap something up into a class, we just take all of the variables and function definitions and put them into a class definition. See the tutorial, or stick around to find out what that entails. Then we do a few extra things, adding scope, letting functions know how they fit into the class.





:spin2:
 
This particular project turns out to be a non-trivial case of a class because we are also messing with interrupts. A class member function cannot be attached to an interrupt. This is because the ISR cannot return any value, and cannot take any parameters.

But doEncoderA() fit's both of those, so what's the problem?

Well, when we put it into a class, it does actually take a parameter. It's called the this pointer. It tells the function which set of member variables to use. That is, it tells the function which class object it is a member of.

I was a step ahead of this problem. Since I am only going to use 1 of these knobs at a time, I'm going to make this class with static members

Static members are members of the class that are shared between different objects of the class. That means there's only one of each static member. There are some special rules. Static member functions can only access static member variables. How would the static function know which object's members to use? Static members also have to be initialized in program space, outside of the functions and the class, before the class can be used.

But a static member has no this pointer. So we can use it for an interrupt. That little fact turns out to be a lot of trouble. But we're into learning, so we're going to do this the hard way and make it work easy for everyone else.




When I put all the code back together into a class definition, it comes out looking like this:






:frog:
 
#define EncoderPinA 2
#define EncoderPinB 3

#include "pins_arduino.h"


/* ======================================================================================
********* Class Definition *************
======================================================================================*/



class QuickRotaryEncoder {

private:
static byte portA;
static byte portB;
static byte bit_maskA;
static byte bit_maskB;
volatile static byte *registerA;
volatile static byte *registerB;


public:
static int* attached_val;

public:
QuickRotaryEncoder(byte, byte);

static void doEncoderA();

void attachVariable(int&);
void detachVariable();

private:
void attachFunction();
void detachFunction();

static void setupPins(byte, byte);



};

int* QuickRotaryEncoder::attached_val = NULL;
byte QuickRotaryEncoder::portA=0;
byte QuickRotaryEncoder::portB=0;
byte QuickRotaryEncoder::bit_maskA=0;
byte QuickRotaryEncoder::bit_maskB=0;
volatile byte *QuickRotaryEncoder::registerA=0;
volatile byte *QuickRotaryEncoder::registerB=0;



/* ======================================================================================
********* Example Function Setup and Loop *************
======================================================================================*/





int number[3];

QuickRotaryEncoder knob(2,3);


void setup()
{
Serial.begin(9600);
knob.attachVariable(number[1]);

}

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)) //WINNER SECTION
{
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();
}





/* ======================================================================================
********* Class Functions *************
======================================================================================*/




QuickRotaryEncoder::QuickRotaryEncoder(byte _pin1, byte _pin2)
{
setupPins(_pin1, _pin2);
}



void QuickRotaryEncoder::doEncoderA()
{
((((*QuickRotaryEncoder::registerA) & QuickRotaryEncoder::bit_maskA) && ((*QuickRotaryEncoder::registerB) & QuickRotaryEncoder::bit_maskB)) || ((!((*QuickRotaryEncoder::registerA) & QuickRotaryEncoder::bit_maskA)) && (!((*QuickRotaryEncoder::registerB) & QuickRotaryEncoder::bit_maskB))))? (*QuickRotaryEncoder::attached_val)++ : (*QuickRotaryEncoder::attached_val)--;
}


void QuickRotaryEncoder::attachFunction()
{
attachInterrupt(0, QuickRotaryEncoder::doEncoderA, FALLING); // the new mouser encoders only work on A falling and b rising
}

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



void QuickRotaryEncoder::detachFunction()
{
detachInterrupt(0);
}

void QuickRotaryEncoder::detachVariable()
{
detachFunction();
attached_val = NULL;
}


void QuickRotaryEncoder::setupPins(byte _pin1, byte _pin2)
{
pinMode(_pin1, INPUT);
pinMode(_pin2, INPUT);
digitalWrite(_pin1, HIGH);
digitalWrite(_pin2, HIGH);

portA=digitalPinToPort(_pin1);
portB=digitalPinToPort(_pin2);

bit_maskA = digitalPinToBitMask(_pin1);
bit_maskB = digitalPinToBitMask(_pin2);
registerA = portInputRegister(portA);
registerB = portInputRegister(portB);

}






:spin1:
 
That looks like a lot of code if you're seeing it for the first time, but it's just what we've built so far.
 
Back
Top