数字闹钟及温度计(不需时钟模块)

前言

最近想用Arduino制作一个数字闹钟,但因为没有独立的时钟模块造成时间误差太大让我困扰,但还是想了一个办法,让程序自动校正时间,于是不需要时钟模块,时间也能相当准确,效果如下图:

材料

名称 数量
Arduino UNO 一个
LCD 12864显示屏 一个
LM35 热敏电阻 一个
3W小喇叭 一个
面包版 一个
跳线 数根

校正时间的方法

以准确时间为标准,测量一段时间内的误差(比如测量3小时后的误差),计算每秒钟的误差毫秒数,取整后然后每秒进行校正,由于取整,仍然会有误差,再将误差扩大60倍,也就是每分钟的误差,取整后每分钟再进行校正,以此类推,直到你认为精确度足够。

Arduino代码

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

#include <U8glib.h>



#define BUZZER 10//喇叭针脚


U8GLIB_ST7920_128X64_4X u8g(13, 12, 11);//配置屏幕针脚

int base = 0, ms = 0, s = 0, mi = 48, hr = 19, week = 6;//进行初始化时间
int nMi = 0, nHr = 8;//闹钟时间
double tem = 0;//温度

void setup() {
pinMode(BUZZER, OUTPUT);
u8g.setRot180();//旋转屏幕180度
base = millis();//初始化基准时间
}

void loop() {
calc();//更新时间
func();//处理函数
u8g.firstPage(); do draw(); while (u8g.nextPage());//刷新屏幕
}

void func() {
tem = (double)analogRead(A0) * (5 / 10.23);//读取并计算温度
if (hr == nHr && mi == nMi) {//播放闹钟1分钟
tone(BUZZER, 200 + ms);//随毫秒数改变闹钟音调,产生铃声
} else {
noTone(BUZZER);//结束播放铃声
digitalWrite(BUZZER, LOW);//设置为低电平
}
}

void calc() {
ms = millis() - base;
if (ms > 999) {
ms = 0;
base = millis() - 61; //用于校正误差,每过1s提前61ms
if (++s > 59) {
s = 0;
base = millis() + 8; //用于校正误差,每过1min延迟8ms
if (++mi > 59) {
mi = 0;
base = millis() - 38; //用于校正误差,每过1hour提前38ms
if (++hr > 23) {
hr = 0;
base = millis() - 8; //用于校正误差,每过1day提前8ms
if (++week > 6) week = 0;
}
}
}
}
}

void draw() {
for (int x = 0; x < 2 * s; x++)//按照秒数绘制宽4个像素的进度条
for (int y = 0; y < 4; y++)
u8g.drawPixel(x + 5, y + 3);

u8g.setFont(u8g_font_gdb30n);//设置gdb30像素数字类字体

if (hr > 9) {//如果数字只有一位就添加0在前面
u8g.setPrintPos( 8, 41);
} else {
u8g.drawStr(10, 41, "0");
u8g.setPrintPos( 32, 41);
}
u8g.print(hr);

if (s % 2 == 0)//冒号在秒数为偶数时才绘制,达到冒号闪烁的效果
u8g.drawStr( 56, 37, ":");

if (mi > 9) {//如果数字只有一位就添加0在前面
u8g.setPrintPos( 70, 41);
} else {
u8g.drawStr(70, 41, "0");
u8g.setPrintPos( 94, 41);
}
u8g.print(mi);

u8g.setFont(u8g_font_gdb14r);//设置gdb14像素常用类字体
switch (week) {//绘制星期数到屏幕
case 0: u8g.drawStr(6, 61, "Sun"); break;
case 1: u8g.drawStr(6, 61, "Mon"); break;
case 2: u8g.drawStr(6, 61, "Tue"); break;
case 3: u8g.drawStr(6, 61, "Wed"); break;
case 4: u8g.drawStr(6, 61, "Thu"); break;
case 5: u8g.drawStr(6, 61, "Fri"); break;
default: u8g.drawStr(6, 61, "Sat");
}

u8g.setPrintPos( 56, 61);//绘制温度示数到屏幕
u8g.print(tem);
u8g.drawStr(111, 61, "C");
}

