БАНО на Arduino

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

Итак. Данный проектик “одного вечера” был больше года назад реализован. Вчера я про него вспомнил, отрыл исходники, собрал на макетке с использованием подвернувшейся под руку Arduino Nano на базе 328-й атмеги, и оно заработало =)

Итак. что нам нужно:
Ардуина
6 светодиодов с резисторами
макетная плата - по желанию

подключаем светодиоды к портам D7, D8, D9, D10, D12 и D13

создаем в Arduino IDE новый скетч и копируем туда этот код

#define MSG_SERIAL 0

const int maxTaskCount = 16;


//  базовый класс для запускаемой задачи
class TaskInfo {
  private:
     int _priority;
     unsigned int _timer;                                                   // таймер-счетчик задачи. когда он равен нулю - будет вызван метод Proc() задачи
  public:
  inline TaskInfo() {  _priority = 0; _timer=0;  }                          // конструктор по умолчанию
  inline TaskInfo(int priority) {  _priority = priority; _timer=0; }        // конструктор с параметром - задает приоритет задачи
  inline int GetPriority () { return _priority; }                           // получить приоритет задачи
  inline unsigned int GetTimer() { return _timer; }                         // возвращает количество тиков таймера до следующего вызова
  inline void SetTimer ( unsigned int value ) { _timer = value; }           // устанавливаем количество тиков таймера до следующего вызова
  inline void Tick() { if(_timer > 0) _timer--;  }                          // уменьшаем значение счетчика

  virtual inline void HandleMsg (unsigned int msg, unsigned int arg ) {  return; }  // обработчик сообщений о системных событиях
  virtual unsigned int Proc(void);                                                  // исполнемый метод
};
//----------------------------------------------------------
// менеджер задач

class TaskManager {
  private:
    int _taskCnt;                                                // количество задач в списке
    TaskInfo * _taskList[maxTaskCount];                          // список задач
    volatile int _tick;                                          // признак сработавшего таймера
   public:
     inline TaskManager() { _taskCnt = 0; _tick = 0; }           // конструктор
     void AddTask (TaskInfo * task) ;                            // добавить задачу в очередь
     void Tick();                                                // метод вызываемый при срабатывании таймера
     void Run();                                                 // запустить задания в очереди на выполнение
     void HandleMsg (unsigned int msg, unsigned int arg );       // обработчик внешних событий. передает данные задачам в очереди
};

TaskManager taskManager;                                         // глобальный объект - менеджер заданий

void TaskManager::AddTask (TaskInfo * task)
{
    _taskList[_taskCnt] = task;
    _taskCnt++;
}
void TaskManager::Tick()
{
     _tick = 1;
}
void TaskManager::Run()
{
    int i;
    int taskIndex = -1;
    int maxPriority = -1000;
    int taskPriority;



    // уменьшаем счетчики у задач
    if (_tick)
    {
        for (i = 0; i < _taskCnt; i++)
        {
            ( _taskList[i] )->Tick();
        }
        _tick = 0;
    }

    // поиск задачи с максимальным приоритетом
    for ( i = 0; i < _taskCnt; i++)
    {
        taskPriority = (_taskList[i])->GetPriority();
        if ((_taskList[i]->GetTimer() == 0) && (taskPriority > maxPriority))
        {
            taskIndex = i;
            maxPriority = taskPriority;
        }
    }
    // если нашли - запускаем
    if (taskIndex >= 0)
    {
        _taskList[taskIndex]->SetTimer((_taskList[taskIndex])->Proc());
    }
    //
}

void TaskManager::HandleMsg (unsigned int msg, unsigned int arg )
{
    int i;
    for ( i = 0; i < _taskCnt; i++)
    {
        _taskList[i]->HandleMsg (msg, arg);
    }
}



//----------------------------------------------------------
// Обычная "мигалка" c двумя состояниями и настраиваемыми интервалами
class BlinkTask : public TaskInfo {
  private:
    byte _state;
    unsigned int _activeTime;
    unsigned int _inactiveTime;
    int _pin;

    int _enabled;
  public:
     BlinkTask (int pin, unsigned int activeTime, unsigned int inactiveTime);
     void HandleMsg (unsigned int msg, unsigned int arg );
     unsigned int Proc(void);
};

BlinkTask::BlinkTask (int pin, unsigned int activeTime, unsigned int inactiveTime)
{
  TaskInfo(0);  //вызов конструктора базового класса
  _activeTime = activeTime;
  _inactiveTime = inactiveTime;
  _state = 0;
  _pin = pin;

  _enabled = 1;
  pinMode(_pin, OUTPUT);
}

unsigned int BlinkTask::Proc(void)
{
  if (_enabled)
  {
      _state = ( _state + 1 ) & 0x01;
      digitalWrite(_pin, _state);

      return (_state == 0)?_inactiveTime:_activeTime;
  }
  else
  {
      digitalWrite(_pin, LOW);
      return _inactiveTime;
  }
}

void BlinkTask::HandleMsg (unsigned int msg, unsigned int arg )
{
   if (msg == MSG_SERIAL)
   {

       if (arg == 49)
       {
           Serial.println("Blink ON");
           _enabled = 1;
       }

       if (arg == 48)
       {
           Serial.println("Blink ON");
           _enabled = 0;
       }
   }
}
//---------------------------------------------------------
//   просто фары с функцией вкл-выкл (символы 2 и 3 с посл. порта)

class OnOffLightTask : public TaskInfo {
  private:
    byte _state;
    unsigned int _pauseTime;
    int _pin;
    int _enabled;
  public:
     OnOffLightTask (int pin);
     void HandleMsg (unsigned int msg, unsigned int arg );
     unsigned int Proc(void);
};

OnOffLightTask::OnOffLightTask (int pin)
{
    TaskInfo(0);  //вызов конструктора базового класса
    _state = 0;
    _pauseTime = 50;
    _pin = pin;
    pinMode(_pin, OUTPUT);
}

unsigned int OnOffLightTask::Proc(void)
{
  if(_state == 0)
  {
      digitalWrite(_pin, LOW);
  }
  else
  {
      digitalWrite(_pin, HIGH);
  }
  return _pauseTime;
}

void OnOffLightTask::HandleMsg (unsigned int msg, unsigned int arg )
{
   if (msg == MSG_SERIAL)
   {
       if (arg == 51)
       {
           _state = 1;
            Serial.println("OnOffLight ON");
       }

       if (arg == 50)
       {
           _state = 0;
           Serial.println("OnOffLight OFF");
       }

   }
}

//---------------------------------------------------------
//    стробоскоп с двумя вспышками и паузой
class StrobeTask : public TaskInfo {
  private:
    byte _state;
    unsigned int _activeTime;
    unsigned int _inactiveTime;
    unsigned int _pauseTime;
    int _pin;
    int _enabled;
  public:
     StrobeTask (int pin, unsigned int activeTime, unsigned int inactiveTime, unsigned int pauseTime);
     void HandleMsg (unsigned int msg, unsigned int arg );
     unsigned int Proc(void);
};

StrobeTask::StrobeTask (int pin, unsigned int activeTime, unsigned int inactiveTime, unsigned int pauseTime)
{
  TaskInfo(0);  //вызов конструктора базового класса
  _activeTime = activeTime;
  _inactiveTime = inactiveTime;
  _pauseTime = pauseTime;
  _state = 0;
  _pin = pin;
  _enabled = 1;
  pinMode(_pin, OUTPUT);
}

unsigned int StrobeTask::Proc(void)
{
  unsigned int pause;

  if (_enabled == 0)
  {
      digitalWrite(_pin, LOW);
      _state=0;
      return _pauseTime;
  }

  switch(_state) {
    case 0:   digitalWrite(_pin, HIGH);
              pause = _activeTime;
              break;
    case 1:   digitalWrite(_pin, LOW);
              pause = _inactiveTime;
              break;
    case 2:   digitalWrite(_pin, HIGH);
              pause = _activeTime;
              break;
    case 3:   digitalWrite(_pin, LOW);
              pause = _pauseTime;
              break;
  }



  _state =  _state + 1 ;
  if (_state == 4)
    _state = 0;



  return pause;
}

void StrobeTask::HandleMsg (unsigned int msg, unsigned int arg )
{
   if (msg == MSG_SERIAL)
   {

       if (arg == 49)
       {
         _enabled = 1;
         Serial.println("Strobe ON");
       }

       if (arg == 48)
       {
           _enabled = 0;
           Serial.println("Strobe OFF");
       }
   }
}

//---------------------------------------------------------
// мигалка с плавным поджиганием. с настройкой скорости
class FadeTask : public TaskInfo {
    private:
        byte _state;                // текущее состояние
        int _pin;
        unsigned int _sampleTime;   // скорость работы. чем меньше - тем быстрее мигает
    public:
        FadeTask (int pin, unsigned int sampleTime); // конструктор
        unsigned int Proc(void);                     // обработчик
};
FadeTask::FadeTask (int pin, unsigned int sampleTime)
{
  TaskInfo(0);
  _state = 0;
  _pin = pin;
  _sampleTime = sampleTime;
  pinMode(_pin, OUTPUT);
}
unsigned int FadeTask::Proc(void)
{
    switch (_state) {
        case 0: analogWrite(_pin, 1);
                break;
        case 1: analogWrite(_pin, 2);
                break;
        case 2: analogWrite(_pin, 4);
                break;
        case 3: analogWrite(_pin, 6);
                break;
        case 4: analogWrite(_pin, 8);
                break;
        case 5: analogWrite(_pin, 10);
                break;
        case 6: analogWrite(_pin, 12);
                break;
        case 7: analogWrite(_pin, 15);
                break;
        case 8: analogWrite(_pin, 255);   // максимум
                break;
        case 9: analogWrite(_pin, 15);
                break;
        case 10: analogWrite(_pin, 12);
                break;
        case 11: analogWrite(_pin, 10);
                break;
        case 12: analogWrite(_pin, 8);
                break;
        case 13: analogWrite(_pin, 6);
                break;
        case 14: analogWrite(_pin, 4);
                break;
        case 15: analogWrite(_pin, 2);
                break;
    }
    _state++;
    if (_state >= 16)
    {
        _state = 0;
    }
    return (_state == 9)?_sampleTime * 5:_sampleTime;
}
//---------------------------------------------------------


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

  taskManager.AddTask(new FadeTask(10, 40));                 // на D10 - мигалка PWM.
  taskManager.AddTask(new FadeTask(9, 41));                  // на D10 - мигалка PWM.
  taskManager.AddTask(new BlinkTask(13, 100, 500));          // на D13 - обычная мигалка в возможностью включения/выключения (символы 1 и 0 через Serial port ) вкл - 100 тиков, выкл - 500 тиков
  taskManager.AddTask(new StrobeTask(12, 100, 100 , 999));   // на D12 - двойной стробоскоп. вкл 2 раза 100 тиков с паузой между ними в 100 тиков. между сериями - 999 тиков
  taskManager.AddTask(new BlinkTask(8, 99, 500));            // на D13 - обычная мигалка в возможностью включения/выключения (символы 1 и 0 через Serial port ) вкл - 100 тиков, выкл - 500 тиков
  taskManager.AddTask(new OnOffLightTask(7));                // "фары" на D7. вкл/выкл символы 3 и 2 в терминале

  // настройка таймера 2 (первый нельзя использоватьтак как он нужен для работы PWM analogWrite )
  noInterrupts();           // disable all interrupts

  //Timer2 Settings: Timer Prescaler
  TCCR2B |= ((1<<CS22) | (0<<CS21) | (0<<CS20));
  // Use normal mode
  TCCR2B |= (0<<WGM21) | (0<<WGM20);
  // Use internal clock - external clock not used in Arduino
  ASSR |= (0<<AS2);
  TIMSK2 |= (1<<TOIE2) | (0<<OCIE2A);        //Timer2 Overflow Interrupt Enable
  interrupts();             // разрешаем прерывания
}




ISR(TIMER2_OVF_vect)        // обработка прерывания от таймера2
{
  taskManager.Tick();
}




void loop()                  // главный цикл программы
{
   byte incomingByte;
   taskManager.Run();

    if (Serial.available() > 0) {  //если есть доступные данные
        // считываем байт
        incomingByte = Serial.read();
        // отправляем сообщение в очередь задач
        Serial.print("Command:");
        Serial.println((int)incomingByte);
        taskManager.HandleMsg(MSG_SERIAL, (int)incomingByte );
    }

}

можем открыть окно терминала COM порта, и поиграться с включением/выключением светодиодов ( команды 1,2,3,4 )
Может быть кому-то будет полезно. Не вижу никаких преград чтобы вместо команд из компорта сделать захват ППМ от приемника.

  • 4235
Comments
Yahen

Мощный подход. Солидный 😃 Я бы просто в пару массивов все упихал. А тут с классами, менеджером 😃
Но в целом радует, так как без millis() и не в лупе.

omegapraim

Спасибо пригодится.

Lazy

Да да. И поставить 1Вт лампочки. 😃

DireSnake

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

Boev_Dmitry

ну просто было желание получить легко масштабируемый код. Ну первоначальное ТЗ “заказчика” было такое - “поставлю 2 лишних банки пива если я сам смогу в коде разобраться”. =) Насколько я помню, захват ППМ он сам и прикрутил к проекту довольно безболезненно, ибо я старался чтобы каждый блок кода был изолированным, и его изменение не тянуло за собой переписывание половины всего )))

Про масштабируемость… Можно, например, на один пин повесить несколько задач разного типа. и активировать их через функцию HandleMsg, переключая режимы работы “на лету”. Ничего “глобального” для этого переделывать не придется, только дописать чуток код задачек, или новые создать на основе базового класса.

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

Yahen

Это везде такая шняга, где однопоточная многозадачность. Во Flash машине, например, вообще на уровне ядра зашито ограничение на время работы кадровой процедуры 😃 Во всяких JS самому следить приходится. А в Ардуине все обычно перетекает в борьбу с алгоритмикой на 16МГц, и тем, что в ее дурноватой архитектуре подпаивание, например, резистора к одной ноге , вырубает ШИМ на трех других ногах, после чего проект плавно уползает на какой-нибудь STM32Discovery или другую плату на кортексе
Лампочки, лампочки… как, однако, после лета не хочется опять на работу. Программист, кстати, никому не нужен? 😃

Yahen
Lazy;bt105014

Да да. И поставить 1Вт лампочки. 😃

Вот за что уважаю Владимира, так за доброе слово к месту 😃

Lazy
Yahen;bt105022

Вот за что уважаю Владимира, так за доброе слово к месту 😃

В школе не учили КАК НАДО делать? Сойдёт и так? 😁 Ведь ничего сложного не говорю.
Да, будет работать, без вопросов.

Prikupets

Чето у Вас там Blink ON выводится на вкл и выкл 😃
А вообще, написано солидно, только зачем приоритеты в этом проекте? Или Task Scheduler скопирован откуда?

Boev_Dmitry

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