Mmc/sd wav стерео плеер на atmega32 с пду

MMC/SD WAV стерео плеер на ATmega32 с ПДУ

Это мой первый и самый успешный проект на микроконтроллерах AVR. Несколько месяцев назад я попытался сделать WAV плеер на PIC16F877A. Он работал, но качество звука было не очень хорошим, потому что у него не было достаточного объема оперативной памяти, и я не смог сделать достаточный буфер данных. Когда я купил микроконтроллер ATmega32, первое, что пришло мне в голову — сделать хороший WAV плеер. Я закончил свою работу и качество звука действительно хорошее. Я могу сказать, что мой WAV плеер способен воспроизводить 8-битное моно /стерео с максимальным битрейтом 1300kbps для моно и 1600kbps для стерео. Т.е. можно играть 8-битное WAV моно с частотой дискретизации до 160 кГц и стерео до 96 кГц без шума!

Примечание: Полный спектр человеческого слуха составляет от 20 Гц до 20 кГц. Минимально достаточная частота дискретизации 40 кГц. Именно для этого частота дискретизации 44,1 кГц была выбрана для компакт-диска.

Я считаю, что это идеальный 8-битный WAV- стерео плеер. Я не нашел отличий между качеством звука с компьютера и этим плеером. Видео снималось мобильным телефоном, поэтому возможны потери качества звука. Поскольку ШИМ имеет некоторые ограничения, я пытался использовать R-2R ЦАП, но не заметил существенных улучшений.
Еще одной интересной особенностью плеера является то, что он может управляться с пульта от ТВ Philips (протокол RC5). Пультом можно переключатся между следующей и предыдущей песней, включать воспроизведение, ставить на паузу, включать некоторые забавные эффекты, такие как увеличение или уменьшение скорости воспроизведения. Программа для работы с MMC картой памяти не писалась с нуля, а была взята готовая.

Возможности:

  • Дистанционное управление
  • Высокое качество на выходе
  • Максимальная скорость передачи данных — 144 Кб / сек.
  • Поддержка стерео
  • Автоматический возврат в начало после окончания всех песен
  • Дополнительная регулировка битрейта с пульта
  • Установка задержки перед воспроизведением

Краткое описание работы:

Инициализация MMC карты и работа с ней была описана ранее тут.

Я использую дисплей 16×2 для отображения многих вещей. Сначала он при необходимости отображает сообщение об ошибке при попытке инициализации MMC. Когда инициализация проходит успешно, он показывает сообщение об этом.

Потом проверяется загрузочный сектор карты MMC (сектор 0), чтобы проверить файловую систему карты. Для этого нам нужно прочитать сектор 0 карты MMC и копировать его в буфер (для этого задействовано 512б буфера). Мой код предназначен только для файловой системы FAT16, если на карте другая файловая система, он выдает сообщение об ошибке на дисплей. Если всё в порядке, то он читает данных из буфера и вычисляет номер сектора с началом данных, начало FAT и начало корневого каталога. Кроме того, он обнаруживает секторы кластеров. Каждый сектор это 512 байт. Эти данные необходимы для дальнейшей работы MMC / SD карт, хранятся как глобальные переменные.

Теперь необходимо попасть в корневой каталог. Нам нужно загрузить первый сектор корневого каталога в 512 байт буфера. Каждая запись содержит подробную информацию о файле или папке в корневом каталоге. Оттуда мы можем прочитать имя файла, атрибуты файла, адрес самого файл и многое другое. Нас интересует расширение файлов (WAV), атрибутах файла, адрес первого кластера куда записан файл.

Теперь мы можем прочитать первый кластер (группу секторов, размер зависит от размера памяти MMC / SD). Теперь, после чтения и воспроизведения всех секторов, в первой группе (проигрывание будет описано далее), то мы должны найти следующий номер кластера того же файла. Файл не может быть распределен в памяти как один раздел. Вместо этого, он может быть разделен на части, чтобы использовать свободную память эффективно (На самом деле это происходит только тогда, когда есть свободное место от удаленных файлов и добавлены новые). Таким образом, мы не можем сказать, что следующий кластер файла будет ближайшим. Но для чтения каждого файла есть список кластеров FAT (File Allocation Table), в котором каждый номер кластера имеет уникальную позицию. Мы уже вычислили начальный адрес FAT. С FAT мы можем получить следующий номер кластера в файле.

У нас есть номер первого кластера, и мы рассчитали начальный адрес сектора с определенным числом кластеров. Теперь мы читаем данные из первого сектора файла и оттуда мы получаем битрейт, частоту дискретизации, количество каналов и многое другое. Возьмем битрейт, и использовать его, чтобы установить частоту прерывания таймера. Теперь таймер генерирует прерывания в зависимости от битрейта и количества канала. Теперь на каждом прерывании таймера, 8 бит данных вводится в регистр OCR модуль таймера ШИМ. Соответственно, генерация ШИМ сигнала идет в фоновом режиме без затрат каких-либо ресурсов процессора. Этот сигнал ШИМ может быть легко демодулирован при помощи RC фильтра. Если значение конденсатора или резистора увеличивается или уменьшается , это повлияет на качество звука, т.е. он может не отфильтровать некоторые высокие частоты и можно услышать сигнал AM радио.

