/**
* @file adc.cpp
* @brief Manages signal reading through the ADC.
*
* Copyright (C) 2020 Clyne Sullivan
*
* Distributed under the GNU GPL v3 or later. You should have received a copy of
* the GNU General Public License along with this program.
* If not, see .
*/
#include "adc.hpp"
#if defined(TARGET_PLATFORM_L4)
ADCDriver *ADC::m_driver = &ADCD1;
ADCDriver *ADC::m_driver2 = &ADCD3;
#else
ADCDriver *ADC::m_driver = &ADCD3;
//ADCDriver *ADC::m_driver2 = &ADCD1; // TODO
#endif
const ADCConfig ADC::m_config = {
.difsel = 0,
#if defined(TARGET_PLATFORM_H7)
.calibration = 0,
#endif
};
const ADCConfig ADC::m_config2 = {
.difsel = 0,
#if defined(TARGET_PLATFORM_H7)
.calibration = 0,
#endif
};
ADCConversionGroup ADC::m_group_config = {
.circular = true,
.num_channels = 1,
.end_cb = ADC::conversionCallback,
.error_cb = nullptr,
.cfgr = ADC_CFGR_EXTEN_RISING | ADC_CFGR_EXTSEL_SRC(13), /* TIM6_TRGO */
.cfgr2 = ADC_CFGR2_ROVSE | ADC_CFGR2_OVSR_1 | ADC_CFGR2_OVSS_0, // Oversampling 2x
#if defined(TARGET_PLATFORM_H7)
.ccr = 0,
.pcsel = 0,
.ltr1 = 0, .htr1 = 4095,
.ltr2 = 0, .htr2 = 4095,
.ltr3 = 0, .htr3 = 4095,
#else
.tr1 = ADC_TR(0, 4095),
.tr2 = ADC_TR(0, 4095),
.tr3 = ADC_TR(0, 4095),
.awd2cr = 0,
.awd3cr = 0,
#endif
.smpr = {
ADC_SMPR1_SMP_AN5(ADC_SMPR_SMP_12P5), 0
},
.sqr = {
ADC_SQR1_SQ1_N(ADC_CHANNEL_IN5),
0, 0, 0
},
};
static bool readAltDone = false;
static void readAltCallback(ADCDriver *)
{
readAltDone = true;
}
ADCConversionGroup ADC::m_group_config2 = {
.circular = false,
.num_channels = 1,
.end_cb = readAltCallback,
.error_cb = nullptr,
.cfgr = ADC_CFGR_EXTEN_RISING | ADC_CFGR_EXTSEL_SRC(13), /* TIM6_TRGO */
.cfgr2 = ADC_CFGR2_ROVSE | ADC_CFGR2_OVSR_1 | ADC_CFGR2_OVSS_0, // Oversampling 2x
#if defined(TARGET_PLATFORM_H7)
.ccr = 0,
.pcsel = 0,
.ltr1 = 0, .htr1 = 4095,
.ltr2 = 0, .htr2 = 4095,
.ltr3 = 0, .htr3 = 4095,
#else
.tr1 = ADC_TR(0, 4095),
.tr2 = ADC_TR(0, 4095),
.tr3 = ADC_TR(0, 4095),
.awd2cr = 0,
.awd3cr = 0,
#endif
.smpr = {
ADC_SMPR1_SMP_AN1(ADC_SMPR_SMP_12P5), 0
},
.sqr = {
ADC_SQR1_SQ1_N(ADC_CHANNEL_IN1),
0, 0, 0
},
};
adcsample_t *ADC::m_current_buffer = nullptr;
size_t ADC::m_current_buffer_size = 0;
ADC::Operation ADC::m_operation = nullptr;
void ADC::begin()
{
#if defined(TARGET_PLATFORM_H7)
palSetPadMode(GPIOF, 3, PAL_MODE_INPUT_ANALOG);
#else
palSetPadMode(GPIOA, 0, PAL_MODE_INPUT_ANALOG); // Algorithm in
palSetPadMode(GPIOC, 0, PAL_MODE_INPUT_ANALOG); // Potentiometer 1
#endif
adcStart(m_driver, &m_config);
adcStart(m_driver2, &m_config2);
}
void ADC::start(adcsample_t *buffer, size_t count, Operation operation)
{
m_current_buffer = buffer;
m_current_buffer_size = count;
m_operation = operation;
adcStartConversion(m_driver, &m_group_config, buffer, count);
SClock::start();
}
void ADC::stop()
{
SClock::stop();
adcStopConversion(m_driver);
m_current_buffer = nullptr;
m_current_buffer_size = 0;
m_operation = nullptr;
}
adcsample_t ADC::readAlt(unsigned int id)
{
if (id != 0)
return 0;
static adcsample_t result[32] = {};
readAltDone = false;
adcStartConversion(m_driver2, &m_group_config2, result, 32);
while (!readAltDone)
;
adcStopConversion(m_driver2);
return result[0];
}
void ADC::setRate(SClock::Rate rate)
{
#if defined(TARGET_PLATFORM_H7)
std::array, 6> m_rate_presets = {{
// Rate PLL N PLL P
{/* 8k */ 80, 20},
{/* 16k */ 80, 10},
{/* 20k */ 80, 8},
{/* 32k */ 80, 5},
{/* 48k */ 96, 4},
{/* 96k */ 288, 10}
}};
auto& preset = m_rate_presets[static_cast(rate)];
auto pllbits = (preset[0] << RCC_PLL2DIVR_N2_Pos) |
(preset[1] << RCC_PLL2DIVR_P2_Pos);
adcStop(m_driver);
// Adjust PLL2
RCC->CR &= ~(RCC_CR_PLL2ON);
while ((RCC->CR & RCC_CR_PLL2RDY) == RCC_CR_PLL2RDY);
auto pll2divr = RCC->PLL2DIVR &
~(RCC_PLL2DIVR_N2_Msk | RCC_PLL2DIVR_P2_Msk);
pll2divr |= pllbits;
RCC->PLL2DIVR = pll2divr;
RCC->CR |= RCC_CR_PLL2ON;
while ((RCC->CR & RCC_CR_PLL2RDY) != RCC_CR_PLL2RDY);
m_group_config.smpr[0] = rate != SClock::Rate::R96K ? ADC_SMPR1_SMP_AN5(ADC_SMPR_SMP_12P5)
: ADC_SMPR1_SMP_AN5(ADC_SMPR_SMP_2P5);
adcStart(m_driver, &m_config);
#elif defined(TARGET_PLATFORM_L4)
std::array, 6> m_rate_presets = {{
// Rate PLLSAI2N R OVERSAMPLE
{/* 8k */ 16, 3, 1},
{/* 16k */ 32, 3, 1},
{/* 20k */ 40, 3, 1},
{/* 32k */ 64, 3, 1},
{/* 48k */ 24, 3, 0},
{/* 96k */ 48, 3, 0}
}};
auto& preset = m_rate_presets[static_cast(rate)];
auto pllnr = (preset[0] << RCC_PLLSAI2CFGR_PLLSAI2N_Pos) |
(preset[1] << RCC_PLLSAI2CFGR_PLLSAI2R_Pos);
bool oversample = preset[2] != 0;
// Adjust PLLSAI2
RCC->CR &= ~(RCC_CR_PLLSAI2ON);
while ((RCC->CR & RCC_CR_PLLSAI2RDY) == RCC_CR_PLLSAI2RDY);
RCC->PLLSAI2CFGR = (RCC->PLLSAI2CFGR & ~(RCC_PLLSAI2CFGR_PLLSAI2N_Msk | RCC_PLLSAI2CFGR_PLLSAI2R_Msk)) | pllnr;
RCC->CR |= RCC_CR_PLLSAI2ON;
while ((RCC->CR & RCC_CR_PLLSAI2RDY) != RCC_CR_PLLSAI2RDY);
// Set 2x oversampling
m_group_config.cfgr2 = oversample ? ADC_CFGR2_ROVSE | ADC_CFGR2_OVSR_0 | ADC_CFGR2_OVSS_1 : 0;
m_group_config2.cfgr2 = oversample ? ADC_CFGR2_ROVSE | ADC_CFGR2_OVSR_0 | ADC_CFGR2_OVSS_1 : 0;
#endif
}
void ADC::setOperation(ADC::Operation operation)
{
m_operation = operation;
}
void ADC::conversionCallback(ADCDriver *driver)
{
if (m_operation != nullptr) {
auto half_size = m_current_buffer_size / 2;
if (adcIsBufferComplete(driver))
m_operation(m_current_buffer + half_size, half_size);
else
m_operation(m_current_buffer, half_size);
}
}