5. The microprocessor: how to control the DSP

The term “microprocessor” covers a broad range of programmable devices that can perform arithmetic and logical operations.  We are going to focus on the category sometimes called microcontrollers, which typically use 8-bit computing cores and include peripherals such as timers, input/output ports, analog inputs and serial communications.  This is not a small boutique category, as annual unit sales of microcontrollers are in the 20 billion range.  These devices are used to control washing machines, microwave ovens, TV’s, children’s toys, and a wide range of small electronic devices.  A modern automobile will have on the order of 10 to 100 microcontrollers, for the entertainment system, engine/transmission control, lighting control, to airbag control.

As you might expect with sales of 20 billion a year, there are many ways to develop the code for these devices, and some are a lot easier than others.  Also, there is a wide range of capabilities to choose from.  The average selling price of a microcontroller in large quantities is around 70 to 80 cents so this large volume includes some fairly simple low-cost controllers.  We are going to focus on the microcontrollers that are fairly easy to work with but that offer good speed and a lot of capability.  These include devices that are in the 5 to 100 millions of instructions per second range.  Controlling a DSP chip is a complicated task, so we are going to need that processing power.

Arduino

The Arduino initiative has deep roots in creative efforts to control devices for artists and designers.  But it is also well grounded in traditional C language programming.  As a result, it offers both ease of use and a professional, disciplined code development capability.  One of the key features of Arduino is the open-source hardware based on very capable microprocessors.  The first generation of Arduino devices were based on the capable 8-bit AVR processor cores from Atmel.  More recent releases are using the 32-bit ARM devices–the same series that is powering the current generation of cell phones.  This combination of user-friendly code development, low cost open source hardware and an upgrade path that supports floating point computing is very attractive.

But the most important benefit of using the Arduino devices is the great open source software development initiative.  Individual developers are writing code to interface to a number of devices such as remote controls, LCD displays, and interesting new products such as “smart” RGB displays and DSP chips.  We will leverage some of those code libraries for a later case study.

Getting Started

The Arduino website at www.arduino.cc has frequent updates to the development environment and has numerous tutorials and examples.  You can buy an Arduino board at Microcenter for $5.99 and connect some LED’s and build yourself a blinking t-shirt if you want to start out with a simple flashy  example.  Arduino boards are also available online from Sparkfun, Adafruit and many other vendors, or you can order directly from www.arduino.cc.  If you aren’t familiar with programming, it might take a while to get oriented, but once you master a few examples things start making sense.

Code Example #1:  I2C Write to the ADAU1701

Let’s jump in with a relatively complicated example–controlling the I2C communications with the ADAU1701.  This is probably the most complicated example we could have picked for the ADAU1701, because it requires extending an existing library and understanding some complicated details of how the ADAU1701 memory is organized.  If you follow this example, the rest of the code in the DSP test bed should be “easy” by comparison.

If you looked at the Sparkfun tutorial on I2C, you will see a fairly simple example where the device address gets sent, followed by multiple data bytes.  That sequence is illustrated in the graphic below.

i2c_simple

The slave address field is a 7-bit value that is different for each DSP device.  Vendors who license the I2C bus are assigned address ranges for their devices, and the ADAU1701 is 00110100, or 0x34.

There is a built-in library called Wire that makes simple I2C communication fairly easy.  You don’t need to know how the data gets formatted into the I2C sequence shown above–you just need to call some high-level functions with the data you want to send.  You can add the directive to use that library by selecting “Sketch” from the Arduino IDE menu and selecting Wire from the Library menu items, which will generate the following code:

#include <Wire.h>     			//This tells the compiler to include the Wire library

For the type of communication in the example, you can use the Wire functions to specify the device I2C address  and send the data.  The code for this simple case would look like the example below.  The various methods for the Wire routine might look cryptic, but just do a Google search on “Arduino Wire” and you’ll see a list of all of the functions and lots of examples.  Note: in the examples below,  declarations and directives are in blue text, library calls are in red, and the comments are in green text.

void setup() {
  Wire.beginTransmission(0x34);		//The ADAU1701 device address is 0x34 
  Wire.write('H');			//Send one data byte
  Wire.write('I');			//Send another
  Wire.endTransmission();		//Send the stop bit to end the I2C write sequence
  }

However, the ADAU1701 uses a more complicated variation of the I2C protocol.  As we noted in the previous article, the ADAU1701 exposes three memory areas to external devices:  Program RAM, Parameter RAM, and Control Registers.  Each of these is assigned a separate address range as follows:

Parameter RAM:  0 to 1023 (0x0000 to 0x03FF) 
Program RAM:  1024 to 2047 (0x0400 to 0x07FF)
Control Registers:   2048 to 2087 (0x0800 to 0x0827)

According to the ADAU1701 data sheet, the address location is sent right after the device address (it is referred to as a “subaddress”:

adau1701_i2ca

So before sending data on the I2C bus, we must specify the address of the memory location we are trying to change.  So let’s make an extension to the Wire routines that allows us to write to the ADAU1701 more easily.  We’ll call it I2C_16_write to remind us that we need to send the 16 bits of the subaddress before we send the data.  Also, the Parameter RAM data is 32-bits wide, so we will need to send 4 bytes of data whenever we update one of the parameters.  For this routine we will assume that the 4 bytes of parameter data has been placed in an array that is called Cmd_Data[].  Our routine will look something like this:

word Param_address;
byte Cmd_Data[4];

void  I2C_16_write()		//the routine is "void" because it doesn't return anything
  {
    Wire.beginTransmission(0x34);     		//start transmitting to the ADAU1701 
    Wire.write(highByte(Param_address));  	//write subaddress high
    Wire.write(lowByte(Param_address));   	//write subaddress low
    for (byte x = 0; x < 4; x++) {		//set up a loop to write 4 bytes
        Wire.write(Cmd_Data[x]);   		//write 8-bits of data for each loop iteration
    }						//loop 4 times (32bits total)
    Wire.endTransmission();			//send the stop bit
  }

This routine might still look a bit cryptic if you are new to microprocessors.  But if it makes sense, you are well on your way to writing code to control the ADAU1701.

A note on number representation

One of the challenges of controlling a DSP chip is knowing how the numeric values are represented.  The microprocessor uses integers, bytes, strings, floating point, and so forth, and there is a standardized way of mapping those values to the bits and bytes inside microprocessors.  But in general DSP chips use a different format, and not all of the DSP devices use the same format.  The ADAU1701 uses a “5.23” format, where there are 5 binary digits to the left of the “binary point” and the remaining 23 bits are the fractional part.  The ADAU1452 uses an “8.24” representation, and the ST chips like the STA328 use a pure fractional representation (0.24).

So we need some routines to convert the data to the correct format before sending it to the device on the I2C bus.  Once we have these routines, we simply call them before sending the data, and the DSP chip will be happy.  Here’s an outline of the steps needed to format a floating point value into the 4 bytes we need to send to the ADAU1701:

1.  Multiply the floating point number by 8,388,608 (2 to the 23rd power )
2.  Convert the result to a 32-bit integer
3.  Truncate to 28 bits
4.  Convert to 4 separate bytes by isolating the proper bits and shifting them over to the right
5.  Put the individual bytes into the Cmd_Data[] array

This conversion routine is already written and available in the DSP test bed for the ADAU1701, and you simply need to call it before sending the 4 bytes to the DSP.  But if you use a different DSP chip, this routine will need to be adjusted accordingly.

Code Example #2:  Enable the A/D and DAC’s

This example illustrates how to control the registers for the ADAU1701.  This is about as “technical” as it comes, so if you can get through this example, you are probably ready to write your own code to control the ADAU1701.

The ADAU1701 has quite a few registers that control various features inside the device, such as enabling digital audio and selecting which pin it will use for input or output.  But the “main” register is the DSP Core Control, which is at address 0x081C.  We are going to look at programming this register for one important reason.  The ADAU1701 powers up with the A/D and DAC disabled, which ensures that the IC is “quiet” after power is first applied.  And if you don’t enable the A/D and DAC by writing to this register, you won’t get any output from the device.

We need to take a look at the ADAU1701 data sheet to understand the DSP Core Control register.  Here it is in full glory:

control_reg

Arrgh–what does this mean?  Each bit turns on or off a feature or capability.  Lets’s go through them so we understand every single bit.  There is more information about each bit in the data sheet.  We’ve condensed the description here to make this list a bit shorter.  If there is an “(X)” near the pin name, we don’t use that feature in the DSP test bed, so it doesn’t matter whether that bit is set or cleared.  RSVD is “reserved”, so we don’t need to worry about that bit, either.

GD[1:0]: (X)  GPIO Debounce Control. Sets debounce time of multipurpose pins that are set as GPIO inputs. 

AACW:  (X) Auxiliary ADC Data Registers Control Port Write Mode. 

GPCW: (X) GPIO Pin Setting Register Control Port Write Mode. 

IFCW: (X) Interface Registers Control Port Write Mode.  

IST:  Initiate Safeload Transfer. Setting this bit to 1 initiates a safeload transfer to the parameter RAM. This bit is automatically cleared when the operation is complete. 

ADM: Mute ADCs. This bit mutes the output of the ADCs.  The bit defaults to 0 and is active low; therefore, it must be set to 1 to transmit audio signals from the ADCs.

DAM Mute DACs. This bit mutes the output of the DACs.

CR: (X) Clear Internal Registers to 0. This bit defaults to 0 and is active low. It must be set to 1 for a signal to pass through the SigmaDSP core. 

SR[1:0] Sample Rate. (X) These bits set the number of DSP instructions for every sample and the sample rate at which the ADAU1701 operates.  Defaults to 00 (48K sampling rate).

So there are only 4 bits that we need to be concerned about:  D4 (ADM), D3 (DAM), D2(CR) and bit 5 (IST).

We aren’t concerned about the IST bit for this example, so let’s just keep that one at “0”.  In order to mute the device, we should make bits D4 and D3 a “0”.  To enable audio, those bits should be “1”.  So if we send a 0000000000011100 to the Core Control Register, we should enable audio.  In hexadecimal notation, that is 0x001C.  And sending a 0x0014 will turn off the DAC without changing anything else.

So let’s look at the code for two other functions:  mute() and unmute().  We will use the same variable declarations as before, and call the I2C_16_write routine to load the data into the ADAU1701:

void ADAU1701_mute()
 {
     Param_address = 0x081C;	//Core Control Register address = 0x081C
     Cmd_Data[0] = 0;		//MSB = 0
     Cmd_Data[1] = 0x14;	//ADC on; DAC off
     I2C_16_write();
}

void ADAU1701_unmute()
 {
     Param_address = 0x081C; 	//Core Control Register address = 0x081C
     Cmd_Data[0] = 0; 		//MSB = 0
     Cmd_Data[1] = 0x1C; 	//ADC on; DAC on
     I2C_16_write();
}

That’s real working code.  If the I2C lines are connected properly and a signal is applied to the input, this code will turn the audio on or off.

Admittedly, this is all rather arcane, but such is the world of DSP chips.  But you might be able to appreciate by now that once you have a few code building blocks in place, such as reading and writing on the I2C bus, the rest of the code is a lot easier.  And these were “hard” examples.  Most of the other code in the DSP test bed is “higher-level” and for the most part is easier to follow.