Путь к пониманию микроконтроллеров: на основе Arduino
Путь к пониманию микроконтроллеров: на основе Arduino

Полная версия

Путь к пониманию микроконтроллеров: на основе Arduino

Язык: Русский
Год издания: 2026
Добавлена:
Настройки чтения
Размер шрифта
Высота строк
Поля
На страницу:
2 из 3

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

Но в этом варианте всё еще есть лишняя часть в виде переменной, поэтому следующим шагом нам нужно уйти от переменной state.

Уход от переменной, при сохранении общего вида кода возможен благодаря функции digitalRead ().

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

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

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

#define pinLed 13

int delTime = 300;

void setup () {

pinMode (pinLed, 1);

}

void loop () {

digitalWrite (pinLed,!digitalRead (pinLed));

delay (delTime);

}

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

Это все возможные вариации на Wiring для решения этой задачи.

*** Если вы впервые знакомитесь с Arduino и впервые читаете книгу — рекомендую пропустить объяснение ниже и перейти сразу к окончанию этого подраздела — к «задачи для закрепления материала», а к тексту ниже вернуться после изучения 1 — 3 разделов.


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

Тогда используя регистры мигание светодиодом будет выглядеть по-другому:

int delTime = 300;

void setup () {

DDRB |= (1 <

}

void loop () {

PORTB |= (1 <

delay (delTime);

PORTB &= ~ (1 <

delay (delTime);

}

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

Решение является простым:

int delTime = 300;

void setup () {

DDRB |= (1 <

}

void loop () {

PORTB ^= (1 <

/* аналог digitalWrite (pinLed,!digitalRead (pinLed));

Оператор ^= выполняет побитовое исключающее ИЛИ (XOR). Если бит был 0, он станет 1; если был 1 — станет 0. Поэтому светодиод переключается между включением и выключением. */

delay (delTime);

}

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

void setup () {

DDRB |= (1 <

}

void loop () {

PORTB ^= (1 <

delay (300);

}

От Wiring уже осталось не так много и всматриваясь в код мы видим конструкции void setup () и void loop (), а так же функцию delay ().

От конструкций void setup () и void loop () мы уйти не можем, эти конструкции являются ядром языка Wiring и обязательно должны быть в скетче в единичном экземпляре, чтобы код скомпилировался.

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

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

#define F_CPU 16000000UL

#include

void setup () {

DDRB |= (1 <

}

void loop () {

PORTB ^= (1 <

_delay_ms (300);

}

Код выше мог бы выглядеть и проще:

#include

void setup () {

DDRB |= (1 <

}

void loop () {

PORTB ^= (1 <

_delay_ms (300);

}

Значение F_CPU в таком случае будет взято компилятором на основе стандартного кварцевого резонатора для выбранной платы (Мы всегда в Arduino IDE выбираем конкретную модель платы (например Arduino UNO) и номер COM — порта. Так для Arduino UNO стандартный кварцевый резонатор — 16 МГц).

Постараемся уйти и от void setup () и void loop (), тогда код мигания светодиодом будет выглядеть следующим образом:

#define F_CPU 16000000UL

#include

#include

int main (void) {

DDRB |= (1 <

while (1) {

PORTB ^= (1 <

_delay_ms (300);

}

}

Последний вариант — это прошивка на языке программирования С, для бесконечного (пока на микроконтроллер поступает питание) мигания светодиодом.

На этом моменте я вас поздравляю с первой прошивкой микроконтроллера на языке С!

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

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


Задачи для закрепления материала:

Задачи следует решать с использованием функций Wiring.

(Дополнительно) Решение задач с использованием регистров.

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

2. Постоянное одновременное мигание тремя светодиодами.

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

* Мигание сначала одним светодиодом, потом другим. Мигание каждым светодиодом осуществляется два раза за всю работу Arduino.

* Одновременное мигание двумя светодиодами. Мигание светодиодами осуществляется два раза за всю работу Arduino.


Что мы изучили в подразделе 1.2:

1. Принцип мигания светодиода;

2. Функцию delay () — задержка выполнения программы;

3. Назначение void loop ();

4. Логическое переключение состояния вывода;

5. Использование переменных для хранения состояния;

6. Функцию digitalRead ();

7. Основы оптимизации кода;

8. Переключение состояния вывода через (^=) в регистрах (подробнее в подразделе 3.3).

1.3 Мигание светодиода во время нажатия тактовой кнопки

Включение и выключение светодиода только при определенном условии

Одним из способов выполнять действия, пока соблюдается некоторое условие, является цикл while () {} (подробное описание цикла while () в подразделе 2.2.1).

while с английского переводится, как «пока» (некий процесс, во время). Как правильно прочитать строчку «while () {}»? Пока некоторое условие (событие) (в круглых скобках) происходит нужно делать следующее (то, что в фигурных скобках). В круглых скобках условие, а в фигурных скобках функционал.

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

Другая ситуация — микроконтроллер добрался до проверки условия, до круглых скобок цикла while и условие оказалось истиной, то есть условие выполняется. Тогда микроконтроллер входит в фигурные скобки и выполняет необходимый нам функционал — выполняет команды внутри фигурных скобок цикла while сверху вниз. После того, как достигает закрывающую фигурную скобку} возвращается к проверке условия — в круглые скобки. Алгоритм повторяется вновь, если условие не выполняется — микроконтроллер выходит из цикла и переходит на строчку, которая следует за закрывающей фигурной скобкой}. Если выполняется, то проходит строки в фигурных скобках и снова повторяется проверка условия в круглых скобках. Количество таких повторений ничем не ограничивается. Действия выполняются пока выполняется условие.

Необходимо подключить светодиод и тактовую кнопку. Мы уже выяснили, что вывод Arduino с подключенным светодиодом нужно назначить выходом (OUTPUT). А как быть с тактовой кнопкой?

Есть два способа подключения тактовой кнопки к Arduino. Один из них интуитивно понятен — подключение кнопки между питанием и цифровым выводом. Тактовая кнопка отличается от любой другой тем, что она не фиксируется в нажатом положении, стоит убрать с неё палец, как она возвращается в прежнее положение. Во время нажатия она замыкает свои выводы, тем самым соединяя электрическую цепь. Так один её вывод подключаем к 5V Arduino, а второй вывод к одному из цифровых выводов Arduino (от 2 до 13 включительно). Допустим, мы будем использовать 2 вывод, тогда в тот момент, когда мы нажмем на тактовую кнопку, 5В через кнопку поступит на цифровой вывод 2 и мы сможем на нем прочитать наличие высокого логического уровня напряжения. Если мы собираемся читать наличие напряжения, и так как напряжение поступает с вывода 5В на вывод 2 Arduino, то для микроконтроллера это входящая информация, а значит вывод надо назначить входом (INPUT).

Получаем следующий void setup:

void setup () {

pinMode (2, INPUT); // вывод с тактовой кнопкой — вход

pinMode (13, OUTPUT); // вывод с светодиодом — выход

}

Нам нужно проверить нажата ли кнопка, для этого нужно прочитать напряжение, для этого используется функция digitalRead (pin);, которая дословно переводится, как цифровое чтение, то есть мы собираемся прочитать логический ноль или логическую единицу с вывода. В круглых скобках необходимо указывать номер вывода (или переменная его содержащая), с которого мы хотим прочитать логический уровень напряжения. Функция позволяет прочитать, но никуда не сохраняет, поэтому результат нужно записать в переменную. Это как с часами на телефоне. Возможно у вас была следующая ситуация: мы достаем телефон из кармана, смотрим на время, и убираем его обратно, а время не помним, хоть только что и смотрели. Почему? Потому что необходимо увиденное время запомнить. Так и микроконтроллеру необходимо запомнить прочитанное время, а для этого нам нужно записать значение в переменную. Нужно определить тип данных этой переменной. Мы будем записывать либо HIGH, либо LOW, а значит мы можем выбрать тип данных логический — bool.

(Далее используется цикл while () — подробнее о цикле в подразделе 2.2.1 этой книги).

Тогда получаем следующую часть кода (внимание! код промежуточный):

void loop () {

bool button = digitalRead (2);

while (button == HIGH) {

digitalWrite (13, HIGH);

}

}

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

void loop () {

bool button = digitalRead (2);

while (button == HIGH) {

digitalWrite (13, HIGH);

}

digitalWrite (13, LOW);

}

Обратите внимание, что мы прочитали логический уровень напряжения над циклом, а после смотрим, равна ли переменная button — HIGH, вот только мы в нее после этого ничего не записываем, а значит скетч до сих пор будет работать полноценно только один раз. Чтобы исправить это нужно прочитать напряжение внутри цикла, чтобы при повторной проверке у переменной было актуальное значение.

void loop () {

bool button = digitalRead (2);

while (button == HIGH) {

digitalWrite (13, HIGH);

button = digitalRead (2);

}

digitalWrite (13, LOW);

}

Если мы отпустим кнопку быстро, то результат работы скетча мы не увидим, поэтому следует добавить задержку в цикле после подачи напряжения и void loop () будет выглядеть следующим образом:

void loop () {

bool button = digitalRead (2);

while (button == HIGH) {

digitalWrite (13, HIGH);

button = digitalRead (2);

delay (100);

}

digitalWrite (13, LOW);

}

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

void setup () {

pinMode (2, INPUT); // вывод с тактовой кнопкой — вход

pinMode (13, OUTPUT); // вывод с светодиодом — выход

}


void loop () {

while (digitalRead (2)) {

digitalWrite (13, HIGH);

delay (50);

}

digitalWrite (13, LOW);

}

Улучшить код можно введя переменные вместо номеров выводов (2 и 13), а так же поменяв INPUT на INPUT_PULLUP.

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

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

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

В микроконтроллере к каждому выводу относится свой встроенный резистор, который можно программно (через код) подключить к питанию, если вывод мы будем использовать, как вход. Для подключения встроенного резистора достаточно и необходимо вместо INPUT использовать INPUT_PULLUP. Следует учитывать, что при использовании INPUT_PULLUP логика инвертируется: нажатие будет соответствовать LOW, а отпускание HIGH.


Скетч с описанными улучшениями примет следующий вид:


#define button 2

#define led1 13


void setup () {

pinMode (button, INPUT_PULLUP); // вывод с тактовой кнопкой — вход

pinMode (led1, OUTPUT); // вывод с светодиодом — выход

}

void loop () {

while (digitalRead (button) ==LOW) {

digitalWrite (led1, HIGH);

delay (50);

}

digitalWrite (led1, LOW);

}

*** Если вы впервые знакомитесь с Arduino и впервые читаете книгу — рекомендую пропустить объяснение ниже и перейти сразу к окончанию этого подраздела — к «задачи для закрепления материала», а к тексту ниже вернуться после изучения 1 — 3 разделов.


Следующим шагом является применение регистров вместо функций языка Wiring, для этого используем уже знакомые нам регистры для установки состояния вывода DDR, включения и выключения напряжения PORT. Для чтения напряжения используется регистр PIN (подробнее в подразделе 3.6 этой книги). При использовании регистров скетч примет следующий вид:


void setup () {

DDRD &=~ (1 <<2); //вывод с тактовой кнопкой — вход

PORTD |= (1 <<2); // подключение подтягивающего резистора (pull-up)

DDRB |= (1 <<5); // вывод с светодиодом — выход

}

void loop () {

while (! (PIND & (1 <<2))) {//пока кнопка нажата

PORTB |= (1 <<5); // включение светодиода

delay (50);

}

PORTB &=~ (1 <<5); // выключение светодиода

}

Равноценной записью будет такой же скетч, но записью PD2 вместо 2 и PB5 вместо 5 для улучшения читаемости.


Задачи для закрепления материала:

Задачи следует решать с использованием функций Wiring.

(Дополнительно) Решение задач с использованием регистров.

1. Выключение светодиода пока нажата тактовая кнопка, включение светодиода пока кнопка не нажата.

2. Мигание светодиодом пока тактовая кнопка нажата.

3. Мигание светодиодом пока нажата одна из двух тактовых кнопок.

4. Мигание светодиодом пока нажаты обе тактовые кнопки.

5. Три тактовые кнопки и три светодиода. Пока нажатая первая тактовая кнопка — светится первый светодиод, пока нажата вторая тактовая кнопка — светится второй светодиод, пока нажата третья тактовая кнопка — светится третий светодиод.

6. Три тактовые кнопки и три светодиода. Пока нажата первая кнопка — три светодиода светятся. Пока нажата вторая кнопка — три светодиода мигают одновременно. Пока нажата третья кнопка — три светодиода мигают по очереди.


Что мы изучили в подразделе 1.3:

1. Работу тактовой кнопки;

2. Цикл while () (подробнее в подразделе 2.2.1);

3. Чтение состояния вывода через digitalRead ();

4. Логические условия (==, HIGH, LOW);

5. Режим INPUT_PULLUP;

6. Основы работы с кнопками и условиями.

1.4 Мигание светодиода с плавным изменением яркости

Плавное включение и выключение светодиода

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

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

Соответственно, мы можем видеть его в один момент выключенным, а в другой уже включенным на максимальной яркости. Реализовать плавное включение светодиода можно обеспечив подачу нужного напряжения в несколько этапов (на самом деле это происходит по-другому и обеспечивается широтно-импульсной модуляцией (ШИМ), но в качестве упрощения можно представлять процесс таким образом). То есть не сразу подавать 5 В, а сначала 1 В, потом 2 В, 3 В, 4 В и уже только после этого 5 В или с любым другим шагом изменения напряжения. В результате мы будем видеть, как светодиод плавно «разгорается» до полной яркости.

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

В Arduino для таких задач есть специальные выводы, только на них можно реализовать такую задачу. Это цифровые выводы с ШИМ (широтно-импульсной модуляцией), которые позволяют подавать не просто логический ноль или логическую единицу, но и другие уровни напряжения (для упрощения понимания, на самом деле позволяют изменять мощность сигнала). Такие выводы на плате обозначены символом тильда (~). На Arduino Nano и Arduino UNO таких выводов шесть (3, 5, 6, 9, 10, 11), они являются цифровыми. То есть их можно использовать, как обычные цифровые выводы (читать с них логическую единицу или ноль (для подключения тактовых кнопок, цифровых датчиков…), подавать логическую единицу или ноль (для подключения светодиодов, реле…)), а можно, как выводы с ШИМ.

Для работы с такими выводами в Wiring существует команда analogWrite (pin, state);

Где pin — номер вывода Arduino, state — значение ШИМ от 0 до 255. Где 0 — соответствует полностью выключенному сигналу (логический ноль поданный функцией digitalWrite, светодиод выключен), а 255 — постоянно включенному (логическая единица поданная функцией digitalWrite, максимальная яркость для светодиода).

Обеспечим плавное включение светодиода подключенного к выводу с ШИМ.

void setup () {

pinMode (3, OUTPUT);

}

void loop () {

analogWrite (3,0);

delay (100);

analogWrite (3,50);

delay (100);

analogWrite (3,100);

delay (100);

analogWrite (3,150);

delay (100);

analogWrite (3,200);

delay (100);

analogWrite (3,255);

delay (100);

}

Всё хорошо, но если мы захотим шаг не 50, а 1, то количество строк увеличивается в разы и скетч станет похож на безумство!

На страницу:
2 из 3