CSCI 255 — Bit banging with structures and pointers

You’re going to learn a bit about C structures today. The C structure is similar to a method-less Java class.

Getting ready

Attach your Arduino to a breadboard with an LED and current limiting resistor in series connected to pin 5 or borrow a DangerShield which already has an LED at pin 5.

Verifying the circuit

To make sure everyone’s breadboard is connected correctly, try out the following program. Both you and all your lab neighbors should see the the LED blink.

const int outputPin = 5 ;

void setup() {
  pinMode(outputPin, OUTPUT) ;
}

void loop() {
  digitalWrite(outputPin, LOW) ;
  delay(1000) ;
  digitalWrite(outputPin, HIGH) ;
  delay(1000) ;
}

Using variables

Declare an int variable delayTime as a local variable of loop(). Initialize delayTime to 1000. Use delayTime as the argument to the calls of delay().

Compile and run (verify and upload in the Arduino IDE) your program. There should be no change in its behavior.

If you are having trouble remembering how to declare and use local variables, take a look at either Local Variables in Java or a C tutorial on Functions and Global/Local variables.

Faster than the eye

Do two experiments.

Hearing what you can’t see

Set both delayTime to 10. Although you can’t see the LED flashing; if you replace it with a piezo speaker, you should be able to hear it.

If you are using a breadboard, unplug your Arduino for a minute and replace the LED at pin 5 with a piezo speaker. Some people, such as Brett Hagman of Rogue robotics, warn that you should always connect a resistor in series with the speaker. Most Arduino examples omit the resistor, except to make the speaker a little quieter. Do whatever you wish.

If you are using a DangerShield, change your program to set outputPin to pin 3. That’s where the buzzer is located.

Reconnect the Arduino and see if you hear anything. You should hear a tone around 50 hz. That’s G1 which is about two and a half octaves below middle C.

Change both delayTime1 and delayTime2 to 2. That should give you a 250 Hz tone: close to B3, the note just below middle C.

Either use your perfect pitch or your cell phone’s guitar tuner app to check the accuracy of the note. (I use the embarrassingly named gStrings to tune my ukulele.)

The need for higher resolution

Clearly we need a delay with a resolution better than milliseconds to make music. Otherwise, we are stuck with E4 and B4 as our only notes above middle C.

Fortunately, there is an Arduino function delayMicroseconds() with microsecond resolution. The only problem is that delayMicroseconds() is restricted to delays no more than 16383 (215-1) microseconds. That’s not really a problem as long as we avoid the lowest notes of the piano.

Modify your program to use delayMicroseconds() rather than delay(). Also, change it to play a note at around 440 Hz, the ISO specified pitch for A4, the A above middle C.

Now, let’s see. The frequency is 440 Hz. This means the period (1/frequency) is about 2272 µs. So let’s set delayTime to the half-period in µs or 1136.

Again, test it with your tuner app.

Varying the sound a bit

You don’t get much of a sound with our little piezo speakers, but you can get a little variation by changing the times for the high and low outputs.

Modify your program to pass delayTime*4/5 to the first call delayTime/5 to the second call of delayMicroseconds(). Does this change the sound? Some claim that this makes the note “thin” or “reedy.”

In any case, set both calls back to using delayTime.

Bit banging

Many microcontrollers contain special hardware to support communication with other devices, such as sensors and other microcontrollers. If this hardware isn’t available, the programmer will have to resort to bit banging, where low-level programming is used to read and write voltage levels on pins using calls similar to digitalWrite(9,LOW) or mPORTDSetBits(BIT_2) or statements similar to PORTB|=B00010000 or PORTBSET=0b00010000. Some programmers consider bit banging to be the programming equivalent of free climbing and post their bit banging conquests on YouTube. For the ultimate in this genre, check out I2C Bit-Banged without Microcontroller!.

In Tom Igoe’s Play a Melody using the tone() function Arduino tutorial, the tone function is used to generate a square wave that can be sent through a piezo speaker. If you read the reference page for tone, you see the function has some unusual restrictions, such as only allowing one tone to be played at a time.

Use the source. Look!

It’s time to take a look at GitHub. Open up the GitHub repository for arduino/Arduino.

Now search for Tone.cpp in the repository. This is the C++ source code for the tone function.

Let’s take a quick guided tour of Tone.cpp. Note the following:

With all that heavy use of timers and interrupts, this clearly isn’t bit banging.

Starting with the tone() program

Let’s start with a fresh sketch. Create a new Arduino sketch and cut-and-paste the following program for your code.

const int outputPin = 5 ;   // or 3 if in DangerShield

void playTone(int pin, int frequency, int duraction) ;
/*
   pin:        input pin for playing note
   frequency:  frequency of the tone in cycles per second (Hz)
               if frequency is 0, play no tone, that is, take a rest
   duration:   length of the tone (or rest) in milliseconds (msec)
 */

