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?

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

3 Answers 3

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.

answered Aug 28, 2016 at 11:33
\$\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.

answered Aug 28, 2016 at 13:51
\$\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).

answered Aug 28, 2016 at 19:54
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.