Multiple quadrature decoders on the Giga and Due

The decoders have been tested for the Arduino Giga and Due. The decoders are read and processed via interrupt. Usually edge detection is used for the interrupt routine, but I use a system of fixed sampling time that simultaneously processes all encoders. You can choose the sampling period according to the expected max encoder speed. You can start from the max motor speed and the number of pulses per revolution of the encoder. I use a lot of Fischertechnik motors and they have an unload speed of max 600 tr/min. If you have an encoder of 100 pulses per revolution then you have 600/60*100 = 1000 pulses /sec.
You should also keep in mind that most encoders have a worst-case ratio of 40/60 ratio, so calculate 20% higher speed. I take another 100% safety myself which means I have to make the calculation at 240%, so 2.4Khz instead of 1Khz. It follows that you have to take an interrupt sample at least every 416 us and calculate all encoder positions. I measured the interrupt routine to handle 5 quadrature decoders which is 4691 ns for the Due and 420.5 ns for the Giga. So the Giga is 12x faster. For all tests, I brought the sampling period at 132 us. That's still a lot higher than necessary.
I use an LCD 4x20 char display with I2C. I read that 10x per sec to display position values.
You can see a scope shot here:

Giga_5x_decoder_scope_0428 by Frans, on Flickr

The Giga setup:

Giga_5x_decoder_20230428 by Frans, on Flickr

And the Due setup:

Due_decoders_20230427_152454 by Frans, on Flickr

The Due program:

/* 
Multiple Quadrature decoders + LCD display 4x20 char   
fotoopa 2023
https://www.flickr.com/photos/fotoopa_hs/

The Giga takes about  416 ns to process all 5 decoders in the interrupt routine
The Due  takes about 4691 ns to process all 5 decoders in the interrupt routine
The Giga is about 12x faster than the Due.
Interrupt every 132 us via timer

*/
#include "HD44780_LCD_PCF8574.h"
HD44780LCD myLCD( 4, 20, 0x27); // I2C LCD adr $27 4x20 char

static int32_t counter0 = 100;  // powerup counter value 100
static int32_t counter1 = 200;  // powerup counter value 200
static int32_t counter2 = 300;  // powerup counter value 300
static int32_t counter3 = 400;  // powerup counter value 400
static int32_t counter4 = 500;  // powerup counter value 500

static uint16_t enc_inputs = 0;

bool ena_ena0;
bool ena_dir0;
bool ena_ena1;
bool ena_dir1;
bool ena_ena2;
bool ena_dir2;
bool ena_ena3;
bool ena_dir3;
bool ena_ena4;
bool ena_dir4;
bool ena_ena5;
bool ena_dir5;
bool enc_A0;
bool enc_B0;
bool enc_A1;
bool enc_B1;
bool enc_A2;
bool enc_B2;
bool enc_A3;
bool enc_B3;
bool enc_A4;
bool enc_B4;
bool enc_A5;
bool enc_B5;
bool enc_old_A0;
bool enc_old_B0;
bool enc_old_A1;
bool enc_old_B1;
bool enc_old_A2;
bool enc_old_B2;
bool enc_old_A3;
bool enc_old_B3;
bool enc_old_A4;
bool enc_old_B4;

void setup() {
  Serial.begin(115200);
  pinMode(33, OUTPUT);    // D33 pulse output for trigger scope timing
  /*
  Clearing one (or more) PIO line(s) and setting another one (or more) PIO line(s) synchronously cannot be done by
  using PIO_SODR and PIO_CODR registers. It requires two successive write operations into two different
  registers. To overcome this, the PIO Controller offers a direct control of PIO outputs by single write access to
  PIO_ODSR (Output Data Status Register).Only bits unmasked by PIO_OWSR (Output Write Status Register) are
  written. The mask bits in PIO_OWSR are set by writing to PIO_OWER (Output Write Enable Register) and cleared
  by writing to PIO_OWDR (Output Write Disable Register).
  After reset, the synchronous data output is disabled on all the I/O lines as PIO_OWSR resets at 0x0.
  */
  PIOC -> PIO_OWSR = PIO_OWSR_P1; // Output Write Status Register actief for pin D33 = scope trigger pulse

  digitalWrite(33, 0);
  pinMode(25, INPUT);   // PIOD bit 0 D25
  pinMode(26, INPUT);   // PIOD bit 1 D26
  pinMode(27, INPUT);   // PIOD bit 2 D27
  pinMode(28, INPUT);   // PIOD bit 3 D28
  pinMode(14, INPUT);   // PIOD bit 4 D14
  pinMode(15, INPUT);   // PIOD bit 5 D15
  pinMode(29, INPUT);   // PIOD bit 6 D29
  pinMode(11, INPUT);   // PIOD bit 7 D11
  pinMode(12, INPUT);   // PIOD bit 8 D12
  pinMode(30, INPUT);   // PIOD bit 9 D30

  PIOD->PIO_PUER = PIO_PUDR_P0 |PIO_PUDR_P1 |PIO_PUDR_P2 |PIO_PUDR_P3
                  |PIO_PUDR_P4 |PIO_PUDR_P5 |PIO_PUDR_P6 |PIO_PUDR_P7
                  |PIO_PUDR_P8 |PIO_PUDR_P9 ;  // pullup on inputs for D25 D26 D27 D28 D14 D15 D29 D11 D12 D30
  tc_setup();
  delay(5);
  myLCD.PCF8574_LCDInit(LCDCursorTypeOff);
  myLCD.PCF8574_LCDClearScreen();
  myLCD.PCF8574_LCDBackLightSet(true);
}

void loop() {
  char titel[] = " Quadrature Decoders";
  myLCD.PCF8574_LCDGOTO(LCDLineNumberOne, 0);
  myLCD.PCF8574_LCDSendString(titel);

  while (true) {
	  myLCD.PCF8574_LCDGOTO(LCDLineNumberTwo, 9);
	  myLCD.print(counter0);
    myLCD.PCF8574_LCDSendChar(' ');
	  myLCD.PCF8574_LCDGOTO(LCDLineNumberThree, 4);
	  myLCD.print(counter1);
    myLCD.PCF8574_LCDSendChar(' ');
	  myLCD.PCF8574_LCDGOTO(LCDLineNumberFour, 4);
	  myLCD.print(counter2);
    myLCD.PCF8574_LCDSendChar(' ');
	  myLCD.PCF8574_LCDGOTO(LCDLineNumberThree, 14);
	  myLCD.print(counter3);
    myLCD.PCF8574_LCDSendChar(' ');
	  myLCD.PCF8574_LCDGOTO(LCDLineNumberFour, 14);
	  myLCD.print(counter4);
    myLCD.PCF8574_LCDSendChar(' ');
    delay(100);
  }
}

