PROGCONT.RU

Форма входа







Регистрпция Вход/

STM32f030 а также контроллеры серии STM32F031 настройка использования аналого-цифрового преобразователя(ADC) с помощью SPL, а также применение аналогового монитора(Analog window watchdog).

Ссылка на видео в YouTubeСтатья для контроллеров моделей STM32F030 и STM32F031 но также возможно подойдёт для контроллеров всей серии STM32F0xx(xx это любые цифры в модели).

Первым делом нужно разобраться с тактированием аналого-цифрового преобразователя далее просто ADC, которое не должно превышать частоты 14MHz согласно datasheet, но для более стабильной работы может может быть уменьшена.


NO


Согласно схемы тактирования у контроллеров имеется специальный высокочастотный генератор(HSI14) для использования только с ADC в асинхронном режиме(частота ADC не синхронизирована с частотой контроллера), с которым ADC будет работать на предельной частоте 14MHz. Второй вариант тактирования от шины PCLK через делитель на 2 или 4.

Теперь как происходит преобразование аналогового сигнала в цифровое значение, сам процесс состоит из двух стадий это захват и преобразование плюс задержки, смотри ниже.

Захват может происходит за минимальное время 1.5 такта ADC(можно увеличивать для получения более точного результата) преобразование всегда происходит за 12.5 такта ADC, в итоге если ADC тактируется на максимальной частоте 14MHz частота готовых преобразований будет 1MHz.

NO


Задержка перед стартом(1) tlatency(2) и задержка Wlatency(3) для сохранения готового значения в регистре ADC_DR, после которой готовый результат можно прочесть.

Таблица от чего зависят WLATENCY и tLATENCY

NO


Таблица зависимости скорости преобразования от разрешения результата, где показано, что при двенадцати битном результате можно производить сканирование на скорости 1/1000ns=1MHz а при результате шесть бит скорость сканирования при тех же условиях возрастёт до 1/643ns=1.555210MHz.

NO


Перейдём к практике, первый самый простой пример, запускаем ADC, тактирование от HSI14, сканирование вывода PORTA GPIO_Pin_0 и считываем результат готового преобразования из регистра ADC_DR.

#include "stm32f0xx.h" /*Переменная для результата сканирования*/ uint16_t ADC_result; int main(){ RCC_DeInit(); /*Настраиваем порт сканирования*/ RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_A; GPIO_A.GPIO_Pin = GPIO_Pin_0 ; GPIO_A.GPIO_Mode = GPIO_Mode_AN; GPIO_A.GPIO_PuPd = GPIO_PuPd_NOPULL ; GPIO_Init(GPIOA, &GPIO_A); /*Настройка ADC*/ ADC_InitTypeDef ADC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); ADC_DeInit(ADC1); /*Настраиваем тактирование от HSI14, асинхронный режим*/ ADC_ClockModeConfig( ADC1, ADC_ClockMode_AsynClk); /*Результата сканирования 12бит, постоянный захват, внешний тригер отсутствует, данные сдвигаются в право, сканирование портов от меньшего к большему по нумерации*/ ADC_StructInit(&ADC_InitStructure); ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; 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); /*Сканируется нулевой канал, захват за 1.5 цикла*/ ADC_ChannelConfig(ADC1, ADC_Channel_0 , ADC_SampleTime_1_5Cycles); /*Калибровка ADC и запуск сканирование*/ ADC_GetCalibrationFactor(ADC1); ADC_Cmd(ADC1, ENABLE); while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_ADRDY)); ADC_StartOfConversion(ADC1); while(1){ /*Вынимаем готовый результат из ADC*/ ADC_result= ADC_GetConversionValue(ADC1); } return 0; }


Пример работы ADC с DMA, контроллер будет сканировать порт GPIOA вывод 0 и 1 и через DMA сохранять результат в массив. Всё будет происходить в автоматическом режиме, вам останется только проверять готовые результаты сохранённые в массиве. Обязательно настраиваем контроллер для работы на максимальной частоте 48MHz, что бы DMA успел скопировать данные из ADC1_DR, иначе система может зависнуть или будет работать не корректно и ещё подключение к DMA ADC должно выполнятся сразу после функции ADC_GetCalibrationFactor(ADC1); как у меня в коде программы.

#include "stm32f0xx.h" /*Адрес ADC1_DR, откуда DMA будет вынимать результата сканирования*/ #define ADC1_DR_Address 0x40012440 /*Две переменные куда будем читать из DMA два результата преобразования ADC*/ uint16_t ADC_result_A0; uint16_t ADC_result_A1; /*Резервируем пространство в памяти в виде массива для DMA*/ uint16_t DMA_data[2];//Данные в сохранёные в DMA int main(){ RCC_DeInit(); /*Включаем и настраиваем буфер бодгрузки для процессора*/ FLASH_PrefetchBufferCmd( ENABLE); FLASH_SetLatency( FLASH_Latency_1); /*Подклчаем множитель тактовой частоты к делителю на два от HSI, настраиваем множитель как 12*/ RCC_PLLConfig( RCC_PLLSource_HSI_Div2, RCC_PLLMul_12);//8MHz=4*12=48MHz. RCC_PLLCmd( ENABLE);//Доступ к PLL разрешен. while( RCC_GetFlagStatus( RCC_FLAG_PLLRDY)== RESET);//Ждем пока PLL будет готов. RCC_SYSCLKConfig( RCC_SYSCLKSource_PLLCLK); //Выбираем PLL как источник частоты. /*Ждём когда система тактирования заработает устойчиво на частоте 48MHz и станет источником*/ while( RCC_GetSYSCLKSource()!= 0x08); /*Настройка DMA*/ RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1 , ENABLE); DMA_DeInit(DMA1_Channel1); DMA_InitTypeDef DMA_InitStructure; DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADC1_DR_Address; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)DMA_data; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = 2; 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_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel1, &DMA_InitStructure); DMA_Cmd(DMA1_Channel1, ENABLE); /*Настройка порта и его выводов для ADC*/ RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_A; GPIO_A.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; GPIO_A.GPIO_Mode = GPIO_Mode_AN; GPIO_A.GPIO_PuPd = GPIO_PuPd_NOPULL ; GPIO_Init(GPIOA, &GPIO_A); /*Настройка ADC*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); ADC_InitTypeDef ADC_InitStructure; ADC_DeInit(ADC1); ADC_ClockModeConfig( ADC1, ADC_ClockMode_AsynClk); ADC_StructInit(&ADC_InitStructure); ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; 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); /*Настройка каналов ADC*/ ADC_ChannelConfig(ADC1, ADC_Channel_0 , ADC_SampleTime_1_5Cycles); ADC_ChannelConfig(ADC1, ADC_Channel_1 , ADC_SampleTime_1_5Cycles); ADC_GetCalibrationFactor(ADC1); /*Подключаем DMA к ADC*/ ADC_DMARequestModeConfig(ADC1, ADC_DMAMode_Circular); ADC_DMACmd(ADC1, ENABLE); /*Запускаем ADC*/ ADC_Cmd(ADC1, ENABLE); while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_ADRDY)); ADC_StartOfConversion(ADC1); while(1){ /*Вынимаем результат вывода 0 и следом из вывода 1 */ ADC_result_A0= DMA_data[0]; ADC_result_A1= DMA_data[1]; } return 0; }


Последний пример использования функции аналогового монитора(Analog window watchdog) в ADC, ниже на рисунке демонстрация его работы с примерами расчёта нижнего и высокого порога срабатывания прерывания, для моего примера где используется двенадцати битное преобразование ADC. Тактирование необходимо настраивать на максимальную частоту 48MHz работы контроллера, иначе возможны зависания программы в прерывании.

Не настраивайте работу ADC на максимальной частоте, иначе будет зависание контроллера при входе в низкую или высокую зону установленных параметров аналогового монитора, из за прерывания. NO#include "stm32f0xx.h" /*Переменная для проверки ADC, на всякий случай*/ uint16_t ADC_result_A0; /*Высокое и низкое контролируемые значения*/ uint16_t ADC_max = 3102; uint16_t ADC_min = 1861; int main(){ RCC_DeInit(); FLASH_PrefetchBufferCmd( ENABLE); FLASH_SetLatency( FLASH_Latency_1); RCC_PLLConfig( RCC_PLLSource_HSI_Div2, RCC_PLLMul_12);//8MHz=4*12=48MHz. RCC_PLLCmd( ENABLE); while( RCC_GetFlagStatus( RCC_FLAG_PLLRDY)== RESET); RCC_SYSCLKConfig( RCC_SYSCLKSource_PLLCLK); while( RCC_GetSYSCLKSource()!= 0x08); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_A; GPIO_A.GPIO_Pin = GPIO_Pin_0; GPIO_A.GPIO_Mode = GPIO_Mode_AN; GPIO_A.GPIO_PuPd = GPIO_PuPd_NOPULL ; GPIO_Init(GPIOA, &GPIO_A); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); ADC_InitTypeDef ADC_InitStructure; ADC_DeInit(ADC1); ADC_ClockModeConfig( ADC1, ADC_ClockMode_SynClkDiv2); ADC_StructInit(&ADC_InitStructure); ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; 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); ADC_ChannelConfig(ADC1, ADC_Channel_0 , ADC_SampleTime_239_5Cycles);//Преобразование выполняется за 239.5+12.5=252 цикла тактирования ADC /*Установка значений для контроля от 1.5 до 2.5V(1861, 3102)*/ ADC_AnalogWatchdogThresholdsConfig(ADC1, ADC_max, ADC_min); /*Режим для одного для контроля одного вывода*/ ADC_AnalogWatchdogSingleChannelCmd(ADC1, ENABLE); /*Постоянная загрузка нового преобразования в ADC_DR, не зависимо прочтено ли старое*/ ADC_OverrunModeCmd(ADC1, ENABLE); /*Включаем analog watchdog*/ ADC_AnalogWatchdogCmd(ADC1,ENABLE); /*Устанавливаем контролируемый канал ADC для analog watchdog*/ ADC_AnalogWatchdogSingleChannelConfig(ADC1, ADC_AnalogWatchdog_Channel_0); /*Настраиваем прерывание analog watchdog*/ ADC_ITConfig(ADC1, ADC_IT_AWD, ENABLE); NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = ADC1_COMP_IRQn; NVIC_InitStructure.NVIC_IRQChannelPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); ADC_GetCalibrationFactor(ADC1); ADC_Cmd(ADC1, ENABLE); while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_ADRDY)); ADC_StartOfConversion(ADC1); while(1){ ADC_result_A0=ADC_GetConversionValue(ADC1); } return 0; } void ADC1_COMP_IRQHandler(void){ if(ADC_GetITStatus(ADC1, ADC_IT_AWD) != RESET){ if(ADC_GetConversionValue(ADC1) > ADC_max){ /*Здесь делаем, что то если значение больше установленного максимума */ }else{ /*Здесь делаем, что то если значение меньше установленного минимума*/ } ADC_ClearITPendingBit(ADC1, ADC_IT_AWD); } }


Функция для смены верхнего и нижнего порога аналогового монитора во время работы ADC, что бы не пропустить все конвертации.

void Watchdog_thresholds_set( uint16_t HT, uint16_t LT){ ADC_StopOfConversion( ADC1 ); while(ADC_GetFlagStatus(ADC1, ADC_FLAG_ADSTART)); ADC_AnalogWatchdogThresholdsConfig(ADC1, HT, LT); ADC_StartOfConversion( ADC1 ); }


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

ADC_AnalogWatchdogThresholdsConfig(ADC1, HT, LT);

Комментариев нет  Только зарегистрированные пользователи могут оставлять комментарии!