Lab 9: Light and Sound Show


Topics

  1. First Build
    1. The Circuit
    2. The Code
  2. Hardware Theory
    1. RGB LED
    2. R/2R Ladder DAC Circuit
  3. Software Theory
    1. Input Pullup
    2. Built-in Registers
    3. Long Term Storage
    4. Big Data Storage
    5. Returning More Than One Value From a Function
  4. Exercise
    1. Remember a Colour Selection.
    2. Play a Sound
    3. Challenge



    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 Demo Video (YouTube)

    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 build all eight R/2R modules. If you don't have enough, you should ask around or remove one module. If you leave out modules, you will have to halve sound levels for each missing 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 9 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; //divide this by 2 for each missing R/2R module
       }
       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 a 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 a isolated power supply. You can learn more about R/2R circuits on the IKALogic website.


    3. Software Theory

    3.1 Input Pullup

    Input Pullup Video (YouTube)
    Buttons That Don't Cycle (YouTube) & Related Challenge (Lab 3)

    When you learned to wire up buttons, you learned to use an external pull resistor. Some buttons are pull up, some are pull down. Many microcontrollers have a pull resistor built in to their input pins that you can use to simplify your circuit designs. The Arduino has built-in pull up resistors. Other microcontrollers have built-in pull down.

    For example, in this lab there's a button on pin 12 with no pull resistor. Yet it works. Pin 12 has been put into built-in pullup mode. There are two ways to do this. The first is what we do in the First Build code:

      
      //Set a pin to input pull up mode (old way).
      pinMode(buttonPin, INPUT); // Put pin in input mode
      digitalWrite(buttonPin, HIGH); // Writing HIGH to an input activates its pull up resistor
    
    The second combines both into one step:
    
      //Set a pin to input pull up mode (new way).
      pinMode(buttonPin, INPUT_PULLUP);
    

    3.2 Built-in Registers

    PORTD Video (YouTube)

    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.3 Long Term Storage

    Long Term Storage With EEPROM Video (YouTube)

    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.put() command. You can put any type, but different types take up a different number of addresses. You can check how many address slots a put takes with the sizeof() function.

    For example to write an 8-bit value you would do this:

      char value = 10; //char is a signed 8 bit value that uses only one slot.
      EEPROM.put(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 an Arduino Uno you can use addresses from 0 to 1023.

    On the Arduino integers are 16-bit values, so they take up two EEPROM slots. This means that two addresses will be used, so plan your storage accordingly. To write an integer you would do this:

      int value = 1024;
      EEPROM.put(address1, value);      //This writes the int into two slots
    

    To read a value from EEPROM you use the EEPROM.get() command. Like the put command, the read command will read the right number of consecutive slots to build up the type of the variable you are reading to. This means the type you read with must match the type you wrote with.

    For example to read an 8-bit value you would do this:

      char value2;
      EEPROM.get(address, value2);
    

    To read an integer you would do this:

      int value2;
      EEPROM.get(address1, value2); // This reads the int from two consecutive slots
    

    3.4 Big Data Storage

    Using PROGMEM Video (YouTube)

    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 bytes like this:
    const PROGMEM byte 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)


    So, going into this function we need to have known values for h, s, and v that represent the color in one form. When the function is done we'll have values in r, g, and b that represent levels we can use directly on our RGB led. This has already been done for you in exercise 4.1.


    4. Exercise

    4.1 Use Long Term Storage to Remember a Colour Selection.

    Starting with the First Build circuit:
    Exercise 1 Playlist

    4.2 Use Big Data Storage to Play a Sound

    Exercise 2 Playlist

    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.

    To do this exercise you will need the following additional tools (they are all free):

    Running Audacity on macOS Catalina requires extra steps. Watch the video, to see what to do. When the video says you need to paste a command, use this:

    open /Applications/Audacity.app/Contents/MacOS/Audacity

    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.

    Deliverables

    During the lab (10 marks):

    In URCourses immediately after you are marked (to release marks):