2020年6月28日日曜日

RGB LED付きロータリエンコーダ習作

次の工作に使おうと考えているロータリエンコーダを試してみました。秋月で売られているフルカラーRGB LEDが付いていて、軸がプッシュスイッチになっているものです。取り合えずの習作なのでArduino UNOを引っ張り出してきて使っています。


動かしているときの様子です。



ロータリエンコーダのエンコード信号とLEDは独立しています。ただし各LEDのアノードとプッシュスイッチの一方は共通ピンです。

ロータリエンコーダのデコード方法は、ELMさんのロータリー・エンコーダの使い方 を参考にさせていただきました。感謝。回路・スケッチは無保証です。

//------------------------------------------------------------
//                                              2020.6.28 naka
//  ロータリエンコーダ習作
//
//------------------------------------------------------------
#include <FaBoLCDmini_AQM0802A.h>
FaBoLCDmini_AQM0802A lcd;

#define SDAPIN  A4
#define SCLPIN  A5
#define RT0     2
#define RT1     3
#define LED_R   4
#define LED_G   5
#define LED_B   6
#define SW      7

int Count;

void setup() {
  // i/oピン設定
  pinMode(RT0,INPUT_PULLUP);
  pinMode(RT1,INPUT_PULLUP);
  pinMode(LED_R,OUTPUT);
  pinMode(LED_G,OUTPUT);
  pinMode(LED_B,OUTPUT);
  pinMode(SW,INPUT);

  // モニタLCD設定
  SetupLCD();
  lcd.clear();
  lcd.print("Encoder ");
  
  // カウンタ
  Count  = 0;
}

void loop() {
  int  dir;
  char buff[10];
  
  while(1) {
    dir = sample_coder();    // エンコーダの回転方向

    if (dir!=0 || Count==0) {
      Count += dir;
      disp_count(Count);
      LED_LightsUp(Count);
    }

    if (digitalRead(SW)==1) {  // ロータリエンコーダの軸スイッチが押されたか?
      Count = 0;
      disp_count(Count);
      LED_LightsUp(Count);
      break;
    }
  }
  while(digitalRead(SW)==1);
    
  delay(100);
}

void SetupLCD() {
  lcd.begin();
  lcd.command(0x38);
  lcd.command(0x39);
  lcd.command(0x14);
  lcd.command(0x73);
  lcd.command(0x51);  // 5V
//  lcd.command(0x56);  // 3.3V
  delay(2);
  lcd.command(0x6c);
  delay(300);
  lcd.command(0x38);
  delay(1);
  lcd.command(0x01);
  delay(2);
  lcd.command(0x0c);
  delay(2);
}

void disp_count(int cnt) {
  char buff[9];
  sprintf(buff,"CNT=%3d",cnt);
  lcd.setCursor(0, 1); // Col,Raw
  lcd.print(buff);
}

void LED_LightsUp(int val) {
  if (val<0)        led_rgb(0,0,1); // Blue
  else if (val==0)  led_rgb(0,0,0); // off
  else if (val<10)  led_rgb(0,1,0); // green
  else if (val<20)  led_rgb(1,1,0); // yellow
  else if (val<30)  led_rgb(1,0,0); // red
  else if (val>=30) led_rgb(1,0,1); // purple
}

void led_rgb(int r,int g,int b) {
  r = (r ^ -1) & 1;        // 論理反転 
  digitalWrite(LED_R,r); 
  g = (g ^ -1) & 1; 
  digitalWrite(LED_G,g); 
  b = (b ^ -1) & 1; 
  digitalWrite(LED_B,b); 
}

int sample_coder()
{
  static int code = 0;
  int code_val,code_pin0,code_pin1;

  code_val = (PIND >> 2) & 3;
  code = ((code << 2) | code_val) & 15; //
  switch (code) {  // デテンド位置=3
    case 0xE:
      return(1);
    case 0xD:
      return(-1);
    default:
      return(0);
  }
}
//------------------------------------------------------------

2020年6月27日土曜日

中国から電子工作部品が届き始めた

自粛期間中にAliexpressで注文した部品が届き始めました。注文時に表示された30〜50日よりは若干早く、これまで5件中2件が到着しています。残り3件の内2件は国内に届いている模様で、1件は出荷国を出発したステータスで長らく止まっています。さてどうなるか!?

届いた一つ目は、熱電対とMAX6675というデジタル変換ICが載ったモジュールです。SPIで温度を取り出せます。200〜300度の温度調整を試してみたくて購入。送料込みで350円位でした。

二つ目は小型カラー液晶モジュール3種です。左から順に、先日XIAOで使った80x160、240x240、128x128ピクセルです。右端は大きいですが解像度は低い。3個で750円位でした。安いから30日かかっても納得です。たまに届かないことがありますがAliexpressで申告すると返金されるので安心です。

これは温度調整のモニタにしようと思っています。どのサイズを使うかは、作りながら決めるつもり。制御はArduino Nano互換機にするつもりで注文してありますがまだ届かない。

Aliexpressでの部品購入は安さと時間のトレードオフです。趣味の電子工作にはとてもよいのですが、到着まであまり時間がかかると工作熱が冷めてしまうリスクもあります。何を注文していたかも忘れてしまったり。でも、安いからデッドストックになってもよいか、と悟ってポチッています。

2020年6月24日水曜日

MacBookの外付けキーボードが悩ましい

テレワークの環境がiMacからMacBookになり性能は格段によくなったのですが、困った事がありました。MacBookの画面は小さいので外付けディスプレイを使っており、そのための外付けキーボードがない。そんな話しをしていたら子供が旧タイプの純正無線キーボードを持っていることが判明。これでいいかと借りてきて使い始めましたが、USキーボードだったのです。


文章を打つにはそれほど困らなかったのですが、プログラムを書き出したら記号の位置が大きく違っていてストレスです。WindowsノートはJISなので、USに乗り換えるわけにもいきません。

純正のJISキーボードは高いし、個人的にイマイチ打鍵感がよくないのです。打ち易いキーボードでREALFORCEがありますが、25,000円位するのでちょっと手が出ません。悩んだ末、バッファローの安価なMac用キーボードにしました。テンキーはいらなかったというか無い方がコンパクトでよかったのですが、JISキーボードはこれくらいしか選択肢がありません。


使い始めましたが、打鍵感はお世辞にもよいとは言えません。安いから納得しています。ただ、左側のコマンドキーが小さいのと、少し左に寄っているのでショートカットキーを打ち間違えます。コピペをしようとして消してしまうことがしばしば。アマゾンの評価にも記載がありましたが何故この様な仕様になっているのか?

あと、ファンクションキーがそのままでは使えなかったのでkarabinerというプログラムを入れて対処しています。これも評価に載っていたので承知の上です。

そのうち慣れることを期待しています。

2020年6月19日金曜日

引き篭もり生活のリフレッシュ

あいにくの天候ですが、今日から県境をまたいでの移動制限が解除になったので、ちょっと出掛けました。と、言いつつ県境に近い所に住んでいるので、これまでも散歩してたりすると越境していたのですが、って、そういう事ではないですね。

今回は隣の県を越えた先までやってきました。


久しぶりに引き篭もり生活から離れてドライブを楽しみ、温泉に入りリフレッシュしています。平日の雨模様ということで温泉街は閑散としていましたが、週末の明日から徐々に復活していって欲しいですね。観光だけでなくビジネス全体が早く平常になることを祈らずにはいられません。

テレワークや家での電子工作も良いのですが、たまには外に出ないと思考や発想も内に固まってしまう様な気がします。ネットのサイバー空間の方が広大で可能性は無限なのかもしれませんが、現時点ではまだリアルな世界が欠かせません。

という訳で少し意識して色々と出掛けようと思います。もちろんコロナには気を付けて。しばらくは海外には行けないので国内ですね。GO TOキャンペーンなるものも始まる様なので、内需拡大も兼ねて利用してみますか。


2020年6月14日日曜日

除湿器のハッキング(その2)

今日も梅雨らしい天気ですが、涼しいのが救いです。

昨日の続きの除湿器のハッキングです。制御パネルにあるLEDの点灯/消灯をArduinoにフィードバックするためのレベル変換ですが、ググってairvariableさんのFETで異なる電圧のインタフェースを見つけ、手元にあったBSS138で試したらうまく行きました。感謝。

ユニバーサル基板の切れ端にFETを載せて(抵抗は裏に載ってます)、ブレッドボードに挿せるようにピンヘッダを付けてます。電源LEDと連続運転LEDの2つをモニタするので、2回路分載せています。+5Vは共通でよかったです。組んでから気づきました。


除湿器の制御基板と操作パネル基板から、以下の8本の電源・信号を取り出しています。+5V電源は制御基板に載っていた+5Vの三端子レギュレータから引っ張っています。

・電源スイッチ                1本(他方はGND)
・運転切り替えスイッチ      1本(他方はGND)
・電源モニタLED信号       1本
・連続運転モニタLED信号 1本
・圧電ブザー接続/切断    2本
・+5V                          1本
・GND                        1本

除湿器のプラグをコンセントを挿すと除湿器本体とArduinoの電源が入ります。除湿機本体の電源が入るのは元々の仕様。そこから5Vを取り出しているのでArduinoが起動します。Arduinoは起動すると現在の湿度を調べ、設定湿度より高かったら運転開始シーケンスに入ります。運転開始は、圧電ブザーを切断し、電源スイッチの代わりのリレーをオン/オフし、さらに運転切り替えスイッチの代わりにリレーをオン/オフし、圧電ブザーを接続に戻します。このとき、LEDの点灯状態も確認しています。

運転停止も同様に、圧電ブザーを切断し、電源スイッチの代わりのリレーをオン/オフし、圧電ブザーを元に戻します。圧電ブザーは人が除湿器本体の操作ボタンを押したときには鳴るように、通常は接続状態にしておきます。リレーのb接点があれば電力を抑えられるのですが、使ったリードリレーにはないので通常onしています。約10mA程度なので許容範囲かと思います。

なお、湿度の測定は、初回後は10秒毎に行い、最大10回の移動平均で求めています。また、電源を入れてからコンプレッサが動くまで1~2分のタイムラグがあるので、頻繁なオン/オフは行わないようにしています。具体的には一度運転を開始したら最低5分間は運転し続けます。その間にたとえ湿度が設定湿度より下がっても運転を続けます。逆に停止後も5分間は運転を再開しません。


湿度の設定は、タクトスイッチを押すたびに70%,65%,60%,55%,50%と下がり、50%の次は70%に戻ります。この設定湿度はArduinoのEEPROMに記憶するのでコンセントを抜いても保持します。

湿度は運転中も設定できます。運転停止中に湿度設定を行い、現在の湿度より低くなると即座に運転を開始します(前述の5分待ちはなし)。逆も同様に、運転中に湿度設定を行い現在の湿度より高い湿度が設定されると即座に運転を停止します。

全景はこんな感じ。除湿器のたばこセンサの穴からケーブルを引っ張り出して、ブレッドボードに繋いでします。しばらくこの状態で使ってみて、問題がなければユニバーサル基板に載せてケースに入れようと思っています。ケースに入れたときに湿度センサーの反応がどうなるか気になるところです。穴を開けて空気の流れができるようにしておかないと反応が鈍くなりそう。どの程度にするか悩みどころですが、まあ、エイヤーと決めるしかないです。

まだ未完成ですが、なんとか梅雨の季節に間に合ってよかったです。

2020年6月13日土曜日

古い除湿器をハッキング

梅雨入りして蒸し暑い季節になりました。今日は少し涼しいですがまた暑くなりそう。テレワークで終日エアコンを入れているので電気代も気になるところです。

梅雨の季節、仕舞い込んであったシャープ製の古い除湿器DW-3SFで遊んでみようと、引っ張り出してきました。本来は自動運転で湿度70%を超えると自動的に運転が始まるはずですが、湿度センサが不調のようで自動運転しません。そこでマニュアル操作で「連続」を指定して使っていましたが、常に動いてうるさいし、電気代も気になるし、水のタンクがすぐに一杯になってしまうので、昨年新機種に買い替えました。新機種は当たり前ですが湿度に応じできちんとオン/オフして調子いいです。


そこでこの古い機種も、この操作スイッチをArduinoで制御して、設定湿度で自動運転してみようと思った次第です。スイッチは電源と運転切り替えの2つなのでプログラムは単純です。コントローラはArduino Nano互換機と、湿度センサSHT31、小型キャラクタ液晶(いずれもI2C接続)で作ります。設定湿度は、50%~70%で5%刻みでタクトスイッチで設定。設定湿度を超えたら電源オン・連続運転(電源オン直後は自動モードなので1回スイッチを押して連続にする)を始め、湿度が設定温度以下になったら電源オフです。


制御パネルの基板です。タクトスイッチなのでリレーを使って押す操作を模擬します。現在の状態をモニタするLEDが単純な回路ではないのと、5Vではなく11Vで点灯していました。制御基板には5Vの三端子レギュレータが載っていてメインのコントロールは5Vで動いているようですが。

除湿器のスイッチを押す代わりは、小さなリードリレーを使います。駆動電流は10mA程度なので、Arduino Nanoの出力ピンで直接駆動できます。スイッチ用に2個と、操作時に除湿器の圧電ブザーを切るためにもう1個使います。そのままだとスイッチを押すたびに「ピッ」と音がしてうるさいのです。完全に切断してしまうと、水タンクが一杯になったときのアラームも聞こえなくなってしまうので、操作時のみ切るようにしました。

基本的な動作はできるようになりましたが、念のために操作パネルのLED点灯状態をフィードバックしようとしています。単純にトランジスタを使ってレベル変換(11V位でているので、そのままArduinoに繋げない)したところ、うまく行きませんでした。もっと高い入力インピーダンスにしないとダメっぽい。もう少し調べてみます。

全景はこんな感じ。まだ本体カバーを外したままで色々と試して確認しています。

外出もままならないので、Aliexpressで色々と安い電子部品を注文しています。2週間ほど経ちますがまだひとつも届きません。まあ、到着まで30~50日ほどだったので、仕方ないですね。届くまでにこの除湿器コントローラで遊んでいます。

2020年6月7日日曜日

給付金でテレワーク用の椅子購入

テレワークの腰痛対策として給付金で購入した椅子とマットが届きました。箱がテープで雑に止めてあり、中古品のようですが、伝票には新品と印刷されてます。まぁ新品を買ったのだから当然なのですが。

