Arduino parallel processes. Interrupts in Arduino



Hardware interrupts

I couldn’t find a funny picture for this lesson, I only found some lecture on programming, and the very beginning of this lecture perfectly explains to us what interrupt. An interruption in Arduino can be described in exactly the same way: the microcontroller “drops everything,” switches to executing a block of functions in the interrupt handler, executes them, and then returns exactly to the place in the main code where it stopped.

There are different types of interruptions, that is, not the interruptions themselves, but their causes: an interruption can be caused by an analog-to-digital converter, a timer-counter, or literally a microcontroller pin. These interrupts are called external interrupts. hardware, and that’s what we’ll talk about today.

External hardware interrupt is an interrupt caused by a change in voltage on a microcontroller pin. The main point is that the microcontroller (computing core) does not poll the pin And doesn't waste time on this, the pin is handled by another piece of hardware. As soon as the voltage on the pin changes (meaning a digital signal, +5 applied/+5 removed), the microcontroller receives the signal, drops everything, processes the interrupt, and returns to work. Why is this necessary? Most often, interrupts are used to detect short events - pulses, or even to count their number, without loading the main code. A hardware interrupt can catch a short button press or sensor trigger during complex long calculations or delays in code, i.e. roughly speaking, the pin is being polled parallel to the main code. Interrupts can also wake up the microcontroller from power saving modes, when almost all peripherals are turned off. Let's see how to work with hardware interrupts in the Arduino IDE.

Interrupts in Arduino

Let's start with the fact that not all pins “can” interrupt. Yes, there is such a thing as pinChangeInterrupts, but we will talk about it in advanced lessons. Now we need to understand that hardware interrupts can only generate certain pins:

MK / interrupt number INT 0 INT 1 INT 2 INT 3 INT 4 INT 5
ATmega 328/168 (Nano, UNO, Mini) D2 D3
ATmega 32U4 (Leonardo, Micro) D3 D2 D0 D1 D7
ATmega 2560 (Mega) D2 D3 D21 D20 D19 D18

As you understand from the table, interrupts have their own number, which differs from the pin number. By the way, there is a convenient function digitalPinToInterrupt(pin), which takes the pin number and returns the interrupt number. By feeding this function the number 3 to the Arduino nano, we get 1. Everything is according to the table above, a function for the lazy.

An interrupt is connected using the function attachInterrupt(pin, handler, mode):

  • pin– interrupt number
  • handler– name of the interrupt handler function (you need to create it yourself)
  • mode– “mode” of interrupt operation:
    • LOW(low) – triggered by a signal LOW on pin
    • RISING(growth) – triggered when the signal on pin c changes LOW on HIGH
    • FALLING(drop) – triggered when the signal on pin changes HIGH on LOW
    • CHANGE(change) – triggered when the signal changes (with LOW on HIGH and vice versa)

The interrupt can also be disabled using the function detachInterrupt(pin), where pin – again interrupt number.

You can also globally disable interrupts using the function noInterrupts() and resolve them again with interrupts(). Be careful with them! noInterrupts() will also stop timer interrupts, and all time functions and PWM generation will break.

Let's look at an example in which button presses are counted in the interrupt, but in the main loop they are output with a delay of 1 second. Working with the button in normal mode, it is impossible to combine such a rough output with a delay:

Volatile int counter = 0; // counter variable void setup() ( Serial.begin(9600); // opened a port for communication // connected a button on D2 and GND pinMode(2, INPUT_PULLUP); \ // D2 is interrupt 0 // handler - function buttonTick // FALLING - when the button is pressed there will be a signal 0, we catch it attachInterrupt(0, buttonTick, FALLING); ) void buttonTick() ( counter++; // + pressing ) void loop() ( Serial.println(counter); // output delay(1000); // wait )

So, our code counts keystrokes even during the delay! Great. But what is volatile? We have declared a global variable counter, which will store the number of clicks on the button. If the value of the variable changes in the interrupt, you need to inform the microcontroller about this using a specifier volatile, which is written before indicating the data type of the variable, otherwise the work will be incorrect. This is just something to remember: if a variable changes in an interrupt, do it volatile.

