Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

pwm basics

Status
Not open for further replies.
That code was done by someone else over a year ago. He will not respond to my e-mails about it so what you see is what you get. I'm just using it because it was all setup to go on the board I purchased, I figured I could just edit it and use it for my needs. It does work but if I am to use the same calculations you gave it would end up having a

As I understand the following set the prescale, postscale, RP2 registers. The following sets pre=16, post=16, PR2=255


Code:
// Timer2 prescale = 16
	T2CONbits.T2CKPS0 = 0;
	T2CONbits.T2CKPS1 = 1;
	
	// Timer2 postscale = 16
	T2CONbits.T2OUTPS0 = 1;
	T2CONbits.T2OUTPS1 = 1;
	T2CONbits.T2OUTPS2 = 1;
	T2CONbits.T2OUTPS3 = 1;

	// Timer period = maximum
	PR2 = 0xFF; 

	// Timer2 is on
	T2CONbits.TMR2ON = 1;

        // Enable Timer2 interrupt
        PIE1bits.TMR2IE = 1;

(200-nsecs * prescale 16 * postscale 16 * PR2 256) which comes out to 13.1-msecs. The code also only allows the 1-2ms pulse to be sent every 4 interrupts. How is he getting away with sending a pulse every 52.5ms. Does this seem right to you?
 
HakBot said:
The code also only allows the 1-2ms pulse to be sent every 4 interrupts. How is he getting away with sending a pulse every 52.5ms. Does this seem right to you?

The important part is the width of the pulse, and NOT the time between them, the only difference it will make is that the servo won't respond quite as fast (as it's got an extra 30mS to wait for the next pulse).
 
Nigel,

The important part is the width of the pulse, and NOT the time between them, the only difference it will make is that the servo won't respond quite as fast (as it's got an extra 30mS to wait for the next pulse).

So what you are saying is that I could send a 2ms pulse and wait for a few seconds and then send a 1ms pulse and it will still work just really slow? Wont the servo lose some of its holding power if the pulse is not sent every 20ms? Kind of like turning off the servo for 30ms?

Also, if the period can be greater than 20ms can it also be shorter (10ms for example)?
 
Last edited:
HakBot said:
Nigel,



So what you are saying is that I could send a 2ms pulse and wait for a few seconds and then send a 1ms pulse and it will still work just really slow? Wont the servo lose some of its holding power if the pulse is not sent every 20ms? Kind of like turning off the servo for 30ms?

Obviously you will reach the point where things start to fail, and it will probably vary from servo to servo - but certainly a couple of hundred percent increase is no problem.

Also, if the period can be greater than 20ms can it also be shorter (10ms for example)?

Yes you could, I suspect you don't know the reason for the 20mS frame time? - it's simply because servo's are intended for radio control, so the pulses are sent out sequentially - servo one, servo two, servo three etc. - this allows you 8 or so channels (2x8 = 16mS, plus some sort of start identification). The decoder in the receiver detects the start identification, and routes each individual pulse out to it's own servo. As the complete frame takes 20mS, each individual servo pulse repeats every 20mS.
 
In general, the PWM frequency for RC servos is way too low for PWM hardware. You will want no higher than 250 hz or so. It's going to be easier to use a timer and "roll your own". here's how I've done it:
Code:
define PWMperiod value for 50hz (or what ever you want)
define PMWDuty, init to timer value for pulse length
initialize the timer to start with PWMDuty
PWMPin is the output pin, initialize to 1 (pulse high)

TimerISR:
    if PWMPin == 1 then
        PWMPin = 0
        Timer = PWMPeriod - PWMDuty
    else
        PWMPin = 1
        Timer  = PWM_Duty
    endif

in your main line code, you set PWMDuty to what ever you want but keeping in mind the max and min pulse width values. Note, also that the actual timer value depends on the timer you are using (count up, count down, period reg, etc). also, you need to set your pre and post scalar values to get the proper timer length.

this approach uses almost no cpu time. Interrupt rate is 100/S and ISR time is tiny.
 
Last edited:
philba,

Im going to try something similar to what you implemented. Using a timer interrupt to signal when the 1-2ms pulse needs to be generated.

Nigel,

I used the following code to oscilate the servo:

Code:
    if (PIR1bits.TMR2IF) 			// if the timer interrupt bit is set...
	{
    	if (--count == 0) 			// create a pulse only on every 4th timer interrupt (to make the period longer)
		{
    		LATCbits.LATC2 = 1; 	// output high voltage to pin
			Delay100TCYx( delay ); 	// wait the desired pulse width
    		LATCbits.LATC2 = 0; 	// output 0 voltage to pin

        	count = 4; 				// reset the pulse counter

			// oscillate between max and min angular displacement of servo
       		delay += incr;
       		if (delay > 254) 
        		incr = -incr;
       		if (delay < 70) 
        		incr = -incr;
       }

       PIR1bits.TMR2IF = 0; 		// reset the interrupt bit
    }


The timer interrupts every 13ms. The pulse is generated every 4th interrupt which gives me a ~52ms period. I changed the code to generate the pulse on every interrupt which makes the period ~13ms. When I did this the servo did not oscilate, it jumped around and behaved very erradically. I suspect that since the pulse is generated 4 times as often that the serv is just being oscilated very quickly and it does not have enough time to smoothly turn. What do you think?
 
I don't do C, so I've no idea what your code is doing - but do you have a scope to check what's being sent to the servo?. Obviously if you're not moving the servo position then the waveform should be regular and not changing at all.

From what I can understand, it looks like you're trying to move the servo between two positions as fast as you can?, this is probably not a very normal operation for a servo.
 
From what I can understand, it looks like you're trying to move the servo between two positions as fast as you can?, this is probably not a very normal operation for a servo.

When the period was 52ms that code worked very well. Before you told me that a servo can take pulses at 10ms I thought that the period was my problem. I agree with your conclusion, I think the oscilations are just going too fast. I guess 52ms was just slow enough to allow the counters to run and turn the servo without problems.

I just wouldnt have thought 52ms was slow enough to make it oscilate with that code.
 
A servo is a mechanical device, it takes time to move, you're probably trying to move it far too fast - bear in mind it's supposed to move at a similar speed to your fingers moving a joystick (and NOT waggling it backwards and forwards as fast as you can!).
 
Nigel,

When the period is 52ms the servo seems to lose a lot of torque. Doesn't the 20ms period help to keep the holding power of the servo?
 
dude, use PR2 to set your period. Then use the ISR to switch it between the current pulse width and (PWMPeriod - the current pulse width). the pseudo code I posted is actually pretty close to what you want. just set PR2. you will need to calculate out what the PR2 value is.

I agree with nigel, don't change it too often. maybe once a second max. what I did for my first servo epxeriment was to read a pot with the adc and set the servo position to a scaled value of the pot. pretty cool the first time it worked. you could also use up/down buttons and move the servo in small increments per up/down.
 
jiayisux said:
Actually i get it but why haven't anyone of you implement CCPR1L and CCPR1CON? my project also ask me to get a 50 HZ PWM using P18F452 but i didn't know how ? any clue?

Because hardware PWM for servo is a very poor choice, you're looking for a 1mS change in 20mS, so can only have 50 descrete steps - which may be enough?, but it's easier to get more by doing it in software.

What has been discussed here before is using hardware PWM but changing it 'on the fly' using interrupts generated from the PWM - but this is more complicated.

If you're wanting normal 50Hz PWM, then this is pretty simple to do, just a question of programming the registers correctly - and for good accuracy, choosing a suitable clock frequency.
 
hi i kinda noob here

ok, so wat you actually mean 50 steps...now i seems to understand abit of what Hakbot did cause my project is really about the same as him...


i assume using 40 Mhz for my IC P18F452...now the problem is how do i implement to get a 50 Hz? using interrupt?


