Программирование ARM ESP PWM Audio Sun, December 07 2025  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.


ESP PWM Audio Печать
Добавил(а) microsin   

Библиотеку для генерации звука (функции pwm_audio_*) и пример её использования можно найти в ESP-IoT-Solution [2].

Функция генерации звука с помощью ШИМ (PWM audio) использует внутреннее периферийное устройство LEDC, которое имеется на борту большинства чипов семейства ESP32. Такой способ формирования звука не требует внешней микросхемы кодека audio, что используется во многих недорогих приложениях, когда не требуется высокое качество звука.

Функциональные возможности PWM audio:

● Позволяет формировать ШИМ на любом выводе порта GIPO.
● Поддерживает разрешающую способность ШИМ 8 .. 10 бит (PWM resolution).
● Поддерживает stereo.
● Поддерживает частоту выборок 8 .. 48 кГц (sampling rate).

pwm audio fig01

Рис. 1. Пример типовой структуры приложения на основе PWM audio.

Как это работает:

1. Сначала данные звука записываются в буфер (или подготавливаются на лету, runtime) таким образом, чтобы они удовлетворяли входным требованиям библиотеки (PWM input) включая сдвиг и смещение данных.
2. Данные посылаются в функцию ISR (Interrupt Service Routines) группы таймера (Timer Group) через кольцевой буфер (Ring Buffer).
3. Timer Group читает данные из Ring Buffer в соответствии с предварительно установленной частотой дискретизации выборок звука (sampling rate), и записывает данные в периферийное устройство LEDC.

Примечание: поскольку вывод звука осуществляется модуляцией ШИМ (PWM), необходим ФНЧ для получения звукового сигнала сглаженной формы. Во многих недорогих приложениях в случае частоты дискретизации свыше 15 кГц такой фильтр опускается, и сигнал ШИМ сразу подается на звуковой излучатель (динамик) с помощью ключей на транзисторах.

[Частота PWM]

Частота выходных импульсов PWM не может конфигурироваться напрямую, она должна быть вычислена конфигурированием количества бит разрешающей способности ШИМ (PWM resolution) как показано в следующей формуле:

pwm audio fig02

Здесь частота fAPB_CLK равна 80 МГц, а res_bits это количество бит (разрешающая способность) PWM. Когда разрешающая способность установлена LEDC_TIMER_10_BIT, частота PWM получится 78 кГц. Как известно, чем выше частота PWM, тем проще воспроизвести звуковой сигнал, и тем проще может быть ФНЧ для импульсов ШИМ. Однако по этой формуле видно, что увеличение частоты PWM означает снижение разрешающей способности, и наоборот, повышение разрешающей способности требует понижения частоты PWM. Здесь требуется найти разумный компромисс. Поэтому настройте эти параметры в соответствии с реальным сценарием использования вашего приложения.

Пример программы (полный рабочий пример проекта см. в папке examples/audio/wav_player/ каталога установки ESP-IoT-Solution [2]):

pwm_audio_config_t pac;
pac.duty_resolution    = LEDC_TIMER_10_BIT;
pac.gpio_num_left      = LEFT_CHANNEL_GPIO;
pac.ledc_channel_left  = LEDC_CHANNEL_0;
pac.gpio_num_right     = RIGHT_CHANNEL_GPIO;
pac.ledc_channel_right = LEDC_CHANNEL_1;
pac.ledc_timer_sel     = LEDC_TIMER_0;
pac.tg_num             = TIMER_GROUP_0;
pac.timer_num          = TIMER_0;
pac.ringbuf_len        = 1024 * 8;
pwm_audio_init(&pac); /**< Инициализация pwm audio */ pwm_audio_set_param(48000, 8, 2); /**< Установка частоты дискретизации (sample rate),
разрядности выборок и количества каналов */ pwm_audio_start(); /**< Запуск генерации звука */

while(1) {
//< Подготовка буфера звуковых данных, таких как например декодированный
// файл mp3/wav.

/**< Запись данных для проигрывания */
pwm_audio_write(audio_data, length, &written, 1000 / portTICK_PERIOD_MS); }

[Справочник по API PWM Audio]

Заголовочный файл: components/audio/pwm_audio/include/pwm_audio.h.

В следующей таблице приведена общая справка по функциям библиотеки PWM audio. Полное описание функций, используемых структур и типов см. в оригинальной документации [1].

