Сегодня разберём решение небольшой практической задачи: запустим советские вторичные электромеханические часы максимально простым способом. Попутно узнаем несколько простых и не очень вещей из области электроники и программирования микроконтроллеров.
Сложность проекта в самый раз для начинающих, уже сделавших свои первые шаги, и теперь нуждающихся в чуть более сложных следующих шагах.
▍ Начало
Этот проект выходного дня начался с того, что не особо искушённый в электронике друг раздобыл большие круглые советские часы с двумя торчащими из них проводами, захотел их запустить, но не знал как, и обратился за помощью ко мне. Ведь у меня есть Гугл.
В Гугле мне сказали, что для запуска таких часов есть множество решений. Как оказалось, на эту тему в интернете уже десятки статей и видео — похоже, что только ленивый не затронул эту тему. В том есть статьи и Хабре.
Признаюсь, что ничего не могу сказать обо всём этом, потому что чукча не читатель и готовых решений не покупатель: задачка обещала быть несложной, захотелось разобраться самому и почувствовать радость от изобретения личного велосипеда. Теперь в интернете и на Хабре будет на одну такую статью больше.
Для разнообразия решить эту задачу я задумал при помощи простых народных средств, в лице готовых электронных модулей, которые были под рукой. И один из них, разумеется, Arduino Nano.
Модули для проекта
Конечно, найдутся люди, которые скажут: «Опять эти ваши ардуины, ничего-то без них сделать не можете; это как из пушки по микроскопам, а вот в наше-то время это делалось на двух логических микросхемах».
С одной стороны, да, но с другой Arduino и был создан для решения простых задач несоразмерными их сложности мощностями. Таков его путь. И, боюсь, проектами, где имеющемуся вычислительному потенциалу нашлось бы действительно достойное применение — управлением реактором или там лунной ракетой — я займусь нескоро.
Максимально же простая конструкция из доступных в любом ларьке деталей с понятными объяснениями оказалась прекрасным поводом для создания материала, имеющего некоторую познавательную ценность, и в том числе демонстрирующего ограниченность применимости Arduino даже в довольно простых на первый взгляд задачах.
▍ Вторичные часы
Не буду читать пространную лекцию про историю часофикации и систем единого времени, так как я не настоящий сварщик, и сам заинтересовался вопросом совсем недавно, в связи с описываемым проектом.
Ранее я имел лишь общее представление о существовании подобных систем, так как имел возможность лицезреть первичные часы в детстве. Они были красивые, железные, с рядами микросхем под тонированным стеклом, и исправно работали в школьной учительской, не давая никакой возможности немедленно разобрать их на красивые запчасти, чтобы собрать очередной мультивибратор. Осадочек остался.
Первичные часы
И всё же краткого брифинга не избежать. Вскоре после вымирания динозавров человечеству для повышения эффективности совместной деятельности понадобились источники синхронного, иначе говоря, единого времени. Интернета и мобильных устройств с глобальной синхронизацией времени ещё не было, а личные часы могли спешить или отставать.
Чтобы миллионы рабочих могли вовремя узнать, когда нужно уходить с работы, а спешащие на трамвай могли понимать, что уже опоздали или ещё не успели, повсюду были установлены крупные, хорошо заметные часы: на площадях, остановках, в метро, на каждом заводе, в каждой организации и образовательных учреждениях. Тысячи их.
Вторичные часы
Но тысячи часов потребовали бы значительных усилий и появления профессии часовщика, по аналогии с фонарщиком, чтобы следить за синхронностью и точностью их хода вручную. К счастью, к этому времени уже изобрели электричество. Так были созданы электрические системы единого времени.
В двух словах идея заключается в том, что источник точного времени один — первичные часы, а тысячи часов на стенах — только показометр, вторичные часы, получающие информацию о ходе времени по проводам от единственных первичных часов. Те же, в свою очередь, могут синхронизироваться по чему-нибудь ещё, например, сигналам точного времени на радио.
Тоже вторичные часы
Иначе говоря, вторичные часы так называются не потому, что они бывшие в употреблении, а потому что это и не часы вовсе. Это всего лишь шкала со стрелками или табло с цифрами. Они не умеют считать время самостоятельно, только показывают. Для превращения их в полноценные часы требуется источник управляющего сигнала — первичные часы. К одним первичным часам может подключаться несколько десятков вторичных, и все они будут передвигать стрелки одновременно.
К слову, если вы застали фразу «передаём сигналы точного времени», звучавшую раз в сутки на основных радиостанциях и по проводному вещанию в сопровождении шести коротких звуковых сигналов, это оно — источник синхронизации, так называемые сигналы проверки времени, или «шесть точек». Они используются уже более ста лет, и это простейшая система, используемая для задач с низкой ответственностью. Есть множество других, более точных и продвинутых систем, и это весьма интересная тема, однако выходящая за рамки данной статьи и моих компетенций.
Сами вторичные часы могут быть электромеханическими или цифровыми, обладать стрелками или цифровым табло.
Весь механизм вторичных часов
Электромеханические часы — это исключительно простое и красивое устройство и, судя по всему, крайне надёжное. Оно включает в себя простейший редуктор из нескольких шестерёнок, катушку электромагнита и главный элемент — вращающийся круглый магнит с двумя шайбами-щёчками с выемками, находящийся между полюсами электромагнита.
Электромагнитный привод
Шайбы имеют смещение относительно друг друга на половину шага выемок, и фактически представляют собой полюса постоянного магнита. Это позволяет элементу поворачиваться на долю оборота при переключении полярности магнитного поля и останавливаться в таком состоянии до следующего изменения полярности. Можно сказать, что это примитивная вариация шагового двигателя.
Электромеханические вторичные часы обладают только способностью синхронного хода, но если показания нескольких часов разбежались, придётся идти за стремянкой, чтобы покрутить колёсико или выполнить иные манипуляции, так как все они подсоединяются к источнику синхронизации параллельно.
Цифровые вторичные часы по устройству мало отличаются от своих полноценных собратьев — это такой же набор счётчиков-делителей и декодеров. Отличаются они источником тактовых импульсов, а в продвинутых вариантах и наличием возможности дистанционной установки времени особой последовательностью сигналов.
▍ Управление часами
Есть много разных систем управления вторичными часами, особенно в наше высокотехнологичное время. Классическая система использует всего два провода, к которым все вторичные часы подключаются параллельным соединением, в том числе и разных типов. Способы передачи сигналов по этим проводам, однако, могут отличаться.
Вторичные часы бывают минутные и секундные. И те и другие имеют две стрелки, часовую и минутную. Разница в том, что минутные часы передвигают стрелки раз в минуту скачком от одного деления к другому, а стрелки секундных часов движутся плавно, с проходом между делениями.
Управляются и те и другие часы постоянным током с типичным напряжением около 24 вольт. Как правило, используется знакопеременная система: напряжение подаётся короткими импульсами, и каждый импульс меняет полярность. Другими словами, происходит цикл из четырёх шагов: ничего, плюс и минус, ничего, минус и плюс. Также есть современные системы с однополярными импульсами. Стрелки сдвигаются на следующую позицию только по приходу очередного импульса.
Как же при такой системе установить время, не залезая под потолок и не снимая каждые часы? Только увеличением частоты следования управляющих импульсов. Это называется «подгоном» — время начинает идти быстрее. Обратный ход не предусмотрен конструкцией электромагнитной системы вторичных часов, если нужно подвести время назад, придётся проходить полный круг вперёд.
Для минутных часов импульсы следуют каждую минуту, а длительность импульса составляет около 1.6 секунды. В режиме подгона частота следования импульсов увеличивается примерно до 4 секунд, что даёт ускорение хода в 15 раз. Для секундных часов частота следования импульсов составляет одну секунду, длительность 0.25 секунды, а в режиме подгона частота 0.5 секунды — ускорение хода всего в два раза.
Если придерживаться стандартов, подгон получается очень неспешным, к тому же подгонять секундные часы придётся значительно дольше. На практике же часы неплохо работают и с гораздо более частыми и короткими импульсами.
Для электронных вторичных часов есть способы синхронизации, позволяющие дистанционно установить нужные показания. Они передают информацию посредством изменения длительности управляющих импульсов, которые при этом остаются в пределах допусков для электромеханических часов, что обеспечивает совместимость между устройствами разного типа, подключаемыми к одной управляющей линии.
Кодировка может включать признак синхронизации в виде серии импульсов с длиной, отличающейся от стандартной. После определённого числа признаков передаётся номер текущего часа, заданный в длительности очередного импульса: чем больше времени, тем длиннее импульс.
▍ Arduino и время
В задачах, связанных с точным отсчётом времени на Arduino часто применяют готовый внешний модуль RTC (Real Time Clock) — часов реального времени. Он считает время внутри себя, в нём предусмотрено всё, что нужно для точного хода, а Arduino только запрашивает показания.
Типичный RTC-модуль на микросхеме DS1302
Причина этого излишнего, на первый взгляд, усложнения конструкции заключается в точности генератора, задающего тактовую частоту Arduino. Как правило, для этих целей используются кварцевые и керамические резонаторы, имеющие отклонение частоты от эталонных 16 МГц порядка 0.01..0.07%, что даёт отклонение 8..60 секунд в сутки.
Но всё же для простого счёта времени достаточно и возможностей самого Arduino, по крайней мере для, таких неответственных проектов, как самодельные часы. Нужно только правильно задействовать имеющиеся ресурсы и выжать из них максимально стабильный счёт.
По сути, задача управления вторичными часами — это вариация на тему простейшего мигания светодиодом, только очень точная по времени.
Типичный Arduino Nano китайского производства
Первая идея, которая может прийти в голову новичку — сделать простейший цикл с delay. Либо так:
void loop() {
make_pulse();
delay(1000);
}
Либо, обладая пониманием, что снаружи loop выполняется какой-то код, на что требуется некоторое неопределённое время, так:
void loop() {
while(1)
{
make_pulse();
delay(1000);
}
}
Но и в этом случае часы будут идти неточно, так как и код формирования управляющего импульса, и вызов функции задержки, и даже обвязка цикла while занимают какое-то время, пусть и незначительное, и постепенно оно будет складываться в секунды и минуты. При разрешённых прерываниях это время к тому же не гарантировано, так как может прийти прерывание, и его обработка также займёт некоторое время.
Можно компенсировать точность хода изменением задержки в delay. Только это должна быть более точная задержка delayMicroseconds, а значение придётся подбирать методом тыка, что потребует немало времени, и к тому же любое изменение кода потребует изменения этой компенсирующей задержки.
Первая часть решения проста. У Arduino (точнее, у контроллера ATmega) есть аппаратные таймеры, способные генерировать прерывания. Нужно задействовать прерывание от такого таймера, работающего в циклическом режиме.
Таймер управляется входной тактовой частотой, которая не подвержена влиянию никаких других процессов — какой бы код ни выполнялся, по завершении отсчёта заданного количества тактов таймер сгенерирует прерывание и сразу же начнёт цикл отсчёта заново. Код обработчика при этом может выполняться сколь угодно долго, следующее прерывание придёт через точно такое же время. Конечно, коду обработчика лучше бы успеть выполниться до того, как это случится — иначе новое прерывание не будет обработано.
Вторая часть решения проблемы несколько менее очевидна. Нужно правильно выбрать предварительный и программируемый делители частоты таймера. Нельзя просто так взять и установить секундный интервал для таймера. К тому же одного только секундного интервала недостаточно, ведь управляющие импульсы должны иметь определённую длительность.
Таймеры AVR, такие простые и понятные
Таймеры в микроконтроллерах AVR, на базе одного из которых построена платформа Arduino, работают следующим образом. Тактовая частота всего контроллера, задаваемая внешним или внутренним источником, сначала делится на 1, 8, 64, 256 или 1024 — так называемый предварительный делитель (предделитель, прескалер), а потом на заданное значение. Набор таймеров в разных моделях контроллеров отличается. Конкретно в Arduino на базе ATmega32 есть три таймера: два восьмибитных — их максимальный программируемый делитель 256, и один 16-битный — его максимальный делитель 65536.
Для простоты кода я решил ограничиться использованием только одного 8-битного таймера. Мне представлялось, что пара элементарных программных счётчиков-делителей проще в понимании, чем один 16-битный таймер, запускающий другой 8-битный.
Arduino тактируется внешним кристаллом с рабочей частотой 16000000 герц — то есть, столько импульсов тактирования получает система за одну секунду. Чтобы получить секундный счёт, нужно разделить 16000000 на 16000000. Как можно догадаться, такой делитель слегка больше, чем даже максимальный 16-битный делитель таймера.
Поэтому нужно подобрать подходящий предделитель и делитель. Важно: сделать это нужно таким образом, чтобы всюду получались только целые числа. Ведь если мы поделим число с остатком, дробная часть будет просто теряться, и со временем будет накапливаться погрешность, которая выразится в неточном ходе часов.
К счастью, тактовая частота в 16 МГц и набор предделителей выбраны таким образом, чтобы любой пред-делитель давал целое число:
16000000/1=16000000
16000000/8=2000000
16000000/64=250000
16000000/256=62500
16000000/1024=15625
Остаётся только выбрать программируемый делитель в допустимом диапазоне, также дающий целое число. Я использую 8-битный таймер, значит, диапазон деления у меня 1..256.
Как можно снова догадаться, даже если я поделю наименьшее из входных значений 15625 на наибольший 8-битный делитель 256, один герц мне не получить (но если хочется, то 16-битным таймером можно). Поэтому я выбрал делитель 125 дающий — магия цифр! — 125 герц: 16000000/1024/125=125.
Но зачем мне 125, когда нужно 1? Я могу поделить это число ещё раз, теперь уже в программе, простейшим счётчиком. Поделив его на 125, я получу секундные периодические импульсы счёта. Также это позволит мне отсчитывать длительность управляющих импульсов с точностью в 1/125 секунды, чего должно быть вполне достаточно для данной задачи.
▍ Питание
Чтобы подключить вторичные часы к Arduino, придётся решить одну проблему: они требуют иные параметры питающего напряжения, нежели привычные 5 или 3.3 вольт, и просто так к выводам микросхемы их подключить нельзя.
Параметры питания вторичных часов бывают разные в зависимости от их модели, номинально это может быть 18 (плюс-минус 4) или 24 вольт (плюс-минус 8). Итого диапазон напряжений от 14 до 32 вольт. Потребляемый электромагнитом ток мизерный, порядка 10-20 мА. Также всё равно понадобится и 5 вольт, для питания Arduino.
Конечно, можно сделать или купить блок питания, обеспечивающий оба напряжения. Или взять источник на 24 вольта, в лице импульсного БП, трансформатора, или 5S-вязанку аккумуляторов 18650, а потом понизить напряжение до 5 вольт с помощью старой доброй КРЕНки (7805).
Но так как ток небольшой, есть способ попроще. Можно применить повышающий DC-DC модуль, например, на микросхеме MT3608, позволяющий получить до 28 вольт от пятивольтового источника с возможностью точной настройки нужного напряжения. Такие модули сейчас производятся в ассортименте и стоят копейки.
Использование повышающего модуля позволяет питать всё это безобразие от обычной телефонной зарядки или любого пауэрбанка, а заодно разместить все компоненты устройства на небольшой площади.
▍ Полярность и H-мост
Следующая проблема, которую требуется как-то решить — периодическая смена полярности управляющего напряжения. Нужно обеспечить возможность подачи на вход вторичных часов плюса и минуса, либо минуса и плюса, либо ничего вообще. Причём управлять подачей нужно логическими уровнями Arduino, тогда как коммутируемое напряжение будет существенно выше.
Для этой задачи человечество давно придумало типовое решение, с помощью которого обычно управляют шаговыми двигателями и моторчиками в радиоуправляемых моделях: H-мост. H — не н-word, в оригинале это буква «эйч», но разницы нет: просто начертание электрической схемы этого устройства напоминает такую букву.
Типовая схема H-моста из выключателей
H-мост состоит из четырёх выключателей. На входе у него плюс и минус источника тока, посередине располагается потребитель. Разные комбинации переключателей направляют плюс и минус на разные полюса потребителя, или отключают ток. Также есть запрещённое состояние, вызывающее короткое замыкание источника тока.
В практической реализации роль выключателей (ключей) могут играть электромеханические реле, биполярные или МОП-транзисторы (MOSFET’ы). Типовая схема включения обладает двумя управляющими входами, синхронно переключающими противоположные пары ключей, а бинарные комбинации наличия сигналов на входах обеспечивают все возможные состояния моста.
Реле для решения задачи управления часами никак не подойдут, потому что их ресурс ограничен примерно 100-200 тысячами переключений. Так как в сутках 86400 секунд, для секундных часов ресурса хватит лишь на сутки работы, а для минутных — от силы на пару месяцев.
Транзисторы же ограничением на количество переключений не обладают, и я без проблем мог бы их использовать. Если бы они у меня были под рукой. Так вышло, что я не нашёл в запасах MOSFET’ов разных типов проводимости, а ехать покупать их было долго и лень. К тому же надо что-то паять, добавлять всякие резисторы.
Возможные состояния H-моста
К счастью, в современном мире есть более простое решение: специализированные микросхемы, реализующие H-мост, и готовые модули на их основе. Их тоже нужно покупать, но хотя бы не нужно много паять. Приятным бонусом является наличие защиты от запрещённого состояния: одинаковые сигналы любого уровня на управляющих входах одинаково приводят к выключению выходного напряжения.
В продаже имеется три популярных вида подобных модулей: маленький на микросхеме L9110S, другой маленький на микросхеме TC1508A, и сильно побольше, с алюминиевым радиатором, на микросхеме L298N. Все модули содержат сразу два H-моста и позволяют управлять парой обычных двигателей или одним шаговым. Выходы Arduino подключается прямо к управляющим входам модулей, внутри модулей предусмотрена развязка питающего и управляющих напряжений.
Готовые модули H-мостов: послабее и помощнее
Разница между модулями в их параметрах. Модуль на микросхеме L9110S может запитываться напряжением от 2.5 до 12 вольт (6 номинальное), на микросхеме TC1508A — от 2 до 10 вольт. Падение выходного напряжения от питающего составляет около 1.5 вольт, продолжительный ток потребления до 0.8 ампер на канал. Модуль на L298N значительно крупнее и мощнее, он коммутирует от 5 до 36 вольт с током до 2 ампер на канал.
Для нашей цели ток не важен, так как потребление их совершенно незначительное. Напряжение, однако, важно: при недостаточном напряжении часы просто не пойдут.
Сначала я решил попытаться задействовать значительно более компактный модуль на L9910S. К сожалению, его рабочее напряжение до 12 вольт, а выходное будет около 10.5, что значительно ниже необходимых 18 вольт.
Я провёл эксперименты, и хотя часы шли при 10.5 вольт, делали они это очень неуверенно, с явным замедлением вращения шайбы — значит, при напряжении ниже номинального не гарантируется точность хода. Поэтому пришлось задействовать крупный модуль на L298N, хотя это и явный перебор по мощности и габаритам.
▍ Код
Приятной особенностью проекта является простота кода, и, как следствие, его размер. В кои-то веки его можно привести в тексте целиком. Он даже поддерживает минутные и секундные часы и реализует функцию подгона:
#define PIN_H_BRIDGE_POS 8 //пин управления положительным плечом H-моста
#define PIN_H_BRIDGE_NEG 9 //пин управления отрицательным плечом H-моста
#define PIN_FORWARD_BTN 10 //пин кнопки подгона для установки времени
#define PIN_CONTROL_LED 13 //стандартный светодиод для контроля секундного счёта
//#define CLOCK_TYPE_ONE_SECOND //для секундных часов, иначе часы считаются минутными
//константы с параметрами хода для соответствующего типа часов
#ifdef CLOCK_TYPE_ONE_SECOND
const unsigned int normal_div = 125;
const unsigned char pulse_duration = 125 * .5;
#else
const unsigned int normal_div = 125 * 60;
const unsigned char pulse_duration = 125 * .5; //по стандарту 1.6
#endif
const unsigned int adjust_repeat = pulse_duration * 1.5;
//переменные счёта времени и длительности импульса
unsigned int int_counter = 0;
unsigned char led_counter = 0;
unsigned char pulse_polarity = 0;
unsigned char pulse_counter = 0;
unsigned int adjust_counter = 0;
bool adjust_mode = false;
//обработчик прерывания timer0
ISR(TIMER0_COMPA_vect) {
//делитель частоты прерываний для получения секундных импульсов
++int_counter;
unsigned int int_div = normal_div;
bool pulse = false;
if (digitalRead(PIN_FORWARD_BTN) == LOW) //при нажатой кнопке подгона ускоряем ход, первый импульс выдаём сразу же по нажатию
{
if (!adjust_mode)
{
adjust_mode = true;
adjust_counter = 0;
pulse = true;
}
else
{
++adjust_counter;
if (adjust_counter >= adjust_repeat)
{
adjust_counter = 0;
pulse = true;
}
}
}
else
{
adjust_mode = false;
}
if (int_counter >= int_div)
{
int_counter = 0;
pulse = true;
}
if (pulse)
{
//включаем выходное напряжение нужной полярности
digitalWrite(PIN_H_BRIDGE_POS, pulse_polarity == 0 ? HIGH : LOW);
digitalWrite(PIN_H_BRIDGE_NEG, pulse_polarity == 0 ? LOW : HIGH);
//задаём длительность импульса
pulse_counter = pulse_duration;
pulse_polarity ^= 1; //смена полярности
}
//счётчик длительности импульса
if (pulse_counter > 0)
{
--pulse_counter;
if (pulse_counter == 0)
{
//выключаем выходное напряжение
digitalWrite(PIN_H_BRIDGE_POS, LOW);
digitalWrite(PIN_H_BRIDGE_NEG, LOW);
}
}
//дополнительный счётчик для индикации секундных импульсов
//просто для визуального контроля работы прошивки
++led_counter;
if (led_counter >= 125)
{
digitalWrite(PIN_CONTROL_LED, HIGH);
led_counter = 0;
}
if (led_counter == 125 / 8)
{
digitalWrite(PIN_CONTROL_LED, LOW);
}
}
//инициализация
void setup() {
//выключаем прерывания на время инициализации
cli();
//устанавливаем управляющие пины H-моста и входной пин кнопки
pinMode(PIN_H_BRIDGE_POS, OUTPUT);
pinMode(PIN_H_BRIDGE_NEG, OUTPUT);
pinMode(PIN_CONTROL_LED, OUTPUT);
pinMode(PIN_FORWARD_BTN, INPUT_PULLUP);
digitalWrite(PIN_H_BRIDGE_POS, LOW);
digitalWrite(PIN_H_BRIDGE_NEG, LOW);
//настраиваем timer0 на генерацию прерываний с частотой 125 герц ровно
TCCR0A = 0;
TCCR0B = 0;
TCNT0 = 0;
OCR0A = 16000000 / 1024 / 125 - 1; //делитель таймера, дающий самую низкую целочисленную частоту (125 герц ровно)
TCCR0A |= (1 << WGM01);
TCCR0B |= (1 << CS02) | (1 << CS00); //входной делитель на 1024
TIMSK0 |= (1 << OCIE0A); //прерывание по сравнению с OCR0A
//разрешаем прерывания, начинается генерация импульсов
sei();
}
//в основном цикле ничего не происходит, весь код в обработчике прерывания
void loop() {
}
В этом варианте кода я задействовал 8-битный счётчик с предварительным делителем 1024 и заданным делителем 125 для генерации прерываний с частотой 125 герц.
▍ Схема
Итоговая, если позволите так её назвать, схема состоит из трёх готовых модулей, нескольких проводков для их соединения между собой, а также одного диода Шоттки для «защиты от дурака». Кто не дурак, диод может не ставить.
Смысл наличия диода в том, чтобы при подключении питания к DC-DC модулю питались все элементы, а при подключении питания к Arduino (при прошивке) не запитывался DC-DC модуль и не перегружал питающие цепи Arduino, которые в этом случае не смогут сдерживать свой волшебный дым внутри.
Схема соединения модулей
Для проверки схема сначала была собрана на проводках.
Сборка на проводках
После обхода всех граблей я пересобрал её более надёжно, на монтажной плате. Модуль H-моста привинчен на две стойки, DC-DC преобразователь припаян на четыре одноконтактных пина.
Финальная форма устройства
Фактическое потребление устройства по показаниям простенького и вряд ли сильно точного тестера нагрузки USB-портов составляет 30-50 мА в режиме ожидания и 150 мА в момент выдачи импульса.
Измерение потребления тока
Модуль L298N таит сюрприз: на его плате есть 5-вольтовый стабилизатор, позволяющий получить 5 вольт из входящих 24, а также есть прямой вход для 5-вольтового питания. Джампер необходимо снять, иначе возникнет замыкание между двумя источниками, а на пятивольтовую линию подать питание с USB-разъёма.
Джампер питания на плате H-моста
Если же не подавать на 5-вольтовый вход модуля питание, а джампер оставить на месте, это тоже будет работать. Но из-за низкого КПД стабилизатора в модуле ток потребления схемы будет составлять уже 150-250 мА — при питании от аккумулятора разница во времени работы будет довольно существенной.
▍ Проверка временем
Электронные часы кажутся довольно простой конструкцией, не требующей особой наладки: собрал и заработало. Однако их мало просто взять и сделать. Неплохо бы как-то убедиться в точности их хода. Хотя в теории предпринятые мной меры должны обеспечить адекватную точность, критерий истины — практика. И проверка точности хода может оказаться несколько сложнее, чем кажется на первый взгляд.
Для современных механических часов бытового назначения допустима погрешность плюс-минус 40-60 секунд в сутки. Для бытовых же часов с кварцевой стабилизацией нормой считается отклонение до 20 секунд в течение месяца, а у лучших производителей отклонение составляет 5 секунд в год, и эти приборы претендуют уже на звание хронометра — часов высокой точности. Мы уже знаем, что теоретически от Arduino без RTC можно ожидать погрешности 8-60 секунд в сутки, что сравнимо с механическими часами.
Как можно проконтролировать точность? Простейшая идея, которая приходит в голову: нужно запустить часы на некоторый срок, сверить с эталонным источником времени в один момент, и потом в другой. Таким образом, можно понять, насколько проверяемые часы спешат или отстают.
Немного покумекав, как бы мне осуществить такую схему измерений, я решил задействовать современные подручные технические средства во благо колхоза. Я снял на видео с частотой 60 кадров в секунду вторичные часы и эталонные часы в один день, фиксируя момент перемещения минутной стрелки и текущее точное время на экране. Такую же видеозапись я сделал и в другой день. В роли эталонных часов выступил компактный ноутбук под управлением Windows 10, на котором в браузере был открыт веб-сайт, показывающий время с точностью до секунды — такой себе эталон.
Сверка показаний в кадрах видео
Теперь, имея два видео, с помощью покадровой перемотки можно определить долю секунды точного текущего времени в момент перемещения стрелки. Точность измерения составила соответствующее частоте кадров значение, то есть 1/60 секунды. Это, конечно, тоже не атомные часы, да и телефон такой себе эталон точного времени, но этого вполне достаточно для моих целей. По крайней мере, понятно, нет ли критичных проблем. Улучшить точность можно увеличением длительности измерений, например, дав часам поработать неделю.
▍ В поисках точности
Результат измерений указанным способом для моей конструкции в течение ровно 18 часов или 64800 секунд по стрелкам вторичных часов (на такое время хватило заряда небольшого частично заряженного пауэрбанка), составил 18 часов 1 минуту 32 секунды или 64892 секунды по эталонным часам — 0.14%, вдвое больше максимально ожидаемых 0.07%. Даже подсчитывать доли секунды не пришлось. Почему получилось такое огромное расхождение, не вполне понятно — ведь повлиять могло множество факторов, от точности счёта временных интервалов в Arduino до пропуска шагов в механизме часов и точности средств измерения.
Попытка проконтролировать точное время между минутными импульсами по звуку сдвига стрелки также не прояснила ситуацию. Дело в том, что этот звук размазан по времени, стрелка переходит не сразу. Примерное попадание в пики дало около минуты и 0.05 секунды, что в пределах погрешности по угадыванию фактического момента сдвига стрелки.
Измерение точности по аудиозаписи в Wavosaur
Для уточнения фактической частоты следования импульсов я подключил динамическую головку параллельно контрольному светодиоду, который мигает раз в секунду, записал щелчки в звуковой файл и посмотрел время между пиками. Частота следования импульсов получилась 1.001417..1.001438 секунды. То есть отклонение в те же 0.14%. Таким образом, я сделал вывод, что проблема не в механике, а в источнике импульсов.
Так как отклонение определённо возникало именно в частоте следования прерываний от таймера, а не в программных делителях, предпринять можно было немногое: попробовать скорректировать делитель таймера. Однако одна единица моего 8-битного делителя даст разницу в 0.8%, а значит отклонение всё равно будет велико.
Чтобы оперировать меньшими отклонениями, был только один выход: задействовать для счёта времени 16-битный таймер с меньшим предварительным делителем, чтобы значение задаваемого делителя было больше, а значит каждая его единица соответствовала меньшему отклонению.
Именно это я и сделал. Сама по себе замена таймера ничего не изменила и дала ровно такое же отклонение, а значит проблема кроется в самых глубинах, в тактовой частоте Arduino. Но теперь стало возможно скомпенсировать расхождение ручной подстройкой.
▍ Другой код
Новый вариант кода отличается только установками таймера в функции setup и введением компенсационной константы TIMER1_ADJUST:
//настраиваем timer1 на генерацию прерываний с частотой 125 герц ровно
//отличие от версии для timer0 в использовании меньшего пред-делителя и возможности тонкой подстройки
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
OCR1A = 16000000 / 8 / 125 - 1 + TIMER1_ADJUST; //делитель таймера, дающий самую низкую целочисленную частоту (125 герц ровно)
TCCR1B |= (1 << WGM12);
TCCR1B |= (1 << CS11); //входной делитель на 8
TIMSK1 |= (1 << OCIE1A); //прерывание по сравнению с OCR0A
Для моего экземпляра Arduino подобранное приблизительными расчётами и методом высокоточного тыка значение этой константы составило минус 20 единиц, что дало измеренное отклонение в 0.01% и уход часов на 8 секунд в сутки — теоретически ожидаемая точность. На этом я и успокоился.
▍ За кадром
Современное медиа за редким исключением предпочитает строгое деление на истории успеха или неудач — простой и понятный зрителю формат. Статьи по электронике и различные видео про самоделки могут создать впечатление, что DIY — это когда придумал, купил детали, собрал, и всё заработало. К сожалению, так бывает редко. Обычно же не обходится без приключений, без поисков совершенно пустяковых ошибок, затягивающихся на долгие часы, а иногда и без совершенно загадочных обстоятельств, когда что-то работает или не работает, а потом вдруг перестаёт.
Так вышло и в этот раз. В реальности этот проект выходного дня вместо нескольких часов занял несколько дней, а потом ещё лежал несколько месяцев без движения, ожидая проверки на точность. За это время я:
- Случайно оторвал USB-разъём у одного повышающего DC-DC модуля.
- Другому DC-DC модулю испортил USB-разъём его предварительной пропайкой, чтобы случайно не оторвать (припой затёк куда не надо).
- Испортил USB-провод, пытаясь исправить разъём на модуле (припой затёк в провод тоже).
- Модуль H-моста на L9110S или не вполне работал изначально, или тихо сгорел, или я сам что-то перемудрил: один из мостов у него работал только в одну сторону (давал одну из полярностей), а второй не работал совсем.
- Arduino Nano таки испустило дымок и перестало работать, сгорел диод. Заменил, заработало. Так в схеме появился диод, а в тексте шутка про него.
- Устройство стартовало через раз, срабатывала защита источника питания: забыл джампер на плате H-моста на L298N, замыкающий вход 5 вольт на выход внутреннего стабилизатора.
- Часы изначально были покрыты многовековыми слоями масляной краски, и для красивого кадра я задумал привести их в относительный порядок. Лучше бы не трогал. Оказалось, что смывка для краски не всегда работает хорошо, и к тому же часы местами оказались в нерастворимой шпаклёвке. В итоге дома была разведена жуткая грязища, а я так и не нашёл времени, чтобы довести начатое до конца и привести их в достойное состояние.
Процесс снятия краски смывкой
Поэтому настоящий трюк — не в том, чтобы научиться понимать что-то в электронике, или красиво паять, а в том, чтобы не опускать руки, и доводить начатое до конца, несмотря на неудачи, происходящие в процессе.
▍ Затраты
В случае с этим проектом детали извлекались из личных запасов, а какие-то покупались по несколько штук сразу, поэтому подсчитать фактические расходы затруднительно. Но ориентировочный масштаб затрат можно представить по актуальным розничным ценам на том же Озоне:
- Arduino — 350.
- Повышающий DC-DC модуль — 190.
- Модуль H-моста — 220.
- Монтажная плата — 150.
- Часы из СССР — бесценны.
Результат
▍ Заключение
Вот так с помощью нехитрых приспособлений можно взять и приспособить одно к другому. Я уже приспособил, и вы тоже можете!
Возможно, эта история получит продолжение: пока я жёг диоды на Ардуинах, поступила новая информация. На горизонте замаячила перспектива подключения вторичных электронных часов со светящимся цифровым табло, ВЧЦ1-С2ПГ 12К-80.
Telegram-канал со скидками, розыгрышами призов и новостями IT ?
Комментарии (86)
voldemar_d
16.07.2024 09:44+2Случайно оторвал USB-разъём у одного повышающего DC-DC модуля
У меня как-то в процессе отладки и втыкания/вытыкания кабеля USB-разъем от Arduino Pro Micro отвалился :-)
Насчет внешнего источника точного времени - может, довести дело до абсурда и приделать к Arduino WiFi-модуль, брать показания времени с какого-нибудь сайта (с парсингом json) и к нему подводить время периодически? Наверное, еще железобетоннее приделать GPS-источник точного времени, только это стоить будет негуманно, скорее всего.
Где советские вторичные часы удалось найти?
LAutour
16.07.2024 09:44+6GPS-источник точного времени, только это стоить будет негуманно, скорее всего.
Не особо дорого: модуль с антенной - несколько сотен рублей
longmaster
16.07.2024 09:44+4Проще сразу на esp всё сделать
voldemar_d
16.07.2024 09:44+1Почему не Raspberry Pi?
longmaster
16.07.2024 09:44+6Я без сарказма. Esp8266 сейчас стоит дешевле минимальной arduino pro mini, хотя разница в десятки рублей тут несущественна. Зато 8266 имеет кроме весьма неточного rtc, но работающего в deepsleep, ещё и довольно точные системные часы, дающие погрешность 1сек/сутки. Ну и позволяет просто реализовать синхронизацию по NTP готовыми библиотеками, без всякого парсинга с сайтов. Если эксплуатация часов планируется рядом с доступным wifi, то всё получится проще и дешевле, чем мудрить с GPS или автономным RTC. Можно даже реализовать сохранение последнего зафиксированного стрелками времени и автоматом подводить часы после отключения питания. Ресурс ячеек памяти ограничен, но можно периодически сдвигать адрес.
shiru8bit Автор
16.07.2024 09:44+1Да, если бы требовалась синхронизация по интернету, я однозначно сделал бы на ESP8266. Она стоит плюс-минус столько же, сделать на ней очень просто. Raspberry Pi — это совсем другая ценовая категория.
dlinyj
16.07.2024 09:44+3Где советские вторичные часы удалось найти?
На Авито, мешке и прочих сландо тысячи их, даже новых
fobo
16.07.2024 09:44одним вайфаем не получится. нужно же будет сделать схему получения текущих показаний механики, чтобы потом доворачивать стрелки до нового положения соответственно источнику точного времени.
voldemar_d
16.07.2024 09:44Сейчас в коде есть некий параметр, корректирующий неточность хода внутренних часов Arduino. Если есть внешнее точное время, можно попытаться этот параметр рассчитывать автоматически.
fobo
16.07.2024 09:44попытаться можно, но какой в этом смысл, если ты не знаешь как в данный момент установлены стрелки?
kuzzdra
16.07.2024 09:44не знаешь как в данный момент установлены стрелки
Как так не знаешь? А кто их крутит?
Задача на 4 балла: как сохранять значение счетчика часов, чтобы EEPROM не закончилось через месяц.
grishkaa
16.07.2024 09:44Хранить не в EEPROM :)
Ну то есть, если очень хочется, то можно придумать хитрую схему для равномерного износа EEPROM для растягивания его срока службы на максимум, но, как бы, кажется, что наша цивилизация уже придумала более практичное решение этой проблемы — хранить время в энергозависимой памяти с неограниченным ресурсом перезаписи, питающейся от дополнительной батарейки. А если ставить дополнительную батарейку, то уже и чип RTC можно. А ещё можно не батарейку, а большой конденсатор.
shiru8bit Автор
16.07.2024 09:44+1Задача типовая, поэтому такие схемы уже давно придуманы и реализованы в готовых библиотеках. Например, в EEPROMWearLevel, доступна в менеджере библиотек в Arduino IDE.
fobo
16.07.2024 09:44можно предположить, что в начальный момент механику выставили с некое "нулевое" положение и больше неучтенных воздействий она не претерпевает. и при каждом вкл-выкл электроники опять же механику "обнуляют". но так не прикольно НМВ. тогда вообще ничего не надо изобретать. путь тикает как может от самого простого источника импульсов без всяких мозгов и программ, а когда в 15:00 (на камчатке полночь) пользователь увидит расхождение, сам пальцем поправит стрелки.
если уж тыкать в такое устройство ардуину, так и предусмотреть датчик нуля хотя бы. тогда нехитрыми действиями "мозг" будет способен из любого начального положения выйти в рабочий режим и поддерживать точность показаний.
быстрое гугление показывает доступность 12-битных I2C энкодеров за 3 бакса. но тут я пас, я не настоящий электронщик.
kuzzdra
16.07.2024 09:44можно предположить, что в начальный момент механику выставили с некое "нулевое" положение
Нужно. Иначе это не часы а прикольный моторчик.
и больше неучтенных воздействий она не претерпевает
Конечно. Первичные часы одни.
и при каждом вкл-выкл электроники опять же механику "обнуляют".
Нет никакого вкл-выкл. Первичные часы всегда тикают.
Датчики, енкодеры.. Можно, конечно же. Но неспортивно ;) Не нужно ломать хороший надежный механизм.
fobo
16.07.2024 09:44как по мне, неспортивно писать программу, которая работает только в одних начальных условиях, а во всех остальных 3599 фейлится насмерть.
к тому же, речь идет все же о вторичных часах. и в заголовке, и в статье, и в коментах. посему, вторичные часы суть индикатор состояния первичных и вообще не факт, что могут и должны быть включены постоянно.
kuzzdra
16.07.2024 09:44вторичные часы суть индикатор состояния первичных
Совершенно не индикатор. Если не вносить в конструкцию улучшений. А если вносить.. Готовый стрелочный механизм 2-3 бакса стоит. 1 батарейка АА его будет год крутить.
вообще не факт, что могут и должны быть включены постоянно.
Должны быть включены постоянно. by design.
LAutour
16.07.2024 09:44Задача на 4 балла: как сохранять значение счетчика часов, чтобы EEPROM не закончилось через месяц.
Использовать FRAM и не страдать.
shiru8bit Автор
16.07.2024 09:44При желании конечно можно добавить датчик положения часовой стрелки, магнитик плюс датчик Холла. Но это надо дорабатывать уже сами часы, тянуть лишние провода.
HardWrMan
16.07.2024 09:44+2Зачем вообще писать какой-то код, участвующий в отсчёте времени и вносящий в это погрешность, если можно просто соединить таймеры каскадно и только менять их настройки по желанию + легко контроллировать состояние?
dlinyj
16.07.2024 09:44Я бы рекомендовал вообще отказаться от таймеров, так как слишком большая погрешность. Все эти поделки на ардуино для таких часов все коллекционеры ругают на чём свет стоит, так как пользоваться в дальней перспективе невозможно.
shiru8bit Автор
16.07.2024 09:44+3Как я понял из своих экспериментов, дело не в таймерах, а в погрешности генератора тактовой частоты. Если на нормальном Uno стоит кварц, на этих китайских копеечных клонах Nano стоит керамический резонатор, и у него очень большое отклонение для часовых применений. Типа, хватает для стабильного UART, и ладно.
dlinyj
16.07.2024 09:44+1Разумеется проблема в самом генераторе, при этом она плавает от температуры, фазы луны и биополя. Поэтому лучше всего использовать внешние часы и не измерять время с помощью таймера на процессоре.
На самом деле, там если не поленится и открыть даташит, то можно прикинуть погрешность измерения времени с помощью таймера, и окажется что она весьма неважнецкая.
sim2q
16.07.2024 09:44+2В модуле ещё удобно хранить в памяти полярность прошлого импульса (на случай отключения питания). У меня вторичные часы - перекидные, т.е. там вообще должно быть всё точно!
VT100
16.07.2024 09:44Ну при чём аппаратный таймер, если дело в точности и стабильности задающего генератора?
Подстройка хода в цифровых часах была 40+ лет назад, что и показал @shiru8bit . Вопрос только в увеличении длительности интервалов калибровки по мере роста точности (предполагаю - решаемый) и разрядности.
dlinyj
16.07.2024 09:44Вы прекрасно поняли о чем я говорю. Не стоит тратить время на реализацию программного таймера, надо брать внешнее часы.
VT100
16.07.2024 09:44+1Не только не понял. Но и не нахожу ничего конструктивного в нападках на таймеры в то время, как бороться надо за источник частоты.
надо брать внешнее часы.
В которых точно такой же кварц (в лучшем случае - состаренный), счётчик и щепотка "соли" (датчики температуры и, возможно, напряжения питания).
Если DIY'ть, то почему бы не оттянуться за счёт ПО?
dlinyj
16.07.2024 09:44Покажите мне где я нападал на таймеры? Процитируйте пожалуйста.
Я написал, что в этом решении стоит отказаться от реализации измерения времени с помощью программных таймеров, а стоит использовать внешние часы.
Если DIY'ть, то почему бы не оттянуться за счёт ПО?
Можно реализовать множеством способов, да.
tituszx
16.07.2024 09:44+2Во внешних часах, кроме хорошей DS3231 такой же кварц.
При этом при относительно стабильной комнатной температуре можно получить ПГ порядка 1 сек в сутки. Меньше уже затруднительно при использовании только 16 битного аппаратного таймера AVR, не хватает разрешающей способности.
А компенсацию температуры можно сделать по термодатчику, показания которого будут влиять на значение перезагрузки таймера
UFO_01
16.07.2024 09:44Тогда уж проще взять копеечный модуль приёмника DCF77 с антенной, если конечно находитесь в зоне приёма.
shiru8bit Автор
16.07.2024 09:44Код не вносит погрешность в отсчёт времени. Для универсальности.
HardWrMan
16.07.2024 09:44+1У тебя таймер стреляет 125 раз в секунду и далее идёт программный счётчик. Помимо джиттера на вход прерывания, ты имеешь ещё джиттер реакции на флаг счётчика в main(). Зачем, если можно OCR этого таймера соединить с ICR другого и поделить там на 125 вход, причём сразу сформировать импульс уже на его OCR прямо для управления мотором? А доли этой 1/125 частоты ты можешь смотреть в CNT таймера, если так надо, да ещё и стрелять его UF в прерывание. Решительно непонятно.
Точность самого кварцевого резонатора вопросов не вызывает. Если нужна прям точность лучше его поменять на внешнего гену с хорошим PPM.
shiru8bit Автор
16.07.2024 09:44+1Процитирую код:
//в основном цикле ничего не происходит, весь код в обработчике прерывания
Про какой джиттер входа прерывания идёт речь в случае с полностью целочисленными делителями, фиксированным временем выполнения основного цикла, и единственным таймером?
Что за реакция на счётчик в main?
Все эти игры с каскадированием счётчиков AVR — не для начинающих пользователей Arduino, и статью писать про это бессмысленно. Кто знает, тот знает, кто не знает, тому рано. К тому же, платформа Arduino — это давно уже далеко не только AVR. Для других плат достаточно заменить установку таймера и сохранить логику. Непортируемо, но хотя бы легко адаптируемо.
kuzzdra
16.07.2024 09:44Зачем, если можно OCR этого таймера соединить с ICR другого
Затем что arduino так не умеет, например. Система тактирования таймеров у конкретного камня может и умеет, но переносимость получится нулевая. А неправильно поделить на 125.. нужно стараться, я не могу представить как этого добиться.
HardWrMan
16.07.2024 09:44+1Затем что arduino так не умеет, например.
А пользователь умеет? Ну цветным проводочком 2 пина соединить между собой, как оно обычно бывает.
kuzzdra
16.07.2024 09:44https://www.arduino.cc/reference/en/
Соединяйте ;) Только за пределы API не выходите, иначе это уже не arduino ;)
HardWrMan
16.07.2024 09:44Hidden text
// Дампер NES/Dendy на основе Funduino // HardWareMan (c) 2013 // ------------------------------------------------ // Глобальные переменные byte CmdBuf[256]; byte AnsBuf[256]; byte Buf[128]; // Включить F2 void EnableF2() { TCCR1A = 0x04; TCCR1B = 0x09; OCR1AH = 0x00; OCR1AL = 0x08; } // Выключить F2 void ResetF2() { TCCR1A = 0x00; TCCR1B = 0x00; OCR1CH = 0x00; OCR1CL = 0x00; PORTB = PORTB | 0x80; } // Бездействие F2 void IdleF2() { TCCR1A = 0x00; TCCR1B = 0x00; OCR1CH = 0x00; OCR1CL = 0x00; PORTB = PORTB & 0x7F; } // Чтение блока 128 байт PRG+CHR void ReadPrgPage(word Adr) { // Переменные byte c,p,b,t,ah,al; // Настройка управления PORTB = 0x7F; // Настройка шины данных DDRF = 0x00; PORTF = 0xFF; // Цикл чтения 128 байт ah = highByte(Adr); al = lowByte(Adr); for (c = 0; c < 128; c++) { // Настройка адреса PORTA = al; PORTK = ah; // Эпюры сигнала if ((Adr & 0x8000) == 0) { asm volatile ( "cli \n" "pw0: \n" "sbis %3,7 \n" "breq pw0 \n" "pw1: \n" "sbic %3,7 \n" "brne pw1 \n" "ldi %1,0x7F \n" "ldi %2,0x7F \n" "nop \n" "nop \n" "nop \n" "out %4,%1 \n" "nop \n" "nop \n" "in %0,%5 \n" "out %4,%2 \n" "sei \n" : "=&r" (b), "=&r" (p), "=&r" (t) : "I" (_SFR_IO_ADDR(PINB)), "I" (_SFR_IO_ADDR(PORTB)), "I" (_SFR_IO_ADDR(PINF)) : "memory" ); } else { asm volatile ( "cli \n" "pw2: \n" "sbis %3,7 \n" "breq pw2 \n" "pw3: \n" "sbic %3,7 \n" "brne pw3 \n" "ldi %1,0x7D \n" "ldi %2,0x7F \n" "out %4,%1 \n" "nop \n" "nop \n" "nop \n" "nop \n" "nop \n" "in %0,%5 \n" "out %4,%2 \n" "sei \n" : "=&r" (b), "=&r" (p), "=&r" (t) : "I" (_SFR_IO_ADDR(PINB)), "I" (_SFR_IO_ADDR(PORTB)), "I" (_SFR_IO_ADDR(PINF)) : "memory" ); } Buf[c] = b; // Инкремент al++; if (al == 0) {ah++;} } // Настройка управления PORTB = 0x7F; } // Чтение блока 128 байт PRG+CHR void ReadChrPage(word Adr) { // Переменные byte c,p,b,t,ah,al; // Настройка управления PORTB = 0x7F; // Настройка шины данных DDRC = 0x00; PORTC = 0xFF; // Цикл чтения 128 байт ah = highByte(Adr); al = lowByte(Adr); for (c = 0; c < 128; c++) { // Настройка адреса PORTA = al; PORTK = ah; // Эпюры сигнала if ((Adr & 0x2000) == 0) { asm volatile ( "cli \n" "cw0: \n" "sbis %3,7 \n" "breq cw0 \n" "cw1: \n" "sbic %3,7 \n" "brne cw1 \n" "ldi %1,0x6F \n" "ldi %2,0x7F \n" "nop \n" "nop \n" "nop \n" "out %4,%1 \n" "nop \n" "nop \n" "in %0,%5 \n" "out %4,%2 \n" "sei \n" : "=&r" (b), "=&r" (p), "=&r" (t) : "I" (_SFR_IO_ADDR(PINB)), "I" (_SFR_IO_ADDR(PORTB)), "I" (_SFR_IO_ADDR(PINC)) : "memory" ); } else { asm volatile ( "cli \n" "cw2: \n" "sbis %3,7 \n" "breq cw2 \n" "cw3: \n" "sbic %3,7 \n" "brne cw3 \n" "ldi %1,0x5F \n" "ldi %2,0x7F \n" "nop \n" "nop \n" "nop \n" "out %4,%1 \n" "nop \n" "nop \n" "in %0,%5 \n" "out %4,%2 \n" "sei \n" : "=&r" (b), "=&r" (p), "=&r" (t) : "I" (_SFR_IO_ADDR(PINB)), "I" (_SFR_IO_ADDR(PORTB)), "I" (_SFR_IO_ADDR(PINC)) : "memory" ); } Buf[c] = b; // Инкремент al++; if (al == 0) {ah++;} } // Настройка управления PORTB = 0x7F; } // Запись в регистр void WriteMem(word Adr, byte Data) { byte b,p,t; // Настройка управления PORTB = 0x7F; // Настройка адреса PORTA = lowByte(Adr); PORTK = highByte(Adr); // Настройка шины данных DDRF = 0xFF; PORTF = Data; // Строб записи if ((Adr & 0x8000) == 0) { asm volatile ( "cli \n" "mw0%=: \n" "in %0,%3 \n" "andi %0,128 \n" "breq mw0%= \n" "ldi %1,0x7E \n" "mw1%=: \n" "in %0,%3 \n" "andi %0,128 \n" "brne mw1%= \n" "out %4,%1 \n" "ldi %2,0x7F \n" "ldi %1,0x7E \n" "nop \n" "out %4,%1 \n" "ldi %1,0x7E \n" "nop \n" "nop \n" "nop \n" "out %4,%1 \n" "nop \n" "nop \n" "nop \n" "nop \n" "out %4,%2 \n" "sei \n" : "=&r" (b), "=&r" (p), "=&r" (t) : "I" (_SFR_IO_ADDR(PINB)), "I" (_SFR_IO_ADDR(PORTB)) : "memory" ); } else { asm volatile ( "cli \n" "mw2%=: \n" "in %0,%3 \n" "andi %0,128 \n" "breq mw2%= \n" "ldi %1,0x7C \n" "mw3%=: \n" "in %0,%3 \n" "andi %0,128 \n" "brne mw3%= \n" "out %4,%1 \n" "ldi %2,0x7F \n" "ldi %1,0x7C \n" "nop \n" "out %4,%1 \n" "ldi %1,0x7E \n" "nop \n" "nop \n" "nop \n" "out %4,%1 \n" "nop \n" "nop \n" "nop \n" "nop \n" "out %4,%2 \n" "sei \n" : "=&r" (b), "=&r" (p), "=&r" (t) : "I" (_SFR_IO_ADDR(PINB)), "I" (_SFR_IO_ADDR(PORTB)) : "memory" ); } // Настройка шины данных DDRF = 0x00; PORTF = 0xFF; } // Инициализация void setup() { // Настройка портов ввода вывода на значение по умолчанию // Шина данных на ввод с подтяжкой DDRF = 0x00; PORTF = 0xFF; DDRC = 0x00; PORTC = 0xFF; // Шина адреса на вывод DDRA = 0xFF; PORTA = 0x00; DDRK = 0xFF; PORTK = 0x00; // Шина управления на вывод с деактивацией сигналов DDRB = 0xFF; PORTB = 0x7F; // Статус картриджа DDRG = 0x00; PORTG = 0x07; // Настройка F2 EnableF2(); delay(1000); // COM порт Serial.begin(921600); while (!Serial) { } } // Ожидание пакета void WaitForPacket() { // Локальные переменные boolean Ok; byte B, Sum, Pos, Size, State; Size = 0; State = 0; Ok = false; // Бесконечный цикл приема while (true) { if (Serial.available() > 0) { // Считываем байт B = Serial.read(); // Анализируем switch (State) { // Шаг поиска синхры case 0: if (((Size ^ B) == 0xFF) & (B > 0)) { // Есть синхра, инитим загрузку тела Size = B; Pos = 0; Sum = Size; State = 1; break; } else { Size = B; break; } // Шаг загрузки тела case 1: { // Все еще загружаем CmdBuf[Pos] = B; Sum = Sum ^ B; Pos++; if (Pos == Size) { State = 2; } break; } // Шаг проверки контрольки case 2: if (Sum == B) { Ok = true; State = 0; break; } else { Size = 0; State = 0; break; } // По умолчанию делаем синхру default: { Size = 0; State = 0; break; } } } if (Ok) { break; } } } // Посылка пакета void SendPacket(byte Size) { // Переменные byte c, Sum; // Проверка if (Size > 0) { // Посылаем размер Serial.write(Size ^ 0xFF); Serial.write(Size); Sum = Size; // Посылаем тело for (c = 0; c < Size; c++) { Sum = Sum ^ AnsBuf[c]; } Serial.write(AnsBuf,Size); // Посылаем контрольную сумму Serial.write(Sum); } } // Главный цикл void loop() { // Переменные word Adr; byte c,Data; // Ожидаем пакет WaitForPacket(); // Варианты реакции на пакет switch(CmdBuf[0]) { // Команда 00 - Получение баннера case 0x00: { AnsBuf[0] = 0x00; AnsBuf[1] = 0x41; AnsBuf[2] = 0x72; AnsBuf[3] = 0x64; AnsBuf[4] = 0x75; AnsBuf[5] = 0x44; AnsBuf[6] = 0x75; AnsBuf[7] = 0x6D; AnsBuf[8] = 0x70; AnsBuf[9] = 0x65; AnsBuf[10] = 0x72; AnsBuf[11] = 0x3A; AnsBuf[12] = 0x20; AnsBuf[13] = 0x4E; AnsBuf[14] = 0x45; AnsBuf[15] = 0x53; AnsBuf[16] = 0x2F; AnsBuf[17] = 0x44; AnsBuf[18] = 0x65; AnsBuf[19] = 0x6E; AnsBuf[20] = 0x64; AnsBuf[21] = 0x79; AnsBuf[22] = 0x20; AnsBuf[23] = 0x44; AnsBuf[24] = 0x75; AnsBuf[25] = 0x6D; AnsBuf[26] = 0x70; AnsBuf[27] = 0x65; AnsBuf[28] = 0x72; AnsBuf[29] = 0x00; SendPacket(30); break; } // Команда 01 - Получение 128 байт PRG case 0x01: { Adr = CmdBuf[2]; Adr = Adr * 256; Adr = Adr + CmdBuf[1]; ReadPrgPage(Adr); // Выплюнем пакет PRG AnsBuf[0] = 0x01; AnsBuf[1] = CmdBuf[1]; AnsBuf[2] = CmdBuf[2]; for (c = 0; c < 128; c++) {AnsBuf[c+3] = Buf[c];} SendPacket(131); break; } // Команда 02 - Получение 128 байт PRG / CHR case 0x02: { Adr = CmdBuf[2]; Adr = Adr * 256; Adr = Adr + CmdBuf[1]; ReadChrPage(Adr); // Выплюнем пакет PRG AnsBuf[0] = 0x02; AnsBuf[1] = CmdBuf[1]; AnsBuf[3] = CmdBuf[2] & 0x3F; for (c = 0; c < 128; c++) {AnsBuf[c+3] = Buf[c];} SendPacket(131); break; } // Запись байта case 0x03: { Adr = CmdBuf[2]; Adr = Adr * 256; Adr = Adr + CmdBuf[1]; WriteMem(Adr,CmdBuf[3]); AnsBuf[0] = 0x03; AnsBuf[1] = 0x00; SendPacket(2); break; } // Сброс case 0x04: { AnsBuf[0] = 0x04; AnsBuf[1] = 0x00; SendPacket(2); IdleF2(); delay(500); ResetF2(); delay(500); EnableF2(); delay(500); break; } // Дефолтовое значение - пакет с ошибкой default: { AnsBuf[0] = 0xFF; AnsBuf[1] = 0x55; AnsBuf[2] = 0x6E; AnsBuf[3] = 0x6B; AnsBuf[4] = 0x6E; AnsBuf[5] = 0x6F; AnsBuf[6] = 0x77; AnsBuf[7] = 0x6E; AnsBuf[8] = 0x00; SendPacket(9); break; } } }
Используя такое джитсу, остаюсь ли я в рамках ардуины?
kuzzdra
16.07.2024 09:44Нет конечно. Разве что вы портируете это на все ардуино-совместимые камни. И в процессе не сломаете совместимость с сервоприводами, PWM и прочей ерундой, на таймер завязанной.
Если ваш код использует только API - он запустится на чем угодно, хоть ESP, хоть STM32, хоть ATMEGA.HardWrMan
16.07.2024 09:44Я всегда думал, что ардуино это про снижение порога входа в программирование МК, про отсутвие необходимости в специнструменте, вроде программатора. А не про переносимость. Ведь глупо ожидать, что код, работающий на AVR, будет корректно работать на ARM, который и по обвесу и по скорости совсем другой. Не говоря за ESP, где вообще RTOS. А в пределах одной архитектуры - да, всё будет работать правильно, даже у моего кода.
kuzzdra
16.07.2024 09:44Тем не менее, если в arduino ide нажать на board manager - вы можете удивиться ;) А если загуглите "arduino ${CPU_NAME} support" - еще больше удивитесь ;)
А если в плату с Arduino Mega залить RTOS - это еще снижение порога входа в программирование МК, или уже серьезный программист работает? ;)
глупо ожидать, что код, работающий на AVR, будет корректно работать на ARM, который и по обвесу и по скорости совсем другой
C++ везде С++. Да, arduino framework не даст доступа ко всем возможностям камня. Ну, так это и не всегда нужно. Зато переносимость - мегаудобно.
HardWrMan
16.07.2024 09:44А если загуглите "arduino ${CPU_NAME} support" - еще больше удивитесь ;)
Как не было 78K0/s так и нет. Безобразие! Доколе?!
kuzzdra
16.07.2024 09:44Ну, я не обещал поддержки во всех камнях ;) Да и не везде оно осмысленно - фреймворк достаточно жырный.
dlinyj
16.07.2024 09:44+7Новое - это хорошо забытое старое.
В этом же блоге ранее писал статьи:
Проблема этого решения банальна: никакая точность, надо ставить нормальную микросхему часов и по ней синхронизировать секунды.
MaFrance351
16.07.2024 09:44+4И, по-хорошему, закопать уже эту DS1302. DS3231 при не сказать, что намного большей стоимости лишена многих недостатков предыдущей.
LAutour
16.07.2024 09:44+3DS3231 при не сказать, что намного большей стоимости лишена многих недостатков предыдущей.
Оригинальная скорее всего, но обычно все любят использовать китайские модули, которые стоят дешевле самой микросхемы. И в точности работы (да и вообще работоспособности) этой китайщины большой уверенности нет.
MaFrance351
16.07.2024 09:44+5Просто DS1302, даже оригинальная, имеет фатальный недостаток в виде внешнего кварца и отсутствия термостабилизации. Поэтому точность очень сильно зависит от установленного на плате кварца (те, что на китайских модулях, вообще никуда не годятся, как выяснилось), от температуры в помещении и от настроения её создателя. В DS3231 это исключено.
Что же до модулей - так и есть, у самого из двух купленных один абсолютно нормальный (стоит в самопальных часах, за несколько лет ушёл где-то на минуту), второй просто не завёлся.
fio
16.07.2024 09:44+2Исследовал точность нескольких китайских модулей на DS3231. В качестве эталона использовал PPS-импульсы геодезического GPS-приемника.
Долговременная стабильность лучшего модуля RTC получилась 0.5 ppm (микросекунды за секунду). Или около 1.5 секунды в месяц.. Худший модуль вписался в обещанные 3.5ppm.
По datasheet стабильность генератора у DS3231SN должна быть не хуже ±3.5 ppm, у микросхемы DS3231M вместо кварца используется MEMS-генератор, с точностью ±5ppm. Обе микросхемы имеют механизм автокомпенсации точности хода по внутреннему термодатчику.
shiru8bit Автор
16.07.2024 09:44+1Собственнно основной поинт моего варианта статьи для начинающих самодельщиков в понимании, что нормальной точности не будет.
dlinyj
16.07.2024 09:44+1Я когда продавал после статьи всю свою систему с первичными часами, коллекционер мне сказал что его замучили эти поделия на ардуинках. Мол, точности никакой, хуже только механика, которая зависит от температуры и влажности. Поэтому самый правильный вариант - это подобрать внешние часы, которые могут генерировать прерывание раз в минуту. Либо, тут достаточно просто опроса и при достижении минуты генерировать импульс перевода стрелки.
Народ тут начал возмущаться на счёт того, что нет возможности синхронизировать в случае отключения. Тут решения два: ИБП и реализация как была на первичных часах: вводится время на вторичных часах, а далее импульсами сами первичные часы выбирают разницу.
kuzzdra
16.07.2024 09:44коллекционер мне сказал что его замучили эти поделия на ардуинках
А зачем он коллекционирует эти поделия? ;)
dlinyj
16.07.2024 09:44А зачем он коллекционирует эти поделия? ;)
Вопрос "зачем" тут из разряда: зачем вы это спрашиваете?
kuzzdra
16.07.2024 09:44Ну, раз коллекционер, значит интересуется, то ли часами, то ли поделками. Ему поделки неинтересны, но они его замучили.. Ему под видом каминных часов 19 века поделки на ардуинах толкали, что ли..
Да шуточный вопрос, не нужно отвечать так серьезно ;)
dlinyj
16.07.2024 09:44Пардон, я не уловил сарказма. Смысл в чём, что у него на даче, в гараже, подвале и т.п. стоят вторичные часы. Первичных на всех не хватает или просто не хочет тянуть линию, и он ставит такие вот на контроллерах. Ругал.
kuzzdra
16.07.2024 09:44В 21 веке вторичые часы это NTP. КМК если запускать старое железо - то нужно оставлять его максимально аутентичным.
dlinyj
16.07.2024 09:44Так его никто и не модифицирует. Модифицируют первичные. А на счёт аутентичности, зайдите на авито и посмотрите цену на первичные и вторичные часы. Ещё с учётом того, что вторичные чуть ли не новые есть, то первичные чаще всего просто недостать, а если и достать то в не рабочем состоянии.
Плюс вторичные часы прикольные без всякой аутентичности.kuzzdra
16.07.2024 09:44Так его никто и не модифицирует.
ну, тут предлагали енкодеры, датчики..
Плюс вторичные часы прикольные без всякой аутентичности.
Часы за 20 баксов и за 20 000 показывают то же время ;) Часы с электромеханическим приводом выглядят так же, только мороки с ними нет - батарейки хватает на год, точность пристойная.
dlinyj
16.07.2024 09:44ну, тут предлагали енкодеры, датчики..
Это не ко мне, я про коллекционера говорил.
электромеханическим приводом выглядят так же, только мороки с ними нет - батарейки хватает на год, точность пристойная.
Этот пост вообще не про практичность.
AlexanderS
16.07.2024 09:44+1Я тоже подобным образом игрался с вторичными часами. Только у меня задумка была "вечная": солнечные батареи + контроллер + сборка 18650 + ардуина с самопальным шилдом с электроникой. Предполагалось, что контроллер будет всё время спать, потребление будет никакое и этого будет достаточно для подзарядки сборки АКБ днём от солнечных панелей. Но в эффективности панелей с алиэкспресс я жестоко просчитался и работало это только 2-3 суток.
Всё хотел статью на хабр запилить, годы шли... ну вот... теперь уже прочитал)
shiru8bit Автор
16.07.2024 09:44Интересная идея! Но похоже, что в моём варианте с DC-DC преобразователем для получения 28 вольт нужен не такой уж мизерный ток для передвижения стрелок, что-то около 100 мА в пике. По грубой прикидке мне бы понадобился аккумулятор ёмкостью около 5000-6000 мАч, и как-то успевать дозаряжать его наполовину за сутки. Можно конечно набрать большую батарею 18650 на 36 вольт, заряжать без лишних преобразователей, питать H-мост напрямую от батареи, а для МК понижать тоже DC-DC. Надо считать, и всё равно, наверное понадобится не такая уж маленькая солнечная панель.
ciuafm
16.07.2024 09:44У вас проблема с ДЦ преобразователем в том что вы его питаете всю минуту, а вам нужно всего 1-3 секунды в минуту. Т.е. транзистор для включения ДЦ там просто необходим. Опять же таки ёмкий (подобранный) конденсатор на выходе ДЦ должен уменьшить расход энергии. Не верю что для перевода стрелки нужно 3 w .
shiru8bit Автор
16.07.2024 09:44Если рассчитывать на питание от солнечных панелей, то конечно да, надо экономить на всём, и отключение преобразователя даст основную экономию. Для моей же задачи такое питание не предполагалось и необходимости в транзисторе не было. Между переводами стрелки всё это хозяйство потребляет около 30 мА.
vvzvlad
16.07.2024 09:44Ну так часовые кварцы не просто так называются часовыми и не просто так используются там, где надо с хорошей точностью отсчитывать секунды.
Можно взять stm32 с отдельным тактовым генератором для часового кварца и не мучаться с подстройкой таймеров.tklim
16.07.2024 09:44Вполне можно и указанную ардуину нано использовать. Нужен только паяльник и ISP программатор: перевести основной генератор на внутренний RC, заменить 16М кварц на 32768 и использовать с таймером2.
fio
16.07.2024 09:44+1Часовой кварц имеет частоту, удобную для расчета в двоичной системе. Температурную нестабильность механической системы в нем не исключили.
tklim
16.07.2024 09:44Я снял на видео с частотой 60 кадров в секунду вторичные часы и эталонные часы в один день, фиксируя момент перемещения минутной стрелки и текущее точное время на экране.
Зачем это всё? Есть ардуина, есть компьютер. В код добавляется счётчик который раз в секунду (или любой другой интервал) отправляет свое значение в uart. Дальше это дело запускается на несколько часов/сутки и сравнивается с часами на компе.
dlinyj
16.07.2024 09:44+1Можно сразу на LPT-порт повесить управляющую схему и двигать часами. Но вообще мне нравится любовь стрелять из пушки по воробьям.
shiru8bit Автор
16.07.2024 09:44+2В системе есть электромеханический элемент древнего года выпуска и не с консервационного хранения. Достоверно известно, что он подвержен нестабильной работе при излишнем занижении напряжения. При около-номинальном напряжении на глазок вроде работает стабильно, но кто его знает, не пропускает ли оно за сутки несколько шагов.
tklim
16.07.2024 09:44Это тоже крайне просто протестировать: выставить на 12:00, отправить по-быстрому 720 импульсов (ну или больше если секунды) и потом смотреть где оказалась стрелка
Mishootk
16.07.2024 09:44Есть идея установки в ноль. Шаговые импульсы идут на одном напряжении. При подаче более высокого напряжения срабатывает отключение шаговых обмоток и включатеся "втягивающее реле" расцепителя стрелок. Обе падают на "полшестого". Таким образом спроектировав механизм с "электронным сцеплением" можно два раза в сутки выставлять показания "по нулям". К сожалению, без вмешательства в механику не представляю как это сделать.
VT100
16.07.2024 09:44Интересно. Но "обе вниз" - это не полшестого и не полседьмого. Это - запрещённое состояние.
Mishootk
16.07.2024 09:44Да, только на весе классических стрелок такое не сделать, нужно будет на приводе часовой делать смещающий противовес для состояния свободного свисания на 2.5 минутных деления до или после 6. Время синхронизации, соответственно, будет либо на полшестого либо на полседьмого.
kuzzdra
16.07.2024 09:44Без усложнения механики (и ухудшения надежности) не получится. Фишка этих часов в том, что механизм очень простой и надежный.
Mishootk
16.07.2024 09:44Предложу фантастический вариант. По току катушки можно определить нагрузку создаваемую сопротивлением стрелки (груза) на подъем или на опускание. Удаленно это вряд-ли получится сделать из-за большой погрешности вносимой длинной линией. Но в качестве задачки "на слабо" рядом размещенным контроллером можно попробовать.
Если же реализовывать обратную связь для длинной линии, то вешать датчик нуля на каждую стрелку (магнит+геркон) и им докидывать сопротивление на линию. Его величина не должна сильно влиять на мощность привода, но должна быть однозначно считываема удаленно (независимые нули по каждой стрелке с разными величинами сопротивления). Или одинаковыми, но закрытыми разнонаправленными диодами.kuzzdra
16.07.2024 09:44У.. сложное.. тогда просто подавать 12В 50Гц, активировать электромагнит, который утянет механизм в состояние 00:00. Но это все равно сложное - вкрячивать кучу механики, которая работает раз в год. Оригинальный механизм простой как молоток.
sterr
16.07.2024 09:44Ну если честно, делать такое на ардуине конечно просто, но тупо. Как раз из за точности. У меня давно идея во все свои домашние часы поставить NTP. Так же и здесь - оптимальный вариант ESP[NTP]+RTC. И считать время должен RTC, на не какой то непонятный таймер. На ESP поднимаем NTP и хоть раз в секунду сверяйте точность. К тому же можно сделать например приложение в котором указать реальное время и нужное и пусть сам подводит. Ну и если отойти от каноничности, можно встроить в механизм определяющий позицию стрелок, чтобы точнее подводить время.
shiru8bit Автор
16.07.2024 09:44Меня несколько удивило количество комментариев про непонятность таймера и недоверие к его работе. Таймер на AVR - достаточно простая и понятная, хорошо задокументированная вещь. Почему он всех так пугает? Это же не какая-то хитроумная многозадачная система, где много факторов, которые нужно учитывать. На той же ESP подобное, локальный счёт без RTC, местными системными таймерами не провернуть (зато можно приспособить в качестве источника времени i2s). Вот там действительно непонятно, что внутри чёрного ящика и как они на самом деле считают - но и их хватает для стабильного цифрового звука, например. В RTC модулях, к слову, тоже нет никакой магии, там тоже кварц, таймер, заводские подстройки под конкретный кристалл, плюс температурная коррекция.
NTP в этот раз меня делать просто не просили. Но если буду писать продолжение про цифровые часы, вероятно именно так и сделаю: ESP, RTC-модуль, синхронизация с NTP, все дела. Посмотрим, как мне скажут, что так делать тупо и даже ежу понятно, что нужно было делать иначе (широкий смайл).
Des_75
16.07.2024 09:44"Поэтому настоящий трюк — не в том, чтобы научиться понимать что-то в электронике, или красиво паять, а в том, чтобы не опускать руки, и доводить начатое до конца, несмотря на неудачи, происходящие в процессе."
Аминь!
KOT2K
Спасибо за интересный материал!