bbm2Chaosknoten
Stefan Schuermans

Stefan Schuermans commited on 2019-06-21 21:47:43
Showing 7 changed files, with 269 additions and 2 deletions.


convert *.bbm movie to firmware data
... ...
@@ -2,3 +2,5 @@
2 2
 /*.hex
3 3
 /*.lst
4 4
 /*.obj
5
+/movie_funcs.inc
6
+/movie_tab.inc
... ...
@@ -580,6 +580,43 @@ ANIM_BW_WORM_LOOP:
580 580
 
581 581
 
582 582
 
583
+; play animation
584
+; input: Z = pointer to movie data
585
+; output: -
586
+; changes: X, Z, FRAME, CNT, DATA, TMP, TMP2
587
+ANIM_MOVIE:
588
+; get duration in 6ms steps, zero means end of movie
589
+        lpm     TMP,Z+
590
+        cpi     TMP,0
591
+        breq    ANIM_MOVIE_END
592
+; extract frame to frame buffer
593
+        ldi     XL,low(FRAME)           ; ptr to pixel data
594
+                                        ;   XH not there on ATtiny2313
595
+ANIM_MOVIE_FRAME_LOOP:
596
+        lpm     DATA,Z+                 ; get two pixels
597
+        mov     TMP2,DATA               ; write first pixel
598
+        swap    TMP2
599
+        andi    TMP2,0x0F
600
+        st      X+,TMP2
601
+        andi    DATA,0x0F               ; write second pixel
602
+        st      X+,DATA
603
+        cpi     XL,low(FRAME)+42        ; bottom of loop
604
+                                        ;   XH not there on ATtiny2313
605
+        brlo    ANIM_MOVIE_FRAME_LOOP
606
+; show frame
607
+        rcall   OUT_FRAME_TIME          ; frame time is already in TMP
608
+; next frame
609
+        rjmp    ANIM_MOVIE
610
+; end of movie
611
+ANIM_MOVIE_END:
612
+        ret
613
+
614
+
615
+
616
+.INCLUDE        "movie_funcs.inc"
617
+
618
+
619
+
583 620
 ; read mode from switch and (store animation number)
584 621
 ; input: MODE = old mode, CNT = animation number
585 622
 ; output: MODE = new mode
... ...
@@ -618,6 +655,7 @@ MODE_READ_NOT_0_TO_1:
618 655
 
619 656
 ; animation table: animation function, iteration count (<= 255)
620 657
 ANIM_TAB:
658
+.INCLUDE        "movie_tab.inc"
621 659
         .dw     ANIM_BLINK
622 660
         .dw     3
623 661
         .dw     ANIM_WORM
... ...
@@ -19,6 +19,14 @@ AVRDUDE := avrdude
19 19
 
20 20
 AVRDUDE_CALL := $(AVRDUDE) -c $(PROGRAMMER) -P $(CONNECTION) -p $(DEVICE)
21 21
 
22
+MOVIE_FUNC_TMPL := movie_func.tmpl
23
+
24
+MOVIES := $(wildcard movies/*.bbm)
25
+MOVIES_INC := $(MOVIES:.bbm=.inc)
26
+MOVIE_FUNCS_INC := movie_funcs.inc
27
+MOVIE_TAB_INC := movie_tab.inc
28
+MOVIES_ALL_INC := $(MOVIES_INC) $(MOVIE_FUNCS_INC) $(MOVIE_TAB_INC)
29
+
22 30
 SUFFIXES :=
23 31
 
24 32
 .PHONY: all prog prog_fuses prog_auto clean
... ...
@@ -27,7 +35,21 @@ SUFFIXES :=
27 35
 
28 36
 all: $(NAME).hex
29 37
 
30
-$(NAME).hex: $(NAME).asm $(INC).inc Makefile
38
+movies/%.inc: movies/%.bbm
39
+	./bbm2Chaosknoten.py -i $< -f $@
40
+
41
+movie_funcs.inc: $(MOVIE_FUNC_TMPL) $(MOVIES_INC) Makefile
42
+	for N in $(notdir $(MOVIES_INC:.inc=)); do \
43
+	  sed "s/NAME/$$N/g" $(MOVIE_FUNC_TMPL); \
44
+	done >$@
45
+
46
+movie_tab.inc: $(MOVIES_INC) Makefile
47
+	for N in $(notdir $(MOVIES_INC:.inc=)); do \
48
+	  echo "        .dw     ANIM_MOVIE_$$N"; \
49
+	  echo "        .dw     $${N##*x}"; \
50
+	done >$@
51
+
52
+$(NAME).hex: $(NAME).asm $(INC).inc $(MOVIES_ALL_INC) Makefile
31 53
 	$(AVRA) -l $(NAME).lst $(NAME).asm
32 54
 
33 55
 prog_fuses: Makefile
... ...
@@ -44,5 +66,6 @@ prog_auto: $(NAME).hex Makefile
44 66
 	while ! $(MAKE) prog_fuses || ! $(MAKE) prog; do echo -n; done
45 67
 
46 68
 clean:
47
-	rm -f $(addprefix $(NAME)., lst obj cof hex eep.hex)
69
+	rm -f $(addprefix $(NAME)., lst obj cof hex eep.hex) \
70
+	      $(MOVIES_ALL_INC)
48 71
 
... ...
@@ -0,0 +1,196 @@
1
+#! /usr/bin/env python3
2
+
3
+import argparse
4
+import struct
5
+import sys
6
+
7
+
8
+class Movie(object):
9
+    class Frame(object):
10
+
11
+        LED_COORDS = [
12
+            408, 414, 420, 468, 492, 677, 684, 863, 945, 951, 957, 963, 971,
13
+            975, 1034, 1209, 1215, 1221, 1227, 1259, 1299, 1304, 1373, 1469,
14
+            1476, 1497, 1572, 1633, 1656, 1680, 1728, 1734, 1740, 1832, 1968,
15
+            2106, 2163, 2225, 2365, 2559, 2620, 2956
16
+        ]
17
+
18
+        LED_COORDS_OUT = [
19
+            [408, 407, 409], [414, 413, 415], [420, 419, 421], [468, 533, 403],
20
+            [492, 425, 559], [677, 610, 744], [684, 617, 751], [863, 797, 929],
21
+            [945, 944, 946], [951, 950, 952], [957, 956, 958], [963, 962, 964],
22
+            [971, 904, 1038], [975, 908, 1042], [1034, 967, 1101],
23
+            [1209, 1208, 1210], [1215, 1214, 1216], [1221, 1220, 1222],
24
+            [1227, 1226, 1228], [1259, 1193, 1325], [1299, 1298, 1300],
25
+            [1304, 1369, 1239], [1373, 1438, 1308], [1469, 1534, 1404],
26
+            [1476, 1541, 1411], [1497, 1496, 1498], [1572, 1637, 1507],
27
+            [1633, 1698, 1568], [1656, 1589, 1723], [1680, 1745, 1615],
28
+            [1728, 1727, 1729], [1734, 1733, 1735], [1740, 1739, 1741],
29
+            [1832, 1897, 1767], [1968, 1901, 2035], [2106, 2105, 2107],
30
+            [2163, 2097, 2229], [2225, 2290, 2160], [2365, 2299, 2431],
31
+            [2559, 2493, 2625], [2620, 2619, 2621], [2956, 2889, 3023]
32
+        ]
33
+
34
+        def __init__(self):
35
+            self.duration = 100
36
+            self.leds = len(self.LED_COORDS) * [0]
37
+
38
+        def from_frame_data(self, duration, data):
39
+            self.duration = duration
40
+            for i in range(len(self.LED_COORDS)):
41
+                ledno = self.LED_COORDS[i]
42
+                self.leds[i] = data[ledno * 3 + 1]
43
+
44
+        def to_frame_data(self):
45
+            pixels = 51 * 66 * [0, 0, 255]
46
+            for i in range(len(self.LED_COORDS_OUT)):
47
+                for ledno in self.LED_COORDS_OUT[i]:
48
+                    pixels[ledno * 3 + 0] = self.leds[i]
49
+                    pixels[ledno * 3 + 1] = self.leds[i]
50
+                    pixels[ledno * 3 + 2] = 0
51
+            data = bytes(pixels)
52
+            return self.duration, data
53
+
54
+        def to_firmware_data(self):
55
+            duration = (self.duration + 5) // 10
56
+            if duration < 1:
57
+                duration = 1
58
+            if duration > 255:
59
+                duration = 255
60
+            fw_data = [duration]
61
+            half = None
62
+            for led in self.leds:
63
+                val = (led >> 4) & 0x0F
64
+                if half is None:
65
+                    half = val
66
+                else:
67
+                    fw_data.append(half << 4 | val)
68
+                    half = None
69
+            return fw_data
70
+
71
+    def __init__(self):
72
+        self.frames = []
73
+        self.main_hdr = struct.Struct("!LHHHH")
74
+        self.main_info = struct.Struct("!LLL")
75
+        self.subhdr_magic = struct.Struct("!L")
76
+        self.subhdr_size = struct.Struct("!H")
77
+        self.frame_hdr = struct.Struct("!H")
78
+
79
+    def read_bbm(self, filename):
80
+        try:
81
+            with open(filename, "rb") as f:
82
+                # main header
83
+                magic, height, width, channels, maxval = self.main_hdr.unpack(
84
+                    f.read(12))
85
+                if magic != 0x23542666:
86
+                    raise ValueError(
87
+                        "invalid magic 0x%X != 0x23542666".format(magic))
88
+                if height != 51 or width != 66 or channels != 3 or maxval != 255:
89
+                    raise ValueError(
90
+                        "invalid format {:d}x{:d}-{:d}/{:d} != 66x51-3/256".
91
+                        format(width, height, channels, maxval - 1))
92
+                # main information
93
+                framecnt, duration, frameptr = self.main_info.unpack(
94
+                    f.read(12))
95
+                # skip additional headers until frame start marker
96
+                while True:
97
+                    subhdr_magic, = self.subhdr_magic.unpack(f.read(4))
98
+                    if subhdr_magic == 0x66726d73:
99
+                        break
100
+                    subhdr_size, = self.subhdr_size.unpack(f.read(2))
101
+                    if subhdr_size < 6:
102
+                        raise ValueError("truncated sub-header")
103
+                    f.read(subhdr_size - 6)
104
+                # read frames
105
+                frames = []
106
+                for frameno in range(framecnt):
107
+                    duration, = self.frame_hdr.unpack(f.read(2))
108
+                    n = height * width * channels
109
+                    framedata = f.read(n)
110
+                    if len(framedata) != n:
111
+                        raise ValueError("truncated frame")
112
+                    frame = self.Frame()
113
+                    frame.from_frame_data(duration, framedata)
114
+                    frames.append(frame)
115
+            self.frames = frames
116
+            return True
117
+        except Exception as e:
118
+            print(str(e), file=sys.stderr)
119
+            return False
120
+
121
+    def write_bbm(self, filename):
122
+        with open(filename, "wb") as f:
123
+            # main header
124
+            f.write(self.main_hdr.pack(0x23542666, 51, 66, 3, 255))
125
+            # main information
126
+            duration = 0
127
+            for frame in self.frames:
128
+                duration += frame.duration
129
+            f.write(self.main_info.pack(len(self.frames), duration, 24))
130
+            # frame start marker
131
+            f.write(self.subhdr_magic.pack(0x66726d73))
132
+            # write frames
133
+            for frame in self.frames:
134
+                duration, data = frame.to_frame_data()
135
+                f.write(self.frame_hdr.pack(duration))
136
+                f.write(data)
137
+
138
+    def write_firmware(self, filename):
139
+        with open(filename, "w") as f:
140
+            for frame in self.frames:
141
+                fw_data = frame.to_firmware_data()
142
+                for i in range(0, len(fw_data), 8):
143
+                    vals = ["0x{:02X}".format(v) for v in fw_data[i:i + 8]]
144
+                    print("        .db     {:s}".format(",".join(vals)), file=f)
145
+            print("        .db     0,0", file=f)
146
+
147
+
148
+def parse_arguments():
149
+    """parse command line arguments"""
150
+    parser = argparse.ArgumentParser(
151
+        description="convert *.bbm to Chaosknoten")
152
+    parser.add_argument(
153
+        "-i",
154
+        "--input",
155
+        metavar="BBM_FILE",
156
+        type=str,
157
+        required=True,
158
+        dest="input",
159
+        help="input binary blinken movie (*.bbm, required)")
160
+    parser.add_argument(
161
+        "-o",
162
+        "--output",
163
+        metavar="BBM_FILE",
164
+        type=str,
165
+        dest="output",
166
+        help="output binary blinken movie (*.bbm, optional)")
167
+    parser.add_argument(
168
+        "-f",
169
+        "--firmware",
170
+        metavar="INC_FILE",
171
+        type=str,
172
+        dest="firmware",
173
+        help="output firmware data (*.inc, optional)")
174
+    try:
175
+        args = parser.parse_args()
176
+    except:
177
+        return None
178
+    return args
179
+
180
+
181
+def main():
182
+    args = parse_arguments()
183
+    if args is None:
184
+        return 2
185
+    movie = Movie()
186
+    if not movie.read_bbm(args.input):
187
+        return 3
188
+    if args.output is not None:
189
+        movie.write_bbm(args.output)
190
+    if args.firmware is not None:
191
+        movie.write_firmware(args.firmware)
192
+    return 0
193
+
194
+
195
+if __name__ == "__main__":
196
+    sys.exit(main())
... ...
@@ -0,0 +1,7 @@
1
+ANIM_MOVIE_NAME_DATA:
2
+.INCLUDE        "movies/NAME.inc"
3
+ANIM_MOVIE_NAME:
4
+        ldi     ZL,low(2 * ANIM_MOVIE_NAME_DATA)
5
+        ldi     ZH,high(2 * ANIM_MOVIE_NAME_DATA)
6
+        rjmp    ANIM_MOVIE
7
+