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
}
}