Запуск Arduino с 2 выходами и 2 входами

Совершенно новый для Arduino, 3 недели назад!

Я делаю проект с 2 входами (2 ультразвуковых датчика) и 2 выходами (зуммер и отправка SMS)

Код для запуска зуммера и смс отлично работает по отдельности. Звуковой сигнал перестанет звучать примерно через 5 секунд.

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

Я читал на форумах об использовании millis / blinkwithoutdelay, но не знаю, как включить эти функции в свой режим, поскольку примеры, опубликованные в Интернете, отличаются от моего проекта.

Ниже приведен код, который я создал.

Был бы признателен, если бы мне помогли опытные люди.

#define TRIG A0 //Выводы модуля
#define ECHO A1
#define TRIG1 A2 //Выводы модуля
#define ECHO1 A3
#define Buzzerpin 13

int ctn = 0;

void setup() {
  Serial.begin(9600); // Последовательный мониторинг
  pinMode(TRIG, OUTPUT); // Инициализация триггерного выхода и эхо-ввода
  pinMode(ECHO, INPUT_PULLUP);
  pinMode(TRIG1, OUTPUT); // Инициализация триггерного выхода и эхо-ввода
  pinMode(ECHO1, INPUT_PULLUP);
  pinMode(Buzzerpin, OUTPUT);
}

void loop() {

  digitalWrite(TRIG, LOW); // Установить вывод триггера в низкий уровень на 2 мкс
  delayMicroseconds(2);

  digitalWrite(TRIG, HIGH); // Отправляем максимум 10 мкс, чтобы активировать ранжирование в соответствии со спецификациями
  delayMicroseconds(20);

  digitalWrite(TRIG, LOW); // Снова отправляем низкий пин
  delayMicroseconds(2);

  digitalWrite(TRIG1, LOW); // Установите триггерный контакт в низкий уровень на 2 мкс. Перед этим подайте короткий НИЗКИЙ импульс, чтобы обеспечить чистый ВЫСОКИЙ импульс.
  delayMicroseconds(2);

  digitalWrite(TRIG1, HIGH); // Отправляем максимум 10 мкс, чтобы активировать ранжирование в соответствии со спецификациями
  delayMicroseconds(20);

  digitalWrite(TRIG1, LOW); // Снова отправляем низкий пин
  delayMicroseconds(2);

  int distance = pulseIn(ECHO, HIGH, 26000); // Чтение импульса времени
  int distance1 = pulseIn(ECHO1, HIGH, 26000); // Чтение импульса времени
  distance = distance/58;
  distance1 = distance1/58;

  if (distance < 25) {
    Serial.print("\r");
    delay(1000);
    Serial.print("AT+CMGF=1\r");
    delay(1000);
    /*Replace XXXXXXXXXX to 10 digit mobile number & ZZ to 2 digit country code*/
    Serial.print("AT+CMGS=\"+YYXXXX\"\r"); // YY — код страны. XXX — номер.
    delay(1000); //
    //Текст сообщения для отправки.
    Serial.print("HELLO There");
    delay(1000);
    Serial.write(0x1A);
    delay(1000);
  }
  else {}

  if (distance1 < 25 && ctn < 150) {
    ctn += 1;
    digitalWrite(Buzzerpin, HIGH);
  }
  else {
    digitalWrite(Buzzerpin, LOW);
  }

  if (distance1 > 25 && ctn >= 150) {
    ctn = 0;
    digitalWrite(Buzzerpin, HIGH);
  }
}

, 👍1


2 ответа


4

Первое: вы должны вызвать pulseIn() сразу после отправки TRIG. пульс. Если вы будете ждать слишком долго, вы пропустите начало эхо-импульса. Например, здесь:

int distance = pulseIn(ECHO, HIGH, 26000);
int distance1 = pulseIn(ECHO1, HIGH, 26000);

второй pulseIn() запускается слишком поздно из-за затраченного времени первым.

Далее, общий подход к ведению дел — неблокирующий способ. (и, таким образом, иметь возможность обрабатывать параллельные задачи) заключается в программировании каждой задачи в виде конечного автомата. Соедините это с метод синхронизации, используемый в руководстве по Arduino «Мигание без задержки», если вам нужны действия по времени.

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

