Teensy 3.2 encoder library loosing counts + overclock + possible upgrade to 3.5

Status
Not open for further replies.

kubasmyk

Member
Hi, I have vertical position sensor with 0.5 micron resolution which is interfaced to my teensy through line driver. Im using pjrc encoder library and I am able to read roughly 200,000 counts/second at 72mhz without loosing count and roughly 280,000 counts/second at 144mhz. The position sensor speed at 280,000 is 140mm/s and I want to push it to +230mm/s at around 460,000 counts/second.

Are my counts/per second reasonable and would you expect this to work much faster on teensy 3.2.
Is there a possibility for the library to be optimised in assembly and has that already been done?
Would me switching to teensy 3.5/3.6 provide me with enough processing to catch all those interrupts?

Any input much appreciated.
 
Last edited:
The current encoder library is rather inefficient in its interrupt processing. I will put out a much faster encoder library very soon.
 
Teensy 3.x has 2 hardware quadrature decoders. They are built into FTM1 and FTM2
These work independently of your main body of code, and are clocked by the system clock.
https://forum.pjrc.com/threads/26803-Hardware-Quadrature-Code-for-Teensy-3-x?highlight=hardware+quadrature
This thread describes a library that uses the hardware decoder but farther down in the thread (#51), davidthings has stripped the setup code from the library which can be included in any program.
The hardware decoder supports both quadrature, and pulse and direction type encoders.
By minimizing the built in filtering, the maximum frequency can be achieved.
I don't know what the maximum rate is, because I have never tried to use it that fast.
You may need to handle counting through overflows as the FTMn_CNT register is only 16bits.
 
The hardware decoder is a good point. I kind of assumed you are already out of hardware channels. But if you are not using the hardware decoders, that's definitely the best way to go.
 
Thanks for input.

Hi I tried the code in that thread you mentioned and it seems to work , however I'm having difficulty in changing it from int 16 to int 32.

I had a look at the other code in that thread but most of it seems not be arduino-fied but rather extern "C" "templated" that I just don't know how to compile.

From what I gathered i need to create IRQ sub-routine to add/subtract the number to larger variable when TOF flag is set with the help of TOFDIR overflow direction. I'm just not enough skillful to do it. I need to change it to long type because my position sensor is only able to travel about 3cm and I need it to be more than 1m in length.

I really like the idea of using the hardware that experts on the forum here said it should be in the clock divisor mhz range which is pretty impressive and very robust and takes so little processing power for stuff like digital filtering or PID position motor control that could be a really cool project. Still trying
 
Last edited:
I had a look at the other code in that thread but most of it seems not be arduino-fied but rather extern "C" "templated" that I just don't know how to compile.
It's not that difficult. Just copy the 2 header files "QuadDecode.h" and "QuadDecode_def.h" into your sketch directory. Here is a simple test sketch. It uses the hardware decoder on FTM1 (pins 3 and 4). Pins 22 and 23 are connected to 3 and 4 to create a test signal.

QuadDecode<1> is using FTM1, QuadDecode<2> would use FTM2.

Code:
#include "QuadDecode_def.h"

QuadDecode<1> encoder;

const uint8_t out_pin1 = 22;
const uint8_t out_pin2 = 23;

inline void delayEncWrite() {
    // this is a 3 clock cycle delay
    asm volatile ("nop");
    asm volatile ("nop");
    asm volatile ("nop");
}

inline void writeEncoder4Pulses() {
    digitalWriteFast(out_pin2, HIGH);
    delayEncWrite();
    digitalWriteFast(out_pin1, HIGH);
    delayEncWrite();
    digitalWriteFast(out_pin2, LOW);
    delayEncWrite();
    digitalWriteFast(out_pin1, LOW);
    delayEncWrite();
}

void setup() {
    Serial.begin(9600);
    encoder.setup();

    delay(2000);
    pinMode(out_pin1, OUTPUT);
    pinMode(out_pin2, OUTPUT);
    
    encoder.start();

    uint32_t loop_counter = 0;

    uint32_t startTime = millis();
    while(true) {
        if(loop_counter % 100000 == 0) {
            uint32_t pos = encoder.calcPosn();
            Serial.print(loop_counter);
            Serial.print(" Encoder pos: ");
            Serial.print(pos);
            Serial.print(" elapsed: ");
            Serial.print(millis()-startTime);
            Serial.println();
        }
        writeEncoder4Pulses();
        writeEncoder4Pulses();
        writeEncoder4Pulses();
        writeEncoder4Pulses();
        writeEncoder4Pulses();
        writeEncoder4Pulses();
        writeEncoder4Pulses();
        writeEncoder4Pulses();
        writeEncoder4Pulses();
        writeEncoder4Pulses();

        loop_counter++;
    }
}
void loop() {}
 
Software Overflow handler

Thanks for input.

Hi I tried the code in that thread you mentioned and it seems to work , however I'm having difficulty in changing it from int 16 to int 32

kubasmyk
,

I'm working on an encoder routine for use as an HMI input device.
It's still a work in progress.
It reads the FTM1 overflow and direction bits, but uses a software handler for converting the encoder value to 32 bit.
My application has low quadrature pulse rates, so I'm not sure if the software handler will be fast enough for your application.
It does use the hardware decoder, for the counts, so it may work however. as long as the overflows don't occur faster than the main loop.
Much credit goes to TLB and davidthings from the forum for the bulk of the Hardware Quadrature Decoder
Here is the code:
Code:
// HardwareQuadratureEncoderScaleV500.ino

/*Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in the
Software without restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */

/* From decode.h by TLB  https://forum.pjrc.com/threads/26803-Hardware-Quadrature-Code-for-Teensy-3-x?p=56036&viewfull=1#post56036
 *
 * HARDWARE QUADRATURE DECODER DETAILS
 *
 *    ENCXA	 3	PTA12	 28	Input	    FTM1    7
 *    ENCXB	 4	PTA13	 29	Input	    FTM1    7
 *    ENCYA	32	PTB18	 41	Input	    FTM2    6
 *    ENCYB	25	PTB19	 42	Input	    FTM2    6
 *
 *Pin Control Registers PORTx_PCRn
 * Bit 10-8 MUX Pin Mux Control 1-7 alternative
 *   Alternative 1 is GPIO
 * Bit 4 PFE Passive Filter Enable 1 to enable
 * Bit 1 PE Pull Enable 1 to enable
 * Bit 0 PS Pull Select 0 is pulldown 1 is pullup
 *
 * The following registers are set in pins_teensy.c
 *        and need to be reset.
 *     FTM1_SC
 *     FTM1_CNT
 *     FTM1_MOD
 *     FTM1_C0SC
 *     FTM1_C1SC
 *
 *   FTM1_CNT Counter value
 *      Bit 15-0 is counter value
 *      Writing any value updates counter with CNTIN 
 *   FTM1_MOD Modulo (Max value)
 *	Bit 15-0 is counter value - set to 0xFFFF
 *	Write to CNT first
 *   FTM1_MODE Features Mode Selection
 *      Bit0 FTMEN FTM Enable - [WPDIS must be 1 to write]
 *      Bit2 WPDIS Write Protect to Disable
 *      Set WPDIS, then set FTMEN - must be set to access FTM regs
 *   FTM1_FMS Fault Mode Status
 *       Bit 6 WPEN Write Protect Enable
 *         Write 1 to clear WPDIS
 *   FTM1_FILTER
 *	Filter out pulses shorter than CHxFVALx 4 system clocks
 *	Bit 7-4 CH1FVAL for PHB
 *	Bit 3-0 CH0FVAL for PHA
 *   FTM1_QDCTRL Quadrature Decoder Control and Status
 *	Bit 7 PHAFLTREN Phase A Filter Enable
 *	Bit 6 PHBFLTREN Phase B Filter Enable
 *	Bit 5 PHAPOL Phase A Polarity
 *	Bit 4 PHBPOL Phase B Polarity
 *	Bit 3 QUADMODE Quadrature Decoder Mode
 *	  0 for Quadrature
 *	Bit 2 QUADIR Counting Direction
 *	Bit 1 TOFDIR Timer Overflow Dir
 *	  0 was set on bottom of counting
 *	  1 was set on top of counting
 *	Bit 0 QUADEN Quadrature Mode Enable
 *	  1 is quadrature mode enabled [WPDIS to write]
 *   FTM1_SC FTM1 Status and Control
 *     Bit 7 TOF Timer Overflow Flag
 *     Bit 6 TOIE Timer Overflow Interrupt Enable
 *     Bit 5 CPWMS Center Aligned PWM Select
 *       Resets to 0 (Write when WPDIS is 1)
 *     Rest of bits are 0 (Write when WPDIS is 1)
 *   FTM1_C0SC  FTM1 Channel 0 Status and Control
 *     Set WPDIS before writing control bits
 *     Bit 7 CHF Channel Flag
 *       Channel event occured
 *       Read and write 0 to clear
 *     Bit 6 CHIE Channel Interrupt Enable
 *       Set for compare interrupt
 *     Bit 5 MSB Channel Mode Select B
 *       Set to 0
 *     Bit 4 MSA Channel Mode Select A
 *       Set to 1
 *     Bit 3:2  ELS Edge or Level Select
 *       Set to 0:0
 *   FTM1_COMBINE 
 *     Set WPDIS to 1 before writing
 *     DECAPEN (Dual Edge Capture Enable)
 *     COMBINE (Combine Channels)
 *     Resets to all zero
 *   FTM1_C0V Channel 0 Value
 *     Channel Compare Value
 *     Set to 0x80 - halfway thru count
 *   FTM1_STATUS 
 *     Duplicate of CHnF bit for all channels
 *     Bit 0 CH0F
 */

/*  Simplified for one encoder connected to pins 3 and 4. 
 *  based on code provided https://forum.pjrc.com/threads/26803-Hardware-Quadrature-Code-for-Teensy-3-x/page3
 *  Pin Assignments, 3, 4
 *  for documentation see https://www.pjrc.com/teensy/K20P64M72SF1RM.pdf page 227
 *  for ports see https://github.com/russellbarnes/Teensy-3.1-C-Example/blob/master/core_pins.h
 *  (but did not yet get ports 8 and 7 working, nor pullups but the encoder required debounce sircuit anyway
 * https://hifiduino.wordpress.com/2010/10/21/arduino-code-for-buffalo-ii-dac-rotary-encoder-connections/ 
 
 * wozzy 10/09/2016  Tested with:  Arduino 1.6.12 with Teensyduino 1.31 beta 1 Hardware Teensy 3.6 @ 240MHz */

	#include <Bounce2.h>
	#define BUTTON_PIN 2    					// toggles count step size
	#define LED_PIN 29      					// Red LED indicates low speed
	#define LED_PIN2 30     					// Green LED indicates high speed

	bool ledState = HIGH; 						// RED LED initial state
	bool led2State = LOW; 						// GREEN LED initial state
	float count1InitialValue = 0.0;				// encoder value at startup
	float count1StepSize = 0.001;				// encouder count stepsize at startup
	float count1StepSizeSlow = 0.001;			// low speed encoder step size
	float count1StepSizeFast = 1.0;				// high speed encoder step size
	float count1StepSizeTurboBoost = 1.0;       // turbo boost step initial value
	float count1StepSizeTurboBoostValue = 10.0; // turbo boost step multiplier (activates on rapid turn rate of encoder)
	float encoder1 = count1InitialValue;        // sets stepsize to initial value defined above
	int16_t encoder1ShiftSpeed = 15;			// milliseconds threshold to activate turbo boost
	int16_t count1Min = -25000;					// the maximum count value continuing to turn encoder holds here
	int16_t count1Max = 25000;					// the minimum count value continuing to turn encoder holds here
	int16_t pulse1Time;							// time between pulses to determine turbo boost
	int16_t oldPulse1Time;
	int16_t newPulse1Time;
	int16_t rawChangeCounts1;					// total number of counts since last read (almost always -1 or +1)
	int16_t changeCounts1;						// total number of counts since last read (almost always -1 or +1)
	int16_t oldrawEncoder1Counts16;
	int16_t rawEncoder1Counts16;              	// 16-BIT hardware encoder count register (-32767 to +32767)
	int16_t overFlows1 = 0;
	int32_t buttonDownTime;					    // length of time in ms that button was depressed to toggle output enable
	int32_t oldEncoder1Counts32;
	int32_t encoder1Counts32;              		// 32-BIT encoder counts (-2,147,483,647 to +2,147,483,647)
	int32_t changeCounts32;						// total number of counts since last read (almost always -1 or +1)
	bool TOFDIR;								// Timer Overflow Direction ( 0 = Counting DOWN, 1 = Counting UP)
	
// Instantiate a Bounce object :
Bounce debouncer = Bounce(); 

void setup() {

  // Setup the button with an internal pull-up :
  pinMode(BUTTON_PIN,INPUT_PULLUP);
  
  // After setting up the button, setup the Bounce instance :
  debouncer.attach(BUTTON_PIN);
  debouncer.interval(20);
  
  // Setup the LEDS :
  pinMode(LED_PIN,OUTPUT);
  digitalWrite(LED_PIN,ledState);
  pinMode(LED_PIN2,OUTPUT);
  digitalWrite(LED_PIN2,led2State);

      PORTA_PCR12 = 0b0000011100010010;   	//Alt7-QD_FTM1,FilterEnable,Pulldown, 712h = 0b011100010010
      PORTA_PCR13 = 0x00000712;   			//Alt7-QD_FTM1,FilterEnable,Pulldown

      //Set FTMEN to be able to write registers
      FTM1_MODE=0x04;     // Write protect disable - reset value
      FTM1_MODE=0x05;     // Set FTM Enable

      // Set registers written in pins_teensy.c back to default
      FTM1_CNT = 0;
      FTM1_MOD = 0;
      FTM1_C0SC =0;
      FTM1_C1SC =0;
      FTM1_SC = 0;

      // Set registers to count quadrature
      FTM1_FILTER=0x22; // 2x4 clock filters on both channels
      FTM1_CNTIN=0;
	  FTM1_MOD=0xFFFF;  // Maximum value of counter
      FTM1_CNT=0;       // Updates counter with CNTIN
	  bitRead(FTM1_SC,7);
	  FTM1_SC = 0b00000010;   // BITS 0,1,2 = "PS" Prescaler (010 = /4) -  use /4 for 4 quadrature edges / detent type encoders
	  FTM1_QDCTRL=0b00000001; 	// Quadrature control  BIT 0 ="QUAD ENABLE",  BIT 1 = "TOFDIR" (Read Only)
								// BIT 2 = "QUADDIR" (Read Only), BIT 3 = "QUADMODE" (0=quadrature, 1=Count & Direction)
								// BITS 4&5 ="PHAPOL" (0 = normal polarity, 1 = reverse polarity), BITS 6&7 = "PHAFLTREN", "PHBFLTREN"
	  
// Write Protect Enable
	  FTM1_FMS=0x40;    // Write Protect, WPDIS=1
    
  Serial.begin(9600);  
}

void loop() {
    rawEncoder1Counts16 = FTM1_CNT;
 if (rawEncoder1Counts16 != oldrawEncoder1Counts16) {	  
      newPulse1Time = millis();
      rawChangeCounts1 = rawEncoder1Counts16 - oldrawEncoder1Counts16;
      oldrawEncoder1Counts16 = rawEncoder1Counts16;	  
	if (bitRead(FTM1_SC,7)==1){
		TOFDIR = bitRead(FTM1_QDCTRL,1);
		bitWrite(FTM1_SC,7,0); // Need to write 0 to TOF to clear it
	}
      encoder1Counts32 = rawEncoder1Counts16 + (overFlows1 * 256);
	  //encoder1Counts32 = rawEncoder1Counts16 + (overFlows1 * 32768);
	  changeCounts1 = encoder1Counts32 - oldEncoder1Counts32;	  	  	  	  
	  oldEncoder1Counts32 = encoder1Counts32;
	  updateEncoder1();
 }

   // Update the Bounce instance :
   debouncer.update(); 
   // Call code if Bounce fell (transition from HIGH to LOW) :
   if ( debouncer.fell() ) shiftSpeed();
   if ( debouncer.rose() ) toggleOutput();
}

void updateEncoder1 (void) {
	 pulse1Time = (int16_t)(newPulse1Time - oldPulse1Time);
	 oldPulse1Time = newPulse1Time;

	 if(pulse1Time < encoder1ShiftSpeed){
		count1StepSizeTurboBoost = count1StepSizeTurboBoostValue; 
	 }
	 else{
		count1StepSizeTurboBoost = 1.0;
	 }
	 
	 encoder1 = encoder1 + (changeCounts1 * count1StepSize * count1StepSizeTurboBoost);
	 if (encoder1 <= count1Min) encoder1 = count1Min;
	 if (encoder1 >= count1Max) encoder1 = count1Max;
	 Serial.print("ENCODER1 = ");
	 Serial.println(encoder1,3);
}

void shiftSpeed(void){
	buttonDownTime=millis();
     // Toggle LED state :
     ledState = !ledState;
     digitalWrite(LED_PIN,ledState);
     digitalWrite(LED_PIN2,!ledState); 

	 if(ledState){
	 count1StepSize = count1StepSizeSlow;
	 }
	 else{
	 count1StepSize = count1StepSizeFast;
	} 	 
}

void toggleOutput(void){
	if (millis()- buttonDownTime >= 2000) {
		Serial.println("TOGGLE STROBE OUTPUT");
	}	
}

Also here's a photo of my Sturdy EC11 Encoder Adaptor for Breadboards that was inspired by Paul's Sturdy Pot Adapter
SturdyEncoder.jpg
 
OMG it works! I have tried the code given by the tni and it does work perfectly. I removed the parts for "self testing" and this fits my application perfectly. I am able to measure stuff to "half a micron" under temperature laboratory conditions. I did simple code with millis() to calculate encoder speed and moved it by hand at speed of 70cm/s equivalent to 1,400,000 counts/second and so far i haven't got an anomalous reading.

Thanks tni & Woozy for brilliant input you're my heroes.
 
Good to hear that you have things working. In case you do have noise issues with your encoder signals, the hardware decoder has an adjustable glitch filter.

@Woozy:
The overflow handling is non-trivial, since there is the potential for oscillations right at the overflow point (tlb went through a lot of trouble to make this work). For your polling, using the overflow flag doesn't make sense. You can update the 32-bit encoder count using deltas of the 16-bit hardware count, not using the overflow flag at all.
 
Kubasmyk:
I'm really happy its working so well for you... Most of the credit goes to TLB for all the heavy lifting of his most excellent Hardware Quadrature Code.

tni:
I agree that my code is a mess as it's a work in progress. It's a learning tool for me to understand the inner workings of the hardware decoder. I totally agree that TLB went through great lengths to to handle the overflows. Unfortunatley for me I can't fully understand how it works. My handling of the TOF does work well for my application and I've tested it quite thoroughly. I'm still working on cleaning up the the code for my HMI application which uses a single encoder in conjunction with a touch screen to control many input parameters.
 
Last edited:
Status
Not open for further replies.
Back
Top