August 2009 Archives

Prototype fabrication

| No Comments
As I mentioned back in April after I'd put together my first prototype leg, "the prototype leg has some fairly major failings from a mechanical point of view". Ideally the real legs will be designed and cut from sheet material, either plastic or metal. Getting to that point is going to take some time though and right now I haven't been able to locate a UK based laser cutting service at an affordable price. Anyway, before I get to the point where I can start fabricating the real legs I thought it would be useful to get myself some materials to enable me to prototype the robot's chassis and legs. The Meccano Special Edition Mechanical Workshop set seems to have enough parts for me to get started on bolting together some brackets for the servos and, hopefully moving the mechanical side of things along a little. We'll see. Perhaps I'll just sit around making the models instead :)

And then switch to the ATMega168...

| No Comments
The new functionality in my servo control takes more memory to implement due to the more complex state that we need to maintain for each servo. The simple controller could support up to 64 PWM channels on the ATTiny2313, the more advanced controller can, at the present stage of the design, support around 15. There's scope to optimise the storage of the expanded 2 byte pulse duration values; they're 12 bit values and so will fit in 1.5 bytes, but even that will only save me 5.5 bytes. The increased complexity of the serial commands also requires more buffer space to hold the complete command before processing. All of these pressures on the limited memory space of the ATTiny2313 lead me to believe that I need a device with more memory to implement the 18 channel servo controller which is my minimum requirement for the hexapod robot.

I had thought that it would be a relatively painless move to move from the ATTiny2313 to the ATMega168; the 168 has ample memory and its instruction set possesses a multiplication instruction which, in itself, would save us 5 bytes of stack space if we can remove our own implementation. However apart from the issue of needing to adjust the code to run on the 168 (the concept of 'extended I/O registers' means that we can't use out and in for some operations and need to use more complex instructions, and the increased memory size has shown up some issues in how I was looping over ranges of memory (when looping a pointer, such as X, it seems easier to loop until X is something (testing XH and XL) rather than looping and decrementing a counter register (which is fine as long as the loop size fits in a single register))), the main pain, seems to be the fact that although the ATMega168 has more I/O pins than the ATTiny2313 the arrangement of them is less ideal for my design.

The ATTiny2313 has 3 output ports; A, B and D. A has 3 pins, D has 7 and B 8. Some of these pins are needed for the external clock and UART RX/TX lines but the whole 8 pins of port B are available to us and we make use of them to control the 8 channels of PWM that we generate at a time. It's nice an easy as we have a single out instruction on port B that can control any of the pins that we're using to generate PWM in one go.  

The ATMega168 also has 3 output ports, B, C and D. B and D have 8 pins and C has 7 pins. Overall an increase of 5 pins but, unfortuantely for us the location of the externally required pins has changed such that PB6 ad PB7 are required for the external clock, PC6 is required for external reset and PD0 and PD1 are needed for the UART's RX and TX lines. This means that we don't have a whole 8 pins on a single port for use with our PWM generation so the generation code will need to become more complex. I'll need to use some pins on port B and some on port C, probably 4 on each so that the bit twiddling that needs to be done to separate the control data into two bytes for the two out instructions is relatively simple.

An alternative approach might be to use ATTiny2313's without the CD74HCT238Es and simply generate 8 channels per controller and chain multiple controllers together via a common control bus. Unfortunately, due to the fact that the communication between servo controllers and the controlling computer is bi-directional (the controllers echo commands and respond asynchronously to the completion of incremental moves) the serial channel that we're currently using isn't really going to work. Whilst we could chain multiple controllers on a serial line if the communications is purely from computer to controller (by having controllers ignore commands that weren't for them) we can't do that when the controllers can talk back. It may be possible to use something like I2C or to use the USI in either 2 or 3 wire mode; however, it looks like the pins required for doing this also clash with port B and so would require the redesign that we'd be doing if we simply switched to the ATMega168 and continued to use the UART.

For now I think I'll move the code towards the ATMega168 and then, perhaps, back port an 8 channel version to the ATTiny2313. 

Registers, stack usage and timing

| No Comments
So far the servo controller development has been reasonably straight forward; once various design issues were considered and once I'd got my head around building the hardware and learning AVR assembly language. However I expect that my assembly code has many novice mistakes and that it's probably not especially idiomatic. I haven't been writing in the language long enough and I haven't read enough of other people's code for it to be otherwise.

