sam3u-tests/basic-ssc-i2s-wm8731-project/main.c

539 lines
19 KiB
C

/* ----------------------------------------------------------------------------
* ATMEL Microcontroller Software Support
* ----------------------------------------------------------------------------
* Copyright (c) 2008, Atmel Corporation
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the disclaimer below.
*
* Atmel's name may not be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
* DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* ----------------------------------------------------------------------------
*/
//-----------------------------------------------------------------------------
/// \dir "Basic SSC-I2S WM8731 project"
///
/// !!!Purpose
///
/// This example uses the Synchronous Serial Controller (SSC) of an AT91 microcontroller
/// to output an audio steam through the on-board WM8731 CODEC.
///
///
/// !!!See
/// - ssc: SSC driver interface
///
/// !!!Description
///
/// This program plays a WAV file pre-loaded into the SDcard. The audio stream is sent through
/// the SSC interface connected to the on-board WM8731, enabling the sound to be audible using a pair of headphones.
///
/// Since the WM8731 DAC requires that it be feeded a master clock multiple of the sample rate,
/// it is difficult to handle any WAV file. As such, this example application is limited to playing files with the following format:
/// - Format: WAV
/// - Sample rate: 48 kHz
///
/// !!!Usage
///
/// -# Build the program and download it inside the evaluation board. Please
/// refer to the
/// <a href="http://www.atmel.com/dyn/resources/prod_documents/doc6224.pdf">
/// SAM-BA User Guide</a>, the
/// <a href="http://www.atmel.com/dyn/resources/prod_documents/doc6310.pdf">
/// GNU-Based Software Development</a> application note or to the
/// <a href="ftp://ftp.iar.se/WWWfiles/arm/Guides/EWARM_UserGuide.ENU.pdf">
/// IAR EWARM User Guide</a>, depending on your chosen solution.
/// -# On the computer, open and configure a terminal application
/// (e.g. HyperTerminal on Microsoft Windows) with these settings:
/// - 115200 bauds
/// - 8 bits of data
/// - No parity
/// - 1 stop bit
/// - No flow control
/// -# Start the application.
/// -# In the terminal window, the following text should appear:
/// \code
/// -- Basic SSC I2S WM8731 Project xxx --
/// -- AT91xxxxxx-xx
/// -- Compiled: xxx xx xxxx xx:xx:xx --
/// Menu :
/// ------
/// W: Play the WAV file pre-loaded in SD Card
/// I: Display the information of the WAV file
/// \endcode
/// The user can then choose any of the available options to perform the described action.
///
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
/// \unit
///
/// !Purpose
///
/// This file contains all the specific code for the
/// basic-ssc-i2s-wm8731-project
///
/// !Contents
///
/// The code can be roughly broken down as follows:
/// - Enable the clock
/// - Load WAV file information
/// - Configure and enable the Codec
/// - Configure and enable the SSC interrupt
/// - Play WAV file
///
/// Please refer to the list of functions in the #Overview# tab of this unit
/// for more detailed information.
//-----------------------------------------------------------------------------
//------------------------------------------------------------------------------
// Headers
//------------------------------------------------------------------------------
#include <board.h>
#include <board_memories.h>
#include <pio/pio.h>
#include <irq/irq.h>
#include <twi/twid.h>
#include <twi/twi.h>
#include <dbgu/dbgu.h>
#include <ssc/ssc.h>
#include <codec-wm8731/wm8731.h>
#include <utility/wav.h>
#include <utility/assert.h>
#include <utility/math.h>
#include <utility/trace.h>
#include <memories/MEDSdcard.h>
#include "fatfs_config.h"
#if _FATFS_TINY != 1
#include <fatfs/src/ff.h>
#else
#include <fatfs/src/tff.h>
#endif
#include <fatfs/src/ff_util.h>
#include <string.h>
#define AUDIO_USING_DMA
#if defined (AUDIO_USING_DMA)
#include <dmad/dmad.h>
#include <dma/dma.h>
#endif
//------------------------------------------------------------------------------
// Local constants
//------------------------------------------------------------------------------
/// Master clock frequency in Hz
#define SSC_MCK 49152000
/// Address at which the WAV file is located
#define WAV_FILE_ADDRESS (0x60000000 + 0x100)//0x8000)
/// Maximum size in bytes of the WAV file.
#define MAX_WAV_SIZE 0x100000
// TWI clock
#define TWI_CLOCK 100000
// PMC define
#define AT91C_CKGR_PLLR AT91C_CKGR_PLLAR
#define AT91C_PMC_LOCK AT91C_PMC_LOCKA
#define AT91C_CKGR_MUL_SHIFT 16
#define AT91C_CKGR_OUT_SHIFT 14
#define AT91C_CKGR_PLLCOUNT_SHIFT 8
#define AT91C_CKGR_DIV_SHIFT 0
/// Maximum number of LUNs which can be defined.
/// (Logical drive = physical drive = medium number)
#define MAX_LUNS 1
/// Available medias.
Media medias[MAX_LUNS];
#define ID_DRV DRV_MMC
#define SAMPLE_RATE (48000)
#define SLOT_BY_FRAME (2)
#define BITS_BY_SLOT (16)
#define AT91C_I2S_MASTER_TX_SETTING(nb_bit_by_slot, nb_slot_by_frame)( +\
AT91C_SSC_CKS_DIV +\
AT91C_SSC_CKO_CONTINOUS +\
AT91C_SSC_START_FALL_RF +\
((1<<16) & AT91C_SSC_STTDLY) +\
((((nb_bit_by_slot*nb_slot_by_frame)/2)-1) <<24))
#define AT91C_I2S_TX_FRAME_SETTING(nb_bit_by_slot, nb_slot_by_frame)( +\
(nb_bit_by_slot-1) +\
AT91C_SSC_MSBF +\
(((nb_slot_by_frame-1)<<8) & AT91C_SSC_DATNB) +\
(((nb_bit_by_slot-1)<<16) & AT91C_SSC_FSLEN) +\
AT91C_SSC_FSOS_NEGATIVE)
//------------------------------------------------------------------------------
// Local variables
//------------------------------------------------------------------------------
/// List of pins to configure.
static const Pin pins[] = {PINS_TWI0, PINS_SSC_CODEC, PIN_PCK0};
/// Pointer to the playback WAV file header.
static const WavHeader *userWav = (WavHeader *) (0x60000000);
/// Indicates if the WAV file is currently being played.
static unsigned char isWavPlaying;
#if defined (AUDIO_USING_DMA)
/// Number of samples which have already been transmitted.
static unsigned int transmittedSamples;
/// Number of samples that have not yet been transmitted.
static unsigned int remainingSamples;
#endif
#if _FATFS_TINY == 0
#define STR_ROOT_DIRECTORY "0:"
#else
#define STR_ROOT_DIRECTORY ""
#endif
#if defined(at91cap9stk)
#define MCI_ID 1 //no connector for MCIO/SPI0
#else
#define MCI_ID 0
#endif
const char* FileName = STR_ROOT_DIRECTORY "sample.wav";
static Twid twid;
//------------------------------------------------------------------------------
// Local functions
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
/// Display the information of the WAV file (sample rate, stereo/mono and frame
/// size) on the DBGU.
//------------------------------------------------------------------------------
static void DisplayWavInfo(void)
{
printf("%c[2J", 27);
printf( " Wave file header information\n\r");
printf( "--------------------------------\n\r");
printf( " - Chunk ID = 0x%08X\n\r", userWav->chunkID);
printf( " - Chunk Size = %d\n\r", userWav->chunkSize);
printf( " - Format = 0x%08X\n\r", userWav->format);
printf( " - SubChunk ID = 0x%08X\n\r", userWav->subchunk1ID);
printf( " - Subchunk1 Size = %d\n\r", userWav->subchunk1Size);
printf( " - Audio Format = 0x%04X\n\r", userWav->audioFormat);
printf( " - Num. Channels = %d\n\r", userWav->numChannels);
printf( " - Sample Rate = %d\n\r", userWav->sampleRate);
printf( " - Byte Rate = %d\n\r", userWav->byteRate);
printf( " - Block Align = %d\n\r", userWav->blockAlign);
printf( " - Bits Per Sample = %d\n\r", userWav->bitsPerSample);
printf( " - Subchunk2 ID = 0x%08X\n\r", userWav->subchunk2ID);
printf( " - Subchunk2 Size = %d\n\r", userWav->subchunk2Size);
printf("Press a key to return to the menu ...\n\r");
DBGU_GetChar();
}
//------------------------------------------------------------------------------
/// Displays the user menu on the DBGU.
//------------------------------------------------------------------------------
static void DisplayMenu(void)
{
printf("%c[2J-- Basic SSC I2S WM8731 Project xxx --\n\r", 27);
printf("Menu :\n\r");
printf("------\n\r");
// Play a WAV file pre-loaded in SDCARD using SAM-BA
if (!isWavPlaying) {
printf(" W: Play the WAV file pre-loaded in SDCARD\n\r");
}
// Display the information of the WAV file (sample rate, stereo/mono and frame size)
printf(" I: Display the information of the WAV file\n\r");
// Stop the current playback (if any)
if (isWavPlaying) {
printf(" S: Stop playback\n\r");
}
}
#if defined(AUDIO_USING_DMA)
void HDMA_IrqHandler(void)
{
unsigned int size;
unsigned intFlag;
// One buffer sent & more buffers to send
if (remainingSamples > 0 ) {
size = min(remainingSamples / (userWav->bitsPerSample / 8), BOARD_SSC_DMA_FIFO_SIZE * MAX_SSC_LLI_SIZE/2);
SSC_WriteBuffer(AT91C_BASE_SSC0, (void *) (WAV_FILE_ADDRESS + transmittedSamples), size);
remainingSamples -= size * (userWav->bitsPerSample / 8);
transmittedSamples += size * (userWav->bitsPerSample / 8);
intFlag = 1 << (BOARD_SSC_DMA_CHANNEL + 8) ;
DMA_EnableIt(intFlag);
DMA_EnableChannel(BOARD_SSC_DMA_CHANNEL);
}
else if (remainingSamples == 0){
DMA_DisableChannel(BOARD_SSC_DMA_CHANNEL);
intFlag = 1 << (BOARD_SSC_DMA_CHANNEL + 8) ;
DMA_DisableIt(intFlag);
isWavPlaying = 0;
DisplayMenu();
}
}
#endif
//------------------------------------------------------------------------------
/// Play a WAV file pre-loaded in SDCARD.
//------------------------------------------------------------------------------
void PlayLoop(unsigned short *pExtMem, unsigned int numSamples)
{
unsigned int i;
for (i = 0; i < numSamples; i++) {
SSC_Write(AT91C_BASE_SSC0, pExtMem[i]);
}
}
//------------------------------------------------------------------------------
/// Play a WAV file pre-loaded in SDCARD
//------------------------------------------------------------------------------
static void PlayWavFile(void)
{
#if !defined(AUDIO_USING_DMA)
unsigned int size;
size = userWav->subchunk2Size > MAX_WAV_SIZE ? MAX_WAV_SIZE : userWav->subchunk2Size;
SSC_EnableTransmitter(AT91C_BASE_SSC0);
PlayLoop((unsigned short *)WAV_FILE_ADDRESS, size >> 1);
#else
unsigned int size;
unsigned int intFlag = 0;
size = userWav->subchunk2Size > MAX_WAV_SIZE ? MAX_WAV_SIZE : userWav->subchunk2Size;
SSC_EnableTransmitter(AT91C_BASE_SSC0);
// Start transmitting WAV file to SSC
remainingSamples = userWav->subchunk2Size;
transmittedSamples = 0;
intFlag = 1 << (BOARD_SSC_DMA_CHANNEL + 8) ;
DMA_DisableIt(intFlag);
DMA_DisableChannel(BOARD_SSC_DMA_CHANNEL);
// Fill DMA buffer
size = min(remainingSamples / (userWav->bitsPerSample / 8), BOARD_SSC_DMA_FIFO_SIZE * MAX_SSC_LLI_SIZE/2);
SSC_WriteBuffer(AT91C_BASE_SSC0, (void *) (WAV_FILE_ADDRESS + transmittedSamples), size);
remainingSamples -= size * (userWav->bitsPerSample / 8);
transmittedSamples += size * (userWav->bitsPerSample / 8);
intFlag = 1 << (BOARD_SSC_DMA_CHANNEL + 8) ;
DMA_EnableIt(intFlag);
DMA_EnableChannel(BOARD_SSC_DMA_CHANNEL);
#endif
}
//------------------------------------------------------------------------------
/// Stop the current playback (if any).
//------------------------------------------------------------------------------
static void StopPlayback(void)
{
SSC_DisableTransmitter(AT91C_BASE_SSC0);
}
//------------------------------------------------------------------------------
/// Check wav file from sdcard
//------------------------------------------------------------------------------
unsigned char CheckWavFile()
{
FRESULT res;
FATFS fs; // File system object
FIL FileObject;
unsigned int numRead, pcmSize;
// Init Disk
printf("-I- Init media Sdcard\n\r");
MEDSdcard_Initialize(&medias[ID_DRV], MCI_ID);
// Mount disk
printf("-I- Mount disk %d\n\r", ID_DRV);
memset(&fs, 0, sizeof(FATFS)); // Clear file system object
res = f_mount(ID_DRV, &fs);
if( res != FR_OK ) {
printf("-E- f_mount pb: 0x%X (%s)\n\r", res, FF_GetStrResult(res));
return 0;
}
res = f_open(&FileObject, FileName, FA_OPEN_EXISTING|FA_READ);
if (res == FR_OK) {
printf("-I- File Found!\n\r");
//f_close(&FileObject);
// FilePlay();
}
else {
printf("-E- File Not Found!\n\r");
return 1;
}
// Read header
f_read(&FileObject, (void*)userWav, sizeof(WavHeader), &numRead);
DisplayWavInfo();
// Load PCM
pcmSize = userWav->subchunk2Size;
if (pcmSize > MAX_WAV_SIZE) {
pcmSize = MAX_WAV_SIZE;
}
f_read(&FileObject, (void*)WAV_FILE_ADDRESS, pcmSize, &numRead);
printf("-I- PCM Load to %x, size %d\n\r", WAV_FILE_ADDRESS, numRead);
f_close(&FileObject);
return 0;
}
//------------------------------------------------------------------------------
/// Main function
//------------------------------------------------------------------------------
int main(void)
{
unsigned char key;
unsigned char isValid;
// Configure all pins
PIO_Configure(pins, PIO_LISTSIZE(pins));
// Initialize the DBGU
TRACE_CONFIGURE(DBGU_STANDARD, 115200, BOARD_MCK);
// Initialize PSRAM
BOARD_ConfigurePsram();
// Switch to Main clock
AT91C_BASE_PMC->PMC_MCKR = (AT91C_BASE_PMC->PMC_MCKR & ~AT91C_PMC_CSS) | AT91C_PMC_CSS_MAIN_CLK;
while ((AT91C_BASE_PMC->PMC_SR & AT91C_PMC_MCKRDY) == 0);
// Configure PLL to 98.285MHz
*AT91C_CKGR_PLLR = ((1 << 29) | (171 << AT91C_CKGR_MUL_SHIFT) \
| (0x0 << AT91C_CKGR_OUT_SHIFT) |(0x3f << AT91C_CKGR_PLLCOUNT_SHIFT) \
| (21 << AT91C_CKGR_DIV_SHIFT));
while ((AT91C_BASE_PMC->PMC_SR & AT91C_PMC_LOCK) == 0);
// Configure master clock in two operations
AT91C_BASE_PMC->PMC_MCKR = (( AT91C_PMC_PRES_CLK_2 | AT91C_PMC_CSS_PLLA_CLK) & ~AT91C_PMC_CSS) | AT91C_PMC_CSS_MAIN_CLK;
while ((AT91C_BASE_PMC->PMC_SR & AT91C_PMC_MCKRDY) == 0);
AT91C_BASE_PMC->PMC_MCKR = ( AT91C_PMC_PRES_CLK_2 | AT91C_PMC_CSS_PLLA_CLK);
while ((AT91C_BASE_PMC->PMC_SR & AT91C_PMC_MCKRDY) == 0);
// DBGU reconfiguration
DBGU_Configure(DBGU_STANDARD, 115200, SSC_MCK);
// Configure and enable the TWI (required for accessing the DAC)
*AT91C_PMC_PCER = (1<< AT91C_ID_TWI0);
TWI_ConfigureMaster(AT91C_BASE_TWI0, TWI_CLOCK, SSC_MCK);
TWID_Initialize(&twid, AT91C_BASE_TWI0);
// Enable the DAC master clock
AT91C_BASE_PMC->PMC_PCKR[0] = AT91C_PMC_CSS_PLLA_CLK | AT91C_PMC_PRES_CLK_8;
AT91C_BASE_PMC->PMC_SCER = AT91C_PMC_PCK0;
while ((AT91C_BASE_PMC->PMC_SR & AT91C_PMC_PCKRDY0) == 0);
printf("-- Basic SSC I2S WM8731 Project %s --\n\r", SOFTPACK_VERSION);
printf("-- %s\n\r", BOARD_NAME);
printf("-- Compiled: %s %s --\n\r", __DATE__, __TIME__);
// Check and load wav file from sdcard
isValid = CheckWavFile();
if(isValid) {
printf("-E- Open wav file fail!\r\n");
return 1;
}
// Load WAV file information
isValid = WAV_IsValid(userWav);
ASSERT(isValid, "-F- Invalid WAV file provided\n\r");
isWavPlaying = 0;
// Sample rate must be 48kHz
printf("-I- Sample rate = %d Hz\n\r", userWav->sampleRate);
ASSERT(userWav->sampleRate == 48000, "-F- The WAV file must have a sample rate of 48kHz\n\r");
// Initialize the audio DAC
WM8731_DAC_Init(&twid, WM8731_SLAVE_ADDRESS);
// Configure SSC
SSC_Configure(AT91C_BASE_SSC0,
AT91C_ID_SSC0,
SAMPLE_RATE * BITS_BY_SLOT * 2,
SSC_MCK);
SSC_ConfigureReceiver(AT91C_BASE_SSC0, 0, 0);
SSC_ConfigureTransmitter(AT91C_BASE_SSC0,
AT91C_I2S_MASTER_TX_SETTING(BITS_BY_SLOT, SLOT_BY_FRAME),
AT91C_I2S_TX_FRAME_SETTING( BITS_BY_SLOT, SLOT_BY_FRAME));
SSC_DisableTransmitter(AT91C_BASE_SSC0);
#if defined(AUDIO_USING_DMA)
// Initialize DMA controller.
DMAD_Initialize(BOARD_SSC_DMA_CHANNEL, 0);
// Configure and enable the SSC interrupt
IRQ_ConfigureIT(AT91C_ID_HDMA, 0, HDMA_IrqHandler);
IRQ_EnableIT(AT91C_ID_HDMA);
#endif
// Enter menu loop
while (1) {
// Display menu
DisplayMenu();
// Process user input
key = DBGU_GetChar();
// Play WAV file
if ((key == 'W') && !isWavPlaying) {
PlayWavFile();
isWavPlaying = 1;
}
// Display WAV information
else if (key == 'I') {
DisplayWavInfo();
}
// Stop playback
else if ((key == 'S') && isWavPlaying) {
StopPlayback();
isWavPlaying = 0;
}
}
}