Compare commits

..
31 Commits
Author SHA1 Message Date
Jonathan Naylor 046e1df161 Merge pull request #362 from n0mjs710/link-audio-fidelity
FM link-mode network audio: anti-alias/reconstruction filtering
2026-08-16 17:36:20 +01:00
Cort Buffington d70ab85bae Copy-construct the ds/us filters from m_filterStage1/2/3
Same coefficients, no need to retype them. CFMDirectFormI's copy ctor
already copies coefficients and (zeroed, at this point) state.
2026-08-13 13:52:06 -05:00
Cort Buffington 384c5d559d Reconstruction filter after ext-audio upsampling in FM mode
CFMUpSampler zero-stuffs 8kHz to 24kHz with no lowpass after it,
leaving imaging artifacts in the reconstructed ext audio. Filter it
with a second, separately-stated pass of the same 300-2700Hz design
already used for TX shaping, right where it comes out of the upsampler.
2026-08-12 07:43:23 -05:00
Cort Buffington 4c7e8fbe46 Anti-alias filter before RF->network downsampling in FM mode
The 24kHz->8kHz decimation in CFMDownSampler had no lowpass ahead of
it, aliasing everything above 4kHz back into the audio band. Reuse
the existing 300-2700Hz filter design (separate instance/state) ahead
of all three addSample() call sites in FM.cpp.

Also: reset() wasn't clearing the ring buffer or in-progress sample
pack, so stale audio could bleed into the next transmission. And
addSample() packed samples into 12 bits with no saturation, so an
out-of-range value corrupted the neighbouring sample.
2026-08-11 09:57:47 -05:00
Jonathan Naylor 894ba76432 Merge pull request #361 from n0mjs710/link-mode-ext-gap
Link mode ext audio gap
2026-08-11 15:02:32 +01:00
Cort Buffington 8a89804fda Refactor FM_LINK_EXT_GAP_MS and improve comments
Updated the FM_LINK_EXT_GAP_MS constant declaration and added comments for clarity.
2026-08-11 08:55:22 -05:00
Cort Buffington 8bd467598c Debounce link-mode external-audio underrun before dropping PTT
Under [FM] LinkMode=1, linkStateMachine() set m_extSignal false the
instant a single ext-audio sample went missing, with no debounce --
unlike the non-link duplex/simplex paths, which already tolerate a
brief gap via the RELAYING_EXT -> RELAYING_WAIT_EXT / m_ackDelayTimer
pattern before declaring a real loss. Since IO::process() drops PTT
the moment the downstream TX buffer runs dry, that zero-debounce cutoff
meant even a very brief, late-but-recoverable gap from the gateway
(scheduling jitter, a late UDP frame, etc.) could cascade into a full
PTT drop-and-rekey cycle -- far more audible than the underlying gap
itself.

Adds m_extGapTimer, mirroring the existing pattern: on the first missed
sample, start the timer instead of immediately clearing m_extSignal; a
gap that recovers before FM_LINK_EXT_GAP_MS (default 60ms) expires
never affects m_extSignal at all, so the transmitter never notices.
Only a gap that outlasts the timer is treated as a genuine end of
transmission, exactly as before.

linkSamples()'s currentExtSample is now zero-initialized, since
m_extSignal can now stay true for a few samples past an underrun --
silence, not stack garbage, must be what gets played out to the modem
during that window.
2026-08-09 11:42:20 -05:00
Jonathan Naylor 715d3f5839 Merge pull request #359 from chancellor1101/bugfixes
Fix several small bugs: STM UART repeater port, FM noise squelch, Due FM LED, DMR trunking state
2026-07-13 12:07:39 +01:00
Clint Chance 68252f15f5 Fix the FM 25kHz calibration debug message.
It said 10kHz, a copy and paste from the case above.
2026-07-12 16:45:24 -05:00
Clint Chance 96061a6013 Make DMR trunking state handling order-independent.
The CSBK slot type was only encoded into the ALOHA frame inside
setColorCode(), so it took effect only if a reconfigure happened after
an ALOHA frame had already been received; on the usual command ordering
(SET_CONFIG then ALOHA) the transmitted slot type depended entirely on
the host-supplied bytes. Remember the colour code and stamp the slot
type in writeAloha() as well.

Also clear m_controlChannel when trunking is disabled, so the host has
a way to stop control channel transmission other than rebooting, and
fix a stray tab introduced in createCACH().
2026-07-12 16:45:24 -05:00
Clint Chance 90220cbb9d Fix the FM mode LED pin guard on the Arduino Due.
The pinMode() call for PIN_FM was guarded by USE_ALTERNATE_POCSAG_LEDS
instead of USE_ALTERNATE_FM_LEDS, a copy and paste error. The Teensy
version and the setFMInt() implementation in this file both use the FM
macro.
2026-07-12 16:42:58 -05:00
Clint Chance 2eb6f7fd06 Initialise and reset the FM noise squelch invalid counter.
m_invalidCount was missing from the constructor initialiser list and was
read uninitialised in process(), making the squelch behaviour after
power-up indeterminate. reset() also cleared neither hysteresis counter,
so state leaked across squelch resets.
2026-07-12 16:42:58 -05:00
Clint Chance a1794b44ed Fix RX interrupt flag being cleared on the wrong USART in STMUART.
handleIRQ() hardcoded USART1 when clearing the RXNE pending bit, so on
boards where the repeater/display UART is not USART1 (Nucleo, Discovery,
F767, UART5-based boards) the flag was cleared on the wrong peripheral.
Use the m_usart member, matching the TXE handling below.

