Вы находитесь на странице: 1из 19

http://picfernalia.blogspot.com.co/2013/04/comunicacionesserie-spi.

html
Comunicaciones Serie SPI

La familia PIC18 dispone de varias posibilidades de comunicaciones serie.


Adems del puerto USART que ya describimos, dispone de un puerto
dedicado a comunicaciones sncronas serie, el SSP (Serial Synchronous
Port). Dicho puerto puede dedicarse a varios protocolos, tales como SPI o
I2C. Ambos son excluyentes, esto es, si se configura el perifrico para SPI
no podr usarse para I2C y viceversa. Si se precisan de forma conjunta
comunicaciones I2C y SPI la nica solucin es un microcontrolador con dos
puertos SSP o bien, implementar algunos de los dos protocolos a travs de
software.
En este tutorial vamos a examinar en el protocolo SPI (Serial Protocol
Interface), describiendo los registros SFR involucrados y detallando los
procedimientos para transmitir y recibir. Lo ilustraremos con un ejemplo
muy sencillo de comunicacin con un perifrico SPI, un conversor DAC
(MCP4822 de Microchip).
En entradas posteriores veremos ejemplos de comunicaciones SPI con otros
perifricos con protocolos de comunicacin ms complicados.
Archivos de cdigo asociados a esta entrada: spi_mcp4822.c
-------------------------------------------------------------------------------------------Es importante entender los conceptos bsicos detrs de una comunicacin
SPI, que son diferentes de otros tipos de comunicaciones.
Empezaremos recordando las propiedades de un registro de desplazamiento
o shift register (ver figura):

En un registro de desplazamiento, con cada clock del reloj, un nuevo bit


entra en el registro y desplaza a todos los bits una posicin. El ltimo bit
sale del registro. Los registros de desplazamiento son la base de las

conversiones paralelo/serie y viceversa. Por ejemplo, en la UART existe un


TSR (Tx Shift Register) donde se carga el byte a enviar (de forma paralela)
y van saliendo sucesivamente (serie) los bits a enviar. Dicho TSR se carga
como vimos con el dato colocado en TXREG. De forma totalmente anloga
en el circuito de recepcin de la UART tenemos otro registro RSR (Rx Shift
register) donde van entrando sucesivamente (serie) los bits recibidos. Al
llenarse, el dato se transfiere (en paralelo) al registro buffer de recepcin
(RCREG). Otro uso similar (se suele denominar SIPO, Serial In, Parallel
Out) de un SR se da en un port expander, donde un registro (puerto
extendido) se va llenando sucesivamente con datos recibidos de forma
serie.
Vemos que en la UART tenemos sendos registros de desplazamiento para la
recepcin como para la transmisin. Dichos registros son independientes
entre s. En el caso de SPI solo tendremos un registro SSP SR (accesible a
travs de un buffer SSPBUF).
Cmo podemos recibir y transmitir con un solo registro? Consideremos
ahora un registro de desplazamiento circular, donde la salida del registro se
usa como entrada del mismo:

Supongamos que nuestro registro tiene 16 bits y al ser circular, el bit que
entra empujando a los dems es justo el que acaba de salir por el otro
lado. Ahora pensar que este registro se parte en dos, cada uno de 8 bits,
pero formando conceptualmente un nico registro de 16 bits:

Lo que tenemos es justo la base de una comunicacin SPI entre dos


dispositivos. Cada una de las partes del registro circular es el registro
SSPSR de cada dispositivo y ambos comparten el reloj. La idea es que si en
el SSPSR1 hay un dato A y en el SSPSR2 un dato B, tras 8 ticks de reloj, los
datos A y B se habrn intercambiado entre los dispositivos. Esta es la razn
por la que al contrario que la UART slo se dispone de un registro, sin
diferenciar entre entrada y salida. En el protocolo SPI no hay realmente
transmisiones ni recepciones, solo intercambios de datos A y B, ya que por
cada dato enviado debe haber siempre uno recibido. Depende de las
circunstancias el cmo se interprete una transferencia SPI:
1. Ser una transmisin si el 1er dispositivo tena por objetivo
era mandar el dato A al 2do dispositivo, mientras que el dato B
recibido era basura (pero no puedo "evitar" recibir dicho dato).
2. Ser una recepcin si el dato A enviado es irrelevante y solo lo
mando para obtener a cambio el dato B (pero no hay forma de
recibir nada si yo no mando algo "a cambio").
3. Puede ser una transmisin/recepcin simultnea si tanto el
dato A como el B son significativos para la comunicacin.
Pensar por ejemplo en un DigitalSignalProcesor (DSP) que
recibe una serie de muestras de una seal y efecta algn tipo
de procesado sobre ella. Tras un cierto retraso, inherente al
procesado, empezar a mandar muestras de vuelta. A partir de
ese momento, por cada muestra de la seal original que
mande el host recibir una muestra procesada en una
comunicacin full-duplex.
4. Finalmente, hay situaciones donde los datos intercambiados no
le interesan a ningn dispositivo. Por ejemplo, en las
especificaciones del protocolo SPI de las tarjetas SD se
requiere mandar 8 clocks de reloj tras un intercambio
comando/respuesta para que la tarjeta pase a ejecutar el
comando recibido. En ese caso el microcontrolador y la tarjeta
se intercambiarn un byte que a ninguno de los dos interesa

