aboutsummaryrefslogtreecommitdiff
path: root/code/src/main.c
blob: de2d35f7a5a53077232b09be128217cae423c0a8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/*
 Copyright 2021, tslil clingman

 This program is free software: you can redistribute it and/or modify it under
 the terms of the GNU General Public License as published by the Free Software
 Foundation, either version 3 of the License.

 This program 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 General Public License for more details.

 You should have received a copy of the GNU General Public License along with
 this program. If not, see <https://www.gnu.org/licenses/>.
*/

#include <stdint.h>
#include <avr/pgmspace.h>
#include <util/delay.h>

#include "TinyI2CMaster.h"

#include "font.h"

#define DISP1_ADDR 0x3C
#define DISP2_ADDR 0x3D
#define RTC_ADDR 0x68

// -----------------------------------------------------------------------------
// SSD1306 driver

#define INIT_LEN 7
PROGMEM const uint8_t init[INIT_LEN] = {0xAE, // display off
                                        0x8D, // charge pump
                                        0x14,
                                        0xAF, // display on
                                        0x20,
                                        0xA6, // Normal display
                                        0x02 // page address mode
};

void disp_init(const uint8_t addr, const uint8_t upside_down) {
  i2c_start(addr,0);
  i2c_write(0x00);
  for (uint8_t k = 0; k<INIT_LEN; k++)
    i2c_write(pgm_read_byte(&init[k]));
  if (upside_down) {
    i2c_write(0xA1);
    i2c_write(0xC8);
  }
  i2c_stop();
}

void disp_draw_digit(const uint8_t addr, const uint8_t d, const uint8_t off) {
  for (uint8_t y=0; y<8; y++, i2c_stop()) {
    // Set cursor
    i2c_start(addr,0);
    i2c_write(0x00);
    // Page select
    i2c_write(0xB0|y); // 0<y<8 anyway
    // Column select specialised to halves of screen
    i2c_write(0); // 0..15, lower bits
    i2c_write(0x10|off); // 0x10 .. 0x14, upper bits
    // Prepare to draw
    i2c_start(addr,0);
    i2c_write(0x40);
    for (uint8_t x=0; x<4; x++) {
      // Read the byte
      uint8_t b = pgm_read_byte(&font[d*32+(y*4+x)]);
      // Unpack and draw it
      for (uint8_t j=0; j<4; j++, b>>=2)
        for (uint8_t l=0; l<4; l++)
          i2c_write(((b&1)?0xF0:0x00)|
                    ((b&2)?0x0F:0x00));
    }
  }
}

// -----------------------------------------------------------------------------
// Interface

uint8_t bcd_to_dec(uint8_t k) { return (((k)/16)*10) + ((k)%16); }

uint8_t dec_to_bcd(uint8_t k) { return (((k)/10)*16) + ((k)%10); }

int main(void) {
  // Init screens
  i2c_init();
  disp_init(DISP1_ADDR,1);
  disp_init(DISP2_ADDR,0);
  // Setup input pullups
  DDRA = ~(_BV(DDA0) | _BV(DDA1));
  PORTA = _BV(DDA0) | _BV(DDA1);
  // Tracking state
  uint8_t input = 0, hours = 99, minutes = 99;
  while(1) {
    // Increase hours?
    if (!(PINA&_BV(DDA0))) {
        if ((hours+=1)>23) hours=0;
        input = 1;
    }
    // Increase minutes?
    if (!(PINA&_BV(DDA1))) {
      if ((minutes+=1)>59) minutes=0;
      input = 1;
    }
    // Store changes
    if (input) {
      input = 0;
      i2c_start(RTC_ADDR,0);
      i2c_write(0);
      i2c_write(0); // Seconds
      i2c_write(dec_to_bcd(minutes));
      i2c_write(dec_to_bcd(hours));
      i2c_stop();
    }
    // Draw minutes
    disp_draw_digit(DISP1_ADDR, (minutes/10)%10, 0);
    disp_draw_digit(DISP1_ADDR, (minutes%10), 4);
    // Draw hours
    disp_draw_digit(DISP2_ADDR, (hours/10)%10, 0);
    disp_draw_digit(DISP2_ADDR, (hours%10), 4);
    // Poll for update
    i2c_start(RTC_ADDR,0);
    i2c_write(1);
    i2c_start(RTC_ADDR,2);
    minutes = bcd_to_dec(i2c_read());
    hours = bcd_to_dec(i2c_read());
    i2c_stop();
    // Delay
    _delay_ms(100);
  }
  return 0;
}