Stefan Schuermans commited on 2019-06-22 21:35:43
Showing 3 changed files, with 236 additions and 27 deletions.
add movie compression: run length encoding (if shorter than plain frame) and refer to previous frames if frames repeat, add breathe movie
... | ... |
@@ -579,36 +579,173 @@ ANIM_BW_WORM_LOOP: |
579 | 579 |
ret |
580 | 580 |
|
581 | 581 |
|
582 |
- |
|
583 |
-; play animation |
|
582 |
+; movie data format |
|
583 |
+; movie = frame ... frame end |
|
584 |
+; frame = 0x0_ ... -> plain frame |
|
585 |
+; 0x1_ ... -> rle (run length encoded) frame |
|
586 |
+; 0x2_ ... -> back reference |
|
587 |
+; plain frame = duration_high (0x00..0x0F), duration_low (0x00..0xFF), |
|
588 |
+; 21 * ( pixel (0x00..0x0F)) << 4 | pixel (0x00..0x0F) ) |
|
589 |
+; rle frame = 0x10 | duration_high (0x00..0x0F), duration_low (0x00..0xFF), |
|
590 |
+; rle entry, ..., rle_entry (until 42 pixels are encoded) |
|
591 |
+; rle_entry = repeat (0x00..0x0F) << 4 | value (0x00..0x0F) |
|
592 |
+; meaning: repeat + 1 pixels of value |
|
593 |
+; back reference = 0x20 | back_high (0x00..0x0F), back_low (0x00..0xFF) |
|
594 |
+; meaning: read frame from earlier position, |
|
595 |
+; frame data start = pos after back ref - back |
|
596 |
+; end = 0xF_ |
|
597 |
+ |
|
598 |
+ |
|
599 |
+ |
|
600 |
+; play animation - process code and following section |
|
584 | 601 |
; 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 |
|
602 |
+; TMP2 = 0 -> initial call, 1 -> nested call |
|
603 |
+; output: Z = pointer to behind processed movie data (only for initial call) |
|
604 |
+; TMP = 0 -> go on, 1 -> end |
|
605 |
+; changes: X, FRAME, TMP, DATA, CNT |
|
606 |
+; Z (only for nested call) |
|
607 |
+ANIM_MOVIE_CODE: |
|
608 |
+; get 4 bit code and 12 bit value |
|
609 |
+ lpm DATA,Z+ |
|
610 |
+ lpm CNT,Z+ |
|
611 |
+ mov TMP,DATA |
|
612 |
+ andi TMP,0xF0 ; 4 bit code (shifted left 4) is in TMP |
|
613 |
+ andi DATA,0x0F ; 12 bit value is in DATA:CNT |
|
614 |
+; interpret code |
|
615 |
+ cpi TMP,0x20 |
|
616 |
+ brsh ANIM_MOVIE_CODE_2_TO_F |
|
617 |
+ cpi TMP,0x00 |
|
618 |
+ breq ANIM_MOVIE_CODE_0 |
|
619 |
+ rjmp ANIM_MOVIE_CODE_1 |
|
620 |
+ANIM_MOVIE_CODE_2_TO_F: |
|
621 |
+ cpi TMP,0x20 |
|
622 |
+ breq ANIM_MOVIE_CODE_2 |
|
623 |
+; unknown code -> end |
|
624 |
+ ldi TMP,1 ; end |
|
625 |
+ ret |
|
626 |
+ |
|
627 |
+; plain frame |
|
628 |
+ANIM_MOVIE_CODE_0: |
|
629 |
+; save 12 bit value |
|
630 |
+ push DATA |
|
631 |
+ push CNT |
|
592 | 632 |
; extract frame to frame buffer |
593 | 633 |
ldi XL,low(FRAME) ; ptr to pixel data |
594 | 634 |
; XH not there on ATtiny2313 |
595 |
-ANIM_MOVIE_FRAME_LOOP: |
|
635 |
+ANIM_MOVIE_CODE_FRAME_PLAIN_LOOP: |
|
596 | 636 |
lpm DATA,Z+ ; get two pixels |
597 |
- mov TMP2,DATA ; write first pixel |
|
598 |
- swap TMP2 |
|
599 |
- andi TMP2,0x0F |
|
600 |
- st X+,TMP2 |
|
637 |
+ mov TMP,DATA ; write first pixel |
|
638 |
+ swap TMP |
|
639 |
+ andi TMP,0x0F |
|
640 |
+ st X+,TMP |
|
601 | 641 |
andi DATA,0x0F ; write second pixel |
602 | 642 |
st X+,DATA |
603 | 643 |
cpi XL,low(FRAME)+42 ; bottom of loop |
604 | 644 |
; XH not there on ATtiny2313 |
605 |
- brlo ANIM_MOVIE_FRAME_LOOP |
|
645 |
+ brlo ANIM_MOVIE_CODE_FRAME_PLAIN_LOOP |
|
646 |
+; restore 12 bit value |
|
647 |
+ pop CNT |
|
648 |
+ pop DATA |
|
649 |
+; show frame |
|
650 |
+ rjmp ANIM_MOVIE_CODE_SHOW |
|
651 |
+ |
|
652 |
+; rle frame |
|
653 |
+ANIM_MOVIE_CODE_1: |
|
654 |
+; save 12 bit value |
|
655 |
+ push DATA |
|
656 |
+ push CNT |
|
657 |
+; extract frame to frame buffer |
|
658 |
+ ldi XL,low(FRAME) ; ptr to pixel data |
|
659 |
+ ; XH not there on ATtiny2313 |
|
660 |
+ ldi CNT,0 ; no pixel data yet |
|
661 |
+ANIM_MOVIE_CODE_FRAME_RLE_LOOP: |
|
662 |
+; load new pixel data if none available |
|
663 |
+ cpi CNT,0 |
|
664 |
+ brne ANIM_MOVIE_CODE_FRAME_RLE_DATA_OK |
|
665 |
+ lpm DATA,Z+ ; get repeat count and pixel value |
|
666 |
+ mov CNT,DATA |
|
667 |
+ andi DATA,0x0F ; pixel value in DATA |
|
668 |
+ swap CNT |
|
669 |
+ andi CNT,0x0F ; repeat count in CNT |
|
670 |
+ inc CNT ; use for repeat count + 1 pixels |
|
671 |
+ANIM_MOVIE_CODE_FRAME_RLE_DATA_OK: |
|
672 |
+; write pixel data to frame |
|
673 |
+ st X+,DATA |
|
674 |
+; count down available pixel data |
|
675 |
+ dec CNT |
|
676 |
+; bottom of loop |
|
677 |
+ cpi XL,low(FRAME)+42 ; XH not there on ATtiny2313 |
|
678 |
+ brlo ANIM_MOVIE_CODE_FRAME_RLE_LOOP |
|
679 |
+; restore 12 bit value |
|
680 |
+ pop CNT |
|
681 |
+ pop DATA |
|
606 | 682 |
; 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: |
|
683 |
+ rjmp ANIM_MOVIE_CODE_SHOW |
|
684 |
+ |
|
685 |
+; back reference |
|
686 |
+ANIM_MOVIE_CODE_2: |
|
687 |
+; check if in nested call |
|
688 |
+ cpi TMP2,0 |
|
689 |
+ brne ANIM_MOVIE_CODE_2_NESTED |
|
690 |
+; save pointer to frame data |
|
691 |
+ push ZL |
|
692 |
+ push ZH |
|
693 |
+; go back by 12 bit value in DATA:CNT |
|
694 |
+ sub ZL,CNT |
|
695 |
+ sbc ZH,DATA |
|
696 |
+; recursive call |
|
697 |
+ ldi TMP2,1 ; nested call |
|
698 |
+ rcall ANIM_MOVIE_CODE |
|
699 |
+; restore pointer to frame data |
|
700 |
+ pop ZH |
|
701 |
+ pop ZL |
|
702 |
+; done |
|
703 |
+ ldi TMP,0 ; continue |
|
704 |
+ ret |
|
705 |
+ |
|
706 |
+; back reference - nested call |
|
707 |
+ANIM_MOVIE_CODE_2_NESTED: |
|
708 |
+; go back by 12 bit value in DATA:CNT |
|
709 |
+ sub ZL,CNT |
|
710 |
+ sbc ZH,DATA |
|
711 |
+; recursive tail call (no need to save Z on nested call) |
|
712 |
+ ldi TMP2,1 ; nested call |
|
713 |
+ rjmp ANIM_MOVIE_CODE |
|
714 |
+ |
|
715 |
+; show frame |
|
716 |
+ANIM_MOVIE_CODE_SHOW: |
|
717 |
+; high part of frame time |
|
718 |
+ cpi DATA,0 |
|
719 |
+ breq ANIM_MOVIE_CODE_SHOW_HIGH_DONE |
|
720 |
+ ldi TMP,0 ; means 256 frames times |
|
721 |
+ rcall OUT_FRAME_TIME |
|
722 |
+ dec DATA |
|
723 |
+ rjmp ANIM_MOVIE_CODE_SHOW |
|
724 |
+ANIM_MOVIE_CODE_SHOW_HIGH_DONE: |
|
725 |
+; low part of frame time |
|
726 |
+ cpi CNT,0 |
|
727 |
+ breq ANIM_MOVIE_CODE_SHOW_LOW_DONE |
|
728 |
+ mov TMP,CNT |
|
729 |
+ rcall OUT_FRAME_TIME |
|
730 |
+ANIM_MOVIE_CODE_SHOW_LOW_DONE: |
|
731 |
+; done |
|
732 |
+ ldi TMP,0 ; continue |
|
733 |
+ ret |
|
734 |
+ |
|
735 |
+ |
|
736 |
+ |
|
737 |
+; play animation |
|
738 |
+; input: Z = pointer to movie data |
|
739 |
+; output: - |
|
740 |
+; changes: X, Z, FRAME, CNT, DATA, TMP, TMP2 |
|
741 |
+ANIM_MOVIE: |
|
742 |
+; process code block |
|
743 |
+ ldi TMP2,0 ; initial call |
|
744 |
+ rcall ANIM_MOVIE_CODE |
|
745 |
+; continue if not yet end |
|
746 |
+ cpi TMP,0 |
|
747 |
+ breq ANIM_MOVIE |
|
748 |
+; done |
|
612 | 749 |
ret |
613 | 750 |
|
614 | 751 |
|
... | ... |
@@ -8,6 +8,8 @@ import sys |
8 | 8 |
class Movie(object): |
9 | 9 |
class Frame(object): |
10 | 10 |
|
11 |
+ # offsets (y * width(66) + x) of the pixels whose green value |
|
12 |
+ # is taken from the *.bbm frames for the LEDs |
|
11 | 13 |
LED_COORDS = [ |
12 | 14 |
408, 414, 420, 468, 492, 677, 684, 863, 945, 951, 957, 963, 971, |
13 | 15 |
975, 1034, 1209, 1215, 1221, 1227, 1259, 1299, 1304, 1373, 1469, |
... | ... |
@@ -15,6 +17,9 @@ class Movie(object): |
15 | 17 |
2106, 2163, 2225, 2365, 2559, 2620, 2956 |
16 | 18 |
] |
17 | 19 |
|
20 |
+ # offsets (y * width(66) + x) of the pixels whose color is set to |
|
21 |
+ # the LED brightness in the *.bbm frames on output |
|
22 |
+ # (multiple (-> list) per LED) |
|
18 | 23 |
LED_COORDS_OUT = [ |
19 | 24 |
[408, 407, 409], [414, 413, 415], [420, 419, 421], [468, 533, 403], |
20 | 25 |
[492, 425, 559], [677, 610, 744], [684, 617, 751], [863, 797, 929], |
... | ... |
@@ -36,12 +41,15 @@ class Movie(object): |
36 | 41 |
self.leds = len(self.LED_COORDS) * [0] |
37 | 42 |
|
38 | 43 |
def from_frame_data(self, duration, data): |
44 |
+ """parse frame from duration and *.bbm pixel data""" |
|
39 | 45 |
self.duration = duration |
40 | 46 |
for i in range(len(self.LED_COORDS)): |
41 | 47 |
ledno = self.LED_COORDS[i] |
42 | 48 |
self.leds[i] = data[ledno * 3 + 1] |
43 | 49 |
|
44 | 50 |
def to_frame_data(self): |
51 |
+ """convert frame to *.bbm frame data, |
|
52 |
+ return duation and pixel data""" |
|
45 | 53 |
pixels = 51 * 66 * [0, 0, 255] |
46 | 54 |
for i in range(len(self.LED_COORDS_OUT)): |
47 | 55 |
for ledno in self.LED_COORDS_OUT[i]: |
... | ... |
@@ -52,21 +60,55 @@ class Movie(object): |
52 | 60 |
return self.duration, data |
53 | 61 |
|
54 | 62 |
def to_firmware_data(self): |
63 |
+ """convert a frame to firmware data""" |
|
64 |
+ # duration: in 6ms steps, 12 bits |
|
55 | 65 |
duration = (self.duration + 3) // 6 |
56 | 66 |
if duration < 1: |
57 | 67 |
duration = 1 |
58 |
- if duration > 255: |
|
59 |
- duration = 255 |
|
60 |
- fw_data = [duration] |
|
68 |
+ if duration > 0x3FF: |
|
69 |
+ duration = 0x3FF |
|
70 |
+ # use shorter encoding |
|
71 |
+ plain = self._fw_pix_data_plain() |
|
72 |
+ rle = self._fw_pix_data_rle() |
|
73 |
+ if len(rle) < len(plain): |
|
74 |
+ code = 0x10 # rle compressed |
|
75 |
+ data = rle |
|
76 |
+ else: |
|
77 |
+ code = 0x00 # plain |
|
78 |
+ data = plain |
|
79 |
+ # encode code and duration at begin of data |
|
80 |
+ dur_h = (duration >> 8) & 0x0F |
|
81 |
+ dur_l = duration & 0xFF |
|
82 |
+ return [code | dur_h, dur_l] + data |
|
83 |
+ |
|
84 |
+ def _fw_pix_data_plain(self): |
|
85 |
+ """return pixel data, plain, no compression""" |
|
86 |
+ data = [] |
|
61 | 87 |
half = None |
62 | 88 |
for led in self.leds: |
63 | 89 |
val = (led >> 4) & 0x0F |
64 | 90 |
if half is None: |
65 | 91 |
half = val |
66 | 92 |
else: |
67 |
- fw_data.append(half << 4 | val) |
|
93 |
+ data.append(half << 4 | val) |
|
68 | 94 |
half = None |
69 |
- return fw_data |
|
95 |
+ return data |
|
96 |
+ |
|
97 |
+ def _fw_pix_data_rle(self): |
|
98 |
+ """return pixel data, compressed using run length encoding""" |
|
99 |
+ data = [] |
|
100 |
+ val = (self.leds[0] >> 4) & 0x0F |
|
101 |
+ cnt = 0 |
|
102 |
+ for led in self.leds: |
|
103 |
+ ledval = (led >> 4) & 0x0F |
|
104 |
+ if val == ledval and cnt < 0x10: # same value -> count |
|
105 |
+ cnt += 1 |
|
106 |
+ else: |
|
107 |
+ data.append((cnt - 1) << 4 | val) # append RLE item |
|
108 |
+ val = ledval |
|
109 |
+ cnt = 1 |
|
110 |
+ data.append((cnt - 1) << 4 | val) # last RLE item |
|
111 |
+ return data |
|
70 | 112 |
|
71 | 113 |
def __init__(self): |
72 | 114 |
self.frames = [] |
... | ... |
@@ -77,6 +119,7 @@ class Movie(object): |
77 | 119 |
self.frame_hdr = struct.Struct("!H") |
78 | 120 |
|
79 | 121 |
def read_bbm(self, filename): |
122 |
+ """read movie from *.bbm file""" |
|
80 | 123 |
try: |
81 | 124 |
with open(filename, "rb") as f: |
82 | 125 |
# main header |
... | ... |
@@ -119,6 +162,7 @@ class Movie(object): |
119 | 162 |
return False |
120 | 163 |
|
121 | 164 |
def write_bbm(self, filename): |
165 |
+ """write movie as *.bbm file""" |
|
122 | 166 |
with open(filename, "wb") as f: |
123 | 167 |
# main header |
124 | 168 |
f.write(self.main_hdr.pack(0x23542666, 51, 66, 3, 255)) |
... | ... |
@@ -136,13 +180,41 @@ class Movie(object): |
136 | 180 |
f.write(data) |
137 | 181 |
|
138 | 182 |
def write_firmware(self, filename): |
139 |
- with open(filename, "w") as f: |
|
183 |
+ """write movie as firmware (assembly include file)""" |
|
184 |
+ # convert all frames to firware data |
|
185 |
+ fw_frames = [] |
|
186 |
+ fw_len = 0 |
|
140 | 187 |
for frame in self.frames: |
141 | 188 |
fw_data = frame.to_firmware_data() |
189 |
+ # search for identical frame before |
|
190 |
+ id_len = 0 |
|
191 |
+ for id_frame in fw_frames: |
|
192 |
+ if id_frame == fw_data and id_frame[0] & 0xE0 == 0x00: |
|
193 |
+ # identical frame found (and code is 0x00 or 0x10) |
|
194 |
+ # -> replace data with back reference |
|
195 |
+ back = fw_len - id_len + 2 |
|
196 |
+ if back <= 0x3FF: |
|
197 |
+ back_h = (back >> 8) & 0x0F |
|
198 |
+ back_l = back & 0xFF |
|
199 |
+ fw_data = [0x20 | back_h, back_l] |
|
200 |
+ print("DEBUG back", fw_data) |
|
201 |
+ break |
|
202 |
+ id_len += len(id_frame) |
|
203 |
+ # append frame to list |
|
204 |
+ fw_frames.append(fw_data) |
|
205 |
+ fw_len += len(fw_data) |
|
206 |
+ # build firmware data |
|
207 |
+ fw_data = [] |
|
208 |
+ for fw_frame in fw_frames: |
|
209 |
+ fw_data += fw_frame |
|
210 |
+ fw_data.append(0xF0) # end marker |
|
211 |
+ if len(fw_data) & 1 != 0: |
|
212 |
+ fw_data.append(0) # ensure even length |
|
213 |
+ # write firmware data as assembly |
|
214 |
+ with open(filename, "w") as f: |
|
142 | 215 |
for i in range(0, len(fw_data), 8): |
143 | 216 |
vals = ["0x{:02X}".format(v) for v in fw_data[i:i + 8]] |
144 | 217 |
print(" .db {:s}".format(",".join(vals)), file=f) |
145 |
- print(" .db 0,0", file=f) |
|
146 | 218 |
|
147 | 219 |
|
148 | 220 |
def parse_arguments(): |