RPi
Pico HF-Generator 7074,0 kHz
Elektronik-Labor Projekte Mikrocontroller Raspberry
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
#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.
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