前言
最早做了基于FPGA:运动目标检测(VGA显示,原理图+源码+硬件选择),有网友反应,VGA一个大大的屏幕,做起来很不方便,并且功能过于单一。
因此,在上个工程的基础上,修改成了TFT-LCD屏幕检测,并且将检测结果通过串口输出到电脑上位机上,以便大家做扩展开发。
一、先看效果
话不多说,先上视频看效果。
基于FPGA运动目标检测(LCD显示-串口输出)
二、硬件选择
开发板Altera:EP4CE6F17C8(兼容E10)
摄像头:OV5640
屏幕:TFT-LCD
缓存数据:SDRAM
板子是自制的
三、系统框架
本系统在图像采集之前,由于板载晶振与摄像头模块及 TFT-LCD 模块频率不一致,所以在系统工作之前,需要设计锁相环模块输出与OV5640摄像头一致的时钟信号。时钟输入后,根据 OV5640 的手册进行寄存器配置,使 OV5640 的工作模式符合本系统的设计需求。CMOS 传感器采集进来的图像进入到图像处理模块,输出当前帧的灰度,用于写入SDRAM。同时从SDRAM中读出前一帧的灰度(与当前输出相差一个时钟周期)重新进入图像处理模块进行帧差法的图像处理(即运动目标检测相关的处理)。
在本系统中,满足选择功能的情况下,更需要满足实时性,所以选择只输入一路视频流。并且根据时钟域不同,设计了异步 FIFO 完成数据传输缓存。最后将处理结果通过 ILI9488 驱动模块显示在屏幕上,并将检测信息通过串口输出到上位机上。
四、程序模块
代码框架:本设计基本使用纯Verilog设计,运用了较少的IP,如:PLL,FIFO。代码附带注释,可读性强,便于大家移植,参加竞赛、毕设。
部分模块仿真:
1、系统顶层模块
module dmk_photo_uart(
input clk,
input rst_n, //KEY4(o) 即OK按键作为复位
//CMOS Port
inout cmos_scl, //cmos i2c clock
inout cmos_sda, //cmos i2c data
input cmos_vsync, //cmos vsync
input cmos_href, //cmos hsync refrence,data valid
input cmos_pclk, //cmos pxiel clock
output cmos_xclk, //cmos externl clock
input [7:0] cmos_db, //cmos data
output cmos_rst_n, //cmos reset
output cmos_pwdn, //cmos power down
//LCD ILI9488 Port
output WR,
output RD,
output CS,
output RS,
output BL_cnt,
output [15:0] data,
output RESET,
//LED
output [3:0] led,
//uart
input uart_rxd, //uart 串行数据接收
output uart_txd, //uart 串行数据发送
//SDRAM Port
output sdram_clk, //sdram clock
output sdram_cke, //sdram clock enable
output sdram_cs_n, //sdram chip select
output sdram_we_n, //sdram write enable
output sdram_cas_n, //sdram column address strobe
output sdram_ras_n, //sdram row address strobe
output[1:0] sdram_dqm, //sdram data enable
output[1:0] sdram_ba, //sdram bank address
output[12:0] sdram_addr, //sdram address
inout[15:0] sdram_dq //sdram data
);
// parameter CMOS_H_PIXEL = 24'd640 ; //CMOS水平方向像素个数,用于设置SDRAM缓存大小
// parameter CMOS_V_PIXEL = 24'd480 ; //CMOS垂直方向像素个数,用于设置SDRAM缓存大小
parameter CMOS_H_PIXEL = 24'd480 ; //CMOS水平方向像素个数,用于设置SDRAM缓存大小
parameter CMOS_V_PIXEL = 24'd320 ; //CMOS垂直方向像素个数,用于设置SDRAM缓存大小
parameter Diff_Threshold = 8'd40 ; //帧差检测阈值
assign cmos_rst_n = 1'b1;
assign cmos_pwdn = 1'b0;
wire clk_ref;
wire clk_refout;//sdram控制器100M工作时钟、给SDRAM芯片的100M时钟
wire clk_lcd_w; //LCD驱动模块工作时钟,12.5MHz
wire pll_lock_w;
wire sys_rst_n;
wire lcd_init_done;
//PLL模块
sys_pll u_sys_pll_0(
.areset (!rst_n),
.inclk0 (clk),
.c0 (clk_ref ),//sdram控制器100M 工作时钟
.c1 (clk_refout),//SDRAM芯片 100M 时钟
.c2 (cmos_xclk),//CMOS XCLK 24M 时钟
.c3 (clk_lcd_w),//LCD驱动模块工作时钟,12.5MHz
.locked (pll_lock_w)
);
//延迟复位模块
delay_reset u_delay_reset_0(
.clk (clk),
.rst_n (rst_n && pll_lock_w && lcd_init_done),
.reset_n (sys_rst_n)
);
//摄像头I2C配置模块
ov5640_config #(
.CMOS_H_PIXEL (CMOS_H_PIXEL),
.CMOS_V_PIXEL (CMOS_V_PIXEL)
) u_ov5640_config_0(
.rst_n (sys_rst_n),
.clk (clk),
.i2c_scl (cmos_scl),
.i2c_sda (cmos_sda)
);
wire cmos_frame_vsync;
wire cmos_frame_href;
wire cmos_frame_clken;
wire [15:0] cmos_frame_data;
//CMOS图像数据采集模块
cmos_capture_data u_cmos_capture_data( //系统初始化完成之后再开始采集数据
.rst_n (sys_rst_n && sdram_init_done),
.cam_pclk (cmos_pclk),
.cam_vsync (cmos_vsync),
.cam_href (cmos_href),
.cam_data (cmos_db),
.cmos_frame_vsync (cmos_frame_vsync ),
.cmos_frame_href (cmos_frame_href ),
.cmos_frame_valid (cmos_frame_clken ), //数据有效使能信号
.cmos_frame_data (cmos_frame_data ) //有效数据
);
//----------------------------------------------------
//Video Image processor module.
wire per_frame_vsync = cmos_frame_vsync;
wire per_frame_href = cmos_frame_href;
wire per_frame_clken = cmos_frame_clken;
wire [7:0] per_img_red = {cmos_frame_data[15:11], cmos_frame_data[15:13]};
wire [7:0] per_img_green = {cmos_frame_data[10:5], cmos_frame_data[10:9]};
wire [7:0] per_img_blue = {cmos_frame_data[4:0], cmos_frame_data[4:2]};
wire post_frame_vsync;
wire post_frame_href;
wire post_frame_clken;
wire post_img_Bit;
wire [7:0] post_img_red;
wire [7:0] post_img_green;
wire [7:0] post_img_blue;
wire [15:0] post_frame_data ;
assign post_frame_data = {post_img_red[7:3],post_img_green[7:2],post_img_blue[7:3]} ;
wire YCbCr_frame_vsync ;
wire YCbCr_frame_href ;
wire YCbCr_frame_clken ;
wire [7:0] YCbCr_img_Y_current; //当前帧灰度
wire [15:0] YCbCr_img_Y_pre; //前一帧灰度
wire target_detect;
Video_Image_Processor #(
.IMG_HDISP (CMOS_H_PIXEL),
.IMG_VDISP (CMOS_V_PIXEL)
) u_Video_Image_Processor
(
//global clock
.clk (cmos_pclk),
.rst_n (sys_rst_n && sdram_init_done),
//Image data prepred to be processd
.per_frame_vsync (per_frame_vsync),
.per_frame_href (per_frame_href),
.per_frame_clken (per_frame_clken),
.per_img_red (per_img_red),
.per_img_green (per_img_green),
.per_img_blue (per_img_blue),
//RGB2YCbCr output
.YCbCr_frame_vsync (YCbCr_frame_vsync),
.YCbCr_frame_href (YCbCr_frame_href ),
.YCbCr_frame_clken (YCbCr_frame_clken),
.YCbCr_img_Y_current (YCbCr_img_Y_current),
.YCbCr_img_Y_pre (YCbCr_img_Y_pre[7:0]),
//Image data has been processd
.post_frame_vsync (post_frame_vsync),
.post_frame_href (post_frame_href),
.post_frame_clken (post_frame_clken),
.post_img_red (post_img_red ),
.post_img_green (post_img_green),
.post_img_blue (post_img_blue ),
//User interface
.Diff_Threshold (Diff_Threshold),
.target_detect (target_detect)
);
//sdram read & write
wire wr1_wrreq;
wire [15:0] wr1_data;
wire rd1_rdreq;
wire [15:0] rd1_data;
assign wr1_wrreq = YCbCr_frame_clken;
assign wr1_data = {8'd0,YCbCr_img_Y_current};
assign rd1_rdreq = YCbCr_frame_clken;
assign YCbCr_img_Y_pre = rd1_data;
//摄像头采集模块,dvp转avalon-st裸流
wire [15:0] data_w0;
wire valid_w0,ready_w0,sop_w0,eop_w0;
cmos_to_st_top #(
.WIDTH (CMOS_H_PIXEL),
.HEIGHT (CMOS_V_PIXEL),
.DWIDTH (16)
) u_cmos_to_st_top(
.clk (clk),
.rst_n (sys_rst_n && sdram_init_done),
.cmos_pclk (cmos_pclk),
.cmos_vsync (post_frame_vsync ),
.cmos_href (post_frame_href ),
.cmos_clken (post_frame_clken ),
.cmos_data (post_frame_data ),
// .cmos_vsync (YCbCr_frame_vsync ),
// .cmos_href (YCbCr_frame_href ),
// .cmos_clken (YCbCr_frame_clken ),
// .cmos_data ( {YCbCr_img_Y_current[7:3],YCbCr_img_Y_current[7:2],YCbCr_img_Y_current[7:3]} ),
.source_sop (sop_w0),
.source_valid (valid_w0),
.source_data (data_w0),
.source_eop (eop_w0),
.source_ready (ready_w0)
);
//TFTLCD模块
vip_ILI9488 u_vip_ILI9488_0(
.clk (clk),
.clk12p5M (clk_lcd_w),
.rst_n (rst_n),
//Avalon-ST Sink
.sink_sop (sop_w0),
.sink_valid (valid_w0),
.sink_data (data_w0),
.sink_eop (eop_w0),
.sink_ready (ready_w0),
.lcd_intdone (lcd_init_done),
//TFTLCD interface
.WR (WR),
.RD (RD),
.CS (CS),
.RS (RS),
.BL_cnt (BL_cnt),
.data (data),
.RESET (RESET)
);
wire wr_full_1/*synthesis keep*/;
wire wr_full_2;
wire rd_empty_1;
wire rd_empty_2/*synthesis keep*/;
//assign led = {wr_full_1,1'b0,1'b0,rd_empty_1};
//4port SDRAM控制器模块
Sdram_Control_4Port Sdram_Control_4Port(
.REF_CLK (clk_ref),
.OUT_CLK (clk_refout),
.RESET_N (sys_rst_n), //复位输入,低电平复位
.WR1_DATA (wr1_data), //写入端口1的数据输入端,16bit
.WR1 (wr1_wrreq), //写入端口1的写使能端,高电平写入
.WR1_ADDR (0), //写入端口1的写起始地址
.WR1_MAX_ADDR (CMOS_H_PIXEL*CMOS_V_PIXEL), //写入端口1的写入最大地址
.WR1_LENGTH (256), //一次性写入数据长度
.WR1_LOAD (~sys_rst_n), //写入端口1清零请求,高电平清零写入地址和fifo
.WR1_CLK (cmos_pclk ), //写入端口1 fifo写入时钟
.WR1_FULL (wr_full_1), //写入端口1 fifo写满信号
.WR1_USE (), //写入端口1 fifo已经写入的数据长度
.WR2_DATA ( ), //写入端口2的数据输入端,16bit
.WR2 (0), //写入端口2的写使能端,高电平写入
.WR2_ADDR (CMOS_H_PIXEL*CMOS_V_PIXEL), //写入端口2的写起始地址
.WR2_MAX_ADDR (CMOS_H_PIXEL*CMOS_V_PIXEL*2), //写入端口2的写入最大地址
.WR2_LENGTH (256), //一次性写入数据长度
.WR2_LOAD (~sys_rst_n), //写入端口2清零请求,高电平清零写入地址和fifo
.WR2_CLK (clk), //写入端口2 fifo写入时钟
.WR2_FULL (wr_full_2), //写入端口2 fifo写满信号
.WR2_USE (), //写入端口2 fifo已经写入的数据长度
.RD1_DATA (rd1_data), //读出端口1的数据输出端,16bit
.RD1 (rd1_rdreq), //读出端口1的读使能端,高电平读出
.RD1_ADDR (0), //读出端口1的读起始地址
.RD1_MAX_ADDR (CMOS_H_PIXEL*CMOS_V_PIXEL), //读出端口1的读出最大地址
.RD1_LENGTH (256), //一次性读出数据长度
.RD1_LOAD (~sys_rst_n), //读出端口1 清零请求,高电平清零读出地址和fifo
.RD1_CLK (cmos_pclk), //读出端口1 fifo读取时钟
.RD1_EMPTY (rd_empty_1), //读出端口1 fifo读空信号
.RD1_USE (), //读出端口1 fifo已经还可以读取的数据长度
.RD2_DATA (), //读出端口2的数据输出端,16bit
.RD2 (0), //读出端口2的读使能端,高电平读出
.RD2_ADDR (CMOS_H_PIXEL*CMOS_V_PIXEL), //读出端口2的读起始地址
.RD2_MAX_ADDR (CMOS_H_PIXEL*CMOS_V_PIXEL*2), //读出端口2的读出最大地址
.RD2_LENGTH (256), //一次性读出数据长度
.RD2_LOAD (~sys_rst_n), //读出端口2清零请求,高电平清零读出地址和fifo
.RD2_CLK (clk), //读出端口2 fifo读取时钟
.RD2_EMPTY (rd_empty_2), //读出端口2 fifo读空信号
.RD2_USE (), //读出端口2 fifo已经还可以读取的数据长度
.SA (sdram_addr), //SDRAM 地址线,
.BA (sdram_ba), //SDRAM bank地址线
.CS_N (sdram_cs_n), //SDRAM 片选信号
.CKE (sdram_cke), //SDRAM 时钟使能
.RAS_N (sdram_ras_n), //SDRAM 行选中信号
.CAS_N (sdram_cas_n), //SDRAM 列选中信号
.WE_N (sdram_we_n), //SDRAM 写请求信号
.DQ (sdram_dq), //SDRAM 双向数据总线
.SDR_CLK (sdram_clk),
.DQM (), //SDRAM 数据总线高低字节屏蔽信号
.Sdram_Init_Done(sdram_init_done)
);
assign sdram_dqm = 2'b00;
///串口波特率
parameter CLK_FREQ = 50000000;
parameter UART_BPS = 9600;
wire [7:0] uart_tx_data ;
wire uart_tx_en ;
wire uart_tx_done ;
uart_tx_ctrl uart_tx_ctrl(
.clk (clk),
.rst_n (rst_n),
.target_detect (target_detect),
.uart_tx_en (uart_tx_en),
.uart_tx_data (uart_tx_data),
.uart_tx_done (uart_tx_done)
);
// 串口 发送
uart_send #(
.CLK_FREQ (CLK_FREQ),
.UART_BPS (UART_BPS)
)
rj45_1_send_mode1(
.sys_clk (clk),
.sys_rst_n (rst_n),
.uart_din (uart_tx_data ),
.uart_en (uart_tx_en ),
.uart_txd (uart_txd),
.tx_flag (),
.tx_done (uart_tx_done)
);
// 串口 接收
uart_recv #(
.CLK_FREQ (CLK_FREQ),
.UART_BPS (UART_BPS)
)
rs485_1_recv(
.sys_clk (clk),
.sys_rst_n (rst_n),
.uart_rxd (uart_rxd),
.uart_done ( ),
.uart_data ( ),
.uart_valid ( )
);
assign led[0] = target_detect;
assign led[1] = target_detect;
assign led[3] = uart_txd ;
endmodule
2、图像处理顶层模块
`timescale 1ns/1ns
module Video_Image_Processor #(
parameter [9:0] IMG_HDISP = 10'd480, //640*480
parameter [9:0] IMG_VDISP = 10'd320
)
(
//global clock
input clk, //cmos video pixel clock
input rst_n, //global reset
//Image data prepred to be processd
input per_frame_vsync, //Prepared Image data vsync valid signal
input per_frame_href, //Prepared Image data href vaild signal
input per_frame_clken, //Prepared Image data output/capture enable clock
input [7:0] per_img_red, //Prepared Image red data to be processed
input [7:0] per_img_green, //Prepared Image green data to be processed
input [7:0] per_img_blue, //Prepared Image blue data to be processed
output YCbCr_frame_vsync,
output YCbCr_frame_href,
output YCbCr_frame_clken,
output [7:0] YCbCr_img_Y_current, //输出当前帧的灰度,用于写入SDRAM
input [7:0] YCbCr_img_Y_pre, //同时从SDRAM中读出前一帧的灰度,与当前输出相差一个时钟周期
//Image data has been processd
output post_frame_vsync, //Processed Image data vsync valid signal
output post_frame_href, //Processed Image data href vaild signal
output post_frame_clken, //Processed Image data output/capture enable clock
output [7:0] post_img_red, //Processed Image red data to be processed
output [7:0] post_img_green, //Processed Image green data to be processed
output [7:0] post_img_blue, //Processed Image blue data to be processed
//user interface
input [7:0] Diff_Threshold, //Frame Difference Threshold for move detect
output target_detect
);
assign YCbCr_frame_vsync = post0_frame_vsync;
assign YCbCr_frame_href = post0_frame_href ;
assign YCbCr_frame_clken = post0_frame_clken;
assign YCbCr_img_Y_current = post0_img_Y ;
//-------------------------------------
//Convert the RGB888 format to YCbCr444 format.
wire post0_frame_vsync;
wire post0_frame_href ;
wire post0_frame_clken;
wire [7:0] post0_img_Y ;
wire [7:0] post0_img_Cb ;
wire [7:0] post0_img_Cr ;
VIP_RGB888_YCbCr444 u_VIP_RGB888_YCbCr444
(
//global clock
.clk (clk), //cmos video pixel clock
.rst_n (rst_n), //system reset
//Image data prepred to be processd
.per_frame_vsync (per_frame_vsync), //Prepared Image data vsync valid signal
.per_frame_href (per_frame_href), //Prepared Image data href vaild signal
.per_frame_clken (per_frame_clken), //Prepared Image data output/capture enable clock
.per_img_red (per_img_red), //Prepared Image red data input
.per_img_green (per_img_green), //Prepared Image green data input
.per_img_blue (per_img_blue), //Prepared Image blue data input
//Image data has been processd
.post_frame_vsync (post0_frame_vsync), //Processed Image frame data valid signal
.post_frame_href (post0_frame_href), //Processed Image hsync data valid signal
.post_frame_clken (post0_frame_clken), //Processed Image data output/capture enable clock
.post_img_Y (post0_img_Y), //Processed Image brightness output
.post_img_Cb (post0_img_Cb), //Processed Image blue shading output
.post_img_Cr (post0_img_Cr) //Processed Image red shading output
);
//--------------------------------------
//frame difference
wire post1_frame_vsync; //Processed Image data vsync valid signal
wire post1_frame_href; //Processed Image data href vaild signal
wire post1_frame_clken; //Processed Image data output/capture enable clock
wire post1_img_Bit; //Processed Image Bit flag outout(1: Value, 0:inValid)
VIP_Frame_Difference u_VIP_Frame_Difference(
//global clock
.clk (clk), //cmos video pixel clock
.rst_n (rst_n), //global reset
//Image data prepred to be processd
.per_frame_vsync (post0_frame_vsync), //Prepared Image data vsync valid signal
.per_frame_href (post0_frame_href), //Prepared Image data href vaild signal
.per_frame_clken (post0_frame_clken), //Prepared Image data output/capture enable clock
.per_img_Y (post0_img_Y), //Prepared Image brightness input
.YCbCr_img_Y_pre (YCbCr_img_Y_pre),
//Image data has been processd
.post_frame_vsync (post1_frame_vsync), //Processed Image data vsync valid signal
.post_frame_href (post1_frame_href), //Processed Image data href vaild signal
.post_frame_clken (post1_frame_clken), //Processed Image data output/capture enable clock
.post_img_Bit (post1_img_Bit), //Processed Image Bit flag outout(1: Value, 0:inValid)
//User interface
.Diff_Threshold (Diff_Threshold) //Sobel Threshold for image edge detect
);
//--------------------------------------
//Bit Image Process with Erosion before Dilation Detector.
wire post2_frame_vsync; //Processed Image data vsync valid signal
wire post2_frame_href; //Processed Image data href vaild signal
wire post2_frame_clken; //Processed Image data output/capture enable clock
wire post2_img_Bit; //Processed Image Bit flag outout(1: Value, 0:inValid)
VIP_Bit_Erosion_Detector
#(
.IMG_HDISP (IMG_HDISP), //640*480
.IMG_VDISP (IMG_VDISP)
)
u_VIP_Bit_Erosion_Detector
(
//global clock
.clk (clk), //cmos video pixel clock
.rst_n (rst_n), //global reset
//Image data prepred to be processd
.per_frame_vsync (post1_frame_vsync), //Prepared Image data vsync valid signal
.per_frame_href (post1_frame_href), //Prepared Image data href vaild signal
.per_frame_clken (post1_frame_clken), //Prepared Image data output/capture enable clock
.per_img_Bit (post1_img_Bit), //Processed Image Bit flag outout(1: Value, 0:inValid)
//Image data has been processd
.post_frame_vsync (post2_frame_vsync), //Processed Image data vsync valid signal
.post_frame_href (post2_frame_href), //Processed Image data href vaild signal
.post_frame_clken (post2_frame_clken), //Processed Image data output/capture enable clock
.post_img_Bit (post2_img_Bit) //Processed Image Bit flag outout(1: Value, 0:inValid)
);
//--------------------------------------
//Bit Image Process with Dilation after Erosion Detector.
wire post3_frame_vsync; //Processed Image data vsync valid signal
wire post3_frame_href; //Processed Image data href vaild signal
wire post3_frame_clken; //Processed Image data output/capture enable clock
wire post3_img_Bit; //Processed Image Bit flag outout(1: Value, 0:inValid)
VIP_Bit_Dilation_Detector
#(
.IMG_HDISP (IMG_HDISP), //640*480
.IMG_VDISP (IMG_VDISP)
)
u_VIP_Bit_Dilation_Detector
(
//global clock
.clk (clk), //cmos video pixel clock
.rst_n (rst_n), //global reset
//Image data prepred to be processd
.per_frame_vsync (post2_frame_vsync), //Prepared Image data vsync valid signal
.per_frame_href (post2_frame_href), //Prepared Image data href vaild signal
.per_frame_clken (post2_frame_clken), //Prepared Image data output/capture enable clock
.per_img_Bit (post2_img_Bit), //Processed Image Bit flag outout(1: Value, 0:inValid)
//Image data has been processd
.post_frame_vsync (post3_frame_vsync), //Processed Image data vsync valid signal
.post_frame_href (post3_frame_href), //Processed Image data href vaild signal
.post_frame_clken (post3_frame_clken), //Processed Image data output/capture enable clock
.post_img_Bit (post3_img_Bit) //Processed Image Bit flag outout(1: Value, 0:inValid)
);
wire [9:0] rectangular_up ;
wire [9:0] rectangular_down ;
wire [9:0] rectangular_left ;
wire [9:0] rectangular_right ;
wire rectangular_flag ;
//检测运动目标所在的矩形区域
VIP_detect_rectangular
#(
.IMG_HDISP (IMG_HDISP), //640*480
.IMG_VDISP (IMG_VDISP)
)
u_VIP_detect_rectangular
(
//global clock
.clk (clk), //cmos video pixel clock
.rst_n (rst_n), //global reset
//Image data prepred to be processd
.per_frame_vsync (post3_frame_vsync), //Prepared Image data vsync valid signal
.per_frame_href (post3_frame_href), //Prepared Image data href vaild signal
.per_frame_clken (post3_frame_clken), //Prepared Image data output/capture enable clock
.per_img_Bit (post3_img_Bit), //Processed Image Bit flag outout(1: Value, 0:inValid)
//检测出的矩形边界
.rectangular_up (rectangular_up ),
.rectangular_down (rectangular_down ),
.rectangular_left (rectangular_left ),
.rectangular_right (rectangular_right),
.rectangular_flag (rectangular_flag)
);
//在输入视频上叠加检测出的矩形框
VIP_Video_add_rectangular
#(
.IMG_HDISP (IMG_HDISP), //640*480
.IMG_VDISP (IMG_VDISP)
)
u_VIP_Video_add_rectangular
(
//global clock
.clk (clk), //cmos video pixel clock
.rst_n (rst_n), //global reset
//Image data prepred to be processd
.per_frame_vsync (per_frame_vsync), //Prepared Image data vsync valid signal
.per_frame_href (per_frame_href), //Prepared Image data href vaild signal
.per_frame_clken (per_frame_clken), //Prepared Image data output/capture enable clock
.per_img_red (per_img_red), //Prepared Image red data input
.per_img_green (per_img_green), //Prepared Image green data input
.per_img_blue (per_img_blue), //Prepared Image blue data input
//检测出的矩形边界
.rectangular_up (rectangular_up ),
.rectangular_down (rectangular_down ),
.rectangular_left (rectangular_left ),
.rectangular_right (rectangular_right),
.rectangular_flag (rectangular_flag),
.post_frame_vsync (post_frame_vsync), //Prepared Image data vsync valid signal
.post_frame_href (post_frame_href), //Prepared Image data href vaild signal
.post_frame_clken (post_frame_clken), //Prepared Image data output/capture enable clock
.post_img_red (post_img_red), //Prepared Image red data input
.post_img_green (post_img_green), //Prepared Image green data input
.post_img_blue (post_img_blue) //Prepared Image blue data input
);
assign target_detect = rectangular_flag;
endmodule
3、LCD驱动顶层模块
//功能:Avalon-ST视频流接口的ILI9488驱动模块
module vip_ILI9488(
input clk,
input clk12p5M,
input rst_n,
//Avalon-ST Sink
input sink_sop,
input sink_valid,
input [15:0] sink_data,
input sink_eop,
output sink_ready,
//Conduit
output lcd_intdone,
//TFTLCD interface
output WR,
output RD,
output CS,
output RS,
output BL_cnt,
output [15:0] data,
output RESET
);
wire sop_w0,eop_w0,valid_w0,ready_w0;
wire [15:0] data_w0;
wire sop_w1;
wire [15:0] data_w1;
wire fifo_empty_w,fifo_rd_w;
wire [7:0] wrusedw_w;
wire pixelReady;
reg valid_r1;
assign fifo_rd_w = !fifo_empty_w && pixelReady;
assign sink_ready = (wrusedw_w <= 8'd200);
always@(posedge clk12p5M or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
valid_r1 <= 1'b0;
end
else
begin
valid_r1 <= fifo_rd_w;
end
end
vip_ili9488_dcfifo u_vip_ili9488_dcfifo_0 (
.aclr(!rst_n),
.data({sink_sop,sink_data}),
.rdclk(clk12p5M),
.rdreq(fifo_rd_w),
.wrclk(clk),
.wrreq(sink_valid),
.q({sop_w1,data_w1}),
.rdempty(fifo_empty_w),
.wrusedw(wrusedw_w)
);
ILI9488 u_ILI9488_0(
.clk12p5M(clk12p5M),
.rst_n(rst_n),
.x_start(10'd0),//横坐标
.x_end(10'd479),
.y_start(10'd0),
.y_end(10'd319),//纵坐标
.color(data_w1), //RGB565
.sop(sop_w1),
.write_en(valid_r1), //high active
.write_ready(pixelReady),//write_en拉高后必须等待write_ready为高才表示
.lcd_intdone(lcd_intdone),//TFTLCD 初始化完成标志,高有效
.WR(WR),
.RD(RD),
.CS(CS),
.RS(RS),
.BL_cnt(BL_cnt),
.data(data),
.RESET(RESET)
);
endmodule
4、SDRAM控制器顶层模块
module Sdram_Control_4Port(
// HOST Side
REF_CLK,
OUT_CLK,
RESET_N,
// CLK, //
//CLK_18, //
// FIFO Write Side 1
WR1_DATA,
WR1,
WR1_ADDR,
WR1_MAX_ADDR,
WR1_LENGTH,
WR1_LOAD,
WR1_CLK,
WR1_FULL,
WR1_USE,
// FIFO Write Side 2
WR2_DATA,
WR2,
WR2_ADDR,
WR2_MAX_ADDR,
WR2_LENGTH,
WR2_LOAD,
WR2_CLK,
WR2_FULL,
WR2_USE,
// FIFO Read Side 1
RD1_DATA,
RD1,
RD1_ADDR,
RD1_MAX_ADDR,
RD1_LENGTH,
RD1_LOAD,
RD1_CLK,
RD1_EMPTY,
RD1_USE,
// FIFO Read Side 2
RD2_DATA,
RD2,
RD2_ADDR,
RD2_MAX_ADDR,
RD2_LENGTH,
RD2_LOAD,
RD2_CLK,
RD2_EMPTY,
RD2_USE,
// SDRAM Side
SA,
BA,
CS_N,
CKE,
RAS_N,
CAS_N,
WE_N,
DQ,
DQM,
SDR_CLK,
// user inter
Sdram_Init_Done
);
`include "Sdram_Params.h"
// HOST Side
input REF_CLK; //System Clock
input OUT_CLK;
input RESET_N; //System Reset
// FIFO Write Side 1
input [`DSIZE-1:0] WR1_DATA; //Data input
input WR1; //Write Request
input [`ASIZE-1:0] WR1_ADDR; //Write start address
input [`ASIZE-1:0] WR1_MAX_ADDR; //Write max address
input [8:0] WR1_LENGTH; //Write length
input WR1_LOAD; //Write register load & fifo clear
input WR1_CLK; //Write fifo clock
output WR1_FULL; //Write fifo full
output [15:0] WR1_USE; //Write fifo usedw
// FIFO Write Side 2
input [`DSIZE-1:0] WR2_DATA; //Data input
input WR2; //Write Request
input [`ASIZE-1:0] WR2_ADDR; //Write start address
input [`ASIZE-1:0] WR2_MAX_ADDR; //Write max address
input [8:0] WR2_LENGTH; //Write length
input WR2_LOAD; //Write register load & fifo clear
input WR2_CLK; //Write fifo clock
output WR2_FULL; //Write fifo full
output [15:0] WR2_USE; //Write fifo usedw
// FIFO Read Side 1
output [`DSIZE-1:0] RD1_DATA; //Data output
input RD1; //Read Request
input [`ASIZE-1:0] RD1_ADDR; //Read start address
input [`ASIZE-1:0] RD1_MAX_ADDR; //Read max address
input [8:0] RD1_LENGTH; //Read length
input RD1_LOAD; //Read register load & fifo clear
input RD1_CLK; //Read fifo clock
output RD1_EMPTY; //Read fifo empty
output [15:0] RD1_USE; //Read fifo usedw
// FIFO Read Side 2
output [`DSIZE-1:0] RD2_DATA; //Data output
input RD2; //Read Request
input [`ASIZE-1:0] RD2_ADDR; //Read start address
input [`ASIZE-1:0] RD2_MAX_ADDR; //Read max address
input [8:0] RD2_LENGTH; //Read length
input RD2_LOAD; //Read register load & fifo clear
input RD2_CLK; //Read fifo clock
output RD2_EMPTY; //Read fifo empty
output [15:0] RD2_USE; //Read fifo usedw
// SDRAM Side
output [11:0] SA; //SDRAM address output
output [1:0] BA; //SDRAM bank address
output [1:0] CS_N; //SDRAM Chip Selects
output CKE; //SDRAM clock enable
output RAS_N; //SDRAM Row address Strobe
output CAS_N; //SDRAM Column address Strobe
output WE_N; //SDRAM write enable
inout [`DSIZE-1:0] DQ; //SDRAM data bus
output [`DSIZE/8-1:0] DQM; //SDRAM data mask lines
output SDR_CLK; //SDRAM clock
assign SDR_CLK=OUT_CLK;
wire CLK=REF_CLK;
output Sdram_Init_Done;
// Internal Registers/Wires
// Controller
reg [`ASIZE-1:0] mADDR; //Internal address
reg [8:0] mLENGTH; //Internal length
reg [`ASIZE-1:0] rWR1_ADDR; //Register write address
reg [`ASIZE-1:0] rWR1_MAX_ADDR; //Register max write address
reg [8:0] rWR1_LENGTH; //Register write length
reg [`ASIZE-1:0] rWR2_ADDR; //Register write address
reg [`ASIZE-1:0] rWR2_MAX_ADDR; //Register max write address
reg [8:0] rWR2_LENGTH; //Register write length
reg [`ASIZE-1:0] rRD1_ADDR; //Register read address
reg [`ASIZE-1:0] rRD1_MAX_ADDR; //Register max read address
reg [8:0] rRD1_LENGTH; //Register read length
reg [`ASIZE-1:0] rRD2_ADDR; //Register read address
reg [`ASIZE-1:0] rRD2_MAX_ADDR; //Register max read address
reg [8:0] rRD2_LENGTH; //Register read length
reg [1:0] WR_MASK; //Write port active mask
reg [1:0] RD_MASK; //Read port active mask
reg mWR_DONE; //Flag write done, 1 pulse SDR_CLK
reg mRD_DONE; //Flag read done, 1 pulse SDR_CLK
reg mWR,Pre_WR; //Internal WR edge capture
reg mRD,Pre_RD; //Internal RD edge capture
reg [9:0] ST; //Controller status
reg [1:0] CMD; //Controller command
reg PM_STOP; //Flag page mode stop
reg PM_DONE; //Flag page mode done
reg Read; //Flag read active
reg Write; //Flag write active
reg [`DSIZE-1:0] mDATAOUT; //Controller Data output
wire [`DSIZE-1:0] mDATAIN; //Controller Data input
wire [`DSIZE-1:0] mDATAIN1; //Controller Data input 1
wire [`DSIZE-1:0] mDATAIN2; //Controller Data input 2
wire CMDACK; //Controller command acknowledgement
// DRAM Control
reg [`DSIZE/8-1:0] DQM; //SDRAM data mask lines
reg [11:0] SA; //SDRAM address output
reg [1:0] BA; //SDRAM bank address
reg [1:0] CS_N; //SDRAM Chip Selects
reg CKE; //SDRAM clock enable
reg RAS_N; //SDRAM Row address Strobe
reg CAS_N; //SDRAM Column address Strobe
reg WE_N; //SDRAM write enable
wire [`DSIZE-1:0] DQOUT; //SDRAM data out link
wire [`DSIZE/8-1:0] IDQM; //SDRAM data mask lines
wire [11:0] ISA; //SDRAM address output
wire [1:0] IBA; //SDRAM bank address
wire [1:0] ICS_N; //SDRAM Chip Selects
wire ICKE; //SDRAM clock enable
wire IRAS_N; //SDRAM Row address Strobe
wire ICAS_N; //SDRAM Column address Strobe
wire IWE_N; //SDRAM write enable
// FIFO Control
reg OUT_VALID; //Output data request to read side fifo
reg IN_REQ; //Input data request to write side fifo
wire [15:0] write_side_fifo_rusedw1;
wire [15:0] read_side_fifo_wusedw1;
wire [15:0] write_side_fifo_rusedw2;
wire [15:0] read_side_fifo_wusedw2;
// DRAM Internal Control
wire [`ASIZE-1:0] saddr;
wire load_mode;
wire nop;
wire reada;
wire writea;
wire refresh;
wire precharge;
wire oe;
wire ref_ack;
wire ref_req;
wire init_req;
wire cm_ack;
wire active;
/*
Sdram_PLL sdram_pll1 (
.inclk0(REF_CLK),
.c0(CLK),
.c1(SDR_CLK),
.c2(CLK_18)
);
*/
control_interface control1 (
.CLK(CLK),
.RESET_N(RESET_N),
.CMD(CMD),
.ADDR(mADDR),
.REF_ACK(ref_ack),
.CM_ACK(cm_ack),
.NOP(nop),
.READA(reada),
.WRITEA(writea),
.REFRESH(refresh),
.PRECHARGE(precharge),
.LOAD_MODE(load_mode),
.SADDR(saddr),
.REF_REQ(ref_req),
.INIT_REQ(init_req),
.CMD_ACK(CMDACK),
.Sdram_Init_Done(Sdram_Init_Done)
);
command command1(
.CLK(CLK),
.RESET_N(RESET_N),
.SADDR(saddr),
.NOP(nop),
.READA(reada),
.WRITEA(writea),
.REFRESH(refresh),
.LOAD_MODE(load_mode),
.PRECHARGE(precharge),
.REF_REQ(ref_req),
.INIT_REQ(init_req),
.REF_ACK(ref_ack),
.CM_ACK(cm_ack),
.OE(oe),
.PM_STOP(PM_STOP),
.PM_DONE(PM_DONE),
.SA(ISA),
.BA(IBA),
.CS_N(ICS_N),
.CKE(ICKE),
.RAS_N(IRAS_N),
.CAS_N(ICAS_N),
.WE_N(IWE_N)
);
sdr_data_path data_path1(
.CLK(CLK),
.RESET_N(RESET_N),
.DATAIN(mDATAIN),
.DM(2'b00),
.DQOUT(DQOUT),
.DQM(IDQM)
);
Sdram_WR_FIFO write_fifo1(
.data(WR1_DATA),
.wrreq(WR1),
.wrclk(WR1_CLK),
.aclr(WR1_LOAD),
.rdreq(IN_REQ&WR_MASK[0]),
.rdclk(CLK),
.q(mDATAIN1),
.wrfull(WR1_FULL),
.wrusedw(WR1_USE),
.rdusedw(write_side_fifo_rusedw1)
);
Sdram_WR_FIFO write_fifo2(
.data(WR2_DATA),
.wrreq(WR2),
.wrclk(WR2_CLK),
.aclr(WR2_LOAD),
.rdreq(IN_REQ&WR_MASK[1]),
.rdclk(CLK),
.q(mDATAIN2),
.wrfull(WR2_FULL),
.wrusedw(WR2_USE),
.rdusedw(write_side_fifo_rusedw2)
);
assign mDATAIN = (WR_MASK[0]) ? mDATAIN1 :
mDATAIN2 ;
Sdram_RD_FIFO read_fifo1(
.data(mDATAOUT),
.wrreq(OUT_VALID&RD_MASK[0]),
.wrclk(CLK),
.aclr(RD1_LOAD),
.rdreq(RD1),
.rdclk(RD1_CLK),
.q(RD1_DATA),
.wrusedw(read_side_fifo_wusedw1),
.rdempty(RD1_EMPTY),
.rdusedw(RD1_USE)
);
Sdram_RD_FIFO read_fifo2(
.data(mDATAOUT),
.wrreq(OUT_VALID&RD_MASK[1]),
.wrclk(CLK),
.aclr(RD2_LOAD),
.rdreq(RD2),
.rdclk(RD2_CLK),
.q(RD2_DATA),
.wrusedw(read_side_fifo_wusedw2),
.rdempty(RD2_EMPTY),
.rdusedw(RD2_USE)
);
always @(posedge CLK)
begin
SA <= (ST==SC_CL+mLENGTH) ? 12'h200 : ISA;
BA <= IBA;
CS_N <= ICS_N;
CKE <= ICKE;
RAS_N <= (ST==SC_CL+mLENGTH) ? 1'b0 : IRAS_N;
CAS_N <= (ST==SC_CL+mLENGTH) ? 1'b1 : ICAS_N;
WE_N <= (ST==SC_CL+mLENGTH) ? 1'b0 : IWE_N;
PM_STOP <= (ST==SC_CL+mLENGTH) ? 1'b1 : 1'b0;
PM_DONE <= (ST==SC_CL+SC_RCD+mLENGTH+2) ? 1'b1 : 1'b0;
DQM <= {(`DSIZE/8){1'b0}}; //( active && (ST>=SC_CL) ) ? ( ((ST==SC_CL+mLENGTH) && Write)? 2'b11 : 2'b00 ) : 2'b11 ;
mDATAOUT<= DQ;
end
assign DQ = oe ? DQOUT : `DSIZE'hzzzz;
assign active = Read | Write;
always@(posedge CLK or negedge RESET_N)
begin
if(RESET_N==0)
begin
CMD <= 0;
ST <= 0;
Pre_RD <= 0;
Pre_WR <= 0;
Read <= 0;
Write <= 0;
OUT_VALID <= 0;
IN_REQ <= 0;
mWR_DONE <= 0;
mRD_DONE <= 0;
end
else
begin
Pre_RD <= mRD;
Pre_WR <= mWR;
case(ST)
0: begin
if({Pre_RD,mRD}==2'b01)
begin
Read <= 1;
Write <= 0;
CMD <= 2'b01;
ST <= 1;
end
else if({Pre_WR,mWR}==2'b01)
begin
Read <= 0;
Write <= 1;
CMD <= 2'b10;
ST <= 1;
end
end
1: begin
if(CMDACK==1)
begin
CMD<=2'b00;
ST<=2;
end
end
default:
begin
if(ST!=SC_CL+SC_RCD+mLENGTH+1)
ST<=ST+1;
else
ST<=0;
end
endcase
if(Read)
begin
if(ST==SC_CL+SC_RCD+1)
OUT_VALID <= 1;
else if(ST==SC_CL+SC_RCD+mLENGTH+1)
begin
OUT_VALID <= 0;
Read <= 0;
mRD_DONE <= 1;
end
end
else
mRD_DONE <= 0;
if(Write)
begin
if(ST==SC_CL-1)
IN_REQ <= 1;
else if(ST==SC_CL+mLENGTH-1)
IN_REQ <= 0;
else if(ST==SC_CL+SC_RCD+mLENGTH)
begin
Write <= 0;
mWR_DONE<= 1;
end
end
else
mWR_DONE<= 0;
end
end
// Internal Address & Length Control
always@(posedge CLK or negedge RESET_N)
begin
if(!RESET_N)
begin
rWR1_ADDR <= WR1_ADDR;
rWR1_MAX_ADDR <= WR1_MAX_ADDR;
rWR2_ADDR <= WR2_ADDR;
rWR2_MAX_ADDR <= WR2_MAX_ADDR;
rRD1_ADDR <= RD1_ADDR;
rRD1_MAX_ADDR <= RD1_MAX_ADDR;
rRD2_ADDR <= RD2_ADDR;
rRD2_MAX_ADDR <= RD2_MAX_ADDR;
rWR1_LENGTH <=WR1_LENGTH;
rRD1_LENGTH <=RD1_LENGTH;
rWR2_LENGTH <=WR2_LENGTH;
rRD2_LENGTH <=RD2_LENGTH;
end
else
begin
// Write Side 1
if(WR1_LOAD)
begin
rWR1_ADDR <= WR1_ADDR;
rWR1_LENGTH <= WR1_LENGTH;
end
else if(mWR_DONE&WR_MASK[0])
begin
if(rWR1_ADDR<rWR1_MAX_ADDR-rWR1_LENGTH)
rWR1_ADDR <= rWR1_ADDR+rWR1_LENGTH;
else
rWR1_ADDR <= WR1_ADDR;
end
// Write Side 2
if(WR2_LOAD)
begin
rWR2_ADDR <= WR2_ADDR;
rWR2_LENGTH <= WR2_LENGTH;
end
else if(mWR_DONE&WR_MASK[1])
begin
if(rWR2_ADDR<rWR2_MAX_ADDR-rWR2_LENGTH)
rWR2_ADDR <= rWR2_ADDR+rWR2_LENGTH;
else
rWR2_ADDR <= WR2_ADDR;
end
// Read Side 1
if(RD1_LOAD)
begin
rRD1_ADDR <= RD1_ADDR;
rRD1_LENGTH <= RD1_LENGTH;
end
else if(mRD_DONE&RD_MASK[0])
begin
if(rRD1_ADDR<rRD1_MAX_ADDR-rRD1_LENGTH)
rRD1_ADDR <= rRD1_ADDR+rRD1_LENGTH;
else
rRD1_ADDR <= RD1_ADDR;
end
// Read Side 2
if(RD2_LOAD)
begin
rRD2_ADDR <= RD2_ADDR;
rRD2_LENGTH <= RD2_LENGTH;
end
else if(mRD_DONE&RD_MASK[1])
begin
if(rRD2_ADDR<rRD2_MAX_ADDR-rRD2_LENGTH)
rRD2_ADDR <= rRD2_ADDR+rRD2_LENGTH;
else
rRD2_ADDR <= RD2_ADDR;
end
end
end
// Auto Read/Write Control
always@(posedge CLK or negedge RESET_N)
begin
if(!RESET_N)
begin
mWR <= 0;
mRD <= 0;
mADDR <= 0;
mLENGTH <= 0;
RD_MASK <= 0;
WR_MASK <= 0;
end
else if(Sdram_Init_Done)
begin
if( (mWR==0) && (mRD==0) && (ST==0) &&
(WR_MASK==0) && (RD_MASK==0) &&
(WR1_LOAD==0) && (RD1_LOAD==0) &&
(WR2_LOAD==0) && (RD2_LOAD==0) )
begin
// Read Side 1
if( (read_side_fifo_wusedw1 < rRD1_LENGTH) )
begin
mADDR <= rRD1_ADDR;
mLENGTH <= rRD1_LENGTH;
WR_MASK <= 2'b00;
RD_MASK <= 2'b01;
mWR <= 0;
mRD <= 1;
end
// Read Side 2
else if( (read_side_fifo_wusedw2 < rRD2_LENGTH) )
begin
mADDR <= rRD2_ADDR;
mLENGTH <= rRD2_LENGTH;
WR_MASK <= 2'b00;
RD_MASK <= 2'b10;
mWR <= 0;
mRD <= 1;
end
// Write Side 1
else if( (write_side_fifo_rusedw1 >= rWR1_LENGTH) && (rWR1_LENGTH!=0) )
begin
mADDR <= rWR1_ADDR;
mLENGTH <= rWR1_LENGTH;
WR_MASK <= 2'b01;
RD_MASK <= 2'b00;
mWR <= 1;
mRD <= 0;
end
// Write Side 2
else if( (write_side_fifo_rusedw2 >= rWR2_LENGTH) && (rWR2_LENGTH!=0) )
begin
mADDR <= rWR2_ADDR;
mLENGTH <= rWR2_LENGTH;
WR_MASK <= 2'b10;
RD_MASK <= 2'b00;
mWR <= 1;
mRD <= 0;
end
end
if(mWR_DONE)
begin
WR_MASK <= 0;
mWR <= 0;
end
if(mRD_DONE)
begin
RD_MASK <= 0;
mRD <= 0;
end
end
end
endmodule
5、上位机发送模块
module uart_tx_ctrl(
input clk,
input rst_n,
input target_detect,
output reg uart_tx_en ,
output reg [7:0] uart_tx_data,
input uart_tx_done
);
wire [ 7:0] string_data [15:0]; //待发送字符串
assign string_data[0] = "I";
assign string_data[1] = "n";
assign string_data[2] = "t";
assign string_data[3] = "r";
assign string_data[4] = "u";
assign string_data[5] = "d";
assign string_data[6] = "e";
assign string_data[7] = "r";
assign string_data[8] = 8'h21; // '!'
assign string_data[9] = 8'h0A; // '\n'
reg [4:0] ctrl_state ;
reg [3:0] cmd_byte_cnt;
//状态机,
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
ctrl_state <= 5'd0;
cmd_byte_cnt <= 4'd0;
uart_tx_en <= 1'b0;
uart_tx_data <= 8'd0;
end
else begin
case(ctrl_state)
//Idle,无数据发送状态
5'd0: begin
cmd_byte_cnt <= 4'd0;
uart_tx_en <= 1'b0;
uart_tx_data <= 8'd0;
if(target_detect == 1'b1) begin //有目标出现
ctrl_state <= 5'd1;
end
else begin
ctrl_state <= 5'd0;
end
end
//开始发送串口数据
5'd1: begin
uart_tx_en <= 1'b1; //拉高发送使能
uart_tx_data <= string_data[cmd_byte_cnt];
ctrl_state <= 5'd2; //等待发送完成
end
//等待字节发送完成
5'd2: begin
uart_tx_en <= 1'b0;
if(uart_tx_done) begin // 字节发送完成
if(cmd_byte_cnt < 4'd9) begin
ctrl_state <= 5'd1; //字符串继续发送
cmd_byte_cnt <= cmd_byte_cnt + 1'b1;
end
else begin
ctrl_state <= 5'd0; //字符串继续发送
cmd_byte_cnt <= 4'd0;
end
end
end
default: begin
ctrl_state <= 5'd0;
end
endcase
end
end
endmodule
五、工程及套件获取
1、工程获取
A:直接点击下载:基于FPGA:运动目标检测(LCD显示+串口输出,完整工程).zip
B:私信我或添加邮箱获取(完整原理图+源码工程+关键模块仿真)(另外赠送基于FPGA单目标运动检测VGA显示)文章来源:https://www.toymoban.com/news/detail-472061.html
2、套件
觉得调试麻烦的,我手上有几套调试好的套件,有需要的话,可以购买,上电就可以看现象,方便。有需要添加我邮箱:bumianzhe@126.com。
文章来源地址https://www.toymoban.com/news/detail-472061.html
到了这里,关于基于FPGA:运动目标检测(LCD显示+串口输出,纯Verilog工程)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!