© James R. Strickland 2016

James R. Strickland, Junk Box Arduino, 10.1007/978-1-4842-1425-1_4

4. 8 Bit Ports

James R. Strickland

(1)Highlands Ranch, Colorado, USA

The Arduino LLC set out to accomplish some very specific goals with the Arduino, and one of these was to make programming Arduinos as simple as possible for beginners. Most hardware interactions involve digitalRead() and digitalWrite() or some variation on those two functions. These are useful functions, but they’re an abstraction—there’s that word again—from how the ATmega really communicates with the outside world.

So far as the ATmega1284P (or any other ATmega used in Arduinos that I’m aware of) knows, there is no such thing as pin 1 (in the Cestino’s case.) It knows that pin only as Port B Bit 0. The digitalWrite() function essentially tells the ATmega to turn on specific bit of a port, and subsequent digitalWrite commands to other bits in that port will switch them off and on, one at a time.

There’s another way. The ATmega can send whole bytes to ports with one command. This is important. It’s much faster and more efficient to control the port that way if you actually need all eight bits at once.

We’ll need all eight bits at once. Most of the ancient integrated circuits you’ll find in your junk box expect their communication in 8 bit parallel. In this chapter, we’ll get to know the ATmega1284’s 8 bit ports, and how to use them in sketches. We’ll do it with a simple, classic project: the Larson (memorial) Scanner.

The Stuff You Need

This chapter is mostly about theory and code, but there are two miniprojects, both of which need the same parts.

New Parts

  • 8 330Ω resistors . These are current limiting resistors to keep the LEDs from getting fried, and the ATmega1284P from having its outputs overloaded. 1/4 watt resistors are fine, as always.

New or Used Parts

  • 8 LEDs , preferably the same size, shape, and color or 1 LED bar graph (any orientation) with at least 8 LEDs in it. Common cathode or common anode bargraph LEDs will work, but the wiring will be different. Most common LEDs will work fine with 330Ω resistors at five volts, and won’t draw so much current that the ATmega can’t source it. If yours are unusually large or bright, you may need to adjust the resistor values.

A Little Binary With That

The Arduino foundation hid the 8 bit ports for one good reason: in order to use them, you really need to have a good understanding of binary. So that we’re all on the same page with binary, here’s a quick refresher.

Counting in Binary

Computers are a human artifact. We made them to do things we do in ways we understand. Like us, computers store numbers in dual-state systems. They have logic gates that can be either on or off. We have fingers that can be either up or down. Seems pretty obvious, right?

Well, yes and no. If you count on your fingers, zero through nine, carry the one for ten, you’re counting in base ten numbers. Like computers, the base 10 number system is a human artifact. It is how it is because we have ten fingers. Multiplying and dividing are easy because you just move the decimal point one space to the left or the right. It’s all very convenient with our ten fingered hands.

It’s not very efficient, though. Fingers are all used individually, as symbols. There are huge numbers of permutations of fingers that simply aren’t used. A thumb and finger is two, but a thumb, finger, and pinkie have no meaning. And so on.

