Kampis Elektroecke

Abspielen von Wave-Dateien

Im letzten Teil des I2S-Tutorials ist der erstellte I2S-Sender mit einem AXI-Stream Interface ausgestattet worden, um ihn mit dem Processing System verbinden zu können. Dieses Interface wollen wir nun verwenden, um mit Hilfe des Processing Systems Wave-Dateien von einer SD-Karte auszulesen und die gespeicherte Musik mit Hilfe eines CS4344 D/A-Wandlers über einen angeschlossenen Lautsprecher auszugeben.

Für das Projekt werden die folgenden IP-Cores benötigt:

Der Clocking Wizard generiert den Takt, der dann als Master Clock für den CS4344 genutzt wird. Über das AXI-Lite Interface lässt sich der Ausgangstakt an die Abtastrate der Audiodatei anpassen. Initialisiert wird der Clocking Wizard mit einem 12,288 MHz Takt für 48 kHz Audiosignale.

Der AXI-Stream FIFO dient als Bindeglied zwischen Processing System und I2S-Sender. Das Processing System schreibt über das AXI-Lite (oder AXI) Interface Daten in den FIFO und dieser streamt die Daten dann zum I2S-Sender.

Aus dem Design wird ein Bitstream erzeugt und dann kann auch schon die Software entwickelt werden.

Zum Auslesen der SD-Karte wird die FAT-Bibliothek xilffs von Xilinx benötigt, die in dem Board Support Package des Vitis Projektes eingebunden werden muss:

In den Optionen zu der Bibliothek muss zudem die Variable use_lfn auf 1 gesetzt werden, damit lange Dateinamen erlaubt werden. Andernfalls ist die Länge des Namens auf 5 Zeichen + 3 Zeichen für die Endung begrenzt.


Info:

Die FAT-Bibliothek von Xilinx basiert auf der FAT-Bibliothek von Elm Chan, die ich auch bereits für meine Implementierung einer SD-Karte für den AVR genutzt habe.


Die Software initialisiert im ersten Schritt über die Funktion AudioPlayer_Init den Audioplayer und damit den FIFO, den GIC und die Interrupthandler, sowie den Clocking Wizard und die SD-Karte.

u32 AudioPlayer_Init(void)
{
	xil_printf("[INFO] Looking for FIFO configuration...\r\n");
	_Fifo_ConfigPtr = XLlFfio_LookupConfig(XPAR_FIFO_DEVICE_ID);
	if(_Fifo_ConfigPtr == NULL)
	{
		xil_printf("[ERROR] Invalid FIFO configuration!\r\n");
		return XST_FAILURE;
	}

	xil_printf("[INFO] Initialize FIFO...\r\n");
	if(XLlFifo_CfgInitialize(&_Fifo, _Fifo_ConfigPtr, _Fifo_ConfigPtr->BaseAddress) != XST_SUCCESS)
	{
		xil_printf("[ERROR] FIFO initialization failed!\n\r");
		return XST_FAILURE;
	}

	xil_printf("[INFO] Looking for GIC configuration...\r\n");
	_GIC_ConfigPtr = XScuGic_LookupConfig(XPAR_PS7_SCUGIC_0_DEVICE_ID);
	if(_GIC_ConfigPtr == NULL)
	{
		xil_printf("[ERROR] Invalid GIC configuration!\n\r");
		return XST_FAILURE;
	}

	xil_printf("[INFO] Initialize GIC...\r\n");
	if(XScuGic_CfgInitialize(&_GIC, _GIC_ConfigPtr, _GIC_ConfigPtr->CpuBaseAddress) != XST_SUCCESS)
	{
		xil_printf("[ERROR] GIC initialization failed!\n\r");
		return XST_FAILURE;
	}

	xil_printf("[INFO] Setup interrupt handler...\r\n");
	XScuGic_SetPriorityTriggerType(&_GIC, XPAR_FABRIC_FIFO_INTERRUPT_INTR, 0xA0, 0x03);
	if(XScuGic_Connect(&_GIC, XPAR_FABRIC_FIFO_INTERRUPT_INTR, (Xil_ExceptionHandler)AudioPlayer_FifoHandler, &_Fifo) != XST_SUCCESS)
	{
		xil_printf("[ERROR] Can not connect interrupt handler!\n\r");
		return XST_FAILURE;
	}
	XScuGic_Enable(&_GIC, XPAR_FABRIC_FIFO_INTERRUPT_INTR);

	xil_printf("[INFO] Enable exceptions...\r\n");
	Xil_ExceptionInit();
	Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)XScuGic_InterruptHandler, &_GIC);
	Xil_ExceptionEnable();

	xil_printf("[INFO] Enable FIFO interrupts...\r\n");
	XLlFifo_IntClear(&_Fifo, XLLF_INT_ALL_MASK);

	xil_printf("[INFO] Initialize Clocking Wizard...\r\n");
	if((ClockingWizard_Init(&_ClkWiz, XPAR_CLOCKINGWIZARD_BASEADDR) || ClockingWizard_GetOutput(&_ClkWiz, &_AudioClock))!= XST_SUCCESS)
	{
		xil_printf("[ERROR] Clocking Wizard initialization failed!\n\r");
		return XST_FAILURE;
	}
	xil_printf("[INFO] Mount SD card...\r\n");
	if(SD_Init())
	{
		xil_printf("[ERROR] Can not initialize SD card!\n\r");
		return XST_FAILURE;
	}

	return XST_SUCCESS;
}

Sobald die Initialisierung abgeschlossen ist, wird die Funktion AudioPlayer_LoadFile aufgerufen um die Datei Audio.wav von der SD-Karte zu laden.

if(AudioPlayer_LoadFile("Audio.wav"))
{
	xil_printf("[ERROR] Can not open Audio file!\n\r");
	return XST_FAILURE;
}

u32 AudioPlayer_LoadFile(char* File)
{
	if(SD_LoadFileFromCard(File, &_File))
	{
		xil_printf("[ERROR] Can not open Audio file!\n\r");
		return XST_FAILURE;
	}

	xil_printf("	File size: %lu bytes\n\r", _File.Header.ChunkSize + 8);
	xil_printf("	File format: %lu\n\r", _File.Format.AudioFormat);
	xil_printf("	Channels: %lu\n\r", _File.Format.NumChannels);
	xil_printf("	Sample rate: %lu Hz\n\r", _File.Format.SampleRate);
	xil_printf("	Bits per sample: %lu bits\n\r", _File.Format.BitsPerSample);
	xil_printf("	Block align: %lu bytes\n\r", _File.Format.BlockAlign);
	xil_printf("	Data bytes: %lu bytes\n\r", _File.Header.ChunkSize / _File.Format.NumChannels);
	xil_printf("	Samples: %lu\n\r", 8 * _File.Header.ChunkSize / _File.Format.NumChannels / _File.Format.BitsPerSample);

	if(( _File.Format.BitsPerSample != 16) || (_File.Format.NumChannels > 2))
	{
		xil_printf("[ERROR] Invalid file format!\n\r");
		return XST_FAILURE;
	}
	AudioPlayer_ChangeFreq(_File.Format.SampleRate);

	XLlFifo_TxReset(&_Fifo);
	XLlFifo_IntEnable(&_Fifo, XLLF_INT_ALL_MASK);
	SD_CopyDataIntoBuffer(_FifoBuffer, 256);
	AudioPlayer_CopyBuffer();

	return XST_SUCCESS;
}

In der Funktion AudioPlayer_LoadFile wird zuerst die Funktion SD_LoadFileFromCard genutzt, um die die Wave-Datei von der SD-Karte zu laden.

