For цикл в программировании ардуино

Код

 1 /*
 2   Условные конструкции – оператор While
 3 
 4  Этот пример демонстрирует использование оператора while().
 5 
 6  Пока нажата кнопка, в программе будет работать только функция калибровки.
 7  На основе данных от датчика, считанных за это время, будут определены новый максимум и минимум.
 8 
 9  Это вариация примера «Калибровка».
10 
11  Цепь:
12  * фоторезистор подключен к 0-ому аналоговому и 5-вольтовому контактам
13  * 10000-омовый резистор подключен к «земле» и 0-ому аналоговому контакту
14  * светодиод через 220-омовый резистор подключен к 9-ому цифровому контакту, а также к «земле»
15  * кнопка подключена ко 2-ому цифровому и 5-вольтовому контактам
16  * 10000-омовый резистор подключен ко 2-ому цифровому контакту и «земле»
17 
18  Создан 17 января 2009,
19  модифицирован 30 августа 2011 Томом Иго (Tom Igoe). 
20 
21  Этот пример не защищен авторским правом.
22 
23  http://arduino.cc/en/Tutorial/WhileLoop
24 
25  */
26 
27 
28 // это константы, т.е. значения, которые не изменятся:
29 const int sensorPin = A0;       // контакт, к которому подключен датчик 
30 const int ledPin = 9;           // контакт, к которому подключен светодиод 
31 const int indicatorLedPin = 13; // контакт, к которому подключен встроенный светодиод 
32 const int buttonPin = 2;        // контакт, к которому подключена кнопка 
33 
34 
35 // это переменные, т.е. значения, которые изменятся:
36 int sensorMin = 1023;  // минимальное значение от датчика
37 int sensorMax = ;     // максимальное значение от датчика
38 int sensorValue = ;         // значение, считанное от датчика
39 
40 
41 void setup() {
42   // задаем контакты светодиодов как выходные,
43   // а контакт кнопки – как входной
44   pinMode(indicatorLedPin, OUTPUT);
45   pinMode (ledPin, OUTPUT);
46   pinMode (buttonPin, INPUT);
47 }
48 
49 void loop() {
50   // пока нажата кнопка, берем данные для калибровки:
51   while (digitalRead(buttonPin) == HIGH) {
52     calibrate();
53   }
54   // даем сигнал о начале калибровочного периода:
55   digitalWrite(indicatorLedPin, LOW);  
56 
57   // считываем данные от датчика:
58   sensorValue = analogRead(sensorPin);
59 
60   // применяем калибровку к считанным данным:
61   sensorValue = map(sensorValue, sensorMin, sensorMax, , 255);
62 
63   // задаем лимит, если во время калибровки данные от датчика
64   // вышли за пределы нужного диапазона:
65   sensorValue = constrain(sensorValue, , 255);
66 
67   // меняем яркость светодиода на основе откалиброванных значений:
68   analogWrite(ledPin, sensorValue);
69 }
70 
71 void calibrate() {
72   // включаем светодиод, оповещая о том, что калибровка завершена:
73   digitalWrite(indicatorLedPin, HIGH);
74   // считываем данные от датчика:
75   sensorValue = analogRead(sensorPin);
76 
77   // записываем максимальное значение:
78   if (sensorValue > sensorMax) {
79     sensorMax = sensorValue;
80   }
81 
82   // записываем минимальное значение:
83   if (sensorValue < sensorMin) {
84     sensorMin = sensorValue;
85   }
86 }

Мигаем светодиодом без delay()

или всегда ли официальный примеру учат «хорошему».

Обычно это одна из первых проблем с которой сталкивается навичок в микроконтроллерх. Помигал диодом, запустил стандартный скетч blink(), но как только он него возникает желание что-бы контроллер «занимался чем-то еще» его ждет неприятный сюрприз — тут нет многопоточности. Как только он написали где-то что-то типа delay(1000) — обнаруживается что на это строчке «контроллер остановился» и ничего больше не делает (кнопки не опрашивает, датчики не читает, вторым диодом «помигать» не может).

Новичок лезет с этим вопросом на форум и тут же получает ушат поучений: «отказывайтесь от delay()», учитесь работать с millis() и в прочитайте, в конце концов базовые примеры. В частности Мигаем светодиодом без delay()

Приведу его код:

/* Blink without Delay
 2005
 by David A. Mellis
 modified 8 Feb 2010
 by Paul Stoffregen
 */

const int ledPin =  13;      // номер выхода, подключенного к светодиоду
// Variables will change:
int ledState = LOW;             // этой переменной устанавливаем состояние светодиода 
long previousMillis = 0;        // храним время последнего переключения светодиода

long interval = 1000;           // интервал между включение/выключением светодиода (1 секунда)

void setup() {
  // задаем режим выхода для порта, подключенного к светодиоду
  pinMode(ledPin, OUTPUT);      
}

void loop()
{
  // здесь будет код, который будет работать постоянно
  // и который не должен останавливаться на время между переключениями свето
  unsigned long currentMillis = millis();
 
  //проверяем не прошел ли нужный интервал, если прошел то
  if(currentMillis - previousMillis > interval) {
    // сохраняем время последнего переключения
    previousMillis = currentMillis;  

    // если светодиод не горит, то зажигаем, и наоборот
    if (ledState == LOW)
      ledState = HIGH;
    else
      ledState = LOW;

    // устанавливаем состояния выхода, чтобы включить или выключить светодиод
    digitalWrite(ledPin, ledState);
  }
}
  

В принципе отправка к этому примеру — вполне правильна. В нем действительно виден стандартный паттерн как нужно выполнять какое-то переодическое действие (или отложенное):

1. Сохраняем время в какую-то переменную
2. В loop() все время смотрим на разницу «текущие-время — сохраненное»
3. Когда эта разница превысила какое-то значение (нужный нам «интервал переодичности»)

4. Выполняем какое-то действие (меняем состояние диода, заново запоминаем «стартовое время и т.п.»)
С задачей «объяснить идею» — пример справляется. Но, с моей точки зрения, в нем есть несколько методологических ошибок. Написание скетчек в подобно стиле — рано или поздно приведет к проблемам.
Итак, что же тут не так?

1. Не экономно выбираем тип переменных

Переменная ledPin у нас объявлена как тип int. Зачем?  Разве номер пина может быть очень большим числом? Или может быть отрицательным числом? Зачем под его хранение выделять два байта, когда вполне хватит одного. Тип byte может хранить числа от 0 до 255. Для номера пина — этого вполне хватит.

  const byte ledPIN = 13; //  номер выхода, подключенного к светодиоду

Этого будет вполне достаточно.

2. А зачем нам переменная для малых чисел?

А зачем нам тут вообще переменая? (пусть и объявленная как const). Зачем тратить такты процессора на чтение переменной? И расходовать еще один байт?  Воспользуемся директивой препроцессора #define

#define LED_PIN  13 //  номер выхода, подключенного к светодиоду

Тогда еще на этапе компиляции компилятор просто подставить 13-ть везде где в коде используется LED_PIN и не будет выделять отдельных переменных.

3. Тип int опять выбрали как «первое что в голову пришло»?

И опять спотыкаемся на объявлении следующей же переменной. Почему ledState опять int? Кроме того что снова «два байта там где можно один использовать», так еще и «смысловая нагрузка» теряется. Что у нас хранится в переменной? Состояние светодиода. Включен/выключен. Горит/Не горит. Явно же напрашивается тип boolean. По крайней мере до тех пор, пока светодиод у нас может принимать два состояния.

Цикл for

Цикл for, в простонародии счётчик, в различных видах этот цикл есть и в других языках программирования, но на C++ он имеет очень гибкую настройку. При создании цикл принимает три “значения” (настройки): инициализация, условие и изменение. Цикл for обычно содержит переменную, которая изменяется на протяжении работы цикла, мы можем пользоваться её меняющимся значением в своих целях. Переменная является локальной для цикла, если она создаётся при инициализации.

  • Инициализация – здесь обычно присваивают начальное значение переменной цикла. Например: int i = 0;
  • Условие – здесь задаётся условие, при котором выполняется цикл. Как только условие нарушается, цикл завершает работу. Например: i < 100;
  • Изменение – здесь указывается изменение переменной цикла на каждой итерации. Например: i++;

Объединим три предыдущих пункта в пример:

for (int i = 0; i < 100; i++) { // тело цикла }

В теле цикла мы можем пользоваться значением переменной i, которая примет значения от 0 до 99 на протяжении работы цикла, после этого цикл завершается. Как это использовать? Вспомним предыдущий урок про массивы и рассмотрим следующий пример:

// создадим массив на 100 ячеек byte myArray;

for (int i = 0; i < 100; i++) { // заполним все ячейки значением 25 myArray = 25; }

Именно при помощи цикла for очень часто работают с массивами. Можно, например, сложить все элементы массива для поиска среднего арифметического:

// создадим массив данных byte myVals[] = {5, 60, 214, 36, 98, 156};

// переменная для хранения суммы // обязательно инициализируем в 0 int sum = 0;

for (int i = 0; i < 6; i++) { // суммируем весь массив в sum sum += myVals; }

// разделим sum на количество элементов // и получим среднее арифметическое! sum /= 6;

// получилось 94!

Что касается особенностей использования for в языке C++: любая его настройка является необязательной, то есть её можно не указывать для каких-то особенных алгоритмов. Например вы не хотите создавать переменную цикла, а использовать для этого другую имеющуюся переменную. Пожалуйста! Но не забывайте, что разделители настроек (точка с запятой) обязательно должны присутствовать на своих местах, даже если настроек нет!

// есть у нас какая-то переменная int index = 0;

for (; index < 60; index += 10) { // переменная index принимает значения // 0, 10, 20, 30, 40, 50 }

В цикле for можно сделать несколько счётчиков, несколько условий и несколько инкрементов, разделяя их при помощи оператора запятая, , смотрите пример:

// объявить i и j // прибавлять i+1 и j+2 for (byte i = 0, j = 0; i < 10; i++, j += 2) { // тут i меняется от 0 до 9 // и j меняется от 0 до 18 }

Также в цикле может вообще не быть настроек, и такой цикл можно считать вечным, замкнутым:

for (;;) { // выполняется вечно… }

Использование замкнутых циклов не очень приветствуется, но иногда является очень удобным способом поймать какое-то значение, или дать программе “зависнуть” при наступлении ошибки. Но, как мы знаем, нет ничего вечного, поэтому из цикла таки можно выйти при помощи оператора break.

Code Structure

Libraries 

In Arduino, much like other leading programming platforms,  there are built-in libraries that provide basic functionality. In addition, it’s possible to import other libraries and expand the Arduino board capabilities and features. These libraries are roughly divided into libraries that interact with a specific component or those that implement new functions.

To import a new library, you need to go to Sketch > Import Library

In addition, at the top of your .ino file, you need to use ‘#include’ to include external libraries. You can also create custom libraries to use in isolated sketches.

Pin Definitions

To use the Arduino pins, you need to define which pin is being used and its functionality. A convenient way to define the used pins is by using:

 ‘#define pinName pinNumber’.

The functionality is either input or output and is defined by using the pinMode () method in the setup section.

Variables

Whenever you’re using Arduino, you need to declare global variables and instances to be used later on. In a nutshell, a variable allows you to name and store a value to be used in the future. For example, you would store data acquired from a sensor in order to use it later. To declare a variable you simply define its type, name and initial value.

It’s worth mentioning that declaring global variables isn’t an absolute necessity. However, it’s advisable that you declare your variables to make it easy to utilize your values further down the line.

Instances

In software programming, a class is a collection of functions and variables that are kept together in one place. Each class has a special function known as a constructor, which is used to create an instance of the class. In order to use the functions of the class, we need to declare an instance for it.

Setup()

Every Arduino sketch must have a setup function. This function defines the initial state of the Arduino upon boot and runs only once.

Here we’ll define the following:

  1. Pin functionality using the pinMode function
  2. Initial state of pins
  3. Initialize classes
  4. Initialize variables
  5. Code logic

Loop()

The loop function is also a must for every Arduino sketch and executes once setup() is complete. It is the main function and as its name hints, it runs in a loop over and over again.  The loop describes the main logic of your circuit.

For example:

Note: The use of the term ‘void’ means that the function doesn’t return any values.

Оператор while

Цикл for идеально подходит там, где мы хотим выполнить подсчет. В ситуации, когда необходимо выполнить определенные действия в результате какого-то события, которое не обязательно является предсказуемым (например, мы ждем нажатия кнопки), то мы можем использовать оператор while, который выполняет блок оператора до тех пор, пока выполняется условие. Синтаксис оператора while выглядит следующим образом:

While(условие)
{
// блок инструкций для выполнения
}

Важно, чтобы проверка состояния происходила в начале цикла. Может случиться так, что инструкции внутри цикла while не исполняться никогда

Кроме того, возможно создание бесконечного цикла. Давайте посмотрим два примера:

Int x=2;
while(x>5)
{
Serial.print(x);
}
—————————————-
int y=5;
while(y>0)
{
Serial.print(y);
}

Первый блок операторов, расположенный внутри while не выполнится никогда. Переменная x имеет значение два и она не станет больше 5. Во втором примере мы имеем дело с бесконечным циклом. Переменная «y» имеет занчение 5, т. е. больше нуля. Внутри цикла не происходит никакого изменения переменной «y», поэтому цикл никогда не будет завершен.

Это распространенная ошибка, когда мы забываем об изменении параметра, вызывающего прекращение цикла. Ниже приведено два правильных примера применения цикла while:

Int x=0;
while(x

В первом примере мы позаботились об изменении значения переменной, которая проверяется в условии. В результате цикл когда-нибудь завершится. Во втором примере был преднамеренно создан бесконечный цикл. Этот цикл эквивалентен функции loop () в Arduino IDE. Кроме того, внутри цикла введена проверка условия, после выполнения которого цикл завершается командой break.

Using the for Loop

The following sketch demonstrates the use of the for loop.

void setup() {
  int i;
  
  Serial.begin(9600);
  
  for (i = 0; i < 10; i++) {
    Serial.print("i = ");
    Serial.println(i);
  }
}

void loop() {
}

Load the sketch to your Arduino to see how it runs. This video shows the sketch running.

https://youtube.com/watch?v=5a2im1PcVjc

Can’t see the video? View on YouTube →

How the for Loop Works

The image below shows the parts of the for loop.

Parts of a for Loop in an Arduino Sketch

for Loop Structure

A basic for loop is started as follows:

for () {
}

Three expressions are added between the opening and closing parentheses () that determine how many times the statements in the loop are run before exiting the loop. When the loop is exited, program execution continues below the loop – i.e. statements outside and below the loop are executed from top to bottom.

The body of the loop between the opening and closing braces {} contains statements that will run in the loop.

The expressions between the parentheses are called the initialize expression, test expression and increment expression.

A variable must be defined to use in the three loop expressions. In the example sketch an integer variable called i is used.

The three loop expressions must appear in the order: initialize, test and increment. They are separated by semicolons (;). The increment expression does not end with a semicolon.

Initialize Expression

The initialize expression is only run once at the time that the loop starts. It initializes our i variable to zero (0) in the example sketch.

Test Expression

The test expression determines when we break out of the loop. It is used to set the number of times that the statements in the body of the loop are run.

When the test expression evaluates to true, the statements in the loop body will be run. When the test expression evaluates to false the loop will not be run again, but will be exited.

The test expression is evaluated every time that execution starts at the top of the loop.

Increment Expression

The increment expression is used to change the value that the i variable holds. It is run every time that execution starts at the top of the loop.

The image below shows how program flow works in the for loop.

for Loop Program Flow

First Time Through the Loop

The first time through the loop, i is initialized to 0, the test expression tests whether i < 10 (0 < 10) which is true, so the statements in the loop will run.

Because the post increment operator is used with the variable, i will only be incremented at the end of the loop. The statements in the loop run and print the value of i as 0 because it has not yet been incremented.

We therefore have this:

i is initialized to 0
i contains 0
i < 10 evaluates to true or 1 because i is less than 10
The two statements in the loop run, print i as 0
At the end of the loop i is incremented so i == 1

Second Time Through the Loop

The second time through the loop, i now contains 1 as it was incremented at the bottom of the loop. The test expression now tests whether i < 10 (1 < 10) which is true, so the statements in the loop will run again. The i variable will only be incremented to 2 at the bottom of the loop, so 1 is printed to the serial monitor window.

We now have this:

i is not initialized again
i contains 1
i < 10 evaluates to true or 1 because i is less than 10
The two statements in the loop run, print i as 1
At the end of the loop i is incremented so i == 2

Last Time Through the Loop

Execution of the loop will continue and i will be incremented every time.

The last time through the loop, we have this:

i is not initialized again
i contains 9
i < 10 evaluates to true or 1 because i is less than 10
The two statements in the loop run, print i as 9
At the end of the loop i is incremented so i == 10

Execution starts at the top of the loop again, the evaluation expression is tested.

We now have this:

i is not initialized again
i contains 10
i < 10 evaluates to false or 0 because i is not less than 10 (it is equal to 10)
The statements in the loop are not run again
The loop is exited
The statement below the closing bracket of the loop will be run

Books that may interest you:

Alternative Way of Writing the for Loop

The sketch that follows shows that the variable used in the loop expressions can also be defined in the loop and does not have to be defined outside the loop as the previous sketch did.

void setup() {
  Serial.begin(9600);
  
  for (int i = 0; i < 10; i++) {
    Serial.print("i = ");
    Serial.println(i);
  }
}

void loop() {
}

This sketch works exactly the same as the previous sketch and outputs the numbers 0 to 9.

Arduino Code libraries

Library Structure

A library is a folder comprised of files with C++ (.cpp) code files and C++ (.h) header files.

The .h file describes the structure of the library and declares all its variables and functions.

The .cpp file holds the function implementation.

Importing Libraries

The first thing you need to do is find the library you want to use out of the many libraries available online. After downloading it to your computer, you just need to open Arduino IDE and click on Sketch > Include Library > Manage Libraries. You can then select the library that you want to import into the IDE. Once the process is complete the library will be available in the sketch menu.

In the code provided by circuito.io instead of adding external libraries like mentioned before, we provide them with the firmware folder. In this case, the IDE knows how to find them when using #include.

From Software to Hardware

There is a lot to be said of Arduino’s software capabilities, but it’s important to remember that the platform is comprised of both software and hardware. The two work in tandem to run a complex operating system.

Code →  Compile → Upload →  Run

At the core of Arduino, is the ability to compile and run the code.

After writing the code in the IDE you need to upload it to the Arduino. Clicking the Upload button (the right-facing arrow icon), will compile the code and upload it if it passed compilation. Once your upload is complete, the program will start running automatically.

You can also do this step by step:

  1. First, compile the code. To do this simply click the check icon (or click on sketch > Verify / Compile in the menu bar.

As you can see, the check icon is located in the top left underneath the “File” tag in the menu section.

Once you’ve done this, Arduino will start to compile. Once it’s finished, you’ll receive a completion message that looks like this:

As you can see, the green line at the bottom of the page tells you that you’re “done compiling”. If your code fails to run, you’ll be notified in the same section, and the problematic code will be highlighted for editing.

Once you’ve compiled your sketch, it’s time to upload it.

  1. Choose the serial port your Arduino is currently connected to. To do this click on Tools > Serial port in the menu to designate your chosen serial port (as shown earlier above). You can then upload the compiled sketch.
  2. To upload the sketch, click on the upload icon next to the tick. Alternatively you can go the the menu and click File> upload. Your Arduino LEDS will flicker once the data is being transferred.

Once complete, you’ll be greeted with a completion message that tells you Arduino has finished uploading.

Setting Up Your IDE

In order to connect an Arduino board to your computer you need a USB cable. When using the Arduino UNO, the USB transfers the data in the program directly to your board. The USB cable is used to power your arduino. You can also run your Arduino through an external power source.

Before you can upload the code, there are some settings that you need to configure.

Choose your board — You need to designate which Arduino board you’re going to be using. Do this by click Tools > Board > Your Board.

Choose your processor- there are certain boards (for example Arduino pro-mini) for which you need to specify which processor model you have. Under tools > processor > select the model you have.

Choose your port — to select the port to which your board is connected, go to tools > Port > COMX Arduino (This is Arduino’s serial port).

How to Install Non-Native Boards (e.g. NodeMCU)

Some board models are not pre-installed in the Arduino IDE, therefore you’ll need to install them before you can upload code.

 To install a non-native board such as NodeMCU, you need to:

  1. Click on tools > Boards > Boards Manager
  2. Search for the board you want to add in the search bar and click “install”.

Some boards cannot be found through the Board Manager. In this case, you’ll need to add them manually. In order to do this:

  1. Click on Files > Preferences
  2. In the Additional Boards Manager field, paste the URL of the installation package of your board. For instance, for nodeMCU, add the following URL: http://arduino.esp8266.com/stable/package_esp8266com_index.json
  3. Click OK
  4. Go to tools > Boards > Boards Manager
  5. Search for the board you want to add in the search bar and click “install”.

Once you’ve completed this step, you will see the installed boards in the boards’ list under tools.

Note: the process may differ slightly for different boards.

Оператор goto

Из идеологических соображений необходимо пропустить это описание… Оператор goto является командой, которую не следует использовать в обычном программировании. Он вызывает усложнения кода и является плохой привычкой в программировании. Настоятельно рекомендуем не использовать эту команду в своих программах. Из-за того, что goto есть в официальной документации на сайте arduino.cc приведем его краткое описание. Синтаксис команды goto:

….
goto metka; // перейдите на строку с надписью ‘metka’
…..
….
….
metka: // метка, с которой программа продолжит работу

Команда позволяет переход к обозначенной метке, т. е. к месту в программе.

Циклы с использованием операторов for
и while
являются одной из важнейших конструкций языка C++, лежащего в основе Ардуино. Они встречаются абсолютно в каждом скечте, даже если вы не подозреваете об этом. В этой статье мы познакомимся поближе с циклами, узнаем, в чем отличие for от while, как можно упростить написание программы с их помощью и каких ошибок следует избегать.

Если вы пока еще начинающий программист и хотите понять, что вообще такое цикл и зачем он нужен – посмотрите следующий раздел этой статьи с подробным описанием.

Оператор WHILE используется в C++ и Ардуино для организации повтора одних и тех команд произвольное количества раз. По сравнению с FOR цикл WHILE выглядит проще, он обычно используется там, где нам не нужен подсчет числа итераций в переменной, а просто требуется повторять код, пока что-то не изменится, не наступит какие-то событие.

Синтаксис WHILE

while() { }

В качестве условий может использоваться любая конструкция языка, возвращающая логическое значение. Условиями могут быть операции сравнения, функции, константы, переменные. Как и при любых других логических операциях в Ардуино любое значение, кроме нуля будет восприниматься как истина (true), ноль – ложь (false).

// Бесконечный цикл
while(true){
Serial.println(«Waiting…»);
}
// Цикл, выполняющийся до изменения значения функции checkSignal()
while(!checkSignal()){
Serial.println(«Waiting…»);
}

Обратите внимание, что оператор while может использоваться без выделения блока фигурными скобками, в этом случае повторяться будет первая команда, встреченная после цикла. Крайне не рекомендуется использовать while без фигурных скобок, т.к

в этом случае можно очень легко допустить ошибку. Пример:

While(true)
Serial.print(«Waiting for interruption»);
delay(1000);

В данном случае надпись будет выводиться в бесконечном цикле без пауз, потому что команда delay(1000) повторяться не будет. Вы можете потратить много времени, выявляя такие ошибки – гораздо проще использовать фигурную скобку.

Пример использования цикла while

Чаще всего while используется для ожидания какого-либо события. Например, готовности объекта Serial к работе.

Serial.begin(9600);
while (!Serial) {
; // Для некоторых плат ардуино требуется ждать, пока не освободится последовательный порт
}

Пример ожидания прихода символа от внешних устройств по UART:

While(Serial.available()){
int byteInput = Seria.read();
// Какие-то другие действия
}

В данном случае мы будем считывать значения до тех пор, пока Serial.available() будет возвращать не нулевое значение. Как только все данные в буфере закончатся, цикл остановится.