Hardware Quadrature Code for Teensy 3.x

Simplified QuadDecode

Many thanks to TLB (OP) for this very helpful library.

My application is only a single revolution, so the 64K position counter restriction is no problem. In addition I will always need both decoders. These two relaxations of the requirements mean that the original code can be simplified dramatically. TLB saved me days of work with the original code, so I present my simplification in the hope that there might be others who need a simplified version.

I attach my simple of QuadDecode. No templates, no interrupts, header only.

Connect the encoders in the same way:
Encoder 1 - PhaseA pin 3, PhaseB pin 4.
Encoder 2 - Phase A pad 32, Phase B pad 25. (On the underside of the board)

Following TLB, I have left the Kinetis weak pulldowns activated. If you need pullups, tweak the PORTx_PCRn registers in lines 101-108 of the QuadDecode.h file.

Here's the demo. Comments / corrections welcome.

Code:
#include <QuadDecode.h>

void setup() {
  Serial.begin(115200);  
}

int16_t Count1;
int16_t Count2;

void loop() {

  int16_t c1 = QuadDecode.getCounter1();
  if ( c1 != Count1 ) {
    Serial.printf( " 1:%d\n", c1 );
    Count1 = c1;
  }

  int16_t c2 = QuadDecode.getCounter2();
  if ( c2 != Count2 ) {
    Serial.printf( " 2:%d\n", c2 );
    Count2 = c2;
  }
}

For fun, and to wrap up, here's the whole QuadDecode.h file

Code:
#ifndef QUADDECODE_H
#define QUADDECODE_H

#include <stdint.h>
#include "mk20dx128.h"
#include "core_pins.h"

/*
* Code simplified (and functionality reduced) from tlb's excellent code announced and discussed here
*    https://forum.pjrc.com/threads/26803-Hardware-Quadrature-Code-for-Teensy-3-x
* Changes:
*   - removed >64k irq code
*   - untemplated into a single monolith
*
* HARDWARE DETAILS
*          Teensy ARM
*    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
*
* KINETIS REGISTERS
*   PORTx_PCRn
*      Bit 10-8 MUX - function Multiplexor (0 = Disabled - Analog) See P223 of Manual
*      Bit    4 PFE - Passive Filter Enable
*      Bit    1 PE  - Pull Enable
*      Bit    0 PS  - Pull Select 0 - Down, 1 - Up
*      0x00000712 = Alt7-QD_FTM1,FilterEnable,Pulldown
*
*   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
*/

class QuadDecode_t {
  public:
    QuadDecode_t() {

      // Pin Assignments

      // FTM1 Pins
      // K20 pin 28,29
      // Bit 8-10 is Alt Assignment
      PORTA_PCR12 = 0x00000712;   //Alt7-QD_FTM1,FilterEnable,Pulldown
      PORTA_PCR13 = 0x00000712;   //Alt7-QD_FTM1,FilterEnable,Pulldown

      // FTM2 Pins
      // K20 pin 41,42
      // Bit 8-10 is Alt Assignment
      PORTB_PCR18 = 0x00000612;   //Alt6-QD_FTM2,FilterEnable,Pulldown
      PORTB_PCR19 = 0x00000612;   //Alt6-QD_FTM2,FilterEnable,Pulldown

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

      FTM2_MODE=0x04;	    // Write protect disable - reset value
      FTM2_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;

      FTM2_CNT = 0;
      FTM2_MOD = 0;
      FTM2_C0SC =0;
      FTM2_C1SC =0;
      FTM2_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

      FTM2_FILTER=0x22;	// 2x4 clock filters on both channels
      FTM2_CNTIN=0;
      FTM2_MOD=0xFFFF;	// Maximum value of counter
      FTM2_CNT=0;	    	// Updates counter with CNTIN

      // Set Registers for output compare mode - for IRQ?
      //FTM1_COMBINE=0;	    // Reset value, make sure
      //FTM1_C0SC=0x10;	      // Bit 4 Channel Mode
      //FTM1_C0V= COMP_LOW;	    // Initial Compare Interrupt Value

      FTM1_QDCTRL=0b11000001;	    // Quadrature control
      FTM2_QDCTRL=0b11000001;	    // Quadrature control
      //        Filter enabled, QUADEN set

      // Write Protect Enable
      FTM1_FMS=0x40;		// Write Protect, WPDIS=1
      FTM2_FMS=0x40;		// Write Protect, WPDIS=1
    }

    void setCounter1( int16_t c ) {
      FTM1_CNT = c;
    }

    void setCounter2( int16_t c ) {
      FTM2_CNT = c;
    }

    int16_t getCounter1( ) {
      int16_t c = FTM1_CNT;

      return c;
    }

