Sunday, 12 September 2021 17:15
administrator
Ofenquartz-Untersuchungen
April - Juni 2021
Vor einiger Zeit hatte ich eine Handvoll Ofenquartze (OCXOs) gebraucht bei Aliexpress bestellt für mein Frequenzzähler-Projekt bestellt. Diese wollte ich so gut es geht, auf Frequenzgenauigkeit prüfen.
Wer OCXOs nicht kennt: Bei klassischen Quartzoszillatoren hängt die Frequenz stark von der Temperatur ab. Ein Ofenquartz sitzt nun in einer beheizten und temperaturgeregelten Kammer aus Blech. Die Temperaturregelung sorgt dafür, dass der Quartz auf einer konstant hohen Temperatur bleibt. Diese Temperatur wird so gewählt, dass dort die Steigung der Frequenz als Funktion der Temperatur null ist. Üblicherweise handelt es sich um eine nach unten geöffnete Parabel mit Scheitelpunkt bei ca 75°C.
Der OCXO, den ich bestellt hatte, hat folgende technische Daten
Output Frequency |
10,00MHz |
Initial Accuracy |
≤ 200ppb |
Power Supply Stability |
≤ 2ppb |
Load Stability |
≤ 2ppb |
Aging |
≤ 100ppb / first year
≤ 400ppb / 10 years
|
Temperature Stability |
≤ 10ppb
|
Tuning |
0...4V (±2ppm) |
Wie am Tabelleneintrag "Tuning" zu sehen, kann dieser Ofenquartz noch minimal verstimmt werden. Bei 2V ist er bei nominal 10Mhz, kann aber um ±20Hz verstimmt werden, bspw um Alterungseffekte auszugleichen.
Wie kann man nun die Genauigkeit eines solchen Ofenquartzes prüfen? Man kann zum Beispiel eine Uhr bauen!
Zuerst einmal hab ich mir ein kleines Ofenquartz-Modul mit dem Ofenquartz selbst, einem 7805 Spannungsregler, einer BNC-Buchse und einem 20Gang Spindelpoti für den Tune-Eingang gebaut. Es hat ja keinen Sinn, den Ofenquartz zu kalibrieren oder zu vermessen, während er am Labornetzteil hängt. Beim nächsten Mal erwischt man eine minimal andere Ausgangsspannung des Labornetzteils und die Messergebnisse sind hinfällig, denn eine Abhängigkeit der Ausgangsfrequenz von der Versorgungsspannung ist zu erwarten. Spannungsregelung und Ofenquartz müssen also auf einer Platine gekoppelt werden.
Für eine Uhr brauchen wir ein genaues 1Hz Signal, keins mit 10MHz. Das Ausgangssignal vom Quartz wird also benutzt, um die CPU eines Attiny2313 zu takten. Zusätzlich wird das 10MHz-Signal vom Prescaler des Atttiny2313 durch 64 geteilt. Der Timer/Counter vom Attiny wird dann so aufgesetzt, dass er das durch 64 geteilte Signal nutzt, um bis 250 zu zählen. Ist er bei 250 angekommen, wird ein Interrupt ausgelöst. In Software werden die Interrupts gezählt. Erst wenn 625 Interrupts gezählt wurden, ist eine Sekunde vergangen, denn 625 * 250 * 64 = 10Millionen = 10MHz. Mit jeder Sekunde updatet man dann den Sekunden-, den Minuten- oder auch den Stundenzähler im Code. In meiner Realisierung wird die Uhrzeit dann über ein 2x20 Character LCD angezeigt.

