MDUV3x0: using HR_C6000 for MCU to speaker audio

This commit is contained in:
Silvano Seva 2024-10-12 12:24:05 +02:00
parent 2c3f9c50c5
commit 2d0bf51873
7 changed files with 90 additions and 36 deletions

View File

@ -159,7 +159,7 @@ mdx_src = ['platform/drivers/ADC/ADC1_MDx.c',
'platform/drivers/NVM/W25Qx.c', 'platform/drivers/NVM/W25Qx.c',
'platform/drivers/NVM/nvmem_settings_MDx.c', 'platform/drivers/NVM/nvmem_settings_MDx.c',
'platform/drivers/NVM/nvmem_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/baseband/HR_Cx000.cpp',
'platform/drivers/tones/toneGenerator_MDx.cpp', 'platform/drivers/tones/toneGenerator_MDx.cpp',
'platform/drivers/SPI/spi_custom.c', '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/display/HX8353_MD3x.cpp',
'platform/drivers/backlight/backlight_MDx.c', 'platform/drivers/backlight/backlight_MDx.c',
'platform/drivers/chSelector/chSelector_UV3x0.c', 'platform/drivers/chSelector/chSelector_UV3x0.c',
'platform/drivers/audio/Cx000_dac.cpp',
'platform/drivers/baseband/radio_UV3x0.cpp', 'platform/drivers/baseband/radio_UV3x0.cpp',
'platform/drivers/baseband/AT1846S_UV3x0.cpp', 'platform/drivers/baseband/AT1846S_UV3x0.cpp',
'platform/drivers/baseband/HR_C6000_UV3x0.cpp'] 'platform/drivers/baseband/HR_C6000_UV3x0.cpp']

View File

@ -29,6 +29,7 @@
#define UI_THREAD_STKSIZE 2048 #define UI_THREAD_STKSIZE 2048
#define RTX_THREAD_STKSIZE 512 #define RTX_THREAD_STKSIZE 512
#define CODEC2_THREAD_STKSIZE 16384 #define CODEC2_THREAD_STKSIZE 16384
#define AUDIO_THREAD_STKSIZE 512
/** /**
* Thread priority levels, UNIX-like: lower level, higher thread priority * Thread priority levels, UNIX-like: lower level, higher thread priority

View File

@ -36,7 +36,7 @@
* provide the helper function below to keep the real volume level consistent * provide the helper function below to keep the real volume level consistent
* with the knob position. * with the knob position.
*/ */
#if defined(PLATFORM_MDUV3x0) || defined(PLATFORM_TTWRPLUS) #if defined(PLATFORM_TTWRPLUS)
void _setVolume() void _setVolume()
{ {
static uint8_t oldVolume = 0xFF; static uint8_t oldVolume = 0xFF;
@ -45,15 +45,8 @@ void _setVolume()
if(volume == oldVolume) if(volume == oldVolume)
return; 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 volume control is 4 bit
AT1846S::instance().setRxAudioGain(volume / 16, volume / 16); AT1846S::instance().setRxAudioGain(volume / 16, volume / 16);
#endif
oldVolume = volume; oldVolume = volume;
} }
#endif #endif
@ -91,7 +84,7 @@ void OpMode_FM::update(rtxStatus_t *const status, const bool newCfg)
{ {
(void) newCfg; (void) newCfg;
#if defined(PLATFORM_MDUV3x0) || defined(PLATFORM_TTWRPLUS) #if defined(PLATFORM_TTWRPLUS)
// Set output volume by changing the HR_C6000 DAC gain // Set output volume by changing the HR_C6000 DAC gain
_setVolume(); _setVolume();
#endif #endif

View File

@ -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 * * Niccolò Izzo IU2KIN *
* Frederik Saraci IU2NRO * * Frederik Saraci IU2NRO *
* Silvano Seva IU2KWO * * Silvano Seva IU2KWO *
@ -18,15 +18,24 @@
* along with this program; if not, see <http://www.gnu.org/licenses/> * * along with this program; if not, see <http://www.gnu.org/licenses/> *
***************************************************************************/ ***************************************************************************/
#include <interfaces/platform.h>
#include <interfaces/delays.h> #include <interfaces/delays.h>
#include <interfaces/audio.h> #include <interfaces/audio.h>
#include <interfaces/radio.h> #include <interfaces/radio.h>
#include <peripherals/gpio.h> #include <peripherals/gpio.h>
#include <hwconfig.h> #include <hwconfig.h>
#include <threads.h>
#include <state.h>
#include "toneGenerator_MDx.h" #include "toneGenerator_MDx.h"
#include "stm32_pwm.h" #include "stm32_pwm.h"
#include "stm32_adc.h" #include "stm32_adc.h"
#ifdef PLATFORM_MDUV3x0
#include <HR_C6000.h>
#include "Cx000_dac.h"
#endif
#define PATH(x,y) ((x << 4) | y) #define PATH(x,y) ((x << 4) | y)
static const uint8_t pathCompatibilityMatrix[9][9] = static const uint8_t pathCompatibilityMatrix[9][9] =
@ -64,10 +73,48 @@ static const struct PwmChannelCfg stm32pwm_cfg =
stm32pwm_stopCbk 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[] = const struct audioDevice outputDevices[] =
{ {
{NULL, NULL, 0, SINK_MCU}, {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}, {&stm32_pwm_audio_driver, &stm32pwm_cfg, 0, SINK_SPK},
#endif
{&stm32_pwm_audio_driver, &stm32pwm_cfg, 0, SINK_RTX}, {&stm32_pwm_audio_driver, &stm32pwm_cfg, 0, SINK_RTX},
}; };
@ -102,6 +149,25 @@ void audio_init()
stm32pwm_init(); stm32pwm_init();
stm32adc_init(STM32_ADC_ADC2); 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, &param);
pthread_create(&thread, &attr, audio_thread, NULL);
#endif
} }
void audio_terminate() void audio_terminate()
@ -136,7 +202,11 @@ void audio_connect(const enum AudioSource source, const enum AudioSink sink)
radio_enableAfOutput(); radio_enableAfOutput();
break; 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): case PATH(SOURCE_MCU, SINK_SPK):
#endif
case PATH(SOURCE_MCU, SINK_RTX): case PATH(SOURCE_MCU, SINK_RTX):
gpio_setMode(BEEP_OUT, ALTERNATE | ALTERNATE_FUNC(2)); gpio_setMode(BEEP_OUT, ALTERNATE | ALTERNATE_FUNC(2));
break; break;
@ -182,7 +252,9 @@ void audio_disconnect(const enum AudioSource source, const enum AudioSink sink)
radio_disableAfOutput(); radio_disableAfOutput();
break; break;
#ifndef PLATFORM_MDUV3x0
case PATH(SOURCE_MCU, SINK_SPK): case PATH(SOURCE_MCU, SINK_SPK):
#endif
case PATH(SOURCE_MCU, SINK_RTX): case PATH(SOURCE_MCU, SINK_RTX):
gpio_setMode(BEEP_OUT, INPUT); // Set output to Hi-Z gpio_setMode(BEEP_OUT, INPUT); // Set output to Hi-Z
break; break;

View File

@ -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, 0xC2, 0x00); // Codec AGC gain
writeReg(M::CONFIG, 0xE5, 0x1A); // Unknown (Default value = 0A) writeReg(M::CONFIG, 0xE5, 0x1A); // Unknown (Default value = 0A)
writeReg(M::CONFIG, 0x25, 0x0E); // Undocumented Register 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, 0x83, 0xFF); // Clear all Interrupts
writeReg(M::CONFIG, 0x87, 0x00); // Clear Int Masks writeReg(M::CONFIG, 0x87, 0x00); // Clear Int Masks
writeReg(M::CONFIG, 0xA1, 0x80); // FM_mod, all modes cleared 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, 0x60, 0x00); // Stop analog transmission
writeReg(M::CONFIG, 0xE0, 0xC9); // Codec enabled, LineIn1, LineOut2, I2S slave mode 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, 0x34, 0x98); // FM bpf enabled, 25kHz bandwidth
writeReg(M::CONFIG, 0x26, 0xFD); // Undocumented register, enable FM receive
} }

View File

@ -85,15 +85,10 @@ void radio_init(const rtxStatus_t *rtxState)
nvm_readCalibData(&calData); 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(); at1846s.init();
C6000.init();
radio_disableAfOutput(); radio_disableAfOutput();
} }
@ -150,14 +145,16 @@ bool radio_checkRxDigitalSquelch()
void radio_enableAfOutput() 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 // TODO: AF output management for DMR mode
C6000.writeCfgRegister(0x36, 0x02); // 0xFD enable FM receive.
C6000.writeCfgRegister(0x26, 0xFD);
} }
void radio_disableAfOutput() void radio_disableAfOutput()
{ {
C6000.writeCfgRegister(0x36, 0x00); // Undocumented register, disable FM receive
C6000.writeCfgRegister(0x26, 0xFE);
} }
void radio_enableRx() void radio_enableRx()
@ -174,18 +171,6 @@ void radio_enableRx()
at1846s.setFrequency(config->rxFrequency); at1846s.setFrequency(config->rxFrequency);
at1846s.setFuncMode(AT1846S_FuncMode::RX); 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) if(currRxBand == BND_VHF)
{ {
gpio_setPin(VHF_LNA_EN); gpio_setPin(VHF_LNA_EN);

View File

@ -21,9 +21,13 @@
#include <spi_bitbang.h> #include <spi_bitbang.h>
#include <spi_custom.h> #include <spi_custom.h>
#include <hwconfig.h> #include <hwconfig.h>
#include <pthread.h>
#include <pinmap.h> #include <pinmap.h>
#include <spi_stm32.h> #include <spi_stm32.h>
static pthread_mutex_t c6000_mutex;
/** /**
* SPI bitbang function for HR_C6000 command interface (U_SPI). * 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; 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) SPI_STM32_DEVICE_DEFINE(nvm_spi, SPI1, NULL)