From 906e390786bf4060a33a95739673de99adde634e Mon Sep 17 00:00:00 2001
From: eleccoder <9162301+eleccoder@users.noreply.github.com>
Date: Sun, 1 Aug 2021 17:18:36 +0200
Subject: [PATCH] Split into lib + example application
---
CMakeLists.txt | 62 +++++++++++++++++-------
README.md | 23 +++++----
include/aprs_pico.h | 44 +++++++++++++++++
src/aprs_pico.c | 105 ++++++++++++++--------------------------
src/aprs_pico_example.c | 48 ++++++++++++++++++
5 files changed, 186 insertions(+), 96 deletions(-)
create mode 100644 include/aprs_pico.h
create mode 100644 src/aprs_pico_example.c
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e238af9..06bfaf9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -11,7 +11,7 @@ set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
-# Setup of eleccoder's 'ax25-aprs-lib'
+# (1) Setup of eleccoder's 'ax25-aprs-lib'
include(FetchContent)
FetchContent_Declare(ax25_aprs_lib
@@ -24,30 +24,56 @@ FetchContent_MakeAvailable(ax25_aprs_lib)
add_library(ax25_aprs_lib::ax25beacon ALIAS ax25beacon)
-# Setup of the application
+# (2) Configure the library build
-set(EXE_NAME aprs_pico)
+set(TARGET_LIB_NAME aprs_pico)
-add_executable(${EXE_NAME}
- src/aprs_pico.c
+add_library(${TARGET_LIB_NAME}
+ src/aprs_pico.c)
+
+set_target_properties(${TARGET_LIB_NAME}
+ PROPERTIES
+ ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
)
-
-target_include_directories(${EXE_NAME} PRIVATE
+
+target_include_directories(${TARGET_LIB_NAME} PUBLIC
include
)
-#target_compile_options(${EXE_NAME} PRIVATE "-Wall")
+# Enable on demand
+#target_compile_options(${TARGET_LIB_NAME} PRIVATE "-Wundef;-Wall")
-# Set the console interface
-pico_enable_stdio_usb(aprs_pico 1) # USB
-# pico_enable_stdio_uart(aprs_pico 1) # UART
-
-# create map/bin/hex file etc.
-pico_add_extra_outputs(${EXE_NAME})
-
-target_link_libraries(${EXE_NAME}
- pico_stdlib
- pico_audio_pwm
+# This properly resolves the include dirs of the dependent libraries
+target_link_libraries(${TARGET_LIB_NAME}
ax25_aprs_lib::ax25beacon
+ pico_audio_pwm
+ pico_stdlib
+)
+
+
+# (3) Configure the example application build
+
+set(TARGET_EXAMPLE_EXE_NAME aprs_pico_example)
+
+add_executable(${TARGET_EXAMPLE_EXE_NAME}
+ src/aprs_pico_example.c
+)
+
+target_include_directories(${TARGET_EXAMPLE_EXE_NAME} PRIVATE
+ include
+)
+
+# Set the console interface
+pico_enable_stdio_usb(${TARGET_EXAMPLE_EXE_NAME} 1) # USB
+#pico_enable_stdio_uart(${TARGET_EXAMPLE_EXE_NAME} 1) # UART
+
+# create map/bin/hex file etc.
+pico_add_extra_outputs(${TARGET_EXAMPLE_EXE_NAME})
+
+target_link_libraries(${TARGET_EXAMPLE_EXE_NAME}
+ ${TARGET_LIB_NAME}
+ ax25_aprs_lib::ax25beacon
+ pico_audio_pwm
+ pico_stdlib
)
diff --git a/README.md b/README.md
index 9afa3fe..ddeda20 100644
--- a/README.md
+++ b/README.md
@@ -6,21 +6,25 @@ An analog line-out audio signal will be produced at GPIO-pin 'GP0'. You can obse
Basically, this is the data/signal flow:
```
-APRS (text msg + geo-coordinates + meta-data) -> AX.25 -> PCM -> PWM -> Low-Pass filtering -> AFSK signal
+APRS (text msg + geo-coordinates + meta-data) -> AX.25 -> PCM -> PWM -> Band-Pass filtering -> AFSK audio signal
```
+Both a static library `libaprs_pico.a` and an example application will be generated by the build.
+
## Preliminaries
Your host platform is assumed to be LINUX.
If you have already installed the Pico-SDK, set the `PICO_SDK_PATH` environment variable accordingly to avoid installing the SDK twice.
+
## Hardware
We just need a simple band-pass filter to extract the AFSK-signal from the PWM signal:
![band-pass filter](https://github.com/eleccoder/raspi-pico-aprs-tnc/blob/main/doc/schematic/band_pass_filter.png)
-## Build the application
+
+## Build the library and the example application
```
(cd into the cloned dir)
@@ -28,28 +32,27 @@ cmake -S . -B build
cmake --build build
```
-## Run the application
+`build/lib/libaprs_pico.a` and `build/aprs_pico_example.xxx` will be created.
+
+## Run the example application
```
cd build
-(flash 'aprs_pico.uf2' or 'aprs_pico.elf' to the Pico board as usual)
+(flash 'aprs_pico_example.uf2' or 'aprs_pico_example.elf' to the Pico board as usual)
```
The GPIO-pin 'GP0' is the line-out for the analog AFSK-signal. You can observe it by using a scope, listen to it by using an audio amp, or connect it to any RF transceiver to send it on the air (ham radio license required).
![AFSK scope screenshot](https://github.com/eleccoder/raspi-pico-aprs-tnc/blob/main/doc/img/afsk_scope.png "Scope screenshot of an AFSK output signal")
-## Modify the application
-
-To send an APRS message of your choice, you have to modify the *main()* function in `src/aprs_pico.c`.
## TODO (Aug 2021)
-- [x] Thorough evaluation
+- [x] Thorough evaluation, in general
- [x] Send the APRS message on the console (USB or UART) rather than hard-coding
-- [x] Code documentation
- [x] Show how to connect to a Baofeng HT
-- [x] PTT control for HTs
+- [x] PTT control for RF tranceivers
+
## Acknowledgements
diff --git a/include/aprs_pico.h b/include/aprs_pico.h
new file mode 100644
index 0000000..a6169b1
--- /dev/null
+++ b/include/aprs_pico.h
@@ -0,0 +1,44 @@
+/*
+* Project 'raspi-pico-aprs-tnc'
+* Copyright (C) 2021 Thomas Glau, DL3TG
+*
+* 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 APRS_PICO_H
+#define APRS_PICO_H
+
+#include
+#include
+
+// WARNING: ATTOW, the pico audio PWM lib worked only @ 22050 Hz sampling frequency and 48 MHz system clock
+// This is documented here: https://github.com/raspberrypi/pico-extras
+#define APRS_PICO__PICO_EXTRA_AUDIO_PWM_LIB_FIXED_SAMPLE_FREQ_IN_HZ (22050)
+
+
+void sendAPRS(const char* call_sign_src,
+ const char* call_sign_dst,
+ const char* aprs_path_1,
+ const char* aprs_path_2,
+ const char* aprs_message,
+ const double latitude_in_deg,
+ const double longitude_in_deg,
+ const double altitude_in_m,
+ const uint8_t volume,
+ const bool is_loop);
+
+
+void send1kHz(unsigned int sample_freq_in_hz, uint8_t volume);
+
+#endif // APRS_PICO_H
diff --git a/src/aprs_pico.c b/src/aprs_pico.c
index 63c9f54..c6d9abd 100644
--- a/src/aprs_pico.c
+++ b/src/aprs_pico.c
@@ -16,7 +16,8 @@
* along with this program. If not, see .
*/
-#include
+#include
+
#include
#include
@@ -26,21 +27,16 @@
#include
-// WARNING: ATTOW, the pico audio PWM lib worked only @ 22050 Hz sampling frequency and 48 MHz system clock
-// This is documented here: https://github.com/raspberrypi/pico-extras
-#define PICO_EXTRA_AUDIO_PWM_LIB_FIXED_SAMPLE_FREQ_IN_HZ (22050)
-#define SINE_WAVE_TEST (0) // For test & debug
-
-
typedef struct AudioCallBackUserData
{
- uint aprs_sample_freq_in_hz;
- bool is_loop;
- uint8_t volume;
+ unsigned int aprs_sample_freq_in_hz;
+ bool is_loop;
+ uint8_t volume;
+
} AudioCallBackUserData_t;
-audio_buffer_pool_t* init_audio(uint sample_freq_in_hz, uint16_t audio_buffer_format)
+static audio_buffer_pool_t* init_audio(unsigned int sample_freq_in_hz, uint16_t audio_buffer_format)
{
const int NUM_AUDIO_BUFFERS = 3;
const int SAMPLES_PER_BUFFER = 256;
@@ -68,12 +64,12 @@ audio_buffer_pool_t* init_audio(uint sample_freq_in_hz, uint16_t audio_buffer_fo
}
-static void init(uint sample_freq_in_hz)
+static void init(unsigned int sample_freq_in_hz)
{
// WARNING: ATTOW, the pico audio PWM lib worked only @ 22050 Hz sampling frequency and 48 MHz system clock
// This is documented here: https://github.com/raspberrypi/pico-extras
- if (sample_freq_in_hz == PICO_EXTRA_AUDIO_PWM_LIB_FIXED_SAMPLE_FREQ_IN_HZ)
+ if (sample_freq_in_hz == APRS_PICO__PICO_EXTRA_AUDIO_PWM_LIB_FIXED_SAMPLE_FREQ_IN_HZ)
{
// This is the safe case, see the comment above
set_sys_clock_48mhz();
@@ -83,7 +79,7 @@ static void init(uint sample_freq_in_hz)
// Compensate a non-'PICO_EXTRA_AUDIO_PWM_LIB_FIXED_SAMPLE_FREQ_IN_HZ' sampling frequency
// by a adapting the system clock accordingly
- float sys_clock_in_mhz = 48.0f * (float)sample_freq_in_hz / (float)PICO_EXTRA_AUDIO_PWM_LIB_FIXED_SAMPLE_FREQ_IN_HZ;
+ float sys_clock_in_mhz = 48.0f * (float)sample_freq_in_hz / (float)APRS_PICO__PICO_EXTRA_AUDIO_PWM_LIB_FIXED_SAMPLE_FREQ_IN_HZ;
// Round to full Mhz to increase the chance that 'set_sys_clock_khz()' can exactly realize this frequency
sys_clock_in_mhz = round(sys_clock_in_mhz);
@@ -96,9 +92,9 @@ static void init(uint sample_freq_in_hz)
static void fill_audio_buffer(audio_buffer_pool_t* audio_pool, const int16_t* pcm_data,
- uint num_samples, uint8_t volume, bool is_loop_forever)
+ unsigned int num_samples, uint8_t volume, bool is_loop_forever)
{
- uint pos = 0u;
+ unsigned int pos = 0u;
bool is_keep_going = true;
while (is_keep_going)
@@ -106,7 +102,7 @@ static void fill_audio_buffer(audio_buffer_pool_t* audio_pool, const int16_t* pc
audio_buffer_t* buffer = take_audio_buffer(audio_pool, true);
int16_t* samples = (int16_t*)buffer->buffer->bytes;
- for (uint i = 0u; i < buffer->max_sample_count; i++)
+ for (unsigned int i = 0u; i < buffer->max_sample_count; i++)
{
samples[i] = (volume * pcm_data[pos]) >> 8u;
pos++;
@@ -131,13 +127,23 @@ static void fill_audio_buffer(audio_buffer_pool_t* audio_pool, const int16_t* pc
}
+static void sendAPRS_audioCallback(void* callback_user_data, int16_t* pcm_data, size_t num_samples)
+{
+ const AudioCallBackUserData_t user_data = *((const AudioCallBackUserData_t*)callback_user_data);
+
+ audio_buffer_pool_t* audio_pool = init_audio(user_data.aprs_sample_freq_in_hz, AUDIO_BUFFER_FORMAT_PCM_S16);
+
+ fill_audio_buffer(audio_pool, pcm_data, num_samples, user_data.volume, user_data.is_loop);
+}
+
+
// Test tone: 1 kHz sine wave
-static void send1kHz(uint sample_freq_in_hz, uint8_t volume)
+void send1kHz( unsigned int sample_freq_in_hz, uint8_t volume)
{
init(sample_freq_in_hz);
- const uint tone_freq_in_hz = 1000u;
- const uint num_samples = sample_freq_in_hz / tone_freq_in_hz;
+ const unsigned int TONE_FREQ_IN_HZ = 1000u;
+ const unsigned int num_samples = sample_freq_in_hz / TONE_FREQ_IN_HZ;
int16_t* sine_wave_table = malloc(num_samples * sizeof(int16_t));
@@ -146,7 +152,7 @@ static void send1kHz(uint sample_freq_in_hz, uint8_t volume)
panic("Out of memory: malloc() failed.\n");
}
- for (uint i = 0u; i < num_samples; i++)
+ for (unsigned int i = 0u; i < num_samples; i++)
{
sine_wave_table[i] = (int16_t)(32767.0f * sinf(2.0f * (float)M_PI * (float)i / (float)num_samples));
}
@@ -159,26 +165,16 @@ static void send1kHz(uint sample_freq_in_hz, uint8_t volume)
}
-static void sendAPRS_audioCallback(void* callback_user_data, int16_t* pcm_data, size_t num_samples)
-{
- const AudioCallBackUserData_t user_data = *((const AudioCallBackUserData_t*)callback_user_data);
-
- audio_buffer_pool_t* audio_pool = init_audio(user_data.aprs_sample_freq_in_hz, AUDIO_BUFFER_FORMAT_PCM_S16);
-
- fill_audio_buffer(audio_pool, pcm_data, num_samples, user_data.volume, user_data.is_loop);
-}
-
-
-static void sendAPRS(const char* call_sign_src,
- const char* call_sign_dst,
- const char* aprs_path_1,
- const char* aprs_path_2,
- const char* aprs_message,
- const double latitude_in_deg,
- const double longitude_in_deg,
- const double altitude_in_m,
- const uint8_t volume,
- const bool is_loop)
+void sendAPRS(const char* call_sign_src,
+ const char* call_sign_dst,
+ const char* aprs_path_1,
+ const char* aprs_path_2,
+ const char* aprs_message,
+ const double latitude_in_deg,
+ const double longitude_in_deg,
+ const double altitude_in_m,
+ const uint8_t volume,
+ const bool is_loop)
{
static AudioCallBackUserData_t callback_user_data;
@@ -200,30 +196,3 @@ static void sendAPRS(const char* call_sign_src,
aprs_message,
'/', 'O');
}
-
-
-int main()
-{
-#if (SINE_WAVE_TEST == 1)
-
- const uint8_t VOLUME = 128u;
- send1kHz(PICO_EXTRA_AUDIO_PWM_LIB_FIXED_SAMPLE_FREQ_IN_HZ, VOLUME);
-
-#else // !SINE_WAVE_TEST
-
- // Send an APRS test message
- sendAPRS("SRC", // Src call sign
- "DST", // Dst call sign
- "PATH1",
- "PATH2",
- "Test message",
- 10.0, // Lat in deg
- 20.0, // Long in deg
- 100.0, // Alt in m
- 128u, // Volume (0 ... 255)
- false); // Loop forever
-
-#endif // SINE_WAVE_TEST, !SINE_WAVE_TEST
-
- return 0;
-}
diff --git a/src/aprs_pico_example.c b/src/aprs_pico_example.c
new file mode 100644
index 0000000..7999549
--- /dev/null
+++ b/src/aprs_pico_example.c
@@ -0,0 +1,48 @@
+/*
+* Project 'raspi-pico-aprs-tnc'
+* Copyright (C) 2021 Thomas Glau, DL3TG
+*
+* 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
+
+#define SINE_WAVE_TEST (0) // For test & debug
+
+
+int main()
+{
+#if (SINE_WAVE_TEST == 1)
+
+ const uint8_t VOLUME = 128u;
+ send1kHz(APRS_PICO__PICO_EXTRA_AUDIO_PWM_LIB_FIXED_SAMPLE_FREQ_IN_HZ, VOLUME);
+
+#else // !SINE_WAVE_TEST
+
+ // Send an APRS test message
+ sendAPRS("SRC", // Src call sign
+ "DST", // Dst call sign
+ "PATH1",
+ "PATH2",
+ "Test message",
+ 10.0, // Lat in deg
+ 20.0, // Long in deg
+ 100.0, // Alt in m
+ 128u, // Volume (0 ... 255)
+ false); // Loop forever
+
+#endif // SINE_WAVE_TEST, !SINE_WAVE_TEST
+
+ return 0;
+}