solo para que le lleguen los 8 pulsos de reloj necesarios a la


tarjeta.
La nica asimetra entre ambos dispositivos es que uno de ellos debe
generar los pulsos de reloj que hacen avanzar el registro de
desplazamiento. Dicho dispositivo (a la izquierda en la grfica anterior) es el
master y ser quien controle la transmisin.
En la figura siguiente (extrada del datasheet de Microchip para el
PIC18F252) se ilustra lo que acabamos de contar. Como se ve es totalmente
anloga a la figura anterior, aadiendo el hecho de que el usuario (al igual
que suceda en el caso de la UART) no puede acceder al verdadero registro
de desplazamiento SSPSR, trabajando en su lugar con un buffer SSPBUF.

La lnea de SDO (master) a SDI (slave) tambin se suele etiquetar MOSI


(Master Out Slave In). Igualmente la lnea que conecta SDO (slave) con SDI
(master) es denominada MISO (Master In Slave Out):

Adems de las dos lneas de datos (MOSI y MISO) y el reloj (SCK), en la


figura anterior se muestra una cuarta lnea (CS, Chip Select, o SS, Slave
Select) que se usa para indicar al slave que se va a iniciar una
comunicacin. Tambin permite la comunicacin de un master con varios
slaves:

La barra encima de SS indica negacin, y es la forma standard de expresar


que si queremos seleccionar al esclavo #2 debemos poner a nivel bajo SS2
y mantener altas SS1 y SS3. De esta forma los esclavos #1 y #3 ignoraran
educadamente la conversacin entre Master y Slave #2.
Como se ve, si empieza a haber muchos esclavos el nmero de lneas
dedicadas a la seleccin de dispositivos crece. Adems, el master tiene que
estar continuamente preguntando a los esclavos si desean algo, ya que un
esclavo no tiene ninguna forma de iniciar la conversacin.
Esta es la razn por la cual SPI es el protocolo preferido por su simplicidad
cuando slo tenemos una nica conexin master-slave. Cuando hay que
manejar varios esclavos se prefiere el protocolo I2C. Este protocolo tambin
es de tipo serie y sncrono (en la familia PIC18, SPI e I2C comparten el
mismo puerto de comunicaciones serie sncronas SSP), pero implementa un
sistema de direcciones, por lo que no es preciso aadir lneas adicionales
para los nuevos dispositivos.
COMUNICACIONES SPI en el PIC:
Pasemos ahora a detallar como implementar el protocolo SPI al trabajar
sobre un PIC (asumimos que dispone del hardware adecuado, el puerto
sncrono paralelo SSP). Como todo perifrico del PIC su configuracin y
manejo estn controlado por una serie de registros SFR (Special Function
Registers. Para el puerto SPI dichos registros SFRs son:
SSPCON1, SSPSTAT y SSPBUF
los dos primeros son registros de configuracin, mientras que el segundo es
donde se ponen los datos a transmitir (y como hemos explicado, de donde
se recogern los datos recibidos).

Pasamos ahora a describir las opciones posibles en la configuracin del


puerto SPI en un PIC, que se determinan con una serie de bits en los
registros SSPCON1 y SSPSTAT.
1) Eleccin Slave/Master
Obviamente la primera eleccin es decidir si el PIC ser el master o un
dispositivo slave en la comunicacin. Los contenidos de los 4 bits ms bajos
de SSPCON1 determinan esta eleccin. Sus posibles valores son:
Opciones modo master:

00 11 --> clock = TMR2/2


00 10 --> clock = Fosc/64
00 01 --> clock = Fosc/16
00 00 --> clock = Fosc/4

Opciones en modo slave: 01 01 -> No se usa SS


