Бибикать и не тормозить

Тема в разделе "Arduino & Shields", создана пользователем petypen, 3 окт 2016.

  1. petypen

    petypen Нерд

    Коллеги, добрый день. Прошу вашего опыта.
    Для озвучивания успешного события (считывание RFID карты) хочу динамиком издавать звук "Бии-боо" длительностью до 0,5 - 0,8 сек. "Бии" - примерно 800 ... 1000Гц, "Боо" - 200 ... 300 Гц

    Как бы мне так сделать, что бы на время бибиканья не тормозить основной код? Я наверное какой то асинхронности хочу, но может есть решение, что бы не ставить delay()?
     
  2. Tomasina

    Tomasina Сушитель лампочек Модератор

    каждое событие (чтение карты, управление бипером и пр.) записать в виде отдельной функции.
    Эти функции постоянно крутятся в loop, неиспользуемые в данный момент (определять можно по флагам) просто игнорируются.
    Типичный пример (только здесь функции не вынесены отдельно, а всунуты в switch):
    Код (C++):
    enum { LED_PIN = 13 };
    enum LedState { LED_ON, LED_OFF, LED_BLINK };

    LedState led_state;

    void setup()
    {
      led_state = LED_OFF;
      pinMode(LED_PIN, OUTPUT);

      Serial.begin(115200);
    }

    void loop()
    {
      if (Serial.available())
      {
        char command = Serial.read();
     
        switch (command)
        {
          case '1': led_state = LED_ON; break;
          case '0': led_state = LED_OFF; break;
          case '*': led_state = LED_BLINK; break;
       
          default:
          {
            for (int i = 0; i < 5; ++i)
            {
              digitalWrite(LED_PIN, HIGH);
              delay(50);
              digitalWrite(LED_PIN, LOW);
              delay(50);
            }
          }
        }
      }

      switch (led_state)
      {
        case LED_ON: digitalWrite(LED_PIN, HIGH); break;
        case LED_OFF: digitalWrite(LED_PIN, LOW); break;
     
        case LED_BLINK:
        {
          static unsigned long start_millis = 0;
       
          if (millis() - start_millis >= 300)
          {
            start_millis = millis();
            digitalWrite(LED_PIN, !digitalRead(LED_PIN));
          }
        }
      }
    }
     
  3. Onkel

    Onkel Гуру

    в ардуинах есть шим 500 Гц, включите и выключите любой порт с шимом. Асинхрон полный!
     
  4. AlexU

    AlexU Гуру

    Подтверждаю -- асинхрон полный, вот только нужно будет почитать документацию на ATmega328, т.к. стандартные Arduino'вские библиотеки не умеют менять частоту ШИМ. Поэтому частоту нужно будет задавать при помощи изменения регистров напрямую (или найти стороннюю библиотеку, которая это сможет делать). И желательно не трогать 'Timer 0' -- пины 5 и 6, т.к. он используется часами реального времени, которые программно реализованы в Arduino. 'Timer 1' имеет диапазон от 15 Гц до 62 кГц, 'Timer 2' -- от 61 Гц до 62 кГц.

    PS: Всё сказанное для Arduino на базе ATmega328.
     
    petypen нравится это.
  5. rkit

    rkit Гуру

    Прерывание на таймер. Прервет скрипт в момент срабатывания, потом продолжит.
     
  6. AlexU

    AlexU Гуру

    Зачем активировать прерывание, если нужно просто выдать сигнал нужной частоты и скважности? Выдачу сигнала определённой частоты можно сделать без всяких прерываний и влияния на выполнение основного кода.
     
  7. petypen

    petypen Нерд

    Коллеги, спасибо. Вы помогли сформулировать задачу в голове, осталось только решить.
    Важно! нужен звук БииБоо (две тональности последовательно) общей продолжительностью 0,6 - 0,8 сек

    вот скелет тестового примера
    Код (C++):
    // LIBRARY
    #include <RFID.h>
    #include <SPI.h>

    // SETUP PINS RC522
    #define RFID_SS 4 // slave select
    #define RFID_RST 3 // reset

    // SETUP PINS BUZZER
    #define BUZZER_PIN 2

    // CREATE INSTANS
    RFID rfidReader = RFID(RFID_SS, RFID_RST);

    // GLOBAL VAR
    char rfidTagID[14]; // id RFID tag

    ///////////////////////////////////////////////////////

    void prepareRFID() {
      SPI.begin();
      rfidReader.init();
    }

    void setup() {
      prepareRFID();
      pinMode(BUZZER_PIN, OUTPUT);
    }

    void loop() {
      if (rfidReader.isCard()) {
        beepOK(); // БииБоо однократно, когда считана карта и не дожидаясь
                  // окончания сигнала продолжить выполнять какой то важный код
      }
      // ... какой то важный код ...
    }

    void beepOK() {

      // 1. тут нужно бикнуть Бии - 0,4сек

      // тут после того, как закончится п.1, нужно биккнуть Боо - 0,4сек

      // !!! и не тормозить основной loop() !!!
    }
    Важные моменты:
    1. БииБоо делать однократно только после успешного чтения карты rfidReader.isCard(). Постоянно сигналить не нужно.
    2. БииБоо не тормозит основной цикл
    3. Хочется именно БииБоо а не просто Бии

    Продолжаю копать. Пока идея вырисовывается такая:
    Код (C++):
    void beepOK() {
      // 1. Установить таймер на вызыване прерывания каждые 0,4сек
      // 2. Если это первое срабатывание таймера, то заставить ШИМ генерить
      //    на ножку 1000Гц (Бии).
      //    ?? ШИМ же может генерить самостоятельно, по заданным параметрам ??
      // 3. Если это второе срабатывание таймера, то заставить ШИМ генерить
      //    на ножку 300Гц (Боо).
      // 4. Если это треье срабатывание таймера, то сбросить таймер
    }
     
  8. rkit

    rkit Гуру

    Я предлагал просто генерировать звук прерываниями, без сложностей с ШИМ.
     
  9. petypen

    petypen Нерд

    Коллеги, спасибо всем за помощь. Толкнули в нужном направлении.
    БииБоо готово. Бибикает при поднесении карточки и не тормозит программу:
    Код (C++):
    // LIBRARY
    #include <RFID.h>
    #include <SPI.h>
    #include <TimerOne.h>

    // SETUP PINS RC522
    #define RFID_SS 4 // slave select
    #define RFID_RST 2 // reset

    // SETUP PINS BUZZER
    #define BUZZER_PIN 3

    // CREATE INSTANS
    RFID rfidReader = RFID(RFID_SS, RFID_RST);

    ///////////////////////////////////////////////////////

    void prepareRFID() {
      SPI.begin();
      rfidReader.init();
    }

    void setup() {
      prepareRFID();
      pinMode(BUZZER_PIN, OUTPUT);
    }

    void loop() {
      if (rfidReader.isCard()) {
        beeBoo();
      }
      // ... какой то важный код ...
    }

    void beeBoo() {
      Timer1.initialize();
      Timer1.attachInterrupt(beepPWM,400000);
    }

    void beepPWM(){
      static byte count = 0;

      switch(count){
        case 0:
          TCCR2B = TCCR2B & 0b11111000 | 0x03;
          analogWrite(BUZZER_PIN,128);
          count++;
          break;
        case 1:
          TCCR2B = TCCR2B & 0b11111000 | 0x05;
          analogWrite(BUZZER_PIN,128);
          count++;
          break;
        default:
          Timer1.detachInterrupt();
          digitalWrite(BUZZER_PIN,LOW);
          count = 0;
      }
    }
     
    ostrov и ИгорьК нравится это.
  10. save.l

    save.l Нерд

    Добрый день!
    Сейчас в loop использую код, в котором через каждые 9 секунд, светодиод начинает моргать определенным образом в течении последующей 1 секунды:
    Код (C++):
            msErr = millis();
            if((msErr - ms2Err) > 9000){                                        
                 if((msErr - ms1Err) > 125 || msErr < ms1Err){
                      ms1Err = msErr;
                      if(blink_mode_err & 1<<(blink_loop&0x07) ) mcp.digitalWrite(p_ledErr, HIGH);
                      else  mcp.digitalWrite(p_ledErr, LOW);
                      blink_loop++;  
                 }
                 if((msErr - ms2Err) > 10000) ms2Err = msErr;
            }
    Естественно, при увеличении кол-ва других операций в loop, стали вылазить проблемы.
    Пришлось обратиться к TimerOne.h
    Но не могу понять алгоритм действий. Ну вызываем мы прерывание через каждые 9 сек, а дальше то нужно выполнить не какую то быструю операцию, а посвятить целую секунду на работу со светодиодом, не нарушая в это время работу других операций в loop. Подскажите плз
     
  11. KindMan

    KindMan Гик

    А что там за операции, которые вам доставляют проблемы? Может их оптимизировать?
    Мигание у вас какое хитрое…
     
  12. save.l

    save.l Нерд

    может. но меньше их не станет точно, и в любом случае не сегодня так завтра вопрос снова возникнет
    код из статьи https://habr.com/ru/post/357904/
     
  13. Asper Daffy

    Asper Daffy Гуру

    И что же ты такое делать собираешься со светодиодом целую секунду? Тереть его как лампу Алладина?

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

    Хотя, если бы описал что тебе надо чуть подробнее, чем "мигать определённым образом", то было бы проще. А так, мог только сказать: чтобы светодиод мигал определённым образом, нужно определённым образом написать программу.
     
  14. save.l

    save.l Нерд

    Самое интересное, что ответ на мой вопрос есть в приведенной мною же статье...Недосмотрел
     
  15. b707

    b707 Гуру

    вот именно :) Сначала читаем невнимательно, а потом вопросы...
    Таймер в статье вызывается не на "1 секунду раз в 9 секунд" - а 8 раз в секунду.

    Прям ностальгией повеяло - я тоже мигал светиком по битовой матрице в своем самом первом проекте :)
     
  16. save.l

    save.l Нерд

    а я и не говорил что это из статьи. из статьи я взял код управления светодиодом по битовой матрице. а интервал в 9 секунд - это уже по моей задаче нарисовалось
    ;)
    сейчас это уже не модно?
     
    Последнее редактирование: 11 сен 2019 в 22:02
  17. parovoZZ

    parovoZZ Гуру

    нужен конечный автомат состояний.
    что-то подобное я описывал здесь
    http://forum.amperka.ru/threads/Пощебечем-Часть-i.19009/
     
  18. Airbus

    Airbus Радиохулиган Модератор

  19. save.l

    save.l Нерд

    так работает (светодиод моргает как указано):
    Код (C++):
    #include "TimerOne.h"
    #include "Wire.h"

    uint8_t  blink_loop = 0;
    uint8_t  blink_mode = 0;
    uint8_t  modes_count = 0;

    // Callback функция по таймеру
    void timerIsr()
    {
       if(  blink_mode & 1<<(blink_loop&0x07) ) digitalWrite(13, HIGH);
       else  digitalWrite(13, LOW);
       blink_loop++;  
    }

    void setup() {
       Serial.begin(9600);
      pinMode(13,OUTPUT);
    // blink_mode = 0B00001010;
      Timer1.initialize(125000);
      Timer1.attachInterrupt( timerIsr );
    }
     
    void loop() {
       blink_mode = 0B00001111; //Мигание по 0.5 сек
       delay(5000);
       Serial.println("1");
       blink_mode = 0B00000001; //Короткая вспышка раз в секунду
       delay(5000);
       Serial.println("2");
       blink_mode = 0B00000101; //Две короткие вспышки раз в секунду
       delay(5000);
       Serial.println("3");
       blink_mode = 0B00010101; //Три короткие вспышки раз в секунду
       delay(5000);
       Serial.println("4");
       blink_mode = 0B01010101;  //Частые короткие вспышки (4 раза в секунду)
       delay(5000);
       Serial.println("5");
    }
    а так не работает (убрал только строчки Serial):
    Код (C++):
    #include "TimerOne.h"
    #include "Wire.h"

    uint8_t  blink_loop = 0;
    uint8_t  blink_mode = 0;
    uint8_t  modes_count = 0;

    // Callback функция по таймеру
    void timerIsr()
    {
       if(  blink_mode & 1<<(blink_loop&0x07) ) digitalWrite(13, HIGH);
       else  digitalWrite(13, LOW);
       blink_loop++;  
    }

    void setup() {
       Serial.begin(9600);
      pinMode(13,OUTPUT);
    // blink_mode = 0B00001010;
      Timer1.initialize(125000);
      Timer1.attachInterrupt( timerIsr );
    }
     
    void loop() {
       blink_mode = 0B00001111; //Мигание по 0.5 сек
       delay(5000);
    //   Serial.println("1");
       blink_mode = 0B00000001; //Короткая вспышка раз в секунду
       delay(5000);
    //   Serial.println("2");
       blink_mode = 0B00000101; //Две короткие вспышки раз в секунду
       delay(5000);
    //   Serial.println("3");
       blink_mode = 0B00010101; //Три короткие вспышки раз в секунду
       delay(5000);
    //   Serial.println("4");
       blink_mode = 0B01010101;  //Частые короткие вспышки (4 раза в секунду)
       delay(5000);
    //  Serial.println("5");
    }
    почему?
     
  20. Asper Daffy

    Asper Daffy Гуру

    Потому, что неправильно написано.

    Переменная blink_mode используется в обработчике прерывания, а изменяется вне его. Она сама должна сообщать об этом компилятору? Так сама она этого не делает - это работа программиста. Исправь и проблема уйдёт.

    И ещё. Прямо к твоей проблеме не относится, но из общих соображений: переменная blink_loop используется только в функции timerIsr, так какого рожна она у тебя глобальна? Чтобы пудрить мозги любому, кто будет разбираться в коде? Если переменная используется только в одной функции - она должна быть локально ней объявлена. Если её при этом нужно сохранять значение, то объявляй статической.

    Ну, и ... "if( blink_mode &1<<(blink_loop&0x07))" формально то правильно, но ... имей совесть, тебе что скобок жалко?