Hi,
I am trying to make a rotary encoder work with my Ardino 101. Right now I am working with a rotary knob but later I will also read a motor encoder. So I will need something faster than digitalRead(). Sadly, the great encoder.h library doesn’t work with my 101.
To avoid port register manipulation, I am just counting the interrupts. Also I couldn't find out if the port registers work the same way on the 101 as on the avr chips.
So the approach bellow works, however every 10 turns, or so, it counts 3 or 5 interrupts instead of 4 and then logically starts counting backwards. Can this be solved with programming or perhaps with external pullups or capacitors? I am not so fit hardwarewise.
PaulS:
For most Arduinos, the 1st argument is the interrupt number, not the pin number.
On the 101 interrupt number equals the pin number.
PaulS:
How fast are the interrupts actually happening?
How can I test that? The interrupt function itself takes about 3 micro seconds. but I can't think of a way right now to measure the time between the electric signal and the beginning of the interrupt function.
PaulS:
Is the problem that the encoder is bouncing? Or is it something else?
I don't know. The results get worse if I go above or below 1 millisecond of debouncing time. It is a really nice 3€ encoder made in Japan. One would expect good results from it.
How can I test that? The interrupt function itself takes about 3 micro seconds. but I can't think of a way right now to measure the time between the electric signal and the beginning of the interrupt function.
What I want to know is how often, when you rotate the encoder, the interrupt happens. While Serial.print() statements in ISRs are generally now a good thing, sometimes you just have to take the risk. Use Serial.print() to print the value of micros() each time the interrupt happens. Show the output.
/*
Rev 1.00: July 25, 2015, econjack
*/
#include <rotary.h>
#define ROTARYSWITCHPIN 7 // Used by switch for rotary encoder
#define ELEMENTCOUNT(x) (sizeof(x) / sizeof(x[0])) // A macro that calculates the number
// of elements in an array.
// ISR variables:
volatile int rotationDirection; // + is CW, - is CCW
int menuSelection; // Which menu is active
int menuIndex; // Which menu option is active
#define MAXMENUS 3 // We have the following menus
static char *filters[] = {"500Hz", "1kHz", "1.5kHz", "2kHz", "3kHz"};
static char* incrementStrings[] = {"10", "20", "100", ".5", "1", "2.5", "5", "10", "100"};
static long favorites[] = {7030, 7195, 14250, 21300, 28500};
Rotary r = Rotary(2, 3); // sets the pins the rotary encoder uses. Must be interrupt pins.
void setup() {
Serial.begin(115200); // Activate the Serial object. Make sure you set the Monitor to the same rate
pinMode(ROTARYSWITCHPIN, INPUT_PULLUP); // Use the pullup resistors on the switch pin
Splash();
PCICR |= (1 << PCIE2); // Pin Change Interrupt Enable 2 to set the Pin Change Interrupt Control Register
PCMSK2 |= (1 << PCINT18) | (1 << PCINT19); // Set up pins 2 and 3 for the control mask
sei(); // Set interrupts flag
rotationDirection = 0;
unsigned int radChanValue = (9 & 0x001f) >> 0;
lcd.setCursor(0, 0);
lcd.print("CHN:");
lcd.setCursor(4, 0);
lcd.print(radChanValue);
}
void loop() {
static int switchState = 1;
if (Serial.available() > 0) { // Anything waiting in the Serial buffer??
menuSelection = Serial.read() - '0';
if (menuSelection < 1 || menuSelection > MAXMENUS) {
menuSelection = 1; // If they can't follow directions, use menu 1.
}
menuIndex = 0;
MenuHeader();
ShowCurrentSelection();
}
switchState = digitalRead(ROTARYSWITCHPIN);
if (switchState == LOW) { // Switch pressed?
delay(200); // Yep...
MenuHeader();
menuIndex = 0;
menuSelection++;
if (menuSelection > MAXMENUS)
menuSelection = 0;
ShowCurrentSelection();
}
if (rotationDirection != 0) { // Encoder rotated??
menuIndex += rotationDirection; // Yep...
ShowCurrentSelection();
rotationDirection = 0;
}
}
/*****
This routine displays a simple menu header
Parameter List:
void
Return value:
void
*****/
void MenuHeader()
{
Serial.println();
Serial.println("====== New Menu Selection =====");
Serial.println();
}
/*****
This routine displays a simple menu selection sequence on the Serial monitor
Parameter List:
void
Return value:
void
*****/
void Splash()
{
Serial.println();
Serial.println("Rotary Encoder Routines");
Serial.println("Menu Selections: ");
Serial.println(" 1. Favorite Frequencies");
Serial.println(" 2. Filters");
Serial.println(" 3. Frequency Increments");
Serial.println("");
Serial.println("Enter 1, 2, or 3 above");
}
/*****
This routine displays a menu item from the current menu selection
Parameter List:
void
Return value:
void
CAUTION: This function assume menuIndex and menuSelection are set
prior to the call.
*****/
void ShowCurrentSelection()
{
if (menuIndex < 0) { // Should not be negative
menuIndex = 0;
}
switch (menuSelection) {
case 1:
if (menuIndex >= ELEMENTCOUNT(favorites)) { // Rotated too far??
menuIndex = 0;
}
Serial.println(favorites[menuIndex]);
break;
case 2:
if (menuIndex >= ELEMENTCOUNT(filters))
menuIndex = 0;
Serial.println(filters[menuIndex]);
break;
case 3:
if (menuIndex >= ELEMENTCOUNT(incrementStrings))
menuIndex = 0;
Serial.println(incrementStrings[menuIndex]);
break;
default:
menuIndex = 0; // Start over with choice
menuSelection = 1;
Splash();
break;
}
}
/*****
This is the Interrupt Service Routine for the rotary encoder
Parameter List:
PCINT2_vec Pin change interrupt request #2, acts on pins D0 to D7)
Return value:
void
*****/
ISR(PCINT2_vect) {
unsigned char result = r.process();
switch (result) {
case 0: // Nothing done...
return;
case DIR_CW: // Turning Clockwise, going to higher frequencies
rotationDirection = 1;
break;
case DIR_CCW: // Turning Counter-Clockwise, going to lower frequencies
rotationDirection = -1;
break;
default: // Should never be here
break;
}
}
It's a program for a rotary encoder demo for some ham radio guys. Ignore the menus; just see if the ISR performs any differently for you. Also, I had 0.1uF caps solder to the clock and data lines to ground to help tame down the pulse chain. Also, the encoder has a built-in switch which I tied to pin 7.
Edit: I also used a 2x16 LCD display for the demo.
Ok so I changed to digitalRead(). My code above works in theory, but only when no misreadings occur ever. However, even with digitalRead() the results are very wonky.
@PaulS: Here are the timings. On the top: Outside the debounce function. Bellow: Inside the debounce function. On the right: The difference.
It appears that my debouncing was just a placebo, but the timings don't make sense to me. The bonces should be shorter than the normal contacts. The Serial.write() adds about 6000 micro seconds to the function. @econjack: Thanks I will test your code.
@econjack: I can't find a function where you are actually reading the encoder. I am guessing it is in the rotary.h library. Do you have a link? And does it rely on port registry manipulation? I can't use it if it does because the Arduino 101 has a very different chip.
The ISR is just setting rotationDirection, which is either CW or CCW. The menu example simply responds to the direction of rotation. If the encoder is not being moved, the ISR returns 0 and loop() ignores it:
Now it works great! This method of debouncing should be on the playground. I wonder if it is possible to trigger the interrupt from the common-pin of the encoder to free up one interrupt pin.