Arduino UNOブレッドボードコンピュータプログラマー – 8ビットシステムの構築とプログラミング
部品と材料
| Arduino UNO | ×1 |
このプロジェクトについて
このプロジェクトとは?
このプロジェクトは、Ben Eater氏の8ビットブレッドボードコンピュータプロジェクトの拡張版です。Ben氏のブレッドボードコンピュータ構築動画を追いかけて自作した際、非常に楽しめました。まだ動画を見ていない方は、まずBen氏の動画を視聴することを強くお勧めします。このプロジェクトはその文脈なしでは理解しにくいからです。
コンピュータを組み立てた後、毎回DIPスイッチで手動でプログラムを書き込むのは現実的ではないことがすぐにわかりました。わずか16バイトでも繰り返し書き込むのは面倒です。また、メモリを256バイトに拡張する予定でしたが、プログラムの読み書きができなければ意味がありません。
何ができるか?
- ブレッドボードコンピュータのRAMからプログラムを保存
- ブレッドボードコンピュータのRAMへプログラムを読み込み
- 起動時に保存済みプログラムを自動実行
- シリアル接続でメモリ内容の検査と変更
- アセンブリ言語でブレッドボードコンピュータをプログラミング
- メモリ内容の逆アセンブル
- 命令単位のシングルステップ実行
- ブレークポイントの設定とブレークポイントまでの実行
これらの機能をデモする動画があります(画質はご容赦ください)。
仕組み
このプロジェクトでは以下の目標を設定しました:
- 必要なハードウェアを最小限に抑える
- ブレッドボードコンピュータ自体への変更を最小限に抑える
最初はEEPROMにプログラムを保存し、ロジックでEEPROMからブレッドボードコンピュータのRAMへ転送することを考えました。しかし、転送ロジックの設計はソフトウェア寄りの私には複雑すぎました。そこでArduinoファンとして、Arduinoで転送ロジックを置き換えることを思いつきました。Arduino UNOはブレッドボードコンピュータより遥かに高性能なので「ズル」ではないかと悩みましたが、結果には満足しています。
Arduinoに必要な機能は、メモリからのデータ読み取りと書き込みです。最も簡単で侵襲の少ない方法は、メモリモジュールが既に持っているバスと制御信号のインターフェースを利用することです。ArduinoのデジタルI/Oピンは双方向なので、8本をバスに直接接続すればバスの読み書きが可能です。RAMモジュールにバスからの読み書きをさせるには、MI/RI/RO信号を適切に設定するだけです。
これらの信号は通常、制御ロジックのEEPROMが駆動しています。Arduinoもそれらを制御すると競合やショートの恐れがあります。しかし、Ben氏が使用しているAT28C16 EEPROMにはチップイネーブル(CE)入力があり、これをHighにするとデータ出力がハイインピーダンス状態になり、Arduinoが信号を操作できるようになります。
RAM読み出し手順
- EEPROMのCE信号をHighにして制御ロジックを無効化
- 読み出す最初のアドレスをバスに出力
- MI信号をHighにし、クロックの立ち上がりを待つ
- MIをLow、ROをHighにし、クロックの立ち上がりを待つ
- バスからデータバイトを読み取り
- 全16アドレスについて手順2〜5を繰り返す
- EEPROMのCE信号をLowにして制御ロジックを再有効化
RAMへの書き込みも同様で、バスにデータを出力しROの代わりにRIをHighにするだけです。技術的な課題はいくつかありますが、この基本メカニズムが本プロジェクトの核心です。
ブレッドボードコンピュータへの必要な変更
2つの変更が必要です:
- 制御ロジックEEPROMのデータ出力にプルダウン抵抗を追加
- EEPROM無効化中、リングカウンタを無効化または継続的にリセット
プルダウン抵抗
EEPROMを無効化すると、制御信号(AO/AI/EOなど)に接続されたロジックゲートのプルアップ抵抗がそれらの信号をHighに引き上げ、複数のレジスタがバスに書き込もうとして競合が起きます。74LSゲートの入力プルアップは約10kΩなので、十分に低電圧にするにはより小さなプルダウン抵抗が必要です。ほとんどの信号線には3.3kΩを使用しましたが、例外が2つあります:
- SU信号: 8個の排他的論理和ゲートに接続されているため、実効プルアップは10kΩ/8=1.25kΩ。1kΩ未満のプルダウンが必要ですが、SU(減算)信号はバスとのやり取りを制御しないため、プルダウンなしで無視できます。
- CE信号(カウンタイネーブル): 1kΩのプルダウンが必要。大きい値ではランダムなプログラムカウンタ動作の原因になります。
プルダウン抵抗の追加に最適なのは、青色LEDを搭載したブレッドボード上、LEDのアノード(EEPROM出力に接続)とGNDの間です。
リングカウンタのリセット
この変更はプログラムの保存/読み込みには必須ではありませんが、Arduinoから制御ロジックへの制御移譲をスムーズにします。CEがHighの間(制御ロジック無効中)、リングカウンタを0に保ちます。ArduinoがCEをLowに戻すと(制御ロジック有効化)、リングカウンタが0の状態から次の命令実行を開始できます。
私の構成では、EEPROM出力の1つでリングカウンタをリセットしているため、この変更は不要でした。この方式では命令完了時に即座にリングカウンタがリセットされ、性能向上につながります。また、制御ロジックEEPROMが無効化されると、その出力のプルダウン抵抗が信号をLowに引き下げ、自動的にリングカウンタがリセットされます。
Ben氏の固定5ステップリングカウンタを使用している場合、以下の拡張でCEがHighのときにカウンタをリセットできます(左/右矢印でBen氏のオリジナル回路と拡張版を切り替え可能):
これには3つのNANDゲート、つまり74LS00チップ1つ追加が必要です。このアプローチは未検証ですが、理屈上は動作するはずです。
この変更は絶対に必要ではありません。最初は省略しても、保存/読み込み、モニタ/アセンブラ/逆アセンブラは正常に動作します。ただし、Arduinoから制御ロジックへの制御移譲を必要とする機能(起動時の自動実行、シングルステップ、デバッガでの実行)は動作しません。
Arduinoのセットアップ方法
GitHubアーカイブからスケッチをArduinoに書き込み、以下のようにブレッドボードコンピュータと接続します:
- Arduino 5Vピン(Vinではない) → ブレッドボードの5Vレール
- Arduino GNDピン → ブレッドボードのGND
- Arduino デジタルピン2〜9 → バス0〜7
- Arduino デジタルピン10 → ROを制御する制御ロジックEEPROM出力
- Arduino デジタルピン11 → RIを制御する制御ロジックEEPROM出力
- Arduino デジタルピン12 → MIを制御する制御ロジックEEPROM出力
- Arduino アナログピン0 → CLOCK信号
- Arduino アナログピン3 → 全ての制御ロジックEEPROMのCE(ピン18) および 10kΩ抵抗経由で+5V
さらに、Arduinoのアナログ1とアナログ2をDIPスイッチとプッシュボタンに接続します(詳細は添付のFritzingファイル参照)。
最小構成(機能は維持)
- EEPROM出力ピン(AO, CO, EO, IO, ROを制御するもの)に3.3kΩプルダウン抵抗を追加
- 「リングカウンタのリセット」手順をスキップ
- 上記のArduino-ブレッドボード配線を実施(最後の10kΩ抵抗は省略可)
- アナログピン1とアナログピン2の両方をGNDに接続
ロード/セーブボタンを使うには、ボタン、DIPスイッチ、関連抵抗を回路図通りにアナログピン1、2に接続します。
オートスタート機能を使うには、リセット中とプログラム転送中にArduinoがプログラムカウンタとリングカウンタを0に保持する必要があります。アナログピン3と+5V間のプルアップ抵抗は、Arduinoリセット中に制御ロジックを無効化(プログラムカウンタを0に保持)します。リングカウンタについては上記の「リングカウンタのリセット」手順に従ってください。
プログラムの読み書き方法
最小構成ではシリアルインターフェースでArduinoを制御できます。PuTTYやTeraTermなどのターミナルプログラムが必要です(Arduino IDEのシリアルモニタでも動作しますが、入出力領域が分離されているためやや使いにくいです)。
- ブレッドボードコンピュータの電源を入れる
- USBケーブルでPCとArduinoを接続
- ターミナルプログラムを起動し、9600ボーレート、8ビット、パリティなし、ストップビット1に設定
- ターミナルウィンドウでESCキーを押してモニタモードに入る
- 「.」というコマンドプロンプトが表示される
- 「h」と入力してEnterを押すとサポートコマンド一覧が表示される
最小構成では「m」「M」「C」「l」「s」コマンドが使用可能です。これらでメモリ内容の表示、変更、プログラムの読み書きができます。
ボタンでの保存/読み込み
- ブレッドボードコンピュータのクロックを止める
- DIPスイッチで保存/読み込みするファイル番号を選択
- 「セーブ」または「ロード」ボタンを押す。CEに接続されたLEDが点灯し、Arduinoが制御権を取得したことを示す
- ブレッドボードコンピュータのクロックを動かす。メモリアドレスレジスタのLEDがアドレスを巡回するのが見える
- バスLEDの点滅が止まり、メモリアドレスレジスタが1111(15)を示すまで待つ
- クロックを止める。CEのLEDが消灯し、制御権が制御ロジックに戻ったことを示す
起動時の自動実行
必要な回路がすべて揃っていれば、DIPスイッチで保存済みプログラムのファイル番号を選び、ブレッドボードコンピュータの電源を入れる(またはリセットボタンを押す)だけで自動実行されます。特殊ケースとして、全DIPスイッチOFFなら通常起動、全ONなら起動時に直接モニタモードに入ります。
アセンブラと逆アセンブラの使い方
アセンブラ/逆アセンブラとデバッガ機能を使うには、Arduinoのプログラムを自分の環境に合わせて修正する必要があります。ソースコード内のopcodes_4bit構造体定義部分を探してください:
struct opcodes_struct opcodes_4bit [] = {
{"NOP ", B00000000, 0, false},
{"LDA ", B00010000, 2, true},
...
{".OR ", B11111110, 0, true}, // 開始アドレス設定
{".BY ", B11111111, 0, true}, // 1バイトデータ定義
{NULL, 0, 0, false}
};
各行が1つのオペコードを定義します:
- 第1フィールド: ニモニック("LDA")。即値アドレッシングの場合はニモニックに"#"を含めます。Ben氏の"LDI"はここでは"LDA #"となります(私はC64で6510アセンブラを育ちました)。
- 第2フィールド: オペコード本体。下位4ビットは常に0(.ORと.BYを除く)。
- 第3フィールド: 実行に必要なサイクル数(フェッチサイクル以外)。私の実装ではLDAはオペコード0001で合計4サイクル、うち2サイクルがフェッチです。Ben氏の指示通り(全オペコード5サイクル)なら常に3になります。
- 第4フィールド: 引数が必要かどうか。LDAは必要、OUTは不要。
このリストを自分のブレッドボードコンピュータで実装したオペコードに合わせて修正してください。最後の2行(.ORと.BY)はアセンブラ用の特殊オペコードなのでそのままにします。
修正後、Arduinoに書き込み、ターミナルを接続してモニタモードに入ります(ターミナルでESCキー、または全DIPスイッチON)。これで逆アセンブラが使えます。モニタで「d」と入力するとアドレス0から逆アセンブルを開始します。
アセンブラは最小限ですが実用的です。「a」でアドレス0からアセンブル開始。規則は以下の通り:
- 行が空白で始まらない場合、ラベル定義で始める必要があります。ラベルは英字で始まり英数字が続き、最大3文字、大文字小文字を区別します。
- 行の最初の空白以降、アセンブラはニモニック(LDA, STA, OUT...)を期待します。
- 特殊ニモニック".BY"で現在位置にデータバイトを直接指定。
- 特殊ニモニック".OR"でアセンブル位置を新しいアドレスに変更。
- 引数が英字で始まる場合はラベルとみなします。
- 数値引数は10進数とみなします。16進数の場合は"$"を前置。例:Aレジスタに0xFFをロードするには"LDA #$FF"。
- ";"以降はコメントとして無視されます。
フィボナッチ数列の例:
rst LDA #0 ; x = 0
STA x
LDA #1 ; y = 1
STA y
lp ADD x ; z = y + x
STA z
JC rst ; オーバーフローなら再起動
OUT ; zを出力
LDA y ; x = y
STA x
LDA z ; y = z
STA y
JMP lp ; ループ
x .BY 0
y .BY 0
z .BY 0
制限事項
ArduinoのRAM節約のため、アセンブラは1パスアセンブラとして動作します(複数パスだとソースコード全体をバッファリングする必要があるため)。オペコードは入力と同時にブレッドボードコンピュータのメモリに書き込まれます。つまり、アセンブル速度はブレッドボードコンピュータのクロック速度に制約されます。ターミナルにテキストをコピペすると、Arduinoがクロック待ちで忙しいため9600ボーレートでの文字受信に追いつけず、文字化けすることがあります。対策として:
- ボーレートを下げる
- 文字送信間隔を指定できるTeraTermを使う
- ブレッドボードコンピュータのクロック速度を上げる(私の環境では160kHzまで上げて9600ボーレートでコピペ可能)
デフォルト設定ではArduinoスケッチはブレッドボードコンピュータのクロック周波数1〜2kHz程度まで対応(Ben氏のデフォルトクロックは500Hz)。より高速なクロックの場合、コード内の#ifdef FAST_IOスイッチを探して有効化してください。これで250kHzまで対応可能になります(160kHzで動作確認済み)。より高速化するには時間クリティカルなループをアセンブラで書く必要がありますが、160kHzでもブレッドボードコンピュータの他の機能を考えると十分高速です。FAST_IO有効化前にはコード内の該当コメントを必ず読んでください。
Arduinoには1KBのEEPROMがあり、1024/16=64個のプログラムを保存可能。実際は設定データ用に16バイト予約されているため63個です。十分な数ですが、DIPスイッチで選択できるのは0〜15番(うち1〜14がオートスタート用)のみ。"s"と"l"コマンドなら全0〜62番を扱えます。
見た目を整えるには?
最終版ではArduino UNOの代わりに、裸のATmega328Pチップ(16MHz水晶発振子とコンデンサ付き)を使用しました。UNOの電圧レギュレータは不要です(ブレッドボードの5Vを直接使用するため)。犠牲になるのは、ATmegaと通信するために別途USB-シリアル変換器(FTDI等)が必要になることだけです。この方がブレッドボードコンピュータ全体の見た目に馴染みます:
さらにブートローダーを削除すれば、Arduino起動時の2秒遅延をなくせます。興味があれば手順を投稿します。コメントでお知らせください!
コード
ブレッドボードコンピュータプログラマー
Arduinoスケッチ: https://github.com/dhansel/programmer
回路図
drive_pD8k28E85v.fzz
製造プロセス
- コンピュータマウスの進化:設計、製造、未来のトレンド
- Satellite CCRMAキットでセンサー駆動オーディオエフェクトを構築
- Bluetooth操作のArduinoスパイボットを自作する
- FlickMote:ジェスチャー操作のIRリモコンでスマートホームを制御
- 自作Arduino TV-B-Gone:全テレビ対応の万能リモコン
- 12メートル先から見える大型LEDマスタークロック(アラーム機能付き)を自作しよう
- ArduinoとBluetoothで作るスマート紛失防止タグ「Find Me」
- Arduinoプロジェクトのための最適化電源ソリューション
- Arduino三目並べ:MAX7219 LEDマトリクスと段ボールケースで自作
- スマートArduino四足歩行ロボットの製作:ステップバイステップガイド
- ArduinoとThingSpeakで作るリアルタイム気象モニタリングシステム