diff --git a/meson.build b/meson.build
index 4782d0ba..dd8d2920 100644
--- a/meson.build
+++ b/meson.build
@@ -159,7 +159,7 @@ mdx_src = ['platform/drivers/ADC/ADC1_MDx.c',
'platform/drivers/NVM/W25Qx.c',
'platform/drivers/NVM/nvmem_settings_MDx.c',
'platform/drivers/NVM/nvmem_MDx.c',
- 'platform/drivers/audio/audio_MDx.c',
+ 'platform/drivers/audio/audio_MDx.cpp',
'platform/drivers/baseband/HR_Cx000.cpp',
'platform/drivers/tones/toneGenerator_MDx.cpp',
'platform/drivers/SPI/spi_custom.c',
@@ -361,6 +361,7 @@ mduv3x0_src = ['platform/drivers/CPS/cps_io_native_MDUV3x0.c',
'platform/drivers/display/HX8353_MD3x.cpp',
'platform/drivers/backlight/backlight_MDx.c',
'platform/drivers/chSelector/chSelector_UV3x0.c',
+ 'platform/drivers/audio/Cx000_dac.cpp',
'platform/drivers/baseband/radio_UV3x0.cpp',
'platform/drivers/baseband/AT1846S_UV3x0.cpp',
'platform/drivers/baseband/HR_C6000_UV3x0.cpp']
diff --git a/openrtx/include/core/threads.h b/openrtx/include/core/threads.h
index 3cc54f60..4fd83bf7 100644
--- a/openrtx/include/core/threads.h
+++ b/openrtx/include/core/threads.h
@@ -29,6 +29,7 @@
#define UI_THREAD_STKSIZE 2048
#define RTX_THREAD_STKSIZE 512
#define CODEC2_THREAD_STKSIZE 16384
+#define AUDIO_THREAD_STKSIZE 512
/**
* Thread priority levels, UNIX-like: lower level, higher thread priority
diff --git a/openrtx/src/rtx/OpMode_FM.cpp b/openrtx/src/rtx/OpMode_FM.cpp
index 0eec9244..78cf1575 100644
--- a/openrtx/src/rtx/OpMode_FM.cpp
+++ b/openrtx/src/rtx/OpMode_FM.cpp
@@ -36,7 +36,7 @@
* provide the helper function below to keep the real volume level consistent
* with the knob position.
*/
-#if defined(PLATFORM_MDUV3x0) || defined(PLATFORM_TTWRPLUS)
+#if defined(PLATFORM_TTWRPLUS)
void _setVolume()
{
static uint8_t oldVolume = 0xFF;
@@ -45,15 +45,8 @@ void _setVolume()
if(volume == oldVolume)
return;
-#if defined(PLATFORM_MDUV3x0)
- // Apply new volume level, map 0 - 255 range into -31 to 31
- int8_t gain = ((int8_t) (volume / 4)) - 31;
- C6000.setDacGain(gain);
-#elif defined(PLATFORM_TTWRPLUS)
// AT1846S volume control is 4 bit
AT1846S::instance().setRxAudioGain(volume / 16, volume / 16);
-#endif
-
oldVolume = volume;
}
#endif
@@ -91,7 +84,7 @@ void OpMode_FM::update(rtxStatus_t *const status, const bool newCfg)
{
(void) newCfg;
- #if defined(PLATFORM_MDUV3x0) || defined(PLATFORM_TTWRPLUS)
+ #if defined(PLATFORM_TTWRPLUS)
// Set output volume by changing the HR_C6000 DAC gain
_setVolume();
#endif
diff --git a/platform/drivers/audio/audio_MDx.c b/platform/drivers/audio/audio_MDx.cpp
similarity index 78%
rename from platform/drivers/audio/audio_MDx.c
rename to platform/drivers/audio/audio_MDx.cpp
index 1d554587..190686fc 100644
--- a/platform/drivers/audio/audio_MDx.c
+++ b/platform/drivers/audio/audio_MDx.cpp
@@ -1,5 +1,5 @@
/***************************************************************************
- * Copyright (C) 2021 - 2023 by Federico Amedeo Izzo IU2NUO, *
+ * Copyright (C) 2021 - 2024 by Federico Amedeo Izzo IU2NUO, *
* Niccolò Izzo IU2KIN *
* Frederik Saraci IU2NRO *
* Silvano Seva IU2KWO *
@@ -18,15 +18,24 @@
* along with this program; if not, see *
***************************************************************************/
+#include
#include
#include
#include
#include
#include
+#include
+#include
#include "toneGenerator_MDx.h"
#include "stm32_pwm.h"
#include "stm32_adc.h"
+#ifdef PLATFORM_MDUV3x0
+#include
+#include "Cx000_dac.h"
+#endif
+
+
#define PATH(x,y) ((x << 4) | y)
static const uint8_t pathCompatibilityMatrix[9][9] =
@@ -64,10 +73,48 @@ static const struct PwmChannelCfg stm32pwm_cfg =
stm32pwm_stopCbk
};
+#ifdef PLATFORM_MDUV3x0
+static void *audio_thread(void *arg)
+{
+ (void) arg;
+
+ static uint8_t oldVolume = 0xFF;
+ unsigned long long now = getTick();
+
+ Cx000dac_init(&C6000);
+
+ while(state.devStatus != SHUTDOWN)
+ {
+ Cx000dac_task();
+
+ uint8_t volume = platform_getVolumeLevel();
+ if(volume != oldVolume)
+ {
+ // Apply new volume level, map 0 - 255 range into -31 to 31
+ int8_t gain = ((int8_t) (volume / 4)) - 31;
+ C6000.setDacGain(gain);
+
+ oldVolume = volume;
+ }
+
+ now += 4;
+ sleepUntil(now);
+ }
+
+ Cx000dac_terminate();
+
+ return NULL;
+}
+#endif
+
const struct audioDevice outputDevices[] =
{
{NULL, NULL, 0, SINK_MCU},
+ #ifdef PLATFORM_MDUV3x0
+ {&Cx000_dac_audio_driver, NULL, 0, SINK_SPK},
+ #else
{&stm32_pwm_audio_driver, &stm32pwm_cfg, 0, SINK_SPK},
+ #endif
{&stm32_pwm_audio_driver, &stm32pwm_cfg, 0, SINK_RTX},
};
@@ -102,6 +149,25 @@ void audio_init()
stm32pwm_init();
stm32adc_init(STM32_ADC_ADC2);
+
+ #ifdef PLATFORM_MDUV3x0
+ gpio_setMode(DMR_CLK, OUTPUT);
+ gpio_setMode(DMR_MOSI, OUTPUT);
+ gpio_setMode(DMR_MISO, INPUT);
+ spi_init((const struct spiDevice *) &c6000_spi);
+ C6000.init();
+
+ pthread_attr_t attr;
+ pthread_t thread;
+ struct sched_param param;
+
+ pthread_attr_init(&attr);
+ pthread_attr_setstacksize(&attr, AUDIO_THREAD_STKSIZE);
+
+ param.sched_priority = THREAD_PRIO_RT;
+ pthread_attr_setschedparam(&attr, ¶m);
+ pthread_create(&thread, &attr, audio_thread, NULL);
+ #endif
}
void audio_terminate()
@@ -136,7 +202,11 @@ void audio_connect(const enum AudioSource source, const enum AudioSink sink)
radio_enableAfOutput();
break;
+ // MD-UV380 uses HR_C6000 for MCU->SPK audio output. Switching between
+ // incoming FM audio and DAC output is done automatically inside the IC.
+ #ifndef PLATFORM_MDUV3x0
case PATH(SOURCE_MCU, SINK_SPK):
+ #endif
case PATH(SOURCE_MCU, SINK_RTX):
gpio_setMode(BEEP_OUT, ALTERNATE | ALTERNATE_FUNC(2));
break;
@@ -182,7 +252,9 @@ void audio_disconnect(const enum AudioSource source, const enum AudioSink sink)
radio_disableAfOutput();
break;
+ #ifndef PLATFORM_MDUV3x0
case PATH(SOURCE_MCU, SINK_SPK):
+ #endif
case PATH(SOURCE_MCU, SINK_RTX):
gpio_setMode(BEEP_OUT, INPUT); // Set output to Hi-Z
break;
diff --git a/platform/drivers/baseband/HR_C6000_UV3x0.cpp b/platform/drivers/baseband/HR_C6000_UV3x0.cpp
index 46da53f4..afb2fe01 100644
--- a/platform/drivers/baseband/HR_C6000_UV3x0.cpp
+++ b/platform/drivers/baseband/HR_C6000_UV3x0.cpp
@@ -202,7 +202,6 @@ void HR_Cx000< M >::startAnalogTx(const TxAudioSource source, const FmConfig cfg
writeReg(M::CONFIG, 0xC2, 0x00); // Codec AGC gain
writeReg(M::CONFIG, 0xE5, 0x1A); // Unknown (Default value = 0A)
writeReg(M::CONFIG, 0x25, 0x0E); // Undocumented Register
- writeReg(M::CONFIG, 0x26, 0xFE); // Undocumented register Turns off FM receive
writeReg(M::CONFIG, 0x83, 0xFF); // Clear all Interrupts
writeReg(M::CONFIG, 0x87, 0x00); // Clear Int Masks
writeReg(M::CONFIG, 0xA1, 0x80); // FM_mod, all modes cleared
@@ -221,5 +220,4 @@ void HR_Cx000< M >::stopAnalogTx()
writeReg(M::CONFIG, 0x60, 0x00); // Stop analog transmission
writeReg(M::CONFIG, 0xE0, 0xC9); // Codec enabled, LineIn1, LineOut2, I2S slave mode
writeReg(M::CONFIG, 0x34, 0x98); // FM bpf enabled, 25kHz bandwidth
- writeReg(M::CONFIG, 0x26, 0xFD); // Undocumented register, enable FM receive
}
diff --git a/platform/drivers/baseband/radio_UV3x0.cpp b/platform/drivers/baseband/radio_UV3x0.cpp
index 9ea77403..68e5a3a7 100644
--- a/platform/drivers/baseband/radio_UV3x0.cpp
+++ b/platform/drivers/baseband/radio_UV3x0.cpp
@@ -85,15 +85,10 @@ void radio_init(const rtxStatus_t *rtxState)
nvm_readCalibData(&calData);
/*
- * Configure AT1846S and HR_C6000, keep AF output disabled at power on.
+ * Initialize AT1846S keep AF output disabled at power on.
+ * HR_C6000 is initialized in advance by the audio system.
*/
- gpio_setMode(DMR_CLK, OUTPUT);
- gpio_setMode(DMR_MOSI, OUTPUT);
- gpio_setMode(DMR_MISO, INPUT);
- spi_init((const struct spiDevice *) &c6000_spi);
-
at1846s.init();
- C6000.init();
radio_disableAfOutput();
}
@@ -150,14 +145,16 @@ bool radio_checkRxDigitalSquelch()
void radio_enableAfOutput()
{
- // Bit 2 of register 0x36: enable voice channel in FM mode
+ // Undocumented register, bits [1:0] seem to enable/disable FM audio RX.
// TODO: AF output management for DMR mode
- C6000.writeCfgRegister(0x36, 0x02);
+ // 0xFD enable FM receive.
+ C6000.writeCfgRegister(0x26, 0xFD);
}
void radio_disableAfOutput()
{
- C6000.writeCfgRegister(0x36, 0x00);
+ // Undocumented register, disable FM receive
+ C6000.writeCfgRegister(0x26, 0xFE);
}
void radio_enableRx()
@@ -174,18 +171,6 @@ void radio_enableRx()
at1846s.setFrequency(config->rxFrequency);
at1846s.setFuncMode(AT1846S_FuncMode::RX);
- /*
- * Force silencing of audio output when RX is enabled with M17 operating
- * mode selected. Avoids the spillover of baseband signal towards the
- * speaker.
- *
- * TODO: improve this solution.
- */
- if(config->opMode == OPMODE_M17)
- {
- C6000.writeCfgRegister(0xE0, 0x00);
- }
-
if(currRxBand == BND_VHF)
{
gpio_setPin(VHF_LNA_EN);
diff --git a/platform/targets/MD-UV3x0/hwconfig.c b/platform/targets/MD-UV3x0/hwconfig.c
index c916d01e..3c646bb9 100644
--- a/platform/targets/MD-UV3x0/hwconfig.c
+++ b/platform/targets/MD-UV3x0/hwconfig.c
@@ -21,9 +21,13 @@
#include
#include
#include
+#include
#include
#include
+
+static pthread_mutex_t c6000_mutex;
+
/**
* SPI bitbang function for HR_C6000 command interface (U_SPI).
*
@@ -67,5 +71,5 @@ static uint8_t spiC6000_func(const void *priv, uint8_t value)
return incoming;
}
-SPI_CUSTOM_DEVICE_DEFINE(c6000_spi, spiC6000_func, NULL, NULL)
+SPI_CUSTOM_DEVICE_DEFINE(c6000_spi, spiC6000_func, NULL, &c6000_mutex)
SPI_STM32_DEVICE_DEFINE(nvm_spi, SPI1, NULL)