/* * Copyright (C) 2021-2025 Roger Clark, VK3KYY / G4KYF * * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * 4. Use of this source code or binary releases for commercial purposes is strictly forbidden. This includes, without limitation, * incorporation in a commercial product or incorporation into a product or project which allows commercial use. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include "functions/settings.h" #include "functions/trx.h" #include "functions/rxPowerSaving.h" #include "hardware/HR-C6000.h" #include "hardware/AT1846S.h" #if defined(USING_EXTERNAL_DEBUGGER) #include "SeggerRTT/RTT/SEGGER_RTT.h" #endif #include "main.h" #if defined(PLATFORM_MD9600) //list of CTCSS tones supported by the HRC6000. These are used to find the index for the tone. The order is critical. const uint16_t HRC_CTCSSTones[] = { 6700, 7190, 7440, 7700, 7970, 8250, 8540, 8850, 9150, 9480, 9740, 10000, 10350, 10720, 11090, 11480, 11880, 12300, 12730, 13180, 13650, 14130, 14620, 15140, 15670, 16220, 16790, 17380, 17990, 18620, 19280, 20350, 21070, 21810, 22570, 23360, 24180, 25030, 6930, 6250, 15980, 16550, 17130, 17730, 18350, 18990, 19660, 19950, 20650, 22910, 25410 }; #endif // AT-1846 native values for Rx static uint8_t fl_l; static uint8_t fl_h; static uint8_t fh_l; static uint8_t fh_h; RadioDevice_t currentRadioDeviceId = RADIO_DEVICE_PRIMARY; TRXDevice_t radioDevices[RADIO_DEVICE_MAX] = { { .currentRxFrequency = FREQUENCY_UNSET, .currentTxFrequency = FREQUENCY_UNSET, .lastSetTxFrequency = FREQUENCY_UNSET, .trxCurrentBand = { RADIO_BAND_VHF, RADIO_BAND_VHF }, .trxDMRModeRx = DMR_MODE_DMO, .trxDMRModeTx = DMR_MODE_DMO, .currentMode = RADIO_MODE_NONE, .txPowerLevel = POWER_UNSET, .lastSetTxPowerLevel = POWER_UNSET, .trxRxSignal = 0, .trxRxNoise = 255, .currentBandWidthIs25kHz = BANDWIDTH_12P5KHZ, .analogSignalReceived = false, .analogTriggeredAudio = false, .digitalSignalReceived = false } }; TRXDevice_t *currentRadioDevice = &radioDevices[RADIO_DEVICE_PRIMARY]; void radioPowerOn(void) { //Turn off Tx Voltages to prevent transmission. HAL_GPIO_WritePin(PA_EN_1_GPIO_Port, PA_EN_1_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(PA_EN_2_GPIO_Port, PA_EN_2_Pin, GPIO_PIN_RESET); //Turn on all receiver voltages. (thats what the TYT firmware does, both receivers are on together) radioSetRxLNAForDevice(currentRadioDeviceId); HAL_GPIO_WritePin(C6000_PWD_GPIO_Port, C6000_PWD_Pin, GPIO_PIN_RESET);// Power On the C6000 //Turn on the Microphone power HAL_GPIO_WritePin(MICPWR_SW_GPIO_Port, MICPWR_SW_Pin, GPIO_PIN_SET); } void radioSetRxLNAForDevice(RadioDevice_t deviceId) { if (radioDevices[deviceId].trxCurrentBand[TRX_RX_FREQ_BAND] == RADIO_BAND_VHF) { HAL_GPIO_WritePin(R5_V_SW_GPIO_Port, R5_V_SW_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(R5_U_SW_GPIO_Port, R5_U_SW_Pin, GPIO_PIN_RESET); } else { HAL_GPIO_WritePin(R5_V_SW_GPIO_Port, R5_V_SW_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(R5_U_SW_GPIO_Port, R5_U_SW_Pin, GPIO_PIN_SET); } } void radioPowerOff(bool invalidateFrequency, bool includeMic) { //turn off all of the transmitter and receiver voltages HAL_GPIO_WritePin(PA_EN_1_GPIO_Port, PA_EN_1_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(PA_EN_2_GPIO_Port, PA_EN_2_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(R5_V_SW_GPIO_Port, R5_V_SW_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(R5_U_SW_GPIO_Port, R5_U_SW_Pin, GPIO_PIN_RESET); // Some external speaker mic are problematic when the // Mic pin is powered down, triggering some cycling PTT down events. // So, we're keeping that pin high all the time when Rx Power Saving is running if (includeMic) { HAL_GPIO_WritePin(MICPWR_SW_GPIO_Port, MICPWR_SW_Pin, GPIO_PIN_RESET); } HAL_GPIO_WritePin(C6000_PWD_GPIO_Port, C6000_PWD_Pin, GPIO_PIN_SET);// Power Down the C6000 // Turn off Rx in AT1846S if (currentRadioDevice->currentBandWidthIs25kHz) { // 25 kHz settings radioWriteReg2byte(0x30, 0x70, 0x06); // RX off } else { // 12.5 kHz settings radioWriteReg2byte(0x30, 0x40, 0x06); // RX off } if (invalidateFrequency) { trxInvalidateCurrentFrequency(); } } void radioInit(void) { radioSetTRxDevice(RADIO_DEVICE_PRIMARY); AT1846sInit(); } void radioPostinit(void) { AT1846sPostInit(); } RadioDevice_t radioSetTRxDevice(RadioDevice_t deviceId) { RadioDevice_t previousDeviceId = currentRadioDeviceId; currentRadioDeviceId = deviceId; currentRadioDevice = &radioDevices[currentRadioDeviceId]; return previousDeviceId; } RadioDevice_t radioGetTRxDeviceId(void) { return currentRadioDeviceId; } void radioSetBandwidth(bool Is25K) { AT1846sSetBandWidth(Is25K); } void radioSetMode(int mode) // Called withing trx.c: in task critical sections { if (mode == RADIO_MODE_ANALOG) { HRC6000SetFMRx(); } else { HRC6000SetDMR(); } AT1846sSetMode(mode); } void radioReadVoxAndMicStrength(void) { trxTxVox = adcGetVOX(); //MDUV380 Mic is not connected to AT1846 but has dedicated ADC channel like MD9600 trxTxMic = trxTxVox; //MDUV380 doesn't have separate Signals so use Vox for both. } static void trxUpdateAT1846SCalibration(void) { //Nothing needed on the MDUV380 } void radioSetFrequency(uint32_t f_in, bool Tx) { // G4EML. Hack to fix the -3KHz offset on transmit on the MDUV380 // Believed to be caused by something in the AT1846S configuration. // If we ever fix what is causing it then this hack can be removed. if (Tx) { if (currentRadioDevice->currentBandWidthIs25kHz) { #warning Hack for FM 25khz bandwidth, somehow needed after changes to activate both AT1846S. Needs more investigation, as it may be masking another problem f_in = f_in + 615; //add 6.15Khz to Tx frequency } else { f_in = f_in + 300; //add 3Khz to Tx frequency } } // if (currentChannelData->libreDMR_Power != 0x00) { currentRadioDevice->txPowerLevel = currentChannelData->libreDMR_Power - 1; } else { currentRadioDevice->txPowerLevel = nonVolatileSettings.txPowerLevel; } if (Tx) { currentRadioDevice->trxCurrentBand[TRX_TX_FREQ_BAND] = trxGetBandFromFrequency(f_in); } else { currentRadioDevice->trxCurrentBand[TRX_RX_FREQ_BAND] = trxGetBandFromFrequency(f_in); radioSetRxLNAForDevice(currentRadioDeviceId); } uint32_t f = f_in * 0.16f; fl_l = (f & 0x000000ff) >> 0; fl_h = (f & 0x0000ff00) >> 8; fh_l = (f & 0x00ff0000) >> 16; fh_h = (f & 0xff000000) >> 24; if (currentRadioDevice->currentBandWidthIs25kHz) { // 25 kHz settings radioWriteReg2byte(0x30, 0x70, 0x06); // RX off } else { // 12.5 kHz settings radioWriteReg2byte(0x30, 0x60, 0x06); // RX off } radioWriteReg2byte(0x05, 0x87, 0x63); // select 'normal' frequency mode radioWriteReg2byte(0x29, fh_h, fh_l); radioWriteReg2byte(0x2a, fl_h, fl_l); radioWriteReg2byte(0x49, 0x0C, 0x15); // setting SQ open and shut threshold if (currentRadioDevice->currentBandWidthIs25kHz) { // 25 kHz settings radioWriteReg2byte(0x30, 0x70, 0x26); // RX on } else { // 12.5 kHz settings radioWriteReg2byte(0x30, 0x60, 0x26); // RX on } trxUpdateAT1846SCalibration(); if (Tx) //We have just set the Tx frequency so we are about to transmit. Set the Tx Power Control DAC (Shared with VHF Receive Tuning) { dacOut(1, txDACDrivePower); } else { dacOut(1, 0); } } void radioSetIF(int band, bool wide) { UNUSED_PARAMETER(band); radioSetBandwidth(wide); } void radioSetTx(uint8_t band) { //Turn off receiver voltages. (thats what the TYT firmware does, both receivers are on or off together) HAL_GPIO_WritePin(R5_V_SW_GPIO_Port, R5_V_SW_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(R5_U_SW_GPIO_Port, R5_U_SW_Pin, GPIO_PIN_RESET); //Configure HRC-6000 for transmit if (trxGetMode() == RADIO_MODE_ANALOG) { HRC6000SetFMTx(); if (currentRadioDevice->currentBandWidthIs25kHz) { radioWriteReg2byte(0x30, 0x70, 0x46); // 25 kHz settings // RX on } else { radioWriteReg2byte(0x30, 0x60, 0x46); // 12.5 kHz settings // RX on } trxSelectVoiceChannel(AT1846_VOICE_CHANNEL_MIC);// For 1750 tone burst //trxSelectVoiceChannel(AT1846_VOICE_CHANNEL_NONE);// the MDUV380 does not use the AT1846S for modulation. trxSetMicGainFM(nonVolatileSettings.micGainFM); } else { HRC6000SetDMR(); radioWriteReg2byte(0x30, 0x60, 0xC6); // Digital Tx } //Turn on Tx Voltage for the current band. HAL_GPIO_WritePin(PA_EN_1_GPIO_Port, PA_EN_1_Pin, GPIO_PIN_SET); if (band == RADIO_BAND_VHF) { HAL_GPIO_WritePin(PA_SEL_SW_GPIO_Port, PA_SEL_SW_Pin, GPIO_PIN_RESET); } else { HAL_GPIO_WritePin(PA_SEL_SW_GPIO_Port, PA_SEL_SW_Pin, GPIO_PIN_SET); } //Turn on the power control circuit HAL_GPIO_WritePin(PA_EN_2_GPIO_Port, PA_EN_2_Pin, GPIO_PIN_SET); trxIsTransmitting = true; } //just enable or disable the RF output . doesn't change back to receive void radioFastTx(bool tx) { if (tx) { radioWriteReg2byte(0x30, 0x60, 0xC6); // Digital Tx HAL_GPIO_WritePin(PA_EN_1_GPIO_Port, PA_EN_1_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(PA_EN_2_GPIO_Port, PA_EN_2_Pin, GPIO_PIN_SET); } else { //Turn off Tx Voltages to prevent transmission. HAL_GPIO_WritePin(PA_EN_1_GPIO_Port, PA_EN_1_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(PA_EN_2_GPIO_Port, PA_EN_2_Pin, GPIO_PIN_RESET); if (currentRadioDevice->currentBandWidthIs25kHz) { // 25 kHz settings radioWriteReg2byte(0x30, 0x70, 0x26); // RX on } else { // 12.5 kHz settings radioWriteReg2byte(0x30, 0x60, 0x26); // RX on } } } void radioSetRx(uint8_t band) { //Turn off the power control circuit //HAL_GPIO_WritePin(RF_APC_SW_GPIO_Port, RF_APC_SW_Pin, GPIO_PIN_RESET); //Turn off Tx Voltages to prevent transmission. HAL_GPIO_WritePin(PA_EN_1_GPIO_Port, PA_EN_1_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(PA_EN_2_GPIO_Port, PA_EN_2_Pin, GPIO_PIN_RESET); //Turn on receiver voltages. (thats what the TYT firmware does, both receivers are on together) radioSetRxLNAForDevice(currentRadioDeviceId); trxIsTransmitting = false; if (currentRadioDevice->currentBandWidthIs25kHz) { // 25 kHz settings radioWriteReg2byte(0x30, 0x70, 0x26); // RX on } else { // 12.5 kHz settings radioWriteReg2byte(0x30, 0x60, 0x26); // RX on } if (trxGetMode() == RADIO_MODE_ANALOG) { HRC6000SetFMRx(); } else { HRC6000SetDMR(); } } void radioReadRSSIAndNoiseForBand(uint8_t band) { uint8_t val1, val2; UNUSED_PARAMETER(band);// not band specific on the MDUV380 if (rxPowerSavingIsRxOn()) { taskENTER_CRITICAL(); if (radioReadReg2byte(0x1b, &val1, &val2)) { currentRadioDevice->trxRxSignal = val1; currentRadioDevice->trxRxNoise = val2; } taskEXIT_CRITICAL(); trxDMRSynchronisedRSSIReadPending = false; } } #if defined(PLATFORM_MD9600) static int getCSSToneIndex(uint16_t tone) { //the TYT firmware uses a PWM output to directly generate the CTCSS Tone. //however the HR-C6000 can also generate tones so we will use that instead //this uses a table so we need to convert the CTCSS tone to the Table Index. uint8_t HRCToneIndex = 1; for (int i = 0; i < ARRAY_SIZE(HRC_CTCSSTones); i++) { if (HRC_CTCSSTones[i] == tone) { HRCToneIndex = i + 1; break; } } return HRCToneIndex; } #endif void radioTxCSSOff(void) { AT1846sSetTxCTCSS(0); } void radioTxCTCSOn(uint16_t tone) { AT1846sSetTxCTCSS(tone); } void radioTxDCSOn(uint16_t code, bool inverted) { #if defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017) AT1846sSetTxDCS(code, inverted); #elif defined(PLATFORM_MD9600) HRC6000SetTxDCS(code, inverted); #else #error UNSUPPORTED PLATFORM #endif } void radioRxCSSOff(RadioDevice_t deviceId) { AT1846sSetRxCSSOff(deviceId); } void radioRxCTCSOn(RadioDevice_t deviceId, uint16_t tone) { AT1846sSetRxCTCSS(deviceId, tone); } void radioRxDCSOn(RadioDevice_t deviceId, uint16_t code, bool inverted) { AT1846sSetRxDCS(deviceId, code, inverted); } bool radioCheckCSS(uint16_t tone, CodeplugCSSTypes_t type) { return AT1846sCheckCSS(tone, type); } void radioSetTone1(int tonefreq) { if(tonefreq > 0) { trxSelectVoiceChannel(AT1846_VOICE_CHANNEL_TONE1); //configure AT1846S for tone HRC6000SetMic(false); //mute the microphone while sending tone. } else { trxSelectVoiceChannel(AT1846_VOICE_CHANNEL_MIC); //reset At1846S HRC6000SetMic(true); //enable the mic } AT1846sWriteTone1Reg(tonefreq * 10); } void radioSetMicGainFM(uint8_t gain) { HRC6000SetMicGainFM(gain); } void radioAudioAmp(bool on) { HAL_GPIO_WritePin(AUDIO_AMP_EN_GPIO_Port, AUDIO_AMP_EN_Pin, (on ? GPIO_PIN_SET : GPIO_PIN_RESET)); //Turn on the audio amp. HAL_GPIO_WritePin(SPK_MUTE_GPIO_Port, SPK_MUTE_Pin, (on ? GPIO_PIN_RESET : GPIO_PIN_SET)); //Unmute the speaker } void radioSetAudioPath(bool fromFM) { HRC6000SetFmAudio(fromFM); } void radioSelectVoiceChannel(uint8_t channel, uint8_t *voiceGainTx, uint16_t *deviation) { AT1846sSelectVoiceChannel(channel, voiceGainTx, deviation); }