01 00 -> Se usa SS
Como se ve el primer bit (SSPCON1.SSPM3) es siempre 0 para ambos
modos (esto sucede porque al estar compartido el puerto SSP, estos cuatro
bits tambin son usados para la configuracin del modo I2C).
El segundo bit (SSPCON1.SSPM2) determina si el dispositivo es master (0)
o slave (1).
En modo master los dos ltimos bits (SSPCON1.SSPM1 y SSPCON.SSPM0)
determinan las cuatro posibles frecuencias del reloj. La frecuencia del reloj
ser una fraccin (4, 16, 64) del oscilador principal o puede asociarse al
ritmo del Timer2.
Por ejemplo, con un cristal de 20 MHz podramos tener un master con un
reloj de 5MHz (0000), 1.25MHz (0001) y 312KHz (0010). La opcin del
TMR2/2 (0011) nos permite programar otras frecuencias a travs del timer
TMR2.
Si hemos escogido el modo slave, los bits restantes determinan si usaremos
o no el pin dedicado para SS (Slave Select). En la familia PIC18F4520, dicho
pin es el RA4. Si el valor es 01 no se usar SS y RA4 podr usarse como un
pin normal. Si el valor es 00 se habilita RA4 como pin de control SS.
Si vamos a ser un dispositivo SLAVE ya no hay nada ms que configurar. Lo
nico recomendable es hacer SSPSTAT.SMP=0 aunque no es estrictamente
necesario ya que ese es su valor por defecto.
En cambio, si nuestro dispositivo va a actuar como MASTER debemos
configurar el modo SPI en el que vamos a trabajar.

Modos SPI (master): relacin reloj/datos


Aunque tengamos establecida la frecuencia del reloj, todava hay varias
opciones para el master, referidas a la polaridad de la seal de reloj, y la
fase entre dicha seal y los datos de entrada/salida.
Los bits que determinan estos aspectos son:
SSPCON1.CKP (Clock polarity)
SSPSTAT.CKE (Clock Edge)

SSPSTAT.SMP

(Sample bit)

El primero (CKP) define la polaridad de la seal de reloj (su IDDLE_LEVEL,


si est a nivel alto o bajo cuando el puerto este inactivo).
El segundo bit (CKE) especifica la fase de los datos de salida con respecto al
reloj.
Por ltimo, el tercer bit(SMP) determina el momento en que se muestrean
los datos de entrada (tambin referido a la seal de reloj).
El parmetro ms sencillo es la polaridad del reloj (SSPCON1.CKP) que en la
literatura SPI se suele denotar como CPOL (Clock Polarity). Si es 0 indica
que el reloj esta bajo mientras no se manda nada. Si es 1 el IDDLE_STATE
del reloj ser un nivel alto (1).
El segundo parmetro (SSPSTAT.CKE) determina la fase de los datos de
salida con respecto al reloj. En la literatura standard nos encontramos con
un parmetro totalmente equivalente CPHA (Clock Phase), aunque su
definicin es inversa de CKE. Esto es, CPHA = 1-CKE.
Juntos, CPOL y CPHA determinan lo que se conoce como el modo
SPI usado. Generalmente se expresa como un par de nmero. As, el modo
SPI (0,1) indica que debemos hacer CPOL=0 y CPHA=1, o traducido a la
nomenclatura PIC
SSPCON1.CKP= CPOL = 0
SSPSTAT.CKE = (1-CPHA)= 0
Hemos dicho que CPHA determina el momento en el que los datos de salida
estn estables (y deberan ser muestreados por el otro dispositivo), pero no
hemos explicado cual es su relacin ni que significa un valor de 0 o 1.
Para entenderlo, veamos la siguiente figura (adaptada del datasheet de
Microchip), ilustrando las posibilidades del reloj y su relacin con los datos
de entrada/salida:

Las cuatro primeras trazas ilustran las cuatro posibilidades de reloj y la


traza etiquetada como SDO la posicin de los datos de salida. Las lneas
verdes indican el momento en que los datos debera ser muestreados.
Como se ve, la interpretacin de CPOL=CKP es inmediata. CKP=0 indica un
estado de reposo bajo (azul) y CKP=1 indica un estado de reposo (antes y
despus de enviar datos) alto (color rojo).
Mirando la grfica (primeras dos trazas de reloj) podemos ver que si
CKE=0 (CPHA=1) el "centro" del bit de salida corresponde a las "segundas"
transiciones del reloj. Por el contrario si CKE=1 (CPHA=0) el centro del bit
est alineado con la primera transicin del reloj.
El problema es que dicha interpretacin no es muy intuitiva. A veces se
prefiere describir el protocolo en trminos de si los bits estarn estables con
las subidas o bajadas de reloj. Con la descripcin anterior si escogemos
CKE=1 sabemos que el dato est listo en la primera transicin de reloj, pero
dicha transicin puede ser de subida (traza 3) o de bajada (traza 4),
dependiendo de la polaridad del (CPOL).

Si queremos formalizarlo, podemos definir un nuevo parmetro Low2High,


(L2H=1 si el bit esta listo en las subidas y L2H=0 si est disponible en las
bajadas) y determinar CKE como:
CKE = L2H xor CKP

Finalmente queda decidir el valor de SPSSTAT.SMP que determina el


momento de muestreo de los datos entrantes, como se aprecia en la parte
baja de la grfica anterior:
SMP=0 los bits de entrada estn alineados con el centro del periodo de
reloj
SMP=1 los bits de entrada estn disponibles al final del periodo de reloj.
En la prctica, cmo elegir estos parmetros para comunicar nuestro PIC
con un cierto dispositivo? Como siempre hay varias posibilidades:
1. Reusar un cdigo que andaba por ah y que funciona.
2. Leer las especificaciones del dispositivo donde por algn lado
vendr descrito que modos SPI acepta. Normalmente se
describen con la pareja (CPOL,CPHA). Recordar que CKE=1CPHA.
3. A veces en vez de especificar el modo SPI nos dan un
diagrama de tiempos con el reloj y la posicin esperada de
datos de salida y entrada. Con lo que hemos explicado
deberamos ser capaces de determinar los tres parmetros
necesarios (CKP, CKE, SMP).
Aunque la opcin 1 es muchas veces un buen punto de partida, no debemos
fiarnos del todo. Hay algunos casos en los que es posible que una eleccin
incorrecta de datos funcione (a medias o intermitentemente). Por ejemplo,
imaginad un caso donde los datos de entrada deberan muestrearse en el
centro del periodo (SMP=0) pero usamos SMP=1. Aunque el punto de
muestreo se ha llevado al momento de cambio de datos, posiblemente
seguir funcionando porque el otro dispositivo necesitar un tiempo para
cambiar los datos y puede que los datos recogidos sean los correctos. Sin
embargo est claro que estamos muestreando en un momento en el que los
datos pueden cambiar de repente, por lo que cualquier cambio de timings,
etc. puede hacer que lo recibido sea basura. Si tenemos la documentacin
del dispositivo no costar mucho determinar la eleccin de parmetros
correcta.
Una vez establecidas las opciones solo queda habilitar el puerto SSP (con
SSPCON1.SSPEN=1) y establecer las correspondientes direcciones de los
pines involucrados (a travs del correspondiente registro TRIS).
En la mayora de los PICs los pines asociados a las lneas SCL, SDI, SD0 son
respectivamente RC3, RC4 y RC5.
El pin RC4 (SDI) deber ser configurado como entrada.

Implementacin de las comunicacin SPI


Al igual que hicimos en el caso de la UART presentaremos las tpicas
funciones de las que disponemos en un compilador y luego las
reescribiremos usando nuestros recin adquiridos conocimientos.
Las funciones de un compilador respecto al mdulo SPI se dividen en
rutinas de inicializacin (que afectarn a SSPCON1 y SSPSTAT) y rutinas de
transferencia de datos (bsicamente poner/sacar datos de SSPBUF). Por
ejemplo, en el compilador de MikroC Pro encontramos las siguientes
funciones bsicas:
INICIALIZACION: SPI_Init_advance,
TRANSFERENCIA: SPI_read, SPI_write.

La primera inicializa y configuran el puerto SSP en modo SPI. Los


parmetros que se pasan a SPI_Init_advance son bastante descriptivos y
corresponden a las opciones explicadas con anterioridad. Por ejemplo:
_SPI_CLK_IDLE_HIGH,_SPI_CLK_IDLE_LOW
_SPI_DATA_SAMPLE_MIDDLE,_SPI_DATA_SAMPLE_END
_SPI_LOW_2_HIGH,_SPI_LOW_2_HIGH
(L2H=1/0)
_SPI_MASTER_OSC_DIV4, DIV16, DIV64, TMR2
_SPI_SLAVE_SS_ENABLE,_SPI_SLAVE_SS_DIS

-> Polaridad de reloj (CKP=CPOL)


-> Muestreo de datos entrada SMP=0/1
-> Transicin datos transmitidos
-> Setup as master y eleccin de reloj
-> Setup as slave con y sin SS pin

Notad que MikroC Pro usa la convencin de especificar la fase reloj/datosTX


a travs de L2H (especificar si estn disponibles en las subidas/bajadas de
reloj) y no directamente a travs de CKE o CPHA=1-CKE.
En el caso del compilador C18, las rutinas son:
INICIALIZACIN: OpenSPI
TRANSFERENCIA: ReadSPI, WriteSPI o de forma equivalente getcSPI,
putcSPI
Los parmetros de la funcin de inicializacin son:
void OpenSPI(unsigned char sync_mode, unsigned char bus_mode,
unsigned char smp_phase)
sync_mode:
--> master: SPI_FOSC_4,SPI_FOSC_16,SPI_FOSC_64,SPI_FOSC_TMR2(clock)
--> slave:
SLV_SSON,SLV_SSOFF(usoonodelpindedicadoparaSS)
bus_mode:
-->
-->
-->
-->

