Работа с SD картой. Воспроизведение wav файла. Ч3

17/03/2014 - 18:40 Pavel Bobkov

Введение

В этой статье мы рассмотрим практический пример использования 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 файл заканчивается таймер выключается, не "доигрывая" оставшееся содержимое буфера.


//****************************************
//
// Author(s)...: Pashgan
//
// Target(s)...: Mega16
//
// Compiler....: GCC
//
// Description.: Воспроизведение wav файла с SD карты
//
//****************************************

#include "compilers_4.h"
#include "diskio.h"
#include "pff.h"

/* выводы микроконтроллера */
#define LED_PORT PORTD
#define LED_DIR DDRD
#define LED_PIN 4

#define PWM_PORT PORTB
#define PWM_DIR DDRB
#define PWM_PIN 3

/* буфер */
#define BUF_SIZE 256UL
#define HALF_BUF ((BUF_SIZE)/2)

uint8_t buf[BUF_SIZE];

/*переменные для доступа к SD*/
FATFS fs;
WORD s1;
FRESULT res;

/*остальные переменные*/
typedef enum{ST_LOW_BUF, ST_HI_BUF, ST_STOP}state_t;
static state_t st;

static volatile uint8_t i;
static char f[] = "1.wav";

int main( void )
{
st = 0;
i = 0;

/*настройка шим выхода*/
PWM_DIR |= (1<<PWM_PIN);
PWM_PORT &= ~(1<<PWM_PIN);

/*статусный светодиод*/
LED_DIR |= (1<<LED_PIN);
LED_PORT &= ~(1<<LED_PIN);

/*монтируем диск, открываем файл, заполняем буфер*/
res = pf_mount(&fs);
if (res == FR_OK){
   res = pf_open(f);
   if (res == FR_OK){
      res = pf_read(buf, BUF_SIZE, &s1);
   }
}

/*если файл прочитался, то запускаем таймер*/
if (res == FR_OK){
   TCCR0 = 0;
   TCNT0 = 0;
   TIMSK |= (1<<TOIE0);
   TIFR = (1<<TOV0);
   TCCR0 = (1<<COM01)|(0<<COM00)|(1<<WGM01)|(1<<WGM00)|(1<<CS00);

   LED_PORT |= (1<<LED_PIN);
}
else{

   /*в противном случае зацикливаемся
   и мигаем светодиодом */
   while(1){
      LED_PORT ^= (1<<LED_PIN);
      _delay_ms(300);
   }
}

sei();
while(1){

switch (st){
case ST_LOW_BUF:

/*если индекс указывает на верхнюю половину
буфера, то заполняем нижнюю половину*/
if (i >= HALF_BUF) {
   pf_read(buf, HALF_BUF, &s1);
   if (s1 < HALF_BUF){
      TCCR0 = 0;
      PWM_PORT &= ~(1<<PWM_PIN);
      LED_PORT &= ~(1<<LED_PIN);
      st = ST_STOP;
   }
   else{
      st = ST_HI_BUF;
   }
}
break;

case ST_HI_BUF:
/*если индекс указывает на нижнюю половину
буфера, то заполняем верхнюю половину*/
if (i < HALF_BUF) {
   pf_read(&buf[HALF_BUF], HALF_BUF, &s1);
   if (s1 < HALF_BUF){
      TCCR0 = 0;
      PWM_PORT &= ~(1<<PWM_PIN);
      LED_PORT &= ~(1<<LED_PIN);
      st = ST_STOP;
   }
   else{
      st = ST_LOW_BUF;
   }
}
break;

case ST_STOP:
/*файл закончился, ничего не делаем*/
break;

default:
break;
}

}
return 0;
}

/*ШИМ, который формирует нам аналоговый сигнал*/
ISR(TIMER0_OVF_vect)
{
   uint8_t tmp = i;

   OCR0 = buf[tmp];
   tmp++;

   i = tmp;
}

Заключение

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

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

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

Файлы

SD-card-1-IAR.rar
SD-card-1-WinAvr.rar
SD-card-1-AS6.rar
SD-card-1-CodeVision.rar

Комментарии   

# frolikum 19.03.2014 08:12
А почему бы не использовать кварц на 16Mhz,
таймер T1 с ШИМОМ до ICR1. Так можно и точно подобрать частоту дискретизыции и МК будет с запасом по производительно сти.
Ответить | Ответить с цитатой | Цитировать
# Pashgan 19.03.2014 08:52
Я просто реализовал самое очевидное для себя решения. Одно из возможных. Потом мне пришла в голову мысль, что таймер Т0 можно тактировать через внешний вывод от таймера Т2. В этом случае подобрать частоту дискретизации проще.
Ответить | Ответить с цитатой | Цитировать
# Pashgan 19.03.2014 09:27
Еще хорошо получается 32 кГц при тактовой частоте 8 МГц. Но при этом на максимальной частоте SPI у меня не запускалась SD карта. Видимо из-за шлейфа и схемы сопряжения из резисторов.
Ответить | Ответить с цитатой | Цитировать
# dim 01.04.2014 13:40
А как на SDшку писать данные от контроллера? Например, писать на флешку время, в которое была нажата кнопка или что-нибудь подобное в какой-нибудь txt файл?
Ответить | Ответить с цитатой | Цитировать
# Pashgan 04.04.2014 15:33
В предыдущем материале были разобраны все функции библиотеки. Посмотри там пример.
Ответить | Ответить с цитатой | Цитировать
# вас 08.04.2014 03:56
здраствуйте, а можно проект для CodeVision?
Ответить | Ответить с цитатой | Цитировать
# Pashgan 08.04.2014 13:32
Выложу на днях. Он есть, только его надо залить на сайт.
Ответить | Ответить с цитатой | Цитировать
# hut 19.04.2014 15:16
Собираю проект в протеусе потестить как работает, не запускается((
это еще одна странность протеуса тип только в железе собирать ?
Ответить | Ответить с цитатой | Цитировать
# Pashgan 19.04.2014 15:51
Я проверял проект только в железе. В Proteus`e даже пробовать не стал. Скорее всего работать не будет.
Ответить | Ответить с цитатой | Цитировать
# ПАЧ 13.03.2015 22:09
У меня работает в протеусе, но он не успевает считать в реальном времени и звук не понятный. В реальности классно играет, я не ожидал, подключил к автономной колонке от телефонов, офигел прямо) не парился с кварцем, подобрал чатоту дискретизации в Sound Forge, на 8МГц там 31650 что ли вышло.
Ответить | Ответить с цитатой | Цитировать
# hut 26.04.2014 18:47
В самом начале воспроизведения файла всегда присутствует щелчок, как от него избавиться?
Ответить | Ответить с цитатой | Цитировать
# Pashgan 26.04.2014 19:29
В начале wav файла есть заголовок, содержащий служебную информацию. Нужно пропускать ее. В моем проекте это не сделано, но в тексте об этом указано.
Ответить | Ответить с цитатой | Цитировать
# hut 26.04.2014 19:52
поправка - первый файл при первом запуске функции воспроизведения (как первый раз включается (например, после ресета) - щелчок, потом в цикле все воспроизводится нормально без посторонних звуков, программу переделывал под себя для того, чтобы разобраться, этот кусок можно и не пропускать на самом деле в обоих вариантах эффект один), впринципе не критично, просто интересно откуда оно берется

попутно вопрос по Си: если у меня на флешке 100 файлов, то как организовать воспроизведение в зависимости от переменной - если n=1, то "1.wav", если 2, то "2.wav"? что-то не выходит с названиями
Ответить | Ответить с цитатой | Цитировать
# Pashgan 27.04.2014 11:04
Тогда это связано с полным заполнением буфера в начале программы.

Преобразовываеш ь переменную в строку и делаешь конкатенацию с ".wav". Можно свои функции сделать, можно использовать стандартные Си библиотеки.
Ответить | Ответить с цитатой | Цитировать
# hut 27.04.2014 15:16
Почитал методы по Си, получилось так, вроде работает, сожрало 250 байт примерно
Код:char str[]=".wav";
int i=56;
char buf[5];
utoa(i, buf, 10);

char full[7];

strcat(full, buf);
strcat(full, str);


Спасибо, за наставление на путь истинный
Если кто-то делал оптимальнее, тоже интересно посмотреть))
Ответить | Ответить с цитатой | Цитировать
# Serfeld 01.05.2014 09:12
Здравствуйте.Та кой же вопрос по воспроизведению нескольких файлов.Попробов ал вариант от hut.Компилятор принял,но в железе файл не находится.Интер есно,если переменная равна единице,то строка будет выглядеть как "1.wav" или как-то иначе?Например, "01.wav"...
Ответить | Ответить с цитатой | Цитировать
# ПАЧ 13.03.2015 22:13
попробуйте писать большими буквами, у меня с маленькими тоже не работает. "1.WAV"
Ответить | Ответить с цитатой | Цитировать
# hut 01.05.2014 12:31
я протестировал сначала на ЖК - чтоб убедиться как сливаются строки - если неправильно задать размеры строк, то полная строка выходит совсем не такой как надо, поэтому и железо может не видеть
Код:char str[]=".wav"; // расширение, которое нужно прилепить к названию файла
int i=57; // переменная, определяющая название файла
char buf[10]; // строка, куда преобразуется название файла
itoa(i, buf, 10); // число превращаем в строку (utoa - беззнаковый перевод, itoa - знаковый перевод)

char full[10]; // строка, в которой будут склеины 2 других - получится название файла

strcat(full, buf); // этой функцией заносим (лепим) первую часть строки
strcat(full, str); // затем вторую

протестил строго для воспроизведения wav - так все работает, по крайней мере у меня :P
Ответить | Ответить с цитатой | Цитировать
# hut 01.05.2014 12:57
Хотя все равно через раз работает(((
то воспроизводит, то нет, печалька, всегда нужно раз 20 проверить, а потом говорить, что работает :cry:
ума не приложу как решить проблему, строки как то странно преобразовывают ся, раз на раз не приходится (если что я Си язык первый месяц вижу, совсем не программист)
Ответить | Ответить с цитатой | Цитировать
# hut 01.05.2014 13:22
А еще можно на switch конструкции сделать - памяти жрет меньше даже и надежнее, но 100 раз переписывать case утомительно
Код:int j=1; // переменная, определяющая название файла

switch (j)
{
case (1): {if (j!=1) {break;} wave_play ("1.wav");}
case (2): {if (j!=2) {break;} wave_play ("2.wav");}
case (3): {if (j!=3) {break;} wave_play ("3.wav");}
case (4): {if (j!=4) {break;} wave_play ("4.wav");}
case (5): {if (j!=5) {break;} wave_play ("5.wav");}
case (6): {if (j!=6) {break;} wave_play ("6.wav");}
case (7): {if (j!=7) {break;} wave_play ("7.wav");}
case (8): {if (j!=8) {break;} wave_play ("8.wav");}
case (9): {if (j!=9) {break;} wave_play ("9.wav");}
case (10): {if (j!=10) {break;} wave_play ("10.wav");}
case (11): {if (j!=11) {break;} wave_play ("11.wav");}
case (12): {if (j!=12) {break;} wave_play ("12.wav");}
case (13): {if (j!=13) {break;} wave_play ("13.wav");}
case (14): {if (j!=14) {break;} wave_play ("14.wav");}
case (15): {if (j!=15) {break;} wave_play ("15.wav");}
case (16): {if (j!=16) {break;} wave_play ("16.wav");}
case (17): {if (j!=17) {break;} wave_play ("17.wav");}
case (18): {if (j!=18) {break;} wave_play ("18.wav");}
case (19): {if (j!=19) {break;} wave_play ("19.wav");}
case (20): {if (j!=20) {break;} wave_play ("20.wav");}
case (21): {if (j!=21) {break;} wave_play ("21.wav");}
case (57): {if (j!=57) {break;} wave_play ("57.wav");}
}
Ответить | Ответить с цитатой | Цитировать
# Serfeld 02.05.2014 14:05
И всё таки как у тебя надёжнее!!!
Ответить | Ответить с цитатой | Цитировать
# JoJo 01.05.2014 14:25
Если не заморачиваться, то можно так.

Код:
char bufs[10];

void Conv(uint8_t value, char *buf)
{
uint8_t i, j;
uint8_t tmp;
char s[3];


j = 0;

for(i = 0; i < 3; i++){
tmp = ((value % 10) + 48);
value = value/10;
s[j++] = tmp;
}

j = 0;

if (s[2] != '0'){
buf[j++] = s[2];
}

if (s[1] == '0'){
if (s[2] != '0'){
buf[j++] = s[1];
}
}
else{
buf[j++] = s[1];
}

buf[j++] = s[0];

buf[j++] = '.';
buf[j++] = 'w';
buf[j++] = 'a';
buf[j++] = 'v';
buf[j] = 0;
}

..
//записывает в buf "12.wav"
Conv(12, buf);

Ответить | Ответить с цитатой | Цитировать
# Serfeld 01.05.2014 17:24
У меня тоже работает надёжно,только не через switch,как у hut,а через if только флэша отъела поболе.Думал,мо жно легче т.к делаю на atmega8...Скомп илировал switch - гораздо легче.А скопировать строки,поменяв переменные-дело минутное.Главно е-работает...Ви дел проект на attiny2313,там файл определяется по одной заглавной букве латинского алфавита-осталь ные не имеют значения,включа я "wav",только получается ограничение по количеству файлов...Афтор ваяет на визуальном ассемблере...
Ответить | Ответить с цитатой | Цитировать
# hut 01.05.2014 17:57
я тоже продумывал уже сделать как Serfeld сначала, но на if/else это ж еще 100 условий будет, а на swith всего одно, я прям не ожидал даже такой компактности - съело на те 22 кейса 6 байт, думал ошибка, перепроверил, перекомпилирова л, все верно.

Такое дело, на сайте этой либы Petit Fat FS написано очень малый размер кода - 2-4 кб, что автор имел ввиду? у меня без всего лишнего 4600 б (чтение + поддержка fat32) и код примерно как Павла только на чтение без пропуска части файла:) а так вообще 6 - 8 кб в полном составе функций

Да, у getchiper компактно все вышло, но уж слишком много ограничений - количество файлов, не читает SD более 2 Гб, да и к тому же АБ не знаком и не вижу смысла его вкуривать, Си наше все, хотя на нем иногда не слишком компактно выходит
Ответить | Ответить с цитатой | Цитировать
# Pashgan 01.05.2014 22:50
2-4 кБ это размер скомпилированно го модуля pff.c Так и есть, я проверял.

Если смотреть на проект, который я выложил, то при таких настройках:
Код:
#define _USE_READ 1
#define _USE_DIR 0
#define _USE_LSEEK 0
#define _USE_WRITE 0
#define _FS_FAT12 1
#define _FS_FAT32 1

модуль требует 2.5 кБ флэша
Ответить | Ответить с цитатой | Цитировать
# hut 02.05.2014 13:03
у меня ~ 4,5 кб с такими настройками (AS4, все никак на 6 не перейду), даже если из проекта удалить все кроме инициализации таймеров, 4300 б (ничего, кроме Petit, не подключено), брал твои библиотеки с SPI с этой статьи, неужели это компилятор?
Ответить | Ответить с цитатой | Цитировать
# Serfeld 01.05.2014 20:06
ИМХО Си при нынешних объёмах флэша можно простить некоторую некомпактность. Да и никто не отменял ассемблерных вставок,где нужна шустрость и точность выдержек...Кста ти,собрал конструкцию getchipera-руга ется на то,что не FAT16 и дальше не идёт....
Ответить | Ответить с цитатой | Цитировать
# Serfeld 01.05.2014 22:02
Вариант JoJo к сожалению в моей железяке не пошёл,хотя файл,похоже, узнаётся...В конце кода наверно Conv(12, bufs);
Остановился на варианте hut с функцией switch .32 файла вполне достаточно.А если убрать ЖКИ-можно будет и поболе.Благодар ю!!!
Pashgan!Огромна я благодарность за проделанную работу!У меня Atmega8,питание 5V,кварц на 8mHz,микроSD карта 2Гига через резисторные делители и кренкой на 3.3V.Частота дискредитации 22.050Hz.Звук через высокоомный наушник чисто без помех и щелчков.И вообще это единственный проект из просторов инета,который заработал сразу и с открытым кодом,дающим возможность практически разобраться в этом вопросе с возможностью подогнать под свои девайсы.Благода рю!!!
Ответить | Ответить с цитатой | Цитировать
# Pashgan 01.05.2014 22:45
Пожалуйста.
Ответить | Ответить с цитатой | Цитировать
# Damir 03.03.2016 08:25
Посмотрите, пожалуйста, мой вопрос внизу. Спасибо.
Ответить | Ответить с цитатой | Цитировать
# Serfeld 02.05.2014 08:20
Кстати,у меня работает более лёгкий вариантКод:
switch (j)
{
case 1: wave_play ("1.wav"); break;
case 2: wave_play ("2.wav"); break;
..................................
case n:wave_play ("n.wav");break;
case m:wave_play ("m.wav");
.
Ответить | Ответить с цитатой | Цитировать
# hut 02.05.2014 17:29
Просто, насколько я понимаю, так будет воспроизводится весь список до конца начиная с кейса со значением переменной: если переменная 10, то отработают все кейсы с 10 до m-го или если переменная 17, то с 17го по m-ный кейс. Здесь break срабатывает после воспроизведения и только на определенный кейс в котором он прописан, а не на switch вцелом
это я просто от "учебников" не ушел еще далеко))
Ответить | Ответить с цитатой | Цитировать
# Serfeld 02.05.2014 18:44
Я не встречал ранее такой конструкции,как предложенную тобой.По сути это одно и то же,только у твоей break,то бишь выход, происходит строкой ниже,но работает она в данной проге надёжнее моей-почему?Не знаю.Очевидно,н адо обращаться к магистрам...
Ответить | Ответить с цитатой | Цитировать
# hut 02.05.2014 19:42
выход из кейса строкой выше по условию, если переменная НЕ равна номеру данного кейса, иначе воспроизвести трек - так проработает только 1 нужный нам кейс, остальные пропустятся.
Меню на этой конструкции получаются неплохо
Читай мануалы DI HALT по программировани ю на Си, я от туда начинал, хотя у него много больше материала по Асму
Ответить | Ответить с цитатой | Цитировать
# Serfeld 03.05.2014 11:54
Собственно,суть работы этой конструкции понятна.Я тут почитал сравнительный анализ
switch и if/else.По сути принципиальной разницы по скорости и весу не отмечается.Поэт ому вернулся к своему изначальному варианту...Оста лась загвоздка в конкатенации.Иб о,чем больше файлов,тем этот вариант становится привлекательнее .Принципиально то,что у тебя и у JoJo должно работать,но почему-то не работает....Воз можно из-за незначащих нулей в процессе преобразования - я ниже писал об этом.И тут всё таки стоит разобраться,хот я бы на будущее...
Ответить | Ответить с цитатой | Цитировать
# JoJo 03.05.2014 12:23
У меня нули не пишутся. 1 преобразовывает ся в 1.wav, а не 001.wav.
Ответить | Ответить с цитатой | Цитировать
# Serfeld 02.05.2014 09:56
Интересное наблюдение.Есть две SD карты на 32MB и 2Гига.Обе работали нормально,пока не попробовал их на питание от 5V.Теперь у карты на 32MB при воспроизведении сильный треск .Вторая работает абсолютно без помех.Хотя в компе обе работают нормально...
Ответить | Ответить с цитатой | Цитировать
# JoJo 02.05.2014 10:20
У них максимальное напряжение питания 3.6 волт. Зачем включить их от 5?
Ответить | Ответить с цитатой | Цитировать
# Serfeld 02.05.2014 12:51
Дык в инете многие пишут,что выдерживают и 5v,по крайней мере в процессе наладки.А умные они ж на чужих ошибках учатся.А мне ж захотелось на своих...
Ответить | Ответить с цитатой | Цитировать
# Serfeld 02.05.2014 13:27
Есть соображение,поч ему не идёт преобразования переменной и строки в строку.Загвоздк а в незначащих нулях.Я попробовал переименовать файлы в switch (j)
типа "01wav" и пошли глюки.Т.е либо в преобразовании нужно как-то убирать эти нули,либо переменная должна начинаться с 10
и закансиваться 99...Если есть желание -поиграйтесь.Я уже замучил железяку прошивками.А Proteus не моделирует....
Ответить | Ответить с цитатой | Цитировать
# Sanya 17.12.2015 16:46
Нужна ваша помощь. skyline.alex2010@yandex.ru
Ответить | Ответить с цитатой | Цитировать
# Serfeld 03.05.2014 22:28
Ура!!!Вариант JoJo ЗАРАБОТАЛ!!!РЕС ПЕКТ!!!Теперь файлов можно делать миллион до неба,не раздувая код!Очевидно первая попытка проверить в железе делалась кривыми руками автора этих строк.А вариант hut к сожалению не работает,хотя ИМХО всё грамотно...Надо подумать...Да здравствует конкатенация усилий!!!
Ответить | Ответить с цитатой | Цитировать
# Юрийwind 04.07.2014 01:15
собрал на mega8 - работает отлично. сделал переносной вариант: mega8L, питание 3.3в. не работает... подскажите, пожалуйста, куда копать?
Ответить | Ответить с цитатой | Цитировать
# tw1cker 30.11.2014 03:20
Цитирую Юрийwind:
собрал на mega8 - работает отлично. сделал переносной вариант: mega8L, питание 3.3в. не работает... подскажите, пожалуйста, куда копать?

А можете поделиться проектом под мегу8
Ответить | Ответить с цитатой | Цитировать
# Юрийwind 30.11.2014 03:39
Поделюсь, но через пару недель. Влез в долги - купил машину. Временно (до зарплаты) я без интернета, а со смартфона писать геморно. Скиньте свой e-mail на wind_sto@mail.ru
Ответить | Ответить с цитатой | Цитировать
# MyBruin 09.01.2015 20:25
Добрый день! Прошу совета, собрал устройство на ATMEGA32 16Mhz шим настроил на (0
Ответить | Ответить с цитатой | Цитировать
# MyBruin 09.01.2015 20:26
Добрый день! Прошу совета, собрал устройство на ATMEGA32 16Mhz шим настроил на Код: (0<<CS02)|(1<<CS01)|(0<<CS00); . частоту и изменил TCNT0 = 180; При воспроизведении звука слышен сильный треск. И мне не понятно почему увеличение кода заполнения буфера (мне нужно одновременно воспроизводить фоновую музыку и ситуационную) приводит к замедлению воспроизведения (пробовал увеличивать буфер не помогает). Каким образом лучше воспроизводить звук при кварце 16Mhz (как это сделать не представляю Потом мне пришла в голову мысль, что таймер Т0 можно тактировать через внешний вывод от таймера Т2. В этом случае подобрать частоту дискретизации проще)
Ответить | Ответить с цитатой | Цитировать
# Олег777 20.05.2015 21:34
Добрый день. Подскажите как лучше сделать проигрывание WAV на STM32F030K6T6. Пришлось задействовать 2 таймера (1й TIM3 под ШИМ 23.4375 КГц) второй (TIM16) под выборку из буфера 22,038 КГц. А на AVR как то на одном таймере работает, может и на STM можно упростить. Если не жалко дайте рабочий пример если у кого есть.
Ответить | Ответить с цитатой | Цитировать
# Proton 24.06.2015 20:50
А почему на линии MISO напряжение 5В? Вроде как входной порт. Динамик попищал и вскоре карточка перестала определяться, видимо сдохла... :sad:
Ответить | Ответить с цитатой | Цитировать
# DVF 21.08.2015 11:36
Братцы! Как правильно прервать воспроизведение ? В частности, в примерах от Чена (файл sound.c, функция "load_wav") цикл, начинающийся:
Код: k = 0; wi = 0;
while (szwav || fcb.ct >= 4) {
if (szwav && fcb.ct <= sz_work / 2) { /* Refill FIFO when it gets half empty */

крутится пока не закончится файл и мирным способом прервать его у меня не получается.
Ответить | Ответить с цитатой | Цитировать
# Айрат 02.09.2015 08:44
Брейком в цикле не пробовали?
Ответить | Ответить с цитатой | Цитировать
# DVF 04.10.2015 14:19
Не брейком - if на фрагменте кода выше преобразовал в if else и в else:
Код:else
{
if (WavStreamAbort | v_WavStreamAbort)
{
szwav = 0;
fcb.ct = 0;
}
}

В общем, устраиваю false в цикле while.
Ответить | Ответить с цитатой | Цитировать
# sirQWERTY 06.12.2015 19:34
Добрый день. Никак не найду, где описаны эти функции:
Код:void init_spi (void); /* Initialize SPI port (usi.S) */
void dly_100us (void); /* Delay 100 microseconds (usi.S) */
void xmit_spi (BYTE d); /* Send a byte to the MMC (usi.S) */
BYTE rcv_spi (void);

Ткните плз носом...
Ответить | Ответить с цитатой | Цитировать
# sirQWERTY 07.12.2015 10:38
Все, нашел. Смотрел файл mmc.c из примеров Чана, а думал, что из скачанного здесь архива, а там все описано в ассемблерных вставках.
Ответить | Ответить с цитатой | Цитировать
# SANYAx1 19.12.2015 15:03
Исправьте ошибку в описании:
Fpwm = (Fcpu/k)/2^8 = Fcpu/(k*256) на
Fpwm = (Fcpu/k)/2^8 = Fcpu/(k/256)
Ответить | Ответить с цитатой | Цитировать
# Damir 03.03.2016 08:24
Можно ли переделать на mega8? Что то с таймером не могу разобраться. И поддерживает ли данная программа FAT32? Спасибо.
Ответить | Ответить с цитатой | Цитировать
# Damir 03.03.2016 09:57
C FAT32 разобрался, поддерживает. Определяется, короче светодиод 1 раз моргает и все. Значит находит файл, читает. Но не могу воспроизвести, с таймером и с выводом разобраться надо(
Ответить | Ответить с цитатой | Цитировать
# Юрийwind 04.03.2016 05:56
Damir, куда скинуть архив с исходником?
Ответить | Ответить с цитатой | Цитировать
# Константин 20.03.2016 15:00
Добрый день, собираю данный проект на atmega328p. Файл то , воспроизводит, но с сопровождением лютого писка. Может кто что подскажет?
//настройка таймера
Код:
TCCR0A = 0;
TCNT0 = 0;
TIMSK0 = 1<<TOIE0;
TIFR0 = (1<<TOV0);
TCCR0A = (1<<COM0B1)|(0<<COM0B0)|(1<<WGM01)|(1<<WGM00);
TCCR0B = (0<<CS02)|(1<<CS01)|(0<<CS00);
Ответить | Ответить с цитатой | Цитировать
# Васян 06.04.2016 14:16
Удалось запустить на 328р? Пытаюсь повторить, и ничего кроме писка получить не удалось. Можете поделиться своими исходниками?
Ответить | Ответить с цитатой | Цитировать
# Oleg 23.03.2016 17:29
Настроил под таймер1 для частоты мк 8Мгц и при этом wav настроил на частоту дискретизации 24 кГц. Короче режим Fast PWM top = OCR1A. выходом на динамик будет уже OCR1B в прерываниий туда переписываем значение от buf.Вот такие измениния в проге Код:if (res == FR_OK){ // Clock value: 8000,000 kHz 1 такт =0.125 мкс // Mode: Fast PWM top=OCR1A // OC1B output: Non-Inv. // Compare A Match Interrupt: On TCCR1A=0x23; TCCR1B=0x19; TIMSK=0x10; //Настройка OCR1A для частоты дискретизации звука 24 kHz //24 kHz = 41.6 мкс /0,125 мкс = 333 OCR1A=333; LED_PORT |= (1<<LED_PIN); }
а прерывание
Код:interrupt [TIM1_COMPA] void timer1_compa_isr(void) { uint8_t tmp = i; OCR1B = buf[tmp]; tmp++; i = tmp; }
Ответить | Ответить с цитатой | Цитировать
# Oleg 23.03.2016 17:34
Да кстати еще чтобы не устанавливать программы для конвертирования звука нашел сайт где все это делается онлайн быстро и понятно audio.online-convert.com/ru/convert-to-wav
Ответить | Ответить с цитатой | Цитировать
# Proton 01.04.2016 08:32
>>Константин
Цитата:
с сопровождением лютого писка. Может кто что подскажет?
Ты забираешь с карты байты со слышимой частотой, нужно повысить вот так:
TCCR1B = (0
Ответить | Ответить с цитатой | Цитировать
# Proton 01.04.2016 08:33
У сайта явные проблемы с выводом комментариев.
TCCR1B = (0
Ответить | Ответить с цитатой | Цитировать
# Proton 01.04.2016 08:35
Aaaaaaaaaa!! В общем так: TCCR1B= 1;
Ответить | Ответить с цитатой | Цитировать
# Васян 06.04.2016 14:19
Буду благодарен если кто-нибудь поделится исходниками для Atmega328p. Пытался повторить, но получается только писк. Почта xehhxtkf@yandex.ru
Ответить | Ответить с цитатой | Цитировать
# Юрий420 27.05.2016 05:42
Доброго времени суток! Знатоки помогите, имеется wav 16bit mono. Мне надо его в теле прерывания сделать 8 битным и положить регистр шима. Интересует сам алгоритм, как из двух переменных типа char (младшее и старшее) значения выборки сделать готовое значение 8 бит и положить в шим. Спасибо
Ответить | Ответить с цитатой | Цитировать
# Stas 27.05.2016 08:03
Использовать только старшие 8 бит...
Ответить | Ответить с цитатой | Цитировать
# Stas 27.05.2016 07:59
Проект для CodeVision не компилируется, выдаёт ошибки ???...
Ответить | Ответить с цитатой | Цитировать
# Stas 30.05.2016 08:41
В CVavr 2.04 всё работает ...
Ответить | Ответить с цитатой | Цитировать
# Юрий420 27.05.2016 15:30
Stas - Не прокатит качество в обще получается никакое, не могу понять(( Может кто натолкнет хотябы на идею?
Ответить | Ответить с цитатой | Цитировать
# Павел Сметанников 27.05.2016 20:39
Какую идею? как в 8 бит засунуть 16?) Никак- старшие 8 бит это и есть твое преобразование. Может у тебя там пусто в старших байтах, тогда методом научного тыка берешь ((HBx)&0xFF, где х от 0 до 8, и слушаешь, что вышло.
Ответить | Ответить с цитатой | Цитировать
# Павел Сметанников 27.05.2016 20:41
Написал одно а выложилось другое что за сайт... ((HBx)&0xFF
Ответить | Ответить с цитатой | Цитировать
# Павел Сметанников 27.05.2016 20:42
это я туплю тут теги нужны оказывается Код: ((HB<<8+LB)>>x)&0xFF
Ответить | Ответить с цитатой | Цитировать
# Юрий420 31.05.2016 16:12
Я так понимаю мне надо соединить старшую часть и младшую часть выборки расширением shar в знаковою переменную int, потом умножить на коэффициент и сместить нулевую точку. Если так то как соединить две shar в одну int? Я знаю как соединять, но для этого надо несколько битовых операций, получается не очень коротко. Может я ошибаюсь и мне ктото подскажет правильный путь
Ответить | Ответить с цитатой | Цитировать
# Михаил1991 12.07.2016 07:35
Добрый день! Ребята, подскажите пожалуйста почему при воспроизведении появляется треск в колонках, у этого треска частота 208Гц. Спасибо!
Ответить | Ответить с цитатой | Цитировать
# Михаил1991 12.07.2016 14:34
От треска избавился путем добавления последовательно к RC фильтру конденсатора 0.1мкФ, таким образом на усилитель не попадала постоянная составляющая
Ответить | Ответить с цитатой | Цитировать
# Дмитрий34 07.01.2017 23:38
Добрый день! Прошу подсказки, собрал проект на mega 328p, 8mhz, wav pcm 8bit 32khz, всё сразу заработало но воспроизводит с треском, пробовал разные карты, все то же самое, закинул вавку с голосом и в моменты тишины треска нет, только при большей амплитуде видимо, настройки таймера стандартные:

Код:
TCNT0 = 0;
TIMSK0 |= (1<<TOIE0);
TIFR0 = (1<<TOV0);
TCCR0A = (1<<COM0A1)|(0<<COM0A0)|(1<<WGM01)|(1<<WGM00);
TCCR0B = (0<<CS02)|(0<<CS01)|(1<<CS00);


Скорость SPI не менял, все как тут в проекте:

Код:
SPCR = (0<<SPIE)|(1<<SPE)|(0<<DORD)|(1<<MSTR)|(0<<CPOL)|(0<<CPHA)|(0<<SPR1)|(0<<SPR0);
SPSR = (1<<SPI2X);


Комбинировал разное подключение RC фильтра, ничего не меняется, такой же треск как избавиться ? Что это примерно может быть ?
Ответить | Ответить с цитатой | Цитировать
# Дмитрий34 10.01.2017 22:59
Вопрос снимается, частота ШИМ при 8MHz (31250KHz) не позволяет полноценно воспроизводить на этом семпле трек, запустил камень на 16MHz (ШИМ 62500KHz) + еще один таймер на 44198KHz, всё играет как положено, очень рад )
Ответить | Ответить с цитатой | Цитировать

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

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