Blog/content/posts/2023/arduino/start.md
2023-03-10 21:15:56 +03:00

501 lines
25 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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.рф