|
@@ -14,7 +14,37 @@ import interstate75
|
14
|
14
|
import hub75
|
15
|
15
|
from mapper import MapperReduceBrightness
|
16
|
16
|
import time
|
17
|
|
-from machine import Pin
|
|
17
|
+from machine import Pin, ADC
|
|
18
|
+import math
|
|
19
|
+
|
|
20
|
+# https://github.com/pimoroni/pimoroni-pico/blob/main/micropython/examples/interstate75/75W/clock.py
|
|
21
|
+@micropython.native # noqa: F821
|
|
22
|
+def from_hsv(h, s, v):
|
|
23
|
+ i = math.floor(h * 6.0)
|
|
24
|
+ f = h * 6.0 - i
|
|
25
|
+ v *= 255.0
|
|
26
|
+ p = v * (1.0 - s)
|
|
27
|
+ q = v * (1.0 - f * s)
|
|
28
|
+ t = v * (1.0 - (1.0 - f) * s)
|
|
29
|
+
|
|
30
|
+ i = int(i) % 6
|
|
31
|
+ if i == 0:
|
|
32
|
+ return int(v), int(t), int(p)
|
|
33
|
+ if i == 1:
|
|
34
|
+ return int(q), int(v), int(p)
|
|
35
|
+ if i == 2:
|
|
36
|
+ return int(p), int(v), int(t)
|
|
37
|
+ if i == 3:
|
|
38
|
+ return int(p), int(q), int(v)
|
|
39
|
+ if i == 4:
|
|
40
|
+ return int(t), int(p), int(v)
|
|
41
|
+ if i == 5:
|
|
42
|
+ return int(v), int(p), int(q)
|
|
43
|
+
|
|
44
|
+def batt_to_color(batt):
|
|
45
|
+ h = batt[0] / 100.0 * 0.3333
|
|
46
|
+ r, g, b = from_hsv(h, 1.0, 1.0)
|
|
47
|
+ return r, g, b
|
18
|
48
|
|
19
|
49
|
class PicoMatrix:
|
20
|
50
|
def __init__(self, w = 32, h = 32):
|
|
@@ -38,9 +68,74 @@ class PicoMatrix:
|
38
|
68
|
|
39
|
69
|
self.ledTime = time.time()
|
40
|
70
|
self.led = Pin("LED", Pin.OUT)
|
|
71
|
+ self.ledRefresh = 0.5
|
|
72
|
+
|
|
73
|
+ self.adc = ADC(26)
|
|
74
|
+ self.battState = None
|
|
75
|
+ self.battTime = time.time()
|
|
76
|
+ self.battRefresh = 10
|
41
|
77
|
|
42
|
78
|
self.loop_start() # initialize with blank image for ScrollText constructor
|
43
|
79
|
|
|
80
|
+ def battery(self):
|
|
81
|
+ n = const(10)
|
|
82
|
+ bits = const(10)
|
|
83
|
+ raw = 0
|
|
84
|
+ for i in range(0, n):
|
|
85
|
+ raw += self.adc.read_u16() >> (16 - bits)
|
|
86
|
+ time.sleep(0.1 / n)
|
|
87
|
+ raw /= n
|
|
88
|
+
|
|
89
|
+ v_adc_ref = const(3.3) # V
|
|
90
|
+ v_adc = (raw / ((1 << bits) - 1)) * v_adc_ref
|
|
91
|
+
|
|
92
|
+ r1 = const(27.0) # kOhm
|
|
93
|
+ r2 = const(5.6) # kOhm
|
|
94
|
+ v_bat_uncal = (v_adc * (r1 + r2)) / r2
|
|
95
|
+
|
|
96
|
+ # Calibration
|
|
97
|
+ # TODO supports only 2 points
|
|
98
|
+ cal_pts = [
|
|
99
|
+ # adc, bat
|
|
100
|
+ (13.65, 15.46),
|
|
101
|
+ (13.60, 15.34),
|
|
102
|
+ # TODO better values
|
|
103
|
+ ]
|
|
104
|
+
|
|
105
|
+ # https://www.ti.com/europe/downloads/f2810_12_calibration_10.pdf
|
|
106
|
+ gain = (cal_pts[0][1] - cal_pts[1][1]) / (cal_pts[0][0] - cal_pts[1][0])
|
|
107
|
+ offset = cal_pts[1][1] - cal_pts[1][0] * gain
|
|
108
|
+ v_bat = v_bat_uncal * gain + offset
|
|
109
|
+
|
|
110
|
+ # TODO auto-detect cell count
|
|
111
|
+ n_lipo = const(4) # 4S
|
|
112
|
+ v_cell = v_bat / n_lipo
|
|
113
|
+
|
|
114
|
+ v_cell_min = const(3.25)
|
|
115
|
+ v_cell_max = const(4.2)
|
|
116
|
+ p_cell = (v_cell - v_cell_min) / (v_cell_max - v_cell_min) * 100.0
|
|
117
|
+ p_cell = max(min(p_cell, 100.0), 0.0)
|
|
118
|
+
|
|
119
|
+ ret = (p_cell, v_cell, v_bat)
|
|
120
|
+
|
|
121
|
+ r, g, b = batt_to_color(ret)
|
|
122
|
+ self.matrix.set_led(r, g, b)
|
|
123
|
+
|
|
124
|
+ return ret
|
|
125
|
+
|
|
126
|
+ def batteryCache(self, refresh = False):
|
|
127
|
+ now = time.time()
|
|
128
|
+ if (self.battState == None) or ((now - self.battTime) >= self.battRefresh) or (now < self.battTime) or refresh:
|
|
129
|
+ self.battTime = now
|
|
130
|
+ self.battState = self.battery()
|
|
131
|
+ return self.battState
|
|
132
|
+
|
|
133
|
+ def heartbeat(self):
|
|
134
|
+ now = time.time()
|
|
135
|
+ if ((now - self.ledTime) >= self.ledRefresh) or (now < self.ledTime):
|
|
136
|
+ self.ledTime = now
|
|
137
|
+ self.led.toggle()
|
|
138
|
+
|
44
|
139
|
def loop_start(self):
|
45
|
140
|
self.matrix.display.set_pen(self.black)
|
46
|
141
|
self.matrix.display.clear()
|
|
@@ -51,6 +146,12 @@ class PicoMatrix:
|
51
|
146
|
def loop_end(self):
|
52
|
147
|
self.matrix.update()
|
53
|
148
|
|
|
149
|
+ # LED heartbeat blink
|
|
150
|
+ self.heartbeat()
|
|
151
|
+
|
|
152
|
+ # update battery if necessary
|
|
153
|
+ self.batteryCache(False)
|
|
154
|
+
|
54
|
155
|
def loop(self, func = None):
|
55
|
156
|
while True:
|
56
|
157
|
if self.loop_start():
|
|
@@ -61,11 +162,6 @@ class PicoMatrix:
|
61
|
162
|
|
62
|
163
|
self.loop_end()
|
63
|
164
|
|
64
|
|
- now = time.time()
|
65
|
|
- if ((now - self.ledTime) >= 0.5) or (now < self.ledTime):
|
66
|
|
- self.ledTime = now
|
67
|
|
- self.led.toggle()
|
68
|
|
-
|
69
|
165
|
self.matrix.stop()
|
70
|
166
|
|
71
|
167
|
def set_pixel(self, x, y, color):
|
|
@@ -86,6 +182,7 @@ class PicoText:
|
86
|
182
|
|
87
|
183
|
def text(self, s, f, offset = 0, earlyAbort = True, yOff = 0, compat = True):
|
88
|
184
|
if not earlyAbort:
|
|
185
|
+ self.gui.matrix.display.set_font(f)
|
89
|
186
|
return self.gui.matrix.display.measure_text(s, scale=1)
|
90
|
187
|
|
91
|
188
|
color = self.fg
|
|
@@ -116,23 +213,90 @@ class PicoText:
|
116
|
213
|
|
117
|
214
|
self.gui.matrix.display.text(s, x, y, scale=1)
|
118
|
215
|
|
|
216
|
+class PicoBatt:
|
|
217
|
+ def __init__(self, g, ti = 5.0, tt = 5.0):
|
|
218
|
+ self.gui = g
|
|
219
|
+ self.timeImage = ti
|
|
220
|
+ self.timeText = tt
|
|
221
|
+ self.text = PicoText(self.gui)
|
|
222
|
+ self.restart()
|
|
223
|
+
|
|
224
|
+ def restart(self):
|
|
225
|
+ self.start = time.time()
|
|
226
|
+
|
|
227
|
+ def finished(self):
|
|
228
|
+ now = time.time()
|
|
229
|
+ return ((now - self.start) >= (self.timeText + self.timeImage)) or (now < self.start)
|
|
230
|
+
|
|
231
|
+ def drawText(self, refresh = False):
|
|
232
|
+ batt = self.gui.batteryCache(refresh)
|
|
233
|
+ c = batt_to_color(batt)
|
|
234
|
+
|
|
235
|
+ self.text.fg = (255, 255, 255)
|
|
236
|
+ self.text.text( "Batt:", "bitmap8", 0, True, 8 * 0, False)
|
|
237
|
+
|
|
238
|
+ self.text.fg = c
|
|
239
|
+ self.text.text("{:.2f}%".format(batt[0]), "bitmap8", 0, True, 8 * 1, False)
|
|
240
|
+ self.text.text("{:.2f}V".format(batt[1]), "bitmap8", 0, True, 8 * 2, False)
|
|
241
|
+ self.text.text("{:.2f}V".format(batt[2]), "bitmap8", 0, True, 8 * 3, False)
|
|
242
|
+
|
|
243
|
+ def drawImage(self, refresh = False):
|
|
244
|
+ x_off = const(1)
|
|
245
|
+ y_off = const(16)
|
|
246
|
+ nub_w = const(3)
|
|
247
|
+ nub_h = const(6)
|
|
248
|
+
|
|
249
|
+ w = self.gui.width - x_off * 2
|
|
250
|
+ h = self.gui.height - y_off
|
|
251
|
+
|
|
252
|
+ batt = self.gui.batteryCache(refresh)
|
|
253
|
+ c = batt_to_color(batt)
|
|
254
|
+ fill_w = int(batt[0] / 100.0 * (w - nub_w))
|
|
255
|
+
|
|
256
|
+ s = "{:.0f}%".format(batt[0])
|
|
257
|
+ s_w = self.text.text(s, "bitmap14_outline", 0, False)
|
|
258
|
+ self.text.fg = (255, 255, 255)
|
|
259
|
+ self.text.text(s, "bitmap14_outline", int((self.gui.width - s_w) / 2), True, 1, False)
|
|
260
|
+
|
|
261
|
+ for x in range(0, w - nub_w):
|
|
262
|
+ for y in range(0, h):
|
|
263
|
+ if (x == 0) or (x == (w - nub_w - 1)) or (y == 0) or (y == (h - 1)) or (x < fill_w):
|
|
264
|
+ self.gui.set_pixel(x + x_off, y + y_off, c)
|
|
265
|
+
|
|
266
|
+ for x in range(0, nub_w):
|
|
267
|
+ for y in range(0, nub_h):
|
|
268
|
+ self.gui.set_pixel(x + x_off + w - nub_w, y + y_off + int((h - nub_h) / 2), c)
|
|
269
|
+
|
|
270
|
+ def draw(self, refresh = False):
|
|
271
|
+ now = time.time()
|
|
272
|
+ if (now - self.start) < self.timeImage:
|
|
273
|
+ self.drawImage(refresh)
|
|
274
|
+ else:
|
|
275
|
+ self.drawText(refresh)
|
|
276
|
+
|
119
|
277
|
if __name__ == "__main__":
|
120
|
278
|
import time
|
121
|
279
|
|
122
|
280
|
t = PicoMatrix(32, 32)
|
123
|
281
|
s = PicoText(t)
|
|
282
|
+ b = PicoBatt(t, 10.0, 10.0)
|
124
|
283
|
|
125
|
284
|
start = time.time()
|
126
|
285
|
i = 0
|
127
|
286
|
def helper():
|
128
|
|
- global s, start, i
|
|
287
|
+ global s, start, i, b
|
129
|
288
|
|
130
|
289
|
now = time.time()
|
131
|
|
- if ((now - start) > 2.0) or (now < start):
|
|
290
|
+ if ((now - start) > 5.0) or (now < start):
|
132
|
291
|
start = now
|
133
|
|
- i = (i + 1) % 2
|
134
|
|
-
|
135
|
|
- if i == 0:
|
|
292
|
+ i = (i + 1) % 6
|
|
293
|
+ if i == 0:
|
|
294
|
+ b.restart()
|
|
295
|
+
|
|
296
|
+ if i < 4:
|
|
297
|
+ b.draw(True)
|
|
298
|
+ time.sleep(1.0)
|
|
299
|
+ elif i == 4:
|
136
|
300
|
s.text("Abgj6", "bitmap6", 0, True, 0, False)
|
137
|
301
|
s.text("Abdgj8", "bitmap8", 0, True, 6 + 2, False)
|
138
|
302
|
s.text("Ag14", "bitmap14_outline", 0, True, 6 + 2 + 8 + 1, False)
|