16x8 LEDマトリクスの表示を矯正する

### はじめに
この記事は、Adafruit LED Backpack Library を使って arduino から 16x8 LEDマトリクス [K-LED1608K33D](http://www.aitendo.com/product/16658) を使う(正しい向きで文字・グラフィックを表示できるようにする) という内容です。

***
### arduino で LEDマトリクスに文字を表示してみるも表示がくずれる
製品の説明によれば、Adafruit の arduino LED Backpack Library が使えるとあります。確かにコントローラチップに Adafruit の LEDマトリクスと同じ HT16K33 が使われているようです。
さっそく arduino IDE に LED Backpack Library とその他必要なライブラリをインストールし、ライブラリ付属の matrix16x8 というサンプルコードを動かしてみました。

* arduino IDE 1.8.10
* Adafruit LED Backpack Library 1.1.6
* Adafruit GFX Library 1.7.3
* 16x8 LEDマトリクス [K-LED1608K33D](http://www.aitendo.com/product/16658)

が、表示の向きがおかしい。。片方の 8x8マトリクスにしか表示されない。。文字も左右反転してるし。。
気を取り直して minimatrix16x8 というサンプルも動かしてみましたが、16x8全体に表示はされるものの文字は左右反転しています。

でもこれでLEDマトリクスキット自体は動いているのが確認できたので、あとはソフトウェアでなんとかできる領域でしょう。
さきほど試した matrix16x8 と minimatrix16x8 のソースコードを見比べると、ベースとなっているコード（class Adafruit_LEDBackpack）は同じで、LEDマトリクスデバイス種別ごとにクラスメンバー関数の drawPixel() を適切に書いてあげることで正しい場所のLEDを点灯させることができそうです。

***
### 気を取り直して drawPixel() を解読する
ソースコードによると、

* drawPixel() は、指定された(x, y)の場所にLED点灯のためのフラグを立てる
* 点灯フラグは、uint16_t displaybuffer[8] に格納されている（16bit × 8）
* displaybuffer[8] の 0～7bit が一つの8x8マトリクス、8～15bit がもう一つの8x8マトリクスを表している
* さらに drawPixel() では、rotation（表示の向き）の設定に従ってLED点灯場所を適切な位置に移動している

つまり、displaybuffer[] の上位bit下位bitと二つのマトリクスの対応関係、x座標y座標と displaybuffer[]の各bitの対応関係、が分かれば正しい位置のLEDを点灯させることができます。
LEDマトリクス基盤上の「aitendo」のロゴが正しい向きになっている状態を rotation＝0 の状態と決めたうえで、(0, 0)や(15, 7)を表示させてどの位置のLEDが点灯するのかを調べたところ、以下の対応関係でした。

横軸(x座標)：displaybuffer[n]のn
縦軸(y座標)：displaybuffer[n]の bit0(LSB)～bit15(MSB)

||→ x座標|0|1|2|3|4|5|6|7|
|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|
|**↓ y座標**|n=|0|1|2|3|4|5|6|7|
|**0**|bit=|0|0|0|0|0|0|0|0|
|**1**|bit=|1|1|1|1|1|1|1|1|
|**2**|bit=|2|2|2|2|2|2|2|2|
|**3**|bit=|3|3|3|3|3|3|3|3|
|**4**|bit=|4|4|4|4|4|4|4|4|
|**5**|bit=|5|5|5|5|5|5|5|5|
|**6**|bit=|6|6|6|6|6|6|6|6|
|**7**|bit=|7|7|7|7|7|7|7|7|

||→ x座標|8|9|10|11|12|13|14|15|
|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|
|**↓ y座標**|n=|0|1|2|3|4|5|6|7|
|**0**|bit=|8|8|8|8|8|8|8|8|
|**1**|bit=|9|9|9|9|9|9|9|9|
|**2**|bit=|10|10|10|10|10|10|10|10|
|**3**|bit=|11|11|11|11|11|11|11|11|
|**4**|bit=|12|12|12|12|12|12|12|12|
|**5**|bit=|13|13|13|13|13|13|13|13|
|**6**|bit=|14|14|14|14|14|14|14|14|
|**7**|bit=|15|15|15|15|15|15|15|15|

例えば、

* (0,0) の LEDを点灯したければ、displaybuffer[0] |= 0x1
* (1,1) displaybuffer[1] |= 0x2
* (15,7) displaybuffer[7] |= 0x8000 (1<<15)

ここまでわかれば、rotation=1(右90度回転)、rotation=2(右180度回転)、rotation=3(右270度回転)の時にどの位置のLEDを点灯させればよいかも drawPixel()内で個別に記述します。

***
### 正しく動くサンプルコード
Adafruit LED Backpack Library の matrix16x8 サンプルコードにならって、文字表示（スクロールあり）、8x8 bitmap 表示、点、直線、四角、円を順次書いていくサンプルコードです。

```C++:16x8LEDmatrix.ino
#include <Wire.h>
#include <Adafruit_GFX.h>
#include "Adafruit_LEDBackpack.h"

#ifndef _swap_int16_t
#define _swap_int16_t(a, b) { int16_t t = a; a = b; b = t; }
#endif

class aitendo_KLED1608K33D_8x16matrix : public Adafruit_LEDBackpack, public Adafruit_GFX {
 public:
  aitendo_KLED1608K33D_8x16matrix(void);

  void drawPixel(int16_t x, int16_t y, uint16_t color);

 private:
};

aitendo_KLED1608K33D_8x16matrix::aitendo_KLED1608K33D_8x16matrix(void) : Adafruit_GFX(16, 8) {
}

void aitendo_KLED1608K33D_8x16matrix::drawPixel(int16_t x, int16_t y, uint16_t color) {
  if ((y < 0) || (x < 0)) return;
  if ((getRotation() % 2 == 0) && ((x >= 16) || (y >= 8))) return;
  if ((getRotation() % 2 == 1) && ((y >= 16) || (x >= 8))) return;

  switch (getRotation()) {
  case 0:
    if (x >= 8) {
      x -= 8;
      y += 8; 
    }
    break;
  case 1:
    y = 16 - y - 1;
    if(y >= 8) {
      y -= 8;
      x += 8;
    }
    _swap_int16_t(x, y);
    break;
  case 2:
    x = 16 - x - 1;
    y = 8 - y - 1;
    if (x >= 8) {
      x -= 8;
      y += 8; 
    }
    break;
  case 3:
    x = 8 - x - 1;
    if(y >= 8) {
      y -= 8;
      x += 8;
    }
    _swap_int16_t(x, y);
    break;
  }

  if (color) {
    displaybuffer[x] |= 1 << y;
  } else {
    displaybuffer[x] &= ~(1 << y);
  }
}

aitendo_KLED1608K33D_8x16matrix matrix = aitendo_KLED1608K33D_8x16matrix();

void setup() {
  matrix.begin(0x70);
  matrix.setBrightness(3);
  matrix.setRotation(0);
}

static const uint8_t PROGMEM
  smile_bmp[] =
  { B00111100,
    B01000010,
    B10100101,
    B10000001,
    B10100101,
    B10011001,
    B01000010,
    B00111100 };

void loop() {
  matrix.setTextSize(1);
  matrix.setTextWrap(false);
  matrix.setTextColor(LED_ON);
  for (int8_t x=7; x>=-7*12; x--) {
    matrix.clear();
    matrix.setCursor(x,0);
    matrix.print("Hello World!");
    matrix.writeDisplay();
    delay(100);
  }
  delay(500);

  matrix.clear();
  matrix.drawBitmap(0, 0, smile_bmp, 8, 8, LED_ON);
  matrix.writeDisplay();
  delay(1000);

  matrix.clear();
  matrix.drawPixel(0, 0, LED_ON);  
  matrix.writeDisplay();
  delay(1000);

  matrix.clear();
  matrix.drawLine(0,0, 15,7, LED_ON);
  matrix.writeDisplay();
  delay(1000);

  matrix.clear();
  matrix.drawRect(0,0, 16,8, LED_ON);
  matrix.fillRect(2,2, 12,4, LED_ON);
  matrix.writeDisplay();
  delay(1000);

  matrix.clear();
  matrix.drawCircle(8,3, 3, LED_ON);
  matrix.writeDisplay();
  delay(1000);
}
```

***
### もう一つ、電流の確認
LEDマトリクスの論理的な動作はできるようになりましたが、実はもう一つ懸念事項があります。LEDマトリクス表示に必要な電流です。

Adafruit LED Backpack Library では setBrightness(0～15) でLEDの明るさが設定できますので、全点灯状態で明るさを変えながら LEDマトリクスキットに流れ込む電流量をテスターで測ってみました。
明るさ 0～15 の値はシリアル通信経由で任意のタイミングで任意の値を arduino に入力できるようにしました。

```C++:current_test.ino
// ポイントだけ抜粋
aitendo_KLED1608K33D_8x16matrix matrix = aitendo_KLED1608K33D_8x16matrix();
String str;
int br;

void setup() {
  Serial.begin(115200);

  matrix.begin(0x70);
  matrix.setRotation(0);
}

void loop() {
  if(Serial.available()) {
    str = Serial.readString();
    br = str.toInt();
    if(br < 0) { br = 0; }
    else if(br > 15) { br = 15; }

    matrix.clear();
    matrix.setBrightness(br);
    matrix.fillRect(0,0, 16,8, LED_ON);
    matrix.writeDisplay();
    Serial.println("[" + String(br) + "]");
  }
}
```

|setBrightness(n)|電流(mA)|
|:--|:--|
|0|10.5|
|1|20.4|
|2|30.1|
|3|39.5|
|4|48.9|
|5|58.1|
|6|67.2|
|7|76.3|
|8|85.3|
|9|94.2|
|10|103.1|
|11|112.0|
|12|120.6|
|13|129.3|
|14|137.8|
|15|145.9|

これは USB給電の arduino の 5Vピンで十分給電可能な範囲です。このためだけに外部電源はつけたくないですからね。
また、Brightness 10 以上ではほとんど明るさに変化はなく、かなりまぶしいです。個人的には Brightness 3～5 くらいでも十分。

***
### ここまでは下準備。本題はまた
なぜこんなことやってるかというと、家で動かしているヘッドレスな Linux サーバの状態を監視したい。
24H動かしているサーバなので、異常があったら知りたいのです。でもステータスをリアルタイムで Twitter に投げ込んで欲しい訳ではなく、家に帰ってPCデスクに座ると隅っこでアラートを示す何かが控えめに点滅しているくらいのつつましい監視がしたい。

手段をいくつか考えてみました。

* ディスプレイつなぐ：ヘッドレスで運用してるメリットを損なう
* 音を鳴らす：夜中に鳴ると困る。つつましい監視に反する
* LED光らす：HDDアクセスLEDのように。まあ一番近いかな

ただLED光らすにしても、サーバから点灯をコントロールできて、かつサーバから物理的に少し離れているPCデスクまでLEDを持ってこれる手段が必要なので、サーバ単独ではなく arduino に LED コントローラを担ってもらうことにしました。USBシリアルでサーバと arduino 接続すれば手間もないし、USBで給電もできて一石二鳥。

arduino はさむなら単独LEDでは面白くないのでLCDモジュールかLEDマトリクスを使って、通常時はサーバのリソース情報を、異常時はアラートを表示させることにしました。大きすぎず、一定の情報量が表示できて、視認性も良いもの（これ重要）ということで、[aitendo](http://www.aitendo.com/) で売っていた 16x8 LEDマトリクスキット([K-LED1608K33D](http://www.aitendo.com/product/16658))を使うことにしました。