501 lines
25 KiB
Markdown
501 lines
25 KiB
Markdown
---
|
||
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.рф
|