Arduino STM32 - таймеры








Это третья часть посвящённая плате Blue Pill (Arduino STM32).

Первая часть — прошивка
Вторая часть — внешние прерывания




Таймер — это адски крутая штуковина просто счетчик, который при достижении заданного значения может вызывать определённые события — прерывания, измерение времени между импульсами, генерировать ШИМ, отвечает за работу интерфейсов типа I2C, и ещё кучу всяких полезных делишек может делать.


Микроконтроллер STM32F103х имеет на борту четыре 16-ти битных таймера

• три таймера — TIM2, TIM3, TIM4, общего назначения (general-purpose timers).
• один продвинутый таймер — TIM1, с расширенными возможностями (advanced-control timers).


А так же…

• два WDT (WatchDog Timer).
• один SysTick Timer.



16-ти битный таймер умеет считать в диапазоне от 0 до 65535 (это значение называется «переполнение» см. ниже). То есть, говоря простым языком, в пямяти есть переменная, которая увеличивается на единицу с каждым следующим «тиком» таймера. При достижении заданного пользователем значения (если значение не задавать, то счёт идет до максимального значения — 65535), таймер инициирует какое-либо событие, после чего сбрасывается в ноль, и отсчёт начинается заново.

Таймеры независимы друг от друга.

Таймеры тактируются (то есть «тикают») от системной частоты и соответственно не потребляют ресурсов.
Если не использовать дополнительных настроек, то таймер будет считать со скоростью 72Мгц (72 миллиона «тиков» в секунду). То есть до максимального значения (65535) он досчитает за ~0.9 миллисекунды. В общем шустрый парнишка.




Настройки таймера (режимы)

Так как настроек и режимов достаточно много, то я буду описывать их «от простого к сложному». Каким образом и при каких обстоятельствах применять те или иные режимы решать Вам.

На всякий случай:
мс — миллисекунда (1000мс = 1сек).
мкс — микросекунда (1000000мкс = 1000мс = 1сек).




Предделитель


Как написано выше, если запустить таймер без настроек, то он будет считать очень быстро, что конечно же не всегда целесообразно. Для того чтобы понизить скорость отсчёта, нужно использовать предделитель.

Предделитель делит системную частоту на любое число от 1 до 65536. Например, если установить значение предделителя 3600, то таймер будет «тикать» со скоростью 20000 раз в секунду (72МГц / 3600 = 20КГц). То есть он будет доходить до максимального значения (65536) примерно за 3.22 секунды.


Для примера используем таймер №3, установим делитель 720 (72МГц / 720 = 100КГц) и активируем прерывание, которое будет происходить каждый раз когда таймер досчитает до мах. значения (примерно каждые 650мс). В обработчике будем менять состояние светодиода на противоположное…


volatile bool LEDOn13 = 0;

void setup()
{
  pinMode(LED_BUILTIN, OUTPUT); // PC13
  Timer3.pause(); // останавливаем таймер перед настройкой
  Timer3.setPrescaleFactor(720); // устанавливаем делитель
  Timer3.attachInterrupt(TIMER_UPDATE_INTERRUPT, func_tim_3); // активируем прерывание
  Timer3.refresh(); // обнулить таймер 
  Timer3.resume(); // запускаем таймер
}

void loop() 
{}

void func_tim_3() // обработчик прерывания
{
  digitalWrite(LED_BUILTIN, (LEDOn13 = !LEDOn13));
} 

TIMER_UPDATE_INTERRUPT — режим таймера (автоматическое обновление и вызов прерывания).

Функции pause(), refresh() и resume() не обязательны, я их просто обозначил. Эти функции можно использовать в любом месте программы, например, если где-то в основном цикле нужно приостановить, запустить или обнулить таймер.



Переполнение


Переполнением называется то самое значение (65535), до которого считает таймер. Его можно менять по своему усмотрению.
Если переполнение не указано в коде, как в примере выше, то по умолчанию устанавливается мах. значение — 65535.

Если мы укажем в нашем коде переполнение равное 16000, тогда таймер будет обнуляться и начинать отсчёт заново достигнув этой цифры. Таким образом светодиод будет моргать в четыре раза чаще…


