当前位置:网站首页>Implementation of SPI communication protocol based on FPGA

Implementation of SPI communication protocol based on FPGA

2022-07-23 19:00:00 qq_ forty-four million nine hundred and eighty-five thousand si

** One . SPI Basic principles of communication

  1. SPI Communication introduction **
    SPI(Serial Perripheral Interface, Serial peripheral interface ) yes Motorola A synchronous serial interface technology introduced by the company .SPI The bus is physically connected to the peripheral microcontroller (PICmicro) The microprocessor control unit above (MCU)
    It is called synchronous serial port (Synchronous Serial Port) Module (Module) To achieve , It allows the MCU
    In full duplex synchronous serial mode , High speed data communication with various peripheral devices .

SPI Mainly used in EEPROM, Flash, Real time clock (RTC), A / D converter (ADC), Digital signal processor (DSP) And digital signal decoder . It only takes up four pins in the chip (Pin) Used to control and data transmission , Save the cost of the chip pin number , Also for PCB Save space in layout . It is out of this simple and easy-to-use feature , Now more and more chips are integrated SPI technology .

SPI It mainly has the following characteristics :
1、 Adopt master - Slave mode (Master-Slave) Control mode of

SPI There are two rules SPI Communication between devices must be done by the master device (Master) To control the secondary device (Slave). One Master Equipment can be provided by Clock And right Slave Device for film selection (Slave Select) To control multiple Slave equipment , SPI The agreement also states that Slave The equipment Clock from Master Equipment passing SCK Pins are provided to Slave equipment , Slave The device itself cannot produce or control Clock, No, Clock be Slave The device is not working properly .
 Insert picture description here

2、 In synchronous mode (Synchronous) To transmit data
Master The device will generate the corresponding clock pulse according to the data to be exchanged (Clock Pulse), The clock pulses make up the clock signal (Clock Signal) , The clock signal passes through the clock polarity (CPOL) and Clock phase (CPHA) Controlling two SPI When to exchange data between devices and when to sample the received data , To ensure that data is transmitted synchronously between two devices .

3、 Data exchange (Data Exchanges)
SPI Data transmission between devices is also called data exchange , Because SPI The agreement provides for a SPI The device can't just act as a “ sender (Transmitter)” perhaps “ The receiver (Receiver)”. At every Clock In cycle , SPI The device sends and receives a bit Size data , It's equivalent to that the device has a bit The size of the data is exchanged .
One Slave If the device wants to be able to receive Master The control signal coming from , Must be able to be Master Device access (Access). therefore , Master The device must first pass through SS/CS pin Yes Slave Device for film selection , Put what you want to visit Slave The device is selected .
In the process of data transmission , Each received data must be sampled before the next data transmission . If the previously received data is not read , Then the received data may be discarded , Lead to SPI The physical module eventually fails . therefore , In a program, it's usually in SPI After transmitting the data , Read out SPI The data in the device , Even if the data (Dummy Data) It's useless in our program .
 Insert picture description here
2. SPI Master slave module communication rules
The picture is right SPI A simple description of communication between devices , Let's explain the components shown in the figure below (Module):
 Insert picture description here
SSPBUF, Synchronous Serial Port Buffer, Generally speaking SPI The internal buffer inside the device , Generally speaking, it is based on FIFO In the form of , Save temporary data during transmission ;

SSPSR, Synchronous Serial Port Register, Generally speaking SPI The shift register in the device (Shift Regitser), Its function is based on the set data bit width (bit-width) Move data in or out of SSPBUF;

Controller, Generally speaking SPI The control register in the device , You can configure them to set up SPI Bus transmission mode .

Usually , We only need to analyze the four pins described above (pin) Programming can control the whole SPI Data communication between devices :

SCK, Serial Clock, The main function is Master The equipment goes to Slave The device transmits the clock signal , Control the timing and rate of data exchange ;

SS/CS, Slave Select/Chip Select, be used for Master Device selection Slave equipment , Make the selected Slave Devices can be Master Device access ;

SDO/MOSI, Serial Data Output/Master Out Slave In, stay Master It's also called Tx-Channel, As an export of data , It is mainly used for SPI The device sends data ;

SDI/MISO, Serial Data Input/Master In Slave Out, stay Master It's also called Rx-Channel, As an entry to data , It is mainly used for SPI The device receives data ;

