/** * @file adc.cpp * @brief Manages signal reading through the ADC. * * Copyright (C) 2021 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 = 0,//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 = 2, .end_cb = readAltCallback, .error_cb = nullptr, .cfgr = ADC_CFGR_EXTEN_RISING | ADC_CFGR_EXTSEL_SRC(13), /* TIM6_TRGO */ .cfgr2 = 0,//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_2P5) | ADC_SMPR1_SMP_AN2(ADC_SMPR_SMP_2P5), 0 }, .sqr = { ADC_SQR1_SQ1_N(ADC_CHANNEL_IN2) | ADC_SQR1_SQ2_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 palSetPadMode(GPIOC, 1, PAL_MODE_INPUT_ANALOG); // Potentiometer 2 #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 > 1) return 0; static adcsample_t result[16] = {}; readAltDone = false; adcStartConversion(m_driver2, &m_group_config2, result, 8); while (!readAltDone); //__WFI(); adcStopConversion(m_driver2); return result[id]; } 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 = {{ // PLLSAI2 sources MSI of 4MHz, divided by PLLM of /1 = 4MHz. // 4MHz is then multiplied by PLLSAI2N (x8 to x86), with result // between 64 and 344 MHz. // // SAI2N MUST BE AT LEAST 16 TO MAKE 64MHz MINIMUM. // // That is then divided by PLLSAI2R: // R of 0 = /2; 1 = /4, 2 = /6, 3 = /8. // PLLSAI2 then feeds into the ADC, which has a prescaler of /10. // Finally, the ADC's SMP value produces the desired sample rate. // // 4MHz * N / R / 10 / SMP = sample rate. // // With oversampling, must create faster clock // (x2 oversampling requires x2 sample rate clock). // // Rate PLLSAI2N R SMPR {/* 8k */ 16, 1, ADC_SMPR_SMP_12P5}, // R3=32k (min), R1=64k {/* 16k */ 16, 0, ADC_SMPR_SMP_12P5}, {/* 20k */ 20, 0, ADC_SMPR_SMP_12P5}, {/* 32k */ 32, 0, ADC_SMPR_SMP_12P5}, {/* 48k */ 48, 0, ADC_SMPR_SMP_12P5}, {/* 96k */ 73, 0, ADC_SMPR_SMP_6P5} // Technically 96.05263kS/s }}; auto& preset = m_rate_presets[static_cast(rate)]; auto pllnr = (preset[0] << RCC_PLLSAI2CFGR_PLLSAI2N_Pos) | (preset[1] << RCC_PLLSAI2CFGR_PLLSAI2R_Pos); auto smpr = preset[2]; // 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); m_group_config.smpr[0] = ADC_SMPR1_SMP_AN5(smpr); // 8x oversample m_group_config.cfgr2 = ADC_CFGR2_ROVSE | (2 << ADC_CFGR2_OVSR_Pos) | (3 << ADC_CFGR2_OVSS_Pos); m_group_config2.cfgr2 = ADC_CFGR2_ROVSE | (2 << ADC_CFGR2_OVSR_Pos) | (3 << ADC_CFGR2_OVSS_Pos); #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); } }