文章目錄
- 一、WS2812手冊分析
- 1.1 WS2812燈源特性及概述
- 1.2 手冊重點內容分析
- 1.2.1 產品概述
- 1.2.2 碼型及24bit數據設計
- 二、系統設計
- 2.1 模塊設計
- 2.2 模塊分析
- 2.2.1 驅動模塊
- 2.2.1 數據控制模塊
- 三、IP核設置及項目源碼
- 3.1 MIF文件設計
- 3.2 ROM IP核調用
- 3.3 FIFO IP核調用
- 3.4 項目各模塊源碼
- 四、最終顯示效果
- 五、總結
一、WS2812手冊分析
1.1 WS2812燈源特性及概述
1.2 手冊重點內容分析
1.2.1 產品概述
由產品概述可以得到的重要信息有:
- 該燈源數據協議采用單線歸零碼的通訊方式
- 一個像素點需要24bit數據才能正常工作(該燈板共有8×8 64個像素點)
- 復位時間需要至少280us
- 傳輸數據每經過一個像素點便會被鎖存24bit數據,因此數據會逐級減少
- 數據發送速度最高為800Kbps
1.2.2 碼型及24bit數據設計
由上述手冊截圖可以看出,該光源0碼和1碼占空比并不相同,同時一個碼元持續時間也可以不同,因此在設計碼型時,一個碼元持續時間需要同時滿足0碼和1碼。
數據傳輸方法與前文概述所說一樣,沒經過一個像素點便會被鎖存24bit數據,然后繼續逐級傳輸,同時每一次24bit數據傳輸結束需要經過經過至少280us的復位才能繼續傳輸下一個24bit數據。
同時該光源所需24bit數據結構為GRB順序,在設計時需要將RGB數據進行重新拼接,該拼接過程可以在傳入數據時,也可以在傳出數據時。
二、系統設計
2.1 模塊設計
設計本項目時,建議將模塊劃分為:驅動模塊,控制模塊,以及頂層模塊。
- 在控制模塊中設計不同數據的輸入
- 在驅動模塊中設計碼型以及輸入數據的拼接及輸出
- 頂層模塊僅進行邏輯連線,不建議在頂層模塊進行任何邏輯編寫
2.2 模塊分析
2.2.1 驅動模塊
在驅動模塊中,首先我們主要考慮的是64個24bit數據的傳輸。
-
由上述分析可知,每24bit數據傳輸間隔中我們需要至少280us的復位,復位結束才能傳輸下一個24bit數據,因此我們可以設計一個狀態機實現數據傳輸與復位的狀態切換。
-
同時我們可以設計一個ready使能信號,將該信號傳輸給控制模塊,該信號拉高后代表驅動模塊處于空閑狀態,可以進入復位狀態。
-
同時我們可以在控制模塊設計一個data_vld信號,將該信號輸入驅動模塊,該信號拉高驅動模塊應該立即進入復位狀態,并在復位結束后即刻準備接受數據。
-
再由手冊的介紹可知,在該模塊我們至少需要四個計數器,分別為:
-
- 24bit數據計數器,計數從控制模塊傳來的數據量。
-
- 64個像素點計數器,該燈板共有8×8 64個像素點,每個像素點均需要24bit數據。
-
- 一個碼元持續時間計數器,由于該光源1碼和0碼要求時間可以不同,因此我們需要取一個能同時滿足0碼和1碼的值作為一個碼元的傳輸時間。博主的一個碼元所需傳輸時間設置為1200ns(既能滿足0碼(220ns-380ns的高電平時間以及580ns-1us的低電平時間)也能滿足1碼高低電平時間(均為580ns-1us))。
-
- 復位時間計數器,至少為280us,博主設計為400us。
-
同時,由于該光源數據格式為GRB,因此我們還需要在驅動模塊對傳入的RGB數據重新進行拼接。
-
此外,由于時鐘數據采用頻率為50MHz,而該光源發送速率只有800Kbps,傳入數據的速度遠大于傳出數據的速度,因此博主調用了一個FIFO核,用來臨時存儲傳入驅動模塊的數據,避免造成數據丟失。
該模塊狀態機設計如下:
其中,end_cnt_rst為復位計數器結束信號,end_cnt_pix為64個像素點計數器結束信號。
2.2.1 數據控制模塊
在該模塊我們所需要的考慮僅為所需數據的存儲以及如何傳入驅動模塊。
- 為了存儲我們設計好的數據,博主在該模塊調用了一個ROM IP核。
- 同時博主在該模塊設計了X,Y兩個計數器,計數最大值均為8。將X,Y作為光源像素點的坐標從而確定ROM中所要取得的數據。
- 為了方便設計并存儲數據,在調用ROM時需要設計MIF文件初始化ROM,在后文博主會將自己設計MIF文件的方式和工具分享給大家。
三、IP核設置及項目源碼
3.1 MIF文件設計
打開系統自帶的畫圖工具,點擊左上角文件,選擇圖像屬性
在圖像屬性中設置圖像為 32×8(大家可以根據自己所需自由設計寬度(如果自定義寬度,就需要在ROM核設置的時候進行更改,同時需要在博主的數據控制模塊進行一個小細節的更改,博主會在后文標明),但高度固定因為動態顯示相當于滾動移動寬度,無法移動高度)
然后在畫圖中設計自己想要顯示的圖案即可
設計完成后點擊右上角文件另存為24位寬BMP文件,注意必須為24位寬
然后利用格式轉換工具,將該BMP文件轉化為mif文件
然后將生成的文件存放在自己創建的quartus prj文件夾下
3.2 ROM IP核調用
在quartus IP Catalog中搜索ROM并選擇單端口ROM
按照下圖進行設置:
- 輸出數據設置為24bit寬
- 深度設置為256,若BMP圖像高度不是博主設置的32,各位需要自行更改所需深度,將其改為:你所設置的寬度×8
點擊next,按下圖進行如下設置:
- 將輸出q寄存一拍(勾選則在后續讀使能需要打兩拍,博主個人習慣,可以不選,后續只需打一拍)
- 勾選異步復位信號
- 勾選讀使能信號
點擊next按照下圖進行如下設置:
- 點擊Browse選擇前文所設置的MIF文件,對ROM進行初始化
一路next至總結界面:
- 勾選例化模板
最終設置如下:
點擊finish完成ROM IP核配置
3.3 FIFO IP核調用
在IP Catalog中搜索FIFO,進入配置選項卡
- 數據寬度同樣為24bit,深度同ROM
- 由于本項目為跨時鐘域,因此FIFO讀寫選擇單時鐘即可
點擊next,按下圖進行設置:
- 空、滿信號可用于確定讀寫請求
- usew可在仿真時看到FIFO中的數據個數(此處不勾選,不仿真用不到)
- 勾選異步復位信號
點擊next,按下圖進行配置:
- 勾選前顯模式
一路next至總結界面:
- 勾選例化模板
FIFO總體配置如下:
3.4 項目各模塊源碼
驅動模塊:
module ws2812_driver (input wire clk ,input wire rst_n ,input wire [23:0] pix_data ,//輸入數據input wire pix_data_vld,//輸入數據有效信號output wire ready ,output reg ws2812_io //輸出碼元信號
);//內部參數定義//狀態參數定義parameter IDLE = 3'b001 ,RST = 3'b010 ,DATA = 3'b100 ;//碼元電平參數定義parameter T0H = 300/20 ,T0L = 900/20 ,T1H = 600/20 ,T1L = 600/20 ;//復位碼元計數參數parameter RST_MAX = 14'd15_000;//內部信號定義//現態、次態寄存器reg [2:0] cstate ;reg [2:0] nstate ;//跳轉條件定義wire idle2rst ;wire rst2data ;wire data2idle ;//一個碼元時間計數器reg [5:0] cnt_time ;wire add_cnt_time ;wire end_cnt_time ;//24bit計數器reg [4:0] cnt_bit ;wire add_cnt_bit ;wire end_cnt_bit ;//64個數據計數器reg [5:0] cnt_pix ;wire add_cnt_pix ;wire end_cnt_pix ;//復位碼元計數器reg [13:0] cnt_reset ;wire add_cnt_reset ;wire end_cnt_reset ;//FIFO信號定義wire [23:0] fifo_wr_data ;wire fifo_rd_req ;wire fifo_wr_req ;wire fifo_empty ;wire fifo_full ;wire [23:0] fifo_rd_data ;wire [7:0] fifo_usedw ;//FIFO例化fifo fifo_inst (.aclr ( ~rst_n ),.clock ( clk ),.data ( fifo_wr_data ),.rdreq ( fifo_rd_req ),.wrreq ( fifo_wr_req ),.empty ( fifo_empty ),.full ( fifo_full ),.q ( fifo_rd_data ),.usedw ( fifo_usedw ));assign fifo_wr_data = {pix_data[15:8],pix_data[23:16],pix_data[7:0]};//將傳入的24bit RGB數據轉為GRB順序//24'hff0000;assign fifo_wr_req = pix_data_vld && ~fifo_full;//1'b1 && ~fifo_full;//assign fifo_rd_req = end_cnt_bit && ~fifo_empty;//三段式狀態機//第一段always @(posedge clk or negedge rst_n) beginif(!rst_n)begincstate <= IDLE;endelse begincstate <= nstate;endend//第二段組合邏輯always@(*)begincase(cstate)IDLE : beginif(idle2rst)beginnstate = RST;endelse beginnstate = cstate;endendRST : beginif(rst2data)beginnstate = DATA;endelse beginnstate = cstate;endendDATA : beginif(data2idle)beginnstate = IDLE;endelse beginnstate = cstate;endenddefault : nstate <= cstate;endcaseendassign idle2rst = cstate == IDLE && pix_data_vld ;//1'b1;//assign rst2data = cstate == RST && end_cnt_reset ;assign data2idle = cstate == DATA && end_cnt_pix ;//碼元最低持續時間計數器always @(posedge clk or negedge rst_n)begin if(!rst_n)begincnt_time <= 'd0;end else if(add_cnt_time)begin if(end_cnt_time)begin cnt_time <= 'd0;endelse begin cnt_time <= cnt_time + 1'b1;end endend assign add_cnt_time = cstate == DATA;assign end_cnt_time = add_cnt_time && cnt_time == 6'd59;//一位數據的24bit數計數器always @(posedge clk or negedge rst_n)begin if(!rst_n)begincnt_bit <= 'd0;end else if(add_cnt_bit)begin if(end_cnt_bit)begin cnt_bit <= 'd0;endelse begin cnt_bit <= cnt_bit + 1'b1;end endend assign add_cnt_bit = end_cnt_time;assign end_cnt_bit = add_cnt_bit && cnt_bit == 5'd23;//64個數據計數器always @(posedge clk or negedge rst_n)begin if(!rst_n)begincnt_pix <= 'd0;end else if(add_cnt_pix)begin if(end_cnt_pix)begin cnt_pix <= 'd0;endelse begin cnt_pix <= cnt_pix + 1'b1;end endend assign add_cnt_pix = end_cnt_bit;assign end_cnt_pix = add_cnt_pix && cnt_pix == 6'd63;//復位計數器always @(posedge clk or negedge rst_n)begin if(!rst_n)begincnt_reset <= 'd0;end else if(add_cnt_reset)begin if(end_cnt_reset)begin cnt_reset <= 'd0;endelse begin cnt_reset <= cnt_reset + 1'b1;end endend assign add_cnt_reset = cstate == RST;assign end_cnt_reset = add_cnt_reset && cnt_reset == RST_MAX - 1'b1;//第三段 狀態機輸出always@(*)begincase(cstate)IDLE : ws2812_io = 1'b0;RST : ws2812_io = 1'b0;DATA : beginif(fifo_rd_data[23 - cnt_bit])beginif(cnt_time < T1H)beginws2812_io = 1'b1;endelse beginws2812_io = 1'b0;endendelse beginif(cnt_time < T0H)beginws2812_io = 1'b1;endelse beginws2812_io = 1'b0;endendenddefault : ws2812_io = 1'b0;endcaseend //ready使能信號賦值
assign ready = cstate == IDLE;endmodule
數據控制模塊:
/**************************************************************
@File : ws2812_control.v
@Time : 2023/08/11 15:09:52
@Author : majiko
@EditTool: VS Code
@Font : UTF-8
@Function: 提供64個RGB像素數據,給到ws2812接口模塊,用于驗證接口模塊
**************************************************************/
module ws2812_ctrl2(input clk ,input rst_n ,input ready ,//可以接收圖像數據了output [23:0] pix_data ,output pix_data_vld );parameter IDLE = 0,DATA = 1,DELAY = 2;parameter DELAY_TIME = 25'd25_000_000;wire rom_rd_req ;reg rom_rd_req1 ;reg rom_rd_req2 ;wire rom_rd_data ;reg [2:0] state ;reg [5:0] cnt_x ;wire add_x_cnt;wire end_x_cnt; reg [4:0] cnt_y ;wire add_y_cnt;wire end_y_cnt;reg [4:0] cnt_offset ;wire add_cnt_offset ;wire end_cnt_offset ;reg [24:0] cnt_delay ;wire add_cnt_delay ;wire end_cnt_delay ; // localparam RED = 24'hFF0000, //紅色// ORANGE = 24'hFF8000, //橙色// YELLOW = 24'hFFFF00, //黃色// GREEN = 24'h00FF00, //綠色// CYAN = 24'h00FFFF, //青色// BLUE = 24'h0000FF, //藍色// PURPPLE = 24'h8000FF, //紫色// BLACK = 24'h000000, //黑色// WHITE = 24'hFFFFFF, //白色// GRAY = 24'hC0C0C0; //灰色/**************************************************************狀態機
**************************************************************/always@(posedge clk or negedge rst_n)if(!rst_n)state <= IDLE;else case(state)IDLE : if(ready)state <=DATA;DATA : if(end_y_cnt)state <=DELAY;DELAY : if(end_cnt_delay)state <=IDLE;default : state <= IDLE;endcase//幀間隔計數器always @(posedge clk or negedge rst_n)begin if(!rst_n)begincnt_delay <= 'd0;end else if(add_cnt_delay)begin if(end_cnt_delay)begin cnt_delay <= 'd0;endelse begin cnt_delay <= cnt_delay + 1'b1;end endend assign add_cnt_delay = state == DELAY;assign end_cnt_delay = add_cnt_delay && cnt_delay == DELAY_TIME - 1;/**************************************************************圖像數據個數計數器
**************************************************************/ always@(posedge clk or negedge rst_n) if(!rst_n) cnt_x <= 'd0; else if(add_x_cnt) begin if(end_x_cnt) cnt_x <= 'd0; else cnt_x <= cnt_x + 1'b1; end assign add_x_cnt = state == DATA;assign end_x_cnt = add_x_cnt && cnt_x == 8 - 1;always@(posedge clk or negedge rst_n) if(!rst_n) cnt_y <= 'd0; else if(add_y_cnt) begin if(end_y_cnt) cnt_y <= 'd0; else cnt_y <= cnt_y + 1'b1; end assign add_y_cnt = end_x_cnt;assign end_y_cnt = add_y_cnt && cnt_y == 8 - 1;//偏移計數器always @(posedge clk or negedge rst_n)begin if(!rst_n)begincnt_offset <= 'd0;end else if(add_cnt_offset)begin if(end_cnt_offset)begin cnt_offset <= 'd0;endelse begin cnt_offset <= cnt_offset + 1'b1;end endend assign add_cnt_offset = end_cnt_delay;assign end_cnt_offset = add_cnt_offset && cnt_offset == 5'd23;// assign pix_data_vld = add_x_cnt;// always@(*)// case(cnt_y)// 0 : pix_data = RED ;// 1 : pix_data = ORANGE ;// 2 : pix_data = YELLOW ;// 3 : pix_data = GREEN ;// 4 : pix_data = CYAN ;// 5 : pix_data = BLUE ;// 6 : pix_data = PURPPLE ;// 7 : pix_data = GRAY ;// default : pix_data = RED ;// endcasewire [4:0] real_row ;assign real_row = cnt_x + cnt_offset;rom rom_inst (.aclr ( ~rst_n ),.address ( cnt_y*32 + real_row),.clock ( clk ),.rden ( rom_rd_req ),.q ( pix_data ));//ROM讀請求打兩拍always@(posedge clk or negedge rst_n)beginif(!rst_n)beginrom_rd_req1 <= 1'b0;rom_rd_req2 <= 1'b0;endelse beginrom_rd_req1 <= rom_rd_req;rom_rd_req2 <= rom_rd_req1;endendassign rom_rd_req = state == DATA;assign rom_rd_data = rom_rd_req2 ;assign pix_data_vld = rom_rd_data ;endmodule
頂層模塊:
module ws2812_top(input wire clk ,input wire rst_n ,output wire ws2812_io
);wire [23:0] pix_data ;
wire ready ;
wire pix_data_vld;//模塊例化
//顯示圖片
// ws2812_ctrl u_ws2812_ctrl(// .clk (clk ),// .rst_n (rst_n ),// .pix_data (pix_data ),// .pix_data_vld (pix_data_vld ),// .ready (ready )
// );//動態顯示圖片
ws2812_ctrl2 u_ws2812_ctrl2(.clk (clk ),.rst_n (rst_n ),.pix_data (pix_data ),.pix_data_vld (pix_data_vld ),.ready (ready )
);interface u_interface(.clk (clk ),.rst_n (rst_n ),.pix_data (pix_data ),.pix_data_vld (pix_data_vld),.ready (ready ),.ws2812_io (ws2812_io )
);endmodule
四、最終顯示效果
引腳綁定如下:
實現效果:
五、總結
博主本人為FPGA初學者,因此才會寫下此篇博客作為自己對WS2812的一個總結,如果有和博主一樣的初學者,希望此博文能夠幫助到你。
這也是博主第一次利用FPGA驅動外設,第一次設計一個較為簡單的單總線通訊協議,盡管很大程度博主都是依靠著博主老師的代碼才完成的。
如果此篇博文有任何錯誤,還請各位提出!