I am building a simple security system with my Arduino Uno. I have an alarm()
function:
void alarm() {
tone(8, 3300, 200);
delay(200);
tone(8, 3000, 200);
delay(200);
}
When I call it in the loop()
function like so:
void loop() {
alarm();
}
the alarm sounds fine. I just want to be able to stop it. What I mean is, I want it to start the alarm (if it is off) when I call the alarm()
function, then stop it when I call the alarm()
function again. How can I do this?
P.S. Can someone with over 150 reputation please make the tone
tag? I can't believe it's not here!
Edit: I tried the following code, but the alarm just kept going:
bool alarmOn;
void setup() {
pinMode(8, OUTPUT);
alarm(true);
delay(5000);
alarm(false);
}
void loop() {
}
void alarm(boolean call) {
if (call) {
alarmOn = true;
} else {
alarmOn = false;
}
while (alarmOn) {
tone(8, 3300, 200);
delay(200);
tone(8, 3000, 200);
delay(200);
}
}
1 Answer 1
You can just have a variable that remembers whether the alarm is
supposed to be on or off. Let's call it alarm_on
. Then:
bool alarm_on;
static void start_alarm() { alarm_on = true; }
static void stop_alarm() { noTone(); alarm_on = false; }
void loop()
{
if (alarm_on) alarm();
if (some_condition()) start_alarm();
if (some_other_condition()) stop_alarm();
}
For a better approach that allows you to sound the alarm without blocking the whole program, see the Blink Without Delay Arduino tutorial.
Edit: I just wrote (but did not test) a non-blocking version of this alarm, based on a state machine and the timing technique from the Blink Without Delay tutorial. Here it is, as a class:
class TwoToneAlarm {
public:
static const uint16_t HIGH_FREQ = 3300;
static const uint16_t LOW_FREQ = 3000;
static const uint16_t TONE_DURATION = 200;
TwoToneAlarm(uint8_t output_pin)
: state(OFF), pin(output_pin) {}
// Start the alarm.
void start()
{
tone(pin, HIGH_FREQ);
state = HIGH_TONE;
last_tone_change = millis();
}
// Stop the alarm.
void stop()
{
noTone(pin);
state = OFF;
}
// Call this periodically to handle the tone changes.
void update()
{
if (state == OFF) return;
if (millis() - last_tone_change >= TONE_DURATION) {
switch (state) {
case HIGH_TONE:
tone(pin, LOW_FREQ);
state = LOW_TONE;
break;
case LOW_TONE:
tone(pin, HIGH_FREQ);
state = HIGH_TONE;
break;
default:
break;
}
last_tone_change += TONE_DURATION;
}
}
private:
enum { OFF, HIGH_TONE, LOW_TONE } state;
uint16_t last_tone_change;
uint8_t pin;
};
It could be used like this:
TwoToneAlarm alarm(8);
void loop()
{
if (some_scary_condition()) alarm.start();
if (some_other_condition()) alarm.stop();
alarm.update();
}
Note that the whole program must be non-blocking (delay()
-free),
otherwise the tone changes will not be done on time.
Edit: more about being non-blocking.
There is normally no reason for you to use delay()
at all in your
program. The only reason for this function to exist is to help complete
beginners write their first blink-the-LED program. As soon as you start
writing anything non trivial, you should ban it from your vocabulary.
The Blink Without Delay tutorial is here precisely to teach you how to
get rid of delay()
by using millis()
.
In this particular case, however, the drawback of delay()
may not be
too severe: if the alarm is sounding and you delay a tone change, you
may hear it as a glitch that breaks the rhythm of the sound. If the
delay is short enough, maybe up to 10 ms or so, you are unlikely to
notice it. Longer delays will be noticeable, and it's up to you to
decide whether you find them annoying or not.
It is possible to ensure a very steady rhythm despite the delays by
using interrupts, but this will cost you your 16-bit timer. Or you could
write your own implementation of tone()
that does both the pin
toggling and the tone changes using a single timer. Any such option will
require you to learn about low-level timer
programming, which is significantly
more demanding than learning to use millis()
.
-
Your code inspired me to do something different. See my edited answer above. About making the
tone
tag...Nobody– Nobody2017年03月25日 20:38:39 +00:00Commented Mar 25, 2017 at 20:38 -
That's perfect! Still, is there any way I can use
delay()
in the body? It is a security system and it needs to be user-friendly.Nobody– Nobody2017年03月27日 00:06:18 +00:00Commented Mar 27, 2017 at 0:06 -
How can I use this as an Arduino library?Nobody– Nobody2017年03月27日 00:35:50 +00:00Commented Mar 27, 2017 at 0:35
-
@CelloGuy: 1) There is no incompatibility between being
delay()
-free and being user friendly, but see the amended answer. 2) Making a piece of code into a library is a different question, unrelated to this one.Edgar Bonet– Edgar Bonet2017年03月27日 08:28:13 +00:00Commented Mar 27, 2017 at 8:28
while (alarmOn)
loop that can unsetalarmOn
: ifalarmOn
is initiallytrue
you have an infinite loop.