void tc_setup() {
  PMC->PMC_PCER1 |= PMC_PCER1_PID35;                        // TC8 power ON : Timer Counter 2 channel 2 IS TC8 - see page 38
  TC2->TC_CHANNEL[2].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK2    // MCK/8 = 82/8 --> 10.5 M Hz, clk on rising edge
                              | TC_CMR_WAVE                 // Waveform mode
                              | TC_CMR_WAVSEL_UP_RC;        // UP mode with automatic trigger on RC Compare
  TC2->TC_CHANNEL[2].TC_RC = 42;                            // 4 usec/ timerloop
  TC2->TC_CHANNEL[2].TC_IER = TC_IER_CPCS;                  // Interrupt on RC compare match
  TC2->TC_CHANNEL[2].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN;  // Software trigger TC2 counter and enable
  NVIC_EnableIRQ(TC8_IRQn);
}
static uint32_t Count;
void TC8_Handler() {

  if (Count++ == 31) {                // TC_CMR_TCCLKS_TIMER_CLOCK2 x32 = 128 us decoder update periode.
  Count = 0;                          // update duration to process all 5 decoders 4.775 us
  PIOC -> PIO_SODR = PIO_SODR_P1 ;    // set pin D33 high scope trigger pulse
  // copy encoders old
  enc_old_A0 = enc_A0;
  enc_old_B0 = enc_B0;
  enc_old_A1 = enc_A1;
  enc_old_B1 = enc_B1;
  enc_old_A2 = enc_A2;
  enc_old_B2 = enc_B2;
  enc_old_A3 = enc_A3;
  enc_old_B3 = enc_B3;
  enc_old_A4 = enc_A4;
  enc_old_B4 = enc_B4;
  enc_inputs  = (PIOD->PIO_PDSR);   // Read PIOD Reg  for Data: D25 D26 D27 D28 D14 D15 D29 D11 D12 D30 (D32)
  // copy encoders    
  enc_A0 = enc_inputs & PIO_PER_P0; // copy D25 level
  enc_B0 = enc_inputs & PIO_PER_P1; // copy D26 level
  enc_A1 = enc_inputs & PIO_PER_P2; // copy D27 level
  enc_B1 = enc_inputs & PIO_PER_P3; // copy D28 level
  enc_A2 = enc_inputs & PIO_PER_P4; // copy D14 level
  enc_B2 = enc_inputs & PIO_PER_P5; // copy D15 level
  enc_A3 = enc_inputs & PIO_PER_P6; // copy D29 level
  enc_B3 = enc_inputs & PIO_PER_P7; // copy D11 level
  enc_A4 = enc_inputs & PIO_PER_P8; // copy D12 level
  enc_B4 = enc_inputs & PIO_PER_P9; // copy D30 level
  // ena dir
  ena_ena0 = enc_A0 ^ enc_old_A0 ^ enc_B0 ^ enc_old_B0;
  ena_dir0 = enc_A0 ^ enc_old_B0;
  ena_ena1 = enc_A1 ^ enc_old_A1 ^ enc_B1 ^ enc_old_B1;
  ena_dir1 = enc_A1 ^ enc_old_B1;
  ena_ena2 = enc_A2 ^ enc_old_A2 ^ enc_B2 ^ enc_old_B2;
  ena_dir2 = enc_A2 ^ enc_old_B2;
  ena_ena3 = enc_A3 ^ enc_old_A3 ^ enc_B3 ^ enc_old_B3;
  ena_dir3 = enc_A3 ^ enc_old_B3;
  ena_ena4 = enc_A4 ^ enc_old_A4 ^ enc_B4 ^ enc_old_B4;
  ena_dir4 = enc_A4 ^ enc_old_B4;
  // pos direction
  if (ena_ena0 & ena_dir0) counter0++;
  if (ena_ena1 & ena_dir1) counter1++;
  if (ena_ena2 & ena_dir2) counter2++;
  if (ena_ena3 & ena_dir3) counter3++;
  if (ena_ena4 & ena_dir4) counter4++;
  // negatief direction
  if (ena_ena0 & !ena_dir0) counter0--;
  if (ena_ena1 & !ena_dir1) counter1--;
  if (ena_ena2 & !ena_dir2) counter2--;
  if (ena_ena3 & !ena_dir3) counter3--;
  if (ena_ena4 & !ena_dir4) counter4--;
  PIOC -> PIO_CODR = PIO_CODR_P1 ;      // set pin D33 low scope trigger pulse.
}
TC2->TC_CHANNEL[2].TC_SR;               // Read and clear status register
}

And the Giga program:

/* 
Multiple Quadrature decoders + LCD display 4x20 char   
fotoopa 2023
https://www.flickr.com/photos/fotoopa_hs/

The Giga takes about  416 ns to process all 5 decoders in the interrupt routine
The Due  takes about 4691 ns to process all 5 decoders in the interrupt routine
The Giga is about 12x faster than the Due.
Interrupt every 132 us via pin 2, signal given by the Arduino Due.
*/

#include "HD44780_LCD_PCF8574.h"
HD44780LCD myLCD( 4, 20, 0x27); // I2C LCD adr $27 4x20 char

static int counter0 = 200;
static int counter1 = 300;
static int counter2 = 400;
static int counter3 = 500;
static int counter4 = 600;
static uint16_t enc_inputs = 0;

bool ena_ena0;
bool ena_dir0;
bool ena_ena1;
bool ena_dir1;
bool ena_ena2;
bool ena_dir2;
bool ena_ena3;
bool ena_dir3;
bool ena_ena4;
bool ena_dir4;
bool ena_ena5;
bool ena_dir5;
bool enc_A0;
bool enc_B0;
bool enc_A1;
bool enc_B1;
bool enc_A2;
bool enc_B2;
bool enc_A3;
bool enc_B3;
bool enc_A4;
bool enc_B4;
bool enc_A5;
bool enc_B5;
bool enc_old_A0;
bool enc_old_B0;
bool enc_old_A1;
bool enc_old_B1;
bool enc_old_A2;
bool enc_old_B2;
bool enc_old_A3;
bool enc_old_B3;
bool enc_old_A4;
bool enc_old_B4;
/*
GPIOJ  0  D25           enc_A0 = enc_inputs & 0x01;
GPIOJ  1  D27           enc_B0 = enc_inputs & 0x02;
GPIOJ  2  D29           enc_A1 = enc_inputs & 0x04;
GPIOJ  3  D31           enc_B1 = enc_inputs & 0x08;
GPIOJ  4  D33           enc_A2 = enc_inputs & 0x10;
GPIOJ  5  D35           enc_B2 = enc_inputs & 0x20;
GPIOJ  6  D37           enc_A3 = enc_inputs & 0x40;
GPIOJ  7  D38           enc_B3 = enc_inputs & 0x80;
GPIOJ  8  D4            ----
GPIOJ  9  D57	XCLK Cam  ----
GPIOJ 10  D11	SPI5_COPI ----
GPIOJ 11  D12	SPI5_CIPO ----
GPIOJ 12  D22           Scope trigger ouput
GPIOJ 13  D87	Green led ----
GPIOJ 14  D26           enc_A4 = enc_inputs & 0x4000;
GPIOJ 15  D28           enc_B4 = enc_inputs & 0x8000;
*/
#define PIN_int_decoder 2    //interrupt request pin 2

void setup() {
  Serial.begin(115200);
  pinMode(D22, OUTPUT);    // scope trigger signal
  digitalWrite(D22, 0);    // trigger scope low
  pinMode(D25, INPUT);     // enc_A0
  pinMode(D27, INPUT);     // enc_B0
  pinMode(D29, INPUT);     // enc_A1
  pinMode(D31, INPUT);     // enc_B1
  pinMode(D33, INPUT);     // enc_A2
  pinMode(D35, INPUT);     // enc_B2
  pinMode(D37, INPUT);     // enc_A3
  pinMode(D38, INPUT);     // enc_B3
  pinMode(D26, INPUT);     // enc_A4
  pinMode(D28, INPUT);     // enc_B3
  // set pullup registers  GPIOJ -> PUPDR = 1<<30 | 1<<28 | 1<<14 | 1<<12 | 1<<10 | 1<<8 || 1<<6  | 1<<4  | 1<<2  | 1<<0 ;
  GPIOJ -> PUPDR =  GPIO_PUPDR_PUPD15_0 |
                    GPIO_PUPDR_PUPD14_0 |
                    GPIO_PUPDR_PUPD7_0 |
                    GPIO_PUPDR_PUPD6_0 |
                    GPIO_PUPDR_PUPD5_0 |
                    GPIO_PUPDR_PUPD4_0 |
                    GPIO_PUPDR_PUPD3_0 |
                    GPIO_PUPDR_PUPD2_0 |
                    GPIO_PUPDR_PUPD1_0 |
                    GPIO_PUPDR_PUPD0_0 ; // set pullup register for all decoder lines.
  pinMode(PIN_int_decoder, INPUT);
  attachInterrupt(digitalPinToInterrupt(PIN_int_decoder), Int_Decoder_Handler, RISING);
  delay(5);
  myLCD.PCF8574_LCDInit(LCDCursorTypeOff);
  myLCD.PCF8574_LCDClearScreen();
  myLCD.PCF8574_LCDBackLightSet(true);
}

void loop() {
  char titel[] = " Quadrature Decoders";
  myLCD.PCF8574_LCDGOTO(LCDLineNumberOne, 0);
  myLCD.PCF8574_LCDSendString(titel);
  while (true) {
    delay(100);     // Update LCD counters every 100 ms
    update_lcd();
  }
}

void update_lcd()   // Update the 5 counters on the LCD
{
  	myLCD.PCF8574_LCDGOTO(LCDLineNumberTwo, 9);
	  myLCD.print(counter0);
    myLCD.PCF8574_LCDSendChar(' ');
	  myLCD.PCF8574_LCDGOTO(LCDLineNumberThree, 4);
	  myLCD.print(counter1);
    myLCD.PCF8574_LCDSendChar(' ');
	  myLCD.PCF8574_LCDGOTO(LCDLineNumberFour, 4);
	  myLCD.print(counter2);
    myLCD.PCF8574_LCDSendChar(' ');
	  myLCD.PCF8574_LCDGOTO(LCDLineNumberThree, 14);
	  myLCD.print(counter3);
    myLCD.PCF8574_LCDSendChar(' ');
	  myLCD.PCF8574_LCDGOTO(LCDLineNumberFour, 14);
	  myLCD.print(counter4);
    myLCD.PCF8574_LCDSendChar(' ');
}

void Int_Decoder_Handler() {    
  // set pin D22 high. Processing time interrupt routine 461 ns
  // Interrupt every 132 us via pin 2, signal given by the Arduino Due.
  GPIOJ -> BSRR = GPIO_BSRR_BS12_Msk;     
  // copy encoders old
  enc_old_A0 = enc_A0;
  enc_old_B0 = enc_B0;
  enc_old_A1 = enc_A1;
  enc_old_B1 = enc_B1;
  enc_old_A2 = enc_A2;
  enc_old_B2 = enc_B2;
  enc_old_A3 = enc_A3;
  enc_old_B3 = enc_B3;
  enc_old_A4 = enc_A4;
  enc_old_B4 = enc_B4;
  // read all pins on input data register port GPIOJ
  enc_inputs  = (GPIOJ->IDR); 
  // copy encoders    
  enc_A0 = enc_inputs & 0x01;
  enc_B0 = enc_inputs & 0x02;
  enc_A1 = enc_inputs & 0x04;
  enc_B1 = enc_inputs & 0x08;
  enc_A2 = enc_inputs & 0x10;
  enc_B2 = enc_inputs & 0x20;
  enc_A3 = enc_inputs & 0x40;
  enc_B3 = enc_inputs & 0x80;
  enc_A4 = enc_inputs & 0x4000;
  enc_B4 = enc_inputs & 0x8000;
  // ena dir
  ena_ena0 = enc_A0 ^ enc_old_A0 ^ enc_B0 ^ enc_old_B0;
  ena_dir0 = enc_A0 ^ enc_old_B0;
  ena_ena1 = enc_A1 ^ enc_old_A1 ^ enc_B1 ^ enc_old_B1;
  ena_dir1 = enc_A1 ^ enc_old_B1;
  ena_ena2 = enc_A2 ^ enc_old_A2 ^ enc_B2 ^ enc_old_B2;
  ena_dir2 = enc_A2 ^ enc_old_B2;
  ena_ena3 = enc_A3 ^ enc_old_A3 ^ enc_B3 ^ enc_old_B3;
  ena_dir3 = enc_A3 ^ enc_old_B3;
  ena_ena4 = enc_A4 ^ enc_old_A4 ^ enc_B4 ^ enc_old_B4;
  ena_dir4 = enc_A4 ^ enc_old_B4;
  // pos direction
  if (ena_ena0 & ena_dir0) counter0++;
  if (ena_ena1 & ena_dir1) counter1++;
  if (ena_ena2 & ena_dir2) counter2++;
  if (ena_ena3 & ena_dir3) counter3++;
  if (ena_ena4 & ena_dir4) counter4++;
  // negatief direction
  if (ena_ena0 & !ena_dir0) counter0--;
  if (ena_ena1 & !ena_dir1) counter1--;
  if (ena_ena2 & !ena_dir2) counter2--;
  if (ena_ena3 & !ena_dir3) counter3--;
  if (ena_ena4 & !ena_dir4) counter4--;
  GPIOJ -> BSRR = GPIO_BSRR_BR12_Msk;     // set pin D22 low
  }

