Wednesday 12 June 2013

PWM Configuration for ATMEGA16

     In previous articles we have talked about digital reading, digital writing, now let's learn how to use the timers to generate PWM.
  • What is PWM? For simplicity, let's say it's a square signal with a given frequency and a given duty cycle. For more details refer to this pwm explanation.


  • To generate PWM , we will use the timer
What  is a timer? Well, I'll keep it simple. Let's say that a timer is a unit that counts up or down when a clock edge appears. So if the clock frequency is 10Hz, the counter does something ten times a second.
Atmega16 has three timers called Timer0(8 bit), Timer1(16 bit), Timer2(8 bit).
If a timer is an 8bit timer, it can count up to 2^8-1 = 255. If it is a 16 bit timer, it can count up to 2^16-1 = 65535.

  • For this tutorial we will study Timer0. Timer1 and 2 are configured in the same way. 

Timers have some registers that can be used.
The first one is the Timer/Counter Configuration Register (TCCR0). This register is used to configure the timer, what we want the timer to do, what function to be executed. We said that the timer counts up or down. The number that is increased or decreased will be found in TCNT0 register(8 bit register), so it can be between 0 and 255.
Now, it counts up or down, but the timer can do something like put a pin on "1" or "0" if the timer reaches a limit. This limit is set in the Output Compare Register(OCR0).
TCNT0 is compared with OCR0. On compare match it can generate an interrupt or it can generate a signal to the outside world.

  • Let's have a look in the datasheet at page 83, at Waveform Generation Mode Bit Description Table 38. We can see that for FastPWM we have to set WGM01 and WGM00 to "1".  Now let's have a look at Table 40 Compare Output Mode, Fast PWM Mode and choose from there the third option(non-inverting mode). So, we have to set COM01 to "1". Until now we have configured what function the timer has to execute(non inverting fast PWM). 
  • FastPWM non-inverting works something like this(simple explanation).
TCNT0 is set to 0. You set the duty cycle in OCR Value.
The timer starts counting up, TCNT0 increases. Until TCNT0 doesn't reach the value in OCR0, the output ping OC0 is set to "1". When TCNT0 is greater than OCR0 , OC0 is set to "0".  TCNT0 continues to count up until it reaches the maximum limit of 255, after that it goes back to 0 (cleared) and OC0 is set back to "1".

So that why the frequency between the rising edges of OC0 is (fTimer / 2^8)

Now, we have to set the frequency.

The timer executes when a clock edge appears. The clock is the one that is used for the uC. So , for example is you have a 16MHz clock, this signal will be used as a clock for the timer. However, if you want, you can use this value divided by 8, 64, 256, or 1024 or you can even use an external clock source. The divider is called prescaler.  You can select the clock that is used for the timer by setting CS00 CS01 CS02 bits.

The frequency of your PWM signal will be equal to (CLK freq/(N*256)) where N is the clock divider. 256 is 2^8.

CLKfreq/N is fTimer from the explanation of FastPWM.

  • Having set the mode of operation and the frequency, all that remains is to set the duty cycle. The duty cycle can be set by writing the OCR0 register. The duty cycle will be OCR0value/255. 
  • Also, OC0 is set as OUTPUT, so let us set the correct value in the Data Direction Register .  OC0 is PB3




#include <avr/io.h>
#include <avr/interrupt.h>
#define F_CPU 16000000
#include <util/delay.h>

int main()
{
    TCCR0 |= (1 << WGM00) | (1 << COM01) | (1 << WGM01) | (1 << CS00); /*Configure the timer for FastPWM at frequency of  f =~ 62.5kHz with no prescaling */
    DDRB|=( 1 << PB3 );//set OC0 as output.
    OCR0 = 200;//set the duty cycle(200/255)
   
    while(1); 
    return 0;
}



Usage:

- Modifying the speed of a motor
- Controlling the luminosity of an LED or other element
- Controlling the average voltage
- Controlling servo-motors
-Other things :)

Friday 15 February 2013

ATMEGA16 Tutorial: Digital Write, Digital Read

Basics for programming an ATMEGA16

1. Digital Write.
When you want to program a uC, you always start from it's datasheet.
http://www.atmel.com/Images/doc2466.pdf

