Неудача с часами на ATMega48

Преотличнейшие часы на завалявшемся жк-индикаторе и супермикросхеме ATMega48. Но не получились.

Неудача с часами на ATMega48

То есть, часы, конечно, работают, но, увы, проработают они недолго.

Давным-давно в коробке в дальней тумбочке болталась у меня пара семисегментных жк-индикаторов. И так же давно мне хотелось взять их в оборот и соорудить на основе одного из них часы. Давным-давно: это, в буквальном смысле, семь лет. Именно тогда, в 2011 году возник у меня интерес к электронике. Не думая долго, заказал я тогда всякой всячины в одном хорошем интернет магазине (нет, не на Али; не уверен, что он тогда уже был). Но как-то у меня не заладилось творить вечное. После нескольких протравленных плат, забросил я это развлечение и забыл.

И вот, когда с Али пришла посылка, содержавшая макетки, пакетик 595-х в dip-корпусах, Tiny RTC на ds1307, и, что самое важное, USBasp, пришло время вернуться к старой задумке. Из старой заначки у меня был ATMega48, тот который о 28 ног, lm7805, всякая мелочевка в виде резисторов/конденсаторов/кнопок, и, собственно, индикатор на 40 ногах.

Вообще говоря, изначально планировалось использовать AtTiny13, которые у меня тоже валяются, но прикинув и так, и этак, я не придумал как обойтись его 5 ногами что бы и на индикатор выводить, и две кнопки читать, и с часами по i2c общаться. С 28 ногами меги, экономить и извращаться с объединением ног уже не приходится. Хотя, конечно, и в этом случае без 595-х не обойтись. Но если с тини я планировал выводить все 32 бита данных для индикатора последовательно, то с мегой можно выводить картинку сразу на все четыре микросхемы параллельно.


Все 595-е спрятаны под индикатором. На фото видны выводы корпуса микросхемы.

595 было использовано именно четыре штуки, потому что индикатор, хотя и имеет 40 ног, но на сегменты выведены только 32. Увы, но это только 3.5 индикатор, то есть у него 3 полноценных цифры, плюс единица. Есть символ секунд, но никакого обозначения для AM/PM. Но уж что есть. Заказывать более продвинутый индикатор, не собрав ни разу в жизни ничего на жк-индикаторах, мне бы не хотелось.


Рабочая документация на индикатор. Пришлось тестером выяснять какая ножка какому сегменту соответствует.

Ну а дальше дело техники. Схемы никогда не было, но там все очевидно. Надо было только выделить четыре ноги на вход буферов, общую ногу для индикатора, ногу для SCLK/RCLK, ногу на OE, две ноги для i2c, две ноги для кнопок. Все это было, конечно, неправильно. Почему я решил, что OE надо заводить на контроллер, а SCLK объединять с RCLK — уже и сам не вспомню. Надо было делать как раз наоборот. А общий провод индикатора на контроллере не нужен вообще, можно было обойтись одним из выводов первой 595-й (что я тоже, в итоге, сделал).


Провода. Много их. Вид изнутри.


Вид снаружи.

Самое интересное во всем этом прожекте: код вывода на индикатор. Тонкость в том, что на жк-индикатор нельзя просто так подать напряжение и забыть. Надо около ста раз в секунду менять полярность между сегментами и общим контактом, что бы все было красиво и приятно глазу. В качестве среды разработки, не мудрствуя лукаво, я использовал Arduino IDE, лишь чуть помучавшись в паре моментов. Во-первых, пришлось использовать пакет MiniCore ( https://github.com/MCUdude/MiniCore ), ведь писать пришлось не для большой готовой ардуины, а для слабого и голого ATMega48. Во-вторых, прямая попытка использовать встроенную библиотеку для i2c ни к чему не привела. По какой-то не вполне понятной причине, работать оно не пожелало, пришлось найти другую библиотеку, а потом и обрезать ее для своих нужд.

Как оказалось, 4 килобайта — это очень мало. Особенно, если писать на C. Возможно, если бы я решился перейти на ассемблер, это ограничение не давило бы так серьезно, но помня мои прошлые эксерсизы в писании на ассемблере для AVR, желания у меня такого не возникло. А на C писать — много места надо. Чуть скобку ненароком поставил — сотня байт в трубу.

Собственно, комментированный код

#include  #include  #include   #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< 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<= 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<= 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<= 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<= 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<= twi_timeOutDelay)     {       twi_lockUp();       return(1);     }    }   return(0); } void twi_lockUp() {   TWCR = 0;   TWCR = _BV(TWEN) | _BV(TWEA); }

А дальше начались проблемы. Вернее, проблема выявилась одна, но глобальная — энергопотребление. Запустив часы на тестовой макетке, уже с частотой 1MHz, выяснил, что получается около 6mA. Предпринятые меры к уменьшению этого значения ни к чему не привели. Пытался, например, на время паузы уменьшать частоту контроллера до предела: в результате получил проблемы с USBasp-ом и крайне скромную экономию электричества.

Штука в том, что все рекомендации встреченные мною на тему энергопотребления AVR-ок замечательно описываются фразой «спите глубже». Но контроллеру в часах нельзя спать! Ему денно и нощно, 100 раз в секунду необходимо обновлять экран. А один только вывод из сна занимает как минимум 65ms, требуемые на стабилизацию осциллятора, что кратно больше требуемого промежутка между обновлениями экрана.

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

Но! У меня же есть еще один такой же индикатор. Может быть, если взять какой-то более приспособленный к этой задаче контроллер, удастся создать, наконец, часы моей мечты? Я был бы признателен, если бы уважаемая публика подсказала мне куда следует смотреть для решения этой задачи. STM8/32? MSP430? Или, все-таки, шаманить с режимами AVR?

 
Источник

Читайте также