Pull to refresh

Comments 99

Спасибо за интересный материал!

Случайно оторвал USB-разъём у одного повышающего DC-DC модуля

У меня как-то в процессе отладки и втыкания/вытыкания кабеля USB-разъем от Arduino Pro Micro отвалился :-)

Насчет внешнего источника точного времени - может, довести дело до абсурда и приделать к Arduino WiFi-модуль, брать показания времени с какого-нибудь сайта (с парсингом json) и к нему подводить время периодически? Наверное, еще железобетоннее приделать GPS-источник точного времени, только это стоить будет негуманно, скорее всего.

Где советские вторичные часы удалось найти?

GPS-источник точного времени, только это стоить будет негуманно, скорее всего.

Не особо дорого: модуль с антенной - несколько сотен рублей

Проще сразу на esp всё сделать

Я без сарказма. Esp8266 сейчас стоит дешевле минимальной arduino pro mini, хотя разница в десятки рублей тут несущественна. Зато 8266 имеет кроме весьма неточного rtc, но работающего в deepsleep, ещё и довольно точные системные часы, дающие погрешность 1сек/сутки. Ну и позволяет просто реализовать синхронизацию по NTP готовыми библиотеками, без всякого парсинга с сайтов. Если эксплуатация часов планируется рядом с доступным wifi, то всё получится проще и дешевле, чем мудрить с GPS или автономным RTC. Можно даже реализовать сохранение последнего зафиксированного стрелками времени и автоматом подводить часы после отключения питания. Ресурс ячеек памяти ограничен, но можно периодически сдвигать адрес.

Да, если бы требовалась синхронизация по интернету, я однозначно сделал бы на ESP8266. Она стоит плюс-минус столько же, сделать на ней очень просто. Raspberry Pi — это совсем другая ценовая категория.

Полагаю можно запитать от ионистора и в нем хватит энергии чтобы записывать только когда питание пропадет. Но это не точно :). (Терзают смутные сомнения касательно того сколько это всё жрет с включенным вайфаем)

Где советские вторичные часы удалось найти?

На Авито, мешке и прочих сландо тысячи их, даже новых

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

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

попытаться можно, но какой в этом смысл, если ты не знаешь как в данный момент установлены стрелки?

Хотя бы скорость хода часов корректировать.

не знаешь как в данный момент установлены стрелки

Как так не знаешь? А кто их крутит?

Задача на 4 балла: как сохранять значение счетчика часов, чтобы EEPROM не закончилось через месяц.

Хранить не в EEPROM :)

Ну то есть, если очень хочется, то можно придумать хитрую схему для равномерного износа EEPROM для растягивания его срока службы на максимум, но, как бы, кажется, что наша цивилизация уже придумала более практичное решение этой проблемы — хранить время в энергозависимой памяти с неограниченным ресурсом перезаписи, питающейся от дополнительной батарейки. А если ставить дополнительную батарейку, то уже и чип RTC можно. А ещё можно не батарейку, а большой конденсатор.

Задача типовая, поэтому такие схемы уже давно придуманы и реализованы в готовых библиотеках. Например, в EEPROMWearLevel, доступна в менеджере библиотек в Arduino IDE.

можно предположить, что в начальный момент механику выставили с некое "нулевое" положение и больше неучтенных воздействий она не претерпевает. и при каждом вкл-выкл электроники опять же механику "обнуляют". но так не прикольно НМВ. тогда вообще ничего не надо изобретать. путь тикает как может от самого простого источника импульсов без всяких мозгов и программ, а когда в 15:00 (на камчатке полночь) пользователь увидит расхождение, сам пальцем поправит стрелки.

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

быстрое гугление показывает доступность 12-битных I2C энкодеров за 3 бакса. но тут я пас, я не настоящий электронщик.

можно предположить, что в начальный момент механику выставили с некое "нулевое" положение

Нужно. Иначе это не часы а прикольный моторчик.

и больше неучтенных воздействий она не претерпевает

Конечно. Первичные часы одни.

и при каждом вкл-выкл электроники опять же механику "обнуляют".

Нет никакого вкл-выкл. Первичные часы всегда тикают.

Датчики, енкодеры.. Можно, конечно же. Но неспортивно ;) Не нужно ломать хороший надежный механизм.

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

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

вторичные часы суть индикатор состояния первичных

