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.
- 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.
- 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.
The second adjustment is the easiest and also affects the timing of the PWM generation code, so we’ll deal with that first and then deal with all of the resulting timing issues in one go. Previously we had a very simple timer interrupt which immediately used an ijmp into a jump table for the actual interrupt handler. This code now needs to save the state that is shared between the serial code that’s running in the main loop and the timer interrupt code.
TC1CmpA: push temp20 ; save the state of these as they are used by the serial protocol code push temp21 in temp20, SREG ; save status register push temp20 ijmp ; Z is set up to point into our jump table (above) so we jump to Z then ; jump to the correct state for our timer handling...
Both of the interrupt handlers now need to restore the state from the stack before returning. They now do that by jumping here.
TC1CmpAEnd : pop temp20 ; restore the state prior to the interrupt out SREG, temp20 pop temp21 pop temp20 reti
The adjustments for the difference in clock rates between the 8.0MHz clock that we used to develop the code and the 7.3728MHz clock that we will be using for the final project are a little more complex but relatively isolated. They’re also possible which is more than could be said for the code that we started out with before the redesign. Firstly the new clock is running at 7.3728/8 (0.9216) the speed of the old clock. This means that where before we would have had 100000 ticks per second (we have configured the timer to run at 1/8th clock speed), we now get 9216 ticks per second. This affects the calculation that we made to ensure that a servo control value of 127 results in a pulse length of 1.5ms. Where before we had an equate of 738 (1500-(6127)) now we need an equate of 620 ((15000.9216)-(6127)). Likewise the length of the 2.5ms cycle period is different with the new clock, 25000.9216 = 2304. The next change is a little less precise, we need to tune the nops in the interrupt handler so that the centre pulse and the 2.5ms ‘cycle end’ are accurate. This required a little trial and error in the simulator and the result is that the 1.5ms pulse is actually 1499.97us long and the 2.5ms cycle length is spot on. I would imagine that it would be better to tune this code using an oscilloscope, but until I invest in one this will need to do. What surprised me was how easy these timing related changes were, remember this was part of the reason that I decided to redesign the code in the first place. Now, hopefully, we can add as much complexity to the serial protocol as we like and most of the changes shouldn’t affect the PWM generation at all. The completed code is available here.