Laboratorul 0xC1: Interrupts and Timers. Build a 7-segment digital counter

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.

Interrupts and Timers

Interrupts

  • Allow program to respond to asynchronous events when they occur, without busy-waiting.
  • The processor does an automatic procedure call (Interrupt Service Routine, ISR) when an interrupt event is detected.
  • Defined as interrupt vectors which are memory locations for the ISRs. Each interrupt vector has an ISR which is basically a function that can be defined by the programmer.
  • A way to define “hardware threads” that run independently of the main program loop.
  • Have to be enabled and configured for each peripheral device.

External interrupts on the Atmega324 are available as INT0..2 (for pins PD2, PD3, PB2) and PCINT0..3 (for ports A, B, C, D). There are interrupts available for other peripherals as well, such as Timers, USART, ADC, SPI, TWI, reset and watchdog timer, each with an interrupt vector (and ISR).

The steps for working with interrupts are as follows:

  • Enable the global interrupts
  • Configure the peripheral for generating interrupts
  • Define the ISR
  • Activate the interrupt

External Interrupts and Pin Change Interrupts are both used to detect GPIO events. There are only 3 External Interrupts on the Atmega324 and they can be configured for edge detection (rising edge, falling edge) on pins PD2, PD3 and PB2, while Pin Change Interrupts are available for all the ports (and pins) to detect logic level changes (but require more overhead to detect the pin that triggered the interrupt).

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

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:

  • Internal timers: counters that update on precise intervals
  • External timers: counters that update external events from a GPIO pin
  • Pulse Width Modulation (PWM) generator: hardware generated PWM signal

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:

  • Overflow (when TCNTx reaches TOP)
  • Compare A (when TCNTx reaches OCRxA)
  • Compare B (when TCNTx reaches OCRxB)

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

When we calculate the prescaler and counter limit, the values calculated the for counter limit should be exact (integer) and not exceed the register size (8 bit/16 bit). Usually, the method is to check each prescaler (1/8/64/256/1024 for Timer0/1, with the additional 32/128 for Timer2) until a good combination is found.

As we know, all the peripherals, including Timers are connected to the CPU by some registers for configuration and input-output:

  • TCCRn: In this register (used to configure the timer), there are 8 bits, but only last 3 bits CS02, CS01 and CS00 are used. These are ClockSelect bits used to setup the prescaler.
  • TCNTn: This is the timer counter register.
  • TIMSKn: This register is used to enabled/disable the Timer interrupts.

Timer modes:

  • Normal: the timer counts up until it overflows (0xFF/0xFFFF)
  • CTC (Clear on Timer Compare): the timer counts up until 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 display

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:

  • Common anode: the anodes (positive) side of all the LEDs are electrically connected at one pin and each LED cathode (negative) side has its own pin
  • Common cathode: the cathodes (negative) side of all the LEDs are electrically connected at one pin and each LED anode (positive) side has its own pin

There are also variants with more than one digit integrated into the display component. Multiple digit displays are the reason why the cathodes (or anodes) are connected, so that the digits can be multiplexed and not use a whole bunch of pins to drive the display.

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

Setup

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):

Fuse bits, also known as fuses or configuration bits are used to control certain operations. These are not normally changed during the execution of the program code.

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?

Remember to use the 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

  • Entry Level. Start by connecting a single 7-segment digit to the microcontroller. Pick a 7-segment common cathode display and place it on the schematic. Connect the anodes to 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.
  • Master of Segments. The 7-segment display has to show numbers from 00 to 99. Pick a 7-segment common cathode display and place two of them on the schematic. Each digit will share the anodes that are connected to 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.

The common cathodes will be used to switch the digits on/off fast enough so that the POV (persistance of vision) effect will give you the impression that both digits are visible at the same time. This way, you can make 7-segment displays with more digits that also draw less power and use less pins on the microcontroller.

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.

Remember to mask the PA7 pin when writing the binary combination of segments on PORTA as that pin is being used for another task (enabling the digits)


pm/lab/lab0xc0-2.txt · Last modified: 2020/03/29 13:04 by dumitru.tranca
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0