ESP32 and PWM – Part 2

In this second part of my look into the ESP32 and PWM I’ve updated the code to expand to several channels to make a sort of (fixed) simple signal generator.

  • Part 1 – All the theory and research around PWM and the ESP32.
  • Part 2 – Generating different waveforms on multiple channels.
  • Part 3 – Introducing analog control and frequency modulation.

Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

If you are new to microcontrollers, see the Getting Started pages.

Parts list

  • ESP32 WROOM Module
  • 2x 1kΩ resistor
  • 1x 10uF electrolytic capacitor
  • 1x 100nF capacitor
  • 1x TRS socket
  • Breadboard and jumper wires

The Circuit

This is using exactly the same circuit as in part 1, but I’m now configuring PWM on the following ESP32 pins for convenience: GPIO 13, 12, 14, 27.

I’ve not done anything special to combine the outputs. For these tests, I’m just moving the wire that connects a PWM pin to the output filter circuit, so I can only have one output at a time.

The Code

I’ve updated the code to maintain 4 accumulators and increment counters and it now supports four different wavetables.

The code is still using a simple, fixed frequency, but I have included an option to provide a set of frequency multipliers to each output if I want to experiment with producing harmonics. The multipliers and wavetables are pre-configured at the start of the code, but could relatively easily be made dynamically configurable in response to GPIO inputs.

#define NUM_PWM_PINS 4
int pwm_pins[NUM_PWM_PINS] = {13, 12, 14, 27};

uint16_t acc[NUM_PWM_PINS];
uint16_t inc[NUM_PWM_PINS];

uint16_t mul[NUM_PWM_PINS] = {1,1,1,1};
uint8_t *pWT[NUM_PWM_PINS] = {sinedata, sawdata, tridata, sqdata};

The above configuration sets up each different wave on one of each of the four GPIO PWM output pins. The following configuration would use sine waves on all pins but giving the first four harmonics:

uint16_t mul[NUM_PWM_PINS] = {1,2,3,4};
uint8_t *pWT[NUM_PWM_PINS] = {sinedata, sinedata, sinedata, sinedata};

Recall, I’m using a 256 value, 8-bit wavetable and set everything up for a 10MHz timer triggering to give me 32768Hz sample rate, with 8-bit resolution for the PWM, giving me a PWM frequency of just over 313kHz.

I’ve now added (calculated) wavetables for saw, triangle and square wave outputs in addition to the pre-calculated sine table. The new wavetables are calculated as follows:

uint8_t sawdata[NUM_SAMPLES];
uint8_t tridata[NUM_SAMPLES];
uint8_t sqdata[NUM_SAMPLES];

void setupWavetables () {
for (int i=0; i<NUM_SAMPLES; i++) {
sawdata[i] = i;
if (i<NUM_SAMPLES/2) {
tridata[i] = i*2;
sqdata[i] = 255;
} else {
tridata[i] = 255-(i-128)*2;
sqdata[i] = 0;
}
}
}

To glue it all together, I’ve updated my ddsOutput routine to take a “channel” number and then in the PWM interrupt routine I just call ddsOutput for all channels.

void ddsUpdate (int ch) {
acc[ch] += inc[ch];
ledcWrite (pwm_pins[ch], pWT[ch][acc[ch] >> 8]);
}

void ARDUINO_ISR_ATTR timerIsr (void) {
for (int i=0; i<NUM_PWM_PINS; i++) {
ddsUpdate(i);
}
}

void setup () {
for (int i=0; i<NUM_PWM_PINS; i++) {
ledcAttach(pwm_pins[i], PWM_FREQUENCY, PWM_RESOLUTION);
}
}

The sample code still uses a fixed test base frequency 440Hz tone for now.

Find it on GitHub here.

Closing Thoughts

I thought this would be a lot more complicated than it was, but I guess all the essential details had been worked out last time.

I’m now wondering if I want to provide some way of mixing the PWM outputs or if that is best done in software. It would be nice to add some IO to control everything, but I think that is probably the stage where it would be worth putting together some kind of ESP32 audio experimenter PCB!

Kevin

7 thoughts on “ESP32 and PWM – Part 2

    1. Do you mean change the PWM carrier frequency to 20kHz or to get it to output a 20kHz signal?

      Either way, I’m not sure I know directly I’m afraid, but you should be able to re-run the calculations I described to hopefully get you somewhere near.

      I do suspect however that 20kHz would be too slow for useful audio PWM and I wonder if it might be too fast for the provided PWM carrier frequencies…

      Kevin

      Like

      1. I’m not sure a 32kHz sample rate would be able to go that high, so you’d have to do something quite different, I think. I’m only interested in audio frequencies so haven’t really looked into anything beyond that I’m afraid.

        Kevin

        Like

      2. Can you add sine wave and control buttons for amplitude and frequency to this program ?

        include <driver/ledc.h>

        void setup() {ledc_timer_config_t timerConfig = { .speed_mode = LEDC_LOW_SPEED_MODE, .duty_resolution = LEDC_TIMER_10_BIT, .timer_num = LEDC_TIMER_0, .freq_hz = 9975, .clk_cfg = LEDC_AUTO_CLK }; ledc_timer_config(&timerConfig); ledc_channel_config_t channelConfig1 = { .gpio_num = 12, .speed_mode = LEDC_LOW_SPEED_MODE, .channel = LEDC_CHANNEL_0, .timer_sel = LEDC_TIMER_0, .duty = 512, ) .hpoint = 0 а) }; ledc_channel_config(&channelConfig1); ledc_channel_config_t channelConfig2 = { .gpio_num = 14, .speed_mode = LEDC_LOW_SPEED_MODE, .channel = LEDC_CHANNEL_1, .timer_sel = LEDC_TIMER_0, .duty = 512, ) .hpoint = 0 // 0 deg }; ledc_channel_config(&channelConfig2); ledc_channel_config_t channelConfig3 = { .gpio_num = 27, .speed_mode = LEDC_LOW_SPEED_MODE, .channel = LEDC_CHANNEL_2, .timer_sel = LEDC_TIMER_0, .duty = 512, .hpoint = 256 // 90 deg }; ledc_channel_config(&channelConfig3);

        //++++++++++++++++++++++++++++
        // add sine wave and control buttons for amplitude and frequency.
        //++++++++++++++++++++++++

        }
        void loop() {}

        Liked by 1 person

  1. Sorry, I’m happy to try to help people work through my own code, but its not really possible for me to be writing code for others on demand as I have too many of my own projects on the go 🙂

    To be honest, I’m not entirely sure I understand what you’re attempting with that code. Are you sure it compiles? When I was doing PWM I just used higher level functions as per my provided code (see part 1).

    Kevin

    Like

Leave a reply to Kevin Cancel reply