フォト
2017年3月
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  
無料ブログはココログ

表示器制御関連

2016年8月 7日 (日)

Arduinoで8x8LEDマトリックスを直接(直結)で制御する

以前、IchigoDotSが電流制御抵抗なしにマイコンでLEDマトリックスを制御しており、
「どういう制御しているのだろう?」と疑問に思い、調査がてらArduinoでやってみました。

※注意 真似するとArduinoが破損する場合があります。

動作の様子



LEDマトリックスは秋月電子で購入した MNA20SR092G を利用しました。

組み立て
D7、D8ピンは隙間が空いているのですが、LEDマトリックスのピンを広げて装着させました。

Dscn5520

反対側のピンは、ジャンパワイヤー(メス・オス)で結線しました。

Dscn5515


回路図(結線図)

02


     接続ピンの対応表
        2つの表は同じものです(左がLED PIN順 、右がMATRIX割り付け機能順)。
        表のLEDピンは上図のPIN1~PIN16に対応します。
        MATRIXはPINに割り当てられている機能を示しています。       

                     LED PIN順                          MATRIX 割り付け順
      01_2

      アナログピンA0~A3もデジタル入出力として利用しています。
      A0~A3はデジタルピンとして使う場合ピン番号14~17として利用出来ます。

スケッチ
  ダウンロード matrix4.zip (22.8K)

メイン処理部のみ掲載しました。

#include <avr/interrupt.h>
#include <TimerOne.h>
#include "misakiUTF16.h"

//*************************************
//   出力ピンの定義(MNA20SR092G接続用)
//*************************************
// 横
#define COL1  10
#define COL2  7
#define COL3  6
#define COL4  16
#define COL5  4
#define COL6  15
#define COL7  12
#define COL8  13

// 縦
#define ROW1  17
#define ROW2  11
#define ROW3  2
#define ROW4  14
#define ROW5  9
#define ROW6  3
#define ROW7  8
#define ROW8  5

#define UARTBPS 9600
#define TIMERTIK 100

//*****************************
// グローバル変数
//*****************************
// COL,ROWのピン割り付けテーブル
uint8_t col[8] = {COL1,COL2,COL3,COL4,COL5,COL6,COL7,COL8};
uint8_t row[8] = {ROW1,ROW2,ROW3,ROW4,ROW5,ROW6,ROW7,ROW8};

// 表示用バッファ(8x8ドット分)
uint8_t pdata[8];
uint8_t line, colno;

//*********************************
// ドットマトリックス表示用関数
//*********************************

//
// digitalWrite高速化版
//
void new_digitalWrite(uint8_t pin, uint8_t val) {
  uint8_t bit = digitalPinToBitMask(pin);
  volatile uint8_t *out = portOutputRegister(digitalPinToPort(pin));
  if (val == LOW)
    *out &= ~bit;
  else
    *out |= bit;  
}

// 点灯する行の選択
// y: 行(0〜7)
void selectRow(uint8_t y) {
  for(uint8_t i=0; i <8; i++)
    new_digitalWrite(row[i], HIGH);   
  new_digitalWrite(row[y], LOW);
}

// 1行分データの出力
void setData(uint8_t d) {
  uint8_t msk = B10000000;
  for (uint8_t i = 0; i<8; i++) {
    if (msk & d) { 
      new_digitalWrite(col[i], HIGH);
    } else {
      new_digitalWrite(col[i], LOW);
    }
    msk>>=1;
  }  
}

// ドット単位のダイナミック駆動
void update_dot() {
  setData(0);
  selectRow(line);
  setData(pdata[line] & (B10000000>>colno));
  colno++;
  if (colno == 8) {
    colno =0;
    line++;
    if (line==8) 
      line = 0;  
  }
}

// ドットマトリックスの表示OFF
void matrix_off() {
  for (uint8_t i = 0; i < 8; i++)
       new_digitalWrite(row[i], HIGH);
}

// バッファクリア
void clrar_buf() {
  for (uint8_t i=0; i <8; i++) 
     pdata[i]=0;
}

// バッファへの書き込み
// 8x8フォントパターンを表示用バッファに書き込む
void write_buf(uint8_t* dat) {
  for (uint8_t i=0; i<8; i++)
     pdata[i]= dat[i];
}

// 指定座標にフォントパターンをセット
void write_bufat(uint8_t* fptr, uint8_t x, uint8_t y) {
  uint8_t w;
  
  if (x>7 || y >7)
    return;     
  for (byte j=y,i=0; j < 8; j++,i++)
    pdata[j] = (pdata[j]>>(8-x))<<(8-x) | fptr[i]>>x;
}

// バッファーデータのスクロール
// h_mode : 0 なし,1 左 ,2 右 
// v_mode : 0 なし,1 上, 2 下      
void scroll(uint8_t h_mode, uint8_t v_mode) {
  if (h_mode ==1) 
    for (byte i = 0; i < 8; i++) 
      pdata[i] = pdata[i]<<1;
  if (h_mode ==2)
    for (byte i = 0; i < 8; i++)
      pdata[i] = pdata[i]>>1;
  if (v_mode ==1) {
    for (byte i = 0; i < 7; i++)
     pdata[i]= pdata[i+1];
    pdata[15]=0;
  }
  if (v_mode == 2) {
    for (byte i = 7; i >0; i--)
     pdata[i]= pdata[i-1];
    pdata[0] = 0;    
  }
}

// スクロールしながらパターンを表示
void scrollout(uint8_t* fptr, uint16_t dly) {
  for (byte i=0; i<8; i++) {
    scroll(1, 0);
    write_bufat(fptr, 7-i, 0) ;
    delay(dly);
  }
}  

char buf[129];
bool lfgMsg;

//*********************************
// メッセージ受信チェック
//*********************************
void chekMessage() {
  // シリアルデータ受信
  uint8_t n=0;
  while (Serial.available() > 0) {
    buf[n] = Serial.read();
    n++;
    if (n>128) {
      break;  
    }
  }

  buf[n] = 0;
  lfgMsg = true;
  Serial.println("OK");
  Serial.flush();  
}

//*********************************
// メイン処理
//*********************************
void setup() {
  for (uint8_t i=0; i <8; i++) {
    // ピンモードの設定
    pinMode(col[i], OUTPUT);
    pinMode(row[i], OUTPUT);
  }
  Serial.begin(UARTBPS);
  clrar_buf();
  line = 0;
  colno = 0;
  lfgMsg = false;
   
  Timer1.initialize(TIMERTIK); 
  Timer1.attachInterrupt(update_dot);  
  //setfull();
}

char *str="こんにちは さいたま県♪ 今、さいたまがアツい!";
uint8_t fnt[FONT_LEN];
char *pUTF8;

void setfull() {
  for (uint8_t i=0; i <8;i++) 
    pdata[i] = 0xff;
}

void loop() { 
  if (lfgMsg) {
    pUTF8 = buf;
    lfgMsg = false;
  } else {
    pUTF8 = str;  
  }
  while(*pUTF8) {
    pUTF8 = getFontData(fnt, pUTF8,true);     // フォントデータの取得
    scrollout(fnt, 70);                      // スクロールしながら文字を表示
    if (Serial.available() > 0 && !lfgMsg) 
      chekMessage();      
  }
  delay(1000);
}

解説

1)LEDの駆動
電流制御抵抗なしでも、LEDにかける電圧のディューティー比 とパルス幅を調整すると
定格内でLEDの駆動が行えます。
(専門家ではないので、間違えているかもしれませんが..)

まずは、利用したLEDマトリックス MNA20SR092Gのデータシートに下記の記載があります。

  03

    IPF 100mA が注目する数値です。
    ディューティー比 1/10サイクル パルス幅 0.1ms内なら100mAまで可能です。

    これを守れば、通常の30mA(IAF)を超えてもOKです。
    これがまず可能にする理由の第一点
    ((パルス幅 0.1msはちょっと短すぎです。別製品 OSL641501-ARAだと1ms)

LEDに関しては問題なしです。

参考にしたサイト
   ikkei blog -  Arduino UNO にLEDを直結して電流を測ってみた


2)Arduino
Arduinoの出力ポートの出力抵抗(インピータンス)が30Ω程度あります。

LEDのVfが2.0Vなので、
ソース(HIGH)→ LED → シンク (LOW)で点灯させた場合に流れる電流は
  (5.0 - 2.0) / (30+30) = 0.0645 = 50mA
となります(自信はないので断言は出来ません)。

arduino的には最大定格40mAを超えていますね。

一方別の記載で1ピンで推奨の20mA超えても
ソースで他ピンを含めた合計で100mA厳守、同シンクで150mA厳守の記載があります。
インターネット上で調べると定格以上でも70mA位流せるようです。
ただし、動作保証範囲外の利用となります。

ここは、趣味で利用する範囲で許容範囲としましょう。


実際にオシロスコープで測定でしてみて、
3.3V稼働時に28mA、5V稼働で48mA流れていることを確認しましった。

スケッチの実装について

Arduin IDE 1.6.9にて実装しています。

ハードウエア的制約を考慮して実装しました。
ダイナミック駆動は1ドット単位で制御するようにしました。
(瞬間的には常に最大で1点しか点灯していないことになります)
ボタン電池による駆動を視野にいれ省電力化に貢献すると思います。

パルス幅 0.1ms内を考慮し、0.1ms間隔のタイマー割り込みを利用し、
1回の割り込みで1ドットの制御、64回の割り込みで1画面分64ドットの制御を
行うようにしました。ディューティー比 1/64 となります。
    
6.4msで1画面の更新となります。
人間の目で1/60秒以内ならチラついて見えないのでこの更新なら十分でしょう。

この周期でタイマ割り込みで行うにおいてOneTimerライブラリを使いました。
更に0.1ms内でのLED制御を考慮してdigitalWrite()を高速版に置き換えました。

実際にオシロスコープで測定すると、0.1ms幅のパスルは出ていなくて0.04msでした。
パルス周波数は156.2Hzであるため正確な間隔で割り込みが発生しています。
(周期 0.1ms x 64 = 6.4 ms   1/6.4ms = 156.25Hz)

日本語表示は、美咲フォントを利用しています。

機能的には、固定文字列 "こんにちはさいたま県.."を表示しています。

さらに、シリアル通信にて受信した文字列を表示出来るようにしました。
(まだ作りが甘いです。たまにおかしくなります) 

実際の実装は、3Vボタン電池稼働でやろうと思っています。
電流制御抵抗使用しないのも省電力につながると思います。
ほとんどIchigoDotのマネですが..
Atmega328(中身はArduino) 3V 内部RCクロック 8MHzにて動作しました。

Dscn5511



2016年7月23日 (土)

アクティブマトリクス蛍光表示管の実験用表示モジュールの試用

Facebookのお友達からお借りしました、「アクティブマトリクス蛍光表示管(CL-VFD)
MW25616L 実験用表示モジュール」を調査中です。

16x256ドットのグラフィック表示が可能なモジュールです。

Dscn5467

制御用にAtmega328pが使われており、Arduino IDE環境にてプログラム開発が出来ます。
いくつか、自由に使えるGPIOピンが開放されており、I2Cやシリアル通信も使えそうです。

日本語フォントROM GT20L16J1Y16を搭載していており、日本語表示も可能です。

Dscn5465

背面にICSP端子があり、この端子経由でSPI接続でSDカードなんかが使えそうですね。

Arduinoと同じようにUSB経由での書込みと電源供給が出来ます。
非常にお手軽に使えるモジュールです。

Dscn5453

プログラムを書きこんで表示で表示してみた様子
公開かさているサンプルスケッチを書きこんで動かしてみました。

Dscn5456_2

輝度が強く、私のデジカメでは綺麗に映すことがが出来ません。光が広がってフォントが
つぶれた映像となってしまいます。動画も撮影したのですが、ダメダメでした。
(何枚も撮って一番よさそうなのを掲載しました)
実際には表示は非常にきれいです。文字が左にスクロール表示します。
日本語のメッセージが表示出来ます。

ただし、日本語フォントROM GT20L16J1Yは私も使ったことがあるのですが、
ひらがな、カタカナ、漢字、全角英数、半角英数そのれぞれのバランスが微妙です。

制御方法について

表示のための制御は比較的簡単のようです。
表示パターンは2バイト16ビットが縦1列に対応しています。そのデータが
横256ドット分、合計512バイト分シーケンシャルに並んでいる構造です。

シフトレジスタを使う要領で4つのSI、CLK、LAT、ENを使ってデータを転送します。
16x256ドット分の4096ビット分のレジスタがあり、4096ビット単位でラッチして
表示データを確定出来ます。

表示機器にはこの形式多く、過去に手掛けて蓄積したノウハウが役立てそうです。

02

03

この構造の場合、表示中の画像に線を書いたり、追記描画するのは苦手です。
追記しようにも、データを送るとシフトされて左に画像全体が移動します。

通常、このような構造で自由に描画するにはフレームバッファ(ワークメモリ)を用います。
一旦、フレームバッファに描画してそのデータを一括送信して表示更新する方法です。

次のような感じです。

04

この方式では一括転送に要する時間が早いほど映像が素早く更新出来ます。
動きのある表示や、リアルタイム更新表示を行う場合は早い方が良いです。

(ちょっと前にやった「4連8x8ドットLEDマトリックス」なんかも同じ方式でやっています)

実際に、フレームバッファを使って1点描画するごとに表示更新を行う処理を
ArduinoのShiftOut()関数を使って行ったところ、目視で5画面/秒程度がやっとな感じでした。

ShiftOut()は無駄に遅いことで有名で、同じ処理を直接I/Oレジスタを操作して
転送を行うとかなり改善します。ShiftOut()とdigitalWrite()と次のような感じで
実装して置き換えるだけで、転送速度は8倍ぐらいに改善しました。

void new_shiftOut(uint8_t dataPin,uint8_t clockPin,uint8_t bitOrder,byte val) {
  uint8_t i;
  uint8_t bit_data = digitalPinToBitMask(dataPin);
  uint8_t bit_clock = digitalPinToBitMask(clockPin);
  volatile uint8_t *out_data = portOutputRegister(digitalPinToPort(dataPin));
  volatile uint8_t *out_clock = portOutputRegister(digitalPinToPort(clockPin));

  for (i = 0; i < 8; i++)  {
    if (bitOrder == LSBFIRST) {
      if(val & (1 << i)) {
        *out_data |= bit_data;
      } else {
        *out_data &= ~bit_data;
      }
    } else {
      if(val & (1 << (7 - i))) {
        *out_data |= bit_data;
      } else {
        *out_data &= ~bit_data;
      }
    }
    *out_clock |= bit_clock;
    *out_clock &= ~bit_clock;
  }
}

inline void new_digitalWrite(uint8_t pin, uint8_t val) {
  uint8_t bit = digitalPinToBitMask(pin);
  volatile uint8_t *out = portOutputRegister(digitalPinToPort(pin));
  if (val == LOW)
    *out &= ~bit;
  else
    *out |= bit;  
}

ShiftOut()なら本来、SPIを使った高速通信に置き換えるのが容易なのですが、
SPIピンとは別ピンに接続されているのがちょっと残念です。
それでも、上記修正で秒20画面位は更新できそうです。

現状、表示する文字列はシフトJIS指定でプログラムに埋め込み、表示画像も
プログラムに埋め込みでちょっと使い勝手が良くないです。

05

この当たり、自作のマルチフォントライブラリ、ビットマップ画像ローダを使って
自由度のあるコンテンツ表示をやりたいなぁと思います。

2016/12/20~  随時追記中

裏の端子にアクセスしやすいように専用の脱着式の基板を作成しました。

Dscn6226

SPI接続にてSDカードを接続。自作のマルチサイズフォントライブラリが動くようになりました(ただし、調整中)。

Dscn6268

SDカードが3.3V駆動にため、電圧レベルの調整をしています。

Photo

動作の様子


8ドット漢字も意外と読めますね。

表示は前述のフレームバッファに一旦書きこんでから、VDFに一括転送しています。
表示するメッセージの指定はUTF-8文字列です。

void demo02() {

  uint8_t font[MAXFONTLEN];   uint16_t x = 0;   uint8_t sz[] = {16,14,12,10,8};   mw_claerbuf();   SDfonts.open(); // フォントのオープン   for (uint8_t i = 0; i < 5; i++) {     char* pUTF8 = "SDカード漢字フォント利用テスト";     SDfonts.setFontSize(sz[i]); // フォントサイズの設定     x = 0;     while ( pUTF8 = SDfonts.getFontData(font, pUTF8) ) { // フォントの取得       mw_drawFont(x, font);       x+=SDfonts.getWidth();     }     mw_update();     delay(1500);     mw_claerbuf();      }   SDfonts.close();  // フォントのクローズ   delay(2000); }

次はメッセージの縦スクロール、SDカードに青空文庫の小説(テキストファイル)入れて
表示出来るようにしてみます。

(つづく)


2016年7月16日 (土)

IchigoJamで有機ELキャラクタディスプレイモジュールを制御

IchigoJamで秋月電子で販売されている有機ELキャラクタディスプレイモジュール
制御してみました(型番はSO1602AWWB-UC-WB-Uという製品)。
IchigoJamのファームウェアは1.2.1を使っています。

IchigoJamからはI2C接続にて制御しています。

Dscn5417

さすが有機EL、表示が綺麗ですね。

この有機ELキャラクタディスプレイモジュール(以後長いのでOLEDと略)の使い方は
製品ページリンクのデータシートよりも、製品についてる説明書(日本語)の方が分かり
易いです。

結線
製品についてる説明書の「マイコンとの接続例」に従って結線するだけです。

プログラムソース

1 'OLED Character Display DEMO
10 CLV
20 GSB @ショキカ
30 [0]="IchigoJam World"
40 [1]="ネ・コ・ニ・コ・ン・バ・ン・ワ"
50 X=0:Y=0:S=[0]:GSB @ヒョウジ
60 X=0:Y=1:S=[1]:GSB @ヒョウジ
70 END
500 @ショキカ
510 POKE #700,0,1,2,#C,1,#40
520 FOR M=#701 TO #704
530 R=I2CW(#3C,#700,1,M,1)
540 NEXT
550 RTN
600 @ヒョウジ
610 L=LEN(S)
620 POKE#706,#80+Y*32+X
630 R=I2CW(#3C,#700,1,#706,1)
640 FOR I=0 TO L-1
650 R=I2CW(#3C,#705,1,S+I,1)
660 NEXT
670 RTN

プログラムはわりと短めですね。使いやすいモジュールです。
GSBGOSUBの省略形、RTNRETURNの省略形です)

簡単な解説

OLEDの利用のために2つのサブルーチン(GOSUBで呼び出し)を定義しています。
初期化処理(500行 @ショキカ)と、表示処理(600行 @ヒョウジ)です。

・初期化処理(500行 @ショキカ)
  OLED対する命令は2バイト構成(コマンドコード+データ)です。
  510行でそのコマンドとデータをメモリ上に定義しています。
  520行~540行でそのデータをI2Cアドレス#3CのOLEDに送っています。

  具体的には
    #00, #01 : Clear Display (表示部をクリア)
    #00, #02 : Return Home (カーソル位置を先頭に移動)
    #00, #0C : Send Display on command (表示ON、カーソル表示なし)
    #00, #01 : Clear Display (表示部をクリア、取りあえず説明書通りにもう一度クリア) 
  という4つの命令を送っています。

・表示処理(600行 @ヒョウジ)
  引数(値を渡す変数)としてSXYを使用しています。

     表示する文字列  S(文字列)
     表示位置横        X(0~15)
     表示位置縦        Y(0~1)
 
  610行が表示する文字列の長さを変数Lに設定、
  620行が表示位置アドレス (OLEDの内部アドレス)をメモリアドレス#706に設定
  しています。630行でその表示位置をOLEDに送信しています。
  具体的には次の2バイトを送信しています。
    #80, 表示位置アドレス : Set DDRAM Address
  表示アドレスは1行目が#00~#0F(左→右)、2行目が#20~#2F(左→右)となります。
  640行~660行が変数Sから1文字づつ文字を取り出して、OLEDに送信して
  表示を行っています。

これらのサブルーチンを10行から70行で利用しています。




2016年7月 5日 (火)

4連8x8ドットLEDマトリックスを試してみる 続編

前回の続きです。

4連8x8ドットLEDマトリックス2つを連結して32x16ドット表示にしてみました。
スケッチは前回からの少々の改造で実装出来ました。

01

1つめの出力を2つめの入力に接続しいます。増やしても5線のみので制御できます。

16x16ドットの日本語文を表示するため、自作フォントライブラリを利用しました。
フォントをmicroSDカードから読むため3.3V稼働させています。
(Arduino Unoも3.3V稼働出来るものを利用)

MAX7219としては3.3Vは動作保証外ですが問題なく動作しました。
3.3VでもOKなので、ARMやESP-WROOM-02でも利用出来ますね。

動作の様子


下段のモジュールのLEDが不良のため1点だけ点灯しませんが
動作としては期待以上に動いてくれました。

ただ、Arduino標準のshiftOut()関数によるシリアルデータの送り出しが遅いようです。
早いスクロール表示が出来ませんでした。そのため高速版のshiftOut()に置き換えました。

8個もLEDマトリックスを使用しているので消費電流が気になり、計ってみました。

Dscn5391

表示部(Arduino Unoを除く)は、最大35.1mAでした。まあ、こんなもんですかね。
バッテリー駆動も可能なレベルですかね。

このモジュールなかなか使いやすですね。用途に応じて、連結して利用出来ます。
更に、4連基板をカットして利用することも出来るみたいです。

基板の裏はこんな感じ

Dscn5380

今回作成したスケッチ(プログラム)  ダウンロード max7219_test3.zip (6.2K)

いくつかのファイルに別れますが、取りあえずメイン部はこんな感じです。

// max7219_test3.ino
// MAX7179ドライバーライブラリ動作チェック 32x16ドットバージョン
// 2016/07/05 たま吉さん

#include <string.h>
#include <sdfonts.h>

#include "MAX7219_matrix.h"
#include "libmatrix.h"

// MAX7219接続
#define MAX7219_DIN   2 // DIN
#define MAX7219_CLK   4 // CLK
#define MAX7219_LOAD  3 // LOAD

// SDカード接続
#define MOSI_SD   11    // SDカードのMOISへ接続
#define MISO_SD   12    // SDカードのMISOへ接続
#define CLK_SD    13    // SDカードのCLKへ接続
#define CS_SD     10    // SDカードモジュールCSへ接続

#define  SCREENS   8    // MAX7219カスケード接続数
#define  BRT       0    // LEDの輝度(0~7)
#define  SC_WIDTH  32   // マトリック表示横ドット数
#define  SC_HIGHT  16   // マトリック表示縦ドット数

uint8_t fbuf[SC_WIDTH*SC_HIGHT/8];  // 表示用バッファ

// 表示用バッファを表示
// (フレームバッファ32x16ドットをMAX7219用バッファ64x8にマッピング)
void update_screen() {
  uint8_t* ptr = MAX7219_getBuffer();
  for (uint8_t i=0; i < 8; i++) {
    memcpy(&ptr[i*8],&fbuf[i*4+32], 4);
    memcpy(&ptr[i*8+4],&fbuf[i*4], 4);
  }
  MAX7219_update();  
}

// デモ
void demo() {
  uint8_t font[MAXFONTLEN];
  SDfonts.open();            // フォントのオープン
  SDfonts.setFontSize(16);    // フォントサイズの設定

  char* str = "こんにちは埼玉県!熱いぞ!埼玉県!";

  MAX7219_clear();
  while(1) {
    str = SDfonts.getFontData(font, str);  // フォントデータの取得
    if (!str)
      break;
    scrollInFont(fbuf, font, 30);
  }
  SDfonts.close(); 
}

// 1文字分スクロール挿入表示
void scrollInFont(uint8_t*ptr, uint8_t *fnt, uint16_t dt) {
  for (int8_t t = 0; t < 16; t++) {
    scrollBitmap(ptr, SC_WIDTH, SC_HIGHT, B0001); // 左スクロール
    clearBitmapAt(ptr, SC_WIDTH, SC_HIGHT, SC_WIDTH-t-1, 0, SC_HIGHT, SC_HIGHT);
    setBitmapAt(ptr, SC_WIDTH, SC_HIGHT, SC_WIDTH-t-1, 0, fnt, SC_HIGHT, SC_HIGHT);
    update_screen();
    delay(dt);
  }  
}

void setup() {
  //Serial.begin(115200);
  SDfonts.init(10);     // フォント管理の初期化
  
  // マトリック表示ドライバの初期化
  MAX7219_init(MAX7219_DIN, MAX7219_CLK, MAX7219_LOAD, SCREENS, BRT);
}

void loop() {
  MAX7219_clear();
  demo();
  delay(1000);
}

別途自作ライブラリ sdfonts を利用しています。
  ・sdfonts : Arduino用漢字フォントライブラリ SDカード版
    https://github.com/Tamakichi/Arduino-KanjiFont-Library-SD

今まで、LEDドットマトリックスを色々やってきましたが、このモジュールの利用で
落ち着きそうです。

2016年7月 4日 (月)

4連8x8ドットLEDマトリックスを試してみる

Aliexpressで4連の8x8ドットLEDマトリックが格安で売っていたので入手しました。
ドライバーにMAX7219を4つ使った製品です。

なんか、ドットマトリックスばっかりやってる感がありますが、表示器の制御は
面白いです。

MAX7219 Dot Matrix Module For Arduino Microcontroller 4 In One Display with 5P Line

01

到着した製品はこんな感じです。

Matrix

早速、Arduinoにて動作確認してみました。
輝度が強く、眩しいので輝度設定を最小にしました。LEDマトリック自体は明るい製品みたいです。

Dscn5373

Arduinoとの接続は5線(VCC、GND、DIN、CLK、LOAD)のみです。シフトレジスタを
使うような感じでデータを送ります。

購入した2つのうち1つは1ドット点灯しない不具合を発見。
入れ替ると点灯するので基板ではなく、8x8ドットLEDマトリックス1個が不良のようです。
まあ、これは諦めましょう。

本製品で使われている8x8ドットLEDマトリックスは5個で$3で売っているのでそちらを
買って差し換えるつもりです。

Dscn5377

動いている様子


4文字表示できると時刻や気温の表示なんかにも使えそうです。


スケッチ(プログラムソース)
ダウンロード max7219_test.zip (5.6K)

4連結の制御にちょっとはまりましたが、取りあえず出来ました。
下記のライブラリを参考にさせて頂き、制御部分を作成しました。
  https://github.com/lstoll/arduino-libraries/tree/master/Matrix

日本語フォント表示は美咲フォントを使わせて頂きて、自作ライブラリにて
行っています。コンパイルには上記スケッチとは別に下記が必要となります。

  Arduino用 美咲フォントライブラリ 教育漢字・内部フラッシュメモリ乗せ版
  https://github.com/Tamakichi/Arduino-misakiUTF16

さて、2つあるので繋げてなんとか32x16ドット表示を実装したいですね。
その後は、ESP-WROOM-02に繋げてインターネットから取得した情報を表示なんてのを
やってみたいです。

2016年6月28日 (火)

ESP-WROOM-02を始めました(3) SPIFFSの利用

2016/07/02 追記更新

前回はSDカードに漢字フォントとビットマップファイルをを載せていたのですが、
そのデータを標準搭載のフラッシュメモリ上に置けることが分かりました。

ということで、実際にフラッシュメモリ上に漢字フォントをおいて動かしてみました。
取りあえず、動作しました。

03

だだし若干、表示にチラつきが出ます。
フラッシュメモリにアクセスする時にダイナミック駆動に利用しているタイマー割り込みが
中断させられるのかもしれません。

今後、WiFi通信等の利用も考えると、1msecの割り込みのダイナミック駆動はちょっと
負担が大きそうです。

フラッシュメモリの具体的な利用方法

ESP-WROOM-02には4MバイトのSPI接続フラッシュメモリが搭載されており、
そのうち、1M or 3Mバイトをデータ用のファイルシステムとして利用出来ます。

この領域は、専用に確保されておりスケッチを書き換えても影響を受けません。

  Flash layout

  ※公式サイトのリファレンスマニュアル ESP8266 Arduino Core /  File System /  より引用
    http://esp8266.github.io/Arduino/versions/2.3.0/doc/filesystem.html

ファイルシステムに利用する領域はArduino IDEにて選択が出来ます。

フラッシュサイズが4MバイトのESP-WROOM-02は1M or 3Mバイトが選択出来ます。

Arduino IDEから任意のファイルを配置出来る

やり方は、公式サイトのリファレンスマニュアル
  ESP8266 Arduino Core /  File System / Uploading files to file system
     http://esp8266.github.io/Arduino/versions/2.3.0/doc/filesystem.html#uploading-files-to-file-system
に記載されています。

ブラグインをダウンロードして、Arduino IDEに組み込み、Arduino IDEを再起動、
これで、ツールメニューに「EPS8266 Sketch data upload」というメニューが追加されます。

現在開いているスケッチが置いてあるフォルダにフォルダ data を作成してその中に
データファイルを置いておきます。

そんでもって、メニュー 「EPS8266 Sketch data upload」 をクリックしてアップロードします。

実際に漢字フォントとビットマップファイルの約1.75Mバイトをアップロードしてみると
数分かかりましたが、エラーなく完了出来ました。

プログラムからのファイルアクセス

ヘッダーファイル fs.h をインクルドします。これにより、File system object (SPIFFS)が
利用出来ます。

ファイルシステムの操作はSPIFFSオブジェクトを使って行います。
SDライブラリのSDオブジェクトとほとんど同じ感覚で利用出来ます。

SDオブジェクトからSPIFFSオブジェクトへの移行は関数がほぼ1対1でかつ、
パラメタも類似しているためすんなりと行えました。

ただ、どうもSDライブラリとの相性が悪いのかSD.hも別途インクルドするFileクラス関連の
コンパイルエラーが発生しました(これは要調査です)。
まあ、今回は、併用しないのでSD.hのインクルドを止めました。

プログラム(スケッチ)

現時点のバージョンを置いておきます。
     ダウンロード sample5v2_esp.zip (14.1K)   

ココログでは、1Mバイト以上のファイルを置けないので、フォントデータは下記より
ダウンロードして下さい。
     https://github.com/Tamakichi/Arduino-KanjiFont-Library-SD/archive/master.zip

フォントの著作権は私ではなくフォント作成者にあります。
解凍し、README.TXTのライセンス等に関する記載内容を確認の上お使い下さい。

解凍したフォルダのfontbin内のFONT.BINが利用するフォントデータとなります。
このフォントデータをスケッチ配置フォルダ下のdataフォルダに入れて、ESP-WROOM-02
にアップロードして利用します。

最後に

最終的にはESP-WROOM-02用の漢字フォントライブラリを整備したいと思います。

2016/07/02 追記

ライブラリ化し、下記にて公開しました。
  ・ESP8266用漢字フォントライブラリ SPIFFS版 ESP8266-KanjiFont-Library-SPIFFS
    https://github.com/Tamakichi/ESP8266-KanjiFont-Library-SPIFFS

2016年6月26日 (日)

ESP-WROOM-02を始めました(2)

前回からの続きです。
ESP8266搭載のESP-WROOM-02の調査中です。

Arduinoとしてどれくらい利用可能かを確認するため、
Arduino UNO(Atmega328)用のaitendo 16x16LEDマトリックス制御プログラムを移植
してみました。

Dscn5336

動作の様子



意外とライブラリの互換性レベルが高く、
I/Oピンの割り付け変更とタイマー割り込みの設定の変更程度で動きました。
(直接レジスタをいじっている高速版digitalWrite()、shiftOut()はさすがに動かないので、
正規版に戻しました)。

行儀のよいArduino UNO用スケッチなら少々の修正で利用出来ると思います。
実際に自作のビットマップファイルロードライブラリ、フォントライブラリが利用出来ました。
(この動作確認にて、自作のフォントライブラリの不具合を見つけました)

SPI、SDライブラリは割り付けピンを変えるだけで利用出来ました。

プログラム領域にデータを置いて参照利用するpgm_read_byte()利用出来ました。

タイマー割り込みに使っていたMsTimer2ライブラリは動かないので、ESP8266用に
用意されているTickerを利用しました。利用はとても簡単です。

ヘッダーファイル Ticker.hをインクルードして、グローバル変数を宣言して、
  #include <Ticker.h>
  Ticker ticker ;


タイマー割り込み設定の
  MsTimer2::set(1, fontout);
  MsTimer2::start();
  ticker.attach_ms(1, fontout);
と変更するだけです。

「いろは歌」はSDカード上のフォントを利用して表示しています。
「ねこにこんばんわ」はビットマップファイルを読み込んで表示していま。

タイマ割り込みTickerは、MsTimer2に比べると若干、精度が悪い感じです。
ダイナミック駆動のLED表示に若干ムラが生じます。

ピン割り付けがちょっと悩みましたが、下記のサイトの情報が大変参考になりました。

・Qiita - ESP-WROOM-02 Arduino互換ボードのGPIOはこうやって使う
   http://qiita.com/umi_kappa/items/ac3d37db44a2dcfe71fd

スケッチ  ダウンロード sample5_esp.zip (4.6K)

  ※コンパイルには下記のライブラリが別途必要です。
   sdfonts   : Arduino用漢字フォントライブラリ SDカード版
   sdbitmap : Arduino用Bitmap画像ロードライブラリ

// sample5_esp.ino
// aitendo 16x16LEDドットマトリックスの制御サンプル 漢字フォントライブラリ利用バージョン
// 2016/06/16 ESP-WROOM-02(ESP8266)用に移植 by たま吉さん
// 2016/06/29 コメント追記、定数の定義追加 by たま吉さん
//

#include <sdfonts.h>
#include <string.h>
#include <sdbitmap.h>
#include <Ticker.h>

// ビットマップ操作関数宣言
void scrollBitmap(uint8_t *bmp, uint16_t w, uint16_t h, uint8_t mode);
void clearBitmapAt(uint8_t* bmp, uint16_t w, uint16_t h, int16_t x, int16_t y, uint8_t cw, uint8_t ch);
void setBitmapAt(uint8_t *dstbmp, uint16_t dstw, uint16_t dsth, int16_t dstx, int16_t dsty,uint8_t *srcbmp, uint16_t srcw, uint16_t srch);

//ESP-WROOM-02 接続ピン設定
#define DATAPIN   (5)     // TB62706のSERIAL-INへ接続
#define LATCHPIN  (2)     // TB62706のLATCHへ接続
#define CLOCKPIN  (4)     // TB62706のCLOCKへ接続
#define ENABLEPIN (16)    // TB62706のENABLEへ接続

#define MOSI_SD   (13)    // SDカードのMOISへ接続
#define MISO_SD   (12)    // SDカードのMISOへ接続
#define CLK_SD    (14)    // SDカードのCLKへ接続
#define CS_SD     (15)    // SDカードモジュールCSへ接続

//LEDドットマトリックス用定義
#define FBUFSIZE     32   // ドットマトリックスフレームバッファサイズ
#define MWIDTH       16   // ドットマトリックス幅
#define MHIGTHT      16   // ドットマトリックス高さ

// タイマー割り込み
Ticker ticker;

// 表示用バッファ
uint8_t fbuf[FBUFSIZE];   // 表示パターンバッファデータ
uint8_t buf[MAXFONTLEN];  // フォントデータ格納アドレス(最大24x24/8 = 72バイト)

// バッファ内データを表示する(1ライン分割表示)
void fontout() {
  static uint8_t _line = 0;      // 表示対象行
  // 点灯LEDが移動するパターン
  digitalWrite(ENABLEPIN,LOW);   // OUTを有効にする
  digitalWrite(LATCHPIN, LOW);   //送信開始        
 
  // 表示対象行選択
  uint16_t val = _BV(15-_line);
  shiftOut(DATAPIN, CLOCKPIN, LSBFIRST, val);
  shiftOut(DATAPIN, CLOCKPIN, LSBFIRST, val>>8);       
     
  // 表示対象行のフォントパターン送信
  shiftOut(DATAPIN, CLOCKPIN, LSBFIRST, fbuf[(_line<<1)+1]);
  shiftOut(DATAPIN, CLOCKPIN, LSBFIRST, fbuf[_line<<1]);

  digitalWrite(LATCHPIN, HIGH);  //送信終了

  // 表示対象行を次行に設定
  _line++;
  if (_line == MHIGTHT )
    _line = 0;
}

// バッファクリア
void clearBuf() {
   memset(fbuf, 0, FBUFSIZE);
}

// いろは歌デモ
void test_iroha() {
  char* str="色はにほへど散りぬるを我が世たれぞ常ならむ有為の奥山今日越えて浅き夢見じ酔ひもせず";
  uint8_t fn;
  uint8_t tmpbuf[FBUFSIZE];        // 表示パターン2次バッファデータ
  memset(tmpbuf,0,FBUFSIZE);       // 2次バッファクリア
  SDfonts.open();                 // フォントのオープン

  // 表示文字数分ループ処理
  while(1) {
    fn = rand() % (EXFONT24+1);            // 表示フォントサイズランダム決定
    SDfonts.setFontSizeAsIndex(fn);       // フォントサイズの指定
    str = SDfonts.getFontData(buf, str);  // フォントデータの取得
    if (!str)
      break;                               // 文末なら抜ける

    // 1文字分スクロール表示
    for (int8_t t = 0; t < SDfonts.getWidth(); t++) {
      scrollBitmap(tmpbuf, MWIDTH, MHIGTHT, B0001); // 左スクロール
      clearBitmapAt(tmpbuf, MWIDTH, MHIGTHT, MWIDTH-t-1, MHIGTHT-t-1, SDfonts.getWidth(), SDfonts.getHeight());
      setBitmapAt(tmpbuf, MWIDTH, MHIGTHT, MWIDTH-t-1, MHIGTHT-t-1, buf, SDfonts.getWidth(), SDfonts.getHeight());
      memcpy(fbuf,tmpbuf,FBUFSIZE);
      delay(30);
    }
  }
  SDfonts.close(); // フォントのクローズ
}

// ビットマップパターン表示
void test_bitmap() {
  uint8_t tmpbuf[FBUFSIZE]; // 表示パターンバッファデータ
  uint8_t rc;
  uint8_t w,h;

  memset(tmpbuf,0,FBUFSIZE);
  sdbitmap bitmap;
  bitmap.setFilename("IMG2.BMP");

  // ビットマップデータオープン
  rc = bitmap.open();
  w  = bitmap.getWidth();
  h  = bitmap.getHeight();
  
  for (uint8_t i=0; i <= w-MWIDTH; i++) {  
    // データの取得
    bitmap.getBitmapEx(tmpbuf, i, 0, MWIDTH, MHIGTHT, 1);
    memcpy(fbuf,tmpbuf,FBUFSIZE);
    delay(30);
  }
  delay(2000);
  bitmap.close();
}

// セットアップ
void setup() {
  // LEDマトリックス用制御ピンのモード設定
  pinMode(ENABLEPIN,OUTPUT);
  pinMode(DATAPIN, OUTPUT);
  pinMode(LATCHPIN, OUTPUT);
  pinMode(CLOCKPIN, OUTPUT);

  // 制御ピンの初期化
  digitalWrite(ENABLEPIN,HIGH);
  digitalWrite(CLOCKPIN, LOW);
  digitalWrite(LATCHPIN, HIGH);

  // SDカード利用フォントライブラリの初期化
  SDfonts.init(CS_SD);         // フォント管理の初期化

  // 1msec間隔タイマー割り込み設定
  ticker.attach_ms(1, fontout); 
}

// デモループ
void loop(){
  test_bitmap();  // ビットマップデータロードテスト
  test_iroha();   // いろは歌デモ表示
  //clearBuf();   // 表示用バッファ初期化
  delay(1000);
}

今回の動作確認でデジタル出力、SPI通信普通に使えることが分かりました。
次はI2Cを調べてみます。

2016年6月19日 (日)

aitendo 16x16LEDマトリックスの制御 (5)

前回からの続きです。

モノクロ(2値)のWindowsビットマップファイルを汎用的に利用したいなぁと思い、
ライブラリ化を目指して機能を追加しています。

取りあえず、手持ちのアナログジョイスティックを繋げて、ビットマップ画像を
「ぐりぐり」と任意にスクロール表示出来るよう、機能追加しました。

実装したものの、想像していたよりは面白くなかったです。 Otz..
(一度に16x16ドットしか表示出来ないのが敗因ですね)

まあ、ビットマップファイル操作機能は今後フラフィック液晶なんかにも使おうと思います。

接続の様子

Dscn5322

動作の様子その1

下記のモノクロビットマップ画像658x431(36kバイト)から、任意の座標から16x16ドットを
切り出して表示をしています。青空文庫の「芥川龍之介 蜘蛛の糸」をキャプチャしたものです。

01

36kバイトの画像データの全てをArduinoにロードすることは出来ませんが、
ビットマップ画像から部分切り出しでデータをロードすることで表示可能にしています。


動作の様子その2

グラフィック画像っぽいののも試してみました。画像サイズは96x69です。

02

思っていたよりも面白くなかったですが、ビットマップ画像操作ライブラリの動作確認と
割り切ることにします^^


スケッチ ダウンロード sample6.zip (19.8K)   (2016/06/21 バグがあり修正しました)

ビットマップ画像の操作については、下記のソースを参考にさせて頂きました。
   Adafruit-SSD1331-OLED-Driver-Library-for-Arduino/examples/bmp/bmp.pde

ソース自体は全面的に作り直しですが、ヘッダー部と、画像の取り出し部分が大変参考になりました。

スケッチは3つのソースファイルに分かれています。
   sample6.ino   ・・・  メイン
   sdbitmap.h    ・・・  sdbitmapクラス定義ヘッダファイル
   sdbitmap.cpp ・・・  sdbitmapクラス定義本体

sample6.ino

// sample6.ino
// aitendo 16x16LEDドットマトリックスの制御サンプル ビットマップファイル表示
// 2016/06/19 たま吉さん
//

#include <arduino.h>
#include <MsTimer2.h>
#include <string.h>
#include "sdbitmap.h"

#define DATAPIN	  (7)	  // TB62706のSERIAL-INへ
#define LATCHPIN  (9)	  // TB62706のLATCHへ
#define CLOCKPIN  (8)	  // TB62706のCLOCKへ
#define ENABLEPIN (6)     // TB62706のENABLEへ
#define CS_SD     (10)    // SDカードモジュールCSへ

// 表示用バッファ
uint8_t fbuf[32];        // 表示パターンバッファデータ(16x16)

//
// shiftOut高速化版
//
void new_shiftOut(uint8_t dataPin,uint8_t clockPin,uint8_t bitOrder,byte val) {
  uint8_t i;
  uint8_t bit_data = digitalPinToBitMask(dataPin);
  uint8_t bit_clock = digitalPinToBitMask(clockPin);
  volatile uint8_t *out_data = portOutputRegister(digitalPinToPort(dataPin));
  volatile uint8_t *out_clock = portOutputRegister(digitalPinToPort(clockPin));

  for (i = 0; i < 8; i++)  {
    if (bitOrder == LSBFIRST) {
      if(val & (1 << i)) {
        *out_data |= bit_data;
      } else {
        *out_data &= ~bit_data;
      }
    } else {
      if(val & (1 << (7 - i))) {
        *out_data |= bit_data;
      } else {
        *out_data &= ~bit_data;
      }
    }
    *out_clock |= bit_clock;
    *out_clock &= ~bit_clock;
  }
}

//
// digitalWrite高速化版
//
inline void new_digitalWrite(uint8_t pin, uint8_t val) {
  uint8_t bit = digitalPinToBitMask(pin);
  volatile uint8_t *out = portOutputRegister(digitalPinToPort(pin));
  if (val == LOW)
    *out &= ~bit;
  else
    *out |= bit;  
}

//
// バッファ内データを表示する(1ライン分割表示)
//
void fontout() {
  static uint8_t _line = 0;  
  // 点灯LEDが移動するパターン
  new_digitalWrite(ENABLEPIN,LOW);   // OUTを有効にする
  new_digitalWrite(LATCHPIN, LOW);   //送信開始        
 
  // 行選択
  uint16_t val = _BV(15-_line);
  new_shiftOut(DATAPIN, CLOCKPIN, LSBFIRST, val);
  new_shiftOut(DATAPIN, CLOCKPIN, LSBFIRST, val>>8);       
     
  // 行フォントパターンの送信
  new_shiftOut(DATAPIN, CLOCKPIN, LSBFIRST, fbuf[(_line<<1)+1]);
  new_shiftOut(DATAPIN, CLOCKPIN, LSBFIRST, fbuf[_line<<1]);

  new_digitalWrite(LATCHPIN, HIGH);  //送信終了
  _line++;
  if (_line == 16)
    _line = 0;
}

//
// バッファクリア
//
void clearBuf() {
   memset(fbuf,0,32);
}

void test_bitmapEx2() {
  uint8_t tmpbuf[32];        // 表示パターンバッファデータ
  uint16_t w,h,x=0,y=0;
  int16_t  px,py;
  int16_t dx,dy;
  uint16_t tm;
  
  memset(tmpbuf,0,32);
  sdbitmap bitmap;
  bitmap.setFilename("IMG3.BMP");

  // ビットマップデータオープン
  bitmap.open();
  w  = bitmap.getWidth();
  h  = bitmap.getHeight();
  x = 0; y= 0;

  bitmap.getBitmapEx(tmpbuf, x, y, 16, 16, 1);
  memcpy(fbuf,tmpbuf,32);

  while(1) {
    dx =  analogRead(A1);  
    dy =  analogRead(A0); 
    px = x;
    py = y;
    
    if (x>0 && dx > 520)
      x--;
    else if (x < w-16 && dx < 490) 
      x++;
    if (y>0 && dy < 490)
      y--;
    else if (y < h-16 && dy > 520) 
      y++;

    if (px!=x || py!=y) {
      dx = 40-abs(dx-512)/16;
      dy = 40-abs(dy-512)/16;
      tm = min(dx,dy);
      bitmap.getBitmapEx(tmpbuf, x, y, 16, 16, 1);
      memcpy(fbuf,tmpbuf,32);
    } else
      tm = 30;
    delay(tm);
  }
  delay(2000);
  bitmap.close();
}

void setup() {
  //Serial.begin(115200);
  
  // LEDマトリックス用制御ピンのモード設定
  pinMode(ENABLEPIN,OUTPUT);
  pinMode(DATAPIN, OUTPUT);
  pinMode(LATCHPIN, OUTPUT);
  pinMode(CLOCKPIN, OUTPUT);

  // 制御ピンの初期化
  digitalWrite(ENABLEPIN,HIGH);
  digitalWrite(CLOCKPIN, LOW);
  digitalWrite(LATCHPIN, HIGH);

  // SDカード利用開始
  SD.begin(CS_SD);     // フォント管理の初期化
  
  // 割り込み開始
  MsTimer2::set(1, fontout); //  1ライン描画/1回 版
  MsTimer2::start();

  // デモ
  test_bitmapEx2();  // ビットマップデータロードテスト
}

void loop(){
} 

sdbitmap.h

// 
// sdbitmap.h
// ビットマップファイル操作クラス
// 2016/06/18 たま吉さん
//

#ifndef __SDBITMAP_H__
#define __SDBITMAP_H__

#include <arduino.h>
#include <SD.h>
#include <SPI.h>

// クラス定義
class sdbitmap {
 
 // メンバ変数の定義
 private:
   char*    _filename;         // ビットマップファイル名
   File	    _bmpfile;          // ファイルオブジェクト
   uint8_t  _sts;              // ファイルアクセス状態(0:初期 1:オープン中1)
   int16_t  _bmpWidth;         // 画像幅
   int16_t  _bmpHeight;        // 画像高さ
   uint32_t _bmpImageoffset;   // Start of image data in file
   uint32_t _rowSize;          // 1ラインのバイトサイズ
   boolean  _flip;             // 画像格納方向
	 
 // メンバ関数の定義
 public:
   sdbitmap();		           // コンストラクタ
   void	    init();	           // 初期化
   void	    setFilename(char* str);// ファイルの設定
   uint8_t  open();                // ファイルのオープン
   void	    close();               // ファイルのクローズ
   int16_t  getWidth();            // 画像幅の取得
   int16_t  getHeight();           // 画像高さの取得	 

   uint8_t getByte(uint16_t x, uint16_t y); // 指定位置から8ドット(バイトデータ)取り出し
   uint8_t getBitmap(uint8_t*bmp,           // ビットマップデータの切り出し取得(高速版)
   uint16_t x, uint16_t y, 
	uint8_t w, uint8_t h, uint8_t mode);

   uint8_t getBitmapEx(uint8_t*bmp,         // ビットマップデータの切り出し取得
   uint16_t x, uint16_t y, 
   uint8_t w, uint8_t h, uint8_t mode);
   uint8_t getBitmap(uint8_t*bmp, uint8_t mode); // ビットマップデータの取得  

 private:
   uint16_t read16();		// ワードデータ読み込み
   uint32_t read32();		// ロングデータ読み込み
};

#endif

sdbitmap.cpp

//
// sdbitmap.cpp
// ビットマップファイル操作クラス
// 2016/06/21 たま吉さん
//

#include "sdbitmap.h"
#define DEBUG_BMPLOAD 0

//
// コンストラクタ
//

sdbitmap::sdbitmap() {
	init();
}

//
// 初期化
//
void sdbitmap::init() {
	_filename = NULL;
	_sts = 0;
}

//
// ファイル名設定
//
void sdbitmap::setFilename(char* str) {
	_filename = str;	
}

//
// ワードデータ読み込み
//
uint16_t sdbitmap::read16() {
  uint16_t result;
  ((uint8_t *)&result)[0] = _bmpfile.read(); // LSB
  ((uint8_t *)&result)[1] = _bmpfile.read(); // MSB
  return result;
}

//
// ロングデータ読み込み
//
uint32_t sdbitmap::read32() {
  uint32_t result;
  ((uint8_t *)&result)[0] = _bmpfile.read(); // LSB
  ((uint8_t *)&result)[1] = _bmpfile.read();
  ((uint8_t *)&result)[2] = _bmpfile.read();
  ((uint8_t *)&result)[3] = _bmpfile.read(); // MSB
  return result;
}


//
// ビットマップファイルのオープン 
// 引数なし
// 戻り値	0:正常終了
//			1:オープン失敗
//			2:フォーマットエラー
// 
uint8_t	sdbitmap::open() {
  uint8_t  bmpDepth;              // Bit depth (currently must be 24)
  uint32_t tmp_val;

  _flip    = true;      		  // BMP is stored bottom-to-top

  // ファイルオープン

#if DEBUG_BMPLOAD == 1
  Serial.println();
  Serial.print("Loading image '"); Serial.print(_filename);  Serial.println('\'');
#endif
 
  // Open requested file on SD card
  if ((_bmpfile = SD.open(_filename)) == NULL) {

#if DEBUG_BMPLOAD == 1
    Serial.print("File not found");
#endif

    return 1;	// オープン失敗
  }

  // [ファイルヘッダ] (14バイト)
  //   アドレス (サイズ)  名称  内容
  //   0x0000 (2)  bfType  ファイルタイプ 通常は'BM'
  //   0x0002 (4)  bfSize  ファイルサイズ (byte)
  //   0x0006 (2)  bfReserved1 予約領域 常に 0
  //   0x0008 (2)  bfReserved2 予約領域 常に 0
  //   0x000A (4)  bfOffBits ファイル先頭から画像データまでのオフセット (byte)
  //

  if(read16() != 0x4D42) 			  // BMPシグニチャチェック(2バイト)
	return 2;						  // フォーマットエラー
	
  tmp_val = read32();			   	  // ファイルサイズの取得(4バイト)
#if DEBUG_BMPLOAD == 1    
    Serial.print("File size: "); Serial.println(tmp_val);
#endif
    tmp_val = read32();				  // 予約領域の取得(4バイト)
    _bmpImageoffset = read32();		  // オフセット値の取得(4バイト)
#if DEBUG_BMPLOAD == 1    
    Serial.print("Image Offset: "); Serial.println(_bmpImageoffset, DEC);
#endif

  // [情報ヘッダ] (40バイト)
  //   0x000E (4)  bcSize          ヘッダサイズ 40
  //   0x0012 (4)  bcWidth         画像の幅 (ピクセル)
  //   0x0016 (4)  bcHeight        画像の高さ (ピクセル)
  //   0x001A (2)  bcPlanes        プレーン数 常に 1
  //   0x001C (2)  bcBitCount      1画素あたりのデータサイズ
  //   0x001E (4)  biCompression   圧縮形式 0:BI_RGB(無圧縮) 1:BI_RLE8 2:BI_RLE4 3: BI_BITFIELDS 4:BI_JPEG 5: BI_PNG
  //   0x0022 (4)  biSizeImage     画像データ部のサイズ (byte) 
  //   0x0026 (4)  biXPixPerMeter  横方向解像度
  //   0x002A (4)  biYPixPerMeter  縦方向解像度
  //   0x002E (4)  biClrUsed       格納されているパレット数
  //   0x0032 (4)  biCirImportant  重要なパレットのインデックス
  //
    
  tmp_val = read32();		// ヘッダサイズの取得

#if DEBUG_BMPLOAD == 1        
    Serial.print("Header size: "); Serial.println(tmp_val);
#endif

  _bmpWidth  = read32();	// 画像幅の取得
  _bmpHeight = read32();	// 画像高さの取得
   
  if(read16() != 1)     // プレーン数の取得(必ず1である必要がある)
 	return 2;
 
  bmpDepth = read16();  // 1ピクセル当たりのビット数
#if DEBUG_BMPLOAD == 1        
    Serial.print("Bit Depth: "); Serial.println(bmpDepth);
#endif

  if (bmpDepth != 1)	// 2値(白,黒)
  	return 2;			// 形式エラー

  if(read32() != 0 )  	// 圧縮形式:無圧縮か?
  	return 2;			// 形式エラー

  // 1ラインのバイト数
  _rowSize = (((_bmpWidth+7)>>3) + 3) & ~3;
  if(_bmpHeight < 0) {
    _bmpHeight = - _bmpHeight;
    _flip      = false;
  }

#if DEBUG_BMPLOAD == 1    
    Serial.print("flip: "); Serial.println(_flip);
    Serial.print("Image size: "); Serial.print(_bmpWidth); Serial.print('x'); Serial.println(_bmpHeight);
#endif
  return 0;
}

//
// ファイルのクローズ
//
void sdbitmap::close() {
  _bmpfile.close();
}

//
// 画像幅の取得
//
int16_t sdbitmap::getWidth() {
	return _bmpWidth;
}

//
// 画像高さの取得
//
int16_t sdbitmap::getHeight() {
	return _bmpHeight;
}

//
// ビットマップデータの取得
// 引数
//  bmp : データ格納アドレス
//  mode: 0:通常 1:反転
// 戻り値
//  0:     正常終了
//  0以外  異常終了
//

uint8_t sdbitmap::getBitmap(uint8_t*bmp, uint8_t mode) {
  getBitmap(bmp, 0, 0, _bmpWidth, _bmpHeight, mode);
}

//
// ビットマップデータの切り出し取得
// 引数
//  bmp : データ格納アドレス
//  x   : 取り出し位置:横 (8の倍数であること)
//  y   : 取り出し位置:縦
//  w   : 取り出し幅 (8の倍数であること)
//  h   : 取り出し高さ
//  mode: 0:通常 1:反転
// 戻り値
//  0:     正常終了
//  0以外  異常終了
// 
uint8_t sdbitmap::getBitmap(uint8_t*bmp, uint16_t x, uint16_t y, uint8_t w, uint8_t h, uint8_t mode) {
  uint32_t pos = 0;
  uint16_t ptr = 0;

  uint16_t bx = x>>3;
  uint16_t bw = (w+7)>>3;
  uint8_t bit_w = w & 7;
  
  if (y + h > _bmpHeight)
    return -1;
  if ( ((x + w)>>3) > _rowSize)
    return 1;

  for ( uint16_t row = y; row < y+h ; row++ ) { // ラインループ
    if(_flip) 
      pos = _bmpImageoffset + (_bmpHeight - 1 - row) * _rowSize + bx;
    else
      pos = _bmpImageoffset + row * _rowSize + bx;
      
    // ファイルのシーク  
    if( _bmpfile.position() != pos ) // Need seek?
      _bmpfile.seek(pos);

    // 1ライン分のデータの取得
    for ( uint16_t col = bx ; col < bx+bw ; col++ ) {
      bmp[ptr++] =  mode ? ~_bmpfile.read(): _bmpfile.read();
    }    
    if (bit_w && ptr) // 8の倍数でない
      bmp[ptr-1] &= 0xff<<(8-bit_w);
  }
}

//
// 指定位置から8ビット(1バイト)取り出し
//
uint8_t sdbitmap::getByte(uint16_t x, uint16_t y) {
  uint8_t d1,d2;
  uint32_t pos = 0;
  uint16_t bx = x>>3;        // 取り出しバイト位置:横
  uint8_t bit_x = x & 7;     // ビット位置

  if (bx >= _rowSize || y >= _bmpHeight ) 
    return 0;

  if(_flip) 
    pos = _bmpImageoffset + (_bmpHeight - 1 - y) * _rowSize + bx;
  else
    pos = _bmpImageoffset + y * _rowSize + bx;

    // ファイルのシーク  
    if( _bmpfile.position() != pos )
      _bmpfile.seek(pos);

  d1 = _bmpfile.read();
  if (bit_x) {
    // 8の倍数でない
    d1<<=bit_x;
    d2 = (bx+1 >= _rowSize) ? 0:_bmpfile.read()>>(8-bit_x);
  } else {
    // 8の倍数
    d2 = 0;
  }
  return d1|d2;
}

//
// ビットマップデータの切り出し取得
// 引数
//  bmp : データ格納アドレス
//  x   : 取り出し位置:横
//  y   : 取り出し位置:縦
//  w   : 取り出し幅 
//  h   : 取り出し高さ
//  mode: 0:通常 1:反転
// 戻り値
//  0:     正常終了
//  0以外  異常終了
// 
uint8_t sdbitmap::getBitmapEx(uint8_t*bmp, uint16_t x, uint16_t y, uint8_t w, uint8_t h, uint8_t mode) {
  // ラインループ
  uint16_t ptr = 0;
  uint8_t d;
  uint8_t bit_w = w % 8;
  for (uint16_t row = y; row < y+h ; row++ ) { 
    for (uint16_t col = x; col < x + w ; col += 8 ) {
      bmp[ptr++] = mode? ~getByte(col,row):getByte(col, row);      
    }
    if (bit_w && w > 8) // 右端の端数ビットの補正
      bmp[ptr-1] &= 0xff<<(8-bit_w);
  }  
}

もう少し整理して、ビットマップファイルの操作部分をArduinoのlibraryディレクトリに
登録できるよう、ライブラリ化したいと思います。

2016年6月16日 (木)

aitendo 16x16LEDマトリックスの制御 (4)

前回からの続きです。

取りあえず、SDカードにWindows ビットマップファイル(モノクロ)を入れてその画像を
表示出来るようにしました。
前回からビットマップファイルを操作するクラスを作成・追加しました。

次のような画像をペイントツールで作成して表示してみました。

01

動作の様子


この方法だと、画像ファイルを差し換えるだけで任意のメッセージを表示出来ますね。

スケッチ
  ダウンロード sample4.zip (7.9K)

※ライブラリは含まれていません。別途ダウンロードして下さい。

   ・MsTimer2
     http://playground.arduino.cc/Main/MsTimer2

  ・漢字フォントライブラリSDカード版
    https://github.com/Tamakichi/Arduino-KanjiFont-Library-SD

ビットマップファイルの表示部は次のような感じです。

void test_bitmap() {
  uint8_t tmpbuf[32];        // 表示パターンバッファデータ
  uint8_t rc;
  uint8_t w,h;

  memset(tmpbuf,0,32);
  sdbitmap bitmap;
  bitmap.setFilename("IMG2.BMP");

  // ビットマップデータオープン
  rc = bitmap.open();
  w  = bitmap.getWidth();
  h  = bitmap.getHeight();
  
  Serial.print(F("bitmap open rc="));
  Serial.println(rc);

  for (uint8_t i=0; i<w; i+=16) {  
    // データの取得
    bitmap.getBitmap(buf, i, 0, 16, 16, 1);

    // 1文字分スクロール表示
    for (int8_t t = 0; t < 16; t++) {
      scrollBitmap(tmpbuf, 16, 16, B0001);
      clearBitmapAt(tmpbuf, 16, 16, 15-t, 0, 16, h);
      setBitmapAt(tmpbuf,   16 ,16, 15-t ,0, buf, 16, h);
      memcpy(fbuf,tmpbuf,32);
      delay(30);
    }
  }
  delay(2000);
  bitmap.close();
}

巨大なビットマップファイルも扱えるよう、ビットマップファイルから任意の位置の
画像データを取り出して表示出来るよう作成しました(まだまだ作成中)。
bitmap.getBitmap(buf, i, 0, 16, 16, 1)が16x16のサイズの画像を取り出しています。

現状、取り出す座標指定が横座標が8の倍数である必要がありますが、これも
完全に任意の座標を指定できるようにしたいと考えています。

アナログジョイスティック操作で巨大画像を上下左右スクロール表示するってな
感じのものを実装し、ビットマップファイル操作ライブラリを完成させたいと思います。

2016年6月 7日 (火)

aitendo 16x16LEDマトリックスの制御 (3)

前回の続きです。

日本語表示をしたいと思い、自作の漢字フォントライブラリ(SDカード版)を使ってみました。
機能は以前作成したものと同じですが、かなり簡略化出来ました。表示も明るいです。

01


SDカードを利用するにあたり、3.3Vで動かす必要がありますが
レベルシフト等が面倒(パーツも増える)なので、aitendoのあちゃんでいいの(Arduino)に
変更しました。

LEDドライバ TB62706の推奨利用条件は4.5V~5.5Vなのですが、3.3Vでも問題なく
利用出来ています。

回路図(結線)的には「aitendo 16x16LEDマトリックスの制御 (1)」に掲載の回路図に
microSDカードモジュール(SPI接続)を追加しただけです。

04

aitendoに私が使っているものと同じっぽいのがありますね。
マイクロSDモジュール [MSD-M6P]

02

動作の様子


「いろは歌」をスクロール表示しています。表示するフォントデータはSDカードから
逐次読みだしています。乱数でフォントサイズを変えてスクロール表示しています。
(20x20、24x24ドットのフォントは当然はみ出して表示となります)

ダイナミック駆動による表示のために1msecのタイマー割り込みを行いつつ、
SPI接続のSDカードにアクセスしています。
ちょっと心配でしたが、問題なくデータ取得出来ています。

スケッチ  ダウンロード sample3.zip (3.9K) 

※ライブラリは含まれていません。別途ダウンロードして下さい。
   ・MsTimer2
     http://playground.arduino.cc/Main/MsTimer2

  ・漢字フォントライブラリSDカード版
    https://github.com/Tamakichi/Arduino-KanjiFont-Library-SD

メインのスケッチのみ掲載します。

// sample3.ino
// aitendo 16x16LEDドットマトリックスの制御サンプル 漢字フォントライブラリ利用バージョン
// 2016/06/07 たま吉さん
//

#include <arduino.h>
#include <MsTimer2.h>
#include <sdfonts.h>
#include <string.h>

void scrollBitmap(uint8_t *bmp, uint16_t w, uint16_t h, uint8_t mode);
void clearBitmapAt(uint8_t* bmp, uint16_t w, uint16_t h, int16_t x, int16_t y, uint8_t cw, uint8_t ch);
void setBitmapAt(
 uint8_t *dstbmp, uint16_t dstw, uint16_t dsth, int16_t dstx, int16_t dsty,
 uint8_t *srcbmp, uint16_t srcw, uint16_t srch);
 
#define DATAPIN		(7)	// TB62706のSERIAL-INへ
#define LATCHPIN 	(9)	// TB62706のLATCHへ
#define CLOCKPIN 	(8)	// TB62706のCLOCKへ
#define ENABLEPIN    (6)     // TB62706のENABLEへ
#define CS_SD        (10)    // SDカードモジュールCSへ

// 表示用バッファ
uint8_t fbuf[32];        // 表示パターンバッファデータ
uint8_t buf[MAXFONTLEN]; // フォントデータ格納アドレス(最大24x24/8 = 72バイト)

//
// shiftOut高速化版
//
void new_shiftOut(uint8_t dataPin,uint8_t clockPin,uint8_t bitOrder,byte val) {
  uint8_t i;
  uint8_t bit_data = digitalPinToBitMask(dataPin);
  uint8_t bit_clock = digitalPinToBitMask(clockPin);
  volatile uint8_t *out_data = portOutputRegister(digitalPinToPort(dataPin));
  volatile uint8_t *out_clock = portOutputRegister(digitalPinToPort(clockPin));

  for (i = 0; i < 8; i++)  {
    if (bitOrder == LSBFIRST) {
      if(val & (1 << i)) {
        *out_data |= bit_data;
      } else {
        *out_data &= ~bit_data;
      }
    } else {
      if(val & (1 << (7 - i))) {
        *out_data |= bit_data;
      } else {
        *out_data &= ~bit_data;
      }
    }
    *out_clock |= bit_clock;
    *out_clock &= ~bit_clock;
  }
}

//
// digitalWrite高速化版
//
inline void new_digitalWrite(uint8_t pin, uint8_t val) {
  uint8_t bit = digitalPinToBitMask(pin);
  volatile uint8_t *out = portOutputRegister(digitalPinToPort(pin));
  if (val == LOW)
    *out &= ~bit;
  else
    *out |= bit;  
}

//
// バッファ内データを表示する(1ライン分割表示)
//
void fontout() {
  static uint8_t _line = 0;  
  // 点灯LEDが移動するパターン
  new_digitalWrite(ENABLEPIN,LOW);   // OUTを有効にする
  new_digitalWrite(LATCHPIN, LOW);   //送信開始        
 
  // 行選択
  uint16_t val = _BV(15-_line);
  new_shiftOut(DATAPIN, CLOCKPIN, LSBFIRST, val);
  new_shiftOut(DATAPIN, CLOCKPIN, LSBFIRST, val>>8);       
     
  // 行フォントパターンの送信
  new_shiftOut(DATAPIN, CLOCKPIN, LSBFIRST, fbuf[(_line<<1)+1]);
  new_shiftOut(DATAPIN, CLOCKPIN, LSBFIRST, fbuf[_line<<1]);

  new_digitalWrite(LATCHPIN, HIGH);  //送信終了
  _line++;
  if (_line == 16)
    _line = 0;
}

//
// バッファクリア
//
void clearBuf() {
   memset(fbuf,0,32);
}

//
// いろは歌デモ
//
void test_iroha() {
  char* str="色はにほへど散りぬるを我が世たれぞ常ならむ有為の奥山今日越えて浅き夢見じ酔ひもせず";
  uint8_t fn;

  // 表示文字数分ループ処理
  while(1) {
    fn = rand() % (EXFONT24+1);            // 表示フォントサイズランダム決定
    SDfonts.setFontSizeAsIndex(fn);       // フォントサイズの指定
    str = SDfonts.getFontData(buf, str);  // フォントデータの取得
    if (!str)
      break;                               // 文末なら抜ける

    // 1文字分スクロール表示
    for (int8_t t = 0; t < SDfonts.getWidth(); t++) {
      scrollBitmap(fbuf, 16, 16, B0001);
      clearBitmapAt(fbuf, 16, 16, 15-t, 15-t, SDfonts.getWidth(), SDfonts.getHeight());
      setBitmapAt(fbuf, 16,16,15-t,15-t, buf, SDfonts.getWidth(), SDfonts.getHeight());
      delay(40);
    }
  }
}

void setup() {
  // LEDマトリックス用制御ピンのモード設定
  pinMode(ENABLEPIN,OUTPUT);
  pinMode(DATAPIN, OUTPUT);
  pinMode(LATCHPIN, OUTPUT);
  pinMode(CLOCKPIN, OUTPUT);

  // 制御ピンの初期化
  digitalWrite(ENABLEPIN,HIGH);
  digitalWrite(CLOCKPIN, LOW);
  digitalWrite(LATCHPIN, HIGH);

  // SDカード利用フォントライブラリの初期化
  SDfonts.init(CS_SD);                // フォント管理の初期化
  SDfonts.open();                     // フォントのオープン
  
  // 割り込み開始
  MsTimer2::set(1, fontout); //  1ライン描画/1回 版
  MsTimer2::start();
}

// デモループ
void loop(){
  clearBuf();     // 表示用バッファ初期化
  test_iroha();   // いろは歌デモ表示
  delay(1000);
}


スケッチの簡単な説明

デモ表示の大半の処理はtest_iroha()関数が行っています。
この関数内では、文字列strから逐次文字を取り出して該当するフォントデータを
SDカードから取得しています。そのデータをscrollBitmap(),clearBitmapAt(),setBitmapAt()
にてビットマップデータ加工をして表示用バッファfbufに書き込んでいます。

scrollBitmap(),clearBitmapAt(),setBitmapAt()は、sample3.zipのlibmatrix.cpp内に
定義しています。これらは作成中の汎用ビットマップ加工関数群の一部です。

汎用的なビットマップデータ加工ライブラリをちびちびと作っているのですが
中々完成しません(今回を機に機能拡張していきたい)^^。

このシリーズもうちょっと続きます。

windowsのbitmap画像フォーマット(モノクロ2値)の画像ファイルをSDカードに入れて
読みだしてマトリックス表示する機能を試作中です^^

より以前の記事一覧