Мои первые страшные опыты с Arduino

18 января 2017

Это было неизбежно. Увлекшись электроникой, я должен был рано или поздно дойти и до программирования микроконтроллеров. А что может быть проще программирования AVR-микроконтроллеров в устройствах Arduino? Не удивительно, что начать я решил именно с них. Что же из этого получилось — читайте далее.

Примечание: Описанные далее действия производились под Linux, на Arduino Uno и, соответственно, используемом в этом устройстве микроконтроллере ATmega328P. Однако все написанное далее справедливо и для других моделей Arduino, а также мало отличаться в случае, если вы используете другую ОС.

Настройка Arduino IDE

Подключаем Arduino к компьютеру через USB порт и определяем при помощи dmesg имя соответствующего устройства. У меня это ttyACM0. Если увидели что-то вроде:

usb 1-1: new full-speed USB device number 59 using xhci_hcd
usb 1-1: Device not responding to setup address.
usb 1-1: device not accepting address 59, error -71
usb usb1-port1: unable to enumerate USB device

… значит проблема в USB-кабеле, используйте другой.

Смотрим, какие права стоят на это устройство:

ls -la /dev/ttyACM0

В разных дистрибутивах результат немного отличается. В Arch Linux я увидел:

crw-rw---- 1 root uucp 166, 0 Jan  1 21:34 /dev/ttyACM0

То есть, для доступа к устройству пользователя нужно добавить в группу uucp:

sudo usermod -a -G uucp eax

В Ubuntu название группы будет другим, а именно «dialout». После добавления пользователя в группу скорее всего придется перелогиниться. В выводе id должны видеть, что ваш пользователь теперь состоит в группе.

Качаем Arduino IDE отсюда и распаковываем куда-нибудь:

cd ~/temp
tar --xz -xvf arduino-1.8.0-linux64.tar.xz
mkdir ~/opt
mv arduino-1.8.0 ~/opt/arduino

В каталоге с IDE можно запустить install.sh, чтобы он создал красивые икноки для вашего окружения рабочего стола. Я пользуюсь i3, поэтому никаких красивых иконочек мне не нужно.

После запуска IDE в Tools → Port выбираем /dev/ttyACM0. Затем жмем Files → Examples → Basics → Blink. Откроется следующий код:

void setup()
{
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop()
{
  digitalWrite(LED_BUILTIN, HIGH);
  delay(1000);
  digitalWrite(LED_BUILTIN, LOW);
  delay(1000);
}

Используемый в Arduino язык программирования, как не сложно заметить, является диалектом C/C++. Эта программа просто мигает встроенным в Arduino светодиодом. Вы тут все программисты, поэтому разжевывать, как именно работает код, полагаю, не требуется. Жмем Upload (Ctr+U) и… я лично получил такую ошибку:

libtinfo.so.5: cannot open shared object file: No such
  file or directory

Лечится так:

sudo pacman -S ncurses
sudo ln -s /usr/lib/libncursesw.so /usr/lib/libtinfo.so.5

Если все было сделано правильно, теперь после нажатия Ctr+U Arduino начнет мигать желтым светодиодом с подписью L. Бывает так, что программа Blink уже была залита в Arduino заранее. В этом случае попробуйте изменить в коде частоту мигания и перезалить программу.

Работа без IDE

Arduino IDE довольно убога, как текстовый редактор, плюс я заметил в ней неприятные глюки при сохранении файлов. Мне, знаете ли, нравится писать код в Vim. Вам может нравиться Sublime Text или другой текстовый редактор. Так давайте же разберемся, как программировать Arduino в текстовом редакторе или IDE по вкусу.

Говорим и заодно сразу прописываем в ~/.bashrc:

export ARDUINODIR=/home/eax/opt/arduino

Далее нам понадобится файл arduino.mk, который доступен здесь. Однако не спешите его качать. Мне этот файл пришлось во многих местах подправить, чтобы он заработал с последней Arduino IDE. Детали скучны, поэтому я не буду вас ими грузить. Просто берите пропатченную мною версию. Если вам вдруг интересно, что именно я поменял, измененные строчки помечены вот так:

# patched by afiskon

Создаем файл Makefile такого содержания:

TARGET := main
SOURCES := main.cpp
BOARD := uno
include arduino.mk

… а также main.cpp:

#include <Arduino.h>

/* Номера пинов, к которым подключены аноды RGB-светодиода */
const int RLED = 9;
const int GLED = 11;
const int BLED = 10;

/* Номера пинов, к которым подключены кнопки */
const int RBUTTON = 2;
const int GBUTTON = 4;
const int BBUTTON = 6;

/* Номер аналогового входа, подключенного к фоторезистору */
const int LIGHT = 0;

/* Как часто считывать состояния кнопок, в мс */
const int DELAY = 10;

/* Как часто выводить уровень освещенности, в мс */
const int LIGHT_REPORT_PERIOD = 1000;

/* Текущая яркость RGB-светодиода */
int rval = 120;
int gval = 0;
int bval = 250;

/*
 * Была ли отпущена соответствующая кнопка.
 *
 * Эти переменные нужны, чтобы одно нажатие кнопки
 * не считалось за несколько.
 */

bool rbutton_released = true;
bool gbutton_released = true;
bool bbutton_released = true;

/* Как долго не выводили уровень освещенности, в мс */
int silent_time = 0;

void setup()
{
  pinMode(RLED, OUTPUT);
  pinMode(GLED, OUTPUT);
  pinMode(BLED, OUTPUT);

  pinMode(RBUTTON, INPUT);
  pinMode(GBUTTON, INPUT);
  pinMode(BBUTTON, INPUT);

  Serial.begin(9600);
}

void loop()
{
  /* Обработка нажатия первой кнопки */
  if(digitalRead(RBUTTON) == HIGH)
  {
    if(rbutton_released)
    {
      rbutton_released = false;
      rval += 10;
      if(rval > 255)
        rval = 0;
      Serial.println("rval = " + String(rval));
    }
  } else
    rbutton_released = true;

  /* То же самое для второй кнопки */
  if(digitalRead(GBUTTON) == HIGH)
  {
    if(gbutton_released)
    {
      gbutton_released = false;
      gval += 10;
      if(gval > 255)
        gval = 0;
      Serial.println("gval = " + String(gval));
    }
  } else
    gbutton_released = true;  

  /* То же самое для третьей кнопки */
  if(digitalRead(BBUTTON) == HIGH)
  {
    if(bbutton_released)
    {
      bbutton_released = false;
      bval += 10;
      if(bval > 255)
        bval = 0;
      Serial.println("bval = " + String(bval));
    }
  } else
    bbutton_released = true;

  /* Задаем уровень ШИМ-сигнала на анодах RGB-светодиода */
  analogWrite(RLED, rval);
  analogWrite(GLED, gval);
  analogWrite(BLED, bval);

  /* Небольшая задержка */
  delay(DELAY);
  silent_time += DELAY;

  /* Выводим освещенность, если долго ее не выводили */
  if(silent_time >= LIGHT_REPORT_PERIOD)
  {
    int light_val = analogRead(LIGHT);
    Serial.println("light_val = " + String(light_val));
    silent_time = 0;
  }
}

Как видите, программа стала сложнее. Этот код мы разберем подробнее чуть ниже. Если хотите, можете пока что оставить код программы Blink. Главное — не забудьте добавить в начале строчку:

#include <Arduino.h>

Для загрузки программы говорим make upload. Для просмотра сообщений от Arduino говорим make monitor. В последнем случае будет использован screen. Как альтернативный вариант, можно воспользоваться программой minicom:

sudo pacman -S minicom
minicom -D /dev/ttyACM0 -b 9600

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

stty -F /dev/ttyACM0

Теперь, когда с настройкой рабочего окружения покончено, давайте разберемся, как работает приведенная программа.

Немного матчасти и разбор кода

Приведенная программа управляет RGB-светодиодом, а также считывает данные с трех кнопок и фоторезистора:

Мой первый проект на Arduino

RGB-светодиод — это по сути три светодиода в одном корпусе с общим катодом или анодом, которому соответствует наиболее длинная ножка из четырех. У меня был RGB-светодиод с общим катодом, то есть самую длинную ножку я подключал к минусу. Такой светодиод интересен тем, что его цвет со временем можно менять. Как видите, на фото светодиод светится фиолетово-розовым цветом. Если у вас нет RGB-светодиода, вы можете с тем же успехом использовать три отдельных светодиода.

Фоторезистор представляет собой резистор, чье сопротивление меняется в зависимости от того, как много света на него падает. Тот, что есть у меня, имеет сопротивление около 400 Ом при ярком свете и более 2 МОм (максимум, который может померить мой мультиметр) в полной темноте. Используя его в качестве одного из резисторов в делителе напряжения, можно оценить яркость света вокруг. Если у вас нет фоторезистора, можете с тем же успехом подключить обычный потенциометр.

У Arduino Uno есть 14 цифровых пинов, 6 аналоговых входов, а также пины с 5 В напряжения, 3.3 В и землей. В программе Blink мы использовали 13-ый пин со встроенным светодиодом. Подавая на него высокое и низкое напряжение, можно заставить светодиод мигать. Также цифровые пины позволяют считывать напряжение. В приведенной выше программе пины 2, 4 и 6 используются для чтения состояния кнопок. Пока это все не сильно отличается от уже знакомых нам GPIO пинов в Raspberry Pi.

Часть цифровых пинов помечены тильдой (~). У меня таких пинов 6, с номерами 3, 5, 6, 9, 10 и 11. Это означает, что пин может подавать ШИМ-сигнал (Широтно-Импульсная Модуляция или PWM, Pulse-Width Modulation). В данном контексте ШИМ всего лишь означает, что на пин будет подаваться сигнал с заданным коэффициентом заполнения. В Arduino коэффициент заполнения задается от 0 до 255. Например, если указать 127, то половину времени будет подаваться высокий сигнал, а половину — низкий. Если же указать 0, то сигнал будет все время низким. Частота ШИМ-сигнала в Arduino разная на разных пинах и отличается от модели к модели. В Arduino Uno частота составляет примерно 980 Гц на пинах 5 и 6, и примерно 490 Гц на остальных ШИМ-пинах.

ШИМ позволяет аппроксимировать подачу напряжения между 0 и 5 В. В приведенной программе нажатия на кнопки увеличивают ШИМ-сигнал на соответствующих анодах RGB-светодиода. В результате тремя кнопками регулируется яркость и цвет светодиода.

Наконец, аналоговые входы предназначены для более точного считывания напряжения, чем просто «высокий сигнал» и «низкий сигнал». В приведенном примере аналоговый вход A0 подключен к делителю напряжения из фоторезистора и резистора сопротивлением 10 кОм. Вызов analogRead(LIGHT) возвращает значение около 800 при ярком свете, 125 при свете от настольной лампы, 0 в полной темноте и около 960, если посветить на фоторезистор лазерной указкой. В целом диапазон возможных возвращаемых значений лежит от 0 до 1023. Плюс к этому, аналоговые входы можно использовать точно так же, как и цифровые пины (без ШИМ), например:

pinMode(A1, OUTPUT);
digitalWrite(A1, HIGH);

Fun fact! Фактически, теперь вы знаете, как сделать лазерную сигнализацию. Также с помощью светодиодов или ИК-светодиодов и фоторезистора можно сделать датчик движения (бесконтактный выключатель?) вроде того, что используется в сушилках для рук. Также помимо фоторезистора можно использовать фототранзистор, представляющий собой обычный транзистор, ток базы которого определяется интенсивностью света. Если желаете, можете реализовать эти проекты в качестве домашнего задания.

Заключение

С Arduino мне понравилось работать намного больше, чем с Raspberry Pi. У последней, насколько мне известно, аналоговых входов нет совсем, да и с ШИМ все как-то не слишком удобно. Плюс Arduino Uno — это всего лишь микроконтроллер ATmega328P, на котором нет этой бесполезной операционной системы и который при желании можно извлечь и впаять прямо в изготовленную по ЛУТ плату. В общем, для занятия электроникой это устройство куда интереснее.

Исходники к этой заметке вы найдете на GitHub. Как обычно, буду рад вашим вопросам и дополнениям.

Дополнение: Как собрать Arduino прямо на макетной плате

Метки: , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.