Asuka Kuroda
Published © GPL3+

Arduboy MSX Cartridge

Arduboy comes to MSX! It's an Arduboy MSX cartridge to play games on the MSX computers with joystick! MSX can talk to Arduboy via I2C too!

AdvancedFull instructions provided5 hours1,014

Things used in this project

Hardware components

Arduboy Compatible
Arduboy Compatible
×1
Arduino Pro Micro (Arduino Micro Clone)
×1
OLED SSD1306 SPI
×1
MSX cartridge prototyping board with 50 pin edge connector
×1
Small bread board
×3
Crystal Oscillator (through hole 4 pin) 3MHz
Clock for PCF8584
×1
Capacitor (104 ceramic)
For each chip
×5
Capacitor (10uF 16V Electrolytic)
×1
PCF8584 (DIP 20)
I2C to 8 bit bus controller bridge chip
×1
74LS682 (DIP 20)
For address decoder
×1
74LS32 (DIP 14)
For generating read and write IO port signal to PCF8584 and GAL16V8
×1
74LS05 (DIP 14)
For connecting to 6 button inputs of Arduboy, open collector
×1
GAL16V8 (DIP 20)
Receive 6 button input from an IO port address of MSX and send to Arduboy through 74LS05. Generate chip select for PCF8584
×1
DIP Switch (8 switches DIP 16)
To set the MSX IO port address for address decoder to chip select
×1
Button
For reset, not needed if a Arduino Micro is used
×1
Buzzer
For Arduboy sound
×1
Resistor 10k ohm
Resistor 10k ohm
4.7K to 10K for I2C pull up
×2

Software apps and online services

Arduino IDE
Arduino IDE
Fusion-C-v1.2 for MSX
SDCC
WinCupl 5.30.4
Fritzing

Hand tools and fabrication machines

MSX computer
A generic writer to burn the GAL chip

Story

Read more

Schematics

Schematics

A screenshot from Fritzing

Fritzing file

Code

Arduboy Hello World sketch

Arduino
Build it with Arduino IDE and upload to Arduboy
/*
Arduboy MSX Hello World 
Joystick and I2C example

This example is based on below:
------------------------------------------------------------------------------
Buttons example
June 11, 2015
Copyright (C) 2015 David Martinez
All rights reserved.
This code is the most basic barebones code for showing how to use buttons in
Arduboy.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
------------------------------------------------------------------------------
*/

#include <Arduboy2.h>
#include <Wire.h>

// Make an instance of arduboy used for many functions
Arduboy2 arduboy;

// Variables for your game go here.
char title[] = "Press Buttons!";
byte x;
byte y;

// Width of each charcter including inter-character space
#define CHAR_WIDTH 6

// Height of each charater
#define CHAR_HEIGHT 8

// To get the number of characters, we subtract 1 from the length of
// the array because there will be a NULL terminator at the end.
#define NUM_CHARS (sizeof(title) - 1)

// This is the highest value that x can be without the end of the text
// going farther than the right side of the screen. We add one because
// there will be a 1 pixel space at the end of the last character.
// WIDTH and HEIGHT are defined in the Arduboy library.
#define X_MAX (WIDTH - (NUM_CHARS * CHAR_WIDTH) + 1)

// This is the highest value that y can be without the text going below
// the bottom of the screen.
#define Y_MAX (HEIGHT - CHAR_HEIGHT)


// This function runs once in your game.
// use it for anything that needs to be set only once in your game.
void setup() {
  //initiate arduboy instance
  arduboy.begin();

  // here we set the framerate to 30, we do not need to run at default 60 and
  // it saves us battery life.
  arduboy.setFrameRate(30);

  // set x and y to the middle of the screen
  x = (WIDTH / 2) - (NUM_CHARS * CHAR_WIDTH / 2);
  y = (HEIGHT / 2) - (CHAR_HEIGHT / 2);

  //--
  Wire.setClock(90000);
  Wire.begin(1);                // join i2c bus with address #2 in MSX
  //Wire.setClock(90000);
  
  Wire.onReceive(receiveEvent); // register event
  Wire.onRequest(requestEvent); // register event
  //--
}

// I2C (MSX OUT, Arduboy receive)
void receiveEvent(int howMany) {
  
  int cnt = 0;
  while (Wire.available()) { // loop through
    char c = Wire.read(); // receive byte as a character
    
    if (cnt < 14)
    {
      title[cnt] = c;
      cnt++;
    }
  }

  while (cnt < 14)
  {
    title[cnt] = '!';
    cnt++;
  }
}

// I2C (MSX INP, Arduboy send)
void requestEvent() {

  // send Y coordinate
  Wire.write(y); // respond
}

// our main game loop, this runs once every cycle/frame.
// this is where our game logic goes.
void loop() {
  // pause render until it's time for the next frame
  if (!(arduboy.nextFrame()))
    return;

  // the next couple of lines will deal with checking if the D-pad buttons
  // are pressed and move our text accordingly.
  // We check to make sure that x and y stay within a range that keeps the
  // text on the screen.

  // if the right button is pressed move 1 pixel to the right every frame
  if(arduboy.pressed(RIGHT_BUTTON) && (x < X_MAX)) {
    x++;
  }

  // if the left button is pressed move 1 pixel to the left every frame
  if(arduboy.pressed(LEFT_BUTTON) && (x > 0)) {
    x--;
  }

  // if the up button or B button is pressed move 1 pixel up every frame
  //if((arduboy.pressed(UP_BUTTON) || arduboy.pressed(B_BUTTON)) && (y > 0))
  if(arduboy.pressed(UP_BUTTON) && (y > 0)) 
  {
    y--;
  }

  // if the down button or A button is pressed move 1 pixel down every frame
  //if((arduboy.pressed(DOWN_BUTTON) || arduboy.pressed(A_BUTTON)) && (y < Y_MAX)) 
  if(arduboy.pressed(DOWN_BUTTON) && (y < Y_MAX)) 
  {
    y++;
  }


  // we clear our screen to black
  arduboy.clear();

  // we set our cursor x pixels to the right and y down from the top
  arduboy.setCursor(x, y);

  // then we print to screen what is stored in our title variable we declared earlier
  arduboy.print(title);

  // then we finaly we tell the arduboy to display what we just wrote to the display.
  arduboy.display();
}

Cupl language source code for GAL chip

VHDL
Use WinCupl to compile to create a jed file to burn onto the GAL16V8 chip
Name     Arduboy ;
PartNo   00 ;
Date     31/05/2021 ;
Revision 01 ;
Designer Engineer ;
Company  Asuka ;
Assembly None ;
Location  ;
Device   g16v8a ;

/* *************** INPUT PINS *********************/
PIN 1    = Aone                        ; /* address A1, 1 select Arduboy Controller */
PIN 2    = D0                        ; /* data D0, UP from MSX */ 
PIN 3    = D1                        ; /* data D1, DOWN from MSX */ 
PIN 4    = D2                        ; /* data D2, LEFT from MSX */ 
PIN 5    = D3                        ; /* data D3, RIGHT from MSX */ 
PIN 6    = D4                        ; /* data D4, A from MSX */
PIN 7    = D5                        ; /* data D5, B from MSX */
PIN 8    = !iorq_rd                  ; /* MSX Read + IO Request + Address Decoder Result A7 to A2 */
PIN 9    = !iorq_wr                  ; /* MSX Write + IO Request + Address Decoder Result A7 to A2 */
/* PIN 11   = !iorq                     ; */ 

/* *************** OUTPUT PINS *********************/

PIN 12   = !CS                        ; /* chip select pcf8584 */ 
PIN 13   = Q5                        ; /* Arduboy B */
PIN 14   = Q4                        ; /* Arduboy A */ 
PIN 15   = Q3                        ; /* Arduboy RIGHT */ 
PIN 16   = Q2                        ; /* Arduboy LEFT */ 
PIN 17   = Q1                        ; /* Arduboy DOWN */ 
PIN 18   = Q0                        ; /* Arduboy UP */ 
/* PIN 19   =                           ; */ 

FIELD INPUT = Aone; 
FIELD OUTPUT = addr_io;

/* keep in table form for upgrading to a bigger GAL */
TABLE INPUT => OUTPUT {
'b'0 => 'b'0; /* address for I2C CS  */
'b'1 => 'b'1; /* address for Arduboy controller */
}

iwrite_data = iorq_wr & addr_io; /* Arduboy controller write enable */

ENA = iwrite_data; /* enable for the SR latch with enable */

CS = (iorq_wr # iorq_rd) & !addr_io;

/* Q0 ------------------------------------------------------ */

S0 = D0;
R0 = !S0;

MR0 = R0 & ENA; 
MS0 = S0 & ENA;

Q0 = !(MR0 # QN0); 
QN0 = !(MS0 # Q0); 

/* Q1 ------------------------------------------------------ */

S1 = D1;
R1 = !S1;

MR1 = R1 & ENA; 
MS1 = S1 & ENA;

Q1 = !(MR1 # QN1); 
QN1 = !(MS1 # Q1); 

/* Q2 ------------------------------------------------------ */

S2 = D2;
R2 = !S2;

MR2 = R2 & ENA; 
MS2 = S2 & ENA;

Q2 = !(MR2 # QN2); 
QN2 = !(MS2 # Q2); 

/* Q3 ------------------------------------------------------ */

S3 = D3;
R3 = !S3;

MR3 = R3 & ENA; 
MS3 = S3 & ENA;

Q3 = !(MR3 # QN3); 
QN3 = !(MS3 # Q3); 

/* Q4 ------------------------------------------------------ */

S4 = D4;
R4 = !S4;

MR4 = R4 & ENA; 
MS4 = S4 & ENA;

Q4 = !(MR4 # QN4); 
QN4 = !(MS4 # Q4); 

/* Q5 ------------------------------------------------------ */

S5 = D5;
R5 = !S5;

MR5 = R5 & ENA; 
MS5 = S5 & ENA;

Q5 = !(MR5 # QN5); 
QN5 = !(MS5 # Q5); 

MSX joystick code

C/C++
Compile using SDCC and Fusion-C. Run it on MSX-DOS.
//
// Arduboy MSX cartridge 
// Joystick control only
// By Asuka Kuroda (June 2021)

// requires MSX Fusion-C Library
#include "fusion-c/header/msx_fusion.h"
#include "fusion-c/header/io.h"

#include <string.h>
#include <stdio.h>
#include <stdlib.h>


//#define  HALT __asm halt __endasm   //wait for the next interrupt

// 0x04 and 0x05 for I2C
// 0x06 or 0x07 for Arduboy controls
//#define IOPORT0 0x04
//#define IOPORT1 0x05
#define IOPORTC 0x06

// interrupt vsync count
int icount = 0;
// interrupt
static char my_interrupt( void ) {
if( IsVsync() == 0 ) return 0;
 icount++;
 
 return 1;
}

// Generates a pause in the execution of n interruptions.
// PAL: 50=1second. ; NTSC: 60=1second. 
/*
void Wait(int cycles)
{
  int i;
  for(i=0;i<cycles;i++) HALT;
  return;
}
*/
void Wait(int cycles)
{
    //Print("Wait\n");
    int i;
    for (i = 0; i < cycles; i++)
    {

    __asm    
    NOP
    __endasm;    

    }
}

// 8bit value (6bits used) to OUT to IO port to control Arduboy buttons
// From LSB: UP,DOWN,LEFT,RIGHT,UP,DOWN,A,B
unsigned char outval;

void  joy_UP(void)
{
    outval |= 1;
    //Print("UP");
}
void  joy_DOWN(void)
{
	outval |= 2;

}
void  joy_RIGHT(void)
{
	outval |= 8;
}
void  joy_LEFT(void)
{
	outval |= 4;
}

void  joy_direction (char a)
{
       switch (a) 
        {
            case 0:
                // no button press
				break;
            case 1:
                 joy_UP();
                break;
            case 2:
                 joy_UP();
                 joy_RIGHT();
                break;
            case 3:
                 joy_RIGHT();
                break;
            case 4:
                 joy_RIGHT();
                 joy_DOWN();
            case 5:
                 joy_DOWN();
                break;
            case 6:
                 joy_DOWN();
                 joy_LEFT();
                break;
            case 7:
                 joy_LEFT();
                break;
            case 8:
                 joy_LEFT();
                 joy_UP();
                break;
            default:
                Print("?");
        
        }
}

void main(void)
{ 
    int buttonA;
    int buttonB;

    int joystick1;
    int j = 1; // Joystick 1 (0 for Cursor Keys on the keyboard)   

    Cls();

    InitInterruptHandler();
    SetInterruptHandler( my_interrupt );

    // set WX to turbo mode (only for FS-A1WX MSX2+)
    //OutPort(64, 8);
    //OutPort(65, 0);

	Print("Arduboy Joystick\n(use joystick 1, ESC to quit)\nver. 0.92");
	outval = 0;
    OutPort(IOPORTC, outval); // reset button states
	// reset Arduboy if the screen is blank or white
	
    while(Inkey()!=27) // ESC
    {
		if (icount > 0)
        {
            DisableInterrupt();

            outval = 0;
            joystick1 = JoystickRead(j); // direction pad
		    buttonA = TriggerRead(1); // joystick 1 button A 
		    buttonB = TriggerRead(3); // joystick 1 button B

            joy_direction(joystick1);
		
		    if (buttonA != 0)
		    {
			    outval |= 16;
		    }
		    if (buttonB != 0)
		    {
			    outval |= 32;
		    }
		
		    OutPort(IOPORTC, outval); // output to Arduboy buttons
		    // Wait (Halt here for non VSync version)
            icount = 0;
            EnableInterrupt();
        }
    }

    EndInterruptHandler();

    outval = 0;
    OutPort(IOPORTC, outval); // reset button states

    // set WX to normal mode (only for FS-A1WX MSX2+)
    //OutPort(64, 8);
    //OutPort(65, 1);
	
	Exit(0);
}

MSX Hello I2C code

C/C++
For use with the Arduboy Hello World demonstration. Compile with SDCC and Fusion-C. Run it on MSX-DOS.
// Hello World Example
// Arduboy MSX cartridge 
// Joystick control, PCF8584 I2C functions 
// By Asuka Kuroda (June 2021)

// requires MSX Fusion-C Library
#include "fusion-c/header/msx_fusion.h"
#include "fusion-c/header/io.h"

#include <string.h>
#include <stdio.h>
#include <stdlib.h>


//#define  HALT __asm halt __endasm   //wait for the next interrupt

// 0x04 and 0x05 for I2C
// 0x06 or 0x07 for Arduboy controls
#define IOPORT0 0x04
#define IOPORT1 0x05
#define IOPORTC 0x06

// interrupt vsync count
int icount = 0;
// interrupt
static char my_interrupt( void ) {
if( IsVsync() == 0 ) return 0;
 icount++;
 
 return 1;
}

// Generates a pause in the execution of n interruptions.
// PAL: 50=1second. ; NTSC: 60=1second. 
/*
void Wait(int cycles)
{
  int i;
  for(i=0;i<cycles;i++) HALT;
  return;
}
*/
void Wait(int cycles)
{
    //Print("Wait\n");
    int i;
    for (i = 0; i < cycles; i++)
    {

    __asm    
    NOP
    __endasm;    

    }
}

// pcf8584 I2C 
int pcf8584_slave_w = 2; // slave address 1 (7 bit, 1st bit is W register)
int pcf8584_slave_r = 3; // slave address 1 (7 bit, 1st bit is R register)

void pcf8584_init(void)
{
    OutPort(IOPORT1, 0x80);
    Wait(32);
    OutPort(IOPORT0, 0x55);
    Wait(32);
    OutPort(IOPORT1, 0xA0);
    Wait(32);
    OutPort(IOPORT0, 0x00); // 3MHz Crystal 90KHz I2C CLK
    Wait(32);
    OutPort(IOPORT1, 0xC1);
    Wait(32);
}

void pcf8584_master_send(char* title, int len)
{
    // todo: implement register read to check status
    int i;

    OutPort(IOPORT0, pcf8584_slave_w); // slave address with WR register
    Wait(32);
    OutPort(IOPORT1, 0xC5); // Start 
    Wait(32);

    for (i = 0; i < len; i++)
    {
        OutPort(IOPORT0, title[i]); // data
        Wait(32);
    }
    
    OutPort(IOPORT1, 0xC3); // Stop
    Wait(32);
}

void pcf8584_master_send2(void)
{
    // todo: implement register read to check status
    OutPort(IOPORT0, pcf8584_slave_w); // slave address with WR register
    Wait(32);
    OutPort(IOPORT1, 0xC5); // Start 
    Wait(32);
    OutPort(IOPORT0, 'H'); // data
    Wait(32);
    OutPort(IOPORT0, 'e'); // data
    Wait(32);
    OutPort(IOPORT0, 'l'); // data
    Wait(32);
    OutPort(IOPORT0, 'l'); // data
    Wait(32);
    OutPort(IOPORT0, 'o'); // data
    Wait(32);
    OutPort(IOPORT0, ' '); // data
    Wait(32);
    OutPort(IOPORT0, 'A'); // data
    Wait(32);
    OutPort(IOPORT0, 'r'); // data
    Wait(32);
    OutPort(IOPORT0, 'd'); // data
    Wait(32);
    OutPort(IOPORT0, 'u'); // data
    Wait(32);
    OutPort(IOPORT0, 'b'); // data
    Wait(32);
    OutPort(IOPORT0, 'o'); // data
    Wait(32);
    OutPort(IOPORT0, 'y'); // data
    Wait(32);
    OutPort(IOPORT1, 0xC3); // Stop
    Wait(32);
}

void pcf8584_master_receive(unsigned char *val1, unsigned char *val2)
{
    // todo: implement register read to check status
    // todo: read more bytes
    OutPort(IOPORT0, pcf8584_slave_r); // slave address with WR register
    Wait(32);
    OutPort(IOPORT1, 0xC5); // Start
    Wait(32);
    OutPort(IOPORT1, 0x40); 
    Wait(32);
    *val1 = InPort(IOPORT0); //
    Wait(32);
    OutPort(IOPORT1, 0xC3); // Stop
    Wait(32);
    
    *val2 = InPort(IOPORT0); //
    Wait(32);
} 


// 8bit value (6bits used) to OUT to IO port to control Arduboy buttons
// From LSB: UP,DOWN,LEFT,RIGHT,UP,DOWN,A,B
unsigned char outval;

void  joy_UP(void)
{
    outval |= 1;
    //Print("UP");
}
void  joy_DOWN(void)
{
	outval |= 2;

}
void  joy_RIGHT(void)
{
	outval |= 8;
}
void  joy_LEFT(void)
{
	outval |= 4;
}

void  joy_direction (char a)
{
       switch (a) 
        {
            case 0:
                // no button press
				break;
            case 1:
                 joy_UP();
                break;
            case 2:
                 joy_UP();
                 joy_RIGHT();
                break;
            case 3:
                 joy_RIGHT();
                break;
            case 4:
                 joy_RIGHT();
                 joy_DOWN();
            case 5:
                 joy_DOWN();
                break;
            case 6:
                 joy_DOWN();
                 joy_LEFT();
                break;
            case 7:
                 joy_LEFT();
                break;
            case 8:
                 joy_LEFT();
                 joy_UP();
                break;
            default:
                Print("?");
        
        }
}

// todo: this is sdcc gets source, so link the lib instead
char *
gets (char *s)
{
  char c;
  unsigned int count = 0;

  while (1)
    {
      c = getchar ();
      switch(c)
      {
       case '\b': /* backspace */
          if (count)
            {
              putchar ('\b');
              putchar (' ');
              putchar ('\b');
          --s;
          --count;
            }
          break;

        case '\n':
        case '\r': /* CR or LF */
          putchar ('\r');
          putchar ('\n');
          *s = 0;
          return s;

        default:
          *s++ = c;
          ++count;
          putchar (c);
          break;
        }
    }
}

char t[14];
void main(void)
{ 
    int buttonA;
    int buttonB;

    int joystick1;
    int j = 1; // Joystick 1 (0 for Cursor Keys on the keyboard)   

    unsigned char my_val = 0;
    unsigned char my_val1 = 0;
    unsigned char my_val2 = 0;

    Cls();

    InitInterruptHandler();
    SetInterruptHandler( my_interrupt );

    // set WX to turbo mode (only for FS-A1WX MSX2+)
    //OutPort(64, 8);
    //OutPort(65, 0);

	Print("Arduboy Joystick and I2C\n(use joystick 1, ESC to quit)\nver. 0.92\n");
	outval = 0;
    OutPort(IOPORTC, outval); // reset button states
	// reset Arduboy if the screen is blank or white
	
    while(Inkey()!=27) // ESC
    {
		if (icount > 0)
        {
            DisableInterrupt();

            outval = 0;
            joystick1 = JoystickRead(j); // direction pad
		    buttonA = TriggerRead(1); // joystick 1 button A 
		    buttonB = TriggerRead(3); // joystick 1 button B

            joy_direction(joystick1);
		
		    if (buttonA != 0)
		    {
			    outval |= 16;
                // Send a title name to Arduboy

                // type in a title
                Print("Input title: ");
                //InputString(t, 14); // this function causes a bug use gets instead
                gets(t);

                pcf8584_init();
                pcf8584_master_send(t, StrLen(t));
		    }
		    if (buttonB != 0)
		    {
                outval |= 32;
                // Receive Y coordinate from Arduboy
                pcf8584_init();
                pcf8584_master_receive(&my_val1, &my_val2);
                //if (my_val1 == 3) // from the right slave address
                {
                    Print("Y: ");
                    PrintNumber(my_val2);
                    Print("\n");
                }                
		    }
		
		    OutPort(IOPORTC, outval); // output to Arduboy buttons
		    // Wait (Halt here for non VSync version)
            icount = 0;
            EnableInterrupt();
        }

    }

    EndInterruptHandler();

    outval = 0;
    OutPort(IOPORTC, outval); // reset button states

    // set WX to normal mode (only for FS-A1WX MSX2+)
    //OutPort(64, 8);
    //OutPort(65, 1);
	
	Exit(0);
}

Credits

Asuka Kuroda

Asuka Kuroda

1 project • 0 followers
Software/Embedded Systems Engineer in Aerospace.

Comments