ADC (перевод из книги Mastering STM32)

Наконец я взялся и закончил новый перевод из книги Mastering STM32, на этот раз я взялся за главу про АЦП (ADC). Остальные мои переводы, можно также найти в блоге. Глава большая, так что пост длинный, поэтому вот вам содержание.

Содержание

12. Аналого-цифровое преобразование
   12.1 Введение в АЦП последовательного приближения SAR ADC
   12.2 Модуль HAL_ADC
      12.2.1 Режимы преобразования
         12.2.1.1 Один канал, однократный режим преобразования
         12.2.1.2 Несколько каналов, однократный режим преобразования
         12.2.1.3 Один канал, режим непрерывного преобразования
         12.2.1.4 Режим непрерывного преобразования нескольких каналов
         12.2.1.5 Режим преобразования инжектированных каналов
         12.2.1.6 Сдвоенные режимы
      12.2.2 Выбор канала
      12.2.3 Разрядность АЦП и скорость преобразования
      12.2.4 Аналого-цифровое преобразование в режиме опроса
      12.2.5 Аналого-цифровое преобразование в режиме прерывания
      12.2.6 Аналого-цифровое преобразование в режиме DMA
         12.2.6.1 Несколько преобразований одного канала в режиме работы с DMA
         12.2.6.2 Многократное не непрерывное преобразование в режиме DMA
         12.2.6.3 Непрерывное преобразование в режиме DMA
      12.2.7 Обработка ошибок
      12.2.8 Преобразования, управляемые таймером
      12.2.9 Преобразования, управляемые внешними событиями
      12.2.10 Калибровка АЦП
   12.3 Использование CubeMX для конфигурации АЦП

12. Аналого-цифровое преобразование

Наличие аналоговой периферии в микроконтроллерах является общепринятой практикой. В цифровую эпоху все еще очень много устройств, работающих с аналоговыми сигналами: датчики, потенциометры, преобразователи и аудио периферия — вот несколько примеров аналоговых устройств, которые вырабатывают переменное напряжение, которое, обычно, находится в определенном фиксированном интервале значений. Считывая данное напряжение, мы можем конвертировать его в числовое представление, которое может быть обработано прошивкой нашего микроконтроллера. К примеру, TMP36 довольно популярный датчик температуры, который генерирует напряжение, пропорциональное напряжению питания и температуре окружающей среды.

Все микроконтроллеры STM32 имеют в своем составе хотя бы один аналого-цифровой преобразователь (АЦП), который может получать несколько входных напряжений через специально предназначенные входа и конвертировать их в числовое представление. Данное входное напряжение сравнивается с хорошо известным фиксированным напряжением, известным как опорное напряжение или reference voltage, в англоязычной литературе. Опорное напряжение может либо браться из VDDA, либо, в микроконтроллерах с большим количеством выводов, для этого есть отдельный вывод VREF+, к которому можно подвести внешнее опорное напряжение от источника опорного напряжения. В большинстве микроконтроллеров STM32 представлен 12-битный АЦП. Некоторые имеют 16-битный АЦП, например из семейства STM32F3.

В отличие от другой периферии STM32, рассматриваемой далее, реализация АЦП может отличаться довольно сильно между различными семействами этих микроконтроллеров и даже в пределах одного семейства. По этой причине, в книге дано только лишь введение в его работу, оставляя на ответственность читателя более глубокий анализ в каждом конкретном случае.

Прежде чем начать рассмотрение функционала АЦП в STM32 и, связанный с ним API CubeHAL, было бы неплохо сделать небольшое введение в то, как данная периферия работает.

12.1 Введение в АЦП последовательного приближения SAR ADC

Почти во всех микроконтроллерах STM32, АЦП выполнено по схеме 12-битного АЦП последовательного приближения (на момент написания данной книги появилось семейство STM32F37x, в котором представлен более прецизионный 16-битный сигма-дельта АЦП. В данной книге он не рассматривается. Тем не менее, функции HAL, используемые в данном случае, имеют такую же организацию). В зависимости от типа и используемого корпуса, микроконтроллер может иметь различное количество мультиплексированных входных каналов (обычно более 10 в большинстве МК), позволяющих измерять сигналы от внешних источников. Кроме того, доступны также несколько внутренних каналов: канал внутреннего температурного сенсора (Vsense), канал для внутреннего опорного напряжения (Vrefint), канал для мониторинга внешнего напряжения питания батареи (Vbat) и в некоторых случаях канал для мониторинга напряжения питания LCD в микроконтроллерах с интерфейсом подключения монохромного ЖК-дисплея (например, STM32L053 один из таких). Более того, АЦП, реализованный в STM32F3 и большинстве микроконтроллеров STM32L4, также может похвастаться поддержкой полностью дифференциальных входов. В Таблице 1 представлен полный список по количеству периферии АЦП и источникам входных сигналов всех микроконтроллеров STM32, которые имеются в составе 16 плат Nucleo, рассматриваемых в данной книге.

Таблица 1: Наличие АЦП периферии в микроконтроллерах STM32 в платах Nucleo

Преобразование различных каналов может выполняться в одноканальном (single), многоканальном (scan), непрерывном (continuous) или прерывистом (discontinuous) режимах. Результат работы АЦП записывается в 16-битный регистр данных с левым или правым выравниванием. Кроме того, АЦП также может работать в качестве аналогового сторожевого таймера, который позволяет приложению определять момент, когда входное напряжение выходит за рамки определенного пользователем, в случае, если это происходит, генерируется прерывание.

Рисунок 1: Упрощенная структура АЦП

На Рисунке 1 показана блок-схема структуры АЦП. Блок выбора канала и контроля сканирования выполняет выборку источника входного напряжения для АЦП. В зависимости от режима преобразования (single, scan или continuous), этот блок автоматически переключает входные каналы, так что каждый из них может быть считан с определенным периодом. Выход данного блока подключен к АЦП.

На Рисунке 1 также можно увидеть еще один важный блок АЦП: блок контроля запуска и остановки. Его роль заключается в контроле процесса АЦП, он может переключаться как программно, так и по событиям от некоторых входных источников. Кроме того, внутренне он подключен к линии TRGO нескольких таймеров, поэтому может использовать преобразования по таймеру в режиме DMA. Мы рассмотрим этот важный режим немного позже.

Рисунок 2: Внутренняя структура SAR ADC

Рисунок 2 иллюстрирует основные блоки, формирующие SAR ADC блок из Рисунка 1. На Рисунке 1 можно было заметить коммутатор и конденсатор последовательно со входом АЦП. Данный узел представлен на Рисунке 2 как блок Выборки и хранения (Sample-and-Hold SHA), который присутствует во всех АЦП. Этот узел играет важную роль в сохранении входного сигнала неизменным на время всего цикла преобразования. Благодаря внутреннему блоку таймингов, который регулируется настраиваемым тактированием, как мы увидим позднее, АЦП непрерывно подключает/отключает источник сигнала путем открытия/закрытия коммутатора с Рисунка 1. Для сохранения уровня напряжения входа постоянным SHA узел выполняется в связке с конденсатором: это гарантирует некоторое постоянное значение входного сигнала на время аналого-цифрового преобразования, которое зависит от выбранного периода преобразования.

Выход блока выборки и хранения (SHA) подключен к компаратору, который сравнивает его с другим сигналом, источником которого является внутренний цифро-аналоговый преобразователь (DAC). Результат сравнения отправляется в блок Логики (Logic unit), который вычисляет числовое представление входного сигнала в соответствии с хорошо-известным алгоритмом. Этот алгоритм это то, что отличает АЦП последовательного приближения (SAR) от других аналого-цифровых преобразователей.

