VU3CER - misc cleanups

- Sync with upstream 'ft8_lib' code

- Whitespace conversions and fixes

- 'Easy Frequency Counter' using Pico itself

- Si5351 calibration example for Pi Pico

- GPS support for timing (https://github.com/kholia/pico-arduino-compat)
main
Dhiru Kholia 2022-04-12 00:05:59 +05:30 zatwierdzone przez Dhiru Kholia
rodzic d7e52277c0
commit dc2de91a99
151 zmienionych plików z 13277 dodań i 4107 usunięć

3
.gitignore vendored
Wyświetl plik

@ -1,2 +1,3 @@
build/
.vscode/
.vscode/
*.o

Wyświetl plik

@ -5,12 +5,19 @@ project(ft8_xcvr VERSION 0.1.0)
#set(CMAKE_C_STANDARD 11)
#set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
add_executable(run_ft8 run_ft8.c)
add_executable(run_ft8 run_ft8.cpp)
# Overclocking hacks
pico_define_boot_stage2(slower_boot2 ${PICO_DEFAULT_BOOT_STAGE2_FILE})
target_compile_definitions(slower_boot2 PRIVATE PICO_FLASH_SPI_CLKDIV=4)
pico_set_boot_stage2(run_ft8 slower_boot2)
add_subdirectory(ft8)
add_subdirectory(fft)
add_subdirectory(util)
add_subdirectory(peripheral_util)
add_subdirectory(../pico-arduino-compat/libs/tinygpsplus build_pac-tinygpsplus)
add_subdirectory(../pico-arduino-compat/libs/time build_pac-time)
#target_include_directories(run_ft8 PUBLIC ft8 fft util)
@ -18,8 +25,8 @@ add_subdirectory(peripheral_util)
#target_link_libraries(run_ft8 ft8 fft util)
target_link_libraries(run_ft8 ft8 fft util pico_keypad4x4 st7789 pico_si5351 hardware_adc hardware_dma hardware_rtc pico_multicore pico_stdlib)
target_link_libraries(run_ft8 ft8 fft util pico_si5351 hardware_adc hardware_dma hardware_rtc pac-tinygpsplus pac-time pico_multicore pico_stdlib)
pico_enable_stdio_usb(run_ft8 1)
pico_enable_stdio_uart(run_ft8 0)
pico_add_extra_outputs(run_ft8)
pico_add_extra_outputs(run_ft8)

Wyświetl plik

@ -1,6 +1,7 @@
MIT License
Copyright (c) 2021 Godwin Duan
Copyright (c) 2022 Dhiru Kholia (VU3CER)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the Software), to deal

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 109 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 103 KiB

2
Makefile.PC 100644
Wyświetl plik

@ -0,0 +1,2 @@
dummy:
g++ -DPC=1 ft8/*.c ft8/*.cpp fft/*.c ft8/common/*.c -lm -Ift8 -I. -o pc

Wyświetl plik

@ -0,0 +1,75 @@
{
"board": {
"active_layer": 0,
"active_layer_preset": "All Layers",
"auto_track_width": true,
"hidden_nets": [],
"high_contrast_mode": 0,
"net_color_mode": 1,
"opacity": {
"pads": 1.0,
"tracks": 1.0,
"vias": 1.0,
"zones": 0.6
},
"ratsnest_display_mode": 0,
"selection_filter": {
"dimensions": true,
"footprints": true,
"graphics": true,
"keepouts": true,
"lockedItems": true,
"otherItems": true,
"pads": true,
"text": true,
"tracks": true,
"vias": true,
"zones": true
},
"visible_items": [
0,
1,
2,
3,
4,
5,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
32,
33,
34,
35,
36
],
"visible_layers": "fffffff_ffffffff",
"zone_display_mode": 0
},
"meta": {
"filename": "PDX-FSK-Processing.kicad_prl",
"version": 3
},
"project": {
"files": []
}
}

Wyświetl plik

@ -0,0 +1,420 @@
{
"board": {
"design_settings": {
"defaults": {
"board_outline_line_width": 0.09999999999999999,
"copper_line_width": 0.19999999999999998,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.049999999999999996,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": false,
"text_position": 0,
"units_format": 1
},
"fab_line_width": 0.09999999999999999,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.15,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 0.762,
"height": 1.524,
"width": 1.524
},
"silk_line_width": 0.15,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.15,
"silk_text_upright": false,
"zones": {
"45_degree_only": false,
"min_clearance": 0.508
}
},
"diff_pair_dimensions": [],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"copper_edge_clearance": "error",
"courtyards_overlap": "error",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "warning",
"extra_footprint": "warning",
"footprint_type_mismatch": "error",
"hole_clearance": "error",
"hole_near_hole": "error",
"invalid_outline": "error",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"malformed_courtyard": "error",
"microvia_drill_out_of_range": "error",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"net_conflict": "warning",
"npth_inside_courtyard": "ignore",
"padstack": "error",
"pth_inside_courtyard": "ignore",
"shorting_items": "error",
"silk_over_copper": "warning",
"silk_overlap": "warning",
"skew_out_of_range": "error",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_dangling": "warning",
"track_width": "error",
"tracks_crossing": "error",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
"zone_has_empty_net": "error",
"zones_intersect": "error"
},
"rules": {
"allow_blind_buried_vias": false,
"allow_microvias": false,
"max_error": 0.005,
"min_clearance": 0.0,
"min_copper_edge_clearance": 0.0,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.19999999999999998,
"min_microvia_drill": 0.09999999999999999,
"min_silk_clearance": 0.0,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.19999999999999998,
"min_via_annular_width": 0.049999999999999996,
"min_via_diameter": 0.39999999999999997,
"solder_mask_clearance": 0.0,
"solder_mask_min_width": 0.0,
"use_height_for_length_calcs": true
},
"track_widths": [],
"via_dimensions": [],
"zones_allow_external_fillets": false,
"zones_use_no_outline": true
},
"layer_presets": []
},
"boards": [],
"cvpcb": {
"equivalence_files": []
},
"erc": {
"erc_exclusions": [],
"meta": {
"version": 0
},
"pin_map": [
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
1,
0,
1,
2
],
[
0,
1,
0,
0,
0,
0,
1,
1,
2,
1,
1,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
[
1,
1,
1,
1,
1,
0,
1,
1,
1,
1,
1,
2
],
[
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
1,
2,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
0,
2,
1,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
]
],
"rule_severities": {
"bus_definition_conflict": "error",
"bus_entry_needed": "error",
"bus_label_syntax": "error",
"bus_to_bus_conflict": "error",
"bus_to_net_conflict": "error",
"different_unit_footprint": "error",
"different_unit_net": "error",
"duplicate_reference": "error",
"duplicate_sheet_names": "error",
"extra_units": "error",
"global_label_dangling": "warning",
"hier_label_mismatch": "error",
"label_dangling": "error",
"lib_symbol_issues": "warning",
"multiple_net_names": "warning",
"net_not_bus_member": "warning",
"no_connect_connected": "warning",
"no_connect_dangling": "warning",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "warning",
"power_pin_not_driven": "error",
"similar_labels": "warning",
"unannotated": "error",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "PDX-FSK-Processing.kicad_pro",
"version": 1
},
"net_settings": {
"classes": [
{
"bus_width": 12.0,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.25,
"via_diameter": 0.8,
"via_drill": 0.4,
"wire_width": 6.0
}
],
"meta": {
"version": 2
},
"net_colors": null
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"specctra_dsn": "",
"step": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"annotate_start_num": 0,
"drawing": {
"default_line_thickness": 6.0,
"default_text_size": 50.0,
"field_names": [],
"intersheets_ref_own_page": false,
"intersheets_ref_prefix": "",
"intersheets_ref_short": false,
"intersheets_ref_show": false,
"intersheets_ref_suffix": "",
"junction_size_choice": 3,
"label_size_ratio": 0.375,
"pin_symbol_size": 25.0,
"text_offset_ratio": 0.15
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"net_format_name": "",
"ngspice": {
"fix_include_paths": true,
"fix_passive_vals": false,
"meta": {
"version": 0
},
"model_mode": 0,
"workbook_filename": ""
},
"page_layout_descr_file": "",
"plot_directory": "",
"spice_adjust_passive_values": false,
"spice_external_command": "spice \"%I\"",
"subpart_first_id": 65,
"subpart_id_separator": 0
},
"sheets": [
[
"b92ca747-b427-4740-8cd1-19dc6b1d15e5",
""
]
],
"text_variables": {}
}

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 80 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 62 KiB

Wyświetl plik

@ -0,0 +1,78 @@
Version 4
SHEET 1 1332 680
WIRE -672 -160 -672 -208
WIRE -400 -160 -400 -208
WIRE 144 -128 64 -128
WIRE 64 -112 64 -128
WIRE 192 -64 192 -112
WIRE 240 -64 192 -64
WIRE 144 -48 144 -128
WIRE 144 -48 112 -48
WIRE -672 -32 -672 -80
WIRE -400 -32 -400 -80
WIRE 240 -32 240 -64
WIRE 192 -16 192 -64
WIRE 144 0 144 -48
WIRE 160 0 144 0
WIRE 240 16 240 0
WIRE 240 16 224 16
WIRE 288 16 240 16
WIRE -16 32 -32 32
WIRE 96 32 64 32
WIRE 112 32 96 32
WIRE 160 32 112 32
WIRE 192 96 192 48
FLAG -96 32 W1
IOPIN -96 32 In
FLAG -672 -32 0
FLAG -672 -208 W1
IOPIN -672 -208 In
FLAG -400 -32 0
FLAG -400 -208 Vb
IOPIN -400 -208 In
FLAG 192 -112 Vb
IOPIN 192 -112 In
FLAG 64 -112 0
FLAG 192 96 0
FLAG 96 96 0
SYMBOL voltage -672 -176 R0
WINDOW 3 -259 214 Left 2
WINDOW 123 0 0 Left 0
WINDOW 39 0 0 Left 0
SYMATTR Value SINE(0 1 1000 0.1p 0.1p 0.5 1000000)
SYMATTR InstName V1
SYMBOL cap -32 16 R90
WINDOW 0 0 32 VBottom 2
WINDOW 3 32 32 VTop 2
SYMATTR InstName C4
SYMATTR Value 100nF
SYMBOL voltage -400 -176 R0
WINDOW 123 0 0 Left 0
WINDOW 39 0 0 Left 0
SYMATTR InstName V3
SYMATTR Value 3.3
SYMBOL SBORKA\\COMPARATOR\\lm393 192 16 R0
SYMATTR InstName U1
SYMBOL MiniSyms4\\res- 240 -16 R0
WINDOW 3 16 18 Center 0
WINDOW 0 17 -2 Left 0
SYMATTR Value 10k
SYMATTR InstName R1
SYMBOL schottky 112 96 R180
WINDOW 0 -37 31 Left 2
WINDOW 3 -16 -39 Left 2
SYMATTR InstName D1
SYMATTR Value 1N5819
SYMATTR Description Diode
SYMATTR Type diode
SYMBOL res 80 16 R90
WINDOW 0 0 56 VBottom 2
WINDOW 3 32 56 VTop 2
SYMATTR InstName R2
SYMATTR Value 1k
SYMBOL res 96 -64 R0
WINDOW 0 -28 29 Left 2
WINDOW 3 -35 65 Left 2
SYMATTR InstName R3
SYMATTR Value 10k
TEXT -104 -136 Left 2 !.tran 50ms

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -0,0 +1 @@
0

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 3.2 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 113 KiB

Wyświetl plik

@ -0,0 +1,34 @@
This method can be used to calibrate Si5351 in a cost-effective manner.
- 'Install' MicroPython on Pi Pico.
Push and hold the BOOTSEL button and plug your Pico into the USB port of your
Raspberry Pi or other computer. Release the BOOTSEL button after your Pico is
connected. It will mount as a Mass Storage Device called RPI-RP2. Drag and drop
the MicroPython UF2 file onto the RPI-RP2 volume.
(Text borrowed from [this URL](https://www.raspberrypi.com/documentation/microcontrollers/micropython.html))
- [Optional] Install `ampy`
```
pip3 install adafruit-ampy
```
Run the frequency counting program.
```
ampy --port /serial/port run main.py
```
- Alternate: Use `Thonny IDE` to upload `main.py` to Pi Pico.
Connections:
Connect the input-frequency to Pin 20 (GP15). Connect the input-frequency GND
to any of the GND pins of Pico (e.g. Pin 18).
This `v2` version also optionally configures a GPS module (>= NEO-6M) connected
to the Pico board - see https://github.com/kholia/uBlox7_TimePulse for more
details.

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 739 KiB

Wyświetl plik

@ -0,0 +1,8 @@
from machine import Pin, Timer
led = Pin(25, Pin.OUT)
timer = Timer()
def blink(timer):
led.toggle()
timer.init(freq=2.5, mode=Timer.PERIODIC, callback=blink)

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 140 KiB

Wyświetl plik

@ -0,0 +1,208 @@
# Reciprocal pulse counter from 'horuable' (RecipCounter1.py)
# https://www.raspberrypi.org/forums/viewtopic.php?f=146&t=306250&p=1832034#p1832034
# https://github.com/jbeale1/pico/blob/main/RecipCounter1.py - A few mods by J.Beale 24-March-2021
import rp2
import math
import utime
from rp2 import PIO, asm_pio
from machine import UART, Pin
from micropython import const
# machine CPU frequency defaults to 125 MHz, but can be set up to 250 MHz (or more)
# fractional calibration changes as well
#fcal = 1.0000150 # (at 125 MHz)
#fcal = 1.0000073 # (at 250 MHz)
fcal = 1
#MFREQ = 125000000
MFREQ = 250000000
MCAL = int(MFREQ * fcal) # calibrated value for this board at given CPU freq
global gateVal
# gateVal = int(MFREQ/10000)
gateVal = int(MFREQ)
@asm_pio(sideset_init=PIO.OUT_HIGH)
def gate():
"""PIO to generate gate signal."""
mov(x, osr) # load gate time (in clock pulses) from osr
wait(0, pin, 0) # wait for input to go low
wait(1, pin, 0) # wait for input to go high - effectively giving us rising edge detection
label("loopstart")
jmp(x_dec, "loopstart") .side(0) # keep gate low for time programmed by setting x reg
wait(0, pin, 0) # wait for input to go low
wait(1, pin, 0) .side(1) # set gate to high on rising edge
irq(block, 0) # set interrupt 0 flag and wait for system handler to service interrupt
wait(1, irq, 4) # wait for irq from clock counting state machine
wait(1, irq, 5) # wait for irq from pulse counting state machine
@asm_pio()
def clock_count():
"""PIO for counting clock pulses during gate low."""
mov(x, osr) # load x scratch with max value (2^32-1)
wait(1, pin, 0) # detect falling edge
wait(0, pin, 0) # of gate signal
label("counter")
jmp(pin, "output") # as long as gate is low //
jmp(x_dec, "counter") # decrement x reg (counting every other clock cycle - have to multiply output value by 2)
label("output")
mov(isr, x) # move clock count value to isr
push() # send data to FIFO
irq(block, 4) # set irq and wait for gate PIO to acknowledge
@asm_pio(sideset_init=PIO.OUT_HIGH)
def pulse_count():
"""PIO for counting incoming pulses during gate low."""
mov(x, osr) # load x scratch with max value (2^32-1)
wait(1, pin, 0)
wait(0, pin, 0) .side(0) # detect falling edge of gate
label("counter")
wait(0, pin, 1) # wait for rising
wait(1, pin, 1) # edge of input signal
jmp(pin, "output") # as long as gate is low //
jmp(x_dec, "counter") # decrement x req counting incoming pulses (probably will count one pulse less than it should - to be checked later)
label("output")
mov(isr, x) .side(1) # move pulse count value to isr and set pin to high to tell clock counting sm to stop counting
push() # send data to FIFO
irq(block, 5) # set irq and wait for gate PIO to acknowledge
def init_sm(freq, input_pin, gate_pin, pulse_fin_pin):
"""Starts state machines."""
global gateVal
gate_pin.value(1)
pulse_fin_pin.value(1)
max_count = const((1 << 32) - 1)
sm0 = rp2.StateMachine(0, gate, freq=freq, in_base=input_pin, sideset_base=gate_pin)
sm0.put(gateVal)
sm0.exec("pull()")
sm1 = rp2.StateMachine(1, clock_count, freq=freq, in_base=gate_pin,
jmp_pin=pulse_fin_pin)
sm1.put(max_count)
sm1.exec("pull()")
sm2 = rp2.StateMachine(2, pulse_count, freq=freq, in_base=gate_pin,
sideset_base = pulse_fin_pin, jmp_pin=gate_pin)
sm2.put(max_count-1)
sm2.exec("pull()")
sm1.active(1)
sm2.active(1)
sm0.active(1)
return sm0, sm1, sm2
# Calculate the crc (16-bit) for the message
def calcChecksum(packet):
CK_A,CK_B = 0, 0
for i in range(len(packet)):
CK_A = CK_A + packet[i]
CK_B = CK_B + CK_A
# ensure unsigned byte range
CK_A = CK_A & 0xFF
CK_B = CK_B & 0xFF
return CK_A, CK_B
# Send a message to set a new frequency and locked frequency Note: This does
# not wait for an acknowledge, nor poll the actual settings
def configureTimepulse(freq, freqLock, pulseRatio, pulseRatioLock):
factor = math.pow(2.0, -32.0);
pulseRatio = 1 - pulseRatio;
dc = int(pulseRatio / factor);
pulseRatioLock = 1 - pulseRatioLock;
dcLock = int(pulseRatioLock / factor);
message = [0] * 40
message[0] = 0xB5 # header
message[1] = 0x62 # header
message[2] = 0x06 # class
message[3] = 0x31 # id
message[4] = 0x20 # length
message[5] = 0x00 # length
message[6] = 0x00 # time pulse selection
message[7] = 0x01 # version
message[8] = 0x00 # reserved
message[9] = 0x00 # reserved
message[10] = 0x32 # antenna cable delay (here fixed)
message[11] = 0x00 # antenna cable delay
message[12] = 0x00 # rf group delay (here fixed)
message[13] = 0x00 # rf group delay
message[14] = (freq >> 0) & 0xFF # frequency
message[15] = (freq >> 8) & 0xFF # frequency
message[16] = (freq >> 16) & 0xFF # frequency
message[17] = (freq >> 24) & 0xFF # frequency
message[18] = (freqLock >> 0) & 0xFF # frequency on lock
message[19] = (freqLock >> 8) & 0xFF # frequency on lock
message[20] = (freqLock >> 16) & 0xFF # frequency on lock
message[21] = (freqLock >> 24) & 0xFF # frequency on lock
message[22] = (dc >> 0) & 0xFF # dutycycle
message[23] = (dc >> 8) & 0xFF # dutycycle
message[24] = (dc >> 16) & 0xFF # dutycycle
message[25] = (dc >> 24) & 0xFF # dutycycle
message[26] = (dcLock >> 0) & 0xFF # dutycycle on lock
message[27] = (dcLock >> 8) & 0xFF # dutycycle on lock
message[28] = (dcLock >> 16) & 0xFF # dutycycle on lock
message[29] = (dcLock >> 24) & 0xFF # dutycycle on lock
message[30] = 0x00 # user configured delay
message[31] = 0x00 # user configured delay
message[32] = 0x00 # user configured delay
message[33] = 0x00 # user configured delay
message[34] = 0xEF # flags
message[35] = 0x00 # flags
message[36] = 0x00 # flags
message[37] = 0x00 # flags
message[38], message[39] = calcChecksum(message[2:40-2]) # crc will be included after calculation
return message
if __name__ == "__main__":
from machine import Pin, freq
import uarray as array
global gateVal
# Configure the (>=) NEO-6M GPS module
uart1 = UART(1, baudrate=9600, tx=Pin(8), rx=Pin(9))
message = configureTimepulse(1, 12000000, 0.5, 0.5)
# message = configureTimepulse(5, 8000000, 0.5, 0.5)
uart1.write(bytes(message))
freq(MFREQ) # set CPU frequency; not necessarily the default 125 MHz
update_flag = False
data = array.array("I", [0, 0])
def counter_handler(sm):
#print("IRQ")
global update_flag
if not update_flag:
sm0.put(gateVal)
sm0.exec("pull()")
data[0] = sm1.get() # clock count
data[1] = sm2.get() # pulse count
update_flag = True
sm0, sm1, sm2 = init_sm(MFREQ, Pin(15, Pin.IN, Pin.PULL_UP), Pin(14, Pin.OUT), Pin(13, Pin.OUT))
#sm0, sm1, sm2 = init_sm(MFREQ, Pin(16, Pin.IN, Pin.PULL_UP), Pin(14, Pin.OUT), Pin(13, Pin.OUT))
sm0.irq(counter_handler)
print("n,msec,clocks,pulses,Hz") # CSV file column headers
i = 0
rCtr = 0
while True:
if update_flag:
msec=utime.ticks_ms()
rCtr += 1
update_flag = False
#if (rCtr % 100) == 0:
if True:
clock_count = (max_count - data[0]) # units of 2 * Tcpu (Tcpu = 1/MFREQ)
pulse_count = max_count - data[1]
freq = pulse_count * ( MCAL / (2*clock_count)) # calibration constant
print("{:d},{:d},{:d},{:d},{:0.5f}".format(i,msec,clock_count,pulse_count,freq))
i += 1

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 113 KiB

Wyświetl plik

@ -0,0 +1,30 @@
This method can be used to calibrate Si5351 in a cost-effective manner.
- 'Install' MicroPython on Pi Pico.
Push and hold the BOOTSEL button and plug your Pico into the USB port of your
Raspberry Pi or other computer. Release the BOOTSEL button after your Pico is
connected. It will mount as a Mass Storage Device called RPI-RP2. Drag and drop
the MicroPython UF2 file onto the RPI-RP2 volume.
(Text borrowed from [this URL](https://www.raspberrypi.com/documentation/microcontrollers/micropython.html))
- [Optional] Install `ampy`
```
pip3 install adafruit-ampy
```
Run the frequency counting program.
```
ampy --port /serial/port run main.py
```
- Alternate: Use `Thonny IDE` to upload `main.py` to Pi Pico.
Connections:
Connect the input-frequency to Pin 20 (GP15). Connect the input-frequency GND
to any of the GND pins of Pico (e.g. Pin 18).

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 739 KiB

Wyświetl plik

@ -0,0 +1,8 @@
from machine import Pin, Timer
led = Pin(25, Pin.OUT)
timer = Timer()
def blink(timer):
led.toggle()
timer.init(freq=2.5, mode=Timer.PERIODIC, callback=blink)

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 140 KiB

Wyświetl plik

@ -0,0 +1,136 @@
# Reciprocal pulse counter from 'horuable' (RecipCounter1.py)
# https://www.raspberrypi.org/forums/viewtopic.php?f=146&t=306250&p=1832034#p1832034
# https://github.com/jbeale1/pico/blob/main/RecipCounter1.py - A few mods by J.Beale 24-March-2021
from micropython import const
import rp2
from rp2 import PIO, asm_pio
import utime
# machine CPU frequency defaults to 125 MHz, but can be set up to 250 MHz (or more)
# fractional calibration changes as well
#fcal = 1.0000150 # (at 125 MHz)
#fcal = 1.0000073 # (at 250 MHz)
fcal = 1
#MFREQ = 125000000
MFREQ = 250000000
MCAL = int(MFREQ * fcal) # calibrated value for this board at given CPU freq
global gateVal
# gateVal = int(MFREQ/10000)
gateVal = int(MFREQ)
@asm_pio(sideset_init=PIO.OUT_HIGH)
def gate():
"""PIO to generate gate signal."""
mov(x, osr) # load gate time (in clock pulses) from osr
wait(0, pin, 0) # wait for input to go low
wait(1, pin, 0) # wait for input to go high - effectively giving us rising edge detection
label("loopstart")
jmp(x_dec, "loopstart") .side(0) # keep gate low for time programmed by setting x reg
wait(0, pin, 0) # wait for input to go low
wait(1, pin, 0) .side(1) # set gate to high on rising edge
irq(block, 0) # set interrupt 0 flag and wait for system handler to service interrupt
wait(1, irq, 4) # wait for irq from clock counting state machine
wait(1, irq, 5) # wait for irq from pulse counting state machine
@asm_pio()
def clock_count():
"""PIO for counting clock pulses during gate low."""
mov(x, osr) # load x scratch with max value (2^32-1)
wait(1, pin, 0) # detect falling edge
wait(0, pin, 0) # of gate signal
label("counter")
jmp(pin, "output") # as long as gate is low //
jmp(x_dec, "counter") # decrement x reg (counting every other clock cycle - have to multiply output value by 2)
label("output")
mov(isr, x) # move clock count value to isr
push() # send data to FIFO
irq(block, 4) # set irq and wait for gate PIO to acknowledge
@asm_pio(sideset_init=PIO.OUT_HIGH)
def pulse_count():
"""PIO for counting incoming pulses during gate low."""
mov(x, osr) # load x scratch with max value (2^32-1)
wait(1, pin, 0)
wait(0, pin, 0) .side(0) # detect falling edge of gate
label("counter")
wait(0, pin, 1) # wait for rising
wait(1, pin, 1) # edge of input signal
jmp(pin, "output") # as long as gate is low //
jmp(x_dec, "counter") # decrement x req counting incoming pulses (probably will count one pulse less than it should - to be checked later)
label("output")
mov(isr, x) .side(1) # move pulse count value to isr and set pin to high to tell clock counting sm to stop counting
push() # send data to FIFO
irq(block, 5) # set irq and wait for gate PIO to acknowledge
def init_sm(freq, input_pin, gate_pin, pulse_fin_pin):
"""Starts state machines."""
global gateVal
gate_pin.value(1)
pulse_fin_pin.value(1)
max_count = const((1 << 32) - 1)
sm0 = rp2.StateMachine(0, gate, freq=freq, in_base=input_pin, sideset_base=gate_pin)
sm0.put(gateVal)
sm0.exec("pull()")
sm1 = rp2.StateMachine(1, clock_count, freq=freq, in_base=gate_pin,
jmp_pin=pulse_fin_pin)
sm1.put(max_count)
sm1.exec("pull()")
sm2 = rp2.StateMachine(2, pulse_count, freq=freq, in_base=gate_pin,
sideset_base = pulse_fin_pin, jmp_pin=gate_pin)
sm2.put(max_count-1)
sm2.exec("pull()")
sm1.active(1)
sm2.active(1)
sm0.active(1)
return sm0, sm1, sm2
if __name__ == "__main__":
from machine import Pin, freq
import uarray as array
global gateVal
freq(MFREQ) # set CPU frequency; not necessarily the default 125 MHz
update_flag = False
data = array.array("I", [0, 0])
def counter_handler(sm):
#print("IRQ")
global update_flag
if not update_flag:
sm0.put(gateVal)
sm0.exec("pull()")
data[0] = sm1.get() # clock count
data[1] = sm2.get() # pulse count
update_flag = True
sm0, sm1, sm2 = init_sm(MFREQ, Pin(15, Pin.IN, Pin.PULL_UP), Pin(14, Pin.OUT), Pin(13, Pin.OUT))
#sm0, sm1, sm2 = init_sm(MFREQ, Pin(16, Pin.IN, Pin.PULL_UP), Pin(14, Pin.OUT), Pin(13, Pin.OUT))
sm0.irq(counter_handler)
print("n,msec,clocks,pulses,Hz") # CSV file column headers
i = 0
rCtr = 0
while True:
if update_flag:
msec=utime.ticks_ms()
rCtr += 1
update_flag = False
#if (rCtr % 100) == 0:
if True:
clock_count = (max_count - data[0]) # units of 2 * Tcpu (Tcpu = 1/MFREQ)
pulse_count = max_count - data[1]
freq = pulse_count * ( MCAL / (2*clock_count)) # calibration constant
print("{:d},{:d},{:d},{:d},{:0.5f}".format(i,msec,clock_count,pulse_count,freq))
i += 1

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

243
README.md
Wyświetl plik

@ -1,13 +1,230 @@
RPi Pico FT8 Transciever
Code to run a standalone FT8 transceiver based on the new Raspberry Pi Pico RP2040 microcontroller. Implemented in C, based on Karlis Goba YL3JG's FT8 Library. Uses the RPi Pico C/C++ SDK.
Currently, it is able to decode live audio signals that are input into the ADC, displaying them on the LCD. Uses a ST7789 240x320 LCD display, 4x4 membrane keyboard, Si5351 clock generator.
To do list:
-fix snr readings
-add abort option during sending
-add split frequency operation
-find how to use DSP quadrature filter + LPF in software
-sending waterfall
### RPi Pico FT8 Transceiver
Code to run a standalone FT8 transceiver based on the new Raspberry Pi Pico
RP2040 microcontroller. Implemented in C, based on Karlis Goba YL3JG's FT8
Library.
Currently, it is able to decode live audio signals that are input into the ADC
(GPIO 26), displaying them on the serial port.
This firmware is written for the [PDX++ project](https://github.com/kholia/Easy-Transceiver/tree/master/PDX++).
Author 1: Godwin Duan (AA1GD)
Author 2: Dhiru Kholia (VU3CER)
#### Hardware Requirements
- Raspberry Pi Pico (or a compatible) board
- [Option 1 - Preferred] `DFRobot Fermion: MEMS Microphone Module`
- [Option 2 - Fallback] Adafruit - Silicon MEMS Microphone Breakout - SPW2430
Alternate: Any electret microphone module with analog output should also be
OK.
- Jumper wires
#### Connections
Si5351 (used for TX):
- SDA to GPIO16
- SCL to GPIO17
ADC on GPIO26. I use the `DFRobot Fermion: MEMS Microphone Module` in my
testing. The FT8 audio is fed externally into this microphone.
#### Build Steps
Follow https://github.com/raspberrypi/pico-sdk#quick-start-your-own-project
```
mkdir -p ~/repos
cd ~/repos
git clone https://github.com/raspberrypi/pico-sdk.git
export PICO_SDK_PATH=${HOME}/repos/pico-sdk
git clone https://github.com/kholia/pico_ft8_xcvr.git
cd pico_ft8_xcvr
cmake .
make
```
Done. At the end of this process a `run_ft8.uf2` file is generated which can be
uploaded to the Pico board.
#### Testing
Live decoding works great with the audio feed coming from a uBITX transceiver.
![Demo 1](./screenshots/Screenshot_2022-07-28_18-48-16.png)
Pico (in ML76 grid) even decoded a US station - lucky timing!
![Demo 2](./screenshots/Screenshot_2022-07-28_18-43-09.png)
```
$ ./decode_ft8 tests/191111_110700.wav
Sample rate 12000 Hz, 180000 samples, 15.000 seconds
Block size = 1920
Subblock size = 960
N_FFT = 3840
Max magnitude: -16.9 dB
000000 31 +1.52 1244 ~ DG0OFT W4FGA EM83
000000 31 +1.60 1953 ~ JH1AJT RK6AH 73
000000 31 +1.76 519 ~ IZ1ANK PC2J R+10
000000 30 +1.52 841 ~ CQ OR18RSX
000000 30 +1.76 2359 ~ LA2GCA F5MXH JN07
000000 29 +1.52 1034 ~ CQ EA3UV JN01
000000 28 +2.00 2728 ~ CQ DX IK0YVV JN62
000000 27 +1.52 1725 ~ JH1AJT SP8BJU -04
000000 27 +0.16 972 ~ JA2GQT SP7XIF JO91
000000 25 +1.52 2028 ~ JL1TZQ R3BV R-12
000000 24 +2.24 1403 ~ RK6AUV SV1GN RR73
000000 22 +1.60 1669 ~ CQ PB5DX JO22
000000 19 +1.60 1484 ~ SP8NFO PA3EPP +04
000000 18 +0.96 1112 ~ CQ JR5MJS PM74
Decoded 14 message
```
```
$ sox tests/191111_110700.wav -r 6000 sample.wav
$ ls -la sample.wav tests/191111_110700.wav
-rw-rw-r-- 1 dhiru dhiru 180044 Apr 12 21:22 sample.wav
-rw-rw-r-- 1 dhiru dhiru 360202 Jul 3 2021 tests/191111_110700.wav
$ ./decode_ft8 sample.wav
Sample rate 6000 Hz, 90000 samples, 15.000 seconds
Block size = 960
Subblock size = 480
N_FFT = 1920
Max magnitude: -16.9 dB
000000 31 +1.76 519 ~ IZ1ANK PC2J R+10
000000 31 +1.60 1953 ~ JH1AJT RK6AH 73
000000 31 +1.52 1244 ~ DG0OFT W4FGA EM83
000000 30 +1.52 841 ~ CQ OR18RSX
000000 30 +1.76 2359 ~ LA2GCA F5MXH JN07
000000 29 +1.52 1034 ~ CQ EA3UV JN01
000000 28 +2.00 2728 ~ CQ DX IK0YVV JN62
000000 27 +0.16 972 ~ JA2GQT SP7XIF JO91
000000 27 +1.52 1725 ~ JH1AJT SP8BJU -04
000000 25 +1.52 2028 ~ JL1TZQ R3BV R-12
000000 24 +2.24 1403 ~ RK6AUV SV1GN RR73
000000 22 +1.60 1669 ~ CQ PB5DX JO22
000000 19 +1.60 1484 ~ SP8NFO PA3EPP +04
000000 18 +1.04 1112 ~ CQ JR5MJS PM74
Decoded 14 messages
```
```
$ make -f Makefile.PC
$ ./pc sample.wav # with upstream values
Sample rate 6000 Hz, 90000 samples, 15.000 seconds
Block size = 960
Subblock size = 480
N_FFT = 1920
Max magnitude: -16.9 dB
000000 31 +1.76 519 ~ IZ1ANK PC2J R+10
000000 31 +1.60 1953 ~ JH1AJT RK6AH 73
000000 31 +1.52 1244 ~ DG0OFT W4FGA EM83
000000 30 +1.52 841 ~ CQ OR18RSX
000000 30 +1.76 2359 ~ LA2GCA F5MXH JN07
000000 29 +1.52 1034 ~ CQ EA3UV JN01
000000 28 +2.00 2728 ~ CQ DX IK0YVV JN62
000000 27 +0.16 972 ~ JA2GQT SP7XIF JO91
000000 27 +1.52 1725 ~ JH1AJT SP8BJU -04
000000 25 +1.52 2028 ~ JL1TZQ R3BV R-12
000000 24 +2.24 1403 ~ RK6AUV SV1GN RR73
000000 22 +1.60 1669 ~ CQ PB5DX JO22
000000 19 +1.60 1484 ~ SP8NFO PA3EPP +04
000000 18 +1.04 1112 ~ CQ JR5MJS PM74
Decoded 14 messages
```
```
With following settings:
#define kLDPC_iterations 10 // Original was 20
#define kMax_decoded_messages 14 // Was 50, change to 14 since there's 14 buttons on the 4x4 membrane keyboard
#define kFreq_osr 1 // both default 2
#define kTime_osr 1
$ ./pc tests/recordings/191111_110700.wav
Sample rate 12000 Hz, 180000 samples, 15.000 seconds
Block size = 1920
Subblock size = 1920
N_FFT = 1920
Max magnitude: -16.3 dB
000000 34 +0.00 975 ~ JA2GQT SP7XIF JO91
000000 29 +1.60 2356 ~ LA2GCA F5MXH JN07
000000 29 +1.44 1956 ~ JH1AJT RK6AH 73
000000 25 +2.08 1406 ~ RK6AUV SV1GN RR73
000000 23 +1.60 519 ~ IZ1ANK PC2J R+10
000000 22 +1.44 1725 ~ JH1AJT SP8BJU -04
000000 20 +1.76 2731 ~ CQ DX IK0YVV JN62
000000 18 +1.28 1038 ~ CQ EA3UV JN01
Decoded 8 messages
```
Question: Can we feed `tests/recordings/191111_110700.wav` into the ADC port of
Pico, and confirm that it too decodes these 8 messages just fine?
#### Misc Notes
Folks have done FT8 decoding on STM32F7, and Teensy 3.6 before.
However, STM32 stuff is unobtanium (and expensive) in year 2022. At almost half
the Teensy 3.6's price point, the RPi 2W is a much better platform for decoding
FT8 and also happens to be unobtanium!
Besides these points, decoding FT8 in real-time on a 4 USD Pi Pico (Arm
Cortex-M0+ Inside) microcontroller with < 150mA current consumption sounds like
fun :-)
The `microphone_adc` example is borrowed from the `pico-extras` repository. It
is quite helpful for debugging stuff.
FT8 TX works fine from CLK 1 (VFO 1) port.
#### Done
- Test the FT8 RX functionality with CD2003 / TA2003 circuit.
Result: The output of CD2003 is not enough for the Pico's ADC. Use a small
single transistor pre-amplifier (from Gajendra Sir - VU2BGS) to solve this
problem.
- Use Pico W for `spotting` - done as part of `SunshineFT8` project.
#### TODO
- Use `Serial2` to send the decoded output to an Android phone / RPi.
- Implement CAT control to get commands (e.g. TX on, PTT on) from an Android
phone / RPi.
- Keep monitoring the upstream `pico_ft8_xcvr` project for changes.
- [LP] Shift to the new `monitor_t` paradigm (frame processing)
#### References
- https://www.cnx-software.com/2022/07/03/getting-started-with-wifi-on-raspberry-pi-pico-w-board/

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 494 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 72 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 25 KiB

Wyświetl plik

@ -0,0 +1,19 @@
# https://github.com/arduino/arduino-cli/releases
port := $(shell python3 board_detect.py)
default:
@# echo $(port)
arduino-cli compile --fqbn=rp2040:rp2040:rpipico si5351_calibration
arduino-cli -v upload -p "${port}" --fqbn=rp2040:rp2040:rpipico si5351_calibration
install_platform:
arduino-cli config init --overwrite
arduino-cli core update-index
arduino-cli core install rp2040:rp2040
deps:
arduino-cli lib install "Etherkit Si5351"
install_arduino_cli:
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=~/.local/bin sh

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 181 KiB

Wyświetl plik

@ -0,0 +1,30 @@
#### What
Traditionally Si5351 calibration has required an external SDR or frequency
counter. In our PDX (PDX++) design, we implement a frequency counter on the
Pico micro-controller itself, and feed it with CLK2 signal from the Si5351.
This way are able to calibrate Si5351 natively without requiring external
hardware.
#### Connections
Note: GP16 is SDA. GP17 is SCL. GP9 is the frequency input.
Automatic calibration is implemented - Wait for around a minute for it to kick
in ;)
![DEMO 1](./Screenshot_2022-08-05_11-43-14.png)
![DEMO 2](./Screenshot_2022-08-07_18-56-13.png)
#### Credits
- Burkhard Kainka - RPi Pico Projects and Circuits
- WB2CBA, Dr. Pedro, Alan (ADX + PDX team)
- https://github.com/earlephilhower/arduino-pico
- https://github.com/etherkit/Si5351Arduino/blob/master/src/si5351.cpp

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 29 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 162 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 90 KiB

Wyświetl plik

@ -0,0 +1,3 @@
board_manager:
additional_urls:
- https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json

Wyświetl plik

@ -0,0 +1,13 @@
#!/usr/bin/env python
# pip3 install pyserial
import serial.tools.list_ports
import serial
a = serial.tools.list_ports.comports()
for w in a:
# https://devicehunt.com/view/type/usb/vendor/1A86/device/7523
# https://linux-hardware.org/?id=usb:2e8a-000a
if "1a86" in w.hwid.lower() or "2e8a" in w.hwid.lower():
print(w.device)

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 200 KiB

Wyświetl plik

@ -0,0 +1,2 @@
format:
astyle --options="formatter.conf" si5351_calibration.ino

Wyświetl plik

@ -0,0 +1,31 @@
# This configuration file contains a selection of the available options provided by the formatting tool "Artistic Style"
# http://astyle.sourceforge.net/astyle.html
#
# If you wish to change them, don't edit this file.
# Instead, copy it in the same folder of file "preferences.txt" and modify the copy. This way, you won't lose your custom formatter settings when upgrading the IDE
# If you don't know where file preferences.txt is stored, open the IDE, File -> Preferences and you'll find a link
mode=c
# 2 spaces indentation
indent=spaces=2
# also indent macros
indent-preprocessor
# indent classes, switches (and cases), comments starting at column 1
indent-classes
indent-switches
indent-cases
indent-col1-comments
# put a space around operators
pad-oper
# put a space after if/for/while
pad-header
# if you like one-liners, keep them
keep-one-line-statements
remove-comment-prefix

Wyświetl plik

@ -0,0 +1,228 @@
/*
si5351_calibration.ino - Simple calibration routine for the Si5351
breakout board.
Copyright 2015 - 2018 Paul Warren <pwarren@pwarren.id.au>
Jason Milldrum <milldrum@gmail.com>
Copyright 2022 Dhiru Kholia (VU3CER) <dhiru@openwall.com> - Implement the
automatic, self-sufficient calibration logic.
Uses code from https://github.com/darksidelemm/open_radio_miniconf_2015
and the old version of the calibration sketch
This sketch is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License.
If not, see <http://www.gnu.org/licenses/>.
*/
#include "si5351.h"
#include "Wire.h"
#include "pico/stdlib.h"
#include "hardware/pwm.h"
#include "pico/multicore.h"
uint32_t f_hi;
Si5351 si5351;
int32_t cal_factor = 0;
// int32_t old_cal = 139600;
// int32_t old_cal = 140300;
int32_t old_cal = 0;
int64_t existing_error = 0;
int64_t error = 0;
int count = 15; /* reverse count */
uint64_t rx_freq;
uint64_t target_freq = 1000000000ULL; // 10 MHz, in hundredths of hertz
void setup()
{
gpio_set_function(9, GPIO_FUNC_PWM); // GP9
// Start serial and initialize the Si5351
Serial.begin(115200);
Serial.println("Check 1 2 3...");
// Wire = I2C0 => SDA can be {0, 4, 8, 12, 16, 20, 24, 28}, for example
Wire.setSDA(16);
Wire.setSCL(17);
Wire.begin();
// The crystal load value needs to match in order to have an accurate calibration
si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);
Serial.println("Check 5 6 7...");
// Start on target frequency
si5351.set_correction(old_cal, SI5351_PLL_INPUT_XO);
si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
si5351.set_freq(target_freq, SI5351_CLK2);
si5351.drive_strength(SI5351_CLK2, SI5351_DRIVE_2MA); // Set for minimum power
}
void loop()
{
si5351.update_status();
if (si5351.dev_status.SYS_INIT == 1)
{
Serial.println(F("Initialising Si5351, you shouldn't see many of these!"));
delay(500);
}
else
{
Serial.println();
Serial.println(F("Adjust until your frequency counter reads as close to 10 MHz as possible."));
Serial.println(F("Press 'q' when complete."));
Serial.println(F("Press 'S' to automatically set calibration.")); // ATTENTION: This can be done automatically ;)
vfo_interface();
}
}
void pwm_int() {
pwm_clear_irq(4);
f_hi++;
}
void setup1() {
uint32_t f = 0;
delay(5000);
// Frequency counter
while (true) {
count = count - 1;
pwm_config cfg = pwm_get_default_config();
pwm_config_set_clkdiv_mode(&cfg, PWM_DIV_B_RISING);
pwm_init(4, &cfg, false);
gpio_set_function(9, GPIO_FUNC_PWM);
pwm_set_irq_enabled(4, true);
irq_set_exclusive_handler(PWM_IRQ_WRAP, pwm_int);
irq_set_enabled(PWM_IRQ_WRAP, true);
f_hi = 0;
uint32_t t = time_us_32() + 2;
while (t > time_us_32());
pwm_set_enabled(4, true);
t += 3000000; // Gate time (in uSeconds), 3 seconds
// t += 100000; // Gate time (in uSeconds), 100 ms
while (t > time_us_32());
pwm_set_enabled(4, false);
f = pwm_get_counter(4);
f += f_hi << 16;
Serial.print(f / 3.0); // Divide by gate time in seconds
Serial.println(" Hz");
error = ((f / 3.0) * 100ULL) - target_freq;
Serial.print("Current calibration correction value is ");
Serial.printf("%" PRId64 "\n", error);
Serial.print("Total calibration value is ");
Serial.println(error + existing_error);
if (count <= 0) { // Auto-calibration logic
flush_input();
Serial.println();
Serial.print(F("Calibration factor is "));
Serial.println(error);
Serial.println(F("Setting calibration factor automatically"));
si5351.set_correction(error + existing_error, SI5351_PLL_INPUT_XO);
existing_error = existing_error + error;
si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
Serial.println(F("Resetting target frequency"));
si5351.set_freq(target_freq, SI5351_CLK2);
count = 15;
}
}
}
void loop1() {
}
static void flush_input(void)
{
while (Serial.available() > 0)
Serial.read();
}
static void vfo_interface(void)
{
rx_freq = target_freq;
cal_factor = old_cal;
Serial.println(F(" Up: r t y u i o p"));
Serial.println(F(" Down: f g h j k l ;"));
Serial.println(F(" Hz: 0.01 0.1 1 10 100 1K 10k"));
while (1)
{
if (Serial.available() > 0)
{
char c = Serial.read();
switch (c)
{
case 'q':
flush_input();
Serial.println();
Serial.print(F("Calibration factor is "));
Serial.println(cal_factor);
Serial.println(F("Setting calibration factor"));
si5351.set_correction(cal_factor, SI5351_PLL_INPUT_XO);
si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
Serial.println(F("Resetting target frequency"));
si5351.set_freq(target_freq, SI5351_CLK2);
old_cal = cal_factor;
return;
case 'S':
flush_input();
Serial.println();
Serial.print(F("Calibration factor is "));
Serial.println(error);
Serial.println(F("Setting calibration factor"));
si5351.set_correction(error + existing_error, SI5351_PLL_INPUT_XO);
existing_error = existing_error + error;
si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
Serial.println(F("Resetting target frequency"));
si5351.set_freq(target_freq, SI5351_CLK2);
old_cal = cal_factor;
return;
case 'r': rx_freq += 1; break;
case 'f': rx_freq -= 1; break;
case 't': rx_freq += 10; break;
case 'g': rx_freq -= 10; break;
case 'y': rx_freq += 100; break;
case 'h': rx_freq -= 100; break;
case 'u': rx_freq += 1000; break;
case 'j': rx_freq -= 1000; break;
case 'i': rx_freq += 10000; break;
case 'k': rx_freq -= 10000; break;
case 'o': rx_freq += 100000; break;
case 'l': rx_freq -= 100000; break;
case 'p': rx_freq += 1000000; break;
case ';': rx_freq -= 1000000; break;
case '?':
Serial.println(F(" Up: r t y u i o p"));
Serial.println(F(" Down: f g h j k l ;"));
Serial.println(F(" Hz: 0.01 0.1 1 10 100 1K 10k"));
break;
default:
// Do nothing
continue;
}
cal_factor = (int32_t)(target_freq - rx_freq) + old_cal;
si5351.set_correction(cal_factor, SI5351_PLL_INPUT_XO);
si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
si5351.pll_reset(SI5351_PLLA);
si5351.set_freq(target_freq, SI5351_CLK2);
Serial.print(F("Current difference:"));
Serial.println(cal_factor);
}
delay(10);
}
}

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 12 KiB

