M5StackでBad Apple!!

もうM5Stackでの取り組みは一段落させよう…と思っていたけれども、320x240というサイズに身に覚えがありすぎて、やはりこれだけは挑戦せねばならぬだろう…。

そう、Bad Apple!!だ(^^)

 

f:id:PocketGriffon:20210914171153j:plain

過去のブログを見てもらったら分かるけれど、私は半年ほど前にポケコンでBad Apple!!の表示に取り組んでいる。

Bad, Apple の検索結果 - レトロパソコンであそぼう!

 

最終的にはポケコンに取り付ける1MBの増設メモリを作ってまで、フルのBad Apple!!を再生させている。ココまで行くともはや執念だ(^^;

 

 

ぼんやりと「M5StackでBad Apple!!を動かそうとしたら、何が問題になるだろうな〜…」と考えていた。ぱっと分かるのは、データがメモリに入り切らないという、いつもの問題。

なんとなく電卓を叩きつつ「行けるかも……?」と思ったのが運の尽き……。

見えてしまったらプログラマとしてやらねばならないのだ!!!!

 

実現方法の検討

とりあえず検討するべきことは以下の2つかな?

 データの置き場所、転送方法

 圧縮方法、展開方法

それぞれに答えが出れば、組み合わせる事でBad Apple!!が実現できそうだ。

 

まずはデータの置き場所を考えないといけない。

M5StackはでっかいPSRAMがあるとは言え、最大で4MB弱が限度だ。Bad Apple!!オリジナルのデータは11MB以上ある。そのままの状態ではRAM絶対に入らない。

 

過去にPC-1600Kで圧縮した際には884KBに収まったが、あの時とは画像サイズが違う。PC-1600Kでは64x32の2値だったが、今回のM5Stackでは320x240の2値だ。単純にドット数だけ比べても37.5倍の違いがある。

 

今回も圧縮方法から考えてみよう……と思ったが、圧縮データをストリームで展開するのならば、ランレングス圧縮が最適だ。これはもう疑いの余地がない気がする。あとは「ランレングス圧縮した後のデータをどうするか」だ。

 

あれこれこねくりまわす事はしてみたが圧縮の方針を決めた。さっそく圧縮プログラムを作ってみたところ、320x240ドットの画像データが最小で305バイト、最大で1892バイトのファイルとなった。全てのファイルを全部つなぎ合わせると約5.1MB、1枚の画像平均だと約800バイトだ。

 

5.1MBのデータということは、やはりオンメモリに収まらない。SDカードから少しずつデータを読んでくる方式を検討する事にした。

 

Bad Apple!!は秒間30枚の画像データを表示していく必要がある。30枚ということは平均800バイトとして約23.5KBが1秒間のデータサイズとなる。最低でもこのサイズのデータがメモリ中に置かれてなければ、1/30秒単位での表示が無理ということになる。

 

……SDカードからは秒間でどのくらいのデータが読み出しできるんだろうか??

さっそく実験プログラムを作ってみる。

M5Stackを使っていてホントに楽ちんだと思うのは、こういうこまめな実験がとても手軽にできるという事だろう。面倒とか手間とか思えないほど簡単だ!(^-^)

f:id:PocketGriffon:20210914181208j:plain

一番下にある「Speed」というのが、1秒間に読み込めるバイト数だ。これを見ると読み込みに専念した状態で約100KB/秒の転送速度があるらしい。おそらくSDカードの特性に左右されるものだと思うので、あくまでも「うちの環境では」としておく。

 

そして「読み込みに専念して100KB」というのも考慮しないいけない。他のことをしながら読み込みに行ったら、平気で1/10以下になる。そして実際間に合わない可能性が高い。

 

そこで以下のような方針を考えた。

 大きなバッファを用意し、あらかじめバッファの9/10は先読みしておく

 表示が開始されたら、少量ずつバックグランドで読み込みをしていく

 

ここまで考えてみて、ようやく「行けそうだ」という手応えを感じられた(^^)

 

実装

f:id:PocketGriffon:20210914182258j:plain

まずはデータを圧縮して巨大なファイルを作成しておく。この手のツールはMacでぱぱっと組み上げる。2値しかなければテキストでそれっぽく表示させて確認するのも簡単だ!(^^)

そしてM5Stack側のプログラム構成はこんな感じにした。

・Core0: 表示、SDカードの読み込み

・Core1: 圧縮データの展開

 

表示にはLovyanGFXを利用した。

表示とSDアクセスは同時に出来ない?みたいな話が出ていたので、これらの処理を同じコアににまとめる事で、暗にバッティングを回避する事にした。

 

Core1で進む圧縮データの展開が終わったら、Core0の表示を開始するようにして、画像のガタツキを極力なくしてみた。Core0は展開を待つ間、SDカードを読みまくる。

久しぶりにvolatile宣言を使ったかも(^^;

 

表示についてはpushSpriteをする回数が増えると比例して処理負荷も増えるので、スプライトは320x120と、画面を2分割する大きなフレームバッファを持った。

 

表示のタイミングはCore1側で行っている。圧縮データの展開に掛かった時間を測っておき、1/30秒を待つタイミング分をdelayしている。

しかしここで困った問題が!

 

delayはms単位しか指定出来ない関数なので、ms単位はdelayで、それ以下の待ち時間はdelayMicrosecondsという関数を用いた。ここに…どうしても誤差が入る(T-T)

 

例えば1枚の映像につき1ms(1秒の1000分の1の時間)の誤差があると、6566枚を表示し終える頃には6秒もずれてしまうのだ!これでは音楽を載せる事は出来ない(T-T) 最後の状態で誤差を0.2秒くらいに抑えるためには、1枚の絵での誤差を0.075msくらいに抑えないといけない。これが難しい。

 

タイマー割り込みがあるのかないのか分からなかったので、今は感覚値であわせる事をしてみた。将来的に音楽を入れるようになったらタイミング取るための割り込みあるかな…汗

 

心配していたSDカードの読み込みの遅さも、調整していく流れで表示に追いつかれなくなった!バッファサイズを思い切って2MBにしているが、実験してみた感じでは100KB以下でも大丈夫そうだ。

f:id:PocketGriffon:20210914185639j:plain

M5Stack実機の画面で見ると白は真っ白に見えてるんだけど、動画で見るとなぜか縦線が入ってしまっている…。カメラの関係??こういうのを上手に撮れるようになりたい(T-T)

 

あとは音楽かー……音楽。

どうやって鳴らすのかも分かってないけれども、PCMが使えるのならば画像データの合間にインターリーブで突っ込んくしかなさそうな感じも…汗

 

とりあえず音は保留で!やってみたい気持ちはあるけれども(^^;;

 

ではまた次回!(^-^)ノ