Алгоритм последовательного приближения вычисляет напряжение входного сигнала путем его сравнения с генерируемым от внутреннего ЦАП: если входной сигнал больше, чем это внутреннее опорное напряжение, то происходит дальнейшее увеличение этого опорного напряжения пока входной сигнал не станет меньше него. Окончательный результат соотносится с числовым представлением в пределах от нуля до максимального значения 12 битного целого беззнакового числа, т.е. 212 − 1 = 4095. При условии, что VREF = 3300мВ, мы получаем, что 3300мВ представляется числом 4095. Это означает что 1 единица АЦП равно 3300/4095 = 0.8мВ. Для примера, пусть входной сигнал равен 2.5В, тогда он будет преобразован:

Алгоритм SAR работает следующим образом:

  1. Значение в выходном регистре данных зануляется и старший значащий бит (MSB) устанавливается в 1. Это соответствует хорошо известному уровню напряжения внутреннего ЦАП (половина опорного напряжения).
  2. Данное опорное напряжение ЦАП сравнивается с входным сигналом VIN:
    1. если VIN больше, то этот бит (MSB) остается в 1;
    2. если VIN меньше, то бит устанавливается в 0;
  3. Алгоритм продолжает свою работу со следующим MSB битом в регистре данных пока все его биты не будут в одном из двух состояний, 0 или 1.

На Рисунке 3 показан процесс преобразования в блоке логики SAR внутри 4-битного АЦП. Давайте рассмотрим путь, подсвеченный красным и предположим что VIN = 2700мВ, а VREF = 3300мВ. Алгоритм начинается с установки в 1 MSB бита, что соответствует 10002 = 810 . Это означает следующее:

Текущее значение VIN больше, чем 1760мВ, следовательно 4 бит остается равным 1 и алгоритм переходит к следующему MSB биту. Теперь значение в регистре данных равно 11002 = 1210 и ЦАП генерирует напряжение равное 2640 мВ. Текущее значение VIN все еще больше опорного и следовательно 3 бит также остается равным 1. Регистр устанавливается в значение 11102 = 1410, которое соответствует опорному напряжению 3080мВ. Теперь VIN меньше опорного напряжения и второй бит сбрасывается в 0. Теперь алгоритм установит в 1 первый бит, который установит значение опорного напряжения равным 2860мВ. Это значение все еще больше, чем VIN и алгоритм сбросит и этот бит в 0. Очевидно, что чем выше разрядность АЦП, тем более близкое значение к VIN может быть получено после преобразования.

Как вы можете видеть, алгоритм последовательного приближения по сути осуществляет поиск в бинарном дереве. Большое преимущество этого алгоритма в том, что преобразование выполняется за N циклов, где N зависит от разрядности АЦП. Таким образом 12-битный АЦП нуждается в 12 циклах для осуществления преобразования. Но как долго может продолжаться цикл? Количество циклов в секунду это частота АЦП, параметр, по которому оценивается его производительность. SAR АЦП может быть реально очень быстрым, особенно, если уменьшена разрядность АЦП (меньше выборок соответствует меньшему количеству циклов на одно преобразование). Однако, импеданс аналогового источника сигнала или последовательного сопротивления RIN между источником сигнала и выводом микроконтроллера, является причиной падения напряжения на нем из-за тока протекающего по этой цепи.

Рисунок 3: Процесс преобразования в SAR АЦП

Заряд внутреннего конденсатора в цепи СADC контролируется коммутатором на Рисунке 1, который имеет сопротивление, равное RADC. С дополнительным сопротивлением источника (RTOT = RADC + RIN) время, необходимое для полной зарядки конденсатора увеличивается. На Рисунке 4 показан эффект сопротивления аналогового источника сигнала. На эффективный заряд емкости СADC влияет RTOT, таким образом постоянная времени заряда конденсатора tC = (RADC + RIN) * СADC. Если время выборки меньше, чем время, необходимое для полной зарядки конденсатора СADC через сопротивление RTOT, цифровое значение, получаемое в результате преобразования будет меньше, нежели актуальное значение. В общем, необходимо выжидать время tC для получения приемлемой точности измерения.

Рисунок 4: Эффект вносимого сопротивления аналогового источника сигнала

Для высокой скорости преобразования важно принимать в расчет эффекты, вносимые печатной платой устройства и правильность развязки цепей питания в процессе проектирования платы. ST предоставляет отлично написанный appnote, который предлагает несколько важных техник для получения наилучшей производительности от встроенных АЦП микроконтроллеров STM32.

12.2 Модуль HAL_ADC

После короткого введения в наиболее важные возможности, предлагаемые периферией АЦП в микроконтроллерах STM32, самое время погрузиться в соответствующий CubeHAL API.

Для управления АЦП в HAL определена C структура ADC_HandleTypeDef, которая выглядит следующим образом:

typedef struct {
    ADC_TypeDef       *Instance;                   /* Указатель на ADC дескриптор */
    ADC_InitTypeDef    Init;                       /* Параметры инициализации ADC */
    __IO uint32_t      NbrOfCurrentConversionRank; /* Количество текущих преобразований в очереди */
    DMA_HandleTypeDef *DMA_Handle;                 /* Указатель на DMA обработчик */
    HAL_LockTypeDef    Lock;                       /* Lock объект ADC */
    __IO uint32_t      State;                      /* Состояние связи ADC */
    __IO uint32_t      ErrorCode;                  /* Коды ошибок */
} ADC_HandleTypeDef;

Давайте проанализируем наиболее важные поля данной структуры:

  • Instance: это указатель на дескриптор АЦП, который мы собираемся использовать. Для примера, ADC1 это дескриптор первого АЦП.
  • Init: объект структуры ADC_InitTypeDef, которая используется для конфигурации АЦП. Мы изучим ее позднее более подробно.
  • NbrOfCurrentConversionRank: соответствует текущему i-му каналу (rank) в регулярной группе преобразования. Также мы далее опишем этот параметр подробнее.
  • DMA_Handle: указатель на дескриптор DMA, конфигурируемый для обеспечения работы аналого-цифрового преобразования в режиме DMA или, по другому, прямого доступа к памяти. Он конфигурируется автоматически с помощью вызова __HAL_LINKDMA() макроса.

Настройка АЦП выполняется использованием структуры ADC_InitTypeDef, которая определена следующим образом:

typedef struct {
    uint32_t ClockPrescaler;         /* Выбор частоты тактирования ADC */
    uint32_t Resolution;             /* Настройка разрядности ADC */
    uint32_t ScanConvMode;           /* Установка режима сканирования каналов */
    uint32_t ContinuousConvMode;     /* Установка непрерывного или однократного режима преобразования */
    uint32_t DataAlign;              /* Установка левого или правого выравнивания данных в регистре данных */
    uint32_t NbrOfConversion;        /* Установка количества входов АЦП, которые будут обрабатываться в рамках группы регулярной последовательности */
    uint32_t NbrOfDiscConversion;    /* Установка количества прерывистых преобразований основной последовательности регулярных каналов */
    uint32_t DiscontinuousConvMode;  /* Установка режима преобразования регулярной последовательности в полную последовательность или прерывистую последовательность */
    uint32_t ExternalTrigConv;       /* Выбор внешнего события для запуска преобразования */
    uint32_t ExternalTrigConvEdge;   /* Выбор фронта внешнего триггера и включение его */
    uint32_t DMAContinuousRequests;  /* Установка режима работы DMA */
    uint32_t EOCSelection;           /* Установка EOC (End Of Conversion) флага в режиме опроса АЦП и прерывания */
} ADC_InitTypeDef;

Давайте проанализируем наиболее значимые поля этой структуры.

  • ClockPrescaler: определяет частоту тактирования (ADCCLK) для аналоговой части АЦП. В прошлом параграфе мы видели, что АЦП имеет внутренний блок таймингов, который контролирует частоту переключения входного коммутатора (см. Рисунок 2). ADCCLK устанавливает частоту этого блока таймингов и влияет на количество выборок в секунду, потому что она определяет общее время, затрачиваемое на каждый цикл преобразования. Этот сигнал тактирования генерируется программируемым делителем частоты тактирования периферии и разрешает работу на следующих частотах или fPCLK /2, /4, /6 или /8 (смотри даташит для конкретного микроконтроллера). В некоторых микроконтроллерах STM32 ADCCLK может быть установлен от HSI источника тактирования. Значение этого делителя влияет на скорость всех АЦП в конкретном микроконтроллере.
  • Resolution: кроме STM32F1 микроконтроллеров, в которых не разрешено выбирать разрядность выборок (см. Таблицу 1), данное поле дает возможность определить разрядность АЦП. Это поле может принимать значение из Таблицы 2. Чем выше разрядность, тем меньшее число преобразований в секунду может быть выполнено. Если скорость в вашем приложении не имеет значение, то рекомендуется установить максимальную разрядность АЦП и минимальную скорость преобразования.
  • ScanConvMode: это поле принимать только два значения ENABLE или DISABLE и используется для включения/отключения режима сканирования. Подробнее об этом далее.
  • ContinuousConvMode: определяет режим преобразования однократный или непрерывный и может принимать значения ENABLE или DISABLE. Также далее будет более подробная информация.
  • NbrOfConversion: устанавливает количество регулярных каналов, которые будут преобразованы в режиме сканирования.
  • DataAlign: определяет выравнивание результата преобразования. Регистр данных АЦП реализован как half-word регистр. Только 12 бит результата преобразования могут быть в него записаны, таким образом данный параметр устанавливает как именно это 12-битное значение будет размещено в регистре данных. Может принимать два значения ADC_DATAALIGN_LEFT или ADC_DATAALIGN_RIGHT.
  • ExternalTrigConvEdge: осуществляет выбор источника внешнего запуска АЦП.
  • EOCSelection: в зависимости от режима сканирования АЦП устанавливает соответствующий флаг завершения преобразования (EOC End Of Conversion). Это поле используется в режиме опроса АЦП или прерывания для определения, когда заканчивается преобразование, и данное поле может принимать значения ADC_EOC_SEQ_CONV для многоканального преобразования и ADC_EOC_SINGLE_CONV для одноканального преобразования.
Разрядность АЦПОписание
ADC_RESOLUTION_12BРазрядность АЦП 12 бит
ADC_RESOLUTION_10BРазрядность АЦП 10 бит
ADC_RESOLUTION_8BРазрядность АЦП 8 бит
ADC_RESOLUTION_6BРазрядность АЦП 6 бит

Прежде чем мы сможем рассмотреть практический пример, мы должны разобраться в еще двух темах: как конфигурируется входной канал и как осуществляется сэмплирование (выборки) входных сигналов.

12.2.1 Режимы преобразования

АЦП в микроконтроллерах STM32 имеет несколько режимов работы, которые могут использоваться в различных сценариях работы приложения. Сейчас мы познакомимся с наиболее значимыми из них: документ AN3116 от ST дает описание всех возможных режимов преобразования АЦП с описанием сценариев применения.

12.2.1.1 Один канал, однократный режим преобразования

Это самый простой режим работы АЦП. В этом режиме, АЦП осуществляет одно преобразование (одна выборка) одного канала, как показано на Рисунке 5, и завершает свою работу, когда преобразование завершено.

Рисунок 5: Один канал, одно преобразование

12.2.1.2 Несколько каналов, однократный режим преобразования

Этот режим также называют многоканальным в документации ST, используется для получения по одной выборке с каждого канала в независимом режиме. Используя rank каждого канала, можно задать любую последовательность до 16 каналов АЦП с различным временем преобразования и различной очередности. Для примера, вы можете выполнить последовательность преобразований, показанную на Рисунке 6. В данном случае, вы не можете остановить АЦП во время процесса преобразования для того, чтобы изменить время преобразования следующего канала. Этот режим дополнительно нагружает процессорное ядро и сложен программно. Режим сканирования лучше осуществлять в режиме DMA.

Рисунок 6: Несколько каналов, одно преобразование

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

12.2.1.3 Один канал, режим непрерывного преобразования

Этот режим позволяет преобразовывать один канал непрерывно и бесконечно долго в режиме преобразования регулярных каналов. Непрерывный режим работы позволяет АЦП работать фоново. АЦП преобразует значение одного канала без какого-либо вмешательства со стороны процессора. Также может быть использован DMA в циклическом режиме, таким образом нагрузка на процессорное ядро уменьшиться еще больше.

Рисунок 7: Режим непрерывного преобразования одного канала

Для примера, этот режим АЦП можно использовать для мониторинга напряжения батареи, измерении и регулировании температуры печи с использованием PID регуляторов и т.д.

12.2.1.4 Режим непрерывного преобразования нескольких каналов

Этот режим также называют многоканальным непрерывным режимом и он может использоваться для преобразования нескольких каналов в независимом режиме. Используя индексы каналов rank, вам доступна настройка любой последовательности из 16 каналов с различным временем выборок и очередностью преобразования. Этот режим похож на простой многоканальный режим с той лишь разницей, что преобразование не останавливается после преобразования последнего канала в последовательности регулярных каналов, а начинается по новой с самого первого канала и продолжается этот цикл бесконечно долго. В этом режиме также доступен DMA.

Рисунок 8: Режим непрерывного преобразования нескольких каналов

Использовать такой режим работы АЦП можно для мониторинга нескольких напряжений и температур в многоканальном зарядном устройстве. Напряжение и температура каждой батареи может считываться в процессе зарядки. Когда напряжение или температура достигают максимального уровня, соответствующая батарея отключается от зарядного устройства.

12.2.1.5 Режим преобразования инжектированных каналов

Этот режим предназначен для использования в режиме запуска преобразования по внешнему событию или программно. Группа инжектированных каналов имеет более высокий приоритет нежели группа регулярных каналов. Ее преобразование может прервать преобразование текущего канала в группе регулярных каналов.

Рисунок 9: Режим преобразования инжектированных каналов

Такой режим может использоваться для синхронизации преобразования с каким-то внешним событием. Может быть интересно использовать данный режим в приложениях контроля моторов, где ключевой транзистор создает шум, который может негативно сказаться на результатах измерения АЦП. Используйте таймер для включения режима инжектированных каналов, таким образом введя задержку преобразования в момент переключения транзистора.

12.2.1.6 Сдвоенные режимы

Сдвоенный режим доступен в STM32 как функция двух АЦП: ADC1 в качестве master и ADC2 slave. Триггеры ADC1 и ADC2 синхронизируются внутри для преобразований регулярных и инжектированных каналов. Оба АЦП работают вместе. В некоторых микроконтроллерах имеется до 3х АЦП: ADC1, ADC2 и ADC3. В этом случае ADC3 всегда работает независимо и не синхронизируется ни каким способом с другими АЦП.

