Hi All,
Using an ESP32-S3 FN8 on a custom PCB, I'm trying to use a timer-based interrupt to frequently adjust settings on an analog-out pin (i.e., update both frequency of pwm and duration) for a piezo speaker.
Each time the timer alarm fires, the interrupt routine should adjust the pwm frequency and also re-arm the timer alarm for the new updated time period.
Here's the simplest version of the code that exemplifies the issue:
(Note: I've been developing on ESP32 Board Manager package v2.0.14)
#define SPEAKER_PIN 41
#define PWM_CHANNEL 0 // ESP32 has many channels; we'll use the first
#define SPKR_CLK_DIV 40000 // max 65535
#define FX_NOTE_LENGTH 500*2 // timer ticks twice every ms, so multiply desired ms by 2 for proper number of ticks
hw_timer_t *speaker_timer = NULL;
void onSpeakerTimerSimple(void);
volatile uint16_t tonal_frequency = 440;
void setup() {
delay(1000);
Serial.begin(115200);
delay(1000);
//configure pinouts and PWM channel
pinMode(SPEAKER_PIN, OUTPUT);
ledcSetup(PWM_CHANNEL, 1000, 8);
ledcAttachPin(SPEAKER_PIN, PWM_CHANNEL);
//setup speaker timer interrupt to track each "beat" of sound
speaker_timer = timerBegin(1, SPKR_CLK_DIV, true); // timer#, clock divider, count up -- this just configures, but doesn't start. NOTE: timerEnd() de-configures.
timerAttachInterrupt(speaker_timer, &onSpeakerTimerSimple, true); // timer, ISR call, rising edge NOTE: timerDetachInterrupt() does the opposite
}
void loop() {
onSpeakerTimerSimple();
while(1);
}
void IRAM_ATTR onSpeakerTimerSimple() {
Serial.print("ENTER ISR: "); Serial.print(tonal_frequency); Serial.print(" @ "); Serial.println(millis());
ledcWriteTone(PWM_CHANNEL, tonal_frequency);
tonal_frequency += 100;
timerAlarmWrite(speaker_timer, FX_NOTE_LENGTH, false); // set timer for play period (don't reload; we'll trigger back into this ISR when time is up)
timerWrite(speaker_timer, 0); // start at 0
timerAlarmEnable(speaker_timer); // and.. go!
}
I expect to see a continuous series of updates every 500ms.
What I actually see is two updates, then nothing. The Serial.print messages are:
ENTER ISR: 100 @ 2103
ENTER ISR: 200 @ 2603
...and that's it. The ISR routine seems to run successfully once from the initial main loop call, and seems to successfully "re-arm" itself, and then gets called once again... but then ceases to fire after that. We only make it through that code path twice.
Any ideas why it doesn't work again after that?
On the off chance the 3.0 update fixed things, I gave that a shot here:
(running on ESP32 v3.0.1)
#define SPEAKER_PIN 41
#define SPKR_CLK_DIV 40000 // max 65535
#define FX_NOTE_LENGTH 1000 // timer ticks per note play
hw_timer_t *speaker_timer = NULL;
void onSpeakerTimerSimple(void);
volatile uint16_t tonal_frequency = 440;
void setup() {
delay(1000);
Serial.begin(115200);
delay(1000);
//configure pinouts and PWM channel
pinMode(SPEAKER_PIN, OUTPUT);
ledcAttach(SPEAKER_PIN, 1000, 10);
//setup speaker timer interrupt to track each "beat" of sound
speaker_timer = timerBegin(1000);
timerAttachInterrupt(speaker_timer, &onSpeakerTimerSimple); // timer, ISR call
}
void loop() {
onSpeakerTimerSimple();
while(1);
}
void IRAM_ATTR onSpeakerTimerSimple() {
Serial.print("ENTER ISR: "); Serial.print(tonal_frequency); Serial.print(" @ "); Serial.println(millis());
ledcWriteTone(SPEAKER_PIN, tonal_frequency);
tonal_frequency += 100;
if (tonal_frequency >= 2200) tonal_frequency = 100;
timerAlarm(speaker_timer, FX_NOTE_LENGTH, false, 0); // set timer for play period (don't reload; we'll trigger back into this ISR when time is up)
timerWrite(speaker_timer, 0); // start at 0
}
Curiously, this time it runs continuously, but twice per "update cycle". Serial.print output is:
ENTER ISR: 440 @ 2097
ENTER ISR: 540 @ 3097
ENTER ISR: 640 @ 3097
ENTER ISR: 740 @ 4097
ENTER ISR: 840 @ 4097
ENTER ISR: 940 @ 5097
ENTER ISR: 1040 @ 5097
... etc etc
Note the millis() timestamp repeating twice. So now the ISR is executing twice in a row each time.
Any help solving these interrupt issues would be much appreciated!
PS: a few notes:
- I've learned that putting Serial.print debugging statements inside an ISR is bad form. In this case they're just for clarity during debugging, and I've tried the code with those removed and get the same results.
- I'm aware that using interrupts is probably not required. I'm making a variometer for paragliding/sailplanes, where the key feature is adjusting a continuous beeping tone based on rate of climb (altitude change). Beeps get more rapid and higher pitch as climb rate increases. The shortest a "beep" might be is around 100ms. And my main loop code will be handling tasks that take approximately 8~14ms. I'd prefer to not have 8~14ms of variation on a 100ms beep.
Regardless, one of the main goals of this project is just to learn Arduino and ESP32 architecture, so I'd love to get smarter on interrupt handling!
Thanks!