SPI The device is in the process of communication , Master Equipment and Slave There will be a data link loopback between devices (Data Loop), Just like the picture above , adopt SDO and SDI Pin , SSPSR Control data moving in and out SSPBUF, Controller determine SPI Bus communication mode , SCK Transmit the clock signal .
1、timing
First , Explain two concepts here :
CPOL: Clock polarity , Express SPI In my spare time , Is the clock signal high or low . if CPOL Is set to 1, Then when the device is idle SCK The clock signal at the foot of the tube is high . When CPOL Is set to 0 It's just the opposite .
CPHA: Clock phase , Express SPI The equipment is in SCK Data sampling is triggered when the clock signal on the pin becomes the rising edge , Or trigger data sampling when the clock signal becomes the falling edge . if CPHA Set to 1, be SPI The device triggers data sampling when the clock signal becomes a falling edge , Send data at rising edge . When CPHA Is set to 0 It's just the opposite .
The... Used in this example SPI The data transmission mode is set to CPOL = 1, CPHA = 1. such , In a Clock In cycle , Each individual SPI All devices can operate in full duplex (Full-Duplex) The way , Send and receive simultaneously 1 bit data , That is, it is equivalent to exchanging 1 bit Size data . If SPI Bus Channel-Width Is set to Byte, Express SPI The minimum unit of each data transmission on the bus is Byte, Then mount in the SPI Each data transmission process of bus equipment needs at least 8 individual Clock cycle ( Ignore the physical latency of the device ). therefore , SPI The faster the frequency of the bus , Clock Shorter period , be SPI The faster the rate of data exchange between devices .

2、SSPSR
SSPSR yes SPI Shift register inside the device (Shift Register). Its main function is based on SPI Clock signal status , Go to SSPBUF To move data in or out of , The data size of each move is determined by Bus-Width as well as Channel-Width Determined by .
Bus-Width The function of the is to specify the address of the bus to Master The unit of data transmission between devices .
for example , We want to go to Master In the equipment SSPBUF write in 16 Byte Size data : First , to Master Device configuration register settings Bus-Width by Byte; Then go to Master The equipment Tx-Data The shift register writes data at the entrance of the address bus , Every time you write 1 Byte Size data ( Use writeb function ); finish writing sth. 1 Byte After the data , Master In the equipment Tx-Data The shift register will automatically transfer from the address bus 1 Byte Data moved into SSPBUF in ; The above actions need to be repeated 16 Time .
Channel-Width The purpose of this is to specify Master Equipment and Slave The unit of data transmission between devices . And Bus-Width be similar , Master The shift register inside the device will be based on Channel-Width Automatically transfer data from Master-SSPBUF Pass through Master-SDO The pins are transported to Slave In the device Slave-SDI Pin , Slave-SSPSR Then move the received data into the Slave-SSPBUF in .
Usually , Bus-Width It's always greater than or equal to Channel-Width, This will ensure that there will be no cause of Master And Slave The frequency of data exchange between address bus and Master The frequency of data exchange between them should be fast , Lead to SSPBUF The data stored in it is invalid .

3、SSPBUF
We know , In every clock cycle , Master And Slave The data exchanged between them is actually SPI Internal shift register from SSPBUF It's a copy of . We can go to SSPBUF The corresponding register (Tx-Data / Rx-Data register) Read and write data in the library , Control indirectly SPI Inside the equipment SSPBUF.
for example , Before sending data , We should go to Master Of Tx-Data Register writes the data to be sent out , The data will be Master-SSPSR The shift register is based on Bus-Width Automatically move in Master-SSPBUF in , And then the data will be Master-SSPSR according to Channel-Width from Master-SSPBUF To remove from , adopt Master-SDO The pin passes to Slave-SDI Pin , Slave-SSPSR From Slave-SDI The received data is moved into Slave-SSPBUF in . meanwhile , Slave-SSPBUF The data in it depends on the size of the data received each time (Channel-Width), adopt Slave-SDO Sent to Master-SDI, Master-SSPSR And then take it from Master-SDI The received data is moved into Master-SSPBUF. After a single data transfer , User programs can be accessed from Master The equipment Rx-Data Register read Master Data from device data exchange .

4、Controller
Master In the equipment Controller Mainly through the clock signal (Clock Signal) And the chip selection signal (Slave Select Signal) To control Slave equipment . Slave The device will be waiting , Until received Master The chip selection signal from the device , Then it works according to the clock signal .
Master The chip selection operation of the device must be realized by the program . for example : By the program SS/CS The clock signal of the pin is pulled down , complete SPI The preliminary work of equipment data communication ; When the program wants SPI When the device ends data communication , And then SS/CS The clock signal on the pin is pulled high .
** Two . SPI Master slave module cases and FPGA Realization

  1. Case description **
    Design 4 Line SPI master Module and slave modular , Requirements are as follows :

1、 Host module (master) Interface definition :

module spi_master(
    input         clk_40k,      // Clock signal ,40kHz
    input         rst_n,        // Reset signal , Low efficiency 
	input  [7:0]  data_in,      // The master prepares the data to be output to the slave ,8 A wide 
	input		  send_start,	 // Communication enable signal , Highly effective , Width is 1 Clock cycles (40kHz), After receiving this signal, start a master-slave device communication .
	output [7:0]  data_out,     // Data received by the host from the slave ,8 A wide 
	output		  data_out_vld,	 // Output data valid flag , High active , Width is 1 Clock cycles (40kHz)
	output		  cs_n,		 // Select the enable signal from the device chip , Low efficiency , At low power level, select the slave device to communicate with the master device , Maintain low level when in communication state .
	output        sclk,         // Synchronous clock ,1kHz, Set low level when idle 
    input         miso,        // The serial data that the master currently receives from the slave 
    output        mosi        // The serial data currently sent by the master to the slave 
 	);

2、 Slave module (slave) Interface definition :

module spi_slave(
    input        rst_n,        // Reset signal , Low efficiency 
	input        cs_n,         // Select the enable signal from the device chip 
	input        sclk,         //SPI The clock ,1kHz Set low level when idle ,
 	input        mosi,        // Serial data received by the slave from the host 
    output       miso,        // Serial data to be sent from the slave to the host 
	output [7:0]   reg0_out,	 // Internal register 0 Value 
	output [7:0]   reg1_out,	 // Internal register 1 Value 
	output [7:0]   reg2_out,	 // Internal register 2 Value 
	output [7:0]   reg3_out 	 // Internal register 3 Value 
);

3、 Circuit function description :
Slave There are four eight bit internal registers in the module (reg0、reg1、reg2、reg3), The addresses are 0~3,master Module through SPI Bus configuration slave The values of the four registers in the module ,slave The value of the register is output directly through its port .
Master Module received send_start After the signal , Put the data data_in adopt spi The bus is sent to slave Modular reg0, And then data_in Rotate two bits to the right and send to slave Modular reg1, And then data_in Rotate two bits to the right and send to reg2, The final will be data_in Recirculation shift right two bits sent to reg3. thus ,master Finish right slave Configuration of all registers in . then master Re pass spi The bus will slave in reg3 Read the data , adopt data_out Output , At the same time, a cycle width of data_out_vld.

4、SPI Transmission format :
SPI Each frame of data contains 16 position , The first to send 0 Bit is the read / write control bit , This bit is 0 representative master towards slave Writing data , by 1 Then represent master from slave Reading data ; Then send the 1-7 Bit is address bit , Send the high address first and then the low address ,9-16 Bits are data bits , High data first . All data are in sclk The rising edge of produces , Falling edge sampling .

SPI Write data format as shown in the figure 1 Shown :
 Insert picture description here
SPI The read data format is shown in the figure 2 Shown :
 Insert picture description here
2. Design thinking

1、SPI The agreement shall be determined by both parties , Here, the main module is required to send 5 Group number , The first four groups of slave modules are used to send data to different addresses , The fifth group is used to send the address and read the last data received from the module , Therefore, as required mosi Just send it out ;
2、 From the module slave Data to be read mosi Judge , If read 1, Judge later 7 Whether the bit is address , If yes, the address will be slave From module data through miso send out ; If read 0, Judge later 7 Whether the bit is address , Yes, it will mosi The sent data is received at the address slave From the register of the module .

3. FPGA Source code
1.SPI_master: The 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 or negedge rst_n)
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 or negedge rst_n)
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: From the 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: The 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 picture description here

原网站

版权声明
本文为[qq_ forty-four million nine hundred and eighty-five thousand si]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/204/202207231641422294.html