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

このページをスマートフォンなどでご覧になる場合は、画面を横長にする方が読みやすくなる事があります。
目次へ  前のページへ (7) (8) (9) (10) (11) (12) (13) (14) (15) 次のページへ
2016年05月05日 公開。

4-1-8.点を描画するSetPixel関数を作る

前のページではビットマップの描画を行いましたが、今度は、x座標とy座標を指定すれば、その座標に点を描画するSetPixel関数を作ってみましょう。

4-1-8-1.とにかく動作するSetPixel関数を作る(既知の問題点あり)

AQM1248Aにはビットマップを表示する機能しかありませんので、点を描画するには、描画したい点のビットマップを関数内で動的に生成する事になります。

まず最初に、SetPixel関数で用いる座標系について説明します。図57の様に、画面の左上を原点にして、x軸は右に行くほど座標が大きく、y軸は下に行くほど座標が大きくなるように、座標系を定めます。

図57、AQM1248Aの座標系
↑ 画像をクリックすると拡大
図57、AQM1248Aの座標系

画面の左上は原点ですので、座標が(0,0)となります。また、AQM1248Aは横128ピクセル、縦48ピクセルのLCDですので、画面の右下の座標は(127,47)となります。

以後、AQM1248Aの座標系は、図57の座標系を使う事にします。

x座標をx、y座標をyとし、ビットマップを書き込む必要のあるVRAM上のバイトの、ページアドレスとカラムアドレスを求めます。

VRAMの構成を示した図43を再掲します。

図43(再掲)、VRAMの構成
↑ 画像をクリックすると拡大
図43(再掲)、VRAMの構成
広告

まずページアドレスについてですが、1ページが縦方向に8ピクセルの幅を持っていることを考えると、y座標を8で割って、余りを切り捨てた値がページアドレスになる事が分ります。このページアドレスをPage address setコマンドで指定する事になります。

カラムアドレスについては、x座標に一致します。ただし、AQM1248Aにカラムアドレスを指定する時は、上位4ビット(Column address set uppder bitコマンド)と下位4ビット(Column address set lower bitコマンド)に分けて指定する必要があります。

以上より、ページアドレスとカラムアドレスの指定のコードは次の様になります。

リスト14、SetPixel関数のページアドレスとカラムアドレスの指定部分COPY
  LcdCommand(0xb0+(y >> 3));  // Page address set
  LcdCommand(0x10+(x >> 4));  // Column address set upper bit
  LcdCommand(0x00+(x & 0xf)); // Column address set lower bit

1行目にy >> 3とありますが、この数式でy座標を8で割って、余りを切り捨てた数値(ページアドレス)を求めています。これをy/8としても、同じ実行結果になりますが、シフト演算子を使ったy >> 3の方が実行が速くなります。(ただし、コンパイラの最適化で同じコンパイル結果になる可能性もある)

2行目のx >> 4で、カラムアドレスの上位4ビットを計算しています。x / 16でも同じ実行結果になりますが、シフト演算子を使ったx >> 4の方が、確実に実行の速いコードを生成します。

3行目のx & 0xfで、カラムアドレスの下位4ビットを計算しています。剰余を求める%演算子を使い、x % 16としても同じ実行結果になりますが、論理演算子を使うx & 0xfの方が、確実に実行の速いコードを生成します。

ページアドレスとカラムアドレスの指定ができたので、次は、そのアドレスに書き込む1バイトのビットマップを生成します。

y座標を8で割った余りが、1バイトの中で、1にするべきビット位置を表します。(表18参照)

表18、y座標を8で割った余りと、書き込むべきビットマップの関係
y座標を8で割った余り 書き込むべきビットマップ ビットマップを表す2進数
0 1番上のピクセルが黒いビットマップ 00000001B
1 上から2番目のピクセルが黒いビットマップ 00000010B
2 上から3番目のピクセルが黒いビットマップ 00000100B
3 上から4番目のピクセルが黒いビットマップ 00001000B
4 上から5番目のピクセルが黒いビットマップ 00010000B
5 上から6番目のピクセルが黒いビットマップ 00100000B
6 上から7番目のピクセルが黒いビットマップ 01000000B
7 上から8番目のピクセルが黒いビットマップ 10000000B

この表に示す様に、y座標を8で割った余りが大きいほど、黒くするべきピクセルが下になります。

図43に示す様に、同一バイト内では下のピクセルほど上位ビットに対応しますから、y座標を8で割った余りが大きいほど、上位のビットが1になります。

この事から、書き込むべきビットマップは1 << (y & 0x7)で与えられる事が分ります。

ここでy & 0x7は、y座標を8で割った余りを表しています。y % 8とするよりも、高速に実行されるコードに確実にコンパイルされるので、こういう表記になっています。

よって、ビットマップの書き込みは、次のコードで行える事が分ります。

リスト15、点のビットマップを書き込むコードCOPY
  LcdData(1 << (y & 0x7));    // write bitmap data

x座標が128未満になっているかのチェックとy座標が48未満になっているかのチェックのコードを追加して、SetPixel関数を完成すると、リスト16の様になります。(ただしこのコードには、後に述べるように問題点があります)

リスト16、SetPixel関数(問題のあるバージョン)COPY
boolean SetPixel(uint8_t x, uint8_t y)
{
  if(x>=LCD_WIDTH || y>=LCD_HEIGHT) {
    return false;
  } // if
  LcdCommand(0xb0+(y >> 3));  // Page address set
  LcdCommand(0x10+(x >> 4));  // Column address set upper bit
  LcdCommand(0x00+(x & 0xf)); // Column address set lower bit
  LcdData(1 << (y & 0x7));    // write bitmap data
  return true;
} // SetPixel

このリストにはLCD_WIDTHとLCD_HEIGHTという2つの定数が出てきますが、これはそれぞれLCDの水平方向の解像度(128)と垂直方向の解像度(48)を意味します。SetPixel関数よりも前に、次の様に宣言しておきます。

リスト17、LCD_WIDTHとLCD_HEIGHTの宣言COPY
#define LCD_WIDTH  128
#define LCD_HEIGHT  48

なお、リスト16にはxやyが負の値になっているかどうかのチェックが含まれていませんが、これは、xもyも符号なし整数だからです。符号付き整数を使う場合は、負になっていないかどうかもチェックすべきでしょう。

SetPixel関数はboolean型(trueまたはfalseの値を取る真理値型)の返り値を返す様にしています。x座標またはy座標が画面外を指していると、SetPixel関数は点の描画を行わずにfalseを返します。座標が画面内を指していれば、点を描画してtrueを返します。

次に、作ったSetPixel関数を呼び出して、実際に点を描画する試験をしてみます。

AQM1248Aは小型で高解像度のLCDモジュールですから、1点のみを描画したのでは、描画した点が目立ちにくくなってしまいます。そこで、水平方向に1ピクセルおきに点を書いていって、点線を引くことにしました。スケッチをリスト18に示します。

リスト18、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

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);
    } // for x
  } // for page
}  // ClearScreen

boolean SetPixel(uint8_t x, uint8_t y)
{
  if(x>=LCD_WIDTH || y>=LCD_HEIGHT) {
    return false;
  } // if
  LcdCommand(0xb0+(y >> 3));  // Page address set
  LcdCommand(0x10+(x >> 4));  // Column address set upper bit
  LcdCommand(0x00+(x & 0xf)); // Column address set lower bit
  LcdData(1 << (y & 0x7));    // write bitmap 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 x=0; x<LCD_WIDTH; x+=2) {
    SetPixel(x,23);
  } // for x
}

void loop() {
}

このスケッチはハードウェアSPIでAQM1248Aと通信する前提で作ってあります。また、Arduino IDE 1.0.X、1.6.X、および1.7.Xのいずれでも動作します。

点線を引く処理を行なっているループだけを抜き出すとリスト19の様になります。

リスト19、点線を引く処理を行っているループCOPY
  for(int x=0; x<LCD_WIDTH; x+=2) {
    SetPixel(x,23);
  } // for x

ループ変数のxは、点のx座標を表しています。x=0,2,4,6,…,124,126と、0から126まで2ステップでカウントアップしていきます。y座標は23で固定にしています。

リスト18の実行結果を写真30に示します。この様に、うまく点線が引けている事が分ります。

写真30、リスト18の実行結果
↑ 画像をクリックすると拡大
写真30、リスト18の実行結果

このページでは、点を描画するSetPixel関数を作りました。ただ、既に述べたように、このSetPixel関数には問題があります。次のページでは、問題の具体的な内容と、その回避方法について説明すします。

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

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

関連ページ

関連製品

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