1、模块来源
SPI 驱动的 IC 型号是 ST7735s ,是由台湾矽创(Sitronix)出的一颗 TFT 屏驱动/控制芯片
ST7735S 是一块1.8英寸采用SPI通信的TFT 全彩屏,支持RGB444,RGB565,RGB666三种
我们此例程将通过 spi 协议来控制屏幕
如果需要具体的资料可以自行百度,关键词:st7735s 中文手册
ST7735S接口说明
- BLK:这里用不到,可以不用连接
- CS:片选信号
- DC:数据模式选择信号 1为写命令/0为写数据
- RST:复位信号
- SDA:SPI数据线
- SCL:SPI时钟线
- VDD:兼容+5V和+3.3V
- GND:接地
2、实验原理
2.1 SPI 协议介绍
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 公司的 ST7735S 芯片数据手册,我们本次实验所使用的 SPI 通讯模式即为此模式(模式 3)。
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、时序说明
ST7735S 的控制流程如下图
图片来源于csdn博主,右下角水印为出处ST7735S 的发送时序图如图,其中
- CSX 为片选信号,低电平有效
- D/CX为命令/数据信号 0时表示 SDA 中的数据为命令,1 表示 SDA 中的数据是要写入的数据
- SCL为 spi 的时钟
- SDA 为数据
这里建立时间和保持时间的概念我们不再赘述
4、代码编写
`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坐标
// 显示九宫格测试图片 128*160
always @(posedge sys_clk or negedge sys_rst_n) begin
if (sys_rst_n == 1'b0)
flush_data <= 16'd0;
// 第一行
else if ((flush_addr_width >= 'd0 && flush_addr_width < 'd43) && (flush_addr_height >= 'd0 && flush_addr_height
< 'd53))
flush_data <= 16'hF800;//红 1111 1000 0000 0000
else if ((flush_addr_width >= 'd43 && flush_addr_width < 'd86) && (flush_addr_height >= 'd0 && flush_addr_height <
'd53))
flush_data <= 16'h07E0;//绿 0000 0111 1110 0000
else if ((flush_addr_width >= 'd86 && flush_addr_width < 'd128) && (flush_addr_height >= 'd0 && flush_addr_height
< 'd53))
flush_data <= 16'h001F;//蓝 0000 0000 0001 1111
// 第二行
else if ((flush_addr_width >= 'd0 && flush_addr_width < 'd43) && (flush_addr_height >= 'd53 && flush_addr_height
< 'd106))
flush_data <= 16'h07E0;//绿
else if ((flush_addr_width >= 'd43 && flush_addr_width < 'd86) && (flush_addr_height >= 'd53 && flush_addr_height
< 'd106))
flush_data <= 16'h001F;//蓝
else if ((flush_addr_width >= 'd86 && flush_addr_width < 'd128) && (flush_addr_height >= 'd53 && flush_addr_height
< 'd106))
flush_data <= 16'hF800;//红
// 第三行
else if ((flush_addr_width >= 'd0 && flush_addr_width < 'd43) && (flush_addr_height >= 'd106 &&
flush_addr_height < 'd160))
flush_data <= 16'h001F;//蓝
else if ((flush_addr_width >= 'd43 && flush_addr_width < 'd86) && (flush_addr_height >= 'd106 && flush_addr_height
< 'd160))
flush_data <= 16'hF800;//红
else if ((flush_addr_width >= 'd86 && flush_addr_width < 'd128) && (flush_addr_height >= 'd106 &&
flush_addr_height < 'd160))
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
71
72
73
74
75
76
77
78
79
80
`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'd128;
parameter SCREEN_HEIGHT = 16'd160;
// 屏幕偏移量:绿标签 ST7735S 列+2、行+1,不同标签见文档说明
localparam COL_OFFSET = 16'd2;
localparam ROW_OFFSET = 16'd1;
localparam COL_END = COL_OFFSET + SCREEN_WIDTH - 16'd1; // 129
localparam ROW_END = ROW_OFFSET + SCREEN_HEIGHT - 16'd1; // 160
localparam INIT_TOTAL = 'd87; // 初始化共 87 个字节(0~86)
// 延时:sys_clk = 50MHz,1 个时钟 = 20ns
localparam DELAY_120ms = 32'd6_000_000; // 复位后延时
localparam DELAY_500ms = 32'd25_000_000; // 唤醒后延时
localparam DELAY_10ms = 32'd500_000; // 正常显示模式延时
localparam DELAY_100ms = 32'd5_000_000; // 开显示延时
localparam DELAY_200us = 32'd10_000; // 命令之间的短延时
localparam S_IDLE = 4'b0001;//初始状态:空闲
localparam S_SEND_DATA = 4'b0010;//发送数据状态
localparam S_DELAY = 4'b0100;//延迟状态,发完一个数据之间等待的状态
localparam S_ACK = 4'b1000;//响应状态,全部发完
reg [ 7: 0] init_cnt ;//初始化命令/数据计数 0~86
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 == INIT_TOTAL )//发完最后一个字节 DISPON,延时后进响应状态
if( delay_cnt == DELAY_100ms)
next_state <= S_ACK;
else
next_state <= S_DELAY;
else if( init_cnt == 'd1 )//发完 SWRESET,延时 120ms
if( delay_cnt == DELAY_120ms)
next_state <= S_SEND_DATA;
else
next_state <= S_DELAY;
else if( init_cnt == 'd2 )//发完 SLPOUT,延时 500ms
if( delay_cnt == DELAY_500ms)
next_state <= S_SEND_DATA;
else
next_state <= S_DELAY;
else if( init_cnt == 'd86 )//发完 NORON,延时 10ms
if( delay_cnt == DELAY_10ms)
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; tft_screen_init_dc_o = 1'b0; end // SWRESET 软复位
'd1: begin tft_screen_init_data_o = 8'h11; tft_screen_init_dc_o = 1'b0; end // SLPOUT 唤醒
'd2: begin tft_screen_init_data_o = 8'hB1; tft_screen_init_dc_o = 1'b0; end // FRMCTR1 帧率控制
'd3: begin tft_screen_init_data_o = 8'h01; tft_screen_init_dc_o = 1'b1; end
'd4: begin tft_screen_init_data_o = 8'h2C; tft_screen_init_dc_o = 1'b1; end
'd5: begin tft_screen_init_data_o = 8'h2D; tft_screen_init_dc_o = 1'b1; end
'd6: begin tft_screen_init_data_o = 8'hB2; tft_screen_init_dc_o = 1'b0; end // FRMCTR2
'd7: begin tft_screen_init_data_o = 8'h01; tft_screen_init_dc_o = 1'b1; end
'd8: begin tft_screen_init_data_o = 8'h2C; tft_screen_init_dc_o = 1'b1; end
'd9: begin tft_screen_init_data_o = 8'h2D; tft_screen_init_dc_o = 1'b1; end
'd10: begin tft_screen_init_data_o = 8'hB3; tft_screen_init_dc_o = 1'b0; end // FRMCTR3
'd11: begin tft_screen_init_data_o = 8'h01; tft_screen_init_dc_o = 1'b1; end
'd12: begin tft_screen_init_data_o = 8'h2C; tft_screen_init_dc_o = 1'b1; end
'd13: begin tft_screen_init_data_o = 8'h2D; tft_screen_init_dc_o = 1'b1; end
'd14: begin tft_screen_init_data_o = 8'h01; tft_screen_init_dc_o = 1'b1; end
'd15: begin tft_screen_init_data_o = 8'h2C; tft_screen_init_dc_o = 1'b1; end
'd16: begin tft_screen_init_data_o = 8'h2D; tft_screen_init_dc_o = 1'b1; end
'd17: begin tft_screen_init_data_o = 8'hB4; tft_screen_init_dc_o = 1'b0; end // INVCTR 反色控制
'd18: begin tft_screen_init_data_o = 8'h07; tft_screen_init_dc_o = 1'b1; end
'd19: begin tft_screen_init_data_o = 8'hC0; tft_screen_init_dc_o = 1'b0; end // PWCTR1 电源控制
'd20: begin tft_screen_init_data_o = 8'hA2; tft_screen_init_dc_o = 1'b1; end
'd21: begin tft_screen_init_data_o = 8'h02; tft_screen_init_dc_o = 1'b1; end
'd22: begin tft_screen_init_data_o = 8'h84; tft_screen_init_dc_o = 1'b1; end
'd23: begin tft_screen_init_data_o = 8'hC1; tft_screen_init_dc_o = 1'b0; end // PWCTR2
'd24: begin tft_screen_init_data_o = 8'hC5; tft_screen_init_dc_o = 1'b1; end
'd25: begin tft_screen_init_data_o = 8'hC2; tft_screen_init_dc_o = 1'b0; end // PWCTR3
'd26: begin tft_screen_init_data_o = 8'h0A; tft_screen_init_dc_o = 1'b1; end
'd27: begin tft_screen_init_data_o = 8'h00; tft_screen_init_dc_o = 1'b1; end
'd28: begin tft_screen_init_data_o = 8'hC3; tft_screen_init_dc_o = 1'b0; end // PWCTR4
'd29: begin tft_screen_init_data_o = 8'h8A; tft_screen_init_dc_o = 1'b1; end
'd30: begin tft_screen_init_data_o = 8'h2A; tft_screen_init_dc_o = 1'b1; end
'd31: begin tft_screen_init_data_o = 8'hC4; tft_screen_init_dc_o = 1'b0; end // PWCTR5
'd32: begin tft_screen_init_data_o = 8'h8A; tft_screen_init_dc_o = 1'b1; end
'd33: begin tft_screen_init_data_o = 8'hEE; tft_screen_init_dc_o = 1'b1; end
'd34: begin tft_screen_init_data_o = 8'hC5; tft_screen_init_dc_o = 1'b0; end // VMCTR1 VCOM控制
'd35: begin tft_screen_init_data_o = 8'h0E; tft_screen_init_dc_o = 1'b1; end
'd36: begin tft_screen_init_data_o = 8'h20; tft_screen_init_dc_o = 1'b0; end // INVOFF 关闭反色
'd37: begin tft_screen_init_data_o = 8'h36; tft_screen_init_dc_o = 1'b0; end // MADCTL 扫描方向
'd38: begin tft_screen_init_data_o = 8'hC8; tft_screen_init_dc_o = 1'b1; end // 竖屏 RGB
'd39: begin tft_screen_init_data_o = 8'h3A; tft_screen_init_dc_o = 1'b0; end // COLMOD 像素格式
'd40: begin tft_screen_init_data_o = 8'h05; tft_screen_init_dc_o = 1'b1; end // 16bit RGB565
'd41: begin tft_screen_init_data_o = 8'h2A; tft_screen_init_dc_o = 1'b0; end // CASET 列地址设置
'd42: begin tft_screen_init_data_o = COL_OFFSET[15:8]; tft_screen_init_dc_o = 1'b1; end // 列起始高8
'd43: begin tft_screen_init_data_o = COL_OFFSET[7:0]; tft_screen_init_dc_o = 1'b1; end // 列起始低8 = 2
'd44: begin tft_screen_init_data_o = COL_END[15:8]; tft_screen_init_dc_o = 1'b1; end // 列结束高8
'd45: begin tft_screen_init_data_o = COL_END[7:0]; tft_screen_init_dc_o = 1'b1; end // 列结束低8 = 129
'd46: begin tft_screen_init_data_o = 8'h2B; tft_screen_init_dc_o = 1'b0; end // RASET 行地址设置
'd47: begin tft_screen_init_data_o = ROW_OFFSET[15:8]; tft_screen_init_dc_o = 1'b1; end // 行起始高8
'd48: begin tft_screen_init_data_o = ROW_OFFSET[7:0]; tft_screen_init_dc_o = 1'b1; end // 行起始低8 = 1
'd49: begin tft_screen_init_data_o = ROW_END[15:8]; tft_screen_init_dc_o = 1'b1; end // 行结束高8
'd50: begin tft_screen_init_data_o = ROW_END[7:0]; tft_screen_init_dc_o = 1'b1; end // 行结束低8 = 160
'd51: begin tft_screen_init_data_o = 8'hE0; tft_screen_init_dc_o = 1'b0; end // GMCTRP1 正伽马
'd52: begin tft_screen_init_data_o = 8'h02; tft_screen_init_dc_o = 1'b1; end
'd53: begin tft_screen_init_data_o = 8'h1C; tft_screen_init_dc_o = 1'b1; end
'd54: begin tft_screen_init_data_o = 8'h07; tft_screen_init_dc_o = 1'b1; end
'd55: begin tft_screen_init_data_o = 8'h12; tft_screen_init_dc_o = 1'b1; end
'd56: begin tft_screen_init_data_o = 8'h37; tft_screen_init_dc_o = 1'b1; end
'd57: begin tft_screen_init_data_o = 8'h32; tft_screen_init_dc_o = 1'b1; end
'd58: begin tft_screen_init_data_o = 8'h29; tft_screen_init_dc_o = 1'b1; end
'd59: begin tft_screen_init_data_o = 8'h2D; tft_screen_init_dc_o = 1'b1; end
'd60: begin tft_screen_init_data_o = 8'h29; tft_screen_init_dc_o = 1'b1; end
'd61: begin tft_screen_init_data_o = 8'h25; tft_screen_init_dc_o = 1'b1; end
'd62: begin tft_screen_init_data_o = 8'h2B; tft_screen_init_dc_o = 1'b1; end
'd63: begin tft_screen_init_data_o = 8'h39; tft_screen_init_dc_o = 1'b1; end
'd64: begin tft_screen_init_data_o = 8'h00; tft_screen_init_dc_o = 1'b1; end
'd65: begin tft_screen_init_data_o = 8'h01; tft_screen_init_dc_o = 1'b1; end
'd66: begin tft_screen_init_data_o = 8'h03; tft_screen_init_dc_o = 1'b1; end
'd67: begin tft_screen_init_data_o = 8'h10; tft_screen_init_dc_o = 1'b1; end
'd68: begin tft_screen_init_data_o = 8'hE1; tft_screen_init_dc_o = 1'b0; end // GMCTRN1 负伽马
'd69: begin tft_screen_init_data_o = 8'h03; tft_screen_init_dc_o = 1'b1; end
'd70: begin tft_screen_init_data_o = 8'h1D; tft_screen_init_dc_o = 1'b1; end
'd71: begin tft_screen_init_data_o = 8'h07; tft_screen_init_dc_o = 1'b1; end
'd72: begin tft_screen_init_data_o = 8'h06; tft_screen_init_dc_o = 1'b1; end
'd73: begin tft_screen_init_data_o = 8'h2E; tft_screen_init_dc_o = 1'b1; end
'd74: begin tft_screen_init_data_o = 8'h2C; tft_screen_init_dc_o = 1'b1; end
'd75: begin tft_screen_init_data_o = 8'h29; tft_screen_init_dc_o = 1'b1; end
'd76: begin tft_screen_init_data_o = 8'h2D; tft_screen_init_dc_o = 1'b1; end
'd77: begin tft_screen_init_data_o = 8'h2E; tft_screen_init_dc_o = 1'b1; end
'd78: begin tft_screen_init_data_o = 8'h2E; tft_screen_init_dc_o = 1'b1; end
'd79: begin tft_screen_init_data_o = 8'h37; tft_screen_init_dc_o = 1'b1; end
'd80: begin tft_screen_init_data_o = 8'h3F; tft_screen_init_dc_o = 1'b1; end
'd81: begin tft_screen_init_data_o = 8'h00; tft_screen_init_dc_o = 1'b1; end
'd82: begin tft_screen_init_data_o = 8'h00; tft_screen_init_dc_o = 1'b1; end
'd83: begin tft_screen_init_data_o = 8'h02; tft_screen_init_dc_o = 1'b1; end
'd84: begin tft_screen_init_data_o = 8'h10; tft_screen_init_dc_o = 1'b1; end
'd85: begin tft_screen_init_data_o = 8'h13; tft_screen_init_dc_o = 1'b0; end // NORON 正常显示模式
'd86: begin tft_screen_init_data_o = 8'h29; tft_screen_init_dc_o = 1'b0; end // DISPON 开显示
default: begin
tft_screen_init_data_o = 8'h01;
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
214
215
216
217
218
219
220
221
`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'd128;
parameter SCREEN_HEIGHT = 16'd160;
parameter Number_Of_Pixels = 32'd128*32'd160*32'd2; // 像素点字节数
// 偏移量与初始化模块保持一致
localparam COL_OFFSET = 16'd2;
localparam ROW_OFFSET = 16'd1;
localparam COL_END = COL_OFFSET + SCREEN_WIDTH - 16'd1;
localparam ROW_END = ROW_OFFSET + SCREEN_HEIGHT - 16'd1;
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 )
next_state = S_DELAY;
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)
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 <= 'd0;
else if( spi_send_flush_ack_i == 1'b1 )
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 <= 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 // CASET
'd1: begin tft_screen_flush_data_o = COL_OFFSET[15:8]; tft_screen_flush_dc_o = 1'b1; end // 列起始高8
'd2: begin tft_screen_flush_data_o = COL_OFFSET[7:0]; tft_screen_flush_dc_o = 1'b1; end // 列起始低8
'd3: begin tft_screen_flush_data_o = COL_END[15:8]; tft_screen_flush_dc_o = 1'b1; end // 列结束高8
'd4: begin tft_screen_flush_data_o = COL_END[7:0]; tft_screen_flush_dc_o = 1'b1; end // 列结束低8
'd5: begin tft_screen_flush_data_o = 8'h2B; tft_screen_flush_dc_o = 1'b0; end // RASET
'd6: begin tft_screen_flush_data_o = ROW_OFFSET[15:8]; tft_screen_flush_dc_o = 1'b1; end // 行起始高8
'd7: begin tft_screen_flush_data_o = ROW_OFFSET[7:0]; tft_screen_flush_dc_o = 1'b1; end // 行起始低8
'd8: begin tft_screen_flush_data_o = ROW_END[15:8]; tft_screen_flush_dc_o = 1'b1; end // 行结束高8
'd9: begin tft_screen_flush_data_o = ROW_END[7:0]; tft_screen_flush_dc_o = 1'b1; end // 行结束低8
'd10: begin tft_screen_flush_data_o = 8'h2C; tft_screen_flush_dc_o = 1'b0; end // RAMWR 写像素
// 第 11 个以后:发像素字节
default: begin
tft_screen_flush_data_o = spi_screen_flush_data_i;
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'd128;
parameter SCREEN_HEIGHT = 32'd160;
localparam RESET_DELAY = 32'd500_000; // 复位脉冲 10ms @ 50MHz
//总模块信号
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数据/命令信号
reg [ 31: 0] reset_cnt ;//复位计数
wire lcd_reset_done ;//复位完成
// 初始化模块信号
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 ;//刷新模块数据发送完成响应
// 复位脉冲:上电先拉低 10ms,再拉高(ST7735S 需要硬件复位)
always@(posedge sys_clk or negedge sys_rst_n) begin
if( sys_rst_n == 1'b0 )
reset_cnt <= 'd0;
else if( reset_cnt < RESET_DELAY )
reset_cnt <= reset_cnt + 1'b1;
else
reset_cnt <= reset_cnt;
end
assign lcd_reset_done = (reset_cnt >= RESET_DELAY) ? 1'b1 : 1'b0;
assign lcd_reset = lcd_reset_done;
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_reset_done == 1'b1 ) ? ~lcd_init_done : 1'b0;
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 <= 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 ),
.spi_send_flush_req_o (spi_send_flush_req ),
.spi_send_flush_end_o (spi_send_flush_end ),
.spi_send_flush_ack_i (spi_send_flush_ack )
);
//初始化模块
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 ),
.spi_send_init_req_o (spi_send_init_req ),
.spi_send_init_end_o (spi_send_init_end ),
.spi_send_init_ack_i (spi_send_init_ack )
);
//spi主机驱动模块
spi_master_driver spi_master_driver_inst(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.spi_start_i (spi_start ),
.spi_end_i (spi_end ),
.spi_send_data_i (spi_send_data ),
.spi_send_ack_o (spi_send_ack ),
.lcd_dc_i (lcd_dc_i ),
.lcd_dc (lcd_dc ),
.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
`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'd128;
parameter SCREEN_HEIGHT = 32'd160;
//屏幕用户接口
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)
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)
data_update_cnt <= data_update_cnt + 1'b1;
else
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 )
flush_data_reg <= flush_data_i;
else
flush_data_reg <= flush_data_reg << 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)
if( width_cnt == (SCREEN_WIDTH-1))
width_cnt <= 'd0;
else
width_cnt <= width_cnt + 1'b1;
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;
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 ),
.spi_screen_flush_fsync_o (spi_screen_flush_fsync ),
//spi tft screen 屏幕接口
.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 )
);
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
// //模式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 ,//数据还是命令信号输出
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;
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)
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)
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)
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;
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;
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)
if( spi_send_data_bit_cnt == 'd7)
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
126
5、程序下载
引脚绑定如图引脚绑定后,按引脚连接对应位置,观察现象。