The first version of this project's hardware was developed in the fall of 2019. Due to how my school/co-op cycle worked I only had time to work on this project between September and December 2019. Since four months wasn't really enough time to get things done properly I rushed a lot of stuff, specifically the v1 PCB design, and as a result it didn't really work properly. Therefore this post will explain (one method of) how not to build a digital real time clock.
To begin prototyping I basically just connected some devboards on a breadboard. Recalling the block diagram in my last post The initial prototype implemented the various blocks as follows:
for testing purposes this was just the output of my function generator. This is not nearly accurate enough to meet my time keeping specification, but it did let me work on developing the RTL for the counting logic.
This was implemented with a TinyFPGA Ax2 devboard. This is a very barebones (and easy to use) dev board for the Lattice LMXO2-1200HC-4SG32C, which is a basic fpga/cpld in Lattice's machXO2 family of devices. This part was selected entirely based on the fact that the TinyFPGA dev board was readily available to me, and reasonably well documented. Probably there are better ways to select parts for a project, but oh well.
The time computation was handled with an ESP32-WROOM32D devkit-c development board. This was selected since I had it in my parts box all ready and I all ready knew how to put micropython on the ESP32.
Display and Set/Reset logic:
I basically ignored this for the earliest prototype, and just had all of my i/o via the devkit-c's USB serial port.
A recreation of the initial prototype:
The initial prototype was mostly used to develop the RTL for the FPGA. I spare the gory details of the exact counting logic implementation (the code is on github here if you really care), and just summarize how the counting logic works.
Basically, the RTL runs off of the FPGA's internal resonator which is faster than the master oscillator frequency (FPGA clk ~80MHz, master oscillator ~10 to 20MHz), and every tick it checks to see if the output of the master oscillator has risen. If it has then the FPGA increments a register. when the value in this register is equal to the master oscillator's nominal frequency we know that one second has elapsed, and a signal is generated to increment the Seconds Register. The Seconds Register holds an epoch (some absolute number of seconds since a known time), and is the basis of the time count. I use a 64 bit register to hold the unix epoch because the universe began on 01 Jan 1971.
The other part of the RTL is a state machine that implements a simple SPI protocol that allows a bus-master to query the FPGA for the current value of the epoch, and to allow the bus-master to load a new epoch into the seconds register.
There was also some non-sense with external triggers that I abandoned part way through. (the idea was basically to allow the FPGA to measure the time that elapsed between two trigger signals)
The above is all well and good, but it glosses over two important details:
- comparing a non-power of 2 number to the value in a register sometimes misses, especially if the register is incrementing quickly since the value of the register might not be stable for long enough for the comparison to happen reliably
- polling the state of the master oscillator introduces some error in the time count, since a rising edge on the MO, might not be detected until nearly one FPGA-clk has elapsed
Issue 1. turned out to be important since I want to be able to synthesize a 1Hz signal from non power of 2 master oscillator signals. The solution is pretty straightforward though. Basically I just pipelined the divider so that the first stage is a reliable rollover counter with a width equal to the largest power of two less than the MO's nominal frequency, and the second stage, which is now much slower, is a less reliable comparison counter which derives the required 1Hz signal. There's probably a better way to do this, but this seems to work for now.
Note: the rollover counter doesn't guarantee that an MO edge is never missed, it just doesn't runaway if a comparison is missed, so I think it might still be a potential source of error. I'd need 30mins and a whiteboard to explain what I mean here properly, so @ me if you really care for some reason lmao.
Issue 2. This is a known source of error with an upper bound, and I'm not smart enough to think of a better way to deal with it. I guess the counting logic was bound to introduce some amount of error somehow (I bet there's a famous result that I don't know about in information theory that proves this)
ok. that was more RTL talk than I wanted to write, but whatever
once I the counting logic working on my bench it was time to start making a board, which required selecting components. The big items were the Master Oscillator (MO), FPGA, and the microcontroller.
The FPGA was easy, since all of my designs thus far had targeted the Lattice part in the tinyFPGA Ax2 I just picked it. (hard to solder QFN-32, but c'est la vie)
For the master oscillator I scraped through Digikey for a part that met my accuracy specifications, and operated on 3v3 (required to ensure that I didn't need a level shifter between the MO and the counting logic, which is just another source of potential error). I ended up with the OX4150A-D3-1-20.000-3.3-7 which is an oven compensated crystal oscillator with a frequency stability of ~10ppb.
(This is well in excess (3 orders of magnitude!) of the ~12ppm required to be more accurate than my Casio wristwatch, but the accuracy of a real time clock and frequency stability of an oscillator are different (but related) things. (even deeper aside, it seems like my other wristwatch, a Timex, might be more accurate than the Casio, but Timex doesn't publish good datasheets for their watch movements...))
For the microcontroller I abandoned the ESP32 for an atmega328pb since writing embedded code in python is cheating. (and also I didn't want wi-fi and bluetooth).
For the display I just picked a generic four row character LCD from digikey.
These parts and the associated support circuitry (voltage reg, and FTDI USB/UART bridge) were haphazardly slapped on a board, assembled, and programed (ish) during my Christmas break. The result was the following:
Which looks neat, but had some serious problems.
- power: the power supply is a 12V junk box special, and the clock runs on 3v3, so a buck converter was required. This fine, but during bringup I manged to short out the board's power rails and explode some buck converters :(
- this shorting happened because I didn't have a 3v3 connection on the atmega's ICSP header, and needed a flywire to make my programmer work
- the footprint for the OCXO was mirrored (confusing datasheet drawing)
- insufficient protection on the USB input. The sheild was left floating, and I didn't TVS diodes on the signal wires (dumb, huh?) and this resulted in some dead FTDI chips
- mixed up micro to FPGA/Display connections (partially fixed in software, partially fixed with bodge wires on the bottom of the board)
- weird intermittent failures/low assembly yield of the FPGA and micro circuits (seemed like maybe an ESD issue, but now I think it might have to do with the flux I was using. more on this later)
- not a great case (I didn't have a 3d printer yet)
despite its myriad flaws, The V1 hardware worked enough that I was able to get my software tools figured out and upload some testcode, before I fried my last voltage regulator.
After that I just went and bought a 3v3 power supply for this project and bypassed the burned out buck converter, but then I started running into the aforementioned intermittency issues with the micro. I was also completely out of time, since a new school term was starting.
In the next post I'll explain why this project was mothballed for all of 2020, what happened to the V2 hardware design, and the state of the current V3 hardware.