Adding more functionality to the servo controller and extending the 'second' thread of execution, the serial protocol code has started to expose weaknesses in the design. The timer code uses lots of registers; it was the easy way to make the code a bit more readable. Rather than simply reusing the same registers I have named the various registers with reasonably accurate names and hoped to give the code a little more meaning. Unfortunately the register set is limited and the more I use in the timer code the more complex it makes the transition between the serial code and the interrupting timer code. Every register that is used in both threads of execution needs to be pushed onto the stack when the interrupt occurs. This uses precious SRAM for our stack as we need to reserve space for the largest stack that we can use and we then can't use that space to support additional servos. It also takes time at the start of the interrupt handler and that time is something that is more important to us that the space used by the stack.

The idea of using the same prologue and epilogue code for the timer interrupt no matter which state we were in; setup or pulse switch off, is good from a 'simple design' point of view and it's a good way to make sure that we don't forget to adjust the code in one place whilst adjusting it in another but it means that the setup code, which uses lots of registers, inflicts its stack requirements on the pulse switch off code which uses fewer registers. The more work that the pulse switch off code does before it gets to the point where the pulses are switched off the greater the minimum pulse length we support will be. 

Whilst stubbing out some of the new serial code and adjusting the stack operations to protect the shared registers I not only managed to reduce the number of servos that the ATTiny2313 can support from 18 down to 15 due to stack space requirements but I also managed to take longer than the minimum pulse length of 6 ticks. This caused a very obvious failure as, during the interrupt handling for one pulse, the timer was reset to a duration of 6 yet the timer was already at a point beyond 6 and so the timer ran for the whole 16-bit duration before going off... 

I expect my redesign will separate the stack handling for the setup and switch off interrupts and attempt to reduce the number of registers that the setup phase uses. We'll see. I think the code for this project has just moved beyond 'toy example' status...  

Asynchronous serial responses

| No Comments
Part of the new functionality that I'm developing for the serial servo controller for my hexapod robot is for the servo controller to take its time in moving a servo from where it is at the moment to where I want it to be. The idea being that the servo controller can worry about the required pacing of the servo moves and we can 'step' from the current position to the required position automatically within the servo controller. Since we're stepping rather than moving directly to the new position in one go we can stop the servo controller mid move if we detect an obstruction.  So, rather than saying to the controller "move servo 0 from position 0 to position 127". We say "move servo 0 from position 0 to position 127 in 1 step increments". We can then say "no! stop now wherever you are we've hit an obstruction!".

The incremental move command echoes back the command when the command is accepted by the servo controller and then reports back asynchronously when the move completes. Until the move completes we can always fire a 'stop that servo' command into the servo controller and it will stop at the point it has reached. The interesting part of all of this is that the servo controller can send back messages to the controlling computer when it decides to. The question is, how do we do this.

At first it may be tempting to have the PWM code send the asynchronous response. After all that's the code that knows when a movement sequence has completed so it's easiest for that code to send the response back to the controlling computer. Unfortunately that way lies garbled serial communications. As I mentioned last time, we have two threads of execution; the serial code which runs as the main loop of the program and the PWM code which runs as an interrupt. The problem with having our PWM code send the asynchronous response is similar to our data access synchronisation issue. Since our PWM code interrupts our serial code the serial code could be doing anything when the PWM code runs. The worst possible thing that the serial code could be doing if our PWM code was responsible for asynchronous serial messages is sending a serial message itself.

Imagine we've just sent a command to the serial code and it has processed it and is echoing the command back to the controlling computer. The serial code sends the first byte of the command echo and then the timer interrupt fires. The PWM code runs and an incremental move completes so the PWM code sends an asynchronous serial message, it then returns. The serial code continues where it left off and completes its command echo. Unfortunately the  controlling computer gets a garbled message which consists of the first byte of the command echo followed by the asynchronous message from the PWM code and finally the rest of the command echo. 

To avoid this situation we will have the serial code as the only code that can access the UART and send data to the controlling computer. Now the PWM code needs to communicate the fact that an incremental move has completed to the serial code which can then send the asynchronous message. We can do this by having the PWM code set a register to a specific value when a incremental move has completed and for the serial code to include a test of this register into its serial read loop. If the value is set then the serial code can scan the serial control data, work out which servo has completed its move and send the appropriate message to the controlling computer. 

So, how does the serial code know which servo has completed its movement and how does it only send a response once? A servo that has completed a movement will have an actual position which equals its target position and it will have a non zero 'move every' value. We can check through the servos for servos that meet this criteria, send the message and set the 'move every' value to zero. The serial code can then clear the signalling register. 

