Home
Name Modified Size InfoDownloads / Week
moon_clock_3.tgz 2023-01-29 16.8 MB
README 2023-01-29 24.1 kB
IMG_1086.JPG 2023-01-29 2.3 MB
IMG_1299.JPG 2023-01-29 2.6 MB
IMG_1046.JPG 2023-01-29 2.0 MB
IMG_1042.JPG 2023-01-29 2.1 MB
IMG_1069.JPG 2023-01-29 2.4 MB
IMG_1080.JPG 2023-01-29 2.5 MB
IMG_1074.JPG 2023-01-29 1.8 MB
combined_docs.pdf 2023-01-29 196.1 kB
wiring.pdf 2023-01-29 41.5 kB
Totals: 11 Items   32.7 MB 0
Thu 22 Sep 2022 11:18:23 PM PDT

Moon Clock 3

This will be an all-new system design, probably still using the Atmel
ATmega168, but depending upon "neopixels" instead of directly driving
a scanned LED matrix with PWM waveforms, as in the first version.

  (Update: now using ATmega328P, and "dotstars" rather than "neopixels".)

Benjamin W. Ketcham-McGrath
September 2022

----
Usage:

Compile the code and generate the .hex file:
    make -f Makefile.avr-gcc

Burn the fuses:
    avrdude -u -p m328p -c usbasp -E noreset -U efuse:w:0xff:m
    avrdude -u -p m328p -c usbasp -E noreset -U hfuse:w:0xd9:m
    avrdude -u -p m328p -c usbasp -E noreset -U lfuse:w:0xe0:m

Burn the chip:
    avrdude -u -p m328p -c usbasp -E noreset -U flash:w:moon_clock_3.hex 

(Or use the scripts BURN_FUSES and BURN_CHIP.)

----
  (Note that in many of my drawings, I've been calling this Moon Clock 2,
  since it's my first total redesign of the system, but I had forgotten that
  I was already calling the improvements I made in 2020 to the original,
  "moon_clock_2" -- and for that matter, I wouldn't be surprised if there
  are some other "moon_clock_2" files from back in the 2008-2010 days,
  because I contemplated various improvements back then, too.  When in doubt,
  check the general timeframe of the file dates.)

----
Neopixels.

The "neopixels" each contain an RGB (or RGBW) LED, plus a microcontroller
which produces the PWM.  The neopixels have a single serial data input
and a single output; they are simply daisy-chained together, and the
user's microcontroller (the "host") produces a serial data stream which sets
all of the values.  Each neopixel strips out the values it needs from the head
of the stream (either 3 or 4 bytes, depending on RGB or RGBW -- I'm not
aware of any which provide more than 8 bits per colour), and then
it forwards any additional values to its output.  Thus, rather similar
(conceptually) to the "DMX" protocol used by theatrical lighting systems.
Because only one data line is used, timing is specific and critical.  And
with most neopixels, there is no way to "pause" in the middle of sending
a data stream, nor is there any way to only set a few of the neopixels.
Once the host microcontroller starts sending a data stream, it must
send the complete data set for all neopixels in the string.  Once set,
the pixels retain and continue to produce the selected colour indefinitely.
(There exists another neopixel type which allow pixels to be individually
addressed, which relieves these restrictions; but we're not going to use
those here.)

It seems to me, it might be very useful if one could read out the values
stored in the neopixels, but such is not the case.  Thus, the host must
generally allocate a memory map of all the pixel values -- although if
the pixels don't all need to be free to assume independent values, memory
savings can be realized.  In the Moon Clock 3, there will be 60 neopixels,
but there will only be 4-5 different colours in the display at any given
time, and any given neopixel will generally only take one of two values
(either a colour or black in some cases, or one of two colours in other
cases).  So the display map will mainly consist of single bits, thus
fitting into 8 bytes, plus an array of 3 or 4 byte colour values, perhaps
up to 5, so at worst, 20 more bytes.  So a total of 28 map bytes, to generate
240 bytes worth of neopixel values.  The user will be able to set the
colour values to preference, but the functional meaning and use of each
colour will be predetermined (e.g., there will be alphanumeric segments
which are either the "text" colour or black, there will be astrological
symbols which are the "zodiac background" colour except for one which is
"zodiac highlight" colour, etc.).

(Check adafruit.com for more info on neopixels.)

----
GPS.

I think I am going to integrate a GPS receiver module into this system.
This will allow the unit to set its own time, and also to determine its
latitude and longitude, needed for the moon calculations.  In the old
Moon Clock, the user had to manually set the time (including date) after
any power cycle, and the latitude was set in EPROM but not adjustable
through the interface.

