Skip to content

Commit

Permalink
Encoder: added rotary encoder library and example
Browse files Browse the repository at this point in the history
  • Loading branch information
giuliomoro committed Jun 10, 2020
1 parent 254c334 commit ec39d45
Show file tree
Hide file tree
Showing 4 changed files with 248 additions and 0 deletions.
87 changes: 87 additions & 0 deletions examples/Sensors/rotary-encoder/render.cpp
@@ -0,0 +1,87 @@
/*
____ _____ _ _
| __ )| ____| | / \
| _ \| _| | | / _ \
| |_) | |___| |___ / ___ \
|____/|_____|_____/_/ \_\
http://bela.io
*/
/**
\example Sensors/rotary-encoder/render.cpp
Rotary Encoder
================
Connect a rotary encoder to Bela's digital inputs, detect the direction of the
rotation, and keep track of how far you've gone.
If your encoder has a built-in switch, you can use that to reset the current count.
*/

#include <Bela.h>
#include <libraries/Scope/Scope.h>
#include <libraries/Encoder/Encoder.h>

Encoder gEncoder;
Scope gScope;
// Bela digital input channels connected to the encoder and button
unsigned int kEncChA = 0;
unsigned int kEncChB = 1;
unsigned int kEncChBtn = 2;

// adjust the values below based on your encoder and wiring
unsigned int kDebouncingSamples = 15;
Encoder::Polarity polarity = Encoder::ANY; // could be ANY, ACTIVE_LOW, ACTIVE_HIGH

bool setup(BelaContext *context, void *userData)
{
gScope.setup(3, context->audioSampleRate);
gEncoder.setup(kDebouncingSamples, polarity);

// Set the digital pins to inputs
pinMode(context, 0, kEncChA, INPUT);
pinMode(context, 0, kEncChB, INPUT);
pinMode(context, 0, kEncChBtn, INPUT);
return true;
}

bool gButton;
unsigned int holdoff;
void render(BelaContext *context, void *userData)
{
for(unsigned int n=0; n<context->digitalFrames; n++){
bool a = digitalRead(context, n, kEncChA);
bool b = digitalRead(context, n, kEncChB);
bool button = digitalRead(context, n, kEncChBtn);
Encoder::Rotation ret = gEncoder.process(a, b);
if(Encoder::NONE != ret)
{
rt_printf("%s : %3d\n", Encoder::CCW == ret ? "ccw" : "cw ", gEncoder.get());
}
if(gButton != button) {
gButton = button;
if(button) {
gEncoder.reset();
rt_printf("reset : %3d\n", gEncoder.get());
}
}
if(1)
{
if(Encoder::CCW == ret)
button = 1;
else
button = 0;

}
gScope.log( // log the values with a vertical offset to make them easier to see
a * 0.5 + 0.333,
b * 0.5 - 0.333,
button * 0.5 - 0.88
);
}
}

void cleanup(BelaContext *context, void *userData)
{
}
66 changes: 66 additions & 0 deletions libraries/Encoder/Encoder.cpp
@@ -0,0 +1,66 @@
#include "Encoder.h"
#include <Bela.h>

void Encoder::setup(unsigned int debounce, Polarity polarity)
{
debounce_ = debounce;
reset();
polarity_ = polarity;
debouncing_ = 0;
primed_ = false;
}

void Encoder::reset(int position)
{
position_ = position;
}

bool Encoder::validEdge(bool a)
{
return (
ANY == polarity_
|| (ACTIVE_LOW == polarity_ && !a)
|| (ACTIVE_HIGH == polarity_ && a)
);
}

Encoder::Rotation Encoder::process(bool a, bool b)
{
Rotation ret = NONE;
if(!primed_) {
lastA_ = a_ = a;
primed_ = true;
}
if(a != lastA_) {
if(validEdge(a))
debouncing_ = debounce_ + 1;
else
a_ = a;
}

// wait for the data to be stable long enough
// before checking for an updated value
if(1 == debouncing_ && a_ != a)
{
a_ = a;
if(validEdge(a))
{
if(b == a)
ret = CCW;
else
ret = CW;
position_ += ret;
}
}
if(debouncing_)
--debouncing_;
lastA_ = a;
return ret;
}

int Encoder::get()
{
return position_;
}


78 changes: 78 additions & 0 deletions libraries/Encoder/Encoder.h
@@ -0,0 +1,78 @@
#pragma once

/**
* @brief Connect a quadrature rotary encoder.
*
* Connect a quadrature rotary encoder.
*
* It provides a debouncer to ignore spurious readings due to mechanical
* bouncing of the contacts.
*
* It contains an internal counter which tracks how many steps the encoder has
* moved from its initial position. Position is incremented when it moves
* clockwise and decremented when it movew counter-clockwise.
*/
class Encoder
{
public:
typedef enum {
CCW = -1, ///< The encoder rotated counter-clockwise
NONE = 0, ///< The encoder did not rotate
CW = 1, ///< The encoder rotate clockwise
} Rotation;

/**
* Which edge of the `a` signal denotes a transition.
*/
typedef enum {
ANY, ///< Trigger on any edge
ACTIVE_LOW, ///< Trigger on negative edges
ACTIVE_HIGH, ///< Trigger on negative edges
} Polarity;

Encoder() { setup(0, ANY); };

/**
* Same as setup().
*/
Encoder(unsigned int debounce, Polarity polarity = ANY) { setup(debounce, polarity); };

/**
* Set up the encoder with a given debounce interval.
*
* @param debounce the number of calls to process() during which
* changes are ignored after a change in `a` has been detected.
* @param polarity on which edge to trigger.
*
*/
void setup(unsigned int debounce, Polarity polarity = ANY);

/**
* Sets the current position of the encoder to the specified value.
*
* @param position The new position (defaults to 0).
*/
void reset(int position = 0);

/**
* Process new readings from the encoder's terminals.
*
* @return whether, and in which direction, the encoder has moved in
* this last step.
*/
Rotation process(bool a, bool b);

/**
* Get the position of the encoder.
*/
int get();
private:
bool validEdge(bool a);
unsigned int debouncing_;
unsigned int debounce_;
int position_;
Polarity polarity_;
bool a_;
bool lastA_;
bool primed_;
};
17 changes: 17 additions & 0 deletions libraries/Encoder/lib.metadata
@@ -0,0 +1,17 @@
name=Encoder
version=1.0.0
author=Giulio Moro<giulio@bela.io>
maintainer=Giulio Moro<giulio@bela.io>>
description=A class to connect a quadrature encoder.
examples=Sensors/rotary-encoder
license=LGPL 3.0
url=
board=*
dependencies=
LDFLAGS=
LDLIBS=
CXXFLAGS=
CC=
CXX=
CFLAGS=
CPPFLAGS=

0 comments on commit ec39d45

Please sign in to comment.