0

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);
  }
}
  • There is nothing in the while (alarmOn) loop that can unset alarmOn: if alarmOn is initially true you have an infinite loop. – Edgar Bonet Mar 25 '17 at 21:15
3

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... – Cello Guy Mar 25 '17 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. – Cello Guy Mar 27 '17 at 0:06
  • How can I use this as an Arduino library? – Cello Guy Mar 27 '17 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 Mar 27 '17 at 8:28

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.