An alternative to a GPS receiver would be WiFi, which could also obtain
the correct time (and I guess, perhaps the lat/long, but I'd have to
research exactly how).  This would enable the use of an "app" to set stuff
remotely and such -- but then again, the "app" could just give the moon values
itself, and the entire Moon Clock would become pretty (and pretty expensive)
fluff.  The GPS alternative eliminates an RF transmitter, and much other
rigamarole of WiFi, and it enables somewhat "magical" behaviour of the
device always having the right time and knowing where it is on the Earth.

The main question is, how well will these cheap and ubiquitous GPS modules
work indoors.  Probably, not well; so it'll be important for the unit to
be able to grab correct data whenever it can, and then "carry on" accurately
without regular updates.  In conjunction with some plentiful capacitance in
the power supply, this will allow the user to temporarily take the unit
outdoors or move it to other positions to "grab a fix", then take it
indoors and plug it back in -- because I'm not planning to make the unit
actually battery-powered, just +5v powered with some significant ability to
operate after power is lost (at least 60 seconds or more).
The neopixels take a large amount of current, so a hefty +5v supply will
be needed: several amps.

(Later: not attempting GPS -- at least, not yet.  Just plain old
manually-set-at-power-up.)

----
Mon 21 Nov 2022 09:50:54 PM PST

Changes in the design since the last time I wrote.  Not planning to use
GPS in the "basic" version; maybe I'll add that later as an "option".
The basic unit will still have to be set manually after each power-cycle
(still no backup battery system, either; just going with "simple" here).

Not using "neopixels" per se, instead using what adafruit.com calls
"dotstars", which are similar RGB pixel units except with two-wire SPI
daisychain, which relaxes the timing requirement.

Using ATmega328P-20, rather than my traditional mega168.  This will leave
much more room for features and updates, as right now the code would
still fit in a mega168.  I had anticipated possible supply problems with
the mega328, since it is used in Arduinos; but seems like there is available
stock as of Nov 2022 (Arduinos prob mainly use the SMD packages anyway).

All-in-one TTL crystal clock oscillators, like I've always used since the
1980s, are now so relatively costly that I'm breaking down and going with
a plain 20MHz crystal, plus the two capacitors.  Don't need to clock any
other chips anyway, and the AVRs can drive their own crystals, so no reason
not to -- I just like the clean look of the modules better!  But not 4000%
better...

