STM32Cube. Использование АЦП (ADC).

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

АЦП в STM32

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

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

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

Итак запускаем STM32CubeMx и создаем новый проект.

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

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

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

STM32Cube

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

ADC

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

Настройки модуля АЦП

После установки всех параметров остается только нажать на кнопку генерации кода и вся дальнейшая работа будет связана только с полученным кодом. Итак, открываем проект, который создал для нас 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);
    }
  }
 
 
}

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

Понравилась статья? Поделись с друзьями!

STM32Cube. Использование АЦП (ADC).: 36 комментариев
  1. Доброго времени суток! А где находится описание функций (например HAL_GPIO_WritePin)? В spl библиотеках был chm файл с таким описанием.

    • Даже не знаю, есть ли тут аналог такого файла.. Я лично просто перед функцией смотрю описание, либо по коду функции.

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

    • Ну по сути да, в любом случае прежде, чем брать значение с АЦП нужно убедиться, что преобразование завершено. Для экономии времени можно вместо функции из HAL_Driver вручную проверять состояние бита EOC.

  3. У меня на 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. Так у меня все работает по примеру.

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

    • Проще всего сделать через ДМА — значение будет всегда обновляться без участия процессора.

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

    • Насколько я помню флаг EOC проверяется в любом случае — он и является критерием окончания преобразования. Параметр в функции — величина тайм-аута. Если к примеру передать в функцию 10 мс, то по истечению 10 мс функция вылетит с ошибкой тайм-аута. Это сделано для того, чтобы если вдруг по какой-то причине флаг не будет выставлен, программа не зависла навсегда на проверке, а вышла из функции. При нормальной работе АЦП флаг будет выставлен раньше, чем истечет тайм-аут.

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

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

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

    • Абсолютно весь код НЕ сгенерированный Cube здесь присутствует. Если Вам интересно было бы, если бы я копировал код из готовых библиотек только ради увеличения количества этого кода, то я могу в почту покидать отрывки из HAL драйвера.

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

        • Ну курс то для начинающих, для тех кто заходит, читает про основной режим, реализует и все работает. Если впоследствии этим людям понадобится использовать другой режим, то они его аналогично через Cube и настроят.

          Если рассмотреть два варианта — статья про основной режим и статья про режим, который будут использовать 2-3 человека из 1000, то первый случай будет более интересен и полезен читателям. А если под каждого индивидуально делать статью под его задачу, то это никакого времени не хватит, хотя это было бы, конечно, хорошо.

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

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

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

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

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

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

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

    • Не из блога, а из Google, не «сперли», а скачал. Про Ваш блог ничего не знал и не знаю, про авторские права на картинке ничего не сказано.

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

    • ADC_Stop() можно не вызывать, просто перед преобразованием вызывать ADC_Start(). Можно запустить АЦП в связке с ДМА и актуальное значение будет аппаратно обновляться в нужной переменной.

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

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *