ArduinoでグラフィックLCDを動かす(AQM1248A編)(12)

このページをスマホなどでご覧になる場合は、画面を横長にする方が読みやすくなります。
目次へ  前のページへ (8) (9) (10) (11) (12) (13) (14) (15) (16) 次のページへ
2016年05月15日 公開。
4-1-8-2.SetPixel関数の問題が表面化

前のページでは、水平方向に1つおきに点を並べて点線を引きましたが、今度は垂直方向に点線を引いてみましょう。

前のページリスト19のループを次の様に書き変えれば縦の点線が引けるようになるはずです。

リスト20、縦方向に点線を引く処理を行うループCOPY
  for(int y=0; y<LCD_HEIGHT; y+=2) {
    SetPixel(63,y);
  } // for y

ループ変数yは、点のy座標を表しています。y=0,2,4,…,44,46と、0~46の範囲で、2ステップで増加します。

一方で、x座標は63に固定してあります。

リスト18の点線を引くループ部分(リスト19)をリスト20に置き換えて実行すると、写真31の様な画面になります。

写真31、リスト18の中の点線を引くループ部分をリスト20に書き換えた場合の画面
↑ 画像をクリックすると拡大
写真31、リスト18の中の点線を引くループ部分をリスト20に書き換えた場合の画面

確かに、縦に点線が引けていますが、点と点の間隔が長いですね。どうしてでしょうか?

最初のループで、SetPixel(63,0);を実行します。この時、ページアドレス0、カラムアドレス63に縦に並ぶ8ピクセルの内、一番上のピクセルのみが黒いビットマップを、書き込みます。(図58参照)

図58、最初のループで書き込むビットマップ
図58、最初のループで書き込むビットマップ

2回目のループでは、SetPixel(63,2);を実行します。この時、最初のループの時と同じページアドレス、カラムアドレスに、上から3番目のピクセルのみが黒いビットマップを書き込んでしまいます。(図59参照)

図59、2回目のループで書き込むビットマップ
図59、2回目のループで書き込むビットマップ

上から3番目のピクセルのみが黒いデータを書き込んでしまいます。本来なら一番上のピクセルも黒いビットマップを書き込むべきです。

最初のループで、図58の様に一番上のピクセルを黒くしてあったので、本来なら2回目のループでは、一番上と上から3番目のピクセルが黒いビットマップ(図60参照)を書き込むべきなのですが、図59では一番上のピクセルを白で上書きして、消してしまっています。

図60、2回目のループで本来書き込むべきビットマップ
図60、2回目のループで本来書き込むべきビットマップ

前のページで作ったSetPixel関数では、ビットマップを書き込むアドレスのVRAMに、既に何かが書き込まれている事を考慮せずに実装してしまいました。そのため、同一アドレスのVRAMに何かが書き込まれていると、それを白で上書きし、消してしまいます。

リスト18の様に点がまばらになったのは、SetPixel関数で同一アドレスに4回書き込みした時に、最初の3回の書き込み分は上書きされてしまって消えるために、起こった現象だったのです。

この問題を改善するには、VRAMにすでに書き込んであったビットマップと、新たに書き込みたいビットマップの論理和(OR)を取って、それを書き込む必要があります。(図61参照)

この様に、LSBから3番目のビットを1にして(上から3ピクセル目を黒にして)、他のビットの内容を保持する事を、「LSBから3ビット目を1にマスクする」といいます。

図61、論理和によりVRAMに書き込むビットマップを求める
↑ 画像をクリックすると拡大
図61、論理和によりVRAMに書き込むビットマップを求める
広告
4-1-8-3.VRAMを二重化してSetPixel関数の不具合を修正する

図61で示した処理で問題になるのが、VRAMに元から書き込んであったビットマップの取得法です。SG12232CSG12864ASLB-GBなどのパラレルインターフェース接続のLCDモジュールの場合、ArduinoがVRAMを読み取る事ができます。しかし、AQM1248Aの場合は、信号線が書き込み専用になっているため、ArduinoがVRAMを読み取る事はできません。

そこで、Arduino側にもVRAM(のコピー)を設け、LCDのVRAMに何か書き込む時には、必ずArduino側のVRAMにも、同じ内容を書き込むようにします。そうすれば、AQM1248AのVRAMの内容をマイコン内蔵のVRAMから読み出すことができます。これをVRAMの二重化と呼ぶ事にします。(図62参照)

図62、VRAMの二重化
↑ 画像をクリックすると拡大
図62、VRAMの二重化

Arduino内蔵RAM内に、VRAMのコピーを設けます。AQM1248AのVRAMに何か書き込むたびに、ArduinoのVRAMにも同一内容を書き込んで、2つのVRAMの内容を同一に維持します。VRAMの内容を読みたい場合は、Arduino側のVRAMから読み出します。

VRAMの二重化については、2ページ中ほどのコラムでも説明しましたが、ここではもっと具体的に、どの様にコーディングすれば実現できるのかを説明します。

最初に、VRAMを二重化したスケッチの全体を紹介してしまいましょう。リスト21が、VRAMを二重化したSetPixel関数のデモスケッチになります。また、このスケッチを実行した時の画面の写真を写真32に示します。

リスト21、VRAMを二重化したSetPixel関数のデモスケッチCOPY
#include <SPI.h>

#define DI_PIN  9
#define CS_PIN 10

#ifndef SPI_CLOCK_DIV32 // for arduino M0(怒) 
  #define SPI_CLOCK_DIV32 32
#endif

#define LCD_WIDTH  128
#define LCD_HEIGHT  48

uint8_t VRAM[LCD_WIDTH][LCD_HEIGHT/8];

void LcdCommand(uint8_t cmd)
{
  digitalWrite(CS_PIN,LOW);
  digitalWrite(DI_PIN,LOW);
  SPI.transfer(cmd);
  digitalWrite(CS_PIN,HIGH);
} // LcdCommand

void LcdData(uint8_t dat)
{
  digitalWrite(CS_PIN,LOW);
  digitalWrite(DI_PIN,HIGH);
  SPI.transfer(dat);
  digitalWrite(CS_PIN,HIGH);
} // LcdCommand

void InitializeLcd()
{
  LcdCommand(0xae);
  LcdCommand(0xa0);
  LcdCommand(0xc8);
  LcdCommand(0xa3);

  LcdCommand(0x2c);
  delay(2);
  LcdCommand(0x2e);
  delay(2);
  LcdCommand(0x2f);

  LcdCommand(0x23);
  LcdCommand(0x81);
  LcdCommand(0x1c);

  LcdCommand(0xa4);
  LcdCommand(0x40);
  LcdCommand(0xa6);
  LcdCommand(0xaf);
} // InitializeLcd

void ClearScreen()
{
  for(int page=0; page<6; page++) {
    LcdCommand(0xb0+page); // Page address set
    LcdCommand(0x10);      // Column address set upper bit
    LcdCommand(0x00);      // Column address set lower bit
    for(int x=0; x<128; x++) {
      LcdData(0);
      VRAM[x][page]=0;
    } // for x
  } // for page
}  // ClearScreen

boolean SetPixel(uint8_t x, uint8_t y)
{
  uint8_t page;
  uint8_t data;
  
  if(x>=LCD_WIDTH || y>=LCD_HEIGHT) {
    return false;
  } // if
  page=y >> 3; // calculate page address
  LcdCommand(0xb0+page);  // Page address set
  LcdCommand(0x10+(x >> 4));  // Column address set upper bit
  LcdCommand(0x00+(x & 0xf)); // Column address set lower bit
  data=VRAM[x][page] | (1 << (y & 0x7));
  LcdData(data);    // write bitmap data
  VRAM[x][page]=data;
  return true;
} // SetPixel

void setup() {
  pinMode(DI_PIN,OUTPUT);
  pinMode(CS_PIN,OUTPUT);
  digitalWrite(CS_PIN,HIGH);
  SPI.begin();
  SPI.setClockDivider(SPI_CLOCK_DIV32);
  SPI.setDataMode(SPI_MODE3);
  SPI.setBitOrder(MSBFIRST);
  InitializeLcd();
  ClearScreen();
  for(int y=0; y<LCD_HEIGHT; y+=2) {
    SetPixel(63,y);
  } // for y
}

void loop() {
}
写真32、、リスト22のスケッチを実行した場合の画面の写真
↑ 画像をクリックすると拡大
写真32、、リスト22のスケッチを実行した場合の画面の写真

写真31に見られた不具合は解消されて、点が1ピクセルおきに描画されています。

このスケッチでは、最初の方でVRAMという名前の2次元配列の変数を宣言しています。(リスト22参照) これが、Arduino内部に用意したVRAM(のコピー)です。128×6=768バイトあります。

リスト22、リスト21のスケッチのVRAM配列の宣言部分COPY
uint8_t VRAM[LCD_WIDTH][LCD_HEIGHT/8];

ClearScreen関数は次の様に書き換えられています。

リスト23、リスト21のClearScreen関数を抜き出した物COPY
void ClearScreen()
{
  for(int page=0; page<6; page++) {
    LcdCommand(0xb0+page); // Page address set
    LcdCommand(0x10);      // Column address set upper bit
    LcdCommand(0x00);      // Column address set lower bit
    for(int x=0; x<128; x++) {
      LcdData(0);
      VRAM[x][page]=0;
    } // for x
  } // for page
}  // ClearScreen

この中のVRAM[x][page]=0;の部分で、VRAM配列のすべての要素を0に初期化しています。Clear Screen関数を呼び出すと、AQM1248AのVRAMとArduinoのVRAMの双方が、0にクリアされるので、これら2つのVRAMの内容が一致します。

SetPixel関数は、次の様に書き換えられています。

リスト24、リスト21のSetPixel関数を抜き出した物COPY
boolean SetPixel(uint8_t x, uint8_t y)
{
  uint8_t page;
  uint8_t data;

  if(x>=LCD_WIDTH || y>=LCD_HEIGHT) {
    return false;
  } // if
  page=y >> 3; // calculate page address
  LcdCommand(0xb0+page);  // Page address set
  LcdCommand(0x10+(x >> 4));  // Column address set upper bit
  LcdCommand(0x00+(x & 0xf)); // Column address set lower bit
  data=VRAM[x][page] | (1 << (y & 0x7));
  LcdData(data);    // write bitmap data
  VRAM[x][page]=data;
  return true;
} // SetPixel

新しいSetPixel関数では、2つの符号なし8ビット変数、pageとdataが追加されています。

pageは、ビットマップを書き込むページアドレスを表します。y座標を8で割って小数点以下を切り捨てればページアドレスになりますから、page=y >> 3;でページアドレスが求まります。

また、VRAMに書き込む8ビットのビットマップは、data=VRAM[x][page] | (1 << (y & 0x7));で求めています。

VRAM[x][page]が点を描画する前のビットマップで、(1 << (y & 0x7))が、新たに書き込みたい点のビットマップです。元から描いてあったビットマップを消さないように、両者の値の論理和(OR)を取って、上書きするビットマップを求めています。

こうして求めたビットマップの値は、AQM1248Aに送信されるとともに、VRAM[x][page]=data;でArduino内のVRAMにも書き込まれます。この様にして、AQM1248AのVRAMとArduinoのVRAMは、常に同一内容に保たれます。

4-1-8-4.SetPixel関数を拡張して白い点も描画できるようにする

これまでに紹介してきたSetPixel関数は、指定した座標に黒い点を描画する事しかできませんでしたが、これを拡張し、白い点も描画できるようにしましょう。

例えば、VRAM上の同一アドレス内の8ピクセルの内、上から3番目のピクセルを白くしたい場合を考えます。

この場合は、図63に示す様に、上から3番目のピクセルのみ白く、他のピクセルが黒いビットマップを生成し、元々VRAMに書いてあったビットマップとの論理積(AND)を取って、VRAMに書き戻します。(図64参照)

この様に、LSBから3番目のビットを0にして(上から3ピクセル目を白にして)、他のビットの内容は保持する事を、「LSBから3ビット目を0にマスクする」といいます。

図63、上から3番目のピクセルのみが白いビットマップ
↑ 画像をクリックすると拡大
図63、上から3番目のピクセルのみが白いビットマップ

同一アドレスの8ピクセルの内、上から3番目のピクセルのみを白くしたい場合は、この図の様に上から3番目が白で、他のピクセルが黒のビットマップを、マスク用ビットマップとして生成します。

図64、論理積によりVRAMに書き込むビットマップを求める
↑ 画像をクリックすると拡大
図64、論理積によりVRAMに書き込むビットマップを求める

VRAMに書き込んであったビットマップと、図63のマスク用ビットマップの論理積(AND)を取ると、上から3番目のピクセルが白くなったビットマップが得られます。VRAMに書き込んであったビットマップの上から3番目のピクセルが元々白い場合は、マスク用ビットマップとの論理積をとっても、変化は生じません。

SetPixel関数に、黒い点も白い点も描画させられるように、3番目の引数を追加しましょう。新しいSetPixel関数の書式は次の様になります。

SetPixel(uint8_t x, uint8_t y, uint8_t color=1)

3番目の引数のcolorが0の場合は、白い点を描画する事にします。colorが0以外の場合は、黒い点を描画する事にします。colorの指定が省略された場合は、colorが1と指定されたものとみなし、黒い点を描画する事にします。

この様に仕様を決めると、SetPixel関数はリスト25の様に書き換えられます。

リスト25、白い点も描画できるようにしたSetPixel関数COPY
boolean SetPixel(uint8_t x, uint8_t y, uint8_t color=1)
{
  uint8_t page;
  uint8_t data;
  
  if(x>=LCD_WIDTH || y>=LCD_HEIGHT) {
    return false;
  } // if
  page=y >> 3; // calculate page address
  LcdCommand(0xb0+page);  // Page address set
  LcdCommand(0x10+(x >> 4));  // Column address set upper bit
  LcdCommand(0x00+(x & 0xf)); // Column address set lower bit
  if(color) { // set pixel
    data=VRAM[x][page] | (1 << (y & 0x7));
  } else { // clear pixel
    data=VRAM[x][page] & ~(1 << (y & 0x7));
  } // if
  LcdData(data);    // write bitmap data
  VRAM[x][page]=data;
  return true;
} // SetPixel

以上の様に、黒い点も白い点も描画できるSetPixel関数ができました。動作確認のスケッチは、自分で作ってみてください。

この連載では、点を描画する関数しか紹介しませんが、直線や長方形、円など、他の図形を描画したい場合は、その図形を構成するピクセルの座標を計算し、SetPixel関数にその座標を渡して呼び出せば、望みの図形が描画できます。

次のページでは、Arduino用のLCDライブラリである、MGLCDライブラリを用いて、"Hello, World!"を表示します。

目次へ  前のページへ (8) (9) (10) (11) (12) (13) (14) (15) (16) 次のページへ

このページで使われている用語の解説

関連ページ

関連製品

122X32モノクログラフィックLCDシールド 商品名 122X32モノクログラフィックLCDシールド
税抜き小売価格 3333円
販売店 スイッチサイエンス
サポートページ
GLCD学習シールドキット 商品名 GLCD学習シールドキット
税抜き小売価格 1410円
販売店 スイッチサイエンス
サポートページ
Arduino 電子工作
このサイトの記事が本になりました。
書名:Arduino 電子工作
ISBN:978-4-7775-1941-5
工学社の書籍の内容の紹介ページ
本のカバーの写真か書名をクリックすると、Amazonの書籍購入ページに移動します。