Ooooh…
Why settle for the accuracy of an optical encoder when we’re dealing with the low speeds of a servo and don’t have any real size restrictions?
Why not further itterate to an decaencoder, or greater?
*
|█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ |row6
|██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ |row5
|████ ████ ████ ████ ████ ████ ████ ████ |row4
|████████ ████████ ████████ ████████ |row3
|████████████████ ████████████████ |row2
|████████████████████████████████ |row1
The optic sensors would ideally be a phototranstor for each row, positioned one on top of the other, like so:
() Phototransistor 6
() Phototransistor 5
() Phototransistor 4
() Phototransistor 3
() Phototransistor 2
() Phototransistor 1
The above would produce a 6-bit resolution of position (a resolution of , one bit for each row).
I’d have shown an 10-bit setup, but that wouldn’t fit on the width of this screen.

Methinks that upon startup, the controlling micro would be able to easilly tell where it is within the resolution of it’s readout.
// Note the star in the topmost code window.
// Let's imagine that's where this bugger would be upon startup.
// Here's how I'd find out where it is:
// A return of 0 = unshaded area; a return of 1 = shaded area.
// Get row6 first, since it'll be changing the fastest.
Row6 = IsRow1Shaded();
Row5 = IsRow1Shaded();
Row4 = IsRow1Shaded();
Row3 = IsRow1Shaded();
Row2 = IsRow1Shaded();
Row1 = IsRow1Shaded();
// Zero out a variable to hold the servo position.
Degrees = 0;
// If the row 1 transistor is over a non-shaded area...
If Row1 = 0 Then
// Then the position must be greater than 180 degrees.
Degrees = 180;
// Otherwise, if it's over shaded area, then the value is between 0 and 180, and so Degrees will stay = to 0, for now.
// And so on for the other ones:
If Row2 = 0 Then
Degrees = Degrees + 90;
If Row3 = 0 Then
Degrees = Degrees + 45;
If Row4 = 0 Then
Degrees = Degrees + 22.5;
If Row5 = 0 Then
Degrees = Degrees + 11.25;
If Row6 = 0 Then
Degrees = Degrees + 5.625;
With the star representing the position of the servo in the example, the 6 rows would be shaded as follows:
Row 1 = 1;
Row 2 = 0;
Row 3 = 0;
Row 4 = 1;
Row 5 = 0;
Row 6 = 0;
Which means, Degrees would turn out to be:
90 + 45 + 11.25 + 5.625 = 151.875
The actual position with only 6 rows, though could be up to 5.625 degrees greater than that.
So the position is somewhere between 151.875 and 157.5 degrees.
With 10 bits, the position could be read to about a third of a degree, which should be fine.
The only hurtle for this would be size.
The outermost row would have 2^10 = 512 shaded sections which makes the sections really small or the ring really large.
Either way, it’d only be a problem when the servo is moving, and even then, only when it’s moving at a rapid pace.
If a very rapid pace is required (faster than the above code can run) then the PID code will just have to switch over to using the messier analog value of the rapidly-switching transistor.
That’ll tell it the speed of rotation, which it can use to pretty accurately estimate position.
When the position becomes close to the target position, the servo will be braked and then the accurate low-speed digital positioning can be used to tweak it into just the right spot.
And all that might not be necessary, if the servo is slow enough, which it very well may be.