Pull to refresh

Электроника всем начинающим

Reading time20 min
Views53K

Хабр! Добро пожаловать снова. 

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

Мы будем делать часы, таймер и игру в одном устройстве.

Готовое устройство и печеньки.
Готовое устройство и печеньки.

UPD #1

Дисклеймер

Внимание!

Эта статья имеет много профессиональных неточностей, по мнению уважаемых экспертов по всем вопросам и по совместительству комментаторов, также не является руководством к действию, может привести к производственным травмам и нарушениям психики. Автор призывает, не использовать данное руководство для обучения и лишь демонстрирует хаотично накопленные знания и реализует их в горделивой позе больного шизофренией с бредовыми идеями величия. Никогда не верьте мне! Я украл ваши технологии!

Некоторое вступление

Спустя много лет я решил вернуться снова к написанию статей, с новыми знаниями и силами. Знаете, интернет научил меня всему, что я знаю и даже больше, чем просто всему. Интернет стал не просто учением, в котором тяжело, но и боем, в котором легко. И я благодарен всем, кто так или иначе принял участие в моем обучении, через статьи, описание каких-то технологий, видео на YouTube и просто критику моих работ. Это герои моего времени, только благодаря им я сейчас являюсь неплохим специалистом. Ведь я не учился в этих ваших институтах и образований не получал, да и всего у меня 9 классов. Спасибо тем, кто пишет интернет.

И еще

В детстве, когда я только начинал гуглить какие-то схемы, я любил статьи с картинками, больше всего мне нравилось, как нагляден процесс сборки, как процесс обучения реализован через картинки. Буквы придумали не для меня и вообще не для детей, которые хотят заниматься электроникой. Поэтому я приложил грандиозное количество усилий, чтобы эта статья могла стать для кого-то первой ступенью. Я знаю, как сложно сделать первый шаг. Мое соприкосновение с контроллером случилось только в 2016 году, хотя я был знаком с ними и заочно, задолго до 2016 года.

Компоненты

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

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

  1. Резисторы 150 Ом 0.25 Ватт — 12 шт.

  2. Конденсаторы 50 вольт 10 микрофарад — 4 шт.

  3. Тактовая кнопка  6x6мм — 3 шт.

  4. Светодиод 75x3мм — 1 шт.

  5. Пьезо зуммер  — 1 шт.

  6. Кварцевый резонатор 16 МГц — 1 шт.

  7. Разъём типа гребёнка — 7 шт.

  8. Джампер (перемычка) — 1 шт.

  9. Четырехразрядный семисегментный индикатор (Sm56425bsr3 или аналоги) — 1 шт.

  10. Сдвиговый регистр 74ch595 корпус DIP — 1 шт.

  11. Панель под микросхему 74ch595 корпус DIP (16 ножек) — 1 шт.

  12. Микроконтроллер ATmega328p корпус DIP — 1 шт.

  13. Панель под микросхему ATmega328p корпус DIP (28 ножек) — 1 шт.

  14. Монтажная плата 40x60мм — 2 шт.

  15. Батарейный отсек cr2032 — 2 шт.

  16. Батарейка cr2032 — 2 шт.

  17. Втулка 5x8x0мм  (Не точно) — 4 шт.

  18. Болт 3x6мм (Не точно) — 4 шт.

  19. Шайба 5мм (Не точно)— 4 шт.

  20. Гайка 3мм (Не точно) — 4 шт.

  21. Преобразователь USB-UART CP2102 — 1 шт.

Также, рекомендую при необходимости купить флюс, припой и паяльник.

Я намеренно не указываю марку проводов, которая вам подойдет, так как совсем не владею информацией об их параметрах. Могу посоветовать МГТФ, вполне возможно, что очень хорошо подойдут. Если вы знаете, какие провода точно оптимальны, оставьте информацию в комментариях или напишите мне в личные сообщения  @prohetamine.

Сдвиговый регистр 74ch595

Наверное, многим новичкам станет не по себе от понимания принципов работы микросхемы 74ch595, вне этой статьи и пропустить этот этап я просто не хочу. Сейчас я попробую максимально доступно объяснить, как она работает и чем будет полезна в конкретном случае с моим устройством.

Проще говоря, микросхема предназначена для расширения количества цифровых выходов.

Распиновка. Внимание! Рисунок имеет незначительные неточности в маркировке контактов, это сделано для более простого усвоения и понимания работы.

Самые загадочные контакты управления, которые вызывают интерес: 

  • output pin * — контакты вывода

  • DS — (Serial Data Input) контакт, который определяет состояние напряжения на контактах вывода

  • SH — (Shift Register Clock Input) контакт, который записывает состояние которое определенно в DS

  • ST — (Storage Register Clock Input) контакт, который открывает микросхему для записи и закрывает, устанавливая на контакты вывода нужные состояния определенные DS

Уверен, визуальный пример, поможет вам понять происходящее лучше.

Монтажная схема соединений

Если нет, то я оставил и интерактивную версию, кнопочки работают, можно понажимать.

Теперь, когда вы овладели работой с микросхемой, можно приступить к следующему пункту.

Тонкости

Внимание!

Чтоб ничего не перепутать и ничего не испортить, в том числе настроение. Не ждем, а готовимся! Просто оставлю это здесь, для самых маленьких. Я, конечно, понимаю, что всех тонкостей в рамках и без того длинной статьи мне обозначить не удастся, и у вас все же могут возникнуть ошибки, пускай, хотя бы не самые очевидные.

