Se trata de un protocolo en el cual dos entidades debe comunicarse: un maestro y un esclavo. La comunicación es síncrona y bidireccional. La figura siguiente ilustra a estas entidades y los terminales que conforman el puerto respectivo


   SCLK: slave clk
MOSI: Master Output, Slave Input
MISO: Master Input, Slave Output
SS: Slave Select

Internamente, este puerto tiene la siguiente estructura.



Este puerto, suele tener cuatro modos de operación, dependiendo de como se maneje la señal de reloj.

Existen cuatro formas en las cuales se maneja la señalización. Cada forma se caracteriza por dos parámetros: fase de la señal de datos y polaridad de la señal de reloj.


Wikipedia contributors, "Serial Peripheral Interface," Wikipedia, The Free Encyclopedia, https://en.wikipedia.org/w/index.php?title=Serial_Peripheral_Interface&oldid=861965942 (accessed October 2, 2018).


Modo 0: CPOL = 0
CPHA = 0.
Modo en el cual el estado del reloj permanece en estado lógico bajo y la información se envía en cada transición de bajo a alto, es decir alto activo.

Modo 1: CPOL = 0
CPHA = 1.
Modo en el cual el estado del reloj permanece en estado lógico bajo y la información se envía en cada transición de alto a bajo, es decir bajo activo.

Modo 2: CPOL = 1
CPHA = 0.
Modo en el cual el estado del reloj permanece en estado lógico alto y la información se envía en cada transición de bajo a alto, es decir alto activo.

Modo 3: CPOL = 1
CPHA = 1.
Modo en el cual el estado del reloj permanece en estado lógico alto y la información se envía en cada transición de alto a bajo, es decir bajo activo.



La página de Rasperry Pi [2] ejemplifica como puede usarse este puerto mediante bibliotecas instaladas.

contenido

  1. ....


La Raspberry Pi es una computadora en una placa apenas más grandes que una tarjeta de crédito.


La Raspberry Pi suele actuar como maestro y tiene por tanto, un bus SPI con dos terminales para elegir a a uno de dos esclavos.


MOSI    T-19
MISO    T-21
SCLK    T-23
GND     T-25
CE0	T-24
CE1	T-26


Para habilitar el controlador SPI, se debe acceder al menu preferencias y en la pestaña «preferences» se habilita el mecionado controlador.

Ingrese al menu inicio -> preferences -> Raspberry Pi configuration.
Se despligue una caja de diálogo con cuatro pestañas.
Ingrese a la pestaña Inerfaces y active los servidores respectivos



Esta biblioteca es una interfaz entre Python y el controlador de Linux.

Los datos son transferidos desde el bit más significativo (bit a la izquierda) al menos significativo (bit mas a la derecha).


La biblioteca ya viene instalada. Sus comandos de iniciación son:

import spidev
spi = spidev.SpiDev()
bus=0			#Solamente se cuenta con el bus 0
device=0		#Se puede elegir el esclavo 0 (device=0) o el esclavo 1 (device=1)
spi.open(bus, device)


Debe configurarse el modo de transferenca para el puerto SPI modificando el campo mode. El valor a este campo se asigna mediante la combinación de bits [CPOL|CPHA]). Los posibles valores se describena a continuación.

Modo 0: CPOL = 0
CPHA = 0.
Modo en el cual el estado del reloj permanece en estado lógico bajo y la información se envía en cada transición de bajo a alto, es decir alto activo.

Modo 1: CPOL = 0
CPHA = 1.
Modo en el cual el estado del reloj permanece en estado lógico bajo y la información se envía en cada transición de alto a bajo, es decir bajo activo.

Modo 2: CPOL = 1
CPHA = 0.
Modo en el cual el estado del reloj permanece en estado lógico alto y la información se envía en cada transición de bajo a alto, es decir alto activo.

Modo 3: CPOL = 1
CPHA = 1.
Modo en el cual el estado del reloj permanece en estado lógico alto y la información se envía en cada transición de alto a bajo, es decir bajo activo.



Por ejemplo:

spi.mode = 0	# Modo 0: valor por defecto
spi.mode = 1	# Modo 1
spi.mode = 2	# Modo 2
spi.mode = 3	# Modo 3


A menos que el campo max_speed_hz tenga un valor aceptado por el controlador, la transferencia fallará. Este campo puede ser uno de los siguientes:

Speed spi.max_speed_hz
125.0 MHz 125000000
62.5 MHz 62500000
31.2 MHz 31200000
15.6 MHz 15600000
7.8 MHz 7800000
3.9 MHz 3900000
1953 kHz 1953000
976 kHz 976000
488 kHz 488000
244 kHz 244000
122 kHz 122000
61 kHz 61000
30.5 kHz 30500
15.2 kHz 15200
7629 Hz 7629
5000 Hz 5000


spi.max_speed_hz = 5000


La bilioteca «spidev» define un unico búfer el cual identifica con el índice 0

list of values Los datos a enviar se colocan en una lista Python. Esta función deshabilita el puerto (SS='1') antes de enviar el siguiente dato. Al respecto, el lector debe tener en consideración que:

  • Se envía primero el bit mas significativo.
  • Se deshabilita CS entre cada elemento de la lista a enviar: CS=1.


speed_hz Velocidad de transferencia en Hz. Se puede usar cualquier frecuencia hasta «1 000 000Hz». Así entonces, es posible definir velocidades como: 5000,150000,250000,1000000.
delay_usec Se declara como un dato entero que dividido por la frecuencia de transferencia define un tiempo de espera modo de porcentaje.

Este argumento suele omitirse dado que ha quedado prefijado en Python.

bits_per_word Este parámetro suele omitirse ya que solamente se permiten datos de 8 bits.


Para enviar información se suele dividir en paquetes de 8 bits. El siguiente código enviaría 16 bits con un tiempo de espera entre los dos paquetes. Este tiempo de espera está prefijado. La línea SS no se deshabiita hasta que se ha enviado toda la información.


Enviar información: en forma de lista
to_send = [0x01, 0x02]
spi.xfer(to_send)


Esta función acepta una lista-El terminal CS se apaga (CS=0) al inicio de la lista y se enciende (CS=1) al terminar de enviar la lista. Esta lista, antes de su envío es transferida a un búfer. El tamaño del búfer puede encontrarse en la carpeta:

/sys/module/spidev/parameters/bufsiz.


spi.close()



El siguiente código envia una lista de dos datos de 8 bits: el FPGA interpretará la información como una palabra de 16 bits en la cual el bytes más significativo es 0x02 el byte menos significativo es 0x01.

import spidev
spi = spidev.SpiDev()
bus=0
device=0
spi.open(bus, device)
to_send = [0x02, 0x01]
spi.xfer(to_send,5000)


El siguiente código envía datos de 16 bits y lee datos del puerto. Entre palabras de 8 bits hay un tiempo de espera.

import spidev
import time
spi = spidev.SpiDev()
spi.open(0,0)
spi.max_speed_hz = 7629
for k in renge(5):
   resp = spi.xfer2([0x1F,1E]) #Se lee al tiempo que se envía
   print resp[0]
   time.sleep(1)
spi.close()
tomado de [5]


Se usará la tarjeta DE10-Lite, la cual contiene un FPGA modelo MAX10.

La figura siguiente ilustra la tarjeta usada en este experimento y cuales terminales se dedican al puerto SPI.



Las terminales que se usará para el puerto SPI son mostradas en la figura derecha:



Se envían palabras de 8 bits al FPGA y éste muestra el byte en sus leds. El puerto SPI se trabajará en modo 0

Se pretenden implementar el circuito SPI entre la raspberry Pi y el DE10-Lite. La figura siguiente muestra el circuito SPI deseado.



El siguiente código es funcional con el protocolo SPI.


--mosi is the input line
	process (sclk)
	begin
		if rising_edge(sclk) then
			if cs='0' then
				...
			end if;
		end if;
	end process;


Following coded is used ounly for receive data throug the shifter register "word".


	word<= word(6 downto 0) & mosi;


The register "word" is shifted at every clock cycle. Bit "word(7)" is used to transmit return data:

library ieee;
use ieee.std_logic_1164.all;

entity spi is
port (
	cs	:	in	std_logic;  --negativo
	sclk	:	in	std_logic:='0';
	mosi	:	in 	std_logic:='0';
	miso	:	out	std_logic;
	din	:	out	std_logic_vector (7 downto 0)
	);
end entity spi;

architecture behavior of spi is
	signal word : std_logic_vector (7 downto 0):=x"45";
begin

	din<=word;
	miso<=word(7);

	process (sclk)
	begin
		if rising_edge(sclk) then
			if cs='0' then
				word<= word(6 downto 0) & mosi;
			end if;
		end if;
	end process;

end architecture behavior;


El siguiente código envia una lista de dos datos de 8 bits: el FPGA interpretará la información como una palabra de 16 bits en la cual el byte más significativo es 0x02 el byte menos significativo es 0x01.

