Воспроизведение аудиофайлов с помощью Ардуино без сторонних плееров и SD-карт

В этой статье пойдет речь о воспроизведении аудиофайлов с помощью одной лишь Ардуино.

Иногда в проектах, построенных на основе микроконтроллера arduino присутствует необходимость в воспроизведении неких аудиофайлов. Если эти файлы являются набором нот или частотой колебания, то нет проблем – в примерах самой arduino IDE есть несколько примеров проигрывания нот и «пищания» на разных тонах.

Если же нам нужно воспроизвести речь или музыкальную запись, встроенных средств будет недостаточно. Самым банальным решением будет поставить сторонний плеер и отправлять ему команды на включение того или иного трека, есть еще вариант подключить CD – карту и читать файлы с нее (примеров этого в интернете много).

Эти варианты подходят для воспроизведения большого числа треков разных размеров, но если вам нужно воспроизводить небольшие аудиофайлы, и вы не хотите тратиться на сторонние модули, то можно залить их в саму плату arduino. Так как же это сделать? Читайте далее!

Для начала необходимо скачать специальную программу wav2asm_c для преобразования аудиофайла в код, который будет читать и воспроизводить arduino.

Теперь нужно выбрать подходящий аудиофайл. При его выборе нужно понимать, что памяти у Arduino мало и большой трек вы туда не сможете засунуть, так же не стоит надеяться на высокое качество звука (для того, чтобы аудиофайл влез во встроенную память Arduino его придется ужать, для обычной речи это незаметно, а вот на некоторой музыке скажется значительно).

Вот сайт с простыми фразами на английском: http://ru.nemoapps.com/phrasebooks/english они как раз укладываются в размеры памяти. Возьмем с него один файл «That's good» и на его примере рассмотрим весь процесс.

Хорошо, файл нашли. Скачиваем его. Программе wav2asm_c нужен специальный формат (.wav). Для перевода скачанного файла воспользуемся онлайн конвертером https://audio.online-convert.com/ru/convert-to-wav.

Перетаскиваем скачанный аудиофайл в поле с облачком.

Выбираем настройки как на скриншоте ниже и жмем кнопочку «Начать конвертирование». В графе каналов можно выбрать и стерео, но тогда вес сконвертированного файла будет раза в два больше.

Справка. Для тех, кто не в курсе: моно это одна аудиодорожка, а стерео – две, следовательно, для воспроизведения моно необходим только один динамик, а для стерео два независимых динамика (можно воспроизводить и на одном, но тогда аудиодорожка все равно сольется в моно).

Поскольку мы используем только один динамик, разницы между стерео и моно не почувствуем, так что лучше сэкономить память.

После окончания конвертирования скачиваем получивший файл.

Для удобства использования рекомендую создать где-нибудь отдельную директорию для программы конвертера и ваших вайлов, с которыми она будет работать. Помещаем туда скачанный файл.

Затем открываем программу wav2asm_c, выбираем нужный файл, ставим настройки как на скриншоте (название роли играть не будет, поэтому там как хотите, с числом байт в строке тоже самое) и жмем кнопочку создать.

Получим следующий файл:

Его можно открыть с помощью Блокнота винды, но я предпочитаю Notepad++ (скачать с официального сайта https://notepad-plus-plus.org ).

В папке проекта программы нужно создать библиотеку – файл с разрешением «.h» и названием sounddata.

Только после этого можно запускать Arduino IDE, созданный файл откроется автоматически рядом со скетчем. Заходим в него и прописываем следующее:

const int sounddata_length = ;

const unsigned char sounddata_data[] PROGMEN = {

};

Важно! Все названия и типы данных должны в точности совпадать со скриншотом.

Переменной sounddata_length присваиваем длину массива, хранящего запись аудиофайла, длину массива так же указываем явно. Она записана в файле, сгенерированном программой wav2asm_c между /* и */ .

Теперь копируем содержание массива из сгенерированного файла в библиотеку. Библиотеку можно редактировать не только через Arduino IDE, но и через другие текстовые редакторы, поддерживающие данное расширение файла, тут уже кому как удобнее.

Теперь остается лишь подключить динамик к Arduino и залить скетч. Скачать и скопировать скетч можно ниже:

#include <stdint.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include "sounddata.h"
#define SAMPLE_RATE 8000 // скорость воспроизведения

int speakerPin = 11;
volatile uint16_t sample;
byte lastSample;

// далее идут команды не использующие Ардуиновскую оболочку, особо не вникайте.

// Это называется на 8000 Гц для загрузки следующего образца.
ISR(TIMER1_COMPA_vect) {
    if (sample >= sounddata_length) {
        if (sample == sounddata_length + lastSample) {
            stopPlayback();
        }
        else {
            // Рампа вниз до нуля, чтобы уменьшить щелчок в конце воспроизведения.
            OCR2A = sounddata_length + lastSample - sample;
        }
    }
    else {
          OCR2A = pgm_read_byte(&sounddata_data[sample]);
          }
    ++sample;
}

void startPlayback()
{
    // Настраиваем 2-ой таймер для использования широтно-импульсной модуляции на динамике

    // Используем внутренние часы
    ASSR &= ~(_BV(EXCLK) | _BV(AS2));

    // Устанавливаем быстрый режим PWM
    TCCR2A |= _BV(WGM21) | _BV(WGM20);
    TCCR2B &= ~_BV(WGM22);

    // Не используйте ШИМ на контакте OC2A
    // На Arduino это вывод 11.
    TCCR2A = (TCCR2A | _BV(COM2A1)) & ~_BV(COM2A0);
    TCCR2A &= ~(_BV(COM2B1) | _BV(COM2B0));

    //  Не используем предварительный делитель
    TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);

    // Задаем начальную ширину импульса для первого образца.
    OCR2A = pgm_read_byte(&sounddata_data[0]);


    // Настраиваем Таймер 1, чтобы отправить образец каждого прерывания.

    cli();

    // Устанавливаем режим CTC (Очистить таймер на совпадении)
    // Необходимо установить OCR1A * после * ( *after*), иначе он будет сброшен на 0!
    TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12);
    TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10));

    //  Не используем предварительный делитель 
    TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);

    // Устанавливаем регистр сравнения (OCR1A).
    // OCR1A - это 16-разрядный регистр, поэтому мы должны сделать это с помощью
    // отключенный прерываний, чтобы быть в безопасности.
    OCR1A = F_CPU / SAMPLE_RATE;    // 16e6 / 8000 = 2000

    // Включаем прерывание, когда TCNT1 == OCR1A (p.136)
    TIMSK1 |= _BV(OCIE1A);

    lastSample = pgm_read_byte(&sounddata_data[sounddata_length-1]);
    sample = 0;
    sei();
}

void stopPlayback()
{
    // Отключаем прерывание воспроизведения на выборку.
    TIMSK1 &= ~_BV(OCIE1A);

    // Полностью отключаем таймер за выборку.
    TCCR1B &= ~_BV(CS10);

    // Отключаем таймер PWM.
    TCCR2B &= ~_BV(CS10);

    digitalWrite(speakerPin, LOW);
}

void setup()
{
    // проигрываем наш аудиофайл
    startPlayback();
}

void loop()
{
  
}

Динамик элемент не полярный, поэтому один его контакт подключаем к 11 пину, а другой к GND. Скорость воспроизведения (SAMPLE_RATE) нужно подобрать под себя. Если вы выбрали при первой конвертации стерео, то она должна быть в 2 раза больше, чем при моно. Если все было сделано правильно, то Arduino скажет нам «that’s good!».

7 марта 2019 в 09:00 | Обновлено 22 октября 2020 в 18:39 (редакция)
Опубликовано:
Уроки,

11 комментариев

  1. Алексей
    18 мая 2020 в 02:36

    выдает ошибку (exit status 1
    expected initializer before ‘PROGMEN’ )

    Ответить
    1. Valeek5546
      13 октября 2020 в 21:19

      PROGMEN измени на PROGMEM

      Ответить
  2. Максим
    21 декабря 2020 в 13:59

    не работает, ошибок не выдает…

    Ответить
    1. Paramond
      21 августа 2023 в 20:52

      у меня тоже колонка просто гудит

      Ответить
  3. Степан
    22 января 2021 в 18:59

    выдает ошибку
    exit status 1
    too many initializers for ‘const unsigned char [0]’

    Ответить
    1. ё
      27 января 2021 в 18:12

      у меня тоже

      Ответить
  4. Евгений
    17 августа 2022 в 07:17

    Всё отлично работает! Автору Огромное спасибо!
    Правда строчку пришлось чутка изменить:
    const PROGMEM unsigned char sounddata_data
    потому что при POGMEM в конце нехотела работать.

    Ответить
  5. Алекс
    26 февраля 2023 в 17:49

    Очень сырой проект . При применении простейших алгоритмов сжатия качество и длительность звучания увеличивается на порядок.

    Ответить
  6. Арсений
    10 апреля 2023 в 19:01

    size of array is too large
    Ошибка что делать

    Ответить
  7. *********
    16 мая 2023 в 21:35

    не работает ошибок не выдаёт

    Ответить
  8. Владимир
    10 июня 2023 в 22:05

    Здесь пропущена (специально убрана?) важная строчка (увидел в английском варианте):
    в функции void startPlayback() первой командой должна идти
    pinMode(speakerPin, OUTPUT);
    Только после этого работает.

    Ответить

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

Ваш E-mail не будет никому виден. Обязательные поля отмечены *