FPGA (3) - Realization of SPI communication protocol based on FPGA

1. The basic principle of SPI communication

1. Introduction to SPI communication

SPI (Serial Peripheral Interface, Serial Peripheral Interface) is a synchronous serial interface technology introduced by Motorola. The SPI bus is physically implemented by a module called a synchronous serial port (Synchronous Serial Port) on the microprocessor control unit (MCU) connected to the peripheral device microcontroller (PICmicro), which allows the MCU Performs high-speed data communication with various peripheral devices in full-duplex synchronous serial mode.

SPI is mainly used between EEPROM, Flash, real-time clock (RTC), digital-to-analog converter (ADC), digital signal processor (DSP) and digital signal decoder. It only occupies four pins (Pin) in the chip for control and data transmission, which saves the number of pins of the chip and saves space for PCB layout. It is precisely because of this easy-to-use feature that more and more chips have integrated SPI technology.

SPI mainly has the following characteristics:

  1. The master-slave mode (Master-Slave) control method
    SPI stipulates that the communication between two SPI devices must be controlled by the master device (Master) to control the slave device (Slave). A Master device can provide Clock and Slave device Chip selection (Slave Select) to control multiple Slave devices. The SPI protocol also stipulates that the Clock of the Slave device is provided to the Slave device by the Master device through the SCK pin. The Slave device itself cannot generate or control the Clock. Without the Clock, the Slave device cannot work normally. .

insert image description here

  1. Using synchronous mode (Synchronous) to transmit data
    Master device will generate corresponding clock pulse (Clock Pulse) according to the data to be exchanged, the clock pulse constitutes the clock signal (Clock Signal), the clock signal passes the clock polarity (CPOL) and clock phase (CPHA) controls when data is exchanged between two SPI devices and when the received data is sampled to ensure that data is transmitted synchronously between the two devices.

  2. Data Exchanges
    The reason why data transmission between SPI devices is also called data exchange is because the SPI protocol stipulates that an SPI device cannot only act as a "Transmitter" or "Receiver" in the process of data communication. (Receiver)”. In each Clock period, the SPI device will send and receive a bit-sized data, which means that the device has a bit-sized data exchanged. If a Slave device can receive the data sent by the
    Master The control signal must be able to be accessed by the Master device before this (Access). Therefore, the Master device must first select the Slave device through the SS/CS pin, and select the Slave device that it wants to access. In the process of data
    transmission In , each received data must be sampled before the next data transmission. If the previously received data is not read, then the data that has been received may be discarded, resulting in the final failure of the SPI physical module. Therefore , in the program, the data in the SPI device is usually read after the SPI transfers the data, even if the data (Dummy Data) is useless in our program.

insert image description here

2. SPI master-slave module communication rules

The figure is a simple description of the communication between SPI devices, let's explain the several components (Module) shown in the figure:

insert image description here

SSPBUF, Synchronous Serial Port Buffer, generally refers to the internal buffer in the SPI device, generally in the form of FIFO physically, storing temporary data during transmission;

SSPSR, Synchronous Serial Port Register, generally refers to the shift register (Shift Regitser) in the SPI device, its function is to move data into or out of SSPBUF according to the set data bit width (bit-width);

Controller, generally refers to the control registers in the SPI device, you can configure them to set the transmission mode of the SPI bus.

Normally, we only need to program the four pins (pins) described above to control the data communication between the entire SPI device:

SCK, Serial Clock, the main function is to transmit the clock signal from the Master device to the Slave device, and control the timing and rate of data exchange;

SS/CS, Slave Select/Chip Select, used for Master device chip selection Slave device, so that the selected Slave device can be accessed by the Master device;

SDO/MOSI, Serial Data Output/Master Out Slave In, also known as Tx-Channel on the Master, as the data export, mainly used for SPI equipment to send data;

SDI/MISO, Serial Data Input/Master In Slave Out, also known as Rx-Channel on the Master, as a data entry, mainly used for SPI devices to receive data;

During the communication process of the SPI device, a data link loop (Data Loop) will be generated between the Master device and the Slave device. As shown in the figure above, through the SDO and SDI pins, the SSPSR controls the data to be moved in and out of the SSPBUF , the Controller determines the communication mode of the SPI bus, and the SCK transmits the clock signal.

  1. Timing
    First, explain two concepts here:
    CPOL: clock polarity, indicating whether the clock signal is high or low when the SPI is idle. If CPOL is set to 1, then the device is under the SCK pin when idle The clock signal of the SCK pin is high. When CPOL is set to 0, it is just the opposite.
    CPHA: Clock phase, indicating that the SPI device triggers data sampling when the clock signal on the SCK pin becomes a rising edge, or when the clock signal changes Trigger data sampling when the falling edge. If CPHA is set to 1, the SPI device triggers data sampling when the clock signal becomes a falling edge, and sends data when the rising edge. When CPHA is set to 0, it is just the opposite. This
    example The SPI data transmission mode used is set to CPOL = 1, CPHA = 1. In this way, within a Clock period, each individual SPI device can send and receive at the same time in a full-duplex (Full-Duplex) manner 1 bit data, which is equivalent to exchanging data of 1 bit size. If the Channel-Width of the SPI bus is set to Byte, it means that the minimum unit of each data transmission on the SPI bus is Byte, then the device mounted on the SPI bus Each data transmission process requires at least 8 Clock cycles (ignoring the physical delay of the device). Therefore, the faster the frequency of the SPI bus and the shorter the Clock cycle, the faster the data exchange rate between SPI devices.

  2. SSPSR
    SSPSR is the shift register (Shift Register) inside the SPI device. Its main function is to move data into or out of the SSPBUF according to the state of the SPI clock signal. The size of the data moved each time is determined by the Bus-Width and Channel-Width The role of Bus-Width is to specify the unit
    of data transmission between the address bus and the Master device.
    For example, we want to write 16 Byte data to the SSPBUF in the Master device: First, set the Bus-Width for the configuration register of the Master device. Width is Byte; Then write data to the Tx-Data shift register of the Master device at the entry of the address bus, and write data of 1 Byte each time (using the writeb function); After writing 1 Byte data, the data in the Master device The Tx-Data shift register will automatically shift the 1 Byte data from the address bus into the SSPBUF; the above actions need to be repeated 16 times. The function of
    Channel-Width is to specify the unit of data transmission between the Master device and the Slave device. Similar to the Bus-Width, the shift register inside the Master device will automatically transfer the data from the Master-SSPBUF to the Slave-SDI pin in the Slave device through the Master-SDO pin according to the Channel-Width, and then the Slave-SSPSR Each received data is moved into Slave-SSPBUF.
    Normally, Bus-Width will always be greater than or equal to Channel-Width, so as to ensure that the frequency of data exchange between Master and Slave is higher than that between the address bus and Master. The frequency of data exchange between them should be fast, resulting in the situation that the data stored in SSPBUF is invalid data.

  3. SSPBUF
    We know that in each clock cycle, the data exchanged between Master and Slave is actually copied from SSPBUF by the SPI internal shift register. We can pass to the register corresponding to SSPBUF (Tx-Data / Rx-Data register ) to read and write data, and indirectly manipulate the SSPBUF inside the SPI device.
    For example, before sending data, we should first write the data to be sent to the Tx-Data register of the Master, and these data will be transferred by the Master-SSPSR shift register According to the Bus-Width, the data is automatically moved into the Master-SSPBUF, and then the data will be moved out of the Master-SSPBUF according to the Channel-Width by the Master-SSPSR, and passed to the Slave-SDI pin through the Master-SDO pin, and the Slave-SSPSR will transfer The data received from Slave-SDI is moved into Slave-SSPBUF. At the same time, the data in Slave-SSPBUF is sent to Master-SDI, Master-SSPSR through Slave-SDO according to the size of each received data (Channel-Width). Then move the data received from Master-SDI into Master-SSPBUF. After a single data transmission is completed, the user program can read the data exchanged by the Master device from the Rx-Data register of the Master device.

  4. Controller The
    Controller in the Master device mainly controls the Slave device through the clock signal (Clock Signal) and the chip select signal (Slave Select Signal). The Slave device will wait until it receives the chip select signal sent by the Master device, and then according to the clock signal To work.
    The chip selection operation of the Master device must be implemented by the program. For example: the program pulls the clock signal of the SS/CS pin low to complete the preliminary work of the SPI device data communication; when the program wants the SPI device to end the data During communication, pull the clock signal on the SS/CS pin to a high level.

2. SPI master-slave module case and FPGA implementation

1. Case description

To design 4-wire SPI master module and slave module, the requirements are as follows:

  1. Host module (master) interface definition:
module spi_master(
    input         clk_40k,      //时钟信号,40kHz
    input         rst_n,        //复位信号,低有效
	input  [7:0]  data_in,      //主机准备要输出给从机的数据,8位宽
	input		  send_start,	 //通信使能信号,高有效,宽度为1个时钟周期(40kHz),收到该信号后开始一次主从设备通信。
	output [7:0]  data_out,     //主机从从机接收到的数据,8位宽
	output		  data_out_vld,	 //输出数据有效标志,高电平有效,宽度为1个时钟周期(40kHz)
	output		  cs_n,		 //从设备片选使能信号,低有效,低电平时选中从设备与主设备进行通信,处于通信状态时维持低电平。
	output        sclk,         //同步时钟,1kHz,空闲时置低电平
    input         miso,        //主机当前从从机收到的串行数据
    output        mosi        //主机当前发送给从机的串行数据
 	);
  1. Slave module (slave) interface definition:
module spi_slave(
    input        rst_n,        //复位信号,低有效
	input        cs_n,         //从设备片选使能信号
	input        sclk,         //SPI时钟,1kHz空闲时置低电平,
 	input        mosi,        //从机从主机接收到的串行数据
    output       miso,        //从机要发送给主机的串行数据
	output [7:0]   reg0_out,	 //内部寄存器0的值
	output [7:0]   reg1_out,	 //内部寄存器1的值
	output [7:0]   reg2_out,	 //内部寄存器2的值
	output [7:0]   reg3_out 	 //内部寄存器3的值
);
  1. Circuit function description:

There are four eight-bit internal registers (reg0, reg1, reg2, reg3) in the Slave module, and the addresses are 0~3 respectively. The master module configures the values ​​of the four registers in the slave module through the SPI bus, and the value of the slave register is directly passed through its port. output.
After the master module receives the send_start signal, it sends the data data_in to the reg0 of the slave module through the spi bus, then shifts the data_in to the right by two bits and sends it to the slave module’s reg1, and then shifts the data_in to the right by two bits and sends it to reg2. Finally, data_in is recirculated to the right by two bits and sent to reg3. So far, the master has completed the configuration of all registers in the slave. Then the master reads the data of reg3 in the slave through the spi bus, outputs it through data_out, and gives a cycle-width data_out_vld at the same time.

  1. SPI transmission format:

Each frame of SPI data contains 16 bits. The 0th bit sent first is the read and write control bit. If the bit is 0, it means that the master writes data to the slave, and if it is 1, it means that the master reads data from the slave; the bits 1-7 sent later It is the address bit, send the high address first and then the low address, 9-16 bits are the data bits, and the high bit data is sent first. All data is generated on the rising edge of sclk and sampled on the falling edge.

The SPI write data format is shown in Figure 1:

insert image description here

The SPI read data format is shown in Figure 2:

insert image description here

2. Design ideas

  1. The SPI protocol is determined by both the master and the slave. Here, the master module is required to send 5 groups of numbers each time. The first four groups are used to send data to slave modules with different addresses, and the fifth group is used to send addresses to read the data received by the last slave module. data, so just send mosi as required;
  2. The slave module slave will judge the read data mosi, if it reads 1, judge whether the next 7 bits are the address, if yes, send the data of the slave slave module at the address through miso; if it reads 0, judge the next 7 bits Whether the bit is the address, if it is, the data sent by mosi will be received into the register of the slave slave module at the address.

3. FPGA source code

1.SPI_master: main module

module spi_master(
    input         clk_40k,     
    input         rst_n,        
    input  [7:0]  data_in,      
    input	        send_start,	 
    
    output  reg [7:0]  data_out,     
    output  reg   data_out_vld,	
    output  reg   cs_n,		
    output  reg   sclk,         

    input         miso,       
    output  reg   mosi      
 	);


reg flag; //recieve&transfer part
reg addr_flag;  //mosi address output
reg data_flag;  //mosi data output

reg [4:0] bit_cnt;  //bit count
reg [6:0] clk_cnt;  //40 count
reg [2:0] cs_n_cnt;  //data transform count

reg [7:0] data_in_m0;  //shifting input data 
reg [7:0] data_in_m1;  //shifting input data 
reg [7:0] data_in_m2;  //shifting input data 
reg [7:0] data_in_m3;  //shifting input data 

reg [7:0] rx_data_out; //output data

reg [7:0] addr0;
reg [7:0] addr1;
reg [7:0] addr2;
reg [7:0] addr3;
reg [7:0] read;

parameter reg0_address = 7'b0000000; //address of reg0
parameter reg1_address = 7'b0000001; //address of reg1
parameter reg2_address = 7'b0000010; //address of reg2
parameter reg3_address = 7'b0000011; //address of reg3

localparam	    		 idle = 1'b0;
localparam	    		 work = 1'b1;
localparam        transfer = 1'b0;
localparam        recieve = 1'b1;

//set address of reg
always @ *
begin
  if(~rst_n)
  begin
    addr0 <= {
    
    1'b0, reg0_address};
    addr1 <= {
    
    1'b0, reg1_address};
    addr2 <= {
    
    1'b0, reg2_address};
    addr3 <= {
    
    1'b0, reg3_address};
    read <= {
    
    1'b1, reg3_address};
  end
end

//cs_n
always @ (posedge clk_40k or negedge rst_n)
begin
  if(~rst_n)
    cs_n <= 1'b1;
  else if(send_start == 1'b1)
    cs_n <= 1'b0;
  else if(clk_cnt == 7'd39 && bit_cnt == 5'd15 && cs_n_cnt == 3'b100)
    cs_n <= 1'b1;
end

//clk_cnt 20 count
always @ (posedge clk_40k or negedge rst_n)
begin
	if(~rst_n)
		clk_cnt <= 7'b0;
	else if(cs_n == 1'b0 && clk_cnt != 7'd39)
		clk_cnt <= clk_cnt + 1'b1;
	else if(cs_n == 1'b0 && clk_cnt == 7'd39)
		clk_cnt <= 7'b0;
end

//sclk
always @ (posedge clk_40k or negedge rst_n)
begin
	if(~rst_n)
		sclk <= 1'b0;
	else if(send_start == 1'b1)
		sclk <= 1'b1;
	else if(clk_cnt == 7'd19 || clk_cnt == 7'd39)
		sclk <= ~sclk;
end

//bit_cnt 16 count
always @ (posedge clk_40k or negedge rst_n)
begin
	if(~rst_n || cs_n)
		bit_cnt <= 5'b0;
	else if(bit_cnt != 5'd15 && cs_n == 1'b0 && clk_cnt == 7'd39)
		bit_cnt <= bit_cnt + 1'b1;
	else if(bit_cnt == 5'd15 && cs_n == 1'b0 && clk_cnt == 7'd39)
		bit_cnt <= 5'b0;
	else if(cs_n == 1'b1)
	  bit_cnt <= 5'b0;
end

//cs_n_cnt 4 salve count
always @ (posedge clk_40k or negedge rst_n)
begin
	if(~rst_n || cs_n)
		cs_n_cnt <= 3'b0;
	else if(bit_cnt == 5'd15 && cs_n_cnt != 3'b100 && clk_cnt == 7'd39)
		cs_n_cnt <= cs_n_cnt + 1'b1;
	else if(bit_cnt == 5'd15 && cs_n_cnt == 3'b100 && clk_cnt == 7'd39)
		cs_n_cnt <= 3'b0;
	else if(cs_n == 1'b1)
	  cs_n_cnt <= 3'b0;
end

//input data shifting
always @ (posedge sclk)
begin
	if(send_start == 1'b1)
	begin
		data_in_m0 <= data_in;
		data_in_m1 <= {
    
    data_in[1:0],data_in[7:2]};
		data_in_m2 <= {
    
    data_in[3:0],data_in[7:4]};
		data_in_m3 <= {
    
    data_in[5:0],data_in[7:6]};
  end
end

//send part mosi
//flag of transfering address and data
always @ (negedge sclk or negedge rst_n)
begin
  if(~rst_n)
  begin
    addr_flag <= work;
    data_flag <= idle;
  end
  else if(bit_cnt == 5'd7)
  begin
    addr_flag <= idle;
    data_flag <= work;
  end
  else if(bit_cnt == 5'd15 || send_start == 1'b1)
  begin
    addr_flag <= work;
    data_flag <= idle;
  end
end

//output mosi address and data
always @ (posedge sclk)
begin
  if(addr_flag == work && cs_n == 1'b0)
	begin
	  case(cs_n_cnt)
	    3'b000:
	    begin
		    mosi <= addr0[7];
		    addr0 <= {
    
    addr0[6:0],addr0[7]};
	    end
	    3'b001:
	    begin
		    mosi <= addr1[7];
		    addr1 <= {
    
    addr1[6:0],addr1[7]};
	    end
	    3'b010:
	    begin
		    mosi <= addr2[7];
		    addr2 <= {
    
    addr2[6:0],addr2[7]};
	    end
	    3'b011:
	    begin
		    mosi <= addr3[7];
		    addr3 <= {
    
    addr3[6:0],addr3[7]};
	    end
	    3'b100:
	    begin
		    mosi <= read[7];
		    read <= {
    
    read[6:0],read[7]};
	    end
	  endcase
	end
	else if(data_flag == work && cs_n == 1'b0)
	begin
	  case(cs_n_cnt)
	    3'b000:
	    begin
		    mosi <= data_in_m0[7];
		    data_in_m0 <= {
    
    data_in_m0[6:0],data_in_m0[7]};
	    end
	    3'b001:
	    begin
		    mosi <= data_in_m1[7];
		    data_in_m1 <= {
    
    data_in_m1[6:0],data_in_m1[7]};
	    end
	    3'b010:
	    begin
		    mosi <= data_in_m2[7];
		    data_in_m2 <= {
    
    data_in_m2[6:0],data_in_m2[7]};
	    end
	    3'b011:
	    begin
		    mosi <= data_in_m3[7];
		    data_in_m3 <= {
    
    data_in_m3[6:0],data_in_m3[7]};
	    end
	    3'b100:
	    begin
	      mosi <= 1'bz;
	     end
	  endcase
	end
	else if(cs_n == 1'b1)
		mosi <= 1'bz;
end


//recieve part miso
//recieve data
always @ (negedge sclk or negedge rst_n)
begin
	if(~rst_n)
		rx_data_out <= 8'b0;
	else if(flag == 1'b1)
		rx_data_out <= {
    
    rx_data_out[6:0],miso};
end

//output & valid
always @ (posedge clk_40k or negedge rst_n)
begin
	if(~rst_n)
		data_out_vld <= 1'b0;
	else if(bit_cnt == 5'd15 && clk_cnt== 7'd39 && cs_n == 1'b0 && cs_n_cnt == 3'b100)
	begin
		data_out_vld <= 1'b1;
		data_out <= rx_data_out;
	end
	else
		data_out_vld <= 1'b0;
end

//recieve/transfer switch
always @ (posedge clk_40k or negedge rst_n)
begin
  if(~rst_n)
    flag <= 1'b0;
  else if(clk_cnt == 7'd39 && bit_cnt == 5'd7 && cs_n_cnt == 3'b100 && flag == 1'b0)
    flag <= 1'b1;
  else if(clk_cnt == 7'd0 && bit_cnt == 5'd0 && flag == 1'b1)
    flag <= 1'b0;
end

endmodule


2.SPI_slave: slave module


module	spi_slave
(
  input					 rst_n,
	input      cs_n,
	input	     sclk,
	input		    mosi,
  output reg               miso,
	output reg  [7:0]        reg0_out,
  output reg  [7:0]        reg1_out,
  output reg  [7:0]        reg2_out,
  output reg  [7:0]        reg3_out
	);

reg         [6:0]       bit_cnt;
reg                     state;
reg                     n_state;
reg         [6:0]       address;
reg                     token;
localparam	    		 idle = 1'b0;
localparam	    		 transmit = 1'b1;
	
	
//bit_cnt
always @ (posedge sclk or negedge rst_n)
    if(~rst_n || cs_n)
        bit_cnt <= 7'd0;
    else if(bit_cnt == 7'd15)
        bit_cnt <= 7'd0;
    else if(state || n_state)
        bit_cnt <= bit_cnt + 1'b1;

//n_state
always @ *
    if(cs_n)
        n_state <= idle;
    else
        n_state <= transmit;
        
//state
always @ (posedge sclk or negedge rst_n)
    if(~rst_n)
        state <= idle;
    else
        state <= n_state;
       
//reg_out
always @ (negedge sclk or negedge rst_n)
    if(~rst_n) 
    begin
        reg0_out <= 8'd0;
        reg1_out <= 8'd0;
        reg2_out <= 8'd0;
        reg3_out <= 8'd0;
    end
    else if(bit_cnt >= 'd8 && token == 'b0) 
    begin
        case(address)
        7'd0:
            reg0_out <= {
    
    reg0_out[6:0], mosi};
        7'd1:
            reg1_out <= {
    
    reg1_out[6:0], mosi};
        7'd2:
            reg2_out <= {
    
    reg2_out[6:0], mosi};
        7'd3: 
            reg3_out <= {
    
    reg3_out[6:0], mosi}; 
        endcase
    end

//address
always @ (negedge sclk or negedge rst_n)
    if(~rst_n)
        address <= 7'd0;
    else if(bit_cnt >= 7'd1 && bit_cnt <= 7'd7)
        address <= {
    
    address[5:0], mosi};
        
//token
always @ (negedge sclk or negedge rst_n)
    if(~rst_n)
        token <= 1'b0;
    else if(state == transmit && bit_cnt == 7'b0)
        token <= mosi;

//miso
always @ (posedge sclk or negedge rst_n)
    if(~rst_n || state == idle || cs_n)
        miso <= 1'b0;
    else if(state == transmit && token == 1'b1 && bit_cnt >= 7'd7) 
    begin
        case(address)
            7'd0: miso <= reg0_out[14-bit_cnt];
            7'd1: miso <= reg1_out[14-bit_cnt];         
            7'd2: miso <= reg2_out[14-bit_cnt];
            7'd3: miso <= reg3_out[14-bit_cnt];
        endcase
    end
    else
        miso <= 1'b0;
endmodule


3.testbench: test file

`timescale 1us/1us

module tb();

	reg	clk_40k;   
    	reg	rst_n;        
	reg  [7:0]  data_in;      
	reg	send_start;	 

	wire	sclk;
	wire	cs_n;
	wire	mosi;

	wire	miso;
	wire [7:0] data_out;
	wire    data_out_vld;

	wire [7:0] reg0_out;
	wire [7:0] reg1_out;
	wire [7:0] reg2_out;
	wire [7:0] reg3_out;

	spi_master i_spi_master(
		.clk_40k	(clk_40k   ),     
    		.rst_n		(rst_n	   ),
		.data_in	(data_in   ),
		.send_start	(send_start),
		.sclk		(sclk	   ),
		.cs_n		(cs_n	   ),
		.mosi		(mosi	   ),
		.miso		(miso      ),
		.data_out	(data_out  ),  
		.data_out_vld	(data_out_vld  )  
 	);

	spi_slave i_spi_slave(
    		.rst_n		(rst_n	   ),       
    		.cs_n           (cs_n	   ),
    		.sclk		(sclk	   ),        
    		.mosi		(mosi	   ),      
    		.miso		(miso	   ),       
    
    		.reg0_out	(reg0_out  ),	 
    		.reg1_out	(reg1_out  ),	 
    		.reg2_out	(reg2_out  ),	 
    		.reg3_out	(reg3_out  ) 	 
);


	initial 
	begin
		rst_n = 1'b0;
	#10	rst_n = 1'b1;
	end


	initial
	begin
		clk_40k = 1'b0;
		forever
	#1	clk_40k = ~clk_40k;
	end


	initial 
	begin
		send_start = 1'b0;
		data_in = 8'd0;
		forever
		begin
			#200;
			data_in = $random()%256;
			send_start = 1'b1;
			#2
			send_start = 1'b0;
			#8000;
		end
	end

endmodule


Waveform output results:

insert image description here

Guess you like

Origin blog.csdn.net/weixin_43361652/article/details/108399366