可动态调整时间的Arduino代码

使用一个电位器和一个按钮进行调整时间,电位器选择模式或调整数字,按钮进行选定,具体请从代码中理解。

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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190

#include <U8glib.h>



#define BUZZER 10//喇叭针脚

#define YJ 7//额外的控制硬件

U8GLIB_ST7920_128X64_4X u8g(13, 12, 11);//配置屏幕针脚

int base = 0, ms = 0, s = 55 , mi = 59, hr = 5, week = 6;//进行初始化时间
int nMi = 0, nHr = 6;//闹钟时间
double tem = 0;//温度
char mode = 0;//模式

void setup() {
pinMode(BUZZER, OUTPUT);
pinMode(YJ, OUTPUT);
u8g.setRot180();//旋转屏幕180度
base = millis();//初始化基准时间
}

void loop() {
func();//处理函数
u8g.firstPage(); do draw(); while (u8g.nextPage());//刷新屏幕
}

void func() {
if (map(analogRead(A2), 0, 1010, 0, 1) == 1) {
switch (mode) {
case 0: calc(); break; //更新时间
case 1: hr = map(analogRead(A1), 0, 1020, 0, 23); break;//设置小时
case 2: mi = map(analogRead(A1), 0, 1020, 0, 59); break;//设置分钟
case 3: s = map(analogRead(A1), 0, 1020, 0, 59); break;//设置秒
case 4: week = map(analogRead(A1), 0, 1020, 0, 6); break;//设置星期
case 5: nHr = map(analogRead(A1), 0, 1020, 0, 23); break;//设置闹钟小时
case 6: nMi = map(analogRead(A1), 0, 1020, 0, 59); break;//设置闹钟分钟
}
} else {
mode = map(analogRead(A1), 0, 1010, 0, 6);//读取模式
calc();//更新时间
}

tem = (double)analogRead(A0) * (5 / 10.23);//读取并计算温度
if (hr == nHr && mi == nMi) {//播放闹钟1分钟
digitalWrite(YJ, LOW);
tone(BUZZER, 200 + ms);//随毫秒数改变闹钟音调,产生铃声
} else {
digitalWrite(YJ, HIGH);
noTone(BUZZER);//结束播放铃声
digitalWrite(BUZZER, LOW);//设置为低电平
}
}

void calc() {
ms = millis() - base;
if (ms > 999) {
ms = 0;
base = millis() - 61; //用于校正误差,每过1s提前61ms
if (++s > 59) {
s = 0;
base = millis() + 8; //用于校正误差,每过1min延迟8ms
if (++mi > 59) {
mi = 0;
base = millis() - 38; //用于校正误差,每过1hour提前38ms
if (++hr > 23) {
hr = 0;
base = millis() - 8; //用于校正误差,每过1day提前8ms
if (++week > 6) week = 0;
}
}
}
}
}

