08 - Achieving PWM in software


Depending on your requirements, and the hardware that you have to hand, then it may be that you don't have sufficient hardware PWM channels available. So what now? Well you could try replacing your microcontroller with a 'plug-in compatible' chip. The ATMega8 only has 3 PWM channels but can be replaced with an ATMega168 which has 6 PWM channels. That may, or may not, solve the issue.


But why would I want to do this versus using delays as done in the $50 robot?

The delay method is fine if you are only using a few servos. But if you are trying to drive 10 servos then each of those delays adds up. So if each servo is sent a position pulse and then you are pausing for 20ms then with 10 servos you are pausing for a total of 200ms every time around your main loop. Whilst the pauses are happening then the rest of your code cannot be doing anything. So each servo actually ends up getting the full delay of 200ms and will therefore be very jerky. This also means that you are also only reading your sensors every 200ms. In other words: the more servos you add then the slower, and more jerky, the whole robot becomes.


So to solve this issue we need to get rid of all of those delay loops so that the main program can run at full speed and so that each servo is 'refreshed' every 20ms no matter how many additional servos you connect (within reason!).


The solution is to use software interrupts to simulate PWM which, in theory, could be used to produce PWM on any general output pin on your microcontroller and thus provide a much larger number of PWM output channels. The big caveat is that this will never be quite as precise as doing it using hardware because it is more susceptible to being interrupted by other interrupt service routines you may have. However, if the devices you are driving are not time critical then this gives an acceptible solution - even if it allows you to use software for the non-time-critical devices thereby freeing up your precious hardware channels for the devices which are.


The basic technique is that each software PWM channel requires that you:

  1. Turn on the output pin for the PWM channel and
  2. queue up a 'toggle event' that will be run at a later date.
  3. When the time has elapsed you change the output pin to low and
  4. queue up a 'toggle event' that will be run at a later date.
  5. When the time has elapsed go to step 1

The sum of the two delays will dictate your PWM frequency, and the duration of the delay in step 2 will dictate the duty cycle. If we can perform all of this under interrupts then your main program can run without pauses and each servo should be refreshed every 20ms.


Of course the simple solution is to use my library WebbotLib.  See http://webbot.org.uk