Recently in Source code Category

This is the AVR Studio project and assembly language source code files for the latest version (v7.0) of my 64 channel serial servo controller.

This is the latest version of the ATMega168 version of the code which includes all of the new servo commands that I wrote about here including the multi-move command and the unit tests that I spoke of here. The controller allows you to set minimum, maximum and startup servo positions for each servo which can be saved into eeprom and used every time the controller is powered up. It also includes a programmable, per servo, "centre adjust" value which can be used to adjust for servos that are installed slightly off centre.

Source code is available here.


ATMega168 64 channel servo controller

| 2 Comments
This is the source code for the latest version of the 64 channel servo controller as detailed here.

This is an ATMega168 version of the controller that was originally developed for the ATtiny2313 but which was ported to the ATMega when I ran out of memory on the ATtiny.

The schematic required is similar to the one for the ATtiny2313, I'll produce a new one for the ATMega168 when I get some time. Note that we now use pins 0-3 of port B and port C rather than just 0-7 on port B, so the connections for mux chips 5 through 8 will come off of port C.

Source code is available here.

Tweaking the servo controller

| No Comments
The 64 channel serial servo controller that I've been developing works pretty well for me but most of my development and testing was done in the AVR studio simulator. Once I actually started working with my hardware again I noticed a slight problem. Although my servo controller was operating to spec my spec was wrong.

The problem is that the data sheet I have for the Hitec HS-422 servo states that it requires a pulse length of between 900us and 2.1ms with a centre position of 1.5ms. That's correct but it only gives a range of just over 90 degrees. The Pololu servo controller board that I've been using for my prototype work can send pulses of between 275us and 2.75ms which gives more range than I need but I've been using the servos with a range of 180 degrees with it. Further investigation showed that the HS-422 actually requires 600us to 2.4ms for 180 degrees.

Luckily the pulse generation code is pretty easy to adjust for this kind of range. Whilst we'd never be able to get the 275us to 2.75ms range that the Pololu controller manages due to the 64 channel (2.5ms cycle length) design, we can achieve a range of 611-2389 just by changing the multiplier we use from 6 to 7. Of course this also requires a change to the equate that provides our 1.5ms pulse for a control value of 127. The value changes from 738 to 611 for an 8.0MHz clock and from 620 to 493 for the 7.3728MHz clock that we're using.

If we were to switch to allowing the serial interface to program the pulse lengths directly as two byte values we could extend the range slightly (as long as we never go beyond 2.5ms at the top end or below the maximum time it takes to perform the PWM setup code at the bottom end), I may do this later, but for now the slightly reduced range is OK for me.

The other thing that could be improved is that the servos are spread across all of the MUX chips in such a manner that servo 1 is on pin 0 of MUX1, servo 2 is on pin 0 of MUX2, etc. This makes it difficult to wire up a board so that the servo connections are sequential on the jumper blocks and it makes it hard to use the controller with fewer than 8 MUX chips. 

A simple change to the PWM setup code can copy the data from the serial configuration buffer to the PWM generation buffer in such a way that the servos are numbered sequentially from MUX1 pin 0 to MUX8 pin 7. This means that servo 0 appears on MUX1 pin 0, servo 2 on MUX1 pin 1, etc. This means that supporting fewer channels can be achieved simply by reducing the number of MUX chips that you add to the circuit. 

Source code is here

A timer driven PWM servo controller - part 4

| No Comments
The time has finally come to put all of the code from the last three parts of this article together to form a complete serial configured, 64 channel, PWM servo controller for the ATTiny2313 and several CD74HCT238Es.

Here's a recap of where we are and how we got here:
  • In part 1 I put together some basic PWM generation code that uses the 16-bit Timer1 to generate a rock solid PWM pulse train from some pulse duration data.  
  • In part 2 I took the simple single byte per servo configuration data and converted it into the pulse duration data that part 1 required as input for the PWM generation code. 
  • In part 3 I wrote some simple polled serial communications and code that accepts the simple three byte 'SSC' control protocol and updates the single byte per servo configuration data that  part 2 takes as input.
The code from each of the previous parts was stand alone so that it was easier to develop and test. Now that I'm happy that all of that is working we need to integrate it. The integration is pretty straight forward, there are but two issues that we need to bear in mind.
  1. Parts 1 & 2 were written and tested with an 8.0MHz clock and Part 3 requires a 7.3728MHz clock so that we have a clock source that gives us 100% reliable serial communications at all baud rates. Part 2 doesn't have any clock dependent code in it but part 1 will need to be adjusted in several places to allow for the slower clock.
  2. The serial communication code uses some registers that are also used by parts 1 and 2. The timer interrupt handlers will need to deal with pushing and popping the shared registers on the stack so that we don't corrupt the serial code's state when the timer interrupts fire.

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...
Here's the source code to the 64 channel ATtiny2313 servo controller. Note that you'll need to use up to 8 CD74HCT238E, or equivalent, demultiplexor chips and that you can adjust the number of servos that you can control in steps of 8 using as many or as few CD74HCT238E chips as you want. If you only want 8 channels then you can do this without any demultiplexor chips; see here for source code to the 8 channel version.

The controller uses the 'standard' three byte serial "SSC" protocol of 0xFF <servo> <position>. Where <servo> is a value between 0 and 63 (or as many servos as you decide to support) and <position> is a value between 0 and 254 where 127 gives a pulse length of 1500us. At present the timing gives us pulses of 579.25us for 0 and 2420.75us for 254. This is a greater range than we really need (at least for the Hitec servos that I'm using) but the issue here is the number of instructions required to check each pin each time through the loop. A faster clock speed would allow us to perform the real work faster and add a delay into this loop to allow us to fine tune the range a little better... Perhaps... Anyway, I hope to move away from the current design shortly to one that gives a finer control over the pulse lengths.

Source code can be downloaded here.

The jitters and general instability of the hacked together simple servo controller (see here) for the ATtiny2313 were, it seems, down to the fact that the internal clock wasn't stable enough and this caused enough timing issues to throw the PWM off enough to jiggle the servo position around rather than hold it steady. This morning my external 4MHz crystals arrived and after changing the processor's fuse bits to accommodate the change in clock source everything was fine and the code started behaving itself.

The source code for the servo controller follows; as mentioned before, it's a) pretty simple and b) heavily based on the code that I found here for the AT90S4414. It may be better to adjust it to work at an RS232 baud rate friendly clock speed, but I expect I'll move away from the hard coded timing loops as soon as possible if I can and this might make such clock speed adjustments slightly easier.

The next step is to expand this to use one (or more) of the demultiplexor chips so that I can expand the number of channels supported. 

About this Archive

This page is an archive of recent entries in the Source code category.

Servos is the previous category.

Unit Testing is the next category.

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