A few more important points:

  • Variables modified in the interrupt must be declared as volatile
  • Delays like this do not work in interrupts delay()
  • Does not change its meaning in an interrupt millis() And micros()
  • In the interrupt, output to the port does not work correctly ( Serial.print()), also you shouldn't use it there - it loads the kernel
  • When interrupting, you should try to do as few calculations and generally “long” actions as possible - this will slow down the operation of the MC with frequent interruptions! What to do? Read below.

If the interrupt catches some event that does not need to be processed immediately, then it is better to use the following algorithm for working with the interrupt:

  • In the interrupt handler we simply raise the flag
  • In the main loop of the program we check the flag, if it is raised, we reset it and perform the necessary actions
volatile boolean intFlag = false; // flag void setup() ( Serial.begin(9600); // opened a port for communication // connected a button to D2 and GND pinMode(2, INPUT_PULLUP); // D2 is interrupt 0 // handler - function buttonTick // FALLING - when you press the button there will be a signal 0, we catch it attachInterrupt(0, buttonTick, FALLING); ) void buttonTick() ( intFlag = true; // raised the interrupt flag ) void loop() ( if (intFlag) ( intFlag = false; // reset // perform some actions Serial.println("Interrupt!"); ) )

This is basically all you need to know about interruptions; we will look at more specific cases in advanced lessons.

Video

During the course of a project, several interrupts may be required, but if each of them has maximum priority, then in fact none of the functions will have it. For the same reason, it is not recommended to use more than a dozen interrupts.

Handlers should only be applied to processes that have the highest sensitivity to time intervals. Do not forget that while the program is in the interrupt handler, all other interrupts are disabled. A large number of interruptions leads to a deterioration in their response.

At the moment when one interrupt is active and the others are disabled, two important nuances arise that the circuit designer must take into account. First, the interruption time should be as short as possible.

This will ensure that you don't miss any other scheduled interrupts. Secondly, when handling an interrupt, the program code should not require activity from other interrupts. If this is not prevented, the program will simply freeze.

Do not use long-term processing loop() , it is better to develop the code for the interrupt handler by setting the variable to volatile. It will tell the program that no further processing is needed.

If the function call Update() is still necessary, you will first need to check the state variable. This will allow you to determine whether further processing is necessary.

Before configuring the timer, you should check the code. Anduino timers should be classified as limited resources, because there are only three of them, and they are used to perform a variety of functions. If you get confused with the use of timers, then a number of operations may simply stop working.

What functions does this or that timer operate?

For the Arduino Uno microcontroller, each of the three timers has its own operations.

So Timer0 responsible for PWM on the fifth and sixth pins, functions millis() , micros() , delay() .

Another timer - Timer1, used with PWM on the ninth and tenth pins, with libraries WaveHC and Servo.

Timer2 works with PWM on pins 11 and 13, as well as Tone.

The circuit designer must take care to ensure the safe use of shared data. After all, an interrupt stops all processor operations for a millisecond, and data exchange between loop() and interrupt handlers must be persistent. A situation may arise when the compiler starts optimizing the code to achieve its maximum performance.

The result of this process will be to store a copy of the main code variables in the register, which will ensure maximum speed of access to them.

The downside of this process may be that real values ​​are replaced by stored copies, which can lead to loss of functionality.

To prevent this from happening, you need to use a variable voltatile , which will help prevent unnecessary optimizations. When using large arrays that require cycles for updates, you need to disable interrupts during these updates.

And here is an example of using the Arduino function attachInterrupt().

An interrupt is a signal that notifies the processor that an event has occurred that requires immediate attention. The processor must respond to this signal by interrupting the execution of current instructions and transferring control to the interrupt handler (ISR, Interrupt Service Routine). A handler is a regular function that we write ourselves and place there the code that should respond to the event.

After servicing the ISR interrupt, the function completes its work and the processor happily returns to its interrupted activities - it continues executing the code from where it left off. All this happens automatically, so our task is only to write an interrupt handler without breaking anything or causing the processor to be distracted by us too often. You will need an understanding of the circuit, the principles of operation of the connected devices and an idea of ​​how often an interruption can be triggered and what are the features of its occurrence. All this constitutes the main difficulty of working with interruptions.

Hardware and software interrupts

Interrupts in Arduino can be divided into several types:

  • Hardware interrupts. Interruption at the microprocessor architecture level. The event itself can occur at a productive moment from an external device - for example, pressing a button on the keyboard, moving a computer mouse, etc.
  • Software interrupts. They are launched inside the program using special instructions. Used to call the interrupt handler.
  • Internal (synchronous) interrupts. An internal interrupt occurs as a result of a change or violation in program execution (for example, when accessing an invalid address, an invalid operation code, etc.).

Why are hardware interrupts needed?

Hardware interrupts occur in response to an external event and originate from an external hardware device. Arduino provides 4 types of hardware interrupts. They all differ in the signal at the interrupt pin:

  • The contact is pulled to the ground. The interrupt handler is executed as long as there is a LOW signal on the interrupt pin.
  • Changing the signal on the contact. In this case, Arduino executes an interrupt handler when a signal change occurs on the interrupt pin.
  • Changing the signal from LOW to HIGH on a pin - when changing from a low signal to a high, the interrupt handler will be executed.
  • Changing the signal from HIGH to LOW on a pin - when changing from a high signal to a low signal, the interrupt handler will be executed.

Interrupts are useful in Arduino programs as they help solve timing problems. For example, when working with UART, interrupts allow you to avoid having to track the arrival of each character. An external hardware device issues an interrupt signal, the processor immediately calls an interrupt handler, which captures the character in time. This saves CPU time that would otherwise be spent checking the UART status without interrupts; instead, all necessary actions are performed by the interrupt handler without affecting the main program. No special capabilities are required from the hardware device.

The main reasons why an interrupt needs to be called are:

  • Detecting a change in output state;
  • Timer interrupt;
  • Data interrupts via SPI, I2C, USART;
  • Analog-to-digital conversion;
  • Willingness to use EEPROM, flash memory.

How interrupts are implemented in Arduino

When an interrupt signal is received, operation is suspended. The execution of the function that is declared to be executed when interrupted begins. A declared function cannot accept input values ​​and return values ​​when it exits. The interrupt does not affect the code itself in the main program loop. To work with interrupts in Arduino, a standard function is used attachInterrupt().

Differences in interrupt implementation in different Arduino boards

Depending on the hardware implementation of a particular microcontroller model, there are several interrupts. The Arduino Uno board has 2 interrupts on the second and third pins, but if more than two outputs are required, the board supports a special “pin-change” mode. This mode works by changing the input for all pins. The difference between the input change interrupt mode is that interrupts can be generated on any of the eight pins. In this case, processing will be more difficult and longer, since you will have to track the latest state on each of the contacts.

On other boards the number of interrupts is higher. For example, the board has 6 pins that can handle external interrupts. For all Arduino boards, when working with the attachInterrupt function (interrupt, function, mode), the Inerrupt 0 argument is associated with digital pin 2.

Interrupts in the Arduino language

Now let's get practical and talk about how to use interrupts in your projects.

Syntax attachInterrupt()

The attachInterrupt function is used to work with interrupts. It serves to connect an external interrupt to a handler.

Call syntax: attachInterrupt(interrupt, function, mode)

Function arguments:

  • interrupt – number of the interrupt to be called (standard 0 – for the 2nd pin, for the Arduino Uno board 1 – for the 3rd pin),
  • function – the name of the function to be called when interrupted (important - the function should neither accept nor return any values),
  • mode – condition for triggering the interrupt.

The following trigger conditions can be set:

  • LOW – performed when the signal level is low when the contact has a zero value. The interruption can be repeated cyclically - for example, when a button is pressed.
  • CHANGE – on the edge, interruption occurs when the signal changes from high to low or vice versa. Executes once for any signal change.
  • RISING – execute an interrupt once when the signal changes from LOW to HIGH.
  • FALLING – execute an interrupt once when the signal changes from HIGH to LOW.4

Important Notes