    int16_t getCounter2( ) {
      int16_t c = FTM2_CNT;

      return c;
    }

};

QuadDecode_t QuadDecode;

#endif
 

Attachments

  • QuadDecode.h
    5.3 KB · Views: 637
  • demo.ino
    366 bytes · Views: 527
Hi TLB,

I'm new to coding,
Reference to the above code running standalone on Arduino, I manage to get pretty accurate pulse counted.
In my project, I need to start and stop the counting based on another sensor input. (high=start;low=stop)
How can I embed the your encoding routine into the call function.
I have tried without success, either I get corrupted output, or the count just stop.
Your advice would be of great help.
;)
 
wushumike:

I'm not exactly sure what you are asking.

If you want to stop the hardware counting, my question is what is the encoder doing during this time. If the encoder is not moving, then there is no counting. If the encoder continues to move, there are issues with starting and stopping the hardware counting.

What I would suggest is having an internal position variable. When you start counting, either zero the position count, or read an initial position count. Then your internal position is some combination of your internal position and the initial position and the current encoder position.

TLB
 
Hi TLB

Thanks for the reply. To clarify, yes the encoder is continuously running. The counter value is taken when sensor is on; and counter value will not update when the sensor is off.... On the next sensor "on" the counter will continue from where it last stopped...
So to say for the existing code, it is not feasible for starting and stopping the hardware counting while the encoder is continuously running.

Ok, I will work on your advice using internal position variable.

Thanks
wushumike
 
Interesting, just taking into use a rotary encoder, it is just a hand knob so no fast pulses, was thinking writing my own code, simply polling the lines, but this looks interesting, thanks for posting.

"If you need pullups, tweak the PORTx_PCRn registers in lines 101-108" I do need pullups, but how should it be tweaked? also can other ports be used and how many encoders does it support?

Edit: reading the users manual

0x00000713 = Alt7-QD_FTM1,FilterEnable,Pull up

I use pins 7 and 8, reading this https://github.com/russellbarnes/Teensy-3.1-C-Example/blob/master/core_pins.h
// 7 D2
// 8 D3
So I suppose
PORTD_PCR2 = 0x00000713; //Alt7-QD_FTM1,FilterEnable,Pullup
PORTD_PCR3 = 0x00000713; //Alt7-QD_FTM1,FilterEnable,Pullup

I find only FTM1_QDCTRL and FTM2_QDCTRL so I suppose 2 channels is max?
 
Last edited:
Did not get it working on pins 8 and 7 with internal pullups. Works now fine with the further simplified code below on inputs 3 and 4.

Code:
//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

void setup() {
// 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/

      PORTA_PCR12 = 0x00000712;   //Alt7-QD_FTM1,FilterEnable,Pulldown
      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


      FTM1_QDCTRL=0b11000001;     // Quadrature control

      // Write Protect Enable
      FTM1_FMS=0x40;    // Write Protect, WPDIS=1


  Serial.begin(115200);  
}

int16_t Count1;
int16_t c1;

void loop() {

  c1=FTM1_CNT;


  if ( c1 != Count1 ) {
    Serial.println( c1 );
    Count1 = c1;
  }


}
 
Hello,

I'm new to Teensy (3.2), and was wondering about the quadrature feature of FTM. When you say "64K limit", do you mean the counter doesn't roll over? I've used the quadrature function on Freescale's TPU, and it's also a 16-bit counter, but you can read the counter periodically to synthesize a 32-bit (or 64-bit) count in software.

Joe
 
I'm new to Teensy (3.2), and was wondering about the quadrature feature of FTM. When you say "64K limit", do you mean the counter doesn't roll over? I've used the quadrature function on Freescale's TPU, and it's also a 16-bit counter
Same here. It's a 16-bit hardware counter. It does roll over.
but you can read the counter periodically to synthesize a 32-bit (or 64-bit) count in software.
The code in the initial post provides a 32-bit counter. It doesn't use polling, it's based on counter interrupts and properly handles overflow (which is very tricky). There are subsequent posts with mangled versions (not providing a 32-bit counter) of the original code.
 
Hi Guys. I am just checking in to hear whether or not the 3.5 has more than two quad-decoder enabled Flex Timer Modules.

I would rather use the 3.5 than the 3.6 because my rotary encoders are 5V.


I have been patiently waiting for a new chip so I can get more quad decoders on one board. Reading the 66 and 64 manuals, it looks like there are still only two FTM's. ;(
 
After some reading, the pins on the Teensy 3.5 that are relevant to updating this library are:

K9 as QD_PHA or pin 3
J9 as QD_PHB or pin 4

D12 as QD_PHA or pin 29
D11 as QD_PHB or pin 30

Four channels of hardware quadrature decoding is decent. rough schematic.jpg
 