Правка. Я подробно рассказал, как спроектировал этот конечный автомат.

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

SENT_NOTHING → SENT_CR → SENT_CMGF → SENT_CMGS → SENT_MSG → SENT_ALL.

Все вышеперечисленные переходы отправляют строку на модуль GSM. Первый один (SENT_NOTHING → SENT_CR) запускается обнаруженным расстоянием быть меньше 25 см. Следующие запускаются по времени второй после предыдущего. Мы должны добавить один последний переход (SENT_ALL → SENT_NOTHING), который не выполняет никаких действий, но позволяет системе «забыть» о том, что он отправил SMS, и, таким образом, подготовить его к отправке нового. Этот переход также будет инициирован по времени после потенциально долгого задержка (как часто пользователь хотел бы получать эти напоминания?). В код ниже, эта последняя задержка составляет одну секунду, что я считаю слишком короткий, но соответствует последнему delay(1000) в опубликованном вами коде. реализация будет такой:

static enum {
  SENT_NOTHING, SENT_CR, SENT_CMGF, SENT_CMGS, SENT_MSG, SENT_ALL
} sms_state = SENT_NOTHING;
static uint32_t time_last_command_sent;
uint32_t now = millis();
switch (sms_state) {
  case SENT_NOTHING:
    if (distance < 25) {
      Serial.print("\r");
      time_last_command_sent = now;
      sms_state = SENT_CR;
    }
    break;
  case SENT_CR:
    if (now - time_last_command_sent >= 1000) {
      Serial.print("AT+CMGF=1\r");
      time_last_command_sent = now;
      sms_state = SENT_CMGF;
    }
    break;
  case SENT_CMGF:
    if (now - time_last_command_sent >= 1000) {
      Serial.print("AT+CMGS=\"+YYXXXX\"\r");
      time_last_command_sent = now;
      sms_state = SENT_CMGS;
    }
    break;
  // и так далее для случаев SENT_CMGS и SENT_MSG...
  case SENT_ALL:
    if (now - time_last_command_sent >= 1000) {
      sms_state = SENT_NOTHING;  // забываем, что отправили смс
    }
    break;
}

Однако у меня есть проблема с этой реализацией: она слишком повторяющиеся, как случаи SENT_CR, SENT_CMGF, SENT_CMGS и SENT_MSG по сути являются копиями одного и того же кода. Для того, чтобы сделать код сушилка, я предпочитаю объединять их в одно состояние и разделять информацию о состоянии в две переменные: sms_state и отправлено_команд. Новые возможные значения для sms_state теперь следующие:

  • SMS_READY: код готов к отправке сообщения, как только оно будет требуется обнаруженным расстоянием.
  • SMS_SENDING: он начал отправлять команды модулю GSM, но это еще не сделано.
  • SMS_DONE: SMS-сообщение было отправлено, но никакое другое не будет отправлено, пока прошло некоторое время, после чего он перейдет в SMS_READY.

Тогда сопоставление с «полными» состояниями машины:

 full state    │ sms_state   commands_sent
───────────────┼───────────────────────────
 SENT_NOTHING  │ SMS_READY         0
 SENT_CR       │ SMS_SENDING       1
 SENT_CMGF     │ SMS_SENDING       2
 SENT_CMGS     │ SMS_SENDING       3
 SENT_MSG      │ SMS_SENDING       4
 SENT_ALL      │ SMS_DONE          5

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

if (commands_sent == 0) {
  // обрабатываем случай SMS_READY
} else if (commands_sent < command_count) {
  // обрабатываем случай SMS_SENDING
} else {
  // обрабатываем случай SMS_DONE
}

У зуммера есть только два состояния: BUZZER_OFF и BUZZER_ON. Переходы обусловлены измеренным расстоянием и для ВКЛ. → OFF переход, а также к моменту включения.

Вот моя предварительная, непроверенная реализация этого подхода:

#define TRIG A0 //Выводы модуля
#define ECHO A1
#define TRIG1 A2 //Выводы модуля
#define ECHO1 A3
#define Buzzerpin 13

// Команды для отправки модулю GSM.
const int command_count = 5;
const char * const commands[command_count] = {
  "\r",
  "AT+CMGF=1\r",
  "AT+CMGS=\"+YYXXXX\"\r",
  "HELLO There",
  "\x1a"
};

void setup() {
  Serial.begin(9600);
  pinMode(TRIG, OUTPUT);
  pinMode(ECHO, INPUT_PULLUP);
  pinMode(TRIG1, OUTPUT);
  pinMode(ECHO1, INPUT_PULLUP);
  pinMode(Buzzerpin, OUTPUT);
}

void loop() {
  // Измеряем оба расстояния.
  digitalWrite(TRIG, LOW);
  digitalWrite(TRIG, HIGH);
  delayMicroseconds(20);
  digitalWrite(TRIG, LOW);
  int distance = pulseIn(ECHO, HIGH, 26000) / 58;
  digitalWrite(TRIG1, HIGH);
  delayMicroseconds(20);
  digitalWrite(TRIG1, LOW);
  int distance1 = pulseIn(ECHO1, HIGH, 26000) / 58;

  // Время для обоих конечных автоматов.
  uint32_t now = millis();

  // Отправляем СМС.
  static enum {SMS_READY, SMS_SENDING, SMS_DONE} sms_state;
  static uint32_t time_last_command_sent;
  static int commands_sent = 0;
  switch (sms_state) {
    case SMS_READY:
      if (distance < 25) {
        Serial.print(commands[commands_sent++]);
        time_last_command_sent = now;
        sms_state = SMS_SENDING;
      }
      break;
    case SMS_SENDING:
      if (now - time_last_command_sent >= 1000) {
        Serial.print(commands[commands_sent++]);
        time_last_command_sent = now;
        if (commands_sent >= command_count) {
          sms_state = SMS_DONE;
        }
      }
      break;
    case SMS_DONE:
      if (now - time_last_command_sent >= 1000) {
        commands_sent = 0;
        sms_state = SMS_READY;
      }
      break;
  }

  // Включите зуммер.
  static enum {BUZZER_OFF, BUZZER_ON} buzzer_state;
  static uint32_t time_buzzer_started;
  if (buzzer_state == BUZZER_OFF && distance1 < 25) {
    digitalWrite(Buzzerpin, HIGH);
    buzzer_state = BUZZER_ON;
    time_buzzer_started = now;
  } else if (buzzer_state == BUZZER_ON && distance1 >= 25
             && now - time_buzzer_started >= 2000) {
    digitalWrite(Buzzerpin, LOW);
    buzzer_state = BUZZER_OFF;
  }
}
,

Ух ты. Вы серьезно переписали код OP. Я решил относиться к этому как к ситуации «дайте человеку рыбу/научите человека ловить рыбу» и вместо этого предложил общее руководство., @Duncan C

@DuncanC, важнее то, что это хороший пример техники, @Juraj

Согласитесь, это элегантная переработка кода OP., @Duncan C

Привет Эдгар, большое спасибо за код. Могу ли я проверить, могу ли я изменить время в 'if (сейчас - time_last_command_sent >= 1000) {' как для SMS_SENDING, так и для SMS_DONE? Нарушит ли это поток цепи?, @Zac

@Zac: Я призываю вас изучить этот код (начните со ссылки на «конечный автомат», которую я предоставил), понять его (задайте вопросы, если необходимо), а затем измените _все, что хотите_. На самом деле я считаю, что вам действительно нужно поиграть с ним, чтобы полностью понять его работу., @Edgar Bonet


0

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

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

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

Вызовы delay(1000) в вашем цикле не будут работать. Каждый из них заставляет ВСЕ останавливаться на целую секунду. Вы не будете проверять ультразвуковой датчик, вы не измените состояние зуммера, вы не будете делать НИЧЕГО в течение 5 секунд, как только вы войдете в тело этого оператора if. Эти вызовы с задержкой должны быть прекращены.

,