В предыдущих двух статьях, мы разобрались как работает светодиодный индикатор 8х8 пикселей, напрямую, без помощи доп. регистров и прочего. А также протестировали датчики наклона. Что можно собрать, используя эти два навыка? Правильно – игральный кубик. Раз по отдельности всё у нас работает, осталось только объединить всё это в одно устройство.
В прошлый раз мы познакомились с микросхемой ULN2803, и её роли в этой схеме, датчик же подключается совсем элементарно, его мы тоже разбирали. Напомним схему подключения индикатора, а контакт для датчика наклона выберем A5
Я не буду касаться кода динамической индикации, если что можно обратиться к предыдущей статье. Только в этот раз массив мы зададим пустой, не будем инициализировать картинку в нём заранее, ведь она будет меняться.
char indication[8][8];
Помимо пустого массива индикации, который мы будем использовать для отображения на индикаторе, создадим ещё заготовки для цифр кубика. Например единица.
const char one[8][8] = {
{'0', '0', '0', '0', '0', '0', '0', '0'}, // Единица
{'0', '0', '0', '0', '0', '0', '0', '0'},
{'0', '0', '0', '0', '0', '0', '0', '0'},
{'0', '0', '0', '1', '1', '0', '0', '0'},
{'0', '0', '0', '1', '1', '0', '0', '0'},
{'0', '0', '0', '0', '0', '0', '0', '0'},
{'0', '0', '0', '0', '0', '0', '0', '0'},
{'0', '0', '0', '0', '0', '0', '0', '0'}
};
Создадим по аналогии остальные, так будет выглядеть пять
const char five[8][8] = {
{'1', '1', '0', '0', '0', '0', '1', '1'}, // Пять
{'1', '1', '0', '0', '0', '0', '1', '1'},
{'0', '0', '0', '0', '0', '0', '0', '0'},
{'0', '0', '0', '1', '1', '0', '0', '0'},
{'0', '0', '0', '1', '1', '0', '0', '0'},
{'0', '0', '0', '0', '0', '0', '0', '0'},
{'1', '1', '0', '0', '0', '0', '1', '1'},
{'1', '1', '0', '0', '0', '0', '1', '1'}
};
По аналогии зададим другие числа, не буду перечислять их в статье, принцип понятен. Начнём с самого необходимого – в зависимости от случайного значения от 1 до 6, будем менять массив indication, на значение другого массива. Кстати о функции random. Как бы это странно не звучало, но микроконтроллеру тяжело из ниоткуда придумать число с помощью кода – код выполняется прямолинейно. Поэтому обычно берётся АЦП аналогового входа, ведь если его оставить в воздухе, на нём постоянно будет изменяться напряжение. Оцифровав его, путем дальнейших преобразований, можно получить достаточно случайное число. В Arduino это делается очень просто, сначала читаем значение с аналогового входа, я использую 4, потому-что он у меня свободен, и “висит в воздухе”.
randomSeed(analogRead(4)); // Случайное значение с аналогового входа
А потом генерируем случайное число, где в скобках указываем минимальное и максимальное значение, которое нам нужно получить от включительно и до (не включительно).
randomValue = random(1, 7); // Принимаем значение от 1 до 7
Теперь, получив случайное число, попробуем поменять значение массива indication. К сожалению нельзя написать массив1 = массив2, ничего не получится. Значения нужно переписывать в цикле, по двум измерениям, в нашем случае. Попробуем для понимания сбросить экран и напишем функцию очистки, в котором один цикл for (для значений i) будет вложен в другой цикл for (для значений J), ведь суммарно нам нужно получить 64 итерации.
void reset_display()
{
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
indication[i][j] = 48;
}
}
}
В результате этого, все значения массива indication примут значение равное 0. Точно таким же методом, мы можем переназначить значения массива на значения другого массива, например в данном цикле, значение массива one будет скопировано в массив indication
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
indication[i][j] = one[i][j];
}
}
Далее, дело за малым, в зависимости от значения переменной random, мы назначаем массиву значения от 1 до 6, для отображения необходимого значения. Для этого воспользуемся оператором множественного значения switch
void random_value ()
{
randomSeed(analogRead(4)); // Случайное значение с аналогового входа
randomValue = random(1, 7); // Принимаем значение от 1 до 7
for (int i = 0; i < 8; i++) // Цикл, который значения одного массива передаёт другому
{
for (int j = 0; j < 8; j++)
{
switch (randomValue) {
case 1:
indication[i][j] = one[i][j]; // В случае рандомного значения = 1, назначаем циклу indication значение массива one
break;
case 2:
indication[i][j] = two[i][j]; // И т.д. для остальных значений
break;
case 3:
indication[i][j] = three[i][j];
break;
case 4:
indication[i][j] = four[i][j];
break;
case 5:
indication[i][j] = five[i][j];
break;
case 6:
indication[i][j] = six[i][j];
break;
}
}
}
}
Мы уже смотрели в статье с датчиками наклона, как запустить действие при многоразовой тряске – для этого мы используем счётчик переменную valCrash. Как только мы зафиксировали более 16 изменений выхода датчика с одного состояния на другое – нам нужно немного изменить переменные, для запуска отображения числа на индикаторе. Для этого мы обнуляем дисплей, сбрасываем переменные времени и активируем флаг event = 1;
void crash()
{
if (digitalRead(vibration) == readSensor && event == 0) // Если состояние датчика равно переменной состояния
{
readSensor = !readSensor; // Меняем переменную состояния на противоположное значение
valCrash++; // Инкрементируем счётчик трясок
resetTime = millis();
if (valCrash > 16) // Если количество трясок более 20, то делаем действие
{
reset_display();
eventTime = millis();
resetTime = millis();
event = 1;
valCrash = 0; // Обнуляем счётчик трясок
delay(30);
}
}
}
Если же нет никого действия в течении 7 секунд – мы обнуляем счётчик трясок valCrash и очищаем дисплей – чтобы не потреблять энергию просто так и не показывать старый результат.
void resetCrash()
{
if (millis() - resetTime > 7000) // Сбрасываем дисплей, если время простоя более 7 секунд
{
resetTime = millis(); // Приравниваем переменную сброса к времени контроллера
valCrash = 0;
reset_display(); // Функция сброса
}
}
Функция сброса дисплея реализуется просто. Мы уже научились назначать значения одного массива другому. В данном случае просто заполняем массив нулями в двух циклах.
void reset_display() // Функция сброса информации на экране
{
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
indication[i][j] = 48; // Заполняем нулями все 64 значения
}
}
}
Также для красоты перед выпадением числа на кубике – можно сделать любую анимацию. Чтобы её легко можно было изменить – я сделал её в 8 массивах, которые меняются по очереди в цикле. Для примера, так выглядят первые два кадра.
const char frame1[8][8] = {
{'0', '0', '0', '0', '0', '0', '0', '1'}, // Кадр 1
{'1', '0', '0', '0', '0', '0', '0', '0'},
{'0', '0', '0', '0', '0', '0', '0', '1'},
{'1', '0', '0', '0', '0', '0', '0', '0'},
{'0', '0', '0', '0', '0', '0', '0', '1'},
{'1', '0', '0', '0', '0', '0', '0', '0'},
{'0', '0', '0', '0', '0', '0', '0', '1'},
{'1', '0', '0', '0', '0', '0', '0', '0'}
};
const char frame2[8][8] = {
{'0', '0', '0', '0', '0', '0', '1', '0'}, // Кадр 2
{'0', '1', '0', '0', '0', '0', '0', '0'},
{'0', '0', '0', '0', '0', '0', '1', '0'},
{'0', '1', '0', '0', '0', '0', '0', '0'},
{'0', '0', '0', '0', '0', '0', '1', '0'},
{'0', '1', '0', '0', '0', '0', '0', '0'},
{'0', '0', '0', '0', '0', '0', '1', '0'},
{'0', '1', '0', '0', '0', '0', '0', '0'}
};
В ключевом условии if (millis() – shakeTime > 80 && event == 1) мы запускаем действие анимации, как только флаг event == 1. Каждые 80мс, мы меняем кадр в цикле, чтобы глаз успевал заметить кадр. Этим значением можно замедлять или ускорять анимацию.
void shaking()
{
if (millis() - shakeTime > 80 && event == 1) // Функция
{
if (a > 7)
{
a = 0;
}
for (int i = 0; i < 8; i++) // Цикл, который значения одного массива передаёт другому
{
for (int j = 0; j < 8; j++)
{
switch (a) {
case 0:
indication[i][j] = frame1[i][j]; // В случае рандомного значения = 1, назначаем циклу indication значение массива one
break;
case 1:
indication[i][j] = frame2[i][j]; // И т.д. для остальных значений
break;
case 2:
indication[i][j] = frame3[i][j];
break;
case 3:
indication[i][j] = frame4[i][j];
break;
case 4:
indication[i][j] = frame5[i][j];
break;
case 5:
indication[i][j] = frame6[i][j];
break;
case 6:
indication[i][j] = frame7[i][j];
break;
case 7:
indication[i][j] = frame8[i][j];
break;
}
}
}
a++;
shakeTime = millis();
}
}
Ну и действие отображения самого числа. Оно срабатывает спустя время eventTime >= 1500, чтобы дать поработать анимации. Анимация же сразу отключится, потому-что мы снимаем флаг event = 0; в этой функции.
void action () // Проверка условий для запуска отображения случайного числа
{
if (millis() - eventTime >= 1500 && event == 1)
{
event = 0; // Сбрасываем флаг действия
reset_display(); // Очищаем экран
random_value(); // Задаём массиву случайное значение
eventTime = millis(); // Сбрасываем переменную времени действия
}
}
Ну а в главном цикле мы проверяем постоянно все наши функции. Сначала при тряске активируются действия в crash();, затем если набрали необходимое число трясок, запускаем функцию анимации shaking(); . action(); активируется через определённое время после старта анимации, и выключают саму анимацию, заменяя картинку на экране случайным числом. resetCrash(); очищает экран, при отсутствии действий со стороны пользователя, а dynamic_indi(); у нас в работе всегда – это динамическая индикация экрана.
void loop()
{
shaking(); // Функция анимации
action(); // Вызов проверки функции действия
resetCrash(); // Вызов функции сброса экрана и действия
crash(); // Вызов функции проверки тряски датчика наклона
dynamic_indi(); // Динамическая индикация всегда должна быть активна
}
В следующих статьях мы перенесём проект на автономную плату, и я расскажу как уйти от arduino, что важно в реальном проекте.
Добавить комментарий
Для отправки комментария вам необходимо авторизоваться.