arduino-audio-tools
AudioTimerESP32.h
1 #pragma once
2 
3 #if defined(ESP32) && defined(ARDUINO)
4 #include <esp_task_wdt.h>
5 
6 #include "AudioTools/CoreAudio/AudioTimer/AudioTimerBase.h"
7 
8 namespace audio_tools {
9 
16 class UserCallback {
17  public:
18  void setup(repeating_timer_callback_t my_callback, void *user_data,
19  bool lock) {
20  TRACED();
21  this->my_callback = my_callback;
22  this->user_data = user_data;
23  this->lock = lock; // false when called from Task
24  }
25 
26  IRAM_ATTR void call() {
27  if (my_callback != nullptr) {
28  if (lock) portENTER_CRITICAL_ISR(&timerMux);
29  my_callback(user_data);
30  if (lock) portEXIT_CRITICAL_ISR(&timerMux);
31  }
32  }
33 
34  protected:
35  repeating_timer_callback_t my_callback = nullptr;
36  void *user_data = nullptr;
37  portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
38  bool lock;
39 
40 } static *simpleUserCallback = nullptr;
41 
42 static IRAM_ATTR void userCallback0() { simpleUserCallback[0].call(); }
43 static IRAM_ATTR void userCallback1() { simpleUserCallback[1].call(); }
44 static IRAM_ATTR void userCallback2() { simpleUserCallback[2].call(); }
45 static IRAM_ATTR void userCallback3() { simpleUserCallback[3].call(); }
46 
53 class TimerCallback {
54  public:
55  TimerCallback() {
56  TRACED();
57  timerMux = portMUX_INITIALIZER_UNLOCKED;
58  p_handler_task = nullptr;
59  }
60 
61  void setup(TaskHandle_t handler_task) {
62  TRACED();
63  p_handler_task = handler_task;
64  assert(handler_task != nullptr);
65  }
66 
67  IRAM_ATTR void call() {
68  if (p_handler_task != nullptr) {
69  // A mutex protects the handler from reentry (which shouldn't happen, but
70  // just in case)
71  portENTER_CRITICAL_ISR(&timerMux);
72  BaseType_t xHigherPriorityTaskWoken = pdFALSE;
73  vTaskNotifyGiveFromISR(p_handler_task, &xHigherPriorityTaskWoken);
74  if (xHigherPriorityTaskWoken) {
75  portYIELD_FROM_ISR();
76  }
77  portEXIT_CRITICAL_ISR(&timerMux);
78  }
79  }
80 
81  protected:
82  portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
83  TaskHandle_t p_handler_task = nullptr;
84 
85 } static *timerCallbackArray = nullptr;
86 
87 static IRAM_ATTR void timerCallback0() { timerCallbackArray[0].call(); }
88 static IRAM_ATTR void timerCallback1() { timerCallbackArray[1].call(); }
89 static IRAM_ATTR void timerCallback2() { timerCallbackArray[2].call(); }
90 static IRAM_ATTR void timerCallback3() { timerCallbackArray[3].call(); }
91 
102 class TimerAlarmRepeatingDriverESP32 : public TimerAlarmRepeatingDriverBase {
103  public:
104  TimerAlarmRepeatingDriverESP32() {
105  setTimerFunction(DirectTimerCallback);
106  setTimer(0);
107  }
108 
109  TimerAlarmRepeatingDriverESP32(int timer, TimerFunction function) {
110  setTimerFunction(function);
111  setTimer(timer);
112  }
113 
114  void setTimer(int id) override {
115  if (id >= 0 && id < 4) {
116  this->timer_id = id;
117  handler_task = nullptr;
118  } else {
119  LOGE("Invalid timer id %d", timer_id);
120  }
121  }
122 
123  void setTimerFunction(TimerFunction function = DirectTimerCallback) override {
124  this->function = function;
125  }
126 
128  bool begin(repeating_timer_callback_t callback_f, uint32_t time,
129  TimeUnit unit = MS) override {
130  TRACED();
131 
132  // we determine the time in microseconds
133  switch (unit) {
134  case MS:
135  timeUs = time * 1000;
136  break;
137  case US:
138  timeUs = time;
139  break;
140  case HZ:
141  // convert hz to time in us
142  timeUs = AudioTime::toTimeUs(time);
143  break;
144  }
145 #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 1, 0)
146  LOGI("Timer every: %u us", timeUs);
147  adc_timer = timerBegin(timer_id, 80,
148  true); // divider=80 -> 1000000 calls per second
149 #else
150  uint32_t freq = AudioTime::AudioTime::toRateUs(timeUs);
151  LOGI("Timer freq: %u hz", (unsigned)freq);
152  adc_timer = timerBegin(1000000); // divider=80 -> 1000000 calls per second
153 #endif
154  switch (function) {
155  case DirectTimerCallback:
156  setupDirectTimerCallback(callback_f);
157  break;
158 
159  case TimerCallbackInThread:
160  setupTimerCallbackInThread(callback_f);
161  break;
162 
163  case SimpleThreadLoop:
164  setupSimpleThreadLoop(callback_f);
165  break;
166  }
167 
168  started = true;
169  return true;
170  }
171 
173  bool end() override {
174  TRACED();
175  if (started) {
176  timerDetachInterrupt(adc_timer);
177  timerEnd(adc_timer);
178  if (handler_task != nullptr) {
179  vTaskDelete(handler_task);
180  handler_task = nullptr;
181  }
182  }
183  started = false;
184  return true;
185  }
186 
187  void setCore(int core) { this->core = core; }
188 
191  void setIsSave(bool is_save) override {
192  setTimerFunction(is_save ? TimerCallbackInThread : DirectTimerCallback);
193  }
194 
195  protected:
196  int timer_id = 0;
197  volatile bool started = false;
198  TaskHandle_t handler_task = nullptr;
199  hw_timer_t *adc_timer = nullptr; // our timer
200  UserCallback user_callback; // for task
201  TimerFunction function;
202  int core = 1;
203  int priority = 1; // configMAX_PRIORITIES - 1;
204  uint32_t timeUs;
205 
207  void attach(hw_timer_t *timer, void (*cb)()) {
208 #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
209  timerAttachInterrupt(timer, cb);
210 #else
211  timerAttachInterrupt(timer, cb, false);
212 #endif
213  }
214 
216  void setupDirectTimerCallback(repeating_timer_callback_t callback_f) {
217  TRACED();
218  // We start the timer which executes the callbacks directly
219  if (simpleUserCallback == nullptr) {
220  simpleUserCallback = new UserCallback[4];
221  }
222  simpleUserCallback[timer_id].setup(callback_f, object, true);
223 
224  if (timer_id == 0)
225  attach(adc_timer, userCallback0);
226  else if (timer_id == 1)
227  attach(adc_timer, userCallback1);
228  else if (timer_id == 2)
229  attach(adc_timer, userCallback2);
230  else if (timer_id == 3)
231  attach(adc_timer, userCallback3);
232 
233 #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 1, 0)
234  timerAlarmWrite(adc_timer, timeUs, true);
235  timerAlarmEnable(adc_timer);
236 
237 #else
238  // timerStart(adc_timer);
239  // timerWrite(adc_timer,timeUs);
240  timerAlarm(adc_timer, timeUs, true, 0);
241 #endif
242  }
243 
246  void setupTimerCallbackInThread(repeating_timer_callback_t callback_f) {
247  TRACED();
248  // we start the timer which runs the callback in a seprate task
249  if (timerCallbackArray == nullptr) {
250  timerCallbackArray = new TimerCallback[4];
251  }
252 
253  if (timer_id == 0)
254  attach(adc_timer, timerCallback0);
255  else if (timer_id == 1)
256  attach(adc_timer, timerCallback1);
257  else if (timer_id == 2)
258  attach(adc_timer, timerCallback2);
259  else if (timer_id == 3)
260  attach(adc_timer, timerCallback3);
261 
262  // we record the callback method and user data
263  user_callback.setup(callback_f, object, false);
264  // setup the timercallback
265  xTaskCreatePinnedToCore(complexTaskHandler, "TimerAlarmRepeatingTask",
266  configMINIMAL_STACK_SIZE + 10000, &user_callback,
267  priority, &handler_task, core);
268  LOGI("Task created on core %d", core);
269  timerCallbackArray[timer_id].setup(handler_task);
270 
271 #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 1, 0)
272  timerAlarmWrite(adc_timer, timeUs, true);
273  timerAlarmEnable(adc_timer);
274 #else
275  timerAlarm(adc_timer, timeUs, true, 0); // set time in us
276 #endif
277  }
278 
280  void setupSimpleThreadLoop(repeating_timer_callback_t callback_f) {
281  TRACED();
282  user_callback.setup(callback_f, object, false);
283  xTaskCreatePinnedToCore(simpleTaskLoop, "TimerAlarmRepeatingTask",
284  configMINIMAL_STACK_SIZE + 10000, this, priority,
285  &handler_task, core);
286  LOGI("Task created on core %d", core);
287  }
288 
291  static void complexTaskHandler(void *param) {
292  TRACEI();
293  UserCallback *cb = (UserCallback *)param;
294 
295  while (true) {
296  // Sleep until the ISR gives us something to do
297  uint32_t thread_notification =
298  ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1000));
299  // Do something complex and CPU-intensive
300  if (thread_notification) {
301  cb->call();
302  }
303  delay(0);
304  }
305  }
306 
309  static void simpleTaskLoop(void *param) {
310  TRACEI();
311  TimerAlarmRepeatingDriverESP32 *ta =
312  (TimerAlarmRepeatingDriverESP32 *)param;
313 
314  while (true) {
315  unsigned long end = micros() + ta->timeUs;
316  ta->user_callback.call();
317  long waitUs = end - micros();
318  long waitMs = waitUs / 1000;
319  waitUs = waitUs - (waitMs * 1000);
320  delay(waitMs);
321  waitUs = end - micros();
322  if (waitUs > 0) {
323  delayMicroseconds(waitUs);
324  }
325  }
326  }
327 };
328 
330 using TimerAlarmRepeatingDriver = TimerAlarmRepeatingDriverESP32;
331 
332 } // namespace audio_tools
333 
334 #endif
static uint32_t toTimeUs(uint32_t samplingRate, uint8_t limit=10)
converts sampling rate to delay in microseconds (μs)
Definition: AudioTypes.h:256
TimeUnit
Time Units.
Definition: AudioTypes.h:45
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition: AudioConfig.h:872
TimerAlarmRepeatingDriverAVR TimerAlarmRepeatingDriver
use TimerAlarmRepeating!
Definition: AudioTimerAVR.h:94