diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
new file mode 100644
index 0000000000..f234059070
--- /dev/null
+++ b/.github/workflows/build.yaml
@@ -0,0 +1,46 @@
+name: Build
+on: [push, pull_request]
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Prepare build environment
+ run: ./cicd/prepare_runner.sh
+ - name: Build
+ run: ./cicd/docker_brun.sh
+ - name: Create Release
+ id: create_release
+ uses: actions/create-release@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
+ with:
+ tag_name: Balloon${{ github.run_id }}${{ github.run_attempt }}
+ release_name: Release ${{ github.sha }}
+ body: |
+ Contents of this release:
+ * update.img - Update image for LuckyFox pico Board.
+ * output.zip - Contents of output folder
+ draft: true
+ prerelease: false
+ - name: Upload Release Asset update.img
+ id: upload-release-asset-update
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
+ asset_path: ./output/image/update.img
+ asset_name: update.img
+ asset_content_type: application/img
+ - name: Upload Release Asset output.zip
+ id: upload-release-asset-output
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
+ asset_path: ./output.zip
+ asset_name: output.zip
+ asset_content_type: application/zip
+
\ No newline at end of file
diff --git a/README.md b/README.md
index c86229f280..4cfcfbacb7 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,24 @@
This project is designed to build a linux based LoraWan HAB balloon. The idea of the project is to make an advancement of the current PicoBalloon communities.
+## Project Advantages/Disadvantages
+
+As ever, re-inventing the wheel has it's own pros and cons. This section aims to answer FAQs on why this project has been built and designed.
+
+### Advantages
+
+* Linux based - In the past, common sense would have assumed to have used low-power/arduino based microcontrollers. However, as chips have become vastly more efficient and cheaper in recent years (OK, that's a topic for another day!). This means that it has become sensible to start building using Linux. Advantages include being able to use Linux based drivers. For example the LoraWan interface is now a standard linux socket interface, and the HAM radio module can be interfaced with sysfs!
+* Ease of development - Linux will allow GDB to be run on the end device for debugging, the environment is running ADB allowing code to easily be pushed or shell to be accessed. Multiple layers of _shit_ can be abstracted... Don't understand the FSK Lora Protocol or SPI interfaces? Fine. It's just a unix socket now! :)
+* Ease of build - You can repeat this project with a soldering iron, a few thin wires and a swear jar.
+* Sets a new standard of MVP (Minimal Viable Product) - The SDK and simple build means it can be used as a base design for quickly bootstrapping any future projects.
+
+### Disadvantages
+
+* Linux based - Yeah, it's not a RTOS. Looking at the drift of WSPR.. eh, famous last words but should be fine.
+* Ease of development - It vastly changes the paradigm compared to Arduino based solutions
+* Ease of build - WEIGHT!!!!!! - I will work on this one.
+* Complexity - While fine once you get used to it... there is a lot going on here.
+
## Network design decisions
There are 2 major providers of LoraWan coverage:
@@ -54,10 +72,14 @@ This design decision has been made for the following reasons:
Generally modern GPS modules are simply UART based, use similar chipsets and have essentially all become unified since they became integrated into smartphones. I didn't take much bother into researching this one. They should in theory neary all just be hotswappable.
+*Update*: No, no they are not. Added gpsd as a common interface.
+
#### Camera Module
This one needs research. I just picked the smallest, lightest camera I had on hand (It came from a ESP32 kit). Might not be the best solution power wise. This section might need an update.
+*Update*, who uses a 20 Pin non-conformant MIPI/CSI interface on a dev board?!? Luckfox.. Otherwise the best dev board i've ever worked with :/
+
## Notes on design documentation and Source code
The linux module used as the central processor is based on the LuckyFox Pico Mini B. Hence the basis of this git repository is a fork of their SDK for ease of updating any bug fixes for the board. Where there have been significant code changes, the README.md from LuckyFox have been renamed with a _luckyfox.md appendage.
@@ -68,15 +90,24 @@ I have placed design thoughts within the ./doc directory within this repository.
## Initial Prototype
-The initial prototype can be seen in the image below.
+The initial prototype can be seen in the images below.
![Prototype](./docs/images/prototype.jpg)
+![Prototype](./docs/images/schematic.jpg)
+
# Project status
-* It boots into linux and doesn't blow up.
-* DTS Updated for correct pin attribution
-* GPS Works
-* Starnights LoraWan module upgraded to work with Linux 5.10
-* Lora and LoraWan Kernel Modules Building & Inserting
-* Basically the entire code base is missing.
\ No newline at end of file
+* [_COMPLETED_] It boots into Linux/Buildroot system.
+* [_COMPLETED_] SPI working with Semtec sx1276 FSK Modem.
+* [_COMPLETED_] I2C working with Si5351 Pulse Generator.
+* [_COMPLETED_] UART working with uBlox GPS Module.
+* [_COMPLETED_] GPS Works, gpsd installed.
+* [_COMPLETED_] CI/CD Works.
+* [_TESTING_] WSPR Working, Still requires real-world tests.
+* [_IN PROGRESS_] Lora works, LoraWan not tested, added EU region, missing others in kernel driver.
+* [_TODO_] "Glue" shell scripts to tack everything together.
+* [_TODO_] CSI Camera Interface with CSI/MIPI camera.
+* [_TODO_] Camera Images to Lorawan Packets.
+* [_TODO_] LoraWan MAC to IPv4 Bridge.
+* [_TODO_] Reduce physical weight (Daughter Board?)
diff --git a/cicd/docker_brun.sh b/cicd/docker_brun.sh
new file mode 100755
index 0000000000..d84cfbc2f3
--- /dev/null
+++ b/cicd/docker_brun.sh
@@ -0,0 +1,5 @@
+set -e
+
+sudo docker run --privileged --mount type=bind,source="$(pwd)",target=/balloon luckfoxtech/luckfox_pico:1.0 /bin/bash /balloon/build.sh
+
+zip output.zip output
\ No newline at end of file
diff --git a/cicd/prepare_runner.sh b/cicd/prepare_runner.sh
new file mode 100755
index 0000000000..b65b7ba346
--- /dev/null
+++ b/cicd/prepare_runner.sh
@@ -0,0 +1,25 @@
+set -e
+
+# Install binfmt-support and zip
+sudo apt install -y binfmt-support qemu-user-static zip
+
+# Install docker
+# Add Docker's official GPG key:
+sudo apt-get update
+sudo apt-get install ca-certificates curl gnupg
+sudo install -m 0755 -d /etc/apt/keyrings
+curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
+sudo chmod a+r /etc/apt/keyrings/docker.gpg
+
+# Add the repository to Apt sources:
+echo \
+ "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
+ "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
+ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
+sudo apt-get update
+
+sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
+
+# Do submodule checkout
+git submodule init
+git submodule update --depth=1
\ No newline at end of file
diff --git a/docs/WSPR.md b/docs/WSPR.md
new file mode 100644
index 0000000000..4ee782eccf
--- /dev/null
+++ b/docs/WSPR.md
@@ -0,0 +1,29 @@
+# WSPR Design
+
+## Introduction
+
+WSPR is a stands for Weak Signal Propogation Reporter, more information can be found at [https://www.sigidwiki.com/wiki/WSPR]https://www.sigidwiki.com/wiki/WSPR
+
+WSPR is designed to be able to report incredibly weak signals, and has various base points for detection around the world. This allows communication at an extremely slow speed for location data to provide telemetry. This can be seen on WSPRNet.
+
+[http://www.wsprnet.org/drupal/WSPRnet/map]WSPRNet
+
+For use the protocol requires an amateur radio license. For this project, I have aquired one for use of WSPRNet, however anyone using this project will need to aquire a license for themselves in order to use this technology.
+
+## Base Design
+
+### Code
+
+The project is based on [https://github.com/alexf91/WSPR-Beacon/]alexf91/WSPR-Beacon however has been completely rewritten.
+
+The initial code was designed for an Arduino based solution with a USB interface. The re-write here disregards the code apart from the WSPR encoding library.
+
+A special mention here is [https://github.com/threeme3/WsprryPi]threeme3/WsprryPi. I's not used in this project, but was vastly helpful for design purposes.
+
+### Electronics
+
+The WSPR part of the project requires an oscillator to function at around 14MHz.
+
+For this it was chosen to use the si5351, for the good community support, and the ease of the i2c interface.
+
+Technically, it a low pass filter should be added, but given the harmonics of a square wave and the transmission power, for this indiviual use case, I would consider unnecessary.
\ No newline at end of file
diff --git a/docs/images/schematic.jpg b/docs/images/schematic.jpg
new file mode 100644
index 0000000000..ea2b211016
Binary files /dev/null and b/docs/images/schematic.jpg differ
diff --git a/docs/images/schematic.xml b/docs/images/schematic.xml
new file mode 100644
index 0000000000..eae26a19a2
--- /dev/null
+++ b/docs/images/schematic.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/local.sh b/local.sh
index ff0a2a76ac..d67f3915c3 100755
--- a/local.sh
+++ b/local.sh
@@ -3,4 +3,7 @@ if ./build.sh ; then
sudo cp -rf output/image ~/nfs/image
else
echo "Build failure, not copying"
-fi
\ No newline at end of file
+fi
+
+# Note to self, compiling a single thing is a lot easier when you add the toolchain!
+# export PATH=/home/aj/lorawan-balloon/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin:$PATH
\ No newline at end of file
diff --git a/sysdrv/cfg/package.mk b/sysdrv/cfg/package.mk
index 4d04dd6a3d..10c383bb23 100644
--- a/sysdrv/cfg/package.mk
+++ b/sysdrv/cfg/package.mk
@@ -42,6 +42,11 @@ $(eval $(call MACRO_CHECK_ENABLE_PKG, RK_ENABLE_OTA))
CONFIG_SYSDRV_ENABLE_STRACE=n
$(eval $(call MACRO_CHECK_ENABLE_PKG, RK_ENABLE_STRACE))
+
# Enable build lorawan-bridge
CONFIG_SYSDRV_ENABLE_LORAWAN_BRIDGE=y
-$(eval $(call MACRO_CHECK_ENABLE_PKG, RK_ENABLE_LORAWAN_BRIDGE))
\ No newline at end of file
+$(eval $(call MACRO_CHECK_ENABLE_PKG, RK_ENABLE_LORAWAN_BRIDGE))
+
+# Enable build WSPR
+CONFIG_SYSDRV_ENABLE_WSPR=y
+$(eval $(call MACRO_CHECK_ENABLE_PKG, RK_ENABLE_WSPR))
diff --git a/sysdrv/source/kernel/arch/arm/boot/dts/rv1103g-luckfox-pico-mini-b.dts b/sysdrv/source/kernel/arch/arm/boot/dts/rv1103g-luckfox-pico-mini-b.dts
index e8ea642fd7..fbc2eebfae 100755
--- a/sysdrv/source/kernel/arch/arm/boot/dts/rv1103g-luckfox-pico-mini-b.dts
+++ b/sysdrv/source/kernel/arch/arm/boot/dts/rv1103g-luckfox-pico-mini-b.dts
@@ -20,6 +20,15 @@
regulator-name = "gpio1_pd0";
regulator-always-on;
};
+
+ clocks {
+ /* 25MHz reference crystal */
+ ref25: ref25M {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <25000000>;
+ };
+ };
};
&sfc {
@@ -67,14 +76,85 @@
// status = "disabled";
//};
+
// /**********I2C**********/
&i2c3 {
status = "okay";
pinctrl-0 = <&i2c3m1_xfer>;
clock-frequency = <100000>;
+/* Si5351a msop10 i2c clock generator */
+ si5351a: clock-generator@60 {
+ compatible = "silabs,si5351a-msop";
+ reg = <0x60>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ #clock-cells = <1>;
+
+ /* connect xtal input to 25MHz reference */
+ clocks = <&ref25>;
+ clock-names = "xtal";
+
+ /* connect xtal input as source of pll0 and pll1 */
+ silabs,pll-source = <0 0>, <1 0>;
+
+ /*
+ * overwrite clkout0 configuration with:
+ * - 8mA output drive strength
+ * - pll0 as clock source of multisynth0
+ * - multisynth0 as clock source of output divider
+ * - multisynth0 can change pll0
+ * - set initial clock frequency of 74.25MHz
+ */
+ clkout0 {
+ reg = <0>;
+ silabs,drive-strength = <8>;
+ silabs,multisynth-source = <0>;
+ silabs,clock-source = <0>;
+ silabs,pll-master;
+ /* clock-frequency = <74250000>;*/
+ };
+
+ /*
+ * overwrite clkout1 configuration with:
+ * - 4mA output drive strength
+ * - pll1 as clock source of multisynth1
+ * - multisynth1 as clock source of output divider
+ * - multisynth1 can change pll1
+ */
+ clkout1 {
+ reg = <1>;
+ silabs,drive-strength = <4>;
+ silabs,multisynth-source = <1>;
+ silabs,clock-source = <0>;
+ pll-master;
+ };
+
+ /*
+ * overwrite clkout2 configuration with:
+ * - xtal as clock source of output divider
+ */
+ clkout2 {
+ reg = <2>;
+ silabs,clock-source = <2>;
+ };
+ };
+
+};
+
+&pinctrl {
+ i2c3 {
+ /omit-if-no-ref/
+ i2c3m1_xfer: i2c3m1-xfer {
+ rockchip,pins =
+ /* i2c3_scl_m1 */
+ <1 RK_PD3 3 &pcfg_pull_up>,
+ /* i2c3_sda_m1 */
+ <1 RK_PD2 3 &pcfg_pull_up>;
+ };
+ };
};
-// /**********SPI**********/
+/**********SPI**********/
&spi0 {
status = "okay";
pinctrl-names = "default";
diff --git a/sysdrv/source/kernel/arch/arm/configs/luckfox_rv1106_linux_defconfig b/sysdrv/source/kernel/arch/arm/configs/luckfox_rv1106_linux_defconfig
index 4c54b69658..d1652b1ee2 100755
--- a/sysdrv/source/kernel/arch/arm/configs/luckfox_rv1106_linux_defconfig
+++ b/sysdrv/source/kernel/arch/arm/configs/luckfox_rv1106_linux_defconfig
@@ -320,3 +320,4 @@ CONFIG_DEBUG_FS=y
# CONFIG_SCHED_DEBUG is not set
# CONFIG_FTRACE is not set
# CONFIG_RUNTIME_TESTING_MENU is not set
+CONFIG_COMMON_CLK_SI5351=y
diff --git a/sysdrv/tools/board/Makefile.tools.board.mk b/sysdrv/tools/board/Makefile.tools.board.mk
index 152cb5a08f..f1af4b9dea 100644
--- a/sysdrv/tools/board/Makefile.tools.board.mk
+++ b/sysdrv/tools/board/Makefile.tools.board.mk
@@ -10,7 +10,8 @@ tools_board-builds: \
board-build-e2fsprogs \
board-build-sysstat \
board-build-mtd_utils \
- board-build-lorawan-bridge
+ board-build-lorawan-bridge \
+ board-build-wspr
@echo "build tools board done"
tools_board-clean:
@@ -25,6 +26,7 @@ tools_board-clean:
$(MAKE) -C $(SYSDRV_DIR)/tools/board/rk_ota distclean
$(MAKE) -C $(SYSDRV_DIR)/tools/board/sysstat distclean
$(MAKE) -C $(SYSDRV_DIR)/tools/board/lorawan-bridge distclean
+ $(MAKE) -C $(SYSDRV_DIR)/tools/board/wspr distclean
board-build-toolkits:
$(MAKE) -C $(SYSDRV_DIR)/tools/board/toolkits
@@ -76,6 +78,7 @@ ifeq ($(BOOT_MEDIUM),spi_nor)
popd;
endif
endif
+
board-build-sysstat:
ifeq ($(ENABLE_SYSSTAT),y)
$(MAKE) -C $(SYSDRV_DIR)/tools/board/sysstat
@@ -85,3 +88,8 @@ board-build-lorawan-bridge:
ifeq ($(ENABLE_LORAWAN_BRIDGE),y)
$(MAKE) -C $(SYSDRV_DIR)/tools/board/lorawan-bridge
endif
+
+board-build-wspr:
+ifeq ($(ENABLE_WSPR),y)
+ $(MAKE) -C $(SYSDRV_DIR)/tools/board/wspr
+endif
diff --git a/sysdrv/tools/board/buildroot/luckfox_pico_defconfig b/sysdrv/tools/board/buildroot/luckfox_pico_defconfig
index d0cb9a7f64..48eb174b8e 100755
--- a/sysdrv/tools/board/buildroot/luckfox_pico_defconfig
+++ b/sysdrv/tools/board/buildroot/luckfox_pico_defconfig
@@ -35,3 +35,7 @@ BR2_PACKAGE_SAMBA4=y
BR2_PACKAGE_TIME=y
BR2_PACKAGE_HTOP=y
BR2_PACKAGE_NANO=y
+BR2_PACKAGE_GPSD=y
+BR2_PACKAGE_GPSD_DEVICES="/dev/ttyS2"
+BR2_PACKAGE_GPSD_UBX=y
+BR2_PACKAGE_GPSD_PYTHON=y
diff --git a/sysdrv/tools/board/wspr/Makefile b/sysdrv/tools/board/wspr/Makefile
new file mode 100644
index 0000000000..225a406de3
--- /dev/null
+++ b/sysdrv/tools/board/wspr/Makefile
@@ -0,0 +1,26 @@
+
+#ifeq ($(SYSDRV_PARAM), )
+ WSPR_PARAM:=../../../Makefile.param
+ include $(WSPR_PARAM)
+#endif
+
+export LC_ALL=C
+SHELL:=/bin/bash
+
+CURRENT_DIR := $(shell pwd)
+PKG_NAME := wspr
+PKG_BIN := out
+
+all:
+ @test -f $(PKG_BIN)/usr/sbin/$(PKG_NAME)_noexist || (\
+ mkdir -p $(CURRENT_DIR)/$(PKG_BIN)/usr/sbin; \
+ $(SYSDRV_CROSS)-gcc -g main.c wspr.c -o $(CURRENT_DIR)/$(PKG_BIN)/usr/sbin/$(PKG_NAME); \
+ )
+ $(call MAROC_COPY_PKG_TO_SYSDRV_OUTPUT, $(SYSDRV_DIR_OUT_ROOTFS), $(PKG_BIN))
+# $(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNEL_DIR) M=$(shell pwd) $@ -j12
+
+clean: distclean
+
+distclean:
+ -rm -rf $(PKG_NAME) $(PKG_BIN)
+
diff --git a/sysdrv/tools/board/wspr/README.md b/sysdrv/tools/board/wspr/README.md
new file mode 100644
index 0000000000..73aa05fef3
--- /dev/null
+++ b/sysdrv/tools/board/wspr/README.md
@@ -0,0 +1,6 @@
+# WSPR Transmission Code
+
+This code is designed to transmit WSPR tones and is a largely rewritten version of:
+https://github.com/alexf91/WSPR-Beacon/
+
+The code directly interfaces with the clock sources through the unix filesystem.
\ No newline at end of file
diff --git a/sysdrv/tools/board/wspr/debug.h b/sysdrv/tools/board/wspr/debug.h
new file mode 100644
index 0000000000..098e8fc8d8
--- /dev/null
+++ b/sysdrv/tools/board/wspr/debug.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2018 Alexander Fasching
+ *
+ * This program 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.
+ *
+ * This program 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
+ * along with this program. If not, see .
+ */
+
+#ifndef DEBUG_H
+#define DEBUG_H
+
+#include
+#include
+
+static inline void debug(const char *format, ...) {
+#ifdef DEBUG
+ va_list args;
+ va_start(args, format);
+ vprintf(format, args);
+#endif
+}
+
+#endif /* DEBUG_H */
diff --git a/sysdrv/tools/board/wspr/main.c b/sysdrv/tools/board/wspr/main.c
new file mode 100644
index 0000000000..8530259123
--- /dev/null
+++ b/sysdrv/tools/board/wspr/main.c
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2023 Alistair Jordan
+ *
+ * This program 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.
+ *
+ * This program 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
+ * along with this program. If not, see .
+ */
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include "wspr.h"
+ #include
+ #include
+ #include // for clock_gettime()
+
+ #define BASE_FREQ_LOW 14097000
+ // high is end of 200hz spectrum - 6 hz to take account for transmission.
+ // Useful docs here: https://www.qrp-labs.com/images/ultimate3s/operation3.11.pdf
+ #define BASE_FREQ_HIGH 14097194
+
+/**
+ * @brief Convert GPS coordinates into the 6 character grid locator.
+ * @param grid Buffer for the result
+ * @param lon Longitude in 15.16 signed fixed point format.
+ * @param lat Latitude in 15.16 signed fixed point format.
+ * @returns 0 on uccess.
+ */
+static int8_t gps_to_grid(char *grid, int32_t lon, int32_t lat) {
+ const int32_t SCALE = 65536;
+
+ int32_t tmp_lon = lon + 180 * SCALE;
+ int32_t tmp_lat = lat + 90 * SCALE;
+
+ if (tmp_lon < 0 || tmp_lon > 360 * SCALE)
+ return 1;
+ if (tmp_lat < 0 || tmp_lat > 180 * SCALE)
+ return 2;
+
+ /* Convert the longitude */
+ grid[0] = tmp_lon / (20 * SCALE) + 'A';
+ tmp_lon %= (20 * SCALE);
+
+ grid[2] = tmp_lon / (2 * SCALE) + '0';
+ tmp_lon %= (2 * SCALE);
+
+ grid[4] = tmp_lon * 12 / SCALE + 'a';
+
+ /* Convert the latitude */
+ grid[1] = tmp_lat / (10 * SCALE) + 'A';
+ tmp_lat %= (10 * SCALE);
+
+ grid[3] = tmp_lat / SCALE + '0';
+ tmp_lat %= SCALE;
+
+ grid[5] = tmp_lat * 24 / SCALE + 'a';
+
+ return 0;
+}
+
+int init_wspr(char *clk) {
+ char echo_command[100];
+ // use system calls for the time being, direct file access seems to be throwing all kinds of errors, and I can't workout why..
+ // this needs FIXING!
+ snprintf(echo_command, sizeof(echo_command), "echo 'enable clk0' > /proc/clk/enable");
+ return (system(echo_command));
+}
+
+int send_tone(uint32_t base_freq, char *data, int index, char *clk) {
+ char echo_command[100];
+ // set freq
+ // echo [clk_name] [rate(Hz)] > /proc/clk/rate
+ uint8_t tone = wspr_get_tone(data, index);
+ uint32_t f = base_freq + (3 * tone) / 2;
+ printf("Sending tone %i of %i at %i Hz\n",index+1,WSPR_SYMBOL_COUNT,f);
+ snprintf(echo_command, sizeof(echo_command), "echo 'clk0 %d' > /proc/clk/rate", tone);
+ return (system(echo_command));
+}
+
+int stop_wspr(char *clk) {
+ //disable clk0
+ // echo disable [clk_name] > /proc/clk/enable
+ char echo_command[100];
+ // use system calls for the time being, direct file access seems to be throwing all kinds of errors, and I can't workout why..
+ // this needs FIXING!
+ snprintf(echo_command, sizeof(echo_command), "echo 'disable clk0' > /proc/clk/enable");
+ return (system(echo_command));
+}
+
+ int main(int argc, char *argv[]) {
+ char grid[6];
+ char callsign[6];//"=2X0UAJ";
+ char clk_name[5] = "clk0\0";
+ int32_t lon = 0;
+ int32_t lat = 0;
+ uint8_t wspr_data[WSPR_BUFFER_SIZE];
+
+ if (argc < 4) {
+ printf("Usage is ./wspr lon[int32] lat[int32] CALLSIGN[char[6]]");
+ return 1;
+ }
+ sscanf(argv[1],"%ld",&lon);
+ sscanf(argv[2],"%ld",&lat);
+ sscanf(argv[3],"%s.6",&callsign);
+
+ if (gps_to_grid(grid, lon, lat) != 0) {
+ printf("Cannot translate long/lat\n");
+ return 1;
+ }
+ if (wspr_encode(wspr_data, callsign, grid, 7) != 0) {
+ printf("Cannot encode WSPR message\n");
+ return 1;
+ }
+
+ printf("Decide random frequency within band for transmission\n");
+ // Decide base frequency for transmission
+ srand(time(NULL));
+ uint32_t base_freq = BASE_FREQ_LOW + (rand() % (BASE_FREQ_HIGH - BASE_FREQ_LOW));
+ printf("Freqeuency decided at %i\n",base_freq);
+
+ printf("Attempt to init %s for transmission\n", clk_name);
+ // Start transmit
+ if (init_wspr(clk_name) != 0) {
+ printf("%s unable to init!\n", clk_name);
+ return 1;
+ }
+ printf("%s init completete, start sending tones.\n", clk_name);
+
+ int msec = 0;
+ struct timeval start, end;
+ long secs_used,micros_used;
+ //printf("Start time: %d\n", start_time);
+ for (int i=0; i < WSPR_SYMBOL_COUNT; i++) {
+ gettimeofday(&start, NULL);
+ if (send_tone(base_freq, wspr_data, i, clk_name) != 0) {
+ printf("Transmit failure\n");
+ break;
+ }
+ gettimeofday(&end, NULL);
+ // account for how long it took for send_tone to work with drift correction
+ secs_used=(end.tv_sec - start.tv_sec); //avoid overflow by subtracting first
+ micros_used= ((secs_used*1000000) + end.tv_usec) - (start.tv_usec);
+ usleep((WSPR_SYMBOL_TIME*1000 - micros_used));
+ }
+
+ if (stop_wspr(clk_name) != 0) {
+ printf("Disconnect device immediately!\n");
+ return 2;
+ }
+
+ printf("Transmission Complete\n");
+
+}
\ No newline at end of file
diff --git a/sysdrv/tools/board/wspr/wspr.c b/sysdrv/tools/board/wspr/wspr.c
new file mode 100644
index 0000000000..fc9c675513
--- /dev/null
+++ b/sysdrv/tools/board/wspr/wspr.c
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2018 Alexander Fasching
+ *
+ * This program 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.
+ *
+ * This program 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
+ * along with this program. If not, see .
+ */
+
+#include
+#include
+#include
+
+#include "debug.h"
+
+
+const uint8_t sync_vector[] = {
+ 0x03, 0x71, 0xa4, 0x07, 0xa4, 0x40, 0xb3, 0x58, 0x58, 0x95, 0x34, 0x56,
+ 0x04, 0xc9, 0xcd, 0xe2, 0xa0, 0x0c, 0x58, 0x63, 0x00
+};
+
+
+/**
+ * Calculate the parity bits from the value of the shift register.
+ * The parity bits are returned in bit0 and bit1 of the returned value.
+ */
+static uint8_t get_parity(uint32_t * shiftreg0, uint32_t * shiftreg1) {
+ // use Galois-type shift register calculation
+ // for this to work, polynomials have to be bit-reversed
+ const uint32_t rev_poly0 = 0x8ACA0B4E; // invert lowest bit because it
+ const uint32_t rev_poly1 = 0xE23C8626; // is set by having Data put there
+
+ uint8_t par0;
+ uint8_t par1;
+
+ // if new data bit is 1, apply polynomial to SR
+ // new data bit is the same in both SRs, so we only need to test LSB once
+ if(*shiftreg0 & 1){
+ *shiftreg0 ^= rev_poly0;
+ *shiftreg1 ^= rev_poly1;
+ }
+ // parity is at MSB
+ par0 = *shiftreg0 & 0x80000000 ? 1 : 0;
+ // implement shift in constant
+ par1 = *shiftreg1 & 0x80000000 ? 2 : 0;
+ return par1 | par0;
+}
+
+
+static void set_bit(uint8_t *bitvector, int index, uint8_t value) {
+ if (value)
+ bitvector[index >> 3] |= (1 << (index & 0x07));
+ else
+ bitvector[index >> 3] &= ~(1 << (index & 0x07));
+}
+
+static uint8_t get_bit(const uint8_t *bitvector, int index) {
+ return !!(bitvector[index >> 3] & (1 << (index & 0x07)));
+}
+
+static uint8_t bit_reverse(uint8_t byte) {
+ uint8_t result = 0;
+ for (int i = 0; i < 8; i++) {
+ if (byte & (1 << i))
+ result |= (1 << (7 - i));
+ }
+ return result;
+}
+
+
+//#ifdef DEBUG
+static void print_vector(const char *name, const uint8_t *bitvec) {
+ printf("%s:", name);
+ for (int i = 0; i < 162; i++) {
+ if (i % 30 == 0)
+ printf("\n ");
+ printf("%d", get_bit(bitvec, i));
+
+ if (i % 30 != 29)
+ printf(" ");
+ }
+ printf("\n");
+}
+//#endif
+
+
+/**
+ * @brief Encode a WSPR message
+ * @param call Callsign as string in the format AABCCC
+ * @param loc 4 character QTH locator as string
+ * @param power Power in dBm
+ */
+int wspr_encode(uint8_t *result, const char *call, const char *loc, int power) {
+ uint32_t call_enc = 0;
+
+ /* Map the callsign to integers between 0 and 36. */
+ uint8_t call_mapped[6];
+ for (int i = 0; i < 6; i++) {
+ if ('0' <= call[i] && call[i] <= '9')
+ call_mapped[i] = call[i] - '0';
+ else if ('A' <= call[i] && call[i] <= 'Z')
+ call_mapped[i] = 10 + call[i] - 'A';
+ else if (call[i] == ' ')
+ call_mapped[i] = 36;
+ else
+ return 1;
+ }
+
+ /* Encode the mapped callsign. */
+ call_enc += call_mapped[0];
+ call_enc = 36 * call_enc + call_mapped[1];
+ call_enc = 10 * call_enc + call_mapped[2];
+ call_enc = 27 * call_enc + (call_mapped[3] - 10);
+ call_enc = 27 * call_enc + (call_mapped[4] - 10);
+ call_enc = 27 * call_enc + (call_mapped[5] - 10);
+
+ /* Encode the grid locator. */
+ uint32_t loc_pwr_enc = (179 - 10 * (loc[0] - 'A') - (loc[2] - '0')) * 180 +
+ 10 * (loc[1] - 'A') + (loc[3] - '0');
+ /* Encode the transmit power. */
+ loc_pwr_enc = 128 * loc_pwr_enc + power + 64;
+
+ /* Forward Error Correction */
+ uint32_t shiftreg0 = 0;
+ uint32_t shiftreg1 = 0;
+ uint8_t packed[21] = {0};
+
+ for (int i = 0; i < 50; i++) {
+ uint8_t bit;
+ if (i < 28) {
+ uint8_t index = 27 - i;
+ bit = !!(call_enc & (1UL << index));
+ } else {
+ uint8_t index = 21 - (i - 28);
+ bit = !!(loc_pwr_enc & (1UL << index));
+ }
+
+ shiftreg0 = (shiftreg0 << 1) | bit;
+ shiftreg1 = (shiftreg1 << 1) | bit;
+
+ uint32_t parity = get_parity(&shiftreg0, &shiftreg1);
+ uint8_t par0 = !!(parity & (1UL << 0));
+ uint8_t par1 = !!(parity & (1UL << 1));
+
+ set_bit(packed, 2 * i, par0);
+ set_bit(packed, 2 * i + 1, par1);
+ }
+ for (int i = 50; i < 81; i++) {
+ shiftreg0 <<= 1;
+ shiftreg1 <<= 1;
+
+ uint32_t parity = get_parity(&shiftreg0, &shiftreg1);
+ uint8_t par0 = !!(parity & (1 << 0));
+ uint8_t par1 = !!(parity & (1 << 1));
+
+ set_bit(packed, 2 * i, par0);
+ set_bit(packed, 2 * i + 1, par1);
+ }
+
+ /* Shuffle the result. */
+ memset(result, 0, 21);
+ uint8_t p = 0;
+ for (int i = 0; i < 256; i++) {
+ uint8_t j = bit_reverse(i);
+ if (j < 162) {
+ set_bit(result, j, get_bit(packed, p));
+ p++;
+ }
+ if (p >= 162)
+ break;
+ }
+
+//#ifdef DEBUG
+ //print_vector("Data symbols", result);
+ //printf("\n");
+ //print_vector("Sync symbols", sync_vector);
+//#endif
+ return 0;
+}
+
+
+uint8_t wspr_get_tone(const uint8_t *enc, uint8_t index) {
+ return get_bit(sync_vector, index) + 2 * get_bit(enc, index);
+}
diff --git a/sysdrv/tools/board/wspr/wspr.h b/sysdrv/tools/board/wspr/wspr.h
new file mode 100644
index 0000000000..d2592ae729
--- /dev/null
+++ b/sysdrv/tools/board/wspr/wspr.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018 Alexander Fasching
+ *
+ * This program 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.
+ *
+ * This program 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
+ * along with this program. If not, see .
+ */
+
+#ifndef WSPR_H
+#define WSPR_H
+
+#define WSPR_BUFFER_SIZE 21
+#define WSPR_SYMBOL_TIME 683
+#define WSPR_SYMBOL_COUNT 162
+
+
+/**
+ * @brief Encode a WSPR message
+ * @param result Buffer of size WSPR_BUFFER_LEN in which the result is stored.
+ * @param call Callsign as string in the format AABCCC
+ * @param loc 4 character QTH locator as string
+ * @param power Power in dBm
+ */
+int8_t wspr_encode(uint8_t *result, const char *call, const char *loc, int power);
+
+
+/**
+ * @brief Combine the encoded message with the synchronization vector and
+ * return the tone that is sent over the air.
+ * @param enc Encoded message
+ * @param index Index of the symbol.
+ * @returns Number of the tone that is sent (0-3)
+ */
+uint8_t wspr_get_tone(const uint8_t *enc, uint8_t index);
+
+
+#endif /* WSPR_H */