2014年1月4日土曜日

Raspberry Pi (9) LCD表示 1

LCD表示 1 (SPI接続でQVGA LCDパネル表示)


最初に

AitendoからSPI制御のQVGA LCDパネルM-TM022-SPIを入手したので接続してみました。
このLCDはSPIで接続できるので、簡単な配線だけで表示が可能です。
またドライバICはILI9340という物ですが、この辺のドライバICはどれも似たような コマンド体系を持っているようなので、他のドライバの資料を参考にする事が出来ます。
参考:LCD&タッチセンサ活用の素

実験を行う前にbcm2835ライブラリのインストールSPIが使える準備をしておいてください。

材料

  1. 2.2インチ液晶モジュール(240x320/SPI)[M-TM022-SPI]
  2. 抵抗(220Ω位。私は手元に470Ωしかなかったので、それを代用)
  3. 26pinの2列ソケット。出来れば10pinの1列ソケットも。

配線

  1. 配線は簡単です。下記のソースリストを参考にしてください。

プログラム

  1. SPIの初期化

    SPIの初期化はbcm2835ライブラリのサンプルプログラムをそのままお借りしました。
    SPI access
    SPIのモードは以下の様にセッティングしました。

    1. MSBFIRST
    2. MODE0
    3. Clock divide=8
    4. CS=Lo

  2. パネルの初期化

    パネルの初期化はAitendoのサンプルプログラムを流用しました。
    デモコード(8051)

  3. RGB565フォーマット変換

    このLCDの1画素あたりの色情報は16ビットであり、そのフォーマットはRGB565などと呼ばれています。
    RGB565フォーマットとは赤5ビット、緑6ビット、青5ビットで色を表す仕組みで、データの並びは"RRRRRGGG GGGBBBBB"の様になっており、 2バイトで色を表現できるのでプログラムが楽になります。
    但し2バイトだと色数が限られるので、現在のパソコンはは24ビット(8bit*3色)で表現する事が多い。両者を変換するルーチンを用意しておくと便利です。
    詳しくは、16 ビット RGB の操作 - MSDN - Microsoft を見てください。

  4. ILI9340のコマンド

    コマンド発行の仕方
    ILI9340はコマンドを発行し、直後にデータを送信する。と言う形式で操作します。
    D/CラインをLoにしてからデータ送信するとコマンドと解釈されます。
    D/CラインをHighにしてからデータ送信するとデータと解釈されます。
    コマンドは非常に沢山ありますが、基本的に下記の三つのコマンドだけで描画が可能です。

    1. xアドレス(座標)セットコマンド (0x2A)
      操作したいx座標をセットするには、コマンド0x2Aを発行し、その後にスタートアドレス2バイト、エンドアドレス2バイトを送信します。
      1画素だけ操作したい場合はスタートアドレス=エンドアドレスとします。
    2. yアドレス(座標)セットコマンド (0x2B)
      同様に操作したいy座標をセットするには、コマンド0x2Bを発行し、その後にスタートアドレス2バイト、エンドアドレス2バイトを送信します。
      1画素だけ操作したい場合はスタートアドレス=エンドアドレスとします。
    3. メモリ書き込みコマンド (0x2C)
      0x2Cコマンドを発行し、その後にデータ2バイト(RGB565)を送信すると上記のアドレス(1画素)にデータ(色)が書き込まれます。
      更に連続してデータを送信するとエンドアドレスまで順々にデータが書き込まれます。この機能を使えば、矩形の塗りつぶし(FILL)を高速に実行できます。
  5. 直線の描画

    ドライバ標準のコマンドだけでは、点か矩形しか描画が出来ません。任意の直線を引くには計算が必要です。
    どこかから2D描画ルーチンを拝借するのが一番手っ取り早いのですが、お正月で暇なので、復習を兼ねて直線描画ルーチンを書いてみます。 (これを車輪の再発明と言います)
     参考にしたのは、1982年発行 ポケットコンピューティング―情報化時代の知的ツール (アスキーブックス) [単行本]乾 謙一 (著)  名機PC-1500の活用本、何十年ぶりに開きました。

    ブレゼンハムのアルゴリズムが元になっているらしいですが、整数演算だけで直線描画が出来るので、非常に高速です。
    動きを簡単に説明すると、座標をx方向に移動しながら、傾きの余りが残っていたらY座標を1ドット上に上げる。と言う方法で直線を描きます。  傾きが45度以上になる時は、移動方向を入れ替えます。

完成したプログラム

// LCD test program 20130103 by ImageWriter
// ili9340.c
// This is M-TM022-SPI(controled by ILI9340) LCD-panel test program on Raspberry Pi.
// Used by bcm2835 library.(Mike McCauley)
// 
// After installing bcm2835, you can build this
// with something like:
// gcc -o spi ili9340.c -l bcm2835
// sudo ./ili9340
//
//  [Pin connection]
//  M-TM022-SPI     Rpi(pin)
//  ------------------------
//  MISO------------MISO(21)
//  LED---220ohm----3.3V( 1)
//  SCK-------------SCLK(23)
//  MOSI------------MOSI(19)
//  D/C-------------IO17(11)  LOW = 0 = COMMAND
//  RES-------------IO18(12)  LOW = 0 = RESET
//  CS--------------CS0 (24)  LOW = 0 = Chip Select
//  GND-------------0V  ( 6)
//  VCC-------------3.3V( 1)
//  ------------------------
//  
//  [SPI settings (spi_init())]
// ORDER MSB First
// MODE Mode0
// Divide 8
// CS  CS0
// CS_POL LOW
//
//

#include  < bcm2835.h >
#include  < stdio.h >
#include  < stdlib.h >

#define D_C  17  // GPIO17=D/C
#define RES_ 18  // GPIO18=RESET

int a,data;

// SPI interfase initialize
// MSB,mode0,clock=8,cs0=low
void spi_init(void){
 bcm2835_spi_begin();
 bcm2835_spi_setBitOrder(BCM2835_SPI_BIT_ORDER_MSBFIRST); // The default MSBFIRST
 bcm2835_spi_setDataMode(BCM2835_SPI_MODE0); // The default MODE0
 bcm2835_spi_setClockDivider(BCM2835_SPI_CLOCK_DIVIDER_8); // The default 65536
 bcm2835_spi_chipSelect(BCM2835_SPI_CS0); // The default CS0
 bcm2835_spi_setChipSelectPolarity(BCM2835_SPI_CS0, LOW); // the default LOW
 // Send a byte to the slave and simultaneously read a byte back from the slave
 // If you tie MISO to MOSI, you should read back what was sent
}

// SPI setup
void spi_reset(void){
 bcm2835_gpio_fsel(D_C,BCM2835_GPIO_FSEL_OUTP); // D/C
 bcm2835_gpio_fsel(RES_,BCM2835_GPIO_FSEL_OUTP); // Reset
 bcm2835_gpio_write(D_C, HIGH);   // D/C = H

 bcm2835_gpio_write(RES_, LOW);   // Reset
 bcm2835_delay(100);
 bcm2835_gpio_write(RES_, HIGH);   // Reset off
 bcm2835_delay(100); 
}

// SPI Write Command
// D/C=LOW then,write command(8bit)
void write_command(unsigned char c){
 int data;
 bcm2835_gpio_write(D_C,LOW);
 data = bcm2835_spi_transfer(c);
 bcm2835_gpio_write(D_C,HIGH);
// printf("COMMAND: %02X\n", c);
// printf("DATA: %02X\n", data);
}

// SPI Write Data
// D/C=HIGH then,write data(8bit)
void write_data(unsigned char c){
 int data;
 bcm2835_gpio_write(D_C,HIGH);
 data = bcm2835_spi_transfer(c);
}

// ILI9340 initialize
//  see M-TM022-SPI demo code(8051)
void lcd_initial(void){
  write_command(0xCB);  
  write_data(0x39); 
  write_data(0x2C); 
  write_data(0x00); 
  write_data(0x34); 
  write_data(0x02); 

  write_command(0xCF);  
  write_data(0x00); 
  write_data(0XC1); 
  write_data(0X30); 

  write_command(0xE8);  
  write_data(0x85); 
  write_data(0x00); 
  write_data(0x78); 

  write_command(0xEA);  
  write_data(0x00); 
  write_data(0x00); 

  write_command(0xED);  
  write_data(0x64); 
  write_data(0x03); 
  write_data(0X12); 
  write_data(0X81); 

  write_command(0xF7);  
  write_data(0x20); 

  write_command(0xC0);    //Power control 
  write_data(0x23);   //VRH[5:0] 

  write_command(0xC1);    //Power control 
  write_data(0x10);   //SAP[2:0];BT[3:0] 

  write_command(0xC5);    //VCM control 
  write_data(0x3e); //
  write_data(0x28); 

  write_command(0xC7);    //VCM control2 
  write_data(0x86);  //--

  write_command(0x36);    // Memory Access Control 

  //
  //0x48 0x68
  //0x28 0xE8

  write_data(0x48); //

  write_command(0x3A);    
  write_data(0x55); 

  write_command(0xB1);    
  write_data(0x00);  
  write_data(0x18); 

  write_command(0xB6);    // Display Function Control 
  write_data(0x08); 
  write_data(0x82);
  write_data(0x27);  

  write_command(0xF2);    // 3Gamma Function Disable 
  write_data(0x00); 

  write_command(0x26);    //Gamma curve selected 
  write_data(0x01); 

  write_command(0xE0);    //Set Gamma 
  write_data(0x0F); 
  write_data(0x31); 
  write_data(0x2B); 
  write_data(0x0C); 
  write_data(0x0E); 
  write_data(0x08); 
  write_data(0x4E); 
  write_data(0xF1); 
  write_data(0x37); 
  write_data(0x07); 
  write_data(0x10); 
  write_data(0x03); 
  write_data(0x0E); 
  write_data(0x09); 
  write_data(0x00); 

  write_command(0XE1);    //Set Gamma 
  write_data(0x00); 
  write_data(0x0E); 
  write_data(0x14); 
  write_data(0x03); 
  write_data(0x11); 
  write_data(0x07); 
  write_data(0x31); 
  write_data(0xC1); 
  write_data(0x48); 
  write_data(0x08); 
  write_data(0x0F); 
  write_data(0x0C); 
  write_data(0x31); 
  write_data(0x36); 
  write_data(0x0F); 

  write_command(0x11);    //Exit Sleep 
  bcm2835_delay(120); 
  
  write_command(0x29);    //Display on 
  write_command(0x2c); 
}

// RGB565 conversion
// RGB565 is R(5)+G(6)+B(5)=16bit color format.
// Bit image "RRRRRGGGGGGBBBBB"
// 
unsigned int rgb565_conv(unsigned int r,unsigned int g,unsigned int b){
 unsigned int RR,GG,BB;
 RR = (r * 31 / 255) << 11;
 GG = (g * 63 / 255) << 5;
 BB = (b * 31 / 255);
 return(RR | GG | BB);
}

// Write data word
//
unsigned int write_data_w(unsigned int w){
 unsigned char hi,lo;
 hi = (char) (w >> 8);
 lo = (char) (w & 0x00FF);
 write_data(hi);
 write_data(lo);
}

// Addres set (1point)
//
void addset(unsigned int x, unsigned int y){
 write_command(0x2A); // set column(x) address
 write_data_w(x);
 write_data_w(x);
 write_command(0x2B); // set Page(y) address
 write_data_w(y);
 write_data_w(y);
 write_command(0x2C); // Memory Write
}

// Addres set 2 (rectangle)
//
void addset2(unsigned int x1, unsigned int y1,
      unsigned int x2, unsigned int y2 ){
 write_command(0x2A); // set column(x) address
 write_data_w(x1); // set start x
 write_data_w(x2); // set end x
 write_command(0x2B); // set Page(y) address
 write_data_w(y1); // set start y
 write_data_w(y2); // set end y
 write_command(0x2C); // Memory Write
}

// Mem read
int mem_read(unsigned int x0, unsigned int y0){
 int data;
 write_command(0x2A); // set column(x) address
 write_data_w(x0);
 write_data_w(x0);
 write_command(0x2B); // set Page(y) address
 write_data_w(y0);
 write_data_w(y0);
 write_command(0x2E);
 bcm2835_gpio_write(D_C,HIGH);
 data = bcm2835_spi_transfer(0x00); // dummy data
 data = bcm2835_spi_transfer(0x00);
 printf("%02X",data);
 data = bcm2835_spi_transfer(0x00);
 printf("%02X:",data);
}

void dot_plot(unsigned int x,unsigned int y){
 addset(x,y);
 write_data_w(rgb565_conv(255,255,255)); //plot
}


// line plot
//
//                          /---(x1,y1) -
//                     /---/            |
//                /---/                 dy
//     (x0,y0)---/                      |
//        |<--------- dx --------->|    -
//        |<--- SS --->|

void line_plot(int x0,int y0,int x1,int y1){
 int  dx,dy,  // difference x,y
  SS,  // rest
  XX,YY,  // plot point
  temp,  
  ADDX,ADDY; // Addition X,Y

 if(x0>240 | y0>320 | x1>240 | y0>320) return;

 dx = x1-x0;  // dx
 dy = y1-y0;  // dy
 if(dx>0){  // plot direction x
  ADDX=  1;
 } else if(dx<0){
  ADDX= -1;
 } else {
  ADDX=  0;
 }
 if(dy>=0){  // plot direction y
  ADDY=  1;
 } else if(dy<0){
  ADDY= -1;
 } else {
  ADDY=  0;
 }
 if(abs(dx)>=abs(dy)){ // if angle < 45deg
  SS = abs(dx/2);
  XX = x0;
  YY = y0;

  while (XX != x1+ADDX){
   //printf("XX,YY:%d,%d\n",XX,YY);
   addset(XX,YY);
   write_data_w(rgb565_conv(255,255,255)); //plot
   SS = SS - abs(dy);
   if(SS > 0){
    XX = XX + ADDX;
    YY = YY;
   } else {
    XX = XX + ADDX;
    YY = YY + ADDY;
    SS = SS + abs(dx);
   }
  }
 } else {
  SS = abs(dx/2);
  XX = x0;
  YY = y0;

  while (YY != y1+ADDY){
   //printf("XX,YY:%d,%d\n",XX,YY);
   addset(XX,YY);
   write_data_w(rgb565_conv(255,255,255)); //plot
   SS = SS - abs(dx);
   if(SS > 0){
    XX = XX;
    YY = YY + ADDY;
   } else {
    XX = XX + ADDX;
    YY = YY + ADDY;
    SS = SS + abs(dy);
   }
  }

 }

}
 
int main(int argc, char **argv)
{
 if (!bcm2835_init())
  return 1;
 
 spi_init();
 spi_reset();
 lcd_initial(); 

 int i,j; 

 // color bar
 for(i=0;i<240;i++){   // x = 0 to 239
  for(j=0;j<320;j++){  // y = 0 to 319 
   //write_data(rand());
   //write_data(rand());
   if(j<106){
    addset(i,j);
    write_data_w(rgb565_conv(255,0,0)); //red
   } else if(j<212) {
    addset(i,j);
    write_data_w(rgb565_conv(0,255,0)); //green
   } else { 
    addset(i,j);
    write_data_w(rgb565_conv(0,0,255)); //blue
   }
  }
 }

 // rectangle fill
 addset2(200,200,200+30,200+30); // x1,y1,x2,y2
 for(i=0;i<30*30;i++){
  write_data_w(rgb565_conv(64,128,32)); // fill 
 }
 
 // line plot(radial)
 line_plot(100,100, 50, 80);
 line_plot(100,100, 50,120);
 line_plot(100,100,150, 80);
 line_plot(100,100,150,120);

 line_plot(100,100, 80, 50);
 line_plot(100,100,120, 50);
 line_plot(100,100, 80,150);
 line_plot(100,100,120,150);
 
 line_plot(100,100, 62, 62);
 line_plot(100,100, 62,138);
 line_plot(100,100,138, 62);
 line_plot(100,100,138,138);

 line_plot(100,100,100, 48);
 line_plot(100,100,100,152);
 line_plot(100,100,152,100);
 line_plot(100,100, 48,100);
 
 // line_plot(rectangle)
 line_plot(10,10,10,50);
 line_plot(10,50,50,50);
 line_plot(50,50,50,10);
 line_plot(50,10,10,10);

 bcm2835_delay(1000);

 // line_plot(random)
 int x0,y0,x1,y1;
 for(i=0;i<50;i++){
  x0=(char)rand();
  y0=((char)rand()*2);
  x1=(char)rand();
  y1=((char)rand()*2);
  line_plot(x0,y0,x1,y1);
//  printf("%d,%d,%d,%d\n",x0,y0,x1,y1);
 }

 //for(i=0;i<240;i++){
 // for(j=0;j<320;j++){
 //  //dot_plot(i,j);
 //  //printf("%d:%d:",i,j);
 //  mem_read(i,j);
 // }
 //}
 //mem_read(10,10);

 bcm2835_spi_end();
 bcm2835_close();
 return 0;
}

最初に3色のカラーバーを表示し、その後、放射状の線の描画、矩形のフィルを行った後、ランダムに直線を描きます。