0

Help with programming PWM using Attiny2313 AVR microcontroll
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

Help with programming PWM using Attiny2313 AVR microcontroll

by buddha on Sat Mar 08, 2008 7:27 am

Hi ladyada...

I need some help in programming PWM to control a brushless DC motor.
I need to run the motor at a very low speed (low rpm). I am using the computer cooler fan that has an DC Brushless motor.

I need help regarding the programming part of the PWM. I am using ATtiny 2313 microcontroller.

help....!!!!
buddha
 
Posts: 2
Joined: Sat Mar 08, 2008 7:21 am

by magician13134 on Sat Mar 08, 2008 11:31 am

darus67 whipped up some awesome PWM code for a knight rider effect on the MiniPOV which uses the 2313. Here's the thread:
http://www.ladyada.net/forums/viewtopic.php?t=3982

That should be pretty easy to mod to fit your needs.
magician13134
 
Posts: 1119
Joined: Wed Jun 13, 2007 9:17 am
Location: Ann Arbor

by mtbf0 on Sat Mar 08, 2008 11:14 pm

hit the datasheet. you've got two timers, each with two pwm channels available. timer0 is an 8 bit timer with 7 modes available allowing 256 power levels. timer1 is a 16 bit timer with 15 modes offering 256, 512, 1024 or 65536 power levels.

the code below will give you pwm on pin PB2 at a frequency 1/256 the 2313's i/o clock with a 50% duty cycle. modify OCR0A to change the duty cycle.

Code: Select all | TOGGLE FULL SIZE
DDRB |= (1<<PB2);                              // make OC0A an output
TCCRB0 = 0;                                    // stop timer 0
TCCRA0 = (1<<WGM01)|(1<<WGM02);                // select fast pwm mode 3
TCCRA0 |= (1<<COM0A1);                         // clear OC0A on compare match
OCR0A = 0x7f;                                  // 50% duty cycle
TCCRB0 = (1<<CS00);                            // prescale = 1.  freq = clock / 256


WGM01 and WGM00 in TCCRA0 and WGM02 in TCCRB0 select the timer mode. COM01 and COM00 in TCCRA0 select the compare output mode. CS02, CS01 and CS00 select the clock prescale. selecting a prescale other than 0 starts the timer. none of which probably makes any sense, so rtfm, then connect PB2 to your h-bridge and watch your motor spin.
User avatar
mtbf0
 
Posts: 1645
Joined: Sat Nov 10, 2007 12:59 am
Location: oakland ca

by darus67 on Mon Mar 10, 2008 12:32 am

I'm not sure a DC brushless motor is going to like being driven with a PWM signal.
"He's just this guy. You know?"
darus67
 
Posts: 246
Joined: Wed Sep 26, 2007 10:25 pm
Location: Minnesota, USA

by mtbf0 on Mon Mar 10, 2008 6:39 am

darus67 wrote:I'm not sure a DC brushless motor is going to like being driven with a PWM signal.


typical. only read the part of the question i knew the answer to.
User avatar
mtbf0
 
Posts: 1645
Joined: Sat Nov 10, 2007 12:59 am
Location: oakland ca

by MrShifty on Wed May 21, 2008 11:53 pm

* eidt: woops, had the wrong mmcu argument in the compile options - it wasn't finding the io2313.h file >.< alas, the errors remain...


mtbf0 wrote:hit the datasheet. you've got two timers, each with two pwm channels available. timer0 is an 8 bit timer with 7 modes available allowing 256 power levels. timer1 is a 16 bit timer with 15 modes offering 256, 512, 1024 or 65536 power levels.

the code below will give you pwm on pin PB2 at a frequency 1/256 the 2313's i/o clock with a 50% duty cycle. modify OCR0A to change the duty cycle.

Code: Select all | TOGGLE FULL SIZE
DDRB |= (1<<PB2);                              // make OC0A an output
TCCRB0 = 0;                                    // stop timer 0
TCCRA0 = (1<<WGM01)|(1<<WGM02);                // select fast pwm mode 3
TCCRA0 |= (1<<COM0A1);                         // clear OC0A on compare match
OCR0A = 0x7f;                                  // 50% duty cycle
TCCRB0 = (1<<CS00);                            // prescale = 1.  freq = clock / 256


WGM01 and WGM00 in TCCRA0 and WGM02 in TCCRB0 select the timer mode. COM01 and COM00 in TCCRA0 select the compare output mode. CS02, CS01 and CS00 select the clock prescale. selecting a prescale other than 0 starts the timer. none of which probably makes any sense, so rtfm, then connect PB2 to your h-bridge and watch your motor spin.


That code won't compile for me using avr-gcc (on Linux). Here's the code:
Code: Select all | TOGGLE FULL SIZE
#include <avr/io.h>

int main(void)
{   

  DDRB |= (1<<PB2);                              // make OC0A an output
  TCCRB0 = 0;                                    // stop timer 0
  TCCRA0 = (1<<WGM01)|(1<<WGM02);                // select fast pwm mode 3
  TCCRA0 |= (1<<COM0A1);                         // clear OC0A on compare match
  OCR0A = 0x7f;                                  // 50% duty cycle
  TCCRB0 = (1<<CS00);                            // prescale = 1.  freq = clock / 256
 
  return 0;
}


and the errors:
Code: Select all | TOGGLE FULL SIZE
avr-gcc -I. -I/path/to/include -g -mmcu=at90s2313 -Os -fpack-struct -fshort-enums -funsigned-bitfields -funsigned-char -Wall -Wstrict-prototypes -Wa,-ahlms=test.lst -c test.c -o test.o
test.c: In function ‘main’:
test.c:7: error: ‘TCCRB0’ undeclared (first use in this function)
test.c:7: error: (Each undeclared identifier is reported only once
test.c:7: error: for each function it appears in.)
test.c:8: error: ‘TCCRA0’ undeclared (first use in this function)
test.c:8: error: ‘WGM01’ undeclared (first use in this function)
test.c:8: error: ‘WGM02’ undeclared (first use in this function)
test.c:9: error: ‘COM0A1’ undeclared (first use in this function)
test.c:10: error: ‘OCR0A’ undeclared (first use in this function)
make: *** [test.o] Error 1



Any help would be appreciated...
MrShifty
 
Posts: 24
Joined: Thu May 15, 2008 3:11 pm
Location: Las Cruces, New Mexico

by mtbf0 on Thu May 22, 2008 10:41 am

what processor are you using? the code is for a tiny2313. you tell the compiler you're using an at90s2313. not the same chip. more importantly the register names are different. strange as it may seem you can move timer code from a tiny2313 to a mega168, (assuming you're not using timer2, heh-heh), but not to a 90s2313.
User avatar
mtbf0
 
Posts: 1645
Joined: Sat Nov 10, 2007 12:59 am
Location: oakland ca

by MrShifty on Thu May 22, 2008 12:27 pm

I see. I'm using an Attiny2313; I dunno why I was using the at90s2313 mcu (probably because it was the first one listed that had 2313 in it >.>)

Anyway, I changed to the attiny2313 mmcu, but I still get the following errors:
Code: Select all | TOGGLE FULL SIZE
avr-gcc -I. -I/path/to/include -g -mmcu=attiny2313 -Os -fpack-struct -fshort-enums -funsigned-bitfields -funsigned-char -Wall -Wstrict-prototypes -Wa,-ahlms=test.lst -c test.c -o test.o
test.c: In function ‘main’:
test.c:7: error: ‘TCCRB0’ undeclared (first use in this function)
test.c:7: error: (Each undeclared identifier is reported only once
test.c:7: error: for each function it appears in.)
test.c:8: error: ‘TCCRA0’ undeclared (first use in this function)
make: *** [test.o] Error 1


changing all instances of "TCCRBO" and "TCCRA0" to "TCCR0B" and "TCCR0A" gets rid of the errors, but when loaded onto the microcontroller, it doesn't seem to work.

Code: Select all | TOGGLE FULL SIZE
#include <avr/io.h>

int main(void)
{   

  DDRB |= (1<<PB2);                              // make OC0A an output
  TCCR0B = 0;                                    // stop timer 0
  TCCR0A = (1<<WGM01)|(1<<WGM02);                // select fast pwm mode 3
  TCCR0A |= (1<<COM0A1);                         // clear OC0A on compare match
  OCR0A = 0x7f;                                  // 50% duty cycle
  TCCR0B = (1<<CS00);                            // prescale = 1.  freq = clock / 256
 
  return 0;
}
MrShifty
 
Posts: 24
Joined: Thu May 15, 2008 3:11 pm
Location: Las Cruces, New Mexico

by mtbf0 on Thu May 22, 2008 2:41 pm

ok, sorry for the crappy example code. i'm a seriously dyslexic typist. i don't know what you've got connected, but the following is more likely to actually make something happen.

Code: Select all | TOGGLE FULL SIZE
  TCCR0A |= (1<<COM0A0);                         // toggle OC0A on compare match
User avatar
mtbf0
 
Posts: 1645
Joined: Sat Nov 10, 2007 12:59 am
Location: oakland ca

by MrShifty on Thu May 22, 2008 5:58 pm

That change made the light light up :idea:, but the toggling kept it at 50% duty cycle. I hit the data sheet some more and came up with this:

Code: Select all | TOGGLE FULL SIZE
#include <avr/io.h>

int main(void)
{
  DDRB |= (1<<PB2);               // make OC0A an output
  TCCR0B = 0;                     // stop timer 0
  TCCR0A = (1<<WGM00)|(1<<WGM01); // select fast pwm mode 3
  TCCR0A |= (1<<COM0A1);          //Clear OC0A on Compare Match when up-counting. Set OC0A on Compare Match when down-counting.
  OCR0A = 0x0f;                   //duty cycle
  TCCR0B |= (1<<CS00);            // no prescaling
 
  return 0;
}


Thanks for a good starting point!
MrShifty
 
Posts: 24
Joined: Thu May 15, 2008 3:11 pm
Location: Las Cruces, New Mexico

by koolkat on Thu May 22, 2008 6:14 pm

MrShifty wrote:That change made the light light up :idea:, but the toggling kept it at 50% duty cycle. I hit the data sheet some more and came up with this:

Code: Select all | TOGGLE FULL SIZE
#include <avr/io.h>

int main(void)
{
  DDRB |= (1<<PB2);               // make OC0A an output
  TCCR0B = 0;                     // stop timer 0
  TCCR0A = (1<<WGM00)|(1<<WGM01); // select fast pwm mode 3
  TCCR0A |= (1<<COM0A1);          //Clear OC0A on Compare Match when up-counting. Set OC0A on Compare Match when down-counting.
  OCR0A = 0x0f;                   //duty cycle
  TCCR0B |= (1<<CS00);            // no prescaling
 
  return 0;
}


Thanks for a good starting point!

Thanks for the code...i compiles great...
I am building a line following robot and i need to cut the speed of the motors by 50 percent...how would i do that with your code?
koolkat
 
Posts: 358
Joined: Tue May 06, 2008 8:42 pm
Location: Missouri

by MrShifty on Thu May 22, 2008 6:28 pm

You would need enable the other output compare register for timer 0:
Code: Select all | TOGGLE FULL SIZE
DDRD |= (1<<PD5); //PD5's alternate function is OC0B


Then set up the "compare output mode" for that OCR the same as the other:
Code: Select all | TOGGLE FULL SIZE
TCCR0B |=  (1<<COM0B1);


Finally, give it a duty cycle
Code: Select all | TOGGLE FULL SIZE
OCR0B = 0x7f;  //0x7f = 127 or 50% duty cylce


To change the speed later, just change the value of the output compare registers
MrShifty
 
Posts: 24
Joined: Thu May 15, 2008 3:11 pm
Location: Las Cruces, New Mexico

by koolkat on Thu May 22, 2008 7:10 pm

How would i do that on this robot that is on this website?
Here is the link:
http://www.micahcarrick.com/05-27-2006/failurebot-line-following-robot.html

