FLINTの「お世話になっております。キーボード」for SKK & Dvorak配列

#はじめに
[FLINT「お世話になっております。キーボード」の使い方](http://ichirowo.com/%E3%81%8A%E4%B8%96%E8%A9%B1%E3%81%AB%E3%81%AA%E3%81%A3%E3%81%A6%E3%81%8A%E3%82%8A%E3%81%BE%E3%81%99%E3%80%82%E3%82%AD%E3%83%BC%E3%83%9C%E3%83%BC%E3%83%89/)という大変面白い製品が発売されていました。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/211703/1fc4fbf9-7d3c-2736-dc71-68dc829db272.png)

これなら手持ちのArduino Pro Micro(のコピー)でも作成できるなということで、作成してみることにしました。

#本家からの変更点
![image.png](https://qiita-image-store.s3.amazonaws.com/0/211703/cca738ab-c971-39c0-ef7a-835ed72c7ca8.png)

##1. SKK日本語FEP対応 & Dvorak対応
本家ではprintを使用して文章を送信していますが、私はSKKを使用しているため、文章の初めにCtrl+Jを送信する必要があります。また変換、区切りにスペースキーやShiftキー併用したりする必要もありますので一文字づつ送信することんしました。
またレジストリでDvorak配列にしていますので、その変換関数も備えました。不要ならバイパスしてください。

###SKKとは
佐藤雅彦氏が開発した日本語入力システムの一つで、Simple Kana to Kanji conversion programの頭文字です。
私は[SKK日本語入力FEP](http://coexe.web.fc2.com/skkfep.html)を使用しています。詳しい説明はリンクを確認ください。

##2. 2ボタン式
本家は3ボタンの専用キーですが、SelectキーとSendキーの2つボタンとしました。これにより定型文を数を増やすことが可能になりました。

##3. 立ち上がり、立ち下がり信号検出
立ち上がり信号、立ち下がり信号を検出し、これをトリガに動作するようにしています。
これによりキー押下保持で何度も定型文が送信されたり、定型文設定がグルグル回るということがなくなります。

##4. LCD表示
2ボタンとした事により、現在どの定型文が選択されているか判りません。そのためキャラクタLCDで現在の設定を表示することにしています。

##5. 見た目
全然違いますね。

#配線
プログラムにもメモが書いていますが、簡単に。
[Arduino Pro Micro]   ---   [LCD]
PIN 11 --- RS
PIN 10 --- EN
PIN 09 --- D4
PIN 08 --- D5
PIN 07 --- D6
PIN 06 --- D5
[Arduino Pro Micro]   ---   [ボタン]  --- GND
PIN 02 --- [ボタン] --- GND : Selectボタン
PIN 03 --- [ボタン] --- GND : Sendボタン

#プログラム

```CPP:FlintCopy.cpp
/////////////////////////////////////////////////////////////////
//  FLINTコピーを作る
//
//　2018/08/14   first
//
// 参考HP
// https://akiba-pc.watch.impress.co.jp/docs/news/news/1137968.html
// http://ichirowo.com/%E3%81%8A%E4%B8%96%E8%A9%B1%E3%81%AB%E3%81%AA%E3%81%A3%E3%81%A6%E3%81%8A%E3%82%8A%E3%81%BE%E3%81%99%E3%80%82%E3%82%AD%E3%83%BC%E3%83%9C%E3%83%BC%E3%83%89/

#include <string.h>
#include <ctype.h>
#include "Keyboard.h"

#include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins
//LiquidCrystal  (RS, EN, D4, D5, D6, D7);
LiquidCrystal lcd(11, 10, 9, 8, 7, 6);

// set pin numbers for the five buttons:
#define ButtonPin1 2
#define ButtonPin2 3

// 定型文
const char *stringFormat[] = {
  "Osewa ninatteorimasu.\n"
  , "Otukaresama desu.\n"
  , "yoroshikuOnegai shimasu.\n"
};

// 定型文のタイトル (LCD表示)
const char *LCDMessage[] = {
  "MSG1:\xB5\xBE\xDC\xc6\xc5\xaf\xc3\xb5\xd8\xbd\xa1"   // ｵｾﾜﾆﾅｯﾃｵﾘﾏｽ｡
  , "MSG2:\xb5\xc2\xb6\xda\xbb\xcf\xc3\xde\xbd  " // ｵﾂｶﾚｻﾏﾃﾞｽ
  , "MSG3:\xd6\xdb\xbc\xb8\xb5\xc8\xb6\xde\xb2\xbc\xcf\xbd" // ﾖﾛｼｸｵﾈｶﾞｲｼﾏｽ
};

struct KEYSTATUS
{
  byte btn1;
  byte btn2;
};

KEYSTATUS stKeyCur;
KEYSTATUS stKeyOld;
KEYSTATUS stKeySts; // 0:OFF, 1:ON, 2:OFF(EDGE), 3:ON(EDGE)
#define OFF_STS   (0)
#define ON_STS    (1)
#define OFF_EDGE  (2)
#define ON_EDGE  (3)


int message = 0;

void setup() {
  // put your setup code here, to run once:
  //Serial.begin(9600);
 
  pinMode(ButtonPin1, INPUT_PULLUP);    // BTN1 (SELECT)
  pinMode(ButtonPin2, INPUT_PULLUP);    // BTN2 (KEYCODE SENT)

  memset(&stKeyCur, 0, sizeof(stKeyCur));
  memset(&stKeyOld, 0, sizeof(stKeyOld));
  memset(&stKeySts, 0, sizeof(stKeySts));

  // initialize mouse control:
  Keyboard.begin();
  Keyboard.releaseAll();

  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);
  lcd.print("\xB5\xBE\xDC\xc6\xc5\xaf\xc3\xb5\xd8\xbd\xa1 \xb7\xb0");
  lcd.setCursor(0, 1);
  lcd.print(LCDMessage[0]);   // ｵｾﾜﾆﾅｯﾃｵﾘﾏｽ｡
}

///////////////////////////////////////////////
// ボタンの押下状況をチェックする
void ReadKeyStatus( KEYSTATUS *pKey )
{
  byte *pBtnSts = (byte*)pKey;
  int nKey[] = {ButtonPin1, ButtonPin2 };
  for ( int i = 0; i < 2; i++)
  {
    pBtnSts[i] = !digitalRead(nKey[i]);
  }
}

///////////////////////////////////////////////////////////////////
// ON, OFFのトリガを保存
void SetKeyStatus( KEYSTATUS *pstCur, KEYSTATUS *pstOld, KEYSTATUS *pstKeySts )
{
  byte *pBtnSts = (byte*)pstCur;
  byte *pBtnOld = (byte*)pstOld;
  byte *pKeySts = (byte*)pstKeySts;
  int nNumOfKeyDown = 0;
  for ( int i = 0; i < 2; i++)
  {
    if ( pBtnSts[i] != pBtnOld[i] )   // 変化した
    {
      pKeySts[i] = (pBtnSts[i]) ? ON_EDGE : OFF_EDGE;
    }
    else    // 変化無し。本プログラムでは使用しないステータス。
    {
      pKeySts[i] = (pBtnSts[i]) ? ON_STS : OFF_STS;
    }
  }
}
//////////////////////////////////////////////////////////////
// キーコードを送信する
void SendKeyCode(char cKeyCode, bool bShiftKey = false, bool bCtrlKey = false )
{
  if ( bShiftKey )
  {
    Keyboard.press(KEY_RIGHT_SHIFT);
  }
  if ( bCtrlKey )
  {
    Keyboard.press(KEY_LEFT_CTRL);
  }
  Keyboard.press(q2d(cKeyCode));
  delay(50);
  Keyboard.releaseAll();    // Release allでやっつけ
}

/////////////////////////////////////////////////////////////
// メインループ
void loop() {
  // put your main code here, to run repeatedly:

  // キー状態の読み込み
  ReadKeyStatus(&stKeyCur);
  // キーの立ち上がり、立ち下がりの検出 -> stKeyStsに状態保存
  SetKeyStatus(&stKeyCur, &stKeyOld, &stKeySts);

  // Selectキーが押下された(キーアップを検出)
  if ( OFF_EDGE == stKeySts.btn1 )  // SELECT KEY
  {
    // 出力するメッセージを選択
    message ++;
    if (message > 2 )  message = 0;
    // LCD出力
    lcd.setCursor(0, 1);
    lcd.print(LCDMessage[message]); // display message LCD
  }
  // 送信キーが押下された(キーアップを検出)
  else if ( OFF_EDGE == stKeySts.btn2 ) // SEND KEY
  {
    SendString( stringFormat[message] );  // Sent key command
  }
  memcpy( &stKeyOld, &stKeyCur, sizeof(stKeyCur));
  delay(50);
}

//////////////////////////////////////////////////////////
// キーコードを送信する
void SendString(char* string)
{
  bool bShift;
  char c;
  SendKeyCode('j', false, true);  // Ctrl +J  SKK起動

  for (int i = 0; '\0' != string[i]; i++)
  {
    bShift = 0;
    c = string[i];
    if ( c == ¥xCJ )  // \xCJを Ctrl+C のエスケープシーケンスとする
    {
      SendKeyCode('j', false, true);  // Ctrl +J  SKK起動
    }
    if ( isupper( c ) ) // 大文字か?
    {
      bShift = true ;
      c = tolower( c ); // 小文字に変換して、Shiftキーを有効にする
    }
    if ( c == '\n' )
    {
      c = KEY_RETURN;
    }
    SendKeyCode( c , bShift );
  }
}

//////////////////////////////////////////////////////////////////////
// Dvorakでのキーを返す - RegEditでDvorak配列にしてる人のみ必要
char q2d(char q)
{
  const char qwerty[] = "1234567890-=qwertyuiop[]\\asdfghjkl;\'zxcvbnm,./"; // "1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./";
  const char dvorak[] = "1234567890[]\',.pyfgcrl/=\\aoeuidhtns-;qjkxbmwvz"; // "1234567890[]',.pyfgcrl/=\aoeuidhtns-;qjkxbmwvz";
  int i;
  char ret = q;
  for (i = 0; '\0' != qwerty[i]; i++)
  {
    if ( q == dvorak[i] )
    {
      ret = qwerty[i] ;
      break;
    }
  }
  return ret;
 }

 ////////////////////////////////////////////////////
  // これなんとかしたいな
  //
  //// UTF -> JIS
  //char u2j(char u)
  //{
  //  const char utf[] = "｡｢｣､･ｦｧｨｩｪｫｬｮｯーｱｲｳｴｵｶｷｸｹｺｻｼｽｾｿﾀﾁﾂﾃﾄﾅﾆﾇﾈﾉﾊﾋﾌﾍﾎﾏﾐﾑﾒﾓﾔﾕﾖﾗﾘﾙﾚﾛﾜﾝ";
  //  const char jis[] = "\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF";
  //  int i;
  //  char ret = u;
  //  Serial.print(ret);
  //  for (i = 0; '\0' != utf[i]; i++)
  //  {
  //    if ( u == utf[i] )
  //    {
  //      ret = jis[i] ;
  //      break;
  //    }
  //  }
  //  Serial.print(ret);
  //  return ret;
  //}
  //char* lcdprint(const char * string, char *buf)
  //{
  //  for ( int i = 0; '\0' != string[i] ; i++)
  //  {
  //    buf[i] = u2j( string[i] );
  //  }
  //  return buf;
  //}
```

#解説
定型文およびLCDのタイトルは下記変数に列挙します。

##定型文の設定
```CPP:定型文
// 定型文
const char *stringFormat[] = {
  "Osewa ninatteorimasu.\n"
  , "Otukaresama desu.\n"
  , "yoroshikuOnegai shimasu.\n"
};
// 定型文のタイトル (LCD表示)
const char *LCDMessage[] = {
  "MSG1:\xB5\xBE\xDC\xc6\xc5\xaf\xc3\xb5\xd8\xbd\xa1"   // ｵｾﾜﾆﾅｯﾃｵﾘﾏｽ｡
  , "MSG2:\xb5\xc2\xb6\xda\xbb\xcf\xc3\xde\xbd  " // ｵﾂｶﾚｻﾏﾃﾞｽ
  , "MSG3:\xd6\xdb\xbc\xb8\xb5\xc8\xb6\xde\xb2\xbc\xcf\xbd" // ﾖﾛｼｸｵﾈｶﾞｲｼﾏｽ
};
```

###SKK文法
SKKの入力方式に則り記述します。
Shift併用は大文字で記載します。
Enterは\nを記載します。
Ctrl+Jは0xCJ (\xCJ)を使用してください。

###LCD文法
ｶﾀｶﾅを表示する場合は16進で記載します。変換テーブルをぐぐって変換ください。
アルファベットならそのまま記載可能です。

##キー状態の確認
```CPP:Mainloop
  // キー状態の読み込み
  ReadKeyStatus(&stKeyCur);
  // キーの立ち上がり、立ち下がりの検出 -> stKeyStsに状態保存
  SetKeyStatus(&stKeyCur, &stKeyOld, &stKeySts);
```

ここでキーの検出を行います。状態はstKeyStsに保存されます。
Selectキーは stKeySts.btn1
Sendキーは stKeySts.btn2
を確認します。

```CPP:MainLoop2
// Selectキーが押下された(キーアップを検出)
  if ( OFF_EDGE == stKeySts.btn1 )  // SELECT KEY
  {
    // 出力するメッセージを選択
    message ++;
    if (message > 2 )  message = 0;
    // LCD出力
    lcd.setCursor(0, 1);
    lcd.print(LCDMessage[message]); // display message LCD
  }
  // 送信キーが押下された(キーアップを検出)
  else if ( OFF_EDGE == stKeySts.btn2 ) // SEND KEY
  {
    SendString( stringFormat[message] );  // Sent key command
  }
```

キー状態を確認し、スイッチがOFFになった時(指を離した時)に反応する用にします。OFF_EDGE状態はその瞬間の1ループのみ保持されており、次のループにはOFF_STSになります。そのため、何度も送信されることがなくなります。

#動作
動作を[YouTube](https://youtu.be/eJrip6iAioM)にアップしました。興味ある方はどうぞ。

#おわりに
全角、半角切替が可能となっていますので、定型文にアスキーアートも設定可能となっています。
改造して楽しんでいただけれべ幸いです。

2018/08/15 Ikeda