I'm new to this forum, and this is my first post, so I'll try to explain in as best detail as I can an issue that I'd like some help resolving.
I'm using an Arduino Mega, and I'd like to be able to use more than the 6 interrupts available (to be honest, I need just one more - my interface calls for 7 rotary encoders which I would like to use interrupts to read).
I've read through basically everything I can find, and tried using the sample PcInt code from the playground which I found here:
playground/Main/PcInt
But I've had little luck getting it to work. It seems I'm just not capable enough of a programmer to sift through all of the code in this example that actually sets an interrupt on a specific pin (not one of the default pins).
Any help tackling this problem would be greatly appreciated.
So I've answered my own post - to an extent. Seems a minor bug was holding me back in defining additional interrupts. HOWEVER -
I've noticed that my code for reading the rotary encoder behaves quite differently when being called from the PCattachInterrupt function than from the standard attachInterrupt function. My code reads the encoder perfectly when called from attachInterrupt, but when I call it from PCattachInterrupt I get much more inconsistent results. Maybe it's a timing issue, or maybe I'm not setting up a pull-up resistor right?
Unless you are doing a very, very small amount of processing inside your ISR, it is probably not a good idea to make extensive use of interrupts. Search around the forums for various conversations about this, but the issue is that they block other actions from continuing.
The typical scheme is to only set a global flag inside the routine and then immediately return to the main loop, where you can be continuously checking your state variables.
Often the best strategy to get help in the forum is to post your code (use the "#" button above) so that people can take a look at what you are doing and offer suggestions.
Thanks for the suggestion. So here is my code - basically ripped from the PCInt playground example and integrated with a simple function that controls an LED's brightness (using PWM) with a rotary encoder.
#include "pins_arduino.h"
volatile uint8_t *port_to_pcmask[] = {
&PCMSK0,
&PCMSK1,
&PCMSK2
};
typedef void (*voidFuncPtr)(void);
volatile static voidFuncPtr PCintFunc[24] = {
NULL };
volatile static uint8_t PCintLast[3];
/*
* attach an interrupt to a specific pin using pin change interrupts.
* First version only supports CHANGE mode.
*/
void PCattachInterrupt(uint8_t pin, void (*userFunc)(void), int mode) {
uint8_t bit = digitalPinToBitMask(pin);
uint8_t port = digitalPinToPort(pin);
uint8_t slot;
volatile uint8_t *pcmask;
if (mode != CHANGE) {
return;
}
// map pin to PCIR register
if (port == NOT_A_PORT) {
return;
}
else {
port -= 2;
pcmask = port_to_pcmask[port];
}
slot = port * 8 + (pin % 8);
PCintFunc[slot] = userFunc;
// set the mask
*pcmask |= bit;
// enable the interrupt
PCICR |= 0x01 << port;
}
void PCdetachInterrupt(uint8_t pin) {
uint8_t bit = digitalPinToBitMask(pin);
uint8_t port = digitalPinToPort(pin);
volatile uint8_t *pcmask;
// map pin to PCIR register
if (port == NOT_A_PORT) {
return;
}
else {
port -= 2;
pcmask = port_to_pcmask[port];
}
// disable the mask.
*pcmask &= ~bit;
// if that's the last one, disable the interrupt.
if (*pcmask == 0) {
PCICR &= ~(0x01 << port);
}
}
// common code for isr handler. "port" is the PCINT number.
// there isn't really a good way to back-map ports and masks to pins.
static void PCint(uint8_t port) {
uint8_t bit;
uint8_t curr;
uint8_t mask;
uint8_t pin;
// get the pin states for the indicated port.
curr = *portInputRegister(port+2);
mask = curr ^ PCintLast[port];
PCintLast[port] = curr;
// mask is pins that have changed. screen out non pcint pins.
if ((mask &= *port_to_pcmask[port]) == 0) {
return;
}
// mask is pcint pins that have changed.
for (uint8_t i=0; i < 8; i++) {
bit = 0x01 << i;
if (bit & mask) {
pin = port * 8 + i;
if (PCintFunc[pin] != NULL) {
PCintFunc[pin]();
}
}
}
}
SIGNAL(PCINT0_vect) {
PCint(0);
}
SIGNAL(PCINT1_vect) {
PCint(1);
}
SIGNAL(PCINT2_vect) {
PCint(2);
}
int encoder0PinA = 7;
int encoder0PinB = 6;
int LEDPinA = 13;
volatile unsigned int encoder0Pos = 0;
void setup() {
pinMode(LEDPinA, OUTPUT);
pinMode(encoder0PinA, INPUT);
digitalWrite(encoder0PinA, HIGH); // turn on pullup resistor
pinMode(encoder0PinB, INPUT);
digitalWrite(encoder0PinB, HIGH); // turn on pullup resistor
// attachInterrupt(0, doEncoder, CHANGE);
// attachInterrupt(1, doEncoder, CHANGE);
PCattachInterrupt(encoder0PinA, doEncoder, CHANGE);
// PCattachInterrupt(encoder0PinB, doEncoder, CHANGE);
Serial.begin (9600);
Serial.println("start");
}
void loop(){
// do some stuff here - the joy of interrupts is that they take care of themselves
}
void doEncoder(){
if (digitalRead(encoder0PinA) == HIGH) { // found a low-to-high on channel A
if (digitalRead(encoder0PinB) == LOW) { // check channel B to see which way
// encoder is turning
if (encoder0Pos > 0) {encoder0Pos = encoder0Pos - 1;} // CCW
}
else {
if (encoder0Pos <= 255) {encoder0Pos = encoder0Pos + 1;} // CW
}
}
else // found a high-to-low on channel A
{
if (digitalRead(encoder0PinB) == LOW) { // check channel B to see which way
// encoder is turning
if (encoder0Pos <= 255) {encoder0Pos = encoder0Pos + 1;} // CW
}
else {
if (encoder0Pos > 0) {encoder0Pos = encoder0Pos - 1;} // CCW
}
}
Serial.println (encoder0Pos, DEC); // debug - remember to comment out
// before final program run
analogWrite(LEDPinA, encoder0Pos);
// you don't want serial slowing down your program if not needed
}
Hope one of you can help me figure out exactly what it is the PCInt code is doing, especially in the PCAttachInterrupt function. I'm not 100% keen on how to program at the microcontroller level, but I'd love to know how to get this working. Again, I'm trying to get an additional pin or two functioning as an interrupt on the Arduino Mega.
Hmm, this low-level code is beyond my familiarity and interest, at this point.
I would still be asking why you need to use interrupts for the application, and consider if you could stay at the basic app-level coding and just continuously poll all the devices from the main loop.
I suppose I will wind up doing just that - my impression was that interrupts are the best way to handle rotary encoders in general. My project calls for quite a few rotary encoders - perhaps continuous polling of the encoders in the main loop might be the best way to go.
Can anyone be so kind as to point me in the direction of some nice example code for reading rotary encoders in the main loop?
Well personally I would not really on software to cope with a lot of rotary encoders, it's OK for a few but you have to look at them too often to make sure you don't miss a pulse. If you use hardware to isolate step and direction the software polling is much less onerous.
I have used this hardware in the past:-