В уроке расскажу об использовании HAL-библиотеки для настройки конфигурации и управления модулем АЦП. В качестве примеров реализуем проекты из предыдущих уроков с применением HAL-функций. Разберемся, как настраивать АЦП с помощью конфигуратора STM32CubeMX.
Предыдущий урок Список уроков Следующий урок
В предыдущих уроках мы подробно разобрали все режимы работы АЦП, его функциональные возможности, управляющие регистры. Если вся эта информация уложилась у вас в голове, хотя бы на уровне принципов управления и возможностей АЦП, то работа с ним через HAL-функции не представит ни малейших затруднений. Имена функций и аргументов сами подскажут, как их использовать. А настройка конфигурации с помощью STM32CubeMX сведется к выбору закладок меню.
Я все время беспокоюсь, что мои уроки об использовании HAL-библиотеки будут похожи на большинство уроков из Интернета, построенных по принципу – сюда задаем то-то, здесь пишем то-то и “Вуаля!”. Они и получаются похожими, потому что смысл операций с АЦП я объяснял в предыдущих уроках. А в этом показываю более простой способ их реализации. Но этот материал не может быть полноценно воспринят без предыдущих уроков.
Давайте реализуем все проекты уроков 27-30 с использованием библиотеки HAL. Увидите насколько это просто.
Еще. В справочнике библиотеки HAL появилось описание функций работы с АЦП. Я не буду в уроке описывать их форматы, смотрите в справочнике.
Настройка конфигурации АЦП с помощью STM32CubeMX.
Я подробно расскажу о задании конфигурации через CubeMX и одновременно настрою модуль АЦП на первый пример урока 27, а именно режим один канал, однократное преобразование.
Открываем STM32CubeMX.
Разрешаем работу генератора.
Мы будем использовать аналоговые входы IN0 … IN3. Выбираем их в поле Analog -> ADC1 -> Mode.
Переходим в окно настройки тактирования. Задаем основную частоту тактирования 72 мГц.
Предделитель АЦП (ADC Prescaler) устанавливаем на коэффициент деления 6. Частота тактирования АЦП равна 12 мГц.
Возвращаемся к окну конфигурации АЦП Configuration -> Parameter Settings.
В поле Mode выбираем Independent mode – независимый режим работы АЦП.
Поле Data Alignment задает режим выравнивания результата преобразования. Оставляем выравнивание по правому краю (Right alignment).
Следующие 3 поля разрешают или запрещают следующие режимы:
- Scan Conversion Mode - режим сканирования каналов;
- Continues Conversion Mode - режим непрерывного преобразования;
- Discontinuous Conversion Mode - прерывистый режим.
Напомню таблицу основных режимов.
Состояние битов | Режим | |
CONT | SCAN | |
0 | 0 | Один канал, однократное преобразование |
0 | 1 | Несколько каналов, однократное преобразование |
1 | 0 | Один канал, непрерывное преобразование |
1 | 1 | Несколько каналов, непрерывное преобразование |
Для однократного преобразования одного канала запрещаем все поля.
Ниже две корневые закладки, ADC_Regular_ConversionMode и ADC_Injected_ConversionMode определяют конфигурацию аналоговых входов для регулярных и инжектированных групп.
В этом проекте мы собираемся использовать только один регулярный канал.
Открываем закладку для регулярной группы.
- Enable Regular Conversions – разрешает преобразование регулярной группы.
- Number Of Conversion – количество каналов сканирования. Сейчас задаем 1.
- External Trigger Conversion Source – источник запуска преобразований. Выбираем программный запуск от бита SWSTART.
- Rank – последовательность преобразования каналов. У нас в параметре Number Of Conversion задан только один кагал. Поэтому только одно поле Rank.
Открываем его и устанавливаем:
- Channel – номер канала для этого преобразования.
- Sampling Time – время выборки сигнала.
В этом проекте мы не используем прерывания и DMA. Все, настройка конфигурации АЦП закончена.
Разрешим работу UART в асинхронном режиме со скоростью 9600 бит/сек.
Создаем проект. У меня Lesson31_1.
Думаю, вы уже оценили простоту конфигурирования АЦП таким образом. Мы не просматривали бесконечные битовые поля регистров, боясь установить что-то не так или забыть про что-то. Мы просто выбрали из того, что нам предложил конфигуратор CubeMX.
В принципе, можно настраивать конфигурацию АЦП с помощью CubeMX, а работать с АЦП через регистры CMSIS.
Давайте посмотрим и осознаем, что сделал конфигуратор. Это необходимо для понимания работы HAL-библиотеки. Возможно, нам в будущем потребуется изменять режимы АЦП динамически или захочется задать его конфигурацию без использования STM32CubeMX.
В файле stm32f1xx_hal_msp.c :
Разрешается тактирование АЦП1 и порта GPIOA.
__HAL_RCC_ADC1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
Конфигурируются выводы GPIOA 0 … 3 как аналоговые входы.
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
АЦП конфигурируется с помощью функции
HAL_ADC_Init (ADC_HandleTypeDef * hadc)
Функция имеет один аргумент – структуру параметров конфигурации типа ADC_HandleTypeDef. А одним из элементов этой структуры является другая структура типа ADC_InitTypeDef. Она определяет основные параметры АЦП. Для конфигурации АЦП необходимо задать значения полей этих структур и вызвать функцию инициализации HAL_ADC_Init.
Разберемся на примере нашего проекта. Все происходит в файле main.c.
Создается экземпляр структуры типа ADC_HandleTypeDef с именем hadc1.
ADC_HandleTypeDef hadc1;
Поле Instance определяет базовый адрес АЦП, т.е. какой именно модуль АЦП будет использоваться.
hadc1.Instance = ADC1;
Дальше заданы поля структуры Init. Это параметры АЦП, которые мы устанавливали в STM32CubeMX.
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 1;
И в завершение вызывается функция инициализации.
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
Для установки параметров каналов используется функция
HAL_ADC_ConfigChannel (ADC_HandleTypeDef * hadc, ADC_ChannelConfTypeDef * sConfig).
Создается экземпляр структуры типа ADC_ChannelConfTypeDef.
ADC_ChannelConfTypeDef sConfig = {0};
В нем выбирается канал, параметры которого в данный момент устанавливаются.
sConfig.Channel = ADC_CHANNEL_0;
Задаются параметры канала
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_28CYCLES_5;
И вызывается функция конфигурации канала.
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
Если нужно настроить несколько каналов, то функция вызывается для каждого канала.
Добавим еще калибровку АЦП.
/* USER CODE BEGIN 2 */
HAL_ADCEx_Calibration_Start(&hadc1);
На этом настройка конфигурации АЦП закончена.
Все эти операции сделал за нас конфигуратор CubeMX, но ничего не мешает выполнить их нам самим или изменить параметры в ходе выполнения программы.
Еще нам потребуется определение опорного напряжения АЦП, которое будем использовать при вычислениях измеренного напряжения.
/* USER CODE BEGIN PD */
#define VREF 3.3065 // напряжение ИОН
Мы собираемся выводить информацию на LCD-дисплей. Добавим в проект библиотеку для работы с ним из урока 25.
Скопируем в проект папку Libraries из проекта предыдущего урока. В ней содержатся библиотеки для работы с LCD-дисплеем.
В 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-дисплея.
Мы создали шаблон проекта для урока. Дальше будем разрабатывать конкретные проекты.
Режим один канал, однократное преобразование.
Конфигурацию АЦП для режима однократного преобразования одного канала мы уже установили.
В основном цикле:
- запускаем АЦП;
- ждем окончания преобразования;
- считываем результат;
- пересчитываем его в напряжение;
- выводим на LCD-дисплей;
- передаем на компьютер через UART.
- Для разрешения работы АЦП и запуска преобразования используется функция HAL_ADC_Start.
- Функция HAL_ADC_PollForConversion ожидает окончание преобразования. Программа в ней "подвисает". Второй аргумент – время тайм-аута в мс.
- Чтение результата преобразования регулярных каналов происходит с помощью функции HAL_ADC_GetValue.
Остальные операции цикла аналогичны действиям из проектов предыдущих уроков.
Проект можно загрузить по ссылке.
Зарегистрируйтесь и оплатите. Всего 40 руб. в месяц за доступ ко всем ресурсам сайта!
Один канал, однократное преобразование инжектированного канала.
Сделаем тоже самое для инжектированного канала. В этом случае используются другие HAL-функции.
Я копировал проект с именем Lesson31_2 и открыл в нем файл Lesson31_1.ioc.
Изменяем конфигурацию АЦП через CubeMX.
Запрещаем работу регулярной группы, устанавливаем параметры для инжектированной.
- Число преобразований -1.
- Запуск – программный.
- Автозапуск – запрещен.
- Очередь каналов – 1.
- Канал – 0.
- Время выборки – 28,5 циклов.
- Смещение нуля – 0.
Создаем проект.
Добавился блок конфигурации инжектированных каналов.
sConfigInjected.InjectedChannel = ADC_CHANNEL_0;
sConfigInjected.InjectedRank = ADC_INJECTED_RANK_1;
sConfigInjected.InjectedNbrOfConversion = 1;
sConfigInjected.InjectedSamplingTime = ADC_SAMPLETIME_28CYCLES_5;
sConfigInjected.ExternalTrigInjecConv = ADC_INJECTED_SOFTWARE_START;
sConfigInjected.AutoInjectedConv = DISABLE;
sConfigInjected.InjectedDiscontinuousConvMode = DISABLE;
sConfigInjected.InjectedOffset = 0;
if (HAL_ADCEx_InjectedConfigChannel(&hadc1, &sConfigInjected) != HAL_OK)
{
Error_Handler();
}
Для определения параметров используется структура sConfigInjected типа ADC_InjectionConfTypeDef.
Параметры расписаны в справочнике, хотя и без него все понятно.
Сама установка производится функцией HAL_ADCEx_InjectedConfigChannel.
Для запуска, ожидания завершения преобразования и чтения результата инжектированной группы используются другие функции. Это:
- HAL_ADCEx_InjectedStart – запуск;
- HAL_ADCEx_InjectedPollForConversion – ожидание;
- HAL_ADCEx_InjectedGetValue – чтение результата.
У функции HAL_ADCEx_InjectedGetValue добавился еще один аргумент – номер инжектированного канала.
Основной цикл программы выглядит так.
Зарегистрируйтесь и оплатите. Всего 40 руб. в месяц за доступ ко всем ресурсам сайта!Полностью проект можно загрузить по ссылке.
Зарегистрируйтесь и оплатите. Всего 40 руб. в месяц за доступ ко всем ресурсам сайта!
В обоих последних примерах функции ожидания завершения преобразования блокируют выполнение программы.
Один канал, непрерывное преобразование.
Вернемся к проекту Lesson31_1.
Единственное изменение, которое мы сделаем – разрешим непрерывное преобразование.
Это можно сделать в строке
hadc1.Init.ContinuousConvMode = ENABLE;
или с помощью CubeMX.
Теперь достаточно запустить преобразование один раз. АЦП будет автоматически конвертировать значение сигнала, и загружать результат в регистр DR. Остается только в цикле считывать его, вычислять значение напряжения и выводить на дисплей.
Зарегистрируйтесь и оплатите. Всего 40 руб. в месяц за доступ ко всем ресурсам сайта!Здесь окончательный проект.
Зарегистрируйтесь и оплатите. Всего 40 руб. в месяц за доступ ко всем ресурсам сайта!
В отличие от предыдущих двух проектов, в этом программа не блокируется для ожидании результата конверсии.
Непрерывное преобразование одного регулярного и одного инжектированного каналов.
Можно запустить непрерывное преобразование для регулярного и инжектированного канала одновременно. Тогда получим два регистра с постоянно обновляемыми значениями сигналов на двух аналоговых входах.
Напомню, что у инжектированных каналов нет режима непрерывных преобразований, но есть возможность установить режим их автоматического запуска после конверсии регулярной группы.
Открываем CubeMx.
- Разрешаем работу и регулярной, и инжектированной групп на преобразование одного канала.
- Для регулярной группы выбираем канал 0, для инжектированной - канал 1.
- Разрешаем режим непрерывных преобразований (Continues Converson Mode).
- Разрешаем режим автозапуска инжектированной группы (Injected Conversion Mode).
В программе один раз запускаем преобразование, и после этого у нас есть два регистра, в которых хранятся всегда “свежие” значения сигналов на двух аналоговых каналах.
Вот, как выглядит основной цикл.
Зарегистрируйтесь и оплатите. Всего 40 руб. в месяц за доступ ко всем ресурсам сайта!Здесь можно загрузить весь проект.
Зарегистрируйтесь и оплатите. Всего 40 руб. в месяц за доступ ко всем ресурсам сайта!
Чтение результатов также происходит в неблокирующем режиме.
Несколько каналов, однократное преобразование.
В этом примере по программному запуску будут происходить 5 преобразований: для одного регулярного канала и четырех инжектированных.
Настраиваем АЦП с помощью STM32CubeMX.
- Разрешаем режим сканирования и настраиваем регулярную группу на 1 преобразование канала IN4.
- Непрерывный режим запрещаем.
Для инжектированной группы устанавливаем 4 преобразования, разрешаем автозапуск и заполняем параметры очереди сканирования каналы 0…3.
В основном цикле:
- Запускаем преобразование регулярной группы.
HAL_ADC_Start(&hadc1); // запуск преобразования
- Ожиданием окончания сканирования всех каналов.
HAL_ADCEx_InjectedPollForConversion(&hadc1, 10); // ожидание окончания преобразования
- Считываем результаты преобразований, вычисляем напряжение и выводим на дисплей.
Вот этот проект.
Зарегистрируйтесь и оплатите. Всего 40 руб. в месяц за доступ ко всем ресурсам сайта!
Работа с АЦП происходит в блокирующем режиме. При ожидании окончания преобразований программа зависает.
Несколько каналов, непрерывное преобразование.
Модифицируем предыдущий проект таким образом, что преобразования будут происходить в непрерывном режиме. В результате в пяти регистрах будут всегда храниться “свежие” значения сигналов на входах пяти каналов IN0 … IN4.
В настройках АЦП одно изменение - разрешим непрерывный режим.
В программе:
- Размещаем функцию запуска до основного цикла.
- Функцию ожидания окончания преобразования убираем.
- В цикле считываем значения измерений из регистров результатов и выводим данные на дисплей.
Полный проект можно загрузить по ссылке.
Зарегистрируйтесь и оплатите. Всего 40 руб. в месяц за доступ ко всем ресурсам сайта!
Еще раз подчеркну, что простота работы с АЦП через HAL-функции основана на понимании всех режимов и управляющих параметров АЦП, которым были посвящены предыдущие уроки.
В следующем уроке продолжим работу с АЦП через функции библиотеки HAL.
Нашёл у Вас в 21-м уроке функцию HAL_TIM_PeriodElapsedCallback(). Подскажите, а в чём преимущества перед обычным обработчиком прерывания? И как определять какой таймер вызвал данную колбэк функцию, сравнивать в данной функции указатели?
Здравствуйте!
Вызов функции инициируется прерыванием. Преимущество — системный подход к программированию.
Определить таймер можно так:
if(htim1->Instance == TIM1)
{
// таймер 1
}