Skip to content

Commit 8db500f

Browse files
committed
Draft PWMComplementaryDriverESP32
1 parent c081206 commit 8db500f

File tree

1 file changed

+190
-0
lines changed

1 file changed

+190
-0
lines changed
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
2+
#pragma once
3+
#ifdef ESP32
4+
#include "AudioTools/CoreAudio/AudioPWM/PWMDriverBase.h"
5+
#include "driver/mcpwm.h"
6+
7+
namespace audio_tools {
8+
9+
/**
10+
* @brief Information for a PIN
11+
* @author Phil Schatzmann
12+
* @copyright GPLv3
13+
*/
14+
struct PinInfoESP32Compl {
15+
int gpio_high; // high-side pin (PWMxA)
16+
int gpio_low; // low-side pin (PWMxB)
17+
mcpwm_unit_t unit; // 0..1
18+
mcpwm_timer_t timer; // 0..2
19+
};
20+
21+
/**
22+
* @brief Audio output to PWM pins for the ESP32. The ESP32 supports up to 16
23+
* channels.
24+
* @ingroup platform
25+
* @author Phil Schatzmann
26+
* @copyright GPLv3
27+
*/
28+
29+
class PWMComplementaryDriverESP32 : public DriverPWMBase {
30+
public:
31+
// friend void pwm_callback(void*ptr);
32+
33+
PWMComplementaryDriverESP32() { TRACED(); }
34+
35+
// Ends the output
36+
virtual void end() {
37+
TRACED();
38+
timer.end();
39+
is_timer_started = false;
40+
for (int j = 0; j < pins.size(); j++) {
41+
mcpwm_stop(pins[j].unit, pins[j].timer);
42+
}
43+
deleteBuffer();
44+
}
45+
46+
/// when we get the first write -> we activate the timer to start with the
47+
/// output of data
48+
virtual void startTimer() {
49+
if (!timer) {
50+
TRACEI();
51+
timer.begin(pwm_callback, effectiveOutputSampleRate(), HZ);
52+
actual_timer_frequency = effectiveOutputSampleRate();
53+
is_timer_started = true;
54+
}
55+
}
56+
57+
/// Setup complementary MCPWM
58+
virtual void setupPWM() {
59+
// frequency is driven by selected resolution
60+
if (audio_config.pwm_frequency == 0) {
61+
audio_config.pwm_frequency = frequency(audio_config.resolution) * 1000;
62+
}
63+
if (audio_config.channels > maxChannels()) {
64+
LOGE("Only %d complementary channels supported", maxChannels());
65+
audio_config.channels = maxChannels();
66+
}
67+
bool has_pairs = audio_config.pins().size() >= (size_t)(audio_config.channels * 2);
68+
if (!has_pairs) {
69+
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());
70+
}
71+
pins.resize(audio_config.channels);
72+
for (int j = 0; j < audio_config.channels; j++) {
73+
pins[j].unit = (mcpwm_unit_t)(j / 3);
74+
pins[j].timer = (mcpwm_timer_t)(j % 3);
75+
if (pins[j].unit > MCPWM_UNIT_1) { LOGE("Too many channels for MCPWM: %d", j); break; }
76+
if (has_pairs) {
77+
pins[j].gpio_high = audio_config.pins()[j*2];
78+
pins[j].gpio_low = audio_config.pins()[j*2 + 1];
79+
} else {
80+
pins[j].gpio_high = audio_config.pins()[j];
81+
pins[j].gpio_low = pins[j].gpio_high + 1;
82+
}
83+
mcpwm_io_signals_t sigA = (mcpwm_io_signals_t)(MCPWM0A + (pins[j].timer * 2));
84+
mcpwm_io_signals_t sigB = (mcpwm_io_signals_t)(MCPWM0B + (pins[j].timer * 2));
85+
esp_err_t err = mcpwm_gpio_init(pins[j].unit, sigA, pins[j].gpio_high);
86+
if (err != ESP_OK) LOGE("mcpwm_gpio_init high error=%d", (int)err);
87+
err = mcpwm_gpio_init(pins[j].unit, sigB, pins[j].gpio_low);
88+
if (err != ESP_OK) LOGE("mcpwm_gpio_init low error=%d", (int)err);
89+
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;
90+
err = mcpwm_init(pins[j].unit, pins[j].timer, &cfg);
91+
if (err != ESP_OK) LOGE("mcpwm_init error=%d", (int)err);
92+
if (audio_config.dead_time_us > 0) {
93+
uint32_t dead_ticks = audio_config.dead_time_us * 80u; // 80MHz APB
94+
uint32_t period_ticks = 80000000UL / audio_config.pwm_frequency;
95+
if (dead_ticks * 2 >= period_ticks) dead_ticks = period_ticks / 4;
96+
if (dead_ticks > 0) {
97+
err = mcpwm_deadtime_enable(pins[j].unit, pins[j].timer, MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE, dead_ticks, dead_ticks);
98+
if (err != ESP_OK) LOGE("deadtime_enable error=%d", (int)err);
99+
}
100+
}
101+
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);
102+
}
103+
}
104+
105+
106+
/// Setup ESP32 timer with callback
107+
virtual void setupTimer() {
108+
timer.setCallbackParameter(this);
109+
timer.setIsSave(false);
110+
111+
if (actual_timer_frequency != effectiveOutputSampleRate()) {
112+
timer.end();
113+
startTimer();
114+
}
115+
}
116+
117+
/// write a pwm value to the indicated channel. The max value depends on the
118+
/// resolution
119+
virtual void pwmWrite(int channel, int value) {
120+
if (channel < 0 || channel >= pins.size()) return;
121+
int duty = (int)((int64_t)value * 100 / maxOutputValue());
122+
if (duty < 0) duty = 0; else if (duty > 100) duty = 100;
123+
mcpwm_set_duty(pins[channel].unit, pins[channel].timer, MCPWM_OPR_A, duty);
124+
mcpwm_set_duty_type(pins[channel].unit, pins[channel].timer, MCPWM_OPR_A, MCPWM_DUTY_MODE_0);
125+
if (audio_config.dead_time_us == 0) {
126+
// software complementary: invert
127+
mcpwm_set_duty(pins[channel].unit, pins[channel].timer, MCPWM_OPR_B, 100 - duty);
128+
mcpwm_set_duty_type(pins[channel].unit, pins[channel].timer, MCPWM_OPR_B, MCPWM_DUTY_MODE_0);
129+
}
130+
}
131+
132+
protected:
133+
Vector<PinInfoESP32Compl> pins;
134+
TimerAlarmRepeating timer;
135+
uint32_t actual_timer_frequency = 0;
136+
137+
/// provides the max value for the indicated resulution
138+
int maxUnsignedValue(int resolution) { return pow(2, resolution); }
139+
140+
virtual int maxChannels() { return 6; };
141+
142+
/// provides the max value for the configured resulution
143+
virtual int maxOutputValue() {
144+
return maxUnsignedValue(audio_config.resolution);
145+
}
146+
147+
/// determiens the PWM frequency based on the requested resolution
148+
float frequency(int resolution) {
149+
// On ESP32S2 and S3, the frequncy seems off by a factor of 2
150+
#if defined(ESP32S2) || defined(ESP32S3)
151+
switch (resolution) {
152+
case 7:
153+
return 312.5;
154+
case 8:
155+
return 156.25;
156+
case 9:
157+
return 78.125;
158+
case 10:
159+
return 39.0625;
160+
case 11:
161+
return 19.53125;
162+
}
163+
return 312.5;
164+
#else
165+
switch (resolution) {
166+
case 8:
167+
return 312.5;
168+
case 9:
169+
return 156.25;
170+
case 10:
171+
return 78.125;
172+
case 11:
173+
return 39.0625;
174+
}
175+
return 312.5;
176+
#endif
177+
}
178+
179+
/// timer callback: write the next frame to the pins
180+
static void pwm_callback(void *ptr) {
181+
PWMComplementaryDriverESP32 *drv = (PWMComplementaryDriverESP32 *)ptr;
182+
if (drv != nullptr) {
183+
drv->playNextFrame();
184+
}
185+
}
186+
};
187+
188+
} // namespace audio_tools
189+
190+
#endif

0 commit comments

Comments
 (0)