Arduino. Изучаем вместе

Всё о ЧПУ (CNC). Компоненты, станки, программы.
Аватара пользователя
Автор темы
KimIV
Реальное имя: Игорь
Откуда: Кунгур

Arduino. Изучаем вместе

Сообщение KimIV » 15 дек 2018, 15:06

Управление движением светящейся точки на светодиодной матрице 8х8.

Я думаю, что к текущему моменту мы уже набрались достаточного опыта и знаний, чтобы не просто передирать примеры из книжек и слегка их модифицировать, а с нуля разработать и реализовать свой проект. Пусть небольшой и несложный, но с нуля и полностью самостоятельно. Сама задача собственно уже озвучена в заголовке сообщения. Осталось только выбрать органы управления и определиться с характером движения светящейся точки. А это во многом будет зависеть от количества оставшихся свободных выводов после подключения светодиодной матрицы 8х8. Давайте посчитаем. Матрица имеет 16 выводов, дублирующих среди них нет, поэтому задействовать нужно все. Плата Arduino Uno имеет 14 цифровых и 6 аналоговых выводов, причём аналоговые можно использовать как цифровые. Всего значит 20 выводов. 16 из них отдаём матрице и на органы управления остаются 4 вывода. Напрашиваются обычные кнопки: влево, вправо, вверх и вниз.

В программе Fritzing рисуем "схему". Для лучшего восприятия используем следующие цвета проводов:
- зелёный - для соединения светодиодной матрицы с платой ардуино,
- синий - для снятия сигналов с кнопок и подачи их на аналоговые входы ардуино,
- чёрный - общий провод GND,
- красный - провод питания +5 В.
По строкам матрицы (или по столбцам :jokingly: ) ставлю резисторы номиналом 220 Ом, кнопки подтягиваю к шине +5 В резисторами 47 кОм. Готовый файл схемы в формате программы Fritzing я прицеплю в ZIP-архиве в конце сообщения.

023_1.png


Как видно из схемы, все цифровые выходы (14 штук) и два аналоговых вывода я присоединил к матрице. Как мы помним из этого сообщения, аналоговые выводы могут работать не только как входы АЦП, но и как цифровые выходы. И это можно использовать, когда для проекта не хватает штатных цифровых выходов. Теперь переходим к макетке, ставим дополнительную небольшую BreadBoard, которая была в комплекте kit-набора ардуино, устанавливаем матрицу, кнопки, резисторы и всё это соединяем проводами. В результате у меня получился вот такой ёжик. Цвета проводов правда не соответствуют тем, что в схеме, потому что лепил те, что были :jokingly:

023_2.jpg


Тут, наверно, нужно забежать немного вперёд. Когда я рисовал схему во Fritzing, то полагал, что нижний ряд выводов матрицы - это либо все столбцы, либо все строки, поэтому токоограничительные резисторы поставил ко всем выводам нижнего ряда. Но после написания скетча и при его отладке, когда схема сразу не заработала и пришлось досконально во всё вникать, я выяснил, что выводы строк и столбцов матрицы идут вразнобой. То есть, например, вывод 1 - это 5-тая строка, вывод 2 - 7-мая строк, а вывод 3 - второй столбец. Ну и в других номерах выводов тоже порядка нет :crazy: Вообщем, кому интересно, вот распиновка выводов матрицы.

023_3.png


А индикатором нижнего ряда служит вот этот выступ внизу корпуса.

023_4.jpg
023_5.jpg


Переходим к написанию скетча. Назовём его "LED_Matrix_8x8_Move_Point_By_Btn". Сначала объявляем константы, переменные и массивы, и сразу же инициируем их нужными нам значениеми. В константы "кладём" номера выводов платы ардуино, к которым подключены кнопки. Переменные инициируем низкими логическими уровнями, то есть изначально все кнопки не нажаты. Так же в качестве переменных нам понадобятся номер строки и номер столбца активной в данный момент (светящейся) точки матрицы. Пусть при включении начинает светиться первая точка, то есть номер строки и номер столбца равны 1. А ещё создаём пару массивов, в которых будут храниться номера выводов платы ардуино, подключенные к соответствующим строкам и столбцам матрицы. Например, первая строка матрицы подключена к выводу 8 платы ардуино, поэтому число 8 указываем в качестве первого элемента массива строк. Вторая строка подключена к выводу 13, поэтому число 13 - это второй элемент массива pinsStr и т.д. Аналогично со столбцами.

Код: Выделить всё

// Номера выводов подключения кнопок
const int BTL = 19;  // Влево
const int BTR = 16;  // Вправо
const int BTU = 17;  // Вверх
const int BTD = 18;  // Вниз
// Состояния кнопок
int tekBTL  = LOW;   // Текущее состояние кнопки Влево
int prevBTL = LOW;   // Предыдущее состояние кнопки Влево
int tekBTR  = LOW;   // Текущее состояние кнопки Вправо
int prevBTR = LOW;   // Предыдущее состояние кнопки Вправо
int tekBTU  = LOW;   // Текущее состояние кнопки Вверх
int prevBTU = LOW;   // Предыдущее состояние кнопки Вверх
int tekBTD  = LOW;   // Текущее состояние кнопки Вниз
int prevBTD = LOW;   // Предыдущее состояние кнопки Вниз
// Строки и столбцы матрицы
int nStr = 1;        // Номер строки активной точки
int nStb = 1;        // Номер столбца активной точки
// Номера выводов подключения строк и столбцов матрицы
int pinsStr[8] = {8, 13, 7, 11, 0, 6, 1, 4};
int pinsStb[8] = {12, 2, 3, 9, 5, 10, 14, 15};


В функции setup() конфигурируем выводы как выходы и как входы.

Код: Выделить всё

void setup() {
  // Выводы 0-15 конфигурируем как выходы
  for (int i=0; i<=15; i++) {
    pinMode(i, OUTPUT);
  }
  // Выводы 16-19 конфигурируем как входы
  for (int i=16; i<=19; i++) {
    pinMode(i, INPUT);
  }
}


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

Код: Выделить всё

// Функция сглаживания дребезга. Принимает в качестве
// Аргументы:
//   last   - Предыдущее состояние кнопки.
//   button - Номер входа, к которому подключена кнопка.
// Возвращаемое значение:
//   LOW  - Кнопка не нажата.
//   HIGH - Кнопка нажата.
boolean debounce(boolean last, int button)
{
  boolean current = digitalRead(button);  // Считываем состояние кнопки
  if (last != current)                    // Если изменилось...
  {
    delay(5);                             // Ждём 5 мс
    current = digitalRead(button);        // Считываем состояние кнопки
    return current;                       // Возвращаем состояние кнопки
  }
}


В функции setup() первым пишем блок обработки нажатий кнопок. Это 4 структурно одинаковых блока, просто каждый для своей кнопки. Ну и действия на эти нажатия немного разные. Для двух кнопок - уменьшение/увеличение номера строки, а для двух других - уменьшение/увеличение номера столбца.

Код: Выделить всё

  // Обработка нажатий кнопок
  tekBTL = debounce(prevBTL, BTL);        // Кнопка Влево
  if (prevBTL == LOW && tekBTL == HIGH)   // Если нажатие...
  {
    nStb--;                   // Уменьшаем номер столбца на единицу
    if (nStb < 1)             // Если номер столбца вышел за допустимые пределы
    {
      nStb = 8;               // Активируем крайний правый
    }
  }
  prevBTL = tekBTL;


Ну и последним пишем блок вывода светящейся точки на матрицу. Здесь два вложенных цикла. Наружный цикл перебирает номера строк, а внутренний - номера столбцов. И когда счётчики циклов совпадут с номерами строки и столбца активной точки, на вывод соответствующей строки подаётся высокий уровень. Полный код скетча в архиве ZIP прицеплю в конце сообщения.

Код: Выделить всё

  // Выводим на матрицу активную (светящуюся) точку
  for (int i = 1; i < 9; i++) {
    for (int j = 1; j < 9; j++) {
      if (i == nStr && j == nStb) {
        digitalWrite(pinsStr[i - 1], HIGH);
        digitalWrite(pinsStb[j - 1], LOW);
      } else {
        digitalWrite(pinsStr[i - 1], LOW);
        digitalWrite(pinsStb[j - 1], HIGH);
      }
    }
  }


Видео работы схемы и скетча.

https://youtu.be/ZMPt27e7Ulo
У вас нет необходимых прав для просмотра вложений в этом сообщении.
Ты должен делать добро из зла, потому что его больше не из чего делать. Уоренн Роберт Пенн.

Аватара пользователя
Автор темы
KimIV
Реальное имя: Игорь
Откуда: Кунгур

Arduino. Изучаем вместе

Сообщение KimIV » 16 дек 2018, 09:10

LED матрица 8х8. Управление движением точки с автоповтором.

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

Код: Выделить всё

// Переменные для автоперемещения при длительном удержании кнопки
const int holdTime = 200;       // Время удержания кнопки в мс
int currentTime = millis();     // Текущее время в мс


Для устранения повторяемости кода добавим две функции, которые будут заниматься уменьшением и увеличением переменной на единицу с контролем допустимых пределов значения переменной. Таким образом 4 строки кода, которые будут повторяться аж 8 раз, мы заменим на одну строчку вызова соответствующей функции.

Код: Выделить всё

// Функция уменьшения с контролем пределов
int dec(int x)
{
  x--;              // Уменьшаем переменную x на единицу
  if (x < 1)        // Если x вышла за допустимые пределы
  {
    x = 8;          // Присваиваем восемь
  }
  return x;
}

// Функция увеличения с контролем пределов
int inc(int x)
{
  x++;              // Увеличиваем переменную x на единицу
  if (x > 8)        // Если x вышла за допустимые пределы
  {
    x = 1;          // Присваиваем единицу
  }
  return x;
}


Вызов этих функций будет выглядеть следующим образом. Ну и сразу же привожу пример кода обработки длительного удержания кнопки в нажатом состоянии.

Код: Выделить всё

  if (prevBTL == HIGH && tekBTL == HIGH)    // Если кнопка нажата длительное время
  {
    if (millis() - currentTime > holdTime)  // Если время удержания кнопки больше заданного
    {
      nStb = dec(nStb);         // Уменьшаем номер столбца на единицу
      currentTime = millis();   // Текущее время
    }
  }


Весь код скетча прикрепляю в конце сообщения. А сейчас видео работы схемы и скетча.

https://youtu.be/fTBNBoCW1dc
У вас нет необходимых прав для просмотра вложений в этом сообщении.
Ты должен делать добро из зла, потому что его больше не из чего делать. Уоренн Роберт Пенн.

Аватара пользователя
Автор темы
KimIV
Реальное имя: Игорь
Откуда: Кунгур

Arduino. Изучаем вместе

Сообщение KimIV » 18 дек 2018, 10:05

Подключаем двухосевой джойстик

В моём китовом наборе его не было. Заказывал отдельно на али.

025_1.png


Китайцы его почему-то артикулируют как KY-023, хотя на плате джойстика напечатано название HW-504.

025_2.jpg
025_3.jpg
025_4.jpg
025_5.jpg


Как видно из фоток, на плате есть место под резистор R5, который подтягивает свободный контакт кнопки к +5V. В моём экземпляре джойстика этого резистора нет, поэтому подтяжку к +5 нужно будет обеспечивать самому. При замыкании кнопки контакт SW напрямую соединяется с GND. Схема джойстика.

025_6.png


Потенциометры X и Y включены параллельно между контактами +5V и GND. Замер сопротивления между ними показал 4,39 кОм.

025_7.jpg


Значит, если исходить из предположения, что потенциометры одинаковые, то сопротивление каждого должно быть в районе 8,8 кОм, что не вписывается в распространённый ряд 7,5; 8,2; 9,1 и т.д. Но если взять два разных сопротивления, а именно 8,2 и 9,1, то их параллельное соединение даст 4,31 кОм, что с учётом возможной погрешности вполне правдоподобно. Но кроме основного ряда номиналов есть ещё расширенные. Вот в них есть значения, близкие к 8,8. Про ряды номиналов радиодеталей можно почитать здесь.

Для подключения джойстика к плате ардуино воспользуемся предыдущей схемой, то есть оставим LED матрицу, а уберём только кнопки. Таким образом, управление движением светящейся точки кнопками сменим на джойстик. Но прежде, чем управлять, нужно разобраться, понять и почувствовать, как работает двухосевой джойстик. Поэтому матрицу пока мысленно отбросим, как-будто её нет, а есть только ардуино с аналоговыми входами и джойстик. В программе Fritzing рисуем схему подключения джойстика к плате Arduino Uno.

025_8.png


И пишем скетч. Первым делом, как обычно, объявляем и инициализируем нужные нам переменные.

Код: Выделить всё

int X = 0;       // Текущее значение по оси X
int Y = 0;       // Текущее значение по оси Y
int prevX = 0;   // Предыдущее значение по оси X
int prevY = 0;   // Предыдущее значение по оси Y


Переменные с префиксом "prev" нужны для того, чтобы сравнивать текущие значения с предыдущими и выводить значения в порт только, если они изменились. То есть неизменённые значения нам в порту не нужны. А иначе их будет слишком много.

В функции setup() конфигурируем выводы и инициализируем последовательный порт.

Код: Выделить всё

void setup() {
  // Выводы 17-19 конфигурируем как входы
  for (int i=17; i<=19; i++) {
    pinMode(i, INPUT);
  }
  // Инициализация последовательного порта
  Serial.begin(9600);
}


В функции loop() считываем значения с аналоговых входов, к которым подсоединены выводы X и Y джойстика, сравниваем их с предыдущими. И если значения изменились, то выводим их в порт.

Код: Выделить всё

void loop() {
  X = analogRead(18);     // Считываем значение по оси X
  Y = analogRead(19);     // Считываем значение по оси Y
  if (X != prevX || Y != prevY)
  {
    Serial.print("X = ");
    Serial.print(X, DEC);
    Serial.print("   Y = ");
    Serial.println(Y, DEC);
  }
  prevX = X;
  prevY = Y;
}


Загружаем скетч, открываем монитор порта и смотрим.

025_9.png


Сразу же побежали значения и их очень много. Повторяющихся подряд нету, но и изменения в пределах единицы. Хотя джойстик я не трогал, он лежал спокойно. Значит это значения на его выводах так пляшут. Для нивелирования этой пляски попробуем "загрубить" значения с помощью функции map() в два раза. То есть диапазон значений "сожмём" с 1024 до 512.

Код: Выделить всё

  X = map(X, 0, 1023, 0, 511);  // Масштабирование по X
  Y = map(Y, 0, 1023, 0, 511);  // Масштабирование по Y


Ну и при запуске скетча в порт попала только одна строка

Код: Выделить всё

X = 254   Y = 252


Попробуем подвигать джойстик влево/вправо, вверх/вних. Жаль из монитора данные не копируются в буфер обмена, поэтому привожу несколько скриншотов.

025_10.png
025_11.png
025_12.png
У вас нет необходимых прав для просмотра вложений в этом сообщении.
Ты должен делать добро из зла, потому что его больше не из чего делать. Уоренн Роберт Пенн.

Аватара пользователя
е_Вячеслав
Откуда: Москва

Arduino. Изучаем вместе

Сообщение е_Вячеслав » 18 дек 2018, 10:55

KimIV писал(а):Источник цитаты Значит это значения на его выводах так пляшут.
А, это не погрешность метода измерения ардуинкой, как и всех цифровых измерителей - плюс-минус один импульс. тогда здесь метод борьбы только один, увеличивать разрядность и игнорировать младший разряд.

Аватара пользователя
L0ki
Реальное имя: Женя

Arduino. Изучаем вместе

Сообщение L0ki » 18 дек 2018, 18:10

е_Вячеслав, еще можно сделать несколько измерений подряд и усреднить результат :)
тогда ничего отбрасывать не придется.

P.S.
Чтобы получить у атмеги максимальную точность АЦП преобразования, нужно:
настроить его работу в режим однократного преобразования,
и сделать чтобы АЦП выдавал прерывание по окончанию преобразования.
Т.е. АЦП-преобразование будет происходить по запросу программы.
Задать минимальную скорость работы АЦП (на максимальной достоверными будут старшие 8 из 10 бит).
На время измерения, чтобы цифровая часть МК минимально "гадила" помехами запрещаем все прерывания кроме АЦПшного, и отправляем контроллер в слип (спячку то бишь). По окончанию измерения АЦП выдаст прерывание и разбудит МК.
// ну и если волнует абсолютная точность измерений то использовать внешний ИОН (источник опорного напряжения) вместо внутреннего.
// про то что надо еще и делать хорошую развязку питания аналоговой части МК от цифровой - надеюсь напоминать не надо.
:pardon: вотЪ как-то так.....

Отправлено спустя 12 минут 14 секунд:
Р.S.
если что, деление двоичных чисел на числа являющиеся степенями двойки т.е. 2^n (например 2, 4, 8, 16, 32, 64... и т.д.)
сводится к побитовому сдвигу числа вправо на n разрядов (с потерей младших разрядов).
Это я про усреднение.
чукча - не писатель, чукча - читатель.
Аднака.

Аватара пользователя
е_Вячеслав
Откуда: Москва

Arduino. Изучаем вместе

Сообщение е_Вячеслав » 18 дек 2018, 19:46

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

Аватара пользователя
L0ki
Реальное имя: Женя

Arduino. Изучаем вместе

Сообщение L0ki » 18 дек 2018, 21:17

е_Вячеслав писал(а):Источник цитаты лучше не усреднять, а из нескольких измерений выбирать или большее или меньшее значение.

е_Вячеслав, эээээ.... :shock: ваще-та ошибки измерений обычно имеют нормальное распределение ("нормальное" в терминах теории вероятностей и матстатистики)
Ну а брать минимум и/или максимум из выборки... :shock: - это гм... :crazy: дедушка Гаусс перевернулся в гробу.
:wall:

:? А вообще-то "это" то бишь усреднение, как раз и применяется унутре серьезных измерительных приборов.

P.S.
е_Вячеслав, настоятельно рекомендую к ознакомлению. :tease:
чукча - не писатель, чукча - читатель.
Аднака.

Аватара пользователя
е_Вячеслав
Откуда: Москва

Arduino. Изучаем вместе

Сообщение е_Вячеслав » 19 дек 2018, 00:01

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

Отправлено спустя 13 минут 9 секунд:
KimIV,Игорь, можно еще другой алгоритм применить - сравнить текущее измерение с предыдущим, если разница больше единицы, выдать текущее, если меньше, то оставить предыдущее.

Аватара пользователя
Автор темы
KimIV
Реальное имя: Игорь
Откуда: Кунгур

Arduino. Изучаем вместе

Сообщение KimIV » 19 дек 2018, 05:32

Тест аналогового входа на предмет шумности АЦП.

Я решил проверить шумы на одном из аналоговых входов, подавая на него напряжение с каких-нибудь других источников, например, с переменного резистора 100 кОм, который шёл в комплекте с китовым набором. Для этого собрал на макетке вот такую схему. То есть концы резистора подключил к GND и +5V, а середину к выводу 16 платы ардуино.

026_1.png


Написал вот такой скетч.

Код: Выделить всё

int val = 0;       // Текущее значение
int prevVal = 0;   // Предыдущее значение

void setup() {
  // Вывод 16 конфигурируем как вход
  pinMode(16, INPUT);
  // Инициализация последовательного порта
  Serial.begin(9600);
}

void loop() {
  val = analogRead(16);        // Считываем значение с аналогового входа 16
  if (val != prevVal)
  {
    Serial.print("Value = ");
    Serial.println(val, DEC);
    prevVal = val;             // Сохраняем предыдущее значение
  }
}


Перед загрузкой и запуском скетча резистор поставил примерно в среднее положение. И в мониторе порта увидел примерно следующее:

Код: Выделить всё

Value = 499
Value = 498
Value = 499
Value = 498
Value = 499
.....


Покрутил резистор. Скачки на единицу везде кроме крайних положений, то есть Value = 0 и Value = 1023 устойчивы.

Ещё заметил, что когда касаешься рукой переменного резистора, то иногда проскакивает разница между текущим и предыдущим значениями не единица, а двойка, то есть что-то типа такого.

Код: Выделить всё

Value = 724
Value = 723
Value = 724
Value = 722     <-------
Value = 724
.....


Предположив, что это из-за высокоомности резистора (100 кОм), изменил схему на постоянные номиналом 1 кОм.

026_2.png


Но это не изменило картины скакания единицы, зато наводки от руки исчезли, то есть двойки перестали проскакивать.

Ну и для обеих схем проверил предложение е_Вячеслав
е_Вячеслав писал(а):Источник цитаты если разница больше единицы

заменив условие

Код: Выделить всё

  if (val != prevVal)


на

Код: Выделить всё

  if (abs(val - prevVal) > 1)


Танцы единицы прекратились! Ну и напоследок хочу заметить, что в контексте применения джойстика 1024 значений вообще не нужны. Достаточно 64 или даже 32, которые легко получить масштабированием с помощью функции map().
У вас нет необходимых прав для просмотра вложений в этом сообщении.
Ты должен делать добро из зла, потому что его больше не из чего делать. Уоренн Роберт Пенн.

Аватара пользователя
е_Вячеслав
Откуда: Москва

Arduino. Изучаем вместе

Сообщение е_Вячеслав » 19 дек 2018, 05:58

KimIV писал(а):Источник цитаты Ну и для обеих схем проверил предложение е_Вячеслав
Ну, и еще одна идея пришла, как от скачков единицы избавится, первоначально я говорил об увеличении разрядности и игнорировании младшего разряда, а можно младший разряд игнорировать и без увеличения разрядности, для этого достаточно результат разделить на 2 и выводить целое.
Не знаю, как работает функция map(), имею в виду как она реализована на уровне ассемблера, но мне кажется последний вариант с делением будет самым быстрым! Если достаточно получить только 64 значения джойстика, то полученные числа нужно делить на 16 и выводить целое.


Вернуться в «Системы ЧПУ»