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