---
title: "📟 Начало работы с Arduino"
date: 2023-02-24T16:46:09+03:00
draft: true
tags: [arduino, tips]
---

⚠️ Статья носит информационный характер и находится в процессе написания.  
Есть вероятность, что через некоторое время, части текста не будет.

## Цифровые и аналоговые пины и их назначение

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

1. **Power Pins** — порты питания, режим их работы нельзя запрограммировать или изменить.  
Они выдают стабилизированное напряжение 5V или 3,3V,  
`Vin` выдает напряжение от источника питания,  
а `GND` — это заземление (общий минус)
2. **PWM Pins** — порты с ШИМ модуляцией, которые можно запрограммировать,  
как цифровой выход/вход. Данные порты обозначены на плате знаком тильда (˜)
3. **Analog In** — порты, принимающие аналоговый сигнал от датчиков, работают на вход.  
Данные порты тоже можно запрограммировать, как цифровой вход/выход.  
Данные пины не поддерживают ШИМ модуляцию.

Режим пинов назначается в процедуре `void setup` с помощью `pinMode()`, например:

```c
void setup() {
    pinMode(10, OUTPUT); // объявляем пин 10 как выход
    pinMode(A2, OUTPUT); // объявляем пин A2 как выход

    pinMode(12, INPUT); // объявляем пин 12 как вход
    pinMode(A1, INPUT); // объявляем пин A1 как вход
}
```

**Пояснения к коду:**
1. К выходу `10` и `A2` можно подключить светодиод,
который будет включаться и выключаться при вызове команды в программе.
2. Пин `10` может использоваться для ШИМ сигнала,
например, чтобы плавно включить светодиод, 
а пин `A2` может выдавать только цифровой сигнал (`0` или `1`)
3. К входу `12` и `A1` можно подключить цифровой датчик
и микроконтроллер будет проверять наличие сигнала на этих пинах
(логический нуль или единицу)
4. К входу `A1` можно подключить аналоговый датчик,
тогда микроконтроллер будет получать не только сигнал,
но и узнавать характеристику сигнала.

ℹ️ **Для справки:**

Пины не случайно разделены на пины с ШИМ модуляцией (_PWM Pins_) и аналоговые.  
**PWM** пины создают аналоговый сигнал, к ним подключают сервопривод,
шаговый двигатель и другие устройства,
где требуется подавать сигнал с разными характеристиками.  
**Аналоговые пины** (_Analog In_) используются для подключения аналоговых датчиков,
с них входящий сигнал преобразуется в цифровой с помощью встроенного АЦП.

### Использование аналоговых пинов как цифровые

При подключении большого количества устройств к плате
пинов общего назначения может не хватить.  
Тогда в скетче можно указыть, что необходимо использовать аналоговые пины как цифровые.  
Также можно использовать не буквенное, а цифровое обозначение выходов,
т.е. `A0` — это 14 пин, `A1` — это 15 пин и т.д. (работает только на Uno или Nano).

В следующем примере две строчки имеют одинаковое значение.

```c
void setup() {
    pinMode(A2, OUTPUT); // объявляем пин A2, как цифровой выход
    pinMode(16, OUTPUT); // объявляем пин 16, как цифровой выход
}
```

### Назначение пинов SDA, SCL

Данные пины используются для приема/передачи информации по протоколу **I2C**.
Например, при подключении жк дисплея с модулем I2C или GPS модуля.
С помощью специальной библиотеки микроконтроллер может обмениваться информацией
с подключенным периферийным устройством, поддерживающим данный протокол.
На Ардуино Mega, в отличии от Uno и Nano, имеется целых три пары пинов SDA и SCL.

### Назначение пинов TX, RX

Пины TX/RX также используются для коммуникации,
но уже по протоколу **UART**.  
На платах Uni и Nano пины TX/RX подключены параллельно USB разъему для связи с компьютером.
Поэтому, если подключиться к данным портам устройство, например, Bluetooth модуль,
то загрузить в Ардуино скетч не получится, так как плата автоматически переключится
на чтение данных с устройства, а не с компьютера.

## Функции `void loop()` и `void setup()`

Функции `void loop()` и `void setup()` — это первое с чем сталкивается любой,
кто начинает знакомство с языком программирования микроконтроллеров Ардуино.

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

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

Если пользователь не начал прошивку,
то контроллер начинает выполнять ранее загруженный скетч.

Оба цикла вызываются встроенной функцией `main()` из файла `main.cpp`.  
При этом функция `void setup()` вызывается один раз,
а и `void loop()` вызывается в цикле `for` бесконечное количество раз.

Если в скетче будут присутствовать более одной функции
`void setup()` или `void loop()`, то при компиляции Aduino IDE выдаст ошибку:
`redefinition of ‘void setup()‘` или `redefinition of ‘void loop()’` соответственно.

### Функция `void setup()`

```c
void setup() {
    // код должен располагаться между фигурных скобок
}
```

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

Процедура `void setup()` вызывается один раз
и её используют для назначения режима работы пинов или команд,
которые необходимо выполнить только в момент загрузки программы.

### `void loop()`

После выполнения цикла `setup`, программа переходит в цикл `loop`,
который будет повторяться до тех пор, пока на плату подано питание.

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

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

Функция `loop*()` имеет следующую конструкцию:

```c
void loop() {
    // основной код программы располагается здесь
}
```

Таким образом, если необходимо при запуске программы
включить светодиод один раз (для индикации работы устройства)
на микроконтроллере Arduino Nano,
то команду лучше написать в цикле `void setup()`.

Если в программе необходимо выполнять какое-то действие постоянно, например,
получать данные с ультразвукового дальномера **HC-SR04**,
то команду следует располагать в цикле `void loop()`.

### Функция `pinMode()`

Функция `pinMode()` устанавливает режим работы заданного пина, как входа или выхода.

Цифровой пин может находиться в двух состояниях.
В режиме входа пин считывает напряжение от 0 до 5 Вольт,
а в режиме выхода – выдавать на пине такое же напряжение.

Режим работы пина микроконтроллера выбирается при помощи функции `pinMode(pin, mode)`,
где `pin` это номер пина, а `mode` это режим.

### pinMode OUTPUT

**OUTPUT** (порт работает как выход) — пин становится управляемым источником питания
с максимальным током 40 мА.

В зависимости от команды `digitalWrite()` пин принимает значение единицы или нуля.  
Пример: `pinMode(10, OUTPUT)`.

### Функция `digitalWrite()` и `analogWrite()`

Цифровой пин может генерировать цифровой сигнал с помощью команды `digitalWrite()`,
т.е. выдавать напряжение 5 Вольт.

Цифровой сигнал может иметь два значения — `0` или `1` (0 Вольт или 5 Вольт).

Если в программе используется команда `analogWrite()` для ШИМ портов платы,
то микроконтроллер может генерировать сигнал **PWM** на портах —
создавать имитацию аналогового сигнала.

```c
void setup() {
    pinMode(10, OUTPUT);
}

void loop() {
    digitalWrite(10, HIGH);
    delay(250);
    digitalWrite(10, LOW);
    delay(250);
}
```

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

### pinMode INPUT

**INPUT** (порт работает как вход) —
пин в этом режиме считывает данные с аналоговых и цифровых датчиков, состояния кнопок.
Порт находится в высокоимпедансном состоянии,
т.е. у пина высокое сопротивление.  
Пример: `pinMode(10, INPUT)`.

### Функция `digitalRead()` и `analogRead()`

Arduino может определить наличие напряжения на пине через функцию `digitalRead()`,
которая возвращает `0` (`LOW`) или `1` (`HIGH`).

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

Используя функцию `analogRead()`, можно прочитать напряжение с аналогового датчика,
функция возвращает число от `0` до `1023`.

```c
void setup() {
    pinMode(10, INPUT);
    Serial.begin(9600);
}

void loop() {
    int data = digitalRead(10);
    Serial.println(data);
    delay(250);
}
```

Нельзя подавать на вход микроконтроллера напряжение выше напряжения питания платы.

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

### pinMode INPUT_PULLUP

**INPUT_PULLUP** (порт работает как вход)
но к пину подключается резистор в 20 кОм.

В этом режиме при подключении кнопки к Ардуино можно не использовать
внешние подтягивающие резисторы.  
Пример: `pinMode(10, INPUT_PULLUP)`.

## Директива `#define`

Директива `#define` позволяет задавать имена значениям (константам),
которые делают скетч более понятным.

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

Рассмотрим на примерах с описанием правильные варианты использования директивы `#define`.

Константы, которые определены через директиву `#define`,
не занимают места в памяти микроконтроллера,
так как значение подставляет значения вместо имен при компиляции скетча.

Работу директивы можно сравнить с операцией `НАЙТИ` и `ЗАМЕНИТЬ` в текстовом редакторе.

При компиляции скетча компилятор находит в программе часть кода `<что меняем>`
и заменяет ее на кусок кода `<на что меняем>`.

**Синтаксис директивы:**

```c
#define <что меняем> <на что меняем>
```

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

ℹ️ **Обрати внимание:**

В конце строчки не ставится точка с запятой и знак равенства,
как это делается при объявлении переменной.

```c
#define RED 11 // присваиваем имя RED для пина 11
#define GRN 12 // присваиваем имя GRN для пина 12
#define BLU 13 // присваиваем имя BLU для пина 13
 
void setup() {
    pinMode(RED, OUTPUT); // используем PIN 11 для вывода
    pinMode(GRN, OUTPUT); // используем PIN 12 для вывода
    pinMode(BLU, OUTPUT); // используем PIN 13 для вывода
}
```

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

А программа автоматически будет заменять имена `RED`, `GRN`, `BLU`
на соответствующие значения при компиляции.

### Директивы `#ifdef`, `#ifndef` и `#endif` в скетче

Инструкция `#ifdef` проверят, было ли встречено в программе данное определение ранее,
если было, то ставится блок кода с последующей строки и до `#endif`.

В примере проверяется был ли ранее в `#define` определен признак отладки,
если да, то код (вывод сообщения на монитор порта) будет выполнен,
если признак не определен, то сообщение на мониторе выводиться не будет.

```c
#ifdef DEBUG
   Serial.println("Debug message: ...");
#endif
```

Инструкция `#ifndef` проверят, было ли встречено в программе данное определение ранее
и если не было, то вставится блок кода с последующей строки и до `#endif`.

В следующем простом примере объявляется новая константа,
если только ранее её не объявляли в скетче.

Если дефайн с таким именем уже использовался,
то программа проигнорирует строчки внутри конструкции `#ifndef … #endif`.

```c
#ifndef RED
    #define RED 11
#endif
```

### Замена функций с помощью `#define`

Кроме использования дефайн в программе для объявления констант,
можно заменять целые фрагменты кода с помощью директивы `#define`.

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

Например, можно в первом примере заменить функцию `pinMode()`
на конструкцию с заданными параметрами.

```c
#define out(pin) pinMode(pin, OUTPUT)
 
void setup() {
    out(11);
    out(12);
    out(13);
}
```

В примере функция `pinMode()` закодирована одним словом `out`.

Теперь, где в скетче встретится слово `out`,
компилятор подставит строку `pinMode(pin, OUTPUT)` с заданным параметром `pin`.

Таким же образом можно заменить команды `digitalWrite()` и `delay()`.

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

```c
#define out(pin) pinMode(pin, OUTPUT)
#define on(pin, del) digitalWrite(pin, HIGH); delay(del)
#define off(pin, del) digitalWrite(pin, LOW); delay(del)
 
void setup() {
    out(11);
    out(12);
    out(13);
}

void loop() {
    on(11, 500);
    off(11, 500);
    on(12, 500);
    off(12, 500);
    on(13, 500);
    off(13, 500);
}
```

## `define` или `const`, что выбрать?

Иногда бывает не удобно применять директиву `#define` для создания констант,
в этом случае используют ключевое слово `const`.

В отличие от глобальных переменных, значение `const` должно быть определено сразу
при объявлении константы.

Необходимо помнить, что при использовании `#define` имена следует делать
максимально уникальными, чтобы не было совпадений с командами из подключаемых библиотек.

```c
const int RED = 11; // присваиваем имя RED для пина 11
const int GRN = 12; // присваиваем имя GRN для пина 12
const int BLU = 13; // присваиваем имя BLU для пина 13
 
void setup() {
    pinMode(RED, OUTPUT); // используем PIN 11 для вывода
    pinMode(GRN, OUTPUT); // используем PIN 12 для вывода
    pinMode(BLU, OUTPUT); // используем PIN 13 для вывода
}
```

ℹ️ **Для справки:**

Использование `#define` или `const` не дает никаких преимуществ,
с точки зрения экономии объема памяти микроконтроллера.

## Задержки, `delay()` и `millis()`

### `delayMicroseconds()`

Функция `delayMicroseconds()` останавливает выполнение программы
на заданное количество микросекунд (**в 1 секунде 1 000 000 микросекунд**).

При необходимости задержки в программе более чем на несколько тысяч микросекунд
рекомендуется использовать `delay()`.

### `delay()`

Функция `delay()` останавливает выполнение программы на заданное количество миллисекунд
(**в 1 секунде 1 000 миллисекунд**).

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

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

### `millis()`

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

Счетчик времени сбрасывается на ноль при переполнении значения `unsigned long`
(**приблизительно через 50 дней**).

Функция `millis()` позволяет сделать многозадачность в ардуинке,
так как выполнение программы не останавливается
и можно выполнять параллельно другие операции в скетче.

```c
unsigned long timer = 0;
bool led_state = HIGH;

void setup() {
    pinMode(LED_BUILTIN, OUTPUT);
    Serial.begin(9600);
}
 
void loop() {
    if (millis() - timer > 200) {
      led_state =! led_state;
      digitalWrite(LED_BUILTIN, led_state);

      timer = millis();
    }

    // выводим количество миллисекунд прошедших с момента начала программы
    Serial.print("Time: ");
    Serial.println(timer);
}
```

## `tone()`

```text
// FIXIT: https://xn--18-6kcdusowgbt1a4b.xn--p1ai/tone-arduino/
```

## `map()`

```text
// FIXIT: https://xn--18-6kcdusowgbt1a4b.xn--p1ai/map-arduino/
```

## `random()`

```text
// FIXIT: https://xn--18-6kcdusowgbt1a4b.xn--p1ai/random-arduino/
```

## Serial Monitor

```text
// FIXIT: https://xn--18-6kcdusowgbt1a4b.xn--p1ai/%d0%bc%d0%be%d0%bd%d0%b8%d1%82%d0%be%d1%80-%d0%bf%d0%be%d1%80%d1%82%d0%b0-%d0%b0%d1%80%d0%b4%d1%83%d0%b8%d0%bd%d0%be/
```

## Используемые материалы:
1. [Уроки по Arduino](https://xn--18-6kcdusowgbt1a4b.xn--p1ai/%d0%bf%d0%b8%d0%bd%d1%8b-%d0%b0%d1%80%d0%b4%d1%83%d0%b8%d0%bd%d0%be/) | робототехника18.рф
