diff --git a/CMakeLists.txt b/CMakeLists.txt
index 775baecf412..d952d51474e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -57,6 +57,7 @@ set(CORE_SRCS
   cores/esp32/stdlib_noniso.c
   cores/esp32/Stream.cpp
   cores/esp32/StreamString.cpp
+  cores/esp32/Tone.cpp
   cores/esp32/HWCDC.cpp
   cores/esp32/USB.cpp
   cores/esp32/USBCDC.cpp
diff --git a/cores/esp32/Arduino.h b/cores/esp32/Arduino.h
index b1e8f7cd7de..c8be9222b39 100644
--- a/cores/esp32/Arduino.h
+++ b/cores/esp32/Arduino.h
@@ -195,6 +195,10 @@ extern "C" void configTime(long gmtOffset_sec, int daylightOffset_sec,
 extern "C" void configTzTime(const char* tz,
         const char* server1, const char* server2 = nullptr, const char* server3 = nullptr);
 
+void setToneChannel(uint8_t channel = 0);
+void tone(uint8_t _pin, unsigned int frequency, unsigned long duration = 0);
+void noTone(uint8_t _pin);
+
 // WMath prototypes
 long random(long);
 #endif /* __cplusplus */
diff --git a/cores/esp32/Tone.cpp b/cores/esp32/Tone.cpp
new file mode 100644
index 00000000000..ca69c10c13b
--- /dev/null
+++ b/cores/esp32/Tone.cpp
@@ -0,0 +1,131 @@
+#include <Arduino.h>
+#include "esp32-hal-ledc.h"
+#include "freertos/task.h"
+#include "freertos/queue.h"
+#include "freertos/semphr.h"
+
+static TaskHandle_t _tone_task = NULL;
+static QueueHandle_t _tone_queue = NULL;
+static uint8_t _channel = 0;
+
+typedef enum{
+  TONE_START,
+  TONE_END,
+  TONE_SET_CHANNEL
+} tone_cmd_t;
+
+typedef struct{
+  tone_cmd_t tone_cmd;
+  uint8_t pin;
+  unsigned int frequency;
+  unsigned long duration;
+  uint8_t channel;
+} tone_msg_t;
+
+static void tone_task(void*){
+  tone_msg_t tone_msg;
+  while(1){
+    xQueueReceive(_tone_queue, &tone_msg, portMAX_DELAY);
+    switch(tone_msg.tone_cmd){
+      case TONE_START:
+        log_d("Task received from queue TONE_START: _pin=%d, frequency=%u Hz, duration=%u ms", tone_msg.pin, tone_msg.frequency, tone_msg.duration);
+
+        log_d("Setup LED controll on channel %d", _channel);
+        // ledcSetup(_channel, tone_msg.frequency, 11);
+        // ledcAttachPin(tone_msg.pin, _channel);
+        // ledcWrite(_channel, 1024);
+        ledcWriteTone(_channel, tone_msg.frequency);
+        ledcAttachPin(tone_msg.pin, _channel);
+
+        if(tone_msg.duration){
+          delay(tone_msg.duration);
+          ledcDetachPin(tone_msg.pin);
+          ledcWriteTone(_channel, 0);
+        }
+        break;
+
+      case TONE_END:
+        log_d("Task received from queue TONE_END: pin=%d", tone_msg.pin);
+        ledcDetachPin(tone_msg.pin);
+        ledcWriteTone(_channel, 0);
+        break;
+
+      case TONE_SET_CHANNEL:
+        log_d("Task received from queue TONE_SET_CHANNEL: channel=%d", tone_msg.channel);
+        _channel = tone_msg.channel;
+        break;
+
+      default: ; // do nothing
+    } // switch
+  } // infinite loop
+}
+
+static int tone_init(){
+  if(_tone_queue == NULL){
+    log_v("Creating tone queue");
+    _tone_queue = xQueueCreate(128, sizeof(tone_msg_t));
+    if(_tone_queue == NULL){
+      log_e("Could not create tone queue");
+      return 0; // ERR
+    }
+    log_v("Tone queue created");
+  }
+
+  if(_tone_task == NULL){
+    log_v("Creating tone task");
+    xTaskCreate(
+      tone_task, // Function to implement the task
+      "toneTask", // Name of the task
+      3500,  // Stack size in words
+      NULL,  // Task input parameter
+      1,  // Priority of the task
+      &_tone_task  // Task handle.
+      );
+    if(_tone_task == NULL){
+      log_e("Could not create tone task");
+      return 0; // ERR
+    }
+    log_v("Tone task created");
+  }
+  return 1; // OK
+}
+
+void setToneChannel(uint8_t channel){
+  log_d("channel=%d", channel);
+  if(tone_init()){
+    tone_msg_t tone_msg = {
+      .tone_cmd = TONE_SET_CHANNEL,
+      .channel = channel
+    };
+    xQueueSend(_tone_queue, &tone_msg, portMAX_DELAY);
+  }
+}
+
+void noTone(uint8_t _pin){
+  log_d("noTone was called");
+  if(tone_init()){
+    tone_msg_t tone_msg = {
+      .tone_cmd = TONE_END,
+      .pin = _pin
+    };
+    xQueueSend(_tone_queue, &tone_msg, portMAX_DELAY);
+  }
+}
+
+// parameters:
+// _pin - pin number which will output the signal
+// frequency - PWM frequency in Hz
+// duration - time in ms - how long will the signal be outputted.
+//   If not provided, or 0 you must manually call noTone to end output
+void tone(uint8_t _pin, unsigned int frequency, unsigned long duration){
+  log_d("_pin=%d, frequency=%u Hz, duration=%u ms", _pin, frequency, duration);
+  if(tone_init()){
+    tone_msg_t tone_msg = {
+      .tone_cmd = TONE_START,
+      .pin = _pin,
+      .frequency = frequency,
+      .duration = duration
+    };
+    xQueueSend(_tone_queue, &tone_msg, portMAX_DELAY);
+  }
+}