Man könnte eine so gestaltete Uhr bspw um 12 Uhr mittags starten und so lange laufen lassen, bis merkbare Gangunterschiede zwischen ihr und einem Referenzsignal (zB einer Funkuhr) zu erkennen sind. Das Starten der eigenen Ofenquartz-basierten Uhr gelingt allerdings nie genau auf die Sekundenmarke. Außerdem kann man die Gangunterschiede nur durch manuelles, optisches Vergleichen von LCD und Funkuhr erledigen (="hingucken"). Man muss also warten, bis deutliche Gangunterschiede von mehreren Sekunden zusammen gekommen sind. Unter Umständen kann das also recht lange dauern.
Aus diesen Gründen habe ich der ganzen Geschichte ein DCF77 Modul spendiert. Das hat einen Datenausgang, der zu Beginn einer neuen Sekunde einen Low-to-High Übergang macht. (Die Pulslänge codiert dann eine binäre 1 oder 0, das ist hier aber nicht relevant.) Immer wenn der Attiny diesen Low-to-High Übergang an seinem Eingang detektiert, wird ein Interrupt generiert. Mit diesem Interrupt wird der aktuelle Wert des Zählers, der von 0 bis 625 zählt, auf dem LCD Display ausgegeben wird. Die Ofenquartz-Sekunde wird also in 625 gleichgroße Teile eingeteilt und auf dem Display kenntlich gemacht, wo sich der Zähler gerade befand, als die DCF77-Sekunde begann. Das wird jede Sekunde wiederholt, der angezeigte Zählerwert ist quasi eine Phasendifferenz zwischen Ofenquartz-Sekunde und DCF77-Sekunde. Am Display bekommt man eine gute Übersicht, ob der Ofenquartz zu schnell läuft (Zählerwert wird größer) oder ob der Ofenquartz zu langsam läuft (Zählerwert wird kleiner).