Теперь мы знаем, если мы используем один буфер для воспроизведения и сбора данных, то будет небольшой шум во время воспроизведения, что очень раздражает. Я выделил два специальных 512б буфера для только для аудио данных. Когда один буфер играет, другой буфер будет наполнятся.

Для декодирования сигнала телевизионного пульта мы используем прерывания таймера, проверяя входящей сигнал каждые 1778мс. При включении питания происходит задержка 400мс перед началом считывания сигнала с пульта. Возможно, плеер будет реагировать на сигнал с пульта со второго раза. Чтобы этого надо декодировать RC5 вне atmega32 и отправить значения через последовательный интерфейс для лучшей производительности.

Кварц я использовал на 16.450 МГц, т.к. под рукой не оказалось 16 МГц. Это никак не повлияло на работу микроконтроллера.

Введение

В этой статье мы рассмотрим практический пример использования SD карты с микроконтроллером AVR. По просьбе трудящихся я написал проект, который читает с SD карты wav файл и воспроизводит его.

Для проекта я использовал микроконтроллер atmega16, тактируемый от внешнего кварца с частотой 6 МГц. В качестве ЦАПа задействована функция формирования ШИМ сигнала таймера Т0. Wav файл для воспроизведения был выбран с такими параметрами: 8 бит, 22 кГц, моно.

Низкоуровневые функции для работы с SD картой

Чтобы использовать библиотеку Petit FatFs с SD картой, нужно реализовать три низкоуровневые функции для работы с ней — это функция инициализации, чтения и записи. Если вы читали предыдущий материал, в котором была описана библиотека Petit FatFs, то должны помнить, что «пустышки» этих функций находятся в файле diskio.c

На сайте Elm Chan`a есть примеры использования библиотеки с SD картами, поэтому можно взять уже готовые функции из этих проектов, что я и сделал. Я позаимствовал из одного примера файл mmc.c и заменил им файл diskio.c , однако файл mmc.c тоже потребовал небольшого «допиливания».

В начале файла определены макросы, реализующие работу с SPI интерфейсом. Можно определить эти макросы для работы с аппаратным SPI модулем, а можно реализовать программный SPI. Это уже вопрос ресурсов и желания пользователя.

#define SELECT() — формирует низкий уровень на CS выводе карты
#define DESELECT() — формирует высокий уровень на СS выводе карты
#define MMC_SEL — возвращает единицу, если на выводе CS низкий уровень
#define init_spi() — инициализирует SPI модуль
#define dly_100us() — формирует задержку на 100 микросекунд
#define xmit_spi(d) — передает байт данных по SPI
#define rcv_spi() — принимает байт данных по SPI
#define FORWARD(d) — перенаправляет поток данных, этот макрос можно оставить пустым

Все эти макросы легко реализовать, если прочитать материал про SPI модуль AVR микроконтроллера. Я как раз взял оттуда spi драйвер и «прицепил» его к файлу mmc.c.

Короче, получается такая последовательность. Мы берем библиотеку Petit FatFs добавляем к ней файл mmc.c из примеров Elm Chan`a, описываем макросы реализующие spi и после этого можем работать с SD картой. Немного заморочено, но если один раз разобрался, то все становится понятно.

Итак, я все это проделал и теперь могу использовать SD карту. И чтобы показать, что это действительно работает, я написал проект, в котором микроконтроллер читает с SD карты wav файл и воспроизводит.

Воспроизведение звука микроконтроллером

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

В качестве файла для воспроизведения я взял произвольный mp3 трек (мне попалась песня группы Prodigy) и перекодировал его в wav файл с такими параметрами: 8 бит, 22 кГц, моно. Для конвертирования файла я использовал видео редактор Sony Vegas, но можно найти программу и попроще. Например, такая функция есть во многих аудио редакторах вроде Sound Forge, WaveLab, Cool Edit и т.д.

«8 бит» — это разрядность одной выборки аналогового сигнала. Звук хорошего качества обычно имеет разрядность 16 (CD качество) или 24 бита (студийная запись), но для микроконтроллерной «говорилки» 8-и разрядов хватит за глаза.

«22 кГц» — это частота дискретизации. То есть частота, с которой из аналогового сигнала «брались» выборки. С этой же частотой мы должны преобразовывать цифровые выборки сигнала в аналоговые напряжения. Цифровой звук хорошего качества обычно имеет частоту дискретизации 44.1 кГц (CD качество), 96 кГц ( студийная запись) и т.д.

«моно» — означает одну звуковую дорожку, которая будет воспроизводиться как в правом, так и в левом аудио каналах.

Итак, для воспроизведения wav файла с параметрами 8 бит, 22 кГц, моно, нам понадобится одноканальный 8-и разрядный ЦАП, способный формировать на выходе аналоговые напряжения с частотой 22 кГц. Поскольку у большинства AVR`ок нет встроенного цифро-аналогового преобразователя, мы можем использовать следующие варианты:

— аппаратный ШИМ,
— программный ШИМ,
— внешний интегральный ЦАП,
— внешняя схема ЦАП`a .

Реализация программного ШИМ`a требует от микроконтроллера большого быстродействия, поэтому я не захотел с ним связываться. Внешний ЦАП обычно использует SPI, который нужен для SD карты. Внешняя схема ЦАП`а, например схема R-2R, неплохой вариант, но под нее нужно отдать целый порт микроконтроллера.

Читайте также  Светодиод и операционный усилитель: как услышать свет

Исходя из этого, я остановил свой выбор на аппаратном 8-и разрядном ШИМ`е. Во первых, эта функция есть во всех микроконтроллерах AVR, а во-вторых, для реализации ЦАП`а требуется всего один вывод микроконтроллера.

В одном из старых материалов я уже описывал принцип формирования аналогового сигнала с помощью ШИМ, однако в случае wav файла здесь не все так просто.

С какой частотой можно формировать аналоговые напряжения с помощью ШИМ? Это зависит от трех параметров: тактовой частоты микроконтроллера, делителя таймера и его разрядности. Например, для 8-и разрядного ШИМ сигнала при тактовой частоте микроконтроллера 16 МГц можно получить следующие частоты.

Тактовая частота микроконтроллера Fcpu = 16000000 Гц
Тактовая частота таймера Т0 Ftim = Fcpu/k , где k — 1, 8, 64, 256, 1024.
Частота ШИМ сигнала Fpwm = (Fcpu/k)/2^8 = Fcpu/(k*256)

при k = 1 Fpwm = 62500
при k = 8 Fpwm = 7812
при k = 64 Fpwm = 976
при k = 256 Fpwmn = 244
при k = 1024 Fpwm = 61

Ближайшая частота к требуемым 22 килогерцам — это 7812, но такая частота не подойдет. Файл, воспроизводимый с такой частотой, будет уж слишком замедленным. Надо подобрать такую тактовую частоту микроконтроллера, при которой можно получить требуемую частоту формирования аналоговых напряжений. Неплохой результат получается при 6 МГц и k = 1

Fcpu = 6000000 Гц
Fan = 6000000/(2^8 * 1) = 23437 Гц

Звуковой файл будет немного ускоренно воспроизводиться, но на слух это почти незаметно.

Итак, аналоговые напряжения будут формироваться с помощью ШИМ функции аппаратного таймера Т0, но как разнести процесс чтения данных с процессом воспроизведения? Считывать с SD карты по одной выборке сигнала с частотой 22 кГц не получится, микроконтроллер не будет успевать это сделать.

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

Я подбирал размер буфера следующим образом. Задал максимальную частоту SPI модуля микроконтроллера atmega16 и посмотрел сколько времени затрачивается на чтение данных с SD карты. То есть сколько времени выполняется функция pf_read(..).

При тактовый частоте Fcpu = 6 МГц эта функция выполнялась

2.5 мс, но иногда попадались циклы по 5 мс (наверное из-за чтения на границе секторов .. напишите в комментариях, если знаете). Причем это время не зависело от количества данных — и 32, и 64, и 128 байт читались за одно и то же время.

