フォト
2025年4月
    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      
無料ブログはココログ

« Synology のNAS(DS216j)でインターネットラジオを楽しむ | トップページ | LPC810を使ってIchigoJamで赤外線リモコン受信を行う(3) »

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.05 = 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

関連記事
Arduinoで8x8LEDマトリックスを直接(直結)で制御する(2016.08.07) ・・・ この記事です
LEDドットマトリックスを使ったメッセージ表示デバイスを作成中(2016/09/18)
LEDドットマトリックスを使ったメッセージ表示デバイスを作成中(2)(2016/09/20)
LEDドットマトリックスを使ったメッセージ表示デバイスを作成中(3)(2016/10/01)
LEDドットマトリックスを使ったメッセージ表示デバイスを作成中(4)(2016/10/26)

« Synology のNAS(DS216j)でインターネットラジオを楽しむ | トップページ | LPC810を使ってIchigoJamで赤外線リモコン受信を行う(3) »

arduino」カテゴリの記事

表示器制御関連」カテゴリの記事

コメント

コメントを書く

(ウェブ上には掲載しません)

トラックバック


この記事へのトラックバック一覧です: Arduinoで8x8LEDマトリックスを直接(直結)で制御する:

« Synology のNAS(DS216j)でインターネットラジオを楽しむ | トップページ | LPC810を使ってIchigoJamで赤外線リモコン受信を行う(3) »