void setup() {
  pinMode(outputPin, OUTPUT) ;
}

#define MIDDLEC 261.625

void loop() {
  playTone(outputPin, (int)MIDDLEC, 750) ;
  playTone(outputPin, 0, 250) ;
  playTone(outputPin, (int)(MIDDLEC*5/4), 750) ;
  playTone(outputPin, 0, 250) ;
  playTone(outputPin, (int)(MIDDLEC*3/2), 750) ;
  playTone(outputPin, 0, 250) ;
  playTone(outputPin, (int)(MIDDLEC*2), 750) ;
  playTone(outputPin, 0, 250) ;
}


// This implementation of playTone() uses tone()
void playTone(int pin, int frequency, int duration) {
  if (frequency > 0) {
    tone(pin, frequency) ;
    delay(duration) ;
    noTone(pin) ;
  } else {
    delay(duration) ;
  }
}

The program should play something resembling a major chord, but the resemblance may be very weak with some of our piezos.

A little bit like bit banging

You goal for this part of the lab is to rewrite playTone so that it does no calls to tone, but instead uses digitalWrite and delayMicroseconds to create the music.

This will not be easy.

Here is one suggestion for completing the task.

There are better ways to implement playTone, but this approach has worked well in previous CSCI 255 offerings.

This isn’t truly a bit banging solution because delayMicroseconds is implemented with timers and interrupts. If you used delay loop functions, like _delay_loop_2, you could avoid timers.

Giving some structures to your program

So have just you had written a routine playTone to play on a pin, a tone of a frequency for a duration. Again, here’s the C/C++ prototype for that function.

void playTone(int pin, int frequency, int duration) ;

Our representation of a note has an abstraction issue. The note is a real-world entity: We shouldn’t be splitting the note into two variables, one for frequency and the other for duration. We should use a C structure to represent a note.

Defining the structure

So add the following structure definition at the front of your program, close to the prototype you have for playTone.

struct noteInfo {
   int  frequency ;    /* frequency in Hz */
   long duration ;     /* duration in mSec */
} ;

Note the slight resemblance to a Java class definition.

Storing the song

Using the struct noteinfo you can store your entire song in an array, rather than embedding it in your program. So just insert the following, admittedly ugly, definition right before the call to setup.

#define MIDDLEC 261.625

struct noteInfo song[] = {
  { (int)MIDDLEC, 750},
  { 0, 250},
  {(int)(MIDDLEC*5/4), 750},
  {0, 250},
  {(MIDDLEC*3/2), 750},
  {0, 250},
  {(int)(MIDDLEC*2), 750},
  {0, 250}  
} ;

Now add an additional prototype for playTone to receive the note as a pointer to the struct noteInfo. The * in the prototype indicates that we are passing the address rather than the contents of the structure. This is the way it’s always done in C.

void playTone(int pin, struct noteInfo *note) ;

Because Arduino is based on C++ and consequently supports polymorphism, you should still be able to compile you program.

Using your song

Next, modify loop to call playTone for each note of the song. We will use one of the ugliest features of C/C++, the sizeof hack for determining the size of a statically allocated array. Hold you nose while you replace loop with the following:

void loop() {
  int i ;
  for (i = 0; i < sizeof(song)/sizeof(song[0]); ++i) {
    playTone(outputPin1, &song[i]) ;
  }
}

At this point, the compile should fail with an error message that there is no implementation of playTone that fits the playTone(int, noteInfo*) signature.

While C insists that you always refer to the type of the second parameter as struct noteInfo; C++ is fine with noteInfo. This is related to the esoteric domain of the C++ namespace.

Adding another playTone

Using your object-oriented programming skills, add the three-line implementation of playTone(int, struct noteInfo*) needed to complete this program. This is an overload of playTone. Do not remove the first playTone.

While this is a three-liner, it does require the use of the dereference operator * to map a pointer to its contents followed by the element select operator . to retrieve the desired field. It looks a bit like (*pntr).fld. Notice the Java also uses the dot for selecting fields (and methods) of an object.

Because (*pntr).fld is so ugly, C allows the use of the sweeter arrow operator pntr->fld  as syntactic sugar for (*pntr).fld .

What about simultaneous notes?

If you want decent sounding music with harmonies; you need a better speaker, a small amplifier, and a much more complicated program, such as the one found in the Skill Builder: Advanced Arduino Sound Synthesis Make project. I’d be happy to lend you the parts.

But first, you need to take CSCI 333, Data Structures, to learn about priority queues, and then take CSCI 331, Operating Systems, to learn how priority queues are used to schedule tasks and then write a program that schedules the wave changes.