2017年1月21日土曜日

【電子工作】 AUO B101EAN01 でHDMI LCD displayを作ってみる (完)

中華通販LCDでHDMIディスプレイを作ってみるシリーズの、今回は最終回です.

↓ラズパイPCの出来上がりということになりました.Raspberry-pi3でyoutubeの麻雀番組を見ているところ.フリーズなどせずにフツーに観れます.
↓裏面はこんなかんじ.左の基板はHDMI→LCD変換回路、右上はラズパイ3、左上の小さいのは5V→17VのDDコンでバックライト用.中央部の紫色の基板はピッチ変換基板.
ラズパイを除く消費電力は5V 650mAぐらいです.
HDMIは周波数の高い信号ですけど、このようなクソ配線でも意外と動くものです.差動伝送の恩恵かと思います.

今までの出費
(LCD部分)
  LCD                            ¥2000   ←安い!
  ピッチ変換基板+FPC   ¥1000   ←クソ高い!
  HDMIケーブル                ¥200
  FPGAボード                 ¥2600   ←クソ高い!
  DDコン                          ¥100

(PC部分)
  ラズパイ3                    ¥3400
  SDカード                      ¥1000
  無線KBD+MOUSE         ¥2200


ソースファイル
XILINX ISE13.3のprojectフォルダをupしときました.他バージョンのため開けなかった場合には、top.vがトップモジュールですので、適宜add sourceしてください.
全てのソースを解説するのもかったるいので、以下ではポイントだけ解説します.


FPGAブロック図
FPGAボード上のSpartan6で、HDMI-LCDのデータ変換をします.HDMI信号をTDMSで受信して、LCDパネルへの信号をLVDSで出力します.ブルーの四角はXILINXのcoreです.クロックは750M, 150M, 75M, 525MHzの4種類を使います.

シンボル抽出回路
HDMIケーブルから来るビットストリームの正体は、10bitシンボルの連続体です.ゆえにビットストリームを適切な位置で10bitにブツ切りする必要があります.ビットストリームを20bit shift registerに流し込んでやりますと、連続したいずれか10bitが正しいシンボルであるはずです.正しいシンボルの位置が一度決まれば、10bit時間間隔で同じ場所を切り出せば常に最新の受信シンボルを得られます.というのが原理です.

↓ソースコードではここらへんです.
// parallel RGB data
reg [19:0] r20,g20,b20;
always @(posedge clk150) begin
 r20 <= {r5,r20[19:5]} ;    20bitシフトレジスタ
 g20 <= {g5,g20[19:5]} ;    20bitシフトレジスタ
 b20 <= {b5,b20[19:5]} ;    20bitシフトレジスタ
end

// extract RGB symbols
reg [4:0] symbol_boundary_position;
reg [9:0] symbol_r, symbol_g, symbol_b;
always @(posedge clk75) begin
 {symbol_r, symbol_g, symbol_b} <=     適切な位置でシンボルをブツ切りする
symbol_boundary_position==19 ? {r20[19:10],g20[19:10],b20[19:10]} :
symbol_boundary_position==18 ? {r20[18: 9],g20[18: 9],b20[18: 9]} :
symbol_boundary_position==17 ? {r20[17: 8],g20[17: 8],b20[17: 8]} :
symbol_boundary_position==16 ? {r20[16: 7],g20[16: 7],b20[16: 7]} :
symbol_boundary_position==15 ? {r20[15: 6],g20[15: 6],b20[15: 6]} :
symbol_boundary_position==14 ? {r20[14: 5],g20[14: 5],b20[14: 5]} :
symbol_boundary_position==13 ? {r20[13: 4],g20[13: 4],b20[13: 4]} :
symbol_boundary_position==12 ? {r20[12: 3],g20[12: 3],b20[12: 3]} :
symbol_boundary_position==11 ? {r20[11: 2],g20[11: 2],b20[11: 2]} :
symbol_boundary_position==10 ? {r20[10: 1],g20[10: 1],b20[10: 1]} :
                              {r20[ 9: 0],g20[ 9: 0],b20[ 9: 0]} ;
end

SYNC検出回路
正しいシンボルの位置を決めるための回路です.上のソースコードで参照されている symbol_boundary_position という変数を決める役割です.
ビットストリームから探索するSYNCパターンは、垂直・水平同期期間でのみ出現する、1101010100 です.1101010100 は、1水平期間毎に必ず到来するはずなので、100uSec待っても1101010100 が到来しなければそれは、symbol_boundary_position が不正値である証しですから、symbol_boundary_position を+1します.1101010100 を受信できるまで+1を繰り返します.というのが原理です. (これがHDMIの通常の作法なのかは知りません)

↓ソースコードではここらへんです.
// sync pattern definitions (=control period symbols)
wire [9:0] sync0 = 10'b1101010100; // zero
wire sync = symbol_b==sync0;

// check sync
// 100uSec timer to detect H-SYNC
reg [12:0] timecnt;
always @(posedge clk75 or negedge xrst)
if(!xrst)     timecnt<=2;
else if(sync) timecnt<=2; // if sync then timer is initialized
else          timecnt<=timecnt+1;

// if no sync then change symbol boundary
always @(posedge clk75 or negedge xrst)
if(!xrst) symbol_boundary_position<=19;
else if(timecnt==0) // when missing sync
begin // change symbol boundary
       if(symbol_boundary_position==19) symbol_boundary_position<=18;
  else if(symbol_boundary_position==18) symbol_boundary_position<=17;
  else if(symbol_boundary_position==17) symbol_boundary_position<=16;
  else if(symbol_boundary_position==16) symbol_boundary_position<=15;
  else if(symbol_boundary_position==15) symbol_boundary_position<=14;
  else if(symbol_boundary_position==14) symbol_boundary_position<=13;
  else if(symbol_boundary_position==13) symbol_boundary_position<=12;
  else if(symbol_boundary_position==12) symbol_boundary_position<=11;
  else if(symbol_boundary_position==11) symbol_boundary_position<=10;
  else if(symbol_boundary_position==10) symbol_boundary_position<=9;
  else                                  symbol_boundary_position<=19;
  led0<=~led0; // LED indicatoro
end


HDMIデコーダーはとても簡単
最低限、RGBのデコーダーと、垂直・水平同期のデコーダーとを実装する必要があります.

まずRGBのデコーダーですけど、HDMI規格書の情報どおりのたったのこれだけです.10bitシンボルを入れると8bitバイトが出てくると、それだけです.
module hdmi_dec
(
input clk,
input [9:0] in,
output [7:0] out
);
wire [9:0] D = in;
wire [9:0] DD = D[9] ? {D[9:8],~D[7:0]} : D;
reg [7:0] Q;
always @(posedge clk)
if(DD[8]) begin
Q[0] <= DD[0];
Q[1] <= DD[1] ^ DD[0];
Q[2] <= DD[2] ^ DD[1];
Q[3] <= DD[3] ^ DD[2];
Q[4] <= DD[4] ^ DD[3];
Q[5] <= DD[5] ^ DD[4];
Q[6] <= DD[6] ^ DD[5];
Q[7] <= DD[7] ^ DD[6];
end
else begin
Q[0] <= DD[0];
Q[1] <= DD[1] ~^ DD[0];
Q[2] <= DD[2] ~^ DD[1];
Q[3] <= DD[3] ~^ DD[2];
Q[4] <= DD[4] ~^ DD[3];
Q[5] <= DD[5] ~^ DD[4];
Q[6] <= DD[6] ~^ DD[5];
Q[7] <= DD[7] ~^ DD[6];
end
assign out = Q;
endmodule

つぎに垂直・水平同期のデコーダーはこれです.4パターンとの一致チェックの結果、de,hd,vdを得るという仕組みです.deはRGB valid、hdは垂直同期、vdは水平同期、の意味です.
wire [9:0] sync0 = 10'b1101010100; // zero
wire [9:0] sync1 = 10'b0010101011; // hd
wire [9:0] sync2 = 10'b0101010100; // vd
wire [9:0] sync3 = 10'b1010101011; // vd,hd
reg hdmi_de,hdmi_hd,hdmi_vd;
always@(posedge clk75)
case(symbol_b)
   sync0 : {hdmi_de,hdmi_hd,hdmi_vd}<=3'b000;
   sync1 : {hdmi_de,hdmi_hd,hdmi_vd}<=3'b010; // hd
   sync2 : {hdmi_de,hdmi_hd,hdmi_vd}<=3'b001; // vd
   sync3 : {hdmi_de,hdmi_hd,hdmi_vd}<=3'b011; // vd,hd
   default:{hdmi_de,hdmi_hd,hdmi_vd}<=3'b100;
endcase


制約は大事です
まずは、TDMSの入力ピンのロケーションと論理レベルの設定のところ.
・下記のロケーションならビルドが通りますけど、近所のピンに変えると「配線リソースがありません」というエラーが出たりするので注意が必要と思われます.
・「=FALSE」の部分は、クロックバッファの自動挿入をするなと命じています.クロックバッファはPLL coreに挿入されるので衝突を防ぐためです.
・VCCAUXは、デフォルトでは2.5Vなのですが、TDMSならば3.3Vである必要があります.
NET "clkm" LOC = "P9" ; //| DIFF_TERM = TRUE ;
NET "clkp" LOC = "P10" ; //| DIFF_TERM = TRUE ;  # HDMI clock
NET "bm"   LOC = "P16" ; //| DIFF_TERM = TRUE ;
NET "bp"   LOC = "P17" ; //| DIFF_TERM = TRUE ;
NET "gm"   LOC = "P21" ; //| DIFF_TERM = TRUE ;
NET "gp"   LOC = "P22" ; //| DIFF_TERM = TRUE ;
NET "rm"   LOC = "P23" ; //| DIFF_TERM = TRUE ;
NET "rp"   LOC = "P24" ; //| DIFF_TERM = TRUE ;
NET "clk?" IOSTANDARD = "TMDS_33" ;
NET "r?"   IOSTANDARD = "TMDS_33" ;
NET "g?"   IOSTANDARD = "TMDS_33" ;
NET "b?"   IOSTANDARD = "TMDS_33" ;
NET "clkp" CLOCK_DEDICATED_ROUTE = FALSE ;
NET "clkm" CLOCK_DEDICATED_ROUTE = FALSE ;
CONFIG VCCAUX = "3.3";  # needed for TMDS_33

次にclock制約はtoolで自動生成させた結果です.
・clk50はオンボードXTALですが、最終的に使わなかった
・clkp/clkmは12ns=83MHzで制約しておきました
・グループHDMI_DATAは、1ns before clkp にしました.前回にも書きましたが、仕様が不明なのでこれで動いたからこれでいいや、という設定です.
・グループLCD_SIGNALSは、1ns after clkp にしました.前回にも書きましたが、仕様が不明なのでこれで動いたからこれでいいや、という設定です.
NET "clk50" TNM_NET = clk50;
TIMESPEC TS_clk50 = PERIOD "clk50" 20 ns HIGH 50%;
NET "clkp" TNM_NET = clkp;
TIMESPEC TS_clkp = PERIOD "clkp" 12 ns HIGH 50%;
NET "clkm" TNM_NET = clkm;
TIMESPEC TS_clkm = PERIOD "clkm" 12 ns LOW 50%;
INST "gm" TNM = HDMI_DATA;
INST "gp" TNM = HDMI_DATA;
INST "bm" TNM = HDMI_DATA;
INST "bp" TNM = HDMI_DATA;
INST "rm" TNM = HDMI_DATA;
INST "rp" TNM = HDMI_DATA;
TIMEGRP "HDMI_DATA" OFFSET = IN 1 ns BEFORE "clkp" RISING;
INST "lvo_0m" TNM = LCD_SIGNALS;
INST "lvo_0p" TNM = LCD_SIGNALS;
INST "lvo_1m" TNM = LCD_SIGNALS;
INST "lvo_1p" TNM = LCD_SIGNALS;
INST "lvo_2m" TNM = LCD_SIGNALS;
INST "lvo_2p" TNM = LCD_SIGNALS;
INST "lvo_3m" TNM = LCD_SIGNALS;
INST "lvo_3p" TNM = LCD_SIGNALS;
INST "lvo_clkm" TNM = LCD_SIGNALS;
INST "lvo_clkp" TNM = LCD_SIGNALS;
TIMEGRP "LCD_SIGNALS" OFFSET = OUT 20 ns AFTER "clkp";


-----
設計上のキモのところは以上です.

なお、前回も書きましたが、windows PCに接続しても動きませんでした.PC-LCDのネゴシエーションを実装してないからだと思っています.

読者の皆さんが色々なLCDパネルをいじり倒していただく参考になれば幸いです.なお、上記情報に誤りが在った結果貴方が蒙った如何なる損害もわたしは関知しませんので素直に死んで頂戴ませ.それと、HDMIって使用にあたってはライセンス料の支払いが必要だと思うので、商売で無断で使うのはどうかと思うよ、と優等生ぶってみたりしておこう.

前回へ

かしこ

0 件のコメント:

コメントを投稿