RPi Pico HF-Generator 7074,0 kHz                    

            

Elektronik-Labor  Projekte  Mikrocontroller  Raspberry     





Das Ziel war es, eine quarzgenaue Signalquelle mit 7074 kHz zu bauen. Stellt man diese neben einen KW-AM-Empfänger, kann man die FT8-Signale im 40m-Band hören und dekodieren. Der eingesteckte Widerstand erinnert an einen stark verkürzten, endgespeisten Dipol. Aber weil der Empfänger im Nahfeld des Senders steht, kann man diese Antenne eher als eine Hälfte eines sehr kleinen Kondensators betrachten, sodass das Signal kapazitiv in den Empfänger koppelt.

Ich hatte ja bereits eine Lösung mit  einem Timer, der als Frequenzteiler eingesetzt wurde (Rpi Pico Stand Alone VFO). Aber da konnten zwar stabile Signale, aber nicht jede beliebige Frequenz eingestellt werden. Inzwischen gab es aber zwei Lösungen, die noch viel mehr aus dem Pico herausholen:

https://github.com/dawsonjon/101Things

https://github.com/RPiks/pico-hf-oscillator

Besonders die Lösung von  Roman Piksaykin, R2BDY fand ich spannend, wenn auch nicht ganz einfach zu verstehen und anzuwenden. Ich wollte deshalb einen ähnlichen, aber möglichst einfachen Weg versuchen, den auch Leute nachvollziehen können, die keine Profi-Programmierer sind. Ich verwende daher die Arduino-IDE und nutze den zweiten Prozessorkern des Pico, wie ich es schon im Buch RPi Pico Schaltungen und Projekte gemacht habe. Das HF-Signal wird von einem PIO-Assemblerprogramm vfo.pio einfach durch Ein- und Ausschalten eines Ports mit einer Zählschleife zur Verzögerung erzeugt.


.program vfo
    set pindirs, 1
loop:
    pull        ;1
    out x, 32        ;1
loop2:
    jmp x-- loop2      ;1+x
    set pins, 1 [4]    ;1+4
    set pins, 0        ;1
    jmp loop        ;+1=10+x


In der der loop2-Schleife wird der Port mit einer zusätzlichen Wartezeit von vier Taktlängen eingeschaltet und dann wieder ausgeschaltet. Vorher gibt es noch eine variable Zählschleife, die x herunterzählt und damit die Länge der Low-Phase bestimmt. Die variable Wartezeit wird vor jedem neuen Impuls des Oszillators von Hauptprogramm übergeben. Ich konnte mir erst gar nicht vorstellen, dass man das schnell genug hinbekommt, aber es geht, weil der Systemtakt mit 125 MHz deutlich höher ist als die Ausgangsfrequenz von rund 7 MHz. Der PIO-Assembler übersetzt das Programm in die Datei vfo.pio.h, die dann später ins Hauptprogramm eingebunden wird:

// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //

#pragma once

#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif

// --- //
// vfo //
// --- //

#define vfo_wrap_target 0
#define vfo_wrap 6

static const uint16_t vfo_program_instructions[] = {
            //     .wrap_target
    0xe081, //  0: set    pindirs, 1                
    0x80a0, //  1: pull   block                     
    0x6020, //  2: out    x, 32                     
    0x0043, //  3: jmp    x--, 3                    
    0xe401, //  4: set    pins, 1                [4]
    0xe000, //  5: set    pins, 0                   
    0x0001, //  6: jmp    1                         
            //     .wrap
};

#if !PICO_NO_HARDWARE
static const struct pio_program vfo_program = {
    .instructions = vfo_program_instructions,
    .length = 7,
    .origin = -1,
};

static inline pio_sm_config vfo_program_get_default_config(uint offset) {
    pio_sm_config c = pio_get_default_sm_config();
    sm_config_set_wrap(&c, offset + vfo_wrap_target, offset + vfo_wrap);
    return c;
}
#endif


Eine Schwingung des ausgegebenen HF-Signals dauert 10+x Taktperioden. Bei einem Prozessortakt von 125 MHz braucht man also 125000 / 7074=17,67034209782301 Taktperioden. Dem Pio-Programm kann man aber nur Ganzzahlen übergeben. Mit x = 17 bekommt man 7352,941 kHz, mit x=18 sind es 6944,444 kHz. Das Hautprogramm muss also in schneller Folge und im richtigen Verhältnis die Zahlen 17 und 18 übertragen. Man kann sich kaum vorstellen, wie daraus genaue 7074 kHz werden sollen, aber es geht. Man überträgt also 17 + (manchmal) 1. In einer Simulation habe ich gefunden, dass diese Manchmal-Eins etwa so verteilt sein müsste, um gerade 7074 kHz zu bekommen:

0 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1



Das Programm berechnet zunächst den Quotient 125000000 Hz / 7074000 Hz, aber nicht als Realzahl, sondern als Ganzzahl mit 32 Bit, wobei die oberen acht Bit das ganzzahlige Verhältnis enthalten und die unteren 24 Bit den Nachkommateil 0,67034209782301. Die Division ist entscheidend für die Genauigkeit und muss mit 64-Bit-Zahlen durchgeführt werden. Am Ende steht in periods die Anzahl der Prozessortakt-Perioden mit ihrem gebrochenen Teil. Dabei werden schon die zehn Taktperioden abgezogen, die das Assemblerprogramm selbst verbraucht.

#include "vfo.pio.h"
#include "pico/stdlib.h"
#include "pico/multicore.h"

uint32_t clk_khz = 125000;
uint32_t f=7074000;
 
void setup(void){
  set_sys_clock_khz(clk_khz,true);
}

void loop(){}

void setup1(void){
  uint32_t periods;
  uint32_t delta = 0;
  uint16_t t;

  PIO pio = pio0;
  gpio_init(0);
  gpio_set_dir(0, GPIO_OUT);
  pio_gpio_init(pio0, 15);        
  uint offset = pio_add_program(pio0, &vfo_program);        
  pio_sm_set_consecutive_pindirs(pio0, 0, 15, 1, true);         
  pio_sm_config c = vfo_program_get_default_config(offset);
  sm_config_set_set_pins(&c, 15, 1);                 
  pio_sm_init(pio0, 0, offset, &c);      
  pio_sm_set_enabled(pio0, 0, true);
 
  uint64_t ratio = (uint64_t)clk_khz * 1000LL *(1<<24)/(uint64_t)f;
  periods=(uint32_t)(ratio/1-(10<<24));
 
  while(1){
    t = (periods+delta) >> 24; 
    pio_sm_put_blocking(pio0, 0, t);
    delta += periods-(t << 24);     
  }
}

void loop1(){}


In der schnellen Endlosschleife werden mit pio_sm_put_blocking(pio0, 0, t); die aktuellen Wartezeiten t an das PIO-Programm übergeben. Der Zusatz blocking bewirkt, dass die Übergabe erst dann ausgeführt wird, wenn eine Stelle im vierstufigen Eingangspuffer frei ist, weil der nächste Wert vom PIO-Programm bereits abgeholt wurde. Die Schleife läuft also automatisch so schnell wie es die Takterzeugung des Ausgangssignals erfordert. Zusätzlich wird in delta die Abweichung von der mittleren Periode in periods aufsummiert. Immer wenn dieser Zeitfehler den 24-Bit-Nachkommateil überschreitet, wird die Wartezeit in t um 1 erhöht. So entsteht also die erforderliche Folge von Einsen und Nullen, die letztlich zu der richtigen Frequenz zwischen den Extremen Takt/17 und Takt/18  führt. Die ganze Sache hat eine mögliche Auflösung von 1 Hz. Dass in der gemessenen Ausgangsfrequenz 23 Hz zu viel gemessen wurden, liegt an der Toleranz des Quarzaktes.



Am Oszilloskop, sieht man deutlich, dass das Signal zwischen zwei Periodenlängen hin und her springt. Der zeitliche Versatz entspricht  einer Periode des Controllertakts also 1 / 125 MHz = 8 ns.

Trotzdem sieht man im Spektrum eines SDR ein sauberes Signal, und es hört sich in der Überlagerung auch absolut sauber an. Allerdings gibt es in größeren Abständen einzelne Nebenwellen, die vor allem bei höheren Frequenzen teilweise nur 30 dB unter dem Nutzsignal liegen. Der Oszillator ist daher für einfache Empfänger geeignet, nicht aber für übliche Sender. 



Mit dem Oszillator empfangene FT8-Stationen

Download incl. Hexdatei (uf2): Pico7074kHz.zip


Elektronik-Labor  Projekte  Mikrocontroller  Raspberry