Orangutan PWM code for WinAVR

Folks:

I requested sample PWM code for the Orangutan well over a year ago, but judging from the lack of response and seeing similar requests from others, I conclude that Pololu engineers are unable to support their products with software. The hardware is nice, though!

So, users need to support themselves. I’ve spent some time puzzling over the documentation and design, which led to the following motor routines. The forward and reverse actions (brake vs. coast) are not the same because of the H-bridge and the Orangutan design, but the code seems to work OK. See notes below. To achieve the same PWM mode for forward and reverse, PB.1 and PB.2 will need to be disconnected from the output compare registers and handled separately in the code.

This code should work on the baby Orangutan (which I just purchased) but I haven’t tested it. The delay loops and PWM clock divider will need to be changed.

Corrections/suggestions would be appreciated. Note that posting removes tabs. Email me if you want the tabbed source.

Cheers, Jim

/*
** ORANGUTAN from pololu
** PWM for atmega8/LB1836M motor controller by Jim Remington
** sjames_remington _at_ yahoo.com
** WinAVR
*/
#include <avr/io.h>

/*
** Delays
*/
#define F_CPU 1000000UL
#include <util/delay.h>
#define	Delay( x )	_delay_loop_2( x )
// above, x is uint16

/*
** PWM_Init - Set up the pwm ports (PORT B1=IN1, B2=IN3, D5=IN2, D6=IN4)
*/
void	pwm_init( void ) 
{
	DDRB |= (1<<1) | (1<<2);
	DDRD |= (1<<5) | (1<<6);
    TCCR1A = 0xF1; // 8 bit pwm phase correct
    TCCR1B = 0x01; // clock/1. clock/8 works too (0x02).
    OCR1A = 0;
    OCR1B = 0;
}

/*
** pwm_set - Set the two PWM speeds.  Each ranges from -255 to 255
** assumes left motor = PB.2 and PD.6, right = PB.1 and PD.5. 
If direction of travel is wrong, switch motor leads (but see note below on asymmetry of PWM modes)

Truth Table for LB1836 controller from data sheet.
IN1/3	IN2/4	MODE
----	-----	----
1		0		forward
1		1		brake (both outputs low)
0		1		reverse
0		0		coast (both outputs off =high impedance?)
*/

void	pwm_set( int left_speed, int right_speed )
{
	
	if ( right_speed < 0 ) {	// If the right speed is negative
		PORTD |= _BV(5);  		//IN2=1
        TCCR1A |= _BV(COM1A0); 	//COM1A1 is 1, set COM1A0=1. Result: OC1A=IN1=0 on upcount, =1 on compare match
	    OCR1A = -right_speed;	//(this should be reverse on upcount, brake on compare match)
	} 
	else {						//if right speed is positive
		PORTD &= ~_BV(5); 		//IN2=0
		TCCR1A &= ~_BV(COM1A0); //COM1A1 is 1, set COM1A0=0. Result: OC1A=IN1=1 on upcount, =0 on compare match
	    OCR1A = right_speed;    //(this should be forward on upcount, coast on compare match)
	}

	if ( left_speed < 0 ) {	// If the left speed is negative
		PORTD |= _BV(6);  		//IN4=1
        TCCR1A |= _BV(COM1B0);  //COM1B1 is 1, set COM1B0=1. Result: OC1B=IN3=0 on upcount, =1 on compare match.
	    OCR1B = -left_speed;  	//(this should be reverse on upcount, brake on compare match)
    }
	else {						// If the left speed is positive
		PORTD &= ~_BV(6);  	//IN4=0			
        TCCR1A &= ~_BV(COM1B0); //COM1B1 is 1, set COM1B0=0. Result: OC1B=IN3=1 on upcount,=0 on compare match.
	    OCR1B = left_speed;		//(this should be forward on upcount, off on compare match)
	}
/* NOTE ASYMMETRY of forward and reverse PWM modes!
In preliminary tests with a mini-sumo rig using the LB1836 to directly drive two servo motors, 
little difference between "brake" and "coast" modes could be detected. 
However, reverse travel seemed to be a bit smoother and the motors responded more uniformly to identical
PWM setpoints. Need to test average current draw in the two modes
*/

}

/*
** MAIN. Exercise left and right motors.
** ramp up and down, forward and back
*/
int main(void)
{
	unsigned int i,j;
	pwm_init();
	while(1)
	{
	for(i=1;i<256;i++)
		{
		pwm_set(i,i);
		Delay(5000); //20000 cycles
		}
	for(i=255;i>0;i--)
		{
		pwm_set(i,i);
		Delay(5000);
		}
	for(i=1;i<256;i++)
		{
		pwm_set(-i,-i);
		Delay(5000); //20000 cycles
		}
	for(i=255;i>0;i--)
		{
		pwm_set(-i,-i);
		Delay(5000);
		}
	}
	return 0;
}
1 Like

Hello,

Thanks for your post; we will look at your code soon to give you more specific feedback. Unfortunately, the Orangutans are targeted at advanced users already familiar with AVRs, but we will be improving software examples, especially as we release the upcoming Orangutan X2 (https://www.pololu.com/products/pololu/0715/).

In case you haven’t seen it, there is a fairly complete Orangutan example available at http://www.idleloop.com/robotics/Orangutan/index.html.

- Jan

Hello,

We recommend that you handle the speed=0 case by turning off the PWM output since the outputs can have small glitches on them even with the period set to 0.

You might want to change to the 8 MHz operation (turn off the divide-by-8 fuse). Be careful not to set the oscillator setting to a nonexistent source, or you won’t be able to talk to your microcontroller any more. If you do increase the clock to 8 MHz, make sure you change the PWM clock source to at most 1/8 the main clock (TCCR1B = 0x02).

- Jan

Hi, Jan:

Thanks for the clarification on the target audience and support policy for Orangutan controllers. If you were to indicate this in the product description, some user frustration might be avoided.

The extensive C++ code example posted by Cathy Saxton at Idle Loop Robotics is elegant, but also overkill (user preferences in EEPROM?) and would not be recommended for beginners learning vanilla C.

I appreciate the suggestion for handling the case of speed=0 and plan to incorporate this in a completely different code example, to be posted shortly. I’ve investigated the difference in robot performance for the two choices of the motor “off” state during PWM, i.e. setting the H-bridge to “brake” versus “coast”, and find it to be nonintuitive and enlightening!

Regards, Jim

Hi
I’ve been trying to get the Motor Drive going on my Baby Orangutan.

I’ve got it running forward just fine, having tried a number of combinations of PWM modes and using Timer1 interrupt to manually toggle PB1. (I’m familiar with PWM, having built a Mega128 board with a traditional H-Bridge, and I’m using an oscilloscope so I can see the waveforms).

My problem is that I can’t seem to get the reverse direction working properly.

I’ve resorted to a simple code loop (Bascom) to try and eliminate possible sources of error, so I’m now down to

FORWARD:

$crystal = 20000000

Config Portb = $ff
Config Portd = $ff
Dim Temp As Integer

Do

Toggle Portd.1       'so I can see something happening

For Temp = 1 To 500

Set Portb.1
Reset Portd.5
Waitms 1

Reset Portb.1
Reset Portd.5
Waitms 5

Next Temp

Loop

And this works just fine, with 20% PWM duty cycle.

Now if I try to go in reverse as follows:

........
Do

Toggle Portd.1

For Temp = 1 To 500

Reset Portb.1
Set Portd.5
Waitms 1

Reset Portb.1
Reset Portd.5
Waitms 5

Next Temp

Loop

then the Motor Driver IC immediately superheats (far too hot to touch) and I assume goes into thermal shutdown.

What am I doing wrong?
Or have I somehow fried the bridge in my earlier attempts to get PWM mode going and driving the Brake Mode too much?

thanks
Andrew
Auckland NZ

Hello.

Your test looks good to me, so that indicates that the motor driver is probably shot. Do you have something connected to the motor driver when you run your test? You could go a step simpler and see if you can just get reverse to work (take out the PWM stuff).

You can also do the test on the other motor driver to see what that does. I don’t think you could break the motor driver just from manipulating its inputs; more common causes are static discharge and overvoltage.

Oh, and just in case you forgot to check, make sure one of your motor pins isn’t shorted to ground.

- Jan

Jan

thanks for the prompt reply. As soon as I saw your note about not shorting the motor pins, it twigged…

Motors now drive in both directions fine…as long as I don’t attach the 'scope! Of course this is effectively shorting one leg to ground… :blush:

I’ll follow up and post some sample code that runs PWM mode control (on/coast in both directions, no Brake mode) for Forward and Reverse.

thanks
Andrew

As noted above, now that I’ve solved the mystery of reverse drive, here is a bit of PWM code (tested :)) that might be useful for Bascom users of Baby Orangutan. This uses the PWM mode of Timer1 (for PB1 and PB2) and Timer0 (For PD5 and PD6).
This drives the Motors using only the Powered and Coast modes, not the Brake mode, so hopefully is power-efficient.

'** Orangutan 8 bit PWM Routines
$crystal = 20000000

Declare Sub Pwm_init()
                                      ' Set Up The Pwm Ports(port B1 = In1 , B2 = In3 , D5 = In2 , D6 = In4)

Declare Sub Pwm_set(byval Left_speed As Integer , Byval Right_speed As Integer)       
'Sets the two PWM speeds.  Each ranges from -255 to 255


Dim Speedstep As Integer                                    ' loop counter for testing only


Call Pwm_init

' this next section is test code only, to exercise the PWM

Do

For Speedstep = 0 To 255 Step 5                             ' both motors forward, speed up
   Toggle Portd.1
   Call Pwm_set(speedstep , Speedstep)
   Waitms 500
Next Speedstep

For Speedstep = 250 To 0 Step -5                            ' both motors forward, slow down
   Toggle Portd.1
   Call Pwm_set(speedstep , Speedstep)
   Waitms 500
Next Speedstep

For Speedstep = 0 To -255 Step -5                           ' both motors reverse, speed up
   Toggle Portd.1
   Call Pwm_set(speedstep , Speedstep)
   Waitms 500
Next Speedstep


For Speedstep = -250 To 0 Step 5                            ' both motors reverse, slow down
   Toggle Portd.1
   Call Pwm_set(speedstep , Speedstep)
   Waitms 500
Next Speedstep


Loop


End


'==================================================================================================================================
' SET UP THE PWM PORTS(PORT B1 = IN1 , B2 = IN3 , D5 = IN2 , D6 = IN4)
' set the Timers ready for PWM mode, both to 0FFh top Count (i.e. PWM 8 bit). Writing to OCxRx sets the Duty Cycle on the PWM
' Timer0 (PD5=OC0b and PD6=OC0a)
' Timer1 (PB1=OC1a and PB2=OC1b)
Sub Pwm_init()

Config Portb = &B00000110
Config Portd = &B01100000
 Tccr1a = &B00000001                                        ' 8 bit pwm phase correct
 Tccr1b = &B00000011                                        ' TCCR1B = 1 for clock/1.
                                                            ' TCCR1B=2 (clock/8) for faster CPU clocks
                                                            ' TCCR1B = 3 (clock/64)
                                                            ' TCCR1B = 4 (clock/256)

 Tccr0a = &B00000001                                        ' same as for Timer1
 Tccr0b = &B00000011

 ' turn off the triggers for now
 Ocr1ah = 0
 Ocr1al = 0
 Ocr1bh = 0
 Ocr1bl = 0
 Ocr0a = 0
 Ocr0b = 0


End Sub

'==========================================================================
'Set the two PWM speeds.  Each ranges from -255 to 255
Sub Pwm_set(byval Left_speed As Integer , Byval Right_speed As Integer)
'** assumes left motor = PB.2 and PD.6, right = PB.1 and PD.5.
'** Note: speeds > +/- 255 WILL LEAD to unexpected behavior!

'Truth Table for LB1836 controller from data sheet.
'IN1/3 IN2/4 MODE
'---- ----- ----
'1  0  forward
'1  1  brake (both outputs Hi), power hungry mode.
'0  1  reverse
'0  0  coast (both outputs off), power saving mode.
'
'So running forward, we set the In2 (PD5) direction to Lo, and Pulse the In1 line.
'this gives either Forward (when Pulse is Hi) or Coast when Pulse is Low
'
' IN1 is driven from the Timer1/OC1A
'
'When running in Reverse, we set the IN1 (PB1) to Low, and pulse the IN2 (PD5) Line
'This gives Reverse when Pulse is High and coast when Pulse is Lo
'
' IN2 (PD5) is driven from the Timer0/OC0B
'*/


 If Right_speed > 0 Then

   Portd.5 = 0                                              ' / / In2 = 0
   Tccr1a.com1a1 = 1
   Tccr1a.com1a0 = 0                                        '  //set COM1A1=1, COM1A0=0. Result: OC1A=IN1=1 on upcount, =0 on compare match
   Ocr1al = Right_speed                                     '  //(this should be forward on upcount, coast on compare match)
 End If


 If Right_speed = 0 Then

   Tccr1a.com1a1 = 0
   Tccr1a.com1a0 = 0                                        ' //disconnect PWM channel A from PORTB
   Portd.5 = 0                                              '  //IN2=0
   Portb.1 = 0
   Tccr0a.com0b1 = 0
   Tccr0a.com0b0 = 0


 End If
                                                ' //IN1=0 ; set coast mode (conserves power). Set both=1 for brake mode

 If Right_speed < 0 Then

   Portb.1 = 0                                              'set IN1 to Lo
   Tccr0a.com0b1 = 1
   Tccr0a.com0b0 = 0                                        '// and setup IN2 (PD5=0c0b) to pulse
   Ocr0b = -right_speed                                     '
End If


 If Left_speed > 0 Then

    Portd.6 = 0                                             '  //IN4=0
    Tccr1a.com1b1 = 1
    Tccr1a.com1b0 = 0                                       '  //set COM1B1=1, COM1B0=0. Result: OC1B=IN3=1 on upcount, =0 on compare match
    Ocr1bl = Left_speed                                     '  //(this should be forward on upcount, coast on compare match)
  End If

 If Left_speed = 0 Then

   Tccr1a.com1b1 = 0
   Tccr1a.com1b0 = 0                                        ' //disconnect PWM channel B from PORTB
   Portd.6 = 0                                              '//IN4=0
   Tccr0a.com0a1 = 0
   Tccr0a.com0a0 = 0
   Portb.2 = 0                                              ' //IN3=0 ; set coast mode (conserves power). Set both=1 for brake mode
 End If

 If Left_speed < 0 Then

   Portb.2 = 0                                              '    //IN3=1
   Tccr0a.com1a1 = 1                                        'In4(pd6 = 0c0a) Pulse
   Tccr0a.com1a0 = 0                                        '
   Ocr0a = -left_speed                                      '
 End If


End Sub

I trust that is useful to someone.

rgds
Andrew Wells
Auckland
New Zealand