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:
Converting that code from AVR to ESP is not going to be straight forward. They are completely different architectures. You'd be better off to just throw this out an completely rewrite it. Or try to understand how it works and rewrite it.
If that's something that is beyond your abilities, then I would recommend getting the board that is compatible with the code you want to use or finding a project that is compatible with the board you want to use. Trying to port this from one architecture to the other is NOT a trivial task and would surely be more work than simply rewriting code for a balancer from scratch.
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.
Oh, it's certainly not impossible. But it will be harder than writing from scratch. There are not going to be one-to-one replacements for those registers.
Right, Arduino is meant to abstract that stuff away. You shouldn't have to go to that level with most Arduino projects. It's not the intended audience.
If you want to find information on the registers in the ESP then check the datasheets from Espressif. For the AVR chips get the AVR datasheets from Microchip (formerly Atmel).
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.
That's what I mean. You'd have to know what this code is trying to do with those timers, and then figure out how to do the same sort of thing with a different set of timers. But how to balance a self-balancer is not something that you can only learn from this code. There are tons of examples on how a balancing robot works. I think it would be easier to decide what needs to be done (measure roll at certain intervals, run PID calculation at certain intervals, provide output to the motors as appropriate) and then write code for an ESP to do that. Taking apart how its done on an AVR won't be any more informative and will require twice as much study.
Basically you'll end up rewriting the code at the end of the day either way. The question is how do you want to learn what to do in the code you write? Do you want to struggle to understand how it works on an AVR, and then from there try to understand what's needed, and then from there try to understand how to do that on an ESP? Or do you think it makes more sense to cut that first step out and just go straight to learning what needs to be done and then how to do it on the chip you have.
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.