STM32 и ADC. STM32CubeMx. Настройка и использование АЦП.

АЦП в STM32

Возвращаемся к теме STM32CubeMx и на очереди работа с аналого-цифровым преобразователем. По традиции, создадим проект при помощи Cube, в котором произведем настройку модуля STM32 ADC.

Давайте начнем с постановки задачи. Будем измерять аналоговый сигнал на выводе PA2 микроконтроллера, получать оцифрованное значение и зажигать светодиоды в соответствии с этим значением. Поскольку АЦП в STM32 может измерять входные сигналы в диапазоне от 0 до 3.3 В, то зададим следующие интервалы:

  • 0 В < U < 1 В – горит один светодиод.
  • 1 В < U < 2 В – горят два светодиода.
  • 2 В < U < 3 В – горят три светодиода.
  • 3 В < U – горят четыре светодиода.

Я буду использовать для этой задачи плату STM32F4Discovery, а на ней как раз-таки установлены 4 светодиода 🙂

Итак запускаем STM32CubeMx и создаем новый проект. Первый делом настраиваем порты ввода-вывода – четыре на работу в режиме выхода для светодиодов и вывод PA2 в качестве аналогового входа. Предлагаю использовать ADC1, поэтому PA2 у нас станет – ADC1_IN2:

Настройка АЦП в STM32CubeMx.

Готово! Переходим непосредственно к АЦП. В окне Pinout во вкладке Peripherals нужный нам канал уже включен автоматически:

STM32CubeMx.

Переходим на вкладку Configuration и видим там наш модуль ADC1 под меткой Analog:

Конфигурация АЦП.

Дважды нажимаем на ADC1 и в результате открывается окно расширенных настроек непосредственно модуля ADC. В этом окне мы просто настраиваем режим, который нам нужен:

Настройки модуля ADC.

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

Как и при работе с другими периферийными модулями, видим готовые функции инициализации, как для АЦП, так и для портов ввода-вывода. Но опять же, как и в предыдущих статьях, всю активную работу (а именно включение/выключение АЦП, изменение состояния светодиодов и т. д.) нам необходимо выполнить вручную. Объявляем переменную, в которую мы будем считывать значение преобразования:

/* USER CODE BEGIN PV */
uint32_t adcResult = 0;

/* USER CODE END PV */

Также определим пороговые значения, соответствующие разным уровням напряжения:

/* USER CODE BEGIN 0 */
#define ADC_0V_VALUE                                             0
#define ADC_1V_VALUE                                             1241
#define ADC_2V_VALUE                                             2482
#define ADC_3V_VALUE                                             3723

/* USER CODE END 0 */

Что это за числа? Сейчас разберемся. Результат АЦП у нас 12-битный, а значит максимальное значение равно 4095 (0b111111111111). То есть при напряжении, равном 3.3 В результат составит 4095. При напряжении 1 В получим значение преобразования: 1 * 4095 / 3.3 = 1241. Для 2 В соответственно будет 2482 и т. д.

В цикле while(1) в функции main() мы будем включать АЦП, ожидать результата преобразования, считывать результат, выключать АЦП и в зависимости от результата зажигать или гасить диоды. Вот как выглядит вся функция main():

int main(void)
{
	while(1)
	{
		HAL_ADC_Start(&hadc1);

		HAL_ADC_PollForConversion(&hadc1, 100);

		adcResult = HAL_ADC_GetValue(&hadc1);

		HAL_ADC_Stop(&hadc1);

		if ((adcResult > ADC_0V_VALUE) && (adcResult < ADC_1V_VALUE))
		{
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET);
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_RESET);
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_14, GPIO_PIN_RESET);
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_15, GPIO_PIN_RESET);
		}

		if ((adcResult > ADC_1V_VALUE) && (adcResult < ADC_2V_VALUE))
		{
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET);
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_SET);
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_14, GPIO_PIN_RESET);
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_15, GPIO_PIN_RESET);
		}

		if ((adcResult >= ADC_2V_VALUE) && (adcResult < ADC_3V_VALUE))
	{
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET);
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_SET);
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_14, GPIO_PIN_SET);
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_15, GPIO_PIN_RESET);
		}

		if (adcResult >= ADC_3V_VALUE)
		{
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET);
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_SET);
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_14, GPIO_PIN_SET);
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_15, GPIO_PIN_SET);
		}
	}
}

