Overview
The entire firmware of the NTPClock was written in the assembly language of the MCU; while it also saves code space, the main reason for
this choice was the need to have control over timing at the instruction cycle level for display and sound generation. Arguably, the PIC assembly is
not particularly user-friendly; to remedy this, I added a set of macros that replace or improve the most counter-intuitive instructions.
The firmware code can be found at the NTPClock GitHub
repository.
Firmware Architecture
The NTPClock firmware has two main components:
- Background code — Handles the display and sound generation
- Timer 0 ISR — Manages time-keeping and the Control button state machine
The background code is a very simple infinite loop with only two subroutine calls; one that converts the binary clock field values to decimal, and
the other that generates the display on the spinning Nixie and produces the sound. However, the devil without a doubt is in the details...
Timing and Time-Keeping
In the NTPClock, the MCU's clock source is the built-in high-speed oscillator using an external 19.6608 MHz crystal. While this number
may not say much at first glance, recognizing that it is in fact 218⋅3⋅52, the reason this crystal exists becomes more
clear. Since in the PIC one instruction cycle consists of 4 clock cycles, the free-running 8-bit Timer 0 clocked through the 1:256 prescaler
setting will trigger an overflow interrupt at 75.0 Hz, which means that counting out an exact 1‑second time period is simple.
Furthermore, the 13.3 ms cycle time is long enough to debounce the Control button, but short enough to discern the different button press
patterns.
Generating the Display
The full display circle is output as two half-circle sections, referred to as arcs - one for the time and one for the date. Each arc is
divided into 1000 segments called points. The firmware controls the timing required for the different angular displacements of the Nixie tube
in point units via the very carefully crafted PtDelay subroutine, which is wrapped into the Point macro to provide an intuitive interface.
Most point-values come directly from the desired display geometry. One exception is the amount of time a digit is lit up on the Nixie (defined by
the cDIGLTP constant in the code), which was experimentally tuned to ensure that the digits are bright enough, but not
smudged.
The different scenarios are explained below. The attached figures demonstrate the two arcs of the display; orange represents the actual printouts,
while the gray sections stand for the "padding" of the arcs. The darker gray post-string pause carries special importance, as it determines the
speed and direction of a display pattern's scroll.
Scrolling Display Patterns
When the sum of delays in the arc results in the Nixie tube covering other than 180° along its path, the display will rotate. Point delay
ΔP is the value, by which the post-string pause needs to be modified to get a certain display scroll; it is calculated as:
ΔP = 1000 ⋅ ωscroll / ωspin
where ωscroll is the angular velocity of the display scroll, and ωspin is that of the Spinning
Board with the Nixie tube (360 rev/min). Note that for clockwise display scroll ωscroll is negative, as the floppy motor is
spinning anti-clockwise. A scenario like that is illustrated by the figure below; since ΔP is negative (i.e., the post-string pause is
shortened), the two arcs will add up to less than 360° total. The white gap section does not actually occur; rather, a new arc begins as soon as the
previous one ends, causing the display to scroll clockwise.

Stationary Display Patterns
For these patterns, ΔP is set to a small positive value, but rather than allowing for an anti-clockwise scroll, the arc is "cut to size"
because the post-string pause is terminated as soon as the Index Hole is seen by the photodiode. The figure below visualizes this scenario. The yellow
dot marks the position of the Nixie tube at the moment when the Index Hole is seen; this ends the post-string pause, which otherwise would have
stretched all the way to the dotted line. (The amount of angle distortion is exaggerated for demonstration purposes; in reality, it is a mere 5 points
per arc, which means 1.8° over the entire circle.)

Note that the Index Hole's transit only happens for one of the arcs; the other one will "time out" naturally. This also informs the firmware that
the upcoming arc will be in the front.
Quasi-Stationary Display
When ΔP is set to zero, the display becomes stationary, at least in theory. I first added this as a test mode to tune the different timing
constants, then decided to keep it as an interesting feature. Because the clocking of the PIC and the floppy motor electronics are asynchronous,
slight variations in the frequencies (e.g., due to changes in temperature) will cause the display to eventually creep away from its original position.
With the addition of the sound engine, the aim at perfect synchronization is no longer applicable; this made the Index Hole even the more essential
for [true] stationary patterns.
MagnetoSpin
I discovered this pattern totally by accident during experimentation. In this mode, the display scrolls anti-clockwise quickly, but slows down when
the arcs are around the middle position, so that the one in the front can be read out. This is achieved by reducing ΔP from a large to a small
positive value during the Index Hole's "transit" over the IR LED.
Generating the Sounds
My measurements determined that the MCU spends approximately 99.7% (!) of its time (about 200 µs per each 83.3 ms arc) in
the dumb spinning loops of the PtDelay subroutine, which is an egregious waste of computing resources. However, the PIC
being too limited for mining crypto or training LLM's (the latter wasn't even a trend at the time), I got the idea to use these idle periods to
generate sound via a piezo buzzer, which was attached to a still available GPIO.
The sound engine that toggles the buzzer's GPIO purely from firmware was meticulously worked into the inner loop of
PtDelay. The calculations that determine how to put the desired frequencies on the GPIO are rather complex, and were done offline. The results,
however, could be captured in a look-up table, so the sound generation code itself is relatively straightforward.
The smallest unit of note duration the sound engine can play is the time it takes to draw one arc. This is associated with the length of the
sixteenth musical note, and the commonly used multiples are also supported, all the way up to the whole note. There is also an option to sound
out a note through the entire period vs. "staccato-style" for the Manic Miner and Jet Set Willy jingles. Writing tunes is made easy by the formalism
provided by constant definitions and the N(pch,dr) macro, which allow specifying the pitch and duration of a note in an
intuitive form. For example, N(G5,V1_2) represents a G5 half note.
|