2021-07-31 22:26:44 +00:00
|
|
|
/*
|
|
|
|
* Project 'raspi-pico-aprs-tnc'
|
2022-12-21 17:14:45 +00:00
|
|
|
* Copyright (C) 2021-2023 Thomas Glau, DL3TG
|
2021-07-31 22:26:44 +00:00
|
|
|
*
|
|
|
|
* 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
|
2022-12-22 20:41:30 +00:00
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2021-07-31 22:26:44 +00:00
|
|
|
*/
|
|
|
|
|
2022-12-22 20:41:30 +00:00
|
|
|
#include "aprs_pico.h"
|
2021-08-01 15:18:36 +00:00
|
|
|
|
2022-12-22 20:41:30 +00:00
|
|
|
#include "ax25beacon.h"
|
|
|
|
#include "pico/stdlib.h"
|
2021-07-31 22:26:44 +00:00
|
|
|
|
2022-12-22 20:41:30 +00:00
|
|
|
#include <math.h>
|
2021-07-31 22:26:44 +00:00
|
|
|
|
|
|
|
|
2022-12-22 20:41:30 +00:00
|
|
|
// NOTE: ATTOW, the pico-extra audio PWM lib worked only at a fixed 22050 Hz sampling frequency, while the
|
|
|
|
// system clock runs at 48 MHz. This is documented here: https://github.com/raspberrypi/pico-extras
|
|
|
|
#define APRS_PICO__PICO_EXTRA_AUDIO_PWM_LIB__FIXED_SAMPLE_FREQ_IN_HZ (22050)
|
|
|
|
#define APRS_PICO__PICO_EXTRA_AUDIO_PWM_LIB__SYS_CLOCK_FREQ_OF_IN_MHZ (48)
|
2021-08-03 20:54:07 +00:00
|
|
|
|
|
|
|
|
2021-08-01 13:29:00 +00:00
|
|
|
typedef struct AudioCallBackUserData
|
|
|
|
{
|
2021-08-03 20:54:07 +00:00
|
|
|
audio_buffer_pool_t* audio_buffer_pool; // The pool of audio buffers to be used for rendering an audio signal
|
|
|
|
uint16_t volume; // Valid range: 0 ... 256
|
2021-08-01 15:18:36 +00:00
|
|
|
|
2021-08-01 13:29:00 +00:00
|
|
|
} AudioCallBackUserData_t;
|
|
|
|
|
2021-07-31 22:26:44 +00:00
|
|
|
|
2021-08-01 16:24:32 +00:00
|
|
|
/** \brief Init function for the Pico audio PWM library
|
|
|
|
*
|
2021-08-02 21:02:43 +00:00
|
|
|
* \param[in] sample_freq_in_hz The sampling frequency to be used for audio signals
|
|
|
|
* \param[in] audio_buffer_format The format of the audio buffers to be created, representing
|
|
|
|
* the data format of the audio samples
|
2021-08-01 16:24:32 +00:00
|
|
|
*
|
2021-08-03 20:54:07 +00:00
|
|
|
* \return A pool of audio buffers to be used for rendering any audio signal
|
2021-08-01 16:24:32 +00:00
|
|
|
*/
|
2021-08-03 20:54:07 +00:00
|
|
|
static audio_buffer_pool_t* aprs_pico_initAudio(unsigned int sample_freq_in_hz, uint16_t audio_buffer_format)
|
2021-07-31 22:26:44 +00:00
|
|
|
{
|
|
|
|
const int NUM_AUDIO_BUFFERS = 3;
|
|
|
|
const int SAMPLES_PER_BUFFER = 256;
|
|
|
|
|
|
|
|
const audio_format_t audio_format = {.format = audio_buffer_format,
|
|
|
|
.sample_freq = sample_freq_in_hz,
|
|
|
|
.channel_count = 1};
|
|
|
|
|
|
|
|
audio_buffer_format_t producer_format = {.format = &audio_format,
|
|
|
|
.sample_stride = 2};
|
|
|
|
|
|
|
|
audio_buffer_pool_t* producer_pool = audio_new_producer_pool(&producer_format, NUM_AUDIO_BUFFERS, SAMPLES_PER_BUFFER);
|
|
|
|
|
|
|
|
if (!audio_pwm_setup(&audio_format, -1, &default_mono_channel_config))
|
|
|
|
{
|
|
|
|
panic("PicoAudio: Unable to open audio device.\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
bool __unused is_ok = audio_pwm_default_connect(producer_pool, false);
|
|
|
|
assert(is_ok);
|
|
|
|
|
|
|
|
audio_pwm_set_enabled(true);
|
|
|
|
|
|
|
|
return producer_pool;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-08-03 20:54:07 +00:00
|
|
|
/** \brief Init function for the Pico's clock system
|
2021-08-01 16:24:32 +00:00
|
|
|
*
|
2021-08-02 21:02:43 +00:00
|
|
|
* \param[in] sample_freq_in_hz The sampling frequency to be used for rendering audio signals
|
2021-08-01 16:24:32 +00:00
|
|
|
*/
|
2021-08-03 20:54:07 +00:00
|
|
|
static void aprs_pico_initClock(unsigned int sample_freq_in_hz)
|
2021-07-31 22:26:44 +00:00
|
|
|
{
|
2022-12-22 20:41:30 +00:00
|
|
|
// NOTE: ATTOW, the pico-extra audio PWM lib worked only at a fixed 22050 Hz sampling frequency, while the
|
|
|
|
// system clock runs at 48 MHz. This is documented here: https://github.com/raspberrypi/pico-extras
|
2021-07-31 22:26:44 +00:00
|
|
|
|
2022-12-22 20:41:30 +00:00
|
|
|
// Compensate a non-'APRS_PICO__PICO_EXTRA_AUDIO_PWM_LIB__FIXED_SAMPLE_FREQ_IN_HZ' sampling frequency
|
|
|
|
// by adapting the system clock frequency accordingly.
|
2021-08-01 16:24:32 +00:00
|
|
|
|
2022-12-22 20:41:30 +00:00
|
|
|
float sys_clock_in_mhz = (float)APRS_PICO__PICO_EXTRA_AUDIO_PWM_LIB__SYS_CLOCK_FREQ_OF_IN_MHZ *
|
|
|
|
((float)sample_freq_in_hz / (float)APRS_PICO__PICO_EXTRA_AUDIO_PWM_LIB__FIXED_SAMPLE_FREQ_IN_HZ);
|
2021-08-01 12:09:03 +00:00
|
|
|
|
2021-08-04 12:46:52 +00:00
|
|
|
if (!set_sys_clock_khz((uint32_t)(1000.0f * sys_clock_in_mhz), false))
|
|
|
|
{
|
|
|
|
// Round to full MHz to increase the chance that 'set_sys_clock_khz()' can exactly attain this frequency
|
2021-08-01 12:09:03 +00:00
|
|
|
sys_clock_in_mhz = round(sys_clock_in_mhz);
|
2021-07-31 22:26:44 +00:00
|
|
|
|
2021-08-03 20:54:07 +00:00
|
|
|
// With the second parameter set 'true', the function will assert if the frequency is not attainable
|
|
|
|
set_sys_clock_khz(1000u * (uint32_t)sys_clock_in_mhz, true);
|
2021-07-31 22:26:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-08-01 16:24:32 +00:00
|
|
|
/** \brief Renders given PCM audio samples
|
|
|
|
*
|
2021-08-03 20:54:07 +00:00
|
|
|
* \param[in, out] audio_buffer_pool The pool of audio buffers to be used for rendering any audio signal
|
|
|
|
* \param[in] pcm_data The PCM audio samples to be rendered
|
|
|
|
* \param[in] num_samples The number of PCM audio samples to be rendered
|
|
|
|
* \param[in] volume The volume level of the generated AFSK signal (0 ... 256)
|
2022-12-21 17:13:43 +00:00
|
|
|
* \param[in] is_loop_forever If 'true', rendering of the audio samples will be continuously repeated
|
2021-08-01 16:24:32 +00:00
|
|
|
*/
|
2021-08-03 20:54:07 +00:00
|
|
|
static void aprs_pico_renderAudioSamples(audio_buffer_pool_t* audio_buffer_pool, const int16_t* pcm_data,
|
|
|
|
unsigned int num_samples, uint16_t volume, bool is_loop_forever)
|
2021-07-31 22:26:44 +00:00
|
|
|
{
|
2021-08-03 20:54:07 +00:00
|
|
|
assert(audio_buffer_pool != NULL);
|
|
|
|
assert(pcm_data != NULL);
|
|
|
|
|
2022-12-21 21:00:47 +00:00
|
|
|
unsigned int idx_src = 0u;
|
|
|
|
bool is_all_samples_processed = num_samples == 0u;
|
2021-08-01 13:29:00 +00:00
|
|
|
|
2022-12-21 21:00:47 +00:00
|
|
|
// Write the PCM sample data into the next audio buffer while applying the 'volume' value
|
|
|
|
while (!is_all_samples_processed)
|
2021-07-31 22:26:44 +00:00
|
|
|
{
|
2022-12-21 17:13:43 +00:00
|
|
|
audio_buffer_t* audio_buffer = take_audio_buffer(audio_buffer_pool, true);
|
|
|
|
int16_t* audio_buffer_pcm_data = (int16_t*)audio_buffer->buffer->bytes;
|
2021-07-31 22:26:44 +00:00
|
|
|
|
2022-12-21 21:00:47 +00:00
|
|
|
unsigned int idx_dst = 0u;
|
|
|
|
|
|
|
|
while (!is_all_samples_processed && (idx_dst < audio_buffer->max_sample_count))
|
2021-07-31 22:26:44 +00:00
|
|
|
{
|
2022-12-21 17:13:43 +00:00
|
|
|
audio_buffer_pcm_data[idx_dst] = ((int32_t)volume * (int32_t)pcm_data[idx_src]) >> 8u;
|
2022-12-21 21:00:47 +00:00
|
|
|
|
2022-12-21 17:13:43 +00:00
|
|
|
idx_src++;
|
2022-12-21 21:00:47 +00:00
|
|
|
idx_dst++;
|
2021-07-31 22:26:44 +00:00
|
|
|
|
2022-12-21 17:13:43 +00:00
|
|
|
if (idx_src == num_samples)
|
2021-07-31 22:26:44 +00:00
|
|
|
{
|
2021-08-01 13:29:00 +00:00
|
|
|
if (is_loop_forever)
|
|
|
|
{
|
2022-12-21 17:13:43 +00:00
|
|
|
idx_src = 0u;
|
2021-08-01 13:29:00 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-12-21 21:00:47 +00:00
|
|
|
is_all_samples_processed = true;
|
2021-08-01 13:29:00 +00:00
|
|
|
}
|
2021-07-31 22:26:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-21 21:00:47 +00:00
|
|
|
assert(idx_src <= num_samples);
|
|
|
|
assert(idx_dst <= audio_buffer->max_sample_count);
|
|
|
|
|
|
|
|
audio_buffer->sample_count = idx_dst;
|
2022-12-21 17:13:43 +00:00
|
|
|
give_audio_buffer(audio_buffer_pool, audio_buffer);
|
2021-07-31 22:26:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-08-01 16:24:32 +00:00
|
|
|
/** \brief The callback function to render the generated PCM audio samples of an APRS message
|
|
|
|
*
|
2021-08-02 21:02:43 +00:00
|
|
|
* \param[in] callback_user_data User data provided by the caller function of this callback
|
|
|
|
* \param[in] pcm_data The PCM audio samples to be rendered
|
|
|
|
* \param[in] num_samples The number of samples the PCM data consist of
|
2022-12-22 09:16:18 +00:00
|
|
|
* \param[in] sample_freq_in_hz The sample frequency of the PCM data (in Hz)
|
2021-08-01 16:24:32 +00:00
|
|
|
*/
|
2022-12-22 09:16:18 +00:00
|
|
|
static void aprs_pico_sendAPRSAudioCallback(const void* callback_user_data, const int16_t* pcm_data, size_t num_samples, uint16_t sample_freq_in_hz)
|
2021-08-01 15:18:36 +00:00
|
|
|
{
|
2021-08-03 20:54:07 +00:00
|
|
|
assert(callback_user_data != NULL);
|
|
|
|
assert(pcm_data != NULL);
|
|
|
|
|
2021-08-02 21:02:43 +00:00
|
|
|
const AudioCallBackUserData_t user_data = *((AudioCallBackUserData_t*)callback_user_data);
|
2021-08-01 15:18:36 +00:00
|
|
|
|
2022-12-22 09:16:18 +00:00
|
|
|
aprs_pico_initClock(sample_freq_in_hz);
|
2021-08-03 20:54:07 +00:00
|
|
|
aprs_pico_renderAudioSamples(user_data.audio_buffer_pool, pcm_data, num_samples, user_data.volume, false);
|
|
|
|
}
|
|
|
|
|
2021-08-01 15:18:36 +00:00
|
|
|
|
2021-08-03 20:54:07 +00:00
|
|
|
// See the header file for documentation
|
|
|
|
audio_buffer_pool_t* aprs_pico_init()
|
|
|
|
{
|
2022-12-22 20:41:30 +00:00
|
|
|
audio_buffer_pool_t* audio_buffer_pool = aprs_pico_initAudio(APRS_PICO__PICO_EXTRA_AUDIO_PWM_LIB__FIXED_SAMPLE_FREQ_IN_HZ,
|
2021-08-04 12:46:52 +00:00
|
|
|
AUDIO_BUFFER_FORMAT_PCM_S16);
|
|
|
|
return audio_buffer_pool;
|
2021-08-01 15:18:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-08-01 16:24:32 +00:00
|
|
|
// See the header file for documentation
|
2022-12-21 22:08:27 +00:00
|
|
|
void aprs_pico_send_sine_wave(audio_buffer_pool_t* audio_buffer_pool, unsigned int freq_in_hz, unsigned int sample_freq_in_hz, uint16_t volume)
|
2021-07-31 22:26:44 +00:00
|
|
|
{
|
2021-08-03 20:54:07 +00:00
|
|
|
assert(audio_buffer_pool != NULL);
|
|
|
|
|
2022-12-21 17:13:43 +00:00
|
|
|
typedef int16_t wave_table_value_t;
|
|
|
|
const wave_table_value_t WAVE_TABLE_VALUE_MAX = INT16_MAX;
|
2021-07-31 22:26:44 +00:00
|
|
|
|
|
|
|
|
2022-12-21 22:08:27 +00:00
|
|
|
aprs_pico_initClock(sample_freq_in_hz);
|
|
|
|
|
|
|
|
const unsigned int num_samples_per_period = sample_freq_in_hz / freq_in_hz;
|
2021-07-31 22:26:44 +00:00
|
|
|
|
2022-12-21 17:13:43 +00:00
|
|
|
wave_table_value_t* sine_period_wave_table = malloc(num_samples_per_period * sizeof(wave_table_value_t));
|
|
|
|
|
|
|
|
if (sine_period_wave_table == NULL)
|
2021-07-31 22:26:44 +00:00
|
|
|
{
|
|
|
|
panic("Out of memory: malloc() failed.\n");
|
|
|
|
}
|
|
|
|
|
2022-12-21 17:13:43 +00:00
|
|
|
for (unsigned int i = 0u; i < num_samples_per_period; i++)
|
2021-07-31 22:26:44 +00:00
|
|
|
{
|
2022-12-21 17:13:43 +00:00
|
|
|
sine_period_wave_table[i] = (wave_table_value_t)((float)WAVE_TABLE_VALUE_MAX * sinf(2.0f * (float)M_PI * (float)i / (float)num_samples_per_period));
|
2021-07-31 22:26:44 +00:00
|
|
|
}
|
|
|
|
|
2022-12-21 17:13:43 +00:00
|
|
|
aprs_pico_renderAudioSamples(audio_buffer_pool, sine_period_wave_table, num_samples_per_period, volume, true);
|
2021-07-31 22:26:44 +00:00
|
|
|
|
2022-12-21 17:13:43 +00:00
|
|
|
free(sine_period_wave_table);
|
2021-07-31 22:26:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-08-01 16:24:32 +00:00
|
|
|
// See the header file for documentation
|
2021-08-04 12:46:52 +00:00
|
|
|
bool aprs_pico_sendAPRS(audio_buffer_pool_t* audio_buffer_pool,
|
2021-08-03 20:54:07 +00:00
|
|
|
const char* call_sign_src,
|
|
|
|
const char* call_sign_dst,
|
|
|
|
const char* aprs_path_1,
|
|
|
|
const char* aprs_path_2,
|
|
|
|
const char* aprs_message,
|
|
|
|
double latitude_in_deg,
|
|
|
|
double longitude_in_deg,
|
|
|
|
double altitude_in_m,
|
|
|
|
uint16_t volume)
|
2021-07-31 22:26:44 +00:00
|
|
|
{
|
2021-08-03 20:54:07 +00:00
|
|
|
// NOTE: 'aprs_message' is allowed to be 'NULL'
|
|
|
|
assert(audio_buffer_pool != NULL);
|
|
|
|
assert(call_sign_src != NULL);
|
|
|
|
assert(call_sign_dst != NULL);
|
|
|
|
assert(aprs_path_1 != NULL);
|
|
|
|
assert(aprs_path_2 != NULL);
|
|
|
|
|
2021-08-01 13:29:00 +00:00
|
|
|
static AudioCallBackUserData_t callback_user_data;
|
|
|
|
|
2022-12-22 09:16:18 +00:00
|
|
|
callback_user_data.audio_buffer_pool = audio_buffer_pool;
|
|
|
|
callback_user_data.volume = volume;
|
2021-07-31 22:26:44 +00:00
|
|
|
|
2021-08-04 12:46:52 +00:00
|
|
|
int ret_val = ax25_beacon((void*)&callback_user_data,
|
|
|
|
aprs_pico_sendAPRSAudioCallback,
|
|
|
|
call_sign_src,
|
|
|
|
call_sign_dst,
|
|
|
|
aprs_path_1,
|
|
|
|
aprs_path_2,
|
|
|
|
latitude_in_deg,
|
|
|
|
longitude_in_deg,
|
|
|
|
altitude_in_m,
|
|
|
|
aprs_message,
|
|
|
|
'/', 'O');
|
|
|
|
|
|
|
|
return ret_val == AX25_OK;
|
2021-07-31 22:26:44 +00:00
|
|
|
}
|