July 2009 Archives

A timer driven PWM servo controller - part 3

| No Comments
This is part three of a series of articles about the servo controller that I'm building for use in the hexapod robot that I intend to build. The first two articles in the series have presented the timer driven PWM generation code and the code used to take the configuration data that is managed by the serial port protocol and convert it into the data that is needed by the PWM generation code. Now we will develop some simple serial port handling code that will be used to allow maintenance of the configuration data that is used to generate the PWM signals.

Since the purpose of this design is to allow the servo controller to produce rock solid PWM signals without any interference from the serial protocol code the serial code will not use interrupts. This allows the timer driven PWM generation code to run with the highest priority and for it to always run when it needs to run. Using polling rather than interrupts for the serial port code also simplifies how the serial protocol is implemented. With an interrupt driven serial system not only could the processing of the serial receive interrupt delay the PWM generation timer interrupts but we'd also need to explicitly deal with buffering serial data that arrived whilst we were processing commands; if we had buffer space for 3 bytes of protocol data and we were busy processing the command that had just arrived we'd need to deal with any new serial data received somehow. With the polled approach we simply don't look for more serial data until we are ready to process it. The protocol itself can require that the computer at the other end of the serial link only sends one command at a time and waits for a response to the current command before sending the next. If the remote computer doesn't still to that protocol then we may end up with garbled commands but we wont need to worry about buffering pending commands...

Whilst the way we're separating out the serial and PWM generation code is overkill for the simple 3 byte SSC commands that we'll implement here it will become more important as we create a more complex command structure later on. The thing to realise is that it doesn't matter how long it takes us to process the serial protocol, the PWM generation will always be correct and adjusting the serial protocol code will not require us to tinker with the PWM generation code to ensure that the timing stays correct.

A timer driven PWM servo controller - part 2

| No Comments
In part1 of this timer driven PWM servo controller I built some code which uses Timer1 in the ATTiny2313 to generate 64 PWM signals. The code in part 1 worked from hard-coded dummy data. The code presented here shows how we can create the data that the PWM generation code needs to run. 

The timer driven PWM code presented here works in terms of two byte pulse durations and a 1 byte value that determines which pulses are still being generated. Thus each unique pulse duration is represented by three bytes of PWM control data. Finally we have a final duration that uses up any remaining time so that each batch of pulses takes 2.5ms to generate. This means that we need at most 9x 3 byte sets of control data. 

The serial I/O code which we've yet to write works with single byte servo control values between 0 and 254 with 127 being the centre position (a pulse of 1.5ms for Hitec servos). So the code we need to write needs to take 8 bytes of servo control data from the buffer that the serial code manages and process it in the following way:

  1. Copy the 1 byte control values from the serial data store into the PWM data store and associate a value that represents the pin that needs to be turned off to control this particular servo. We now have a series of eight two byte structures which consist of the 1 byte control value and the 1 byte pin value.
  2. Sort the two byte structures into ascending order based on the value of the 1 byte control value. 
  3. Merge the pin vales of duplicate control values, so, for example, if we have a value of 127 for pin 1 and a value of 127 for pin 2 we would end up with a single two byte structure that represents a value of 127 for pins 1 and 2. This leaves us with between 1 and 8 two byte control structures.
  4. Merge the pin values of the earlier structures with those of the later structures; the idea being that if the first servo to be switched off is on pin 1 and it switches off at a control value of 100 and the next servo to switch off is on pin 2 and it switches off at 105 then the pin control value for the second data structure should be turning off pins 1 and 2. As each pulse duration is processed more pulses are stopped and once a pin has been switched off it stays switched off.
  5. Make the control values relative to each other rather than absolute. If the first control value is 100, the second is 105 and the third is 107 then we need to change the second to be 5 and the third to be 2.
  6. Expand the control values into two byte pulse durations in us. To do this we multiply the single byte control values by 6 to get a range of pulse durations from 0-1524.
  7. Add an initial base value to the first pulse such that a control value of 127, which gives a pulse duration of 762 is adjusted to give a pulse duration of 1500. For our 8MHz clock this means that we need to add 1500-762 = 738 to the first pulse duration.
  8. Calculate the duration of the longest pulse, we do this by looping over the series of pulse durations and adding them up. 
  9. Calculate a final timeout to take the longest pulse duration to 2.5ms. Simply subtract the longest pulse duration that we calculated in 8 from 2500.
  10. Adjust all of the durations to allow for the time that the interrupt handler takes to get from being called to the point where the pulses are actually turned off. In our case we subtract 1 from all of the durations. 
The result is a series of between 1 and 8 three byte structures, each with 2 bytes of pulse duration (stored in high byte, low byte order) and one byte of accumulated pins that remain on. Following this (immediately after the last structure that is in use for pulse control) we have a further three byte structure with the final duration and a sentinel value of 0xFF. This is exactly the data that is needed for the timer driven PWM servo controller that I presented in part 1. The code that can be downloaded from here is just the data calculation code. It contains dummy test data that is sorted, merged and adjusted and with appropriate break points you can watch the progress of the data conversion. All of this code takes the place of the dummy data load in the PWM setup handler from part 1.

A timer driven PWM servo controller

| No Comments
As I mentioned recently, the original servo controller firmware that I posted was flawed and wasn't suitable to use as a base for the more complex servo control code that I want to add to the controller. The new firmware follows the design that I spoke of here and relies on the ATTiny's 16-bit timer to generate our PWM signals. Since we're using a timer we don't need to waste processor time with hard-coded timing loops full of 'nop's as we did in the original firmware and this gives us much more time to do useful work. Since the code is quite complex (and also not quite finished yet!) I'm going to present it in stages. First the initial shell of a timer driven PWM generator, next the calculations and data munging required to transform the control data from what we receive via the serial protocol to what we need for the PWM signal generation and finally the serial protocol. Once all of that is done I'll begin to extend the firmware beyond the basic commands so that I can, perhaps, at last switch back to working with my prototype legs.

