2016年05月05日 | 公開。 |
前のページではビットマップの描画を行いましたが、今度は、x座標とy座標を指定すれば、その座標に点を描画するSetPixel関数を作ってみましょう。
AQM1248Aにはビットマップを表示する機能しかありませんので、点を描画するには、描画したい点のビットマップを関数内で動的に生成する事になります。
まず最初に、SetPixel関数で用いる座標系について説明します。図57の様に、画面の左上を原点にして、x軸は右に行くほど座標が大きく、y軸は下に行くほど座標が大きくなるように、座標系を定めます。
画面の左上は原点ですので、座標が(0,0)となります。また、AQM1248Aは横128ピクセル、縦48ピクセルのLCDですので、画面の右下の座標は(127,47)となります。
以後、AQM1248Aの座標系は、図57の座標系を使う事にします。
x座標をx、y座標をyとし、ビットマップを書き込む必要のあるVRAM上のバイトの、ページアドレスとカラムアドレスを求めます。
VRAMの構成を示した図43を再掲します。
まずページアドレスについてですが、1ページが縦方向に8ピクセルの幅を持っていることを考えると、y座標を8で割って、余りを切り捨てた値がページアドレスになる事が分ります。このページアドレスをPage address setコマンドで指定する事になります。
カラムアドレスについては、x座標に一致します。ただし、AQM1248Aにカラムアドレスを指定する時は、上位4ビット(Column address set uppder bitコマンド)と下位4ビット(Column address set lower bitコマンド)に分けて指定する必要があります。
以上より、ページアドレスとカラムアドレスの指定のコードは次の様になります。
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参照)
y座標を8で割った余り | 書き込むべきビットマップ | ビットマップを表す2進数 |
---|---|---|
0 | 00000001B | |
1 | 00000010B | |
2 | 00000100B | |
3 | 00001000B | |
4 | 00010000B | |
5 | 00100000B | |
6 | 01000000B | |
7 | 10000000B |
この表に示す様に、y座標を8で割った余りが大きいほど、黒くするべきピクセルが下になります。
図43に示す様に、同一バイト内では下のピクセルほど上位ビットに対応しますから、y座標を8で割った余りが大きいほど、上位のビットが1になります。
この事から、書き込むべきビットマップは1 << (y & 0x7)
で与えられる事が分ります。
ここでy & 0x7
は、y座標を8で割った余りを表しています。y % 8
とするよりも、高速に実行されるコードに確実にコンパイルされるので、こういう表記になっています。
よって、ビットマップの書き込みは、次のコードで行える事が分ります。
LcdData(1 << (y & 0x7)); // write bitmap data
x座標が128未満になっているかのチェックとy座標が48未満になっているかのチェックのコードを追加して、SetPixel関数を完成すると、リスト16の様になります。(ただしこのコードには、後に述べるように問題点があります)
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関数よりも前に、次の様に宣言しておきます。
#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に示します。
#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の様になります。
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に示します。この様に、うまく点線が引けている事が分ります。
このページでは、点を描画するSetPixel関数を作りました。ただ、既に述べたように、このSetPixel関数には問題があります。次のページでは、問題の具体的な内容と、その回避方法について説明すします。
商品名 | 122X32モノクログラフィックLCDシールド | |
税抜き小売価格 | 3333円 | |
販売店 | スイッチサイエンス | |
サポートページ | 122X32モノクログラフィックLCDシールドサポートページ |
商品名 | GLCD学習シールドキット | |
税抜き小売価格 | 1410円 | |
販売店 | スイッチサイエンス | |
サポートページ | GLCD学習シールドキットサポートページ |