Full messaging support, implemented fresh in three layers: - dmrDataProtocol: pure short-data codec (CSBK preamble, data/response headers, IP/UDP/TMS payload, ETSI CRCs) validated against on-air captures by a host-side test suite. Transmits Motorola TMS (UDP 4007), receives TMS and Anytone/"DMR standard" (UDP 5016). - smsCore/smsStorage: TX queue with wait-for-channel and delivery confirmation (response PDU / ACK, resend prompt on timeout), ISR-side block assembly with layout auto-detection (confirmed/unconfirmed, rate-1/2 and full 18-byte rate-3/4 blocks), delivery reports to senders that request them, and a checksummed message store in SPI flash at 0xC00000 (inbox + sent, 8 each; 8 quick texts). - UI: Messages hub (main menu or long-press GREEN), keypad compose with multi-tap (160 chars), inbox/sent/view with reply-resend-delete, quick-text picker/editor, options (Wait for ACK, My ID only), and an incoming-message popup with chime. Strings in all 20 languages. The HR-C6000 does the BPTC/FEC; the driver streams 12-byte logical bursts through the existing TX state machine (repeater wake included), locks out PTT during a send, and leaves hotspot mode untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3278 lines
107 KiB
C
3278 lines
107 KiB
C
/*
|
|
* Copyright (C) 2019 Kai Ludwig, DG4KLU
|
|
* 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 "hardware/HR-C6000.h"
|
|
#include "functions/settings.h"
|
|
#if defined(USING_EXTERNAL_DEBUGGER)
|
|
#include "SeggerRTT/RTT/SEGGER_RTT.h"
|
|
#endif
|
|
#include "functions/trx.h"
|
|
#include "functions/hotspot.h"
|
|
#include "user_interface/uiUtilities.h"
|
|
#include "functions/voicePrompts.h"
|
|
#include "interfaces/gpio.h"
|
|
#include "interfaces/interrupts.h"
|
|
#include "functions/rxPowerSaving.h"
|
|
#include "functions/ticks.h"
|
|
#include "functions/smsCore.h"
|
|
#include "interfaces/gps.h"
|
|
#if defined(PLATFORM_MD9600) || defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
|
|
#include "hardware/radioHardwareInterface.h"
|
|
#endif
|
|
|
|
#define QSODATA_TIMER_TIMEOUT 2400
|
|
#define QSODATA_RX_BEEP_TIMER_TIMEOUT (QSODATA_TIMER_TIMEOUT - 1000)
|
|
// we can't immediately use the LC data out of the chip, as it will return the header from the previous
|
|
// reception (except in hotspot mode), and mess up all the lastheard, contact data, display QSO Info
|
|
#define QSODATA_THRESHOLD_COUNT 2 // number of frames sequence #1 before sending data to the UI.
|
|
#define INTERRUPT_TIMEOUT 200
|
|
|
|
#define SYS_INT_SEND_REQUEST_REJECTED 0x80
|
|
#define SYS_INT_SEND_START 0x40
|
|
#define SYS_INT_SEND_END 0x20
|
|
#define SYS_INT_POST_ACCESS 0x10
|
|
#define SYS_INT_RECEIVED_DATA 0x08
|
|
#define SYS_INT_RECEIVED_INFORMATION 0x04
|
|
#define SYS_INT_ABNORMAL_EXIT 0x02
|
|
#define SYS_INT_PHYSICAL_LAYER 0x01
|
|
|
|
#define WAKEUP_RETRY_PERIOD 600 // The official firmware seems to use a 600mS retry period.
|
|
|
|
#define NUM_AMBE_BLOCK_PER_DMR_FRAME 3
|
|
#define NUM_AMBE_BUFFERS 4
|
|
#define LENGTH_AMBE_BLOCK 9
|
|
|
|
#define START_TICK_TIMEOUT 20
|
|
#define END_TICK_TIMEOUT 13
|
|
|
|
#define CC_HOLD_TIME 5000 // 5 second
|
|
#define TS_SYNC_STARTUP_TIMEOUT 2500 // 2.5 seconds timeout while synchronizing timeslot
|
|
#define TS_SYNC_SCAN_TIMEOUT (360 + 30) // 1 superframe + 1 TS timeout, for timeslot sync while scanning
|
|
|
|
#define TS_DISAGREE_THRESHOLD 3
|
|
#define TS_STABLE_THRESHOLD 4
|
|
#define TS_IS_LOCKED 6
|
|
|
|
#define HS_NUM_OF_SILENCE_SEQ_ON_STARTUP 1 // Hotspot: number of silence sequences (x6 frames) sent to the chip when a transmission is starting (cleaner audio result)
|
|
|
|
#define SUPERFRAME_NUM_FRAMES 6
|
|
|
|
#define CC_PROBE_MAX_COUNT (4 * 2)
|
|
#define CC_PROBE_LOCKED (CC_PROBE_MAX_COUNT + 1)
|
|
|
|
|
|
Task_t hrc6000Task;
|
|
|
|
static bool sendingDCS = false;
|
|
|
|
static const uint8_t SILENCE_AUDIO[AMBE_AUDIO_LENGTH] = {
|
|
0xB9U, 0xE8U, 0x81U, 0x52U, 0x61U, 0x73U, 0x00U, 0x2AU, 0x6BU, 0xB9U, 0xE8U, 0x81U, 0x52U,
|
|
0x61U, 0x73U, 0x00U, 0x2AU, 0x6BU, 0xB9U, 0xE8U, 0x81U, 0x52U, 0x61U, 0x73U, 0x00U, 0x2AU, 0x6BU
|
|
};
|
|
|
|
//ALL THE FOLLOWING INITIALISATION VALES WERE CAPTURED FROM THE OFFCIAL TYT FIRMWARE FOR THE MD-9600
|
|
|
|
|
|
static const uint8_t MS_sync_pattern[] = { 0xd5, 0xd7, 0xf7, 0x7f, 0xd7, 0x57 }; //Mobile Station Sync Pattern for voice calls Repeater or Simplex
|
|
//static const uint8_t TDMA1_sync_pattern[] = { 0xf7, 0xfd, 0xd5, 0xdd, 0xfd, 0x55 }; //TDMA1 Sync Pattern for voice calls TDMA Simplex
|
|
//static const uint8_t TDMA2_sync_pattern[] = { 0xd7, 0x55, 0x7f, 0x5f, 0xf7, 0xf5 }; //TDMA2 Sync Pattern for voice calls TDMA Simplex
|
|
|
|
//send to 0x01 0x10
|
|
static const uint8_t spi_init_values_2[] = { 0x69, 0x69, 0x96, 0x96, 0x96, 0x99, 0x99, 0x99, 0xa5, 0xa5, 0xaa, 0xaa, 0xcc, 0xcc, 0x00, 0xf0, 0x01, 0xff, 0x01, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
|
//send to 0x01 0x30
|
|
static const uint8_t spi_init_values_3[] = { 0x00, 0x00, 0x20, 0x3C, 0xFF, 0xFF, 0x3F, 0x50, 0x07, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
|
//send to 0x01 0x40
|
|
static const uint8_t spi_init_values_4[] = { 0x00, 0x01, 0x01, 0x02, 0x01, 0x1e, 0xf0 };
|
|
// send to 0x01 0x50
|
|
static const uint8_t spi_init_values_5[] = { 0x00, 0x08, 0xeb, 0x78, 0x67 };
|
|
/* send to 0x01 0x60 (these values are not sent by the TYT firmware but retained just in case.)
|
|
static const uint8_t spi_init_values_6[] = { 0x32, 0xef, 0x00, 0x31, 0xef, 0x00, 0x12, 0xef, 0x00, 0x13, 0xef, 0x00, 0x14, 0xef, 0x00, 0x15, 0xef, 0x00, 0x16, 0xef, 0x00, 0x17, 0xef, 0x00, 0x18, 0xef, 0x00, 0x19, 0xef, 0x00, 0x1a, 0xef, 0x00, 0x1b, 0xef, 0x00, 0x1c, 0xef, 0x00, 0x1d, 0xef, 0x00, 0x1e, 0xef, 0x00, 0x1f, 0xef, 0x00, 0x20, 0xef, 0x00, 0x21, 0xef, 0x00, 0x22, 0xef, 0x00, 0x23, 0xef, 0x00, 0x24, 0xef, 0x00, 0x25, 0xef, 0x00, 0x26, 0xef, 0x00, 0x27, 0xef, 0x00, 0x28, 0xef, 0x00, 0x29, 0xef, 0x00, 0x2a, 0xef, 0x00, 0x2b, 0xef, 0x00, 0x2c, 0xef, 0x00, 0x2d, 0xef, 0x00, 0x2e, 0xef, 0x00, 0x2f, 0xef, 0x00 };
|
|
*/
|
|
|
|
static const uint8_t spiInitReg0x04_PLL[7][2] = {
|
|
{0x0A, 0x80}, //internal clock
|
|
{0x0B, 0x28}, //Set PLL M Register Changed to suit MD9600 Clock
|
|
{0x0C, 0x33}, //Set PLL Dividers Changed to suit MD9600 Clock
|
|
{0x0A, 0x00}, //Set Clock Source to PLL //MD9600
|
|
{0xB9, 0x05}, // Configure system clocks to Datasheet values
|
|
{0xBA, 0x04}, //
|
|
{0xBB, 0x02} //
|
|
};
|
|
|
|
static const uint8_t spiInitReg0x04MultiInit1[][2] = {
|
|
{0xA1, 0x80}, //Set Baseband receive mode (this overrides and IF mode selections)
|
|
{0x10, 0xF3}, //
|
|
{0x5F, 0xF0}, //G4EML Enable Sync detection for MS, BS , TDMA1 or TDMA2 originated signals (Was originally 0xC0)
|
|
{0x40, 0x43}, //Enable DMR Rx, Passive Timing, Normal mode
|
|
{0x07, 0x0B}, //Set IF Frequency H to default 450KHz
|
|
{0x08, 0xB8}, //Set IF Frequency M to default 450KHz
|
|
{0x09, 0x00}, //Set IF Frequency L to default 450KHz
|
|
{0x06, 0x21}, //Use SPI vocoder under MCU control
|
|
{0x00, 0xFF}, //Reset All modules.
|
|
{0x01, 0xB0}, //set 2 point Mod, receive mode IF
|
|
{0x02, 0x00}, //zero Transmit I Offset
|
|
{0x03, 0x00}, //zero Receive I Offset
|
|
{0x04, 0x00}, //zero Transmit Q Offset
|
|
{0x05, 0x00}, //Zero Receive Q Offset
|
|
{0x01, 0xF8}
|
|
};
|
|
|
|
// send to 0x04 0x11
|
|
static const uint8_t spi_init_values_7[] = {0x80, 0x0C, 0x22, 0x01, 0x00, 0x00, 0x33, 0xEF, 0x00, 0xFF, 0xFF, 0xFF, 0xF0, 0xF0, 0x10, 0x00, 0x00, 0x06, 0x3B, 0xF8, 0x0E, 0xFD, 0x40, 0xFF, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x06, 0x0B, 0x00, 0x17, 0x02, 0xFF, 0xE0, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
|
|
static const uint8_t spiInitReg0x04MultiInit2[][2] = {
|
|
{0x00, 0x2A}, //Partial Reset
|
|
{0x06, 0x22} //open music
|
|
};
|
|
|
|
static const uint8_t spiInitReg0x04MultiInit3[][2] = {
|
|
{0x06, 0x20}, //Open Music Off
|
|
{0x14, 0xB0}, // Set DMR ID = 2350000
|
|
{0x15, 0xDB}, // (will be reset later by radio ID)
|
|
{0x16, 0x23} //
|
|
};
|
|
|
|
static const uint8_t spiInitReg0x04MultiInit4[][2] = {
|
|
{0x39, 0x02}, //Unknown Register
|
|
{0x3D, 0x0A}, //Unknown Register
|
|
{0x83, 0xFF}, // clear interrupts
|
|
{0x87, 0x00}, //interrupt masks
|
|
{0x65, 0x0A}, //Unknown Register
|
|
{0x1D, 0xFF}, //Unaddress mask
|
|
{0x1E, 0xF1}, //broadcast address
|
|
{0xE2, 0x06}, //Configure Codec
|
|
{0xE4, 0x27}, //Mic Gain
|
|
{0xE3, 0x52}, //Codec Default value
|
|
{0xE5, 0x1A}, //Codec Default value
|
|
{0xE1, 0x0F}, //Codec Default value
|
|
{0xD1, 0xC4},
|
|
{0x25, 0x0E}, //DAC power control
|
|
{0x26, 0xFD}, //ADC Control
|
|
{0x64, 0x00}, //Unknown Register
|
|
{0x10, 0x6B}, //Set DMR,Tier2,Timeslot Mode, Layer 2, Repeater, Aligned, Slot1
|
|
{0x81, 0x19},
|
|
{0x01, 0xF0},
|
|
{0xE4, 0x27},
|
|
{0xE5, 0x1A},
|
|
{0x37, 0x9E},
|
|
{0xE0, 0xC9},
|
|
{0x25, 0x0E},
|
|
{0x26, 0xFD},
|
|
{0x48, 0x00}, //Set Offset from Ref Osc calibration value
|
|
{0x47, 0x21}, //'''
|
|
{0x1F, 0x10} //Set CC1
|
|
};
|
|
|
|
|
|
enum RXSyncClass { SYNC_CLASS_HEADER = 0, SYNC_CLASS_VOICE = 1, SYNC_CLASS_DATA = 2, SYNC_CLASS_RC = 3 };
|
|
enum RXSyncType { MS_SYNC = 0, BS_SYNC = 1, TDMA1_SYNC = 2, TDMA2_SYNC = 3 };
|
|
|
|
|
|
static volatile uint8_t reg_0x51;
|
|
static volatile uint8_t reg_0x5F;
|
|
static volatile uint8_t reg_0x82;
|
|
static volatile uint8_t reg_0x84;
|
|
static volatile uint8_t reg_0x86;
|
|
static volatile uint8_t reg_0x90;
|
|
static volatile uint8_t reg_0x98;
|
|
|
|
volatile uint8_t DMR_frame_buffer[DMR_FRAME_BUFFER_SIZE];
|
|
static uint8_t deferredUpdateBuffer[AMBE_AUDIO_LENGTH * NUM_AMBE_BUFFERS];// WAS [DMR_FRAME_BUFFER_SIZE * 6]; 384
|
|
static const uint8_t *DEFERRED_UPDATE_BUFFER_END = (uint8_t *)deferredUpdateBuffer + (AMBE_AUDIO_LENGTH * NUM_AMBE_BUFFERS) - 1;
|
|
|
|
static struct
|
|
{
|
|
volatile bool hasEncodedAudio;
|
|
volatile bool hasAudioData;
|
|
volatile bool hasAbnormalExit;
|
|
volatile int receivedFramesCount;
|
|
volatile bool insertSilenceFrame;
|
|
volatile bool transmissionEnabled;
|
|
volatile bool rxCRCisValid;
|
|
volatile bool hotspotDMRTxFrameBufferEmpty;
|
|
volatile bool hotspotDMRRxFrameBufferAvailable;
|
|
volatile bool inIRQHandler;
|
|
volatile uint8_t *deferredUpdateBufferOutPtr;
|
|
volatile uint8_t *deferredUpdateBufferInPtr;
|
|
volatile int ambeBufferCount;
|
|
volatile int interruptTimeout;
|
|
volatile uint32_t receivedTgOrPcId;
|
|
volatile uint32_t receivedSrcId;
|
|
volatile int tickCount;
|
|
volatile int skipCount;
|
|
volatile bool skipOneTS;
|
|
volatile int qsoDataSeqCount;
|
|
volatile int qsoDataTimeout;
|
|
volatile int txSequence;
|
|
volatile int timeCode;
|
|
volatile uint8_t rxColorCode;
|
|
volatile int repeaterWakeupResponseTimeout;
|
|
volatile int isWaking;
|
|
volatile int tsAgreed;
|
|
volatile int tsDisagreed;
|
|
volatile int rxTSToggled; // used for Repeater wakeup sequence
|
|
volatile int tsLockedTS;
|
|
volatile int lastTimeCode;
|
|
volatile uint8_t previousLCBuf[LC_DATA_LENGTH];
|
|
volatile int dmrMonitorCapturedTimeout;
|
|
volatile int TAPhase;
|
|
volatile bool keepMonitorCapturedTSAfterTxing;
|
|
volatile uint8_t lastRxColorCode;
|
|
volatile int lastRxColorCodeCount;
|
|
volatile uint32_t lastRxColorCodeTime;
|
|
volatile bool ccHold;
|
|
uint8_t bufferLimitReachedCount;
|
|
volatile int ccHoldTimer;
|
|
volatile uint32_t ccHoldReleaseTickTime;
|
|
int wakeTriesCount;
|
|
int hotspotPostponedFrameHandling;
|
|
char talkAliasText[33];
|
|
uint8_t talkAliasLocation[7];
|
|
volatile bool smsActive; // an SMS air job is being streamed instead of voice
|
|
volatile uint8_t smsFrameIndex;
|
|
const smsAirJob_t *volatile smsJob;
|
|
} hrc = {
|
|
.hasEncodedAudio = false,
|
|
.hasAudioData = false,
|
|
.hasAbnormalExit = false,
|
|
.receivedFramesCount = -1,
|
|
.insertSilenceFrame = false,
|
|
.transmissionEnabled = false,
|
|
.rxCRCisValid = false,
|
|
.hotspotDMRTxFrameBufferEmpty = true,
|
|
.hotspotDMRRxFrameBufferAvailable = false,
|
|
.inIRQHandler = false,
|
|
.deferredUpdateBufferOutPtr = deferredUpdateBuffer,
|
|
.deferredUpdateBufferInPtr = deferredUpdateBuffer,
|
|
.ambeBufferCount = 0,
|
|
.interruptTimeout = 0,
|
|
.receivedTgOrPcId = 0,
|
|
.receivedSrcId = 0,
|
|
.tickCount = 0,
|
|
.skipCount = 0,
|
|
.skipOneTS = false,
|
|
.qsoDataSeqCount = 0,
|
|
.qsoDataTimeout = 0,
|
|
.txSequence = 0,
|
|
.timeCode = -1,
|
|
.rxColorCode = 0,
|
|
.repeaterWakeupResponseTimeout = 0,
|
|
.isWaking = WAKING_MODE_NONE,
|
|
.tsAgreed = 0,
|
|
.tsDisagreed = 0,
|
|
.rxTSToggled = 0,
|
|
.tsLockedTS = -1,
|
|
.lastTimeCode = -2,
|
|
.previousLCBuf = { 0 },
|
|
.dmrMonitorCapturedTimeout = 0,
|
|
.TAPhase = 0,
|
|
.keepMonitorCapturedTSAfterTxing = false,
|
|
.lastRxColorCode = 0xFF,
|
|
.lastRxColorCodeCount = 0,
|
|
.lastRxColorCodeTime = 0,
|
|
.bufferLimitReachedCount = 0,
|
|
.ccHold = true,
|
|
.ccHoldTimer = 0,
|
|
.ccHoldReleaseTickTime = 0,
|
|
.wakeTriesCount = 0,
|
|
.hotspotPostponedFrameHandling = 0,
|
|
.talkAliasText = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
.talkAliasLocation = { 0, 0, 0, 0, 0, 0, 0},
|
|
.smsActive = false,
|
|
.smsFrameIndex = 0,
|
|
.smsJob = NULL
|
|
};
|
|
|
|
volatile int slotState = DMR_STATE_IDLE;
|
|
volatile ticksTimer_t readDMRRSSITimer = { 0, 0 };
|
|
volatile bool updateLastHeard = false;
|
|
volatile int dmrMonitorCapturedTS = -1;
|
|
|
|
|
|
static bool hrc6000CallAcceptFilter(void);
|
|
static void hrc6000SendPcOrTgLCHeader(void);
|
|
#ifdef CPU_MK22FN512VLL12
|
|
static inline void hrc6000SysInterruptHandler(void);
|
|
static inline void hrc6000TimeslotInterruptHandler(void);
|
|
static inline void hrc6000TxInterruptHandler(void);
|
|
#endif
|
|
static inline void hrc6000RxInterruptHandler(void);
|
|
static void hrc6000TransitionToTx(void);
|
|
static void hrc6000InitDigitalState(void);
|
|
static void hrc6000TriggerPrivateCallQSODataDisplay(void);
|
|
|
|
static HRC6000_Tone1Config_t savedTone1Config = { .Mode = 0, .Dev = 0, .D1 = 0 };
|
|
|
|
uint8_t getCurrentTATxFlag(void)
|
|
{
|
|
uint8_t flag;
|
|
uint8_t taTxFlagTS1 = currentChannelData->flag1 & 0x03;
|
|
uint8_t taTxFlagTS2 = (currentChannelData->flag1 >> 2) & 0x03;
|
|
|
|
if (currentRadioDevice->trxDMRModeTx == DMR_MODE_DMO)
|
|
{
|
|
flag = taTxFlagTS1 | taTxFlagTS2;
|
|
}
|
|
else
|
|
{
|
|
flag = ((trxGetDMRTimeSlot() == 0) ? taTxFlagTS1 : taTxFlagTS2);
|
|
}
|
|
|
|
if ((flag == TA_TX_APRS) && (settingsLocationIsValid() == false))
|
|
{
|
|
flag = TA_TX_OFF;
|
|
}
|
|
#if defined(USING_EXTERNAL_DEBUGGER)
|
|
SEGGER_RTT_printf(0, "%02x\n",flag);
|
|
#endif
|
|
|
|
return flag;
|
|
}
|
|
|
|
static void hrc6000WriteSPIRegister0x04Multi(const uint8_t values[][2], uint8_t length)
|
|
{
|
|
for(uint8_t i = 0; i < length; i++)
|
|
{
|
|
SPI0WritePageRegByte(0x04, values[i][0], values[i][1]);
|
|
}
|
|
}
|
|
|
|
//Updated by G4EML to reflect the sequence used by the official TYT firmware on the MD-9600
|
|
|
|
void HRC6000Init(void)
|
|
{
|
|
hrc.inIRQHandler = false;
|
|
|
|
// Wake up C6000
|
|
HAL_GPIO_WritePin(C6000_PWD_GPIO_Port, C6000_PWD_Pin, 0);
|
|
vTaskDelay((10U / portTICK_PERIOD_MS));
|
|
|
|
// initialise clocks
|
|
hrc6000WriteSPIRegister0x04Multi(spiInitReg0x04_PLL, (sizeof(spiInitReg0x04_PLL) / sizeof(spiInitReg0x04_PLL[0])));
|
|
|
|
//Initial setup
|
|
hrc6000WriteSPIRegister0x04Multi(spiInitReg0x04MultiInit1, (sizeof(spiInitReg0x04MultiInit1) / sizeof(spiInitReg0x04MultiInit1[0])));
|
|
|
|
//Send multiple byte setup
|
|
SPI0WritePageRegByteArray(0x04, 0x11, spi_init_values_7, 0x2C);
|
|
|
|
//reset and send open music (I think this possibly inits the vocoder)
|
|
hrc6000WriteSPIRegister0x04Multi(spiInitReg0x04MultiInit2, (sizeof(spiInitReg0x04MultiInit2) / sizeof(spiInitReg0x04MultiInit2[0])));
|
|
|
|
// send 128 0x7Fs to bank 3
|
|
const int SIZE_OF_FILL_BUFFER = 128;
|
|
uint8_t spi_values[SIZE_OF_FILL_BUFFER];
|
|
memset(spi_values, 0x7F, SIZE_OF_FILL_BUFFER);
|
|
SPI0WritePageRegByteArray(0x03, 0x00, spi_values, SIZE_OF_FILL_BUFFER);
|
|
|
|
//reset open music and set DMR ID
|
|
hrc6000WriteSPIRegister0x04Multi(spiInitReg0x04MultiInit3, (sizeof(spiInitReg0x04MultiInit3) / sizeof(spiInitReg0x04MultiInit3[0])));
|
|
|
|
//send auxiliary config registers
|
|
SPI0WritePageRegByteArray(0x01, 0x10, spi_init_values_2, 0x20);
|
|
SPI0WritePageRegByteArray(0x01, 0x30, spi_init_values_3, 0x10);
|
|
SPI0WritePageRegByteArray(0x01, 0x40, spi_init_values_4, 0x07);
|
|
SPI0WritePageRegByteArray(0x01, 0x50, spi_init_values_5, 0x05);
|
|
// SPI0WritePageRegByteArray(0x01, 0x60, spi_init_values_6, 0x60);
|
|
|
|
//set a few more auxiliary config registers
|
|
SPI0WritePageRegByte(0x01, 0x52, 0x08);
|
|
SPI0WritePageRegByte(0x01, 0x53, 0xEB);
|
|
SPI0WritePageRegByte(0x01, 0x54, 0x78);
|
|
SPI0WritePageRegByte(0x01, 0x45, 0x1E);
|
|
SPI0WritePageRegByte(0x01, 0x37, 0x50);
|
|
SPI0WritePageRegByte(0x01, 0x35, 0xFF);
|
|
|
|
//More Initialisation
|
|
hrc6000WriteSPIRegister0x04Multi(spiInitReg0x04MultiInit4, (sizeof(spiInitReg0x04MultiInit4) / sizeof(spiInitReg0x04MultiInit4[0])));
|
|
|
|
//set a few more auxiliary config registers
|
|
SPI0WritePageRegByte(0x01, 0x54, 0x78);
|
|
SPI0WritePageRegByte(0x01, 0x24, 0x00);
|
|
SPI0WritePageRegByte(0x01, 0x25, 0x00);
|
|
SPI0WritePageRegByte(0x01, 0x26, 0x00);
|
|
SPI0WritePageRegByte(0x01, 0x27, 0x00);
|
|
|
|
//initialise ready to receive
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x40); //Rx in next Slot
|
|
SPI0WritePageRegByte(0x04, 0x56, 0x00); //Unknown Register
|
|
SPI0WritePageRegByte(0x04, 0x5C, 0x09); //Unknown Register
|
|
SPI0WritePageRegByte(0x04, 0x5F, 0xF0); //Set Sync detect to MS, BS, TDMA1 and TDMA2
|
|
|
|
//set the MS Synch pattern
|
|
SPI0WritePageRegByteArray(0x01, 0x04, MS_sync_pattern, 0x06);
|
|
|
|
//final init
|
|
SPI0WritePageRegByte(0x04, 0x11, 0x80); //Set local chan mode
|
|
SPI0WritePageRegByte(0x04, 0x81, 0x19); //Interrupt Masks
|
|
SPI0WritePageRegByte(0x04, 0x85, 0x00); //Disable Interrupts
|
|
|
|
|
|
HRC6000SetMicGainDMR(nonVolatileSettings.micGainDMR);
|
|
|
|
HRC6000ClearActiveDMRID();
|
|
HRC6000InitDTMF(); // initialise the DTMF Tones/
|
|
}
|
|
|
|
void HRC6000FlushMusic(void)
|
|
{
|
|
// send 128 0x7Fs to bank 3
|
|
const int SIZE_OF_FILL_BUFFER = 128;
|
|
uint8_t spi_values[SIZE_OF_FILL_BUFFER];
|
|
|
|
|
|
HRC6000SetDmrRxGain(-15); //temporarily set the gain to -15dB. Any less and the buffer flush doesn't seem to work.
|
|
|
|
SPI0WritePageRegByte(0x04, 0x06, 0x22); //set open music mode
|
|
memset(spi_values, 0x7F, SIZE_OF_FILL_BUFFER);
|
|
SPI0WritePageRegByteArray(0x03, 0x00, spi_values, SIZE_OF_FILL_BUFFER);
|
|
SPI0WritePageRegByte(0x04, 0x06, 0x20); //reset open music mode
|
|
|
|
HRC6000SetDmrRxGain(getVolumeControl()); //restore gain to the volume control setting
|
|
}
|
|
|
|
|
|
void HRC6000SetMicGainDMR(uint8_t gain)
|
|
{
|
|
SPI0WritePageRegByte(0x04, 0xE4, 0x20 + gain);
|
|
}
|
|
|
|
static inline bool hrc6000CrcIsValid(void)
|
|
{
|
|
if ((settingsUsbMode != USB_MODE_HOTSPOT) && settingsIsOptionBitSet(BIT_DMR_CRC_IGNORED))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return hrc.rxCRCisValid;
|
|
}
|
|
|
|
static int hrc6000GetTSTimeoutValue(void)
|
|
{
|
|
if (uiDataGlobal.Scan.active)
|
|
{
|
|
if ((uiDataGlobal.Scan.state == SCAN_STATE_SHORT_PAUSED) || (uiDataGlobal.Scan.state == SCAN_STATE_SCANNING))
|
|
{
|
|
return TS_SYNC_SCAN_TIMEOUT;
|
|
}
|
|
}
|
|
|
|
if (monitorModeData.isEnabled)
|
|
{
|
|
return (hrc.hasAudioData ? (nonVolatileSettings.dmrCaptureTimeout * 1000) : TS_SYNC_SCAN_TIMEOUT);
|
|
}
|
|
|
|
return ((hrc.transmissionEnabled || hrc.hasAudioData) ? (nonVolatileSettings.dmrCaptureTimeout * 1000) : TS_SYNC_STARTUP_TIMEOUT);
|
|
}
|
|
|
|
static inline bool hrc6000CheckTimeSlotFilter(void)
|
|
{
|
|
if (hrc.transmissionEnabled)
|
|
{
|
|
return (hrc.timeCode == trxGetDMRTimeSlot());
|
|
}
|
|
else
|
|
{
|
|
if (nonVolatileSettings.dmrCcTsFilter & DMR_TS_FILTER_PATTERN)
|
|
{
|
|
int currentTS = (((hrc.tsAgreed == TS_IS_LOCKED) && (hrc.tsLockedTS != -1)) ? hrc.tsLockedTS : trxGetDMRTimeSlot());
|
|
|
|
dmrMonitorCapturedTS = currentTS;
|
|
hrc.dmrMonitorCapturedTimeout = nonVolatileSettings.dmrCaptureTimeout * 1000;
|
|
|
|
return (hrc.timeCode == currentTS);
|
|
}
|
|
else
|
|
{
|
|
if ((hrc.timeCode != -1) && (hrc.tsAgreed > TS_STABLE_THRESHOLD) && ((dmrMonitorCapturedTS == -1) || (dmrMonitorCapturedTS == hrc.timeCode)))
|
|
{
|
|
dmrMonitorCapturedTS = hrc.timeCode;
|
|
hrc.dmrMonitorCapturedTimeout = hrc6000GetTSTimeoutValue();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool HRC6000CheckTalkGroupFilter(void)
|
|
{
|
|
if (((hrc.receivedTgOrPcId >> 24) == PC_CALL_FLAG) && (settingsUsbMode != USB_MODE_HOTSPOT))
|
|
{
|
|
// Handle private calls if they are allowed
|
|
if((nonVolatileSettings.privateCalls != 0) || ((trxTalkGroupOrPcId >> 24) == PC_CALL_FLAG))
|
|
{
|
|
// Monitor all private calls if DMR filter = None. Only allow private call to this radio in other cases.
|
|
if((nonVolatileSettings.dmrDestinationFilter == DMR_DESTINATION_FILTER_NONE) || (hrc.receivedTgOrPcId == (trxDMRID | (PC_CALL_FLAG << 24))))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// All call bypasses filtering
|
|
if (hrc.receivedTgOrPcId == ALL_CALL_VALUE)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
|
|
switch(nonVolatileSettings.dmrDestinationFilter)
|
|
{
|
|
case DMR_DESTINATION_FILTER_TG:
|
|
return ((trxTalkGroupOrPcId & 0x00FFFFFF) == hrc.receivedTgOrPcId);
|
|
break;
|
|
|
|
case DMR_DESTINATION_FILTER_DC:
|
|
return codeplugContactsContainsPC(hrc.receivedSrcId);
|
|
break;
|
|
|
|
case DMR_DESTINATION_FILTER_RXG:
|
|
for(int i = 0; i < currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup; i++)
|
|
{
|
|
if (currentRxGroupData.NOT_IN_CODEPLUG_contactsTG[i] == hrc.receivedTgOrPcId)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Also include currently selected talkgroup even if it is not in the RXG
|
|
if ((trxTalkGroupOrPcId & 0x00FFFFFF) == hrc.receivedTgOrPcId)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool hrc6000CheckColourCodeFilter(void)
|
|
{
|
|
return (hrc.rxColorCode == trxGetDMRColourCode());
|
|
}
|
|
|
|
static void hrc6000TransmitTalkerAlias(void)
|
|
{
|
|
int loopSize;
|
|
uint8_t flag = getCurrentTATxFlag();
|
|
|
|
switch(flag)
|
|
{
|
|
case TA_TX_APRS:
|
|
loopSize = 2;
|
|
break;
|
|
case TA_TX_TEXT:
|
|
loopSize = 9;
|
|
break;
|
|
case TA_TX_BOTH:
|
|
loopSize = (settingsLocationIsValid() == false) ? 9 : 11;
|
|
break;
|
|
default:
|
|
loopSize = 0;// This should not occur as this function should not be called if the flag is TA_TX_OFF
|
|
break;
|
|
}
|
|
|
|
if (hrc.TAPhase % 2 == 0)
|
|
{
|
|
hrc6000SendPcOrTgLCHeader();
|
|
}
|
|
else
|
|
{
|
|
uint8_t TA_LCBuf[LC_DATA_LENGTH] = {0,0,0,0,0,0,0,0,0,0,0,0};
|
|
int taPosition = 2;
|
|
int taOffset, taLength;
|
|
uint32_t phase = hrc.TAPhase / 2;
|
|
|
|
if (flag == TA_TX_APRS)
|
|
{
|
|
phase = 4;
|
|
}
|
|
|
|
switch(phase)
|
|
{
|
|
case 0:
|
|
taPosition = 3;
|
|
TA_LCBuf[2]= (0x01 << 6) | (strlen(hrc.talkAliasText) << 1);
|
|
taOffset = 0;
|
|
taLength = 6;
|
|
break;
|
|
case 1:
|
|
taOffset = 6;
|
|
taLength = 7;
|
|
break;
|
|
case 2:
|
|
taOffset = 13;
|
|
taLength = 7;
|
|
break;
|
|
case 3:
|
|
taOffset = 20;
|
|
taLength = 7;
|
|
break;
|
|
case 4:
|
|
taOffset = 20;// not used for this phase
|
|
taLength = 7;
|
|
break;
|
|
default:
|
|
taOffset = 0;
|
|
taLength = 0;
|
|
break;
|
|
}
|
|
|
|
TA_LCBuf[0]= (phase) + 0x04;
|
|
|
|
if (phase <= 3)
|
|
{
|
|
memcpy(&TA_LCBuf[taPosition], &hrc.talkAliasText[taOffset], taLength);
|
|
}
|
|
else
|
|
{
|
|
memcpy(&TA_LCBuf[taPosition], &hrc.talkAliasLocation[0], 7);
|
|
}
|
|
|
|
SPI0WritePageRegByteArray(0x02, 0x00, (uint8_t*)TA_LCBuf, taPosition + taLength);// put LC into hardware
|
|
}
|
|
|
|
|
|
hrc.TAPhase = ((hrc.TAPhase + 1) % loopSize);
|
|
}
|
|
|
|
static void hrc6000HandleLCData(void)
|
|
{
|
|
uint8_t LCBuf[LC_DATA_LENGTH];
|
|
bool lcResult = (SPI0ReadPageRegByteArray(0x02, 0x00, LCBuf, LC_DATA_LENGTH) == kStatus_Success); // read the LC from the C6000
|
|
|
|
if (lcResult && hrc6000CrcIsValid() && hrc.ccHold && (hrc.tsAgreed > TS_STABLE_THRESHOLD))
|
|
{
|
|
bool lcSent = false;
|
|
|
|
if ((((LCBuf[0] == TG_CALL_FLAG) || (LCBuf[0] == PC_CALL_FLAG))
|
|
|| ((LCBuf[0] >= DMR_EMBEDDED_DATA_TALKER_ALIAS_HEADER) && (LCBuf[0] <= DMR_EMBEDDED_DATA_GPS_INFO))) &&
|
|
(((settingsUsbMode == USB_MODE_HOTSPOT) || (memcmp((uint8_t *)hrc.previousLCBuf, LCBuf, LC_DATA_LENGTH) != 0)) ||
|
|
(monitorModeData.isEnabled && monitorModeData.dmrIsValid && (monitorModeData.qsoInfoUpdated == false))))
|
|
{
|
|
if (((currentRadioDevice->trxDMRModeRx == DMR_MODE_DMO) || hrc6000CheckTimeSlotFilter()) && hrc6000CheckColourCodeFilter()) // only do this for the selected timeslot, or when in Active mode
|
|
{
|
|
if ((LCBuf[0] == TG_CALL_FLAG) || (LCBuf[0] == PC_CALL_FLAG))
|
|
{
|
|
uint32_t prevTgOrPcId = hrc.receivedTgOrPcId;
|
|
uint32_t prevSrcId = hrc.receivedSrcId;
|
|
|
|
hrc.receivedTgOrPcId = (LCBuf[0] << 24) + (LCBuf[3] << 16) + (LCBuf[4] << 8) + (LCBuf[5] << 0); // used by the call accept filter
|
|
hrc.receivedSrcId = (LCBuf[6] << 16) + (LCBuf[7] << 8) + (LCBuf[8] << 0); // used by the call accept filter
|
|
|
|
if ((hrc.receivedTgOrPcId != 0) && (hrc.receivedSrcId != 0) && HRC6000CheckTalkGroupFilter() &&
|
|
((settingsUsbMode == USB_MODE_HOTSPOT) || (hrc.qsoDataSeqCount >= QSODATA_THRESHOLD_COUNT))) // only store the data if its actually valid
|
|
{
|
|
if (monitorModeData.isEnabled && monitorModeData.dmrIsValid &&
|
|
(monitorModeData.dmrFrameSkip == 0) && monitorModeData.qsoInfoUpdated && (updateLastHeard == false))
|
|
{
|
|
monitorModeData.qsoInfoUpdated = false;
|
|
}
|
|
|
|
if ((monitorModeData.isEnabled && monitorModeData.dmrIsValid &&
|
|
(monitorModeData.dmrFrameSkip == 0) && ((monitorModeData.qsoInfoUpdated == false))) ||
|
|
((monitorModeData.isEnabled == false) && (updateLastHeard == false)))
|
|
{
|
|
memcpy((uint8_t *)DMR_frame_buffer, LCBuf, LC_DATA_LENGTH);
|
|
// Don't update the screen until it's synced, or after the QSO turn has ended
|
|
lcSent = updateLastHeard = ((slotState != DMR_STATE_IDLE) && (hrc.qsoDataTimeout > 0));
|
|
monitorModeData.qsoInfoUpdated = true;
|
|
|
|
if (uiDataGlobal.rxBeepState & RX_BEEP_CARRIER_HAS_STARTED)
|
|
{
|
|
// ID or TG/PC has changed too fast (mostly because people are stepping on others toes),
|
|
// hence we were unable to play the CALLER_HAS_ENDED beep.
|
|
if (((prevTgOrPcId > 0) && (prevTgOrPcId != hrc.receivedTgOrPcId)) ||
|
|
((prevSrcId > 0) && (prevSrcId != hrc.receivedSrcId)))
|
|
{
|
|
if ((uiDataGlobal.rxBeepState & RX_BEEP_TALKER_HAS_STARTED) && ((uiDataGlobal.rxBeepState & RX_BEEP_TALKER_HAS_ENDED) == 0))
|
|
{
|
|
uiDataGlobal.rxBeepState |= (RX_BEEP_TALKER_HAS_ENDED | RX_BEEP_TALKER_HAS_ENDED_EXEC);
|
|
}
|
|
|
|
// Clearing this bit, as we don't want the qso info timeout trigger that beep again.
|
|
uiDataGlobal.rxBeepState &= ~(RX_BEEP_TALKER_IDENTIFIED | RX_BEEP_TALKER_HAS_STARTED | RX_BEEP_TALKER_HAS_STARTED_EXEC);
|
|
}
|
|
else
|
|
{
|
|
uiDataGlobal.rxBeepState |= RX_BEEP_TALKER_IDENTIFIED;
|
|
|
|
if ((uiDataGlobal.rxBeepState & RX_BEEP_TALKER_HAS_STARTED) == 0)
|
|
{
|
|
uiDataGlobal.rxBeepState |= (RX_BEEP_TALKER_HAS_STARTED | RX_BEEP_TALKER_HAS_STARTED_EXEC);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((updateLastHeard == false) && (hrc.receivedTgOrPcId != 0) && HRC6000CheckTalkGroupFilter() &&
|
|
((settingsUsbMode == USB_MODE_HOTSPOT) || (hrc.qsoDataSeqCount >= QSODATA_THRESHOLD_COUNT)))
|
|
{
|
|
if (monitorModeData.isEnabled && monitorModeData.dmrIsValid &&
|
|
(monitorModeData.dmrFrameSkip == 0) && monitorModeData.qsoInfoUpdated)
|
|
{
|
|
monitorModeData.qsoInfoUpdated = false;
|
|
}
|
|
|
|
if ((monitorModeData.isEnabled && monitorModeData.dmrIsValid &&
|
|
(monitorModeData.dmrFrameSkip == 0) && (((monitorModeData.qsoInfoUpdated == false)) && (hrc.receivedSrcId != 0))) ||
|
|
(monitorModeData.isEnabled == false))
|
|
{
|
|
memcpy((uint8_t *)DMR_frame_buffer, LCBuf, LC_DATA_LENGTH);
|
|
// Don't update the screen until it's synced, or after the QSO turn has ended
|
|
lcSent = updateLastHeard = ((slotState != DMR_STATE_IDLE) && (hrc.qsoDataTimeout > 0));
|
|
monitorModeData.qsoInfoUpdated = true;
|
|
|
|
if (uiDataGlobal.rxBeepState & RX_BEEP_CARRIER_HAS_STARTED)
|
|
{
|
|
if ((uiDataGlobal.rxBeepState & RX_BEEP_TALKER_IDENTIFIED) == 0)
|
|
{
|
|
uiDataGlobal.rxBeepState |= RX_BEEP_TALKER_IDENTIFIED;
|
|
}
|
|
|
|
if ((uiDataGlobal.rxBeepState & RX_BEEP_TALKER_HAS_STARTED) == 0)
|
|
{
|
|
uiDataGlobal.rxBeepState |= (RX_BEEP_TALKER_HAS_STARTED | RX_BEEP_TALKER_HAS_STARTED_EXEC);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (lcSent) // only track LC data sent.
|
|
{
|
|
memcpy((uint8_t *)hrc.previousLCBuf, LCBuf, LC_DATA_LENGTH);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef CPU_MK22FN512VLL12
|
|
void PORTC_IRQHandler(void)
|
|
{
|
|
//#warning port to MDUV380
|
|
|
|
hrc.inIRQHandler = true;
|
|
|
|
if (interruptsWasPinTriggered(Port_INT_C6000_SYS, Pin_INT_C6000_SYS))
|
|
{
|
|
if (rxPowerSavingIsRxOn())
|
|
{
|
|
hrc6000SysInterruptHandler();
|
|
}
|
|
interruptsClearPinFlags(Port_INT_C6000_SYS, Pin_INT_C6000_SYS);
|
|
}
|
|
|
|
if (interruptsWasPinTriggered(Port_INT_C6000_RF_RX, Pin_INT_C6000_TS))
|
|
{
|
|
hrc6000TimeslotInterruptHandler();
|
|
interruptsClearPinFlags(Port_INT_C6000_RF_RX, Pin_INT_C6000_TS);
|
|
}
|
|
|
|
if (interruptsWasPinTriggered(Port_INT_C6000_RF_RX, Pin_INT_C6000_RF_RX))
|
|
{
|
|
hrc6000RxInterruptHandler();
|
|
interruptsClearPinFlags(Port_INT_C6000_RF_RX, Pin_INT_C6000_RF_RX);
|
|
}
|
|
|
|
if (interruptsWasPinTriggered(Port_INT_C6000_RF_TX, Pin_INT_C6000_RF_TX))
|
|
{
|
|
hrc6000TxInterruptHandler();
|
|
interruptsClearPinFlags(Port_INT_C6000_RF_TX, Pin_INT_C6000_RF_TX);
|
|
}
|
|
|
|
hrc.interruptTimeout = 0;
|
|
hrc.inIRQHandler = false;
|
|
|
|
/* Add for ARM errata 838869, affects Cortex-M4, Cortex-M4F Store immediate overlapping
|
|
exception return operation might vector to incorrect interrupt */
|
|
__DSB();
|
|
|
|
}
|
|
#endif
|
|
static inline void hrc6000SysSendRejectedInt(void)
|
|
{
|
|
if (hrc.smsActive)
|
|
{
|
|
// The channel became busy before our data went out
|
|
hrc.smsActive = false;
|
|
hrc.transmissionEnabled = false;
|
|
trxTransmissionEnabled = false;
|
|
smsTxNotifyRejected();
|
|
}
|
|
}
|
|
|
|
static inline void hrc6000SysSendStartInt(void)
|
|
{
|
|
SPI0ReadPageRegByte(0x04, 0x84, ®_0x84); //Read sub status register
|
|
|
|
/*
|
|
The sub-status registers indicate
|
|
seven interrupts that initiate the transmission, including:
|
|
Bit7: Voice transmission starts
|
|
|
|
Bit6: OACSU requests to send interrupts, including first-time send and resend requests.
|
|
|
|
Bit5: End-to-end voice enhanced encryption interrupt, including EMB72bits update interrupt
|
|
and voice 216bits key update interrupt, which are distinguished by Bit5~Bit4 of Register
|
|
0x88, where 01 indicates EMB72bits update interrupt and 10 indicates voice 216bits key update interrupt.
|
|
|
|
Bit4: The Vocoder configuration returns an interrupt (this interrupt is sent by the HR_C6000
|
|
to the MCU when the MCU manually configures the AMBE3000). This interrupt is only
|
|
valid when using the external AMBE3000 vocoder.
|
|
|
|
Bit3: Data transmission starts
|
|
|
|
Bit2: Data partial retransmission
|
|
|
|
Bit1: Data retransmission
|
|
|
|
Bit0: The vocoder is initialized to an interrupt. This interrupt is only valid when using an external AMBE3000 or AMBE1000 vocoder.
|
|
|
|
In MSK mode, there is no sub-interrupt status.
|
|
*/
|
|
}
|
|
|
|
static inline void hrc6000SysSendEndInt(void)
|
|
{
|
|
SPI0ReadPageRegByte(0x04, 0x86, ®_0x86); //Read Interrupt Flag Register2
|
|
|
|
/*
|
|
In DMR mode, there is a sub-status register 0x86 at the end of the transmission, and the
|
|
corresponding interrupt can be masked by 0x87. The sub-status register indicates six interrupts that
|
|
generate the end of the transmission, including:
|
|
|
|
Bit7: Indicates that the service transmission is completely terminated, including voice and data.
|
|
The MCU distinguishes whether the voice or data is sent this time. Confirming that the
|
|
data service is received is the response packet that receives the correct feedback.
|
|
|
|
Bit6: Indicates that a Fragment length confirmation packet is sent in the sliding window data
|
|
service without immediate feedback.
|
|
|
|
Bit5: VoiceOACSU wait timeout
|
|
|
|
Bit4: The Layer 2 mode handles the interrupt. The MCU sends the configuration information
|
|
to the last processing timing of the chip to control the interrupt. If after the interrupt, the
|
|
MCU has not written all the information to be sent in the next frame to the chip, the next
|
|
time slot cannot be Configured to send time slots. This interrupt is only valid when the chip
|
|
is operating in Layer 2 mode.
|
|
|
|
Bit3: indicates that a Fragment that needs to be fed back confirms the completion of the data
|
|
packet transmission. The interrupt is mainly applied to the acknowledgment message after
|
|
all the data packets have been sent or the data packet that needs to be fed back in the sliding
|
|
window data service is sent to the MCU to start waiting for the timing of the Response
|
|
packet. Device.
|
|
|
|
Bit2 : ShortLC Receive Interrupt
|
|
|
|
Bit1: BS activation timeout interrupt
|
|
|
|
In MSK mode, there is no substate interrupt.
|
|
*/
|
|
|
|
if (reg_0x86 & 0x08) // confirmed-data feedback packet was received
|
|
{
|
|
smsTxNotifyHwAck();
|
|
}
|
|
}
|
|
|
|
static inline void hrc6000SysPostAccessInt(void)
|
|
{
|
|
/*
|
|
In DMR mode, the access interrupt has no sub-status register.
|
|
After receiving the interrupt, it indicates that the access voice communication mode is post-access. the way.
|
|
|
|
In MSK mode, this interrupt has no substatus registers.
|
|
*/
|
|
// Late entry into ongoing RX
|
|
if ((slotState == DMR_STATE_IDLE) && hrc.ccHold && hrc6000CheckColourCodeFilter())
|
|
{
|
|
codecInit(false);
|
|
LedWrite(LED_GREEN, 1);
|
|
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x50); //Receive only in next timeslot
|
|
slotState = DMR_STATE_RX_1;
|
|
hrc.hasEncodedAudio = false;
|
|
hrc.receivedFramesCount = -1;
|
|
hrc.insertSilenceFrame = false;
|
|
hrc.tsAgreed = 0;
|
|
hrc.tsDisagreed = 0;
|
|
hrc.tsLockedTS = -1;
|
|
hrc.qsoDataSeqCount = 0;
|
|
hrc.hasAudioData = false;
|
|
hrc.qsoDataTimeout = 0;
|
|
hrc.skipOneTS = false;
|
|
memset((uint8_t *)hrc.previousLCBuf, 0x00, LC_DATA_LENGTH); // Ensure the LC data will be send to the UI.
|
|
lastHeardClearLastID();// Tell the LastHeard system that this is a new start
|
|
|
|
hrc.skipCount = 2;// RC. seems to be something to do with late entry but I'm but sure what, or whether its still needed
|
|
|
|
if (settingsUsbMode == USB_MODE_HOTSPOT)
|
|
{
|
|
DMR_frame_buffer[AMBE_AUDIO_LENGTH + LC_DATA_LENGTH] = HOTSPOT_RX_START_LATE;
|
|
hrc.hotspotDMRRxFrameBufferAvailable = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline void hrc6000SysReceivedDataInt(void)
|
|
{
|
|
/*
|
|
In DMR mode, this interrupt has no sub-status register, but the error and receive type of its received
|
|
data is given by the 0x51 register. The DLLRecvDataType, DLLRecvCRC are used to indicate the
|
|
received data type and the error status, and the MCU accordingly performs the corresponding status.
|
|
|
|
Display, you can also block the corresponding interrupt.
|
|
In MSK mode, this interrupt has no substate interrupts.
|
|
|
|
The FMB frame's EMB information parsing completion prompt is also the completion of the
|
|
interrupt, which is distinguished by judging the 0x51 register SyncClass=0.
|
|
*/
|
|
int rxDataType;
|
|
int rxSyncClass;
|
|
int rxPrivacyIndicator;
|
|
int rxSyncType;
|
|
|
|
|
|
if (SPI0ReadPageRegByte(0x04, 0x51, ®_0x51) != kStatus_Success)
|
|
{
|
|
hrc.rxCRCisValid = false;
|
|
return;
|
|
}
|
|
|
|
//read_SPI_page_reg_byte_SPI0(0x04, 0x57, ®_0x57);// Kai said that the official firmware uses this register instead of 0x52 for the timecode
|
|
|
|
rxDataType = (reg_0x51 >> 4) & 0x0F;//Data Type or Voice Frame sequence number
|
|
rxSyncClass = (reg_0x51 >> 0) & 0x03;//Received Sync Class 0=Sync Header 1=Voice 2=data 3=RC
|
|
hrc.rxCRCisValid = (((reg_0x51 >> 2) & 0x01) == 0);// CRC is OK if its 0
|
|
rxPrivacyIndicator = (reg_0x51 >> 3) & 0x01;
|
|
|
|
if (SPI0ReadPageRegByte(0x04, 0x5f, ®_0x5F) != kStatus_Success)
|
|
{
|
|
return;
|
|
}
|
|
|
|
rxSyncType = (reg_0x5F & 0x03); //received Sync Type
|
|
|
|
if (codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_FORCE_DMO) == 0)
|
|
{
|
|
if (rxSyncType == BS_SYNC) // if we are receiving from a base station (Repeater)
|
|
{
|
|
currentRadioDevice->trxDMRModeRx = DMR_MODE_RMO; // switch to RMO mode to allow reception
|
|
}
|
|
else
|
|
{
|
|
currentRadioDevice->trxDMRModeRx = DMR_MODE_DMO; // not base station so must be DMO
|
|
}
|
|
}
|
|
|
|
if (((slotState == DMR_STATE_RX_1) || (slotState == DMR_STATE_RX_2)) &&
|
|
((rxPrivacyIndicator != 0) || (hrc6000CrcIsValid() == false) || (hrc6000CheckColourCodeFilter() == false)))
|
|
{
|
|
// Something is not correct
|
|
return;
|
|
}
|
|
|
|
hrc.tickCount = 0;
|
|
|
|
// Wait for the repeater to wakeup and count the number of frames where the Timecode (TS number) is toggling correctly
|
|
if (slotState == DMR_STATE_REPEATER_WAKE_3)
|
|
{
|
|
if (hrc.lastTimeCode != hrc.timeCode)
|
|
{
|
|
hrc.rxTSToggled++;// timecode has toggled correctly
|
|
}
|
|
else
|
|
{
|
|
hrc.rxTSToggled = 0;// timecode has not toggled correctly so reset the counter used in the TS state machine
|
|
}
|
|
}
|
|
|
|
// Note only detect terminator frames in Active mode, because in passive we can see our own terminators echoed back which can be a problem
|
|
|
|
if (hrc6000CrcIsValid() && (rxSyncClass == SYNC_CLASS_DATA) && (rxDataType == 2)) //Terminator with LC
|
|
{
|
|
if ((currentRadioDevice->trxDMRModeRx == DMR_MODE_DMO) && hrc6000CallAcceptFilter())
|
|
{
|
|
slotState = DMR_STATE_RX_END;
|
|
trxIsTransmitting = false;
|
|
|
|
if (settingsUsbMode == USB_MODE_HOTSPOT)
|
|
{
|
|
DMR_frame_buffer[AMBE_AUDIO_LENGTH + LC_DATA_LENGTH] = HOTSPOT_RX_STOP;
|
|
hrc.hotspotDMRRxFrameBufferAvailable = true;
|
|
}
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
audioAmpDisable(AUDIO_AMP_CHANNEL_RF);
|
|
if ((voicePromptsIsPlaying() == false) && (soundMelodyIsPlaying() == false))
|
|
{
|
|
soundTerminateSound(); //stop any i2s sound transfers
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hrc6000CrcIsValid() && (slotState != DMR_STATE_IDLE) && (hrc.skipCount > 0) && (rxSyncClass != SYNC_CLASS_DATA) && ((rxDataType & 0x07) == 0x01))
|
|
{
|
|
hrc.skipCount--;
|
|
}
|
|
|
|
// DMR short data (SMS): data header, rate-1/2 and rate-3/4 data bursts.
|
|
// The chip has already BPTC/FEC decoded the burst; the logical payload
|
|
// is read from the LC RAM (rate-3/4 blocks carry 18 bytes, everything
|
|
// else 12).
|
|
if (hrc6000CrcIsValid() && (rxPrivacyIndicator == 0) && (rxSyncClass == SYNC_CLASS_DATA) &&
|
|
((rxDataType == DMR_DATA_TYPE_DATA_HEADER) || (rxDataType == DMR_DATA_TYPE_RATE_12_DATA) || (rxDataType == DMR_DATA_TYPE_RATE_34_DATA)) &&
|
|
(settingsUsbMode != USB_MODE_HOTSPOT) && (hrc.transmissionEnabled == false) &&
|
|
hrc6000CheckColourCodeFilter())
|
|
{
|
|
uint8_t smsFrame[DMR_DATA_RATE34_BLOCK_LENGTH];
|
|
uint8_t burstLength = ((rxDataType == DMR_DATA_TYPE_RATE_34_DATA) ? DMR_DATA_RATE34_BLOCK_LENGTH : LC_DATA_LENGTH);
|
|
|
|
if (SPI0ReadPageRegByteArray(0x02, 0x00, smsFrame, burstLength) == kStatus_Success)
|
|
{
|
|
smsRxProcessDataFrame(rxDataType, smsFrame);
|
|
}
|
|
}
|
|
|
|
// Check for correct received packet
|
|
if (hrc6000CrcIsValid() && (rxPrivacyIndicator == 0) && (slotState < DMR_STATE_TX_START_1))
|
|
{
|
|
// Start RX
|
|
if (slotState == DMR_STATE_IDLE)
|
|
{
|
|
if (hrc6000CheckColourCodeFilter())// Voice LC Header
|
|
{
|
|
codecInit(false);
|
|
LedWrite(LED_GREEN, 1);
|
|
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x50); //Receive only in next timeslot
|
|
slotState = DMR_STATE_RX_1;
|
|
hrc.timeCode = -1;
|
|
hrc.hasEncodedAudio = false;
|
|
hrc.receivedFramesCount = -1;
|
|
hrc.insertSilenceFrame = false;
|
|
hrc.tsAgreed = 0;
|
|
hrc.tsDisagreed = 0;
|
|
hrc.tsLockedTS = -1;
|
|
hrc.qsoDataSeqCount = 0;
|
|
hrc.hasAudioData = false;
|
|
hrc.qsoDataTimeout = 0;
|
|
hrc.skipCount = 0;
|
|
lastHeardClearLastID();// Tell the LastHeard system that this is a new start
|
|
|
|
if (settingsUsbMode == USB_MODE_HOTSPOT)
|
|
{
|
|
DMR_frame_buffer[AMBE_AUDIO_LENGTH + LC_DATA_LENGTH] = HOTSPOT_RX_START;
|
|
hrc.hotspotDMRRxFrameBufferAvailable = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int sequenceNumber = (rxDataType & 0x07);
|
|
// Detect/decode voice packet and transfer it into the output soundbuffer
|
|
|
|
if (((hrc.skipCount == 0) || ((hrc.receivedSrcId != 0x00) && (hrc.receivedSrcId != trxDMRID))) &&
|
|
(rxSyncClass != SYNC_CLASS_DATA) && ((sequenceNumber >= 0x01) && (sequenceNumber <= 0x06)) &&
|
|
(slotState == DMR_STATE_RX_1) // We are only listening on DMR_STATE_RX_1, in both DMO and RMO
|
|
&& HRC6000CheckTalkGroupFilter() && hrc6000CheckColourCodeFilter())
|
|
{
|
|
bool ccLocked = (((nonVolatileSettings.dmrCcTsFilter & DMR_CC_FILTER_PATTERN) == 0) ? (hrc.lastRxColorCodeCount == CC_PROBE_LOCKED) : true);
|
|
|
|
// We just got a valid audio, while in Monitor Mode, hence, we need to
|
|
// (re)send the QSO info
|
|
if (monitorModeData.isEnabled)
|
|
{
|
|
if (monitorModeData.triggered && ccLocked)
|
|
{
|
|
if (monitorModeData.dmrIsValid == false)
|
|
{
|
|
monitorModeData.triggered = false;
|
|
monitorModeData.qsoInfoUpdated = false;
|
|
updateLastHeard = false;
|
|
monitorModeData.dmrIsValid = true;
|
|
// Hack: Skip the two first frames
|
|
// The HR-C6000 is returning, on the first iteration, the LC data from the previous
|
|
// transmission, hence the displayed QSO info are messed up on the screen at the beginning
|
|
// of the decoding.
|
|
// Skipping the first two frames circumvent the problem (but delay a bit the QSO info displaying).
|
|
monitorModeData.dmrFrameSkip = 2;
|
|
hrc.receivedFramesCount = -1;
|
|
hrc.insertSilenceFrame = false;
|
|
hrc.tsAgreed = 0;
|
|
hrc.tsDisagreed = 0;
|
|
hrc.tsLockedTS = -1;
|
|
hrc.qsoDataSeqCount = 0;
|
|
hrc.hasAudioData = false;
|
|
hrc.qsoDataTimeout = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Mostly, we are only set the CC as hold if the CC filter is OFF, as the locking is handled
|
|
// in hrc6000SysInterruptHandler(), except when the monitor mode is enabled.
|
|
if (ccLocked || (monitorModeData.isEnabled && monitorModeData.dmrIsValid))
|
|
{
|
|
hrc.ccHold = true; //don't allow CC to change if we are receiving audio
|
|
}
|
|
|
|
if((settingsUsbMode != USB_MODE_HOTSPOT) && ((audioAmpGetStatus() & AUDIO_AMP_CHANNEL_RF) == 0) &&
|
|
(((currentRadioDevice->trxDMRModeRx == DMR_MODE_RMO) || (currentRadioDevice->trxDMRModeRx == DMR_MODE_DMO)) && (hrc.tsAgreed > TS_STABLE_THRESHOLD)))
|
|
{
|
|
audioAmpEnable(AUDIO_AMP_CHANNEL_RF);
|
|
}
|
|
|
|
if (sequenceNumber == 1)
|
|
{
|
|
if ((monitorModeData.isEnabled == false) ||
|
|
(monitorModeData.isEnabled && monitorModeData.dmrIsValid && (monitorModeData.dmrFrameSkip == 0)))
|
|
{
|
|
// Once the TS is locked, we're waiting for QSODATA_THRESHOLD_COUNT frame's
|
|
// sequence #1 before sending LC data to the UI. This avoid QSO_DISPLAY_CALLER_DATA retrigger,
|
|
// mostly at the end of a QSO run.
|
|
if ((hrc.tsAgreed == TS_IS_LOCKED) && (hrc.qsoDataSeqCount <= QSODATA_THRESHOLD_COUNT) &&
|
|
((uiDataGlobal.Scan.active == false) || (uiDataGlobal.Scan.active && (uiDataGlobal.Scan.state == SCAN_STATE_PAUSED))))
|
|
{
|
|
hrc.qsoDataSeqCount++;
|
|
}
|
|
|
|
hrc6000TriggerPrivateCallQSODataDisplay(); // it also reset the qsoDataTimeout value.
|
|
}
|
|
|
|
if (monitorModeData.isEnabled && monitorModeData.dmrIsValid && monitorModeData.dmrFrameSkip)
|
|
{
|
|
monitorModeData.dmrFrameSkip--;
|
|
|
|
if (monitorModeData.dmrFrameSkip == 0) // All frames are skipped, clear the last heard now.
|
|
{
|
|
HRC6000ClearActiveDMRID();
|
|
lastHeardClearLastID();
|
|
monitorModeData.qsoInfoUpdated = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
SPI1ReadPageRegByteArray(0x03, 0x00, DMR_frame_buffer + LC_DATA_LENGTH, AMBE_AUDIO_LENGTH);
|
|
|
|
if (settingsUsbMode == USB_MODE_HOTSPOT)
|
|
{
|
|
DMR_frame_buffer[AMBE_AUDIO_LENGTH + LC_DATA_LENGTH] = HOTSPOT_RX_AUDIO_FRAME;
|
|
DMR_frame_buffer[AMBE_AUDIO_LENGTH + LC_DATA_LENGTH + 1] = (rxDataType & 0x07);// audio sequence number
|
|
hrc.hotspotDMRRxFrameBufferAvailable = true;
|
|
}
|
|
else
|
|
{
|
|
if (hrc.tsAgreed == TS_IS_LOCKED)
|
|
{
|
|
if (monitorModeData.isEnabled == false)
|
|
{
|
|
// We need to count the number of received audio frames, as when a dropout happens,
|
|
// that put the decoded audio buffering in a low state, triggering stuttering audio.
|
|
// Hence, if we didn't get 5 frames between two sequence #1, a silence frame is
|
|
// inserted to the buffering system, which permits to the real decoded audio to refill/recover.
|
|
if (((hrc.hasAudioData == false) && (sequenceNumber == 1)) ||
|
|
(hrc.hasAudioData && (sequenceNumber == 1) && (hrc.receivedFramesCount == -1)))
|
|
{
|
|
hrc.receivedFramesCount = 0;
|
|
}
|
|
else if (hrc.receivedFramesCount >= 0)
|
|
{
|
|
if (sequenceNumber == 1)
|
|
{
|
|
if ((hrc.receivedFramesCount >= 0) && (hrc.receivedFramesCount != (SUPERFRAME_NUM_FRAMES - 1)))
|
|
{
|
|
// Insert SILENCE frame
|
|
hrc.insertSilenceFrame = true;
|
|
}
|
|
|
|
hrc.receivedFramesCount = 0;
|
|
}
|
|
else
|
|
{
|
|
hrc.receivedFramesCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tell foreground that there is audio to encode
|
|
// But not until we get a TS lock, there is no
|
|
// need to fill the audio buffer with garbage, hence
|
|
// over-filling could happen, triggering stuttering audio
|
|
hrc.hasEncodedAudio = true;
|
|
hrc.hasAudioData = true;
|
|
|
|
if (currentRadioDevice->trxDMRModeRx == DMR_MODE_RMO)
|
|
{
|
|
hrc.dmrMonitorCapturedTimeout = hrc6000GetTSTimeoutValue(); // Keep resetting the TS timeout
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hrc.transmissionEnabled == false) // ignore the LC data when we are transmitting
|
|
{
|
|
hrc6000HandleLCData();
|
|
}
|
|
|
|
if (hrc.timeCode != -1)
|
|
{
|
|
hrc.lastTimeCode = hrc.timeCode;
|
|
}
|
|
}
|
|
|
|
static inline void hrc6000SysReceivedInformationInt(void)
|
|
{
|
|
SPI0ReadPageRegByte(0x04, 0x90, ®_0x90);
|
|
|
|
/*
|
|
In DMR mode, this interrupt has sub-status register 0x90, which has three types:
|
|
1. 0x80 indicates that the entire information is received and verified. After the
|
|
service data is verified, the MCU extracts the data after the address 0x30 in
|
|
the RX terminal 1.2KRAM through the SPI port. The length of the data is defined by
|
|
the corresponding field of the received frame header.
|
|
2. 0x00 indicates the entire information reception check error;
|
|
3. 0x40 indicates that a non-confirmed SMS abnormal interrupt is generated;
|
|
*/
|
|
}
|
|
|
|
static inline void hrc6000SysAbnormalExitInt(void)
|
|
{
|
|
SPI0ReadPageRegByte(0x04, 0x98, ®_0x98);
|
|
|
|
/*
|
|
In DMR mode, the cause of the abnormality in DMR mode is the unexpected abnormal voice
|
|
interrupt generated inside the state machine. The corresponding voice exception type is obtained
|
|
through Bit2~Bit0 of register address 0x98.
|
|
*/
|
|
|
|
if (hrc.ccHold && hrc6000CheckColourCodeFilter() && (hrc.transmissionEnabled == false) && (hrc.tsAgreed == TS_IS_LOCKED))
|
|
{
|
|
if ((settingsUsbMode != USB_MODE_HOTSPOT) && ((reg_0x98 >> (hrc.tsLockedTS + 1)) & 0x01)) // Check if it did happened on the TS we're locked on.
|
|
{
|
|
hrc.hasAbnormalExit = true; // The next audio frame will be skipped.
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline void hrc6000SysPhysicalLayerInt(void)
|
|
{
|
|
}
|
|
#ifdef CPU_MK22FN512VLL12
|
|
static inline
|
|
#endif
|
|
void hrc6000SysInterruptHandler(void)
|
|
{
|
|
uint8_t reg0x52;
|
|
bool reg82Result = (SPI0ReadPageRegByte(0x04, 0x82, ®_0x82) == kStatus_Success); // Read Interrupt Flag Register1
|
|
bool reg52Result = (SPI0ReadPageRegByte(0x04, 0x52, ®0x52) == kStatus_Success); // Read Received CC and CACH
|
|
|
|
if (reg52Result)
|
|
{
|
|
hrc.rxColorCode = (reg0x52 >> 4) & 0x0F;
|
|
}
|
|
|
|
if (hrc.transmissionEnabled == false)
|
|
{
|
|
if ((hrc.ccHold == false) && ((nonVolatileSettings.dmrCcTsFilter & DMR_CC_FILTER_PATTERN) == 0) && reg52Result)
|
|
{
|
|
if (trxGetSNRMargindBm(RADIO_DEVICE_PRIMARY) >= 5) // Minimum SNR Margin
|
|
{
|
|
uint32_t timeDiff = (ticksGetMillis() - hrc.lastRxColorCodeTime);
|
|
|
|
// Too much time has passed since last turn, reset hrc.lastRxColorCodeCount.
|
|
// Also, if a CC detection failed (no ccHold), we have to reset the counter to
|
|
// restart the whole detection process.
|
|
if (timeDiff > 200)
|
|
{
|
|
hrc.lastRxColorCode = 0xFF;
|
|
}
|
|
|
|
if(hrc.rxColorCode == hrc.lastRxColorCode)
|
|
{
|
|
if (hrc.lastRxColorCodeCount < CC_PROBE_MAX_COUNT)
|
|
{
|
|
if (hrc.lastRxColorCodeCount % 2) // Slow down calling trxSetDMRColourCode() otherwise the FW will crash
|
|
{
|
|
trxSetDMRColourCode(hrc.rxColorCode);
|
|
SPI0WritePageRegByte(0x04, 0x40, 0xC3); // Enable DMR Tx, DMR Rx, Passive Timing, Normal mode
|
|
|
|
if (currentRadioDevice->trxDMRModeTx == DMR_MODE_RMO) // we need to do extra config while in RMO, otherwise the chip will get stuck on a wrong CC
|
|
{
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x20); // Set Sync Fail Bit (Reset?))
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x00); // Reset
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x20); // Set Sync Fail Bit (Reset?)
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x50); // Receive during next Timeslot
|
|
}
|
|
|
|
// Give the HR-C6000 a bit of time.
|
|
uint32_t m = ticksGetMillis();
|
|
while ((ticksGetMillis() - m) < 3);
|
|
}
|
|
|
|
hrc.lastRxColorCodeCount++;
|
|
}
|
|
else
|
|
{
|
|
hrc.ccHold = true;
|
|
headerRowIsDirty = true; // force the UI to display the correct CC
|
|
hrc.lastRxColorCodeCount = CC_PROBE_LOCKED;
|
|
hrc.ccHoldTimer = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hrc.lastRxColorCodeCount = 0;
|
|
}
|
|
|
|
hrc.lastRxColorCode = hrc.rxColorCode;
|
|
hrc.lastRxColorCodeTime = ticksGetMillis();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hrc.ccHold = true; // prevent CC change when transmitting.
|
|
hrc.lastRxColorCodeCount = 0;
|
|
}
|
|
|
|
if (reg82Result)
|
|
{
|
|
/*
|
|
Bit7:
|
|
In DMR mode: indicates that the transmission request rejects the interrupt without a sub-status register.
|
|
In DMR mode, it indicates that this transmission request is rejected because the channel is busy;
|
|
*/
|
|
if (reg_0x82 & SYS_INT_SEND_REQUEST_REJECTED)
|
|
{
|
|
hrc6000SysSendRejectedInt();
|
|
}
|
|
|
|
/*
|
|
Bit6:
|
|
In DMR mode: indicates the start of transmission.
|
|
In MSK mode: indicates that the ping-pong buffer is half-full interrupted.
|
|
In DMR mode, the sub-status register 0x84 is transmitted at the beginning, and the corresponding interrupt can be masked by 0x85.
|
|
*/
|
|
if (reg_0x82 & SYS_INT_SEND_START)
|
|
{
|
|
hrc6000SysSendStartInt();
|
|
}
|
|
else
|
|
{
|
|
reg_0x84 = 0x00;
|
|
}
|
|
|
|
/*
|
|
Bit5:
|
|
In DMR mode: indicates the end of transmission.
|
|
In MSK mode: indicates the end of transmission.
|
|
*/
|
|
if (reg_0x82 & SYS_INT_SEND_END) // Kai's comment was InterSendStop interrupt
|
|
{
|
|
hrc6000SysSendEndInt();
|
|
}
|
|
else
|
|
{
|
|
reg_0x86 = 0x00;
|
|
}
|
|
|
|
/*
|
|
Bit4:
|
|
In DMR mode: indicates the access interruption;
|
|
In MSK mode: indicates that the response response is interrupted.
|
|
*/
|
|
if (reg_0x82 & SYS_INT_POST_ACCESS)
|
|
{
|
|
hrc6000SysPostAccessInt();
|
|
}
|
|
|
|
/*
|
|
Bit3:
|
|
In DMR mode: indicates that the control frame parsing completion interrupt;
|
|
In MSK mode: indicates the receive interrupt.
|
|
*/
|
|
if (reg_0x82 & SYS_INT_RECEIVED_DATA)
|
|
{
|
|
hrc6000SysReceivedDataInt();
|
|
}
|
|
|
|
/*
|
|
Bit2:
|
|
In DMR mode: indicates service data reception interrupt.
|
|
In FM mode: indicates FM function detection interrupt.
|
|
*/
|
|
if (reg_0x82 & SYS_INT_RECEIVED_INFORMATION)
|
|
{
|
|
hrc6000SysReceivedInformationInt();
|
|
}
|
|
else
|
|
{
|
|
reg_0x90 = 0x00;
|
|
}
|
|
|
|
/*
|
|
Bit1:
|
|
In DMR mode: indicates that the voice is abnormally exited;
|
|
In DMR mode, the cause of the abnormality in DMR mode is the unexpected abnormal voice
|
|
interrupt generated inside the state machine. The corresponding voice exception type is obtained
|
|
through Bit2~Bit0 of register address 0x98.
|
|
*/
|
|
if (reg_0x82 & SYS_INT_ABNORMAL_EXIT)
|
|
{
|
|
hrc6000SysAbnormalExitInt();
|
|
}
|
|
else
|
|
{
|
|
reg_0x98 = 0x00;
|
|
}
|
|
|
|
/*
|
|
Bit0:
|
|
physical layer separate work reception interrupt
|
|
The physical layer works independently to receive interrupts without a sub-status register. The
|
|
interrupt is generated in the physical layer single working mode. After receiving the data, the
|
|
interrupt is generated, and the MCU is notified to read the corresponding register to obtain the
|
|
received data. This interrupt is typically tested in bit error rate or other performance in physical
|
|
layer mode.
|
|
*/
|
|
if (reg_0x82 & SYS_INT_PHYSICAL_LAYER)
|
|
{
|
|
hrc6000SysPhysicalLayerInt();
|
|
}
|
|
}
|
|
|
|
SPI0WritePageRegByte(0x04, 0x83, reg_0x82); // Clear all Interrupt flags set for this run
|
|
}
|
|
|
|
static void hrc6000TransitionToTx(void)
|
|
{
|
|
audioAmpDisable(AUDIO_AMP_CHANNEL_RF);
|
|
LedWrite(LED_GREEN, 0);
|
|
|
|
if (settingsUsbMode != USB_MODE_HOTSPOT)
|
|
{
|
|
codecInit(false);
|
|
}
|
|
|
|
SPI0WritePageRegByte(0x04, 0x21, 0xA2); // Set Polite to Color Code and Reset vocoder encodingbuffer
|
|
SPI0WritePageRegByte(0x04, 0x22, 0x86); // Start Vocoder Encode, I2S mode
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x00); // Do nothing on the next TS
|
|
|
|
slotState = DMR_STATE_TX_START_1;
|
|
hrc.txSequence = 0;
|
|
}
|
|
|
|
// Queue the next SMS burst for the upcoming timeslot: 12-byte logical
|
|
// payload into the LC RAM, its DMR data type into register 0x50. The chip
|
|
// performs the BPTC/FEC encoding. Once the last frame is queued the
|
|
// transmission is ended the same way as a released PTT.
|
|
static void hrc6000SendSmsFrame(void)
|
|
{
|
|
const smsAirJob_t *job = hrc.smsJob;
|
|
uint8_t dataType;
|
|
|
|
if ((job == NULL) || (hrc.smsFrameIndex >= job->frameCount))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// register 0x50 value: high nibble = DMR data type, low bits per datasheet
|
|
if (hrc.smsFrameIndex < job->preambleCount)
|
|
{
|
|
dataType = 0x30; // CSBK
|
|
}
|
|
else if (hrc.smsFrameIndex == job->preambleCount)
|
|
{
|
|
dataType = 0x64; // data header
|
|
}
|
|
else
|
|
{
|
|
dataType = 0x70; // rate-1/2 data
|
|
}
|
|
|
|
SPI0WritePageRegByteArray(0x02, 0x00, (uint8_t *)job->frames[hrc.smsFrameIndex], LC_DATA_LENGTH);
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x80); // Transmit during next Timeslot
|
|
SPI0WritePageRegByte(0x04, 0x50, dataType);
|
|
|
|
hrc.smsFrameIndex++;
|
|
|
|
if (hrc.smsFrameIndex >= job->frameCount)
|
|
{
|
|
// all bursts are queued, terminate (the terminator frame is sent by the TX_END path)
|
|
hrc.transmissionEnabled = false;
|
|
trxTransmissionEnabled = false;
|
|
}
|
|
}
|
|
|
|
//used as a delayed callback to allow time for the final Tx burst and then simulate the final Rx Interrupt and return to receive.
|
|
void hrc6000TxBurstCallback(void)
|
|
{
|
|
trxIsTransmittingDMR = false;
|
|
|
|
hrc6000SetInIRQHandler(true);
|
|
hrc6000RxInterruptHandler();
|
|
hrc6000SetInIRQHandler(false);
|
|
}
|
|
|
|
void hrc6000TimeslotInterruptHandler(void)
|
|
{
|
|
//this check needs to be immediately at the start of the ISR to try to keep the Tx Burst length as close as possible to 30ms.
|
|
if((trxIsTransmittingDMR) && ( ticksGetMillis() - trxDMRstartTime > 20 )) // The MDUV380 doesn't have the hardware for the Rx Interrupt so we turn off the tx from here instead if it has been on for at least 20ms
|
|
{
|
|
hrc6000RxInterruptHandler();
|
|
}
|
|
|
|
uint8_t reg0x52;
|
|
bool reg52Result = (SPI0ReadPageRegByte(0x04, 0x52, ®0x52) == kStatus_Success); // Read CACH Register to get the timecode (TS number)
|
|
|
|
if (reg52Result == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// This only happen in RMO, when TS filter is OFF, and a locked TS became silent for dmrMonitorCapturedTimeout long,
|
|
// but the slotState is still toggling between DMR_STATE_RX_1 and DMR_STATE_RX_2 (receiving signal).
|
|
// In this case, we're switching to the other TS.
|
|
if (hrc.skipOneTS)
|
|
{
|
|
hrc.skipOneTS = false;
|
|
hrc.timeCode = !hrc.timeCode;
|
|
hrc.lastTimeCode = !hrc.lastTimeCode;
|
|
hrc.tsLockedTS = !hrc.tsLockedTS;
|
|
hrc.qsoDataSeqCount = 0;
|
|
hrc.receivedFramesCount = -1;
|
|
hrc.insertSilenceFrame = false;
|
|
hrc.hasAudioData = false;
|
|
hrc.qsoDataTimeout = 0;
|
|
dmrMonitorCapturedTS = !dmrMonitorCapturedTS;
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x00); // No Transmit or receive in next timeslot
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
|
|
HRC6000ClearActiveDMRID();
|
|
lastHeardClearLastID();
|
|
|
|
if (settingsUsbMode != USB_MODE_HOTSPOT)
|
|
{
|
|
codecInit(false);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// NOTE:
|
|
// When RXing, one of the timeslot is not listened once the TS lock status is
|
|
// acquired, hence the CACH register is not updated by the HR-C6000.
|
|
// The following variable is only usable while tsAgreed <= 4
|
|
int receivedTimeCode = ((reg0x52 & 0x04) >> 2); // extract the timecode from the CACH register
|
|
|
|
if ((slotState == DMR_STATE_REPEATER_WAKE_3) || (hrc.timeCode == -1)) // if we are waking up the repeater, or we don't currently have a valid value for the timecode
|
|
{
|
|
hrc.tsAgreed = 0;
|
|
hrc.tsDisagreed = 0;
|
|
hrc.timeCode = receivedTimeCode; // use the received TC directly from the CACH
|
|
hrc.qsoDataSeqCount = 0;
|
|
hrc.hasAudioData = false;
|
|
hrc.receivedFramesCount = -1;
|
|
hrc.insertSilenceFrame = false;
|
|
hrc.qsoDataTimeout = 0;
|
|
}
|
|
else
|
|
{
|
|
hrc.timeCode = !hrc.timeCode; // toggle the timecode.
|
|
|
|
// We're entering this part of the code everytime, except when a TS lock has been acquired while RXing
|
|
if (((((slotState == DMR_STATE_RX_1) || (slotState == DMR_STATE_RX_2)) && (hrc.tsAgreed > TS_STABLE_THRESHOLD)) || (hrc.tsAgreed == TS_IS_LOCKED)) == false)
|
|
{
|
|
if (hrc.timeCode == receivedTimeCode) // if this agrees with the received version
|
|
{
|
|
if (hrc.tsAgreed < (TS_STABLE_THRESHOLD + 1))
|
|
{
|
|
hrc.tsAgreed++;
|
|
}
|
|
|
|
if (hrc.tsDisagreed > 0) // decrement the disagree count
|
|
{
|
|
hrc.tsDisagreed--;
|
|
}
|
|
}
|
|
else // if there is a disagree it might be just a glitch so ignore it a couple of times.
|
|
{
|
|
hrc.tsDisagreed++; // count the number of disagrees.
|
|
|
|
if (hrc.tsAgreed > 0)
|
|
{
|
|
hrc.tsAgreed--;
|
|
}
|
|
|
|
if (hrc.tsDisagreed > TS_DISAGREE_THRESHOLD) // if we have had four disagrees then re-sync.
|
|
{
|
|
hrc.timeCode = receivedTimeCode;
|
|
hrc.tsDisagreed = 0;
|
|
hrc.tsAgreed = 0;
|
|
hrc.receivedFramesCount = -1;
|
|
hrc.insertSilenceFrame = false;
|
|
hrc.hasEncodedAudio = false;
|
|
hrc.receivedFramesCount = -1;
|
|
hrc.insertSilenceFrame = false;
|
|
hrc.qsoDataSeqCount = 0;
|
|
hrc.hasAudioData = false;
|
|
hrc.qsoDataTimeout = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// RX/TX state machine
|
|
#if defined(USING_EXTERNAL_DEBUGGER) && defined(DEBUG_DMR)
|
|
SEGGER_RTT_printf(0, "state:%d\n",slotState);
|
|
#endif
|
|
|
|
switch (slotState)
|
|
{
|
|
case DMR_STATE_RX_1: // Start RX (first step)
|
|
if (currentRadioDevice->trxDMRModeRx == DMR_MODE_RMO)
|
|
{
|
|
bool tsMatches = hrc6000CheckTimeSlotFilter();
|
|
|
|
if(hrc.transmissionEnabled)
|
|
{
|
|
if (((hrc.isWaking == WAKING_MODE_NONE) || (hrc.isWaking == WAKING_MODE_AWAKEN)) && (hrc.tsDisagreed == 0) && (hrc.tsAgreed > TS_STABLE_THRESHOLD))
|
|
{
|
|
// Opposite TS is currently active, start transmitting on the next one, otherwise wait for the next call
|
|
if (tsMatches == false)
|
|
{
|
|
hrc.tsLockedTS = !hrc.timeCode;
|
|
|
|
hrc.tsAgreed = TS_IS_LOCKED; // Lock the TS
|
|
hrc.isWaking = WAKING_MODE_AWAKEN;
|
|
|
|
hrc6000TransitionToTx();
|
|
hrc.ambeBufferCount = 0;
|
|
hrc.deferredUpdateBufferOutPtr = deferredUpdateBuffer;
|
|
hrc.deferredUpdateBufferInPtr = deferredUpdateBuffer;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if (LedRead(LED_GREEN) == 0)
|
|
{
|
|
LedWrite(LED_GREEN, 1);
|
|
}
|
|
|
|
// TS lock acquired
|
|
if ((hrc.tsAgreed == (TS_STABLE_THRESHOLD + 1)) && !tsMatches)
|
|
{
|
|
hrc.tsLockedTS = !hrc.timeCode;
|
|
hrc.tsAgreed = TS_IS_LOCKED; // Lock the TS
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// When in Active (simplex) mode. We need to only receive on one of the 2 timeslots, otherwise we get random data for the other slot
|
|
// and this can sometimes be interpreted as valid data, which then screws things up.
|
|
// TS lock acquired
|
|
if (hrc.tsLockedTS == -1)
|
|
{
|
|
hrc.tsLockedTS = hrc.timeCode; // Timecode is meaningless here
|
|
hrc.tsAgreed = TS_IS_LOCKED; // Lock the TS
|
|
}
|
|
|
|
if (hrc.transmissionEnabled)
|
|
{
|
|
hrc6000TransitionToTx();
|
|
hrc.ambeBufferCount = 0;
|
|
hrc.deferredUpdateBufferOutPtr = deferredUpdateBuffer;
|
|
hrc.deferredUpdateBufferInPtr = deferredUpdateBuffer;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Once the TS lock is acquired start listening one TS out of two
|
|
if (hrc.tsAgreed == TS_IS_LOCKED)
|
|
{
|
|
if ((hrc.transmissionEnabled == false) && ((uiDataGlobal.rxBeepState & RX_BEEP_CARRIER_HAS_STARTED) == 0))
|
|
{
|
|
uiDataGlobal.rxBeepState |= (RX_BEEP_CARRIER_HAS_STARTED | RX_BEEP_CARRIER_HAS_STARTED_EXEC);
|
|
uiDataGlobal.rxBeepState &= ~(RX_BEEP_TALKER_IDENTIFIED | RX_BEEP_TALKER_HAS_ENDED_EXEC);
|
|
}
|
|
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x00); // No Transmit or receive in next timeslot
|
|
slotState = DMR_STATE_RX_2;
|
|
}
|
|
|
|
if (!trxDMRSynchronisedRSSIReadPending)
|
|
{
|
|
ticksTimerStart((ticksTimer_t *)&readDMRRSSITimer, (180 + 15)); // wait 3 * 60ms complete frames + 15 ticks (of approximately 15mS) before reading the RSSI, in the middle of a TS
|
|
trxDMRSynchronisedRSSIReadPending = true;
|
|
}
|
|
break;
|
|
|
|
case DMR_STATE_RX_2: // Start RX (second step)
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x50); // Receive only in next timeslot
|
|
slotState = DMR_STATE_RX_1;
|
|
break;
|
|
|
|
case DMR_STATE_RX_END: // Stop RX
|
|
HRC6000ClearActiveDMRID();
|
|
HRC6000InitDigitalDmrRx();
|
|
audioAmpDisable(AUDIO_AMP_CHANNEL_RF);
|
|
if ((voicePromptsIsPlaying() == false) && (soundMelodyIsPlaying() == false))
|
|
{
|
|
soundTerminateSound(); //stop any i2s sound transfers
|
|
}
|
|
|
|
LedWrite(LED_GREEN, 0);
|
|
|
|
if (uiDataGlobal.rxBeepState & RX_BEEP_CARRIER_HAS_STARTED)
|
|
{
|
|
uiDataGlobal.rxBeepState |= RX_BEEP_CARRIER_HAS_ENDED;
|
|
|
|
// In DMO (HT to HT), otherwise Talker end beep won't be played.
|
|
// The beep will be played first, followed 100ms later with carrier ending beep
|
|
if ((uiDataGlobal.rxBeepState & RX_BEEP_TALKER_HAS_STARTED) && ((uiDataGlobal.rxBeepState & RX_BEEP_TALKER_HAS_ENDED) == 0))
|
|
{
|
|
uiDataGlobal.rxBeepState |= (RX_BEEP_TALKER_HAS_ENDED | RX_BEEP_TALKER_HAS_ENDED_EXEC);
|
|
}
|
|
}
|
|
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
slotState = DMR_STATE_IDLE;
|
|
hrc.tsLockedTS = -1;
|
|
trxIsTransmitting = false;
|
|
break;
|
|
|
|
case DMR_STATE_TX_START_1: // Start TX (second step)
|
|
LedWrite(LED_RED, 1); // for repeater wakeup
|
|
if (hrc.smsActive)
|
|
{
|
|
hrc6000SendSmsFrame();
|
|
}
|
|
else
|
|
{
|
|
hrc6000SendPcOrTgLCHeader();
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x80); // Transmit during next Timeslot
|
|
SPI0WritePageRegByte(0x04, 0x50, 0x10); // Set Data Type to 0001 (Voice LC Header), Data, LCSS=00
|
|
}
|
|
trxIsTransmitting = true;
|
|
slotState = DMR_STATE_TX_START_2;
|
|
break;
|
|
|
|
case DMR_STATE_TX_START_2: // Start TX (third step)
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x00); // Do nothing on the next TS
|
|
slotState = DMR_STATE_TX_START_3;
|
|
break;
|
|
|
|
case DMR_STATE_TX_START_3: // Start TX (fourth step)
|
|
if (hrc.smsActive)
|
|
{
|
|
hrc6000SendSmsFrame();
|
|
}
|
|
else
|
|
{
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x80); // Transmit during Next Timeslot
|
|
SPI0WritePageRegByte(0x04, 0x50, 0x10); // Set Data Type to 0001 (Voice LC Header), Data, LCSS=00
|
|
}
|
|
slotState = DMR_STATE_TX_START_4;
|
|
break;
|
|
|
|
case DMR_STATE_TX_START_4: // Start TX (fifth step)
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x00); // Do nothing on the next TS
|
|
slotState = DMR_STATE_TX_START_5;
|
|
|
|
if ((settingsUsbMode != USB_MODE_HOTSPOT) && (hrc.smsActive == false))
|
|
{
|
|
hrc.ambeBufferCount = 0;
|
|
hrc.deferredUpdateBufferOutPtr = deferredUpdateBuffer;
|
|
hrc.deferredUpdateBufferInPtr = deferredUpdateBuffer;
|
|
soundReceiveData();
|
|
}
|
|
break;
|
|
|
|
case DMR_STATE_TX_START_5: // Start TX (sixth step)
|
|
if (hrc.smsActive)
|
|
{
|
|
hrc6000SendSmsFrame();
|
|
}
|
|
else
|
|
{
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x80); // Transmit during next Timeslot
|
|
SPI0WritePageRegByte(0x04, 0x50, 0x10); // Set Data Type to 0001 (Voice LC Header), Data, LCSS=00
|
|
}
|
|
hrc.TAPhase = 0;
|
|
slotState = DMR_STATE_TX_1;
|
|
break;
|
|
|
|
case DMR_STATE_TX_1: // Ongoing TX (inactive timeslot)
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x00);
|
|
if ((hrc.transmissionEnabled == false) && (hrc.txSequence == 0))
|
|
{
|
|
slotState = DMR_STATE_TX_END_1; // only exit here to ensure staying in the correct timeslot
|
|
}
|
|
else
|
|
{
|
|
slotState = DMR_STATE_TX_2;
|
|
}
|
|
break;
|
|
|
|
case DMR_STATE_TX_2: // Ongoing TX (active timeslot)
|
|
if (hrc.smsActive)
|
|
{
|
|
// One data burst per active timeslot; no audio involved, and
|
|
// txSequence stays at 0 so DMR_STATE_TX_1 can terminate cleanly
|
|
hrc6000SendSmsFrame();
|
|
slotState = DMR_STATE_TX_1;
|
|
break;
|
|
}
|
|
|
|
if (hrc.transmissionEnabled)
|
|
{
|
|
if (settingsUsbMode == USB_MODE_HOTSPOT)
|
|
{
|
|
if (hrc.txSequence == 0)
|
|
{
|
|
SPI0WritePageRegByteArray(0x02, 0x00, (uint8_t*)deferredUpdateBuffer, LC_DATA_LENGTH); // put LC into hardware
|
|
}
|
|
|
|
if (hrc.hotspotDMRTxFrameBufferEmpty == false)
|
|
{
|
|
SPI1WritePageRegByteArray(0x03, 0x00, (uint8_t*)(deferredUpdateBuffer + LC_DATA_LENGTH), AMBE_AUDIO_LENGTH); // send the audio bytes to the hardware
|
|
hrc.hotspotDMRTxFrameBufferEmpty = true; // we have finished with the current frame data from the hotspot
|
|
}
|
|
else
|
|
{
|
|
SPI1WritePageRegByteArray(0x03, 0x00, SILENCE_AUDIO, AMBE_AUDIO_LENGTH); // send the audio bytes to the hardware
|
|
}
|
|
|
|
if (hrc.hotspotPostponedFrameHandling > 0) // Send frames of silence until it's equal to zero
|
|
{
|
|
hrc.hotspotPostponedFrameHandling--;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if((hrc.txSequence == 0) && (getCurrentTATxFlag() != TA_TX_OFF))
|
|
{
|
|
hrc6000TransmitTalkerAlias();
|
|
}
|
|
|
|
if (hrc.ambeBufferCount >= NUM_AMBE_BLOCK_PER_DMR_FRAME)
|
|
{
|
|
SPI1WritePageRegByteArray(0x03, 0x00, (uint8_t*)hrc.deferredUpdateBufferOutPtr, AMBE_AUDIO_LENGTH);// send the audio bytes to the hardware
|
|
hrc.deferredUpdateBufferOutPtr += AMBE_AUDIO_LENGTH;
|
|
|
|
if (hrc.deferredUpdateBufferOutPtr > DEFERRED_UPDATE_BUFFER_END)
|
|
{
|
|
hrc.deferredUpdateBufferOutPtr = deferredUpdateBuffer;
|
|
}
|
|
hrc.ambeBufferCount -= NUM_AMBE_BLOCK_PER_DMR_FRAME;
|
|
}
|
|
else
|
|
{
|
|
SPI1WritePageRegByteArray(0x03, 0x00, SILENCE_AUDIO, AMBE_AUDIO_LENGTH); // send the audio bytes to the hardware
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SPI1WritePageRegByteArray(0x03, 0x00, SILENCE_AUDIO, AMBE_AUDIO_LENGTH); // send the audio bytes to the hardware
|
|
}
|
|
|
|
//write_SPI_page_reg_bytearray_SPI1(0x03, 0x00, (uint8_t*)(DMR_frame_buffer + LC_DATA_LENGTH), AMBE_AUDIO_LENGTH);// send the audio bytes to the hardware
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x80); // Transmit during next Timeslot
|
|
SPI0WritePageRegByte(0x04, 0x50, 0x08 + (hrc.txSequence << 4)); // Data Type= sequence number 0 - 5 (Voice Frame A) , Voice, LCSS = 0
|
|
|
|
hrc.txSequence = ((hrc.txSequence + 1) % SUPERFRAME_NUM_FRAMES); // 0 .. 5
|
|
|
|
slotState = DMR_STATE_TX_1;
|
|
break;
|
|
|
|
case DMR_STATE_TX_END_1: // Stop TX (first step)
|
|
// After an SMS the LC RAM still holds the last data block, so a
|
|
// valid LC has to be rewritten for the terminator frame
|
|
if (hrc.smsActive || (getCurrentTATxFlag() != TA_TX_OFF))
|
|
{
|
|
hrc6000SendPcOrTgLCHeader();
|
|
}
|
|
SPI1WritePageRegByteArray(0x03, 0x00, SILENCE_AUDIO, AMBE_AUDIO_LENGTH); // send silence audio bytes
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x80); // Transmit during Next Timeslot
|
|
SPI0WritePageRegByte(0x04, 0x50, 0x20); // Data Type =0010 (Terminator with LC), Data, LCSS=0
|
|
slotState = DMR_STATE_TX_END_2;
|
|
break;
|
|
|
|
case DMR_STATE_TX_END_2: // Stop TX (second step)
|
|
if (hrc.smsActive)
|
|
{
|
|
hrc.smsActive = false;
|
|
hrc.smsJob = NULL;
|
|
LedWrite(LED_RED, 0); // no TX screen is handling the LED for SMS
|
|
smsTxNotifyAirDone();
|
|
}
|
|
// Need to hold on this TS after Tx ends otherwise if DMR Mon TS filtering is disabled the radio may switch timeslot
|
|
dmrMonitorCapturedTS = hrc.tsLockedTS = trxGetDMRTimeSlot();
|
|
hrc.dmrMonitorCapturedTimeout = nonVolatileSettings.dmrCaptureTimeout * 1000;
|
|
hrc.keepMonitorCapturedTSAfterTxing = true;
|
|
hrc.tickCount = 0;
|
|
#ifdef THREE_STATE_SHUTDOWN
|
|
slotState = DMR_STATE_TX_END_3_RMO;
|
|
#else
|
|
|
|
if (currentRadioDevice->trxDMRModeTx == DMR_MODE_RMO)
|
|
{
|
|
SPI0WritePageRegByte(0x04, 0x40, 0xC3); // Enable DMR Tx and Rx, Passive Timing
|
|
//SPI0WritePageRegByte(0x04, 0x41, 0x50); // Receive during Next Timeslot And Layer2 Access success Bit
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x00); // No Transmit or receive in next timeslot
|
|
slotState = DMR_STATE_TX_END_3_RMO;
|
|
addTimerCallback(hrc6000TxBurstCallback, 29, MENU_ANY, false); //allow time for the last burst to be sent then return to receive
|
|
}
|
|
else
|
|
{
|
|
addTimerCallback(hrc6000TxBurstCallback, 29, MENU_ANY, false); //allow time for the last burst to be sent then return to receive
|
|
HRC6000InitDigitalDmrRx();
|
|
slotState = DMR_STATE_TX_END_3_DMO;
|
|
}
|
|
#endif
|
|
break;
|
|
|
|
case DMR_STATE_TX_END_3_DMO:
|
|
slotState = DMR_STATE_IDLE;
|
|
trxIsTransmitting = false;
|
|
hrc.timeCode = -1;
|
|
hrc.tsAgreed = 0;
|
|
hrc.hasEncodedAudio = false;
|
|
hrc.receivedFramesCount = -1;
|
|
hrc.insertSilenceFrame = false;
|
|
hrc.tsDisagreed = 0;
|
|
hrc.lastTimeCode = -2;
|
|
dmrMonitorCapturedTS = -1;
|
|
hrc.keepMonitorCapturedTSAfterTxing = false;
|
|
hrc.dmrMonitorCapturedTimeout = hrc6000GetTSTimeoutValue();
|
|
break;
|
|
|
|
case DMR_STATE_TX_END_3_RMO:
|
|
LedWrite(LED_GREEN, 1);
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x50); // Receive during Next Timeslot And Layer2 Access success Bit
|
|
slotState = DMR_STATE_RX_1;
|
|
hrc.isWaking = WAKING_MODE_NONE;
|
|
trxIsTransmitting = false;
|
|
break;
|
|
|
|
case DMR_STATE_REPEATER_WAKE_1:
|
|
{
|
|
LedWrite(LED_RED, 1); // Turn on the Red LED while when we transmit the wakeup frame
|
|
uint8_t spi_tx1[LC_DATA_LENGTH] = { 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
|
spi_tx1[7] = (trxDMRID >> 16) & 0xFF;
|
|
spi_tx1[8] = (trxDMRID >> 8) & 0xFF;
|
|
spi_tx1[9] = (trxDMRID >> 0) & 0xFF;
|
|
SPI0WritePageRegByteArray(0x02, 0x00, spi_tx1, LC_DATA_LENGTH);
|
|
SPI0WritePageRegByte(0x04, 0x50, 0x30);
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x80);
|
|
trxIsTransmitting = true;
|
|
}
|
|
hrc.repeaterWakeupResponseTimeout = WAKEUP_RETRY_PERIOD;
|
|
slotState = DMR_STATE_REPEATER_WAKE_2;
|
|
break;
|
|
|
|
case DMR_STATE_REPEATER_WAKE_2:
|
|
addTimerCallback(hrc6000TxBurstCallback, 29, MENU_ANY, false); //allow time for the wakeup burst to be sent then return to receive
|
|
LedWrite(LED_RED, 0); // Turn off the Red LED while we are waiting for the repeater to wakeup
|
|
HRC6000InitDigitalDmrRx();
|
|
hrc.rxTSToggled = 0; // Start to count the TS toggling times while waking up the repeater.
|
|
slotState = DMR_STATE_REPEATER_WAKE_3;
|
|
break;
|
|
|
|
case DMR_STATE_REPEATER_WAKE_3:
|
|
if (hrc.rxTSToggled > 3)
|
|
{
|
|
// wait for the signal from the repeater to have toggled timecode at least three times, i.e the signal should be stable and we should be able to go into Tx
|
|
slotState = DMR_STATE_RX_1;
|
|
trxIsTransmitting = false;
|
|
hrc.isWaking = WAKING_MODE_AWAKEN;
|
|
hrc.tsLockedTS = -1;
|
|
hrc.qsoDataSeqCount = 0;
|
|
hrc.hasAudioData = false;
|
|
hrc.receivedFramesCount = -1;
|
|
hrc.insertSilenceFrame = false;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
|
|
|
|
// Timeout interrupted RX
|
|
// In RMO, we don't check for the LC Terminator (because of echo), so this timeout handles
|
|
// the end of reception, as it resets tickCount to zero when receiving data (hrc6000SysReceivedDataInt())
|
|
if (slotState < DMR_STATE_TX_START_1)
|
|
{
|
|
hrc.tickCount++;
|
|
if (((slotState == DMR_STATE_IDLE) && (hrc.tickCount > START_TICK_TIMEOUT)) ||
|
|
(((slotState == DMR_STATE_RX_1) || (slotState == DMR_STATE_RX_2)) && (hrc.tickCount > END_TICK_TIMEOUT)))
|
|
{
|
|
HRC6000InitDigitalDmrRx();
|
|
HRC6000ClearActiveDMRID();
|
|
audioAmpDisable(AUDIO_AMP_CHANNEL_RF);
|
|
if ((voicePromptsIsPlaying() == false) && (soundMelodyIsPlaying() == false))
|
|
{
|
|
soundTerminateSound(); //stop any i2s sound transfers
|
|
}
|
|
|
|
LedWrite(LED_GREEN, 0);
|
|
|
|
if (uiDataGlobal.rxBeepState & RX_BEEP_CARRIER_HAS_STARTED)
|
|
{
|
|
uiDataGlobal.rxBeepState |= RX_BEEP_CARRIER_HAS_ENDED;
|
|
|
|
// In DMO (HT to HT), otherwise Talker end beep won't be played.
|
|
// The beep will be played first, followed 100ms later with carrier ending beep
|
|
if ((uiDataGlobal.rxBeepState & RX_BEEP_TALKER_HAS_STARTED) && ((uiDataGlobal.rxBeepState & RX_BEEP_TALKER_HAS_ENDED) == 0))
|
|
{
|
|
uiDataGlobal.rxBeepState |= (RX_BEEP_TALKER_HAS_ENDED | RX_BEEP_TALKER_HAS_ENDED_EXEC);
|
|
}
|
|
}
|
|
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
slotState = DMR_STATE_IDLE;
|
|
trxIsTransmitting = false;
|
|
hrc.tickCount = 0;
|
|
hrc.tsLockedTS = -1;
|
|
hrc.keepMonitorCapturedTSAfterTxing = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
//the MDUV380 does not have the hardware for the Rx Interrupt This handler is retained for compatibility and is called from the timeslot interrupt handler.
|
|
static inline void hrc6000RxInterruptHandler(void)
|
|
{
|
|
if(trxIsTransmittingDMR) //if we are currently sending DMR then just turn off the RF
|
|
{
|
|
trxFastDMRTx(false);
|
|
}
|
|
else
|
|
{
|
|
trxActivateRx(false); // Else activate full Rx
|
|
}
|
|
|
|
}
|
|
|
|
void hrc6000TxInterruptHandler(void)
|
|
{
|
|
if(trxIsTransmittingDMR)
|
|
{
|
|
trxFastDMRTx(true); //already transmitting so just use the fast Tx method.
|
|
}
|
|
else
|
|
{
|
|
trxActivateDMRTx(); //Not currently transmitting so do a full Tx activation.
|
|
}
|
|
trxDMRstartTime = ticksGetMillis(); // save the time the Tx was started so it can be stopped by the timeslot interrupt.
|
|
}
|
|
|
|
void hrc6000SetInIRQHandler(bool in)
|
|
{
|
|
if (in)
|
|
{
|
|
hrc.inIRQHandler = true;
|
|
}
|
|
else
|
|
{
|
|
hrc.interruptTimeout = 0;
|
|
hrc.inIRQHandler = false;
|
|
}
|
|
}
|
|
|
|
static void hrc6000InitDigitalState(void)
|
|
{
|
|
hrc.interruptTimeout = 0;
|
|
slotState = DMR_STATE_IDLE;
|
|
trxIsTransmitting = false;
|
|
hrc.smsActive = false;
|
|
hrc.smsJob = NULL;
|
|
hrc.smsFrameIndex = 0;
|
|
hrc.tickCount = 0;
|
|
hrc.skipCount = 0;
|
|
hrc.tsAgreed = 0;
|
|
hrc.hasEncodedAudio = false;
|
|
hrc.receivedFramesCount = -1;
|
|
hrc.insertSilenceFrame = false;
|
|
hrc.tsDisagreed = 0;
|
|
hrc.tsLockedTS = -1;
|
|
hrc.qsoDataSeqCount = 0;
|
|
hrc.hasAudioData = false;
|
|
hrc.qsoDataTimeout = 0;
|
|
hrc.skipOneTS = false;
|
|
hrc.lastRxColorCode = 0xFF;
|
|
hrc.lastRxColorCodeCount = 0;
|
|
monitorModeData.dmrIsValid = false;
|
|
}
|
|
|
|
void HRC6000InitInterrupts(void)
|
|
{
|
|
hrc6000InitDigitalState();
|
|
|
|
interruptsInitC6000Interface();
|
|
}
|
|
|
|
void HRC6000InitDigitalDmrRx(void)
|
|
{
|
|
HRC6000SetDMR(); // ensure any registers changed by FM use are restored to DMR settings
|
|
|
|
SPI0WritePageRegByte(0x04, 0x40, 0xC3); // Enable DMR Tx, DMR Rx, Passive Timing, Normal mode
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x20); // Set Sync Fail Bit (Reset?))
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x00); // Reset
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x20); // Set Sync Fail Bit (Reset?)
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x50); // Receive during next Timeslot
|
|
|
|
hrc.hasEncodedAudio = false;
|
|
hrc.receivedFramesCount = -1;
|
|
hrc.insertSilenceFrame = false;
|
|
hrc.tsAgreed = 0; // Restart TS detection
|
|
hrc.tsDisagreed = 0;
|
|
hrc.qsoDataSeqCount = 0;
|
|
hrc.hasAudioData = false;
|
|
hrc.qsoDataTimeout = 0;
|
|
hrc.lastRxColorCode = 0xFF;
|
|
hrc.lastRxColorCodeCount = 0;
|
|
}
|
|
|
|
void HRC6000ResetTimeSlotDetection(void)
|
|
{
|
|
if (hrc.keepMonitorCapturedTSAfterTxing == false)
|
|
{
|
|
audioAmpDisable(AUDIO_AMP_CHANNEL_RF);
|
|
if ((voicePromptsIsPlaying() == false) && (soundMelodyIsPlaying() == false))
|
|
{
|
|
soundTerminateSound(); //stop any i2s sound transfers
|
|
}
|
|
|
|
LedWrite(LED_GREEN, 0);
|
|
|
|
hrc.timeCode = -1; // Clear current timecode synchronisation
|
|
hrc.tsLockedTS = -1;
|
|
hrc.hasEncodedAudio = false;
|
|
hrc.receivedFramesCount = -1;
|
|
hrc.insertSilenceFrame = false;
|
|
hrc.tickCount = 0;
|
|
hrc.tsAgreed = 0;
|
|
hrc.tsDisagreed = 0;
|
|
hrc.lastTimeCode = -2;
|
|
dmrMonitorCapturedTS = -1;
|
|
hrc.dmrMonitorCapturedTimeout = hrc6000GetTSTimeoutValue();
|
|
hrc.qsoDataSeqCount = 0;
|
|
hrc.hasAudioData = false;
|
|
hrc.qsoDataTimeout = 0;
|
|
hrc.skipOneTS = false;
|
|
hrc.lastRxColorCode = 0xFF;
|
|
hrc.lastRxColorCodeCount = 0;
|
|
uiDataGlobal.rxBeepState = RX_BEEP_UNSET;
|
|
}
|
|
}
|
|
|
|
void HRC6000InitDigital(void)
|
|
{
|
|
// Partial timeslot detection reset
|
|
audioAmpDisable(AUDIO_AMP_CHANNEL_RF);
|
|
if ((voicePromptsIsPlaying() == false) && (soundMelodyIsPlaying() == false))
|
|
{
|
|
soundTerminateSound(); //stop any i2s sound transfers
|
|
}
|
|
|
|
LedWrite(LED_GREEN, 0);
|
|
|
|
hrc.timeCode = -1; // Clear current timecode synchronisation
|
|
hrc.tickCount = 0;
|
|
hrc.tsAgreed = 0;
|
|
hrc.tsDisagreed = 0;
|
|
hrc.lastTimeCode = -2;
|
|
dmrMonitorCapturedTS = -1;
|
|
hrc.qsoDataSeqCount = 0;
|
|
hrc.hasAudioData = false;
|
|
hrc.receivedFramesCount = -1;
|
|
hrc.insertSilenceFrame = false;
|
|
hrc.qsoDataTimeout = 0;
|
|
hrc.dmrMonitorCapturedTimeout = hrc6000GetTSTimeoutValue();
|
|
hrc.keepMonitorCapturedTSAfterTxing = false;
|
|
hrc.skipOneTS = false;
|
|
|
|
HRC6000InitDigitalDmrRx();
|
|
hrc6000InitDigitalState();
|
|
|
|
interruptsEnableC6000Interrupts();// NVIC_EnableIRQ(PORTC_IRQn);
|
|
|
|
codecInit(false);
|
|
}
|
|
|
|
void HRC6000TerminateDigital(void)
|
|
{
|
|
audioAmpDisable(AUDIO_AMP_CHANNEL_RF);
|
|
if ((voicePromptsIsPlaying() == false) && (soundMelodyIsPlaying() == false))
|
|
{
|
|
soundTerminateSound(); //stop any i2s sound transfers
|
|
}
|
|
LedWrite(LED_GREEN, 0);
|
|
hrc6000InitDigitalState();
|
|
|
|
//vk3kyy this does not seem to be needed NVIC_DisableIRQ(PORTC_IRQn);
|
|
}
|
|
|
|
static void hrc6000TriggerPrivateCallQSODataDisplay(void)
|
|
{
|
|
// If the display is holding on the PC accept text and the incoming call is not a PC
|
|
if ((hrc.qsoDataSeqCount == QSODATA_THRESHOLD_COUNT) &&
|
|
(uiDataGlobal.PrivateCall.state == PRIVATE_CALL_ACCEPT) && (DMR_frame_buffer[0] == TG_CALL_FLAG))
|
|
{
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_CALLER_DATA;
|
|
}
|
|
|
|
hrc.qsoDataTimeout = QSODATA_TIMER_TIMEOUT;
|
|
}
|
|
|
|
static void hrc6000SendPcOrTgLCHeader(void)
|
|
{
|
|
uint8_t spi_tx[LC_DATA_LENGTH];
|
|
|
|
spi_tx[0] = (trxTalkGroupOrPcId >> 24) & 0xFF;
|
|
spi_tx[1] = 0x00;
|
|
spi_tx[2] = 0x00;
|
|
spi_tx[3] = (trxTalkGroupOrPcId >> 16) & 0xFF;
|
|
spi_tx[4] = (trxTalkGroupOrPcId >> 8) & 0xFF;
|
|
spi_tx[5] = (trxTalkGroupOrPcId >> 0) & 0xFF;
|
|
spi_tx[6] = (trxDMRID >> 16) & 0xFF;
|
|
spi_tx[7] = (trxDMRID >> 8) & 0xFF;
|
|
spi_tx[8] = (trxDMRID >> 0) & 0xFF;
|
|
spi_tx[9] = 0x00;
|
|
spi_tx[10] = 0x00;
|
|
spi_tx[11] = 0x00;
|
|
SPI0WritePageRegByteArray(0x02, 0x00, spi_tx, LC_DATA_LENGTH);
|
|
}
|
|
|
|
static bool hrc6000CallAcceptFilter(void)
|
|
{
|
|
if (settingsUsbMode == USB_MODE_HOTSPOT)
|
|
{
|
|
//In Hotspot mode, we need to accept all incoming traffic, otherwise private calls won't work
|
|
if ((DMR_frame_buffer[0] == TG_CALL_FLAG) || (DMR_frame_buffer[0] == PC_CALL_FLAG) ||
|
|
((DMR_frame_buffer[0] >= 0x04) && (DMR_frame_buffer[0] <= 0x08))) // we maybe received TA / GPS
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false; // Not a PC or TG call
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return ((DMR_frame_buffer[0] == TG_CALL_FLAG) || (hrc.receivedTgOrPcId == (trxDMRID | (PC_CALL_FLAG << 24))));
|
|
}
|
|
}
|
|
|
|
static void hrc6000ManageCCHoldState(void)
|
|
{
|
|
uint32_t m = ticksGetMillis();
|
|
|
|
if ((m - hrc.ccHoldReleaseTickTime) >= 1)
|
|
{
|
|
hrc.ccHoldReleaseTickTime = m;
|
|
|
|
if ((nonVolatileSettings.dmrCcTsFilter & DMR_CC_FILTER_PATTERN) && (hrc.ccHold == false))
|
|
{
|
|
hrc.ccHold = true;
|
|
}
|
|
else if((hrc.transmissionEnabled == false) && ((nonVolatileSettings.dmrCcTsFilter & DMR_CC_FILTER_PATTERN) == 0) && hrc.ccHold)
|
|
{
|
|
if (slotState == DMR_STATE_IDLE)
|
|
{
|
|
if ((audioAmpGetStatus() & AUDIO_AMP_CHANNEL_RF) == 0)
|
|
{
|
|
hrc.ccHoldTimer++;
|
|
}
|
|
else
|
|
{
|
|
hrc.ccHoldTimer -= ((hrc.ccHoldTimer > 0) ? 1 : 0);
|
|
}
|
|
|
|
if (hrc.ccHoldTimer >= CC_HOLD_TIME)
|
|
{
|
|
hrc.ccHold = false;
|
|
hrc.ccHoldTimer = 0;
|
|
hrc.lastRxColorCode = 0xFF;
|
|
hrc.lastRxColorCodeCount = 0;
|
|
monitorModeData.dmrIsValid = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hrc.ccHoldTimer = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void hrc6000Tick(void)
|
|
{
|
|
hrc6000ManageCCHoldState();
|
|
|
|
if (hrc.transmissionEnabled && (hrc.isWaking == WAKING_MODE_NONE))
|
|
{
|
|
// DMO: Start transmitting
|
|
// RMO: Waking up the repeater if it's not already listening to it.
|
|
if (slotState == DMR_STATE_IDLE)
|
|
{
|
|
// Because the ISR's also write to the SPI we need to disable interrupts on Port C when doing any SPI transfers.
|
|
// Otherwise there could be clashes in the SPI subsystem.
|
|
// This is possibly not the ideal solution, and a better solution may be found at a later date
|
|
// But at least it should prevent things going too badly wrong
|
|
|
|
interruptsDisableC6000Interrupts();// NVIC_DisableIRQ(PORTC_IRQn);
|
|
|
|
// Ensure the ISR has exited
|
|
while (hrc.inIRQHandler);
|
|
|
|
if (currentRadioDevice->trxDMRModeTx == DMR_MODE_DMO)
|
|
{
|
|
if (settingsUsbMode != USB_MODE_HOTSPOT)
|
|
{
|
|
codecInit(false);
|
|
}
|
|
else
|
|
{
|
|
hrc.hotspotPostponedFrameHandling = (HS_NUM_OF_SILENCE_SEQ_ON_STARTUP * 6);
|
|
// LC and Frame data will be uplodaded in hrc6000TimeslotInterruptHandler(), DMR_STATE_TX_2 case.
|
|
memcpy((uint8_t *)deferredUpdateBuffer, (uint8_t *)&audioAndHotspotDataBuffer.hotspotBuffer[wavbuffer_read_idx], AMBE_AUDIO_LENGTH + LC_DATA_LENGTH);
|
|
// Note:
|
|
// We don't increment the buffer indexes, because this is also the first frame of audio and we need
|
|
// it later, and LC data are needed for the silent frames
|
|
hrc.hotspotDMRTxFrameBufferEmpty = true; // We will send silence at the beginning of the transmission
|
|
}
|
|
|
|
slotState = DMR_STATE_TX_START_1;
|
|
}
|
|
else
|
|
{
|
|
if (settingsUsbMode != USB_MODE_HOTSPOT)
|
|
{
|
|
codecInit(false);
|
|
}
|
|
|
|
hrc.isWaking = WAKING_MODE_WAITING;
|
|
hrc.wakeTriesCount = 0;
|
|
slotState = DMR_STATE_REPEATER_WAKE_1;
|
|
}
|
|
|
|
hrc.txSequence = 0;
|
|
hrc.ambeBufferCount = 0;
|
|
hrc.deferredUpdateBufferOutPtr = deferredUpdateBuffer;
|
|
hrc.deferredUpdateBufferInPtr = deferredUpdateBuffer;
|
|
|
|
hrc.timeCode = -1; // Clear current timecode synchronisation
|
|
hrc.tsAgreed = 0;
|
|
hrc.tsDisagreed = 0;
|
|
hrc.hasEncodedAudio = false;
|
|
hrc.receivedFramesCount = -1;
|
|
hrc.insertSilenceFrame = false;
|
|
hrc.lastTimeCode = -2;
|
|
dmrMonitorCapturedTS = -1;
|
|
hrc.dmrMonitorCapturedTimeout = TS_SYNC_STARTUP_TIMEOUT;
|
|
hrc.keepMonitorCapturedTSAfterTxing = false;
|
|
hrc.qsoDataSeqCount = 0;
|
|
hrc.hasAudioData = false;
|
|
hrc.qsoDataTimeout = 0;
|
|
hrc.skipOneTS = false;
|
|
|
|
SPI0WritePageRegByte(0x04, 0x21, 0xA2); // Set Polite to Color Code and Reset vocoder encodingbuffer
|
|
SPI0WritePageRegByte(0x04, 0x22, 0x86); // Start Vocoder Encode, I2S mode
|
|
|
|
interruptsEnableC6000Interrupts();// NVIC_EnableIRQ(PORTC_IRQn);
|
|
|
|
SPI0WritePageRegByte(0x04, 0x40, 0xE3); // TX and RX enable, Active Timing.
|
|
hrc.interruptTimeout = 0;
|
|
}
|
|
else
|
|
{
|
|
// Note. In Tier 2 Passive (Repeater operation). The radio will already be receiving DMR frames from the repeater
|
|
// And the transition from Rx to Tx is handled in the Timeslot ISR state machine
|
|
}
|
|
}
|
|
|
|
// Timeout interrupt
|
|
// This code appears to check whether there has been a TS ISR in the last 200 RTOS ticks
|
|
// If not, it reinitialises the DMR subsystem
|
|
// -> As long the ISR is called, interruptTimeout is set to 0. This permits to
|
|
// detect when the DMR subsystem has stopped running (no DMR signal).
|
|
if (slotState != DMR_STATE_IDLE)
|
|
{
|
|
if (hrc.interruptTimeout < INTERRUPT_TIMEOUT)
|
|
{
|
|
hrc.interruptTimeout++;
|
|
|
|
if (hrc.interruptTimeout == INTERRUPT_TIMEOUT)
|
|
{
|
|
HRC6000InitDigital();// sets interruptTimeout=0;
|
|
HRC6000ClearActiveDMRID();
|
|
if (uiDataGlobal.displayQSOState != QSO_DISPLAY_DEFAULT_SCREEN)
|
|
{
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
}
|
|
slotState = DMR_STATE_IDLE;
|
|
trxIsTransmitting = false;
|
|
hrc.tickCount = 0;
|
|
hrc.tsLockedTS = -1;
|
|
hrc.tsAgreed = 0;
|
|
hrc.tsDisagreed = 0;
|
|
hrc.hasEncodedAudio = false;
|
|
hrc.receivedFramesCount = -1;
|
|
hrc.insertSilenceFrame = false;
|
|
hrc.qsoDataSeqCount = 0;
|
|
hrc.hasAudioData = false;
|
|
hrc.qsoDataTimeout = 0;
|
|
hrc.skipOneTS = false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hrc.interruptTimeout = 0;
|
|
}
|
|
|
|
if (hrc.transmissionEnabled)
|
|
{
|
|
if (hrc.isWaking == WAKING_MODE_WAITING)
|
|
{
|
|
// The wakeup response is still expected...
|
|
if (hrc.repeaterWakeupResponseTimeout > 0)
|
|
{
|
|
hrc.repeaterWakeupResponseTimeout--;
|
|
}
|
|
else
|
|
{
|
|
// The wakeup response time has expired
|
|
// Retry, once again (limit is set into the codeplug)
|
|
hrc.wakeTriesCount++;
|
|
if (hrc.wakeTriesCount > codeplugGetRepeaterWakeAttempts())
|
|
{
|
|
hrc.isWaking = WAKING_MODE_FAILED;// signal that the Wake process has failed.
|
|
}
|
|
else
|
|
{
|
|
// Retry. Stop everything and restart.
|
|
|
|
interruptsDisableC6000Interrupts();//NVIC_DisableIRQ(PORTC_IRQn);
|
|
|
|
// Ensure the ISR has exited
|
|
while (hrc.inIRQHandler);
|
|
|
|
hrc.repeaterWakeupResponseTimeout = WAKEUP_RETRY_PERIOD;
|
|
slotState = DMR_STATE_REPEATER_WAKE_1;
|
|
hrc.timeCode = -1; // Clear current timecode synchronisation
|
|
hrc.tickCount = 0;
|
|
hrc.tsAgreed = 0;
|
|
hrc.tsDisagreed = 0;
|
|
hrc.hasEncodedAudio = false;
|
|
hrc.receivedFramesCount = -1;
|
|
hrc.insertSilenceFrame = false;
|
|
hrc.lastTimeCode = -2;
|
|
dmrMonitorCapturedTS = -1;
|
|
hrc.dmrMonitorCapturedTimeout = TS_SYNC_STARTUP_TIMEOUT;
|
|
hrc.keepMonitorCapturedTSAfterTxing = false;
|
|
hrc.qsoDataSeqCount = 0;
|
|
hrc.hasAudioData = false;
|
|
hrc.qsoDataTimeout = 0;
|
|
hrc.skipOneTS = false;
|
|
|
|
SPI0WritePageRegByte(0x04, 0x21, 0xA2); // Set Polite to Color Code and Reset vocoder encodingbuffer
|
|
SPI0WritePageRegByte(0x04, 0x22, 0x86); // Start Vocoder Encode, I2S mode
|
|
|
|
interruptsEnableC6000Interrupts();// NVIC_EnableIRQ(PORTC_IRQn);
|
|
|
|
SPI0WritePageRegByte(0x04, 0x40, 0xE3); // TX and RX enable, Active Timing.
|
|
hrc.interruptTimeout = 0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// normal operation. Not waking the repeater
|
|
if (settingsUsbMode == USB_MODE_HOTSPOT)
|
|
{
|
|
if ((hrc.hotspotPostponedFrameHandling == 0) && hrc.hotspotDMRTxFrameBufferEmpty && (wavbuffer_count > 0))
|
|
{
|
|
memcpy((uint8_t *)deferredUpdateBuffer, (uint8_t *)&audioAndHotspotDataBuffer.hotspotBuffer[wavbuffer_read_idx], AMBE_AUDIO_LENGTH + LC_DATA_LENGTH);
|
|
|
|
wavbuffer_read_idx = ((wavbuffer_read_idx + 1) % HOTSPOT_BUFFER_COUNT);
|
|
|
|
wavbuffer_count--;
|
|
hrc.hotspotDMRTxFrameBufferEmpty = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Once there are 2 buffers available they can be encoded into one AMBE block
|
|
// The will happen prior to the data being needed in the TS ISR, so that by the time tick_codec_encode encodes complete,
|
|
// the data is ready to be used in the TS ISR
|
|
if (wavbuffer_count >= 2)
|
|
{
|
|
codecEncodeBlock((uint8_t *)hrc.deferredUpdateBufferInPtr);
|
|
|
|
hrc.deferredUpdateBufferInPtr += LENGTH_AMBE_BLOCK;
|
|
|
|
if (hrc.deferredUpdateBufferInPtr > DEFERRED_UPDATE_BUFFER_END)
|
|
{
|
|
hrc.deferredUpdateBufferInPtr = deferredUpdateBuffer;
|
|
}
|
|
hrc.ambeBufferCount++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// receiving RF DMR
|
|
if (settingsUsbMode == USB_MODE_HOTSPOT)
|
|
{
|
|
if (hrc.hotspotDMRRxFrameBufferAvailable && hrc6000CrcIsValid() && hrc.ccHold && (hrc.tsAgreed > TS_STABLE_THRESHOLD) && hrc6000CheckColourCodeFilter())
|
|
{
|
|
hotspotRxFrameHandler((uint8_t *)DMR_frame_buffer);
|
|
hrc.hotspotDMRRxFrameBufferAvailable = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (monitorModeData.isEnabled && (monitorModeData.dmrTimeout > 0))
|
|
{
|
|
if (hrc.rxCRCisValid == false) // Here, we shouldn't use hrc6000CrcIsValid(), otherwise monitor mode will get stuck in DMR
|
|
{
|
|
monitorModeData.dmrTimeout--;
|
|
if (monitorModeData.dmrTimeout == 0)
|
|
{
|
|
monitorModeData.dmrIsValid = false;
|
|
// switch to analog
|
|
trxSetModeAndBandwidth(RADIO_MODE_ANALOG, true);
|
|
currentChannelData->sql = CODEPLUG_MIN_VARIABLE_SQUELCH;
|
|
trxSetRxCSS(RADIO_DEVICE_PRIMARY, CODEPLUG_CSS_TONE_NONE);
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
headerRowIsDirty = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//found DMR signal
|
|
monitorModeData.dmrTimeout = 0;
|
|
monitorModeData.qsoInfoUpdated = true;
|
|
monitorModeData.dmrIsValid = false;
|
|
if (monitorModeData.isEnabled)
|
|
{
|
|
monitorModeData.triggered = true;
|
|
}
|
|
HRC6000ClearActiveDMRID();
|
|
lastHeardClearLastID();
|
|
// Detect the correct TS
|
|
// Avoid to call HRC6000ResetTimeSlotDetection(), as it close the Audio amp and turn the LED off
|
|
hrc.timeCode = -1;
|
|
hrc.tsAgreed = 0;
|
|
hrc.tsDisagreed = 0;
|
|
hrc.hasEncodedAudio = false;
|
|
hrc.receivedFramesCount = -1;
|
|
hrc.insertSilenceFrame = false;
|
|
hrc.lastTimeCode = -2;
|
|
dmrMonitorCapturedTS = -1;
|
|
hrc.dmrMonitorCapturedTimeout = TS_SYNC_STARTUP_TIMEOUT;
|
|
hrc.keepMonitorCapturedTSAfterTxing = false;
|
|
hrc.qsoDataSeqCount = 0;
|
|
hrc.hasAudioData = false;
|
|
hrc.qsoDataTimeout = 0;
|
|
hrc.skipOneTS = false;
|
|
}
|
|
}
|
|
|
|
taskENTER_CRITICAL();
|
|
if (hrc.hasEncodedAudio || hrc.insertSilenceFrame)
|
|
{
|
|
// voice prompts take priority over incoming DMR audio
|
|
if ((voicePromptsIsPlaying() == false) && (soundMelodyIsPlaying() == false))
|
|
{
|
|
if ((WAV_BUFFER_COUNT - wavbuffer_count) < 3) // If we're running low on audio decoding storage
|
|
{
|
|
hrc.bufferLimitReachedCount = 6; // cancels decoding of the next 6 buffers.
|
|
}
|
|
|
|
if (hrc.bufferLimitReachedCount > 0)
|
|
{
|
|
hrc.bufferLimitReachedCount--;
|
|
}
|
|
else
|
|
{
|
|
codecDecode((uint8_t *)((hrc.hasAbnormalExit || hrc.insertSilenceFrame) ? SILENCE_AUDIO : (DMR_frame_buffer + LC_DATA_LENGTH)), 3);
|
|
}
|
|
}
|
|
|
|
// Will process the encoded audio on the next call, silence was inserted
|
|
if ((hrc.hasEncodedAudio && hrc.insertSilenceFrame) == false)
|
|
{
|
|
hrc.hasEncodedAudio = false;
|
|
}
|
|
|
|
hrc.insertSilenceFrame = false;
|
|
hrc.hasAbnormalExit = false; // Clear abnormal exit
|
|
}
|
|
soundTickRXBuffer();
|
|
taskEXIT_CRITICAL();
|
|
}
|
|
|
|
if (hrc.qsoDataTimeout > 0)
|
|
{
|
|
// Only timeout the QSO data display if not displaying the Private Call Accept Yes/No text
|
|
// if menuUtilityReceivedPcId is non zero the Private Call Accept text is being displayed
|
|
if (uiDataGlobal.PrivateCall.state != PRIVATE_CALL_ACCEPT)
|
|
{
|
|
hrc.qsoDataTimeout--;
|
|
if (hrc.qsoDataTimeout == QSODATA_RX_BEEP_TIMER_TIMEOUT)
|
|
{
|
|
if ((uiDataGlobal.rxBeepState & RX_BEEP_CARRIER_HAS_STARTED) && (uiDataGlobal.rxBeepState & RX_BEEP_TALKER_IDENTIFIED) &&
|
|
(uiDataGlobal.rxBeepState & RX_BEEP_TALKER_HAS_STARTED) && ((uiDataGlobal.rxBeepState & RX_BEEP_TALKER_HAS_ENDED) == 0))
|
|
{
|
|
uiDataGlobal.rxBeepState |= (RX_BEEP_TALKER_HAS_ENDED | RX_BEEP_TALKER_HAS_ENDED_EXEC);
|
|
}
|
|
}
|
|
|
|
if (hrc.qsoDataTimeout == 0)
|
|
{
|
|
hrc.qsoDataSeqCount = 0;
|
|
hrc.tickCount = 0;
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
memset((uint8_t *)hrc.previousLCBuf, 0x00, LC_DATA_LENGTH); // Some repeaters are accessed from analog, hence sending the same DMR ID for all everyone
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((currentRadioDevice->trxDMRModeRx == DMR_MODE_RMO) &&
|
|
(hrc.transmissionEnabled == false) && (dmrMonitorCapturedTS != -1) && (hrc.dmrMonitorCapturedTimeout > 0))
|
|
{
|
|
hrc.dmrMonitorCapturedTimeout--;
|
|
if (hrc.dmrMonitorCapturedTimeout == 0)
|
|
{
|
|
hrc.hasAudioData = false;
|
|
hrc.dmrMonitorCapturedTimeout = hrc6000GetTSTimeoutValue();
|
|
|
|
// We're still receiving something, maybe from the other TS, hence start to dance between both TS.
|
|
if (((nonVolatileSettings.dmrCcTsFilter & DMR_TS_FILTER_PATTERN) == 0) &&
|
|
((slotState == DMR_STATE_RX_1) || (slotState == DMR_STATE_RX_2)))
|
|
{
|
|
hrc.skipOneTS = true;
|
|
}
|
|
else
|
|
{
|
|
dmrMonitorCapturedTS = -1; // Reset the TS capture
|
|
hrc.keepMonitorCapturedTSAfterTxing = false;
|
|
hrc.tickCount = 0;
|
|
hrc.tsAgreed = 0;
|
|
hrc.tsDisagreed = 0;
|
|
hrc.hasEncodedAudio = false;
|
|
hrc.receivedFramesCount = -1;
|
|
hrc.insertSilenceFrame = false;
|
|
hrc.qsoDataSeqCount = 0;
|
|
hrc.qsoDataTimeout = 0;
|
|
if (monitorModeData.isEnabled) // Reset monitor mode startup, if enabled.
|
|
{
|
|
monitorModeData.triggered = true;
|
|
monitorModeData.dmrIsValid = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
hrc.rxCRCisValid = false;// Reset this
|
|
}
|
|
|
|
static void hrc6000TaskFunction(void *data)
|
|
{
|
|
while (1U)
|
|
{
|
|
hrc6000Task.AliveCount = TASK_FLAGGED_ALIVE;
|
|
|
|
// if (timer_hrc6000task == 0) G4EML don't use PIT counter method. Use vtaskDelay() instead
|
|
// {
|
|
|
|
// Update our atomic transmission state
|
|
hrc.transmissionEnabled = trxTransmissionEnabled;
|
|
|
|
// If DIGITAL mode is active, we must handle it ;-)
|
|
if (trxGetMode() == RADIO_MODE_DIGITAL)
|
|
{
|
|
hrc6000Tick();
|
|
}
|
|
|
|
// timer_hrc6000task = 1; // Reset ISR activity marker
|
|
// }
|
|
vTaskDelay((1U / portTICK_PERIOD_MS));
|
|
}
|
|
}
|
|
|
|
void HRC6000InitTask(void)
|
|
{
|
|
xTaskCreate(hrc6000TaskFunction, /* pointer to the task */
|
|
"hrc6000Task", /* task name for kernel awareness debugging */
|
|
5000L / sizeof(portSTACK_TYPE), /* task stack size */
|
|
NULL, /* optional task startup argument */
|
|
(UBaseType_t)osPriorityNormal, /* initial priority */
|
|
&hrc6000Task.Handle /* optional task handle to create */
|
|
);
|
|
|
|
hrc6000Task.Running = true;
|
|
hrc6000Task.AliveCount = TASK_FLAGGED_ALIVE;
|
|
}
|
|
|
|
// RC. I had to use accessor functions for the isWaking flag
|
|
// because the compiler seems to have problems with volatile vars as externs used by other parts of the firmware (the Tx Screen)
|
|
void HRC6000ClearIsWakingState(void)
|
|
{
|
|
hrc.isWaking = WAKING_MODE_NONE;
|
|
}
|
|
|
|
int HRC6000GetIsWakingState(void)
|
|
{
|
|
return hrc.isWaking;
|
|
}
|
|
|
|
void HRC6000ClearActiveDMRID(void)
|
|
{
|
|
memset((uint8_t *)hrc.previousLCBuf, 0x00, LC_DATA_LENGTH);
|
|
memset((uint8_t *)DMR_frame_buffer, 0x00, LC_DATA_LENGTH);
|
|
hrc.receivedTgOrPcId = 0x00;
|
|
hrc.receivedSrcId = 0x00;
|
|
}
|
|
|
|
void HRC6000ResyncTimeSlot(void)
|
|
{
|
|
if (hrc.transmissionEnabled == false)
|
|
{
|
|
if (hrc.keepMonitorCapturedTSAfterTxing == false)
|
|
{
|
|
// If we are in RMO mode, without TS filtering, and currently receiving, toggles the active TS.
|
|
if ((currentRadioDevice->trxDMRModeRx == DMR_MODE_RMO) && (dmrMonitorCapturedTS != -1) && (hrc.dmrMonitorCapturedTimeout > 0) &&
|
|
((nonVolatileSettings.dmrCcTsFilter & DMR_TS_FILTER_PATTERN) == 0) &&
|
|
((slotState == DMR_STATE_RX_1) || (slotState == DMR_STATE_RX_2)))
|
|
{
|
|
hrc.skipOneTS = true;
|
|
hrc.keepMonitorCapturedTSAfterTxing = false;
|
|
return;
|
|
}
|
|
|
|
hrc.tickCount = 0;
|
|
hrc.tsAgreed = 0;
|
|
hrc.tsDisagreed = 0;
|
|
hrc.hasEncodedAudio = false;
|
|
hrc.receivedFramesCount = -1;
|
|
hrc.insertSilenceFrame = false;
|
|
hrc.tsLockedTS = -1;
|
|
hrc.qsoDataSeqCount = 0;
|
|
hrc.hasAudioData = false;
|
|
hrc.qsoDataTimeout = 0;
|
|
dmrMonitorCapturedTS = -1;
|
|
hrc.dmrMonitorCapturedTimeout = hrc6000GetTSTimeoutValue();
|
|
hrc.skipOneTS = false;
|
|
HRC6000ClearActiveDMRID();
|
|
}
|
|
|
|
hrc.keepMonitorCapturedTSAfterTxing = false;
|
|
}
|
|
}
|
|
|
|
uint32_t HRC6000GetReceivedTgOrPcId(void)
|
|
{
|
|
return hrc.receivedTgOrPcId;
|
|
}
|
|
|
|
uint32_t HRC6000GetReceivedSrcId(void)
|
|
{
|
|
return hrc.receivedSrcId;
|
|
}
|
|
|
|
void HRC6000ClearTimecodeSynchronisation(void)
|
|
{
|
|
hrc.timeCode = -1;
|
|
}
|
|
|
|
void HRC6000ClearColorCodeSynchronisation(void)
|
|
{
|
|
if (hrc.transmissionEnabled == false)
|
|
{
|
|
hrc.ccHold = ((nonVolatileSettings.dmrCcTsFilter & DMR_CC_FILTER_PATTERN) != 0);
|
|
hrc.lastRxColorCode = 0xFF;
|
|
hrc.lastRxColorCodeCount = 0;
|
|
hrc.ccHoldTimer = 0;
|
|
}
|
|
}
|
|
|
|
void HRC6000SetTalkerAlias(const char *text)
|
|
{
|
|
snprintf(hrc.talkAliasText, sizeof(hrc.talkAliasText), "%s", text);
|
|
}
|
|
|
|
void HRC6000SetTalkerAliasLocation(uint32_t Lat, uint32_t Lon)
|
|
{
|
|
double longDouble = latLonFixed32ToDouble(Lon);
|
|
int32_t intLong = (33554432 * longDouble) / 360;
|
|
double latDouble = latLonFixed32ToDouble(Lat);
|
|
int32_t intLat = (16777216 * latDouble) / 180;
|
|
|
|
hrc.talkAliasLocation[0] = (intLong >> 24) & 0x01;
|
|
hrc.talkAliasLocation[1] = (intLong >> 16) & 0xFF;
|
|
hrc.talkAliasLocation[2] = (intLong >> 8) & 0xFF;
|
|
hrc.talkAliasLocation[3] = (intLong ) & 0xFF;
|
|
|
|
|
|
hrc.talkAliasLocation[4] = (intLat >> 16) & 0xFF;
|
|
hrc.talkAliasLocation[5] = (intLat >> 8) & 0xFF;
|
|
hrc.talkAliasLocation[6] = (intLat >> 0) & 0xFF;
|
|
}
|
|
|
|
bool HRC6000IRQHandlerIsRunning(void)
|
|
{
|
|
return hrc.inIRQHandler;
|
|
}
|
|
|
|
// Start streaming the prepared SMS air job. The regular DMR TX machinery
|
|
// (including the repeater wake sequence in RMO) is reused; only the burst
|
|
// contents differ from a voice call.
|
|
bool HRC6000StartSmsTransmission(void)
|
|
{
|
|
const smsAirJob_t *job = smsGetPendingAirJob();
|
|
|
|
if ((job == NULL) || (job->frameCount == 0) ||
|
|
trxTransmissionEnabled || trxIsTransmitting ||
|
|
(trxGetMode() != RADIO_MODE_DIGITAL) ||
|
|
(settingsUsbMode == USB_MODE_HOTSPOT) ||
|
|
(slotState != DMR_STATE_IDLE))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (hrc.isWaking == WAKING_MODE_FAILED)
|
|
{
|
|
hrc.isWaking = WAKING_MODE_NONE; // recover from an earlier failed repeater wake
|
|
}
|
|
|
|
hrc.smsJob = job;
|
|
hrc.smsFrameIndex = 0;
|
|
hrc.smsActive = true;
|
|
smsAirJobStarted();
|
|
|
|
trxEnableTransmission();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HRC6000IsSendingSMS(void)
|
|
{
|
|
return hrc.smsActive;
|
|
}
|
|
|
|
void HRC6000CancelSmsTransmission(void)
|
|
{
|
|
hrc.smsActive = false;
|
|
hrc.smsJob = NULL;
|
|
hrc.smsFrameIndex = 0;
|
|
}
|
|
|
|
bool HRC6000HasGotSync(void)
|
|
{
|
|
return (hrc.timeCode != -1);
|
|
}
|
|
|
|
void HRC6000SetMicGainFM(uint8_t gain)
|
|
{
|
|
SPI0WritePageRegByte(0x04, 0xE4, 0x20 + (gain));
|
|
}
|
|
|
|
void HRC6000SetFMTx(void)
|
|
{
|
|
SPI0WritePageRegByte(0x04, 0x10, 0x80); //Switch to FM Mode
|
|
// SPI0WritePageRegByte(0x04, 0xE2, 0x00); //configure ADC and DAc
|
|
SPI0WritePageRegByte(0x04, 0xE0, 0xC9); //CPU Controls Codec, Line in 1,LineOut2, I2S Slave Mode
|
|
SPI0WritePageRegByte(0x04, 0xE4, 0x20 + (nonVolatileSettings.micGainFM)); //Mic Gain
|
|
SPI0WritePageRegByte(0x04, 0xC2, 0x00); //Mic AGC Off
|
|
SPI0WritePageRegByte(0x04, 0xE5, 0x1A); //Unknown (Default value = 0A)
|
|
SPI0WritePageRegByte(0x04, 0x25, 0x0E); //Undocumented Register
|
|
SPI0WritePageRegByte(0x04, 0x26, 0xFE); //Undocumented register Turns off FM receive
|
|
SPI0WritePageRegByte(0x04, 0x83, 0xFF); //Clear aLL Interrupts
|
|
SPI0WritePageRegByte(0x04, 0x87, 0x00); //Clear Int Masks
|
|
SPI0WritePageRegByte(0x04, 0x45, analogIGain); //Set MOD2 Level (from cal table)
|
|
SPI0WritePageRegByte(0x04, 0x46, analogQGain); //Set MOD1 Level (from cal table)
|
|
SPI0WritePageRegByte(0x04, 0x48, 0x00); //Two Point Mod Bias =0
|
|
SPI0WritePageRegByte(0x04, 0x04, Mod2Offset); //Set MOD 2 Offset (Cal Value)
|
|
SPI0WritePageRegByte(0x04, 0x49, 0xFF); //set mod limit registers to max
|
|
SPI0WritePageRegByte(0x04, 0x4A, 0xFF);
|
|
|
|
uint8_t deviation;
|
|
uint8_t CTCdeviation;
|
|
uint8_t DCSdeviation;
|
|
|
|
if(trxGetFrequency() > 30000000)
|
|
{
|
|
if(trxGetBandwidthIs25kHz())
|
|
{
|
|
deviation = 0x24; //FM Deviation Coefficients for UHF 25KHz Channel Spacing
|
|
CTCdeviation = 0x09;
|
|
DCSdeviation = 0x02;
|
|
}
|
|
else
|
|
{
|
|
deviation = 0x12; //FM Deviation Coefficients for UHF 12.5KHz Channel Spacing
|
|
CTCdeviation = 0x06;
|
|
DCSdeviation = 0x01;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(trxGetBandwidthIs25kHz())
|
|
{
|
|
deviation = 0x73; //FM Deviation Coefficients for VHF 25KHz Channel Spacing (Warning 0x68 seems to be the maximum before distortion)
|
|
CTCdeviation = 0x18;
|
|
DCSdeviation = 0x06;
|
|
}
|
|
else
|
|
{
|
|
deviation = 0x37; //FM Deviation Coefficients for VHF 12.5KHz Channel Spacing
|
|
CTCdeviation = 0x10;
|
|
DCSdeviation = 0x04;
|
|
}
|
|
}
|
|
|
|
SPI0WritePageRegByte(0x04, 0x35, deviation); //FM Deviation Coefficient
|
|
|
|
if(sendingDCS)
|
|
{
|
|
SPI0WritePageRegByte(0x04, 0xA0, DCSdeviation); //set the DCS deviation level
|
|
}
|
|
else
|
|
{
|
|
SPI0WritePageRegByte(0x04, 0xA0, CTCdeviation); //Set CTCSS Deviation level
|
|
}
|
|
SPI0WritePageRegByte(0x04, 0x3F, 0x04); //Set FM Limiting Modulation Factor
|
|
SPI0WritePageRegByte(0x04, 0x34, 0x3C); //Compressor off, Pre-Emph on 3KHz Audio Filter
|
|
SPI0WritePageRegByte(0x04, 0x3E, 0x08); //Rx FM Deviation Coefficient
|
|
SPI0WritePageRegByte(0x01, 0x50, 0x00); //Aux Register 0x50 Undocumented
|
|
SPI0WritePageRegByte(0x01, 0x51, 0x00); //Aux Register 0x51 Undocumented
|
|
SPI0WritePageRegByte(0x04, 0x60, 0x80); //Set Tx to Analogue Voice Sending mode
|
|
}
|
|
|
|
void HRC6000SetFMRx(void)
|
|
{
|
|
SPI0WritePageRegByte(0x04, 0x60, 0x00); //FM Voice Tx Mode Off
|
|
SPI0WritePageRegByte(0x04, 0xE0, 0x89); //Turn off Microphone input
|
|
SPI0WritePageRegByte(0x04, 0x10, 0x80); //Mod Mode FM
|
|
SPI0WritePageRegByte(0x04, 0x34, 0x3C); //Compressor off, de-Emph on 3KHz Audio Filter
|
|
SPI0WritePageRegByte(0x04, 0x81, 0x19); //Interrupt Masks (for DMR?)
|
|
SPI0WritePageRegByte(0x04, 0x85, 0x00); //Interrupt Masks )For DMR?)
|
|
SPI0WritePageRegByte(0x04, 0x26, 0xFD); //Undocumented register Turns on FM receive
|
|
}
|
|
|
|
//restore all important registers that may have been changed by FM mode
|
|
void HRC6000SetDMR(void)
|
|
{
|
|
if(trxGetFrequency() > 30000000)
|
|
{
|
|
SPI0WritePageRegByte(0x04, 0x01, 0xF0); //set 2 point Mod, receive mode IF, non inverted (for UHF)
|
|
}
|
|
else
|
|
{
|
|
SPI0WritePageRegByte(0x04, 0x01, 0xF0); //set 2 point Mod, receive mode IF, inverted (for VHF)
|
|
}
|
|
|
|
SPI0WritePageRegByte(0x04, 0x45, digitalIGain); //Set MOD2 Level (from cal table)
|
|
SPI0WritePageRegByte(0x04, 0x46, digitalQGain); //Set MOD1 Level (from cal table)
|
|
SPI0WritePageRegByte(0x04, 0x10, 0x6E); //Set mode to DMR,Tier2,Timeslot Mode, Layer 2, Repeater, Aligned, Slot1
|
|
SPI0WritePageRegByte(0x04, 0xE0, 0xC9); //CODEC under MCU Control, LineOut2 Enabled, Mic_p Enabled, I2S Slave Mode
|
|
SPI0WritePageRegByte(0x04, 0xE4, 0x20 + (nonVolatileSettings.micGainDMR)); //Mic Gain
|
|
SPI0WritePageRegByte(0x04, 0x26, 0xFD); //Undocumented register believed to control IF ADC
|
|
}
|
|
|
|
void HRC6000SetTxCTCSS(uint8_t index)
|
|
{
|
|
if(index > 0)
|
|
{
|
|
SPI0WritePageRegByte(0x04, 0xA1, 0x88); //Enable CTCSS Mode
|
|
SPI0WritePageRegByte(0x04, 0xA8, index); //set the CTCSS Tone
|
|
sendingDCS = false;
|
|
}
|
|
else
|
|
{
|
|
SPI0WritePageRegByte(0x04, 0xA1, 0x80); //Disable CTCSS and DCS Mode
|
|
sendingDCS = false;
|
|
}
|
|
}
|
|
|
|
void HRC6000SetTxDCS(uint16_t code, bool inverted)
|
|
{
|
|
if(code > 0)
|
|
{
|
|
SPI0WritePageRegByte(0x04, 0xA1, 0x84); //Enable DCS Mode
|
|
if(inverted)
|
|
{
|
|
SPI0WritePageRegByte(0x04, 0xA2, 0x00); //set the DCS Signalling Polarity
|
|
}
|
|
else
|
|
{
|
|
SPI0WritePageRegByte(0x04, 0xA2, 0x08);
|
|
}
|
|
SPI0WritePageRegByte(0x04, 0xAB, code & 0xFF); //low 8 bits of Octal Code
|
|
SPI0WritePageRegByte(0x04, 0xAC, (code>>8) & 0x01); //High bit of Octal Code
|
|
sendingDCS = true;
|
|
}
|
|
else
|
|
{
|
|
SPI0WritePageRegByte(0x04, 0xA1, 0x80); //Disable CTCSS and DCS Mode
|
|
sendingDCS = false;
|
|
}
|
|
}
|
|
|
|
void HRC6000SetRxDCS(uint16_t code, bool inverted)
|
|
{
|
|
if(code > 0)
|
|
{
|
|
SPI0WritePageRegByte(0x04, 0xA1, 0x84); //Enable DCS Mode
|
|
if(inverted)
|
|
{
|
|
SPI0WritePageRegByte(0x04, 0xA2, 0x04); //set the DCS Signalling Polarity
|
|
}
|
|
else
|
|
{
|
|
SPI0WritePageRegByte(0x04, 0xA2, 0x00);
|
|
}
|
|
SPI0WritePageRegByte(0x04, 0xD4, code & 0xFF); //low 8 bits of Octal Code to receive
|
|
SPI0WritePageRegByte(0x04, 0xD3, ((code>>4) & 0x10) + 0x03); //High bit of Octal Code to receive plus sampling depth high nibble
|
|
SPI0WritePageRegByte(0x04, 0xD2, 0x20); //Sampling Depth Low byte to Set Sampling Depth to 800 (100ms at 8KHz)
|
|
}
|
|
else
|
|
{
|
|
SPI0WritePageRegByte(0x04, 0xA1, 0x80); //Disable CTCSS and DCS Mode
|
|
}
|
|
}
|
|
|
|
void HRC6000SetRxCTCSS(uint8_t index)
|
|
{
|
|
SPI0WritePageRegByte(0x04, 0xA1, 0x88); //Enable CTCSS Mode
|
|
SPI0WritePageRegByte(0x04, 0xA7, 0x10); //Set Detection Threshold (was 0x10)
|
|
SPI0WritePageRegByte(0x04, 0xD3, 0x07); //Set Sampling Depth to 2000 (250ms at 8KHz)
|
|
SPI0WritePageRegByte(0x04, 0xD2, 0xD0); //
|
|
SPI0WritePageRegByte(0x04, 0xD4, index); //Set the tone index to decode
|
|
}
|
|
|
|
bool HRC6000CheckCSS(void)
|
|
{
|
|
uint8_t res;
|
|
|
|
SPI0ReadPageRegByte(0x04,0x93, &res);
|
|
|
|
return (res & 0x01); //register 0x92 bit 0 has the CTCSS or DCS detected flag
|
|
}
|
|
|
|
void HRC6000SetLineOut(bool isOn)
|
|
{
|
|
SPI0ClearPageRegByteWithMask(0x04, 0xE2, 0xFD, isOn ? 0x02 : 0x00); //Enable or disable the DAC output to Line out
|
|
}
|
|
|
|
void HRC6000SetMic(bool isOn)
|
|
{
|
|
SPI0ClearPageRegByteWithMask(0x04, 0xE0, 0xBF, isOn ? 0x40 : 0x00); //Enable or disable the Mic Input to Line in 1
|
|
}
|
|
|
|
void HRC6000SetFmAudio(bool isOn)
|
|
{
|
|
if(isOn)
|
|
{
|
|
SPI0WritePageRegByte(0x04, 0x36, 0x02); // Enable the FM audio FeedThrough
|
|
SPI0WritePageRegByte(0x04, 0x10, 0x80); //Set HRC6000 rx mode to FM
|
|
}
|
|
else
|
|
{
|
|
SPI0WritePageRegByte(0x04, 0x36, 0x00); // Disable the FM audio FeedThrough
|
|
SPI0WritePageRegByte(0x04, 0x10, 0x6E); //Set HRC6000 rx mode to DMR
|
|
}
|
|
}
|
|
|
|
void HRC6000MuteFmAudio(bool isMute)
|
|
{
|
|
if(isMute)
|
|
{
|
|
SPI0WritePageRegByte(0x04, 0x36, 0x00); // Disable the FM audio FeedThrough
|
|
}
|
|
else
|
|
{
|
|
SPI0WritePageRegByte(0x04, 0x36, 0x02); // Enable the FM audio FeedThrough
|
|
}
|
|
}
|
|
|
|
#if defined(PLATFORM_MD9600) || defined(PLATFORM_MDUV380) || defined(PLATFORM_MD380) || defined(PLATFORM_RT84_DM1701)
|
|
void HRC6000GetTone1Config(HRC6000_Tone1Config_t *cfg)
|
|
{
|
|
memcpy(cfg, &savedTone1Config, sizeof(HRC6000_Tone1Config_t));
|
|
}
|
|
|
|
void HRC6000SetTone1Config(HRC6000_Tone1Config_t *cfg)
|
|
{
|
|
memcpy(&savedTone1Config, cfg, sizeof(HRC6000_Tone1Config_t));
|
|
}
|
|
#endif
|
|
|
|
void HRC6000SetDTMF(uint8_t code)
|
|
{
|
|
uint8_t deviation;
|
|
|
|
SPI0ReadPageRegByte(0x04, 0xA1, &savedTone1Config.Mode); //save the current tone mode
|
|
SPI0WritePageRegByte(0x04, 0xA1, 0x82); //Enable DTMF Mode
|
|
SPI0ReadPageRegByte(0x04, 0xA0, &savedTone1Config.Dev); //save the current tone deviation
|
|
SPI0ReadPageRegByte(0x04, 0xD1, &savedTone1Config.D1); //save the current Register D1. (undocumented high order bits are important)
|
|
|
|
if(trxGetFrequency() > 30000000)
|
|
{
|
|
if(trxGetBandwidthIs25kHz())
|
|
{
|
|
deviation = 0x24; //DTMF Deviation Coefficient for UHF 25KHz Channel Spacing
|
|
}
|
|
else
|
|
{
|
|
deviation = 0x12; //DTMF Deviation Coefficient for UHF 12.5KHz Channel Spacing
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(trxGetBandwidthIs25kHz())
|
|
{
|
|
deviation = 0x75; //DTMF Deviation Coefficient for VHF 25KHz Channel Spacing
|
|
}
|
|
else
|
|
{
|
|
deviation = 0x3A; //DTMF Deviation Coefficient for VHF 12.5KHz Channel Spacing
|
|
}
|
|
}
|
|
|
|
SPI0WritePageRegByte(0x04, 0xA0, deviation); //set the DTMF deviation
|
|
SPI0WritePageRegByte(0x04, 0xA4, 0xFA); //set the tone time to FA (this seems to set the tone to be continuous) ( Normally 2ms increments)
|
|
SPI0WritePageRegByte(0x04, 0xA3, 0x19); //set the tone gap (2ms increments)
|
|
SPI0WritePageRegByte(0x04, 0xD1, 0x01); //set the number of codes to one
|
|
SPI0WritePageRegByte(0x04, 0xAF, (code << 4)); //set the code to be sent
|
|
SPI0WritePageRegByte(0x04, 0x60, 0x00); //Set Analogue Voice Sending mode Off
|
|
SPI0WritePageRegByte(0x04, 0x60, 0x80); //Set Analogue Voice Sending mode on again to send code
|
|
}
|
|
|
|
void HRC6000DTMFoff(bool enableMic)
|
|
{
|
|
HRC6000SetMic(enableMic); //turn on or mute the mic as required.
|
|
|
|
SPI0WritePageRegByte(0x04, 0xA0, savedTone1Config.Dev); //restore the previous tone deviation
|
|
SPI0WritePageRegByte(0x04, 0xA1, savedTone1Config.Mode); //restore the previous tone Mode
|
|
SPI0WritePageRegByte(0x04, 0xD1, savedTone1Config.D1); //restore the previous D1 Contents. (undocumented but seem to be important)
|
|
SPI0WritePageRegByteExtended(0x01, 0x11B, 0x05); //restore the DTMF 697 Hz tone in case it has been changed by SetTone
|
|
SPI0WritePageRegByteExtended(0x01, 0x11A, 0x93); //
|
|
SPI0WritePageRegByteExtended(0x01, 0x123, 0x09); //restore the DTMF 1209 Hz tone in case it has been changed by SetTone
|
|
SPI0WritePageRegByteExtended(0x01, 0x122, 0xAC); //
|
|
}
|
|
|
|
void HRC6000SendTone(int tonefreq)
|
|
{
|
|
uint32_t tval;
|
|
|
|
tval = (tonefreq * 65536) / 32000; //calculate the value required to generate this tone
|
|
|
|
SPI0WritePageRegByteExtended(0x01, 0x11B, (tval >> 8) & 0xFF); //re-configure the DTMF 697 Hz tone to be this frequency
|
|
SPI0WritePageRegByteExtended(0x01, 0x11A, tval & 0xFF); //
|
|
|
|
SPI0WritePageRegByteExtended(0x01, 0x123, (tval >> 8) & 0xFF); //re-configure DTMF 1209 Hz tone to be this frequency
|
|
SPI0WritePageRegByteExtended(0x01, 0x122, tval & 0xFF); //
|
|
|
|
HRC6000SetDTMF(1); //send DTMF key 1 to send these 2 tones.
|
|
}
|
|
|
|
void HRC6000InitDTMF(void)
|
|
{
|
|
SPI0WritePageRegByteExtended(0x01, 0x11B, 0x05); //configure 697 Hz tone
|
|
SPI0WritePageRegByteExtended(0x01, 0x11A, 0x93); //
|
|
SPI0WritePageRegByteExtended(0x01, 0x11D, 0x06); //configure 770 Hz tone
|
|
SPI0WritePageRegByteExtended(0x01, 0x11C, 0x29); //
|
|
SPI0WritePageRegByteExtended(0x01, 0x11F, 0x06); //configure 852 Hz tone
|
|
SPI0WritePageRegByteExtended(0x01, 0x11E, 0xD1); //
|
|
SPI0WritePageRegByteExtended(0x01, 0x121, 0x07); //configure 941 Hz tone
|
|
SPI0WritePageRegByteExtended(0x01, 0x120, 0x87); //
|
|
|
|
SPI0WritePageRegByteExtended(0x01, 0x123, 0x09); //configure 1209 Hz tone
|
|
SPI0WritePageRegByteExtended(0x01, 0x122, 0xAC); //
|
|
SPI0WritePageRegByteExtended(0x01, 0x125, 0x0A); //configure 1336 Hz tone
|
|
SPI0WritePageRegByteExtended(0x01, 0x124, 0xB0); //
|
|
SPI0WritePageRegByteExtended(0x01, 0x127, 0x0B); //configure 1447 Hz tone
|
|
SPI0WritePageRegByteExtended(0x01, 0x126, 0xD1); //
|
|
SPI0WritePageRegByteExtended(0x01, 0x129, 0x0D); //configure 1633 Hz tone
|
|
SPI0WritePageRegByteExtended(0x01, 0x128, 0x10); //
|
|
}
|
|
|
|
void HRC6000SetDmrRxGain(int8_t gain)
|
|
{
|
|
HRC6000SetLineOut((gain < -31) ? false : true);
|
|
|
|
gain = CLAMP(gain, -31, 31);
|
|
uint8_t val = (0x80 + ((gain > 0) * 0x40)) | (gain & 0x1F);
|
|
SPI0WritePageRegByte(0x04, 0x37, val);
|
|
}
|
|
|
|
void HRC6000SetDmrAGCGain(int8_t gain)
|
|
{
|
|
|
|
}
|
|
|
|
|
|
bool HRC6000CCIsHeld(void)
|
|
{
|
|
return hrc.ccHold;
|
|
}
|