いつクラウドファンディングされるのか分かりませんが、MSXをMSX2+ゃTurboRにアップグレードするMSXブースターというものが発表されています。
MSXのZ80と差し替えて使用するとの事なのですが、中身はESP32となっており、想像ですがESP32でMSXのバスを乗っ取ってカートリッジスロットとかキーボードとかを利用できるようにするのだと思います。でも結局はESP32でMSX2+とかのエミュレータを動かしているので、MSX0とそれほど違わないですね。最近の発表ではFPGAも載せたいとのことなので、それならばクラウドファンディングの支援も有りかな。(でもエミュレータでなくハードウェアでMSXを構築するMSX DIYの方が魅力的ですね。個人的にはこちらを支援したいです。)
そんなMSXブースターですがMSX本体のビデオ出力はアップグレードできないと思いますので、WiFi経由でリモートディスプレイを利用することになるのでしょう。MSX0Stackを持っていないので間違っているかもしれませんが、リモートディスプレイとはPC側で動いているMSXエミュレータにESP32が接続しているものだと思っています。
もしそうならばPCで動いているMSXPLAYerなどのエミュレータに実機のMSXを接続して利用できれば、MSXブースター?
そこでMSXとPCのエミュレータを連動させて動かしてみたいと考えたのですが、そんな難しいことは出来ないのでMSXをUSBキーボードにして、これをPCにつなげてエミュレータを利用することにしました。お手軽な方式でMSXブースターの雰囲気だけ味わってみることにします。
MSXをUSBキーボードにするにはマイコンが必要です。ここのところ全く使用していなかったSparkfun Pro Micro(互換機)を使うことにしました。手持ちの物は5V動作なので、電圧レベルをコンバートしなくてもMSXにつなげられるからです。(実際は「μP-1 ユニバーサル テストボード」の8255と接続します)
Pro MicroはCMOSですがMCUのATmega32U4はTTL対応のようなので、入力ポートをプルアップしておけばまず問題ないでしょう。
前に記事に書いたようにユニバーサル テストボードの8255のポートAとポートCのビット0,1,4,5をSDカードのドライバで使用していますので、今回はポートBとポートCのビット2,6をPro Microの接続に利用しました。
ユニバーサルテストボードがなくても8255(とアドレスデコード回路)があれば試せるので、一応詳細を書いておきます。
配線は以下の通り。ユニバーサルテストボード(8255)のGNDとPro MicroのGNDも接続します。
MSXのアセンブラソースは以下のようになります。z88dkでコンパイルしました。
keyboard.asm
SNSMAT | equ | 141h | ; キーマトリックススキャン |
PORTB | equ | 1 | ; 8255 ポートB |
PORTC | equ | 2 | ; 8255 ポートC |
CONTROL | equ | 3 | ; 8255 コントロールポート |
org | 0c000h | ||
ld | b,0 | ; スキャンするマトリックス番号を保持 | |
scanreq: | in | a,(PORTC) | |
and | 40h | ; 8255 ポートC ビット6 セット待ち スキャン要求 | |
jr | z,scanreq | ||
ld | a,b | ||
cp | 9 | ; スキャン番号8を超えたか? | |
jr | nz,setnum | ||
xor | a | ; スキャン番号を0に | |
ld | b,a | ||
setnum: | inc | b | |
call | SNSMAT | ; マトリックススキャン | |
out | (PORTB),a | ||
ld | a,5 | ; ポートC ビット2 セット 1行スキャンデータ出力済み | |
out | (CONTROL),a | ||
dataok: | in | a,(PORTC) | |
and | 40h | ; 8255 ポートC ビット6 リセット待ち スキャンデータ取得 | |
jr | nz,dataok | ||
ld | a,4 | ; ポートC ビット2 リセット ACK | |
out | (CONTROL),a | ||
jr | scanreq |
Makefile
- ASM=z80asm
- APPMAKE=z88dk-appmake
- FLAGS=+msx --fmsx --audio -b
- AFLAGS=-mz80 -b -l -m
- build: keyboard.cas
- %.cas: %.bin
- $(APPMAKE) $(FLAGS) $< --org 0xc000
- %.bin: %.asm
- $(ASM) $(AFLAGS) $<
- clean:
- rm *.o
- rm *.lis
- rm *.map
- rm *.cas
- rm *.wav
いつもMSX使用時にSDカードドライバを起動しているので、このプログラムは8255の初期化が終わった後に実行するように作ってあります。したがってこのプログラムを利用するときは事前に8255の初期化を行う必要があります。上記プログラムは8255のモード0、ポートA,Bが出力、ポートCの上位4ピットが入力、下位4ビットが出力の前提で作ってあります。具体的にはコントロールポートに0x88を出力します。
Pro Microのスケッチは以下の通りです。
Keyboard.h
- #ifndef KEYBOARD_h
- #define KEYBOARD_h
- #include "HID.h"
- #define MODIFIER_LEFT_CTRL 0b00000001
- #define MODIFIER_LEFT_SHIFT 0b00000010
- #define MODIFIER_LEFT_ALT 0b00000100
- #define MODIFIER_LEFT_GUI 0b00001000
- #define MODIFIER_RIGHT_CTRL 0b00010000
- #define MODIFIER_RIGHT_SHIFT 0b00100000
- #define MODIFIER_RIGHT_ALT 0b01000000
- #define MODIFIER_RIGHT_GUI 0b10000000
- typedef struct
- {
- uint8_t modifiers;
- uint8_t reserved;
- uint8_t keys[6];
- } KeyReport;
- class Keyboard_ : public Print
- {
- private:
- KeyReport _keyReport;
- void sendReport(KeyReport* keys);
- public:
- Keyboard_(void);
- size_t write(uint8_t k);
- size_t press(uint8_t k);
- size_t release(uint8_t k);
- size_t pressmod(uint8_t m);
- size_t releasemod(uint8_t m);
- void releaseAll(void);
- };
- extern Keyboard_ Keyboard;
- #endif
Keyboard.cpp
- #include "Keyboard.h"
- #if defined(_USING_HID)
- //================================================================================
- //================================================================================
- // Keyboard
- static const uint8_t _hidReportDescriptor[] PROGMEM = {
- 0x05, 0x01, // USAGE_PAGE (Generic Desktop) // 47
- 0x09, 0x06, // USAGE (Keyboard)
- 0xa1, 0x01, // COLLECTION (Application)
- 0x85, 0x02, // REPORT_ID (2)
- 0x05, 0x07, // USAGE_PAGE (Keyboard)
- 0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)
- 0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)
- 0x15, 0x00, // LOGICAL_MINIMUM (0)
- 0x25, 0x01, // LOGICAL_MAXIMUM (1)
- 0x75, 0x01, // REPORT_SIZE (1)
- 0x95, 0x08, // REPORT_COUNT (8)
- 0x81, 0x02, // INPUT (Data,Var,Abs)
- 0x95, 0x01, // REPORT_COUNT (1)
- 0x75, 0x08, // REPORT_SIZE (8)
- 0x81, 0x03, // INPUT (Cnst,Var,Abs)
- //LED状態のアウトプット
- 0x95, 0x05, // REPORT_COUNT (5) 全部で5つ。
- 0x75, 0x01, // REPORT_SIZE (1) 各LEDにつき1ビット
- 0x05, 0x08, // USAGE_PAGE (LEDs)
- 0x19, 0x01, // USAGE_MINIMUM (1) (NumLock LEDが1)
- 0x29, 0x05, // USAGE_MAXIMUM (5) (KANA LEDが5)
- 0x91, 0x02, // OUTPUT (Data,Var,Abs) // LED report
- //LEDレポートのパディング
- 0x95, 0x01, // REPORT_COUNT (1)
- 0x75, 0x03, // REPORT_SIZE (3) 残りの3ビットを埋める。
- 0x91, 0x01, // OUTPUT (Cnst,Var,Abs) // padding
- 0x95, 0x06, // REPORT_COUNT (6)
- 0x75, 0x08, // REPORT_SIZE (8)
- 0x15, 0x00, // LOGICAL_MINIMUM (0)
- 0x25, 0x73, // LOGICAL_MAXIMUM (115)
- 0x05, 0x07, // USAGE_PAGE (Keyboard)
- 0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
- 0x29, 0x8c, // USAGE_MAXIMUM (Keyboard Application)
- 0x81, 0x00, // INPUT (Data,Ary,Abs)
- 0xc0, // END_COLLECTION
- };
- Keyboard_::Keyboard_(void)
- {
- static HIDSubDescriptor node(_hidReportDescriptor, sizeof(_hidReportDescriptor));
- HID().AppendDescriptor(&node);
- }
- void Keyboard_::sendReport(KeyReport* keys)
- {
- HID().SendReport(2,keys,sizeof(KeyReport));
- }
- void Keyboard_::releaseAll(void)
- {
- _keyReport.keys[0] = 0;
- _keyReport.keys[1] = 0;
- _keyReport.keys[2] = 0;
- _keyReport.keys[3] = 0;
- _keyReport.keys[4] = 0;
- _keyReport.keys[5] = 0;
- _keyReport.modifiers = 0;
- sendReport(&_keyReport);
- }
- size_t Keyboard_::write(uint8_t c)
- {
- uint8_t p = press(c); // Keydown
- release(c); // Keyup
- return p;
- }
- size_t Keyboard_::press(uint8_t k)
- {
- uint8_t i;
- for (i=1; i
- {
- if(_keyReport.keys[i] == k) //すでに押されているキーは無視
- break;
- if (_keyReport.keys[i] == 0)
- {
- _keyReport.keys[i] = k;
- break;
- }
- }
- sendReport(&_keyReport);
- return 1;
- }
- size_t Keyboard_::release(uint8_t k)
- {
- uint8_t i;
- for (i=1; i
- {
- if (0 != k && _keyReport.keys[i] == k)
- {
- _keyReport.keys[i] = 0;
- }
- }
- sendReport(&_keyReport);
- return 1;
- }
- size_t Keyboard_::pressmod(uint8_t m)
- {
- _keyReport.modifiers |= m;
- sendReport(&_keyReport);
- return 1;
- }
- size_t Keyboard_::releasemod(uint8_t m)
- {
- _keyReport.modifiers &= ~m;
- sendReport(&_keyReport);
- return 1;
- }
- Keyboard_ Keyboard;
- #endif
MSX_keyboard.ino
- #include "Keyboard.h"
- #define LED1 18
- #define LED2 19
- #define REQ 14
- #define ACK 15
- uint8_t inp[] = {2, 3, 4, 5, 6, 7, 8, 9};
- const uint8_t keyMap[9][8] = {
- { 0x27, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24},
- { 0x25, 0x26, 0x2d, 0x2e, 0x89, 0x2f, 0x30, 0x33},
- { 0x34, 0x31, 0x36, 0x37, 0x38, 0x87, 0x04, 0x05},
- { 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d},
- { 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15},
- { 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d},
- { 0xe1, 0xe0, 0xe2, 0x39, 0x88, 0x3a, 0x3b, 0x3c},
- { 0x3d, 0x3e, 0x29, 0x2b, 0x48, 0x2a, 0x35, 0x28},
- { 0x2c, 0x4a, 0x49, 0x4c, 0x50, 0x52, 0x51, 0x4f}
- };
- uint8_t keyStat[9];
- int scanNO = 0;
- void setup()
- {
- ADCSRA = 0; // AD変換無効
- pinMode(LED1, OUTPUT);
- pinMode(LED2, OUTPUT);
- pinMode(REQ, OUTPUT);
- pinMode(ACK, INPUT_PULLUP);
- for(int i=0; i<8; i++)
- pinMode(inp[i], INPUT_PULLUP);
- digitalWrite(LED1, HIGH);
- digitalWrite(LED2, HIGH);
- //キーの状態リセット
- for(int i=0; i<9; i++)
- keyStat[i] = 0xff;
- }
- void loop()
- {
- uint8_t keyin, stat, chk;
- digitalWrite(REQ, HIGH); // スキャンデータ要求
- while(digitalRead(ACK) == LOW); // スキャンデータセット待ち
- // スキャンデータ取得
- keyin = 0;
- for(int i=0; i<8; i++)
- {
- if(digitalRead(inp[i]) == HIGH)
- keyin |= (uint8_t)(0b00000001 << i);
- }
- digitalWrite(REQ, LOW); // スキャンデータ取得済み
- while(digitalRead(ACK) == HIGH); // ACK待ち
- stat = keyStat[scanNO];
- if(keyin != stat)
- {
- keyStat[scanNO] = keyin;
- chk = keyin ^ stat;
- for(int i=0; i<8; i++)
- {
- if((chk & 1) == 1) // 変化あり
- {
- if((keyin & 1) == 0) // キーが押された
- pressKey(keyMap[scanNO][i]);
- else // キーが離された
- releaseKey(keyMap[scanNO][i]);
- }
- chk >>= 1;
- keyin >>= 1;
- }
- }
- if(++scanNO > 8)
- scanNO = 0;
- }
- uint8_t isModifier(uint8_t num)
- {
- uint8_t val = 0;
- switch(num)
- {
- case 0xe1:
- val = MODIFIER_LEFT_SHIFT;
- break;
- case 0xe0:
- val = MODIFIER_LEFT_CTRL;
- break;
- case 0xe2:
- val = MODIFIER_LEFT_ALT;
- break;
- }
- return val;
- }
- void pressKey(uint8_t code)
- {
- uint8_t modcode;
- modcode = isModifier(code);
- if(modcode == 0)
- Keyboard.press(code);
- else
- Keyboard.pressmod(modcode);
- }
- void releaseKey(uint8_t code)
- {
- uint8_t modcode;
- modcode = isModifier(code);
- if(modcode == 0)
- Keyboard.release(code);
- else
- Keyboard.releasemod(modcode);
- }
スケッチのLED1とLED2は使用したPro Microに別の用途で接続していたLEDです。今回は必要ないので消灯させています。
使用手順はPCとMSX(8255)の電源を入れたらPro MicroをPCにつなぎ、次にMSXのUSBキーボードプログラムを起動します。
プログラム起動後はMSXはただのUSBキーボードです。キーが少ないので全てではありませんが、OADG109のUSBキーコードを出力します。
それではWindowsXPのPCにインストールしたMSX公式エミュレータのMSXPLAYerを起動してMSXキーボードで利用してみます。
MSXでMSX2+やTurboRのゲームを遊んだり、プログラムしている気になれます。MSXブースターを使うとこんな感じになるのではないでしょうか。
そればかりか、「みんながコレで燃えた!PC-8001・PC-6001」とか「蘇るPC-8801伝説」「蘇るPC-9801伝説」などがあればPC-6001、8001、8801、9801のゲームもMSXキーボードで遊ぶことが出来ます。
他愛ないお遊びですが、結構楽しめます。
※コメント投稿者のブログIDはブログ作成者のみに通知されます