This lab covers the basics for working with External Interrupts, Pin Change Interrupts and Timers on the Atmega324. You will use timers and interrupts to create a digital counter and you will use buttons, LEDs and the good old 7-segment display to interact with your design in Proteus. You can check out the lab Laboratorul 2: Întreruperi, Timere and datasheet (Datasheet ATmega324) for more information and references about timers and interrupts.
The steps for working with interrupts are as follows:
Now let's see how this looks in code for Pin Change Interrupts:
#include <avr/interrupt.h> void init() { // disable global interrupts cli(); // configure pin change interrupt vector PCICR |= (1 << PCIE1); // enable the pin change interrupt, set PCIE1 to enable PCMSK1 scan // enable pin change interrupt PCMSK1 |= (1 << PCINT9); // Turns on PCINT9 (PB1) // enable global interrupts sei(); } // define the pin change interrupts ISR ISR(PCINT1_vect) { // interrupt code if ((PINB & (1 << PB1)) == 0) { // interrupt generated by PB1 } }
Timers are used to count fixed time intervals without busy-waiting (delay). They are defined by the counter register TCNT, a prescaler and some interrupt vectors that can be defined to trigger when the counter reaches a preset threshold (e.g OCRnA, OCRnB).
Timers can be configured for multiple use cases:
The Atmega324 has 3 timers: 8 bit Timer0/2 and 16 bit Timer1. In this lab, we will discuss the configuration and applications for internal timers.
A timer can generate the following interrupts:
Speed of the counting (and interrupts) can be controlled by the prescaler, mode of operation and counter limits as follows:
// calculate the frequency of the interrupt (f) from the timer configuration and clock speed (f_clk) f_int = f_clk / (prescaler * (tc + 1)) // calculate the target timer count (tc) for the required interrupt frequency tc = f_clk / (prescaler * f_int) - 1
As we know, all the peripherals, including Timers are connected to the CPU by some registers for configuration and input-output:
Timer modes:
TCNTn
reaches the compare threshold OCRnA
/ICRn
Now let's see how this looks in code:
#include <avr/interrupt.h> void init_timer1() { // initialize the timer counter TCNT1 = 0; // configure the threshold OCR1A = 10000; // enable compare interrupt A TIMSK1 = (1 << OCIE1A); // configure mode of operation e.g. CTC with OCR1A TCCR1B = (1 << WGM12); // set the prescaler e.g 256 TCCR1B |= (1 << CS12); } ISR(TIMER1_COMPA_vect) { // interrupt code }
Allright, now we are interested in counting precise time intervals and doing repeated actions. For example, we want to count seconds and display them on a 7-segment display. Check out Laboratorul 2: Întreruperi, Timere and (Datasheet ATmega324) for configuration options.
7-segment LED displays are used in many applications as front panel numeric indicators. The most common applications are calculators, digital clocks, microwave ovens, electronic lab equipment like function generators and frequency counters. It's also common to have 7-segment displays that have a dot that can be enabled after the digit.
A 7-segment LED display is an arrangement of LED bars (a, b, c, d, e, f, g, dp) that can be powered individually to display digits (and some characters too). The configuration can be either common anode or common cathode:
The following lookup table may be useful for programming a 7-segment display driver to show some numbers:
Digit | hex | a | b | c | d | e | f | g | |
---|---|---|---|---|---|---|---|---|---|
0 | 0x7e | 1 | 1 | 1 | 1 | 1 | 1 | 0 | |
1 | 0x30 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | |
2 | 0x6d | 1 | 1 | 0 | 1 | 1 | 0 | 1 | |
3 | 0x79 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | |
4 | 0x33 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | |
5 | 0x5b | 1 | 0 | 1 | 1 | 0 | 1 | 1 | |
6 | 0x5f | 1 | 0 | 1 | 1 | 1 | 1 | 1 | |
7 | 0x70 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | |
8 | 0x7f | 1 | 1 | 1 | 1 | 1 | 1 | 1 | |
9 | 0x7b | 1 | 1 | 1 | 1 | 0 | 1 | 1 |
1. (1p) Now we are dealing with timers, so accurate timing is important.
In the previous lab, the microcontroller was configured (by default) to use the internal 8MHz oscillator on the Atmega for clock generation, with a clock divider (CLKDIV8
fuse bit) set by default, making for a 1MHz actual clock. However, if we want higher precision/more speed we can use an external oscillator (crystal/quartz) with frequencies up to 20MHz (with Vcc = 5V) for the Atmega324.
For this, we have to set the fuse bits as shown in the figure below. Right click on the microcontroller and select Edit Component to open the configuration editor and change the fuse bits to use external oscillator (CKSEL
Fuses) and disable the clock divider (clear 'CLKDIV8') fuse bit. Configure the clock speed at 12MHz (for convenience, to match the clock on the PM boards and use the same Makefile):
2. (1p) Add a quartz crystal from the parts list and connect it to the XTAL1
and XTAL2
pins on the Atmega324. Right click on the crystal to open the configuration editor and set the frequency to 12MHz. Note: for physical designs, you need additional 22pF capacitors on each pin (with the other sides connected to GND).
3. (2p) Connect a push button to PD3
and a generic LED to PD4
. Pick a resistor for the LED to draw less than 10mA. The LED will show the state (enabled/disabled) of the 7-segment digital counter that we'll build next. When the user pushes the button, the program will enable/disable the counter instantly (use interrupts on PD3
and the PCINT ISR on PORTD
to detect the button transition).
4. (2p) Configure Timer1 to generate interrupts every second. Now, use a software counter (aka. variable) to count seconds (0-9 for 5.1 and 0-99 for 5.2). Now, add another LED to PD2
to check if the counter is working. Make the LED toggle on each timer interrupt. Watch the LED blinking. What is the frequency of the LED blinking?
volatile
keyword (e.g. volatile uint8_t counter;) for variables that you use in interrupts. Also, try to use standard types from stdlib.h
/stdint.h
(e.g. int8_t, uint8_t, uint16_t, uint32_t).
5. (2p/3p) Place and connect the components on the schematic for building the 7-segment display
PORTA
0..6 through a resistor network (single resistors work too) and the cathode to ground. Write a program that counts seconds from 0 to 9 and then starts over.PORTA
0..6 through a resistor network (single resistors work too). Pick a transistor (2n2222A is a good choice for switching) and place two of them to drive the cathodes. Connect the base of each transistor to a microcontroller pin with a resistor of 1k in between. Let's use PA7
and PC2
. Write a program that counts seconds from 0 to 99 and then starts over.
Check out the 7-segment display counter in action:
6. (2p/3p) In the main loop, get the digits from the counter (variable) and use them to drive the two 7-segment displays. Power the segments as shown in the truth table and show each of the two digits with a small delay between them. You can actually use the _delay_ms function in the main loop and this will not affect your timing (counting is done with timer interrupts). Note: In simulation, you will have to use at least 100 ms and you won't be able to see them both at the same time.
PA7
pin when writing the binary combination of segments on PORTA
as that pin is being used for another task (enabling the digits)