arduino-audio-tools
Loading...
Searching...
No Matches
PWMComplementaryDriverESP32.h
1
2#pragma once
3#ifdef ESP32
4#include "AudioTools/CoreAudio/AudioPWM/PWMDriverBase.h"
5#include "driver/mcpwm.h"
6
7namespace audio_tools {
8
20 int gpio_high; // high-side pin (PWMxA)
21 int gpio_low; // low-side pin (PWMxB)
22 mcpwm_unit_t unit; // 0..1
23 mcpwm_timer_t timer; // 0..2
24};
25
83 public:
84 // friend void pwm_callback(void*ptr);
85
86 PWMComplementaryDriverESP32() { TRACED(); }
87
88 // Ends the output
89 virtual void end() {
90 TRACED();
91 timer.end();
92 is_timer_started = false;
93 for (int j = 0; j < pins.size(); j++) {
94 mcpwm_stop(pins[j].unit, pins[j].timer);
95 }
96 deleteBuffer();
97 }
98
101 virtual void startTimer() {
102 if (!timer) {
103 TRACEI();
104 timer.begin(pwm_callback, effectiveOutputSampleRate(), HZ);
105 actual_timer_frequency = effectiveOutputSampleRate();
106 is_timer_started = true;
107 }
108 }
109
111 virtual void setupPWM() {
112 // frequency is driven by selected resolution
113 if (audio_config.pwm_frequency == 0) {
114 audio_config.pwm_frequency = frequency(audio_config.resolution) * 1000;
115 }
116 if (audio_config.channels > maxChannels()) {
117 LOGE("Only %d complementary channels supported", maxChannels());
118 audio_config.channels = maxChannels();
119 }
120 bool has_pairs = audio_config.pins().size() >= (size_t)(audio_config.channels * 2);
121 if (!has_pairs) {
122 LOGW("Expected %d pins for %d complementary channels, got %d - assuming consecutive pin+1 as low-side", audio_config.channels*2, audio_config.channels, audio_config.pins().size());
123 }
124 pins.resize(audio_config.channels);
125 for (int j = 0; j < audio_config.channels; j++) {
126 pins[j].unit = (mcpwm_unit_t)(j / 3);
127 pins[j].timer = (mcpwm_timer_t)(j % 3);
128 if (pins[j].unit > MCPWM_UNIT_1) { LOGE("Too many channels for MCPWM: %d", j); break; }
129 if (has_pairs) {
130 pins[j].gpio_high = audio_config.pins()[j*2];
131 pins[j].gpio_low = audio_config.pins()[j*2 + 1];
132 } else {
133 pins[j].gpio_high = audio_config.pins()[j];
134 pins[j].gpio_low = pins[j].gpio_high + 1;
135 }
136 mcpwm_io_signals_t sigA = (mcpwm_io_signals_t)(MCPWM0A + (pins[j].timer * 2));
137 mcpwm_io_signals_t sigB = (mcpwm_io_signals_t)(MCPWM0B + (pins[j].timer * 2));
138 esp_err_t err = mcpwm_gpio_init(pins[j].unit, sigA, pins[j].gpio_high);
139 if (err != ESP_OK) LOGE("mcpwm_gpio_init high error=%d", (int)err);
140 err = mcpwm_gpio_init(pins[j].unit, sigB, pins[j].gpio_low);
141 if (err != ESP_OK) LOGE("mcpwm_gpio_init low error=%d", (int)err);
142 mcpwm_config_t cfg; cfg.frequency = audio_config.pwm_frequency; cfg.cmpr_a = 0; cfg.cmpr_b = 0; cfg.counter_mode = MCPWM_UP_COUNTER; cfg.duty_mode = MCPWM_DUTY_MODE_0;
143 err = mcpwm_init(pins[j].unit, pins[j].timer, &cfg);
144 if (err != ESP_OK) LOGE("mcpwm_init error=%d", (int)err);
145 if (audio_config.dead_time_us > 0) {
146 uint32_t dead_ticks = audio_config.dead_time_us * 80u; // 80MHz APB
147 uint32_t period_ticks = 80000000UL / audio_config.pwm_frequency;
148 if (dead_ticks * 2 >= period_ticks) dead_ticks = period_ticks / 4;
149 if (dead_ticks > 0) {
150 err = mcpwm_deadtime_enable(pins[j].unit, pins[j].timer, MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE, dead_ticks, dead_ticks);
151 if (err != ESP_OK) LOGE("deadtime_enable error=%d", (int)err);
152 }
153 }
154 LOGI("Complementary PWM ch=%d unit=%d timer=%d high=%d low=%d freq=%u dead_us=%u", j,(int)pins[j].unit,(int)pins[j].timer,pins[j].gpio_high,pins[j].gpio_low,(unsigned)audio_config.pwm_frequency,(unsigned)audio_config.dead_time_us);
155 }
156 }
157
158
160 virtual void setupTimer() {
161 timer.setCallbackParameter(this);
162 timer.setIsSave(false);
163
164 if (actual_timer_frequency != effectiveOutputSampleRate()) {
165 timer.end();
166 startTimer();
167 }
168 }
169
172 virtual void pwmWrite(int channel, int value) {
173 if (channel < 0 || channel >= pins.size()) return;
174 int duty = (int)((int64_t)value * 100 / maxOutputValue());
175 if (duty < 0) duty = 0; else if (duty > 100) duty = 100;
176 mcpwm_set_duty(pins[channel].unit, pins[channel].timer, MCPWM_OPR_A, duty);
177 mcpwm_set_duty_type(pins[channel].unit, pins[channel].timer, MCPWM_OPR_A, MCPWM_DUTY_MODE_0);
178 if (audio_config.dead_time_us == 0) {
179 // software complementary: invert
180 mcpwm_set_duty(pins[channel].unit, pins[channel].timer, MCPWM_OPR_B, 100 - duty);
181 mcpwm_set_duty_type(pins[channel].unit, pins[channel].timer, MCPWM_OPR_B, MCPWM_DUTY_MODE_0);
182 }
183 }
184
185 protected:
188 uint32_t actual_timer_frequency = 0;
189
191 int maxUnsignedValue(int resolution) { return pow(2, resolution); }
192
193 virtual int maxChannels() { return 6; };
194
196 virtual int maxOutputValue() {
197 return maxUnsignedValue(audio_config.resolution);
198 }
199
201 float frequency(int resolution) {
202// On ESP32S2 and S3, the frequncy seems off by a factor of 2
203#if defined(ESP32S2) || defined(ESP32S3)
204 switch (resolution) {
205 case 7:
206 return 312.5;
207 case 8:
208 return 156.25;
209 case 9:
210 return 78.125;
211 case 10:
212 return 39.0625;
213 case 11:
214 return 19.53125;
215 }
216 return 312.5;
217#else
218 switch (resolution) {
219 case 8:
220 return 312.5;
221 case 9:
222 return 156.25;
223 case 10:
224 return 78.125;
225 case 11:
226 return 39.0625;
227 }
228 return 312.5;
229#endif
230 }
231
233 static void pwm_callback(void *ptr) {
235 if (drv != nullptr) {
236 drv->playNextFrame();
237 }
238 }
239};
240
241} // namespace audio_tools
242
243#endif
Base Class for all PWM drivers.
Definition PWMDriverBase.h:114
virtual int effectiveOutputSampleRate()
Provides the effective sample rate.
Definition PWMDriverBase.h:246
void playNextFrame()
writes the next frame to the output pins
Definition PWMDriverBase.h:274
Audio output to PWM pins for the ESP32. The ESP32 supports up to 16 channels.
Definition PWMComplementaryDriverESP32.h:82
float frequency(int resolution)
determiens the PWM frequency based on the requested resolution
Definition PWMComplementaryDriverESP32.h:201
virtual void setupPWM()
Setup complementary MCPWM.
Definition PWMComplementaryDriverESP32.h:111
virtual int maxOutputValue()
provides the max value for the configured resulution
Definition PWMComplementaryDriverESP32.h:196
virtual void startTimer()
Definition PWMComplementaryDriverESP32.h:101
int maxUnsignedValue(int resolution)
provides the max value for the indicated resulution
Definition PWMComplementaryDriverESP32.h:191
virtual void pwmWrite(int channel, int value)
Definition PWMComplementaryDriverESP32.h:172
static void pwm_callback(void *ptr)
timer callback: write the next frame to the pins
Definition PWMComplementaryDriverESP32.h:233
virtual void setupTimer()
Setup ESP32 timer with callback.
Definition PWMComplementaryDriverESP32.h:160
Common Interface definition for TimerAlarmRepeating.
Definition AudioTimer.h:25
Vector implementation which provides the most important methods as defined by std::vector....
Definition Vector.h:21
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition AudioCodecsBase.h:10
uint16_t channels
Number of channels: 2=stereo, 1=mono.
Definition AudioTypes.h:57
uint8_t resolution
Only used by ESP32: must be between 8 and 11 -> drives pwm frequency // 20,000Hz (not used by ESP32)
Definition PWMDriverBase.h:48
uint32_t pwm_frequency
additinal info which might not be used by all processors
Definition PWMDriverBase.h:46
uint16_t dead_time_us
Dead time in microseconds for symmetric PWM (ESP32 only)
Definition PWMDriverBase.h:53
Mapping information for one complementary PWM audio channel.
Definition PWMComplementaryDriverESP32.h:19