Wie im Foto zu sehen, habe ich den aktuell laufenden Langzeittest am 14. Juni begonnen. Da zeigte der Zähler ein Delay von 88 an. Heute, 92 Tage später, wird ein Zählerwert von 249 angezeigt. Einen Überlauf des Zählers (also einen Fehler über mehr als eine Sekunde) kann ich über die normale Uhrzeit zwar nicht mehr ausschliessen (denn die stimmt leider überhaupt nicht), aber eine mehr oder weniger kontinuierliche Beobachtung des Zählerwerts über die 90 Tage zeigte ein Ansteigen des Zählerwertes in den ersten Tagen und Wochen und danach ein Pendeln bei Werten um 230.
Mit diesen Informationen kann ich den Frequenzfehler berechnen: Die Laufzeit in Sekunden beträgt 92 * 24 * 3600 = 7948800. Der Fehler in Sekunden beträgt (249-88)/625 = 0,2576. Der Fehler beträgt also 0,2576 / 7948800 = 0,0324ppm = 32,4ppb. Das finde ich für ein gebraucht gekauftes 2EUR-Teil schon sehr respektabel.
Auch interessant zu beobachten ist das oben erwähnte Pendeln. Der Zählerwert, welcher sekündlich auf dem Display angezeigt wird, ist alles andere als konstant. Er variiert um grob 10 während einer Beobachtungszeit von ein bis zwei Minuten. Aber auch im Zeitraum von mehreren Wochen und MOnaten wäre der zeitliche Verlauf des Zählerwerts interessant zu sehen. Ich überlege, genau diese Phase zwischen Ofenquartz-Sekunde und DCF77-Sekunde über einen längeren Zeitraum aufzuzeichnen und zu plotten. Dem DCF77 Signal sagt man nach, nur langfristig stabil zu sein aber kurzfristig stärkeren Schwankungen unterworfen zu sein (u.a. wegen atmosphärischen Effekten). Das - verbunden mit Wetterphänomenen und Tageszeiteffekten - müsste man in einem solchen Plot eigentlich sehen.
Last Updated on Sunday, 05 March 2023 21:26
Wednesday, 24 March 2021 11:02
administrator
AD9834 at Raspberry Pi
March 2021
On AliExpress I saw cheap AD9834 DDS frequency generator modules like this one. They appeared to be fun little modules to play with, additionally I was working on my frequency counter and was missing a generator capable of generating more than 3MHz. Furthermore I read about this Farnell / Element14-funded DDS project. While buying the module, I loosely thought implementing a driver for the module in userspace C myself. Later, I found out that a kernel driver for the AD9834 exists since Kernel version 3.5a. So this is how to get the kernel driver to work.
The kernel driver itself can be found here. There is some official documentation from Analog Devices regarding the kernel driver here. However, it appears to be incomplete and inaccurate. Maybe I am also just lacking knowledge, anyway I was unable to make use of the information there so I had to do some additional work to get the kernel driver to work. This work is described here:
I did my work with a spare Raspberry Pi 2 and started with kernel version 4.9.35. The distribution was from the official Raspberry PI sources, so it was an unknown version of Rasbian / Raspberry Pi OS. As those official images do not come with a pre-compiled kernel driver for the AD9834 and also without the full kernel sources, I used rpi-source to download the correct version of the kernel sources. The linux kernel sources will be downloaded to /home/<user>/linux-xyz, with a symlink /home/<user>/linux pointing to it (xyz is a lang alphanumera string, possibly the commit ID?). I can then use
$ make menuconfig
from the linux source tree root (/home/<user>/linux) to enable the AD9834 kernel driver under Device Drivers -> Staging Drivers -> Industrial IO -> Direct Digital Synthesis -> Analog Devices AD9833/4 driver.
You are supposed to run
$ make prepare
after changing the kernel config so I did that. Finally, since we do not want to compile the whole kernel, but just one module, we execute
$ cd ~/linux/drivers/staging/iio/frequency $ make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
from where the driver code is located. Building the module worked fine, loading the module using
$ sudo insmod ~/linux/drivers/staging/iio/frequency/ad9834.ko
also worked. However, dmesg only contained two pretty general lines about tainting the kernel and warning about staging drivers. /sys/bus/iio/devices also did not contain anything. Putting some printk statements in the kernel module, re-compiling and re-running showed that the probe() function does not complete. Some platform data was missing. Some googling around seemed to show that this is a way of passing HW information to the driver, different from device tree. If I remember it correctly, I did not mess with device tree overlays too much at that point.
Since the offending function call was removed in more recent versions of the driver, I used $ rpi-update to update the kernel to version 5.10.23. While I was able to boot the new 5.11 kernel, it resulted in no working Wifi, no serial console on the GPIO pins and trying to get the kernel source files for 5.11 using rpi-source failed as well.
So I tried a different approach on a different SD card: I took the newest Raspberry PI OS Image (2021-01-11-raspios-buster-armhf-lite.img) and started with a fresh OS install. This version of Raspberry Pi OS came with kernel version 5.4.83. I then
- used $ rpi-source as above to get the complete kernel source tree,
- installed libncurses5-dev,
- entered root directory of the linux source tree,
- executed $ make prepare,
- used $ make menuconfig to enable AD9834 driver under Device Drivers -> Staging Drivers -> IIO Staging Drivers -> Direct Digital Synthesis -> Analog Devices AD9833/4/7/8 as module
- executed $ make prepare again
- cd linux/drivers/staging/iio/frequency
- $ make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
Before we can load the driver, we need to make sure three things:
First, we need to ensure device tree informatio is present and correct. Second, the AD9834 driver relies on the IndustrialIO (iio) subsystem. Therefore, we need to load that one first. Finally, we need to make sure that no userspace SPI drivers (spidev) are loaded.
Device Tree Information was not trivial to get. The Analog Devices page linked above gives an example device tree excerpt, but it was unclear to me how exactly a device tree overlay would need to look like. Furthermore, It states that both a reference to a voltage regulator as well as a reference to an oscillator are optional. The second is not true. When loading a device tree overlay without any clock reference, the driver probe() function fails. Below is a working device tree overlay.
Let's call the following file ad9834.dtso
/dts-v1/;
/plugin/;
/ {
compatible = "brcm,bcm2835", "brcm,bcm2708", "brcm,bcm2709";
fragment@0 {
target = <&spi0>;
__overlay__ {
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
ad9834: ad9834@0 {
#clock-cells = <0>;
compatible = "adi,ad9834";
reg = <0>;
spi-max-frequency = <100000>;
spi-cpol;
clocks = <&osc_ad9834>;
};
};
};
fragment@1 {
target-path = "/";
__overlay__ {
osc_ad9834: ad9834-osc {
#clock-cells = <0>;
compatible = "fixed-clock";
clock-frequency = <75000000>;
};
};
};
fragment@2 {
target = <&spidev0>;
__overlay__ {
status = "disabled";
};
};
};
We can compile this into a device tree overlay by executing
$ dtc -I dts -O dtb -o ad9834.dtbo ad9834.dtso
The first fragment is similar to what is given at the Analog Devices Wiki.
The second is the mandatory clock provider section. (For an actually pretty ok introduction into Device Tree Clock Providers and Clock Consumers and the theory behind this, read this) This basically specifies that the master clock used to clock the AD9834 is 75MHz. I wasn't aware of this need at first, but it makes sense that the kernel driver wants to know this frequency! Once the driver is installed, you can specify absolute output frequencies but actually, the register content of the AD9834 is obviously relative to its master clock frequency input. f_out = f_master / 2^28 * RegisterValue.
The third fragment disables the spidev userspace SPI driver. It would interfer with the AD9834 driver taking control of the ChipSelect CS0 line.
We can now manually load the device tree overlay using
$ sudo dtoverlay ad9834
and finally load our driver (after first loading the industrialio subsystem)
$ sudo modprobe industrialio $ sudo insmod ad9834.ko
Being able to manually load and unload the device tree saves us the constant rebooting effort. (For a nice introduction to the dtoverlay tool, see here). However, we want both the device tree overlay and the driver to be loaded automatically. So we add the line "dtoverlay=ad9834" to the bottom of /boot/config.txt and we need to add our new driver to the driver dependency database of the kernel, such that not only insmod correct/path/to/ad9834 works but also modprobe ad9834. Therefore, we execute
$ cd ~/linux/drivers/staging/iio/frequency $ sudo make -C /lib/modules/$(uname -r)/build M=$(pwd) modules_install $ sudo depmod $ sudo modprobe ad9834 (as a test)
After a powercycle, you should see the driver being loaded, and a new directory under /sys/bus/iio/devices
Now, we can use the device just like described in the Analog Devices Wiki. Some files are named different to what is shown there, but that doesn't stop us.

