Ардуино функции: Уроки Ардуино. Функции

Функции Arduino | Учи Урок информатики


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

Использование функций (или, функциональный подход к программированию) имеет ряд преимуществ:

  • функции помогают самоорганизации программиста, часто способствуя более четкому осмыслению программы;
  • кодирование функции сосредоточено в одном месте программы — она должна быть написана и отлажена один раз;
  • функции позволяют уменьшить размер скетча и сделать его более компактным, поскольку один и тот же код может использован много раз;
  • уменьшается вероятность ошибок, если надо изменить поведение кода — это тоже надо делать только в одном месте;
  • функции проще переносить в другие программы и делать общий код более читаемым.

В скетче Arduino должны быть две обязательные функции — setup() и loop(). Остальные функции должны находиться за пределами фигурных скобок этих функций (до или после).

Разберем в качестве примера простую функцию, умножающую два числа.

Для «вызова» нашей простейшей функции умножения, мы должны передать ей параметры, соблюдая соответствие их типов тем, которые она ожидает (а именно целые числа):


1
2
3
4
5
6
7
8

void loop{
  int i = 2;
  int j = 3;
  int k;
 
  k = myMultiplyFunction(i, j); // в k будет помещен результат "возвращенный"
//функцией  myMultiplyFunction. То есть 6.
}

При этом функция обязательно должна быть объявлена за пределами других функций, например, после функции loop(), перед setup() или после setup() и перед loop()

Целиком текст полученного скетча будет выглядеть так:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

void setup(){
  Serial.begin(9600);
}
 
void loop() {
  int i = 2;
  int j = 3;
  int k;
 
  k = myMultiplyFunction(i, j); // k теперь равно 6
  Serial.println(k);
  delay(500);
}
 
int myMultiplyFunction(int x, int y){
  int result;
  result = x * y;
  return result;
}

к содержанию ->>

return

Описание

Завершает текущую функцию и возвращает результат в вызвавшую функцию (если необходимо).

Синтаксис


1
2

return;
return value; // обе формы записи допустимы

value: любая переменная или константа

Пример


1
2
3
4
5
6
7

int myAnalogSensor(){       
    if (analogRead(0) > 100) {
        return true;
    else{
        return false;
    }
}

к содержанию ->>

Пожалуйста, оцените статью

4.18 из 5. (Всего голосов:262)

Arduino — Функции — CoderLessons.com

Функции позволяют структурировать программы по сегментам кода для выполнения отдельных задач. Типичный случай создания функции — это когда в программе нужно выполнить одно и то же действие несколько раз.

Стандартизация фрагментов кода в функции имеет несколько преимуществ:

  • Функции помогают программисту оставаться организованным. Часто это помогает осмыслить программу.

  • Функции кодифицируют одно действие в одном месте, так что функцию нужно продумать и отладить только один раз.

  • Это также уменьшает вероятность ошибок при модификации, если код необходимо изменить.

  • Функции делают весь эскиз меньше и компактнее, потому что фрагменты кода многократно используются.

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

Функции помогают программисту оставаться организованным. Часто это помогает осмыслить программу.

Функции кодифицируют одно действие в одном месте, так что функцию нужно продумать и отладить только один раз.

Это также уменьшает вероятность ошибок при модификации, если код необходимо изменить.

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

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

В скриншоте Arduino или программе есть две обязательные функции: setup () и loop (). Другие функции должны быть созданы вне скобок этих двух функций.

Наиболее распространенный синтаксис для определения функции —

Объявление функции

Функция объявляется вне любых других функций, выше или ниже функции цикла.

Мы можем объявить функцию двумя различными способами —

Первый способ — просто записать часть функции, называемую прототипом функции, над функцией цикла, которая состоит из:

  • Тип возврата функции
  • Имя функции
  • Тип аргумента функции, не нужно писать имя аргумента

Прототип функции должен сопровождаться точкой с запятой (;).

В следующем примере показана демонстрация объявления функции с использованием первого метода.

пример

int sum_func (int x, int y) // function declaration {
   int z = 0;
   z = x+y ;
   return z; // return the value
}

void setup () {
   Statements // group of statements
}

Void loop () {
   int result = 0 ;
   result = Sum_func (5,6) ; // function call
}

Вторая часть, которая называется определением или объявлением функции, должна быть объявлена ​​ниже функции цикла, которая состоит из —

  • Тип возврата функции
  • Имя функции
  • Тип аргумента функции, здесь вы должны добавить имя аргумента
  • Тело функции (операторы внутри функции, выполняемые при вызове функции)

В следующем примере демонстрируется объявление функции с использованием второго метода.

пример

int sum_func (int , int ) ; // function prototype

void setup () {
   Statements // group of statements
}

Void loop () {
   int result = 0 ;
   result = Sum_func (5,6) ; // function call
}

int sum_func (int x, int y) // function declaration {
   int z = 0;
   z = x+y ;
   return z; // return the value
}

Второй метод просто объявляет функцию над функцией цикла.

Arduino IDE для ESP8266 – esp8266

Arduino IDE для ESP8266 позволяет писать скетчи и загружать их одним кликом в ESP8266 в знакомой среде (я бы даже сказал «до боли знакомой») Arduino IDE. Для тех кому интерфейс Arduino IDE не очень по душе, есть поддержка плагина для Eclipse. Итак, обо всем по порядку. Самые нетерпеливые могут сразу перейти к Arduino IDE для ESP8266: быстрый старт

Arduino IDE для ESP8266 позволяет создавать прошивки и прошивать их в ESP8266 точно так же, как вы это делаете с Arduino. При этом никаких плат Arduino не требуется, это не тот случай, когда ESP8266 используется в качестве WiFi шилда для Arduino. Кроме того, вы можете использовать практически все Arduino библиотеки с ESP8266 после небольшой доработки. В настоящее время уже достаточно много библиотек адаптировано для использования с ESP8266, но о них чуть ниже.

Arduino IDE для ESP8266 поддерживает все существующие на сегодняшний день модули ESP8266 (потому что они особо и не отличаются), включая модули с флеш бОльшего, чем 512k объема. Поддерживаются модули NodeMCU (всех версий), Olimex-MOD-WiFi-ESP8266.

Поддерживается режим авторестарта и прошивки по RTS+DTR, как у обычной Arduino, для этого потребуется USB-TTL адаптер с разведенными пинами DTR и RTS. Если у вас только RX, TX и GND на USB-TTL, то придется по-старинке вручную притягивать к земле GPIO0 и передергивать питание модуля для прошивки.

Arduino IDE для ESP8266: краткий обзор реализованных в настоящее время функций

Базовые функции языка Wiring

Управление GPIO осуществляется точно также, как и управление pin для arduino:
pinMode,
digitalRead,
digitalWrite,  
analogWrite функционируют как обычно. GPIO нумеруются так, как мы уже привыкли: для чтения состояния GPIO2 нужно использовать команду
digitalRead(2)

GPIO0-GPIO15 могут быть
INPUT,
OUTPUT,
INPUT_PULLUP, и
INPUT_PULLDOWN. GPIO16 может быть только
INPUT,
OUTPUT или
INPUT_PULLDOWN. Команда
analogRead(A0) считывает значение ADC (АЦП) с TOUT.

Команда
analogWrite(pin, value) включает программный PWM (ШИМ) на указанном GPIO. Команда
analogWrite(pin, 0) отключает PWM.
value может быть в диапазоне от 0 до
PWMRANGE. Константа
PWMRANGE в настоящее время равна 1023.

Поддержка прерываний обеспечивается функциями
attachInterrupt,
detachInterrupt. Прерывания могут быть назначены на любой GPIO, кроме GPIO16. Стандартные прерывания Arduino
CHANGE,
RISING,
FALLING тоже поддерживаются.

ESP8266 — функции пинов

Тайминг и delay

Функции
millis и
micros возвращают миллисекунды и микросекунды соответственно, прошедшие с момента старта модуля. Любимая многими функция
delay также присутствует и приостанавливает выполнение скетча на указанное время в миллисекундах и позволяет отработать операциям WiFi и TCP/IP. Функция
delayMicroseconds используется аналогично, только время задается в микросекундах.

Помните о том, что когда модуль поддерживает WiFi соединение, ему приходится выполнять множество фоновых задач, кроме вашего скетча. WiFi и TCP/IP функции библиотек SDK имеют возможность обработать все события в очереди после завершения каждого цикла вашей функции
loop() или во время выполнения
delay(…). Если в вашем коде есть фрагменты, которые выполняются более 50 миллисекунд, то необходимо использовать 
delay(…) для сохранения нормальной работоспособности стека WiFi.

Также вы можете использовать функцию
yield(), которая эквивалентна
delay(0). С другой стороны, функция
delayMicroseconds блокирует выполнение других задач, поэтому ее использование для временных задержек свыше 20 миллисекунд не рекомендуется.

Последовательные порты Serial и Serial1 (UART0 и UART1)

Объект
Serial работает точно также, как и с Arduino. Помимо аппаратного FIFO (по 128 байт для приема и передачи) определен и программный буфер размером по 256 байт для приема и передачи данных. Прием и передача данных происходит по прерываниям, прозрачно для вашего скетча. Функции записи и чтения блокируют выполнение скетча только когда аппаратный FIFO и программный буфер переполняются.

Serial использует аппаратный UART0, работающий на GPIO1(TX) и GPIO3(RX). Эти пины могут быть переназначены на GPIO15 (TX) и GPIO13 (RX) вызовом функции
Serial.swap(); после
Serial.begin();. Повторный вызов
Serial.swap(); вернет все на свои места.

Serial1 использует аппаратный UART1, работающий только на передачу. UART1 TX это GPIO2. Для включения
Serial1 используйте
Serial1.begin();

По умолчанию, отладочная информация библиотек WiFi выключается, когда вы вызываете функцию
Serial.begin();. Для включения отладочной информации на UART0 используйте
Serial.setDebugOutput(true); Для перенаправления вывода отладочной информации на UART1 используйте команду
Serial1.setDebugOutput(true);

И
Serial и
Serial1 поддерживают 5, 6, 7, 8 бит данных, odd (O), even (E), и no (N) режимы четности, и 1 или 2 стоп бита. Для выбора нужного режима вызывайте
Serial.begin(baudrate, SERIAL_8N1); или
Serial.begin(baudrate, SERIAL_6E2); и т.д.

PROGMEM

Макрос
PROGMEM работает точно также, как в Arduino, помещая read only данные и строковые константы (литералы) во флеш память, высвобождая HEAP. Важное отличие состоит в том, что в ESP8266 одинаковые литералы не хранятся в одном месте, поэтому использование строковых констант внутри конструкций
F(«») и/или
PSTR(«») приводит к расходованию флеш памяти при каждом вызове этих функций. Вы должны самостоятельно управлять одинаковыми строками для экономичного расходования места во флеш памяти.

Библиотека WiFi ESP8266 (ESP8266WiFi)

Функции библиотеки WiFi ESP8266 очень схожи с функциями библиотеки для обычного WiFi шилда.

Список отличий:

  • WiFi.mode(m): выбрать режим
    WIFI_AP (точка доступа),
    WIFI_STA (клиент), или
    WIFI_AP_STA (оба режима одновременно).
  • WiFi.softAP(ssid) создает открытую точку доступа
  • WiFi.softAP(ssid, password) создает точку доступа с WPA2-PSK шифрованием, пароль должен быть не менее 8 символов
  • WiFi.macAddress(mac) позволяет получить MAC адрес в режиме клиента
  • WiFi.softAPmacAddress(mac) позволяет получить MAC адрес в режиме точки доступа
  • WiFi.localIP() позволяет получить IP адрес в режиме клиента
  • WiFi.softAPIP() позволяет получить IP адрес в режиме точки доступа
  • WiFi.RSSI() пока не реализована
  • WiFi.printDiag(Serial); выводит диагностическую информацию
  • Класс
    WiFiUDP поддерживает прием и передачу multicast пакетов в режиме клиента. Для передачи multicast пакета используйте вместо
    udp.beginPacket(addr, port) функцию
    udp.beginPacketMulticast(addr, port, WiFi.localIP()). Когда вы ожидаете multicast пакеты, используйте вместо
    udp.begin(port) функцию
    udp.beginMulticast(WiFi.localIP(), multicast_ip_addr, port). Вы можете использовать
    udp.destinationIP() для определения того, был ли пакет отправлен на multicast адрес или предназначался именно вам. Multicast функции не поддерживаются в режиме точки доступа.

WiFiServer,
WiFiClient, и
WiFiUDP работаю точно так же, как и с библиотекой обычного WiFi шилда. Четыре примера идет в комплекте с этой библиотекой.

Тикер

Библиотека Ticker может быть использована для выполнения периодически повторяющихся событий через определенное время. Два примера включено в поставку.

В настоящее время не рекомендуется блокировать операции ввода-вывода (сеть, последовательный порт, файловые операции) в callback функциях тикера. Вместо блокирования устанавливайте флаг в callback функциях и проверяйте этот флаг в основном цикле.

EEPROM

Эта библиотека немного отличается от стандартной Arduino EEPROM. Необходимо вызвать функцию
EEPROM.begin(size) каждый раз перед началом чтения или записи, размер (указывается в байтах) соответствует размеру данных, которые вы намереваетесь использовать в EEPROM. Размер данных должен быть в диапазоне от 4 до 4096 байт.

Функция
EEPROM.write не производит запись данных во флеш память немедленно, вы должны использовать функцию
EEPROM.commit() каждый раз, когда вы хотите сохранить данные в память. Функция
EEPROM.end() тоже производит запись данных, а также освобождает оперативную память от данных, запись которых произведена. Библиотека EEPROM использует один сектор во флеш памяти, начиная с адреса 0x7b000 для хранения данных. В поставку включено три примера работы с EEPROM.

I2C (Библиотека Wire)

Реализован только режим ведущего, частота ориентировочно до 450 кГц. Перед использованием шины I2C, нужно выбрать пины SDA и SCL путем вызова функции
Wire.pins(int sda, int scl), например
Wire.pins(0, 2) для модуля ESP-01. Для других модулей пины по умолчанию 4(SDA) и 5(SCL).

SPI

Библиотека SPI поддерживает весь Arduino SPI API, включая транзакции, в том числе фазу синхронизации (CPHA). Clock polarity (CPOL) пока не поддерживается (SPI_MODE2 и SPI_MODE3 не работают).

ESP8266 API

Поддержка функций, специфичных для ESP8266 (режим глубокого сна и сторожевой таймер), реализована в объекте
ESP. Функция
ESP.deepSleep(microseconds, mode) переводит модуль в режим глубокого сна. Параметр
mode может принимать значения:
WAKE_DEFAULT,
WAKE_RFCAL,
WAKE_NO_RFCAL,
WAKE_RF_DISABLED. GPIO16 должен быть соединен с RESET для выхода из режима глубокого сна.

Функции
ESP.wdtEnable(),
ESP.wdtDisable(), и
ESP.wdtFeed() управляют сторожевым таймером.

ESP.reset() перезагружает модуль

ESP.getFreeHeap() возвращает размер свободной памяти

ESP.getFreeHeap() возвращает размер свободной памяти

ESP.getChipId() возвращает ESP8266 chip IDE, int 32bit

ESP.getFlashChipId() возвращает flash chip ID, int 32bit

ESP.getFlashChipSize() возвращает размер флеш памяти в байтах, так, как его определяет SDK (может быть меньше реального размера).

ESP.getFlashChipSpeed(void) возвращает частоту флеш памяти, в Гц.

ESP.getCycleCount() возвращает количество циклов CPU с момента старта, unsigned 32-bit. Может быть полезна для точного тайминга очень коротких операций

Библиотека OneWire

Библиотека OneWire была адаптирована для ESP8266 (внесены изменения в OneWire.h) Если у вас установлена библиотека OneWire в папку Arduino/libraries, то будет использоваться именно она, а не из комплекта поставки.

mDNS библиотека ESP8266mDNS

Библиотека позволяет реализовать в вашей программе ответ на мультикастовые DNS запросы для локальной зоны, например «esp8266.local». В настоящее время поддерживается только одна зона. Позволяет обращаться к WEB серверу ESP8266 по имени, а не только по IP адресу. Дополнительную информацию вы можете найти в прилагаемом примере и в файле readme данной библиотеки.

Библиотека Servo

Библиотека позволяет управлять сервомоторами. Поддерживает до 24 сервоприводов на любых доступных GPIO. По умолчанию первые 12 сервоприводов будут использовать Timer0 и будут независимы от любых других процессов. Следующие 12 сервоприводов будут использовать Timer1 и будут разделять ресурсы с другими функциями, использующими Timer1. Большинство сервоприводов будут работать с управляющим сигналом ESP8266 3,3в, но не смогут работать на напряжении 3,3в и потребуют отдельный источник питания. Не забудьте соединить общий провод GND этого источника с GND ESP8266

Другие библиотеки, не включенные в поставку Arduino IDE

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

  • arduinoWebSockets — WebSocket сервер и клиент для esp8266 (RFC6455)
  • aREST REST API handler библиотека, позволяет управлять GPIO через http запросы вида http://192.168.1.101/digital/6/1
  • Blynk — легкий в освоении IoT фреймворк (страница на Kickstarter). Статья на нашем сайте об этой библиотеке и мобильном приложении ESP8266 – Управляем со смартфона через Blynk
  • DallasTemperature DS18B20, DS1820, DS18S20, DS1822
  • DHT11 — используйте для инициализации следующие параметры
    DHT dht(DHTPIN, DHTTYPE, 15)
  • NeoPixelBus — Arduino NeoPixel библиотека для esp8266
  • PubSubClient Библиотека MQTT by @Imroy. Статья на нашем сайте об этой библиотеке ESP8266 подключаемся к OpenWRT+Mosquitto+mqttwarn и передаем данные на ThingSpeak, EMAIL, Android, iOS, Twitter, CloudMQTT в 100 строчек кода в Arduino IDE
  • RTC — библиотека for Ds1307 & Ds3231 для esp8266
  • Souliss, Smart Home — фреймворк для Умного Дома, построенный на Arduino, Android и OpenHAB

Установка Arduino IDE через Boards Manager

  1. Установите Arduino IDE с официального сайта Arduino.cc
  2. Запустить Arduino IDE, далее Файл — Настройки — в поле Additional Boards Manager URLs вставить ссылку на стабильную версию
    http://arduino.esp8266.com/package_esp8266com_index.jsonили для nightly build
    http://arduino.esp8266.com/staging/package_esp8266com_index.json, нажать OK (В это поле вы можете вводить несколько ссылок, разделенных запятой)
  3. Инструменты — Плата — Boards Manager
  4. В Boards Manager в поле фильтра введите esp8266 или вручную пролистайте список и кликните на ESP8266 by ESP8266 Community Forum
  5. Кликните Install и дождитесь окончания загрузки (около 130 Мегабайт). Если загрузка произошла слишком быстро, возможно, что вы уже устанавливали Arduino IDE для ESP8266 и потребуется почистить кэш Boards Manager, иначе у вас останется установленной старая версия. Нужно сначала деинсталлировать старую версию, а потом необходимо удалить файлы кэша. Для Win7 x64 удалите файлы из папки C:\Users\Пользователь\AppData\Roaming\Arduino15 и повторите все, начиная с п.2
  6. Закройте Boards Manager и в меню Инструменты выберите Плата — Generic ESP8266
  7. Установите частоту вашего модуля 80 или 160Mhz, размер флеш памяти и выберите последовательный порт, к которому подключен ваш USB-TTL адаптер

Схема подключения ESP8266

Оптимальное подключение ESP8266 для Arduino IDE

Оптимальное подключение ESP8266

Подключение ESP8266ПримечаниеUSB-TTL
VCCESP8266 подключайте к внешнему источнику питания >300мА, 3,3V
GNDвсе контакты GND должны быть соединены вместе: ESP8266, USB-TTL и источника питанияGND
TX (UTXD)RX
RX (URXD)TX
GPIO0подтягивающий к питанию резистор 10kDTR (если на вашем USB-TTL не разведен пин DTR, то вам придется вручную переключать GPIO0 на землю для перевода ESP8266 в режим прошивки)
RESET (RSBT, REST)подтягивающий к питанию резистор 10k, также можете добавить кнопку, соединяющую RESET и GND для ручного сброса модуляRTS (если на вашем USB-TTL не разведен пин RTS, то вам придется вручную перезагружать модуль )
CH_PD (CH_EN)подтягивающий к питанию резистор 10k
GPIO15 (MTDO)подтягивающий к земле резистор 10k
(для тех модулей, где выведен пин GPIO15)
GPIO2подтягивающий к питанию резистор 10k
(на схеме не показан, но рекомендуется для увеличения стабильности)
GPIO16для успешного выхода из режима Deep Sleep необходимо соединить пины ESP8266 GPIO16 и RESET через резистор 470 Ом (на схеме не показан)

Примечания.

1. Не на всех модулях выведены все пины. Перед приобретением модуля ознакомьтесь с видами модулей и их распиновкой.

2. Если на вашем USB-TTL конвертере выведены пины CTS и DSR — для автозагрузки прошивки они вам не помогут, т.к. работают только на вход.

3. Для стабильной работы ESP8266 требуется источник стабилизированного питания 3,3 вольт, ток более 250 миллиампер. Использование питания от USB-TTL конвертера может привести к нестабильности в работе.

Минимальное подключение ESP8266

Минимальное подключение ESP8266 (повышенная стабильность)

Более подробно, со всеми деталями, о подключении ESP8266 вы можете прочитать в нашей статье ESP8266 – подключение и обновление прошивки

Arduino IDE для ESP8266: быстрый старт

1. Подключить USB-TTL к USB

2. Подключить ESP8266 к USB-TTL как обычно. Если вы подключили ESP8266 по схеме без поддержки автозагрузки прошивки (не подключены DTR и RTS), то вручную соедините GPIO0 с землей, передерните питание модуля — все готово для  прошивки

3. Запускаем Arduino IDE

4. В меню ИнструментыПлатаGeneric ESP8266 board (в самом низу)

5. В меню Инструменты выбираем порт, к которому подключен наш USB-TTL

6. В меню Инструменты — выбираете частоту, размер флеш памяти вашего модуля

7. В меню Файл — Примеры (Образцы) — ESP8266WiFi — WiFiWebServer

8. В скетче заполняете SSID и пароль вашей WiFi сети

9. Жмем кнопку компиляции и загрузки скетча

10. Ждем окончании процесса прошивки. После прошивки, если модуль подключен по схеме без поддержки автопрошивки, отсоедините GPIO0 от земли и передерните питание модуля без отключения USB-TTL от питания

11. В меню Инструменты — Монитор последовательного порта

12. Выбираем скорость 115200

13. Смотрим что происходит в терминале

14. Когда модуль подключится к сети, то появятся надписи в мониторе «WiFi connected» и «Server started»

15. Ниже будет IP адрес вашего модуля ESP8266, например 192.168.1.248

16. Открываете любой браузер, в строке адреса вбиваете «http://192.168.1.248/gpio/1»

17. Смотрите монитор последовательно порта и если к ESP8266 к GPIO2 у вас подключен светодиод (через резистор, разумеется), то он включится.

18. Profit!

Автором этого проекта адаптации Arduino IDE для ESP8266 является наш соотечественник из Санкт-Петербурга Иван Грохотков.

Скачать Arduino IDE для ESP8266 с github

Скачать Arduino IDE для ESP8266 с build сервера

Скачать исходный код Arduino IDE для ESP8266

Задать вопросы автору проекта Ивану Грохоткову aka igrr или сообщить об ошибке в Arduino IDE для ESP8266 можно в специальном разделе на нашем форуме.

Функция задержки Arduino, и почему вы не должны ее использовать

Когда вы впервые начали учиться, как развиваться

для Arduino

Вы, вероятно, создали продукт, который работает примерно так:

К вашему Arduino будет подключен один светодиод. Это будет включать и выключать каждую секунду или около того, и будет продолжаться, пока Arduino не будет выключен. Это программа «Hello World» от Arduino, которая прекрасно иллюстрирует, как всего несколько строк кода могут создать что-то осязаемое.

Я также готов поспорить, что вы использовали функцию delay () для определения интервалов между включением и выключением света. Но вот в чем дело: хотя задержка удобна для основных демонстраций того, как работает Arduino, вы не должны использовать ее в реальном мире. Вот почему — и что вы должны использовать вместо этого.

Как работает Delay ()

Принцип работы функции delay () довольно прост. Он принимает одно целое число

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

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

Разница между блокирующими и неблокирующими функциями

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

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

Эдуардо работает немного по-другому. Пока его хлеб поджаривается, он уже начал жарить яйца и бекон. Вместо того, чтобы ждать, пока один предмет закончит готовиться, прежде чем переходить к следующему, он готовит несколько блюд одновременно. Конечным результатом является то, что Эдуардо тратит меньше времени на приготовление завтрака, чем Генри, и к тому времени, когда Генри Блокинг закончил, тост и яйца уже остыли.

Это глупая аналогия, но она иллюстрирует суть.

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

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

К счастью, delay () — не единственный способ заставить вашу программу ждать при кодировании для Arduino.

Знакомьтесь, Миллис ()

Функция millis () выполняет одну задачу. При вызове он возвращает (в виде длинного типа данных) количество миллисекунд, прошедших с момента первого запуска программы. Итак, почему это полезно?

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

Теперь давайте посмотрим, как это работает с Arduino.

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

А поскольку он неблокирующий, любой код, расположенный за пределами первого оператора if, должен работать нормально.

Нашли какие-нибудь другие блокирующие функции, к которым мы должны относиться с осторожностью? Дайте мне знать в комментариях ниже, и мы будем общаться.

Авторы фотографий: Ардуино (Дэниел Спиесс), шеф-повар (Олли Свенсон)

Tweaking4All.com — Программирование Arduino для начинающих

Обзор этой главы

Полный обзор этого курса можно найти здесь: Обзор курса .

Что такое функции и зачем они нужны?

Теоретически нам не нужны функции… однако без функций наш код стал бы очень длинным и совершенно нечитаемым. Не считая; нам нужно было бы написать намного больше кода.

Мы уже работали с двумя функциями, которые необходимы для вашего Arduino: «setup ()» и «loop ()».

Но что такое функции и зачем они нужны? Или лучше сказать: почему они нам нравятся?

Функцию (также называемую подпрограммой ) можно рассматривать как набор инструкций, сгруппированных вместе, с учетом конкретной задачи.
Мы можем создать функцию, чтобы сделать наш код более читабельным, или потому, что мы хотим использовать эту конкретную задачу в нескольких местах нашей программы (программ).

На некоторых языках это называется «подпрограмма», потому что так оно и есть; воспринимайте это как отдельную небольшую программу.И, как и в случае с обычной программой, функция может иметь другие функции, определенные внутри нее, и это подводит нас к другой проблеме, на которую следует обратить внимание: функция имеет область видимости, как и в случае с переменными, — «область», в которой она может быть увиденным и использованным!

Я понимаю, что это немного сложно понять сразу, но об этом нужно помнить.

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

Функция — это группа инструкций для определенной задачи.

Когда мы определяем нашу функцию?

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

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

Допустим, у нас есть собака, которую нужно выгуливать 4 раза в день: в , 8 утра, , , 12 вечера, , , 5 вечера, , и в , 9 вечера, .

Задача выгула собаки включает:
— Надеть пальто
— Надеть собаку на поводок
— Выйти на улицу
— Прогуляться по парку 15 минут
— Вернуться внутрь
— Снять поводок dog
— Снимай пальто.

Теперь предположим, что наша программа справляется с повседневными задачами, когда дело доходит до выгула собаки:

, если 8 утра, то
— Наденьте пальто
— Наденьте собаку на поводок
— Выйдите на улицу
— Пройдите по парку 15 минут
— Вернитесь внутрь
— Снимите поводок с собаки
— Возьмите с пальто.

, если 12:00, то
— Наденьте пальто
— Наденьте собаку на поводок
— Выйдите на улицу
— Пройдите по парку 15 минут
— Вернитесь внутрь
— Снимите поводок с собаки
— Возьмите с пальто.

, если 17:00, то
— Наденьте пальто
— Наденьте собаку на поводок
— Выйдите на улицу
— Пройдите по парку 15 минут
— Вернитесь внутрь
— Снимите поводок с собаки
— Возьмите с пальто.

, если 21:00, то
— Наденьте пальто
— Наденьте собаку на поводок
— Выйдите на улицу
— Пройдите по парку 15 минут
— Вернитесь внутрь
— Снимите поводок с собаки
— Возьмите с пальто.

Наша программа довольно длинная, правда? И еще много повторяющегося кода…

Мы могли бы создать функцию, назовем ее «WalkTheDog ()» и определить ее следующим образом.

WalkTheDog ():
— Наденьте пальто
— Наденьте собаку на поводок
— Выйдите на улицу
— Пройдите по парку 15 минут
— Вернитесь внутрь
— Снимите поводок с собаки
— Возьмите с пальто.

Теперь наша программа будет выглядеть намного проще:

если 8:00, то
WalkTheDog ()

если 12:00, то
WalkTheDog ()

если 17:00, то
WalkTheDog ()

если 21:00, то
WalkTheDog ()

Вы видите, что не только наш код стал намного короче, но и стал более читабельным. — «функция» вызывается так, как если бы это был обычный оператор.Фактически мы расширили наш «язык».

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

Но функции имеют также то преимущество, что если вы допустили ошибку на шагах выгула собаки, вам нужно изменить свой код только в одном месте: функции, которая делает ваш код более управляемым .Например, если мы забыли добавить «Разблокировать дверь» в качестве шага. Мы просто редактируем функцию WalkTheDog () вместо того, чтобы редактировать код в 4 разных местах в предыдущем коде.

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

Имейте в виду, что:

  • Функция подобна отдельной маленькой программе … внутри программы
  • Функция может иметь внутри функции
  • Функция имеет « область » — как мы видели с переменными и константами.

Создание собственных функций

Довольно легко определить наши собственные функции на языке C, который мы используем для программирования Arduino. Базовая структура выглядит так:

 

тип данных FunctionName (FunctionParameters) {
// код функции
}

Надеюсь, вы помните некоторые из DataTypes , которые мы упоминали в Части 3 — DataTypes. Если нет, не волнуйтесь, вы можете оглянуться назад, если хотите, но я также упомяну здесь некоторые детали.

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

Наиболее распространенными типами данных для возвращаемого значения являются boolean , int , char , float и void (также будут работать другие типы, такие как double, long и unsigned).

Очевидно, что нашей функции требуется FunctionName , и нам нужно следовать тем же правилам именования, что и для переменных и констант:

Название функции :

  • Должен начинаться с a буква (a, b,…, z, A, B,…, Z) или подчеркивание (_)
  • Может содержать букв
  • Может содержать знаков подчеркивания (с)
  • Может содержать чисел
  • НЕ МОЖЕТ содержать специальные символы, символы или пробелы
  • с учетом регистра !

Параметры функции — это ноль или более значений, которые мы хотим передать функции.Однако количество значений и тип данных этих значений фиксировано и определено как в нашем определении функции!

В некоторых диалектах C вам нужно будет «объявить» (объявить) или «определить» функцию, прежде чем она будет использована в коде. Компилятору необходимо «знать» о его существовании, прежде чем он сможет его использовать. Это похоже на , а не на , как в случае с C на Arduino, поэтому здесь он не рассматривается.

Мы использовали параметры функций и раньше, хотя вы могли не знать об этом полностью.Эти значения мы передаем в скобки, например, в этой строке: Serial.print ("Hello");

Здесь мы вызываем функцию «print ()» из объекта «Serial» и передаем параметр «Hello» (строку). Подробнее об объектах позже.

Следующий блок кода, между похвалами, — это то, что мы видели раньше, например, в функции «if» и различных типах циклов («for», «while» и «do… while…»).

Этот блок кода группирует инструкции для нашей функции.

Начнем с простой функции:

 

1
2
3

void SayHello () {
Serial.println ("Привет");
}

Эта функция, называемая SayHello (), принимает без параметров , поскольку между скобками () нет ничего.
Он также не возвращает значение, поскольку мы использовали специальный тип данных «void», что означает «ничего, разреженный воздух, нада».

«Тело» функции (блок кода) содержит инструкции для этой функции и просто выводит «Hello».

Пример того, как мы будем использовать это:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

пустая установка () {
// устанавливаем скорость для последовательного монитора:
Serial.begin (9600);

для (int A = 1; A <= 5; A ++) {
Скажи привет();
}
}

void loop () {
// пока оставим пустым
}

void SayHello () {
Serial.println ("Привет");
}

Большая часть этого должна показаться знакомой, я все равно надеюсь…

В конце кода вы видите, как мы определили нашу функцию «SayHello ()».
В цикле «for» вы видите, как мы вызываем нашу функцию 5 раз, что дает следующий результат:

Привет
Привет
Привет
Привет
Привет

Легко, правда?

Передача значения функции

Это был довольно простой пример, давайте рассмотрим пример, в котором мы фактически передаем параметр, где мы используем предыдущий пример включения 5 источников света с помощью цикла «for».

Для этого мы создаем новую функцию под названием «DoLights», которой мы хотим передать номер источника света, который необходимо включить.

Мы уже знаем, что это число является целым числом типа «int» (см. Также определение «A» в цикле «for»).

Мы также знаем, что он не вернет никаких значений, поэтому мы получаем эту функцию:

 

void DoLights (int LightNumber) {
Serial.print («Включить свет для света»);
Серийный.println (LightNumber);
}

Параметр «LightNumber» определяется как «int». Вы можете видеть, что есть определение переменной «LightNumber» внутри функции «DoLights». Когда мы передаем значение для этого параметра, значение будет скопировано в эту «новую» переменную.

Имея в виду «область действия», «LightNumber», очевидно, существует только в функции «DoLights».

Вызов нашей функции (строка 6) точно такой же, как и в предыдущем примере, однако на этот раз мы передаем значение переменной «A».Значение «A» копируется в переменную «LightNumber» — помните, что оно КОПИРОВАННЫЙ.

Всего этого вместе:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

пустая установка () {
// устанавливаем скорость для последовательного монитора:
Serial.begin (9600);

для (int A = 1; A <= 5; A ++) {
DoLights (A);
}
}

void loop () {
// пока оставим пустым
}

void DoLights (int LightNumber) {
Serial.print («Включай свет, чтобы свет»);
Serial.println (LightNumber);
}

Вы видите, как переменная «LightNumber» используется в функции?

Но это был всего лишь пример того, как передать только одно значение. Как это работает, если нам нужно передать несколько значений?

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

Для этого нам нужно разделить два значения, и для этого мы используем запятую (,).

Параметры в функции разделяются запятой,
как при определении функции, так и при ее вызове.

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

Я добавил оператор «if», чтобы функция могла включать и выключать свет — еще одна интересная особенность функций; мы можем написать их таким образом, чтобы они могли работать более чем для одного сценария.

Всего этого вместе:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

Пустая настройка () {
// устанавливаем скорость для последовательного монитора:
Serial.begin (9600);

для (int A = 1; A <= 5; A ++) {
DoLights (A, истина);
}

для (int A = 1; A <= 5; A ++) {
DoLights (A, ложь);
}
}

void loop () {
// пока оставим пустым
}

void DoLights (int LightNumber, boolean LightOn) {
if (LightOn) {
Serial.print («Включить свет, чтобы свет»);
}
else
{
Serial.print («Выключить свет для освещения»);
}

Serial.println (LightNumber);
}

Как видите, сначала мы запускаем цикл for из 5 итераций, включая 5 индикаторов.
Значения, которые мы передаем, также разделяются запятой!
В следующем цикле for, также в 5 итерациях, мы выключаем свет.

Ваш результат должен выглядеть так:

Включите фары для света 1
Включите фары для света 2
Включите фары для света 3
Включите фары для света 4
Включите фары для света 5
Выключите фары для света 1
Выключите фары для света 2
Выключить освещение для освещения 3
Выключить освещение для освещения 4
Выключить освещение для освещения 5

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

Возврат значения из функции

Итак, мы знаем, что функция может «получать» значения с помощью параметров. А что, если мы хотим, чтобы функция возвращала ответ или результат — например, из сложного вычисления?

Помните, мы использовали «void» в наших предыдущих определениях функций? Здесь мы определяем, каким будет возвращаемый результат.

Однако в самой функции нам действительно нужно «вернуть» этот тип значения, и для этого мы используем функцию « return ».

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

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

Итак, мы создали функцию «CalculateMyMoney», которая принимает два параметра. Я намеренно назвал их по-другому, чтобы проиллюстрировать, что значения двух переменных (PocketMoney и Savings) КОПИРУЮТСЯ в имена переменных, которые мы определили в функции. Было бы хорошо дать именам переменных параметров одинаковые имена, если вы вспомните «область действия» переменных: переменные в функции не являются теми же переменными, что и переменные вне функции.

В функции мы складываем эти 2 значения и возвращаем результат «Итого». Это «возвращаемое» значение затем присваивается переменной «AllMyMoney».

Попробуйте этот код.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

пустая настройка () {
// устанавливаем скорость для последовательного монитора:
Серийный.begin (9600);

// определяем наши переменные
int PocketMoney;
int Экономия;
int AllMyMoney;

// присваиваем значения
PocketMoney = 4;
Экономия = 12;
AllMyMoney = CalculateMyMoney (карманные деньги, сбережения);

// выводим значения на серийный монитор
Serial.print ("PocketMoney =");
Serial.println (PocketMoney);

Serial.print ("Экономия =");
Serial.println (Экономия);

Serial.print ("AllMyMoney =");
Серийный.println (AllMyMoney);
}

void loop () {
// пока оставим пустым
}

int CalculateMyMoney (int Wallet, int Bank) {
int Total;

Итого = кошелек + банк;

возврат Итого;
}

Функция с возвращаемым значением может рассматриваться как переменная. Так что везде, где мы можем использовать значение или переменную, мы также можем использовать функцию, которая возвращает значение, что я проиллюстрирую в этой слегка измененной версии.Посмотрите на строку 21… мы только что поместили вызов функции прямо здесь как «параметр» для « Serial.println () »… и это работает! Таким образом, нам не нужно сначала назначать возвращаемое значение переменной.

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

Функция с возвращаемым значением может заменять обычное значение или переменную…

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

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

пустая настройка () {
// устанавливаем скорость для последовательного монитора:
Серийный.begin (9600);

// определяем наши переменные
int PocketMoney;
int Экономия;

// присваиваем значения
PocketMoney = 4;
Экономия = 12;

// выводим значения на серийный монитор
Serial.print ("PocketMoney =");
Serial.println (PocketMoney);

Serial.print ("Экономия =");
Serial.println (Экономия);

Serial.print ("AllMyMoney =");
Serial.println (CalculateMyMoney (PocketMoney, сбережения));
}

void loop () {
// пока оставим пустым
}

int CalculateMyMoney (int Wallet, int Bank) {
int Total;

Итого = кошелек + банк;

возврат Итого;
}

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

Функции, вызывающие себя (рекурсия)

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

Функция может вызывать сама себя, это называется «рекурсией».

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

Итак, в этом курсе мы оставим все как есть: то, что вы не будете использовать, пока не приобретете больше опыта в программировании.

Recursion может быть очень мощным инструментом для написания ограниченного количества кода, но с потрясающими результатами. Например, определенная графика может быть сгенерирована с помощью рекурсии, как дерево ниже или так называемый треугольник Серпинского. (Источник изображения: Википедия — Дерево Brentsmith201 , Серпинский Wereon ).
Они нарисованы с помощью рекурсии.

Рекурсивное дерево

Треугольник Серпинского — Треугольник в треугольнике

Если вам все равно интересно … вот быстрый и простой пример, который менее сложен, чем два изображения выше.

Допустим, мы хотим сложить все целые числа для всех чисел от 1 до 5. Итак, мы хотим знать, сколько равно 1 + 2 + 3 + 4 + 5 (15).
Очевидно, мы можем сделать это очень просто, введя это вручную или используя цикл «for», но сегодня мы хотим усложнить задачу, поэтому используем этот сценарий в качестве примера рекурсии.

Мы собираемся создать функцию, которая начинается с «5» и добавляет число под ним (4), затем прибавляет число под ним (3) и т. Д., Пока мы не добавим 1.

 

1
2
3
4
5
6

int AddOneLess (int Number) {
if (Number == 0)
return Number;
иначе
return Number + AddOneLess (Number-1);
}

Итак, что делает эта функция, берет значение «Число».Если это число равно нулю, верните ноль. Если число не равно нулю, возьмите это число и вызовите функцию «AddOneLess» для числа минус 1.

Странно, правда?

Другими словами: эта функция будет начинаться с заданного числа и каждый раз добавлять предыдущее число, пока оно не достигнет нуля (0).

Позвольте мне отобразить это по-другому, используя этот пример кода:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

пустая установка () {
// устанавливаем скорость для последовательного монитора:
Серийный.begin (9600);

Serial.println (AddOneLess (5));
}

void loop () {
// пока оставим пустым
}

int AddOneLess (int Number) {
if (Number == 0)
return Number;
иначе
return Number + AddOneLess (Number-1);
}

Сначала мы вызываем AddOneLess (5).

Значение «Number» не равно нулю, поэтому мы делаем 5 + «AddOneLess (5-1)», и еще НЕТ возвращаемого значения, так как мы перешли к вызову функции AddOneLess (5-1).
В этом втором вызове значение Number равно 4, но все еще не равно нулю, поэтому мы добавляем 4 + AddOneLess (4-1) и снова вызываем AddOneLess, так что возвращаемого значения пока нет.
Это приводит к третьему вызову, в котором значение «Number» равно 3, но все еще не равно нулю, поэтому мы добавляем 3 + «AddOneLess (3-1)» — возвращаемого значения пока нет.
Это приводит к четвертому вызову, в котором значение «Number» равно 2, но все еще не равно нулю, поэтому мы добавляем 2 + «AddOneLess (2-1)» — возвращаемого значения еще нет.
Это приводит к пятому вызову, в котором значение «Number» равно 1, но все еще не равно нулю, поэтому мы добавляем 1 + «AddOneLess (1-1)» — возвращаемого значения пока нет.

Шестой вызов, однако, значение «Number» равно 0 (нулю), поэтому мы возвращаем «ноль».

Однако… мы возвращаем ноль в «области действия» пятого вызова, мы вернулись на один шаг назад, где значение «Number» равно 1, и где мы говорим: вернуть значение «Number» и возвращаемое значение вызов «AddOneLess (1-1)».

Таким образом, пятый вызов возвращает 1 (значение «Number») + 0 (возвращаемое значение AddOneLess (1-1)) = 1.

Теперь мы возвращаемся в область видимости четвертого вызова, где «Число» имело значение 2 и которое возвращает значение «Число» + возвращаемое значение «AddOneLess (2-1)», так что: 2 + 1 = 3.

Возвращаясь к области действия третьего вызова, «Число» имеет значение 3, а возвращаемое значение = 3 + 3 (возврат из «AddOneLess (3-1)») = 6.

При возвращении ко второму вызову «Число» принимает значение 4, поэтому возвращаемое значение = 4 + 6 = 10;

И, возвращаясь к исходному вызову, «Number» равно 5, а результат «AddOneLess (5-1)» стал 10. Таким образом, этот вызов возвращает 5 + 10 = 15.

Красиво и запутанно, правда?

Может быть, имеет смысл показать это так:

 

1
2
3
4
5
6
7
8
9
10
11
12
13

1-й вызов: AddOneLess (5) // в функции настройки ()
2-й вызов: AddOneLess (4) // в вызове функции AddOneLess (5)
3-й вызов: AddOneLess (3) // в вызове функции AddOneLess (4)
4-й вызов: AddOneLess (2) // в вызове функции AddOneLess (3)
5-й вызов: AddOneLess (1) // в вызове функции AddOneLess (2)
6-й вызов: AddOneLess (0) // в вызове функции AddOneLess (1) Number теперь будет равно нулю!
5-й звонок: возврат = номер + результат 6-й звонок = 1 + 0 = 1
4-й звонок: возврат = номер + результат 5-й звонок = 2 + 1 = 3
3-й звонок: возврат = номер + результат 4-й звонок = 3 + 3 = 6
2-й звонок: возврат = номер + результат 3-й звонок = 4 + 6 = 10
1-й звонок: возврат = номер + результат 5-й звонок = 5 + 10 = 15

Окончательный возврат: = 15

Я знаю, что это сложно понять в первый раз, так что не беспокойтесь, если это вам не по зубам.Мне действительно пришлось серьезно подумать, чтобы даже поместить это объяснение здесь, но для всей полноты я все равно добавил его. Просто помните, что функция может вызывать сама себя и не мешает предыдущим вызовам. Каждый вызов функции получит свою «область видимости».

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

При создании Рекурсивных функций :

ВСЕГДА убедитесь, что функция имеет условие « exit » для выхода из функции.

Если у вас есть вопросы, просто задавайте их ниже, в разделе комментариев, и имейте в виду: глупых вопросов нет! В какой-то момент нам всем пришлось начать!

Следующая глава: Программирование Arduino для начинающих — Часть 7: Строки

delay () Функция Arduino: замкнутые циклы и код блокировки

Вы когда-нибудь создавали проект Arudino и хотели бы, чтобы что-то происходило с заданным интервалом? Может быть, каждые 3 секунды вы хотите, чтобы сервопривод перемещался, или, может быть, каждые 1 минуту вы хотите отправлять обновление статуса на веб-сервер.

Как вы это делаете? Есть ли простая и понятная функция, которая поможет нам в этом? Да, есть! Мы обсудим функцию задержки, а также функцию millis (), в видео ниже:

Это вторая часть нашей мини-серии о функциях millis (). Часть 1 помогает нам понять, что делает функция millis (), часть 2 обсуждает жесткие циклы и код блокировки, а часть 3 обсуждает, когда функция millis () затмевает функцию delay ().

Темы этого урока

  • Петли тугие
  • Код блокировки
  • Древний рецепт вдохновляющей музыки

Миллис () по сравнению с задержкой ()

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

Более крутым и красивым вариантом является функция Arduino millis (). И чем больше вы знакомы с использованием функции millis, которая помогает вам определять время событий в коде Arduino, тем легче будет позже включить другие части в вашу программу.

Как только вы начнете использовать функцию millis (), вы станете счастливее, веселее, и, черт возьми, вы понравитесь людям.

Узкие петли

Сначала давайте обсудим концепцию замкнутой петли. И когда мы говорим о замкнутой петле, что это означает?

Давайте взглянем на скетч Arduino для демонстрации замкнутого цикла. Начиная с самого простого скетча, у нас есть только две функции: настройка void и цикл void. И, как вы, возможно, знаете, установка void запускается только один раз, а затем передает шоу в цикл void.

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

Он выполняет первую строку, затем выполняет вторую, затем третью, и так далее и так далее, пока не дойдет до конца. А затем он возвращается наверх.

Как быстро он выполняет цикл? Это зависит от того, какую плату Arduino вы используете, но тактовая частота Arduino Uno составляет 16 мегагерц. Это означает, что каждую секунду на Arduino выполняется 16 миллионов инструкций!

Каждая строка кода не обязательно является одной инструкцией.На самом деле, скорее всего, это несколько инструкций. Но все же это относительно быстро (процессор вашего компьютера, вероятно, работает на гигагерцовых скоростях… это миллиарды).

Так вы считаете этот пустой набросок тесной петлей? Определенно, это так быстро и плотно, как вы можете сделать петлю. Поскольку внутри цикла нет ничего для выполнения, время, необходимое для прохождения эскиза, практически ничтожно. Другими словами, интервал от начала петли до финиша короткий (следовательно, быстрый или «плотный»).

Давайте добавим несколько строк кода. Мы начнем последовательную связь, а затем распечатаем что-нибудь в окне последовательного монитора.

Это тугая петля? То есть от начала до конца цикла занимает много времени? Это занимает очень мало времени, так что это быстрый и жесткий цикл.

Однако стоит отметить, что этот цикл не такой плотный, как в предыдущем примере. В предыдущем примере у нас не было кода. Так что это просто мчалось по петле.Теперь, когда у нас есть функция, последовательная печать, потребуется (крошечное) время, чтобы напечатать «Ice Ice Baby» на последовательном мониторе.

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

.

Итак, мы объявили кнопку и использовали оператор if, чтобы проверить, была ли нажата кнопка. Высокое напряжение на пятом контакте? Если да, то мы выводим что-нибудь еще в окно монитора последовательного порта.

Это тугая петля? Итак, от начала цикла до конца, это довольно быстро?

Да, все еще довольно быстро. Это довольно сложная петля. У нас есть четыре строчки кода. Мы выполняем печать на монитор последовательного порта, а затем быстро проверяем, не нажата ли кнопка. А если есть, то что-то распечатываем. По-прежнему плотно, по-прежнему быстро.

Теперь давайте добавим задержку в эту программу, используя функцию Arduino delay (). Как видно ниже, мы добавили в цикл задержку в тысячу миллисекунд (1 секунду).

Это все еще замкнутая петля? Много ли времени от начала до конца цикла? Нет, это определенно не замкнутая петля. Код запускается быстро, мы выполняем последовательную печать, но затем мы останавливаемся прямо на функции задержки.

Вся программа останавливается, пока мы ждем завершения этого кода задержки.

Когда Arduino доходит до этой строки кода, это похоже на поход в продуктовый магазин. Вы попадаете в строку с 12 или менее пунктами, но затем парень перед вами вытаскивает свою чековую книжку и начинает выписывать чек.Ты знаешь, что будешь там на минуту. Здесь то же самое.

Так что это не замкнутый цикл. Время от начала цикла до конца цикла довольно существенно. Особенно по сравнению с парой последних программ. Порядок времени огромен.

Теперь мы попытались продемонстрировать здесь, что вся идея о тесных петлях относительна. Все зависит от вашего приложения. Если вам нужно проверять состояние датчика каждые 10 миллионных долей секунды, то программа с тремя строками кода может быть недостаточно жесткой, это просто зависит от обстоятельств.

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

Итак, плотность петли — понятие относительное.

Код блокировки

Когда программа останавливается в какой-то момент и требуется некоторое время для выполнения некоторого кода, этот код может называться блокирующим кодом .Это общий термин.

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

Блокирующий код действует не только тогда, когда мы используем функцию delay ().

Давайте возьмем нашу программу и избавимся от задержки, но мы добавим цикл for. Наш цикл for будет выводить числа и текст на последовательный порт.

Итак, как долго длится этот цикл? Он будет работать какое-то время, потому что ему нужно пройти 100 итераций, прежде чем он остановится.

А как насчет кода после этого цикла for? Может ли он работать? Нет, он должен ждать, потому что он блокируется циклом for.

Этот цикл for вложен в основной цикл . Цикл for — это «плотный» цикл? Прежде чем вы ответите, давайте еще раз подчеркнем, как вы должны думать об этом: много ли времени нужно, чтобы пройти этот цикл для цикла сверху вниз?

Ну, не совсем. У него всего две строчки кода.Так что это довольно сложный цикл.

Но это сложный цикл, который должен пройти множество итераций, прежде чем программа сможет перейти к коду под ним. Таким образом, даже жесткий цикл, если он должен выполняться через несколько итераций, может заблокировать нашего кода.

Подробнее о функции delay ()

Давайте поговорим еще об этой функции задержки. Что мы уже установили?

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

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

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

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

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

В следующем уроке этой серии, часть 3, мы собираемся определить, где это действительно становится проблемой.Вы узнаете, когда имеет смысл использовать функцию delay () в программе, а когда пора переходить к использованию функции millis ().

Обзор

Сначала мы поговорили о герметичности петли. Мы сказали, что плотность петли относительна. Это зависит от вашего приложения.

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

Надеемся, урок вам понравился! В следующей части этой серии мы продолжим наше путешествие, изучая, как использовать функцию millis для создания синхронизированных повторяющихся событий в нашем коде Arduino. Увидимся в следующий раз!

Что нужно для написания хорошей библиотеки Arduino?

Написать приличную библиотеку непросто. Мнения разработчиков о том, как должна выглядеть такая библиотека, разнятся. Я думаю, что у надежной библиотеки должна быть подробная документация, а разработчики должны покрывать ее модульными тестами.Кроме того, достойная библиотека должна скрывать сложность и предлагать интуитивно понятный и простой в использовании интерфейс.

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

Я получил свой комплект Arduino около полутора лет назад и очень быстро понял структуру.Создатели создали Arduino для любителей и программистов-самоучок. С одной стороны, это привлекает к электронике большое сообщество людей; с другой стороны, качество публикуемого кода сильно различается. Когда мне требовалась какая-то библиотека для моих проектов Arduino, я часто проверял код на GitHub, который часто не поддерживался. Поддерживаемые библиотеки часто не имели надлежащей документации. У большинства из них не было модульных тестов.

Зачем вам тестировать свой код? Посмотрите следующее видео.

В 1996 году первый полет ракеты-носителя Ariane 5 закончился масштабной катастрофой, вызванной ошибкой программирования. Прочтите полную историю здесь.

Arduino не такая уж критичная и дорогая, но я все же считаю, что написание тестов того стоит. Обратной стороной является то, что это требует дополнительного времени на разработку. Я бы сказал, что каждый проект индивидуален, и вам нужно решить, стоит ли тратить время на написание тестов. На мой взгляд, тесты принесут вам большую пользу, если вы реализуете какой-то конечный автомат.Это повсеместно в Arduino, и тесты позволяют с уверенностью изменять код. Кроме того, библиотеки Arduino обычно крошечные, всего сотни строк кода по сравнению с тысячами строк в приложениях Android или где-либо еще. Чтобы тщательно протестировать вашу библиотеку, вам часто нужно написать менее 10 тестовых примеров. Например, мне удалось написать модульные тесты для библиотеки реле менее чем за три часа, и я все еще учился использовать среду тестирования.

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

Racing Wheel, любезно предоставлено компанией JSC electronics

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

Для обнаружения лопаточных прессов я изначально использовал библиотеку OneButton Arduino. Однако он не очень хорошо интегрировался с моей существующей кодовой базой, а также не транслировал такие события, что была отпущена какая-то кнопка.Подобные события очень важны для меня, потому что мне нужно знать, что какое-то действие произошло мгновенно. Задержка от 50 до 100 миллисекунд сильно влияет на задержку. В конце концов, я решил написать собственную библиотеку.

ObjectButton

ObjectButton — это библиотека Arduino для обнаружения общих действий кнопок при изменении на входных контактах GPIO. Эта библиотека должна легко интегрироваться с объектами C ++, отсюда и название.

Ограничения функции обратного вызова

Большинство библиотек Arduino позволяют подключать функцию обратного вызова, которая вызывается, как только происходит какое-либо действие.Обратный вызов обычно реализуется как указатель на функцию без входных параметров.

 Копировать 

1extern "C" {

2 typedef void (* callbackFunction) (void);

3}

На практике вы зарегистрируете обратный вызов следующим образом:

 Копировать 

1 ...

2

3void onClick () {

4 Serial.println («Была нажата кнопка» );

5};

6

7

8OneButton button = OneButton (INPUT_PIN, true);

9кнопка.attachClick (onClick);

Следующий подход отлично подходит для регистрации функций типа C, но не для функций-членов объекта C ++. Это потому, что все функции-члены имеют неявный (первый) параметр — это .

Чтобы обойти ограничение, вы можете:

  • Передать адрес статической функции-члена, которая будет вызывать функцию-член, запущенную на конкретном объекте
  • Создать некоторую функцию C вне любого объекта, которая будет вызывать функцию-член на объекте
 Копия 

1...

Контроллер 2LedController = LedController ();

3

4void onClick () {

5 controller.turnLedOn ();

6};

Примечание. Лямбда / анонимные функции, насколько мне известно, недоступны в Arduino. Arduino не полностью поддерживает стандарт C ++.

Listeners

Вместо передачи ссылки на функцию обратного вызова ObjectButton представляет Listeners . Такой подход широко используется в Android.Мы определяем слушателя как абстрактный класс. Слушатель определяет контракт между нашей библиотекой и кодом, написанным пользователем.

Например, слушатель для получения событий onClick определяется следующим образом:

 Копировать 

1class IOnClickListener {

2public:

3 virtual ~ IOnClickListener () = default;

4

5 виртуальная пустота onClick (ObjectButton & button) = 0;

6};

Пользователь пишет класс C ++, который наследуется от этого абстрактного класса.Вам необходимо реализовать все виртуальные функции. Когда вы регистрируете такой класс в библиотеке, внутренний код в библиотеке может безопасно вызывать функцию onClick (...) .

 Копировать 

1 # include

2 # include

3

4class OnClickListener: частный виртуальный IOnClickListener {

= 5public:

6 OnClickListener (по умолчанию)

7 ....

8

9private:

10 void onClick (ObjectButton & button) override;

11...

12};

13

14void OnClickListener :: onClick (ObjectButton & button) {

15

16}

Вы зарегистрируете слушателя в библиотеке следующим образом:

 Copy 

1

9000ton4 2ObjectButton (ObjectButton .);

3

4OnClickListener onClickListener = OnClickListener ();

5button.setOnClickListener (onClickListener);

Как структурировать код

Когда вы пишете свою собственную библиотеку Arduino, это совершенно новый опыт по сравнению с написанием обычного проекта Arduino.На этот раз вы, , напишите код, который кто-то другой использует в своем проекте. В библиотеке нет функций setup () и loop () , которые нужно реализовать. Arduino дает вам рекомендации о том, как структурировать код. Некоторые из них являются обязательными, другие — необязательными, но рекомендуются. Ваша цель должна заключаться в том, чтобы сделать вашу библиотеку простой в использовании для всех, даже для новичков. Например, убедитесь, что у вас есть какая-то функция begin () , которая инициализирует ваш код. Вы часто видите этот паттерн в Arduino.

Предположим следующее о ваших пользователях:

  • Они могут не иметь опыта программирования
  • Они не понимают указатели
  • Они не поняли, что такое конструктор и деструктор
  • Они знакомы с begin () и end () функций из других библиотек
  • Они ожидают, что имена функций будут camelCase, то есть getPressure ()

Одна из ваших основных целей должна заключаться в том, чтобы скрыть всю сложность и предоставить множество примеров использования вашей библиотеки .Arduino предоставляет несколько справочных руководств; вы должны хотя бы проверить это:

После того, как вы узнаете своих пользователей и рекомендуете рекомендации сообщества Arduino, пора определить некоторые цели, которых необходимо достичь.

Цели для достижения

Как видите, охват модульных тестов и документация — это лишь некоторые из целей, которые необходимо достичь.

Абстракция высокого уровня

Когда вы пытаетесь придумать достойный дизайн библиотеки, начните с ручки и бумаги. В качестве вдохновения посмотрите официальные библиотеки Arduino.Все они хорошо написаны. Я кратко упомяну библиотеку Servo. Представьте, что вы хотите управлять сервоприводом. Какие функции вы хотели бы иметь? Возможно, вам понадобится какая-то функция, чтобы указать выходной контакт, к которому вы подключаете сервопривод. Вам также понадобится функция, которая сообщает серво, на сколько он должен повернуться, и функция, которая сообщает текущее положение вашего сервопривода. И это почти все, что вам нужно как пользователю. Вы не беспокоите пользователей подробностями реализации; все они скрыты.

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

Еще одна вещь, о которой вам нужно подумать, — это структурировать код в отношении тестирования. Когда вы пишете код, постарайтесь продумать, как его протестировать. Как правило, вам следует избегать одного большого занятия. Вместо этого разбейте код на несколько более мелких классов. Позже вы можете протестировать их отдельно от остальной системы. Их функции должны делать только одно, а их имя должно подсказывать вам, что они должны делать, не читая документацию.В нормальных условиях эти функции короткие — менее десяти строк кода. Код, реализующий конечный автомат, обычно огромен. Однако вы можете перенести большую часть функциональности во вспомогательные функции. Когда вы это сделаете, ваш конечный автомат предоставит высокоуровневую картину того, что он делает. Вот пример из моего кода для уведомления слушателей о возникновении события клика.

Вместо этого кода:

 Скопируйте 

1if (timeDelta> m_clickTicks) {

2 m_state = State :: BUTTON_NOT_PRESSED;

3 если (m_onClickListener! = Nullptr)

4 m_onClickListener-> onClick (* this);

5}

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

 Копировать 

1if (timeDelta> m_clickTicks) {

2 m_state = State :: BUTTON_NOT_PRESSED;

3 notifyOnClick ();

4}

5

6

7void ObjectButton :: notifyOnClick () {

8 if (m_onClickListener! = Nullptr)

9 m_onClickListener-> onClick (* this);

10}

Если вы посмотрите на отредактированный код, вы с первого взгляда увидите, что он делает.В коде конечного автомата может быть много предложений if ... else ; все, что делает его более читабельным, — хорошо. Кроме того, теперь вы можете писать тесты в ObjectButton :: notifyOnClick () , что намного проще, чем писать тесты, которые проверяют тот же код в коде основного конечного автомата.

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

Документация

Итак, у вас есть библиотека, которая делает то, что вы задумали. В качестве следующего шага задокументируйте свой код.

Есть два подхода:

  • Напишите документацию к вашему коду отдельно. Взгляните на библиотеку Sphinx, чтобы увидеть примеры. Sphinx использует reStructuredText в качестве языка разметки.
  • Напишите документацию внутри вашего кода. Такой подход широко используется в мире Java и его генераторе документации Javadoc.

Я предлагаю объединить оба подхода. Начните с документирования того, что делает ваша библиотека. Включите блок-схемы, диаграммы и все, что вам может быть полезно. Markdown отлично подходит для этой цели. Более того, вы можете поместить свои документы в то же хранилище, что и ваш основной код. Например, вы можете создать папку docs для хранения этих документов. Если вы используете GitHub или GitLab, вы можете сохранять документы внутри Wiki. Технически это отдельный репозиторий git, но он привязан к вашему проекту и легко доступен.

GitHub Wiki для ObjectButton

После того, как вы написали общий обзор библиотеки, пора писать комментарии внутри вашего кода. Я решил использовать Doxygen: это стандарт де-факто, и его легко настроить. Кроме того, он поддерживает несколько стилей комментариев, включая стиль Javadoc.

Doxygen

Если вы хотите создать документацию Doxygen, вам нужно создать Doxyfile. Это шаблон для Doxygen с различными настройками, например, названием вашего проекта, расположением исходного кода и т. Д.Мне посчастливилось найти Doxyfile, который кто-то адаптировал для Arduino. Одной из важнейших настроек было включение файлов * .ino . Doxygen обычно не знает, что эти файлы содержат действительный код C / C ++. Если вам когда-нибудь понадобится этот шаблон, просто возьмите мой и измените имя и путь к проекту.

Если вы хотите узнать подробности о том, как добавлять комментарии в код, лучше всего проверить веб-страницу Doxygen.

Вот пример комментария внутри кода:

 Копировать 

1

2

3

4

5

6

7IOnClickListener * m_onClickListener = nullptr;

8

9

10

11

12

13

14

15IOnDoubleClickListener * m_onDoubleClickListener = nullptr;

Комментарий Javadoc начинается с / ** , который по-прежнему является действительным комментарием C ++.Альтернативный синтаксис выглядит так: /// . Также обратите внимание на такие флаги, как @see . Это поможет вам создавать гиперссылки на другие классы и функции.

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

 Копировать 

1doxygen <путь к doxyfile>

Вы можете разместить сгенерированную документацию где угодно. Это статическая HTML-страница без внешних зависимостей. Я размещаю документацию на страницах GitHub бесплатно.Для вдохновения сгенерированная документация для одного из моих проектов находится здесь.

keywords.txt

Отлично, теперь у вас есть достойная документация. Ваша библиотека сейчас в лучшем состоянии, чем, может быть, 80 процентов библиотек. В качестве следующего шага создайте файл keywords.txt . Arduino IDE имеет минимальные возможности подсветки синтаксиса. Следовательно, нам нужно записать все функции, классы и переменные в этот файл и назначить правильный тип. Код будет выделен только тогда, когда IDE Arduino сопоставит ключевое слово с вашим определением.Создание файла keywords.txt обязательно, если вы хотите, чтобы ваша библиотека была включена в официальный реестр библиотек Arduino.

 Копия 

1 #####################################

2 # Типы данных ( KEYWORD1)

3 #####################################

4

5IOnClickListener KEYWORD1

6IOnDoubleClickListener KEYWORD1

7IOnPressListener KEYWORD1

8

9 ################################### ####

10 # Методы и функции (KEYWORD2)

11 ################################ ######

12

13getId KEYWORD2

14setOnClickListener KEYWORD2

15setOnDoubleClickListener KEYWORD2

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

Примеры

Наконец, вам нужно написать несколько примеров того, как использовать вашу библиотеку. Примеры необязательны, но они очень полезны для пользователей. Часто это первое, что люди ищут, когда ищут библиотеку. Вы можете получить к ним доступ прямо из Arduino IDE. Подумайте о некоторых основных задачах о том, как люди будут использовать библиотеку, а также попытайтесь написать хотя бы один расширенный пример.Чем больше примеров вы напишете, тем лучше для ваших пользователей.

Все примеры представляют собой файлы проекта Arduino ( * .ino ), которые вы можете скомпилировать из Arduino IDE. Если вы работали с Arduino, я уверен, что вы более чем знакомы.

Для вдохновения взгляните на примеры из моей библиотеки.

Автоматическое тестирование

Теперь у вас есть библиотека, которая хорошо документирована и предположительно проста в использовании. В качестве следующего шага напишите несколько модульных тестов, чтобы убедиться, что ваш код выполняет то, что он должен делать.Автоматическое тестирование по-прежнему не характерно для библиотек Arduino, и я не удивлен. Если мы посмотрим на другие среды программирования, они часто поставляются с официальной средой тестирования или, по крайней мере, с хорошо известной сторонней средой. В Java и Android это JUnit; Python имеет встроенный модуль unittest, иногда люди также используют pytest; мир C ++ использует Googletest. Arduino не предоставляет официальной среды тестирования, и вы не найдете много статей по этой теме.

Ручное тестирование везде

Большинство разработчиков Arduino тестируют свой код вручную.Когда код в какой-то мере работает, он считается завершенным. Если они сломают код в будущих обновлениях, они могут долго этого не замечать. Не очень хорошая вещь!

Другой вариант использования — проверка того, что ваш код работает на нескольких аппаратных платах. Если вы протестируете его вручную на Arduino Uno, как вы узнаете, что ваша библиотека также работает на Леонардо? Если вы подтвердите свой код вручную на нескольких платформах, вы потеряете время. Если вы не отметите все поддерживаемые платформы, у вас нет гарантии, что код будет работать на всех досках.

Компиляция библиотеки для разных плат

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

У вас есть несколько вариантов, как это сделать:

Подход PlatformIO

У проектов PlatformIO всегда есть файл platformio.ini. Здесь вы определяете конфигурацию всего проекта, включая платформы, для которых вы создаете двоичные файлы. Каждая платформа представляет собой отдельную плату Arduino или даже другую архитектуру процессора.

 Копия 

1; Arduino Uno: первая целевая плата

2 [env: uno]

3platform = atmelavr

4board = uno

5framework = arduino

6

7; Arduino Mega: вторая целевая плата

8 [env: megaatmega2560]

9platform = atmelavr

10board = megaatmega2560

11framework = arduino

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

 Копировать 

1

2pio run -e uno

3pio run -e megaatmega2560

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

Примеры подходов к сборке

Два других упомянутых варианта требуют, чтобы вы создали примеры эскизов, чтобы продемонстрировать, как использовать вашу библиотеку. Вы должны поместить эти примеры в папку examples , как определено в спецификации библиотеки Arduino. Когда вы запустите сценарий, он просмотрит все ваши примеры и скомпилирует их для всех перечисленных платформ. Когда Arduino IDE создает ваши примеры, она также создает вашу библиотеку.Это их зависимость.

Хотя написание примеров необязательно, в каждой хорошо поддерживаемой библиотеке должен быть хотя бы один. А когда вы это сделаете, подготовка вашего кода для работы со сценариями сборки займет считанные минуты. Я не вижу причин не использовать его!

Написание модульных тестов

Модульные тесты исследуют код вашей библиотеки изолированно. Иногда их сложнее написать, но плюсы преобладают над минусами.

Я нашел несколько сторонних фреймворков для тестирования для использования с Arduino:

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

Для ObjectButton я выбрал ArduinoCI в качестве среды тестирования. Но здесь нельзя сделать неправильный выбор. Просмотрите все варианты и решите, что лучше для вас.

Почему ArduinoCI?

ArduinoCI дает хороший обзор фреймворков для тестирования. Что привлекло мое внимание, так это то, что он поддерживает насмешку над оборудованием Arduino. Представьте, что вы хотите имитировать действие нажатия кнопки. Как это сделать, не нажимая физическую кнопку? Если у вас есть контроль над входными контактами и внутренним таймером, вы можете имитировать жест программно.И это то, что ArduinoCI предоставляет в наше распоряжение.

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

Модульные тесты ArduinoCI, выполняемые Travis CI

Test Features

Все ваши тестовые файлы помещаются в папку test . Вы обычно называете их протестированной функцией. ObjectButton должен уметь распознавать три типа различных действий.

Я создал три тестовых файла для их представления и один тестовый файл для проверки основных функций:

  • action_click.cpp для запуска всех модульных тестов, связанных с нажатием кнопки
  • action_double_click.cpp для запуска модульных тестов, связанных с двойным щелчком
  • action_press_release.cpp для запуска модульных тестов, связанных с нажатием кнопки, длительным нажатием и т. д.
  • sanity_checks.cpp для проверки таких функций, как getId , чтобы вернуть правильный идентификатор кнопки

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

Написание модульного теста

Давайте проведем тест, который проверяет, правильно ли наша библиотека определяет действие щелчка.

Чтобы написать тест для действия щелчка, сначала нужно понять, что такое щелчок , . Вы когда-нибудь думали об этом? Обычно, когда мы пользуемся вещами, мы не рассуждаем, как именно они работают, но когда мы хотим протестировать действие клика, мы должны определить, что такое клик и как он будет обнаруживаться.Затем мы можем написать тест, чтобы зафиксировать поведение.

Определение: Щелчок — это действие, которое происходит, когда пользователь нажимает кнопку, а затем отпускает ее. Нажатие и отпускание должно произойти до истечения тайм-аута щелчка, а также кнопку нельзя снова касаться в течение этого времени (возможно, это будет действие двойного щелчка).

Теперь, когда мы определили, что такое щелчок, мы можем имитировать щелчок в нашем тесте:

  • Пользователь нажимает кнопку
  • Пользователь отпускает кнопку
  • Нажатие и отпускание кнопки произошло в течение определенного времени ожидания нажатий

Ниже приведен реальный пример из моего набора тестов:

 Копировать 

1unittest (receive_on_click_event_after_default_click_ticks) {

2 ListenerMock testMock = ListenerMock (INPUT_PIN, true);

3 GodmodeState * state = GODMODE ();

4

5

6 state-> digitalPin [INPUT_PIN] = LOW;

7 testMock.getButton (). Tick ();

8

9

10 состояние-> микросхемы = (DEFAULT_DEBOUNCE_TICKS_MS + 1) * 1000;

11 testMock.getButton (). Tick ();

12

13

14 состояние-> digitalPin [INPUT_PIN] = HIGH;

15 состояний-> микросхемы = (DEFAULT_CLICK_TICKS_MS + 1) * 1000;

16 testMock.getButton (). Tick ();

17

18

19 testMock.getButton (). Tick ();

20

21

22 assertEqual (1, testMock.getPressEventsReceivedCount ());

23 assertEqual (1, testMock.getReleaseEventsReceivedCount ());

24 assertEqual (1, testMock.getClickEventsReceivedCount ());

25 assertEqual (0, testMock.getDoubleClickEventsReceivedCount ());

26 assertEqual (0, testMock.getLongPressStartEventsReceivedCount ());

27 assertEqual (0, testMock.getLongPressEndEventReceivedCount ());

28}

Состояние: пользователь нажал кнопку
В этом случае мы считаем кнопку нажатой, когда уровень напряжения на входном контакте равен 0 вольт.Благодаря ArduinoCI мы можем легко смоделировать это:

 Копировать 

1state-> digitalPin [INPUT_PIN] = LOW;

состояние — это указатель на структуру ArduinoCI, которая управляет «виртуальным» Arduino, выполняющим наш тест.

Итак, мы нажали кнопку, и нам нужно отпустить ее через определенное время. Если бы мы просто изменили напряжение на входном контакте, внутренний таймер все равно вернул бы то же значение — 0. Практически не прошло времени. Чтобы смоделировать время, нам нужно установить микрона, i.e .:

 Копировать 

1state-> micros = (DEFAULT_DEBOUNCE_TICKS_MS + 1) * 1000;

Обратите внимание, что вы управляете таймером, задавая время в микросекундах, а не в миллисекундах (как используется функцией millis () ).

Состояние: пользователь отпустил кнопку
Пользователь некоторое время удерживает кнопку; пора выпустить его.

 Копировать 

1state-> digitalPin [INPUT_PIN] = HIGH;

2state-> micros = (DEFAULT_CLICK_TICKS_MS + 1) * 1000;

Мы только что установили входной контакт на 3.3 Вольта, что имитирует отпускание кнопки. При необходимости вы также можете сместить внутренний таймер, чтобы смоделировать реальный сценарий.

Обнаружила ли библиотека действие щелчка?
Наш модульный тест точно имитирует действие щелчка. Если наша библиотека работает правильно, она уведомит наш OnClickListener об этом действии. Обычно мы проверяем результаты в конце теста:

 Копировать 

1assertEqual (1, testMock.getClickEventsReceivedCount ());

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

Доверие

Почему полезно иметь модульные тесты? Это уверенность в том, что мой код делает то, что должен. Как и в предыдущем тестовом примере, вы пишете другие тесты.Они должны фиксировать ожидаемое поведение — то, что вы указали, что ваша библиотека должна делать — и проверять, что ваша библиотека делает это.

Написание тестов занимает много времени, а их выполнение — нет. Вы можете повторно запустить набор тестов в течение нескольких секунд. Это особенно полезно, когда вы работаете над новой функцией и хотите убедиться, что вы не повлияли на существующие функции, вызвав ошибку. В случае ObjectButton, вся внутренняя логика выполняется в конечном автомате. Те из вас, кто реализовал государственную машину, знают, насколько это может быть утомительно.Иногда на первый взгляд непонятно, что делает код. Если вы измените реализацию конечного автомата, высока вероятность появления ошибки. Но если у вас есть рабочий набор тестов, вы можете убедиться, что все по-прежнему работает, и вы всегда можете быть уверены в своем коде.

Новая функция == новые тесты

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

Заключение

В этом посте я написал о необходимых действиях для создания качественных библиотек Arduino. Эти принципы применимы и к вашим проектам. Вы все равно должны задокументировать, что делает ваш код, и хотя бы подумать о написании некоторых тестов. Вы можете запустить набор тестов из командной строки.Веб-разработчики знакомы с рабочим процессом.

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

Если у вас есть вопросы или у вас есть ощущение, что я что-то забыл, дайте мне знать в комментариях. Спасибо!

Arduino * IDE Tutorial

Требуемое оборудование:

  • Плата Intel® Galileo
  • Блок питания (входит в комплект)
  • Кабель Micro USB (тип B)
  • Установлено и настроено программное обеспечение Arduino * версии 1.5.3

Пример эскиза

Когда вы создаете файл в программном обеспечении Arduino *, он открывает эскиз с базовой компоновкой программы Arduino. Вот пользовательский интерфейс:

Слева направо значки в верхней части пользовательского интерфейса Arduino представляют следующее:
Verify compiles code. Используйте для проверки кода на наличие ошибок перед загрузкой скетча.
Загрузить эскиз.
Новое окно редактора открывает новое окно редактирования кода вместо текущего.
Открывает файл.
Сохраняет эскиз.
Serial Monitor открывает последовательный монитор, полезный для отладки.
Стрелка вниз дает вам такие опции, как добавление эскиза в текущий проект. Он открывается как новая вкладка в текущем редакторе кода, что полезно для организации кода в логические файлы.

Число в нижнем левом углу пользовательского интерфейса Arduino указывает номер строки, в которой находится курсор.

Примечание Изображение представляет интерфейс программного обеспечения Arduino под названием BareMinimum и находится в Файл> Примеры> 0.1 Основы . Ознакомьтесь с другими примерами и поэкспериментируйте.

Комментарии

Две косые черты (между {и}) представляют собой начало встроенного комментария кода. Когда ваш код загружается на плату, компилятор игнорирует текст после двух косых черт. Использование встроенного комментария к коду позволяет вам оставлять заметки для себя и для людей, читающих ваш код. Вы также можете писать многострочные комментарии, начав свой комментарий с / * и закончив * /.

/ * Вы читаете
пример комментария
, который состоит из многих строк. * /

Переменные

Передача данных по программе может быстро стать беспорядочной. Переменные похожи на контейнеры для хранения, в которых хранятся разные типы значений. Использование переменных для передачи значений — отличный способ сохранить ваш код организованным и читаемым.

При объявлении переменной (введении ее в программу) важен выбор правильного типа данных.Если вы пытаетесь измерить интенсивность света с помощью фотометра, вам может потребоваться точное показание. Объявление типа переменной double резервирует место в памяти для числа с десятичной точкой.

Пример: double light_sensitivity;

Где double — это тип объявляемой переменной, а light_sensitivity — это имя переменной. Чтобы ссылаться на переменную в вашем коде, просто используйте то имя, которое вы ей дали.

Примечание Выберите имя переменной, соответствующее тому, на что вы ссылаетесь.Если имя состоит из более чем одного слова, используйте символ подчеркивания (_) между словами, чтобы повысить удобочитаемость.

Не забудьте проверить написание слов, которые вы выбираете. Один неверный символ может привести к некорректной компиляции вашей программы.

Для получения дополнительной информации о типах данных и переменных посетите справочную страницу Arduino.

Функции

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

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

Функция цикла — это сердце вашей программы. Он выполняет то, что следует из названия, непрерывно зацикливается, выполняя основную логику вашей программы.

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


Введение
Начало работы
Arduino IDE
Hello World
Заключение

Основы эскизов Arduino и Embedded C

Предыдущее руководство включало обсуждение инструментов и компонентов, необходимых для начала работы с Arduino . Однако, прежде чем начинать работу с Arduino UNO (или любой другой платой Arduino) — и экспериментировать с проектами оборудования на различных датчиках, исполнительных механизмах и модулях — важно ознакомиться с основами эскизов Arduino и встроенного C для кодирования, совместимого с Arduino.

Термин «Arduino-совместимое кодирование» относится ко всем Arduino и Arduino-совместимым платам микроконтроллеров, которые могут быть запрограммированы и загружены с помощью Arduino IDE.

Платы

Arduino запрограммированы на «C». C — популярный язык системного программирования, который имеет минимальное время выполнения на оборудовании по сравнению с другими языками программирования высокого уровня. По этой причине большинство операционных систем и несколько языков программирования построены на C.

.

Как и другие микроконтроллеры, микроконтроллеры AVR, размещенные на платах Arduino, запрограммированы в подмножестве C.Общий термин для таких подмножеств — «Встроенный C», потому что они применяются к программированию встроенных контроллеров. Язык, на котором программируется Arduino, является подмножеством C и включает только те функции стандарта C, которые поддерживаются IDE Arduino.

Это не означает, что Arduino C где-то отстает, потому что это подмножество C. Большинство недостающих функций стандартного C можно легко обойти. Скорее, Arduino C — это гибрид C и C ++, что означает, что он функциональный и объектно-ориентированный.

Структура скетчей
По сути, пустой скетч Arduino выполняет две функции:

1. Настройка ()
2. Петля ()

Когда скетч Arduino начинает выполняться, сначала вызывается функция setup (). Он выполняется только один раз и должен использоваться для инициализации переменных, установки pinModes, настройки компонентов оборудования, использования библиотек и т. Д.

Функция loop () находится рядом с функцией setup () и повторяется бесконечно.Любые другие пользовательские функции должны вызываться внутри функции цикла. Вот как микроконтроллеры выполняют свой код прошивки, повторяя свой код бесконечное количество раз, пока они остаются включенными.

Если пользователи запрограммировали другие микроконтроллеры (например, 8051, AVR, PIC или RX), можно сравнить код внутри функции setup () с кодом вне цикла main () встроенной программы на C, что может были написаны для инициализации переменных и настройки оборудования.Функции setup () и loop () имеют возвращаемые типы void.

Программа для микроконтроллера должна иметь такую ​​же структуру, как и ее функции. Микроконтроллер должен «знать» о своей аппаратной среде и знать, как с ней взаимодействовать.

Микроконтроллер может взаимодействовать с другими аппаратными компонентами или устройствами только этими пятью способами:

1. Цифровой вход. Это может быть получено в цифровом формате LOW или HIGH от других устройств. Это будут логические уровни TTL или напряжения, преобразованные в логические уровни TTL перед подачей на GPIO.
2. Цифровой выход. Цифровой выход может быть НИЗКИМ или ВЫСОКИМ по сравнению с другими устройствами. Опять же, на выходе будут логические уровни TTL.
3. Аналоговый вход. Может «определять» аналоговое напряжение от других устройств. Измеренное напряжение преобразуется в цифровое значение с помощью встроенного аналого-цифрового преобразователя.
4. Аналоговый выход. Может выводить аналоговое напряжение на другие устройства. Этот аналоговый выход представляет собой не аналоговое напряжение, а сигнал ШИМ, который приближается к аналоговым уровням напряжения.
5. Последовательная связь. Он может передавать, получать или принимать данные с другими устройствами последовательно в соответствии со стандартным протоколом последовательной передачи данных, таким как UART, USART, I2C, SPI, микропровод, 1-провод, CAN и т. Д. Последовательная связь с другими устройствами может быть одноранговым (UART / USART), полудуплексным (I2C) или полнодуплексным (SPI).

Пользователи, которые знают, как выполнять эти пять типов взаимодействия с микроконтроллером, могут сопрягать с ним любое оборудование.

Программа Arduino или любая программа микроконтроллера должна сначала иметь код для инициализации.Это может включать:

  • Определение переменных и констант
  • Настройка pinModes
  • Настройка каналов АЦП / ШИМ
  • Инициализация настроек для последовательной связи

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

В отличие от настольных приложений, программы µc не предназначены для завершения работы. Эти программы повторяются бесконечное количество раз, пока система не будет выключена или пока не произойдет сбой. После отключения питания Arduino или любой микроконтроллер перезагружается при возобновлении питания и начинает выполнение своей программы с самого начала.

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

1. Инициализация
2. Вход — он должен включать код для проверки данных и обработки неверных или неожиданных входящих данных
3. Обработка — это должен включать код для непредвиденных сбоев или исключений, возникающих при обработке данных
4. Выход — это может включать код для проверки ожидаемых результатов, если сопряженное устройство также может обмениваться данными с микроконтроллером

Комментарии
Комментарии в Arduino C аналогичны комментариям в стандарте C.Однострочные комментарии начинаются с пары косых черт (//) и заканчиваются в конце строки (EOL). Многострочные комментарии начинаются с пары косая черта и звездочка (/ *) и заканчиваются парой звездочка-косая черта (* /).

Это примеры однострочных и многострочных комментариев:

// Это однострочный комментарий
/ * Это
равно
a
многострочный
комментарий * /

Типы данных Arduino C
Эти типы данных поддерживаются в Arduino C.

Стоит отметить, что «строковые» и «строковые объекты» — разные вещи. Тип данных строки определяет простой массив символов, а тип данных строки определяет объект строки.

Arduino C поддерживает следующие встроенные функции для управления строковыми объектами:

Идентификаторы
Идентификаторы — это имена, присвоенные переменным, функциям, константам, классам, методам и другим объектам в программе. В Arduino C идентификаторы должны содержать только буквенно-цифровые символы, тире (-) или подчеркивание (_).Идентификатор может начинаться только с символа подчеркивания или буквы.

Ключевые слова
Ключевые слова — это константы, переменные или имена функций, которые нельзя использовать в качестве идентификаторов.

Arduino C имеет следующие ключевые слова:

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

Например, в Arduino C каждая переменная должна быть явно определена с указанным типом данных, прежде чем она будет использована в коде.

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

Ячейка памяти, в которой значение переменной сохраняется во время выполнения, называется ее «lvalue» или значением местоположения. Значение, хранящееся в ячейке памяти переменной, называется ее «rvalue» или значением регистра.

Определенная переменная имеет lvalue, но не rvalue. Объявленная переменная имеет lvalue и rvalue.

Это допустимое определение переменной:
int num1;

Это действительное объявление переменной:
int num1 = 0;

Или…
int num1;
число1 = 0;

Константы
Константы — это ссылки в программе, значение которых не изменяется во время выполнения программы.), побитовое не (~), левый битовый сдвиг (<<) и правый битовый сдвиг (>>)

5. Логическое — и (&&), или (||), а не (!)

6. Соединение — приращение (++), декремент (-), сложное сложение (+ =), составное вычитание (- =), составное умножение (* =), составное деление (/ =), составное побитовое и (& =), и составное побитовое или (| =)

7. Cast — Эти операторы переводят текущий тип переменной в другой тип. Приведение типов можно применить к переменной, указав новый тип данных в скобках перед появлением переменной.

Например:
i = (int) f

8. sizeof — Оператор sizeof возвращает размер массива в байтах.

9. Тройной (:?)

10. Указатель — оператор разыменования (*) и оператор ссылки (&)

Операторы и блоки операторов
Оператор — это полная инструкция C для процессора. Все операторы C заканчиваются точкой с запятой (;).Блок операторов — это группа операторов, заключенная в фигурные скобки ({,}). Блок операторов также рассматривается компилятором как единый оператор.

Приоритет операторов
В этой таблице показан приоритет операторов в Arduino C в порядке убывания:

Управляющие структуры
Arduino C поддерживает следующие управляющие структуры:

  • если
  • если… иначе…
  • для
  • корпус переключателя
  • , а
  • делать… пока…
  • перерыв
  • продолжить
  • перейти
  • возврат

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

Определение функции имеет следующий синтаксис:
тип_функции имя_функции (аргументы) {
тело_функции
}

Тип функции может быть любым типом данных, включая void. Ожидается, что функция вернет значение того же типа через оператор return. Этот оператор должен быть последним в теле функции (любые операторы, сделанные после оператора return, не будут выполнены).

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

Тело функции — это блок операторов. Этот блок операторов выполняется всякий раз, когда вызывается функция.

Это действительный пример пользовательской функции C:
int add_inputs (int a, int b, int c) {
return a + b + c;
}

Функция вызывается по имени, за которым следует скобка.Любые позиционные аргументы должны быть заключены в круглые скобки.

Это действительный пример вызова функции:
add_inputs (5, 2, 14)

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

Область видимости переменной
Область видимости переменной относится к видимости и времени жизни переменной в программе.Например, переменные:

  • Только видимая внутри функции имеет локальную область видимости.
  • Видны все функции программы глобального масштаба.

Переменная, имеющая глобальную область видимости, должна быть определена или объявлена ​​вне любой функции, включая функции setup () и loop ().