Results tagged “embedded assembly programming”

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.


Testing backwards

It's taken me almost a month but I'm finally back to working on integrating the multiple servo move command into the rest of the code. Well, the integration was done long ago, unfortunately the debugging was the bit that was taking up my time.

I decided that putting 'printf' style debug output into the routine to attempt to debug it from my PC based control software was just the wrong way to go about finding the problems and so I set off on a mission to finally get some unit testing into my code. This worked out well and I now have over 80 tests for all of the serial command code. I found a few subtle bugs and I'm in a much better position for reactoring away some duplicate code and other design smells. I also feel much more confident about making the other wide ranging changes that I will eventually make when I switch to using interrupt driven serial comms and move to a 16bit control value for the PWM signals...

So now I have to write the tests for the code that I know has some bugs in it... The problem I have with the code under test is that it's quite big. In fact it's quite a bit bigger, and more complex, than all of the other serial protocol routines that I've tested. The initial tests are easy enough, parameter validation etc, but the main tests involve me testing a large block of code that appears to have at least one 'oh dear I've gone off into an infinite loop' bug in it somewhere under some input data conditions... At first I thought I would have to break the code down into smaller functions that were easier to test and then I realised that I can already test the code in smaller sections, just as long as I do so backwards.

The code is one long function that we jump into and which jumps back out to the command accumulation loop. Throughout the code there are various labels that break up the various sections of the code. If I structure my tests so that I test the code from the end back towards the front I can simply set up the environment with the data that each stage expects and then jump to a label that processes the data. So, if the code takes input data at A and then processes it via B, C, D, E and F, I can first test pushing the kind of data that E should produce by jumping to F. Once that's works I can test E with the data that D would produce, etc... Once I get to A the whole thing is tested...

We'll see how it goes...

Relative branch out of reach

The test code for the serial command processing code for my serial servo controller is turning out to be the largest piece of assembly language that I've written. This means that all of a sudden I'm coming across "Relative branch out of reach" errors during the compile. I've got to a point where every time I add a test I have shuffled the code to such an extent that several relative branches need adjusting from rjmp to jmp or rcall to call. Not a great problem but, of course, jmp and call take up more space so the first round of adjustments often triggers a second round, etc. It seems that once you get to the point where your code needs jmp and call its size can just explode due to the fact that you now need to use jmp and call...

Ah well, it's not a problem really... I'm now at 66 tests and 59% of my code space used up. I've got pretty complete coverage up to the "stop servos" command. I have the query commands to test and then I'm finally on to the multiple servo move command... Then I have the PWM code to test, but I expect I'll take a break from the testing for a while once I get the multiple servo move command to actually work!

Testing, Testing...

The AVR assembly language unit tests that I spoke of last week are going well. I decided to explore the idea of unit testing by writing tests for the easier to test aspects of the serial protocol code and then, as this went well, I decided to write tests for the serial protocol code in order rather that simply jumping to write tests for the code that I know is broken. I figure that I'm more likely to write the tests that I actually need (i.e. for all of the code) this way, rather than simply writing the tests that I think I need.

So far this is going well. The test harness program is now considerably bigger than the real servo controller program. I have around 50 tests at present and I'm around half way through the serial protocol code. I've found a couple of otherwise hard to find bugs; which is good. As usual the tests act as documentation for how the code is used and what the inputs and outputs and side effects are. This doesn't remove the need for actual documentation but it helps.
As I mentioned yesterday the servo controller project has got to the point where being able to unit test the code would be useful to me. In my day job as a C++ server developer I've been using unit tests for several years and most of the code that I write is written in a Test Driven Development style. This works well for me and was one of the first things that I missed when I started to develop in AVR assembler in AVR Studio. At the time I didn't know enough about how I would structure my code, or even how I'd write the code at all, and testing, though obviously missing, was something I managed to do without.

Now that my code has got pretty large I've begun to split it into separate asm files. These are broken down by purpose, in effect I'm creating different modules of functionality. Due to the way that AVR Studio works with assembler projects these files are then included into a master file which is then assembled. Since my code is already broken down into modules, if I'm careful with how I split the code up I've found that I can build separate test projects which include one block of real functionality from the servo controller project and which then include test harness code that provides the functionality that the real code needs to build; for example, I have a function called SerialEchoCommand which will echo a command back to the serial port, this is used once the command's arguments have been validated. By placing this function in one file and the code that uses it in another I can build a test which can replace SerialEchoCommand with a function that the test harness can use to determine if the real code would have done the right thing if it were linked with the real implementation of SerialEchoCommand. In testing terms this is called mocking. I've provided a mock implementation of an interface that the code under test uses and the test can interrogate the mock to determine if the code under test is behaving as expected. With most testing the challenge is to separate the code that you want to test from the code that you don't want to test, or the code that is too hard to manipulate within the test.

