Перейти к содержанию
    

Как проявляет себя сбой DMA ADC в STM32F030?

собственно на одном из приборов, с которого длительно пишется информация в скаду, проявился дефект.

Прибор имеет 8 каналов АЦП (STM32F030), которые преобразовывают напряжение в температуру. Произошел сбой порядка чтения температур на один канал, т.е данные со второго канала попали в первый и т.д. 

Грешу на сбой DMA, т.к. все остальные преобразования производятся последовательным вызовом одной функции без статических переменных. После сброса прибора показания нормализовались, до этого он не выключался примерно несколько месяцев.

Ситуация очень нехорошая, т.к. температуры все не сильно отличаются и как такой сбой диагностировать для меня полная загадка.

 

***********************************************************

Версия с DMA не подходит, т.к. в массив температур в этом случае попали бы коды температуры процессора и ИОН!

**********************************************************

 

Spoiler
/*----------------------------------------------------------------------------
 * Name:    12ADC.с
 * Purpose:
 * Note(s):
 *----------------------------------------------------------------------------


 *----------------------------------------------------------------------------*/
#include "stm32f0xx_conf.h"
#include "stm32f0xx.h"
#include "stm32f0xx_it.h"

#include "12ADC.h"

//-------- Работа с АЦП --------------------------------------------------------
// ДМА кладет первый отсчет регулярного канала в 1 ячейку,
// видимо есть неочищенные флаги при запуске


// 8 - на отсчеты adcin A0-A7
// 2 - VREF + internal TermoDat
#define SIZEADCBUFF (8+2)		// размер буфера данных АЦП 

#define ADC1_DR_Address    ((uint32_t)0x40012440)
#define CODE_FULL 4095 			// АЦП 12 бит 

// local
static uint16_t adc_raw[SIZEADCBUFF];
static volatile bool endconversion;

/*******************************************************************************
* Режим АЦП, когда данные по нескольким каналам передаются через DMA напрямую
в ОЗУ
* АЦП запускается по команде ADC_StartOfConversion(ADC1);
* Генерируется прерывание обработчика массива
   Parameter:
*  Return:
Тактирование - 14 мгц от внутр.  RC генератора HSI14
14М/239 = 58577 Гц - частота семплирования для одного канала (17.1uS)
Для 10 каналов результат будет с частотой 5857 Гц или каждые 170uS
При времени семплирования 239.5 периодов входное сопротивление 50 килоом
*******************************************************************************/
void ADC12_init(void)
{
	GPIO_InitTypeDef  				GPIO_InitStructure;
	ADC_InitTypeDef 					ADC_InitStructure;
	DMA_InitTypeDef 					DMA_InitStructure;
	NVIC_InitTypeDef	 				NVIC_InitStructure;

	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | \
	                              GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | \
	                              GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	/* DMA1 channel1 configuration ----------------------------------------------*/
	DMA_DeInit(DMA1_Channel1);

	DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)adc_raw;
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
	DMA_InitStructure.DMA_BufferSize = (SIZEADCBUFF);
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);

	// Включим прерывание от ДМА по окончанию передачи блока
	DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE); // весь блок
	NVIC_EnableIRQ(DMA1_Channel1_IRQn);

	DMA_Cmd(DMA1_Channel1, ENABLE);
	ADC_DMARequestModeConfig(ADC1, ADC_DMAMode_Circular);
	ADC_DMACmd(ADC1, ENABLE);

	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPriority = 2; 	//наименьшему число соответствует больший приоритет
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	ADC_StructInit(&ADC_InitStructure);

	ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
	ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
	ADC_InitStructure.ADC_ScanDirection = ADC_ScanDirection_Upward;
	ADC_Init(ADC1, &ADC_InitStructure);

	/* ADC1 regular channel configuration */
	ADC_ChannelConfig(ADC1, ADC_Channel_0, ADC_SampleTime_239_5Cycles); 						// PA0
	ADC_ChannelConfig(ADC1, ADC_Channel_1, ADC_SampleTime_239_5Cycles); 						// PA1
	ADC_ChannelConfig(ADC1, ADC_Channel_2, ADC_SampleTime_239_5Cycles); 						// PA2
	ADC_ChannelConfig(ADC1, ADC_Channel_3, ADC_SampleTime_239_5Cycles); 						// PA3
	ADC_ChannelConfig(ADC1, ADC_Channel_4, ADC_SampleTime_239_5Cycles); 						// PA4
	ADC_ChannelConfig(ADC1, ADC_Channel_5, ADC_SampleTime_239_5Cycles); 						// PA5
	ADC_ChannelConfig(ADC1, ADC_Channel_6, ADC_SampleTime_239_5Cycles); 						// PA6
	ADC_ChannelConfig(ADC1, ADC_Channel_7, ADC_SampleTime_239_5Cycles); 						// PA7

	ADC_ChannelConfig(ADC1, ADC_Channel_TempSensor, ADC_SampleTime_239_5Cycles);
	ADC_ChannelConfig(ADC1, ADC_Channel_Vrefint, ADC_SampleTime_239_5Cycles);

	ADC_TempSensorCmd(ENABLE); 						// температурный датчик
	ADC_VrefintCmd(ENABLE);								// ИОН

	ADC_GetCalibrationFactor(ADC1);
	ADC_Cmd(ADC1, ENABLE);
	ADC_StartOfConversion(ADC1);
	ADC_DMACmd(ADC1, ENABLE);

	// Далее АЦП будет работать в автоматическом режиме
	// готовые данные по DMA складывать в массив в ОЗУ.
}

