Failure with the clock on ATMega48

Excellent watches on a lit up LCD display and ATMega48 supermicrocircuit. But they didn’t work out.



UPD : Crohn died, cut off two wires, applied sleep, 0.3-1.6mA


That is, the watches, of course, work, but, alas, they will not work for long.


Once upon a time, in a box in the far nightstand I hung a pair of seven-segment LCD indicators. And just as long ago, I wanted to take them into circulation and build a watch based on one of them. Once upon a time: it is, literally, seven years. It was then, in 2011, that I became interested in electronics. Without thinking for a long time, I then ordered all sorts of things in one good online store (no, not on Ali; I'm not sure that he was already there then). But somehow it didn’t work out for me to create the eternal. After several etched boards, I abandoned this entertainment and forgot.


And when Ali came parcels containing maketki, bag 595 -x in dip-housings, Tiny RTC on the DS1307 , and, most importantly, USBasp , it's time to go back to the old idea. From the old stash I had an ATMega48 , the one with 28 legs, lm7805 , all small things in the form of resistors / capacitors / buttons, and, in fact, an indicator on 40 legs.


Generally speaking, it was originally planned to use AtTiny13, which I also roll around, but having estimated this way and that, I didn’t figure out how to get around with 5 legs to display the indicator, read two buttons, and communicate with the i2c clock. With 28 mega legs, saving and perverting with the union of the legs is no longer necessary. Although, of course, in this case, you can’t do without the 595s. But if I planned to display all 32 bits of data for the indicator in succession with a teenager, then with mega you can display a picture on all four microcircuits at once.



All 595s are hidden under the indicator. The photo shows the conclusions of the housing of the microcircuit.


595 exactly four pieces were used, because the indicator, although it has 40 legs, displays only 32 on segments. Alas, this is only a 3.5 indicator, that is, it has 3 full digits, plus one. There is a symbol for seconds, but no designation for AM / PM. But there really is. I would not want to order a more advanced indicator without having collected anything in the LCD indicators in my life.



Working documentation for the indicator. The tester had to find out which leg corresponds to which segment.


Well, then a matter of technology. There was never a scheme, but everything is obvious there. It was only necessary to select four legs for the input of buffers, a common leg for the indicator, a leg for SCLK / RCLK, a leg for OE, two legs for i2c, two legs for buttons. All this was, of course, wrong. Why I decided that OE should be installed on the controller, and SCLK combined with RCLK - I don’t remember it myself. It was necessary to do just the opposite. And the general indicator wire on the controller is not needed at all, one of the conclusions of the first 595th could be dispensed with (which I also did in the end).



Wires. A lot of them. Inside view.



The view from the outside.


The most interesting thing in all this project: the output code for the indicator. The subtlety is that you can’t just apply voltage to the LCD indicator and forget it. It is necessary to change the polarity between the segments and the common contact about a hundred times per second, so that everything is beautiful and pleasing to the eye. As a development environment, without further ado, I used the Arduino IDE, only a little torment in a couple of points. Firstly, I had to use the MiniCore package ( https://github.com/MCUdude/MiniCore ), because I had to write not for a large ready-made arduino, but for a weak and naked ATMega48. Secondly, a direct attempt to use the built-in library for i2c did not lead to anything. For some reason, it didn’t want to work, I had to find another library, and then trim it for my needs.


As it turned out, 4 kilobytes is very small. Especially if you write in C. Perhaps if I decided to switch to assembler, this restriction would not have pressed so seriously, but remembering my past excerpts in writing in assembler for AVR, I didn’t have such a desire. And to write in C - a lot of space is needed. I put a little bracket inadvertently - a hundred bytes into the pipe.


Actually commented code
#include <avr/io.h>
#include <util/delay.h>
#include <avr/power.h>

#define cbi(sfr, bit)   (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit)   (_SFR_BYTE(sfr) |= _BV(bit))

// на самом деле у меня ds1307, но адрес тот же
#define DS3231_I2C_ADDRESS 0x68

#define LCD_PORT PORTD
#define LCD_DDR DDRD
#define LCD_DATA1 PD0
#define LCD_DATA2 PD1
#define LCD_DATA3 PD2
#define LCD_DATA4 PD3
#define LCD_SCLK PD4
#define LCD_OE PD5
#define LCD_COM PD6

#define KEY1_PORT PORTB
#define KEY1_DDR DDRB
#define KEY1_PIN PINB
#define KEY1 PB0
#define KEY2_PORT PORTD
#define KEY2_DDR DDRD
#define KEY2_PIN PIND
#define KEY2 PD7

#define DS1307SQ PB1
#define DS1307SQ_INT PCIE0
#define DS1307SQ_PORT PORTB
#define DS1307SQ_DDR DDRB
#define DS1307SQ_PIN PINB
#define DS1307SQ_VEC PCINT0_vect
#define DS1307SQ_MSK PCMSK0

// были планы. но, во-первых, лениво, во-вторых все равно уже не лезет
#define LM335_PORT PORTC
#define LM335_DDR DDRC
#define LM335 PC3

// это фреймбуфер
unsigned char lcd_buf[4];

#define MODE_MAIN 0
#define MODE_CALENDAR 1
#define MODE_YEAR 2
#define MODE_TERMOMETER 3 // нет
#define MODE_VOLTMETER 4 // нет
#define MODE_SET_MINUTE 5
#define MODE_SET_HOUR 6
#define MODE_SET_DAY 7
#define MODE_SET_MONTH 8
#define MODE_SET_YEAR 9
#define MODE_SECOND 10
#define MODE_DEBUG 11 // не используется

// таймауты в виде количества десятков циклов по 10ms (грубо)
#define MODE_TIMEOUT 20 // 2 секунды
#define MODE_TIMEOUT_SET 100 // 10 секунд
#define KEY_TIMEOUT 10 // 1 секунда

byte mode = MODE_MAIN;
byte mode_timeout = 0;

byte key1_press = 0;
byte key1_time = 0;
byte key2_press = 0;
byte key2_time = 0;

byte cycle_count_10 = 0; // счетчик десятков циклов
byte even_10 = 0; // меняется раз в 1/10 секунды

// последние данные от ds1307
byte second;
byte minute;
byte hour;
byte dayOfWeek;
byte dayOfMonth;
byte month;
byte year;

volatile byte need_render_int = 1;
uint8_t porthistory = 0xFF;
volatile uint8_t debug_value = 0;

byte twi_problems = 0;

// раз в секунду ds1307 дергает линию SQ
// я это ловлю, читаю время и перерисовываю экран
ISR(DS1307SQ_VEC) {
  uint8_t changedbits = DS1307SQ_PIN ^ porthistory;
  porthistory = DS1307SQ_PIN;
  if (changedbits & _BV(DS1307SQ) && porthistory & _BV(DS1307SQ)) {
    need_render_int = 1;
  }
}

// рисует цифру в фреймбуфере в нужной позиции
void lcd_num(char pos, char num) {
  unsigned char buf = 0b01110110;
  if (pos < 1 || pos > 3) {
    return;
  }
  switch (num) {
    case 0:
      // а почему это у меня 3-я позиция обрабатывается иначе, чем две другие?
      // правильно, потому что я криворукий косоглаз, который в трех проводках 
      // постоянно путается :-(
      if (pos == 3) {
        buf = 0b11101110;
      } else {
        buf = 0b11100111;
      }
      break;
    case 1:
      if (pos == 3) {
        buf = 0b10001000;
      } else {
        buf = 0b10000001;
      }
      break;
    case 2:
      buf = 0b11010110;
      break;
    case 3:
      if (pos == 3) {
        buf = 0b11011100;
      } else {
        buf = 0b11010011;
      }
      break;
    case 4:
      if (pos == 3) {
        buf = 0b10111000;
      } else {
        buf = 0b10110001;
      }
      break;
    case 5:
      if (pos == 3) {
        buf = 0b01111100;
      } else {
        buf = 0b01110011;
      }
      break;
    case 6:
      if (pos == 3) {
        buf = 0b01111110;
      } else {
        buf = 0b01110111;
      }
      break;
    case 7:
      if (pos == 3) {
        buf = 0b11001000;
      } else {
        buf = 0b11000001;
      }
      break;
    case 8:
      if (pos == 3) {
        buf = 0b11111110;
      } else {
        buf = 0b11110111;
      }
      break;
    case 9:
      if (pos == 3) {
        buf = 0b11111100;
      } else {
        buf = 0b11110011;
      }
      break;
  }
  lcd_buf[pos] = buf;
}

// самоочевидные функции
void lcd_one(bool e) {
  if (e) {
    lcd_buf[0] |= (1 << 0);
  } else {
    lcd_buf[0] &= ~(1 << 0);
  }
}
void lcd_sec(bool e) {
  if (e) {
    lcd_buf[0] |= (1 << 7);
  } else {
    lcd_buf[0] &= ~(1 << 7);
  }
}
void lcd_minus(bool e) {
  if (e) {
    lcd_buf[0] |= (1 << 1);
  } else {
    lcd_buf[0] &= ~(1 << 1);
  }
}
void lcd_plus(bool e) {
  if (e) {
    lcd_buf[0] |= (1 << 6);
  } else {
    lcd_buf[0] &= ~(1 << 6);
  }
}
void lcd_lo(bool e) {
  if (e) {
    lcd_buf[0] |= (1 << 5);
  } else {
    lcd_buf[0] &= ~(1 << 5);
  }
}
void lcd_over(bool e) {
  if (e) {
    lcd_buf[0] |= (1 << 4);
  } else {
    lcd_buf[0] &= ~(1 << 4);
  }
}
void lcd_dot(int pos, bool e) {
  int pos_buf;
  if (pos == 1) {
    pos_buf = 3;
  } else if (pos == 2) {
    pos_buf = 2;
  } else if (pos == 3) {
    pos_buf = 1;
  } else {
    return;
  }
  if (pos_buf == 3) {
    if (e) {
      lcd_buf[pos_buf] |= (1 << 0);
    } else {
      lcd_buf[pos_buf] &= ~(1 << 0);
    }
  } else {
    if (e) {
      lcd_buf[pos_buf] |= (1 << 3);
    } else {
      lcd_buf[pos_buf] &= ~(1 << 3);
    }
  }
}

// дергается из основного цикла 100 раз в секунду
// выдает данные на 595-е, каждый раз меняя полярность
void lcd_refresh() {
  unsigned char data1 = lcd_buf[0];
  unsigned char data2 = lcd_buf[1];
  unsigned char data3 = lcd_buf[2];
  unsigned char data4 = lcd_buf[3];
  byte reverse = data1 & (1 << 3); // вот тут хранится бит текущей полярности
  if (reverse) { // и если он стоит, переворачиваем биты
    data1 = ~data1;
    data2 = ~data2;
    data3 = ~data3;
    data4 = ~data4;
  }
  for (int i = 0; i < 8; i++) {
    // берем данные из фреймбуфера побитно и выставляем на выводах контроллера
    if (data1 & (1 << i)) {
      LCD_PORT |= _BV(LCD_DATA1);
    } else {
      LCD_PORT &= ~_BV(LCD_DATA1);
    }
    if (data2 & (1 << i)) {
      LCD_PORT |= _BV(LCD_DATA2);
    } else {
      LCD_PORT &= ~_BV(LCD_DATA2);
    }
    if (data3 & (1 << i)) {
      LCD_PORT |= _BV(LCD_DATA3);
    } else {
      LCD_PORT &= ~_BV(LCD_DATA3);
    }
    if (data4 & (1 << i)) {
      LCD_PORT |= _BV(LCD_DATA4);
    } else {
      LCD_PORT &= ~_BV(LCD_DATA4);
    }
    // SCLK 595-х вверх
    sbi(LCD_PORT, LCD_SCLK);
    // SCLK 595-х вниз
    cbi(LCD_PORT, LCD_SCLK);
  }
  // еще раз дергаем SCLK
  // а все потому что у меня SCLK связан с RCLK и надо дернуть еще раз, что бы на выводах
  // оказалось то что мне нужно.
  // и все это, вообще-то, неправильно. надо было на контроллер выводить отдельно 
  // SCLK и RCLK, а OE тупо сажать на землю (см. даташит на 74HC595)
  // но и так сойдет.
  sbi(LCD_PORT, LCD_SCLK);
  cbi(LCD_PORT, LCD_SCLK);
  // включаем общий контакт жк-шки в нужной полярности
  // вообще-то у моей жк-шки два общих контакта, пины 1 и 40
  // (почему-то не соединенных между собой; теряюсь в догадках зачем так)
  // и второй контакт заведен на 4 вывод первой 595-й, так что провод к 
  // контроллеру немного лишний, но так уж распаялось
  if (reverse) {
    sbi(LCD_PORT, LCD_COM);
  } else {
    cbi(LCD_PORT, LCD_COM);
  }
  // переключаем полярность для следующего цикла
  lcd_buf[0] ^= (1 << 3);
}

// рисуем в фреймбуфере что надо и когда надо, в соответствии с текущим режимом
void do_render() {
  lcd_buf[0] = 0;
  lcd_buf[1] = 0;
  lcd_buf[2] = 0;
  lcd_buf[3] = 0;
  if (twi_problems) {
    lcd_lo(1);
  }
  if (mode == MODE_MAIN) {
    lcd_num(3, minute % 10);
    lcd_num(2, minute / 10);
    byte hour1 = (hour <= 12) ? hour : (hour % 12);
    lcd_num(1, hour1 % 10);
    if (hour1 >= 10) {
      lcd_one(1);
    }
    // мигалка секунд одну секунду горит, другую не горит. эстетично.
    // собственно ради нее я и возился с линией SQ и прерыванием,
    // что бы оно мигало равномерно, и что бы интерференция между часами и 
    // циклами контроллера этому не мешала
    if (second % 2) {
      lcd_sec(1);
    }
  } else if (mode == MODE_CALENDAR) {
    lcd_num(3, dayOfMonth % 10);
    lcd_num(2, dayOfMonth / 10);
    lcd_num(1, month % 10);
    if (month >= 10) {
      lcd_one(1);
    } else {
      lcd_one(0);
    }
    lcd_dot(2, 1);
  } else if (mode == MODE_YEAR) {
    lcd_num(3, year % 10);
    lcd_num(2, year / 10);
    lcd_buf[1] = 0b10110011; // это буква y. типа
  } else if (mode == MODE_TERMOMETER) {
  } else if (mode == MODE_VOLTMETER) {
  } else if (mode == MODE_SET_MINUTE) {
    if (even_10) {
      lcd_num(3, minute % 10);
      lcd_num(2, minute / 10);
    }
    byte hour1 = (hour <= 12) ? hour : (hour % 12);
    lcd_num(1, hour1 % 10);
    if (hour1 >= 10) {
      lcd_one(1);
    } else {
      lcd_one(0);
    }
    lcd_sec(1);
  } else if (mode == MODE_SET_HOUR) {
    lcd_num(3, minute % 10);
    lcd_num(2, minute / 10);
    if (even_10) {
      byte hour1 = (hour <= 12) ? hour : (hour % 12);
      lcd_num(1, hour1 % 10);
      if (hour1 >= 10) {
        lcd_one(1);
      } else {
        lcd_one(0);
      }
      if (hour > 12) {
        lcd_over(1);
      } else {
        lcd_over(0);
      }
    }
    lcd_sec(1);
  } else if (mode == MODE_SET_DAY) {
    if (even_10) {
      lcd_num(3, dayOfMonth % 10);
      lcd_num(2, dayOfMonth / 10);
    }
    lcd_num(1, month % 10);
    if (month >= 10) {
      lcd_one(1);
    } else {
      lcd_one(0);
    }
    lcd_dot(2, 1);
  } else if (mode == MODE_SET_MONTH) {
    lcd_num(3, dayOfMonth % 10);
    lcd_num(2, dayOfMonth / 10);
    if (even_10) {
      lcd_num(1, month % 10);
      if (month >= 10) {
        lcd_one(1);
      } else {
        lcd_one(0);
      }
    }
    lcd_dot(2, 1);
  } else if (mode == MODE_SET_YEAR) {
    if (even_10) {
      lcd_num(3, year % 10);
      lcd_num(2, year / 10);
    }
    lcd_buf[1] = 0b10110011;
  } else if (mode == MODE_SECOND) {
    lcd_sec(1);
    lcd_num(3, second % 10);
    lcd_num(2, second / 10);
  } else if (mode == MODE_DEBUG) {
    byte d = debug_value;
    lcd_num(3, d % 10);
    d /= 10;
    lcd_num(2, d % 10);
    lcd_num(2, d / 10);
  }
}

int main(void)
{
  // мумбо-юмбо на тему энерго сохранения.
  // помогает, приблизительно, на никак.
  // ACSR = (1<<ACD);
  ADCSRA = (0<<ADEN);
  PRR = (1<<PRTIM0) | (1<<PRTIM1) | (1<<PRTIM2) | (1<<PRSPI) | (1<<PRADC) | (1<<PRUSART0);

  // 
  DDRB = 0x00;
  PORTB = 0xff;
  DDRC = 0x00;
  PORTC = 0xff;
  DDRD = 0x00;
  PORTD = 0xff;

  lcd_buf[0] = 0x0;
  lcd_buf[1] = 0x0;
  lcd_buf[2] = 0x0;
  lcd_buf[3] = 0x0;

  twi_begin();

  // светодиодик для отладки. у меня же usbasp, так что отладочной консоли нет
  // и, надо сказать, светодиодик очень помог
  // DDRB |= _BV(PB7);
  // PORTB |= _BV(PB7); // off

  // инициализация портов жк-шки
  LCD_DDR |= (_BV(LCD_DATA1) | _BV(LCD_DATA2) | _BV(LCD_DATA3) | _BV(LCD_DATA4) | _BV(LCD_SCLK) | _BV(LCD_OE) | _BV(LCD_COM));
  cbi(LCD_PORT, LCD_SCLK);
  cbi(LCD_PORT, LCD_OE);

  // кнопки
  KEY1_DDR &= ~(_BV(KEY1));
  KEY1_PORT |= _BV(KEY1);
  KEY2_DDR &= ~(_BV(KEY2));
  KEY2_PORT |= _BV(KEY2);

  // ежесекундный привет от ds1307 и его обработчик
  DS1307SQ_DDR &= ~_BV(DS1307SQ);
  DS1307SQ_PORT |= _BV(DS1307SQ);
  PCICR |= _BV(DS1307SQ_INT);
  DS1307SQ_MSK |= _BV(DS1307SQ);
  sei();

  // ds1307 без батарейки теряет данные и, будучи подключен вновь к питанию, даже не тикает
  // что бы он затикал, необходимо выставить хоть какое-то время
  // setDS3231time(30,40,21,6,11,3,18);

  while(1)
  {
    byte need_render = 0;
    byte need_date_set = 0;

    // пришел привет от ds1307, зафиксированный обработчиком прерываний
    // читаем
    if (need_render_int) {
      byte rc;
      if (mode == MODE_MAIN) {
        rc = readDS3231time_hms(&second, &minute, &hour);
      } else {
        rc = readDS3231time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);
      }
      if ( ! rc && twi_problems) {
        twi_problems -= 1;
      } else {
        twi_problems += rc;
      }
      need_render = 1;
      need_render_int = 0;
    }

    // выполняется раз в 1/10 секунды
    if ( ! cycle_count_10) {
      even_10 = ! even_10;

      // если включен неосновной режим, уменьшаем счетчик 
      if (mode_timeout > 0) {
        mode_timeout -= 1;
      }
      // и если счетчик кончился, возвращаемся в основной режим
      if (mode != MODE_MAIN && ! mode_timeout) {
        mode = MODE_MAIN;
        need_render = 1;
      }
      // если мы в режиме установки времени, мигаем 10 раз в секунду
      if (mode == MODE_SET_MINUTE || mode == MODE_SET_HOUR || mode == MODE_SET_DAY || mode == MODE_SET_MONTH || mode == MODE_SET_YEAR) {
        need_render = 1; 
      }

      // читаем кнопки
      byte key1_down = (KEY1_PIN & _BV(KEY1)) ? 0 : 1;
      byte key2_down = (KEY2_PIN & _BV(KEY2)) ? 0 : 1;
      if (key1_down || key2_down || key1_press || key2_press) {
        need_render = 1;
      }
      if (key1_down && key1_press) {
        key1_time += 1;
      }
      if (key2_down && key2_press) {
        key2_time += 1;
      }
      // и хитрым образом переключаем режимы
      if (key1_down && ! key1_press) {
          if (mode == MODE_SET_MINUTE) {
            mode = MODE_SET_HOUR;
            mode_timeout = MODE_TIMEOUT_SET;
          } else if (mode == MODE_SET_HOUR) {
            mode = MODE_SET_DAY;
            mode_timeout = MODE_TIMEOUT_SET;
          } else if (mode == MODE_SET_DAY) {
            mode = MODE_SET_MONTH;
            mode_timeout = MODE_TIMEOUT_SET;
          } else if (mode == MODE_SET_MONTH) {
            mode = MODE_SET_YEAR;
            mode_timeout = MODE_TIMEOUT_SET;
          } else if (mode == MODE_SET_YEAR) {
            mode = MODE_MAIN;
          } else if (mode == MODE_MAIN) {
            mode = MODE_SECOND;
            mode_timeout = MODE_TIMEOUT_SET;
          } else if (mode == MODE_SECOND) {
            mode = MODE_MAIN;
            mode_timeout = 0;
          }
      } else if ( ! key1_down && key1_press) {
        if (key1_time >= KEY_TIMEOUT) {
        } else {
        }
      } else if (key1_down && key1_press) {
        if (key1_time >= KEY_TIMEOUT) {
          if (mode == MODE_MAIN || mode == MODE_SECOND) {
            mode = MODE_SET_MINUTE;
            mode_timeout = MODE_TIMEOUT_SET;
          }
        } else {
        }
      }
      if (key2_down && ! key2_press) {
        if (mode == MODE_MAIN) {
          mode = MODE_CALENDAR;
          mode_timeout = MODE_TIMEOUT;
        } else if (mode == MODE_CALENDAR) {
          mode = MODE_YEAR;
          mode_timeout = MODE_TIMEOUT;
        } else if (mode == MODE_YEAR) {
          mode = MODE_MAIN;
          mode_timeout = 0;
        } else if (mode == MODE_SET_MINUTE) {
          minute += 1;
          mode_timeout = MODE_TIMEOUT_SET;
          need_date_set = 1;
        } else if (mode == MODE_SET_HOUR) {
          hour += 1;
          mode_timeout = MODE_TIMEOUT_SET;
          need_date_set = 1;
        } else if (mode == MODE_SET_DAY) {
          dayOfMonth += 1;
          mode_timeout = MODE_TIMEOUT_SET;
          need_date_set = 1;
        } else if (mode == MODE_SET_MONTH) {
          month += 1;
          mode_timeout = MODE_TIMEOUT_SET;
          need_date_set = 1;
        } else if (mode == MODE_SET_YEAR) {
          year += 1;
          mode_timeout = MODE_TIMEOUT_SET;
          need_date_set = 1;
        } else if (mode == MODE_SECOND) {
            second = 0;
            need_date_set = 1;
            mode_timeout = MODE_TIMEOUT_SET;
        }
      } else if ( ! key2_down && key2_press) {
        if (key2_time >= KEY_TIMEOUT) {
        } else {
        }
      } else if (key2_down && key2_press) {
        if (key2_time >= KEY_TIMEOUT) {
          if (mode == MODE_SET_MINUTE) {
            minute += 1;
            mode_timeout = MODE_TIMEOUT_SET;
            need_date_set = 1;
          } else if (mode == MODE_SET_HOUR) {
            hour += 1;
            mode_timeout = MODE_TIMEOUT_SET;
            need_date_set = 1;
          } else if (mode == MODE_SET_DAY) {
            dayOfMonth += 1;
            mode_timeout = MODE_TIMEOUT_SET;
            need_date_set = 1;
          } else if (mode == MODE_SET_MONTH) {
            month += 1;
            mode_timeout = MODE_TIMEOUT_SET;
            need_date_set = 1;
          } else if (mode == MODE_SET_YEAR) {
            year += 1;
            mode_timeout = MODE_TIMEOUT_SET;
            need_date_set = 1;
          }
        } else {
        }
      }
      key1_press = key1_down;
      if ( ! key1_press) {
        key1_time = 0;
      }
      key2_press = key2_down;
      if ( ! key2_press) {
        key2_time = 0;
      }
    }

    if (need_date_set) {
      // корректируем время
      // вообще я не очень понял как ds1307 проверяет валидность установки времени,
      // но, вроде бы, пока проблем не замечено
      if (minute > 59) {
        minute = 0;
      }
      if (hour > 23) {
        hour = 0;
      }
      if (dayOfMonth > 31) {
        dayOfMonth = 1;
      }
      if (month > 12) {
        month = 1;
      }
      if (year > 99) {
        year = 0;
      }
      // записываем время в ds1307
      setDS3231time(second, minute, hour, dayOfWeek, dayOfMonth, month, year);
    }

    // собственно, рисуем фреймбуфер по необходимости
    if (need_render) {
      do_render();
    }

    // обновляем экран
    cli();
    lcd_refresh();
    sei();

    _delay_ms(10);

    cycle_count_10 += 1;
    if (cycle_count_10 >= 10) {
      cycle_count_10 = 0;
    }
  }

  return 0;
}

// это я взял где-то в другом месте
byte decToBcd(byte val)
{
  return( (val/10*16) + (val%10) );
}
byte bcdToDec(byte val)
{
  return( (val/16*10) + (val%16) );
}

// запись времени в ds1307
void setDS3231time(byte second, 
                   byte minute, 
                   byte hour, 
                   byte dayOfWeek, 
                   byte dayOfMonth, 
                   byte month, 
                   byte year)
{
  twi_write((uint8_t) DS3231_I2C_ADDRESS, (uint8_t) 0, (uint8_t) decToBcd(second));
  twi_write((uint8_t) DS3231_I2C_ADDRESS, (uint8_t) 1, (uint8_t) decToBcd(minute));
  twi_write((uint8_t) DS3231_I2C_ADDRESS, (uint8_t) 2, (uint8_t) decToBcd(hour));
  twi_write((uint8_t) DS3231_I2C_ADDRESS, (uint8_t) 3, (uint8_t) decToBcd(dayOfWeek));
  twi_write((uint8_t) DS3231_I2C_ADDRESS, (uint8_t) 4, (uint8_t) decToBcd(dayOfMonth));
  twi_write((uint8_t) DS3231_I2C_ADDRESS, (uint8_t) 5, (uint8_t) decToBcd(month));
  twi_write((uint8_t) DS3231_I2C_ADDRESS, (uint8_t) 6, (uint8_t) decToBcd(year));
  // выставляем частоту на выходе SQ 1Hz
  // это надо сделать хотя бы только один раз, что бы ds1307 не устроил дос 
  // в обработчике прерываний на частоте 8kHz
  twi_write((uint8_t) DS3231_I2C_ADDRESS, (uint8_t) 7, (uint8_t) 0b00010000);
}

// чтение времени из ds1307
byte readDS3231time(byte *second,
                    byte *minute,
                    byte *hour,
                    byte *dayOfWeek,
                    byte *dayOfMonth,
                    byte *month,
                    byte *year)
{
  byte rc = twi_read(DS3231_I2C_ADDRESS, 0, 7);
  if (rc) return 1;
  *second = bcdToDec(twi_receive() & 0x7f);
  *minute = bcdToDec(twi_receive());
  *hour = bcdToDec(twi_receive() & 0x3f);
  *dayOfWeek = bcdToDec(twi_receive());
  *dayOfMonth = bcdToDec(twi_receive());
  *month = bcdToDec(twi_receive());
  *year = bcdToDec(twi_receive());
}

// чтение только секунд, минут и часов, то есть, для основного режима
byte readDS3231time_hms(byte *second, byte *minute, byte *hour) {
  byte rc = twi_read(DS3231_I2C_ADDRESS, 0, 3);
  if (rc) return 1;
  *second = bcdToDec(twi_receive() & 0x7f);
  *minute = bcdToDec(twi_receive());
  *hour = bcdToDec(twi_receive() & 0x3f);
}

/*
 * Код ниже, на самом деле, взят тут: http://dsscircuits.com/articles/arduino-i2c-master-library
 * Переработан и урезан до самого минимума, ибо иначе оно вместе с моим кодом в 4k не лезло.
 */
/*
  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#define START           0x08
#define REPEATED_START  0x10
#define MT_SLA_ACK  0x18
#define MT_SLA_NACK 0x20
#define MT_DATA_ACK     0x28
#define MT_DATA_NACK    0x30
#define MR_SLA_ACK  0x40
#define MR_SLA_NACK 0x48
#define MR_DATA_ACK     0x50
#define MR_DATA_NACK    0x58
#define LOST_ARBTRTN    0x38
#define TWI_STATUS      (TWSR & 0xF8)
#define SLA_W(address)  (address << 1)
#define SLA_R(address)  ((address << 1) + 0x01)

#define MAX_BUFFER_SIZE 32

uint8_t twi_bytesAvailable = 0;
uint8_t twi_bufferIndex = 0;
uint8_t twi_totalBytes = 0;
uint16_t twi_timeOutDelay = 0;
uint8_t twi_returnStatus;
uint8_t twi_nack;
uint8_t twi_data[MAX_BUFFER_SIZE];

void twi_begin()
{
  sbi(PORTC, 4);
  sbi(PORTC, 5);
  cbi(TWSR, TWPS0);
  cbi(TWSR, TWPS1);
  TWBR = ((F_CPU / 100000) - 16) / 2;
  TWCR = _BV(TWEN) | _BV(TWEA); 
}
uint8_t twi_read(uint8_t address, uint8_t registerAddress, uint8_t numberBytes)
{
  twi_bytesAvailable = 0;
  twi_bufferIndex = 0;
  if(numberBytes == 0){numberBytes++;}
  twi_nack = numberBytes - 1;
  twi_returnStatus = 0;
  twi_returnStatus = twi_start();
  if(twi_returnStatus){return(twi_returnStatus);}
  twi_returnStatus = twi_sendAddress(SLA_W(address));
  if(twi_returnStatus)
  {
    if(twi_returnStatus == 1){return(2);}
    return(twi_returnStatus);
  }
  twi_returnStatus = twi_sendByte(registerAddress);
  if(twi_returnStatus)
  {
    if(twi_returnStatus == 1){return(3);}
    return(twi_returnStatus);
  }
  twi_returnStatus = twi_start();
  if(twi_returnStatus)
  {
    if(twi_returnStatus == 1){return(4);}
    return(twi_returnStatus);
  }
  twi_returnStatus = twi_sendAddress(SLA_R(address));
  if(twi_returnStatus)
  {
    if(twi_returnStatus == 1){return(5);}
    return(twi_returnStatus);
  }
  for(uint8_t i = 0; i < numberBytes; i++)
  {
    if( i == twi_nack )
    {
      twi_returnStatus = twi_receiveByte(0);
      if(twi_returnStatus == 1){return(6);}
      if(twi_returnStatus != MR_DATA_NACK){return(twi_returnStatus);}
    }
    else
    {
      twi_returnStatus = twi_receiveByte(1);
      if(twi_returnStatus == 1){return(6);}
      if(twi_returnStatus != MR_DATA_ACK){return(twi_returnStatus);}
    }
    twi_data[i] = TWDR;
    twi_bytesAvailable = i+1;
    twi_totalBytes = i+1;
  }
  twi_returnStatus = twi_stop();
  if(twi_returnStatus)
  {
    if(twi_returnStatus == 1){return(7);}
    return(twi_returnStatus);
  }
  return(twi_returnStatus);
}
uint8_t twi_write(uint8_t address, uint8_t registerAddress, uint8_t data)
{
  twi_returnStatus = 0;
  twi_returnStatus = twi_start(); 
  if(twi_returnStatus){return(twi_returnStatus);}
  twi_returnStatus = twi_sendAddress(SLA_W(address));
  if(twi_returnStatus)
  {
    if(twi_returnStatus == 1){return(2);}
    return(twi_returnStatus);
  }
  twi_returnStatus = twi_sendByte(registerAddress);
  if(twi_returnStatus)
  {
    if(twi_returnStatus == 1){return(3);}
    return(twi_returnStatus);
  }
  twi_returnStatus = twi_sendByte(data);
  if(twi_returnStatus)
  {
    if(twi_returnStatus == 1){return(3);}
    return(twi_returnStatus);
  }
  twi_returnStatus = twi_stop();
  if(twi_returnStatus)
  {
    if(twi_returnStatus == 1){return(7);}
    return(twi_returnStatus);
  }
  return(twi_returnStatus);
}
uint8_t twi_receive()
{
  twi_bufferIndex = twi_totalBytes - twi_bytesAvailable;
  if(!twi_bytesAvailable)
  {
    twi_bufferIndex = 0;
    return(0);
  }
  twi_bytesAvailable--;
  return(twi_data[twi_bufferIndex]);
}
uint8_t twi_start()
{
  unsigned long startingTime = millis();
  TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
  while (!(TWCR & (1<<TWINT)))
  {
    if(!twi_timeOutDelay){continue;}
    if((millis() - startingTime) >= twi_timeOutDelay)
    {
      twi_lockUp();
      return(1);
    }

  }
  if ((TWI_STATUS == START) || (TWI_STATUS == REPEATED_START))
  {
    return(0);
  }
  if (TWI_STATUS == LOST_ARBTRTN)
  {
    uint8_t bufferedStatus = TWI_STATUS;
    twi_lockUp();
    return(bufferedStatus);
  }
  return(TWI_STATUS);
}
uint8_t twi_sendAddress(uint8_t i2cAddress)
{
  TWDR = i2cAddress;
  unsigned long startingTime = millis();
  TWCR = (1<<TWINT) | (1<<TWEN);
  while (!(TWCR & (1<<TWINT)))
  {
    if(!twi_timeOutDelay){continue;}
    if((millis() - startingTime) >= twi_timeOutDelay)
    {
      twi_lockUp();
      return(1);
    }

  }
  if ((TWI_STATUS == MT_SLA_ACK) || (TWI_STATUS == MR_SLA_ACK))
  {
    return(0);
  }
  uint8_t bufferedStatus = TWI_STATUS;
  if ((TWI_STATUS == MT_SLA_NACK) || (TWI_STATUS == MR_SLA_NACK))
  {
    twi_stop();
    return(bufferedStatus);
  }
  else
  {
    twi_lockUp();
    return(bufferedStatus);
  } 
}
uint8_t twi_sendByte(uint8_t i2cData)
{
  TWDR = i2cData;
  unsigned long startingTime = millis();
  TWCR = (1<<TWINT) | (1<<TWEN);
  while (!(TWCR & (1<<TWINT)))
  {
    if(!twi_timeOutDelay){continue;}
    if((millis() - startingTime) >= twi_timeOutDelay)
    {
      twi_lockUp();
      return(1);
    }

  }
  if (TWI_STATUS == MT_DATA_ACK)
  {
    return(0);
  }
  uint8_t bufferedStatus = TWI_STATUS;
  if (TWI_STATUS == MT_DATA_NACK)
  {
    twi_stop();
    return(bufferedStatus);
  }
  else
  {
    twi_lockUp();
    return(bufferedStatus);
  } 
}
uint8_t twi_receiveByte(uint8_t ack)
{
  unsigned long startingTime = millis();
  if(ack)
  {
    TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA);

  }
  else
  {
    TWCR = (1<<TWINT) | (1<<TWEN);
  }
  while (!(TWCR & (1<<TWINT)))
  {
    if(!twi_timeOutDelay){continue;}
    if((millis() - startingTime) >= twi_timeOutDelay)
    {
      twi_lockUp();
      return(1);
    }
  }
  if (TWI_STATUS == LOST_ARBTRTN)
  {
    uint8_t bufferedStatus = TWI_STATUS;
    twi_lockUp();
    return(bufferedStatus);
  }
  return(TWI_STATUS); 
}
uint8_t twi_stop()
{
  unsigned long startingTime = millis();
  TWCR = (1<<TWINT)|(1<<TWEN)| (1<<TWSTO);
  while ((TWCR & (1<<TWSTO)))
  {
    if(!twi_timeOutDelay){continue;}
    if((millis() - startingTime) >= twi_timeOutDelay)
    {
      twi_lockUp();
      return(1);
    }

  }
  return(0);
}
void twi_lockUp()
{
  TWCR = 0;
  TWCR = _BV(TWEN) | _BV(TWEA);
}

And then the problems began. Rather, there was one problem, but a global one - energy consumption. Having started the clock on a test breadboard, already with a frequency of 1 MHz, I found out that it turns out about 6 mA. The measures taken to reduce this value did not lead to anything. I tried, for example, to pause the frequency of the controller to the limit during a pause: as a result, I got problems with USBasp and extremely modest electricity savings.


The thing is that all the recommendations I met on the topic of energy consumption of the AVR-ok are remarkably described by the phrase "sleep deeper." But the controller in the clock can not sleep! He needs day and night, 100 times per second to update the screen. And just getting out of sleep takes at least 65ms, required to stabilize the oscillator, which is a multiple of the required interval between screen updates.


Perhaps there is some shamanism unknown to me, with which you can use a dream for the controller under the conditions of the clock, but so far I have given up, stuck 7805 with the crown, and I expect the clock to stop after about a week.


But! I have one more same indicator. Maybe if you take some kind of controller more adapted to this task, will it be possible to finally create the watch of my dreams? I would be grateful if a respected public would tell me where to look to solve this problem. STM8 / 32? MSP430? Or, after all, a shaman with AVR modes?




UPD


After only three days, which is a couple of days earlier than planned, the crown died. And here is the interesting thing. As planned, the i2c communication channel between the controller and ds1307 was to be cut off first. According to the datasheet, the connection is chopped off when VCC drops to the level of 1.2 * VBAT. I counted on this, suggesting to show the icon of a dead battery in case of problems with i2c. In fact, it turned out like this: the numbers on the indicator are barely visible, on the + 5v bus, hardly just 2.5v, and i2c is working, the clock is ticking. On VBAT Tiny RTC, wonderful 3v. Unclear.


Then I began to look at why my indicator blinks. Slightly noticeable, but still unpleasant. It turned out an unpleasant thing - what I got, in addition to the general indicator contact from the controller, the second general contact from one of 595, was categorically wrong. That is, it would be correct if I made the RCLK line correctly, but it turned out to me that with every indicator update, completely left bits run through a common contact, in contrast to another common contact and everything else. Believe it or not, it cost a good milliampere of electricity. Fortunately, the wire cutters could easily help in this matter.


After that, I took up the realization of sleep. I don’t know why I so easily believed some random message on a random forum, having hardly seen it, the very one where it was claimed that the AVR from hibernation certainly takes more than 65 milliseconds. That is, it certainly is true, but only in some modes, while in others everything happens much faster. The idea that sleep was not applicable to AVR in my situation was, of course, incorrect. Thanks again olartamonov for opening my eyes.


And finally, the measurement technique definitely leaves much to be desired, I got 6mA with lm7805 connected to the circuit by an ass (Vout and ground to the circuit; Vin in the air). After applying the nippers - minus 4mA.


Total, at the moment it turned out something between 0.3 and 1.6 mA. Alas, the only current measuring instrument available to me is the old workshop, it cannot show me the exact value, the result jumps like crazy inside these limits.


Thank you all for participating in the discussion! Ideas, like a chthonic something, revolve in my head, in which bits in a ring of four 595s rotate under the influence of 555 through an element NOT, coupled with some kind of counter that pulls a 595th latch every 32 bars. And all this to serve the LCD indicator :-)