import spidev
spi = spidev.SpiDev()

bus=0
device=0
spi.open(bus, device)
resp=spi.xfer([0x67],5000)
print(resp)


También es posible probar el código siguiente que envía varias palabras binarias.

import spidev
import time

spi = spidev.SpiDev()
bus=0
device=0
spi.open(bus, device)

resp=spi.xfer2([0x12],5000)
print(resp)
time.sleep(2)

resp=spi.xfer2([0x34],5000)
print(resp)
time.sleep(2)

resp=spi.xfer2([0x56],5000)
print(resp)
time.sleep(2)


16 bits must sent from RPi to FPGA. To prepare the FPGA for data transfer is easy but Python requires a gimmick to separeate 16 bits wors into 8-bit words since SPI port operates only on 8 bits words.

  1. Se pretenden implementar el circuito SPI entre la raspberry Pi y el DE10-Lite. La figura siguiente muestra el circuito SPI deseado.



  2. The received data will be sbown using 7-segments display.



For 16 bits is used the same system used for 8-bits.

library ieee;
use ieee.std_logic_1164.all;

entity spi is
port (
	cs	:	in	std_logic;  --negativo
	sclk	:	in	std_logic:='0';
	mosi	:	in 	std_logic:='0';
	miso	:	out	std_logic;
	
	digit1	:	out	std_logic_vector(6 downto 0);
	digit2	:	out	std_logic_vector(6 downto 0);
	digit3	:	out	std_logic_vector(6 downto 0);
	digit4	:	out	std_logic_vector(6 downto 0)	
	);
end entity spi;

architecture behavior of spi is
	signal word : std_logic_vector (15 downto 0):=x"447F";
begin

	miso<=word(15);

	process (sclk)
	begin
		if rising_edge(sclk) then
			if cs='0' then
					word<= word(14 downto 0) & mosi;
			end if;
		end if;
	end process;
	
u1: entity work.dec7seg (behavior) port map (word( 3 downto  0),digit1);	
u2: entity work.dec7seg (behavior) port map (word( 7 downto  4),digit2);	
u3: entity work.dec7seg (behavior) port map (word(11 downto  8),digit3);	
u4: entity work.dec7seg (behavior) port map (word(15 downto 12),digit4);	


end architecture behavior;


The code shown above is insensible to the pauses involved in sending two 8-btis words.

Next code divide 16 bits words into two 8-bits words, then, two 8-btis words are sent. Next code send 27183 (0x6a-0x2f) and waits for 17535 (0x44-0x7f).


import spidev
spi = spidev.SpiDev()

data=27183
low=data & 255
high=(data>>8) &255

bus=0
device=0
spi.open(bus, device)
[r_high,r_low]=spi.xfer([high,low],5000)
print(hex(r_high),hex(r_low))

#Reconstructing
print( (r_high<<8)+r_low )


Se implementa un puerto SPI con dos regitros de corrimiento. El registro que recibe datos se llama word y el registro cuyo contenido será enviado se llama word2.

En este caso, los bits del registro de corrimiento word2 serán tomados de la posición mas significativa mientras que el registro se corre a la izqierda en cada transición negativa del reloj.

library ieee;
use ieee.std_logic_1164.all;

entity spi16 is
port (
	cs	:	in	std_logic;  --negativo
	sclk	:	in	std_logic;
	mosi	:	in 	std_logic;
	miso	:	out	std_logic;
	din	:	out	std_logic_vector (15 downto 0)  --Se monitorea por led
);
end entity spi16;

architecture behavior of spi16 is
	signal word : std_logic_vector (15 downto 0):=x"0000";
	signal word2 : std_logic_vector (15 downto 0):=x"0203";
begin

	din<=word;
	miso<=word2(15);

	--Recerver
	process (sclk)
	begin
		if rising_edge(sclk) then
			if cs='0' then
				word<= word(14 downto 0) & mosi;
			end if;
		end if;
	end process;

	--Transmitter
	process (cs,sclk)
	begin
		if falling_edge(sclk) then
			if cs='0' then
				word2<= word2(14 downto 0) & '0';
			end if;
		end if;
	end process;
			
end architecture behavior;


El siguiente código utiliza un ciclo para enviar una lista de un dato separado en subpalabras de 8 bits: el FPGA interpretará la información como una palabra de 16 bits.

import spidev
spi = spidev.SpiDev()

data=27183
low=data & 255
high=(data>>8) &255

bus=0
device=0
spi.open(bus, device)
[r_high,r_low]=spi.xfer([high,low],5000)
print(hex(r_high),hex(r_low))

#Reconstructing
print( (r_high<<8)+r_low )


El lector debe notar una particularidad, la palabra recibida por RPI se almacena del bit mas significativo al bit menos significativo: esto implica que resp[0] contendrá el byte más significativo y que resp[1] contendrá el byte menos significativo.

An 8-bit input buffer with 16 words has been implemented in the FPGA and it must be filled from the RPi.

Due to the implementatio of the SPI port in the FPGA, a pause between data to send is necessary. This pause is generated by raising the CS signal between each byte sent.

For this practice, a returned data is not waited.

Receiving an array of data requieres a best control of SPI port. For this reason there are three "process" block in the code:

First, the variables:



Now, the process blocks.





-- I am the slave

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity spi is
port (
	cs	:	in	std_logic;  --negativo
	sclk	:	in	std_logic:='0';
	mosi	:	in 	std_logic:='0';
	miso	:	out	std_logic;
	
	addr	:	in	std_logic_vector(3 downto 0);
	
	digit1	:	out	std_logic_vector(6 downto 0);
	digit2	:	out	std_logic_vector(6 downto 0)
	);
end entity spi;

architecture behavior of spi is
	type tBuff is array (0 to 15) of std_logic_vector(7 downto 0);
	signal buff:tBuff := (others=>x"00");
	signal word : std_logic_vector (7 downto 0):=x"00";
	signal index: integer range 0 to 15:=15;
begin

	--nothing to back
	miso<='0';
	
	--index counter
	process(cs)
	begin
		if falling_edge(cs) then
			if index=15 then
				index<=0;
			else
				index<=index+1;
			end if;
		end if;
	end process;
	
	--Reception
	process (sclk)
	begin
		if rising_edge(sclk) then
			if cs='0' then
				word<=word(6 downto 0) & mosi;
			end if;
		end if;
	end process;
	
	--Storage
	process (sclk)
	begin
		if rising_edge(cs) then
			buff(index)<=word;
		end if;	
	end process;

	
---**************************************	
u1: entity work.dec7seg (behavior) port map (buff(to_integer(unsigned(addr)))( 3 downto  0),digit1);	
u2: entity work.dec7seg (behavior) port map (buff(to_integer(unsigned(addr)))( 7 downto  4),digit2);	

end architecture behavior;


Next sends a cunting from 0 to 15. Reader can note that function "xfer2()" is called to send every data. The effect of this is a necessary raising CS signal between data.

import spidev

spi=spidev.SpiDev()
bus=0
device=0
spi.open(bus,device)

for k in range(16):
	spi.xfer2([k],5000)


This a repetition of experiment 4 but with 16-bit data.

In this experiment, a Python code will write into a buffer in the FPGA ("Buff") and will read from a second buffer in the same FPGA ("qBuff"). In this experiment the same "CS". signal will be used to activate both, write and read process.

The process of read from buffer in the FPGA will work as:


 "buff" -> "qword" -> "miso<=qword(n)"

As reade can see, from "qBuff" to "qword" and an index "n" will be necessary to retrieve the bits from variable "qword".

The input code was reviewed in previous section. Next diagrama depicts the write process (diagram above the dotted line) and read process (diagram below the dotted line).




library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity spi is
port (
	cs	:	in	std_logic;  --negativo
	sclk	:	in	std_logic:='0';
	mosi	:	in 	std_logic:='0';
	miso	:	out	std_logic;
	
	addr	:	in	std_logic_vector(3 downto 0);
	
	digit1	:	out	std_logic_vector(6 downto 0);
	digit2	:	out	std_logic_vector(6 downto 0);
	digit3	:	out	std_logic_vector(6 downto 0);
	digit4	:	out	std_logic_vector(6 downto 0)	
	);
end entity spi;

architecture behavior of spi is
	type tBuff is array (0 to 15) of std_logic_vector(7 downto 0);
	signal buff:tBuff := (x"20",x"21",x"22",x"23",x"24",x"25",x"26",x"27",x"28",x"29",x"2a",x"2b",x"2c",x"2d",x"2e",x"2f");
	signal word : std_logic_vector (7 downto 0):=x"00";
	signal wIndex: integer range 0 to 15:=15;
	
	signal qBuff:tBuff := (x"30",x"31",x"32",x"33",x"34",x"35",x"36",x"37",x"38",x"39",x"3a",x"3b",x"3c",x"3d",x"3e",x"3f");	
	signal qWord	:	std_logic_vector (7 downto 0);	
	signal rIndex	:	integer range 0 to 15:=0;
	signal n	:	integer range 0 to 7:=7;
	
