Hardware Quadrature Code for Teensy 3.x

I have had a look at the code by TLB, and I am quite puzzled by the fact that the addresses of the registers that are set up do not match the addresses as specified in the memory map from the K20 reference manual.

All the adresses seem to be off by 0x1000 compared to the datasheet (or kinetis.h for that matter...).

What am I missing here?
 
Last edited:
The programs attached are:

QuadDecode.h, QuadDecode_def.h - the important ones. Code to utilize the hardware quadrature decode channels on Teensy3.x

main.cpp - an example of use. As I mentioned, it is templated, and I did not Arduinofy it, so this shows how to use the templates.

GenEncoder.h, GenEncoder.cpp - program that generates simulated encoder signals for debug and development.


That means that you do not forcibly have to include all 5 files.

As tlb wrote, he did not "Arduinofy" it. That means that you can use the example file main.cpp as inspiration and documentation to create your own .ino sketch based on that. You'd have to copy QuadDecode.h and QuadDecode_def.h in your sketch folder and using #include tags to use their functions in the .ino file.
 
I feel like this is a silly question but I don't see a way to download tlb's attached files... Where can I find them?
 
In the very first post of this thread. Depending on your browser and OS, these files are either opened or downloaded when you click on them. In case they open, just copy/paste the content in new tabs with identical names in the Arduino IDE
 
Hi TLB,

For 4x count mode, 4 counts are made per pulse (low, rising, high, falling)
For 2x count mode, 2 counts are made per pulse (low, high)
For 1x count mdoe, 1 count is made per pulse (just the high I believe).

The encoder library is in 4x count mode https://www.pjrc.com/teensy/td_libs_Encoder.html

Is there any way to set 2x or 1x mode when using the Encoder library?

I’d like to reduce the amount of times the interrupts are triggered and would be okay with resolution being lowered.

Thanks
 
Sorry for the necro. This is a great library, but there's one catch - tlb uses pointers and non-template functions in the headers.

If you try to include the library in your sketch and into another library (e.g. trying to allow a communications library to use encoder data), you'll get a pile of multiple definition errors from the linker (despite the compile guards and even a #pragma once). It's a particularly tricky thing to debug if you haven't worked much with template classes.

I separated out those non-template parts into its own cpp. I'm uploading it here in case it helps someone else with the same problem.

I haven't changed anything except the arrangement of the code in files.

I also added an arduino sketch showing example use of the library (but not the encoder simulator).

View attachment QuadDecode.zip
 

Attachments

  • QuadDecode.zip
    8.4 KB · Views: 345
It is worth noting that C++ templates are actually considered a separate language. I try to avoid them when possible.
Sometimes they make sense, though, and in some instances I have found that templates can produce smaller code footprints.
No idea on why, but it is an observation of mine.
 
Hello everyone,

thanks for the code! I'm fairly new to uC-design and it offered me a great help and a bit of guidance through all these registers.
But I still need help.

I want to use the quadrature decoder not for positioning, but for speed. It is used to read out the speed of a motor, so it needs to run continuously. I migrated the project from an Arduino Due (with built in quad decoder) to a teensy 3.6. On the Arduino, I had an "extra" timer which just counted the main clock up to create a user-given interval, when the counter reached a limit it resets and at the same time it triggers the readout from the quadrature decoder which was stored in a variable.
But FTM1 and FTM2 on the Teensy only have two channels which are used for the encoder input. I tried to play around with some software waiting, but that contradicts the idea of doing the decoding on the hardware.
I hope you understand, what I try to do, and I'm not exactly sure if that is even possible. Can you help me out?

Right now I use the quaddecoder.h from this post. Since the readout should be continuous, there is also no problem with only having 16bit CNT since that value should not be reached within the desired time frame (motor has 2000 increments per rotation, is used around 1 rps)
 
Hello,

I got the encoder working, works nice. It is used to control a piezo motor. The motor is pulsed, so the pin for turning the motor on is set HIGH at the beginning of a cycle and the encoder throws an interrupt once the desired encoder flanks are counted, which turns the motor off. I managed to use an IntervalTimer with the cycle, so i got away from the loop()-method at all.
Using a template from this thread, i setup FTM2 in quadrature decode modus and in the setup it is given the MOD count, when to interrupt.
The speed of the motor is defined by this MOD count and the intervaltime of a cycle. For varying speed while the program/motor is running, I want to be able to change the MOD. But writing to the register never updates it. Tried different variations (reading FTM2_SC = 0, ...) which I found in some threads around here, but none if them worked.
Looking at the chip manual, it says that the MOD register is writen independent from FTMEN bit, if CLKS[1:0]=0:0, which is the case for my quadrature encoding since no internal clock is used?
Do I need to reset anything or disable the write protection which is setup?