Совершенно не индикатор. Если не вносить в конструкцию улучшений. А если вносить.. Готовый стрелочный механизм 2-3 бакса стоит. 1 батарейка АА его будет год крутить.

вообще не факт, что могут и должны быть включены постоянно.

Должны быть включены постоянно. by design.

Геркон ваш энкодер... Нужна одна метка.

Задача на 4 балла: как сохранять значение счетчика часов, чтобы EEPROM не закончилось через месяц.

Использовать FRAM и не страдать.

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

Зачем вообще писать какой-то код, участвующий в отсчёте времени и вносящий в это погрешность, если можно просто соединить таймеры каскадно и только менять их настройки по желанию + легко контроллировать состояние?

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

Как я понял из своих экспериментов, дело не в таймерах, а в погрешности генератора тактовой частоты. Если на нормальном Uno стоит кварц, на этих китайских копеечных клонах Nano стоит керамический резонатор, и у него очень большое отклонение для часовых применений. Типа, хватает для стабильного UART, и ладно.

Разумеется проблема в самом генераторе, при этом она плавает от температуры, фазы луны и биополя. Поэтому лучше всего использовать внешние часы и не измерять время с помощью таймера на процессоре.

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

В модуле ещё удобно хранить в памяти полярность прошлого импульса (на случай отключения питания). У меня вторичные часы - перекидные, т.е. там вообще должно быть всё точно!

Ну при чём аппаратный таймер, если дело в точности и стабильности задающего генератора?

Подстройка хода в цифровых часах была 40+ лет назад, что и показал @shiru8bit . Вопрос только в увеличении длительности интервалов калибровки по мере роста точности (предполагаю - решаемый) и разрядности.

Вы прекрасно поняли о чем я говорю. Не стоит тратить время на реализацию программного таймера, надо брать внешнее часы.

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

надо брать внешнее часы.

В которых точно такой же кварц (в лучшем случае - состаренный), счётчик и щепотка "соли" (датчики температуры и, возможно, напряжения питания).

Если DIY'ть, то почему бы не оттянуться за счёт ПО?

Покажите мне где я нападал на таймеры? Процитируйте пожалуйста.

Я написал, что в этом решении стоит отказаться от реализации измерения времени с помощью программных таймеров, а стоит использовать внешние часы.

Если DIY'ть, то почему бы не оттянуться за счёт ПО?

Можно реализовать множеством способов, да.

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

Но вообще лучше проветривать.

Совершенно там другой кварц + кучка компенсации. И даташит обещает 0.17с неточности в день, минуту в год....

В то время, как программным путем из таймеров не удается даже после калибровки выжать и -+30секунд. (аппаратный генератор тотр решили rtc не снабжать, теперь его приходится синхронизировать регулярно)

Совершенно там другой кварц

Ну, как сказать... Все фирмы используют для "часовых" кварцев одну и ту же ориентацию среза, в результате - характеристики кварцев достаточно близки. Начальная точность порядка ±20 ppm и квадратичный температурный коэффициент с минимумом в районе 25 °С. Не говоря о том, что Maxim закупает кварцевые кристаллы "на стороне" - 100%.

Вот сравнение ТКЧ "каких-то" кварцев Sunny (слева) и кварцев DS3231 (справа):

Сравнение температурного коэффициента частоты.
Сравнение температурного коэффициента частоты.

И сравнение заявленного старения:

Сравнение старения.
Сравнение старения.

+ кучка компенсации.

Да, об этом прямо на первой странице написано. Сейчас почитал по диагонали, в DS3231 даже старение можно корректировать.

Во внешних часах, кроме хорошей DS3231 такой же кварц.

При этом при относительно стабильной комнатной температуре можно получить ПГ порядка 1 сек в сутки. Меньше уже затруднительно при использовании только 16 битного аппаратного таймера AVR, не хватает разрешающей способности.

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

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

Код не вносит погрешность в отсчёт времени. Для универсальности.

У тебя таймер стреляет 125 раз в секунду и далее идёт программный счётчик. Помимо джиттера на вход прерывания, ты имеешь ещё джиттер реакции на флаг счётчика в main(). Зачем, если можно OCR этого таймера соединить с ICR другого и поделить там на 125 вход, причём сразу сформировать импульс уже на его OCR прямо для управления мотором? А доли этой 1/125 частоты ты можешь смотреть в CNT таймера, если так надо, да ещё и стрелять его UF в прерывание. Решительно непонятно.

Точность самого кварцевого резонатора вопросов не вызывает. Если нужна прям точность лучше его поменять на внешнего гену с хорошим PPM.

Процитирую код:

//в основном цикле ничего не происходит, весь код в обработчике прерывания

Про какой джиттер входа прерывания идёт речь в случае с полностью целочисленными делителями, фиксированным временем выполнения основного цикла, и единственным таймером?

Что за реакция на счётчик в main?

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

Разве матожидание джиттера не ноль?

Зачем, если можно OCR этого таймера соединить с ICR другого

Затем что arduino так не умеет, например. Система тактирования таймеров у конкретного камня может и умеет, но переносимость получится нулевая. А неправильно поделить на 125.. нужно стараться, я не могу представить как этого добиться.

Затем что arduino так не умеет, например.

А пользователь умеет? Ну цветным проводочком 2 пина соединить между собой, как оно обычно бывает.

Hidden 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; }
    }
}

Используя такое джитсу, остаюсь ли я в рамках ардуины?

Нет конечно. Разве что вы портируете это на все ардуино-совместимые камни. И в процессе не сломаете совместимость с сервоприводами, PWM и прочей ерундой, на таймер завязанной.
Если ваш код использует только API - он запустится на чем угодно, хоть ESP, хоть STM32, хоть ATMEGA.

Я всегда думал, что ардуино это про снижение порога входа в программирование МК, про отсутвие необходимости в специнструменте, вроде программатора. А не про переносимость. Ведь глупо ожидать, что код, работающий на AVR, будет корректно работать на ARM, который и по обвесу и по скорости совсем другой. Не говоря за ESP, где вообще RTOS. А в пределах одной архитектуры - да, всё будет работать правильно, даже у моего кода.

Тем не менее, если в arduino ide нажать на board manager - вы можете удивиться ;) А если загуглите "arduino ${CPU_NAME} support" - еще больше удивитесь ;)

А если в плату с Arduino Mega залить RTOS - это еще снижение порога входа в программирование МК, или уже серьезный программист работает? ;)

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

C++ везде С++. Да, arduino framework не даст доступа ко всем возможностям камня. Ну, так это и не всегда нужно. Зато переносимость - мегаудобно.

А если загуглите "arduino ${CPU_NAME} support" - еще больше удивитесь ;)

Как не было 78K0/s так и нет. Безобразие! Доколе?!

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

Новое - это хорошо забытое старое.

В этом же блоге ранее писал статьи:

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

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

Без сомнения.

DS3231 при не сказать, что намного большей стоимости лишена многих недостатков предыдущей.

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

Просто DS1302, даже оригинальная, имеет фатальный недостаток в виде внешнего кварца и отсутствия термостабилизации. Поэтому точность очень сильно зависит от установленного на плате кварца (те, что на китайских модулях, вообще никуда не годятся, как выяснилось), от температуры в помещении и от настроения её создателя. В DS3231 это исключено.

Что же до модулей - так и есть, у самого из двух купленных один абсолютно нормальный (стоит в самопальных часах, за несколько лет ушёл где-то на минуту), второй просто не завёлся.

В 3231 нет термостабилизации. Там термокомпенсация.

Исследовал точность нескольких китайских модулей на DS3231. В качестве эталона использовал PPS-импульсы геодезического GPS-приемника.

Долговременная стабильность лучшего модуля RTC получилась 0.5 ppm (микросекунды за секунду). Или около 1.5 секунды в месяц.. Худший модуль вписался в обещанные 3.5ppm.

По datasheet стабильность генератора у DS3231SN должна быть не хуже ±3.5 ppm, у микросхемы DS3231M вместо кварца используется MEMS-генератор, с точностью ±5ppm. Обе микросхемы имеют механизм автокомпенсации точности хода по внутреннему термодатчику.

В том же эксперименте выяснил, что таймер моего модуля ESP-WROOM32 имеет нестабильность 40ppm - это где-то 2 минуты в месяц.

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

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

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

коллекционер мне сказал что его замучили эти поделия на ардуинках

А зачем он коллекционирует эти поделия? ;)

А зачем он коллекционирует эти поделия? ;)

Вопрос "зачем" тут из разряда: зачем вы это спрашиваете?