結構重くて20Kg弱です。ある程度の重さがあった方がしっかりしていて安定するだろうという期待があり、重いものを選びました。


中身はこんな感じです。あまり緩衝材が入ってなくて、ギチギチに収めて中身が動かないようにしてありました。ちなみに作業用の軍手が入っていました。

確かに金属バリがあって、組み立て時は軍手をした方が安全です。組み立て説明書に記載されていた内容物一覧の小袋と実際の小袋が合ってなくて、入っていない小袋(ワッシャ)があり、焦りましたが他の袋に入ってました。なお、ビスは余りました。

右側に写っているのが一緒に購入したチェアマットですが、折りたたまれて届いたので折り皺がついています。ロール状に丸めて発送されることを期待しましたが違いました。残念。

背もたれの金具には擦り傷のような痕がついていました。こすっても消えません。まあこんな感じの品質と云う事です。

アマゾンのレビューが異常に良いのが怪しいです。もっとも、似たような椅子のレビューもみんな良くて怪しいので、怪しいのを承知で購入したのですが。

テレワーク部屋に設置した様子です。座り心地はまずまず。これまではダイニングテーブルの椅子を使っていたのでキャスタはないし、回転もしないし、背もたれも動かなかったので、それに比べたら雲泥の差です。重いのも安定してよかったです。腰の辺りにあるメッシュのクッションが腰痛対策に効きそうな感じでした。しばらく使ってみないとわからないですが。

今月はテレワーク予定ですが、時々出勤も始まるようなので、デスクトップMacからMacBookに仕事の環境とデータを移行しています。Macの移行は初めてでしたが、簡単に行えるユーティリティがありました。iPhoneの移行も楽でしたが、似たような感じです。素晴らしい。まだライセンスが必要なアプリが移行できてないので、しばらくデスクトップも併用です。

MacBookはパームレストあたりが結構発熱するので夏場は辛いですね。テレワーク中は外付けキーボードと外部ディスプレイを使ってます。外付けキーボードもデスクトップに付いていた純正のMagicキーボードです。このキーボード、デザインは良いのですが私にはイマイチ打ちにくいです。給付金で買うか?!

2020年6月4日木曜日

XIAOで解くペントミノの動画・スケッチ

今朝、たまたま銀行の通帳の記帳をしたら、10万円の給付金が振り込まれていました。その後、郵便で振り込んだ旨の通知書も届きました。でも、2~3日前に申請用紙も郵送されてきていて、なんか無駄遣いの気がする。オンライン申請した人を除外する手間の方がかかるのでしょうか? オンラインと紙申請を二重に出した人をチェックするのにも手間がかかると思うけど。

給付金を当てにして、テレワーク用の椅子を注文してしまっていました。明日には届く予定。アーロンチェアがよかったけど、20万円くらいするので(見た目は)似たような感じの中国製です。それでも2万5千円ほどでした。しばらくテレワークが続くようなので腰痛対策への投資です。

さて、XIAOで解くペントミノの動画を作りました。iPhone6sのカメラですが、液晶パネルの偏光板との相性がよくないのか、画面はあまり綺麗に撮れません。微妙な色の違いが分からない。肉眼ではもっと綺麗に見えます。


スケッチです。無保証です。
//------------------------------------------------------------
//                                               2020.6.4 naka
//
//  Pentomino puzzle solver by seeeduino XIAO with ST7735 LCD
//
//  Board size : 10x6, 12x5, 15x4, 20x3、8x8(center 4 cells are blank)
//  Note       : Except for 10x6, the number of solutions is double
//               because of the algorithm.
//
//------------------------------------------------------------
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library for ST7735
#include <SPI.h>
//-----------------------------------------
//  define constant for XIAO port
//-----------------------------------------
#define TFT_CS   4
#define TFT_DC   6
#define TFT_SDA 10
#define TFT_SCL  8
#define TFT_RST  5
#define SelectPin 2   // Select Switch (active low)
#define StartPin  3   // Start Switch  (active low)

//-----------------------------------------
//  define constant for LCD ST7735
//-----------------------------------------
#define INITR_BLACKTAB  INITR_MINI160x80
#define SelectMenuX 80
#define SelectMenuY 15
#define SelectMenuH 12
#define ColorNum  12
#define DARKGREEN 0x02E0
#define DARKBLUE  0x0017
#define PARPLE    0x991F
#define GLAY      0x7BEF

//-----------------------------------------
//  define gloval variables for Display
//-----------------------------------------
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_SDA, TFT_SCL, TFT_RST);
uint16_t color_map[ColorNum] = {ST77XX_RED, ST77XX_WHITE, ST77XX_GREEN, DARKGREEN,
                                ST77XX_BLUE, DARKBLUE, ST77XX_CYAN, ST77XX_MAGENTA,ST77XX_YELLOW,
                                ST77XX_ORANGE,PARPLE,GLAY,};  
uint16_t disp_offset_x = 0;
uint16_t disp_offset_y = 0;
uint16_t disp_cellsize = 10;
uint16_t select_board_size = 0;
int      Continue = 0;

//-----------------------------------------
//  define constant for pentomino solver
//-----------------------------------------
#define PIECE_NO        12
#define PIECE_CELL_NO    5
#define PIECE_DIRECTION  8
#define UNUSE            0
#define USE              1
#define INHIBIT         -1
#define EMPTY            0
#define EMPTY_FLAG    9999
#define MAX_X           22
#define MAX_Y           15
#define GOOD             0
#define NG               1

//-----------------------------------------
//  define structure and variable for Solver
//-----------------------------------------
int16_t  board[MAX_X][MAX_Y];
int16_t  use_piece[PIECE_NO];
int16_t  seq_no;
int16_t  size_x;
int16_t  size_y;
int16_t  hsize_x;
int16_t  hsize_y;
int16_t  blank_no;

struct piece_s {
 int16_t x;
 int16_t y;
};

struct piece_org_s {
  struct piece_s piece[PIECE_CELL_NO];
};

struct piece_x {
 int no;
  struct {
    int16_t x[PIECE_CELL_NO-1];
    int16_t y[PIECE_CELL_NO-1];
  } type[PIECE_DIRECTION];
} piece[PIECE_NO];

//-----------------------------------------
//  Arduino(seeeduino) setup()
//-----------------------------------------
void setup() {
  pinMode(SelectPin, INPUT_PULLUP);
  pinMode(StartPin,  INPUT_PULLUP);

  tft.initR(INITR_BLACKTAB);      // Init ST7735S chip, black tab
  tft.setRotation(3);

  while(1) {
    tft.fillScreen(ST77XX_BLACK);  // Erace display
    select_board();
  
    Continue = 0;                  // execution mode 0:step
    init_piece();
    init_board(select_board_size);
    tft.fillScreen(ST77XX_BLACK);  // Erace display

    search_start();
  
    while (digitalRead(SelectPin)==HIGH && digitalRead(StartPin)==HIGH) {}
  }
}

void loop() {
  // nothing
}

//-----------------------------------------
//  function to make pieces (12 tyes)
//-----------------------------------------
void copy_piece(struct piece_org_s *target, struct piece_org_s *source)
{
  int i;
  for (i=0; i<PIECE_CELL_NO; i++) {
    target->piece[i].x = source->piece[i].x;
    target->piece[i].y = source->piece[i].y;
  }
}

void mirror_piece(struct piece_org_s *target,
                  struct piece_org_s *source)
{
  int i;
  for (i=0; i<PIECE_CELL_NO; i++) {
    target->piece[i].x = source->piece[i].x;
    target->piece[i].y = -(source->piece[i].y);
  }
}

void rotate_piece(struct piece_org_s *target)
{
  int i;
  int tmp;
  for (i=0; i<PIECE_CELL_NO; i++) {
    tmp = target->piece[i].x;
    target->piece[i].x = -(target->piece[i].y);
    target->piece[i].y = tmp;
  }
}

int sort_piece(struct piece_s *a, struct piece_s *b)
{
  if (a->x > b->x) return(1);
  else if (a->x == b->x) {
    if (a->y > b->y) return(1);
  }
  return(-1);
}

void origin_piece(struct piece_org_s *target)
{
  int i;
  qsort(&(target->piece[0]),PIECE_CELL_NO,sizeof(struct piece_s),
        (__compar_fn_t)sort_piece);
  for (i=PIECE_CELL_NO-1; i>=0; i--) {
    target->piece[i].x -= target->piece[0].x;
    target->piece[i].y -= target->piece[0].y;
  }
}

void entry_piece(int piece_no,struct piece_org_s *target)
{
  int i;
  int same;
  int no;

  origin_piece(target);

  same = 0;
  for (i=0; i<piece[piece_no].no; i++) {
    if (piece[piece_no].type[i].x[0] == target->piece[1].x &&
        piece[piece_no].type[i].y[0] == target->piece[1].y &&
        piece[piece_no].type[i].x[1] == target->piece[2].x &&
        piece[piece_no].type[i].y[1] == target->piece[2].y &&
        piece[piece_no].type[i].x[2] == target->piece[3].x &&
        piece[piece_no].type[i].y[2] == target->piece[3].y &&
        piece[piece_no].type[i].x[3] == target->piece[4].x &&
        piece[piece_no].type[i].y[3] == target->piece[4].y) {
       same = 1;
       break;
     }
  }

  if (same == 0) {
    no = piece[piece_no].no++;
    for (i=0; i<PIECE_CELL_NO-1; i++) {
      piece[piece_no].type[no].x[i] = target->piece[1+i].x;
      piece[piece_no].type[no].y[i] = target->piece[1+i].y;
    }
  }
}

void init_piece()
{
  int i,j,k;
  int no;
  struct piece_org_s piece_tmp;

  static struct piece_org_s piece_org[PIECE_NO] = {
     1,0,  0,1,  1,1,  2,1,  1,2,   /*  0.   X      */
                                    /*      XXX     */
                                    /*       X      */
     0,0,  1,0,  2,0,  3,0,  4,0,   /*  1.  XXXXX   */

     0,0,  1,0,  2,0,  3,0,  0,1,   /*  2.  XXXX    */
                                    /*      X       */

     0,0,  1,0,  2,0,  3,0,  1,1,   /*  3.  XXXX    */
                                    /*       X      */

     0,0,  1,0,  2,0,  0,1,  2,1,   /*  4.  XXX     */
                                    /*      X X     */

     0,0,  1,0,  2,0,  0,1,  1,1,   /*  5.  XXX     */
                                    /*      XX      */

     0,0,  1,0,  2,0,  2,1,  3,1,   /*  6.  XXX     */
                                    /*        XX    */

     0,0,  0,1,  1,1,  2,1,  1,2,   /*  7.  X       */
                                    /*      XXX     */
                                    /*       X      */

     0,0,  0,1,  1,1,  2,1,  2,2,   /*  8.  X       */
                                    /*      XXX     */
                                    /*        X     */

     0,0,  1,0,  1,1,  2,1,  2,2,   /*  9.  XX      */
                                    /*       XX     */
                                    /*        X     */

     0,0,  1,0,  2,0,  0,1,  0,2,   /* 10.  XXX     */
                                    /*      X       */
                                    /*      X       */

     0,0,  1,0,  2,0,  1,1,  1,2,   /* 12.  XXX     */
                                    /*       X      */
                                    /*       X      */
  };

  /*** Init ***/
  for (i=0; i<PIECE_NO; i++) {
    piece[i].no = 0;
    for (j=0; j<PIECE_DIRECTION; j++) {
      for (k=0; k<PIECE_CELL_NO-1; k++) {
        piece[i].type[j].x[k] = 0;
        piece[i].type[j].y[k] = 0;
      }
    }
  }

  /*** Main ***/
  for (i=0; i<PIECE_NO; i++) {
    for (j=0; j<2; j++) {    /*** normal and mirror   ***/
      if (j==0) copy_piece  (&piece_tmp,&piece_org[i]);
      else      mirror_piece(&piece_tmp,&piece_org[i]);
      for (k=0; k<4; k++) {  /*** 4 rotate directions ***/
        if (k>0) rotate_piece(&piece_tmp);
        entry_piece(i,&piece_tmp);
      }
    }
  }

}

//-----------------------------------------
// Display answer
//-----------------------------------------
int display()
{
  int x,y,i;

  char buff[16];
  sprintf(buff,"no:%4d",seq_no);              // answer number

  tft.fillRect(132, 10, 25, 8, ST77XX_BLACK); // Erase
  tft.setCursor(115, 10);
  tft.setTextColor(ST77XX_WHITE);             // Display answer number
  tft.print(buff);

  for (y=1; y<=size_y; y++) {
    uint16_t ypos = disp_offset_y + (y-1) * disp_cellsize;
    for (x=1; x<=size_x; x++) {
      uint16_t color = color_map[board[x][y]-1];
      uint16_t xpos  = disp_offset_x + (x-1) * disp_cellsize;
      tft.fillRect(xpos, ypos, disp_cellsize, disp_cellsize, color); // Display cell
    }
  }

  return(check_sw()); // check select,start switch to Start/Stop/Continue
    
  return(0);
}

/*------------------------*/
/*  check_blank_area      */
/*------------------------*/
void search_blank(int x,int y) {
    board[x][y] = EMPTY_FLAG;
    blank_no++;
    if (board[x][y+1]==EMPTY) search_blank(x,y+1);
    if (board[x+1][y]==EMPTY) search_blank(x+1,y);
    if (board[x-1][y]==EMPTY) search_blank(x-1,y);
    if (board[x][y-1]==EMPTY) search_blank(x,y-1);
  }

void search_blank_clear(int x,int y) {
    while(x<=size_x) {
      while(y<=size_y) {
        if (board[x][y]==EMPTY_FLAG) board[x][y] = EMPTY;
        y++;
      }
      y = 1;
      x++;
    }
  }

/*------------------------*/
/*  check_board           */
/*------------------------*/
int check_board(int x,int y)
{

  if (board[x+1][y]  !=EMPTY &&
      board[x][y+1]  !=EMPTY) return NG;

  if (board[x][y+2]  !=EMPTY &&
      board[x+1][y+1]!=EMPTY &&
      board[x+1][y]  !=EMPTY) return NG;

  if (board[x][y+1]  !=EMPTY &&
      board[x+1][y+1]!=EMPTY &&
      board[x+2][y]  !=EMPTY &&
      board[x+1][y-1]!=EMPTY) return NG;

  blank_no =0;
  search_blank(x,y);
  search_blank_clear(x,y);
  if (blank_no%PIECE_CELL_NO!=0) return NG;

  return GOOD;
}

/*------------------------*/
/*  put_piece             */
/*------------------------*/
int search(int posx,int posy,int put_no)
{
  int i,j;
  int avno;
  int piece_no;

  while(board[posx][posy]!=EMPTY) {
    posy++;
    if (posy>size_y) {
      posy = 1;
      posx++;
    }
  }
  /** check board **/
  if (check_board(posx, posy)) return(0);

  for (i=1; i<PIECE_NO; i++) {

    if (use_piece[i]==UNUSE) {

      for (j=0; j<piece[i].no; j++) {

        /*** area check ***/
        if (board[piece[i].type[j].x[0]+posx]
                 [piece[i].type[j].y[0]+posy]==EMPTY &&
            board[piece[i].type[j].x[1]+posx]
                 [piece[i].type[j].y[1]+posy]==EMPTY &&
            board[piece[i].type[j].x[2]+posx]
                 [piece[i].type[j].y[2]+posy]==EMPTY &&
            board[piece[i].type[j].x[3]+posx]
                 [piece[i].type[j].y[3]+posy]==EMPTY) {
          /*** put ***/
          board[posx][posy] =
          board[piece[i].type[j].x[0]+posx]
               [piece[i].type[j].y[0]+posy] =
          board[piece[i].type[j].x[1]+posx]
               [piece[i].type[j].y[1]+posy] =
          board[piece[i].type[j].x[2]+posx]
               [piece[i].type[j].y[2]+posy] =
          board[piece[i].type[j].x[3]+posx]
               [piece[i].type[j].y[3]+posy] = i+1; // piece no.

          if (put_no!=PIECE_NO) {
            use_piece[i] = put_no;
            /*** search next piece place ***/
            if (search(posx,posy+1,put_no+1)<0) {
              return(-1);
            }
            use_piece[i] = UNUSE;
          }
          else {
            /*** display ***/
            seq_no++;
            if (display()<0) {
              return (-1);
            }
          }

          /*** get last piece ***/
          board[posx][posy] =
          board[piece[i].type[j].x[0]+posx]
               [piece[i].type[j].y[0]+posy] =
          board[piece[i].type[j].x[1]+posx]
               [piece[i].type[j].y[1]+posy] =
          board[piece[i].type[j].x[2]+posx]
               [piece[i].type[j].y[2]+posy] =
          board[piece[i].type[j].x[3]+posx]
               [piece[i].type[j].y[3]+posy] = EMPTY;
        }

      } /* end of for-j loop */

    }  /* end of unuse if (unuse piece) */

  }  /* end of for-i loop */

  return(0);

} /* end of search */

/*------------------------*/
/*  initialize            */
/*------------------------*/
void init_board(uint16_t size)
{
  int i,j;

  /*** set area type ***/
  size_x = 10;
  size_y =  6;
  hsize_x = 5;
  hsize_y = 3;

    if (size==0) {     // 10x6 size
      size_x = 10;
      size_y =  6;
      hsize_x = 5;
      hsize_y = 3;
      disp_offset_x = 0;
      disp_offset_y = 10;
      disp_cellsize = 10;
    }
    else if (size==1) { // 12x5 size
      size_x = 12;
      size_y =  5;
      hsize_x = 6;
      hsize_y = 4;
      disp_offset_x = 10;
      disp_offset_y = 20;
      disp_cellsize = 10;
    }
    else if (size==2) { // 15x4 size
      size_x = 15;
      size_y =  4;
      hsize_x = 13;
      hsize_y = 2;
      disp_offset_x = 5;
      disp_offset_y = 30;
      disp_cellsize = 10;
    }
    else if (size==3) {  // 20x3 size
      size_x = 20;
      size_y =  3;
      hsize_x = 10;
      hsize_y = 2;
      disp_offset_x = 0;
      disp_offset_y = 30;
      disp_cellsize = 8;
    }
    else if (size==4) { // 8x8 size
      size_x =  8;
      size_y =  8;
      hsize_x = 4;
      hsize_y = 4;
      disp_offset_x = 10;
      disp_offset_y = 0;
      disp_cellsize = 10;
    }
    
  /*** init counter ***/
  seq_no = 0;

  /*** init board ***/
  for (i=0; i<MAX_X; i++)
    for (j=0; j<MAX_Y; j++)
      board[i][j] = INHIBIT;

  for (i=1; i<=size_x; i++)
    for (j=1; j<=size_y; j++)
      board[i][j] = EMPTY;

  if (size_x==8 && size_y==8) {
    board[4][4] = INHIBIT;
    board[4][5] = INHIBIT;
    board[5][4] = INHIBIT;
    board[5][5] = INHIBIT;
  }

  /*** init use_piece ***/
  for (i=0; i<PIECE_NO; i++)
    use_piece[i] = UNUSE;

} /*** end of init ***/

/*------------------------*/
/*  put X piece           */
/*------------------------*/
void search_start() {
  int posy = 3;
  int posx = 1;
  int put_no = 1;
  use_piece[0] = put_no;

  while(posx<hsize_x) {
    /*** put ***/
    if (board[piece[0].type[0].x[0]+posx]
             [piece[0].type[0].y[0]+posy]==EMPTY &&
        board[piece[0].type[0].x[1]+posx]
             [piece[0].type[0].y[1]+posy]==EMPTY &&
        board[piece[0].type[0].x[2]+posx]
             [piece[0].type[0].y[2]+posy]==EMPTY &&
        board[piece[0].type[0].x[3]+posx]
             [piece[0].type[0].y[3]+posy]==EMPTY) {
    board[posx][posy] =
    board[piece[0].type[0].x[0]+posx]
         [piece[0].type[0].y[0]+posy] =
    board[piece[0].type[0].x[1]+posx]
         [piece[0].type[0].y[1]+posy] =
    board[piece[0].type[0].x[2]+posx]
         [piece[0].type[0].y[2]+posy] =
    board[piece[0].type[0].x[3]+posx]
         [piece[0].type[0].y[3]+posy] = put_no;

    if (search(1,1,put_no+1)<0) {
      return;
    }

    /*** get ***/
    board[posx][posy] =
    board[piece[0].type[0].x[0]+posx]
         [piece[0].type[0].y[0]+posy] =
    board[piece[0].type[0].x[1]+posx]
         [piece[0].type[0].y[1]+posy] =
    board[piece[0].type[0].x[2]+posx]
         [piece[0].type[0].y[2]+posy] =
    board[piece[0].type[0].x[3]+posx]
         [piece[0].type[0].y[3]+posy] = EMPTY;

    }
    posy++;
    if (posy>hsize_y) {
      posy = 2;
      posx++;
    }
  }

  return;
}

//-----------------------------------------
//  Display 1st menu (select board menu)
//-----------------------------------------
void disp_selectmenu() {
  char* mode_tbl[5] = {"10x6","12x5","15x4","20x3"," 8x8"};
  
  tft.setTextColor(ST77XX_MAGENTA);
  tft.setTextSize(0);
  tft.setCursor(0, 0);
  tft.print("*** Pentomino Solver ***");
  tft.setTextSize(0);
  tft.setTextColor(ST77XX_WHITE);
  tft.setCursor(20, 24);
  tft.print("Select");
  tft.setCursor(20, 36);
  tft.print(" board");
  tft.setCursor(20, 48);
  tft.print("  size");

  for (int i=0;i<5;i++) {
    uint16_t x = SelectMenuX;
    uint16_t y = SelectMenuY + i * SelectMenuH;
    tft.setCursor(x, y);
    tft.setTextColor(ST77XX_WHITE);
    tft.print(mode_tbl[i]);
  }  
}

void disp_select_triangle(uint16_t mode) {
  uint16_t x1 = SelectMenuX-10, y1 = SelectMenuY + mode * SelectMenuH;
  uint16_t x2 = x1+3, y2 = y1 + SelectMenuH/4;
  uint16_t x3 = x1,   y3 = y1 + SelectMenuH/2;
  tft.fillRect(x1,15,7,80,ST77XX_BLACK);  // clear previous triangle
  tft.fillTriangle(x1,y1,x2,y2,x3,y3,ST77XX_RED);
}

//-----------------------------------------
//  Select board by Select Switch
//-----------------------------------------
void select_board() {
    disp_selectmenu();

    // Wait release Select,Start switch
    while(digitalRead(StartPin)==LOW || digitalRead(SelectPin)==LOW) {};

    // Move triangle mark each time the select switch
    disp_select_triangle(select_board_size);
    int select_sw = HIGH;
    int start_sw  = HIGH;
    while (1) {
      select_sw = digitalRead(SelectPin);
      start_sw  = digitalRead(StartPin); 
      if (select_sw==LOW) {
        select_board_size++;
        if (select_board_size>4) {
          select_board_size = 0;
        }
        disp_select_triangle(select_board_size);
        while (!digitalRead(SelectPin)) {} // wait release Serect SW
        delay(10);
      }
      else if (start_sw==LOW) {
        return;
      }
    }
}

//-----------------------------------------
// check SW status
//-----------------------------------------
int check_sw() {

  if (Continue==1) { // Continuas mode
    // Check stop of continuous mode
    if (digitalRead(StartPin)==LOW) {
      Continue = 0;  
      while (digitalRead(StartPin)==LOW) {
        if (digitalRead(SelectPin)==LOW) {
            return (-1); // Press Start and Select at the same time to end
        }
      }
    }
  }

  if (Continue==0) {  // Stepping mode
    while (digitalRead(SelectPin)==HIGH and digitalRead(StartPin)==HIGH) {}
    if (digitalRead(SelectPin)==LOW) {
      Continue = 1; // Continuas mode
    }
  }

  return(0);
}

//------------------------------------------------------------
//  end of file
//------------------------------------------------------------