By Joe Pardue View In Digital Edition
Our last episode finished the LCD Navigator Project Kit by learning how to use the navigator buttons with our hard-won knowledge of C bitwise operators. So now, we'll start to learn about C pointers, arrays, and structures. This is going to take a while — maybe three Workshops — and since it will be about the densest thing you’ll come across, we will lighten things up a bit by using some blinking LEDs for examples of how to use what we are learning. We will apply the hard stuff to storing data to be displayed on the Simple Chaser Lights Board. This board will let us build various sorts of chaser lights such as in a theater marquee like in Figure 1,
FIGURE 1. Attack of the Killer Chaser Lights.
or for Battlestar Glactica Cylons or Knight Rider Kitts as shown in Figures 4 and 5.
FIGURE 4. Cylon eyes.
FIGURE 5. Kitt from Knight Rider.
In the December Workshop (Part 3-41), we learned about C bitwise operations and we used that knowledge to make a simple chaser lights project using the BreadboArduino as shown in Figure 2. This is a great project for learning bitwise digital input and output operations since it reads the bit states from an eight-bit DIP switch and outputs eight-bit patterns on eight LEDs, thus providing tangible physical representations of bits and bytes used for input and output.
FIGURE 2. Chaser lights on BreadboArduino.
If you built the breadboard project shown in Figure 2, you probably had some problems getting it wired correctly — I sure did and it was my project! So, I decided to put the design (minus the USB board) on a printed circuit board so we can build it easily and have something we can knock around a bit and not worry about parts and wires falling off. Figure 3 shows the results.
FIGURE 3. Chaser lights board.
This kit is very easy to assemble, you can find details in the video at the end. As usual, this is a fully open source project, so the source code is available at https://github.com/nutsvolts/avrtoolbox and the schematic and layout are available here.
The main difference between this board and the circuit discussed in December’s Workshop is that this one does not have a communication channel for reprogramming. It comes with a bunch of chaser patterns built in, but if you want to reprogram it, you will have to remove the IC and reprogram it on an STK500 or Dragon, or some other socketed programmer with an ISP connection. You will find the simple_chaser_lights software in avrtoolbox under the avr_applications directory so you can get a head start on this visually arresting project before we look at the details in our next Workshop.
Before the dessert, eat your broccoli!
Let’s do some theory first. We will look at C variables and data types as a lead into introducing pointers. Next time, we will dig into the gory details of pointers and look at how the theory of variables, data types, and pointers applies to the code used for the Simple Chaser Lights kit.
What is a variable?
Back in elementary school, you probably learned that a variable is just a way to name some unknown number before knowing what it is. Like, if X is my variable name, it can represent any number. If we find that it has a value of 12, then X = 12. In computers, a variable is a name used to refer to a location in memory. That location in memory holds any number that can fit at that memory location. If the memory is eight-bit, then it can hold up to 256 unique values; if 16-bit, up to 65535 values; and so on.
According to the dictionary, a variable used as an adjective is something that is subject to variation or change. When used as a noun, it means a quantity that can assume any one of a set of values; it also means a symbol representing a variable. If X is an eight-bit variable, it is restricted to the set of all integers from 0 to 255. Now, notice that we have three features that characterize a variable:
In C, a variable is a name used to refer to a location in memory that holds a value that is ‘subject to variation or change’ within a set of values. The name aspect of the variable in C is simply an alias for an address in memory. The ‘set of valid values’ aspect is defined by the data type (more on data types in a moment). In C, data types are related to the way data is stored in memory. For instance, on the AVR a char data type is stored in an eight-bit memory location and an int data type is stored in a 16-bit memory location. There are many other data types, and we will discuss them elsewhere so as not to dilute the discussion of pointers. If you write the following C program:
int main()<br />
unsigned char x = 10;<br />
unsigned char y = 20;<br />
unsigned char z = 0;
z = x + y;
printf(“For z = x + y\n”);<br />
printf(“z = %d\n”,z);<br />
The result shown in a terminal program will be:
For z = x + y<br />
z = 30.
You have created three variables, each restricted to the data type unsigned char which is the set of integers that can be encoded in eight-bits — 0 to 255. When you perform the operation z = x + y, the value of x and y are added together, and z is set to the value of that sum. You can do this sort of thing all day long with pencil and paper, but computers do things their own way. The C compiler will allocate three SRAM memory locations for storing eight-bit data — one each for x, y, and z. Then, it will generate assembly code that will cause the computer to take the data at the memory location named x and add it to the data at the memory location named y, then put the results in the memory location named z.
The hardware involved in this operation varies among various computer families, but that isn’t important here. What is important is that we understand the dual nature of a variable. It has an address of a memory location and it has the data stored at that location. We see the data, but we don’t see the address. Instead of the actual address, we see a name for that address. C does this as a convenience for the programmer who sees x, y, and z and not 0x0342, 0x0145, and 0x0215 (which might be the addresses of those values). By now, you may be thinking that I’m way overdoing this explanation, but it is critical that you understand this before proceeding. So, get out a pencil and some paper, and answer the following questions:
The answers are at the end of the Workshop.
What are data types?
Data types are a system that C uses to declare variables of different kinds. Basic data types and methods are provided for building array and compound types. The original C types included things like char, int, unsigned char, unsigned int, float, etc. When you declare a variable with a type, the compiler knows how much memory to allocate for that type. For instance, in AVRs the char data type is stored in a single eight-bit memory location, while the int is stored in a 16-bit memory location. If we want to store eight bits of data, we can use char data type on an AVR, but if we port the code to some other device we can’t be certain that the char will be eight bits. However, other systems might use different sizes for storing these, and for this reason the header inttypes.h was added so that we can specify the bits used to store a particular data type. This is where we get things like uint8_t (unsigned eight-bit integer) that you’ll often see in the avrtoolbox code.
Unsigned? We can have unsigned char and unsigned int that are equivalent to uint8_t and uint16_t. Without ‘unsigned,’ the compiler considers the number to be signed which means it can have negative values. A char can hold numbers from -128 to 127, while an unsigned char holds numbers from 0 to 255. Either way, the data is stored in eight bits and its signed-ness is only used for computations using the data.
The important thing to remember about data types for this discussion is that they define how much memory storage will be needed for a particular type. Again, this is all system-dependent. However, for the AVR — since the unit of memory is an eight-bit byte — a char can be stored at a single memory location while an int requires two memory locations.
Typecasting. C has a cast operator that lets us change the type of a variable for a specific use. For instance, we may have defined a variable as an int but want to use it in an operation, as in char. For example, we might want to print all the ASCII characters and the number of each character:
for(int i = 0; i < 256; i++)<br />
printf(“%d = %c\n”,i,(char)i);<br />
Here we cast the int x used for the count to a char that we can print by using (char)x;
As before, the answers are at the end of the article.
In C, we have a pointer data type with a value that refers directly to (points to) another value stored elsewhere in memory. A pointer is said to reference that value, and using a pointer to get that value is called dereferencing. A pointer is like any other variable in that it has an address and that there is data stored at that address. However, it is different from other variables in that C knows that it is to be used to store the address of another variable. This means that at the memory address of a pointer, the memory address of another variable is stored. By dereferencing a pointer, C gets the address stored at the pointer’s address and uses that stored address to access the data stored at the address indicated. So, a variable has an address and data is stored at that address. A pointer has an address and the address of another variable is stored at that address.
FIGURE 6. Memory for pointer and pointee.
You might ask why on earth would you want to do this? One answer is that it provides a very efficient mechanism to access large blocks of data. Think for a moment about how a function works. When you call a function, you provide parameters that the function can work on, but what if you wanted to send the function all the byte data you logged in one-minute intervals over the past 24 hours? That is 60*24=1440 bytes. As you may recall, the parameters for a function are pushed on to the stack, so to send this data you would have to push 1,440 bytes and likely blow your stack. However, if you had those 1,440 bytes stored sequentially in memory, you would only need to send the function the address of the first byte and the number of bytes logged. For the AVR, an address is two bytes long and the number ‘1440’ requires two bytes, so only four bytes are pushed on the stack. The function can then take the pointer address and increment it 1,440 times to access each byte stored. If you are thinking, ‘Blocks of data ... is he talking about arrays?’ you are ahead of the game and later you’ll see how pointers are intimately related to arrays.
At this point, I hope you have at least some concept of what a pointer might be. Our next episode will really wring these out and see some program examples to help understand these concepts. And — lucky you — there is no pointer quiz at this point, but next time...
Figure 7 and Table 1 show the Simple Chaser Lights Kit parts and Bill of Materials. Figures 8 and 9 show the schematic and layout.
|Table 1. Simple Chaser Lights Bill of Materials.|
|4||1||Eight-bit DIP switch|
|5||8||5 mm Red LED|
|6||1||220 resistor network|
|7||1||10K resistor network|
|9||1||.01 µF capacitor|
|10||1||Battery box 2-AA|
FIGURE 7. Simple Chaser Lights Kit parts.
FIGURE 8. Simple Chaser Lights schematic.
FIGURE 9. Simple Chaser Lights layout.
Most folks will be able to figure out how to build this thing from the picture and parts list. Since I just noticed that it is the 21st century and that for very little money I could start doing videos for some of these concepts I am trying to get across, I decided to do a video demonstration on how to construct the Simple Chaser Lights Board Kit. You can skip to the end to watch it.
When you build this, one thing to remember is that the LED images on the PCB (and the LEDs themselves) have a flat side that faces the LED short leg that is the one you connect to ground. Don’t get them backwards since LEDs are diodes and current will flow in one direction. Another thing to note is that the VCC and GND pads do not have any sort of connection hardware. You solder the connections to the battery box’s red and black connections (VCC and GND, respectively).
Unfortunately, this economy comes at the cost of fragility. As you wiggle these wires around, they quickly fatigue and will break off. So, to prevent this add a glob of high temperature hot glue to the connection to seal the wires and their insulation to the PCB. This will serve for strain relief and help lessen the likelihood that you’ll bread the power connection. Yeah, it’s ugly — but cheap — so like many a date I’ve had, I just go with it.
How to Use the Simple Chaser Lights
Out of the box, the microcontroller is programmed to show 32 patterns (encoded on the leftmost five bits of the DIP) at four speeds (encoded on the next two bits), and you can invert all the patterns (encoded on the rightmost bit) as shown in Figure 10.
FIGURE 10. DIP switch.
The following list shows the results of various bit patterns set on the DIP switch:
DIP Switch Bit Positions for Patterns:
00000xxx (0): right_sweep1
00001xxx (1): right_sweep2
00010xxx (2): right_sweep3
00011xxx (3): right_sweep4
00100xxx (4): left_sweep1
00101xxx (5): left_sweep2
00110xxx (6): left_sweep3
00111xxx (7): left_sweep4
01000xxx (8): right_stack
01001xxx (9): left_stack
01010xxx (10): right_ant
01011xxx (11): left_ant
01100xxx (12): inout
01101xxx (13): blinkin
01110xxx (14): brownian
01111xxx (15): random
10000xxx (16): cylon1
10001xxx (17): cylon2
10010xxx (18): cylon3
10011xxx (19): cylon4
10100xxx (20): cylon_all
10101xxx (21): tick_tock_half
10110xxx (22): tick_tock_all
10111xxx (23): pov_smiley_micros
11000xxx (24): pov_nutsvolts
11001xxx (25): pov_help
11010xxx (26): pov_stop
11011xxx (27): pov_ I_love_you
11100xxx (28): pov_ taxi
11101xxx (29): pov_right_arrows
11110xxx (30): pov_left_arrows
11111xxx (31): show_all_chasers
DIP Switch Bit Positions for Speeds:
xxxxx00x (0) lowest speed
xxxxx01x (0) lower speed
xxxxx10x (0) higher speed
xxxxx11x (0) highest speed
DIP Switch Bit Positions for Polarity:
xxxxxxx0 (0) Normal
xxxxxxx1 (1) Inverted
You may notice that patterns 23 through 30 start with ‘pov_’ which stands for persistence of vision. When you set one of those patterns, the output will look pretty strange, unless you wave the board about — then they will look even stranger. We’ll reserve the details of that for another Workshop. Next time, we'll see how to extend the eight LEDs into a full-fledged chaser marquee style frame. NV
Theory is all well and good, but to really learn this stuff you have to get your hands on some tangible items that blink, whirr, and sometimes detonate. As a service for the readers of the Smiley’s Workshop articles, we have simple and inexpensive projects kits available that can help you make it real. You can find these kits (and some darn good books) at the Nuts & Volts Webstore.
Variables Quiz Answers:
• What are the three aspects of a variable?
It has a name, a value, and a data type.
• What is the name of a variable?
It is an alias of the address in memory where the value of the variable is stored.
• What is the value of a variable?
It is the data stored at the address in memory specified by the name.
• What is the data type of a variable?
It is the range of values that are valid for the variable data.
• Where does C keep a copy of the value of a variable?
It stores the value in SRAM memory on the AVR.
• How does C know where to find the value of a variable?
The variable name is an alias for the address in memory for the value.
Data Types Quiz:
• How many bits are used to store an int? That is entirely device-dependent, so we can’t be sure.
• What is the largest number that can be stored in an uint8_t variable? 127
• What is the smallest number that can be stored in a uint8_t variable? -128
• What is the largest number that can be stored in an int8_t variable? 255
• What is the smallest number that can be stored in an int8_t variable? 0
• How would I use a char as an unsigned int?
char myChar = ‘A’;<br />
unsigned int myInt = 0;
myInt = (unsigned int)myChar;
<a id="Assembly Video" name="Assembly Video"></a>