i calculated to get the least frequency = (1/40 *4*16*16*(PR2=195)= 4.99Hz... As what he did, i shall interrupt it for 10 times to get my 50 hz? i quite confused here..
 
jiayisux,

You might consider using the TMR1/CCP "compare" mode with interrupt to provide a rock-solid (no jitter) Servo output on the CCP1 pin as a simple background task.

With your 40-MHz oscillator (Tcyc = 100-nsec) and 16-bit "compare" registers you'll have to use a TMR1 prescale value of 4 to cover a 20-msec period. This could provide 400-nsec pulse width resolution but we should probably scale it by 2.5 for 2-usec resolution. Your MAIN program would simply stuff a "Pulse" variable with some value between 1000 and 2000 (microseconds) in 2-usec steps for 500 steps between 1.0-msecs and 2.0-msecs.

Your ISR code will simply alternate between setting the compare registers to either their current value plus (on-time) or their current value plus (period minus on-time) and setup the CCP1CON register to toggle the CCP1 pin as appropriate for the next compare match interrupt.

I'm relatively new at C so please forgive me if my C18 example ISR code isn't entirely correct. Hopefully you'll get the idea.

Code:
/*****************************************************************
 *  function prototypes                                          *
 *****************************************************************/

void main (void);
void isr_hi (void);

//
//  Pulse is some value between 400 and 2400 usecs in 2-usec steps
// 
//  our TMR1 prescaler is set to 4 with TMR1 ticks of 400-nsecs so
//  we must scale our Pulse and Period "usec" values by 2.5 before
//  using them in calculations used for updating the CCPR1L:CCPR1H 
//  compare registers.
//

static unsigned int Pulse =  1500;     // unscaled value (usecs)
static unsigned int Period = 20000*5/2; // scaled 20-msec period

/*****************************************************************
 *  setup interrupt vectors                                      *
 *****************************************************************/

#pragma code high_interrupt = 0x08

void high_interrupt (void)
{
  _asm goto isr_hi _endasm
}

#pragma code

/*****************************************************************
 *  ISR (high)                                                   *
 *****************************************************************/
#pragma interrupt isr_hi

void isr_hi ()
{
  if (PIR1bits.CCP1IF == 1)             // CCP1 interrupt?
  {
    PIR1bits.CCP1IF = 0;                // clear CCP1 interrupt flag

    if (CCP1CONbits.CCP1M0 == 0)        // if CCP1 is hi this cycle
    {                                   // tell CCP module to make
      CCP1CONbits.CCP1M0 = 1;           // CCP1 go lo on next match
                                        // setup next compare int
      CCPR1H += ((Pulse*5/2)/256);
      CCPR1L += ((Pulse*5/2)%256);
    }
    else                                // if CCP1 is lo this cycle
    {                                   // tell CCP module to make
      CCP1CONbits.CCP1M0 = 0;           // CCP1 go hi on next match
                                        // setup next compare int
      CCPR1H += ((Period-Pulse*5/2)/256);
      CCPR1L += ((Period-Pulse*5/2)%256);
    }
  }
}
Surprisingly simple, isn't it?

Regards, Mike
 
Last edited:
i don't understand, since my teacher ask me to do on PWm why use compare mode? i mean won't the result NOT be PWM instead?

your code is kinda confusing... explain more?


thnks u been a great help
 
jiayisux,

It will be difficult to help you if you're unwilling to put some energy and effort into learning "the basics".

Are you the user on the Microchip Forum going by the name of MPSIAO who just left there in a tantrum promising never to come back?

Mike
 
#include <p18f452.h>

unsigned int Pulse = 1500;
unsigned int Period = 20000*5/2;

void isr_hi (void);
void initialize(void);


void initialize(void)
{
TRISC=0b11111011;
T1CON=0b10100111;
CCP1CON=0b00000010;
INTCONbits.GIEH=0;
RCONbits.IPEN=1;
IPR1.TMR1IP=1;
PIE1.TMR1IE=1;
PIR1.TMR1IF=1;
INTCONbits.GIEH=1;
}

#pragma code high_interrupt = 0x08

void high_interrupt (void)
{
_asm goto isr_hi _endasm
}

#pragma code

#pragma interrupt isr_hi

void isr_hi ()
{
if (PIR1bits.CCP1IF == 1) // CCP1 interrupt?
{
PIR1bits.CCP1IF = 0; // clear CCP1 interrupt flag

if (CCP1CONbits.CCP1M0 == 0) // if CCP1 is hi this cycle
{ // tell CCP module to make
CCP1CONbits.CCP1M0 = 1; // CCP1 go lo on next match
// setup next compare int
CCPR1H += ((Pulse*5/2)/256);
CCPR1L += ((Pulse*5/2)%256);
}
else // if CCP1 is lo this cycle
{ // tell CCP module to make
CCP1CONbits.CCP1M0 = 0; // CCP1 go hi on next match
// setup next compare int
CCPR1H += ((Period-Pulse*5/2)/256);
CCPR1L += ((Period-Pulse*5/2)%256);
}
}
}

void main()
{
initialize();
}








I studied and i not really very familiar using timer1 cause most of the time i using timer 3... So please correct me if i am wrong... I am sorry for my rashness the other times... hope you all can feedback to me whether my project code is it correct? i kinda at wits end...sigh..


thnks again
 
Status
Not open for further replies.

Latest threads

New Articles From Microcontroller Tips

Back
Top