MODE_00
MODE_01
MODE_10
MODE_11

(CKP=0,
(CKP=0,
(CKP=1,
(CKP=1,

CKE=1)
CKE=0)
CKE=1)
CKE=0)

smp_phase
--> SMPEND

datos de entrada disponible al final del ciclo (SMP=1)

--> SMPMID

datos de entrada disponible en el medio del ciclo (SMP=0)

Como se observa la inicializacin de C18 (parmetro bus_mode) sigue la


nomenclatura tpica de los modos SPI (CKP, CPHA).
Reescribir por nuestra cuenta una funcin de inicializacin es sencillo: basta
poner los bits de los registros SSPCON1 y SSPSTAT a los valores
adecuados.
Las siguientes funciones pueden ser usadas para conseguir el mismo
objetivo:

void spi_enable(void)
// Enable SSP port and set TRIS register
{
TRISCbits.TRISC3=0; TRISCbits.TRISC5=0; TRISCbits.TRISC4=1; // SCL out, SDO,
out, SDI in
SSPCON1bits.SSPEN=1; // Enable SPI port
}
// Sets SPI mode (CPOL,CPHA,SMP)
void spi_mode(uint8 CPOL,uint8 CPHA,uint8 sample)
{
SSPCON1bits.CKP=CPOL; SSPSTATbits.CKE=1-CPHA;
SSPSTATbits.SMP=sample;
}
// Sets clock frequency for SPI in master mode
// clock =3 (TMR2/2) =2 (Fosc/64) =1 (Fosc/16)
void spi_master(uint8 clock)
{
SSPCON1 = (SSPCON1 & 0xF0) | clock;
}

=0 (Fosc/4)

// Set SPI port as slave.


// ss=0 -> no dedicated SS pin,
// ss=1 -> dedicated SS pin (RA5 in PIC18F4520)
void spi_slave(uint8 ss)
{
ss=1-ss; ss = ss+4;
SSPCON1 = (SSPCON1 & 0xF0) | ss;
}

Remarcar que estas funciones no aportan nada que las funciones de C18 o
MikroC no puedan hacer. Las listamos para mostrar que configurar un
perifrico no es complicado y para beneficio de aquellos que usen un
compilador sin soporte para SPI.
Una vez inicializado el puerto con unas u otras funciones, estamos listos
para mandar/recibir datos. Tanto en C18 (ReadSPI, WriteSPI) como en
MikroC (SPI_read, SPI_write) tenemos un par de funciones que
leen/escriben un byte en la lnea SPI.
La siguiente funcin sera nuestro equivalente:
uint8 spi_transfer(uint8 x)
{

// basic SPI transfer

SSPBUF = x; while(SSPSTATbits.BF==0); return(SSPBUF);


}

La funcin recibe un byte de datos x y lo coloca en el buffer SSPBUF. Esto


provoca las siguientes acciones:
1. Inmediatamente x se transfiere al verdadero registro de
desplazamiento del puerto SSPSR y el buffer SSPBUF queda
vaco, esperando los datos de llegada.
2. Se inicia la transmisin (y simultnea recepcin) de datos.
3. Cuando se han transmitido los 8 bits (y por lo tanto se han
recibido 8 bits) el byte recibido se pasa a SSPBUF y la bandera
SSPSTAT.BF se pone a 1 (buffer full).
En nuestra funcin tras poner los datos en SSPBUF monitorizamos
SSPSTAT.BF hasta que se haga 1 (seal de que los datos recibidos estn
listos). En ese momento se devuelve el valor recibido (en SSPBUF). La
accin de leer SSPBUF pone a 0 el bit SSPBUF.BF (indicando buffer empty).
Notad que la funcin se llama spi_transfer, sin especificar si es TX o RX,
porque nosotros ya sabemos que en SPI no hay transmisiones ni
recepciones propiamente dichas, slo transferencias.
Si por una mayor legibilidad del cdigo queremos disponer de una funcin
de escritura y otra de lectura podemos simplemente usar unos #define:
// SPI functions aliases
#define spi_tx(x)
spi_transfer(x)
value.
#define spi_rx()
spi_transfer(0xFF)
#define spi_clock() spi_transfer(0xFF)
the data)

// sends TX data, ignores return


// sends dummy data, returns RX data.
// send 8 clocks (nobody cares about

Y eso es todo. Hemos reproducido la funcionalidad de un compilador tpico.