Now I have a problem using a timer interrupt at the Giga. I cannot find a solution to trigger a timer like with the Due. There are also no examples to be found after many searches. Probably there are no sources for this yet. I would be greatly helped if someone could help me for this to trigger an interrupt from a timer.
Now I do it via:

attachInterrupt(digitalPinToInterrupt(PIN_int_decoder), Int_Decoder_Handler, RISING);

I post all my photos of setups and measurements on Flickr, most in UHD resolution. You can find the link in my program at the top.
I have now made the tests for 5 decoders but more decoders would not give much higher load. I did choose the inputs so that they are on 1 port. This makes the processing faster.

Frans.

How is that physically possible?
Link to the encoder data sheet?

I think it doesn't mean that one phase has higher frequency than the other, but that the jitter means that some intervals might be shorter than others--You can't budget for a constant interval between edges, and have to budget for the smallest intervals.

@ DrDiettrich

This statement was indeed incorrect. This 40/60 ratio is only something manufacturers of encoders specifies. This sentence I had to leave out. In a quadrature encoder, the polarity of 1 channel is allowed to change while the other channel must remain stable. Everything works on levels so the direction is also calculated.
Thanks to @DaveX that is better explained.

Now, of course, my essential problem remains that I cannot yet use a timer to generate interrupts at a fixed time. I did have something like that with the Due:

void tc_setup() {
  PMC->PMC_PCER1 |= PMC_PCER1_PID35;                        // TC8 power ON : Timer Counter 2 channel 2 IS TC8 - see page 38
  TC2->TC_CHANNEL[2].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK2    // MCK/8 = 82/8 --> 10.5 M Hz, clk on rising edge
                              | TC_CMR_WAVE                 // Waveform mode
                              | TC_CMR_WAVSEL_UP_RC;        // UP mode with automatic trigger on RC Compare
  TC2->TC_CHANNEL[2].TC_RC = 42;                            // 4 usec/ timerloop
  TC2->TC_CHANNEL[2].TC_IER = TC_IER_CPCS;                  // Interrupt on RC compare match
  TC2->TC_CHANNEL[2].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN;  // Software trigger TC2 counter and enable
  NVIC_EnableIRQ(TC8_IRQn);
}

I still can't find examples of this for the Giga. The pdf file of the stm32h74775 (3550 pages) does explain a lot but it looks so complex.... Still, I will have to look further there.

You are generating too many interrupts if you want to be sure not to miss any state change. If PCINT are available with your controllers then one interrupt can handle up to 4 encoders with no interrupts missed and no excess interrupts triggered.

You are generating too many interrupts if you want to be sure not to miss any state change

I use only 1 interrupt to process all the decoders. You know in advance how many interrupts you have to process and how much time the processing will take. If you see that for 5 decoders only 420ns processing time is needed and you repeat that every 132 us then this is only 0.42/132= 3.18% cpu time. The rest is left for other tasks.
If you work with edge detection as an interrupt then you never know in advance what the load will be. Suppose an encoder stands still but vibrates by external mechanical influence then you can generate interrups at a high rate on each encoder. You cannot know the load in advance. It is a different way of working but it works perfectly. I can immediately add 5 more decoders without any problem.

Which frequency do you expect from mechanical vibrations?

That depends a lot on how the motor is controlled and how the encoders are made. That is precisely what is difficult to predict. If you then have multiple encoders they are all going to generate extra interrupts. If I use a fixed sampling period it is determined. Already in 1980 we used this method but we still had a small hardware counter of 8 bit because processing interrupts were much slower then. That was with the MC6809.

Now I understand why you assumed

Nowadays I'd assume that the hardware fits all needs for exact counting, no need for error prone polling.
But of course it's your project, your decision...

BTW at the 6809 time there existed TTL up/down counters, perfectly suited for lossless counting of quadrature encoder signals.

How many encoders can be done with the GIGA's hardware quadrature handling?

https://www.st.com/content/ccc/resource/technical/document/application_note/54/0f/67/eb/47/34/45/40/DM00042534.pdf/files/DM00042534.pdf/jcr:content/translations/en.DM00042534.pdf

From the datasheet at https://www.st.com/en/microcontrollers-microprocessors/stm32h747xi.html looks like 4 at up to 240MHz:

@DaveX
Thanks for the links! That's certainly good info to read.

My problem is actually that I am not familiar with C++, I have been working with FPGA boards for years and that is written in verilog. I bought these Arduino boards to get some experience with C++.
My primary concern is to be able to use an simple timer interrupt routine. Those quadrature decoders would be a good example. I don't really need it because on my FPGA boards I have up to 12 motors connected with decoders, min, max and home switch. Inputs and outputs are widely available (120 digital input and output pins via 5 SPI boxes at 2Mhz).
My "DE0-Nano-Soc" board cost only slightly more than a Giga board.

DSC05922 by Frans, on Flickr

I read the data sheets as much as possible, I am turning 80 years old, and new info becomes difficult. In any case, I will go through your links carefully.

1 Like

I don't know much about them, but was curious about the hardware/software contrast.

If one ran up against a timing constraint in software, one might be able to shift some high-speed motors (spindles?) to hardware quadrature decoders, and not compromise for the rest.

Meanwhile, I made a test setup for 9 quadrature decoders. I use an Arduino Due as an encoder generator and the Arduino Giga as quadrature decoder. Since I still cannot write a timer interrupt routine for the Giga, I have to create an external trigger signal for this. This signal also comes from the Due. A photo of the entire setup can be seen here:

Giga-Due_quadrature-20230506 by Frans, on Flickr

As you can see a lot of wires! To make measurements of all signals, I connected a 34 channel logic analyzer. Which operates at 200 Mhz but unfortunately has only a small buffer memory. Given the many wires, I made some adapter connectors where 2 connections can be made for each signal. You can see the capture of the logic analyzer here:

Giga_9x_decoder_0506 by Frans, on Flickr

