Introduction: Reading Switches With ATtiny2313

There have been several Instructables dealing with outputs from the ATtiny2313 and similar AVR devices. For example,, Working on the latest one from The Real Elliot, which showed how to control stepper motors, I found that it would be really helpful to be able to run alternate sections of code in the same program so I didn't have to reprogram the ATtiny2313 each time I wanted to try a slight code variation (such as half-stepping or running the stepper in reverse). While it is easy to write code using a switch/case statement to allow selection of alternate variations, some way of selecting the case is needed. That means some sort of input device has to be read to control the case.

Fortunately, the ATtiny2313 has plenty of I/O pins and is well-designed for reading inputs from switches. This Instructable will show how to read inputs and make decisions based on their state. Since that alone would make a pretty boring Instructable, I'll explain a simple way of using the timer/counter capability of the ATtiny2313 to drive a small speaker as a beeper. There will also be a small digression on simple debugging techniques.

Step 1: The Input Device

This Instructable builds on the excellent work of The Real Elliot and uses the ATtiny2313 Ghetto development system he describes. The ATtiny2313 data sheet from Atmel is the ultimate reference for all functions, but it is not necessarily easy to read. (Link has all AVR data sheets, locate the 2313.)

The figure shows a simple set of input switches. This is simply a package of four on/off switches; also known as single pole, single throw switches (SPST). Typically, one connection, or pole, of each switch is tied to ground while the other connection is pulled high through a current limiting resistor (10K or so). A microcontroller input is connected to the pole with the resistor. If the switch is open, the microcontroller will read the input as HI. If the switch is closed, the microcontroller will read the input LO. Refer to the schematic for details.

The ATtiny2313 simplifies things by providing programmable pull-up resistors on I/O pins when they are configured as inputs. This means that the switches can simply have one pole tied to ground (LO) and the other pole connected to a processor input. The first example shows only two switches. The switches are read and configured with the following code.

Configure the switches as inputs:
(No code required; this is the default.)
Turn on the pull-up resistors:
PORTB = _BV(PB0) | _BV(PB1) ;
Read the inputs:
but1 = ~PINB & 0x03;
Note use of inversion and masking to get correct value.

Step 2: Blinkenlights for a Signal

We'll use these two switches to blink an LED a programmable number of times. The LEDs we'll use will be the blinkenlights that The Real Elliot made famous. Switches 1 and 2 will be treated as two binary digits, so the combination can represent the numbers 0, 1, 2, and 3. Our program will read the two switches and blink the LED the appropriate number of times, but only if the switch settings have changed. The switches are debounced for 500 milliseconds (not optimized). The debounce algorithm is pretty simple. The switches are read and the reading is noted. If it is different from the oldBut value (the last saved value), then the program is delayed for 500 milliseconds and the switches are read again. If the value is the same as previously read, the value of oldBut will be updated and the LED will blink the number of times implied by the binary value of the two switches. Note the inversion of the value since a switch that is "on" reads LO. The switches will be scanned continuously for further changes.

Please refer to earlier Instructables by The Real Elliot to learn more about blinkenlights. Have a look at this to learn more about debouncing switches.

Here's the ATtiny2313 code for this example. In operation, this program will blink the LED on PB4 (physical pin 8) twice to show it is initialized. It will then read switches one and two, and blink one to three times depending on the switch setting whenever they are changed. When the switches are not changing, the LED will blink slowly.

To run this code, create a new directory (call it "Basic" if you like) and download the following C code file and makefile into it. Rename Makefile1.txt to just Makefile. Using WinAVR, compile the program and load it into your ATtiny2313.

Step 3: A Minor Digression on Debugging.

If you're like me (and every other programmer in the world) you probably have experienced times when the "error-free" code you've carefully typed in and compiled doesn't do what you expect it to do. Maybe it simply does nothing! So what's the problem? How are you going to find out?

Fortunately, there are several approaches to getting things to work. (Get this book for an excellent treatment of the topic of debugging. I'd like to offer a few simple suggestions pertaining to the topic of debugging microcontroller applications.

Step one is to build on what you know. If you have gotten a blinkenlight to work once, then use it again to see where you are in your program. I like to have the LED blink twice to signal the start of the program. You can put the code in to do this initially at the start of your program. Once you know that nothing is wrong with your hardware, create a function to do the blinking. Here's the function I use.

** blinkEm - function to blink LED using PD4
** PD4 must be configured as an output.
** ---------------------------------------------------------------------*/
void blinkEm( uint8_t count){
while (count > 0){

PORTD = ~_BV(PD4);

It is now possible to use this function at various points in your code as a signal that the code has executed that far. Knowing the code is running means you can carefully examine each section that has run, but not done what you expected, to find errors.

Changing one thing at a time is a key technique for debugging also (described in the reference above). This classic method works along with "divide and conquer": taking baby steps to add functionality incrementally. This may seem like a slow approach, but it's not nearly as slow as trying to debug a large section of non-working code all at once.

Step 4: More Debugging

There are many times when we want to check a section of code by skipping most of the lines in it, then enabling them one at a time as we verify each one works. Typically, we do this by "commenting out"lines we want to skip. An extension of this technique is to cut and paste a block of code, comment out the original (so we don't lose it), and hack away at the copy.

C has four easy ways to comment out lines.
Putting "//"in front of a line comments out that line.
Enclosing one or more lines in "/*"and "*/"will comment out an entire section. For this method to work effectively, there must be no other "*/" in the code block (other than the ending one). So an effective discipline is to use // for comments within blocks of code, and reserve the /* */ construct for comment blocks and for commenting out sections of code.
Placing "#if 0" at the start of a block to comment out and ending the section with "#endif".
More selective control is possible using "#ifdef (identifier)" at the start of a block and "#endif" at the end. If you want the block to be compiled, use "#define (identifier)" earlier in the program. Note the quote marks are for emphasis only and are not to be included.

Combining these techniques should provide a useful approach to debugging your ATtiny2313 programs. You may find these tools useful as we proceed through this Instructable.

Step 5: Using Timer/Counter 0 for Beeps

The ATtiny2313 has two powerful timer/counter resources: one 8-bit and one 16-bit. These may be configured as frequency generators, variable pulse width modulation controllers, and output compare registers. The full functionality of these is described in 49 pages of the data sheet. However, we'll use a simple case. Only Timer/Counter 0 (the 8-bit one) will be used and it will be used simply as a frequency generator. The frequency will be routed to a small speaker to produce a beep. Timer/Counter 0 is fully described in pages 66 to 83 of the ATtiny2313 data sheet. A close reading of this material will provide one with a complete understanding of Time/Counter 0. Happily, a fairly simple mode, Clear Timer on Compare (CTC), is all that is required to generate the beep tone we want.

For the mode we'll use, operation of the Timer/Counter is straight-forward. When a clock signal is selected, the counter starts at zero and increments each clock pulse. When the counter value reaches the value in the Output Compare Register (TOP), the counter resets to zero and counting starts again. The output bit associated with the Timer/Counter is toggled to produce a square wave output. This directly drives an audio transducer to make a beep sound.

A small TDK Audio Transducer produces the beep. A suitable unit is Digikey 445-2530-ND, TDK SD1209T3-A1 (I used an early version of this). This is a 3 volt version; the 5 volt version will also work I expect. I drive this directly off the output port of the Attiny2313 and it seems to work fine. Sparkfun has a similar device.

Step 6: Configuring Timer/Counter 0

CTC mode can be used to toggle the output OC0A on Pin 2, Port B (physical pin 14). To enable output on this pin, DDRB must be set appropriately. The C code for this is just like setting up an output for a blinkenlight.

DDRB = _BV(PB2); // Port B2 is an output.

The next step is to supply a clock signal and load the output compare register to produce a waveform as a frequency. The equation for the resulting frequency is given in the data sheet (page 72). Terms in the equation will be described below. Here's the equation:

fOC0A = fclk_I/O / 2*N*(1+OCR0A)

Where fOC0A:= output frequency
fclk_I/O:= clock source frequency
N:= clock prescale factor
OCR0A:= value in output compare register for Timer/Counter 0A.

Clock Source Frequency, fclk_I/O
This is the frequency of the system clock. The default value is 1MHz. Bits CS00, CS01, and CS02 of TCCR0B control this selection. Since these bits also select the value of N, it is described next.

Prescaler Value, N
N is the value used to divide, or prescale, the system clock. Bits CS00, CS01, and CS02 of TCCR0B control this selection. Table 41 on page 81 of the ATtiny2313 data sheet describes the combinations. Since a frequency near 1kHz is desired, bits CS00 and CS01 of TCCR0B will be set. Note that setting all three bits to 0, thus selecting no clock source, effectively stops the output. This is the method that will be used to start and stop the beep.

TOP Value, OCR0A
This value is the TOP value for the counter which is loaded into the Output Compare Register for Timer/Counter 0A. When this value is reached, the counter will be reset to zero and counting will begin again until TOP is reached and the cycle repeats. TOP is easily modified, so the frequency of the beeper is easy to change. Since a frequency near 1kHz is desired, TOP is set to 7. (Note the prescaler could have been set to 8, and TOP set to 63. Same result - your choice.)

Output Frequency, fOC0A
Using the equation to calculate the output frequency results in:

fOC0A = 1,000,000 / 2 * 64 * (1+7)
fOC0A = 977Hz

Close enough! Here's the code to load the Output Compare Register and the Timer Counter Control Register 0B. Please see the actual program code to understand how these are used.
OCR0A = 7; // Time Value

TCCR0B = _BV(CS01) | _BV(CS00); // Select internal clock & prescale=8

TCCR0B = 0; // no clock source turns tone off

Setting the Time/Counter Mode

As a last detail, we'll specify the Timer/Counter mode we desire by setting appropriate bits in Timer/Counter Control Register 0A. CTC mode is selected by setting bit WGM01 as described in Table 40, page 79 of the data sheet. Since we want the output to toggle each cycle, bit COM0A0 also needs to be set as described in Table 34 on page 77. Here's the code:

TCCR0A = _BV(COM0A0) | _BV(WGM01); // CTC Toggle Mode

Step 7: Using Four Switches

As we implement the beeper, let's extend our hardware and software to handle four switches. Since the output of Timer Counter 0A is on Port B, pin 2, we can't simply hook up more switches sequentially to Port B. An easy solution would be to use Port D, but let's keep that port available for other functions (perhaps a stepper motor). So let's hook up the additional switches to PB3 and PB4.

Reading the switches is mostly unchanged. The mask value is changed to 0x1B (00011011 binary) to mask bit 2 along with 5, 6, and 7. One further trick is used to create a 4-bit binary number. Shift bits 3 and 4 right one bit and combine them with bits 0 and 1 into a 4 bit binary number. This is standard C syntax for shifting and combining bits, but may not be well known to the novice.

but1a = (but1 & 0x03) | ((but1 & 0x18) >> 1); // but1 has switch reading

In operation, the program will blink twice and beep twice to signal initialization. Anytime the switches are changed, the number they represent will be beeped. When the switches aren't changing, the LED will blink.

To run this code, create a new directory (call it Beep if you like) and download the following C code file and makefile into it. Rename Makefile2.txt to just Makefile. Using WinAVR, compile the program and load it into your Attiny2313.

Step 8: Using the Switch/case Construct

The final step is "just software": As promised, we'll implement the switch/case construct. Although this example only shows two alternate actions, it should be very clear how to use this construct to select one of several alternate code sections. In operation, this program monitors the switches and if there is a change, it will beep the appropriate number if it is odd ; it'll blink if the number is even. It does nothing unless a switch changes.

To run this code, create a new directory (call it Switch if you like) and download the following C code file and makefile into it. Rename Makefile3.txt to just Makefile. Using WinAVR, compile the program and load it into your Attiny2313.

Step 9: Conclusion

So that's it! Now you know how to use switches to control execution of your program by reading them in and selecting an action based on the switch setting. You also know how to create a beep tone and have learned some debug strategy as well.

If you'd like to test your understanding, try modifying the last program to beep at a high pitch if even, beep a low note if odd, and blink the LED continuously if there is no change in the switches.You might want to look back at the section on debugging for help.