Also fix flush(): it passed a flag constant to USART_GetITStatus and
span while TXE was set, which returns immediately since TXE indicates
the data register is already empty. Wait for the software TX FIFO to
drain and then for the TC flag, which indicates the shift register has
actually emptied.
2026-07-12 16:42:47 -05:00
Jonathan Naylor 57e692ad8c Small cleanups of the DMR trunking code. 2026-04-24 16:54:23 +01:00
Jonathan Naylor 841d7505c2 Merge pull request #358 from qradiolink/trunking_patches
Add support for DMR tier III trunking
2026-04-24 14:50:28 +01:00
adrian 549dfd7258 Change DMR ALOHA header to 0x17 2026-04-19 11:32:07 +03:00
adrian 30ba4c4c55 Fix merge gone wrong in DMRTX.cpp 2026-04-19 11:12:37 +03:00
adrian a696fd454c DMR trunking patches to DMRTX and SerialPort 2026-04-18 11:29:48 +03:00
Jonathan Naylor 3fd84d5ed4 Fix merge compilation issue. 2026-02-19 15:59:46 +00:00
Jonathan Naylor 78216afbb7 Clean up the ring buffer. 2026-02-18 13:40:05 +00:00
Jonathan Naylor 0308bfc6c7 Merge branch 'master' into mqtt 2026-02-18 13:36:22 +00:00
Jonathan Naylor 5dd59fc473 Merge branch 'master' into mqtt 2025-08-28 17:00:21 +01:00
Jonathan Naylor 15227d9cec Merge branch 'master' into mqtt 2024-10-02 16:10:09 +01:00
Jonathan Naylor 65c51bf849 Merge branch 'master' into mqtt 2024-08-01 12:55:06 +01:00
Jonathan Naylor a7fddc37e1 Don't send a status when the FM repeater is reset, it causes a loop. 2023-11-18 16:45:36 +00:00
Jonathan Naylor a8bf99037e Merge branch 'master' into mqtt 2023-11-09 19:13:12 +00:00
Jonathan Naylor 85d3086d4c Restart the RSSI ADC conversion in the STM32 interrupt to match the signal ADC. 2023-10-10 20:06:50 +01:00
Jonathan Naylor ccf3b7301e Make the type correct for silence frames. 2023-10-10 20:03:01 +01:00
Jonathan Naylor 6b3a811ae7 Simplify the serial repeater code. 2023-10-05 19:49:17 +01:00
Jonathan Naylor 3e079f58ad Add RSSI reporting to FM and AX.25 2023-08-02 17:34:52 +01:00
Jonathan Naylor 7ef0d992d9 Improve the status reporting for FM mode. 2023-01-18 14:30:02 +00:00
19 changed files with 337 additions and 149 deletions
+2
View File
@@ -1,3 +1,5 @@
.pio
.vscode
*.o *.o
obj/ obj/
bin/ bin/
+64 -11
View File
@@ -2,6 +2,7 @@
* Copyright (C) 2009-2017,2020 by Jonathan Naylor G4KLX * Copyright (C) 2009-2017,2020 by Jonathan Naylor G4KLX
* Copyright (C) 2016 by Colin Durbridge G4EML * Copyright (C) 2016 by Colin Durbridge G4EML
* Copyright (C) 2017 by Andy Uribe CA6JAU * Copyright (C) 2017 by Andy Uribe CA6JAU
* Copyright (C) 2022,2023,2026 by Adrian Musceac YO8RZZ
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@@ -42,6 +43,12 @@ const uint8_t IDLE_DATA[] =
0x36U, 0x00U, 0x0DU, 0xFFU, 0x57U, 0xD7U, 0x5DU, 0xF5U, 0xD0U, 0x03U, 0xF6U, 0x36U, 0x00U, 0x0DU, 0xFFU, 0x57U, 0xD7U, 0x5DU, 0xF5U, 0xD0U, 0x03U, 0xF6U,
0xE4U, 0x65U, 0x17U, 0x1BU, 0x48U, 0xCAU, 0x6DU, 0x4FU, 0xC6U, 0x10U, 0xB4U}; 0xE4U, 0x65U, 0x17U, 0x1BU, 0x48U, 0xCAU, 0x6DU, 0x4FU, 0xC6U, 0x10U, 0xB4U};
// DT Terminator with LC, empty
const uint8_t TERMINATOR_DATA[] =
{0x00U, 0xCCU, 0x01U, 0x80U, 0x01U, 0x94U, 0x01U, 0x48U, 0x08U, 0xB0U, 0x08U,
0x80U, 0x04U, 0xADU, 0xFFU, 0x57U, 0xD7U, 0x5DU, 0xF5U, 0xD9U, 0x65U, 0x90U,
0x01U, 0x78U, 0x00U, 0xD0U, 0x08U, 0x40U, 0x14U, 0x00U, 0x0DU, 0x80U, 0x15U};
const uint8_t CACH_INTERLEAVE[] = const uint8_t CACH_INTERLEAVE[] =
{ 1U, 2U, 3U, 5U, 6U, 7U, 9U, 10U, 11U, 13U, 15U, 16U, 17U, 19U, 20U, 21U, 23U, { 1U, 2U, 3U, 5U, 6U, 7U, 9U, 10U, 11U, 13U, 15U, 16U, 17U, 19U, 20U, 21U, 23U,
25U, 26U, 27U, 29U, 30U, 31U, 33U, 34U, 35U, 37U, 39U, 40U, 41U, 43U, 44U, 45U, 47U, 25U, 26U, 27U, 29U, 30U, 31U, 33U, 34U, 35U, 37U, 39U, 40U, 41U, 43U, 44U, 45U, 47U,
@@ -65,6 +72,7 @@ m_modFilter(),
m_modState(), m_modState(),
m_state(DMRTXSTATE_IDLE), m_state(DMRTXSTATE_IDLE),
m_idle(), m_idle(),
m_aloha(),
m_cachPtr(0U), m_cachPtr(0U),
m_shortLC(), m_shortLC(),
m_newShortLC(), m_newShortLC(),
@@ -74,7 +82,10 @@ m_poLen(0U),
m_poPtr(0U), m_poPtr(0U),
m_frameCount(0U), m_frameCount(0U),
m_abortCount(), m_abortCount(),
m_abort() m_abort(),
m_controlChannel(false),
m_trunking(false),
m_colorCode(0U)
{ {
::memset(m_modState, 0x00U, 16U * sizeof(q15_t)); ::memset(m_modState, 0x00U, 16U * sizeof(q15_t));
@@ -211,6 +222,23 @@ uint8_t CDMRTX::writeShortLC(const uint8_t* data, uint16_t length)
return 0U; return 0U;
} }
uint8_t CDMRTX::writeAloha(const uint8_t* data, uint16_t length)
{
if (length != DMR_FRAME_LENGTH_BYTES)
return 4U;
::memcpy(m_aloha, data, length);
// Stamp the slot type with the current colour code so that the result
// doesn't depend on the ordering of the ALOHA and SET_CONFIG commands.
CDMRSlotType slotType;
slotType.encode(m_colorCode, DT_CSBK, m_aloha);
m_controlChannel = true;
return 0U;
}
uint8_t CDMRTX::writeAbort(const uint8_t* data, uint16_t length) uint8_t CDMRTX::writeAbort(const uint8_t* data, uint16_t length)
{ {
if (length != 1U) if (length != 1U)
@@ -294,7 +322,7 @@ uint8_t CDMRTX::getSpace2() const
void CDMRTX::createData(uint8_t slotIndex) void CDMRTX::createData(uint8_t slotIndex)
{ {
if (m_fifo[slotIndex].getData() >= DMR_FRAME_LENGTH_BYTES && m_frameCount >= STARTUP_COUNT && m_abortCount[slotIndex] >= ABORT_COUNT) { if ((m_fifo[slotIndex].getData() >= DMR_FRAME_LENGTH_BYTES) && (m_frameCount >= STARTUP_COUNT) && (m_abortCount[slotIndex] >= ABORT_COUNT)) {
for (unsigned int i = 0U; i < DMR_FRAME_LENGTH_BYTES; i++) { for (unsigned int i = 0U; i < DMR_FRAME_LENGTH_BYTES; i++) {
m_fifo[slotIndex].get(m_poBuffer[i]); m_fifo[slotIndex].get(m_poBuffer[i]);
m_markBuffer[i] = MARK_NONE; m_markBuffer[i] = MARK_NONE;
@@ -303,7 +331,11 @@ void CDMRTX::createData(uint8_t slotIndex)
m_abort[slotIndex] = false; m_abort[slotIndex] = false;
// Transmit an idle message // Transmit an idle message
for (unsigned int i = 0U; i < DMR_FRAME_LENGTH_BYTES; i++) { for (unsigned int i = 0U; i < DMR_FRAME_LENGTH_BYTES; i++) {
m_poBuffer[i] = m_idle[i]; if ((slotIndex == 0U) && m_controlChannel && m_trunking)
m_poBuffer[i] = m_aloha[i];
else
m_poBuffer[i] = m_idle[i];
m_markBuffer[i] = MARK_NONE; m_markBuffer[i] = MARK_NONE;
} }
} }
@@ -354,10 +386,14 @@ void CDMRTX::createCACH(uint8_t txSlotIndex, uint8_t rxSlotIndex)
m_cachPtr = 0U; m_cachPtr = 0U;
if (m_cachPtr == 0U) { if (m_cachPtr == 0U) {
if (m_fifo[0U].getData() == 0U && m_fifo[1U].getData() == 0U) if ((m_fifo[0U].getData() == 0U) && (m_fifo[1U].getData() == 0U)) {
::memcpy(m_shortLC, EMPTY_SHORT_LC, 12U); if (m_trunking)
else ::memcpy(m_shortLC, m_newShortLC, 12U);
else
::memcpy(m_shortLC, EMPTY_SHORT_LC, 12U);
} else {
::memcpy(m_shortLC, m_newShortLC, 12U); ::memcpy(m_shortLC, m_newShortLC, 12U);
}
} }
::memcpy(m_poBuffer, m_shortLC + m_cachPtr, 3U); ::memcpy(m_poBuffer, m_shortLC + m_cachPtr, 3U);
@@ -367,8 +403,8 @@ void CDMRTX::createCACH(uint8_t txSlotIndex, uint8_t rxSlotIndex)
bool at = false; bool at = false;
if (m_frameCount >= STARTUP_COUNT) if (m_frameCount >= STARTUP_COUNT)
at = m_fifo[rxSlotIndex].getData() > 0U; at = (m_fifo[rxSlotIndex].getData() > 0U);
bool tc = txSlotIndex == 1U; bool tc = (txSlotIndex == 1U);
bool ls0 = true; // For 1 and 2 bool ls0 = true; // For 1 and 2
bool ls1 = true; bool ls1 = true;
@@ -397,10 +433,20 @@ void CDMRTX::createCACH(uint8_t txSlotIndex, uint8_t rxSlotIndex)
void CDMRTX::setColorCode(uint8_t colorCode) void CDMRTX::setColorCode(uint8_t colorCode)
{ {
::memcpy(m_idle, IDLE_DATA, DMR_FRAME_LENGTH_BYTES); m_colorCode = colorCode;
if (m_trunking)
::memcpy(m_idle, TERMINATOR_DATA, DMR_FRAME_LENGTH_BYTES);
else
::memcpy(m_idle, IDLE_DATA, DMR_FRAME_LENGTH_BYTES);
CDMRSlotType slotType; CDMRSlotType slotType;
slotType.encode(colorCode, DT_IDLE, m_idle); if (m_trunking) {
slotType.encode(colorCode, DT_TERMINATOR_WITH_LC, m_idle);
slotType.encode(colorCode, DT_CSBK, m_aloha);
} else {
slotType.encode(colorCode, DT_IDLE, m_idle);
}
} }
void CDMRTX::resetFifo1() void CDMRTX::resetFifo1()
@@ -418,5 +464,12 @@ uint32_t CDMRTX::getFrameCount()
return m_frameCount; return m_frameCount;
} }
#endif void CDMRTX::setTrunking(bool trunking)
{
m_trunking = trunking;
if (!trunking)
m_controlChannel = false;
}
#endif
+8 -2
View File
@@ -1,6 +1,7 @@
/* /*
* Copyright (C) 2015,2016,2017,2020 by Jonathan Naylor G4KLX * Copyright (C) 2015,2016,2017,2020 by Jonathan Naylor G4KLX
* Copyright (C) 2016 by Colin Durbridge G4EML * Copyright (C) 2016 by Colin Durbridge G4EML
* Copyright (C) 2026 by Adrian Musceac YO8RZZ
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@@ -45,10 +46,12 @@ public:
uint8_t writeData2(const uint8_t* data, uint16_t length); uint8_t writeData2(const uint8_t* data, uint16_t length);
uint8_t writeShortLC(const uint8_t* data, uint16_t length); uint8_t writeShortLC(const uint8_t* data, uint16_t length);
uint8_t writeAloha(const uint8_t* data, uint16_t length);
uint8_t writeAbort(const uint8_t* data, uint16_t length); uint8_t writeAbort(const uint8_t* data, uint16_t length);
void setStart(bool start); void setStart(bool start);
void setCal(bool start); void setCal(bool start);
void setTrunking(bool trunking);
void process(); void process();
@@ -62,11 +65,12 @@ public:
void setColorCode(uint8_t colorCode); void setColorCode(uint8_t colorCode);
private: private:
CRingBuffer<uint8_t> m_fifo[2U]; CRingBuffer<uint8_t> m_fifo[2U];
arm_fir_interpolate_instance_q15 m_modFilter; arm_fir_interpolate_instance_q15 m_modFilter;
q15_t m_modState[16U]; // blockSize + phaseLength - 1, 4 + 9 - 1 plus some spare q15_t m_modState[16U]; // blockSize + phaseLength - 1, 4 + 9 - 1 plus some spare
DMRTXSTATE m_state; DMRTXSTATE m_state;
uint8_t m_idle[DMR_FRAME_LENGTH_BYTES]; uint8_t m_idle[DMR_FRAME_LENGTH_BYTES];
uint8_t m_aloha[DMR_FRAME_LENGTH_BYTES];
uint8_t m_cachPtr; uint8_t m_cachPtr;
uint8_t m_shortLC[12U]; uint8_t m_shortLC[12U];
uint8_t m_newShortLC[12U]; uint8_t m_newShortLC[12U];
@@ -77,6 +81,9 @@ private:
uint32_t m_frameCount; uint32_t m_frameCount;
uint32_t m_abortCount[2U]; uint32_t m_abortCount[2U];
bool m_abort[2U]; bool m_abort[2U];
bool m_controlChannel;
bool m_trunking;
uint8_t m_colorCode;
void createData(uint8_t slotIndex); void createData(uint8_t slotIndex);
void createCACH(uint8_t txSlotIndex, uint8_t rxSlotIndex); void createCACH(uint8_t txSlotIndex, uint8_t rxSlotIndex);
@@ -87,4 +94,3 @@ private:
#endif #endif
#endif #endif
+171 -46
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020,2021,2025 by Jonathan Naylor G4KLX * Copyright (C) 2020,2021,2023,2025 by Jonathan Naylor G4KLX
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@@ -27,6 +27,20 @@ const uint16_t FM_TX_BLOCK_SIZE = 100U;
const uint16_t FM_SERIAL_BLOCK_SIZE = 80U;//this is the number of sample pairs to send over serial. One sample pair is 3bytes. const uint16_t FM_SERIAL_BLOCK_SIZE = 80U;//this is the number of sample pairs to send over serial. One sample pair is 3bytes.
//three times this value shall never exceed 252 //three times this value shall never exceed 252
const uint16_t FM_SERIAL_BLOCK_SIZE_BYTES = FM_SERIAL_BLOCK_SIZE * 3U; const uint16_t FM_SERIAL_BLOCK_SIZE_BYTES = FM_SERIAL_BLOCK_SIZE * 3U;
const uint16_t FM_LINK_EXT_GAP_MS = 60U;// How long a link-mode external-audio underrun is tolerated before it is
// treated as a real end of transmission (see linkStateMachine()).
const uint8_t FS_LISTENING = 0U;
const uint8_t FS_KERCHUNK_RF = 1U;
const uint8_t FS_RELAYING_RF = 2U;
const uint8_t FS_RELAYING_WAIT_RF = 3U;
const uint8_t FS_TIMEOUT_RF = 4U;
const uint8_t FS_TIMEOUT_WAIT_RF = 5U;
const uint8_t FS_KERCHUNK_EXT = 6U;
const uint8_t FS_RELAYING_EXT = 7U;
const uint8_t FS_RELAYING_WAIT_EXT = 8U;
const uint8_t FS_TIMEOUT_EXT = 9U;
const uint8_t FS_TIMEOUT_WAIT_EXT = 10U;
const uint8_t FS_HANG = 11U;
CFM::CFM() : CFM::CFM() :
@@ -48,12 +62,18 @@ m_kerchunkTimer(),
m_ackMinTimer(), m_ackMinTimer(),
m_ackDelayTimer(), m_ackDelayTimer(),
m_hangTimer(), m_hangTimer(),
m_statusTimer(),
m_reverseTimer(), m_reverseTimer(),
m_extGapTimer(),
m_needReverse(false), m_needReverse(false),
m_filterStage1( 724, 1448, 724, 32768, -37895, 21352),//3rd order Cheby Filter 300 to 2700Hz, 0.2dB passband ripple, sampling rate 24kHz m_filterStage1( 724, 1448, 724, 32768, -37895, 21352),//3rd order Cheby Filter 300 to 2700Hz, 0.2dB passband ripple, sampling rate 24kHz
m_filterStage2(32768, 0,-32768, 32768, -50339, 19052), m_filterStage2(32768, 0,-32768, 32768, -50339, 19052),
m_filterStage3(32768, -65536, 32768, 32768, -64075, 31460), m_filterStage3(32768, -65536, 32768, 32768, -64075, 31460),
m_dsFilterStage1(m_filterStage1),//same coefficients, separate state
m_dsFilterStage2(m_filterStage2),
m_dsFilterStage3(m_filterStage3),
m_usFilterStage1(m_filterStage1),//same coefficients, separate state
m_usFilterStage2(m_filterStage2),
m_usFilterStage3(m_filterStage3),
m_blanking(), m_blanking(),
m_accessMode(1U), m_accessMode(1U),
m_linkMode(false), m_linkMode(false),
@@ -68,23 +88,25 @@ m_inputRFRB(2401U), // 100ms of audio + 1 sample
m_outputRFRB(2400U), // 100ms of audio m_outputRFRB(2400U), // 100ms of audio
m_inputExtRB(), m_inputExtRB(),
m_rfSignal(false), m_rfSignal(false),
m_extSignal(false) m_extSignal(false),
m_rssiAccum(0U),
m_rssiCount(0U)
{ {
m_statusTimer.setTimeout(1U, 0U);
m_reverseTimer.setTimeout(0U, 150U); m_reverseTimer.setTimeout(0U, 150U);
m_extGapTimer.setTimeout(0U, FM_LINK_EXT_GAP_MS);
insertDelay(100U); insertDelay(100U);
} }
void CFM::samples(bool cos, q15_t* samples, uint8_t length) void CFM::samples(bool cos, q15_t* samples, const uint16_t* rssi, uint8_t length)
{ {
if (m_linkMode) if (m_linkMode)
linkSamples(cos, samples, length); linkSamples(cos, samples, length);
else else
repeaterSamples(cos, samples, length); repeaterSamples(cos, samples, rssi, length);
} }
void CFM::repeaterSamples(bool cos, q15_t* samples, uint8_t length) void CFM::repeaterSamples(bool cos, q15_t* samples, const uint16_t* rssi, uint8_t length)
{ {
if (m_cosInvert) if (m_cosInvert)
cos = !cos; cos = !cos;
@@ -93,6 +115,11 @@ void CFM::repeaterSamples(bool cos, q15_t* samples, uint8_t length)
uint8_t i = 0U; uint8_t i = 0U;
for (; i < length; i++) { for (; i < length; i++) {
if (m_state == FS_RELAYING_RF) {
m_rssiAccum += rssi[i];
m_rssiCount++;
}
// ARMv7-M has hardware integer division // ARMv7-M has hardware integer division
q15_t currentRFSample = q15_t((q31_t(samples[i]) << 8) / m_rxLevel); q15_t currentRFSample = q15_t((q31_t(samples[i]) << 8) / m_rxLevel);
@@ -101,6 +128,7 @@ void CFM::repeaterSamples(bool cos, q15_t* samples, uint8_t length)
q15_t currentExtSample; q15_t currentExtSample;
bool inputExt = m_inputExtRB.getSample(currentExtSample);//always consume the external input data so it does not overflow bool inputExt = m_inputExtRB.getSample(currentExtSample);//always consume the external input data so it does not overflow
currentExtSample = m_usFilterStage3.filter(m_usFilterStage2.filter(m_usFilterStage1.filter(currentExtSample)));
inputExt = inputExt && m_extEnabled; inputExt = inputExt && m_extEnabled;
switch (m_accessMode) { switch (m_accessMode) {
@@ -182,8 +210,10 @@ void CFM::repeaterSamples(bool cos, q15_t* samples, uint8_t length)
if (m_duplex) { if (m_duplex) {
if (m_state == FS_RELAYING_RF || m_state == FS_KERCHUNK_RF || m_state == FS_RELAYING_EXT || m_state == FS_KERCHUNK_EXT) { if (m_state == FS_RELAYING_RF || m_state == FS_KERCHUNK_RF || m_state == FS_RELAYING_EXT || m_state == FS_KERCHUNK_EXT) {
currentSample = m_blanking.process(currentSample); currentSample = m_blanking.process(currentSample);
if (m_extEnabled && (m_state == FS_RELAYING_RF || m_state == FS_KERCHUNK_RF)) if (m_extEnabled && (m_state == FS_RELAYING_RF || m_state == FS_KERCHUNK_RF)) {
m_downSampler.addSample(currentSample); q15_t dsSample = m_dsFilterStage3.filter(m_dsFilterStage2.filter(m_dsFilterStage1.filter(currentSample)));
m_downSampler.addSample(dsSample);
}
currentSample *= currentBoost; currentSample *= currentBoost;
} else { } else {
@@ -193,8 +223,10 @@ void CFM::repeaterSamples(bool cos, q15_t* samples, uint8_t length)
if (m_state == FS_RELAYING_EXT || m_state == FS_KERCHUNK_EXT) { if (m_state == FS_RELAYING_EXT || m_state == FS_KERCHUNK_EXT) {
currentSample *= currentBoost; currentSample *= currentBoost;
} else { } else {
if (m_extEnabled && (m_state == FS_RELAYING_RF || m_state == FS_KERCHUNK_RF)) if (m_extEnabled && (m_state == FS_RELAYING_RF || m_state == FS_KERCHUNK_RF)) {
m_downSampler.addSample(currentSample); q15_t dsSample = m_dsFilterStage3.filter(m_dsFilterStage2.filter(m_dsFilterStage1.filter(currentSample)));
m_downSampler.addSample(dsSample);
}
continue; continue;
} }
} }
@@ -232,14 +264,17 @@ void CFM::linkSamples(bool cos, q15_t* samples, uint8_t length)
uint8_t i = 0U; uint8_t i = 0U;
for (; i < length; i++) { for (; i < length; i++) {
// ARMv7-M has hardware integer division
q15_t currentRFSample = q15_t((q31_t(samples[i]) << 8) / m_rxLevel); q15_t currentRFSample = q15_t((q31_t(samples[i]) << 8) / m_rxLevel);
if (m_noiseSquelch) if (m_noiseSquelch)
cos = m_squelch.process(currentRFSample); cos = m_squelch.process(currentRFSample);
q15_t currentExtSample; // Zero-initialized: getSample() leaves this untouched on underrun, and
// with the ext-gap debounce below, m_extSignal can now stay true for a
// few samples past that point
q15_t currentExtSample = 0;
bool inputExt = m_inputExtRB.getSample(currentExtSample);//always consume the external input data so it does not overflow bool inputExt = m_inputExtRB.getSample(currentExtSample);//always consume the external input data so it does not overflow
currentExtSample = m_usFilterStage3.filter(m_usFilterStage2.filter(m_usFilterStage1.filter(currentExtSample)));
inputExt = inputExt && m_extEnabled; inputExt = inputExt && m_extEnabled;
switch (m_accessMode) { switch (m_accessMode) {
@@ -309,7 +344,8 @@ void CFM::linkSamples(bool cos, q15_t* samples, uint8_t length)
if (m_rfSignal && m_extEnabled) { if (m_rfSignal && m_extEnabled) {
q15_t currentSample = m_blanking.process(currentRFSample); q15_t currentSample = m_blanking.process(currentRFSample);
m_downSampler.addSample(currentSample); q15_t dsSample = m_dsFilterStage3.filter(m_dsFilterStage2.filter(m_dsFilterStage1.filter(currentSample)));
m_downSampler.addSample(dsSample);
} }
if (!m_extSignal) if (!m_extSignal)
@@ -377,7 +413,6 @@ void CFM::reset()
m_ackMinTimer.stop(); m_ackMinTimer.stop();
m_ackDelayTimer.stop(); m_ackDelayTimer.stop();
m_hangTimer.stop(); m_hangTimer.stop();
m_statusTimer.stop();
m_reverseTimer.stop(); m_reverseTimer.stop();
m_ctcssRX.reset(); m_ctcssRX.reset();
@@ -390,6 +425,12 @@ void CFM::reset()
m_inputExtRB.reset(); m_inputExtRB.reset();
m_downSampler.reset(); m_downSampler.reset();
m_dsFilterStage1.reset();
m_dsFilterStage2.reset();
m_dsFilterStage3.reset();
m_usFilterStage1.reset();
m_usFilterStage2.reset();
m_usFilterStage3.reset();
m_squelch.reset(); m_squelch.reset();
m_needReverse = false; m_needReverse = false;
@@ -583,13 +624,8 @@ void CFM::clock(uint8_t length)
m_ackMinTimer.clock(length); m_ackMinTimer.clock(length);
m_ackDelayTimer.clock(length); m_ackDelayTimer.clock(length);
m_hangTimer.clock(length); m_hangTimer.clock(length);
m_statusTimer.clock(length);
m_reverseTimer.clock(length); m_reverseTimer.clock(length);
m_extGapTimer.clock(length);
if (m_statusTimer.isRunning() && m_statusTimer.hasExpired()) {
serial.writeFMStatus(m_state);
m_statusTimer.start();
}
} }
void CFM::listeningStateDuplex(bool validRFSignal, bool validExtSignal) void CFM::listeningStateDuplex(bool validRFSignal, bool validExtSignal)
@@ -598,12 +634,19 @@ void CFM::listeningStateDuplex(bool validRFSignal, bool validExtSignal)
if (m_kerchunkTimer.getTimeout() > 0U) { if (m_kerchunkTimer.getTimeout() > 0U) {
DEBUG1("State to KERCHUNK_RF"); DEBUG1("State to KERCHUNK_RF");
m_state = FS_KERCHUNK_RF; m_state = FS_KERCHUNK_RF;
serial.writeFMStatus(m_state);
m_kerchunkTimer.start(); m_kerchunkTimer.start();
if (m_callsignAtStart && !m_callsignAtLatch) if (m_callsignAtStart && !m_callsignAtLatch)
sendCallsign(); sendCallsign();
} else { } else {
DEBUG1("State to RELAYING_RF"); DEBUG1("State to RELAYING_RF");
m_state = FS_RELAYING_RF; m_state = FS_RELAYING_RF;
serial.writeFMStatus(m_state);
m_rssiAccum = 0U;
m_rssiCount = 0U;
if (m_callsignAtStart) if (m_callsignAtStart)
sendCallsign(); sendCallsign();
} }
@@ -618,20 +661,21 @@ void CFM::listeningStateDuplex(bool validRFSignal, bool validExtSignal)
io.setDecode(true); io.setDecode(true);
io.setADCDetection(true); io.setADCDetection(true);
m_statusTimer.start();
serial.writeFMStatus(m_state);
} }
} else if (validExtSignal) { } else if (validExtSignal) {
if (m_kerchunkTimer.getTimeout() > 0U) { if (m_kerchunkTimer.getTimeout() > 0U) {
DEBUG1("State to KERCHUNK_EXT"); DEBUG1("State to KERCHUNK_EXT");
m_state = FS_KERCHUNK_EXT; m_state = FS_KERCHUNK_EXT;
serial.writeFMStatus(m_state);
m_kerchunkTimer.start(); m_kerchunkTimer.start();
if (m_callsignAtStart && !m_callsignAtLatch) if (m_callsignAtStart && !m_callsignAtLatch)
sendCallsign(); sendCallsign();
} else { } else {
DEBUG1("State to RELAYING_EXT"); DEBUG1("State to RELAYING_EXT");
m_state = FS_RELAYING_EXT; m_state = FS_RELAYING_EXT;
serial.writeFMStatus(m_state);
if (m_callsignAtStart) if (m_callsignAtStart)
sendCallsign(); sendCallsign();
} }
@@ -643,9 +687,6 @@ void CFM::listeningStateDuplex(bool validRFSignal, bool validExtSignal)
m_callsignTimer.start(); m_callsignTimer.start();
m_reverseTimer.stop(); m_reverseTimer.stop();
m_statusTimer.start();
serial.writeFMStatus(m_state);
} }
} }
} }
@@ -655,26 +696,22 @@ void CFM::listeningStateSimplex(bool validRFSignal, bool validExtSignal)
if (validRFSignal) { if (validRFSignal) {
DEBUG1("State to RELAYING_RF"); DEBUG1("State to RELAYING_RF");
m_state = FS_RELAYING_RF; m_state = FS_RELAYING_RF;
serial.writeFMStatus(m_state);
io.setDecode(true); io.setDecode(true);
io.setADCDetection(true); io.setADCDetection(true);
m_timeoutTimer.start(); m_timeoutTimer.start();
m_reverseTimer.stop(); m_reverseTimer.stop();
m_statusTimer.start();
serial.writeFMStatus(m_state);
} else if (validExtSignal) { } else if (validExtSignal) {
DEBUG1("State to RELAYING_EXT"); DEBUG1("State to RELAYING_EXT");
m_state = FS_RELAYING_EXT; m_state = FS_RELAYING_EXT;
serial.writeFMStatus(m_state);
insertSilence(50U); insertSilence(50U);
m_timeoutTimer.start(); m_timeoutTimer.start();
m_reverseTimer.stop(); m_reverseTimer.stop();
m_statusTimer.start();
serial.writeFMStatus(m_state);
} }
} }
@@ -684,6 +721,11 @@ void CFM::kerchunkRFStateDuplex(bool validSignal)
if (m_kerchunkTimer.hasExpired()) { if (m_kerchunkTimer.hasExpired()) {
DEBUG1("State to RELAYING_RF"); DEBUG1("State to RELAYING_RF");
m_state = FS_RELAYING_RF; m_state = FS_RELAYING_RF;
serial.writeFMStatus(m_state);
m_rssiAccum = 0U;
m_rssiCount = 0U;
m_kerchunkTimer.stop(); m_kerchunkTimer.stop();
if (m_callsignAtStart && m_callsignAtLatch) { if (m_callsignAtStart && m_callsignAtLatch) {
sendCallsign(); sendCallsign();
@@ -696,11 +738,12 @@ void CFM::kerchunkRFStateDuplex(bool validSignal)
DEBUG1("State to LISTENING"); DEBUG1("State to LISTENING");
m_state = FS_LISTENING; m_state = FS_LISTENING;
serial.writeFMStatus(m_state);
m_kerchunkTimer.stop(); m_kerchunkTimer.stop();
m_timeoutTimer.stop(); m_timeoutTimer.stop();
m_ackMinTimer.stop(); m_ackMinTimer.stop();
m_callsignTimer.stop(); m_callsignTimer.stop();
m_statusTimer.stop();
m_needReverse = true; m_needReverse = true;
if (m_extEnabled) if (m_extEnabled)
serial.writeFMEOT(); serial.writeFMEOT();
@@ -710,9 +753,21 @@ void CFM::kerchunkRFStateDuplex(bool validSignal)
void CFM::relayingRFStateDuplex(bool validSignal) void CFM::relayingRFStateDuplex(bool validSignal)
{ {
if (validSignal) { if (validSignal) {
#if defined(SEND_RSSI_DATA)
if (m_rssiCount >= 24000U) {
uint16_t rssi = m_rssiAccum / m_rssiCount;
serial.writeFMRSSI(rssi);
m_rssiAccum = 0U;
m_rssiCount = 0U;
}
#endif
if (m_timeoutTimer.isRunning() && m_timeoutTimer.hasExpired()) { if (m_timeoutTimer.isRunning() && m_timeoutTimer.hasExpired()) {
DEBUG1("State to TIMEOUT_RF"); DEBUG1("State to TIMEOUT_RF");
m_state = FS_TIMEOUT_RF; m_state = FS_TIMEOUT_RF;
serial.writeFMStatus(m_state);
m_ackMinTimer.stop(); m_ackMinTimer.stop();
m_timeoutTimer.stop(); m_timeoutTimer.stop();
m_timeoutTone.start(); m_timeoutTone.start();
@@ -726,6 +781,8 @@ void CFM::relayingRFStateDuplex(bool validSignal)
DEBUG1("State to RELAYING_WAIT_RF"); DEBUG1("State to RELAYING_WAIT_RF");
m_state = FS_RELAYING_WAIT_RF; m_state = FS_RELAYING_WAIT_RF;
serial.writeFMStatus(m_state);
m_ackDelayTimer.start(); m_ackDelayTimer.start();
if (m_extEnabled) if (m_extEnabled)
@@ -744,6 +801,7 @@ void CFM::relayingRFStateSimplex(bool validSignal)
if (m_timeoutTimer.isRunning() && m_timeoutTimer.hasExpired()) { if (m_timeoutTimer.isRunning() && m_timeoutTimer.hasExpired()) {
DEBUG1("State to TIMEOUT_RF"); DEBUG1("State to TIMEOUT_RF");
m_state = FS_TIMEOUT_RF; m_state = FS_TIMEOUT_RF;
serial.writeFMStatus(m_state);
m_timeoutTimer.stop(); m_timeoutTimer.stop();
@@ -756,6 +814,8 @@ void CFM::relayingRFStateSimplex(bool validSignal)
DEBUG1("State to RELAYING_WAIT_RF"); DEBUG1("State to RELAYING_WAIT_RF");
m_state = FS_RELAYING_WAIT_RF; m_state = FS_RELAYING_WAIT_RF;
serial.writeFMStatus(m_state);
m_ackDelayTimer.start(); m_ackDelayTimer.start();
if (m_extEnabled) if (m_extEnabled)
@@ -771,11 +831,17 @@ void CFM::relayingRFWaitStateDuplex(bool validSignal)
DEBUG1("State to RELAYING_RF"); DEBUG1("State to RELAYING_RF");
m_state = FS_RELAYING_RF; m_state = FS_RELAYING_RF;
serial.writeFMStatus(m_state);
m_rssiAccum = 0U;
m_rssiCount = 0U;
m_ackDelayTimer.stop(); m_ackDelayTimer.stop();
} else { } else {
if (m_ackDelayTimer.isRunning() && m_ackDelayTimer.hasExpired()) { if (m_ackDelayTimer.isRunning() && m_ackDelayTimer.hasExpired()) {
DEBUG1("State to HANG"); DEBUG1("State to HANG");
m_state = FS_HANG; m_state = FS_HANG;
serial.writeFMStatus(m_state);
if (m_ackMinTimer.isRunning()) { if (m_ackMinTimer.isRunning()) {
if (m_ackMinTimer.hasExpired()) { if (m_ackMinTimer.hasExpired()) {
@@ -809,11 +875,15 @@ void CFM::relayingRFWaitStateSimplex(bool validSignal)
DEBUG1("State to RELAYING_RF"); DEBUG1("State to RELAYING_RF");
m_state = FS_RELAYING_RF; m_state = FS_RELAYING_RF;
serial.writeFMStatus(m_state);
m_ackDelayTimer.stop(); m_ackDelayTimer.stop();
} else { } else {
if (m_ackDelayTimer.isRunning() && m_ackDelayTimer.hasExpired()) { if (m_ackDelayTimer.isRunning() && m_ackDelayTimer.hasExpired()) {
DEBUG1("State to LISTENING"); DEBUG1("State to LISTENING");
m_state = FS_LISTENING; m_state = FS_LISTENING;
serial.writeFMStatus(m_state);
m_ackDelayTimer.stop(); m_ackDelayTimer.stop();
m_timeoutTimer.stop(); m_timeoutTimer.stop();
} }
@@ -826,6 +896,8 @@ void CFM::kerchunkExtStateDuplex(bool validSignal)
if (m_kerchunkTimer.hasExpired()) { if (m_kerchunkTimer.hasExpired()) {
DEBUG1("State to RELAYING_EXT"); DEBUG1("State to RELAYING_EXT");
m_state = FS_RELAYING_EXT; m_state = FS_RELAYING_EXT;
serial.writeFMStatus(m_state);
m_kerchunkTimer.stop(); m_kerchunkTimer.stop();
if (m_callsignAtStart && m_callsignAtLatch) { if (m_callsignAtStart && m_callsignAtLatch) {
sendCallsign(); sendCallsign();
@@ -835,11 +907,12 @@ void CFM::kerchunkExtStateDuplex(bool validSignal)
} else { } else {
DEBUG1("State to LISTENING"); DEBUG1("State to LISTENING");
m_state = FS_LISTENING; m_state = FS_LISTENING;
serial.writeFMStatus(m_state);
m_kerchunkTimer.stop(); m_kerchunkTimer.stop();
m_timeoutTimer.stop(); m_timeoutTimer.stop();
m_ackMinTimer.stop(); m_ackMinTimer.stop();
m_callsignTimer.stop(); m_callsignTimer.stop();
m_statusTimer.stop();
m_needReverse = true; m_needReverse = true;
} }
} }
@@ -850,6 +923,8 @@ void CFM::relayingExtStateDuplex(bool validSignal)
if (m_timeoutTimer.isRunning() && m_timeoutTimer.hasExpired()) { if (m_timeoutTimer.isRunning() && m_timeoutTimer.hasExpired()) {
DEBUG1("State to TIMEOUT_EXT"); DEBUG1("State to TIMEOUT_EXT");
m_state = FS_TIMEOUT_EXT; m_state = FS_TIMEOUT_EXT;
serial.writeFMStatus(m_state);
m_ackMinTimer.stop(); m_ackMinTimer.stop();
m_timeoutTimer.stop(); m_timeoutTimer.stop();
m_timeoutTone.start(); m_timeoutTone.start();
@@ -857,6 +932,7 @@ void CFM::relayingExtStateDuplex(bool validSignal)
} else { } else {
DEBUG1("State to RELAYING_WAIT_EXT"); DEBUG1("State to RELAYING_WAIT_EXT");
m_state = FS_RELAYING_WAIT_EXT; m_state = FS_RELAYING_WAIT_EXT;
serial.writeFMStatus(m_state);
m_ackDelayTimer.start(); m_ackDelayTimer.start();
} }
@@ -872,12 +948,14 @@ void CFM::relayingExtStateSimplex(bool validSignal)
if (m_timeoutTimer.isRunning() && m_timeoutTimer.hasExpired()) { if (m_timeoutTimer.isRunning() && m_timeoutTimer.hasExpired()) {
DEBUG1("State to TIMEOUT_EXT"); DEBUG1("State to TIMEOUT_EXT");
m_state = FS_TIMEOUT_EXT; m_state = FS_TIMEOUT_EXT;
serial.writeFMStatus(m_state);
m_timeoutTimer.stop(); m_timeoutTimer.stop();
} }
} else { } else {
DEBUG1("State to RELAYING_WAIT_EXT"); DEBUG1("State to RELAYING_WAIT_EXT");
m_state = FS_RELAYING_WAIT_EXT; m_state = FS_RELAYING_WAIT_EXT;
serial.writeFMStatus(m_state);
m_ackDelayTimer.start(); m_ackDelayTimer.start();
} }
} }
@@ -887,11 +965,13 @@ void CFM::relayingExtWaitStateDuplex(bool validSignal)
if (validSignal) { if (validSignal) {
DEBUG1("State to RELAYING_EXT"); DEBUG1("State to RELAYING_EXT");
m_state = FS_RELAYING_EXT; m_state = FS_RELAYING_EXT;
serial.writeFMStatus(m_state);
m_ackDelayTimer.stop(); m_ackDelayTimer.stop();
} else { } else {
if (m_ackDelayTimer.isRunning() && m_ackDelayTimer.hasExpired()) { if (m_ackDelayTimer.isRunning() && m_ackDelayTimer.hasExpired()) {
DEBUG1("State to HANG"); DEBUG1("State to HANG");
m_state = FS_HANG; m_state = FS_HANG;
serial.writeFMStatus(m_state);
if (m_ackMinTimer.isRunning()) { if (m_ackMinTimer.isRunning()) {
if (m_ackMinTimer.hasExpired()) { if (m_ackMinTimer.hasExpired()) {
@@ -922,11 +1002,14 @@ void CFM::relayingExtWaitStateSimplex(bool validSignal)
if (validSignal) { if (validSignal) {
DEBUG1("State to RELAYING_EXT"); DEBUG1("State to RELAYING_EXT");
m_state = FS_RELAYING_EXT; m_state = FS_RELAYING_EXT;
serial.writeFMStatus(m_state);
m_ackDelayTimer.stop(); m_ackDelayTimer.stop();
} else { } else {
if (m_ackDelayTimer.isRunning() && m_ackDelayTimer.hasExpired()) { if (m_ackDelayTimer.isRunning() && m_ackDelayTimer.hasExpired()) {
DEBUG1("State to LISTENING"); DEBUG1("State to LISTENING");
m_state = FS_LISTENING; m_state = FS_LISTENING;
serial.writeFMStatus(m_state);
m_ackDelayTimer.stop(); m_ackDelayTimer.stop();
m_timeoutTimer.stop(); m_timeoutTimer.stop();
m_needReverse = true; m_needReverse = true;
@@ -942,6 +1025,11 @@ void CFM::hangStateDuplex(bool validRFSignal, bool validExtSignal)
DEBUG1("State to RELAYING_RF"); DEBUG1("State to RELAYING_RF");
m_state = FS_RELAYING_RF; m_state = FS_RELAYING_RF;
serial.writeFMStatus(m_state);
m_rssiAccum = 0U;
m_rssiCount = 0U;
DEBUG1("Stop ack"); DEBUG1("Stop ack");
m_rfAck.stop(); m_rfAck.stop();
m_extAck.stop(); m_extAck.stop();
@@ -949,6 +1037,8 @@ void CFM::hangStateDuplex(bool validRFSignal, bool validExtSignal)
} else if (validExtSignal) { } else if (validExtSignal) {
DEBUG1("State to RELAYING_EXT"); DEBUG1("State to RELAYING_EXT");
m_state = FS_RELAYING_EXT; m_state = FS_RELAYING_EXT;
serial.writeFMStatus(m_state);
DEBUG1("Stop ack"); DEBUG1("Stop ack");
m_rfAck.stop(); m_rfAck.stop();
m_extAck.stop(); m_extAck.stop();
@@ -957,8 +1047,9 @@ void CFM::hangStateDuplex(bool validRFSignal, bool validExtSignal)
if (m_hangTimer.isRunning() && m_hangTimer.hasExpired()) { if (m_hangTimer.isRunning() && m_hangTimer.hasExpired()) {
DEBUG1("State to LISTENING"); DEBUG1("State to LISTENING");
m_state = FS_LISTENING; m_state = FS_LISTENING;
serial.writeFMStatus(m_state);
m_hangTimer.stop(); m_hangTimer.stop();
m_statusTimer.stop();
if (m_callsignAtEnd) if (m_callsignAtEnd)
sendCallsign(); sendCallsign();
@@ -982,6 +1073,7 @@ void CFM::timeoutRFStateDuplex(bool validSignal)
DEBUG1("State to TIMEOUT_WAIT_RF"); DEBUG1("State to TIMEOUT_WAIT_RF");
m_state = FS_TIMEOUT_WAIT_RF; m_state = FS_TIMEOUT_WAIT_RF;
serial.writeFMStatus(m_state);
if (m_callsignAtEnd) if (m_callsignAtEnd)
sendCallsign(); sendCallsign();
@@ -1003,6 +1095,7 @@ void CFM::timeoutRFStateSimplex(bool validSignal)
DEBUG1("State to TIMEOUT_WAIT_RF"); DEBUG1("State to TIMEOUT_WAIT_RF");
m_state = FS_TIMEOUT_WAIT_RF; m_state = FS_TIMEOUT_WAIT_RF;
serial.writeFMStatus(m_state);
m_ackDelayTimer.start(); m_ackDelayTimer.start();
} }
@@ -1016,11 +1109,14 @@ void CFM::timeoutRFWaitStateDuplex(bool validSignal)
DEBUG1("State to TIMEOUT_RF"); DEBUG1("State to TIMEOUT_RF");
m_state = FS_TIMEOUT_RF; m_state = FS_TIMEOUT_RF;
serial.writeFMStatus(m_state);
m_ackDelayTimer.stop(); m_ackDelayTimer.stop();
} else { } else {
if (m_ackDelayTimer.isRunning() && m_ackDelayTimer.hasExpired()) { if (m_ackDelayTimer.isRunning() && m_ackDelayTimer.hasExpired()) {
DEBUG1("State to HANG"); DEBUG1("State to HANG");
m_state = FS_HANG; m_state = FS_HANG;
serial.writeFMStatus(m_state);
m_timeoutTone.stop(); m_timeoutTone.stop();
DEBUG1("Send RF ack"); DEBUG1("Send RF ack");
m_rfAck.start(); m_rfAck.start();
@@ -1045,11 +1141,14 @@ void CFM::timeoutRFWaitStateSimplex(bool validSignal)
DEBUG1("State to TIMEOUT_RF"); DEBUG1("State to TIMEOUT_RF");
m_state = FS_TIMEOUT_RF; m_state = FS_TIMEOUT_RF;
serial.writeFMStatus(m_state);
m_ackDelayTimer.stop(); m_ackDelayTimer.stop();
} else { } else {
if (m_ackDelayTimer.isRunning() && m_ackDelayTimer.hasExpired()) { if (m_ackDelayTimer.isRunning() && m_ackDelayTimer.hasExpired()) {
DEBUG1("State to LISTENING"); DEBUG1("State to LISTENING");
m_state = FS_LISTENING; m_state = FS_LISTENING;
serial.writeFMStatus(m_state);
m_ackDelayTimer.stop(); m_ackDelayTimer.stop();
m_timeoutTimer.stop(); m_timeoutTimer.stop();
} }
@@ -1061,6 +1160,7 @@ void CFM::timeoutExtStateDuplex(bool validSignal)
if (!validSignal) { if (!validSignal) {
DEBUG1("State to TIMEOUT_WAIT_EXT"); DEBUG1("State to TIMEOUT_WAIT_EXT");
m_state = FS_TIMEOUT_WAIT_EXT; m_state = FS_TIMEOUT_WAIT_EXT;
serial.writeFMStatus(m_state);
m_ackDelayTimer.start(); m_ackDelayTimer.start();
} }
@@ -1075,6 +1175,7 @@ void CFM::timeoutExtStateSimplex(bool validSignal)
if (!validSignal) { if (!validSignal) {
DEBUG1("State to TIMEOUT_WAIT_EXT"); DEBUG1("State to TIMEOUT_WAIT_EXT");
m_state = FS_TIMEOUT_WAIT_EXT; m_state = FS_TIMEOUT_WAIT_EXT;
serial.writeFMStatus(m_state);
m_ackDelayTimer.start(); m_ackDelayTimer.start();
} }
} }
@@ -1084,11 +1185,14 @@ void CFM::timeoutExtWaitStateDuplex(bool validSignal)
if (validSignal) { if (validSignal) {
DEBUG1("State to TIMEOUT_EXT"); DEBUG1("State to TIMEOUT_EXT");
m_state = FS_TIMEOUT_EXT; m_state = FS_TIMEOUT_EXT;
serial.writeFMStatus(m_state);
m_ackDelayTimer.stop(); m_ackDelayTimer.stop();
} else { } else {
if (m_ackDelayTimer.isRunning() && m_ackDelayTimer.hasExpired()) { if (m_ackDelayTimer.isRunning() && m_ackDelayTimer.hasExpired()) {
DEBUG1("State to HANG"); DEBUG1("State to HANG");
m_state = FS_HANG; m_state = FS_HANG;
serial.writeFMStatus(m_state);
m_timeoutTone.stop(); m_timeoutTone.stop();
DEBUG1("Send Ext ack"); DEBUG1("Send Ext ack");
m_extAck.start(); m_extAck.start();
@@ -1110,11 +1214,14 @@ void CFM::timeoutExtWaitStateSimplex(bool validSignal)
if (validSignal) { if (validSignal) {
DEBUG1("State to TIMEOUT_EXT"); DEBUG1("State to TIMEOUT_EXT");
m_state = FS_TIMEOUT_EXT; m_state = FS_TIMEOUT_EXT;
serial.writeFMStatus(m_state);
m_ackDelayTimer.stop(); m_ackDelayTimer.stop();
} else { } else {
if (m_ackDelayTimer.isRunning() && m_ackDelayTimer.hasExpired()) { if (m_ackDelayTimer.isRunning() && m_ackDelayTimer.hasExpired()) {
DEBUG1("State to LISTENING"); DEBUG1("State to LISTENING");
m_state = FS_LISTENING; m_state = FS_LISTENING;
serial.writeFMStatus(m_state);
m_ackDelayTimer.stop(); m_ackDelayTimer.stop();
m_timeoutTimer.stop(); m_timeoutTimer.stop();
m_needReverse = true; m_needReverse = true;
@@ -1131,7 +1238,6 @@ void CFM::linkStateMachine(bool validRFSignal, bool validExtSignal)
if (!m_extSignal) { if (!m_extSignal) {
DEBUG1("State to RELAYING_RF"); DEBUG1("State to RELAYING_RF");
m_state = FS_RELAYING_RF; m_state = FS_RELAYING_RF;
m_statusTimer.start();
serial.writeFMStatus(m_state); serial.writeFMStatus(m_state);
} }
@@ -1142,7 +1248,6 @@ void CFM::linkStateMachine(bool validRFSignal, bool validExtSignal)
if (!m_rfSignal) { if (!m_rfSignal) {
DEBUG1("State to RELAYING_EXT"); DEBUG1("State to RELAYING_EXT");
m_state = FS_RELAYING_EXT; m_state = FS_RELAYING_EXT;
m_statusTimer.start();
serial.writeFMStatus(m_state); serial.writeFMStatus(m_state);
} }
@@ -1151,6 +1256,12 @@ void CFM::linkStateMachine(bool validRFSignal, bool validExtSignal)
m_extSignal = true; m_extSignal = true;
} }
if (validExtSignal && m_extGapTimer.isRunning()) {
// Recovered before the gap timer committed to a real loss below --
// m_extSignal was never flipped, so there is nothing else to undo.
m_extGapTimer.stop();
}
if (!validRFSignal && m_rfSignal) { if (!validRFSignal && m_rfSignal) {
io.setDecode(false); io.setDecode(false);
io.setADCDetection(false); io.setADCDetection(false);
@@ -1158,7 +1269,7 @@ void CFM::linkStateMachine(bool validRFSignal, bool validExtSignal)
if (!m_extSignal) { if (!m_extSignal) {
DEBUG1("State to LISTENING"); DEBUG1("State to LISTENING");
m_state = FS_LISTENING; m_state = FS_LISTENING;
m_statusTimer.stop(); serial.writeFMStatus(m_state);
} }
m_rfSignal = false; m_rfSignal = false;
@@ -1168,14 +1279,28 @@ void CFM::linkStateMachine(bool validRFSignal, bool validExtSignal)
} }
if (!validExtSignal && m_extSignal) { if (!validExtSignal && m_extSignal) {
if (!m_rfSignal) { // A missed/late sample used to flip m_extSignal false right here, on
DEBUG1("State to LISTENING"); // the very first bad sample, with zero debounce -- that is what
m_state = FS_LISTENING; // actually keys the transmitter off (IO::process() drops PTT the
m_statusTimer.stop(); // instant the downstream TX buffer runs dry, see IO.cpp). A gap well
} // under FM_LINK_EXT_GAP_MS is less offensive than the PTT
// drop-and-rekey it triggers. Give it a short grace period
// mirroring the RELAYING_EXT/RELAYING_WAIT_EXT pattern the
// non-link duplex/simplex paths already use for exactly this
// situation (see relayingExtStateDuplex()/relayingExtWaitStateDuplex()).
if (!m_extGapTimer.isRunning()) {
m_extGapTimer.start();
} else if (m_extGapTimer.hasExpired()) {
if (!m_rfSignal) {
DEBUG1("State to LISTENING");
m_state = FS_LISTENING;
serial.writeFMStatus(m_state);
}
m_needReverse = true; m_needReverse = true;
m_extSignal = false; m_extSignal = false;
m_extGapTimer.stop();
}
} }
} }
+13 -20
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020,2021,2025 by Jonathan Naylor G4KLX * Copyright (C) 2020,2021,2023,2025 by Jonathan Naylor G4KLX
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@@ -35,27 +35,12 @@
#include "FMUpSampler.h" #include "FMUpSampler.h"
#include "FMNoiseSquelch.h" #include "FMNoiseSquelch.h"
enum FM_STATE {
FS_LISTENING,
FS_KERCHUNK_RF,
FS_RELAYING_RF,
FS_RELAYING_WAIT_RF,
FS_TIMEOUT_RF,
FS_TIMEOUT_WAIT_RF,
FS_KERCHUNK_EXT,
FS_RELAYING_EXT,
FS_RELAYING_WAIT_EXT,
FS_TIMEOUT_EXT,
FS_TIMEOUT_WAIT_EXT,
FS_HANG
};
class CFM { class CFM {
public: public:
CFM(); CFM();
void samples(bool cos, q15_t* samples, uint8_t length); void samples(bool cos, q15_t* samples, const uint16_t* rssi, uint8_t length);
void process(); void process();
@@ -78,7 +63,7 @@ private:
CFMCTCSSTX m_ctcssTX; CFMCTCSSTX m_ctcssTX;
CFMNoiseSquelch m_squelch; CFMNoiseSquelch m_squelch;
CFMTimeout m_timeoutTone; CFMTimeout m_timeoutTone;
FM_STATE m_state; uint8_t m_state;
bool m_callsignAtStart; bool m_callsignAtStart;
bool m_callsignAtEnd; bool m_callsignAtEnd;
bool m_callsignAtLatch; bool m_callsignAtLatch;
@@ -89,12 +74,18 @@ private:
CFMTimer m_ackMinTimer; CFMTimer m_ackMinTimer;
CFMTimer m_ackDelayTimer; CFMTimer m_ackDelayTimer;
CFMTimer m_hangTimer; CFMTimer m_hangTimer;
CFMTimer m_statusTimer;
CFMTimer m_reverseTimer; CFMTimer m_reverseTimer;
CFMTimer m_extGapTimer; // debounce for link-mode ext-audio underrun, see linkStateMachine()
bool m_needReverse; bool m_needReverse;
CFMDirectFormI m_filterStage1; CFMDirectFormI m_filterStage1;
CFMDirectFormI m_filterStage2; CFMDirectFormI m_filterStage2;
CFMDirectFormI m_filterStage3; CFMDirectFormI m_filterStage3;
CFMDirectFormI m_dsFilterStage1; // anti-alias filter for the downsampler, own state
CFMDirectFormI m_dsFilterStage2;
CFMDirectFormI m_dsFilterStage3;
CFMDirectFormI m_usFilterStage1; // reconstruction filter for the upsampler, own state
CFMDirectFormI m_usFilterStage2;
CFMDirectFormI m_usFilterStage3;
CFMBlanking m_blanking; CFMBlanking m_blanking;
uint8_t m_accessMode; uint8_t m_accessMode;
bool m_linkMode; bool m_linkMode;
@@ -110,10 +101,12 @@ private:
CFMUpSampler m_inputExtRB; CFMUpSampler m_inputExtRB;
bool m_rfSignal; bool m_rfSignal;
bool m_extSignal; bool m_extSignal;
uint32_t m_rssiAccum;
uint16_t m_rssiCount;
void stateMachine(bool validRFSignal, bool validExtSignal); void stateMachine(bool validRFSignal, bool validExtSignal);
void repeaterSamples(bool cos, q15_t* samples, uint8_t length); void repeaterSamples(bool cos, q15_t* samples, const uint16_t* rssi, uint8_t length);
void linkSamples(bool cos, q15_t* samples, uint8_t length); void linkSamples(bool cos, q15_t* samples, uint8_t length);
void duplexStateMachine(bool validRFSignal, bool validExtSignal); void duplexStateMachine(bool validRFSignal, bool validExtSignal);
+3
View File
@@ -34,6 +34,7 @@ m_sampleIndex(0U)
void CFMDownSampler::addSample(q15_t sample) void CFMDownSampler::addSample(q15_t sample)
{ {
sample = __SSAT(sample, 12);//clamp before packing to 12 bits
uint32_t usample = uint32_t(int32_t(sample) + 2048); uint32_t usample = uint32_t(int32_t(sample) + 2048);
//only take one of three samples //only take one of three samples
switch(m_sampleIndex){ switch(m_sampleIndex){
@@ -74,6 +75,8 @@ uint16_t CFMDownSampler::getData()
void CFMDownSampler::reset() void CFMDownSampler::reset()
{ {
m_sampleIndex = 0U; m_sampleIndex = 0U;
m_samplePack = 0U;
m_ringBuffer.reset();
} }
#endif #endif
+4 -1
View File
@@ -36,7 +36,8 @@ m_count(0U),
m_q0(0), m_q0(0),
m_q1(0), m_q1(0),
m_state(false), m_state(false),
m_validCount(0U) m_validCount(0U),
m_invalidCount(0U)
{ {
} }
@@ -127,6 +128,8 @@ void CFMNoiseSquelch::reset()
m_q1 = 0; m_q1 = 0;
m_state = false; m_state = false;
m_count = 0U; m_count = 0U;
m_validCount = 0U;
m_invalidCount = 0U;
} }
#endif #endif
+5 -5
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2015,2016,2017,2018,2020,2021,2025 by Jonathan Naylor G4KLX * Copyright (C) 2015,2016,2017,2018,2020,2021,2023,2025,2026 by Jonathan Naylor G4KLX
* Copyright (C) 2015 by Jim Mclaughlin KI6ZUM * Copyright (C) 2015 by Jim Mclaughlin KI6ZUM
* Copyright (C) 2016 by Colin Durbridge G4EML * Copyright (C) 2016 by Colin Durbridge G4EML
* *
@@ -457,9 +457,9 @@ void CIO::process()
if (m_fmEnable) { if (m_fmEnable) {
bool cos = getCOSInt(); bool cos = getCOSInt();
#if defined(USE_DCBLOCKER) #if defined(USE_DCBLOCKER)
fm.samples(cos, dcSamples, RX_BLOCK_SIZE); fm.samples(cos, dcSamples, rssi, RX_BLOCK_SIZE);
#else #else
fm.samples(cos, samples, RX_BLOCK_SIZE); fm.samples(cos, samples, rssi, RX_BLOCK_SIZE);
#endif #endif
} }
#endif #endif
@@ -554,9 +554,9 @@ void CIO::process()
else if (m_modemState == STATE_FM) { else if (m_modemState == STATE_FM) {
bool cos = getCOSInt(); bool cos = getCOSInt();
#if defined(USE_DCBLOCKER) #if defined(USE_DCBLOCKER)
fm.samples(cos, dcSamples, RX_BLOCK_SIZE); fm.samples(cos, dcSamples, rssi, RX_BLOCK_SIZE);
#else #else
fm.samples(cos, samples, RX_BLOCK_SIZE); fm.samples(cos, samples, rssi, RX_BLOCK_SIZE);
#endif #endif
} }
#endif #endif
+1 -1
View File
@@ -110,7 +110,7 @@ void CIO::initInt()
#if !defined(USE_ALTERNATE_POCSAG_LEDS) #if !defined(USE_ALTERNATE_POCSAG_LEDS)
pinMode(PIN_POCSAG, OUTPUT); pinMode(PIN_POCSAG, OUTPUT);
#endif #endif
#if !defined(USE_ALTERNATE_POCSAG_LEDS) #if !defined(USE_ALTERNATE_FM_LEDS)
pinMode(PIN_FM, OUTPUT); pinMode(PIN_FM, OUTPUT);
#endif #endif
#endif #endif
+8 -2
View File
@@ -1,7 +1,7 @@
/* /*
* Copyright (C) 2016 by Jim McLaughlin KI6ZUM * Copyright (C) 2016 by Jim McLaughlin KI6ZUM
* Copyright (C) 2016,2017,2018 by Andy Uribe CA6JAU * Copyright (C) 2016,2017,2018 by Andy Uribe CA6JAU
* Copyright (C) 2017,2018,2020,2025 by Jonathan Naylor G4KLX * Copyright (C) 2017,2018,2020,2023,2025 by Jonathan Naylor G4KLX
* Copyright (C) 2019,2020 by BG5HHP * Copyright (C) 2019,2020 by BG5HHP
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
@@ -349,10 +349,16 @@ void CIO::interrupt()
#endif #endif
} }
// trigger next ADC1 // Trigger next ADC1
ADC_ClearFlag(ADC1, ADC_FLAG_EOC); ADC_ClearFlag(ADC1, ADC_FLAG_EOC);
ADC_SoftwareStartConv(ADC1); ADC_SoftwareStartConv(ADC1);
#if defined(SEND_RSSI_DATA)
// Trigger next ADC2
ADC_ClearFlag(ADC2, ADC_FLAG_EOC);
ADC_SoftwareStartConv(ADC2);
#endif
m_rxBuffer.put(sample); m_rxBuffer.put(sample);
m_rssiBuffer.put(rawRSSI); m_rssiBuffer.put(rawRSSI);
+1 -1
View File
@@ -175,7 +175,7 @@ void CNXDNTX::writeByte(uint8_t c)
void CNXDNTX::writeSilence() void CNXDNTX::writeSilence()
{ {
q15_t inBuffer[4U] = {0x00U, 0x00U, 0x00U, 0x00U}; q15_t inBuffer[4U] = {0, 0, 0, 0};
q15_t intBuffer[NXDN_RADIO_SYMBOL_LENGTH * 4U]; q15_t intBuffer[NXDN_RADIO_SYMBOL_LENGTH * 4U];
q15_t outBuffer[NXDN_RADIO_SYMBOL_LENGTH * 4U]; q15_t outBuffer[NXDN_RADIO_SYMBOL_LENGTH * 4U];
+1 -1
View File
@@ -173,7 +173,7 @@ void CP25TX::writeByte(uint8_t c)
void CP25TX::writeSilence() void CP25TX::writeSilence()
{ {
q15_t inBuffer[4U] = {0x00U, 0x00U, 0x00U, 0x00U}; q15_t inBuffer[4U] = {0, 0, 0, 0};
q15_t intBuffer[P25_RADIO_SYMBOL_LENGTH * 4U]; q15_t intBuffer[P25_RADIO_SYMBOL_LENGTH * 4U];
q15_t outBuffer[P25_RADIO_SYMBOL_LENGTH * 4U]; q15_t outBuffer[P25_RADIO_SYMBOL_LENGTH * 4U];
+2 -2
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020 by Jonathan Naylor G4KLX * Copyright (C) 2020,2026 by Jonathan Naylor G4KLX
* Copyright (C) 2020 by Geoffrey Merck F4FXL - KC3FRA * Copyright (C) 2020 by Geoffrey Merck F4FXL - KC3FRA
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
@@ -50,7 +50,7 @@ public:
uint16_t getData() const; uint16_t getData() const;
bool put(TDATATYPE item) volatile; bool put(const TDATATYPE& item) volatile;
bool get(TDATATYPE& item) volatile; bool get(TDATATYPE& item) volatile;
+2 -2
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020 by Jonathan Naylor G4KLX * Copyright (C) 2020,2026 by Jonathan Naylor G4KLX
* Copyright (C) 2020 by Geoffrey Merck F4FXL - KC3FRA * Copyright (C) 2020 by Geoffrey Merck F4FXL - KC3FRA
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
@@ -56,7 +56,7 @@ template <typename TDATATYPE> uint16_t CRingBuffer<TDATATYPE>::getData() const
return m_length - m_tail + m_head; return m_length - m_tail + m_head;
} }
template <typename TDATATYPE> bool CRingBuffer<TDATATYPE>::put(TDATATYPE item) volatile template <typename TDATATYPE> bool CRingBuffer<TDATATYPE>::put(const TDATATYPE& item) volatile
{ {
if (m_full) { if (m_full) {
m_overflow = true; m_overflow = true;
+7 -3
View File
@@ -61,7 +61,7 @@ void CSTMUART::handleIRQ()
if (USART_GetITStatus(m_usart, USART_IT_RXNE)) { if (USART_GetITStatus(m_usart, USART_IT_RXNE)) {
if(!m_rxFifo.isFull()) if(!m_rxFifo.isFull())
m_rxFifo.put((uint8_t) USART_ReceiveData(m_usart)); m_rxFifo.put((uint8_t) USART_ReceiveData(m_usart));
USART_ClearITPendingBit(USART1, USART_IT_RXNE); USART_ClearITPendingBit(m_usart, USART_IT_RXNE);
} }
if (USART_GetITStatus(m_usart, USART_IT_TXE)) { if (USART_GetITStatus(m_usart, USART_IT_TXE)) {
@@ -82,8 +82,12 @@ void CSTMUART::flush()
if(m_usart == NULL) if(m_usart == NULL)
return; return;
// wait until the TXE shows the shift register is empty // wait until the TX FIFO has drained
while (USART_GetITStatus(m_usart, USART_FLAG_TXE)) while (!m_txFifo.isEmpty())
;
// wait until the TC flag shows the shift register is empty
while (USART_GetFlagStatus(m_usart, USART_FLAG_TC) == RESET)
; ;
} }
+39 -44
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2013,2015-2021,2025 by Jonathan Naylor G4KLX * Copyright (C) 2013,2015-2021,2023,2025 by Jonathan Naylor G4KLX
* Copyright (C) 2016 by Colin Durbridge G4EML * Copyright (C) 2016 by Colin Durbridge G4EML
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
@@ -45,6 +45,7 @@ const uint8_t MMDVM_DSTAR_DATA = 0x11U;
const uint8_t MMDVM_DSTAR_LOST = 0x12U; const uint8_t MMDVM_DSTAR_LOST = 0x12U;
const uint8_t MMDVM_DSTAR_EOT = 0x13U; const uint8_t MMDVM_DSTAR_EOT = 0x13U;
const uint8_t MMDVM_DMR_ALOHA = 0x17U;
const uint8_t MMDVM_DMR_DATA1 = 0x18U; const uint8_t MMDVM_DMR_DATA1 = 0x18U;
const uint8_t MMDVM_DMR_LOST1 = 0x19U; const uint8_t MMDVM_DMR_LOST1 = 0x19U;
const uint8_t MMDVM_DMR_DATA2 = 0x1AU; const uint8_t MMDVM_DMR_DATA2 = 0x1AU;
@@ -72,6 +73,7 @@ const uint8_t MMDVM_FM_PARAMS4 = 0x63U;
const uint8_t MMDVM_FM_DATA = 0x65U; const uint8_t MMDVM_FM_DATA = 0x65U;
const uint8_t MMDVM_FM_STATUS = 0x66U; const uint8_t MMDVM_FM_STATUS = 0x66U;
const uint8_t MMDVM_FM_EOT = 0x67U; const uint8_t MMDVM_FM_EOT = 0x67U;
const uint8_t MMDVM_FM_RSSI = 0x68U;
const uint8_t MMDVM_ACK = 0x70U; const uint8_t MMDVM_ACK = 0x70U;
const uint8_t MMDVM_NAK = 0x7FU; const uint8_t MMDVM_NAK = 0x7FU;
@@ -132,10 +134,8 @@ m_buffer(),
m_ptr(0U), m_ptr(0U),
m_len(0U), m_len(0U),
m_debug(false), m_debug(false),
m_serialData(),
m_lastSerialAvail(0), m_lastSerialAvail(0),
m_lastSerialAvailCount(0U), m_lastSerialAvailCount(0U)
m_i2CData()
{ {
} }
@@ -467,6 +467,7 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint16_t length)
#endif #endif
#if defined(MODE_DMR) #if defined(MODE_DMR)
bool trunking = (data[25U] & 128U) == 128U;
uint8_t colorCode = data[26U]; uint8_t colorCode = data[26U];
if (colorCode > 15U) if (colorCode > 15U)
return 4U; return 4U;
@@ -485,7 +486,7 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint16_t length)
#if defined(MODE_DMR) #if defined(MODE_DMR)
m_dmrEnable = dmrEnable; m_dmrEnable = dmrEnable;
dmrDMOTX.setTXDelay(txDelay); dmrDMOTX.setTXDelay(txDelay);
dmrTX.setTrunking(trunking);
dmrTX.setColorCode(colorCode); dmrTX.setColorCode(colorCode);
dmrRX.setColorCode(colorCode); dmrRX.setColorCode(colorCode);
dmrRX.setDelay(dmrDelay); dmrRX.setDelay(dmrDelay);
@@ -744,7 +745,7 @@ void CSerialPort::setMode(MMDVM_STATE modemState)
DEBUG1("Mode set to FM 20Khz Calibrate"); DEBUG1("Mode set to FM 20Khz Calibrate");
break; break;
case STATE_FMCAL25K: case STATE_FMCAL25K:
DEBUG1("Mode set to FM 10Khz Calibrate"); DEBUG1("Mode set to FM 25Khz Calibrate");
break; break;
case STATE_FMCAL30K: case STATE_FMCAL30K:
DEBUG1("Mode set to FM 30Khz Calibrate"); DEBUG1("Mode set to FM 30Khz Calibrate");
@@ -871,20 +872,6 @@ void CSerialPort::process()
} }
#if defined(SERIAL_REPEATER) #if defined(SERIAL_REPEATER)
// Write any outgoing serial data
uint16_t serialSpace = m_serialData.getData();
if (serialSpace > 0U) {
int avail = availableForWriteInt(3U);
if (avail < serialSpace)
serialSpace = avail;
for (uint16_t i = 0U; i < serialSpace; i++) {
uint8_t c = 0U;
m_serialData.get(c);
writeInt(3U, &c, 1U);
}
}
// Read any incoming serial data, and send out in batches // Read any incoming serial data, and send out in batches
int serialAvail = availableForReadInt(3U); int serialAvail = availableForReadInt(3U);
if ((serialAvail > 0 && serialAvail == m_lastSerialAvail && m_lastSerialAvailCount >= MAX_SERIAL_COUNT) || (serialAvail >= MAX_SERIAL_DATA)) { if ((serialAvail > 0 && serialAvail == m_lastSerialAvail && m_lastSerialAvailCount >= MAX_SERIAL_COUNT) || (serialAvail >= MAX_SERIAL_DATA)) {
@@ -902,22 +889,6 @@ void CSerialPort::process()
m_lastSerialAvailCount = 0U; m_lastSerialAvailCount = 0U;
} }
#endif #endif
#if defined(I2C_REPEATER)
// Write any outgoing serial data
uint16_t i2CSpace = m_i2CData.getData();
if (i2CSpace > 0U) {
int avail = availableForWriteInt(10U);
if (avail < i2CSpace)
i2CSpace = avail;
for (uint16_t i = 0U; i < i2CSpace; i++) {
uint8_t c = 0U;
m_i2CData.get(c);
writeInt(10U, &c, 1U);
}
}
#endif
} }
void CSerialPort::processMessage(uint8_t type, const uint8_t* buffer, uint16_t length) void CSerialPort::processMessage(uint8_t type, const uint8_t* buffer, uint16_t length)
@@ -1154,6 +1125,15 @@ void CSerialPort::processMessage(uint8_t type, const uint8_t* buffer, uint16_t l
} }
break; break;
case MMDVM_DMR_ALOHA:
if (m_dmrEnable)
err = dmrTX.writeAloha(buffer, length);
if (err != 0U) {
DEBUG2("Received invalid DMR ALOHA", err);
sendNAK(type, err);
}
break;
case MMDVM_DMR_ABORT: case MMDVM_DMR_ABORT:
if (m_dmrEnable) if (m_dmrEnable)
err = dmrTX.writeAbort(buffer, length); err = dmrTX.writeAbort(buffer, length);
@@ -1264,18 +1244,14 @@ void CSerialPort::processMessage(uint8_t type, const uint8_t* buffer, uint16_t l
break; break;
#if defined(SERIAL_REPEATER) #if defined(SERIAL_REPEATER)
case MMDVM_SERIAL_DATA: { case MMDVM_SERIAL_DATA:
for (uint16_t i = 0U; i < length; i++) writeInt(3U, buffer, length);
m_serialData.put(buffer[i]);
}
break; break;
#endif #endif
#if defined(I2C_REPEATER) #if defined(I2C_REPEATER)
case MMDVM_I2C_DATA: { case MMDVM_I2C_DATA:
for (uint16_t i = 0U; i < length; i++) writeInt(10U, buffer, length);
m_i2CData.put(buffer[i]);
}
break; break;
#endif #endif
@@ -1612,6 +1588,25 @@ void CSerialPort::writeFMStatus(uint8_t status)
writeInt(1U, reply, 4U); writeInt(1U, reply, 4U);
} }
void CSerialPort::writeFMRSSI(uint16_t rssi)
{
if (m_modemState != STATE_FM && m_modemState != STATE_IDLE)
return;
if (!m_fmEnable)
return;
uint8_t reply[10U];
reply[0U] = MMDVM_FRAME_START;
reply[1U] = 5U;
reply[2U] = MMDVM_FM_RSSI;
reply[3U] = (rssi >> 8) & 0xFFU;
reply[4U] = (rssi >> 0) & 0xFFU;
writeInt(1U, reply, 5U);
}
void CSerialPort::writeFMEOT() void CSerialPort::writeFMEOT()
{ {
if (m_modemState != STATE_FM && m_modemState != STATE_IDLE) if (m_modemState != STATE_FM && m_modemState != STATE_IDLE)
+2 -3
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2015,2016,2017,2018,2020,2021,2025 by Jonathan Naylor G4KLX * Copyright (C) 2015,2016,2017,2018,2020,2021,2023,2025 by Jonathan Naylor G4KLX
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@@ -67,6 +67,7 @@ public:
#if defined(MODE_FM) #if defined(MODE_FM)
void writeFMData(const uint8_t* data, uint16_t length); void writeFMData(const uint8_t* data, uint16_t length);
void writeFMStatus(uint8_t status); void writeFMStatus(uint8_t status);
void writeFMRSSI(uint16_t rssi);
void writeFMEOT(); void writeFMEOT();
#endif #endif
@@ -93,10 +94,8 @@ private:
uint16_t m_ptr; uint16_t m_ptr;
uint16_t m_len; uint16_t m_len;
bool m_debug; bool m_debug;
CRingBuffer<uint8_t> m_serialData;
int m_lastSerialAvail; int m_lastSerialAvail;
uint16_t m_lastSerialAvailCount; uint16_t m_lastSerialAvailCount;
CRingBuffer<uint8_t> m_i2CData;
void sendACK(uint8_t type); void sendACK(uint8_t type);
void sendNAK(uint8_t type, uint8_t err); void sendNAK(uint8_t type, uint8_t err);
+2 -3
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020,2021,2022,2025 by Jonathan Naylor G4KLX * Copyright (C) 2020,2021,2022,2023,2025,2026 by Jonathan Naylor G4KLX
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@@ -19,7 +19,6 @@
#if !defined(VERSION_H) #if !defined(VERSION_H)
#define VERSION_H #define VERSION_H
#define VERSION "20240113" #define VERSION "20260424"
#endif #endif
+1 -1
View File
@@ -166,7 +166,7 @@ void CYSFTX::writeByte(uint8_t c)
void CYSFTX::writeSilence() void CYSFTX::writeSilence()
{ {
q15_t inBuffer[4U] = {0x00U, 0x00U, 0x00U, 0x00U}; q15_t inBuffer[4U] = {0, 0, 0, 0};
q15_t outBuffer[YSF_RADIO_SYMBOL_LENGTH * 4U]; q15_t outBuffer[YSF_RADIO_SYMBOL_LENGTH * 4U];
::arm_fir_interpolate_q15(&m_modFilter, inBuffer, outBuffer, 4U); ::arm_fir_interpolate_q15(&m_modFilter, inBuffer, outBuffer, 4U);