New side project of mine - program to generate graycode-patterns for manufacturing of optical rotary encoder discs.
Graycodes can be any desired bitlength, in my case I need 12, but I'm not paying 500 uk pounds for something I can make out of some junk components and an old CD or DVD (who hasn't got lots of dead/scratched ones laying around?)

I decided to use OA32's Pixelmap object because it lets me quickly create BMP files from nothing.
It has much functionality, but is sadly missing things like "DrawCircle" and "DrawLine".

Here is a picture of the output of a quick function I wrote for plotting circles using Pixelmap's integer-based SetPixel method (there's a floating-coord version of SetPixel which uses bilinear filtering, it would make nicer LOOKING circles by bleeding into neighbouring pixels at reduced brightness).
I needed the function to be accurate, rather than fast. I chose to use 32bit fpu trig, plotting with an angular stepover calculated from the tangent of the circle at one pixel versus the radius.
It's not perfect - theres a little occasional pixel crowding - but the circles I generate are 'leak-proof' (can be filled by any algorithm).

Here's some circles generated onto a 512x512 image (I'm using 4096x4096 here).
Next will be to reimplement lineplotting algorithm.
If radial lines fill the pattern cleanly, as they should, I can think about combining the circle and line plotting algos to generate the complete filled graycode encoder wheel in a single pass.
Attachments:
Posted on 2010-06-14 12:57:15 by Homer
Just incase anyone is actually curious to see the code.. here's that circle plotting function.
I tried to implement several other circle algos, but they just didn't cut it, especially at high resolutions.


;PosX,PosY = coordinate of center of circle
;Rad = Radius in Pixels
PlotCircle proc posX, posY, rad, color
LOCAL x,y
LOCAL step:real4,angle:real4

fld1 ;Calculate the angle of a triangle whose
mov eax,rad ;base is the radius, and whose side is 1.0
fildReg eax ;This is the inverse tangent of 1/radius
fdiv ;Note that this value is in radians
fArcTan
fstp step

mov x,0
mov y,0

mov angle,0
.repeat
fld angle ;X and Y position are calculated via trig
fsincos
fimul rad
fRndDn     ;I will round DOWN to nearest integer (just a convention)
fistp y      ;which slightly improves appearance of circle by eliminating 'wobble' during plotting
fimul rad
fRndDn
fistp x

    mov eax,posX
    add eax,x
    mov edx,posY
    add edx,y
    OCall pPXM::Pixelmap.SetPixel, eax, edx,, color

;Increment angle
fld step
fadd angle
fst angle

;Check if we have passed "2PI" (a full circle)
fldpi
fldpi
fadd
fsubr
fstpReg eax
.ifBitSet eax,BIT31
DbgFloat eax
.break
.endif



.until 0
   ret
PlotCircle endp


And the new one, integer-optimized Bresenham line (and no, the code is not optimized, the algo is)



PlotLine proc x0,y0,x1,y1,color
LOCAL steep:BOOL
LOCAL error:SDWORD
LOCAL deltax,deltay,ystep,y
    mov eax,y1
    sub eax,y0
    BitClr eax,BIT31
    mov edx,x1
    sub edx,x0
    BitClr edx,BIT31
    .if eax>edx
    mov steep,TRUE
    .else
    mov steep,FALSE
    .endif
   
    .if steep
    push x0
    push y0
    pop x0
    pop y0
    push x1
    push y1
    pop x1
    pop y1
    .endif
   
    mov eax,x0
    .if eax > x1
        push x0
        push x1
        pop x0
        pop x1
        push y0
        push y1
        pop y0
        pop y1
    .endif
   
    mov eax,x1
    sub eax,x0
    mov deltax,eax
    mov eax,y1
    sub eax,y0
    mov deltay,eax
    mov eax,deltax
    shr eax,1
    mov error,eax

    m2m y,y0,edx
   
    .if edx < y1
    mov ystep, 1
    .else
        mov ystep, -1
