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

Leave a comment