HobbyKing.com New Products Flash Sale
Reply
Thread Tools
Old Apr 15, 2014, 02:12 PM
Registered User
Joined Dec 2012
95 Posts
Another suggestion... I haven't read through everything in this thread so sorry if you've already covered / solved for this. You mentioned not turning the rotary encoder too fast because it might get missed. That's because you are reading the rotary encoder's pins in your main loop(). There is a better, more efficient way. You can use interrupts so that when the state changes on either pin, the normal loop() is "interrupted" and a special function is executed (that you write) to process the state changes on the encoder. When the function ends, the loop() routine is continued where it left off. If you setup an interrupt on both the encoder pins, you won't miss any steps no matter how fast you turn the encoder. This is what I do on my ship transmitter. I think there's only 2 pin interrupts on the UNO (there may be more on what you are using), and if you're already using them for something else then this won't be doable. There's lots of stuff on encoders here:
http://playground.arduino.cc/Main/RotaryEncoders
Skystream is offline Find More Posts by Skystream
Reply With Quote
Sign up now
to remove ads between posts
Old Apr 15, 2014, 02:23 PM
Registered User
Dutchraptor's Avatar
The Netherlands, ZH, Hoogvliet
Joined Jul 2008
270 Posts
I haven't tried the interupt verson yet. This test version is a little extract from the previous main program. I'm now building smaller blocks so I can test each step. How does this interupt thing work? Can you point me in the right direction? I've tried before but the google suggestions don't Always work as they should.

Danny

EDIT: found it, a new library on Arduino.cc I used an older version of Arduino and never checked the site again. I'll try. Thanks anyway.
Dutchraptor is online now Find More Posts by Dutchraptor
Last edited by Dutchraptor; Apr 15, 2014 at 02:35 PM.
Reply With Quote
Old Apr 15, 2014, 02:41 PM
Registered User
Joined Dec 2012
95 Posts
I am not at home right now so I can't access my source code for my ship transmitter, but this example should prove the concept:

Code:
#define encoder0PinA 2

#define encoder0PinB 3

volatile unsigned int encoder0Pos = 0;

void setup() {

  pinMode(encoder0PinA, INPUT); 
  pinMode(encoder0PinB, INPUT); 
// encoder pin on interrupt 0 (pin 2)

  attachInterrupt(0, doEncoderA, CHANGE);
// encoder pin on interrupt 1 (pin 3)

  attachInterrupt(1, doEncoderB, CHANGE);  
  Serial.begin (9600);
}

void loop(){ //Do stuff here }

void doEncoderA(){

  // look for a low-to-high on channel A
  if (digitalRead(encoder0PinA) == HIGH) { 
    // check channel B to see which way encoder is turning
    if (digitalRead(encoder0PinB) == LOW) {  
      encoder0Pos = encoder0Pos + 1;         // CW
    } 
    else {
      encoder0Pos = encoder0Pos - 1;         // CCW
    }
  }
  else   // must be a high-to-low edge on channel A                                       
  { 
    // check channel B to see which way encoder is turning  
    if (digitalRead(encoder0PinB) == HIGH) {   
      encoder0Pos = encoder0Pos + 1;          // CW
    } 
    else {
      encoder0Pos = encoder0Pos - 1;          // CCW
    }
  }
  Serial.println (encoder0Pos, DEC);          
  // use for debugging - remember to comment out
}

void doEncoderB(){

  // look for a low-to-high on channel B
  if (digitalRead(encoder0PinB) == HIGH) {   
   // check channel A to see which way encoder is turning
    if (digitalRead(encoder0PinA) == HIGH) {  
      encoder0Pos = encoder0Pos + 1;         // CW
    } 
    else {
      encoder0Pos = encoder0Pos - 1;         // CCW
    }
  }
  // Look for a high-to-low on channel B
  else { 
    // check channel B to see which way encoder is turning  
    if (digitalRead(encoder0PinA) == LOW) {   
      encoder0Pos = encoder0Pos + 1;          // CW
    } 
    else {
      encoder0Pos = encoder0Pos - 1;          // CCW
    }
  }
}
Basically what is happening here is a variable called "encoder0Pos" contains an integer that increases when you turn the encoder one way and decreases when you turn it the other way. The act of turning the encoder causes the state changes on the pins, and this causes the microcontroller to call the interrupt functions doEncoderA() and doEncoderB(). You don't have to worry about all that unless you want to to a maximum negative and maximum positive value for encoder0Pos. All you have to do is use the "encoder0Pos" variable in your loop. You don't need to "read" anything... the interrupt code does that for you. You could just put the following code in the loop() to watch what happens on the Serial Monitor as you turn the encoder each way.

Code:
loop()
{
  Serial.println(encoder0Pos);
  delay(50);
}
You'll get something like this:
0
0
0
0
0
1
1
2
3
4
5
6
6
6
6
6
5
5
4
3
2
1
0
0
0
0
-1
-1
-2
-2
-3
-4
-5
-5
-5
-5

Keep turning the encoder in the same direction and the number will keep climbing.

If you want to set maximum values, adjust doEncoderA() and doEncoderB() by adding this to them:
Code:
if (encoder0Pos > 1000) encoder0Pos = 1000;
if (encoder0Pos < -1000) encoder0Pos = -1000;
This would make the encoder not count past -1000 or 1000. Just adjust the numbers for what you need. If you want the count to wrap-around, then just set the encoder0Pos to the opposite end of the range.
Skystream is offline Find More Posts by Skystream
Reply With Quote
Old Apr 15, 2014, 03:58 PM
Registered User
Dutchraptor's Avatar
The Netherlands, ZH, Hoogvliet
Joined Jul 2008
270 Posts
Thanks for the info. I tried, but I think I'm doing something wrong. I keep getting a high number (63000 and higher) and after a few turns it hangs. Also if I change the volatile to int, I do get 0 counting up/down, but also after a few turns it stops, freezing the serial monitor.

I found this version, also interupt driven (as far as I can see since I have no experience with interupts) and added the led-display part (as a function). It works OK, faster, but not very direct. Up it counts very fast, but down it tends to skip steps. But still way faster then the previous version. So thanks again for pointing me this direction.

EDIT: deleted the serial monitor since it also creates drag.

Code:
#include <Wire.h>

#define expander1 B0111000
#define expander2 B0100000
#define encoder0PinA 3 // exchange pins when it goes the wrong way
#define encoder0PinB 2 // exchange pins when it goes the wrong way

volatile unsigned int encoder0Pos = 0;
static boolean rotating=false;
int b;
int channel = 0;
int channelold = 100;
const int buttonPin = 53;     // the number of the pushbutton Reset pin
int pushed = 0; int buttonState = 0;

void(* resetFunc) (void) = 0; //declare reset function @ address 0

void setup() {
Wire.begin();
  expanderWrite1(B01110111);
  expanderWrite2(B10110111); 
  pinMode(encoder0PinA, INPUT_PULLUP); 
  digitalWrite(encoder0PinA, HIGH);       
  pinMode(encoder0PinB, INPUT_PULLUP); 
  digitalWrite(encoder0PinB, HIGH); 
  attachInterrupt(0, rotEncoder, CHANGE);  
  pinMode(buttonPin, INPUT_PULLUP); 
  //Serial.begin (9600);
}

void rotEncoder(){
  rotating=true; 
  // If a signal change (noise or otherwise) is detected
  // in the rotary encoder, the flag is set to true
}

void loop() {
    // read the state of the pushbutton value:
  buttonState = digitalRead(buttonPin);
  if (buttonState == LOW) {     
    delay(500);
    resetFunc();  //call reset  
  } 
  else { 
  }
  while(rotating) {
    delay(2);
    // When signal changes we wait 2 milliseconds for it to 
    // stabilise before reading (increase this value if there
    // still bounce issues)
    if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB)) {  
      encoder0Pos++;
    } 
    else {                                   
      encoder0Pos--;
    }
    rotating=false; // Reset the flag back to false
    //Serial.println(encoder0Pos);
    displayer();
  }
}

void displayer(){
    b = encoder0Pos;
    channel = encoder0Pos;
       if (channel > 98){encoder0Pos = 98;}
    if (channel <= 1){encoder0Pos = 1;} 
  if (channel != channelold){

if (b<10){expanderWrite1(B01110111);}
if (b>=90){expanderWrite1(B11110101);b=b-90;}
if (b>=80){expanderWrite1(B11110111);b=b-80;}
if (b>=70){expanderWrite1(B00110100);b=b-70;}
if (b>=60){expanderWrite1(B11010111);b=b-60;}
if (b>=50){expanderWrite1(B11010101);b=b-50;}
if (b>=40){expanderWrite1(B11100100);b=b-40;}
if (b>=30){expanderWrite1(B10110101);b=b-30;}
if (b>=20){expanderWrite1(B10110011);b=b-20;}
if (b>=10){expanderWrite1(B00100100);b=b-10;}
delay(10);
//expanderWrite2(B00001000); // dot
if (b==0){expanderWrite2(B10110111);}
if (b==1){expanderWrite2(B00100100);}
if (b==2){expanderWrite2(B01110011);}
if (b==3){expanderWrite2(B01110110);}
if (b==4){expanderWrite2(B11100100);}
if (b==5){expanderWrite2(B11010110);}
if (b==6){expanderWrite2(B11010111);}
if (b==7){expanderWrite2(B00110100);}
if (b==8){expanderWrite2(B11110111);}
if (b==9){expanderWrite2(B11110110);}
channelold = b;
  }
}

  
void expanderWrite1(byte _data ) {
Wire.beginTransmission(expander1);
Wire.write(_data);
Wire.endTransmission();
} 

void expanderWrite2(byte _data ) {
Wire.beginTransmission(expander2);
Wire.write(_data);
Wire.endTransmission();
}
Dutchraptor is online now Find More Posts by Dutchraptor
Last edited by Dutchraptor; Apr 15, 2014 at 04:22 PM.
Reply With Quote
Old Apr 15, 2014, 08:36 PM
Registered User
Joined Dec 2012
95 Posts
Believe it or not, the interrupt code isn't your problem. The problem is you are are overloading the serial buffer by sending too much data too fast. Once the buffer fills up, it either breaks the Arduino IDE's serial monitor or the microcontroller hangs -- can't remember which happens, but I've done this several times. All you need to do is put in bigger delay in your main loop so that you don't send too much serial data.

Also the code that you just posted is wrong. Like way wrong! But that's okay... That's how we learn. First off, you are trying to manually read the encoder pins and do interrupt processing at the same time. You can't do both... it's one or the other. Do not set encoder0Pos outside of interrupt functions and do not even attempt to do any digital reads on the encoder pins in your loop. There is absolutely NO reason to do that. The interrupts handle incrementing and decrementing the encoder0Pos variable for you... That's the whole point of using the interrupts. The value inside encoder0Pos changes automatically. Don't do the digitalReads on the encoder pins... you're fighting over what the interrupts are already doing for you, and you will get garbage as a result.


As a quick test, just replace your main loop with this:

void loop()
{
Serial.println(encoder0Pos);
delay(200);
}

That's it. You'll see that encoder0Pos does actually increment and decrement properly with each step of the encoder. Note that I am not doing any digitalRead() calls on the encoder pins in the main loop. That is an absolute no-no since the variable is being set by the interrupts. The digitalReads are done from the interrupt functions. You'll see that the value still changes despite that you are not setting the value in the main loop. That's the whole point of using interrupts here. It really is the best way to do this.

Also your manual read of the encoder pins in the loop() above would never work properly even if you were not doing interrupts. You don't just compare the 2 pins together and if they are both the same increase the value and if not decrease the value. That's not how encoders work. You need to compare the value of 1 pin to the value of the previous read from that same pin and then check the state of the opposite pin to see it's state which would tell you in which direction the encoder was turned. Your code doesn't do that and will never work properly.

One more thing... you do need to declare encoder0Pos as volatile. Failing to do that can result in inconsistencies in the variable value.

Hope this helps.
Skystream is offline Find More Posts by Skystream
Last edited by Skystream; Apr 15, 2014 at 11:24 PM.
Reply With Quote
Old Apr 16, 2014, 02:33 AM
Registered User
Dutchraptor's Avatar
The Netherlands, ZH, Hoogvliet
Joined Jul 2008
270 Posts
Skystream,
Let me get this straight, I try to understand. An interupt is a kind of hardware switch that, no matter what the processor is doing, it stops the proces and does what it said to do when the interupt is triggered.

I got your version to work, but it still keeps missing steps or returns 1 or 2 steps while going up. Or it skips a lot of steps. I think that there is something wrong with my Rotary or it's a different kind, I don't know.

Danny
Dutchraptor is online now Find More Posts by Dutchraptor
Reply With Quote
Old Apr 16, 2014, 12:57 PM
Registered User
Joined Dec 2012
95 Posts
Yes, an interrupt will "freeze" execution of the current function and switch to execute whatever is inside your interrupt function. When that function ends, the micro goes back to where it left off before execution froze on the main thread. Generally you want to keep the interrupt code small and as "tight" as possible.

I think you are mistaken when you say it is skipping steps. The interrupt functions are going to be called each and every time the encoder state changes. You have to remember that the encoder code is decoupled from your main loop, and that is making you *think* it is skipping steps. Here's an example.

Let's say you have the following main loop:
Code:
void loop()
{
   Serial.println(encoder0Pos);
   delay(1000);
}
This main loop prints whatever the current value of the encoder0Pos is once a second. When the program first starts, it would print "0" once a second. Turn the encoder a single step, and the next time it prints, it should print either "1" or "-1" depending on which way you turned the encoder. Now give the encoder a quick twist in one direction. The next time the serial data prints, it will be a bigger number, say 31. So it went from 0, to 1, to 31. What happened? Did it skip between 1 and 31? No, it didn't. It counted each and every single step between 1 and 31. Remember what I said about your interrupt code being decoupled from your main loop? Your main loop never printed the values 2 through 30 because it was in the "delay()" statement for 1 second. Meanwhile the interrupt code was fired 30+ times and didn't miss a single step.

Believe it or not, this is exactly the way you want it to happen. You don't care that the encoder went from 1 to 31. All you care about is what the current value of encoder0Pos is. If you are writing code in your main loop that is supposed to do something with each every step the encoder is turned, then you're kind of of missing the point, but you can still do that if you really want to. All you have to do is save the previous "sample" of the encoder so that you can compare it with the current "sample". Here's an example:

Code:
int prevsample = 0;
void loop()
{
  int currentsample = encoder0Pos;
   if (currentsample != prevsample)
  {
    Serial.print("Current Position=");
    Serial.print(currentsample);
  }
  if (currentsample > prevsample)
  {
    Serial.print(" encoder was turned ");
    Serial.print(currentsample-prevsample);
    Serial.println(" steps to the right.");
  } 
  else if (currentsample < prevsample)
  {
    Serial.print(" encoder was turned ");
    Serial.print(prevsample - currentsample);
    Serial.println(" steps to the left.");
  }

  // save previous sample
  prevsample = currentsample;
  delay(1000);
}
In this example, you'll know exactly how many steps and in which direction the encoder was turned since the last time the loop() was executed. Play around with the delay value. You'll see that it doesn't matter if you wait 1 second or 20ms, you'll still know how many steps the encoder was turned. Also nothing is printed when the encoder isn't being turned. If you decrease the delay, you'll see more serial messages per second with a smaller delta in steps, and if you increase the delay, you'll see less serial messages but greater deltas. But you will always know exactly how many steps the encoder was turned. If you have a 48 step encoder, and you turn it exactly 48 steps, you'll see all 48 steps accounted for no matter what the delay is.
Skystream is offline Find More Posts by Skystream
Last edited by Skystream; Apr 16, 2014 at 01:02 PM.
Reply With Quote
Old Apr 16, 2014, 02:19 PM
Registered User
Dutchraptor's Avatar
The Netherlands, ZH, Hoogvliet
Joined Jul 2008
270 Posts
OK, thanks for the great info. I changed the code with your addition and yes, it's all clear now. It doesn't skip, it's just very fast.

Danny
Dutchraptor is online now Find More Posts by Dutchraptor
Reply With Quote
Old Apr 16, 2014, 03:44 PM
Registered User
Joined Dec 2012
95 Posts
No problem. The advantage of using the interrupts is that you won't lose track of exactly how many steps the encoder was turned. Doing the reads in the main loop like you were doing is actually prone to losing steps and would be inaccurate.
Skystream is offline Find More Posts by Skystream
Reply With Quote
Old Apr 19, 2014, 06:19 PM
Registered User
United States, PA
Joined Feb 2013
87 Posts
Danny,

The only board I could find locally is this "strip board" (photo below). It is different than the board you are using, all the holes in each row are connected by copper conductor. Can I use this? I am thinking if I cut the copper between the holes in the right places, it could work, or should I just scrap it and try and find the type you are using?

HD
Heavy_Duty is offline Find More Posts by Heavy_Duty
Reply With Quote
Old Apr 19, 2014, 06:56 PM
Registered User
Dutchraptor's Avatar
The Netherlands, ZH, Hoogvliet
Joined Jul 2008
270 Posts
@HD: Sure it will work. In some cases it's even more handy.

I've been debugging for a couple of days, just to fnd out that the mega doesn't have a very stable 3.3 volt output. Beware of that. changed to code to make reservations for people who want to use the RX/TX port to use an extra Arduino nano which then sends the codes to the NRF unit. The direct access is still in place. You can choose with the define which route you want to go. With the old version it worked well, but now with fewer parts and more options, it sometimes stops or gets overloaded.
And there are more options available. With the standard analogInSerialOut program you can find the ranges of the pots. You can use them by changing values from line 97 and further.

To test the receiving side, I used a little program I've used in the past to see if any codes come through the line. It print to the serial window and has a leddisplay for the channel number. It's optional since it will also appear in the serial window.

Have fun. Let me know if any of you did this with an Original Arduino Mega and got the3.3 working stable. The definable pins for the nrf lib I used are pin 8 and 9.

Good luck. I keep you guys posted about the seperate Nano. A seperate unit just for transmission has it's own positives and negatives.

Danny
Dutchraptor is online now Find More Posts by Dutchraptor
Reply With Quote
Old Apr 20, 2014, 06:05 AM
Registered User
Dutchraptor's Avatar
The Netherlands, ZH, Hoogvliet
Joined Jul 2008
270 Posts
Small update. The extra arduino works. It picks up the serial command and send it to the NRF. The Nano/Uno are way better when it comes to supplying the NRF. Now I have to wait a few days for my voltage regulators. Then I can check if the NRF and direct control from the Mega still works and if the NRF gets a more stable connection.

Danny
Dutchraptor is online now Find More Posts by Dutchraptor
Reply With Quote
Reply


Thread Tools

Similar Threads
Category Thread Thread Starter Forum Replies Last Post
New Product Open Source Lights - Arduino based RC Light Controller LukeZ DIY Electronics 14 Apr 10, 2014 10:03 PM
Discussion Arduino Uno Based Flight Controller? alexgator Multirotor Electronics 4 Feb 15, 2013 12:49 AM
Discussion I want to auto controlled RC helicopter with arduino+wmp+nunchuk parkjungho Electric Heli Talk 3 Jun 21, 2011 02:58 PM
Discussion Arduino project to build autopilot controlled RC plane! Ecibob Electric Plane Talk 2 Apr 25, 2011 10:17 AM