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 входа для подключения отладчика:

Настройка портов в STM32CubeMx.

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

Настройки тактирования.

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

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

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

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

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

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

  • 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 этого канала. Общее же время преобразования вычисляется следующим образом:

T_{conv} = Sampling Time + 12.5\medspace cycles

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

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

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

Здесь у нас:

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

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

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define ADC_CHANNELS_NUM                                         6

/* USER CODE END PD */

/* USER CODE BEGIN PV */
uint16_t adcData[ADC_CHANNELS_NUM];
float adcVoltage[ADC_CHANNELS_NUM];

/* USER CODE END PV */

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

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_ADC1_Init();
  /* USER CODE BEGIN 2 */
  HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adcData, ADC_CHANNELS_NUM);
  
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

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

/* USER CODE BEGIN 4 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
  if(hadc->Instance == ADC1)
  {
    for (uint8_t i = 0; i < ADC_CHANNELS_NUM; i++)
    {
      adcVoltage[i] = adcData[i] * 4095 / 3.3;
    }
  }
}

/* USER CODE END 4 */

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

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

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

HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adcData, ADC_CHANNELS_NUM);

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

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

Проект для этой статьи – ссылка.

Поделиться!

Подписаться
Уведомление о
guest
0 Комментарий
Inline Feedbacks
View all comments

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

Profile Profile Profile Profile Profile
Vkontakte
Twitter

Язык сайта

Август 2020
Пн Вт Ср Чт Пт Сб Вс
 12
3456789
10111213141516
17181920212223
24252627282930
31  

© 2013-2020 MicroTechnics.ru