ガラケー型開発ボード「PiPoPa」を作った

この記事ははJLCPCBの提供でお送りします。

JLCPCBとは

jlcpcb.com (↑こちらは日本語版のログインページで、お得なクーポンも配布されています。)

JLCPCBとは、プリント基板製造などで有名な香港の企業です。

日本からでもWebページでポチポチするだけでKiCADなどで作成した基板データの製造を依頼できます。

値段もかなりお手頃で、ホビー電子工作ユーザーの間では広く利用されています。

この記事の作例もJLCPCBに基板を発注して実現しました。

ガラケー型開発ボード「PiPoPa」とは

PiPoPaはガラケー型の開発ボードです。Arduinoで開発でき、以下の機能を持ちます

  • 2+3×4キー
  • 5wayキースイッチ
  • USBシリアル(CH340N)によるArduinoでの書き込み
  • I2C接続のOLED
  • RTC DS1307
  • 表面実装スピーカー
  • 単4乾電池2本駆動
  • 5V昇圧(以下から選べる)
    • 5V昇圧モジュール
    • HT7750を使った昇圧回路
  • ICSP書き込みピン
  • I2C接続ポート
  • 余ったピンの接続ポート
  • 乾電池の電圧測定

なお、ガラケーの形はしていますが無線通信機能は搭載していません

さて、次に今回利用した特徴的な部品について紹介していきます。

CH340N

安いArduinoクローンなどで使われているUSBシリアルICであるCH340Gとよく似たICですが、CH340Nの特徴は水晶発振子が不要というところです。

(ピンぼけ画像ですが・・8ピンで比較的はんだ付けしやすいパッケージです)

これにより部品点数を押さえてUSBシリアル機能を持たせることが出来ます。

DS1307

Arduinoで時刻を扱う際によく用いられるRTCです。周辺回路が同梱されたモジュールとなったものをよく見かけますが、今回はICをそのまま取り付けています。 バックアップ電源を用いることで、メイン電源が切れても時を刻み続けることが出来ますが、今回はメイン電源をそのままバックアップ電源として接続しています。 (電源スイッチがOFFでもRTCのバックアップ電源ポートに電気が流れるようにしてあります)

5Wayキースイッチ

上下左右とセンタークリックの5つのスイッチが内部に備えたスイッチです。

この部品は背面に凸が2つあり、基板に位置合わせできるようになっていますが、この穴の位置が似たような部品でも違うパターンのものがあるので注意が必要です。

スイッチの感触は少し硬いように感じますが、そのおかげで誤った操作はしづらくなっていると感じます。

5V昇圧モジュール

これは以前紹介しました。

inajob.hatenablog.jp

上記の記事のArduino Nanoを単4電池で動かすボードと同様に5V昇圧モジュールとHT7750の好きな方を実装できるようにしています。 単4電池2本(3V)から5V昇圧し、標準的なArduinoと同じ電圧で動作するようにしています。

(今回利用している部品だと3Vのままでも一応動くような気がします)

デザイン、発注

この基板は、以前似たようなものを作っていて、それを改良する形で作成しました。

回路的にはArduino Unoとほとんど同じで、それにRTC回路や、昇圧回路、キーボードマトリクスなどを付け足した感じです。 部品の配置は、なるべくスマートになるようにギュっっと詰め込みました。

横幅は33mm以内となっているため、10cmx10cmの中に3枚面付けできるようになっています(今回は面付けしていませんが・・)

基板の発注

JLCPCBに基板を発注します。

jlcpcb.com

今回は何かカッコ良さそうな紫色の基板にしました。

今回のミス

スピーカーに直列して470Ωの抵抗をつける回路にしていたのですが、これはいつもRakuChordで利用している大きめのスピーカーのための抵抗値で、実際はもっと小さい抵抗値にする必要がありました。まぁこれはシルクが間違っているだけなので、適切な部品を使うことで対応できました。

RTC用の水晶のフットプリントが電池ボックスと少し干渉している問題もありました。この部品はスルーホール部品なので、表面からはんだ付けすることで、裏面の電池ボックスと干渉している部分にはんだ付けすることを回避しました。組み立て順を工夫して、先にRTC用の水晶を取り付けるようにしても、この問題を回避できます。

動作確認

動作確認として、まずはOLEDの表示、キーの入力、RTC、電源電圧測定を試してみました。 どれも想定通りに動き、安心しました。

OLEDの操作はu8g2、RTCの操作はRTCLibを利用しました。

github.com

github.com

以下のソースコードで、時計の表示・設定、電源電圧の測定、キーの入力を試すことが出来ました。

#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
#include <RTClib.h>

#define COL1 8
#define COL2 9
#define COL3 10
#define COL4 11
#define COL5 12

#define ROW1 4
#define ROW2 5
#define ROW3 6
#define ROW4 7

#define BAT A0

#define INT 2

#define M_NORMAL 0
#define M_SETTING 1

int mode = M_NORMAL;

RTC_DS1307 RTC;

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
enum Button{
  LEFT,
  UP,
  DOWN,
  RIGHT,
  CENTER,
  SS1,
  SS2,
  BTN1,
  BTN2,
  BTN3,
  BTN4,
  BTN5,
  BTN6,
  BTN7,
  BTN8,
  BTN9,
  BTN0,
  BTNS,
  BTNA,
  BTNNUM
};
byte trigger[BTNNUM];
String buf = "";
char timebuf[32];
int count = 0;
bool isCounting = false;

void setup(){
  RTC.begin();

  u8g2.begin();
  u8g2.setFont(u8g2_font_profont17_tf);

  pinMode(COL1, INPUT_PULLUP);
  pinMode(COL2, INPUT_PULLUP);
  pinMode(COL3, INPUT_PULLUP);
  pinMode(COL4, INPUT_PULLUP);
  pinMode(COL5, INPUT_PULLUP);

  pinMode(ROW1, INPUT_PULLUP);
  pinMode(ROW2, INPUT_PULLUP);
  pinMode(ROW3, INPUT_PULLUP);
  pinMode(ROW4, INPUT_PULLUP);

  pinMode(INT, INPUT_PULLUP);

  analogReference(DEFAULT);
  pinMode(BAT, INPUT);
  digitalWrite(BAT, LOW);

  pinMode(3, OUTPUT);
  tone(3, 440, 100);

  for(byte i = 0; i < BTNNUM; i ++){
    trigger[i] = 0;
  }
}
void loop(){
  bool state[BTNNUM];

  state[SS2] = !digitalRead(INT);

  pinMode(ROW1, OUTPUT);
  digitalWrite(ROW1, LOW);
  pinMode(ROW2, INPUT_PULLUP);
  pinMode(ROW3, INPUT_PULLUP);
  pinMode(ROW4, INPUT_PULLUP);

  state[LEFT] = !digitalRead(COL1);
  state[CENTER] = !digitalRead(COL2);
  state[DOWN] = !digitalRead(COL3);
  state[RIGHT] = !digitalRead(COL4);
  state[UP] = !digitalRead(COL5);

  pinMode(ROW1, INPUT_PULLUP);
  pinMode(ROW2, OUTPUT);
  digitalWrite(ROW2, LOW);
  pinMode(ROW3, INPUT_PULLUP);
  pinMode(ROW4, INPUT_PULLUP);

  state[SS1] = !digitalRead(COL5);
  state[BTN1] = !digitalRead(COL4);
  state[BTN4] = !digitalRead(COL3);
  state[BTN7] = !digitalRead(COL2);
  state[BTNA] = !digitalRead(COL1);

  pinMode(ROW1, INPUT_PULLUP);
  pinMode(ROW2, INPUT_PULLUP);
  pinMode(ROW3, OUTPUT);
  digitalWrite(ROW3, LOW);
  pinMode(ROW4, INPUT_PULLUP);

  state[BTN2] = !digitalRead(COL4);
  state[BTN5] = !digitalRead(COL3);
  state[BTN8] = !digitalRead(COL2);
  state[BTN0] = !digitalRead(COL1);

  pinMode(ROW1, INPUT_PULLUP);
  pinMode(ROW2, INPUT_PULLUP);
  pinMode(ROW3, INPUT_PULLUP);
  pinMode(ROW4, OUTPUT);
  digitalWrite(ROW4, LOW);

  state[BTN3] = !digitalRead(COL4);
  state[BTN6] = !digitalRead(COL3);
  state[BTN9] = !digitalRead(COL2);
  state[BTNS] = !digitalRead(COL1);

  for(byte i = 0; i < BTNNUM; i ++){
    if(state[i]){
      trigger[i] ++;
    }else{
      trigger[i] = 0;
    }
  }

  bool dirty = false;

  if(mode == M_NORMAL){
    for(byte i = 0; i < BTNNUM; i ++){
      if(trigger[i] == 1){
        switch(i){
          case SS1:  mode = M_SETTING; break;
          case SS2:  break;
          case BTN1: buf.concat("1"); break;
          case BTN2: buf.concat("2"); break;
          case BTN3: buf.concat("3"); break;
          case BTN4: buf.concat("4"); break;
          case BTN5: buf.concat("5"); break;
          case BTN6: buf.concat("6"); break;
          case BTN7: buf.concat("7"); break;
          case BTN8: buf.concat("8"); break;
          case BTN9: buf.concat("9"); break;
          case BTN0: buf.concat("0"); break;
          case BTNA: buf.concat("*"); break;
          case BTNS: buf.concat("#"); break;

          case LEFT: if(buf.length() > 0){buf.remove(buf.length() - 1);} break;
        }
        if(buf.length() > 14){
          buf.remove(buf.length() - 1);
        }
        dirty = true;
      }
    }

    if (RTC.isrunning()) {
      DateTime now = RTC.now();
      sprintf(timebuf, "%02d:%02d:%02d", now.hour(), now.minute(), now.second());
    }
    float bat = 5.0 * analogRead(BAT)/1024;
    char bbuf[6];
    dtostrf(bat, 3,2, bbuf);
    char batbuf[25];
    sprintf(batbuf, "%sV", bbuf);

    u8g2.clearBuffer();
    u8g2.drawStr(0,12,"HELLO PiPoPa2!");
    u8g2.drawStr(0,24, buf.c_str());
    u8g2.drawStr(0,36, timebuf);
    u8g2.drawStr(0,48, batbuf);
    u8g2.sendBuffer();
  }else if(mode == M_SETTING){
    for(byte i = 0; i < BTNNUM; i ++){
      if(trigger[i] == 1){
        switch(i){
          case SS1:  break;
          case SS2:  mode = M_NORMAL;break;
          case BTN1: buf.concat("1"); break;
          case BTN2: buf.concat("2"); break;
          case BTN3: buf.concat("3"); break;
          case BTN4: buf.concat("4"); break;
          case BTN5: buf.concat("5"); break;
          case BTN6: buf.concat("6"); break;
          case BTN7: buf.concat("7"); break;
          case BTN8: buf.concat("8"); break;
          case BTN9: buf.concat("9"); break;
          case BTN0: buf.concat("0"); break;
          case BTNA: break;
          case BTNS: break;

          case LEFT: if(buf.length() > 0){buf.remove(buf.length() - 1);} break;
        }
        if(buf.length() == 12){
          // complete
          int year = buf.substring(0, 4).toInt();
          int month = buf.substring(4, 6).toInt();
          int day = buf.substring(6, 8).toInt();
          int hour = buf.substring(8, 10).toInt();
          int minute = buf.substring(10, 12).toInt();
          RTC.adjust(DateTime(year, month, day, hour, minute, 0));
          buf = "";
          mode = M_NORMAL;
        }
        dirty = true;
      }
    }

    if (RTC.isrunning()) {
      DateTime now = RTC.now();
      sprintf(timebuf, "%02d:%02d:%02d", now.hour(), now.minute(), now.second());
    }

    u8g2.clearBuffer();
    u8g2.drawStr(0,12,"SETTING");
    u8g2.drawStr(0,24, "YYYYMMDDHHMM");
    u8g2.drawStr(0,24, buf.c_str());
    u8g2.drawBox(buf.length() * 9,12 + 12-2, 9, 2);
    u8g2.drawStr(0,36, timebuf);
    u8g2.sendBuffer();

  }
}

まとめ

ガラケー型開発ボードPiPoPaを作成しました。Arduinoの標準的な構成にすることで、既存の資源をうまく利用した設計が出来ました。 基本機能の確認をしただけですが、キーがたくさんあるので、かつてのガラケーのような操作性を備えたアプリケーションを色々動かすことが出来そうです。 (うまいことやればひらがな入力くらいまではできそうに思います)

また、基板むき出しのこのままでは使いづらいので3Dプリンタでケースを作ることにも挑戦したいなと考えています。