ENGR1050 C for Engineers

PROJECT: Create A Theremin (CAT0)

OBJECTIVE

Before we can make an instrument that plays by waving your hand, we need to understand its two parts. We commence our exploration of two new components from the electronics kit: an ultrasonic distance sensor that measures how far away things are, and a passive buzzer that makes the sounds we tell it to.

PROCESS

Do note, the productive way to go about this project involves taking the following steps:

  • starting early
  • reading the project page
  • asking questions regarding things you do not know, are not clear on, or are confused about
  • as information, concepts, processes become clear, that is something you can contribute to the project documentation (so you can better remember)

If you start too late, and do not ask questions, and do not have enough time and don't know what is going on, you are not doing the project correctly.

OBTAIN THE PROJECT FILES

To assist with consistency across all implementations, data files for use with this project are available on lab46 as a grabit. Please copy the data to your private semester repository under an appropriate project directory to begin work, and ensure your implementation properly works with the provided data.

The project should be obtained by the Sunday prior to the due date.

lab46:~/SEMESTER/c4eng$ grabit c4eng cat0

Please study any provided code and supporting documents, and look up, experiment, and ask questions on aspects that you do not understand.

OPERATING THE PROJECT

These projects will have two paths to them, depending on which device you are using:

  • Raspberry Pi single board computer (4/5) using the wiringPi library
  • Raspberry Pi Pico microcontroller
lab46:~/SEMESTER/c4eng/cat0$ make
Please select which development environment:

  make wiringpi - using the Raspberry Pi with wiringpi library
  make pico     - using the Raspberry Pi pico

after which, you can use 'make' to build
lab46:~/SEMESTER/c4eng/cat0$ 

Run the make command with the category representing the path you will be working with for this project.

After which, if you do an ls, you will see a cyan-colored file called cat0.c. This is the C file you will be editing for this project.

Go ahead and view it, study the code, eventually make any changes, save and return to the command-line.

To build it, you simply run: make

If there are no errors, it will build, and you proceed from there.

MAINTAINING YOUR REPOSITORY

Be sure to keep your repository up-to-date, regularly committing changes to files.

As a reminder, here are the suggested steps:

-1. make clean <– remove all unnecessary files

  1. git status <– see the current state of things
  2. git pull <– pull down any changes from elsewhere
  3. git add <– add each new or modified file 2a. git add ./ <– try this the first time
  4. git commit -m "your message" <– make a snapshot
  5. git push

UNDERSTANDING THE TECHNOLOGY

The ultrasonic sensor (HC-SR04)

The sensor has two "eyes": one is a speaker that emits a burst of ultrasound (too high-pitched for us to hear), and the other is a microphone listening for that burst to bounce back. It has four pins:

  • VCC and GND: power (the sensor runs on 5 volts)
  • Trig (trigger): an input to the sensor. A HIGH pulse here, at least 10 microseconds long, tells the sensor to take a measurement.
  • Echo: an output from the sensor. It goes HIGH when the burst is sent, and goes LOW when the echo returns.

The sensor in our kit is rated for distances of 2cm to 200cm.

Note what the sensor does NOT do: it does not tell us a distance. It gives us a time: the length of time Echo stayed HIGH. Turning that time into a distance is our job (and our math). Sound travels at about 343 meters per second, which is 0.0343 centimeters per microsecond. Since the time measured covers the trip to the object AND back, we only want half of it:

distance (cm) = time (us) * 0.0343 / 2   which is about   time (us) / 58

Check your understanding: if Echo stays HIGH for 1160 microseconds, how far away is the object?

Timing a pulse this short means our program sits in a tight loop, reading the Echo pin as fast as it can, noting the time when it changes. If Echo never changes (nothing in range, a wire fell out), a naive loop would wait forever, so the code you are given gives up after a while and reports that no echo was received. Your code needs to handle that case.

Sensors are not perfect. Readings wobble. Soft surfaces (clothing, foam) absorb sound; angled surfaces bounce it off in the wrong direction. You will see this for yourself.

The passive buzzer