Logic gates are expensive. Okay, they’re beyond cheap today, but in 1937 when Dr. George Stibitz ( https://en.wikipedia.org/wiki/George_Stibitz ) was building an adding machine, his logic gates (relays) were expensive, and he knew that using the base 2 (binary) number system would use all the permutations of his relays. It also meant the thing would fit in his kitchen, where he was building it. By the way, he’s also the one who coined the term “digital” to describe electronic computing.

So. A single gate has two possible states, on or off, binary 1 or zero. If a computer wants to count beyond one, it has to add another Binary digIT. Now the computer can count from zero to three, representing a total of four values. Need to go higher? Add another bit. Now we can go from zero to seven, for a total of eight values. Continue adding bits until you can compute the size numbers you have in mind.

Remember how I said that the ATmega1284P is an 8 bit computer? That means it is designed to handle up to eight bits at a time, values from 0 to 255. More on that shortly.

Here’s a party trick you can do to set it in your mind. Bet your friends you can count to 31 on one hand, assuming that hand has all five fingers. Your thumb is the rightmost bit. (This is easier with your left hand, for those so equipped. To do it with your right hand, rotate your hand so your thumb is pointed to the right.) Your thumb is the ones bit, the least significant bit. Zero is no fingers. Unless I tell you to raise a finger, keep it curled in the off position. Raise your thumb. That’s one. Add one, carry to the left, which would be your index finger, and put your thumb back down. That’s two. Three is thumb and index finger raised. Four is an obscene gesture: second finger raised, thumb and index finger lowered. Five is middle finger and thumb raised. Six is either a peace sign or another obscene gesture, depending on where you are: second finger and index finger raised. Seven is middle finger, index finger and thumb. Eight is hard to do. You have to raise your ring finger without raising any others. (Did you know that the middle of your middle finger and your ring and little fingers are connected to a different nerve from the thumb, index finger, and the other half of your middle finger? Now you do.)

Eight through fifteen are the same as zero through seven plus the eight finger. Your pinky is 16, the most significant bit. Seventeen is hang loose or call me maybe. Thirty-one is all five fingers raised. Go ahead and collect your bets.

If you’ve the patience, coordination, and flexibility to use both hands and all ten fingers, you can count to 1,023, but it hurts a lot, and your friends will probably wander off long before you’re finished. If it were possible to use your toes this way, for a total of twenty bits, you could count to 1,048,576. IPv6, the new emerging standard for Internet addresses, has 128 bits of address space. Theoretically, this is enough to address a bit less than half of the atoms in the observable universe.

Bytes and Words

Okay. That’s bits, and how to use them. Let’s talk about bytes. I’ve thrown the word around, and you can’t get away from it in modern computing. Most people know that there are eight bits in a byte.

It wasn’t always so.

Back in the stone age of computing, 1956, to be exact, the term byte was coined because a particular IBM computer could use arbitrary numbers of bits to represent a given number, and the number of bits was called the byte length. It was a play on bite, but one that wouldn’t easily be typoed into /bit/ by manual writers. Later, minicomputers shipped with eight bit bytes, and twelve bit bytes, and sixteen bit bytes. AT&T standardized on eight bit bytes for data transmission, and the standard was cast in stone with the rise of eight bit microcomputers. Today a byte is eight bits, 0 through 7, as in Figure 4-1. No more, no less.

A340964_1_En_4_Fig1_HTML.jpg
Figure 4-1. Byte Diagram

I know, I know. What about 64 bit computers? What do you call a number like that?

If there are more than eight bits in a single ordered set of bits, it’s called a word, in modern parlance. Although this may seem like historical trivia, we are dealing with some very, very old components, and in old datasheets you sometimes see references to 16 bit bytes, or 8 bit words. Now you know how all these go together. Oh, and half a byte? Four bits? Is a nybble.

Bytes are usually represented with the least significant bit on the right, and the most significant bit on the left, just like base 10 mathematics. Ports on a given IC are under no obligation to follow this tradition. Remember that port A on the ATmega1284P has the least significant bit (the ones bit) on pin 40 and the most significant bit (the 128 bit) on pin 33. When we get to the Z80, you’ll see that some bits for a given port aren’t even on adjacent pins.

Bit Twiddling

Understanding binary and binary math gives us the same kind of control over each pin of the ATmega1284P as digitalRead() and digitalWrite() do. Even though we throw whole bytes at a port, we can twiddle the individual bits until the pins are how we want them.

A bit’s value is either zero or one, so it’s possible to do bitwise logic (boolean logic) on binary numbers. This means along with adding, subtracting, multiplying, and dividing, you can AND two numbers together, OR them together, or XOR (exclusive-or) them together. You can NOT bits. Finally, you can shift bits left and right in the word.

Wait, what?

AND

Let’s start with AND.

If you’ve done any Arduino programming (or any programming at all) you’re already familiar with AND. If condition a and condition b are true, then the AND is true. Otherwise it’s false. Bitwise ANDing is based on the same general concept.

Note

Don’t confuse the logical operators: &&, || and ! (AND, OR, and NOT) with bitwise operators &, |, ^, and ∼. (Bitwise AND, OR, XOR, and NOT, respectively.) It’s easy to do because in logical terms, they do the same thing. The key difference is that bitwise operators act on binary data types, whereas logical operators evaluate conditions that may involve multiple variables. We’re dealing exclusively with bitwise operators here.

00000001 & 00000011 = 00000001 If we AND 1 and 3, I get 1. Only the digits where both bits in both operands are set to one are evaluated as one. Any others are evaluated as zero. The & is the bitwise AND operator, just as + is an addition operator and * is a multiplication operator.

If you want to see the Cestino actually do this, the code looks like this:

void setup() {
  //All code is in setup because we want to run it only once.
  Serial.begin (115200); //Set up serial com at 115200 Baud.
  byte mybyte; //Declare the variable mybyte of the type byte.


  mybyte = 0x00000001 & 0x00000011;
  //Assign mybyte to the bitwise AND of 0x00000001 and B00000011, which are //1 and 3, respectively.
  Serial.print(mybyte, BIN);
  //Serial.print the result, formatted as binary. Note that leading 0s //will not be displayed.
  Serial.println(); //Print a linefeed.
}
void loop() {
  if (1 == 1) {}; //Do nothing, over and over, really fast.
}

The Cestino will return 1. Note that this is binary 1, equivalent to 0x00000001, but all the leading zeros are chopped off.

OR

OR is the opposite of AND. OR returns a 1 for any bit that is set to 1 in either byte. If the same bit is 1 in both bytes, OR will return a 1 for that bit as well. The OR operator is |, or the pipe character (not an exclamation point.) Unix and DOS users will be well acquainted with this character. Others may not be. On my U.S. Mac keyboard, it’s a shifted backslash, all the way on the right above the return key, but there’s no hard and fast standard where it will be on any given keyboard. Here’s an example.

void setup() {  //All code is in setup so it runs only once.
  Serial.begin (115200); //Set up serial com at 115200 Baud.
  byte mybyte; //Declare the variable mybyte of the type byte.


  mybyte = 1; //Set mybyte to 1. Note that 1 is equal to 0b00000001.
  mybyte = mybyte | 0b00000011;
  //Set mybyte to the OR of itself and 3.
  Serial.print(mybyte, BIN);
  //Serial print mybyte in binary format.
  Serial.println(); //Print a linefeed.
}
void loop() {
  if (1 == 1) {}; //Do nothing, over and over, really fast.
}

The Cestino will return 11 (3 in binary.)

XOR

XOR (the ^ operator) does the same job as OR except that if /both/ bits are 1 at a given position, the return value gets a zero. Only bits that are different between the two operands get a 1 in the result.

void setup() {
  //All this code is in setup so it runs only once.
  Serial.begin (115200); //Set up serial com at 115200 Baud.
  byte mybyte; //Declare the variable mybyte of the type byte.


  mybyte = 0b00000001; //Set mybyte to 1.
  mybyte = mybyte ^ 0b00000011;
  //Set mybyte to the XOR of itself and 3.
  Serial.print(mybyte, BIN);
  //Serial print mybyte in binary format.
  Serial.println(); //Print a linefeed.
}
void loop() {
  if (1 == 1) {}; //Do nothing, over and over, really fast.
}

The Cestino will return 10. Only bit 2 will be set.

NOT

NOT is different. It takes only one operand, and inverts all the bits. Any bit that is zero is returned as one, and any bit that is one is returned as zero. So 0b00000001 becomes 0b11111110 when NOT is applied to it.

The NOT operator is a tilde. This: ∼. On my Mac keyboard, it’s on a key all the way at the top left above the tab key with a lot of other obscure, seldom used punctuation. It’s the shifted version of that. Here's some demo code:

void setup() { //All code is in setup so it runs only once.
  Serial.begin (115200); //Set up serial com at 115200 Baud.
  byte mybyte; //Declare the variable mybyte of the type byte.


  mybyte = 0b00000001; //Set mybyte to 1.
  mybyte = ∼mybyte; //Set mybyte to the NOT of itself.
  Serial.print(mybyte, BIN);
  //Serial print mybyte in binary format.
  Serial.println(); //Print a linefeed.
}


void loop() {
  if (1 == 1) {}; //Do nothing, over and over, really fast.
}

The Cestino will return 11111110, the inverse or negation of 00000001. This is 254 in decimal. Each bit of the original value 00000001 has been reversed.

Bit Shift Operators

The weirdest operators, conceptually, are the bitshift left and right. This is done with the less-than character, twice. Like this: <<.

A byte, you will recall, has eight bits, or binary digits. In Arduino code, we’d picture it like this: 0b00000001.

If we reassign it with mybyte=mybyte << 2; we shift the entire byte two steps to the left, which gives us a value of 0b00000100, which is 4. Bit shifting to the left is effectively multiplying by 2. Let’s look at some demo code.

void setup() { //All code is in setup so it runs only once.
  Serial.begin (115200); //Set up serial com at 115200 Baud.
  byte mybyte; //Declare the variable mybyte of the type byte.


  mybyte = 0b00000001; //Set mybyte to 1.
  mybyte = mybyte << 1; //Bitshift mybyte one step to the left.
  Serial.print(mybyte, BIN);
  //Serial print mybyte in binary format.
  Serial.println(); //Print a linefeed.

The result of this sketch fragment (note that it’s missing the entire loop() statement and is continued below) will be that the Arduino prints 10, which is two in decimal. Why? Because 00000001 (decimal one) shifted left one digit is 00000010 (decimal two.)

If we shift six more steps to the left, we set the most significant bit in our single byte to 1. Looking at the byte once again, 00000010 shifted six more steps to the left gives us 10000000. Here’s more code. It would go in right under the code we’ve just looked at.

  mybyte = mybyte << 6;
  //Bitshift mybyte six more steps to the left.
  Serial.print(mybyte, BIN);
  //Serial print mybyte in binary format. We should get 10000000, //equal to decimal 128.
  Serial.println(); //Print a linefeed.

Right shifts work the same way, in the other direction. If you guessed that the greater-than sign twice is how you get a right-shift, you guessed right. It looks like this: >>, and is a shifted period on my Mac keyboard. Right shifting is the equivalent of dividing by two. If we take our existing byte, 10000000 (decimal 128) and shift it a step to the right, we get 01000000 (decimal 64.) Here’s some more code. Again, it’s not a complete sketch without the code above and below.

  mybyte = mybyte >> 1;
  //Bitshift mybyte one step to the right.
  Serial.print(mybyte, BIN);
  //Serial print mybyte in binary format. We should get 1000000, equal to decimal 64.
  Serial.println(); //Print a linefeed.

You might wonder what happens to the bits that get shifted off the edge of the byte in either direction. The answer is they cease to exist. They aren’t stored anywhere. Shifting the other direction will not retrieve them. Once they’re gone, they’re gone. But let’s see. These lines do exactly that.

  mybyte = mybyte >> 7;                                                              
  //Bitshift mybyte 7 more steps to the right, off the edge of the byte.
  Serial.print(mybyte, BIN);
  //Serial print mybyte in binary format. We should get 0. We shifted the //set bit all the way out of the byte.
  Serial.println(); //Print a linefeed.


  mybyte=mybyte <<1;
  //Shift mbyte to the left once to see if that bit is really gone.
  Serial.print(mybyte,BIN);
  //Serial print the result. We should still get 0.
  Serial.println(); //Print a linefeed.
}

Yup. Shifting seven digits to the right gives us a zero. The 1 goes off the edge of the byte, never to return. Shifting it left again shows that. The result is still zero.

And, because this is the end of the demo sketch, we’ll put the usual boilerplate at the end.

void loop() {
  if (1 == 1) {}; //Do nothing, over and over, really fast.
}

Gotchas

There are some gotchas for bit twiddling in C++, which is what we’re doing. Some of them are unique to the Arduino environment, some are just functions of binary math and how large numbers are stored.

Multibyte Data Types

Most Arduino data types aren’t single bytes. The vast majority are multibyte words. Shifting them may not give you the value you expect, particularly if you assume as we did in the sample code earlier, that shifting a bit 8 steps to the left shifts it out of the byte altogether. On a 16 bit unsigned int, shifting 1 eight steps to the left gives you 256, or 0000000100000000.

void setup() { //All code is in setup so it runs only once.
  Serial.begin (115200); //Set up serial com at 115200 Baud.
  unsigned int myint;
  //Declare an unsigned integer - two bytes.


  myint = 1; //set it to 1.
  Serial.print(myint << 8, DEC);
  //Print it shifted 8 digits to the left.
  Serial.println(); //Print a linefeed.
}
void loop() {
  if (1 == 1) {}; //Do nothing, over and over, really fast.
}

If you run this, you’ll get decimal 256. In a single byte, this would be a value you could never see. It takes 9 bits to represent it. Because Arduino ints are two bytes long, there it is.

Signed Data Types

Many variable types in Arduino and C++ are signed. This means that the most significant bit of the variable is used to determine whether the value is positive or negative. These are trickier than they look. An int, in Arduino, is a 16 bit signed variable. So int c = 1; stores a variable in memory that looks like this: 0000000000000001

So far so good. 255 is encoded as 0000000011111111, 256 is encoded 0000000100000000, and so on right up until you get to 32767, which looks like this: 0111111111111111. Add one to that, and a strange thing happens. The last bit gets set. Because this is a signed integer, the default type of integer, the last bit has a special meaning. If it is set, it means that the number is negative. But if you add 1 to 32767 in a two-byte signed integer, it will set that bit anyway. A number suddenly going negative means you’ve overflowed the datatype.

So 1111111111111111 is -32768. Add one to that, and you get 1111111111111110, or -32767. This is a function of twos-compliment math, which is a lengthy topic worth reading up on. Fortunately we don’t need to bit twiddle the data we get from a device, so it doesn’t come up often.

Demo code? Sure.

void setup() {
  //All code is in setup so it runs only once.
  Serial.begin (115200); //Set up serial com at 115200 Baud.
  int mysignedint; //declare a signed integer.


  mysignedint = 32767; //set to max value for a signed int.
  mysignedint = mysignedint + 1; //add one.
  Serial.print(mysignedint, DEC); //It's now negative.
  Serial.println();
  mysignedint = mysignedint + 1; //add one more.
  Serial.print(mysignedint, DEC); //it's now a lower negative.
  Serial.println();
}
void loop() {
  if (1 == 1) {}; //Do nothing, over and over, really fast.
}
Endian-ness

There is one more gotcha, and unlike the others, it can bite us even if we haven’t touched the bytes coming out of a device.

If we have a 16 bit word, especially if we’re passing it through an 8 bit computer, which byte is the higher one? If you picture the bytes all in a line, it makes sense that the least significant bit is the furthest right, and the most significant bit is the furthest left, but when we move them around in the ATmega1284P, they’re stacked on top of each other in 8 bit columns. How do you know which one is bits 16 through 8 and which one is bits 7 through 0? Welcome to the concept of endian-ness. The word itself comes from Jonathan Swift in Gulliver’s Travels, where there is a hundred years’ war over between the little endians—people who break their soft-boiled eggs open on the small end—and the big endians—people who break them open on the big end. So it is in computer science.

It comes down to this: when two or more bytes of a given number (word) are stored in 8 bit memory at sequential addresses, if the lowest address is the most significant byte, your machine is big-endian. If the lowest address is the least significant byte, your machine is little endian. The Intel x86 architecture has always been little endian, and so are both the ATmega1284P and the Arduino GCC compiler. Trivia? You might think so, but I guarantee you it will come up again. There’s no easy way to demonstrate this without more hardware, but I’ll point it out when it comes up in the projects to come.

Serial.print() Weirdness

If you find yourself messing with negative integers and other signed, multibyte data types, be aware that Serial.print(int,BIN) (or many other formats) will give you results that are simply wrong. When you call Serial.print, it type casts your variable (changes the type of your variable) into a 4 byte long variable type. For positive values, this is transparent, but negative values coming from signed variables shorter than 4 bytes the sign bit gets absorbed where it shouldn’t be, and the value is corrupted.

In order to get the values you expect, you have to cast the variable you’re putting into Serial.print() into the unsigned version of the data type. Fortunately, casts in C and C++ are easy. Just put the data type you want to cast your existing variable into in parenthesis in front of the variable.

For an int, you’d do it like this: Serial.print((unsigned int) mysignedint,BIN);

Binary Notation in Arduino

The Arduino core lets you use nonstandard notation for binary values, like this: B00000001. It’s very handy. Unfortunately, it’s limited to single byte values, and will produce values that are simply wrong (without warning) if you try to use it for longer values.

The somewhat more standard notation is 0b00000001. It’s a GCC extension, and the Arduino app uses GCC for its compiler. Rumor has it that this notation will be part of the next C++ standard, real soon now. So that touching multibyte values with binary values doesn’t bite us really hard, I’ll use the GCC extension to C++ notation: 0b00000001.

The Cestino’s Ports and How to Use Them

The ATmega1284P at the heart of the Cestino has four 8 bit ports : A, B, C, and D. See the modified pinout diagram in Figure 4-2.

A340964_1_En_4_Fig2_HTML.jpg
Figure 4-2. Cestino Ports

Don’t Step on the Ports

Each port on the ATmega1284P , in addition to providing General Purpose IO (GPIO) , has special functions, which you can see on Figure 4-2.

Port A is where the analog pins live.

Note

Port A is backwards from how you’d expect. Its bit 0 is on pin 40 of the IC and its bit 7 is on pin 33 of the IC. The analog pins are numbered the same direction, with Analog 1 on pin 40 and Analog 8 on pin 33. On the Cestino, only digital pins as used in digitalRead() and digitalWrite() follow the package pin numbers.

Port B is where the SPI connections live. If you’re using SPI in a project, these pins have to be protected when they’re going to be used.

Port C has the I2C connections. We're not using them, so we can use this port with impunity.

Port D has two different USARTs, one of which we’re using for RS232 communication. This matters if we’re trying to use a port that has another job. It can be done, but we have to watch out for the bits (pins) that are in use.

Port Registers and Commands

There are three special registers in the ATmega1284P for each port.

That’s great. What’s a register?

A register is, from the sketch’s point of view, just another variable, although you don’t have to declare it. The fundamental difference is that it’s a variable that the ATmega1284P will automatically look at or deposit things in. Registers really go deeper than this. They’re not handled with the same mechanisms as normal memory. They’re little windows into the microcontroller, wherein you can change what is being done, or read what is being done. Registers can be read-only, write-only, or both, and they are fundamental to the architecture of the microcontroller or microprocessor.

The port registers are exposed by the Arduino core as DDRx, PORTx and PINx, where x is one of the Cestino’s ports.

Note

Your Arduino has ports also, but they may be different and have different names from the Cestino’s. A common port environment is one of the big reasons to build the Cestino for the projects in this book.

We’ll use port C (DDRC, PORTC, and PINC) for all of our discussions here, but DDRA, PORTA, PINA DDRB, PORTB, PINB, DDRD, PORTD, and PIND do the same things for the other ports. The ports are controlled, read, and written to by setting and reading these registers. You know how we spent all those pages in this chapter on binary math? This is why.

DDRx Register

The DDRx register—DDRC , for example—is the Data Direction Register. It does exactly the same thing as pinMode(pin number). It determines whether the pins in the port are inputs or outputs. You set these to a binary value, like this:

DDRC=0b11100111;

This command would set the first three bits and the last three bits of port C as outputs, leaving the middle two pins as inputs. You could do the same thing this way:

DDRC=231;

PINx Register

The PINx register (PINC, for our examples) is the input register for a given port, that returns what values are being read by the inputs. PINx is read-only. When you read the PINx register, any pins on the physical port which are held high will result in that bit of the register being a 1. Pins held low will return bits set to 0.

If, as I did when I was testing the Cestino firmware, you have a switch set up to connect the pins of Port C to the + bus or the - bus depending on position, here’s what you’d do to read it.

First, set up the port.

DDRC=0b00000000; //Set all pins of port C to read mode.

Then read it and send the output to the serial console.

  Serial.print(PINC,BIN);
//print the value of PINC in binary format to the console.

Again, we treat the PINC register just like an 8 bit variable type in C++, because that’s how it’s exposed by the Arduino core. The difference is that we don’t declare it, and that we can’t change the value. It’s set by the ATmega1284P and the Arduino core software.

PORTx Register

The PORTx register is the output register for a given port. To use it, set it equal to a given 8 bit value , such as a byte variable type, a decimal number less than 256, or a binary of eight bits, no more, no less. When the PORTx register is set, the corresponding bits on the physical port will go high for 1s and low for 0s, and they’ll do it all at once, rather than one at a time as they would using digitalWrite() .

Like this:

DDRC=B11111111; //Set all pins of port C to output (write) mode.
PORTC=B10101010; //Turn on alternating pins on port C.

Pretty straightforward, right? Well, yes, as long as you want to control all the pins of that port at once. With ports A, B, and C on the Cestino, we can do that. Port D is another matter.

In our case, we’re using pins 0 and 1 of port D for RS232 communications between the Cestino and the Arduino application. If we don’t care whether the Cestino can communicate while our sketch is running, we can ignore the RS232 pins and use them as we please, with the caveat that the RS232 electronics are still connected to them and will affect their values electronically. If we do want the Cestino to be able to communicate with the Arduino application while the sketch is running, we need to protect those two pins from getting clobbered. This is where all that binary we talked about comes in.

Let’s assume we want to use all the pins except 0 and 1 on port D. We have to set it up correctly first.

Note

Know your port pin order! Binary is always numbered right to left, both on paper and in binary formatted numbers in Arduino sketches. Ports A, B and D, by contrast, have their lowest significant bit on the left with pins 1 and 20 facing you. In this orientation, only port C will line up with the binary notation and bit shift directions. This fact can produce confusing results if you (or your author) lose sight of it.

DDRD=DDRD | 0b11111100;
//Change all the pins except 0 and 1 to output mode.

This sets all the pins except 0 and 1 of port D as outputs. If you OR 0 with the input/output value of pins 0 and 1, you'll recall from our discussion of binary, it leaves those pins with whatever value they had before, so RS232 should go on working.

Likewise, when we set PORTC to some value, we need to OR that value with the current output settings of port C.

PORTD=PORTD | B11111100;
//Set all the pins high except 0 and 1.
Serial.println("Zortwootle");
//Prove that console communications still work.

Remember: OR returns a 0 only if neither byte being evaluated has a 1 in that position, and a 1 for a given position otherwise, so ORing PORTD with a byte that has bits 0 and 1 set to 0 will return whatever value was already in PORTD.

Note

It might look like you could read the input state of the port with the PORTx register. You can’t. That’s what PINx is for. PORTx will only tell you what output pins have been turned on.

Pullup and Pulldown Resistors

There’s one more wrinkle with the PORTx register.

You may recall from Chapter 2 where we put a pull-up resistor on the reset circuit of the Cestino to ensure that, unless the reset switch is pressed or a reset pulse comes in from the RS232 port, the Cestino does NOT reset.

Pull-up resistors are very, very common in digital electronics. So common, in fact, that the ATmega1284P has them internally on all the ports. By default, they’re turned off, so we haven’t dealt with them until now.

Pull-up resistors are only useful on input pins, so the way they are controlled is to write to pins in a port that are set as inputs. Here’s some sample code:

DDRC=0b00000000; //Set all pins of port C to read mode.
PORTC=0b11111111; //Turn on all pull-up resistors in port C.
PORTC=0b00000000; //Turn off all pull-up resistors in port C.

Just as we have to be careful not to change the input/output settings of pins that are doing other jobs for us (like pin 0 and 1 of port D) it’s important not to write to these pins at all, lest we change the pull-up resistor states for those pins.

For reference, the ATmega1284P’s internal pull-up resistors are between 20kΩ and 50kΩ (20,000 and 50,000 ohms.) You don’t have to conduct much current at all to pull an input pin low with its pull-up resistors set. There are no corresponding internal pull-down resistors. If you need pull-downs in your circuit, you’ll have to add them yourself.

Build the Larson (memorial) Scanner

The Larson (memorial) scanner is a project that comes straight from television. In the late 1970s and early 1980s (my youth) TV executive Glen A. Larson produced two science fiction TV series: Battlestar Galactica and Knight Rider. Although they were wildly different, both of them featured sentient robots with a single red light for an eye that scanned back and forth. (In the day, we used to joke that this was why Cylons couldn’t hit anything when shooting—no depth perception and their eye kept moving back and forth.) The Larson Scanner, which I’m calling the Larson (memorial) scanner, is homage to those shows, and to the man himself, now that he’s gone. It’s also a classic Arduino project. We’re going to bend it here, slightly, to get some familiarity with port manipulation.

The Circuit

The Larson (memorial) Scanner is a fairly simple animal.

If the schematic in Figure 4-3 looks suspiciously like I've added the pin 1 LED circuit over and over again on port C with a couple variations, you’ve got a good eye. It's exactly that. Although I’m using an LED bar-graph display , internally it is ten separate LEDs, each with its own anode and cathode, of which I’m wiring up eight. Each LED gets its own dropping resistor between the ground (-) bus and the cathode, and each anode connects to a pin on port C. You know how to calculate dropping resistors and current already. Do make sure that the Cestino can drive all 8 LEDs at once without overloading.

A340964_1_En_4_Fig3_HTML.jpg
Figure 4-3. The Larson (memorial) Scanner Schematic

All wired up? Does it look something like Figure 4-4? Good.

A340964_1_En_4_Fig4_HTML.jpg
Figure 4-4. Larson (memorial) Scanner

If you have a bar graph larger than 10 segments, and it just doesn’t seem to have enough pins, it’s probably set up for multiplexing. Instead of using one data line for each segment, as we have in Figure 1-3, they have (usually) four data lines and half a dozen or more common cathodes or anodes (usually cathodes). You switch a cathode to ground, display your four pins, then raise that cathode and switch the next one to ground, display those four pins, and so on. If the driving electronics (Your Cestino and a sketch, perhaps) hustle, all the segments can appear lit at the same time, even though only a maximum of four really are. This trades speed (which the LEDs and our eyes can’t use anyway) for current and data lines (port connections), and we’ll do some of it when we get to chapter 10. For now, I recommend putting that bar graph back in the junk box and using a smaller one, or separate LEDs.

The Code

Here’s where we get to use that port manipulation and binary math stuff we’ve been talking about. The traditional way to do the Larson Scanner with Arduino is to count from 1 to 8, use digitalWrite() to set a pin on, and another digitalWrite to turn the previous pin off, then change direction and go the other way. We can do it a whole lot easier.

//Larson Memorial Scanner (Port C)
// --------------------------------------------------------
//This sketch turns on a sequence of LEDs, one at a time,
//from the least significant bit to the most significant bit,
//then back again using bit shifting.
// ---------------------------------------------------------
//Hardware:
//LEDs wired with their anodes to port C's pins, and dropping
//resistors connecting their cathodes to the ground (-) bus.
// ----------------------------------------------------------
// James R. Strickland
// ----------------------------------------------------------


//precompiler definitions.
#define DELAYTIME 50 //How many mS (milliseconds) between LEDs?


//declaration of variables.
bool reverse; //Declare our reverse-direction flag


//setup() function - runs only once.
void setup() {
  DDRC = 0b11111111; //Set DDRC to all outputs.
  PORTC = 0b00000001; //Set PORTC to 1. This will turn on pin 22.
  //PORTC can be initalized to any bit you want.


  reverse=(PORTC == 1); //Sanity checking for reverse.
  //If PORTC==1, reverse is set true. Otherwise the
  //boundary logic below barfs.
}


//loop() function - runs forever.
void loop() {
  //Here's that boundary logic. Whatever reverse is, flip it
  //if PORTC is 128 or 1.
  if  ((PORTC == 128) || (PORTC == 1)) reverse = !reverse;


  delay(DELAYTIME); //The Cestino can change the port value
//thousands of times a second. Our eyes and the LEDs, not so
//much. 50mS seems to give a nice, quick scan back and forth.


//Here's where we actually set the PORTC values.
  if (reverse) { //shift right
    PORTC = PORTC >> 1;
  }
  else { //shift left
    PORTC = PORTC << 1;
  }
}

How It Works

This sketch works by shifting a single “on” or true (1) bit left or right. When the value in PORTC reaches either 1 or 128, the least or most significant bit in the port, we change the shifting direction until we hit one of those two boundaries again. Repeat forever.

The direction change logic is the most complicated. In a single if statement, I check to see if bits 128 or 1 are set, and if so, I invert the value of reverse, whatever it is. If it starts out true, we were shifting right, and we’ve just hit 1, so we need to shift left. If it’s false, we were already shifting left and we need to shift right.

The remaining pair of if statements carry the shift out, depending on whether the reverse flag is set, and there’s a delay to make it smooth.

So that’s the Larson (memorial) Scanner, done with bit shifting. You could do it with loops and setting the port value directly, but if we’re going to throw values at the port anyway, let’s do something interesting.

Binary Numbers on Display

Have you ever seen a binary clock? I kid you not, they’re a clock that has bit fields for hours, minutes, and seconds, and the bits rotate once a second. (Thankfully they don’t usually display Unix epoch time, a 4 byte, unsigned integer of seconds since midnight, January 1, 1970.) We don’t have enough bits in the Larson (memorial) scanner to display the full time, let alone epoch time, but we can count seconds for a while.

There aren’t any changes to the hardware. We have a port wired to LEDs, and the Cestino can drive them all simultaneously. (You did calculate your dropping resistors so it could, right?) That’s all we need. All that changes is the sketch.

//Binary Numbers On Display
//----------------------------------------------------------
//This sketch counts seconds in binary from 0 to 255, then
//resets. It's a really, really short sketch.
// ---------------------------------------------------------
//Hardware:
//LEDs wired with their anodes to port C's pins, and dropping
//resistors connecting their cathodes to the ground (-) bus.
// ----------------------------------------------------------
// James R. Strickland
// ----------------------------------------------------------


//precompiler definitions.
#define DELAYTIME 10 //How many mS (milliseconds) per update?


//setup() function - runs only once.
void setup() {
  DDRC = 0b11111111; //Set DDRC to all outputs.
  PORTC=0;
}


//loop() function - runs forever.
void loop() {
  PORTC++; //Take whatever is in PORTC and add 1.
  delay(DELAYTIME);  //Wait DELAYTIME milliseconds.
}

How it works: As before, setup() configures DDRC as all outputs. Then it sets PORTC to 0. The loop() function increments whatever value is in PORTC by 1, waits DELAYTIME milliseconds (You can set it shorter than 1000 if you’re not patient), and repeats forever.

Wait. Where’s the reset logic? How does the Cestino know when to zero the register and start counting over again?

PORTC is a single byte value. When the Cestino tries to increment PORTC from 255 to 256, it clears all the rest of the bits and sets the 256 bit. There is no 256 bit in the 8 bit PORTC register, so the register gets set to 0.

Some times, when you know your binary, it really is that simple.

Further

There exist bar graph LEDs with 50 or more LEDs, all multiplexed together so no more than four of them are on at any given time. Connecting these to the Cestino seems easy. You connect the anodes to one port, and the common cathodes to one or more ports, do a little port manipulation, make sure the delays are short, and you’re good. This would work because when the ATmega1284Ps port pins are zero or low, they are effectively connected to the ground (-) bus.

There’s a catch. Those connections still go through the integrated circuit, and the limits for how much current the ATmega1284P can sink (that’s what we’re talking about) are almost as small as the amounts of current it can source (what we’ve been doing in this chapter.) Look in the datasheet and do the calculations to make sure you don’t overload the Cestino, or it may reward you by spontaneously restarting, or other maladaptive behavior. If you want to drive large bar graph displays like this, the best answer is to get a Darlington transistor array, like the ULN2803. They’re cheap, they work very well with ATmegas, and they can switch 500 milliamps per channel. We’ll use one later.

A Cestino port combined with a ULN2803 or similar Darlington array can be used to control robotics, sprinkler systems, or whatever you can imagine. Eight bits at a time.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.177.86