Lab 8: Light and Sound and On-Chip Storage.


1. First Build

The circuit this week combines an RGB LED and a digital to analog conversion circuit called an R/2R ladder.

1.1 The Circuit

Build the following circuit. Connect your headphones by using the two paperclips to clamp the tip and base of the headphone jack. If you have enough 560Ω resistors, you should add one more R/2R module just before the headphone jack or paperclips. If you don't have enough, you should ask around or remove one module.

First Build Circuit: 7-bit DAC, RGB LED and a Button


Implementation of the First Build Circuit

1.2 The Code

Use this code to test your circuit. You will know everything is working if pushing the button causes the RGB LED to alternate between red, green and blue and if you can hear a tone fading from loud to quiet in your headphones.

/* Lab 8 First Build Sketch 
 * Written Nov 15 2011 by Alex Clarke
 */
// Pin Constants
const int redPin = 9;
const int greenPin = 10;
const int bluePin = 11;
const int buttonPin = 12;

// Color Variables
int color = 0;
// Button State Variables
float curButton = HIGH;
float lastButton = HIGH;

// variables for tone
int value;
int toneOn;
int increment = 1;

void setup()
{
  // Set up RGB LED pins
  pinMode(redPin, OUTPUT);
  pinMode(greenPin, OUTPUT);
  pinMode(bluePin, OUTPUT);

  //Set up button pin
  pinMode(buttonPin, INPUT);
  // Trick to make this a pull up button
  // Other side of button must be attached to ground
  digitalWrite(buttonPin, HIGH);
  // Quick way to set pins 0 to 7 to OUTPUT
  DDRD = B11111111;


void loop()
{
  //Store Last Button State
  lastButton = curButton;

  //Read Current Button
  curButton = digitalRead(buttonPin);


  //If button was just pressed cycle color
  if (curButton == LOW && lastButton == HIGH)
  {
    color++;
  }
  //Set RGB LED to a color
  if (color == 0) //RED
  { 
    digitalWrite(redPin, HIGH);
    digitalWrite(greenPin, LOW);
    digitalWrite(bluePin, LOW);
  }
  else if (color == 1) //GREEN
  {
    digitalWrite(redPin, LOW);
    digitalWrite(greenPin, HIGH);
    digitalWrite(bluePin, LOW);
  }
  else if (color == 2) //BLUE
  {
    digitalWrite(redPin, LOW);
    digitalWrite(greenPin, LOW);
    digitalWrite(bluePin, HIGH);
  }
  else //Invalid color, reset to RED
  {
    color = 0;
  }

  
  if (toneOn == true)
  {
    PORTD = value;
  }
  else
  {
    PORTD = 0;
  }
  
  toneOn = !toneOn;
  value+= increment;
  
  if (value == 255 || value == 0)
  {
    increment = -increment;
  }
  delayMicroseconds(1000000l/440);
}


2. Hardware Theory

2.1 RGB LED

The RGB LED is a single package that contains three different colored LED elements each with its own positive terminal or anode. They all share a single negative terminal or cathode. Because the colours, red, green and blue, are the additive primary colours, you can send different signal strengths to each of the three anodes to create almost any colour. Some shades can only be perceived by contrast with the environment, so you may have difficulty getting the exact shade you want.

RGB LED Pins

2.2 R/2R Ladder DAC Circuit

If you want to be able to turn a digital signal into an analog signal you need to use some kind of Digital to Analog Converter or DAC. The R/2R ladder circuit in this lab is a classic example of a DAC. Because of the ladder arrangement of the resistors in this circuit, a current division effect is created that makes it so that the signal nearest to the output is delivered at half voltage. The next nearest at half that voltage, and so on through each ladder module. In this way each signal pin contributes a voltage that is appropriate to the binary value in the digital signal.

R/2R ladder diagram

Just as the values of each binary digit can be added together to tell us the value of the a binary number, this circuit adds together the voltages indicated to create an analog representation of the binary input.The output of this circuit will not quite reach the reference voltage of 5V. Instead it will have an error of reference/2 N where N is the number of modules in the ladder. In our circuit that means that the error will be 5V/256 or 0.1953V. This also happens to be the voltage step size – the voltage that represents a difference of 1 in the digital signal. As you add more modules at the output end, the voltage step size will shrink and you can more accurately reach specific voltage levels.

Our implementation is good enough for experimenting with, but the headphones can introduce noise into the resistor network and distort the audio signal. Normally the output of the resistor ladder is used to drive a special circuit called an op-amp. The op-amp copies the exact voltages from the ladder circuit to an isolated power supply. You can learn more about R/2R circuits on the IKALogic website.


3. Software Theory

3.1 Built-in Registers

Your Arduino has three built-in port registers. These are a bit like shift registers - you can write a single value to them and it will be presented all at once. You can't chain them together though. You can learn about all of them on the Arduino.cc Port Manipulation page.

Pins 0 to 7 are all part of port D. You can initialize each of these pins to input or output mode with a single assignment to DDRD – the Port D Data Direction Register – like this:
  DDRD = B11110000; // sets pins 0 to 3 to input, and pins 4 to 7 to output
You can write to pins 0 to 7 with a single assignment as well. You assign a value to PORTD like this:
  PORTD = value; // the low order 8 bits from value will be put on pins 0 to 7
This is the equivalent of the following code, but it is much faster:
  digitalWrite(0, value&B00000001);
digitalWrite(1, value&B00000010);
digitalWrite(2, value&B00000100);
digitalWrite(3, value&B00001000);
digitalWrite(4, value&B00010000);
digitalWrite(5, value&B00100000);
digitalWrite(6, value&B01000000);
digitalWrite(7, value&B10000000);

In this week's first build code we use port manipulation to write values to the DAC. We do this because using digitalWrite to set the values of each pin would be too slow - the DAC would present several different levels of output as you wrote to each pin. Your ear would hear this as noise. You might not notice this in the first build example because we hold each volume level for a long time, about .003 seconds. The noise would be very obvious if you tried to play back a sound clip. In Exercise 2 you will play back an 8000Hz sound clip. At 8000Hz you hold each volume level for only .000125 seconds.

3.2 Long Term Storage

Sometimes you want your Arduino to remember things while it is turned off. The Arduino UNO has 1KB of long term storage called EEPROM or Electronically Erasable Programmable Read-Only Memory. It is a bit like having a little hard drive in your Arduino. This storage can be read as often as you like, but it can only be written a limited number of times - about 100,000 times. You should only write values there when you need to. We will use it a few times for this lab, but not enough to cause any trouble.

To use EEPROM you need to include the EEPROM library. You do this by placing #include <EEPROM.h> at the beginning of your program.

To write a value to EEPROM you use the EEPROM.write() command. You can only write one byte at a time. If you have a larger value to write, you have to break it up into 8-bit chunks.

For example to write a byte you would do this:

  char value = 10; //char is a signed 8 bit value.
EEPROM.write(address, value);

Where address is a number that represents the byte in EEPROM you want to write to. It's like an array index.

On the Arduino, integers are 16-bit values. To write an integer you would do this:

  int value = 1024;
EEPROM.write(address1, value); //This writes only the low 8 bits
EEPROM.write(address2, value >> 8); //This shifts value over by 8 bits
// and allows you to write the high 8 bits

To read a value from EEPROM you use the EEPROM.read() command. Like with writing, you can only read one byte at a time. If you want to read a larger value you have to build it up.

For example to read a byte you would do this:

  char value2;
value2 = EEPROM.read(address);

To read an integer you would do this:

  integer value2, temp;
value2 = EEPROM.read(address1); //Read in the low 8 bits
temp = EEPROM.read(address2); //Read in the high 8 bits
value2 |= temp << 8; //Shift them into the right position,
// then combine them with the low 8 bits

3.3 Big Data Storage

Some programs require a lot of data to work properly, but the Arduino UNO's working memory, or SRAM, is very limited. It can only work with 2KB of data at one time. This isn't enough to store and play sound samples. All the arrays and variables you have used in your program in past labs have used the SRAM. If you want to be able to use more data in your programs you can put it in the 32KB of flash memory that is used to store your programs and the Arduino bootloader. This flash memory is read only. You cannot write to it from a running program. If you were allowed to do this, then your Arduino could change its programming and render itself inoperable.

To use flash memory you need to include the PROGMEM library. You do this by placing #include <avr/pgmspace.h> at the beginning of your program.

You store data in flash by declaring your variables and arrays in a special way:

For example, if you wanted to store 8-bit music where each sample was a value between 0 and 255, you would declare an array of unsigned characters like this:
PROGMEM prog_uchar music_array[] =
{
127, 137, 127, 117, 149, 126, 146, 165, //0 - 7
102, 133, 136, 113, 121, 120, 139, 136, //8 - 15

...

127, 127, 127, 127, 127, 127, 127, 127, //10960 - 10967
127, 127, 127, 127, 128, 128, 128, 128, //10968 - 10975
};

This shortened example suggests that we want to store 10975 samples. Notice that we have to use an array of explicitly stated values. No normal music conversion program would do this for you. I have written a converter for Mac OS X that you can use in the lab. It will convert mono .WAV files into an array of 8-bit unsigned values at a variety of different bit rates. There is also a Windows tool that will do the same job, but it tries to do more and so is more complicated to use. The links to these programs are in the exercise section.

You read values out of flash by using the pgm_read command that matches the size of your data type. You can find a list of these commands in Arduino.cc's PROGMEM documentation.

For example, if you wanted to read the 8-bit music samples from the example above, you would use pgm_read_byte() like this:

  sample = pgm_read_byte(music_array + i);

Where music_array is the name of the array declared above, and i is the array element you want to retrieve. Normally you only store arrays in flash, but it is possible to store simple variables if you wish.

3.4 Returning More Than One Value From a Function

Sometimes you need to get more than one value back from a function. The return statement in the Arduino programming language will only return one value. So, instead, we use a special type of parameter called a reference parameter. If you change the value of a reference parameter in a function, then the change will be visible in the argument used on that parameter when the function is finished.

Reference parameters have an & right after their data type and just before their name.

In exercise part 1, we use a function called hsv2rgb to convert HSV colors to red, green and blue values which are compatible with our LED. HSV color is useful to us, because it represents colors directly in terms of their hue – an angle on the colour wheel where red is 0º. The red, green and blue values are returned as reference parameters.

Function call:

hsv2rgb(hue, saturation, value, red, green, blue);

Function header:

void hsv2rgb(float h, float s, float v, float &r, float &g, float &b)

4. Exercise

4.1 Use Long Term Storage to Remember a Colour Selection.

Starting with the First Build circuit:

4.2 Use Big Data Storage to Play a Sound

Starting with the First Build circuit, modify the DAC_Exercise to store and play back a sound sample you create in Audacity. Your lab instructor will demonstrate the basic process as part of the lab lecture.

4.3 Challenge

Find a way to remix your sound sample. Audacity can be told to pretend that a WAV file is at 8000Hz. It can also be told to show at what sample the the play head is located. It should be possible to use positions in your sound sample to do things like play a song.