Когда мы программируем контроллер, очень важно не путать rx и tx, иначе контроллер просто не прошьется.

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

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

Я смотрю на эту схему каждый раз, когда вспоминаю, как припаял более 671 кнопку не в ту сторону.. Не совершай ошибку.

Плюсик: у всех новых электронных компонентов, которые имеют полярность, выглядит как хромоног.

Цветной хромоног:

Батарейный отсек, тоже имеет свою не очевидную полярность..

Монтажная схема соединений

Так выглядит схема нашего устройства:

UPD #2

По требованию комментаторов скоро тут появится принципиальная схема.

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

Пока пин кнопки состояние, которого мы читаем не притянут к плюсу или минусу он выдает случайные (101010000101010) результаты и кнопка, не может работать нормально, чтобы «Стабилизировать» состояние кнопки нам нужно притянуть наш пин через резистор к минусу или плюсу (принято к минусу). Тогда при нажатии у нас будет 1 иначе 0. На момент создания устройства и написания статьи, автор не знал, что существует pull-up резистор встроенный в саму ATmega328p. Почитать можно об этом на официальном сайте.

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

Резисторы предназначены для ограничения тока исходящего от ATmega328p, а именно 5 вольт мы ограничиваем до 3-х вольт, так как почти все светодиоды ограничены напряжением в 3 вольта и привыкли работать за еду, более высокое напряжение приведет к деградации, насколько быстрой зависит от тока, хоть у ATmega328p он не большой, примерно 20-40 миллиампер, деградацию и сгорание не будет видно сразу, но оно случится, явно намного раньше положенного.

О нем говорят все, но никто не знает зачем он. На самом деле все просто. Предельно просто. Эта микросхема умножает количество контактов, с условных трех до N. Мой максимум 265+ выводов, но возможно и больше. В этом месте мог бы возникнуть хороший вопрос, по сути ты ведь делаешь из трех контактов четыре, а остальные четыре не используешь(?) На эту тему можно конечно рассуждать, зачем и почему, правильный ответ только один — дать возможность устройству развиваться.

Монтаж компонентов

Внимание!

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

Внимание!

Соблюдайте порядок установки микросхем по ключам.

Устанавливаем панели.

Устанавливаем конденсаторы и кнопки.

Устанавливаем разъемы и пьезо зуммер.

Устанавливаем микросхемы и резисторы.

Устанавливаем индикатор, светодиод и резонатор.

Устанавливаем батарейные отсеки.

Прототип

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

Объединенная схема

Соединим основные линии питания и необходимую обвязку первой платы.

Соединим кнопки, светодиод индикатор прошивки и пьезо зуммер.

Соединим конденсаторы и семисегментный индикатор с сдвиговым регистром 74ch595.

Соединим семисегментный индикатор с микроконтроллером.

В финале первая плата у вас получится такой:

Вторая плата, но тут все совсем просто. Соединим последовательно элементы питания.

Соединим все вместе.

Устройство

Программирование

Подключаем так как на картинке, и можно начинать прошивать микроконтроллер.

Бесспорно, абсолютно, однозначно. Мой код на C++ далек от идеала, но я, как всегда, пытался. Я пишу на JS (ну, вы поняли). И тем не менее, я все равно собой доволен, хотя бы, потому что не притрагиваясь и без того к незнакомому мне языку больше года, мне как-то удалось организовать не только структуру с своими правилами, а также создать богатый функционал: часы, игру и два таймера c разными уровнями точности. Можешь сделать лучше? Есть что дополнить? GitHub

Основной файл проекта, к которому я подключаю все остальные файлы и библиотеку AsyncDelay, с которой управлять синхронным потоком становится проще, чем обычно (имхо). Изначально, в процессе написания кода, я обозначил для себя два компонента - это actionDriver и actionContoller. где первый переводя на JavaScript - тянский (является почти как Event Loop), то есть выполняет стек задач только не событийных, а перманентных, а второй выполняет роль Setter'a.

// Подключаем библиотеку
#include <AsyncDelay.h>

// Назначаем имена прерываний
AsyncDelay delayRenderRowFirst;
AsyncDelay delayRenderRowTwo;
AsyncDelay delayRenderRowThree;
AsyncDelay delayRenderRowFour;
AsyncDelay delayAnimaton;
AsyncDelay delayButtonHandle;

// Определяем пины кнопок
const int BTN_SET_TOP = A5
        , BTN_SET_MIDDLE = A4
        , BTN_SET_BOTTOM = A3;

// Определяем пины сдвигового регистра
const int DS = 11
        , ST_CP = 10
        , SH_CP = 9;

// Определяем пины семисигментного индикатора
const int A = 2
        , B = 4
        , C = 7
        , D = 5
        , E = 1
        , F = 3
        , G = 8
        , DP = 6;

// Определяем пины светодиода и зуммера
const int BLINK = 13;
const int SIGNAL = 12;

// подключаем модули проекта
#include "viewer.h"
#include "animation.h"
#include "time.h"
#include "mtimer.h"
#include "timer.h"
#include "gameUnLocker.h"
#include "button.h"

void setup () {
  // назнчаем интервалы прерываний 
  delayRenderRowFirst.start(1, AsyncDelay::MILLIS);
  delayRenderRowTwo.start(1, AsyncDelay::MILLIS);
  delayRenderRowThree.start(1, AsyncDelay::MILLIS);
  delayRenderRowFour.start(1, AsyncDelay::MILLIS);
  delayButtonHandle.start(1000, AsyncDelay::MILLIS);
  delayAnimaton.start(500, AsyncDelay::MILLIS);
  
  // Устанавливаем пины на выход
  pinMode(A, OUTPUT);
  pinMode(B, OUTPUT);
  pinMode(C, OUTPUT);
  pinMode(D, OUTPUT);
  pinMode(E, OUTPUT);
  pinMode(F, OUTPUT);
  pinMode(G, OUTPUT);
  pinMode(DP, OUTPUT);

  pinMode(BLINK, OUTPUT);
  pinMode(SIGNAL, OUTPUT);
  
  pinMode(DS, OUTPUT);
  pinMode(ST_CP, OUTPUT);
  pinMode(SH_CP, OUTPUT);

  pinMode(BTN_SET_TOP, INPUT);
  pinMode(BTN_SET_MIDDLE, INPUT);
  pinMode(BTN_SET_BOTTOM, INPUT);

  // Сбрасываем все значения на пинах
  digitalWrite(A, 0);
  digitalWrite(B, 0);
  digitalWrite(C, 0);
  digitalWrite(D, 0);
  digitalWrite(E, 0);
  digitalWrite(F, 0);
  digitalWrite(G, 0);
  digitalWrite(DP, 0);

  // Сбрасываем значения на сдвиговом регистре
  digitalWrite(ST_CP, 0);
  for (int i = 0; i < 8; i++) {
    digitalWrite(SH_CP, 0);
    digitalWrite(DS, 1); 
    digitalWrite(SH_CP, 1);
  }
  digitalWrite(ST_CP, 1);
}

// Активируем анимацию
boolean aminationStartActive = true; 

void loop () {  
  // Устанавливаем драйвера в общик поток
  viewDriver(); 
  buttonDriver();
  timeDriver();
  mTimerDriver();
  timerDriver();
  gameUnLockerDriver();
  animationDriver();

  // Останавливаем анимацию и запускам виджет времени
  if (millis() > 3000 && aminationStartActive) {
    aminationStartActive = false;
    timeShow = true;
  }

  // Запускаем анимацию
  if (millis() < 1) {
    animationController(false, "hey ");
  }
}
Управление устройством
// Режим работы
int modeId = -1;
// Перемещение между режимами
int selectId = 0;
// Премещение между символами
int carret = 0;

/*
 * Режимы работ
 * 0 - время
 * 1 - таймер
 * 2 - минутный таймер
 * 3 - игра
 */

#define MODE_NONE -1
#define MODE_TIME 0
#define MODE_TIMER 1
#define MODE_MINUTES_TIMER 2
#define MODE_GAME 3
 
// Состояния кнопок
int clickedFirstButton = 0;
int clickedMiddleButton = 0;
int clickedLastButton = 0;

// Управление состоянием кнопок
void buttonController (int first, int middle, int last) {
  if (first != -1) {
    clickedFirstButton = first;
  }

  if (middle != -1) {
    clickedMiddleButton = middle;
  }

  if (last != -1) {
    clickedLastButton = last;  
  }
}

// Прекращает работу всех виджетов
void mainOffControllers () {
  timeController(false);
  mTimerController(false);
  timerController(false);
  gameUnLockerController(false);
}

// Меню
void menuList () {
  if (selectId == MODE_TIME) {
    viewController(0, String('c'));
    viewController(1, String(' '));
    viewController(2, String(' '));
    viewController(3, String(' ')); 
  }

  if (selectId == MODE_TIMER) {
    viewController(0, String('t'));
    viewController(1, String(' '));
    viewController(2, String(' '));
    viewController(3, String(' ')); 
  }

  if (selectId == MODE_MINUTES_TIMER) {
    viewController(0, String('m'));
    viewController(1, String('t'));
    viewController(2, String(' '));
    viewController(3, String(' ')); 
  }

  if (selectId == MODE_GAME) {
    viewController(0, String('g'));
    viewController(1, String(' '));
    viewController(2, String(' '));
    viewController(3, String(' ')); 
  }
}

// Обработчик первой кнопки
void buttonFristEvent (int clickedFirst, int clickedMiddle, int clickedLast) {
  if (modeId == MODE_NONE) {
    selectId++;
    if (selectId > 3) {
      selectId = MODE_TIME;
    }
    mainOffControllers();
    menuList();
  }

  if (clickedLast == 1 || clickedLast == 2 || clickedLast == 3 || clickedLast == 4) {
    if (modeId == MODE_TIME) {
      for (int i = 0; i < 4; i++) {
        if (carret == i) {
          timeTickerController(i, 1);
          viewController(carret, String(timeTicker[i]));
        }
      }
    }

    if (modeId == MODE_TIMER) {
      for (int i = 0; i < 4; i++) {
        if (carret == i) {
          timerTickerController(i, 1);
          viewController(carret, String(timerTicker[i]));
        }
      } 
    }

    if (modeId == MODE_MINUTES_TIMER) {
      for (int i = 0; i < 4; i++) {
        if (carret == i) {
          mTimerTickerController(i, 1);
          viewController(carret, String(mTimerTicker[i]));
        }
      }     
    }

    if (modeId == MODE_GAME) {
      for (int i = 0; i < 4; i++) {
        if (carret == i) {
          gameUnLockerPlayerController(i, 1);
          viewController(carret, String(gameUnLockerData[i]));
        }
      }     
    }
  }
}

// Обработчик второй кнопки
void buttonMiddleEvent (int clickedFirst, int clickedMiddle, int clickedLast) {
  if (modeId == MODE_NONE) {
    selectId--;
    if (selectId < 0) {
      selectId = MODE_MINUTES_TIMER;
    }
    mainOffControllers();
    menuList();
  }

  if (clickedLast == 1 || clickedLast == 2 || clickedLast == 3 || clickedLast == 4) {
    if (modeId == MODE_TIME) {
      for (int i = 0; i < 4; i++) {
        if (carret == i) {
          timeTickerController(i, -1);
          viewController(carret, String(timeTicker[i]));
        }
      }
    }

    if (modeId == MODE_TIMER) {
      for (int i = 0; i < 4; i++) {
        if (carret == i) {
          timerTickerController(i, -1);
          viewController(carret, String(timerTicker[i]));
        }
      } 
    }

    if (modeId == MODE_MINUTES_TIMER) {
      for (int i = 0; i < 4; i++) {
        if (carret == i) {
          mTimerTickerController(i, -1);
          viewController(carret, String(mTimerTicker[i]));
        }
      }     
    }

    if (modeId == MODE_GAME) {
      for (int i = 0; i < 4; i++) {
        if (carret == i) {
          gameUnLockerPlayerController(i, -1);
          viewController(carret, String(gameUnLockerData[i]));
        }
      }     
    }
  }
}

// Обработчик третьей кнопки
void buttonLastEvent (int clickedFirst, int clickedMiddle, int clickedLast) {
  if (modeId == MODE_NONE) {
    modeId = selectId;
    buttonController(0, 0, 0);
  
    if (modeId == MODE_TIME) {
      timeController(true);
    }

    if (modeId == MODE_TIMER) {
      timerController(true);
    }

    if (modeId == MODE_MINUTES_TIMER) {
      mTimerController(true);
    }

    if (modeId == MODE_GAME) {
      gameUnLockerController(true);
    }
    return; 
  }

  if (clickedLast == 1 && modeId == MODE_TIME) {
    mainOffControllers();
    for (int i = 0; i < 4; i++) {
      viewController(i, String(timeTicker[i]));
    }
    viewController(carret, String('_'));  
    animationController(false, "edit"); 
    return;
  }

  if (modeId == MODE_TIME) {
    for (int i = 0; i < 4; i++) {
      viewController(i, String(timeTicker[i]));   
    } 
  
    if (carret < 3) {
      carret++;
      viewController(carret, String('_'));  
      return;
    } else {
      carret = 0;
      modeId = MODE_NONE;
      timeController(true);
      buttonController(0, 0, 0);
    }
  }

  if (clickedLast == 1 && modeId == MODE_TIMER) {
    mainOffControllers();
    for (int i = 0; i < 4; i++) {
      viewController(i, String(timerTicker[i]));
    }
    viewController(carret, String('_'));  
    animationController(false, "edit"); 
    return;
  }

  if (modeId == MODE_TIMER) {
    for (int i = 0; i < 4; i++) {
      viewController(i, String(timerTicker[i]));   
    } 
  
    if (carret < 3) {
      carret++;
      viewController(carret, String('_'));  
      return;
    } else {
      carret = 0;
      modeId = MODE_NONE;
      timerController(true);
      buttonController(0, 0, 0);
    }
  }

  if (clickedLast == 1 && modeId == MODE_MINUTES_TIMER) {
    mainOffControllers();
    for (int i = 0; i < 4; i++) {
      viewController(i, String(mTimerTicker[i]));
    }
    viewController(carret, String('_'));  
    animationController(false, "edit"); 
    return;
  }

  if (modeId == MODE_MINUTES_TIMER) {
    for (int i = 0; i < 4; i++) {
      viewController(i, String(mTimerTicker[i]));   
    } 
  
    if (carret < 3) {
      carret++;
      viewController(carret, String('_'));  
      return;
    } else {
      carret = 0;
      modeId = MODE_NONE;
      mTimerController(true);
      buttonController(0, 0, 0);
    }
  }

  if (clickedLast == 1 && modeId == MODE_GAME) {
    mainOffControllers();
    for (int i = 0; i < 4; i++) {
      viewController(i, String(gameUnLockerData[i]));
    }
    viewController(carret, String('_'));  
    animationController(false, "play"); 
    return;
  }

  if (modeId == MODE_GAME) {
    for (int i = 0; i < 4; i++) {
      viewController(i, String(gameUnLockerData[i]));   
    } 
  
    if (carret < 3) {
      carret++;
      viewController(carret, String('_'));  
      return;
    } else {
      carret = 0;
      modeId = MODE_NONE;
      gameUnLockerController(true);
      buttonController(0, 0, 0);
    }
  }
}

// Отслеживает состояние кнопок
boolean buttonDriverFlag = false;

// Ловит нажатия кнопок
void buttonDriver () {
  if (delayButtonHandle.isExpired()) {
    if (analogRead(BTN_SET_TOP) >= 128) {
      if (!buttonDriverFlag) {
        tone(SIGNAL, 1700, 50);
        clickedFirstButton++;
        buttonFristEvent(clickedFirstButton, clickedMiddleButton, clickedLastButton);
      }
      buttonDriverFlag = true;
      return;
    }

    if (analogRead(BTN_SET_MIDDLE) >= 128) {
      if (!buttonDriverFlag) {
        tone(SIGNAL, 1700, 50);
        clickedMiddleButton++;
        buttonMiddleEvent(clickedFirstButton, clickedMiddleButton, clickedLastButton);
      }
      buttonDriverFlag = true;
      return;
    }
    
    if (analogRead(BTN_SET_BOTTOM) >= 128) {
      if (!buttonDriverFlag) {
        tone(SIGNAL, 1700, 50);
        clickedLastButton++;
        buttonLastEvent(clickedFirstButton, clickedMiddleButton, clickedLastButton);
      }
      buttonDriverFlag = true;
      return;
    }

    buttonDriverFlag = false;
  }
}
Отрисовка на семисигментном индикаторе
/*
 *                     A                
 *              @@@@@@@@@@@@@@@@@      
 *             @@@@@@@@@@@@@@@@@      
 *         @@                    @@   
 *        @@@                   @@@   
 *        @@@                   @@@   
 *       @@@                    @@@   
 *       @@@ F                 @@@ B   
 *       @@@                   @@@    
 *      @@@                    @@     
 *      @@@                   @@@     
 *      @@          G         @@@     
 *         @@@@@@@@@@@@@@@@@@         
 *         @@@@@@@@@@@@@@@@@          
 *     @@                    @@@      
 *    @@@                   @@@       
 *    @@@                   @@@       
 *    @@                    @@@       
 *   @@@ E                 @@@ C       
 *   @@@                   @@@        
 *   @@                    @@@        
 *  @@@                   @@@         
 *  @@@         D         @@@         
 *     @@@@@@@@@@@@@@@@@@      @@     
 *     @@@@@@@@@@@@@@@@@       @@ DP
 * 
 * 
 */

// Состояние
int valueRenderRow[4][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, 0, 0, 0, 0, 0 }
};

// Состояние двоеточия 
boolean dotShow = false;

// Отрисовывет отдельный символ
void view (int* symbol, int offset) {
  digitalWrite(A, 0);
  digitalWrite(B, 0);
  digitalWrite(C, 0);
  digitalWrite(D, 0);
  digitalWrite(E, 0);
  digitalWrite(F, 0);
  digitalWrite(G, 0);
  digitalWrite(DP, 0); 
  
  digitalWrite(ST_CP, 0);
  for (int i = 0; i < 8; i++) {
    digitalWrite(SH_CP, 0);
    digitalWrite(DS, 1); 
    digitalWrite(SH_CP, 1);
  }
  digitalWrite(ST_CP, 1);

  delayMicroseconds(1000);

  for (int r = 0; r < 10; r++) {
    digitalWrite(A, symbol[0]);
    digitalWrite(B, symbol[1]);
    digitalWrite(C, symbol[2]);
    digitalWrite(D, symbol[3]);
    digitalWrite(E, symbol[4]);
    digitalWrite(F, symbol[5]);
    digitalWrite(G, symbol[6]);
    digitalWrite(DP, symbol[7]); 
  
    if (dotShow && offset == 4) {
      digitalWrite(DP, 1); 
    } else {
      digitalWrite(DP, 0); 
    }
    
    digitalWrite(ST_CP, 0);
    for (int i = 0; i < 8; i++) {
      digitalWrite(SH_CP, 0);
      digitalWrite(DS, i != offset); 
      digitalWrite(SH_CP, 1);
    }
    digitalWrite(ST_CP, 1); 
  }
  
  delayMicroseconds(1000);
}

// Отрисовывет символы
void viewDriver () {
  int OFFSET_0 = 6;
  int OFFSET_1 = 4;
  int OFFSET_2 = 3;
  int OFFSET_3 = 5;

  if (delayRenderRowFirst.isExpired()) {
    view(valueRenderRow[0], OFFSET_0);
  }

  if (delayRenderRowTwo.isExpired()) {
    view(valueRenderRow[1], OFFSET_1);
  }

  if (delayRenderRowThree.isExpired()) {
    view(valueRenderRow[2], OFFSET_2);
  }

  if (delayRenderRowFour.isExpired()) {
    view(valueRenderRow[3], OFFSET_3);
  }
}

// Контроллер управляющий символами
void viewController (int offset, String symbol) {
  String viewSymbol = "00000000";

  if (symbol[0] == '0') { viewSymbol = "11111100"; }
  if (symbol[0] == '1') { viewSymbol = "01100000"; }
  if (symbol[0] == '2') { viewSymbol = "11011010"; }
  if (symbol[0] == '3') { viewSymbol = "11110010"; }
  if (symbol[0] == '4') { viewSymbol = "01100110"; }
  if (symbol[0] == '5') { viewSymbol = "10110110"; }
  if (symbol[0] == '6') { viewSymbol = "10111110"; }
  if (symbol[0] == '7') { viewSymbol = "11100000"; }
  if (symbol[0] == '8') { viewSymbol = "11111110"; }
  if (symbol[0] == '9') { viewSymbol = "11110110"; }
  if (symbol[0] == ' ') { viewSymbol = "00000000"; }
  if (symbol[0] == '_') { viewSymbol = "00010000"; }
  if (symbol[0] == 'a') { viewSymbol = "11101110"; }
  if (symbol[0] == 'b') { viewSymbol = "00111110"; }
  if (symbol[0] == 'c') { viewSymbol = "00011010"; }
  if (symbol[0] == 'd') { viewSymbol = "01111010"; }
  if (symbol[0] == 't') { viewSymbol = "00011110"; }
  if (symbol[0] == 'e') { viewSymbol = "10011110"; }
  if (symbol[0] == 'f') { viewSymbol = "10001110"; }
  if (symbol[0] == 'g') { viewSymbol = "11110110"; }
  if (symbol[0] == 'h') { viewSymbol = "00101110"; }
  if (symbol[0] == 'i') { viewSymbol = "00001100"; }
  if (symbol[0] == 'j') { viewSymbol = "01111000"; }
  if (symbol[0] == 'k') { viewSymbol = "01101110"; }
  if (symbol[0] == 'l') { viewSymbol = "00011100"; }
  if (symbol[0] == 'm') { viewSymbol = "00101010"; }
  if (symbol[0] == 'n') { viewSymbol = "00101010"; }
  if (symbol[0] == 'o') { viewSymbol = "00111010"; }
  if (symbol[0] == 'p') { viewSymbol = "11001110"; }
  if (symbol[0] == 'q') { viewSymbol = "11100110"; }
  if (symbol[0] == 'r') { viewSymbol = "11001100"; }
  if (symbol[0] == 's') { viewSymbol = "10110110"; }
  if (symbol[0] == 't') { viewSymbol = "00011110"; }
  if (symbol[0] == 'u') { viewSymbol = "00111000"; }
  if (symbol[0] == 'v') { viewSymbol = "01111100"; }
  if (symbol[0] == 'w') { viewSymbol = "00111000"; }
  if (symbol[0] == 'x') { viewSymbol = "01101110"; }
  if (symbol[0] == 'x') { viewSymbol = "01101110"; }
  if (symbol[0] == 'y') { viewSymbol = "01110110"; }
  if (symbol[0] == 'z') { viewSymbol = "11011010"; }
 
  for (int i = 0; i < 8; i++) {
    valueRenderRow[offset][i] = String(viewSymbol[i]).toInt();
  }
}

// Контроллер отвечающий за двоеточие
void viewControllerDot (boolean isShow) {
  dotShow = isShow;
}
Анимации переходов
// Состояние переходов анимации
float animationTicker = -1;

// Звук в анимации
int animationSound = true;

// Сообщение анимации
String animationMessage = "";

// Последнее отрисовоное состояние, так как я не понял как 
// работают коллбеки и есть ли они вообще, я придумал свой способ 
// тут я храню то что было отрисованно в основном стейте чтобы показать его после анимации 
int animationSaveValueRow[4][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, 0, 0, 0, 0, 0 }
};

// Сохраняем основное состояние
void animationSaveState () {
  for (int y = 0; y < 4; y++) {
    for (int x = 0; x < 8; x++) {
      animationSaveValueRow[y][x] = valueRenderRow[y][x];
    }  
  }
}

// Восстанавливаем состояние
void animationPushState () {
  for (int y = 0; y < 4; y++) {
    for (int x = 0; x < 8; x++) {
      valueRenderRow[y][x] = animationSaveValueRow[y][x];
    }  
  }
}

// Отрисовывем анимацию
void animationDriver () {
  if (delayAnimaton.isExpired()) {
    if (animationTicker != -1) {
      animationTicker += 0.2;      
    }

    if (animationTicker > 32 || (animationMessage.length() == 0 && animationTicker > 16)) {
      animationPushState();
      animationTicker = -1;
      return;
    }

    if (animationTicker == -1) {
      return;
    }

    viewController(0, String(' ')); 
    viewController(1, String(' ')); 
    viewController(2, String(' ')); 
    viewController(3, String(' ')); 

    if (animationSound) {
      if (int(animationTicker) > 23) {
        tone(SIGNAL, (100 * (int(animationTicker) + 1) + 500), 50);
      } else {
        if (int(animationTicker) % 23) {
          tone(SIGNAL, (20 * (int(animationTicker) + 1) + 500), 50);
        } else {
          noTone(SIGNAL); 
        }
      }
    }

    if (animationMessage.length() != 0 && int(animationTicker) > 8 && int(animationTicker) < 24) {
      for (int x = 0; x < 4; x++) { 
        viewController(x, String(animationMessage[x]));
      }
      return;
    } else {
      if (int(animationTicker) % 8 < 4) {
        viewController(int(animationTicker) % 4, String('0')); 
        return;
      } else {
        viewController(int(animationTicker) % 4, String(' '));
        return;
      } 
    }
  }
}

// Контроллируем состояние анимации
void animationController (boolean isSound, String message) {  
  animationSaveState();
  animationMessage = message;
  animationSound = isSound;
  animationTicker = 0;
}

Виджеты

Время
// Изначательное время
int timeTicker[4] = { 1,2,4,8 };
// Счетчик секунд
int timeSecond = 0;

// Последнее время внутреннего счетчика микроконтроллера
uint32_t timeDelta;

// Флаг включающий и отключающий виджет 
boolean timeShow = false;

// Управление виджетом
void timeController (boolean isShow) {
  timeShow = isShow;
  viewControllerDot(isShow);
}

// Управление состоянием виджета
void timeTickerController (int offset, int n) {
  if (offset == 0) {
    timeTicker[0] += n;
    if (timeTicker[0] > 2) {
      timeTicker[0] = 0;
    }
    if (timeTicker[0] < 0) {
      timeTicker[0] = 2;
    }
  }

  if (offset == 1) {
    timeTicker[1] += n;

    if (timeTicker[0] < 2) {
      if (timeTicker[1] < 0) {
        timeTicker[1] = 9;
      }
      if (timeTicker[1] > 9) {
        timeTicker[1] = 0;
      }
    } else {
      if (timeTicker[1] < 0) {
        timeTicker[1] = 3;
      }
      if (timeTicker[1] > 3) {
        timeTicker[1] = 0;
      }
    }
  }

  if (offset == 2) {
    timeTicker[2] += n;
    if (timeTicker[2] > 5) {
      timeTicker[2] = 0;
    }
    if (timeTicker[2] < 0) {
      timeTicker[2] = 5;
    }
  }

  if (offset == 3) {
    timeTicker[3] += n;
    if (timeTicker[3] > 9) {
      timeTicker[3] = 0;
    }
    if (timeTicker[3] < 0) {
      timeTicker[3] = 9;
    }
  }
}

// Отвечает за ход времени
void timeDriver () {
  if (timeShow && millis() - timeDelta >= 1000) {   
    timeDelta = millis();              

    timeSecond++;
    viewControllerDot(timeSecond % 2 == 0);
    if (timeSecond > 59) {
      timeSecond = 0;
      timeTicker[3]++;
      if (timeTicker[3] > 9) {
        timeTicker[3] = 0;
        timeTicker[2]++;
        if (timeTicker[2] > 5) {
          timeTicker[2] = 0;
          timeTicker[1]++;
          if ((timeTicker[0] < 2 && timeTicker[1] > 9) || (timeTicker[0] == 2 && timeTicker[1] > 3)) {
            timeTicker[1] = 0;
            timeTicker[0]++;
            if (timeTicker[0] > 2) {
              timeTicker[0] = 0;
            }
          }  
        } 
      }
    }
    
    viewController(0, String(timeTicker[0])); 
    viewController(1, String(timeTicker[1])); 
    viewController(2, String(timeTicker[2])); 
    viewController(3, String(timeTicker[3])); 
  }
}
Таймер
// Изначальное время
int timerTicker[4] = { 0, 0, 3, 0 };
// Счетчик секунд
int timerSecond = 59;

// Последнее время внутреннего счетчика микроконтроллера
uint32_t timerDelta;

// Флаг включающий и отключающий виджет
boolean timerShow = false;

// Управление виджетом
void timerController (boolean isShow) {
  timerShow = isShow;
  viewControllerDot(isShow);
}

// Управление состоянием виджета
void timerTickerController (int offset, int n) {
  if (offset == 0) {
    timerTicker[0] += n;
    if (timerTicker[0] > 2) {
      timerTicker[0] = 0;
    }
    if (timerTicker[0] < 0) {
      timerTicker[0] = 2;
    }
  }

  if (offset == 1) {
    timerTicker[1] += n;

    if (timerTicker[0] < 2) {
      if (timerTicker[1] < 0) {
        timerTicker[1] = 9;
      }
      if (timerTicker[1] > 9) {
        timerTicker[1] = 0;
      }
    } else {
      if (timerTicker[1] < 0) {
        timerTicker[1] = 3;
      }
      if (timerTicker[1] > 3) {
        timerTicker[1] = 0;
      }
    }
  }

  if (offset == 2) {
    timerTicker[2] += n;
    if (timerTicker[2] > 5) {
      timerTicker[2] = 0;
    }
    if (timerTicker[2] < 0) {
      timerTicker[2] = 5;
    }
  }

  if (offset == 3) {
    timerTicker[3] += n;
    if (timerTicker[3] > 9) {
      timerTicker[3] = 0;
    }
    if (timerTicker[3] < 0) {
      timerTicker[3] = 9;
    }
  }
}

// Отвечает за обратный отсчет
void timerDriver () {
  if (timerShow && millis() - timerDelta >= 1000) {   
    timerDelta = millis();              

    timerSecond--;
    viewControllerDot(timerSecond % 2 == 0);
    if (timerSecond < 0) {
      timerSecond = 60;
      timerTicker[3]--;
      if (timerTicker[3] < 0) {
        timerTicker[3] = 9;
        timerTicker[2]--;
        if (timerTicker[2] < 0) {
          timerTicker[2] = 5;
          timerTicker[1]--;
          if (timerTicker[1] < 0) {
            timerTicker[1] = 9;
            timerTicker[0]--;
            if (timerTicker[0] < 0) {
              timerTicker[0] = 0;
            } 
          }
        }
      }
    }
        
    viewController(0, String(timerTicker[0])); 
    viewController(1, String(timerTicker[1])); 
    viewController(2, String(timerTicker[2])); 
    viewController(3, String(timerTicker[3]));

    if (
      timerTicker[0] == 0 && 
      timerTicker[1] == 0 && 
      timerTicker[2] == 0 && 
      timerTicker[3] == 0
    ) {
      timerController(false);
      viewControllerDot(false);
      animationController(true, "end");
    } 
  }
}
Минутный таймер
// Изначальное время
int mTimerTicker[4] = { 0, 0, 0, 5 };

// Последнее время внутреннего счетчика микроконтроллера
uint32_t mTimerDelta;

// Флаг включающий и отключающий виджет
boolean mTimerShow = false;

// Управление виджетом
void mTimerController (boolean isShow) {
  mTimerShow = isShow;
  viewControllerDot(isShow);
}

// Управление состоянием виджета
void mTimerTickerController (int offset, int n) {
  if (offset == 0) {
    mTimerTicker[0] += n;
    if (mTimerTicker[0] > 5) {
      mTimerTicker[0] = 0;
    }
    if (mTimerTicker[0] < 0) {
      mTimerTicker[0] = 5;
    }
  }

  if (offset == 1) {
    mTimerTicker[1] += n;
    if (mTimerTicker[1] > 9) {
      mTimerTicker[1] = 0;
    }
    if (mTimerTicker[1] < 0) {
      mTimerTicker[1] = 9;
    }
  }

  if (offset == 2) {
    mTimerTicker[2] += n;
    if (mTimerTicker[2] > 5) {
      mTimerTicker[2] = 0;
    }
    if (mTimerTicker[2] < 0) {
      mTimerTicker[2] = 5;
    }
  }

  if (offset == 3) {
    mTimerTicker[3] += n;
    if (mTimerTicker[3] > 9) {
      mTimerTicker[3] = 0;
    }
    if (mTimerTicker[3] < 0) {
      mTimerTicker[3] = 9;
    }
  }
}

// Отвечает за обратный отсчет
void mTimerDriver () {
  if (mTimerShow && millis() - mTimerDelta >= 1000) {   
    mTimerDelta = millis();              

    mTimerTicker[3]--;
    viewControllerDot(mTimerTicker[3] % 2 == 0);
    if (mTimerTicker[3] < 0) {
      mTimerTicker[3] = 9;
      mTimerTicker[2]--;
      if (mTimerTicker[2] < 0) {
        mTimerTicker[2] = 5;
        mTimerTicker[1]--;
        if (mTimerTicker[1] < 0) {
          mTimerTicker[1] = 9;
          mTimerTicker[0]--;
          if (mTimerTicker[0] < 0) {
            mTimerTicker[0] = 5;
          }
        }  
      }
    }
        
    viewController(0, String(mTimerTicker[0])); 
    viewController(1, String(mTimerTicker[1])); 
    viewController(2, String(mTimerTicker[2])); 
    viewController(3, String(mTimerTicker[3]));

    if (
      mTimerTicker[0] == 0 && 
      mTimerTicker[1] == 0 && 
      mTimerTicker[2] == 0 && 
      mTimerTicker[3] == 0
    ) {
      mTimerController(false);
      viewControllerDot(false);
      animationController(true, "end");
    } 
  }
}
Игра
// Изначальное состояние
int gameUnLockerData[4] = { 0,0,0,0 };

// Состояние измененное случайным образом
int gameUnLockerHiddenData[4] = { 
  random(0, 9),
  random(0, 9),
  random(0, 9),
  random(0, 9)
};

// Флаг включающий и отключающий виджет
boolean gameUnLockerShow = false;

// Управление виджетом
void gameUnLockerController (boolean isShow) {
  gameUnLockerShow = isShow; 
}

// Управление состояние виджета
void gameUnLockerPlayerController (int offset, int n) {
  if (offset == 0) {
    gameUnLockerData[0] += n;
    if (gameUnLockerData[0] > 9) {
      gameUnLockerData[0] = 0;
    }
    if (gameUnLockerData[0] < 0) {
      gameUnLockerData[0] = 9;
    }
  }

  if (offset == 1) {
    gameUnLockerData[1] += n;
    if (gameUnLockerData[1] > 9) {
      gameUnLockerData[1] = 0;
    }
    if (gameUnLockerData[1] < 0) {
      gameUnLockerData[1] = 9;
    }  
  }

  if (offset == 2) {
    gameUnLockerData[2] += n;
    if (gameUnLockerData[2] > 9) {
      gameUnLockerData[2] = 0;
    }
    if (gameUnLockerData[2] < 0) {
      gameUnLockerData[2] = 9;
    }   
  }

  if (offset == 3) {
    gameUnLockerData[3] += n;
    if (gameUnLockerData[3] > 9) {
      gameUnLockerData[3] = 0;
    }
    if (gameUnLockerData[3] < 0) {
      gameUnLockerData[3] = 9;
    }    
  }
}

// Обработка состояния виджета
void gameUnLockerDriver () {
  if (gameUnLockerShow) {
    if (
      gameUnLockerData[0] == gameUnLockerHiddenData[0] &&
      gameUnLockerData[1] == gameUnLockerHiddenData[1] &&
      gameUnLockerData[2] == gameUnLockerHiddenData[2] &&
      gameUnLockerData[3] == gameUnLockerHiddenData[3] 
    ) {
      viewController(0, String('g'));
      viewController(1, String('o'));
      viewController(2, String('o'));
      viewController(3, String('d')); 
    } else {
      viewController(0, String('b'));
      viewController(1, String('a'));
      viewController(2, String('d'));
      viewController(3, String(' '));   
    } 
  }
}

Демонстрационная версия

Я искренне надеюсь, что это получился хороший материал для начинающих, пробовать себя в электронике и программировании. Полный код проекта, вы также можете найти на GitHub.

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

Tags:
Hubs:
Total votes 39: ↑28 and ↓11+17
Comments56

Articles