Компилируем проект, прошиваем в микроконтроллер и видим, что светодиоды меняют свое состояние в зависимости от напряжения на входе контроллера 🙂 Таким образом, мы успешно справились с задачей запуска аналого-цифрового преобразователя при помощи STM32CubeMx, поэтому на сегодня на этой позитивной ноте заканчиваем, до скорого!

Поделиться!

Подписаться
Уведомление о
guest
40 Комментарий
старее
новее большинство голосов
Inline Feedbacks
View all comments
Дмитрий
Дмитрий
5 лет назад

Доброго времени суток! А где находится описание функций (например HAL_GPIO_WritePin)? В spl библиотеках был chm файл с таким описанием.

Дмитрий
Дмитрий
5 лет назад

Нашел я этот файл, скачать его можно там же где и саму библиотеку. Например для F3 http://www.st.com/web/en/catalog/tools/PF260613
называется файл UM1786: Description of STM32F3xx HAL drivers.С HAL как-то все заморочено)

Сергей
Сергей
5 лет назад

Доброго времени суток!
Для одиночного измерения все прошло удачно!
А вот для двумерного массива А[16][1024] все не так гладко – мне нужно перед каждым измерением вставлять HAL_ADC_PollForConversion(&hadc, 100)?
но тогда время преобразования увеличивается в 3,2 раза…

Андрей
Андрей
4 лет назад

У меня на stm32f429 при таких настройках при каждом запуске АЦП в регистре SR взводится бит OVR (overrun).
При считывании данных АЦП этот бит не сбрасывается. Но при следующем запуске не появляется флаг готовности EOC. И соответственно функция ожидания вылетает с таймаутом. Флаг OVR сбрасываю закрытием АЦП после забора данных. Тогда работает.
Чем вызвано OVR пока мне не понятно..

Андрей
Андрей
4 лет назад

в описании на stm32f4xx вычитал, что если используются разовые измерения без DMA, то EOCS = 0. CubeMX -> ADCx Configuration -> End Of Conversion Selection = EOC flag at the end of all conversions. Так у меня все работает по примеру.

Николай
Николай
4 лет назад

Добрый день, а как запустить измерение на 4-5 каналах с помощью HAL??

Евгений
Евгений
4 лет назад

Сгенерил код для STM32L152, работает, но измеряет он только один раз, а как сделать, чтобы он мерил постоянно?

Евгений
Евгений
4 лет назад

…а еще лучше увидеть бы целиком файл main.c

Руслан
Руслан
4 лет назад

Скажите пожалуйста, а если в HAL_ADC_PollForConversion(&hadc, 0) ставить задержку 0 млс – это эквивалентно проверке флага EOC? И будет ли это экономить время?

Ефим
Ефим
4 лет назад

stm32 STM32F103C8T6 все заработало почти с первого раза (ошибся пином и один диод с полюслвкой) спасибо

Иван
Иван
4 лет назад

Столкнулся с подобной проблемой, однако на STM32L100. Решение – установил бит DDS в регистре CR2. Бит отвечает за выдачу запроса DMA после оцифровки последнего значения. В STM32F4xx тоже что-то подобное должно быть. Если этот бит снят, последний замер из пачки копируется DMA (по флагу EOC если установлен EOCS), однако запрос DMA не выдается и АЦП затирает последний результат первым из следующей пачки, после чего камень ругается на OVR.

Павел
Павел
4 лет назад

Что за супер короткие статьи? вы на рекорд идете, ни в одной статье нет полного описания рабочего кода одни огрызки

Павел
Павел
Reply to  Aveal
4 лет назад

Речь не о том что код из хол нужно тащить, а о том, что режимов АЦП куча, а здесь только самый простой без ДМА который итак можно потыкав пару минут запустить. Даешь народу одновременное считывание с 2х АЦП по событию таймера с сохранением в буфер через ДМА, а не чтение одной функцией. И так везде в этих статьях, то по уарту 10 букв передают и читают, то тут..

Павел
Павел
Reply to  Aveal
4 лет назад

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

Антон
Антон
4 лет назад

Здравствуйте . статьи очень полезные. в инете покопался ацп в режиме interleaved dual/trial не нашел пример . если есть возможности , может в кубе не большую статью организуете 🙂 буду очень благодарен 😉

Jensi
Jensi
4 лет назад

На ножку питание подавать с блока питания и всё? Если случайно подать, скажем, 5-10 вольт, ну мало ли переменник глюканул, что будет? Нарисовали бы в статью схему сразу, чтоб не было подобных вопросов.

Алексей
Алексей
3 лет назад

Добрый вечер.
Столкнулся вот с какой проблемой.
Использую ADC в режиме DMA (normal). Настраивал с помощью Cube. Регулярный канал считываю данные с пяти выводов. Заметил, что на значения канала влияет значение предыдущего канала. Например если изменяется значение 1 канала, то это влияет и на 2 канал( изменяются и его значения) . Подскажите в чем может быть проблема.

Алексей
Алексей
Reply to  Алексей
3 лет назад

Проблему решил увеличением SamplingTime при инициализации ADC.
Отчитываюсь. Вдруг кому то пригодится.

Слава Панас
Слава Панас
3 лет назад

У меня значение adcResult всегда в районе 1000 – 1060. Горит только одна комбинация светодиодов. А как оно может меняться?

Сабр
Сабр
3 лет назад

а как сделать несколько измерений ацп можете помочь нигде не могу найти

Никита
3 лет назад

А чего это картинку спёрли мою из моего блога?

Никита
Reply to  Aveal
3 лет назад

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

Алексей
Алексей
3 лет назад

Добрый день!
Подскажите, пожалуйста, если не останавливать АЦП, и опрашивать,скажем, раз в секунду, будут ли корректно обновляться данные adcResult ?

Dimaster
Dimaster
3 лет назад

Вопрос уважаемому автору (не знал, куда еще написать). Необходимо внедрить в проекты вычисления с фиксированной точкой. Где их для STM32 взять? Для того же TI имеется специальная библиотека. Тут же сколько не лазил, нашел только кривую CMSIS DSP Lib, которая заточена чисто под вектора. Подскажите, где искать?

Сергей
Сергей
2 лет назад

Здравствуйте. Подскажите, пожалуйста, продолжаю знакомиться с STM32 и с микроконтроллерами в целом, дошёл до DMA и появилось пара вопросов:

1) Почему для АЦП всегда не используют DMA, если это такой классный инструмент? Зачем предусмотрены ещё 2-а режима Polling и Interrupt, для каких задач, если по сути всё можно сделать через DMA? Т.е. опрашивает он постоянно каналы и заносит данные в массив, а я когда мне удобно, раз в полсекунды/секунду вывожу значения на дисплей.

2) Что приводит в действие DMA, т.е. чьи ресурсы он расходует? Ведь опрашивать (переносить значения в память), например, АЦП с такой частотой это же какие ресурсы нужны, за счёт чего это достигается, если мы не задействуем процессор?

Как Вы думаете, для опроса 2-3 каналов АЦП раз в секунду/полсекунды более рационально будет воспользоваться таймером с прерываниями или всё также через DMA? Что будет больше нагружать МК, DMA со своими тысячами опросов в секунду или разницы особой не будет?

Сергей
Сергей
Reply to  Aveal
2 лет назад

Понял, спасибо
А что в настройках АЦП означают значения в “External Trigger Conversion Source”, например, “Timer1 Capture Compare 1 event”, т.е. какое событие должно произойти, чтобы запустился АЦП?
Значение “Regular Conversion launched by software” понятно, т.е. когда мы вызываем сами из программы, а вот другие значения, как я не искал в интернете и руководствах, не нашел что означают.

Присоединяйтесь!

Profile Profile Profile Profile Profile
Vkontakte
Twitter

Язык сайта

Июль 2020
Пн Вт Ср Чт Пт Сб Вс
« Июн    
 12345
6789101112
13141516171819
20212223242526
2728293031  

© 2013-2020 MicroTechnics.ru