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
|
@ -1,2 +1,3 @@
|
|||
build/
|
||||
.vscode/
|
||||
.vscode/
|
||||
*.o
|
||||
|
|
|
@ -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)
|
||||
|
|
1
LICENSE
|
@ -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
|
||||
|
|
Po Szerokość: | Wysokość: | Rozmiar: 109 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 103 KiB |
|
@ -0,0 +1,2 @@
|
|||
dummy:
|
||||
g++ -DPC=1 ft8/*.c ft8/*.cpp fft/*.c ft8/common/*.c -lm -Ift8 -I. -o pc
|
|
@ -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": []
|
||||
}
|
||||
}
|
|
@ -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": {}
|
||||
}
|
Po Szerokość: | Wysokość: | Rozmiar: 80 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 62 KiB |
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
0
|
Po Szerokość: | Wysokość: | Rozmiar: 3.2 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 113 KiB |
|
@ -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.
|
Po Szerokość: | Wysokość: | Rozmiar: 739 KiB |
|
@ -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)
|
Po Szerokość: | Wysokość: | Rozmiar: 140 KiB |
|
@ -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
|
Po Szerokość: | Wysokość: | Rozmiar: 113 KiB |
|
@ -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).
|
Po Szerokość: | Wysokość: | Rozmiar: 739 KiB |
|
@ -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)
|
Po Szerokość: | Wysokość: | Rozmiar: 140 KiB |
|
@ -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
|
243
README.md
|
@ -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/
|
||||
|
|
Po Szerokość: | Wysokość: | Rozmiar: 494 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 72 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 25 KiB |
|
@ -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
|
Po Szerokość: | Wysokość: | Rozmiar: 181 KiB |
|
@ -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
|
Po Szerokość: | Wysokość: | Rozmiar: 29 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 162 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 90 KiB |
|
@ -0,0 +1,3 @@
|
|||
board_manager:
|
||||
additional_urls:
|
||||
- https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
|
|
@ -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)
|
Po Szerokość: | Wysokość: | Rozmiar: 200 KiB |
|
@ -0,0 +1,2 @@
|
|||
format:
|
||||
astyle --options="formatter.conf" si5351_calibration.ino
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
Po Szerokość: | Wysokość: | Rozmiar: 12 KiB |
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,3 @@
|
|||
board_manager:
|
||||
additional_urls:
|
||||
- https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
|
|
@ -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)
|
|
@ -0,0 +1,2 @@
|
|||
format:
|
||||
astyle --options="formatter.conf" si5351_calibration.ino
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
Thanks to Gajendra Sir (VU2BGS) for sharing this circuit.
|
Po Szerokość: | Wysokość: | Rozmiar: 65 KiB |
|
@ -0,0 +1,2 @@
|
|||
(kicad_pcb (version 20211014) (generator pcbnew)
|
||||
)
|
|
@ -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": []
|
||||
}
|
||||
}
|
|
@ -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": {}
|
||||
}
|
|
@ -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 "")
|
||||
)
|
||||
)
|
||||
)
|
|
@ -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)
|
|
@ -0,0 +1 @@
|
|||
0
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
This folder has unmodified code from https://github.com/mborgerding/kissfft.
|
|
@ -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 */
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 */
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Upstream is https://github.com/kgoba/ft8_lib/ - commit cd0dc2e (Feb-7th-2022).
|
|
@ -0,0 +1,3 @@
|
|||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
|
@ -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__)
|
|
@ -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;
|
||||
}
|
|
@ -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_
|
720
ft8/constants.c
|
@ -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
|
||||
};
|
101
ft8/constants.h
|
@ -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_
|
||||
|
|
|
@ -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);
|
||||
|
|
33
ft8/crc.h
|
@ -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_
|
517
ft8/decode.c
|
@ -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-OR’ed 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;
|
||||
|
||||
}
|
130
ft8/decode.h
|
@ -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_
|
||||
|
|
346
ft8/decode_ft8.c
|
@ -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;
|
||||
}
|
|
@ -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
|
122
ft8/decode_ft8.h
|
@ -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);
|
||||
|
|
104
ft8/encode.c
|
@ -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-OR’ed 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
50
ft8/encode.h
|
@ -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_
|
||||
|
|
149
ft8/gen_ft8.c
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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[]);
|
109
ft8/ldpc.c
|
@ -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;
|
||||
|
|