The photo is a UHD version, click goes to see the original on my Flickr account. An interrupt signal is sent from the Dua to the Giga every 128 us. I extended that pulse slightly so that it is 96 ns wide. But it can also be done with 24 ns if desired. The quadrature signals are created with digitalWrite commands on the Due. Because of this, there is a small shift of 2.11 us per quadrature group. With the Giga, I am also going to pass all the counters serially. I am using TX1, TX2 and TX3 for this purpose. Each TX line sends 3 counters so that all 9 decoder results are visible on the analyzer. I do this at 2Mbd speed. You can go up to 4Mbd which makes counter transfer even faster. There is plenty of time between each quadrature change. So you can see any change in the counter, even at this high speed. Every 174 us, the quadrature signals are changed. This corresponds to 5750 pulses per sec. If you have an encoder of 100 pulses per revolution then it may run (5750/100)*60 = 3450 tr/min. I also created a measurement signal in the Giga that records the time to fully process all 9 quadrature signals including calculating the new position. That time is 795 ns. You can also see on the timing that I need very few interrupts for all 9 quadrature decoding. If you used edge detection that would be 4x9= 36 interrupts per full quadrature. I have a maximum of 6. You can see that this system consumes less interrupts! I can also measure the time between receiving the interrupt signal from the Due and starting processing from the Giga. This time is 510 ns.

Attached is the full program of the Due as a quadrature generator:

/* 
9x Quadrature encoders + LCD display 4x20 char   
fotoopa 2023
Update: 20230504 D2 interrupt puls 96 ns
Update: 20230506 D7 scope puls/encoder step

https://www.flickr.com/photos/fotoopa_hs/

*/

int quad_counter = 1000;  // 1000 -> 4000 encoder pulses
int sample_time = 150;    // --> 174 us encoder pulse time or 5750 pulses/secon all 9 quadrature encoders.

void setup() {
  Serial.begin(115200);
  pinMode(2, OUTPUT);    // D2 int puls to Giga --> D2
  pinMode(7, OUTPUT);    // D7 scope pin 1 puls/encoder step

  pinMode(25, OUTPUT);   // PIOD b0 D25 enc_A0  --> D25
  pinMode(26, OUTPUT);   // PIOD b1 D26 enc_B0  --> D27
  pinMode(27, OUTPUT);   // PIOD b2 D27 enc_A1  --> D29
  pinMode(28, OUTPUT);   // PIOD b3 D28 enc_B1  --> D31
  pinMode(14, OUTPUT);   // PIOD b4 D14 enc_A2  --> D33
  pinMode(15, OUTPUT);   // PIOD b5 D15 enc_B2  --> D35
  pinMode(29, OUTPUT);   // PIOD b6 D29 enc_A3  --> D37
  pinMode(11, OUTPUT);   // PIOD b7 D11 enc_B3  --> D38
  pinMode(12, OUTPUT);   // PIOD b8 D12 enc_A4  --> D26
  pinMode(30, OUTPUT);   // PIOD b9 D30 enc_B4  --> D28

  pinMode(33, OUTPUT);   // PIOC b0 D33 enc_A5  --> D48
  pinMode(34, OUTPUT);   // PIOC b1 D34 enc_B5  --> D10
  pinMode(35, OUTPUT);   // PIOC b2 D35 enc_A6  --> D52
  pinMode(36, OUTPUT);   // PIOC b3 D36 enc_B6  --> D30
  pinMode(37, OUTPUT);   // PIOC b4 D37 enc_A7  --> D32
  pinMode(38, OUTPUT);   // PIOC b5 D38 enc_B7  --> D34
  pinMode(39, OUTPUT);   // PIOC b6 D39 enc_A8  --> D36
  pinMode(40, OUTPUT);   // PIOC b7 D40 enc_B8  --> D41

  digitalWrite(25, 0);
  digitalWrite(26, 0);
  digitalWrite(27, 0);
  digitalWrite(28, 0);
  digitalWrite(14, 0);
  digitalWrite(15, 0);
  digitalWrite(29, 0);
  digitalWrite(11, 0);
  digitalWrite(12, 0);
  digitalWrite(30, 0);

  digitalWrite(33, 0);
  digitalWrite(34, 0);
  digitalWrite(35, 0);
  digitalWrite(36, 0);
  digitalWrite(37, 0);
  digitalWrite(38, 0);
  digitalWrite(39, 0);
  digitalWrite(40, 0);

  digitalWrite(7, 0);   // default scope puls low
  tc_setup();
  delay(5);
}

void loop() {
  while (true) {
    for(int i=1; i<= quad_counter; i++){
      pos_dir_encoders();
    }
    delay(100);
    for(int i=1; i<= quad_counter; i++){
      neg_dir_encoders();
    }
    delay(100);
  }
}



void pos_dir_encoders()
{
  digitalWrite(25, 1);
  digitalWrite(27, 1);
  digitalWrite(14, 1);
  digitalWrite(29, 1);
  digitalWrite(12, 1);
  digitalWrite(33, 1);
  digitalWrite(35, 1);
  digitalWrite(37, 1);
  digitalWrite(39, 1);
  digitalWrite(7, 1);   // scope puls high
  digitalWrite(7, 0);   // scope puls low
delayMicroseconds(sample_time);

  digitalWrite(26, 1);
  digitalWrite(28, 1);
  digitalWrite(15, 1);
  digitalWrite(11, 1);
  digitalWrite(30, 1);
  digitalWrite(34, 1);
  digitalWrite(36, 1);
  digitalWrite(38, 1);
  digitalWrite(40, 1);
  digitalWrite(7, 1);   // scope puls high
  digitalWrite(7, 0);   // scope puls low
delayMicroseconds(sample_time);

  digitalWrite(25, 0);
  digitalWrite(27, 0);
  digitalWrite(14, 0);
  digitalWrite(29, 0);
  digitalWrite(12, 0);
  digitalWrite(33, 0);
  digitalWrite(35, 0);
  digitalWrite(37, 0);
  digitalWrite(39, 0);
  digitalWrite(7, 1);   // scope puls high
  digitalWrite(7, 0);   // scope puls low
delayMicroseconds(sample_time);

  digitalWrite(26, 0);
  digitalWrite(28, 0);
  digitalWrite(15, 0);
  digitalWrite(11, 0);
  digitalWrite(30, 0);
  digitalWrite(34, 0);
  digitalWrite(36, 0);
  digitalWrite(38, 0);
  digitalWrite(40, 0);
  digitalWrite(7, 1);   // scope puls high
  digitalWrite(7, 0);   // scope puls low
delayMicroseconds(sample_time);
}
void neg_dir_encoders()
{
  digitalWrite(26, 1);
  digitalWrite(28, 1);
  digitalWrite(15, 1);
  digitalWrite(11, 1);
  digitalWrite(30, 1);
  digitalWrite(34, 1);
  digitalWrite(36, 1);
  digitalWrite(38, 1);
  digitalWrite(40, 1);
  digitalWrite(7, 1);   // scope puls high
  digitalWrite(7, 0);   // scope puls low
delayMicroseconds(sample_time);

 digitalWrite(25, 1);
  digitalWrite(27, 1);
  digitalWrite(14, 1);
  digitalWrite(29, 1);
  digitalWrite(12, 1);
  digitalWrite(33, 1);
  digitalWrite(35, 1);
  digitalWrite(37, 1);
  digitalWrite(39, 1);
  digitalWrite(7, 1);   // scope puls high
  digitalWrite(7, 0);   // scope puls low
delayMicroseconds(sample_time);

  digitalWrite(26, 0);
  digitalWrite(28, 0);
  digitalWrite(15, 0);
  digitalWrite(11, 0);
  digitalWrite(30, 0);
  digitalWrite(34, 0);
  digitalWrite(36, 0);
  digitalWrite(38, 0);
  digitalWrite(40, 0);
  digitalWrite(7, 1);   // scope puls high
  digitalWrite(7, 0);   // scope puls low
delayMicroseconds(sample_time);

  digitalWrite(25, 0);
  digitalWrite(27, 0);
  digitalWrite(14, 0);
  digitalWrite(29, 0);
  digitalWrite(12, 0);
  digitalWrite(33, 0);
  digitalWrite(35, 0);
  digitalWrite(37, 0);
  digitalWrite(39, 0);
  digitalWrite(7, 1);   // scope puls high
  digitalWrite(7, 0);   // scope puls low
delayMicroseconds(sample_time);
}