Al igual que hicimos en el caso de la UART, conociendo los detalles
podramos adaptar las rutinas a nuestras necesidades especificas.
Por ejemplo, la rutina de transferencia anterior (al igual que las de MikroC o
C18) son bloqueantes. El bucle while(SSPSTAT.BF==0) asegura que la
funcin vuelve con un dato. Pero la comunicacin se interrumpe en mitad de
un byte, nuestro programa se bloqueara. El PIC cuenta con una
interrupcin del puerto SSP. La bandera SSPIF se levanta cuando una
recepcin se ha completado y el dato est listo para ser recogido en
SSPBUF. Usando interrupciones la funcin anterior podra volver sin haber
completado la transferencia y sera responsabilidad de la interrupcin
rescatar el dato recibido.
Definicin de la lnea CS: estrategias:
En el apartado anterior vimos las funciones necesarias para una
comunicacin SPI, pero dejamos un aspecto de lado: la definicin de la lnea
CS a usar.

Esto es necesario puesto que al contrario del resto de los pines


(SCL,SDO,SDI = RC3,RC5,RC4) para la lnea CS podemos usar cualquier pin
I/O genrico que tengamos libre. El puerto SPI no depende de donde est ni
le importa. Habr situaciones donde no se use u otras donde tengamos
varias lneas al haber ms de un dispositivo. Es por eso por lo que la
declaracin y manejo de dicho lnea debe hacerse dentro de las rutinas del
dispositivo SPI especfico. Sin embargo, como con la mayora de los
dispositivo SPI vamos a necesitar una lnea CS, este puede ser un buen
lugar para explorar como hacerlo y las alternativas posibles.
La forma ms sencilla (aunque como veremos no la ms recomendable) es
hacer un par de #defines indicando el pin que va a ser usado como lnea CS
y su correspondiente bit TRIS. Por ejemplo, para usar el pin RC1 como CS
en C18 usaramos:
// CS line
#define device_CS
LATCbits.LATC1
#define device_CS_dir TRISCbits.TRISC1
#define select_device
device_CS=0
#define deselect_device device_CS=1
De esta forma solo tenemos que aadir la lnea device_CS_dir=0; (output) durante la inicializacin del
dispositivo. Posteriormente,

cada vez que queramos hacer un intercambio SPI

haramos:
select_device;
SPI_talk ...
deselect_device;

El problema de este enfoque viene si en otro montaje nos viene mejor usar
un pin diferente como lnea CS. Si estamos usando nuestro propio cdigo no
hay problemas, cambiamos el #define y recompilamos. El problema es si
estamos escribiendo una librera. En ese caso el usuario final tendra que
estar buceando en nuestro cdigo y cambiando los ficheros fuente de la
librera. Eso en el caso de que disponga de los ficheros fuente. Si solo
publicamos la librera ya compilada forzamos al usuario a usar siempre el
pin RC1 cuando use un dispositivo SPI.
Obviamente esto no es muy conveniente, pero parece ser que en C18 no
hay otra alternativa. De hecho, en el manual de libreras del C18 (pag. 80)
indica que si se desea p.e, usar un LCD con una seleccin de pines diferente
de la asignada por defecto se deben modificar las definiciones del
correspondiente fichero xlcd.h.
Este es un caso donde el compilador de MikroC presenta una ventaja frente
al C18. En MikroC tenemos una solucin ms flexible: se trata de declarar
dos variables (p.e. devicename_CS ydevicename_CS_dir) para informar
al resto de las rutinas de que pin estamos usando como ChipSelect (CS) y
su correspondiente bit de direccin TRIS.
En MikroCPro podemos declarar dichas variables de tipo sbit y asociarlas a
los correspondientes pines:
sbit devicename_CS at LATC.B1;

// Will use RC1 as CS line

sbit devicename_CS_dir at TRISC.B1;

Esto parece muy similar a los #defines de antes. La ventaja es que si


estamos escribiendo una librera, en el fichero fuente de la librera
declararamos las variables devicename_CS, devicename_CS_dircomo
externas, sin asignarles ningn valor en particular. Sera el usuario en su
programa quien diera valores a dichas variables, en funcin de su
configuracin hardware:
// Within library source file
extern sfr sbit devicename_CS;
extern sfr sbit devicename_CS_dir;

En el fichero del usuario


sbit devicename_CS
at LATC.B1;
sbit devicename_CS_dir at TRISC.B1;

// Choose your own pin here

De esta forma el usuario puede elegir el pin a usar para cada montaje o
definir diferentes pines para varios dispositivos, sin tener que modificar la
librera original.
Terminaremos este tutorial usando las rutinas dadas en un programa muy
sencillo que establezca una simple comunicacin SPI entre un PIC (master)
y un perifrico (slave).

Conversor Digital Anagico (DAC) MCP4822:


En la entrada Audio con PWM vimos como era posible usar la salida PWM del
PIC como un conversor DAC de conveniencia. Si se desean mejores
resultados existen por supuesto dispositivos DAC especficos.
Vamos a mostrar como usar uno de estos dispositivos (MCP4822 de
Microchip). ES un conversor DAC de 12 bits (4096 niveles) alimentado con
5V y que cuenta con dos canales independientes de salida.
Lo que nos importa en este caso es que la comunicacin con dicho DAC es a
travs de SPI. Adems se trata de
una comunicacin muy sencilla, perfecta para una primera prueba. El
perifrico recibe un slo tipo de mensaje (de 2 bytes de tamao, donde se
le especifica el voltaje deseado en uno u otro canal) y no enva ninguno.
Es por lo tanto una comunicacin unidireccional, en la que esencialmente el
PIC manda mensajes con los voltajes deseados y no hay que preocuparse
de ningn protocolo de respuestas, validaciones, tiempos de espera, etc.

Hardware:

El DAC MCP4822 es un integrado de 8


pines cuya descripcin se muestra en la figura adjunta. La alimentacin
(Vdd) es a 5V por lo que no tendremos problemas de cambios de nivel con
respecto al PIC. Los pines 2(CS line) ,3 (Clock) y 4 (SDI) se usarn en la
comunicacin SPI. Notad que no hay un pin SDO, al ser la comunicacin
unidireccional. El pin 7 (AVss) es tierra y los pines 8 (OUT_A) y 9 (OUT_B)
corresponden a los dos canales analgicos disponibles. Finalmente el pin 5
(~LDAC) puede usarse para sincronizar la salida de ambos canales.
Nosotros lo pondremos a tierra, lo que hace que el voltaje en la salida se
actualice cuando el valor de la lnea CS vuelve a un valor alto tras acabar la
comunicacin.
La conexin PIC-DAC ser por lo tanto la correspondiente al esquema
siguiente:

Notad que estamos usando RC1 como lnea CS y que no usamos el pin SDI
(RC4) del PIC, puesto que no vamos a recibir ningn dato. La salida de los
canales A y B las conectamos al osciloscopio para ver los resultados.

Software:
El programa es muy sencillo y consiste en poco ms de las funciones que ya
hemos visto.
Lo primero que hay que hacer es determinar el modo SPI con el que trabaja

el perifrico. En su datasheet nos dicen que soporta los modos SPI (0,0) y
(1,1). Del momento de muestreo de los datos de entrada no debemos
preocuparnos porque no existen.
En la docu tambin se nos informa de que el dispositivo acepta hasta 20
MHz de reloj en la comunicacin SPI. En este montaje estoy usando un reloj
de 8 MHz, por lo que puedo usar la frecuencia mxima (Fosc/4 = 2 MHz) y
an as estar muy por debajo de las capacidades del dispositivo.
Una rutina que inicializase la comunicacin SPI con los parmetros
indicados usando las rutinas presentadas antes sera:
void DAC_spi_init(byte clock)
{
DAC_CS_dir=0; deselect_DAC; // Configure CS line as output and set it low.
spi_master(clock);
// Configure SPI as master and set clock
spi_mode(0,0,0);
// SPI mode (0,0). Third arg doesnt matter
spi_enable();
// Enable SPI
}

Respecto a la comunicacin en si, el nico tipo de comando reconocido por


el DAC es un mensaje de 16 bits:

Los 12 bits menos significativos contienen simplemente el voltaje deseado


(un valor de 0 a 4095). De los otros 4 bits, solo 3 tienen una funcin:

El ms significativo (/B) indica el canal donde debe aparecer el


voltaje (0-->A, 1-->B).
El tercero (~GA) nos permite especificar una ganancia. Si es 0
cubrimos el rango completo de voltaje (aprox de 0 a 4V) y si es 1 solo
barremos la mitad (de 0 a 2V).
Finalmente (~SHDW) nos permite poner al dispositivo en un modo
"apagado" (0)
Por lo tanto si usamos el rango completo de voltaje los valores de esos 4
bits sern 0001 si queremos usar el canal A y 1001 si queremos mandar un
voltaje al canal B

Finalmente las especificaciones tambin nos indican que debemos mandar


primero el byte ms significativo. Dado un uin16 (2 bytes) que contenga el
mensaje a mandar, el siguiente macro lo mandara por la lnea:
#define send_msg(msg) { select_DAC; spi_tx(msg>>8); spi_tx(msg&0xFF);
deselect_DAC; }

Primero avisamos al DAC de que se inicia una comunicacin y luego


mandamos el byte ms significativo seguido del menos significativo.
Finalmente devolviendo a nivel alto la lnea CS terminamos la conversacin.
Vamos a escribir un programa que incremente un contador de 0 a 4095 y
mande dicho contador al canal A, por lo que en dicho canal deberamos ver
un voltaje rampa. En el canal B vamos a crear una sinusoide. Para ello
creamos una tabla de 256 valores de la funcin seno cubriendo un periodo.
Los valores estn escalados entre 0 y 255 para que entren en una variable
de tipo uint8 (byte):
uint8 tabla[256]= {
0x80,0x83,0x86,0x89,0x8C,0x8F,0x92,0x95,0x98,0x9B,0x9E,0xA2,0xA5,0xA7,0xAA,0xAD,
0xB0,0xB3,0xB6,0xB9,0xBC,0xBE,0xC1,0xC4,0xC6,0xC9,0xCB,0xCE,0xD0,0xD3,0xD5,0xD7,
0xDA,0xDC,0xDE,0xE0,0xE2,0xE4,0xE6,0xE8,0xEA,0xEB,0xED,0xEE,0xF0,0xF1,0xF3,0xF4,
0xF5,0xF6,0xF8,0xF9,0xFA,0xFA,0xFB,0xFC,0xFD,0xFD,0xFE,0xFE,0xFE,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFE,0xFE,0xFE,0xFD,0xFD,0xFC,0xFB,0xFA,0xFA,0xF9,0xF8,0xF6,
0xF5,0xF4,0xF3,0xF1,0xF0,0xEE,0xED,0xEB,0xEA,0xE8,0xE6,0xE4,0xE2,0xE0,0xDE,0xDC,
0xDA,0xD7,0xD5,0xD3,0xD0,0xCE,0xCB,0xC9,0xC6,0xC4,0xC1,0xBE,0xBC,0xB9,0xB6,0xB3,
0xB0,0xAD,0xAA,0xA7,0xA5,0xA2,0x9E,0x9B,0x98,0x95,0x92,0x8F,0x8C,0x89,0x86,0x83,
0x80,0x7C,0x79,0x76,0x73,0x70,0x6D,0x6A,0x67,0x64,0x61,0x5D,0x5A,0x58,0x55,0x52,
0x4F,0x4C,0x49,0x46,0x43,0x41,0x3E,0x3B,0x39,0x36,0x34,0x31,0x2F,0x2C,0x2A,0x28,
0x25,0x23,0x21,0x1F,0x1D,0x1B,0x19,0x17,0x15,0x14,0x12,0x11,0x0F,0x0E,0x0C,0x0B,
0x0A,0x09,0x07,0x06,0x05,0x05,0x04,0x03,0x02,0x02,0x01,0x01,0x01,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x02,0x02,0x03,0x04,0x05,0x05,0x06,0x07,0x09,
0x0A,0x0B,0x0C,0x0E,0x0F,0x11,0x12,0x14,0x15,0x17,0x19,0x1B,0x1D,0x1F,0x21,0x23,
0x25,0x28,0x2A,0x2C,0x2F,0x31,0x34,0x36,0x39,0x3B,0x3E,0x41,0x43,0x46,0x49,0x4C,
0x4F,0x52,0x55,0x58,0x5A,0x5D,0x61,0x64,0x67,0x6A,0x6D,0x70,0x73,0x76,0x79,0x7C};

Los valores anteriores se han obtenido con el siguiente comando MATLAB:


>> t=2*pi*[0:255]/256; tabla = round(127.5*sin(t)+127.5);
y se representan en la figura adjunta:

A partir de aqu el programa principal queda simplemente:


void main()
{
uint16 d=0;
uint16 msg;
uint16 v;
DAC_spi_init(0); Delay1KTCYx(10); // Configure SPI @ Fosc/4
while(1)
{
msg = d + 0x1000; send_msg(msg); // Value of d to chan A
v = tabla[d&0xFF]; v<<=4;
// v = 16 x value in sin array
msg = v + 0x9000; send_msg(msg); // Value of v to chan B
d++; d&=0x0FFF;

// increment d and makes sure it remains within [0,4095]

}
}

El resultado, visto en el osciloscopio para dos escalas de tiempo, es el


siguiente:

El canal A (arriba) muestra la rampa del contador (de 0 a 4095 y vuelta a


empezar) y el canal B la sinusoide. La sinusoide es ms rpida que la rampa
porque cada ciclo se repite cada 256 valores del contador, en vez de cada
4096. Notad tambin que no estamos usando toda la resolucin del DAC, ya
que para el seno usamos un valor entre 0 y 255 que posteriormente
multiplicamos por 16. Por lo tanto los 4 bits menos significativos del voltaje
est a 0.

En el siguiente tutorial pondremos a prueba nuestras rutinas SPI con un


ejemplo un poco ms complicado de interfaz con un dispositivo SPI, en
particular una memoria flash (M25P80 de 1 Mbyte de capacidad), donde
tendremos comunicaciones en ambos sentidos.