Ну, раз коллекционер, значит интересуется, то ли часами, то ли поделками. Ему поделки неинтересны, но они его замучили.. Ему под видом каминных часов 19 века поделки на ардуинах толкали, что ли..

Да шуточный вопрос, не нужно отвечать так серьезно ;)

Пардон, я не уловил сарказма. Смысл в чём, что у него на даче, в гараже, подвале и т.п. стоят вторичные часы. Первичных на всех не хватает или просто не хочет тянуть линию, и он ставит такие вот на контроллерах. Ругал.

В 21 веке вторичые часы это NTP. КМК если запускать старое железо - то нужно оставлять его максимально аутентичным.

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

Плюс вторичные часы прикольные без всякой аутентичности.

Так его никто и не модифицирует.

ну, тут предлагали енкодеры, датчики..

Плюс вторичные часы прикольные без всякой аутентичности.

Часы за 20 баксов и за 20 000 показывают то же время ;) Часы с электромеханическим приводом выглядят так же, только мороки с ними нет - батарейки хватает на год, точность пристойная.

ну, тут предлагали енкодеры, датчики..

Это не ко мне, я про коллекционера говорил.

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

Этот пост вообще не про практичность.

Я тоже подобным образом игрался с вторичными часами. Только у меня задумка была "вечная": солнечные батареи + контроллер + сборка 18650 + ардуина с самопальным шилдом с электроникой. Предполагалось, что контроллер будет всё время спать, потребление будет никакое и этого будет достаточно для подзарядки сборки АКБ днём от солнечных панелей. Но в эффективности панелей с алиэкспресс я жестоко просчитался и работало это только 2-3 суток.

Всё хотел статью на хабр запилить, годы шли... ну вот... теперь уже прочитал)

Интересная идея! Но похоже, что в моём варианте с DC-DC преобразователем для получения 28 вольт нужен не такой уж мизерный ток для передвижения стрелок, что-то около 100 мА в пике. По грубой прикидке мне бы понадобился аккумулятор ёмкостью около 5000-6000 мАч, и как-то успевать дозаряжать его наполовину за сутки. Можно конечно набрать большую батарею 18650 на 36 вольт, заряжать без лишних преобразователей, питать H-мост напрямую от батареи, а для МК понижать тоже DC-DC. Надо считать, и всё равно, наверное понадобится не такая уж маленькая солнечная панель.

У вас проблема с ДЦ преобразователем в том что вы его питаете всю минуту, а вам нужно всего 1-3 секунды в минуту. Т.е. транзистор для включения ДЦ там просто необходим. Опять же таки ёмкий (подобранный) конденсатор на выходе ДЦ должен уменьшить расход энергии. Не верю что для перевода стрелки нужно 3 w .

Если рассчитывать на питание от солнечных панелей, то конечно да, надо экономить на всём, и отключение преобразователя даст основную экономию. Для моей же задачи такое питание не предполагалось и необходимости в транзисторе не было. Между переводами стрелки всё это хозяйство потребляет около 30 мА.

Ну так часовые кварцы не просто так называются часовыми и не просто так используются там, где надо с хорошей точностью отсчитывать секунды.
Можно взять stm32 с отдельным тактовым генератором для часового кварца и не мучаться с подстройкой таймеров.

Вполне можно и указанную ардуину нано использовать. Нужен только паяльник и ISP программатор: перевести основной генератор на внутренний RC, заменить 16М кварц на 32768 и использовать с таймером2.

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

Я снял на видео с частотой 60 кадров в секунду вторичные часы и эталонные часы в один день, фиксируя момент перемещения минутной стрелки и текущее точное время на экране.

Зачем это всё? Есть ардуина, есть компьютер. В код добавляется счётчик который раз в секунду (или любой другой интервал) отправляет свое значение в uart. Дальше это дело запускается на несколько часов/сутки и сравнивается с часами на компе.

Можно сразу на LPT-порт повесить управляющую схему и двигать часами. Но вообще мне нравится любовь стрелять из пушки по воробьям.

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

Это тоже крайне просто протестировать: выставить на 12:00, отправить по-быстрому 720 импульсов (ну или больше если секунды) и потом смотреть где оказалась стрелка

По быстрому может работать немного не так как по медленному.

Ну сделайте с нормальной скоростью. Все тоже самое, просто ограничить число импульсов.

Есть идея установки в ноль. Шаговые импульсы идут на одном напряжении. При подаче более высокого напряжения срабатывает отключение шаговых обмоток и включатеся "втягивающее реле" расцепителя стрелок. Обе падают на "полшестого". Таким образом спроектировав механизм с "электронным сцеплением" можно два раза в сутки выставлять показания "по нулям". К сожалению, без вмешательства в механику не представляю как это сделать.

Интересно. Но "обе вниз" - это не полшестого и не полседьмого. Это - запрещённое состояние.

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

Без усложнения механики (и ухудшения надежности) не получится. Фишка этих часов в том, что механизм очень простой и надежный.

Предложу фантастический вариант. По току катушки можно определить нагрузку создаваемую сопротивлением стрелки (груза) на подъем или на опускание. Удаленно это вряд-ли получится сделать из-за большой погрешности вносимой длинной линией. Но в качестве задачки "на слабо" рядом размещенным контроллером можно попробовать.
Если же реализовывать обратную связь для длинной линии, то вешать датчик нуля на каждую стрелку (магнит+геркон) и им докидывать сопротивление на линию. Его величина не должна сильно влиять на мощность привода, но должна быть однозначно считываема удаленно (независимые нули по каждой стрелке с разными величинами сопротивления). Или одинаковыми, но закрытыми разнонаправленными диодами.

У.. сложное.. тогда просто подавать 12В 50Гц, активировать электромагнит, который утянет механизм в состояние 00:00. Но это все равно сложное - вкрячивать кучу механики, которая работает раз в год. Оригинальный механизм простой как молоток.

Ну если честно, делать такое на ардуине конечно просто, но тупо. Как раз из за точности. У меня давно идея во все свои домашние часы поставить NTP. Так же и здесь - оптимальный вариант ESP[NTP]+RTC. И считать время должен RTC, на не какой то непонятный таймер. На ESP поднимаем NTP и хоть раз в секунду сверяйте точность. К тому же можно сделать например приложение в котором указать реальное время и нужное и пусть сам подводит. Ну и если отойти от каноничности, можно встроить в механизм определяющий позицию стрелок, чтобы точнее подводить время.

Меня несколько удивило количество комментариев про непонятность таймера и недоверие к его работе. Таймер на AVR - достаточно простая и понятная, хорошо задокументированная вещь. Почему он всех так пугает? Это же не какая-то хитроумная многозадачная система, где много факторов, которые нужно учитывать. На той же ESP подобное, локальный счёт без RTC, местными системными таймерами не провернуть (зато можно приспособить в качестве источника времени i2s). Вот там действительно непонятно, что внутри чёрного ящика и как они на самом деле считают - но и их хватает для стабильного цифрового звука, например. В RTC модулях, к слову, тоже нет никакой магии, там тоже кварц, таймер, заводские подстройки под конкретный кристалл, плюс температурная коррекция.

NTP в этот раз меня делать просто не просили. Но если буду писать продолжение про цифровые часы, вероятно именно так и сделаю: ESP, RTC-модуль, синхронизация с NTP, все дела. Посмотрим, как мне скажут, что так делать тупо и даже ежу понятно, что нужно было делать иначе (широкий смайл).

Таймер на AVR - достаточно простая и понятная, хорошо задокументированная вещь.

А в RTC есть еще и календарь, летнезимнее время и прочие приколы, которые нужно будет не забыть реализовать.

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

"Поэтому настоящий трюк — не в том, чтобы научиться понимать что-то в электронике, или красиво паять, а в том, чтобы не опускать руки, и доводить начатое до конца, несмотря на неудачи, происходящие в процессе."
Аминь!

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

"Женщину вынули - автомат поставили"?

Можете мне объяснить, зачем для этого обратная связь ?

Если стрелки перемещает только ваше устройство вы всегда знаете их положение. Запомнить его при пропадании питания не труд. Если у вас rtc от батареек, он продолжает тикать и без питания(собственно у вас всё может работать при нормальном дизайне от этой батарейки, кроме движения стрелок). Соответственно в момент появления питания вы знаете текущее время и время пропадания питания.

Зачем вам какая-то обратная связь ?

Суть систем часофикации в том, что вторичных часов много, первичные часы одни, между ними только сеть из двух проводов, и всё это придумано и реализовано в доисторические времена. Если приделывать какие-то датчики, это уже не подключение вторичных часов, а изобретение совершенно иной, штучной системы.

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

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

Sign up to leave a comment.