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;
}
|