This is an old revision of the document!
This lab covers the basics for working with External Interrupts, Pin Change Interrupts and Timers on the Atmega324. You will use buttons to interact with your design and you will create your own display using the good old 7-segment display. You can check out the lab Laboratorul 2: Întreruperi, Timere and datasheet 1)
#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) { // cod întrerupere de tip pin change if ((PINB & (1 << PB1)) == 0) { // întrerupere generată de pinul 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 given threshold (e.g OCRnA, OCRnB). Timers can be configured for multiple use cases:
// 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
TCNTn
reaches the compare threshold OCRnA
/ICRn
#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.
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 |
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 for the Atmega324. For this, we have to set the fuse bits as shown in the figure below. 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. 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):
XTAL1
and XTAL2
pins on the Atmega324. Right click on the crystal to open the configuration editor and set the frequency to 12MHz.
3. 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).
volatile
keyword (e.g. volatile uint8_t counter;) for variables that you use in interrupts. Also, try to use standard types from stdlib.h
(e.g. int8_t, uint8_t, uint16_t, uint32_t). 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?
5.1. (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 to count seconds from 0 to 9 and then start over.
5.2. (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). The 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. 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 to count seconds from 0 to 99 and then start over.
Check out the 7-segment display counter in action:
PA7
pin when writing the binary combination on PORTA
as that pin is being used for another task (enabling the digits). Light up 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 in 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.