goo blog サービス終了のお知らせ 

レトロでハードな物語

レトロなゲーム機・マイコン・中古デバイスなどをArduinoやAVRで再活用する方法を模索しています。

MSXをUSBキーボード化してMSXブースター気分を味わう

2025年03月19日 | 電子工作

いつクラウドファンディングされるのか分かりませんが、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キーボードで遊ぶことが出来ます。

他愛ないお遊びですが、結構楽しめます。



最新の画像もっと見る

コメントを投稿