u32 SD_LoadFileFromCard(const char* FileName, Wave_t* File)
{
	xil_printf("[INFO] Opening file: %s...\n\r", FileName);

	if(f_open(&_FileHandle, FileName, FA_READ))
	{
		xil_printf("[ERROR] Can not open audio file!\n\r");
		return XST_FAILURE;
	}

	if(f_read(&_FileHandle, &File->RIFF, sizeof(Wave_RIFF_t), &_BytesRead) || f_read(&_FileHandle, &File->Format, sizeof(Wave_Format_t), &_BytesRead))
	{
		xil_printf("[ERROR] Can not read SD card!\n\r");
		return XST_FAILURE;
	}

	Wave_Header_t Header;
	uint32_t Offset = sizeof(Wave_RIFF_t) + sizeof(Wave_Format_t);
	if(f_read(&_FileHandle, Header.ChunkID, sizeof(Wave_Header_t), &_BytesRead) || f_lseek(&_FileHandle, Offset))
	{
		xil_printf("[ERROR] Can not read SD card!\n\r");
		return XST_FAILURE;
	}

	if(strncmp("LIST", Header.ChunkID, 4) == 0)
	{
		Offset += Header.ChunkSize + sizeof(Wave_Header_t);
		if(f_read(&_FileHandle, &File->ListHeader, sizeof(Wave_Header_t), &_BytesRead) || f_lseek(&_FileHandle, Offset))
		{
			xil_printf("[ERROR] Can not place SD card pointer!\n\r");
			return XST_FAILURE;
		}
	}

	if(f_read(&_FileHandle, &File->DataHeader, sizeof(Wave_Header_t), &_BytesRead))
	{
		xil_printf("[ERROR] Can not read SD card!\n\r");
		return XST_FAILURE;
	}

	if(File->Format.AudioFormat != WAVE_FORMAT_PCM)
	{
		xil_printf("[ERROR] Audio format not supported! Keep sure that the file use the PCM format!\n\r");
		return XST_FAILURE;
	}

	_RemainingBytes = File->DataHeader.ChunkSize;

	_IsBusy = true;

	return XST_SUCCESS;
}

Anschließend wird die Ausgangsfrequenz des Clocking Wizards entsprechend der verwendeten Abtastfrequenz aus der Wave-Datei gesetzt:

static void AudioPlayer_ChangeFreq(const u32 SampleRate)
{
	if(SampleRate == 44100)
	{
		xil_printf("	Use clock setting 1...\n\r");
		_ClkWiz.DIVCLK_DIVIDE = 5;
		_ClkWiz.CLKFBOUT_MULT = 42;
		_ClkWiz.CLKFBOUT_Frac_Multiply = 0;
		_AudioClock.DIVIDE = 93;
		_AudioClock.FRAC_Divide = 0;
	}
	else if(SampleRate == 48000)
	{
		xil_printf("	Use clock setting 2...\n\r");
		_ClkWiz.DIVCLK_DIVIDE = 3;
		_ClkWiz.CLKFBOUT_MULT = 23;
		_ClkWiz.CLKFBOUT_Frac_Multiply = 0;
		_AudioClock.DIVIDE = 78;
		_AudioClock.FRAC_Divide = 0;
	}
	else if(SampleRate == 96000)
	{
		xil_printf("	Use clock setting 3...\n\r");
		_ClkWiz.DIVCLK_DIVIDE = 3;
		_ClkWiz.CLKFBOUT_MULT = 23;
		_ClkWiz.CLKFBOUT_Frac_Multiply = 0;
		_AudioClock.DIVIDE = 39;
		_AudioClock.FRAC_Divide = 0;
	}

	ClockingWizard_SetClockBuffer(&_ClkWiz);
	ClockingWizard_SetOutput(&_ClkWiz, &_AudioClock);
}

Wenn die Audiodatei geladen werden konnte und die Ausgangsfrequenz des Clocking Wizards angepasst wurde, wird der erste Block Daten aus der Wave-Datei ausgelesen und in den FIFO kopiert:

u32 SD_CopyDataIntoBuffer(u8* Buffer, const u32 Length)
{
	if(_RemainingBytes >= Length)
	{
		if(f_read(&_FileHandle, Buffer, Length, &_BytesRead))
		{
			return XST_FAILURE;
		}

		_RemainingBytes -= _BytesRead;
	}
	else
	{
		if(f_read(&_FileHandle, Buffer, _RemainingBytes, &_BytesRead))
		{
			return XST_FAILURE;
		}

		if(f_close(&_FileHandle))
		{
			xil_printf("[ERROR] Can not close audio file!\n\r");
			return XST_FAILURE;
		}

		_IsBusy = false;
	}

	return XST_SUCCESS;
}

static void AudioPlayer_CopyBuffer(void)
{
    u32 Bytes = 0x00;
    for(u32 i = 0x00; i < AUDIOPLAYER_FIFO_BUFFER_SIZE; i += _File.Format.BlockAlign)
    {
        if(XLlFifo_iTxVacancy(&_Fifo))
        {
            u32 Word = 0x00;
            for(u8 Byte = 0x00; Byte < _File.Format.BlockAlign; Byte++)
            {
                Word |= _FifoBuffer[i + Byte];
                Word <<= 0x08;
            }
            Bytes += sizeof(u32);
            XLlFifo_TxPutWord(&_Fifo, Word);
        }
    }

    XLlFifo_iTxSetLen(&_Fifo, Bytes);
}

Der Rest des Programmablaufs findet dann in dem Callback des FIFOs statt:

static void AudioPlayer_FifoHandler(void* CallbackRef)
{
	XLlFifo* InstancePtr = (XLlFifo*)CallbackRef;

	u32 Pending = XLlFifo_IntPending(InstancePtr);
	while(Pending)
	{
		if(Pending & XLLF_INT_TC_MASK)
		{
			SD_CopyDataIntoBuffer(_FifoBuffer, 256);

			XLlFifo_IntClear(InstancePtr, XLLF_INT_TC_MASK);
		}
		else if(Pending & XLLF_INT_TFPE_MASK)
		{
			AudioPlayer_CopyBuffer();
			if(!SD_IsBusy())
			{
				XLlFifo_IntDisable(&_Fifo, XLLF_INT_ALL_MASK);
			}

			XLlFifo_IntClear(InstancePtr, XLLF_INT_TFPE_MASK);
		}
		else if(Pending & XLLF_INT_ERROR_MASK)
		{
			xil_printf("	Error: %lu!\n\r", Pending);
			XLlFifo_IntClear(InstancePtr, XLLF_INT_ERROR_MASK);
		}
		else
		{
			XLlFifo_IntClear(InstancePtr, Pending);
		}

		Pending = XLlFifo_IntPending(InstancePtr);
	}
}

Sobald der FIFO einen TFPE-Interrupt (Transmit FIFO Programmable Empty) auslöst wird der FIFO mit neuen Daten aus dem internen Buffer gefüllt. Ist die Übertragung vom Processing System zum FIFO abgeschlossen, wird ein TC-Interrupt (Transmit Complete) ausgelöst und der nächste Block an Daten von der SD-Karte gelesen. Wenn die komplette Datei ausgelesen wurde, werden die FIFO Interrupts deaktiviert und die Ausgabe ist beendet.Jetzt muss nur noch eine Wave-Datei erzeugt werden. Einfache Testsignale sind im Repository vorhanden oder können z. B. bei wavtones.com erzeugt werden. Die jeweilige Datei muss dann nur noch unter dem Namen Audio.wav auf die SD-Karte kopiert werden und schon kann es los gehen.

-----------I2S Audio player-----------

[INFO] Looking for FIFO configuration...
[INFO] Initialize FIFO...
[INFO] Looking for GIC configuration...
[INFO] Initialize GIC...
[INFO] Setup interrupt handler...
[INFO] Enable exceptions...
[INFO] Enable FIFO interrupts...
[INFO] Initialize Clocking Wizard...
[INFO] Mount SD card...
[INFO] Opening file: Single.wav...
        File size: 264610 bytes
        File format: 1
        Channels: 1
        Sample rate: 48000 Hz
        Bits per sample: 16 bits
        Data bytes: 264602 bytes
        Samples: 132301
        Use clock setting 2...
[INFO] Finished!

Bzw. in Stereo:

-----------I2S Audio player-----------

[INFO] Looking for FIFO configuration...
[INFO] Initialize FIFO...
[INFO] Looking for GIC configuration...
[INFO] Initialize GIC...
[INFO] Setup interrupt handler...
[INFO] Enable exceptions...
[INFO] Enable FIFO interrupts...
[INFO] Initialize Clocking Wizard...
[INFO] Mount SD card...
[INFO] Opening file: Dual.wav...
        File size: 529208 bytes
        File format: 1
        Channels: 2
        Sample rate: 44100 Hz
        Bits per sample: 16 bits
        Block align: 4 bytes
        Data bytes: 264600 bytes
        Samples: 132300
        Use clock setting 1...
[INFO] Finished!

Zurück

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert