I bought some of the new Arduino Nano R4 boards to play with. These boards step up from AVR to ARM (RA4M1) microcontrollers while still running at 5V, a rarity these days. The complete change of architecture brings with it some growing pains. Nothing is the same under the hood, and few of the new features are meaningfully supported within the Arduino ecosystem.
I'd like to implement some kind of autotune for a synth, but how do we measure frequency with the new R4 peripherals? Since Arduino S.r.l. (Qualcomm) isn't going to hold our hand, we have to dig into the manufacturer's datasheet.
RTFM
There's a feature that some timers have, called "input capture". It doesn't get mentioned all that much, but it's very handy for measuring external pulses. It allows an incoming signal to trigger a "snapshot" (capture) of a timer's current count. When configured correctly, it can measure things like the timing/width of a pulse, or the period/frequency of a waveform. If we search the datasheet for this term, we can find Figure 22.17
| P 445 of the Renesas RA4M1 Group 32 User’s Manual |
This is the process to set up a timer for input capture. Great, but how do we follow this, and what are these acronyms? GTCR, GTUDDTYC, GTICASR... These are memory-mapped registers that control the timers. Explanations of every timer (GPT) register exist in the datasheet starting with Table 22.4 on page 396.
| P 396 of the Renesas RA4M1 Group 32 User’s Manual |
Ok, but how do we actually use these registers within the Arduino IDE? Buried in the Arduino files is a header that defines these: R7FA4M1AB.h
Here's GTCR. We can see that it's the "General PWM Timer Control Register". The header defines this as a union. This is just a method of declaring multiple variables that live within one address. CST is a single bit of GTCR, the least significant, while MD spans bits 16 through 18 of the same address.
| GTCR definition from R7FA4M1AB.h |
If we look higher in the header, we see that the GTCR union is within a struct named "R_GPT0". This serves to contain all the timer-related registers. If we want to refer to MD, like the datasheet instructs, we would use: R_GPT0->GTCR_b.MD
The Wind-up
The following code will configure the timer using the syntax we've established. There is a "gotcha" though. The timer module has to be enabled before it will function.
//Timer Setup------------------------------------------------------------------------------------ R_MSTP->MSTPCRD_b.MSTPD5 = 0; //enable GPT0 module clock (General PWM Timer 321 to 320 Module Stop) delayMicroseconds(10); R_GPT0->GTCR_b.CST = 0; //stop timer R_GPT0->GTCR_b.MD = 0b000; //saw-wave PWM mode (000b) R_GPT0->GTUDDTYC = 0b11; //set 11b first (per datasheet Figure 22.17) R_GPT0->GTUDDTYC = 0b01; //01b for up-counting R_GPT0->GTPR = 0xFFFFFFFF; //max cycle R_GPT0->GTCNT = 0; //0 initial count //GTCCRA input capture enabled on the... R_GPT0->GTICASR_b.ASCARBL = 1; //rising edge of GTIOCA input when GTIOCB input is 0 R_GPT0->GTICASR_b.ASCARBH = 1; //rising edge of GTIOCA input when GTIOCB input is 1 R_GPT0->GTCR_b.CST = 1; //start count operation
Some of these acronyms above aren't mentioned in Figure 22.17, but the datasheet can clarify them.
On Time
The timer is running, and is configured for input capture on the rising edge of input GTIOCA. Well, what is that? It's a pin of the microcontroller, but the Arduino board obscures the true names of the pins by holding to their naming convention of D0-D13.
We can view the real pin names in the schematic of the Nano board. GTIOCA is P107_GPT0_A, and that maps to "D7" of the Arduino header.
| Arduino Nano R4 schematic |
When this pin state changes to high, the counter's value will be dumped into register GTCCR and the TCFA flag will be set. We can wait for this flag, read the value from the register, then clear the flag.
while (!R_GPT0->GTST_b.TCFA); //wait for input capture A uint32_t lastTS = R_GPT0->GTCCR[0]; R_GPT0->GTST_b.TCFA = 0; //clear the flag
We run the risk of not polling the flag quickly enough, and missing a captured value though. A better approach is to use interrupts.
Excuse Me
Interrupts are another poorly covered topic when it comes to the R4 within the Arduino IDE. The first snag to getting an input capture interrupt is the GPIO. Using the "Port mn Pin Function Select Register", the pin must be switched to work as a peripheral instead of standard IO. We then need to select GTIOC0A as the peripheral. The correct value for this can be found in Table 19.6
| P 370 of the Renesas RA4M1 Group 32 User’s Manual |
Plugging in the value gives us this:
//GPIO Pin D7 (P107) Setup R_PFS->PORT[1].PIN[7].PmnPFS_b.PMR = 1; //Used as an I/O port for peripheral functions R_PFS->PORT[1].PIN[7].PmnPFS_b.PSEL = 0b11; //GTIOC0A (GPT peripheral function)
Then we need to set up the interrupt. This process isn't exactly straightforward, but we can crib from those who have gone before us. This post on the Arduino forums by pertomaslarsson is very helpful. Referencing it, I came up with this:
//Asynchronous General Purpose Timer interrupt (from vector_data.h) static const IRQn_Type IRQn_CCMPA = AGT0_INT_IRQn; //Interrupt Setup //assign GPT0 Capture to IRQn_CCMPA (AGT0_INT_IRQn #17) R_ICU->IELSR_b[IRQn_CCMPA].IELS = ELC_EVENT_GPT0_CAPTURE_COMPARE_A; NVIC_SetVector(IRQn_CCMPA, (uint32_t)captureISR); //point to the ISR function NVIC_SetPriority(IRQn_CCMPA, 12); NVIC_EnableIRQ(IRQn_CCMPA);
Much like the pins, there are a finite number of interrupt vectors, and we have to select what functionality we'd like them to have. Here we assign our input capture interrupt to the generic AGT0 vector. We also give it a function(Interrupt Service Routine) to call when the interrupt happens.
Here's a simple example of an ISR function:
uint32_t lastTS, currentTS, delta = 0; //input capture readings void captureISR() { lastTS = currentTS; currentTS = R_GPT0->GTCCR[0]; //read from input capture register delta = currentTS - lastTS; //clear interrupt flag for compare match A R_ICU->IELSR_b[IRQn_CCMPA].IR = 0; }
It stores the previous reading, grabs a new reading, and clears the flag, allowing a new interrupt to fire. We can use the readings elsewhere in our code.
Alright Stop
So far, this has all been academic. Let's finish with a functional example. I've created a simple guitar tuner that displays the frequency in Hz, the name of the note, and how far the tuning is from concert pitch.
| Guitar tuner proof of concept |
The source code is available on pastebin