My whole code is here:

The QuadratureDecode Subclass
Code:
#ifndef QUADDECODE_H
#define QUADDECODE_H

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

/*
* - MAGIE - 
* https://forum.pjrc.com/threads/26803-Hardware-Quadrature-Code-for-Teensy-3-x?p=90774&viewfull=1#post90774
*/

class QuadDecode_t {
  public:
    QuadDecode_t(int cts) {

      // Pin Assignments

      // 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
      FTM2_MODE=0x04;	    // Write protect disable - reset value
      FTM2_MODE=0x05;	    // Set FTM Enable

      // Set registers written in pins_teensy.c back to default
      FTM2_CNT = 0;
      FTM2_MOD = 0;
      FTM2_C0SC =0;
      FTM2_C1SC =0;
      FTM2_SC = 0x40;     //enable hardware trigger

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

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

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

      
      NVIC_ENABLE_IRQ(IRQ_FTM2);  
    }

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

    int16_t getCounter2( ) {
      int16_t c = FTM2_CNT;

      return c;
    }

  void call_changeCTS(int count){
    FTM2_MOD = count;
    Serial.println(count);
  }
};



#endif


The MAIN CLASS which controls the motor interval and basically just switches counts from 10 to 50 (but nothing happens)
Code:
#include "QuadDecode.h"
#include "AD9837.h"
 
#define pin_motor_direction 9 // for toggling the direction (left and right) of the motor
#define pin_enable_pushpull_converter 17 // to turn the push pull converters on and off
#define pin_test 20 // test pin for various debugging

const long interval = 107000; //Intervall in MIKROSEKUNDEN!
const int cts = 19; //cts pro Intervall - 1 weniger als angegeben! (!!!Schon vorher prüfen?!!!)

volatile bool motorTurning = false; 
bool dutyCycleReached = true;

const double minfrequency = 48000;
double frequency = 55000;
double initialfrequency;
float dutyCycle = 0.25;

AD9837 ad9837(frequency*4);
QuadDecode_t QuadDecode(cts);

IntervalTimer motorTimer; //Intervaltimer des Motors
IntervalTimer dutyCycleTimer; //Hilfsintervall zur Überprüfung vom eingestellten Duty Cycle

elapsedMillis changeCTS;
bool switched = false;
  
void setup() {
  tone(2, 2000, 200);
      // TURN OFF PUSH PULL CONVERTER
  pinMode(pin_enable_pushpull_converter,OUTPUT);
  digitalWrite(pin_enable_pushpull_converter, LOW);

  pinMode(pin_test, OUTPUT);
  digitalWrite(pin_test, LOW);

    // SETUP MOTOR DIRECTION
  pinMode(pin_motor_direction,OUTPUT); // set TOGGLE to OUTPUT
  digitalWrite(pin_motor_direction, LOW);  // set high for one motor direction

  Serial.begin(115200); 
  delay(1000);
 
  ad9837.setfrequency(frequency * 4);
 
    // TURN ON PUSH PULL CONVERTER
  digitalWrite(pin_enable_pushpull_converter, HIGH);
  motorTurning = false;
  while(!motorTurning){
    if(frequency < minfrequency){
      frequency = 49000;
    }
    frequency = frequency - 100;
    ad9837.setfrequency(frequency * 4);
    delay(300);
    Serial.println(frequency);
  }
  digitalWrite(pin_enable_pushpull_converter, LOW);
  frequency = frequency - 1000;
  Serial.println(frequency);
  ad9837.setfrequency(frequency * 4);
  initialfrequency = frequency;



  tone(2, 400, 200);
  delay(200);
  tone(2, 800, 200);
  delay(200);
  tone(2, 2000, 200);
  Serial.println("Start");

  digitalWrite(pin_enable_pushpull_converter, HIGH);
  motorTimer.begin(motorLoop, interval);
  dutyCycleTimer.begin(dutyCycleLoop, interval*dutyCycle);
  changeCTS = 0;
}
 
 
 
void loop() {

	if(changeCTS >= 10000){
		changeCTS = 0;
		if(switched){
			QuadDecode.call_changeCTS(50);
		}
		else{
			QuadDecode.call_changeCTS(10);
		}
		switched = !switched;
	}

}


//ISR FUNKTIONEN

//ISR Counter-Overflow: Erforderliche Anzahl Flanken erreicht
void ftm2_isr(void) {
  digitalWrite(pin_enable_pushpull_converter, LOW);
  motorTurning = true;
  // clear overflow flag 
  FTM2_SC&=0x7F;
  //digitalWrite(pin_test, LOW);
}

//Duty Cycle Überprüfung
void dutyCycleLoop(){
  if(motorTurning) {
    dutyCycleReached = true;
  }
  dutyCycleTimer.end();
}

//Motor Cycle Loop
void motorLoop(){
  //falls Flanken NICHT erreicht!
  if(!motorTurning){
    Serial.println("Dreht nicht! Dreht zu langsam! Erreicht erforderliche Flanken nicht! Frequenz herabsetzen.");
    frequency -= 250;
    ad9837.setfrequency(frequency * 4);
  }
  //falls Duty Cycle nicht erreicht!
  else if(!dutyCycleReached){
    Serial.println("Dreht, aber Duty Cycle wurde nicht erreicht! Frequenz herabsetzen.");
    frequency -= 250;
    ad9837.setfrequency(frequency * 4);
  }
  //falls unter die Mindestfrequenz gerutscht - setze Duty Cycle rauf, nochmal von vorne!
  if(frequency <= minfrequency){
    Serial.println("Innerhalb des vorgegebenen Duty Cycle nicht möglich! Erlaube höheren Duty Cycle.");
    frequency = initialfrequency;
    dutyCycle += 0.1;
    ad9837.setfrequency(frequency * 4);
    if(dutyCycle > 0.95){
      while(1){
        tone(2, 4500, 100);
        delay(200);            
      }
    }
  }
  //ansonsten: "ganz normaler" Durchlauf, setze Variablen zurück, "starte" Motor erneut
  motorTurning = false;
  dutyCycleReached = false;
  dutyCycleTimer.begin(dutyCycleLoop, interval * dutyCycle);
  digitalWrite(pin_enable_pushpull_converter, HIGH);
}
 
I got it working, by adjusting the FTM_PWMLOAD register. Not very intuitive to look there.
The code snippet in the ISR where the MOD is changed is:

Code:
void ftm2_isr(void) {
	if((FTM2_SC & FTM_SC_TOF) != 0){
		FTM2_SC &= ~FTM_SC_TOF;
	}
  digitalWrite(pin_enable_pushpull_converter, LOW);
  motorTurning = true;

  digitalWrite(pin_test, LOW);

  if(newMOD != 0){
	  Serial.println(newMOD);
	  FTM2_CNTIN = 0;
	  FTM2_MOD = newMOD;
	  FTM2_CNT = 0;
	  FTM2_PWMLOAD = 0x200; //sets the LDOK bit in PWNLOAD register
	  newMOD = 0;
	  Serial.println("MOD should be changed.");
  }
}
 
Back after many months, but since I managed to really solve my problem I'd like to update my solution to the problem. Looking back one can quite wonder what took so long to find the right answer.

When setting up the Flextimer, set the SYNCONF to a software trigger

FTM2_SYNCONF = 0x380; // configures the SYNC to SOFTWARE-TRIGGER and resetting counter with update

and then the updateMOD function is just

void setMOD(int newMOD){
noInterrupts();
FTM2_MOD = newMOD;
FTM2_SYNC = 0x80;
interrupts();
}

This updates the MOD instantly and resets CNT to CNTIN.
 
Sorry for the necro. This is a great library, but there's one catch - tlb uses pointers and non-template functions in the headers.

If you try to include the library in your sketch and into another library (e.g. trying to allow a communications library to use encoder data), you'll get a pile of multiple definition errors from the linker (despite the compile guards and even a #pragma once). It's a particularly tricky thing to debug if you haven't worked much with template classes.

I separated out those non-template parts into its own cpp. I'm uploading it here in case it helps someone else with the same problem.

I haven't changed anything except the arrangement of the code in files.

I also added an arduino sketch showing example use of the library (but not the encoder simulator).

View attachment 16449

We were having an issue with hard faults that we couldn't track down for the life of us. Switching to your version from the original (which has served me well for years) did the trick.

For reference, Teensy 3.2 and PlatformIO. Lots of sub-functions. Out of curiosity went back to early 2018 version of the Teensy framework to see if some other backend changes were the cause but again, only this version (TMcGahee) seemed to get us out of trouble. Thanks a million!!
 
Back
Top