STM32 запуск АЦП по таймеру.

STM32 запуск АЦП по таймеру.

На днях мне понадобилось раз в секунду запускать АЦП преобразование и сразу же стало понятно, что битами SMPR[2:1], которые задают количество тактов между выборками, сделать это не получится, поэтому придётся искать другой способ.
Максимальное количество тактов между выборками, которое можно задать с помощью этих битов, равно 239,5. Если к этому числу прибавить время преобразования — 12.5 тактов, то получим период преобразования равный 252 такта, в общем, до 1 секунды очень далеко.
Хотелось бы напомнить, что источником внешнего запуска для регулярного преобразования могут служить следующие события:
STM32 запуск АЦП по таймеру.

То есть запускать преобразование можно по захвату-сравнению(ССх event), переполнению таймера(TRGO), внешнему прерыванию(EXTI line), или установкой бита SWSTART. Нам подходит запуск по переполнению таймера(TRGO), осталось только разобраться как сделать, чтобы таймер генерировал тот самый сигнал для запуска АЦП, но для начала давайте инициализируем его.

void ADC_Init(void)
{
   //разрешаем тактирование порта А 
   RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
   //настраиваем вывод для работы АЦП в режим аналогового входа 
   GPIOA->CRL &= ~GPIO_CRL_CNF1;
   //так как тактовая частота АЦП не должна превышать 14MHz 
   RCC->CFGR |= RCC_CFGR_ADCPRE_DIV8; 
   //разрешаем тактирование АЦП 
   RCC->APB2ENR |= RCC_APB2ENR_ADC1EN ; 
   //запускаем калибровку и ждем пока завершится, в симуляторе это не работает, в железе делать обязательно 
   /* ADC1->CR2 |= ADC_CR2_CAL; while (!(ADC1->CR2 & ADC_CR2_CAL))*/ 
   //разрешаем прерывание от АЦП 
   NVIC->ISER[0] |= NVIC_ISER_SETENA_18; 
   //для первого канала между выборками 7.5 цикла 
   ADC1->SMPR2 = ADC_SMPR2_SMP1_0; 
   //разрешаем прерывания по окончанию преобразования 
   ADC1->CR1 = ADC_CR1_EOCIE;
   //разрешаем запуск по совпадению таймера3, разрешаем работу АЦП, разрешаем запуск внешним сигналам, 
   ADC1->CR2 &= ~ADC_CR2_EXTSEL;
   ADC1->CR2 = ADC_CR2_EXTSEL_2 | ADC_CR2_ADON | ADC_CR2_EXTTRIG ;
   //длина последовательности равна 1, первый канал 
   ADC1->SQR1 = 0x00000000; 
   ADC1->SQR2 = 0x00000000;
   ADC1->SQR3 = 0x00000001; 
}

Для того чтобы по переполнению таймера генерировался сигнал TRGO необходимо, разрешить это битами MMS: Master mode selection, тогда инициализация таймера будет выглядеть так.

void Tim3_Init(void)
{
   //разрешаем тактирование таймера
   RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;     
   TIM3->PSC = 7200 - 1;//10KHz or 0.0001s
   TIM3->ARR  = 10000; //period = 1sec
   //разрешаем генерацию TRGO
   TIM3->CR2 |= TIM_CR2_MMS_1; 
   //включаем таймер
   TIM3->CR1 |= TIM_CR1_CEN;
}

Теперь это всё объединим.

#include "stm32f10x.h" 

unsigned temp;

void ADC1_2_IRQHandler(void)
{
   if(ADC1->SR & ADC_SR_EOC)
   {
      //записываем результат в переменную 
      temp =ADC1->DR;
   }
   //сбрасываем все флаги в регистре статуса
   ADC1->SR=0;
}
int main(void)
{
   ADC_Init();
   Tim3_Init();
   while(1)
   {
		
   }
}

В результате каждую секунду будет запускаться АЦП, а получать результат преобразования можно либо в прерывании, по окончанию преобразования, либо используя DMA.
Надо сказать, что в симуляторе программа не заработала как положено, прерывания то возникали одно друг за другим с паузой в несколько милисекунд, то раз в секунду. Затем попробовал отладить программу в железе, подключившись по jtag и поставив breakpoint на строчке temp =ADC1->DR;, но эффекта это не дало, программа работала так же как в симуляторе. Поискав ошибку и не найдя её, попробовал для отладки в реальном времени использовать вывод SWO интерфейса SWD и как оказалось код работает почти правильно. В прерывании после проверки на окончание преобразования, выводил сообщение, типо "+1", итого за 10 секунд, мне выдало 11 сообщений, то есть сразу после включения таймера запускается преобразование, почему так происходит мне пока не известно, как разберусь обязательно исправляю.
комментарии
2