.endif
   
      mov ebx,x0
      .while ebx<x1
        .if steep
            OCall pPXM::Pixelmap.SetPixel, y, ebx, color
        .else
    OCall pPXM::Pixelmap.SetPixel, ebx, y, color
        .endif
        mov eax,error
        sub eax,deltay
        mov error,eax
       
        .if error < 0         
            mov eax,y
            add eax,ystep
            mov y,eax
            mov eax,error
            add eax,deltax
            mov error,eax
        .endif

        inc ebx
.endw
ret
PlotLine endp



I tried tracing lines from the center of the circle to each point plotted on its circumference.
The resulting image is attached.
As you can see, this method of filling the circle is entirely unacceptable - and I suspected it would be.
But that is ok, I'll probably develop a raster-based flood-filling algo.

Attachments:
Posted on 2010-06-15 00:46:02 by Homer
Seems the problem was in the line drawing algorithm.
I implemented a slightly different one, and here's my current result... ooh, pretty patterns!
This is an Absolute 8-Bit graycode encoder, I've only filled half of the pattern (as I was doing it by hand, which is NOT the goal)... also, it's rendered to only a 1024x1024 image, so it appears a little pixellated - this problem is reduced greatly by increasing the resolution further, my final goal is CD LABEL sized image, so remember that even at a lowly 300 dpi, we still need 300 pixels for every inch.. this is an optical device, cutting corners WILL adversely affect its performance.

I have written a function to generate graycode tables of up to 32 bit precision - if anyone wants that, let me know.

Next will be to attempt to implement automatic cell filling of some kind, so I can get rid of the 'grid' completely.
Attachments:
Posted on 2010-06-15 07:48:03 by Homer
Instead of plotting pixels, have you considered outputting to a vector format? That way you would get perfect quality & scaling...
Posted on 2010-06-15 08:40:55 by f0dder
I have not thought about that no, I know nothing about "modern 2d vector graphics".

But heres some more eye candy :)
I finished writing the code anyways.

For each 'cell' which has an associated graycode bit of 1, I draw all 4 edges of that cell, then I flood-fill it using a particularly lame recursive filler that I wrote on the spot. Just happens I can guarantee that my polygon is always closed and convex which made life easy - and doing it this way avoids any messy edge-ordering / coordinate-switching, and no need for interpolation.
Lazy man gets the job done.

OK, these gray codes are counted from the inside outwards - LSB on the outside track.
The first graycode on my image is located at 6 oclock, and the series then counts in the counterclockwise direction.

00000000
00000001
00000011
00000010 and so on


I am considering whether or not to remove the circles marking the 'tracks' from the image - just leaving the filled areas, the outermost circle, and the little one in the middle with the crosshair on it.
But I guess the circles are doing no harm being there - they won't affect the device's operation.



6 oclock - yes, I mixed up sine and cosine, silly me, and im WAY too lazy to fix it, so NER

Posted on 2010-06-15 10:08:23 by Homer

Hum, last minute bug crept in
Probably noone noticed theres only 7 tracks worth of active cells in those images.

Shoulda looked like this:
Attachments:
Posted on 2010-06-15 10:24:39 by Homer
I have not thought about that no, I know nothing about "modern 2d vector graphics".
:)

Never dealth with 2D vector graphics myself, but for a project like this I would've looked into the SVG graphics format. If you want to be able to do printing directly from your app instead of just generating files and rendering+printing with another app I dunno if it's more trouble than it's worth, though.

Anyway, nice-looking eyecandy =)
Posted on 2010-06-15 10:44:06 by f0dder
I have now generated 9,10,11 and 12-bit encoder wheels.

Now I know why the commercial ones all stop at 12 bits.


10 bits required an image resolution of 2048x2048 to look respectable.
11 bits needed 4096x4096.
For 12 bits, even 8192x8192 was not enough.
I settled on 9000x9000
This is close to the limit of what winpaint will handle - its a 308MB bmp file !!
I haven't tried to generate more bit depth, I'd need to free up some hard drive space lol
Posted on 2010-06-15 11:03:24 by Homer
For 12 bits, even 8192x8192 was not enough.
I settled on 9000x9000
This is close to the limit of what winpaint will handle - its a 308MB bmp file !!
I haven't tried to generate more bit depth, I'd need to free up some hard drive space lol
Well, there's your incentive to look at a vector-based solution :P
Posted on 2010-06-15 11:07:19 by f0dder

Well, there's your incentive to look at a vector-based solution :P


Agreed, especially considering the geometric nature of this application.

Grab a copy of Inkscape and have a quick look at what the SVG format can do for you.
Posted on 2010-06-15 11:23:17 by SpooK
I'll certainly take a look :)

However what I meant was that there is a physical problem with increasing resolution further.
I need to be able to shine a light through these holes while the disc is rotating, and capture that light with a detector.
If the cells (or, in the case of the outer edge at high precision, slots) are too narrow, there wont be enough time for the detector to switch states (rise time) unless the encoder is rotating very slowly, and the light is very bright (or is a laser diode).
I was planning on using regular Infrared LEDs both for emitter and reversebiased as detector.
These have rise/fall times of about 5 microseconds.
So there is a relationship between how fast the encoder can rotate, and how narrow the slots are (regardless of the other optical issues caused by the slot).

The only solution to that problem is to use a BIGGER DISC.
But I want to use a CD - its already quite large compared to commercial encoders, which use special IR LED array ICs that have little slot-shaped apertures all red in a row.
And to etch the cd, I've confirmed that I can use sulphuric or hydrochloric acids in household strength, or even sodium hydroxide will work - and I think I can use almost anything as a resist for those - good news :D

Now I just need to find a really cheap and nasty CD with no print and no lacquer on the surface. Should not be too hard :P

Posted on 2010-06-15 11:58:11 by Homer
Just did a quick calculation, with a rise/fall time of 5us, and resolution of 12 bits = 4096 absolute positions, my system WILL FAIL UTTERLY at 2929.6875 RPM.

The shaft whose position I intend to measure will never exceed 300 RPM.
More good news :)


Posted on 2010-06-15 12:16:36 by Homer

If anyone wants to ask any questions about rotary encoders (for example, what the hell they are for), then please feel free to do so. I have a formal qualification in this field, and I'm happy to pass on what I've learned.

Posted on 2010-06-16 01:20:59 by Homer

If anyone wants to ask any questions about rotary encoders (for example, what the hell they are for), then please feel free to do so. I have a formal qualification in this field, and I'm happy to pass on what I've learned.


I've read Hiltgen/Paterson paper on STCCs (not completely understood it yet ;)) and thought about combining several single-track encoders to get the best of two worlds. Is there a theory about this approach?
Posted on 2010-06-16 05:53:54 by baldr

OK, Let's start with the idea that we have one track, containing a sequence of just 0, 1, 0, 1, 0, 1 .....
Now let's split that track into two, and shift one of them out of phase by half of one cell.
We now have enough to build a quadrature encoder ...  the offset pattern is a quadrature pattern.
That means we can tell which direction the device is rotating, as well as merely being able to count the ticks.

If we want to be able to tell the position at any moment without using a counter (that can miss counts), we can make an absolute encoder wheel as I have, and fill it will gray codes.

Now, there are more than one set of gray codes.
The professor Gray who invented them defined them as any set of binary numbers which increment by changing of only one bit at each step.
I used the 'classic' or 'vanilla flavored' set of gray codes, which follow a very simple pattern that I'd be happy to describe.
But we could choose ANY gray code set, and put that on an encoder wheel...
Note that the gray codes appear on the encoder wheel "radially", ie you pick some angle , draw a line from the center of the encoder wheel along that angle, and start reading one bit from each track the line passes through.

