├── LICENSE ├── QuadEncoder.cpp ├── QuadEncoder.h ├── README.md ├── examples ├── QuadEncoder │ └── QuadEncoder.ino ├── QuadEncoder_Interrupt_pins │ └── QuadEncoder_Interrupt_pins.ino └── SimpleEncoder │ └── SimpleEncoder.ino └── library.properties /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Mike S 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /QuadEncoder.cpp: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | #include "QuadEncoder.h" 3 | 4 | //#define DEBUG_OUTPUT 5 | 6 | const QuadEncoder::ENC_Channel_t QuadEncoder::channel[] = { 7 | {0,&IMXRT_ENC1, IRQ_ENC1, isrEnc1, 66, 67, 68, 69, 70,&CCM_CCGR4,CCM_CCGR4_ENC1(CCM_CCGR_ON)}, //this is a dummy entry - use 1-4 for channels 8 | {1, &IMXRT_ENC1, IRQ_ENC1, isrEnc1, 66, 67, 68, 69, 70,&CCM_CCGR4,CCM_CCGR4_ENC1(CCM_CCGR_ON)}, 9 | {2, &IMXRT_ENC2, IRQ_ENC2, isrEnc2, 71, 72, 73, 74, 75,&CCM_CCGR4,CCM_CCGR4_ENC2(CCM_CCGR_ON)}, 10 | {3, &IMXRT_ENC3, IRQ_ENC3, isrEnc3, 76, 77, 78, 79, 80,&CCM_CCGR4,CCM_CCGR4_ENC3(CCM_CCGR_ON)}, 11 | {4, &IMXRT_ENC4, IRQ_ENC4, isrEnc4, 81, 82, 83, 84, 85,&CCM_CCGR4,CCM_CCGR4_ENC4(CCM_CCGR_ON)} 12 | }; 13 | const uint8_t QuadEncoder::_channel_count = (sizeof(QuadEncoder::channel)/sizeof(QuadEncoder::channel[0])); 14 | 15 | //xbara1 pin config 16 | // idx, pin, *reg, alt,xbarIO, xbarMUX 17 | 18 | #if defined( ARDUINO_TEENSY40) 19 | const QuadEncoder::ENC_Hardware_t QuadEncoder::hardware[] = { 20 | {0, 0, &CORE_XIO_PIN0, 1, 17, 1}, {1, 1, &CORE_XIO_PIN1, 1, 16, 0}, 21 | {2, 2, &CORE_XIO_PIN2, 3, 6, 0}, {3, 3, &CORE_XIO_PIN3, 3, 7, 0}, 22 | {4, 4, &CORE_XIO_PIN4,3, 8, 0}, {5, 5, &CORE_XIO_PIN5, 3, 17, 0}, 23 | {6, 7, &CORE_XIO_PIN7, 1, 15, 1}, {7, 8, &CORE_XIO_PIN8, 1, 14, 1}, 24 | {8, 30, &CORE_XIO_PIN30, 1, 23, 0}, {9, 31, &CORE_XIO_PIN31, 1, 22, 0}, 25 | {10, 33, &CORE_XIO_PIN33, 3, 9, 0} 26 | }; 27 | #endif 28 | #ifdef ARDUINO_TEENSY41 29 | const QuadEncoder::ENC_Hardware_t QuadEncoder::hardware[] = { 30 | {0, 0, &CORE_XIO_PIN0, 1, 17, 1}, {1, 1, &CORE_XIO_PIN1, 1, 16, 0}, 31 | {2, 2, &CORE_XIO_PIN2, 3, 6, 0}, {3, 3, &CORE_XIO_PIN3, 3, 7, 0}, 32 | {4, 4, &CORE_XIO_PIN4,3, 8, 0}, {5, 5, &CORE_XIO_PIN5, 3, 17, 0}, 33 | {6, 7, &CORE_XIO_PIN7, 1, 15, 1}, {7, 8, &CORE_XIO_PIN8, 1, 14, 1}, 34 | {8, 30, &CORE_XIO_PIN30, 1, 23, 0}, {9, 31, &CORE_XIO_PIN31, 1, 22, 0}, 35 | {10, 33, &CORE_XIO_PIN33, 3, 9, 0}, {11, 36, &CORE_XIO_PIN36, 1, 16, 1}, 36 | {12, 37, &CORE_XIO_PIN37, 1, 17, 3} 37 | 38 | }; 39 | #endif 40 | 41 | #if defined( ARDUINO_TEENSY_MICROMOD) 42 | const QuadEncoder::ENC_Hardware_t QuadEncoder::hardware[] = { 43 | {0, 0, &CORE_XIO_PIN0, 1, 17, 1}, {1, 1, &CORE_XIO_PIN1, 1, 16, 0}, 44 | {2, 2, &CORE_XIO_PIN2, 3, 6, 0}, {3, 3, &CORE_XIO_PIN3, 3, 7, 0}, 45 | {4, 4, &CORE_XIO_PIN4,3, 8, 0}, {5, 5, &CORE_XIO_PIN5, 3, 17, 0}, 46 | {6, 7, &CORE_XIO_PIN7, 1, 15, 1}, {7, 8, &CORE_XIO_PIN8, 1, 14, 1}, 47 | {8, 30, &CORE_XIO_PIN30, 1, 23, 0}, {9, 31, &CORE_XIO_PIN31, 1, 22, 0}, 48 | {10, 33, &CORE_XIO_PIN33, 3, 9, 0}, {11, 36, &CORE_XIO_PIN36, 3, 5, 1}, 49 | {12, 37, &CORE_XIO_PIN37, 3, 4, 1} 50 | }; 51 | #endif 52 | const uint8_t QuadEncoder::_hardware_count = (sizeof(QuadEncoder::hardware)/sizeof(QuadEncoder::hardware[0])); 53 | 54 | QuadEncoder * QuadEncoder::list[5]; 55 | 56 | uint8_t QuadEncoder::compareValueFlag; 57 | uint32_t QuadEncoder::homeCounter; 58 | uint32_t QuadEncoder::indexCounter; 59 | 60 | 61 | void QuadEncoder::setInitConfig() { 62 | getConfig1(&EncConfig); 63 | } 64 | 65 | void QuadEncoder::init() 66 | { 67 | Init(&EncConfig); 68 | setConfigInitialPosition(); 69 | enableInterrupts(&EncConfig); 70 | } 71 | 72 | 73 | QuadEncoder::QuadEncoder(uint8_t encoder_ch, uint8_t PhaseA_pin, uint8_t PhaseB_pin, uint8_t pin_pus, uint8_t index_pin, uint8_t home_pin, uint8_t trigger_pin){ 74 | if(encoder_ch >= 0){ 75 | _encoder_ch = encoder_ch; 76 | 77 | CCM_CCGR2 |= CCM_CCGR2_XBAR1(CCM_CCGR_ON); //turn clock on for xbara1 78 | 79 | #ifdef DEBUG_OUTPUT 80 | Serial.printf("begin: encoder channel-> %d\n",_encoder_ch); 81 | Serial.printf("begin: pinA-> %d, pinB-> %d\n", PhaseA_pin, PhaseB_pin); 82 | #endif 83 | if(PhaseA_pin != 255 ) 84 | enc_xbara_mapping(PhaseA_pin, PHASEA, pin_pus); 85 | if(PhaseB_pin != 255 ) 86 | enc_xbara_mapping(PhaseB_pin, PHASEB, pin_pus); 87 | if(home_pin != 255 ) 88 | enc_xbara_mapping(home_pin, HOME, pin_pus); 89 | if(index_pin != 255 ) 90 | enc_xbara_mapping(index_pin, INDEX, pin_pus); 91 | if(trigger_pin != 255 ) 92 | enc_xbara_mapping(trigger_pin, TRIGGER, pin_pus); 93 | } else { 94 | Serial.println("No Encoder Channel Selected!"); 95 | } 96 | 97 | disableInterrupts(_positionROEnable); 98 | disableInterrupts(_positionRUEnable); 99 | } 100 | 101 | void QuadEncoder::getConfig1(enc_config_t *config) 102 | { 103 | /* Initializes the configure structure to zero. */ 104 | memset(config, 0, sizeof(*config)); 105 | 106 | config->enableReverseDirection = DISABLE; 107 | config->decoderWorkMode = DISABLE; 108 | config->HOMETriggerMode = DISABLE; 109 | config->INDEXTriggerMode = 0; 110 | config->IndexTrigger = DISABLE; 111 | config->HomeTrigger = DISABLE; 112 | config->clearCounter = DISABLE; 113 | config->clearHoldCounter = DISABLE; 114 | config->filterCount = 0; 115 | config->filterSamplePeriod = 0; 116 | config->positionMatchMode = false; 117 | config->positionCompareMode = DISABLE; 118 | config->positionCompareValue = 0xffffffff; 119 | config->revolutionCountCondition = DISABLE; 120 | config->enableModuloCountMode = DISABLE; 121 | config->positionModulusValue = 0; 122 | config->positionInitialValue = 0; 123 | config->positionROIE = DISABLE; 124 | config->positionRUIE = DISABLE; 125 | 126 | } 127 | 128 | void QuadEncoder::printConfig(enc_config_t *config) 129 | { 130 | Serial.printf("\tenableReverseDirection: %d\n",config->enableReverseDirection); 131 | Serial.printf("\tdecoderWorkMode: %d\n",config->decoderWorkMode); 132 | Serial.printf("\tHOMETriggerMode: %d\n",config->HOMETriggerMode); 133 | Serial.printf("\tINDEXTriggerMode: %d\n",config->INDEXTriggerMode); 134 | Serial.printf("\tIndexTrigger: %d\n",config->IndexTrigger); 135 | Serial.printf("\tHomeTrigger: %d\n",config->HomeTrigger); 136 | 137 | Serial.printf("\tclearCounter: %d\n",config->clearCounter); 138 | Serial.printf("\tclearHoldCounter: %d\n",config->clearHoldCounter); 139 | 140 | Serial.printf("\tfilterCount: %d\n",config->filterCount); 141 | Serial.printf("\tfilterSamplePeriod: %d\n",config->filterSamplePeriod); 142 | Serial.printf("\tpositionCompareValue: %x\n",config->positionCompareValue); 143 | Serial.printf("\trevolutionCountCondition: %d\n",config->revolutionCountCondition); 144 | Serial.printf("\tenableModuloCountMode: %d\n",config->enableModuloCountMode); 145 | Serial.printf("\tpositionModulusValue: %d\n", config->positionModulusValue); 146 | 147 | Serial.printf("\tpositionInitialValue: %d\n",config->positionInitialValue); 148 | Serial.printf("\tpositionROIE: %d\n",config->positionROIE); 149 | Serial.printf("\tpositionRUIE: %x\n",config->positionRUIE); 150 | Serial.printf("\n"); 151 | } 152 | 153 | void QuadEncoder::setConfigInitialPosition() 154 | { 155 | uint16_t tmp16 = channel[_encoder_ch].ENC->CTRL & (uint16_t)(~ENC_CTRL_W1C_FLAGS); 156 | 157 | tmp16 |= ENC_CTRL_SWIP_MASK; /* Write 1 to trigger the command for loading initial position value. */ 158 | channel[_encoder_ch].ENC->CTRL = tmp16; 159 | } 160 | 161 | void QuadEncoder::Init(const enc_config_t *config) 162 | { 163 | uint32_t tmp16; 164 | 165 | // make sure the appropriate clock gate is enabled. 166 | *channel[_encoder_ch].clock_gate_register |= channel[_encoder_ch].clock_gate_mask; 167 | 168 | /* ENC_CTRL. */ 169 | tmp16 = channel[_encoder_ch].ENC->CTRL & (uint16_t)(~(ENC_CTRL_W1C_FLAGS | ENC_CTRL_HIP_MASK | ENC_CTRL_HNE_MASK | ENC_CTRL_REV_MASK | ENC_CTRL_PH1_MASK | ENC_CTRL_XIP_MASK | ENC_CTRL_XNE_MASK | ENC_CTRL_WDE_MASK)); 170 | 171 | /* For HOME trigger. */ 172 | if (config->HOMETriggerMode != 0) 173 | { 174 | tmp16 |= ENC_CTRL_HIP_MASK; 175 | if (FALLING_EDGE == config->HOMETriggerMode) 176 | { 177 | tmp16 |= ENC_CTRL_HNE_MASK; 178 | } 179 | } 180 | 181 | /* For encoder work mode. */ 182 | if (config->enableReverseDirection) 183 | { 184 | tmp16 |= ENC_CTRL_REV_MASK; 185 | } 186 | if (true == config->decoderWorkMode) 187 | { 188 | tmp16 |= ENC_CTRL_PH1_MASK; 189 | } 190 | /* For INDEX trigger. */ 191 | if (config->INDEXTriggerMode != 0 ) 192 | { 193 | tmp16 |= ENC_CTRL_XIP_MASK; 194 | if (FALLING_EDGE == config->INDEXTriggerMode) 195 | { 196 | tmp16 |= ENC_CTRL_XNE_MASK; 197 | } 198 | } 199 | 200 | channel[_encoder_ch].ENC->CTRL = tmp16; 201 | 202 | /* ENC_FILT. */ 203 | channel[_encoder_ch].ENC->FILT = ENC_FILT_FILT_CNT(config->filterCount) | ENC_FILT_FILT_PER(config->filterSamplePeriod); 204 | 205 | /* ENC_CTRL2. */ 206 | tmp16 = channel[_encoder_ch].ENC->CTRL2 & (uint16_t)(~(ENC_CTRL2_W1C_FLAGS | ENC_CTRL2_OUTCTL_MASK | ENC_CTRL2_REVMOD_MASK | ENC_CTRL2_MOD_MASK | ENC_CTRL2_UPDPOS_MASK | ENC_CTRL2_UPDHLD_MASK | ENC_CTRL2_ROIRQ_MASK | ENC_CTRL2_RUIRQ_MASK)); 207 | 208 | if (1 == config->positionMatchMode) 209 | { 210 | tmp16 |= ENC_CTRL2_OUTCTL_MASK; 211 | } 212 | if (1 == config->revolutionCountCondition) 213 | { 214 | tmp16 |= ENC_CTRL2_REVMOD_MASK; 215 | } 216 | if (config->enableModuloCountMode) 217 | { 218 | tmp16 |= ENC_CTRL2_MOD_MASK; 219 | /* Set modulus value. */ 220 | channel[_encoder_ch].ENC->UMOD = (uint16_t)(config->positionModulusValue >> 16U); /* Upper 16 bits. */ 221 | channel[_encoder_ch].ENC->LMOD = (uint16_t)(config->positionModulusValue); /* Lower 16 bits. */ 222 | } 223 | if (config->clearCounter) 224 | { 225 | tmp16 |= ENC_CTRL2_UPDPOS_MASK; 226 | } 227 | if (config->clearHoldCounter) 228 | { 229 | tmp16 |= ENC_CTRL2_UPDHLD_MASK; 230 | } 231 | channel[_encoder_ch].ENC->CTRL2 = tmp16; 232 | 233 | /* ENC_UCOMP & ENC_LCOMP. */ 234 | channel[_encoder_ch].ENC->UCOMP = (uint16_t)(config->positionCompareValue >> 16U); /* Upper 16 bits. */ 235 | channel[_encoder_ch].ENC->LCOMP = (uint16_t)(config->positionCompareValue); /* Lower 16 bits. */ 236 | 237 | /* ENC_UINIT & ENC_LINIT. */ 238 | channel[_encoder_ch].ENC->UINIT = (uint16_t)(config->positionInitialValue >> 16U); /* Upper 16 bits. */ 239 | channel[_encoder_ch].ENC->LINIT = (uint16_t)(config->positionInitialValue); /* Lower 16 bits. */ 240 | 241 | } 242 | 243 | int32_t QuadEncoder::read() 244 | { 245 | uint32_t ret32; 246 | 247 | ret32 = channel[_encoder_ch].ENC->UPOS; /* Get upper 16 bits and make a snapshot. */ 248 | ret32 <<= 16U; 249 | ret32 |= channel[_encoder_ch].ENC->LPOSH; /* Get lower 16 bits from hold register. */ 250 | 251 | return (int32_t) ret32; 252 | } 253 | 254 | void QuadEncoder::write(uint32_t value) 255 | { 256 | channel[_encoder_ch].ENC->UINIT = (uint16_t)(value >> 16U); /* Set upper 16 bits. */ 257 | channel[_encoder_ch].ENC->LINIT = (uint16_t)(value); /* Set lower 16 bits. */ 258 | 259 | setConfigInitialPosition(); 260 | } 261 | 262 | void QuadEncoder::setCompareValue(uint32_t compareValue) { 263 | /* ENC_UCOMP & ENC_LCOMP. */ 264 | channel[_encoder_ch].ENC->UCOMP = (uint16_t)(compareValue >> 16U); /* Upper 16 bits. */ 265 | channel[_encoder_ch].ENC->LCOMP = (uint16_t)(compareValue); /* Lower 16 bits. */ 266 | } 267 | uint32_t QuadEncoder::getHoldPosition() 268 | { 269 | uint32_t ret32; 270 | 271 | ret32 = channel[_encoder_ch].ENC->UPOSH; /* Get upper 16 bits and make a snapshot. */ 272 | ret32 <<= 16U; 273 | ret32 |= channel[_encoder_ch].ENC->LPOSH; /* Get lower 16 bits from hold register. */ 274 | 275 | return ret32; 276 | } 277 | 278 | 279 | uint16_t QuadEncoder::getPositionDifference() 280 | { 281 | return channel[_encoder_ch].ENC->POSD; 282 | } 283 | 284 | 285 | uint16_t QuadEncoder::getHoldDifference() 286 | { 287 | return channel[_encoder_ch].ENC->POSDH; 288 | } 289 | 290 | 291 | uint16_t QuadEncoder::getRevolution() 292 | { 293 | return channel[_encoder_ch].ENC->REV; 294 | } 295 | 296 | uint16_t QuadEncoder::getHoldRevolution() 297 | { 298 | return channel[_encoder_ch].ENC->REVH; 299 | } 300 | 301 | void QuadEncoder::enc_xbara_mapping(uint8_t pin, uint8_t PHASE, uint8_t PUS){ 302 | 303 | const struct digital_pin_bitband_and_config_table_struct *p; 304 | 305 | for (int idx_channel = 0; idx_channel < _hardware_count; idx_channel++) { 306 | if (hardware[idx_channel].pin == pin) { 307 | _pin_idx = idx_channel; 308 | break; 309 | } 310 | } 311 | #ifdef DEBUG_OUTPUT 312 | Serial.printf("\nxbara_mapping: pin-> %d, idx-> %d\n", pin, _pin_idx); 313 | Serial.printf("xbara_mapping: hw_count-> %d\n",_hardware_count); 314 | Serial.printf("xbara_mapping: hardware[_pin_idx].pin-> %d, .select_val-> %d\n", hardware[_pin_idx].pin, hardware[_pin_idx].select_val); 315 | Serial.printf("xbara_mapping: Encoder-> %d\n", _encoder_ch); 316 | #endif 317 | 318 | if (_pin_idx == _hardware_count) return; 319 | 320 | p = digital_pin_to_info_PGM + hardware[_pin_idx].pin; 321 | //mux is ctrl config for pin 322 | //pad is pad config 323 | //pinmode = *(p->reg + 1); 324 | 325 | //Pin ctrl configuration for encoder/xbara1 326 | *(p->mux) = hardware[_pin_idx].select_val; 327 | 328 | //Pad configuration for encoder/xbara1 329 | if(PUS == 0){ 330 | *(p->pad) = 0x10B0; 331 | } else { 332 | *(p->pad) = 0x1f038; 333 | } 334 | 335 | //x = xio_pin_to_info_PGM + pin; 336 | //*(x->reg) = xbara1_mux[pin]; 337 | *hardware[_pin_idx].reg = hardware[_pin_idx].xbarMUX; 338 | #ifdef DEBUG_OUTPUT 339 | if(PHASE == 1) Serial.printf("xbarIO-> %d, PhaseA-> %d\n",hardware[_pin_idx].xbarIO, channel[_encoder_ch].phaseA); 340 | if(PHASE == 2)Serial.printf("xbarIO-> %d, PhaseB-> %d\n",hardware[_pin_idx].xbarIO, channel[_encoder_ch].phaseB); 341 | if(PHASE == 3)Serial.printf("xbarIO-> %d, INDEX-> %d\n",hardware[_pin_idx].xbarIO, channel[_encoder_ch].index); 342 | if(PHASE == 4)Serial.printf("xbarIO-> %d, HOME-> %d\n",hardware[_pin_idx].xbarIO, channel[_encoder_ch].home); 343 | #endif 344 | 345 | //XBARA1 Connection to encoder 346 | if(PHASE == 1) xbar_connect(hardware[_pin_idx].xbarIO, channel[_encoder_ch].phaseA); 347 | if(PHASE == 2) xbar_connect(hardware[_pin_idx].xbarIO, channel[_encoder_ch].phaseB); 348 | if(PHASE == 3) xbar_connect(hardware[_pin_idx].xbarIO, channel[_encoder_ch].index); 349 | if(PHASE == 4) xbar_connect(hardware[_pin_idx].xbarIO, channel[_encoder_ch].home); 350 | if(PHASE == 5) xbar_connect(hardware[_pin_idx].xbarIO, channel[_encoder_ch].trigger); 351 | } 352 | 353 | void QuadEncoder::xbar_connect(unsigned int input, unsigned int output) 354 | { 355 | if (input >= 88) return; 356 | if (output >= 132) return; 357 | #if 1 358 | volatile uint16_t *xbar = &XBARA1_SEL0 + (output / 2); 359 | uint16_t val = *xbar; 360 | if (!(output & 1)) { 361 | val = (val & 0xFF00) | input; 362 | } else { 363 | val = (val & 0x00FF) | (input << 8); 364 | } 365 | *xbar = val; 366 | #else 367 | // does not work, seems 8 bit access is not allowed 368 | volatile uint8_t *xbar = (volatile uint8_t *)XBARA1_SEL0; 369 | xbar[output] = input; 370 | #endif 371 | } 372 | 373 | void QuadEncoder::enableInterrupts(const enc_config_t *config) 374 | { 375 | uint32_t tmp16 = 0; 376 | 377 | //enable interrupt 378 | NVIC_SET_PRIORITY(channel[_encoder_ch].interrupt, 32); 379 | NVIC_ENABLE_IRQ(channel[_encoder_ch].interrupt); 380 | attachInterruptVector(channel[_encoder_ch].interrupt, channel[_encoder_ch].isr); 381 | 382 | /* ENC_CTRL. */ 383 | if (config->HomeTrigger != DISABLE) 384 | { 385 | tmp16 |= ENC_CTRL_HIE_MASK; 386 | } 387 | if (config->IndexTrigger != DISABLE) 388 | { 389 | tmp16 |= ENC_CTRL_XIE_MASK; 390 | } 391 | 392 | if (config->positionCompareMode == ENABLE) 393 | { 394 | tmp16 |= ENC_CTRL_CMPIE_MASK; 395 | } 396 | if (tmp16 != 0) 397 | { 398 | channel[_encoder_ch].ENC->CTRL = (channel[_encoder_ch].ENC->CTRL & (uint16_t)(~ENC_CTRL_W1C_FLAGS)) | tmp16; 399 | } 400 | /* ENC_CTRL2. */ 401 | tmp16 = 0U; 402 | if (config->positionROIE != DISABLE) 403 | { 404 | tmp16 |= ENC_CTRL2_ROIRQ_MASK; 405 | } 406 | if (config->positionRUIE != DISABLE) 407 | { 408 | tmp16 |= ENC_CTRL2_RUIRQ_MASK; 409 | } 410 | if (tmp16 != 0U) 411 | { 412 | channel[_encoder_ch].ENC->CTRL2 = (uint16_t)(channel[_encoder_ch].ENC->CTRL2 & (uint16_t)(~ENC_CTRL2_W1C_FLAGS)) & (uint16_t)(~tmp16); 413 | } 414 | 415 | } 416 | 417 | 418 | void QuadEncoder::disableInterrupts(uint32_t flag) 419 | { 420 | uint16_t tmp16 = 0U; 421 | 422 | /* ENC_CTRL. */ 423 | if (_HOMETransitionEnable == (_HOMETransitionEnable & flag)) 424 | { 425 | tmp16 |= ENC_CTRL_HIE_MASK; 426 | } 427 | if (_INDEXPulseEnable == (_INDEXPulseEnable & flag)) 428 | { 429 | tmp16 |= ENC_CTRL_XIE_MASK; 430 | } 431 | if (_positionCompareEnable == (_positionCompareEnable & flag)) 432 | { 433 | tmp16 |= ENC_CTRL_CMPIE_MASK; 434 | } 435 | if (0U != tmp16) 436 | { 437 | channel[_encoder_ch].ENC->CTRL = (uint16_t)(channel[_encoder_ch].ENC->CTRL & (uint16_t)(~ENC_CTRL_W1C_FLAGS)) & (uint16_t)(~tmp16); 438 | } 439 | /* ENC_CTRL2. */ 440 | tmp16 = 0U; 441 | if (_positionRollOverFlag == (_positionRollOverFlag & flag)) 442 | { 443 | tmp16 |= ENC_CTRL2_ROIRQ_MASK; 444 | } 445 | if (_positionRollUnderFlag == (_positionRollUnderFlag & flag)) 446 | { 447 | tmp16 |= ENC_CTRL2_RUIRQ_MASK; 448 | } 449 | if (tmp16 != 0U) 450 | { 451 | channel[_encoder_ch].ENC->CTRL2 = (uint16_t)(channel[_encoder_ch].ENC->CTRL2 & (uint16_t)(~ENC_CTRL2_W1C_FLAGS)) & (uint16_t)(~tmp16); 452 | } 453 | } 454 | 455 | 456 | void QuadEncoder::clearStatusFlags(uint32_t flag, uint8_t index) 457 | { 458 | uint32_t tmp16 = 0U; 459 | 460 | /* ENC_CTRL. */ 461 | if (_HOMETransitionFlag == (_HOMETransitionFlag & flag)) 462 | { 463 | tmp16 |= ENC_CTRL_HIRQ_MASK; 464 | } 465 | if (_INDEXPulseFlag == (_INDEXPulseFlag & flag)) 466 | { 467 | tmp16 |= ENC_CTRL_XIRQ_MASK; 468 | } 469 | if (_positionCompareFlag == (_positionCompareFlag & flag)) 470 | { 471 | tmp16 |= ENC_CTRL_CMPIRQ_MASK; 472 | } 473 | if (0U != tmp16) 474 | { 475 | channel[index].ENC->CTRL = (channel[index].ENC->CTRL & (uint16_t)(~ENC_CTRL_W1C_FLAGS)) | tmp16; 476 | } 477 | /* ENC_CTRL2. */ 478 | tmp16 = 0U; 479 | if (_positionRollOverFlag == (_positionRollOverFlag & flag)) 480 | { 481 | tmp16 |= ENC_CTRL2_ROIRQ_MASK; 482 | } 483 | if (_positionRollUnderFlag == (_positionRollUnderFlag & flag)) 484 | { 485 | tmp16 |= ENC_CTRL2_RUIRQ_MASK; 486 | } 487 | if (0U != tmp16) 488 | { 489 | channel[index].ENC->CTRL2 = (channel[index].ENC->CTRL2 & (uint16_t)(~ENC_CTRL2_W1C_FLAGS)) | tmp16; 490 | } 491 | } 492 | 493 | inline void QuadEncoder::checkAndProcessInterrupt(uint8_t index) 494 | { 495 | list[index]->isr(index); 496 | } 497 | 498 | 499 | void QuadEncoder::isrEnc1() 500 | { 501 | checkAndProcessInterrupt(1); 502 | asm volatile ("dsb"); // wait for clear memory barrier 503 | } 504 | 505 | void QuadEncoder::isrEnc2() 506 | { 507 | checkAndProcessInterrupt(2); 508 | asm volatile ("dsb"); // wait for clear memory barrier 509 | 510 | } 511 | 512 | void QuadEncoder::isrEnc3() 513 | { 514 | checkAndProcessInterrupt(3); 515 | asm volatile ("dsb"); // wait for clear memory barrier 516 | } 517 | void QuadEncoder::isrEnc4() 518 | { 519 | checkAndProcessInterrupt(4); 520 | asm volatile ("dsb"); // wait for clear memory barrier 521 | } 522 | 523 | void QuadEncoder::isr(uint8_t index) 524 | { 525 | if (ENC_CTRL_XIRQ_MASK == (ENC_CTRL_XIRQ_MASK & channel[index].ENC->CTRL) && (ENC_CTRL_XIE_MASK & channel[index].ENC->CTRL)) 526 | { 527 | indexCounter = indexCounter + 1; 528 | clearStatusFlags(_INDEXPulseFlag, index); 529 | 530 | if(ENC_CTRL2_ROIRQ_MASK == (ENC_CTRL2_ROIRQ_MASK & channel[index].ENC->CTRL2)) 531 | { 532 | //Serial.println("ROLL OVER INDICATED!!!!!"); 533 | //Serial.println("========================="); 534 | //Serial.println(); 535 | clearStatusFlags(_positionRollOverFlag, index); 536 | } 537 | if(ENC_CTRL2_RUIRQ_MASK == (ENC_CTRL2_RUIRQ_MASK & channel[index].ENC->CTRL2)) 538 | { 539 | //Serial.println("ROLL UNDER INDICATED!!!!!"); 540 | //Serial.println("========================="); 541 | //Serial.println(); 542 | clearStatusFlags(_positionRollUnderFlag, index); 543 | } 544 | 545 | } 546 | 547 | if (ENC_CTRL_HIRQ_MASK == (ENC_CTRL_HIRQ_MASK & channel[index].ENC->CTRL) && (ENC_CTRL_HIE_MASK & channel[index].ENC->CTRL)) 548 | { 549 | 550 | homeCounter++; 551 | clearStatusFlags(_HOMETransitionFlag, index); 552 | } 553 | 554 | if (ENC_CTRL_CMPIRQ_MASK == (ENC_CTRL_CMPIRQ_MASK & channel[index].ENC->CTRL)) 555 | { 556 | compareValueFlag = 1; 557 | // 12/03/21 JWP add line below to disable compare interrupt 558 | channel[index].ENC->CTRL &= ~ENC_CTRL_CMPIE_MASK; 559 | clearStatusFlags(_positionCompareFlag, index); 560 | } 561 | } 562 | 563 | void QuadEncoder::enableCompareInterrupt() 564 | { 565 | channel[_encoder_ch].ENC->CTRL |= ENC_CTRL_CMPIE_MASK; 566 | } 567 | -------------------------------------------------------------------------------- /QuadEncoder.h: -------------------------------------------------------------------------------- 1 | #ifndef __QUADENCODER_H__ 2 | #define __QUADENCODER_H__ 3 | 4 | #ifndef __IMXRT1062__ 5 | #error "Sorry, Quad Encoder only works on Teensy 4.x boards" 6 | #endif 7 | 8 | #include 9 | 10 | #define ENC_CTRL_HIRQ_MASK (0x8000U) 11 | #define ENC_CTRL_XIRQ_MASK (0x100U) 12 | #define ENC_CTRL_DIRQ_MASK (0x10U) 13 | #define ENC_CTRL_CMPIRQ_MASK (0x2U) 14 | #define ENC_CTRL_HIP_MASK (0x2000U) 15 | #define ENC_CTRL_HNE_MASK (0x1000U) 16 | #define ENC_CTRL_REV_MASK (0x400U) 17 | #define ENC_CTRL_PH1_MASK (0x200U) 18 | #define ENC_CTRL_XIP_MASK (0x40U) 19 | #define ENC_CTRL_XNE_MASK (0x20U) 20 | #define ENC_CTRL_WDE_MASK (0x4U) 21 | #define ENC_CTRL_WDE_MASK (0x4U) 22 | #define ENC_CTRL_SWIP_MASK (0x800U) 23 | //interrupts 24 | #define ENC_CTRL_HIE_MASK (0x4000U) 25 | #define ENC_CTRL_XIE_MASK (0x80U) 26 | #define ENC_CTRL_CMPIE_MASK (0x1U) 27 | 28 | #define ENC_CTRL2_SABIRQ_MASK (0x800U) 29 | #define ENC_CTRL2_ROIRQ_MASK (0x80U) 30 | #define ENC_CTRL2_RUIRQ_MASK (0x20U) 31 | #define ENC_CTRL2_OUTCTL_MASK (0x200U) 32 | #define ENC_CTRL2_REVMOD_MASK (0x100U) 33 | #define ENC_CTRL2_MOD_MASK (0x4U) 34 | #define ENC_CTRL2_UPDHLD_MASK (0x1U) 35 | #define ENC_CTRL2_UPDPOS_MASK (0x2U) 36 | //interrupts 37 | //#define ENC_CTRL2_ROIE_MASK (0x80U) 38 | //#define ENC_CTRL2_RUIE_MASK (0x20U) 39 | 40 | 41 | #define ENC_CTRL_W1C_FLAGS (ENC_CTRL_HIRQ_MASK | ENC_CTRL_XIRQ_MASK | ENC_CTRL_DIRQ_MASK | ENC_CTRL_CMPIRQ_MASK) 42 | #define ENC_CTRL2_W1C_FLAGS (ENC_CTRL2_SABIRQ_MASK | ENC_CTRL2_ROIRQ_MASK | ENC_CTRL2_RUIRQ_MASK) 43 | 44 | #define ENC_FILT_FILT_PER(x) (((uint16_t)(((uint16_t)(x)) << 0U)) & 0xFFU) 45 | #define ENC_FILT_FILT_CNT(x) (((uint16_t)(((uint16_t)(x)) << 8U)) & 0x700U) 46 | 47 | #define DISABLE 0 48 | #define ENABLE 1 49 | #define FALLING_EDGE 2 50 | #define RISING_EDGE 1 51 | 52 | #define PHASEA 1 53 | #define PHASEB 2 54 | #define INDEX 3 55 | #define HOME 4 56 | #define TRIGGER 5 57 | #define PULLUPS 0 //set to 1 if pullups are needed 58 | 59 | 60 | #define CORE_XIO_PIN0 IOMUXC_XBAR1_IN17_SELECT_INPUT //ALT1 61 | #define CORE_XIO_PIN1 IOMUXC_XBAR1_IN16_SELECT_INPUT // SD_B0_02 ALT1 62 | #define CORE_XIO_PIN2 IOMUXC_XBAR1_IN06_SELECT_INPUT // EMC_04, ALT3 63 | #define CORE_XIO_PIN3 IOMUXC_XBAR1_IN07_SELECT_INPUT //ALT3 64 | #define CORE_XIO_PIN4 IOMUXC_XBAR1_IN08_SELECT_INPUT //ALT3 65 | #define CORE_XIO_PIN5 IOMUXC_XBAR1_IN17_SELECT_INPUT //ALT3 66 | #define CORE_XIO_PIN7 IOMUXC_XBAR1_IN15_SELECT_INPUT //ALT1 67 | #define CORE_XIO_PIN8 IOMUXC_XBAR1_IN14_SELECT_INPUT //ALT1 68 | #define CORE_XIO_PIN30 IOMUXC_XBAR1_IN23_SELECT_INPUT //ALT1,0 69 | #define CORE_XIO_PIN31 IOMUXC_XBAR1_IN22_SELECT_INPUT //EMC_36, ALT1,0 70 | #define CORE_XIO_PIN33 IOMUXC_XBAR1_IN09_SELECT_INPUT //ALT3,0 71 | 72 | #if defined(ARDUINO_TEENSY41) 73 | #define CORE_XIO_PIN36 IOMUXC_XBAR1_IN16_SELECT_INPUT // SD_B1_02, ALT1 74 | #define CORE_XIO_PIN37 IOMUXC_XBAR1_IN17_SELECT_INPUT // SC_B1_03, ALT1 75 | #endif 76 | 77 | #if defined(ARDUINO_TEENSY_MICROMOD) 78 | #define CORE_XIO_PIN36 IOMUXC_XBAR1_IN05_SELECT_INPUT // SD_B0_01, ALT3 79 | #define CORE_XIO_PIN37 IOMUXC_XBAR1_IN04_SELECT_INPUT // SD_B0_00, ALT3 80 | #endif 81 | 82 | 83 | 84 | enum _flags 85 | { 86 | _HOMETransitionFlag = (1 << 0), 87 | _INDEXPulseFlag = (1 << 1), 88 | _positionCompareFlag = (1 << 3), 89 | _positionRollOverFlag = (1 << 5), 90 | _positionRollUnderFlag = (1 << 6), 91 | _lastDirectionFlag = (1 << 7), 92 | 93 | }; 94 | 95 | enum _interrupts 96 | { 97 | _HOMETransitionEnable = (1 << 0), 98 | _INDEXPulseEnable = (1 << 1), 99 | _positionCompareEnable = (1 << 3), 100 | _positionROEnable = (1 << 5), 101 | _positionRUEnable = (1 << 6), 102 | }; 103 | 104 | 105 | class QuadEncoder 106 | { 107 | public: 108 | void isr(uint8_t index); 109 | 110 | typedef struct 111 | { 112 | /* Basic counter. */ 113 | bool enableReverseDirection; 114 | bool decoderWorkMode; // 0 = Normal mode, 1 = PHASEA input generates a count signal while PHASEB input control the direction. 115 | 116 | /* Signal detection. */ 117 | uint8_t HOMETriggerMode; //0 - disable, 1 - rising, 2 - falling 118 | uint8_t INDEXTriggerMode; //0 - disabled, 1 - Use positive going edge-to-trigger initialization of position counters!, 2 - use falling 119 | bool clearCounter; 120 | bool clearHoldCounter; 121 | 122 | /* Filter for PHASEA, PHASEB, INDEX and HOME. */ 123 | /* Input Filter Sample Count. This value should be chosen to reduce the probability of noisy samples causing an incorrect transition to be recognized. The value represent the number of consecutive samples that must agree prior to the input filter accepting an input transition. A value of 0x0 represents 3 samples. A value of 0x7 represents 10 samples. The Available range is 0 - 7. */ 124 | uint16_t filterCount; 125 | 126 | /* Input Filter Sample Period. This value should be set such that the sampling period is larger than the period of the expected noise. This value represents the sampling period (in IPBus clock cycles) of the decoder input signals. The available range is 0 - 255. */ 127 | uint16_t filterSamplePeriod; 128 | 129 | /* Position compare. */ 130 | /* 0 - POSMATCH pulses when a match occurs between the position counters (POS) and the compare value (COMP). 1 - POSMATCH pulses when any position counter register is read. */ 131 | bool positionMatchMode; 132 | 133 | /* Position Compare Enabled. */ 134 | bool positionCompareMode; 135 | /*!< Position compare value. The available value is a 32-bit number.*/ 136 | uint32_t positionCompareValue; 137 | 138 | /* Modulus counting. */ 139 | /*0 - Use INDEX pulse to increment/decrement revolution counter. 1 - Use modulus counting roll-over/under to increment/decrement revolution counter. */ 140 | bool revolutionCountCondition; 141 | 142 | bool enableModuloCountMode; //Enable Modulo Counting. */ 143 | 144 | /*Position modulus value. This value would be available only when "enableModuloCountMode" = true. The available value is a 32-bit number. */ 145 | uint32_t positionModulusValue; 146 | 147 | //Position initial value. The available value is a 32-bit number. */ 148 | uint32_t positionInitialValue; 149 | 150 | //Position Roll Over or Roll Under Interrupt Enable 151 | uint8_t positionROIE; 152 | uint8_t positionRUIE; 153 | 154 | bool IndexTrigger; 155 | bool HomeTrigger; 156 | } enc_config_t; 157 | 158 | //encoder 159 | typedef struct { 160 | uint8_t enc_ch; 161 | volatile IMXRT_ENC_t* ENC; 162 | IRQ_NUMBER_t interrupt; 163 | void (*isr)(); 164 | uint16_t phaseA; 165 | uint16_t phaseB; 166 | uint16_t index; 167 | uint16_t home; 168 | uint16_t trigger; 169 | volatile uint32_t *clock_gate_register; 170 | uint32_t clock_gate_mask; 171 | } ENC_Channel_t; 172 | 173 | //Encoder channel structure 174 | typedef struct { 175 | uint8_t idx; 176 | uint8_t pin; 177 | volatile uint32_t *reg; // Which register controls the selection 178 | const uint32_t select_val; // Value for that selection 179 | uint16_t xbarIO; 180 | uint8_t xbarMUX; 181 | } ENC_Hardware_t; 182 | 183 | enc_config_t EncConfig; 184 | static const ENC_Channel_t channel[]; 185 | static const ENC_Hardware_t hardware[]; 186 | static const uint8_t _channel_count; 187 | static const uint8_t _hardware_count; 188 | 189 | 190 | public: 191 | QuadEncoder(uint8_t encoder_ch = 255, uint8_t PhaseA_pin = 255, uint8_t PhaseB_pin = 255, uint8_t pin_pus = 0, uint8_t index_pin = 255, uint8_t home_pin = 255, uint8_t trigger_pin = 255); 192 | void getConfig1(enc_config_t *config); 193 | void setConfigInitialPosition(); 194 | void setInitConfig(); 195 | void init(); 196 | void Init(const enc_config_t *config); 197 | int32_t read(); 198 | void write(uint32_t value); 199 | void setCompareValue(uint32_t compareValue); 200 | uint32_t getHoldPosition(); 201 | uint16_t getPositionDifference(); 202 | uint16_t getHoldDifference(); 203 | uint16_t getRevolution(); 204 | uint16_t getHoldRevolution(); 205 | void printConfig(enc_config_t *config); 206 | 207 | //xbara1 configuration 208 | void enc_xbara_mapping(uint8_t pin, uint8_t PHASE, uint8_t PUS); 209 | void xbar_connect(unsigned int input, unsigned int output); 210 | 211 | //interrupts 212 | void enableInterrupts(const enc_config_t *config); 213 | void disableInterrupts(uint32_t flag); 214 | void clearStatusFlags(uint32_t flag, uint8_t index); 215 | void enableCompareInterrupt(); 216 | 217 | // static class functions 218 | static void isrEnc1(); 219 | static void isrEnc2(); 220 | static void isrEnc3(); 221 | static void isrEnc4(); 222 | static inline void checkAndProcessInterrupt(uint8_t index); 223 | 224 | //counters 225 | static uint32_t homeCounter; 226 | static uint32_t indexCounter; 227 | static uint8_t compareValueFlag; 228 | private: 229 | static QuadEncoder *list[5]; 230 | volatile uint16_t _encoder_ch, _pin_idx; 231 | 232 | }; 233 | 234 | #endif 235 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hardware Quadrature Library for the Teensy 4.x 2 | Library based on NXP Quad Encoder SDK driver for the Encoder module but modified to support the Teensy 4.x infrastructure. 3 | 4 | There are 4 hardware quadrature encoder channels available the Teensy 4.x. The Teensy 4.x Encoders are supported on pins: 0, 1, 2, 3, 4, 5, 7, 8, 30, 31 and 33. On the T4.1 the following additional pins are supported: 36 and 37. 5 | 6 | WARNING! Pins 0, 5 and 37 share the same internal crossbar connections and are as such exclusive...pick one or the other. Same thing applies to pins 1 / 36 and 5 / 37. 7 | 8 | For Teensy 4.0: pins 0-8, 30, 31, 33 are supported. 9 | For Teensy 4.1: pins 0-8, 30, 31, 33 and pin 37 are supported. 10 | For Teensy Micromod: pins 0-8, 30, 31, 33, 36 and 37 are supported 11 | 12 | The constuctor is designed to tell the library what encoder channel, PhaseA and PhaseB pins to use as well as whether to use pullups on those pins. Example: 13 | ```c++ 14 | QuadEncoder myEnc1(1, 0, 1, 0); // Encoder on channel 1 of 4 available 15 | // Phase A (pin0), PhaseB(pin1), Pullups Req(0) 16 | QuadEncoder myEnc2(2, 2, 3, 0); // Encoder on channel 2 of 4 available 17 | //Phase A (pin2), PhaseB(pin3), Pullups Req(0) 18 | ``` 19 | The full constructor allows for the INDEX, HOME and TRIGGER pins if available: 20 | ```c++ 21 | QuadEncoder(uint8_t encoder_ch = 255, uint8_t PhaseA_pin = 255, uint8_t PhaseB_pin = 255, uint8_t pin_pus = 0, uint8_t index_pin = 255, uint8_t home_pin = 255, uint8_t trigger_pin = 255); 22 | ``` 23 | 24 | In setup the encoder is initialized as: 25 | ```c++ 26 | myEnc1.setInitConfig(); //Loads default configuration for the encoder channel 27 | myEnc1.init(); //Initializers the encoder for the channel selected 28 | ``` 29 | To access counts in loop: 30 | ```c++ 31 | mCurPosValue = myEnc1.read(); 32 | Serial.printf("Current position value1: %ld\r\n", mCurPosValue); 33 | Serial.printf("Position differential value1: %d\r\n", (int16_t)myEnc1.getHoldDifference()); 34 | ``` 35 | all you have to call is ```getPosition()``` while ```myEnc1.getHoldDifference()``` shows direction. 36 | 37 | Another example is the ability to change initial parameters once they are loaded: 38 | ```c++ 39 | myEnc2.setInitConfig(); // 40 | myEnc2.EncConfig.positionInitialValue =100; 41 | myEnc2.init(); 42 | ``` 43 | In this case ```myEnc2.EncConfig.positionInitialValue``` changes the starting value for the position counts. 44 | 45 | Current available parameters: 46 | ```c++ 47 | /* Basic counter. */ 48 | bool enableReverseDirection; 49 | bool decoderWorkMode; // 0 = Normal mode, 1 = PHASEA input generates a count signal while PHASEB input control the direction. 50 | 51 | /* Signal detection. */ 52 | uint8_t HOMETriggerMode; //0 - disable, 1 - rising, 2 - falling 53 | uint8_t INDEXTriggerMode; //0 - disabled, 1 - Use positive going edge-to-trigger initialization of position counters!, 2 - use falling 54 | bool clearCounter; 55 | bool clearHoldCounter; 56 | 57 | /* Filter for PHASEA, PHASEB, INDEX and HOME. */ 58 | /* Input Filter Sample Count. This value should be chosen to reduce the probability of noisy samples causing an incorrect transition to be recognized. The value represent the number of consecutive samples that must agree prior to the input filter accepting an input transition. A value of 0x0 represents 3 samples. A value of 0x7 represents 10 samples. The Available range is 0 - 7. */ 59 | uint16_t filterCount; 60 | 61 | /* Input Filter Sample Period. This value should be set such that the sampling period is larger than the period of the expected noise. This value represents the sampling period (in IPBus clock cycles) of the decoder input signals. The available range is 0 - 255. */ 62 | uint16_t filterSamplePeriod; 63 | 64 | /* Position compare. */ 65 | /* 0 - POSMATCH pulses when a match occurs between the position counters (POS) and the compare value (COMP). 1 - POSMATCH pulses when any position counter register is read. */ 66 | bool positionMatchMode; 67 | 68 | /* Position Compare Enabled. */ 69 | bool positionCompareMode; **<< NEW examples updated ** 70 | /*!< Position compare value. The available value is a 32-bit number.*/ 71 | uint32_t positionCompareValue; 72 | 73 | /* Modulus counting. */ 74 | /*0 - Use INDEX pulse to increment/decrement revolution counter. 1 - Use modulus counting roll-over/under to increment/decrement revolution counter. */ 75 | bool revolutionCountCondition; 76 | 77 | bool enableModuloCountMode; //Enable Modulo Counting. */ 78 | 79 | /*Position modulus value. This value would be available only when "enableModuloCountMode" = true. The available value is a 32-bit number. */ 80 | uint32_t positionModulusValue; 81 | 82 | //Position initial value. The available value is a 32-bit number. */ 83 | uint32_t positionInitialValue; 84 | ``` 85 | 86 | A couple of things to note when using the INDEX or the HOME triggers are used: 87 | 88 | 1. If one of the two trigger pins are used while ```INDEXTriggerMode``` is ```DISABLED``` in the configuration structure the position counts will continue to increment while the "Position HOLD revolution value" will increment when the ```index``` pulse is seen on the pin. 89 | 2. If ```INDEXTriggerMode``` is set to ```RISING_EDGE``` or ```FALLING_EDGE`` the associated interrupt will fire and increment the ```indexCounter``` but the position counts will be reset to zero. 90 | 3. This applies to the HOME trigger as well. 91 | 4. If indexTrigger = ENABLE and INDEXTriggerMode = DISABLE (default). The encoder count will continue increase with no reset while the indexCounter with increment when trigger by the index signal (needs to be negative trigger) and the Position HOLD revolution value will increment or decrement depending on direction. 92 | 5. If indexTrigger = ENABLE and INDEXTriggerMode = ENABLE. The encoder count will continue reset and the indexCounter with increment when trigger by the index signal (needs to be negative trigger) and the Position HOLD revolution value will increment or decrement depending on direction. 93 | 6. If indexTrigger = DISABLE (default) and INDEXTriggerMode = ENABLE. The encoder count will continue reset and the indexCounter will not increment with index signal (needs to be negative trigger) and the Position HOLD revolution value will increment or decrement depending on direction. 94 | 7. items 4, 5, and 6 apply for the home trigger signal (needs to be positive) as well 95 | 96 | ## Basic Usage for the Teensy 4.x 97 | Basic usage for the T4 is similar to the current Teensy Encoder library. 98 | 99 | ``` 100 | Encoder myEnc(enc, pin1, pin2); 101 | enc - encoder object (specify 1, 2, 3 or 4). 102 | pin1 - phase A 103 | pin2 - phase b 104 | myEnc.read(); 105 | Returns the accumulated position. This number can be positive or negative. 106 | myEnc.write(newPosition); 107 | Set the accumulated position to a new number. 108 | ``` 109 | ## Example Program 110 | (similar to example on https://www.pjrc.com/teensy/td_libs_Encoder.html. See SimpleEncoder.ino in the examples folder. 111 | ```/* Teensy 4 H/S Encoder Library - TwoKnobs Example 112 | * http://www.pjrc.com/teensy/td_libs_Encoder.html 113 | * 114 | * This example code is in the public domain. 115 | */ 116 | 117 | #include "Quadencoder.h" 118 | 119 | // Change these pin numbers to the pins connected to your encoder. 120 | // Allowable encoder pins: 121 | // 0, 1, 2, 3, 4, 5, 7, 30, 31 and 33 122 | // Encoder on channel 1 of 4 available 123 | // Phase A (pin0), PhaseB(pin1), 124 | QuadEncoder knobLeft(1, 0, 1); 125 | // Encoder on channel 2 of 4 available 126 | //Phase A (pin2), PhaseB(pin3), Pullups Req(0) 127 | QuadEncoder knobRight(2, 2, 3); 128 | // avoid using pins with LEDs attached 129 | 130 | void setup() { 131 | Serial.begin(9600); 132 | Serial.println("TwoKnobs Encoder Test:"); 133 | /* Initialize Encoder/knobLeft. */ 134 | knobLeft.setInitConfig(); 135 | //Optional filter setup 136 | //knobLeft.EncConfig.filterCount = 5; 137 | //knobLeft.EncConfig.filterSamplePeriod = 255; 138 | knobLeft.init(); 139 | /* Initialize Encoder/knobRight. */ 140 | knobRight.setInitConfig(); 141 | //knobRight.EncConfig.filterCount = 5; 142 | //knobRight.EncConfig.filterSamplePeriod = 255; 143 | knobRight.init(); 144 | } 145 | 146 | long positionLeft = -999; 147 | long positionRight = -999; 148 | 149 | void loop() { 150 | long newLeft, newRight; 151 | newLeft = knobLeft.read(); 152 | newRight = knobRight.read(); 153 | if (newLeft != positionLeft || newRight != positionRight) { 154 | Serial.print("Left = "); 155 | Serial.print(newLeft); 156 | Serial.print(", Right = "); 157 | Serial.print(newRight); 158 | Serial.println(); 159 | positionLeft = newLeft; 160 | positionRight = newRight; 161 | } 162 | // if a character is sent from the serial monitor, 163 | // reset both back to zero. 164 | if (Serial.available()) { 165 | Serial.read(); 166 | Serial.println("Reset both knobs to zero"); 167 | knobLeft.write(0); 168 | knobRight.write(0); 169 | } 170 | } 171 | ``` 172 | -------------------------------------------------------------------------------- /examples/QuadEncoder/QuadEncoder.ino: -------------------------------------------------------------------------------- 1 | #include "QuadEncoder.h" 2 | 3 | 4 | uint32_t mCurPosValue; 5 | uint32_t old_position = 0; 6 | uint32_t mCurPosValue1; 7 | uint32_t old_position1 = 0; 8 | QuadEncoder myEnc1(1, 0, 1, 0); // Encoder on channel 1 of 4 available 9 | // Phase A (pin0), PhaseB(pin1), Pullups Req(0) 10 | QuadEncoder myEnc2(2, 2, 3, 0); // Encoder on channel 2 of 4 available 11 | //Phase A (pin2), PhaseB(pin3), Pullups Req(0) 12 | 13 | void setup() 14 | { 15 | while(!Serial && millis() < 4000); 16 | 17 | /* Initialize the ENC module. */ 18 | myEnc1.setInitConfig(); // 19 | myEnc1.EncConfig.revolutionCountCondition = ENABLE; 20 | myEnc1.EncConfig.enableModuloCountMode = ENABLE; 21 | myEnc1.EncConfig.positionModulusValue = 20; 22 | // with above settings count rev every 20 ticks 23 | // if myEnc1.EncConfig.revolutionCountCondition = ENABLE; 24 | // is not defined or set to DISABLE, the position is zeroed every 25 | // 20 counts, if enabled revolution counter is incremented when 26 | // phaseA ahead of phaseB, and decrements from 65535 when reversed. 27 | myEnc1.init(); 28 | 29 | myEnc2.setInitConfig(); // 30 | myEnc2.EncConfig.positionInitialValue = 160; 31 | myEnc2.EncConfig.positionCompareMode = ENABLE; 32 | myEnc2.EncConfig.positionCompareValue = 200; 33 | myEnc2.EncConfig.filterCount = 5; 34 | myEnc2.EncConfig.filterSamplePeriod = 255; 35 | myEnc2.init(); 36 | } 37 | 38 | void loop(){ 39 | 40 | /* This read operation would capture all the position counter to responding hold registers. */ 41 | mCurPosValue = myEnc1.read(); 42 | 43 | if(mCurPosValue != old_position){ 44 | /* Read the position values. */ 45 | Serial.printf("Current position value1: %ld\r\n", mCurPosValue); 46 | Serial.printf("Position differential value1: %d\r\n", (int16_t)myEnc1.getHoldDifference()); 47 | Serial.printf("Position HOLD revolution value1: %d\r\n", myEnc1.getHoldRevolution()); 48 | Serial.println(); 49 | } 50 | 51 | old_position = mCurPosValue; 52 | 53 | mCurPosValue1 = myEnc2.read(); 54 | 55 | if(myEnc2.compareValueFlag == 1) { 56 | //myEnc2.init(); 57 | //resets counter to positionInitialValue so compare 58 | //will hit every positionCompareValue 59 | myEnc2.write(myEnc2.EncConfig.positionInitialValue); 60 | Serial.print("Compare Value Hit for Encoder 2: "); 61 | Serial.println(myEnc2.compareValueFlag); 62 | Serial.println(); 63 | myEnc2.compareValueFlag = 0; 64 | //Resets the compare value to 300 after initial value is set 65 | myEnc2.setCompareValue(300); 66 | // re-enable the Compare Interrupt 67 | myEnc2.enableCompareInterrupt(); 68 | 69 | } 70 | 71 | if(mCurPosValue1 != old_position1){ 72 | /* Read the position values. */ 73 | Serial.printf("Current position value2: %ld\r\n", mCurPosValue1); 74 | Serial.printf("Position differential value2: %d\r\n", (int16_t)myEnc2.getHoldDifference()); 75 | Serial.printf("Position revolution value2: %d\r\n", myEnc2.getHoldRevolution()); 76 | Serial.println(); 77 | } 78 | 79 | old_position1 = mCurPosValue1; 80 | } -------------------------------------------------------------------------------- /examples/QuadEncoder_Interrupt_pins/QuadEncoder_Interrupt_pins.ino: -------------------------------------------------------------------------------- 1 | #include "QuadEncoder.h" 2 | 3 | 4 | uint32_t mCurPosValue; 5 | uint32_t old_position = 0; 6 | uint32_t mCurPosValue1; 7 | uint32_t old_position1 = 0; 8 | const int channelNum = 1; 9 | const int phaseAPin = 0; 10 | const int phaseBPin = 1; 11 | const int pullupsRequired = 0; 12 | const int indexPin = 4; 13 | 14 | QuadEncoder myEnc1(channelNum, phaseAPin, phaseBPin, pullupsRequired, indexPin); // Encoder on channel 1 of 4 available 15 | // Phase A (pin0), PhaseB(pin1), Pullups Req(0) 16 | 17 | void setup() 18 | { 19 | while(!Serial && millis() < 4000); 20 | 21 | /* Initialize the ENC module. */ 22 | myEnc1.setInitConfig(); 23 | myEnc1.EncConfig.IndexTrigger = ENABLE; //enable to use index counter 24 | myEnc1.EncConfig.INDEXTriggerMode = RISING_EDGE; 25 | 26 | myEnc1.init(); 27 | 28 | } 29 | 30 | void loop(){ 31 | 32 | /* This read operation would capture all the position counter to responding hold registers. */ 33 | mCurPosValue = myEnc1.read(); 34 | 35 | if(mCurPosValue != old_position){ 36 | /* Read the position values. */ 37 | Serial.printf("Current position value1: %ld\r\n", mCurPosValue); 38 | Serial.printf("Position differential value1: %d\r\n", (int16_t)myEnc1.getHoldDifference()); 39 | Serial.printf("Position HOLD revolution value1: %d\r\n", myEnc1.getHoldRevolution()); 40 | Serial.printf("Index Counter: %d\r\n", myEnc1.indexCounter); 41 | Serial.println(); 42 | } 43 | 44 | old_position = mCurPosValue; 45 | 46 | } 47 | -------------------------------------------------------------------------------- /examples/SimpleEncoder/SimpleEncoder.ino: -------------------------------------------------------------------------------- 1 | /* Teensy 4 H/S Encoder Library - TwoKnobs Example 2 | * http://www.pjrc.com/teensy/td_libs_Encoder.html 3 | * 4 | * This example code is in the public domain. 5 | */ 6 | 7 | #include "QuadEncoder.h" 8 | 9 | // Change these pin numbers to the pins connected to your encoder. 10 | // Allowable encoder pins: 11 | // 0, 1, 2, 3, 4, 5, 7, 30, 31 and 33 12 | // Encoder on channel 1 of 4 available 13 | // Phase A (pin0), PhaseB(pin1), 14 | QuadEncoder knobLeft(1, 0, 1); 15 | // Encoder on channel 2 of 4 available 16 | //Phase A (pin2), PhaseB(pin3), Pullups Req(0) 17 | QuadEncoder knobRight(2, 2, 3); 18 | // avoid using pins with LEDs attached 19 | 20 | void setup() { 21 | Serial.begin(9600); 22 | Serial.println("TwoKnobs Encoder Test:"); 23 | /* Initialize Encoder/knobLeft. */ 24 | knobLeft.setInitConfig(); 25 | knobLeft.init(); 26 | /* Initialize Encoder/knobRight. */ 27 | knobRight.setInitConfig(); 28 | knobRight.init(); 29 | } 30 | 31 | long positionLeft = -999; 32 | long positionRight = -999; 33 | 34 | void loop() { 35 | long newLeft, newRight; 36 | newLeft = knobLeft.read(); 37 | newRight = knobRight.read(); 38 | if (newLeft != positionLeft || newRight != positionRight) { 39 | Serial.print("Left = "); 40 | Serial.print(newLeft); 41 | Serial.print(", Right = "); 42 | Serial.print(newRight); 43 | Serial.println(); 44 | positionLeft = newLeft; 45 | positionRight = newRight; 46 | } 47 | // if a character is sent from the serial monitor, 48 | // reset both back to zero. 49 | if (Serial.available()) { 50 | Serial.read(); 51 | Serial.println("Reset both knobs to zero"); 52 | knobLeft.write(0); 53 | knobRight.write(0); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=QuadEncoder 2 | version=1.0.0 3 | author=mjs513 4 | maintainer=mjs513 5 | sentence=Counts quadrature pulses from rotary & linear position encoders using Teensy 4.x hardware. 6 | paragraph=Library based on NXP Quad Encoder SDK driver for the Encoder module but modified to support the Teensy 4.x infrastructure. 7 | category=Signal Input/Output 8 | url=https://github.com/PaulStoffregen/Teensy-4.x-Quad-Encoder-Library 9 | architectures=* 10 | 11 | --------------------------------------------------------------------------------