В статье описывается открытие WAV-файла с карты SD/MMC и воспроизведение его с помощью ШИМ (PWM, Pulse-Wide Modulation). Для упрощения допускаются только несжатые файлы звука (uncompressed PCM), 8 бит, моно, частота выборок 16000 Гц. Проект работает на макетной плате AT91SAM7X (микроконтроллер ARM Atmel AT91SAM7X256 или AT91SAM7X512), но его можно также запустить на макетных платах Atmel и Olimex. Для работы с файловой системой FAT32 используется библиотека EFSL.
Алгоритм работы проекта прост - через консоль DBGU (коннектор DB9 на макетной плате, подключенный к COM-порту компьютера) можно подавать команды на считывание и воспроизведение файлов с карты памяти SD/MMC. Файлы формата WAV (несжатый PCM, 8-бит, моно, скорость выборок 16000 Гц) должны лежать в каталоге wav. В консоли DBGU доступна система подсказки (команда h или ?).
-- PlayWav Project 1.0 --
Board : microsin AT91SAM7X, Chip ID : 0x275B0940
?
help<CR> or ? - help
reboot<CR> - restart/reload crontab
play filename.wav<CR> - play WAV-file
info<CR> - show info
play muson04.wav
play muson05.wav
play muzon06.wav
Не найден файл wav/muzon06.wav
info
[elapsed]
0 days, 0 hours, 0 minutes, 54 seconds
Звук формируется с помощью широтно-импульсной модуляции (PWM). Карта памяти подключена к разъему UEXT макетной платы Olimex SAM7-EX256 (U6 UEXT макетки AT91SAM7X), а PWM выведена на разъем EXT (U4 EXT макетки AT91SAM7X). Схемы макеток и подключения есть в каталоге doc архива проекта (см. ссылки в конце статьи).
В статье приведены только файлы, касающиеся открытия и воспроизведения WAV-файлов (projtypes.h, wav.h, wav.c). Полностью проект можно скачать по ссылке из конца статьи.
[projtypes.h]
#ifndef _TYPES_
#define _TYPES_
#include "settings.h"
#define u8 unsigned char
#define u16 unsigned short
#define u32 unsigned int
#define s8 signed char
#define s16 signed short
#define s32 signed int
#define true 1
#define false 0
#define bool u8
#define LEFT 0
#define RIGHT 1
typedef __packed struct _TWavBlock
{
u16 size;
u8 data[512];
}TWavBlock;
typedef __packed struct _TRIFFsection
{
char id[4]; // 4 byte
u32 len; // 4 byte
}TRIFFsection;
typedef __packed struct _TWavFmtGeneric
{
u16 compression; // 2 byte
u16 channels; // 2 byte
u32 samplerate; // 4 byte
u32 bytespersecond; // 4 byte
u16 blockalign; // 2 byte
u16 bitspersample; // 2 byte
}TWavFmtGeneric;
#endif //_TYPES_
[wav.h]
#ifndef _WAV_
#define _VAV_
#include "projtypes.h"
#define WAV_BUF_BLOCKS 16
#define WAV_BUF_BLOCKS_MASK (WAV_BUF_BLOCKS-1)
#define WAVQUEUE_SIZE 16
#define WAVQUEUE_SIZE_MASK (WAVQUEUE_SIZE-1)
#define WAVFILENAME_MAX_LEN 16
//коды ошибок, возвращаемые OpenWaveFile
#define WAVFILE_NOT_FOUND (1 << 0)
#define WAVFILE_HEADER_NO_DATA (1 << 1)
#define WAVFILE_SECTION_NO_DATA (1 << 2)
#define WAVFILE_FORMAT_ERR (1 << 3)
#define WAVFILE_EFSL_ERR (1 << 6)
extern TWavBlock wavbuf [WAV_BUF_BLOCKS];
extern u8 wavIn, wavOut;
extern u32 bytes_to_read;
extern char wavqueue [WAVQUEUE_SIZE][WAVFILENAME_MAX_LEN];
extern u8 inWav, outWav;
void wavreadPoll (void);
u8 OpenWaveFile (char* name, u32* datalen);
void ConfigureTc1 (void);
#endif // _WAV_
[wav.c]
#include <string.h>
#include <board.h>
#include <tc/tc.h>
#include <aic/aic.h>
#include "wav.h"
#include "efs.h"
#include "vars.h"
#include "isr.h"
#include "pins.h"
TWavBlock wavbuf [WAV_BUF_BLOCKS];
u8 wavIn, wavOut;
static EmbeddedFile wfile;
u32 bytes_to_read;
char wavqueue [WAVQUEUE_SIZE][WAVFILENAME_MAX_LEN];
u8 inWav, outWav;
TWavFmtGeneric WAVEfmt;
u8 StoredInBuffer (u8 idxIN, u8 idxOUT, u8 bufsize)
{
if (idxIN >= idxOUT)
return (idxIN - idxOUT);
else
return ((bufsize - idxOUT) + idxIN);
}
////////////////////////////////////////////////////////////////////////////////
// Таймер TC1 используется для записи выборок воспроизводимого звука в регистр
// PWM (настроен на 16000 Гц).
void ConfigureTc1 (void)
{
u32 tcclks; //поле TCCLKS в регистре TC_CMR (режим формирования)
u32 div; //коэффициент деления входной частоты при наших битиках tcclks
/// Конфигурируем Timer Counter 1 на частоту генерации выборок звука TC1_FREQ.
// Enable peripheral clock TC1
AT91C_BASE_PMC->PMC_PCER = 1 << AT91C_ID_TC1;
#define TC1_FREQ 16000
// Configure TC1 for a TC1_FREQ Hz frequency and trigger on RC compare
// if (TC_FindMckDivisor(TC1_FREQ, BOARD_MCK, &div, &tcclks))
// {
// TC_Configure(AT91C_BASE_TC1, tcclks | AT91C_TC_CPCTRG);
// AT91C_BASE_TC1->TC_RC = (BOARD_MCK / div) / TC1_FREQ; // timerFreq / desiredFreq
//
// // Configure and enable interrupt on RC compare
// AIC_ConfigureIT(AT91C_ID_TC1, AT91C_AIC_PRIOR_HIGHEST, /*(void(*)(void))*/ISR_Tc1);
// AT91C_BASE_TC1->TC_IER = AT91C_TC_CPCS;
// AIC_EnableIT(AT91C_ID_TC1);
//
// TC_Start(AT91C_BASE_TC1);
// //printf("TC1: TCCLKS=%u, div=%u, freq=%iHz\n\r", tcclks, div, TC1_FREQ);
// }
// else
// {
// printf("Cannot configure TC1 for freq %i Hz...\n\r", TC1_FREQ);
// }
div = 8;
tcclks = 1;
TC_Configure(AT91C_BASE_TC1, tcclks | AT91C_TC_CPCTRG);
AT91C_BASE_TC1->TC_RC = (BOARD_MCK / div) / TC1_FREQ; // timerFreq / desiredFreq
AIC_ConfigureIT(AT91C_ID_TC1, AT91C_AIC_PRIOR_HIGHEST, /*(void(*)(void))*/ISR_Tc1);
AT91C_BASE_TC1->TC_IER = AT91C_TC_CPCS;
AIC_EnableIT(AT91C_ID_TC1);
TC_Start(AT91C_BASE_TC1);
}
////////////////////////////////////////////////////////////////////////////////
// Функция открывает WAV-файл по имени name, считывает его параметры
// воспроизведения. На выходе функция возвращает длину аудиоданных *datalen,
// а также открытый файл, готовый к чтению в позиции секции аудиоданных.
// Если все в порядке, то результат функции 0, иначе результат содержит
// код ошибки.
u8 OpenWaveFile (char* name, u32* datalen)
{
TRIFFsection riffsection;
u32 rifflen;
u32 readed, portion;
char tmpbuf[512];
*datalen = 0;
file_opened = false;
//инициализируем файловую систему
if (!efs_initialized)
{
if ( efs_init (&efsl, 0) !=0 )
{
printf("Карта MMC не установлена");printf(CRLF);
return WAVFILE_EFSL_ERR;
}
else
{
efs_initialized = true;
}
}
//открываем файл
if (0 != file_fopen(&wfile, &efsl.myFs, name, 'r'))
{
printf ("Не найден файл %s", name);printf(CRLF);
CloseFileSystem(&wfile);
return WAVFILE_NOT_FOUND;
}
file_opened = true;
//прочитаем заголовок RIFF
memset (&riffsection, 0, sizeof(TRIFFsection));
readed = file_read(&wfile, sizeof(TRIFFsection), (u8*)&riffsection);
if ((sizeof(TRIFFsection) != readed) || efsl.myIOman.error)
{
printf ("ERR read RIFF header.");printf(CRLF);
CloseFileSystem(&wfile);
return WAVFILE_HEADER_NO_DATA;
}
//проверим заголовок RIFF
rifflen = riffsection.len;
if (0 != memcmp(riffsection.id, "RIFF", 4) || 0 == rifflen)
{
printf ("ERR in RIFF header.");printf(CRLF);
CloseFileSystem(&wfile);
return WAVFILE_FORMAT_ERR;
}
//прочитаем идентификатор типа WAV-файла
readed = file_read(&wfile, 4, (u8*)tmpbuf);
if ((4 != readed) || efsl.myIOman.error)
{
printf ("ERR read WAVE ID.");printf(CRLF);
CloseFileSystem(&wfile);
return WAVFILE_HEADER_NO_DATA;
}
rifflen -= readed;
//проверим идентификатор типа WAV-файла
if (0 != memcmp(tmpbuf, "WAVE", 4))
{
printf ("ERR WAVE ID.");printf(CRLF);
CloseFileSystem(&wfile);
return WAVFILE_FORMAT_ERR;
}
//читаем и пропускаем все секции, кроме "fmt " и "data"
memset (&WAVEfmt, 0, sizeof(TWavFmtGeneric));
while (rifflen)
{
readed = file_read(&wfile, sizeof(TRIFFsection), (u8*)&riffsection);
if ((sizeof(TRIFFsection) != readed) || efsl.myIOman.error)
{
printf ("ERR read file section.");printf(CRLF);
CloseFileSystem(&wfile);
return WAVFILE_SECTION_NO_DATA;
}
rifflen -= readed;
//секция формата?
if (0==memcmp("fmt ", riffsection.id, 4))
{
//проверим длину секции формата
if (sizeof(TWavFmtGeneric) != riffsection.len)
{
printf ("ERR not support extra WAV format.");printf(CRLF);
CloseFileSystem(&wfile);
return WAVFILE_FORMAT_ERR;
}
//прочитаем секцию формата
readed = file_read(&wfile, sizeof(TWavFmtGeneric), (u8*)&WAVEfmt);
if ((sizeof(TWavFmtGeneric) != readed) || efsl.myIOman.error)
{
printf ("ERR read WAV file format section.");printf(CRLF);
CloseFileSystem(&wfile);
return WAVFILE_SECTION_NO_DATA;
}
rifflen -= readed;
//тут надо проверить формат
if (1 != WAVEfmt.compression)
{
printf ("ERR compression not supported");printf(CRLF);
CloseFileSystem(&wfile);
return WAVFILE_FORMAT_ERR;
}
}
else if (0==memcmp("data", riffsection.id, 4))
{
if (0==WAVEfmt.channels)
{
printf ("ERR section 'data' before section 'fmt '");printf(CRLF);
CloseFileSystem(&wfile);
return WAVFILE_FORMAT_ERR;
}
else
{
//все ОК, выходим на позиции чтения данных
*datalen = riffsection.len;
return 0;
}
}
else
{
//не интересующая нас секция, просто пропускаем её данные
while (riffsection.len)
{
if (512 >= riffsection.len)
portion = riffsection.len;
else
portion = 512;
readed = file_read(&wfile, portion, (u8*)&tmpbuf);
if ((portion != readed) || efsl.myIOman.error)
{
printf ("ERR dummy read section %s.", riffsection.id);printf(CRLF);
CloseFileSystem(&wfile);
return WAVFILE_SECTION_NO_DATA;
}
rifflen -= readed;
riffsection.len -= readed;
}
}
}// while (rifflen): читаем секции WAV-файла
printf ("ERR not exist 'data' section");printf(CRLF);
CloseFileSystem(&wfile);
return WAVFILE_FORMAT_ERR;
}
////////////////////////////////////////////////////////////////////////////////
// Автоматом проигрывает файлы из очереди wavqueue.
void wavreadPoll (void)
{
u32 readed, portion;
char filepath [32];
if ((mode == MODE_IDLE)||(MODE_PLAY_DONE == mode))
{
if (inWav == outWav)
{
RADIOSEND_OFF();
return;
}
mode = MODE_INIT_PLAY;
}
else if (MODE_INIT_PLAY == mode)
{
strcpy(filepath, WAVFOLDER);
strcat(filepath, "/");
strcat(filepath, wavqueue[outWav++]);
outWav &= WAVQUEUE_SIZE_MASK;
errstate = OpenWaveFile(filepath, &bytes_to_read);
if ((0 == errstate)&&(0 != bytes_to_read))
{
//читаем новую порцию данных в буфер
if (512 > bytes_to_read)
portion = bytes_to_read;
else
portion = 512;
readed = file_read(&wfile, portion, wavbuf[wavIn].data);
if (readed != portion)
{
printf("ошибка чтения файла");printf(CRLF);
CloseFileSystem(&wfile);
bytes_to_read = 0;
mode = MODE_IDLE;
}
else
{
RADIOSEND_ON();
bytes_to_read -= portion;
wavbuf[wavIn].size = portion;
wavIn++;
wavIn &= WAV_BUF_BLOCKS_MASK;
if (0 == bytes_to_read)
CloseFileSystem(&wfile);
mode = MODE_PLAY_FILE;
}
}
else
{
bytes_to_read = 0;
mode = MODE_IDLE;
}
}
else if (MODE_PLAY_FILE == mode)
{
if (0 == bytes_to_read)
return;
//есть еще данные для чтения
if (WAV_BUF_BLOCKS-2 < StoredInBuffer(wavIn, wavOut, WAV_BUF_BLOCKS))
return;
//буфер не переполнен, читаем новую порцию данных в буфер
if (512 > bytes_to_read)
portion = bytes_to_read;
else
portion = 512;
readed = file_read(&wfile, portion, wavbuf[wavIn].data);
if (readed != portion)
{
printf("ошибка чтения файла");printf(CRLF);
CloseFileSystem(&wfile);
bytes_to_read = 0;
mode = MODE_IDLE;
}
else
{
bytes_to_read -= portion;
wavbuf[wavIn].size = portion;
wavIn++;
wavIn &= WAV_BUF_BLOCKS_MASK;
if (0 == bytes_to_read)
CloseFileSystem(&wfile);
}
}
}
[Ссылки]
1. Макетная плата AT91SAM7X.
2.
IAR EW ARM: как сделать USB Mass Storage Device на основе MMC/SD.
3.
IAR EW ARM: работа с файловой системой FAT на карточках SD/MMC (с использованием библиотеки EFSL).
4.
Wave File Format - формат звукового файла WAV.
5. Проект playwav для IAR Embedded Workbench for ARM 5.50.1, микроконтроллер ARM AT91SAM7X256 или AT91SAM7X512.
|
Комментарии
2011-01-1500:35:57 Очень сильно. Разрешите полюбопытствова ть, Андрей, это хобби или работа?
microsin: благодарю, Алексей. Рад, что Вас что-то заинтересовало на моем сайте. Статья, про которую Вы спрашиваете, составлена из кусочков, которые применяются в проектах, связанных с работой. Поскольку много заработать не очень-то получается, а работа интересная, то лучше всего назвать все это занятие "хобби".