Comunicación serial SPI half duplex, Pic C Compiler

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
   }
}
PCBWay2
Scroll al inicio