volatile bool LEDOn13 = 0;

void setup()
{
  pinMode(LED_BUILTIN, OUTPUT); // PC13
  Timer3.pause(); // останавливаем таймер перед настройкой
  Timer3.setPrescaleFactor(720); // устанавливаем делитель
  Timer3.setOverflow(16000); // переполнение
  Timer3.attachInterrupt(TIMER_UPDATE_INTERRUPT, func_tim_3); // активируем прерывание
  Timer3.refresh(); // обнулить таймер 
  Timer3.resume(); // запускаем таймер
}

void loop() 
{}

void func_tim_3() // обработчик прерывания
{
  digitalWrite(LED_BUILTIN, (LEDOn13 = !LEDOn13));
} 




Оперируя предделителем и переполнением можно получать различные интервалы времени. Однако если нет желания заморачиваться с подсчётами, то можно функции setPrescaleFactor() и setOverflow() заменить одной функцией — setPeriod(), которая сделает всё сама, ей нужно только указать время в микросекундах…


volatile bool LEDOn13 = 0;

void setup()
{
  pinMode(LED_BUILTIN, OUTPUT); // PC13
  Timer3.pause(); // останавливаем таймер перед настройкой
  Timer3.setPeriod(500000); // время в микросекундах (500мс)
  Timer3.attachInterrupt(TIMER_UPDATE_INTERRUPT, func_tim_3); // активируем прерывание
  Timer3.refresh(); // обнулить таймер 
  Timer3.resume(); // запускаем таймер
}

void loop() 
{}

void func_tim_3() // обработчик прерывания
{
  digitalWrite(LED_BUILTIN, (LEDOn13 = !LEDOn13));
} 

Таймер будет срабатывать каждые 500мс.

Эту функцию стоило бы описать самой первой, так как она самая простая, но тогда было бы непонятно, что она объединяет в себе настройки предделителя и переполнения.


Максимальная задержка ~59сек — setPeriod(59000000).



Вернёмся ко второму примеру и добавим функцию — setCount(). Эта функция в некотором роде противоположность setOverflow(), она указывает таймеру с какого места нужно начинать отсчёт.

Допустим что переполнение у нас равно 60000, то есть счётчик считает от 0 до 60000. Теперь если в обработчике прерывания дописать — Timer3.setCount(59000), то отсчёт будет вестись не от нуля, а от 59000. Соответственно таймер переполнится очень быстро.

Функция setCount() устанавливает новое значение только один раз, при следующей итерации таймер снова будет отсчитывать от нуля, поэтому её нужно вызывать каждый раз…


volatile int i = 0;

volatile bool LEDOn13 = 0;

void setup()
{
  pinMode(LED_BUILTIN, OUTPUT); // PC13
  Timer3.pause(); // останавливаем таймер перед настройкой
  Timer3.setPrescaleFactor(1720); // устанавливаем делитель
  Timer3.setOverflow(60000); // переполнение
  Timer3.attachInterrupt(TIMER_UPDATE_INTERRUPT, func_tim_3); // активируем прерывание
  Timer3.refresh(); // обнулить таймер 
  Timer3.resume(); // запускаем таймер
}

void loop() 
{}

void func_tim_3() // обработчик прерывания
{
  digitalWrite(LED_BUILTIN, (LEDOn13 = !LEDOn13));
  
  i++;
  if(i > 6 && i < 91) Timer3.setCount(59000);
  if(i > 90 && i < 101) Timer3.setCount(35000);
  if(i > 100) Timer3.setCount(0);
} 

Сначала светик мигнёт три раза с частотой заданой в setup(), потом заморгает очень быстро, потом медленней, и на конец вернётся к изначальной частоте.



Теперь пришло время разобраться с каналами таймеров…


У таймеров TIM1, TIM2, TIM3, TIM4 имеется в наличии по четыре канала ввода/вывода (TIMER_CH1...TIMER_CH4). Каналы могут работать в режимах — захват, сравнение, генерировать ШИМ и одиночные импульсы.


Сравнение


Работа каналов в режиме сравнения, это когда в специальную ячейку (для каждого канала своя ячейка) памяти помещается число от 0 до 65535, а таймер ведя отсчёт постоянно сравнивает своё значение со значением в ячейке. Как только значения совпадают, то тут же вызывается какое-либо событие, например прерывание. Таймер при этом продолжает считать пока не переполнится, после чего цикл повторяется.

Таким образом, один таймер может выполнить несколько действий с разными интервалами времени в рамках одного цикла переполнения.

В примере ниже происходит следующее: таймер №3 будет считать от 0 до 14000 (переполнение), и после каждого «тика» сравнивать своё значение со значениями сравнений каналов (в примере я задействовал все четыре канала). При каждом совпадении значений будут генерироваться прерывания…

пример
volatile bool LEDOn10 = 0;
volatile bool LEDOn11 = 0;
volatile bool LEDOn12 = 0;
volatile bool LEDOn13 = 0;

void setup()
{
  Serial.begin(115200);

  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(PB10, OUTPUT); 
  pinMode(PB11, OUTPUT);
  pinMode(PB12, OUTPUT);

  Timer3.setMode(TIMER_CH1, TIMER_OUTPUT_COMPARE);
  Timer3.setMode(TIMER_CH2, TIMER_OUTPUT_COMPARE);
  Timer3.setMode(TIMER_CH3, TIMER_OUTPUT_COMPARE);
  Timer3.setMode(TIMER_CH4, TIMER_OUTPUT_COMPARE);
  Timer3.pause();
  Timer3.setPrescaleFactor(7200);
  Timer3.setOverflow(14000); // счетчик считает от 0 до 14000

  Timer3.setCompare(TIMER_CH1, 3000); // сравнение
  Timer3.attachInterrupt(TIMER_CH1, func_1);

  Timer3.setCompare(TIMER_CH2, 6000); 
  Timer3.attachInterrupt(TIMER_CH2, func_2);

  Timer3.setCompare(TIMER_CH3, 9000); 
  Timer3.attachInterrupt(TIMER_CH3, func_3);

  Timer3.setCompare(TIMER_CH4, 11000); 
  Timer3.attachInterrupt(TIMER_CH4, func_4);

  Timer3.resume();
}

void loop() 
{}

void func_1(void) 
{
  Serial.println("Compare CH1"); 
  digitalWrite(PB10, (LEDOn10 = !LEDOn10));
} 

void func_2(void) 
{
  Serial.println("Compare CH2"); 
  digitalWrite(PB11, (LEDOn11 = !LEDOn11));
} 

void func_3(void) 
{
  Serial.println("Compare CH3"); 
  digitalWrite(PB12, (LEDOn12 = !LEDOn12));
} 

void func_4(void) 
{
  Serial.println("Compare CH4"); 
  digitalWrite(LED_BUILTIN, (LEDOn13 = !LEDOn13));
} 




… при этом ничто не мешает использовать ещё и прерывание по переполнению.

пример
volatile bool LEDOn10 = 0;
volatile bool LEDOn11 = 0;
volatile bool LEDOn12 = 0;
volatile bool LEDOn13 = 0;
volatile bool LEDOn15 = 0;


void setup()
{
  Serial.begin(115200);

  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(PB10, OUTPUT); 
  pinMode(PB11, OUTPUT);
  pinMode(PB12, OUTPUT);
  pinMode(PB15, OUTPUT);

  Timer3.setMode(TIMER_CH1, TIMER_OUTPUT_COMPARE);
  Timer3.setMode(TIMER_CH2, TIMER_OUTPUT_COMPARE);
  Timer3.setMode(TIMER_CH3, TIMER_OUTPUT_COMPARE);
  Timer3.setMode(TIMER_CH4, TIMER_OUTPUT_COMPARE);
  Timer3.pause();
  Timer3.setPrescaleFactor(7200);
  Timer3.setOverflow(14000); // счетчик считает от 0 до 14000

  Timer3.setCompare(TIMER_CH1, 3000); // сравнение
  Timer3.attachInterrupt(TIMER_CH1, func_1);

  Timer3.setCompare(TIMER_CH2, 6000); 
  Timer3.attachInterrupt(TIMER_CH2, func_2);

  Timer3.setCompare(TIMER_CH3, 9000); 
  Timer3.attachInterrupt(TIMER_CH3, func_3);

  Timer3.setCompare(TIMER_CH4, 11000); 
  Timer3.attachInterrupt(TIMER_CH4, func_4);

  Timer3.attachInterrupt(TIMER_UPDATE_INTERRUPT, func_5); // прерывание по переполнению
  Timer3.resume();
}

void loop() 
{}

void func_1(void) 
{
  Serial.println("Compare CH1"); 
  digitalWrite(PB10, (LEDOn10 = !LEDOn10));
} 

void func_2(void) 
{
  Serial.println("Compare CH2"); 
  digitalWrite(PB11, (LEDOn11 = !LEDOn11));
} 

void func_3(void) 
{
  Serial.println("Compare CH3"); 
  digitalWrite(PB12, (LEDOn12 = !LEDOn12));
} 

void func_4(void) 
{
  Serial.println("Compare CH4"); 
  digitalWrite(LED_BUILTIN, (LEDOn13 = !LEDOn13));
} 

void func_5(void) 
{
  Serial.println("Overflow"); 
  digitalWrite(PB15, (LEDOn15 = !LEDOn15));
}






Поскольку в рамках одного цикла интервал получается очень маленьким, то можно просто добавить переменную, которая будет увеличиваться при каждом прерывании и событие произойдёт по достижении нужного значения…

пример
volatile bool LEDOn10 = 0;
volatile bool LEDOn11 = 0;
volatile bool LEDOn12 = 0;
volatile bool LEDOn13 = 0;
volatile bool LEDOn15 = 0;

volatile int count = 0;


void setup()
{
  Serial.begin(115200);

  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(PB10, OUTPUT); 
  pinMode(PB11, OUTPUT);
  pinMode(PB12, OUTPUT);
  pinMode(PB15, OUTPUT);

  Timer3.setMode(TIMER_CH1, TIMER_OUTPUT_COMPARE);
  Timer3.setMode(TIMER_CH2, TIMER_OUTPUT_COMPARE);
  Timer3.setMode(TIMER_CH3, TIMER_OUTPUT_COMPARE);
  Timer3.setMode(TIMER_CH4, TIMER_OUTPUT_COMPARE);
  Timer3.pause();
  Timer3.setPrescaleFactor(7200);
  Timer3.setOverflow(14000); // счетчик считает от 0 до 14000

  Timer3.setCompare(TIMER_CH1, 3000); // сравнение
  Timer3.attachInterrupt(TIMER_CH1, func_1);

  Timer3.setCompare(TIMER_CH2, 6000); 
  Timer3.attachInterrupt(TIMER_CH2, func_2);

  Timer3.setCompare(TIMER_CH3, 9000); 
  Timer3.attachInterrupt(TIMER_CH3, func_3);

  Timer3.setCompare(TIMER_CH4, 11000); 
  Timer3.attachInterrupt(TIMER_CH4, func_4);

  Timer3.attachInterrupt(TIMER_UPDATE_INTERRUPT, func_5); // прерывание по переполнению
  Timer3.resume();
}

void loop() 
{}

void func_1(void) 
{
  count++;
  if(count == 5)
   {
     Serial.println("Канал_1 - моргаю при каждом пятом сравнении"); 
     digitalWrite(PB10, (LEDOn10 = !LEDOn10));
     count = 0;
   }
} 

void func_2(void) 
{
  Serial.println("Compare CH2"); 
  digitalWrite(PB11, (LEDOn11 = !LEDOn11));
} 

void func_3(void) 
{
  Serial.println("Compare CH3"); 
  digitalWrite(PB12, (LEDOn12 = !LEDOn12));
} 

void func_4(void) 
{
  Serial.println("Compare CH4"); 
  digitalWrite(LED_BUILTIN, (LEDOn13 = !LEDOn13));
} 

void func_5(void) 
{
  Serial.println("Overflow"); 
  digitalWrite(PB15, (LEDOn15 = !LEDOn15));
}



TIMER_CH1...TIMER_CH4 — номера каналов.
TIMER_OUTPUT_COMPARE — режим канала (когда значение счетчика достигает одного из значений сравнения канала, выполняется соответствующее прерывание).




ШИМ


К каждому каналу таймера привязана конкретная «ножка» МК, которую можно использовать в различных целях, например генерировать сигнал ШИМ

каналы
TIM1_CH1 — PA8
TIM1_CH2 — PA9
TIM1_CH3 — PA10
TIM1_CH4 — PA11 (это usb — не надо использовать)

TIM2_CH1 — PA0
TIM2_CH2 — PA1
TIM2_CH3 — PA2
TIM2_CH4 — PA3

TIM3_CH1 — PA6
TIM3_CH2 — PA7
TIM3_CH3 — PB0
TIM3_CH4 — PB1

TIM4_CH1 — PB6
TIM4_CH2 — PB7
TIM4_CH3 — PB8
TIM4_CH4 — PB9




Для примера воспользуемся первым и вторым каналом третьего таймера. Переполнением (setOverflow) зададим период, а сравнением (setCompare) будем регулировать длину импульса…


tдлина импульса.
Tпериод.


void setup()
{
  pinMode(PA6, PWM); // выход ШИМ канал 1
  pinMode(PA7, PWM); // выход ШИМ канал 2
  Timer3.setPrescaleFactor(72);       // 1мкс
  Timer3.setOverflow(5000);           // период 5 мс
  Timer3.setCompare(TIMER_CH1, 4000); // импульс 4 мс
  Timer3.setCompare(TIMER_CH2, 100);  // импульс 0.1 мс
  Timer3.refresh(); // обнулить счётчик
  Timer3.resume(); 
}

void loop()
{
  Timer3.setCompare(TIMER_CH1, 4000); 
  Timer3.setCompare(TIMER_CH2, 100);
  Timer3.refresh();
  delay(2000);
  Timer3.setCompare(TIMER_CH1, 2000);
  Timer3.setCompare(TIMER_CH2, 700); 
  Timer3.refresh();
  delay(2000);
  Timer3.setCompare(TIMER_CH1, 700); 
  Timer3.setCompare(TIMER_CH2, 2000);
  Timer3.refresh();
  delay(2000);
  Timer3.setCompare(TIMER_CH1, 100); 
  Timer3.setCompare(TIMER_CH2, 4000);
  Timer3.refresh();
  delay(2000);
}



Добавив ещё один канал можно рулить RGB-лентой…

RGB
int compare_R = 2000;
int compare_G = 2000;
int compare_B = 2000;

void setup()
{
  Serial.begin(115200);
  pinMode(PA6, PWM); // выход ШИМ канал 1
  pinMode(PA7, PWM); // выход ШИМ канал 2
  pinMode(PB0, PWM); // выход ШИМ канал 3
  Timer3.setPrescaleFactor(72);       // 1мкс
  Timer3.setOverflow(5000);           // период 5 мс
  Timer3.setCompare(TIMER_CH1, compare_R); // R
  Timer3.setCompare(TIMER_CH2, compare_G); // G 
  Timer3.setCompare(TIMER_CH3, compare_B); // B 
  Timer3.refresh(); // обнулить счётчик
  Timer3.resume(); 
}

void loop()
{
  if(Serial.available() > 1)
   {
     char first_sim = Serial.read();
     char second_sim = Serial.read();
     
     for(int i=0; i < 3; i++) 
      {
        Serial.read(); 
        delay(1);   
      }

     switch(first_sim)
      {
        case 'R': 
         if(second_sim == '+')
          {
            compare_R += 1000;
            if(compare_R > 5000) compare_R = 5000;
            Timer3.setCompare(TIMER_CH1, compare_R);
            Serial.print("Compare_CH1 ");
            Serial.println(Timer3.getCompare(TIMER_CH1));

          }
         else if(second_sim == '-')
          {
            compare_R -= 1000;
            if(compare_R < 0) compare_R = 0;
            Timer3.setCompare(TIMER_CH1, compare_R);
            Serial.print("Compare_CH1 ");
            Serial.println(Timer3.getCompare(TIMER_CH1));
          }
        break;
         
        case 'G': 
         if(second_sim == '+')
          {
            compare_G += 1000;
            if(compare_G > 5000) compare_G = 5000;
            Timer3.setCompare(TIMER_CH2, compare_G);
            Serial.print("Compare_CH2 ");
            Serial.println(Timer3.getCompare(TIMER_CH2));

          }
         else if(second_sim == '-')
          {
            compare_G -= 1000;
            if(compare_G < 0) compare_G = 0;
            Timer3.setCompare(TIMER_CH2, compare_G);
            Serial.print("Compare_CH2 ");
            Serial.println(Timer3.getCompare(TIMER_CH2));
          }
        break; 
 
        case 'B': 
         if(second_sim == '+')
          {
            compare_B += 1000;
            if(compare_B > 5000) compare_B = 5000;
            Timer3.setCompare(TIMER_CH3, compare_B);
            Serial.print("Compare_CH3 ");
            Serial.println(Timer3.getCompare(TIMER_CH3));

          }
         else if(second_sim == '-')
          {
            compare_B -= 1000;
            if(compare_B < 0) compare_B = 0;
            Timer3.setCompare(TIMER_CH3, compare_B);
            Serial.print("Compare_CH3 ");
            Serial.println(Timer3.getCompare(TIMER_CH3));
          }
        break;
        
        default:
        break;
      }    
   }
}


Отправляя в «Монитор порта» R+ или R-, G+ или G-, B+ или B- можно управлять яркостью светиков.


Задействовав все четыре таймера можно подключить четыре ленты, и при этом выполнять ещё какие-либо действия без ущерба для всей этой иллюминации.


В примере под спойлером появилась новая функция — getCompare(). Если у некоторых функций заменить приставку — set на get, то можно посмотреть текущие значения…


volatile bool LEDOn13 = 0;

void setup()
{
  Serial.begin(115200);

  pinMode(LED_BUILTIN, OUTPUT);

  Timer3.setMode(TIMER_CH1, TIMER_OUTPUT_COMPARE);
  Timer3.pause();
  Timer3.setPrescaleFactor(7200);
  Timer3.setOverflow(14000); 
  Timer3.setCompare(TIMER_CH1, 3000); 
  Timer3.attachInterrupt(TIMER_CH1, func_1);
  Timer3.resume();
}

void loop() 
{
  Serial.println(Timer3.getPrescaleFactor());
  Serial.println(Timer3.getOverflow());
  Serial.println(Timer3.getCount()); // текущее значение счётчика
  Serial.println(Timer3.getCompare(TIMER_CH1));
  Serial.println("--------");
  delay(1000);
}

void func_1(void) 
{
  digitalWrite(LED_BUILTIN, (LEDOn13 = !LEDOn13));
}




Захват


Предположим что у вас есть некий источник сигналов, пускай это будет чередующиеся HIGH и LOW, генерируемые обычной Ардуиной. Нам нужно измерить длину импульса и период…


tдлина импульса.
Tпериод.

… вот как раз для решения этой задачи, лучше всего и подойдёт режим захвата.

Коротко говоря, выглядит это так: источник сигнала подключается к «ножке» МК связанной с таймером, таймер тикает с заданной частотой (допустим один «тик» за одну микросекунду, соответственно весь цикл будет длиться 65535 мкс = 65.5 мс) и при появлении сигнала HIGH (передний фронт) происходит захват, то есть в спец. ячейку памяти записывается кол-во «тиков». После этого таймер продолжает считать и при появлении сигнала LOW (задний фронт) происходит ещё один захват. Кол-во «тиков» между захватами и будет длиной импульса. Ну и наконец при появлении очередного сигнала HIGH, мы узнаем длину периода.

Важно понимать, что таймер должен быть настроен так, чтоб все три импульса (HIGH ⇨ LOW ⇨ HIGH) произошли за один цикл (от 0 до 65535), об этом ниже...



Итак, источником сигналов послужит обычная ардуина (конечно ничто не мешает задействовать для этого отдельный таймер нашей платы, таймеры ведь независимые, но для большей наглядности воспользуемся ардуиной), поэтому зальём в неё вот этот код…


void setup() 
{
  pinMode(5, OUTPUT);
}

void loop() 
{
  digitalWrite(5, HIGH);
  delay(20); // длина импульса 20 миллисекунд
         
  digitalWrite(5, LOW);
  delay(30); // длина периода получится 20 + 30 = 50 миллисекунд
}


… и соединим пин 5 ардуины с пином PA6 (TIM3_CH1) платы. Согласуйте уровни напряжения.


В stm загрузим такую программу…


void setup()
{
  Serial.begin(115200);
  pinMode(PA6, INPUT_PULLDOWN);

  Timer3.pause();
  Timer3.setPrescaleFactor(72); // один "тик" равен одной микросекунде
  Timer3.setInputCaptureMode(TIMER_CH1, TIMER_IC_INPUT_DEFAULT); 
  Timer3.setInputCaptureMode(TIMER_CH2, TIMER_IC_INPUT_SWITCH); 
  Timer3.setPolarity(TIMER_CH2, 1); 
  Timer3.setSlaveFlags(TIMER_SMCR_TS_TI1FP1 | TIMER_SMCR_SMS_RESET);
  Timer3.refresh();
  Timer3.resume(); 
}

void loop()
{
  if(Timer3.getInputCaptureFlag(TIMER_CH2)) 
   {
     Serial.print("Длина импульса ");
     Serial.println(Timer3.getCompare(TIMER_CH2));
   }
 
  if(Timer3.getInputCaptureFlag(TIMER_CH1)) 
   {
     Serial.print("Период ");
     Serial.println(Timer3.getCompare(TIMER_CH1));
   }
}

Этот код взят из предоставляемых примеров (A_STM32_Examples ⇨ Sensors ⇨ HardwareTimerPWMInput) и немного переделан.

Описание новых функций и параметров я сделаю в следующей статье, с более подробным рассмотрением режима захвата.


Открываем




… и видим то, что и должны были увидеть. Длина импульса 20000 мкс = 20 мс и период 50000 мкс = 50 мс (небольшие хвостики не в счёт).

Теперь в коде для ардуины увеличим длительность LOW с 30-ти до 60-ти миллисекунд…

digitalWrite(5, LOW);
delay(60); // длина периода 20 + 60 = 80 миллисекунд


В терминале вы увидите, что длина импульса будет верной, а период, вместо ожидаемых 80мс, будет какой-то странный. Это происходит потому, что наш счётчик обнуляется раньше чем ардуина поменяет состояние с LOW на HIGH. То есть, сейчас счётчик «тикает» с интервалом в одну микросекунду, соответственно он переполняется через 65535 микросекунд = 65.5 миллисекунд, и конечно же 80 мс сюда никак не влезают.

Для измерения сигналов различной длины, нужно изменять значение предделителя — setPrescaleFactor(). Если мы поменяем 72 на 720, то каждый «тик» таймера будет равен 10 микросекундам, и соответственно диапазон измерений вырастет с 65 мс до 655 мс (65535 «тиков» по 10 мкс = 655350 мкс = 655 мс).

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


Измените значение и загрузите в плату…

Timer3.setPrescaleFactor(720); // один "тик" равен 10-ти микросекундам





Вот теперь всё в порядке, период равен 8000 (80мс = 80000мкс / 10 = 8000).



На этом наверно пора закругляться. В статье затронута лишь крохотная часть возможностей таймеров, однако на первое время этого должно хватить для использования в рамках IDE Arduino. Дальнейшее описание будет в следующих частях.



Полезные ресурсы…

Основной форум — www.stm32duino.com/

Человека, который пилит ядро, зовут Roger Clark — github.com/rogerclarkmelbourne/Arduino_STM32

Ещё разработчик — github.com/stevstrong/Arduino_STM32

HardwareTimer — librambutan.readthedocs.io/en/latest/lang/api/hardwaretimer.html#_CPPv210timer_mode

timer.h — librambutan.readthedocs.io/en/latest/libmaple/api/timer.html#capture-compare-mode-register-2-ccmr2



Чтобы использовать всю мощь таймеров нужно переходить на более низкий уровень программирования, например использовать среду CoIDE и писать на СИ. Примеров в сети просто море, достаточно загуглить — stm32...



  • 0
  • 791
Telegram-канал istarik

Известит Вас о новых публикациях...


Комментарии (0)

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.