Corning Community College
ENGR1050 C for Engineers
PROJECT: Colored LED Research (clr2)
A mild throwback to the ledX projects, where we find ourselves once again playing with an LED, only an LED of MANY colours: the RGB LED!
We will be integrating buttons and a switch to allow finer, manual control over the individual red, green, blue values of the LED.
As an exercise in toning your abstraction abilities (literally seeing one thing, but understanding and using it as something else), there will be a number of 'generic' terms used throughout this (and other) documents throughout the course, requiring you to substitute in the pertinent information (else face confusion or error).
Some examples:
This sort of abstraction is very similar to that we will find in our utilization of variables in programming, where we can have a “name”, but the data associated with it can change based on various conditions.
Do not be a literalist computer! Start to exercise your abstraction abilities.
This document is written with TWO locations in mind:
There are commands you can ONLY run on one system or the other. Pay attention to any prompt cues in the given examples (or section headings, context of language leading up to any examples).
For example:
Please pay attention to your prompt, so you can perform the needed activity on the correct system.
In “The C Book”, please read through Chapter 8.
Review needed concepts in this tutorial and also this one
ALSO: read through Chapter 5 in the FreeNove Tutorial
AND: the wiringPi API on Sofware PWM functionality
For this project, you will be working with a C program using the wiringPi library on the Raspberry Pi, wiring up a circuit containing an RGB LED to your breadboard and witnessing your ability to access and control it in varying states of on and off (more than just simply “ON” and “OFF”) via a software-based Pulse Width Modulation (PWM) scheme.
With the help of buttons and a switch, we will be able to allow the user to manually adjust the individual red, green, and blue values of the RGB LED,
A loop is basically instructing the computer to repeat a section, or block, or code a given amount of times. It can be used in the following fashions:
Loops enable us to simplify our code– allowing us to write something closer to a “one-size-fits-all” algorithm (provided the algorithm itself can appropriately scale!), where the computer merely repeats the instructions we gave. We only have to write them once, but the computer can do that task any number of times.
Loops can be initially difficult to comprehend because unlike other programmatic actions, they are not single-state in nature– loops are multi-state. What this means is that in order to correctly “see” or visualize a loop, you must analyze what is going on with EACH iteration or cycle, watching the values/algorithm/process slowly march from its initial state to its resultant state. Think of it as climbing a set of stairs… yes, we can describe that action succinctly as “climbing a set of stairs”, but there are multiple “steps” (heh, heh) involved: we place our foot, adjust our balance– left foot, right foot, from one step, to the next, to the next, allowing us to progress from the bottom step to the top step… that process of scaling a stairway is the same as iterating through a loop– but what is important as we implement is what needs to happen each step along the way.
With that said, it is important to be able to focus on the process of the individual steps being taken. What is involved in taking a step? What constitutes a basic unit of stairway traversal? If that unit can be easily repeated for the next and the next (and in fact, the rest of the) steps, we've described the core process of the loop, or what will be iterated a given number of times.
In C and C-syntax influenced languages (C++, Java, PHP, among others), we typically have 3 types of loops:
A for() loop is the most syntactically unique of the loops, so care must be taken to use the proper syntax.
With any loop, we need (at least one) looping variable, which the loop will use to analyze whether or not we've met our looping destination, or to perform another iteration.
A for loop typically also has a defined starting point, a “keep-looping-while” condition, and a stepping equation.
Here's a sample for() loop, in C, which will display the squares of each number, starting at 0, and stepping one at a time, for 8 total iterations:
int index = 0; for (index = 0; index < 8; index++) { fprintf(stdout, "loop #%d ... %d\n", (index+1), (index*index)); }
The output of this code, with the help of our loop should be:
loop #1 ... 0 loop #2 ... 1 loop #3 ... 4 loop #4 ... 9 loop #5 ... 16 loop #6 ... 25 loop #7 ... 36 loop #8 ... 49
Note how we can use our looping variable (index) within mathematical expressions to drive a process along… loops can be of enormous help in this way.
And again, we shouldn't look at this as one step– we need to see there are 8 discrete, distinct steps happening here (when index is 0, when index is 1, when index is 2, … up until (and including) when index is 7).
The loop exits once index reaches a value of 8, because our loop determinant condition states as long as index is less than 8, continue to loop. Once index becomes 8, our looping condition has been satisfied, and the loop will no longer iterate.
The stepping (that third) field is a mathematical expression indicating how we wish for index to progress from its starting state (of being equal to 0) to satisfying the loop's iterating condition (no longer being less than 8).
index++ is a shortcut we can use in C; the longhand (and likely more familiar) equivalent is: index = index + 1
A while() loop isn't as specific about starting and stepping values, really only caring about what condition needs to be met in order to exit the loop (keep looping while this condition is true).
In actuality, anything we use a for loop for can be expressed as a while loop– we merely have to ensure we provide the necessary loop variables and progressions within the loop.
That same loop above, expressed as a while loop, could look like:
int index = 0; while (index < 8) { fprintf(stdout, "loop #%d ... %d\n", (index+1), (index*index)); index = index + 1; // I could have used "index++;" here }
The output of this code should be identical, even though we used a different loop to accomplish the task (try them both out and confirm!)
while() loops, like for() loops, will run 0 or more times; if the conditions enabling the loop to occur are not initially met, they will not run… if met, they will continue to iterate until their looping conditions are met.
It is possible to introduce a certain kind of logical error into your programs using loops– what is known as an “infinite loop”; this is basically where you erroneously provide incorrect conditions to the particular loop used, allowing it to start running, but never arriving at its conclusion, thereby iterating forever.
Another common logical error that loops will allow us to encounter will be the “off by one” error– where the conditions we pose to the loop are incorrect, and the loop runs one magnitude more or less than we had intended. Again, proper debugging of our code will resolve this situation.
The third commonly recognized looping structure in C, the do-while loop is identical to the while() (and therefore also the for()) loop, only it differs in where it checks the looping condition: where for() and while() are “top-driven” loops (ie the test for loop continuance occurs at the top of the loop, before running the code in the loop body), the do-while is a “bottom-driven” loop (ie the test for loop continuance occurs at the bottom of the loop).
The placement of this test determines the minimal number of times a loop can run.
In the case of the for()/while() loops, because the test is at the top- if the looping conditions are not met, the loop may not run at all. It is for this reason why these loops can run “0 or more times”
For the do-while loop, because the test occurs at the bottom, the body of the loop (one full iteration) is run before the test is encountered. So even if the conditions for looping are not met, a do-while will run “1 or more times”.
That may seem like a minor, and possibly annoying, difference, but in nuanced algorithm design, such distinctions can drastically change the layout of your code, potentially being the difference between beautifully elegant-looking solutions and those which appear slightly more hackish. They can BOTH be used to solve the same problems, it is merely the nature of how we choose express the solution that should make one more preferable over the other in any given moment.
I encourage you to intentionally try your hand at taking your completed programs and implementing other versions that utilize the other types of loops you haven't utilized. This way, you can get more familiar with how to structure your solutions and express them. You will find you tend to think in a certain way (from experience, we seem to get in the habit of thinking “top-driven”, and as we're unsure, we tend to exert far more of a need to control the situation, so we tend to want to use for loops for everything– but practicing the others will free your mind to craft more elegant and efficient solutions; but only if you take the time to play and explore these possibilities).
So, expressing that same program in the form of a do-while loop (note the changes from the while):
int index = 0; do { fprintf(stdout, "loop #%d ... %d\n", (index+1), (index*index)); index = index + 1; // again, we could just as easily use "index++;" here } while(index < 8);
In this case, the 0 or more vs. 1 or more minimal iterations wasn't important; the difference is purely syntactical.
With the do-while loop, we start the loop with a do statement.
Also, the do-while is the only one of our loops which NEEDS a terminating semi-colon (;).. please take note of this.
One of the intended uses of the Raspberry Pi and other small, single board computers is as an interface tool to peripherals in projects, personal and industrial.
The pi has a set of General Purpose Input Output (GPIO) pins intended for precisely this purpose:
Please note the orientation of the pi (ethernet/USB at bottom) to calibrate yourself to the location of pin 1 and all subsequent pins, along with their identified function (ie top left pin, pin 1, provides a constant 3.3v DC power)
We can also get a live update on the state of each pin on our pi itself, using the 'gpio readall' command at our pi prompt:
yourpi:~/src/desig/clr1$ gpio readall +-----+-----+---------+------+---+---Pi 4B--+---+------+---------+-----+-----+ | BCM | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | BCM | +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+ | | | 3.3v | | | 1 || 2 | | | 5v | | | | 2 | 8 | SDA.1 | ALT0 | 1 | 3 || 4 | | | 5v | | | | 3 | 9 | SCL.1 | ALT0 | 1 | 5 || 6 | | | 0v | | | | 4 | 7 | GPIO. 7 | IN | 0 | 7 || 8 | 1 | ALT5 | TxD | 15 | 14 | | | | 0v | | | 9 || 10 | 1 | ALT5 | RxD | 16 | 15 | | 17 | 0 | GPIO. 0 | IN | 0 | 11 || 12 | 0 | IN | GPIO. 1 | 1 | 18 | | 27 | 2 | GPIO. 2 | IN | 0 | 13 || 14 | | | 0v | | | | 22 | 3 | GPIO. 3 | IN | 0 | 15 || 16 | 0 | IN | GPIO. 4 | 4 | 23 | | | | 3.3v | | | 17 || 18 | 0 | IN | GPIO. 5 | 5 | 24 | | 10 | 12 | MOSI | IN | 0 | 19 || 20 | | | 0v | | | | 9 | 13 | MISO | IN | 0 | 21 || 22 | 0 | IN | GPIO. 6 | 6 | 25 | | 11 | 14 | SCLK | IN | 0 | 23 || 24 | 1 | IN | CE0 | 10 | 8 | | | | 0v | | | 25 || 26 | 1 | IN | CE1 | 11 | 7 | | 0 | 30 | SDA.0 | IN | 1 | 27 || 28 | 1 | IN | SCL.0 | 31 | 1 | | 5 | 21 | GPIO.21 | IN | 1 | 29 || 30 | | | 0v | | | | 6 | 22 | GPIO.22 | IN | 1 | 31 || 32 | 0 | IN | GPIO.26 | 26 | 12 | | 13 | 23 | GPIO.23 | IN | 0 | 33 || 34 | | | 0v | | | | 19 | 24 | GPIO.24 | IN | 0 | 35 || 36 | 0 | IN | GPIO.27 | 27 | 16 | | 26 | 25 | GPIO.25 | IN | 0 | 37 || 38 | 0 | IN | GPIO.28 | 28 | 20 | | | | 0v | | | 39 || 40 | 0 | IN | GPIO.29 | 29 | 21 | +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+ | BCM | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | BCM | +-----+-----+---------+------+---+---Pi 4B--+---+------+---------+-----+-----+
Yes, it may be packed with information, but it is an informative, technical reference.
Why is this so layered and directed, you may ask? Well, it is a matter of competing standards (approaches to identification of each pin, based on particular reference points).
For example, the “Physical” columns (dead center) in the center conform to the image diagram I posted above of the 40 pins (where pin 1 is the top-left).
The “BCM” columns (far left and far right) refer to the hardware identifications for each pin by the manufacturer. You might notice that BCM pin 17 (physical pin 11) corresponds to wPi pin 0.
And the “wPi” columns (second from left, and second from right), correspond to the pin identifications as use by the wiringPi library, which we are using here.
Additionally, we see “Mode” and “V” columns; mode informs us of the operating mode of that pin at present configuration (IN means it is configured for INPUT, OUT means it is configured for OUTPUT). V implies voltage on the pin (0 means no voltage, 1 means there is voltage present at time of checking). For this project, we will be configuring a specific pin to OUTPUT mode, and modulating it between a state of ON (1) and OFF (0).
So, in exploring the use of the table: if we wanted to hook a component up to wiringPi pin #0, that corresponds to manufacturer (BCM) pin 17, which is physical pin 11.
You will want to verify placement before supplying power, that is why we are taking things slow, and providing you opportunities to confirm (by posting pictures in the discord channel) before proceeding.
It may seem a bit bewildering or overwhelming at first, but like anything, time and exposure will ensure it becomes increasingly second nature.
I have prepared a grabit for resources related to this project. To obtain:
lab46:~/src/desig$ grabit desig clr2 make: Entering directory '/var/public/SEMESTER/desig/clr2' '/var/public/SEMESTER/desig/clr2/Makefile' -> '/home/user/src/desig/clr2/Makefile' '/var/public/SEMESTER/desig/clr2/clr2.c' -> '/home/user/src/desig/clr2/clr2.c' make: Leaving directory '/var/public/SEMESTER/desig/clr2' lab46:~/src/desig$
At which point you can change into the newly created and populated clr2/ directory.
Okay, you've snagged the project files on lab46. Now, how to get them to your pi?
The same way you've been juggling project files already, by using your mercurial repository!
Using the hg tool, be sure to add, commit, and push successfully on lab46.
Then, over on your pi, use hg to pull and update the new changes into place. Then you can proceed.
Please reference the FreeNove Tutorial, Chapter 5, Project 5.1 for the parts and circuit diagram.
The RGB LED circuit for this project remains identical to that of clr0 and clr1. New will be the buttons and switch circuits that are added.
You will want to add THREE button circuits, each connected to a unique GPIO. You can utilize the plastic button caps to help identify which button is which. Each button will be directly responsible for adjusting the current level of colour intensity (ie pressing the button intended to adjust the red component of the RGB LED will adjust the red value).
You will also want to utilize a switch, which looks like this:
Your kit should have come with two of them, you will need ONE.
The switch circuit will involve hooking up 3.3v to the center pin, putting a 1k resistor between the pin of the switch and the 3.3v source coming from the pi. And ONE of the other pins should then go through a 10k resistor into a GPIO pin on the pi (set to INPUT mode, just like the buttons).
This switch is going to control the DIRECTION of change- one way, it will be INCREASING in value. The other, it will be DECREASING.
So, your ultimate circuit will have FOUR independent inputs- red control, green control, and blue control (all buttons), and then the DIRECTION switch (increase/decrease). FOUR GPIOs in INPUT mode are needed.
Once all connected, your circuit should allow for directional, per-colour adjustment of each of the three colours. Here's an animated gif of one such circuit in action:
Your program to implement for this project involves using the wired up RGB LED, the three buttons, and the switch, to do the following:
Since the grabit brought in a Makefile, you can compile your code simply by typing: make
Any compiler errors will go into a text file called errors
To do a full cleaning, run: make clean then make (or make debug)
If you'd like to see compiler messages as you compile, run: make debug
When done and ready to submit, on lab46: make submit
In general, you will want your completed program to perform in the manner described as follows (in English-like pseudocode):
LOOP TO KEEP PROGRAM GOING IF SWITCH IS IN POSITION 1: DIR <- +10 ELSE DIR <- -10 END IF IF RED BUTTON IS PRESSED: REDCOLOR <- REDCOLOR + DIR END IF IF GREEN BUTTON IS PRESSED: GREENCOLOR <- GREENCOLOR + DIR END IF IF BLUE BUTTON IS PRESSED: BLUECOLOR <- BLUECOLOR + DIR END IF SETRED(REDCOLOR) SETGREEN(GREENCOLOR) SETBLUE(BLUECOLOR) DELAY END LOOP
To successfully complete this project, the following criteria must be met:
To submit this program to me using the submit tool, run the following command at your lab46 prompt:
lab46:~/src/desig/clr2$ submit desig clr2 clr2.c Submitting desig project "clr2": -> clr2.c(OK) SUCCESSFULLY SUBMITTED
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:
91:clr2:final tally of results (91/91) *:clr2:post picture of unpowered layout to #desig and get approval [13/13] *:clr2:post picture to #desig by Sunday before deadline [13/13] *:clr2:post picture of powered layout to #desig [13/13] *:clr2:grabit on the code on lab46 by Sunday before deadline [13/13] *:clr2:clr2.c code adequately modified per project requirements [26/26] *:clr2:updated code is pushed to lab46 repository [13/13]
Additionally: