此程序可以实现发送端发出一个8位数字信号,接收端通过解码,将对应的数字显示管点亮
一.接收模块
module SPI_rx_exer(
input clk, //系统时钟
input spi_clk, //spi时钟
input cs_n, //cs片选信号
input rst_n, //复位信号
input spi_rx, //输入数据
output reg valid, //使能信号
output reg [7:0]rx_data //输出数据
);
wire spi_clk_negedge; //边缘信号
reg [7:0]rx_data_r;
//reg [7:0]tx_data;
reg [2:0]count;
reg [3:0]negedge_count;
reg data_ready;zhi'xing
reg data_ready_r0;
reg data_ready_r1;
reg data_ready_t;
reg spi_clk_r0;
reg spi_clk_r1;
//捕捉clk信号
always@(posedge clk)begin
spi_clk_r0<=spi_clk; //延迟捕捉1次
spi_clk_r1<=spi_clk_r0; //延迟不占2次
end
//二值选择
assign spi_clk_negedge={spi_clk_r1,spi_clk_r0}==2'b10 ? 1'b1 : 1'b0; //10捕捉下降沿,01捕捉上升沿
//计数器第二种写法
always@(posedge clk or negedge rst_n)begin //@是敏感列表,下一个时钟敏感,晚一拍的关系
if(~rst_n)begin
negedge_count<=1'b0; //初始化计数信号,此信号用于记录spi_clk_negedge的下降沿
rx_data_r<=8'd0; //初始化转存寄存器
data_ready<=1'b0; //初始化延迟信号
end
else if(spi_clk_negedge==1)begin
negedge_count<=negedge_count+4'b1; //计数
rx_data_r[7-negedge_count]<=spi_rx; //注意输入输出时从高位开始
end
else if(negedge_count==8)begin
data_ready<=1'b1; //一个信号从一个always快流向另外一个always或寄存器当中会延迟一个时钟周期
negedge_count<=1'b0; //negedge_count置零
end
end
//输出寄存器中的数据到输出信号中
always@(posedge clk or negedge rst_n)begin
if(~rst_n)
valid<=1'b0; //初始化使能信号
else if(data_ready==1)begin
valid<=1'b1; //激活使能信号
rx_data<=rx_data_r; //输出转存寄存器中的数据
end
else
valid<=1'b0; //拉低使能信号
end
/*
//计数器 //第一种写法(不用状态机)
always@(negedge spi_clk or negedge rst_n)
if (~rst_n)
count <= 3'b0;
else if(cs_n==1'b0) begin
if(count < 3'd7 )
count <= count+1;
else
count <= 3'b0;
end
always @(negedge spi_clk or negedge rst_n)begin //移位寄存器串转并
if (~rst_n)begin
rx_data_r<=8'b0;
data_ready_t<=1'b0;
end
else begin
if(cs_n==1'b0)begin
rx_data_r<={rx_data_r[6:0],spi_rx};
count<=count+1;
data_ready_t<=1'b1;
end
end
end
//快时钟预处理,and 输出数据(使能信号) //跨时钟域处理
always @(posedge clk or negedge rst_n)
if(~rst_n)begin
data_ready<=1'b0;
end
else if(count == 3'd0 && data_ready_t == 1)begin
rx_data<=rx_data_r;
data_ready<=1'b1;
end
else
data_ready<=1'b0;
//data_ready进行边缘检测
always@(clk)begin
data_ready_r0<=data_ready;
data_ready_r1<=data_ready_r0; //ready数据存在r0和r1中
end
always@(clk or negedge rst_n)begin
if(~rst_n)
valid<=1'b0;
else if({data_ready_r1,data_ready_r0}==2'b01)
valid<=1'b1;
else
valid<=1'b0;
end
//在voilog中begin end用来代替c语言中的花括号
// 在函数括号中必须定义output,input来定义具体变量的作用
//分频器
/*
always@(posedge clk negedge rst_n)begin
if(rst_n == 1'b0)
spi_clk<=1'b0;
else if(count == 9)
spi_clk<=~spi_clk;
else
spi_clk<=spi_clk;
end
*/
endmodule
二.数据发出模块
module SPI_Master(
input valid, //valid == 1数据有效时间,valid == 0无效时间
input clk, //系统时钟
input [7:0]tx_data, //输入信号tx_data,表示需要发送的数据
input rst_n, //复位,rst_n = 0时,参数回归初始值
output reg cs, //输出信号cs,表示片选信号
output spi_tx, //输出信号spi_tx,表示SPI总线上的发送数据
output reg spi_clk //输出信号spi_clk,表示SPI总线上的时钟信号
);
//状态
localparam IDLE = 2'b00; //定义一个局部参数IDLE,表示空闲状态
localparam SEND = 2'b01; //定义一个局部参数SEND,表示发送状态
reg bool = 1;
reg [1:0]current_state, next_state; //定义两个寄存器,分别表示当前状态和下一个状态
reg [3:0]count = 4'd0;
//reg send_data; //定义一个寄存器send_data,用于存储发送数据
//reg [7:0] reg_shift = 8'b0000_0001; //定义一个寄存器reg_shift,用于移位操作
reg tx_data_r; //定义一个寄存器tx_data_r,用于接收数据
reg [3:0]bit_cnt; //定义一个寄存器bit_cnt,用于计数位
assign spi_tx = tx_data_r; //将tx_data_r的值赋给spi_tx
//状态机1
always @(posedge clk or negedge rst_n)begin //敏感列表,表示在spi_clk的上升沿或rst_n的下降沿时触发该always块
if(rst_n == 1'b0)
current_state <= 2'b00; //设置为初始状态(IDLE)
else
current_state <= next_state; //将当前状态更新为下一个状态
end
//2
always @(*)begin //敏感列表,表示在任何输入变化时触发该always块
if(bool == 1)
case(current_state)
IDLE:
if(valid == 1) //如果valid信号为高电平(有效),则执行下一行代码
next_state <= SEND; //将下一个状态设置为SEND
else
next_state <= IDLE;
SEND:
if(bit_cnt == 4'd8) //如果valid信号为低电平(无效),则执行下一行代码
next_state <= IDLE; //将下一个状态设置为IDLE
else
next_state <= SEND;
endcase
else
next_state <= IDLE;
end
//3
always @(posedge spi_clk or negedge rst_n)begin
if(rst_n == 0)begin
tx_data_r <= 1'b1; //将发送数据寄存器tx_data_r设置为1
bit_cnt <= 3'd0; //将位计数器bit_cnt重置为0
end
else if (current_state == SEND && bit_cnt < 4'd8)begin
tx_data_r <= tx_data[7 - bit_cnt]; //将发送数据寄存器tx_data_r设置为tx_data数组中对应的值
bit_cnt <= bit_cnt + 1'd1; //位计数器bit_cnt加1
end
else if (current_state == SEND && bit_cnt == 4'd8)begin
bool <= 0; //将下一个状态设置为IDLE
end
else
bit_cnt <= bit_cnt;
end
//计数器 产生spi_clk
always @(posedge clk)begin
if(current_state == SEND)begin
if(count < 4'd9)
count <= count + 1'b1;
else
count <= 4'd0;
end
else if(current_state != SEND && count != 0)
count <= count + 1'b1;
end
//分频器,2/4/6分频变成原0.5/0.25频率---偶分频
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)
spi_clk <= 1'b0;
else if(current_state == SEND && bit_cnt < 4'd8 && count == 4'd4)
spi_clk <= 1;
else if(current_state == SEND && bit_cnt < 4'd8 && count == 4'd9)
spi_clk <= 0;
else if(current_state != SEND && bit_cnt == 4'd8 && count == 4'd9)
spi_clk <= 0;
else
spi_clk <= spi_clk;
end
always @(posedge clk or negedge rst_n)begin //
if(rst_n == 1'b0)
cs <= 1;
else if(current_state == SEND)
cs <= 0;
else if(current_state != SEND && spi_clk != 0)
cs <= 0;
else
cs <= 1;
end
endmodule
三.接收程序调用模块
module top(
//接收信号模块输入
//接受的数据线要在版上连线
input clk,
input spi_clk, //时钟线 L1
input cs_n, //片选线 N1
input rst_n,
input spi_rx, //接受数据线 P1
//数码管输出
output [5:0]sel,
output [7:0]seg_led
);
wire [7:0]rx_data;
wire valid;
//调用rx数据接受模块
SPI_rx_exer spi_rx_inst(
//输入信号
.spi_rx (spi_rx),
.spi_clk (spi_clk),
.cs_n (cs_n),
//版内控制信号
.rst_n (rst_n),
.clk (clk),
//输出信号
.valid (valid),
.rx_data (rx_data)
);
//调用数码管译码模块
decode_exer decoder_inst(
.decode_number (rx_data),
.sel (sel),
.seg_led (seg_led)
);
endmodule
三.烧录立化程序
module top(
//接收信号模块输入
//接受的数据线要在版上连线
input clk,
input spi_clk, //时钟线 L1
input cs_n, //片选线 N1
input rst_n,
input spi_rx, //接受数据线 P1
//数码管输出
output [5:0]sel,
output [7:0]seg_led
);
wire [7:0]rx_data;
wire valid;
//调用rx数据接受模块
SPI_rx_exer spi_rx_inst(
//输入信号
.spi_rx (spi_rx),
.spi_clk (spi_clk),
.cs_n (cs_n),
//版内控制信号
.rst_n (rst_n),
.clk (clk),
//输出信号
.valid (valid),
.rx_data (rx_data)
);
//调用数码管译码模块
decode_exer decoder_inst(
.decode_number (rx_data),
.sel (sel),
.seg_led (seg_led)
);
endmodule
四.烧录
在quartus软件上编译程序,配置对应管脚实现功能
fpga spi串口实现