Last edited:
After even more research, the following pins can use hardware quad on the Teensy 3.5 and 3.6

Teensy 3.6
FTM1 Quad-A: Teensy 3.6 pins 3 or 31/A12
FTM1 Quad-B: Teensy 3.6 pins 28, 27, 19/A5, or 32/A13
FTM2 Quad-A: Teensy 3.6 pin 29
FTM2 Quad-B: Teensy 3.6 pin 30

Teensy 3.5
FTM1 Quad-A: Teensy 3.5 pins 3 or 16/A2
FTM1 Quad-B: Teensy 3.5 pins 4 or 17/A3
FTM2 Quad-A: Teensy 3.5 pin 29
FTM2 Quad-B: Teensy 3.5 pin 30
 
Getting 1's and 0's when motor turns

Are your grounds hooked up correctly? You get this kind of a pattern when one encoder line is not toggling. One line goes high, counts 1, same line goes low again, counts down again. The line may be toggling but have some kind of an offset such that it never goes below the switching threshold.

And it looks like both sets of code give similar results; both are saying one encoder line has a problem.

Teensy3.1 inputs are 5 V compatible so you can run 5V encoder lines directly into the part. A very nice feature.

TLB

{... it never goes below the switching threshold... Do you mean the pin does not go to zero so the Quadrature does not recognize it as valid toggle to increment the counter?}


Thanks TLB for the wonderful work. TLB, I am having some problems implementing your code with my hardware.

My issue is similar to the previous poster (harryprayiv). I am using Faulharber 1717 Motors with built in 1024 tic/rev encoders. (https://www.faulhaber.com/fileadmin/Import/Media/EN_1717_SR_DFF.pdf) I tested my ground and 5v supply line to the encoder, as you suggested, and they seem fine. Meaning, the 5v supply is not fluctuating. Also, all my grounds are common. I am not sure what other electrical abnormality I should look for.

I am using your original code, without the GenEncoder of course. The output shows x and y values toggling between 0 and 1 for the respective motor being turned. Like (harryprayiv) I am not getting an accumulated count when turning the motors by hand. I have been attempting to get the original code working with my motors and circuitry, with little success. Is there anything I am missing?

My test circuit is simple: Motor 1: 5v and ground to pins 3 and 4 on the encoder respectfully. Encoder Channels A and B are connected to pins 32 and 25 on the Teensy. Lastly, encoder pin 1 and 2 are connected to the motor driver output ( + and - ). Motor 2: has similar connections except I utilize pin 3 and 4 on the Teensy.{Encoder pdf specs: https://www.faulhaber.com/fileadmin/Import/Media/EN_IE2-1024_DFF.pdf }

Without activating the motor driver and spinning it by hand, the original code toggles 1 and 0 both forward and backward (no negative for backwards and no accumulated tic count sum). Driving the motor by adding code to the sample moves the motors, yet the 1's and 0's toggling remains the same.

Project details (MicroMouse): This is a robotic project for the Micromouse competition. My Micromouse/Robot Maze solver utilizes Teensy 3.2 as its processor. For navigating the maze, precise wheel turns and speed control is needed. Thus the high resolution encoders built into the Faulhaber motors. I have been utilizing software and interrupt based encoder counting with success at low speeds. However, with motors at full throttle, at 1024 tic/rev on a geared system, plus retrieving data from other sensors controlled by Interval Timers, the data processing and timing becomes quickly overwhelmed. Therefore, hardware encoder counting is a must as far as I see it.

Any help in implementing hardware quadrature accumulated encoder counts would be greatly appreciated. The competition is in three weeks Arrrrrrrrgggggghhhh.
 
Last edited:
Same issue seen as in the last post : 1's and 0's

{... it never goes below the switching threshold... Do you mean the pin does not go to zero so the Quadrature does not recognize it as valid toggle to increment the counter?}


Thanks TLB for the wonderful work. TLB, I am having some problems implementing your code with my hardware.

Using an US Digital E4T quad encoder 5V supply with channel pins connected to T3.2 pins 3,4 ( no external resistors). Getting only a string of (1,0,1..)
Encoder spec: E4TQuadEncSpec.JPG
Phase relationship:e_series_output_0.gif

The voltage measured at both pins goes from 4.3V to 0 consistently.
Also attached 1K resistors in series with pins 3, 4 - Same issue. Getting only a string of (1,0,1..)
 
If you want to quickly check whether the problem lies in your hardware or the your code you can run encSim on a second Teensy. (see here: https://github.com/luni64/EncSim.
It generates quadrature signals with or without chatter, adjustable frequency and phase. It can be controlled by a simple serial interface. The repo includes precompiled .hex files for a quick upload to a T3.2 or T3.6.

Hope that helps



interface.PNG
 
If you want to quickly check whether the problem lies in your hardware or the your code you can run encSim on a second Teensy. (see here: https://github.com/luni64/EncSim.
It generates quadrature signals with or without chatter, adjustable frequency and phase. It can be controlled by a simple serial interface. The repo includes precompiled .hex files for a quick upload to a T3.2 or T3.6.

Hope that helps



interface.PNG

Awesome! Will check this out.

Note: Switched the pins to 22,23 on the T3.2 and used encoder.h (teensy lib for encoders). Works fine.
Now trying to figure out how to modify the examples in this post to assign ports 22,23 for the Hard Encoder. That is no trivial task.

Full disclosure: Am also using the PropShield LC. But the propshield's pins do not conflict with ports 3,4 or 22,23
 
IIRC the alternative pins for the HW encoder on a T3.2 are 28/29. -> You can not use 22/23 with the hardware encoder.



Any special reason why you want to use the HW encoders? Your US Digital E4T encoder doesn't look like a high speed device?
 
IIRC the alternative pins for the HW encoder on a T3.2 are 28/29. -> You can not use 22/23 with the hardware encoder.

Ah I overlooked that, thanks!


Any special reason why you want to use the HW encoders? Your US Digital E4T encoder doesn't look like a high speed device?

It's 500 CPR/2000 PPR - sufficient for my project, but may go for higher resolution. On a zip wire-line trolley, it gives me results that vary off by at least 1 to 2 full revolutions on returning back so I'm looking for more slow speed precision to overcome the chatter on the surface.
 
All these functions are jammed into a 1.4 x 0.7 inch panel with all the headings on a "grid of 0.1, so you can slap on a breadboard and get working! Of all the other things the teensy has gifted with more RAM (64K, thats 4x more teensy 3.0) and 256kb of flash memory! The teensy 3.1 now has the ability to withstand 5V digital inputs though. All analog pins are still only 3.3V
 
Teensy 3.6
FTM1 Quad-A: Teensy 3.6 pins 3 or 31/A12
FTM1 Quad-B: Teensy 3.6 pins 28, 27, 19/A5, or 32/A13

Teensy 3.5
FTM1 Quad-A: Teensy 3.5 pins 3 or 16/A2
FTM1 Quad-B: Teensy 3.5 pins 4 or 17/A3

Hi I've got both FTM1 and FTM2 working great on a teensy 3.5 but would like to look at using a Teensy 3.6. Is the FTM1 Quad B really on different pins in the 3.6?

Thanks

Trev
 
I could be wrong, but my excel document I put together as part of beta, makes it look like the 3.6 and 3.5 have the same pins...
Example pins 3 and 4:
Teensy 3.6
Code:
3	PTA12	CMP2_IN0	CMP2_IN0	PTA12	CAN0_TX	[COLOR="#FF0000"]FTM1_CH0	[/COLOR]RMII0_RXD1/MII0_RXD1	I2C2_SCL	I2S0_TXD0	FTM1_QD_PHA/TPM1_CH0
4	PTA13/LLWU_P4	CMP2_IN1	CMP2_IN1	PTA13/LLWU_P4	CAN0_RX	[COLOR="#FF0000"]FTM1_CH1	[/COLOR]RMII0_RXD0/MII0_RXD0	I2C2_SDA	I2S0_TX_FS	FTM1_QD_PHB/TPM1_CH1
Teensy 3.5
Code:
3	PTA12	CMP2_IN0	CMP2_IN0	PTA12	CAN0_TX	[COLOR="#FF0000"]FTM1_CH0	[/COLOR]RMII0_RXD1/MII0_RXD1	I2C2_SCL	I2S0_TXD0	FTM1_QD_PHA
4	PTA13/LLWU_P4	CMP2_IN1	CMP2_IN1	PTA13/LLWU_P4	CAN0_RX	[COLOR="#FF0000"]FTM1_CH1	[/COLOR]RMII0_RXD0/MII0_RXD0	I2C2_SDA	I2S0_TX_FS	FTM1_QD_PHB
And similar for 16 and 17

But again maybe I did not double verify that functionality
 
Hi KurtE, thanks for the reply. That looks great. I'll buy a 3.6 and give it a go, if not if still got the 3.6 for another project. The pcb I've done is all 3.3v safe so would nice to have the choice between the two.

Trev
 
Hi, did anyone manage to get the quad_ decoders working on teensie 3.6. I am trying by reading this post and the manuals to understand how to change the pin assignments in the existing library of davidthings. I am quite new to embedded and all look Chinese to me. Any help will be great.


My encoders are connected as follows:

ENCXA=3,PTA12 =64
ENCXB=4, PTA13 =65
ENCYA=29,PTB18=97
ENCYB=30,PTB19=98
 
Back
Top