(Later note: so far I'm not yet convinced on these discrete-crystal
clock circuits.  I noticed the oscillator was highly-susceptible to
finger capacitance/resistance; and I've also seen various signs that
make me suspect the system is not keeping very good time, though I have
yet to test this extensively.  The old 20MHz oscillator modules seemed
to be rock-solid, both in terms of stability and in terms of frequency
precision.  I assumed the raw-crystal approach would give just about the
same results, but so far, I seem to be rather wrong.  May have to go back
to the costly modules: they are a convenient solution to what I didn't quite
realize was such a problem!

Oh and also, contrary to my implication above,
it would be possible to clock additional chips with the AVR, when using the
crystal oscillator.  If only it worked (or, if only these bargain Amazon/Ebay
crystals weren't so horrible, perhaps).)

----
Dotstars.

This design uses 60 dotstars; 60 is, conveniently, exactly the number
available in a one-meter strip from adafruit.com (roughly $30 in 2022).
(I promise: I did the design first, then found that it fits into 60!
Initially, I was actually looking at an 8x8 array, or 64...)
The resulting spacing of the dotstars, 1.66cm center-to-center, becomes
a fundamental metric which determines the overall size of the display.

In order to minimize cutting of the strip and the associated hand-wiring,
the alphanumeric digits are sized so that the two pairs of vertical "side"
segments on each digit, top and bottom, are provided by consecutive
dotstars in two horizontal strips of 10: i.e., the width of the digits
(to the center of each segment) is 1.66cm.  The two "middle" vertical
segments, and also the three horizontal segments, are provided by
vertically-oriented strips of 5 dotstars.  Thus, the spacing between
the three horizontal segments is 3.3cm, and the total height of the
digits is 6.6cm.  (The width of the dotstar flexible strip is just
narrow enough that one strip can cross another at right angles, and
the strip on top can tuck in between two dotstars on the strip
underneath, so that nothing is blocked; the strip on top can thus
insert a third dotstar in between two at the 1.66cm spacing, which
provides for the "middle" vertical segments in my 9-segment alphanumeric
display design.)

The colon is provided by a separate small strip of 2 dotstars.
The am, pm, degree, and percent symbols are provided by the dotstars
on the ends of the two 10-dotstar strips which also make up digit segments
as described above.

The Moon is lit by a strip of 6 dotstars across its equator, so its
circumference is fairly restricted.
Placing the twelve Zodiac symbols in a reasonable
spacing around the Moon, basically determines the full extent of the layout.
The Zodiac symbols are lit by 6 2-dotstar strips, so that only 5 (rather
than 11) 4-wire interconnections are needed for this portion.

Visually, the layout of the dotstar strips is like this:

                  Z   Z

          Z                   Z

        Z                       Z

          M   M   M   M   M   M

        Z                       Z

          Z                   Z

                  Z   Z


        C       D       E       F

  A   A C A   A D A   A E A   A F A   A

        C       D       E       F
                    G
  B   B C B   B D B   B E B   B F B   B
                    G
        C       D       E       F

So the one-meter strip of 60 dotstars is cut into:
  (2) segments of  10 dotstars  A, B
  (4) segments of   5 dotstars  C, D, E, F
  (1) segment of    6 dotstars  M
  (7) segments of   2 dotstars  G, Z

Counting the input connection from the AVR, that makes a total of
14 4-wire interconnects, or 112 solder joints.  Lots of hand-work, but
still much easier than trying to use 60 discrete SMD "dotstar" components,
though this would be cheaper.  Of course, a fabbed circuit board would be
an obvious improvement to the manufacturing process, along with some way
of 3-D printing or otherwise automagically fabricating the "labyrinth".

The entire layout, at this scale, fits snugly within the 8" x 10" format
of a common size of "shadow box".  These boxes are available from
the Internet or hobby stores; I prefer the type with a plastic, rather
than glass, faceplate.  The depth of the shadow box provides enough
room for a "labyrinth" constructed from 1/2"-wide cardboard strips,
along with the thickness of a foam board behind which helps to keep
everything compressed into a tight package; a rectangle cut into the
foam board provides clearance for the circuit board with the ATmega328P.

----
Colours.

Regarding the way colours are defined and set in this system.
To facilitate the multiple colour-setting modes I want to support
(Hue-only, HSV, and RGB), I am defining a rather non-optically-correct
way to convert between RGB and HSV.  All colours are stored as
3-byte RGB values.  If the user chooses to edit a colour in HSV mode,
then the conversion is done as follows.  The R, G, and B components
are first (in effect) sorted by intensity.  The two brighter components
are considered in determining the Hue and Value; the third component
is ignored.  The Value is taken to be the value of the brightest component.
The Hue is determined by scaling the two bright components such that the
brightest one is 255.  The scaled second component then uniquely determines
the Hue.

The Hue is defined as a two-byte number between 0..1535, which represents
index position in the typical "full power" RGB spectrum cycle:

    colour   R   G   B    Hue        change
    -------  --- --- ---  ---------  ------
    red      255   0   0     0-255   G+
                   +
    yellow   255 255   0   256-511   R-
               -
    green      0 255   0   512-767   B+
                       +
    cyan       0 255 255   768-1023  G-
                   -
    blue       0   0 255  1024-1279  R+
               +
    magenta  255   0 255  1280-1535  B-
                       -
    red      255   0   0     0
    -------  --- --- ---  ---------  ------

(****  Actually, the table above is not quite correct, this ends up repeating
the "endpoint" colour values (such as 255 255 0, once for end of "red", then
again for beginning of "yellow").  Red should just go up to 255 254 0, and
then 255 255 0 is the first step of "yellow" range.
Highest Hue number then becomes 1530.  ****)

  (Later note: the correction above is true, it should only go to 1530,
  but in fact I've gone with the "naive" approach as shown in the table,
  so certain hues are redundant (R255 == Y000, Y255 == G000, etc.).
  This allows the hue value to be conveniently split into two bytes:
  the upper byte represents the colour range, one of six as seen in
  the table above; the lower byte represents the position within that
  range, 0-255.)

So, one RGB component (at least) is always 255, one is 0, and one varies,
ramping up or ramping down, for each position in the cycle.

Once the Hue and Value are determined, the Saturation is defined to be
255 minus the value of the third (dimmest) RGB component, scaled
by the same factor which made the brightest component equal to 255.
  (...Or something along those lines.  Not sure about this Saturation
  math, yet...)
So, if the third component is 0, then Saturation is 255.  If the other
two components are both 255, then as the third component approaches 255,
the Saturation approaches 0 (i.e., white, regardless of the Hue value).

I'm pretty sure this definition of HSV is not optically correct!
But it is numerically simple to implement, consistent and reasonable in
behaviour, and it makes HSV <-> RGB conversions smooth and unambiguous.
  (I hope.)

In either the HSV mode as described above, or the RGB mode, the user will
have the ability to set any of the RGB components to any value, 0..255.
There will also be provided a "simplified" hue-only mode.  In this mode,
a hue between 0..153 can be selected, or "W" for white.  The hue number
is multiplied by 10 to create the actual Hue value; the Saturation and Value
are fixed at 255.  So, the user gets a reduced palette of colours, with no
option for desaturated colours (except white).  When entering this mode,
the original colour value is "bit-crushed" into this format, by finding
the Hue from the two brighter components as described above, dividing it
by 10, and simply discarding the third component (Saturation = Value = 255).

  (Later: I do have the three colour editing modes as discussed above,
  but the Hue-only mode does not "bitcrush", it still permits access to
  the full range of available Hue numbers.  The mode simply does not
  provide a means to change the Saturation or Value of the edited colour:
  these values remain the same as the "factory" colour set.  (Thus, all the
  colours are 100% saturated except the Moon colours: one of which is
  White, and the other is a reduced-Value, desaturated blue.  So the user
  could change the Moon colours to any crazy hues they might like, but the
  "occult" colour will always be darker in Value than the "bright" colour.))

----
RGB<->HSV Not So Simple...

  (Later:  Is it pretty obvious that I haven't fully thought this out,
  above?  Heh, heh.  Yep, gets kinda complicated, and all my hopes of
  somehow avoiding the serious complexity and coming up with a
  quick-n-dirty way to convert RGB<->HSV, oops, doesn't work so well
  when I start making demands like it has to be reversible etc...  So
  who's surprised?  So yeah, working on some more complicated math, because
  I do want to have the full HSV mode with 1535 Hue settings, etc..
  But ignore my hand-waving above, near (I hope.).  And to initially get
  things operating, I can probably just implement RGB and Hue (i.e.,
  simplified hue-only) modes.)

----
Tue 29 Nov 2022 01:14:34 AM PST

segtable.h
seg_nums.pdf

I've redesigned the display layout slightly, resulting in a new
mapping of the same 60 dotstars, as reflected in segtable.h.
The physical change is just that "am" and "pm" are moved to the
left edge of the alphanumeric display, instead of underneath digits
3 & 4.  This tightens up the display visually and spatially, and
also makes the construction and wiring slightly more regular.

The old layout is preserved in segtable_orig_wiring.h, which is of use
to me because I have one prototype wired that way.  However, any units
I build in the future will use the new layout as in segtable.h, and
I'd recommend that anyone else wishing to copy/modify this design
should go that way too.

See the scan of my working drawing, seg_nums.pdf.  As well as giving
the dotstar addresses, this drawing is to-scale and can be used to
position the partitions in the "labyrinth" which directs light from
each LED only to its intended segment in the display.  In my prototype
and small batch of units, I am making these labyrinths from thin
cardboard, carefully cut (with a paper-cutter) and glued together,
very labour-intensive but good results.  I use a reverse-side image
of the one in seg_nums.pdf, and glue the partitions right to the
paper, which then becomes the diffusive front face of the display
(the un-printed side of the paper), behind the pierced foil mask.

But the labyrinth would be an obvious candidate for 3-D printing, and/or
other "21st-century" fab techniques.  Even if I could laser-cut a bunch
of flat pieces, let's say out of thin plywood like those slot-together
dinosaur skeleton models, this could make a set of parts which were then
relatively quick to glue together, unlike the painstaking paper-cutter
method.  Of course the same technique could produce the rest of the box,
and probably the display mask itself as well, if desired.
To be explored in the future...

----
Fri 02 Dec 2022 11:20:21 PM PST

font.txt
mk_font.sh
font.h

The "font" is the mapping of ASCII character values to the pattern of
on and off segments in the 4-digit, 9-segment display.  The function
display_string() renders the given four-character ASCII string on
the display, using the font specified in font.h.

Notice that the six extra symbol segments
(am, pm, colon dots, degree, and %) are not considered part of the font,
and are handled separately after a call to display_string().

Changes to the segment states do not actually appear on the display
until paint() is called.

font.h is mechanically generated by the mk_font.sh script, from the
input file font.txt.  To change the representation of selected
characters, or to add additional characters, edit font.txt, then do
  ./mk_font.sh < font.txt > font.h
and then recompile moon_clock_3.c ("make -f Makefile.avr-gcc").

From the comments in mk_font.sh:

#  Usage:
#    ./mk_font.sh < font.txt > font.h
#
#  For Moon Clock 3.  Creates the font[] table, i.e., a mapping of
#  selected ASCII character values to on/off bits for each
#  segment of a digit in the 9-segment alphanumeric display.
#  Because there are 9 segments per digit, each table entry requires
#  two bytes, although the second byte is only ever 0x00 or 0x01.
#  Any character value not found in the table is rendered same as ' ',
#  i.e., all-segments-off.
#
#  The 9 segments form the usual 7-segment numeric display, plus two
#  additional vertical segments down the centerline.  This arrangement
#  allows a semi-legible display of many, but not all, alphanumeric
#  characters.  Characters such as 'T', 'M', and 'W' become possible,
#  so considerable expressive potential is gained for only a few more
#  segments.  The usual alphanumeric displays (as in the VFD display
#  in the "Speak and Spell") have many more segments per digit: not
#  feasible for this design.  Instead, we simply try to avoid use of the
#  "difficult" characters (e.g. 'Z').  And a certain idiom of spelling
#  tends to be helpful: many times it's better to mix upper and lower
#  case, for legibility.  E.g., uppercase 'L' is unambiguous, lowercase
#  'l' tends to be confused with '1' and perhaps 'I'.  Also, certain
#  letters such as 'S' and 'P' have only one representation available,
#  regardless of the case requested (both 'p' and 'P', 's' and 'S', etc.,
#  appear in the font table, but the associated segment data is the same).
#
#  Digit segments are numbered top to bottom, left to right, like so:
#
#    0
#  1 2 3
#    4
#  5 6 7
#    8
#
#  Each line of font.txt starts with the given ASCII character, then
#  nine bit values, 1 or 0, for the nine segments, 0-8, in order.
#  Each token must be separated by whitespace.
#
#  Example portion of font.txt:
#  a 1 0 0 1 1 1 0 1 1
#  A 1 1 0 1 1 1 0 1 0
#  b 0 1 0 0 1 1 0 1 1
#  B 1 0 1 1 1 0 1 1 1
#  c 0 0 0 0 1 1 0 0 1
#  C 1 1 0 0 0 1 0 0 1
#  d 0 0 0 1 1 1 0 1 1
#  D 1 0 1 1 0 0 1 1 1
#
#  (Be aware, there's no support for comments in font.txt.)

(I have to note, the statement above is true, there is no explicit
support for comments in font.txt; however, it is easy to add comments
if desired, at the end of any line of data in the file.  The first 10
tokens on a line are processed by the awk script within mk_font.sh, but any
text afterwards is ignored, to the end of the line; thus, this could be
a comment.  It is not possible to have comments on their own lines.
I'd recommend that comments be started with '#', even though it isn't
required, in case the font.txt format gets re-used or extended, perhaps
coming to be processed by more-sophisticated programs which do recognize
"real" comments.)

----
Fri 16 Dec 2022 12:11:57 AM PST

Progress: first code-burn, "hello world".

    avrdude -u -p m328p -c usbasp -t
    avrdude -u -p m328p -c usbasp -E noreset -U flash:w:blink_b0.hex 
    avrdude -u -p m328p -c usbasp -E noreset -U flash:w:moon_clock_3.hex 

Several firsts here:  First time using the ATmega328P.
First time using discrete crystal and capacitors, instead of TTL osc module.
First time programming via USB, using USBasp.

Took a while to get to this point!  When, at first, there was "no answer" from
the device, I didn't know which of the "firsts" above, might be the problem --
or something else.  After much futzing and debugging and wishing for a scope,
I found that amazingly enough, the ribbon cable that came with my Chinese
USBasp programmer was constructed wrong: one of the connectors was flipped.
(This would seem to indicate extremely low-tech, labour-intensive production:
just some guy with cable-crimpers in his hand, reaching into a bin full of
connectors and crimping them onto pre-cut cable lengths, one at a time.  Ouch!)
Anyway, once I remedied that situation, everything worked fine.

I now have a debug LED on PORTB:0, flashing at 1Hz.  Everything else
from here on out should be a Simple Matter of Programming.  Heh.

  (See note, further back in this file, about troubles I've been
  having with accuracy and stability from these discrete-crystal
  oscillators.  Simple Matter of the Usual Same Old Story of Engineering!)

----
Sat 21 Jan 2023 12:04:07 AM PST

Started this project just about exactly 4 months ago, at the Autumn Equinox.
Now I hope to have the four finished units shipped out by Feb 2 2023, if
not sooner.  Three Pagan Quarter holidays.

----
Source: README, updated 2023-01-29