Урок 32. Работа с АЦП через функции библиотеки HAL (продолжение).

уроки STM32

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

Предыдущий урок     Список уроков     Следующий урок

В этой статье реализуются проекты уроков 28 и 29 с использованием HAL-библиотеки. Логику работы АЦП я подробно не объясняю. Если что-то непонятно, то лучше вернитесь к предыдущим урокам. Для понимания материала этого урока режимы и логику функционирования АЦП надо знать.

 

Четырех канальный вольтметр среднего значения.

Реализуем с использованием HAL-функций проект вольтметра из урока 28. Научимся работать с АЦП в фоновом режиме с использованием прерываний. А именно:

  • запускать преобразование АЦП циклически от таймера;
  • считывать результат в обработчике прерываний;
  • вычислять среднее значение сигнала.

В этом проекте ожидание конверсии АЦП не будет блокировать выполнение основного цикла.

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

Повторю алгоритм работы программы.

  • Аппаратный таймер циклически запускает преобразование 4х инжектированных каналов с периодом 100 мкс.
  • По окончанию преобразования АЦП генерирует прерывание, в обработчике которого считываются и усредняются измеренные значения.
  • Время усреднения результатов преобразований 250 мс или 2500 выборок по 0,1 мс.
  • По истечению периода усреднения измеренные значения выводятся на LCD-дисплей и через UART передаются на компьютер.

За основу я взял проект из предыдущего урока Lesson31_1. В нем уже установлена библиотека для работы с LCD-дисплеем и конфигурирован UART.

Копируем проект или создаем новый. Открываем конфигуратор STM32CubeMX.

Настраиваем АЦП. Мы будем использовать только 4 инжектированных каналов.

  • Регулярные каналы запрещаем.
  • Число преобразований инжектированных каналов задаем 4.
  • Разрешаем режим сканирования.

окно STM32CubeMX

Запускать АЦП будем от таймера 2. Поэтому в качестве источника внешнего запуска выбираем событие таймера 2.

Конфигуратор STM32CubeMX

Осталось задать параметры каналов в последовательности сканирования.

STM32CubeMX

Результат будем считывать по прерыванию АЦП. Поэтому разрешаем его в закладке NVIC Settings.

Конфигуратор STM32CubeMX

Настраиваем таймер 2. Мы научились делать это в уроке 16.

Наша задача конфигурировать таймер, чтобы он сбрасывался каждые 100 мкс.

В окне Timers -> TIM2 -> TIM2 Mode and Configuration разрешаем тактирование от внутреннего источника.

Конфигуратор STM32CubeMX

В окне Parameter Settings задаем:

  • Предделитель 719. Коэффициент деления будет равен 720, период тактирующих импульсов на выходе предделителя – 10 мкс.
  • Режим счетчика – Up. Будем считать в строну увеличения.
  • В регистр перезагрузки установим значение 9. Коэффициент деления будет 10, а значит период тактирования счетчика 10 мкс * 10 = 100 мкс.
  • В качестве выходного события таймера выбираем перезагрузку.

Конфигуратор CubeMX

Прерывание по таймеру разрешать не надо. Мы запускаем АЦП по событию таймера.

Все. Создаем проект.

 

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

void ADC1_2_IRQHandler(void) {

}

Но в HAL-библиотеке для этого есть Callback-функции. Они вызываются по завершении какого-либо события. Нас интересует функция завершения преобразования HAL_ADCEx_InjectedConvCpltCallback.

Callback-функции объявляются в библиотеке HAL  как “слабые ссылки”. При объявлении используется атрибут __weak. Это означает, что они могут быть переопределены пользователем в своем коде без объявления. Т.е. это своеобразные функции-заглушки.

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

/* USER CODE BEGIN 0 */
void HAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef* hadc)
{

}

Здесь и будем работать с результатом АЦП.

Callback-вызов общий для двух АЦП. Если используются оба модуля, то надо делать проверку, от которого из них пришел вызов функции.

void HAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef* hadc)
{
  if(hadc->Instance == ADC1)
  {
    // обработка АЦП1

  }
}

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

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!
  • По каждому вызову функции HAL_ADCEx_InjectedConvCpltCallback результаты каналов накапливаются в массиве sumADC;
  • Счетчик averageCounter считает число выборок. При достижении его равным 2500, перегружает накопленные числа в переменные среднего значения averageADC.
  • Устанавливается признак flagReady, сообщающий о том, что появился новый результат измерений.

Остается в основном цикле периодически проверять флаг готовности flagReady и выводить измеренные величины на дисплей и в UART.

if( flagReady != 0 ) {
  flagReady=0;
  res= (float)averageADC[0] * VREF / 4096. / 2500.; // пересчет в напряжение

  // вывод на LCD
  . . . . . . . .
}

Еще надо добавить в исходный код калибровку АЦП

HAL_ADCEx_Calibration_Start(&hadc1);

А также запуск его в режиме с прерываниями

HAL_ADCEx_InjectedStart_IT(&hadc1);

и запуск таймера 2.

HAL_TIM_Base_Start(&htim2);

Компилируем программу, загружаем в микроконтроллер.

У меня не заработало. Таймер 2 генерирует событие по перезагрузке,  но АЦП по нему не запускается.

Объяснить не могу. Опытным путем выяснил, что мешает, установленный в битах EXTSEL, источник запуска регулярной группы - программный запуск SWSTART.

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

Такое состояние битов EXTSEL устанавливает CubeMX при запрещении регулярных каналов.

Можно разрешить регулярные каналы и установить источник запуска для них любой, кроме программного. Можно изменить биты EXTSEL в регистре CR2.

Я исправил строку

//  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T3_TRGO;

в блоке конфигурации АЦП. При этом бит разрешения внешнего запуска для регулярных каналов остался в запрещающем состоянии.

После этого вольтметр заработал правильно.

Вольтметр среднего занчения

Терминал

Проект можно загрузить по ссылке

 

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!
 

Работает с этим устройством и программа верхнего уровня из урока 28.

Вольтметр среднего значения

 

Проект 4х канального вольтметра с использованием АЦП в режиме сдвоенных преобразований.

Разработаем устройство подобное предыдущему проекту, только добавим условие, что пары каналов 0,  1 и 2, 3 должны считываться одновременно.

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

Открываем STM32CubeMX.

Настраиваем систему тактирования, как в предыдущем проекте.

Настраиваем АЦП.

Для первого АЦП выбираем каналы IN0 и IN2.

Конфигуратор STM32CubeMX

Для второго каналы IN1 и IN3.

Конфигуратор STM32CubeMX

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

STM32CubeMX

STM32CubeMX

Теперь доступны все режимы АЦП1.

Выбираем Dual injected simultaneous mode.

Источник запуска – событие таймера 2.

Конфигуратор STM32CubeMX

Разрешаем прерывание АЦП 1.

STM32CubeMX

Таймер 2 настраиваем аналогично предыдущему проекту.

STM32CubeMX

Прерывание по таймеру разрешать не надо. Мы запускаем АЦП по событию таймера.

Не забудем про UART1.

Конфигуратор STM32CubeMX

Создаем проект.

В Atollic TrueStudio:

  • Подключаем библиотеки.

/* USER CODE BEGIN Includes */
#include "LCD780.h"
#include "DelayDWT.h"
/* USER CODE END Includes */

  • Правой кнопкой мыши нажимаем на папку LCD780 в проекте, Add/Remove Include Path -> OK.
  • Ту же операцию повторяем с папкой DelayDWT.
  • Правой кнопкой по Libraries, Properties -> C/C++ General -> Paths and Symbols -> Source Location -> Add Folder -> Apply.
  • Добавим блок инициализации дисплея.

// инициализация дисплея
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPAEN ;
initDelayDwt();
init_LCD(GPIOA, 1<<12, GPIOA, 1<<15, GPIOB, 1<<14, GPIOB, 1<<15, GPIOA, 1<<8, GPIOA, 1<<11, 16, 2, 1);

Теперь можно работать с функциями библиотеки LCD-дисплея.

Создаем Callback-функцию окончания преобразования инжектированных каналов. Она повторяет аналогичную функцию предыдущего проекта, за исключением строк чтения результата.

sumADC[0] += HAL_ADCEx_InjectedGetValue(&hadc1, 1);
sumADC[1] += HAL_ADCEx_InjectedGetValue(&hadc2, 1);
sumADC[2] += HAL_ADCEx_InjectedGetValue(&hadc1, 2);
sumADC[3] += HAL_ADCEx_InjectedGetValue(&hadc2, 2);

Результаты преобразований хранятся в регистрах разных модулях АЦП.

Остается в основном цикле периодически проверять флаг готовности flagReady и выводить измеренные величины на дисплей и в UART.

if( flagReady != 0 ) {
  flagReady=0;
  res= (float)averageADC[0] * VREF / 4096. / 2500.; // пересчет в напряжение

  // вывод на LCD
  . . . . . . . .
}

Добавим в исходный код калибровку АЦП

HAL_ADCEx_Calibration_Start(&hadc1);
HAL_ADCEx_Calibration_Start(&hadc2);

А также запуск его в режиме с прерываниями

HAL_ADCEx_InjectedStart_IT(&hadc1);

и запуск таймера 2.

HAL_TIM_Base_Start(&htim2);

Подкорректируем биты EXTSEL регулярной группы.

//  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T3_TRGO;

Еще надо разрешить запуск АЦП2. HAL-функция конфигурации почему-то этого не делает.

ADC2->CR2 |= ADC_CR2_JEXTTRIG; // разрешение запуска АЦП2

Источник запуска задавать не нужно. Второй АЦП запускается от первого.

Компилируем, загружаем.

Вот мой проект.

 

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!
 

Внешне работа устройства ничем не отличается от предыдущего проекта.

 

Проект простого осциллографа.

Повторим второй проект урока 29 – цифровой осциллограф. Напомню, что в нем мы создали цифровой осциллограф со следующими параметрами.

  • Частота сканирования – 2 мГц.
  • Длительность развертки 4 мс.
  • Число выборок 8000.
  • Запуск от компьютера.

Чтобы создать устройство со временем выборки 0,5 мкс (частота 2 мГц) необходимо использовать оба АЦП в режиме быстрых преобразований со смещением во времени. При этом АЦП должны работать с максимальным быстродействием. Все временные расчеты в уроке 29.

Проверку окончания преобразования и чтение результатов мы не сможем реализовать с помощью HAL-функций. Вызов функций достаточно длительная операция, а нам необходимо в цикле с периодом всего 1 мкс делать одну проверку и два чтения.

Поэтому настройку АЦП сделаем с помощью библиотеки HAL, а цикл сканирования оставим без изменений, т.е. с использованием обращений к регистрам CMSIS.

Я копировал проект Lesson29_2 и переименовал его в Lesson32_3.

Удалил блок инициализации АЦП.

Открыл проект конфигуратором CubeMX.

Настроил частоту тактирования АЦП на 14 мГц.

Конфигуратор CubeMX

Теперь настраиваем АЦП.

Для первого модуля:

  • выбираем канал IN0;
  • разрешаем непрерывный режим преобразования;
  • выбираем преобразование одного регулярного канала;
  • задаем его параметры.

STM32CubeMX

Для второго АЦП задаем абсолютно такую же конфигурацию.

Возвращаемся к АЦП 1 и задаем режим быстрых преобразований со смещением во времени.

STM32CubeMX

Создаем проект.

Добавляем калибровку.

HAL_ADCEx_Calibration_Start(&hadc1);
HAL_ADCEx_Calibration_Start(&hadc2);

И разрешение запуска АЦП 2.

ADC2->CR2 |= ADC_CR2_EXTTRIG; // разрешение запуска АЦП2

Строки запуска преобразования заменяем одной функцией.

HAL_ADC_Start(&hadc1);

Останавливаем преобразование функцией

HAL_ADC_Stop(&hadc1);

И все. Вот мой проект цифрового осциллографа.

 

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!
 

Проверить можно программой верхнего уровня из урока 29.

Это синусоидальный сигнал частотой 1 кГц.

Осциллограф STM32

Это частота 500 Гц

Цифровой осциллограф STM32

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

 

В следующем уроке начнем изучать контроллер прямого доступа к памяти (DMA).

 

Предыдущий урок     Список уроков     Следующий урок

0

Автор публикации

не в сети 2 недели

Эдуард

280
Комментарии: 1936Публикации: 197Регистрация: 13-12-2015

7 комментариев на «Урок 32. Работа с АЦП через функции библиотеки HAL (продолжение).»

  1. Уважаемый автор! Спасибо Вам за Ваши статьи.
    Вопрос: Когда будет статья про генерацию ШИМ (если будет)?
    Интересует генерация шим на любом пине, не только на выводах таймеров. Долго бодался с этим вопросом, но не хватает знаний. Стм-ки изучаю уже неделю, ранее кодил на аврках, там все значительно проще.
    Спасибо еще раз:)

    0
    • Здравствуйте!
      Посмотрел. Последний пост 8 июля. Вы несколько преувеличили.
      Все равно, спасибо за беспокойство. Правда, приятно.
      У меня все нормально. Увлекся разработкой системы умного растениеводства. В сентябре съездил в Абхазию, приехал — часть растений пострадала от засухи. Сейчас разрабатываю интеллектуальную систему полива. Очень красивая и простая система получается. Надеюсь, через пару недель вернуться к урокам STM32. Еще раз, спасибо за беспокойство.

      0
      • Да ну, бросьте! Спасибо Вам за Ваш труд! Очень было непросто влиться в разработку на STM32 «с нуля», без опыта работы даже с Ардуино. Стало гораздо легче благодаря Вашим обстоятельным, подробным урокам. По сему очень хочется, чтобы Вы не останавливались!

        0

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

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

Нажимая кнопку "Отправить" Вы даёте свое согласие на обработку введенной персональной информации в соответствии с Федеральным Законом №152-ФЗ от 27.07.2006 "О персональных данных".