I would REALLY appreciate your help

Here is the code
Code: Select all | TOGGLE FULL SIZE
#define F_CPU 8000000UL         /* 8 MHz crystal clock */

/* defines for line sensors */
#define LED_PORT PORTD
#define LED_DDR DDRD
#define LED1 PD3                /* left-most sensor */
#define LED2 PD4
#define LED3 PD5
#define LED4 PD6
#define LED5 PD7                /* right-most sensor */

/* defines for motor control */
#define MOTOR_PORT PORTB
#define MOTOR_DDR DDRB
#define M1PULSE PB0
#define M1DIR PB1
#define M2PULSE PB2
#define M2DIR PB3
#define MOTOR_ENABLE PB4

#define MOTOR_FORWARD 0
#define MOTOR_REVERSE 1

#define GO_LEFT 0
#define GO_HARD_LEFT 1
#define GO_FORWARD 2
#define GO_HARD_RIGHT 3
#define GO_RIGHT 4
#define GO_BRAKE 5

/* defines for switch */
#define SW_PORT PORTD
#define SW_BIT PD2
#define SW_DELAY 250

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

/* function prototypes */
void init_io();
void init_adc();
void enable_motors();
void disable_motors();
void set_motor_direction(uint8_t motor, uint8_t direction);
void steer_robot(uint8_t direction);
uint8_t get_line_sensor_value(uint8_t led_index);
void calibrate_line_sensors(uint8_t *midpoint);
void delay_ms(uint16_t ms);

/* INT0 Interrupt Service Routine (ISR) */
ISR(INT0_vect)
{
        /* toggle motor enable and delay for de-bounce */
        MOTOR_PORT ^= _BV(MOTOR_ENABLE);
        delay_ms(SW_DELAY);
       
        /* clear pending interrupts */
        GIFR |= _BV (INTF0);
}

int
main (void)
{
       
        uint8_t adc_value;                      /* ADC value */
        uint8_t midpoint[5] = { 0,0,0,0,0 };    /* sensor trip points */
        uint8_t last_direction = GO_FORWARD;    /* last direction steered */
        uint8_t i;                              /* loop counter */
        uint8_t sensor_bits;                    /* sensor bit values */
       
        /* initializations */
        init_io();       
        init_adc();
       
        /* calibration */
        enable_motors();   
        steer_robot(GO_HARD_LEFT);
        calibrate_line_sensors(midpoint);
        disable_motors();       

        while (1)                       
        {
                /* clear previous value */
                sensor_bits = 0;
               
                /*
                create bit patter for sensors where a 1 represents the line
                under the sensor and a 0 represents no line
                */
                for (i=0; i<5; i++)
                {
                        sensor_bits = sensor_bits << 1;
                        adc_value = get_line_sensor_value(i);
                        if (adc_value >= midpoint[i])
                        {
                                sensor_bits |= _BV(0); 
                        }
                        else
                        {
                                sensor_bits &= ~_BV(0);
                        }
                }

                /* hexidecimal representations for various bit patterns of lines */ 
                           
                if (sensor_bits == 0x04 || sensor_bits == 0x0E || sensor_bits == 0x1F)
                {
                        steer_robot(GO_FORWARD);
                        last_direction = GO_FORWARD;
                }
                else if (sensor_bits == 0x0C || sensor_bits == 0x08)
                {
                        steer_robot(GO_LEFT);
                        last_direction = GO_LEFT;
                }
                else if (sensor_bits == 0x02 || sensor_bits == 0x06)
                {
                        steer_robot(GO_RIGHT);
                        last_direction = GO_RIGHT;
                }
                else if (sensor_bits == 0x10 || sensor_bits == 0x18 || sensor_bits == 0x1C)
                {
                        steer_robot(GO_HARD_LEFT);
                        last_direction = GO_HARD_LEFT;
                }
                else if (sensor_bits == 0x01 || sensor_bits == 0x03 || sensor_bits == 0x07)
                {
                        steer_robot(GO_HARD_RIGHT);
                        last_direction = GO_HARD_RIGHT;
                }
                else
                {
                        /*
                        If the robot does not see a line at all, it's possible that
                        the line is between sensors. Continue going the direction
                        previously determined until the line is found.
                        */
                        steer_robot(last_direction);
                }   
        }
        return (0);
}

void
init_io()
{
        /* setup input/outpus */
        LED_DDR = _BV(LED1) | _BV(LED2) | _BV(LED3) | _BV(LED4) | _BV(LED5);
        MOTOR_DDR = _BV(M1PULSE) | _BV(M1DIR) | _BV(M2PULSE) | _BV(M2DIR) | _BV(MOTOR_ENABLE);
       
        /* turn on internal pull-up resistor for the switch */
        SW_PORT |= _BV(SW_BIT);
       
        /* set INT0 interrupt enable bit */
        GICR |= _BV(INT0);
       
        /* enable interrupts */
        sei();
}

void
init_adc(void)
{
        ADMUX = 0 | _BV(ADLAR); // channel 0, left-justified result
        ADCSRA = _BV(ADEN) | _BV(ADPS2);
}

void
enable_motors()
{
        MOTOR_PORT |= _BV(MOTOR_ENABLE);        /* set ENABLE pin */
}

void
disable_motors()
{
        MOTOR_PORT &= ~_BV(MOTOR_ENABLE);       /* clear ENABLE pin */
}

void
set_motor_direction(uint8_t motor, uint8_t direction)
{
        if (direction == MOTOR_REVERSE)
        {
                MOTOR_PORT |= _BV(motor);       /* set DIR pin */
        }
        else
        {
                MOTOR_PORT &= ~_BV(motor);      /* clear DIR pin */
        }
}

void steer_robot(uint8_t direction)
{
        /*
        The gear motors have such a high reduction ration that I actually don't
        need to use PWM at this point in time. Therefore, this routine will simply
        set or clear the pulse pins depending on the desired direction of the motor.
        */
       
        switch (direction)
        {
                case GO_LEFT:
                        set_motor_direction(M1DIR, MOTOR_FORWARD);
                        set_motor_direction(M2DIR, MOTOR_FORWARD);
       
                        MOTOR_PORT &= ~_BV(M1PULSE);        /* clear M1PULSE pin */
                        MOTOR_PORT |= _BV(M2PULSE);         /* set M2PULSE pin */
                        break;
               
                case GO_HARD_LEFT:
                        set_motor_direction(M1DIR, MOTOR_REVERSE);
                        set_motor_direction(M2DIR, MOTOR_FORWARD);
       
                        MOTOR_PORT &= ~_BV(M1PULSE);        /* clear M1PULSE pin */
                        MOTOR_PORT |= _BV(M2PULSE);         /* set M2PULSE pin */
                        break;
               
                case GO_FORWARD:
                        set_motor_direction(M1DIR, MOTOR_FORWARD);
                        set_motor_direction(M2DIR, MOTOR_FORWARD);
       
                        MOTOR_PORT |= _BV(M1PULSE);         /* set M1PULSE pin */
                        MOTOR_PORT |= _BV(M2PULSE);         /* set M2PULSE pin */
                        break;
               
               
                case GO_HARD_RIGHT:
                        set_motor_direction(M1DIR, MOTOR_FORWARD);
                        set_motor_direction(M2DIR, MOTOR_REVERSE);
       
                        MOTOR_PORT |= _BV(M1PULSE);         /* set M1PULSE pin */
                        MOTOR_PORT &= ~_BV(M2PULSE);        /* clear M2PULSE pin */
                        break;
               
                case GO_RIGHT:
                        set_motor_direction(M1DIR, MOTOR_FORWARD);
                        set_motor_direction(M2DIR, MOTOR_FORWARD);
       
                        MOTOR_PORT |= _BV(M1PULSE);         /* set M1PULSE pin */
                        MOTOR_PORT &= ~_BV(M2PULSE);        /* clear M2PULSE pin */
                        break;
               
                case GO_BRAKE:
                        set_motor_direction(M1DIR, MOTOR_FORWARD);
                        set_motor_direction(M2DIR, MOTOR_FORWARD);
       
                        MOTOR_PORT &= ~_BV(M1PULSE);        /* clear M1PULSE pin */
                        MOTOR_PORT &= ~_BV(M2PULSE);        /* clear M2PULSE pin */
                        break;
                break;   
        }
}

uint8_t get_line_sensor_value(uint8_t led_index)
{
        uint8_t adc_value;
        uint8_t led_bit;
       
        /* convert led_index (for arrays) into it's bit value */
        switch (led_index)
        {
                case 0: led_bit = _BV(LED1); break;
                case 1: led_bit = _BV(LED2); break;
                case 2: led_bit = _BV(LED3); break;
                case 3: led_bit = _BV(LED4); break;
                case 4: led_bit = _BV(LED5); break;
        }
       
        /* turn on LED */
        LED_PORT &= ~led_bit;
        _delay_ms(5);
       
        /* read output from ADC */
        ADCSRA |= _BV(ADSC);
        while (!(ADCSRA & _BV(ADIF)));
        adc_value = ADCH;
        ADCSRA |= _BV(ADIF);
       
        /* turn off LED */
        LED_PORT |= led_bit;
       
        return adc_value;
}

void calibrate_line_sensors(uint8_t *midpoint)
{
        uint8_t adc_value;              /* ADC value */
        uint8_t i, j;                   /* loop counter */
        uint8_t thresh_high[5] = { 0,0,0,0,0 };
        uint8_t thresh_low[5] = { 255,255,255,255,255 };
                     
        for (i=0; i<100; i++)
        {
                for (j=0; j<5; j++)
                {
                        adc_value = get_line_sensor_value(j);
                        if (adc_value < thresh_low[j]) thresh_low[j] = adc_value;
                        if (adc_value > thresh_high[j]) thresh_high[j] = adc_value;
                }
                _delay_ms(250);
        }
       
        for (i=0; i<5; i++)
        {
                midpoint[i] = (thresh_low[i] + (thresh_high[i] - thresh_low[i]) / 2);
        }
 
}

/*
 * delay_ms - Perform long delays in approximate milliseconds.
*/
void
delay_ms(uint16_t ms)
{
        while ( ms )
        {
                _delay_ms(1);
                ms--;
        }
}
koolkat
 
Posts: 358
Joined: Tue May 06, 2008 8:42 pm
Location: Missouri

by MrShifty on Thu May 22, 2008 7:38 pm

Hmm... since I don't have that robot in front of me, I can't be sure =\

As written, that PWM code is only good for going one direction. A possible solution would be to have, for each motor, a circuit that takes two bits as input: an "on bit" (the one your PWM output would be connected to) and a "direction bit" (which would determine which direction the motor goes. Some extra circuitry could be constructed to switch which lead on the motor the "on" bit went to depending on the state of the "direction" bit. You could build it with discreet transistors, but a ready-made solution almost certainly exists on silicon somewhere...

If you don't want to reverse, then you would set up the clocks in the beginning and change the output compare register's whenever you needed to turn. For example:
(example assumes that left motor is wired to OC0A and right is wired to OC0B)
Code: Select all | TOGGLE FULL SIZE
void turnLeft()
{
  OCR0A = 0x00;
  OCR0B = 0x7f;
}

void goStraight()
{
  OCR0A = 0x7f;
  OCR0B = 0x7f;
}


*obviously, some #defines would make the above code more readable in context
MrShifty
 
Posts: 24
Joined: Thu May 15, 2008 3:11 pm
Location: Las Cruces, New Mexico

by mtbf0 on Fri May 23, 2008 8:18 am

first off, i guess you need to swap the direction and pulse pins for motor 1 to get the the pulse onto an output compare pin. beyond that, i don't have the mega8 datasheet handy and i have to go to work. just pick a pwm mode for timer1, get your prescalers and top configured, enable output compare, then start diddling the output compare registers to control the speed of your motors.
User avatar
mtbf0
 
Posts: 1645
Joined: Sat Nov 10, 2007 12:59 am
Location: oakland ca

Please be positive and constructive with your questions and comments.