Во время работы сдвоенного режима, когда завершается преобразование, его результат одновременно сохраняется от обоих АЦП ADC1 и ADC2 в 32-битном регистре данных ADC1. Разделение результатов позволяет нам получать данные от двух отдельных каналов в одно время.

Для более подробной информации можно обратиться к AppNote от ST AN3116.

12.2.2 Выбор канала

В зависимости от семейства STM32 и используемого корпуса, АЦП в микроконтроллерах STM32 могут преобразовывать сигналы от различного числа каналов. В семействах F0 и L0 расположение каналов фиксированное: первый всегда IN0, второй — IN1 и так далее. Пользователь лишь может принимать решение использовать или нет тот или иной канал. Это означает, что в режиме сканирования или многоканальном по другому, первым будет считан всегда канал IN0, вторым IN1 и так далее. Тогда как в других микроконтроллерах STM32 предлагается идея групп каналов. Группа содержит последовательность преобразований для любых каналов и в любой очередности. В то время как входные каналы фиксированы и привязаны к конкретным выводам микроконтроллера (IN0, IN1 и т.д.), в то же самое время они могут быть логически переназначены на любую очередность выборок. Переназначение каналов реализовано путем назначения каждому каналу индекса в диапазоне от 1 до 16. Этот индекс называется rank в CubeHAL и соответствует очередности преобразования канала в последовательности регулярных каналов.

Рисунок 10: Как входные каналы могут быть переназначены с использованием индекса rank

Рисунок 10 демонстрирует данный принцип. Несмотря на то, что канал IN4 фиксирован (здесь он соответствует выводу PA4 микроконтроллера STM32F401RE), он может быть логически назначен индексу rank 1 и станет первым каналом, из которого будут браться выборки. Микроконтроллеры, предлагающие данный функционал, также позволяют выбрать частоту выборок АЦП для каждого канала индивидуально, за исключением микроконтроллеров F0/L0, где конфигурация работает на весь АЦП в целом.

Конфигурация канал/индекс представлена структурой C ADC_ChannelConfTypeDef, которая определена следующим образом:

typedef struct {
    uint32_t Channel;        /* Канал, которому назначается индекс rank */
    uint32_t Rank;           /* Индекс очередности канала */
    uint32_t SamplingTime;   /* Время одной выборки для выбранного канала */
    uint32_t Offset;         /* Зарезервировано, может быть установлено в 0 */
} ADC_ChannelConfTypeDef;
  • Channel: устанавливает идентификатор канала. Может принимать следующие значения ADC_CHANNEL_0, ADC_CHANNEL_1ADC_CHANNEL_N, в зависимости от доступного числа каналов.
  • Rank: соответствует индексу очередности для конкретного канала. Может принимать значения от 1 до 16.
  • SamplingTime: устанавливает время одной выборки для канала и определяется числом тактов АЦП. Это число не может быть случайным и выбирается из списка конкретных значений. Как мы увидим позднее, CubeMX предлагает этот список допустимых значений в зависимости от конкретного микроконтроллера.

Существует две группы для каждого АЦП:

  • Регулярная группа, в ней может быть до 16 каналов, которые соответствуют последовательности сканируемых регулярных каналов АЦП.
  • Инжектированная группа, до 4х каналов, которые соответствуют последовательности инжектированных каналов, если выполняется инжектированное преобразование.

12.2.3 Разрядность АЦП и скорость преобразования

Можно выполнить преобразования быстрее, если уменьшить разрядность АЦП (это справедливо для все микроконтроллеров STM32, кроме STM32F1). Время одной выборки, по существу, определяется фиксированным числом циклов (всегда 3) плюс переменным числом циклов, которое зависит от разрядности АЦП. Минимальное время преобразования для каждой из разрядностей выглядит следующим образом:

  • 12 бит: 3 + ~12 = 15 циклов ADCCLK
  • 10 бит: 3 + ~10 = 13 циклов ADCCLK
  • 8 бит: 3 + ~8 = 11 циклов ADCCLK
  • 6 бит: 3 + ~6 = 9 циклов ADCCLK

Уменьшая разрядность, возможно увеличить максимальное число выборок в секунду, которое в некоторых микроконтроллерах STM32 может достигать 15Msps. Помните, что ADCCLK производно от тактирования периферии: это означает, что SYSCLK и PCLK сильно влияют на число выборок в секунду.

12.2.4 Аналого-цифровое преобразование в режиме опроса

Как и большинство периферии STM32, АЦП может работать в трех режимах: опрос, прерывание и режим DMA. Как мы увидим позднее, последним режимом можно управлять с помощью таймера, чтобы обеспечить регулярный интервал преобразований. Это очень полезно, если нам нужно получать выборки сигнала с фиксированной частотой, как скажем в приложениях, обрабатывающих звук.

Как только контроллер АЦП сконфигурирован с помощью экземпляра структуры ADC_InitTypeDef, передаваемого в функцию HAL_ADC_Init(), мы можем запустить работу АЦП функцией HAL_ADC_Start(). В зависимости от выбранного режима преобразования, АЦП преобразует сигналы каждого выбранного канала один раз или непрерывно: в этом случае, для повторного преобразования необходимо вызвать функцию HAL_ADC_Stop() перед повторным вызовом HAL_ADC_Start().

В режиме опроса мы будем использовать функцию

HAL_StatusTypeDef HAL_ADC_PollForConversion(ADC_HandleTypeDef* hadc, uint32_t Timeout);

для определения когда преобразование завершено и результат уже доступен в регистре данных АЦП. Эта функция принимает в качестве параметра указатель на дескриптор АЦП и значение таймаута, который определяет максимальное время в миллисекундах, которое мы можем ждать до завершения преобразования. В качестве последнего параметра можно передать параметр HAL_MAX_DELAY, чтобы ждать неопределенно долго.

Чтобы получить результат преобразования необходимо использовать функцию

uint32_t HAL_ADC_GetValue(ADC_HandleTypeDef* hadc);

Теперь мы наконец готовы рассмотреть полный пример работы. И начнем с рассмотрения API, используемого в режиме опроса. Как вы увидите, здесь не будет ничего нового, такого, что вы еще не видели в примерах работы с другими видами периферии.

Пример будет делать совершенно простою вещь: он будет использовать внутренний температурный сенсор, доступный во всех микроконтроллерах STM32 как источник для работы АЦП. Температурный сенсор уже подключен внутри к соответствующему каналу АЦП. Точный номер этого канала зависит от семейства микроконтроллера и корпуса. Тем не менее, HAL позволяет абстрагироваться от таких тонкостей проектирования. Прежде чем мы проанализируем реальный код, будет лучше, если мы быстро рассмотрим электрические характеристики температурного сенсора, которые описаны в даташите на микроконтроллер.

Таблица 3: Электрические параметры температурного сенсора в микроконтроллере STM32F401RE

В Таблице 3 показаны характеристики температурного сенсора в микроконтроллере STM32F401RE. У него типовая погрешность измерения параметра 1°C и наклон линейной характеристики 2.5мВ/°C. Также известно, что при 25°C падение напряжения равно 760мВ. Это означает, что для вычисления измеряемой температуры мы можем использовать формулу:

Следующий код показывает как использовать преобразование для получения значения от внутреннего датчика температуры в микроконтроллере STM32F401RE.

/* Private variables ---------------------------------------------------------*/
extern UART_HandleTypeDef huart2;
ADC_HandleTypeDef hadc1;