void draw() {
switch (mode) {
case 0: {
for (int x = 0; x < 2 * s; x++)//按照秒数绘制宽4个像素的进度条
for (int y = 0; y < 4; y++)
u8g.drawPixel(x + 5, y + 3);

u8g.setFont(u8g_font_gdb30n);//设置gdb30像素数字类字体

if (hr > 9) {//如果数字只有一位就添加0在前面
u8g.setPrintPos( 8, 41);
} else {
u8g.drawStr(10, 41, "0");
u8g.setPrintPos( 32, 41);
}
u8g.print(hr);

if (s % 2 == 0)//冒号在秒数为偶数时才绘制,达到冒号闪烁的效果
u8g.drawStr( 56, 37, ":");

if (mi > 9) {//如果数字只有一位就添加0在前面
u8g.setPrintPos( 70, 41);
} else {
u8g.drawStr(70, 41, "0");
u8g.setPrintPos( 94, 41);
}
u8g.print(mi);

u8g.setFont(u8g_font_gdb14r);//设置gdb14像素常用类字体
switch (week) {//绘制星期数到屏幕
case 0: u8g.drawStr(6, 61, "Sun"); break;
case 1: u8g.drawStr(6, 61, "Mon"); break;
case 2: u8g.drawStr(6, 61, "Tue"); break;
case 3: u8g.drawStr(6, 61, "Wed"); break;
case 4: u8g.drawStr(6, 61, "Thu"); break;
case 5: u8g.drawStr(6, 61, "Fri"); break;
default: u8g.drawStr(6, 61, "Sat");
}

u8g.setPrintPos( 56, 61);//绘制温度示数到屏幕
u8g.print(tem);
u8g.drawStr(111, 61, "C");
break;
}
case 1: {
u8g.setFont(u8g_font_gdb30n);//设置gdb30像素数字类字体
if (hr > 9) {//如果数字只有一位就添加0在前面
u8g.setPrintPos( 8, 41);
} else {
u8g.drawStr(10, 41, "0");
u8g.setPrintPos( 32, 41);
}
u8g.print(hr);
break;
}
case 2: {
u8g.setFont(u8g_font_gdb30n);//设置gdb30像素数字类字体
if (mi > 9) {//如果数字只有一位就添加0在前面
u8g.setPrintPos( 70, 41);
} else {
u8g.drawStr(70, 41, "0");
u8g.setPrintPos( 94, 41);
}
u8g.print(mi);
break;
}
case 3: {
for (int x = 0; x < 2 * s; x++)//按照秒数绘制宽4个像素的进度条
for (int y = 0; y < 4; y++)
u8g.drawPixel(x + 5, y + 3);

u8g.setFont(u8g_font_gdb30n);//设置gdb30像素数字类字体
if (s % 2 == 0)//冒号在秒数为偶数时才绘制,达到冒号闪烁的效果
u8g.drawStr( 56, 37, ":");
break;
}
case 4: {
u8g.setFont(u8g_font_gdb14r);//设置gdb14像素常用类字体
switch (week) {//绘制星期数到屏幕
case 0: u8g.drawStr(6, 61, "Sun"); break;
case 1: u8g.drawStr(6, 61, "Mon"); break;
case 2: u8g.drawStr(6, 61, "Tue"); break;
case 3: u8g.drawStr(6, 61, "Wed"); break;
case 4: u8g.drawStr(6, 61, "Thu"); break;
case 5: u8g.drawStr(6, 61, "Fri"); break;
default: u8g.drawStr(6, 61, "Sat");
}
break;
}
case 5: {
u8g.setFont(u8g_font_gdb30n);//设置gdb30像素数字类字体
if (nHr > 9) {//如果数字只有一位就添加0在前面
u8g.setPrintPos( 8, 41);
} else {
u8g.drawStr(10, 41, "0");
u8g.setPrintPos( 32, 41);
}
u8g.print(nHr);
break;
}
case 6: {
u8g.setFont(u8g_font_gdb30n);//设置gdb30像素数字类字体
if (nMi > 9) {//如果数字只有一位就添加0在前面
u8g.setPrintPos( 70, 41);
} else {
u8g.drawStr(70, 41, "0");
u8g.setPrintPos( 94, 41);
}
u8g.print(nMi);
break;
}
}
}

使用红外遥控器的代码

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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279

#include <IRremote.h>

#include <U8glib.h>


#define BUZZER 10//喇叭针脚


U8GLIB_ST7920_128X64_4X u8g(13, 12, 11);//配置屏幕针脚

int base = 0, ms = 0, s = 0, mi = 0, hr = 6, week = 0;//进行初始化时间
int nMi = 0, nHr = 8;//闹钟时间
double tem = 0;//温度
char mode = 0;//模式
char mode2 = 0;

int RECV_PIN = 9;
IRrecv irrecv(RECV_PIN);
decode_results results;

void setup() {
pinMode(BUZZER, OUTPUT);
u8g.setRot180();//旋转屏幕180度
irrecv.enableIRIn(); //启动红外解码
base = millis();//初始化基准时间
}

void loop() {
if (mode == 0) calc();
else {
if (hr > 23) hr = 0; else if (hr < 0) hr = 23;
if (mi > 59) mi = 0; else if (mi < 0) mi = 59;
if (s > 59) s = 0; else if (s < 0) s = 59;
if (week > 6) week = 0; else if (week < 0) week = 6;
if (nHr > 23) nHr = 0; else if (nHr < 0) nHr = 23;
if (nMi > 59) nMi = 0; else if (nMi < 0) nMi = 59;
}

func();//处理函数
u8g.firstPage(); do draw(); while (u8g.nextPage());//刷新屏幕

//是否接收到解码数据,把接收到的数据存储在变量results中
if (irrecv.decode(&results)) {
switch (results.value) {
case 150409965: {//遥控器右按键
mode2 = 1;
mode++;
if (mode > 6) mode = 0;
}; break;

case 150419655: {//遥控器左按键
mode2 = 2;
mode--;
if (mode < 0) mode = 6;
}; break;

case 150414045: {//遥控器上按键
mode2 = 3;
switch (mode) {
case 0: break; //彩蛋
case 1: hr++; break;//设置小时
case 2: mi++; break;//设置分钟
case 3: s++; break;//设置秒
case 4: week++; break;//设置星期
case 5: nHr++; break;//设置闹钟小时
case 6: nMi++; break;//设置闹钟分钟
}
}; break;

case 150452295: {//遥控器下按键
mode2 = 4;
switch (mode) {
case 0: break; //彩蛋
case 1: hr--; break;//设置小时
case 2: mi--; break;//设置分钟
case 3: s--; break;//设置秒
case 4: week--; break;//设置星期
case 5: nHr--; break;//设置闹钟小时
case 6: nMi--; break;//设置闹钟分钟
}
}; break;

case 150420165: {//遥控器ok按键
mode2 = 5;
mode = 0;
}; break;

case 4294967295: {//遥控器长按键
switch (mode) {
case 0: break; //彩蛋
case 1: if (mode2 == 3) {
hr++;
} else if (mode2 == 4) {
hr--;
}; break; //设置小时
case 2: if (mode2 == 3) {
mi++;
} else if (mode2 == 4) {
mi--;
}; break;//设置分钟
case 3: if (mode2 == 3) {
s++;
} else if (mode2 == 4) {
s--;
}; break;//设置秒
case 4: if (mode2 == 3) {
week++;
} else if (mode2 == 4) {
week;
}; break;//设置星期
case 5: if (mode2 == 3) {
nHr++;
} else if (mode2 == 4) {
nHr--;
}; break;//设置闹钟小时
case 6: if (mode2 == 3) {
nMi++;
} else if (mode2 == 4) {
nMi--;
}; break;//设置闹钟分钟
}
}; break;

}
irrecv.resume(); // 继续等待接收下一组信号
}

}

void func() {
tem = (double)analogRead(A0) * (5 / 10.23);//读取并计算温度
if (hr == nHr && mi == nMi) {//播放闹钟1分钟
tone(BUZZER, 200 + ms);//随毫秒数改变闹钟音调,产生铃声
} else {
noTone(BUZZER);//结束播放铃声
digitalWrite(BUZZER, LOW);//设置为低电平
}
}

void calc() {
ms = millis() - base;
if (ms > 999) {
ms = 0;
base = millis() - 61; //用于校正误差,每过1s提前61ms
if (++s > 59) {
s = 0;
base = millis() + 8; //用于校正误差,每过1min延迟8ms
if (++mi > 59) {
mi = 0;
base = millis() - 38; //用于校正误差,每过1hour提前38ms
if (++hr > 23) {
hr = 0;
base = millis() - 8; //用于校正误差,每过1day提前8ms
if (++week > 6) week = 0;
}
}
}
}
}