Allowing the serial code to clear the register opens up a potential hole. Ideally only the PWM code would set and clear the register that it uses to communicate with the serial code. It would set the register when moves were completed during a PWM setup phase and clear the register if it completed all of the setup phases for all banks without completing any moves. Unfortunately this could leave the register set for 20ms and that would cause the serial code to 'spin' uselessly looking for completions that don't exist. By allowing the serial code to clear the register after it has finished sending the notifications for the completions that are currently available we open up the possibility that the serial code might miss some notifications. Imagine that the serial code has started to process completion notifications, it does this by looping over all of the control data, sending completion notifications and updating the control data. If the serial code is some way through this loop and it gets interrupted the PWM could complete some more moves that are earlier in the control data than the where the serial code is currently located. The PWM code then updates the register to say that there are new completions but when the serial code completes its loop it clears the register and will not then be signalled to process the new completions. One solution for this is for the serial code to clear the register before it begins its loop through the control data, that way it will loop again if the PWM code completes move moves. 

That's the theory anyway...
One of the problems of moving from the simple three byte SSC style control system to a system where we can do clever things with multiple servos at once is that the data required to do these clever things is bigger than the data required to do the simple things. See here for the details of the new control data structure. The problem with data that is bigger is reading or writing it atomically (that is, ensuring that our multi-byte data structures are in a consistent state at all times). Why do we need to worry about atomic writes? Interrupts. My day job is writing multi-threaded C++ server code. I'm used to having to worry about protecting sections data from concurrent access by multiple threads of execution. I'm used to using locks to make sure that multi-byte structures, are updated together, atomically. I'm used to seeing the problems that can occur when this isn't done correctly. So, the first thing that worried me about changing the serial protocol to something more complex was that I had two threads of execution accessing the data structures that would be required and that suddenly these data structures were more than single byte values. 

The first thread of execution is the serial protocol code that runs in the main loop of the servo controller program. This will need to read and write the new data structures. The second thread of execution is the code that runs as part of the PWM setup routine from the Timer1 interrupt. This can start running when the serial code is at any point in its execution, it literally interrupts. The problem is that the PWM code may interrupt whilst the serial code is in the middle of an update to the configuration data for a servo and the PWM code may then read data that is half the old data and half the new. We don't have that problem at the moment as the control data is a single byte and the single byte can be written and read atomically so there's no way that the interrupting code can try and read data whilst it's being updated.

I expect a picture or two might help...

The existing servo control data consists of a single byte per servo and that byte can be written to by the serial protocol code (in blue) and read from by the PWM setup code (in red). Arrows into the box are writes to the data and arrows out are reads from it.

ExistingServoControlData.pngThe new servo control data consists of a 5 byte structure and different parts of this can be read to and written by either the serial protocol code or the PWM setup code. 
NewServoControlData.png
The problem is that the 'target position', 'step size' and 'step when' values should only be used when all of them are either 'new' or 'old' but due to how the timer code that reads these values can interrupt the serial protocol code that writes them we have a bit of a problem. Or we do unless we design the code to carefully avoid the problems.

The standard solution to this kind of thing in my day job is to use some form of lock to protect the data access. The update thread would lock the lock before it starts updating and unlock it after it was done. The reading thread would lock the lock before it starts reading and unlock it when it was done. If the lock was locked when a thread wanted to lock it then the thread would wait until the lock has been unlocked. This explicit synchronisation is common and useful but it's not possible for us here. The situation we have here is subtly different to the synchronisation issues that I face from day to day. Here only the PWM code can interrupt the serial code. Normally, when dealing with real threads, any thread can interrupt any other thread, we're lucky in that we can guarantee that the serial code will never interrupt the PWM code. The most obvious fix for this kind of problem would be to turn off interrupts for the critical period of time where we are updating the control values. This would work but, right now at least, I don't want to delay the PWM code at all and turning off interrupts would do that, albeit only slightly. The approach that we will take instead is to make sure that we can update the shared data in such a way that the code that accesses it responds to changes in single byte values, rather than requiring the complete structure to be consistent at all times. This is harder to verify as you have to work harder to understand the code and the consequences than if you were simply checking that there was a cli, sei pair around each set of atomic updates; for now though this is the approach we'll take.

The simplest of the new serial protocol commands that we're adding to the servo controller is the stop command; but even this simple command needs some care to implement in the presence of interrupts. A stop could essentially set the target position to the actual position. However if we were to do that then we might find that the servo oscillates rather than stopping smoothly. The problem is that to update the target position to the current actual position we need to read the actual position and then write the value to the target position. If the PWM code interrupts the serial code after the read but before the write then by the time the serial code comes to write the new target position the actual position has changed. The next time the PWM code decides to step the target towards the actual it will move the servo again. The larger the 'step size' the bigger the potential oscillation.

