arduino learning
This commit is contained in:
parent
1e68534290
commit
0cbe9fce12
500
content/posts/2023/arduino/start.md
Normal file
500
content/posts/2023/arduino/start.md
Normal file
@ -0,0 +1,500 @@
|
||||
---
|
||||
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.рф
|
Loading…
Reference in New Issue
Block a user