Our kit contains TWO kinds of buzzer, and they look similar:

  • an active buzzer contains its own oscillator. Apply power, and it makes sound. Remove power, and it stops. Its pitch is built in: we can only turn it ON or OFF.
  • a passive buzzer is just a speaker element: it makes sound only when we feed it a rapidly switching signal, and the pitch it makes is whatever frequency we feed it.

We need to be able to change the pitch, so we want the passive buzzer. To tell them apart:

  • an active buzzer often has a label covering the hole where the sound comes out (though not always)
  • turn them over: the bottom of an active buzzer is sealed (it hides a circuit and a crystal), while the bottom of a passive buzzer is open, and you can see a circuit board, coils, and a magnet through the pin holes

If you use the wrong buzzer you will find out quickly: an active buzzer will make the same tone no matter what frequency we ask for, and a passive buzzer given steady power will just click.

Frequency is how many times per second something repeats, measured in Hertz (Hz). For sound, frequency is pitch: the higher the frequency, the higher the note. The concert A that orchestras tune to (the A above middle C) is 440Hz. Doubling a frequency raises the note by an octave. Dividing an octave into twelve equal steps (the twelve semitones of the piano keyboard) means each step multiplies the frequency by the twelfth root of 2 (about 1.0595).

The passive buzzer in our kit has a resonant frequency of 2000Hz, meaning it is loudest there. Notes far from 2000Hz will sound weaker.

On the Raspberry Pi, the wiringPi library provides softTone functionality: softToneCreate() prepares a pin, and softToneWrite() sets the frequency (0 for silence) while a background thread does the rapid switching for us. On the pico, we use its PWM hardware to produce the switching, and the code provided has tone_setup() and tone_play() functions to do so (study them!).

TASK

Taking our knowledge of GPIOs, LEDs, and now the ultrasonic distance sensor and the passive buzzer:

  • wire up the ultrasonic distance sensor (two unique GPIO pins: one for trigger, one for echo)
  • wire up the passive buzzer to its own unique GPIO pin
  • get the provided code working: it plays a test tone, then measures the distance continuously, flagging when an object is within a threshold distance. On the Raspberry Pi it reports the distance on the screen. The pico has no screen, so it lights its onboard LED whenever an object is within the threshold
  • extend the code as described below

theremin circuit

The circuit

You will be building two small circuits. Pin choices are defined at the top of your cat0.c file; if you wire to different pins, change the values there.

Ultrasonic sensor

Sensor pin Raspberry Pi Pico
VCC 5V (physical pin 2) VBUS (physical pin 40)
GND GND (physical pin 6) GND (physical pin 38)
Trig wiringPi 4 (GPIO23, pin 16) GP2 (physical pin 4)
Echo wiringPi 5 (GPIO24, pin 18) GP3 (physical pin 5)

IMPORTANT: Echo must go through a voltage divider. The sensor is powered by 5 volts, so its Echo output is a 5 volt signal. The GPIO pins on both the Raspberry Pi and the pico are 3.3 volt devices, and are NOT 5 volt tolerant: connecting Echo directly can damage the pin (or the whole board). Three 1k ohm resistors fix this:

Echo ----[ 1k ohm ]----+---- to GPIO (wiringPi 5 / GP3)
                       |
                       +----[ 1k ohm ]----[ 1k ohm ]---- GND

The resistors split the 5 volts in a 1:2 ratio, leaving 5 * 2/3, which is about 3.3 volts at the GPIO. Trig is fine connected directly: the GPIO is sending 3.3 volts to the sensor, which the sensor understands.

Passive buzzer

The buzzer draws more current than a GPIO pin should supply, so we have the GPIO control a transistor, which in turn switches the buzzer:

Connection Goes to
GPIO (Pi: wiringPi 0 / pico: GP4, physical pin 6) 1k ohm resistor
other end of 1k ohm resistor transistor base
transistor emitter GND
transistor collector buzzer negative (-)
buzzer positive (+) 5V