Once I realised that I could use relatively standard mocking techniques to separate the code under test from the underlying hardware it became obvious that I could build a test harness that would run in the AVR Studio Simulator and that could test various parts of my servo controller. Unit testing was within reach.

I've spent a few hours adding some tests to some of the simpler parts of the serial protocol handling code and it's going well. I seem to have a structure that works and so far it's been easy to provide data for the function under test and then to test that it produces the correct outcome and uses the correct services in the expected way. The next release of the servo controller source code will include my test harnesses.

I expect an example will help. Especially if you're not used to testing!

Suppose we have a function called SerialProcessCommandSetServoMinPosn which is called from the code that accumulates and executes serial commands and who's job it is to take a servo index and a position and to update the servo configuration data so that the supplied position is the minimum position that the servo can be moved to. This function might look something like this:

SerialProcessCommandSetServoMinPosn : 

    ld servoIndex, X+

    cpi servoIndex, NUM_SERVOS                  ; check the servo index is valid
    brlt PC+2
    rjmp SerialServoOutOfRange

    ld temp1, X                                 ; load new min posn

    rcall SerialSelectServoData

    adiw XL, MAX_POS_OFFSET

    ld temp2, X                                 ; read existing max position

    cp temp2, temp1                             ; new min must be less than 
    brsh PC+2                                   ; or equal to exisiting max
    rjmp SerialPosnOutOfRange 

    sbiw XL, MAX_POS_OFFSET
    adiw XL, MIN_POS_OFFSET

    st X, temp1
        
    rcall SerialEchoCommand
    
    rjmp SerialStart
This is generally how all of the serial command processing code is structured. The call into us is a rjmp from the serial command dispatch code. We validate our parameters, report errors or echo our command back to the guy on the end of the serial port and then either jump back to the serial data accumulation code or execute the command and then jump back to the serial data accumulation code.

It's probably clear from the code above that to be able to test it we need to set X to be pointing to some valid data; outside of the test this would be pointing into the serial data accumulation buffer at the point just after the command code that tells the dispatcher that this is the 'set min' command. In our test X can point anywhere that has two bytes of data available, our test harness will set this up and set the contents of the buffer that X is pointing to so that it contains a servo index and a position. Our test also needs to provide implementations of SerialServoOutOfRange, SerialPosnOutOfRange, SerialEchoCommand and SerialStart. As long as these labels exist in a different file we can replace the real code with mocks for our test simply by including the mock code rather than the real code. We'll use the real implemetation of SerialSelectServoData as that function is responsible for taking a servo index and pointing X to the right place in the servo position data. It looks like this:

SerialSelectServoData :

    push temp2

    ldi XL, LOW(POSITION_DATA_START)       
    ldi XH, HIGH(POSITION_DATA_START)

    ldi temp2, BYTES_PER_SERVO
    
    mul servoIndex, temp2

    add XL, resl
    adc XH, resh

    pop temp2

    ret
Our test just needs to provide a valid position data buffer (i.e. an equate that sets POSITION_DATA_START to something sensible) with some known values in it. 

The test might look something like this:

TestSerialProcessCommandSetServoMinPosn :

    ldi temp1, 15
    mov testIndex, temp1
    
    rcall InitialiseSerialOutputBuffer

    rcall InitialisePositionDataToKnownValues

    ldi XL, LOW(TEST_SERIAL_INPUT_BUFFER)     
    ldi XH, HIGH(TEST_SERIAL_INPUT_BUFFER)

    ldi temp1, 0                            
    st X+, temp1                            ; servo index to change

    ldi temp1, 20                            
    st X, temp1                             ; new min value

    ldi XL, LOW(TEST_SERIAL_INPUT_BUFFER)   ; reset X   
    ldi XH, HIGH(TEST_SERIAL_INPUT_BUFFER)

    rjmp SerialProcessCommandSetServoMinPosn

    rjmp TestsFailed
