АЦП в режиме прерываний (interrupt)
Предположим, что наша программа не терпит, чтобы суперцикл был занят работой АЦП. При таком условии необходимо использовать режим работы, в котором запуск АЦП и анализ аналоговых сигналов производился в фоновом режиме.
Напишем программу, которая будет анализировать сигнал на контакте PA0, и в зависимости от полученной величины зажигать или гасить штатный светодиод на контакте PC13. В отличие от предыдущей программы, теперь мы заставим АЦП осуществлять преобразования строго каждые 20мс в фоновом режиме, а результат оценивать только после возникновения соответствующего прерывания.
Настройка АЦП
Нажимаем правую кнопку мыши на PA0 и выбираем пункт ADC1_IN0. Выбор этого пункта означает, что мы подключаем данный контакт к каналу №0 аппаратного АЦП №1. Теперь к PA0 можно подключить нужный нам прибор с аналоговым выходом, с которого мы в дальнейшем будем снимать данные.
Теперь настроим событие, по которому АЦП будет начинать преобразование. Пусть это будет происходить каждый раз, когда на первом канале таймера №1 возникает событие Capture Compare.
Заходим на вкладку Parameter Settings, и в поле External Trigger Conversion Source выбираем пункт Timer 1 Capture Compare 1 event.
На вкладке NVIC Settings активируем прерывание.
Настройка таймера
Настроим генерацию событий Capture Compare на первом канале таймера №1.
В левом меню жмём на TIM1. В поле Channel1 выбираем Output Compare No Output. Это означает, что таймер будет генерировать нужные нам события, ни на какие физические контакты этот сигнал выдавать не будет.
В поле Clock Source выбираем Internal Clock.
По умолчанию, на в среде CubeIDE тактирование микроконтроллера stm32f103c8t6 осуществляется на частоте 8 МГц, без внешнего резонатора. Так и оставим, высокие скорости нам не требуются.
Пусть таймер будет генерировать событие каждые 20мс (50 Гц). Выставляем предделитель на 800-1, а период счетчика на 200-1. Это и даст нам нужную частоту.
Там же в параметрах таймера, находим блок Output Compare No Output и выбираем режим (Mode) Toggle on match.
Индикатор
Настроим штатный индикатор, который на отладочных платах обычно привязан к контакту PC13.
Жмём на контакт PC13 и выбираем режим GPIO_Output. Теперь этот контакт будет работать на выход, и мы сможем выставлять на нем логический уровень. Затем в левом меню открываем раздел GPIO, прописываем этому контакту псевдоним LED в поле User Label.
С настройки покончили. Сохраняем проект и переходим к исходному коду.
Программа
После генерации шаблона, приступаем к написанию своего кода, строго в выделенных для этого разделах.
Начнем с объявления полезных переменных. Нам понадобится переменная 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 */