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;
}
}