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_cho模块的输出信号,
//sw_cnt控制spi_input_cho模块选择特定的数据输出给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的上升沿
menuitem
wire spi_cs_neg;
wire spi_sck_neg;
wire spi_sck_pos;
wire spi_mosi_flag;
always @(podge 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
el
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;
2929
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 @(podge clk or negedge rst_n)
if(!rst_n)
state <= IDLE;
el
begin
ca(state)
IDLE:
begin
if(spi_cs_neg)
state <= RXD_COM;
el
state <= IDLE;
end
RXD_COM:
begin
if(cnt == 4'b1000)
state <= JUDGE;
el
state <= RXD_COM;
end
JUDGE:
begin
if(para == COMMAND_READ) state <= TXD_NUM;
clot怎么读
el if(para == COMMAND_WRITE)
state <= RXD_PARA;
el
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
el if(cnt1 == 5'b10000)
state <= TXD_NUM;
el
state <= TXD_DATA;
end
RXD_PARA:
begin
if(cnt2 < 5'b10110)
state <= RXD_PARA;
el
state <= IDLE;
end
default: state <= IDLE;
endca
end
always @(podge 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
el
begin
ca(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;
el if(spi_sck_pos)//上升沿接收数据
begin
//接收命令参数存⼊para 也即是写COMMAND_WRITE还是读COMMAND_READ cnt <= cnt + 4'b0001;
//从⾼位到低位接收
para[7 - cnt[2:0]] <= spi_mosi_flag;
end
el
begin
cnt <= cnt;
para <= para;
end
end
JUDGE:
begin
if(para == COMMAND_READ)
//识别到读ave数据的命令COMMAND_READ,
//rd_flag拉⾼,直到读完8个数据再拉低
rd_flag <= 1'b1;
el
rd_flag <= 1'b0;
chuck berryend
TXD_NUM:
begin
tdata <= data_out;
end
TXD_DATA://发送数据
begin
magnatif(cnt1 == 5'b10000)
begin
//每发送完⼀个数据,cnt0+1,cnt0作为地址读取下⼀个要发送的数据
cnt1 <= 5'b00000;
cnt0 <= cnt0 + 4'b0001;
end
el if(spi_sck_neg)//下降沿发送数据
begin
cnt1 <= cnt1 + 5'b00001;
国庆节祝福语大全//从⾼位到低位发送
spi_miso <= tdata[15 - cnt1[4:0]];
end
el
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;
el
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
el if(spi_sck_pos)//上升沿接收数据
begin
data_valid <= 1'b0;
cnt3 <= cnt3 + 5'b00001;
godata[15 - cnt3[4:0]] <= spi_mosi_flag;
end
el
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
endca
end
always @(podge clk or negedge rst_n)begin
if(!rst_n)
rxd_finish_en <= 1'b0;
el if (rxd_finish)
rxd_finish_en <= 1'b1;
el
rxd_finish_en <= rxd_finish_en;
end
always @(podge clk or negedge rst_n)begin
if(!rst_n)
counter <= 8'b0;
//两点校正参数接收完成之后counter再计数⼀段时间,最后初始化完成 el if (rxd_finish_en && counter < 8'b11111111)
counter <= counter + 1'b1;
el
counter <= counter;
end
always @(podge clk or negedge rst_n)begin
if(!rst_n)
init_finish <= 1'b0;
el if (counter == 8'b11111111)
init_finish <= 1'b1;
el
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
该模块的状态机有六个状态:
philtrumlocalparam 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输出到的存储参数的存储器的地址有关
下⾯代码可以看出是下降沿发送数据:
el if(spi_sck_neg)//下降沿发送数据
begin
cnt1 <= cnt1 + 5'b00001;
//从⾼位到低位发送
spi_miso <= tdata[15 - cnt1[4:0]];
end
下⾯代码可以看出是上升沿接收数据:
el 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没关系
junior是什么意思
ave3_in,
ave4_in,
ave5_in,
ave6_in,