たまりば

パソコン・インターネット パソコン・インターネット三鷹市 三鷹市

Switch用ファミコンコントローラでファミコン以外のゲームをプレイしてみた
2023年05月04日 09:25

ファミコンコントローラでSwitchのマリオ64をプレイしているかのような画像
先日、「Nintendo Switch Online 加入者限定 ファミリーコンピュータ コントローラー」を買った。
これは名目上「ファミリーコンピュータ Nintendo Switch Online」をプレイするための専用コントローラであり、公式ページにも
※Nintendo Switch 本体付属のJoy-Conよりもボタンが少ないため、『ファミリーコンピュータ Nintendo Switch Online』以外のソフトを遊ぶことはできません。
とはっきり書かれている。
しかし、他のソフトを遊べない理由は「ボタンが少ないため」だけであり、ゲームが起動しないようなことはなく、ボタンさえ足りればプレイ可能である。
これはそのへんの記事で事前に分かっていた。例えばファミ通のこれ。
2コンのマイクが使える!? Nintendo Switch“ファミコンコントローラー”の各種機能や使えるタイトルなどを実機レビュー

せっかくなのでどのゲームが遊べるか、手持ちのゲーム(体験版含む)を片っ端から試してみよう。

まず、このコントローラのキーだが、ファミコンのボタンに加え、本体への接続部はJoyConと同一なので、LRボタンも付いている。
キー割り当てはStart/Selectが+/-に対応する他は名前通り。非常に残念なことに、本体機能でのキーコンフィグには対応していない。
LRは単体では押しづらいがJoyConストラップがそのまま使えるので付ければだいぶ使いやすくなる。
ただJoyConと違って+/-の目印がないため、(本体への)装着の向きを示す▶▶▶マークと逆向きに装着することになり向きを間違えやすいので注意が必要だ。

また、本体左右に装着した場合のキー割り当ても参考に示そう。
左(I)
十字: 十字
St: +
Se: -
A: (反応なし)
B: キャプチャ
右(II)
A: A
B: B
十字: (4方向すべて)Home
LRが無い分単独に劣り、追加のボタンはゲーム外の機能であるHomeとキャプチャができるだけで、勝る点は無い。この使用法は考えなくてよいだろう。

以下、ゲームごとの評価をしていく。
評価はあまり厳密なものではないので参考程度に見てほしいが、一応意味合いとしては次のようなイメージだ。

良好: 重要な操作が全て賄え、全編通して普通のプレイが可能。
微妙: 重要な操作に不可能なものがあり、プレイの幅は狭まるが、普通のプレイが楽しめる場面も多い。
困難: 普通のプレイに必須の操作が不可能だが、普通でないプレイで楽しめる場面はあるかもしれない。
不可: プレイに必須の操作が不可能で、ゲームを楽しむことはまず無理。

また、
・主要なゲームモードが複数あってモードによって評価が異なる場合はそれぞれ評価する。
・ゲーム中ほとんど触る必要のないメニュー操作は評価とは別扱いとする。(例えばファミコンコントローラにないボタンを押さないとゲームを開始できない場合でも開始してからの操作ができれば高評価)

並び順はなんとなく、「問題なくプレイできる」または「曲がりなりにもプレイできることが意外」なものから順に。



マリオカート8DX

著名な3Dレースゲーム。ドリフトとアイテムが特徴。
【良好】
一番意外性があったのがこれ。(まあ事前に上記記事で分かっていたが)
2Dゲームは多くが十字キーに対応しておりボタン数が足りれば操作可能なものは多いのに対し、3Dのゲームは方向指示操作がアナログスティック限定なことが多く、プレイ可能なものはかなり珍しい。
またキーコンフィグもないのに奇跡的にボタンが配置されている。
十字: 移動
A: アクセル
B: ブレーキ
L: アイテム使用
R: ドリフト
使えない機能
Xの視点変更は使えないがまあ無くてもそんなに困らない。
メニュー操作は若干問題があり、マップ表示・ジャイロ操作のOn/Offができなかったり、タイムアタックで150/200ccの切り替えができなかったりする。
なおプレイ中にポーズを掛けることをしなければIIコンでも操作可能である。

横並びのABでAを押しながらBや、LRが若干押しづらく指が疲れるが、一応プレイは可能な程度の操作性。
例えば200ccのスペシャルカップを全1位でクリアできた。

スーパーマリオ3Dコレクション

3Dマリオ3作のオムニバス。
【困難・不可・不可】
マリオサンシャイン・マリオギャラクシーは不可。例に漏れず移動操作が左スティックのみ。
意外なことに64のみ十字キーで操作が可能。マリオ64DSで十字キー操作があった関係だろうか。
ただしボタンが非常に残念な割付け。
十字: 移動
A,B: ジャンプ
L,R: カメラ切り替え
キー数はあるのにしゃがみも攻撃もできない。
キーコンフィグさえあればLをZ、BをBで視点変更以外問題なくプレイできたものを。
しかし十字キーで操作可能なのと、いちばん重要なジャンプだけは使えることから、何気にそこそこ遊べる。
Aボタン縛りプレイはなかなかマイナーな縛りプレイだが、最初のスターを取るのがバグ技を使わなければ不可能である点とクッパが倒せない点を除けば取得できるスターはけっこうあり、なかなか楽しいプレイである。(逆のAボタンのみ使用不可の縛りプレイはA Button Challengeと呼ばれ有名だ)
加えて十字キー操作で8方向最高速の移動しかできないのがいい感じに難易度を上げ、一味違った縛りプレイが楽しめる。Nintendo64(というかマリオ64?)の開発時に3D空間を自在に移動するにはアナログスティックが必須だと分かったという話があった気がするが、それを実感する。
冒頭の写真はファミコンコントローラでチックタックロックのえっへんてっぺんドッスンを目指してスター目前で死んで心が折れた時のもの…の録画を流しながらコントローラを持ってあたかもプレイ中のように装っているものだ。プレイしながら写真を撮るのはだいぶ無理があった。
3DスティックではAボタン縛りでもさほど難なくクリアできる場所なので、十字キーの難しさが出た。

Vertical Strike Endless Challenge

戦闘機で敵を倒す3Dシューティングゲーム。
【微妙】
圧倒的自由度のキーコンフィグ。
方向操作を十字キーに割り当てられるし、各種操作はStart/Selectにも割り当て可能。
使える操作が多いので6ボタンでは足りないが、上手く選べばそこそこプレイ可能。

ファルコニア ウォーリア エディション

鳥に乗って空を飛んで敵を倒す3Dシューティングゲーム。
【微妙】
キーコンフィグあり。方向操作を十字キーに割り当て可能。
しかしプレイに必要な操作が多く、Start/Selectに割り当てられないので割り当てられるABLRではちょっと足りなそう。
プレイ中にキーコンフィグを変えることはできるので、なんとかプレイ可能かもしれない。

Aery - Beyond Time(ザフライーングトラベラージャーニービヨンドタイム)

なんかいい感じの風景の中をただ飛ぶ3Dゲーム。
【良好】
ゲームプレイ自体は3Dゲームには珍しく十字キーが効き、LRと合わせて普通に操作可能。
にもかかわらずなんとメニューの項目移動がスティックのみという変わったUI。

アクセス不可

スイッチや表示機のついた機械を3Dでいじりまわす謎解きパズル。
【微妙】
大抵の面では右スティックで回して様々な方向から見る必要があるが、LRで横回転しかできない。XYで拡大縮小もできない。
普段は拡大して操作する場面で非常に細かい操作が必要とされ、かなり厳しいものがある。
クリア可能な面はある程度ありそう…というかもしかすると全部可能かもしれないが、使える操作をすべて使って謎解きをするゲームなので、可能な操作だけではクリアできないかもしれない状態でプレイするのはゲームにならない。(答えを知っていてプレイするのもゲームにならない)

海腹川背Fresh

ゴム紐を地形に引っ掛けてステージを跳び回るゲーム。
【良好・微妙】
キーコンフィグ: +-以外ボタン自由
デフォルトではBジャンプ・A特殊能力で、ルアーがLRで斜め上にしか打てない。
変な縛りプレイをするのでなければキーコンフィグは必須。ABをジャンプ・ルアーに割り当てれば普通にプレイ可能。ゴールはデフォルトはXボタンだが、上入力でもゴールにできるようにできる。
使用頻度の低い斜め下ルアーは諦めよう。3DSやVitaには無かったのだし。
特殊能力の無い川背さんはそれでよいのだが、特殊能力ありのキャラを使う場合はボタンが足りない。StartはメニューとしてもSelectは余っているのに割当て不可で残念。ジャンプやルアーを外すわけにもいかないし斜め上ルアーの片方を諦めるしかなく、これは微妙。まあスーファミには無かったのだし遊べなくはない。

Celeste

空中ダッシュしたり壁に貼り付いたりするパズル気味な死に覚えプラットフォーマー。
【良好】
デフォルトはBがジャンプ、Aがダッシュとやや不便か。
キーコンフィグは充実も、そのキーコンフィグを使うためにはZLが必要。

MuseDash

レーンが2つと少ないのが特徴的な音ゲー。キャラが可愛い。
【良好】
キーコンフィグ: 選択式。
上下2レーンに対しデフォルトの
ABXY,R / 十字,L の分割がファミコンでも使いやすい。
他に
LR / 他
AX左上LR / BY右下
AB左下 / XY右上LR
があり、ABが分かれたり十字キーが分かれたり、意外とファミコンでもそれぞれ利用価値のありそうなコンフィグかもしれない。
ゲーム中の操作としては、手動フィーバーモードの発動操作がZL/ZR固定なのが残念なところ。
まあ最高得点を目指すには必須とはいえ、普通のプレイには無くても困らない。
また、(標準の割り当てでは)下レーンに対して使いやすいのがABの2キーしか無く、3連打以上が困難である。
メニュー操作は十字ABStartで大体可能。XYはランキングを見る・リトライ・お気に入り追加といった細かい機能くらいで遊ぶぶんには問題な…かったのだが、いつからかアップデートで曲選択のUIが変わってから、曲のグループ選択がスティック操作のみになった。バグに近い仕様変更。
そしてその後のアップデートで直った。バグだった模様。

SuperCableBoy

ワイヤーアクションや空中ジャンプなどモード切り替えするプラットフォーマー。
【良好】
キーコンフィグあり。
ジャンプ・能力使用・能力切り替え(ポーズして選択式)・能力切り替え(順送り)・能力切り替え(逆送り)・移動・カメラ操作
を自由割り当て、ポーズは+/-固定。
カメラ操作は諦め、ジャンプ・カートリッジ使用・切り替え左右をABLRに割り付けるのがよいだろう。試しにノーマルエンディングまでクリアしてみたが十分にプレイ可能である。カメラ操作で先を見ると少し有利なことがたまにあるが、そこまで有利でもないしそこまで頻繁にでもない。
キーコンフィグでは+/-(Start/Select)ボタンにも機能が割り当てられるものの、ゲーム中のポーズと干渉する。
例えばカートリッジ切り替えに割り当てると、カートリッジ切り替えUIが出つつゲームはポーズがかかる。ポーズを解除するとカートリッジ切り替えができる。これがなければカメラ操作以外完全だったのに。
…と思っていたのだが再度試すと+/-はポーズが掛かるだけで機能が発動しなかった(キーコンフィグで割り当てはできるのに)。なんだろう、バージョンで動作が変わったか?

ケイデンス・オブ・ハイラル: クリプト・オブ・ネクロダンサー feat. ゼルダの伝説

リズムに乗ってダンジョン進むやつfeat.ゼルダ
【良好】
基本の攻撃操作が「敵の方へ移動」。LRは固定の特殊行動。ABXYは扱いは同等で、アイテムを割り付けて使う。
同時に装備しておけるアイテムが4つから2つになるだけで問題なくプレイ可能。

ガブッチ

足場を食べるアクションパズルゲーム。
【良好】
使うボタン:十字, A=B, L=R, +, -(設定により=X)
珍しく+(メニュー)/-(リトライ)まで使い、すべてファミコンコントローラでまかなえる。
なお-はノーマルコントローラでは押しづらいので設定でXにも割り付けられる。

Sega Ages スペースハリアー

昔の非ポリゴンの3D奥スクロールシューティング。
【良好】
スタートしてからは問題なくプレイ可能。
ただしメニューには問題があり、キーコンフィグはあるのだが、コイン投入およびスタートがX固定。

ピクロスSシリーズ

ピクロス。
【良好】
ABで塗り・×付けは可能。マークは使えない。LでナビゲーションOn/Off、Rでカウント。
と、白黒ピクロスに関しては普通に良好。カラーピクロスでは操作方法が複数選べる中LRで色を選ぶ1種のみ利用可能でちょっと微妙か。まあでも1種とはいえ使えるので評価を落とすほどではない。

けものフレンズピクロス

ピクロス。
【良好】
ピクロスSシリーズと同じ。
カラーピクロスは無い。

初音ミクロジックペイントS

【良好】
ピクロス(と同一のルールのパズル)。
基本操作は十字ABのみ。
しかしメニュー操作に問題があり、問題の3段階に分かれた「レベル」の変更操作がZL/ZRのみのため、メニューも操作しようとするならLv1の問題群しかプレイできない。

キュービック

四角い主人公が地形ブロックを食べ進むパズルゲーム。
【微妙】
食べ操作はできるが、巻き戻しができない。ミスをしなければプレイ可能なものの、面倒。
キーコンフィグなし。

パックマン99

99人対戦パックマン。
【微妙】
基本の操作は十字キーのみだが、
戦略・攻撃対象の選択が左右スティック・ABXYに割当てられるが、ABに割当てた2種のみしか使えない。

YouTube

YouTube。
【良好】
十字ABで普通に操作可能。
Xで検索ができないがメニューから選択して可能。
再生中のシークも十字キーで可能。

TorqueL

ABXYの4ボタンで主人公の4面が伸びるゲーム。
【困難】
ABの2面しか使えないとプレイは困難。
キーコンフィグは無いし、あったところで4面に対応していない配置ではプレイ困難。

マッド・ラット・デッド

音ゲー+プラットフォーマー。
【困難】
キーコンフィグ無し。
ABXYがダッシュ・ジャンプ・溜め・落下の4行動に割り当てられており、ダッシュとジャンプしかできない。
たぶん進めない場面が出てくる。

リバーティア

オセロ式に地形を変化させて進むパズルゲーム。
【良好】
移動とジャンプ・色変えだけの操作で問題なくプレイ可能。

スーパーファミコンNintendoSwitchOnline

【微妙】
個別のゲームについては最後にまとめた。
ここでは全体について。
重要な点として、スーパーファミコンSwitchOnlineではファミコンコントローラのLRボタンはゲーム操作に使えない。
というのも、ファミリーコンピュータSwitchOnlineと同様、L+Rで中断メニュー/巻き戻し、Lがキャプチャ、Rがホームボタンに割り当てられているためだ。メニュー操作は問題ない代わりにゲーム内操作に不自由することになる。
ファミリーコンピュータSwitchOnline以外で使えないと言っておきながら、普通のコントローラと違う専用のキー割り当てを用意しているんだなあ。

ゲームボーイNintendoSwitchOnline

【微妙】
こちらはスーファミとは異なり、特殊な割り当てはされていない。
つまり、L・Rボタンは何にも割り当てられておらず、中断メニュー/巻き戻しはZL+ZRなので使えない。
ゲーム内の操作は問題なくできるのに、実に勿体ない。



以下、不可。
プレイがほとんどあるいは全く不可能なゲーム
3Dのゲームでは先述の通り十字キーで移動ができないパターンが多い。

ハコボーイ&ハコガール

主人公が体から箱を出すアクションパズルゲーム。
【不可】
移動はできるが必須動作のハコ出しがY、ハコ消しがX。キーコンフィグ無し。

NeonWall

玉や壁の色を変えてなんやかんやするアクションパズルゲーム。
【不可】
ジャイロまたは両スティックでポイントする操作が必要。

スマブラSP

スティックの弾き操作が特徴的な格闘ゲーム。
【不可】
試合開始までは操作できるが、試合中の移動操作が左スティックのみ。キーコンフィグはあるが移動は十字キーにはできない。
Wiiの時はWiiリモコンでの操作ができたのに残念だ。
一応、攻撃・必殺・シールド・ジャンプ・つかみ(・アピール)はキーコンフィグで自由にできるので、もしかするとすごい人なら何らかのプレイが可能かもしれない。

MotoRushGT

リアルなグラフィックで昔ながらの敵車避けレースゲーム。
【不可】
ハンドル操作がスティック(またはジャイロ操作)のみで十字キー不可。
十字キーでメニューは操作でき、ABまたはLRで加減速はできる。

マリオオデッセイ

3Dマリオ。
【不可】
移動操作が左スティックのみ。
十字キーにはマップやスナップショットモードなどの機能。
また、ABともにジャンプ。
キーコンフィグ無し。

ポケモンソード

ポケモン。
【不可】
十字キーで移動できない

メトロイドドレッド

メトロイドヴァニア。
【不可】
十字キーで移動できない。(マップ切り替え)

チョコボGP

FFキャラのレースゲーム。
【不可】
十字キーで移動できない。

パイロットスポーツ

パイロットウィングスっぽい飛ぶゲーム。
【不可】
十字キーで移動できない

星のカービィ ディスカバリー

ほぼ初の3Dになったカービィ最新作。
【不可】
十字キーで移動できない

いっしょにチョキッと スニッパーズ プラス

2人のキャラが互いの体を切って形を変えるパズルゲーム。
【不可】
十字キーで移動できない

HUE

世界の色を変えて地形やブロックを消したり出したりするパズルゲーム。
【不可】
キーコンフィグはあるのに、必須操作の色を選ぶ操作が右or左スティックのみ。

以下試すまでもなく諦めたやつ

リングフィットアドベンチャー

加速度/ジャイロセンサーが必須

Splatoon2および3

十字キーに移動以外の機能が割り当てられているため、移動不可

ゼルダ無双厄災の黙示録

同上

ゼルダBotW

同上

イモータルズフィニクスライジング

同上

NintendoLabo 01,04

加速度/ジャイロ・IR・振動あたりが必須

ヒューマンリソースマシーン

操作がジャイロ(or画面タッチ)

セブンビリオンヒューマンズ

同上

Jump Rope Challenge

加速度/ジャイロ必須

タイピングクエスト

キーボード専用



スーパーファミコンNintendoSwitchOnlineのゲームの個別評価

多いので全部は試していない。

ワイルドトラックス【困難】Bアクセル、Aバック。LR急旋回、YブーストX背伸び。一応移動はできるが遅いし曲がりづらい。
スーパーメトロイド【微妙】キーコンフィグあり、Start以外自由。ショット・ジャンプ・ダッシュ・アイテムセレクト・アイテムキャンセル・ななめ上射ち・ななめ下射ち。ショット・ジャンプ・ダッシュ・アイテムセレクトまでは必須で、ボタン足りない。
また、装備の切り替えが必須だがこれにメニュー中にRが必要。
すーぱーぷよぷよ通【良好】ABでぷよ左右回転。キーコンフィグもあり。
マリオのスーパーピクロス【良好】
スターフォックス【困難】キーコンフィグは選択式。AボムBブラスター、減速・ブースト・ローリングが不可
ゼルダの伝説【困難】Yでアイテム使用
スーパーマリオカート【困難】Aアイテム使用とBアクセル。LRドリフトYブレーキが不可。走れるだけマシだがドリフトなしでは遅い。
スーパーマリオワールド【困難】AスピンジャンプBジャンプ、Yダッシュが不可が痛い
ヨッシーアイランド【困難】Aタマゴ投げBジャンプY舌Rタマゴカーソルロック。Y舌不可が痛い
パネルでポン【良好・微妙】ABパネル入れ替え、LRパネル上げ。XYがパズルでのキャンセルなのでパズルモードは微妙。
スーパーマリオコレクション【良好】キーコンフィグ選択式、デフォルトはXYダッシュABジャンプだが、XYBダッシュAジャンプに切り替えれば問題なし。
スーパードンキーコング【困難】A乗り捨てBジャンプY攻撃。
カービィボウル【微妙】フィールドを見渡せない。LRで方向を45度単位で切り替えもできない。
  

  • スーパー電子手帳のカレンダーとドットマトリクス
    2020年10月21日 21:49

    今は別館のサイトの方に昔(2007年)書いた内容を、手直ししたり新たな考察を加えたりしたもの。
    元のページはこちらだが、文字コードが指定されていないのでChromeなどの低機能なブラウザでは文字化けして見えないかもしれない。Firefoxをおすすめする。



    むかーし、むかし。電子手帳というものがあったそうな。
    今でもあるけど、今は「ぴーでーえー」なんて横文字で呼んだりして、過去の「電子手帳」という呼び名の方がカッコよくて自分は好きだ。大体皆PDAが何の略か知らないだろう。なにせ自分も知らないのだ。
    (↑と当時書いたのだが、今はPDAも携帯電話に吸収されて無くなってしまった。諸行無常。)

    その電子手帳が「ちょっと高価な子供のおもちゃ」になりだした頃の話。

    思い出深い電子手帳を紹介しよう。カシオから発売されていた電子手帳だ。
    「スーパー電子手帳」シリーズと、何かわからないがたぶん何か違う「スーパー電子手帳Jr.」シリーズ。
    男児向け女児向けで様々な種類があり、「PKバトルリーグ」「ツインゴール」「ペットテレパシー」などある。

    機能は定番の電話帳・カレンダー(スケジュール帳)・電卓。それに子供向けだから占いやらゲーム。
    この電子手帳は7×7ドットのひらがなが4行×12文字表示できる。つまり普通に考えて縦32ドット×横96ドットだ。
    96×32文字
    ほとんどの表示は8×8単位(漢字は16×16)なのだが、例外がカレンダー。
    カレンダー
    こんな感じに3×5の数字を使っている。3×5は数字をまともに描ける最小のドット数と言ってよいだろう。
    日曜祝日は白黒反転、数字横の点はスケジュールがあることを示す。

    ここで疑問に思うことはないだろうか?
    そう、カレンダーの最大行数は6行。数字は縦5ドット。つまり5*6+隙間5ドットで35ドットとなり、32ドットの画面をオーバーしてしまう。
    6行ははみ出す

    それでどうなっているかというと簡単なことで、その分のドットがきちんと用意されている。
    しかし極限まで無駄を切り詰めているので、なかなかけったいな形になっており、
    ドットパターン(実は横幅が誤り)
    こうである。
    もう惚れちゃいますね。
    ここに、
    カレンダーを表示した状態(横幅は誤り)
    こうなるわけだ。

    ところで、これでは日付の表示に画面全部を使ってしまい、曜日が表示できない。
    分かりづらくないかって?
    大丈夫。
    スーパー電子手帳本体
    本体の方に書いてあるから。



    さて、ここからが新たな発見である。

    この電子手帳、パッケージで画面の総ドット数を誇っているのを過去に見た覚えがある。
    探すとこちらに発売元はツクダオリジナルだが見たところ同シリーズの「似顔絵電子手帳カラーリスト」のパッケージが見つかった。
    3118ドット
    「3118ドット」とある。
    数えてみよう。メイン部が縦32×横96、加えてはみ出し部分が(4×9+3)が2個で、3150ドット。
    おや、計算が合わない。差は3150-3118=32。32? 縦のドット数と同じだ。
    まさか…と別に見つけた写真で数えてみると、確かに横は95ドットしかない。そこも切り詰めていたか。

    しかし32ドット減らすと何が嬉しいのだろう。
    切りの良い32×96=3072ドットには足りないし、よくあるアイコンのためにコモン線が1本多いタイプで33×96=3168なら32ドット減らさなくても足りる(ドットマトリクス外にアイコンがあるが見たところ16個であり、足りる)。
    しかし、考えてみると追加のドットはすべて個別に制御する必要があるわけではない。
    表示する図形は30・31の下の方と右の点だけ。
    右の数字部は「31」しか表示しないので、字と地の2セグメント。左も、調べてみると「1」の字形は上で想像で書いたようなセリフ付きのものではなくデジタル数字の直線の「1」であったので、0と1を表示するには2セグメントで、地を合わせた3セグメントで済む。4つの点も足して合計わずか9セグメントで足りる。
    実際には隣り合っていないドットを1つのセグメントにまとめることはできないので多少多くなるが、下図の左9セグメント右7セグメントでいけそうだ。
    追加ドットパターン
    このセグメントを制御する配線を考えてみよう。

    …と、考えてみたら普通にはできそうにない。
    何が問題かというと、この囲まれたセグメント。
    囲まれたセグメント
    ドットマトリクス部から横を1ドット削ることで使えるようになるセグメントは、1seg×32com分。(マトリックスの縦横のコモン線・セグメント線と個々のセグメントが紛らわしいので前者をcom・segと書く)
    つまりこれだけではマトリックスにすることはできず、セグメント側を全体を1seg(使い方としてはコモン)に割り当ててcom側で制御するしかない。
    これではセグメントの隙間を通しでもしない限り他のセグメントに囲まれたセグメントを個別に制御することができない。

    これがcomがもう1本あればseg線を上から伸ばしてできるのだが…
    1com×複数segでのパターン
    もしくはsegが2本あれば2×Nのマトリックスにできてどうにかなるのだが…

    と思ったが、よく考えたらそんなことをしなくてもできた!
    囲まれたセグメントは1個だけ。1本だけならcom線も追加ドットに隣り合った最下段の1本を上から伸ばすことができる。
    1seg×複数comでのパターン
    周囲のアイコン含め、配線にも問題がない。
    配線

    このパターンだとうまく説明がつくことがある。
    実物の表示は、YouTubeに動画があり、分かったのだが、
    カレンダー実物
    これを見ると、最下段でない日曜日には下に枠が付いていることが分かる。
    最下段の数字が白黒反転した時は、ドット数からして下に枠がつかず、不自然である。つまり、下もう1列のドットは付けたくても付けられないのだと考えられる。
    comがもう1本あって1com×複数segのパターンであれば、下もう1列は容易に追加できる。
    1com×複数segで+1列
    また、追加のドットを下ではなく上に付ければ、白黒反転時に端が切れるのが「30」「31」から「1」「2」になり、日曜を含まず休日表示が起こりにくい点、文字数が少ない点からこちらの方が好ましいと考えられるが、1と2を表示できるパターンは囲まれるセグメントが多く、この作戦は不可能である。0と1の下4ドットだからできたパターンなのだ。
    1com×複数segのパターンであれば上に1と2のセグメントも可能だ。
    1com×複数segで1・2のセグメント
    もうひとつ、「1」の字形がセリフ付きでなく縦棒なのも、セリフ付きの形は不可能だったためとも考えられる。縦棒でも特におかしいわけではないが、3×5ドットの数字では1に上下や上だけにセリフを付ける方がよく見る気がする。
    なお、segが2本ある場合では、下もう1列が不可、1と2のパターンが不可は同様だが、1に上だけならセリフを付けることができる。

    実機は確認していないので合っているかはわからないが、今のところ観察結果を上手く説明できており、なかなか可能性が高いのではないかなと思っている。  

  • マリオ3でのキノコの突然変異の考察
    2020年03月10日 09:37

    スーパーマリオシリーズの代表的なアイテムにスーパーキノコ(や1UPキノコ)がある。

    今は左のようなデザインで定着しているが初代スーパーマリオブラザーズでは右のデザインだった。
    現在と初代のスーパーキノコ
    初めて初代のキノコを見たときは何だこりゃと思った。
    変わりすぎだろ何があった。
    …と、ずっと思っていた。

    先日マリオ2を初めてプレイして知ったのだが、まず顔が付いたのはマリオ2だったんだな。
    マリオ2のキノコ
    マリオコレクション(今風のデザイン)やマリオDXのおまけ(初代のデザイン)でしかマリオ2は見たことがなかったので知らなかった。
    スーパーマリオシリーズの様々な物に顔が付いていることについてはこのように言われている。
    雲や草には顔があるように見えるが、これは立体感を出すために書き込まれた線が、顔に見えていただけだった。
    しかし、これが「怖い」と言われたことから、次回作の『2』では、背景の雲や草にはっきりとした顔が書かれた。
    https://www26.atwiki.jp/gcmatome/pages/3410.html

    ただちょっと不思議なのは、初代の時からパッケージイラストにはマリオの手に持つスーパーキノコと思しきキノコにしっかりと顔が書かれていることだ。地面の花をも差し置いて。
    初代スーパーマリオのパッケージのキノコの顔
    スターやジュゲムの雲には初代から顔があるし、これは物には顔を描くというスタイルは初代からあったのを2で徹底したということか。

    にしても。
    スーパーマリオ1・2・3・ワールドのキノコ

    顔以外も2から3で変わりすぎじゃないか。
    …と思っていた。
    実はそうでもなかったのである。

    まず、実は上の画像には1つ嘘がある。
    マリオ1・2の斑点は橙~茶色・マリオ3の斑点は赤だと認識していたのだが(そして実際上の画像もそうなっているが)、斑点の色はどれも同じである。ついでにマリオの服や赤ノコノコの甲羅、スーパー木の葉も同じ色である。
    マリオ2・3のキノコの色
    今回キノコの色について調べていて初めて気づいた。
    スーパーファミコン以降の真っ赤になった色のイメージや、空の色やキノコの地の色の違いで誤認していたものと思われる。
    そして同じ条件で撮られた別のソフトの画像はあまり存在しないことから、気づけなかったのだろう。
    実際、先の画像はMarioWikiから取ってきたのだが、見ての通り色は一定していなかった。ファミコンの色はかくも曖昧なものである。

    次に地の色が白になった事について。これはアートスタイルの変化に対するハードの制限である。
    マリオ3ではキャラクターの絵に(大半は黒の)縁取りが付くようになった。
    これにより、縁に1色とられることとなり、残り2色でキノコを描くことになった。
    地の黄色を残すか柄(え)の白を残すかの選択になるが、1UPキノコとの統一や他のキャラクターとの兼ね合いを考えると、白にするのが自然だろう。
    ちなみにマリオ3の通常の(地上面での)スプライトのパレットは
    ・プレイヤーキャラ用(色はパワーアップなどで変わる。標準マリオはベージュ/赤茶/黒)
    ・白/赤茶/黒 (スーパーキノコ、スーパー木の葉、赤ノコノコの甲羅、パタクリボーなど)
    ・白/緑/黒 (1UPキノコ、緑ノコノコの甲羅、蔓/葉など)
    ・ベージュ/黄土/黒 (クリボー、ノコノコの顔など)
    の4種である。

    とはいえ縁取りを付けただけで突然3のデザインになるわけでもなかろう。…と思っていた。
    試しに2のキノコに縁取りだけ付けてみよう。
    ところで、縁取りの付け方には4連結と8連結がある。
    4連結と8連結
    不思議なことに、マリオ3ではほとんどのキャラクターのほとんどの部分は8連結で縁取りされているのだが、キノコは4連結である。
    キノコだけ最初に描かれたとか、デザイナーが別とかだろうか。何にせよ4連結で描くことにする。
    まずは外形をそのままにして描くのが自然だろう。
    マリオ2から3のキノコへ_1
    すると、妙に痩せた印象になってしまった。縁取りが無ければ物と背景との境が外形だと認識するのに対し、縁取りをすると縁の1pxの中央を外形と認識するからだな。
    1pxだけ広げよう。
    マリオ2から3のキノコへ_2

    さて、ファミコンにはスプライトの左右反転機能があるので、左右対称な物体は必要なタイル数が半分で済む。
    タイル数は節約したいので、左右対称のデザインにしよう。
    とりあえずそのまま左右対称にしてみると、
    マリオ2から3のキノコへ_3
    左はあからさまに2つの大きな斑点が並んでしまって不自然だ。右の方が望みがありそうだな。
    くっついてしまった上の斑点を切って、中央のスペースが不自然に空いているので、消えてしまった左のものくらいの大きさの斑点を1個置く。
    マリオ2から3のキノコへ_4

    ほぼマリオ3のキノコになってしまった!

    比較してみよう。
    マリオ2から3のキノコへ_比較
    特に上部の丸みが完全に一致しているのと、斑点の形の大部分が一致しているあたりがポイントが高い。
    縁取りを付けてからここまで制限に従って不自然な部分を直していっただけだ。
    ここまできたら後は笠を1px下に下ろし、柄を1px太らせ、斑点を自然に調整するだけ。その程度の変化には特に不思議はない。

    マリオはハードの制限から生まれたデザインというのは有名だが、キノコもそうだったのだなあ。
    次作のワールドで色数やスプライト数の制限が無くなったにも関わらず3のデザインを(地と斑点の色は反転したが)踏襲、更にこの時左右非対称になったのがその後左右対称に戻り、一番制限のきつかった3のデザインに固定されたというのがなかなか面白いところだ。  

  • ファミコンで9×9ドット文字表示(ほか)
    2019年04月08日 02:02

    ファミコンあたりのゲーム機は画面を8×8ドットのタイルを並べて構成している。よって文字表示は8×8ドット(空白含む)にするとプログラムが簡単だ。
    このサイズだとひらがなカタカナ(濁点・半濁点なし)にちょうどいいサイズである。英数字にはやや大きいがさほど不自然でもない。

    問題は漢字表示である。
    8×8ドットに漢字を描くのはかなりの無理がある。16×16ドットにすれば何不自由なく描けるが、このサイズの文字は大きすぎて画面内に書ける文章量が減る。
    そこでその中間あたりのサイズの文字表示をやってみようと思い立った。

    まず考えたのが12×12ドット(空白含む)だ。1.5マスで比較的扱いが楽そうだ。
    ゲームボーイでプログラムを組んでみて、まあうまくいった。
    12×12ドット文字_ゲームボーイ

    なお後に知ったのだがゲームボーイで12×12ドットのフォントはいくつか例がある。その1つの「合格ボーイ」シリーズの「常識の書」がこちらだ。
    常識の書_横書き常識の書_縦書き

    さて、12ドット文字プログラムだが、意外と面倒な面が多かった。
    1文字を18バイトで持っていたのだが、18は切りが悪い。縦横に空白をとると11×11ドットであり、11×11=121ビットというのは16バイトに収まる。せっかくだから収めたくなるがなかなか上手くいかない。
    半角文字も用意したくなるが、横6ドットとなるとせっかくの1.5マスの切りの良さが無くなってしまう。
    12×12ドットはわりと面積が広く、書き込みに意外と時間がかかる。
    (なお先述の「常識の書」だが、英数字は全角の半分ではなく横8ドットにして切りの良さを保ち、フォントデータは中央4ドットを重複して持つことで表示を楽にしている。勉強になる)

    12ドットでも面倒なら別のサイズでも似たようなものではないかという思いにいたり、別のサイズを模索してみることにした。
    すると9×9ドット(空白含まず)がかなり楽そうだと分かった。
    一見中途半端だが、9×9ドットはどのように配置しても8×8ドットのマス目の4マスにかかるという重要な性質がある。場合分けが不要というのは楽だ。
    8bitCPUのレジスタは8bitだが、キャリーフラグをうまく使えば9bitを保持しておくことができる。
    文字を9×9ドットとして1ドットの空白を設ければ10ドット。偶数なので奇数より多少楽だ。

    そんなわけでゲームボーイで組んだ。
    9×9ドット文字_ゲームボーイ
    見ての通り表示できる文字数が増えるのも利点だ。漢字の読みやすさは12ドットには劣るものの、十分だろう。

    その後ファミコンに移植してみた。
    ファミコンでもカートリッジにメモリを積めばゲームボーイと同様にキャラクタを書き換えることができる。(CHR-RAMと呼ばれる)
    (以前はあまりカートリッジ側に複雑なものを載せるのはレトロゲームプログラミングとして反則な気がして敬遠していたのだが、CHR-RAMを使っているソフトは普通にある感じなのでいいかと思い直した)
    9×9ドット文字_ファミコン
    ゲームボーイでは1文字出力のコードしか書いていなかったのだが(上の画像は1文字出力関数を文字数分呼んでいるのだ)、ファミコン版でやっとテキストデータを呼んで文字列を表示するコードを書いた。
    (もっとも現状文字コードが1バイトで文字を256種しか使えないのだが)

    これの製作中に1つ思いついて実装したのが、フォントデータの一部をテキストデータに持たせる手法だ。
    フォントデータは普通には9×9で81bit、10バイトと余り1bitになる。これを11バイトで持つのは無駄が多く気に食わない。
    そこで、例えば文字コードの最上位bitを文字の右上のドットに対応するようにする。するとフォントデータとして持つのは残りの10バイトのみでよくなる。
    文字コードの並びは不規則になってしまうが、文字列を表示するだけなら問題は無い。
    問題が起こるのは、まず外部とのやり取りで既存の文字コードを使う場合。これはゲームではあまり考えなくてよいだろう。
    それと、文字列をソートしたい場合。これはやや問題になるかもしれない。漢字はソートできなくてよいと考えて、仮名のみならテーブルを作るか、文字コードを上手く作れば仮名の順は取れるようにできるかもしれない。

    あと文字の送り幅を変えるコードもやっつけで書いた。やっつけなので1文字あたり1バイトを余計に使う無駄なコードだが。文字データも10バイト使って無駄だ。

    NESファイルは[こちら]
    コードは汚いので(まだ)公開しない。そのうち整えてCC-BY-NCあたりで公開するつもり。ゲームボーイからファミコンへの移植で変わったとことかも文章にまとめたい。

    (2019/07/17追記)
    実機動作していただいた!

    …そして初期化忘れに気付かされる。
    エミュレータはふつう初期状態のメモリは全ゼロになっているので初期化しなくても動いてしまい、実機で動作させて初めて気づくことが多い。PICマイコンでもしょっちゅうやらかした。
    これをエミュレータでテストするのはなかなか面倒である。今回はテスト用に乱数生成(Xorshift)のコードを書いてまず最初にRAMをランダム値で埋めることにした。
    それでできたのがこちらである。見事に再現できた。
    初期化忘れ再現
    さらに調べているとスタックが3バイト単位の繰り返しで全部埋まっていることにも気づいた。スタックオーバーフローだ。
    コードを確認すると、初期化からVBlank割り込みルーチンに流れ込むわ、VBlank割り込みルーチンは最後リターンせずにVBlank待ちに入るわのめちゃくちゃなコードだった。よく動いていたな。

    そんなわけで割り込みルーチンを直しメモリのゼロクリア処理を追加して、
    確認してみると目的のノイズは消えたのだが、起動時に一瞬別のノイズが見えるようになったのが治らない。
    起動時一瞬のノイズ
    どうやらランダム値をVRAMに書き込む際に出ているようで、表示は切ってあるはずなのに不思議である。
    ランダム化を消せば消えると思われたが、理由が分からないのは不安なので調べることにした。
    結果、案の定仕様を勘違いしていたことが分かった。PPUADDRレジスタがパレットのあるアドレスを「表示中に」指しているとそのパレットの色が出るという仕様があるのだが、これを非表示中に起こるものと思っていた。
    https://wiki.nesdev.com/w/index.php/PPU_palettes#The_background_palette_hack
    更に、この仕様に関連して、ファミコンは起動時の一瞬、PPUが操作可能になる前に(条件によって異なる)とある1色を表示するということも知った。そういえばそんなだった気がするな。PPUが操作可能になる前なのでどうしようもないらしい。

    理由が分かったので安心してゼロクリア処理を消す。今まで確認に使っていてNintendulatorではノイズが見えなくなったが、再現度の高いらしいMesenでは本来のパレットへの書き込みの分まだわずかに残っている。VBlank中へ持っていけば消せるのかもしれないが面倒なのでひとまずこれでよしとする。
    画像を載せようかと思ったが上のと全く同じなのでやめる。
    上のNESファイルは更新した…はず。エミュレータで見ても表示は同一なので確認が難しい。

    あと全画面任意画像の方にも同じバグがあったので合わせて修正した。  

  • ポケットプリンタ制御
    2018年12月22日 17:40

    この記事はGame Boy Advent Calendar 2018の22日めです。

    最近ゲームボーイのプログラミングがマイブームだ。
    ところでポケットプリンタってあるよね。
    動く機械って萌えるよね。

    というわけでやってみた。
    使うのはもちろん使い慣れたPICマイコン。
    ちょうど最近電子ペーパーを試すのに使っていたPIC16F1508が手頃だったのでこれを使うことにした。(電子ペーパーについてもブログに書こうと思っているのだが…)
    なお固有の機能は特に使っていないのでEnhancedミッドレンジならどれでも容易に移植できるはず。SPI送信のみとかわざわざ機能調べるほうがめんどい。

    ポケットプリンタ(海外名はGameboy Printer)の制御方法の情報は下記2サイトによくまとまっている。
    In Depth: The Game Boy Printer
    https://shonumi.github.io/articles/art2.html
    Furrtek.org : Reversing GameBoy Printer
    http://furrtek.free.fr/?a=gbprinter (原文フランス語)
    http://furrtek.free.fr/?a=gbprinter&i=2 (英語; 未翻訳部あり)

    さらに実際の送受信内容のダンプが公開されているのが嬉しい。
    http://furrtek.free.fr/noclass/gbprinter/hexdump.txt
    チェックサムが「1バイトごとの和を2バイトで」という珍しい形式なのでこれが無ければ理解は困難だっただろう。

    これらサイトの情報を元に、「最低限動くプログラム」を作った。
    最初のプログラムというものは単純なら単純なほどよい。バグも入りにくいし、他人も参考にしやすい。

    具体的には、
    ・印刷する画像サイズは最小の160×16(ヘッドの1行分)
    ・上下のマージンは手動でフィードすればよいので0
    ・データの圧縮はしない(そもそも白黒でないとほとんど圧縮されなそうだし、圧縮しなくても送信に1秒掛からないし)
    ・データは送信のみ、プリンタからの返答は見ない
    ・statusコマンドのポーリングによる印刷終了判定もしない
    ・各種データはプログラムに直書き
    ・EnhancedミッドレンジPICのデータは14bit中8bitしか使えない簡易な読み方と全部読める読み方があるが、楽な前者
    ・電源ONで自動で1回だけ送信
    といった感じ。

    用紙は感熱紙自体は容易に手に入る。サイズが合わないのでノコギリで切って…
    http://furrtek.free.fr/?a=gbpcable&i=2
    「No paper ? Take a used receipt」
    なるほどその手があったか!
    不要なレシートならそのへんに落ちている。

    電池を用意し(多いなあ…)
    電池6本

    以前GBAと通信を試したときに通信ケーブル変換コネクタをばらして作った線でつないで(コネクタが入手できなければ本体をばらして線をつなぐのが早いと思う)
    GB通信ケーブルからつなぐ線

    …動かない。
    色々チェックし見つけたバグを修正したがやはり動かない。

    出力は確かに出ている。クロック線とデータ線も間違っていない。
    簡略化のためにプリンタからの応答を見なかったのはまずかったか…とも思ったがそれ以前にプリンタからのSO(Serial Out; 分かりやすくいえばMISO)線に何も出ていない。
    何も出ていないというのはつまり、常にHigh-Zになっている。
    出力していない時の信号が不定でHigh-Zにするのはまだ分かるとして、[furrtek]によれば何か信号を入れたら00が返ってくるように書かれているのだが。

    線にLEDをつないでのデバッグではこのくらいが限度だ。
    仕方ない。重い腰を上げオシロスコープを引っ張り出してきて(先日のPCのクラッシュで制御ソフトが消えていたのでインストールもして)確認してみる。
    PICからポケットプリンタへの信号
    想定どおりである。(電源の都合上この時は電圧が違うが本番は5Vで動かしている)
    ポケモンカードGBから印刷して実機の信号も見てみる。
    ゲームボーイからポケットプリンタへの信号
    こちらも想定どおりである。困った。

    実機と違うところといえば、以下2点。
    速度は[furrtek]には「1kHz以下(もっといけるかも)」くらいに書いてあったので、1kHz以下でプログラムが簡単なところとして約650Hz。
    実機の速度は約8kHzだが、ふつう同期シリアル信号というものは遅い分には問題が無いものだ。
    怪しいのは電源投入時だ。線をつないでから
    プリンタ電源ON→PIC電源ON→300msほど待つ→送信開始
    というシーケンスで操作しているが、このPIC側電源投入時にクロック線が暴れて不正なデータが入っているのかもしれない。
    しかし、先頭のマジックバイト8833というのはこの辺の同期ズレをリセットするためのものではないのか?
    つまり、電源投入時から(バイト単位でなく)ビット単位で信号を見て「1000100000110011」のパターンがあったらそこで同期するという仕組みではないのか。

    まあ他に打つ手も無いのでこの2つを修正してみることにした。
    通信速度は8kHz弱に。念のため上の画像でも見える1バイトごとに半bit分くらい止まる部分も実機に合わせた。
    PIC電源投入時の待ち時間を5秒ほどに伸ばし、電源投入シーケンスを
    PIC電源ON→(5秒の間に)プリンタ電源ON→送信開始
    とした。

    すると、
    ポケットプリンタ_ハローワールド
    成功!

    どちらが効いたのか片方づつ試すと、なんと両方とも必要だった。
    650Hzの速度では、全く反応しない。
    電源投入時のシーケンスを最初のものに戻すと、まれに成功するもののほとんど反応しない。(20回ほど試し2回成功)
    再び両方修正後のコードで何度か試すと、ほぼ確実に成功する。(失敗は接触不良か?)

    これは不思議である。
    遅い信号を弾くのはノイズを信号と判断しないためではないのだろうか。そうであれば300ms空けたところでリセットしていてほしい。
    一方そうでないなら、挿抜時のノイズを無視するためにマジックバイトがあるのではないのだろうか。そうであれば挿抜時にどんなノイズが乗っても正しく信号を受けてほしい。

    何か間違っているかもしれないが、おそらく動くようにはなった。
    もっとちゃんとした画像表示もそのうちやりたい。

    [余談]
    ・折角買った感熱紙だし切った。
    感熱紙のロールをノコギリで切った
    …55分掛かった。間に1時間の休憩を入れて。
    学研のふろくじゃなくまともなノコギリを持っておいたほうがいいな。

    ・画像データは以下のようにして作った。
    GIMPで描く
    ハローワールド
    ペイントで8×8ドットごとに並び替える(背景色を変えるとやりやすい)
    ハローワールド_並び替え
    上下反転・色反転、GIMPでトーンカーブを使って上位bitと下位bitのデータを作る
    bit分割
    モノクロビットマップで保存
    バイナリエディタで開く
    テキストエディタで適宜置換

    ・何度か試したの図↓
    何度か試した

    コード:

        list p=16F1508
        #include p16F1508.inc
        radix dec

        ;     Vdd +-v-+ Vss
        ;     RA5 |   | RA0/DAT
        ;     RA4 |   | RA1/CLK 4_1
        ;1_0 (RA3)|   | RA2 CLC1
        ;     RC5 |   | RC0 CLC2
        ;21/4 RC4 |   | RC1
        ;2_0  RC3 |   | RC2
        ;3_1  RC6 |   | RB4 3_0
        ;1_1  RC7 |   | RB5 4_0
        ;CLC3 RB7 +---+ RB6

        __CONFIG _CONFIG1, _FOSC_INTOSC & _WDTE_OFF & _PWRTE_OFF & _MCLRE_OFF & _CP_OFF & _BOREN_OFF & _CLKOUTEN_OFF
        __CONFIG _CONFIG2, _WRT_OFF & _STVREN_OFF & _BORV_LO & _LPBOR_OFF & _LVP_OFF

    #define DATPIN 4
    #define CLKPIN 5

        cblock 0x20

        endc
        cblock 0x70
            cntl, cnth, suml, sumh
            dat, bitcnt, waitcnt, portbuf
        ;    temp, temp2
        endc

        org 0
        goto init

        org 4
    init:
        banksel OSCCON
        movlw b'01101010'
        ;        ^^^^ || IRCF 1101=4MHz (※クロックを4MHzにすると1命令1μsで計算しやすい)
        ;             ^^ SCS SystemClockSelect 1x = internal
        movwf OSCCON

        banksel 0
        clrf PORTC
        clrf PORTB
        movlw b'00110000'
        movwf PORTA

        banksel TRISC
        clrf TRISA
        clrf TRISB
        clrf TRISC

        banksel ANSELC
        clrf ANSELC
        clrf ANSELB
        clrf ANSELA

        banksel 0

        clrf cntl
        clrf cnth
        clrf suml
        clrf sumh
        clrf dat
        clrf bitcnt
        clrf waitcnt
        clrf portbuf

        ;少し待つ
        ;//65536*5=327 680
        ;11*65536*7=5046272
        movlw .11
        movwf waitcnt

        decfsz cntl,F
        goto $+2
        decfsz cnth,F
        goto $+2
        decfsz waitcnt,F
        goto $-5

        ;init
        movlw .4
        movwf cntl
        movlw .1 ;プログラムの都合で+1
        movwf cnth
        movlw high(cmd_init)
        movwf FSR0H
        movlw low(cmd_init)
        movwf FSR0L
        call sendcmd

        ;データ
        movlw 0x84
        movwf cntl
        movlw 0x02+1
        movwf cnth
        movlw high(cmd_data)
        movwf FSR0H
        movlw low(cmd_data)
        movwf FSR0L
        call sendcmd

        ;空データ
        movlw .4
        movwf cntl
        movlw .1
        movwf cnth
        movlw high(cmd_data0)
        movwf FSR0H
        movlw low(cmd_data0)
        movwf FSR0L
        call sendcmd

        ;print
        movlw .8
        movwf cntl
        movlw .1
        movwf cnth
        movlw high(cmd_print)
        movwf FSR0H
        movlw low(cmd_print)
        movwf FSR0L
        call sendcmd

        goto $ ;終了

        ;コマンド送信
        ;具体的には、「88,33,本体,チェックサム,ダミー×2」を送信
    sendcmd:   
        clrf suml
        clrf sumh

        ;magic byte 88,33
        movlw 0x88
        call sendbyte
        movlw 0x33
        call sendbyte

        ;本体
    sendcmdloop:
        moviw FSR0++
        addwf suml,F
        btfsc STATUS,C
        incf sumh,F
        call sendbyte

        decfsz cntl,F
        goto $+2
        decfsz cnth,F
        goto sendcmdloop
        ;チェックサム
        movf suml,W
        call sendbyte
        movf sumh,W
        call sendbyte
        movlw 0
        call sendbyte
        movlw 0
        call sendbyte
        return

        ;1バイト送信
        ;相手はクロック立ち上がりで読む
    sendbyte:
        movwf dat

        movlw .18 ;タイミングを現物合わせ
        movwf waitcnt
        decfsz waitcnt,F
        goto $-1

        movlw .8
        movwf bitcnt
        clrf waitcnt
        ;DAT,CLKをlowにした値を用意
        movf PORTA,W
        andlw ~((1<<DATPIN)|(1<<CLKPIN)) ;b'11001111'
        movwf portbuf
    sendbyteloop:
        movlw .18 ;タイミングを現物合わせ
        movwf waitcnt
        decfsz waitcnt,F
        goto $-1

        movf portbuf,W ;読み込み
        rlf dat,F
        btfsc STATUS,C
        iorlw 1<<DATPIN ;データが1ならDATをhigh
        movwf PORTA ;書き込み

        movlw .20 ;タイミングを現物合わせ
        movwf waitcnt
        decfsz waitcnt,F
        goto $-1

        bsf LATA,CLKPIN ;クロック操作
        decfsz bitcnt,F
        goto sendbyteloop
        return

        ; magic 8833
        ;   cmd   comp  len L/H     sum L/H     dummy
    cmd_init:
        ;dt 0x88, 0x33,
        dt 0x01, 0x00, 0x00, 0x00; 0x01, 0x00, 0x00, 0x00
    cmd_init_len equ $-cmd_init ;やっぱりめんどいので直書きにする
    cmd_print:
        dt 0x02, 0x00, 0x04, 0x00
        dt 0x01, 0x00, 0xE4, 0x40
        ;  ???? margin palt exposure
    cmd_status:
        dt 0x0F, 0x00, 0x00, 0x00
    cmd_data0:
        dt 0x04, 0x00, 0x00, 0x00
    cmd_data:
        dt 0x04, 0x00, 0x80, 0x02
        dt 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x01,0x03,0x03,0x03,0x07,0x07,0x07
        dt 0x28,0x70,0xFC,0x78,0xFC,0xFE,0xFC,0xFE,0xFC,0xFE,0xFC,0xFE,0xFE,0xFC,0xF8,0xFC
        dt 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0x3E,0x7C,0x7F,0xFE,0x7F,0x7F
        dt 0x00,0x00,0x03,0x00,0x2F,0x1F,0x1F,0x3F,0x1F,0x3F,0x1F,0x1F,0x0F,0x1F,0x07,0x0F
        dt 0x03,0x00,0x87,0x7F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xEC,0xF0
        dt 0x1E,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0x7E,0x7E,0x7E
        dt 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x01,0x7F,0x3F,0x7F,0x3F
        dt 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x0F,0x7F,0xFF,0xFF,0xFF,0xFF,0xFF
        dt 0x03,0x01,0x07,0x0F,0x0F,0x0F,0x8F,0x47,0xCF,0xE7,0xC7,0xE7,0xE7,0xE3,0xE3,0xC3
        dt 0xDF,0xEF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xF8,0xE0,0xE0,0xE0,0xE0,0xE0,0xE0
        dt 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x0F,0x0F,0x1F,0x0F,0x1F,0x0F,0x1F,0x1F
        dt 0xC0,0xE0,0xC0,0xE0,0xC0,0xE0,0xE0,0xC0,0xE0,0xC0,0xCE,0xC1,0xFF,0xDF,0x9F,0xDF
        dt 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x1F,0xFF,0xFF,0xFF,0xFF,0xFF
        dt 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0xF8,0xF8,0xF0,0xF8,0xF0,0xF0,0xF0
        dt 0x01,0x03,0x01,0x03,0x01,0x03,0x01,0x7B,0x79,0x7B,0x79,0x7B,0x79,0x7B,0x79,0x7B
        dt 0xF8,0xF8,0xF8,0xF8,0xF0,0xF8,0xF8,0xF0,0xF8,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0
        dt 0x03,0x01,0x03,0x01,0x0B,0x01,0x11,0x09,0x39,0x19,0x18,0x39,0x78,0x39,0x39,0x78
        dt 0xFF,0xFE,0xFF,0xFE,0xFF,0xFE,0xFF,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFF,0xFF
        dt 0x00,0x01,0x00,0x01,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x0F,0xFF,0xFF,0xFF
        dt 0xE7,0xEF,0xE7,0xEF,0xEF,0xE7,0xEF,0xE7,0x00,0x00,0x00,0x00,0x80,0x00,0x80,0x80
        dt 0x07,0x0F,0x0F,0x0F,0x0F,0x1F,0x3F,0x1F,0x3F,0x3F,0x3F,0x7F,0xFF,0x7F,0x7F,0xFF
        dt 0xFC,0xF8,0xFC,0xF8,0xF0,0xF8,0xF0,0xF8,0xF8,0xF0,0xE0,0xF0,0xE0,0xE0,0xC0,0xE0
        dt 0x3F,0x7F,0x7F,0x3F,0x3F,0x3F,0x3F,0x1F,0x0F,0x1F,0x0F,0x0F,0x07,0x0F,0x07,0x07
        dt 0x07,0x8F,0xC7,0x87,0xA7,0xC3,0xE3,0xC3,0xE1,0xE3,0xF1,0xF1,0xF9,0xF0,0xF0,0xF8
        dt 0xF0,0xE0,0xE0,0xF0,0xF0,0xF0,0xF8,0xF1,0xFF,0xFF,0xFF,0xFF,0xFE,0xFF,0xF8,0x70
        dt 0x7E,0x7E,0x7E,0x7E,0x7E,0x7E,0x7C,0xFE,0xFC,0xFE,0xFC,0xFC,0x60,0x80,0x00,0x00
        dt 0x3F,0x3F,0x1F,0x3F,0x3F,0x1F,0x14,0x0F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
        dt 0xFF,0xFF,0xFC,0xFF,0xE8,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
        dt 0xC1,0xC3,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
        dt 0x80,0xE0,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x01,0x0F,0x07,0x1F,0x1F
        dt 0x3F,0x1F,0x1F,0x3F,0x3F,0x7F,0xFE,0x7F,0xFE,0xFE,0xFE,0xFC,0xFA,0xFC,0xFC,0xF8
        dt 0xDF,0x8F,0x07,0x8F,0x8F,0x07,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
        dt 0xFF,0xFF,0xFB,0xFC,0x90,0xE0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
        dt 0xE0,0xF0,0x01,0x00,0x01,0x01,0x01,0x03,0x03,0x07,0x0F,0x07,0x0F,0x3F,0x7F,0x7F
        dt 0x79,0xFB,0xF1,0xFB,0xF1,0xFB,0xF9,0xF3,0xF9,0xF3,0xE1,0xF3,0xF1,0xE3,0xE1,0xE3
        dt 0xE0,0xF0,0xE1,0xF0,0xE3,0xF1,0xE7,0xF3,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
        dt 0x79,0xF8,0xF8,0xF8,0xF8,0xF8,0xF8,0xF8,0xF8,0xF8,0xF8,0xF0,0xC0,0xE0,0x40,0x80
        dt 0xFF,0xFF,0xFF,0xFF,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xF8,0xFC,0x78,0xFC
        dt 0xFF,0xFF,0xFF,0xFF,0x1F,0x7F,0x03,0x07,0x02,0x03,0x00,0x00,0x00,0x00,0x00,0x00
        dt 0x80,0x80,0x00,0x80,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x55,0xAA ;※末尾の55AAはデバッグ時に使ったものの消し忘れ

        end

    (2019/09/03 修正) コード中のシフト演算子がHTMLタグ扱いで消えていたのを修正しました。  

  • ゲームボーイの電池交換に導電インク
    2018年03月25日 03:01

    ファミコンやゲームボーイなど昔のゲームソフトにはデータの保持にボタン電池が使われている。
    (最初に知った時には驚いた。壊れなければ永遠に使えると思っていたゲームソフトにまさか寿命のある電池が入っているとは。しかもちっぽけなボタン電池。すぐにでも切れてしまいそうではないか。)
    古いソフトの電池を交換する上で何が問題かといえば配線の接続方法だ。
    元々はスポット溶接で留められている。これなら確実に導通がとれるだろう。しかし家庭にスポット溶接機はない。どうやって留めるか。
    まっとうな方法は既にタブが溶接された電池を買うことだ。電池ホルダーを使うのもよいだろう。
    しかしなぜか買う気にならない。その辺で売ってないとか割高とかの理由もあるが、たぶん専用品を使うのは負けた気がするという気持ちの問題が大きいと思う。

    溶接以外の方法をいろいろ試してみるのだが、なかなかうまくいかない。溶接の素晴らしさが分かる。
    リード線をテープで留めただけだと、すぐに接触不良でデータが飛ぶ。
    何かしっかりとテンションを掛けられる手段がないだろうか。
    リード線を熱収縮チューブで固定してテンションを掛ければどうかと試してみたが、駄目であった。しばらくしてデータが飛んだ。
    また厚みでケースに微妙に収まらず、無理やり閉じたらケースが膨らんだ。(もっともこれはCR2032を使ったのが悪かった。1616にすれば大丈夫だったかもしれない)
    もっとしっかりとテンションが掛かる物を考えるとクリップが思いつくが、GBソフトの狭いスペースに入るものはなかなかないだろう。
    電池に直接ハンダ付けするという最終手段もあるが、アルカリ電解液の詰まった電池に高熱を与えるのは大変危険なのであまりやりたくない。またそもそも材質がハンダをはじくのであまりしっかりと付いてくれない。

    そんなわけでなかなかこれといった方法が見つからなかったのだが、ふと導電インクが使えるのではないかと思い立った。
    以前何かに使えるかと思って買ったものの何にも使っていなかったものだ。
    「Bare Conductive Electric Paint」という名のもので、調べたら同じものがAmazonで売っている

    試してみると、予想以上によい。
    この種のカーボンタイプの導電インクは回路を描くには少々抵抗が大きいのだが、今回導線と電池表面の間のわずかな隙間を埋めるだけなので抵抗は無視できる。
    無視できる…と思うが念のため計ってみると30Ω。思ったよりはあるな。
    しかし電流はわずかなので抵抗による電圧降下は気にする必要はないだろう。計算したら1mVもないくらいだし実際計っても分からない。

    そして接着力だが、結構ある。剥がそうと思って力を入れれば線を引っ張って剥がしたり爪で引っ掻いて剥がしたりはできるが、意図せず剥がれるようなことは無さそうな程度にある。
    付きにくいハンダ付けよりはるかに信頼できる。

    最初に試したものがこちらだ。
    導電インクでGB電池接続_1個め
    導線の太さで厚みが出て、熱収縮チューブのときほどではないものの微妙にケースが膨らんでしまった。
    このあたりでそもそもGBソフトに使われている電池はCR2032でなくCR1616であったことに気づいたが、買いに行くのは面倒だし、うまくすれば入りそうなのでそのまま2032を使い続けることにした。
    もう1点問題があり、基板上に電池を固定するために斜めにテープを張ったのだが、カートリッジの上蓋は枠が出っ張っているので閉める時引っかかってしまう。

    それを踏まえて2個め。
    厚みを抑えるために芯線をバラして接着することにした。カプトンテープで絶縁をとってある。
    この方が抵抗をより抑えられそうな気がするし。しかし残念ながらこちらの抵抗は測り忘れてしまった…。
    導電インクでGB電池接続_2個め1
    基板にはんだ付け。カートリッジのケースに入れてみると問題なく閉まった。
    導電インクでGB電池接続_2個め2
    固定はカプトンテープでは上手くできず、グルーガンで留めようかと思っていたのだが、ただ閉じるだけで振ってもガタつくこと無く一応固定されているようなのでそのままにしてしまった。

    1個めが2ヶ月半、2個めも1ヶ月ほど経つがどちらもデータを保持できている。

    2018/12/22:
    ふと思い出して確認したところどちらもまだデータを保持できている。

    2020/11/16:
    ふと思い出して確認したところ、1個めはデータが消えてしまっていた…。カートリッジコネクタの接触不良で何度か挿し直したのが悪影響を与えたかもしれない。新しくデータを作成し数分後確認すると正常。
    2個めはデータを保持できている。

    2021/10/21:
    ふと思い出して確認したところどちらもデータを保持できている。(1個めのは2020/11/16に消えて作り直したデータ)
    何もしなければデータがもつことの確認はもう十分かな。せっかくなので開けて見てみよう。
    3年半経ってどう変わったか。
    3年半後_1
    3年半後_2
    うーん…見てもよく分からない。
    テープも取ってみるか…
    3年半後_3
    うむ、特に接着面が剥がれかけてるとかそういうことは無さそう。(テープに付着しているが、乾く前にテープを貼ったので不思議はない)
    バックアップ電池の交換にカーボン系導電インク、なかなか良さそうだ。
      

  • ゲームボーイの吸い出し機を作った (後編)
    2017年01月16日 22:44

    前編の続き。プログラム側について。

    まずは単純に読むことを試みる。
    手持ちの中でバンク切り替えなし(32kB)のソフトとしてDr.マリオを選択。

    バンク切り替えが無ければアドレスを出してデータを読むだけ。
    …とはいえCLKとかCSとかRDとかどう制御すればいいのか。
    GBのカートリッジの仕様くらいいくらでも見つかると思っていたのだが、ROMの読み出し方法は常識なのか、細かい解説が意外と見つからない。
    結局、分かってみると単純で、
    /RDがLにアサートされている間、アドレスピンで指定されたアドレスのデータが、データピンに出る
    というだけのことだった。
    つまり読み出すには、/RDをLにアサートしたままアドレスを順次変え、データピンを読むだけでよい。
    これが分かるまでに/RDをH/L切り替えてしていた。
    あと/CSはSRAMを読む時アサートするものだった。

    プログラミング自体もなんだかんだで苦労した。
    やはり合っているか分からない操作を正しく組めているか分からない機械にプログラムするのは疑心暗鬼に陥ってだいぶ精神を消耗する。
    まずシリアル通信するところからしてうまくいかない。
    シリアルポートが全く反応しなくて焦った挙句Windows10へアップグレードした時にケーブル類全部抜いてたのを戻してなかっただけだったりもした。
    何か出るようになったと思えば文字化けしている。これはどうもTeraTermのバグかWindows10との相性のようで、最新版を使ったら正常であった。
    新しいPICを使う際には毎回のようにGPIO以外の機能を切っていなくて問題が起こるのだが、今回もまんまとその罠にはまった。
    ADコンバータとコンパレータを切ったまでは良かったのだが、それで読もうとしても何も読めない(FFが読める)。
    ポートのレジスタを直接インクリメントしてたのがまずかったかと思い、別の変数をインクリメントしてそれを出力するようにしたところ何かは読めるようになったが、全体にわたりほぼ確実に8バイトづつ同じ値が取れる。
    つまりアドレスの下3bitが何かおかしい。そのピンを調べると、LCDドライバの電圧生成機能がデフォルトでONであった。
    ということはポートのレジスタを直接インクリメントすること自体は問題なく、最下位bitの書き込みが無視されていたせいでインクリメントできていなかったんだな。

    というわけでついにDr.マリオが読めた。
    Dr.マリオ

    次はバンク切り替え…の前に色々なソフトをバンク0だけ読んでみることにした。
    するとポケモンYellowやポケモンカードなど読めるものもあるが、ポケモンSilverが読めない。
    読めないというのは、ほぼ全てFFが返ってくる。ごく稀にFF以外のものが返ってくることもあるのがまた不可解である。
    FFでない箇所のパターンは規則的で、なにかありそうである。2進数で「xxxx xxx1 0000 000x」と「xx00 0110 0000 0001」、つまり0100, 0101, 0300, 0301,…と0601, 4601がFFでない。
    不要なはずのクロックだがMBCを積んでいることもあり何か変わるのではないかと入れてみる。当然変わらず。
    …散々悩んだ挙句、電圧不足であった。
    いつもPICを動かす時はEneloop2本(2.6V程度)を使っていて、使いやすい5V電源を持っていないこともあり、とりあえずそれでやっていたのだが、GBの電圧は5Vなので動かなくてなんの不思議も無いのであった。
    Dr.マリオを始めいくつかのソフトで(バンク0は)読めていたので発覚が遅れた。

    改めてバンク切り換えだ。
    バンクの少ない(最少の4バンク)ものとしてQIXを選択。
    バンク切り換えの手順はMBCによって少々違うが、基本的に特定のアドレスにデータを書き込むだけである。
    書き込むべきデータを入れていなかったり、PICのIOを入力のまま出力したつもりになっていたり、書き込むアドレスを間違ったりして手間取ったが、まあまあすんなりと読めた。
    QIX

    さて続いて本命のポケモンSilverだ。
    128バンクあるが、バンク切り換えのやり方は同じなので、単純に数が多いだけ。難しいことは何もない。
    しかしなぜか途中でデータが飛ぶ。読み取ったデータを見るとファイルサイズが想定より小さい。
    今までこのような大量のデータをシリアルで受信したことは無かった。シリアル通信の信頼性はこの程度なのだろうか。
    だがそれは想定の内。1バンクごとに目印を入れてあるのでどこで抜けたかは分かる。何度か読んで正常な部分を切り貼りすればよいだろう。
    …と思っていたのだが、不思議な事に常にエラーが出ている場所がある。
    バッファ切れを疑ってバッファ量を変えたりウェイトを入れたりしてもなんとなく変化はあるものの直らず。
    データの問題かと思いXOR 0x55したデータを送ってみると抜けの量はほぼ変わらず、抜けの位置が変わった。特定のデータが来ると問題が起こるのだろうか。
    速度を落としてみるとだいぶ改善した。抜けが6バンクまで減ったので試しに起動してみると、一応起動はした。
    ポケモンSilver_データ抜け1ポケモンSilver_データ抜け2
    このような分かりやすいバグった表示になるものなんだなあ。
    なお部屋に出口がないので進めなかった。その後もう1バンク正常に取れたのでそれと合わせると部屋から出られたがBGMが異常になったりする。
    しかしここで何度とってもほぼ同じ場所でエラーを起こす。
    やはり速度を落とすだけでは解決しない。特定のデータが問題という線で考えてみよう。
    改行の処理に時間が掛かっている可能性を考えてCRの後にウェイトを入れてみる。むしろ悪化。
    あとは…エスケープシーケンス。何らかのエスケープシーケンスが来るとそれの処理に時間がかかってデータを取りこぼすのではないか。
    ここでTeraTermを調べて、受信した文字をすべて表示するデバッグモードがあることを知る。
    デバッグモードで受信するようにしたところ、一切取りこぼさなくなった!
    ポケモンSilver
    後で調べたところ、制御シーケンスに「1B 63: 端末リセット」というものがあるらしい。
    つまりこのバイト列が来るとTeraTermがリセット動作を起こし、その間に来たデータを取りこぼしていたようだ。
    調べてみると「1B 63」は最後まで読めなかった5バンク中の3ヶ所にあった。残り2ヶ所やそれ以前のエラー箇所は分からないままだがたぶん他のエスケープシーケンスだろう。
    デバッグモード以外にエスケープシーケンスを無視する方法がないか調べたのだが、見つからなかった。
    人が読む文字列を出す時は改行は使いたいのだが、致し方ない。

    次に困ったのがX(エックス)だ。バンク切り替えができない。
    調べてみるとこれに使われているMBC2はバンク切り替え時に書き込むアドレスに制限があり、今まで使っていた0x2000では駄目だった。
    と0x2100に変えてみたが、やはりバンクは切り替わらない。
    そこで読み取れたバンク0のコードを見てみることにした。この中にバンク切り替えのコードがあるはずである。
    するとやはり0x2100に書き込んでいる。
    合っているのにおかしいなと思い更に調べると、バンク1に変更する時は0x2100だったのだが、バンク2では0x2101、バンク3では0x2102…と、バンクNに変更する時0x2100+(N-1)に書き込むようになっていた。
    1少ないのは書き込み後のインクリメントの関係だろう。ということでバンクNに変更する時は0x2100+Nに書き込むようにコードを書き換えてみると、見事読み取りに成功した。
    なんだろう。バスコンフリクトだろうか。ファミコンのMMCでバスコンフリクトを起こすものがあるという情報はあるが、GBで起こるというのは見たことがなかった。
    X(エックス)

    さて次はニンテンドウパワーのGBメモリの読み取りを試みている。
    これは複数のMBCの動作を再現する特殊なコントローラを積んでおり、普通のバンク切り換えとは異なるコマンドを入れる必要があるらしい。
    色々試しているのだがまだ一切反応がない。一番つらい時期だ。
    読み取りができたら、どうも書き込みも出来るらしいのでやりたい。自作ソフトを実機で動かすのは夢である。

    ただその前に、どうも読み取りが安定しないのでどうにかしたい。
    今まで読めていたソフトでも読めなくなったりしている。
    断線しかかっているとかだろうか…。  

  • ファミコンで全画面に任意の画像(ただしモノクロ)を表示
    2017年01月14日 00:00

    ファミコンは普通には全画面に自由に画像を表示できないことはよく知られている。
    でもモノクロ2値ならできることに気づいたのでやってみた。
    ファミコン全画面_猫耳とりあえず猫耳。
    ファミコン全画面_羽根っ娘酉年らしく羽根っ娘。
    ファミコン全画面_漢字全画面任意なのが分かりやすい図柄も。

    まず前提として、ファミコンのBG(背景)面は32×30=960個のキャラクタで構成されるが、BGの(スプライトも)キャラクタパターンの指定は1バイトなので、一度に256種類からしか選べない。
    よって普通にはBGだけでは256キャラ、画面の1/4強しか埋められず、スプライトを8×16ドットモードで最大の64個使って128キャラ用意してやっと384/960=40%にしかならない。
    何らかのマッパーを使えば描画中にバンク切り替えで全画面を別のキャラクタで埋められるが、マッパーを使ってできるのは面白くないので今回考えないことにする。

    そこで今回の手法だが、パレットの色分けとキャラクタテーブルの描画中切り替えで実質キャラ数を4倍に増やしている。
    まずパレット。
    ファミコンのキャラごとの色数は4色だが、これを2色しか使わなければ例えば「白黒白黒」と「白白黒黒」の2種類のパレットを左右で使い分けることで1つのキャラで2つの絵を表示することができる。
    00011011

    0111111111111100
    1111111111111111
    1111000000001111
    1111010101011110
    1111111111111111
    1111101010101111
    1111010101011111
    1111010101011110
    00011011

    0111111111111100
    1111111111111111
    1111000000001111
    1111010101011110
    1111111111111111
    1111101010101111
    1111010101011111
    1111010101011110
    00011011

    0111111111111100
    1111111111111111
    1111000000001111
    1111010101011110
    1111111111111111
    1111101010101111
    1111010101011111
    1111010101011110
    比較のために単一のパレットで表示したものがこちらだ。
    単一パレット表示

    次にキャラクタテーブル。
    「一度に256種類からしか選べない」と書いたが、この「一度に」の部分がポイントだ。
    仕組みを詳しく言うと、定義できるキャラは256個の領域が2つあり、BGと8×8のスプライトはそれぞれその2つのどちらの領域からキャラを選ぶかを設定できる。同じ領域をBGとスプライトの両方で使うこともできる。
    (なお今回関係ないが8×16のスプライトは一度に2キャラを使うので、どちらかの領域から選ぶのではなく、2つの領域を合わせた512キャラを2キャラづつの256組と見たものから選択する)
    ここでこの「どちらの領域から選ぶか」の設定は画面の描画中にも切り替えることができる。これにより上半分と下半分で別のテーブルからキャラを選ぶことが可能なのだ。
    比較のためにこの切り換えを行わなかったものがこちら。
    切り換えを行わない表示

    基本的なアルゴリズムは以上だ。
    実際のコードは下に置いたが、少々細かい解説を加えておく。

    今回CHR-ROMでなくCHR-RAMを使っている。
    そのためまず最初にPRG-ROMに持っている画像データを元にCHR-RAM上にパターンテーブルのデータを生成している。
    これは事前に適切な並びにしたデータをCHR-ROMに持っておけば必要ない操作だ。
    ただ、この並べ替えのプログラムは、ファミコン自体で行うか外部で事前に行うかの違いでどうせ書かなければならない。
    ならば1つにまとめた方が扱いが楽なのでこうした。
    下に書いたコードと、任意のモノクロビットマップを用意するだけで、NESASMでビルドできる。

    このパターンテーブル書き込みのコードは少々複雑だが、やっていることは単純に、256×240ドットの1bitビットマップを適切な順に並び替えているだけだ。
    パターンテーブル前半0x0000~0x0FFFが画像の上128ドット、後半のうち0x1000~0x0DFFが画像の残り。0x1F00~0x1FFFには後述の通り0x0F00~0x0FFFと同じものを重複して配置している。

    「どちらの領域から選ぶか」の設定、つまりパターンテーブルのベースの切り換えだが、このような描画中のタイミング合わせはファミコンではふつう0番スプライトヒットフラグをポーリングすることで行う。
    だが今回別の方法をとった。
    0番スプライトヒットはBGとスプライトが共に不透明な点でしか起こらない。つまり画像の当該箇所が透明なとき使えない。
    今回任意の画像を表示できるようにしようとしているのでこれは困る。
    代わりにあまり知られていないスプライトオーバーフローフラグを使った。
    これはライン上にスプライトが9個以上並んで横並び数制限を越えたときに立つはずだったフラグだ。
    このフラグはスプライトが透明でも変わらず立つので、BGの内容に関係なく使えて便利だ。
    ただ重大な欠点があって、このフラグ、アドレスの桁上がりの誤りで別のデータを参照するせいでまともに動かないという、致命的かつしょうもないバグが存在する。
    使い物にならないと思うかもしれないが、バグの条件は厳密に判明している。
    詳しくはNesDevの「PPU sprite evaluation」のページを読むとよいが、
    スプライト番号順にライン内に存在するかをチェックし、8つめが見つかった次のチェックまでは正しく、その次から誤ったアドレスを参照しだす。
    ここから、
    ・スプライト数が7つ以下なら、必ずフラグは立たない
    ・スプライト数が9つ以上の時、8つめと9つめのスプライト番号が隣り合っていれば、必ずフラグが立つ
    ということが言える。
    なので今回はスプライト番号順に連続した9つを目的の場所に置くことで確実にスプライトオーバーフローフラグを立てることにした。
    というか残りも面倒なので同じ位置に置いた。

    切り換えタイミングだが、画像を単純に上下に分割した場合、きっちりHBlank中に切り換えを起こすようにしないとこのように画面が乱れてしまう。
    ファミコン全画面_画面乱れ
    そのレベルで合わせるには描画中のスプライトの判定処理の流れをきちんと理解してコードを書かねばならないが、今回面倒なのでそこまではしなかった。
    その代わり、1列分(8ライン)のキャラをパターンテーブル前半と後半で重複させておいた。これでこの8ラインの中で切り替えれば画面を乱さないようにできる。

    画像データのインクルード部分は「画像データ(生)」と「画像データ(BMP)」の2種類を用意した。(生)は256×240bitの生データを使う時用で、(BMP)はWindowsのMSペイントで出力したBMPファイルをそのまま使えるようにヘッダ分だけずらした位置に配置するものだ。
    これを使う時は、まずペイントで適当に描いた絵を上下反転
    ペイントで描いて上下反転
    モノクロビットマップで保存して
    保存
    できたファイル名をソースに書き込んでビルドするとできあがり。
    ビルドした表示
    ぜひ試してみてほしい。

    ; 全画面に任意のモノクロ2値のビットマップを表示する
    ; NESASM v3.1でビルド、NNNesterJとNintendulatorで動作確認している。

        ; INESヘッダー
        .inesprg 1 ; PRG-ROM容量(0x4000=16KB単位)
        .ineschr 0 ; CHR-ROM容量、CHR-RAMでは0
        .inesmir 0 ; Don'tCare(水平ミラーリング)
        .inesmap 0 ; マッパーなし

        .bank 0

    ; レジスタ名定義(してたりしてなかったり、してるのに使ってなかったり)
    PPUMASK = $2001
    PPUADDR = $2006
    PPUDATA = $2007

    ;(グローバル)変数定義
    ;RAMの連続したアドレスにラベルを付けたいだけなのだがどうもROMに値をとっている。
    ;どうせ上書きされる場所で実害ないからいいけど、もっとまともな方法があるのだろうか…。
    ;まあとりあえず今回はこのままでいいや。
        .org $0000
    chrptr .dw 0 ;CHR-RAM書き込み時のポインタ
    cnt1 .db 0 ;カウンタ変数
    sprpos .db 0 ;スプライト書き込み時のポインタ
    posx2 .db 0 ;何だっけ

    ;割り込みベクタ
        .bank 1     ; 0x2000(8KB)単位。ファイルが全16KBなのでbank3でなく1。
        .org $FFFA  ; BFFAだけど。

        .dw intvb   ; VBlank割り込み
        .dw init    ; リセット割り込み。起動時とリセット時
        .dw 0       ; ハードウェア割り込みとソフトウェア割り込み

        .bank 0
        .org $8000

    init:
    ; PPUレジスタに書き込みできるようになるまで時間がかかるらしいので待つ
        ldy #25
    .loop:
        dex
        bne .loop ;5*256=1280 ;30000 30000/1280=23.4375
        dey
        bne .loop

    .loop2:
        lda $2002  ; VBlank待ち
        bpl .loop2

        ; VBlankのNMIを無効
        lda #%00110000
        ;     ^||||||| VBlank開始時にNMI発生
        ;      ^|||||| PPUマスター/スレーブ
        ;       ^||||| スプライトサイズ 0:8x8 1:8x16
        ;        ^|||| BGパターンテーブル
        ;         ^||| スプライトパターンテーブルアドレス
        ;          ^|| VRAMアドレス増分 0:1 1:32
        ;           ^^ ベースネームテーブルアドレス 0:2000 1:2400 2:2800 3:2C00
        sta $2000
        lda #%00000110 ; 初期化中はスプライトとBGを表示OFFにする
        sta $2001

        ; パレットRAM=$3F00
        lda #$3F
        sta $2006
        lda #$00
        sta $2006

        ldx #$00
    loadPal:
        lda pallet, x
        sta $2007
        inx
        cpx #32
        bne loadPal

        lda #50
        sta posx2

    setBg: ;BGのネームテーブルを適宜セット
    ; 00,01,...0F,00,01,...0F
    ; 10,11,...1F,10,11,...1F
    ; ...
    ; F0,
    ; 00,
    ; ...
    ; D0,
        ; $2000: BG1 name table
        lda #$20
        sta $2006 ;PPU_ADDR
        lda #$00
        sta $2006
        ldx $00
        lda #30
        sta cnt1
        ldy #0
    bgLoop0:
        ldx #16
    bgLoop1:
        sty PPUDATA
        iny
        dex
        bne bgLoop1
        tya
        sec
        sbc #$10
        tay
        ldx #16
    bgLoop2:
        sty PPUDATA
        iny
        dex
        bne bgLoop2
        dec cnt1
        bne bgLoop0

    ;CHR-RAM書き込み
    setChrRam:
        lda #0
        sta chrptr
        lda #high(chr)
        sta chrptr+1
        ldy #$00
        sty PPUADDR
        sty PPUADDR
        ldx #30

    .loop
        lda [chrptr],Y
        sta PPUDATA

    ; 8: タイル縦px数 000yxxxx ~ 111yxxxx
    ;Y+=20
        tya
        clc
        adc #$20
        tay
        bcc .loop ; if no carry loop
    ; 2: 色bit 0000xxxx, 0001xxxx
    ;Y+=10
        tya
        eor #$10
        tay
        and #$10
        bne .loop ;if !y&10 loop
    ; 16: 横タイル数 00000000 ~ 00001111
        iny
        tya
        and #$0F
        bne .loop; if neq loop
    ; 30: 縦タイル数
        ldy #$00
        inc (chrptr+1)
        dex
        bne .loop

    clearChrRam: ;背景に使わない16キャラ(=256バイト)をゼロクリア (2個スプライトに使う)
    ;    PPUADDR = #$1E00
        lda #0
    .loop:
        sta PPUDATA
        dey
        bne .loop

    setChrRamOverlap: ;BGのベースを変える境目でオーバーラップさせておく
    ;    PPUADDR = $1F00
        lda #high(chr)+$0F
        sta chrptr+1
        ldy #0
    .loop
        lda [chrptr],Y
        sta PPUDATA

    ; 8:タイル縦 000yxxxx ~ 111yxxxx
    ;Y+=20
        tya
        clc
        adc #$20
        tay
        bcc .loop ; if no carry loop
    ; 2:パレットbit 0000xxxx, 0001xxxx
    ;Y+=10
        tya
        eor #$10
        tay
        and #$10
        bne .loop ;if !y&10 loop
    ; 16: 横タイル数 00000000 ~ 00001111
        iny
        tya
        and #$0F
        bne .loop; if neq loop


    ;属性テーブル (BGパレット)
        ldy #$23
        sty PPUADDR
        ldy #$C0
        sty PPUADDR
        ldx #8
    attrloop:
        lda #$00 ; 左半分 00 00 00 00
        sta PPUDATA
        sta PPUDATA
        sta PPUDATA
        sta PPUDATA
        lda #$55 ; 右半分 01 01 01 01
        sta PPUDATA
        sta PPUDATA
        sta PPUDATA
        sta PPUDATA
        dex
        bne attrloop

    ;scroll
        lda #0
        sta $2005
        sta $2005

        ; スプライト配置
        lda #$00
        sta $2003 ; OAMADDR

    ; Byte 0-3: Y,TileNo,Attr,X
    ; Attr:
    ;76543210
    ;||||||||
    ;||||||++- Palette (4 to 7) of sprite
    ;|||+++--- Unimplemented
    ;||+------ Priority (0: in front of background; 1: behind background)
    ;|+------- Flip sprite horizontally
    ;+-------- Flip sprite vertically

        ldx #64
    sprLoop:
        lda #$7B ; BGのベースの切り替わりあたり
        sta $2004
        lda #$E1 ; 1:E0 空スプライト
        sta $2004
        lda #$00
        sta $2004
        lda #$B0 ; 特に意味はない
        sta $2004

        dex
        bne sprLoop

        ; 表示開始
        lda #%00011110    ; スプライトとBGの表示をONにする
        sta $2001

        lda #%11110000 ;VBlank割り込みをON
        sta $2000

    waitInt:
        jmp waitInt


    intvb:
        lda #%11100000 ;BGパターンテーブルベース=0
        sta $2000

    waitVBlankEnd: ;VBlank終了時点でスプライトオーバーフローフラグが消えるのを待つ
        lda $2002
        and #$20
        bne waitVBlankEnd
       
    waitSprOvr: ;スプライトオーバーフローフラグが立つのを待ってBGパターンを切り替える
        lda $2002
        and #$20
        beq waitSprOvr

        lda #%11110000 ;BGパターンテーブルベース=1
        sta $2000

        rti

    ;色0・色1
    C0 = $0F
    C1 = $30

    pallet:
        .db C0, C1, C0, C1, C0, C0, C1, C1, C0, C0, C0, C0, C0, C0, C0, C0
        .db C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0
    ;    .db 0x03, 0x11, 0x15, 0x30, 0x03, 0x11, 0x15, 0x30, 0x03, 0x11, 0x15, 0x30, 0x03, 0x11, 0x15, 0x30
    ;    .db 0x03, 0x11, 0x15, 0x30, 0x03, 0x11, 0x15, 0x30, 0x03, 0x11, 0x15, 0x30, 0x03, 0x11, 0x15, 0x30

    ; 画像データ(生)
    ;    .bank 1
    ;    .org $A000
    ;chr:
    ;    .incbin "image.bin" ; 画像。256×240/8=0x1E00バイト

    ; 画像データ(BMP)
        .bank 1
        .org $A0C2
        .incbin "image.bmp"
        .org $A100
    chr:


    (2019/07/17 追記)
    CHR-RAMの初期化忘れで空白のつもりのスプライトがノイズとなって見えていたのを修正しました。
    やはり実機で動作させないと初期化忘れは見逃しがちである。
    最初に気づいたのは、これをベースに作った9×9ドットフォントの方で、こっちをまず修正した。
    しかし全画面表示の方はVRAMのほぼ全域に書き込んでいたので同じ問題は起こらないと思いこんでしまった。
    実のところ、スプライト用のメモリは初期化していなかったのだ。

    こちらが最初にわざわざ乱数(Xorshift)で初期化するコードを入れて再現したもの。
    初期化忘れ

    再現したところで修正して、乱数初期化を消して出来上がり。(エミュレータでの見た目は前と変わりない)
    初期化あり

    修正箇所はここを
    setChrRomOverlap: ;BGのベースを変える境目でオーバーラップさせておく
        ldy #$1F
        sty PPUADDR
        ldy #$00
        sty PPUADDR
    こう。
    clearChrRam: ;16キャラをゼロクリア
    ;    PPUADDR = #$1E00
        lda #0
    .loop:
        sta PPUDATA
        dey
        bne .loop

    setChrRamOverlap: ;BGのベースを変える境目でオーバーラップさせておく
    ;    PPUADDR = $1F00 ←アドレスが直前からの続きになったのでセット不要に
        lda #high(chr)+$0F
        sta chrptr+1
        ldy #0
    .loop
        lda [chrptr],Y
        sta PPUDATA

    それで直して確認していたところスタックがオーバーフローしていることに気づき、調べてみると
    ・初期化部分のコードの終わりからVBlank割り込みルーチンに流れ込む
    ・VBlank割り込みルーチンは最後にリターンせずにVBlank待ちに入る
    というめちゃくちゃなコードであった。他にスタックを使う関数呼び出しなどしていなかったので動いてしまっていた。
    これも直し、あとついでにCHR-RAMに書き込むとこのラベルがsetChrRom:だったのも修正した。

    上のコードは修正済み。
    なお、基本的に修正前の文章は残すように心がけているのだが、処理が面倒で適当に編集していたので申し訳ないが今回旧コードは残っていない。  

  • Atari2600詳細
    2015年04月13日 01:20

    アタリショックで有名なAtari2600。
    フェアチャイルドチャンネルFに続くカートリッジ交換式ゲーム機であり、ファミコンの前世代機としてアメリカで流行していたらしい。
    日本では流行らなかったため日本語での情報が少ないのが残念だ。とりあえず日本語で一番詳しくなるくらいに仕様をまとめてみる。
    性能が分かりやすいよう、日本人に馴染みの深いファミコンとの比較を各所に入れた。

    スペック

    ・CPU
    6507、1.19MHz
    この6507というCPUは6502の機能削減版で、割り込みが存在しないことと、メモリ領域が6502の64KBに対し8KBしかない点が違いである。
    ファミコンのCPUもBCD演算が無い他は機能は6502と同一なので単純に比較すると、ファミコンは1.79MHzなので、演算性能はファミコンの2/3と言えそうだ。
    が、Atari2600はファミコンと違い画面の描画にCPUを使うため、実質的に使用できるCPU時間は非表示期間中のみである。
    Atari2600の一般的な解像度は縦192pxなので、262-192=70と、1/4程度の時間しか演算に使えない。
    ファミコンは単純な(ラスタスクロールなどしない)静止画を表示する分にはCPUを使わないので、これと比較すると演算性能はファミコンの1/6程度とも言える。
    なおAtari2600も画面の描画を止めればCPU時間を演算に割くことができる。
    例えば「Vedeo Chess」ではAIの思考中は画面がチカチカする単色になる。
    https://www.youtube.com/watch?v=wdpRDYQgm84

    ・メモリ
    ワーキングRAM128バイト、VRAM数バイト
    ファミコンはワーキングRAMが2KB、VRAMが2KBである。
    ワーキングRAMが6502の高速なゼロページ領域に収まっているのでちょっと嬉しい。
    なおワーキングRAMは6502のスタック用の1ページにも重複してマッピングされているという面白い構造。
    128バイトでは少ないということでカートリッジ側に128バイトや256バイトのRAMを積むこともあったようだ。

    画面表示

    ・解像度
    横解像度は回路で決まっており、160ドットである。
    問題は縦で、(NTSCでは)192pxという値がよく見られるが、これはこの値が多くのゲームで使われているとか、この値が推奨されている程度の意味だろう。
    Atari2600の画面描画の特徴として、メモリには常に走査線1ライン分の情報しか持っていない。
    そこで、1ラインごとに、HBLANKの間にメモリ上の情報を書き換えて画面を作るのが基本の操作となる。描画中に何もしなければ縦線しか描けない。
    つまり、縦方向にどれだけの長さ描画するかはプログラムによって決まる。
    これより、NTSCの規格上許されている最大値をAtari2600の縦解像度の値とするのが妥当だろう。
    (規格外の走査線数を出力することすら可能だが、さすがにそれは考えないことにする)
    NTSCの規格上、映像信号を入れられるのは1フィールドあたり242.5ラインだが、NTSCの規格は2フィールドを半ラインずらすことになっているのをAtari2600などのゲーム機はずらしていないので、242pxとなる。

    なおこの192pxというのは242の約79%にあたる。
    横方向の160pxは44.7μ秒ほどで、描画期間約53.4μ秒の83.5%ほど。
    当時のTVで画面内に表示できる範囲はこの程度だったのであろう。

    さてではこの160×242ドットに自由に絵が描けるかというとそうではない。
    画面構成は後述するが、1ドット単位で絵を作れる構成要素は「オブジェクト」という、今で言うスプライトのみである。
    ファミコンのスプライトに横8枚制限があるのと同様、Atari2600もオブジェクトだけで画面全体を覆うことはまず不可能である。(たぶん本当に不可能だと思うが自信がない。後述)
    もう1つの構成要素としてPlayFieldという今で言う背景面のようなものがあるが、こちらは横解像度が40px、つまり160pxの画面に対し4pxごとにしか絵を描けない。
    これにより、Atari2600の画面はやけに解像度の低い背景の上にキャラクターがぽつぽつと置かれている独特な見た目になる。
    ただし、横は4ドット単位だが縦方向には1ドットごとに絵を書き換えられるため、上手いプログラムでは巧妙に横方向の解像度の低さを感じさせない絵柄を作っている。
    例えば「Pitfall!」の穴や木の表現が上手い。
    Atari2600 Pitfall!

    ・画面構成
    BackGround:
    以降の全てが無い部分の色

    PlayField:
    今で言う背景面
    単色。(Scoreモードでない場合)
    横解像度40px。
    拡大縮小やスクロールなどの機能は無い。
    「Scoreモード」では左右それぞれがPlayer0/1と同色で表示される。

    オブジェクト:
    今で言うスプライト。3種類5個のオブジェクトがある。
    Player0 / Player1
    横8px、それぞれ単色。
    一定間隔の2個または3個に増殖したり横幅2倍・4倍で表示する機能あり。
    具体的には、以下の8通り。2倍幅2個などといった自由な組み合わせはできない。
    Atari2600 Playerの表示パターン
    4倍幅モードでは横32pxと画面横幅の1/5。2つのPlayerで計2/5。ファミコンではスプライト上限横8個で画面の1/4なので、巨大キャラを動かすことについてはファミコンに優っていると言えなくもない。
    (まあファミコンなら背景面を使うところだが)

    Missile0 / Missile1
    横1px
    横幅2・4・8倍表示が可能
    対応するPlayerと同色
    個数・間隔もそれぞれのPlayerと同じ

    Ball
    横1px
    横幅2・4・8倍表示が可能
    色はPlayFieldと同色
    増殖はしない

    ・発色数
    扱える色は、オリジナルのNTSC版では
    (15色相+無彩色)×明度(?)8段階 = 128色
    PAL版では色信号の周波数が約15/12倍である関係か、
    (12+無)×8=104色
    と少々少ないが、どちらもファミコンの54色と比較すると多い。
    SECAMでは仕組みが全く違い明度に対応したデジタル8色のみ。

    なお「明度」と書いたが、彩度も連動して変わるかもしれない。(ファミコンがそうであるように)

    同時発色数は、1ライン中に同時に出せる色は基本的には以下の4色である。
    ・BackGround
    ・PlayField・Ball
    ・Player0・Missile0
    ・Player1・Missile1
    ただしライン中にレジスタを書き換えればこの限りではない。
    例えばAcidDropというゲームがあり、横1列に色違いの7区画+両端の色を表示している。
    https://www.youtube.com/watch?v=5Po47y0ALpU
    また、頑張れば下図のように横15色も可能である。
    Atari2600 1列に15色

    なお縦方向には制限はないので、ファミコンと違い画面中同時発色数は扱える全ての色128色が出力可能。縦方向のグラデーションはAtari2600の特徴である。

    ・オブジェクトの横位置の指定
    現代の感覚で考えれば、オブジェクトのX座標をレジスタに指定するとその位置に書いてくれる仕組みがあると思うだろう。
    否、そんな軟弱な仕組みではない。
    走査線を描画中にここぞというタイミングで各オブジェクトのリセットレジスタに書き込むことで、その地点のX座標に(次ラインから)オブジェクトが表示されるようになるのである。

    とはいえCPUの1クロックは画面上の3ドットに相当する。また時間調整のためのループは最短で1周に5クロックが必要であり、都合15ドットごとにしか書込みができない。
    そこで微調整機能が用意されている。
    5種のオブジェクト(Player0, Player1, Missile0, Missile1, Ball)それぞれについて横位置の差分を登録し、HMOVEレジスタに書き込むことで次ラインでの表示位置がその値だけずらせる。
    これが+7~-8まで設定できるので、任意の位置にオブジェクトを表示することが可能となる。
    またBallやMissileを表示したままラインごとにHMOVEをすることで斜めの線が表示できる。上のPitfall!の画像にもある。ファミコンにはできない芸当だ。
    なおこのHMOVEへの書込みを行うと、次ラインの左端8ドットが黒になるという妙な仕様があり、これによる櫛状の横線がAtari2600の画像の特徴となっている。

    高等技術

    プログラミング技術が成熟するにつれ、走査線の描画中にレジスタ内容を書き換えることで同時表示内容の限界を超えるテクニックが使われるようになる。

    ・PlayField内容の書き換え
    実は上で横40pxと言ったもののデータは20px分しか無く、1ライン中何もしなければ右半分が左半分と同じか、あるいは左半分を左右反転させたもので表示される。
    横40pxをフルに使うにはライン中の書き換えが必須である。
    なおこれは最初から想定されていた操作だろう。PlayFieldのScoreモードはライン中書き換えを前提としている。

    ・Player内容の書き換え
    Playerを複数表示モードにした状態で、1つ表示された後に内容を書き換えると、2つ目に別の内容を表示できる。
    これを最大限に駆使すると、Player0/1を3つづつ交互に並ぶようにして横48pxに自由に絵が描ける。
    ただし、このためにはメモリ(レジスタ)へ4回の書込みが必要になる。(CPUのレジスタとメモリにマップされたレジスタが紛らわしいな…)
    6502では(CPUの)レジスタからメモリへの書込みが最速で3クロック掛かる。画面のドット数で言うと9ドット分である。
    [--P0--][--P1--][--P0--][--P1--][--P0--][--P1--]
            ^P0      ^P1      ^P0      ^P1
    このように、猶予は4ドット分しか無く、書込み以外の操作を入れる余裕は無い。
    かつ、6502での最速の書込みはレジスタが3つしか無いため3連続が限度である。
    よって普通にはこの表示はできないことになる。

    この状況を打破するためには裏レジスタを使う必要がある。
    裏レジスタを理解するには、Vertical Delay(垂直遅延)機能の理解が必要である。
    この機能を有効にすると、Player0/1の書き込みはそれぞれの裏レジスタになされ、他方への書き込みを契機に表レジスタに移される。
    つまり、Player0/1の内容の書込みを他方への書込みまで遅延させるという動作をする。
    「Vertical」Delayの名は、1ラインごとにPlayer0と1へ交互に書き込む場合に1ライン分縦に遅延することになることによる。
    通常の使い道としては、Player0と1を両方1ラインごとに書き換えるのは処理が重いため、1ライン置きで交互に書き換えるようにしたとき、縦位置がずれないように使うものだろう。

    これを利用して、P0,P1両方の遅延を有効にし、非表示期間中にPlayer0,1に加えPlayer0の裏レジスタに表示内容を入れておく。
    すると、
    非表示期間:
    lda A
    sta P0 ;      P0裏=A
    lda B
    sta P1 ;P0=A, P1裏=B
    lda C
    sta P0 ;P1=B, P0裏=C
    lda D
    ldx E
    ldy F
    描画中:
    sta P1 ;P0=C, P1裏=D
    stx P0 ;P1=D, P0裏=E
    sty P1 ;P0=E, P1裏=F
    sta P0 ;P1=F, P0裏=D(表示されないのでstxでもstyでもよい)
    このように最速での4連続書込みが可能となる。

    ・オブジェクト増殖
    通常はリセットレジスタに書き込んだ次のラインからオブジェクトが表示されるのだが、Playerの2個・3個表示モードでは頭の1つを除いて同ラインに表示できる。またBallも同ラインから表示される。
    これを利用して、ライン中に複数回リセットをすることで1ライン中に複数個のPlayerを表示できる…らしい。
    これを使えばひょっとして画面を完全にオブジェクトで覆うこともできたりするのだろうか。恐らくは処理時間が足りず不可能だと思うが、この動作についてはいまいち仕様が理解できていないため自信がない。仮にできても意味のある絵を表示するのは困難だろう。
    この動作については下記「TIA Hardware Notes」が詳しいので気になる人はこれにあたってほしい。

    その他機能

    ・音源
    数種類のノイズと中途半端な音程の矩形波を出せるチャンネルが2チャンネル。
    あまり興味が無いので説明は割愛する。
    「Tone Toy」というHomebrewなソフトがあるので試してみると楽しいだろう。
    https://www.youtube.com/watch?v=Zco1hnyDiVI

    ・衝突検知
    ファミコンにも0番スプライトと背景面の衝突判定があるが、Atari2600ではPlayField, Ball, Player0/1, Missile0/1の6つの物体の全ての組合せ15通りについて衝突判定ができる。
    6502の「BIT」命令で読みやすいよう6,7bit目が立つ。

    ・タイマー
    ファミコンにもあれば良かったのに…。

    参考文献

    ネット上でいろいろと探した気がするが、結果的に以下の2つにだいたい全てまとまっていた。
    Atari 2600 Specifications http://problemkaputt.de/2k6specs.htm
    Atari 2600 TIA Hardware Notes http://www.atarihq.com/danb/files/TIA_HW_Notes.txt  

  • ゲームボーイ始めました。
    2015年03月28日 13:04

    ゲームボーイで猫耳
    ファミコンAtari2600GBAに続いて、ゲームボーイにも手を出してみた。

    やっと最近の話になった。冒頭の画像ができたのが今年の2/11である。

    GBは実機で動作させるのが簡単そうな気がするのが嬉しい点。
    たぶんFlashROMを1個用意してカートリッジの線につなげばできると思う。
    これがファミコンだと2個要るし、GBAだとなんだか特殊な回路が入ってるようなことを見た記憶がある。

    CPUは80系ということで初めて触るCPUだった。
    8080→8086→80x86なのでx86と同系統なのだが、x86は触ったことがあったがレジスタ名に多少面影を感じる程度であった。
    新しいCPUのアセンブラを触るのは楽しいのでこれはこれでよい。

    開発環境はググって見つけたGBDKというものを。
    ルートに展開せよと書いてあるがどうせパスの日本語に非対応という事だろうといつもの場所に置いてみたが、どうもパスの指定の関係でルート直下でないと動かないようだ。
    C言語コンパイラもついてるけど、やっぱりアセンブラだよねー。
    アセンブラのサンプルコードも(1個だけ)ついてるので安心。

    アセンブラの起動方法はなんだかんだで手間取ったが分かってみれば簡単だった。
    ..\bin\lcc -Wa-l -Wl-m -o temp.gb temp.s

    ちょっと困ったことに、この開発環境でアセンブルすると先頭のヘッダあたりを自動で生成してくれるのだが、その中の割り込みハンドラ部分が使い方がよく分からない。
    このようなことはよくある。
    CPU自体の割り込みの扱いはそのCPUを使おうとしている時点で大抵理解しているし、分からなかったらいくらでも資料があるので調べればよいだけなのだが、ある特定の開発環境でだけ使われるコードなど知らないし、調べようにもその付属のドキュメントが不足していたらそれまでである。だいたい、CPUが使いたいのであって開発環境を使いたいわけでもないのに、それを調べなくてはいけないというのはなかなかやる気を殺がれるものである。
    まあコードを追って一応使える程度には理解した。

    GBの面白いところとして、全画面に任意の画像が「頑張れば」表示できるという点がある。
    ファミコンのようにキャラクタ数が足りず明らかに不可能でもなく、
    GBAのようにビットマップモードがあって容易に可能でもない。

    GBの全画面を覆うには20*18=360キャラクタ必要。
    しかしGBで背景面に一度に表示できるキャラクタ数は256個。普通には不可能。
    だが、表示する256個のキャラクタは384個の内から2通りの選び方ができる。
    これを、画面の中程で切り替えることで、全画面に任意の表示ができるのである。

    それで全画面表示をしたのが冒頭の画像なのだが、ユニークタイル数を数えてみたら181。普通にも表示できた画像であった。
    単純に減色した画像では芸がないと思って綺麗にベタ塗りにしたのが災いした。

    なおGBカラーになると同時表示キャラクタ数が512個になるので全画面に別のキャラクタを表示することが容易に可能になる。その代わり色数の面で全画面任意とはいかなくなるのだが。

    あと画像表示の前に試したハローワールド
    ゲームボーイ_ハローワールド
    と、7×7(8×8)ドットでは芸がないので5×7ドットのテキストを表示…してみようとしたが2文字で力尽きた。
    ゲームボーイで5×7ドットフォント

    あとエミュレータはGBAでも使っていたので何も考えずVisualBoyAdvanceを使ったが、どうもGBエミュレータではBGBなるものが再現度が高いようだ。  

  • Atari2600はじめました。
    2015年03月24日 01:19

    Atari2600で猫耳1
    ファミコンGBAに続き、Atari2600の開発を始めてみた。
    …と思っていたのだが、Twitterのログやファイルの日付を見ると2011年5月あたり、GBAより前だった。やってすぐに書かないとこういうことになる。

    Atari2600と聞いてもピンと来ない人もいるかもしれない、というか自分自身始める前はおぼろげに名前を聞いたことがあるかという程度だった。
    日本では流行らなかったけれどアメリカの方ではファミコンの前世代機として一時代を築いた名機らしいのだが。

    このAtari2600、当然(総合的な)性能はファミコンを大きく下回るわけだが、画面の描画方法が他のゲーム機には無い独特なもので、挑戦しがいのある機械である。
    詳細な仕様については別に書こうかと思うが、ごく簡単にまとめると以下のようになる。 (4/13追記: 書いた→Atari2600詳細)
    ・画面解像度は横160ドット
    ・背景面は横4ドット単位でしか描けない
    ・スプライトは計5個
     横8ドットの絵が描けるものが2個
     横1ドットが3個
     (スプライトは複製or拡大できるが、詳細は割愛)

    縦は? と思うだろうが、ここがAtari2600最大のポイント。なんとVRAMを走査線1本分しか持っていないため、何もしなければ縦縞しか描けない。絵を作るには1ラインごとにVRAMを書き換える必要がある。
    さらに走査線の描画中でもお構い無しにVRAMを書き換えることができて、スプライトの3倍複製モードで描画するそばから書き換えることで6キャラクタ分48ドットの任意の画像を表示する高等テクニックがある。(実際にはそれだけでは書き換え速度が足りないので裏レジスタを使うというさらにひとひねり必要。詳細は割愛)

    そのテクニックを使って描いたのが冒頭の画像。
    Atari2600のCPUはファミコンと同じ6502(正確にはその機能削減版の6507)なのでプログラムは楽であった。
    なお開発環境はdasm、エミュレータはz26。
    dasmでアセンブルするとなぜか出力ファイルの頭に謎の2バイトが付いてそれを消さないとz26で動かなかった。なんだかよく分からないがとりあえず備忘録的に書いておく。

    あとつい最近またAtari2600をやりたくなって描いた絵を2枚。
    Atari2600で猫耳2
    Atari2600ではちゅねミク
    プログラムもちょこちょこ変えているが外見はただ絵が変わっただけ。下の数字はエミュレータの機能。
    はちゅねミクの絵で分かる人には分かると思うが、次はアニメーションさせたい。  

  • GBAはじめました。
    2015年03月23日 02:04

    GBA_HelloWorld

    ファミコンに続き、ゲームボーイアドバンス(以下GBA)の開発を始めてみた。3年前に。
    Twitterのログやファイルの更新日時を見るに、2012年5/30~6/6くらいにやっていたようである。
    なぜ今頃書くのかというと、そろそろネタを書きためているテキストファイルの見通しが悪くなってきたので整理するためだ。前回のCボタンユニットもそれである。

    開発環境はdevkitProというもの。これが環境を整えるのがすごく楽だった。インストーラの指示に従ってあとは環境変数を書くだけ。
    言語はとりあえずC言語。まあARMくらいの性能になると敢えてアセンブラを使う理由も薄いだろう。
    ARMの命令セットは条件実行機能が面白そうだから使ってみたいんだけどね。

    GBAではフルスクリーンの1枚絵を何の制限もなく表示できる。GBカラーやファミコンではできなかった芸当だ。
    GBA_15bitカラー1枚絵
    GBAの画面モードは色々あるが、これはGBAの最大色数である15bitカラー表示。背景数は1枚のみである。
    使用した画像データはこちら。
    GBA用画像_青肌
    GBAの色データはxBBBBBGGGGGRRRRRであるが、GIMPで出力する15bitカラーはxRRRRRGGGGGBBBBBしか出せず、RとBを入れ替えて書き出しているため、不気味な色になってしまった。上下反転はBMP画像の特徴。

    もう1枚、こちらは256色インデックスカラー。このモードでは背景面が2枚持てる。
    GBA_インデックスカラー1枚絵

    次はGIF画像を表示したり簡単な落ち物ゲームを作ろうかと思っていたようだが力尽きたようだ。
    その後最近になって(14年8月~11月あたり)、1カートリッジプレイで無改造で実機で動作させることを目指してPICで通信実験などしていた。
    そのうち再開したい。  

  • Cボタンユニット七変化
    2015年03月19日 01:56

    「ニンテンドウ64のコントローラの特徴は?」と問えば十人が十人「3Dスティック」と答えるであろう。
    アナログスティックを標準搭載したことでまさに「ゲームが変わる、ロクヨンが変える」を実感したものだ。

    だが、3Dスティックの陰に隠れて重要な脇役がある。「Cボタンユニット」だ。
    今日はCボタンユニットへの愛を語ろうと思う。

    Cボタンユニットはただの4つのボタンでしかないのだが、配置の妙によってさまざまな使い方ができる。

    単純4ボタン

    まず第一に、Cボタンユニットは実体としてあくまで4つの別々のボタンである。
    よって4つのモノを選択するのに使用できる。
    同様のことは十字キーでもできるだろうが、十字キーと違い斜め入力をしてしまう危険がないという利点がある。
    またゲームキューブのABXYボタンでも可能だが、4つが整然と並んでいること、主となるABボタンが残せることが優っている。
    例: ポケモンスタジアムのバトル中の技選択
    Cボタンユニット七変化の1
    ポケモンの4つの技を上下左右に対応付けることで、すべての技を1動作で選択することができる。
    これにより、本編での十字キーでカーソルを動かしAで決定する形と異なり、画面上に選択肢を表示する必要がなくなる。1画面でポケモン対戦をするために相手の技選択を見せないことは必須事項である。
    ゲームキューブのABXYボタンでも4つの選択は可能だが、並びが整然としていないため画面上の表示が不恰好になってしまうし、技の使用以外の動作がRやLなどの不自然なボタンに飛ばされてしまうだろう。

    4方向指示

    4つのボタンが正方形に並んでいるため、3Dスティックに次ぐ第2の方向入力として使える。その用途を示すように上下左右のマークも付いている。
    例: パイロットウィングスのカメラ
    Cボタンユニット七変化の2
    3Dスティックで自機を動かし、Cボタンユニットでカメラを動かせる。
    十字キーでも同様の操作は可能だが、コントローラー左側には十字キーとLボタンしかないのに対し、右側にはCボタンユニットとRボタンに加えA・Bボタンがあるため、上位互換と言える。
    コントローラーの左と真ん中を持つレフトポジションを使うソフトがほとんど存在しないことからその優位性は明白だ。


    そして、必要な分だけ切り売りして自由な組み合わせで使える。

    2方向指示×2

    例: マリオ64のカメラ
    Cボタンユニット七変化の3
    左右で回り込み
    上下で寄り・引き
    これは十字キーでも可能だが、斜めを押してしまう危険が無い分、上下と左右に別の動作を割り振るにはCボタンユニットの方が向いている。

    2方向指示+1+1

    例: F-Zeroの操作
    Cボタンユニット七変化の4
    上は視点変更
    左右はカメラ操作
    下はブレーキ

    3ボタン+1ボタン

    例: ゼルダの伝説のフィールド上操作
    Cボタンユニット七変化の5
    左下右で3つのアイテムを使用
    上は視点変更および妖精の呼び出し

    このような細かい分割もお手の物。


    さらには、絶妙な配置によりA・Bボタンとも組み合わせることすら可能である。

    4ボタン+A

    例: ゼルダの伝説のオカリナ(など楽器)操作
    Cボタンユニット七変化の6
    A、下、右、左、上の5ボタンがこの順で音高に対応している。
    Cボタンユニットが無ければこのオカリナ操作は生まれなかっただろう。
    実際、のちのシリーズでも音楽を演奏するギミックは毎回のように入っているのだが、
    風のタクトの風のタクトも、トワイライトプリンセスの遠吠えも、スカイウォードソードの琴も、皆オカリナのような自由演奏は不可能である。
    (夢幻の砂時計の笛はまあ頑張れば演奏できなくもないかな…)
    また64版ゼルダの移植版も、ゲームキューブは単純にA+Cスティックに対応させただけで操作しづらい上に音高の並び順が直感に反しており、3DS版では順にLRYXAと広範囲に散らばってしまっている

    4ボタン+A+B

    例: ポケモンスタジアムのポケモン選択
    Cボタンユニット七変化の7
    この作品では当時主流であった「6匹を見せ合い、相手を見た上でバトルに出す3匹を選ぶ」という対戦形式を公式に取り入れた。
    このために、Cボタンユニットに加えA・Bボタンを合わせた6つのボタンを6匹のポケモンに割り当て、3匹の選択を行う。
    ここでABとCボタンユニットがほぼ2×3の長方形に並んでいるため、順番が分かりやすい。
    技の選択と同じく、ここでも相手に見せずに確実に選択することが必要になる。これもCボタンユニットにしかできない芸当だろう。
    十字キーやアナログスティックで6方向を選ぶのはきちんと押せているか不安になる。GCのABXYLRでも可能だが並びが不自然である。


    その他

    4つ使い切る必要もない。
    ポケモンスタジアムではオプションやメニューを開くのにC右や上が使われたり、
    GBの操作でスタート・セレクトボタンをC左・下に割り振ることができる。
    スマッシュブラザーズではCボタンはすべてジャンプに割り当てられている。

    余談

    ・ストリートファイターが64に出てたら弱中強×パンチ・キックの6ボタンがAB+Cボタンユニットに綺麗に収まったろうに。
    ・6点で点字を打つのに便利そうだ。  

  • ファミコンの縦解像度224px説の考察
    2015年02月22日 04:49

    ファミコンの解像度は横256px×縦240pxだが、縦を224pxとする説が根強い。
    中には「内部的には240px」の但し書きをつけたり、「約」224pxとするものもあるが、224という値を書いている時点で五十歩百歩だ。

    「内部的」の解釈も曖昧だが、たぶん次のような認識なのだろう。
    「メモリ上には240px分の領域が用意されているが、出力されるのはそのうちの224px分のみ」
    そのようなことがないのは、Google画像検索で適当なファミコンソフト名を「640×480」や「720×480」の解像度指定で検索すれば分かるだろう。

    ただ似たような現象はあって、240px分が出力されても、普通のアナログTVでは実際に画面に表示されるのはその内の85%や90%程度である。
    (詳しくは「タイトルセーフ・アクションセーフ」「オーバースキャン」あたりで検索するとよい)
    ではそのことを言っているのかというと、2つの点からおかしい。
    第1に、どの程度の範囲が表示されるかはTVによって異なるので、224pxという値は出てこない。「約」であっても(10進法では)切りの悪い値にするのは不自然だ。
    第2に、TVに映らないという理由で解像度を224pxなどの値にするなら、同様にアナログTVに出力するスーパーファミコンやプレイステーションも240pxや480pxを出力できないことになろうが、これらの解像度には239pxや480pxという値が平気で書かれている。

    ではこの224という数字の出処はどこかだが、スーパーファミコンにはどうやら224pxモードがある。
    これこれを見ると、$2133のbit2で縦224pxと239px(448pxと478px)の切り換えができるようだ。
    またプレイステーションについても、興味が無いのであまり調べていないが、224pxや448pxのモードがあるような記述が見られる。

    もう1つの可能性として、エミュレータのオプションから来ているのかもしれない。
    エミュレータには縦240pxのうち224pxのみを表示するモードが付いている物がある。
    エミュレータ240pxモード1エミュレータ240pxモード2
    (というよりどちらもデフォルトが224pxで、240pxモードがある)

    まとめると、
    ・そもそもアナログTVの仕様として映像信号のうち画面には表示されない部分がある
    ・スーパーファミコンには縦224px出力のモードがある
    ・ファミコンのエミュレータには出力のうち縦224pxのみしか表示しないモードがあるものがある
    このあたりを混同した結果、ファミコンの縦224px説が生まれたのではないだろうか。


    さて本題の考察は以上だが、なぜエミュレータには224px設定が付いているのかの説明をしよう。
    まずこちらの画像をご覧頂きたい。
    ファミコン縦スクロール回り込み
    ドラゴンクエスト3で縦スクロール中に撮った画像だ。これは互換機だが、ファミコン実機でも同様の表示になるはずである。
    一番下の数ドットが、本来下に続くマップではなく、最上段からはみ出した分が下に回りこんでいるのが見て取れるだろう。

    これはファミコンの仕様で、内部的に2画面分を縦または横につなげた仮想画面をスクロールしているため、縦か横どちらか、この場合縦は1画面分の幅しかなく、スクロール時に上下にまたがるキャラクタは表示が乱れるのである。
    この乱れは、上記の通り通常のアナログTVでは画面外で表示されない部分であり、ユーザーの目にはいることはまずない。
    しかしPCのキャプチャカードで全ての出力を取り込んだり、エミュレータで全てを表示すると見えてしまうわけだ。
    そこでエミュレータでは、この本来表示されない領域を隠す設定が付けられた。
    どれだけ隠せばいいかといえば、背景面を構成するキャラクタは16×16単位で設定できる(正確には色が16×16、キャラが8×8)ので、うまく作られたプログラムなら半分の8pxづつだけ隠せばよいはずである。
    (うまく作られたプログラムばかりでもないと思うが)

    また、横についても同様の乱れが起こる。さらに縦と違う点として、ファミコンは横方向はもともと映像表示期間の90%強しか使っていないため、少し広めのTVではこの乱れが画面内に見えてしまう場合がありうる。
    おそらくそのために、ファミコンには左のみ8pxを表示しない設定がある(両側でないのはなぜだろう)。上のドラクエ3の画像もそうなっている。左右の黒帯の幅が違うことから分かるだろう。
    これを考えると、ファミコンの解像度は248×240と256×240の2モードあるとすべきだろう。

    追記:
    左8pxを表示しないモードについて、スクロール時の乱れを見せないためだろうと推測したが、おそらく違った。
    ファミコンのスプライトは横8px幅であり、そのX座標は0-255の値をとり、0で左端、248で右端、249-255では右端からはみ出す位置になる。つまり画面の右端からスプライトの一部分が見える表現はできるが、左端で同じことはできない非対称性がある。
    ここで左端8pxを背景・スプライトともに隠すことで、左端からスプライトの一部分が見える表現が可能となる。
    両側でない理由もつくのでこれがこの機能を付けた理由で間違いないだろう。乱れを見せないためにも使えたかもしれないが、少なくともメインの目的ではないだろう。
    なおこちらによればMSXやSG-1000ではスプライトの表示位置を32pxずらす機能があったらしい。