void tc_setup() {
  PMC->PMC_PCER1 |= PMC_PCER1_PID35;                        // TC8 power ON : Timer Counter 2 channel 2 IS TC8 - see page 38
  TC2->TC_CHANNEL[2].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK3    // MCK/32 = 82/32   2,5625 us clk on rising edge
                              | TC_CMR_WAVE                 // Waveform mode
                              | TC_CMR_WAVSEL_UP_RC;        // UP mode with automatic trigger on RC Compare
  TC2->TC_CHANNEL[2].TC_RC = 336;                           // 128 usec/ timerloop
  TC2->TC_CHANNEL[2].TC_IER = TC_IER_CPCS;                  // Interrupt on RC compare match
  TC2->TC_CHANNEL[2].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN;  // Software trigger TC2 counter and enable
  NVIC_EnableIRQ(TC8_IRQn);
}
static uint32_t Count;
void TC8_Handler() {
  PIOB -> PIO_SODR =  PIO_SODR_P25 ;    // pin D2 interrupt pin high for Giga board
  PIOB -> PIO_SODR =  PIO_SODR_P25 ;    //
  PIOB -> PIO_SODR =  PIO_SODR_P25 ;    //
  PIOB -> PIO_SODR =  PIO_SODR_P25 ;    // repeat for  pulse 4x24 ns = 96 ns
  PIOB -> PIO_CODR =  PIO_SODR_P25 ;    // pin D2 interrupt pin low for Giga board
  TC2->TC_CHANNEL[2].TC_SR;             // Read and clear status register
}

And for the Giga:

/* 
Multiple Quadrature decoders  
fotoopa 2023
Update 20230504
Update 20230506 sync readout all encoder positions with enc_update signal pin 7

https://www.flickr.com/photos/fotoopa_hs/

The Giga takes about  795 ns to process all 9 decoders in the interrupt routine
Interrupt every 128 us via pin 2, signal given by the Arduino Due.
*/

static int32_t counter0 = 11100; // powerup values
static int32_t counter1 = 11200;
static int32_t counter2 = 11300;
static int32_t counter3 = 11400;
static int32_t counter4 = 11500;
static int32_t counter5 = 11600;
static int32_t counter6 = 11700;
static int32_t counter7 = 11800;
static int32_t counter8 = 11900;

static int32_t RX_counter0 = 11100; // powerup values
static int32_t RX_counter1 = 11200;
static int32_t RX_counter2 = 11300;
static int32_t RX_counter3 = 11400;
static int32_t RX_counter4 = 11500;
static int32_t RX_counter5 = 11600;
static int32_t RX_counter6 = 11700;
static int32_t RX_counter7 = 11800;
static int32_t RX_counter8 = 11900;

static uint16_t enc_inputs  = 0;
static uint16_t enc_inputs1 = 0;

bool ena_ena0;
bool ena_dir0;
bool ena_ena1;
bool ena_dir1;
bool ena_ena2;
bool ena_dir2;
bool ena_ena3;
bool ena_dir3;
bool ena_ena4;
bool ena_dir4;
bool ena_ena5;
bool ena_dir5;
bool ena_ena6;
bool ena_dir6;
bool ena_ena7;
bool ena_dir7;
bool ena_ena8;
bool ena_dir8;

bool enc_A0;
bool enc_B0;
bool enc_A1;
bool enc_B1;
bool enc_A2;
bool enc_B2;
bool enc_A3;
bool enc_B3;
bool enc_A4;
bool enc_B4;
bool enc_A5;
bool enc_B5;
bool enc_A6;
bool enc_B6;
bool enc_A7;
bool enc_B7;
bool enc_A8;
bool enc_B8;

bool enc_old_A0;
bool enc_old_B0;
bool enc_old_A1;
bool enc_old_B1;
bool enc_old_A2;
bool enc_old_B2;
bool enc_old_A3;
bool enc_old_B3;
bool enc_old_A4;
bool enc_old_B4;
bool enc_old_A5;
bool enc_old_B5;
bool enc_old_A6;
bool enc_old_B6;
bool enc_old_A7;
bool enc_old_B7;
bool enc_old_A8;
bool enc_old_B8;
/*
*******************************************************************
Reg   bit Giga  Giga  connector   function    Due connector   
-----     ------  inputs -------              ----- outputs  ------
GPIOJ  0  D25   D25               enc_A0      D25          D25
GPIOJ  1  D27   D27               enc_B0      D26   D26
GPIOJ  2  D29   D29               enc_A1      D27          D27
GPIOJ  3  D31   D31               enc_B1      D28   D28
GPIOJ  4  D33   D33               enc_A2      D14               D14       
GPIOJ  5  D35   D35               enc_B2      D15               D15
GPIOJ  6  D37   D37               enc_A3      D29          D29
GPIOJ  7  D38         D38         enc_B3      D11               D11
GPIOJ 14  D26         D26         enc_A4      D12               D12
GPIOJ 15  D28         D28         enc_B4      D30   D30
-                   --D24--                              --D31--
-                                                 --D32--
-             --D39--
-                   --D40--
-                   --D42--
-                   --D44--
-                   --D46-- 
-                   --D50--
GPIOK  0  D48         D48         enc_A5      D33          D33
GPIOK  1  D10               D10   enc_B5      D34   D34
GPIOK  2  D52         D52         enc_A6      D35          D35
GPIOK  3  D30         D30         enc_B6      D36   D36
GPIOK  4  D32         D32         enc_A7      D37          D37
GPIOK  5  D34         D34         enc_B7      D38   D38
GPIOK  6  D36         D36         enc_A8      D39          D39
GPIOK  7  D41   D41               enc_B8      D40   D40

GPIOJ 12  D22         D22         trigger ouput 9 decoders

**********************************************************************

Due D2 -> Giga int    D2
serial2   TXT1        D18   serial counter 0..2 outputs
serial3   TXT2        D16   serial counter 3..5 outputs
serial4   TXT3        D14   serial counter 6..9 outputs

*/
#define PIN_int_decoder 2    //interrupt request pin 2

void setup() {
  Serial2.begin(4000000);  // TX1 pin 18
  Serial3.begin(4000000);  // TX2 pin 16
  Serial4.begin(4000000);  // TX3 pin 14
  pinMode(D22, OUTPUT);    // scope trigger signal
  digitalWrite(D22, 0);    // trigger scope low
  pinMode(D7, INPUT);      // enc_update

  pinMode(D25, INPUT);     // enc_A0  counter0
  pinMode(D27, INPUT);     // enc_B0
  pinMode(D29, INPUT);     // enc_A1  counter1
  pinMode(D31, INPUT);     // enc_B1
  pinMode(D33, INPUT);     // enc_A2  counter2
  pinMode(D35, INPUT);     // enc_B2
  pinMode(D37, INPUT);     // enc_A3  counter3
  pinMode(D38, INPUT);     // enc_B3
  pinMode(D26, INPUT);     // enc_A4  counter4
  pinMode(D28, INPUT);     // enc_B4

  pinMode(D48, INPUT);     // enc_A5  counter5
  pinMode(D10, INPUT);     // enc_B5
  pinMode(D52, INPUT);     // enc_A6  counter6
  pinMode(D30, INPUT);     // enc_B6
  pinMode(D32, INPUT);     // enc_A7  counter7
  pinMode(D34, INPUT);     // enc_B7
  pinMode(D36, INPUT);     // enc_A8  counter8
  pinMode(D41, INPUT);     // enc_B8

  GPIOJ -> PUPDR =  GPIO_PUPDR_PUPD15_0 |   // set pullup registers  GPIOJ -> PUPDR = 1<<30 | 1<<28 | 1<<14 | 1<<12 | 1<<10 | 1<<8 || 1<<6  | 1<<4  | 1<<2  | 1<<0
                    GPIO_PUPDR_PUPD14_0 |
                    GPIO_PUPDR_PUPD7_0 |
                    GPIO_PUPDR_PUPD6_0 |
                    GPIO_PUPDR_PUPD5_0 |
                    GPIO_PUPDR_PUPD4_0 |
                    GPIO_PUPDR_PUPD3_0 |
                    GPIO_PUPDR_PUPD2_0 |
                    GPIO_PUPDR_PUPD1_0 |
                    GPIO_PUPDR_PUPD0_0 ; // set pullup register for all decoder lines.
  pinMode(PIN_int_decoder, INPUT);
  attachInterrupt(digitalPinToInterrupt(PIN_int_decoder), Int_Decoder_Handler, RISING);
  delay(5);
}

void loop() {

  while (digitalRead(7)) {  // sync to encoder signal change
    RX_counter0 = counter0; // copy all counters for serial print out
    RX_counter1 = counter1;
    RX_counter2 = counter2;
    RX_counter3 = counter3;
    RX_counter4 = counter4;
    RX_counter5 = counter5;
    RX_counter6 = counter6;
    RX_counter7 = counter7;
    RX_counter8 = counter8;

    Serial2.print(RX_counter0);  // TX1 pin 18
    Serial3.print(RX_counter3);  // TX2 pin 16
    Serial4.print(RX_counter6);  // TX3 pin 14
    Serial2.print(RX_counter1);  // TX1 pin 18
    Serial3.print(RX_counter4);  // TX2 pin 16
    Serial4.print(RX_counter7);  // TX3 pin 14
    Serial2.print(RX_counter2);  // TX1 pin 18
    Serial3.print(RX_counter5);  // TX2 pin 16
    Serial4.print(RX_counter8);  // TX3 pin 14
  }
}

void Int_Decoder_Handler() {    
  // set pin D22 high. Processing time interrupt routine 461 ns
  // Interrupt every 128 us via pin 2, signal given by the Arduino Due.
  GPIOJ -> BSRR = GPIO_BSRR_BS12_Msk;     // set pin D22 high for scope

  // copy encoders old
  enc_old_A0 = enc_A0;
  enc_old_B0 = enc_B0;
  enc_old_A1 = enc_A1;
  enc_old_B1 = enc_B1;
  enc_old_A2 = enc_A2;
  enc_old_B2 = enc_B2;
  enc_old_A3 = enc_A3;
  enc_old_B3 = enc_B3;
  enc_old_A4 = enc_A4;
  enc_old_B4 = enc_B4;
  enc_old_A5 = enc_A5;
  enc_old_B5 = enc_B5;
  enc_old_A6 = enc_A6;
  enc_old_B6 = enc_B6;
  enc_old_A7 = enc_A7;
  enc_old_B7 = enc_B7;
  enc_old_A8 = enc_A8;
  enc_old_B8 = enc_B8;
  
  enc_inputs   = (GPIOJ->IDR); // read all pins on input data register port GPIOJ
  enc_inputs1  = (GPIOK->IDR); // read all pins on input data register port GPIOK

  // copy encoders    
  enc_A0 = enc_inputs & 0x01;   // GPIOJ inputs
  enc_B0 = enc_inputs & 0x02;
  enc_A1 = enc_inputs & 0x04;
  enc_B1 = enc_inputs & 0x08;
  enc_A2 = enc_inputs & 0x10;
  enc_B2 = enc_inputs & 0x20;
  enc_A3 = enc_inputs & 0x40;
  enc_B3 = enc_inputs & 0x80;
  enc_A4 = enc_inputs & 0x4000;
  enc_B4 = enc_inputs & 0x8000;
  enc_A5 = enc_inputs1 & 0x01;   // GPIOK inputs
  enc_B5 = enc_inputs1 & 0x02;
  enc_A6 = enc_inputs1 & 0x04;
  enc_B6 = enc_inputs1 & 0x08;
  enc_A7 = enc_inputs1 & 0x10;
  enc_B7 = enc_inputs1 & 0x20;
  enc_A8 = enc_inputs1 & 0x40;
  enc_B8 = enc_inputs1 & 0x80;

  // ena dir
  ena_ena0 = enc_A0 ^ enc_old_A0 ^ enc_B0 ^ enc_old_B0;
  ena_dir0 = enc_A0 ^ enc_old_B0;
  ena_ena1 = enc_A1 ^ enc_old_A1 ^ enc_B1 ^ enc_old_B1;
  ena_dir1 = enc_A1 ^ enc_old_B1;
  ena_ena2 = enc_A2 ^ enc_old_A2 ^ enc_B2 ^ enc_old_B2;
  ena_dir2 = enc_A2 ^ enc_old_B2;
  ena_ena3 = enc_A3 ^ enc_old_A3 ^ enc_B3 ^ enc_old_B3;
  ena_dir3 = enc_A3 ^ enc_old_B3;
  ena_ena4 = enc_A4 ^ enc_old_A4 ^ enc_B4 ^ enc_old_B4;
  ena_dir4 = enc_A4 ^ enc_old_B4;
  ena_ena5 = enc_A5 ^ enc_old_A5 ^ enc_B5 ^ enc_old_B5;
  ena_dir5 = enc_A5 ^ enc_old_B5;
  ena_ena6 = enc_A6 ^ enc_old_A6 ^ enc_B6 ^ enc_old_B6;
  ena_dir6 = enc_A6 ^ enc_old_B6;
  ena_ena7 = enc_A7 ^ enc_old_A7 ^ enc_B7 ^ enc_old_B7;
  ena_dir7 = enc_A7 ^ enc_old_B7;
  ena_ena8 = enc_A8 ^ enc_old_A8 ^ enc_B8 ^ enc_old_B8;
  ena_dir8 = enc_A8 ^ enc_old_B8;

  // pos direction
  if (ena_ena0 & ena_dir0) counter0++;
  if (ena_ena1 & ena_dir1) counter1++;
  if (ena_ena2 & ena_dir2) counter2++;
  if (ena_ena3 & ena_dir3) counter3++;
  if (ena_ena4 & ena_dir4) counter4++;
  if (ena_ena5 & ena_dir5) counter5++;
  if (ena_ena6 & ena_dir6) counter6++;
  if (ena_ena7 & ena_dir7) counter7++;
  if (ena_ena8 & ena_dir8) counter8++;

  // negatief direction
  if (ena_ena0 & !ena_dir0) counter0--;
  if (ena_ena1 & !ena_dir1) counter1--;
  if (ena_ena2 & !ena_dir2) counter2--;
  if (ena_ena3 & !ena_dir3) counter3--;
  if (ena_ena4 & !ena_dir4) counter4--;
  if (ena_ena5 & !ena_dir5) counter5--;
  if (ena_ena6 & !ena_dir6) counter6--;
  if (ena_ena7 & !ena_dir7) counter7--;
  if (ena_ena8 & !ena_dir8) counter8--;

  GPIOJ -> BSRR = GPIO_BSRR_BR12_Msk;     // set pin D22 low for scope
  }

I am still looking for a solution to write a timer interrupt routine so that I no longer need the external trigger signal on the Giga.

Frans

I am still trying to write a timer interrupt routine for the Giga. I have searched a lot for solutions, including the Portenta H7.
But still without success. The data sheet of the STM32H747 is almost continuously open here. It is still searching through the many pages (the manual has more than 3500 pages!). I tried to initialize Timer2. That should call an interrupt routine every 128 us. The task is pretty simple but it still doesn't work. I use the logic analyzer to see the results.

Here I have the init routine and the interrupt handler routine I'm trying to get going. I seem to be doing things wrong, too little or not in the right order. I have been trying to change some things for days without success. Can anyone help me with this?

type or paste void timer2_setup() {
  TIM2->CR1 = TIM_CR1_URS                // Only counter overflow/underflow generates an update interrupt if enabled.
            | TIM_CR1_CKD_1              // 01: tDTS = 2 × tCK_INT
            | TIM_CR1_CEN;               // Counter enabled.

  TIM2->CR2 = TIM_CR2_TI1S;              // TI1 Selection
  TIM2->CR2 = TIM_CR2_MMS_0;             // MMS[2:0] bits (Master Mode Selection)
  TIM2->DIER =TIM_DIER_UIE               // Update interrupt enable
            | TIM_DIER_CC1IE             // Capture/Compare 1 interrupt enable
            | TIM_DIER_CC2IE             // Capture/Compare 2 interrupt enable
            | TIM_DIER_CC3IE             // Capture/Compare 3 interrupt enable
            | TIM_DIER_CC4IE             // Capture/Compare 4 interrupt enable
            | TIM_DIER_TIE;               // Trigger interrupt enable
  TIM2->CNT = 100;                        // TIM counter register
  TIM2->ARR = Auto_reload;                // Auto-reload timer2 value
  // TIM2->EGR = TIM_EGR_UG;              // sofware Trigger Generation
  TIM2->SR = TIM_SR_UIF                   // Update interrupt Flag
            |TIM_SR_TIF;                  // Trigger interrupt Flag// Update interrupt Flag
  attachInterrupt(TIM2_IRQn, Int_Decoder_Handler, RISING);
  //NVIC_ClearPendingIRQ(TIM2_IRQn);
  NVIC_EnableIRQ(TIM2_IRQn);
}

void Int_Decoder_Handler() {    
  GPIOJ -> BSRR = GPIO_BSRR_BS12;     // set pin D22 high for scope
  GPIOJ -> BSRR = GPIO_BSRR_BR12;     // set pin D22 low for scope
  TIM2->SR = TIM_SR_UIF;              // Update interrupt Flag
  }
code here

Placing the interrupt handler code in the mainloop does work, this proves that my logic analyzer does connect to the correct output pin.
How interrupts work I do understand, I worked with them for 30 years with the Motorola CPUs.

Frans.

I can not say for certain, but I believe timer 2 may be used by system, try using another timet.

Yes I have already tried several other timers. It is clearly the setup code that is not written correctly. I see that there are 26 x 32bit registers per timer that you can set; that remains a puzzle.

update:

@bexin
I suspect that timer2 would indeed have been used, I am now testing with others but unfortunately still without results.