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

Arduino IDE позаимствовал с C/C++ большинство необходимых элементов управления. Их синтаксис идентичен с C. Ниже мы в двух словах опишем их синтаксис.

Оператор if

Оператор if позволяет выполнить определенный фрагмент программы в зависимости от результата проверки определенного условия. Если условие выполняется, то код программы будет выполнен, если же условие не выполняется, то код программы будет пропущен. Синтаксис команды if выглядит следующим образом:

If(условие) { инструкция1; инструкция2; }

Условием может быть любое сравнение переменной или значения, возвращаемое функцией. Основным критерием условия if является то, что ответ всегда должен быть или истина (true) или ложь (false). Примеры условий для оператора if:

If(a!=2) { } if(x<10) { } if(znak==’B’) { }

Внутри скобок, которые прописаны внутри условия, можно выполнить код.

Люди, которые приступают к изучению программирования, часто делают ошибку, приравнивая значение указанной переменной с помощью одного знака «=». Такая запись однозначно указывает на присвоение значения переменно, и, следовательно, условие всегда будет «true», то есть выполняться. Проверка того, что переменная равна определенному значению, всегда обозначается двойным знаком равно (==).

В качестве условия можно использовать состояние функции, например:

If(init()) { Serial.print(«ок»); }

Приведенный выше пример будет выполнен следующим образом: на первом этапе вызывается функция init(). Эта функция возвращает значение, которое будет интерпретировано как «true» или «false». В зависимости от результата сравнения будет отправлен текст «ок» или ничего не будет отправлено.

Оператор if…else

Расширенным оператором if является оператор if….else. Он обеспечивает выполнение одного фрагмента кода, когда условие выполняется (true), и выполнение второй фрагмент кода, если условие не выполняется (false). Синтаксис операторf if….else выглядит следующим образом:

If (условие) { // команда A } else { // команда B }

Команды «A» будут выполняться только в том случае, если условие выполнено, команда «B» будет выполняться, когда условие не выполнено. Одновременное выполнение команды «A» и «B» невозможно. Следующий пример показывает, как использовать синтаксис if…else:

If (init()) { Serial.print(«ок»); } else { Serial.print(«ошибка»); }

Подобным образом можно проверить правильность выполнения функции и информировать об этом пользователя.

Обычной практикой является отрицание условия. Это связано с тем, что функция, которая выполнена правильно возвращает значение 0, а функция, которая отработала неверно по какой-то причине, возвращает ненулевое значение.

Объяснение такого «усложнения жизни» — просто. Если функция выполнена правильно, то это единственная информация, которая нам нужна. В случае же ошибки, стоит иногда понять, что пошло не так, почему функция не выполнена правильно. И здесь на помощь приходят числа, отличающиеся от нуля, т. е. с помощью цифровых кодов мы можем определить тип ошибки. Например, 1 — проблема с чтением какого-то значения, 2 — нет места в памяти или на диске и т. д.

В последнем измененном примере показано, как вызвать функцию, которая возвращает ноль при правильном выполнении:

If (!init()) { Serial.print(«ок»); } else { Serial.print(«ошибка»); }

Оператор switch case

Оператор if позволяет проверить только одно условие. Иногда необходимо выполнить одно из действий в зависимости от возвращаемого или прочитанного значения. Для этого идеально подходит оператор множественного выбора switch. Ниже показан синтаксис команды switch:

Switch (var) { case 1: //инструкция для var=1 break; case 2: // инструкция для var=2 break; default: // инструкция по умолчанию (если var отличается от 1 и 2) }

В зависимости от значения переменной var выполняются инструкции в определенных блоках. Метка case означает начало блока для указанного значения. Например, case 1: означает, что данный блок будет выполнен для значения переменной var, равной один.

Каждый блок должен быть завершен с помощью команды break. Он прерывает дальнейшее выполнение оператора switch. Если команду break пропустить, то инструкции будут выполняться и в последующих блоках до команды break. Метка default не является обязательной, как и else в команде if. Инструкции, расположенные в блоке default выполняются только тогда, когда значение переменной var не подходит ни к одному шаблону.

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

Switch (x) { case 1: //инструкция для x=1 break; case 2: case 3: case 5: // инструкция для x=2 или 3 или 4 break; case 4: // инструкция для x=4 break; case 6: // инструкция для x=6 break; default: // инструкция по умолчанию (если х отличается от 1,2,3,4,5,6) }

В зависимости от значения переменной x будет выполнен соответствующий блок инструкций. Повторение case 2: case 3: case 5: информирует компилятор о том, что если переменная x имеет значение 2 или 3 или 5, то будет выполнен один и тот же фрагмент кода.

Оператор for

Оператор for используется для многократного выполнения одного и того же кода. Часто необходимо выполнить одни и те же инструкции, изменив только значение какой-то переменной. Для этого идеально подходит цикл for. Синтаксис команды выглядит следующим образом:

Int i; for(i=0;i<10;i++) { // инструкции для выполнения в цикле }

Первый параметр, приводимый в инструкции for — начальное значение переменной. Еще один элемент — это проверка условия о продолжении выполнения цикла. Цикл выполняется до тех пор, пока выполняется условие. Последний элемент — это изменение значения переменной. Чаще всего мы увеличиваем или уменьшаем ее значение (по необходимости). В этом примере, инструкции, содержащиеся в цикле будут выполняться при i=0…9.

Часто переменная, используемая в цикле объявляется там же:

For(int i=0;i<10;i++) { // инструкции для выполнения в цикле }

Переменная, которая используется для подсчета последующих шагов цикла, может использоваться внутри нее для вызова функции с соответствующими параметрами.

For(int i=10;i>0;i—) { Serial.print(i); // отправятся номера 10,9,8,7,6,5,4,3,2,1 }

Оператор 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<10) { //блок инструкций x++; } —————————————- while(true) { if(условие) break; // блок инструкций }

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

Оператор do…while

Разновидностью цикла while является цикл do…while. Кроме синтаксиса он отличается местом проверки условия. В случае do…while проверка условия производится после выполнения блока инструкций. Это означает, что блок инструкций в теле цикла выполнится хотя бы один раз. Ниже приведен синтаксис команды do…while:

Do { // блок инструкций } while(условие)

Все, что написано об операторе while относится также и к do…while. Ниже приведен пример использования цикла do…while:

Int x=10; do { // блок инструкций x—; } while(x>0); —————————————- do { // блок инструкций if(условие) break; } while(true);

Оператор break

Оператор break позволяет выйти из цикла (do…while, for, while) и выйти из опции switch. В следующем примере рассмотрим выполнение команды break:

For(i=0;i<10;i++) { if(i==5) break; Serial.print(i); }

Цикл должен быть исполнен для чисел от 0 до 9, но для числа 5 выполняется условие, которое запускает оператор break. Это приведет к выходу из цикла. В результате в последовательный порт (Serial.print) будет отправлены только числа 0,1,2,3,4.

Оператор continue

Оператор continue вызывает прекращение выполнения инструкций цикла (do…while, for, while) для текущего значения и переход к выполнению следующего шага цикла. В следующем примере показано, как работает оператор continue:

For(i=0;i<10;i++) { if(i==5) continue; Serial.print(i); }

Как не трудно заметить, цикл будет выполнен для значения от 0 до 9. Для значения 5 исполнится команда continue, в результате чего инструкции, находящиеся после этой команды выполнены не будут. В результате в последовательный порт (Serial.print) будут отправлены числа 0,1,2,3,4,6,7,8,9.

Оператор return

Оператор return завершает выполнение вызываемой функции и возвращает значение определенного типа. В качестве параметра команды можно указать число, символ или переменную определенного типа. Важно, чтобы возвращаемое значение соответствует типу заявленной функции. В следующем примере показано, как использовать оператор return:

Int checkSensor(){ if (analogRead(0) > 400) { // чтение аналогового входа return 1; // Для значений больше 400 возвращается 1 else{ return 0; // для других возвращается 0 } }

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

Void имя_функции() { инструкция1; if(x==0) return; инструкция2; инструкция3; }

В приведенном выше примере инструкция1 будет выполнять всегда, когда вызывается функция. Выполнение же инструкция2 и инструкция3 зависит от результата команды if. Если условие будет выполнено (true), то будет выполнена команда return и функция завершит работу.

В случае, когда условие не выполнено команда return так же не выполняется, а выполняются инструкции инструкция2 и инструкция3, и после этого функция завершает свою работу.

Оператор goto

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

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

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

Description

The for statement is used to repeat a block of statements enclosed in curly braces. An increment counter is usually used to increment and terminate the loop. The for statement is useful for any repetitive operation, and is often used in combination with arrays to operate on collections of data/pins.

Syntax

For (initialization; condition; increment) { // statement(s); }

Parameters

initialization: happens first and exactly once.
condition: each time through the loop, condition is tested; if it’s true , the statement block, and the increment is executed, then the condition is tested again. When the condition becomes false , the loop ends.
increment: executed each time through the loop when contition is true.

Example Code

// Dim an LED using a PWM pin int PWMpin = 10; // LED in series with 470 ohm resistor on pin 10 void setup() { // no setup needed } void loop() { for (int i = 0; i <= 255; i++) { analogWrite(PWMpin, i); delay(10); } }

Notes and Warnings

The C `for` loop is much more flexible than `for` loops found in some other computer languages, including BASIC. Any or all of the three header elements may be omitted, although the semicolons are required. Also the statements for initialization, condition, and increment can be any valid C statements with unrelated variables, and use any C++ datatypes including floats. These types of unusual for statements may provide solutions to some rare programming problems.

Сегодня будем изучать не менее важную часть языка программирования, как циклы. Зачем они нужны. Давайте например поставим себе цель. Нужно зажигать шесть светодиодов по очереди с периодом в 50 мс, а потом по очереди их гасить с тем же интервалом. Ну что может быть проще. Пишем следующий код:
void setup() { pinMode(2, OUTPUT); pinMode(3, OUTPUT); pinMode(4, OUTPUT); pinMode(5, OUTPUT); pinMode(6, OUTPUT); pinMode(7, OUTPUT); } void loop() { digitalWrite(2, HIGH); delay(50); digitalWrite(3, HIGH); delay(50); digitalWrite(4, HIGH); delay(50); digitalWrite(5, HIGH); delay(50); digitalWrite(6, HIGH); delay(50); digitalWrite(7, HIGH); delay(50); digitalWrite(2, LOW); delay(50); digitalWrite(3, LOW); delay(50); digitalWrite(4, LOW); delay(50); digitalWrite(5, LOW); delay(50); digitalWrite(6, LOW); delay(50); digitalWrite(7, LOW); delay(50); } Сначала мы про инициализировали шесть цифровых выводов со второго по седьмой как выходы, а в основной программе написали поочередно включение светодиода, задержка и так шесть раз. После тоже самое но каждый раз выключали светодиод. Теперь заливаем в Arduino и радуемся работой. Но все таки тут что-то не так. Если внимательно взглянуть на код программы, то можно заметить что есть части кода которые повторяются на протяжении всей программы. Например должно сразу бросится в глаза повторение паузы. А при инициализации выводов меняется только его номер. При включении и выключении тоже меняется только номер. Для такой маленькой программы конечно можно и так оставить, контроллер сожрет это и не поперхнется, а вот если нужно выполнить повторяющийся код, ну например 1000 раз. Я думаю терпения набивать его хватит, а вот хватит ли памяти у МК? Конечно можно спросить, а на кой фиг нам 1000 одинаковых операций? Ну да, тяжело представить.) Но вот не задача, а если у нас есть массив на 1000 ячеек. Такое часто бывает, например несколько датчиков записывают параметры в массив и как скажете разбираться в этом бардаке. Надо бы как-нибудь разобрать его по каким-либо параметрам. Вот для таких казусов и придумали циклы. Цикл - это некая часть кода которая выполняется определенное количество раз. Одна выполненная часть программы в цикле называется итерация. Количество итераций может быть от 0 до бесконечности. Для выполнения циклов в языке программирования предусмотрено аж три варианта цикла. Поверьте, но этого хватает за глаза на любой изощренный кодинг. Давай те ка это все рассмотрим по подробнее.
  • while(условие) {}
  • do {} while(условие);
  • for(счетная переменная; условие; увеличение счетной переменной) {}
Первый цикл while(условие) {} . Как он работает. После слова while в скобках должно быть условие. Условие может быть любы, лишь бы было истинным. Как только условие станет ложным, цикл прекратит свою работу и программа продолжит работать со следующей строки после цикла. Давайте на примере.
char i = 0; while(i Собственно что тут у нас написано. Сначала мы инициализируем счетную переменную i и обнуляем ее. Далее заходим в цикл и начинаем проверять условие в скобках. Если значение i меньше чем 10, то выполнить тело цикла. В самом теле цикла просто увеличиваем значение счетной переменной на единицу и снова проверяем условие. В нашем случае цикл будет выполнятся 10 раз. То есть сначала значение i равно нулю. Ноль меньше чем десять. Далее увеличили переменную на единицу и сравнили, единица меньше чем десять и так далее. Как только счетная переменная станет равна десяти, то проверяем, десять меньше чем десять? Конечно нет и после проверки цикл прекратит работу. Вот так работает этот цикл. А что делать если нужно по любому один раз выполнить код в теле цикла, даже если он не устраивает условие. Для этого есть друго цикл, под названием do {} while(условие) . Работает он точно также как и предыдущий цикл, за исключением одного но. В этом цикле сначало выполняется тело цикла, а затем происходит проверка. Давайте посмотрим как это выглядит в коде.
char i = 0; do { i++; } while((i > 0) & (i Смотрите как интересно. Сначала мы как и в прошлый раз инициализируем счетную переменную нулем, но в условии записали чтобы i было больше нуля и меньше десяти. То есть значение переменной должно лежать в диапазоне от единицы до девяти. Если бы мы так написали с применением предыдущего цикла, то он не разу бы не выполнился. Но у нас есть волшебное слово do . То есть что произойдет. Сначала в теле цикла значение счетной переменной увеличится и станет единицей, а это больше чем ноль, условие станет истинно. соответственно цикл будет продолжать выполнятся пока счетная переменная не станет равна десяти. И на по следок третий вариант цикла. Как он работает:
char i; for(i = 0; i Как это работает. Сначала опять инициируем счетную переменную, но уже без конкретного значения. Далее пишем слово for , а вот в скобках пишем сначала нашу счетную переменную и присваиваем ей начальное значение. Затем проверяем условие и если оно истинно, то выполняем тело цикла и увеличиваем значение счетной переменной. По сути это тоже самое что и while() {} поэтому какой цикл использовать это уже на ваше усмотрение. Пару слов о некоторых моментах. Если например написать while(1); , то цикл будет выполнятся вечно. Или если с for , то это будет выглядеть так for(;;); . Будте внимательны. Иногда при выполнении цикла ну просто очень хочется все бросить и выйти из него, а условие не позволяет. Как быть? Для этого есть еще одна команда break; . Как только в теле цикла МК наткнется на эту команду, он тут же выйдет из цикла и продолжит выполнение программы со следующей строки после цикла. А вот если у нас при работе цикла возникает условие не удовлетворяющие условие или к примеру момент при котором нам не нужно продолжать выполнять конец тела цикла? Тут нам поможет команда continue; . Как только МК наткнется на эту команду, он брасает все и переходит к выполнению следующей итерации цикла. Надеюсь я все понятно объяснил. Теперь получив данные знания, давайте перепишем нашу программу, но уже используя циклы.
void setup() { byte i = 2; // Счетная переменная while(i // Если i меньше 8, то выполняем тело цикла { pinMode(i, OUTPUT); // Инициализация выводов начиная с 2 i++; // Увеличиваем счетную переменную на единицу } } void loop() { byte i = 2; while(i Давайте рассмотрим по ближе. Сначала мы инициализировали счетную переменную i и присвоили ей значение два. Почему два? А потому что я специально выбрал пины со второго по седьмой, дабы убедится что начальное значение не имеет ни какого значения. Каламбур какой-то получился) Ну понятно, да. Далее пишем условие цикла. Нам нужно сделать шесть итераций, так как у нас шесть светодиодов. Замечательно, считаем два плюс шесть будет восемь. Ага, значит нам нужно проверять счетную переменную до тех пор пока она будет меньше восьми. Так и написали while(i . Теперь у нас цикл отработает шесть раз. Что нам нужно сделать внутри тела цикла. Да ничего сложного, просто вписать функцию инициализации вывода на выход, только вместо номера вывода подставить счетную переменную. В чем фокус. Как только МК зайдет в тело цикла, он перед тем как выполнять функцию инициализации вывода, посмотрим на передаваемые аргументы. Один из них должен нести в себе номер вывода, а у нас там счетная переменная. Что делать? А ничего, умный МК посмотрит что там переменная и гордо вытянет из нее число. А у нас там двойка. Ну и замечательно, про инициализируем второй вывод. После увеличим значение счетной переменной еще на единицу и проверим условие. Ага, три меньше восьми, давайте ка все снова и по хорошему, только в переменной теперь три. Значит инициализировать вывод будем уже третий, а затем увеличим счетную переменную на единицу. Вот таким образом перебирая цикл мы настроим все нужные нам выводы. Причем увеличение на единицу счетную переменную это не жесткое условие. Никто не мешает написать например так: i = ((127*i)/31) & 0xF4; И это тоже будет работать, если после выполнения условие будет истинно. Для цикла не важно что происходит в теле, его интересует истинно ли условие или нет. Вот и все. В следующем уроке будем разбирать функции, зачем они нужны и попробуем написать свою.

Доброе время суток. Перед вами, дорогие читатели, третий урок посвященный программированию Arduino.

  • познакомились с устройством монтажной платы;
  • собрали «в железе» и прошили нашу первую схему.

Сегодня мы соберём схему, в которой будет использовано два цифровых выхода платы Arduino Uno. Соответственно нам понадобится 2 светодиода разных цветов (для наглядности), 2 резистора, джамперы и всё та же монтажная плата (или то на чём вы раньше проводили монтаж). В одной из схем будем включать красный светодиод, а во второй – белый.

Преступаем к сборке «самоделки » согласно следующей схеме. Советую использовать в своих проектах программу «Fritzing». С её помощью можно планировать разводку схем до момента непосредственного монтажа.

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

Открываем программную среду arduino IDE.

Набираем программу.

Загружаем её – смотрим на результат. Красный и белый светодиоды мигают по очереди.

Примеры учебных задач всегда были кладезю «ограниченной фантазии» или примером, как не нужно составлять техническое задание…

Представим, что мы только что написали системы управления электрическим двигателем. Забудем о том, что подпрограмма может молотить вечно. Наш движок включился и выключился. Цикл вкл./выкл. запускается по нажатию «мифической кнопки».

Нам нужно, чтобы он прогнал цикл вкл./выкл. 10 раз. Думаю мало кому хочется стоять рядом с ревущей машиной и нажимать на какую-то кнопку… Поэтому модернизируем программу.

Копируем блок из 4 строчек (включение выключение белого светодиода) 9 раз – в итоге получаем 10 циклов вкл./выкл. Добавим еще «второй движок» — красный светодиод он будет вкл/выкл всего один раз.

Загружаем программу – смотрим на результат.

Все получилось, но вот в чём проблема, а если потребность в прогонке вкл/выкл возрастет до 100 или до 1000, что опять будем всё копировать? Как-то не хочется… Так стоп! Мы с вами изучали . Пора применить знания на практике. Для нашей задачи отлично подойдёт цикл с заданным числом повторений – цикл for. Для того, чтобы не нагружать программу глобальными переменными, объявим локальную переменную «счётчика выполнения цикла» в заголовке цикла.

Эта статья также доступна на следующих языках: Тайский

  • Next

    Огромное Вам СПАСИБО за очень полезную информацию в статье. Очень понятно все изложено. Чувствуется, что проделана большая работа по анализу работы магазина eBay

    • Спасибо вам и другим постоянным читателям моего блога. Без вас у меня не было бы достаточной мотивации, чтобы посвящать много времени ведению этого сайта. У меня мозги так устроены: люблю копнуть вглубь, систематизировать разрозненные данные, пробовать то, что раньше до меня никто не делал, либо не смотрел под таким углом зрения. Жаль, что только нашим соотечественникам из-за кризиса в России отнюдь не до шоппинга на eBay. Покупают на Алиэкспрессе из Китая, так как там в разы дешевле товары (часто в ущерб качеству). Но онлайн-аукционы eBay, Amazon, ETSY легко дадут китайцам фору по ассортименту брендовых вещей, винтажных вещей, ручной работы и разных этнических товаров.

      • Next

        В ваших статьях ценно именно ваше личное отношение и анализ темы. Вы этот блог не бросайте, я сюда часто заглядываю. Нас таких много должно быть. Мне на эл. почту пришло недавно предложение о том, что научат торговать на Амазоне и eBay. И я вспомнила про ваши подробные статьи об этих торг. площ. Перечитала все заново и сделала вывод, что курсы- это лохотрон. Сама на eBay еще ничего не покупала. Я не из России , а из Казахстана (г. Алматы). Но нам тоже лишних трат пока не надо. Желаю вам удачи и берегите себя в азиатских краях.

  • Еще приятно, что попытки eBay по руссификации интерфейса для пользователей из России и стран СНГ, начали приносить плоды. Ведь подавляющая часть граждан стран бывшего СССР не сильна познаниями иностранных языков. Английский язык знают не более 5% населения. Среди молодежи — побольше. Поэтому хотя бы интерфейс на русском языке — это большая помощь для онлайн-шоппинга на этой торговой площадке. Ебей не пошел по пути китайского собрата Алиэкспресс, где совершается машинный (очень корявый и непонятный, местами вызывающий смех) перевод описания товаров. Надеюсь, что на более продвинутом этапе развития искусственного интеллекта станет реальностью качественный машинный перевод с любого языка на любой за считанные доли секунды. Пока имеем вот что (профиль одного из продавцов на ебей с русским интерфейсом, но англоязычным описанием):
    https://uploads.disquscdn.com/images/7a52c9a89108b922159a4fad35de0ab0bee0c8804b9731f56d8a1dc659655d60.png