When working with interrupts, the following important limitations must be taken into account:

  • The handler function should not run for too long. The thing is that Arduino cannot handle several interrupts at the same time. While your handler function is running, all other interrupts will be ignored and you may miss important events. If you need to do something big, just transfer event processing to the main loop() loop. In the handler you can only set the event flag, and in the loop you can check the flag and process it.
  • You need to be very careful with variables. An intelligent C++ compiler can “re-optimize” your program - remove variables that, in its opinion, are unnecessary. The compiler simply will not see that you set some variables in one part and use them in another. To eliminate this possibility in the case of basic data types, you can use the volatile keyword, for example: volatile boolean state = 0. But this method will not work with complex data structures. So you have to always be on alert.
  • It is not recommended to use a large number of interrupts (try not to use more than 6-8). A large number of different events requires serious complication of the code, and, therefore, leads to errors. In addition, you need to understand that there can be no talk of any temporal accuracy of execution in systems with a large number of interruptions - you will never understand exactly what the interval is between calls of commands that are important to you.
  • It is strictly forbidden to use delay() in handlers. The mechanism for determining the delay interval uses timers, and they also operate on interrupts that your handler will block. As a result, everyone will wait for everyone and the program will freeze. For the same reason, interrupt-based communication protocols (for example, i2c) cannot be used.

Examples of using attachInterrupt

Let's get down to practice and look at a simple example of using interrupts. In the example, we define a handler function that, when the signal on pin 2 of the Arduino Uno changes, will switch the state of pin 13, to which we traditionally connect the LED.

#define PIN_LED 13 volatile boolean actionState = LOW; void setup() ( pinMode(PIN_LED, OUTPUT); // Set the interrupt // The myEventListener function will be called when // on pin 2 (interrupt 0 is connected to pin 2) // the signal changes (no matter in which direction) attachInterrupt (0, myEventListener, CHANGE); ) void loop() ( // We don't do anything in the loop function, since all event handling code will be in the myEventListener function) void myEventListener() ( actionState != actionState; // / / Perform other actions, for example, turn the LED on or off digitalWrite(PIN_LED, actionState); )

Let's look at some examples of more complex interrupts and their handlers: for timers and buttons.

Interrupts by pressing a button with anti-bounce

When interrupted, it occurs - before the contacts come into close contact when the button is pressed, they will oscillate, generating several operations. There are two ways to deal with bounce: hardware, that is, soldering a capacitor to the button, and software.

You can get rid of chatter using the function - it allows you to measure the time that has passed since the first operation of the button.

If(digitalRead(2)==HIGH) ( //when the button is pressed //If more than 100 milliseconds have passed since the previous press if (millis() - previousMillis >= 100) ( //The time of the first operation is remembered previousMillis = millis(); if (led==oldled) ( //checks that the button state has not changed led=!led; )

This code allows you to remove debounce and does not block program execution, as is the case with the delay function, which is not allowed in interrupts.

Timer interrupts

A timer is a counter that counts at a certain frequency, obtained from the processor 16 MHz. The frequency divider can be configured to obtain the desired counting mode. You can also configure the counter to generate interrupts when a set value is reached.

And a timer interrupt allows you to interrupt once every millisecond. Arduino has 3 timers - Timer0, Timer1 and Timer2. Timer0 is used to generate interrupts once every millisecond, which updates the counter and passes it to the millis() function. This timer is eight-bit and counts from 0 to 255. An interrupt is generated when the value reaches 255. By default, a clock divider of 65 is used to get a frequency close to 1 kHz.

Comparison registers are used to compare the state on the timer and the stored data. In this example, the code will generate an interrupt when the counter reaches 0xAF.

TIMSK0 |= _BV(OCIE0A);

You need to define an interrupt handler for a timer interrupt vector. An interrupt vector is a pointer to the location address of the command that will be executed when the interrupt is called. Multiple interrupt vectors are combined into an interrupt vector table. The timer in this case will be called TIMER0_COMPA_vect. This handler will perform the same actions as in loop().

SIGNAL(TIMER0_COMPA_vect) ( unsigned long currentMillis = millis(); sweeper1.Update(currentMillis); if(digitalRead(2) == HIGH) ( sweeper2.Update(currentMillis); led1.Update(currentMillis); ) led2.Update( currentMillis); led3.Update(currentMillis); ) //The loop() function will remain empty. void loop() ( )

Summarizing

Interruption in Arduino is a rather complex topic, because you have to think about the entire architecture of the project at once, imagine how the code is executed, what events are possible, what happens when the main code is interrupted. We did not set out to reveal all the features of working with this language construct; the main goal was to introduce the main use cases. In future articles we will continue the conversation about interruptions in more detail.

Generally speaking, Arduino does not support true task parallelization, or multithreading. But it is possible with each repetition of the cycle loop() instruct the microcontroller to check whether it is time to perform some additional, background task. In this case, it will seem to the user that several tasks are being performed simultaneously.

For example, let's flash an LED at a given frequency and, at the same time, make increasing and decreasing sounds like a siren from a piezo emitter. We have already connected both the LED and the piezo emitter to Arduino more than once. Let's assemble the circuit as shown in the figure.

If you connect the LED to a digital pin other than "13", don't forget about a current limiting resistor of about 220 ohms.

2 LED and piezo emitter control using the delay() operator

Let's write a sketch like this and upload it to Arduino.

Const int soundPin = 3; /* declare a variable with the number of the pin to which the piezoelectric element is connected */ const int ledPin = 13; // declare a variable with the LED pin number void setup() ( pinMode(soundPin, OUTPUT); // declare pin 3 as output. pinMode(ledPin, OUTPUT); // declare pin 13 as output. } void loop() (// Sound control: tone(soundPin, 700); // make a sound at a frequency of 700 Hz delay(200); tone(soundPin, 500); // at a frequency of 500 Hz delay(200); tone(soundPin, 300); // at a frequency of 300 Hz delay(200); tone(soundPin, 200); // at a frequency of 200 Hz delay(200); // LED control: digitalWrite(ledPin, HIGH); // light delay(200); digitalWrite(ledPin, LOW); // turn off delay(200); }

After turning it on, it is clear that the sketch is not performed exactly as we need: until the siren is fully operational, the LED will not blink, but we would like the LED to blink during the sound of a siren. What's the problem here?

The fact is that this problem cannot be solved in the usual way. Tasks are performed by the microcontroller strictly sequentially. Operator delay() delays the execution of the program for a specified period of time, and until this time expires, the following commands of the program will not be executed. Because of this, we cannot set a different execution duration for each task in the loop loop() programs. Therefore, you need to somehow simulate multitasking.

3 Parallel processes without the "delay()" operator

An option in which Arduino will perform tasks pseudo-parallel was proposed by Arduino developers. The essence of the method is that with each repetition of the cycle loop() we check whether it is time to blink the LED (perform a background task) or not. And if it has arrived, then we invert the state of the LED. This is a kind of bypass option for the operator delay().

Const int soundPin = 3; // variable with the pin number of the piezoelectric element const int ledPin = 13; // variable with the LED pin number const long ledInterval = 200; // LED blinking interval, ms. int ledState = LOW; // initial state of the LED unsigned long previousMillis = 0; // store the time of the previous LED activation void setup() ( pinMode(soundPin, OUTPUT); // set pin 3 as output. pinMode(ledPin, OUTPUT); // set pin 13 as output. } void loop() (// Sound control: tone(soundPin, 700); delay(200); tone(soundPin, 500); delay(200); tone(soundPin, 300); delay(200); tone(soundPin, 200); delay(200); // Flashing LED: // time since Arduino was turned on, ms: unsigned long currentMillis = millis(); // If the time has come to blink, if (currentMillis - previousMillis >= ledInterval) ( previousMillis = currentMillis; // then remember the current time if (ledState == LOW) ( // and invert the state of the LED ledState = HIGH; ) else ( ledState = LOW; ) digitalWrite(ledPin, ledState); // switch the state of the LED ) }

A significant disadvantage of this method is that the section of code before the LED control block must be executed faster than the LED blinking time interval "ledInterval". Otherwise, blinking will occur less often than necessary, and we will not get the effect of parallel execution of tasks. In particular, in our sketch, the duration of the siren sound change is 200+200+200+200 = 800 ms, and we set the LED blinking interval to 200 ms. But the LED will blink with a period of 800 ms, which is 4 times longer than what we set.

In general, if the code uses the operator delay(), in this case it is difficult to simulate pseudo-parallelism, so it is advisable to avoid it.

In this case, it would be necessary for the siren sound control unit to also check whether the time has come or not, and not use delay(). But this would increase the amount of code and make the program less readable.

4 Using the ArduinoThread Library to create parallel threads

To solve the problem, we will use a wonderful library ArduinoThread, which allows you to easily create pseudo-parallel processes. It works in a similar way, but allows you to avoid writing code to check the timing - whether you need to perform a task in this loop or not. This reduces the amount of code and improves the readability of the sketch. Let's check the library in action.


First of all, download the library archive from the official website and unzip it into a directory libraries/ Arduino IDE development environment. Then rename the folder ArduinoThread-master V ArduinoThread.

The connection diagram will remain the same. Only the program code will change.

#include // connecting the ArduinoThread library const int soundPin = 3; // variable with the pin number of the piezoelectric element const int ledPin = 13; // variable with the LED pin number Thread ledThread = Thread(); // create an LED control thread Thread soundThread = Thread(); // create a siren control thread void setup() ( pinMode(soundPin, OUTPUT); // declare pin 3 as output. pinMode(ledPin, OUTPUT); // declare pin 13 as output. ledThread.onRun(ledBlink); // assign a task to the thread ledThread.setInterval(1000); // set the response interval, ms soundThread.onRun(sound); // assign a task to the thread soundThread.setInterval(20); // set the response interval, ms } void loop() (// Check if it's time for the LED to switch: if (ledThread.shouldRun()) ledThread.run(); // start the thread // Check if it's time to change the siren tone: if (soundThread.shouldRun()) soundThread.run(); // start the thread } // LED stream: void ledBlink() ( static bool ledStatus = false; // LED status On/Off ledStatus = !ledStatus; // invert the state digitalWrite(ledPin, ledStatus); // turn on/off the LED } // Siren stream: void sound() ( static int tone = 100; // sound pitch, Hz tone(soundPin, ton); // turn on the siren at "ton" Hz if (ton )

In the program we create two threads - ledThread And soundThread, each performs its own operation: one blinks the LED, the second controls the sound of the siren. In each iteration of the loop, for each thread we check whether the time has come for its execution or not. If it arrives, it is launched for execution using the method run(). The main thing is not to use the operator delay(). The code provides more detailed explanations.


Let's load the code into the Arduino memory and run it. Now everything works exactly as it should!

Generally speaking, Arduino does not support true task parallelization, or multithreading. But it is possible with each repetition of the cycle loop() instruct the microcontroller to check whether it is time to perform some additional, background task. In this case, it will seem to the user that several tasks are being performed simultaneously.

For example, let's flash an LED at a given frequency and, at the same time, make increasing and decreasing sounds like a siren from a piezo emitter. We have already connected both the LED and the piezo emitter to Arduino more than once. Let's assemble the circuit as shown in the figure.

If you connect the LED to a digital pin other than "13", don't forget about a current limiting resistor of about 220 ohms.

2 LED and piezo emitter control using the delay() operator

Let's write a sketch like this and upload it to Arduino.

Const int soundPin = 3; /* declare a variable with the number of the pin to which the piezoelectric element is connected */ const int ledPin = 13; // declare a variable with the LED pin number void setup() ( pinMode(soundPin, OUTPUT); // declare pin 3 as output. pinMode(ledPin, OUTPUT); // declare pin 13 as output. } void loop() (// Sound control: tone(soundPin, 700); // make a sound at a frequency of 700 Hz delay(200); tone(soundPin, 500); // at a frequency of 500 Hz delay(200); tone(soundPin, 300); // at a frequency of 300 Hz delay(200); tone(soundPin, 200); // at a frequency of 200 Hz delay(200); // LED control: digitalWrite(ledPin, HIGH); // light delay(200); digitalWrite(ledPin, LOW); // turn off delay(200); }

After turning it on, it is clear that the sketch is not performed exactly as we need: until the siren is fully operational, the LED will not blink, but we would like the LED to blink during the sound of a siren. What's the problem here?

The fact is that this problem cannot be solved in the usual way. Tasks are performed by the microcontroller strictly sequentially. Operator delay() delays the execution of the program for a specified period of time, and until this time expires, the following commands of the program will not be executed. Because of this, we cannot set a different execution duration for each task in the loop loop() programs. Therefore, you need to somehow simulate multitasking.

3 Parallel processes without the "delay()" operator

An option in which Arduino will perform tasks pseudo-parallel was proposed by Arduino developers. The essence of the method is that with each repetition of the cycle loop() we check whether it is time to blink the LED (perform a background task) or not. And if it has arrived, then we invert the state of the LED. This is a kind of bypass option for the operator delay().

Const int soundPin = 3; // variable with the pin number of the piezoelectric element const int ledPin = 13; // variable with the LED pin number const long ledInterval = 200; // LED blinking interval, ms. int ledState = LOW; // initial state of the LED unsigned long previousMillis = 0; // store the time of the previous LED activation void setup() ( pinMode(soundPin, OUTPUT); // set pin 3 as output. pinMode(ledPin, OUTPUT); // set pin 13 as output. } void loop() (// Sound control: tone(soundPin, 700); delay(200); tone(soundPin, 500); delay(200); tone(soundPin, 300); delay(200); tone(soundPin, 200); delay(200); // Flashing LED: // time since Arduino was turned on, ms: unsigned long currentMillis = millis(); // If the time has come to blink, if (currentMillis - previousMillis >= ledInterval) ( previousMillis = currentMillis; // then remember the current time if (ledState == LOW) ( // and invert the state of the LED ledState = HIGH; ) else ( ledState = LOW; ) digitalWrite(ledPin, ledState); // switch the state of the LED ) }

A significant disadvantage of this method is that the section of code before the LED control block must be executed faster than the LED blinking time interval "ledInterval". Otherwise, blinking will occur less often than necessary, and we will not get the effect of parallel execution of tasks. In particular, in our sketch, the duration of the siren sound change is 200+200+200+200 = 800 ms, and we set the LED blinking interval to 200 ms. But the LED will blink with a period of 800 ms, which is 4 times longer than what we set.

In general, if the code uses the operator delay(), in this case it is difficult to simulate pseudo-parallelism, so it is advisable to avoid it.

In this case, it would be necessary for the siren sound control unit to also check whether the time has come or not, and not use delay(). But this would increase the amount of code and make the program less readable.

4 Using the ArduinoThread Library to create parallel threads

To solve the problem, we will use a wonderful library ArduinoThread, which allows you to easily create pseudo-parallel processes. It works in a similar way, but allows you to avoid writing code to check the timing - whether you need to perform a task in this loop or not. This reduces the amount of code and improves the readability of the sketch. Let's check the library in action.


First of all, download the library archive from the official website and unzip it into a directory libraries/ Arduino IDE development environment. Then rename the folder ArduinoThread-master V ArduinoThread.

The connection diagram will remain the same. Only the program code will change.

#include // connecting the ArduinoThread library const int soundPin = 3; // variable with the pin number of the piezoelectric element const int ledPin = 13; // variable with the LED pin number Thread ledThread = Thread(); // create an LED control thread Thread soundThread = Thread(); // create a siren control thread void setup() ( pinMode(soundPin, OUTPUT); // declare pin 3 as output. pinMode(ledPin, OUTPUT); // declare pin 13 as output. ledThread.onRun(ledBlink); // assign a task to the thread ledThread.setInterval(1000); // set the response interval, ms soundThread.onRun(sound); // assign a task to the thread soundThread.setInterval(20); // set the response interval, ms } void loop() (// Check if it's time for the LED to switch: if (ledThread.shouldRun()) ledThread.run(); // start the thread // Check if it's time to change the siren tone: if (soundThread.shouldRun()) soundThread.run(); // start the thread } // LED stream: void ledBlink() ( static bool ledStatus = false; // LED status On/Off ledStatus = !ledStatus; // invert the state digitalWrite(ledPin, ledStatus); // turn on/off the LED } // Siren stream: void sound() ( static int tone = 100; // sound pitch, Hz tone(soundPin, ton); // turn on the siren at "ton" Hz if (ton )

In the program we create two threads - ledThread And soundThread, each performs its own operation: one blinks the LED, the second controls the sound of the siren. In each iteration of the loop, for each thread we check whether the time has come for its execution or not. If it arrives, it is launched for execution using the method run(). The main thing is not to use the operator delay(). The code provides more detailed explanations.


Let's load the code into the Arduino memory and run it. Now everything works exactly as it should!







2024 gtavrl.ru.