1

Totally new to Arduino, 3 weeks old!

I am doing a project with 2 inputs (2 x ultrasonic sensors) and 2 outputs (buzzer and send SMS)

The code to run the buzzer and sms works perfectly individually. For the buzzer, it will stop buzzing after approx 5 seconds.

I would love them to run concurrently but it didn't work as planned when I upload the code on the board.

I've read on forums to use millis / blinkwithoutdelay but am unsure how I can incorporate those functions on my mode as the examples posted online are different from my project.

The following is the code I have created.

Would appreciate if I can get some help from the experienced folks here.

#define TRIG A0 //Module pins
#define ECHO A1
#define TRIG1 A2 //Module pins
#define ECHO1 A3
#define Buzzerpin 13

int ctn = 0;

void setup() {
  Serial.begin(9600); // Serial monitoring
  pinMode(TRIG, OUTPUT); // Initializing Trigger Output and Echo Input
  pinMode(ECHO, INPUT_PULLUP);
  pinMode(TRIG1, OUTPUT); // Initializing Trigger Output and Echo Input
  pinMode(ECHO1, INPUT_PULLUP);
  pinMode(Buzzerpin, OUTPUT);
}

void loop() {

  digitalWrite(TRIG, LOW); // Set the trigger pin to low for 2uS
  delayMicroseconds(2);

  digitalWrite(TRIG, HIGH); // Send a 10uS high to trigger ranging according to specs
  delayMicroseconds(20);

  digitalWrite(TRIG, LOW); // Send pin low again
  delayMicroseconds(2);

  digitalWrite(TRIG1, LOW); // Set the trigger pin to low for 2uS. Give a short LOW pulse beforehand to ensure a clean HIGH pulse
  delayMicroseconds(2);

  digitalWrite(TRIG1, HIGH); // Send a 10uS high to trigger ranging according to specs
  delayMicroseconds(20);

  digitalWrite(TRIG1, LOW); // Send pin low again
  delayMicroseconds(2);

  int distance = pulseIn(ECHO, HIGH, 26000); // Read in times pulse
  int distance1 = pulseIn(ECHO1, HIGH, 26000); // Read in times pulse
  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 is the country code XXX is the number
    delay(1000); //
    //The text of the message to be sent.
    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);
  }
}
5

First thing: you should call pulseIn() right after sending the TRIG pulse. If you wait too much, you will miss the start of the echo pulse. For instance, here:

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

the second pulseIn() is starting too late, because of the time taken by the first one.

Next, the general approach for doing things is a non-blocking fashion (and thus being able to handle concurrent tasks) is to program each task in the form of a finite state machine. Combine this with the timing technique used in the “Blink without delay” Arduino tutorial if you need time-triggered actions.

Here I would use one state machine for sending the SMS and another one for the buzzer. The first one is the most complex:

Edit: I expanded on how I designed this state machine.

In a canonical finite state machine implementation, each action is associated with a state transition. We then have a transition for each command sent to the GSM module, and we can label the states according to which commands have already been sent, like this:

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

All of the above transitions send a string to the GSM module. The first one (SENT_NOTHING → SENT_CR) is triggered by the distance detected being less than 25 cm. The following ones are time-triggered one second after the previous one. We have to add one final transition (SENT_ALL → SENT_NOTHING) that performs no action but lets the system “forget” it has sent an SMS and thus makes it ready to send a new one. This transition would also be time-triggered, after a potentially long delay (how often would the user like to receive those reminders?). In the code below, this last delay is one second, which I believe is too short, but matches the last delay(1000) in the code you posted. The implementation would be like this:

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;
  // and so on for the cases SENT_CMGS and SENT_MSG...
  case SENT_ALL:
    if (now - time_last_command_sent >= 1000) {
      sms_state = SENT_NOTHING;  // forget we sent an SMS
    }
    break;
}

I have an issue with this implementation though: it is overly repetitive, as the cases SENT_CR, SENT_CMGF, SENT_CMGS and SENT_MSG are essentially copies of the same code. In order to make the code dryer, I prefer merging them into a single state, and splitting the state information into two variables: sms_state and commands_sent. The new possible values for sms_state are now:

  • SMS_READY: the code is ready to send the message as soon as it is required by the detected distance.
  • SMS_SENDING: it has started to send the commands to the GSM module, but is not done yet.
  • SMS_DONE: the SMS has been sent, but no other one will be sent until some time has elapsed, after which it will transition to SMS_READY.

The mapping to the “full” machine states is then:

 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

It could be argued that sms_state is redundant, as all the state information is contained in commands_sent. I still wanted to keep sms_state in order to easily switch on it. This is not the only possible approach though, and it would be perfectly sensible to write something like

if (commands_sent == 0) {
  // handle the case SMS_READY
} else if (commands_sent < command_count) {
  // handle the case SMS_SENDING
} else {
  // handle the case SMS_DONE
}

For the buzzer, there are only two states: BUZZER_OFF and BUZZER_ON. The transitions are conditioned by the measured distance and, for the ON → OFF transition, also by the time it has been ON.

Here is my tentative, untested implementation of this approach:

#define TRIG A0 //Module pins
#define ECHO A1
#define TRIG1 A2 //Module pins
#define ECHO1 A3
#define Buzzerpin 13

// Commands to be sent to the GSM module.
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() {
  // Measure both distances.
  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;

  // Timing for both state machines.
  uint32_t now = millis();

  // Send the SMS.
  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;
  }

  // Drive the buzzer.
  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;
  }
}
5
  • Wow. You did a major rewrite of the OPs code. I decided to treat this as a "give a man a fish/teach a man to fish" situation, and instead offer general guidance. – Duncan C Jul 22 '20 at 15:42
  • @DuncanC, more important is that this is a nice example of the technique – Juraj Jul 22 '20 at 16:45
  • Agreed, it's is an elegant reworking of the OPs code. – Duncan C Jul 22 '20 at 16:49
  • Hi Edgar, thank you so much for the code. Can I check, can I change the time in 'if (now - time_last_command_sent >= 1000) {' for both SMS_SENDING and SMS_DONE? Will it disrupt the flow of the circuit? – Zac Jul 23 '20 at 6:21
  • @Zac: I encourage you to study this code (start with the “finite state machine” link I provided), understand it (ask questions if needed) and then change anything you want. Actually I believe you really have to play with it in order to fully comprehend its working. – Edgar Bonet Jul 23 '20 at 7:42
0

Using the blinkwithoutdelay approach is not incorporating functions, it is a coding style.

The idea is that you write your loop code to pass through without any delays, or with only very tiny delays. On each pass through the loop, you see how much time has passed, and if it's been 5 seconds since you started the buzzer, you turn off the buzzer.

You can probably get away with the microsecond delays that you use in your ultrasonic sensor code. Those delays are so small that they will only cause very short pauses in your app.

The delay(1000) calls in your loop are not going to work. Each one of those causes EVERYTHING to stop for a full second. You won't check the ultrasonic sensor, you won't change the buzzer state, you won't do ANYTHING, for 5 seconds, once you enter the body of that if statement. Those delay calls have to go.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.