2015-06-02

Arduino筆記: Sparkfun 8X16 RGB LED矩陣顯示控制

Sparkfun的8X8 LED矩陣使用SPI介面控制顯示64個LED,同時背板也具備Daisy Chain功能,可串接多個同型的LED矩陣,透過Arduino(SPI Master)傳送資料給第一個控制板,會依序傳遞給所有鏈結的LED矩陣,控制所有LED的顏色顯示。
SparkFun 8x8 LED 矩陣控制板
過去曾經使用過以單一LED矩陣製作『NXT顯示裝置(NXT RGB LED訊息看板)』,由於8X8 LED數量的限制,因此只適用於水平捲動顯示英數字型的訊息。對於筆劃較複雜的中文字型,則至少需要有16X16的解析度,所以這次將會以Daisy chain串接兩個8X8 LED矩陣成為8X16的顯示面板用來顯示中文訊息。
在以下的影片中,會展示以水平捲動方式顯示8X8英文字型以及垂直捲動顯示16X16中文字型等兩種效果。

『EV3 8x16 LED顯示看板』示範影片:




重要技術資訊

在Sparkfun官網文件中『RGB Matrix – Serial Backpack User Guide』,提供了許多重要的技術資訊,另外在產品頁面中也還有許多應用範例值得參考。
  1. Arduino透過SPI介面傳送64個bytes的資料序列給LED矩陣,藉以控制每一個LED的顏色顯示, 而資料byte的顏色值格式如以下圖示,最多可顯示255種顏色:
  2. 在Daisy Chain模式下,SPI bus的clock speed不能超過125KHz,設定SPI時脈的Arduino程式碼如下:
    
    void setup() 
    {
      Serial.begin(9600);
      
      pinMode (CS_pin,   OUTPUT);
      pinMode (MOSI_pin, OUTPUT);
      pinMode (SCLK_pin, OUTPUT);
      SPI.begin(); 
      SPI.setClockDivider(SPI_CLOCK_DIV128); // Daisy-chain should set this speed
      setDaisyChain(true);  
    }
    

  3. 設定Daisy Chain模式的指令為:'%' + nn表示串接的LED矩陣數量,以下的程式碼是將串接數量設定為2。
    同時因為 '%' 字符是指令前置碼,所以由SPI介面傳送的資料序列中,應避免包含 '%' 字符(Ascii值0x25),否則會造成不正常的顯示結果。
    
    /* ************************************************************
     * Enable Daisay-chain mode
     * ************************************************************ */
    void setDaisyChain(boolean setChain)
    {  
      //Send the command mode character
      digitalWrite(CS_pin, LOW);
      delay(1);
      SPI.transfer('%');
      digitalWrite(CS_pin, HIGH);
      delay(100);
      
      //Configure the correct number of boards
      digitalWrite(CS_pin, LOW);
      delay(1);
      if(setChain) SPI.transfer(2);
      else         SPI.transfer(1);
      digitalWrite(CS_pin, HIGH);
      delay(100);
    }            
    

  4. 當以Daisy Chain串接兩個LED矩陣,SPI Master(Arduino)需將128 bytes的資料序列全部傳送給LED矩陣之後,才能夠正常地顯示,而資料序列的前64個bytes會顯示在第二個LED矩陣,後64 Bytes則會顯示在第一個。

    在下面的圖示中,所表示的是SPI buffer中128個bytes的資料序列對應每一個LED實際位置的順序關係,而兩者間的關係式為:

    bufferIndex = ledRow * 8 + ledColumn + (ledColumn > 7 ?  560);

    LED 位置對應SPI Buffer資料序列順序關係
    以下則是SPI介面傳送128個bytes資料序列的Arduino程式碼:
    
    /* ************************************************************
     * SPI write to RGB Matrix
     * ************************************************************ */
    void transferRGBMatrix(char* dotMatrixBuffer, int delayMillis) 
    {
      digitalWrite(CS_pin, LOW);
      delay(1);
      for(int xi=0; xi<128; xi++) SPI.transfer(dotMatrixBuffer[xi]);
      digitalWrite(CS_pin, HIGH);
      delay(1);  
      
      delay (delayMillis);
    }

實作英數字型顯示

  1. 以LED矩陣顯示英數字符,需要有字符的8x8 bitmap字型資料,可以預先以陣列方式將大小寫英文字母(A~Z,a~z)、數字(0~9)以及一些常用符號的bitmap資料建立在Arduino程式中,這樣就可以透過呼叫函式傳入英數字符作為引數方式讀取對應的bitmap資料。
    
    /* ************************************************************
     * Get font buffer extended function
     * char displayChar : Ascii value of the character in the string to be displayed
     * int  cidx        : The index of this character in the string
     * char* fontBuffer : The font pattern buffer of this character  
     * ************************************************************ */
    void getFontEX(char displayChar, int cidx, char* fontBuffer)
    {   
      int fidx=cidx*FONT_HEIGHT;
      
      if((displayChar >= 'A') && (displayChar <= 'Z')) 
        for(int idx=0; idx<FONT_HEIGHT; idx++) fontBuffer[fidx+idx] = captialFont[(displayChar-'A')*FONT_HEIGHT+idx];
      else
      if((displayChar >= 'a') && (displayChar <= 'z')) 
        for(int idx=0; idx<FONT_HEIGHT; idx++) fontBuffer[fidx+idx] = lowerFont[(displayChar-'a')*FONT_HEIGHT+idx];
      else
      if((displayChar >= '0') && (displayChar <= '9'))
        for(int idx=0; idx<FONT_HEIGHT; idx++) fontBuffer[fidx+idx] = numberFont[(displayChar-'0')*FONT_HEIGHT+idx];
      else {
        switch (displayChar) {
          case '.':
            for(int idx=0; idx<FONT_HEIGHT; idx++) fontBuffer[fidx+idx] = dotFont[idx];
            break;      
          case ',':
            for(int idx=0; idx<FONT_HEIGHT; idx++) fontBuffer[fidx+idx] = commaFont[idx];
            break;     
          case '!':
            for(int idx=0; idx<FONT_HEIGHT; idx++) fontBuffer[fidx+idx] = exclamFont[idx];
            break;
        default:
            for(int idx=0; idx<FONT_HEIGHT; idx++) fontBuffer[fidx+idx] = 0x00;
        }
      }
    }
    
    
     
  2. 依據LED矩陣Data Byte顏色值格式,將Bitmap資料中每一個bit值轉換成顏色值資料,轉換的方式為:當bit為ON時設定為前景顏色值,OFF則為背景顏色值。
      
  3. 轉換後的LED顏色值資料依據前面提到的LED位置對應SPI buffer資料序列關係,存放至SPI buffer中,再以SPI介面傳送給LED矩陣就能夠正確的顯示出字符。
    Bitmap轉換SPI buffer資料序列示意圖

實作英數字串水平捲動效果

水平捲動8X8英數字串的方式與動畫原理相似,依序將連續的畫格(Frame)傳送給LED矩陣,製作出捲動的視覺效果:
  1. 首先將所有字符的bitmap資料依序結合成一個新的bitmap資料,因為bitmap是圖像的數位表示方式,這樣就如同接合每個字符的 8 x 8 正方形圖像成為一張水平方位的矩形圖像。
    而矩形圖像的尺寸為:寬 x 長 = 8 x (8 x N) 個 bits,N是字符的數目,每一個bit資料可以視為是一個像素(Pixel) 。
    8 x 32 英數字串bitmap
     
  2. 接著以 8 x 16的LED矩陣作為畫布(Canvas),自矩形圖像資料中,由左往右依序漸次增加讀取 8 x 1個像素作為劃格(Frame),將畫格內的bitmap資料轉換成LED顏色值再傳送至畫布中顯現出來,這樣就可以呈現出以水平方式往左捲動的視覺效果。

實作中文字串垂直捲動顯示

  1. 中文無法如同英數字可在程式中預先建立每個字符的bitmap資料,而是需將所要顯示的中文訊息字串的bitmap資料製作成header file,再由程式中以include方式引用。
    Header file的bitmap圖像尺寸會是:寬 x 長 = (16 x N) x 16 bits,N是中文字符的數目。
    如右圖示為 32 x 16 bits的圖像。

     
  2. 製作向上垂直捲動效果的原理同水平捲動的處理方式,差別在於改成由上往下依序漸次增加讀取 1 x 16 個像素作為劃格(Frame),同樣將 bit 值轉換成LED顏色值之後再傳送至LED矩陣,就可以顯示出垂直捲動的視覺效果。

 ※ 水平捲動英數字型程式碼


/* ************************************************************
 * display font with marguee effect extended function
 * ************************************************************ */
void displayFontMarqueeEX(int strLen, char* fontBuffer, char fontColor, char backColor, int delayMillis)
{
  char dotMatrixBuffer[128];
  short ledidx=0;
  
  for(int cidx=0; cidx<strLen; cidx++) {                         // Each character
    for(int fidx=FONT_WIDTH-1; fidx>=0; fidx--) {                // From MSB to LSB 
      for(int xrow=0; xrow<8; xrow++) {                          // Each row      
        for(int xcol=15, fcnt=fidx; xcol>=0; xcol--, fcnt++) {   // Each column
          ledidx = xrow*8 + (xcol>=8? xcol+56: xcol);
          dotMatrixBuffer[ledidx] = backColor;    
          if(fcnt<=FONT_WIDTH-1) { // if fcnt = 0-7, move current char pattern to led buffer
            if(bitRead(fontBuffer[cidx*FONT_HEIGHT+xrow], fcnt))
              dotMatrixBuffer[ledidx] = fontColor;
          } // if fcnt = 0-7, move current char pattern to led buffer
          else {
            if(fcnt<=FONT_WIDTH*2-1) { // if fcnt = 8-15, move last char pattern to led buffer
              if(cidx>0) {
                if(bitRead(fontBuffer[(cidx-1)*FONT_HEIGHT+xrow], fcnt-FONT_WIDTH))
                  dotMatrixBuffer[ledidx] = fontColor;
              }
            } // if fcnt = 8-15, move last char pattern to led buffer
            else { // if fcnt = 16-23, move last+1 char pattern to led buffer 
              if(cidx>1) {
                if(bitRead(fontBuffer[(cidx-2)*FONT_HEIGHT+xrow], fcnt-FONT_WIDTH*2))
                  dotMatrixBuffer[ledidx] = fontColor;
              }
            } // if fcnt = 16-23, move last+1 char pattern to led buffer 
          }          
        } // Each column    
      } // Each row
      transferRGBMatrix(dotMatrixBuffer, delayMillis);
    } // From MSB to LSB
  } // Each character
}

※ 垂直捲動中文字型程式碼


/* ************************************************************
 * display 16*16 bitmap font with marguee effect extended function
 * ************************************************************ */
void displayBigFontMarqueeEX(byte* fontBuffer, char fontColor, char backColor, int delayMillis)
{
  char dotMatrixBuffer[128];
  short ledidx=0;
  
  for(int fidx=0; fidx<font_height; fidx++) {
    for(int yidx=7, ridx=fidx; yidx>=0; yidx--, ridx++) {
      for(int bidx=7; bidx>=0; bidx--) {
        ledidx = yidx*8+(7-bidx);
        dotMatrixBuffer[ledidx]    = backColor;
        dotMatrixBuffer[ledidx+64] = backColor; 
        if(ridx<font_height) {
          if(bitRead(fontBuffer[ridx*2], bidx))   dotMatrixBuffer[ledidx]    = fontColor;         
          if(bitRead(fontBuffer[ridx*2+1], bidx)) dotMatrixBuffer[ledidx+64] = fontColor;
        }
      }
    }
    transferRGBMatrix(dotMatrixBuffer, delayMillis);
  }
}

沒有留言:

張貼留言