For the Raspberry Pi, wiringPi 0 is GPIO17 (physical pin 11), and 5V is physical pin 2 (or 4). For the pico, 5V is VBUS (physical pin 40). The GPIO only ever touches the 1k ohm resistor and the transistor base: the 5 volts goes no further than the buzzer and the transistor's collector.

The transistor in the kit is an NPN type (S8050). Make sure you have the correct leg for each of the emitter, base, and collector: with the flat side facing you and the legs pointing down, they are (left to right) Emitter, Base, Collector. If the buzzer never sounds, or sounds constantly, check the orientation and your connections!

The extensions

The code you are given works, but it has some deliberate gaps, and what you fill in here you will need in the next project (where we turn this into an instrument). Read all of the provided code carefully, and make sure you understand every line: you may be asked to explain it.

You are to:

1. Calibrate your sensor. Use a ruler or tape measure to place an object (a book, a flat piece of cardboard) at known distances in front of the sensor, and compare what the sensor reports. Fill in the calibration table in the comments at the top of the source file with readings at 10, 25, 50, 100, and 150cm, and notes on what you notice: how far off is it? Does the reading jump around? What happens closer than 2cm? What happens with a soft object (try your sleeve) or an angled one?

(The pico has no screen, so the only thing it can tell you is whether the distance is within THRESHOLD, by lighting its LED. To find what it believes the distance to be, find the smallest THRESHOLD that lights the LED with your object at each ruler distance. The comments in the source file explain.)

2. Finish play_note(). This function is given a frequency (in Hertz) and a duration (in milliseconds), and sounds that note for that duration. As provided, it starts the note, and waits… but never ends it: after the first note, the buzzer just keeps sounding. A note should be followed by silence (for NOTEGAP milliseconds), so that notes played back to back are distinct from each other, and so that the buzzer is quiet when we are done. Study the function, and complete it.

3. Play a scale. Create an array holding the frequencies of the C major scale, from the C just below the A of 440Hz up to the C an octave above it (eight notes), and use a loop and your play_note() to play each note in turn at startup, in place of the single test note. Do not write eight separate play_note() calls!

Some guidance:

  • the notes of a major scale are spaced (in semitones) whole step, whole step, half step, whole step, whole step, whole step, half step. A whole step is two semitones, a half step is one
  • you may hard-code the frequencies in your array, or calculate them (from 440Hz and the semitone relationship above, with the pow() function from math.h). If you hard-code them, cite where you found them. Either way, they should sound like a scale!

4. Finish sample_distance(). A single reading can be noisy, or fail altogether, so this function takes several (SAMPLES of them), stores the good ones in an array, and returns the median of them: the middle value, after sorting them from smallest to largest. As provided, it does nearly everything: it takes the readings, keeps only the successful ones, reports no reading if every one of them failed, and returns the middle value of the array. But the values are never sorted, so what it returns is simply the third reading, not the median!

The loops that perform a "bubble sort" are provided; what is missing is what goes inside them: the comparison and the swap. Complete it. Then:

  • if you end up with an even number of good readings, there are two middle values. The provided function returns the upper one. Decide if that is what you want (or if you would rather average the two) and document your decision in a comment
  • test it: move an object in and out of range, wave your hand across the sensor, and observe how steadily it behaves compared to a single reading (on the Pi, watch the reported distance; on the pico, watch the LED when your object sits right around THRESHOLD)

As you do this, consider the trade-off the constants in this function represent. What does taking more samples do for the smoothness of your readings? What does it do for how quickly your program notices that something has moved? Experiment with SAMPLES and SAMPLEGAP. We will care about this a great deal in the next project.

EXECUTION

If on the pico, follow the process for downloading the binary file and programming your pico. The pico has no screen: its onboard LED is how it tells you what the sensor sees (lit means something is within THRESHOLD), and the buzzer is how you know it is alive.

If on the full Raspberry Pi, you have a green executable you can run just as we've done in our other projects.

STRATEGY

The general flow of the process (one way of going about it, anyway) can be described as follows:

INITIALIZE THE GPIOS AND THE BUZZER
FOR EACH NOTE IN THE SCALE ARRAY:
    PLAY THE NOTE FOR A MOMENT

REPEAT INFINITELY:
    OBTAIN THE MEDIAN DISTANCE
    REPORT THE DISTANCE (ON THE PI), OR THAT THERE WAS NO READING
    SHOULD AN OBJECT BE WITHIN THE THRESHOLD:
        FLAG IT (LIGHT THE LED ON THE PICO)
    DELAY BRIEFLY

REPEAT BLOCK

And for the median function (the parts provided, and the parts you finish):

GIVEN NOTHING (THE SENSOR IS ALREADY SET UP):
    REPEAT FOR EACH OF THE SAMPLES TO BE TAKEN:
        MEASURE THE DISTANCE
        SHOULD THE MEASUREMENT HAVE SUCCEEDED:
            STORE IT IN THE NEXT FREE SPOT IN THE ARRAY
        PAUSE FOR THE SENSOR TO SETTLE

    SHOULD NO MEASUREMENTS HAVE SUCCEEDED:
        RETURN NO READING

    SORT THE VALUES THAT WERE STORED
    RETURN THE MIDDLE ONE

SUBMISSION

To be successful in this project, the following criteria (or their equivalent) must be met:

  • Project must be submit on time, by the deadline.
    • Late submissions will lose 33% credit per day, with the submission window closing on the 3rd day following the deadline.
  • All code must compile cleanly (no warnings or errors)
    • Compile with the -Wall and --std=gnu18 compiler flags
    • All requested functionality must conform to stated requirements (either in this document or in a comment banner in the source code files themselves).
  • Executed programs must display in a manner similar to provided output
    • Output formatted, where applicable, must match that of project requirements
  • Processing must be correct based on input given and output requested
  • Output, if applicable, must be correct based on values input
  • Code must be nicely and consistently indented
  • Code must be consistently written, to strive for readability from having a consistent style throughout
  • Code must be commented
    • Any "to be implemented" comments MUST be removed
      • These "to be implemented" comments, if still present at evaluation time, will result in points being deducted.
    • Sufficient comments explaining the point of provided logic MUST be present
  • No global variables (without instructor approval), no goto statements, no calling of main()!
  • Track/version the source code in your lab46 semester repository
  • Submit a copy of your source code to me using the submit tool (make submit on lab46 will do this) by the deadline.

Submit Tool Usage

Let's say you have completed work on the project, and are ready to submit, you would do the following:

lab46:~/SEMESTER/c4eng/cat0$ make submit

You should get some sort of confirmation indicating successful submission if all went according to plan. If not, check for typos and or locational mismatches.

What I'll be looking for:

RUBRIC

I'll be evaluating the project based on the following criteria:

286:cat0:final tally of results (286/286)
*:cat0:used grabit to obtain project by the Sunday prior to duedate [13/13]
*:cat0:post a short video to the class DISCORD [26/26]
*:cat0:ultrasonic sensor trigger and echo hooked to unique GPIOs [26/26]
*:cat0:passive buzzer hooked to its own unique GPIO [13/13]
*:cat0:distance correctly measured and reported (pico: via LED) [26/26]
*:cat0:calibration table completed, with observations [39/39]
*:cat0:no echo, out of range is handled (no hang, no bogus values) [13/13]
*:cat0:play_note() completed: each note ends, followed by short silence [26/26]
*:cat0:C major scale played from an array of frequencies using a loop [52/52]
*:cat0:sample_distance() completed: values sorted, true median returned [39/39]
*:cat0:code tracked in lab46 semester repo [13/13]

Additionally

  • Solutions not abiding by spirit of project will be subject to a 50% overall deduction
  • Solutions not utilizing descriptive why and how comments will be subject to a 25% overall deduction
  • Solutions not utilizing indentation to promote scope and clarity or otherwise maintaining consistency in code style and presentation will be subject to a 25% overall deduction
  • Solutions not organized and easy to read (assume a terminal at least 90 characters wide, 40 characters tall) are subject to a 25% overall deduction