SPI协议的数据读写实现(spi_slave)
一、SPI协议介绍
二、程序设计
1、spi_slave模块
- 该模块接收8路16bit的数据信号
ave1---ave8
,以及标志数据有效的信号ave_valid
; - 该模块作为SPI的slave端,可以通过
spi_miso
将ave数据发送出去;也可以通过spi_mosi
接收master端发送来的数据,并将数据再通过godata
发送出去; - 该模块采用的是
模式0:CPOL = 0,CPHA = 0
; - 该模块可以接收两种命令:读命令
COMMAND_READ = 8'hA5
、写命令COMMAND_WRITE = 8'H5A
;
`timescale 1ns/1ps
module spi_slave(
input clk,//芯片外部输入的clk_50m
input rst_n,//sys_rst模块输出的复位信号rst_n
input ave_valid,//average输出的平均值有效信号
//spi_input_chose模块的输出信号,
//sw_cnt控制spi_input_chose模块选择特定的数据输出给spi_slave
input [15:0] ave1,
input [15:0] ave2,
input [15:0] ave3,
input [15:0] ave4,
input [15:0] ave5,
input [15:0] ave6,
input [15:0] ave7,
input [15:0] ave8,
//spi协议的相关信号
input spi_cs,
input spi_sck,
input spi_mosi,
output reg spi_miso,//spi slave的数据输出
//下面3个信号是连接到para_rom1模块的,
//和para_rom1模块输出两点校正的两个参数G\O有关
output reg data_valid,
output [4:0] addr,
output reg [15:0] godata,
//spi初始化完成标志
output reg init_finish
);
// cs、sck、mosi的delay信号
reg spi_cs_2,spi_cs_1;
reg spi_sck_2,spi_sck_1;
reg spi_mosi_2,spi_mosi_1;
// cs、sck的下降沿/mosi的上升沿
wire spi_cs_neg;
wire spi_sck_neg;
wire spi_sck_pos;
wire spi_mosi_flag;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
{spi_cs_2,spi_cs_1} <= 2'b11;
{spi_sck_2,spi_sck_1} <= 2'b00;
{spi_mosi_2,spi_mosi_1} <= 2'b00;
end
else
begin
{spi_cs_2,spi_cs_1} <= {spi_cs_1,spi_cs};
{spi_sck_2,spi_sck_1} <= {spi_sck_1,spi_sck};
{spi_mosi_2,spi_mosi_1} <= {spi_mosi_1,spi_mosi};
end
end
assign spi_cs_neg = (spi_cs_2&(~spi_cs_1));
assign spi_sck_neg = ~spi_sck_1&spi_sck_2;
assign spi_sck_pos = ~spi_sck_2&spi_sck_1;
assign spi_mosi_flag = spi_mosi_2;
localparam IDLE = 6'b000001;
localparam RXD_COM = 6'b000010;
localparam JUDGE = 6'b000100;
localparam TXD_NUM = 6'b001000;
localparam TXD_DATA = 6'b010000;
localparam RXD_PARA = 6'b100000;
parameter COMMAND_READ = 8'hA5;
parameter COMMAND_WRITE = 8'H5A;
reg [5:0] state;
reg [3:0] cnt; //计数接收命令参数的位数
reg [3:0] cnt0; //发送数据的地址
reg [4:0] cnt1; //计数发送数据的位数
reg [4:0] cnt2; //计数接收两点校正参数的位数
reg [4:0] cnt3; //cnt2和godata输出到的存储参数的存储器的地址有关
reg [7:0] para;
reg [15:0] tdata;
reg rd_flag;
wire [15:0] data_out;
reg rxd_finish;
reg rxd_finish_en;
reg [7:0] counter;
assign addr = cnt2 - 1;
always @(posedge clk or negedge rst_n)
if(!rst_n)
state <= IDLE;
else
begin
case(state)
IDLE:
begin
if(spi_cs_neg)
state <= RXD_COM;
else
state <= IDLE;
end
RXD_COM:
begin
if(cnt == 4'b1000)
state <= JUDGE;
else
state <= RXD_COM;
end
JUDGE:
begin
if(para == COMMAND_READ)
state <= TXD_NUM;
else if(para == COMMAND_WRITE)
state <= RXD_PARA;
else
state <= IDLE;
end
TXD_NUM:
begin
state <= TXD_DATA;
end
TXD_DATA:
begin
//每发送完8个ave数据回到idle状态
if(cnt0 == 4'b1000 && cnt1 == 5'b00001)
state <= IDLE;
//每发送完一个ave数据,就进入TXD_NUM状态
//此状态更新一次新的要发送的ave
else if(cnt1 == 5'b10000)
state <= TXD_NUM;
else
state <= TXD_DATA;
end
RXD_PARA:
begin
if(cnt2 < 5'b10110)
state <= RXD_PARA;
else
state <= IDLE;
end
default: state <= IDLE;
endcase
end
always @(posedge clk or negedge rst_n)
if(!rst_n)
begin
cnt <= 4'b0;
cnt0 <= 4'b0;
cnt1 <= 5'b0;
para <= 8'b0;
tdata <= 16'b0;
rd_flag <= 1'b0;
spi_miso <= 1'b1;
data_valid <= 1'b0;
cnt3 <= 5'b0000;
cnt2 <= 5'b0000;
godata <= 16'b0;
rxd_finish <= 1'b0;
end
else
begin
case(state)
IDLE:
begin
cnt <= 4'b0;
cnt0 <= 4'b0;
cnt1 <= 5'b0;
para <= 8'b0;
tdata <= 16'b0;
godata <= 16'b0;
rd_flag <= 1'b0;
spi_miso <= 1'b1;
cnt3 <= 5'b0000;
cnt2 <= 5'b0000;
data_valid <= 1'b0;
end
RXD_COM://接收命令参数
begin
if(cnt == 4'b1000)
cnt <= 4'b0000;
else if(spi_sck_pos)//上升沿接收数据
begin
//接收命令参数存入para 也即是写COMMAND_WRITE还是读COMMAND_READ
cnt <= cnt + 4'b0001;
//从高位到低位接收
para[7 - cnt[2:0]] <= spi_mosi_flag;
end
else
begin
cnt <= cnt;
para <= para;
end
end
JUDGE:
begin
if(para == COMMAND_READ)
//识别到读ave数据的命令COMMAND_READ,
//rd_flag拉高,直到读完8个数据再拉低
rd_flag <= 1'b1;
else
rd_flag <= 1'b0;
end
TXD_NUM:
begin
tdata <= data_out;
end
TXD_DATA://发送数据
begin
if(cnt1 == 5'b10000)
begin
//每发送完一个数据,cnt0+1,cnt0作为地址读取下一个要发送的数据
cnt1 <= 5'b00000;
cnt0 <= cnt0 + 4'b0001;
end
else if(spi_sck_neg)//下降沿发送数据
begin
cnt1 <= cnt1 + 5'b00001;
//从高位到低位发送
spi_miso <= tdata[15 - cnt1[4:0]];
end
else
begin
spi_miso <= spi_miso;
cnt1 <= cnt1;
end
end
RXD_PARA://接收两点校正的参数
begin
//这里表示每接收22个两点校正的参数拉高rxd_finish
if(cnt2 == 5'b10101 && cnt3 == 5'b01111)
rxd_finish <= 1'b1;
else
rxd_finish <= 1'b0;
//接收完一个16位的参数,data_valid拉高,cnt2 + 1,
//cnt2和godata输出到的存储参数的存储器的地址有关
if(cnt3 == 5'b10000)
begin
cnt3 <= 5'b0000;
cnt2 <= cnt2 + 5'b00001;
data_valid <= 1'b1;
end
else if(spi_sck_pos)//上升沿接收数据
begin
data_valid <= 1'b0;
cnt3 <= cnt3 + 5'b00001;
godata[15 - cnt3[4:0]] <= spi_mosi_flag;
end
else
begin
data_valid <= data_valid;
cnt3 <= cnt3;
godata <= godata;
end
end
default:
begin
cnt <= 4'b0000;
cnt0 <= 4'b0000;
cnt1 <= 5'b00000;
para <= 8'b0;
tdata <= 12'b0;
rd_flag <= 1'b0;
spi_miso <= 1'b1;
end
endcase
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
rxd_finish_en <= 1'b0;
else if (rxd_finish)
rxd_finish_en <= 1'b1;
else
rxd_finish_en <= rxd_finish_en;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
counter <= 8'b0;
//两点校正参数接收完成之后counter再计数一段时间,最后初始化完成
else if (rxd_finish_en && counter < 8'b11111111)
counter <= counter + 1'b1;
else
counter <= counter;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
init_finish <= 1'b0;
else if (counter == 8'b11111111)
init_finish <= 1'b1;
else
init_finish <= init_finish;
end
ave8_rom ave8_rom (
.clk(clk),
.rst_n(rst_n),
.rd(rd_flag),
.addr(cnt0),
.ave1_in(ave1),
.ave2_in(ave2),
.ave3_in(ave3),
.ave4_in(ave4),
.ave5_in(ave5),
.ave6_in(ave6),
.ave7_in(ave7),
.ave8_in(ave8),
.ave_valid(ave_valid),
.ave_out(data_out)
);
endmodule
该模块的状态机有六个状态:
localparam IDLE = 6'b000001;
localparam RXD_COM = 6'b000010;
localparam JUDGE = 6'b000100;
localparam TXD_NUM = 6'b001000;
localparam TXD_DATA = 6'b010000;
localparam RXD_PARA = 6'b100000;
分别是:
- 空闲状态
IDLE
- 接收命令状态
RXD_COM
- 判断命令是读还是写的状态
JUDGE
- 读取要发送的ave数据的状态
TXD_NUM
- 发送ave数据的状态
TXD_DATA
- 接收数据的状态
RXD_PARA
以下五个计数变量的意思:
reg [3:0] cnt; //计数接收命令参数的位数
reg [3:0] cnt0; //发送数据的地址
reg [4:0] cnt1; //计数发送数据的位数
reg [4:0] cnt2; //计数接收两点校正参数的位数
reg [4:0] cnt3; //cnt2和godata输出到的存储参数的存储器的地址有关
下面代码可以看出是下降沿发送数据:
else if(spi_sck_neg)//下降沿发送数据
begin
cnt1 <= cnt1 + 5'b00001;
//从高位到低位发送
spi_miso <= tdata[15 - cnt1[4:0]];
end
下面代码可以看出是上升沿接收数据:
else if(spi_sck_pos)//上升沿接收数据
begin
//接收命令参数存入para 也即是写COMMAND_WRITE还是读COMMAND_READ
cnt <= cnt + 4'b0001;
//从高位到低位接收
para[7 - cnt[2:0]] <= spi_mosi_flag;
end
2、ave8_rom模块
`timescale 1ns/1ps
/*
该模块在spi_slave1模块里面被例化,输出的ave_out会被spi slave发给master
*/
module ave8_rom (
clk,//时钟
rst_n,//复位信号
rd,//读使能
addr,//读地址
ave_valid,//平均值有效信号
ave1_in,//输入的8个通道的图像数据平均值
ave2_in,//这是直接由平均值计算模块输入的,和spi没关系
ave3_in,
ave4_in,
ave5_in,
ave6_in,
ave7_in,
ave8_in,
ave_out//平均值输出,其实是被外部的spi master读取的
);
input clk;
input rst_n;
input rd;
input [3:0] addr;
input ave_valid;
input [15:0] ave1_in;
input [15:0] ave2_in;
input [15:0] ave3_in;
input [15:0] ave4_in;
input [15:0] ave5_in;
input [15:0] ave6_in;
input [15:0] ave7_in;
input [15:0] ave8_in;
output [15:0] ave_out;
reg [15:0] ave_table [7:0];
assign ave_out = rd ? ave_table[addr] : 16'b1;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
ave_table[7] <= 16'b0000_0000_0000_1000;
ave_table[6] <= 16'b0000_0000_0000_0111;
ave_table[5] <= 16'b0000_0000_0000_0110;
ave_table[4] <= 16'b0000_0000_0000_0101;
ave_table[3] <= 16'b0000_0000_0000_0100;
ave_table[2] <= 16'b0000_0000_0000_0011;
ave_table[1] <= 16'b0000_0000_0000_0010;
ave_table[0] <= 16'b0000_0000_0000_0001 ;
end
else if (ave_valid && rd ==1'b0 )
begin
ave_table[7] <= ave8_in;
ave_table[6] <= ave7_in;
ave_table[5] <= ave6_in;
ave_table[4] <= ave5_in;
ave_table[3] <= ave4_in;
ave_table[2] <= ave3_in;
ave_table[1] <= ave2_in;
ave_table[0] <= ave1_in;
end
else
begin
ave_table[7] <= ave_table[7];
ave_table[6] <= ave_table[6];
ave_table[5] <= ave_table[5];
ave_table[4] <= ave_table[4];
ave_table[3] <= ave_table[3];
ave_table[2] <= ave_table[2];
ave_table[1] <= ave_table[1];
ave_table[0] <= ave_table[0];
end
end
endmodule