It's far better to implement the stop command as follows. First we update the 'step when' value to 0. We can structure the PWM code so that this causes it to stop trying to move the actual position towards the target position. If 'step when' is zero then the PWM code should just use the value of 'actual position' for the servo's position, no matter what the other control values say. Once the atomic update of the 'step when' value is complete the serial code can safely read the actual position as it will not be changed by the PWM code. It can then update the target position. However, since the a zero value for 'step when' effectively means that the servo remains static in its current position we could leave the values for 'target position' and 'step size' unchanged during a stop.

Using this 'atomic stop' method we can now implement everything else in a similar style. Before updating either the 'target position' or the 'step size' we should always update the 'step when' value to 0. Once we have completed our updates we can then update the 'step when' value to something else if need be. 

Extending the servo controller

| No Comments
To be able to implement the new commands for the servo controller I need to adjust the data that we use to control the servos. Right now we have a single byte per servo and that byte contains a value between 0 and 254 which represents the length of the pulse sent to the servo. To be able to implement the delayed or progressive move command we'll need to know the current servo position and the desired servo position. We'll also need to know how often we move the current position towards the desired position and by how much we move it. 

So, for example, if we're currently at position 0 and we want to move to position 150 and we want to do over a period of 3 seconds we could move at a rate of 1 every cycle; that is one step of one every 20ms. Each time the PWM signal for this servo is refreshed we would increment the actual position value until we reach the desired position.

As soon as we change the step rate from 1 we need another byte of control data to keep track of when we should step next. For example, for less fluid movement we might want to move from 0 to 150 in steps of 5. If we wanted to do that in the same time as before, i.e. over a period of 3 seconds, we would need to step every 5 cycles (10 times a second). So we'd set a desired position of 150, a step size of 5 and a step rate of 5. Every five times the PWM signal is refreshed we would increment the actual position by 5 until we reach the desired position. To keep track of when to increment we'd have a step count value which we'd initially set to the step rate then then decrement each time we refresh until it reaches zero at which point we'd increment the actual position and set the step count back to 5.

All of the planned new servo commands can be implemented in terms of these control values. The problem is that we now need 5 bytes of control data per servo and that restricts the number of servos that we can support with an ATTiny2313 to 18; which is just about enough...I expect that I'll implement these features on the ATTiny2313 and then switch to the ATMega168 if I need more channels or more functionality.

What's next?

| No Comments
I now have an easy to extend PWM servo controller and the next job on my list of things to do is extend it so that it supports the functionality that I feel I need for correct control of my hexapod's legs. As I mentioned here, I'd like to be able to tell the servo controller to move a group of servos to a particular set of positions so that they all arrive at the same time and, the movement is incremental and the movement can be stopped at any point in case a leg sensor detects an obstruction. 

Configuring a group of servos in one go requires several other things to be in place before it can work and so my first changes to the serial protocol will be as follows:

  1. Add the ability to send a move command with a 'delay' which will cause the move to take some time to complete. Sending a 'delayed move' of 127 to a servo that is currently in position 0 will cause it to step from 0 towards 127 at a rate determined by the delay specified. Once the servo reaches its final position the servo controller will send an asynchronous serial message back to the controlling computer to inform it that the move is complete.
  2. Add the ability to report on the current location of a servo. 
  3. Add the ability to stop a servo in the current position, this is only really relevant to a servo that is being moved with a 'delayed move'. The idea being that if we send a command to move to 127 and the servo starts at 0 but a leg sensor detects an obstruction we can send a stop command to the servo and it will stop where it is and report the current location.
Working with servo groups then becomes a case of adding additional commands which take multiple servo number and control value pairs and which calculate the movements required so that all of the servos in the group arrive at their destinations at the same time.

The additional commands will break the current SSC compatible command structure and will also make the serial protocol more complex as we'll be dealing with commands with different byte lengths and also with the servo controller sending unsolicited responses to the controlling computer (for 'move complete' notifications).  

I've already got scribbled notes on how all this will work so it's just really a case of implementing it. Hopefully the current code is a good base to develop these changes on.

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
Here is a schematic for a 24 channel version of the ATTiny2313 servo controller. You can expand the number of channels up to the full 64 by adding additional CD74HCT238Es where each additional MUX chip is connected to the next available pin on port b.

ATTiny2313-24ChannelServoController.png
The schematic, in Eagle format, is here: ATTiny2313-24ChannelServoController.sch
and a potential board layout is here: ATTiny2313-24ChannelServoController.brd

These were produced with Eagle and I don't think I could have worked out how to use Eagle without reading Build Your Own Printed Circuit Board by Al Williams. A great book for demystifying Eagle.

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.

About this Archive

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

July 2009 is the previous archive.

September 2009 is the next archive.

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