void draw() {
if (mode == 0 && mode2 == 3) {
u8g.drawStr(50, 24, "By");
u8g.drawStr(16, 48, "HK-SHAO");
return;
}
switch (mode) {
case 0: {
for (int x = 0; x < 2 * s; x++)//按照秒数绘制宽4个像素的进度条
for (int y = 0; y < 4; y++)
u8g.drawPixel(x + 5, y + 3);

u8g.setFont(u8g_font_gdb30n);//设置gdb30像素数字类字体

if (hr > 9) {//如果数字只有一位就添加0在前面
u8g.setPrintPos( 8, 41);
} else {
u8g.drawStr(10, 41, "0");
u8g.setPrintPos( 32, 41);
}
u8g.print(hr);

if (s % 2 == 0)//冒号在秒数为偶数时才绘制,达到冒号闪烁的效果
u8g.drawStr( 56, 37, ":");

if (mi > 9) {//如果数字只有一位就添加0在前面
u8g.setPrintPos( 70, 41);
} else {
u8g.drawStr(70, 41, "0");
u8g.setPrintPos( 94, 41);
}
u8g.print(mi);

u8g.setFont(u8g_font_gdb14r);//设置gdb14像素常用类字体
switch (week) {//绘制星期数到屏幕
case 0: u8g.drawStr(6, 61, "Sun"); break;
case 1: u8g.drawStr(6, 61, "Mon"); break;
case 2: u8g.drawStr(6, 61, "Tue"); break;
case 3: u8g.drawStr(6, 61, "Wed"); break;
case 4: u8g.drawStr(6, 61, "Thu"); break;
case 5: u8g.drawStr(6, 61, "Fri"); break;
default: u8g.drawStr(6, 61, "Sat");
}

u8g.setPrintPos( 56, 61);//绘制温度示数到屏幕
u8g.print(tem);
u8g.drawStr(111, 61, "C");
break;
}
case 1: {
u8g.setFont(u8g_font_gdb30n);//设置gdb30像素数字类字体
if (hr > 9) {//如果数字只有一位就添加0在前面
u8g.setPrintPos( 8, 41);
} else {
u8g.drawStr(10, 41, "0");
u8g.setPrintPos( 32, 41);
}
u8g.print(hr);
break;
}
case 2: {
u8g.setFont(u8g_font_gdb30n);//设置gdb30像素数字类字体
if (mi > 9) {//如果数字只有一位就添加0在前面
u8g.setPrintPos( 70, 41);
} else {
u8g.drawStr(70, 41, "0");
u8g.setPrintPos( 94, 41);
}
u8g.print(mi);
break;
}
case 3: {
for (int x = 0; x < 2 * s; x++)//按照秒数绘制宽4个像素的进度条
for (int y = 0; y < 4; y++)
u8g.drawPixel(x + 5, y + 3);

u8g.setFont(u8g_font_gdb30n);//设置gdb30像素数字类字体
if (s % 2 == 0)//冒号在秒数为偶数时才绘制,达到冒号闪烁的效果
u8g.drawStr( 56, 37, ":");
break;
}
case 4: {
u8g.setFont(u8g_font_gdb14r);//设置gdb14像素常用类字体
switch (week) {//绘制星期数到屏幕
case 0: u8g.drawStr(6, 61, "Sun"); break;
case 1: u8g.drawStr(6, 61, "Mon"); break;
case 2: u8g.drawStr(6, 61, "Tue"); break;
case 3: u8g.drawStr(6, 61, "Wed"); break;
case 4: u8g.drawStr(6, 61, "Thu"); break;
case 5: u8g.drawStr(6, 61, "Fri"); break;
default: u8g.drawStr(6, 61, "Sat");
}
break;
}
case 5: {
u8g.setFont(u8g_font_gdb30n);//设置gdb30像素数字类字体
if (nHr > 9) {//如果数字只有一位就添加0在前面
u8g.setPrintPos( 8, 41);
} else {
u8g.drawStr(10, 41, "0");
u8g.setPrintPos( 32, 41);
}
u8g.print(nHr);
break;
}
case 6: {
u8g.setFont(u8g_font_gdb30n);//设置gdb30像素数字类字体
if (nMi > 9) {//如果数字只有一位就添加0在前面
u8g.setPrintPos( 70, 41);
} else {
u8g.drawStr(70, 41, "0");
u8g.setPrintPos( 94, 41);
}
u8g.print(nMi);
break;
}
}
}

注意

不同的机器和环境可能有不同的误差,建议自己进行校正后进行使用。

数字闹钟及温度计(不需时钟模块)

https://hk-shao.github.io/p/1460.html

作者

烧风

发布于

2018-02-03

更新于

2021-07-11

许可协议

评论