用樹莓派驅動德飛萊16x16點陣屏顯示漢字

這周嘗試用樹莓派驅動了 LY-LED16x16B V2.1 點陣屏。之所以選用這一款是因為它是 16 X 16 點陣(實際上是 4 塊 8 X 8 LED 拼起來的,只不過已經集成好了)。LY-LED16x16B V2.1 本身是用于單片機的,官方提供了 51 單片機和 Arduino 下的測試程序。不過,我們有強大的樹莓派,既然單片機能驅動,樹莓派應該也不是問題。

德飛萊 LY-LED16x16B V2.1 led 點陣屏用在單片機開發板

LY-LED16x16B V2.1

德飛萊 LY-LED16x16B V2.1 led 點陣屏連接樹莓派

LY-LED16x16B V2.1

點亮效果(文字旁邊的虛影不是因為顯示問題,而是因為手機拍照時文字是移動的)

那么,樹莓派驅動這樣一塊集成 16 X 16 LED 點陣屏具體該怎么做呢?

點陣屏顯示的原理

上面這張圖是 LY-LED16x16B 的電路原理圖,很久沒接觸數字電路的小伙伴看了是不是覺得頭暈?其實一看到這張圖,我也覺得暈,不過沒有關系……

我們先來搞清楚點陣屏顯示的基本原理:

簡單 8x8 LED 點陣屏

基礎的 8x8 點陣屏其實是這貨----

它的原理比較簡單:

上面這張圖應該容易理解,它其實就是發光二極管排列成矩陣,利用二極管的單向導通特性,分別用 8 行、8 列一共 16 個引腳來控制。平時 R1 ~ R8 為低電平,C1 ~ C8 為高電平,此時二極管不導通。當要點亮第 i 行,第 j 列時,讓 Ri 輸出高電平,Cj 輸出低電平即可。這樣用 16 個引腳就可以來控制,顯示的時候,每次只能控制一行,否則就會點亮我們不希望點亮的點。比如要想點亮 R2C2、R3C3 兩個點,就要將 R2、R3 置為高電平,C2、C3 置為低電平,但如果同時操作的話,R2C3 和 R3C2 這兩個點也會被點亮,所以只能先將 R2 置為高電平,C2 置為低電平,點亮 R2C2,然后將 R2、C2 恢復,再將 R3 置為高電平,C3 置為低電平。實際上這種方式也就是所謂的逐行掃描。

從 8x8 到 16x16

我們看到驅動普通的 8x8 點陣屏就要 16 個引腳,而 8x8 點陣屏對于顯示漢字來說是遠遠不夠的,那么為了顯示漢字,我們就要將 4 個 8x8 點陣屏連接起來組成一個 16 x 16 點陣屏,但是這樣的話,可想而知需要的引腳數量就更多了,用樹莓派驅動如此多數量引腳的設備顯然不現實。

然而,我們并不需要直接連接 4 個點陣屏,我們可以使用帶有集成電路的 16x16 點陣屏,比如上面那一款。

LY-LED16x16B V2.1

我們看一下 LY-LED16x16B 與普通點陣屏的區別。

可以看到 LY-LED16x16B 由四塊 8x8 點陣屏和一些控制器組成。它一共有 22 個引腳,但只有右側 11 個引腳是輸入引腳,左側的 11 個 是輸出引腳,用于多片級聯。11 個輸入引腳中除去最下方兩個引腳是 VCC 和 GND 外,一共有 9 個控制引腳,從上到下分別是:R1、D、C、B、A、LATCH、SCK、G1、EN/OE。

