2018年1月31日 星期三

OpenQSignage開源迷你電子看板(Arduino LCD動畫胸牌)#2_Arduino程式說明

2. Arduino程式


當要下載程式到Arduino前請記得先關閉本專案PC端程式,以免佔用通訊埠(COM)導致程式無法下載。當按下板上重置鍵後,首先會看到LCD螢幕出現十六道彩色條紋,等待一秒後清除畫面(黑屏),接著開始等待PC端下載排程及影像資料。待完成下載後,會進入自動播放模式,即依排程設定之時間、顯示模式來顯示圖框及橫幅(文字)。
當SW1按住超過三秒時,會交換自動播放或者是手動切換模式。當按鍵時間小於三秒時會視為手動切換模式。在手動切換模式時,不依排程設定,直接秀出圖框影像及橫幅文字。再按一下則切換到下一組,全部輪完就重頭開始。

不管是自動或手動切換模式,當UART收到PC端傳來命令時,會馬上停下排程,改執行接收影像任務。

目前本專案僅支援三組圖框影像及三組橫幅文字,由於PSRAM空間還很大,未來可考慮改成支援更多影像版本。目前PSRAM的空間使用如圖七所示。

圖七、PSRAM空間使用圖

完整程式請參考下方說明,或直接到 https://github.com/OmniXRI/OpenQSignage 下載。

#include <SPI.h> // 引入驅動SPI函式庫,應用於SPI PSRAM

// LCD 顯示模式
#define LCD_DISP_FIXED  0 // 固定顯示
#define LCD_DISP_BLINK  1 // 交替閃爍
#define LCD_DISP_NONE   2 // 不顯示

#define FRAME_TOTAL_AMOUNT  6  // 圖像(含圖框及橫幅)
#define FRAME_HEADER_SIZE   10 // 排程資料位元數

// LCD常用色定義 (16bit, RGB565, 65536色)
#define VGA_BLACK    0x0000  // 黑色
#define VGA_WHITE    0xFFFF  // 白色
#define VGA_RED      0xF800  // 紅色
#define VGA_GREEN    0x0400  // 綠色
#define VGA_BLUE     0x001F  // 藍色
#define VGA_SILVER   0xC618  // 銀(亮灰)色
#define VGA_GRAY     0x8410  // 灰色
#define VGA_MAROON   0x8000  // 栗(暗紅)色
#define VGA_YELLOW   0xFFE0  // 黃色
#define VGA_OLIVE    0x8400  // 橄欖(暗綠)色
#define VGA_LIME     0x07E0  // 青檸(亮綠)色
#define VGA_AQUA     0x07FF  // 水藍(亮藍)色
#define VGA_TEAL     0x0410  // 水鳥(暗監綠)色
#define VGA_NAVY     0x0010  // 海軍(暗藍)色
#define VGA_FUCHSIA  0xF81F  // 紫紅色
#define VGA_PURPLE   0x8010  // 紫色

// 主要I/O設定
int PIN_SW1 = 2;       // 按鍵(SW)1
int PIN_LED1 = 3;      // 指示燈(LED)1

// 2.2吋 LCD 模塊I/O設定(176*220像素,ILI9225驅動晶片,8 Bit匯流排)
int PIN_LCD_CS = 10;   // LCD晶片選擇線(和SPI PSRAM晶片選擇線PIN_PSRAM_CE共用)
int PIN_LCD_RS = 9;    // LCD命令暫存器選擇線
int PIN_LCD_WR = 8;    // LCD寫入線
int PIN_LCD_DB0 = 14;  // LCD資料匯流排位元0, Arduino PC0/A0
int PIN_LCD_DB1 = 15;  // LCD資料匯流排位元1, Arduino PC1/A1
int PIN_LCD_DB2 = 16;  // LCD資料匯流排位元2, Arduino PC2/A2
int PIN_LCD_DB3 = 17;  // LCD資料匯流排位元3, Arduino PC3/A3
int PIN_LCD_DB4 = 4;   // LCD資料匯流排位元4, Arduino PD4
int PIN_LCD_DB5 = 5;   // LCD資料匯流排位元5, Arduino PD5
int PIN_LCD_DB6 = 6;   // LCD資料匯流排位元6, Arduino PD6
int PIN_LCD_DB7 = 7;   // LCD資料匯流排位元7, Arduino PD7

// 來揚科技(Lyontek) SPI PSRAM LY68L6400 I/O設定
int PIN_PSRAM_CE = 10;  // 晶片選擇線, Arduino PB2(和LCD晶片選擇線PIN_LCD_CS共用)
int PIN_PSRAM_SI = 11;  // 串列資料輸入線, Arduino PB3
int PIN_PSRAM_SO = 12;  // 串列資料輸出線, Arduino PB4
int PIN_PSRAM_SCK = 13; // 時脈線, Arduino PB5

int SW1State = 0;       // 按鍵1狀態
int color_table[16] = {VGA_BLACK, VGA_WHITE, VGA_RED, VGA_GREEN,
                       VGA_BLUE,  VGA_SILVER, VGA_GRAY, VGA_MAROON,
                       VGA_YELLOW, VGA_OLIVE, VGA_LIME, VGA_AQUA,
                       VGA_TEAL, VGA_NAVY, VGA_FUCHSIA, VGA_PURPLE}; // 色彩索引表
int auto_loop = 0;       // 0為手動操作,非0為自動依排程播放
int frame_index = 0;     // 目前播放圖框(含橫幅)編號
int frame_updated = 0;   // 0為LCD尚未更新顯示內容,1為已更新
int download_finish = 0; // 下載顯示及排程資料完成
unsigned long startTime; // 計時器起始時間
unsigned long duration;  // 計時器經過時間

// 顯示排程資料格式
// [0] 圖框寬度高位元組(d15 ~ d8)
// [1] 圖框寬度低位元組(d0  ~ d7)
// [2] 圖框高度高位元組(d15 ~ d8)
// [3] 圖框高度低位元組(d0  ~ d7)
// [4] LCD顯示起始位置X高位元組(d15 ~ d8)
// [5] LCD顯示起始位置X低位元組(d0  ~ d7)
// [6] LCD顯示起始位置Y高位元組(d15 ~ d8)
// [7] LCD顯示起始位置Y低位元組(d0  ~ d7)
// [8] LCD顯示模式,0:固定不變,1:交替閃爍,2:不顯示
// [9] LCD顯示計時器, 0 ~ 255 表示 0.0 ~ 25.5 秒
char frame_header[10];  // 顯  

// Arduino初始化程式
void setup() { 
  // 初始化主要I/O
  pinMode(PIN_LED1, OUTPUT);  // 設定LED腳位為輸出
  pinMode(PIN_SW1, INPUT);    // 設定SW腳位為輸入
 
  // 初始化UART通訊埠(COM Port)
  Serial.begin(57600);        // 設定通訊鮑率為57,600bps
 
  // 初始化SPI串列埠為控制來揚科技(Lyontek) SPI PSRAM LY68L6400
  //  pinMode(PIN_PSRAM_CE, OUTPUT); // 和LCD晶片選擇線共用,所以不用重覆設定
  SPI.begin();                          // 啟動SPI
  SPI.setBitOrder(MSBFIRST);            // 設定SPI為高位元先送模式
  SPI.setDataMode(SPI_MODE0);           // 設定SPI為模式0(CPOL,CPHA皆為零,即資料SI/SO備妥後CLK再以正緣觸發)
  SPI.setClockDivider(SPI_CLOCK_DIV2);  // 為外頻再除以2,3.3V Arduino外頻為8MHz,所以SPI時脈為4MHz
 
  // 初始化2.2吋LCD模塊相關I/O
  pinMode(PIN_LCD_CS, OUTPUT);   // 設定晶片選擇線為輸出,和SPI PSRAM晶片選擇線共用
  pinMode(PIN_LCD_RS, OUTPUT);   // 設定暫存器選擇線為輸出
  pinMode(PIN_LCD_WR, OUTPUT);   // 設定寫入線為輸出
  pinMode(PIN_LCD_DB0, OUTPUT);  // 設定資料匯流排DB0為輸出
  pinMode(PIN_LCD_DB1, OUTPUT);  // 設定資料匯流排DB1為輸出 
  pinMode(PIN_LCD_DB2, OUTPUT);  // 設定資料匯流排DB2為輸出 
  pinMode(PIN_LCD_DB3, OUTPUT);  // 設定資料匯流排DB3為輸出 
  pinMode(PIN_LCD_DB4, OUTPUT);  // 設定資料匯流排DB4為輸出 
  pinMode(PIN_LCD_DB5, OUTPUT);  // 設定資料匯流排DB5為輸出 
  pinMode(PIN_LCD_DB6, OUTPUT);  // 設定資料匯流排DB6為輸出 
  pinMode(PIN_LCD_DB7, OUTPUT);  // 設定資料匯流排DB7為輸出 
  ILI9225_init();                // LCD工作暫存器初始化
  ILI9225_LCD_Test();            // LCD顯示測試
}

// Arduino無限循環程式段
void loop() {
  if (digitalRead(PIN_SW1) == HIGH) {    // 當SW1被按下時
    startTime = millis();                // 開始啟動計時器(ms)
   
    while(digitalRead(PIN_SW1) == HIGH); // 等待按鍵放開

    duration = millis() - startTime;     // 計算按鍵按下時間長度
   
    if(duration > 3000){                 // 若按超過3秒
      if(auto_loop == 1)                 // 若目前是自動播放模式
        auto_loop = 0;                   // 則切換到手動切換模式
      else
        auto_loop = 1;                   // 若否則切換到自動模式
    } 
    else{                                // 若按鍵按下不到3秒
      auto_loop = 0;                     // 則切換到手動切換模式
      frame_updated = 0;                 // 畫面設為未更新
    }   
  }   

  // 若顯示資料已下載完成且為手動切換模式且畫面尚未更新
  if (download_finish != 0 && auto_loop == 0 && frame_updated == 0) {
     ShowFrame(frame_index, 0);          // 正常顯示圖框內容到LCD(偶數為圖框)
     frame_index++;                      // 目前圖框編號加一
     ShowFrame(frame_index, 0);          // 正常顯示橫幅內容到LCD(奇時為橫幅)
     frame_updated = 1;                  // 設定LCD顯示已更新
     frame_index++;                      // 圖框(橫幅)編號加一
   
     if(frame_index >= 6)                // 圖框(橫幅)編號大於等於6
       frame_index = 0;                  // 圖框(橫幅)編號歸零
  }
  else if (download_finish != 0 && auto_loop == 1) { // 若顯示資料已下載完成且為自動播放模式
     ShowFrame(frame_index, 0);          // 正常顯示圖框內容到LCD(偶數為圖框)
     frame_index++;                      // 圖框編號加一
     GetFrameHeader(frame_index);        // 取得圖框(橫幅)顯示排程資料
     frame_updated = 0;                  // 設定LCD顯示未更新
     startTime = millis();               // 啟動計時器
    
     while((millis() - startTime) < (frame_header[9]*100)){ // 若計時器未達排程設定時間
       switch(frame_header[8])               // 依顯示模式執行顯示內容
       {
        case LCD_DISP_FIXED:                 // 固定顯示模式
               if(frame_updated == 0)        // 若LCD尚未更新
                 ShowFrame(frame_index, 0);  // 則正常顯示目前圖框編號內容
                 
               frame_updated = 1;            // 設定為LCD已更新內容
               break;
        case LCD_DISP_BLINK:                 // 交替顯示模式
               ShowFrame(frame_index, 0);    // 正常顯示目前圖框(橫幅)編號內容
               delay(500);                   // 延時500ms
               ShowFrame(frame_index, 1);    // 反相顯示目前圖框(橫幅)編號內容
               delay(500);                   // 延時500ms
               break;
        case LCD_DISP_NONE:                  // 不顯式模式(直接跳過)
        default:
             break;
       }
     }
    
     frame_index++;                      // 圖框(橫幅)編號加一
   
     if(frame_index >= 6)                // 圖框(橫幅)編號大於等於6
       frame_index = 0;                  // 圖框(橫幅)編號歸零
  }
 
  int rx_data;                      // 儲存接收到的位元組
 
  if (Serial.available() > 0) {     // 假若UART未接收到任何資料則略過
    download_finish = 0;            // 清除已下載完成旗標
   
    rx_data = Serial.read();        // 取得動作命令
   
    if(rx_data == 0xA5){            // 若命令為0xA5
      digitalWrite(PIN_LED1, HIGH); // 則點亮LED1
      delay(100);                   // 延時100ms
    } 
    else if(rx_data == 0xB4){       // 若命令為0xB4
      ILI9225_Clr_Screen();         // 則清除LCD顯示內容(屏幕全黑)
      digitalWrite(PIN_LED1, LOW);  // 並熄滅LED1
    } 
    else if(rx_data == 0xC3){       // 若命令為0xC3則開始接收PC透過UART送過來的資料       
      for(int f_idx=0; f_idx<FRAME_TOTAL_AMOUNT; f_idx++){ // 預計接收六組資料(圖框加橫幅各三組)       
        digitalWrite(PIN_LCD_CS, LOW);  // SPI PSRAM 晶片選擇線設為低電位
        // 設定 SPI PSRAM寫入起始位置
        SPI.transfer(0x02);             // 寫入PSRAM寫入命令0x02
        SPI.transfer((f_idx+1)*2);      // 寫入PSRAM位址 bit 23 ~ 16,每個圖框(橫幅)儲存間隔20000h
        SPI.transfer(0);                // 寫入PSRAM位址 bit 15 ~ 8
        SPI.transfer(0);                // 寫入PSRAM位址 bit 7 ~ 0          
    
        // 取得圖框排程資料
        for(int i=0; i<FRAME_HEADER_SIZE; i++){  // 預計接收10位元組資料
          while(!Serial.available());            // 等待UART已備妥接收到資料信號
         
          rx_data = Serial.read();               // 接收一個位元組資料
          frame_header[i] = (char)rx_data;       // 將資料讀入frame_header中指定位置
          SPI.transfer(frame_header[i]);         // 將讀入資料寫入PSRAM(位址自動加一)
        }
       
        Serial.write(0x5A);                      // 透過UART回傳0x5A回應已接收到資料
       
        // 依接收到的資料,設定顯示圖框(橫幅)的寬(fw)、高(fh)及起始位置(sx,sy)       
        int fw = (frame_header[0] * 256) + (unsigned char)frame_header[1];
        int fh = (frame_header[2] * 256) + (unsigned char)frame_header[3];
        //int sx = (frame_header[4] * 256) + frame_header[5]);
        //int sy = (frame_header[6] * 256) + frame_header[7];    

        // PC指定的LCD顯示位置(SXdisp,SYdisp)與LCD GRAM和起始位置(SXgram,SYgram)的換算公式
        // SXgram = 176 - SYdisp - 1; LCD width = 176
        // SYgram = SXdisp;       
        // SXlcd,  SYlcd  (Left,Top)-(Right,Bottom), Frame (0  ,0)-(219,175)
        // SXgram, SYgram (Left,Top)-(Right,Bottom), GRAM  (175,0)-(0,  219)
        // x1 <= sx <= x2, y1 <= sy <= y2, if x1>x2 or y1>y2 must be swap
        // ILI9225_SetSY(x1,y1,x2,y2,sx,sy);  // 設定LCD GRAM讀寫區域大小

        if(f_idx % 2 == 0)                       // 若圖框編號為偶數
          ILI9225_SetXY(0, 0, 175, 219, 175, 0); // 則設定顯示區域為(0, 0, 175, 219, 175, 0)
        else                                     // 若為奇數則為橫幅 
          ILI9225_SetXY(0, 0, 35, 219, 35, 0);   // 則設定顯示區域為(0, 0, 35, 219, 35, 0)
         
        digitalWrite(PIN_LCD_CS, LOW);  // SPI PSRAM 晶片選擇線設為低電位
        // 設定 SPI PSRAM寫入起始位置
        SPI.transfer(0x02);             // 寫入PSRAM寫入命令0x02
        SPI.transfer((f_idx+1)*2);      // 寫入PSRAM位址 bit 23 ~ 16,每個圖框(橫幅)儲存間隔20000h
        SPI.transfer(0);                // 寫入PSRAM位址 bit 15 ~ 8
        SPI.transfer(10);               // 寫入PSRAM位址 bit 7 ~ 0,圖框資料從位置10開始
   
        for(int i=0; i<fh; i++){        // 設定迴圈數為圖框高度
          for(int j=0; j<fw*2; j++){    // 設定迴圈數為圖框寬度乘2,每個點為2個位元組(RGB565)
            while(!Serial.available()); // 等待UART已備妥接收到資料信號
         
            rx_data = Serial.read();           // 接收一個位元組資料
            ILI9225_WR_Data8((char) rx_data);  // 寫一個位元組資料到LCD繪圖記憶區(GRAM)先高再低位元組
            SPI.transfer((char) rx_data);      // 寫一個位元組資料到SPI PSRAM
          }

          Serial.write(0x5B);                  // 透過UART回傳0x5B回應已接收到資料
        }
     
        digitalWrite(PIN_LCD_CS, HIGH);        // 將晶片選擇線設為高電位結束接收圖框資料
        digitalWrite(PIN_LED1, HIGH);          // 點亮LED1表目前圖框已下載完成
      }
     
      frame_index = 0;                         // 圖框編號歸零
      download_finish = 1;                     // 設定已完成接收旗標為1
      auto_loop = 1;                           // 預設下載完成後為自動依排程播放
    }
  }
}

// 讀取圖框(橫幅)排程資訊
// [in] idx 圖框(橫幅)編號
void GetFrameHeader(int idx)
{            
      digitalWrite(PIN_LCD_CS, LOW);  // SPI PSRAM 晶片選擇線設為低電位
      // 設定 SPI PSRAM讀取起始位置
      SPI.transfer(0x03);             // 寫入PSRAM低速讀取命令0x03
      SPI.transfer((idx+1)*2);        // 寫入PSRAM位址 bit 23 ~ 16,每個圖框(橫幅)儲存間隔20000h
      SPI.transfer(0);                // 寫入PSRAM位址 bit 15 ~ 8
      SPI.transfer(0);                // 寫入PSRAM位址 bit 7 ~ 0
         
      for(int i=0; i<FRAME_HEADER_SIZE; i++){
        frame_header[i] = SPI.transfer(0);  // 從SPI PSRAM讀取資料寫入frame_header對應位置
      }
     
      digitalWrite(PIN_LCD_CS, HIGH);  // SPI PSRAM 晶片選擇線設為高電位
}

// 顯示圖框(橫幅)於LCD屏幕上
// [in] idx 圖框(橫幅)編號
// [in] inv 0為正常顯示,1為色彩反相顯示
void ShowFrame(int idx, int inv)
{
      digitalWrite(PIN_LED1, LOW);     // 晶片選擇線設為低電位
      GetFrameHeader(idx);             // 讀取圖框(橫幅)排程資訊
     
      int fw = (frame_header[0] * 256) + (unsigned char)frame_header[1];  // 計算圖框寬度
      int fh = (frame_header[2] * 256) + (unsigned char)frame_header[3];  // 計算圖框高度
     
      if(frame_index%2 == 0)                    // 若圖框編號為偶數    
        ILI9225_SetXY(0, 0, 175, 219, 175, 0);  // 則設定顯示區域為(0, 0, 175, 219, 175, 0)
      else                                      // 若為奇數則為橫幅
        ILI9225_SetXY(0, 0, 35, 219, 35, 0);    // 則設定顯示區域為(0, 0, 35, 219, 35, 0)
      
      digitalWrite(PIN_LCD_CS, LOW);    // ILI9225_SetXY()會使晶片選擇線設為高電位,所以要重新設低電位
      // 設定 SPI PSRAM讀取起始位置
      SPI.transfer(0x03);               // 寫入PSRAM低速讀取命令0x03
      SPI.transfer((idx+1)*2);          // 寫入PSRAM位址 bit 23 ~ 16,每個圖框(橫幅)儲存間隔20000h
      SPI.transfer(0);                  // 寫入PSRAM位址 bit 15 ~ 8
      SPI.transfer(10);                 // 寫入PSRAM位址 bit 7 ~ 0,圖框資料從位置10開始
        
      for(int i=0;i<fh;i++){            // 設定迴圈數為圖框高度
        for(int j=0;j<fw*2;j++){        // 設定迴圈數為圖框寬度乘2,每個點為2個位元組(RGB565)
          char dataT = SPI.transfer(0); // 從SPI PSRAM讀取一個位元組資料        
          if(inv==1)                    // 若設定為色彩反相
            dataT = dataT ^ 0xFF;       // 則令數值反相(作XOR運算)
           
          ILI9225_WR_Data8(dataT);      // 將資料寫入LCD GRAM中                
        }
      }
     
      digitalWrite(PIN_LCD_CS, HIGH);   // 晶片選擇線設為高電位
      digitalWrite(PIN_LED1, HIGH);     // 點亮LED1  
}

//*************************************************************************************
// LCD 模塊驅動程式
// 176*220像素,65,536(2^16)色(RGB565),8 bit資料匯流排, ILI9225驅動晶片
// 完整資訊可參考http://www.displayfuture.com/Display/datasheet/controller/ILI9225.pdf
//*************************************************************************************

// LCD初始化
// 【注意】不熟悉參數設定的人請勿任意調整下列參數,以免造成LCD損壞或無法正常顯示
void ILI9225_init()
{
 digitalWrite(PIN_LCD_CS, HIGH);
 digitalWrite(PIN_LCD_RS, HIGH);
 digitalWrite(PIN_LCD_WR, HIGH); 
 delay(50);
 ILI9225_WR_Cmd_Data(0x10, 0x0000); // Power Control 1
 ILI9225_WR_Cmd_Data(0x11, 0x0000); // Power Control 2
 ILI9225_WR_Cmd_Data(0x12, 0x0000); // Power Control 3
 ILI9225_WR_Cmd_Data(0x13, 0x0000); // Power Control 4
 ILI9225_WR_Cmd_Data(0x14, 0x0000); // Power Control 5
 delay(40);
 ILI9225_WR_Cmd_Data(0x11, 0x0018); // Power Control 2
 ILI9225_WR_Cmd_Data(0x12, 0x6121); // Power Control 3
 ILI9225_WR_Cmd_Data(0x13, 0x006F); // Power Control 4
 ILI9225_WR_Cmd_Data(0x14, 0x495F); // Power Control 5
 ILI9225_WR_Cmd_Data(0x10, 0x0800); // Power Control 1
 delay(10);
 ILI9225_WR_Cmd_Data(0x11, 0x103B); // Power Control 2
 delay(50);
 ILI9225_WR_Cmd_Data(0x01, 0x011C); // Driver Output Control
 ILI9225_WR_Cmd_Data(0x02, 0x0100); // LCD AC Driving Control
 ILI9225_WR_Cmd_Data(0x03, 0x1028); // Entry Mode 螢幕橫式顯示,由左而右掃描
 ILI9225_WR_Cmd_Data(0x07, 0x0000); // Display Control 1
 ILI9225_WR_Cmd_Data(0x08, 0x0808); // Blank Period Control 1
 ILI9225_WR_Cmd_Data(0x0b, 0x1100); // Frame Cycle Control
 ILI9225_WR_Cmd_Data(0x0c, 0x0000); // Interface Control
 ILI9225_WR_Cmd_Data(0x0f, 0x0D01); // Oscillation Control
 ILI9225_WR_Cmd_Data(0x15, 0x0020); // ??
 ILI9225_WR_Cmd_Data(0x20, 0x0000); // RAM Address Set 1
 ILI9225_WR_Cmd_Data(0x21, 0x0000); // RAM Address Set 2
 ILI9225_WR_Cmd_Data(0x30, 0x0000); // Gate Scan Control
 ILI9225_WR_Cmd_Data(0x31, 0x00DB); // Vertical Scroll Control 1
 ILI9225_WR_Cmd_Data(0x32, 0x0000); // Vertical Scroll Control 2
 ILI9225_WR_Cmd_Data(0x33, 0x0000); // Vertical Scroll Control 3
 ILI9225_WR_Cmd_Data(0x34, 0x00DB); // Partial Driving Position -1
 ILI9225_WR_Cmd_Data(0x35, 0x0000); // Partial Driving Position -2
 ILI9225_WR_Cmd_Data(0x36, 0x00AF); // Horizontal Window Address -1
 ILI9225_WR_Cmd_Data(0x37, 0x0000); // Horizontal Window Address -2
 ILI9225_WR_Cmd_Data(0x38, 0x00DB); // Vertical Window Address -1
 ILI9225_WR_Cmd_Data(0x39, 0x0000); // Vertical Window Address -2
 ILI9225_WR_Cmd_Data(0x50, 0x0000); // Gamma Control 1
 ILI9225_WR_Cmd_Data(0x51, 0x0808); // Gamma Control 2
 ILI9225_WR_Cmd_Data(0x52, 0x080A); // Gamma Control 3
 ILI9225_WR_Cmd_Data(0x53, 0x000A); // Gamma Control 4       
 ILI9225_WR_Cmd_Data(0x54, 0x0A08); // Gamma Control 5
 ILI9225_WR_Cmd_Data(0x55, 0x0808); // Gamma Control 6
 ILI9225_WR_Cmd_Data(0x56, 0x0000); // Gamma Control 7
 ILI9225_WR_Cmd_Data(0x57, 0x0A00); // Gamma Control 8
 ILI9225_WR_Cmd_Data(0x58, 0x0710); // Gamma Control 9
 ILI9225_WR_Cmd_Data(0x59, 0x0710); // Gamma Control 10
 ILI9225_WR_Cmd_Data(0x07, 0x0012); // Display Control 1
 delay(50);
 ILI9225_WR_Cmd_Data(0x07, 0x1017); // Display Control 1
 digitalWrite(PIN_LCD_CS, LOW);
 ILI9225_WR_Cmd(0x22);              // Write Data to GRAM
 digitalWrite(PIN_LCD_CS, HIGH); 
}

// 設定LCD讀寫視窗範圍
// [in] x1,y1 為左上座標
// [in] x2,y2 為右下座標
// [in] sx,sy 為起始座標
// 必須符合 x1 < sx < x2, y1 < sy < y2,
//          x1 >= 0 && x2 < 176, y1 >= 0 && y2 < 220
// 座標是以直式顯示時表示,當橫式顯示時座標須逆時針轉90度
void ILI9225_SetXY(int x1, int y1, int x2, int y2, int sx, int sy)
{
 ILI9225_WR_Cmd_Data(0x36,x2); // HEX
 ILI9225_WR_Cmd_Data(0x37,x1); // HSX
 ILI9225_WR_Cmd_Data(0x38,y2); // VEY
 ILI9225_WR_Cmd_Data(0x39,y1); // VSY
 ILI9225_WR_Cmd_Data(0x20,sx); // GRAM start address low  byte
 ILI9225_WR_Cmd_Data(0x21,sy); // GRAM start address high byte 

 digitalWrite(PIN_LCD_CS, LOW);
 ILI9225_WR_Cmd(0x22);         // Write Data to GRAM
 digitalWrite(PIN_LCD_CS, HIGH);
}

// LCD寫入控制命令
// [in] VL 只送低位元組,高位元組一律填0x00
void ILI9225_WR_Cmd(char VL)
{
 digitalWrite(PIN_LCD_RS, LOW);   // 晶片選擇線設為低電位
 ILI9225_WR_Data16(0,VL);         // LCD寫入2個位元組(16 bit)資料
 digitalWrite(PIN_LCD_RS, HIGH);  // 晶片選擇線設為高電位 
}

// LCD寫入2個位元組(16 bit)資料
// [in] VH 為資料高位元組
// [in] VL 為資料低位元組
void ILI9225_WR_Data16(char VH, char VL)
{
 ILI9225_WR_Data8(VH);            // LCD寫入1個(高)位元組資料
 ILI9225_WR_Data8(VL);            // LCD寫入1個(低)位元組資料
}

// LCD寫入1個位元組(8 bit)資料
// [in] VL 為資料
// 寫入時由於I/O腳位分散於Arduino Port C & D,為加快速度採埠直接位元運算方式變更I/O
void ILI9225_WR_Data8(char VL)
{
 PORTC = (PORTC & 0xF0) | (VL & 0x0F);  // 取得資料低4位元送到 PC0 ~ 3
 PORTD = (PORTD & 0x0F) | (VL & 0xF0);  // 取得資料高4位元送到 PD4 ~ 7
 digitalWrite(PIN_LCD_WR, LOW);         // 晶片選擇線設為低電位
 digitalWrite(PIN_LCD_WR, HIGH);        // 晶片選擇線設為高電位  
}

// LCD寫入一組完整命令(暫存器編號加16bit資料)
// [in] cmd  命令暫存器編號
// [in] data 資料
void ILI9225_WR_Cmd_Data(char cmd, int data)
{
  digitalWrite(PIN_LCD_CS, LOW);        // 晶片選擇線設為低電位 
  ILI9225_WR_Cmd(cmd);                  // 寫入命令 
  ILI9225_WR_Data16(data>>8, data);     // 寫入資料(高位元組,低位元組)
  digitalWrite(PIN_LCD_CS, HIGH);       // 晶片選擇線設為高電位
}

// LCD清除畫面(全部黑屏)
// 即將GRAM全部填入0x00
void ILI9225_Clr_Screen()
{
  ILI9225_SetXY(0, 0, 175, 219, 175, 0); // 設定視窗範圍為全螢幕
 
  PORTC = (PORTC & 0xF0);                // 清除 PC0 ~ 3
  PORTD = (PORTD & 0x0F);                // 清除 PD4 ~ 7
  digitalWrite(PIN_LCD_CS, LOW);         // 晶片選擇線設為低電位
 
  for(int i=0; i<220; i++){              // 設定迴圈數為圖框高度
    for(int j=0; j<176*2; j++){          // 設定迴圈數為圖框寬度乘2,每個點為2個位元組(RGB565)
      digitalWrite(PIN_LCD_WR, LOW);     // 設定LCD寫入線為低電位
      digitalWrite(PIN_LCD_WR, HIGH);    // 設定LCD寫入線為高電位
    }
  }
 
  digitalWrite(PIN_LCD_CS, HIGH);        // 晶片選擇線設為高電位
}

// LCD測試
// 顯示十六色橫條紋於畫面上
void ILI9225_LCD_Test()
{
    ILI9225_SetXY(0, 0, 175, 219, 175, 0);  // 設定視窗範圍為全螢幕
    digitalWrite(PIN_LCD_CS, LOW);          // 晶片選擇線設為低電位
   
    for(int y=0; y<176; y++){               // 設定迴圈數為圖框高度
      int index = (y/11) % 16;              // 取得顏色表索引值
      int color = color_table[index];       // 取得欲寫入顏色內容
     
      for(int x=0; x<220; x++){             // 設定迴圈數為圖框寬度,每個點為2個位元組(RGB565)
        ILI9225_WR_Data16(color>>8, color); // 寫入LCD GRAM中
      }
   }
   
    digitalWrite(PIN_LCD_CS, HIGH);         // 晶片選擇線設為高電位
    delay(1000);                            // 延時1秒
    ILI9225_Clr_Screen();                   // 清除LCD畫面
    digitalWrite(PIN_LED1, HIGH);           // 點亮LED1
}

OpenQSignage開源迷你電子看板(Arduino LCD動畫胸牌)

OpenQSignage開源迷你電子看板(Arduino LCD動畫胸牌)#1_硬體設計說明

OpenQSignage開源迷你電子看板(Arduino LCD動畫胸牌)#2_Arduino程式說明

OpenQSignage開源迷你電子看板(Arduino LCD動畫胸牌)#3_PC程式說明

沒有留言:

張貼留言

【頂置】簡報、源碼、系列文快速連結區

常有人反應用手機瀏覽本部落格時常要捲很多頁才能找到系列發文、開源專案、課程及活動簡報,為了方便大家快速查詢,特整理連結如下,敬請參考! Edge AI Taiwan 邊緣智能交流區 全像顯示與互動交流區 台科大(NTUST) 人工智慧與邊緣運算實務 開南大學...