Продолжим работу с АЦП через HAL-функции. Научимся запускать преобразование от таймера, считывать результат по прерыванию. Разработаем 4х канальный вольтметр среднего значения. Повторим проекты из предыдущих уроков, в которых АЦП работает в режиме сдвоенных преобразований.
Предыдущий урок Список уроков Следующий урок
В этой статье реализуются проекты уроков 28 и 29 с использованием HAL-библиотеки. Логику работы АЦП я подробно не объясняю. Если что-то непонятно, то лучше вернитесь к предыдущим урокам. Для понимания материала этого урока режимы и логику функционирования АЦП надо знать.
Четырех канальный вольтметр среднего значения.
Реализуем с использованием HAL-функций проект вольтметра из урока 28. Научимся работать с АЦП в фоновом режиме с использованием прерываний. А именно:
- запускать преобразование АЦП циклически от таймера;
- считывать результат в обработчике прерываний;
- вычислять среднее значение сигнала.
В этом проекте ожидание конверсии АЦП не будет блокировать выполнение основного цикла.
В результате должен получиться 4х канальный вольтметр среднего значения вполне применимый в практических задачах.
Повторю алгоритм работы программы.
- Аппаратный таймер циклически запускает преобразование 4х инжектированных каналов с периодом 100 мкс.
- По окончанию преобразования АЦП генерирует прерывание, в обработчике которого считываются и усредняются измеренные значения.
- Время усреднения результатов преобразований 250 мс или 2500 выборок по 0,1 мс.
- По истечению периода усреднения измеренные значения выводятся на LCD-дисплей и через UART передаются на компьютер.
За основу я взял проект из предыдущего урока Lesson31_1. В нем уже установлена библиотека для работы с LCD-дисплеем и конфигурирован UART.
Копируем проект или создаем новый. Открываем конфигуратор STM32CubeMX.
Настраиваем АЦП. Мы будем использовать только 4 инжектированных каналов.
- Регулярные каналы запрещаем.
- Число преобразований инжектированных каналов задаем 4.
- Разрешаем режим сканирования.
Запускать АЦП будем от таймера 2. Поэтому в качестве источника внешнего запуска выбираем событие таймера 2.
Осталось задать параметры каналов в последовательности сканирования.
Результат будем считывать по прерыванию АЦП. Поэтому разрешаем его в закладке NVIC Settings.
Настраиваем таймер 2. Мы научились делать это в уроке 16.
Наша задача конфигурировать таймер, чтобы он сбрасывался каждые 100 мкс.
В окне Timers -> TIM2 -> TIM2 Mode and Configuration разрешаем тактирование от внутреннего источника.
В окне Parameter Settings задаем:
- Предделитель 719. Коэффициент деления будет равен 720, период тактирующих импульсов на выходе предделителя – 10 мкс.
- Режим счетчика – Up. Будем считать в строну увеличения.
- В регистр перезагрузки установим значение 9. Коэффициент деления будет 10, а значит период тактирования счетчика 10 мкс * 10 = 100 мкс.
- В качестве выходного события таймера выбираем перезагрузку.
Прерывание по таймеру разрешать не надо. Мы запускаем АЦП по событию таймера.
Все. Создаем проект.
Считывание результата преобразование можно производить в обработчике прерывания, как это мы делали в уроке 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
}
}
Вот как выглядит блок чтения и усреднения результатов преобразований вольтметра.
Зарегистрируйтесь и оплатите. Всего 40 руб. в месяц за доступ ко всем ресурсам сайта!- По каждому вызову функции 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;
в блоке конфигурации АЦП. При этом бит разрешения внешнего запуска для регулярных каналов остался в запрещающем состоянии.
После этого вольтметр заработал правильно.
Проект можно загрузить по ссылке
Зарегистрируйтесь и оплатите. Всего 40 руб. в месяц за доступ ко всем ресурсам сайта!
Работает с этим устройством и программа верхнего уровня из урока 28.
Проект 4х канального вольтметра с использованием АЦП в режиме сдвоенных преобразований.
Разработаем устройство подобное предыдущему проекту, только добавим условие, что пары каналов 0, 1 и 2, 3 должны считываться одновременно.
Модули АЦП будут работать в режиме сдвоенных преобразований, а точнее в режиме одновременных преобразований инжектированных каналов. Напомню, что это режим, при котором оба АЦП запускаются одновременно по сигналу запуска АЦП1.
Открываем STM32CubeMX.
Настраиваем систему тактирования, как в предыдущем проекте.
Настраиваем АЦП.
Для первого АЦП выбираем каналы IN0 и IN2.
Для второго каналы IN1 и IN3.
В обоих модулях запрещаем преобразования регулярных групп, для инжектированных задаем по два преобразования и настраиваем параметры каналов.
Теперь доступны все режимы АЦП1.
Выбираем Dual injected simultaneous mode.
Источник запуска – событие таймера 2.
Разрешаем прерывание АЦП 1.
Таймер 2 настраиваем аналогично предыдущему проекту.
Прерывание по таймеру разрешать не надо. Мы запускаем АЦП по событию таймера.
Не забудем про UART1.
Создаем проект.
В 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
Источник запуска задавать не нужно. Второй АЦП запускается от первого.
Компилируем, загружаем.
Вот мой проект.
Зарегистрируйтесь и оплатите. Всего 40 руб. в месяц за доступ ко всем ресурсам сайта!
Внешне работа устройства ничем не отличается от предыдущего проекта.
Проект простого осциллографа.
Повторим второй проект урока 29 – цифровой осциллограф. Напомню, что в нем мы создали цифровой осциллограф со следующими параметрами.
- Частота сканирования – 2 мГц.
- Длительность развертки 4 мс.
- Число выборок 8000.
- Запуск от компьютера.
Чтобы создать устройство со временем выборки 0,5 мкс (частота 2 мГц) необходимо использовать оба АЦП в режиме быстрых преобразований со смещением во времени. При этом АЦП должны работать с максимальным быстродействием. Все временные расчеты в уроке 29.
Проверку окончания преобразования и чтение результатов мы не сможем реализовать с помощью HAL-функций. Вызов функций достаточно длительная операция, а нам необходимо в цикле с периодом всего 1 мкс делать одну проверку и два чтения.
Поэтому настройку АЦП сделаем с помощью библиотеки HAL, а цикл сканирования оставим без изменений, т.е. с использованием обращений к регистрам CMSIS.
Я копировал проект Lesson29_2 и переименовал его в Lesson32_3.
Удалил блок инициализации АЦП.
Открыл проект конфигуратором CubeMX.
Настроил частоту тактирования АЦП на 14 мГц.
Теперь настраиваем АЦП.
Для первого модуля:
- выбираем канал IN0;
- разрешаем непрерывный режим преобразования;
- выбираем преобразование одного регулярного канала;
- задаем его параметры.
Для второго АЦП задаем абсолютно такую же конфигурацию.
Возвращаемся к АЦП 1 и задаем режим быстрых преобразований со смещением во времени.
Создаем проект.
Добавляем калибровку.
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);
И все. Вот мой проект цифрового осциллографа.
Зарегистрируйтесь и оплатите. Всего 40 руб. в месяц за доступ ко всем ресурсам сайта!
Проверить можно программой верхнего уровня из урока 29.
Это синусоидальный сигнал частотой 1 кГц.
Это частота 500 Гц
Сигналы подавал с генератора через неэкранированные провода, без емкостного фильтра на входе АЦП. Поэтому есть шум.
В следующем уроке начнем изучать контроллер прямого доступа к памяти (DMA).
Предыдущий урок Список уроков Следующий урок