Quadrature encoder handling by hardware

Hello all,
I'm trying to handle a quadrature encoder without the Encoder library and interrupts on the NANO R4.

GPT helped me a lot, and it seems it should be possible - but it's not working - the count stays at zero.

Looking at https://www.renesas.com/en/document/dst/ra4m1-group-datasheet page 23 and ArduinoCore-renesas/variants/NANOR4/variant.cpp at main · arduino/ArduinoCore-renesas · GitHub I compiled the following mappings:

// G0A   P107 D7
// G0B   P106 D6~
// G1A   P105 D2    P109 11~
// G1B   P104 D3~   P110 12
// G2A   P103 D4
// G2B   P102 D5~
// G3A   P111 D13
// G3B   P112 D10~
// G4A   P302 D1
// G4B   P301 D0
// G5A   P101 A4/SDA
// G5B   P100 A5/SCL
// G6A   P411 LED_BU
// G6B   P410 LED_GN
// G7A   P304 D8
// G7B   P303 D9~

Here is a minimal example. For testing, all events are placed in the count_up not to worry about proper "waveforms" when manually touching the pins. I tried GTIOC0 with pins D7 and D6 and GTIOC2 with pins D4 and D5.

#include "hal_data.h"

#include <FspTimer.h>

//

gpt_instance_ctrl_t gpt_ctrl;
timer_cfg_t gpt_cfg;
gpt_extended_cfg_t gpt_ext;
// timer_instance_t gpt_timer;
//

void setup()
{
	// Basic setup

	Serial.begin(115200);
	delay(1000);

	// Encoders
	{
		Serial.println(R_PFS->PORT[1].PIN[3].PmnPFS, HEX);
		Serial.println(R_PFS->PORT[1].PIN[2].PmnPFS, HEX);

		pinMode(4, INPUT);
		pinMode(5, INPUT);

		R_IOPORT_PinCfg(&g_ioport_ctrl, BSP_IO_PORT_01_PIN_03,              //
		                IOPORT_CFG_PERIPHERAL_PIN | IOPORT_PERIPHERAL_GPT2 |//
		                    IOPORT_CFG_PORT_DIRECTION_INPUT | IOPORT_CFG_PULLUP_ENABLE);

		R_IOPORT_PinCfg(&g_ioport_ctrl, BSP_IO_PORT_01_PIN_02,              //
		                IOPORT_CFG_PERIPHERAL_PIN | IOPORT_PERIPHERAL_GPT2 |//
		                    IOPORT_CFG_PORT_DIRECTION_INPUT | IOPORT_CFG_PULLUP_ENABLE);

		memset(&gpt_cfg, 0, sizeof(gpt_cfg));
		memset(&gpt_ext, 0, sizeof(gpt_ext));

		gpt_cfg.mode = TIMER_MODE_PERIODIC;// TIMER_MODE_TRIANGLE_WAVE_ASYMMETRIC_PWM;
		gpt_cfg.period_counts = 0xFFFFFFFF;
		gpt_cfg.source_div = TIMER_SOURCE_DIV_1;
		gpt_cfg.channel = 2;// GPT2
		gpt_cfg.p_callback = NULL;

		// gpt_ext.gtior_setting.gtior_b.gtioa = GPT_GTIO_PIN_CFG_INPUT;
		// gpt_ext.gtior_setting.gtior_b.gtiob = GPT_GTIO_PIN_CFG_INPUT;

		// gpt_ext.count_up_source =
		//     gpt_source_t(GPT_SOURCE_GTIOCA_RISING_WHILE_GTIOCB_LOW | GPT_SOURCE_GTIOCA_FALLING_WHILE_GTIOCB_HIGH |
		//                  GPT_SOURCE_GTIOCB_RISING_WHILE_GTIOCA_HIGH | GPT_SOURCE_GTIOCB_FALLING_WHILE_GTIOCA_LOW);

		// gpt_ext.count_down_source =
		//     gpt_source_t(GPT_SOURCE_GTIOCA_RISING_WHILE_GTIOCB_HIGH | GPT_SOURCE_GTIOCA_FALLING_WHILE_GTIOCB_LOW |
		//                  GPT_SOURCE_GTIOCB_RISING_WHILE_GTIOCA_LOW | GPT_SOURCE_GTIOCB_FALLING_WHILE_GTIOCA_HIGH);

		gpt_ext.count_up_source =
		    gpt_source_t(GPT_SOURCE_GTIOCA_RISING_WHILE_GTIOCB_LOW | GPT_SOURCE_GTIOCA_FALLING_WHILE_GTIOCB_HIGH |
		                 GPT_SOURCE_GTIOCB_RISING_WHILE_GTIOCA_HIGH | GPT_SOURCE_GTIOCB_FALLING_WHILE_GTIOCA_LOW |
		                 GPT_SOURCE_GTIOCA_RISING_WHILE_GTIOCB_HIGH | GPT_SOURCE_GTIOCA_FALLING_WHILE_GTIOCB_LOW |
		                 GPT_SOURCE_GTIOCB_RISING_WHILE_GTIOCA_LOW | GPT_SOURCE_GTIOCB_FALLING_WHILE_GTIOCA_HIGH);

		gpt_cfg.p_extend = &gpt_ext;

		R_GPT_Open(&gpt_ctrl, &gpt_cfg);
		R_GPT_Start(&gpt_ctrl);

		Serial.println(R_PFS->PORT[1].PIN[3].PmnPFS, HEX);
		Serial.println(R_PFS->PORT[1].PIN[2].PmnPFS, HEX);
	}

}
void loop()
{
	timer_status_t status;
	R_GPT_StatusGet(&gpt_ctrl, &status);
	Serial.println((int32_t)status.counter);
	delay(250);
}

Am I missing something obvious? Why is this not working?

Did you try feeding the sketch with appropriate prompts back to a different AI? I hear Claude is very good at code.

I use one of the cheep rotary switches, with some capacitors for de-bounce. Then I detect the edge of one of the inputs and then look at the other. It will be a 1 or zero depending on direction of rotation. If it is reversed you can swap wires or the logic in your code. This only takes a few lines. I have attached an interrupt version of the code. I normally just pole in my main loop.

volatile long position = 0;

const int pinA = 2;
const int pinB = 3;

void setup() {
  pinMode(pinA, INPUT_PULLUP);
  pinMode(pinB, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(pinA), isrA, CHANGE);
}

void loop() {
  static long last = 0;

  if (last != position) {
    Serial.println(position);
    last = position;
  }
}

void isrA() {
  if (digitalRead(pinB))
    position++;
  else
    position--;
}

I know it can be done in any configuration of interrupts and digitalreads.
However no serious microcontroller will dedicate CPU time to such trivial task.

Perhaps I expected too much from the arduino, after all.

I guess I'm not without fault tho - for featurecreeping a prototype, and for trying to use full capabilities of the MCU.

Yep you're missing a step. It's probably better to consult the FSP documentation than some AI nonsense.

Thanks for stating the obvious.
I consulted the documentation, as linked in the original post, but maybe I should look in some other places too.
Now be a good guy and show me how it's done.

Strange thing to say.

In the past, you could buy specialized quadrature encoder chips, but they all became obsolete when people realized it was so much easier and cheaper to have the MCU perform the task, with minimal, if not negligible impact on performance.

You mean "decoder"?
Also, which MCU does not perform this task in hardware?

Sorry, decoder chip, such as the LS7366R which includes a 32 bit counter.

There do exist MCUs with quadrature decoder peripherals built in, but I have not seen any examples discussed on the Arduino forum. People here use one of the available libraries, usually this one, as it works with any MCU with Arduino IDE support.

Perhaps someone at Renesas could explain how to use the peripherals built in to the RA4M1 processor on the Nano R4 to accomplish your goal.

@herhor67
Please Look at Rotary encoder with R4 using timer