2016年05月15日 | 公開。 |
前のページでは、水平方向に1つおきに点を並べて点線を引きましたが、今度は垂直方向に点線を引いてみましょう。
前のページのリスト19のループを次の様に書き変えれば縦の点線が引けるようになるはずです。
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の様な画面になります。
確かに、縦に点線が引けていますが、点と点の間隔が長いですね。どうしてでしょうか?
最初のループで、SetPixel(63,0);
を実行します。この時、ページアドレス0、カラムアドレス63に縦に並ぶ8ピクセルの内、一番上のピクセルのみが黒いビットマップを、書き込みます。(図58参照)
2回目のループでは、SetPixel(63,2);
を実行します。この時、最初のループの時と同じページアドレス、カラムアドレスに、上から3番目のピクセルのみが黒いビットマップを書き込んでしまいます。(図59参照)
上から3番目のピクセルのみが黒いデータを書き込んでしまいます。本来なら一番上のピクセルも黒いビットマップを書き込むべきです。
最初のループで、図58の様に一番上のピクセルを黒くしてあったので、本来なら2回目のループでは、一番上と上から3番目のピクセルが黒いビットマップ(図60参照)を書き込むべきなのですが、図59では一番上のピクセルを白で上書きして、消してしまっています。
前のページで作ったSetPixel関数では、ビットマップを書き込むアドレスのVRAMに、既に何かが書き込まれている事を考慮せずに実装してしまいました。そのため、同一アドレスのVRAMに何かが書き込まれていると、それを白で上書きし、消してしまいます。
リスト18の様に点がまばらになったのは、SetPixel関数で同一アドレスに4回書き込みした時に、最初の3回の書き込み分は上書きされてしまって消えるために、起こった現象だったのです。
この問題を改善するには、VRAMにすでに書き込んであったビットマップと、新たに書き込みたいビットマップの論理和(OR)を取って、それを書き込む必要があります。(図61参照)
この様に、LSBから3番目のビットを1にして(上から3ピクセル目を黒にして)、他のビットの内容を保持する事を、「LSBから3ビット目を1にマスクする」といいます。
図61で示した処理で問題になるのが、VRAMに元から書き込んであったビットマップの取得法です。SG12232CやSG12864ASLB-GBなどのパラレルインターフェース接続のLCDモジュールの場合、ArduinoがVRAMを読み取る事ができます。しかし、AQM1248Aの場合は、信号線が書き込み専用になっているため、ArduinoがVRAMを読み取る事はできません。
そこで、Arduino側にもVRAM(のコピー)を設け、LCDのVRAMに何か書き込む時には、必ずArduino側のVRAMにも、同じ内容を書き込むようにします。そうすれば、AQM1248AのVRAMの内容をマイコン内蔵のVRAMから読み出すことができます。これをVRAMの二重化と呼ぶ事にします。(図62参照)
Arduino内蔵RAM内に、VRAMのコピーを設けます。AQM1248AのVRAMに何か書き込むたびに、ArduinoのVRAMにも同一内容を書き込んで、2つのVRAMの内容を同一に維持します。VRAMの内容を読みたい場合は、Arduino側のVRAMから読み出します。
VRAMの二重化については、2ページ中ほどのコラムでも説明しましたが、ここではもっと具体的に、どの様にコーディングすれば実現できるのかを説明します。
最初に、VRAMを二重化したスケッチの全体を紹介してしまいましょう。リスト21が、VRAMを二重化したSetPixel関数のデモスケッチになります。また、このスケッチを実行した時の画面の写真を写真32に示します。
#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() {
}
このスケッチでは、最初の方でVRAMという名前の2次元配列の変数を宣言しています。(リスト22参照) これが、Arduino内部に用意したVRAM(のコピー)です。128×6=768バイトあります。
uint8_t VRAM[LCD_WIDTH][LCD_HEIGHT/8];
ClearScreen関数は次の様に書き換えられています。
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関数は、次の様に書き換えられています。
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は、常に同一内容に保たれます。
これまでに紹介してきたSetPixel関数は、指定した座標に黒い点を描画する事しかできませんでしたが、これを拡張し、白い点も描画できるようにしましょう。
例えば、VRAM上の同一アドレス内の8ピクセルの内、上から3番目のピクセルを白くしたい場合を考えます。
この場合は、図63に示す様に、上から3番目のピクセルのみ白く、他のピクセルが黒いビットマップを生成し、元々VRAMに書いてあったビットマップとの論理積(AND)を取って、VRAMに書き戻します。(図64参照)
この様に、LSBから3番目のビットを0にして(上から3ピクセル目を白にして)、他のビットの内容は保持する事を、「LSBから3ビット目を0にマスクする」といいます。
SetPixel関数に、黒い点も白い点も描画させられるように、3番目の引数を追加しましょう。新しいSetPixel関数の書式は次の様になります。
3番目の引数のcolorが0の場合は、白い点を描画する事にします。colorが0以外の場合は、黒い点を描画する事にします。colorの指定が省略された場合は、colorが1と指定されたものとみなし、黒い点を描画する事にします。
この様に仕様を決めると、SetPixel関数はリスト25の様に書き換えられます。
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!"を表示します。
商品名 | 122X32モノクログラフィックLCDシールド | |
税抜き小売価格 | 3333円 | |
販売店 | スイッチサイエンス | |
サポートページ | 122X32モノクログラフィックLCDシールドサポートページ |
商品名 | GLCD学習シールドキット | |
税抜き小売価格 | 1410円 | |
販売店 | スイッチサイエンス | |
サポートページ | GLCD学習シールドキットサポートページ |