A standard real-time clock is sufficient to check the time of day, while some prefer a radio-controlled one. But for other tasks like building a high precision and stable frequency counter, you might want one that can be read electronically, for example from an Arduino or a Raspberry PI.
By the way:Before I start with the story I would like to give you a hint to an Arduino clone with a replaceable crystal: the "OPEN-SMART UNO R3 DIY ATmega328P" offers a socket to insert any crystal you got. So, if you buy a bunch of 16 MHz crystals you can select the best of them, provided you got a calibrated frequency counter.
When I checked some of the Arduino sketches for building frequency counters I found the one published by Nick Gammon and another one by Paul Stoffregen .
Gammon's version is helpful to understand what happens inside, while Stoffregen offers a library hiding everything from your eyes, and it looks like his version is more precise.
As a first test you can connect Arduino-pins 6 (OC0A = Timer-0 output) and 5 (T1 = Timer-1 input) and insert this code
[code]
// using Timer0 as generator
// the counter counts itself
TCCR0A = B01000010;
TCCR0B = B00000001; // = 4 MHz
OCR0A = 1;
pinMode(6, OUTPUT);
[/code]
before the counting procedure is called just to produce a test signal without using external devices, and you should get constant results of 4 MHz printed on the Serial monitor or plotter. (When you use Gammon's code do not forget to comment-out the restoring of the TIMER-0 registers.) As in this case the Arduino tests itself, you also would get 4 MHz if your crystal's frequency was completly wrong.
Now let's start:The ceramic or crystal oscillators are neither precise nor stable, but the TCXOs (temperature compensated crystal oscillators) are usually not cheap. Roughly it can be said that if you want a device that gives you one more digit, the price goes up 10 times. I was surprised to find one that was originally designed for YAESU transceivers and therefore is produced in large quantities and sold at a moderate price. This TCXO shows a very high stability, but its output gives a clock rate of 22, 625, 000 Hz which is a very odd number, and it is not easy to get a standard time base to operate a gate for a frequency counter or anything else. And you couldn't use a microcontroller like an Arduino to divide the frequency because Arduinos don't like to count clocks faster than 8 MHz. The standard TTL chips can also easily divide by 2 or by 5. But the value 22, 625, 000 is equal to 181 * 125, 000. So before I could use this TCXO I had to design a circuit that could divide by 181 which is a prime. Once you have managed to divide the frequency by 181 you come up with 125, 000 Hz which can easily be divided with standard counters or even counted using software.
You might argue: why not divide by 5, 8, or 10 using a standard counter chip, for instance SN7490 of SN7493, in the first place and divide the resulting frequency (which would be 4,525,000 Hz or 2,828,125 Hz or 2,262,500 Hz that could be handled by Arduino) by software later? This is a legitimate question, but the simple answer is: when dividing by 181 in the first place you can get a lot of standard frequencies such as 5,000 Hz and 1,000 Hz and many more as a by-product.
So I had to study the Texas Instruments TTL cookbook. There are synchronous and asynchronous counters and generally you should prefer the synchronous ones. But to count up to 181 you need an eight bit counter, and with synchronous counters the complexity increases dramatically. For the asynchronous counters you just need a wide AND gate whose output is fed into the counter’s reset input. Asynchronous counters include a well-known disadvantage of possibly producing hazards giving bad counting results. So it is a good idea to double-check its results by applying a controlled clock to it and checking the results.
How to divide by 181?The sequence of the counter states would be like this:
- decimal - binary
- 0: 0 0 0 0 0 0 0 0
- 1: 0 0 0 0 0 0 0 1
- 2: 0 0 0 0 0 0 1 0
- ...
- 178: 1 0 1 1 0 0 1 0
- 179: 1 0 1 1 0 0 1 1
- 180: 1 0 1 1 0 1 0 0
- 181: 1 0 1 1 0 1 0 1 (invalid)
Only the states from 0 to 180 are valid states. As soon as state 181 is reached the counter should reset to zero immediately. When reaching 181, the bits d0, d2, d4, d5, and d7 are set. To decode this state you need an AND gate with five input lines.
Reset inputs normally are inverted, so an AND gate with five inputs should invoke the reset operation. As I could not find any AND gates providing five or more inputs I had to cascade two AND gates with four inputs each. The SN74LS21 offer two AND gates each of them providing four inputs.
This schematic shows the counter and the big AND gate:
Figure #1 (+Vcc and Ground are not shown)
In case you can not find an SN74LS21 you can also go for the SN74F08 (the fast version) as there are four gates cascaded.
Figure #2
But how can you verify that this circuit really works and no glitches will happen?
To check this I wrote a short sketch that send pulses to the circuit, reads the counter state and compares it to its software counter modulo the divider value. It shows the difference on the serial monitor. As for higher frequencies the serial monitor is too slow, the built-in LED will flash if the difference between the software counter and the hardware counter is not constant.
[code]
/*
For checking the frequency divider "1 by 181"
1 x SN74LS393: outputs a b c d - A B C D
1 x SN74LS21:
Cascade the counters of 74393 (pin-6 -> pin-13)
A & B & D --> AND-Gate#1
a & c & (Gate#1-out) --> AND-Gate#2
(Gate#2-out) --> RESET of both counters (2 & 14)
Connect Arduino clock output (pin-12)
to the input of 74393, pin-1
outputs of 74393 to Arduino:
low nibble to Port B:
(3) --> d8, (4) --> d9, (5) --> d10, (6) --> d11
high nibble to Port D:
(8) --> d4, (9) -> d5, (10) --> d6, (11) --> d7.
max frequency to test: 10 kHz
Some tricks are used to achieve
right-align in the serial monitor.
*/
const int divideBy = 181;
const byte clock = 12; // PORT B, bit 4
int cnt;
int diffOld; // to check for mismatches
void setup() {
Serial.begin(115200);
Serial.println(__FILE__);
pinMode(clock, OUTPUT);
pinMode(LED_BUILTIN, OUTPUT);
// on divide errors the LED will lit/flash
}
void loop() {
// very short HIGH-to-LOW, LOW-to-HIGH pulse:
asm("cbi 5,4 \n\tsbi 5,4"); // PORT B, bit 4
/* equal to, but faster
digitalWrite(clock, LOW);
digitalWrite(clock, HIGH); */
byte low = PINB & 0x0F;
byte high = PIND & 0xF0;
int val = high + low;
int diff = val - cnt;
if (diff < 0) diff = diff + divideBy;
if (diff != diffOld) asm("sbi 5,5"); // PORT B, bit 5
// equal to digitalWrite(LED_BUILTIN, HIGH);
// to increase the speed of the pulses
// just remove these print commands:
/*
delayMicroseconds(10);
printAll("cnt = ", cnt);
printAll(" val = ", val);
Serial.print("\tdiff = ");
Serial.print(diff);
Serial.println();
/* */
cnt++;
if (cnt >= divideBy) {
cnt = 0;
asm("cbi 5,5"); // PORT B, bit 5
// equal to digitalWrite(LED_BUILTIN, LOW);
}
diffOld = diff;
}
void printAll(char * s, int x) {
// add 0x200 to avoid left-align of BIN
Serial.print(s);
rightAlign(x);
Serial.print(0x200 + x, BIN);
}
void rightAlign(int x) {
char s[] = " ";
byte d0 = x % 10;
s[2] = '0' + d0;
x = x / 10;
if (x) {
byte d1 = x % 10;
s[1] = '0' + d1;
x = x / 10;
if (x) s[0] = '0' + x;
}
Serial.print(s);
}
[/code]
Comments