|
| 1 | +/** |
| 2 | + * GroveEncoder.cpp - Grove Encoder library |
| 3 | + * |
| 4 | + * Copyright (C) 2016 David Antler |
| 5 | + * All rights reserved. |
| 6 | + * |
| 7 | + * This software may be modified and distributed under the terms |
| 8 | + * of the included license. See the LICENSE file for details. |
| 9 | + */ |
| 10 | + |
| 11 | +#include "GroveEncoder.h" |
| 12 | +#include "Arduino.h" |
| 13 | + |
| 14 | +/***************** HELPER FUNCTIONS ********************/ |
| 15 | + |
| 16 | +#define GROVE_ENC_LOGGING 0 |
| 17 | + |
| 18 | +#if GROVE_ENC_LOGGING |
| 19 | +#define DUMP_STATE() { \ |
| 20 | + Serial.println( "* * * DUMP * * *" ); \ |
| 21 | + Serial.print( " pinA : " ); Serial.print( pinA ); Serial.println( "" ); \ |
| 22 | + Serial.print( " pinB : " ); Serial.print( pinB ); Serial.println( "" ); \ |
| 23 | + Serial.print( " stage : " ); Serial.print( stage - STAGE_ZERO, HEX ); Serial.println( "" ); \ |
| 24 | + Serial.print( " rot : " ); Serial.print( rotation ); Serial.println( "" ); \ |
| 25 | +} |
| 26 | +#else |
| 27 | +#define DUMP_STATE() {} |
| 28 | +#endif |
| 29 | + |
| 30 | +/* Unfortunately we need to use a singleton */ |
| 31 | +GroveEncoder* singleton; |
| 32 | + |
| 33 | +/* State machine for parsing encoded data */ |
| 34 | +enum Stage { |
| 35 | + STAGE_UNDEF = 0x0f, |
| 36 | + STAGE_ZERO = 0x10, |
| 37 | + STAGE_ONE = 0x11, |
| 38 | + STAGE_TWO = 0x12, |
| 39 | + STAGE_THREE = 0x13, |
| 40 | + STAGE_FOUR = 0x14 |
| 41 | +}; |
| 42 | + |
| 43 | +/* Another piece of state for rotation state machine */ |
| 44 | +enum Rotation { |
| 45 | + CLOCKWISE = 2, |
| 46 | + COUNTERCLOCKWISE = 3, |
| 47 | + UNCERTAIN = 4 |
| 48 | +}; |
| 49 | + |
| 50 | +#define INDEX_MASK( indexValue ) (indexValue & (PIN_DATA_SIZE - 1) ) |
| 51 | + |
| 52 | +inline bool GroveEncoder::pushToQueue( unsigned char myValue ) |
| 53 | +{ |
| 54 | + if ( queueIsFull() ) |
| 55 | + { |
| 56 | + Serial.println( "Queue full, overwriting!" ); |
| 57 | + } |
| 58 | + pinDataQueue[INDEX_MASK( writeIndex )] = myValue; |
| 59 | + writeIndex++; |
| 60 | + return(true); |
| 61 | +} |
| 62 | + |
| 63 | + |
| 64 | +inline bool GroveEncoder::queueIsEmpty() |
| 65 | +{ |
| 66 | + return(writeIndex == readIndex); |
| 67 | +} |
| 68 | + |
| 69 | + |
| 70 | +inline bool GroveEncoder::queueIsFull() |
| 71 | +{ |
| 72 | + return( (writeIndex - readIndex) >= PIN_DATA_SIZE); |
| 73 | +} |
| 74 | + |
| 75 | + |
| 76 | +inline unsigned char GroveEncoder::popFromQueue() |
| 77 | +{ |
| 78 | + unsigned char outValue = 0xFF; |
| 79 | + if ( !queueIsEmpty() ) |
| 80 | + { |
| 81 | + outValue = pinDataQueue[INDEX_MASK( readIndex )]; |
| 82 | + readIndex++; |
| 83 | + } else { |
| 84 | + Serial.println( "Popped from empty queue" ); |
| 85 | + } |
| 86 | + return(outValue); |
| 87 | +} |
| 88 | + |
| 89 | + |
| 90 | +/* This function only exists due to the singleton issue. */ |
| 91 | +void GroveEncoder::privateIntHandler() |
| 92 | +{ |
| 93 | + singleton->updateEncoderFast(); |
| 94 | +} |
| 95 | + |
| 96 | + |
| 97 | +/* This routine is supposed to be very very fast. */ |
| 98 | +void GroveEncoder::updateEncoderFast() |
| 99 | +{ |
| 100 | + unsigned char newValue; |
| 101 | + newValue = (unsigned char) (digitalRead( LOW_PIN ) + (digitalRead( HIGH_PIN ) << 1) ); |
| 102 | + /* Push to Queue */ |
| 103 | + pushToQueue( newValue ); |
| 104 | + /* Do deferrable "slow" work when steady (e.g. both pins are high) */ |
| 105 | + if ( newValue == 3) |
| 106 | + { |
| 107 | + processQueue(); |
| 108 | + |
| 109 | + if ( optCallBack != NULL ) |
| 110 | + { |
| 111 | + //Serial.println("done"); |
| 112 | + /* Only process the callback if there's new data to post. */ |
| 113 | + static int prevValue = 0xDEADBEEF; /* squelch the warning. */ |
| 114 | + bool bt_flag = button_flag; |
| 115 | + if ( value != prevValue || bt_flag ) |
| 116 | + { |
| 117 | + prevValue = value; |
| 118 | + optCallBack( value, bt_flag ); |
| 119 | + } |
| 120 | + } |
| 121 | + } |
| 122 | +} |
| 123 | + |
| 124 | + |
| 125 | +#define GET_A_NEW_BYTE() { \ |
| 126 | + if ( queueIsEmpty() ) return; \ |
| 127 | + unsigned char pins = popFromQueue(); \ |
| 128 | + pinA = pins & 0x01; \ |
| 129 | + pinB = (pins & 0x02) >> 1; \ |
| 130 | +} |
| 131 | + |
| 132 | + |
| 133 | +/** |
| 134 | + * Processes the queue of bytes |
| 135 | + */ |
| 136 | +void GroveEncoder::processQueue() |
| 137 | +{ |
| 138 | + int pinA, pinB; |
| 139 | + char stage = STAGE_ZERO; |
| 140 | + Rotation rotation = UNCERTAIN; |
| 141 | + |
| 142 | + GET_A_NEW_BYTE(); |
| 143 | + |
| 144 | + do |
| 145 | + { |
| 146 | + DUMP_STATE(); |
| 147 | + if ( (pinA == pinB) && (pinB == HIGH) ) |
| 148 | + { |
| 149 | + GET_A_NEW_BYTE(); |
| 150 | + stage = STAGE_ZERO; |
| 151 | + rotation = UNCERTAIN; |
| 152 | + continue; |
| 153 | + } |
| 154 | + switch ( stage ) |
| 155 | + { |
| 156 | + case STAGE_ZERO: |
| 157 | + /* |
| 158 | + * Stage zero means that we might be at the "rising" edge of our rotation. |
| 159 | + * Decide if CW, CCW, or throw-away. |
| 160 | + */ |
| 161 | + if ( pinA > pinB ) |
| 162 | + { |
| 163 | + /* clockwise */ |
| 164 | + button_flag = 0; |
| 165 | + rotation = CLOCKWISE; |
| 166 | + stage = STAGE_ONE; |
| 167 | + } else if ( pinA < pinB ) |
| 168 | + { |
| 169 | + /* ccw */ |
| 170 | + button_flag = 0; |
| 171 | + //Serial.println("ccw"); |
| 172 | + rotation = COUNTERCLOCKWISE; |
| 173 | + stage = STAGE_ONE; |
| 174 | + } |
| 175 | + else if(pinA == pinB) |
| 176 | + { |
| 177 | + /* button */ |
| 178 | + //Serial.println("bt"); |
| 179 | + button_flag = 1; |
| 180 | + stage = STAGE_ONE; |
| 181 | + } |
| 182 | + GET_A_NEW_BYTE(); |
| 183 | + break; |
| 184 | + |
| 185 | + case STAGE_ONE: |
| 186 | + /* |
| 187 | + * In stage one, we've identified our direction, but have not yet seen double-zeros. |
| 188 | + * Once we see double zeros, lets fall through to stage two. |
| 189 | + */ |
| 190 | + if ( (LOW == pinB) && (pinA == LOW) ) |
| 191 | + { |
| 192 | + stage = STAGE_TWO; |
| 193 | + continue; |
| 194 | + } |
| 195 | + |
| 196 | + stage = STAGE_ZERO; |
| 197 | + break; |
| 198 | + |
| 199 | + case STAGE_TWO: |
| 200 | + /* Stage two means we have seen double zeros, but can escape by getting anything else. */ |
| 201 | + if ( (pinB == LOW) && (pinA == LOW) ) |
| 202 | + { |
| 203 | + GET_A_NEW_BYTE(); |
| 204 | + continue; |
| 205 | + } else if ( pinA ^ pinB ) |
| 206 | + { |
| 207 | + stage = STAGE_THREE; |
| 208 | + continue; |
| 209 | + } else { |
| 210 | + stage = STAGE_ZERO; |
| 211 | + continue; |
| 212 | + } |
| 213 | + |
| 214 | + case STAGE_THREE: |
| 215 | + /* |
| 216 | + * Stage three means that we are at the "falling" edge of our rotation. |
| 217 | + * Lets check to make sure we are being consistent. |
| 218 | + */ |
| 219 | + if ( rotation == CLOCKWISE ) |
| 220 | + { |
| 221 | + if ( pinA < pinB ) |
| 222 | + { |
| 223 | + stage = STAGE_FOUR; |
| 224 | + } else { |
| 225 | + /* failed to stay consistent! */ |
| 226 | + stage = STAGE_ZERO; |
| 227 | + } |
| 228 | + }else if ( rotation == COUNTERCLOCKWISE ) |
| 229 | + { |
| 230 | + if ( pinA > pinB ) |
| 231 | + { |
| 232 | + stage = STAGE_FOUR; |
| 233 | + } else { |
| 234 | + /* failed to stay consistent! */ |
| 235 | + stage = STAGE_ZERO; |
| 236 | + } |
| 237 | + } |
| 238 | + continue; |
| 239 | + |
| 240 | + case STAGE_FOUR: |
| 241 | + /* Stage four is logging the results! */ |
| 242 | + if ( rotation == CLOCKWISE ) |
| 243 | + { |
| 244 | + value++; |
| 245 | + } else if ( rotation == COUNTERCLOCKWISE ) |
| 246 | + { |
| 247 | + value--; |
| 248 | + } |
| 249 | + stage = STAGE_ZERO; |
| 250 | + GET_A_NEW_BYTE(); |
| 251 | + continue; |
| 252 | + |
| 253 | + default: |
| 254 | + Serial.println( "Default is bad" ); |
| 255 | + DUMP_STATE(); |
| 256 | + break; |
| 257 | + } |
| 258 | + } |
| 259 | + while ( !queueIsEmpty() ); |
| 260 | +} |
| 261 | + |
| 262 | + |
| 263 | +/***************** API FUNCTIONS ********************/ |
| 264 | + |
| 265 | +/* Gets the value on the encoder. It's an integer, positive or negative. */ |
| 266 | +int GroveEncoder::getValue() |
| 267 | +{ |
| 268 | + return(value); |
| 269 | +} |
| 270 | + |
| 271 | + |
| 272 | +void GroveEncoder::resetValue() |
| 273 | +{ |
| 274 | + setValue( 0 ); |
| 275 | +} |
| 276 | + |
| 277 | + |
| 278 | +void GroveEncoder::setValue( int newValue ) |
| 279 | +{ |
| 280 | + value = newValue; |
| 281 | +} |
| 282 | + |
| 283 | + |
| 284 | +/** |
| 285 | + * This will enumerate a GroveEncoder on a particular pin. |
| 286 | + * You can provide an optional callback, or poll the "getValue()" API. |
| 287 | + */ |
| 288 | +GroveEncoder::GroveEncoder( int pin, void(*optionalCallBack)(int, bool) ) |
| 289 | +{ |
| 290 | + /* Needed to make GroveEncoder a singleton because attachInterrupt doesn't take a parameter! */ |
| 291 | + singleton = this; |
| 292 | +#if GROVE_ENC_LOGGING |
| 293 | + Serial.begin( 9600 ); |
| 294 | + Serial.print( "Starting Grove Encoder debug logging..." ); |
| 295 | +#endif |
| 296 | + |
| 297 | + /* Initialize values */ |
| 298 | + writeIndex = readIndex = 0; |
| 299 | + LOW_PIN = pin; |
| 300 | + HIGH_PIN = pin + 1; |
| 301 | + value = 0; |
| 302 | + optCallBack = optionalCallBack; |
| 303 | + /* Initialize pins */ |
| 304 | + pinMode( LOW_PIN, INPUT_PULLUP ); |
| 305 | + pinMode( HIGH_PIN, INPUT_PULLUP ); |
| 306 | + |
| 307 | +#if GROVE_ENC_LOGGING |
| 308 | + /* Expect these to be low right now. */ |
| 309 | + if ( digitalRead( LOW_PIN ) || digitalRead( HIGH_PIN ) ) |
| 310 | + { |
| 311 | + /* Unless you're actively playing with the encoder, these will be low... */ |
| 312 | + Serial.print( "Grove Encoder library thinks you have a wiring issue!" ); |
| 313 | + } |
| 314 | +#endif |
| 315 | + |
| 316 | + /* Set up interrupts */ |
| 317 | + attachInterrupt( digitalPinToInterrupt( LOW_PIN ), privateIntHandler, CHANGE ); |
| 318 | + attachInterrupt( digitalPinToInterrupt( HIGH_PIN ), privateIntHandler, CHANGE ); |
| 319 | +} |
| 320 | + |
| 321 | + |
0 commit comments