Михась 2 8 октября, 2023 Опубликовано 8 октября, 2023 (изменено) · Жалоба собственно на одном из приборов, с которого длительно пишется информация в скаду, проявился дефект. Прибор имеет 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); } } //------------------------------------------------------------------------------- Изменено 8 октября, 2023 пользователем haker_fox К названию темы добавил модель МК. Длинный код спрятал под спойлер. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
tonyk_av 31 8 октября, 2023 Опубликовано 8 октября, 2023 · Жалоба В старших СТМ32 есть флаги ошибок работы DMA. Может, и в младших есть. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
jcxz 184 8 октября, 2023 Опубликовано 8 октября, 2023 · Жалоба 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); } Ужас!!! Видимо автор сего безобразия никогда не заглядывает ни в листинги ни в отладчик.... Если DMA используете в режиме: 2 часа назад, Михась сказал: DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; То на кой при каждом "Получении всех параметров из кода АЦП" вызывать ADC_StartOfConversion(ADC1) ??? Чтобы в ней не находилось (кода которой мы не видим). "Циклический режим" - это когда DMA работает сам по себе. Непрерывно. Данные из заполненного DMA-буфера при этом программа, как правило, читает в ISR. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Михась 2 8 октября, 2023 Опубликовано 8 октября, 2023 · Жалоба Спасибо за замечания. Будем изживать недостатки. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 131 8 октября, 2023 Опубликовано 8 октября, 2023 · Жалоба Почему adc_raw не volatile? 3 часа назад, jcxz сказал: То на кой при каждом "Получении всех параметров из кода АЦП" вызывать ADC_StartOfConversion(ADC1) ??? DMA-то да, перезапускать не надо. Вот только АЦП может после N преобразований скинуть бит активации и ждать следующего "пинка". Я не вдавался в подробности исходника ТС, возможно у него так (полагаться на простыни SPL/HAL не хочу). Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Михась 2 8 октября, 2023 Опубликовано 8 октября, 2023 · Жалоба 48 minutes ago, Arlleex said: Почему adc_raw не volatile? DMA-то да, перезапускать не надо. Вот только АЦП может после N преобразований скинуть бит активации и ждать следующего "пинка". Я не вдавался в подробности исходника ТС, возможно у него так (полагаться на простыни SPL/HAL не хочу). На сколько помню, так и есть. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
jcxz 184 8 октября, 2023 Опубликовано 8 октября, 2023 · Жалоба 1 час назад, Arlleex сказал: DMA-то да, перезапускать не надо. Может и не надо, но что именно делает ТС в ADC_StartOfConversion()? он не показал. Есть подозрение, что он там возможно некорректно останавливает и перезапускает DMA. Возможно. Вобщем - опять угадайка. Так как бОльшая часть кода скрыта. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Михась 2 8 октября, 2023 Опубликовано 8 октября, 2023 · Жалоба Это библиотечная функция. void ADC_StartOfConversion(ADC_TypeDef* ADCx) { ADCx->CR |= (uint32_t)ADC_CR_ADSTART; } В общем, проблема, как я думаю, не в DMA. Будем смотреть дальше, может в скаде. Потому как просто сдвиг, это понятно. А сдвиг по кольцу - это уже не понятно. Остальной код - это про другое. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
EdgeAligned 53 8 октября, 2023 Опубликовано 8 октября, 2023 · Жалоба А что по этому поводу говорит референс-мануал микроконтроллера? Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться