Преотличнейшие часы на завалявшемся жк-индикаторе и супермикросхеме 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?
Источник