Функция Описание
pwm_audio_init Инициализирует и конфигурирует pwm audio.
pwm_audio_deinit Отменяет инициализацию LEDC timer_group и порта вывода gpio.
pwm_audio_start Подготовительный запуск для генерации pwm audio.
pwm_audio_stop Остановка pwm audio.
pwm_audio_write Запись звуковых данных для проигрывания.
pwm_audio_set_param Установка параметров audio, наподобие pwm_audio_set_sample_rate(), но также устанавливает разрядность выборок звука.
pwm_audio_set_sample_rate Установка частоты дискретизации.
pwm_audio_set_volume Установка громкости звука.
pwm_audio_get_volume Получение текущей громкости звука.
pwm_audio_get_param Получение параметров pwm audio.
pwm_audio_get_status Получение статуса pwm audio.

Ниже показан пример использования компонента PWM Audio для формирования синусоидального непрерывного сигнала на частоте дискретизации 48000 Гц с помощью технологии прямого цифрового синтеза (Direct Digital Synthesis, DDS).

[Предварительные операции]

Для того, чтобы можно было использовать компонент PWM Audio, нужно:

1. Скачать ESP-IoT-Solution [2] где есть компонент PWM Audio.

2. Добавить переменную окружения IOT_SOLUTION_PATH, указывающую место на диске, куда вы скопировали ESP-IoT-Solution.

3. Добавить в проект зависимость pwm_audio:

$ idf.py add-dependency pwm_audio

После этого можно в проекте подключить заголовочный файл pwm_audio.h, и использовать API-функции pwm_audio_* библиотеки.

[Пример монофонического 8-битного DDS]

#include < stdint.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "task_priorities.h"
#include "pwm_audio.h"
#include "esp_log.h"

#define SND_BUFFER_SIZE 1024
#define SAMPLE_RATE 48000

static const char *TAG = "dds";

int8_t sinus_table[256] = {
0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45,
48, 51, 54, 57, 60, 63, 66, 68, 71, 74, 76, 79, 81, 84, 86, 89,
91, 93, 95, 98, 100, 102, 104, 106, 107, 109, 111, 112, 114, 115, 117, 118,
119, 120, 121, 122, 123, 124, 124, 125, 125, 126, 126, 126, 126, 126, 126, 126,
126, 126, 126, 126, 125, 125, 124, 124, 123, 122, 121, 120, 119, 118, 117, 115,
114, 112, 111, 109, 107, 106, 104, 102, 100, 98, 95, 93, 91, 89, 86, 84,
81, 79, 76, 74, 71, 68, 66, 63, 60, 57, 54, 51, 48, 45, 42, 39,
36, 33, 30, 27, 24, 21, 18, 15, 12, 9, 6, 3, 0, -3, -6, -9,
-12, -15, -18, -21, -24, -27, -30, -33, -36, -39, -42, -45, -48, -51, -54, -57,
-60, -63, -66, -68, -71, -74, -76, -79, -81, -84, -86, -89, -91, -93, -95, -98,
-100, -102, -104, -106, -107, -109, -111, -112, -114, -115, -117, -118, -119, -120, -121, -122,
-123, -124, -124, -125, -125, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126,
-125, -125, -124, -124, -123, -122, -121, -120, -119, -118, -117, -115, -114, -112, -111, -109,
-107, -106, -104, -102, -100, -98, -95, -93, -91, -89, -86, -84, -81, -79, -76, -74,
-71, -68, -66, -63, -60, -57, -54, -51, -48, -45, -42, -39, -36, -33, -30, -27,
-24, -21, -18, -15, -12, -9, -6, -3 };

static uint8_t soundbuf[2][SND_BUFFER_SIZE];

// Глобальные переменные для DDS
static float phase_accumulator;
static float phase_increment;

bool playing = false;

