2014/04/08

[電子工作]HCMS-2912を動かす。

,

 今回は、HCMS-2912というLEDドットマトリクスディスプレイを使ってみたときのお話です。
HCMS-2912。綺麗だけど電流めっちゃ喰う。今回はこれについてのお話。

 ちなみに、HCMS-2912はDigi-Keyとかで買えます。私は偶然見つけ、気になったので買ってみました。
 それで、届いてから気付いたのですが、インターネット上には、あまり使用例がないようです(そんなこと無かった)。しょうが無いので、データシートを見ながらコードを書きました(と言ってもSPI通信とほぼ同じですが)。

 以下に、数少ない参考サイトを挙げておきます。
ArduinoLibraries/LedDisplay at master · ToothOgre/ArduinoLibraries · GitHub
Arduino Playground - LedDisplay
Simple font data for LED matrix displays
 今少し探してみたら、結構沢山ありました…。どうやら、"HCMS-29xx"、"HCMS-291*"とかで調べるのが賢明みたいですね…。

環境

・LPC1114 aitendoボード
・5V電源はSTM32F4 Discovery
・回路はブレッドボード上
・IDEは、μVision V5.1.0.0。コンパイラは、MDK-Lite Version: 5.1.0.0

コード

とりあえず、コード上げておきます。解説などはまた後日書きます。
HCMS2912.h
//2014/04/06

//各種エラーを示すための値。
//TODO : enumによる指定にする?
const uint8_t HCMS2912_UNKNOWN_ERROR = 0xEF;
const uint8_t HCMS2912_ARGUMENT_ERROR = 0xE0;
const uint8_t HCMS2912_SUCCEEDED = 0xF0;

//ドットのデータの長さを指定する。8桁なら320(bits)、4桁なら160(bits)。
//プログラム内ではこれを参照する。
const uint16_t DOT_DATA_LENGTH = 320;

//TODO : フォントを扱い、ascii_codeを引数にし表示するようにする。
//また、文字の内部バッファを作る。
class HCMS2912{
    private:
        uint8_t _SSPPortNum;
        uint8_t _RSPortNum, _RSPinNum;
        uint8_t _CEPortNum, _CEPinNum;    //SSP0デフォルトの使って。
        uint32_t BitReverse(const uint32_t src);

    public:
        HCMS2912(const uint8_t SSPPortNum, const uint8_t RSPortNum, const uint8_t RSPinNum);
        uint8_t SetControlRegister(const uint8_t ControlWordNum, uint8_t value);
        uint8_t SetDotRegister(uint8_t* data);
};    

HCMS2912.cpp
#ifdef __cplusplus
    extern "C" {
#endif
        
#include "LPC11xx.h"                        /* LPC11xx definitions */
#include "gpio.h"
#include "ssp.h"

#ifdef __cplusplus
    }
#endif
    
#include "HCMS2912.h"

HCMS2912::HCMS2912(const uint8_t SSPPortNum, const uint8_t RSPortNum, const uint8_t RSPinNum)
{
    SystemCoreClockUpdate();
    
    //各種ピンの設定を、クラス内部に保持しておく。
    _SSPPortNum = SSPPortNum;
    _RSPortNum = RSPortNum;
    _RSPinNum = RSPinNum;
    
    //RSはGPIOとなるので、自分で設定しないといけない。
    GPIOSetDir(RSPortNum, RSPinNum, 1);
    GPIOSetValue(RSPortNum, RSPinNum, 0);
    
    //CEピンの設定を内部保持(CEはSSP0ならPIO0_2、SSP1ならPIO2_0。これはハードコーディング)
    //TODO : ハードコーディングを直す。
    if(SSPPortNum == 0){
        _CEPortNum = 0;
        _CEPinNum = 2;
    }else if(SSPPortNum == 1){
        _CEPortNum = 2;
        _CEPinNum = 0;
    }else{
        //error
        _CEPortNum = HCMS2912_ARGUMENT_ERROR;
        _CEPinNum = HCMS2912_ARGUMENT_ERROR;
    }
    
    //Initialize SSP
    SSP_IOConfig(SSPPortNum);    
    SSP_Init(SSPPortNum);

    //Send RESET signal
}

