Go Down

Topic: AdaEncoder: a library for use with simple quadrature encoders (Read 5 times) previous topic - next topic

GreyGnome


Well, I tried using the 0.7beta.  ... But I can't get it to work properly. ... I'm going back to 0.5.

---
I do have a few questions on the sample MyEncoder in 0.7beta

On Line# 15, we have this code
AdaEncoder encoderA = AdaEncoder('a', ENCA_a, ENCA_b);

but nowhere else in the program is "encoderA" used. What's the purpose of line#15?

Then on line#30, I see this
AdaEncoder *thisEncoder=NULL;

*thisEncoder is used in the loop() program.  But how does Arduino know how *thisEncoder is setup?  We defined encoder pins 2 and 3 for "encoderA", not "thisEncoder".


I'm sorry it didn't work out for you. It is a little more Object-Oriented like, so it's not everyone's cup of tea.

Also I'm sorry I am late in replying. This thread is not intended for support; there is a support link on the code.google.com page for that. Anyway, I will answer your questions for posterity:

The code is an example that shows you how to set up 2 encoders, A and B. encoderA and encoderB are designed to encapsulate all the important data within, so you can think about "encoders" rather than the pins of your Arduino. It's a conceptual change over version 0.5 of the library, really.

In the old days, then, of version 0.5, the genie() method would fill in the values for "clicks" and "id" for you, so you could then query the variables and ask, "Who turned?  How far?".

Now in version 0.7, the genie() method returns a pointer to an AdaEncoder object. The "thisEncoder" variable is set to NULL on every iteration of loop. Then, if genie() finds a changed encoder, it will send back the pointer to the AdaEncoder object, and now you can do whatever you want with this encoder that you've found. It sends back either encoderA or encoderB- whichever one was turned. "thisEncoder", then, changes its identity to that of either A or B. I have done the simplest thing, which is to have it query itself and return the number of times it was clicked.

By using Object Oriented techniques, you are not bound to if/then statements based on values (like id numbers) of the encoders. Each encoder object intrinsically knows who it is; you can even subclass the encoder and have your subclass do special things simply based on its identity.

On this small of a scale it may be difficult to see any kind of win at all, that I'll grant. But I think it's interesting, and I like the way Object Oriented programming makes me adjust my way of thinking, so I have decided to go that route. Thankfully the AdaEncoder library is simple enough that, in the last year, I haven't moved from the functionality I created in version 0.5. The old one should continue to be useful to people.  Although I do recommend an upgrade of the underlying PinChangeInt library.



vasquo

Thanks for your library, but my problem is solved. It's working fine now.

As I said, I just need to change a couple of lines in your AdaEncoder.cpp.
But of course, the changes I did are hardcoded specific to the PEC09 Bourns encoder I'm using.

If on another project, I use a different encoder, I may need to change the AdaEncoder.cpp again (back to your original code).

I'll try to submit some code to maybe implement either the LOW/LOW-Detent or the HIGH/HIGH-Detent condition so the library can execute what's appropriate for the encoder type.

GreyGnome

Thanks, vasquo. Anyway, your post prompted me to create some test code, and I hope to use this to ensure the integrity of the library in the future... (this works for v. 0.7beta and above only, but the basic algorithm applies to 0.5 as well)

Code: [Select]

// Version 1.0: OO version

#include <ByteBuffer.h>
#include <digitalWriteFast.h>
#include <ooPinChangeInt.h> // necessary otherwise we get undefined reference errors.
#define DEBUG
#ifdef DEBUG
ByteBuffer printBuffer(200);
#endif
#define SWINTR_DEBUG // To debug using software interrupts on your two pins, define this.
                   // Then in your sketch, set your pins as outputs.  Initialize them as you
                   // desire, attach an interrupt, then the interrupt code will be called.
                   // CAUTION: Make sure you do NOT have any switches connected to those outputs,
                   // or you may end up frying your ATmega328!
#include <AdaEncoder.h>

/*
I like to keep this diagram around so I know which Arduino pin
is part of which port.
                 +-\/-+
           PC6  1|    |28  PC5 (AI 5)
     (D 0) PD0  2|    |27  PC4 (AI 4)
     (D 1) PD1  3|    |26  PC3 (AI 3)
     (D 2) PD2  4|    |25  PC2 (AI 2)
PWM+ (D 3) PD3  5|    |24  PC1 (AI 1)
     (D 4) PD4  6|    |23  PC0 (AI 0)
           VCC  7|    |22  GND
           GND  8|    |21  AREF
           PB6  9|    |20  AVCC
           PB7 10|    |19  PB5 (D 13)
PWM+ (D 5) PD5 11|    |18  PB4 (D 12)
PWM+ (D 6) PD6 12|    |17  PB3 (D 11) PWM
     (D 7) PD7 13|    |16  PB2 (D 10) PWM
     (D 8) PB0 14|    |15  PB1 (D 9) PWM
                 +----+*/
// An encoder for every port
#define ENCD_a 2
#define ENCD_b 3
#define ENCC_a A3
#define ENCC_b A4
#define ENCB_a 9
#define ENCB_b 10

AdaEncoder encoderD = AdaEncoder('d', ENCD_a, ENCD_b);
AdaEncoder encoderC = AdaEncoder('c', ENCC_a, ENCC_b);
AdaEncoder encoderB = AdaEncoder('b', ENCB_a, ENCB_b);

int8_t clicks=0;
char id=0;

void setup()
{
 Serial.begin(115200); Serial.println("---------------------------------------");
 Serial.println("AdaEncoder test. Assumes detent position is 1,1 (pin a, pin b).");
 Serial.println("Encoder states: 11->01->00->10->11 (== CCW 1 click)");
 volatile uint8_t portmask0=PCMSK0;
 volatile uint8_t portmask1=PCMSK1;
 volatile uint8_t portmask2=PCMSK2;
 PCMSK0=0; PCMSK1=0; PCMSK2=0; // turn off PinChange interrupts for a minnit
 pinMode(ENCB_a, OUTPUT); pinMode(ENCB_b, OUTPUT);
 digitalWriteFast2(ENCB_a, HIGH); digitalWriteFast2(ENCB_b, HIGH);
 pinMode(ENCC_a, OUTPUT); pinMode(ENCC_b, OUTPUT);
 digitalWriteFast2(ENCC_a, HIGH); digitalWriteFast2(ENCC_b, HIGH);
 pinMode(ENCD_a, OUTPUT); pinMode(ENCD_b, OUTPUT);
 digitalWriteFast2(ENCD_a, HIGH); digitalWriteFast2(ENCD_b, HIGH);
 PCMSK0=portmask0; // interrupts back on.
 PCMSK1=portmask1;
 PCMSK2=portmask2;
}

void changeEncoder(uint8_t pin) {
 if (digitalReadFast2(pin)) { digitalWriteFast2(pin, LOW); }
 else digitalWriteFast2(pin, HIGH);
}

void printEncoderState(uint8_t pina, uint8_t pinb) {
 Serial.print(digitalReadFast2(pina), DEC);
 Serial.println(digitalReadFast2(pinb), DEC);
}

void loop()
{
 char inChar, outChar;
 // 00 -> 10 -> 11 -> 01 -> 00
 if(Serial.available()) {
   inChar=(Serial.read()); // get command from serial input
   switch (inChar) {
   case '1':
       Serial.print("B-a "); changeEncoder(ENCB_a); printEncoderState(ENCB_a, ENCB_b);
     break;
   case '2':
       Serial.print("B-b "); changeEncoder(ENCB_b); printEncoderState(ENCB_a, ENCB_b);
     break;
   case '5':
       Serial.print("C-a "); changeEncoder(ENCC_a); printEncoderState(ENCC_a, ENCC_b);
     break;
   case '6':
       Serial.print("C-b "); changeEncoder(ENCC_b); printEncoderState(ENCC_a, ENCC_b);
     break;
     case '9':
       Serial.print("D-a "); changeEncoder(ENCD_a); printEncoderState(ENCD_a, ENCD_b);
     break;
     case '0':
       Serial.print("D-b "); changeEncoder(ENCD_b); printEncoderState(ENCD_a, ENCD_b);
     break;
   }
 }
 while ((outChar=(char)printBuffer.get()) != 0) Serial.print(outChar);
 AdaEncoder *thisEncoder=NULL;
 thisEncoder=AdaEncoder::genie();
 if (thisEncoder != NULL) {
   Serial.print(thisEncoder->getID()); Serial.print(':');
   clicks=thisEncoder->query();
   if (clicks != 0) { Serial.print("Clicks: "); Serial.print(clicks, DEC); }
   if (clicks > 0) {
     Serial.println(" CW");
   }
   if (clicks < 0) {
      Serial.println(" CCW");
   }
 }
}


...If I then hit a sequence of numbers in the Monitor screen, I can then demonstrate that the library behaves as expected.

For example, if I hit the following sequence, I simulate switch bounce on the two pins and it registers "CCW" exactly when I expect it:
Code: [Select]

Number Output
Typed
1 B-a 01
1 B-a 11
1 B-a 01
1 B-a 11
1 B-a 01
2 B-b 00
2 B-b 01
2 B-b 00
2 B-b 01
2 B-b 00
1 B-a 10
1 B-a 00
1 B-a 10
1 B-a 00
1 B-a 10
2 B-b 11
b:Clicks: -1 CCW

Sleepydoc

Resurrecting an old thread, but there appears to be a minor bug in the routine;

Looking at the AdaEncoder::query() routine in AdaEncoder.cpp, clicks is only incremented/decremented by one each time the routine is called. The problem arrises if the encoder is turned more than one click between queries. If, for example, it is turned 3 clicks and then queried, it will correctly return 3 the first time, but then at the following queries it will return 2 & 1, even though the encoder hasn't moved.

I tested this by simply adding a delay and the end of the example sketch. (The variable total is meant to hold the net number of clicks the encoder has turned):
Code: [Select]
// Version 1.1: OO version
#include <ooPinChangeInt.h> // necessary otherwise we get undefined reference errors.
#include <AdaEncoder.h>

#define ENCA_a 2
#define ENCA_b 3

AdaEncoder encoderA = AdaEncoder('a', ENCA_a, ENCA_b);
int8_t clicks=0;
char id=0;

void setup()
{
  Serial.begin(9600); Serial.println("---------------------------------------");
}
int total = 0;
void loop() {
  char outChar;
  AdaEncoder *thisEncoder=NULL;
  thisEncoder=AdaEncoder::genie();
  if (thisEncoder != NULL) {
    Serial.print(thisEncoder->getID()); Serial.print(':');
    clicks=thisEncoder->query();
    total = total + clicks;
   if (clicks > 0) {
      Serial.print(" CW "); Serial.print(clicks); Serial.println(" Clicks");
    }
    if (clicks < 0) {
       Serial.println(" CCW");
    }
    Serial.print("position: "); Serial.println(total);
   }
  delay(1000);
}


Below is the output - the fist two iterations, the encoder was turned one click and the routine returned 1, as expected.  The 3rd time, it the encoder was turned two clicks between queries. The next time it was queried, it returned "CW 2 Clicks" and the position correctly indicates '4,' but then the at the next time through the main loop, clicks has only been decremented to 1, not 0 and the routine returns 'CW 1 click' despite the encoder not actually moving. It would appear that the query routine should simply reset clicks to 0 after returning the number of clicks, or am I missing something?
Code: [Select]
---------------------------------------
a: CW 1 Clicks
   position: 1
a: CW 1 Clicks
   position: 2
a: CW 2 Clicks
   position: 4
a: CW 1 Clicks
   position: 5


GreyGnome

This is actually a feature, not a bug.  8)  Perhaps it should only have returned a positive or a negative, to indicate any movement since the last query(). But the idea is this:

You have a main routine loop() which takes an arbitrary amount of time to run. Asynchronously, it is interrupted by a rotary encoder going up and down. My thinking is that because of this disconnect, you can't perfectly track the turns of the encoder.

Say I've gone 3 clicks CW and 1 click CCW. Net is 2 clicks CW. Now the main routine does a query(); we discover that we have clicked net CW so we should act on that. Do you want to act on the fact that you've gone 2 clicks CW? Maybe- maybe not. Yes, you have gone CW, so the query() decrements the counter and now indicates 1 click CW. Maybe, while you're processing the first of the two CW clicks, the user actually turns the encoder CCW. Now you are at 0. Do you still want to register the second previous click? I don't know.

If your routine is fast enough, you can query() repeatedly, count the number of query()'s that get you to 0, and move on. But again, based on the disconnected nature of the interrupt and the main loop, I don't want to make any assumptions... that you can act on the current counters fast enough to catch any intermediate moves. If your main routine is slow enough to allow the internal adaencoder counter to accumulate to 3 or 4 clicks, I'm uncomfortable in assuming that it would be able to take the counter and perform 3 or 4 actions. I leave it up to you; drain the counter using repeated query()'s until you get to 0 (should be pretty fast) and then act on that information.

Go Up