├── .github └── FUNDING.yml ├── ATtinyWatch.ino ├── README.md ├── WDT_Time.cpp ├── WDT_Time.h ├── font.h ├── font_2x.h ├── font_3x.h ├── ssd1306.cpp └── ssd1306.h /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: moononournation 2 | -------------------------------------------------------------------------------- /ATtinyWatch.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Latest source: 3 | * https://github.com/moononournation/ATtinyWatch 4 | */ 5 | #include 6 | #include 7 | #include 8 | #include "ssd1306.h" 9 | #include "WDT_Time.h" 10 | 11 | #define TIMEOUT 3000 // 3 seconds 12 | #define UNUSEDPINA 1 13 | #define UNUSEDPINB 4 14 | #define BUTTONPIN 3 15 | 16 | #define SET_UP_BUTTON_THRESHOLD 100 17 | #define UP_DOWN_BUTTON_THRESHOLD 600 18 | #define PRESSED_BUTTON_THRESHOLD 1000 19 | 20 | // enum 21 | typedef enum { 22 | normal, sleeping 23 | } run_status_t; 24 | 25 | typedef enum { 26 | time_mode, debug_mode 27 | } display_mode_t; 28 | 29 | // button field constant 30 | #define NO_FIELD 0 31 | #define YEAR_FIELD 1 32 | #define MONTH_FIELD 2 33 | #define DAY_FIELD 3 34 | #define HOUR_FIELD 4 35 | #define MINUTE_FIELD 5 36 | #define SECOND_FIELD 6 37 | #define FIELD_COUNT 6 38 | 39 | // variables 40 | SSD1306 oled; 41 | static uint32_t display_timeout = 0; 42 | static run_status_t run_status = normal; 43 | static display_mode_t display_mode = time_mode; 44 | static display_mode_t last_display_mode = time_mode; 45 | static bool time_changed = false; 46 | static uint8_t selected_field = NO_FIELD; 47 | 48 | void setup() { 49 | // setup input pins, also pullup unused pin for power saving purpose 50 | pinMode(UNUSEDPINA, INPUT_PULLUP); 51 | pinMode(UNUSEDPINB, INPUT_PULLUP); 52 | pinMode(BUTTONPIN, INPUT_PULLUP); 53 | 54 | // init time 55 | init_time(); 56 | 57 | // init I2C and OLED 58 | TinyWireM.begin(); 59 | oled.begin(); 60 | oled.fill(0x00); // clear in black 61 | 62 | // init display timeout 63 | set_display_timeout(); 64 | } 65 | 66 | void loop() { 67 | // detect and handle button input 68 | check_button(); 69 | 70 | if (run_status == sleeping) { 71 | // return to sleep mode after WDT interrupt 72 | system_sleep(); 73 | } else { // not sleeping 74 | if (millis() > display_timeout) { // check display timeout 75 | enter_sleep(); 76 | } else { // normal flow 77 | readRawVcc(); 78 | readRawTemp(); 79 | draw_oled(); 80 | } // normal flow 81 | } // not sleeping 82 | } 83 | 84 | void enter_sleep() { 85 | oled.fill(0x00); // clear screen to avoid show old time when wake up 86 | oled.off(); 87 | delay(2); // wait oled stable 88 | 89 | run_status = sleeping; 90 | } 91 | 92 | void wake_up() { 93 | run_status = normal; 94 | 95 | delay(2); // wait oled stable 96 | oled.on(); 97 | 98 | // update display timeout 99 | set_display_timeout(); 100 | } 101 | 102 | void set_display_timeout() { 103 | display_timeout = millis() + TIMEOUT; 104 | } 105 | 106 | /* 107 | * UI related 108 | */ 109 | 110 | void draw_oled() { 111 | if (display_mode != last_display_mode) { 112 | oled.fill(0x00); 113 | last_display_mode = display_mode; 114 | } 115 | oled.set_font_size(1); 116 | if (display_mode == time_mode) { 117 | // 1st row: print info 118 | oled.set_pos(0, 0); 119 | oled.print(getTemp() / 1000); 120 | oled.draw_pattern(1, 0b00000010); 121 | oled.draw_pattern(1, 0b00000101); 122 | oled.draw_pattern(1, 0b00000010); 123 | oled.write('C'); 124 | 125 | // top right corner: battery status 126 | uint32_t vcc = getVcc(); 127 | // show battery bar from 1.8 V to 3.0 V in 8 pixels, (3000 - 1800) / 8 = 150 128 | uint8_t bat_level = (vcc >= 3000) ? 8 : ((vcc <= 1800) ? 1 : ((vcc - 1800 + 150) / 150)); 129 | oled.draw_pattern(51, 0, 1, 1, 0b00111111); 130 | oled.draw_pattern(1, 0b00100001); 131 | oled.draw_pattern(bat_level, 0b00101101); 132 | oled.draw_pattern(8 + 1 - bat_level, 0b00100001); 133 | oled.draw_pattern(1, 0b00111111); 134 | oled.draw_pattern(1, 0b00001100); 135 | 136 | // 2nd row: print date 137 | print_digit(7, 1, year(), (selected_field == YEAR_FIELD)); 138 | oled.write('-'); 139 | print_digit(7 + (5 * FONT_WIDTH), 1, month(), (selected_field == MONTH_FIELD)); 140 | oled.write('-'); 141 | print_digit(7 + (8 * FONT_WIDTH), 1, day(), (selected_field == DAY_FIELD)); 142 | 143 | // 3rd-4th rows: print time 144 | oled.set_font_size(2); 145 | print_digit(0, 2, hour(), (selected_field == HOUR_FIELD)); 146 | oled.draw_pattern(2 * FONT_2X_WIDTH + 1, 2, 2, 2, 0b00011000); 147 | print_digit(2 * FONT_2X_WIDTH + 5, 2, minute(), (selected_field == MINUTE_FIELD)); 148 | oled.draw_pattern(4 * FONT_2X_WIDTH + 6, 2, 2, 2, 0b00011000); 149 | print_digit(4 * FONT_2X_WIDTH + 2 * FONT_WIDTH, 2, second(), (selected_field == SECOND_FIELD)); 150 | } else if (display_mode == debug_mode) { // debug_mode 151 | print_debug_value(0, 'I', get_wdt_interrupt_count()); 152 | print_debug_value(1, 'M', get_wdt_microsecond_per_interrupt()); 153 | print_debug_value(2, 'V', getVcc()); 154 | print_debug_value(3, 'T', getRawTemp()); 155 | } // debug_mode 156 | } 157 | 158 | void print_digit(uint8_t col, uint8_t page, int value, bool invert_color) { 159 | oled.set_pos(col, page); 160 | if (invert_color) oled.set_invert_color(true); 161 | if (value < 10) oled.write('0'); 162 | oled.print(value); 163 | if (invert_color) oled.set_invert_color(false); 164 | } 165 | 166 | void print_debug_value(uint8_t page, char initial, uint32_t value) { 167 | oled.set_pos(0, page); 168 | oled.write(initial); 169 | oled.set_pos(14, page); 170 | oled.print(value); 171 | } 172 | 173 | // PIN CHANGE interrupt event function 174 | ISR(PCINT0_vect) { 175 | set_display_timeout(); // extent display timeout while user input 176 | } 177 | 178 | void check_button() { 179 | int buttonValue = analogRead(BUTTONPIN); 180 | 181 | if (buttonValue < PRESSED_BUTTON_THRESHOLD) { // button down 182 | set_display_timeout(); // extent display timeout while user input 183 | 184 | if (run_status == sleeping) { 185 | // wake_up if button pressed while sleeping 186 | wake_up(); 187 | } else { // not sleeping 188 | if (buttonValue > UP_DOWN_BUTTON_THRESHOLD) { // down button 189 | handle_adjust_button_pressed(-1); 190 | } else if (buttonValue > SET_UP_BUTTON_THRESHOLD) { // up button 191 | handle_adjust_button_pressed(1); 192 | } else { // set button 193 | handle_set_button_pressed(); 194 | } 195 | } // not sleeping 196 | } // button down 197 | } 198 | 199 | void handle_set_button_pressed() { 200 | display_mode = time_mode; // always switch to time display mode while set button pressed 201 | 202 | selected_field++; 203 | if (selected_field > FIELD_COUNT) { // finish time adjustment 204 | selected_field = NO_FIELD; 205 | if (time_changed) { 206 | wdt_auto_tune(); 207 | time_changed = false; 208 | } //time changed 209 | } // finish time adjustment 210 | } 211 | 212 | void handle_adjust_button_pressed(long value) { 213 | if (selected_field == NO_FIELD) { 214 | // toggle display_mode if no field selected 215 | display_mode = (display_mode == time_mode) ? debug_mode : time_mode; 216 | } else { 217 | long adjust_value; 218 | if (selected_field == YEAR_FIELD) { 219 | // TODO: handle leap year and reverse value 220 | adjust_value = value * SECS_PER_DAY * (leapYear(CalendarYrToTm(year())) ? 366 : 365); 221 | } else if (selected_field == MONTH_FIELD) { 222 | // TODO: handle leap year and reverse value 223 | adjust_value = value * SECS_PER_DAY * getMonthDays(CalendarYrToTm(year()), month()); 224 | } else if (selected_field == DAY_FIELD) { 225 | // TODO: handle leap year and reverse value 226 | adjust_value = value * SECS_PER_DAY; 227 | } else if (selected_field == HOUR_FIELD) { 228 | adjust_value = value * SECS_PER_HOUR; 229 | } else if (selected_field == MINUTE_FIELD) { 230 | adjust_value = value * SECS_PER_MIN; 231 | } else if (selected_field == SECOND_FIELD) { 232 | adjust_value = value; 233 | } 234 | 235 | adjustTime(adjust_value); 236 | time_changed = true; 237 | } 238 | } 239 | 240 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ATtinyWatch 2 | ATtiny85 Watch Core 3 | 4 | ![Prototype](https://content.instructables.com/ORIG/FUG/98WA/IIM1V9D2/FUG98WAIIM1V9D2.jpg?auto=webp&crop=1.2%3A1&frame=1&width=306) 5 | ![Prototype](https://content.instructables.com/ORIG/F7Y/XUDD/IKH5GSX6/F7YXUDDIKH5GSX6.jpg?auto=webp&crop=1.2%3A1&frame=1&width=306) 6 | 7 | Please find more details at instructables: 8 | 9 | http://www.instructables.com/id/ATtiny-Watch-Core/ 10 | 11 | http://www.instructables.com/id/ATtiny85-Ring-Watch/ 12 | 13 | -------------------------------------------------------------------------------- /WDT_Time.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Revised time function count time by WDT timer instead of millis() function 3 | * Add WDT and power related function 4 | * Ref.: 5 | * time function v1.4: https://github.com/PaulStoffregen/Time 6 | * WDT and power related: http://www.re-innovation.co.uk/web12/index.php/en/blog-75/306-sleep-modes-on-attiny85 7 | * readVcc: http://forum.arduino.cc/index.php?topic=222847.0 8 | * Internal temperature sensor: http://21stdigitalhome.blogspot.hk/2014/10/trinket-attiny85-internal-temperature.html 9 | */ 10 | 11 | #if ARDUINO >= 100 12 | #include 13 | #else 14 | #include 15 | #endif 16 | 17 | #include // Supplied Watch Dog Timer Macros 18 | #include // Supplied AVR Sleep Macros 19 | #include 20 | #include "WDT_Time.h" 21 | 22 | // Routines to clear and set bits (used in the sleep code) 23 | #ifndef cbi 24 | #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) 25 | #endif 26 | #ifndef sbi 27 | #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) 28 | #endif 29 | 30 | static tmElements_t tm; // a cache of time elements 31 | static time_t cacheTime; // the time the cache was updated 32 | 33 | static uint32_t sysTime = 0; 34 | static uint32_t prev_microsecond = 0; 35 | static timeStatus_t Status = timeNotSet; 36 | 37 | static uint32_t wdt_microsecond = 0; 38 | static uint32_t prev_sysTime = 0; 39 | 40 | void refreshCache(time_t t) { 41 | if (t != cacheTime) { 42 | breakTime(t, tm); 43 | cacheTime = t; 44 | } 45 | } 46 | 47 | uint8_t hour() { // the hour now 48 | return hour(now()); 49 | } 50 | 51 | uint8_t hour(time_t t) { // the hour for the given time 52 | refreshCache(t); 53 | return tm.Hour; 54 | } 55 | 56 | uint8_t hourFormat12() { // the hour now in 12 hour format 57 | return hourFormat12(now()); 58 | } 59 | 60 | uint8_t hourFormat12(time_t t) { // the hour for the given time in 12 hour format 61 | refreshCache(t); 62 | if ( tm.Hour == 0 ) 63 | return 12; // 12 midnight 64 | else if ( tm.Hour > 12) 65 | return tm.Hour - 12 ; 66 | else 67 | return tm.Hour ; 68 | } 69 | 70 | bool isAM() { // returns true if time now is AM 71 | return !isPM(now()); 72 | } 73 | 74 | bool isAM(time_t t) { // returns true if given time is AM 75 | return !isPM(t); 76 | } 77 | 78 | bool isPM() { // returns true if PM 79 | return isPM(now()); 80 | } 81 | 82 | bool isPM(time_t t) { // returns true if PM 83 | return (hour(t) >= 12); 84 | } 85 | 86 | uint8_t minute() { 87 | return minute(now()); 88 | } 89 | 90 | uint8_t minute(time_t t) { // the minute for the given time 91 | refreshCache(t); 92 | return tm.Minute; 93 | } 94 | 95 | uint8_t second() { 96 | return second(now()); 97 | } 98 | 99 | uint8_t second(time_t t) { // the second for the given time 100 | refreshCache(t); 101 | return tm.Second; 102 | } 103 | 104 | uint8_t day() { 105 | return (day(now())); 106 | } 107 | 108 | uint8_t day(time_t t) { // the day for the given time (0-6) 109 | refreshCache(t); 110 | return tm.Day; 111 | } 112 | 113 | uint8_t weekday() { // Sunday is day 1 114 | return weekday(now()); 115 | } 116 | 117 | uint8_t weekday(time_t t) { 118 | refreshCache(t); 119 | return tm.Wday; 120 | } 121 | 122 | uint8_t month() { 123 | return month(now()); 124 | } 125 | 126 | uint8_t month(time_t t) { // the month for the given time 127 | refreshCache(t); 128 | return tm.Month; 129 | } 130 | 131 | uint16_t year() { // as in Processing, the full four digit year: (2009, 2010 etc) 132 | return year(now()); 133 | } 134 | 135 | uint16_t year(time_t t) { // the year for the given time 136 | refreshCache(t); 137 | return tmYearToCalendar(tm.Year); 138 | } 139 | 140 | bool leapYear(uint16_t y) { 141 | return !((1970 + y) % 4) && ( ((1970 + y) % 100) || !((1970 + y) % 400) ); 142 | } 143 | 144 | uint8_t getMonthDays(uint16_t y, uint8_t m) { 145 | return ((m == 2) && leapYear(y)) ? 29 : monthDays[m - 1]; 146 | } 147 | 148 | uint16_t getYearDays(uint16_t y) { 149 | return leapYear(y) ? 366 : 365; 150 | } 151 | 152 | 153 | /*============================================================================*/ 154 | /* functions to convert to and from system time */ 155 | /* These are for interfacing with time serivces and are not normally needed in a sketch */ 156 | 157 | void breakTime(time_t timeInput, tmElements_t &tm) { 158 | // break the given time_t into time components 159 | // this is a more compact version of the C library localtime function 160 | // note that year is offset from 1970 !!! 161 | 162 | uint16_t tmp_year = 0; 163 | uint16_t yearLength = 0; 164 | uint8_t tmp_month = 1; 165 | uint8_t monthLength = 0; 166 | uint32_t tmp_time = (uint32_t)timeInput; 167 | 168 | tm.Second = tmp_time % 60; 169 | tmp_time /= 60; // now it is minutes 170 | tm.Minute = tmp_time % 60; 171 | tmp_time /= 60; // now it is hours 172 | tm.Hour = tmp_time % 24; 173 | tmp_time /= 24; // now it is days 174 | tm.Wday = ((tmp_time + 4) % 7) + 1; // Sunday is day 1 175 | 176 | while ((yearLength = getYearDays(tmp_year)) <= tmp_time) { 177 | tmp_time -= yearLength; 178 | tmp_year++; 179 | } 180 | tm.Year = tmp_year; // year is offset from 1970 181 | 182 | while ((monthLength = getMonthDays(tmp_year, tmp_month)) <= tmp_time) { 183 | tmp_time -= monthLength; 184 | tmp_month++; 185 | } 186 | tm.Month = tmp_month; // jan is month 1 187 | 188 | tm.Day = tmp_time + 1; // day of month 189 | } 190 | 191 | time_t makeTime(tmElements_t &tm) { 192 | // assemble time elements into time_t 193 | // note year argument is offset from 1970 (see macros in time.h to convert to other formats) 194 | // previous version used full four digit year (or digits since 2000),i.e. 2009 was 2009 or 9 195 | 196 | uint8_t i; 197 | uint32_t seconds = 0; 198 | uint16_t days = 0; 199 | 200 | // seconds from 1970 till 1 jan 00:00:00 of the given year 201 | for (i = 0; i < tm.Year; i++) { 202 | days += getYearDays(i); 203 | } 204 | 205 | // add days for this year, months start from 1 206 | for (i = 1; i < tm.Month; i++) { 207 | days += getMonthDays(tm.Year, i); 208 | } 209 | seconds += (days + tm.Day - 1) * SECS_PER_DAY; 210 | seconds += tm.Hour * SECS_PER_HOUR; 211 | seconds += tm.Minute * SECS_PER_MIN; 212 | seconds += tm.Second; 213 | return (time_t)seconds; 214 | } 215 | 216 | /*=====================================================*/ 217 | /* Low level system time functions */ 218 | 219 | time_t now() { 220 | while (wdt_microsecond - prev_microsecond >= 1000000UL) { 221 | sysTime++; 222 | prev_microsecond += 1000000UL; 223 | } 224 | 225 | return (time_t)sysTime; 226 | } 227 | 228 | void setTime(time_t t) { 229 | sysTime = (uint32_t)t; 230 | Status = timeSet; 231 | prev_microsecond = wdt_microsecond; // restart counting from now (thanks to Korman for this fix) 232 | } 233 | 234 | void setTime(uint8_t hr, uint8_t mnt, uint8_t scnd, uint8_t dy, uint8_t mnth, uint16_t yr) { 235 | // year can be given as full four digit year or two digts (2010 or 10 for 2010); 236 | //it is converted to years since 1970 237 | if ( yr > 99) 238 | yr = yr - 1970; 239 | else 240 | yr += 30; 241 | tm.Year = yr; 242 | tm.Month = mnth; 243 | tm.Day = dy; 244 | tm.Hour = hr; 245 | tm.Minute = mnt; 246 | tm.Second = scnd; 247 | setTime(makeTime(tm)); 248 | } 249 | 250 | void adjustTime(long adjustment) { 251 | sysTime += adjustment; 252 | } 253 | 254 | /* WDT and power related */ 255 | // TODO: dynamic calibrate wdt_microsecond_per_interrupt by current voltage (readVcc) and temperature 256 | uint32_t wdt_microsecond_per_interrupt = DEFAULT_WDT_MICROSECOND; // calibrate value 257 | uint32_t wdt_interrupt_count = 0; 258 | 259 | // 0=16ms, 1=32ms,2=64ms,3=128ms,4=250ms,5=500ms 260 | // 6=1 sec,7=2 sec, 8=4 sec, 9= 8sec 261 | void setup_watchdog(uint8_t ii) { 262 | byte bb; 263 | int ww; 264 | if (ii > 9 ) ii = 9; 265 | bb = ii & 7; 266 | if (ii > 7) bb |= (1 << 5); 267 | bb |= (1 << WDCE); 268 | ww = bb; 269 | 270 | MCUSR &= ~(1 << WDRF); 271 | // start timed sequence 272 | WDTCR |= (1 << WDCE) | (1 << WDE); 273 | // set new watchdog timeout value 274 | WDTCR = bb; 275 | WDTCR |= _BV(WDIE); 276 | sbi(GIMSK, PCIE); // Turn on Pin Change interrupts (Tell Attiny85 we want to use pin change interrupts (can be any pin)) 277 | sbi(PCMSK, PCINT3); 278 | //sbi(PCMSK, PCINT4); 279 | sei(); // Enable the Interrupts 280 | } 281 | 282 | void init_time() { 283 | time_t t; 284 | EEPROM.get(TIME_ADDR, t); 285 | if (t < 1451606400) t = 1451606400; // 2016-01-01 286 | setTime(t); 287 | 288 | uint32_t temp_microsecond_per_interrupt; 289 | EEPROM.get(TIME_ADDR + 4, temp_microsecond_per_interrupt); 290 | if ((temp_microsecond_per_interrupt >= 950000UL) && (temp_microsecond_per_interrupt <= 1050000UL)) { 291 | wdt_microsecond_per_interrupt = temp_microsecond_per_interrupt; 292 | } 293 | 294 | // init WDT 295 | setup_watchdog(WDT_INTERVAL); 296 | } 297 | 298 | // WDT interrupt event function 299 | ISR(WDT_vect) { 300 | sleep_disable(); 301 | 302 | wdt_interrupt_count++; 303 | wdt_microsecond += wdt_microsecond_per_interrupt; 304 | // flush microsecond every half an hour to avoid overflow 305 | if (wdt_microsecond > 1800000000UL) { 306 | now(); 307 | wdt_microsecond -= prev_microsecond; 308 | prev_microsecond = 0; 309 | } 310 | 311 | sleep_enable(); 312 | } 313 | 314 | void wdt_auto_tune() { 315 | // skip tuning for the first input after power on 316 | if (prev_sysTime == 0) { 317 | prev_sysTime = sysTime - (millis() / 1000); // init prev_sysTime 318 | } else { 319 | // check only tune the time if it have pass enough time range (> 1 hour) 320 | if (wdt_interrupt_count > 3600) { 321 | // calculation equation: wdt_microsecond_per_interrupt = (sysTime - prev_sysTime) / wdt_interrupt_count * 1,000,000 micro second 322 | // rephase equation to use a maximum factor (3579) to retain significant value and avoid overflow 323 | // factor allow 20% adjustment: 2^32 / 1.2 / 1000000 = 3579 324 | wdt_microsecond_per_interrupt = 3579 * 1000000 / wdt_interrupt_count * (sysTime - prev_sysTime) / 3579; 325 | 326 | // Reset time and stat data after tune 327 | prev_microsecond = 0; 328 | wdt_microsecond = 0; 329 | wdt_interrupt_count = 0; 330 | prev_sysTime = sysTime; 331 | } 332 | } 333 | EEPROM.put(TIME_ADDR, sysTime); 334 | delay(5); // wait EEPROM write finish 335 | EEPROM.put(TIME_ADDR + 4, wdt_microsecond_per_interrupt); 336 | delay(5); // wait EEPROM write finish 337 | } 338 | 339 | // set system into the sleep state 340 | // system wakes up when watchdog is timed out 341 | void system_sleep() { 342 | cbi(ADCSRA, ADEN); // switch Analog to Digital converter OFF 343 | set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep mode is set here 344 | sleep_mode(); // System actually sleeps here 345 | sbi(ADCSRA, ADEN); // switch Analog to Digital converter ON 346 | } 347 | 348 | uint32_t get_wdt_microsecond_per_interrupt() { 349 | return wdt_microsecond_per_interrupt; 350 | } 351 | uint32_t get_wdt_interrupt_count() { 352 | return wdt_interrupt_count; 353 | } 354 | 355 | 356 | // Voltage and Temperature related 357 | // Common code for both sources of an ADC conversion 358 | uint16_t readADC() { 359 | ADCSRA |= _BV(ADSC); // Start conversion 360 | while (bit_is_set(ADCSRA, ADSC)); // measuring 361 | return ADC; 362 | } 363 | 364 | uint16_t getNewAccumulatedValue(uint16_t accumulatedValue, uint16_t value) { 365 | if (accumulatedValue == 0) { 366 | return value << 6; // initial value, multiply by 64 367 | } else { 368 | accumulatedValue -= accumulatedValue >> 6; // remove one old value, divide by 64 369 | accumulatedValue += value; // add new value 370 | } 371 | return accumulatedValue; 372 | } 373 | 374 | void readRawVcc() { 375 | // Read 1.1V reference against AVcc 376 | // set the reference to Vcc and the measurement to the internal 1.1V reference 377 | ADMUX = _BV(MUX3) | _BV(MUX2); 378 | delay(2); // Wait for Vref to settle 379 | 380 | accumulatedRawVcc = getNewAccumulatedValue(accumulatedRawVcc, readADC()); 381 | } 382 | 383 | uint32_t getVcc() { 384 | readRawVcc(); 385 | return DEFAULT_VOLTAGE_REF / (accumulatedRawVcc >> 6); // calibrated value, average Vcc in millivolts 386 | } 387 | 388 | void readRawTemp() { 389 | // Measure temperature 390 | ADMUX = 0xF | _BV( REFS1 ); // ADC4 (Temp Sensor) and Ref voltage = 1.1V; 391 | delay(2); // Wait for Vref to settle 392 | 393 | accumulatedRawTemp = getNewAccumulatedValue(accumulatedRawTemp, readADC()); 394 | } 395 | 396 | uint32_t getRawTemp() { 397 | readRawTemp(); 398 | return accumulatedRawTemp; 399 | } 400 | 401 | int32_t getTemp() { 402 | readRawTemp(); 403 | 404 | // Temperature compensation using the chip voltage 405 | // with 3.0 V VCC is 1 lower than measured with 1.7 V VCC 406 | uint32_t vcc = getVcc(); 407 | uint16_t compensation = (vcc < 1700) ? 0 : ( (vcc > 3000) ? 1000 : (vcc - 1700) * 10 / 13); 408 | 409 | return (((accumulatedRawTemp * 100000L) - CHIP_TEMP_OFFSET) / CHIP_TEMP_COEFF) + compensation; 410 | } 411 | 412 | -------------------------------------------------------------------------------- /WDT_Time.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Revised time function count time by WDT timer instead of millis() function 3 | * Add WDT and power related function 4 | * Ref.: 5 | * time function v1.4: https://github.com/PaulStoffregen/Time 6 | * WDT and power related: http://www.re-innovation.co.uk/web12/index.php/en/blog-75/306-sleep-modes-on-attiny85 7 | * readVcc: http://forum.arduino.cc/index.php?topic=222847.0 8 | * Internal temperature sensor: http://21stdigitalhome.blogspot.hk/2014/10/trinket-attiny85-internal-temperature.html 9 | */ 10 | 11 | #define TIME_ADDR 0 // EEPROM address for storing the time you set, it can help restore the time easier after change the battery 12 | #define WDT_INTERVAL 6 // ~1 second 13 | #define DEFAULT_WDT_MICROSECOND 1000000UL // put your calibrated value here, should be within +/- 10000 of 1000000 microseconds 14 | 15 | /* calibrate voltage reference 16 | * step 1: comment the follow 2 #define lines 17 | * step 2: program the watch 18 | * step 3: record the debug screen V reading and multimeter measured voltage 19 | * step 4: uncomment the follow 2 #define lines and fill the reading value 20 | * step 5: re-program the watch 21 | */ 22 | #define DEBUG_SCREEN_V 4979 // put your screen reading here 23 | #define MULTI_METER_VOLTAGE 4740 // put your multimeter reading here (in millivolt) 24 | #ifdef DEBUG_SCREEN_VOLTAGE // use calibrated value 25 | #define 1125300UL / DEBUG_SCREEN_V * MULTI_METER_VOLTAGE 26 | #else // use default value 27 | #define DEFAULT_VOLTAGE_REF 1125300UL // 1.1 * 1023 * 1000 28 | #endif 29 | 30 | /* calibrate temperature constant 31 | * step 1: record the debug screen T reading 2 times in different temperature condition 32 | * step 2: uncomment the follow 4 #define lines and fill the values 33 | */ 34 | #define DEBUG_SCREEN_T_1 23207L 35 | #define TEMPERATURE_1 36000L 36 | #define DEBUG_SCREEN_T_2 20286L 37 | #define TEMPERATURE_2 22500L 38 | #ifdef DEBUG_SCREEN_T_1 // use calibrated value 39 | #define CHIP_TEMP_COEFF ((DEBUG_SCREEN_T_1 - DEBUG_SCREEN_T_2) * 100000L / (TEMPERATURE_1 - TEMPERATURE_2)) 40 | #define CHIP_TEMP_OFFSET ((DEBUG_SCREEN_T_1 * 100000L) - (TEMPERATURE_1 * CHIP_TEMP_COEFF)) 41 | #else // use default value 42 | #define CHIP_TEMP_COEFF 6880L // 64 raw samples, 1.075 * 64 * 10000 43 | #define CHIP_TEMP_OFFSET 1746560000L // 64 raw samples, 272.9 *64 * 10000 44 | #endif 45 | // Calibration of the temperature sensor has to be changed for your own ATtiny85 46 | // per tech note: http://www.atmel.com/Images/doc8108.pdf 47 | 48 | #ifndef _Time_h 49 | #ifdef __cplusplus 50 | #define _Time_h 51 | 52 | #include 53 | #ifndef __AVR__ 54 | #include // for __time_t_defined, but avr libc lacks sys/types.h 55 | #endif 56 | 57 | 58 | #if !defined(__time_t_defined) // avoid conflict with newlib or other posix libc 59 | typedef unsigned long time_t; 60 | #endif 61 | 62 | 63 | static const uint8_t monthDays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; // API starts months from 1, this array starts from 0 64 | 65 | // This ugly hack allows us to define C++ overloaded functions, when included 66 | // from within an extern "C", as newlib's sys/stat.h does. Actually it is 67 | // intended to include "time.h" from the C library (on ARM, but AVR does not 68 | // have that file at all). On Mac and Windows, the compiler will find this 69 | // "Time.h" instead of the C library "time.h", so we may cause other weird 70 | // and unpredictable effects by conflicting with the C library header "time.h", 71 | // but at least this hack lets us define C++ functions as intended. Hopefully 72 | // nothing too terrible will result from overriding the C library header?! 73 | extern "C++" { 74 | typedef enum { 75 | timeNotSet, timeSet 76 | } timeStatus_t ; 77 | 78 | typedef enum { 79 | dowInvalid, dowSunday, dowMonday, dowTuesday, dowWednesday, dowThursday, dowFriday, dowSaturday 80 | } timeDayOfWeek_t; 81 | 82 | typedef enum { 83 | tmSecond, tmMinute, tmHour, tmWday, tmDay, tmMonth, tmYear, tmNbrFields 84 | } tmByteFields; 85 | 86 | typedef struct { 87 | uint8_t Second; 88 | uint8_t Minute; 89 | uint8_t Hour; 90 | uint8_t Wday; // day of week, sunday is day 1 91 | uint8_t Day; 92 | uint8_t Month; 93 | uint16_t Year; // offset from 1970; 94 | } tmElements_t, TimeElements, *tmElementsPtr_t; 95 | 96 | //convenience macros to convert to and from tm years 97 | #define tmYearToCalendar(Y) ((Y) + 1970) // full four digit year 98 | #define CalendarYrToTm(Y) ((Y) - 1970) 99 | #define tmYearToY2k(Y) ((Y) - 30) // offset is from 2000 100 | #define y2kYearToTm(Y) ((Y) + 30) 101 | 102 | typedef time_t(*getExternalTime)(); 103 | //typedef void (*setExternalTime)(const time_t); // not used in this version 104 | 105 | 106 | /*==============================================================================*/ 107 | /* Useful Constants */ 108 | #define SECS_PER_MIN (60UL) 109 | #define SECS_PER_HOUR (3600UL) 110 | #define SECS_PER_DAY (SECS_PER_HOUR * 24UL) 111 | #define DAYS_PER_WEEK (7UL) 112 | #define SECS_PER_WEEK (SECS_PER_DAY * DAYS_PER_WEEK) 113 | #define SECS_PER_YEAR (SECS_PER_WEEK * 52UL) 114 | #define SECS_YR_2000 (946684800UL) // the time at the start of y2k 115 | 116 | /* Useful Macros for getting elapsed time */ 117 | #define numberOfSeconds(_time_) (_time_ % SECS_PER_MIN) 118 | #define numberOfMinutes(_time_) ((_time_ / SECS_PER_MIN) % SECS_PER_MIN) 119 | #define numberOfHours(_time_) (( _time_% SECS_PER_DAY) / SECS_PER_HOUR) 120 | #define dayOfWeek(_time_) ((( _time_ / SECS_PER_DAY + 4) % DAYS_PER_WEEK)+1) // 1 = Sunday 121 | #define elapsedDays(_time_) ( _time_ / SECS_PER_DAY) // this is number of days since Jan 1 1970 122 | #define elapsedSecsToday(_time_) (_time_ % SECS_PER_DAY) // the number of seconds since last midnight 123 | // The following macros are used in calculating alarms and assume the clock is set to a date later than Jan 1 1971 124 | // Always set the correct time before settting alarms 125 | #define previousMidnight(_time_) (( _time_ / SECS_PER_DAY) * SECS_PER_DAY) // time at the start of the given day 126 | #define nextMidnight(_time_) ( previousMidnight(_time_) + SECS_PER_DAY ) // time at the end of the given day 127 | #define elapsedSecsThisWeek(_time_) (elapsedSecsToday(_time_) + ((dayOfWeek(_time_)-1) * SECS_PER_DAY) ) // note that week starts on day 1 128 | #define previousSunday(_time_) (_time_ - elapsedSecsThisWeek(_time_)) // time at the start of the week for the given time 129 | #define nextSunday(_time_) ( previousSunday(_time_)+SECS_PER_WEEK) // time at the end of the week for the given time 130 | 131 | 132 | /* Useful Macros for converting elapsed time to a time_t */ 133 | #define minutesToTime_t ((M)) ( (M) * SECS_PER_MIN) 134 | #define hoursToTime_t ((H)) ( (H) * SECS_PER_HOUR) 135 | #define daysToTime_t ((D)) ( (D) * SECS_PER_DAY) // fixed on Jul 22 2011 136 | #define weeksToTime_t ((W)) ( (W) * SECS_PER_WEEK) 137 | 138 | /*============================================================================*/ 139 | /* time and date functions */ 140 | uint8_t hour(); // the hour now 141 | uint8_t hour(time_t t); // the hour for the given time 142 | uint8_t hourFormat12(); // the hour now in 12 hour format 143 | uint8_t hourFormat12(time_t t); // the hour for the given time in 12 hour format 144 | bool isAM(); // returns true if time now is AM 145 | bool isAM(time_t t); // returns true the given time is AM 146 | bool isPM(); // returns true if time now is PM 147 | bool isPM(time_t t); // returns true the given time is PM 148 | uint8_t minute(); // the minute now 149 | uint8_t minute(time_t t); // the minute for the given time 150 | uint8_t second(); // the second now 151 | uint8_t second(time_t t); // the second for the given time 152 | uint8_t day(); // the day now 153 | uint8_t day(time_t t); // the day for the given time 154 | uint8_t weekday(); // the weekday now (Sunday is day 1) 155 | uint8_t weekday(time_t t); // the weekday for the given time 156 | uint8_t month(); // the month now (Jan is month 1) 157 | uint8_t month(time_t t); // the month for the given time 158 | uint16_t year(); // the full four digit year: (2009, 2010 etc) 159 | uint16_t year(time_t t); // the year for the given time 160 | 161 | bool leapYear(uint16_t y); 162 | uint8_t getMonthDays(uint16_t y, uint8_t m); 163 | 164 | time_t now(); // return the current time as seconds since Jan 1 1970 165 | void setTime(time_t t); 166 | void setTime(uint8_t hr, uint8_t min, uint8_t sec, uint8_t day, uint8_t month, uint16_t yr); 167 | void adjustTime(long adjustment); 168 | 169 | /* date strings */ 170 | #define dt_MAX_STRING_LEN 9 // length of longest date string (excluding terminating null) 171 | char* monthStr(uint8_t month); 172 | char* dayStr(uint8_t day); 173 | char* monthShortStr(uint8_t month); 174 | char* dayShortStr(uint8_t day); 175 | 176 | /* low level functions to convert to and from system time */ 177 | void breakTime(time_t time, tmElements_t &tm); // break time_t into elements 178 | time_t makeTime(tmElements_t &tm); // convert time elements into time_t 179 | 180 | } // extern "C++" 181 | #endif // __cplusplus 182 | #endif /* _Time_h */ 183 | 184 | void init_time(); 185 | 186 | /* WDT and power related */ 187 | void wdt_setup(); 188 | void wdt_auto_tune(); 189 | void system_sleep(); 190 | uint32_t get_wdt_microsecond_per_interrupt(); // debug use only 191 | uint32_t get_wdt_interrupt_count(); // debug use only 192 | 193 | // Voltage and Temperature related 194 | static uint16_t accumulatedRawVcc = 0; 195 | static uint16_t accumulatedRawTemp = 0; 196 | void readRawVcc(); // debug use only 197 | uint32_t getVcc(); 198 | void readRawTemp(); // debug use only 199 | uint32_t getRawTemp(); 200 | int32_t getTemp(); 201 | 202 | -------------------------------------------------------------------------------- /font.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define FONT_WIDTH 5 4 | #define FONT_RANGE_START 32 // (space) 5 | #define FONT_RANGE_END 126 // '~' 6 | 7 | static const uint8_t font_bitmap[] PROGMEM = { // 95 ASCII characters 8 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, 0x03, 9 | 0x00, 0x03, 0x00, 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00, 0x4C, 0x7A, 0x4F, 10 | 0x32, 0x00, 0x66, 0x16, 0x68, 0x66, 0x00, 0x38, 0x4F, 0x4D, 0x32, 0x00, 11 | 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x3E, 0x41, 0x00, 0x00, 0x00, 0x41, 12 | 0x3E, 0x00, 0x00, 0x1C, 0x3E, 0x1C, 0x00, 0x00, 0x08, 0x3E, 0x08, 0x00, 13 | 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 14 | 0x40, 0x00, 0x00, 0x00, 0x40, 0x30, 0x0C, 0x03, 0x00, 0x3E, 0x41, 0x41, 15 | 0x3E, 0x00, 0x42, 0x7F, 0x40, 0x00, 0x00, 0x62, 0x51, 0x49, 0x46, 0x00, 16 | 0x22, 0x49, 0x49, 0x36, 0x00, 0x38, 0x26, 0x7F, 0x20, 0x00, 0x4F, 0x49, 17 | 0x49, 0x31, 0x00, 0x3E, 0x49, 0x49, 0x32, 0x00, 0x03, 0x71, 0x09, 0x07, 18 | 0x00, 0x36, 0x49, 0x49, 0x36, 0x00, 0x26, 0x49, 0x49, 0x3E, 0x00, 0x00, 19 | 0x24, 0x00, 0x00, 0x00, 0x40, 0x24, 0x00, 0x00, 0x00, 0x08, 0x14, 0x22, 20 | 0x41, 0x00, 0x14, 0x14, 0x14, 0x14, 0x00, 0x41, 0x22, 0x14, 0x08, 0x00, 21 | 0x02, 0x51, 0x09, 0x06, 0x00, 0x0E, 0x71, 0x49, 0x7E, 0x00, 0x7E, 0x11, 22 | 0x11, 0x7E, 0x00, 0x7F, 0x49, 0x49, 0x36, 0x00, 0x3E, 0x41, 0x41, 0x22, 23 | 0x00, 0x7F, 0x41, 0x41, 0x3E, 0x00, 0x7F, 0x49, 0x49, 0x41, 0x00, 0x7F, 24 | 0x09, 0x09, 0x01, 0x00, 0x3E, 0x41, 0x49, 0x3A, 0x00, 0x7F, 0x08, 0x08, 25 | 0x7F, 0x00, 0x41, 0x7F, 0x41, 0x00, 0x00, 0x30, 0x40, 0x40, 0x3F, 0x00, 26 | 0x7F, 0x08, 0x14, 0x63, 0x00, 0x7F, 0x40, 0x40, 0x40, 0x00, 0x7F, 0x06, 27 | 0x06, 0x7F, 0x00, 0x7F, 0x06, 0x18, 0x7F, 0x00, 0x7F, 0x41, 0x41, 0x7F, 28 | 0x00, 0x7F, 0x09, 0x09, 0x06, 0x00, 0x3E, 0x51, 0x61, 0x7E, 0x00, 0x7F, 29 | 0x19, 0x29, 0x46, 0x00, 0x26, 0x49, 0x49, 0x32, 0x00, 0x01, 0x7F, 0x01, 30 | 0x01, 0x00, 0x3F, 0x40, 0x40, 0x3F, 0x00, 0x0F, 0x70, 0x70, 0x0F, 0x00, 31 | 0x7F, 0x30, 0x30, 0x7F, 0x00, 0x63, 0x1C, 0x1C, 0x63, 0x03, 0x04, 0x78, 32 | 0x04, 0x03, 0x00, 0x61, 0x59, 0x4D, 0x43, 0x00, 0x00, 0x7F, 0x41, 0x00, 33 | 0x03, 0x0C, 0x30, 0x40, 0x00, 0x00, 0x00, 0x41, 0x7F, 0x00, 0x00, 0x02, 34 | 0x01, 0x02, 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00, 0x03, 0x00, 35 | 0x00, 0x00, 0x20, 0x58, 0x58, 0x78, 0x00, 0x7F, 0x48, 0x48, 0x30, 0x00, 36 | 0x30, 0x48, 0x48, 0x48, 0x00, 0x30, 0x48, 0x48, 0x7F, 0x00, 0x30, 0x58, 37 | 0x58, 0x10, 0x00, 0x08, 0x7C, 0x0A, 0x08, 0x00, 0x10, 0xA8, 0xA8, 0x78, 38 | 0x00, 0x7F, 0x08, 0x78, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00, 0x80, 39 | 0x80, 0x74, 0x00, 0x00, 0x7F, 0x10, 0x28, 0x48, 0x00, 0x40, 0x7F, 0x40, 40 | 0x00, 0x00, 0x78, 0x08, 0x78, 0x78, 0x00, 0x78, 0x08, 0x08, 0x70, 0x00, 41 | 0x30, 0x48, 0x48, 0x30, 0x00, 0xF8, 0x48, 0x48, 0x30, 0x00, 0x30, 0x48, 42 | 0x48, 0xF8, 0x00, 0x78, 0x10, 0x08, 0x08, 0x00, 0x50, 0x58, 0x68, 0x28, 43 | 0x00, 0x08, 0x7C, 0x48, 0x00, 0x00, 0x38, 0x40, 0x40, 0x78, 0x00, 0x18, 44 | 0x60, 0x60, 0x18, 0x18, 0x60, 0x38, 0x60, 0x18, 0x00, 0x48, 0x30, 0x30, 45 | 0x48, 0x00, 0x18, 0xA0, 0xA0, 0x78, 0x00, 0x48, 0x68, 0x58, 0x48, 0x00, 46 | 0x08, 0x77, 0x41, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x41, 0x77, 47 | 0x08, 0x00, 0x00, 0x02, 0x01, 0x02, 0x01, }; 48 | 49 | -------------------------------------------------------------------------------- /font_2x.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define FONT_2X_WIDTH 9 4 | #define FONT_2X_RANGE_START 48 // '0' 5 | #define FONT_2X_RANGE_END 57 // '9' 6 | 7 | static const uint8_t font_2x_bitmap[] PROGMEM = { // digit 0-9 8 | 0x00, 0x00, 0xE0, 0x1F, 0xF8, 0x3F, 0x3C, 0x20, 0x06, 0x20, 0x02, 0x38, 9 | 0xC2, 0x1F, 0xFC, 0x07, 0xF8, 0x00, 0x00, 0x00, 0x08, 0x20, 0x08, 0x20, 10 | 0x04, 0x3C, 0xE4, 0x3F, 0xFC, 0x2F, 0x3E, 0x20, 0x00, 0x00, 0x00, 0x00, 11 | 0x00, 0x20, 0x00, 0x30, 0x0C, 0x38, 0x06, 0x34, 0x06, 0x33, 0xC6, 0x31, 12 | 0xFE, 0x30, 0x3C, 0x30, 0x00, 0x00, 0x00, 0x18, 0x00, 0x30, 0x04, 0x20, 13 | 0x46, 0x20, 0x46, 0x20, 0xE6, 0x18, 0xBE, 0x1F, 0x9C, 0x07, 0x00, 0x00, 14 | 0x00, 0x02, 0x80, 0x03, 0x40, 0x22, 0x30, 0x22, 0x08, 0x3E, 0xF4, 0x3F, 15 | 0xFE, 0x27, 0x1E, 0x22, 0x00, 0x02, 0x00, 0x10, 0x00, 0x30, 0xFC, 0x20, 16 | 0x46, 0x20, 0x46, 0x20, 0xC6, 0x18, 0xC6, 0x1F, 0x86, 0x0F, 0x00, 0x00, 17 | 0xC0, 0x1F, 0xF0, 0x3F, 0xF8, 0x20, 0x4C, 0x20, 0x44, 0x30, 0xC2, 0x1F, 18 | 0x82, 0x0F, 0x02, 0x01, 0x00, 0x00, 0x04, 0x20, 0x06, 0x30, 0x06, 0x3C, 19 | 0x06, 0x0F, 0x86, 0x01, 0x66, 0x00, 0x36, 0x00, 0x0E, 0x00, 0x02, 0x00, 20 | 0x00, 0x1E, 0x78, 0x31, 0xFC, 0x20, 0xC2, 0x20, 0xC2, 0x11, 0xC2, 0x1F, 21 | 0x3E, 0x0F, 0x08, 0x00, 0x00, 0x00, 0xF0, 0x30, 0xF8, 0x21, 0x84, 0x21, 22 | 0x02, 0x11, 0x02, 0x18, 0xEE, 0x0F, 0xFC, 0x03, 0xF8, 0x00, 0x00, 0x00, 23 | }; 24 | -------------------------------------------------------------------------------- /font_3x.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define FONT_3X_WIDTH 14 4 | #define FONT_3X_RANGE_START 48 // '0' 5 | #define FONT_3X_RANGE_END 57 // '9' 6 | 7 | static const uint8_t font_3x_bitmap[] PROGMEM = { // digit 0-9 8 | 0xE0, 0x7F, 0x00, 0xF0, 0xFF, 0x00, 0xFC, 0xFF, 0x03, 0xFC, 0xFF, 0x03, 9 | 0x0E, 0x00, 0x07, 0x06, 0x00, 0x06, 0x02, 0x00, 0x04, 0x06, 0x00, 0x06, 10 | 0xFC, 0xFF, 0x03, 0xFC, 0xFF, 0x03, 0xF8, 0xFF, 0x01, 0xE0, 0x7F, 0x00, 11 | 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 12 | 0x08, 0x00, 0x04, 0x08, 0x00, 0x04, 0xFC, 0xFF, 0x07, 0xFC, 0xFF, 0x07, 13 | 0xFC, 0xFF, 0x07, 0xFE, 0xFF, 0x07, 0x00, 0x00, 0x06, 0x00, 0x00, 0x04, 14 | 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 15 | 0x00, 0x00, 0x04, 0xF0, 0x00, 0x06, 0xF8, 0x81, 0x07, 0xFC, 0xC1, 0x07, 16 | 0xE4, 0xE1, 0x07, 0xC6, 0xB0, 0x07, 0x02, 0xB8, 0x07, 0x06, 0x9E, 0x07, 17 | 0x0E, 0x8F, 0x07, 0xFE, 0x8F, 0x07, 0xFC, 0x87, 0x07, 0xF8, 0xC3, 0x07, 18 | 0xF0, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xE0, 0x01, 19 | 0x78, 0xF0, 0x03, 0x7C, 0xF0, 0x03, 0x7E, 0xE2, 0x06, 0x02, 0x02, 0x04, 20 | 0x02, 0x02, 0x04, 0x06, 0x06, 0x06, 0x8E, 0x0F, 0x07, 0xFE, 0xFF, 0x07, 21 | 0xFC, 0xFD, 0x03, 0xF8, 0xFC, 0x03, 0x70, 0xF8, 0x00, 0x00, 0x00, 0x00, 22 | 0x00, 0x20, 0x00, 0x00, 0x78, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x66, 0x00, 23 | 0x00, 0x63, 0x00, 0xC0, 0x61, 0x04, 0x60, 0x60, 0x04, 0xF0, 0xFF, 0x07, 24 | 0xFC, 0xFF, 0x07, 0xFE, 0xFF, 0x07, 0xFE, 0xFF, 0x07, 0x00, 0x60, 0x06, 25 | 0x00, 0x60, 0x04, 0x00, 0x20, 0x00, 0x00, 0xE0, 0x01, 0xF8, 0xF7, 0x03, 26 | 0xFE, 0xF3, 0x03, 0x3C, 0xE1, 0x06, 0xBC, 0x01, 0x04, 0xBC, 0x01, 0x06, 27 | 0xBC, 0x01, 0x06, 0xBC, 0x83, 0x07, 0x9C, 0xFF, 0x03, 0x1C, 0xFF, 0x03, 28 | 0x0E, 0xFE, 0x01, 0x02, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 29 | 0xC0, 0x7F, 0x00, 0xF0, 0xFF, 0x01, 0xF8, 0xFF, 0x03, 0xFC, 0xFF, 0x03, 30 | 0x3C, 0x0E, 0x07, 0x06, 0x02, 0x04, 0x06, 0x03, 0x04, 0x02, 0x07, 0x06, 31 | 0x3E, 0xFF, 0x07, 0x7E, 0xFF, 0x03, 0x7C, 0xFE, 0x03, 0x38, 0xFC, 0x01, 32 | 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0xFE, 0x00, 0x00, 33 | 0x1E, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x1E, 0xC0, 0x03, 0x1E, 0xF0, 0x07, 34 | 0x1E, 0xFC, 0x07, 0x1E, 0xFE, 0x03, 0x9E, 0x7F, 0x00, 0xDE, 0x01, 0x00, 35 | 0x7E, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 36 | 0xE0, 0xF0, 0x01, 0xF8, 0xFB, 0x03, 0xFC, 0xFB, 0x03, 0xFC, 0x1F, 0x07, 37 | 0x86, 0x0F, 0x06, 0x82, 0x0F, 0x04, 0x82, 0x0F, 0x04, 0x06, 0x0F, 0x06, 38 | 0x8E, 0x1F, 0x06, 0xFC, 0xFF, 0x03, 0xFC, 0xFE, 0x03, 0x78, 0xFC, 0x00, 39 | 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xC3, 0x01, 0xF8, 0xC7, 0x03, 40 | 0xFC, 0xEF, 0x07, 0xFE, 0xCF, 0x07, 0x06, 0x8E, 0x04, 0x02, 0x0C, 0x04, 41 | 0x02, 0x0C, 0x06, 0x06, 0x86, 0x03, 0xFC, 0xFF, 0x03, 0xFC, 0xFF, 0x01, 42 | 0xF8, 0xFF, 0x00, 0xF0, 0x7F, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 43 | }; 44 | 45 | -------------------------------------------------------------------------------- /ssd1306.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Ref.: 3 | * DigisparkOLED: https://github.com/digistump/DigistumpArduino/tree/master/digistump-avr/libraries/DigisparkOLED 4 | * SSD1306 data sheet: https://www.adafruit.com/datasheets/SSD1306.pdf 5 | */ 6 | #include 7 | #include 8 | #include "ssd1306.h" 9 | 10 | /* 11 | * Software Configuration, data sheet page 64 12 | */ 13 | 14 | static const uint8_t ssd1306_configuration[] PROGMEM = { 15 | 16 | #ifdef SCREEN_128X64 17 | 0xA8, 0x3F, // Set MUX Ratio, 0F-3F 18 | #else // SCREEN_128X32 / SCREED_64X32 19 | 0xA8, 0x1F, // Set MUX Ratio, 0F-3F 20 | #endif 21 | 22 | 0xD3, 0x00, // Set Display Offset 23 | 0x40, // Set Display Start line 24 | 0xA1, // Set Segment re-map, mirror, A0/A1 25 | 0xC8, // Set COM Output Scan Direction, flip, C0/C8 26 | 27 | #ifdef SCREEN_128X32 28 | 0xDA, 0x02, // Set COM Pins hardware configuration, Sequential 29 | #else // SCREEN_128X64 / SCREEN_64X32 30 | 0xDA, 0x12, // Set Com Pins hardware configuration, Alternative 31 | #endif 32 | 33 | 0x81, 0x01, // Set Contrast Control, 01-FF 34 | 0xA4, // Disable Entire Display On, 0xA4=Output follows RAM content; 0xA5,Output ignores RAM content 35 | 0xA6, // Set Display Mode. A6=Normal; A7=Inverse 36 | 0xD5, 0x80, // Set Osc Frequency 37 | 0x8D, 0x14, // Enable charge pump regulator 38 | 0xAF // Display ON in normal mode 39 | }; 40 | 41 | SSD1306::SSD1306(void) {} 42 | 43 | void SSD1306::begin(void) 44 | { 45 | for (uint8_t i = 0; i < sizeof (ssd1306_configuration); i++) { 46 | ssd1306_send_command(pgm_read_byte_near(&ssd1306_configuration[i])); 47 | } 48 | } 49 | 50 | void SSD1306::ssd1306_send_command_start(void) { 51 | TinyWireM.beginTransmission(SSD1306_I2C_ADDR); 52 | TinyWireM.send(0x00); //command 53 | } 54 | 55 | void SSD1306::ssd1306_send_command_stop(void) { 56 | TinyWireM.endTransmission(); 57 | } 58 | 59 | void SSD1306::ssd1306_send_command(uint8_t command) 60 | { 61 | ssd1306_send_command_start(); 62 | TinyWireM.send(command); 63 | ssd1306_send_command_stop(); 64 | } 65 | 66 | void SSD1306::ssd1306_send_data_start(void) 67 | { 68 | TinyWireM.beginTransmission(SSD1306_I2C_ADDR); 69 | TinyWireM.send(0x40); //data 70 | } 71 | 72 | void SSD1306::ssd1306_send_data_stop(void) 73 | { 74 | TinyWireM.endTransmission(); 75 | } 76 | 77 | void SSD1306::ssd1306_send_data_byte(uint8_t data) 78 | { 79 | if (TinyWireM.write(data) == 0) { 80 | // push data if detect buffer used up 81 | ssd1306_send_data_stop(); 82 | ssd1306_send_data_start(); 83 | TinyWireM.write(data); 84 | } 85 | } 86 | 87 | void SSD1306::set_area(uint8_t col, uint8_t page, uint8_t col_range_minus_1, uint8_t page_range_minus_1) 88 | { 89 | ssd1306_send_command_start(); 90 | TinyWireM.send(0x20); 91 | TinyWireM.send(0x01); 92 | TinyWireM.send(0x21); 93 | #ifdef XOFFSET // SCREEN_SCREEN_64X32 94 | TinyWireM.send(XOFFSET + col); 95 | TinyWireM.send(XOFFSET + col + col_range_minus_1); 96 | #else // SCREEN_128_64 / SCREEN_128X32 97 | TinyWireM.send(col); 98 | TinyWireM.send(col + col_range_minus_1); 99 | #endif 100 | TinyWireM.send(0x22); 101 | TinyWireM.send(page); 102 | TinyWireM.send(page + page_range_minus_1); 103 | ssd1306_send_command_stop(); 104 | } 105 | 106 | void SSD1306::fill(uint8_t data) 107 | { 108 | set_area(0, 0, WIDTH - 1, PAGES - 1); 109 | uint16_t data_size = (WIDTH) * (PAGES); 110 | 111 | ssd1306_send_data_start(); 112 | for (uint16_t i = 0; i < data_size; i++) 113 | { 114 | ssd1306_send_data_byte(data); 115 | } 116 | ssd1306_send_data_stop(); 117 | } 118 | 119 | void SSD1306::v_line(uint8_t col, uint8_t data) 120 | { 121 | set_area(col, 0, 0, PAGES); 122 | ssd1306_send_data_start(); 123 | for (uint8_t i = 0; i <= PAGES; i++) 124 | { 125 | ssd1306_send_data_byte(data); 126 | } 127 | ssd1306_send_data_stop(); 128 | } 129 | 130 | static uint8_t col = 0; 131 | static uint8_t page = 0; 132 | static bool invert_color = false; 133 | static uint8_t font_size = 1; 134 | static uint8_t font_width = FONT_WIDTH; 135 | static uint8_t font_volume = 1 * FONT_WIDTH; 136 | static uint8_t ascii_code_start = FONT_RANGE_START; 137 | static uint8_t ascii_code_end = FONT_RANGE_END; 138 | 139 | void SSD1306::set_pos(uint8_t set_col, uint8_t set_page) { 140 | col = set_col; 141 | page = set_page; 142 | } 143 | 144 | void SSD1306::draw_pattern(uint8_t width, uint8_t pattern) { 145 | draw_pattern(col, page, width, 1, pattern); 146 | } 147 | 148 | void SSD1306::draw_pattern(uint8_t set_col, uint8_t set_page, uint8_t width, uint8_t height, uint8_t pattern) { 149 | set_area(set_col, set_page, width, height - 1); 150 | ssd1306_send_data_start(); 151 | for (uint8_t i = 0; i < (width * height); i++) { 152 | ssd1306_send_data_byte(pattern); 153 | } 154 | ssd1306_send_data_stop(); 155 | 156 | col = set_col + width; 157 | page = set_page; 158 | } 159 | 160 | void SSD1306::set_invert_color(bool set_invert) { 161 | invert_color = set_invert; 162 | } 163 | 164 | void SSD1306::set_font_size(uint8_t set_size) { 165 | font_size = set_size; 166 | if (set_size == 1) { 167 | font_width = FONT_WIDTH; 168 | font_volume = 1 * FONT_WIDTH; 169 | ascii_code_start = FONT_RANGE_START; 170 | ascii_code_end = FONT_RANGE_END; 171 | #ifdef FONT_2X_WIDTH 172 | } else if (set_size == 2) { 173 | font_width = FONT_2X_WIDTH; 174 | font_volume = 2 * FONT_2X_WIDTH; 175 | ascii_code_start = FONT_2X_RANGE_START; 176 | ascii_code_end = FONT_2X_RANGE_END; 177 | #endif 178 | #ifdef FONT_3X_WIDTH 179 | } else if (set_size == 3) { 180 | font_width = FONT_3X_WIDTH; 181 | font_volume = 3 * FONT_3X_WIDTH; 182 | ascii_code_start = FONT_3X_RANGE_START; 183 | ascii_code_end = FONT_3X_RANGE_END; 184 | #endif 185 | } 186 | } 187 | 188 | size_t SSD1306::write(uint8_t c) { 189 | if ((c < ascii_code_start) || (c > ascii_code_end)) return 0; 190 | 191 | set_area(col, page, font_width - 1, font_size - 1); 192 | 193 | uint16_t offset = (c - ascii_code_start) * font_volume; 194 | uint8_t data; 195 | 196 | ssd1306_send_data_start(); 197 | for (uint8_t i = 0; i < font_volume; i++) 198 | { 199 | if (font_size == 1) { 200 | data = pgm_read_byte_near(&font_bitmap[offset++]); 201 | #ifdef FONT_2X_WIDTH 202 | } else if (font_size == 2) { 203 | data = pgm_read_byte_near(&font_2x_bitmap[offset++]); 204 | #endif 205 | #ifdef FONT_3X_WIDTH 206 | } else if (font_size == 3) { 207 | data = pgm_read_byte_near(&font_3x_bitmap[offset++]); 208 | #endif 209 | } 210 | if (invert_color) data = ~ data; // invert 211 | ssd1306_send_data_byte(data); 212 | } 213 | ssd1306_send_data_stop(); 214 | 215 | // move pos forward 216 | col += font_width; 217 | return font_width; 218 | } 219 | 220 | void SSD1306::print_string(uint8_t col, uint8_t page, const char str[]) { 221 | set_pos(col, page); 222 | print(str); 223 | } 224 | 225 | void SSD1306::off(void) 226 | { 227 | ssd1306_send_command(0xAE); 228 | } 229 | 230 | void SSD1306::on(void) 231 | { 232 | ssd1306_send_command(0xAF); 233 | } 234 | 235 | -------------------------------------------------------------------------------- /ssd1306.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Ref.: 3 | * DigisparkOLED: https://github.com/digistump/DigistumpArduino/tree/master/digistump-avr/libraries/DigisparkOLED 4 | * SSD1306 data sheet: https://www.adafruit.com/datasheets/SSD1306.pdf 5 | */ 6 | #include 7 | #include "font.h" 8 | #include "font_2x.h" 9 | //#include "font_3x.h" 10 | 11 | // custom I2C address by define SSD1306_I2C_ADDR 12 | #ifndef SSD1306_I2C_ADDR 13 | #define SSD1306_I2C_ADDR 0x3C 14 | #endif 15 | 16 | // custom screen resolution by define SCREEN128X64, SCREEN128X32, SCREEN64X48 or SCREED64X32 (default) 17 | //#define SCREEN_128X64 18 | //#define SCREEN_128X32 19 | //#define SCREEN_64X48 // not tested 20 | #define SCREEN_64X32 21 | 22 | #ifdef SCREEN_128X64 23 | #define WIDTH 0x0100 24 | #define PAGES 0x08 25 | #else 26 | #ifdef SCREEN_128X32 27 | #define WIDTH 0x0100 28 | #define PAGES 0x04 29 | #else 30 | #ifdef SCREEN_64X48 31 | #define WIDTH 0x40 32 | #define XOFFSET 0x20 33 | #define PAGES 0x06 34 | #else //SCREED_64X32 35 | #define WIDTH 0x40 36 | #define XOFFSET 0x20 37 | #define PAGES 0x04 38 | #endif 39 | #endif 40 | #endif 41 | 42 | class SSD1306 : public Print { 43 | 44 | public: 45 | virtual size_t write(uint8_t); 46 | 47 | SSD1306(void); 48 | void begin(void); 49 | void ssd1306_send_command_start(void); 50 | void ssd1306_send_command_stop(void); 51 | void ssd1306_send_command(uint8_t command); 52 | void ssd1306_send_data_start(void); 53 | void ssd1306_send_data_stop(void); 54 | void ssd1306_send_data_byte(uint8_t byte); 55 | void set_area(uint8_t col, uint8_t page, uint8_t col_range_minus_1, uint8_t page_range_minus_1); 56 | void v_line(uint8_t col, uint8_t fill); 57 | void fill(uint8_t fill); 58 | void set_pos(uint8_t set_col, uint8_t set_page); 59 | void set_invert_color(bool set_invert); 60 | void set_font_size(uint8_t set_font_size); 61 | 62 | void draw_pattern(uint8_t width, uint8_t pattern); 63 | void draw_pattern(uint8_t set_col, uint8_t set_page, uint8_t width, uint8_t height, uint8_t pattern); 64 | void print_string(uint8_t set_col, uint8_t set_page, const char str[]); 65 | 66 | void off(); 67 | void on(); 68 | }; 69 | 70 | --------------------------------------------------------------------------------