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

« Aliexpressで購入したCH340搭載USB to Serialモジュールが届いた | トップページ | タマの様子がおかしい »

2014年12月 5日 (金)

ウォッチドックタイマ割り込みを使ったドットマトリックス表示

Arduino Unoでウォッチドックタイマを使ったインターバール割り込みが
実用的であるか試してみました。8x8ドットマトリックス表示で試してみました。
この前調べたtiny13A用のソースを流用しています。

Dscn3301

作成したスケッチと回路はテストのためちょっとてんこ盛りとなりました。
特に問題なく動作しています。
ウォッチドックタイマ、今後は積極的に利用していきたいと思います。

解説等
8x8ドットマトリックスの表示はダイナミック点灯(駆動)方式で行っています。
ウォッチドックタイマ割り込みで16ms間隔で点灯処理を行います(ISR(WDT_vect))。
点灯処理内では1行分(横8個のLED)を順次点灯し、人間の目の残像を利用して
8x8のすべてが表示しているように見せています。

09

回路では8x8ドットマトリックスを直接Arduino Unoで駆動しています。
1ピン当たり最大40mAの制限があるため若干暗くなっちゃいますが実用レベルでしょう。

表示パターンの漢字を表示するためI2C接続EEPROM(AT24C1024B)を利用しています。

デフォルトでは「こんにちは さいたま県♪ 今、さいたまがアツい!」とメッセージを
繰り返し表示します。このメッセージは、パソコンから表示したい文字列を送信して
任意のメッセージを表示することもできます(USB経由のシリアル通信で)。
文字列送信はTearaTerm等と使って9600bpsで接続し、あらかじめ作成したメッセージを
[編集]でペーストして送信します(ローカルエコーをオンにした方がよい)。
送信が完了すると"OK"が返ってきます。

03

シリアル通信、I2C通信をしながらウォッチドックタイマでインターバール割り込み
で表示処理をしています。実用として問題無さそうです。

回路図
ドットマトリックスLED回りの配線を記述するとごちゃごちゃするので、
簡略化(8本に束ねている)しています。端子間の接続は配線表を見て下さい。

01

02
利用部品
・Arduino Uno 互換機
・I2C接続 シリアルEEPROM AT24C1024B
・赤色8x8ドットマトリクスLED MNA20SR092G
・抵抗器 2.2kΩ x 2
・抵抗器 220Ω x 8

ドットマトリックスのLEDに流す電流を決める抵抗は厳密に計算すると
ROW1-POW8の各ピンにそれぞれ40mAしか流せないので、
COL1-COL8の各ピンは40mA ÷8 = 5mAしか流せません。

したがって、COL1-COK8の電流制御用の抵抗Rは
  R= (5V-Vf) / 5mA = (5V - 1.8V) / 5mA = 640Ω
となります。Vfはデータシートの数値を利用。

ただ、640Ωだとちょっと暗く、Vf=1.8VはIf=20mA時の値なので
色々試してみて220Ω当たりが良さそうと判断しました。

スケッチ(プログラムソース) ダウンロー(matrix.zip)
ちょっと長いですが、掲載します。

//
// 8x8ドットマトリックスLED直接駆動デモ
// 2014/11/29 たま吉さん

#include <avr/interrupt.h>
#include <Wire.h>

//*****************************
//   出力ピンの定義
//*****************************
// 横
#define COL1  2
#define COL2  3
#define COL3  4
#define COL4  5
#define COL5  6
#define COL6  7
#define COL7  8
#define COL8  9

// 縦
#define ROW1  10
#define ROW2  11
#define ROW3  12
#define ROW4  13
#define ROW5  14
#define ROW6  15
#define ROW7  16
#define ROW8  17

//****************************
// WDT利用のための定義
//****************************
#define WDT_reset() __asm__ __volatile__ ("wdr")
#define sleep()     __asm__ __volatile__ ("sleep")
#define WDT_16MS    B000000
#define WDT_32MS    B000001
#define WDT_64MS    B000010
#define WDT_125MS   B000011
#define WDT_250MS   B000100
#define WDT_500MS   B000101
#define WDT_1S      B000110
#define WDT_2S      B000111
#define WDT_4S      B100000
#define WDT_8S      B100001

//*****************************
// フォントROM参照用
//*****************************
#define DEVICE_ADDRESS 0x50      // AT24C1024B I2Cデバイスアドレス 
#define FONT_DATA_SIZE 56960     // フォントデータサイズ(バイト)
#define FTABLESIZE     7120      // フォントテーブルデータサイズ
#define FTABLE_ADDR    0x10000   // フォントテーブル先頭アドレス 
#define FONT_LEN       8         // 1フォントのバイト数

//*****************************
// グローバル変数
//*****************************
// 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];

//*********************************
// ウォッチドッグタイマー関連関数
//*********************************
// WDTタイマー開始
void WDT_start(uint8_t t) {
  cli();                             // 全割り込み禁止
  WDT_reset();                       // ウォッチドッグタイマーリセット
  WDTCSR |= _BV(WDCE)|_BV(WDIE);     // WDCE:ウォッチドッグ変更許可、WDTIE:動作種別=割り込み
  WDTCSR |= t;                       // 割り込み間隔設定  
  sei();                             // 全割り込み許可
}

// WDTタイマー停止
void WDT_stop() {
  cli();                             // 全割り込み停止
  WDT_reset();                       // ウォッチドッグタイマリセット
  MCUSR &= ~_BV(WDRF);               // ウォッチドッグリセットフラグ解除
  WDTCSR |= _BV(WDCE)|_BV(WDE);      // WDCEとEDEに1をセット
  WDTCSR =  0;                       // ウォッチドッグ禁止
  sei();                             // 全割り込み許可
}

// タイマー割り込みで呼び出される関数
// この割り込みでドットマトリックスLEDを表示
ISR(WDT_vect) {
   matrix_out();
   matrix_off();
 }

//*********************************
//I2C接続 EEPROM AT24C1024B制御用
//*********************************

// 1バイト読込
// address: アドレス(19ビット 0x00000 - 0x7FFFF)
//      [A2][A1][P0] + 16ビットメモリ空間
byte read(unsigned long address)  {
  uint8_t   devaddr = (uint8_t)DEVICE_ADDRESS | (uint8_t)(address>>16);  // DEVICE_ADDRESS + [A2][A1][P0]
  uint16_t  addr    = address & 0xFFFF;
  byte data = 0xFF;
  int rc;

  Wire.beginTransmission(devaddr);
  Wire.write((byte)(addr >> 8));   // アドレス上位
  Wire.write((byte)(addr & 0xFF)); // アドレス下位
  rc = Wire.endTransmission();
  rc = Wire.requestFrom(devaddr,(uint8_t)1);
  data = Wire.read();
  return data;
}

// nバイト読込
// address: アドレス(19ビット 0x00000 - 0x7FFFF)
//      [A2][A1][P0] + 16ビットメモリ空間
// rcvdata: 読込データ格納アドレス
// n      : 読込みデータ数
// 戻り値  : 読み込んだバイト数
byte Sequential_read(unsigned long address, byte* rcvdata, byte n)  {
  uint8_t   devaddr = (uint8_t)DEVICE_ADDRESS | (uint8_t)(address>>16);  // DEVICE_ADDRESS + [A2][A1][P0]
  uint16_t  addr    = address & 0xFFFF;
  byte data = 0xFF;
  int rc;

  Wire.beginTransmission(devaddr);
  Wire.write((byte)(addr >> 8));   // アドレス上位
  Wire.write((byte)(addr & 0xFF)); // アドレス下位
  
  rc = Wire.endTransmission(false);
  rc = Wire.requestFrom(devaddr,(byte)n,(byte)false);
  byte c = 0;
  for (int i=0; i < n; i++) {
    rcvdata[c++] = Wire.read();
  }
   rc = Wire.endTransmission(true);
 return rc;
}

// 1ワード読込
// address: アドレス(19ビット 0x00000 - 0x7FFFF)
//      [A2][A1][P0] + 16ビットメモリ空間
// 戻り値:  取得したデータ
uint16_t read_word(unsigned long address) {
  uint16_t rcv[2];
  uint16_t rc;
  
  address<<=1;
  rcv[0] = read(FTABLE_ADDR+address);
  rcv[1] = read(FTABLE_ADDR+address+1);
  rc= rcv[0]+(rcv[1]<<8); 
  return rc;
}
//*********************************
// 美咲フォントデータ用関数
//*********************************

// フォントコード検索(バイナリーサーチ方式)
// (コードでROM上のテーブルを参照し、フォントコードを取得する)
// ucode(in) UTF-16 コード
// 戻り値    該当フォントがある場合 フォントコード(0-FTABLESIZE)
//           該当フォントが無い場合 -1
int findcode(uint16_t  ucode) {
 int  t_p = 0;            // 検索範囲上限
 int  e_p = FTABLESIZE-1; //  検索範囲下限
 int  pos;
 uint16_t  d = 0;
 int flg_stop = -1;
 
 while(true) {
    pos = t_p + (e_p - t_p+1)/2;
    d = read_word (pos);
   if (d == ucode) {         // 等しい?
     flg_stop = 1;
     break;
   } else if (ucode > d) {   // 大きい?  
     t_p = pos + 1;
     if (t_p > e_p)
       break;
   } else {                  // 小さい?     
    e_p = pos -1;
    if (e_p < t_p) 
      break;
   }
 } 
 if (!flg_stop) 
    return -1;
 return pos;   
}

// UTF8文字(1~3バイト)をUTF16に変換する
// pUTF8(in):   UTF8文字列格納アドレス
// pUTF16(out): UTF16文字列格納アドレス
// 戻り値: 変換処理したUTF8文字バイト数
byte charUFT8toUTF16(char *pUTF8, wchar_t *pUTF16) { 
 byte bytes[3]; 
 wchar_t unicode16; 
 
 bytes[0] = *pUTF8++; 
 if( bytes[0] < 0x80 ) { 
   *pUTF16 = bytes[0]; 
   return(1); 
 } 
 bytes[1] = *pUTF8++; 
 if( bytes[0] >= 0xC0 && bytes[0] < 0xE0 )  { 
   unicode16 = 0x1f&bytes[0]; 
   *pUTF16 = (unicode16<<6)+(0x3f&bytes[1]); 
   return(2); 
 } 
 
 bytes[2] = *pUTF8++; 
 if( bytes[0] >= 0xE0 && bytes[0] < 0xF0 ) { 
   unicode16 = 0x0f&bytes[0]; 
   unicode16 = (unicode16<<6)+(0x3f&bytes[1]); 
   *pUTF16 = (unicode16<<6)+(0x3f&bytes[2]); 
   return(3); 
 } else 
 return(0); 
} 

// UTF8文字列をUTF16文字列に変換する
// pUTF16(out): UFT16文字列
// pUTF8(in):   UTF8文字列
// 戻り値: UFT16文字長さ (変換失敗時は-1を返す)
byte Utf8ToUtf16(wchar_t* pUTF16, char *pUTF8) {
  int len = 0;
  int n;
  wchar_t wstr;

  while (*pUTF8) {
    n = charUFT8toUTF16(pUTF8, pUTF16);
    if (n == 0) 
      return -1;
    
    *pUTF16 = utf16_HantoZen(*pUTF16); // 半角を全角補正   
    pUTF8 += n;
    len++;
    pUTF16++;
  }
  return len; 
}

// UTF16に対応する美咲フォントデータ8バイトを取得する
//   utf16(in): UTF16コード
//   data(out): フォントデータ格納アドレス
//   戻り値: true 正常終了1, false 異常終了
boolean getFontDataByUTF16(wchar_t utf16, byte* fontdata) {
  int code;
  unsigned long addr;
  byte n;
 
  if ( 0 > (code  = findcode(utf16))) { 
    // 該当するフォントが存在しない
    return false;
  }  
  addr = code;
  addr<<=3;
  n =  Sequential_read(addr, fontdata, (byte)FONT_LEN);
  if (n!=8)
    return false;
  return true;
}

// UTF16半角コードをUTF16全角コードに変換する
// (変換できない場合は元のコードを返す)
//   utf16(in): UTF16文字コード
//   戻り値: 変換コード
wchar_t utf16_HantoZen(wchar_t utf16) {
  if (utf16 > 0xff || utf16 < 0x2f )
    return utf16;
    
  switch(utf16) {
    case 0x005C:
    case 0x00A2:
    case 0x00A3:
    case 0x00A7:
    case 0x00A8:
    case 0x00AC:
    case 0x00B0:
    case 0x00B1:
    case 0x00B4:
    case 0x00B6:
    case 0x00D7:
    case 0x00F7: return utf16;
    case 0x00A5: return 0xFFE5;
    case 0x0021: return 0xFF01;
    case 0x0022: return 0x201D; 
    case 0x0023: return 0xFF03;
    case 0x0024: return 0xFF04;
    case 0x0025: return 0xFF05;
    case 0x0026: return 0xFF06;
    case 0x0027: return 0x2019;
    case 0x0028: return 0xFF08;
    case 0x0029: return 0xFF09;
    case 0x002A: return 0xFF0A;
    case 0x002B: return 0xFF0B;
    case 0x002C: return 0xFF0C;
    case 0x002D: return 0x2212;
    case 0x002E: return 0xFF0E;    
  }  
   return  utf16 - 0x2F +  0xFF0F;     
}
//*********************************
// ドットマトリックス表示用関数
//*********************************

// 点灯する行の選択
// y: 行(0〜7)
void selectRow(uint8_t y) {
  for(uint8_t i=0; i <8; i++)
    digitalWrite(row[i], HIGH);   
  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) { 
      digitalWrite(col[i], HIGH);
    } else {
      digitalWrite(col[i], LOW);
    }
    msk>>=1;
  }  
}

// バッファの内容をドットマトリックスに出力する
void matrix_out() {
   for (uint8_t i=0; i <8; i++) {
      setData(0);
      selectRow(i);
      setData(pdata[i]);
   }
}

// ドットマトリックスの表示OFF
void matrix_off() {
  for (uint8_t i = 0; i < 8; i++)
       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);
  }
}  

//*********************************
// メイン処理
//*********************************
void setup() {
  WDT_stop();           // 起動直後に念のためにWDTを停止
  for (uint8_t i=0; i <8; i++) {
   // ピンモードの設定
   pinMode(col[i], OUTPUT);
   pinMode(row[i], OUTPUT);
 }
 Serial.begin(9600);
 Wire.begin(); 
 clrar_buf();
 WDT_start(WDT_16MS);
}

char *str="こんにちは さいたま県♪ 今、さいたまがアツい!";
char buf[129];
wchar_t wstr[128];
uint8_t fnt[8];
uint8_t len;

void loop() {  
  len =  Utf8ToUtf16(wstr, str); // utf8からutf16に変換
  while(1) { 
    for (uint8_t i=0; i <len; i++) {
      getFontDataByUTF16(wstr[i], fnt); // 文字のフォントデータ取得
      scrollout(fnt, 75);  // スクロールしながら文字を表示
    }  
    clrar_buf();
    if (Serial.available() > 0)  { // シリアルデータの着信があった
      break;
    }      
    delay(1000);
  }
  
  // シリアルデータ受信
  uint8_t n=0;
  while (Serial.available() > 0) {
    buf[n] = Serial.read();
    n++;
    if (n>128) {
      break;  
    }
  }
  buf[n] = 0;
  str = buf;
  Serial.println("OK");
  Serial.flush();
}

漢字フォントについて
漢字フォントデータはLittle Limitさんが公開している美咲フォントを利用しています。
公開しているX11 BDF 形式を加工してEEPROMに格納しています。
フォントは半角・全角合わせて7120文字です。ただし8x8ドットのため、
複雑な漢字は可読出来ないものもあります。

ご存じの通り、Arduinoで使用する文字コードはUTF-8が採用されています。
そのためフォントデータをUTF16コード順ソートしています。
文字表示時、文字のUTF-8コード(=可変長)をUTF16(2バイト固定)に変換し、
EEPROM上のインデックス(UTF16コード2バイトをソートして格納)をバイナリーサーチで
検索し、該当フォントデータを取得しています。

半角文字のフォントデータも格納していますが見栄えとバランス悪いため
今回のデモでは全角に変換しています。

表示する1文字毎に、I2Cバス上のEEPROMの7120件のデータをバイナリーサーチで
検索しているので、時間がかかるのではと思ったのですが、問題ないようです。
これなら、EEPROM上に簡単なデータベースを作っても実用になるかも。

EEPROMへのフォントデータの書き込み
書込み用ツールを用意しました(Windows用)。
あまり良い作りではなく、送信速度遅いです。書込み完了に8分かかります。
(以前ブログに書いたCH341A programmerだと1分で完了します)

次のフォントデータとツールをダウンロード後解凍し、
手順に従って書き込みを行って下さい。他の書き込み手段がある場合、
解凍フォルダ内の美咲フォント(misaki_gothic.bin)のみをお使い下さい。      

EEPROM書き込みツール&スケッチ(download2.zip)のダウンロード

2016/08/05 訂正
   上記ファイルを差し換えました。
   添付しているフォントファイルがフォントテーブルが無いファイルでした。
   動作確認で同じ既にテーブルが書かれているEEPROMを使用していたため、
   たまたま動作したようです。  大変申し訳ございません。

   フォントファイルのサイズは 79,776バイトとなります。

手順
   1)上記のリンクからdownload2.zipをダウンロードして解凍します。
      解凍したフォルダdownload2には次の3つのファイルが入っています。
         ・download2.ino            Arduino Uno用スケッチ
         ・EEPROMWrite.exe    Windows用書き込みツール
         ・misaki_gothic.bin       美咲フォント
      
      インターネットからダウンロードした実行モジュールは実行のブロックが
      されている場合があります。
      プロパティを開いて「ブロックの解除」を押して解除してください。

      04

   2)Arduino Unoで回路図の配線を行い、
      Arduino IDEでdownload.inoを開きArduino Unoにスケッチを書込みます。
      書き込みだけを行うのであれば、EEPROM回りの配線のみでもOKです。

   3)EEPROMWrite.exe を実行します。
      EEPROMWrite.exeの実行には .NET Framework 3.5以上が必要です。
      必要に応じて、インストールしてください。
      (Windows XP SP3, Windows 8.1での動作は確認しています)

      実行すると次の画面が表示されます。

     05

     Arduino Unoと接続可能なシリアルポートを選択し、[参照]ボタンで
     フォントデータ misaki_gothic.bin を指定します。 

    06

    [書込み]ボタンを押すと、書き込みが開始されます。
    書き込みの進捗状況が画面に表示されます。

   書込みが完了するまで8分くらいかかります。正常終了すると、
    次のような画面になります。
 
    07

     中断する場合は、[中止]ボタンを押してください。
     正常終了すると、次のような画面になります。    

  08

    稀に、途中で止まってチェックサム処理まで行かない場合があります。
    その場合はArduino Unoをリセットし、ツールを再実行してやり直して下さい。

   ツールのプロトコルの出来が今一悪く、低速です。
  (arduinoのシリアル通信(Serial)の受信部の実装が今一良くないんですよね)

 

« Aliexpressで購入したCH340搭載USB to Serialモジュールが届いた | トップページ | タマの様子がおかしい »

arduino」カテゴリの記事

AVR」カテゴリの記事

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

コメント

コメントを書く

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

トラックバック


この記事へのトラックバック一覧です: ウォッチドックタイマ割り込みを使ったドットマトリックス表示:

« Aliexpressで購入したCH340搭載USB to Serialモジュールが届いた | トップページ | タマの様子がおかしい »