Browse Source

pico show battery state

Thomas Buck 1 year ago
parent
commit
272c7e101b
3 changed files with 181 additions and 11 deletions
  1. 3
    0
      camp_pico.py
  2. 3
    0
      mapper.py
  3. 175
    11
      pico.py

+ 3
- 0
camp_pico.py View File

@@ -22,6 +22,7 @@ if True:#__name__ == "__main__":
22 22
     from scroll import ScrollText
23 23
     from splash import SplashScreen
24 24
     from manager import Manager
25
+    from pico import PicoBatt
25 26
 
26 27
     url = "http://ubabot.frubar.net"
27 28
 
@@ -64,6 +65,8 @@ if True:#__name__ == "__main__":
64 65
     m.add(Solid(t, 1.0))
65 66
     m.add(d) # HTTP Check, either "success" or "fail"
66 67
     m.add(Solid(t, 1.0))
68
+    m.add(PicoBatt(t, 5.0, 5.0))
69
+    m.add(Solid(t, 1.0))
67 70
 
68 71
     m.restart()
69 72
     t.loop(m.draw)

+ 3
- 0
mapper.py View File

@@ -31,6 +31,9 @@ class MapperNull:
31 31
         if hasattr(self.gui, "matrix"):
32 32
             self.matrix = self.gui.matrix
33 33
 
34
+    def batteryCache(self, refresh = False):
35
+        return self.gui.batteryCache(refresh)
36
+
34 37
     def loop_start(self):
35 38
         return self.gui.loop_start()
36 39
 

+ 175
- 11
pico.py View File

@@ -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)

Loading…
Cancel
Save