For now we can ignore the testIndex register. Here we set up an output buffer for our implementation of SerialEchoCommand and initialise POSITION_DATA_START and the data in the buffer to sensible values. We then set up the serial input buffer to contain a servo index and a position value and set X to point to the servo index in the buffer. This is how the serial dispatch code would leave the X pointer after examining the previous byte in the real serial accumulation buffer and switching on it depending on which command code it represents. We then jump to the code under test and, hopefully, never return. In case we DO return we then end the test by jumping to the TestsFailed label. 

The TestsFailed label is one place where the test code will end up after tests have been run. The other is the TestsSucceeded label. Both simply consist of a jump to themselves. By setting break points on each of these jumps we can run the tests and discover if there are any failures.

This serial code is slightly harder to test than it could be because it isn't structured as functions which return to their caller. Instead we jump into the functions and they jump back to the serial command accumulation loop when they're done. This makes error handling easier; failures result in the code at the level of the failure reporting the error back to the serial port and then jumping straight back to accumulate a new command. As such all of the code under test will eventually jump to SerialStart. To be able to determine if the test passed we need to be able to examine what the code under test did whilst it was running. This is where the testIndex register comes in. The mocked out code for SerialStart contains a jump table that jumps based on the contents of the testIndex register. Each test has a corresponding 'check results' function and the SerialStart code jumps to the check results code associated with the test that's currently running. 

TestSerialProcessCommandSetServoMinPosnCheckResults might look something like this:

TestSerialProcessCommandSetServoMinPosnCheckResults : 

    ; The call should leave X pointing at the config value that we have changed...

    cpi XL, LOW(POSITION_DATA_START + MIN_POS_OFFSET)
    breq PC+2
    rjmp TestsFailed

    cpi XH, HIGH(POSITION_DATA_START + MIN_POS_OFFSET)
    breq PC+2
    rjmp TestsFailed

    ld temp1, X                     ; validate that we changed the value we wanted to change
    cpi temp1, 20                   ; to the correct value
    breq PC+2
    rjmp TestsFailed        

    clr temp1                       ; reset the value to its starting value 
    st X, temp1

    rcall ValidatePositionDataIsUnchanged   ; and then make sure nothing else was changed

    ; now validate that the correct mock functions were called...

    ldi XL, LOW(TEST_SERIAL_OUTPUT_BUFFER)     
    ldi XH, HIGH(TEST_SERIAL_OUTPUT_BUFFER)

    ld temp1, X+
    cpi temp1, 1            ; there should have been 1 call
    breq PC+2
    rjmp TestsFailed

    ld temp1, X+
    cpi temp1, 0xFF         ; echo command
    breq PC+2
    rjmp TestsFailed

    inc testResult
    ret
Note that we can check that the X pointer has been left where we expect it to end up and that the data that should have been manipulated has been changed as expected. We can then check that the correct mock functions were called. In this case we check that one function, SerialEchoCommand, was called. The implementation of this could be something like this:

SerialEchoCommand : 

    ldi serialChar, 0xFF

    rcall SendSerial
    
    ret
Where SendSerial might be implemented like this:

SendSerial :
    push XL                                     ; save the registers that we use
    push XH
    push temp1
    push temp2

    ldi XL, LOW(TEST_SERIAL_OUTPUT_BUFFER)     
    ldi XH, HIGH(TEST_SERIAL_OUTPUT_BUFFER)

    ld temp1, X                                 ; load the number of bytes currently stored in the serial
    clr temp2                                   ; output buffer.
                                    
    inc temp1                                   ; increment the number of bytes as the offset from the
                                                ; start of the buffer is one greater than the number of bytes
                                                ; as the buffer also holds the count itself at offset 0
    add XL, temp1
    adc XH, temp2

    st X, serialChar                            ; store the data that would be written to the serial port in
                                                ; out buffer for later analysis in the test

    ldi XL, LOW(TEST_SERIAL_OUTPUT_BUFFER) 
    ldi XH, HIGH(TEST_SERIAL_OUTPUT_BUFFER)

    st X, temp1                                 ; save the number of bytes stored, note that we incremented
                                                ; this value above

    pop temp2                                   ; clean up the stack
    pop temp1
    pop XH
    pop XL

    ret
This makes it easy to check for data that the functions under test might write directly to the serial port, using SendSerial, or other mock functions that they may call, such as SerialEchoCommand. There's no need to test the functionality of SerialEchoCommand here, we can test that separately, so it's adequate that it simply writes a single well known token into the test output buffer.

