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).: 32 комментария
  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, не «сперли», а скачал. Про Ваш блог ничего не знал и не знаю, про авторские права на картинке ничего не сказано.

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

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