1、模块来源
使用逻辑派G1开发板(基于 GW2A 芯片)编写spi屏幕的驱动代码,最终在屏幕上实现红绿蓝交错的九宫格图案。
SPI 屏幕的驱动 IC 型号是 ST7789 是台湾矽创(Sitronix)出的一颗 TFT 屏驱动/控制芯片
如果需要具体的资料可以自行百度,关键词:st7789中文手册
2、实验原理
2.1 SPI协议介绍S
SPI协议(Serial Peripheral Interface,串行外围接口)是一种同步串行通信协议,通常用于短距离设备间的高速数据传输。它采用主从架构,由主设备(如微控制器)和一个或多个从设备(如传感器、显示屏等)组成。SPI通过4条主要的信号线进行数据传输:MOSI(主设备输出,从设备输入)、MISO(主设备输入,从设备输出)、SCLK(串行时钟,由主设备提供)和CS(片选信号,控制与哪个从设备通信)。其中,MOSI和MISO实现双向通信,而SCLK负责同步时钟信号。
SPI的通信特点是全双工,即主设备和从设备可以同时发送和接收数据。通信过程由主设备控制,通过时钟信号SCLK同步数据传输。每次传输的数据按位发送,通常一字节一字节地进行,数据传输的顺序可以配置为从最高有效位(MSB)或最低有效位(LSB)开始。片选信号CS用于选择具体的从设备,低电平激活。当有多个从设备时,主设备可以通过多个CS引脚分别控制它们。SPI协议广泛应用于短距离、高速通信的场景,如显示屏、存储设备、传感器、音频设备等。
SPI有四种通讯模式,由时钟极性和时钟相位共同决定。时钟极性全称 ClockPolarity,通常简写成CPOL。它是指时钟信号在空闲状态下是高电平还是低电平,当时钟空闲时为低电平即CPOL==0,反之则CPOL==1;时钟相位全称 ClockPhase,通常简写成CPHA。它是指时钟信号开始有效的第一个边沿和数据的关系,当CPHA==0时,数据采样发生在时钟信号有效的第一个边沿(也叫奇边沿),当CPHA==1时,数据采样发生在时钟信号有效的第二个边沿(也叫偶边沿);我们以CPOL=1,CPHA=1的模式进行分析:
上图分析了时钟相位与时钟极性均设置为1时的SPI通讯时序。根据时序图可以发现,由于CPOL设置为1,时钟SCK空闲状态为高电平,有效状态为低电平。由于CPHA设置为1,数据采样将发生在时钟有效的第二个边沿,也就是图中时钟为低电平后的第二个边沿。根据Sitronix公司的ST7789VM芯片数据手册,我们本次实验所使用的SPI通讯模式即为此模式。
2.2 SPI屏幕介绍
SPI屏幕是一种通过串行外围接口(SPI, Serial Peripheral Interface)与微控制器或处理器通信的显示设备。其与微控制器的通讯通常包含四条主要的信号线:MOSI(主设备输出,从设备输入)、MISO(主设备输入,从设备输出)、SCLK(串行时钟)和CS(片选/从设备选择)。有时也会简化为三线结构,不使用MISO(本次实验使用的通讯方式就是三线结构)。
相比于并行通信方式(直接使用rgb格式输入像素数据),SPI使用的引脚数量较少,通常只需要4-5根信号线就可以完成数据传输,因此硬件设计更为简洁,尤其适合引脚资源有限的嵌入式系统。
SPI屏幕的控制流程一般是由主控制器通过SPI发送显示数据到屏幕控制器,屏幕控制器再将这些数据处理后驱动液晶或OLED屏幕显示图像。由于SPI是一种全双工通信协议,主设备和从设备可以在同一时钟周期内同时发送和接收数据,不过在大多数SPI屏幕应用中,通常只有主设备(控制器)负责发送显示数据,从设备(屏幕)接收数据。
虽然SPI接口相对于并行接口来说在引脚数和硬件复杂性上有优势,但其缺点是数据传输的带宽相对较小,因此在显示高分辨率图像时速度可能较慢,适合在较小分辨率的屏幕或更新频率要求不高的场景中使用。常见的SPI屏幕有TFT和OLED两种类型,广泛应用于智能手表、物联网设备、传感器终端等对显示要求不高的小型设备上。
其引脚说明如下:
3、时序说明
我们来学习一下 SPI 屏幕驱动模块的时序
我们来分析一下这个时序图,可以看到:
1.首先,最基本的。SCL上升沿到来之前,SDA必须已经稳定,要满足建立时间。而且在 SCL 上升沿之后, SDA 还要等待 10 ns 才能变下一个位,满足保持时间。
2.然后,CS 拉低表示片选被选上,可以开始发送数据。8 次发完之后会被拉高
3.DC 表示的是数据/命令线,0 的时候发的是命令,是 1 的时候发的是数据
4.SDA 就是数据线,这里一次发8位
图中几个字母的意义:
- T_sds:数据建立时间
- T_sdh:数据保持时间
- T_scycw:SCL 时钟周期
4、程序编写
我们下面给出这个例程的程序实现 我们将SPI屏幕的驱动功能分为以下模块:
test:产生测试数据,例化整个spi屏幕驱动模块,用于板级测试产生图像数据。
spi_screen_top:对屏幕驱动模块进行封装,使之便于用户操作。
spi_tft_screen_driver:spi屏幕驱动模块,例化屏幕初始化模块,屏幕刷新显示模块,spi发送模块。
spi_tft_screen_flush:对用户输入的图像数据进行刷新显示。
spi_tft_screen_init:spi屏幕初始化模块,对ST7789VM进行相关配置。
spi_master_driver:接收各个模块传入的命令与数据并通过spi协议发送给ST7789VM。
模块层次框图如下:
`timescale 1ns / 1ps
module test(
input sys_clk ,// 系统时钟
input sys_rst_n ,// 复位
//spi tft screen 屏幕接口
output lcd_spi_sclk ,// 屏幕spi时钟接口
output lcd_spi_mosi ,// 屏幕spi数据接口
output lcd_spi_cs ,// 屏幕spi使能接口
output lcd_dc ,// 屏幕 数据/命令 接口
output lcd_reset ,// 屏幕复位接口
output lcd_blk // 屏幕背光接口
);
wire flush_data_update ;//更新当前坐标点显示数据使能
reg [ 15: 0] flush_data ;//当前坐标点显示的数据
wire [ 15: 0] flush_addr_width ;//当前刷新的x坐标
wire [ 15: 0] flush_addr_height ;//当前刷新的y坐标
always @(posedge sys_clk or negedge sys_rst_n) begin // 显示九宫格测试图片 240*320
if (sys_rst_n == 1'b0)
flush_data <= 16'd0;
else if ((flush_addr_width >= 'd0 && flush_addr_width < 'd106) && (flush_addr_height >= 'd0 && flush_addr_height < 'd80))
flush_data <= 16'hF800;//红 1111 1000 0000 0000
else if ((flush_addr_width >= 'd106 && flush_addr_width < 'd212) && (flush_addr_height >= 'd0 && flush_addr_height < 'd80))
flush_data <= 16'h07E0;//绿 0000 0111 1110 0000
else if ((flush_addr_width >= 'd212 && flush_addr_width < 'd320) && (flush_addr_height >= 'd0 && flush_addr_height < 'd80))
flush_data <= 16'h001F;//蓝 0000 0000 0001 1111
else if ((flush_addr_width >= 'd0 && flush_addr_width < 'd106) && (flush_addr_height >= 'd80 && flush_addr_height < 'd160))
flush_data <= 16'h07E0;//绿
else if ((flush_addr_width >= 'd106 && flush_addr_width < 'd212) && (flush_addr_height >= 'd80 && flush_addr_height < 'd160))
flush_data <= 16'h001F;//蓝
else if ((flush_addr_width >= 'd212 && flush_addr_width < 'd320) && (flush_addr_height >= 'd80 && flush_addr_height < 'd160))
flush_data <= 16'hF800;//红
else if ((flush_addr_width >= 'd0 && flush_addr_width < 'd106) && (flush_addr_height >= 'd160 && flush_addr_height < 'd240))
flush_data <= 16'h001F;//蓝
else if ((flush_addr_width >= 'd106 && flush_addr_width < 'd212) && (flush_addr_height >= 'd160 && flush_addr_height < 'd240))
flush_data <= 16'hF800;//红
else if ((flush_addr_width >= 'd212 && flush_addr_width < 'd320) && (flush_addr_height >= 'd160 && flush_addr_height < 'd240))
flush_data <= 16'h07E0;//绿
else
flush_data <= 16'h0000;
end
//整个spi屏幕控制顶层,用户只需要提供显示数据就可以使用这个顶层进行spi屏幕显示
spi_screen_top spi_screen_top_inst(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
//用户信号
.flush_data_update_o (flush_data_update ),//更新当前坐标点显示数据使能
.flush_data_i (flush_data ),//当前坐标点显示的数据
.flush_addr_width_o (flush_addr_width ),//当前刷新的x坐标
.flush_addr_height_o (flush_addr_height ),//当前刷新的y坐标
//spi tft screen 屏幕接口
.lcd_spi_sclk (lcd_spi_sclk ),// 屏幕spi时钟接口
.lcd_spi_mosi (lcd_spi_mosi ),// 屏幕spi数据接口
.lcd_spi_cs (lcd_spi_cs ),// 屏幕spi使能接口
.lcd_dc (lcd_dc ),// 屏幕 数据/命令 接口
.lcd_reset (lcd_reset ),// 屏幕复位接口
.lcd_blk (lcd_blk ) // 屏幕背光接口
);
endmodule2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
`timescale 1ns / 1ps
//对spi tft屏幕进行初始化,I表示主机发给屏幕,o反之
module spi_tft_screen_init(
input sys_clk ,
input sys_rst_n ,
input tft_screen_init_req_i ,//初始化请求
output tft_screen_init_ack_o ,//初始化完成
output reg [ 7: 0] tft_screen_init_data_o ,//初始化数据
output reg tft_screen_init_dc_o ,//初始化dc,这个字节是命令(0)还是数据(1)
output spi_send_init_req_o ,//spi发送数据请求
output spi_send_init_end_o ,//结束spi发送
input spi_send_init_ack_i //spi一个数据发送完成
);
parameter SCREEN_WIDTH = 16'd320;
parameter SCREEN_HEIGHT = 16'd240;
localparam DELAY_255ms = 32'd12_750_000; // 长延时:255ms
localparam DELAY_200us = 32'd10_000; // 短延时:200us
localparam S_IDLE = 4'b0001;//初始状态:空闲
localparam S_SEND_DATA = 4'b0010;//发送数据状态
localparam S_DELAY = 4'b0100;//延迟状态,发完一个数据之间等待的状态
localparam S_ACK = 4'b1000;//响应状态,全部发完
reg [ 4: 0] init_cnt ;//初始化命令/数据计数 0~18
reg [ 31: 0] delay_cnt ;//延时计数
reg [ 3: 0] state ;//状态寄存器
reg [ 3: 0] next_state ;//下一状态寄存器
//为响应状态时,初始化完成信号拉高
assign tft_screen_init_ack_o = (state == S_ACK) ? 1'b1 : 1'b0;
//为发送数据状态时,spi发送数据请求信号拉高
assign spi_send_init_req_o = (state == S_SEND_DATA) ? 1'b1 : 1'b0;
//为延迟状态时,结束spi发送
assign spi_send_init_end_o = (state == S_DELAY) ? 1'b1 : 1'b0;
always@(posedge sys_clk or negedge sys_rst_n) begin
if( sys_rst_n == 1'b0)
state <= S_IDLE;
else
state <= next_state;
end
always@(*) begin
case(state)
S_IDLE:
if( tft_screen_init_req_i == 1'b1)//初始化请求有效时,跳转到发送数据状态
next_state <= S_SEND_DATA;
else
next_state <= S_IDLE;
S_SEND_DATA:
if( spi_send_init_ack_i == 1'b1)//spi一个数据发送完成,跳转到延迟状态
next_state <= S_DELAY;
else
next_state <= S_SEND_DATA;
S_DELAY:
if( init_cnt == 'd18)//初始化命令和数据发送完成跳转到响应状态
if( delay_cnt == DELAY_255ms)
next_state <= S_ACK;
else
next_state <= S_DELAY;
else if(init_cnt == 'd1 )//延迟结束后,跳转到发送数据状态
if(delay_cnt == DELAY_255ms)
next_state <= S_SEND_DATA;
else
next_state <= S_DELAY;
else if(init_cnt == 'd2 )
if(delay_cnt == DELAY_255ms)
next_state <= S_SEND_DATA;
else
next_state <= S_DELAY;
else if(init_cnt == 'd4 )
if(delay_cnt == DELAY_255ms)
next_state <= S_SEND_DATA;
else
next_state <= S_DELAY;
else if(init_cnt == 'd17 )
if(delay_cnt == DELAY_255ms)
next_state <= S_SEND_DATA;
else
next_state <= S_DELAY;
else if( delay_cnt == DELAY_200us)
next_state <= S_SEND_DATA;
else
next_state <= S_DELAY;
S_ACK:
next_state <= S_IDLE;
default: next_state <= S_IDLE;
endcase
end
//初始化数据计数//
always@(posedge sys_clk or negedge sys_rst_n) begin
if( sys_rst_n == 1'b0)
init_cnt <= 'd0;
else if( spi_send_init_ack_i == 1'b1)//spi一个数据发送完成,init_cnt加1
init_cnt <= init_cnt + 1'b1;
else
init_cnt <= init_cnt;
end
//延时计数//写命令之间需要间隔的时间
always@(posedge sys_clk or negedge sys_rst_n) begin
if( sys_rst_n == 1'b0)
delay_cnt <= 'd0;
else if( state == S_DELAY) //为延迟状态时,delay_cnt加1
delay_cnt <= delay_cnt + 1'b1;
else
delay_cnt <= 'd0;
end
//命令数据输出
//0命令,1数据
always@(*)begin
case (init_cnt)
'd0: begin
tft_screen_init_data_o = 8'h01; //SWRESET//软复位
tft_screen_init_dc_o = 1'b0;
end
'd1: begin
tft_screen_init_data_o = 8'h11; //SLPOUT//唤醒
tft_screen_init_dc_o = 1'b0;
end
'd2: begin
tft_screen_init_data_o = 8'h3A; //COLMOD//像素设置
tft_screen_init_dc_o = 1'b0;
end
'd3: begin
tft_screen_init_data_o = 8'h55; //数据//0_101_0_101//0_65k像素_0_rgb565(16bit)
tft_screen_init_dc_o = 1'b1;
end
'd4: begin
tft_screen_init_data_o = 8'h36; //MADCTL//帧内存数据的读写扫描方向
tft_screen_init_dc_o = 1'b0;
end
'd5: begin
tft_screen_init_data_o = 8'h70; //数据//01110000
tft_screen_init_dc_o = 1'b1;
end
'd6: begin
tft_screen_init_data_o = 8'h2A; //CASET列地址设置
tft_screen_init_dc_o = 1'b0;
end
'd7: begin
tft_screen_init_data_o = 8'h00; //列地址开始的高8位
tft_screen_init_dc_o = 1'b1;
end
'd8: begin
tft_screen_init_data_o = 8'h00; //列地址开始的低8位
tft_screen_init_dc_o = 1'b1;
end
'd9: begin
tft_screen_init_data_o = SCREEN_WIDTH[15:8]; //列地址结束的高8位
tft_screen_init_dc_o = 1'b1;
end
'd10: begin
tft_screen_init_data_o = SCREEN_WIDTH[7:0] - 1'b1; //列地址结束的低8位
tft_screen_init_dc_o = 1'b1;
end
'd11: begin
tft_screen_init_data_o = 8'h2B; //RASET//行地址设置
tft_screen_init_dc_o = 1'b0;
end
'd12: begin
tft_screen_init_data_o = 8'h00; //与列地址设置相似
tft_screen_init_dc_o = 1'b1;
end
'd13: begin
tft_screen_init_data_o = 8'h00; //数据
tft_screen_init_dc_o = 1'b1;
end
'd14: begin
tft_screen_init_data_o = SCREEN_HEIGHT[15:8]; //数据
tft_screen_init_dc_o = 1'b1;
end
'd15: begin
tft_screen_init_data_o = SCREEN_HEIGHT[7:0] - 1'b1; //数据
tft_screen_init_dc_o = 1'b1;
end
'd16: begin
tft_screen_init_data_o = 8'h13; //NORON//转换为正常显示模式
tft_screen_init_dc_o = 1'b0;
end
'd17: begin
tft_screen_init_data_o = 8'h29; //DISPON//开启显示
tft_screen_init_dc_o = 1'b0;
end
default: begin
tft_screen_init_data_o = 8'h01; //SWRESET//软复位
tft_screen_init_dc_o = 1'b0;
end
endcase
end
endmodule2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
`timescale 1ns / 1ps
//对spi tft屏幕进行刷新
module spi_tft_screen_flush(
input sys_clk ,
input sys_rst_n ,
//用户接口
input [ 7: 0] spi_screen_flush_data_i ,//屏幕显示数据
output spi_screen_flush_updte_o ,//像素点数据刷新
output spi_screen_flush_fsync_o ,//屏幕帧同步
//驱动模块
input tft_screen_flush_req_i ,//刷新请求//初始化完成之后此信号拉高模块进入刷新模式
output reg [ 7: 0] tft_screen_flush_data_o ,//刷新数据//刷新的图像数据和刷新命令通过spi模块发送
output reg tft_screen_flush_dc_o ,//刷新dc//区分命令与数据
//SPI主模块
output spi_send_flush_req_o ,//spi发送数据请求
output spi_send_flush_end_o ,//结束spi发送
input spi_send_flush_ack_i //spi一个数据发送完成
);
parameter SCREEN_WIDTH = 16'd320;
parameter SCREEN_HEIGHT = 16'd240;
parameter Number_Of_Pixels = 32'd240*32'd320*32'd2; // 像素点个数
localparam S_IDLE = 4'b0001;
localparam S_DATA = 4'b0010; //发送数据
localparam S_DELAY = 4'b0100; //延时
localparam S_FRAME_SYNC = 4'b1000; // 帧同步
localparam DELAY_5clk = 'd5 ; //命令与数据之间切换等待5个时钟周期
reg [ 31: 0] flush_cnt ; //刷新模块写命令/数据计数器
reg [ 12: 0] delay_cnt ; //延迟计数
reg [ 3: 0] state ,next_state; //状态寄存器
//为写数据状态时,请求拉高
assign spi_send_flush_req_o = (state == S_DATA) ? 1'b1 : 1'b0;
//为延迟状态或帧同步状态时结束信号拉高
assign spi_send_flush_end_o = (state == S_DELAY || state == S_FRAME_SYNC) ? 1'b1 : 1'b0;
//当发送完数据时,且写命令/数据计数器 计数到10时,像素点数据刷新拉高
assign spi_screen_flush_updte_o = ( spi_send_flush_ack_i == 1'b1 && flush_cnt >= 'd10 ) ? 1'b1 : 1'b0;
//为帧同步状态时,屏幕帧同步拉高,产生帧同步信号
assign spi_screen_flush_fsync_o = ( state == S_FRAME_SYNC ) ? 1'b1 : 1'b0;
always@(posedge sys_clk or negedge sys_rst_n) begin
if( sys_rst_n == 1'b0 )
state <= S_IDLE;
else
state <= next_state; //将状态赋值为下一状态
end
always@(*) begin
case(state)
S_IDLE:
if( tft_screen_flush_req_i == 1'b1 ) //初始化完成之后此信号拉高模块进入刷新模式
next_state = S_DATA;
else
next_state = S_IDLE;
S_DATA:
if( spi_send_flush_ack_i == 1'b1 && flush_cnt <= 'd10 ) //地址设置需要发送命令和写入四个参数,设置XY地址共需要发送10个数据
next_state = S_DELAY; //加上发送内存写入命令共需要11个数据,所以flush_cnt计数到10
else if( spi_send_flush_ack_i == 1'b1 && flush_cnt == (Number_Of_Pixels + 'd10))//一帧图像数据发送完成,跳转到帧同步状态
next_state = S_FRAME_SYNC;
else
next_state = S_DATA;
S_DELAY:
if( delay_cnt == DELAY_5clk) //延迟5给时钟周期后,跳转到S_DATA状态
next_state = S_DATA;
else
next_state = S_DELAY;
S_FRAME_SYNC:
next_state = S_IDLE;
default: next_state = S_IDLE;
endcase
end
//发送数据计数
always@(posedge sys_clk or negedge sys_rst_n) begin
if( sys_rst_n == 1'b0 )
flush_cnt <= 'd0;
else if( spi_send_flush_ack_i == 1'b1 && flush_cnt == (Number_Of_Pixels + 'd10))//一帧图像数据发送完成,flush_cnt清零
flush_cnt <= 'd0;
else if( spi_send_flush_ack_i == 1'b1 )//发送完一个数据,flush_cnt加1
flush_cnt <= flush_cnt + 1'b1;
else
flush_cnt <= flush_cnt;
end
//延时计数
always@(posedge sys_clk or negedge sys_rst_n) begin
if( sys_rst_n == 1'b0)
delay_cnt <= 'd0;
else if( state == S_DELAY)//为延迟状态时,delay_cnt加1
delay_cnt <= delay_cnt + 1'b1;
else
delay_cnt <= 'd0;
end
//tft_screen_flush_dc_o=0时写命令
//tft_screen_flush_dc_o=1时写数据
always @(*) begin
case (flush_cnt)
// 前 11 个(发"设置窗口"命令),写区域等参数
'd0: begin tft_screen_flush_data_o = 8'h2A; tft_screen_flush_dc_o = 1'b0; end // 命令:设列(横向)范围
'd1: begin tft_screen_flush_data_o = 8'h00; tft_screen_flush_dc_o = 1'b1; end // 参数:列从 0x00 开始(高8位)
'd2: begin tft_screen_flush_data_o = 8'h00; tft_screen_flush_dc_o = 1'b1; end // 参数:列从 0x00 开始(低8位)
'd3: begin tft_screen_flush_data_o = SCREEN_WIDTH[15:8]; tft_screen_flush_dc_o = 1'b1; end // 参数:列到 319 结束(高8位)
'd4: begin tft_screen_flush_data_o = SCREEN_WIDTH[7:0] - 1'b1; tft_screen_flush_dc_o = 1'b1; end // 参数:列到 319 结束(低8位)
'd5: begin tft_screen_flush_data_o = 8'h2B; tft_screen_flush_dc_o = 1'b0; end // 命令:设行(纵向)范围
'd6: begin tft_screen_flush_data_o = 8'h00; tft_screen_flush_dc_o = 1'b1; end // 参数:行从 0 开始(高8位)
'd7: begin tft_screen_flush_data_o = 8'h00; tft_screen_flush_dc_o = 1'b1; end // 参数:行从 0 开始(低8位)
'd8: begin tft_screen_flush_data_o = SCREEN_HEIGHT[15:8]; tft_screen_flush_dc_o = 1'b1; end // 参数:行到 239 结束(高8位)
'd9: begin tft_screen_flush_data_o = SCREEN_HEIGHT[7:0] - 1'b1; tft_screen_flush_dc_o = 1'b1; end // 参数:行到 239 结束(低8位)
'd10: begin tft_screen_flush_data_o = 8'h2C; tft_screen_flush_dc_o = 1'b0; end // 命令:开始写像素!
// === 第 11 个以后:发像素字节,共 153600 个 ===
default: begin
tft_screen_flush_data_o = spi_screen_flush_data_i; // 直接把 top 送来的像素字节,原样搬出去
tft_screen_flush_dc_o = 1'b1; // 这些是"数据"
end
endcase
end
endmodule2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
`timescale 1ns / 1ps
module spi_tft_screen_driver(
input sys_clk ,
input sys_rst_n ,
//用户接口
input [ 7: 0] spi_screen_flush_data_i ,//屏幕显示数据
output spi_screen_flush_updte_o ,//像素点数据刷新
output spi_screen_flush_fsync_o ,//屏幕帧同步
//------
//spi tft screen 屏幕接口
output lcd_spi_sclk ,// 屏幕spi时钟接口
output lcd_spi_mosi ,// 屏幕spi数据接口
output lcd_spi_cs ,// 屏幕spi使能接口
output lcd_dc ,// 屏幕 数据/命令 接口
output lcd_reset ,// 屏幕复位接口
output lcd_blk // 屏幕背光接口
);
//屏幕尺寸
parameter SCREEN_WIDTH = 32'd320;
parameter SCREEN_HEIGHT = 32'd240;
//总模块信号
reg lcd_init_done ;//初始化完成标志信号
wire spi_start ;//spi开始信号
wire spi_end ;//spi结束信号
wire [ 7: 0] spi_send_data ;//spi发送数据
wire spi_send_ack ;//spi数据发送完成响应
wire lcd_dc_i ;//lcd数据/命令信号
// 初始化模块信号
wire tft_screen_init_req ;//初始化模块请求
wire tft_screen_init_ack ;//初始化模块完成
wire [ 7: 0] tft_screen_init_data ;//初始化模块数据
wire tft_screen_init_dc ;//初始化模块dc
wire spi_send_init_req ;//spi发送数据请求
wire spi_send_init_end ;//结束spi发送
wire spi_send_init_ack ;//spi数据发送完成响应
//刷新模块
wire tft_screen_flush_req ;//刷新模块请求
wire [ 7: 0] tft_screen_flush_data ;//刷新模块数据
wire tft_screen_flush_dc ;//刷新模块数据/命令信号
wire spi_send_flush_req ;//刷新模块发送请求
wire spi_send_flush_end ;//刷新模块发送结束
wire spi_send_flush_ack ;//刷新模块数据发送完成响应
//屏幕驱动信号,默认
assign lcd_reset = 1'b1;
assign lcd_blk = 1'b1;
assign tft_screen_flush_req = ( lcd_init_done == 1'b1) ? 1'b1 : 1'b0;
assign spi_send_flush_ack = ( lcd_init_done == 1'b1) ? spi_send_ack : 1'b0;
assign tft_screen_init_req = ~lcd_init_done;
assign spi_send_init_ack = ( lcd_init_done == 1'b0) ? spi_send_ack : 1'b0;
//像素初始化完成之后进入显示数据刷新模式
assign spi_start = ( lcd_init_done == 1'b0) ? spi_send_init_req : spi_send_flush_req;
assign spi_end = ( lcd_init_done == 1'b0) ? spi_send_init_end : spi_send_flush_end;
assign spi_send_data = ( lcd_init_done == 1'b0) ? tft_screen_init_data : tft_screen_flush_data;
assign lcd_dc_i = ( lcd_init_done == 1'b0) ? tft_screen_init_dc : tft_screen_flush_dc;
//初始化是否完成
always@(posedge sys_clk or negedge sys_rst_n) begin
if( sys_rst_n == 1'b0)
lcd_init_done <= 1'b0;
else if( tft_screen_init_ack == 1'b1) //初始化模块完成 ,lcd_init_done拉高
lcd_init_done <= 1'b1;
else
lcd_init_done <= lcd_init_done;
end
//刷新模块
spi_tft_screen_flush #(
.SCREEN_WIDTH (SCREEN_WIDTH ),
.SCREEN_HEIGHT (SCREEN_HEIGHT ),
.Number_Of_Pixels (SCREEN_WIDTH*SCREEN_HEIGHT*'d2)
)spi_tft_screen_flush_inst(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
//用户接口
.spi_screen_flush_data_i (spi_screen_flush_data_i ),//屏幕显示数据
.spi_screen_flush_updte_o (spi_screen_flush_updte_o ),//像素点数据刷新
.spi_screen_flush_fsync_o (spi_screen_flush_fsync_o ),//屏幕帧同步
.tft_screen_flush_req_i (tft_screen_flush_req ),//刷新请求
.tft_screen_flush_data_o (tft_screen_flush_data ),//刷新数据
.tft_screen_flush_dc_o (tft_screen_flush_dc ),//刷新dc
.spi_send_flush_req_o (spi_send_flush_req ),//spi发送数据请求
.spi_send_flush_end_o (spi_send_flush_end ),//结束spi发送
.spi_send_flush_ack_i (spi_send_flush_ack ) //spi一个数据发送完成
);
//初始化模块
spi_tft_screen_init #(
.SCREEN_WIDTH (SCREEN_WIDTH ),
.SCREEN_HEIGHT (SCREEN_HEIGHT )
) spi_tft_screen_init_inst(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.tft_screen_init_req_i (tft_screen_init_req ),//初始化请求
.tft_screen_init_ack_o (tft_screen_init_ack ),//初始化完成
.tft_screen_init_data_o (tft_screen_init_data ),//初始化数据
.tft_screen_init_dc_o (tft_screen_init_dc ),//初始化dc
.spi_send_init_req_o (spi_send_init_req ),//spi发送数据请求
.spi_send_init_end_o (spi_send_init_end ),//结束spi发送
.spi_send_init_ack_i (spi_send_init_ack ) //spi一个数据发送完成
);
//spi主机驱动模块
spi_master_driver spi_master_driver_inst(
//系统接口
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
//用户接口
.spi_start_i (spi_start ),// spi开始信号
.spi_end_i (spi_end ),// spi结束信号
.spi_send_data_i (spi_send_data ),// spi发送数据
.spi_send_ack_o (spi_send_ack ),// spi发送8bit数据完成信号
.lcd_dc_i (lcd_dc_i ),//数据还是命令信号输入
.lcd_dc (lcd_dc ),//数据还是命令信号输出
//spi 端口
.spi_sclk (lcd_spi_sclk ),
.spi_mosi (lcd_spi_mosi ),
.spi_cs (lcd_spi_cs )
);
endmodule2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
`timescale 1ns / 1ps
//整个spi屏幕控制顶层,用户只需要提供显示数据就可以使用这个顶层进行spi屏幕显示
module spi_screen_top(
input sys_clk ,
input sys_rst_n ,
//用户信号
output flush_data_update_o ,//更新当前坐标点显示数据使能
input [ 15: 0] flush_data_i ,//当前坐标点显示的数据
output [ 15: 0] flush_addr_width_o ,//当前刷新的x坐标
output [ 15: 0] flush_addr_height_o ,//当前刷新的y坐标
//spi tft screen 屏幕接口
output lcd_spi_sclk ,// 屏幕spi时钟接口
output lcd_spi_mosi ,// 屏幕spi数据接口
output lcd_spi_cs ,// 屏幕spi使能接口
output lcd_dc ,// 屏幕 数据/命令 接口
output lcd_reset ,// 屏幕复位接口
output lcd_blk // 屏幕背光接口
);
//屏幕尺寸
parameter SCREEN_WIDTH = 32'd320;
parameter SCREEN_HEIGHT = 32'd240;
//屏幕用户接口
wire [ 7: 0] spi_screen_flush_data ;//屏幕显示数据
wire spi_screen_flush_updte ;//像素点数据刷新
wire spi_screen_flush_fsync ;//屏幕帧同步
//长宽计数器
reg [ 15: 0] width_cnt ;
reg [ 15: 0] height_cnt ;
//数据更新
reg data_update_cnt ;
//更新数据寄存器
reg [ 15: 0] flush_data_reg ;
//更新数据使能寄存器
reg flush_updte_en;
assign spi_screen_flush_data = flush_data_reg[15:8];//高位发送
//当发送完16bit的图像数据时,坐标点显示数据使能拉高
assign flush_data_update_o = (spi_screen_flush_updte == 1'b1 && data_update_cnt == 1'b0 && flush_updte_en == 1'b1) ? 1'b1 : 1'b0;
//将长宽计数器连接到输出
assign flush_addr_width_o = width_cnt;
assign flush_addr_height_o = height_cnt;
//1.flush_updte_en,发完8位就拉高,发完一帧清0
always@(posedge sys_clk or negedge sys_rst_n) begin
if( sys_rst_n == 1'b0)
flush_updte_en <= 'd0;
else if( spi_screen_flush_fsync == 1'b1 ) //刷新模块发送完一帧数据,清零
flush_updte_en <= 'd0;
else if( spi_screen_flush_updte == 1'b1) //发送完8位数据后,flush_updte_en拉高
flush_updte_en <= 'd1;
else
flush_updte_en <= flush_updte_en;
end
//2.data_update_cnt,发送高八位时data_update_cnt=1,发送低八位时data_update_cnt=0
always@(posedge sys_clk or negedge sys_rst_n) begin
if( sys_rst_n == 1'b0)
data_update_cnt <= 'd0;
else if( spi_screen_flush_fsync == 1'b1 ) //刷新模块发送完一帧数据
data_update_cnt <= 'd0;
else if( spi_screen_flush_updte == 1'b1) //刷新模块通过spi发送完一个8bit数据,data_update_cnt加一
data_update_cnt <= data_update_cnt + 1'b1; //发送高八位时data_update_cnt=1,发送低八位时data_update_cnt=0,
else //所以发送完16bit数据时data_update_cnt=0
data_update_cnt <= data_update_cnt;
end
//3.flush_data_reg,
always@(posedge sys_clk or negedge sys_rst_n) begin
if( sys_rst_n == 1'b0)
flush_data_reg <= 'd0;
else if( spi_screen_flush_updte == 1'b1) //刷新模块发送完一个图像数据
if( data_update_cnt == 1'b0 ) //data_update_cnt=0时,发送完了低八位,寄存新数据
flush_data_reg <= flush_data_i;
else
flush_data_reg <= flush_data_reg << 8; //data_update_cnt=1时,发送完了高八位,将数据向左移动8位,发送低八位数据
else
flush_data_reg <= flush_data_reg;
end
//一帧图像刷洗完之后,开始横着加一写像素点
always@(posedge sys_clk or negedge sys_rst_n) begin
if( sys_rst_n == 1'b0 )
width_cnt <= 'd0;
else if( spi_screen_flush_fsync == 1'b1 ) //一帧图像数据发送完,计数器清零
width_cnt <= 'd0;
else if( flush_data_update_o)//发送完当前像素点16bit图像数据时
if( width_cnt == (SCREEN_WIDTH-1)) //计数到计数器最大值时清零
width_cnt <= 'd0;
else
width_cnt <= width_cnt + 1'b1; //width_cnt计数器加1
else
width_cnt <= width_cnt;
end
//一行写满后然后换一行写
always@(posedge sys_clk or negedge sys_rst_n) begin
if( sys_rst_n == 1'b0 )
height_cnt <= 'd0;
else if( spi_screen_flush_fsync == 1'b1) //一帧图像数据发送完,计数器清零
height_cnt <= 'd0;
else if( width_cnt == (SCREEN_WIDTH-1) && flush_data_update_o)//当图像绘制完一行时
if( height_cnt == (SCREEN_HEIGHT-1)) //计数到计数器最大值时清零
height_cnt <= 'd0;
else
height_cnt <= height_cnt + 1'b1; //height_cnt计数器加一
else
height_cnt <= height_cnt;
end
spi_tft_screen_driver spi_tft_screen_driver_inst(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
//用户接口
.spi_screen_flush_data_i (spi_screen_flush_data ),//屏幕显示数据
.spi_screen_flush_updte_o (spi_screen_flush_updte ),//像素点数据刷新//在进行数据到tft屏幕的显示
.spi_screen_flush_fsync_o (spi_screen_flush_fsync ),//屏幕帧同步
//spi tft screen 屏幕接口
.lcd_spi_sclk (lcd_spi_sclk ),// 屏幕spi时钟接口
.lcd_spi_mosi (lcd_spi_mosi ),// 屏幕spi数据接口
.lcd_spi_cs (lcd_spi_cs ),// 屏幕spi使能接口
.lcd_dc (lcd_dc ),// 屏幕 数据/命令 接口
.lcd_reset (lcd_reset ),// 屏幕复位接口
.lcd_blk (lcd_blk ) // 屏幕背光接口
);
endmodule2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
`timescale 1ns / 1ps
// //模式0:CPOL= 0,CPHA=0。SCK串行时钟线空闲是为低电平,数据在SCK时钟的上升沿被采样,数据在SCK时钟的下降沿切换
// //模式1:CPOL= 0,CPHA=1。SCK串行时钟线空闲是为低电平,数据在SCK时钟的下降沿被采样,数据在SCK时钟的上升沿切换
// //模式2:CPOL= 1,CPHA=0。SCK串行时钟线空闲是为高电平,数据在SCK时钟的下降沿被采样,数据在SCK时钟的上升沿切换
// //模式3:CPOL= 1,CPHA=1。SCK串行时钟线空闲是为高电平,数据在SCK时钟的上升沿被采样,数据在SCK时钟的下降沿切换
//模式3:CPOL= 1,CPHA=1。SCK串行时钟线空闲是为高电平,数据在SCK时钟的上升沿被采样,数据在SCK时钟的下降沿切换
module spi_master_driver(
//系统接口
input sys_clk ,
input sys_rst_n ,
//用户接口
input spi_start_i ,// spi开始信号
input spi_end_i ,// spi结束信号
input [ 7: 0] spi_send_data_i ,// spi发送数据
output reg spi_send_ack_o ,// spi发送8bit数据完成信号
input lcd_dc_i ,//数据还是命令信号输入
output reg lcd_dc ,//数据还是命令信号输出
//spi 端口
output reg spi_sclk ,//spi时钟
output reg spi_mosi ,//spi数据
output spi_cs //spi使能
);
localparam IDLE = 3'b001, //空闲状态
DATA = 3'b010, //发送数据状态
STOP = 3'b100; //停止状态
reg [2:0] cure_state;//状态寄存器
reg [2:0] next_state;//下一状态寄存器
reg [7:0] spi_send_data_reg ;//数据寄存器
reg [3:0] spi_send_data_bit_cnt ;//数据发送bit计数器
assign spi_cs = (cure_state == IDLE) ?1:0; //片选信号,低电平有效,为空闲状态时拉高
always @(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0 )
cure_state <= IDLE;
else
cure_state <= next_state;
always @(*)
case(cure_state)
IDLE:begin //空闲
if(spi_start_i) //spi_start 开始信号来临时,开始进行数据发送
next_state = DATA;
else
next_state = IDLE;
end
DATA:begin //发送数据
if(spi_send_data_bit_cnt == 7 && spi_sclk == 1'b0) //字节发送完毕
next_state = STOP;
else
next_state = DATA;
end
STOP:next_state = IDLE; //停止
default:next_state = IDLE;
endcase
//发送数据缓存
always@(posedge sys_clk or negedge sys_rst_n) begin
if( sys_rst_n == 1'b0 )
spi_send_data_reg <= 'd0;
else if(spi_send_data_bit_cnt == 'd0) //8bit数据发送完成后,缓存新的数据
spi_send_data_reg <= spi_send_data_i;
else
spi_send_data_reg <= spi_send_data_reg;
end
always@(posedge sys_clk or negedge sys_rst_n) begin
if( sys_rst_n == 1'b0 )
spi_send_ack_o <= 'd0;
else if(spi_send_data_bit_cnt == 'd7 && spi_sclk == 1'b0 && spi_cs == 1'b0) //发送完8位数据后,spi发送8bit数据完成信号拉高
spi_send_ack_o <= 'd1;
else
spi_send_ack_o <= 'd0;
end
//数据是命令还是数据
always@(posedge sys_clk or negedge sys_rst_n) begin
if( sys_rst_n == 1'b0)
lcd_dc <= 1'b1;
else if( spi_start_i == 1'b1) //接收到开始信号时,lcd_dc赋值为lcd_dc_i
lcd_dc <= lcd_dc_i;
else
lcd_dc <= lcd_dc;
end
//产生spi时钟
always@(posedge sys_clk or negedge sys_rst_n) begin
if( sys_rst_n == 1'b0)
spi_sclk <= 1'b1;
else if(cure_state != DATA )
spi_sclk <= 1'b1;
else if( spi_cs == 1'b0 )
spi_sclk <= ~spi_sclk; //当spi_cs低电平时,翻转spi_sclk,生成40ns周期的sclk时钟
else
spi_sclk <= 1'b1;
end
//数据发送bit数寄存器
always@(posedge sys_clk or negedge sys_rst_n) begin
if( sys_rst_n == 1'b0)
spi_send_data_bit_cnt <= 'd0;
else if( spi_cs == 1'b0 && spi_sclk == 1'b0) //使用SPI模式三,所以数据在下降沿切换,所以当时钟为低电平时,数据计数器加1
if( spi_send_data_bit_cnt == 'd7)//发送完8位数据,清零
spi_send_data_bit_cnt <= 'd0;
else
spi_send_data_bit_cnt <= spi_send_data_bit_cnt + 1'b1;
else if( spi_cs == 1'b0)
spi_send_data_bit_cnt <= spi_send_data_bit_cnt;//在时钟上升沿时,保持不变
else
spi_send_data_bit_cnt <= 'd0;
end
//spi数据发送
always@(posedge sys_clk or negedge sys_rst_n) begin
if( sys_rst_n == 1'b0)
spi_mosi <= 1'b1;
else if( spi_cs == 1'b0)
spi_mosi <= spi_send_data_reg['d7 - spi_send_data_bit_cnt];//将数据从高位到低位逐位发送
else
spi_mosi <= spi_mosi;
end
endmodule2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
5、仿真编写
5.1、仿真代码
在此程序中仿真主要是为了验证代码中的 spi 逻辑没问题,我们给出仿真代码。 ·注意在仿真的时候我们要先去spi_screen_init模块里面把里面延时的两个延时
localparam DELAY_255ms = 32'd12_750_000; // 长延时:255ms
localparam DELAY_200us = 32'd10_000; // 短延时:200us2
把这两个 localparam改成parameter
接下来给出仿真代码
`timescale 1ns / 1ps
module tb_test;
reg clk;
reg rst_n;
wire lcd_spi_sclk;
wire lcd_spi_mosi;
wire lcd_spi_cs;
wire lcd_dc;
wire lcd_reset;
wire lcd_blk;
//被测 test 模块
test uut(
.sys_clk (clk),
.sys_rst_n (rst_n),
.lcd_spi_sclk (lcd_spi_sclk),
.lcd_spi_mosi (lcd_spi_mosi),
.lcd_spi_cs (lcd_spi_cs),
.lcd_dc (lcd_dc),
.lcd_reset (lcd_reset),
.lcd_blk (lcd_blk)
);
//缩短初始化时间
defparam uut.spi_screen_top_inst.spi_tft_screen_driver_inst.spi_tft_screen_init_inst.DELAY_255ms = 30;
defparam uut.spi_screen_top_inst.spi_tft_screen_driver_inst.spi_tft_screen_init_inst.DELAY_200us = 5;
//50MHz
initial clk = 1'b0;
always #10 clk = ~clk;
// 本设计是 SPI 模式3(CPOL=1,CPHA=1):sck 空闲为高,数据下降沿切换、
// 上升沿稳定,所以从机在 sck 上升沿采样 mosi。
reg sclk_d;
reg [2:0] bit_cnt;
reg [7:0] rx_shift;
reg [7:0] rx_data [0:255]; // 抓到的字节
reg rx_dc [0:255]; // 该字节是命令(0)还是数据(1)
integer rx_cnt;
initial begin
sclk_d = 1'b0;
bit_cnt = 3'd0;
rx_shift = 8'd0;
rx_cnt = 0;
end
always @(posedge clk) begin
if (lcd_spi_cs == 1'b0) begin // 片选有效
if (sclk_d == 1'b0 && lcd_spi_sclk == 1'b1) begin // sck 上升沿采样
rx_shift <= {rx_shift[6:0], lcd_spi_mosi};
if (bit_cnt == 3'd7) begin
rx_data[rx_cnt] <= {rx_shift[6:0], lcd_spi_mosi};
rx_dc[rx_cnt] <= lcd_dc;
rx_cnt <= rx_cnt + 1;
bit_cnt <= 3'd0;
end else begin
bit_cnt <= bit_cnt + 1'b1;
end
end
end else begin
bit_cnt <= 3'd0;
end
sclk_d <= lcd_spi_sclk;
end
//初始化检测
task check_init;
integer i;
integer err;
reg [7:0] exp_data [0:17];
reg exp_dc [0:17];
begin
err = 0;
// cmd(0) / data(1),对照 spi_tft_screen_init.v 的 case
exp_data[0] = 8'h01; exp_dc[0] = 1'b0; // SWRESET
exp_data[1] = 8'h11; exp_dc[1] = 1'b0; // SLPOUT
exp_data[2] = 8'h3A; exp_dc[2] = 1'b0; // COLMOD
exp_data[3] = 8'h55; exp_dc[3] = 1'b1; // 0x55 数据(16bit 565)
exp_data[4] = 8'h36; exp_dc[4] = 1'b0; // MADCTL
exp_data[5] = 8'h70; exp_dc[5] = 1'b1; // 0x70 数据
exp_data[6] = 8'h2A; exp_dc[6] = 1'b0; // CASET
exp_data[7] = 8'h00; exp_dc[7] = 1'b1; // 列起 高8
exp_data[8] = 8'h00; exp_dc[8] = 1'b1; // 列起 低8
exp_data[9] = 8'h01; exp_dc[9] = 1'b1; // 列止 高8 (320=0x140)
exp_data[10] = 8'h3F; exp_dc[10] = 1'b1; // 列止 低8 (0x13F)
exp_data[11] = 8'h2B; exp_dc[11] = 1'b0; // RASET
exp_data[12] = 8'h00; exp_dc[12] = 1'b1; // 行起 高8
exp_data[13] = 8'h00; exp_dc[13] = 1'b1; // 行起 低8
exp_data[14] = 8'h00; exp_dc[14] = 1'b1; // 行止 高8 (240=0xF0)
exp_data[15] = 8'hEF; exp_dc[15] = 1'b1; // 行止 低8 (0xEF)
exp_data[16] = 8'h13; exp_dc[16] = 1'b0; // NORON
exp_data[17] = 8'h29; exp_dc[17] = 1'b0; // DISPON
for (i = 0; i < 18; i = i + 1) begin
if (rx_data[i] !== exp_data[i] || rx_dc[i] !== exp_dc[i]) begin
$display("FAIL: init 第%0d字节 期望 data=%h dc=%b 实收 data=%h dc=%b",
i, exp_data[i], exp_dc[i], rx_data[i], rx_dc[i]);
err = err + 1;
end
end
if (err == 0)
$display("PASS: 初始化 18 字节命令序列正确");
end
endtask
// 主流程
integer errors;
initial begin
errors = 0;
// 复位
rst_n = 1'b0;
repeat (20) @(posedge clk);
rst_n = 1'b1;
// int erroe是32位,所以这里只有32位。初始化 18 字节 + 刷窗口 11 字节 + 前 2 个像素字节
// (索引 0~17 是 init,18~28 是刷窗口,29/30 是像素)
wait (rx_cnt >= 31);
repeat (5) @(posedge clk);
// 自检 1:初始化序列
check_init;
// 自检 2:刷窗口(CASET 0x2A + 列参数 + RASET 0x2B + 行参数 + RAMWR 0x2C)
if (rx_data[18] !== 8'h2A) begin
$display("FAIL: 刷窗口第1字节 期望 2A 实收 %h", rx_data[18]);
errors = errors + 1;
end
if (rx_data[19] !== 8'h00 || rx_data[20] !== 8'h00 ||
rx_data[21] !== 8'h01 || rx_data[22] !== 8'h3F) begin
$display("FAIL: CASET 列范围错误 实收 %h %h %h %h",
rx_data[19], rx_data[20], rx_data[21], rx_data[22]);
errors = errors + 1;
end
if (rx_data[23] !== 8'h2B || rx_data[24] !== 8'h00 || rx_data[25] !== 8'h00 ||
rx_data[26] !== 8'h00 || rx_data[27] !== 8'hEF) begin
$display("FAIL: RASET 行范围错误 实收 %h %h %h %h %h",
rx_data[23], rx_data[24], rx_data[25], rx_data[26], rx_data[27]);
errors = errors + 1;
end
if (rx_data[28] !== 8'h2C) begin
$display("FAIL: 写像素命令 期望 2C 实收 %h", rx_data[28]);
errors = errors + 1;
end
// 自检 3:第一个像素 = 坐标(0,0) = 九宫格红色 0xF800
// (test.v 里 width 0~106、height 0~80 是红,高字节 F8 低字节 00)
if (rx_data[29] !== 8'hF8 || rx_data[30] !== 8'h00) begin
$display("FAIL: 第一个像素 期望红色 F8 00 实收 %h %h", rx_data[29], rx_data[30]);
errors = errors + 1;
end
if (errors == 0) begin
$display("PASS: test 顶层 初始化+刷窗口+九宫格像素 全部正确");
$display(" 共抓到 %0d 个 SPI 字节", rx_cnt);
end else begin
$display("FAIL: test 顶层共 %0d 个错误", errors);
end
$finish;
end
// 防止超时
initial begin
#50_000_000; // 50ms
$display("TIMEOUT");
$finish;
end
// 波形
initial begin
$dumpfile("tb_test.vcd");
$dumpvars(0, tb_test);
end
endmodule2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
5.2、仿真数据
编译运行后,我们来看一下 transcript 和波形
可以看到在这里面打印了两行`pass`说明数据是没问题的,后面那串乱码是`modelsim se`识别中文的问题,不用在意。再看波形
可以看到 CS 下拉之后开始正常传数据,说明代码逻辑没问题。
6、I/O 绑定
我们的引脚绑定如图所示,我们需要杜邦线将 逻辑派 G1 上的拓展引脚和我们拓展屏幕的引脚相连接
具体连接如图
当然同学们也可以自行修改引脚的位置
7、程序下载
将程序烧录到板子后可以看到屏幕上是一个不同颜色的九宫格