Comunicación serial SPI «envió de valores», Pic C Compiler

En esta sección aprenderás a realizar una comunicación serial SPI con dos microcontroladores PIC. El objetivos es enviar el valor numérico de una variable a través del puerto SPI a un PIC receptor para mostrar el valor en un Display OLED.

Circuito de conexión 

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.

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=0, 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

Se habilita la conversión analógica-digital para la entrada en los pines analógicos AN0 y An1.

setup_adc(ADC_CLOCK_INTERNAL);

Para realizar la lectura en el pin analógico, se define el pin donde se realizara la lectura, en este caso el pin AN0 o el pin AN1, una vez definido el pin, se realiza la lectura y se guarda el valor en la variable asignada.


       set_adc_channel (0);//lectura Vout del potenciometro en PIN AN0
       delay_us(10);
       POT = read_adc();// lectura del valor

       set_adc_channel (1);//lectura Vout del sensor en PIN AN1
       delay_us(10);
       SENSOR = read_adc();// lectura del valor

De acuerdo con los parámetros SPI, se estableció una tasa de 8 bits de transmisión, en este caso la lectura analógica (ADC) es de 10 bits, por lo tanto debemos separar los bits en dos variables de 8 bits, una parte alta y una parte baja.


       parte_baja = POT & 0xFF;
       parte_alta = (POT >> 8) & 0xFF;

Para enviar datos por SPI inicialmente se activa el pulso de bajada al PIC receptor de datos (PIC esclavo), después se envían los datos y finalmente se envía el pulso de subida para terminar en envió de datos.


 output_bit(enviar,0); 
 spi_write(comando);
 spi_write(parte_baja);
 spi_write(parte_alta);
 output_bit(enviar,1);

Finalmente se envían el comando identificador del potenciometro y su valor dividido en parte alta y parte baja. 

Los datos se enviaran únicamente cuando el valor anterior del potenciometro sea diferente a su valor actual. Esto para evitar enviar el mismo valor múltiples veces.


       if (regPOT != POT)
       {
          output_bit(enviar,0); 
          spi_write(0x01);
          spi_write(parte_baja);
          spi_write(parte_alta);
          output_bit(enviar,1);
          regPOT = POT;
       }

Se realiza el mismo proceso para el envió de datos del sensor.

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

void main()
{
    setup_adc(ADC_CLOCK_INTERNAL);

   int16 POT=0;
   int16 regPOT=0;

   int16 SENSOR=0;
   int16 regSENSOR=0;

   int8 parte_baja;
   int8 parte_alta;


   while(TRUE)
   {
      
       set_adc_channel (0);//lectura Vout del potenciometro en PIN AN0
       delay_us(10);
       POT = read_adc();// lectura del valor
       
       parte_baja = POT & 0xFF;
       parte_alta = (POT >> 8) & 0xFF;


       if (regPOT != POT)
       {
          output_bit(enviar,0);
          spi_write(0x01);
          spi_write(parte_baja);
          spi_write(parte_alta);
          output_bit(enviar,1);
          
          regPOT = POT;
       }

       delay_ms(100);
       
       set_adc_channel (1);//lectura Vout del sensor en PIN AN1
       delay_us(10);
       SENSOR = read_adc();// lectura del valor
       
       parte_baja = SENSOR & 0xFF;
       parte_alta = (SENSOR >> 8) & 0xFF;

       if (regSENSOR != SENSOR)
       {
          output_bit(enviar,0);
          spi_write(0x02);
          spi_write(parte_baja);
          spi_write(parte_alta);
          output_bit(enviar,1);
          
          regSENSOR = SENSOR;
       }

        delay_ms(100);
   }

}

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=0, 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 se habilita la interrupción y se aloja en la sub-rutina «recibe()», donde se realiza la lectura del comando y los valores de la parte baja y alta del numero, finalmente los datos se guardan el la variable «recibito[]»


  recibido[0]=spi_read();//RECIBE el comando
  recibido[1]=spi_read();//RECIBE el valor  8 bits bajos
  recibido[2]=spi_read();//RECIBE el valor  8 bits altos

En la subrutina «datos()» se verifica el comando en la variable «recibido[0]», si corresponde a uno de los dos comandos del potenciometro o del sensor, el valor de «recibido[1]»  y «recibido[2]» se convierten en un valor de 16 bits y se guarda en la variable correspondiente.


      if( recibido[0]==0x01)
      {
         parte_baja = recibido[1];
         parte_alta = recibido[2];
         POT = (parte_alta << 8) | (parte_baja & 0xFF);
         return;
      }
      
       if( recibido[0]==0x02)
      {
         parte_baja = recibido[1];
         parte_alta = recibido[2];
         SENSOR = (parte_alta << 8) | (parte_baja & 0xFF);
         return;
      }

Finalmente se muestran los valores del potenciometro y del sensor en el display OLED.


   sprintf(texto,"POT=%04lu",POT);
   OLED_DrawText(1,1,texto,1);

   sprintf(texto,"SENSOR=%04lu",SENSOR);
   OLED_DrawText(1,10,texto,1);

   OLED_Display();//Muestra la información en pantalla;

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 Esclavo


#FUSES NOMCLR

#use spi (SLAVE, CLK=PIN_B1, DI=PIN_B0, DO=PIN_C7, ENABLE=PIN_A5, MODE=0, BITS=8)

#use i2c(Master,Fast, sda=PIN_B2, scl=PIN_B3, force_sw, stream=OLED_stream)//parametros I2C

#define SH1106_128_64 //DEFINE EL MODELO DE LA PANTALLA OLED
//#define SSD1306_128_64

#include "OLED_I2C.c" //libreria oled I2C

int8 recibido[20];
int16 POT;
int16 SENSOR;

void datos();
void mostrar();

#INT_SSP
void recibe()
{
   while(true)
   {
      recibido[0]=spi_read();//RECIBE el comando
      recibido[1]=spi_read();//RECIBE el valor  8 bits bajos
      recibido[2]=spi_read();//RECIBE el valor  8 bits altos
      datos();
      mostrar();
      return;
   }
}

void datos()
{
   int16 parte_baja;
   int16 parte_alta;
   while (true)
   {
      if( recibido[0] == 0x01)
      {
         parte_baja = recibido[1];
         parte_alta = recibido[2];
         POT = (parte_alta << 8) | (parte_baja & 0xFF);
         return;
      }

       if( recibido[0] == 0x02)
      {
         parte_baja = recibido[1];
         parte_alta = recibido[2];
         SENSOR = (parte_alta << 8) | (parte_baja & 0xFF);
         return;
      }

      return;
   }
}


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);

   OLED_Display();//Muestra la información en pantalla;
}


void main()
{
   enable_interrupts(INT_SSP);
   enable_interrupts(global);

   OLED_Begin();
   OLED_ClearDisplay();
   
   while(TRUE)
   {


      //TODO: User Code
   }

}
Scroll al inicio