這些引腳又分為幾類:

  • R1/G1,點亮 LED 燈的輸入引腳。有的屏是紅綠雙色的,R1 是點亮紅色,G1 是點亮綠色,因為我們是單色屏,所以 G1 引腳是不用的。注意這個 LED 點陣屏是共陽的,所以要點亮的話,應當輸入低電平。

  • D/C/B/A,行選信號,這是四位行選信號,表示點亮哪一行,例如要點亮第三行,需要向 B 引腳輸入高電平,其他三個引腳輸入低電平(行號從0開始,第一行是0000,第二行是0001,所以第三行是0010)。

  • LATCH/SCK,SCK 是時鐘信號,LATCH 是鎖存器。注意到前面 R1/G1 輸入只有一位,行選可以選行,那么我們怎么確定要點亮當前行的哪一列呢?這就需要用時鐘和鎖存器了。實際上如果學過數字電路的同學會比較容易理解。在這里我們用到觸發器串行輸入的概念,利用時鐘脈沖信號觸發寄存器存儲當前值,一共 16 位寄存器能夠存儲 16 個值(最近的 16 個脈沖時,R1/G1 的邏輯值),當輸入完成后,通過 LATCH 鎖存器將寄存器的值保存,然后點亮 LED 燈輸出。

  • EN/OE 使能端,輸出高電平時將關閉屏幕,這是因為實際顯示時,逐行掃描的頻率很高,鎖存器輸出和切換行幾乎同時進行,因此切換時要將屏幕關掉,不然可能會將上一行的信號部分帶到下一行,讓字產生虛影(正常顯示點旁邊會有微弱點亮的LED)。

所以其實要顯示內容的程序邏輯并不復雜,偽代碼如下:

while(1){
        for(var i = 0; i < 16; i++){//行選
            LATCH = 0; //打開鎖存,接收輸入信號
            for(var j = 0; j < 16; j++){
                R1 = 當前位輸出
                //給一個脈沖,記錄到寄存器
                SCK = 0;
                SCK = 1;         
            }
            //一行寫完了準備寫入當前行
            OE = 1; //通過使能端關閉屏幕,避免虛影
            ROW = i;    //通過四位行選信號選擇當前行
            LATCH = 1; //鎖存并輸出
            OE = 0; //打開使能,將數據顯示在屏幕上
        }
    }

可以看到,使用了 LATCH/SCK 之后操作并不復雜,卻大大減少了需要的引腳數量,這就是集成 LED16x16 的作用。接下來該連線了。

設計電路

電路原理圖如下:

由于 LY-LED16x16B 需要 5V 供電,雖然也可以用樹莓派供電,但為了以后擴展,最好還是接外部電源,因此 +5V 和 GND 不接樹莓派。

  • R1 接 P36
  • LATCH/SCK 分別接 P38、P40
  • OE 使能端接 P32
  • 行選信號接 P29、P31、P33、P35

這樣,理論上電路就可以工作了,但是有一個地方需要注意,由于輸入端 R1 在樹莓派不輸出的時候懸空,SCK 頻率又很高,容易產生噪點,影響顯示效果,因此,最好在 R1 端加一個下拉電阻來穩定輸入(阻值不能太大也不能太小,在 1k 左右即可),避免噪點產生:

至此,電路設計就完成了。

設計程序

現在要開始設計驅動程序了。根據我們前面分析的原理,我們需要逐行輸出。在這里我們可以使用一個16位二進制數來表示一行,也可以使用一個數組來表示。為了 JS 方便操作,我們不妨直接用數組來表示:

var pixels = [
        [0,0,1,0,1,0......,0], //第 1 行
        [0,1,1,0,1,1......,1], //第 2 行
        ......
        [1,0,1,0,0,1......,0]    //第 16 行
    ]

這樣的話我們就需要將漢字轉換為點陣數組來輸出,這就涉及到另一個話題:

點陣字庫

在上個世紀的 90 年代,16x16 和 32x32 點陣字庫還在 PC 的操作系統和辦公軟件中使用。而現在,在 PC 上已經看不見點陣字庫,基本上點陣字體都被更漂亮的矢量字庫取代了。

一開始,我的思路是嘗試將系統的宋體 SimSun.ttf 中文字的輪廓提取出來,然后轉成點陣。這么做是可以的,只要通過 fonteditor-core 將文字的 glyf(輪廓)提出來,然后根據矢量轉成點陣即可。但是這么做發現在 32x32 下效果還可以,壓縮到16x16之后,文字嚴重失真。分析原因可能是現在的矢量字體并不為低分辨率設計,也可能是我用的壓縮算法有問題,不管怎么樣,這么做比較難實現。

既然這樣的話,那只能換一種思路,直接下載 16x16 點陣字庫。由于這個庫是 1998 年的,所以它的編碼是 GB2312,一部分 utf-8 中有的符號這個字庫里面沒有。由于是 GB2312 編碼,因此我們還得做 UTF-8 到 GB2312 的轉碼。幸運地是,iconv-lite 可以幫助我們做到。