You can see in the datasheet that this uC has 4 8 bit PORTS: PORT A, PORT B, PORT C, PORT D.
Each port can be controlled using 3 registers. PORT, PIN and DDR
You can read in the datasheet at page 51 all the details in the chapter Configuring the Pin.
To summarise:
DDR means Data Direction Register, so using this register we can set if one pin of a port is being used for input or for output.
In the datasheet we can read the following If DDxn is written logic one, Pxn is configured as an output pin. If DDxn is written logic zero, Pxn is configured as an input pin.
Using the PORT register we write to that port, using the PIN register we read from the port.

Case 1:our port is set as output(all 8 pins are output)
            We write to one pin from that port the logic value one
            Result: On that pin we will have aprox 5V.
            We write to one pin from that port the logic value 0
            Result: On that pin we will have aprox 0V
            Everything seems logic....
Case 2:
          our port is set as input
          We write to one pin set as input the  logic value 1
          Result: an internal pull-up resistor is activate.
          We write to one pin set as input the logic value 0
          Result: the internall pull-up resistor is deactivated
What is a pull-up resistor: http://en.wikipedia.org/wiki/Pull-up_resistor
Basically, it is used to pull the pin to VCC when nothing is wired to it. Nothing does not mean 0 logic or 1 logic, so to avoid hazard(noise signal that influences the pin) we pull it to VCC and we know that when nothing is wired, we will have a logic 1.

Now, let's do a digita write on one pin of PORTA for example.

As we can see in the datasheet, PORTA consists of 0 bits (PA7, PA6, ...,PA0).
Let's say we want to write a logic value of 1 to PA6.
PA7, PA5....PA0 will remain unchanged(input or output)
We have to set PA6 as output and write to it.

First, set PA6 as output. We have to make the 7'th bit of DDRA 1, without modifying the rest of the bits.
We know that x | 0 equals x and x | 1 equals 1.
DDRA = DDRA | (1 << PA6 ); 
1 << PA6 means 1 shifted to the felft with PA6 positions. PA6 is defined in a library as 6.
1 << 0 means 00000001
1 << 1 means 00000010
and 1 << PA6 means 01000000 (what we want)
We make a logic or on bits between DDRA and  (1 << PA6). All the bits beside PA6 remain unchanged, and PA6 will become 1 .
Now, we have to write the logic value of 1 to PA6
Using the same technique:

PORTA = PORTA | (1 << PA6);
If we want to write a logic 0 to PORTA and leave the other bits beside PA6 unchanged, we have to use the function & (and)
x & 1 equals x
x & 0 equals 0.  So we have to make a logic and between PORTA and 0 placed at the PA6'th position. Besides that 0, the other bits have to be 1.
1 << PA6  is 01000000
Not changes a logic value:   NOT 1 equals 0
                                          NOT 0 equals 1.
~ is a NOT operator that makes NOT on every bit of the register.
~(1 << PA6) equals 10111111 (exactly what we want)

PORTA = PORTA & (~ (1 << PA6 ));

In C programming language if you have an expression like:

OPERATOR1 = OPERATOR1  operand OPERATOR2
you can write it as:
OPERATOR1 operand = OPERATOR2

For example
a = a + b; <=>  a + = b;
a = a | b <=> a | = b;
a = a & b <=> a & = b;
PORTA = PORTA | (1 << PA6) is the same as PORTA | = (1 << PA6) ;

Another important thing, when using Atmel libraries you have the macro BIT VALUE :
 _BV(PA6) is equal to (1 <<  PA6 ) , so instead of writing the whole (1 <<  PA6 ) you can write  _BV(PA6) and it's the same thing.

PORTA | = (1 << PA6 ); is the same as PORTA | = _BV(PA6);

So, a complete program to write to a pin of a port will be:


#include <avr/io.h>

#define F_CPU 16000000//the cpu is running at 16MHZ)
#include <util/delay.h>

int main() //this is the main function that is being executed when the program starts
 {
while(1)//the program runs continous
  {
    DDRA | = ( 1 << PA6 ); //set PA6 as output
    PORTA | = (1 << PA6); //set PA6 on logic value 1
    _delay_ms(1000);//we wait for 1 second
    PORTA & = ~ (1 << PA6 ); //set PA6 on logic value 0
    _delay_ms(1000);
   }
return 0;
}


This simple program will make PA6 go to 5V, stay 1 second , then go to 0V and stay 1 sec.

2. Digital READ
For digital read we will read pin PD6. If PD6 is HIGH (logical value 1), we will make PA6 go to 5V.
Else, PA6 will be LOW(0V aka logical value 0)
We have to set PD6 to be input, so we have to make the 7'th bit in DDRD 0.

