This is an old revision of the document!


Laboratorul 0xC2: PWM, Counters and External Interrupts

This lab covers the topics of PWM, H-bridge drivers and reading encoders using external interrupts. For more in-depth knowledge about the capabilities and the timing peripherals and how to program them you can find more information in the ATmega324 datasheet (Datasheet ATmega324).

PWM (Pulse Width Modulation)

PWM (Pulse Width Modulation) or PDM (Pulse Duration Modulation) is a modulation technique used to reduce the average power delivered by an electrical signal to a load by chopping it into discrete parts. The average value of a signal is dependent on the maximum and minimum amplitude and on the duration of the pulses of minimum and maximum amplitude.

PWM - Electronics and average voltage

In order to use PWM and prior to program our microcontroller to generate PWM signals, we first need to understand the basics of PWM and how to use it in different circuits.

Understanding PWM signals

The output signal (or the Pulse Width Modulated signal) will be generated by using electronic switches (such as Bipolar Transistors, MOSFET Transistors, IGBT) that connects a positive voltage power supply (e.g. a battery or a fixed voltage output) for a precise amount of time (T1) to the output, afterwards it connects a negative voltage (or the GND for unipolar PWM) the output from the power supply for another amount of time (T2), and than, the cycle repeats.

In the following schematic, we can see that Q1 connects the output to the positive voltage supply (Vmax) and Q2 connects the ouput to the negative voltage supply (Vmin). Q1 and Q2 are controlled by a more complex circuit (transistor driving circuit), based on the signals given by the microcontroller (MCU). The transistor driving circuit is sometimes necessary because of the different voltage levels of the MCU and the voltages of the power supplies.

All the voltages are measured with respect to GND, unless otherwise is explicitly noted.

The output signal delivered to the load will look similar to the one showed in the next figure:

As we can observe, in one cycle (of duration T=T1+T2) the signal is switched between Vmax and Vmin.

  • Q1 is turned ON (and Q2 is OFF) for an interval of T1, and the output is forced to Vmax
  • Q2 is turned ON (and Q1 is turned OFF) for an interval T2, and the output is forced to Vmin
  • The cycle duration is T=T1+T2;

The average voltage of the signal (measured with respect to GND) can be calculated by computing the integral over time. V_1

\begin{equation} V_{avg}=\frac{1}{T}\int_{0}^{T} Vout(t) dt \end{equation}

Considering for one cycle, that from 0 to T1 the output is Vmax and from T1 to T (interval equal to T2) the output is Vmin than the average voltage will be computes as follows:

\begin{equation} V_{avg}=\frac{T1}{T}V_{max} + \frac{T2}{T} V_{min} \end{equation}

In electronics, the ratio between T1 (the duration of the positive pulse) and the period T is called the duty cycle. The duty cycles tells us how much time, compared to the total period, the positive pulse duration is.

\begin{equation} d=\frac{T1}{T} \end{equation}

The duty cycle can be measured as a (percentage) % and it resembles the fraction of the positive pulse (T1) compared the total period (T).

\begin{equation} d_{\%}=\frac{T1}{T}100 \end{equation}

Considering the definition of the duty cycle, the average voltage is:

\begin{equation} V_{avg}=d\cdot V_{max} + (1-d)\cdot V_{min} \end{equation}

For the signal showed in the previous figure, we can observe that T1 is 7.5ms and T2 is 2.5ms (T=10ms), Vmax=10V, Vmin= -10V. That leads us to $d=0.75 or d\%=75\%$

Thus, our output average voltage will be:

\begin{equation} V_{avg}=0.75\cdot 10 + 0.25\cdot (-10)= 5V \end{equation} For an unipolar signal (the signal is switched between Vmax and 0 (GND) ) the average voltage will be computed as:

\begin{equation} V_{avg}=\frac{T1}{T}V_{max}=d\cdot V_{max} \end{equation}

PWM is used in many applications where with a digital control different elements are controlled. For example, PWM can be used to control the speed of DC motors, to control the brightness of LEDs, to generate different colors using RGB LEDs (by controlling the intensity of each color), to generate analog signals (generating AC from DC), reducing the power and voltage from a power source, in MPTT solar battery chargers/controllers, in D-Class audio amplifiers, etc.

PWM on LEDs

Driving LEDs is done the same way as for simple digital outputs, as explained in our previous labs. If the LED is a high power device (more than 25mW LED), we will need a controllable LED driver (usually based on a switchable current supply).

The LED can be connected from the output of the MCU to GND with a resistor and it will light up when the MCU pin is driven HIGH. The resistor is used to limit the current through the LED.

The LED can be connected from the power supply to the MCU pin and it will light up when the MCU pin is driven LOW (the current will flow from Vcc to the MCU pin).

Delivering PWM for DC brushed motors and other loads

H Bridge basics
H Bridge example
H Bridge circuits

Generating PWM using ATmega Timers

Fast PWM mode

Fixed Frequency
Programmable Frequency

Phase Correct PWM mode

Reading quadrature encoders using external interrupts (INT0/INT1/INT2)

Quadrature encoders - principles of operation

Encoders are electronic or electro-mechanic devices used to codify the relative movement (rotation) of a shaft. They generate two pulses (A and B) that are shifted 90 degrees apart that codify the speed of the movement and the direction of the movement.

  • The rotation speed of the shaft will be deduced from the frequency of the pulses
  • The number of revolutions will be calculated from the number of pulses
  • The direction of rotation (clockwise or anticlockwise) will be deduced from the order of the pulses.

The encoders are usually characterized by the number of pulses/complete revolution and the type of circuitry for the output signals (PNP, NPN, PUSH-PULL stage).

For example, let's say that we have an encoder with a Push-Pull stage of 5V (it can pull the output to 5V and GND and generate both states - HIGH and LOW) that generates a 4 pulses per complete revolution.

  • We can observe that there are a number of 6 pulses, that means that the rotor has executed 1.5 revolutions (6/4)
  • We can see that the period of signal A is 2s (from rising edge to the next rising edge), thus we can say that a complete revolution will be finished in 8 seconds. So, the speed of rotation is 7.5 (60 seconds /8 seconds per revolution) revolutions/minute
  • We can observe that on a rising edge for signal A, signal B is on a LOW state, meaning that the rotation is in counter-clockwise direction

Example of using external interrupts to read quadratic encoders

Setup

Tasks

  • 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

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.1585475786.txt.gz · Last modified: 2020/03/29 12:56 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