Hello! First time posting on the forum!
I am trying to rebuild a self stabilizing two-wheeled robot I saw here.
I am currently looking at the software and I am having some trouble with the timer interrupts since I choose to use an Arduino Nano IoT that's based on ESP32-S3 . Can somebody please help me understanding the registers I need to use for it to work?
Here is an extract of the code that can't work on my micro:
// TIMER 1: controlling motor 1
ISR(TIMER1_COMPA_vect){
TCNT1 = 0;
if (directionMotor1 == 0)
return;
PORTD |= (1<<PORTD4); //set bit // same as digitalWrite(STEPPER_STEP_1, HIGH);
delay_05us();
PORTD &= ~(1<<PORTD4); //clear bit // same as digitalWrite(STEPPER_STEP_1, LOW);
}
// TIMER 3: controlling motor 2
ISR(TIMER3_COMPA_vect){
TCNT3 = 0;
if (directionMotor2 == 0)
return;
PORTD |= (1<<PORTD7); //set bit // same as digitalWrite(STEPPER_STEP_2, HIGH);
delay_05us();
PORTD &= ~(1<<PORTD7); //clear bit // same as digitalWrite(STEPPER_STEP_2, LOW);
}
void setMotorSpeed(int16_t tspeed, int motorID)
{
long timer_period;
int16_t motorspeed = tspeed;
noInterrupts();
if (motorID == 1) {
if (motorspeed > 0) {
timer_period = 2000000 / motorspeed; // 2Mhz timer
directionMotor1 = 1;
digitalWrite(STEPPER_DIR_1, LOW);
} else if (motorspeed < 0) {
timer_period = 2000000 / -motorspeed;
directionMotor1 = -1;
digitalWrite(STEPPER_DIR_1, HIGH);
} else {
timer_period = 65535;
directionMotor1 = 0;
}
if (timer_period > 65535) // Check for maximun period without overflow
timer_period = 65535;
/* ERROR
OCR1A = timer_period;
if (TCNT1 > OCR1A) // Check if we need to reset the timer...
TCNT1 = 0;
*/
} else if (motorID == 2){
if (motorspeed > 0) {
timer_period = 2000000 / motorspeed; // 2Mhz timer
directionMotor2 = 1;
digitalWrite(STEPPER_DIR_2, HIGH);
} else if (motorspeed < 0) {
timer_period = 2000000 / -motorspeed;
directionMotor2 = -1;
digitalWrite(STEPPER_DIR_2, LOW);
} else {
timer_period = 65535;
directionMotor2 = 0;
}
if (timer_period > 65535) // Check for maximun period without overflow
timer_period = 65535;
/* ERROR
OCR3A = timer_period;
if (TCNT3 > OCR3A) // Check if we need to reset the timer...
TCNT3 = 0;
*/
}
interrupts();
}
I found this example that works but I'd like to understand more because I read somewhere that TC3 isn't the most reliable one:
Thanks for the suggestion, I don't think it's impossible, it's just I can't find any documentation about the registers I need to work with. On the Arduino's official datasheet there's not a single mention.
I'll look more into it, at the moment i found this and I'll test it soon. Thank you again.
I don't understand what you mean by rewriting from scratch, I of course need the rest of the code and the concept behind the AVR timers (when they are called, what's the frequency, when they are resetted, what interrupts are they triggering, what are interrupts doing and so on) so it's basically rewriting the entire timer section from scratch, I am aware I can't just do AVR_TIMER --> ESP_TIMER and expect everything to work. But thanks anyway, I'll follow your suggestions an maybe try using an Arduino UNO that hopefully shares the same or similar AVR timer registers.
Alright, it makes sense. I didn't think about it in this way. Thank you very much for the mind opening, I was really stuck. But is arduino nano esp32 really based only on esp? Will any program that work on esp word on arduino esp? Because I tried including some libraries for esp and didn't work.
I found what I was looking for, this full pinout diagram shows all the timer registers and in combination with the Microchip datasheet (I didn't know I was using ARM timers instead of ESP) I can interact with them. Now I just have to figure out how to trigger an ISR at the right time.
This code example shows how to use timers to generate a 33 kHz PWM on PINs D2 and D6 by using the TCC0 registers.
void setup() {
//************************************** Clcok Setting ********************************************************//
// Feed GCLK0 at 48MHz to TCC0 and TCC1
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable GCLK0 as a clock source (1 << 14) (imposta il bit 14 a 1 per abilitarlo)
GCLK_CLKCTRL_GEN_GCLK0 | // Select GCLK0 at 48MHz (0 << 8) (GCLK_CLKCTRL_GEN_GCLK1, GCLK_CLKCTRL_GEN_GCLK2, ...)
GCLK_CLKCTRL_ID_TCC0_TCC1; // Route GCLK0 to TCC0 and TCC1 (0x1A << 0)
//************************************** Clcok Setting ********************************************************//
//************************************** PORT Enabling ********************************************************//
// Enable the port multiplexer for pins D6
PORT->Group[g_APinDescription[6].ulPort].PINCFG[g_APinDescription[6].ulPin].bit.PMUXEN = 1;
// D6 is on EVEN port pin PA04 and TCC0/WO[0] channel 0 is on peripheral E
PORT->Group[g_APinDescription[6].ulPort].PMUX[g_APinDescription[6].ulPin >> 1].reg |= PORT_PMUX_PMUXE_E;
// Enable the port multiplexer for pins D2
PORT->Group[g_APinDescription[2].ulPort].PINCFG[g_APinDescription[2].ulPin].bit.PMUXEN = 1;
// D2 is on EVEN port pin PB10 and TCC0/WO[4] channel 0 is on peripheral F
PORT->Group[g_APinDescription[2].ulPort].PMUX[g_APinDescription[2].ulPin >> 1].reg |= PORT_PMUX_PMUXE_F;
//************************************** PORT Enabling ********************************************************//
//************************************** TIMER Setting ********************************************************//
// Normal (single slope) PWM operation: timer countinuously counts up to PER register value and then is reset to 0
REG_TCC0_WAVE = TCC_WAVE_WAVEGEN_NPWM; // Setup single slope PWM on TCC0 (2 << 0)
while (TCC1->SYNCBUSY.bit.WAVE); // Wait for synchronization
REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 ; // Set prescaler to 1, 48MHz/1 (0 << 8)
REG_TCC0_PER = 1453; // Set the frequency of the PWM on TCC0 to 33kHz: 48MHz / (1 * 1453 + 1) = 33kHz
while (TCC0->SYNCBUSY.bit.PER); // Wait for synchronization
REG_TCC0_CC0 = 726; // TCC1 CC0 - 50% duty cycle
while (TCC0->SYNCBUSY.bit.CC0); // Wait for synchronization
// dead time generation (edit the value to adjust the dead time on Low side (bit: 16) and High side (bit:24))
REG_TCC0_WEXCTRL |= (50 << 16) | (50 << 24) | (1 << 8) | (2 << 0); // (1 << 8): implement dead time at output WO[0] and WO[4]
// (2 << 0): output matrix (all output use CC0
TCC0->CTRLA.bit.ENABLE = 1; // Enable the TCC0 counter
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
//************************************** TIMER Setting ********************************************************//
}
void loop() {
}
why not use the ESP32TimerInterrupt Library
e.g. generating a 10KHz square wave on GPIO4 using a ESP32-S3-DevKitC-1 Timer0 interrupts
// esp32 interrupt every 50uSec generate 20KHz square wave on GPIO4
// original code from https://github.com/khoih-prog/ESP32TimerInterrupt
#if !defined(ESP32)
#error This code is intended to run on the ESP32 platform! Please check your Tools->Board setting.
#endif
// These define's must be placed at the beginning before #include "ESP32_New_TimerInterrupt.h"
// _TIMERINTERRUPT_LOGLEVEL_ from 0 to 4
#define _TIMERINTERRUPT_LOGLEVEL_ 4
// To be included only in main(), .ino with setup() to avoid `Multiple Definitions` Linker Error
#include "ESP32TimerInterrupt.h"
// Don't use PIN_D1 in core v2.0.0 and v2.0.1. Check https://github.com/espressif/arduino-esp32/issues/5868
// Don't use PIN_D2 with ESP32_C3 (crash)
#define PIN_GPIO4 4 // Pin GPIO19 signal output
// With core v2.0.0+, you can't use Serial.print/println in ISR or crash.
// and you can't use float calculation inside ISR Only OK in core v1.0.6-
volatile int counter = 0; // counting timer interrupts
bool IRAM_ATTR TimerHandler0(void* timerNo) {
static bool toggle0 = false;
counter++;
digitalWrite(PIN_GPIO4, toggle0);
toggle0 = !toggle0; //timer interrupt toggles pin PIN_GPIO4
return true;
}
#define TIMER0_INTERVAL_US 50 // timer interrupts in uSec
// Init ESP32 timer 0 and 1
ESP32Timer ITimer0(0);
void setup() {
pinMode(PIN_GPIO4, OUTPUT);
Serial.begin(115200);
while (!Serial && millis() < 5000)
;
delay(500);
// setup timer 0 interrupts
Serial.print(F("\nStarting TimerInterruptTest on "));
Serial.println(ARDUINO_BOARD);
Serial.println(ESP32_TIMER_INTERRUPT_VERSION);
Serial.print(F("CPU Frequency = "));
Serial.print(F_CPU / 1000000);
Serial.println(F(" MHz"));
// Using ESP32 => 80 / 160 / 240MHz CPU clock ,
// For 64-bit timer counter
// For 16-bit timer prescaler up to 1024
// Interval in microsecs
if (ITimer0.attachInterruptInterval(TIMER0_INTERVAL_US, TimerHandler0)) {
Serial.print(F("Starting ITimer0 OK"));
} else
Serial.println(F("Can't set ITimer0. Select another freq. or timer"));
Serial.flush();
}
void loop() {
// display interrupt counter every second
static long timer = millis();
if (millis() - timer >= 1000) {
timer = millis();
Serial.println(counter);
counter = 0;
}
}
Thank you for your partecipation to this topic!
I tried it but thanks to @Delta_G I found out I am not really programming on the ESP32 chip but instead my code runs on the Atmel SAMD21 wich works in combination with the ESP.
In fact this specific line of code stops me from successefully compiling my code:
#if !defined( ESP32 )
#error This code is intended to run on the ESP32 platform! Please check your Tools->Board setting.
#endif
In the board manager I have to use Arduino Nano 33 IoT, if I use the ESP32S3 Dev Module I can't compile it right. It should work if I knew how to put the code directly onto the ESP without using the other chip.
I'm now working on the SAMD21 registers which are explained in the datasheet linked in my previous update.
This is the closest example I found that almost fully matches my needs, I just have to tweak it a little bit:
void setupTimerISR()
{
// Set up the generic clock (GCLK4) used to clock timers
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Divide the 48MHz clock source by divisor 1: 48MHz/1=48MHz
GCLK_GENDIV_ID(4); // Select Generic Clock (GCLK) 4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK4
GCLK_GENCTRL_SRC_DFLL48M | // Set the 48MHz clock source
GCLK_GENCTRL_ID(4); // Select GCLK4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
// Feed GCLK4 to TC4 and TC5
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable GCLK4 to TC4 and TC5
GCLK_CLKCTRL_GEN_GCLK4 | // Select GCLK4
GCLK_CLKCTRL_ID_TC4_TC5; // Feed the GCLK4 to TC4 and TC5
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
// TC4 at 46.875 kHz for step period
REG_TC4_COUNT16_CC0 = 0; // Set the TC4 CC0 register as the TOP value in match frequency mode
while (TC4->COUNT16.STATUS.bit.SYNCBUSY); // Wait for synchronization
// TC5 at 1000 Hz for acceleration
REG_TC5_COUNT16_CC0 = CC0; // Set the TC5 CC0 register as the TOP value in match frequency mode
while (TC5->COUNT16.STATUS.bit.SYNCBUSY); // Wait for synchronization
NVIC_SetPriority(TC4_IRQn, 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for TC4 to 0 (highest)
NVIC_EnableIRQ(TC4_IRQn); // Connect TC4 to Nested Vector Interrupt Controller (NVIC)
NVIC_SetPriority(TC5_IRQn, 1); // Set the Nested Vector Interrupt Controller (NVIC) priority for TC5 to 1
NVIC_EnableIRQ(TC5_IRQn); // Connect TC5 to Nested Vector Interrupt Controller (NVIC)
REG_TC4_INTFLAG |= TC_INTFLAG_OVF; // Clear the interrupt flags
REG_TC4_INTENSET = TC_INTENSET_OVF; // Enable TC4 interrupts
REG_TC5_INTFLAG |= TC_INTFLAG_OVF; // Clear the interrupt flags
REG_TC5_INTENSET = TC_INTENSET_OVF; // Enable TC5 interrupts
REG_TC4_CTRLA |= TC_CTRLA_PRESCALER_DIV1024 | // Set prescaler to 1024, 48MHz/1024 = 46.875kHz
TC_CTRLA_WAVEGEN_MFRQ | // Put the timer TC4 into match frequency (MFRQ) mode
TC_CTRLA_ENABLE; // Enable TC4
while (TC4->COUNT16.STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_TC5_CTRLA |= TC_CTRLA_PRESCALER_DIV1024 | // Set prescaler to 1024, 48MHz/1024 = 46.875kHz
TC_CTRLA_WAVEGEN_MFRQ | // Put the timer TC5 into match frequency (MFRQ) mode
TC_CTRLA_ENABLE; // Enable TC5
while (TC5->COUNT16.STATUS.bit.SYNCBUSY); // Wait for synchronization
}
Thanks again for your suggestion I'll look if it fits better using only the onboard ESP.
But what if I need to operate two separate timers?
I tried the examples on the GitHub page of the library with no success. I even tried to manually include the other header files such as #include <SAMD_ISR_Timer.h> also with "".
error: 'ISR_Timer' does not name a type; did you mean 'ITimer'?
ISR_Timer SAMD_ISR_Timer;
^~~~~~~~~
ITimer
And then many other consequent errors and one in particular i found interesting. What I forgot? I just copypasted the code from github (yes, the two separate code blocks merged).
error: 'lastMillis' was not declared in this scope
lastMillis = millis();
^~~~~~~~~~
I'd like to have two different TimerHandler() functions. That operate at the same frequency and everything, the only difference is the content. Or I could simply put the code inside the same since it doesn't change the frequency.
Yep, that's right. I will use the interrupt to send a step signal to a stepper motor like this:
if (directionMotor1 == 0)
return;
PORTD |= (1<<PORTD4); //set bit // same as digitalWrite(STEPPER_STEP_1, HIGH);
delay_05us();
PORTD &= ~(1<<PORTD4); //clear bit // same as digitalWrite(STEPPER_STEP_1, LOW);
There is a second motor that could use a second interrupt but i don't see any problem putting the code together. The only thing I'd have to edit is the return because I should check both before exiting. But it might work! Atm I don't have the robot with me. I'll write a sketch and tomorrow I'll test it as soon as I can and post an update.
Thank you for the heads up! I am used to set and read pins using registers but forgot they are hardware specific. I'll just use digitalWrite. Or maybe since the SAMD chip is made by Microchip it uses some similar registers as the PIC family which I already know how to use. I should try.
program using two SAMD21 timers to generate square wave 5KHz on pin 6 and 10KHz on pin 7
// MKRFOX 1200 (SAMS21) timer test - generate square wave 5KHz on pin 6 and 10KHz on pin 7
// NOTE: square waves are not syncronised
// from g https://github.com/khoih-prog/SAMD_TimerInterrupt
#if !(defined(ARDUINO_SAMD_ZERO) || defined(ARDUINO_SAMD_MKR1000) || defined(ARDUINO_SAMD_MKRWIFI1010) \
|| defined(ARDUINO_SAMD_NANO_33_IOT) || defined(ARDUINO_SAMD_MKRFox1200) || defined(ARDUINO_SAMD_MKRWAN1300) || defined(ARDUINO_SAMD_MKRWAN1310) \
|| defined(ARDUINO_SAMD_MKRGSM1400) || defined(ARDUINO_SAMD_MKRNB1500) || defined(ARDUINO_SAMD_MKRVIDOR4000) \
|| defined(ARDUINO_SAMD_CIRCUITPLAYGROUND_EXPRESS) || defined(__SAMD51__) || defined(__SAMD51J20A__) \
|| defined(__SAMD51J19A__) || defined(__SAMD51G19A__) || defined(__SAMD51P19A__) \
|| defined(__SAMD21E15A__) || defined(__SAMD21E16A__) || defined(__SAMD21E17A__) || defined(__SAMD21E18A__) \
|| defined(__SAMD21G15A__) || defined(__SAMD21G16A__) || defined(__SAMD21G17A__) || defined(__SAMD21G18A__) \
|| defined(__SAMD21J15A__) || defined(__SAMD21J16A__) || defined(__SAMD21J17A__) || defined(__SAMD21J18A__))
#error This code is designed to run on SAMD21/SAMD51 platform! Please check your Tools->Board setting.
#endif
/////////////////////////////////////////////////////////////////
// These define's must be placed at the beginning before #include "SAMDTimerInterrupt.h"
// _TIMERINTERRUPT_LOGLEVEL_ from 0 to 4
// Don't define _TIMERINTERRUPT_LOGLEVEL_ > 0. Only for special ISR debugging only. Can hang the system.
// Don't define TIMER_INTERRUPT_DEBUG > 2. Only for special ISR debugging only. Can hang the system.
#define TIMER_INTERRUPT_DEBUG 0
#define _TIMERINTERRUPT_LOGLEVEL_ 0
// Select only one to be true for SAMD21. Must must be placed at the beginning before #include "SAMDTimerInterrupt.h"
#define USING_TIMER_TC3 true // Only TC3 can be used for SAMD51
#define USING_TIMER_TC4 true // Not to use with Servo library
#include "SAMDTimerInterrupt.h"
#define SQUARE_WAVE_PIN3 6 // pin to output square wave
#define SQUARE_WAVE_PIN4 7 // pin to output square wave
#define TIMER_INTERVAL_US 50 // timer interval in uSec
// Depending on the board, you can select SAMD21 Hardware Timer from TC3, TC4, TC5, TCC, TCC1 or TCC2
// SAMD51 Hardware Timer only TC3
SAMDTimer ITimer3(TIMER_TC3);
SAMDTimer ITimer4(TIMER_TC4);
// timer interrupt routine
volatile int counter3;
void TimerHandler_TIMER_TC3() {
counter3++;
//timer interrupt toggles pin SQUARE_WAVE_PIN
digitalWrite(SQUARE_WAVE_PIN3, !digitalRead(SQUARE_WAVE_PIN3));
}
// timer interrupt routine
volatile int counter4;
void TimerHandler_TIMER_TC4() {
counter4++;
//timer interrupt toggles pin SQUARE_WAVE_PIN
digitalWrite(SQUARE_WAVE_PIN4, !digitalRead(SQUARE_WAVE_PIN4));
}
void setup() {
pinMode(SQUARE_WAVE_PIN3, OUTPUT);
pinMode(SQUARE_WAVE_PIN4, OUTPUT);
Serial.begin(115200);
while (!Serial && millis() < 5000)
;
delay(100);
Serial.print(F("\nStarting TimerInterruptTest on "));
Serial.println(BOARD_NAME);
Serial.println(SAMD_TIMER_INTERRUPT_VERSION);
Serial.print(F("CPU Frequency = "));
Serial.print(F_CPU / 1000000);
Serial.println(F(" MHz"));
// Interval in microseconds
if (ITimer3.attachInterruptInterval(TIMER_INTERVAL_US * 5, TimerHandler_TIMER_TC3)) {
} else
Serial.println(F("Can't set ITimer3. Select another freq. or timer"));
if (ITimer4.attachInterruptInterval(TIMER_INTERVAL_US, TimerHandler_TIMER_TC4)) {
} else
Serial.println(F("Can't set ITimer4. Select another freq. or timer"));
}
void loop() {
// ecery second print interupt counter
static long timer = millis();
if (millis() - timer > 1000) {
timer = millis();
Serial.print("counter3 ");
Serial.print(counter3);
Serial.print(" counter4 ");
Serial.println(counter4);
counter3 = counter4 = 0;
}
}
NOTE: square waves are not syncronised only appear so on scope because I have stopped the run
to synchronise generate the 2KHz from the 10KHz interrupt, e.g. every fifth interrupt invert the 2KHz output