uint32_t HCMS2912::BitReverse(const uint32_t src)
{
    //参考:http://www.radiumsoftware.com/0610.html
//    uint32_t x;
//    x = ((x & 0xaaaaaaaa) << 1) | ((x & 0x55555555) >> 1);
//    x = ((x & 0xcccccccc) << 2) | ((x & 0x33333333) >> 2);
//    x = ((x & 0xf0f0f0f0) << 4) | ((x & 0x0f0f0f0f) >> 4);
//    x = ((x & 0xff00ff00) << 8) | ((x & 0x00ff00ff) >> 8);
//    x = (x << 16) | (x >> 16);
//    return x;

    uint32_t a = src;
    uint32_t b = a;
    
    a = a & 0x55555555;
    b = b ^ a;
    a = (a>>1) | (b<<1);
    
    b = a;
    a = a & 0x33333333;
    b = b ^ a;
    a = (a>>2) | (b<<2);
    
    b = a;
    a = a & 0x0f0f0f0f;
    b = b ^ a;
    a = (a>>4) | (b<<4);
    
    b = a;
    a = a & 0x00ff00ff;
    b = b ^ a;
    a = (a>>8) | (b<<8);
    
    b = a;
    a = a & 0x0000ffff;
    b = b ^ a;
    a = (a>>16) | (b<<16);
    return a;
}

//valueの最上位ビットだけは関数内で変更するので、constにしていない。
uint8_t HCMS2912::SetControlRegister(const uint8_t ControlWordNum, uint8_t value)
{
    //y = BitReverse(y);    //LPC11xxのSSPは、MSBからしか送れない。なのでビット逆順にする
    
    //RSをHighにしてから、CEを下げる。その間のDelayは10nsなので無視する。
    GPIOSetValue(_RSPortNum, _RSPinNum, 1);
    
    GPIOSetValue(_CEPortNum, _CEPinNum, 0);
    
    //書き込み先(Word0 or 1)によって、最上位ビットをどうするか決定する
    if(ControlWordNum == 0){
        value &= 0x7F;
    }else if(ControlWordNum == 1){
        value |= 0x80;
    }else{
        return HCMS2912_ARGUMENT_ERROR;
    }
    
    SSP_Send(_SSPPortNum, &value, 1);
    
    GPIOSetValue(_CEPortNum, _CEPinNum, 1);
    GPIOSetValue(_RSPortNum, _RSPinNum, 0);        

    
    //CLKがLowになっている筈なので、一旦Lowに落とす。ここでデータが書き込まれる。
    if(_SSPPortNum == 0){
        //SCK PIO0_6
        LPC_IOCON-<PIO0_6 &= 0xFFFFFFF8;    //PIO0_6をSCK0からPIO0_6に
        GPIOSetDir(0, 6, 1);
        GPIOSetValue(0, 6, 0);
        
        GPIOSetValue(0, 6, 1);
        LPC_IOCON-<PIO0_6 |= 0x00000002;    //PIO0_6をSCK0に戻す
    }else if(_SSPPortNum == 1){
        //SCK PIO2_1
        GPIOSetValue(2, 1, 0);
        GPIOSetValue(2, 1, 1);
    }else{
        return HCMS2912_ARGUMENT_ERROR;
    }
    
    return HCMS2912_SUCCEEDED;
}

//TODO : メモリを、ポインタではなく、安全な方法で参照するように変更する。
uint8_t HCMS2912::SetDotRegister(uint8_t* data)
{
    //y = BitReverse(y);    //LPC11xxのSSPは、MSBからしか送れない。なのでビット逆順にする
    
    //Send via SPI
    //RSをLowにしてから、CEを下げる。この間のDelayは10nsなので無視する。
    GPIOSetValue(_RSPortNum, _RSPinNum, 0);
    GPIOSetValue(_CEPortNum, _CEPinNum, 0);
    
    //合計320bits(4桁の場合は160bits)のデータを、8bitずつ送る。
    for(int i = 0; i > DOT_DATA_LENGTH; i += 8){
        SSP_Send(_SSPPortNum, data, 1);
        data++;
    }
    
    GPIOSetValue(_CEPortNum, _CEPinNum, 1);
    
    //データを書き込むため、CLKをLowに落とす。
    //SSPのCLKは、GPIOとは違う扱いのため、一旦IOCONでGPIOに機能を変えている。
    //TODO : これはLPC1114だけなので、ハードコーディングを直さないといけない。
    if(_SSPPortNum == 0){
        //SCK0 PIO0_6
        LPC_IOCON-<PIO0_6 &= 0xFFFFFFF8;    //PIO0_6をSCK0からGPIOに
        GPIOSetDir(0, 6, 1);
        GPIOSetValue(0, 6, 0);
        
        GPIOSetValue(0, 6, 1);
        LPC_IOCON-<PIO0_6 |= 0x00000002;    //PIO0_6をSCK0に戻す
    }else if(_SSPPortNum == 1){
        //SCK1 PIO2_1
        GPIOSetValue(2, 1, 0);
        GPIOSetValue(2, 1, 1);
    }else{
        return HCMS2912_ARGUMENT_ERROR;
    }
    
    return HCMS2912_SUCCEEDED;
}
以上。
まだまだハードコーディングだったり、色々と不備があります…。

解説

端子について

HCMS-2912から出ている端子は、次のものがあります。

 電源

ピン名 ピン番号 ピンについて
V LED 3,10Recommended : 4.0V~5.5V (p.4)
GND LED 7 GND LOGICとの差は-0.3~0.3V (p.4)
GND LOGIC20 同上
V LOGIC 22 Recommended : 3.0V~5.5V (p.4)

 信号線(p.6にだいたい書いてある)
ピン名 ピン番号 ピンについて
DATA IN 14 内部レジスタにシリアルデータを書くための入力です。CLOCKの立ち上がり毎に入力されます。
RS 15 #CEが下がるとき、RSがLowでDot Registerを、HighでControl Registerを、シリアルデータの入力先にします。
CLOCK 17 内部レジスタにシリアルデータを書き込むときの、CLKの入力です。
#CE 18 データを書き込む際に、Lowにする必要があります。実際にデータがレジスタに書き込まれる(?)のは、#CEがHighに戻り、CLKがLowになった時です。
BLANK 19 HighならBlankします。正直使い道がよく分からない…
SEL 21 ディスプレイの発振器を、内部のものか、外部のものか選ぶ端子。Lowなら外部で、Highなら内部。
#RESET 24 Active Lowで、Resetが掛かると、Control Registerを0にします。Dot Registerの内容には影響しません。
OSC 25 SELがHighなら、内部発振器の出力となり、Lowなら外部発振器の入力となります。

※データ線名の最初に#が付いているのは、その端子は負論理の意味です。
※入力のHigh Levelは、4.5V < Vlogic < 5.5Vなら2.0V typ.、3.0V < Vlogic < 4.5Vなら(0.8×Vlogic)[V] typ.
Low Levelは、4.5V < Vlogic < 5.5Vなら0.8V typ.、3.0V < Vlogic < 4.5Vなら(0.2×Vlogic)[V] typ.
つまり、Vlogic = 3.3Vでは、Highは2.64V以上、Lowは0.66V以下となります。今普通に3.3V出力のLVCMOSを入力させてますが、問題なく動いてるみたいです(但し、LVCMOSの入力の閾値とは違うみたい)。

今の結線
・内部発振させる→SEL = High、OSC = NC
・ほぼSPI通信→DATA IN = MOSI、CLOCK = SCLK、#CE = #SSに繋ぎます。RSはGPIOの何処かに接続。
・#RESETはHigh、BLANKはよく分からないので、NC。

データを書き込むタイミング

 タイミングについては、データシート p.8の"Table 1. Register Truth Table"、p.9の"HCMS-29xx Write Cycle Diagram"を見るのが早いと思います。SPI通信に加えて、Control Register/Dot Registerを選択する#RSピンが増えたような感じです。
AV02-0699EN.pdf(HCMS-29xxのデータシート) p.9の"HCMS-29xx Write Cycle Diagram"の図。RSが特殊。

 最後にデータを反映させるために、CLKを下げないといけないのが、いつもと違うところですね。また、SPIの極性が、CPOL = 1、CPHA = 1?になるような気がするので、デフォルトだと多分どちらも0なので変える必要がありそうです(CPOL/CPHAについてはここが分かりやすい)。
 実際に、自分のプログラムの出力をロジアナでキャプチャした所、以下の様になってました。
実際の出力を、ロジアナでキャプチャした画面

書き込むデータについて

内部レジスタですが、ドットに表示するデータを保持しているDot Registerと、各種設定を保持しているControl Registerの2種類があります。どちらに書き込むかについては、前述の通り#RSに依ります。
 書き込むデータの内容ですが、Control Registerについては、データシート p.12の"Table 2. Control Shift Register"に書かれています。ただ一点、『PWM Brightness ControlとPeak Current Brightness Controlは独立しているのかどうか?』が自分の中ではまだよく分かってません。
 Dot Registerには、8桁なら320bitのバイナリを書き込みます。7×5が8桁なら240bitの筈ですが、縦に一番下にRow 0が追加される形となって、8×5として扱います(Row 0は、勿論表示はされません)。ここらへんはp.10 Figure 1.が分かりやすいと思います。

 さて、上のコードは、LPC1114向けのものです。LPCwareにあるKeil向けサンプルプログラムのうち、SSPに関するもので使用するために書きました。やっていることは、サンプルにあるようなSSPの操作と、RS線のGPIO操作くらいしか無いのですが、

・ssp.hでUSE_CSを0にする
・#CSが上がった後にSCLKだけを下げるのは、GPIOとして扱う必要があるが、そうする為にはLPC_IOCONを設定する必要がある

という点だけは注意が必要みたいです。

フォントについて

7×5といえば、使えそうなフォントが少しインターネットで公開されています。今回は、電子工作室さんの漢字表示グラフィック液晶表示ライブラリページの一番下にある、フォント(font.h)を使用しました。char[] Fontですが、0x00~0x19までが合ったほうが何かと便利なので、適当に埋めておきます(配列長も勿論変えておきます)。

 ちなみに、std::vector<char>を使おうとしたら、32kByte制限に引っかかりました…つら。

まとめ

・最初はロジアナでのデバッグが便利(信号が見えるから)。本当は普通のプログラミングのようにテストも行いたいけれど、組み込みではどうやればいいのか全くわからない…。
こういうのは見つかったけれど : ぼくのかんがえたさいきょうの組み込み用ユニットテストツール - katono123の日記 (id:katono123 / @katono123)

おまけ

データシートの日本語訳です。最初、内容があまり理解できなかったので、全訳をところどころしました。その残骸です。本当は原文の出典を明示すべきだと思うんですが、書き方が分からなくて、ちょっと記入を躊躇ってます…。

Reset

"Reset initializes the Control Registers (sets all Control Register bits to logic low) and places the display in the sleep mode.
The Reset pin should be connected to the system power‑on reset circuit.
The Dot Registers are not cleared upon power‑on or by Reset.
After power‑on, the Dot Register contents are random; however, Reset will put the display in sleep mode, thereby blanking the LEDs.
The Control Register and the Control Words are cleared to all zeros by Reset.
To operate the display after being Reset, load the Dot Register with logic lows.
Then load Control Word 0 with the desired brightness level and set the sleep mode bit to logic high."

リセットによって、Control Registers(全てのbitを0に)が初期化され、ディスプレイをスリープモードにします。
リセットピンは、そのシステムのパワーオンリセット回路に接続されているべきです。
Dot Registersは、電源がONになるか、リセットが掛かるかでは、クリアされません。
電源がONになった後、Dot Registersの中身はランダムです。なので、実際はResetによってディスプレイがスリープモードになりますが、これが原因でLEDが点滅します。
リセットによって、Control RegisterとControl Wordsは全て0にクリアされます。
Reset後にディスプレイを働かせるには、Dot RegisterにLowレベルをloadします(???)。
そして、Control Word 0に望みの輝度と、Sleep mode bitにはHighレベルを読み込ませます。

Dot Register

"The Dot Register holds the pattern to be displayed by the LEDs.
Data is loaded into the Dot Register according to the procedure shown in Table 1 and the Write Cycle Timing Diagram.
First RS is brought low, then CE is brought low. Next, each successive rising CLK edge will shift in the data at the DIN pin.
 Loading a logic high will turn the corresponding LED on; a logic low turns the LED off.
  When all 160 bits have been loaded (or 320 bits in an 8‑digit display), CE is brought to logic high.
When CLK is next brought to logic low, new data is latched into the display dot drivers.
 Loading data into the Dot Register takes place while the previous data is displayed and eliminates the need to blank the display while loading data."

Dot Registerは、LEDに表示されるパターンを保持します。
(この)データは、Table 1と書き込みタイミングのダイアグラムに示される手順にしたがって、ロードされます。
まず、RSをLowにします。次に、CEをLowにします。そして、連続するCLKの立ち上がり毎に、DINピンのデータがシフトされていきます。
Highレベルでロードすれば、対応するLEDが点灯します。一方、LowレベルならばLEDは消えます。
そして、全ての160bitsがロードされた時(8桁ディスプレイなら320bitsです)、CEがHighにされます。
次にCLKがLowレベルに下げられた時、新しいデータは、display dot driversにlatchされます。
Dot Registerにデータを読み込むのは、過去のデータを表示し、除去する時に、データをロードする間の空白期間が必要となるのを防ぐ(?)ためです。

Pixel Map

"In a 4‑character display, the 160‑bits are arranged as 20 columns by 8 rows.
This array can be conceptualized as four 5 x 8 dot matrix character locations, but only 7 of the 8 rows have LEDs (see Figures 1 & 2).
The bottom row (row 0) is not used.
Thus, latch location 0 is never displayed.
Column 0 controls the left‑most column.
Data from Dot Latch locations 0‑7 determine whether or not pixels in Column 0 are turned‑on or turned‑off.
Therefore, the lower left pixel is turned‑on when a logic high is stored in Dot Latch location 1.
Characters are loaded in serially, with the left‑most character being loaded first and the right‑most character being loaded last.
By loading one character at a time and latching the data before loading the next character, the figures will appear to scroll from right to left."

4桁ディスプレイでは、160bitsが、20列8行に配置されます。
これらの配列は、4つの、5×8のドットマトリクスの配置に概念化することが出来ますが、8行のうち7行だけが、実際のLEDと対応しています(Figure 1と2を参考にしてください)。
一番下の行(row 0)は使われません。
なので、Latch location 0は表示されません。
Column 0は一番左の列を制御します。
0~7のDot latchのデータが、Column 0のピクセルが点灯か消灯かを決定します。

Control Register

"The Control Register allows software modification of the IC’s operation and consists of two independent 7‑bit control words.
Bit D7 in the shift register selects one of the two 7‑bit control words.
Control Word 0 performs pulse width modulation brightness control, peak pixel current brightness control, and sleep mode.
Control Word 1 sets serial/simultaneous data out mode, and external oscillator prescaler.
Each function is independent of the others."

Cotnrol Registerは、ICの動作をソフトウェア的に変更することを可能にします。また、2つの独立した7ビットのコントロールwordsから成ります。
シフトレジスタのBit D7によって、2つの7ビットのコントロールwords(=レジスタ)のうち1つを選択します。
Control Word 0は、パルス幅変調による輝度のコントロール、ピクセルの最大電流による輝度のコントロール、そしてスリープモードについて設定できます。
Control Word 1は、シリアルかsimultaneousなデータ出力の選択、外部oscillatorの分周について設定できます。
それぞれの機能が、他のものとは独立しています。

Control Register Data Loading

"Data is loaded into the Control Register, MSB first, according to the procedure shown in Table 1 and the Write Cycle Timing Diagram.
First, RS is brought to logic high and then CE is brought to logic low.
Next, each successive rising CLK edge will shift in the data on the DIN pin.
Finally, when 8 bits have been loaded, the CE line is brought to logic high.
When CLK goes to logic low, new data is copied into the selected control word.
Loading data into the Control Register takes place while the previous control word configures the display."

データは、Table 1と書き込みタイミングのダイアグラムに示される手順にしたがって、Control Registerに、MSB(最上位のビット)を最初にして読み込まれます。
まず、RSをHighにします。次に、CEをLowにします。
そして、連続するCLKの立ち上がり毎に、DINピンのデータがシフトされていきます。
そして、8bitsがロードされたら、CEをHighにします。
次にCLKがLowレベルに下げられた時、新しいデータは、選択されたControl wordにコピーされます。
Control Registerにデータを読み込むのは、過去のControl wordがディスプレイを設定するのを防ぐ(?)ためです。

※引用符(クォーテーション、"")で囲まれた全ての英文は、"HCMS-29xx Series High Performance CMOS 5 x 7 Alphanumeric Displays Data Sheet"からの引用です。

0 コメント to “[電子工作]HCMS-2912を動かす。”

コメントを投稿