Паяем простые электронные часы на базе ATmega328P

27 января 2017

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

Честно говоря, поначалу я хотел сделать электронные часы основанными на микросхемах стандартной логики 74xx и таймере 555. То есть, безо всяких микроконтроллеров. Как оказалось, это вполне реально, и потребует всего лишь пары логических И. Однако, такие часы оказались не очень точными. С помощью потенциометров можно установить частоту таймера очень близкой к 1 Гц, но недостаточно близкой. Мои эксперименты показали, что за год такие часы начнут врать часов на 8, и это в лучшем случае. Плюс к этому, такие часы физически будут занимать больше места, чем основанные на микроконтроллере, не говоря уже о том, что их функционал будет крайне сложно расширить (скажем, добавить будильник). В итоге от идеи полностью аналоговых часов пришлось отказаться. Тем не менее, рекомендую подумать над схемой таких часов в качестве упражнения.

Первая версия часов, основанная уже на ATmega328P, выглядела так:

Часы на основе ATmega328P на макетной плате

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

Принцип работы следующий. Раз в 50 мс микроконтроллер просыпается, шлет всем счетчикам Reset, а затем посылает каждому из них сигналы инкремента в количестве, соответствующем цифре, которую должен отобразить индикатор, подсоединенный к счетчику. То есть, на самом деле индикаторы сбрасываются и выставляются заново 20 раз в секунду, но визуально это совершенно незаметно.

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

Окончательный вариант часов на ATmega328P

Код прошивки:

#include <Arduino.h>

/* inc/dec buttons */
const int HINC_PIN = A5;
const int HDEC_PIN = A4;
const int MINC_PIN = A3;
const int MDEC_PIN = A2;

/* pin to 4026 IC's reset inputs */
const int RESET_PIN = 5;

const int DOT_PIN = 8;

/* pins to 4026 IC clock inputs */
const int HPIN_H = 10;
const int HPIN_L = 9;
const int MPIN_H = 7;
const int MPIN_L = 6;

/* current time */
int hour = 0;
int min = 0;
int sec = 0;
int msec = 0;

bool hinc_released = true;
bool hdec_released = true;
bool minc_released = true;
bool mdec_released = true;

unsigned long prevMillis;

void setup()
{
  pinMode(HDEC_PIN, INPUT);
  pinMode(HINC_PIN, INPUT);
  pinMode(MDEC_PIN, INPUT);
  pinMode(MINC_PIN, INPUT);

  pinMode(RESET_PIN, OUTPUT);
  pinMode(DOT_PIN, OUTPUT);

  pinMode(HPIN_H, OUTPUT);
  pinMode(HPIN_L, OUTPUT);
  pinMode(MPIN_H, OUTPUT);
  pinMode(MPIN_L, OUTPUT);

  prevMillis = millis();
}

void displayTime()
{
  digitalWrite(RESET_PIN, HIGH);
  digitalWrite(RESET_PIN, LOW);

  digitalWrite(DOT_PIN, (sec & 1) ? HIGH : LOW);

  for(int i = 0; i < min % 10; i++)
  {
    digitalWrite(MPIN_L, HIGH);
    digitalWrite(MPIN_L, LOW);
  }

  for(int i = 0; i < min / 10; i++)
  {
    digitalWrite(MPIN_H, HIGH);
    digitalWrite(MPIN_H, LOW);
  }

  for(int i = 0; i < hour % 10; i++)
  {
    digitalWrite(HPIN_L, HIGH);
    digitalWrite(HPIN_L, LOW);
  }

  for(int i = 0; i < hour / 10; i++)
  {
    digitalWrite(HPIN_H, HIGH);
    digitalWrite(HPIN_H, LOW);
  }
}

void loop()
{
  unsigned long currMillis;
  unsigned long delta;

  delay(50);

  currMillis = millis();
  delta = currMillis - prevMillis;
  prevMillis = currMillis;
  msec += delta;

  while(msec >= 1000)
  {
    msec -= 1000;
    sec++;
    if(sec == 60)
    {
      sec = 0;
      min++;
      if(min == 60)
      {
        min = 0;
        hour++;
        if(hour == 24)
          hour = 0;
      }
    }
  }

  displayTime();

  if(digitalRead(HDEC_PIN) == LOW)
  {
    if(hdec_released)
    {
      hdec_released = false;
      hour--;
      if(hour < 0)
        hour = 23;
    }
  } else
    hdec_released = true;

  if(digitalRead(HINC_PIN) == LOW)
  {
    if(hinc_released)
    {
      hinc_released = false;
      hour++;
      sec = msec = 0;
      if(hour > 23)
        hour = 0;
    }
  } else
    hinc_released = true;

  if(digitalRead(MDEC_PIN) == LOW)
  {
    if(mdec_released)
    {
      mdec_released = false;
      min--;
      sec = msec = 0;
      if(min < 0)
        min = 59;
    }
  } else
    mdec_released = true;

  if(digitalRead(MINC_PIN) == LOW)
  {
    if(minc_released)
    {
      minc_released = false;
      min++;
      sec = msec = 0;
      if(min > 59)
        min = 0;
    }
  } else
    minc_released = true;
}

Процедура millis() возвращает количество миллисекунд, прошедшее с момента запуска микроконтроллера. Мы не можем считать, что между двумя вызовами delay(50) в процедуре loop() проходит ровно 50 миллисекунд, так как исполнение остального кода вносит значимую задержку. При таком подходе часы быстро начнут врать. Поэтому мы измеряем реально прошедшее время при помощи millis().

Питание я решил подавать по USB. Мне не хотелось постоянно менять батарейки, а USB-кабелей и зарядников в любом доме нынче в избытке. Плюс у меня есть USB-аккумулятор, благодаря которому часы не перестанут идти, если внезапно отключат свет. Но если вам больше нравятся батарейки, часы будут прекрасно работать от двух батареек AA или одной CR2032.

Плата, как несложно догадаться по форме дорожек, была разведена вручную. Изначально я пытался сделать ее при помощи ЛУТ. Но поскольку это был мой первый опыт изготовления двухсторонней печатной платы, я не учел, что расстояние между отверстиями и дорожками на обратной стороне стоит сделать побольше. Пару отверстий я просверлил не совсем под прямым углом. В итоге на обратной стороне сверло вышло вовсе не там, где нужно, повредив дорожки. Я пытался исправить ошибку, навешивая «сопли», но не преуспел — плата так и осталась нерабочей. Изготовление двухсторонней платы 11x9 см занимает довольно много времени. Поэтому вместо того, чтобы переделывать плату, я просто заказал ее на Резоните.

Если вы полны решимости сделать такие же часы и собираетесь изготовить плату при помощи ЛУТ, советую переделать плату в одностороннюю. На второй стороне почти нет дорожек, вместо них можно использовать обычные провода. Если же вам интересно поупражняться в изготовлении двухсторонних плат, советую ее слегка увеличить, хотя бы до 12x10 см, и сделать так, чтобы дорожки на обратной стороне были как можно дальше от каких-либо отверстий. Травление двухсторонних плат происходит так. Травиться первая сторона, вторая при этом заклеивается скотчем. Сверлиться три отверстия. Тонер на вторую сторону наносится по ним. Затем первая сторона заклеивается и травиться вторая.

Исходники двух версий прошивок, для часов с секундами и без, а также исходники платы в формате EAGLE вы найдете в этом репозитории на GitHub. Стоит отметить, что на самом деле часы лучше делать совсем не так. Как уже отмечалось, можно обойтись вообще без счетчиков 4026. Большинство компонентов можно заменить на компоненты для поверхностного монтажа. За счет этого часы получаться компактнее, их будет проще сделать ЛУТом, и отверстий придется сверлить намного меньше. Кроме того, раз уж мы все равно использовали микроконтроллер, стоит добавить в часы будильник и термометр, а также синхронизировать время при помощи GPS. Увы, мы со всем этим еще не знакомы, но непременно скоро познакомимся.

А какие улучшения предложили бы вы?

Дополнение: См также заметки Научился выводить текст на ЖК-индикатор из Arduino и Два способа мультиплексирования светодиодов на примере микроконтроллеров AVR.

Метки: , .


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