Arduboy Writerを作ってみた

これは何?

Arduboy用の書き込み装置です。Arduboy( https://arduboy.com/ )とはパソコンを通じてゲームを書き換え、書き換えたのちは1種類のゲームだけが遊べるというArduinoベースの携帯ゲーム機です。

しかし、出先でもArduboyのゲームを切り替えたいと思い作ったのがこれです。

(ちなみにGamebuino ( Getting started - Gamebuino Wiki) という同じくArduinoベースの携帯ゲーム機はSDカードスロットを搭載しており、そこからゲームを切り替えることができます。)

 

f:id:inajob:20180105194749p:plain

構成

仕組みも何もRaspberryPi Zeroです。

去年スイッチサイエンスで購入したものですが、なんかもったいなくて道具箱にしまいっぱなしにしていました。このままだと積み基板になってしまうということで使いました。

そしてOLEDとタクトスイッチを3つ。最後に3Dプリンタで作ったケースにより構成されています。

f:id:inajob:20180106185903p:plain

回路

とても簡単ですが回路図です。スイッチのプルアップはRaspberryPi Zero内部で行うので直結です。

f:id:inajob:20180105195658p:plain

f:id:inajob:20180105195117p:plain

ソフトウェア

RaspberryPi Zeroといっても中身は普通のLinuxです。Pythonを使ってI2C経由でOLEDを操作します。また、ブート時に自動的に起動させるためにsystemdのserviceファイルを作ります。

この記事が参考になりました。

qiita.com

雑ですがコードはこんな感じ。

import RPi.GPIO as GPIO

import time
import os
import subprocess

import Adafruit_GPIO.SPI as SPI
import Adafruit_SSD1306

from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont

# Input pins:
A_pin = 17
B_pin = 27
C_pin = 22

GPIO.setmode(GPIO.BCM)

GPIO.setup(A_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Input with pull-up
GPIO.setup(B_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Input with pull-up
GPIO.setup(C_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Input with pull-up

# Raspberry Pi pin configuration:
RST = 24
# Note the following are only used with SPI:
DC = 23
SPI_PORT = 0
SPI_DEVICE = 0

# 128x64 display with hardware I2C:
disp = Adafruit_SSD1306.SSD1306_128_64(rst=RST)

# Initialize library.
disp.begin()

# Clear display.
disp.clear()
disp.display()

# Load default font.
font = ImageFont.load_default()

# Create blank image for drawing.
# Make sure to create image with mode '1' for 1-bit color.
width = disp.width
height = disp.height
image = Image.new('1', (width, height))

# Get drawing object to draw on image.
draw = ImageDraw.Draw(image)

# Draw a black filled box to clear the image.
draw.rectangle((0,0,width,height), outline=0, fill=0)

buttons = [0,0,0];

hexes = os.listdir('./data')
position = 0;

def flashWrite():
  draw.rectangle((0,0,width,height), outline=0, fill=0)
  draw.text((0,30), 'writing...', font=font, fill=255);

  disp.image(image)
  disp.display()
  result = subprocess.call(['./leonardoUploader', '/dev/ttyACM0', './data/'+hexes[position]]);
  if result == 0:
    draw.text((0,30), 'success', font=font, fill=255);
  else:
    draw.text((0,30), 'error', font=font, fill=255);

  disp.image(image)
  disp.display()
  time.sleep(1)

def scan():
  global position
  global mode

  if not GPIO.input(A_pin):
    if buttons[0] == 0:
      position = position + 1
      if position >= len(hexes):
        position = 0;
    buttons[0] = 1
  else:
    buttons[0] = 0
  if not GPIO.input(B_pin):
    if buttons[1] == 0:
      position = position - 1
      if position < 0:
        position = len(hexes) - 1;
    buttons[1] = 1
  else:
    buttons[1] = 0

  if not GPIO.input(C_pin):
    if buttons[2] == 0:
      flashWrite();
    buttons[2] = 1
  else:
    buttons[2] = 0

def repaint():
  draw.rectangle((0,0,width,height), outline=0, fill=0)

  if buttons[0]:
    draw.rectangle((0,0,10,10), outline=255, fill=1);
  else:
    draw.rectangle((0,0,10,10), outline=255, fill=0);

  if buttons[1]:
    draw.rectangle((10,0,20,10), outline=255, fill=1);
  else:
    draw.rectangle((10,0,20,10), outline=255, fill=0);

  if buttons[2]:
    draw.rectangle((20,0,30,10), outline=255, fill=1);
  else:
    draw.rectangle((20,0,30,10), outline=255, fill=0);

  draw.text((0,30), hexes[position], font=font, fill=255);

  disp.image(image)
  disp.display()

scan()
repaint()

dirty = False
def sig(n):
  global dirty
  scan()
  dirty = True

GPIO.add_event_detect(A_pin, GPIO.BOTH, callback=sig, bouncetime=20);
GPIO.add_event_detect(B_pin, GPIO.BOTH, callback=sig, bouncetime=20);
GPIO.add_event_detect(C_pin, GPIO.BOTH, callback=sig, bouncetime=20);

try:
    while 1:
        if dirty:
          repaint()
          dirty = False
        #time.sleep(.01)

except KeyboardInterrupt:
    GPIO.cleanup()

肝心のArduboyにhexをアップロードする部分は

github.com

このソフトウェアを使いました。

起動時にプログラムを実行するために、systemdのservice定義を書きました。

[Unit]
Description=arduboyWriter
After=syslog.target

[Service]
Type=simple
WorkingDirectory=/home/pi/arduboyWriter
ExecStart=/usr/bin/python main.py
TimeoutStopSec=5
StandardOutput=null

[Install]
WantedBy = multi-user.target

 

これを/eyc/systemd/systemにコピーして systemctl enable arduboy-writerなどとして有効化できます。

ケース

Fusion360モデリングして、3Dプリンタで出力しました。3層の構造を4本のねじとナットでサンドイッチすることで固定しています。

Raspberry Pi ZeroのGPIOにはメスのピンヘッダをつけたので、ちょうどこの2層目がかっちりはまって、安定します。

f:id:inajob:20180105194916p:plain

f:id:inajob:20180105194949p:plain

まとめ

RaspberryPi ZeroでArduboyのゲームを書き込むのは、冷静に考えるとちぐはぐな解決策です。RaspberryPi ZeroのほうがArduboyの何倍も高スペックだからです。しかし、Arduboyは軽量で、ゲーム機として非常によくできているため、多少ちぐはぐであってもかまわないかな、ということで作ってみました。

世の中にはUSBホストになれるマイコンもあるようなので、そういったもので同じようなものが作れればよりスマートだと思います。(PIC32でできる気がするが、まだ僕にはそのスキルがありません・・勉強せねば・・)

RaspberryPi Zeroを使ったプロダクトは初めて作りましたが、この小ささはとても魅力的だと実感しました。小さいと3Dプリンタでケースを作るのも楽だし、成果物も小さく収められます。

ただ、Linuxをブートするため、起動時間が数十秒かかるというのがネックです。これも前述したUSBホストになれるマイコンを使うことで解決できそうです。