SPI协议的数据读写实现(spi_slave)

一、SPI协议介绍

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
posted @ 2021-10-19 22:06  耐心的小黑  阅读(1466)  评论(0编辑  收藏  举报