What development environments (IDE) are there for Arduino?

For this lesson in the “Arduino for Beginners” course, we have already learned what a microcontroller is, studied Arduino boards and the main Arduino Uno board. We also learned the concept of breadboards and how to work with them.

Separately, we talked about programming languages ​​that are used in robotics, which finally allowed us to get closer to one of the main topics on working with boards, processors and microcontrollers. Next we will talk a lot about how to program boards so that they can control sensors, collect data and do many interesting things.

As we already know, in order for us to interact with peripheral devices we need to write a specific program and upload it to the Arduino board (or any other). For such actions, a very popular tool is used, which is called Arduino IDE (English - Integrated development environment) - Arduino integrated development environment. We have a separate large material about Arduino IDE. The biggest plus of this IDE is that it is absolutely free.

Afterword to basic lessons

This is the end of the basic course of Arduino programming lessons. You and I have studied the most basic concepts, remembered (or studied) part of the school curriculum in computer science, studied most of the syntax and tools of the C++ language, and seemingly the entire set of Arduino functions that the platform offers us. Let me emphasize that we studied C++ and Arduino functions, because there is no “Arduino language”, this is a false concept. Arduino is programmed in C or assembler, and the platform provides us with only a few dozen convenient functions for working with the microcontroller, namely functions, not a language. Now we have a blank sheet of Arduino IDE notepad and a desire to create and program, let's try it!

Arduino firmware

In order to upload a sketch to Arduino, we first need to simply save it. Next, to avoid problems when loading, you need to check the programmer settings. To do this, select the “Tools” tab on the top panel. In the “Payment” section, select your payment. It could be Arduino Uno, Arduino Nano, Arduino Mega, Arduino Leonardo or others. Also in the “Port” section you need to select your connection port (the port to which you connected your platform). After these steps, you can upload the sketch. To do this, click on the arrow or select “Download” in the “Sketch” tab (you can also use the keyboard shortcut “Ctrl + U”). The board firmware has been completed successfully.

Program structure

Before moving on to the actual tasks, we need to talk about some fundamental things. A microcontroller, as we discussed at the very beginning of the journey, is a complex device consisting of a computing core, permanent and RAM memory and various peripheral devices (timers/counters, ADCs, etc.). It is the microcontroller core that processes our code; it issues commands to the rest of the hardware, which can then work independently. The core executes various commands, driven by a clock generator: most Arduino boards have a clock generator with a frequency of 16 MHz. Each push of the clock generator causes the computing core to execute the next instruction, so the Arduino performs 16 million operations per second . Is this too much? More than enough for most tasks, the main thing is to use this speed wisely. Why am I talking about this: a microcontroller can only perform one task at a time, since it has only one computing core, so there is no real “multitasking” and cannot be, but due to the high execution speed, the core can perform tasks in turn, and for a person this will seem like multitasking, because what is “times Mississippi” for us, for a microcontroller – 16 million actions! There are only two options for organizing the code:

  • The main paradigm for working with a microcontroller is the so-called superloop , that is, the main loop of the program, which runs from top to bottom (if you look at the code) and starts from the very beginning when it reaches the end, and so on. In the Arduino IDE, our superloop is loop(). In the main loop, we can poll sensors, control external devices, display data on displays, perform calculations and all that, but in any case, these actions will occur one after another, sequentially . This is the basic mechanism of task parallelism: in fact, they all execute sequentially after each other, but at the same time fast enough to appear “parallel”.
  • In addition to the main loop, we have interrupts , which allow us to implement a certain “threading” of task execution, especially in situations where speed of work is important. An interruption allows you to stop the execution of the main loop at any point, get distracted by executing a certain block of code, and after its successful completion, return to the main loop and continue working. Some problems can be solved only using interrupts, without writing a single line in the loop() loop! We have studied hardware interrupts that allow you to interrupt when contacts are closed. Such interruptions are external, that is, they are provoked by external factors (a person pressed a button, a sensor was triggered, etc.). The microcontroller also has internal interrupts that are caused by the microcontroller’s peripherals, and there can be more than a dozen of these interrupts! One of these interrupts is a timer interrupt: according to the configured period, the program will interrupt and execute the specified code. We will talk about this below, and there is also a separate lesson on working with timer interrupts on the site. This approach is good for tasks that must be performed frequently and with great frequency; for everything else, you can set up one timer to count the time and work with this time.
  • By default, the Arduino IDE configures one of the timers (zero) to count in real time, thanks to which functions such as millis() and micros() work. It is these functions that are a ready-made tool for time management of our code and allow us to create work according to a schedule. The most important and critical point: tasks should not slow down the execution of the program for a longer period than the period of the shortest task, otherwise all tasks will be executed with the period of the longest! This is why you need to give up delays and waits: a delay can always be replaced by checking the timer during the next iterations of the loop, and the same with waiting for something, for example, a sensor response. Tasks should be asynchronous as possible and not block code; unfortunately, not all libraries have non-blocking analogues of functions. Even the native blocking analogRead() can be made non-blocking, but Arduino decided not to make life difficult for beginners.

“Multitasking” with yield()

In the lesson about time functions, we touched on the yield() function, which allows you to execute your code inside delay() delays. This crutch allows you to very quickly implement the “parallel” execution of two tasks: one on a timer, and the second constantly. In that lesson, we looked at an example in which an LED flashes and a button is polled:

void setup() { pinMode(13, OUTPUT); } void loop() { digitalWrite(13, 1); delay(1000); digitalWrite(13, 0); delay(1000); } void yield() { // and here you can poll the button // and not miss clicks due to delay! }

In the same way, you can poll an encoder or other sensors that require the most frequent polling. No less realistic would be the example with the scenario of the movement of a stepper motor or the smooth movement of a servo drive, which require calling polling functions as often as possible. Let's consider an abstract example of a motor moving along several given points; the motor rotation function should be called as often as possible (this is done in almost all libraries for stepper motors):

void setup() { } void loop() { // set target angle No. 1 delay(1000); // set target angle No. 2 delay(120); // set target angle No. 3 delay(2000); // set target angle No. 4 delay(250); // set target angle No. 5 delay(600); } void yield() { // rotate the motor }

Thus, we quickly and simply described the “trajectory” of movement for the stepper motor in time, without using any timers or timer libraries. For more complex programs, for example with the movement of two motors, this trick may no longer work and it is easier to work with a timer.

“Multitasking” with millis()

Most examples for various modules/sensors use the delay() delay as a “braking” of the program, for example, to output data from a sensor to a serial port. It is precisely such examples that spoil the perception of a beginner, and he also begins to use delays. But delays won't get you far! Let's remember the timer construction using millis() from the lesson on time functions: we have a variable that stores the time of the last “action” of the timer. We subtract this time from the current time, this difference is constantly increasing, and according to the condition, we can catch the moment when the time we need has passed. Let's learn how to get rid of delay()! Let's start with something simple: the classic blink :

void setup() { pinMode(13, OUTPUT); // pin 13 as output } void loop() { digitalWrite(13, HIGH); // enable delay(1000); // wait digitalWrite(13, LOW); // turn off delay(1000); // wait }

The program stops completely at the delay() command, waits for the specified time, and then continues execution. Why is this bad? (Are you still asking?) During this stop, we cannot do anything in the loop() loop, for example, we will not be able to poll the sensor 10 times per second: the delay will not allow the code to go further. You can use interrupts (for example, a timer), but we will talk about them in advanced lessons. Now let's get rid of the delay in the simplest sketch.

First of all, let's make the following optimization: we'll cut the code in half and get rid of one delay using the flag:

boolean LEDflag = false; void setup() { pinMode(13, OUTPUT); } void loop() { digitalWrite(13, LEDflag); // on/off LEDflag = !LEDflag; // invert the flag delay(1000); // wait }

Tricky move, remember it! This algorithm allows you to switch the state on each call. Right now our code is still bogged down by the 1 second delay, let's get rid of it:

boolean LEDflag = false; uint32_t myTimer; // time variable void setup() { pinMode(13, OUTPUT); } void loop() { if (millis() - myTimer >= 1000) { myTimer = millis(); // reset the timer digitalWrite(13, LEDflag); // on/off LEDflag = !LEDflag; // invert the flag } }

What's happening here is that loop() is executing several hundred thousand times per second, as it should because we've removed the delay. Each iteration we check whether it is time to switch the LED, has a second passed? With the help of this design, the necessary multitasking is created, which is enough for 99% of all conceivable projects, because you can create a lot of such “timers”!

boolean LEDflag = false; // time variables uint32_t myTimer, myTimer1, myTimer2; uint32_t myTimer3; void setup() { pinMode(13, OUTPUT); Serial.begin(9600); } void loop() { // every second if (millis() - myTimer >= 1000) { myTimer = millis(); // reset the timer digitalWrite(13, LEDflag); // on/off LEDflag = !LEDflag; // invert the flag } // 3 times per second if (millis() - myTimer1 >= 333) { myTimer1 = millis(); // reset the timer Serial.println("timer 1"); } // every 2 seconds if (millis() - myTimer2 >= 2000) { myTimer2 = millis(); // reset the timer Serial.println("timer 2"); } // every 5 seconds if (millis() - myTimer3 >= 5000) { myTimer3 = millis(); // reset the timer Serial.println("timer 3"); } }

This code still flashes the LED once per second, but it also sends messages to the serial port at varying intervals. If you open it, you will see the following text:

timer 1 timer 1 timer 1 timer 1 timer 1 timer 1 timer 2 timer 1 timer 1 timer 1 timer 1 timer 1 timer 1 timer 2 timer 1

This means that we have 4 timers running smoothly with different response periods, working “in parallel”, providing us with multitasking: we can display data on the display once per second, and at the same time poll the sensor 10 times per second and average its readings. A good example for a first project! Be sure to return to the lesson on time functions, where we looked at several designs on the uptime timer!

“Multitasking” with timer interrupts

For time-critical tasks, you can use execution on a timer interrupt. What tasks might these be:

  • Dynamic display;
  • Generation of a specific signal/communication protocol;
  • Software PWM;
  • “Clocking” of stepper motors;
  • Any other example of execution after a strictly specified time or simply periodic execution over a strict period (several microseconds). Since this is an interrupt, the task will be processed in priority to the rest of the code in the superloop.

Setting the timer to the desired frequency and operating mode is an impossible task for a beginner, although it can be solved in 2-3 lines of code, so I suggest using libraries. To configure timer 1 and 2 interrupts, there are TimerOne and TimerTwo libraries. We made our own library, GyverTimers, which also contains timer 0 (for programming without using Arduino.h), as well as all the timers on Arduino MEGA, and there are as many as 6 of them. You can view the documentation and examples on the library page. Now let's look at a simple example in which data will be sent to the port “in parallel” to the running Blink. The example is divorced from reality, you can’t do this, but it is important for understanding the essence: the code in the interrupt will be executed in any case, it does not care about delays and dead loops in the main code.

#include "GyverTimers.h" void setup() { Serial.begin(9600); // Set the timer period to 333000 µs -> 0.333 s (3 times per second) Timer2.setPeriod(300000); Timer2.enableISR(); // start an interrupt on channel A of timer 2 pinMode(13, OUTPUT); // we will blink } void loop() { // “blink” digitalWrite(13, 1); delay(1000); digitalWrite(13, 0); delay(1000); } // Interrupt A of timer 2 ISR(TIMER2_A) { Serial.println("isr!"); }

Timer interrupts are a very powerful tool, but we don't have many timers and we should only use them when really necessary. 99% of problems can be solved without timer interrupts by writing an optimal main loop and using millis() correctly. Look at my greenhouse controller project: it does not use timer interrupts, but the system constantly performs a large number of sensor polls, calculations, controls hardware, displays and is controlled by an encoder.

Switching tasks

The most important tool for organizing the logic of a program is the so-called finite machine (English State Machine) - a value that has a previously known set of states. It sounds complicated, but in fact we are talking about the swith statement and a variable that is toggled by a button or by a timer. For example:

if (click on button 1) mode++; if (click on button 2) mode—; switch (mode) { case 0: // task 0 break; case 1: // task 1 break; case 2: // task 2 break; ……… }

The mode variable must be signed (int or int8_t) to avoid backflow when receiving a negative value!!!

In this way, the selection and execution of selected code sections is organized. Switching the mode variable should also be done not just as in the example above, there are options:

  • Limit the range of the mode variable by the minimum task code (usually 0) and maximum (number of tasks minus 1).
  • Switch from the last task to the first and vice versa, i.e. “loop” the change.

There are several ways to limit the zoom range. The methods are absolutely identical in essence, but can be written differently:

// limit mode to 10 // method 1 mode++; if (mode > 10) mode = 10; // method 2 mode = min(mode++, 10); // method 3 if (++mode > 10) mode = 10;

Similarly when decreasing:

// method 1 mode—; if (mode < 0) mode = 0; // method 2 mode = max(mode—, 0); // method 3 if (—mode < 0) mode = 0;

Switching from first to last and back is done in exactly the same way:

// switch mode in the range 0-10 (11 modes) // with transition through extreme values ​​// METHOD 1 // to increase mode++; if (mode > 10) mode = 0; // to decrease mode—; if (mode < 0) mode = 10; // METHOD 2 // to increase if (++mode > 10) mode = 0; // to decrease if (—mode < 0) mode = 10;

Let's look at several ready-made examples based on the GyverButton library:

Switching operating modes with a button. Permanent Execution

/* This code demonstrates switching operating modes using a button. For convenience, a library for processing button presses is used */ #define PIN 3 // button is connected here (PIN - BUTTON - GND) #define MODE_AM 5 // number of modes (from 0 to specified) #include “GyverButton.h” // my library for more convenient work with the button // can be downloaded here https://github.com/AlexGyver/GyverLibs GButton butt1(PIN); // create our “button” byte mode = 0; // mode variable void setup() { Serial.begin(9600); } void loop() { butt1.tick(); // mandatory processing function. Must be constantly polled if (butt1.isPress()) { // correct processing of pressing with debounce protection // increase the mode number variable. If the number of modes is exceeded, reset it to zero if (++mode >= MODE_AM) mode = 0; } // the switch spins in a loop and tasks are constantly called switch (mode) { case 0: task_0(); break; case 1: task_1(); break; case 2: task_2(); break; case 3: task_3(); break; case 4: task_4(); break; } } // our tasks, inside the functions, of course, there can be anything void task_0() { Serial.println("Task 0"); } void task_1() { Serial.println("Task 1"); } void task_2() { Serial.println("Task 2"); } void task_3() { Serial.println("Task 3"); } void task_4() { Serial.println("Task 4"); }

Switching operating modes with a button. One-time execution

/* This code demonstrates switching operating modes using a button. For convenience, a library for processing button presses is used. In this version of the example, the “modes” functions are called only once */ #define PIN 3 // the button is connected here (PIN - BUTTON - GND) #define MODE_AM 5 // number of modes (from 0 to specified) #include “GyverButton.h” // my library for more convenient work with the button // can be downloaded here https://github.com/AlexGyver/GyverLibs GButton butt1(PIN) ; // create our “button” byte mode = 0; // mode variable void setup() { Serial.begin(9600); } void loop() { butt1.tick(); // mandatory processing function. Must be constantly polled if (butt1.isPress()) { // correct processing of pressing with debounce protection // increase the mode number variable. If the number of modes is exceeded, reset it to zero if (++mode >= MODE_AM) mode = 0; // all switching ultimately comes down to the switch statement // switching and calling occurs only when pressed!!! Inside isPress switch (mode) { case 0: task_0(); break; case 1: task_1(); break; case 2: task_2(); break; case 3: task_3(); break; case 4: task_4(); break; } } } // our tasks, inside the functions, of course, there can be anything void task_0() { Serial.println("Task 0"); } void task_1() { Serial.println("Task 1"); } void task_2() { Serial.println("Task 2"); } void task_3() { Serial.println("Task 3"); } void task_4() { Serial.println("Task 4"); }

Flags

Logical variables, or flags , are a very important tool for organizing the logic of a program. The global flag can store the “state” of program components, and they will be known throughout the program, and can be changed throughout the program. A slightly exaggerated example:

boolean flag = false; void loop() { // if the button was clicked, raise the flag if (buttonClick()) flag = true; if (flag) { // some code } }

The state of the global flag can be read in any other functions and places in the program, so you can greatly simplify the code and get rid of unnecessary calls. Using a flag, you can organize a single execution of a block of code based on some event:

boolean flag = false; void loop() { // if the button was clicked, raise the flag if (buttonClick()) flag = true; if (flag) { flag = false; // will be executed once } }

The flag can also be inverted, which allows you to generate the sequence 10101010 to switch some two states:

boolean flag = false; void loop() { // let's say the condition is satisfied periodically by timer if (timerElapsed()) { flag = !flag; // invert the flag // for example, you need to pass two values ​​to the function, // alternating them according to the timer setSomeValue(flag ? 10 : 200); } }

Flags are a very powerful tool, don't forget about them!

Getting rid of cycles and delays

We discussed above how to blink an LED without delay. How to get rid of the cycle? It’s very simple - the loop is replaced with a counter and a condition. Let's say we have a for loop that prints the counter value:

for (int i = 0; i < 10; i++) { Serial.println(i); }

To get rid of the loop, we need to make our own counter variable, place the whole thing in another loop (for example, in loop) and independently increase the variable and check the condition:

int counter = 0; void loop() { Serial.println(counter); counter++; if (counter >= 10) counter = 0; }

That's all. But what if there was a delay in the cycle? Here's an example

for (int i = 0; i < 30; i++) { // for example, light the i-th LED delay(100); }

We need to get rid of both the loop and delay(). Let's introduce a millis() timer and work on it:

int counter = 0; // replacement i uint32_t timer = 0; // timer variable #define T_PERIOD 100 // switching period void loop() { if (millis() - timer >= T_PERIOD) { // timer for millis() timer = millis(); // reset // action with counter - our i-th LED for example counter++; // add a counter if (counter > 30) counter = 0; // loop the change } }

That's all! Instead of the loop variable i, we now have our own global counter, counter, which runs from 0 to 30 (in this example) with a period of 100 ms.

Smart home design

A smart home “for all occasions” does not exist. Therefore, its design begins with defining the tasks, selecting and placing the main Arduino node, and then the remaining elements. At the final stage, the functionality is connected and finalized using programming.

You can create many projects based on Arduino, and then combine them into a single system. Among these:

  1. Humidity control in the basement.
  2. Automatic switching on of convectors when the temperature in the house drops below the permissible level in two possible scenarios - in the presence and absence of a person in the room.
  3. Turning on the street lights at dusk.
  4. Send messages about changes in each detected state.

As an example, we can consider designing the automation of a one-story house with two rooms and a basement for storing vegetables. The complex includes seven zones: entrance hall, shower room, kitchen, porch, bedroom, dining room, basement.

When drawing up a step-by-step design plan, we consider the following:

  1. Porch. When the owner approaches the house at night, the lighting will turn on. You should also take into account the opposite - when leaving the house at night, you also need to turn on the lighting.
  2. Hallway. When motion is detected and at dusk, turn on the lights. In the dark, it is necessary to have a dim light on the light bulb.
  3. Basement on the street. When the owner approaches, in the dark, the lamp near the basement door should light up. When opening the door, the light inside comes on and turns off when a person leaves the building. When leaving, the lighting on the porch turns on, and as you move away from the basement, it turns off near the door. Humidity control is installed in the basement and when a critical temperature is reached, several fans are turned on to improve air circulation.
  4. Shower room. It has a boiler installed. If a person is present in the house, the boiler turns on the water heating. The automation turns off when the maximum heating temperature is reached. When you enter the toilet, the hood and light turn on.
  5. Kitchen. The main lighting is turned on manually. If the owner of the house is absent from the kitchen for a long time, the light turns off automatically. The hood turns on automatically while cooking.
  6. Dining room. Light control is similar to that in the kitchen. While present in the kitchen, it is possible to give a voice command to the smart speaker assistant so that it starts the music.
  7. Bedroom. The lighting is turned on manually. But there is an automatic shutdown if there is no person in the room for a long time. Additionally, you need to turn off the lighting when there is a clap.

It will be interesting➡ What is arduino nano: features and scope

Convectors are located throughout the house. It is necessary to automatically control the maintained temperature in the house in two modes: when a person is in the house and during his absence. In the first option, the temperature should fall no lower than 20 degrees and rise no higher than 22. In the second, the temperature of the house should fall no lower than 12 degrees.

The project is ready, all that remains is to implement it.

Before selecting components and modules for creating automation in a smart home, you should pay attention to both the advantages and disadvantages of the system.

Advantages of the Arduino smart home:

  1. Using components from other manufacturers with the Arduino controller.
  2. Creation of your own smart home programs, since the source code of the project is open.
  3. The programming language is simple, there are many manuals for it on the Internet, even a beginner can figure it out.
  4. A simple project can be done in one hour of practice using default libraries designed for: reading button signals, outputting information to LCD displays or seven-segment indicators, and so on.
  5. You can power it, send commands and messages, program, or transfer ready-made software solutions to Arduino using a USB cable.

Flaws:

  1. The Arduino IDE development environment is a program built in Java, which includes: a code editor, a compiler, and transferring firmware to the board. Compared to modern solutions for 2022, this is the worst development environment (in the form in which it is presented). Even when you move to another development environment, you will have to leave the IDE for firmware.
  2. Small amount of flash memory for creating programs.
  3. The bootloader needs to be flashed for each microcontroller shield to complete the project. Its size is 2 KB.
  4. An empty project takes up 466 bytes on the Arduino UNO and 666 bytes in the Mega board's ROM.
  5. Low processor frequency.

How to connect several sketches?

To combine several projects into one, you need to deal with all possible conflicts:

  • Are the projects built on the same board/platform? Yes fine!
  • No - you need to make sure that the “common” board can work with the hardware that is in the combined projects, and also has the necessary peripherals.
  • Do the projects have hardware connected to communication interfaces?
      No - great!
  • Yes, I2C - all hardware is connected to the I2C common board. Make sure that the device addresses do not match (extremely rare)!
  • Yes, SPI - all pins on the SPI bus are “common”, except for CS (Chip Select), this pin can be any digital one. You can read more here.
  • Yes, UART is a problem; only one device can be connected to a UART. You can hang one piece of hardware on the hardware UART, and the second on the SoftwareSerial. Or bother with multiplexers.
  • Are there pins involved in both projects?
      No - great!
  • Yes - figure out what function the pin performs in each of the projects and select a replacement, both in the hardware and in the program: If this is a regular digital input-output, you can replace it with any other
  • If this is an analog signal measurement, replace it with another analog pin
  • If this is PWM generation, connect it to another PWM pin accordingly and adjust the program
  • If this is an interruption, be careful
  • Are the same microcontroller peripheral blocks used? To do this, you need to study the glands and their libraries:
      No – GREAT!
  • Yes - the situation requires good experience with Arduino...
  • The same timer is used - you cannot simultaneously use PWM on the legs of the first timer and control servos using the Servo.h library
  • Sound generation is used using tone() - you cannot use PWM on the legs of the second timer
  • Using timer interrupts and generating PWM on the corresponding timer - a difficult situation
  • Etc., there can be an infinite number of situations...
  • You can make all the changes to the schemes and programs of the merged projects so that they do not conflict. Next, we proceed to assembling the general program :

    • We connect all libraries . Some libraries may conflict, such as Servo and Timer1, as discussed above.
    • We compare the names of global variables and definitions in the merged programs: they should not coincide. We change the matching ones by replacing them by code (Edit/Find) with others. Next, copy-paste all global variables and definitions into the general program
    • Combining the contents of the setup() block
    • Copy-paste all “user” functions into the general program
    • All we have left is loop(), and this is the most difficult task

    Previously, we had two (or more) separately running projects. Now our task as a programmer is to think through and program the work of these several projects in one, and here there are an infinite number of situations:

    • The main code (which is in loop()) from different projects should be executed in turn using a timer
    • A set of actions from different projects should be switched with a button or something else
    • A sensor from another project is added to one project - the data needs to be processed and its further movement programmed (display, sending, etc.)
    • All “projects” must run simultaneously on one Arduino
    • And so on

    In most cases, you can’t just take and combine the contents of loop() from different programs, I hope everyone understands this. Even a flasher and a beeper cannot be combined in this way if the code was originally written with delays or closed loops.

    Documenting projects

    Documenting your projects is a good idea. To do this, go to the Examples panel and select Fade sketch there.


    Open 'layout.png' and 'schematic.png' in it. These examples will clearly show you how to place the necessary explanatory drawings (with a project diagram, for example) into your projects. You can always add the documentation you need to your projects by clicking in the last tab of the code area and selecting 'Import File into Sketch' there.

    Example “Weather station”

    The strength of Arduino as a designer lies in the fact that for absolutely any piece of hardware you can find on Google a detailed description, library, connection diagram and example of work: a completely ready-made kit for integration into your project! Let's return to our weather clock and try to “assemble” such a project from example sketches, because that’s what examples are needed for! We will need:

    • Arduino NANO
    • Display. Let it be LCD1602 with an adapter to i2c
    • Real time module, let's take DS3231
    • Thermometer ds18b20

    Let’s start googling information on connections and examples for each piece of hardware:

    • Display (arduino lcd 1602 i2c) – lesson on the first link from Google
    • RTC clock (arduino ds3231) – lesson on the first link from Google
    • Thermometer (arduino ds18b20) – lesson from the first link from Google

    From lessons from Google we learn such important information as connection diagrams: the display and clock are connected to the i2c bus, and the ds18b20 sensor can be connected to any other pin. Our project diagram:


    Download the libraries for our modules and install them. The display library is given to us directly in the article: https://iarduino.ru/file/134.html; based on my experience, I recommend RTClib for the clock library (the one in the article is not very convenient). In the article about the temperature sensor, we were told about the DallasTemperature.h , but they didn’t give us a link. Well, let’s look for “DallasTemperature.h” ourselves and find it using the first link. It also requires the OneWire library; a link to it was given in the article about the thermometer. In total, we should have 4 libraries installed. Now our goal is to find working examples for each piece of hardware, make sure they work, and allocate for ourselves a minimum set of code to control the module , this can be difficult - there are errors in the articles and simply non-working code: these articles are most often copy-paste from people far from Topics. I took an example of working with a display from the article, but I had to look at the clock and thermometer in the library examples. Let's tidy up the examples a little, leaving only the functions we need for getting values ​​or output, I left everything I need in setup():

    Display

    #include #include LiquidCrystal_I2C lcd(0x27, 16, 2); // Set up the display // address can be 0x27 or 0x3f void setup() { lcd.init(); lcd.backlight(); // Turn on the display backlight // Set the cursor to the second line and the zero character. lcd.setCursor(0, 1); lcd.print("Hello!"); // write } void loop() { }

    Watch

    #include "RTClib.h" RTC_DS3231 rtc; void setup() { Serial.begin(9600); // check if the module is connected if (! rtc.begin()) { Serial.println("Couldn't find RTC"); while(1); } // setting the time equal to the compilation time // if the module had a power reset! if (rtc.lostPower()) { Serial.println("RTC lost power, lets set the time!"); rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); } // output time values ​​DateTime now = rtc.now(); Serial.print(now.year(), DEC); Serial.print('/'); Serial.print(now.month(), DEC); Serial.print('/'); Serial.print(now.day(), DEC); Serial.print(" "); Serial.print(now.hour(), DEC); Serial.print(':'); Serial.print(now.minute(), DEC); Serial.print(':'); Serial.print(now.second(), DEC); Serial.println(); } void loop() { }

    Thermometer

    #include #include #define ONE_WIRE_BUS 2 OneWire oneWire(ONE_WIRE_BUS); DallasTemperature sensors(&oneWire); void setup() { Serial.begin(9600); sensors.begin(); sensors.requestTemperatures(); // temperature request float tempC = sensors.getTempCByIndex(0); // get Serial.println(tempC); // output } void loop() { }

    So, the examples have been tested, all modules work correctly. Let's start putting everything together into one project! This block of lessons is basic, so we will write this project in the “sketch” style - we will put all the code in one file and pray that everything works and there are no name conflicts. At the end of the next block of lessons, in the lesson on creating large projects, we will return to this example and do it with a more serious approach, without global variables and breaking it down into independent subroutine files. First of all, we connect all the libraries, declared objects, data types and variables at the beginning of the sketch. For beauty and clarity, we sort: first the settings (define), then the connected libraries, and at the end the data:

    Start of the sketch

    // SETTINGS #define ONE_WIRE_BUS 2 // pin ds18b20 // LIBRARIES #include "RTClib.h" #include #include #include #include // OBJECTS AND VARIABLES // address can be 0x27 or 0x3f LiquidCrystal_I2C lcd(0x3f, 16, 2 ); // Set up the display RTC_DS3231 rtc; OneWire oneWire(ONE_WIRE_BUS); DallasTemperature sensors(&oneWire); void setup() { } void loop() { }

    Next, we transfer the initialization to setup():

    setup()

    void setup() { // display lcd.init(); lcd.backlight(); // Turn on the display backlight // thermometer sensors.begin(); // clock rtc.begin(); // setting the time equal to the compilation time if (rtc.lostPower()) { rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); } }

    Great! Now the most difficult part: you need to think through the logic of the program. Let's break it down into simple steps:

    • 1 time per second – output of the clock (HH:MM:SS) and the current value from the sensor
    • 2 times per second – blinking LED on the board
    • 5 times per second – temperature measurement and averaging

    This is what our loop() will look like:

    loop()

    void loop() { // 2 times per second if (millis() - myTimer1 >= 500) { myTimer1 = millis(); // reset timer toggleLED(); } // 5 times per second if (millis() - myTimer2 >= 200) { myTimer2 = millis(); // reset timer getTemp(); } // every second if (millis() - myTimer3 >= 1000) { myTimer3 = millis(); // reset timer redrawDisplay(); } }

    We will create and fill functions executed by timers

    toggleLED()

    void toggleLED() { digitalWrite(13, LEDflag); // on/off LEDflag = !LEDflag; // invert the flag }

    getTemp()

    void getTemp() { // sum the temperature into a common variable tempSum += sensors.getTempCByIndex(0); sensors.requestTemperatures(); // measurement counter tempCounter++; if (tempCounter >= 5) { // if greater than 5 tempCounter = 0; // reset temp = tempSum / 5; // arithmetic mean tempSum = 0; // reset } }

    redrawDisplay()

    void redrawDisplay() { // TIME DateTime now = rtc.now(); // get the time lcd.setCursor(0, 0); // cursor at 0,0 lcd.print(now.hour()); // clock lcd.print(':'); // the first zero is for beauty if (now.minute() < 10) lcd.print(0); lcd.print(now.minute()); lcd.print(':'); // the first zero is for beauty if (now.second() < 10) lcd.print(0); lcd.print(now.second()); // TEMP lcd.setCursor(11, 0); // cursor at 11.0 lcd.print("Temp:"); lcd.setCursor(11, 1); // cursor at 11.1 lcd.print(temp); // DATE lcd.setCursor(0, 1); // cursor at 0.1 // first zero for beauty if (now.day() < 10) lcd.print(0); lcd.print(now.day()); lcd.print('.'); // the first zero is for beauty if (now.month() < 10) lcd.print(0); lcd.print(now.month()); lcd.print('.'); lcd.print(now.year()); }

    For the functioning of timers and temperature calculation, we also needed global variables, we will write them before setup(): uint32_t myTimer1, myTimer2, myTimer3; boolean LEDflag = false; float tempSum = 0, temp; byte tempCounter;

    And in general our project is completed! While disassembling the thermometer, an interesting feature emerged: reading greatly slows down the code; the requestTemperatures() command waits for the sensor to respond and blocks code execution, which is why the clock does not have time to tick once per second. After digging through the examples, I found asynchronous polling of the sensor: the line sensors.setWaitForConversion(false); . Accordingly, here is the entire project code:

    meteoClock.ino

    // SETTINGS #define ONE_WIRE_BUS 2 // pin ds18b20 // LIBRARIES #include "RTClib.h" #include #include #include #include // OBJECTS AND VARIABLES // address can be 0x27 or 0x3f LiquidCrystal_I2C lcd(0x3f, 16, 2 ); // Set up the display RTC_DS3231 rtc; OneWire oneWire(ONE_WIRE_BUS); DallasTemperature sensors(&oneWire); uint32_t myTimer1, myTimer2, myTimer3; boolean LEDflag = false; float tempSum = 0, temp; byte tempCounter; void setup() { Serial.begin(9600); // for debugging pinMode(13, 1); // display lcd.init(); lcd.backlight(); // Turn on the display backlight // thermometer sensors.begin(); sensors.setWaitForConversion(false); // asynchronous data reception // clock rtc.begin(); // setting the time equal to the compilation time if (rtc.lostPower()) { rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); } } void loop() { // 2 times per second if (millis() - myTimer1 >= 500) { myTimer1 = millis(); // reset timer toggleLED(); } // 5 times per second if (millis() - myTimer2 >= 200) { myTimer2 = millis(); // reset timer getTemp(); } // every second if (millis() - myTimer3 >= 1000) { myTimer3 = millis(); // reset timer redrawDisplay(); } } void toggleLED() { digitalWrite(13, LEDflag); // on/off LEDflag = !LEDflag; // invert the flag } void getTemp() { // sum the temperature into a common variable tempSum += sensors.getTempCByIndex(0); sensors.requestTemperatures(); // measurement counter tempCounter++; if (tempCounter >= 5) { // if greater than 5 tempCounter = 0; // reset temp = tempSum / 5; // arithmetic mean tempSum = 0; // reset } } void redrawDisplay() { // TIME DateTime now = rtc.now(); // get the time lcd.setCursor(0, 0); // cursor at 0,0 lcd.print(now.hour()); // clock lcd.print(':'); // the first zero is for beauty if (now.minute() < 10) lcd.print(0); lcd.print(now.minute()); lcd.print(':'); // the first zero is for beauty if (now.second() < 10) lcd.print(0); lcd.print(now.second()); // TEMP lcd.setCursor(11, 0); // cursor at 11.0 lcd.print("Temp:"); lcd.setCursor(11, 1); // cursor at 11.1 lcd.print(temp); // DATE lcd.setCursor(0, 1); // cursor at 0.1 // first zero for beauty if (now.day() < 10) lcd.print(0); lcd.print(now.day()); lcd.print('.'); // the first zero is for beauty if (now.month() < 10) lcd.print(0); lcd.print(now.month()); lcd.print('.'); lcd.print(now.year()); }

    And this is what it looks like in life


    As you can see, there is nothing particularly complicated here: we took three examples “from Google”, checked them, shortened them, and combined them into a project. Yes, I also had to write my own code decently, but it couldn’t be any other way! The main thing in programming is practice and developing your own “moves” and algorithms, which are then written very quickly: I wrote the code for this lesson at typing speed, without much thinking or debugging, it’s actually all very simple! At the end of the next block of “Advanced Programming”, in the lesson about creating large projects, we will return to this example and make it more adult: we will wrap everything in classes and split it into files to make it easier to refine the project.

    Parameters for target board

    IDE Arduino

    Supports different boards with different chips (currently only
    AVR
    ), processor speeds and bootloaders. All these parameters are set in the “preferences.txt.” file. The following settings are available:

    .name: The name that will be displayed in the Tools > Board menu. .build.mcu: microcontroller on board (usually "atmega8" or "atmega168"). .f_cpu: The clock speed at which the microcontroller runs (typically "16000000L", and the ATmega168 runs at an internal clock of "8000000L"). .core: which subfolder from the “hardware/cores/” folder the sketches are linked to (usually from “arduino”).

    Also in the "preferences.txt"

    There is another useful setting:

    build.verbose: Whether or not debug messages will be printed when building the sketch (for example, "false"). Specifying "true" will print the full command line for each external command executed when building the sketch.

    Note: On Arduino 0004 and later, build.extension is not used - the main sketch file is always referred to as a file with the extension "*.cpp".

    Important Pages

    • GyverKIT kit - a large Arduino starter kit of my design, sold in Russia
    • Catalog of links to cheap Arduins, sensors, modules and other hardware from AliExpress from trusted sellers
    • A selection of libraries for Arduino, the most interesting and useful, official and not so
    • Complete documentation on the Arduino language, all built-in functions and macros, all available data types
    • A collection of useful algorithms for writing sketches: code structure, timers, filters, data parsing
    • Video lessons on Arduino programming from the “Arduino Engineer's Notes ” channel are some of the most detailed in RuNet
    • Support the author for his work on the lessons
    • Feedback - report an error in the lesson or suggest an addition to the text ( [email protected] )

    5 / 5 ( 6 votes)

    Arduino Standard Libraries

    It’s better to start getting acquainted with libraries from the official website, where you can find an impressive list of standard modules and links to official partner libraries.

    List of built-in libraries (they come with the Arduino IDE distribution):

    • EEPROM
    • Ethernet / Ethernet 2
    • Firmata
    • GSM
    • LiquidCrystal
    • SD
    • Servo
    • SPI
    • SoftwareSerial
    • stepper
    • TFT
    • WiFi
    • Wire

    Official page on the Arduino website

    Rating
    ( 2 ratings, average 4 out of 5 )
    Did you like the article? Share with friends:
    For any suggestions regarding the site: [email protected]
    Для любых предложений по сайту: [email protected]