179 lines
6 KiB
C
179 lines
6 KiB
C
/*
|
|
* Copyright (C) 2020-2025 Roger Clark, VK3KYY / G4KYF
|
|
* Daniel Caujolle-Bert, F1RMB
|
|
*
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
|
|
* in the documentation and/or other materials provided with the distribution.
|
|
*
|
|
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* 4. Use of this source code or binary releases for commercial purposes is strictly forbidden. This includes, without limitation,
|
|
* incorporation in a commercial product or incorporation into a product or project which allows commercial use.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
|
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
*/
|
|
|
|
#include "functions/vox.h"
|
|
#include "interfaces/adc.h"
|
|
#include "functions/sound.h"
|
|
#include "functions/settings.h"
|
|
#include "functions/ticks.h"
|
|
#include "interfaces/batteryAndPowerManagement.h" // for micLevel decl
|
|
#include "utils.h"
|
|
|
|
#define VOX_UPDATE_MS 4U
|
|
#define VOX_TAIL_TIME_UNIT (PIT_COUNTS_PER_MS * 500U) // 500ms tail unit
|
|
#define VOX_SETTLE_TIME (6000U / VOX_UPDATE_MS) // Countdown before doing first real sampling;
|
|
#define VOX_TAIL_GET_MILLISECONDS(x) (((x) > 1U) ? (((x) - 1U) * VOX_TAIL_TIME_UNIT) : 1U)
|
|
|
|
typedef struct
|
|
{
|
|
uint16_t sampled;
|
|
uint16_t averaged;
|
|
uint16_t noiseFloor;
|
|
uint16_t sampledNoise;
|
|
ticksTimer_t nextTimeSamplingTimer;
|
|
ticksTimer_t preTriggeringTimer;
|
|
ticksTimer_t tailTimer;
|
|
uint16_t settleCount;
|
|
uint8_t threshold; // threshold is a super low value, 8 bits are enough
|
|
uint8_t tailUnits;
|
|
bool triggered;
|
|
} voxData_t;
|
|
|
|
static voxData_t vox;
|
|
|
|
void voxInit(void)
|
|
{
|
|
voxReset();
|
|
vox.threshold = 0U;
|
|
vox.noiseFloor = 0U;
|
|
vox.tailUnits = 2U;
|
|
vox.settleCount = VOX_SETTLE_TIME;
|
|
}
|
|
|
|
void voxSetParameters(uint8_t threshold, uint8_t tailHalfSecond)
|
|
{
|
|
vox.triggered = false;
|
|
vox.threshold = threshold;
|
|
|
|
voxReset();
|
|
vox.tailUnits = tailHalfSecond;
|
|
vox.settleCount = VOX_SETTLE_TIME;
|
|
}
|
|
|
|
bool voxIsEnabled(void)
|
|
{
|
|
return ((settingsIsOptionBitSet(BIT_TX_INHIBIT) == false) &&
|
|
(codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_VOX) != 0) &&
|
|
(codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_RX_ONLY) == 0) && ((nonVolatileSettings.txFreqLimited == BAND_LIMITS_NONE) || trxCheckFrequencyInAmateurBand(currentChannelData->txFreq)
|
|
#if defined(PLATFORM_MD9600)
|
|
|| (codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_OUT_OF_BAND) != 0)
|
|
#endif
|
|
) && (settingsUsbMode != USB_MODE_HOTSPOT));
|
|
}
|
|
|
|
bool voxIsTriggered(void)
|
|
{
|
|
return vox.triggered;
|
|
}
|
|
|
|
void voxReset(void)
|
|
{
|
|
vox.triggered = false;
|
|
vox.sampled = 0U;
|
|
vox.averaged = 0U;
|
|
ticksTimerStart(&vox.nextTimeSamplingTimer, VOX_UPDATE_MS);
|
|
ticksTimerReset(&vox.tailTimer);
|
|
ticksTimerReset(&vox.preTriggeringTimer);
|
|
vox.settleCount = (VOX_SETTLE_TIME >> 1);
|
|
vox.sampledNoise = 0U;
|
|
}
|
|
|
|
void voxTick(void)
|
|
{
|
|
if (voxIsEnabled())
|
|
{
|
|
if (ticksTimerHasExpired(&vox.nextTimeSamplingTimer))
|
|
{
|
|
if ((audioAmpGetStatus() & (AUDIO_AMP_CHANNEL_RF | AUDIO_AMP_CHANNEL_BEEP | AUDIO_AMP_CHANNEL_PROMPT)))
|
|
{
|
|
voxReset();
|
|
}
|
|
else
|
|
{
|
|
uint16_t sample = micLevel; // NOTE: not using adcGetVOX(), but direct (and valid) ADC value.
|
|
|
|
if (vox.settleCount > 0)
|
|
{
|
|
vox.settleCount--;
|
|
return;
|
|
}
|
|
|
|
vox.sampled += sample;
|
|
vox.averaged = (vox.sampled + (1 << (2 - 1))) >> 2;
|
|
vox.sampled -= vox.averaged;
|
|
|
|
if ((vox.averaged > 0) && (vox.noiseFloor > 0) && (vox.averaged >= (vox.noiseFloor + vox.threshold)))
|
|
{
|
|
if (ticksTimerIsEnabled(&vox.preTriggeringTimer) == false)
|
|
{
|
|
ticksTimerStart(&vox.preTriggeringTimer, 100U);
|
|
}
|
|
|
|
// We need at least 100ms of level above the noise to trigger the VOX
|
|
if (ticksTimerHasExpired(&vox.preTriggeringTimer))
|
|
{
|
|
vox.triggered = true;
|
|
ticksTimerStart(&vox.tailTimer, (VOX_TAIL_GET_MILLISECONDS(vox.tailUnits) + VOX_UPDATE_MS));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// it was pre-triggered, but not long enough to trigger the vox, reset the pre-trigger timer
|
|
if (ticksTimerIsEnabled(&vox.preTriggeringTimer) && ticksTimerHasExpired(&vox.preTriggeringTimer) && (vox.triggered == false))
|
|
{
|
|
ticksTimerReset(&vox.preTriggeringTimer);
|
|
}
|
|
|
|
// Noise is sampled all the time, when the transceiver is silent, and not XMitting
|
|
// Except when it's pre-trigged
|
|
if (ticksTimerIsEnabled(&vox.preTriggeringTimer) == false)
|
|
{
|
|
// Noise floor averaging
|
|
vox.sampledNoise += sample;
|
|
vox.noiseFloor = (vox.sampledNoise + (1 << (2 - 1))) >> 2;
|
|
vox.sampledNoise -= vox.noiseFloor;
|
|
}
|
|
}
|
|
}
|
|
|
|
ticksTimerStart(&vox.nextTimeSamplingTimer, VOX_UPDATE_MS);
|
|
}
|
|
|
|
if (ticksTimerIsEnabled(&vox.tailTimer) && ticksTimerHasExpired(&vox.tailTimer))
|
|
{
|
|
vox.triggered = false;
|
|
ticksTimerReset(&vox.tailTimer);
|
|
ticksTimerReset(&vox.preTriggeringTimer);
|
|
}
|
|
}
|
|
|
|
if (vox.triggered && (settingsUsbMode == USB_MODE_HOTSPOT))
|
|
{
|
|
voxReset();
|
|
}
|
|
}
|