L'Hexapod: Redesigning the servo controller firmware

Previously published

This article was previously published on lhexapod.com as part of my journey of discovery into robotics and embedded assembly programming. A full index of these articles can be found here.

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

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

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

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

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

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