Of course, things get more complex when we're testing for correct handling of invalid values (i.e. if we pass in a servo index that's too big, or if we try and set a min posn that's out of range) but most of the tests required can be built on the same framework.

Most functions, even PWM generating timer interrupt code, can be tested in a similar way. The complex part is always getting the granularity of the code packaging correct so that you can mock the appropriate layers. This often causes several simple functions, or macros, to be required rather than direct hardware access but it's often a good design decision to abstract these hardware access points away anyway. I've found in the past that allowing the tests to lead your design where appropriate (as is the way with TDD) usually results in a better design!

Now, off to fix those bugs in the multi-move command... 

Unit testing AVR assembly language

Way back at the beginning of this journey I mentioned the fact that I'd quite like to be able to use some of the development disciplines that I use in my day job during the development of the firmware for my hexapod. Now that I've actually written some non trivial assembly language for the AVR I find that I'm missing not having my usual unit tests to support my ongoing development and refactoring. This has been most noticeable just recently during the integration of the most complicated serial command of the new servo controller; the multiple move command.

I'm currently working on separating out the code that processes serial command messages from the code underlying support code. The idea being that I can then build the serial command processing code into a new AVR Studio project and 'mock' out the support code so that I can test the serial processing code. Once that's done I should be able to write a suite of tests that can be run on the serial code to make sure that the commands do what they're supposed to do. I'm lucky that the serial command code generally either calls other functions (which I can mock out by creating a testing version of the function and including the source for that rather than the source for the real function) or modifies data (which I can examine as part of the test to ensure that it has been modified in the correct way). 

So far it's going quite well. More once I have it working well enough to document and even more once I then use these new tests to fish out the bugs in the multiple move command!


After the servo controller

The work on turning my excel spreadsheet into AVR assembler code which can move multiple servos to arrive at their target locations at the same time is proceeding well. I have the required code operating in a stand alone environment in the simulator and all I need to do now is merge that in with the rest of the code... Once that's done my servo controller is complete and whilst I already know that there are at least two further versions in the pipeline I expect I'll move onto something more before working on them. 

This weekend I sketched out some ideas for the next programming phase; the servo sequencer or 'gait controller'. This will initially be written in C++ and run on a PC but the design sketches that I did this weekend were for the microprocessor version. The plan is that the servo sequencer is a state machine which can be triggered off of servo move completions and timers. The idea being that it can be programmed to know about a sequence of moves (such as all of the moves required to move a 6 legged robot in a forward direction) and then be told to execute those moves. The sequencer would then manage this and other processors would deal with sensors and other decisions (such as exactly where the feet should land or exactly how the body should be angled, etc.). The sequencer can be isolated from these things a little by allowing the other controllers to tell it the detail (i.e. where exactly each three servos for each leg should be) but the sequencer itself would know that it moves this leg after that leg etc. Step length, step depth and body orientation can then be adjusted by tuning parameters that the overall sequence of steps uses without changing the state machine that makes the middle left leg move after the front right, or whatever.

My current ideas for the microprocessor version of the sequencer are quite ambitious; but then that's the point really, these micro projects should be pushing me forward and shouldn't be easy. The sequencer, since it's essentially a programmable state machine, needs to be able to allocate manipulate and free various blocks of memory; so it seems that I'll be building a dynamic memory allocator at some point. It will use interrupt driven serial I/O; and once that's done that work can be used for the next version of the servo controller. It will need to speak to the PC so that a PC program can be used to controller the sequencer (until the next level of robot controller is conceived and then designed) and it needs to talk to the servo controller to actually move the legs (this may be two UARTS; potentially meaning I have to create the second without hardware support and bit-bang the comms out, or perhaps serial to the PC and TWI/I2C to the servo controller). The sequencer's state machine is driven by the completion of delayed moves from the servo controller; so we can kick of a sequence by actioning the first state and then wait for the completion to trigger the state transition. Additionally a state transition can set a timer which can later trigger a new state transition. The programmable timer code will need to be developed... 

Deliberately the sequencer will not be limited to controlling blocks of 3 servos. This wont be a hexapod sequencer but a generic servo sequencer that will work with the servo controller that I'm currently finishing. So, theoretically when I want to develop l'arachnid once l'hexapod is complete I can 'simply' switch to using 8 x 4 servo legs and the same firmware...

However, the fact that I've now got to the point where I've almost completed a 'non-trivial' assembler project means that I feel I'm able to move the hardware side of the project forward. So I expect that once the servo controller is complete and whilst the sequencer is being designed I'll try and get to a point where I actually have the required number of servos and a 6 legged robot to control...

Almost there....

Work on the latest version of the serial servo controller is going well. I'd accumulated a pile of random nice to have ideas, some of which then necessitated some other ideas, and then there was the one must have command (the one which moves several servos to new positions over time and ensures that they all arrive at their final resting places at the same time). Of course I've worked on some of the nice to haves rather than dealing with the final 'must have' command that I need before I can move on to writing some gait controlling software to move multiple legs in sequence...

The current software includes the ability to turn off the PWM generation completely and to turn it on again; this is needed so that the 'save settings to eeprom' code can work as that requires that we don't have interrupts enabled. I've added the ability to set minimum, maximum and initial positions for all servos. These values can be saved to eeprom so the controller remembers them. I've also added the ability to adjust the 'centre' position for a servo; you can say that a particular servo is centred at 100 for example, rather than 127 and then talk to it as if you're moving relative to a 127 centre and the controller will adjust accordingly. There are also three controller configuration options that can adjusted via serial commands and saved to eeprom. The first determines if the PWM generation is turned on after a reboot. If not then you have to turn it on yourself with the appropriate command. The next determines if the controller sends out a 'get info' response when it starts after a reset (I figure that this will be useful for downstream processors, if the servo controller is reset these other processors might need to know!). Finally there's a setting which determines what happens if you try and move a servo beyond the min or max position. By default such a command is considered to be in error and you get an error response. You can change this so that such a command is OK but the command echo indicates that although you tried to move to, say, 201 the limit is 200... I've also added a command which resets the controller; mainly for testing at the moment. The get info command for this version returns more information so that you can tell what the state of the various programmable options are.

Now, I really should get on with the 'move multiple and arrive together' command...

Serial communications issues

I've just spent a while tracking down a but which ended up being in my PC based control software rather than in the serial servo controller firmware.

The symptoms of the problem were that my servo controller would suddenly to process random, poorly formed commands. My control software was sending commands correctly but it wasn't waiting for a command echo from the servo controller before sending the next command. Due to the design of the controller (i.e. the fact that we don't use interrupts for the serial communication code) if new commands are sent whilst the controller is processing the current command and before it has echoed it then serial data might be lost. This throws the serial command processor out of sync and hence my problems.

Changing the PC control software to wait for command responses before sending new commands has solved the immediate issue but I think I need to have a bit of a rethink about the serial communication design. Right now commands such as the various stop and query commands all do more work after they've echoed the command back. Theoretically the PC software could send a new command at that point and this new command might get garbled due to the fact that the servo controller is still processing the previous command and is not processing new inbound serial data. What's more the whole asynchronous movement complete response system also delays the serial read code.

I guess the best solution is to process the serial data input using interrupts, but that will potentially throw off my PWM generation code as a serial interrupt could be being processed whilst timer interrupt should be running... 

ATMega168 64 channel servo controller

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.

New servo controller commands

The new 64 channel ATMega168 serial servo controller accepts the following commands. All successful commands are echoed back. Parameters are validated and errors are indicated with an error response of [0xFF] [badParamIndex] [Command echo] where badParamIndex is a 1 based index of the parameters in the command and indicates which parameter failed validation.

  • Set Servo Position
    [0x41] [servo] [position] - This command operates in the same way as the standard SSC compatible servo command that the original servo controllers supported. That is the servo specified is moved directly to the position specified. The servo index should be between 0 and the number of servos that the controller supports (up to 64) and the position should be between 0 and 254. A position of 127 sets the servo to the centre. 
  • Delayed Set Servo Position
    [0x42] [servo] [position] [stepSize] - This command will cause the servo to move from where it currently is to the desired position in steps of the specified size. A step is taken each time the PWM pulse refreshes, so 20 times per second. If the servo is currently at position 0 and you want it to move to position 100 and you specify a step size of 1 then it will take around 5 seconds to reach the final destination. With a step size of 2 it would take around 2.5 seconds, etc. Note that the actual time taken will be affected by the speed of the servo. If you specify a step size of larger than the distance between the current position and the desired position then the servo moves to the new position straight away. The command is echoed once it is actioned and an asynchronous 'move complete' notification is generated once the servo arrives at its desired destination. The move complete notification is in the form: [0xFE] [servo] [position]. At any time between starting the move and the move completing you can send a stop command to stop the servo at the point where it currently is.
  • Delayed Set Servo Position with step frequency control
    [0x43] [servo] [position] [stepSize] [stepFrequency] - This command is the same as the command above except that the frequency of steps can also be controlled. The command above is equivalent to this command with a stepFrequency of 1. If the servo is currently at position 0 and you want to move it to position 100 and you specify a step size of 1 and a frequency of 2 then it will take around 10 seconds to reach the final destination.
  • Stop Servo
    [0x44] [servo] - This command will stop the servo moving if it is currently moving as part of a delayed move command. The current status of the servo at the time it was stopped is returned in a servo stop response which is sent after the stop servo command has been echoed and actioned. The servo stopped response is in the form: [0xFD] [servo] [currentPos] [stepFrequency] [targetPosition] [stepSize] where the current position is the position of the servo after it has stopped and the stepFrequency is the frequency with which it was moving before it was stopped. If the stepFrequency is zero then the servo was already stationary.
  • Stop Servos
    [0x45] [numServos] [servo1] [servo2] ... [servoN] - This command will stop all of the servos specified and will generate a servo stopped response for each one.
  • Stop All Servos
    [0x46] - This command stops all of the controller's servos and generates a servo stopped response for each one.
  • Query Servo
  • [0x47] [servo] - This command will query the current position of the specified servo and return it as a servo query response after the command echo. The servo query response is identical to the servo stop response except the response code is 0xFC rather than 0xFD. Note that if the stepFrequency is zero then the servo is stationary. If the stepFrequency is non zero and the currentPos is not equal to the targetPos then the servo is moving.
  • Query Servos
    [0x48] [numServos] [servo1] [servo2] ... [servoN] - This command will query all of the servos specified and will generate a servo query response for each one.
  • Query All Servos
    [0x49] -  This command queries all of the controller's servos and generates a servo query response for each one.
As you can see, this controller gives me much more control over the movement of the servos attached to it. The main thing being that I can stop servos in mid move if required and that I can move servos at slower than maximum speed. This paves the way for being able to simplify the logic in the actual microcontroller that generates the movement sequences required to move the legs whilst still being able to monitor for collisions and stop the servos in mid step if the sensors on the legs indicate that it's necessary. 

Of course, first I actually have to build more than one leg, but before I move on to that there are a couple more servo controller commands to implement. The first being the ability to set several servos to move to new positions in such a way that they arrive there at the same time. This was the reason for the stepSize and stepFrequency elements of the delayed move commands. Ideally the servo controller will do the calculations required which means that the 'gait generator' just needs to know where to put the feet rather than how to move each servo at the right speed so that the foot ends up in the right place at the right time... Next will be the ability to provide a mapping table between 'logical' servo indexes and physical servos, this is so that if it happens to be difficult to get all of the tracks on the board to end up in the right places I can adjust which logical servo is controlled by which pin. This will involve accessing the eeprom of the ATMega168... Once that's done it would be useful to have the ability to set the 'default starting position' for each servo, so that they can start at positions other than 127 without there being a race between the controller starting up and the servos all ending up in odd positions. Finally as a safety feature being able to set a minimum and maximum position for each servo would be useful.

Source code for the current version of the controller will be available soon.

Moving forward

Due to work pressure and then holidays and then more work pressure I had to take a break from the servo controller for a couple of weeks. Most of the code changes that I had previously been discussing have been implemented and I'm now in the process of testing an ATMega168 version of the 64 channel servo controller complete with new style commands! Since this version of the servo controller uses a custom serial protocol and since it can send unsolicited serial responses back to its controller I needed to write a new PC based control program to test it with. That's now operational and I've been testing the delayed moves and their corresponding asynchronous move completion notifications. Once I'm happy with that I need to design and write the final piece of code which will interface between the new serial protocol and the servo control data update code... I've already implemented the move completion notifications, servo position query, servo stop and stop all servos commands so the design of the data structures and synchronisation details have been validated. Once that's done and working on the 168 I may see if I can squeeze an 8 channel version of this new code back onto the ATTiny2313. 

Registers, stack usage and timing

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

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. 

Tweaking the servo controller

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

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

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

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

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...
2