Files
edgetx-gx12/radio/src/targets/common/arm/stm32/stm32_hal_adc.cpp
T
2021-11-20 20:30:42 +01:00

462 lines
13 KiB
C++

/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "opentx.h"
#include "hal/adc_driver.h"
#include "stm32_hal_adc.h"
static bool adc_disable_dma(DMA_Stream_TypeDef * dma_stream);
static void adc_dma_clear_flags(DMA_Stream_TypeDef * dma_stream);
static void adc_init_pins()
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
#if defined(ADC_GPIOA_PINS)
#if defined(RADIO_FAMILY_T16) || defined(PCBNV14)
if (globalData.flyskygimbals)
{
GPIO_InitStructure.GPIO_Pin = ADC_GPIOA_PINS_FS;
}
else
#endif
GPIO_InitStructure.GPIO_Pin = ADC_GPIOA_PINS;
GPIO_Init(GPIOA, &GPIO_InitStructure);
#endif
#if defined(ADC_GPIOB_PINS)
GPIO_InitStructure.GPIO_Pin = ADC_GPIOB_PINS;
GPIO_Init(GPIOB, &GPIO_InitStructure);
#endif
#if defined(ADC_GPIOC_PINS)
GPIO_InitStructure.GPIO_Pin = ADC_GPIOC_PINS;
GPIO_Init(GPIOC, &GPIO_InitStructure);
#endif
#if defined(ADC_GPIOF_PINS)
GPIO_InitStructure.GPIO_Pin = ADC_GPIOF_PINS;
GPIO_Init(GPIOF, &GPIO_InitStructure);
#endif
}
static void adc_setup_scan_mode(ADC_TypeDef* ADCx, uint8_t nconv)
{
ADC_InitTypeDef ADC_InitStructure;
ADC_StructInit(&ADC_InitStructure);
ADC_InitStructure.ADC_ScanConvMode = ENABLE; // Sets ADC_CR1_SCAN
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // Clears ADC_CR2_CONT
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; // Software trigger
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfConversion = nconv; // Channel count
ADC_Init(ADCx, &ADC_InitStructure);
// enable ADC_MAIN
ADC_Cmd(ADCx, ENABLE); // ADC_CR2_ADON
// enable DMA for ADC
ADC_DMACmd(ADCx, ENABLE); // ADC_CR2_DMA
ADC_DMARequestAfterLastTransferCmd(ADCx, ENABLE); // ADC_CR2_DDS
}
stm32_hal_adc_channel ADC_MAIN_channels[] = {
{ADC_CHANNEL_STICK_LH, ADC_SAMPTIME},
{ADC_CHANNEL_STICK_LV, ADC_SAMPTIME},
{ADC_CHANNEL_STICK_RV, ADC_SAMPTIME},
{ADC_CHANNEL_STICK_RH, ADC_SAMPTIME},
#if defined(PCBX10)
{ ADC_CHANNEL_POT1, ADC_SAMPTIME },
{ ADC_CHANNEL_POT2, ADC_SAMPTIME },
{ ADC_CHANNEL_POT3, ADC_SAMPTIME },
{ ADC_CHANNEL_EXT1, ADC_SAMPTIME },
{ ADC_CHANNEL_EXT2, ADC_SAMPTIME },
{ ADC_CHANNEL_EXT3, ADC_SAMPTIME },
{ ADC_CHANNEL_EXT4, ADC_SAMPTIME },
{ ADC_CHANNEL_SLIDER1, ADC_SAMPTIME },
{ ADC_CHANNEL_SLIDER2, ADC_SAMPTIME },
{ ADC_CHANNEL_BATT, ADC_SAMPTIME }
#else
#if defined(RADIO_T8) || defined(RADIO_TLITE)
// fake channels to fill unsused POT1/POT2
{0, 0},
{0, 0},
#elif defined(PCBX7) || defined(PCBXLITE)
{ADC_CHANNEL_POT1, ADC_SAMPTIME},
{ADC_CHANNEL_POT2, ADC_SAMPTIME},
#elif defined(PCBX9LITE)
{ADC_CHANNEL_POT1, ADC_SAMPTIME},
#elif defined(PCBX9E)
{ADC_CHANNEL_POT2, ADC_SAMPTIME},
{ADC_CHANNEL_POT3, ADC_SAMPTIME},
{ADC_CHANNEL_POT4, ADC_SAMPTIME},
{ADC_CHANNEL_SLIDER3, ADC_SAMPTIME},
{ADC_CHANNEL_SLIDER4, ADC_SAMPTIME},
#elif defined(PCBX9D) || defined(PCBX9DP)
{ADC_CHANNEL_POT1, ADC_SAMPTIME},
{ADC_CHANNEL_POT2, ADC_SAMPTIME},
{ADC_CHANNEL_POT3, ADC_SAMPTIME},
{ADC_CHANNEL_SLIDER1, ADC_SAMPTIME},
{ADC_CHANNEL_SLIDER2, ADC_SAMPTIME},
#elif defined(PCBNV14)
{ADC_CHANNEL_POT1, ADC_SAMPTIME},
{ADC_CHANNEL_POT2, ADC_SAMPTIME},
{ADC_CHANNEL_SWA, ADC_SAMPTIME},
{ADC_CHANNEL_SWC, ADC_SAMPTIME},
{ADC_CHANNEL_SWE, ADC_SAMPTIME},
{ADC_CHANNEL_SWF, ADC_SAMPTIME},
{ADC_CHANNEL_SWG, ADC_SAMPTIME},
{ADC_CHANNEL_SWH, ADC_SAMPTIME},
#endif
{ADC_CHANNEL_BATT, ADC_SAMPTIME},
{ADC_Channel_Vbat, ADC_SAMPTIME}
#endif
};
static const stm32_hal_adc_channel* ADC_MAIN_get_channels()
{
#if (NUM_PWMSTICKS > 0)
if (STICKS_PWM_ENABLED())
return ADC_MAIN_channels + 4;
#endif
#if defined(RADIO_FAMILY_T16) || defined(PCBNV14)
if (globalData.flyskygimbals)
{
return ADC_MAIN_channels + FIRST_ANALOG_ADC_FS;
}
else
#endif
{
return ADC_MAIN_channels + FIRST_ANALOG_ADC;
}
}
static uint8_t ADC_MAIN_get_nconv()
{
#if defined(RADIO_FAMILY_T16) || defined(PCBNV14)
if (globalData.flyskygimbals)
return NUM_ANALOGS_ADC_FS;
else
#endif
return NUM_ANALOGS_ADC; // based on STICKS_PWM_ENABLED()
}
#if defined(ADC_EXT)
#if defined(PCBX9E)
static const stm32_hal_adc_channel ADC_EXT_channels[] = {
{ADC_CHANNEL_POT1, ADC_SAMPTIME},
{ADC_CHANNEL_SLIDER1, ADC_SAMPTIME},
{ADC_CHANNEL_SLIDER2, ADC_SAMPTIME}
};
static uint8_t ADC_EXT_get_nconv() { return NUM_ANALOGS_ADC_EXT; }
#elif defined(PCBX10)
static const stm32_hal_adc_channel ADC_EXT_channels[] = {
{ ADC_Channel_Vbat, ADC_SAMPTIME }
};
static uint8_t ADC_EXT_get_nconv() { return 1; }
#elif defined(PCBNV14)
static const stm32_hal_adc_channel ADC_EXT_channels[] = {
{ADC_CHANNEL_SWB, ADC_SAMPTIME},
{ADC_CHANNEL_SWD, ADC_SAMPTIME},
};
static uint8_t ADC_EXT_get_nconv() { return NUM_ANALOGS_ADC_EXT; }
#endif
static const stm32_hal_adc_channel* ADC_EXT_get_channels()
{
return ADC_EXT_channels;
}
#endif
static uint16_t* ADC_MAIN_get_dma_buffer()
{
#if defined(RADIO_FAMILY_T16) || defined(PCBNV14)
if (globalData.flyskygimbals)
{
return &adcValues[FIRST_ANALOG_ADC_FS];
}
else
#endif
{
return &adcValues[FIRST_ANALOG_ADC];
}
}
#if defined(ADC_EXT) && defined(ADC_EXT_DMA_Stream)
static uint16_t* ADC_EXT_get_dma_buffer()
{
#if defined(RADIO_FAMILY_T16) || defined(PCBNV14)
if (globalData.flyskygimbals)
{
return adcValues + NUM_ANALOGS_ADC_FS + FIRST_ANALOG_ADC_FS;
}
else
#endif
return adcValues + NUM_ANALOGS_ADC + FIRST_ANALOG_ADC;
}
#endif
stm32_hal_adc ADC_hal_def[] = {
{
ADC_MAIN,
ADC_DMA_Stream, ADC_DMA_Channel, ADC_MAIN_get_dma_buffer,
ADC_MAIN_get_nconv, ADC_MAIN_get_channels
},
#if defined(ADC_EXT)
{
ADC_EXT,
#if defined(ADC_EXT_DMA_Stream)
ADC_EXT_DMA_Stream, ADC_EXT_DMA_Channel, ADC_EXT_get_dma_buffer,
#else
nullptr, 0, nullptr,
#endif
ADC_EXT_get_nconv, ADC_EXT_get_channels
},
#endif
// required to detect end of ADC definitions
{ nullptr, nullptr, 0, nullptr, nullptr, nullptr },
};
static void adc_init_channels(ADC_TypeDef* adc, const stm32_hal_adc_channel* chan,
uint8_t nconv)
{
if (!chan || !nconv) return;
uint8_t rank = 1;
while (nconv > 0) {
ADC_RegularChannelConfig(adc, chan->adc_channel, rank, chan->sample_time);
nconv--; rank++; chan++;
}
}
static bool adc_init_dma_stream(ADC_TypeDef* adc, DMA_Stream_TypeDef * dma_stream,
uint32_t dma_channel, uint16_t* dest, uint8_t nconv)
{
// Disable DMA before continuing (see ref. manual "Stream configuration procedure")
if (!adc_disable_dma(dma_stream))
return false;
// Clear Interrupt flags
adc_dma_clear_flags(dma_stream);
// setup DMA request
dma_stream->PAR = CONVERT_PTR_UINT(&adc->DR);
dma_stream->M0AR = CONVERT_PTR_UINT(dest);
dma_stream->NDTR = nconv;
// Very high priority, 1 byte transfers, increment memory
dma_stream->CR = DMA_SxCR_PL | dma_channel | DMA_SxCR_MSIZE_0 |
DMA_SxCR_PSIZE_0 | DMA_SxCR_MINC;
// disable direct mode, half full FIFO
dma_stream->FCR = DMA_SxFCR_DMDIS | DMA_SxFCR_FTH_0;
return true;
}
static bool stm32_hal_adc_init()
{
adc_init_pins();
// Init common to all ADCs
ADC_CommonInitTypeDef ADC_CommonInitStruct;
ADC_CommonStructInit(&ADC_CommonInitStruct);
ADC_CommonInitStruct.ADC_Mode = ADC_Mode_Independent;
ADC_CommonInitStruct.ADC_Prescaler = ADC_Prescaler_Div2;
ADC_CommonInit(&ADC_CommonInitStruct);
const stm32_hal_adc* adc_def = ADC_hal_def;
while (adc_def->adc) {
uint8_t nconv = 0;
if (adc_def->get_nconv) {
nconv = adc_def->get_nconv();
if (nconv > 0) {
adc_setup_scan_mode(adc_def->adc, nconv);
// configure each channel
if (adc_def->get_channels) {
const stm32_hal_adc_channel* chan = adc_def->get_channels();
adc_init_channels(adc_def->adc, chan, nconv);
}
if (adc_def->dma_stream && adc_def->get_dma_buffer) {
uint16_t* dma_buffer = adc_def->get_dma_buffer();
if (!adc_init_dma_stream(adc_def->adc, adc_def->dma_stream,
adc_def->dma_channel, dma_buffer, nconv))
return false;
}
}
}
// move to next ADC definition
adc_def++;
}
//TODO: move VBat & PWM sticks somewhere else.
// Enable vbat sensor
ADC_VBATCmd(ENABLE);
#if NUM_PWMSTICKS > 0
if (STICKS_PWM_ENABLED()) {
sticksPwmInit();
}
#endif
return true;
}
#define DMA_Stream0_IT_MASK (uint32_t)(DMA_LISR_FEIF0 | DMA_LISR_DMEIF0 | \
DMA_LISR_TEIF0 | DMA_LISR_HTIF0 | \
DMA_LISR_TCIF0)
#define DMA_Stream4_IT_MASK (uint32_t)(DMA_HISR_FEIF4 | DMA_HISR_DMEIF4 | \
DMA_HISR_TEIF4 | DMA_HISR_HTIF4 | \
DMA_HISR_TCIF4)
static void adc_dma_clear_flags(DMA_Stream_TypeDef * dma_stream)
{
// no other choice, sorry for that...
if (dma_stream == DMA2_Stream4) {
/* Reset interrupt pending bits for DMA2 Stream4 */
DMA2->HIFCR = DMA_Stream4_IT_MASK;
} else if (dma_stream == DMA2_Stream0) {
/* Reset interrupt pending bits for DMA2 Stream0 */
DMA2->LIFCR = DMA_Stream0_IT_MASK;
}
}
static bool adc_start_dma_conversion(ADC_TypeDef* ADCx,
DMA_Stream_TypeDef * dma_stream)
{
// Disable DMA before continuing (see ref. manual "Stream configuration procedure")
if (!adc_disable_dma(dma_stream))
return false;
// Clear Interrupt flags
adc_dma_clear_flags(dma_stream);
// Clear ADC status register
ADCx->SR &= ~(uint32_t)(ADC_SR_EOC | ADC_SR_STRT | ADC_SR_OVR);
// Enable DMA
dma_stream->CR |= DMA_SxCR_EN;
// Trigger ADC start
ADCx->CR2 |= (uint32_t)ADC_CR2_SWSTART;
return true;
}
static bool adc_disable_dma(DMA_Stream_TypeDef* dma_stream)
{
dma_stream->CR &= ~(uint32_t)DMA_SxCR_EN;
// wait until DMA EN bit gets cleared by hardware
uint16_t uiTimeout = 1000;
while (dma_stream->CR & (uint32_t)DMA_SxCR_EN) {
if (--uiTimeout == 0) {
// Timeout. Failed to disable DMA
return false;
}
}
return true;
}
static void adc_start_single_conversion(ADC_TypeDef* ADCx)
{
ADCx->SR &= ~(uint32_t)(ADC_SR_EOC | ADC_SR_STRT | ADC_SR_OVR);
ADCx->CR2 |= (uint32_t)ADC_CR2_SWSTART;
}
static bool stm32_hal_adc_start_read()
{
const stm32_hal_adc* adc_def = ADC_hal_def;
while (adc_def->adc) {
uint8_t nconv = 0;
if (adc_def->get_nconv) {
nconv = adc_def->get_nconv();
if (nconv > 0) {
if (adc_def->dma_stream && adc_def->get_dma_buffer) {
if (!adc_start_dma_conversion(adc_def->adc, adc_def->dma_stream))
return false;
} else if (nconv == 1) {
adc_start_single_conversion(adc_def->adc);
}
}
}
// move to next ADC definition
adc_def++;
}
return true;
}
static void stm32_hal_adc_wait_completion()
{
//TODO:
// - iterate through ADCs
// - replace with IRQ trigger (both)
// - move RTC batt reading somewhere else
#if defined(ADC_EXT) && defined(ADC_EXT_DMA_Stream)
// Wait for all ADCs to complete
for (unsigned int i=0; i<10000; i++) {
if (DMA_GetFlagStatus(ADC_DMA_Stream, ADC_DMA_TC_Flag) != RESET &&
DMA_GetFlagStatus(ADC_EXT_DMA_Stream, ADC_EXT_TC_Flag) != RESET) {
break;
}
}
//TODO: move into completion IRQ trigger
adc_disable_dma(ADC_DMA_Stream);
adc_disable_dma(ADC_EXT_DMA_Stream);
#else
// Wait only for the main ADC, and hope others are done as well
for (unsigned int i = 0; i < 10000; i++) {
if (DMA_GetFlagStatus(ADC_DMA_Stream, ADC_DMA_TC_Flag) != RESET) {
break;
}
}
//TODO: move into completion IRQ trigger
adc_disable_dma(ADC_DMA_Stream);
#if defined(ADC_EXT) && !defined(ADC_EXT_DMA_Stream)
if (isVBatBridgeEnabled()) {
rtcBatteryVoltage = ADC_EXT->DR;
}
#endif
#endif
}
const etx_hal_adc_driver_t stm32_hal_adc_driver = {
stm32_hal_adc_init,
stm32_hal_adc_start_read,
stm32_hal_adc_wait_completion
};