2015年3月6日 星期五

Raspberry Pi 利用 GPIO 控制 32x32 RGB LED 陣列

實驗名稱:
    GPIO 控制 32x32 RGB LED Matrix

實驗目的:
    了解在 Pi 板上,寫 C 語言控制 GPIO 來控制 32 x 32 RGB LED Matrix 的方法。

使用材料及設備:
    市售 32x32 RGB LED 陣列板子


    Raspberry Pi B+ 板
    連接線


原理介紹:

    此 32x32 RGB LED 陣列板子的橫向 (x-軸) LED 亮暗控制器是由 32 位元位移暫存器來操作,而縱向 (y-軸) LED 亮暗控制器是由一個 4 bit 控制16 路的多路分用器來操作。故我們在這個實驗需要練習: 用程式控制 32 bit 位移暫存器、用程式控制16 路的多路分用器。最後在32x32 RGB LED Matrix 上面顯示圖案。下面分別說明位移暫存器和多路分用器的作用。

    一、位移暫存器 IC 是一種序列輸入轉換成並列輸出的一種 IC。基本功能可以用三支 pin 腳來達成資料輸入: 資料 (DATA pin ) 、CLOCK (CLK pin)、觸發 (LAT pin)。跟據IC的規格,輸出 bit 數就是 輸出 pin 腳數目。在此例當中,輸出為 32 bit ,故有 32 支 pin 腳依序控制了LED Matrix 的 x-軸向 32 個的 LED 的亮暗(1 為亮,0 為暗)。而操作方式在下節會說明。

    二、多路分用器 IC 是一種由少數 pin 控制要輸出或接地的pin腳。在此例中,輸入為四根 pin ,代表著 4 個輸入 bit。而輸出有 16 根,代表著 16 個 bit。而四個輸入的 bit 可以表現出來的數值為0~15 (0b0000 ~ 0x1111),所以剛好可以控制第幾個輸出腳為導通,其他的輸出腳為不導通。而操作方式在下節會說明。


實驗解說/功能說明:

    一、位移暫存器操作:

下面我們用最常見的8bit位移暫存器作說明。一個 bit 的輸入分為兩個步驟: 第一個步驟設定 DATA pin 的電位,如下圖我們設為 1 (高電位),此時CLK pin 和 LATCH pin都為 0 (低電位)。


第二個步驟,如下圖,輸出個一個 pulse 到 CLK pin 上( 就是直接對 CLK pin 的電位設為 1 等待若干時間之後再設為 0 )。


完成第二個步驟之後,如下圖位移暫存器就會把原來的 bit 的順序向前推移一個 bit ,然後把DATA pin 的電位狀態給填入到原本會空出來的 bit 0 的位置。

但是這個時候,位移暫存器的輸出並不會直接即時反應所輸入的資料變化。

要將位移暫存器內部記憶體的 bit 狀態反應到八個對應的輸出 pin 腳上的話,只需要輸出一個 pulse 到 LATCH pin 上面,通知位移暫存器,之後位移暫存器就會將輸出狀態變成和內部記憶體的 bit 狀態相同。

所以如下圖,我們送出一個 pulse 訊號到 LATCH pin 上面。

之後(如下圖) 位移暫存器就會把之前輸入的 bit 狀態反應到輸出 pin 腳上了。

換句話說,CLK pin的作用就是通知位移暫存器 IC 將現在的 DATA pin 的電位狀態(高電位為 1 ,低電位為 0) 記錄到記憶體之中,而原來的 bit 狀態則向前推進一個 bit 。而LATCH pin 的作用就是通知位移暫存器把當下的記憶體 bit 狀態反應到對應的輸出pin腳的電位上。


    二、多路分用器操作:
多路分用器的輸出入 pin 腳概略圖如下:
在這邊範例之中,有A、B、C、D,四個數位輸入 pin 腳,而輸出有編號依序由 0 到 15 總共 16 支數位輸出 pin 腳。

而下面這張表格則說明了輸入和輸出的關係。


16 支輸出腳位在同一個時間裡,只有一支輸出高電位其他 15 支則輸出低電位。而選擇哪一支腳位輸出高電位是透過ABCD四支輸入腳位做選擇。由下表的組合來看,A、B、C、D 四支腳位分別代表了一個 byte 的第 0、1、2、3 位元狀態。而我們可以得到輸出高電位的腳位選擇公式如下:

 # OUTPUT = (D * 8 + C * 4 + B * 2 + A * 1)

而公式所得到的最大值為 15 ( ABCD的輸入電位都為 1: 高電位 ),最小值為 0 (ABCD的輸入電位都為 0:低電位 )。

而在這個 LCD 陣列板子上的電路會設計成,16 個輸出 pin 腳位依序對應 16 排 LED 電路,而當該 pin 輸出為高電位時,該對應排的 LED 電路會被導通,相反地,輸出為低電位時則切斷該排 LED 電路使其電流無法導通。所以,總而言之,就是用ABCD這四個輸入pin當作四個 bit 來選擇要導通第幾排 LED。


    三、掃描 LED 陣列操作:

此範例中的 LED 陣列為市面上常用的形式,是由位移暫存器加上多路分用器來點亮指定位置的LED燈。而我們這邊先將之視為一個由 16 排(y軸: 0 ~ 15 ) LED 燈條所組成。而每一條燈條是由32顆 (x軸: 0 ~ 31) LED燈所組成。

LED 陣列是利用 32 bit 的位移暫存器,來選擇橫向(x-軸) 一排 32 顆哪幾顆 LED 燈是要被點亮的。而同時利用多路分用器來選擇縱向(y-軸) 16 排中的哪一排 LED 燈是要被導通(點亮)的。

而整個 32x32 LED陣列是由兩塊 32x16 LED 陣列所組合而成。所以整片板子會用到 2 個32bit的位移暫存器來顯示一個顏色,但考慮到有紅、綠、藍三個顏色,所以整個系統會使用到 2 * 3 = 6 個 32 bit 的位移暫存器。而因為有兩片板子,所以會使用到兩顆 16 bit 輸出的多路分用器。

換句話說,先考慮一個顏色的情形,32x32 LED 陣列同一時間一次只能點亮 32 排中指定的一排 LED ,而被指定的這一排 32 顆 LED 依照圖案可以知道要指定哪幾個位置的LED要被點亮。跟據人眼視覺暫留的原理,顯示整個畫面要用比人眼可辨識還要快的速度把整個畫面"掃瞄"一次又一次。而人眼可辨識的速度大約為16 ms,也就是說,每秒鐘要掃描整個畫面多達 60 次 ( 畫面更新頻率60Hz )。


程式說明:

先只考慮只顯示一個顏色的情形,程式裡面會先宣告一個 uint8_t 變數的二維陣列 led_matrix[][] :

mat[x][y]

大小為 32x32,用來表達 32x32 led matrix 要顯示的圖案。先設定元素值為 1 則表示為亮,0表示為暗。則假設要顯示指定第 y 排的 LED 操作位移暫存器的程式碼如下:

 // 寫入 led_matrix[][] 內第 y 排 LED 的資料到位移暫存器之中
 for(x=0 ; x < 32 ; x++){
  
  GPIO_WRITE(DATA_IN_PIN_1 , (led_matrix[x][y   ]==1)?(HIGH):(LOW) );

  // 因為 32x32 LED 陣列是由兩塊32x16的 LED 陣列組合而成
  // 而且因為共用多路分工器,所以指定了第 y 排 LED 也同時指定了第 y+16 排 LED
  // 其 DATA_IN_PIN_2 為另一顆位移暫存器的資料輸入 pin 腳
  GPIO_WRITE(DATA_IN_PIN_2 , (led_matrix[x][y+16]==1)?(HIGH):(LOW) );

  GPIO_WRITE(CLK_PIN,HIGH); 
                GPIO_WRITE(CLK_PIN,LOW);
 }
 // 導通指定第 y 排(同時第y+16排也會被導通)
 GPIO_WRITE(PIN_A , (y & 0x01 )?(HIGH):(LOW));
 GPIO_WRITE(PIN_B , (y & 0x02 )?(HIGH):(LOW));
 GPIO_WRITE(PIN_C , (y & 0x04 )?(HIGH):(LOW));
 GPIO_WRITE(PIN_D , (y & 0x08 )?(HIGH):(LOW));

 // 將位移暫存器的資料反應到相對應的輸出 pin 腳上。
 GPIO_WRITE(LAT_PIN,HIGH); GPIO_WRITE(LAT_PIN,LOW);


上面的 DATA_PIN、CLK_PIN 、LAT_PIN 分別指的是對應位移暫存器DATA IN、CLK、LATCH pin 的 GPIO 號碼。HIGH 、LOW代表高低電位,GPIO_WRITE()函式則是用來操作指定GPIO pin 的電位用的。PIN_A、PIN_B、PIN_C、PIN_D則分別對應多路分工器的A、B、C、D四個 pin 的 GPIO 號碼。

而接著我們把只顯示一排LED的程式擴充成依序顯示第0~15排LED的迴圈,也就是掃描一整個畫面的迴圈程式:

// 要掃瞄整個畫面,所以代表多路分用器的 y 值要從0~15整個畫面掃一次
for(y=0 ; y < 16 ; y++)
 for(x=0 ; x < 32 ; x++){
  
  GPIO_WRITE(DATA_IN_PIN_1 , (led_matrix[x][y   ]==1)?(HIGH):(LOW) );
  GPIO_WRITE(DATA_IN_PIN_2 , (led_matrix[x][y+16]==1)?(HIGH):(LOW) );
  GPIO_WRITE(CLK_PIN,HIGH); 
                GPIO_WRITE(CLK_PIN,LOW);
 }
 GPIO_WRITE(PIN_A , (y & 0x01 )?(HIGH):(LOW));
 GPIO_WRITE(PIN_B , (y & 0x02 )?(HIGH):(LOW));
 GPIO_WRITE(PIN_C , (y & 0x04 )?(HIGH):(LOW));
 GPIO_WRITE(PIN_D , (y & 0x08 )?(HIGH):(LOW));
 GPIO_WRITE(LAT_PIN,HIGH); GPIO_WRITE(LAT_PIN,LOW);
}



最後再加入考慮RGB三個顏色,在這邊將三個顏色的資訊用三個led_matrix[32][32]來表示。

for(y=0 ; y < 16 ; y++)
 for(x=0 ; x < 32 ; x++){
  
  // 實際上有RGB三個顏色,所以led_matrix 擴充為三組
  GPIO_WRITE(R_DATA_IN_PIN_1 , (r_led_matrix[x][y   ]==1)?(HIGH):(LOW) );
  GPIO_WRITE(R_DATA_IN_PIN _2, (r_led_matrix[x][y+16]==1)?(HIGH):(LOW) );
  GPIO_WRITE(G_DATA_IN_PIN_1 , (g_led_matrix[x][y   ]==1)?(HIGH):(LOW) );
  GPIO_WRITE(G_DATA_IN_PIN _2, (g_led_matrix[x][y+16]==1)?(HIGH):(LOW) );
  GPIO_WRITE(B_DATA_IN_PIN_1 , (b_led_matrix[x][y   ]==1)?(HIGH):(LOW) );
  GPIO_WRITE(B_DATA_IN_PIN _2, (b_led_matrix[x][y+16]==1)?(HIGH):(LOW) );

  // RGB 三個顏色的位移暫存器電路共用同CLK和LAT
  GPIO_WRITE(CLK_PIN,HIGH); 
  GPIO_WRITE(CLK_PIN,LOW);
 }
 GPIO_WRITE(PIN_A , (y_line & 0x01 )?(HIGH):(LOW));
 GPIO_WRITE(PIN_B , (y_line & 0x02 )?(HIGH):(LOW));
 GPIO_WRITE(PIN_C , (y_line & 0x04 )?(HIGH):(LOW));
 GPIO_WRITE(PIN_D , (y_line & 0x08 )?(HIGH):(LOW));
 GPIO_WRITE(LAT_PIN,HIGH); GPIO_WRITE(LAT_PIN,LOW);
}


在整個程式實作之中,我們需要寫一個無窮迴圈來不停地掃瞄更新LED 陣列的畫面。但整個程式不能因此而停留在這個地方,所以比較好的做法就是用一個pthread來專門負責畫面掃瞄的動作。而主程式則只需要負責將要顯示的圖案寫入到 RGB 三個 led_matrix[32][32] 陣列。

而在利用一般的user space 的程式了解整個 LED 陣列的驅動方式之後,未來就可以直接寫成 Linux 的裝置驅動程式直接驅動LED面板。或是利用一個MCU來做LED 陣列的驅動控制器,然後再MCU和Raspberry Pi 之後利用UART、I2C、SPI等方式做為通訊介面,這樣就可以將掃瞄LED陣列的 loading 轉移到MCU之上。


執行結果:

下面為主要的 LED 掃瞄迴圈加上另外一個從音頻分析IC的應用。



沒有留言 :

張貼留言