АЦП в режиме прерываний (interrupt)

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

Напишем программу, которая будет анализировать сигнал на контакте PA0, и в зависимости от полученной величины зажигать или гасить штатный светодиод на контакте PC13. В отличие от предыдущей программы, теперь мы заставим АЦП осуществлять преобразования строго каждые 20мс в фоновом режиме, а результат оценивать только после возникновения соответствующего прерывания.

Настройка АЦП

Нажимаем правую кнопку мыши на PA0 и выбираем пункт ADC1_IN0. Выбор этого пункта означает, что мы подключаем данный контакт к каналу №0 аппаратного АЦП №1. Теперь к PA0 можно подключить нужный нам прибор с аналоговым выходом, с которого мы в дальнейшем будем снимать данные.

STM32 АЦП и DMA. Настройка АЦП

Теперь настроим событие, по которому АЦП будет начинать преобразование. Пусть это будет происходить каждый раз, когда на первом канале таймера №1 возникает событие Capture Compare.

Заходим на вкладку Parameter Settings, и в поле External Trigger Conversion Source выбираем пункт Timer 1 Capture Compare 1 event.

STM32 АЦП и DMA. Настройка события запуска АЦП

На вкладке NVIC Settings активируем прерывание.

STM32 АЦП. Настройка прерывания

Настройка таймера

Настроим генерацию событий Capture Compare на первом канале таймера №1.

В левом меню жмём на TIM1. В поле Channel1 выбираем Output Compare No Output. Это означает, что таймер будет генерировать нужные нам события, ни на какие физические контакты этот сигнал выдавать не будет.

В поле Clock Source выбираем Internal Clock.

По умолчанию, на в среде CubeIDE тактирование микроконтроллера stm32f103c8t6 осуществляется на частоте 8 МГц, без внешнего резонатора. Так и оставим, высокие скорости нам не требуются.

Пусть таймер будет генерировать событие каждые 20мс (50 Гц). Выставляем предделитель на 800-1, а период счетчика на 200-1. Это и даст нам нужную частоту.

STM32 АЦП и DMA. Настройка события capture compare в таймере

Там же в параметрах таймера, находим блок Output Compare No Output и выбираем режим (Mode) Toggle on match.

STM32 АЦП и DMA. Настройка события capture compare в таймере

Индикатор

Настроим штатный индикатор, который на отладочных платах обычно привязан к контакту PC13.

Жмём на контакт PC13 и выбираем режим GPIO_Output. Теперь этот контакт будет работать на выход, и мы сможем выставлять на нем логический уровень. Затем в левом меню открываем раздел GPIO, прописываем этому контакту псевдоним LED в поле User Label.

STM32 настройка светодиода PC13

С настройки покончили. Сохраняем проект и переходим к исходному коду.

Программа

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

Начнем с объявления полезных переменных. Нам понадобится переменная uint16_t для хранения результата и флаг, который мы будем поднимать при возникновении прерывания.

/* USER CODE BEGIN PV */

volatile uint8_t flag_adc = 0;
volatile uint16_t pot_adc;
/* USER CODE END PV */

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

В начале программы, перед суперциклом, запускаем процедуру самокалибровки АЦП — HAL_ADCEx_Calibration_Start. Запускаем генерацию импульсов таймером на первом канале — HAL_TIM_OC_Start. Запускаем АЦП в режиме прерываний — HAL_ADC_Start_IT.

  /* USER CODE BEGIN 2 */

HAL_ADCEx_Calibration_Start(&hadc1); // калибровка АЦП
HAL_TIM_OC_Start(&htim1, TIM_CHANNEL_1); // запуск таймера для опроса АЦП
HAL_ADC_Start_IT(&hadc1);
/* USER CODE END 2 */

В теле суперцикла проверяем, поднят ли флаг flag_adc, и в случае истинности данного условия — анализируем значение АЦП и меняем состояние светодиода при необходимости.

Будем зажигать светодиод при достижении значения 2100 на потенциометре, и выключать его при снижении до 2000. Разница в 100 единиц сделана для создания гистерезиса — чтобы исключить мерцание светодиода в пограничном состоянии потенциометра.

  /* USER CODE BEGIN WHILE */

while (1)
{
if( flag_adc ){
flag_adc = 0;
if( pot_adc > 2100 ){
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
} else
if( pot_adc < 2000 ){
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
}
}
/* USER CODE END WHILE */

А где будет выставляться флаг, и откуда возьмется pot_adc? Всё это будет происходить внутри функции-обработчика.

/* USER CODE BEGIN 4 */

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc){
flag_adc = 1;
pot_adc = HAL_ADC_GetValue(&hadc1);
}
/* USER CODE END 4 */

Изменено: