« 2009年6月 | トップページ | 2009年8月 »

2009年7月の記事

Arduinoで漢字表示(4)

漢字表示プロジェクトの最新状況です。やっと、UTF-8でエンコードした文字列を表示できるようになりました。

Unicode版のフォントデーター

このプロジェクトの最終目的はRSSリーダーを作ることです。そのため;

efontさんのUnicodeフォント全部を収録するとデーター量が多くなるため、日本語表示に必要なブロックを抜き取って使用しました。以下のフォント情報を収録しています。

Codespace_4

JISだと、全角となる記号類(☆○など)や、ギリシャ文字(αβなど)・キリル文字がこのフォントでは半角となります。GLCDの小さいスクリーンに表示するという点では、横幅が少ない半角がよいですが、判読性という点ではやや劣ります。ただし、ロシア語Webサイトではキリル文字は全角でなく半角相当で表示されるので、本来は半角相当のフォントデザインが正解なのだと思いますが、6ドット幅だとちょっときついようです。

フォントのデーター量はData Flashへの格納サイズで573KBとなり、かなり大きいです。JIS第一・二水準文字ならこの半分程度で格納できますので、日本語表示のみであればデーター量的にはJISの方が有利です。ただし、以前にも書きましたがJISコード並びでフォントROMを作るとUnicodeからJISコードに変換するための巨大なテーブルが別途必要になります(この文字コード表を見ても分かるように、UnicodeとJISのコード体系は機械的に変換できず、文字毎の対応表をメモリ上に作る必要があると思います)。対応表はアクセス速度を考えると、シリアル接続の外部メモリーでなくMCU内蔵のFlashメモリーに格納する必要があります。MCU内蔵Flash容量の制約を考えると、データー量が増大してもコード変換が不要なUnicode形式がベターと考えています。

Unicodeの概要は以下のリンクが参考になります:

Unicode - Wikipedia
UCSとUTF
文字コード、標準化について

今回作成したArduino形式のフォントデーターを以下に示します:
 「glcdfont_12_ucs2.zip」をダウンロード

Arduinoへのフォントデーターの転送

PCからシリアル回線経由でフォントデーターを転送し、ArduinoのData Flash Memoryに書き込む、以下のツールを作っています。

  • UploadUcs2.exe(PC側のアップローダー)
  • FontWriteDF.pde(Arduino側のデーター書き込みスケッチ)

UpladUcs2.exeの実行には.Net Framework 2.0以上が必要です。送信を始めてしまうとマウスイベントを受け付けなくなるため、中断するためにはタスクマネージャーから強制終了する必要があります。ArduinoからのレスポンスをSerialPort.ReadChar メソッド で監視しているのですが、このメソッドがブロッキングタイプで他のイベントを受け付けなくなるようです。Arduinoからの受信処理を別スレッドに分ける必要があると思いますが、ここは手抜きをしています。

上記のファイルを以下に示します:
 「UploadTools.zip」をダウンロード

ks0108 GLCDライブラリの機能追加

漢字を表示するために、以下のメソッドを追加しました:

  • PutUChar(unsigned int c): 引数としてUCS2コードを指定して対応する文字を表示する
  • Puts_U(char* str): UTF-8でエンコードした文字列を表示する。文字列の最後はnull (0)で終わっていること。この関数内でUTF-8をUCS2に変換し、一文字毎にPutUChar()を呼び出します

上記の追加を行ったソースを以下に示します:
 「ks0108_unicode.zip」をダウンロード

1byteの英数字もPuts_U()メソッドで表示が可能です。フォントはData Flashに書き込んだ半角フォントを使用します。SelectFont()関数によるフォントの指定は不要です。

ライブラリの使用方法

簡単な表示を行うスケッチを以下に示します:

#include <at45db161d.h>
#include <ks0108.h>

ATD45DB161D dataflash;      // SPI Data Flash Memory libraryのインスタンス宣言

void setup()
{
  delay(1000);
  Serial.begin(115200);
  dataflash.Init();                   // Data Flash libraryの初期化
  GLCD.Init(NON_INVERTED);  // GLCD libraryの初期化

  GLCD.ClearScreen();
  GLCD.GotoXY(0, 0);
  GLCD.Puts_U("Arduinoを始めよう\n");
  GLCD.Puts_U("123123ABCABC\n");
  GLCD.Puts_U("±×αβΣΩ☆△$¥@\n");
  GLCD.Puts_U("①②③ⅠⅡⅢ㍉㌔㌢㊤㊥");
}

void  loop()
{
}

ATD45DB161D dataflashの宣言は、ks0108ライブラリの中でもData Flashからフォントデーターを読み出すために使用しているため、dataflashのインスタンス名は変えないで下さい。インスタンス名が固定ですのでどこかのヘッダファイルに入れてしまうのが間違いを防ぐ意味でよいと思うのですが、ライブラリ間で参照するインスタンスをどこで宣言するのがよいかよく分からず、スケッチ本体で宣言しています。

自分のVista環境の場合、Puts_U()関数の引数に文字列リテラルを指定するとUTF-8でエンコードされ、正しく表示ができました。ちなみに、IDEのシリアルモニターはShit-JISで送信するようです。文字列リテラルに改行コード(\n)を入れると改行することができます。

スケッチを実行した際の表示を以下に示します。

Utf8_kanji

一部の記号類が半角になっていることが分かります。ローマ数字(4行目の丸付き数字の次)は殆ど判読できないです、、

漢字を表示するための最小限の機能は作ることができましたが、画面幅を超えた場合の折り返し処理やスクロールなど欲しい機能は他にもあります。ここから先はRSSリーダーアプリを作る中で少しずつ解決していきたいと思います。

GLCDシールドの制作

これまでブレッドボードに載せていたGLCDをユニバーサル基板を使ってArduinoのシールド調にしてみました。

構成

以下の部品を載せるために、ちょっと大きめですがサンハヤト ICB-97GHD(95x135mm)を使用しました。GLCDは8-bitパラレルバスで必要なピン数が多いため、Arduino Megaを使用しています。

  • GLCD(SG12864)
  • SPI Data Flash Memory(AT45DB161D:漢字フォントの格納用)
  • Ethernet Module(WIZ812MJ:純正Ethrenet shieldと同じW5100 chipを使用)
  • 3.3V3端子レギュレータ(TA48033S
  • 3.3V用リセットIC(TCM809R

Arduino純正のEthernet Shieldでよく指摘されていますが(Under Power 研究所さんの参考記事)、ArduinoのRESETピンは5VにプルアップしてあるだけでEthernet Shieldで使用しているW5100 TCP/IP処理チップのパワーオンリセット(2μs以上Lowレベルに落とす必要がある)に使えません。そのため、リセットICを使用して3.3V系のパワーオンリセットリセットパルスを作っています。

回路図を以下に示します。

 GLCD_Shield21

外観の写真はこんな感じです(漢字表示を行っていますが、こちらは別途続編を投稿します)。W5100チップと3端子レギュレータは熱対策としてヒートシンクを貼り付けています(W5100はそれほど気にする必要はないのでしょうが気休めです)。

Glcd_shield1

裏面の写真です。ピンヘッダを使って、Arduinoを裏側から接続します。

Glcd_shield2

工作テクの限界で配線が汚いです。リセットIC(米粒大)の半田付けは結構苦戦。半田の熱でチップのパッケージが溶けそうになりました、、本当に動いているのか怪しいところがありますが、WIZ812MJが正しく起動しているので多分大丈夫でしょう。

AT45DB161D Data Flashの接続

前回の記事「Arduinoで漢字表示(3)」からの変更点を以下に記載します。WIZ812MJとSPI接続を共有するための変更です。

1)SPIのSlave Select(SS)ピンをPL0に移動しました。ATMega1280のSSピン(PB0)はWIZ812MJ(Ethernet Module)で使用します。SS信号はソフト制御のためどのピンに移動してもよいと思ったのですが、単にピンアサインをPL0に変更しただけでは動いてくれませんでした。Arduino Forumを漁ってみると、オリジナルのPB0をHighに設定してからSPIレジスタ(SPCR)の設定をせよとあります。確かにこの方法で動作します。

2)オリジナルのat45db161dライブラリでは、SPI mode 3を使っています。Ethernetライブラリにあわせてmode 0に変更しました。(WIZ812MJはEthernetライブラリがそのまま使えます)

3)オリジナルのat45db161dライブラリでは、各関数の入り口でSS信号をHigh→Low(active)にトグルしてデバイスをたたいた後、SSをLowのままで放置しています。他のSPIデバイスと共存するためには、アクセス終了後SS信号をHigh(inactive)にする必要があります。

at45db161dライブラリでは、ContinuousArrayRead()関数などでデバイスにコマンドを送った後、引き続きspi_transfer()を呼び出すことでデーターの転送を行うため、この一連のアクセスの中でデバイスをactive状態に保持する必要があります。すなわち、ContinuousArrayRead()関数の出口でSSをinactiveにできません。

at45db161dライブラリには、EndAndWait()という関数があり一連のアクセス終了後この関数を呼んでSSをinactiveにすればよさそうです。EndAndWait()でもSSをactiveのまま放置しているため、inactiveにするコードを追加しました。

4) ComparePageToBuffer()のバグ修正(前回と同様の内容を再掲)

at45db161dライブラリ(オリジナルはBlokoSさんのブログ)の変更点を以下に示します。

<at45db161d.h>

#ifndef AT45DB161D_H
#define AT45DB161D_H

#include <WProgram.h>

extern "C" {
#include <avr/pgmspace.h>
#include <inttypes.h>
#include "WConstants.h"
};

#include "at45db161d_commands.h"

#ifndef SPI

#if defined(__AVR_ATmega1280__)
  #define DATAOUT     51
  #define DATAIN      50
  #define SPICLOCK    52
  #define SLAVESELECT 49     // 53(PB0)→ 49(PL0)に変更
  #define SPI_SS      53
  #define RESET        8
  #define WP           7
#else
  #define DATAOUT     11
  #define DATAIN      12
  #define SPICLOCK    13
  #define SLAVESELECT 10
  #define RESET        8
  #define WP           7
#endif
・・以下略・・

<at45db161d.cpp>

void ATD45DB161D::Init()
{
        uint8_t clr;
       
        /* Initialize pinout */
        pinMode(DATAOUT, OUTPUT);
        pinMode(DATAIN, INPUT);
        pinMode(SPICLOCK, OUTPUT);
        pinMode(SLAVESELECT, OUTPUT);
        pinMode(DATAIN, INPUT);

        /* Disable device */
        DF_CS_inactive;                    // Deactivate alternative SS
#if defined(__AVR_ATmega1280__)
     
digitalWrite(SPI_SS, HIGH);        // Deactivate original SS (Must be HIGH befor SPCR set)
#endif

        /* Setup SPI mode=0 */
        //SPCR = (1 << SPE) | (1 << MSTR) | (1 << CPOL) | (1 << CPHA);
        SPCR = (1 << SPE) | (1 << MSTR);

        /* Cleanup registers */
        clr = SPSR;
        clr = SPDR;
}

void ATD45DB161D::EndAndWait()
{
        DF_CS_inactive;  /* End current operation */
        DF_CS_active;    /* Some internal operation may occur
                          * (buffer to page transfer, page erase, etc... ) */

        /* Wait for the chip to be ready */
        while(!(ReadStatusRegister() & READY_BUSY))
        {}
        DF_CS_inactive;  /* Release SPI Bus */
}

int8_t ATD45DB161D::ComparePageToBuffer(uint16_t page, uint8_t bufferNum)
{
        uint8_t status;
       
        DF_CS_inactive;    /* Make sure to toggle CS signal in order */
        DF_CS_active;      /* to reset Dataflash command decoder     */

        /* Send opcode */
        spi_transfer((bufferNum == 1) ? AT45DB161D_COMPARE_PAGE_TO_BUFFER_1 :
                                        AT45DB161D_COMPARE_PAGE_TO_BUFFER_2);
       
        /* Page address */
        spi_transfer((uint8_t)(page >> 6));
        spi_transfer((uint8_t)(page << 2));
        spi_transfer(0x00);
       
        DF_CS_inactive;  /* Start comparaison */
        DF_CS_active;

        /* Wait for the end of the comparaison and get the result */
        while(!((status = ReadStatusRegister()) & READY_BUSY))
        {}
       
        if ( status & COMPARE )
           return 0;       /* If (COMPARE bit = 1) then Error  */
        else
           return 1;

}

WIZ812MJの接続

1)電源系統

WIZ812MJは3.3V動作かつ200mA弱電力を喰うため、3.3Vボルテージレギュレーターから給電します。ボルテージレギュレーターの入力は、VIN(ACアダプターからの給電)を使用します。5Vから電源を取るとArduino本体のボルテージレギュレーターに結構電流が流れて発熱が大きくなるためです。Data Flashも3.3V動作のためこの電源を使います。

2)MISO信号の接続

AT45DB161Dと同様に、入力は5V許容のため、SCLK, MOSI, SCS(SS)はArduinoと直結します。悩んだのはMISO(WIZ812MJ側は3.3V出力)です。Ethernet shieldではMCUと直結になっています。また、Ethernetライブラリを見ると、MISOをつなぐ入力ピンの内蔵プルアップ抵抗を有効にしています。以下に、hardware\libraries\Ethernet\utility\spi.hの抜粋を示します(コメントは私が追記)

// Set DDR -> SS:output, SCLK: output, MOSI: output. Others: input (default)
// Set PORTB -> SS_HIGH
// Set PORTB -> Input pin pull-up (MISO)
#define SPI0_Init()        DDRB  |= SPI0_SS_BIT|SPI0_SCLK_BIT|SPI0_MOSI_BIT;\
                                 PORTB |= SPI0_SS_BIT; PORTB &= ~(SPI0_SCLK_BIT|SPI0_MOSI_BIT);\
                                 SPCR  = 0x50

MCUのMISOピンを5VにプルアップしてしまえばHighレベルの閾値電圧の問題はなくなりますが、SPIデバイス側で問題が出ないかです。WIZ812MJに載っているW5100 TCP/IPチップはVIN(DC Input Voltage)を5.5Vまで許容しているため、MISOを5Vにプルアップしても問題なさそうです。

一方AT45DB161D Data Flashの方は、出力を5Vにプルアップできるという記載はありませんでした。そのため、AT45DB161Dはレベルシフターをかませて3.3V→5V変換を行っています。レベルシフターのピンが余っていたのでWIZ812MJ側もレベルシフターを介して接続しています(W5100チップの5V入力許容に気がつく前に、レベルシフター経由で配線してしまったというのが実情ですが、、)。

(2009/7/22追記)
複数のSPIデバイスが存在する場合、MISO信号をMCUの手前で束ねる必要があります。このために、レベルシフターの5V出力側で、WIZ812MJとAT45DB161DのMISO信号をワイヤードオアしています。レベルシフターの5V出力側はMOS FETを使っており、オープンドレインのワイヤードオア回路相当になっているので出力をぶつけても問題ないかと思っています。AT45DB161DはSSがinactiveになるとMISOをtri-state(ハイインピーダンス)にするという記述がデーターシートにあります。複数デバイスのの出力を接続する場合は以下の方法がありますが、SPIは(b)の形態になるようです(I2Cはaでしょうか);

(a)各デバイスのオープンドレインをワイヤードオア(プルアップ要)
(b)各デバイスで出力ピンのハイインピーダンス制御を行ってバス接続

今にして思えば、WIZ812MJとAT45DB161DのMISOを3.3V側で束ねてやるのが正しい設計のように思えます。とりあえず動いていますが、どこかで修正して動作確認してみます。

(2009/7/31追記)
WIZ812MJとAT45DB161DのMISOを3.3V側で束ねる形態に回路を変更。問題なく動いています。回路図も修正しました。

(2009/8/15追記)
Arduino Forumにて次のスレッドを見つけました。
Arduino + Ethernet Shield + Another SPI device
W5100チップにてMISOをハイインピーダンスにするためには、SS(SCS)の制御でなくSEN(SPI enable)信号をLowレベルに落としてやる必要があるとのことです(データーシートには記載がないのですが)。WIZ812MJはSS信号をモジュール内で反転してSENに入力しているため、MCUからSSをInactive(High)にすると自動的にSENがLowに落ちてくれます。そのため、WIZ812MJではSS信号に連動してMISOをハイインピーダンスにすることができるため、他のSPIデバイスとの混在が可能です。

Official Ethernet ShieldではSENをプルアップしているのみです。そのため、Official Ethernet ShieldではMISOをハイインピーダンスにすることができず、他のSPIデバイスと混在して使用する際は注意が必要です。

3)Ethernet Libraryの修正

Arduino IDE添付のライブラリはATMega168/328用です。Arduino Megaが使用しているATMega1280はSPI関連のピン収容が異なるためそのままでは動作しません。hardware\libraries\Ethernet\utility\spi.hを以下のように修正することでArduino Megaでも動作するようになります。

//------------------------------------------------------------------------
//AVR Mega168/1280 SPI HAL
#define BIT0                            0x01
#define BIT1                            0x02
#define BIT2                            0x04
#define BIT3                            0x08
#define BIT4                            0x10
#define BIT5                            0x20
#define BIT6                            0x40
#define BIT7                            0x80

#if defined(__AVR_ATmega1280__)            //Arduino Mega用ピン収容
#define SPI0_SS_BIT                    BIT0
#define SPI0_SS_DDR                    DDRB
#define SPI0_SS_PORT                    PORTB
#define SPI0_SCLK_BIT                    BIT1
#define SPI0_MOSI_BIT                    BIT2
#define SPI0_MISO_BIT                    BIT3

#else
#define SPI0_SS_BIT                    BIT2
#define SPI0_SS_DDR                    DDRB
#define SPI0_SS_PORT                    PORTB
#define SPI0_SCLK_BIT                    BIT5
#define SPI0_MOSI_BIT                    BIT3
#define SPI0_MISO_BIT                    BIT4
#endif

#define SPI0_SCLK_DDR                    DDRB
#define SPI0_SCLK_PORT                    PORTB

#define SPI0_MOSI_DDR                    DDRB
#define SPI0_MOSI_PORT                    PORTB

#define SPI0_MISO_DDR                    DDRB
#define SPI0_MISO_PORT                    PORTB


#define SPI0_WaitForReceive()
#define SPI0_RxData()                     (SPDR)

#define SPI0_TxData(Data)                (SPDR = Data)
#define SPI0_WaitForSend()                while( (SPSR & 0x80)==0x00 )

#define SPI0_SendByte(Data)                SPI0_TxData(Data);SPI0_WaitForSend()
#define SPI0_RecvBute()                    SPI0_RxData()

// PB4(MISO), PB3(MOSI), PB5(SCK), PB2(/SS)         // CS=1, waiting for SPI start // SPI mode 0, 4MHz
// Set DDR -> SS:output, SCLK: output, MOSI: output. Others: input (default)
// Set PORTB -> SS_HIGH
// Set PORTB -> Input pin pull-up (MISO)
#define SPI0_Init()                        DDRB  |= SPI0_SS_BIT|SPI0_SCLK_BIT|SPI0_MOSI_BIT;\
                                        PORTB |= SPI0_SS_BIT; PORTB &= ~(SPI0_SCLK_BIT|SPI0_MOSI_BIT);\
                                        SPCR  = 0x50

//------------------------------------------------------------------------

//------------------------------------------------------------------------
//IInChip SPI HAL
#define IINCHIP_SpiInit                    SPI0_Init
#define IINCHIP_SpiSendData                SPI0_SendByte   
#define IINCHIP_SpiRecvData                SPI0_RxData


#if defined(__AVR_ATmega1280__)
#define IINCHIP_CS_BIT                    BIT0
#define IINCHIP_CS_DDR                    DDRB
#define IINCHIP_CS_PORT                PORTB

#else
#define IINCHIP_CS_BIT                    BIT2
#define IINCHIP_CS_DDR                    DDRB
#define IINCHIP_CS_PORT                PORTB
#endif

#define IINCHIP_CSInit()                    (IINCHIP_CS_DDR |= IINCHIP_CS_BIT)
#define IINCHIP_CSon()                    (IINCHIP_CS_PORT |= IINCHIP_CS_BIT)
#define IINCHIP_CSoff()                    (IINCHIP_CS_PORT &= ~IINCHIP_CS_BIT)
//------------------------------------------------------------------------

その他

at45db161d, Ethernet libraryの両方を使って、漢字表示(SPI Data Flash memoryへのアクセス)とTCP/IP接続の併用ができるようになりました。

GLCDはピンソケットを使いユニバーサル基板面から持ち上げて実装しているため、GLCDの下側に広大な敷地があります。この敷地に生AVR(ATMega644あたり)を載せてしまえば、高価なArduino Megaを占有せずに回路が組めそうです。最終目的のRSSリーダー用ファームができるまでは頻繁にライブラリやスケッチの更新を行うためMegaを使うとして、安定版ができたら、ATMega644 + SanguinoにスイッチしてMegaを浮かせるという道もありそうです。

あと、コストパフォーマンスを考えると、ストロベリー・リナックスさんで販売しているWIZ200Webという選択肢もおもしろそうです。W5300とATMEGA128,フラッシュROM(4Mビット),外部RAM(256Kビット)がついて4300円と大変お得です。TCP/IPチップがW5300のため、Arduino Ethernet libraryはそのままでは使えないと思いますが。

2009/7/22追記:
MISO信号のバス接続に関する記述を追加。

2009/7/31追記:
MISO信号の接続を変更。

Arduinoで漢字表示(3)

前回の予告に従い、漢字フォントデーターをI2C EEPROMより大容量のSPI Data Flash Memoryに格納してみました。

使用したメモリ

ATMEL社のAT45DB161Dをスイッチサイエンスさんから購入。容量は2MBとSDメモリーカードに比べると小さいですが、漢字フォントを格納するためには十分です。お値段も購入時点で360円でしたのでお安いです。

Arduinoとの接続

回路図を以下に示します。

Glcd_dataflash_2

AT45DB161DとArduinoはSPIで接続します。

AT45DB161Dの動作電圧は2.7V~3.6Vですので、Arduinoの3.3V電源出力を使用して駆動しました。SI, SCK, CS, REST入力は5V許容のため、Arduinoと直結です。SO信号はHighレベルの出力が3.1V~3.3Vとなり、5Vで動作するAVRがHレベルを認識できる下限値(0.6*VCC = 3.0V)ぎりぎりの値となります。直結でも動作しましたが、レベルシフターを入れてあります。レベルシフターもスイッチサイエンスさんで購入しました。

文字列をFlash Memoryに書き込んで読み出しを行う試験をした際に、最初は不定期に文字化けが発生する現象に悩まされました。パスコンの位置を変えると文字化けの出方が変わるのですが解消はしてくれません。最終的にAT45DB161DのGNDを最短距離で配線することで解決しました(当初は、ブレッドボードのグランドラインを使用したためグランド配線が長めに引き回されていました)。SPIのクロックが4MHz(システムクロックの1/4)の設定になっているため、これまで自作した回路の中では一番ノイズの影響を受けやすいのだと思います。オシロスコープがあれば波形を観測してノイズの影響など調べるとおもしろそう。オシロスコープ欲しいです。この機種あたりなら、手が届くのですが、、、

AT45DB161Dのライブラリ

Arduino用のライブラリが見つかりました。大抵のライブラリはArduinoコミュニティーから調達できるため便利です。

Arduino playgroundに掲載されている、dataflashライブラリも動作しましたが、AT45DB161Dで使用しているSPIコマンドが一部未サポートです。dataflashライブラリはAT45DB041BなどのB version(型番末尾のアルファベットがバージョンを示します)をターゲットにしているためCバージョン以降で追加されたコマンドが未サポートのようです。

Webを漁ると、BlockoSさんのblogにAT45DB161D用のライブラリがありました。丁寧なドキュメントもあります。私が試した範囲では、ComparePageToBuffer()関数にバグがありました。オリジナルのコードは比較を行った後にstatusレジスタBit6が1の場合正常の判定を行っていますが、0が正常値です。そのため、以下のようにコードを修正しました(青字が修正部分)。

int8_t ATD45DB161D::ComparePageToBuffer(uint16_t page, uint8_t bufferNum)
{
        uint8_t status;
       
        DF_CS_inactive;    /* Make sure to toggle CS signal in order */
        DF_CS_active;      /* to reset Dataflash command decoder     */

        /* Send opcode */
        spi_transfer((bufferNum == 1) ? AT45DB161D_COMPARE_PAGE_TO_BUFFER_1 :
                                        AT45DB161D_COMPARE_PAGE_TO_BUFFER_2);
       
        /* Page address */
        spi_transfer((uint8_t)(page >> 6));
        spi_transfer((uint8_t)(page << 2));
        spi_transfer(0x00);
       
        DF_CS_inactive;  /* Start comparaison */
        DF_CS_active;

        /* Wait for the end of the comparaison and get the result */
        while(!((status = ReadStatusRegister()) & READY_BUSY))
        {}
       
        if ( status & COMPARE )
           return 0;       /* If (COMPARE bit = 1) then Error  */
        else
           return 1;

}

動作試験

前回掲載したJIS配列の漢字フォントをAT45DB161Dに書き込んで動作試験を行いました。

AT45DB161Dは書き込みはページ単位、読み出しはページ番号+オフセットを指定して行います。ページサイズはデフォルトで528byteとなっています。512byte/pageに変更できますが、一度変更すると528byteには戻せないとデーターシートに記載があります。2のべき乗でないと使いづらそうなので512byte/pageに変えてやろうかと思ったのですが、ライブラリは528byte/pageを前提に作ってあります。

よく考えてみると、漢字フォントのデーターサイズは24byte(全角)or 12byte(半角)のため、1 pageにぴったり収容できます(全角22文字がぴったり入る)。そのため、528byte/pageのままで使用しました。フォトンデーターの読み出しは、ContinuousArrayRead()関数を使って、指定したページ + オフセット位置から24byte分のデーターを読み出すようにしています。

前回同様のJISコード順に漢字を表示するだけのテストプログラムですが、以下の通り動作させることが出来ました。

Jiskanji_spiflash

正確な測定はしていませんが、I2CインタフェースのEEPROMより表示が早くなったように思います。SPIクロックが4MHzのため、I2Cより高速動作ができているようです。

次の目標は、Unicode(UCS-2)版のフォントデーターを作成して表示を行うことです。あと、そろそろユニバーサル基板にGLCD,メモリーなどを搭載して、GLCDシールドに移行したいと思っています。

2009/7/21更新:Unicodeフォントを「Arduinoで漢字表示(4)」で公開しました。

Arduinoで漢字表示(2)

前回の続編です。まだ最終的な方向性が定まっていないのですが、JISコードベースで漢字が表示できるようになりました。

漢字フォントデーター

RSSなどUTF-8でエンコードされたコンテンツを表示するためには(ちなみに、このブログもUTF-8です)、内部コードとしてUnicode(UCS-2)を使うと効率がよいと思われる点は前回記載しました。漢字をUnicode配列で収録したフリーのフォントデーターを探したところ、efontさんが東雲(Shinonome)フォントのUnicode版を配布していることを発見。また、efontさんからJISコードベースで収録した東雲フォントもダウンロード可能です。

問題は、Unicodeの場合ハングルや中国語固有の漢字など多数のフォントデーターが収録されており、JIS漢字の東雲フォントに比べるとデーター量が大きくなることです。以下にフォントファイルに収録されている文字数(bdfファイルのCHARS宣言の値)を示します:

  • shnmk12min.bdf(JIS): 6879文字
  • b12.bdf(Unicode):22759文字(半角英数字を含む)

12ドットフォントの場合、文字あたり24Bのデーター量になるため、2.2万文字のUnicodeフォントを格納するためには500KB程度のROM容量が必要です。日本語漢字コードが連続した領域に配置されている場合、そこだけ抜き取ってROMに書き込むことができますが、UnicodeではCJK(中国・日本・韓国)統合漢字として由来が同一の文字単位にまとめているせいか、JISに収録されている漢字だけを抜き取ると並びが不連続になってしまいます。そのため日本語漢字だけを抜き取った場合、文字コードからフォントの格納位置を一律に計算できないため、文字単位にアドレス変換テーブルが必要になります。

変換テーブルを置くくらいなら、CJK統合漢字全体をEEPROMに格納して文字コードで直接EEPROMアドレスをインデックスするのがよいのですが、手持ちのAT24C1024B(1Mbit I2C EEPROM)x2個には入りません。そのため、。先ずはJISの東雲フォントを使用することとしました。

漢字フォントは前述のI2C EEPROM x2個に格納しました。EEPROMを接続した全体の回路図を以下に示します。SCL/SDAのプルアップはAVRの内蔵プルアップ抵抗を使用(Wireライブラリを使用すると自動的に内部プルアップが有効になります)。

Glcd_eeprom

フォントデーターの変換

ArduinoのGLCDライブラリとしては、本家のwebで公開されているks0108 GLCDライブラリを使用します。先日Ver.2が公開されたため、V2をベースに漢字表示機能を追加します。V2への高速表示の移植は別途にします。(シリアル接続のEEPROMにフォントデーターを格納する時点で高速動作は期待できないため)。

先ずbdfフォントのビットマップデーターを、Arduinoのks0108 GLCDライブラリで使用しているビットマップデーターに変換します。変換の方法は前回の「Arduinoで漢字表示」に記載した通りです。ツールを作って変換を行ったファイルを以下に掲載します。
 「glcdfont_shnm12min.zip」をダウンロード

  • glcdfont_shnm12min.txt:外付けEEPROMに書き込む形式に変換した漢字フォントデーター(半角カナを含む)
  • shnm.h:東雲6x12の半角英数字フォントをks0108 GLCDライブラリのフォント形式に変換したファイル。スケッチ上で、shnm.hをincludeすることで使用可能(AVR内蔵Flashメモリにフォントデーターを格納)

JIS X 208の文字コードでは、「区(Row)」と呼ばれる面(ページのようなものでしょうか)に、94文字分のコードを割り当てます。例えば、「01区」は0x2121~0x217dのコードエリアを使用します。文字を割り当てていないコードポイント(例えば、0x2230~0x2238)にはフォントデーターが存在しないのですが、フォントを格納したEEPROMのアドレス計算を容易にするため、スペース相当のダミーデーターを挿入しています。

フォントデーターの転送

PCからシリアル通信を使ってArduinoにフォントデーターを転送します。PCのアップローダーアプリとArduino側の受信(およびEEPROMへの書き込み)スケッチを作成しました。アップローダーのバイナリファイルとArduinoのスケッチを以下に掲載します。
 「Upload.zip」をダウンロード

  • Upload.exe: PC側のアップローダー(.Net Framework 2.0以上が必要)
  • FrontWrite.pde: Arduino側のフォントデーター受信、書き込み用スケッチ

アップローダーの画面イメージを以下に示します。

Uploadapp_4

ファイルを選択して、Uploadボタンをクリックすると送信を開始します。プログラムを最初に動かした時は、シリアル通信が始まらない場合があります。その際は一旦プログラムを強制終了してやり直すとうまくいきます。Arduinoへデーター転送中に中断したい場合も、タスクマネージャーから強制終了する必要があります。このあたりは手抜きですが、、

フォントデーターのEEPROMへの書き込みは、当初はページ書き込み(24byte or 12byte単位)を使用したのですが、特定のアドレスで書き込みエラーが発生する問題を解決できず、byte単位の書き込みを行っています。読み出しは、24 or 12Byte単位のシーケンシャル読み出しができています。

ks0108ライブラリの改造

とりあえず、JISコードを直接指定して1文字を表示するPutUChar()関数を追加しました。改造版のソースコードを以下に掲載します。
 「ks0108_jisKanji.zip」をダウンロード

改造版ライブラリで漢字を表示した画面を以下に示します。

Jiskanji

今後の計画

まだまだ先は長いです。次にやることの選択肢として以下があります。

1)Unicode→ JISコード変換テーブルの作成

このURLにJIS - Unicodeの対応表があるのですが、全く一貫性・法則性がなくビット演算レベルでの変換は無理そうです。そのため、力業ですが、文字コード毎に変換テーブルを作る方法が考えられます。Unicode側のコード・スペースを2万として、16bitのJISコードに変換するテーブルを作る場合、テーブルサイズは 2万 x 2≒ 40KBになります。

アクセス速度を考えると、このテーブルはシリアルEEPROMではなく、AVRの内蔵Flash ROMに格納する必要があります。Arduino MegaのATMega 1280なら容量的には格納できる範囲ではあります。

2)大容量メモリーにUnicodeフォントを格納

1MB程度のメモリーがあればUnicodeフォントを格納できますが、I2CのEEPROMでは1Mbit以上の製品はなさそうです。スイッチサイエンスさんのSPI接続フラッシュメモリAT45DB161Dあたりを使ってデーターを格納する解がありそうですが、以下が気になる点です:

  • AT24C1024B(I2C)よりアクセスのオーバーヘッドがあるかも
    → 当初は512Byteのページ単位アクセスのみかと思ったのですが、データーシートを見直すと開始アドレスを指定したシーケンシャル読み出しもあるため、アドレス指定が長くなる以外は(I2C EEPROMに比べて)特にオーバーヘッドはなさそうです
  • 動作電圧が3.3V。Arduinoと接続するためにはレベル変換回路が必要。スイッチサイエンスさんの「ロジックレベル変換モジュール」が使えそう。3.3V電源は、Flashメモリーを駆動するだけならArduinoの3V出力(50mA)が使えそうです

次は、AT45DB161D(SPI版、2MB)を使って、どの程度の表示速度が得られるかを実験してみます。現在使用しているAT24C1024B(I2C版、128KB)では、AVR内蔵Flashに比べるとかなり表示が遅くなりますが、RSSビューワー的な使い方なら許容範囲です。

2009/7/7追記:
AT45DB161Dのデーターシートを見直すと、DC特性表の注釈にI/O(SI, SCK, CS)は5-Volt tolerantという記述を発見(こういう情報はサマリーページに書いて欲しい!)。上記信号はArduinoと直結出来ることが分かりました。

SO(AT45DB161Dのデーター出力)は、3.3V駆動時のV-OH(min) が3.1Vとなるため、Arduinoに直結してもぎりぎりHレベルの認識は可能。SOに関してはレベルシフターをかまして、5Vにきっちり上げてやった方が安定性の点では望ましいですが、実力的には直結でも動きそうです(以前3.3V駆動のシリアルをArduinoに直結した際は正常に動作しました)。

直結・レベルシフター経由の双方で動作を試してみたいと思います。

2009/7/10追記:
GLCD DB0~7のピン番号(回路図)に誤りがあったため修正。

2009/7/12追記:
AT45DB161Dの接続結果を「Arduinoで漢字表示(3)」に記載しました。

« 2009年6月 | トップページ | 2009年8月 »

2018年10月
  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      
無料ブログはココログ