(as we did in the previous case , we will use AND function)

DDRD & = ~ (1 << PD6 ) ; // The PD6th bit will be 0, so it will be an input.
Let's suppose that on PD6 we have connected a button that pul PD6 to GND. We have to activate, for a correct reading of the pin, the internal pull-up resistor.

PORTD | = (1 << PD6 );// we activate the internal pull-up resistor on PD6.

The value of the pin is determined in the following method:
we know that x & 1 equals x, and x & 0 equals 0. We want to tell if PD6 is 1, we don't care about the rest.

PIND & ( 1 << PD6) gives us exactly the value of PD6 - or it's 1("HIGH") , or 0 ( "LOW"). Put this into an if, and voila:




#include <avr/io.h>
#define F_CPU 16000000
#include <util/delay.h>

int main()
{
/* PORT D pins are set as output */
DDRD & = ~ (1 << PD6 ) ; // The PD6th bit will be 0, so it will be an input.
        PORTD | = (1 << PD6 );// we activate the internal pull-up resistor on PD6.
         DDRA | = ( 1 << PA6 ); //set PA6 as output
while(1)
        {
       if((PIND & (1 << PD6))) // if PD6 is "HIGH"
                    {
PORTA |=  ( 1 << PA6 ); // PA6 goes HIGH
    }
else
PORTA & = ~ (1 << PA6 ); //PA6 goes LOW
}

return 0;
}

If you have questions, please comment below

Varying bulb luminosity using ATMEGA16


GENERAL DESCRIPTION

! THE FOLLOWING APPLICATIONS IS DIRECTLY CONNECTED TO 230V POWER LINE. BE CAREFULL. 
! ELECTROCUTION IS POTENTIALY DEADLY.
We know that on the market there are plenty of devices that replace normal wall switches and let you adjust the luminosity of a bulb. But if you want to make an "intelligent" home or a more complicated device to adjust the power of a light bulb what possibilities do you have?

1. If you are using DC , than it's simple: just use a switching element ( a BJT or a MOSFET ). You just apply PWM (Pulse Width Modulation)  and vary the duty cycle from  0% to 99% and you can adjust the luminosity of a lightbulb or LED or you can set the speed of a motor.

2. If  you are using AC .... well you can do the  following:
       2.a. Use a rectifier to obtain DC from AC, filter it and apply what was written at 1.
       2.b.1 Use "something" to turn on and off AC.

If I would use a BJT to implement my application, I would have to consider that in the reverse active region the behavior is different from the forward active region.

Because the application will be used at 50Hz(low frequency) to "drive" a light bulb (small load), and the maximum necessary current that will pass through it will be under 4 - 5 Amps, I have chosen to use a TRIAC. The same application can be easily modified(add some diodes) and can be used to drive high power loads(motors, heating elements, etc) using Thyristors.

In this article, to keep it simple and effective,  I will present how to fire a TRIAC  using an ATMEGA16.


SCHEMATIC

The following schematics will be used:


SCHEMATIC EXPLAINED

We can see that the schematics consists of 6 blocks.
1. Separation block(transformer)
2. Rectifier block
3. Voltage source block
4. 0 crossing detector block
5. Triac block
6. Computational block

1. The galvanic separation is used for safety. The transformer takes the 230V AC from the mains and gives on the output a voltage between 8 to 24V(which is safe tot touch).
This block can be replaced with a group of resistors, capacitors, diodes. IT IS DANGEROUS TO HAVE THE MAINS GO DIRECTLY TO THE MICROCONTROLLER.

2. Use the voltage from the secondary winding of the transformer to power up the uC, and also to detect de 0 crossings of the mains. To power up the uC we need DC, so we take the AC from the transformer and after rectifying(which include a bridge rectifier and a capacitor for filtering) block we obtain DC voltage

3. The uC uses 5V. We take the DC obtained at 2, use it as an input for the 7805 integrated circuit. At the output of the 7805 we will obtain 5V stabilized. We will use this voltage to power up the uC.

4. Zero crossing detector - it's just an opto-coupler. Internally, a diode lights up and turns on or off a photo-transistor which pulls the output to GND. When the transistor is turned off , the output is pulled to VCC by the pull-up resistor.

5. When the output of the uC is HIGH, transistor Q0 is in saturation and the diode inside the opto-triac is lighten and the BT136 TRIAC is fired (it conducts current from A1 to A2).

