POV source code – part 2

Clock interrupt

The STC89C52 has three timer/counters (most of the 8051 chips have the first two, and the third one is pretty common).  One of the modes for timer 1 is to count clock pulses in an 8-bit register and when FF is reached, reload the count register from a separate 8-bit register.  Each time this overflow is reached an interrupt is generated, and we can use this to provide a regular clock ‘tick’.

The crystal frequency is divided by 6 when the chip is set to fast mode and by 12 when it’s set to standard (slow) mode.  The crystal oscillates at 11.0592 MHz so assuming we’re using fast mode the slowest rate we can generate interrupts this way would be to set the timer reload value to zero and then we’d get:

(11059200 / 6) / 256 interrupts per second = 7200 Hz.

I wanted the interrupts to happen a little faster than that as I was also intending to piggy-back the LED switching from the interrupt routine, so I chose a reload value of 0x40 (64) which results in 9600 interrupts per second.

The set-up code to initialize the timer is as follows:

// set up timer 1 interrupt for 9.6 kHz (assuming 11.0592 MHz xtal)
TMOD = 0x20; // timer 1 in 8-bit auto-reload mode
TH1 = 0x40; // reload value to give 9.6 kHz in ‘fast’ (6T) mode
ET1 = 1; // enable timer 1 interrupt
EA = 1; // enable interrupts
TR1 = 1; // run timer 1

The first part of the interrupt handler just counts the interrupts and uses them to advance the clock: (seconds, minutes, hours)

unsigned int fracSecs; // fractions of second in ticks

void T1INT(void) interrupt 3 using 1 { // configured to fire @ 9.6 kHz
    if (++fracSecs == 9600) { // one second elapsed
        fracSecs = 0;
        if (++topRow[7] == 26) { // units seconds overflow
            topRow[7] = 16;
            if (++topRow[6] == 22) { // tens seconds overflow
                topRow[6] = 16;
                if (++topRow[4] == 26) { // units minutes overflow
                    topRow[4] = 16;
                    if (++topRow[3] == 22) { // tens minutes overflow 
                        topRow[3] = 16;
                        // check for midnight
                        if (++topRow[1] == 20 && topRow[0] == 18) {
                            topRow[0] = topRow[1] = 16;
                        } else if (topRow[1] == 26) { // 10am or 8pm
                            topRow[1] = 16;
                            ++topRow[0];
                        }
                    }
                }
            }
        }
    }
}

It’s a little confusing as the routine directly manipulates ‘topRow’ which is a 16-character buffer holding the data to display on the top row of the display in the format: “HH:MM:SS        “ except that the character values are not ASCII but (ASCII – 32) to suit the way the font is defined.

So the first check (topRow[7] == 26) checks if the units seconds character has reached ASCII 58 (a colon – which is the character that comes after ‘9’) and if so it resets it back to 16 (ASCII 48 to display a ‘0’) and increments topRow[6] which is the tens of seconds character, and so on…

So why did I choose 9.6kHz?  Well it was a compromise to do with the rate that the LEDs spin round at, and wanting to get a reasonably well-defined display only using a single interrupt source and without overloading the microcontroller with too many interrupts.  Actually when I moved onto the bottom row display, I used a second interrupt source auto-adjusted to give exactly 256 interrupts per rotor revolution – but more on that later…

When powered with a nominal five volts, the board spins about 900 rpm (revolutions per minute).  The lowest voltage my kit would operate at without the microcontroller ‘browning out’ and resetting was about 3.3V which gave 700 rpm.  It’s unreliable at this low speed – if you try to switch too many LEDs on at the same time the extra current drags down the voltage on the board to the point where the microcontroller resets itself.  The ‘regulator’ on the board is a little sketchy – just a zener diode to bleed away current when the voltage begins to exceed 5.1V – so I’ve not risked going above 6V on the power supply at which voltage the motor still only spins about 950 rpm.

My program displays 32 characters around the circumference of the cylinder drawn out by the LEDs and with 8 pixel width characters at 900 rpm that gives a pixel rate of 900/60 x 32 x 8 = 3840 pixels per second.  As the voltage varies or the rotor is affected by draughts or similar, the rate can vary in the range roughly 3000 to 4000 pixels per second.

So at an interrupt rate of 9.6kHz we need to output a fresh set of pixels roughly every two-and-a-half interrupts (9600 / 3840).  We can’t do half-interrupts, of course, so the program needs to use a mixture of two, three, maybe even four, interrupts between each update – and spread them out in such a way as to provide a fairly regular and stable display.

As the rotor passes a reference position each revolution, it picks up an ‘index pulse’ from a photodiode illuminated by a stationary infra red LED – so it can use that information to recalculate the correct ratio of two interrupt/three interrupt cycles to keep the display fairly steady.

It sounds like a lot of calculation, maybe involving division (which is very slow on this chip as it doesn’t have multiply or divide instructions so it has to ‘long division’ by repeated subtraction/addition and bit-shifts).  Luckily there is a faster way just involving addition and bit shifts, although it’s a little confusing until you’ve studied it for a while.

The basic idea is to have a 32-bit counter which we try to get to count up to 0x01000000 (16777216) for each rev of the rotor.  We do this by adding an amount to the counter each time there is an interrupt.  If the rotor speed were exactly 900 rpm (fifteen revs per second) then we’re going to get 9600 / 15 interrupts  (640) for each revolution of the rotor so to count up to our target number we add 0x01000000 / 640 (which is about 26214)  every interrupt.

Now when the index pulse comes around we can see if we’ve counted past our target number or not reached it – and the error between the actual count and the target can be used to adjust our 26214 value up or down for the next revolution.  If you just divide the error by 1024 and add that value to the ‘26214’ then the counter quickly locks into counting to the correct target value after a few revolutions – and then it automatically adjusts the rate to compensate for any changes in the rotor speed.  Dividing by 1024 is something the microcontroller can do pretty quickly – just by bit shifts.  In fact because the 32-bit count is held in four 8-bit registers, the correction calculation can be done with just  a 2-bit shift on a 16-bit quantity and a 16-bit subtraction.

Looking at the 32-bit count as four bytes, we see that the most significant byte is targeted to count up to exactly ‘1’ per revolution, which means that the next most significant byte counts from 0 to 255 per revolution with the 0 aligned with the index pulse – so we can just look at this byte to give us the current position around the circle in “brads” (one brad = 1/256 of a circle or 1.40625 degrees).

That should be enough explanation to allow you to understand the source code which you can see/download: here.

I’ve not covered how the second interrupt source for the lower row of text works yet though – that’s for another post…

 

 


Posted

in

,

by

Tags:

Comments

2 responses to “POV source code – part 2”

  1. Ewald Burger avatar
    Ewald Burger

    Hi Martin,

    First of all I’d like to wish you a happy new year with good health and lots of engineering fun!

    Thanks for your explanation, most part of it is very clear! I’m sure it will become even more clear after studying your code!
    I did not have much time the last few days, but will try to dive deeper into it soon.

    Ewald

  2. […] „Ceptimus“ großartige Pionierarbeit geleistet. In mehreren Artikeln (1, 2, 3) beschreibt er den Aufbau und die Funktionsweise seiner Software inklusive der verwendeten […]

Leave a Reply

Your email address will not be published. Required fields are marked *