/* Private function prototypes -----------------------------------------------*/
static void MX_ADC1_Init(void);

int main(void) {

    HAL_Init();
    Nucleo_BSP_Init();

    /* Initialize all configured peripherals */
    MX_ADC1_Init();

    HAL_ADC_Start(&hadc1);

    while (1) {
        char msg[20];
        uint16_t rawValue;
        float temp;

        HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);

        rawValue = HAL_ADC_GetValue(&hadc1);
        temp = ((float)rawValue) / 4095 * 3300;
        temp = ((temp - 760.0) / 2.5) + 25;

        sprintf(msg, "rawValue: %hu\r\n", rawValue);
        HAL_UART_Transmit(&huart2, (uint8_t*) msg, strlen(msg), HAL_MAX_DELAY);

        sprintf(msg, "Temperature: %f\r\n", temp);
        HAL_UART_Transmit(&huart2, (uint8_t*) msg, strlen(msg), HAL_MAX_DELAY);
    }
}

/* ADC1 init function */
void MX_ADC1_Init(void) {
    ADC_ChannelConfTypeDef sConfig;

    /* Enable ADC peripheral */
    __HAL_RCC_ADC1_CLK_ENABLE();

    /* Configure the global features of the ADC (Clock, Resolution, Data Alignment and number
    of conversion) */
    hadc1.Instance = ADC1;
    hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2;
    hadc1.Init.Resolution = ADC_RESOLUTION_12B;
    hadc1.Init.ScanConvMode = DISABLE;
    hadc1.Init.ContinuousConvMode = ENABLE;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.NbrOfConversion = 1;
    hadc1.Init.DMAContinuousRequests = DISABLE;
    hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
    HAL_ADC_Init(&hadc1);

    /* Configure for the selected ADC regular channel its corresponding rank in the sequencer
    and its sample time. */
    sConfig.Channel = ADC_CHANNEL_TEMPSENSOR;
    sConfig.Rank = 1;
    sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);
}

Первая часть разбора данного примера будет касаться функции MX_ADC1_Init(), которая инициализирует ADC1. Прежде всего, строка 52 конфигурирует ADCCLK (тактирование аналоговой части АЦП) в половину значения PCLK, максимальная частота которого для микроконтроллера STM32F401RE равна 84МГц. Далее, разрядность АЦП устанавливается в максимальное значение 12 бит. Режим сканирования выключен (строка 54), тогда как непрерывный режим преобразования разрешен (строка 55), таким образом мы можем повторно опрашивать АЦП, не останавливая, а затем перезапуская его. По этой причине флаг EOC устанавливается в ADC_EOC_SEQ_CONV в строке 60. Заметьте, что параметр NbrOfConversion в строке 58 полностью бесполезен в данном случае, потому что режим одиночного преобразования автоматически предполагает, что количество преобразуемых каналов АЦП равно 1.

Строки [66:68] настраивают канал температурного сенсора и назначают ему индекс очередности rank 1: даже если мы не используем многоканальный режим преобразования, нам необходимо определить этот индекс для используемого канала. Время одной выборки устанавливается в 480 циклов: это означает то, что с учетом частоты тактирования 84МГц, ADCCLK, которая равна половине PCLK, мы получим время одной выборки АЦП, равное 10 мкс (ADCCLK = 48МГц, выполняет 48 циклов каждую 1 мкс, таким образом 480 циклов, деленные на 48 циклов/мкс дают нам 10 мкс).

Почему мы выбрали именно такое время преобразования? Причина находится в Таблице 3, которая дает нам параметр TS_temp период опроса датчика, равный 10 мкс, для получения заданной погрешности в 1°C. Для примера, если вы увеличите скорость преобразования до 3 циклов, путем установки параметра SamplingTime в ADC_SAMPLETIME_3CYCLES, вы увидите, что получаемый результат часто абсолютно не верен.

В этой же таблице можно найти другой интересный параметр: время запуска температурного сенсора (это время, необходимое для стабилизации выходного напряжения включенного сенсора), которое находится в пределах от 6 до 10 мкс. Тем не менее, нам не нужно беспокоиться о данном аспекте, т.к. функция HAL_ADC_ConfigChannel() реализована для корректного запуска преобразования значения температурного сенсора. Это означает, что функция автоматически осуществляет задержку в 10 мкс, чтобы дать установиться рабочему режиму сенсора.

Теперь можно рассмотреть функцию main(). Запустив в строке 21 ADC1, мы начинаем бесконечный цикл, который циклически опрашивает АЦП. Когда преобразование завершено, мы можем получить значение преобразования и применить к нему выражение для вычисления температуры в градусах Цельсия. Результат отправляется в UART2 интерфейс.

HAL_ADC модуль в CubeF1 HAL незначительно отличается от других. Для начала преобразования программным запуском рекомендуется настроить параметр hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START во время инициализации АЦП. Это совершенно отличается от того, что нужно делать в других HAL и не ясно почему разработчики ST оставили такое поведение. Более того, CubeMX предлагает различную конфигурацию данного специфичного момента в процессе генерации соответствующего кода инициализации, рассмотрим данный момент в конце главы.

12.2.5 Аналого-цифровое преобразование в режиме прерывания

Выполнение аналого-цифрового преобразования в режиме прерывания не сильно отличается от того, что мы видели ранее. Как всегда, мы определяем обработчик прерывания ISR соответствующего АЦП, назначаем приоритет прерывания и включаем соответствующий вектор прерывания IRQ. Как и в других периферийных устройствах, мы должны вызвать функцию HAL_ADC_IRQHandler() из обработчика прерывания ADC ISR и определить функцию обратного вызова HAL_ADC_ConvCpltCallback(), которая автоматически будет вызываться HAL, когда преобразование будет завершено. Наконец, все прерывания, которые соответствуют ADC, включаются вызовом функции HAL_ADC_Start_IT().

Следующий пример показывает как выполнить преобразование в режиме прерывания. Код инициализации АЦП точно такой же как и в предыдущем примере.

int main(void) {
    HAL_Init();
    Nucleo_BSP_Init();

    /* Initialize all configured peripherals */
    MX_ADC1_Init();

    HAL_NVIC_SetPriority(ADC_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(ADC_IRQn);

    HAL_ADC_Start_IT(&hadc1);

    while (1);
}

void ADC_IRQHandler(void) {
    HAL_ADC_IRQHandler(&hadc1);
}

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
    char msg[20];
    uint16_t rawValue;
    float temp;

    rawValue = HAL_ADC_GetValue(&hadc1);
    temp = ((float)rawValue) / 4095 * 3300;
    temp = ((temp - 760.0) / 2.5) + 25;

    sprintf(msg, "rawValue: %hu\r\n", rawValue);
    HAL_UART_Transmit(&huart2, (uint8_t*) msg, strlen(msg), HAL_MAX_DELAY);
    
    sprintf(msg, "Temperature: %f\r\n", temp);
    HAL_UART_Transmit(&huart2, (uint8_t*) msg, strlen(msg), HAL_MAX_DELAY);
}

12.2.6 Аналого-цифровое преобразование в режиме DMA

Наиболее интересный режим работы АЦП это в связке с DMA. Он позволяет производить преобразования без участия ядра микроконтроллера и, используя DMA в циклическом режиме, таким образом, что мы получим непрерывное преобразование последовательности каналов АЦП. Более того, как мы увидим далее по мере изучения, этот режим идеален для использования АЦП, запускаемого от таймера, что позволяет осуществлять преобразования с фиксированной частотой выборок. Также важно использовать DMA, когда нам необходимо осуществлять преобразования нескольких каналов в непрерывном режиме.

Для осуществления преобразования в режиме работы с DMA необходимо выполнить некоторый набор процедур, описанных ниже:

  • Настроить АЦП в соответствии с требуемым режимом работы.
  • Настроить DMA канал/поток в соответствии с используемым контроллером АЦП.
  • Связать DMA дескриптор с дескриптором ADC, используя макроопределение __HAL_LINKDMA().
  • Включить DMA и соответствующий ему IRQ.
  • Запустить АЦП в режиме DMA, используя функцию HAL_ADC_Start_DMA(), передавая ей в качестве аргумента массив для записи данных.
  • Будьте готовы получать событие EOC, заранее определив функцию обратного вызова HAL_ADC_ConvCpltCallback() (HAL также предоставляет функцию обратного вызова HAL_ADC_ConvHalfCpltCallback() для того, чтобы получать событие, когда половина последовательности преобразована).

Следующий пример, разработанный для запуска на микроконтроллере STM32F401RE, показывает однократное преобразование нескольких каналов с использованием DMA. Рассмотрим настройку периферии и DMA контроллера для начала.

/* ADC1 init function */
void MX_ADC1_Init(void) {
    ADC_ChannelConfTypeDef sConfig;

    /* Enable ADC peripheral */
    __HAL_RCC_ADC1_CLK_ENABLE();

    /**Configure the global features of the ADC
    */
    hadc1.Instance = ADC1;
    hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV8;
    hadc1.Init.Resolution = ADC_RESOLUTION_12B;
    hadc1.Init.ScanConvMode = ENABLE;
    hadc1.Init.ContinuousConvMode = DISABLE;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.NbrOfConversion = 3;
    hadc1.Init.DMAContinuousRequests = DISABLE;
    hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
    HAL_ADC_Init(&hadc1);

    /**Configure for the selected ADC regular channels */
    sConfig.Channel = ADC_CHANNEL_TEMPSENSOR;
    sConfig.Rank = 1;
    sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);

    sConfig.Channel = ADC_CHANNEL_TEMPSENSOR;
    sConfig.Rank = 2;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);

    sConfig.Channel = ADC_CHANNEL_TEMPSENSOR;
    sConfig.Rank = 3;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);
}

