Ацп с использованием dma

STM32. Работа с АЦП через DMA

В этой статье, на примере получения данных от модуля АЦП, мы продемонстрируем вам базовые возможности технологии DMA – Direct Memory Access (прямой доступ к памяти) .
В общем случае, процессор, действуя по инструкциям из основной памяти, общается по шине данных с периферийными устройствами микроконтроллерной системы. Получение данных с АЦП при этом, происходит в следующей последовательности:

  1. Конфигурация АЦП
  2. Ожидание результата преобразования
  3. Считывание результата преобразования
  4. Запись результата преобразования в память
  5. Использование результата преобразования

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

Подготовка аппаратного обеспечения

В этом примере мы будем использовать микроконтроллер STM32L052K8T6. Отладочную плату с этим микроконтроллером можно приобрести в нашем магазине, но также можно использовать многие другие контроллеры, в том числе, в составе отладочных плат.
Нас будет интересовать получение данных с двух каналов АЦП сразу, передача данных через UART и прошивка контроллера через SWD-интерфейс. Мы подключили два переменных резистора к выводам PA4, PA5 (для изменения напряжения на выводах от нуля до напряжения питания), USB-to-COM-преобразователь и программатор STLink.

Подготовка CubeMX

На первой вкладке, Pinout, включаем 4ый и 5ый канал АЦП. Также сразу запускаем USART1 в асинхронном режиме и включаем SWD-интерфейс (SYS -> Debug Serial Wire). Все должно быть как на картинке ниже:

Вкладку Clock Configuration оставляем без изменений:

Далее переходим на вкладку Configuration. Здесь нам потребуется настроить все включенные модули и DMA. Отдельно останавливать на работе с UART мы не будем, так как разбирали этот вопрос в предыдущей статье.
В параметрах АЦП устанавливаем настройки как на картинке. Важно выбрать источник тактирования (Clock Prescaler), установить режим непрерывного преобразования (Continuous Conversion Mode) и разрешить запросы от DMA (DMA Continuous Requests).

Далее откройте меню конфигурации DMA и добавьте поток (кнопка Add).
В списке появится новый пункт. Нажмите на выпадающий пункт меню Select и выберите источником модуль АЦП (ADC). Тут же установите циклический режим запросов (Circular), инкремент адреса памяти и для слова. Все должно быть так, как показано на рисунке ниже:

Обратите внимание на пункт на стройки прерываний NVIC. Cube автоматически включил прерывания от DMA:

На этом подготовка проекта завершена. Можно сгенерировать код и открыть проект в Keil.

Написание кода в Keil

Фактически, CubeMX за нас уже проделал всю работу. Все что нам осталось, это запустить АЦП в режиме DMA и указать область памяти, в которую будет складываться результат преобразования. Для просмотра результата, мы будем отправлять данные в сыром виде в UART два раза в секунду.

Структура adc содержит два поля данных. Для этих целей можно было использовать массив, или обычную переменную, но мы предпочли именно такой способ выделения памяти.
Далее, вызывается встроенная функция драйвера HAL_ADC_Start_DMA();. Для работы она требует:

  • &hadc — указатель на структуру данных, описывающую настройки модуля АЦП. Cube сгенерировал ее автоматически
  • (uint32_t)&adc — указатель на область памяти для хранения результата преобразования с приведением типов данных в 32х-битный формат
  • 2 — количество данных для трансфера

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

Заключение

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

STM32CubeMx. Последовательные АЦП преобразования и DMA.

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

Сегодня мы как раз это и реализуем… Ставим себе следующую задачу:

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

Наверно никого не удивлю выбором контроллера для реализации этого проекта Использовать я буду STM32F10x, а точнее STM32F103VET6. И, конечно, последние несколько лет почти ни один проект для STM32 не обходится без использования STM32CubeMx!

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

  • PA0: ADC1, канал 0
  • PA2: ADC1, канал 2
  • PC1: ADC1, канал 11
  • PC2: ADC1, канал 12
  • PC3: ADC1, канал 13
  • PC5: ADC1, канал 15

Всего 6 каналов, как и планировали. Приступаем к делу! Запускаем Cube и создаем новый проект. Я обычно работаю в IARv8, но, на самом деле, не знаю, насколько он пользуется популярностью по сравнению с 7-ой версией, поэтому для этого проекта использую именно IARv7… Напишите, пожалуйста, в комментариях, кто какую версию использует для своих проектов на сегодняшний день

Проект создан, сразу же активируем необходимые выводы микроконтроллера. Это у нас – 6 входов АЦП, 2 входа для внешнего кварца и 2 входа для подключения отладчика:

Далее привычным нам образом настраиваем тактирование всего и вся:

Задаем все по максимуму – 72 МГц для APB2, 36 МГц – APB1, на модули ADC1, 2, 3 отправляем 12 МГц. В качестве источника тактирования выбираем внешний кварцевый резонатор, который мы уже активировали на вкладке Pinout&Configuration ранее.

Базовую подготовку и настройку проекта на этом заканчиваем и переходим непосредственно к интересующим нас АЦП и DMA! Окно настроек АЦП:

Конфигурируем параметры следующим образом:

Давайте разберем все значения подробнее:

  • Mode – здесь оставляем Independent mode, в общем-то, другие варианты сейчас недоступны (потому что мы задействовали только один модуль ADC)
  • Data alignment – выравнивание данных по правому краю
  • Scan Conversion Mode – этот параметр активизируем в том случае, если собираемся работать с несколькими каналами
  • Continious Conversion Mode – отключаем, будем запускать преобразования программно
  • Enable Regular Conversions – каналы настраиваем как регулярные
  • Number Of Conversion – здесь указываем число, равное числу каналов, которые мы будем опрашивать (в данном примере у нас 6 каналов)
  • External Trigger Conversion Source – этот параметр позволяет настроить событие, по которому будет запускаться АЦП, пока не используем
  • и, наконец, далее следуют настройки для каждого из каналов в отдельности.
Читайте также  Новое исследование по использованию графена для фотоэлементов

Для каналов АЦП у нас есть три параметра. Rank и Channel позволяют установить очередность опроса. Поскольку нам очередность не важна, будем опрашивать каналы просто по порядку, в соответствии с их номерами. С этим все понятно, а вот на параметре Sampling Time хотелось бы остановиться поподробнее.

ADC в STM32 работает в общих чертах так… Все входные каналы АЦП подключаются к аналоговому мультиплексору, на выходе которого установлен конденсатор. При измерении напряжения N-го канала мультиплексор переключается в соответствующее состояние и входной сигнал начинает заряжать конденсатор. Собственно, напряжение на этом конденсаторе и будет подвергаться измерению. А параметр Sampling Time это ни что иное, как время в тактах, которое выделяется на заряд этого внутреннего конденсатора.

Возникает вполне резонный вопрос – почему бы не делать всегда это время минимальным? Меньшее время измерения – лучше?

Тонкость заключается в том, что если ток входного сигнала мал, то конденсатор просто не успеет зарядиться. Поэтому измеренное значения напряжения будет неправильным. В таком случае необходимо увеличить Sampling Time этого канала. Общее же время преобразования вычисляется следующим образом:

Значение 12.5 соответствует контроллерам серии STM32F10x. Для STM32F4, например, это значение составляет 12 тактов. В принципе, разница невелика

Возвращаемся к SCM32CubeMx! С АЦП мы разобрались, переходим на вкладку настроек DMA:

  • Increment Address – адрес регистра данных в периферии – не инкрементируем, адрес в памяти – инкрементируем
  • Data Width – Half Word – 16 бит данных.

С настройками на этом заканчиваем, генерируем проект и вносим в него наши изменения! Первым делом определим переменные, при этом не забывайте о том, чтобы помещать весь пользовательский код в секции USER_CODE. Код, помещенный в такие секции не будет удален/изменен/перемещен при перегенерации проекта в STM32CubeMx. Добавим прямо в файл main.c:

В буфере adcData[] будем сохранять сырые данные с АЦП, а в adcVoltage[] – пересчитанные в Вольты значения. В функции main() добавляем запуск наших последовательных АЦП преобразований функцией HAL_ADC_Start_DMA():

Запустили АЦП и DMA и ожидаем результат. По окончании преобразования будет вызвана callback-функция HAL_ADC_ConvCpltCallback(). Для использования этой функции нам нужно просто переопределить ее и добавить туда любые необходимые нам операции:

Здесь мы проверяем, что источником вызова функции является именно модуль ADC1, и спокойно обрабатываем принятые данные. В нашем случае мы пересчитываем сырые значения в значения напряжения в Вольтах. Вот и все!

Собираем проект, прошиваем микроконтроллер и теперь мы можем под отладчиком проверить измеренные значения, которые в точности соответствуют напряжению, которое мы подаем на входы!

Для того, чтобы запустить еще одно преобразование просто вызываем снова:

Но! Следует отметить, что не стоит запускать преобразование непосредственно из callback-функции. Это может привести к тому, что новое преобразование завершится настолько быстро, что программа просто не успеет перейти к выполнению другого кода и будет постоянно крутиться в callback-функции…

На этом на сегодня заканчиваем, спасибо за внимание и до новых встреч на нашем сайте!

Миландр

Текущее время: 2021-июл-30 04:55

Часовой пояс: UTC+03:00

АЦП DMA

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

в спецификации «spec_1986BE1.pdf»
на странице 398, написано что 30 канал DMA предназначен для работы с АЦП, но в описании АЦП про DMA ничего не нашёл.

Прошу уточнить возможно ли использование DMA при работе с АЦП.

r_o_m_k_a писал(а):

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

в спецификации «spec_1986BE1.pdf»
на странице 398, написано что 30 канал DMA предназначен для работы с АЦП, но в описании АЦП про DMA ничего не нашёл.

Прошу уточнить возможно ли использование DMA при работе с АЦП.

Ладно, с DMA не очень получается, в принципе и по прерыванию АЦП всё забрать успеваю.

тогда ещё вопрос по АЦП
есть задача : через 50мкс оцифровывать каналы АЦП 1,2,3, затем, через 50 мкс каналы 5,6,7, и далее повторяем снова
т.е. каждые 50 мкс разный набор каналов

сейчас делаю так
1. задаю в ADC1_CHSEL каналы 1,2,3
2. выставляю биты Cfg_REG_CHCH и Cfg_REG_SAMPLE — при этом начинается оцифровка выбранных каналов
3. в прерывании АЦП читаю номер канала и данные, сохраняю
4. когда получили данные с канала 2 — сбрасываю бит Cfg_REG_SAMPLE — чтобы оцифровать запущенный уже канал 3 и остановиться
5. в следующем прерывании АЦП получаем данные с канала 3, дальше запуск АЦП не происходит, т.к. сброшен Cfg_REG_SAMPLE

6. через 50 мкс задаю в ADC1_CHSEL каналы 5,6,7, устанавливаю бит Cfg_REG_SAMPLE — начинается оцифровка
7. входим в прерывание АЦП и видим что получили данные с канала 1, а должны были с канала 5
как я понимаю в п.5 произошёл выбор канала 1 вот его и оцфровали
8. далее оцифровывается как надо, каналы 5,6,7
9. когда оцифровали канал 6 аналогично п.4 сбрасываем Cfg_REG_SAMPLE

10. через 50мкс задаём опять каналы 1,2,3, и первым оцифрованным оказывается канал 5 из предыдущего набора

заданием канала в поле Cfg_REG_CHS регистра ADC1_CFG это не лечится

в итоге вопросы:
1. как можно избавится от этого «лишнего» запомненного канала ?
2. возможно ли узнать в какой момент напряжение с этого лишнего канала защёлкивается на конденсаторе АЦП ?
В момент установки Cfg_REG_SAMPLE или может в момент «запоминания» этого канала в п.5 ?
3. вопрос по настройке времени оцифровки АЦП.
Кварц на 10 МГц. CPU_CLK = 140МГц. АЦП тактируется от USB_C2=10*11=110МГц. Делится на 8, получаем ADC_CLK = 13.75МГц.
Время оцифровки одного канала 2,5мкс, а должно вроде быть 2 мкс.
Почему при этом значение полей Cfg_REG_DIVCLK, Delay_ADC, Delay_Go регистра ADC1_CFG не влияет на частоту оцифровки что бы я туда не задавал?
4. Как вообще достичь максимально быстрого и максимально одновременного измерения трёх каналов АЦП ?

на процессоре маркировка 1986ВЕ1Т 1140, наверное ревизия 1

r_o_m_k_a писал(а):

Ладно, с DMA не очень получается, в принципе и по прерыванию АЦП всё забрать успеваю.

тогда ещё вопрос по АЦП
есть задача : через 50мкс оцифровывать каналы АЦП 1,2,3, затем, через 50 мкс каналы 5,6,7, и далее повторяем снова
т.е. каждые 50 мкс разный набор каналов

сейчас делаю так
1. задаю в ADC1_CHSEL каналы 1,2,3
2. выставляю биты Cfg_REG_CHCH и Cfg_REG_SAMPLE — при этом начинается оцифровка выбранных каналов
3. в прерывании АЦП читаю номер канала и данные, сохраняю
4. когда получили данные с канала 2 — сбрасываю бит Cfg_REG_SAMPLE — чтобы оцифровать запущенный уже канал 3 и остановиться
5. в следующем прерывании АЦП получаем данные с канала 3, дальше запуск АЦП не происходит, т.к. сброшен Cfg_REG_SAMPLE

Читайте также  Передача данных ацп на пк

6. через 50 мкс задаю в ADC1_CHSEL каналы 5,6,7, устанавливаю бит Cfg_REG_SAMPLE — начинается оцифровка
7. входим в прерывание АЦП и видим что получили данные с канала 1, а должны были с канала 5
как я понимаю в п.5 произошёл выбор канала 1 вот его и оцфровали
8. далее оцифровывается как надо, каналы 5,6,7
9. когда оцифровали канал 6 аналогично п.4 сбрасываем Cfg_REG_SAMPLE

10. через 50мкс задаём опять каналы 1,2,3, и первым оцифрованным оказывается канал 5 из предыдущего набора

заданием канала в поле Cfg_REG_CHS регистра ADC1_CFG это не лечится

в итоге вопросы:
1. как можно избавится от этого «лишнего» запомненного канала ?
2. возможно ли узнать в какой момент напряжение с этого лишнего канала защёлкивается на конденсаторе АЦП ?
В момент установки Cfg_REG_SAMPLE или может в момент «запоминания» этого канала в п.5 ?
3. вопрос по настройке времени оцифровки АЦП.
Кварц на 10 МГц. CPU_CLK = 140МГц. АЦП тактируется от USB_C2=10*11=110МГц. Делится на 8, получаем ADC_CLK = 13.75МГц.
Время оцифровки одного канала 2,5мкс, а должно вроде быть 2 мкс.
Почему при этом значение полей Cfg_REG_DIVCLK, Delay_ADC, Delay_Go регистра ADC1_CFG не влияет на частоту оцифровки что бы я туда не задавал?
4. Как вообще достичь максимально быстрого и максимально одновременного измерения трёх каналов АЦП ?

на процессоре маркировка 1986ВЕ1Т 1140, наверное ревизия 1

Andrey, спасибо за ответы.

1. Если сбрасывать биты в ADC1_CHSEL, то всё равно останется запомненным канал 3, который и оцифруется первым при задании 5,6,7.

Сделал так — сбрасываю ADC1_CFG:
запоминаю temp = ADC1_CFG,
записываю ADC1_CFG = 0,
записываю ADC1_CFG = temp | Cfg_REG_SAMPLE, после этого начинается оцифровка в соответствии с ADC1_CHSEL
(остаётся только небольшой глюк с каналом 0, он оцифровыается последним, т.е. в ADC1_CHSEL выставляю биты 0,1,2,3, в прерывании АЦП получаю последовательность 1,2,3,0)

3. про работу делителя и задержек при тактировании от PCLK — важное замечание, получается что при тактировании от ACLK задержки на заряд ёмкости АЦП не ввести.

И снова здравствуйте .

Появились очередные вопросы по АЦП.

Исходные данные
кварцевый генератор 10 МГц
CPU_clk = 140 МГц
ADC_clk = USB_clk/8 = 110/8 = 13.75 МГц
Delay_ADC = 0 и Cfg_REG_DIVCLK = 0 — никак не влияют
Delay_GO = 7
для теста каждые 50 мкс измеряем три канала 4,5,6 — три синусоиды 1Гц со сдвигом в 60 градусов.
опорное напряжение АЦП — внутреннее
синусоиды «приподняты» до уровня 1,5 В, в насыщение и в отрицательные значения не уходят

наблюдаем следующее:
канал 4 отображает красивую и ровную синусоиду, на двух других — провалы и выбросы в верхней части
если измерять каналы 5,6 тогда канал 5 отображает правильно, канале 6 — искажения
т.е. правильно измеряется канал, который оцифровывается первым

1. если снижать частоту CPU_clk до 110 МГц, все каналы оцифровываются без искажений, если Delay_GO установить в 0, искажения есть
2. если снижать частоту оцифровки до 9 МГц, все каналы оцифровываются без искажений
3. если не использовать последовательное преобразование каналов, а каждый раз запускать устанавливая бит Cfg_REG_GO, то все каналы оцифровываются без искажений

все эти методы увеличивают время измерения, теряется «одновременность» снятия сигналов — это большая проблема для меня

как я понимаю, искажения связаны со временем заряда внутренней ёмкости АЦП
в «spec_1986BE1.pdf» написано, что максимально возможное общее время заряда емкости АЦП перед началом преобразования равно
4xADC_CLK + 8xCPU_CLK, и получается необходимо снижать или одну частоту или другую

Вопросы:
1. как можно добиться максимального быстродействия АЦП без потери точности ?
2. добавка Delay_GO позволяет добавить задержку 1..8 тактов CPU_clk, но на частоте 140 МГц, этого недостаточно.
Планируется ли в следующих ревизиях как-то исправить этот момент ?

АЦП преобразования в указанные моменты времени на STM32

Доброго времени суток. В этом посте я расскажу, как мне удалось заставить STM32F407VET6 измерять аналоговые сигналы в указанные моменты времени с помощью DMA.

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

Для упрощения и ограничения задачи условимся, что

мы знаем точные моменты времени, когда необходимо измерять сигнал;

из предыдущего пункта также ясно, сколько измерений должно быть произведено;

разница по времени между двумя последовательными измерениями может быть любой;

нам необходим только один канал для измерения в точные промежутки времени.

Учтем также следующие особенности STM32:

STM32 обладает DMA (DMA1) контроллером, который может избавить MCU от перекладывания данных из регистра ADC в RAM память по окончанию преобразования;

ADC преобразование может быть вызвано по некоторым событиям в том числе: TIMx_UP, TIMx_CCRy.

Таким образом каждое последующее преобразование может быть вызвано когда у таймера, который может триггерить ADC, срабатывает Capture/Compare или Update события. После окончания преобразования в работу вступает DMA1 и перекладывает измеренное значение в память MCU.

Конечно, можно на Capture/Compare событие активировать прерывание и в этом прерывании записывать новое значение в этот же Capture/Compare Register (CCR). Минусом этого подхода являются затраты микроконтроллера на обслуживание каждого прерывания (сохранение и восстановление стека для работы прерывания) а также сопутствующее ему отклонение времени измерения. И чем больше измерений должно быть сделано, тем больше будет отклонение. Конечно, можно учесть время входа и выхода из прерывания, но это слишком сложно, да и не нужно, если есть способ проще и лучше.

Обоснование использования CCR

Далее буду рассказывать про работу с Capture/Compare Register. С Update событием тоже можно сделать, принцип останется такой же, но регистры будут другие. Плюс использования CCR я вижу в том, что таймер всегда переполняется с одной и той же частотой.

С другой стороны, ту же самую работу по обновлению значения в TIMx->CCRy может взять на себя ещё один DMA (DMA2), если настроить его таким образом, что каждое событие по CCRy помимо запуска ADC также бы вызывало и обновление этого CCRy с помощью DMA2. Такой подход полностью освобождает нас от использования прерываний (всю работу берут на себя DMA2, ADC и DMA1), необходим лишь только массив значений CCR, который будет предоставлен DMA2 для отправки их в TIMx->CCRy. Поэтому же и все измерения произойдут в точно указанные промежутки времени.

Получается для работы алгоритма, должны соблюдаться следующие пункты:

ADC должен быть настроен на преобразование по внешнему событию от CCRy, каждое последующее преобразование должно ждать соответствующего ему CCR события;

После окончания преобразования DMA1 должен перекладывать преобразованное значение из регистра ADC в память MCU;

DMA2 должен срабатывать по тому же событию от CCR что и ADC. По каждому событию DMA2 должен записывать последующее значение в TIMx->CCRy регистр из массива чисел заданного при настройке DMA2.

Первый момент времени должен быть записан в CCR с помощью MCU, а не DMA.

Читайте также  Фонарик с индукционной зарядкой

Графически это будет выглядеть так:

Настройка периферии

Чтобы не описывать каждый пункт по отдельности, на рисунках укажу, какие параметры периферии должны быть установлены. Также скажу, что вся работа над проектом велась в STM32CubeIDE.

1. Для ADC должен быть установлен Scan Conversion Mode, выбран канал(IN1), настроен триггер и DMA1, который будет перекладывать готовые измерения в память.

2. Настройка таймера. Установку значений предделителя и AutoReload Register я опущу, т.к. они будут зависеть от вашего проекта. Для данного примера я экспериментально подобрал такие значения, которые бы позволили мне провести наглядный эксперимент, который я опишу ниже. Также для TIM3 необходимо добавить DMA, и указать его направление — из памяти в периферию, а также поставить галочку в пункте Increment Address для Memory. Прерывания для TIM3 устанавливались опционально для того, чтобы проверить, что в CCR каждый раз записываются новые значение с помощью DMA. Также в настройках конфигурации Output Compare CH1 может быть заменен на Output Compare No Output, если нет необходимости отображать состояние сравнения CCR с CNT на пине MCU.

Установка режима Toogle on match в TIM3 Output Compare Channel 1 позволяет отрабатывать каждое событие по CCR в ADC. Только в таком режиме со связкой в ADC : Trigger detection on both the rising and failling edges мне удалось заставить ADC запускать каждое преобразование.

3. На этом настройка периферии закончена, но для экспериментальной проверки включим DAC, который будет генерировать значения для оцифровывания, а пины микроконтроллера DAC и ADC IN1 соединим друг с другом, а также с каналом осциллографа.

С настройкой периферии разобрались, теперь необходимо написать код и провести эксперимент.

Запишем нулевое значение в DAC и включим его. Далее в тестовом примере будет написана функция, которая постепенно увеличивает выходное значение DAC до достижения им потолка (4095).

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

Инициализация значений CCR

Далее необходимо положить первое значение из массива в регистр CCR1, а также на всякий случай остановим таймер и сбросим его счетчик. Опционально включаются прерывания таймера по CC1 и заморозка счетчика в режиме отладки.

После этого запускаем преобразования ADC с DMA. ADC теперь будет ждать события от таймера.

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

Осталось запустить на железке и проверить.

Проверка на железе

Подключив два щупа осциллографа к MCU (один к пину DAC/ADC IN1, другой к выходу TIM3_CH1) Можем наблюдать следующее изображение:

Желтый — сигнал с DAC, зеленый — выход TIM3_CH1

На осциллограмме каждое переключение зеленого сигнала является моментом измерения. Можно увидеть, что присутствует разброс по времени между измерениями.

Теперь перенесем измеренные значения ADC на график и посмотрим, что получилось:

Результат измерения, по горизонтальной оси значения CCR, по вертикальной оцифрованные значения DAC

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

Я попытался повторить тот же самый трюк на STM32F103C8T6, но к сожалению мне не удалось добиться такого же эффекта. Как только происходит первое событие по сравнению с CCR, MCU тот час же пытается оцифровать сразу все запрошенное количество, не дожидаясь следующих событий. Если у кого-то получится это реализовать, я с радостью добавлю в статью.

Выводы

Используя АЦП настроенный на преобразование по запросу от таймера, DMA1, который перекладывает оцифрованные значения в память, а также DMA2 для настройки следующего момента измерения удалось реализовать механизм, который позволяет измерять сигнал в произвольно заданные моменты времени не расходуя вычислительных ресурсов микроконтроллера. Если таймер синхронизировать с таймером ШИМ, или использовать этот же таймер, который генерирует ШИМ, то можем легко измерять сигнал точно в нужный момент.

Update 1:
Ссылка на тестовый пример на github.

Русские Блоги

Замечания по разработке STM32CubeIDE 5: АЦП + DMA

Замечания по разработке STM32CubeIDE 5: АЦП + DMA

  • базовая конфигурация
  • HAL по использованию функций библиотеки ADC
    • АЦП опроса
    • ADC_DMA

базовая конфигурация

Конфигурация АЦП очень проста, как показано ниже:

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

Это настроено, импортируйте проект.

HAL по использованию функций библиотеки ADC

АЦП опроса

Добавить в личные переменные

Добавьте следующую программу в while:

среди них
HAL_ADC_Start(&hadc1); Для начала преобразования АЦП.
HAL_ADC_PollForConversion(&hadc1, 50); Представляет ожидание завершения преобразования, второй параметр представляет период ожидания в мс. Когда я использую это, я не думаю, что это имеет большое влияние, но это необходимо.
HAL_ADC_GetValue(&hadc1); Читайте данные преобразования АЦП, данные 12 бит. Глядя на руководство по данным, мы видим, что регистр имеет 16 бит для хранения данных преобразования, и данные выровнены по правому краю, тогда диапазон преобразованных данных составляет 0

2 ^ 12-1, то есть 0

Доступны и другие функции, такие как:
HAL_ADC_GetState(&hadc1); В обмен на статус АЦП HAL_ADC_STATE_REG_EOC представляет флаг завершения преобразования, и данные преобразования доступны.
для использования вместе
HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc1), HAL_ADC_STATE_REG_EOC); Он должен определить, установлен ли флаг завершения преобразования.

Загрузите его в микроконтроллер для проверки.Я использую потенциометр 20KR для подключения к PA1, источника питания к источнику питания микроконтроллера, а внешний источник питания должен быть подключен к одной и той же земле, и измеренное напряжение не может превышать напряжение источника питания микроконтроллера, то есть не более 3,3 В.

Используйте онлайн-отладку для просмотра значения АЦП.

Разделите это значение на 4096 и умножьте на 3,3, чтобы получить фактическое значение напряжения.

ADC_DMA

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


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

Конкретная причина была упомянута в предыдущем разделе. Если вам интересно, вы можете перейти к предыдущему разделу (https://blog.csdn.net/qq_42038029/article/details/103835984).DMA последовательная связьизнотаЗаглянуть внутрь.
Затем сначала добавьте переменные в переменную.

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

Наконец, добавьте следующий код в то время как:

Добавьте все значения, чтобы найти среднее, но результат будет более точным.
Загрузите в MCU для проверки и проверьте значение ad1 в реальном выражении:

Это не легко создать, просто так!