要提取字庫中的文字,也很簡單,直接將字符的 unicode 通過 iconv-lite 轉成 gbk 編碼,然后查找對應的區位即可以定位文件中的位置,詳細的轉換方式可以參考這篇文章。查找到文字在文件中的位置后,將其后的 32 個字節輸出并轉成一個 16x16 的二維數組即可。用 Node.js 實現的代碼如下:

const fs = require("fs");
    const iconv = require("iconv-lite");
    const fontBuffer = fs.readFileSync(__dirname + "/HZK16");

    function readText(text){
      var ret = [];
      var gbkBytes = iconv.encode(text, "gbk");

      for(var i = 0; i < gbkBytes.length / 2; i++){
        var qh = gbkBytes[2 * i] - 0xa0;
        var wh = gbkBytes[2 * i + 1] - 0xa0;

        var offset = (94 * (qh - 1) + (wh - 1)) * 32;
        var buff = fontBuffer.slice(offset, offset+32);
        var font = [];

        for(var j = 0; j < 16; j++){
          var row = ("00000000" + buff[2 * j].toString(2)).slice(-8)
            + ("00000000" + buff[2 * j + 1].toString(2)).slice(-8);
          row = row.split("").map(c=>0|c);
          font.push(row);
        }
        ret.push(font);
      }

      return ret;
    }

    module.exports = {readText};

最終將文字顯示到點陣屏

接下來,將文字顯示到點陣屏就簡單了,方式就如前面的偽代碼那樣。

const Gpio = require("rpio2").Gpio;
    var latch = new Gpio(38);//鎖存器 
    var clk = new Gpio(40); //時鐘信號
    var red = new Gpio(36, true); //點陣輸出信號
    var oe = new Gpio(32); //使能信號
    var rows = Gpio.group([29,31,33,35]);

    rows.open(Gpio.OUTPUT, Gpio.LOW); 
    rows.value = 0; //選行
    latch.open(Gpio.OUTPUT, Gpio.HIGH); //鎖存輸出
    clk.open(Gpio.OUTPUT, Gpio.LOW);
    red.open(Gpio.OUTPUT, Gpio.HIGH);
    oe.open(Gpio.OUTPUT, Gpio.HIGH);
    rows.value = 0;

    const readText = require("./lib/font_matrix.js").readText;
    var pixels = readText("你好,世界!");
    var startTime = Date.now();

    while(1){
      var t = Math.floor((Date.now() - startTime) / 1000);

      data = pixels[t % pixels.length];  //1 秒鐘換一個字,循環播放

      for(var j = 0; j < 16; j++){    
        latch.value = 0;
        for(var i = 0; i < 16; i++){
          red.value = data[j][15 - i]; //注意輸入信號從右到左,所以左右要顛倒一下
          clk.value = 0;
          clk.value = 1;
        }
        oe.value = 1;
        rows.value = j;
        latch.value = 1;
        oe.value = 0;
      }
    }

這里面有一個地方要注意,那就是因為設備用的是鎖存器,所以輸入最早的信號出現在右側(可以將鎖存器想象為一個接收信號的堆棧,當堆棧裝滿了之后輸出,所以最先收到的信號在最后,因此輸入的時候要顛倒一下:red.value = data[j][15 - i])。

讓文字運動起來

上面的代碼,我們是讓點陣屏一秒鐘換一個字,循環播放"你好,世界!"。注意到生活中的廣告屏(比如公交車上的)都是能讓文字移動滾動的,這樣更生動一些。那么我們可不可以讓文字也滾動循環播放呢?答案當然是可以的。事實上我們很容易能實現這樣的動畫,實現后的顯示效果可以看這里。

具體怎么實現,留給各位小伙伴思考。想到或想不到的,都可以看我的實現代碼。

總結

通過實戰設計電路和編寫代碼驅動 LED 點陣屏,我們也重新復習或了解了數字電路中的時序和鎖頻輸入輸出(LATCH/SCK)的重要概念。利用樹莓派和 Node.js 可以很方便地設計硬件和軟件來實現有趣的功能。這個小小的點陣屏板,也可以用來做很多事情。有同事就希望可以將它放在車后面,然后用手機控制它輸出的文本內容,這個實際上是可以做到的,尤其是通過 WoT(Web of Things),我們可以將各種設備連接到一起。

有任何問題,歡迎留言討論~


所屬標簽

無標簽

25选5玩法中奖