436 lines
14 KiB
C
436 lines
14 KiB
C
/* -*- mode: c; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4; coding: utf-8 -*- */
|
|
/************************************************************************************
|
|
** **
|
|
** mcHF QRP Transceiver **
|
|
** K Atanassov - M0NKA 2014 **
|
|
** **
|
|
**---------------------------------------------------------------------------------**
|
|
** **
|
|
** File name: **
|
|
** Description: **
|
|
** Last Modified: **
|
|
** Licence: GNU GPLv3 **
|
|
************************************************************************************/
|
|
#include "ui_driver.h"
|
|
#include "freedv_uhsdr.h"
|
|
#include "ui_lcd_layouts.h"
|
|
#include "profiling.h"
|
|
#include "ui_lcd_hy28.h"
|
|
#include "radio_management.h"
|
|
|
|
#if defined(USE_FREEDV) || defined(USE_ALTERNATE_NR)
|
|
|
|
MultiModeBuffer_t __MCHF_SPECIALMEM mmb;
|
|
#endif
|
|
|
|
#ifdef USE_FREEDV
|
|
|
|
#include "freedv_api.h"
|
|
|
|
freedv_conf_t freedv_conf;
|
|
|
|
struct freedv *f_FREEDV;
|
|
|
|
|
|
RingBuffer_DefineExtMem(fdv_demod_rb,sizeof(mmb.fdv_demod_buff)/sizeof(fdv_demod_rb_item_t), mmb.fdv_demod_buff)
|
|
RingBuffer_DefineExtMem(fdv_iq_rb,sizeof(mmb.fdv_iq_buff)/sizeof(fdv_iq_rb_item_t), mmb.fdv_iq_buff)
|
|
|
|
#define FDV_AUDIO_MEM_SIZE ((FDV_BUFFER_SIZE*2)+IQ_BLOCK_SIZE)
|
|
__MCHF_SPECIALMEM fdv_audio_rb_item_t fdv_audio_rb_mem[FDV_AUDIO_MEM_SIZE];
|
|
RingBuffer_DefineExtMem(fdv_audio_rb, FDV_AUDIO_MEM_SIZE, fdv_audio_rb_mem)
|
|
|
|
typedef struct {
|
|
int32_t start;
|
|
int32_t offset;
|
|
int32_t count;
|
|
} flex_buffer;
|
|
|
|
static uint16_t freedv_display_x_offset;
|
|
|
|
/**
|
|
* Returns the internal UHSDR configuration value in the value range for display and use with the freedv_api
|
|
* @param freedv_conf_p
|
|
* @return SNR in the range of -100 to 99
|
|
*/
|
|
|
|
int32_t FreeDV_Get_Squelch_SNR(freedv_conf_t* freedv_conf_p)
|
|
{
|
|
return (int32_t)(freedv_conf_p->squelch_snr_thresh) + FDV_SQUELCH_OFFSET;
|
|
}
|
|
|
|
/**
|
|
* Sets the internal UHSDR configuration value in the permitted value range
|
|
* @param freedv_conf_p
|
|
* @param squelch_snr SNR in the range of -100 to 99 , -100 represents OFF
|
|
*/
|
|
void FreeDV_Set_Squelch_SNR(freedv_conf_t* freedv_conf_p, int8_t squelch_snr)
|
|
{
|
|
int32_t internal_snr = squelch_snr - FDV_SQUELCH_OFFSET;
|
|
if (internal_snr > FDV_SQUELCH_MAX)
|
|
{
|
|
internal_snr = FDV_SQUELCH_MAX;
|
|
}
|
|
else if (internal_snr < FDV_SQUELCH_OFF)
|
|
{
|
|
internal_snr = FDV_SQUELCH_OFF;
|
|
}
|
|
|
|
freedv_conf_p->squelch_snr_thresh = internal_snr;
|
|
}
|
|
|
|
int FreeDV_Is_Squelch_Enable(freedv_conf_t* freedv_conf_p)
|
|
{
|
|
return freedv_conf_p->squelch_snr_thresh != FDV_SQUELCH_OFF;
|
|
}
|
|
|
|
void FreeDV_Squelch_Update(freedv_conf_t* freedv_conf_p)
|
|
{
|
|
UNUSED(freedv_conf_p);
|
|
freedv_set_squelch_en(f_FREEDV, FreeDV_Is_Squelch_Enable(&freedv_conf));
|
|
freedv_set_snr_squelch_thresh(f_FREEDV, FreeDV_Get_Squelch_SNR(&freedv_conf));
|
|
}
|
|
|
|
static void FreeDv_DisplayBer(void)
|
|
{
|
|
int ber = 0;
|
|
char ber_string[12];
|
|
|
|
|
|
|
|
ber = 1000*freedv_get_total_bit_errors(f_FREEDV)/freedv_get_total_bits(f_FREEDV);
|
|
snprintf(ber_string,12,"0.%03d",ber); //calculate and display the bit error rate
|
|
UiLcdHy28_PrintText(ts.Layout->FREEDV_BER.x + freedv_display_x_offset,ts.Layout->FREEDV_BER.y, ber_string,Yellow,Black, ts.Layout->FREEDV_FONT);
|
|
|
|
}
|
|
|
|
static void FreeDv_DisplaySnr(void)
|
|
{
|
|
static float SNR = 1;
|
|
float SNR_est;
|
|
char SNR_string[12];
|
|
int sync;
|
|
|
|
freedv_get_modem_stats(f_FREEDV,&sync,&SNR_est);
|
|
|
|
SNR = 0.95*SNR + 0.05 * SNR_est; //some averaging to keep it more calm
|
|
int32_t SNR_Int = roundf(SNR);
|
|
|
|
if (SNR_Int > 99)
|
|
{
|
|
SNR_Int = 99;
|
|
}
|
|
else if (SNR_Int < -99)
|
|
{
|
|
SNR_Int = -99;
|
|
}
|
|
|
|
uint32_t clr_fg;
|
|
if (FreeDV_Is_Squelch_Enable(&freedv_conf))
|
|
{
|
|
clr_fg = SNR_Int >= FreeDV_Get_Squelch_SNR(&freedv_conf)? Green:Red;
|
|
}
|
|
else
|
|
{
|
|
clr_fg = Yellow;
|
|
}
|
|
|
|
snprintf(SNR_string,12,"%-2ld",SNR_Int); //Display the current SNR and round it up to the next int
|
|
UiLcdHy28_PrintText(ts.Layout->FREEDV_SNR.x + freedv_display_x_offset, ts.Layout->FREEDV_SNR.y ,SNR_string,clr_fg,Black, ts.Layout->FREEDV_FONT);
|
|
}
|
|
|
|
void FreeDv_DisplayClear()
|
|
{
|
|
// UiLcdHy28_PrintText(ts.Layout->FREEDV_SNR.x, ts.Layout->FREEDV_SNR.y," ",Yellow,Black,ts.Layout->FREEDV_FONT);
|
|
// UiLcdHy28_PrintText(ts.Layout->FREEDV_BER.x, ts.Layout->FREEDV_BER.y," ",Yellow,Black,ts.Layout->FREEDV_FONT);
|
|
UiLcdHy28_PrintText(ts.Layout->FREEDV_SNR.x, ts.Layout->FREEDV_SNR.y," ",Yellow,Black,ts.Layout->FREEDV_FONT); //SNR=00
|
|
UiLcdHy28_PrintText(ts.Layout->FREEDV_BER.x, ts.Layout->FREEDV_BER.y," ",Yellow,Black,ts.Layout->FREEDV_FONT); //BER=0.000 (max 9 chars)
|
|
UiDriver_TextMsgClear();
|
|
}
|
|
|
|
void FreeDv_DisplayPrepare()
|
|
{
|
|
UiDriver_TextMsgClear();
|
|
freedv_display_x_offset = UiLcdHy28_TextWidth("SNR=", ts.Layout->FREEDV_FONT);
|
|
UiLcdHy28_PrintText(ts.Layout->FREEDV_SNR.x, ts.Layout->FREEDV_SNR.y,"SNR=",Yellow,Black, ts.Layout->FREEDV_FONT);
|
|
UiLcdHy28_PrintText(ts.Layout->FREEDV_BER.x, ts.Layout->FREEDV_BER.y,"BER=",Yellow,Black, ts.Layout->FREEDV_FONT);
|
|
}
|
|
|
|
void FreeDv_DisplayUpdate()
|
|
{
|
|
FreeDv_DisplayBer();
|
|
FreeDv_DisplaySnr();
|
|
UiDriver_TextMsgDisplay();
|
|
}
|
|
|
|
|
|
void FreeDv_HandleFreeDv()
|
|
{
|
|
|
|
// Freedv DL2FW
|
|
// static FDV_Out_Buffer FDV_TX_out_im_buff;
|
|
static bool tx_was_here = false;
|
|
static bool rx_was_here = true;
|
|
|
|
|
|
// we are always called, so check if there is FreeDV active
|
|
if (ts.digital_mode == DigitalMode_FreeDV)
|
|
{
|
|
if ((ts.txrx_mode == TRX_MODE_TX)
|
|
&& RingBuffer_GetData(&fdv_audio_rb) >= freedv_get_n_speech_samples(f_FREEDV)
|
|
&& RingBuffer_GetRoom(&fdv_iq_rb) >= freedv_get_n_nom_modem_samples(f_FREEDV))
|
|
{
|
|
// ...and if we are transmitting and samples from dv_tx_processor are ready
|
|
if (tx_was_here == false)
|
|
{
|
|
RingBuffer_ClearGetTail(&fdv_audio_rb);
|
|
RingBuffer_ClearPutHead(&fdv_iq_rb);
|
|
tx_was_here = true;
|
|
rx_was_here = false;
|
|
}
|
|
COMP iq_buffer[freedv_get_n_nom_modem_samples(f_FREEDV)];
|
|
int16_t audio_buffer[freedv_get_n_speech_samples(f_FREEDV)];
|
|
|
|
RingBuffer_GetSamples(&fdv_audio_rb, &audio_buffer, freedv_get_n_speech_samples(f_FREEDV));
|
|
|
|
profileTimedEventStart(7);
|
|
freedv_comptx(f_FREEDV,
|
|
iq_buffer,
|
|
audio_buffer); // start the encoding process
|
|
profileTimedEventStop(7);
|
|
|
|
for (int idx = 0; idx < freedv_get_n_nom_modem_samples(f_FREEDV); idx++)
|
|
{
|
|
fdv_iq_rb_item_t sample;
|
|
sample.real = iq_buffer[idx].real;
|
|
sample.imag = iq_buffer[idx].imag;
|
|
|
|
RingBuffer_PutSamples(&fdv_iq_rb, &sample, 1);
|
|
}
|
|
|
|
}
|
|
else if ((ts.txrx_mode == TRX_MODE_RX))
|
|
{
|
|
|
|
|
|
if (rx_was_here == false)
|
|
{
|
|
RingBuffer_ClearGetTail(&fdv_demod_rb);
|
|
RingBuffer_ClearPutHead(&fdv_audio_rb);
|
|
|
|
freedv_set_total_bit_errors(f_FREEDV,0); //reset ber calculation after coming from TX
|
|
freedv_set_total_bits(f_FREEDV,0);
|
|
|
|
rx_was_here = true; // this is used to clear buffers when going into TX
|
|
tx_was_here = false;
|
|
}
|
|
|
|
// these buffers are large enough to hold the requested/provided amount of data for freedv_comprx
|
|
// these are larger than the FDV_BUFFER_SIZE since some more bytes may be asked for.
|
|
#ifdef USE_SIMPLE_FREEDV_FILTERS
|
|
#define input_rb fdv_iq_rb
|
|
#define input_rb_item_t fdv_iq_rb_item_t
|
|
#define FREEDV_RX_FUNC freedv_comprx
|
|
#define FREEDV_RX_INPUT_T COMP
|
|
#else
|
|
#define input_rb fdv_demod_rb
|
|
#define input_rb_item_t fdv_demod_rb_item_t
|
|
#define FREEDV_RX_FUNC freedv_rx
|
|
#define FREEDV_RX_INPUT_T int16_t
|
|
#endif
|
|
FREEDV_RX_INPUT_T input_buffer[freedv_get_n_max_modem_samples(f_FREEDV)];
|
|
int16_t audio_buffer[freedv_get_n_max_modem_samples(f_FREEDV)];
|
|
|
|
// while makes this highest prio
|
|
// if may give more responsiveness but can cause interrupted reception
|
|
while (RingBuffer_GetData(&input_rb) >= freedv_nin(f_FREEDV)
|
|
&& RingBuffer_GetRoom(&fdv_audio_rb) >= freedv_nin(f_FREEDV))
|
|
{
|
|
// MchfBoard_GreenLed(LED_STATE_OFF);
|
|
// if we arrive here the rx_buffer is full enough and will be consumed now.
|
|
#ifdef USE_SIMPLE_FREEDV_FILTERS
|
|
for (int idx = 0; idx < freedv_nin(f_FREEDV); idx++ )
|
|
{
|
|
input_rb_item_t sample;
|
|
RingBuffer_GetSamples(&input_rb, &sample, 1);
|
|
input_buffer[idx].real = sample.real;
|
|
input_buffer[idx].imag = sample.imag;
|
|
}
|
|
#else
|
|
RingBuffer_GetSamples(&input_rb, &input_buffer, freedv_nin(f_FREEDV));
|
|
#endif
|
|
|
|
int count = FREEDV_RX_FUNC(f_FREEDV, audio_buffer, input_buffer); // run the decoding process
|
|
|
|
if (freedv_get_sync(f_FREEDV) != 0)
|
|
{
|
|
RingBuffer_PutSamples(&fdv_audio_rb, &audio_buffer, count);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (tx_was_here == true || rx_was_here == true)
|
|
{
|
|
tx_was_here = false;
|
|
rx_was_here = false;
|
|
RingBuffer_ClearGetTail(&fdv_iq_rb);
|
|
RingBuffer_ClearGetTail(&fdv_audio_rb);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// FreeDV txt test - will be out of here
|
|
typedef struct {
|
|
char tx_str[80];
|
|
char *ptx_str;
|
|
} my_callback_state_t;
|
|
|
|
static my_callback_state_t my_cb_state;
|
|
|
|
static char my_get_next_tx_char(void *callback_state) {
|
|
my_callback_state_t* pstate = (my_callback_state_t*)callback_state;
|
|
|
|
char c = *pstate->ptx_str++;
|
|
|
|
if (*pstate->ptx_str == 0) {
|
|
pstate->ptx_str = pstate->tx_str;
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
|
|
static void my_put_next_rx_char(void *callback_state, char ch)
|
|
{
|
|
UNUSED(callback_state);
|
|
UiDriver_TextMsgPutChar(ch);
|
|
}
|
|
|
|
freedv_mode_desc_t freedv_modes[] =
|
|
{
|
|
{ "1600", "FD1600", FREEDV_MODE_1600 },
|
|
#ifdef USE_FREEDV_700D
|
|
{ "700D", "FD700D", FREEDV_MODE_700D },
|
|
#endif
|
|
};
|
|
|
|
const uint8_t freedv_modes_num = sizeof(freedv_modes)/sizeof(freedv_modes[0]);
|
|
|
|
|
|
// FreeDV txt test - will be out of here
|
|
void FreeDV_Init()
|
|
{
|
|
// Freedv Test DL2FW
|
|
|
|
// move this to ui_configuration;
|
|
FreeDV_Set_Squelch_SNR(&freedv_conf,-2);
|
|
freedv_conf.mode = 0; // 0 = 1600, 1 = 700D
|
|
|
|
FreeDV_SetMode(freedv_conf.mode, true);
|
|
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @return true if mode was activated, false if old mode is still active
|
|
*/
|
|
int32_t FreeDV_SetMode(uint8_t fdv_mode, int32_t firstTime)
|
|
{
|
|
bool retval = true;
|
|
|
|
// turn off rx audio processing to be able to disable and reenable FreeDV processing
|
|
ads.af_disabled++;
|
|
|
|
// normal init starts
|
|
if (fdv_mode >= freedv_modes_num)
|
|
{
|
|
fdv_mode = 0;
|
|
}
|
|
|
|
// we don't switch if TX is currently active
|
|
if (firstTime == false)
|
|
{
|
|
retval = (ts.txrx_mode == TRX_MODE_RX);
|
|
if (retval == true && f_FREEDV != NULL && freedv_conf.mode != fdv_mode)
|
|
{
|
|
freedv_close(f_FREEDV);
|
|
f_FREEDV = NULL;
|
|
}
|
|
}
|
|
|
|
// only if all went well AND we have not yet initialized the mode,
|
|
// we are going to run the mode, so if same mode is being set, nothing happens
|
|
// really
|
|
if (retval && f_FREEDV == NULL)
|
|
{
|
|
f_FREEDV = freedv_open(freedv_modes[fdv_mode].freedv_id);
|
|
|
|
retval = f_FREEDV != NULL;
|
|
if (retval)
|
|
{
|
|
sprintf(my_cb_state.tx_str, ts.special_functions_enabled == 1 ? FREEDV_TX_DF8OE_MESSAGE : FREEDV_TX_MESSAGE);
|
|
|
|
my_cb_state.ptx_str = my_cb_state.tx_str;
|
|
freedv_set_callback_txt(f_FREEDV, &my_put_next_rx_char, &my_get_next_tx_char, &my_cb_state);
|
|
|
|
if (FreeDV_Is_Squelch_Enable(&freedv_conf))
|
|
{
|
|
freedv_set_squelch_en(f_FREEDV,1);
|
|
freedv_set_snr_squelch_thresh(f_FREEDV, FreeDV_Get_Squelch_SNR(&freedv_conf));
|
|
}
|
|
else
|
|
{
|
|
freedv_set_squelch_en(f_FREEDV,0);
|
|
}
|
|
|
|
freedv_set_tx_bpf(f_FREEDV, 0);
|
|
}
|
|
}
|
|
|
|
if (retval)
|
|
{
|
|
freedv_conf.mode = fdv_mode;
|
|
}
|
|
|
|
// turn on rx audio processing, FreeDV is ready for work
|
|
ads.af_disabled--;
|
|
|
|
return retval;
|
|
}
|
|
int32_t FreeDV_Iq_Get_FrameLen()
|
|
{
|
|
return freedv_get_n_nom_modem_samples(f_FREEDV);
|
|
}
|
|
|
|
int32_t FreeDV_Audio_Get_FrameLen()
|
|
{
|
|
return freedv_get_n_speech_samples(f_FREEDV);
|
|
}
|
|
|
|
|
|
#ifdef DEBUG_FREEDV
|
|
void FreeDV_Test()
|
|
{
|
|
ts.digital_mode = DigitalMode_FreeDV;
|
|
ts.txrx_mode = TRX_MODE_RX;
|
|
while (1)
|
|
{
|
|
for (int idx = 0; idx < FREEDV_TEST_BUFFER_FRAME_SIZE*FREEDV_TEST_BUFFER_FRAME_COUNT; idx++)
|
|
{
|
|
int16_t sample[2];
|
|
sample[0] = test_buffer[idx].real;
|
|
sample[1] = test_buffer[idx].imag;
|
|
|
|
RingBuffer_PutSamples(&fdv_iq_rb,&sample,2);
|
|
FreeDv_HandleFreeDv();
|
|
RingBuffer_GetSamples(&fdv_audio_rb,sample,1);
|
|
}
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
#endif
|