// We use 4-character tabstops, so IN VIM: :set ts=4 sw=4 // ...that's: ESCAPE key, colon key, then "s-e-t SPACE key t-s-=-4 SPACE key s-w-=-4 CARRIAGE RETURN key" /* AdaEncoder.cpp - A library for reading from Lady Ada's or Sparkfun's rotary encoder. Version 0.7 Tue Nov 20 17:56:29 CST 2012 Copyright 2011 Michael Schwager (aka, "GreyGnome"). This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Questions? Send mail to mschwage@gmail.com, or see the AdaEncoder Google code page: http://code.google.com/p/adaencoder/ */ #ifndef AdaEncoder_h #include "AdaEncoder.h" #include "turnOffPWM.h" #endif #undef DEBUG #ifdef DEBUG #include "../ByteBuffer/ByteBuffer.h" #include "../GetPSTR/GetPSTR.h" extern ByteBuffer printBuffer; #endif # define cli() __asm__ __volatile__ ("cli" ::) # define sei() __asm__ __volatile__ ("sei" ::) #undef 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! #undef DEBUGTIME // Used to set/reset pin 13 when measuring timings for the code. To create the software // interrupts for that you probably want to set SWINTR_DEBUG, too. /* * NOTES * This library interfaces with 2-pin encoders (2 pins A and B, then a common pin C). * It does not indicate every state change, rather, it reports only when the decoder * is turned from one detent position to the next. It is interrupt-driven and designed * to be fast and easy to use. The interrupt routine is lightweight, and the * programmer is then able to read the direction the encoder turned at their leisure * (within reason; what's important is that the library is reasonably forgiving). The * library is designed to be easy to use and it is reasonably immune to switch bounce. * * Connecting your encoder: * * Refer to the pin chart at http://www.arduino.cc/en/Hacking/PinMapping168. * * The ATmega328 and its kind (ATmega168, ATmega2560) all use PORTs for their * input and outputs. A PORT is essentially a group of pins on the ATmega * processor. They are interesting because the pins grouped in each * PORT share some things in common. For example, you can read all the pins * in a PORT in one command. What this means for you as a designer is that * you should NOT use two different PORTs for the two pins of your rotary * encoder. * * How do you know which pins are common with which PORTs? Look at the pin * mapping diagram as given in the link, above. The pin names closest to * the IC chip: ie, PD0, PD1, PB6, etc., show you the PORTs. B, C, and D are * the three PORTs available on the ATmega168 and 328. There are more PORTs * on the ATmega2560 used in the Arduino Mega, but only those 3 PORTs allow for * Pin Change interrupts on those bigger chips, too. * * So, when you connect your rotary encoder to your Arduino, make sure that its * two pins- which I call PinA and PinB- both attach to the same PORT. This is * a summary of the Arduino-to-port mappings that are available to you: * Arduino Pins PORT * ------------ ---- * Digital 0-7 D * Digital 8-13 B * Analog 0-5 C * * In summary, what you need to do... the ONLY thing you really need to do... * is ensure that both pins of your encoder are attached to pins within those * ranges, above. Don't cross ranges and connect, for example, digital pin 7 * to digital pin 10. It won't work, and the addEncoder method will not configure * your ports. * * Schematic of Rotary Encoder: * +-- A --> Arduino pinA * +-- common --+ * | +-- B --> Arduino pinB * gnd * * Scattered Design notes: * I want a function that will give me the ATmega value of a pin. * Given: Arduino pin number. * Returns: Value of that pin, using PORT commands for speed. * Note: Given an Arduino pin number, * volatile uint8_t *port; * portNumber=digitalPinToPort(LED); // for a the Register lookup table, this does a 1-1 correspondence to a port's address * port=portOutputRegister(ledportNumber); // if doing output, or, * port=portInputRegister(ledportNumber); // ...if doing input * mask=digitalPinToBitMask(LED); // defines the actual pin on that port * * example for output: * *port|=mask; // sets it high * * For interesting port info, See: http://jeelabs.org/2010/01/06/pin-io-performance/ * digitalPinToPort() goes like this: * #define digitalPinToPort(P) ( pgm_read_byte( digital_pin_to_port_PGM + (P) ) ) * const uint8_t PROGMEM digital_pin_to_port_PGM[] = { * PD, // 0 * PD, * PD, * ...etc... * PB, // 8 * PB, * ...etc... * PC, // 14 * #define PB 2 * #define PC 3 * #define PD 4 */ AdaEncoder *firstEncoder=NULL; AdaEncoder *currentEncoder=NULL; #ifdef DEBUGTIME volatile uint8_t *ada_output_port; uint8_t ada_output_mask; uint8_t ada_not_output_mask; #endif AdaEncoder *AdaEncoder::getFirstEncoder() { return firstEncoder; } /* * addEncoder * Arguments: * - id: a single char that identifies this encoder * - pinA: The pin on the Arduino that one side of the encoder plugs into. Turning in this direction means we're * turning clockwise. * - pinB: The Arduino pin connected to the encoder; this is the counterclockwise direction. * * The pins can be any of the digital pins 0-13, or any of the analog pins A0-A5 (aka, 14-19). You can specify the * analog pins as A0, A1, A2, ... etc. * * The pins must be paired in the same port, as described above. In summary this means that the two pins should * together be grouped within a single port; ie, if you connect pinA to digital pin 9, you must connect pinB to * digital pin 8 or 10-13. See this table: * Arduino Pins PORT * ------------ ---- * Digital 0-7 D * Digital 8-13 B * Analog 0-5 C (== digital pins 14-19) */ void AdaEncoder::addEncoder(char _id, uint8_t _pinA, uint8_t _pinB) { AdaEncoder *tmpencoder; id=_id; pinA=_pinA; pinB=_pinB; #ifdef DEBUG printBuffer.putString(getPSTR("addEncoder: ")); printBuffer.put(id); printBuffer.put('\n'); printBuffer.putHex(pinA); printBuffer.put(' '); printBuffer.putHex(pinB); printBuffer.put(' '); printBuffer.putString(getPSTR(" *\n")); #endif // error checking if (pinA == pinB) return; // No! silly if (pinA < 8 && pinB > 7) return; // No! different ports if ((pinA > 7 && pinA < 14) && (pinB < 8 || pinB > 13)) return; // No! different ports if (pinA > 13 && pinB < 14) return; // No! different ports if (pinA > 19 || pinB > 19) return; // No! out of band turning=0; clicks=0; /* ADD TO THE LIST HERE */ if (firstEncoder==NULL) { firstEncoder=this; } else { tmpencoder=firstEncoder; while (tmpencoder->next != NULL) tmpencoder=tmpencoder->next; tmpencoder->next=this; } this->next=NULL; port=portInputRegister(digitalPinToPort(pinA)); if (port == NOT_A_PIN) { return; } // ** A ** bitA=digitalPinToBitMask(pinA); uint8_t timerA=digitalPinToTimer(pinA); if (timerA != NOT_ON_TIMER) turnOffPWM(timerA); #ifdef DEBUG //char buffer[16]; //Serial.print("encoder addr: "); //sprintf(buffer, "%p", this); //Serial.println(buffer); printBuffer.putString("bitA: "); printBuffer.putHex(bitA); #endif // ** B ** bitB=digitalPinToBitMask(pinB); uint8_t timerB=digitalPinToTimer(pinB); if (timerB != NOT_ON_TIMER) turnOffPWM(timerB); #ifdef DEBUG printBuffer.putString("bitB: "); printBuffer.putHex(bitB); #endif // ** INTERRUPT ** #ifndef SWINTR_DEBUG // This section should be on normally. OFF if performing software interrupts! // GreyGnome DEBUG: Turn these two lines off by defining // SWINTR_DEBUG. In the sketch, turn the pins into Outputs and set them high. // Then set them as you wish, to create software interrupts. pinMode(pinA, INPUT); pinMode(pinB, INPUT); digitalWrite(pinA, HIGH); digitalWrite(pinB, HIGH); #endif attachInterrupt(pinA, pinB); #ifdef DEBUGTIME ada_output_port=portOutputRegister(digitalPinToPort(13)); // GreyGnome DEBUG. ada_output_mask=digitalPinToBitMask(13); // defines the actual pin on that port ada_not_output_mask=ada_output_mask^0xFF; #endif } void AdaEncoder::attachInterrupt(uint8_t pinA, uint8_t pinB) { #ifdef DEBUG printBuffer.putString(getPSTR("attachInterrupt: ")); printBuffer.putHex(pinA); printBuffer.put(' '); #endif PCintPort::attachInterrupt(pinA, this, CHANGE); //PCintPort::attachInterrupt(pinB, AdaEncoder::encoderInterrupt, CHANGE); PCintPort::attachInterrupt(pinB, this, CHANGE); #ifdef DEBUG printBuffer.putHex(pinB); printBuffer.put(' '); printBuffer.putString(" *\n"); #endif } /* * cbmethod() * An interrupt happens. Just before calling this, ooPinChangeInt has set the PCintPort::arduinoPin to whatever * pin triggered the interrupt. The circuit is constructed so detent position is pinA == 1, pinB == 1 * So, the order of things goes like this: * - Get the Pin that triggered the interrupt. * - Get the encoder structure affiliated with that pin. * - Get the state of the ports (the ATmega ports associated with the Arduino pins). * - If we are in the detent position, * . and we are turning: It means we have moved a click. Add/subtract to "clicks" to indicate direction. * . set the turning and direction flags to 0. (we do this whether we have turned, or not). * . return * - If we are in the pinA == 0, pinB == 0 position: We are between detents. This means we MUST have passed * through a 1,0 or a 0,1 position. So we already know which direction we are going so now, simply flag that * we are turning. * - If we have not flagged that we're turning, we need to do choose our direction. * . if pinA is 1, we are going CCW * . if pinB is 1, we are going CW */ void AdaEncoder::cbmethod() { uint8_t stateA; uint8_t stateB; uint8_t portState; #ifdef DEBUGTIME *ada_output_port|=ada_output_mask; // sets it high #endif portState=*this->port; stateA=portState & bitA; stateB=portState & bitB; #ifdef DEBUG printBuffer.putString(" id:"); printBuffer.put(id); printBuffer.put(' '); printBuffer.put('A'); printBuffer.putHex(stateA); printBuffer.put(' '); printBuffer.put('B'); printBuffer.putHex(stateB); printBuffer.putString(" T: "); printBuffer.putDec(turning); printBuffer.putString(" C: "); printBuffer.putDec(clicks); printBuffer.put('\n'); #endif //DEBUG if (stateA && stateB ) { // the detent. If we're turning, mark it. if (turning) { clicks+=direction; #ifdef DEBUG printBuffer.putString("Clicks: "); printBuffer.putDec(clicks); printBuffer.put('\n'); #endif //DEBUG } turning=0; direction=0; // reset counters. #ifdef DEBUGTIME *ada_output_port&=ada_not_output_mask; // sets it low #endif return; } if (stateA == 0 && stateB == 0) { // The 1/2way point. Flag that we've reached it. turning=1; #ifdef DEBUGTIME *ada_output_port&=ada_not_output_mask; // sets it low #endif return; } if (turning == 0) { // Either stateA!=0 or stateB!=0. // We are just starting to turn, so this will indicate direction if (stateA) { direction=1; // CCW #ifdef DEBUG printBuffer.put('+'); #endif #ifdef DEBUGTIME *ada_output_port&=ada_not_output_mask; // sets it low #endif return; }; // CCW. if (stateB) { direction=-1; // CW #ifdef DEBUG printBuffer.put('-'); #endif #ifdef DEBUGTIME *ada_output_port&=ada_not_output_mask; // sets it low #endif return; }; // CW. } #ifdef DEBUGTIME *ada_output_port&=ada_not_output_mask; // sets it low #endif } /* * GEt Next Indicated Encoder - gets the next encoder with non-zero clicks * Gets the next encoder that has been turned, as indicated by a non-zero "clicks" variable. * * Returns: * A pointer to the encoder found. * */ AdaEncoder *AdaEncoder::genie() { // if (currentEncoder == NULL) currentEncoder=firstEncoder; while (currentEncoder != NULL) { if (currentEncoder->clicks) { #ifdef DEBUG printBuffer.putString("genie: "); printBuffer.put(currentEncoder->id); printBuffer.put(' '); printBuffer.putDec(currentEncoder->clicks); printBuffer.put(' '); printBuffer.putString(" *"); #endif return currentEncoder; } currentEncoder=currentEncoder->next; } return NULL; } char AdaEncoder::getID() { return id; } int8_t AdaEncoder::getClicks() { return clicks; } /* * query() returns the clicks, but additionally updates the clicks variable. */ int8_t AdaEncoder::query() { int8_t tmpclicks; tmpclicks=clicks; if (clicks < 0) clicks++; if (clicks > 0) clicks--; return tmpclicks; }