// Инициализация DDS
static void dds_init(float freqHz, uint32_t sampleRate) {
// ПРАВИЛЬНАЯ формула: сколько шагов таблицы на один семпл
phase_increment = 256.0f * freqHz / (float)sampleRate;
//printf("phase_increment: %.6f (freq: %.1f, samplerate: %lu)\n",
// phase_increment, freqHz, sampleRate); }

static void phase_next (void) {
phase_accumulator += phase_increment;
// Поддержание фазы в диапазоне [0, 256):
if (phase_accumulator >= 256.0f) {
phase_accumulator -= 256.0f;
} }

// Заполнение одного буфера семплами
static void fill_buffer(uint8_t *buffer, uint32_t buffer_size) {
// Простая генерация тона
for (uint32_t i = 0; i < buffer_size; i++) {
uint8_t phase_index = (uint32_t)(phase_accumulator + 0.5f) & 0xFF;
buffer[i] = sinus_table[phase_index];
phase_next();
} }

static void pwm_task(void *pvParameters) {
int freqHz = (int)pvParameters;
esp_err_t err;
size_t cnt;
// Конфигурация PWM audio
ESP_LOGI(TAG, "[APP] Startup..");
ESP_LOGI(TAG, "[APP] Free memory: %"PRIu32" bytes", esp_get_free_heap_size());
ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version());

pwm_audio_config_t pac = {
.duty_resolution = LEDC_TIMER_8_BIT,
.gpio_num_left = PWM_PIN,
.ledc_channel_left = LEDC_CHANNEL_0,
.gpio_num_right = -1, // моно
//.ledc_channel_right = LEDC_CHANNEL_1,
.ledc_timer_sel = LEDC_TIMER_0,
.ringbuf_len = SND_BUFFER_SIZE * 8,
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
.tg_num = TIMER_GROUP_0,
.timer_num = TIMER_0,
#endif
};

err = pwm_audio_init(&pac);
if (err != ESP_OK) {
ESP_LOGE(TAG, "pwm_audio_init failed: 0x%x", err);
vTaskDelete(NULL);
return;
}

// Установка параметров
err = pwm_audio_set_param(SAMPLE_RATE, 8, 1); // 48kHz, 8-bit, mono
if (err != ESP_OK) {
ESP_LOGE(TAG, "pwm_audio_set_param failed: 0x%x", err);
pwm_audio_deinit();
vTaskDelete(NULL);
return;
}

ESP_LOGI(TAG, "Starting PWM audio...");

// Запуск PWM audio
err = pwm_audio_start();
if (err != ESP_OK) {
ESP_LOGE(TAG, "pwm_audio_start failed: 0x%x", err);
pwm_audio_deinit();
vTaskDelete(NULL);
return;
}

// Даем время на полную инициализацию:
ESP_LOGI(TAG, "Waiting for PWM audio to stabilize...");
vTaskDelay(pdMS_TO_TICKS(500));

// Инициализация DDS
dds_init(freqHz, SAMPLE_RATE);

// Предварительное заполнение обоих буферов
fill_buffer(soundbuf[0], SND_BUFFER_SIZE);
fill_buffer(soundbuf[1], SND_BUFFER_SIZE);

// Отправляем первый буфер
uint8_t current_buf = 0;
err = pwm_audio_write(soundbuf[current_buf], SND_BUFFER_SIZE, &cnt, pdMS_TO_TICKS(1000));
if (err == ESP_OK) {
ESP_LOGI(TAG, "First buffer sent successfully");
}
else
{
ESP_LOGE(TAG, "Failed to send initial buffer");
pwm_audio_stop();
pwm_audio_deinit();
vTaskDelete(NULL);
return;
}

// Отправляем второй буфер
current_buf = 1;
err = pwm_audio_write(soundbuf[current_buf], SND_BUFFER_SIZE, &cnt, pdMS_TO_TICKS(1000));
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to send second buffer: 0x%x", err);
pwm_audio_stop();
pwm_audio_deinit();
vTaskDelete(NULL);
return;
}

ESP_LOGI(TAG, "Audio streaming started successfully");

// Основной цикл с двойной буферизацией
while(playing) {
// Переключаемся на следующий буфер
current_buf = (current_buf + 1) & 1;

// Заполняем буфер, который будет отправлен следующим
fill_buffer(soundbuf[current_buf], SND_BUFFER_SIZE);

// Отправляем заполненный буфер
err = pwm_audio_write(soundbuf[current_buf], SND_BUFFER_SIZE, &cnt, pdMS_TO_TICKS(1000));
if (err != ESP_OK) {
ESP_LOGE(TAG, "pwm_audio_write error: 0x%x", err);
break;
}
}

// Очистка при ошибке или отстановке
pwm_audio_stop();
pwm_audio_deinit();
vTaskDelete(NULL); }

void play_start(int freqHz) {
phase_accumulator = 0;
ESP_LOGI(TAG, "Creating PWM task for frequency: %d Hz", freqHz);
xTaskCreate(pwm_task, "pwm_task", 4096, (void*)freqHz, PRIORITY_SOUND, NULL); }

Запуск генерации 1000 Гц:

playing = true;
play_start(1000);

Примечание: остановить генерацию можно, если сбросить в false флаг playing. Пример проигрывания стереофонического файла можно посмотреть в папке examples\audio\wav_player\ каталога установки ESP-IoT-Solution (обычно папка esp-iot-solution).

[Ссылки]

1. ESP PWM Audio site:docs.espressif.com.
2. ESP-IoT-Solution: быстрый старт.

 

Добавить комментарий


Защитный код
Обновить

Top of Page