But first, a bit of a refresher. The aim of the code is to generate multiple PWM signals using an ATTiny2313. The signals should be suitable for controlling hobby servo motors like the Hitec HS-422 servo which means that the pulses should be adjustable from 900us to 2.1ms and repeated at a rate of 50Hz (20 times per second). The ATTiny2313 can generate PWM on its own, but for a hexapod robot I need at least 18 channels for the leg servos. The ATTiny2313 has 8 I/O pins on PORTB and 5 on PORTD that we can use but this is nowhere near enough to generate 18 signals with one pin per signal... Using 3-8 line demultiplexor chips (such as the CD74HCT238E it's possible to generate 64 signals from 8 signal generation pins and 3 address control pins. 64 signals is more than I need, but... there's a certain symmetry in it. To generate 64 signals we need to split the signals into 8 batches of 8. Each batch of signals should be generated 20 times per second so each batch of signals can take no longer than 2.5ms to generate. The second version of the simple firmware, presented here, uses 8  CD74HCT238E chips and lots of timing loops to generate 64 signals; but it only just manages it and, as the timing loops burn lots of processor time, we can't make the servo controller that complex... Ideally I'd like to add commands to move servos incrementally over time to a final position, stop them wherever they are right now and report back their current location, move them in batches (so that we can say we want servo 1 in position 128, servo 2 in position 220 and servo 3 in position 1 and for them all to arrive in their final position at the same time.). These more complex commands take processor time to calculate the required servo data and with the simple controller firmware we don't have that processor time available. Switching to a timer based approach gives us back the processor time that we are currently wasting in the timing loops and makes the PWM pulse train generation code the highest priority code; thus removing any chance that the serial I/O code and complex command processing could cause jitters in the PWM pulse train.

And now, on with the code...

Redesigning the servo controller firmware

| No Comments
As I mentioned here, there's a fundamental design problem with the two versions of the ATTiny2313 servo controller firmware that I've presented so far (see the 8 channel source code and the 64 channel source code). The timing that determines the shape of the PWM signals that are generated relies on carefully crafted timing loops and the time taken by particular code sequences and this is affected by the interrupt driven serial I/O that is used to control the controller. Each time a command comes in on the serial port the PWM generation code is interrupted by the serial code and the timing of the generated pulses is affected; albeit slightly.

My proposed redesign moves the PWM generation code off to a timer interrupt and has the serial  I/O dealt with by the main loop of the program. This means that the PWM generation code always runs at a higher priority than the serial I/O code and the resulting pulse train should be rock solid. This will become more and more important as the code required to deal with the serial commands becomes more complex as more complex commands are added.

I've decided to split the work into two smaller projects. The first will deal with generating the PWM pulse train from the timer interrupt. The second will deal with the serial port processing code that will configure the data that is used to generate the PWM signals. Yesterday I started on the timer code... 

The structure of the new code is considerably different to the old code; this isn't too surprising. The old code operates on 8 banks of 8 channels. The signals for each bank of 8 channels are generated at the same time and generating the signals for 8 channels takes < 2.5ms. Generating the signals for all 8 banks takes around 20ms so that each signal is repeated 50 times per second. Ideally, each signal should be between 900us and 2.1ms long and the old code initially starts producing a bank of signals by setting all 8 channels on and then wasting time in a timer loop. We then enter a loop which executes 255 times and which checks the servo control data and turns off each signal as the corresponding control value is reached (see the 'PWM mark' section of the source code, here, for the detail). Due to the number of operations that we need to execute, the number of times that we need to run the loop and the speed of the processor's clock we end up with a range of possible pulse lengths between 579.25us and 2420.75us with a mid point of 1500us.

The new code also operates on 8 banks of 8 channels and still generates the 8 signals in < 2.5ms so that all 8 banks can be processed in 20ms to give the required 50hz refresh rate for each signal. However that's pretty much where the similarity ends. The new code does all of its work in response to timer interrupts. There are two distinct actions that occur during these timer interrupts. The first is the setup phase which is performed during the initial 900us 'on time' that all signals in all banks have. Once setup has completed we enter the 'pulse stop' phase during which the timer is used to tell us when to stop each signal. Once all signals have been stopped we wait for the setup phase for the next bank of channels. Rather than wasting time with a timer loop the first thing that we do once we've switched all channels on during the setup phase for a bank of channels is to set the timer to go off in 900us time and then to start calculating the off times for each of the channels and sorting them into ascending order. The result is a list of up to 8 sets of times and channel data; up to 8 rather than exactly 8 because multiple channels may stop at the same time and so we only need one time for all channels that stop at the same time Once the setup phase is complete we extend the timer, if need be, so that it will next fire at the first 'off time' for the first channel, or bunch of channels. The next time the timer fires we will be in signal termination mode. This mode is simple, we update the output ports to turn off the signals that are no longer being generated and set the timer to the next 'off time'. Once all signals are off we set the time one last time and switch back to setup mode. 

At least that's the plan. I have about half of the code working and the interrupt driven nature of the code means that I have much more time to do things than I did with the polled version. This is good as it means that there's time available to be processing serial configuration changes, etc. The most time that we gain from this new method is the big block of time that we were originally wasting in a timer loop; now we only use what we need to use during the setup phase of the signal generation and the rest of it is free for us to use for serial I/O handling.

About this Archive

This page is an archive of entries from July 2009 listed from newest to oldest.

June 2009 is the previous archive.

August 2009 is the next archive.

Find recent content on the main index or look in the archives to find all content.