Продолжаем разбираться с периферией контроллеров семейства STM32F3 и в этой статье речь пойдет об использовании аналого-цифрового преобразователя (ADC) в связке с модулем прямого доступа к памяти (DMA). Кстати, особенностью микроконтроллеров STM32F3 являются как раз-таки продвинутые аналоговые модули – быстрый 12-битный АЦП (0.2 мкс) последовательного приближения и точные 16-битные сигма-дельта АЦП.
Из основных функций АЦП в STM32F3:
- целых 4 периферийных модуля ADC, причем у каждого по 10-15 внешних каналов и по парочке внутренних )
- быстрое время преобразования – 0.2 мкс, причем это время не зависит от тактовой частоты шины AHB, на которой висят АЦП
- функция самокалибровки
- море всяких режимов работы – там и последовательные преобразования и запуск преобразования от внешнего источника и еще куча всяких вариантов 😉
На самом деле, это все есть в даташите, а нас больше интересует практика и примеры программ ) Мы, как и всегда, будем использовать библиотеку Standard Peripheral Library, соответственно нужны файлы:
stm32f30x_adc.h stm32f30x_adc.c stm32f30x_dma.h stm32f30x_dma.c
В SPL все реализовано точно также, как и для любой другой периферии – в .c файлах функции для настройки и работы с периферией, в .h – объявления специальных структур и переменных. Все как мы уже привыкли 🙂
Давайте теперь настроим модуль ADC1 так, чтобы он производил непрерывные преобразования одно за другим и результат при помощи DMA записывался в специально для этого созданную переменную. Для реализации этого нам нужно выяснить какой из каналов DMA нужно использовать:
Как видно из схемы, нас интересует первый канал DMA. Осталось только определиться какой канал АЦП мы задействуем… Пусть будет, например третий канал. Почему именно третий? Да просто так 🙂 Из даташита узнаем, что 3-ий канал ADC1 – это вывод PA2 нашего микроконтроллера.
Приступаем к реализации! Необходимые файлы:
/***************************************************************************************/ #include "stm32f30x_adc.h" #include "stm32f30x_dma.h" #include "stm32f30x_gpio.h" #include "stm32f30x_rcc.h" #include "stm32f30x_misc.h" #include "stm32f30x.h" /***************************************************************************************/
Объявляем переменные:
/***************************************************************************************/ GPIO_InitTypeDef GPIO_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; ADC_InitTypeDef ADC_InitStructure; DMA_InitTypeDef DMA_InitStructure; ADC_CommonInitTypeDef ADC_CommonInitStructure; uint32_t ADC_Result; /***************************************************************************************/
Надо произвести инициализацию всей периферии, которую мы будем использовать:
/***************************************************************************************/ void initialization() { // Включаем тактирование DMA1, ADC12 и GPIOA RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_ADC12, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); // Настраиваем пин на работу в режиме аналогового входа GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN; GPIO_Init(GPIOA, &GPIO_InitStructure); // Настройки DMA DMA_DeInit(DMA1_Channel1); // Данные будем брать из регистра данных ADC1 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(ADC1->DR); // Переправлять данные будем в переменную ADC_Result DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADC_Result; // Передача данных из периферии в память DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // Размер буфера DMA_InitStructure.DMA_BufferSize = 1; // Адрес источника данных не инкрементируем - он всегда один и тот же DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // Аналогично, и с памятью DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable; // Настройки размера данных 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); // Включаем первый канал DMA1 DMA_Cmd(DMA1_Channel1, ENABLE); // Настраиваем тактирование АЦП RCC_ADCCLKConfig(RCC_ADC12PLLCLK_Div2); ADC_StructInit(&ADC_InitStructure); // Калибровка АЦП ADC_VoltageRegulatorCmd(ADC1, ENABLE); ADC_SelectCalibrationMode(ADC1, ADC_CalibrationMode_Single); ADC_StartCalibration(ADC1); // Настраиваем непрерывные преобразования ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent; ADC_CommonInitStructure.ADC_Clock = ADC_Clock_AsynClkMode; ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; ADC_CommonInitStructure.ADC_DMAMode = ADC_DMAMode_OneShot; ADC_CommonInitStructure.ADC_TwoSamplingDelay = 0; ADC_CommonInit(ADC1, &ADC_CommonInitStructure); // Включаем работу ДМА через АЦП ADC_DMACmd(ADC1, ENABLE); ADC_DMAConfig(ADC1, ADC_DMAMode_Circular); while(ADC_GetCalibrationStatus(ADC1) != RESET ); // Продолжается настройка АЦП ADC_InitStructure.ADC_ContinuousConvMode = ADC_ContinuousConvMode_Enable; ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; ADC_InitStructure.ADC_ExternalTrigConvEvent = ADC_ExternalTrigConvEvent_0; ADC_InitStructure.ADC_ExternalTrigEventEdge = ADC_ExternalTrigEventEdge_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_OverrunMode = ADC_OverrunMode_Disable; ADC_InitStructure.ADC_AutoInjMode = ADC_AutoInjec_Disable; ADC_InitStructure.ADC_NbrOfRegChannel = 1; ADC_Init(ADC1, &ADC_InitStructure); // Включаем третий канал первого модуля АЦП ADC_RegularChannelConfig(ADC1, 3, 1, ADC_SampleTime_7Cycles5); // Наконец-то включаем АЦП ADC_Cmd(ADC1, ENABLE); while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_RDY)); ADC_StartConversion(ADC1); ADC_Result = ADC_GetConversionValue(ADC1); } /***************************************************************************************/
Осталось написать функцию main():
/***************************************************************************************/ int main(void) { initialization(); while(1) { } } /***************************************************************************************/
В теле главной функции вызываем нашу написанную функцию инициализации, а в цикле while(1) пусто – то есть процессор свободен, всю работу взял на себя модуль DMA! Если прошить программу в контроллер в отладчике можно увидеть, что значение переменной ADC_Result меняется в соответствии с уровнем сигнала на выводе PA2.
На этом заканчиваем сегодняшнюю статью, до скорых встреч 🙂