In a previous post, we made a simple WAV file player which loads the WAV file completely into RAM and plays it from there. That is not ideal for a few reasons. First of all loading a 300MByte WAV file from SD card takes a few seconds. Furthermore, we are wasting precious memory.
How about having the DMA controller trigger an interrupt handler which loads a block of one second from the SD card and sends it to the DMA controller?
The block design from the previous post already connected the interrupt output of the DMA controller to the ZYNQ. So in this post we don’t need to change anything on the FPGA side.
In this tutorial we learn:
- How to set up the interrupt controller.
- How to add an interrupt handler.
- How to handle interrupts from the DMA controller.
- How to increase the IRQ stack size to avoid stack overflows.
To handle the interrupt, we are using the driver XScuGic which is invoked by including “xuartps_hw.h“. Nicely enough, the auto-magically generated board-support project has defined a pre-processor symbol for us named XPAR_FABRIC_AXI_DMA_0_MM2S_INTROUT_INTR to address the interrupt.
The function adau1761_setupInterruptSystem() does most of the work. I copied it from an example, so no creativity here.
An interrupt is triggered at the rising edge of the interrupt pin and the function adau1761_interruptHandler() is called. In this function we need to work out what triggered the interrupt. This is done by reading the interrupt status register which tells us what to do next. The interrupt status is a bit vector. During normal operation, we get an interrupt status of XAXIDMA_IRQ_IOC_MASK for each completed buffer descriptor (BD). After the last BD, we get XAXIDMA_IRQ_DELAY_MASK.
The interrupt handler does the following:
- If a BD has been completed, load another block from SD card and send it to the DMA controller which puts another BD into the queue. We are using a ping-pong buffer approach. There are basically two buffers with 48000 samples each. One buffer is currently playing while the other is loading data from SD card.
- Update a global variable with a percentage of the played file. Each BD has a field named “Id” which can be used for signalling user-specific data. We use it to store the percentage of the played file.
- Free all used BDs.
Note that interrupts can cause problems if there are objects used by the main program and the interrupt handler simultaneously. Candidates are here:
- The SD card
- The global object “thePlayBuffer”
- The UART
A few rules to avoid weird intermittent bugs:
- Don’t use printf() in an interrupt routine. Just don’t.
- Atomic operations like writing or reading a 32-bit word are normally fine since they can’t be interrupted.
- Read-modify-write (like “i++”) can be a problem if the interrupt occurs between the read- and write operation. If possible assign a clear read- or write role to the interrupt handler and the main program. E.g. The main program just writes, the interrupt handler just reads.
- If both need to write you can temporarily deactivate interrupts using Xil_ExceptionDisable(). Then no interrupt can occur while modifying the object. We are using this to protect access to the SD card. The SD card is mainly accessed by the interrupt handler. If we want to access the SD card from the main program, we temporarily disable exceptions.
Open the Main Project
- Start with the project from this post. Or download the complete project further down.
- Launch the SDK. I encountered problems occasionally that the SDK creates a new system wrapper project. Normally there is a project named design_1_wrapper_hw_platform_0. I then have two of those. The second one named design_1_wrapper_hw_platform_1. I found the best way to resolve this was to remove both (!) projects and delete the files (you can right-click and select “Delete”). Then close SDK, export the hardware again and re-launch SDK. The project will be regenerated. See details here.
Modify the C-Code
- If you haven’t done so, launch the SDK.
- You might want to check if the right UART driver is selected (refer to “SDK Auto Update Bug“).
- Here is the full modified code (helloworld.c):
- Increase the IRQ stack size in the linker script file. This avoids a tricky problem caused by a stack overflow. The IRQ stack is normally 1024 bytes long which is not enough to hold the DIR and FILE structures needed to read from SD card. With the stack being too small you will see the local variables of the main program being randomly destroyed. Very unpredictable. Read here for more details.
- Run the program with an SD card inserted:
- Note that the file starts playing immediately after pressing RETURN. A nice consequence of us not loading the full WAV file into RAM.
- There is a progress indicator when playing a file.
Download the Complete Project
- Here is a WAV file which I have tested (from the Youtube Audio Library):
Piano March by Audionautix is licensed under a Creative Commons Attribution license (https://creativecommons.org/licenses/by/4.0/). Artist: http://audionautix.com/
- To upload the software to the Zedboard, open the project in Vivado, then click on “Launch SDK”.
- There seems to be a bug when updating the SDK project which selects a wrong UART driver (refer to “SDK Auto Update Bug” to fix it).
- Select the project CodecDMADemoOneshot and click “Run” (the “play” button at the top of the window).
Boot from SD Card
Follow this quick hint if you want to boot this software from SD card: