ZYNQ: Using the Audio Codec (Bidirectional SPI IP-Core)

In this tutorial we create a bidirectional SPI interface. A regular SPI interface receives a word for every word it transmits. So, we need two AXI stream interfaces. One slave and one master.
This tutorial will be less verbose than the others showing only screenshots of the most notable items.

In this tutorial we will learn

  • How to create a bidirectional SPI interface with a master and a slave AXI stream interface.
  • We will briefly talk to the on-board audio codec. However we are not yet producing sounds as the SPI interface is only used to configure the chip.
  • The next tutorial will give an implementation for an I2S interface.

The SPI Interface of the ADAU1761

Since we want to talk to the audio codec ADAU1761, we should probably have a look at the datasheet. Particularly interesting for us is the SPI timing:

  • Noteworthy is that the SS or “Slave Select” is low-active.
  • The MOSI data (this is the data from the SPI controller to the chip, or master-out-slave-in) is accepted at the rising edge of SCLK.
  • Also worth mentioning is that the chip changes the MISO state (this is the data from the chip to the SPI controller, or master-in-slave-out) on every low-transition of SCLK. So, we have to accept the data on the rising edge of SCLK. I believe this is slightly unusual as it would be impossible to receive the very first bit with the very first rising clock transition (the chip would have to predict the future and set up MISO before the master creates the positive clock edge). But since the read operation always starts with 3 transmitted bytes (chip ID and address), it is not a problem to anticipate the clock transitions of the 4th byte.

The IP-Cores we are Using

How to Create the SPI IP-Core

This is how the SPI controller looks like:

  • Following the instructions in this tutorial on how to create a new IP-Core. Or download the complete IP-core from the above links.
  • We are using a master- and a slave- AXI stream interface. Both use the same clock and reset signals.
  • Unfortunately, I could not figure out how to change the arrangement of the ports. It’s probably intentional to have inputs on the left hand side and outputs on the right hand side…
  • Note that the master interface ignores the TREADY flow control. We would have to implement a FIFO in order to handle this in a meaningful way. But we are going to connect it to a FIFO anyway. So, let’s assume the external AXI FIFO will always be ready to receive…

Create the Main Project

  • Following the instructions of this tutorial. Or download the complete Project further down.
  • Create a project named SPIRxTxDemo.
  • Create a directory named myIPCores in the project directory.
  • Copy the two IP-Cores into myIPCores.
  • Add myIPCores as a repository.
  • Create a block design.
  • Add the ZYNQ7 processing system.
  • Connect  FCLK_CLK0 to M_AXI_GP0_ACLK and run Block Automation.
  • Add an AXI-Stream FIFO and configure it like this:

  • Run Connection Automation.
  • Add mySPIRxTx_v1.0 and configure it like this:

     

  • Run Connection Automation and make sclk, mosi, miso and ss external pins.
  • Add myPrescaler_v1_0 and configure it like this:
  • Run Connection Automation.
  • This is going to create the 1MHz mclk signal for the ADAU1761. Create a port by right-clicking the background:
  • Connect the port to the prescaler output.
  • Add another myPrescaler_v1_0 and configure it like this:

  • Run Connection Automation.
  • This is going to be our heartbeat signal. Create a port by right-clicking the background:
  • Connect the port to the prescaler output.

The Complete Block Design

Create the Bitstream

  • Create an HDL wrapper.
  • Add these constraints:
    set_property PACKAGE_PIN T22 [get_ports heartbeat]
    set_property IOSTANDARD LVCMOS33 [get_ports heartbeat]
    
    set_property PACKAGE_PIN Y5 [get_ports mosi]
    set_property IOSTANDARD LVCMOS33 [get_ports mosi]
    set_property PACKAGE_PIN AB5 [get_ports miso]
    set_property IOSTANDARD LVCMOS33 [get_ports miso]
    set_property PACKAGE_PIN AB4 [get_ports sclk]
    set_property IOSTANDARD LVCMOS33 [get_ports sclk]
    set_property PACKAGE_PIN AB1 [get_ports ss]
    set_property IOSTANDARD LVCMOS33 [get_ports ss]
    set_property PACKAGE_PIN AB2 [get_ports mclk]
    set_property IOSTANDARD LVCMOS33 [get_ports mclk]
    
  • Generate the bitstream.
  • Open the Implementation.
  • Export Hardware (including bitstream).

Add C-Code

  • Launch SDK
  • Create stand-alone C-project named SPIRxTxDemo based on “Hello World” template.
  • Add this code (helloworld.c):

  • Create Run Configuration (like here).
  • Run the program. You should see this:
  • If it says “FAILED” something went wrong…

Initializing the ADAU1761

  • By default the chip uses I2C as an interface. But it can be switched into SPI mode by pulling /CLATCH down three times. This is done by reading from address 0x4000 three times during initialization.
  • A clock rate around 1MHz should be fine.
  • An SPI write cycle consists of 4 bytes:
    • Chip ID + R/W bit=0
    • Address high-byte (MSB)
    • Adresss low-byte (LSB)
    • data (MOSI/CDATA)
  • An SPI read cycle consists of 4 bytes:
    • Chip ID + R/W bit=1
    • Address high-byte (MSB)
    • Adresss low-byte (LSB)
    • data (the chip applies data to MISO/COUT)
  • After initialization, we should be able to read a value of 0x01 from address 0x4000.

Download the Complete Project


SPIRxTxDemo.xpr

  • 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 SPIRxTxDemo and click “Run” (the “play” button at the top of the window).

Next: Create an I2S Transmitter to Send Audio Signals

Leave a comment

Your email address will not be published. Required fields are marked *