static uint16_t flash_read(uint32_t address)
{
	return (*(__IO uint16_t*) address);
}

/*******************************************************************************
* Измерение напряжения питания AVCC
*   Parameter:	Код с канала внутреннего ИОН (можно отфильтровать)
*   Return:   	Напряжение питания АЦП AVCC в мВ
*******************************************************************************/
static int16_t get_AVCC(uint16_t Vrefint_code)
{
	// Читаем корректировочный код VREF при 3.3В из FLASH
	uint16_t VREF_CAL = flash_read(0x1FFFF7BA);
	uint16_t AVCC = (VREF_CAL * 3300) / Vrefint_code;
	return AVCC;
}

/*******************************************************************************
* Преобразование кода канала в напряжение после делителя
		Parameter:
		Return: напряжение в милливольтах
*******************************************************************************/
static uint16_t get_vCn(uint16_t cn_code, int16_t avcc)
{
	uint32_t voltage = ((uint32_t)cn_code * avcc) / CODE_FULL;  // напряжение в мВ без делителя
	return (uint16_t)voltage;
}


/*******************************************************************************
* Измерение температуры на сенсоре CPU
Parameter:  код канала температуры, значение питания АЦП в милливольтах
Return: температура в градусах цельсия
*******************************************************************************/
//#define TEMP110_CAL_ADDR ((uint16_t*) ((uint32_t) 0x1FFFF7C2))
//#define TEMP30_CAL_ADDR ((uint16_t*) ((uint32_t) 0x1FFFF7B8))
//#define VDD_CALIB ((uint16_t) (330))		// для F030 измерения при 3.3В

//static int16_t get_temperatureCPU_051(int16_t code_temper, int32_t AVCC)
//{
//	int32_t temperature = ((code_temper * (AVCC / 10) / VDD_CALIB) - (int32_t)(*TEMP30_CAL_ADDR));
//	temperature = temperature * (int32_t)(110 - 30);
//	temperature = temperature / (int32_t)(*TEMP110_CAL_ADDR - *TEMP30_CAL_ADDR);
//	temperature = temperature + 30;
//	return (int16_t)temperature;
//}

/* Temperature sensor calibration value address */
#define TEMP30_CAL_ADDR ((uint16_t*) ((uint32_t) 0x1FFFF7B8)) // сохраненный результат в кодах АЦП при 30грС
#define VDD_CALIB 	(3300) // в милливольтах
#define AVG_SLOPE 	(4300) // в экземпле 5336 в даташите 4300
//AVG_SLOPE in ADC conversion step (@3.3V)/°C multiplied by 1000 for precision on the division

static int16_t get_temperatureCPU_030(int16_t code_temper, int32_t AVCC)
{
	int32_t temperature = ((uint32_t) *TEMP30_CAL_ADDR - ((uint32_t)code_temper * AVCC) / VDD_CALIB) * 1000;
	temperature = (temperature / AVG_SLOPE) + 30;
 	return (int16_t)temperature;
}

/*******************************************************************************
* Получение всех параметров из кода АЦП
*   Parameter:
напряжение канала в милливольтах
температура кристалла в градусах
напряжение на CPU в милливольтах
*   Return:
*******************************************************************************/
void meas_analog12(int32_t * Cn0_voltage,
                   int32_t * Cn1_voltage,
                   int32_t * Cn2_voltage,
                   int32_t * Cn3_voltage,
                   int32_t * Cn4_voltage,
                   int32_t * Cn5_voltage,
                   int32_t * Cn6_voltage,
                   int32_t * Cn7_voltage,
                   int16_t * CPU_temp,
                   int16_t * avccCPU)
{
	if(endconversion == true)
	{
		// вычисляем напряжение питания контроллера
		uint16_t U_AVCC = get_AVCC(adc_raw[0]);
		// измеряем напряжение
		*Cn0_voltage = get_vCn(adc_raw[1], U_AVCC);
		*Cn1_voltage = get_vCn(adc_raw[2], U_AVCC);
		*Cn2_voltage = get_vCn(adc_raw[3], U_AVCC);
		*Cn3_voltage = get_vCn(adc_raw[4], U_AVCC);
		*Cn4_voltage = get_vCn(adc_raw[5], U_AVCC);
		*Cn5_voltage = get_vCn(adc_raw[6], U_AVCC);
		*Cn6_voltage = get_vCn(adc_raw[7], U_AVCC);
		*Cn7_voltage = get_vCn(adc_raw[8], U_AVCC);

		*CPU_temp = get_temperatureCPU_030(adc_raw[9], U_AVCC);
		*avccCPU = U_AVCC;
		endconversion = false;

	}
	else
	{
		*Cn0_voltage = 0;
		*Cn1_voltage = 0;
		*Cn2_voltage = 0;
		*Cn3_voltage = 0;
		*Cn4_voltage = 0;
		*Cn5_voltage = 0;
		*Cn6_voltage = 0;
		*Cn7_voltage = 0;
		*CPU_temp = 0;
		*avccCPU = 0;
	}
	ADC_StartOfConversion(ADC1);
}

/*----------------------------------------------------------------------------
  Обработчик прерывания от DMA - АЦП по окончании передачи блока
 *---------------------------------------------------------------------------*/
void DMA1_Channel1_IRQHandler(void)
{
	if(DMA_GetITStatus(DMA1_IT_TC1))
	{
		endconversion = true;
		DMA_ClearITPendingBit(DMA1_IT_TC1);
	}
}

//-------------------------------------------------------------------------------

 

Изменено пользователем haker_fox
К названию темы добавил модель МК. Длинный код спрятал под спойлер.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

2 часа назад, Михась сказал:
void meas_analog12(int32_t * Cn0_voltage,
                   int32_t * Cn1_voltage,
                   int32_t * Cn2_voltage,
                   int32_t * Cn3_voltage,
                   int32_t * Cn4_voltage,
                   int32_t * Cn5_voltage,
                   int32_t * Cn6_voltage,
                   int32_t * Cn7_voltage,
                   int16_t * CPU_temp,
                   int16_t * avccCPU)
{
	if(endconversion == true)
	{
		// вычисляем напряжение питания контроллера
		uint16_t U_AVCC = get_AVCC(adc_raw[0]);
		// измеряем напряжение
		*Cn0_voltage = get_vCn(adc_raw[1], U_AVCC);
		*Cn1_voltage = get_vCn(adc_raw[2], U_AVCC);
		*Cn2_voltage = get_vCn(adc_raw[3], U_AVCC);
		*Cn3_voltage = get_vCn(adc_raw[4], U_AVCC);
		*Cn4_voltage = get_vCn(adc_raw[5], U_AVCC);
		*Cn5_voltage = get_vCn(adc_raw[6], U_AVCC);
		*Cn6_voltage = get_vCn(adc_raw[7], U_AVCC);
		*Cn7_voltage = get_vCn(adc_raw[8], U_AVCC);

		*CPU_temp = get_temperatureCPU_030(adc_raw[9], U_AVCC);
		*avccCPU = U_AVCC;
		endconversion = false;

	}
	else
	{
		*Cn0_voltage = 0;
		*Cn1_voltage = 0;
		*Cn2_voltage = 0;
		*Cn3_voltage = 0;
		*Cn4_voltage = 0;
		*Cn5_voltage = 0;
		*Cn6_voltage = 0;
		*Cn7_voltage = 0;
		*CPU_temp = 0;
		*avccCPU = 0;
	}
	ADC_StartOfConversion(ADC1);
}

Ужас!!!  :shok:

Видимо автор сего безобразия никогда не заглядывает ни в листинги ни в отладчик....

 

Если DMA используете в режиме:

2 часа назад, Михась сказал:
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;

То на кой при каждом "Получении всех параметров из кода АЦП" вызывать ADC_StartOfConversion(ADC1) ???

Чтобы в ней не находилось (кода которой мы не видим).

"Циклический режим" - это когда DMA работает сам по себе. Непрерывно. Данные из заполненного DMA-буфера при этом программа, как правило, читает в ISR.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Почему adc_raw не volatile?
 

3 часа назад, jcxz сказал:

То на кой при каждом "Получении всех параметров из кода АЦП" вызывать ADC_StartOfConversion(ADC1) ???

DMA-то да, перезапускать не надо. Вот только АЦП может после N преобразований скинуть бит активации и ждать следующего "пинка". Я не вдавался в подробности исходника ТС, возможно у него так (полагаться на простыни SPL/HAL не хочу).

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

48 minutes ago, Arlleex said:

Почему adc_raw не volatile?
 

DMA-то да, перезапускать не надо. Вот только АЦП может после N преобразований скинуть бит активации и ждать следующего "пинка". Я не вдавался в подробности исходника ТС, возможно у него так (полагаться на простыни SPL/HAL не хочу).

На сколько помню, так и есть.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 час назад, Arlleex сказал:

DMA-то да, перезапускать не надо.

Может и не надо, но что именно делает ТС в ADC_StartOfConversion()? он не показал.

Есть подозрение, что он там возможно некорректно останавливает и перезапускает DMA. Возможно.

Вобщем - опять угадайка. :unknw: Так как бОльшая часть кода скрыта.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Это библиотечная функция.

void ADC_StartOfConversion(ADC_TypeDef* ADCx)
{
  ADCx->CR |= (uint32_t)ADC_CR_ADSTART;
}

В общем, проблема, как я думаю, не в DMA. Будем смотреть дальше, может в скаде. Потому как просто сдвиг, это понятно. А сдвиг по кольцу - это уже не понятно.

Остальной код - это про другое.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...