Arduino Tone for ESP32

Arduino has a built-in tone() library which allows you to send a PWM frequency at 50% duty cycle to a specific pin in order to generate a tone on a piezoelectric buzzer with an optional duration.

tone(pin, frequency)
tone(pin, frequency, duration)

This functionality is famously unavailable in Espressif’s arduino-esp32 library and members of the community have found various work-arounds such as using the native LED Control functions to generate PWM signals.

However, looking more closely at arduino-esp32 library, not only has Espressif provided a clean API for generating tones, they’ve provided an interface for generating specific PWM frequencies for specific notes on the chromatic scale in different octaves.

double ledcWriteNote(uint8_t chan, note_t note, uint8_t octave){
  const uint16_t noteFrequencyBase[12] = {
  //   C        C#       D        Eb       E        F       F#        G       G#        A       Bb        B
      4186,    4435,    4699,    4978,    5274,    5588,    5920,    6272,    6645,    7040,    7459,    7902
  };

  if(octave > 8 || note >= NOTE_MAX){
      return 0;
  }
  double noteFreq =  (double)noteFrequencyBase[note] / (double)(1 << (8-octave));
  return ledcWriteTone(chan, noteFreq);
}

Although not directly compatible with Arduino’s tone(), the function provides a dedicated interface for producing named frequencies out of the box via the note_t type.

typedef enum {
    NOTE_C, NOTE_Cs, NOTE_D, NOTE_Eb, NOTE_E, NOTE_F, NOTE_Fs, NOTE_G, NOTE_Gs, NOTE_A, NOTE_Bb, NOTE_B, NOTE_MAX
} note_t;

Example Usage

I’ll take an example from @lbernstone’s Tone32 library. Their library is a great solution for providing cross-compatibility between code written for an Arduino and code written for an ESP32.

void loop() {
  tone(BUZZER_PIN, NOTE_C4, 500, BUZZER_CHANNEL);
  noTone(BUZZER_PIN, BUZZER_CHANNEL);
  tone(BUZZER_PIN, NOTE_D4, 500, BUZZER_CHANNEL);
  noTone(BUZZER_PIN, BUZZER_CHANNEL);
  tone(BUZZER_PIN, NOTE_E4, 500, BUZZER_CHANNEL);
  noTone(BUZZER_PIN, BUZZER_CHANNEL);
  tone(BUZZER_PIN, NOTE_F4, 500, BUZZER_CHANNEL);
  noTone(BUZZER_PIN, BUZZER_CHANNEL);
  tone(BUZZER_PIN, NOTE_G4, 500, BUZZER_CHANNEL);
  noTone(BUZZER_PIN, BUZZER_CHANNEL);
  tone(BUZZER_PIN, NOTE_A4, 500, BUZZER_CHANNEL);
  noTone(BUZZER_PIN, BUZZER_CHANNEL);
  tone(BUZZER_PIN, NOTE_B4, 500, BUZZER_CHANNEL);
  noTone(BUZZER_PIN, BUZZER_CHANNEL);
}

In this example, a major C scale is played while holding each note for 0.5 seconds. The BUZZER_CHANNEL argument is optional. In the case of an ESP32, there are 16 PWM channels which can generate independent waveforms which need to explicitly assigned to any PWM-capable pin.

Here is the equivalent scale programmed using ledcWriteNote().

void loop() {
  ledcAttachPin(BUZZER_PIN, BUZZER_CHANNEL);
  ledcWriteNote(BUZZER_CHANNEL, NOTE_C, 4);
  delay(500);
  ledcWriteNote(BUZZER_CHANNEL, NOTE_D, 4);
  delay(500);
  ledcWriteNote(BUZZER_CHANNEL, NOTE_E, 4);
  delay(500);
  ledcWriteNote(BUZZER_CHANNEL, NOTE_F, 4);
  delay(500);
  ledcWriteNote(BUZZER_CHANNEL, NOTE_G, 4);
  delay(500);
  ledcWriteNote(BUZZER_CHANNEL, NOTE_A, 4);
  delay(500);
  ledcWriteNote(BUZZER_CHANNEL, NOTE_B, 4);
  delay(500);
  ledcDetachPin(pin)
}

The tradeoff here is that this is not cross-compatible with an Arduino, however, it means not having to import an external library or having to define note frequencies yourself. Similar to Arduino’s tone(), each signal is produced at 50% duty cycle. Under the hood, both implementations are calling ledcWriteTone.

References

· hardware, esp32, arduino, firmware