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(): |