Wyświetl plik

@ -0,0 +1,19 @@
# https://github.com/arduino/arduino-cli/releases
port := $(shell python3 board_detect.py)
default:
@# echo $(port)
arduino-cli compile --fqbn=rp2040:rp2040:rpipico si5351_calibration
arduino-cli -v upload -p "${port}" --fqbn=rp2040:rp2040:rpipico si5351_calibration
install_platform:
arduino-cli config init --overwrite
arduino-cli core update-index
arduino-cli core install rp2040:rp2040
deps:
arduino-cli lib install "Etherkit Si5351"
install_arduino_cli:
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=~/.local/bin sh

Wyświetl plik

@ -0,0 +1,10 @@
#### Connections
Note: GPIO16 is SDA. GPIO17 is SCL.
#### Credits
- https://github.com/earlephilhower/arduino-pico
- https://github.com/etherkit/Si5351Arduino/blob/master/src/si5351.cpp

Wyświetl plik

@ -0,0 +1,3 @@
board_manager:
additional_urls:
- https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json

Wyświetl plik

@ -0,0 +1,13 @@
#!/usr/bin/env python
# pip3 install pyserial
import serial.tools.list_ports
import serial
a = serial.tools.list_ports.comports()
for w in a:
# https://devicehunt.com/view/type/usb/vendor/1A86/device/7523
# https://linux-hardware.org/?id=usb:2e8a-000a
if "1a86" in w.hwid.lower() or "2e8a" in w.hwid.lower():
print(w.device)

Wyświetl plik

@ -0,0 +1,2 @@
format:
astyle --options="formatter.conf" si5351_calibration.ino

Wyświetl plik

@ -0,0 +1,31 @@
# This configuration file contains a selection of the available options provided by the formatting tool "Artistic Style"
# http://astyle.sourceforge.net/astyle.html
#
# If you wish to change them, don't edit this file.
# Instead, copy it in the same folder of file "preferences.txt" and modify the copy. This way, you won't lose your custom formatter settings when upgrading the IDE
# If you don't know where file preferences.txt is stored, open the IDE, File -> Preferences and you'll find a link
mode=c
# 2 spaces indentation
indent=spaces=2
# also indent macros
indent-preprocessor
# indent classes, switches (and cases), comments starting at column 1
indent-classes
indent-switches
indent-cases
indent-col1-comments
# put a space around operators
pad-oper
# put a space after if/for/while
pad-header
# if you like one-liners, keep them
keep-one-line-statements
remove-comment-prefix

Wyświetl plik

@ -0,0 +1,138 @@
/*
si5351_calibration.ino - Simple calibration routine for the Si5351
breakout board.
Copyright 2015 - 2018 Paul Warren <pwarren@pwarren.id.au>
Jason Milldrum <milldrum@gmail.com>
Uses code from https://github.com/darksidelemm/open_radio_miniconf_2015
and the old version of the calibration sketch
This sketch is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License.
If not, see <http://www.gnu.org/licenses/>.
*/
#include "si5351.h"
#include "Wire.h"
Si5351 si5351;
int32_t cal_factor = 0;
int32_t old_cal = 0;
uint64_t rx_freq;
uint64_t target_freq = 1000000000ULL; // 10 MHz, in hundredths of hertz
void setup()
{
// Start serial and initialize the Si5351
Serial.begin(115200);
while (!Serial)
; // Serial is via USB; wait for enumeration
delay(5000);
Serial.println("Check 1 2 3...");
// Wire = I2C0 => SDA can be {0, 4, 8, 12, 16, 20, 24, 28}, for example
Wire.setSDA(16); // GPIO16
Wire.setSCL(17); // GPIO17
Wire.begin();
// The crystal load value needs to match in order to have an accurate calibration
si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);
Serial.println("Check 5 6 7...");
// Start on target frequency
si5351.set_correction(cal_factor, SI5351_PLL_INPUT_XO);
si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
si5351.set_freq(target_freq, SI5351_CLK0);
}
void loop()
{
si5351.update_status();
if (si5351.dev_status.SYS_INIT == 1)
{
Serial.println(F("Initialising Si5351, you shouldn't see many of these!"));
delay(500);
}
else
{
Serial.println();
Serial.println(F("Adjust until your frequency counter reads as close to 10 MHz as possible."));
Serial.println(F("Press 'q' when complete."));
vfo_interface();
}
}
static void flush_input(void)
{
while (Serial.available() > 0)
Serial.read();
}
static void vfo_interface(void)
{
rx_freq = target_freq;
cal_factor = old_cal;
Serial.println(F(" Up: r t y u i o p"));
Serial.println(F(" Down: f g h j k l ;"));
Serial.println(F(" Hz: 0.01 0.1 1 10 100 1K 10k"));
while (1)
{
if (Serial.available() > 0)
{
char c = Serial.read();
switch (c)
{
case 'q':
flush_input();
Serial.println();
Serial.print(F("Calibration factor is "));
Serial.println(cal_factor);
Serial.println(F("Setting calibration factor"));
si5351.set_correction(cal_factor, SI5351_PLL_INPUT_XO);
si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
Serial.println(F("Resetting target frequency"));
si5351.set_freq(target_freq, SI5351_CLK0);
old_cal = cal_factor;
return;
case 'r': rx_freq += 1; break;
case 'f': rx_freq -= 1; break;
case 't': rx_freq += 10; break;
case 'g': rx_freq -= 10; break;
case 'y': rx_freq += 100; break;
case 'h': rx_freq -= 100; break;
case 'u': rx_freq += 1000; break;
case 'j': rx_freq -= 1000; break;
case 'i': rx_freq += 10000; break;
case 'k': rx_freq -= 10000; break;
case 'o': rx_freq += 100000; break;
case 'l': rx_freq -= 100000; break;
case 'p': rx_freq += 1000000; break;
case ';': rx_freq -= 1000000; break;
default:
// Do nothing
continue;
}
cal_factor = (int32_t)(target_freq - rx_freq) + old_cal;
si5351.set_correction(cal_factor, SI5351_PLL_INPUT_XO);
si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
si5351.pll_reset(SI5351_PLLA);
si5351.set_freq(target_freq, SI5351_CLK0);
Serial.print(F("Current difference:"));
Serial.println(cal_factor);
}
}
}

Wyświetl plik

@ -0,0 +1 @@
Thanks to Gajendra Sir (VU2BGS) for sharing this circuit.

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 65 KiB

Wyświetl plik

@ -0,0 +1,2 @@
(kicad_pcb (version 20211014) (generator pcbnew)
)

Wyświetl plik

@ -0,0 +1,75 @@
{
"board": {
"active_layer": 0,
"active_layer_preset": "All Layers",
"auto_track_width": true,
"hidden_nets": [],
"high_contrast_mode": 0,
"net_color_mode": 1,
"opacity": {
"pads": 1.0,
"tracks": 1.0,
"vias": 1.0,
"zones": 0.6
},
"ratsnest_display_mode": 0,
"selection_filter": {
"dimensions": true,
"footprints": true,
"graphics": true,
"keepouts": true,
"lockedItems": true,
"otherItems": true,
"pads": true,
"text": true,
"tracks": true,
"vias": true,
"zones": true
},
"visible_items": [
0,
1,
2,
3,
4,
5,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
32,
33,
34,
35,
36
],
"visible_layers": "fffffff_ffffffff",
"zone_display_mode": 0
},
"meta": {
"filename": "Single-Transistor-Preamplifier.kicad_prl",
"version": 3
},
"project": {
"files": []
}
}

Wyświetl plik

@ -0,0 +1,420 @@
{
"board": {
"design_settings": {
"defaults": {
"board_outline_line_width": 0.09999999999999999,
"copper_line_width": 0.19999999999999998,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.049999999999999996,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": false,
"text_position": 0,
"units_format": 1
},
"fab_line_width": 0.09999999999999999,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.15,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 0.762,
"height": 1.524,
"width": 1.524
},
"silk_line_width": 0.15,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.15,
"silk_text_upright": false,
"zones": {
"45_degree_only": false,
"min_clearance": 0.508
}
},
"diff_pair_dimensions": [],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"copper_edge_clearance": "error",
"courtyards_overlap": "error",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "warning",
"extra_footprint": "warning",
"footprint_type_mismatch": "error",
"hole_clearance": "error",
"hole_near_hole": "error",
"invalid_outline": "error",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"malformed_courtyard": "error",
"microvia_drill_out_of_range": "error",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"net_conflict": "warning",
"npth_inside_courtyard": "ignore",
"padstack": "error",
"pth_inside_courtyard": "ignore",
"shorting_items": "error",
"silk_over_copper": "warning",
"silk_overlap": "warning",
"skew_out_of_range": "error",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_dangling": "warning",
"track_width": "error",
"tracks_crossing": "error",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
"zone_has_empty_net": "error",
"zones_intersect": "error"
},
"rules": {
"allow_blind_buried_vias": false,
"allow_microvias": false,
"max_error": 0.005,
"min_clearance": 0.0,
"min_copper_edge_clearance": 0.0,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.19999999999999998,
"min_microvia_drill": 0.09999999999999999,
"min_silk_clearance": 0.0,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.19999999999999998,
"min_via_annular_width": 0.049999999999999996,
"min_via_diameter": 0.39999999999999997,
"solder_mask_clearance": 0.0,
"solder_mask_min_width": 0.0,
"use_height_for_length_calcs": true
},
"track_widths": [],
"via_dimensions": [],
"zones_allow_external_fillets": false,
"zones_use_no_outline": true
},
"layer_presets": []
},
"boards": [],
"cvpcb": {
"equivalence_files": []
},
"erc": {
"erc_exclusions": [],
"meta": {
"version": 0
},
"pin_map": [
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
1,
0,
1,
2
],
[
0,
1,
0,
0,
0,
0,
1,
1,
2,
1,
1,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
[
1,
1,
1,
1,
1,
0,
1,
1,
1,
1,
1,
2
],
[
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
1,
2,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
0,
2,
1,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
]
],
"rule_severities": {
"bus_definition_conflict": "error",
"bus_entry_needed": "error",
"bus_label_syntax": "error",
"bus_to_bus_conflict": "error",
"bus_to_net_conflict": "error",
"different_unit_footprint": "error",
"different_unit_net": "error",
"duplicate_reference": "error",
"duplicate_sheet_names": "error",
"extra_units": "error",
"global_label_dangling": "warning",
"hier_label_mismatch": "error",
"label_dangling": "error",
"lib_symbol_issues": "warning",
"multiple_net_names": "warning",
"net_not_bus_member": "warning",
"no_connect_connected": "warning",
"no_connect_dangling": "warning",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "warning",
"power_pin_not_driven": "error",
"similar_labels": "warning",
"unannotated": "error",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "Single-Transistor-Preamplifier.kicad_pro",
"version": 1
},
"net_settings": {
"classes": [
{
"bus_width": 12.0,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.25,
"via_diameter": 0.8,
"via_drill": 0.4,
"wire_width": 6.0
}
],
"meta": {
"version": 2
},
"net_colors": null
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"specctra_dsn": "",
"step": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"annotate_start_num": 0,
"drawing": {
"default_line_thickness": 6.0,
"default_text_size": 50.0,
"field_names": [],
"intersheets_ref_own_page": false,
"intersheets_ref_prefix": "",
"intersheets_ref_short": false,
"intersheets_ref_show": false,
"intersheets_ref_suffix": "",
"junction_size_choice": 3,
"label_size_ratio": 0.375,
"pin_symbol_size": 25.0,
"text_offset_ratio": 0.15
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"net_format_name": "",
"ngspice": {
"fix_include_paths": true,
"fix_passive_vals": false,
"meta": {
"version": 0
},
"model_mode": 0,
"workbook_filename": ""
},
"page_layout_descr_file": "",
"plot_directory": "",
"spice_adjust_passive_values": false,
"spice_external_command": "spice \"%I\"",
"subpart_first_id": 65,
"subpart_id_separator": 0
},
"sheets": [
[
"29550658-6a7e-4a73-913d-2ff621f8ec78",
""
]
],
"text_variables": {}
}

Wyświetl plik

@ -0,0 +1,808 @@
(kicad_sch (version 20211123) (generator eeschema)
(uuid 29550658-6a7e-4a73-913d-2ff621f8ec78)
(paper "User" 150.012 150.012)
(lib_symbols
(symbol "Connector:AudioJack3" (in_bom yes) (on_board yes)
(property "Reference" "J" (id 0) (at 0 8.89 0)
(effects (font (size 1.27 1.27)))
)
(property "Value" "AudioJack3" (id 1) (at 0 6.35 0)
(effects (font (size 1.27 1.27)))
)
(property "Footprint" "" (id 2) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "~" (id 3) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_keywords" "audio jack receptacle stereo headphones phones TRS connector" (id 4) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_description" "Audio Jack, 3 Poles (Stereo / TRS)" (id 5) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_fp_filters" "Jack*" (id 6) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(symbol "AudioJack3_0_1"
(rectangle (start -5.08 -5.08) (end -6.35 -2.54)
(stroke (width 0.254) (type default) (color 0 0 0 0))
(fill (type outline))
)
(polyline
(pts
(xy 0 -2.54)
(xy 0.635 -3.175)
(xy 1.27 -2.54)
(xy 2.54 -2.54)
)
(stroke (width 0.254) (type default) (color 0 0 0 0))
(fill (type none))
)
(polyline
(pts
(xy -1.905 -2.54)
(xy -1.27 -3.175)
(xy -0.635 -2.54)
(xy -0.635 0)
(xy 2.54 0)
)
(stroke (width 0.254) (type default) (color 0 0 0 0))
(fill (type none))
)
(polyline
(pts
(xy 2.54 2.54)
(xy -2.54 2.54)
(xy -2.54 -2.54)
(xy -3.175 -3.175)
(xy -3.81 -2.54)
)
(stroke (width 0.254) (type default) (color 0 0 0 0))
(fill (type none))
)
(rectangle (start 2.54 3.81) (end -5.08 -5.08)
(stroke (width 0.254) (type default) (color 0 0 0 0))
(fill (type background))
)
)
(symbol "AudioJack3_1_1"
(pin passive line (at 5.08 0 180) (length 2.54)
(name "~" (effects (font (size 1.27 1.27))))
(number "R" (effects (font (size 1.27 1.27))))
)
(pin passive line (at 5.08 2.54 180) (length 2.54)
(name "~" (effects (font (size 1.27 1.27))))
(number "S" (effects (font (size 1.27 1.27))))
)
(pin passive line (at 5.08 -2.54 180) (length 2.54)
(name "~" (effects (font (size 1.27 1.27))))
(number "T" (effects (font (size 1.27 1.27))))
)
)
)
(symbol "Device:C_Polarized" (pin_numbers hide) (pin_names (offset 0.254)) (in_bom yes) (on_board yes)
(property "Reference" "C" (id 0) (at 0.635 2.54 0)
(effects (font (size 1.27 1.27)) (justify left))
)
(property "Value" "C_Polarized" (id 1) (at 0.635 -2.54 0)
(effects (font (size 1.27 1.27)) (justify left))
)
(property "Footprint" "" (id 2) (at 0.9652 -3.81 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "~" (id 3) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_keywords" "cap capacitor" (id 4) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_description" "Polarized capacitor" (id 5) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_fp_filters" "CP_*" (id 6) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(symbol "C_Polarized_0_1"
(rectangle (start -2.286 0.508) (end 2.286 1.016)
(stroke (width 0) (type default) (color 0 0 0 0))
(fill (type none))
)
(polyline
(pts
(xy -1.778 2.286)
(xy -0.762 2.286)
)
(stroke (width 0) (type default) (color 0 0 0 0))
(fill (type none))
)
(polyline
(pts
(xy -1.27 2.794)
(xy -1.27 1.778)
)
(stroke (width 0) (type default) (color 0 0 0 0))
(fill (type none))
)
(rectangle (start 2.286 -0.508) (end -2.286 -1.016)
(stroke (width 0) (type default) (color 0 0 0 0))
(fill (type outline))
)
)
(symbol "C_Polarized_1_1"
(pin passive line (at 0 3.81 270) (length 2.794)
(name "~" (effects (font (size 1.27 1.27))))
(number "1" (effects (font (size 1.27 1.27))))
)
(pin passive line (at 0 -3.81 90) (length 2.794)
(name "~" (effects (font (size 1.27 1.27))))
(number "2" (effects (font (size 1.27 1.27))))
)
)
)
(symbol "Device:C_Small" (pin_numbers hide) (pin_names (offset 0.254) hide) (in_bom yes) (on_board yes)
(property "Reference" "C" (id 0) (at 0.254 1.778 0)
(effects (font (size 1.27 1.27)) (justify left))
)
(property "Value" "C_Small" (id 1) (at 0.254 -2.032 0)
(effects (font (size 1.27 1.27)) (justify left))
)
(property "Footprint" "" (id 2) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "~" (id 3) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_keywords" "capacitor cap" (id 4) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_description" "Unpolarized capacitor, small symbol" (id 5) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_fp_filters" "C_*" (id 6) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(symbol "C_Small_0_1"
(polyline
(pts
(xy -1.524 -0.508)
(xy 1.524 -0.508)
)
(stroke (width 0.3302) (type default) (color 0 0 0 0))
(fill (type none))
)
(polyline
(pts
(xy -1.524 0.508)
(xy 1.524 0.508)
)
(stroke (width 0.3048) (type default) (color 0 0 0 0))
(fill (type none))
)
)
(symbol "C_Small_1_1"
(pin passive line (at 0 2.54 270) (length 2.032)
(name "~" (effects (font (size 1.27 1.27))))
(number "1" (effects (font (size 1.27 1.27))))
)
(pin passive line (at 0 -2.54 90) (length 2.032)
(name "~" (effects (font (size 1.27 1.27))))
(number "2" (effects (font (size 1.27 1.27))))
)
)
)
(symbol "Device:R_Small" (pin_numbers hide) (pin_names (offset 0.254) hide) (in_bom yes) (on_board yes)
(property "Reference" "R" (id 0) (at 0.762 0.508 0)
(effects (font (size 1.27 1.27)) (justify left))
)
(property "Value" "R_Small" (id 1) (at 0.762 -1.016 0)
(effects (font (size 1.27 1.27)) (justify left))
)
(property "Footprint" "" (id 2) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "~" (id 3) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_keywords" "R resistor" (id 4) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_description" "Resistor, small symbol" (id 5) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_fp_filters" "R_*" (id 6) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(symbol "R_Small_0_1"
(rectangle (start -0.762 1.778) (end 0.762 -1.778)
(stroke (width 0.2032) (type default) (color 0 0 0 0))
(fill (type none))
)
)
(symbol "R_Small_1_1"
(pin passive line (at 0 2.54 270) (length 0.762)
(name "~" (effects (font (size 1.27 1.27))))
(number "1" (effects (font (size 1.27 1.27))))
)
(pin passive line (at 0 -2.54 90) (length 0.762)
(name "~" (effects (font (size 1.27 1.27))))
(number "2" (effects (font (size 1.27 1.27))))
)
)
)
(symbol "Device:R_Variable" (pin_numbers hide) (pin_names (offset 0)) (in_bom yes) (on_board yes)
(property "Reference" "R" (id 0) (at 2.54 -2.54 90)
(effects (font (size 1.27 1.27)) (justify left))
)
(property "Value" "R_Variable" (id 1) (at -2.54 -1.27 90)
(effects (font (size 1.27 1.27)) (justify left))
)
(property "Footprint" "" (id 2) (at -1.778 0 90)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "~" (id 3) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_keywords" "R res resistor variable potentiometer rheostat" (id 4) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_description" "Variable resistor" (id 5) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_fp_filters" "R_*" (id 6) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(symbol "R_Variable_0_1"
(rectangle (start -1.016 -2.54) (end 1.016 2.54)
(stroke (width 0.254) (type default) (color 0 0 0 0))
(fill (type none))
)
(polyline
(pts
(xy 2.54 1.524)
(xy 2.54 2.54)
(xy 1.524 2.54)
(xy 2.54 2.54)
(xy -2.032 -2.032)
)
(stroke (width 0) (type default) (color 0 0 0 0))
(fill (type none))
)
)
(symbol "R_Variable_1_1"
(pin passive line (at 0 3.81 270) (length 1.27)
(name "~" (effects (font (size 1.27 1.27))))
(number "1" (effects (font (size 1.27 1.27))))
)
(pin passive line (at 0 -3.81 90) (length 1.27)
(name "~" (effects (font (size 1.27 1.27))))
(number "2" (effects (font (size 1.27 1.27))))
)
)
)
(symbol "Transistor_BJT:BC547" (pin_names (offset 0) hide) (in_bom yes) (on_board yes)
(property "Reference" "Q" (id 0) (at 5.08 1.905 0)
(effects (font (size 1.27 1.27)) (justify left))
)
(property "Value" "BC547" (id 1) (at 5.08 0 0)
(effects (font (size 1.27 1.27)) (justify left))
)
(property "Footprint" "Package_TO_SOT_THT:TO-92_Inline" (id 2) (at 5.08 -1.905 0)
(effects (font (size 1.27 1.27) italic) (justify left) hide)
)
(property "Datasheet" "https://www.onsemi.com/pub/Collateral/BC550-D.pdf" (id 3) (at 0 0 0)
(effects (font (size 1.27 1.27)) (justify left) hide)
)
(property "ki_keywords" "NPN Transistor" (id 4) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_description" "0.1A Ic, 45V Vce, Small Signal NPN Transistor, TO-92" (id 5) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_fp_filters" "TO?92*" (id 6) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(symbol "BC547_0_1"
(polyline
(pts
(xy 0 0)
(xy 0.635 0)
)
(stroke (width 0) (type default) (color 0 0 0 0))
(fill (type none))
)
(polyline
(pts
(xy 0.635 0.635)
(xy 2.54 2.54)
)
(stroke (width 0) (type default) (color 0 0 0 0))
(fill (type none))
)
(polyline
(pts
(xy 0.635 -0.635)
(xy 2.54 -2.54)
(xy 2.54 -2.54)
)
(stroke (width 0) (type default) (color 0 0 0 0))
(fill (type none))
)
(polyline
(pts
(xy 0.635 1.905)
(xy 0.635 -1.905)
(xy 0.635 -1.905)
)
(stroke (width 0.508) (type default) (color 0 0 0 0))
(fill (type none))
)
(polyline
(pts
(xy 1.27 -1.778)
(xy 1.778 -1.27)
(xy 2.286 -2.286)
(xy 1.27 -1.778)
(xy 1.27 -1.778)
)
(stroke (width 0) (type default) (color 0 0 0 0))
(fill (type outline))
)
(circle (center 1.27 0) (radius 2.8194)
(stroke (width 0.254) (type default) (color 0 0 0 0))
(fill (type none))
)
)
(symbol "BC547_1_1"
(pin passive line (at 2.54 5.08 270) (length 2.54)
(name "C" (effects (font (size 1.27 1.27))))
(number "1" (effects (font (size 1.27 1.27))))
)
(pin input line (at -5.08 0 0) (length 5.08)
(name "B" (effects (font (size 1.27 1.27))))
(number "2" (effects (font (size 1.27 1.27))))
)
(pin passive line (at 2.54 -5.08 90) (length 2.54)
(name "E" (effects (font (size 1.27 1.27))))
(number "3" (effects (font (size 1.27 1.27))))
)
)
)
(symbol "power:+VDC" (power) (pin_names (offset 0)) (in_bom yes) (on_board yes)
(property "Reference" "#PWR" (id 0) (at 0 -2.54 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Value" "+VDC" (id 1) (at 0 6.35 0)
(effects (font (size 1.27 1.27)))
)
(property "Footprint" "" (id 2) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "" (id 3) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_keywords" "power-flag" (id 4) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_description" "Power symbol creates a global label with name \"+VDC\"" (id 5) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(symbol "+VDC_0_1"
(polyline
(pts
(xy -1.143 3.175)
(xy 1.143 3.175)
)
(stroke (width 0.508) (type default) (color 0 0 0 0))
(fill (type none))
)
(polyline
(pts
(xy 0 0)
(xy 0 1.27)
)
(stroke (width 0) (type default) (color 0 0 0 0))
(fill (type none))
)
(polyline
(pts
(xy 0 2.032)
(xy 0 4.318)
)
(stroke (width 0.508) (type default) (color 0 0 0 0))
(fill (type none))
)
(circle (center 0 3.175) (radius 1.905)
(stroke (width 0.254) (type default) (color 0 0 0 0))
(fill (type none))
)
)
(symbol "+VDC_1_1"
(pin power_in line (at 0 0 90) (length 0) hide
(name "+VDC" (effects (font (size 1.27 1.27))))
(number "1" (effects (font (size 1.27 1.27))))
)
)
)
(symbol "power:GND" (power) (pin_names (offset 0)) (in_bom yes) (on_board yes)
(property "Reference" "#PWR" (id 0) (at 0 -6.35 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Value" "GND" (id 1) (at 0 -3.81 0)
(effects (font (size 1.27 1.27)))
)
(property "Footprint" "" (id 2) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "" (id 3) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_keywords" "power-flag" (id 4) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "ki_description" "Power symbol creates a global label with name \"GND\" , ground" (id 5) (at 0 0 0)
(effects (font (size 1.27 1.27)) hide)
)
(symbol "GND_0_1"
(polyline
(pts
(xy 0 0)
(xy 0 -1.27)
(xy 1.27 -1.27)
(xy 0 -2.54)
(xy -1.27 -1.27)
(xy 0 -1.27)
)
(stroke (width 0) (type default) (color 0 0 0 0))
(fill (type none))
)
)
(symbol "GND_1_1"
(pin power_in line (at 0 0 270) (length 0) hide
(name "GND" (effects (font (size 1.27 1.27))))
(number "1" (effects (font (size 1.27 1.27))))
)
)
)
)
(junction (at 53.8988 65.532) (diameter 0) (color 0 0 0 0)
(uuid 17315159-e7fe-43fe-b8ee-d4e779aaa373)
)
(junction (at 53.848 65.532) (diameter 0) (color 0 0 0 0)
(uuid 57ee0760-9576-48af-90f7-364c37cabb1b)
)
(junction (at 43.0276 65.532) (diameter 0) (color 0 0 0 0)
(uuid 726b4ecd-b5c6-41fb-b223-0c0dd9bc6eea)
)
(junction (at 78.994 56.8452) (diameter 0) (color 0 0 0 0)
(uuid c79fa170-f201-4298-b9a2-b976ba73d852)
)
(junction (at 96.901 56.8452) (diameter 0) (color 0 0 0 0)
(uuid fa854518-3e9d-4aff-bed4-11e521c138bf)
)
(wire (pts (xy 50.038 65.532) (xy 53.848 65.532))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid 0e27eaec-c0d3-434e-bef4-527602f89337)
)
(wire (pts (xy 43.0276 65.532) (xy 43.0276 62.992))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid 0ff5aa2c-c917-4dc4-acc0-d50253bf384d)
)
(wire (pts (xy 78.994 46.9392) (xy 78.994 48.4124))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid 37f7b59c-38b5-480b-a1eb-3ec04668e26b)
)
(wire (pts (xy 57.404 56.8452) (xy 53.8988 56.8452))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid 42879fad-9d25-424d-9580-fed9c8d03141)
)
(wire (pts (xy 43.0276 65.532) (xy 44.958 65.532))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid 4be05548-be58-4898-9ef6-b45175b1e028)
)
(wire (pts (xy 78.994 53.4924) (xy 78.994 56.8452))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid 4eeb227f-cc7d-419b-85b1-dc4edc14d05b)
)
(wire (pts (xy 78.994 56.8452) (xy 85.217 56.8452))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid 5acd2b0a-deec-48a7-afe4-8b48ee265b49)
)
(wire (pts (xy 96.901 56.8452) (xy 100.33 56.8452))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid 73811e91-5306-4a15-94a4-7d6a8d37e781)
)
(wire (pts (xy 100.33 59.3852) (xy 96.901 59.3852))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid 9198b258-835a-4352-906e-9a8ddea0db56)
)
(wire (pts (xy 71.374 65.532) (xy 53.8988 65.532))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid a51b8f1f-1419-42da-8b9f-ecb2c1041f5e)
)
(wire (pts (xy 71.628 56.8452) (xy 78.994 56.8452))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid a7689943-98b6-4d73-a96b-c03b6f2dde00)
)
(wire (pts (xy 65.024 56.8452) (xy 66.548 56.8452))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid a96927f2-2daf-42f2-98bc-fb9a6a53a901)
)
(wire (pts (xy 96.901 59.3852) (xy 96.901 56.8452))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid b432a701-f032-4b36-a04f-cc761a13d54d)
)
(wire (pts (xy 41.3512 62.992) (xy 43.0276 62.992))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid bd96242b-b734-44a0-aa30-cb247382d0bb)
)
(wire (pts (xy 92.837 56.8452) (xy 96.901 56.8452))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid cd1498be-6f64-476b-a0f8-1c18470908f7)
)
(wire (pts (xy 41.3512 65.532) (xy 43.0276 65.532))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid d5002594-0969-4b6e-87c9-20b35487186d)
)
(wire (pts (xy 53.8988 56.8452) (xy 53.8988 65.532))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid df4b3e73-600d-4f2c-9dd9-84ef5eb5a267)
)
(wire (pts (xy 78.994 60.452) (xy 78.994 56.8452))
(stroke (width 0) (type default) (color 0 0 0 0))
(uuid e7a7af85-0fb2-4614-abdb-dd1ac7ea96f4)
)
(symbol (lib_id "Device:R_Small") (at 78.994 50.9524 0) (unit 1)
(in_bom yes) (on_board yes)
(uuid 00907483-9dbc-4a29-a819-641b654602ef)
(property "Reference" "Rc" (id 0) (at 80.3656 50.6476 0)
(effects (font (size 1.27 1.27)) (justify left))
)
(property "Value" "4.7k" (id 1) (at 76.708 52.5272 90)
(effects (font (size 1.27 1.27)) (justify left))
)
(property "Footprint" "" (id 2) (at 78.994 50.9524 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "~" (id 3) (at 78.994 50.9524 0)
(effects (font (size 1.27 1.27)) hide)
)
(pin "1" (uuid bccf1355-9c4d-4fc4-a660-ba68c97ccfaf))
(pin "2" (uuid c9b81525-c540-4ab4-848f-d7aa5d53405e))
)
(symbol (lib_id "Device:R_Small") (at 69.088 56.8452 90) (unit 1)
(in_bom yes) (on_board yes)
(uuid 0292e234-724d-450e-945a-b0673bb556a9)
(property "Reference" "Rf1" (id 0) (at 69.215 59.6392 90))
(property "Value" "4.7k" (id 1) (at 69.215 54.1782 90))
(property "Footprint" "" (id 2) (at 69.088 56.8452 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "~" (id 3) (at 69.088 56.8452 0)
(effects (font (size 1.27 1.27)) hide)
)
(pin "1" (uuid 50781d6f-74e0-49b3-94e5-aff9bc85ec42))
(pin "2" (uuid 71f8b8e8-878d-4eec-a8c7-79fd3bd9fbc9))
)
(symbol (lib_id "Device:C_Small") (at 47.498 65.532 90) (unit 1)
(in_bom yes) (on_board yes)
(uuid 08201afb-305f-4d95-b6a4-797e919f2282)
(property "Reference" "C1" (id 0) (at 47.5488 62.3824 90))
(property "Value" "100nF" (id 1) (at 48.26 69.2912 90))
(property "Footprint" "" (id 2) (at 47.498 65.532 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "~" (id 3) (at 47.498 65.532 0)
(effects (font (size 1.27 1.27)) hide)
)
(pin "1" (uuid 2f092f08-b771-4b23-a682-12cf0b60753c))
(pin "2" (uuid 918929ce-b3ef-488e-afb8-b28c100601c6))
)
(symbol (lib_id "power:+VDC") (at 78.994 46.9392 0) (unit 1)
(in_bom yes) (on_board yes) (fields_autoplaced)
(uuid 0cb467c4-c8bc-4ecc-93aa-a4422e2d4254)
(property "Reference" "#PWR?" (id 0) (at 78.994 49.4792 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Value" "+VDC" (id 1) (at 78.994 39.3192 0))
(property "Footprint" "" (id 2) (at 78.994 46.9392 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "" (id 3) (at 78.994 46.9392 0)
(effects (font (size 1.27 1.27)) hide)
)
(pin "1" (uuid a3023d6e-74e0-4014-8afe-609bfe8dc480))
)
(symbol (lib_id "Device:R_Variable") (at 61.214 56.8452 270) (mirror x) (unit 1)
(in_bom yes) (on_board yes)
(uuid 3280d9c9-a8dc-44c1-8f23-802994722420)
(property "Reference" "Rf2" (id 0) (at 59.7916 59.6392 90)
(effects (font (size 1.27 1.27)) (justify left))
)
(property "Value" "500k" (id 1) (at 58.2676 53.086 90)
(effects (font (size 1.27 1.27)) (justify left))
)
(property "Footprint" "" (id 2) (at 61.214 58.6232 90)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "~" (id 3) (at 61.214 56.8452 0)
(effects (font (size 1.27 1.27)) hide)
)
(pin "1" (uuid 9e2215b3-5f7b-4e92-9925-e6d830b5d655))
(pin "2" (uuid 2d9a0ff8-076e-4ed0-9542-688401782ecc))
)
(symbol (lib_id "Connector:AudioJack3") (at 36.2712 62.992 0) (unit 1)
(in_bom yes) (on_board yes) (fields_autoplaced)
(uuid 64b9fd4a-dc4c-4d75-b3e7-ea0087b7c826)
(property "Reference" "J1" (id 0) (at 34.3662 54.5592 0))
(property "Value" "Audio Input" (id 1) (at 34.3662 57.0992 0))
(property "Footprint" "" (id 2) (at 36.2712 62.992 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "~" (id 3) (at 36.2712 62.992 0)
(effects (font (size 1.27 1.27)) hide)
)
(pin "R" (uuid 0036499d-7509-49ad-9b7f-f18935b97f71))
(pin "S" (uuid af129976-cb01-4d50-9844-b49e73d41883))
(pin "T" (uuid 7c1b2893-300b-4917-a09a-a13f05384bbd))
)
(symbol (lib_id "power:GND") (at 78.994 70.612 0) (unit 1)
(in_bom yes) (on_board yes) (fields_autoplaced)
(uuid 80beced2-3605-479b-a28d-19c5c3b2f939)
(property "Reference" "#PWR?" (id 0) (at 78.994 76.962 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Value" "GND" (id 1) (at 78.994 76.073 0))
(property "Footprint" "" (id 2) (at 78.994 70.612 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "" (id 3) (at 78.994 70.612 0)
(effects (font (size 1.27 1.27)) hide)
)
(pin "1" (uuid 094dcdf4-d154-40e4-9fa7-f4d87c998c27))
)
(symbol (lib_id "Device:C_Polarized") (at 89.027 56.8452 90) (unit 1)
(in_bom yes) (on_board yes)
(uuid b74afe1f-99fd-43cb-bf92-b80d5c11fd71)
(property "Reference" "C2" (id 0) (at 88.9 52.7812 90))
(property "Value" "10uF" (id 1) (at 89.281 60.5282 90))
(property "Footprint" "" (id 2) (at 92.837 55.88 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "~" (id 3) (at 89.027 56.8452 0)
(effects (font (size 1.27 1.27)) hide)
)
(pin "1" (uuid bd1d110d-1873-4f2a-8b6b-dcd66e701ffa))
(pin "2" (uuid a791392d-bad5-44af-b379-a357b4bed9d5))
)
(symbol (lib_id "Connector:AudioJack3") (at 105.41 59.3852 180) (unit 1)
(in_bom yes) (on_board yes) (fields_autoplaced)
(uuid bdeb6b1d-2b1b-4b11-97c7-580bd4b5e8a7)
(property "Reference" "J2" (id 0) (at 113.538 57.4801 0)
(effects (font (size 1.27 1.27)) (justify right))
)
(property "Value" "Audio Output" (id 1) (at 113.538 60.0201 0)
(effects (font (size 1.27 1.27)) (justify right))
)
(property "Footprint" "" (id 2) (at 105.41 59.3852 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "~" (id 3) (at 105.41 59.3852 0)
(effects (font (size 1.27 1.27)) hide)
)
(pin "R" (uuid 8cd3273b-5b42-4074-aefb-5ce6c5d194ff))
(pin "S" (uuid 5c93d31a-8068-4485-92d1-6a9ad09703ea))
(pin "T" (uuid e77adcf5-23a3-4d61-94e6-b7dce0df2f81))
)
(symbol (lib_id "Transistor_BJT:BC547") (at 76.454 65.532 0) (unit 1)
(in_bom yes) (on_board yes) (fields_autoplaced)
(uuid c4dac3ab-0a43-4193-90f0-f947f1884d05)
(property "Reference" "Q1" (id 0) (at 81.788 64.2619 0)
(effects (font (size 1.27 1.27)) (justify left))
)
(property "Value" "BC547" (id 1) (at 81.788 66.8019 0)
(effects (font (size 1.27 1.27)) (justify left))
)
(property "Footprint" "Package_TO_SOT_THT:TO-92_Inline" (id 2) (at 81.534 67.437 0)
(effects (font (size 1.27 1.27) italic) (justify left) hide)
)
(property "Datasheet" "https://www.onsemi.com/pub/Collateral/BC550-D.pdf" (id 3) (at 76.454 65.532 0)
(effects (font (size 1.27 1.27)) (justify left) hide)
)
(pin "1" (uuid f8b53b88-2c5f-4348-a3a8-9a7cdd783b8b))
(pin "2" (uuid 6e25f923-8f54-4ebe-b2c9-fdef16e2ebc0))
(pin "3" (uuid fa4bf5c3-8def-4c30-845d-51a4dcdf2b6a))
)
(symbol (lib_id "power:GND") (at 100.33 61.9252 0) (unit 1)
(in_bom yes) (on_board yes) (fields_autoplaced)
(uuid cb33844d-c532-48df-b9c5-eb42f680408c)
(property "Reference" "#PWR?" (id 0) (at 100.33 68.2752 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Value" "GND" (id 1) (at 100.33 67.2592 0))
(property "Footprint" "" (id 2) (at 100.33 61.9252 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "" (id 3) (at 100.33 61.9252 0)
(effects (font (size 1.27 1.27)) hide)
)
(pin "1" (uuid 78dfdfbd-23bc-4892-8b84-06a859790001))
)
(symbol (lib_id "power:GND") (at 41.3512 60.452 90) (unit 1)
(in_bom yes) (on_board yes) (fields_autoplaced)
(uuid f79f7393-506c-4906-aaf7-f61b29c28655)
(property "Reference" "#PWR?" (id 0) (at 47.7012 60.452 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Value" "GND" (id 1) (at 44.958 60.4519 90)
(effects (font (size 1.27 1.27)) (justify right) hide)
)
(property "Footprint" "" (id 2) (at 41.3512 60.452 0)
(effects (font (size 1.27 1.27)) hide)
)
(property "Datasheet" "" (id 3) (at 41.3512 60.452 0)
(effects (font (size 1.27 1.27)) hide)
)
(pin "1" (uuid f67da9b7-4372-4b9e-942c-24e13bc41ef0))
)
(sheet_instances
(path "/" (page "1"))
)
(symbol_instances
(path "/0cb467c4-c8bc-4ecc-93aa-a4422e2d4254"
(reference "#PWR?") (unit 1) (value "+VDC") (footprint "")
)
(path "/80beced2-3605-479b-a28d-19c5c3b2f939"
(reference "#PWR?") (unit 1) (value "GND") (footprint "")
)
(path "/cb33844d-c532-48df-b9c5-eb42f680408c"
(reference "#PWR?") (unit 1) (value "GND") (footprint "")
)
(path "/f79f7393-506c-4906-aaf7-f61b29c28655"
(reference "#PWR?") (unit 1) (value "GND") (footprint "")
)
(path "/08201afb-305f-4d95-b6a4-797e919f2282"
(reference "C1") (unit 1) (value "100nF") (footprint "")
)
(path "/b74afe1f-99fd-43cb-bf92-b80d5c11fd71"
(reference "C2") (unit 1) (value "10uF") (footprint "")
)
(path "/64b9fd4a-dc4c-4d75-b3e7-ea0087b7c826"
(reference "J1") (unit 1) (value "Audio Input") (footprint "")
)
(path "/bdeb6b1d-2b1b-4b11-97c7-580bd4b5e8a7"
(reference "J2") (unit 1) (value "Audio Output") (footprint "")
)
(path "/c4dac3ab-0a43-4193-90f0-f947f1884d05"
(reference "Q1") (unit 1) (value "BC547") (footprint "Package_TO_SOT_THT:TO-92_Inline")
)
(path "/00907483-9dbc-4a29-a819-641b654602ef"
(reference "Rc") (unit 1) (value "4.7k") (footprint "")
)
(path "/0292e234-724d-450e-945a-b0673bb556a9"
(reference "Rf1") (unit 1) (value "4.7k") (footprint "")
)
(path "/3280d9c9-a8dc-44c1-8f23-802994722420"
(reference "Rf2") (unit 1) (value "500k") (footprint "")
)
)
)

Wyświetl plik

@ -0,0 +1,55 @@
Version 4
SHEET 1 2344 2032
WIRE 1056 928 880 928
WIRE 1056 976 1056 928
WIRE 784 1216 704 1216
WIRE 1056 1216 1056 1056
WIRE 1056 1216 784 1216
WIRE 1184 1216 1120 1216
WIRE 784 1312 784 1216
WIRE 624 1360 560 1360
WIRE 704 1360 704 1296
WIRE 704 1360 688 1360
WIRE 720 1360 704 1360
WIRE 560 1408 560 1360
WIRE 784 1456 784 1408
WIRE 560 1536 560 1488
FLAG 784 1456 0
FLAG 560 1536 0
FLAG 560 1360 IN
FLAG 880 1008 0
SYMBOL VOLTAGE 880 912 R0
WINDOW 0 -77 69 Left 2
WINDOW 123 0 0 Left 0
WINDOW 39 0 0 Left 0
SYMATTR InstName V1
SYMATTR Value 5
SYMBOL npn 720 1312 R0
WINDOW 0 54 32 Left 2
WINDOW 3 52 69 Left 2
SYMATTR InstName Q1
SYMATTR Value BC547A
SYMBOL res 688 1200 R0
SYMATTR InstName R2
SYMATTR Value 4.7k
SYMBOL voltage 560 1392 R0
WINDOW 123 0 0 Left 0
WINDOW 39 0 0 Left 0
SYMATTR InstName V3
SYMATTR Value SINE(0 0.06 1K)
SYMBOL cap 688 1344 R90
WINDOW 0 0 32 VBottom 2
WINDOW 3 32 32 VTop 2
SYMATTR InstName C
SYMATTR Value 100nF
SYMBOL res 1040 960 R0
SYMATTR InstName R1
SYMATTR Value 4.7k
SYMBOL cap 1120 1200 R90
WINDOW 0 0 32 VBottom 2
WINDOW 3 32 32 VTop 2
SYMATTR InstName C1
SYMATTR Value 10µF
TEXT 1384 1096 Left 2 !.tran 10m
TEXT 1384 1048 Left 2 !.options maxstep=10u
TEXT 1384 1152 Left 2 !.fourier 1K V(a)

Wyświetl plik

@ -0,0 +1 @@
0

16
common.h 100644
Wyświetl plik

@ -0,0 +1,16 @@
#ifndef MY_HEADER_FILE_H
#define MY_HEADER_FILE_H
enum si5351_clock {SI5351_CLK0, SI5351_CLK1, SI5351_CLK2};
void si_output_enable(enum si5351_clock clk, uint8_t enable);
// Relay pins
#define relay_1 19
#define relay_2 20
#define PTT_PIN 21
#define LED_PIN 25
void pre_transmit();
#endif

1
fft/README.md 100644
Wyświetl plik

@ -0,0 +1 @@
This folder has unmodified code from https://github.com/mborgerding/kissfft.

Wyświetl plik

@ -10,11 +10,16 @@
defines kiss_fft_scalar as either short or a float type
and defines
typedef struct { kiss_fft_scalar r; kiss_fft_scalar i; }kiss_fft_cpx; */
#ifndef _kiss_fft_guts_h
#define _kiss_fft_guts_h
#include "kiss_fft.h"
#include "kiss_fft_log.h"
#include <limits.h>
#define MAXFACTORS 32
/* e.g. an fft of length 128 has 4 factors
/* e.g. an fft of length 128 has 4 factors
as far as kissfft is concerned
4*4*4*2
*/
@ -36,22 +41,23 @@ struct kiss_fft_state{
C_ADDTO( res , a) : res += a
* */
#ifdef FIXED_POINT
#include <stdint.h>
#if (FIXED_POINT==32)
# define FRACBITS 31
# define SAMPPROD int64_t
#define SAMP_MAX 2147483647
#define SAMP_MAX INT32_MAX
#define SAMP_MIN INT32_MIN
#else
# define FRACBITS 15
# define SAMPPROD int32_t
#define SAMP_MAX 32767
# define SAMPPROD int32_t
#define SAMP_MAX INT16_MAX
#define SAMP_MIN INT16_MIN
#endif
#define SAMP_MIN -SAMP_MAX
#if defined(CHECK_OVERFLOW)
# define CHECK_OVERFLOW_OP(a,op,b) \
if ( (SAMPPROD)(a) op (SAMPPROD)(b) > SAMP_MAX || (SAMPPROD)(a) op (SAMPPROD)(b) < SAMP_MIN ) { \
fprintf(stderr,"WARNING:overflow @ " __FILE__ "(%d): (%d " #op" %d) = %ld\n",__LINE__,(a),(b),(SAMPPROD)(a) op (SAMPPROD)(b) ); }
if ( (SAMPPROD)(a) op (SAMPPROD)(b) > SAMP_MAX || (SAMPPROD)(a) op (SAMPPROD)(b) < SAMP_MIN ) { \
KISS_FFT_WARNING("overflow (%d " #op" %d) = %ld", (a),(b),(SAMPPROD)(a) op (SAMPPROD)(b)); }
#endif
@ -65,11 +71,11 @@ struct kiss_fft_state{
(m).i = sround( smul((a).r,(b).i) + smul((a).i,(b).r) ); }while(0)
# define DIVSCALAR(x,k) \
(x) = sround( smul( x, SAMP_MAX/k ) )
(x) = sround( smul( x, SAMP_MAX/k ) )
# define C_FIXDIV(c,div) \
do { DIVSCALAR( (c).r , div); \
DIVSCALAR( (c).i , div); }while (0)
do { DIVSCALAR( (c).r , div); \
DIVSCALAR( (c).i , div); }while (0)
# define C_MULBYSCALAR( c, s ) \
do{ (c).r = sround( smul( (c).r , s ) ) ;\
@ -93,28 +99,28 @@ struct kiss_fft_state{
#define C_ADD( res, a,b)\
do { \
CHECK_OVERFLOW_OP((a).r,+,(b).r)\
CHECK_OVERFLOW_OP((a).i,+,(b).i)\
(res).r=(a).r+(b).r; (res).i=(a).i+(b).i; \
CHECK_OVERFLOW_OP((a).r,+,(b).r)\
CHECK_OVERFLOW_OP((a).i,+,(b).i)\
(res).r=(a).r+(b).r; (res).i=(a).i+(b).i; \
}while(0)
#define C_SUB( res, a,b)\
do { \
CHECK_OVERFLOW_OP((a).r,-,(b).r)\
CHECK_OVERFLOW_OP((a).i,-,(b).i)\
(res).r=(a).r-(b).r; (res).i=(a).i-(b).i; \
CHECK_OVERFLOW_OP((a).r,-,(b).r)\
CHECK_OVERFLOW_OP((a).i,-,(b).i)\
(res).r=(a).r-(b).r; (res).i=(a).i-(b).i; \
}while(0)
#define C_ADDTO( res , a)\
do { \
CHECK_OVERFLOW_OP((res).r,+,(a).r)\
CHECK_OVERFLOW_OP((res).i,+,(a).i)\
(res).r += (a).r; (res).i += (a).i;\
CHECK_OVERFLOW_OP((res).r,+,(a).r)\
CHECK_OVERFLOW_OP((res).i,+,(a).i)\
(res).r += (a).r; (res).i += (a).i;\
}while(0)
#define C_SUBFROM( res , a)\
do {\
CHECK_OVERFLOW_OP((res).r,-,(a).r)\
CHECK_OVERFLOW_OP((res).i,-,(a).i)\
(res).r -= (a).r; (res).i -= (a).i; \
CHECK_OVERFLOW_OP((res).r,-,(a).r)\
CHECK_OVERFLOW_OP((res).i,-,(a).i)\
(res).r -= (a).r; (res).i -= (a).i; \
}while(0)
@ -129,30 +135,33 @@ struct kiss_fft_state{
#else
# define KISS_FFT_COS(phase) (kiss_fft_scalar) cos(phase)
# define KISS_FFT_SIN(phase) (kiss_fft_scalar) sin(phase)
# define HALF_OF(x) ((x)*.5)
# define HALF_OF(x) ((x)*((kiss_fft_scalar).5))
#endif
#define kf_cexp(x,phase) \
do{ \
(x)->r = KISS_FFT_COS(phase);\
(x)->i = KISS_FFT_SIN(phase);\
}while(0)
do{ \
(x)->r = KISS_FFT_COS(phase);\
(x)->i = KISS_FFT_SIN(phase);\
}while(0)
/* a debugging function */
#define pcpx(c)\
fprintf(stderr,"%g + %gi\n",(double)((c)->r),(double)((c)->i) )
KISS_FFT_DEBUG("%g + %gi\n",(double)((c)->r),(double)((c)->i))
#ifdef KISS_FFT_USE_ALLOCA
// define this to allow use of alloca instead of malloc for temporary buffers
// Temporary buffers are used in two case:
// Temporary buffers are used in two case:
// 1. FFT sizes that have "bad" factors. i.e. not 2,3 and 5
// 2. "in-place" FFTs. Notice the quotes, since kissfft does not really do an in-place transform.
#include <alloca.h>
#define KISS_FFT_TMP_ALLOC(nbytes) alloca(nbytes)
#define KISS_FFT_TMP_FREE(ptr)
#define KISS_FFT_TMP_FREE(ptr)
#else
#define KISS_FFT_TMP_ALLOC(nbytes) KISS_FFT_MALLOC(nbytes)
#define KISS_FFT_TMP_FREE(ptr) KISS_FFT_FREE(ptr)
#endif
#endif /* _kiss_fft_guts_h */

Wyświetl plik

@ -203,6 +203,10 @@ static void kf_bfly_generic(
int Norig = st->nfft;
kiss_fft_cpx * scratch = (kiss_fft_cpx*)KISS_FFT_TMP_ALLOC(sizeof(kiss_fft_cpx)*p);
if (scratch == NULL){
KISS_FFT_ERROR("Memory allocation failed.");
return;
}
for ( u=0; u<m; ++u ) {
k=u;
@ -244,23 +248,23 @@ void kf_work(
const kiss_fft_cpx * Fout_end = Fout + p*m;
#ifdef _OPENMP
// use openmp extensions at the
// use openmp extensions at the
// top-level (not recursive)
if (fstride==1 && p<=5)
if (fstride==1 && p<=5 && m!=1)
{
int k;
// execute the p different work units in different threads
# pragma omp parallel for
for (k=0;k<p;++k)
for (k=0;k<p;++k)
kf_work( Fout +k*m, f+ fstride*in_stride*k,fstride*p,in_stride,factors,st);
// all threads have joined by this point
switch (p) {
case 2: kf_bfly2(Fout,fstride,st,m); break;
case 3: kf_bfly3(Fout,fstride,st,m); break;
case 3: kf_bfly3(Fout,fstride,st,m); break;
case 4: kf_bfly4(Fout,fstride,st,m); break;
case 5: kf_bfly5(Fout,fstride,st,m); break;
case 5: kf_bfly5(Fout,fstride,st,m); break;
default: kf_bfly_generic(Fout,fstride,st,m,p); break;
}
return;
@ -276,7 +280,7 @@ void kf_work(
do{
// recursive call:
// DFT of size m*p performed by doing
// p instances of smaller DFTs of size m,
// p instances of smaller DFTs of size m,
// each one takes a decimated version of the input
kf_work( Fout , f, fstride*p, in_stride, factors,st);
f += fstride*in_stride;
@ -285,21 +289,21 @@ void kf_work(
Fout=Fout_beg;
// recombine the p smaller DFTs
// recombine the p smaller DFTs
switch (p) {
case 2: kf_bfly2(Fout,fstride,st,m); break;
case 3: kf_bfly3(Fout,fstride,st,m); break;
case 3: kf_bfly3(Fout,fstride,st,m); break;
case 4: kf_bfly4(Fout,fstride,st,m); break;
case 5: kf_bfly5(Fout,fstride,st,m); break;
case 5: kf_bfly5(Fout,fstride,st,m); break;
default: kf_bfly_generic(Fout,fstride,st,m,p); break;
}
}
/* facbuf is populated by p1,m1,p2,m2, ...
where
where
p[i] * m[i] = m[i-1]
m0 = n */
static
static
void kf_factor(int n,int * facbuf)
{
int p=4;
@ -332,9 +336,11 @@ void kf_factor(int n,int * facbuf)
* */
kiss_fft_cfg kiss_fft_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem )
{
KISS_FFT_ALIGN_CHECK(mem)
kiss_fft_cfg st=NULL;
size_t memneeded = sizeof(struct kiss_fft_state)
+ sizeof(kiss_fft_cpx)*(nfft-1); /* twiddle factors*/
size_t memneeded = KISS_FFT_ALIGN_SIZE_UP(sizeof(struct kiss_fft_state)
+ sizeof(kiss_fft_cpx)*(nfft-1)); /* twiddle factors*/
if ( lenmem==NULL ) {
st = ( kiss_fft_cfg)KISS_FFT_MALLOC( memneeded );
@ -367,7 +373,19 @@ void kiss_fft_stride(kiss_fft_cfg st,const kiss_fft_cpx *fin,kiss_fft_cpx *fout,
if (fin == fout) {
//NOTE: this is not really an in-place FFT algorithm.
//It just performs an out-of-place FFT into a temp buffer
if (fout == NULL){
KISS_FFT_ERROR("fout buffer NULL.");
return;
}
kiss_fft_cpx * tmpbuf = (kiss_fft_cpx*)KISS_FFT_TMP_ALLOC( sizeof(kiss_fft_cpx)*st->nfft);
if (tmpbuf == NULL){
KISS_FFT_ERROR("Memory allocation error.");
return;
}
kf_work(tmpbuf,fin,1,in_stride, st->factors,st);
memcpy(fout,tmpbuf,sizeof(kiss_fft_cpx)*st->nfft);
KISS_FFT_TMP_FREE(tmpbuf);

Wyświetl plik

@ -14,7 +14,20 @@
#include <math.h>
#include <string.h>
#include <stdint.h>
// Define KISS_FFT_SHARED macro to properly export symbols
#ifdef KISS_FFT_SHARED
# ifdef _WIN32
# ifdef KISS_FFT_BUILD
# define KISS_FFT_API __declspec(dllexport)
# else
# define KISS_FFT_API __declspec(dllimport)
# endif
# else
# define KISS_FFT_API __attribute__ ((visibility ("default")))
# endif
#else
# define KISS_FFT_API
#endif
#ifdef __cplusplus
extern "C" {
@ -33,13 +46,43 @@ extern "C" {
in the tools/ directory.
*/
#define KISS_FFT_MALLOC malloc
#define KISS_FFT_FREE free
/* User may override KISS_FFT_MALLOC and/or KISS_FFT_FREE. */
#ifdef USE_SIMD
# include <xmmintrin.h>
# define kiss_fft_scalar __m128
# ifndef KISS_FFT_MALLOC
# define KISS_FFT_MALLOC(nbytes) _mm_malloc(nbytes,16)
# define KISS_FFT_ALIGN_CHECK(ptr)
# define KISS_FFT_ALIGN_SIZE_UP(size) ((size + 15UL) & ~0xFUL)
# endif
# ifndef KISS_FFT_FREE
# define KISS_FFT_FREE _mm_free
# endif
#else
# define KISS_FFT_ALIGN_CHECK(ptr)
# define KISS_FFT_ALIGN_SIZE_UP(size) (size)
# ifndef KISS_FFT_MALLOC
# define KISS_FFT_MALLOC malloc
# endif
# ifndef KISS_FFT_FREE
# define KISS_FFT_FREE free
# endif
#endif
#ifdef FIXED_POINT
#include <stdint.h>
# if (FIXED_POINT == 32)
# define kiss_fft_scalar int32_t
# else
# define kiss_fft_scalar int16_t
# endif
#else
# ifndef kiss_fft_scalar
/* default is float */
//CHANGED TO int_16t
#define kiss_fft_scalar float
# define kiss_fft_scalar float
# endif
#endif
typedef struct {
kiss_fft_scalar r;
@ -48,9 +91,9 @@ typedef struct {
typedef struct kiss_fft_state* kiss_fft_cfg;
/*
/*
* kiss_fft_alloc
*
*
* Initialize a FFT (or IFFT) algorithm's cfg/state buffer.
*
* typical usage: kiss_fft_cfg mycfg=kiss_fft_alloc(1024,0,NULL,NULL);
@ -60,18 +103,18 @@ typedef struct kiss_fft_state* kiss_fft_cfg;
*
* If lenmem is NULL, then kiss_fft_alloc will allocate a cfg buffer using malloc.
* The returned value should be free()d when done to avoid memory leaks.
*
*
* The state can be placed in a user supplied buffer 'mem':
* If lenmem is not NULL and mem is not NULL and *lenmem is large enough,
* then the function places the cfg in mem and the size used in *lenmem
* and returns mem.
*
*
* If lenmem is not NULL and ( mem is NULL or *lenmem is not large enough),
* then the function returns NULL and places the minimum cfg
* then the function returns NULL and places the minimum cfg
* buffer size in *lenmem.
* */
kiss_fft_cfg kiss_fft_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem);
kiss_fft_cfg KISS_FFT_API kiss_fft_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem);
/*
* kiss_fft(cfg,in_out_buf)
@ -83,35 +126,35 @@ kiss_fft_cfg kiss_fft_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem)
* Note that each element is complex and can be accessed like
f[k].r and f[k].i
* */
void kiss_fft(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout);
void KISS_FFT_API kiss_fft(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout);
/*
A more generic version of the above function. It reads its input from every Nth sample.
* */
void kiss_fft_stride(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout,int fin_stride);
void KISS_FFT_API kiss_fft_stride(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout,int fin_stride);
/* If kiss_fft_alloc allocated a buffer, it is one contiguous
/* If kiss_fft_alloc allocated a buffer, it is one contiguous
buffer and can be simply free()d when no longer needed*/
#define kiss_fft_free KISS_FFT_FREE
/*
Cleans up some memory that gets managed internally. Not necessary to call, but it might clean up
Cleans up some memory that gets managed internally. Not necessary to call, but it might clean up
your compiler output to call this before you exit.
*/
void kiss_fft_cleanup(void);
void KISS_FFT_API kiss_fft_cleanup(void);
/*
* Returns the smallest integer k, such that k>=n and k has only "fast" factors (2,3,5)
*/
int kiss_fft_next_fast_size(int n);
int KISS_FFT_API kiss_fft_next_fast_size(int n);
/* for real ffts, we need an even size */
#define kiss_fftr_next_fast_size_real(n) \
(kiss_fft_next_fast_size( ((n)+1)>>1)<<1)
#ifdef __cplusplus
}
}
#endif
#endif

36
fft/kiss_fft_log.h 100644
Wyświetl plik

@ -0,0 +1,36 @@
/*
* Copyright (c) 2003-2010, Mark Borgerding. All rights reserved.
* This file is part of KISS FFT - https://github.com/mborgerding/kissfft
*
* SPDX-License-Identifier: BSD-3-Clause
* See COPYING file for more information.
*/
#ifndef kiss_fft_log_h
#define kiss_fft_log_h
#define ERROR 1
#define WARNING 2
#define INFO 3
#define DEBUG 4
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#if defined(NDEBUG)
# define KISS_FFT_LOG_MSG(severity, ...) ((void)0)
#else
# define KISS_FFT_LOG_MSG(severity, ...) \
fprintf(stderr, "[" #severity "] " __FILE__ ":" TOSTRING(__LINE__) " "); \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n")
#endif
#define KISS_FFT_ERROR(...) KISS_FFT_LOG_MSG(ERROR, __VA_ARGS__)
#define KISS_FFT_WARNING(...) KISS_FFT_LOG_MSG(WARNING, __VA_ARGS__)
#define KISS_FFT_INFO(...) KISS_FFT_LOG_MSG(INFO, __VA_ARGS__)
#define KISS_FFT_DEBUG(...) KISS_FFT_LOG_MSG(DEBUG, __VA_ARGS__)
#endif /* kiss_fft_log_h */

Wyświetl plik

@ -13,17 +13,21 @@ struct kiss_fftr_state{
kiss_fft_cfg substate;
kiss_fft_cpx * tmpbuf;
kiss_fft_cpx * super_twiddles;
#ifdef USE_SIMD
void * pad;
#endif
};
kiss_fftr_cfg kiss_fftr_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem)
{
KISS_FFT_ALIGN_CHECK(mem)
int i;
kiss_fftr_cfg st = NULL;
size_t subsize = 0, memneeded;
if (nfft & 1) {
fprintf(stderr,"Real FFT optimization must be even.\n");
KISS_FFT_ERROR("Real FFT optimization must be even.");
return NULL;
}
nfft >>= 1;
@ -63,8 +67,8 @@ void kiss_fftr(kiss_fftr_cfg st,const kiss_fft_scalar *timedata,kiss_fft_cpx *fr
kiss_fft_cpx fpnk,fpk,f1k,f2k,tw,tdc;
if ( st->substate->inverse) {
fprintf(stderr,"kiss fft usage error: improper alloc\n");
exit(1);
KISS_FFT_ERROR("kiss fft usage error: improper alloc");
return;/* The caller did not call the correct function */
}
ncfft = st->substate->nfft;
@ -75,12 +79,12 @@ void kiss_fftr(kiss_fftr_cfg st,const kiss_fft_scalar *timedata,kiss_fft_cpx *fr
* contains the sum of the even-numbered elements of the input time sequence
* The imag part is the sum of the odd-numbered elements
*
* The sum of tdc.r and tdc.i is the sum of the input time sequence.
* The sum of tdc.r and tdc.i is the sum of the input time sequence.
* yielding DC of input time sequence
* The difference of tdc.r - tdc.i is the sum of the input (dot product) [1,-1,1,-1...
* The difference of tdc.r - tdc.i is the sum of the input (dot product) [1,-1,1,-1...
* yielding Nyquist bin of input time sequence
*/
tdc.r = st->tmpbuf[0].r;
tdc.i = st->tmpbuf[0].i;
C_FIXDIV(tdc,2);
@ -88,9 +92,14 @@ void kiss_fftr(kiss_fftr_cfg st,const kiss_fft_scalar *timedata,kiss_fft_cpx *fr
CHECK_OVERFLOW_OP(tdc.r ,-, tdc.i);
freqdata[0].r = tdc.r + tdc.i;
freqdata[ncfft].r = tdc.r - tdc.i;
#ifdef USE_SIMD
freqdata[ncfft].i = freqdata[0].i = _mm_set1_ps(0);
#else
freqdata[ncfft].i = freqdata[0].i = 0;
#endif
for ( k=1;k <= ncfft/2 ; ++k ) {
fpk = st->tmpbuf[k];
fpk = st->tmpbuf[k];
fpnk.r = st->tmpbuf[ncfft-k].r;
fpnk.i = - st->tmpbuf[ncfft-k].i;
C_FIXDIV(fpk,2);
@ -106,3 +115,41 @@ void kiss_fftr(kiss_fftr_cfg st,const kiss_fft_scalar *timedata,kiss_fft_cpx *fr
freqdata[ncfft-k].i = HALF_OF(tw.i - f1k.i);
}
}
void kiss_fftri(kiss_fftr_cfg st,const kiss_fft_cpx *freqdata,kiss_fft_scalar *timedata)
{
/* input buffer timedata is stored row-wise */
int k, ncfft;
if (st->substate->inverse == 0) {
KISS_FFT_ERROR("kiss fft usage error: improper alloc");
return;/* The caller did not call the correct function */
}
ncfft = st->substate->nfft;
st->tmpbuf[0].r = freqdata[0].r + freqdata[ncfft].r;
st->tmpbuf[0].i = freqdata[0].r - freqdata[ncfft].r;
C_FIXDIV(st->tmpbuf[0],2);
for (k = 1; k <= ncfft / 2; ++k) {
kiss_fft_cpx fk, fnkc, fek, fok, tmp;
fk = freqdata[k];
fnkc.r = freqdata[ncfft - k].r;
fnkc.i = -freqdata[ncfft - k].i;
C_FIXDIV( fk , 2 );
C_FIXDIV( fnkc , 2 );
C_ADD (fek, fk, fnkc);
C_SUB (tmp, fk, fnkc);
C_MUL (fok, tmp, st->super_twiddles[k-1]);
C_ADD (st->tmpbuf[k], fek, fok);
C_SUB (st->tmpbuf[ncfft - k], fek, fok);
#ifdef USE_SIMD
st->tmpbuf[ncfft - k].i *= _mm_set1_ps(-1.0);
#else
st->tmpbuf[ncfft - k].i *= -1;
#endif
}
kiss_fft (st->substate, st->tmpbuf, (kiss_fft_cpx *) timedata);
}

Wyświetl plik

@ -14,32 +14,38 @@
extern "C" {
#endif
/*
/*
Real optimized version can save about 45% cpu time vs. complex fft of a real seq.
*/
typedef struct kiss_fftr_state *kiss_fftr_cfg;
kiss_fftr_cfg kiss_fftr_alloc(int nfft,int inverse_fft,void * mem, size_t * lenmem);
kiss_fftr_cfg KISS_FFT_API kiss_fftr_alloc(int nfft,int inverse_fft,void * mem, size_t * lenmem);
/*
nfft must be even
If you don't care to allocate space, use mem = lenmem = NULL
If you don't care to allocate space, use mem = lenmem = NULL
*/
void kiss_fftr(kiss_fftr_cfg cfg,const kiss_fft_scalar *timedata,kiss_fft_cpx *freqdata);
void KISS_FFT_API kiss_fftr(kiss_fftr_cfg cfg,const kiss_fft_scalar *timedata,kiss_fft_cpx *freqdata);
/*
input timedata has nfft scalar points
output freqdata has nfft/2+1 complex points
*/
void KISS_FFT_API kiss_fftri(kiss_fftr_cfg cfg,const kiss_fft_cpx *freqdata,kiss_fft_scalar *timedata);
/*
input freqdata has nfft/2+1 complex points
output timedata has nfft scalar points
*/
#define kiss_fftr_free KISS_FFT_FREE
#ifdef __cplusplus

31
formatter.conf 100644
Wyświetl plik

@ -0,0 +1,31 @@
# This configuration file contains a selection of the available options provided by the formatting tool "Artistic Style"
# http://astyle.sourceforge.net/astyle.html
#
# If you wish to change them, don't edit this file.
# Instead, copy it in the same folder of file "preferences.txt" and modify the copy. This way, you won't lose your custom formatter settings when upgrading the IDE
# If you don't know where file preferences.txt is stored, open the IDE, File -> Preferences and you'll find a link
mode=c
# 2 spaces indentation
indent=spaces=2
# also indent macros
indent-preprocessor
# indent classes, switches (and cases), comments starting at column 1
indent-classes
indent-switches
indent-cases
indent-col1-comments
# put a space around operators
pad-oper
# put a space after if/for/while
pad-header
# if you like one-liners, keep them
keep-one-line-statements
remove-comment-prefix

Wyświetl plik

@ -1,3 +1,3 @@
#add_library(ft8 constants.c constants.h crc.c crc.h decode.c decode.h encode.c encode.h ldpc.c ldpc.h pack.c pack.h text.c text.h unpack.c unpack.h)
add_library(ft8 constants.c constants.h crc.c crc.h decode.c decode.h encode.c encode.h ldpc.c ldpc.h pack.c pack.h text.c text.h unpack.c unpack.h decode_ft8.c decode_ft8.h gen_ft8.c gen_ft8.h)
target_link_libraries(ft8 pico_multicore)
#add_library(ft8 constants.c constants.h crc.c crc.h decode.c decode.h encode.c encode.h ldpc.c ldpc.h pack.c pack.h text.c text.h unpack.c unpack.h)
add_library(ft8 constants.c constants.h crc.c crc.h decode.c decode.h encode.c encode.h ldpc.c ldpc.h pack.c pack.h text.c text.h unpack.c unpack.h decode_ft8.cpp decode_ft8.h gen_ft8.cpp gen_ft8.h)
target_link_libraries(ft8 pico_multicore)

1
ft8/README.md 100644
Wyświetl plik

@ -0,0 +1 @@
Upstream is https://github.com/kgoba/ft8_lib/ - commit cd0dc2e (Feb-7th-2022).

Wyświetl plik

@ -0,0 +1,3 @@
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

12
ft8/common/debug.h 100644
Wyświetl plik

@ -0,0 +1,12 @@
#pragma once
#include <stdio.h>
#define LOG_DEBUG 0
#define LOG_INFO 1
#define LOG_WARN 2
#define LOG_ERROR 3
#define LOG_FATAL 4
#define LOG(level, ...) if (level >= LOG_LEVEL) fprintf(stderr, __VA_ARGS__)

128
ft8/common/wave.c 100644
Wyświetl plik

@ -0,0 +1,128 @@
#include "wave.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>
// Save signal in floating point format (-1 .. +1) as a WAVE file using 16-bit signed integers.
void save_wav(const float* signal, int num_samples, int sample_rate, const char* path)
{
char subChunk1ID[4] = { 'f', 'm', 't', ' ' };
uint32_t subChunk1Size = 16; // 16 for PCM
uint16_t audioFormat = 1; // PCM = 1
uint16_t numChannels = 1;
uint16_t bitsPerSample = 16;
uint32_t sampleRate = sample_rate;
uint16_t blockAlign = numChannels * bitsPerSample / 8;
uint32_t byteRate = sampleRate * blockAlign;
char subChunk2ID[4] = { 'd', 'a', 't', 'a' };
uint32_t subChunk2Size = num_samples * blockAlign;
char chunkID[4] = { 'R', 'I', 'F', 'F' };
uint32_t chunkSize = 4 + (8 + subChunk1Size) + (8 + subChunk2Size);
char format[4] = { 'W', 'A', 'V', 'E' };
int16_t* raw_data = (int16_t*)malloc(num_samples * blockAlign);
for (int i = 0; i < num_samples; i++)
{
float x = signal[i];
if (x > 1.0)
x = 1.0;
else if (x < -1.0)
x = -1.0;
raw_data[i] = (int)(0.5 + (x * 32767.0));
}
FILE* f = fopen(path, "wb");
// NOTE: works only on little-endian architecture
fwrite(chunkID, sizeof(chunkID), 1, f);
fwrite(&chunkSize, sizeof(chunkSize), 1, f);
fwrite(format, sizeof(format), 1, f);
fwrite(subChunk1ID, sizeof(subChunk1ID), 1, f);
fwrite(&subChunk1Size, sizeof(subChunk1Size), 1, f);
fwrite(&audioFormat, sizeof(audioFormat), 1, f);
fwrite(&numChannels, sizeof(numChannels), 1, f);
fwrite(&sampleRate, sizeof(sampleRate), 1, f);
fwrite(&byteRate, sizeof(byteRate), 1, f);
fwrite(&blockAlign, sizeof(blockAlign), 1, f);
fwrite(&bitsPerSample, sizeof(bitsPerSample), 1, f);
fwrite(subChunk2ID, sizeof(subChunk2ID), 1, f);
fwrite(&subChunk2Size, sizeof(subChunk2Size), 1, f);
fwrite(raw_data, blockAlign, num_samples, f);
fclose(f);
free(raw_data);
}
// Load signal in floating point format (-1 .. +1) as a WAVE file using 16-bit signed integers.
int load_wav(float* signal, int* num_samples, int* sample_rate, const char* path)
{
char subChunk1ID[4]; // = {'f', 'm', 't', ' '};
uint32_t subChunk1Size; // = 16; // 16 for PCM
uint16_t audioFormat; // = 1; // PCM = 1
uint16_t numChannels; // = 1;
uint16_t bitsPerSample; // = 16;
uint32_t sampleRate;
uint16_t blockAlign; // = numChannels * bitsPerSample / 8;
uint32_t byteRate; // = sampleRate * blockAlign;
char subChunk2ID[4]; // = {'d', 'a', 't', 'a'};
uint32_t subChunk2Size; // = num_samples * blockAlign;
char chunkID[4]; // = {'R', 'I', 'F', 'F'};
uint32_t chunkSize; // = 4 + (8 + subChunk1Size) + (8 + subChunk2Size);
char format[4]; // = {'W', 'A', 'V', 'E'};
FILE* f = fopen(path, "rb");
// NOTE: works only on little-endian architecture
fread((void*)chunkID, sizeof(chunkID), 1, f);
fread((void*)&chunkSize, sizeof(chunkSize), 1, f);
fread((void*)format, sizeof(format), 1, f);
fread((void*)subChunk1ID, sizeof(subChunk1ID), 1, f);
fread((void*)&subChunk1Size, sizeof(subChunk1Size), 1, f);
if (subChunk1Size != 16)
return -1;
fread((void*)&audioFormat, sizeof(audioFormat), 1, f);
fread((void*)&numChannels, sizeof(numChannels), 1, f);
fread((void*)&sampleRate, sizeof(sampleRate), 1, f);
fread((void*)&byteRate, sizeof(byteRate), 1, f);
fread((void*)&blockAlign, sizeof(blockAlign), 1, f);
fread((void*)&bitsPerSample, sizeof(bitsPerSample), 1, f);
if (audioFormat != 1 || numChannels != 1 || bitsPerSample != 16)
return -1;
fread((void*)subChunk2ID, sizeof(subChunk2ID), 1, f);
fread((void*)&subChunk2Size, sizeof(subChunk2Size), 1, f);
if (subChunk2Size / blockAlign > *num_samples)
return -2;
*num_samples = subChunk2Size / blockAlign;
*sample_rate = sampleRate;
int16_t* raw_data = (int16_t*)malloc(*num_samples * blockAlign);
fread((void*)raw_data, blockAlign, *num_samples, f);
for (int i = 0; i < *num_samples; i++)
{
signal[i] = raw_data[i] / 32768.0f;
}
free(raw_data);
fclose(f);
return 0;
}

10
ft8/common/wave.h 100644
Wyświetl plik

@ -0,0 +1,10 @@
#ifndef _INCLUDE_WAVE_H_
#define _INCLUDE_WAVE_H_
// Save signal in floating point format (-1 .. +1) as a WAVE file using 16-bit signed integers.
void save_wav(const float* signal, int num_samples, int sample_rate, const char* path);
// Load signal in floating point format (-1 .. +1) as a WAVE file using 16-bit signed integers.
int load_wav(float* signal, int* num_samples, int* sample_rate, const char* path);
#endif // _INCLUDE_WAVE_H_

Wyświetl plik

@ -1,368 +1,392 @@
#include "constants.h"
// Costas sync tone pattern
const uint8_t kFT8_Costas_pattern[7] = {3, 1, 4, 0, 6, 5, 2};
const uint8_t kFT8_Costas_pattern[7] = { 3, 1, 4, 0, 6, 5, 2 };
const uint8_t kFT4_Costas_pattern[4][4] = {
{ 0, 1, 3, 2 },
{ 1, 0, 2, 3 },
{ 2, 3, 1, 0 },
{ 3, 2, 0, 1 }
};
// Gray code map
const uint8_t kFT8_Gray_map[8] = {0, 1, 3, 2, 5, 6, 4, 7};
// Gray code map (FTx bits -> channel symbols)
const uint8_t kFT8_Gray_map[8] = { 0, 1, 3, 2, 5, 6, 4, 7 };
const uint8_t kFT4_Gray_map[4] = { 0, 1, 3, 2 };
const uint8_t kFT4_XOR_sequence[10] = {
0x4Au, // 01001010
0x5Eu, // 01011110
0x89u, // 10001001
0xB4u, // 10110100
0xB0u, // 10110000
0x8Au, // 10001010
0x79u, // 01111001
0x55u, // 01010101
0xBEu, // 10111110
0x28u, // 00101 [000]
};
// Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first)
const uint8_t kFT8_LDPC_generator[FT8_LDPC_M][FT8_LDPC_K_BYTES] = {
{0x83, 0x29, 0xce, 0x11, 0xbf, 0x31, 0xea, 0xf5, 0x09, 0xf2, 0x7f, 0xc0},
{0x76, 0x1c, 0x26, 0x4e, 0x25, 0xc2, 0x59, 0x33, 0x54, 0x93, 0x13, 0x20},
{0xdc, 0x26, 0x59, 0x02, 0xfb, 0x27, 0x7c, 0x64, 0x10, 0xa1, 0xbd, 0xc0},
{0x1b, 0x3f, 0x41, 0x78, 0x58, 0xcd, 0x2d, 0xd3, 0x3e, 0xc7, 0xf6, 0x20},
{0x09, 0xfd, 0xa4, 0xfe, 0xe0, 0x41, 0x95, 0xfd, 0x03, 0x47, 0x83, 0xa0},
{0x07, 0x7c, 0xcc, 0xc1, 0x1b, 0x88, 0x73, 0xed, 0x5c, 0x3d, 0x48, 0xa0},
{0x29, 0xb6, 0x2a, 0xfe, 0x3c, 0xa0, 0x36, 0xf4, 0xfe, 0x1a, 0x9d, 0xa0},
{0x60, 0x54, 0xfa, 0xf5, 0xf3, 0x5d, 0x96, 0xd3, 0xb0, 0xc8, 0xc3, 0xe0},
{0xe2, 0x07, 0x98, 0xe4, 0x31, 0x0e, 0xed, 0x27, 0x88, 0x4a, 0xe9, 0x00},
{0x77, 0x5c, 0x9c, 0x08, 0xe8, 0x0e, 0x26, 0xdd, 0xae, 0x56, 0x31, 0x80},
{0xb0, 0xb8, 0x11, 0x02, 0x8c, 0x2b, 0xf9, 0x97, 0x21, 0x34, 0x87, 0xc0},
{0x18, 0xa0, 0xc9, 0x23, 0x1f, 0xc6, 0x0a, 0xdf, 0x5c, 0x5e, 0xa3, 0x20},
{0x76, 0x47, 0x1e, 0x83, 0x02, 0xa0, 0x72, 0x1e, 0x01, 0xb1, 0x2b, 0x80},
{0xff, 0xbc, 0xcb, 0x80, 0xca, 0x83, 0x41, 0xfa, 0xfb, 0x47, 0xb2, 0xe0},
{0x66, 0xa7, 0x2a, 0x15, 0x8f, 0x93, 0x25, 0xa2, 0xbf, 0x67, 0x17, 0x00},
{0xc4, 0x24, 0x36, 0x89, 0xfe, 0x85, 0xb1, 0xc5, 0x13, 0x63, 0xa1, 0x80},
{0x0d, 0xff, 0x73, 0x94, 0x14, 0xd1, 0xa1, 0xb3, 0x4b, 0x1c, 0x27, 0x00},
{0x15, 0xb4, 0x88, 0x30, 0x63, 0x6c, 0x8b, 0x99, 0x89, 0x49, 0x72, 0xe0},
{0x29, 0xa8, 0x9c, 0x0d, 0x3d, 0xe8, 0x1d, 0x66, 0x54, 0x89, 0xb0, 0xe0},
{0x4f, 0x12, 0x6f, 0x37, 0xfa, 0x51, 0xcb, 0xe6, 0x1b, 0xd6, 0xb9, 0x40},
{0x99, 0xc4, 0x72, 0x39, 0xd0, 0xd9, 0x7d, 0x3c, 0x84, 0xe0, 0x94, 0x00},
{0x19, 0x19, 0xb7, 0x51, 0x19, 0x76, 0x56, 0x21, 0xbb, 0x4f, 0x1e, 0x80},
{0x09, 0xdb, 0x12, 0xd7, 0x31, 0xfa, 0xee, 0x0b, 0x86, 0xdf, 0x6b, 0x80},
{0x48, 0x8f, 0xc3, 0x3d, 0xf4, 0x3f, 0xbd, 0xee, 0xa4, 0xea, 0xfb, 0x40},
{0x82, 0x74, 0x23, 0xee, 0x40, 0xb6, 0x75, 0xf7, 0x56, 0xeb, 0x5f, 0xe0},
{0xab, 0xe1, 0x97, 0xc4, 0x84, 0xcb, 0x74, 0x75, 0x71, 0x44, 0xa9, 0xa0},
{0x2b, 0x50, 0x0e, 0x4b, 0xc0, 0xec, 0x5a, 0x6d, 0x2b, 0xdb, 0xdd, 0x00},
{0xc4, 0x74, 0xaa, 0x53, 0xd7, 0x02, 0x18, 0x76, 0x16, 0x69, 0x36, 0x00},
{0x8e, 0xba, 0x1a, 0x13, 0xdb, 0x33, 0x90, 0xbd, 0x67, 0x18, 0xce, 0xc0},
{0x75, 0x38, 0x44, 0x67, 0x3a, 0x27, 0x78, 0x2c, 0xc4, 0x20, 0x12, 0xe0},
{0x06, 0xff, 0x83, 0xa1, 0x45, 0xc3, 0x70, 0x35, 0xa5, 0xc1, 0x26, 0x80},
{0x3b, 0x37, 0x41, 0x78, 0x58, 0xcc, 0x2d, 0xd3, 0x3e, 0xc3, 0xf6, 0x20},
{0x9a, 0x4a, 0x5a, 0x28, 0xee, 0x17, 0xca, 0x9c, 0x32, 0x48, 0x42, 0xc0},
{0xbc, 0x29, 0xf4, 0x65, 0x30, 0x9c, 0x97, 0x7e, 0x89, 0x61, 0x0a, 0x40},
{0x26, 0x63, 0xae, 0x6d, 0xdf, 0x8b, 0x5c, 0xe2, 0xbb, 0x29, 0x48, 0x80},
{0x46, 0xf2, 0x31, 0xef, 0xe4, 0x57, 0x03, 0x4c, 0x18, 0x14, 0x41, 0x80},
{0x3f, 0xb2, 0xce, 0x85, 0xab, 0xe9, 0xb0, 0xc7, 0x2e, 0x06, 0xfb, 0xe0},
{0xde, 0x87, 0x48, 0x1f, 0x28, 0x2c, 0x15, 0x39, 0x71, 0xa0, 0xa2, 0xe0},
{0xfc, 0xd7, 0xcc, 0xf2, 0x3c, 0x69, 0xfa, 0x99, 0xbb, 0xa1, 0x41, 0x20},
{0xf0, 0x26, 0x14, 0x47, 0xe9, 0x49, 0x0c, 0xa8, 0xe4, 0x74, 0xce, 0xc0},
{0x44, 0x10, 0x11, 0x58, 0x18, 0x19, 0x6f, 0x95, 0xcd, 0xd7, 0x01, 0x20},
{0x08, 0x8f, 0xc3, 0x1d, 0xf4, 0xbf, 0xbd, 0xe2, 0xa4, 0xea, 0xfb, 0x40},
{0xb8, 0xfe, 0xf1, 0xb6, 0x30, 0x77, 0x29, 0xfb, 0x0a, 0x07, 0x8c, 0x00},
{0x5a, 0xfe, 0xa7, 0xac, 0xcc, 0xb7, 0x7b, 0xbc, 0x9d, 0x99, 0xa9, 0x00},
{0x49, 0xa7, 0x01, 0x6a, 0xc6, 0x53, 0xf6, 0x5e, 0xcd, 0xc9, 0x07, 0x60},
{0x19, 0x44, 0xd0, 0x85, 0xbe, 0x4e, 0x7d, 0xa8, 0xd6, 0xcc, 0x7d, 0x00},
{0x25, 0x1f, 0x62, 0xad, 0xc4, 0x03, 0x2f, 0x0e, 0xe7, 0x14, 0x00, 0x20},
{0x56, 0x47, 0x1f, 0x87, 0x02, 0xa0, 0x72, 0x1e, 0x00, 0xb1, 0x2b, 0x80},
{0x2b, 0x8e, 0x49, 0x23, 0xf2, 0xdd, 0x51, 0xe2, 0xd5, 0x37, 0xfa, 0x00},
{0x6b, 0x55, 0x0a, 0x40, 0xa6, 0x6f, 0x47, 0x55, 0xde, 0x95, 0xc2, 0x60},
{0xa1, 0x8a, 0xd2, 0x8d, 0x4e, 0x27, 0xfe, 0x92, 0xa4, 0xf6, 0xc8, 0x40},
{0x10, 0xc2, 0xe5, 0x86, 0x38, 0x8c, 0xb8, 0x2a, 0x3d, 0x80, 0x75, 0x80},
{0xef, 0x34, 0xa4, 0x18, 0x17, 0xee, 0x02, 0x13, 0x3d, 0xb2, 0xeb, 0x00},
{0x7e, 0x9c, 0x0c, 0x54, 0x32, 0x5a, 0x9c, 0x15, 0x83, 0x6e, 0x00, 0x00},
{0x36, 0x93, 0xe5, 0x72, 0xd1, 0xfd, 0xe4, 0xcd, 0xf0, 0x79, 0xe8, 0x60},
{0xbf, 0xb2, 0xce, 0xc5, 0xab, 0xe1, 0xb0, 0xc7, 0x2e, 0x07, 0xfb, 0xe0},
{0x7e, 0xe1, 0x82, 0x30, 0xc5, 0x83, 0xcc, 0xcc, 0x57, 0xd4, 0xb0, 0x80},
{0xa0, 0x66, 0xcb, 0x2f, 0xed, 0xaf, 0xc9, 0xf5, 0x26, 0x64, 0x12, 0x60},
{0xbb, 0x23, 0x72, 0x5a, 0xbc, 0x47, 0xcc, 0x5f, 0x4c, 0xc4, 0xcd, 0x20},
{0xde, 0xd9, 0xdb, 0xa3, 0xbe, 0xe4, 0x0c, 0x59, 0xb5, 0x60, 0x9b, 0x40},
{0xd9, 0xa7, 0x01, 0x6a, 0xc6, 0x53, 0xe6, 0xde, 0xcd, 0xc9, 0x03, 0x60},
{0x9a, 0xd4, 0x6a, 0xed, 0x5f, 0x70, 0x7f, 0x28, 0x0a, 0xb5, 0xfc, 0x40},
{0xe5, 0x92, 0x1c, 0x77, 0x82, 0x25, 0x87, 0x31, 0x6d, 0x7d, 0x3c, 0x20},
{0x4f, 0x14, 0xda, 0x82, 0x42, 0xa8, 0xb8, 0x6d, 0xca, 0x73, 0x35, 0x20},
{0x8b, 0x8b, 0x50, 0x7a, 0xd4, 0x67, 0xd4, 0x44, 0x1d, 0xf7, 0x70, 0xe0},
{0x22, 0x83, 0x1c, 0x9c, 0xf1, 0x16, 0x94, 0x67, 0xad, 0x04, 0xb6, 0x80},
{0x21, 0x3b, 0x83, 0x8f, 0xe2, 0xae, 0x54, 0xc3, 0x8e, 0xe7, 0x18, 0x00},
{0x5d, 0x92, 0x6b, 0x6d, 0xd7, 0x1f, 0x08, 0x51, 0x81, 0xa4, 0xe1, 0x20},
{0x66, 0xab, 0x79, 0xd4, 0xb2, 0x9e, 0xe6, 0xe6, 0x95, 0x09, 0xe5, 0x60},
{0x95, 0x81, 0x48, 0x68, 0x2d, 0x74, 0x8a, 0x38, 0xdd, 0x68, 0xba, 0xa0},
{0xb8, 0xce, 0x02, 0x0c, 0xf0, 0x69, 0xc3, 0x2a, 0x72, 0x3a, 0xb1, 0x40},
{0xf4, 0x33, 0x1d, 0x6d, 0x46, 0x16, 0x07, 0xe9, 0x57, 0x52, 0x74, 0x60},
{0x6d, 0xa2, 0x3b, 0xa4, 0x24, 0xb9, 0x59, 0x61, 0x33, 0xcf, 0x9c, 0x80},
{0xa6, 0x36, 0xbc, 0xbc, 0x7b, 0x30, 0xc5, 0xfb, 0xea, 0xe6, 0x7f, 0xe0},
{0x5c, 0xb0, 0xd8, 0x6a, 0x07, 0xdf, 0x65, 0x4a, 0x90, 0x89, 0xa2, 0x00},
{0xf1, 0x1f, 0x10, 0x68, 0x48, 0x78, 0x0f, 0xc9, 0xec, 0xdd, 0x80, 0xa0},
{0x1f, 0xbb, 0x53, 0x64, 0xfb, 0x8d, 0x2c, 0x9d, 0x73, 0x0d, 0x5b, 0xa0},
{0xfc, 0xb8, 0x6b, 0xc7, 0x0a, 0x50, 0xc9, 0xd0, 0x2a, 0x5d, 0x03, 0x40},
{0xa5, 0x34, 0x43, 0x30, 0x29, 0xea, 0xc1, 0x5f, 0x32, 0x2e, 0x34, 0xc0},
{0xc9, 0x89, 0xd9, 0xc7, 0xc3, 0xd3, 0xb8, 0xc5, 0x5d, 0x75, 0x13, 0x00},
{0x7b, 0xb3, 0x8b, 0x2f, 0x01, 0x86, 0xd4, 0x66, 0x43, 0xae, 0x96, 0x20},
{0x26, 0x44, 0xeb, 0xad, 0xeb, 0x44, 0xb9, 0x46, 0x7d, 0x1f, 0x42, 0xc0},
{0x60, 0x8c, 0xc8, 0x57, 0x59, 0x4b, 0xfb, 0xb5, 0x5d, 0x69, 0x60, 0x00}};
const uint8_t kFTX_LDPC_generator[FTX_LDPC_M][FTX_LDPC_K_BYTES] = {
{ 0x83, 0x29, 0xce, 0x11, 0xbf, 0x31, 0xea, 0xf5, 0x09, 0xf2, 0x7f, 0xc0 },
{ 0x76, 0x1c, 0x26, 0x4e, 0x25, 0xc2, 0x59, 0x33, 0x54, 0x93, 0x13, 0x20 },
{ 0xdc, 0x26, 0x59, 0x02, 0xfb, 0x27, 0x7c, 0x64, 0x10, 0xa1, 0xbd, 0xc0 },
{ 0x1b, 0x3f, 0x41, 0x78, 0x58, 0xcd, 0x2d, 0xd3, 0x3e, 0xc7, 0xf6, 0x20 },
{ 0x09, 0xfd, 0xa4, 0xfe, 0xe0, 0x41, 0x95, 0xfd, 0x03, 0x47, 0x83, 0xa0 },
{ 0x07, 0x7c, 0xcc, 0xc1, 0x1b, 0x88, 0x73, 0xed, 0x5c, 0x3d, 0x48, 0xa0 },
{ 0x29, 0xb6, 0x2a, 0xfe, 0x3c, 0xa0, 0x36, 0xf4, 0xfe, 0x1a, 0x9d, 0xa0 },
{ 0x60, 0x54, 0xfa, 0xf5, 0xf3, 0x5d, 0x96, 0xd3, 0xb0, 0xc8, 0xc3, 0xe0 },
{ 0xe2, 0x07, 0x98, 0xe4, 0x31, 0x0e, 0xed, 0x27, 0x88, 0x4a, 0xe9, 0x00 },
{ 0x77, 0x5c, 0x9c, 0x08, 0xe8, 0x0e, 0x26, 0xdd, 0xae, 0x56, 0x31, 0x80 },
{ 0xb0, 0xb8, 0x11, 0x02, 0x8c, 0x2b, 0xf9, 0x97, 0x21, 0x34, 0x87, 0xc0 },
{ 0x18, 0xa0, 0xc9, 0x23, 0x1f, 0xc6, 0x0a, 0xdf, 0x5c, 0x5e, 0xa3, 0x20 },
{ 0x76, 0x47, 0x1e, 0x83, 0x02, 0xa0, 0x72, 0x1e, 0x01, 0xb1, 0x2b, 0x80 },
{ 0xff, 0xbc, 0xcb, 0x80, 0xca, 0x83, 0x41, 0xfa, 0xfb, 0x47, 0xb2, 0xe0 },
{ 0x66, 0xa7, 0x2a, 0x15, 0x8f, 0x93, 0x25, 0xa2, 0xbf, 0x67, 0x17, 0x00 },
{ 0xc4, 0x24, 0x36, 0x89, 0xfe, 0x85, 0xb1, 0xc5, 0x13, 0x63, 0xa1, 0x80 },
{ 0x0d, 0xff, 0x73, 0x94, 0x14, 0xd1, 0xa1, 0xb3, 0x4b, 0x1c, 0x27, 0x00 },
{ 0x15, 0xb4, 0x88, 0x30, 0x63, 0x6c, 0x8b, 0x99, 0x89, 0x49, 0x72, 0xe0 },
{ 0x29, 0xa8, 0x9c, 0x0d, 0x3d, 0xe8, 0x1d, 0x66, 0x54, 0x89, 0xb0, 0xe0 },
{ 0x4f, 0x12, 0x6f, 0x37, 0xfa, 0x51, 0xcb, 0xe6, 0x1b, 0xd6, 0xb9, 0x40 },
{ 0x99, 0xc4, 0x72, 0x39, 0xd0, 0xd9, 0x7d, 0x3c, 0x84, 0xe0, 0x94, 0x00 },
{ 0x19, 0x19, 0xb7, 0x51, 0x19, 0x76, 0x56, 0x21, 0xbb, 0x4f, 0x1e, 0x80 },
{ 0x09, 0xdb, 0x12, 0xd7, 0x31, 0xfa, 0xee, 0x0b, 0x86, 0xdf, 0x6b, 0x80 },
{ 0x48, 0x8f, 0xc3, 0x3d, 0xf4, 0x3f, 0xbd, 0xee, 0xa4, 0xea, 0xfb, 0x40 },
{ 0x82, 0x74, 0x23, 0xee, 0x40, 0xb6, 0x75, 0xf7, 0x56, 0xeb, 0x5f, 0xe0 },
{ 0xab, 0xe1, 0x97, 0xc4, 0x84, 0xcb, 0x74, 0x75, 0x71, 0x44, 0xa9, 0xa0 },
{ 0x2b, 0x50, 0x0e, 0x4b, 0xc0, 0xec, 0x5a, 0x6d, 0x2b, 0xdb, 0xdd, 0x00 },
{ 0xc4, 0x74, 0xaa, 0x53, 0xd7, 0x02, 0x18, 0x76, 0x16, 0x69, 0x36, 0x00 },
{ 0x8e, 0xba, 0x1a, 0x13, 0xdb, 0x33, 0x90, 0xbd, 0x67, 0x18, 0xce, 0xc0 },
{ 0x75, 0x38, 0x44, 0x67, 0x3a, 0x27, 0x78, 0x2c, 0xc4, 0x20, 0x12, 0xe0 },
{ 0x06, 0xff, 0x83, 0xa1, 0x45, 0xc3, 0x70, 0x35, 0xa5, 0xc1, 0x26, 0x80 },
{ 0x3b, 0x37, 0x41, 0x78, 0x58, 0xcc, 0x2d, 0xd3, 0x3e, 0xc3, 0xf6, 0x20 },
{ 0x9a, 0x4a, 0x5a, 0x28, 0xee, 0x17, 0xca, 0x9c, 0x32, 0x48, 0x42, 0xc0 },
{ 0xbc, 0x29, 0xf4, 0x65, 0x30, 0x9c, 0x97, 0x7e, 0x89, 0x61, 0x0a, 0x40 },
{ 0x26, 0x63, 0xae, 0x6d, 0xdf, 0x8b, 0x5c, 0xe2, 0xbb, 0x29, 0x48, 0x80 },
{ 0x46, 0xf2, 0x31, 0xef, 0xe4, 0x57, 0x03, 0x4c, 0x18, 0x14, 0x41, 0x80 },
{ 0x3f, 0xb2, 0xce, 0x85, 0xab, 0xe9, 0xb0, 0xc7, 0x2e, 0x06, 0xfb, 0xe0 },
{ 0xde, 0x87, 0x48, 0x1f, 0x28, 0x2c, 0x15, 0x39, 0x71, 0xa0, 0xa2, 0xe0 },
{ 0xfc, 0xd7, 0xcc, 0xf2, 0x3c, 0x69, 0xfa, 0x99, 0xbb, 0xa1, 0x41, 0x20 },
{ 0xf0, 0x26, 0x14, 0x47, 0xe9, 0x49, 0x0c, 0xa8, 0xe4, 0x74, 0xce, 0xc0 },
{ 0x44, 0x10, 0x11, 0x58, 0x18, 0x19, 0x6f, 0x95, 0xcd, 0xd7, 0x01, 0x20 },
{ 0x08, 0x8f, 0xc3, 0x1d, 0xf4, 0xbf, 0xbd, 0xe2, 0xa4, 0xea, 0xfb, 0x40 },
{ 0xb8, 0xfe, 0xf1, 0xb6, 0x30, 0x77, 0x29, 0xfb, 0x0a, 0x07, 0x8c, 0x00 },
{ 0x5a, 0xfe, 0xa7, 0xac, 0xcc, 0xb7, 0x7b, 0xbc, 0x9d, 0x99, 0xa9, 0x00 },
{ 0x49, 0xa7, 0x01, 0x6a, 0xc6, 0x53, 0xf6, 0x5e, 0xcd, 0xc9, 0x07, 0x60 },
{ 0x19, 0x44, 0xd0, 0x85, 0xbe, 0x4e, 0x7d, 0xa8, 0xd6, 0xcc, 0x7d, 0x00 },
{ 0x25, 0x1f, 0x62, 0xad, 0xc4, 0x03, 0x2f, 0x0e, 0xe7, 0x14, 0x00, 0x20 },
{ 0x56, 0x47, 0x1f, 0x87, 0x02, 0xa0, 0x72, 0x1e, 0x00, 0xb1, 0x2b, 0x80 },
{ 0x2b, 0x8e, 0x49, 0x23, 0xf2, 0xdd, 0x51, 0xe2, 0xd5, 0x37, 0xfa, 0x00 },
{ 0x6b, 0x55, 0x0a, 0x40, 0xa6, 0x6f, 0x47, 0x55, 0xde, 0x95, 0xc2, 0x60 },
{ 0xa1, 0x8a, 0xd2, 0x8d, 0x4e, 0x27, 0xfe, 0x92, 0xa4, 0xf6, 0xc8, 0x40 },
{ 0x10, 0xc2, 0xe5, 0x86, 0x38, 0x8c, 0xb8, 0x2a, 0x3d, 0x80, 0x75, 0x80 },
{ 0xef, 0x34, 0xa4, 0x18, 0x17, 0xee, 0x02, 0x13, 0x3d, 0xb2, 0xeb, 0x00 },
{ 0x7e, 0x9c, 0x0c, 0x54, 0x32, 0x5a, 0x9c, 0x15, 0x83, 0x6e, 0x00, 0x00 },
{ 0x36, 0x93, 0xe5, 0x72, 0xd1, 0xfd, 0xe4, 0xcd, 0xf0, 0x79, 0xe8, 0x60 },
{ 0xbf, 0xb2, 0xce, 0xc5, 0xab, 0xe1, 0xb0, 0xc7, 0x2e, 0x07, 0xfb, 0xe0 },
{ 0x7e, 0xe1, 0x82, 0x30, 0xc5, 0x83, 0xcc, 0xcc, 0x57, 0xd4, 0xb0, 0x80 },
{ 0xa0, 0x66, 0xcb, 0x2f, 0xed, 0xaf, 0xc9, 0xf5, 0x26, 0x64, 0x12, 0x60 },
{ 0xbb, 0x23, 0x72, 0x5a, 0xbc, 0x47, 0xcc, 0x5f, 0x4c, 0xc4, 0xcd, 0x20 },
{ 0xde, 0xd9, 0xdb, 0xa3, 0xbe, 0xe4, 0x0c, 0x59, 0xb5, 0x60, 0x9b, 0x40 },
{ 0xd9, 0xa7, 0x01, 0x6a, 0xc6, 0x53, 0xe6, 0xde, 0xcd, 0xc9, 0x03, 0x60 },
{ 0x9a, 0xd4, 0x6a, 0xed, 0x5f, 0x70, 0x7f, 0x28, 0x0a, 0xb5, 0xfc, 0x40 },
{ 0xe5, 0x92, 0x1c, 0x77, 0x82, 0x25, 0x87, 0x31, 0x6d, 0x7d, 0x3c, 0x20 },
{ 0x4f, 0x14, 0xda, 0x82, 0x42, 0xa8, 0xb8, 0x6d, 0xca, 0x73, 0x35, 0x20 },
{ 0x8b, 0x8b, 0x50, 0x7a, 0xd4, 0x67, 0xd4, 0x44, 0x1d, 0xf7, 0x70, 0xe0 },
{ 0x22, 0x83, 0x1c, 0x9c, 0xf1, 0x16, 0x94, 0x67, 0xad, 0x04, 0xb6, 0x80 },
{ 0x21, 0x3b, 0x83, 0x8f, 0xe2, 0xae, 0x54, 0xc3, 0x8e, 0xe7, 0x18, 0x00 },
{ 0x5d, 0x92, 0x6b, 0x6d, 0xd7, 0x1f, 0x08, 0x51, 0x81, 0xa4, 0xe1, 0x20 },
{ 0x66, 0xab, 0x79, 0xd4, 0xb2, 0x9e, 0xe6, 0xe6, 0x95, 0x09, 0xe5, 0x60 },
{ 0x95, 0x81, 0x48, 0x68, 0x2d, 0x74, 0x8a, 0x38, 0xdd, 0x68, 0xba, 0xa0 },
{ 0xb8, 0xce, 0x02, 0x0c, 0xf0, 0x69, 0xc3, 0x2a, 0x72, 0x3a, 0xb1, 0x40 },
{ 0xf4, 0x33, 0x1d, 0x6d, 0x46, 0x16, 0x07, 0xe9, 0x57, 0x52, 0x74, 0x60 },
{ 0x6d, 0xa2, 0x3b, 0xa4, 0x24, 0xb9, 0x59, 0x61, 0x33, 0xcf, 0x9c, 0x80 },
{ 0xa6, 0x36, 0xbc, 0xbc, 0x7b, 0x30, 0xc5, 0xfb, 0xea, 0xe6, 0x7f, 0xe0 },
{ 0x5c, 0xb0, 0xd8, 0x6a, 0x07, 0xdf, 0x65, 0x4a, 0x90, 0x89, 0xa2, 0x00 },
{ 0xf1, 0x1f, 0x10, 0x68, 0x48, 0x78, 0x0f, 0xc9, 0xec, 0xdd, 0x80, 0xa0 },
{ 0x1f, 0xbb, 0x53, 0x64, 0xfb, 0x8d, 0x2c, 0x9d, 0x73, 0x0d, 0x5b, 0xa0 },
{ 0xfc, 0xb8, 0x6b, 0xc7, 0x0a, 0x50, 0xc9, 0xd0, 0x2a, 0x5d, 0x03, 0x40 },
{ 0xa5, 0x34, 0x43, 0x30, 0x29, 0xea, 0xc1, 0x5f, 0x32, 0x2e, 0x34, 0xc0 },
{ 0xc9, 0x89, 0xd9, 0xc7, 0xc3, 0xd3, 0xb8, 0xc5, 0x5d, 0x75, 0x13, 0x00 },
{ 0x7b, 0xb3, 0x8b, 0x2f, 0x01, 0x86, 0xd4, 0x66, 0x43, 0xae, 0x96, 0x20 },
{ 0x26, 0x44, 0xeb, 0xad, 0xeb, 0x44, 0xb9, 0x46, 0x7d, 0x1f, 0x42, 0xc0 },
{ 0x60, 0x8c, 0xc8, 0x57, 0x59, 0x4b, 0xfb, 0xb5, 0x5d, 0x69, 0x60, 0x00 }
};
// Each row describes one LDPC parity check.
// Each number is an index into the codeword (1-origin).
// The codeword bits mentioned in each row must XOR to zero.
const uint8_t kFT8_LDPC_Nm[FT8_LDPC_M][7] = {
{4, 31, 59, 91, 92, 96, 153},
{5, 32, 60, 93, 115, 146, 0},
{6, 24, 61, 94, 122, 151, 0},
{7, 33, 62, 95, 96, 143, 0},
{8, 25, 63, 83, 93, 96, 148},
{6, 32, 64, 97, 126, 138, 0},
{5, 34, 65, 78, 98, 107, 154},
{9, 35, 66, 99, 139, 146, 0},
{10, 36, 67, 100, 107, 126, 0},
{11, 37, 67, 87, 101, 139, 158},
{12, 38, 68, 102, 105, 155, 0},
{13, 39, 69, 103, 149, 162, 0},
{8, 40, 70, 82, 104, 114, 145},
{14, 41, 71, 88, 102, 123, 156},
{15, 42, 59, 106, 123, 159, 0},
{1, 33, 72, 106, 107, 157, 0},
{16, 43, 73, 108, 141, 160, 0},
{17, 37, 74, 81, 109, 131, 154},
{11, 44, 75, 110, 121, 166, 0},
{45, 55, 64, 111, 130, 161, 173},
{8, 46, 71, 112, 119, 166, 0},
{18, 36, 76, 89, 113, 114, 143},
{19, 38, 77, 104, 116, 163, 0},
{20, 47, 70, 92, 138, 165, 0},
{2, 48, 74, 113, 128, 160, 0},
{21, 45, 78, 83, 117, 121, 151},
{22, 47, 58, 118, 127, 164, 0},
{16, 39, 62, 112, 134, 158, 0},
{23, 43, 79, 120, 131, 145, 0},
{19, 35, 59, 73, 110, 125, 161},
{20, 36, 63, 94, 136, 161, 0},
{14, 31, 79, 98, 132, 164, 0},
{3, 44, 80, 124, 127, 169, 0},
{19, 46, 81, 117, 135, 167, 0},
{7, 49, 58, 90, 100, 105, 168},
{12, 50, 61, 118, 119, 144, 0},
{13, 51, 64, 114, 118, 157, 0},
{24, 52, 76, 129, 148, 149, 0},
{25, 53, 69, 90, 101, 130, 156},
{20, 46, 65, 80, 120, 140, 170},
{21, 54, 77, 100, 140, 171, 0},
{35, 82, 133, 142, 171, 174, 0},
{14, 30, 83, 113, 125, 170, 0},
{4, 29, 68, 120, 134, 173, 0},
{1, 4, 52, 57, 86, 136, 152},
{26, 51, 56, 91, 122, 137, 168},
{52, 84, 110, 115, 145, 168, 0},
{7, 50, 81, 99, 132, 173, 0},
{23, 55, 67, 95, 172, 174, 0},
{26, 41, 77, 109, 141, 148, 0},
{2, 27, 41, 61, 62, 115, 133},
{27, 40, 56, 124, 125, 126, 0},
{18, 49, 55, 124, 141, 167, 0},
{6, 33, 85, 108, 116, 156, 0},
{28, 48, 70, 85, 105, 129, 158},
{9, 54, 63, 131, 147, 155, 0},
{22, 53, 68, 109, 121, 174, 0},
{3, 13, 48, 78, 95, 123, 0},
{31, 69, 133, 150, 155, 169, 0},
{12, 43, 66, 89, 97, 135, 159},
{5, 39, 75, 102, 136, 167, 0},
{2, 54, 86, 101, 135, 164, 0},
{15, 56, 87, 108, 119, 171, 0},
{10, 44, 82, 91, 111, 144, 149},
{23, 34, 71, 94, 127, 153, 0},
{11, 49, 88, 92, 142, 157, 0},
{29, 34, 87, 97, 147, 162, 0},
{30, 50, 60, 86, 137, 142, 162},
{10, 53, 66, 84, 112, 128, 165},
{22, 57, 85, 93, 140, 159, 0},
{28, 32, 72, 103, 132, 166, 0},
{28, 29, 84, 88, 117, 143, 150},
{1, 26, 45, 80, 128, 147, 0},
{17, 27, 89, 103, 116, 153, 0},
{51, 57, 98, 163, 165, 172, 0},
{21, 37, 73, 138, 152, 169, 0},
{16, 47, 76, 130, 137, 154, 0},
{3, 24, 30, 72, 104, 139, 0},
{9, 40, 90, 106, 134, 151, 0},
{15, 58, 60, 74, 111, 150, 163},
{18, 42, 79, 144, 146, 152, 0},
{25, 38, 65, 99, 122, 160, 0},
{17, 42, 75, 129, 170, 172, 0}};
const uint8_t kFTX_LDPC_Nm[FTX_LDPC_M][7] = {
{ 4, 31, 59, 91, 92, 96, 153 },
{ 5, 32, 60, 93, 115, 146, 0 },
{ 6, 24, 61, 94, 122, 151, 0 },
{ 7, 33, 62, 95, 96, 143, 0 },
{ 8, 25, 63, 83, 93, 96, 148 },
{ 6, 32, 64, 97, 126, 138, 0 },
{ 5, 34, 65, 78, 98, 107, 154 },
{ 9, 35, 66, 99, 139, 146, 0 },
{ 10, 36, 67, 100, 107, 126, 0 },
{ 11, 37, 67, 87, 101, 139, 158 },
{ 12, 38, 68, 102, 105, 155, 0 },
{ 13, 39, 69, 103, 149, 162, 0 },
{ 8, 40, 70, 82, 104, 114, 145 },
{ 14, 41, 71, 88, 102, 123, 156 },
{ 15, 42, 59, 106, 123, 159, 0 },
{ 1, 33, 72, 106, 107, 157, 0 },
{ 16, 43, 73, 108, 141, 160, 0 },
{ 17, 37, 74, 81, 109, 131, 154 },
{ 11, 44, 75, 110, 121, 166, 0 },
{ 45, 55, 64, 111, 130, 161, 173 },
{ 8, 46, 71, 112, 119, 166, 0 },
{ 18, 36, 76, 89, 113, 114, 143 },
{ 19, 38, 77, 104, 116, 163, 0 },
{ 20, 47, 70, 92, 138, 165, 0 },
{ 2, 48, 74, 113, 128, 160, 0 },
{ 21, 45, 78, 83, 117, 121, 151 },
{ 22, 47, 58, 118, 127, 164, 0 },
{ 16, 39, 62, 112, 134, 158, 0 },
{ 23, 43, 79, 120, 131, 145, 0 },
{ 19, 35, 59, 73, 110, 125, 161 },
{ 20, 36, 63, 94, 136, 161, 0 },
{ 14, 31, 79, 98, 132, 164, 0 },
{ 3, 44, 80, 124, 127, 169, 0 },
{ 19, 46, 81, 117, 135, 167, 0 },
{ 7, 49, 58, 90, 100, 105, 168 },
{ 12, 50, 61, 118, 119, 144, 0 },
{ 13, 51, 64, 114, 118, 157, 0 },
{ 24, 52, 76, 129, 148, 149, 0 },
{ 25, 53, 69, 90, 101, 130, 156 },
{ 20, 46, 65, 80, 120, 140, 170 },
{ 21, 54, 77, 100, 140, 171, 0 },
{ 35, 82, 133, 142, 171, 174, 0 },
{ 14, 30, 83, 113, 125, 170, 0 },
{ 4, 29, 68, 120, 134, 173, 0 },
{ 1, 4, 52, 57, 86, 136, 152 },
{ 26, 51, 56, 91, 122, 137, 168 },
{ 52, 84, 110, 115, 145, 168, 0 },
{ 7, 50, 81, 99, 132, 173, 0 },
{ 23, 55, 67, 95, 172, 174, 0 },
{ 26, 41, 77, 109, 141, 148, 0 },
{ 2, 27, 41, 61, 62, 115, 133 },
{ 27, 40, 56, 124, 125, 126, 0 },
{ 18, 49, 55, 124, 141, 167, 0 },
{ 6, 33, 85, 108, 116, 156, 0 },
{ 28, 48, 70, 85, 105, 129, 158 },
{ 9, 54, 63, 131, 147, 155, 0 },
{ 22, 53, 68, 109, 121, 174, 0 },
{ 3, 13, 48, 78, 95, 123, 0 },
{ 31, 69, 133, 150, 155, 169, 0 },
{ 12, 43, 66, 89, 97, 135, 159 },
{ 5, 39, 75, 102, 136, 167, 0 },
{ 2, 54, 86, 101, 135, 164, 0 },
{ 15, 56, 87, 108, 119, 171, 0 },
{ 10, 44, 82, 91, 111, 144, 149 },
{ 23, 34, 71, 94, 127, 153, 0 },
{ 11, 49, 88, 92, 142, 157, 0 },
{ 29, 34, 87, 97, 147, 162, 0 },
{ 30, 50, 60, 86, 137, 142, 162 },
{ 10, 53, 66, 84, 112, 128, 165 },
{ 22, 57, 85, 93, 140, 159, 0 },
{ 28, 32, 72, 103, 132, 166, 0 },
{ 28, 29, 84, 88, 117, 143, 150 },
{ 1, 26, 45, 80, 128, 147, 0 },
{ 17, 27, 89, 103, 116, 153, 0 },
{ 51, 57, 98, 163, 165, 172, 0 },
{ 21, 37, 73, 138, 152, 169, 0 },
{ 16, 47, 76, 130, 137, 154, 0 },
{ 3, 24, 30, 72, 104, 139, 0 },
{ 9, 40, 90, 106, 134, 151, 0 },
{ 15, 58, 60, 74, 111, 150, 163 },
{ 18, 42, 79, 144, 146, 152, 0 },
{ 25, 38, 65, 99, 122, 160, 0 },
{ 17, 42, 75, 129, 170, 172, 0 }
};
// Each row corresponds to a codeword bit.
// The numbers indicate which three LDPC parity checks (rows in Nm) refer to the codeword bit.
// 1-origin.
const uint8_t kFT8_LDPC_Mn[FT8_LDPC_N][3] = {
{16, 45, 73},
{25, 51, 62},
{33, 58, 78},
{1, 44, 45},
{2, 7, 61},
{3, 6, 54},
{4, 35, 48},
{5, 13, 21},
{8, 56, 79},
{9, 64, 69},
{10, 19, 66},
{11, 36, 60},
{12, 37, 58},
{14, 32, 43},
{15, 63, 80},
{17, 28, 77},
{18, 74, 83},
{22, 53, 81},
{23, 30, 34},
{24, 31, 40},
{26, 41, 76},
{27, 57, 70},
{29, 49, 65},
{3, 38, 78},
{5, 39, 82},
{46, 50, 73},
{51, 52, 74},
{55, 71, 72},
{44, 67, 72},
{43, 68, 78},
{1, 32, 59},
{2, 6, 71},
{4, 16, 54},
{7, 65, 67},
{8, 30, 42},
{9, 22, 31},
{10, 18, 76},
{11, 23, 82},
{12, 28, 61},
{13, 52, 79},
{14, 50, 51},
{15, 81, 83},
{17, 29, 60},
{19, 33, 64},
{20, 26, 73},
{21, 34, 40},
{24, 27, 77},
{25, 55, 58},
{35, 53, 66},
{36, 48, 68},
{37, 46, 75},
{38, 45, 47},
{39, 57, 69},
{41, 56, 62},
{20, 49, 53},
{46, 52, 63},
{45, 70, 75},
{27, 35, 80},
{1, 15, 30},
{2, 68, 80},
{3, 36, 51},
{4, 28, 51},
{5, 31, 56},
{6, 20, 37},
{7, 40, 82},
{8, 60, 69},
{9, 10, 49},
{11, 44, 57},
{12, 39, 59},
{13, 24, 55},
{14, 21, 65},
{16, 71, 78},
{17, 30, 76},
{18, 25, 80},
{19, 61, 83},
{22, 38, 77},
{23, 41, 50},
{7, 26, 58},
{29, 32, 81},
{33, 40, 73},
{18, 34, 48},
{13, 42, 64},
{5, 26, 43},
{47, 69, 72},
{54, 55, 70},
{45, 62, 68},
{10, 63, 67},
{14, 66, 72},
{22, 60, 74},
{35, 39, 79},
{1, 46, 64},
{1, 24, 66},
{2, 5, 70},
{3, 31, 65},
{4, 49, 58},
{1, 4, 5},
{6, 60, 67},
{7, 32, 75},
{8, 48, 82},
{9, 35, 41},
{10, 39, 62},
{11, 14, 61},
{12, 71, 74},
{13, 23, 78},
{11, 35, 55},
{15, 16, 79},
{7, 9, 16},
{17, 54, 63},
{18, 50, 57},
{19, 30, 47},
{20, 64, 80},
{21, 28, 69},
{22, 25, 43},
{13, 22, 37},
{2, 47, 51},
{23, 54, 74},
{26, 34, 72},
{27, 36, 37},
{21, 36, 63},
{29, 40, 44},
{19, 26, 57},
{3, 46, 82},
{14, 15, 58},
{33, 52, 53},
{30, 43, 52},
{6, 9, 52},
{27, 33, 65},
{25, 69, 73},
{38, 55, 83},
{20, 39, 77},
{18, 29, 56},
{32, 48, 71},
{42, 51, 59},
{28, 44, 79},
{34, 60, 62},
{31, 45, 61},
{46, 68, 77},
{6, 24, 76},
{8, 10, 78},
{40, 41, 70},
{17, 50, 53},
{42, 66, 68},
{4, 22, 72},
{36, 64, 81},
{13, 29, 47},
{2, 8, 81},
{56, 67, 73},
{5, 38, 50},
{12, 38, 64},
{59, 72, 80},
{3, 26, 79},
{45, 76, 81},
{1, 65, 74},
{7, 18, 77},
{11, 56, 59},
{14, 39, 54},
{16, 37, 66},
{10, 28, 55},
{15, 60, 70},
{17, 25, 82},
{20, 30, 31},
{12, 67, 68},
{23, 75, 80},
{27, 32, 62},
{24, 69, 75},
{19, 21, 71},
{34, 53, 61},
{35, 46, 47},
{33, 59, 76},
{40, 43, 83},
{41, 42, 63},
{49, 75, 83},
{20, 44, 48},
{42, 49, 57}};
const uint8_t kFTX_LDPC_Mn[FTX_LDPC_N][3] = {
{ 16, 45, 73 },
{ 25, 51, 62 },
{ 33, 58, 78 },
{ 1, 44, 45 },
{ 2, 7, 61 },
{ 3, 6, 54 },
{ 4, 35, 48 },
{ 5, 13, 21 },
{ 8, 56, 79 },
{ 9, 64, 69 },
{ 10, 19, 66 },
{ 11, 36, 60 },
{ 12, 37, 58 },
{ 14, 32, 43 },
{ 15, 63, 80 },
{ 17, 28, 77 },
{ 18, 74, 83 },
{ 22, 53, 81 },
{ 23, 30, 34 },
{ 24, 31, 40 },
{ 26, 41, 76 },
{ 27, 57, 70 },
{ 29, 49, 65 },
{ 3, 38, 78 },
{ 5, 39, 82 },
{ 46, 50, 73 },
{ 51, 52, 74 },
{ 55, 71, 72 },
{ 44, 67, 72 },
{ 43, 68, 78 },
{ 1, 32, 59 },
{ 2, 6, 71 },
{ 4, 16, 54 },
{ 7, 65, 67 },
{ 8, 30, 42 },
{ 9, 22, 31 },
{ 10, 18, 76 },
{ 11, 23, 82 },
{ 12, 28, 61 },
{ 13, 52, 79 },
{ 14, 50, 51 },
{ 15, 81, 83 },
{ 17, 29, 60 },
{ 19, 33, 64 },
{ 20, 26, 73 },
{ 21, 34, 40 },
{ 24, 27, 77 },
{ 25, 55, 58 },
{ 35, 53, 66 },
{ 36, 48, 68 },
{ 37, 46, 75 },
{ 38, 45, 47 },
{ 39, 57, 69 },
{ 41, 56, 62 },
{ 20, 49, 53 },
{ 46, 52, 63 },
{ 45, 70, 75 },
{ 27, 35, 80 },
{ 1, 15, 30 },
{ 2, 68, 80 },
{ 3, 36, 51 },
{ 4, 28, 51 },
{ 5, 31, 56 },
{ 6, 20, 37 },
{ 7, 40, 82 },
{ 8, 60, 69 },
{ 9, 10, 49 },
{ 11, 44, 57 },
{ 12, 39, 59 },
{ 13, 24, 55 },
{ 14, 21, 65 },
{ 16, 71, 78 },
{ 17, 30, 76 },
{ 18, 25, 80 },
{ 19, 61, 83 },
{ 22, 38, 77 },
{ 23, 41, 50 },
{ 7, 26, 58 },
{ 29, 32, 81 },
{ 33, 40, 73 },
{ 18, 34, 48 },
{ 13, 42, 64 },
{ 5, 26, 43 },
{ 47, 69, 72 },
{ 54, 55, 70 },
{ 45, 62, 68 },
{ 10, 63, 67 },
{ 14, 66, 72 },
{ 22, 60, 74 },
{ 35, 39, 79 },
{ 1, 46, 64 },
{ 1, 24, 66 },
{ 2, 5, 70 },
{ 3, 31, 65 },
{ 4, 49, 58 },
{ 1, 4, 5 },
{ 6, 60, 67 },
{ 7, 32, 75 },
{ 8, 48, 82 },
{ 9, 35, 41 },
{ 10, 39, 62 },
{ 11, 14, 61 },
{ 12, 71, 74 },
{ 13, 23, 78 },
{ 11, 35, 55 },
{ 15, 16, 79 },
{ 7, 9, 16 },
{ 17, 54, 63 },
{ 18, 50, 57 },
{ 19, 30, 47 },
{ 20, 64, 80 },
{ 21, 28, 69 },
{ 22, 25, 43 },
{ 13, 22, 37 },
{ 2, 47, 51 },
{ 23, 54, 74 },
{ 26, 34, 72 },
{ 27, 36, 37 },
{ 21, 36, 63 },
{ 29, 40, 44 },
{ 19, 26, 57 },
{ 3, 46, 82 },
{ 14, 15, 58 },
{ 33, 52, 53 },
{ 30, 43, 52 },
{ 6, 9, 52 },
{ 27, 33, 65 },
{ 25, 69, 73 },
{ 38, 55, 83 },
{ 20, 39, 77 },
{ 18, 29, 56 },
{ 32, 48, 71 },
{ 42, 51, 59 },
{ 28, 44, 79 },
{ 34, 60, 62 },
{ 31, 45, 61 },
{ 46, 68, 77 },
{ 6, 24, 76 },
{ 8, 10, 78 },
{ 40, 41, 70 },
{ 17, 50, 53 },
{ 42, 66, 68 },
{ 4, 22, 72 },
{ 36, 64, 81 },
{ 13, 29, 47 },
{ 2, 8, 81 },
{ 56, 67, 73 },
{ 5, 38, 50 },
{ 12, 38, 64 },
{ 59, 72, 80 },
{ 3, 26, 79 },
{ 45, 76, 81 },
{ 1, 65, 74 },
{ 7, 18, 77 },
{ 11, 56, 59 },
{ 14, 39, 54 },
{ 16, 37, 66 },
{ 10, 28, 55 },
{ 15, 60, 70 },
{ 17, 25, 82 },
{ 20, 30, 31 },
{ 12, 67, 68 },
{ 23, 75, 80 },
{ 27, 32, 62 },
{ 24, 69, 75 },
{ 19, 21, 71 },
{ 34, 53, 61 },
{ 35, 46, 47 },
{ 33, 59, 76 },
{ 40, 43, 83 },
{ 41, 42, 63 },
{ 49, 75, 83 },
{ 20, 44, 48 },
{ 42, 49, 57 }
};
const uint8_t kFT8_LDPC_num_rows[FT8_LDPC_M] = {
const uint8_t kFTX_LDPC_Num_rows[FTX_LDPC_M] = {
7, 6, 6, 6, 7, 6, 7, 6, 6, 7, 6, 6, 7, 7, 6, 6,
6, 7, 6, 7, 6, 7, 6, 6, 6, 7, 6, 6, 6, 7, 6, 6,
6, 6, 7, 6, 6, 6, 7, 7, 6, 6, 6, 6, 7, 7, 6, 6,
6, 6, 7, 6, 6, 6, 7, 6, 6, 6, 6, 7, 6, 6, 6, 7,
6, 6, 6, 7, 7, 6, 6, 7, 6, 6, 6, 6, 6, 6, 6, 7,
6, 6, 6};
6, 6, 6
};

Wyświetl plik

@ -3,47 +3,88 @@
#include <stdint.h>
#ifdef __cplusplus
extern "C"
{
#endif
#define FT8_SYMBOL_PERIOD (0.160f) ///< FT8 symbol duration, defines tone deviation in Hz and symbol rate
#define FT8_SLOT_TIME (15.0f) ///< FT8 slot period
#define FT4_SYMBOL_PERIOD (0.048f) ///< FT4 symbol duration, defines tone deviation in Hz and symbol rate
#define FT4_SLOT_TIME (7.5f) ///< FT4 slot period
// Define FT8 symbol counts
#define FT8_ND (58) ///< Data symbols
#define FT8_NS (21) ///< Sync symbols (3 @ Costas 7x7)
#define FT8_NN (79) ///< Total channel symbols (FT8_NS + FT8_ND)
// FT8 message structure:
// S D1 S D2 S
// S - sync block (7 symbols of Costas pattern)
// D1 - first data block (29 symbols each encoding 3 bits)
#define FT8_ND (58) ///< Data symbols
#define FT8_NN (79) ///< Total channel symbols (FT8_NS + FT8_ND)
#define FT8_LENGTH_SYNC (7) ///< Length of each sync group
#define FT8_NUM_SYNC (3) ///< Number of sync groups
#define FT8_SYNC_OFFSET (36) ///< Offset between sync groups
// Define FT4 symbol counts
// FT4 message structure:
// R Sa D1 Sb D2 Sc D3 Sd R
// R - ramping symbol (no payload information conveyed)
// Sx - one of four _different_ sync blocks (4 symbols of Costas pattern)
// Dy - data block (29 symbols each encoding 2 bits)
#define FT4_ND (87) ///< Data symbols
#define FT4_NR (2) ///< Ramp symbols (beginning + end)
#define FT4_NN (105) ///< Total channel symbols (FT4_NS + FT4_ND + FT4_NR)
#define FT4_LENGTH_SYNC (4) ///< Length of each sync group
#define FT4_NUM_SYNC (4) ///< Number of sync groups
#define FT4_SYNC_OFFSET (33) ///< Offset between sync groups
// Define LDPC parameters
#define FT8_LDPC_N (174) ///< Number of bits in the encoded message (payload with LDPC checksum bits)
#define FT8_LDPC_K (91) ///< Number of payload bits (including CRC)
#define FT8_LDPC_M (83) ///< Number of LDPC checksum bits (FT8_LDPC_N - FT8_LDPC_K)
#define FT8_LDPC_N_BYTES ((FT8_LDPC_N + 7) / 8) ///< Number of whole bytes needed to store 174 bits (full message)
#define FT8_LDPC_K_BYTES ((FT8_LDPC_K + 7) / 8) ///< Number of whole bytes needed to store 91 bits (payload + CRC only)
#define FTX_LDPC_N (174) ///< Number of bits in the encoded message (payload with LDPC checksum bits)
#define FTX_LDPC_K (91) ///< Number of payload bits (including CRC)
#define FTX_LDPC_M (83) ///< Number of LDPC checksum bits (FTX_LDPC_N - FTX_LDPC_K)
#define FTX_LDPC_N_BYTES ((FTX_LDPC_N + 7) / 8) ///< Number of whole bytes needed to store 174 bits (full message)
#define FTX_LDPC_K_BYTES ((FTX_LDPC_K + 7) / 8) ///< Number of whole bytes needed to store 91 bits (payload + CRC only)
// Define CRC parameters
#define FT8_CRC_POLYNOMIAL ((uint16_t)0x2757u) ///< CRC-14 polynomial without the leading (MSB) 1
#define FT8_CRC_WIDTH (14)
#define FT8_CRC_WIDTH (14)
/// Costas 7x7 tone pattern for synchronization
extern const uint8_t kFT8_Costas_pattern[7];
/// Gray code map to encode 8 symbols (tones)
extern const uint8_t kFT8_Gray_map[8];
typedef enum
{
PROTO_FT4,
PROTO_FT8
} ftx_protocol_t;
/// Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first)
extern const uint8_t kFT8_LDPC_generator[FT8_LDPC_M][FT8_LDPC_K_BYTES];
/// Costas 7x7 tone pattern for synchronization
extern const uint8_t kFT8_Costas_pattern[7];
extern const uint8_t kFT4_Costas_pattern[4][4];
/// Column order (permutation) in which the bits in codeword are stored
/// (Not really used in FT8 v2 - instead the Nm, Mn and generator matrices are already permuted)
extern const uint8_t kFT8_LDPC_column_order[FT8_LDPC_N];
/// Gray code map to encode 8 symbols (tones)
extern const uint8_t kFT8_Gray_map[8];
extern const uint8_t kFT4_Gray_map[4];
/// LDPC(174,91) parity check matrix, containing 83 rows,
/// each row describes one parity check,
/// each number is an index into the codeword (1-origin).
/// The codeword bits mentioned in each row must xor to zero.
/// From WSJT-X's ldpc_174_91_c_reordered_parity.f90.
extern const uint8_t kFT8_LDPC_Nm[FT8_LDPC_M][7];
extern const uint8_t kFT4_XOR_sequence[10];
/// Mn from WSJT-X's bpdecode174.f90. Each row corresponds to a codeword bit.
/// The numbers indicate which three parity checks (rows in Nm) refer to the codeword bit.
/// The numbers use 1 as the origin (first entry).
extern const uint8_t kFT8_LDPC_Mn[FT8_LDPC_N][3];
/// Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first)
extern const uint8_t kFTX_LDPC_generator[FTX_LDPC_M][FTX_LDPC_K_BYTES];
/// Number of rows (columns in C/C++) in the array Nm.
extern const uint8_t kFT8_LDPC_num_rows[FT8_LDPC_M];
/// LDPC(174,91) parity check matrix, containing 83 rows,
/// each row describes one parity check,
/// each number is an index into the codeword (1-origin).
/// The codeword bits mentioned in each row must xor to zero.
/// From WSJT-X's ldpc_174_91_c_reordered_parity.f90.
extern const uint8_t kFTX_LDPC_Nm[FTX_LDPC_M][7];
/// Mn from WSJT-X's bpdecode174.f90. Each row corresponds to a codeword bit.
/// The numbers indicate which three parity checks (rows in Nm) refer to the codeword bit.
/// The numbers use 1 as the origin (first entry).
extern const uint8_t kFTX_LDPC_Mn[FTX_LDPC_N][3];
/// Number of rows (columns in C/C++) in the array Nm.
extern const uint8_t kFTX_LDPC_Num_rows[FTX_LDPC_M];
#ifdef __cplusplus
}
#endif
#endif // _INCLUDE_CONSTANTS_H_

Wyświetl plik

@ -7,7 +7,7 @@
// Adapted from https://barrgroup.com/Embedded-Systems/How-To/CRC-Calculation-C-Code
// [IN] message - byte sequence (MSB first)
// [IN] num_bits - number of bits in the sequence
uint16_t ft8_crc(const uint8_t message[], int num_bits)
uint16_t ftx_compute_crc(const uint8_t message[], int num_bits)
{
uint16_t remainder = 0;
int idx_byte = 0;
@ -36,13 +36,13 @@ uint16_t ft8_crc(const uint8_t message[], int num_bits)
return remainder & ((TOPBIT << 1) - 1u);
}
uint16_t extract_crc(const uint8_t a91[])
uint16_t ftx_extract_crc(const uint8_t a91[])
{
uint16_t chksum = ((a91[9] & 0x07) << 11) | (a91[10] << 3) | (a91[11] >> 5);
return chksum;
}
void add_crc(const uint8_t payload[], uint8_t a91[])
void ftx_add_crc(const uint8_t payload[], uint8_t a91[])
{
// Copy 77 bits of payload data
for (int i = 0; i < 10; i++)
@ -54,7 +54,7 @@ void add_crc(const uint8_t payload[], uint8_t a91[])
// Calculate CRC of 82 bits (77 + 5 zeros)
// 'The CRC is calculated on the source-encoded message, zero-extended from 77 to 82 bits'
uint16_t checksum = ft8_crc(a91, 96 - 14);
uint16_t checksum = ftx_compute_crc(a91, 96 - 14);
// Store the CRC at the end of 77 bit message
a91[9] |= (uint8_t)(checksum >> 11);

Wyświetl plik

@ -4,19 +4,28 @@
#include <stdint.h>
#include <stdbool.h>
// Compute 14-bit CRC for a sequence of given number of bits
// [IN] message - byte sequence (MSB first)
// [IN] num_bits - number of bits in the sequence
uint16_t ft8_crc(const uint8_t message[], int num_bits);
#ifdef __cplusplus
extern "C"
{
#endif
/// Extract the FT8 CRC of a packed message (during decoding)
/// @param[in] a91 77 bits of payload data + CRC
/// @return Extracted CRC
uint16_t extract_crc(const uint8_t a91[]);
// Compute 14-bit CRC for a sequence of given number of bits using FT8/FT4 CRC polynomial
// [IN] message - byte sequence (MSB first)
// [IN] num_bits - number of bits in the sequence
uint16_t ftx_compute_crc(const uint8_t message[], int num_bits);
/// Add the FT8 CRC to a packed message (during encoding)
/// @param[in] payload 77 bits of payload data
/// @param[out] a91 91 bits of payload data + CRC
void add_crc(const uint8_t payload[], uint8_t a91[]);
/// Extract the FT8/FT4 CRC of a packed message (during decoding)
/// @param[in] a91 77 bits of payload data + CRC
/// @return Extracted CRC
uint16_t ftx_extract_crc(const uint8_t a91[]);
/// Add FT8/FT4 CRC to a packed message (during encoding)
/// @param[in] payload 77 bits of payload data
/// @param[out] a91 91 bits of payload data + CRC
void ftx_add_crc(const uint8_t payload[], uint8_t a91[]);
#ifdef __cplusplus
}
#endif
#endif // _INCLUDE_CRC_H_

Wyświetl plik

@ -7,125 +7,211 @@
#include <stdbool.h>
#include <math.h>
#include <stdio.h>
#include "decode_ft8.h" // for the constants
/// Compute log likelihood log(p(1) / p(0)) of 174 message bits for later use in soft-decision LDPC decoding
/// @param[in] power Waterfall data collected during message slot
/// @param[in] wf Waterfall data collected during message slot
/// @param[in] cand Candidate to extract the message from
/// @param[in] code_map Symbol encoding map
/// @param[out] log174 Output of decoded log likelihoods for each of the 174 message bits
static void extract_likelihood(const waterfall_t *power, const candidate_t *cand, float *log174);
static void ft4_extract_likelihood(const waterfall_t* wf, const candidate_t* cand, float* log174);
static void ft8_extract_likelihood(const waterfall_t* wf, const candidate_t* cand, float* log174);
/// Packs a string of bits each represented as a zero/non-zero byte in bit_array[],
/// as a string of packed bits starting from the MSB of the first byte of packed[]
/// @param[in] plain Array of bits (0 and nonzero values) with num_bits entires
/// @param[in] num_bits Number of bits (entries) passed in bit_array
/// @param[out] packed Byte-packed bits representing the data in bit_array
static void pack_bits(const uint8_t bit_array[], int num_bits, uint8_t packed[]);
static float max2(float a, float b);
static float max4(float a, float b, float c, float d);
static void heapify_down(candidate_t heap[], int heap_size);
static void heapify_up(candidate_t heap[], int heap_size);
static void decode_symbol(const uint8_t *power, int bit_idx, float *log174);
//static void decode_multi_symbols(const uint8_t *power, int num_bins, int n_syms, int bit_idx, float *log174);
static int get_index(const waterfall_t *power, int block, int time_sub, int freq_sub, int bin)
static void ftx_normalize_logl(float* log174);
static void ft4_extract_symbol(const uint8_t* wf, float* logl);
static void ft8_extract_symbol(const uint8_t* wf, float* logl);
static void ft8_decode_multi_symbols(const uint8_t* wf, int num_bins, int n_syms, int bit_idx, float* log174);
static int get_index(const waterfall_t* wf, const candidate_t* candidate)
{
return ((((block * power->time_osr) + time_sub) * power->freq_osr + freq_sub) * power->num_bins) + bin;
int offset = candidate->time_offset;
offset = (offset * wf->time_osr) + candidate->time_sub;
offset = (offset * wf->freq_osr) + candidate->freq_sub;
offset = (offset * wf->num_bins) + candidate->freq_offset;
return offset;
}
int find_sync(const waterfall_t *power, int num_candidates, candidate_t heap[], int min_score)
static int ft8_sync_score(const waterfall_t* wf, const candidate_t* candidate)
{
int score = 0;
int num_average = 0;
// Get the pointer to symbol 0 of the candidate
const uint8_t* mag_cand = wf->mag + get_index(wf, candidate);
// Compute average score over sync symbols (m+k = 0-7, 36-43, 72-79)
for (int m = 0; m < FT8_NUM_SYNC; ++m)
{
for (int k = 0; k < FT8_LENGTH_SYNC; ++k)
{
int block = (FT8_SYNC_OFFSET * m) + k; // relative to the message
int block_abs = candidate->time_offset + block; // relative to the captured signal
// Check for time boundaries
if (block_abs < 0)
continue;
if (block_abs >= wf->num_blocks)
break;
// Get the pointer to symbol 'block' of the candidate
const uint8_t* p8 = mag_cand + (block * wf->block_stride);
// Weighted difference between the expected and all other symbols
// Does not work as well as the alternative score below
// score += 8 * p8[kFT8_Costas_pattern[k]] -
// p8[0] - p8[1] - p8[2] - p8[3] -
// p8[4] - p8[5] - p8[6] - p8[7];
// ++num_average;
// Check only the neighbors of the expected symbol frequency- and time-wise
int sm = kFT8_Costas_pattern[k]; // Index of the expected bin
if (sm > 0)
{
// look at one frequency bin lower
score += p8[sm] - p8[sm - 1];
++num_average;
}
if (sm < 7)
{
// look at one frequency bin higher
score += p8[sm] - p8[sm + 1];
++num_average;
}
if ((k > 0) && (block_abs > 0))
{
// look one symbol back in time
score += p8[sm] - p8[sm - wf->block_stride];
++num_average;
}
if (((k + 1) < FT8_LENGTH_SYNC) && ((block_abs + 1) < wf->num_blocks))
{
// look one symbol forward in time
score += p8[sm] - p8[sm + wf->block_stride];
++num_average;
}
}
}
if (num_average > 0)
score /= num_average;
return score;
}
static int ft4_sync_score(const waterfall_t* wf, const candidate_t* candidate)
{
int score = 0;
int num_average = 0;
// Get the pointer to symbol 0 of the candidate
const uint8_t* mag_cand = wf->mag + get_index(wf, candidate);
// Compute average score over sync symbols (block = 1-4, 34-37, 67-70, 100-103)
for (int m = 0; m < FT4_NUM_SYNC; ++m)
{
for (int k = 0; k < FT4_LENGTH_SYNC; ++k)
{
int block = 1 + (FT4_SYNC_OFFSET * m) + k;
int block_abs = candidate->time_offset + block;
// Check for time boundaries
if (block_abs < 0)
continue;
if (block_abs >= wf->num_blocks)
break;
// Get the pointer to symbol 'block' of the candidate
const uint8_t* p4 = mag_cand + (block * wf->block_stride);
int sm = kFT4_Costas_pattern[m][k]; // Index of the expected bin
// score += (4 * p4[sm]) - p4[0] - p4[1] - p4[2] - p4[3];
// num_average += 4;
// Check only the neighbors of the expected symbol frequency- and time-wise
if (sm > 0)
{
// look at one frequency bin lower
score += p4[sm] - p4[sm - 1];
++num_average;
}
if (sm < 3)
{
// look at one frequency bin higher
score += p4[sm] - p4[sm + 1];
++num_average;
}
if ((k > 0) && (block_abs > 0))
{
// look one symbol back in time
score += p4[sm] - p4[sm - wf->block_stride];
++num_average;
}
if (((k + 1) < FT4_LENGTH_SYNC) && ((block_abs + 1) < wf->num_blocks))
{
// look one symbol forward in time
score += p4[sm] - p4[sm + wf->block_stride];
++num_average;
}
}
}
if (num_average > 0)
score /= num_average;
return score;
}
int ft8_find_sync(const waterfall_t* wf, int num_candidates, candidate_t heap[], int min_score)
{
int heap_size = 0;
int sym_stride = power->time_osr * power->freq_osr * power->num_bins;
candidate_t candidate;
// Here we allow time offsets that exceed signal boundaries, as long as we still have all data bits.
// I.e. we can afford to skip the first 7 or the last 7 Costas symbols, as long as we track how many
// sync symbols we included in the score, so the score is averaged.
for (int time_sub = 0; time_sub < power->time_osr; ++time_sub)
for (candidate.time_sub = 0; candidate.time_sub < wf->time_osr; ++candidate.time_sub)
{
for (int freq_sub = 0; freq_sub < power->freq_osr; ++freq_sub)
for (candidate.freq_sub = 0; candidate.freq_sub < wf->freq_osr; ++candidate.freq_sub)
{
for (int time_offset = -12; time_offset < 24; ++time_offset)
for (candidate.time_offset = -12; candidate.time_offset < 24; ++candidate.time_offset)
{
for (int freq_offset = 0; freq_offset + 7 < power->num_bins; ++freq_offset)
for (candidate.freq_offset = 0; (candidate.freq_offset + 7) < wf->num_bins; ++candidate.freq_offset)
{
int score = 0;
int num_average = 0;
// Compute average score over sync symbols (m+k = 0-7, 36-43, 72-79)
for (int m = 0; m <= 72; m += 36)
if (wf->protocol == PROTO_FT4)
{
for (int k = 0; k < 7; ++k)
{
int block = time_offset + m + k;
// Check for time boundaries
if (block < 0)
continue;
if (block >= power->num_blocks)
break;
int offset = get_index(power, block, time_sub, freq_sub, freq_offset);
const uint8_t *p8 = power->mag + offset;
// Weighted difference between the expected and all other symbols
// Does not work as well as the alternative score below
// score += 8 * p8[kFT8_Costas_pattern[k]] -
// p8[0] - p8[1] - p8[2] - p8[3] -
// p8[4] - p8[5] - p8[6] - p8[7];
// ++num_average;
// Check only the neighbors of the expected symbol frequency- and time-wise
int sm = kFT8_Costas_pattern[k]; // Index of the expected bin
if (sm > 0)
{
// look at one frequency bin lower
score += p8[sm] - p8[sm - 1];
++num_average;
}
if (sm < 7)
{
// look at one frequency bin higher
score += p8[sm] - p8[sm + 1];
++num_average;
}
if ((k > 0) && (block > 0))
{
// look one symbol back in time
score += p8[sm] - p8[sm - sym_stride];
++num_average;
}
if ((k < 6) && ((block + 1) < power->num_blocks))
{
// look one symbol forward in time
score += p8[sm] - p8[sm + sym_stride];
++num_average;
}
}
candidate.score = ft4_sync_score(wf, &candidate);
}
else
{
candidate.score = ft8_sync_score(wf, &candidate);
}
if (num_average > 0)
score /= num_average;
if (score < min_score)
if (candidate.score < min_score)
continue;
// If the heap is full AND the current candidate is better than
// the worst in the heap, we remove the worst and make space
if (heap_size == num_candidates && score > heap[0].score)
if (heap_size == num_candidates && candidate.score > heap[0].score)
{
heap[0] = heap[heap_size - 1];
--heap_size;
heapify_down(heap, heap_size);
}
// If there's free space in the heap, we add the current candidate
if (heap_size < num_candidates)
{
heap[heap_size].score = score;
heap[heap_size].time_offset = time_offset;
heap[heap_size].freq_offset = freq_offset;
heap[heap_size].time_sub = time_sub;
heap[heap_size].freq_sub = freq_sub;
heap[heap_size] = candidate;
++heap_size;
heapify_up(heap, heap_size);
}
}
@ -147,62 +233,101 @@ int find_sync(const waterfall_t *power, int num_candidates, candidate_t heap[],
return heap_size;
}
void extract_likelihood(const waterfall_t *power, const candidate_t *cand, float *log174)
static void ft4_extract_likelihood(const waterfall_t* wf, const candidate_t* cand, float* log174)
{
int sym_stride = power->time_osr * power->freq_osr * power->num_bins;
int offset = get_index(power, cand->time_offset, cand->time_sub, cand->freq_sub, cand->freq_offset);
const uint8_t* mag_cand = wf->mag + get_index(wf, cand);
// Go over FSK tones and skip Costas sync symbols
const int n_syms = 1;
const int n_bits = 3 * n_syms;
const int n_tones = (1 << n_bits);
for (int k = 0; k < FT8_ND; k += n_syms)
for (int k = 0; k < FT4_ND; ++k)
{
// Add either 7 or 14 extra symbols to account for sync
int sym_idx = (k < FT8_ND / 2) ? (k + 7) : (k + 14);
int bit_idx = 3 * k;
int block = cand->time_offset + sym_idx;
// Skip either 5, 9 or 13 sync symbols
// TODO: replace magic numbers with constants
int sym_idx = k + ((k < 29) ? 5 : ((k < 58) ? 9 : 13));
int bit_idx = 2 * k;
// Check for time boundaries
if ((block < 0) || (block >= power->num_blocks))
int block = cand->time_offset + sym_idx;
if ((block < 0) || (block >= wf->num_blocks))
{
log174[bit_idx] = 0;
log174[bit_idx + 0] = 0;
log174[bit_idx + 1] = 0;
}
else
{
// Pointer to 4 bins of the current symbol
const uint8_t* ps = mag_cand + (sym_idx * wf->block_stride);
ft4_extract_symbol(ps, log174 + bit_idx);
}
}
}
static void ft8_extract_likelihood(const waterfall_t* wf, const candidate_t* cand, float* log174)
{
const uint8_t* mag_cand = wf->mag + get_index(wf, cand);
// Go over FSK tones and skip Costas sync symbols
for (int k = 0; k < FT8_ND; ++k)
{
// Skip either 7 or 14 sync symbols
// TODO: replace magic numbers with constants
int sym_idx = k + ((k < 29) ? 7 : 14);
int bit_idx = 3 * k;
// Check for time boundaries
int block = cand->time_offset + sym_idx;
if ((block < 0) || (block >= wf->num_blocks))
{
log174[bit_idx + 0] = 0;
log174[bit_idx + 1] = 0;
log174[bit_idx + 2] = 0;
}
else
{
// Pointer to 8 bins of the current symbol
const uint8_t *ps = power->mag + offset + (sym_idx * sym_stride);
const uint8_t* ps = mag_cand + (sym_idx * wf->block_stride);
decode_symbol(ps, bit_idx, log174);
ft8_extract_symbol(ps, log174 + bit_idx);
}
}
}
static void ftx_normalize_logl(float* log174)
{
// Compute the variance of log174
float sum = 0;
float sum2 = 0;
for (int i = 0; i < FT8_LDPC_N; ++i)
for (int i = 0; i < FTX_LDPC_N; ++i)
{
sum += log174[i];
sum2 += log174[i] * log174[i];
}
float inv_n = 1.0f / FT8_LDPC_N;
float inv_n = 1.0f / FTX_LDPC_N;
float variance = (sum2 - (sum * sum * inv_n)) * inv_n;
// Normalize log174 distribution and scale it with experimentally found coefficient
float norm_factor = sqrtf(24.0f / variance);
for (int i = 0; i < FT8_LDPC_N; ++i)
for (int i = 0; i < FTX_LDPC_N; ++i)
{
log174[i] *= norm_factor;
}
}
bool decode(const waterfall_t *power, const candidate_t *cand, message_t *message, int max_iterations, decode_status_t *status)
bool ft8_decode(const waterfall_t* wf, const candidate_t* cand, message_t* message, int max_iterations, decode_status_t* status)
{
float log174[FT8_LDPC_N]; // message bits encoded as likelihood
extract_likelihood(power, cand, log174);
float log174[FTX_LDPC_N]; // message bits encoded as likelihood
if (wf->protocol == PROTO_FT4)
{
ft4_extract_likelihood(wf, cand, log174);
}
else
{
ft8_extract_likelihood(wf, cand, log174);
}
uint8_t plain174[FT8_LDPC_N]; // message bits (0/1)
ftx_normalize_logl(log174);
uint8_t plain174[FTX_LDPC_N]; // message bits (0/1)
bp_decode(log174, max_iterations, plain174, &status->ldpc_errors);
// ldpc_decode(log174, max_iterations, plain174, &status->ldpc_errors);
@ -211,22 +336,32 @@ bool decode(const waterfall_t *power, const candidate_t *cand, message_t *messag
return false;
}
// Extract payload + CRC (first FT8_LDPC_K bits) packed into a byte array
uint8_t a91[FT8_LDPC_K_BYTES];
pack_bits(plain174, FT8_LDPC_K, a91);
// Extract payload + CRC (first FTX_LDPC_K bits) packed into a byte array
uint8_t a91[FTX_LDPC_K_BYTES];
pack_bits(plain174, FTX_LDPC_K, a91);
// Extract CRC and check it
status->crc_extracted = extract_crc(a91);
status->crc_extracted = ftx_extract_crc(a91);
// [1]: 'The CRC is calculated on the source-encoded message, zero-extended from 77 to 82 bits.'
a91[9] &= 0xF8;
a91[10] &= 0x00;
status->crc_calculated = ft8_crc(a91, 96 - 14);
status->crc_calculated = ftx_compute_crc(a91, 96 - 14);
if (status->crc_extracted != status->crc_calculated)
{
return false;
}
if (wf->protocol == PROTO_FT4)
{
// '[..] for FT4 only, in order to avoid transmitting a long string of zeros when sending CQ messages,
// the assembled 77-bit message is bitwise exclusive-ORed with [a] pseudorandom sequence before computing the CRC and FEC parity bits'
for (int i = 0; i < 10; ++i)
{
a91[i] ^= kFT4_XOR_sequence[i];
}
}
status->unpack_status = unpack77(a91, message->text);
if (status->unpack_status < 0)
@ -299,111 +434,117 @@ static void heapify_up(candidate_t heap[], int heap_size)
}
}
// Compute unnormalized log likelihood log(p(1) / p(0)) of 2 message bits (1 FSK symbol)
static void ft4_extract_symbol(const uint8_t* wf, float* logl)
{
// Cleaned up code for the simple case of n_syms==1
float s2[4];
for (int j = 0; j < 4; ++j)
{
s2[j] = (float)wf[kFT4_Gray_map[j]];
}
logl[0] = max2(s2[2], s2[3]) - max2(s2[0], s2[1]);
logl[1] = max2(s2[1], s2[3]) - max2(s2[0], s2[2]);
}
// Compute unnormalized log likelihood log(p(1) / p(0)) of 3 message bits (1 FSK symbol)
static void decode_symbol(const uint8_t *power, int bit_idx, float *log174)
static void ft8_extract_symbol(const uint8_t* wf, float* logl)
{
// Cleaned up code for the simple case of n_syms==1
float s2[8];
for (int j = 0; j < 8; ++j)
{
s2[j] = (float)power[kFT8_Gray_map[j]];
s2[j] = (float)wf[kFT8_Gray_map[j]];
}
log174[bit_idx + 0] = max4(s2[4], s2[5], s2[6], s2[7]) - max4(s2[0], s2[1], s2[2], s2[3]);
log174[bit_idx + 1] = max4(s2[2], s2[3], s2[6], s2[7]) - max4(s2[0], s2[1], s2[4], s2[5]);
log174[bit_idx + 2] = max4(s2[1], s2[3], s2[5], s2[7]) - max4(s2[0], s2[2], s2[4], s2[6]);
logl[0] = max4(s2[4], s2[5], s2[6], s2[7]) - max4(s2[0], s2[1], s2[2], s2[3]);
logl[1] = max4(s2[2], s2[3], s2[6], s2[7]) - max4(s2[0], s2[1], s2[4], s2[5]);
logl[2] = max4(s2[1], s2[3], s2[5], s2[7]) - max4(s2[0], s2[2], s2[4], s2[6]);
}
//Added by AA1GD Aug. 23 2021
//I am unsure if the FFT magnitudes given between 0-256 are linear or logarithmic?
//They are log
// Compute unnormalized log likelihood log(p(1) / p(0)) of bits corresponding to several FSK symbols at once
static void ft8_decode_multi_symbols(const uint8_t* wf, int num_bins, int n_syms, int bit_idx, float* log174)
{
const int n_bits = 3 * n_syms;
const int n_tones = (1 << n_bits);
int calc_noise(const waterfall_t *power, const candidate_t *cand){
//other option is to sample the ~half second quiet before transmissions start
// set noise_sample_type to 1 to sample single frequency, 2 for average over frequency range, 3 for the dead time before transmissions
char noise_sample_type = 2;
float s2[n_tones];
long noise_sum = 0;
int noise_sampling_freq = 250; //finds noise levels between (noise_sampling_freq, noise_sampling_freq+noise_sampling_freq_range)
int noise_sampling_freq_range = 50; //a multiple of 6.25Hz
int noise_sampling_start_block = 1; //applies to types 1,2,3
int noise_avg;
//we will start k at one since it seems the first block is zero
if (noise_sample_type == 1){
for(int k = noise_sampling_start_block; k<power->num_blocks; k++){
noise_sum += power->mag[get_index(power,k,0,0,noise_sampling_freq/6.25)];
for (int j = 0; j < n_tones; ++j)
{
int j1 = j & 0x07;
if (n_syms == 1)
{
s2[j] = (float)wf[kFT8_Gray_map[j1]];
continue;
}
noise_avg = noise_sum / (power->num_blocks - noise_sampling_start_block);
int j2 = (j >> 3) & 0x07;
if (n_syms == 2)
{
s2[j] = (float)wf[kFT8_Gray_map[j2]];
s2[j] += (float)wf[kFT8_Gray_map[j1] + 4 * num_bins];
continue;
}
int j3 = (j >> 6) & 0x07;
s2[j] = (float)wf[kFT8_Gray_map[j3]];
s2[j] += (float)wf[kFT8_Gray_map[j2] + 4 * num_bins];
s2[j] += (float)wf[kFT8_Gray_map[j1] + 8 * num_bins];
}
if (noise_sample_type == 2){
for(int i = noise_sampling_start_block; i<noise_sampling_freq_range/6.25; i++){
// Extract bit significance (and convert them to float)
// 8 FSK tones = 3 bits
for (int i = 0; i < n_bits; ++i)
{
if (bit_idx + i >= FTX_LDPC_N)
{
// Respect array size
break;
}
for(int k = 0; k<power->num_blocks; k++){
noise_sum += power->mag[get_index(power,k,0,0,i + noise_sampling_freq/6.25)];
uint16_t mask = (n_tones >> (i + 1));
float max_zero = -1000, max_one = -1000;
for (int n = 0; n < n_tones; ++n)
{
if (n & mask)
{
max_one = max2(max_one, s2[n]);
}
else
{
max_zero = max2(max_zero, s2[n]);
}
}
noise_avg = noise_sum / ((power->num_blocks - noise_sampling_start_block) * noise_sampling_freq_range/6.25);
}
//Samples noise before transmission. This may not be good for wav decoding because of AGC.
//Has not been tested
if (noise_sample_type == 3){
int noise_sampling_end_block = cand->time_sub; //applies to type 3 only, stops sampling just before signal begins.
//May need to subtract 1 although this is done in the for loop?
for(int i = noise_sampling_start_block; i<noise_sampling_end_block; i++){
for(int j = 0; j < 8; j++){
noise_sum += power->mag[get_index(power,i,0,0,j + cand->freq_offset)];
}
}
noise_avg = noise_sum / (8*(noise_sampling_end_block-noise_sampling_start_block));
log174[bit_idx + i] = max_one - max_zero;
}
// if noise_avg is >255 or <0, something went wrong.
return noise_avg;
}
//Very roughly estimates snr based on difference between noise and signal. Just did a linear regression to estimate
//relationship between (signal-noise) and actual snr calculated by WSJT-X
//The logarithmic magnitudes make it hard to do actual power calculations...
//some of these int variables can be replaced with u_int8
int calc_snr(const waterfall_t *power, const candidate_t *cand, int noise_avg){
int sig_sum = 0;
// Packs a string of bits each represented as a zero/non-zero byte in plain[],
// as a string of packed bits starting from the MSB of the first byte of packed[]
static void pack_bits(const uint8_t bit_array[], int num_bits, uint8_t packed[])
{
int num_bytes = (num_bits + 7) / 8;
for (int i = 0; i < num_bytes; ++i)
{
packed[i] = 0;
}
for (int i = 0; i < num_blocks; i++){
//find the strongest of 8 tones for each of 79 symbol times
int8_t max_tone_mag = 0;
for (int k = 0; k < 8; k++){
int8_t tone_mag = power->mag[get_index(power,cand->time_offset + i,cand->time_sub,cand->freq_sub,cand->freq_offset + k)];
if(tone_mag > max_tone_mag){
max_tone_mag = tone_mag;
}
uint8_t mask = 0x80;
int byte_idx = 0;
for (int i = 0; i < num_bits; ++i)
{
if (bit_array[i])
{
packed[byte_idx] |= mask;
}
mask >>= 1;
if (!mask)
{
mask = 0x80;
++byte_idx;
}
//printf("max tone: %d",max_tone_mag);
sig_sum += max_tone_mag;
}
int sig_avg = sig_sum / 79;
printf("sig avg: %d ",sig_avg);
int sig_minus_noise = sig_avg-noise_avg;
printf("sig-noise power: %d \n", sig_minus_noise);
//int snr = round(0.427*sig_minus_noise - 33.7); //this linear regression can be improved with more testing
int snr = (int)(0.427 * sig_minus_noise - 23.0);
if (snr > 30){
snr = 30;
}
if(snr < -24){
snr = -24;
}
//printf("finished calcsnr\n");
return snr;
}

Wyświetl plik

@ -4,76 +4,80 @@
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include "constants.h"
/// Input structure to find_sync() function. This structure describes stored waterfall data over the whole message slot.
/// Fields time_osr and freq_osr specify additional oversampling rate for time and frequency resolution.
/// If time_osr=1, FFT magnitude data is collected once for every symbol transmitted, i.e. every 1/6.25 = 0.16 seconds.
/// Values time_osr > 1 mean each symbol is further subdivided in time.
/// If freq_osr=1, each bin in the FFT magnitude data corresponds to 6.25 Hz, which is the tone spacing.
/// Values freq_osr > 1 mean the tone spacing is further subdivided by FFT analysis.
typedef struct
#ifdef __cplusplus
extern "C"
{
int num_blocks; ///< number of total blocks (symbols) in terms of 160 ms time periods
int num_bins; ///< number of FFT bins in terms of 6.25 Hz
int time_osr; ///< number of time subdivisions
int freq_osr; ///< number of frequency subdivisions
uint8_t *mag; ///< FFT magnitudes stored as uint8_t[blocks][time_osr][freq_osr][num_bins]
} waterfall_t;
#endif
/// Output structure of find_sync() and input structure of extract_likelihood().
/// Holds the position of potential start of a message in time and frequency.
typedef struct
{
int16_t score; ///< Candidate score (non-negative number; higher score means higher likelihood)
int16_t time_offset; ///< Index of the time block
int16_t freq_offset; ///< Index of the frequency bin
uint8_t time_sub; ///< Index of the time subdivision used
uint8_t freq_sub; ///< Index of the frequency subdivision used
} candidate_t;
/// Input structure to ft8_find_sync() function. This structure describes stored waterfall data over the whole message slot.
/// Fields time_osr and freq_osr specify additional oversampling rate for time and frequency resolution.
/// If time_osr=1, FFT magnitude data is collected once for every symbol transmitted, i.e. every 1/6.25 = 0.16 seconds.
/// Values time_osr > 1 mean each symbol is further subdivided in time.
/// If freq_osr=1, each bin in the FFT magnitude data corresponds to 6.25 Hz, which is the tone spacing.
/// Values freq_osr > 1 mean the tone spacing is further subdivided by FFT analysis.
typedef struct
{
int max_blocks; ///< number of blocks (symbols) allocated in the mag array
int num_blocks; ///< number of blocks (symbols) stored in the mag array
int num_bins; ///< number of FFT bins in terms of 6.25 Hz
int time_osr; ///< number of time subdivisions
int freq_osr; ///< number of frequency subdivisions
uint8_t* mag; ///< FFT magnitudes stored as uint8_t[blocks][time_osr][freq_osr][num_bins]
int block_stride; ///< Helper value = time_osr * freq_osr * num_bins
ftx_protocol_t protocol; ///< Indicate if using FT4 or FT8
} waterfall_t;
/// Structure that holds the decoded message
typedef struct
{
// TODO: check again that this size is enough
char text[25]; // plain text
uint16_t hash; // hash value to be used in hash table and quick checking for duplicates
} message_t;
/// Output structure of ft8_find_sync() and input structure of ft8_decode().
/// Holds the position of potential start of a message in time and frequency.
typedef struct
{
int16_t score; ///< Candidate score (non-negative number; higher score means higher likelihood)
int16_t time_offset; ///< Index of the time block
int16_t freq_offset; ///< Index of the frequency bin
uint8_t time_sub; ///< Index of the time subdivision used
uint8_t freq_sub; ///< Index of the frequency subdivision used
} candidate_t;
/// Structure that contains the status of various steps during decoding of a message
typedef struct
{
int ldpc_errors;
uint16_t crc_extracted;
uint16_t crc_calculated;
int unpack_status;
} decode_status_t;
/// Structure that holds the decoded message
typedef struct
{
// TODO: check again that this size is enough
char text[25]; ///< Plain text
uint16_t hash; ///< Hash value to be used in hash table and quick checking for duplicates
} message_t;
/// Localize top N candidates in frequency and time according to their sync strength (looking at Costas symbols)
/// We treat and organize the candidate list as a min-heap (empty initially).
/// @param[in] power Waterfall data collected during message slot
/// @param[in] sync_pattern Synchronization pattern
/// @param[in] num_candidates Number of maximum candidates (size of heap array)
/// @param[in,out] heap Array of candidate_t type entries (with num_candidates allocated entries)
/// @param[in] min_score Minimal score allowed for pruning unlikely candidates (can be zero for no effect)
/// @return Number of candidates filled in the heap
int find_sync(const waterfall_t *power, int num_candidates, candidate_t heap[], int min_score);
/// Structure that contains the status of various steps during decoding of a message
typedef struct
{
int ldpc_errors; ///< Number of LDPC errors during decoding
uint16_t crc_extracted; ///< CRC value recovered from the message
uint16_t crc_calculated; ///< CRC value calculated over the payload
int unpack_status; ///< Return value of the unpack routine
} decode_status_t;
/// Attempt to decode a message candidate. Extracts the bit probabilities, runs LDPC decoder, checks CRC and unpacks the message in plain text.
/// @param[in] power Waterfall data collected during message slot
/// @param[in] cand Candidate to decode
/// @param[out] message message_t structure that will receive the decoded message
/// @param[in] max_iterations Maximum allowed LDPC iterations (lower number means faster decode, but less precise)
/// @param[out] status decode_status_t structure that will be filled with the status of various decoding steps
/// @return True if the decoding was successful, false otherwise (check status for details)
bool decode(const waterfall_t *power, const candidate_t *cand, message_t *message, int max_iterations, decode_status_t *status);
/// Localize top N candidates in frequency and time according to their sync strength (looking at Costas symbols)
/// We treat and organize the candidate list as a min-heap (empty initially).
/// @param[in] power Waterfall data collected during message slot
/// @param[in] sync_pattern Synchronization pattern
/// @param[in] num_candidates Number of maximum candidates (size of heap array)
/// @param[in,out] heap Array of candidate_t type entries (with num_candidates allocated entries)
/// @param[in] min_score Minimal score allowed for pruning unlikely candidates (can be zero for no effect)
/// @return Number of candidates filled in the heap
int ft8_find_sync(const waterfall_t* power, int num_candidates, candidate_t heap[], int min_score);
/// @param[in] power Waterfall data collected during message slot
/// @param[in] cand Candidate to decode
int calc_noise(const waterfall_t *power, const candidate_t *cand);
/// Attempt to decode a message candidate. Extracts the bit probabilities, runs LDPC decoder, checks CRC and unpacks the message in plain text.
/// @param[in] power Waterfall data collected during message slot
/// @param[in] cand Candidate to decode
/// @param[out] message message_t structure that will receive the decoded message
/// @param[in] max_iterations Maximum allowed LDPC iterations (lower number means faster decode, but less precise)
/// @param[out] status decode_status_t structure that will be filled with the status of various decoding steps
/// @return True if the decoding was successful, false otherwise (check status for details)
bool ft8_decode(const waterfall_t* power, const candidate_t* cand, message_t* message, int max_iterations, decode_status_t* status);
#ifdef __cplusplus
}
#endif
/// @param[in] power Waterfall data collected during message slot
/// @param[in] cand Candidate to decode
/// @param[in] noise_avg noise average from previous function
int calc_snr(const waterfall_t *power, const candidate_t *cand, int noise_avg);
#endif // _INCLUDE_DECODE_H_

Wyświetl plik

@ -1,346 +0,0 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <stdbool.h>
#include "unpack.h"
#include "ldpc.h"
#include "decode.h"
#include "constants.h"
#include "crc.h"
#include "../fft/kiss_fftr.h"
#include "decode_ft8.h"
#include "../util/rx_ft8.h"
#include "pico/multicore.h"
//#include "hardware/timer.h" //won't be needed once delay is replaced with interrupt
const float fft_norm = 2.0f / nfft;
//AA1GD-added array of structures to store info in decoded messages 8/22/2021
//setup fft freq output power, which will be accessible by both cores
//maybe should be put in decode_ft8.c?
uint8_t mag_power[num_blocks * kFreq_osr * kTime_osr * num_bins] = {0};
waterfall_t power = {
.num_blocks = num_blocks,
.num_bins = num_bins,
.time_osr = kTime_osr,
.freq_osr = kFreq_osr,
.mag = mag_power};
volatile int offset = 0;
//volatile float max_mag = -120.0f;
kiss_fftr_cfg fft_cfg;
float window[nfft]; //I wonder if static will work here
float hann_i(int i, int N)
{
float x = sinf((float)3.1416 * i / N); //replaced M_PI with a float
return x * x;
}
void make_window(void){
const int len_window = (int) (1.8f * block_size); // hand-picked and optimized
for (int i = 0; i < nfft; ++i)
{
window[i] = (i < len_window) ? hann_i(i, len_window) : 0;
}
}
static float max2(float a, float b)
{
return (a >= b) ? a : b;
}
// Compute FFT magnitudes (log power) for each timeslot in the signal
void inc_extract_power(int16_t signal[])
{
// Loop over two possible time offsets (0 and block_size/2)
for (int time_sub = 0; time_sub < power.time_osr; ++time_sub)
{
kiss_fft_scalar timedata[nfft];
kiss_fft_cpx freqdata[nfft / 2 + 1];
float mag_db[nfft / 2 + 1];
//printf("same sample in decode: %d\n", signal[420]); //this works
// Extract windowed signal block
for (int pos = 0; pos < nfft; ++pos)
{
//maybe I just need to convert back to a float here? added a divide by 32768.0 Sept 19 2021
//Couldn't get kiss_fft to work with int16_t. Dividing by 2048 as ADC gives 12 bit readings Oct. 16 2021
//changing window changed... something. Trying without window (just 1)
//timedata[pos] = window[pos] * signal[(time_sub * subblock_size) + pos] / 2048.0f;
timedata[pos] = signal[(time_sub * subblock_size) + pos] / 2048.0f;
}
//printf("right before kiss_fftr\n");
kiss_fftr(fft_cfg, timedata, freqdata);
//printf("right after kiss_fftr\n");
// Compute log magnitude in decibels
for (int idx_bin = 0; idx_bin < nfft / 2 + 1; ++idx_bin)
{
float mag2 = (freqdata[idx_bin].i * freqdata[idx_bin].i) + (freqdata[idx_bin].r * freqdata[idx_bin].r);
mag_db[idx_bin] = 10.0f * log10f(1E-12f + mag2 * fft_norm * fft_norm);
}
//printf("computed log magnitude\n");
// Loop over two possible frequency bin offsets (for averaging)
for (int freq_sub = 0; freq_sub < power.freq_osr; ++freq_sub)
{
for (int pos = 0; pos < power.num_bins; ++pos)
{
//printf("ith loop of pos is: %d\n",pos);
float db = mag_db[pos * power.freq_osr + freq_sub];
// Scale decibels to unsigned 8-bit range and clamp the value
// Range 0-240 covers -120..0 dB in 0.5 dB steps
int scaled = (int)(2 * db + 240);
power.mag[offset] = (scaled < 0) ? 0 : ((scaled > 255) ? 255 : scaled);
power.mag[offset] = scaled;
offset++;
//printf("offset: %d\n", *offset);
}
}
}
//printf("the counter offset:%d \n",offset);
//printf("finished an extraction\n");
return;
}
// Sept. 30, 2021 this didn't work out... the pointers to power were getting too confusing and unneccesary
// Oct. 1, 2021 trying to implement again
// the signal needs to be 3/2 (1.5) times as long as nfft
// Oct. 2, 2021 got time_osr = 1 incremental decoding working
void inc_collect_power(){
size_t fft_work_size;
kiss_fftr_alloc(nfft, 0, 0, &fft_work_size);
printf("Sample rate %d Hz, %d blocks, %d bins\n", sample_rate, num_blocks, num_bins);
printf("This is size of array mag_power in bytes: %d\n", num_blocks * kFreq_osr * kTime_osr * num_bins);
void *fft_work = malloc(fft_work_size);
fft_cfg = kiss_fftr_alloc(nfft, 0, fft_work, &fft_work_size);
printf("starting incremental collection\n");
//PASS IDX_BLOCK THROUGH THE FIFO-this may help with the offset
for (uint idx_block = 0; idx_block < num_blocks; idx_block++){
collect_adc();
multicore_fifo_push_blocking(idx_block);
}
//may want to wait or get a message back from core 1 before memory is freed
busy_wait_ms(160); //waits a bit for fft to finish. May want to replace with a confirmation back from core 1
free(fft_work);
printf("done collecting power\n");
printf("resetting offset and max mag\n");
offset = 0;
return;
}
int decode_ft8(message_info message_list[])
{
// Find top candidates by Costas sync score and localize them in time and frequency
candidate_t candidate_list[kMax_candidates];
int num_candidates = find_sync(&power, kMax_candidates, candidate_list, kMin_score);
// Hash table for decoded messages (to check for duplicates)
int num_decoded = 0;
message_t decoded[kMax_decoded_messages];
message_t *decoded_hashtable[kMax_decoded_messages];
//if using calc_noise type 1 or 2 use the below two funcions. type 3 goes right before calc_snr
int noise_avg = calc_noise(&power,NULL);
printf("Noise average: %d \n", noise_avg);
// Initialize hash table pointers
for (int i = 0; i < kMax_decoded_messages; ++i)
{
decoded_hashtable[i] = NULL;
}
// Go over candidates and attempt to decode messages
for (int idx = 0; idx < num_candidates; ++idx)
{
//AA1GD added to try correctly stop program when decoded>kMax_decoded_messages
//printf("num decoded %d \n",num_decoded);
if(num_decoded >= kMax_decoded_messages){
printf("decoded more than kMax_decoded_messages\n");
printf("Decoded %d messages and force ended\n", num_decoded);
return(num_decoded);
}
const candidate_t *cand = &candidate_list[idx];
if (cand->score < kMin_score)
continue;
float freq_hz = (cand->freq_offset + (float)cand->freq_sub / kFreq_osr) * kFSK_dev;
float time_sec = (cand->time_offset + (float)cand->time_sub / kTime_osr) / kFSK_dev;
message_t message;
decode_status_t status;
if (!decode(&power, cand, &message, kLDPC_iterations, &status))
{
if (status.ldpc_errors > 0)
{
//printf("LDPC decode: %d errors\n", status.ldpc_errors);
}
else if (status.crc_calculated != status.crc_extracted)
{
//printf("CRC mismatch!\n");
}
else if (status.unpack_status != 0)
{
//printf("Error while unpacking!\n");
}
continue;
}
//printf("Checking hash table for %4.1fs / %4.1fHz [%d]...\n", time_sec, freq_hz, cand->score);
int idx_hash = message.hash % kMax_decoded_messages;
bool found_empty_slot = false;
bool found_duplicate = false;
do
{
if (decoded_hashtable[idx_hash] == NULL)
{
//printf("Found an empty slot\n");
found_empty_slot = true;
}
else if ((decoded_hashtable[idx_hash]->hash == message.hash) && (0 == strcmp(decoded_hashtable[idx_hash]->text, message.text)))
{
//printf("Found a duplicate [%s]\n", message.text);
found_duplicate = true;
}
else
{
//printf("Hash table clash!\n");
// Move on to check the next entry in hash table
idx_hash = (idx_hash + 1) % kMax_decoded_messages;
}
} while (!found_empty_slot && !found_duplicate);
if (found_empty_slot)
{
// Fill the empty hashtable slot
memcpy(&decoded[idx_hash], &message, sizeof(message));
decoded_hashtable[idx_hash] = &decoded[idx_hash];
int snr = calc_snr(&power,cand,noise_avg);
//printf("%x %3d %+4.2f %4d ~ %s\n", num_decoded, cand->score, time_sec, freq_hz, message.text);
//printf("about to ");
printf("%x %3d %+3.1f %4d ~ %s\n", num_decoded, snr, time_sec, (int) freq_hz, message.text);
//printf("estimated snr: %d \n \n", snr);
//AA1GD-add message info to the global variable message_list
//message_list[num_decoded].self_rx_snr = snr;
message_list[num_decoded].self_rx_snr = snr;
message_list[num_decoded].af_frequency = (uint16_t) freq_hz;
message_list[num_decoded].time_offset = time_sec;
strcpy(message_list[num_decoded].full_text, message.text);
++num_decoded;
}
}
printf("Decoded %d messages\n", num_decoded);
return num_decoded;
}
//need to finish this. Sept. 19 2021 also need to fix snr linear regression
//should input global struct message_info current_station to compare current station
void identify_message_types(message_info message_list[], char *my_callsign){
for (int i = 0; i < kMax_decoded_messages; i++){
if(!(message_list[i].af_frequency)){ //checks if its empty
return;
}
if (strstr(message_list[i].full_text, my_callsign)){
message_list[i].addressed_to_me = true;
}
if (strstr(message_list[i].full_text, "CQ")){
message_list[i].type_cq = true;
}
if (strstr(message_list[i].full_text, "RRR")){
message_list[i].type_RRR = true;
}
if (strstr(message_list[i].full_text, "73")){
message_list[i].type_73 = true;
}
char message_buffer[25];
strcpy(message_buffer, message_list[i].full_text);
const char delim[2] = " ";
char *first_word;
first_word = strtok(message_buffer,delim);
//printf("%d first %s\n",i, first_word);
char *second_word;
second_word = strtok(NULL,delim);
//printf("%d second %s\n",i, second_word);
char *third_word;
third_word = strtok(NULL,delim);
//printf("%d third %s\n",i, third_word);
//fourth word supports CQ extra tags like DX, POTA, QRP
char *fourth_word;
fourth_word = strtok(NULL,delim);
//printf("%d fourth %s\n",i, fourth_word);
if (strlen(second_word) > 3 && !fourth_word){
strcpy(message_list[i].station_callsign, second_word);
} else if(strlen(third_word) > 3 && fourth_word){
strcpy(message_list[i].station_callsign, third_word);
}
if(!third_word){
third_word = "N/A";
}
if (message_list[i].type_cq){
if(!fourth_word){
strcpy(message_list[i].grid_square, third_word);
} else{
strcpy(message_list[i].grid_square, fourth_word);
}
}
else if (strlen(third_word)==4 && !strchr(third_word, '-') && !strchr(third_word, '+') && !strcmp(third_word,"RR73")){
message_list[i].type_grid = true;
strcpy(message_list[i].grid_square, third_word);
}
else if (strchr(third_word, '-') || strchr(third_word, '+')){
if (strchr(third_word, 'R')){
message_list[i].type_Rsnr = true;
}
else {message_list[i].type_snr = true;}
strcpy(message_list[i].snr_report, third_word);
}
//printf("station callsign: %s grid square: %s snr report: %s\n\n",message_list[i].station_callsign, message_list[i].grid_square, message_list[i].snr_report);
}
return;
}

607
ft8/decode_ft8.cpp 100644
Wyświetl plik

@ -0,0 +1,607 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <stdbool.h>
#include "unpack.h"
#include "ldpc.h"
#include "decode.h"
#include "constants.h"
#include "crc.h"
#include "common/common.h"
#include "common/wave.h"
#include "common/debug.h"
#include "../fft/kiss_fftr.h"
#define LOG_LEVEL LOG_INFO
#include "decode_ft8.h"
#include "../util/rx_ft8.h"
#ifndef PC
#include "pico/multicore.h"
#endif
//#include "hardware/timer.h" //won't be needed once delay is replaced with interrupt
const float fft_norm = 2.0f / nfft;
// AA1GD - added array of structures to store info in decoded messages 8/22/2021
// Setup fft freq output power, which will be accessible by both cores
uint8_t mag_power[num_blocks * kFreq_osr * kTime_osr * num_bins] = {0};
waterfall_t power = {
.num_blocks = num_blocks,
.num_bins = num_bins,
.time_osr = kTime_osr,
.freq_osr = kFreq_osr,
.mag = mag_power,
.block_stride = (kTime_osr * kFreq_osr * num_bins),
.protocol = PROTO_FT8
};
volatile int offset = 0;
kiss_fftr_cfg fft_cfg;
float window[nfft]; // I wonder if static will work here - AA1GD
void usage()
{
fprintf(stderr, "Decode a 15-second (or slighly shorter) WAV file.\n");
}
static float hann_i(int i, int N)
{
float x = sinf((float)M_PI * i / N);
return x * x;
}
void make_window(void) {
const int len_window = (int) (1.8f * block_size); // hand-picked and optimized
for (int i = 0; i < nfft; ++i)
{
window[i] = (i < len_window) ? hann_i(i, len_window) : 0;
}
}
static float max2(float a, float b)
{
return (a >= b) ? a : b;
}
// Compute FFT magnitudes (log power) for each timeslot in the signal
void inc_extract_power(int16_t signal[])
{
// Loop over two possible time offsets (0 and block_size/2)
for (int time_sub = 0; time_sub < power.time_osr; ++time_sub)
{
kiss_fft_scalar timedata[nfft];
kiss_fft_cpx freqdata[nfft / 2 + 1];
float mag_db[nfft / 2 + 1];
// Extract windowed signal block
for (int pos = 0; pos < nfft; ++pos)
{
// Maybe I just need to convert back to a float here? added a divide by 32768.0 Sept 19 2021
// Couldn't get kiss_fft to work with int16_t. Dividing by 2048 as ADC gives 12 bit readings Oct. 16 2021
// Changing window changed... something. Trying without window (just 1)
// timedata[pos] = window[pos] * signal[(time_sub * subblock_size) + pos] / 2048.0f;
timedata[pos] = signal[(time_sub * subblock_size) + pos] / 2048.0f;
}
kiss_fftr(fft_cfg, timedata, freqdata);
// Compute log magnitude in decibels
for (int idx_bin = 0; idx_bin < nfft / 2 + 1; ++idx_bin)
{
float mag2 = (freqdata[idx_bin].i * freqdata[idx_bin].i) + (freqdata[idx_bin].r * freqdata[idx_bin].r);
mag_db[idx_bin] = 10.0f * log10f(1E-12f + mag2 * fft_norm * fft_norm);
}
// Printf("Compute log magnitude\n");
// Loop over two possible frequency bin offsets (for averaging)
for (int freq_sub = 0; freq_sub < power.freq_osr; ++freq_sub)
{
for (int pos = 0; pos < power.num_bins; ++pos)
{
float db = mag_db[pos * power.freq_osr + freq_sub];
// Scale decibels to unsigned 8-bit range and clamp the value
// Range 0-240 covers -120..0 dB in 0.5 dB steps
int scaled = (int)(2 * db + 240);
power.mag[offset] = (scaled < 0) ? 0 : ((scaled > 255) ? 255 : scaled);
power.mag[offset] = scaled;
offset++;
}
}
}
return;
}
// The signal needs to be 3/2 (1.5) times as long as nfft
// Oct. 2, 2021 got time_osr = 1 incremental decoding working
void inc_collect_power() {
size_t fft_work_size;
kiss_fftr_alloc(nfft, 0, 0, &fft_work_size);
printf("Sample rate %d Hz, %d blocks, %d bins\n", sample_rate_, num_blocks, num_bins);
printf("This is size of array mag_power in bytes: %d\n", num_blocks * kFreq_osr * kTime_osr * num_bins);
void *fft_work = malloc(fft_work_size);
fft_cfg = kiss_fftr_alloc(nfft, 0, fft_work, &fft_work_size);
printf("starting incremental collection\n");
// PASS IDX_BLOCK THROUGH THE FIFO - this may help with the offset
for (uint idx_block = 0; idx_block < num_blocks; idx_block++) {
#ifndef PC
collect_adc();
multicore_fifo_push_blocking(idx_block);
#endif
}
// May want to wait or get a message back from core 1 before memory is freed
#ifndef PC
busy_wait_ms(160); // Waits a bit for the FFT to finish. May want to replace with a confirmation back from core 1.
#endif
free(fft_work);
printf("done collecting power\n");
printf("resetting offset and max mag\n");
offset = 0;
return;
}
// This code is borrowed from ft8_lib's decode_ft8.c file
int decode_ft8(message_info message_list[])
{
// Find top candidates by Costas sync score and localize them in time and frequency
candidate_t candidate_list[kMax_candidates];
int num_candidates = ft8_find_sync(&power, kMax_candidates, candidate_list, kMin_score);
// Hash table for decoded messages (to check for duplicates)
int num_decoded = 0;
message_t decoded[kMax_decoded_messages];
message_t *decoded_hashtable[kMax_decoded_messages];
// Initialize hash table pointers
for (int i = 0; i < kMax_decoded_messages; ++i)
{
decoded_hashtable[i] = NULL;
}
// Go over candidates and attempt to decode messages
for (int idx = 0; idx < num_candidates; ++idx)
{
// AA1GD added to try correctly stop program when decoded>kMax_decoded_messages
// printf("num decoded %d \n",num_decoded);
if (num_decoded >= kMax_decoded_messages) {
printf("decoded more than kMax_decoded_messages\n");
printf("Decoded %d messages and force ended\n", num_decoded);
return (num_decoded);
}
const candidate_t *cand = &candidate_list[idx];
if (cand->score < kMin_score)
continue;
float freq_hz = (cand->freq_offset + (float)cand->freq_sub / kFreq_osr) * kFSK_dev;
float time_sec = (cand->time_offset + (float)cand->time_sub / kTime_osr) / kFSK_dev;
message_t message;
decode_status_t status;
if (!ft8_decode(&power, cand, &message, kLDPC_iterations, &status))
{
if (status.ldpc_errors > 0)
{
// printf("LDPC decode: %d errors\n", status.ldpc_errors);
}
else if (status.crc_calculated != status.crc_extracted)
{
// printf("CRC mismatch!\n");
}
else if (status.unpack_status != 0)
{
// printf("Error while unpacking!\n");
}
continue;
}
// printf("Checking hash table for %4.1fs / %4.1fHz [%d]...\n", time_sec, freq_hz, cand->score);
int idx_hash = message.hash % kMax_decoded_messages;
bool found_empty_slot = false;
bool found_duplicate = false;
do
{
if (decoded_hashtable[idx_hash] == NULL)
{
// printf("Found an empty slot\n");
found_empty_slot = true;
}
else if ((decoded_hashtable[idx_hash]->hash == message.hash) && (0 == strcmp(decoded_hashtable[idx_hash]->text, message.text)))
{
// printf("Found a duplicate [%s]\n", message.text);
found_duplicate = true;
}
else
{
// printf("Hash table clash!\n");
// Move on to check the next entry in hash table
idx_hash = (idx_hash + 1) % kMax_decoded_messages;
}
} while (!found_empty_slot && !found_duplicate);
if (found_empty_slot)
{
// Fill the empty hashtable slot
memcpy(&decoded[idx_hash], &message, sizeof(message));
decoded_hashtable[idx_hash] = &decoded[idx_hash];
int snr = cand->score;
printf("%x %3d %+3.1f %4d ~ %s\n", num_decoded, snr, time_sec, (int) freq_hz, message.text);
// AA1GD - add message info to the global variable message_list
message_list[num_decoded].self_rx_snr = snr;
message_list[num_decoded].af_frequency = (uint16_t) freq_hz;
message_list[num_decoded].time_offset = time_sec;
strcpy(message_list[num_decoded].full_text, message.text);
++num_decoded;
}
}
printf("Decoded %d messages\n", num_decoded);
return num_decoded;
}
static float hamming_i(int i, int N)
{
const float a0 = (float)25 / 46;
const float a1 = 1 - a0;
float x1 = cosf(2 * (float)M_PI * i / N);
return a0 - a1 * x1;
}
static float blackman_i(int i, int N)
{
const float alpha = 0.16f; // or 2860/18608
const float a0 = (1 - alpha) / 2;
const float a1 = 1.0f / 2;
const float a2 = alpha / 2;
float x1 = cosf(2 * (float)M_PI * i / N);
float x2 = 2 * x1 * x1 - 1; // Use double angle formula
return a0 - a1 * x1 + a2 * x2;
}
void waterfall_init(waterfall_t* me, int max_blocks, int num_bins, int time_osr, int freq_osr)
{
size_t mag_size = max_blocks * time_osr * freq_osr * num_bins * sizeof(me->mag[0]);
me->max_blocks = max_blocks;
me->num_blocks = 0;
me->num_bins = num_bins;
me->time_osr = time_osr;
me->freq_osr = freq_osr;
me->block_stride = (time_osr * freq_osr * num_bins);
me->mag = (uint8_t *)malloc(mag_size);
LOG(LOG_DEBUG, "Waterfall size = %zu\n", mag_size);
}
void waterfall_free(waterfall_t* me)
{
free(me->mag);
}
/// Configuration options for FT4/FT8 monitor
typedef struct
{
float f_min; ///< Lower frequency bound for analysis
float f_max; ///< Upper frequency bound for analysis
int sample_rate; ///< Sample rate in Hertz
int time_osr; ///< Number of time subdivisions
int freq_osr; ///< Number of frequency subdivisions
ftx_protocol_t protocol; ///< Protocol: FT4 or FT8
} monitor_config_t;
/// FT4/FT8 monitor object that manages DSP processing of incoming audio data
/// and prepares a waterfall object
typedef struct
{
float symbol_period; ///< FT4/FT8 symbol period in seconds
int block_size; ///< Number of samples per symbol (block)
int subblock_size; ///< Analysis shift size (number of samples)
int nfft; ///< FFT size
float fft_norm; ///< FFT normalization factor
float* window; ///< Window function for STFT analysis (nfft samples)
float* last_frame; ///< Current STFT analysis frame (nfft samples)
waterfall_t wf; ///< Waterfall object
float max_mag; ///< Maximum detected magnitude (debug stats)
// KISS FFT housekeeping variables
void* fft_work; ///< Work area required by Kiss FFT
kiss_fftr_cfg fft_cfg; ///< Kiss FFT housekeeping object
} monitor_t;
void monitor_init(monitor_t* me, const monitor_config_t* cfg)
{
float slot_time = (cfg->protocol == PROTO_FT4) ? FT4_SLOT_TIME : FT8_SLOT_TIME;
float symbol_period = (cfg->protocol == PROTO_FT4) ? FT4_SYMBOL_PERIOD : FT8_SYMBOL_PERIOD;
// Compute DSP parameters that depend on the sample rate
me->block_size = (int)(cfg->sample_rate * symbol_period); // samples corresponding to one FSK symbol
me->subblock_size = me->block_size / cfg->time_osr;
me->nfft = me->block_size * cfg->freq_osr;
me->fft_norm = 2.0f / me->nfft;
// const int len_window = 1.8f * me->block_size; // hand-picked and optimized
me->window = (float *)malloc(me->nfft * sizeof(me->window[0]));
for (int i = 0; i < me->nfft; ++i)
{
// window[i] = 1;
me->window[i] = hann_i(i, me->nfft);
// me->window[i] = blackman_i(i, me->nfft);
// me->window[i] = hamming_i(i, me->nfft);
// me->window[i] = (i < len_window) ? hann_i(i, len_window) : 0;
}
me->last_frame = (float *)malloc(me->nfft * sizeof(me->last_frame[0]));
size_t fft_work_size;
kiss_fftr_alloc(me->nfft, 0, 0, &fft_work_size);
LOG(LOG_INFO, "Block size = %d\n", me->block_size);
LOG(LOG_INFO, "Subblock size = %d\n", me->subblock_size);
LOG(LOG_INFO, "N_FFT = %d\n", me->nfft);
LOG(LOG_DEBUG, "FFT work area = %zu\n", fft_work_size);
me->fft_work = malloc(fft_work_size);
me->fft_cfg = kiss_fftr_alloc(me->nfft, 0, me->fft_work, &fft_work_size);
const int max_blocks = (int)(slot_time / symbol_period);
const int num_bins = (int)(cfg->sample_rate * symbol_period / 2);
waterfall_init(&me->wf, max_blocks, num_bins, cfg->time_osr, cfg->freq_osr);
me->wf.protocol = cfg->protocol;
me->symbol_period = symbol_period;
me->max_mag = -120.0f;
}
void monitor_free(monitor_t* me)
{
waterfall_free(&me->wf);
free(me->fft_work);
free(me->last_frame);
free(me->window);
}
// Compute FFT magnitudes (log wf) for a frame in the signal and update waterfall data
void monitor_process(monitor_t* me, const float* frame)
{
// Check if we can still store more waterfall data
if (me->wf.num_blocks >= me->wf.max_blocks)
return;
int offset = me->wf.num_blocks * me->wf.block_stride;
int frame_pos = 0;
// Loop over block subdivisions
for (int time_sub = 0; time_sub < me->wf.time_osr; ++time_sub)
{
kiss_fft_scalar timedata[me->nfft];
kiss_fft_cpx freqdata[me->nfft / 2 + 1];
// Shift the new data into analysis frame
for (int pos = 0; pos < me->nfft - me->subblock_size; ++pos)
{
me->last_frame[pos] = me->last_frame[pos + me->subblock_size];
}
for (int pos = me->nfft - me->subblock_size; pos < me->nfft; ++pos)
{
me->last_frame[pos] = frame[frame_pos];
++frame_pos;
}
// Compute windowed analysis frame
for (int pos = 0; pos < me->nfft; ++pos)
{
timedata[pos] = me->fft_norm * me->window[pos] * me->last_frame[pos];
}
kiss_fftr(me->fft_cfg, timedata, freqdata);
// Loop over two possible frequency bin offsets (for averaging)
for (int freq_sub = 0; freq_sub < me->wf.freq_osr; ++freq_sub)
{
for (int bin = 0; bin < me->wf.num_bins; ++bin)
{
int src_bin = (bin * me->wf.freq_osr) + freq_sub;
float mag2 = (freqdata[src_bin].i * freqdata[src_bin].i) + (freqdata[src_bin].r * freqdata[src_bin].r);
float db = 10.0f * log10f(1E-12f + mag2);
// Scale decibels to unsigned 8-bit range and clamp the value
// Range 0-240 covers -120..0 dB in 0.5 dB steps
int scaled = (int)(2 * db + 240);
me->wf.mag[offset] = (scaled < 0) ? 0 : ((scaled > 255) ? 255 : scaled);
++offset;
if (db > me->max_mag)
me->max_mag = db;
}
}
}
++me->wf.num_blocks;
}
void monitor_reset(monitor_t* me)
{
me->wf.num_blocks = 0;
me->max_mag = 0;
}
#ifdef PC
int main(int argc, char** argv)
{
// Accepted arguments
const char* wav_path = NULL;
bool is_ft8 = true;
// Parse arguments one by one
int arg_idx = 1;
while (arg_idx < argc)
{
// Check if the current argument is an option (-xxx)
if (argv[arg_idx][0] == '-')
{
// Check agaist valid options
if (0 == strcmp(argv[arg_idx], "-ft4"))
{
is_ft8 = false;
}
else
{
usage();
return -1;
}
}
else
{
if (wav_path == NULL)
{
wav_path = argv[arg_idx];
}
else
{
usage();
return -1;
}
}
++arg_idx;
}
// Check if all mandatory arguments have been received
if (wav_path == NULL)
{
usage();
return -1;
}
int sample_rate = 12000;
int num_samples = 15 * sample_rate;
float signal[num_samples];
int rc = load_wav(signal, &num_samples, &sample_rate, wav_path);
if (rc < 0)
{
return -1;
}
LOG(LOG_INFO, "Sample rate %d Hz, %d samples, %.3f seconds\n", sample_rate, num_samples, (double)num_samples / sample_rate);
// Compute FFT over the whole signal and store it
monitor_t mon;
monitor_config_t mon_cfg = {
.f_min = 100,
.f_max = 3000,
.sample_rate = sample_rate,
.time_osr = kTime_osr,
.freq_osr = kFreq_osr,
.protocol = is_ft8 ? PROTO_FT8 : PROTO_FT4
};
monitor_init(&mon, &mon_cfg);
LOG(LOG_DEBUG, "Waterfall allocated %d symbols\n", mon.wf.max_blocks);
for (int frame_pos = 0; frame_pos + mon.block_size <= num_samples; frame_pos += mon.block_size)
{
// Process the waveform data frame by frame - you could have a live loop here with data from an audio device
monitor_process(&mon, signal + frame_pos);
}
LOG(LOG_DEBUG, "Waterfall accumulated %d symbols\n", mon.wf.num_blocks);
LOG(LOG_INFO, "Max magnitude: %.1f dB\n", mon.max_mag);
// Find top candidates by Costas sync score and localize them in time and frequency
candidate_t candidate_list[kMax_candidates];
int num_candidates = ft8_find_sync(&mon.wf, kMax_candidates, candidate_list, kMin_score);
// Hash table for decoded messages (to check for duplicates)
int num_decoded = 0;
message_t decoded[kMax_decoded_messages];
message_t* decoded_hashtable[kMax_decoded_messages];
// Initialize hash table pointers
for (int i = 0; i < kMax_decoded_messages; ++i)
{
decoded_hashtable[i] = NULL;
}
// Go over candidates and attempt to decode messages
for (int idx = 0; idx < num_candidates; ++idx)
{
const candidate_t* cand = &candidate_list[idx];
if (cand->score < kMin_score)
continue;
float freq_hz = (cand->freq_offset + (float)cand->freq_sub / mon.wf.freq_osr) / mon.symbol_period;
float time_sec = (cand->time_offset + (float)cand->time_sub / mon.wf.time_osr) * mon.symbol_period;
message_t message;
decode_status_t status;
if (!ft8_decode(&mon.wf, cand, &message, kLDPC_iterations, &status))
{
// printf("000000 %3d %+4.2f %4.0f ~ ---\n", cand->score, time_sec, freq_hz);
if (status.ldpc_errors > 0)
{
LOG(LOG_DEBUG, "LDPC decode: %d errors\n", status.ldpc_errors);
}
else if (status.crc_calculated != status.crc_extracted)
{
LOG(LOG_DEBUG, "CRC mismatch!\n");
}
else if (status.unpack_status != 0)
{
LOG(LOG_DEBUG, "Error while unpacking!\n");
}
continue;
}
LOG(LOG_DEBUG, "Checking hash table for %4.1fs / %4.1fHz [%d]...\n", time_sec, freq_hz, cand->score);
int idx_hash = message.hash % kMax_decoded_messages;
bool found_empty_slot = false;
bool found_duplicate = false;
do
{
if (decoded_hashtable[idx_hash] == NULL)
{
LOG(LOG_DEBUG, "Found an empty slot\n");
found_empty_slot = true;
}
else if ((decoded_hashtable[idx_hash]->hash == message.hash) && (0 == strcmp(decoded_hashtable[idx_hash]->text, message.text)))
{
LOG(LOG_DEBUG, "Found a duplicate [%s]\n", message.text);
found_duplicate = true;
}
else
{
LOG(LOG_DEBUG, "Hash table clash!\n");
// Move on to check the next entry in hash table
idx_hash = (idx_hash + 1) % kMax_decoded_messages;
}
} while (!found_empty_slot && !found_duplicate);
if (found_empty_slot)
{
// Fill the empty hashtable slot
memcpy(&decoded[idx_hash], &message, sizeof(message));
decoded_hashtable[idx_hash] = &decoded[idx_hash];
++num_decoded;
// Fake WSJT-X-like output for now
int snr = 0; // TODO: compute SNR
printf("000000 %3d %+4.2f %4.0f ~ %s\n", cand->score, time_sec, freq_hz, message.text);
}
}
LOG(LOG_INFO, "Decoded %d messages\n", num_decoded);
monitor_free(&mon);
return 0;
}
#endif

Wyświetl plik

@ -1,63 +1,59 @@
#pragma once
#include "decode.h"
#include "../fft/kiss_fftr.h"
#define kMin_score 10 // Minimum sync score threshold for candidates
#define kMax_candidates 30 // Original was 120
#define kLDPC_iterations 10 // Original was 20
#define kMax_decoded_messages 14 //was 50, change to 14 since there's 14 buttons on the 4x4 membrane keyboard
#define kFreq_osr 1 //both default 2
#define kTime_osr 1
#define kFSK_dev 6.25 // tone deviation in Hz and symbol rate
#define sample_rate 6000
#define decoding_time 12.8
enum {num_bins = (int)(sample_rate / (2 * kFSK_dev))}; // number bins of FSK tone width that the spectrum can be divided into
enum {block_size = (int)(sample_rate / kFSK_dev)}; // samples corresponding to one FSK symbol
enum {subblock_size = block_size / kTime_osr};
enum {nfft = block_size * kFreq_osr};
enum {num_blocks = (int) (decoding_time * kFSK_dev)};
enum {num_samples_processed = nfft * (1 + kTime_osr) / 2};
//extern uint8_t mag_power[num_blocks * kFreq_osr * kTime_osr * num_bins];
extern waterfall_t power;
typedef struct
{
int8_t self_rx_snr; //(should be -24 to 30 db)
uint16_t af_frequency;
float time_offset;
char full_text[19]; // was 25
bool addressed_to_me;
bool type_cq;
bool type_grid;
bool type_snr;
bool type_Rsnr;
bool type_RRR;
bool type_73;
char station_callsign[7]; //do I need 7 instead of 6? For some reason my testing files have some 7-character callsigns
char grid_square[4];
char snr_report[4];
}message_info;
float hann_i(int i, int N);
void make_window(void);
static float max2(float a, float b);
// Compute FFT magnitudes (log power) for each timeslot in the signal
void inc_extract_power(int16_t signal[]);
void inc_collect_power();
int decode_ft8(message_info message_list[]);
void identify_message_types(message_info message_list[], char *my_callsign);
#pragma once
#include "decode.h"
#include "../fft/kiss_fftr.h"
#define kMin_score 10 // Minimum sync score threshold for candidates
#define kMax_candidates 30 // Original was 120
#define kLDPC_iterations 10 // Original was 20
#define kMax_decoded_messages 14 // Was 50, change to 14 since there's 14 buttons on the 4x4 membrane keyboard
#define kFreq_osr 1 // both default 2
#define kTime_osr 1
#define kFSK_dev 6.25 // tone deviation in Hz and symbol rate
#define sample_rate_ 6000
#define decoding_time 12.8
enum {num_bins = (int)(sample_rate_ / (2 * kFSK_dev))}; // number bins of FSK tone width that the spectrum can be divided into
enum {block_size = (int)(sample_rate_ / kFSK_dev)}; // samples corresponding to one FSK symbol
enum {subblock_size = block_size / kTime_osr};
enum {nfft = block_size * kFreq_osr};
enum {num_blocks = (int) (decoding_time * kFSK_dev)};
enum {num_samples_processed = nfft * (1 + kTime_osr) / 2};
extern waterfall_t power;
typedef struct
{
int8_t self_rx_snr; //(should be -24 to 30 db)
uint16_t af_frequency;
float time_offset;
char full_text[19]; // was 25
bool addressed_to_me;
bool type_cq;
bool type_grid;
bool type_snr;
bool type_Rsnr;
bool type_RRR;
bool type_73;
char station_callsign[7]; // Do I need 7 instead of 6? For some reason my testing files have some 7-character callsigns
char grid_square[4];
char snr_report[4];
} message_info;
void make_window(void);
static float max2(float a, float b);
// Compute FFT magnitudes (log power) for each timeslot in the signal
void inc_extract_power(int16_t signal[]);
void inc_collect_power();
int decode_ft8(message_info message_list[]);
void identify_message_types(message_info message_list[], char *my_callsign);

Wyświetl plik

@ -5,7 +5,7 @@
#include <stdio.h>
// Returns 1 if an odd number of bits are set in x, zero otherwise
uint8_t parity8(uint8_t x)
static uint8_t parity8(uint8_t x)
{
x ^= x >> 4; // a b c d ae bf cg dh
x ^= x >> 2; // a b ac bd cae dbf aecg bfdh
@ -13,36 +13,36 @@ uint8_t parity8(uint8_t x)
return x % 2; // modulo 2
}
// Encode a 91-bit message and return a 174-bit codeword.
// Encode via LDPC a 91-bit message and return a 174-bit codeword.
// The generator matrix has dimensions (87,87).
// The code is a (174,91) regular LDPC code with column weight 3.
// Arguments:
// [IN] message - array of 91 bits stored as 12 bytes (MSB first)
// [OUT] codeword - array of 174 bits stored as 22 bytes (MSB first)
void encode174(const uint8_t *message, uint8_t *codeword)
static void encode174(const uint8_t* message, uint8_t* codeword)
{
// This implementation accesses the generator bits straight from the packed binary representation in kFT8_LDPC_generator
// This implementation accesses the generator bits straight from the packed binary representation in kFTX_LDPC_generator
// Fill the codeword with message and zeros, as we will only update binary ones later
for (int j = 0; j < FT8_LDPC_N_BYTES; ++j)
for (int j = 0; j < FTX_LDPC_N_BYTES; ++j)
{
codeword[j] = (j < FT8_LDPC_K_BYTES) ? message[j] : 0;
codeword[j] = (j < FTX_LDPC_K_BYTES) ? message[j] : 0;
}
// Compute the byte index and bit mask for the first checksum bit
uint8_t col_mask = (0x80u >> (FT8_LDPC_K % 8u)); // bitmask of current byte
uint8_t col_idx = FT8_LDPC_K_BYTES - 1; // index into byte array
uint8_t col_mask = (0x80u >> (FTX_LDPC_K % 8u)); // bitmask of current byte
uint8_t col_idx = FTX_LDPC_K_BYTES - 1; // index into byte array
// Compute the LDPC checksum bits and store them in codeword
for (int i = 0; i < FT8_LDPC_M; ++i)
for (int i = 0; i < FTX_LDPC_M; ++i)
{
// Fast implementation of bitwise multiplication and parity checking
// Normally nsum would contain the result of dot product between message and kFT8_LDPC_generator[i],
// Normally nsum would contain the result of dot product between message and kFTX_LDPC_generator[i],
// but we only compute the sum modulo 2.
uint8_t nsum = 0;
for (int j = 0; j < FT8_LDPC_K_BYTES; ++j)
for (int j = 0; j < FTX_LDPC_K_BYTES; ++j)
{
uint8_t bits = message[j] & kFT8_LDPC_generator[i][j]; // bitwise AND (bitwise multiplication)
uint8_t bits = message[j] & kFTX_LDPC_generator[i][j]; // bitwise AND (bitwise multiplication)
nsum ^= parity8(bits); // bitwise XOR (addition modulo 2)
}
@ -62,15 +62,15 @@ void encode174(const uint8_t *message, uint8_t *codeword)
}
}
void genft8(const uint8_t *payload, uint8_t *tones)
void ft8_encode(const uint8_t* payload, uint8_t* tones)
{
uint8_t a91[12]; // Store 77 bits of payload + 14 bits CRC
uint8_t a91[FTX_LDPC_K_BYTES]; // Store 77 bits of payload + 14 bits CRC
// Compute and add CRC at the end of the message
// a91 contains 77 bits of payload + 14 bits of CRC
add_crc(payload, a91);
ftx_add_crc(payload, a91);
uint8_t codeword[22];
uint8_t codeword[FTX_LDPC_N_BYTES];
encode174(a91, codeword);
// Message structure: S7 D29 S7 D29 S7
@ -122,4 +122,74 @@ void genft8(const uint8_t *payload, uint8_t *tones)
tones[i_tone] = kFT8_Gray_map[bits3];
}
}
}
}
void ft4_encode(const uint8_t* payload, uint8_t* tones)
{
uint8_t a91[FTX_LDPC_K_BYTES]; // Store 77 bits of payload + 14 bits CRC
uint8_t payload_xor[10]; // Encoded payload data
// '[..] for FT4 only, in order to avoid transmitting a long string of zeros when sending CQ messages,
// the assembled 77-bit message is bitwise exclusive-ORed with [a] pseudorandom sequence before computing the CRC and FEC parity bits'
for (int i = 0; i < 10; ++i)
{
payload_xor[i] = payload[i] ^ kFT4_XOR_sequence[i];
}
// Compute and add CRC at the end of the message
// a91 contains 77 bits of payload + 14 bits of CRC
ftx_add_crc(payload_xor, a91);
uint8_t codeword[FTX_LDPC_N_BYTES];
encode174(a91, codeword); // 91 bits -> 174 bits
// Message structure: R S4_1 D29 S4_2 D29 S4_3 D29 S4_4 R
// Total symbols: 105 (FT4_NN)
uint8_t mask = 0x80u; // Mask to extract 1 bit from codeword
int i_byte = 0; // Index of the current byte of the codeword
for (int i_tone = 0; i_tone < FT4_NN; ++i_tone)
{
if ((i_tone == 0) || (i_tone == 104))
{
tones[i_tone] = 0; // R (ramp) symbol
}
else if ((i_tone >= 1) && (i_tone < 5))
{
tones[i_tone] = kFT4_Costas_pattern[0][i_tone - 1];
}
else if ((i_tone >= 34) && (i_tone < 38))
{
tones[i_tone] = kFT4_Costas_pattern[1][i_tone - 34];
}
else if ((i_tone >= 67) && (i_tone < 71))
{
tones[i_tone] = kFT4_Costas_pattern[2][i_tone - 67];
}
else if ((i_tone >= 100) && (i_tone < 104))
{
tones[i_tone] = kFT4_Costas_pattern[3][i_tone - 100];
}
else
{
// Extract 2 bits from codeword at i-th position
uint8_t bits2 = 0;
if (codeword[i_byte] & mask)
bits2 |= 2;
if (0 == (mask >>= 1))
{
mask = 0x80u;
i_byte++;
}
if (codeword[i_byte] & mask)
bits2 |= 1;
if (0 == (mask >>= 1))
{
mask = 0x80u;
i_byte++;
}
tones[i_tone] = kFT4_Gray_map[bits2];
}
}
}

Wyświetl plik

@ -3,25 +3,39 @@
#include <stdint.h>
// typedef struct
// {
// uint8_t tones[FT8_NN];
// // for waveform readout:
// int n_spsym; // Number of waveform samples per symbol
// float *pulse; // [3 * n_spsym]
// int idx_symbol; // Index of the current symbol
// float f0; // Base frequency, Hertz
// float signal_rate; // Waveform sample rate, Hertz
// } encoder_t;
#ifdef __cplusplus
extern "C"
{
#endif
// void encoder_init(float signal_rate, float *pulse_buffer);
// void encoder_set_f0(float f0);
// void encoder_process(const message_t *message); // in: message
// void encoder_generate(float *block); // out: block of waveforms
// typedef struct
// {
// uint8_t tones[FT8_NN];
// // for waveform readout:
// int n_spsym; // Number of waveform samples per symbol
// float *pulse; // [3 * n_spsym]
// int idx_symbol; // Index of the current symbol
// float f0; // Base frequency, Hertz
// float signal_rate; // Waveform sample rate, Hertz
// } encoder_t;
/// Generate FT8 tone sequence from payload data
/// @param[in] payload - 10 byte array consisting of 77 bit payload
/// @param[out] tones - array of FT8_NN (79) bytes to store the generated tones (encoded as 0..7)
void genft8(const uint8_t *payload, uint8_t *tones);
// void encoder_init(float signal_rate, float *pulse_buffer);
// void encoder_set_f0(float f0);
// void encoder_process(const message_t *message); // in: message
// void encoder_generate(float *block); // out: block of waveforms
/// Generate FT8 tone sequence from payload data
/// @param[in] payload - 10 byte array consisting of 77 bit payload
/// @param[out] tones - array of FT8_NN (79) bytes to store the generated tones (encoded as 0..7)
void ft8_encode(const uint8_t* payload, uint8_t* tones);
/// Generate FT4 tone sequence from payload data
/// @param[in] payload - 10 byte array consisting of 77 bit payload
/// @param[out] tones - array of FT4_NN (105) bytes to store the generated tones (encoded as 0..3)
void ft4_encode(const uint8_t* payload, uint8_t* tones);
#ifdef __cplusplus
}
#endif
#endif // _INCLUDE_ENCODE_H_

Wyświetl plik

@ -1,149 +0,0 @@
#include "gen_ft8.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#include "pack.h"
#include "encode.h"
#include "constants.h"
//recreated 9/20/21
//created 9/21/21
//CQ calls will always be manually generated
void manual_gen_message(char message[], message_info Station, UserSendSelection stype, char *myCall, char *myGrid){
char snr_in_string[4];
//itoa(Station.self_rx_snr, snr_in_string, 10);
sprintf(snr_in_string, "%d", Station.self_rx_snr);
if (stype.call_cq){
strcat(message, "CQ ");
strcat(message, myCall);
strcat(message, " ");
strcat(message, myGrid);
} else if(stype.send_grid){
strcat(message, Station.station_callsign);
strcat(message, " ");
strcat(message, myCall);
strcat(message, " ");
strcat(message, myGrid);
} else if(stype.send_snr){
strcat(message, Station.station_callsign);
strcat(message, " ");
strcat(message, myCall);
strcat(message, " ");
strcat(message, snr_in_string);
} else if(stype.send_Rsnr){
strcat(message, Station.station_callsign);
strcat(message, " ");
strcat(message, myCall);
strcat(message," R");
strcat(message, snr_in_string);
} else if(stype.send_RRR){
strcat(message, Station.station_callsign);
strcat(message, " ");
strcat(message, myCall);
strcat(message," RRR");
} else if(stype.send_RR73){
strcat(message, Station.station_callsign);
strcat(message, " ");
strcat(message, myCall);
strcat(message," RR73");
} else if(stype.send_73){
strcat(message, Station.station_callsign);
strcat(message, " ");
strcat(message, myCall);
strcat(message," 73");
}
}
void auto_gen_message(char message[], message_info Station, char *myCall, char *myGrid){
//should make it so if it's not addressed to you, won't respond
char snr_in_string[14];
//itoa(Station.self_rx_snr, snr_in_string, 10);
//this sprintf is making strings from numbers, but isn't making the right strings...
sprintf(snr_in_string, "%d", Station.self_rx_snr);
if (Station.type_cq){
strcat(message, Station.station_callsign);
strcat(message, " ");
strcat(message, myCall);
strcat(message, " ");
strcat(message, myGrid);
}
else if (Station.type_grid){
strcat(message, Station.station_callsign);
strcat(message, " ");
strcat(message, myCall);
strcat(message," ");
strcat(message, snr_in_string);
}
//need to distinguish between Rsnr (recieved) and snr
//if Rsnr is recieved send out an RRR or RR73
else if (Station.type_snr){
strcat(message, Station.station_callsign);
strcat(message, " ");
strcat(message, myCall);
strcat(message," R");
strcat(message, snr_in_string);
}
else if (Station.type_RRR){
strcat(message, Station.station_callsign);
strcat(message, " ");
strcat(message, myCall);
strcat(message," 73");
//set global variable send to false. after this, we're done sending
}
else if (Station.type_73){
strcat(message, Station.station_callsign);
strcat(message, " ");
strcat(message, myCall);
strcat(message," 73");
//set global variable send to false. after this, we're done sending
}
}
void generate_ft8(char message[], uint8_t tone_sequence[])
{
//int message_length = strlen(message);
// First, pack the text data into binary message
uint8_t packed[FT8_LDPC_K_BYTES];
int rc = pack77(message, packed);
if (rc < 0)
{
printf("Cannot parse message!\n");
printf("RC = %d\n", rc);
}
printf("Packed data: ");
for (int j = 0; j < 10; ++j)
{
printf("%02x ", packed[j]);
}
printf("\n");
int num_tones = FT8_NN;
// Second, encode the binary message as a sequence of FSK tones
genft8(packed, tone_sequence);
printf("FSK tones: ");
for (int j = 0; j < num_tones; ++j)
{
printf("%d", tone_sequence[j]);
}
printf("\n");
return;
}

45
ft8/gen_ft8.cpp 100644
Wyświetl plik

@ -0,0 +1,45 @@
#include "gen_ft8.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#include "pack.h"
#include "encode.h"
#include "constants.h"
void generate_ft8(char message[], uint8_t tone_sequence[])
{
// int message_length = strlen(message);
// First, pack the text data into binary message
uint8_t packed[FTX_LDPC_K_BYTES];
int rc = pack77(message, packed);
if (rc < 0)
{
printf("Cannot parse message!\n");
printf("RC = %d\n", rc);
}
printf("Packed data: ");
for (int j = 0; j < 10; ++j)
{
printf("%02x ", packed[j]);
}
printf("\n");
int num_tones = FT8_NN;
// Second, encode the binary message as a sequence of FSK tones
ft8_encode(packed, tone_sequence);
printf("FSK tones: ");
for (int j = 0; j < num_tones; ++j)
{
printf("%d", tone_sequence[j]);
}
printf("\n");
return;
}

Wyświetl plik

@ -1,23 +1,23 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "decode_ft8.h"
typedef struct
{
bool call_cq;
bool send_grid;
bool send_snr;
bool send_Rsnr;
bool send_RRR;
bool send_RR73;
bool send_73;
} UserSendSelection;
void manual_gen_message(char message[], message_info Station, UserSendSelection stype, char *myCall, char *myGrid);
void auto_gen_message(char message[], message_info Station, char *myCall, char *myGrid);
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "decode_ft8.h"
typedef struct
{
bool call_cq;
bool send_grid;
bool send_snr;
bool send_Rsnr;
bool send_RRR;
bool send_RR73;
bool send_73;
} UserSendSelection;
void manual_gen_message(char message[], message_info Station, UserSendSelection stype, char *myCall, char *myGrid);
void auto_gen_message(char message[], message_info Station, char *myCall, char *myGrid);
void generate_ft8(char message[], uint8_t tone_sequence[]);

Wyświetl plik

@ -21,46 +21,19 @@ static int ldpc_check(uint8_t codeword[]);
static float fast_tanh(float x);
static float fast_atanh(float x);
// Packs a string of bits each represented as a zero/non-zero byte in plain[],
// as a string of packed bits starting from the MSB of the first byte of packed[]
void pack_bits(const uint8_t plain[], int num_bits, uint8_t packed[])
{
int num_bytes = (num_bits + 7) / 8;
for (int i = 0; i < num_bytes; ++i)
{
packed[i] = 0;
}
uint8_t mask = 0x80;
int byte_idx = 0;
for (int i = 0; i < num_bits; ++i)
{
if (plain[i])
{
packed[byte_idx] |= mask;
}
mask >>= 1;
if (!mask)
{
mask = 0x80;
++byte_idx;
}
}
}
// codeword is 174 log-likelihoods.
// plain is a return value, 174 ints, to be 0 or 1.
// max_iters is how hard to try.
// ok == 87 means success.
void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int* ok)
{
float m[FT8_LDPC_M][FT8_LDPC_N]; // ~60 kB
float e[FT8_LDPC_M][FT8_LDPC_N]; // ~60 kB
int min_errors = FT8_LDPC_M;
float m[FTX_LDPC_M][FTX_LDPC_N]; // ~60 kB
float e[FTX_LDPC_M][FTX_LDPC_N]; // ~60 kB
int min_errors = FTX_LDPC_M;
for (int j = 0; j < FT8_LDPC_M; j++)
for (int j = 0; j < FTX_LDPC_M; j++)
{
for (int i = 0; i < FT8_LDPC_N; i++)
for (int i = 0; i < FTX_LDPC_N; i++)
{
m[j][i] = codeword[i];
e[j][i] = 0.0f;
@ -69,15 +42,15 @@ void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
for (int iter = 0; iter < max_iters; iter++)
{
for (int j = 0; j < FT8_LDPC_M; j++)
for (int j = 0; j < FTX_LDPC_M; j++)
{
for (int ii1 = 0; ii1 < kFT8_LDPC_num_rows[j]; ii1++)
for (int ii1 = 0; ii1 < kFTX_LDPC_Num_rows[j]; ii1++)
{
int i1 = kFT8_LDPC_Nm[j][ii1] - 1;
int i1 = kFTX_LDPC_Nm[j][ii1] - 1;
float a = 1.0f;
for (int ii2 = 0; ii2 < kFT8_LDPC_num_rows[j]; ii2++)
for (int ii2 = 0; ii2 < kFTX_LDPC_Num_rows[j]; ii2++)
{
int i2 = kFT8_LDPC_Nm[j][ii2] - 1;
int i2 = kFTX_LDPC_Nm[j][ii2] - 1;
if (i2 != i1)
{
a *= fast_tanh(-m[j][i2] / 2.0f);
@ -87,11 +60,11 @@ void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
}
}
for (int i = 0; i < FT8_LDPC_N; i++)
for (int i = 0; i < FTX_LDPC_N; i++)
{
float l = codeword[i];
for (int j = 0; j < 3; j++)
l += e[kFT8_LDPC_Mn[i][j] - 1][i];
l += e[kFTX_LDPC_Mn[i][j] - 1][i];
plain[i] = (l > 0) ? 1 : 0;
}
@ -108,17 +81,17 @@ void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
}
}
for (int i = 0; i < FT8_LDPC_N; i++)
for (int i = 0; i < FTX_LDPC_N; i++)
{
for (int ji1 = 0; ji1 < 3; ji1++)
{
int j1 = kFT8_LDPC_Mn[i][ji1] - 1;
int j1 = kFTX_LDPC_Mn[i][ji1] - 1;
float l = codeword[i];
for (int ji2 = 0; ji2 < 3; ji2++)
{
if (ji1 != ji2)
{
int j2 = kFT8_LDPC_Mn[i][ji2] - 1;
int j2 = kFTX_LDPC_Mn[i][ji2] - 1;
l += e[j2][i];
}
}
@ -139,12 +112,12 @@ static int ldpc_check(uint8_t codeword[])
{
int errors = 0;
for (int m = 0; m < FT8_LDPC_M; ++m)
for (int m = 0; m < FTX_LDPC_M; ++m)
{
uint8_t x = 0;
for (int i = 0; i < kFT8_LDPC_num_rows[m]; ++i)
for (int i = 0; i < kFTX_LDPC_Num_rows[m]; ++i)
{
x ^= codeword[kFT8_LDPC_Nm[m][i] - 1];
x ^= codeword[kFTX_LDPC_Nm[m][i] - 1];
}
if (x != 0)
{
@ -154,15 +127,15 @@ static int ldpc_check(uint8_t codeword[])
return errors;
}
void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
void bp_decode(float codeword[], int max_iters, uint8_t plain[], int* ok)
{
float tov[FT8_LDPC_N][3];
float toc[FT8_LDPC_M][7];
float tov[FTX_LDPC_N][3];
float toc[FTX_LDPC_M][7];
int min_errors = FT8_LDPC_M;
int min_errors = FTX_LDPC_M;
// initialize message data
for (int n = 0; n < FT8_LDPC_N; ++n)
for (int n = 0; n < FTX_LDPC_N; ++n)
{
tov[n][0] = tov[n][1] = tov[n][2] = 0;
}
@ -171,7 +144,7 @@ void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
{
// Do a hard decision guess (tov=0 in iter 0)
int plain_sum = 0;
for (int n = 0; n < FT8_LDPC_N; ++n)
for (int n = 0; n < FTX_LDPC_N; ++n)
{
plain[n] = ((codeword[n] + tov[n][0] + tov[n][1] + tov[n][2]) > 0) ? 1 : 0;
plain_sum += plain[n];
@ -198,16 +171,16 @@ void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
}
// Send messages from bits to check nodes
for (int m = 0; m < FT8_LDPC_M; ++m)
for (int m = 0; m < FTX_LDPC_M; ++m)
{
for (int n_idx = 0; n_idx < kFT8_LDPC_num_rows[m]; ++n_idx)
for (int n_idx = 0; n_idx < kFTX_LDPC_Num_rows[m]; ++n_idx)
{
int n = kFT8_LDPC_Nm[m][n_idx] - 1;
int n = kFTX_LDPC_Nm[m][n_idx] - 1;
// for each (n, m)
float Tnm = codeword[n];
for (int m_idx = 0; m_idx < 3; ++m_idx)
{
if ((kFT8_LDPC_Mn[n][m_idx] - 1) != m)
if ((kFTX_LDPC_Mn[n][m_idx] - 1) != m)
{
Tnm += tov[n][m_idx];
}
@ -217,16 +190,16 @@ void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
}
// send messages from check nodes to variable nodes
for (int n = 0; n < FT8_LDPC_N; ++n)
for (int n = 0; n < FTX_LDPC_N; ++n)
{
for (int m_idx = 0; m_idx < 3; ++m_idx)
{
int m = kFT8_LDPC_Mn[n][m_idx] - 1;
int m = kFTX_LDPC_Mn[n][m_idx] - 1;
// for each (n, m)
float Tmn = 1.0f;
for (int n_idx = 0; n_idx < kFT8_LDPC_num_rows[m]; ++n_idx)
for (int n_idx = 0; n_idx < kFTX_LDPC_Num_rows[m]; ++n_idx)
{
if ((kFT8_LDPC_Nm[m][n_idx] - 1) != n)
if ((kFTX_LDPC_Nm[m][n_idx] - 1) != n)
{
Tmn *= toc[m][n_idx];
}
@ -256,10 +229,10 @@ static float fast_tanh(float x)
return 1.0f;
}
float x2 = x * x;
//float a = x * (135135.0f + x2 * (17325.0f + x2 * (378.0f + x2)));
//float b = 135135.0f + x2 * (62370.0f + x2 * (3150.0f + x2 * 28.0f));
//float a = x * (10395.0f + x2 * (1260.0f + x2 * 21.0f));
//float b = 10395.0f + x2 * (4725.0f + x2 * (210.0f + x2));
// float a = x * (135135.0f + x2 * (17325.0f + x2 * (378.0f + x2)));
// float b = 135135.0f + x2 * (62370.0f + x2 * (3150.0f + x2 * 28.0f));
// float a = x * (10395.0f + x2 * (1260.0f + x2 * 21.0f));
// float b = 10395.0f + x2 * (4725.0f + x2 * (210.0f + x2));
float a = x * (945.0f + x2 * (105.0f + x2));
float b = 945.0f + x2 * (420.0f + x2 * 15.0f);
return a / b;
@ -268,10 +241,10 @@ static float fast_tanh(float x)
static float fast_atanh(float x)
{
float x2 = x * x;
//float a = x * (-15015.0f + x2 * (19250.0f + x2 * (-5943.0f + x2 * 256.0f)));
//float b = (-15015.0f + x2 * (24255.0f + x2 * (-11025.0f + x2 * 1225.0f)));
//float a = x * (-1155.0f + x2 * (1190.0f + x2 * -231.0f));
//float b = (-1155.0f + x2 * (1575.0f + x2 * (-525.0f + x2 * 25.0f)));
// float a = x * (-15015.0f + x2 * (19250.0f + x2 * (-5943.0f + x2 * 256.0f)));
// float b = (-15015.0f + x2 * (24255.0f + x2 * (-11025.0f + x2 * 1225.0f)));
// float a = x * (-1155.0f + x2 * (1190.0f + x2 * -231.0f));
// float b = (-1155.0f + x2 * (1575.0f + x2 * (-525.0f + x2 * 25.0f)));
float a = x * (945.0f + x2 * (-735.0f + x2 * 64.0f));
float b = (945.0f + x2 * (-1050.0f + x2 * 225.0f));
return a / b;

Some files were not shown because too many files have changed in this diff Show More