Регулируемый таймер обратного отсчета на Arduino Mega 2560 | АрдуиноПлюс

Основной кейс

Основной кейс при данном типе организации многозадачности — это создавать так называемый «неблокирующий» код, то есть код, который не использует функцию delay(), которая просто приостанавливает выполнение программы на заданное время.

How it works

Basically, like any other similar device you can buy for a buck at any store near you. But this is yours. And this will show you how those little gadgets actually work.

The keyword here is: current mode. The timer itself can run in only one out of four modes at a time:

The code

First, we need to include the proper libraries:

#include <LiquidCrystal.h>
#include <TimeLib.h>

If you don’t have them already, you’ll need to download and install them:

Next, let’s initialize that nice LCD module:

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

Please feel free to scramble the pins at your will in order to obtain a nice wiring layout: don’t follow me in this, as I did a terrible wiring plan! 😀 For instance, you can reverse the latter four pins in the above statement in order to avoid the yellow wires crossing you can see in the schematic below (obviously, you’ll have to adjust the button pin constants accordingly, see below). Play, have fun! The life with Arduinos starts right after that copy/paste!

The next 51 code lines contain the static variables declaration and initialization. Please feel free to browse them, their crystal-clear names and some scattered comments will guide you understanding the whole thing.

The setup() function carries out the usual preliminary steps you’ve seen gazillions of times in any Arduino sketch out there and so far. The only notable statement is the first, which will set the initial LCD display cursor’s position. Because, yes: this module requires you to setup a position along its rows and cols and then to «print» something, which will appear starting from that position.

Now let’s move to the loop() function.

What you need to do

First: please gather all needed components from the Starter Kit or your preferred component bin; if you don’t have one, don’t panic. There’s plenty of on the Internet. You can find the component list below.

And, well, you’ll need the code too. It’s in its box, below again.

What you need to have

Now that you know where all this came from, let’s go deep into it.

All components I used came from the Arduino Starter Kit, including the small breadboard you see in the pics and in the video. Feel free to accomodate the project into a larger one, if you wish.

You’ll need a power source too: while playing around, the PC’ USB port and cable will be enough.

Wrap up and action!

Now that this little puppy has no more secrets to you, let’s see it in action. Thanks for watching, and have fun!

Видео

На этом всё.

Другие виды таймеров на arduino

Чтобы не загромождать статью, я не буду здесь приводить код и разбирать работу всех возможных типов таймеров — все они строятся по одним и тем же принципам. Если эта тема будет интересна, то можно будет написать отдельную статью об этом. Здесь я только дам общее описание таймеров, которые используются в AMS и прекрасно себя зарекомендовали на практике.

Запуск и остановка таймера в динамике

Для остановки таймера следует использовать вызов

timer_stop_ISR

, для повторного запуска — любой вариант

timer_init_ISR_XYHz

, как и раньше.

arduino-timer-api/examples/timer-api-start-stop/timer-api-start-stop.ino

#include"timer-api.h"

int _timer = TIMER_DEFAULT;

void setup() {
    Serial.begin(9600);
    while(!Serial);

    pinMode(13, OUTPUT);
}

void loop() {
    Serial.println("Start timer");
    timer_init_ISR_1Hz(_timer);
    delay(5000);
    
    Serial.println("Stop timer");
    timer_stop_ISR(_timer);
    delay(5000);
}

void timer_handle_interrupts(int timer) {
    static unsigned long prev_time = 0;
    
    unsigned long _time = micros();
    unsigned long _period = _time - prev_time;
    prev_time = _time;
    
    Serial.print("goodbye from timer: ");
    Serial.println(_period, DEC);

    // мигаем лампочкой
    digitalWrite(13, !digitalRead(13));
}

image

Как это работает?

Сама по себе многозадачность на микроконтроллерах может быть организована разными способами, в данном случае речь пойдёт о самом простом — процессы по очереди получают управление и добровольно отдают его после использования своего кванта времени. Этот способ, конечно, не лишён очевидных недостатков, но, как говорится, практика — критерий истины и он прекрасно зарекомендовал себя в реальных условиях: он используется как в стандартных дистрибутивах

, так и во множестве проектов на

. И эти системы работают в режиме 24/7 и имеют подтверждённые аптаймы во многие месяцы беспроблемной работы.

Это индикация около сотни сущностей распределённой nRF24 системы, управляемых независимо друг от друга в реальном времени. Обратите внимание на два последних индикатора «CPU» — при этом даже на 8-битной Меге загрузка процессора ровна нулю (то есть система полностью свободна).

Код / скетч

В нашу среду разработки Arduino IDE мы должны загрузить следующий скетч:

#include <math.h>

int digit_pin[] = {6, 9, 10, 11}; // PWM Display digit pins from left to right

int speakerPin = 15;

#define DIGIT_ON  LOW
#define DIGIT_OFF  HIGH

int segA = 2; 
int segB = 3; 
int segC = 4; 
int segD = 5; 
int segE = A0; //pin 6 is used bij display 1 for its pwm function
int segF = 7; 
int segG = 8; 
//int segPD = ; 


int button1=13;
int button2=12;
int button3=16;
int button4=17;

int countdown_time = 60;

struct struct_digits {
    int digit[4];
  };


void setup() {                
  pinMode(segA, OUTPUT);
  pinMode(segB, OUTPUT);
  pinMode(segC, OUTPUT);
  pinMode(segD, OUTPUT);
  pinMode(segE, OUTPUT);
  pinMode(segF, OUTPUT);
  pinMode(segG, OUTPUT);

  for (int i=0; i<4; i  ) {
    pinMode(digit_pin[i], OUTPUT);
  }

  pinMode(speakerPin, OUTPUT);

  pinMode(button1,INPUT_PULLUP);
  pinMode(button2,INPUT_PULLUP);
  pinMode(button3,INPUT_PULLUP);
  pinMode(button4,INPUT_PULLUP);
}


void playTone(int tone, int duration) {
  for (long k = 0; k < duration * 1000L; k  = tone * 2) {  
    digitalWrite(speakerPin, HIGH);
    delayMicroseconds(tone);
    digitalWrite(speakerPin, LOW);
    delayMicroseconds(tone);
  }
}


void lightNumber(int numberToDisplay) {

#define SEGMENT_ON  HIGH
#define SEGMENT_OFF LOW

  switch (numberToDisplay){

  case 0:
    digitalWrite(segA, SEGMENT_ON);
    digitalWrite(segB, SEGMENT_ON);
    digitalWrite(segC, SEGMENT_ON);
    digitalWrite(segD, SEGMENT_ON);
    digitalWrite(segE, SEGMENT_ON);
    digitalWrite(segF, SEGMENT_ON);
    digitalWrite(segG, SEGMENT_OFF);
    break;

  case 1:
    digitalWrite(segA, SEGMENT_OFF);
    digitalWrite(segB, SEGMENT_ON);
    digitalWrite(segC, SEGMENT_ON);
    digitalWrite(segD, SEGMENT_OFF);
    digitalWrite(segE, SEGMENT_OFF);
    digitalWrite(segF, SEGMENT_OFF);
    digitalWrite(segG, SEGMENT_OFF);
    break;

  case 2:
    digitalWrite(segA, SEGMENT_ON);
    digitalWrite(segB, SEGMENT_ON);
    digitalWrite(segC, SEGMENT_OFF);
    digitalWrite(segD, SEGMENT_ON);
    digitalWrite(segE, SEGMENT_ON);
    digitalWrite(segF, SEGMENT_OFF);
    digitalWrite(segG, SEGMENT_ON);
    break;

  case 3:
    digitalWrite(segA, SEGMENT_ON);
    digitalWrite(segB, SEGMENT_ON);
    digitalWrite(segC, SEGMENT_ON);
    digitalWrite(segD, SEGMENT_ON);
    digitalWrite(segE, SEGMENT_OFF);
    digitalWrite(segF, SEGMENT_OFF);
    digitalWrite(segG, SEGMENT_ON);
    break;

  case 4:
    digitalWrite(segA, SEGMENT_OFF);
    digitalWrite(segB, SEGMENT_ON);
    digitalWrite(segC, SEGMENT_ON);
    digitalWrite(segD, SEGMENT_OFF);
    digitalWrite(segE, SEGMENT_OFF);
    digitalWrite(segF, SEGMENT_ON);
    digitalWrite(segG, SEGMENT_ON);
    break;

  case 5:
    digitalWrite(segA, SEGMENT_ON);
    digitalWrite(segB, SEGMENT_OFF);
    digitalWrite(segC, SEGMENT_ON);
    digitalWrite(segD, SEGMENT_ON);
    digitalWrite(segE, SEGMENT_OFF);
    digitalWrite(segF, SEGMENT_ON);
    digitalWrite(segG, SEGMENT_ON);
    break;

  case 6:
    digitalWrite(segA, SEGMENT_ON);
    digitalWrite(segB, SEGMENT_OFF);
    digitalWrite(segC, SEGMENT_ON);
    digitalWrite(segD, SEGMENT_ON);
    digitalWrite(segE, SEGMENT_ON);
    digitalWrite(segF, SEGMENT_ON);
    digitalWrite(segG, SEGMENT_ON);
    break;

  case 7:
    digitalWrite(segA, SEGMENT_ON);
    digitalWrite(segB, SEGMENT_ON);
    digitalWrite(segC, SEGMENT_ON);
    digitalWrite(segD, SEGMENT_OFF);
    digitalWrite(segE, SEGMENT_OFF);
    digitalWrite(segF, SEGMENT_OFF);
    digitalWrite(segG, SEGMENT_OFF);
    break;

  case 8:
    digitalWrite(segA, SEGMENT_ON);
    digitalWrite(segB, SEGMENT_ON);
    digitalWrite(segC, SEGMENT_ON);
    digitalWrite(segD, SEGMENT_ON);
    digitalWrite(segE, SEGMENT_ON);
    digitalWrite(segF, SEGMENT_ON);
    digitalWrite(segG, SEGMENT_ON);
    break;

  case 9:
    digitalWrite(segA, SEGMENT_ON);
    digitalWrite(segB, SEGMENT_ON);
    digitalWrite(segC, SEGMENT_ON);
    digitalWrite(segD, SEGMENT_ON);
    digitalWrite(segE, SEGMENT_OFF);
    digitalWrite(segF, SEGMENT_ON);
    digitalWrite(segG, SEGMENT_ON);
    break;

  case 10:
    digitalWrite(segA, SEGMENT_OFF);
    digitalWrite(segB, SEGMENT_OFF);
    digitalWrite(segC, SEGMENT_OFF);
    digitalWrite(segD, SEGMENT_OFF);
    digitalWrite(segE, SEGMENT_OFF);
    digitalWrite(segF, SEGMENT_OFF);
    digitalWrite(segG, SEGMENT_OFF);
    break;  
  }
 
}



void SwitchDigit(int digit) {
  for (int i=0; i<4; i  ) {
    if (i == digit) {
      digitalWrite(digit_pin[i], DIGIT_ON);
    } else {
      digitalWrite(digit_pin[i], DIGIT_OFF);
    }
  }
}


struct struct_digits IntToDigits(int n){
  struct struct_digits dig;
  int zeros=0;
  int d;
  for (int i=0; i<4; i  ) {
    d=n/pow(10,3-i);
    zeros  = d;
    n = n - d*pow(10,3-i);
    if (zeros!=0 || i==3) {
      dig.digit[i]=d;
    } else {
      dig.digit[i]=10;
    }
  }
  return dig;
}

void PrintNumber(int n, int time) {
  struct struct_digits dig;

  dig = IntToDigits(n);
  
  for (int i=0; i<= time/20; i  ) {
    if (digitalRead(button2)==LOW) {
      return;
    }
    for (int j=0; j<4; j  ) {
      SwitchDigit(j);
      lightNumber(dig.digit[j]);
      delay(5);
    }
  }
}


bool Countdown(int n, int del){
  for (int q=n; q>0; q--){
    PrintNumber(q,del);
    if (digitalRead(button2)==LOW) {
      return false;
    }
  }
  PrintNumber(0,0);
  playTone(1519,1000);
  return true;
}



void reset() {
  int m, zeros, d, pressed3 = 0, pressed4 = 0;
  m=countdown_time;
  struct struct_digits dig;

  dig = IntToDigits(countdown_time);
  
  while (digitalRead(button1)==HIGH) {
    for (int j=0; j<4; j  ) {
      SwitchDigit(j);
      lightNumber(dig.digit[j]);
      delay(5);
    }
    if (digitalRead(button3)==LOW) { 
      if (pressed3 == 0 || pressed3 > 30) {
        if (countdown_time > 0) {
          countdown_time -= 1 ;
        }
        dig = IntToDigits(countdown_time);
      } 
      pressed3  = 1;
    }
    else if (digitalRead(button4)==LOW) { 
      if (pressed4 == 0 || pressed4 > 30) {
        if (countdown_time <9999) {
          countdown_time  = 1 ;
        }
        dig = IntToDigits(countdown_time);
      } 
      pressed4  = 1;
    }
    if (digitalRead(button3)==HIGH) {
      pressed3=0;
    }
    if (digitalRead(button4)==HIGH) {
      pressed4=0;
    }
  }
}

void loop(){
  reset();
  while (!Countdown(countdown_time,962)) {
    reset();
  }
  while (digitalRead(button2)==1){};
}

Компоненты

Для того, чтобы нам собрать таймер обратного отсчета на Ардуино нам нужны будут следующие компоненты:

  1. Arduino Mega 2560 / Genuino Mega 2560 (любая Ардуино должна работать) × 1
  2. 4-разрядный 7-сегментный дисплей × 1
  3. Кнопки × 4
  4. Зуммер × 1
  5. Перемычки × 21
  6. Макетная плата × 1
Читайте также:  Как выбрать таймер для кухни | ВАША КУХНЯ

Из программного обеспечения мы никуда без IDE Arduino.

Кухонный таймер на arduino (1)

Когда механический

кухонный таймер

пару раз совершил жесткую посадку на пол и начал заедать при отсчете времени, я понял: есть отличный шанс заменить его на что-то электронное.

При нынешнем развитии микропроцессорной техники потенциально может существовать все, что душе угодно, но… Как назло — попадается либо не совсем то, что надо, либо что-то перенавороченное, где много лишнего. Логичный выход для тех, у кого есть свободное время — создать устройство самому.

Итак, прежде всего — подберем компоненты:

  • дисплей для отображения времени
  • пара кнопок для управления
  • зуммер для привлечения внимания хозяйки
  • корпус, в который все это поместится
  • источник питания

Ну и конечно же,

Arduino

.

Наиболее хорошо для этой цели подходит Arduino Pro: основные разъемы не установлены, можно поставить ровно столько, сколько надо (а также именно то, что требуется — вилки или колодки, нужный разъем питания):

Регулируемый таймер обратного отсчета на Arduino Mega 2560 | АрдуиноПлюс

За неимением такого замечательного девайса, придется собирать на макетке. Как обычно — ATmega8-16PU, кварц 16 МГц 2 конденсатора 22пФ, притянуть сброс через 10К к Vcc, схема птиания на L7805. Поскольку я постоянно собираю Arduino / Freeduino, все это у меня имеется в избыточном количестве.

С эстетической точки зрения, лучше устанавливать часовой дисплей с «двоеточием» по центру, которое мигает раз в секунду при отсчете времени. В минимальном варианте достаточно просто двух цифр с числом оставшихся минут. Поглядывать на таймер надо издалека, поэтому размер и яркость цифр имеют значение — лучше всего подойдет светодиодный дисплей. Удобно использовать индикатор, у которого одноименные выводы уже соединены — все равно мы будем отображать методом мультепликсирования. У меня был красный семисегментный CA56-21EWA (Kingbright), с общим анодом:

Регулируемый таймер обратного отсчета на Arduino Mega 2560 | АрдуиноПлюс

В качестве зуммера можно использовать пьезоизлучатель. Часто на материнских платах можно встретить такой вариант:

Регулируемый таймер обратного отсчета на Arduino Mega 2560 | АрдуиноПлюс

Это пятивольтовый KPX-G1205B (Kepo Electronics).

В него встроен генератор, позволяющий издавать звуковое колебание заданной частоты простой подачей разности потенциалов на его выводы. Очень удобно, да и ток всего 30 мА (напоминаю, что максимальный ток через выход ATmega — 40 мА). Если не закрывать отверстие, пищит достаточно громко — простой бесчеловечный эксперимент показал, что хозяйка слышит его сквозь какафонию из льющей из водопроводного крана воды и орущего телевизора, причем еще и из другой комнаты. Те, кому повезло меньше, могут использовать схему с транзистором 😉

Кнопки могут быть как тактовые, так и с креплением на корпус. Идеально подойдут влагозащищенные, но это влетит в копеечку. Я выбрал компромиссные SWT-9 с разноцветными колпачками:

Регулируемый таймер обратного отсчета на Arduino Mega 2560 | АрдуиноПлюс

Регулируемый таймер обратного отсчета на Arduino Mega 2560 | АрдуиноПлюс

Для включения питания идеально подходит классический тумблер MTS-102:

Регулируемый таймер обратного отсчета на Arduino Mega 2560 | АрдуиноПлюс

Среди корпусов для РЭА, которые я мог заказать у поставщиков, мне приглянулись черные пластиковые, на защелках. Брать размер 70 х 40 мм я побоялся (как потом выяснилось, абсолютно верно), но вот 90 х 56 мм уже выглядело для такой схемы вполне комфортно, тем более, что в высоту он 30 мм, а значит — дисплей с кнопками и контроллер можно разместить на двух разных платах, соединив их при помощи PLS/PBS:

Регулируемый таймер обратного отсчета на Arduino Mega 2560 | АрдуиноПлюс

Правда, придется изрядно поработать напильником, чтобы вытащить наружу кнопки и индикатор (разъем питания и тумблер легко размещаются сбоку, надо вырезать заранее заготовленные утоньшения для ввода кабеля).

Настало время рисовать схему в Eagle, но прежде подсчитаем, сколько потребуется портов ввода/вывода.

Семисегментная шина требует 7 1=8 выходов: восьмой в данном случае управляет двумя точками по центру. Один сигнал на каждое знакоместо: 4, итого уже 12. Один выход потребуется для пьезоизлучателя, два — для подключения кнопок. В сумме надо 15, а у Arduino всего 13 цифровых выходов, из которых категорически не хотелось бы занимать и 1 — там UART, через которую заливается скетч.

На наше счастье, аналоговые выходы можно перепрограммировать в цифровые оператором DigitalMode. Аналоговый вход 0 будет соответствовать 14-му цифровому, а 5й — 19-му. АЦП нашей схеме не требуется, так что смело можно переводить все шесть выходов в цифровой режим.

Для начала соберем такую схему на мактной плате:

Регулируемый таймер обратного отсчета на Arduino Mega 2560 | АрдуиноПлюс

Продолжение следует…

Лекции олега артамонова


Нужно отдать должное, сами

Олега хороши — в них даётся много полезной и хорошо структурированной информации о микроконтроллерах и я бы рекомендовал всем заинтересованным в этом вопросе с ними ознакомиться. Единственным недостатком этих лекций мне показался неприкрытый техно-снобизм в отношении Arduino, которая выступает в них в роли «мальчика для битья».

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

На Arduino можно делать потрясающие многозадачные системы в которых в (псевдо, естественно) многозадачном режиме одновременно работают десятки и сотни сущностей (светодиодов, датчиков, актуаторов, сервоприводов, шаговых моторов, беспроводных и проводных интерфейсов и т. д.).

Не будем далеко ходить за примерами. Вот проект Зимнего сада («Умной теплицы») в котором в реальном времени в многозадачном режиме работают следующие сущности:

Топология распределённого nRF24 контроллера с огромным числом подключённого и работающего в реальном времени оборудования. Пользователь имеет дело только с «базой», работа nRF24 партнёра полностью прозрачна для него. И, да, это Arduino.

На «базе»:

— 7 сервоприводов— 9 шаговых моторов— 6 реле— 3 датчика влажности почвы— 2 датчика освещённости— Датчик уровня воды— Датчик влажности и температуры воздуха

На nRF24 удалённой части:

— 12 датчиков влажности почвы— 12 реле— 3 шаговых мотора— 2 датчика освещённости— Датчик уровня воды

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

Итого, в реальном времени, в многозадачном режиме на 8-битной Меге функционирует как минимум 60 сущностей (и это не считая множества сервисов самой операционной системы AMS, с ними число сущностей приблизится к сотне). Что очевидным образом никак не согласуется с высказыванием о том, что «на Arduino невозможна настоящая многозадачность и мигать даже пятью светодиодами на ней проблематично».

Межпроцессное взаимодействие

Межпроцессное взаимодействие, семафоры, почтовые ящики и прочие атрибуты многозадачных систем организуются на Arduino тоже без каких-либо проблем — их можно организовать любым удобным для вас способом — начиная от передачи параметров через статические переменные и заканчивая упаковкой логики в любые классы и объекты, тут нет абсолютно никаких ограничений и проблем.

Читайте также:  40 Видов настенных часов на кухню: счастливые минуты и часы в уютном доме

Начнем сразу с кода

Подключаем библиотеку timer-api.h (раз)

#include "timer-api.h"

Запускаем таймер с нужной частотой с

timer_init_ISR_XYHz

: здесь XYHz=1Hz — 1 Герц — один вызов прерывания в секунду (

два

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

    // частота=1Гц, период=1с
    timer_init_ISR_1Hz(TIMER_DEFAULT);

    pinMode(13, OUTPUT);
}

ISR — interrupt service routine, процедура-обработчик прерывания

Добавляем в главный цикл loop любую блокирующую или неблокирующую ерунду: печатаем сообщение, ждём 5 секунд (здесь всё, как обычно, поэтому не считаем)

void loop() {
    Serial.println("Hello from loop!");
    delay(5000);

    // здесь любой код: блокирующий или неблокирующий
}


Процедура, вызываемая прерыванием по событию таймера с заданным периодом, — реализация для функции с именем

timer_handle_interrupts

: печатаем сообщение, мигаем лампочкой (

три

void timer_handle_interrupts(int timer) {
    Serial.println("goodbye from timer");

    // мигаем лампочкой
    digitalWrite(13, !digitalRead(13));
}

То же самое, только добавим замер времени между двумя вызовами для наглядности и отладки:

void timer_handle_interrupts(int timer) {
    static unsigned long prev_time = 0;
    
    unsigned long _time = micros();
    unsigned long _period = _time - prev_time;
    prev_time = _time;
    
    Serial.print("goodbye from timer: ");
    Serial.println(_period, DEC);

    // мигаем лампочкой
    digitalWrite(13, !digitalRead(13));
}

Шьем плату, открываем

Инструменты > Монитор порта

, наблюдаем результат:

Как видим, обработчик timer_handle_interrupts печатает сообщение каждые 1000000 (1 миллион) микросекунд, т.е. ровно раз в секунду. И (о чудо!) постоянная блокирующая задержка на 5 секунд delay(5000) в главном цикле никаким образом ему в этом действии не мешает.

Вот вам реальное время и многозадачность в одном скетче в 3 строчки, я обещал.

Немного о таймерах

Для организации управления сложными системами недостаточно просто передавать по очереди управление между процессами и наряду с автоматической передачей управления в AMS используются различные виды таймеров: циклические, циклические с заданным количеством повторений (пакетные), одиночные, рандомные, смешанные и т. д.

Что опять же вступает в прямое противоречие с высказыванием «На 3 светодиода железных таймеров хватит, с дальше у ардуинщиков начнутся проблемы». Не начнутся. Нам доступны любые типы таймеров в любом количестве. И, при желании, мы можем наделать себе ещё сколько угодно новых и сколь угодно экзотических.

Ну и, напоследок,


Вращение шаговым мотором через интерфейс step-dir:

— в фоне по таймеру генерируем постоянный прямоугольный сигнал для шага по фронту HIGH->LOW на ножке STEP

— в главном цикле принимаем от пользователя команды для выбора направления вращения (ножка DIR) или остановки мотора (ножка EN) через последовательный порт

arduino-timer-api/examples/timer-api-stepper/timer-api-stepper.ino

Одиночные


Это различные вариации на тему «автозагрузки», когда какое-либо действие выполняется через определённый интервал времени после старта контроллера или какого-либо события.

Пара слов в защиту arduino

(Хотя очевидно, что Arduino как социо-культурный и технологический феномен с многомиллионной армией поклонников и многими тысячами потрясающих проектов в защите не нуждается.)

Я много раз говорил и ещё раз повторю, что Arduino в своей софтверной составляющей это, по сути, просто один из возможных уровней абстракции (как и любой другой) со своими достоинствами и недостатками. И пользователю нет абсолютно никакой разницы, что «крутится» внутри его маленького кусочка кремния — «чистая» Arduino, RTOS, RIOT OS, AMS или какая-то другая математическая абстракция представления и управления железными ресурсами контроллера.

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

Поддерживаемые чипы и платформы

— Atmega/AVR 16 бит 16МГц на Arduino

— SAM/ARM 32 бит 84МГц на Arduino Due

— PIC32MX/MIPS 32 бит 80МГц на семействе ChipKIT (PIC32MZ/MIPS 200МГц — частично, в работе)

Проект

Таймер обратного отсчета является, пожалуй, самым естественным проектом для 4-разрядного 7-сегментного дисплея. Исследуя то, что доступно в интернете, я нашел несколько проектов, но не нашел тот, который имел в виду, а именно, автономный настраиваемый таймер обратного отсчета, который можно удобно настроить с помощью кнопок (а не загружая измененный код). Поэтому я решил сделать один из них.

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

Прилагаемый код также может быть использован в других проектах с 4-разрядным 7-сегментным дисплеем. В частности, в нем есть функция, которая отображает заданное число (0-9999) за заданный интервал времени. Возможно, такая функциональность есть в некоторых библиотеках, но быстрый поиск ее не выявил.

Произвольная частота


Есть еще вариант установить практически произвольное (в определенных границах) значение частоты таймера при помощи вызова

timer_init_ISR(timer, prescaler, adjustment)

с параметрами — системным делителем тактовой частоты процессора

prescaler

и произвольным значением

adjustment

для размещения в регистре счетчика таймера.

Не вдаваясь в подробности, чтобы не перегружать пост, приведу ссылку на пример с подробными комментариями:arduino-timer-api/examples/timer-api-custom-clock/timer-api-custom-clock.ino

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

Рандомные

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

Например, у вас есть два распределённых контроллера, которые связаны друг с другом по беспроводному каналу. Если одна система будет посылать свои сообщения другой, согласуясь со срабатыванием обычного таймера, например, раз в 20 секунд, то эти сообщения будут приходить на вторую систему в строго определённой «фазе», которая может попадать в «проблемный» интервал работы цикла второго контроллера, в результате его работа может быть нестабильной.

Это только абстрактный пример для понимания того, что собой представляют рандомные таймеры. И вы можете ознакомиться с их реализацией — стандартный дистрибутив Arduino Mega Server содержит код такого таймера.

Реальное время

Описываемый способ реализации многозадачности можно охарактеризовать как «soft-realtime», типовое время задержки в системе составляет 10 мс (но пиковые задержки могут быть значительно больше и не нормируются). Это накладывает известные ограничения на спектр применения данного решения, но для большинства «бытовых» задач (и не только) он прекрасно подходит, см. пример выше.

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

Это общее теоретическое описание работы многозадачности в Arduino вообще и в AMS в частности, теперь перейдём к рассмотрению практических примеров.

Смешанные


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

Нет практически никаких ограничений на количество таймеров и логику работы — их может быть сотни, даже на 8-битном контроллере.

Суммирование периодов (деление частоты)

В том случае, если стандартные частоты из предложенных на выбор вас не устраивают, можно ввести в код прерывания дополнительный счетчик, который будет выполнять полезный код только после определенного количества пропущенных вызовов. Целевой период будет равен сумме пропускаемых базовых периодов. Или можно сделать его вообще переменным.

Читайте также:  Интерьер черно белой кухни шторы - Moy-Instrument.Ru - Обзор инструмента и техники

arduino-timer-api/examples/timer-api-counter/timer-api-counter.ino

#include"timer-api.h"

void setup() {
    Serial.begin(9600);
    while(!Serial);

    // частота=10Гц, период=100мс
    timer_init_ISR_10Hz(TIMER_DEFAULT);
    
    pinMode(13, OUTPUT);
}

void loop() {
    Serial.println("Hello from loop!");
    delay(6000);

    // здесь любой код: блокирующий или неблокирующий
}

void timer_handle_interrupts(int timer) {
    static unsigned long prev_time = 0;

    // дополнильный множитель периода
    static int count = 11;

    // Печатаем сообщение на каждый 12й вызов прерывания:
    // если базовая частота 10Гц и базовый период 100мс,
    // то сообщение будет печататься каждые 100мс*12=1200мс
    // (5 раз за 6 секунд)
    if(count == 0) {
        unsigned long _time = micros();
        unsigned long _period = _time - prev_time;
        prev_time = _time;
    
        Serial.print("goodbye from timer: ");
        Serial.println(_period, DEC);

        // мигаем лампочкой
        digitalWrite(13, !digitalRead(13));

        // взводим счетчик
        count = 11;
    } else {
        count--;
    }
}

image

Схема соединения

Все компоненты мы соединяем согласно схеме ниже:

В собранном виде наш таймер будет выглядеть примерно так:

Установка библиотеки

Клонировать репозиторий прямо в каталог с библиотеками

Циклические с заданным количеством повторений (пакетные)

Это таймеры, которые срабатывают заранее определённое количество раз. Например, вам нужно делать 3 попытки отправки сообщения по беспроводному каналу nRF24. Таймер активируется только 3 раза и соответственное количество раз делаются попытки отправки сообщений.

Тут же возможны различные расширения функциональности типа активации/дезактивации таймера в зависимости от определённых условий и т. п.

Циклические таймеры

Рассмотрим реализацию самых простых циклических таймеров. Это таймеры (в терминологии AMS «cycles»), которые включаются через определённые, заранее заданные промежутки времени и используются для активации циклических процессов.

Вообще, таймеры программно лучше оформлять в виде объектов, но в стандартной поставке Arduino Mega Server эти таймеры реализованы в виде функций, поэтому, для начала, рассмотрим их в этой ипостаси.

Использовать циклические таймеры очень просто: достаточно поместить код, который нужно периодически выполнять, между скобками оператора if. Если нужно использовать другой интервал срабатывания, то просто используем нужную переменную вместо cycle1s. Различных циклов можно сделать сколько угодно — система даже на 8-битной Меге без проблем потянет обслуживание буквально сотен таких таймеров (только, естественно, нужно не забывать чтобы вызываемый код не был блокирующим).

  if (cycle1s) {
    // Код, который необходимо выполнять, например, каждую секунду
  }

Теперь организация работы таймеров. Определение управляющих переменных в главном файле:

// Cycles
bool cycle1s  = false;
bool cycle5s  = false;
bool cycle20s = false;
bool cycle30s = false;
bool cycle1m  = false;
bool cycle3m  = false;
bool cycle5m  = false;


Набор интервалов может быть расширен любыми нужными значениями от десятков миллисекунд до суток и более.

Модуль «Timers»:

/*
  Module Timers
  part of Arduino Mega Server project
*/

// Cycles
unsigned long timeSec;
unsigned long timer1s;
unsigned long timer5s;
unsigned long timer20s;
unsigned long timer30s;
unsigned long timer1m;
unsigned long timer3m;
unsigned long timer5m;

void timersInit() {
  unsigned long uptimeSec = millis() / 1000;
  timeSec  = uptimeSec;
  timer1s  = uptimeSec;  
  timer5s  = uptimeSec; 
  timer20s = uptimeSec;
  timer30s = uptimeSec;
  timer1m  = uptimeSec;
  timer3m  = uptimeSec;
  timer5m  = uptimeSec;
}

void timersWorks() {
  timeSec = millis() / 1000;
    if (timeSec - timer1s  >=  1)  {
                                    timer1s  = timeSec; cycle1s  = true;
    if (timeSec - timer5s  >=  5)  {timer5s  = timeSec; cycle5s  = true;}
    if (timeSec - timer20s >= 20)  {timer20s = timeSec; cycle20s = true;}
    if (timeSec - timer30s >= 30)  {timer30s = timeSec; cycle30s = true;}
    if (timeSec - timer1m  >= 60)  {timer1m  = timeSec; cycle1m  = true;}
    if (timeSec - timer3m  >= 180) {timer3m  = timeSec; cycle3m  = true;}
    if (timeSec - timer5m  >= 300) {timer5m  = timeSec; cycle5m  = true;}
  }
}

void eraseCycles() {
  cycle1s  = false;
  cycle5s  = false;
  cycle20s = false;
  cycle30s = false;
  cycle1m  = false;
  cycle3m  = false;
  cycle5m  = false;
}

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

void loop() {
  timersWorks();

  // Код системных процессов

  eraseCycles();
}

Циклические таймеры в виде объектной библиотеки

Теперь рассмотрим организацию тех же таймеров, но в более правильном объектном виде, оформленном в готовую библиотеку. Назовём её myCycle.

Заголовочный файл в котором представлены объявления класса, методов и некоторых предопределённых констант:

/*
  myCycle Library
  part of Arduino Mega Server project
*/

#ifndef _MYCYCLE_H
#define _MYCYCLE_H

#define MS_500       500
#define MS_01S      1000
#define MS_02S      2000
#define MS_05S      5000
#define MS_10S     10000
#define MS_13S     13000
#define MS_17S     17000
#define MS_20S     20000
#define MS_30S     30000
#define MS_01M     60000
#define MS_03M    180000
#define MS_05M    300000
#define MS_01H   3600000
#define MS_06H  21600000
#define MS_12H  43200000
#define MS_01D  86400000

class myCycle {
  private:
    bool          _go;
    bool          _active;
    unsigned long _start;
    unsigned long _period;
  
  public:
    myCycle(unsigned long per, bool act);
    void reInit(unsigned long per, bool act);
    void reStart();
    bool check();
    bool go();
    void clear();
    
    // active
    bool active();
    void setActive(bool act);
    // period
    unsigned long period();
    void setPeriod(unsigned long per);
}; // class myCycle

#endif // _MYCYCLE_H

И файл реализации в котором находится код библиотеки:

/*
  myCycle Library
  part of Arduino Mega Server project
*/

#include "myCycle.h"
#include <Arduino.h>

myCycle::myCycle(unsigned long per, bool act) {
  _go     = false;
  _active = act;
  _period = per;
  _start  = millis();
}

// Methods

void myCycle::reInit(unsigned long per, bool act) {
  _go     = false;
  _active = act;
  _period = per;
  _start  = millis();
}

void myCycle::reStart() {
  _start = millis();
}

bool myCycle::check() {
  if (millis() - _start >= _period) {
    _start = millis();
    if (_active) {
      _go = true;
    }
  }
  return _go;
}

bool myCycle::go() {
  return _go;
}

void myCycle::clear() {
  _go = false;
}

// Active

bool myCycle::active() {
  return _active;
}

void myCycle::setActive(bool act) {
  _active = act;
}

// Period

unsigned long myCycle::period() {
  return _period;
}

void myCycle::setPeriod(unsigned long per) {
  _period = per;
}

Использование этого варианта тоже просто и имеет некоторые преимущества перед «функциональным» вариантом: тут можно очень легко объявлять таймеры с нужными интервалами и не нужно заранее создавать множество таймеров «на всякий случай».

В главном файле:

Подключаем библиотеку:

#include "myCycle.h"

Создаём объекты:

// Cycles
myCycle cycle500(MS_500, true);
myCycle cycle2s(MS_02S, true);
myCycle cycle5s(MS_05S, true);

Добавляем функции обслуживания работы таймеров:

void timersWorks() {
  cycle500.check();
  cycle2s.check();
  cycle5s.check();
}

void eraseCycles() {
  cycle500.clear();
  cycle2s.clear();
  cycle5s.clear();
}


В главном цикле используем объявленные таймеры в любом нужном месте кода:

void loop() {
  timersWorks();

  // Код системных процессов

  if (cycle5s.go()) {
    Serial.println(F("cycle5s!"));
  }

  eraseCycles();
}

Библиотека имеет несколько более широкий функционал, чем код, приведённый в первом варианте. Например, она содержит методы активации/дезактивации таймеров, установки и получения текущего значения, рестарта и реинициализации таймеров, что ещё больше расширяет возможности использования их в практических задачах.

Заключение

В общем, можно сказать, что средствами Arduino можно создать любые виды таймеров, запускать любое их количество и использовать любое их сочетание, другими словами, Arduino может удовлетворить любой каприз по таймерному (псевдо) многозадачному управлению сложными микроконтроллерными системами.

И это не должно быть секретом для студентов МИРЭА, как будущих инженеров микропроцессорных систем, ведь эти принципы можно применять на любой платформе.

Варианты частот для timer_init_isr_xyhz

    //timer_init_ISR_500KHz(TIMER_DEFAULT);
    //timer_init_ISR_200KHz(TIMER_DEFAULT);
    //timer_init_ISR_100KHz(TIMER_DEFAULT);
    //timer_init_ISR_50KHz(TIMER_DEFAULT);
    //timer_init_ISR_20KHz(TIMER_DEFAULT);
    //timer_init_ISR_10KHz(TIMER_DEFAULT);
    //timer_init_ISR_5KHz(TIMER_DEFAULT);
    //timer_init_ISR_2KHz(TIMER_DEFAULT);
    //timer_init_ISR_1KHz(TIMER_DEFAULT);
    //timer_init_ISR_500Hz(TIMER_DEFAULT);
    //timer_init_ISR_200Hz(TIMER_DEFAULT);
    //timer_init_ISR_100Hz(TIMER_DEFAULT);
    //timer_init_ISR_50Hz(TIMER_DEFAULT);
    //timer_init_ISR_20Hz(TIMER_DEFAULT);
    //timer_init_ISR_10Hz(TIMER_DEFAULT);
    //timer_init_ISR_5Hz(TIMER_DEFAULT);
    //timer_init_ISR_2Hz(TIMER_DEFAULT);
    //timer_init_ISR_1Hz(TIMER_DEFAULT);

вызов timer_init_ISR_1MHz тоже есть, но он не даёт рабочий результат ни на одном из тестовых контроллеров

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

Полагаю, излишне пояснять, что чем выше частота таймера, тем меньше период вызова прерываний, тем быстрее должен выполняться код обработчика. Я бы не рекомендовал помещать в него вызовы блокирующих задержек delay, циклы с неизвестным заранее количеством итераций, любые другие вызовы с плохо предсказуемым временем выполнения (в том числе Serial.print).

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector