2
\$\begingroup\$

I am trying to use a PIC18F4520 microcontroller to drive 8 servo motors. For this I need 8 PWM signals, but the PIC unit has only two built in PWM. So, I decided to use some normal I/O pins as PWM, and searched how to do that. I found some hardware based solution like using extra IC's or software based solution as using interrupt based PWM. But why can't I just use this code segment:

while(1)
{
PORTA.RA0=1;
delay_ms(10);
PORTA.RA0=0;
delay_ms(10);
}

The pin is high for 10ms and low for 10 ms.So the cycle period is 20ms. Shouldn't this create a 50Hz signal with 50% duty cycle? Or am I missing something?

\$\endgroup\$
  • 2
    \$\begingroup\$ ... Because busy loops prevent you from getting anything else done. \$\endgroup\$ – Ignacio Vazquez-Abrams Aug 28 '16 at 11:20
  • 1
    \$\begingroup\$ How would you control the other 7? \$\endgroup\$ – HandyHowie Aug 28 '16 at 11:22
  • \$\begingroup\$ But theoretically, would it work? Say I'm only using the microcontroller for this sole purpose. @IgnacioVazquez-Abrams \$\endgroup\$ – Nafis Aug 28 '16 at 11:23
  • \$\begingroup\$ The code segment is just an example, I'd definitely modify it for the whole project @HandyHowie \$\endgroup\$ – Nafis Aug 28 '16 at 11:25
  • \$\begingroup\$ If all 8 PWMs are the same clock frequency, use the PIC's TMR0 module to generate an interrupt. \$\endgroup\$ – Chu Aug 28 '16 at 13:00
11
\$\begingroup\$

The delay() function causes the PIC to loop and prevents any other activity other than interrupt triggered routines. In your example it will turn on RA0 for 10 ms, pause (preventing servicing any other output) and then turn off RA0 for 10 ms with the same inhibiting action. The pause() function should really be hidden from beginners as it encourages this kind of poor programming practice.

Instead you should run a base timer for your PWM outputs and do something like this pseudo code.

while(1) {
  timer += 1;                // increment the timer.
  if (timer > tmax) {
    timer = 0;               // reset
  }
  PORTA.RA0 = (timer < t0ontime);  // Output on until ontime exceeded.
  PORTA.RA1 = (timer < t1ontime);
  PORTA.RA2 = (timer < t2ontime);
  PORTA.RA3 = (timer < t3ontime);
  PORTA.RA4 = (timer < t4ontime);
  PORTA.RA5 = (timer < t5ontime);
  PORTA.RA6 = (timer < t6ontime);
  PORTA.RA7 = (timer < t7ontime);
}

It's been many years since I've used the PIC compiler so this may not work as written, but you get the idea. You should make the timer increment on a fixed timebase rather than the way I've shown it so that it won't vary if you change the chip oscillator, etc.

One other improvement you could make on this code is to avoid starting all the pulses at the same time. This would smooth out the demand on the power supply and may help reduce noise a little.

\$\endgroup\$
8
\$\begingroup\$

You have bigger problems than not enough hardware PWM outputs and your software attempt not working. You misunderstand the pulses such hobby servo motors require for control.

These units work only on the pulse length. Usually the 0-100% travel range results from 1 to 2 ms pulse width. Each pulse tells the controller in the unit where you want the motor position to be. This position command should usually be repeated every 20 ms or so for smooth operation if the commanded position is changing.

There are several ways to use hardware PWM to do this. The first obvious option is to get a PIC with more PWM outputs. Another is to multiplex the output of a single PWM generator. Use the CCP module in one-shot mode. First, switch the output multiplexor to a particular servo unit, generate the pulse for that unit, switch the output mutiplexor to the next servo unit, etc.

Since the entire travel range is encoded in 1 ms of pulse width, this is really something you want the hardware doing to get the resultion without jitter.

\$\endgroup\$
4
\$\begingroup\$

Shouldn't this create a 50Hz signal with 50% duty cycle? Or am I missing something?

It does (approximately). The problem is that RC servos require a pulse width of 1 to 2ms, not 10ms. The signals you should be generating look like this:-

enter image description here

The channel pulses can be output one after another, using a simple software delay timer if you don't need to do anything else at the same time. However since the total time for each 'frame' may vary from 8 to 16ms depending on the individual pulse widths, you need some way to bring it up to 20ms. One way is to add up all the pulse widths and subtract from 20ms to get the extra time delay required in the gap.

Another way is to set up a hardware timer that fires every 20ms. You could also have all this in an ISR so that the gap time is available for other 'background' processing which is not timing sensitive.

NOTE: the channel pulse widths need to be produced precisely with low jitter (<=2us), but the frame time doesn't. Most RC servos are designed to handle any frame time between 16ms and 22ms, and and an even longer time won't hurt (the servos will just move slower).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.