The somewhat weird "altvoltage0" part of all files is probably due to the missing voltage regulator. I haven't tested this though.
Last Updated on Wednesday, 08 March 2023 14:21
Wednesday, 24 March 2021 08:59
administrator
Einstellbare Stromquelle mit dem LM317
Februar 2021
Für eins meiner Patenkinder habe ich ein Activity Board gebaut, also ein Brett mit einer Menge Möglichkeiten, Feinmotorik und funktionale Zusammenhänge zu lernen. Da das Activity Board (so dachte ich) ins Schlafzimmer des Kindes sollte, wollte ich eine Art Nachtlicht einbauen. Hierzu gab die Bastelkiste eine 10W (350mA, ~10V) LED her. Diese LED musste nun weit unterhalb ihrer Spezifikation betrieben werden, damit ein kleiner Lichtschimmer erzeugt werden kann. Da ich nicht sicher genug einschätzen konnte, wie hell das Nachtlicht scheinen soll, sollte es regelbar sein. Und damit fingen die Probleme an...
Ich wusste, dass der LM317 als einstellbarer Spannungsregler die Möglichkeit bietet, auch als einstellbare Stromquelle zu dienen. Dazu muss ein Shunt so gewählt werden, dass darüber bei Nennstrom 1,25V abfallen. Der LM317 regelt dann die Ausgangsspannung so, dass der Nennstrom eingehalten wird. Das folgende Bild zeigt diese einfache Schaltungsvariante, welche sich auch in den meisten LM317 Datenblättern findet. Dort gilt dann R_shunt = R1 = 1,25V / I_soll

Wenn der Strom für die LED einstellbar sein soll, bräuchte man einen einstellbaren Shunt. Ein Poti mit hoher Belastbarkeit mit entsprechendem Widerstandswert hatte ich aber gerade nicht da, also habe ich mich nach Alternativen umgeschaut. Da hab ich hier den Schaltungsvorschlag für eine einstellbare Stromquelle gefunden. Um die folgende Diskussion zu vereinfachen, gebe ich hier die Schaltung wieder:

Mir war das Funktionsprinzip ehrlich gesagt nicht sofort klar, aber da angegeben war, dass der minimale Strom gegeben ist durch I_min = 1,25V / R1 (genau wie die Konstantstromvariante oben) und dass der Maximalstrom gegeben ist durch I_max = [1+ (R3/R2)] * I_min, konnte ich die Widerstände entsprechend dimensioneren. Im Bild oben sind die Werte des Originals angegeben, ca 125 bis 400mA.
Ich hatte in kleinen Vorstudien einen Strom von maximal 20mA bestimmt. Bei 20mA war schon eine Helligkeit erreicht, die bei indirekter Beleuchtung über die Zimmerwand mehr als genug Helligkeit für das dunkel-adaptierte Auge erzeugt.
Der erste Versuch schlug dann auch grandios fehl. Bei I_min = 2mA und I_max = 20mA kommt man auf R_1 = 680R, R2 hatte ich dann auf 12R und R3 auf 100R gewählt, um einen Faktor I_max / I_min von ca 9 zu erreichen. Das funktionierte überhaupt nicht.Die erste Erkenntnis war dann, dass R1 viel kleiner sein muss als R2 und R3. R1 ist als Shunt ja wie eine Art Innenwiderstand der Spannungsquelle U_R1. Nur wenn die Spannungsquelle U_R1 nicht nennenswert belastet wird, passt das Modell I_out = U_R1 / R1. Wenn R2 und R3 in die Größenordnung von R1 kommen, wird U_R1 stärker belastet und die Werte verschieben sich. Man kann auch sagen, der gewünschte Laststrom I_out fliesst dann nicht mehr nur durch R1, sondern auch durch R2 und R3, was unerwünscht ist und in den Formeln nicht vorgesehen ist.
Also wählte ich R3 = 50k und R2 = 5,5k. Das funktionierte auch nicht wirklich. Die zweite Erkenntnis war dann, dass der Strom in den LM317 über den ADJ Pin (bis 100uA) berücksichtigt werden musste. Bei 50% Potistellung erzeugen 100uA einen Spannungsabfall von 2,5V am unteren Teil von R3! Das kann natürlich auch nicht sein, denn soviel Spannung steht garnicht zur Verfügung.
Simulieren liess sich das Problem leider auch nicht wirklich, denn das mir zur Verfügung stehende LTSpice Modell vom LM317 (von hier) modellierte den Strom in den Pin ADJ mit nur ca 3uA. Also habe ich empirisch (mit dem Lötkolben und dem Widerstandssortiment) grob passende Werte bestimmt. Nach einer Reihe von Versuchen erschien mir R1 = 440R, R2 = 71R (200R||220R||220R) und R3 = 4k7 als halbwegs passend. Aus ihnen resultiert I_min = 6,8mA und I_max = 24,8mA.
Man erkennt allgemein gesagt, dass man sich bei so kleinen Stromwerten in einem Zielkonflikt befindet. Auf der einen Seite müssen R2 und R3 groß sein, damit sie die Spannungsquelle R1 nicht so stark belasten, zum anderen dürfen R2 und R3 allerdings nicht zu groß werden, sonst wirkt sich der Strom I_adj zu stark aus.
Schlussendlich habe ich eingesehen, dass das nicht das richtige Vorgehen ist, und dass man hervorragend mit einem normalen Poti als Shunt arbeiten kann, da die Ströme so gering sind. R2 und R3 werden weggelassen. R1 besteht aus einem fixen Teil von R1a = 100R und einem einstellbaren Teil von R1b = 500R. R1 = R1a + R1b. Damit erreiche ich einen Strombereich von 2mA bis 12mA, was zum einen vollkommen ausreicht und zum anderen das Poti nicht über Gebühr belastet. Man kann sich gedanklich ein bisschen im Kreis drehen, wenn man versucht, die am Poti abfallende Leistung P_R1b zu berechnen. Am einfachsten geht es so:
Die Leistung über R1b wird ausschliesslich verändert über den Strom I_out. Also ist P_R1b maximal, wenn I_out = I_max. Bei I_max ist die Leistung über den gesamten R1 bestimmt durch P_R1 = R1 * I_max^2 = 0,0144W. Dieser Wert ist absolut unproblematisch für ein normales Poti. Selbst günstige Potentiometer sind für 100mW ausgelegt. Diese Leistung teilt sich auf R1a und R1b auf. Nun ist bei I_max R1b = 0, die Leistung fällt also komplett an R1a ab. Wenn R1b vergrößert wird, steigt zwar relativ gesehen die Leistung, die an R1b verbraten wird, absolut gesehen verringert sich die Gesamtleistung jedoch, so dass auch dann keine Gefahr droht.
Das Activity Board von Vorne... |
...und von oben |
 |
 |
und das aktivierte Nachtlicht |

|
Last Updated on Thursday, 25 March 2021 19:57
|