begin

	--**************************************************************
	--Receiver
	--**************************************************************
	
	--Reseting bit counter
	process(cs)
	begin
		if falling_edge(cs) then
			if wIndex=15 then
				wIndex<=0;
			else
				wIndex<=wIndex+1;
			end if;
		end if;
	end process;
	
	--Recepcion
	process (sclk)
	begin
		if rising_edge(sclk) then
			if cs='0' then
				word<=word(6 downto 0) & mosi;
			end if;
		end if;
	end process;
	
	--Escritura a buffer circular
	process (cs)
	begin
		if rising_edge(cs) then
			buff(wIndex)<=word;
		end if;	
	end process;


	--**************************************************************
	--Transmiter
	--**************************************************************

	--Reading
	miso<=qWord(n);

	--Reseting bit counter
	process(cs)
	begin
		if falling_edge(cs) then
			qWord<=qBuff(rIndex);
		end if;
	end process;
	
	--bit counter
	process (sclk)
	begin
		if falling_edge(sclk) then
			if cs='0' then
				if n=0 then
					n<=7;
				else
					n<=n-1;
				end if;
			end if;
		end if;
	end process;	
	
	--Indice al buffer circular
	process (sclk)
	begin
		if rising_edge(cs) then
			if rIndex=15 then
				rIndex<=0;
			else
				rIndex<=rIndex+1;
			end if;
		end if;	
	end process;

	

---**************************************
u1: entity work.dec7seg (behavior) port map (buff(to_integer(unsigned(addr)))( 3 downto  0),digit1);	
u2: entity work.dec7seg (behavior) port map (buff(to_integer(unsigned(addr)))( 7 downto  4),digit2);	
u3: entity work.dec7seg (behavior) port map (buff(to_integer(unsigned(addr)))( 3 downto  0),digit3);	
u4: entity work.dec7seg (behavior) port map (buff(to_integer(unsigned(addr)))( 7 downto  4),digit4);	


end architecture behavior;



import spidev

data=27183
low=data&255
high=(data>>8) & 255

spi=spidev.SpiDev()
bus=0
device=0
spi.open(bus,device)

for k in range(16):
	resp=spi.xfer2([k],5000)
	print(hex(resp[0]))



library ieee;
use ieee.std_logic_1164.all;

-- Anodo comun
entity deco7seg is
generic(
	constant cero		: std_logic_vector (6 downto 0):="1000000";
	constant uno		: std_logic_vector (6 downto 0):="1111001";
	constant dos		: std_logic_vector (6 downto 0):="0100100";
	constant tres		: std_logic_vector (6 downto 0):="0110000";
	constant cuatro		: std_logic_vector (6 downto 0):="0011001";
	constant cinco 		: std_logic_vector (6 downto 0):="0010010";
	constant seis		: std_logic_vector (6 downto 0):="0000010";
	constant siete 		: std_logic_vector (6 downto 0):="1111000";
	constant ocho		: std_logic_vector (6 downto 0):="0000000";
	constant nueve 		: std_logic_vector (6 downto 0):="0010000";
	constant diez		: std_logic_vector (6 downto 0):="0001000";
	constant once		: std_logic_vector (6 downto 0):="0000011";
	constant doce		: std_logic_vector (6 downto 0):="1000110";
	constant trece		: std_logic_vector (6 downto 0):="0100001";
	constant catorce	: std_logic_vector (6 downto 0):="0000110";	
	constant quince		: std_logic_vector (6 downto 0):="0001110";	
	constant otro 		: std_logic_vector (6 downto 0):="1111111"
);
port (
	I  :  in std_logic_vector (3 downto 0);
	Q  : out std_logic_vector (6 downto 0)
);
end entity deco7seg;

architecture behavior of deco7seg is
begin
 with I select
	Q <=	cero	when "0000",
		uno	when "0001",
		dos	when "0010",
		tres	when "0011",
		cuatro	when "0100",
		cinco	when "0101",
		seis	when "0110",
		siete 	when "0111",
		ocho 	when "1000",
		nueve	when "1001",
		diez	when "1010",
		once	when "1011",
		doce	when "1100",
		trece	when "1101",
		catorce	when "1110",
		quince	when "1111",
		otro 	when others;
end architecture behavior;