The idea in the paper you mentioned is an extension of the previous "quadrature from two out of phase tracks" concept.
The idea is that we can find some bit pattern that we can shove onto any one of our rings, which can be then duplicated in every other ring, except at different orientations, to produce that shift... and also produce "some valid set of gray codes". The idea is that such a pattern and set of orientations exists, and that we can find it algorithmically.
I have no backbone for that stuff, sorry, I am wimping out, I am not a math major.


Posted on 2010-06-16 11:48:04 by Homer
OK you got me thinking.

I said I'm not a math major, and that's true.
However I am pretty switched on.

I decided to have a go at generating a cyclic gray code.
For my little test I chose 4 bits.

Forgetting about all the math, and just using these simple rules:
1. Each concurrent set of 4 bits will represent a binary value (from 0000 to 1111).
2. The first and last values must be chosen to form a closed loop of bits.
3. All binary values must appear.
4. Each binary value must appear only once.

For no reason at all, I chose to start with the number 1.
So I wrote down the binary:

0001

Now I have to choose a value that (A) begins with "001" and (B) I have not used it before.
I ended up with the following series:

0 0 0 1 1 0 1 0 0 1 0 1 1 1 1 0

These 16 bits contain an entire 4-bit graycode set !!!
And we KNOW that its a legitimate gray code set because at each step, the value changes by only one bit :D

0001 = 1
0011 = 3
0110 = 6
1101 = 13
1010 = 10
0100 = 4
1001 = 9
0010 = 2
0101 = 5
1011 = 11
0111 = 7
1111 = 15
1110 = 14
(and we start cycling bits already)
1100 = 12
1000 = 8
0000 = 0

ad nauseum.


The idea is to arrange the 16 bits in a circle, and use four sensors that are situated on the same radius as the bitpattern is (a single track) - rather than having the sensors arranged along a ray from the circle origin as normal. The spacing is now important too, because we need to have each of the four sensors situated directly above each of four consecutive bits on our encoder wheel. Now as the wheel turns, the four sensors will see a 4-bit gray code at each of 16 steps, see the above table of values.


Can you come up with another 4 bit pattern?
Can you suggest an algorithmic approach to the discovery of such patterns at arbitrary bitlength?
Posted on 2010-06-17 04:57:19 by Homer

0000
0001
0011
0010
0110
0111
0101
0100
1100
1101
1111
1110
1010
1011
1001
1000

just observe the collumn for each bit, the gray code is as simple as binary
Posted on 2010-06-17 06:32:03 by edfed

Looks like someone didn't bother to read before they posted :P
I know what a gray code is - and that there is more than one !!
In particular, my last post was about "circular" or "cyclic" gray codes.
The classic gray code you posted is not cyclic.
Posted on 2010-06-17 08:27:36 by Homer
i read everything in the post, just i find it usefull to confirm the only real gray code.

your previous "gray" code seems to don't be so gray...
0001 = 1
0011 = 3
*0110 = 6
*1101 = 13
*1010 = 10
*0100 = 4
*1001 = 9
*0010 = 2
*0101 = 5
*1011 = 11
*0111 = 7
1111 = 15
1110 = 14
(and we start cycling bits already)
1100 = 12
1000 = 8
0000 = 0


cyclic means (for me) something that have a cycle, i know only ONE real gray code, the one understood by TTL/CMOS circuits.
the classic gray code is striclly CYCLIC, because you can reloop.
Posted on 2010-06-17 09:44:00 by edfed
Not gray huh?

ok do this

write out the bit sequence i posted (the 16 1s and 0s)

now look at each GROUP of 4 bits using a sliding window, starting at offset 0, then offset 1, etc

note that we shifted by ONE BIT to look at the next 4 bits - in effect our gray code is shifted too.

yes, more than one bit appears to change making it APPEAR not to be a gray code, but we're not moving from value to value in the usual way, we're sliding along our viewing window by one bit instead - it's legit.


its just not a CONVENTIONAL gray code ;)
Posted on 2010-06-17 18:21:26 by Homer