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.

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.

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.

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

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.

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.

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.

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.

Instead of plotting pixels, have you considered outputting to a vector format? That way you would get perfect quality & scaling...

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

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

Hum, last minute bug crept in

Probably noone noticed theres only 7 tracks worth of active cells in those images.

Shoulda looked like this:

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 =)

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

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

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 :PI 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

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.

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

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

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 :)

The shaft whose position I intend to measure will never exceed 300 RPM.

More good news :)

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.

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?

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.

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?

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?

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

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.

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.

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.

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 ;)

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 ;)