void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc) {
    if(hadc->Instance==ADC1) {
        /* Peripheral clock enable */
        __HAL_RCC_ADC1_CLK_ENABLE();

        /* Peripheral DMA init*/
        hdma_adc1.Instance = DMA2_Stream0;
        hdma_adc1.Init.Channel = DMA_CHANNEL_0;
        hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
        hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
        hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
        hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
        hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
        hdma_adc1.Init.Mode = DMA_NORMAL;
        hdma_adc1.Init.Priority = DMA_PRIORITY_LOW;
        hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
        HAL_DMA_Init(&hdma_adc1);

        __HAL_LINKDMA(hadc,DMA_Handle,hdma_adc1);
    }
}

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {

Функция MX_ADC1_Init() настраивает АЦП на однократное преобразование трех входных каналов. ADCCLK устанавливается в минимальное значение, установкой делителя (строка 56), и включается режим сканирования нескольких каналов (строка 58). Как можно заметить, АЦП сконфигурирован так, чтобы выполнять преобразование сигнала внутреннего температурного сенсора: это не очень практично, но к сожалению, платы Nucleo не предоставляют никакой другой аналоговой периферии для практики.

Функция HAL_ADC_MspInit() вызывается автоматически при вызове функции HAL_ADC_Init()(строка 65). В ней происходит конфигурация DMA2 Stream0/Channel0, потока который настроен на передачу данных из периферии в память, когда завершается преобразование АЦП. Очевидно, что последовательность преобразований сформирована путем назначения индекса rank каждому из каналов. В соответствии с размером регистра данных АЦП 16 бит, мы настраиваем DMA на передачу 16 бит half-word. Наконец, функция HAL_ADC_ConvCpltCallback() автоматически вызывается из обработчика прерывания DMA2_Stream0_IRQHandler() (в примере не показан), путем вызова функции HAL_DMA_IRQHandler(), когда завершается преобразование. Функция обратного вызова устанавливает глобальную переменную, используемую как флаг завершения преобразования.

extern UART_HandleTypeDef huart2;
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;
volatile uint8_t convCompleted = 0;

/* Private function prototypes -----------------------------------------------*/
static void MX_ADC1_Init(void);

int main(void) {
    char msg[20];
    uint16_t rawValues[3];
    float temp;

    HAL_Init();
    Nucleo_BSP_Init();

    /* Initialize all configured peripherals */
    MX_ADC1_Init();

    HAL_ADC_Start_DMA(&hadc1, (uint32_t*)rawValues, 3);

    while(!convCompleted);

    HAL_ADC_Stop_DMA(&hadc1);

    for(uint8_t i = 0; i < hadc1.Init.NbrOfConversion; i++) {
        temp = ((float)rawValues[i]) / 4095 * 3300;
        temp = ((temp - 760.0) / 2.5) + 25;

        sprintf(msg, "rawValue %d: %hu\r\n", i, rawValues[i]);
        HAL_UART_Transmit(&huart2, (uint8_t*) msg, strlen(msg), HAL_MAX_DELAY);

        sprintf(msg, "Temperature %d: %f\r\n",i, temp);
        HAL_UART_Transmit(&huart2, (uint8_t*) msg, strlen(msg), HAL_MAX_DELAY);
    }

Код выше демонстрирует функцию main(). В строке 26 производится запуск АЦП в режиме DMA, в функцию передается указатель на массив rawValues и количество преобразований, которое должно соответствовать параметру hadc1.Init.NbrOfConversion из строки 60 прошлого листинга. Наконец, когда переменная convCompleted устанавливается в 1, содержимое массива rawValues конвертируется и результат отправляется в UART2. Пожалуйста, обратите внимание, что функция HAL_ADC_Stop_DMA(), которая вызывается в строке 30, не выполняет остановку преобразования (оно выполняется автоматически после трех выборок сигнала), но выполняет правильный алгоритм использования АЦП в данном режиме (в противном случае новое преобразования не будет запускаться).

12.2.6.1 Несколько преобразований одного канала в режиме работы с DMA

Для получения заданного количества преобразований одного и того же канала (или последовательности каналов) в режиме DMA необходимо произвести следующие шаги:

  • Установить hadc.Init.ContinuousConvMode в ENABLE.
  • Выделите буфер соответствующего размера.
  • Передайте функции HAL_ADC_Start_DMA() количество преобразований, которое необходимо выполнить.

12.2.6.2 Многократное не непрерывное преобразование в режиме DMA

Для выполнения многократного преобразования в режиме DMA проделайте следующее:

  • Установите параметр hadc.Init.DMAContinuousRequests в ENABLE.
  • Вызовите HAL_ADC_Start_DMA() для запуска преобразования.

Если вместо этого установить hadc.Init.DMAContinuousRequests в DISABLE, то будет необходимо каждый раз вызывать HAL_ADC_Stop_DMA() в конце каждой последовательности преобразований, а после снова вызывать HAL_ADC_Start_DMA(). В противном случае преобразование не будет выполнено.

12.2.6.3 Непрерывное преобразование в режиме DMA

Для обеспечения этого алгоритма работы следуйте следующим шагам:

  • Установите hadc.Init.ContinuousConvMode в ENABLE.
  • Установите hadc.Init.DMAContinuousRequests в ENABLE, иначе DMA не запустит новое преобразование после завершения первой последовательности преобразований.
  • Настройте канал DMA в режим DMA_CIRCULAR.

12.2.7 Обработка ошибок

АЦП имеет возможность уведомить разработчика в случае если результат преобразования потерян. Данное состояние ошибки происходит когда при непрерывном или многоканальном режиме преобразования содержимое регистра данных АЦП переписывается успешным преобразованием до того как было прочтено. В таком случае устанавливается специальный бит регистра ADC_SR и генерируется соответствующее прерывание.

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

void HAL_ADC_ErrorCallback(ADC_HandleTypeDef *hadc);

При возникновении ошибки переполнения DMA отключается и запросы более не доступны. Если запрос DMA был в процессе, преобразование обрывается и дальнейшие триггеры запуска игнорируются. В таком случае необходимо очистить флаг OVR и бит DMAEN используемого DMA потока, переинициализировать как DMA, так и ADC, задав верное расположение в памяти для передачи через DMA (все эти действия выполняются автоматически, когда вы вызываете функцию HAL_ADC_Start_DMA()).

Можно просимулировать ошибку переполнения регистра данных, используя код предыдущего примера и установив в ENABLE hadc.Init.DMAContinuousRequests: если прерывание от АЦП разрешено и HAL_ADC_IRQHandler() вызывается из него, то вы сможете поймать данное событие переполнения (в некоторых микроконтроллерах также необходимо явно разрешить детекцию переполнения установкой поля hadc.Init.Overrun в значение ADC_OVR_DATA_OVERWRITTEN).

Ошибка переполнения не всегда связана с ошибочной конфигурацией интерфейса. Она может возникнуть когда АЦП работает в непрерывном режиме работы DMA. Для одного заказного устройства, которое автор делал на базе микроконтроллера STM32F4, DMA был сильно нагружен несколькими периферийными блоками и ошибка переполнения регистра данных АЦП возникала всякий раз, когда происходила конкурентная транзакция DMA. Даже когда установлены верные приоритеты транзакций автор столкнулся с данной проблемой в нескольких трудно-возспроизводимых ситуациях. Для корректной обработки этой ошибки необходимо перезапустить преобразование при ее возникновении. Не лишним будет сказать, что прежде, чем автор нашел в чем проблема, ему понадобилось потратить несколько дней на отладку данной ошибки.

12.2.8 Преобразования, управляемые таймером

АЦП может быть настроен для запуска по таймеру через линию TRGO таймера. Таймер, который позволяет проводить подобную операцию, специально спроектирован разработчиками микросхемы. Для примера, в микроконтроллерах STM32F401RE ADC1 может быть синхронизирован таймером TIM2. Это функция чрезвычайно полезна для реализации преобразований с заданной частотой выборок. К примеру, нам необходимо оцифровать аудиосигнал с микрофонам с частотой 20 кГц с сохранением результирующих данных в постоянную память.

АЦП может управляться таймером как в режиме прерывания, так и в режиме DMA. Первый может быть полезен когда нам нужно получать выборки только одного канала с небольшой частотой. Последний обязателен в многоканальном режиме с высокой частотой преобразования. Следующая процедура поможет вам запустить преобразования, управляемые таймером:

  • Настройте таймер, подключенный к АЦП через линию TRGO на нужную вам частоту выборок.
  • Настройте линию TRGO таймера так, что она генерировала событие обновления (TIM_TRGO_UPDATE).
  • Настройте АЦП так, чтобы линия TRGO выбранного таймера запускала преобразования и убедитесь что непрерывный режим работы неактивен (потому что TRGO будет запускать преобразования соответственно). Более того, установите hadc.Init.DMAContinuousRequests поле настроек в ENABLE и DMA в непрерывный режим, если вы хотите выполнять N-е количество преобразований неопределенное время, или установите hadc.Init.DMAContinuousRequests в DISABLE, если хотите остановить преобразования после выполнения N-го их количества.
  • Убедитесь, что поле настроек АЦП hadc.Init.ContinuousConvMode установлено в DISABLE, в противном случае АЦП будет запускаться сам без ожидания события запуска от таймера.
  • Запустите таймер.
  • Запустите АЦП в режиме прерывания или DMA.

Следующий пример показывает запуск преобразования каждую секунду в микроконтроллере STM32F401RE, используя таймер TIM2.

int main(void) {
    char msg[20];
    uint16_t rawValues[3];
    float temp;

    HAL_Init();
    Nucleo_BSP_Init();

    /* Initialize all configured peripherals */
    MX_TIM2_Init();
    MX_ADC1_Init();

    HAL_TIM_Base_Start(&htim2);
    HAL_ADC_Start_DMA(&hadc1, (uint32_t*)rawValues, 3);

    while(1) {
        while(!convCompleted);

        for(uint8_t i = 0; i < hadc1.Init.NbrOfConversion; i++) {
            temp = ((float)rawValues[i]) / 4095 * 3300;
            temp = ((temp - 760.0) / 2.5) + 25;

            sprintf(msg, "rawValue %d: %hu\r\n", i, rawValues[i]);
            HAL_UART_Transmit(&huart2, (uint8_t*) msg, strlen(msg), HAL_MAX_DELAY);

            sprintf(msg, "Temperature %d: %f\r\n",i, temp);
            HAL_UART_Transmit(&huart2, (uint8_t*) msg, strlen(msg), HAL_MAX_DELAY);
        }
        convCompleted = 0;
    }
}

/* ADC1 init function */
void MX_ADC1_Init(void) {
    ADC_ChannelConfTypeDef sConfig;

    /* Enable ADC peripheral */
    __HAL_RCC_ADC1_CLK_ENABLE();

    /**Configure the global features of the ADC
    */
    hadc1.Instance = ADC1;
    hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV8;
    hadc1.Init.Resolution = ADC_RESOLUTION_12B;
    hadc1.Init.ScanConvMode = DISABLE;
    hadc1.Init.ContinuousConvMode = DISABLE;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIG2_T2_TRGO;
    hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.NbrOfConversion = 3;
    hadc1.Init.DMAContinuousRequests = ENABLE;
    hadc1.Init.EOCSelection = 0;
    HAL_ADC_Init(&hadc1);

    /**Configure for the selected ADC regular channels */
    sConfig.Channel = ADC_CHANNEL_TEMPSENSOR;
    sConfig.Rank = 1;
    sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);
}

void MX_TIM2_Init(void) {
    TIM_ClockConfigTypeDef sClockSourceConfig;
    TIM_MasterConfigTypeDef sMasterConfig;

    __HAL_RCC_TIM2_CLK_ENABLE();

    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 41999; // 84MHz / 42000 = 2000
    htim2.Init.Period = 1999;
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_Base_Init(&htim2);

    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig);

    sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig);
}

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
    convCompleted = 1;
}

Код действительно просто для понимания. Функция MX_TIM2_Init() насраивает таймер TIM2 так, что он переполняется каждые 1 секунду. Кроме того, таймер настроен так, что линия TRGO изменяет состояние при переполнении (строка 95). АЦП же настроен на выполнение трех преобразований одного канала (канала температурного сенсора). Также он настроен таким образом чтобы запускаться от линии TRGO таймера TIM2 (строки [65:66]). Наконец, таймер запускается в строке 29 и АЦП запускается также в режиме DMA на выполнение 3 выборок из регистра данных DMA. Также DMA сконфигурирован в циклическом режиме. Если вы запустите пример, то сможете увидеть, что каждую третью секунду DMA завершает передачу и флаг convCompleted устанавливается в 1: это означает, что 3 выборки были отправлены в UART2.

Владельцы плат Nucleo на базе микроконтроллера STM32F410RB могут найти незначительно отличающийся пример. Это потому, что данные микроконтроллеры не могут запускать преобразование по событию обновления таймера, а только по событию Capture Compare Event. По этой причине таймер запускается в данном режиме, как описано в Главе 11.

12.2.9 Преобразования, управляемые внешними событиями

В некоторых микроконтроллерах STM32 есть возможность настроить линии внешних прерываний EXTI так, чтобы изменения на них запускали преобразования АЦП. Для примера, в STM32F401RE для этого может быть использована линия EXTI11. Это означает, что любой вывод, подключенный к этой линии (PA11, PB11 и т.д.) может являться источником запуска преобразования ADC. Также стоит иметь в виду, что использовать события запуска EXTI и таймера невозможно в одно и то же время.

12.2.10 Калибровка АЦП

АЦП, реализованные в некоторых микроконтроллерах STM32, таких как STM32L4 и STM32F3, обеспечивают процедуру автоматической калибровки, которая управляет всей последовательностью калибровки, включая последовательность включения / выключения АЦП. Во время этого процесса АЦП рассчитывает коэффициент калибровки, размерностью 7 бит и записывает его в настройки АЦП до следующего отключения. В течение данной процедуры приложение не должно использовать АЦП, ожидая завершения калибровки. Калибровка предваряет любые операции с АЦП. Она убирает ошибку смещения, которая может варьироваться от микросхемы к микросхеме из-за изменения процесса или запрещенной зоны. Коэффициент калибровки для несимметричных входов отличается от коэффициента для дифференциальных входов.

Модель HAL_ADC_Ex предоставляет 3 функции, которые используются для калибровки АЦП. Первая

HAL_ADCEx_Calibration_Start(ADC_HandleTypeDef* hadc, uint32_t SingleDiff);

автоматически выполняет процедуру калибровки. Она должна вызываться сразу после вызова HAL_ADC_Init() и перед вызовом HAL_ADC_Start_XXX(). В качестве параметра функции необходимо передать ADC_SINGLE_ENDED для калибровки несимметричного входа или ADC_DIFFERENTIAL_ENDED для дифференциального.

Функция

uint32_t HAL_ADCEx_Calibration_GetValue(ADC_HandleTypeDef* hadc, uint32_t SingleDiff);

используется для получения вычисленного коэффициента калибровки, тогда как

HAL_StatusTypeDef HAL_ADCEx_Calibration_SetValue(ADC_HandleTypeDef* hadc, uint32_t SingleDiff, uint32_t CalibrationFactor);

используется для установки пользовательского значения коэффициента калибровки. Более подробную информацию можно найти в референс-мануале на выбранный микроконтроллер.

12.3 Использование CubeMX для конфигурации АЦП

CubeMX позволяет настроить АЦП периферию в несколько простых шагов. Первым нужно включить необходимые каналы АЦП в дереве компонентов периферии, как показано на Рисунке 11.

Рисунок 11: Выбор каналов АЦП

После того как каналы выбраны, можно приступать к настройке во вкладке Configuration, как на Рисунке 12.

Рисунок 12: Настройка АЦП в CubeMX

Поля отражают настройки АЦП, рассмотренные до сих пор. Есть только одна часть, которая может сбить с толку начинающих пользователей: способ настройки каналов. Сперва необходимо установить количество каналов в поле Number of Conversion. После чего (и это очень важно) нужно кликнуть в любом месте диалогового окна, так чтобы количество полей Rank увеличилось соразмерно количеству установленных каналов. В тех микроконтроллерах, которые имеют как регулярные, так и инжектированные группы, мы можем выбрать время выборки для каждого канала независимо. CubeMX генерирует весь код инициализации автоматически.

Как уже говорилось ранее в этой главе, модуль HAL_ADC в CubeF1 отличается от других. Чтобы запускать преобразования программно необходимо установить параметр hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START в процессе инициализации. CubeMX отражает эти различия в конфигурации, но довольно проблематично понять как сконфигурировать эту периферию правильным способом. Для этого нужно параметр External Trigger Conversion Edge установить в Trigger detection on the rising edge. Это делает поле External Trigger Conversion Source доступным для настройки и уже в нем выбираем Software trigger. В противном случае преобразования не могут быть произведены.


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

14 Ответов в “ADC (перевод из книги Mastering STM32)

  1. Найдена неточность в переводе. DMA_HandleTypeDef *DMA_Handle; — это не указатель на ОБРАБОТЧИК dma, на мой взгляд правильнее это переводить как указатель на ДЕСКРИПТОР dma.

    Дескриптор, handle или хендл, описатель — это все одно и то же в русском языке.

    Если подробнее, дескриптор или хендл — это может быть номер адреса некоторой структуры данных (и в HAL как раз все описывается структурами данных). По этому номеру система (HAL) находит адрес этой структуры в памяти и работает с ней, как будто это определённый объект.

    Вообще вокруг слова handler в HAL довольно много путаницы, поскольку это слово уже зарезервировано в Cortex для понятия «обработчик прерывания/исключения». Поэтому русское сообщество STM32 часто не переводит понятие HAL handler, и говорят просто: хендлер, чтобы отличать 2 этих разных понятия. Но лично мне по душе называть HAL handler дескриптором HAL. Ну или хотя бы хендлером, но уж точно не обработчиком

    1. Есть предложение — строже относиться к терминам. «Номер адреса» — сленг. Адрес по сути — номер ячейки памяти. Номер номера? Документация HAL пестрит такими пенками, из-за которых читать почти не возможно.

  2. uint32_t ContinuousConvMode; /* Установка режима одноканального или многоканального преобразования */

    Опять же неточность в переводе поля в структуре ADC_InitTypeDef

    В оригинале написано: «Specifies whether the conversion is performed in Continuous or Single mode», что можно перевести как «Указывает, выполняется ли преобразование в непрерывном или однократном режиме»

  3. Благодарен за твою работу. Интересно. Хорошо изучать.

  4. Большое СПАСИБО за проделанную работу. Помогло.

  5. Здравствуйте!
    А как быть с калибровкой АЦП, если в нём используются каналы для дифференциального входа, для обычного входа и для обработки сигналов от встроенных датчиков, например, от встроенного датчика температуры?
    Для дифф. входа:
    HAL_ADCEx_Calibration_Start(&hadc1, ADC_DIFFERENTIAL_ENDED);
    Для обычного входа:
    HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED);
    Не делать же калибровку перед каждым замером конкретного канала?
    Спасибо.

    1. Возможно стоит разнести такие каналы по разным АЦП, дифференциальные скажем на adc1, несимметричные на adc2.

      1. На моём контроллере только один АЦП 🙁 Вот и ищу варианты.
        Спасибо.

    1. Спасибо. А кто сделал перевод и верстку?

  6. Дмитрий Карасев, в книге указано это

  7. В книге не были доделаны перекрестные ссылки между главами, терпения уже не хватило, да и не получилось у меня это, т.к. книга была собрана из кучи доков

  8. Спасибо огромное, аккуратный перевод. По привычке держал рядом оригинал, чтобы не «попасть» на ошибки перевода. На ошибки не попал )))

Комментарии отключены.