
En esta sección aprenderÔs a realizar una comunicación serial SPI half duplex con dos microcontroladores PIC. El objetivo es enviar un comando al PIC esclavo para pedir el valor de diferentes sensores y mostrarlos en una pantalla OLED.
Comunicación serial SPI
SPI (Serial Peripheral Interface) es un protocolo de comunicación serial sincrónica que se utiliza para la transferencia de datos entre dispositivos electrónicos. Se compone de una sola lĆnea de reloj de sincronización (SCLK) y dos lĆneas de datos: MOSI (Master Output Slave Input) y MISO (Master Input Slave Output).
En una comunicación SPI, un dispositivo maestro envĆa comandos y datos a uno o varios dispositivos esclavos, y los dispositivos esclavos responden con los datos solicitados. El maestro controla la comunicación estableciendo la frecuencia de reloj y eligiendo el dispositivo esclavo con el que quiere comunicarse mediante una lĆnea de selección de esclavos (SS).
Los dispositivos que se comunican a travƩs de SPI deben estar configurados para trabajar en la misma velocidad de reloj y tener los mismos ajustes de formato de datos, como la longitud de la palabra de datos y la polaridad y fase del reloj.
SPI se utiliza comĆŗnmente en sistemas embebidos, especialmente en aplicaciones que requieren alta velocidad de transferencia de datos y baja complejidad de hardware. Algunos ejemplos de dispositivos que utilizan SPI son sensores, controladores de pantalla, tarjetas de memoria, entre otros.

Circuito de conexión

Programación PIC de Maestro
Inicialmente se establecen los parÔmetros de la comunicación SPI.
- CLK = pin de seƱal de reloj
- DI = Pin de recepción de datos
- D0= Pin de transmisión de datosĀ
- BAUD = Velocidad de transmisiónĀ
- MODE = Modo de comunicación SPI
- BITS = Datos de transmisión
#use spi (MASTER, CLK=PIN_B1, DI=PIN_B0, DO=PIN_C7, BAUD=9600, MODE=1, BITS=8)
Se establece el pin para enviar el pulso que habilita la recepción de datos en el PIC esclavo.
#define enviar PIN_C2
Para poder enviar un comando y recibir los datos por el puerto SPI se utilizaran dos sub-rutinas una para recibir un dato de 8 bits y la segunda para recibir un dato de 16 bits.
Para recibir un dato de 8 bits se realiza la siguiente sub-rutina.
int8 recibe_8bits (int8 comando)
{
int8 recibido;
output_bit(enviar,0); //Habilita la recepcion de datos en esclavo
spi_write(comando); //envia el comando
output_bit(enviar,1); //Deshabilita la recepcion de datos en esclavo
delay_us(100);
output_bit(enviar,0);
recibido = spi_read(0); //recibe el valor
output_bit(enviar,1);
return recibido;
}
Para recibir un dato de 16 bits se envĆan dos comandos correspondientes para recibir el valor de los 8 bits bajos y los 8 bits altos de una variable de 16 bits.
int16 recibe_16bits (int8 comando1, int8 comando2)
{
int16 parte_baja;
int16 parte_alta;
output_bit(enviar,0); //Habilita la recepcion de datos en esclavo
spi_write(comando1); //envia el comando
output_bit(enviar,1); //Deshabilita la recepcion de datos en esclavo
delay_us(100);
output_bit(enviar,0);
parte_baja = spi_read(0); //recibe parte baja del valor
output_bit(enviar,1);
delay_us(100);
output_bit(enviar,0);
spi_write(comando2); //envia el comando
output_bit(enviar,1);
delay_us(100);
output_bit(enviar,0);
parte_alta = spi_read(0); //recibe parte alta del valor
output_bit(enviar,1);
return (parte_alta << 8) | (parte_baja & 0xFF);
}
Para hacer uso de las sub-rutinas solo se coloca el comando a enviar para que la subrutina devuelva el valor de 8 bits o 16 bits.
Inicialmente se envĆa el comando Ā«0x05Ā» para pedirle al PIC esclavo que nos devuelva un valor de 8 bits correspondiente al estado del boton.
boton = recibe_8bits(0x05); //recibe un dato de 8 bits
El segundo ejemplo envĆan los comandon Ā«0x03, 0x04Ā», para pedirle al PIC esclavo que devuelva un valor de 16 bits, en este caso el valor correspondiente al sensor de gas.
sensor = recibe_16bits(0x03, 0x04);//recibe un dato de 16 bits
Finalmente se muestran los valores en el Display OLED.
Para este circuito se modifican los pines de conexión del display I2C en los parÔmetros, para esto solo basta con modificar los pines donde se conectara la pantalla OLED, y el comando para forzar el uso de la comunicación I2C por software.
sda=PIN_B2, scl=PIN_B3, force_sw,
Código completo PIC Maestro
#FUSES NOMCLR
#use spi (MASTER, CLK=PIN_B1, DI=PIN_B0, DO=PIN_C7, BAUD=9600, MODE=0, BITS=8)
#define enviar PIN_C2 //pin ss
#use i2c(Master,Fast, sda=PIN_B2, scl=PIN_B3, force_sw, stream=OLED_stream)
#define SH1106_128_64 //DEFINE EL MODELO DE LA PANTALLA OLED
//#define SSD1306_128_64
#include "OLED_I2C.c" //libreria oled I2C
int16 pot;
int16 sensor;
int8 boton;
int16 recibe_16bits (int8 comando1, int8 comando2)
{
int16 parte_baja;
int16 parte_alta;
output_bit(enviar,0); //Habilita la recepcion de datos en esclavo
spi_write(comando1); //envia el comando
output_bit(enviar,1); //Deshabilita la recepcion de datos en esclavo
delay_us(100);
output_bit(enviar,0);
parte_baja = spi_read(0); //recibe parte baja del valor
output_bit(enviar,1);
delay_us(100);
output_bit(enviar,0);
spi_write(comando2); //envia el comando
output_bit(enviar,1);
delay_us(100);
output_bit(enviar,0);
parte_alta = spi_read(0); //recibe parte alta del valor
output_bit(enviar,1);
return (parte_alta << 8) | (parte_baja & 0xFF);
}
int8 recibe_8bits (int8 comando)
{
int8 recibido;
output_bit(enviar,0); //Habilita la recepcion de datos en esclavo
spi_write(comando); //envia el comando
output_bit(enviar,1); //Deshabilita la recepcion de datos en esclavo
delay_us(100);
output_bit(enviar,0);
recibido = spi_read(0); //recibe el valor
output_bit(enviar,1);
return recibido;
}
void mostrar()
{
char texto[20];
sprintf(texto,"POT=%04lu",pot);
OLED_DrawText(1,1,texto,1);
sprintf(texto,"SENSOR=%04lu",sensor);
OLED_DrawText(1,10,texto,1);
sprintf(texto,"BOTON=%u",boton);
OLED_DrawText(1,20,texto,1);
OLED_Display();//Muestra la información en pantalla;
return;
}
void main()
{
OLED_Begin();
OLED_ClearDisplay();
while(TRUE)
{
pot = recibe_16bits(0x01, 0x02);
delay_us(100);
sensor = recibe_16bits(0x03, 0x04);
delay_us(100);
boton = recibe_8bits(0x05);
mostrar();
}
}
Programación PIC de Esclavo
Se establecen los parÔmetros para la comunicación SPI.
- CLK = pin de seƱal de reloj
- DI = Pin de recepción de datos
- D0= Pin de transmisión de datosĀ
- ENABLE = Pin de pulso de seƱal de recepción de datosĀ
- MODE = Modo de comunicación SPI
- BITS = Datos de transmisión
#use spi(SLAVE, CLK=PIN_B1, DI=PIN_B0, DO=PIN_C7, ENABLE=PIN_A5, MODE=1, BITS=8)
Se habilita la interrupción para la recepción de datos por el puerto SPI.
enable_interrupts(INT_SSP);
enable_interrupts(global);
Cuando se recibe un comando por el puerto SPI se habilitara la interrupción y el programa se alojara en la sub-rutina «recibe()» para leer el comando recibido.
#INT_SSP
void recibe()
{
while (true)
{
if (spi_data_is_in())
{
comando = spi_read();//RECIBE el comando
enviar();
return;
}
}
}
El comando recibido se identifica en una sentencia Ā«switch-caseĀ» y dependiendo del comando se envĆa el valor correspondiente.
switch (comando)
{
case 0x01:
parte_baja = pot & 0xFF;
parte_alta = (pot >> 8) & 0xFF;
SSPBUF = parte_baja;
break;
case 0x02:
SSPBUF = parte_alta;
break;
case 0x03:
parte_baja = sensor & 0xFF;
parte_alta = (sensor >> 8) & 0xFF;
SSPBUF = parte_baja;
break;
case 0x04:
SSPBUF = parte_alta;
break;
case 0x05:
SSPBUF = boton;
break;
}
La lectura de los pines analógicos es de 10 bits por lo que se guarda en una variable de 16 bits.
int16 pot;
set_adc_channel (0);//lectura Vout del potenciometro en PIN AN0
delay_us(10);
pot = read_adc();// lectura del valor
Para enviar el valor de 16 bits se divide en 2 variables de 8 bits, la parte baja y la parte alta.
parte_baja = pot & 0xFF;
parte_alta = (pot >> 8) & 0xFF;
Se utiliza el registro «SSPBUF» para colocar el dato en el puerto SPI de salida del PIC Esclavo esperando a que el PIC Maestro lo lea.
case 0x01:
SSPBUF = parte_baja;
break;
La dirección del registro SSPBUF se encuentra en la hoja de datos del PIC, el registro se encarga de colocar el dato en el bus de transmisión del puerto SPI del PIC Esclavo esperando a ser leĆdo por el PIC Maestro.Ā
Se establece la dirección del registro «SSPBUF» que en este caso es «0xFC9» para el PIC18F4550.
#byte SSPBUF = 0xFC9 //Registro de transmision y buffer de recepcion
Código completo PIC Esclavo
#FUSES NOMCLR
#use spi(SLAVE, CLK=PIN_B1, DI=PIN_B0, DO=PIN_C7, ENABLE=PIN_A5, MODE=0, BITS=8)
#byte SSPBUF = 0xFC9 //Registro de trasnmision y recepcion
int8 comando;
int16 pot;
int16 sensor;
int8 boton;
int8 parte_baja;
int8 parte_alta;
void enviar();
#INT_SSP
void recibe()
{
while (true)
{
if (spi_data_is_in())
{
comando = spi_read();//RECIBE el comando
enviar();
return;
}
}
}
void enviar()
{
switch (comando)
{
case 0x01:
parte_baja = pot & 0xFF;
parte_alta = (pot >> 8) & 0xFF;
SSPBUF = parte_baja;
break;
case 0x02:
SSPBUF = parte_alta;
break;
case 0x03:
parte_baja = sensor & 0xFF;
parte_alta = (sensor >> 8) & 0xFF;
SSPBUF = parte_baja;
break;
case 0x04:
SSPBUF = parte_alta;
break;
case 0x05:
SSPBUF = boton;
break;
}
return;
}
void main()
{
setup_adc(ADC_CLOCK_INTERNAL);
enable_interrupts(INT_SSP);
enable_interrupts(global);
while(TRUE)
{
set_adc_channel (0);//Habilita lectura en PIN AN0
delay_us(10);
pot = read_adc();// lectura del potenciometro
set_adc_channel (1);//Habilita lectura en PIN AN1
delay_us(10);
sensor = read_adc();// lectura del sensor de gas
boton=input(pin_d0);//lectura del boton
}
}