Затем я посчитал сколько данных будет передано в ЦАП за время 5 мс. Частота нашего псевдо ЦАП`a = 23437 Гц, соответственно период = 42.6 мкс. Делим 5 мс на 42.6 и получаем искомую цифру.

n = 0.005/(1/23437) = 117

То есть за 5 мс микроконтроллер выдаст 117 выборок сигнала, при этом за это же время успеет прочитать с карты 128 выборок. Получается, что если взять буфер размером 256 байт микроконтроллер будет успевать выполнять обе задачи и даже остается небольшой запас времени. Он, кстати, необходим, потому что в процесс чтения данных с SD карты, будут вклиниваться прерывания таймера Т0.

Так я и сделал. Выбрал размер буфера равным 256 байт.

Схема для проекта

Схема питается от двух стабилизаторов — 3.3 В и 5 В. Как вариант можно запитать все схему от 3-х вольтового источника (тогда даже не понадобятся преобразователи уровней) или понизить 5-и вольтовое напряжение с помощью трех последовательно включенных диодов и запитать таким образом SD карту.

Микроконтроллер тактируется от внешнего кварцевого резонатора с частотой 6 МГц. SD карта подключена к микроконтроллеру по одной из приведенных ранее схем.

Для преобразования ШИМ сигнала в постоянное напряжение используется низкочастотный RC фильтр из двух каскадов. Частота среза фильтра около 10 кГц, что соответствует полосе воспроизводимого цифрового сигнала.

Для индикации состояния устройства используется светодиод LED1. Если карта не считывается в момент включения устройства, светодиод начинает моргать.

Код проекта

Все основное действо заключено в файле main.c. При старте программы происходит инициализация переменных и настройка выводов — настраивается ШИМ выход и выход для светодиода.

Затем монтируется SD карта, открывается файл под названием 1.wav и из него (в буфер) читаются 256 байт. Далее проверяется результат выполнения операций с картой. Если операции завершились неудачно, программа зацикливается и начинает моргать светодиод. Если чтение прошло успешно, программа инициализирует таймер Т0 и зажигает светодиод.

Далее разрешаются прерывания и программа попадает в бесконечный цикл, внутри которого расположен конечный автомат с тремя состояниями. В первом состоянии идет заполнение нижней половины буфера, во втором — верхней половины, а в третьем программа ничего не делает. В третье состояние автомат переходит, когда wav файл заканчивается. При этом гасится светодиод и выключается таймер Т0.

Параллельно основному циклу программы выполняется прерывание таймера Т0. Оно сделано предельно коротким. В микроконтроллер загружает в регистр сравнения OCR0 содержимое ячейки буфера, на которую указывает индексная переменная, а затем инкрементирует ее. Поскольку размер буфера равен 256 байт, индексную переменную не приходится проверять на граничное значение.

Автомат, заполняющий буфер данными, проверяет индексную переменную. И по ее значению «понимает» в какую часть буфера можно писать данные.

Неиспользуемые функции библиотеки Petit FatFs я отключил в файле pff.h.

Из недостатков проекта можно отметить два момента:

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

Заключение

Подведем итог. Wav файл — это последовательность выборок сигнала. Он имеет такие характеристики, как разрядность и частота дискретизации. Чтобы воспроизвести wav файл мы должны преобразовывать выборки сигнала в аналоговые напряжения, причем делать это с частотой дискретизации, иначе файл будет воспроизводиться замедленно или наоборот ускоренно.

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

Чтобы разнести процесс чтения данных с SD карты с процессом воспроизведения, можно использовать буфер. Из одной половины буфера данные будут читаться, в другую — записываться. Чтобы выбрать достаточный размер буфера, необходимо знать сколько времени требуется для чтения порции данных и сколько времени затрачивается на воспроизведение этой порции.

AterLux › Блог › Воспроизведение WAV c SD на МК. Краткая теория.

Ниже будет исключительно кратенькая информация о том как с места в карьер начитать WAV файлик с SD карточки, форматированной в FAT16. Примерно вот так:

SD карты (но не SDHC!) имеют возможность взаимодействовать с МК по интерфейсу SPI, что делает их подключение и использование очень простым.

Для экспериментов я приляпал штыревой разъём к адаптеру MicroSD карточки. Вот так:

Получился своеобразный адаптер MicroSD карты для использования в макетной плате:

Как подключать

В режиме SPI выводы подключаются следующим образом:

Одна из трудностей — это то, что карта должна питаться напряжением в диапазоне 2,7…3,6 Вольта, а напряжение на каждом входе не должно превышать напряжение питания более чем 0,3 Вольта.

Одним из выходов является использование МК, который будет работать на таком же напряжении. Другой подход — использование отдельного стабилизатора для карты и делителей напряжения на каждом из выводов. Правда, в случае делителей, если номиналы резисторов будут малы, то через них будет утекать значительный ток, а если будут велики, то это ограничит скорость обмена с картой.

Линию CS рекомендую притягивать к питанию внешним резистором около 100кОм, чтобы карта не хулиганила, когда МК находится в состоянии сброса.

Обмен по SPI

Обмен осуществляется в режиме SPI 0 (POL=0, PHA=0). То есть SCK по умолчанию пребывает на низком уровне, в момент нарастающего фронта SCK происходит сэмплирование (т.е. чтение значений на линиях данных), в момент спада — выставление новых значений. Иначе говоря, пока SCK низкий, идёт установка значений, а когда высокий, установленные значения держатся фиксированными и читаются противоположной стороной.

Передача выровнена по границам байта, т.е. всегда кратна 8 битам. Первым передаётся старший бит.

SD карты поддерживают обмен на частоте до 25МГц. Достичь даже половины этой частоты AVRками будет сложно, поэтому настраивать модуль SPI можно на максимальную скорость.

По умолчанию карта работает в своём собственном SD формате. Чтобы перевести её в режим SPI нужно, пока на линиях MOSI и CS высокие уровни, послать не менее 74 импульсов на линии SCK. Большинство карт примут эти импульсы на любой частоте, но для совместимости с MMC картами, требуется их выдавать на частоте от 100 до 400 кГц. Наиболее простой способ — перевести SPI на эту частоту и 10 раз передать 0xFF

Затем должна быть послана команда GO_IDLE_STATE CMD0 (код у неё 0x40)
Дальше уже линия CS должна быть притянута к земле на время выполнения команд.

Формат команд

Здесь будет рассмотрено только чтение с карты, поэтому разобран только один формат команд R1:

0: 1 байт — сама команда
1.4: 4 байта — параметры, сначала старший байт
5: 1 байт — CRC

CRC в режиме SPI игнорируется, и там можно передавать всякую чепуху, но до входа в режим SPI, т.е. для первого выполнения команды 0x40, CRC должно быть рассчитано правильно. У этой команды нет параметров, поэтому CRC у неё всегда одно и то же: 0x95

Весь передаваемый пакет выглядит вот так:
0x40 0x00 0x00 0x00 0x00 0x95

После этого карточка готовит ответ. Пока она его готовит, на линии MISO единичка. Поскольку в этом режиме ответы выравнены по байтам, то нужно читать целые байты из SPI, пока не появится не 0xFF. Ответ можно не анализировать, главное что он есть.

Инициализация

В начале, и после 5 мс бездействия, карта впадает в спящий режим, из которого её нужно вывести, послав в том же формате команду SEND_OP_COND 0x41. Здесь уже нужно анализировать ответ. Когда карта вышла из спячки, он будет равен нулю. Если он не равен нулю, то посылку команды нужно повторять, пока не станет нулём.

Читайте также  Библиотека для символьного дисплея на базе hd44780

Информация на карте хранится секторами по 512 байт, и читается блоками, размер которых по умолчанию также выбран 512 байт.
Размер блока можно изменить командой 0x50, где в качестве параметра указан размер блока от 1 до 512. Но, в любом случае, при чтении блок не должен выходить за границу сектора.

Адрес для чтения задаётся в байтах, в параметре команды 0x51. Поскольку параметр 32-битный, размер карты ограничен 4 гигабайтами.

После посылки команды чтения нужно ожидать ответа на команду, как описано выше, а затем карта готовит блок к передаче, в это время на шине читается 0xFF и когда данные готовы, карта передаёт 0xFE, и следующим байтом идёт собственно содержимое блока.
Завершает блок два байта CRC (на которые в условиях ограниченности ресурсов МК можно забить болт).

Рекомендую начинать чтение всегда с адреса кратного 512 (с начала сектора), а потом просто «догнать» до нужной позиции холостыми чтениями.

Многоблочное чтение

Выполняется командой 0x52 Работает точно также, но после того как завершилась передача одного блока, сразу начинается подготовка второго, 0xFE, и пошёл второй блок.
Чтобы прервать эту передачу нужно, несмотря на передаваемые данные, отослать в любой момент команду 0x4C, без параметров.

Одна из моих карточек, при выборе размера блока, не хотела правильно читать в многоблочном режиме, а всё время возвращала один и тот же сектор по кругу. Поэтому рекомендую оставлять размер блока всегда по умолчанию, т.е. 512 байт.

Итак, доступ к данным организован. Здесь краткое описание структуры FAT16

Сначала читаем самый первый сектор, с позиции 0x1C2
0x1C2: 1 байт — код файловой системы. Для FAT16 это 0x06.
…3 байта пропускаем…
0x1C6: 4 байта — boot_sector = смещение бут-рекорда первого раздела диска, задаётся в секторах. То есть помещённое здесь значение нужно умножать на 512.

Здесь, как и везде далее, числа хранятся в интеловском формате — сначала младший байт

boot_record = boot_sector * 512;

Теперь, когда убедились, что это нужная нам файловая система, идём на позицию boot_sector * 512 + 11 и читаем
+11: 2 байта — всегда 00 02 — размер сектора в байтах.
+13: 1 байт — sectors_per_cluster — количество секторов на кластер. Должно быть степенью двойки, т.е. максимум 128 секторов = 64 кб.
+14: 2 байта — reserved_sectors — количество резервных секторов. Через сколько секторов, начиная с этого, начнётся таблица fat
+16: 1 байт — num_fats — количество копий таблицы fat. Обычно равно 2.
+17: 2 байта — root_entries — количество записей в корневом каталоге. Как повезёт, обычно 512
…пропускаем 3 байта…
+22: 2 байта — sectors_per_fat — размер одной таблицы fat в секторах.

Теперь осталось вычислить интересующие нас позиции:
fat_offset = boot_record + reserved_sectors * 512; // Начало первой копии fat
rootdir = fat_offset + numfats * sectors_per_fat * 512; // Начало корневого каталога
data = rootdir + root_entries * 32; // Начало самих данных.

Структура корневой директории

Корневой каталог начинается с rootdir и состоит из 32байтных записей такой структуры:
+0: 8 байта — ДОС-имя файла, дополненное пробелами. Если начинается с 0x05 или 0xE5 — то это удалённая запись. Если 0x00 — то это конец директории, дальше читать не нужно.
+8: 3 байта — ДОС-расширение имени файла, также дополненное пробелами.
+11: 1 байт — свойства. Бит 3 — имя тома, бит 4 — директория. Если они не установлены, то это похоже на нормальный файл.
…пропускаем 14 байт, они скучные…
+26: 2 байта — номер первого кластера, где живёт файл.
+28: 4 байта — размер файла

Директории по своей сути являются файлами, состоящими из таких же структур

Чтение файла

Первые две позиции в таблице fat зарезервированы, самый первый кластер данных имеет индекс 2, Поэтому узнать исконную позицию данных можно по формуле
позиция_данных = data (см.выше) + (номер_кластера — 2) * sectors_per_cluster * 512.

Поскольку sectors_per_cluster — степень двойки, как и 512, можно заранее вычислить эту степень и выполнять каждый раз операцию логического сдвига.

После того, как весь кластер (а это sectors_per_cluster * 512 байт) прочитан, в таблице fat нужно посмотреть номер кластера, в котором файл продолжается. Файл может быть фрагментирован и его части раскиданы по диску. Таблица fat как раз позволяет найти их в нужной последовательности Для этого читаем 2 байта на позиции

fat_offset + номер_кластера * 2

это и будет номер следующего кластера. Значения 0xFFF8 … 0xFFFF означают что этот кластер был в файле последним. Значения 0xFFF0 … 0xFFF6 — зарезервированы, 0xFFF7 означает «битый кластер» (бэд-блок), значение 0x0000 — пустой, свободный для записи кластер, 0x0001 — не используемый номер кластера. Если эти специальные номера встретились при чтении файла, значит структура диска повреждена.

Когда найдено начало WAV файла, можно читать его структуру.
Начинается он с идентификатора контейнера: 0x52 0x49 0x46 0x46 (‘RIFF’)
затем 4 байта — размер содержимого (здесь тоже всё младшим байтом вперёд)
затем идентификатор содержимого 0x57 0x41 0x56 0x45 (‘WAVE’).
После этого идут блоки в таком формате:
4 байта — идентификатор блока
4 байта — размер содержимого блока
— содержимое блока.

Блоки могут быть всякие разные, но нужны только два и обычно ими wav файл и ограничивается
блок 0x66 0x6D 0x74 0x20 (‘fmt ‘) — формат, имеет следующую структуру:
0: 2 байта — формат сжатия. Нам годится только 1 — PCM
2: 2 байта — количество каналов, 1 — моно, 2 — стерео
4: 4 байта — частота дискретизации (сэмплов в секунду, например 44100)
8: 4 байта — байт в секунду, можно игнорировать, обычно равно частоте_дискретизации * (бит_на_канал / 8) * количество каналов
12: 2 байта — граница выравнивания, равно (бит_на_канал / 8) * количество_каналов, можно игнорировать
14: 2 байта — бит на канал, бывает 8, или 16 бит.

блок 0x64 0x61 0x74 0x61 (‘data’) — сами данные. Т.к. отсюда известна длина данных для чтения, то запоминать размер самого файла изначально нет никакого смысла.

8-битные сэмплы идут в беззнаковом формате, т.е. от 0 до 255, 128 — это примерно середина.
16-битные сэмплы знаковые, т.е. от -32768 до 32767
для стерео файлов чередуются сэмплы: сначала идёт сэмпл левого канала, за ним правого.

Грузим это всё, миксуем, пересчитываем, ресемплируем под частоту таймера, заполняем в буфер, а в таймере по прерыванию грузим из буфера. И вуаля! Звук готов 😉

Аудио-плеер на STM32. Воспроизведение WAV-файла.

Всех приветствую на нашем сайте, и сегодня мы займемся воспроизведением музыки и сделаем свой собственный мини-аудио-плеер! Конечно же, на базе контроллера STM32, как же без него. Итак, воспроизводить будем файл формата WAV, соответственно нам понадобятся:

  • Карта памяти для хранения аудио-файлов. Я буду использовать MicroSD, подключенную к микроконтроллеру по интерфейсу SDIO. Кроме того, для организации работы с файловой системой нужно добавить в проект поддержку FatFs.
  • Динамик и аудио-усилитель, в качестве которого возьмем микросхему LM386. А сами данные с контроллера будем выдавать при помощи цифро-аналогового преобразователя (DAC).
  • И из периферии нам еще понадобится таймер, но об этом чуть позже.

Схема подключения.

В итоге структурная схема нашего устройства будет выглядеть так:

Я сегодня буду использовать STM32F103VE, но это не особо важно, для другого контроллера последовательность действий, да и код, будут практически идентичны. LM386 подключаем по стандартной схеме:

Обвязка для LM386 стандартная, варианты подключения можно найти в даташите. Сигнал с DAC подаем через потенциометр для того, чтобы иметь возможность регулировать громкость, меняя сопротивление.

Разъем для MicroSD на отладочной плате подключен так:

SDIO для карты памяти используем в 4-х битном режиме. Питание контроллера, внешний кварцевый резонатор, SWD разъем для программирования – здесь все стандартно.

Так, со схемой закончили, давайте обсудим, как мы будем получать нужные аудио-данные из WAV-файла. Структура файла довольно проста – после заголовка сразу же следуют непосредственно цифровые данные, которые мы можем преобразовывать в аналоговый сигнал при помощи ЦАП и выдавать на динамик.

Можно использовать два варианта представления этих данных – 8-битное и 16-битное. Вспоминаем, что ЦАП у нас 12-ти битный, а значит при использовании 8-битного формата мы не задействуем его возможности в полной мере. Поэтому лучше возьмем WAV-файл с 16-ти битным представлением данных, а в программе масштабируем эти данные до 12-ти бит. Итоговое качество звука при этом будет выше.

В WAV-файле данные хранятся со знаком, то есть для 16 бит мы получаем диапазон значений от -32767 – до 32768. На ЦАП нам необходимо выдавать значения от 0 до 4095 (12 бит). Поэтому сначала преобразуем исходные данные в беззнаковые, добавив 32767:

Полученные числа (0 – 65535) надо преобразовать в значения от 0 до 4095:

Заголовок WAV-файла может содержать разное количество байт, в зависимости от хранимой в нем дополнительной информации (исполнитель, название и т. д.). Чуть позже, создавая практический пример, разберемся, как определить конец заголовка и, соответственно, начало аудио-данных.

С преобразованием в нужный нам вид разобрались, но открытым остается вопрос, как часто нужно будет изменять сигнал на выходе DAC, с какой периодичностью. И тут все зависит от частоты дискретизации аудио-файла. Будем использовать частоту 16 КГц. Это значит, что именно с такой частотой надо обновлять данные на выходе ЦАП. Рассчитаем период, соответствующий частоте 16 КГц.

Получается, что нам нужен таймер, который будет генерировать прерывания каждые 62.5 мкс. И в прерывании мы будем выдавать на модуль DAC новое значение.

Таким образом, при конвертировании, к примеру, mp3 в wav, необходимо выбирать именно эти параметры – 16 бит данных + частота дискретизации 16 КГц.

Создание проекта.

Итак, все готово, переходим к созданию проекта в STM32CubeMx. Первым делом, активируем DAC, второй канал, то есть вывод PA5. Настроек минимум:

Сразу же настраиваем таймер и его прерывание:

Частота тактирования TIM6 у меня 72 МГц. Предделитель 180 (179 + 1) дает нам итоговую частоту таймера равную:

То есть один “тик” таймера – 2.5 мкс (такой период соответствует частоте 400 КГц). Значит период таймера в отсчетах должен быть равен:

Читайте также  Страшная delta или домашний 3d принтер

Это мы и задали в его настройках.

Осталось добавить поддержку SD-карты по SDIO и драйвер FatFs:

В новых версиях STM32CubeMx в конфигурации FatFs появилась вкладка Platform Settings. Здесь можно настроить какой-либо из входов контроллера для использования в качестве сигнала, по уровню которого можно отследить подключение и извлечение карты памяти:

В сегодняшнем проекте не будем использовать этот функционал, что приведет к тому, что CubeMx будет выдавать предупреждение:

Игнорируем его и переходим к генерации кода.

Программная реализация.

Все приготовления позади, нам остается только написать программный код! Начинаем двигаться поэтапно, монтируем файловую систему и открываем аудио-файл. Пусть файл, который мы предварительно закинули на карту памяти, называется audio.wav. Этот код помещаем прямо в функцию main() до цикла while(1):

Чтобы не нагромождать пример, не будем проверять результат res выполнения функций FatFs, в “боевом” проекте лучше, конечно, этим не пренебрегать и отслеживать все возможные ошибки.

Следующим шагом нужно определить длину заголовка WAV-файла. Для этого воспользуемся простым механизмом – считываем 512 байт (WAV_BUF_SIZE) из файла и проверяем последовательно полученные байты. Особенностью заголовка является то, что в его конце всегда расположены символы ‘data’, после чего следует размер данных (4 байта). И вот после этого уже начинаются именно аудио-данные.

Таким образом, находим позицию в буфере, которая соответствует символу ‘d’ и прибавляем к этому значению 8 (4 байта для ‘data’ и 4 байта для размера данных):

Заголовок обнаружен, перемещаем указатель FatFs для работы с файлом на аудио-данные и заодно определяем количество байт данных. Для этого вычитаем из общего размера файла размер заголовка:

И теперь необходимо остановиться на еще одном нюансе… Нам нужно обновлять данные на выходе DAC каждые 62.5 мкс, то есть относительно часто. Чтобы не было задержек, связанных с временем, которое требуется для того, чтобы прочитать очередные 16 бит из wav-файла, нужно организовать буферизацию аудио-данных.

Идея здесь заключается в следующем. Объявим два буфера размером 512 байт, в которые поочередно будем считывать новые данные из файла с карты памяти. Алгоритм будет такой:

  • Сначала заполняем оба буфера данными и начинаем воспроизведение (то есть вывод данных на ЦАП) из буфера 1.
  • Как только буфер 1 подошел к концу (512 его байт выданы на ЦАП), переходим к выводу данных из буфера 2. И параллельно заполняем буфер 1 новыми данными с карты.
  • Когда мы использовали данные буфера 2, переходим снова к буферу 1, а в буфер 2 считываем новую порцию.

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

Реализуем этот механизм и, первым делом, заполняем оба буфера данными:

Поскольку данные готовы, спокойно включаем DAC и TIM6 на генерацию прерываний:

Размер буфера задан в main.h:

В основном цикле программы занимаемся обновлением данных, то есть чтением их с карты памяти:

В переменную wavReadFlag в прерывании по таймеру будем записывать единицу в том случае, если буфер подошел к концу и его данные можно обновлять. Счетчик curBufIdx содержит текущий номер активного буфера, то есть того, из которого сейчас идет считывание данных для DAC. Соответственно, это либо значение 0 – для первого буфера, либо 1 – для второго.

Поскольку буфер с индексом curBufIdx активен, то считывать данные нам напротив нужно в неактивный, номер которого помещаем в readBufIdx. После этого обнуляем флаг wavReadFlag.

Кроме того, сразу же в while(1) помещаем код, который отвечает за окончание воспроизведения, когда файл подошел к концу:

В main() нам в этом случае требуется только закрыть файл. Аналогично, флагом stopFlag мы управляем из прерывания таймера, к которому и переходим:

Здесь мы, во-первых, формируем данные для DAC в точности так, как обсудили в начале статьи и отправляем их на выход. Также увеличиваем счетчики байт curBufOffset и curWavIdx на 2, поскольку одна порция данных у нас равна двум байтам (16 битам):

  • curBufOffset – номер текущего байта в буфере, этот счетчик, соответственно, будет изменяться от 0 до 512.
  • curWavIdx – общий счетчик байт wav-файла. Его мы используем, чтобы обнаружить конец трека и остановить воспроизведение.

В общем-то по окончанию файла отключаем таймер и выставляем флаг stopFlag в 1:

Если конец файла еще не достигнут, то проверяем счетчик curBufOffset. Если значение равно 512 (WAV_BUF_SIZE), то буфер подошел к концу, а значит надо изменить номер активного буфера и выставить флаг wavReadFlag:

Вот, в принципе, на этом и все…

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

И на этом на сегодня заканчиваем, любые вопросы можно писать в комментарии, либо на форуме, либо в группе ВК, короче, где угодно 🙂

Подборка mp3 модулей для модернизации музыкальных центров и создания бумбоксов

У многих есть музыкальные центры от именитых производителей выпущенные много лет назад. Работают они еще хорошо, но USB и TF портов, а так же блютуз у них нет. Однако из ситуации можно выкрутиться и дооснастить их недостающими современными возможностями. Полезна информация будет и тем, кто сам, своими руками мастерит и хочет сделать бумбокс. Модули небольшие, а разнообразие конструкций даст возможность выбрать нужный, подходящий под конкретные условия.

1. Модуль, определяющийся по блютуз, как BT-SPEAKER, имеет небольшой дисплей для индикации режимов работы, некое подобие спектроанализатора, читает флешки и карты памяти объемом до 32 ГГб. Оснащен Aux входом и FM радио. Питание от 5 до 12 Вольт. В комплекте имеется пульт ИК пульт ДУ. Кнопками на лицевой панели можно переключать треки и менять громкость. Габаритные размеры позволяют вмонтировать в магнитолы формата 1DIN. Можно найти этот же модуль, но с усилителями 2*3 Ватт.

2. Следующий модуль так же оснащен блютуз, Aux входом, FM радио, читает флешки и карты памяти, но уже формата SD (или microSD через переходник). Питание 5-12 Вольт, из форматов понимает MP3, WMA, WAV. Габаритные размеры 107x25x38 мм, установочные 83x20x38 мм. Есть эквалайзер и переход по папкам. Пульт ДУ в комплекте. Важный элемент функционала — есть физический выключатель питания, т.е. хорошо подойдет для бумбоксов.

3. Модуль похожий на предыдущий, но понимающий больше популярных форматов. Этот уже умеет воспроизводить MP3, WMA, WAV, FLAC и APE. Питание 12 Вольт. Набор интерефейсов стандартный — блютуз 5.0, USB, SD card, FM радио и Aux. Последнего нет на лицевой панели, подключается на плате и там же можно подключить микрофон, что даст возможность организовать громкую связь.

4. Следующая версия mp3 модуля кроме блютуз 5.0, FM радио и USB для чтения флешек имеет слот для microSD карт памяти и выведенные на переднюю панель вход Aux и микрофон. Можно не только разговаривать по телефону, но и записывать звук на карту. Из форматов — MP3, WMA, WAV, FLAC, APE. Пульт, питание 12 Вольт.

5. МР3 модуль со стандартным набором интерфейсов, но без микрофона (да, и не всем он нужен). Читать умеет мр3 и wav. Питание 5-12 Вольт. Отличительная особенность — плоская конструкция. Габаритные размеры 90*41 мм. Глубину конструкции можно оценить визуально в районе 20 мм, т.е. хорошо может вписаться на крышку подкассетника старой деки.

6. Еще один модуль в относительно плоском исполнении. Вполне возможно, что так же получится удачно инсталировать в подкассетник деки. Заявленные форматы — MP3, WMA, WAV, FLAC, APE. Габаритные размеры 75*50*31 мм. Можно заказать стального и черного цвета. Питание 5 или 12 Вольт. Есть микрофон и возможность выбора папки. Имеется предустановленный эквалайзер и FM радио. Пульт в комплекте.

7. Модуль глубиной всего 23 мм и хорошим набором читаемых форматов — MP3, WMA, WAV, FLAC, APE. Ссылка здесь. Есть радио, понимает карты памяти и флешки объемом до 32 ГГб. Есть предустановленный эквалайзер, повтор треков и спектроанализатор на экране бОльших размеров по сравнению с предыдущими образцами. Версия блютуз 5.0. Немного удивляет выбор цвета пластика панели, но модуль интересный и в самоделках может занять свое место.

8. Модуль с двумя линейными входами — один на передней панели, второй на плате. Кроме того блютуз, USB, SD карта и радио. Понимает WAV, MP3, WMA. Поддерживает китайские и английские тэги, можно переключать папки, записывает звук, подключается внешний микрофон. Есть эквалайзер, не сбрасывает настройки после отключения питания (12 Вольт). Габариты 135*63. Монтажная глубина 20 мм, с панелью и кнопками 28,5 мм.

9. Недавно появившийся модуль отличается хорошим внешним видом и человеческим пультом управления. Читает APE, FLAC, WMA, WAV, MP3, ААС и отображает теги на китайском и английском языках на экране размером 52*31 мм. Общие габариты модуля 106*66 мм, посадочные 86*50. Имеет прорезиненные кнопки, одна из которых отключает микрофон. Микрофон внешний и подключается сзади на плате. Кроме того можно подключить два линейных входа и энкодер для регулировки громкости. На экране имеется символ уровня заряда аккумулятора автомобиля. Можно делать запись с микрофона, радио или линейного входа. Есть эквалайзер, переходы по папкам, воспроизведение треков подряд, повтор одного или случайный выбор.

10. Самый интересный на мой взгляд и по отзывам знающих людей модуль, который может читать массу форматов, имеет спектроанализатор уже вполне «взрослого» вида, вход для внешнего микрофона для разговоров по телефону и записи звука. Есть эквалайзер, переход по папкам, выбор режимов воспроизведения. Питание 7-12 Вольт. Общие габариты 120*63, монтажные 101*53*20мм. Есть небольшая ошибка в оформлении — вместо CARD на передней панели написали CADR)), но это с лихвой перекрывается главным преимуществом данного модуля — им можно управлять с помощью приложения BTMate из PlayMarket.

У данной версии модуля (старшая модель) плата желто-оранжевого цвета с надписью AVN1715. Есть младшая версия с платой красного цвета. Экран там меньше, а приложение BluetoothBox. Однако новая версия модуля более продвинутая.