6. The computational block consists of an ATMEGA16 uC used to run a small program. We use the external interrupt pin INT1 (PD3) and an output(PD7).

CODE


#include <avr/io.h>
#define F_CPU 16000000
#include <util/delay.h>
#include<avr/interrupt.h>



ISR ( INT1_vect )
{
    _delay_ms( 1 );
    TCCR1B |= ( 1 << CS11 );
}

ISR( TIMER1_COMPA_vect )
{
    PORTD |= ( 1 <<  PD7 ) ;
    TCCR1B &= ~ ( 1 << CS11 ) ;
    TCNT1 = 0 ;
    TCCR0 |= ( 1 << CS01 );
}

ISR( TIMER0_COMP_vect )
{
    PORTD &= ~ ( 1 << PD7 ) ;
    TCCR0 &= ~ ( 1 << CS01 ) ;
    TCNT0 = 0 ;
}
int main()
{
    DDRD = ( 1 << 7 ) ;
    PORTD |= ( 1 << PD7 ) | ( 1 << PD3 ) ;
    MCUCR  |= ( 1 << ISC10 ) ;
    GICR |= ( 1 << INT1 ) ;
    TCCR1B = 0x00;
    TCCR1A = 0x00;
    TCCR1B |= ( 1 << WGM12 ) ;
    TIMSK |= ( 1 << OCIE1A ) ;
    OCR1A = 0X09FF;
    TCCR0 = 0x00;
    TCCR0 |= ( 1<<WGM01 ) ;
    TIMSK |= ( 1 << OCIE0 ) ;
    OCR0 = 0XFF;
    sei();
    while(1);
    return 0;
}

CODE EXPLAINED

ISR- means interrupt service request. It is a function that it's called when an interrupt occurs.
TIMER - internal structure of the microcontroller that counts. In our case the timer works in CTC "Clear Timer on Compare" mode. When TCNT(timer register that is incremented with every tick) is equal to OCR an interrupt is generated.

1 << X means 1 shifted to left with X bits
example: 1<<0 means 00000001
and 1<<7 means 10000000

In the main function we make the initialisation for the code to work(initialise the timers, set the interrupts and then wait for something to happen).

When the sinus signal reaches almost 0V, in the detection block a rising or falling edge will be generated. This edge, aplied to PD3(INT1) pin generate an interrupt. When the interrupt appears, the function  ISR ( INT1_vect ) is called. It waits for 1ms(for the voltage to go down to 0 ) and then enables Timer 1 to start counting.the time for the delay until the trigger signal for the opto-triac will be given.
When Timer 1 generates an interrupt(it has finished counting), PD3 pin is set to TRUE(5V) and the trigger signal is sent to the opto-triac which fires the triac that powers on the load (light bulb). Timer 1 is turned off and timer 0 starts counting.
When Timer 0 generates an interrupt(it has finished counting), it means the trigger signal must end , PD3 is set to FALSE(0V) and timer 0 stops counting.

The program will be enhanced in the future to receive the data from USART or ADC and to be set dynamically using an algorithm. Right now, everything is static. This is just a Proof Of Concept.

PCB - it's easy to design, I had no design, just used one board with ATMEGA16 and components soldered in air to it.

Results: 

Trigger signal after 0 crossing with delay:

Trigger signal and signal on the load:


Bulb at low luminosity and signal with trigger for that:




TO BE MENTIONED:
If you want to drive a motor instead of a light bulb, no problem, but you have to use a snubber circuit to protect the TRIAC from damaging because of the inductive load or from auto-triggering.
Also, it's good practice to make the length of the signal until the sinus reaches 0, but it is a little tricky(it is not included in the code). You have to adjust timer 1 for that.
The same stuff can be done with ARDUINO, which uses Atmel microcontrollers and the code can be written in the same stile or using ARDUINO IDE ( I prefer using the presented style, and defenetly not Arduino IDE).

THIS IS MY INTELLECTUAL PROPERTY, if you want to borrow photos or information please ask first.


Article about thyristor: http://en.wikipedia.org/wiki/Thyristor
Article about the TRIAC: http://en.wikipedia.org/wiki/TRIAC
ATMEGA16 Datasheet: http://www.atmel.com/Images/doc2466.pdf 
Hope you like it and it's useful.
If you have something to say (feedback - negative or positive , don't hesitate to post it).